diff options
author | Martin Kiewitz | 2016-01-29 13:13:40 +0100 |
---|---|---|
committer | Martin Kiewitz | 2016-01-29 13:22:22 +0100 |
commit | 8a595e7771aa89d06876e13d7ab6751e26da8982 (patch) | |
tree | dfc61e112c9f7e5b1d9e295fbb60edf4ae1b65f3 | |
parent | 1e73796bd0b17740ca4c35b9a7bd1882f9de6a37 (diff) | |
download | scummvm-rg350-8a595e7771aa89d06876e13d7ab6751e26da8982.tar.gz scummvm-rg350-8a595e7771aa89d06876e13d7ab6751e26da8982.tar.bz2 scummvm-rg350-8a595e7771aa89d06876e13d7ab6751e26da8982.zip |
AGI: graphics rewrite + cleanup
- graphics code fully rewritten
- Apple IIgs font support
- Amiga Topaz support
- Word parser rewritten
- menu code rewritten
- removed forced 2 second delay on all room changes
replaced with heuristic to detect situations, where it's required
- lots of naming cleanup
- new console commands show_map, screenobj, vmvars and vmflags
- all sorts of hacks/workarounds removed
- added SCI wait mouse cursor
- added Apple IIgs mouse cursor
- added Atari ST mouse cursor
- added Amiga/Apple IIgs transition
- added Atari ST transition
- user can select another render mode and
use Apple IIgs palette + transition for PC versions
- inventory screen rewritten
- SetSimple command now properly implemented
- PreAGI Mickey: Sierra logo now shown
- saved games: now saving controller key mapping
also saving automatic save data (SetSimple command)
- fixed invalid memory access when saving games (31 bytes were saved
using Common::String c_ptr()
Special Thanks to:
- fuzzie for helping out with the Apple IIgs font + valgrind
- eriktorbjorn for helping out with valgrind
- LordHoto for figuring out the code, that caused invalid memory
access in the original code, when saving a game
- sev for help out with reversing the Amiga transition
currently missing:
- mouse support for menu
- mouse support for system dialogs
- predictive dialog support
49 files changed, 10174 insertions, 7159 deletions
diff --git a/engines/agi/agi.cpp b/engines/agi/agi.cpp index e907d3835e..19e05959dc 100644 --- a/engines/agi/agi.cpp +++ b/engines/agi/agi.cpp @@ -21,7 +21,6 @@ */ #include "common/md5.h" -#include "common/events.h" #include "common/file.h" #include "common/memstream.h" #include "common/savefile.h" @@ -42,9 +41,13 @@ #include "agi/agi.h" #include "agi/graphics.h" +#include "agi/inv.h" #include "agi/sprite.h" +#include "agi/text.h" #include "agi/keyboard.h" #include "agi/menu.h" +#include "agi/systemui.h" +#include "agi/words.h" #include "gui/predictivedialog.h" @@ -54,241 +57,6 @@ void AgiEngine::allowSynthetic(bool allow) { _allowSynthetic = allow; } -void AgiEngine::processEvents() { - Common::Event event; - int key = 0; - - while (_eventMan->pollEvent(event)) { - switch (event.type) { - case Common::EVENT_PREDICTIVE_DIALOG: { - GUI::PredictiveDialog _predictiveDialog; - _predictiveDialog.runModal(); - strcpy(_predictiveResult, _predictiveDialog.getResult()); - if (strcmp(_predictiveResult, "")) { - if (_game.inputMode == INPUT_NORMAL) { - strcpy((char *)_game.inputBuffer, _predictiveResult); - handleKeys(KEY_ENTER); - } else if (_game.inputMode == INPUT_GETSTRING) { - strcpy(_game.strings[_stringdata.str], _predictiveResult); - newInputMode(INPUT_NORMAL); - _gfx->printCharacter(_stringdata.x + strlen(_game.strings[_stringdata.str]) + 1, - _stringdata.y, ' ', _game.colorFg, _game.colorBg); - } else if (_game.inputMode == INPUT_NONE) { - for (int n = 0; _predictiveResult[n]; n++) - keyEnqueue(_predictiveResult[n]); - } - } - /* - if (predictiveDialog()) { - if (_game.inputMode == INPUT_NORMAL) { - strcpy((char *)_game.inputBuffer, _predictiveResult); - handleKeys(KEY_ENTER); - } else if (_game.inputMode == INPUT_GETSTRING) { - strcpy(_game.strings[_stringdata.str], _predictiveResult); - newInputMode(INPUT_NORMAL); - _gfx->printCharacter(_stringdata.x + strlen(_game.strings[_stringdata.str]) + 1, - _stringdata.y, ' ', _game.colorFg, _game.colorBg); - } else if (_game.inputMode == INPUT_NONE) { - for (int n = 0; _predictiveResult[n]; n++) - keyEnqueue(_predictiveResult[n]); - } - } - */ - } - break; - case Common::EVENT_LBUTTONDOWN: - if (_game.mouseEnabled) { - key = BUTTON_LEFT; - _mouse.button = kAgiMouseButtonLeft; - keyEnqueue(key); - _mouse.x = event.mouse.x; - _mouse.y = event.mouse.y; - } - break; - case Common::EVENT_RBUTTONDOWN: - if (_game.mouseEnabled) { - key = BUTTON_RIGHT; - _mouse.button = kAgiMouseButtonRight; - keyEnqueue(key); - _mouse.x = event.mouse.x; - _mouse.y = event.mouse.y; - } - break; - case Common::EVENT_WHEELUP: - if (_game.mouseEnabled) { - key = WHEEL_UP; - keyEnqueue(key); - } - break; - case Common::EVENT_WHEELDOWN: - if (_game.mouseEnabled) { - key = WHEEL_DOWN; - keyEnqueue(key); - } - break; - case Common::EVENT_MOUSEMOVE: - if (_game.mouseEnabled) { - _mouse.x = event.mouse.x; - _mouse.y = event.mouse.y; - - if (!_game.mouseFence.isEmpty()) { - if (_mouse.x < _game.mouseFence.left) - _mouse.x = _game.mouseFence.left; - if (_mouse.x > _game.mouseFence.right) - _mouse.x = _game.mouseFence.right; - if (_mouse.y < _game.mouseFence.top) - _mouse.y = _game.mouseFence.top; - if (_mouse.y > _game.mouseFence.bottom) - _mouse.y = _game.mouseFence.bottom; - - g_system->warpMouse(_mouse.x, _mouse.y); - } - } - - break; - case Common::EVENT_LBUTTONUP: - case Common::EVENT_RBUTTONUP: - if (_game.mouseEnabled) { - _mouse.button = kAgiMouseButtonUp; - _mouse.x = event.mouse.x; - _mouse.y = event.mouse.y; - } - break; - case Common::EVENT_KEYDOWN: - if (event.kbd.hasFlags(Common::KBD_CTRL) && event.kbd.keycode == Common::KEYCODE_d) { - _console->attach(); - break; - } - - switch (key = event.kbd.keycode) { - case Common::KEYCODE_LEFT: - case Common::KEYCODE_KP4: - if (_allowSynthetic || !event.synthetic) - key = KEY_LEFT; - break; - case Common::KEYCODE_RIGHT: - case Common::KEYCODE_KP6: - if (_allowSynthetic || !event.synthetic) - key = KEY_RIGHT; - break; - case Common::KEYCODE_UP: - case Common::KEYCODE_KP8: - if (_allowSynthetic || !event.synthetic) - key = KEY_UP; - break; - case Common::KEYCODE_DOWN: - case Common::KEYCODE_KP2: - if (_allowSynthetic || !event.synthetic) - key = KEY_DOWN; - break; - case Common::KEYCODE_PAGEUP: - case Common::KEYCODE_KP9: - if (_allowSynthetic || !event.synthetic) - key = KEY_UP_RIGHT; - break; - case Common::KEYCODE_PAGEDOWN: - case Common::KEYCODE_KP3: - if (_allowSynthetic || !event.synthetic) - key = KEY_DOWN_RIGHT; - break; - case Common::KEYCODE_HOME: - case Common::KEYCODE_KP7: - if (_allowSynthetic || !event.synthetic) - key = KEY_UP_LEFT; - break; - case Common::KEYCODE_END: - case Common::KEYCODE_KP1: - if (_allowSynthetic || !event.synthetic) - key = KEY_DOWN_LEFT; - break; - case Common::KEYCODE_KP5: - key = KEY_STATIONARY; - break; - case Common::KEYCODE_PLUS: - key = '+'; - break; - case Common::KEYCODE_MINUS: - key = '-'; - break; - case Common::KEYCODE_TAB: - key = 0x0009; - break; - case Common::KEYCODE_F1: - key = 0x3b00; - break; - case Common::KEYCODE_F2: - key = 0x3c00; - break; - case Common::KEYCODE_F3: - key = 0x3d00; - break; - case Common::KEYCODE_F4: - key = 0x3e00; - break; - case Common::KEYCODE_F5: - key = 0x3f00; - break; - case Common::KEYCODE_F6: - key = 0x4000; - break; - case Common::KEYCODE_F7: - key = 0x4100; - break; - case Common::KEYCODE_F8: - key = 0x4200; - break; - case Common::KEYCODE_F9: - key = 0x4300; - break; - case Common::KEYCODE_F10: - key = 0x4400; - break; - case Common::KEYCODE_F11: - key = KEY_STATUSLN; - break; - case Common::KEYCODE_F12: - key = KEY_PRIORITY; - break; - case Common::KEYCODE_ESCAPE: - key = 0x1b; - break; - case Common::KEYCODE_RETURN: - case Common::KEYCODE_KP_ENTER: - key = KEY_ENTER; - break; - case Common::KEYCODE_BACKSPACE: - key = KEY_BACKSPACE; - break; - default: - // Not a special key, so get the ASCII code for it - key = event.kbd.ascii; - - if (Common::isAlpha(key)) { - // Key is A-Z. - // Map Ctrl-A to 1, Ctrl-B to 2, etc. - if (event.kbd.flags & Common::KBD_CTRL) { - key = toupper(key) - 'A' + 1; - } else if (event.kbd.flags & Common::KBD_ALT) { - // Map Alt-A, Alt-B etc. to special scancode values according to an internal scancode table. - key = scancodeTable[toupper(key) - 'A'] << 8; - } - } - break; - } - if (key) - keyEnqueue(key); - break; - - case Common::EVENT_KEYUP: - if (_egoHoldKey) - _game.viewTable[0].direction = 0; - - default: - break; - } - } -} - void AgiEngine::pollTimer() { _lastTick += 50; @@ -305,24 +73,14 @@ void AgiEngine::pollTimer() { void AgiEngine::pause(uint32 msec) { uint32 endTime = _system->getMillis() + msec; - _gfx->setCursor(_renderMode == Common::kRenderAmiga, true); + _gfx->setMouseCursor(true); // Busy mouse cursor while (_system->getMillis() < endTime) { processEvents(); _system->updateScreen(); _system->delayMillis(10); } - _gfx->setCursor(_renderMode == Common::kRenderAmiga); -} - -void AgiEngine::initPriTable() { - int i, p, y = 0; - - for (p = 1; p < 15; p++) { - for (i = 0; i < 12; i++) { - _game.priTable[y++] = p < 4 ? 4 : p; - } - } + _gfx->setMouseCursor(); // regular mouse cursor } int AgiEngine::agiInit() { @@ -341,7 +99,7 @@ int AgiEngine::agiInit() { _game.vars[i] = 0; // clear all resources and events - for (i = 0; i < MAX_DIRS; i++) { + for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) { memset(&_game.views[i], 0, sizeof(struct AgiView)); memset(&_game.pictures[i], 0, sizeof(struct AgiPicture)); memset(&_game.logics[i], 0, sizeof(struct AgiLogic)); @@ -353,15 +111,17 @@ int AgiEngine::agiInit() { } // clear view table - for (i = 0; i < MAX_VIEWTABLE; i++) - memset(&_game.viewTable[i], 0, sizeof(struct VtEntry)); + for (i = 0; i < SCREENOBJECTS_MAX; i++) + memset(&_game.screenObjTable[i], 0, sizeof(struct ScreenObjEntry)); + + memset(&_game.addToPicView, 0, sizeof(struct ScreenObjEntry)); - initWords(); + _words->clearEgoWords(); if (!_menu) - _menu = new Menu(this, _gfx, _picture); + _menu = new GfxMenu(this, _gfx, _picture, _text); - initPriTable(); + _gfx->initPriorityTable(); // Clear the string buffer on startup, but not when the game restarts, as // some scripts expect that the game strings remain unaffected after a @@ -394,10 +154,6 @@ int AgiEngine::agiInit() { if (getFeatures() & GF_AGDS) _game.gameFlags |= ID_AGDS; - // Make the 256 color AGI screen the default AGI screen when AGI256 or AGI256-2 is used - if (getFeatures() & (GF_AGI256 | GF_AGI256_2)) - _game.sbuf = _game.sbuf256c; - if (_game.gameFlags & ID_AMIGA) debug(1, "Amiga padded game detected."); @@ -413,12 +169,9 @@ int AgiEngine::agiInit() { if (ec == errOK) ec = _loader->loadWords(WORDS); - // FIXME: load IIgs instruments and samples - // load_instruments("kq.sys16"); - // Load logic 0 into memory if (ec == errOK) - ec = _loader->loadResource(rLOGIC, 0); + ec = _loader->loadResource(RESOURCETYPE_LOGIC, 0); #ifdef __DS__ // Normally, the engine loads the predictive text dictionary when the predictive dialog @@ -443,42 +196,42 @@ void AgiEngine::agiUnloadResources() { int i; // Make sure logic 0 is always loaded - for (i = 1; i < MAX_DIRS; i++) { - _loader->unloadResource(rLOGIC, i); + for (i = 1; i < MAX_DIRECTORY_ENTRIES; i++) { + _loader->unloadResource(RESOURCETYPE_LOGIC, i); } - for (i = 0; i < MAX_DIRS; i++) { - _loader->unloadResource(rVIEW, i); - _loader->unloadResource(rPICTURE, i); - _loader->unloadResource(rSOUND, i); + for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) { + _loader->unloadResource(RESOURCETYPE_VIEW, i); + _loader->unloadResource(RESOURCETYPE_PICTURE, i); + _loader->unloadResource(RESOURCETYPE_SOUND, i); } } int AgiEngine::agiDeinit() { int ec; - cleanInput(); // remove all words from memory + _words->clearEgoWords(); // remove all words from memory agiUnloadResources(); // unload resources in memory - _loader->unloadResource(rLOGIC, 0); + _loader->unloadResource(RESOURCETYPE_LOGIC, 0); ec = _loader->deinit(); unloadObjects(); - unloadWords(); + _words->unloadDictionary(); clearImageStack(); return ec; } -int AgiEngine::agiLoadResource(int r, int n) { +int AgiEngine::agiLoadResource(int16 resourceType, int16 resourceNr) { int i; - i = _loader->loadResource(r, n); + i = _loader->loadResource(resourceType, resourceNr); // WORKAROUND: Patches broken picture 147 in a corrupted Amiga version of Gold Rush! (v2.05 1989-03-09). // The picture can be seen in room 147 after dropping through the outhouse's hole in room 146. - if (i == errOK && getGameID() == GID_GOLDRUSH && r == rPICTURE && n == 147 && _game.dirPic[n].len == 1982) { - uint8 *pic = _game.pictures[n].rdata; - Common::MemoryReadStream picStream(pic, _game.dirPic[n].len); - Common::String md5str = Common::computeStreamMD5AsString(picStream, _game.dirPic[n].len); + if (i == errOK && getGameID() == GID_GOLDRUSH && resourceType == RESOURCETYPE_PICTURE && resourceNr == 147 && _game.dirPic[resourceNr].len == 1982) { + uint8 *pic = _game.pictures[resourceNr].rdata; + Common::MemoryReadStream picStream(pic, _game.dirPic[resourceNr].len); + Common::String md5str = Common::computeStreamMD5AsString(picStream, _game.dirPic[resourceNr].len); if (md5str == "1c685eb048656cedcee4eb6eca2cecea") { pic[0x042] = 0x4B; // 0x49 -> 0x4B pic[0x043] = 0x66; // 0x26 -> 0x66 @@ -492,8 +245,8 @@ int AgiEngine::agiLoadResource(int r, int n) { return i; } -int AgiEngine::agiUnloadResource(int r, int n) { - return _loader->unloadResource(r, n); +int AgiEngine::agiUnloadResource(int16 resourceType, int16 resourceNr) { + return _loader->unloadResource(resourceType, resourceNr); } struct GameSettings { @@ -510,7 +263,8 @@ AgiBase::AgiBase(OSystem *syst, const AGIGameDescription *gameDesc) : Engine(sys _rnd = new Common::RandomSource("agi"); _sound = 0; - _fontData = NULL; + _fontData = nullptr; + _fontDataAllocated = nullptr; initFeatures(); initVersion(); @@ -520,30 +274,419 @@ AgiBase::~AgiBase() { delete _rnd; delete _sound; + + if (_fontDataAllocated) { + free(_fontDataAllocated); + } } void AgiBase::initRenderMode() { - _renderMode = Common::kRenderEGA; + Common::Platform platform = Common::parsePlatform(ConfMan.get("platform")); + Common::RenderMode configRenderMode = Common::parseRenderMode(ConfMan.get("render_mode").c_str()); + + // Default to EGA PC rendering + _renderMode = RENDERMODE_EGA; + + switch (platform) { + case Common::kPlatformDOS: + switch (configRenderMode) { + case Common::kRenderCGA: + _renderMode = RENDERMODE_CGA; + break; + // Hercules is not supported atm + //case Common::kRenderHercA: + //case Common::kRenderHercG: + // _renderMode = RENDERMODE_HERCULES; + // break; + default: + break; + } + break; + case Common::kPlatformAmiga: + _renderMode = RENDERMODE_AMIGA; + break; + case Common::kPlatformApple2GS: + _renderMode = RENDERMODE_APPLE_II_GS; + break; + case Common::kPlatformAtariST: + _renderMode = RENDERMODE_ATARI_ST; + break; + default: + break; + } - if (ConfMan.hasKey("platform")) { - Common::Platform platform = Common::parsePlatform(ConfMan.get("platform")); - _renderMode = (platform == Common::kPlatformAmiga) ? Common::kRenderAmiga : Common::kRenderEGA; + // If render mode is explicitly set, force rendermode + switch (configRenderMode) { + case Common::kRenderAmiga: + _renderMode = RENDERMODE_AMIGA; + break; + case Common::kRenderApple2GS: + _renderMode = RENDERMODE_APPLE_II_GS; + break; + case Common::kRenderAtariST: + _renderMode = RENDERMODE_ATARI_ST; + break; + default: + break; } - if (ConfMan.hasKey("render_mode")) { - Common::RenderMode tmpMode = Common::parseRenderMode(ConfMan.get("render_mode").c_str()); - if (tmpMode != Common::kRenderDefault) - _renderMode = tmpMode; + if (getFeatures() & (GF_AGI256 | GF_AGI256_2)) { + // If current game is AGI256, switch (force) to VGA render mode + _renderMode = RENDERMODE_VGA; } } +void AgiBase::initFont() { + // We are currently using the custom font for all fanmade games + if (getFeatures() & (GF_FANMADE | GF_AGDS)) { + // fanmade game, use custom font for now + _fontData = fontData_FanGames; // our (own?) custom font, that supports umlauts etc. + return; + } + + switch (_renderMode) { + case RENDERMODE_AMIGA: + loadFontAmigaPseudoTopaz(); + //_fontData = fontData_Amiga; // use Amiga Topaz font + break; + case RENDERMODE_APPLE_II_GS: + // Special font, stored in file AGIFONT + loadFontAppleIIgs(); + break; + case RENDERMODE_ATARI_ST: + // TODO: Atari ST uses another font + // Seems to be the standard Atari ST 8x8 system font + + case RENDERMODE_CGA: + case RENDERMODE_EGA: + case RENDERMODE_VGA: + switch (getGameID()) { + case GID_MICKEY: + // load mickey mouse font from interpreter file + loadFontMickey(); + break; + default: + break; + } + break; + + default: + break; + } + + if (!_fontData) { + // no font asigned? + switch (getGameID()) { + case GID_MICKEY: + case GID_TROLL: + case GID_WINNIE: + // use IBM font for pre-AGI games as standard + _fontData = fontData_IBM; + break; + default: + // for everything else use Sierra PC font + _fontData = fontData_Sierra; + break; + } + } +} + +// We load the Mickey Mouse font from MICKEY.EXE +void AgiBase::loadFontMickey() { + Common::File interpreterFile; + int32 interpreterFileSize = 0; + byte *fontData = nullptr; + + if (!interpreterFile.open("mickey.exe")) { + // Continue, if file not found + return; + } + + interpreterFileSize = interpreterFile.size(); + if (interpreterFileSize != 55136) { + // unexpected file size + interpreterFile.close(); + warning("mickey.exe unexpected file size"); + return; + } + interpreterFile.seek(32476); // offset of font data + + // allocate space for font bitmap data + fontData = (uint8 *)calloc(256, 8); + _fontData = fontData; + _fontDataAllocated = fontData; + + // read font data, is already in the format that we need (plain bitmap 8x8) + interpreterFile.read(fontData, 256 * 8); + interpreterFile.close(); +} + +// we create a bitmap out of the topaz data used in parallaction (which is normally found in staticres.cpp) +// it's a recreation of the Amiga Topaz font but not really accurate +void AgiBase::loadFontAmigaPseudoTopaz() { + const byte *topazStart = fontData_AmigaPseudoTopaz + 32; + const byte *topazHeader = topazStart + 78; + const byte *topazData = nullptr; + const byte *topazLocations = nullptr; + byte *fontData = nullptr; + uint16 topazHeight = 0; + uint16 topazWidth = 0; + uint16 topazModulo = 0; + uint32 topazDataOffset = 0; + uint32 topazLocationOffset = 0; + byte topazLowChar = 0; + byte topazHighChar = 0; + uint16 topazTotalChars = 0; + uint16 topazBitLength = 0; + uint16 topazBitOffset = 0; + uint16 topazByteOffset = 0; + + // allocate space for font bitmap data + fontData = (uint8 *)calloc(256, 8); + _fontData = fontData; + _fontDataAllocated = fontData; + + topazHeight = READ_BE_UINT16(topazHeader + 0); + topazWidth = READ_BE_UINT16(topazHeader + 4); + + // we expect 8x8 + assert(topazHeight == 8); + assert(topazWidth == 8); + + topazLowChar = topazHeader[12]; + topazHighChar = topazHeader[13]; + topazTotalChars = topazHighChar - topazLowChar + 1; + topazDataOffset = READ_BE_UINT32(topazHeader + 14); + topazModulo = READ_BE_UINT16(topazHeader + 18); + topazLocationOffset = READ_BE_UINT32(topazHeader + 20); + + // Security checks + assert(topazLowChar == ' '); + assert(topazHighChar == 0xFF); + + // copy first 32 characters over + memcpy(fontData, fontData_Sierra, FONT_DISPLAY_WIDTH * 32); + fontData += FONT_DISPLAY_WIDTH * 32; + + // now actually convert from topaz data + topazData = topazStart + topazDataOffset; + topazLocations = topazStart + topazLocationOffset; + + for (uint16 curChar = 0; curChar < topazTotalChars; curChar++) { + topazBitOffset = READ_BE_UINT16(topazLocations + (curChar * 4) + 0); + topazBitLength = READ_BE_UINT16(topazLocations + (curChar * 4) + 2); + + if (topazBitLength == 8) { + assert((topazBitOffset & 7) == 0); + + topazByteOffset = topazBitOffset >> 3; + for (uint16 curHeight = 0; curHeight < topazHeight; curHeight++) { + *fontData = topazData[topazByteOffset]; + fontData++; + topazByteOffset += topazModulo; + } + } else { + memset(fontData, 0, 8); + fontData += 8; + } + } +} + +void AgiBase::loadFontAppleIIgs() { + Common::File fontFile; + uint16 headerIIgs_OffsetMacHeader = 0; + uint16 headerIIgs_Version = 0; + uint16 macRecord_FirstChar = 0; + uint16 macRecord_LastChar = 0; + int16 macRecord_MaxKern = 0; + uint16 macRecord_RectHeight = 0; + uint16 macRecord_StrikeWidth = 0; + uint16 strikeDataLen = 0; + byte *strikeDataPtr = nullptr; + uint16 actualCharacterCount = 0; + uint16 totalCharacterCount = 0; + uint16 *locationTablePtr = nullptr; + uint16 *offsetWidthTablePtr = nullptr; + + uint16 curCharNr = 0; + uint16 curRow = 0; + uint16 curLocation = 0; + uint16 curLocationBytes = 0; + uint16 curLocationBits = 0; + uint16 curCharOffsetWidth = 0; + uint16 curCharOffset = 0; + uint16 curCharWidth = 0; + uint16 curStrikeWidth = 0; + + uint16 curPixelNr = 0; + uint16 curBitMask = 0; + int16 positionAdjust = 0; + byte curByte = 0; + byte fontByte = 0; + + uint16 strikeRowOffset = 0; + uint16 strikeCurOffset = 0; + + byte *fontData = nullptr; + + if (!fontFile.open("agifont")) { + // Continue, + // This also happens when the user selected Apple IIgs as render for the palette for non-AppleIIgs games + warning("could not open agifont for Apple IIgs font"); + return; + } + + // Apple IIgs header + headerIIgs_OffsetMacHeader = fontFile.readUint16LE(); + fontFile.skip(2); // font family + fontFile.skip(2); // font style + fontFile.skip(2); // point size + headerIIgs_Version = fontFile.readUint16LE(); + fontFile.skip(2); // bounds type + // end of Apple IIgs header + // Macintosh font record + fontFile.skip(2); // font type + macRecord_FirstChar = fontFile.readUint16LE(); + macRecord_LastChar = fontFile.readUint16LE(); + fontFile.skip(2); // max width + macRecord_MaxKern = fontFile.readSint16LE(); + fontFile.skip(2); // negative descent + fontFile.skip(2); // rect width + macRecord_RectHeight = fontFile.readUint16LE(); + fontFile.skip(2); // low word ptr table + fontFile.skip(2); // font ascent + fontFile.skip(2); // font descent + fontFile.skip(2); // leading + macRecord_StrikeWidth = fontFile.readUint16LE(); + + // security-checks + if (headerIIgs_OffsetMacHeader != 6) + error("AppleIIgs-font: unexpected header"); + if (headerIIgs_Version != 0x0101) + error("AppleIIgs-font: not a 1.1 font"); + if ((macRecord_FirstChar != 0) || (macRecord_LastChar != 255)) + error("AppleIIgs-font: unexpected characters"); + if (macRecord_RectHeight != 8) + error("AppleIIgs-font: expected 8x8 font"); + + // Calculate table sizes + strikeDataLen = macRecord_StrikeWidth * macRecord_RectHeight * 2; + actualCharacterCount = (macRecord_LastChar - macRecord_FirstChar + 1); + totalCharacterCount = actualCharacterCount + 2; // replacement-char + extra character + + // Allocate memory for tables + strikeDataPtr = (byte *)calloc(strikeDataLen, 1); + locationTablePtr = (uint16 *)calloc(totalCharacterCount, 2); // 1 word per character + offsetWidthTablePtr = (uint16 *)calloc(totalCharacterCount, 2); // ditto + + // read tables + fontFile.read(strikeDataPtr, strikeDataLen); + for (curCharNr = 0; curCharNr < totalCharacterCount; curCharNr++) { + locationTablePtr[curCharNr] = fontFile.readUint16LE(); + } + for (curCharNr = 0; curCharNr < totalCharacterCount; curCharNr++) { + offsetWidthTablePtr[curCharNr] = fontFile.readUint16LE(); + } + fontFile.close(); + + // allocate space for font bitmap data + fontData = (uint8 *)calloc(256, 8); + _fontData = fontData; + _fontDataAllocated = fontData; + + // extract font bitmap data + for (curCharNr = 0; curCharNr < actualCharacterCount; curCharNr++) { + curCharOffsetWidth = offsetWidthTablePtr[curCharNr]; + curLocation = locationTablePtr[curCharNr]; + if (curCharOffsetWidth == 0xFFFF) { + // character does not exist in font, use replacement character instead + curCharOffsetWidth = offsetWidthTablePtr[actualCharacterCount]; + curLocation = locationTablePtr[actualCharacterCount]; + curStrikeWidth = locationTablePtr[actualCharacterCount + 1] - curLocation; + } else { + curStrikeWidth = locationTablePtr[curCharNr + 1] - curLocation; + } + + // Figure out bytes + bits location + curLocationBytes = curLocation >> 3; + curLocationBits = curLocation & 0x0007; + curCharWidth = curCharOffsetWidth & 0x00FF; // isolate width + curCharOffset = curCharOffsetWidth >> 8; // isolate offset + + if (!curCharWidth) { + fontData += 8; // skip over this character + continue; + } + + if (curCharWidth != 8) { + if (curCharNr != 0x3B) + error("AppleIIgs-font: expected 8x8 font"); + } + + // Get all rows of the current character + strikeRowOffset = 0; + for (curRow = 0; curRow < macRecord_RectHeight; curRow++) { + strikeCurOffset = strikeRowOffset + curLocationBytes; + + // Copy over bits + fontByte = 0; + curByte = strikeDataPtr[strikeCurOffset]; + curBitMask = 0x80 >> curLocationBits; + + for (curPixelNr = 0; curPixelNr < curStrikeWidth; curPixelNr++) { + fontByte = fontByte << 1; + if (curByte & curBitMask) { + fontByte |= 0x01; + } + curBitMask = curBitMask >> 1; + if (!curBitMask) { + curByte = strikeDataPtr[strikeCurOffset + 1]; + curBitMask = 0x80; + } + } + + // adjust, so that it's aligned to the left (starting at 0x80 bit) + fontByte = fontByte << (8 - curStrikeWidth); + + // now adjust according to offset + MaxKern + positionAdjust = macRecord_MaxKern + curCharOffset; + + // adjust may be negative for space, or 8 for "empty" characters + if (positionAdjust > 8) + error("AppleIIgs-font: invalid character spacing"); + + if (positionAdjust < 0) { + // negative adjust strangely happens for empty characters like space + if (curStrikeWidth) + error("AppleIIgs-font: invalid character spacing"); + } + + if (positionAdjust > 0) { + // move the amount of pixels to the right + fontByte = fontByte >> positionAdjust; + } + + *fontData = fontByte; + fontData++; + + strikeRowOffset += macRecord_StrikeWidth * 2; + } + } + + free(offsetWidthTablePtr); + free(locationTablePtr); + free(strikeDataPtr); + + // overwrite character 0x1A with the standard Sierra arrow to the right character + // required for the original save/restore dialogs + memcpy(_fontDataAllocated + (0x1A * 8), fontData_ArrowRightCharacter, sizeof(fontData_ArrowRightCharacter)); +} + AgiEngine::AgiEngine(OSystem *syst, const AGIGameDescription *gameDesc) : AgiBase(syst, gameDesc) { // Setup mixer syncSoundSettings(); - parseFeatures(); - DebugMan.addDebugChannel(kDebugLevelMain, "Main", "Generic debug level"); DebugMan.addDebugChannel(kDebugLevelResources, "Resources", "Resources debugging"); DebugMan.addDebugChannel(kDebugLevelSprites, "Sprites", "Sprites debugging"); @@ -561,20 +704,21 @@ AgiEngine::AgiEngine(OSystem *syst, const AGIGameDescription *gameDesc) : AgiBas memset(&_mouse, 0, sizeof(struct Mouse)); _game.mouseEnabled = true; + _game.mouseHidden = false; + // don't check for Amiga, Amiga doesn't allow disabling mouse support. It's mandatory. if (!ConfMan.getBool("mousesupport")) { // we effectively disable the mouse for games, that explicitly do not want mouse support to be enabled _game.mouseEnabled = false; + _game.mouseHidden = true; } - // We are currently using the custom font for all fanmade games - if (!(getFeatures() & (GF_FANMADE | GF_AGDS))) { - _fontData = fontData_Sierra; // original Sierra font - } else { - _fontData = fontData_FanGames; // our (own?) custom font, that supports umlauts etc. - } + _fontData = nullptr; + _fontDataAllocated = nullptr; _game._vm = this; + _game.gfxMode = true; + _game.clockEnabled = false; _game.state = STATE_INIT; @@ -585,17 +729,13 @@ AgiEngine::AgiEngine(OSystem *syst, const AGIGameDescription *gameDesc) : AgiBas _intobj = NULL; - _menu = NULL; - _menuSelected = false; - - _lastSentence[0] = 0; memset(&_stringdata, 0, sizeof(struct StringData)); _objects = NULL; _restartGame = false; - _oldMode = INPUT_NONE; + _oldMode = INPUTMODE_NONE; _firstSlot = 0; @@ -609,16 +749,17 @@ AgiEngine::AgiEngine(OSystem *syst, const AGIGameDescription *gameDesc) : AgiBas _lastTick = 0; memset(_keyQueue, 0, sizeof(_keyQueue)); - memset(_predictiveResult, 0, sizeof(_predictiveResult)); + _text = NULL; _sprites = NULL; _picture = NULL; _loader = NULL; _console = NULL; + _menu = NULL; + _gfx = NULL; + _systemUI = NULL; _egoHoldKey = false; - - } void AgiEngine::initialize() { @@ -657,29 +798,28 @@ void AgiEngine::initialize() { } initRenderMode(); + initFont(); - _buttonStyle = AgiButtonStyle(_renderMode); - _defaultButtonStyle = AgiButtonStyle(); _console = new Console(this); + _words = new Words(this); _gfx = new GfxMgr(this); _sound = new SoundMgr(this, _mixer); _picture = new PictureMgr(this, _gfx); _sprites = new SpritesMgr(this, _gfx); + _text = new TextMgr(this, _words, _gfx); + _systemUI = new SystemUI(this, _gfx, _text); + _inventory = new InventoryMgr(this, _gfx, _text, _systemUI); + + _text->init(_systemUI); _gfx->initMachine(); _game.gameFlags = 0; - _game.colorFg = 15; - _game.colorBg = 0; + _text->charAttrib_Set(15, 0); _game.name[0] = '\0'; - _game.sbufOrig = (uint8 *)calloc(_WIDTH, _HEIGHT * 2); // Allocate space for two AGI screens vertically - _game.sbuf16c = _game.sbufOrig + SBUF16_OFFSET; // Make sbuf16c point to the 16 color (+control line & priority info) AGI screen - _game.sbuf256c = _game.sbufOrig + SBUF256_OFFSET; // Make sbuf256c point to the 256 color AGI screen - _game.sbuf = _game.sbuf16c; // Make sbuf point to the 16 color (+control line & priority info) AGI screen by default - _gfx->initVideo(); _lastSaveTime = 0; @@ -698,6 +838,30 @@ void AgiEngine::initialize() { debugC(2, kDebugLevelMain, "Init sound"); } +bool AgiEngine::promptIsEnabled() { + return _text->promptIsEnabled(); +} + +void AgiEngine::redrawScreen() { + _game.gfxMode = true; // enable graphics mode + _gfx->setPalette(true); // set graphics mode palette + _text->charAttrib_Set(_text->_textAttrib.foreground, _text->_textAttrib.background); + _gfx->clearDisplay(0); + _picture->showPic(); + _text->statusDraw(); + _text->promptRedraw(); +} + +// Adjust a given coordinate to the local game screen +// Used on mouse cursor coordinates before passing them to scripts +void AgiEngine::adjustPosToGameScreen(int16 &x, int16 &y) { + x = x / 2; // 320 -> 160 + y = y - 8; // remove status bar line + if (y >= SCRIPT_HEIGHT) { + y = SCRIPT_HEIGHT + 1; // 1 beyond + } +} + AgiEngine::~AgiEngine() { // If the engine hasn't been initialized yet via // AgiEngine::initialize(), don't attempt to free any resources, as @@ -710,22 +874,25 @@ AgiEngine::~AgiEngine() { agiDeinit(); delete _loader; _gfx->deinitVideo(); + delete _inventory; + delete _systemUI; + delete _text; delete _sprites; delete _picture; - free(_game.sbufOrig); _gfx->deinitMachine(); delete _gfx; + delete _words; delete _console; } Common::Error AgiBase::init() { // Initialize backend - initGraphics(320, 200, false); + //initGraphics(320, 200, false); initialize(); - _gfx->gfxSetPalette(); + _gfx->setPalette(true); return Common::kNoError; } @@ -747,74 +914,68 @@ Common::Error AgiEngine::go() { return Common::kNoError; } -void AgiEngine::parseFeatures() { - - /* FIXME: Seems this method doesn't really do anything. It might - be a leftover that could be removed, except that some of its - intended purpose may still need to be reimplemented. - - [0:29] <Fingolfin> can you tell me what the point behind AgiEngine::parseFeatures() is? - [0:30] <_sev> when games are created with WAGI studio - [0:31] <_sev> it creates .wag site with game-specific features such as full game title, whether to use AGIMOUSE etc - [0:32] <Fingolfin> ... and the "features" config key is created by our detector based on the wag file, I guess? - [0:33] <_sev> yes - [0:33] <Fingolfin> it's just that I cant seem to find a place we do that - [0:33] <_sev> it is used for fallback - [0:34] <_sev> ah, perhaps it was not updated - [0:34] <Fingolfin> I only see us check the value, but never set it - [0:34] <Fingolfin> maybe I am grepping wrong, who knows :) - [0:44] <Fingolfin> _sev: so, unless I miss something, it seem that function does nothing right now - [0:45] <_sev> Fingolfin: it could be unfinished. It was part of GSoC 3 years ago - [0:45] <Fingolfin> well - [0:45] <_sev> I just don't remember - [0:45] <Fingolfin> but don't we just re-parse the wag when the game is loaded anyway? - [0:45] <_sev> but it documents the format - [0:45] <Fingolfin> the advanced meta engine would re-run the detector, wouldn't it? - [0:45] <_sev> yep - [0:47] <Fingolfin> so... shouldn't we at least add a comment to the function explaining what it does and that it's unfinished etc.? maybe add a TODO to the wiki? - [0:47] <Fingolfin> otherwise it might stay as it is for another 3 years :) - */ - - if (!ConfMan.hasKey("features")) - return; +// Scenes that need this: +// +// Manhunter 1: +// - intro text screen (DrawPic) +// - MAD "zooming in..." during intro and other scenes, for example room 124 (NewRoom) +// The NewRoom call is not done during the same cycle as the "zooming in..." print call. +// Space Quest 1: +// - right at the start of the game (NewRoom) +// Space Quest 2 +// - right at the start of the game (NewRoom) +// - after exiting the very first room, a message pops up, that isn't readable without it (NewRoom) + + +// Games, that must not be triggered: +// +// Fanmade Voodoo Girl: +// - waterfall (DrawPic, room 17) +// - inside shop (NewRoom, changes to same room every new button, room 4) + +void AgiEngine::nonBlockingText_IsShown() { + _game.nonBlockingTextShown = true; + _game.nonBlockingTextCyclesLeft = 2; // 1 additional script cycle is counted too +} +void AgiEngine::nonBlockingText_Forget() { + _game.nonBlockingTextShown = false; + _game.nonBlockingTextCyclesLeft = 0; +} +void AgiEngine::nonBlockingText_CycleDone() { + if (_game.nonBlockingTextCyclesLeft) { + _game.nonBlockingTextCyclesLeft--; + + if (!_game.nonBlockingTextCyclesLeft) { + // cycle count expired, we assume that non-blocking text won't be a problem for room / pic change + _game.nonBlockingTextShown = false; + } + } +} - char *features = strdup(ConfMan.get("features").c_str()); - const char *feature[100]; - int numFeatures = 0; +void AgiEngine::loadingTrigger_NewRoom(int16 newRoomNr) { + if (_game.nonBlockingTextShown) { + _game.nonBlockingTextShown = false; - char *tok = strtok(features, " "); - if (tok) { - do { - feature[numFeatures++] = tok; - } while ((tok = strtok(NULL, " ")) != NULL); - } else { - feature[numFeatures++] = features; - } - - const struct Flags { - const char *name; - uint32 flag; - } flags[] = { - { "agimouse", GF_AGIMOUSE }, - { "agds", GF_AGDS }, - { "agi256", GF_AGI256 }, - { "agi256-2", GF_AGI256_2 }, - { "agipal", GF_AGIPAL }, - { 0, 0 } - }; - - for (int i = 0; i < numFeatures; i++) { - for (const Flags *flag = flags; flag->name; flag++) { - if (!scumm_stricmp(feature[i], flag->name)) { - debug(2, "Added feature: %s", flag->name); - - setFeature(flag->flag); - break; + int16 curRoomNr = _game.vars[VM_VAR_CURRENT_ROOM]; + + if (newRoomNr != curRoomNr) { + if (!_game.automaticRestoreGame) { + // wait a bit, we detected non-blocking text + pause(2000); // 2 seconds } } } +} + +void AgiEngine::loadingTrigger_DrawPicture() { + if (_game.nonBlockingTextShown) { + _game.nonBlockingTextShown = false; - free(features); + if (!_game.automaticRestoreGame) { + // wait a bit, we detected non-blocking text + pause(2000); // 2 seconds + } + } } } // End of namespace Agi diff --git a/engines/agi/agi.h b/engines/agi/agi.h index 04e02dcf87..fd44afd910 100644 --- a/engines/agi/agi.h +++ b/engines/agi/agi.h @@ -79,14 +79,18 @@ typedef signed int Err; #define OBJECTS "object" #define WORDS "words.tok" -#define MAX_DIRS 256 +#define MAX_DIRECTORY_ENTRIES 256 +#define MAX_CONTROLLERS 256 #define MAX_VARS 256 #define MAX_FLAGS (256 >> 3) -#define MAX_VIEWTABLE 255 // KQ3 uses o255! +#define SCREENOBJECTS_MAX 255 // KQ3 uses o255! +#define SCREENOBJECTS_EGO_ENTRY 0 // first entry is ego #define MAX_WORDS 20 #define MAX_STRINGS 24 // MAX_STRINGS + 1 used for get.num #define MAX_STRINGLEN 40 -#define MAX_CONTROLLERS 39 +#define MAX_CONTROLLER_KEYMAPPINGS 39 + +#define SAVEDGAME_DESCRIPTION_LEN 30 #define _EMPTY 0xfffff #define EGO_OWNED 0xff @@ -95,13 +99,6 @@ typedef signed int Err; #define CRYPT_KEY_SIERRA "Avis Durgan" #define CRYPT_KEY_AGDS "Alex Simkin" -#define MSG_BOX_COLOR 0x0f // White -#define MSG_BOX_TEXT 0x00 // Black -#define MSG_BOX_LINE 0x04 // Red -#define BUTTON_BORDER 0x00 // Black -#define STATUS_FG 0x00 // Black -#define STATUS_BG 0x0f // White - #define ADD_PIC 1 #define ADD_VIEW 2 @@ -128,7 +125,7 @@ enum AgiGameID { GID_GETOUTTASQ, // Fanmade GID_MICKEY, // PreAGI GID_WINNIE, // PreAGI - GID_TROLL // PreAGI + GID_TROLL // PreAGI }; enum AgiGameType { @@ -143,6 +140,16 @@ enum AgiGameType { BooterDisk2 = 1 }; +enum AgiRenderMode { + RENDERMODE_EGA = 0, + RENDERMODE_CGA = 1, + RENDERMODE_VGA = 2, + RENDERMODE_HERCULES = 3, + RENDERMODE_AMIGA = 4, + RENDERMODE_APPLE_II_GS = 5, + RENDERMODE_ATARI_ST = 6 +}; + // // GF_OLDAMIGAV20 means that the interpreter is an old Amiga AGI interpreter that // uses value 20 for the computer type (v20 i.e. vComputer) rather than the usual value 5. @@ -206,15 +213,17 @@ enum kDebugLevels { * AGI resources. */ enum { - rLOGIC = 1, - rSOUND, - rVIEW, - rPICTURE + RESOURCETYPE_LOGIC = 1, + RESOURCETYPE_SOUND, + RESOURCETYPE_VIEW, + RESOURCETYPE_PICTURE }; enum { - RES_LOADED = 1, - RES_COMPRESSED = 0x40 + RES_LOADED = 0x01, + RES_COMPRESSED = 0x40, + RES_PICTURE_V3_NIBBLE_PARM = 0x80 // Flag that gets set for picture resources, + // which use a nibble instead of a byte as F0+F2 parameters }; enum { @@ -244,44 +253,37 @@ enum AgiMouseButton { kAgiMouseButtonMiddle // Middle mouse button }; -enum GameId { - GID_AGI = 1 -}; - -#define WIN_TO_PIC_X(x) ((x) / 2) -#define WIN_TO_PIC_Y(y) ((y) < 8 ? 999 : (y) >= (8 + _HEIGHT) ? 999 : (y) - 8) - /** * AGI variables. */ enum { - vCurRoom = 0, // 0 - vPrevRoom, - vBorderTouchEgo, - vScore, - vBorderCode, - vBorderTouchObj, // 5 - vEgoDir, - vMaxScore, - vFreePages, - vWordNotFound, - vTimeDelay, // 10 - vSeconds, - vMinutes, - vHours, - vDays, - vJoystickSensitivity, // 15 - vEgoViewResource, - vAgiErrCode, - vAgiErrCodeInfo, - vKey, - vComputer, // 20 - vWindowReset, - vSoundgen, - vVolume, - vMaxInputChars, - vSelItem, // 25 - vMonitor + VM_VAR_CURRENT_ROOM = 0, // 0 + VM_VAR_PREVIOUS_ROOM, // 1 + VM_VAR_BORDER_TOUCH_EGO, // 2 + VM_VAR_SCORE, // 3 + VM_VAR_BORDER_CODE, // 4 + VM_VAR_BORDER_TOUCH_OBJECT, // 5 + VM_VAR_EGO_DIRECTION, // 6 + VM_VAR_MAX_SCORE, // 7 + VM_VAR_FREE_PAGES, // 8 + VM_VAR_WORD_NOT_FOUND, // 9 + VM_VAR_TIME_DELAY, // 10 + VM_VAR_SECONDS, // 11 + VM_VAR_MINUTES, // 12 + VM_VAR_HOURS, // 13 + VM_VAR_DAYS, // 14 + VM_VAR_JOYSTICK_SENSITIVITY, // 15 + VM_VAR_EGO_VIEW_RESOURCE, // 16 + VM_VAR_AGI_ERROR_CODE, // 17 + VM_VAR_AGI_ERROR_INFO, // 18 + VM_VAR_KEY, // 19 + VM_VAR_COMPUTER, // 20 + VM_VAR_WINDOW_RESET, // 21 + VM_VAR_SOUNDGENERATOR, // 22 + VM_VAR_VOLUME, // 23 + VM_VAR_MAX_INPUT_CHARACTERS, // 24 + VM_VAR_SELECTED_INVENTORY_ITEM, // 25 + VM_VAR_MONITOR // 26 }; /** @@ -335,33 +337,28 @@ enum AgiSoundType { * AGI flags */ enum { - fEgoWater = 0, // 0 - fEgoInvisible, - fEnteredCli, - fEgoTouchedP2, - fSaidAcceptedInput, - fNewRoomExec, // 5 - fRestartGame, - fScriptBlocked, - fJoySensitivity, - fSoundOn, - fDebuggerOn, // 10 - fLogicZeroFirsttime, - fRestoreJustRan, - fStatusSelectsItems, - fMenusWork, - fOutputMode, // 15 - fAutoRestart -}; - -enum AgiSlowliness { - kPauseRoom = 1500, - kPausePicture = 500 -}; - -struct AgiController { + VM_FLAG_EGO_WATER = 0, // 0 + VM_FLAG_EGO_INVISIBLE, + VM_FLAG_ENTERED_CLI, + VM_FLAG_EGO_TOUCHED_P2, + VM_FLAG_SAID_ACCEPTED_INPUT, + VM_FLAG_NEW_ROOM_EXEC, // 5 + VM_FLAG_RESTART_GAME, + VM_FLAG_SCRIPT_BLOCKED, + VM_FLAG_JOY_SENSITIVITY, + VM_FLAG_SOUND_ON, + VM_FLAG_DEBUGGER_ON, // 10 + VM_FLAG_LOGIC_ZERO_FIRST_TIME, + VM_FLAG_RESTORE_JUST_RAN, + VM_FLAG_STATUS_SELECTS_ITEMS, + VM_FLAG_MENUS_WORK, + VM_FLAG_OUTPUT_MODE, // 15 + VM_FLAG_AUTO_RESTART +}; + +struct AgiControllerKeyMapping { uint16 keycode; - uint8 controller; + byte controllerSlot; }; struct AgiObject { @@ -369,11 +366,6 @@ struct AgiObject { char *name; }; -struct AgiWord { - int id; - char *word; -}; - struct AgiDir { uint8 volume; uint32 offset; @@ -389,122 +381,9 @@ struct AgiDir { }; struct AgiBlock { - int active; - int x1, y1; - int x2, y2; - uint8 *buffer; // used for window background -}; - -/** AGI text color (Background and foreground color). */ -struct AgiTextColor { - /** Creates an AGI text color. Uses black text on white background by default. */ - AgiTextColor(int fgColor = 0x00, int bgColor = 0x0F) : fg(fgColor), bg(bgColor) {} - - /** Get an AGI text color with swapped foreground and background color. */ - AgiTextColor swap() const { return AgiTextColor(bg, fg); } - - int fg; ///< Foreground color (Used for text). - int bg; ///< Background color (Used for text's background). -}; - -/** - * AGI button style (Amiga or PC). - * - * Supports positive and negative button types (Used with Amiga-style only): - * Positive buttons do what the dialog was opened for. - * Negative buttons cancel what the dialog was opened for. - * Restart-dialog example: Restart-button is positive, Cancel-button negative. - * Paused-dialog example: Continue-button is positive. - */ -struct AgiButtonStyle { -// Public constants etc -public: - static const int - // Amiga colors (Indexes into the Amiga-ish palette) - amigaBlack = 0x00, ///< Accurate, is #000000 (24-bit RGB) - amigaWhite = 0x0F, ///< Practically accurate, is close to #FFFFFF (24-bit RGB) - amigaGreen = 0x02, ///< Quite accurate, should be #008A00 (24-bit RGB) - amigaOrange = 0x0C, ///< Inaccurate, too much blue, should be #FF7500 (24-bit RGB) - amigaPurple = 0x0D, ///< Inaccurate, too much green, should be #FF00FF (24-bit RGB) - amigaRed = 0x04, ///< Quite accurate, should be #BD0000 (24-bit RGB) - amigaCyan = 0x0B, ///< Inaccurate, too much red, should be #00FFDE (24-bit RGB) - // PC colors (Indexes into the EGA-palette) - pcBlack = 0x00, - pcWhite = 0x0F; - -// Public methods -public: - /** - * Get the color of the button with the given state and type using current style. - * - * @param hasFocus True if button has focus, false otherwise. - * @param pressed True if button is being pressed, false otherwise. - * @param positive True if button is positive, false if button is negative. Only matters for Amiga-style buttons. - */ - AgiTextColor getColor(bool hasFocus, bool pressed, bool positive = true) const; - - /** - * Get the color of a button with the given base color and state ignoring current style. - * Swaps foreground and background color when the button has focus or is being pressed. - * - * @param hasFocus True if button has focus, false otherwise. - * @param pressed True if button is being pressed, false otherwise. - * @param baseFgColor Foreground color of the button when it has no focus and is not being pressed. - * @param baseBgColor Background color of the button when it has no focus and is not being pressed. - */ - AgiTextColor getColor(bool hasFocus, bool pressed, int baseFgColor, int baseBgColor) const; - - /** - * Get the color of a button with the given base color and state ignoring current style. - * Swaps foreground and background color when the button has focus or is being pressed. - * - * @param hasFocus True if button has focus, false otherwise. - * @param pressed True if button is being pressed, false otherwise. - * @param baseColor Color of the button when it has no focus and is not being pressed. - */ - AgiTextColor getColor(bool hasFocus, bool pressed, const AgiTextColor &baseColor) const; - - /** - * How many pixels to offset the shown text diagonally down and to the right. - * Currently only used for pressed PC-style buttons. - */ - int getTextOffset(bool hasFocus, bool pressed) const; - - /** - * Show border around the button? - * Currently border is only used for in focus or pressed Amiga-style buttons - * when in inauthentic Amiga-style mode. - */ - bool getBorder(bool hasFocus, bool pressed) const; - - /** - * Set Amiga-button style. - * - * @param amigaStyle Set Amiga-button style if true, otherwise set PC-button style. - * @param olderAgi If true then use older AGI style in Amiga-mode, otherwise use newer. - * @param authenticAmiga If true then don't use a border around buttons in Amiga-mode, otherwise use. - */ - void setAmigaStyle(bool amigaStyle = true, bool olderAgi = false, bool authenticAmiga = false); - - /** - * Set PC-button style. - * @param pcStyle Set PC-button style if true, otherwise set default Amiga-button style. - */ - void setPcStyle(bool pcStyle = true); - -// Public constructors -public: - /** - * Create a button style based on the given rendering mode. - * @param renderMode If Common::kRenderAmiga then creates default Amiga-button style, otherwise PC-style. - */ - AgiButtonStyle(Common::RenderMode renderMode = Common::kRenderDefault); - -// Private member variables -private: - bool _amigaStyle; ///< Use Amiga-style buttons if true, otherwise use PC-style buttons. - bool _olderAgi; ///< Use older AGI style in Amiga-style mode. - bool _authenticAmiga; ///< Don't use border around buttons in Amiga-style mode. + bool active; + int16 x1, y1; + int16 x2, y2; }; struct ScriptPos { @@ -512,18 +391,17 @@ struct ScriptPos { int curIP; }; -enum { - EGO_VIEW_TABLE = 0, - HORIZON = 36, - _WIDTH = 160, - _HEIGHT = 168 +enum InputMode { + INPUTMODE_NONE = 0x04, + INPUTMODE_NORMAL = 0x01 // prompt active }; -enum InputMode { - INPUT_NORMAL = 0x01, - INPUT_GETSTRING = 0x02, - INPUT_MENU = 0x03, - INPUT_NONE = 0x04 +enum CycleInnerLoopType { + CYCLE_INNERLOOP_GETSTRING = 0, + CYCLE_INNERLOOP_GETNUMBER = 1, + CYCLE_INNERLOOP_INVENTORY = 2, + CYCLE_INNERLOOP_MENU = 3, + CYCLE_INNERLOOP_SYSTEMUI_SELECTSAVEDGAMESLOT = 4 }; enum State { @@ -532,12 +410,7 @@ enum State { STATE_RUNNING = 0x02 }; -enum { - SBUF16_OFFSET = 0, - SBUF256_OFFSET = ((_WIDTH) * (_HEIGHT)), - FROM_SBUF16_TO_SBUF256_OFFSET = ((SBUF256_OFFSET) - (SBUF16_OFFSET)), - FROM_SBUF256_TO_SBUF16_OFFSET = ((SBUF16_OFFSET) - (SBUF256_OFFSET)) -}; +typedef Common::Array<int16> SavedGameSlotIdArray; /** * AGI game structure. @@ -563,94 +436,89 @@ struct AgiGame { uint8 vars[MAX_VARS]; /**< 256 variables */ // internal variables - int horizon; /**< horizon y coordinate */ - int lineStatus; /**< line number to put status on */ - int lineUserInput; /**< line to put user input on */ - int lineMinPrint; /**< num lines to print on */ - int cursorPos; /**< column where the input cursor is */ - byte inputBuffer[40]; /**< buffer for user input */ - byte echoBuffer[40]; /**< buffer for echo.line */ + int16 horizon; /**< horizon y coordinate */ + int keypress; + bool cycleInnerLoopActive; + int16 cycleInnerLoopType; + InputMode inputMode; /**< keyboard input mode */ - bool inputEnabled; /**< keyboard input enabled */ + + uint16 specialMenuTriggerKey; /**< key to trigger menu for platforms except PC */ + int lognum; /**< current logic number */ Common::Array<ScriptPos> execStack; // internal flags int playerControl; /**< player is in control */ - int statusLine; /**< status line on/off */ int clockEnabled; /**< clock is on/off */ int exitAllLogics; /**< break cycle after new.room */ - int pictureShown; /**< show.pic has been issued */ + bool pictureShown; /**< show.pic has been issued */ int hasPrompt; /**< input prompt has been printed */ #define ID_AGDS 0x00000001 #define ID_AMIGA 0x00000002 int gameFlags; /**< agi options flags */ - uint8 priTable[_HEIGHT];/**< priority table */ - // windows uint32 msgBoxTicks; /**< timed message box tick counter */ AgiBlock block; - AgiBlock window; - int hasWindow; // graphics & text - int gfxMode; - char cursorChar; - unsigned int colorFg; - unsigned int colorBg; - - uint8 *sbufOrig; /**< Pointer to the 160x336 AGI screen buffer that contains vertically two 160x168 screens (16 color and 256 color). */ - uint8 *sbuf16c; /**< 160x168 16 color (+control line & priority information) AGI screen buffer. Points at sbufOrig + SBUF16_OFFSET. */ - uint8 *sbuf256c; /**< 160x168 256 color AGI screen buffer (For AGI256 and AGI256-2 support). Points at sbufOrig + SBUF256_OFFSET. */ - uint8 *sbuf; /**< Currently chosen AGI screen buffer (sbuf256c if AGI256 or AGI256-2 is used, otherwise sbuf16c). */ - - // player command line - AgiWord egoWords[MAX_WORDS]; - int numEgoWords; + bool gfxMode; unsigned int numObjects; - bool controllerOccured[MAX_DIRS]; /**< keyboard keypress events */ - AgiController controllers[MAX_CONTROLLERS]; + bool controllerOccured[MAX_CONTROLLERS]; /**< keyboard keypress events */ + AgiControllerKeyMapping controllerKeyMapping[MAX_CONTROLLER_KEYMAPPINGS]; char strings[MAX_STRINGS + 1][MAX_STRINGLEN]; /**< strings */ // directory entries for resources - AgiDir dirLogic[MAX_DIRS]; - AgiDir dirPic[MAX_DIRS]; - AgiDir dirView[MAX_DIRS]; - AgiDir dirSound[MAX_DIRS]; + AgiDir dirLogic[MAX_DIRECTORY_ENTRIES]; + AgiDir dirPic[MAX_DIRECTORY_ENTRIES]; + AgiDir dirView[MAX_DIRECTORY_ENTRIES]; + AgiDir dirSound[MAX_DIRECTORY_ENTRIES]; // resources - AgiPicture pictures[MAX_DIRS]; /**< AGI picture resources */ - AgiLogic logics[MAX_DIRS]; /**< AGI logic resources */ - AgiView views[MAX_DIRS]; /**< AGI view resources */ - AgiSound *sounds[MAX_DIRS]; /**< Pointers to AGI sound resources */ + AgiPicture pictures[MAX_DIRECTORY_ENTRIES]; /**< AGI picture resources */ + AgiLogic logics[MAX_DIRECTORY_ENTRIES]; /**< AGI logic resources */ + AgiView views[MAX_DIRECTORY_ENTRIES]; /**< AGI view resources */ + AgiSound *sounds[MAX_DIRECTORY_ENTRIES]; /**< Pointers to AGI sound resources */ AgiLogic *_curLogic; - // words - Common::Array<AgiWord *> words[26]; - // view table - VtEntry viewTable[MAX_VIEWTABLE]; + ScreenObjEntry screenObjTable[SCREENOBJECTS_MAX]; + + ScreenObjEntry addToPicView; int32 ver; /**< detected game version */ - int simpleSave; /**< select simple savegames */ + bool automaticSave; /**< set by CmdSetSimple() */ + char automaticSaveDescription[SAVEDGAME_DESCRIPTION_LEN + 1]; Common::Rect mouseFence; /**< rectangle set by fence.mouse command */ bool mouseEnabled; /**< if mouse is supposed to be active */ + bool mouseHidden; /**< if mouse is currently hidden */ // IF condition handling int testResult; - int max_logics; int logic_list[256]; + + // used to detect situations, where the game shows some text and changes rooms right afterwards + // for example Space Quest 2 intro right at the start + // or Space Quest 2, when entering the vent also right at the start + // The developers assumed that loading the new room would take a bit. + // In ScummVM it's basically done in an instant, which means that + // the text would only get shown for a split second. + // We delay a bit as soon as such situations get detected. + bool nonBlockingTextShown; + int16 nonBlockingTextCyclesLeft; + + bool automaticRestoreGame; }; class AgiLoader { @@ -662,8 +530,8 @@ public: virtual int init() = 0; virtual int deinit() = 0; virtual int detectGame() = 0; - virtual int loadResource(int, int) = 0; - virtual int unloadResource(int, int) = 0; + virtual int loadResource(int16 resourceType, int16 resourceNr) = 0; + virtual int unloadResource(int16 resourceType, int16 resourceNr) = 0; virtual int loadObjects(const char *) = 0; virtual int loadWords(const char *) = 0; }; @@ -684,8 +552,8 @@ public: virtual int init(); virtual int deinit(); virtual int detectGame(); - virtual int loadResource(int, int); - virtual int unloadResource(int, int); + virtual int loadResource(int16 resourceType, int16 resourceNr); + virtual int unloadResource(int16 resourceType, int16 resourceNr); virtual int loadObjects(const char *); virtual int loadWords(const char *); }; @@ -706,8 +574,8 @@ public: virtual int init(); virtual int deinit(); virtual int detectGame(); - virtual int loadResource(int, int); - virtual int unloadResource(int, int); + virtual int loadResource(int16 resourceType, int16 resourceNr); + virtual int unloadResource(int16 resourceType, int16 resourceNr); virtual int loadObjects(const char *); virtual int loadWords(const char *); }; @@ -728,8 +596,8 @@ public: virtual int init(); virtual int deinit(); virtual int detectGame(); - virtual int loadResource(int, int); - virtual int unloadResource(int, int); + virtual int loadResource(int16 resourceType, int16 resourceNr); + virtual int unloadResource(int16 resourceType, int16 resourceNr); virtual int loadObjects(const char *); virtual int loadWords(const char *); }; @@ -737,7 +605,11 @@ public: class GfxMgr; class SpritesMgr; -class Menu; +class InventoryMgr; +class TextMgr; +class GfxMenu; +class SystemUI; +class Words; // Image stack support struct ImageStackElement { @@ -780,15 +652,21 @@ protected: virtual void initialize() = 0; void initRenderMode(); + void initFont(); + + void loadFontMickey(); + void loadFontAmigaPseudoTopaz(); + void loadFontAppleIIgs(); - const uint8 *_fontData; + const uint8 *_fontData; // pointer to the currently used font + uint8 *_fontDataAllocated; public: + Words *_words; + GfxMgr *_gfx; - AgiButtonStyle _defaultButtonStyle; - AgiButtonStyle _buttonStyle; - Common::RenderMode _renderMode; + AgiRenderMode _renderMode; volatile uint32 _clockCount; AgiDebug _debug; AgiGame _game; @@ -800,6 +678,10 @@ public: bool _noSaveLoadAllowed; + virtual bool promptIsEnabled() { + return false; + } + virtual void pollTimer() = 0; virtual int getKeypress() = 0; virtual bool isKeypress() = 0; @@ -844,6 +726,17 @@ public: bool canSaveGameStateCurrently(); const uint8 *getFontData() { return _fontData; }; + + void cycleInnerLoopActive(int16 loopType) { + _game.cycleInnerLoopActive = true; + _game.cycleInnerLoopType = loopType; + }; + void cycleInnerLoopInactive() { + _game.cycleInnerLoopActive = false; + }; + bool cycleInnerLoopIsActive() { + return _game.cycleInnerLoopActive; + } }; typedef void (*AgiCommand)(AgiGame *state, uint8 *p); @@ -861,8 +754,12 @@ public: AgiEngine(OSystem *syst, const AGIGameDescription *gameDesc); virtual ~AgiEngine(); + bool promptIsEnabled(); + Common::Error loadGameState(int slot); - Common::Error saveGameState(int slot, const Common::String &desc); + Common::Error saveGameState(int slot, const Common::String &description); + + void adjustPosToGameScreen(int16 &x, int16 &y); private: uint32 _lastTick; @@ -873,11 +770,9 @@ private: bool _allowSynthetic; - int checkPriority(VtEntry *v); - int checkCollision(VtEntry *v); - int checkPosition(VtEntry *v); - - void parseFeatures(); + bool checkPriority(ScreenObjEntry *v); + bool checkCollision(ScreenObjEntry *v); + bool checkPosition(ScreenObjEntry *v); int _firstSlot; @@ -886,15 +781,16 @@ public: StringData _stringdata; - Common::String getSavegameFilename(int num) const; - void getSavegameDescription(int num, char *buf, bool showEmpty = true); - int selectSlot(); - int saveGame(const Common::String &fileName, const Common::String &saveName); + SavedGameSlotIdArray getSavegameSlotIds(); + Common::String getSavegameFilename(int16 slotId) const; + bool getSavegameInformation(int16 slotId, Common::String &saveDescription, uint32 &saveDate, uint16 &saveTime, bool &saveIsValid); + + int saveGame(const Common::String &fileName, const Common::String &descriptionString); int loadGame(const Common::String &fileName, bool checkId = true); - int saveGameDialog(); - int saveGameSimple(); - int loadGameDialog(); - int loadGameSimple(); + bool saveGameDialog(); + bool saveGameAutomatic(); + bool loadGameDialog(); + bool loadGameAutomatic(); int doSave(int slot, const Common::String &desc); int doLoad(int slot, bool showMessages); int scummVMSaveLoadDialog(bool isSave); @@ -903,14 +799,13 @@ public: InputMode _oldMode; bool _restartGame; - Menu* _menu; - bool _menuSelected; - - char _lastSentence[40]; - SpritesMgr *_sprites; + TextMgr *_text; + InventoryMgr *_inventory; PictureMgr *_picture; AgiLoader *_loader; // loader + GfxMenu *_menu; + SystemUI *_systemUI; Common::Stack<ImageStackElement> _imageStack; @@ -929,8 +824,8 @@ public: int agiInit(); int agiDeinit(); int agiDetectGame(); - int agiLoadResource(int, int); - int agiUnloadResource(int, int); + int agiLoadResource(int16 resourceType, int16 resourceNr); + int agiUnloadResource(int16 resourceType, int16 resourceNr); void agiUnloadResources(); virtual void pollTimer(); @@ -938,35 +833,27 @@ public: virtual bool isKeypress(); virtual void clearKeyQueue(); - void initPriTable(); - void newInputMode(InputMode mode); void oldInputMode(); - int getvar(int); - void setvar(int, int); + int getVar(int16 varNr); + void setVar(int16 varNr, int); void decrypt(uint8 *mem, int len); void releaseSprites(); int mainCycle(bool onlyCheckForEvents = false); int viewPictures(); int runGame(); - void inventory(); void updateTimer(); int getAppDir(char *appDir, unsigned int size); int setupV2Game(int ver); int setupV3Game(int ver); - void newRoom(int n); + void newRoom(int16 newRoomNr); void resetControllers(); void interpretCycle(); int playGame(); - void printItem(int n, int fg, int bg); - int findItem(); - int showItems(); - void selectItems(int n); - void allowSynthetic(bool); void processEvents(); void checkQuickLoad(); @@ -977,9 +864,9 @@ public: int loadObjects(const char *fname); int loadObjects(Common::File &fp); void unloadObjects(); - const char *objectName(unsigned int); - int objectGetLocation(unsigned int); - void objectSetLocation(unsigned int, int); + const char *objectName(uint16 objectNr); + int objectGetLocation(uint16 objectNr); + void objectSetLocation(uint16 objectNr, int); private: int decodeObjects(uint8 *mem, uint32 flen); int readObjects(Common::File &fp, int flen); @@ -987,8 +874,8 @@ private: // Logic public: - int decodeLogic(int); - void unloadLogic(int); + int decodeLogic(int16 logicNr); + void unloadLogic(int16 logicNr); int runLogic(int); void debugConsole(int, int, const char *); int testIfCode(int); @@ -1010,93 +897,72 @@ public: // View private: - void lSetCel(VtEntry *v, int n); - void lSetLoop(VtEntry *v, int n); - void updateView(VtEntry *v); + void lSetLoop(ScreenObjEntry *screenObj, int16 loopNr); + void updateView(ScreenObjEntry *screenObj); public: + void setView(ScreenObjEntry *screenObj, int16 viewNr); + void setLoop(ScreenObjEntry *screenObj, int16 loopNr); + void setCel(ScreenObjEntry *screenObj, int16 celNr); - void setCel(VtEntry *, int); - void clipViewCoordinates(VtEntry *v); - void setLoop(VtEntry *, int); - void setView(VtEntry *, int); - void startUpdate(VtEntry *); - void stopUpdate(VtEntry *); - void updateViewtable(); - void unloadView(int); - int decodeView(int); - void addToPic(int, int, int, int, int, int, int); - void drawObj(int); - bool isEgoView(const VtEntry *v); + void clipViewCoordinates(ScreenObjEntry *screenObj); + + void startUpdate(ScreenObjEntry *); + void stopUpdate(ScreenObjEntry *); + void updateScreenObjTable(); + void unloadView(int16 viewNr); + int decodeView(byte *resourceData, uint16 resourceSize, int16 viewNr); + +private: + void unpackViewCelData(AgiViewCel *celData, byte *compressedData, uint16 compressedSize); + void unpackViewCelDataAGI256(AgiViewCel *celData, byte *compressedData, uint16 compressedSize); - // Words public: - int showWords(); - int loadWords(const char *); - int loadWords_v1(Common::File &f); - void unloadWords(); - int findWord(const char *word, int *flen); - void dictionaryWords(char *); + void addToPic(int, int, int, int, int, int, int); + void drawObj(int); + bool isEgoView(const ScreenObjEntry *screenObj); // Motion private: int checkStep(int delta, int step); - int checkBlock(int x, int y); - void changePos(VtEntry *v); - void motionWander(VtEntry *v); - void motionFollowEgo(VtEntry *v); - void motionMoveObj(VtEntry *v); - void checkMotion(VtEntry *v); + bool checkBlock(int16 x, int16 y); + void changePos(ScreenObjEntry *screenObj); + void motionWander(ScreenObjEntry *screenObj); + void motionFollowEgo(ScreenObjEntry *screenObj); + void motionMoveObj(ScreenObjEntry *screenObj); + void motionMoveObjStop(ScreenObjEntry *screenObj); + void checkMotion(ScreenObjEntry *screenObj); public: void checkAllMotions(); - void moveObj(VtEntry *); - void inDestination(VtEntry *); - void fixPosition(int); + void moveObj(ScreenObjEntry *screenObj); + void inDestination(ScreenObjEntry *screenObj); + void fixPosition(int16 screenObjNr); + void fixPosition(ScreenObjEntry *screenObj); void updatePosition(); - int getDirection(int x0, int y0, int x, int y, int s); + int getDirection(int16 objX, int16 objY, int16 destX, int16 destY, int16 stepSize); bool _egoHoldKey; // Keyboard - void initWords(); - void cleanInput(); int doPollKeyboard(); void cleanKeyboard(); - void handleKeys(int); - void handleGetstring(int); - int handleController(int); - void getString(int, int, int, int); + + int16 getSpecialMenuControllerSlot(); + bool handleController(uint16 key); uint16 agiGetKeypress(); int waitKey(); int waitAnyKey(); - // Text -public: - int messageBox(const char *); - int selectionBox(const char *, const char **); - void closeWindow(); - void drawWindow(int, int, int, int); - void printText(const char *, int, int, int, int, int, int, bool checkerboard = false); - void printTextConsole(const char *, int, int, int, int, int); - int print(const char *, int, int, int); - char *wordWrapString(const char *, int *); - char *agiSprintf(const char *); - void writeStatus(); - void writePrompt(); - void clearPrompt(bool useBlackBg = false); - void clearLines(int, int, int); - void flushLines(int, int); + void nonBlockingText_IsShown(); + void nonBlockingText_Forget(); + void nonBlockingText_CycleDone(); -private: - void printStatus(const char *message, ...) GCC_PRINTF(2, 3); - void printText2(int l, const char *msg, int foff, int xoff, int yoff, int len, int fg, int bg, bool checkerboard = false); - void blitTextbox(const char *p, int y, int x, int len); - void eraseTextbox(); - bool matchWord(); + void loadingTrigger_NewRoom(int16 newRoomNr); + void loadingTrigger_DrawPicture(); public: - char _predictiveResult[40]; + void redrawScreen(); private: AgiCommand _agiCommands[183]; diff --git a/engines/agi/checks.cpp b/engines/agi/checks.cpp index e61146e901..8399c9834a 100644 --- a/engines/agi/checks.cpp +++ b/engines/agi/checks.cpp @@ -21,159 +21,156 @@ */ #include "agi/agi.h" +#include "agi/graphics.h" namespace Agi { -int AgiEngine::checkPosition(VtEntry *v) { - debugC(4, kDebugLevelSprites, "check position @ %d, %d", v->xPos, v->yPos); +bool AgiEngine::checkPosition(ScreenObjEntry *screenObj) { + bool result = true; // position is fine + debugC(4, kDebugLevelSprites, "check position @ %d, %d", screenObj->xPos, screenObj->yPos); - if (v->xPos < 0 || - v->xPos + v->xSize > _WIDTH || - v->yPos - v->ySize + 1 < 0 || - v->yPos >= _HEIGHT || - ((~v->flags & fIgnoreHorizon) && v->yPos <= _game.horizon)) { - debugC(4, kDebugLevelSprites, "check position failed: x=%d, y=%d, h=%d, w=%d", - v->xPos, v->yPos, v->xSize, v->ySize); - return 0; - } + do { + if (screenObj->xPos < 0) { + result = false; + break; + } + if (screenObj->xPos + screenObj->xSize > SCRIPT_WIDTH) { + result = false; + break; + } + if (screenObj->yPos - screenObj->ySize < -1) { + result = false; + break; + } + if (screenObj->yPos >= SCRIPT_HEIGHT) { + result = false; + break; + } + if (((!(screenObj->flags & fIgnoreHorizon)) && screenObj->yPos <= _game.horizon)) { + result = false; + break; + } + } while (0); // MH1 needs this, but it breaks LSL1 - if (getVersion() >= 0x3000) { - if (v->yPos < v->ySize) - return 0; - } +// TODO: *NOT* in disassembly of AGI3 .149, why was this needed? +// if (getVersion() >= 0x3000) { +// if (screenObj->yPos < screenObj->ySize) +// result = false; +// } - return 1; + if (!result) { + debugC(4, kDebugLevelSprites, "check position failed: x=%d, y=%d, h=%d, w=%d", + screenObj->xPos, screenObj->yPos, screenObj->xSize, screenObj->ySize); + } + return result; } /** * Check if there's another object on the way */ -int AgiEngine::checkCollision(VtEntry *v) { - VtEntry *u; +bool AgiEngine::checkCollision(ScreenObjEntry *screenObj) { + ScreenObjEntry *checkObj; - if (v->flags & fIgnoreObjects) - return 0; + if (screenObj->flags & fIgnoreObjects) + return false; - for (u = _game.viewTable; u < &_game.viewTable[MAX_VIEWTABLE]; u++) { - if ((u->flags & (fAnimated | fDrawn)) != (fAnimated | fDrawn)) + for (checkObj = _game.screenObjTable; checkObj < &_game.screenObjTable[SCREENOBJECTS_MAX]; checkObj++) { + if ((checkObj->flags & (fAnimated | fDrawn)) != (fAnimated | fDrawn)) continue; - if (u->flags & fIgnoreObjects) + if (checkObj->flags & fIgnoreObjects) continue; // Same object, check next - if (v->entry == u->entry) + if (screenObj->objectNr == checkObj->objectNr) continue; // No horizontal overlap, check next - if (v->xPos + v->xSize < u->xPos || v->xPos > u->xPos + u->xSize) + if (screenObj->xPos + screenObj->xSize < checkObj->xPos || screenObj->xPos > checkObj->xPos + checkObj->xSize) continue; // Same y, return error! - if (v->yPos == u->yPos) { - debugC(4, kDebugLevelSprites, "check returns 1 (object %d)", v->entry); - return 1; + if (screenObj->yPos == checkObj->yPos) { + debugC(4, kDebugLevelSprites, "check returns 1 (object %d)", screenObj->objectNr); + return true; } // Crossed the baseline, return error! - if ((v->yPos > u->yPos && v->yPos2 < u->yPos2) || - (v->yPos < u->yPos && v->yPos2 > u->yPos2)) { - debugC(4, kDebugLevelSprites, "check returns 1 (object %d)", v->entry); - return 1; + if ((screenObj->yPos > checkObj->yPos && screenObj->yPos_prev < checkObj->yPos_prev) || + (screenObj->yPos < checkObj->yPos && screenObj->yPos_prev > checkObj->yPos_prev)) { + debugC(4, kDebugLevelSprites, "check returns 1 (object %d)", screenObj->objectNr); + return true; } } - - return 0; - + + return false; } -int AgiEngine::checkPriority(VtEntry *v) { - int i, trigger, water, pass, pri; - uint8 *p0; +bool AgiEngine::checkPriority(ScreenObjEntry *screenObj) { + bool touchedWater = false; + bool touchedTrigger = false; + bool touchedControl = true; + int16 curX; + int16 curY; + int16 celX; + byte screenPriority = 0; - if (~v->flags & fFixedPriority) { + if (!(screenObj->flags & fFixedPriority)) { // Priority bands - v->priority = _game.priTable[v->yPos]; - } - - trigger = 0; - water = 0; - pass = 1; - - if (v->priority == 0x0f) { - // Check ego - if (v->entry == 0) { - setflag(fEgoTouchedP2, trigger ? true : false); - setflag(fEgoWater, water ? true : false); - } - - return pass; - } - - water = 1; - - // Check if any picture is loaded before checking for priority below. - // If no picture has been loaded, the priority buffer won't be initialized, - // thus the check below will always fail. This case causes an infinite loop - // in the fanmade game Nick's Quest (bug #3451122), as the game attempts to - // draw a sprite (view 4, floating Nick) before it loads any picture. This - // causes the checks below to always fail, and the engine keeps readjusting - // the sprite's position in fixPosition() forever, as there is no valid - // position to place it (the default visual and priority screen is set to - // zero, i.e. unconditional black). To remedy this situation, we always - // return true here if no picture has been loaded and no priority screen - // has been set up. - if (!_game._vm->_picture->isPictureLoaded()) { - warning("checkPriority: no picture loaded"); - return pass; + screenObj->priority = _gfx->priorityFromY(screenObj->yPos); } - p0 = &_game.sbuf16c[v->xPos + v->yPos * _WIDTH]; - - for (i = 0; i < v->xSize; i++, p0++) { - pri = *p0 >> 4; + if (screenObj->priority != 0x0f) { - if (pri == 0) { // unconditional black. no go at all! - pass = 0; - break; - } + touchedWater = true; - if (pri == 3) // water surface - continue; + curX = screenObj->xPos; + curY = screenObj->yPos; - water = 0; + for (celX = 0; celX < screenObj->xSize; celX++, curX++) { + screenPriority = _gfx->getPriority(curX, curY); - if (pri == 1) { // conditional blue - if (v->flags & fIgnoreBlocks) - continue; + if (screenPriority == 0) { // unconditional black. no go at all! + touchedControl = 0; + break; + } - debugC(4, kDebugLevelSprites, "Blocks observed!"); - pass = 0; - break; + if (screenPriority != 3) { // not water surface + touchedWater = false; + + if (screenPriority == 1) { // conditional blue + if (!(screenObj->flags & fIgnoreBlocks)) { + debugC(4, kDebugLevelSprites, "Blocks observed!"); + touchedControl = false; + break; + } + } else if (screenPriority == 2) { + debugC(4, kDebugLevelSprites, "stepped on trigger"); + if (!_debug.ignoretriggers) + touchedTrigger = true; + } + } } - if (pri == 2) { // trigger - debugC(4, kDebugLevelSprites, "stepped on trigger"); - if (!_debug.ignoretriggers) - trigger = 1; + if (touchedControl) { + if (!touchedWater) { + if (screenObj->flags & fOnWater) + touchedControl = false; + } else { + if (screenObj->flags & fOnLand) + touchedControl = false; + } } } - if (pass) { - if (!water && v->flags & fOnWater) - pass = 0; - if (water && v->flags & fOnLand) - pass = 0; - } - // Check ego - if (v->entry == 0) { - setflag(fEgoTouchedP2, trigger ? true : false); - setflag(fEgoWater, water ? true : false); + if (screenObj->objectNr == 0) { + setflag(VM_FLAG_EGO_TOUCHED_P2, touchedTrigger ? true : false); + setflag(VM_FLAG_EGO_WATER, touchedWater ? true : false); } - return pass; + return touchedControl; } /* @@ -188,91 +185,106 @@ int AgiEngine::checkPriority(VtEntry *v) { * rules, otherwise the previous position will be kept. */ void AgiEngine::updatePosition() { - VtEntry *v; + ScreenObjEntry *screenObj; int x, y, oldX, oldY, border; - _game.vars[vBorderCode] = 0; - _game.vars[vBorderTouchEgo] = 0; - _game.vars[vBorderTouchObj] = 0; + _game.vars[VM_VAR_BORDER_CODE] = 0; + _game.vars[VM_VAR_BORDER_TOUCH_EGO] = 0; + _game.vars[VM_VAR_BORDER_TOUCH_OBJECT] = 0; - for (v = _game.viewTable; v < &_game.viewTable[MAX_VIEWTABLE]; v++) { - if ((v->flags & (fAnimated | fUpdate | fDrawn)) != (fAnimated | fUpdate | fDrawn)) { + for (screenObj = _game.screenObjTable; screenObj < &_game.screenObjTable[SCREENOBJECTS_MAX]; screenObj++) { + if ((screenObj->flags & (fAnimated | fUpdate | fDrawn)) != (fAnimated | fUpdate | fDrawn)) { continue; } - if (v->stepTimeCount != 0) { - if (--v->stepTimeCount != 0) - continue; + if (screenObj->stepTimeCount > 1) { + screenObj->stepTimeCount--; + continue; } - v->stepTimeCount = v->stepTime; + screenObj->stepTimeCount = screenObj->stepTime; - x = oldX = v->xPos; - y = oldY = v->yPos; + x = oldX = screenObj->xPos; + y = oldY = screenObj->yPos; // If object has moved, update its position - if (~v->flags & fUpdatePos) { + if (!(screenObj->flags & fUpdatePos)) { int dx[9] = { 0, 0, 1, 1, 1, 0, -1, -1, -1 }; int dy[9] = { 0, -1, -1, 0, 1, 1, 1, 0, -1 }; - x += v->stepSize * dx[v->direction]; - y += v->stepSize * dy[v->direction]; + x += screenObj->stepSize * dx[screenObj->direction]; + y += screenObj->stepSize * dy[screenObj->direction]; } // Now check if it touched the borders border = 0; // Check left/right borders - if (x < 0) { - x = 0; - border = 4; - } else if (x <= 0 && getVersion() == 0x3086) { // KQ4 - x = 0; // See Sarien bug #590462 - border = 4; - } else if (v->entry == 0 && x == 0 && v->flags & fAdjEgoXY) { - // Extra test to walk west clicking the mouse - x = 0; - border = 4; - } else if (x + v->xSize > _WIDTH) { - x = _WIDTH - v->xSize; - border = 2; + if (getVersion() == 0x3086) { + // KQ4 interpreter does a different comparison on x + // The interpreter before (2.917) and after that (3.098) don't do them that way + // This difference is required for at least Sarien bug #192 + // KQ4: room 135, hen moves from the center of the screen to the left border, + // but it doesn't disappear. + if (x <= 0) { + x = 0; + border = 4; + } + } else { + // regular comparison + if (x < 0) { + x = 0; + border = 4; + } + } + +// } else if (v->entry == 0 && x == 0 && v->flags & fAdjEgoXY) { // should not be required +// // Extra test to walk west clicking the mouse +// x = 0; +// border = 4; + + if (!border) { + if (x + screenObj->xSize > SCRIPT_WIDTH) { + x = SCRIPT_WIDTH - screenObj->xSize; + border = 2; + } } // Check top/bottom borders. - if (y - v->ySize + 1 < 0) { - y = v->ySize - 1; + if (y - screenObj->ySize < -1) { + y = screenObj->ySize - 1; border = 1; - } else if (y > _HEIGHT - 1) { - y = _HEIGHT - 1; + } else if (y > SCRIPT_HEIGHT - 1) { + y = SCRIPT_HEIGHT - 1; border = 3; - } else if ((~v->flags & fIgnoreHorizon) && y <= _game.horizon) { + } else if ((!(screenObj->flags & fIgnoreHorizon)) && y <= _game.horizon) { debugC(4, kDebugLevelSprites, "y = %d, horizon = %d", y, _game.horizon); y = _game.horizon + 1; border = 1; } // Test new position. rollback if test fails - v->xPos = x; - v->yPos = y; - if (checkCollision(v) || !checkPriority(v)) { - v->xPos = oldX; - v->yPos = oldY; + screenObj->xPos = x; + screenObj->yPos = y; + if (checkCollision(screenObj) || !checkPriority(screenObj)) { + screenObj->xPos = oldX; + screenObj->yPos = oldY; border = 0; - fixPosition(v->entry); + fixPosition(screenObj->objectNr); } - if (border != 0) { - if (isEgoView(v)) { - _game.vars[vBorderTouchEgo] = border; + if (border) { + if (isEgoView(screenObj)) { + _game.vars[VM_VAR_BORDER_TOUCH_EGO] = border; } else { - _game.vars[vBorderCode] = v->entry; - _game.vars[vBorderTouchObj] = border; + _game.vars[VM_VAR_BORDER_CODE] = screenObj->objectNr; + _game.vars[VM_VAR_BORDER_TOUCH_OBJECT] = border; } - if (v->motion == kMotionMoveObj) { - inDestination(v); + if (screenObj->motionType == kMotionMoveObj) { // ANGEPASST + motionMoveObjStop(screenObj); } } - v->flags &= ~fUpdatePos; + screenObj->flags &= ~fUpdatePos; } } @@ -285,42 +297,46 @@ void AgiEngine::updatePosition() { * * @param n view table entry number */ -void AgiEngine::fixPosition(int n) { - VtEntry *v = &_game.viewTable[n]; +void AgiEngine::fixPosition(int16 screenObjNr) { + ScreenObjEntry *screenObj = &_game.screenObjTable[screenObjNr]; + fixPosition(screenObj); +} + +void AgiEngine::fixPosition(ScreenObjEntry *screenObj) { int count, dir, size; - debugC(4, kDebugLevelSprites, "adjusting view table entry #%d (%d,%d)", n, v->xPos, v->yPos); + debugC(4, kDebugLevelSprites, "adjusting view table entry #%d (%d,%d)", screenObj->objectNr, screenObj->xPos, screenObj->yPos); // test horizon - if ((~v->flags & fIgnoreHorizon) && v->yPos <= _game.horizon) - v->yPos = _game.horizon + 1; + if ((!(screenObj->flags & fIgnoreHorizon)) && screenObj->yPos <= _game.horizon) + screenObj->yPos = _game.horizon + 1; dir = 0; count = size = 1; - while (!checkPosition(v) || checkCollision(v) || !checkPriority(v)) { + while (!checkPosition(screenObj) || checkCollision(screenObj) || !checkPriority(screenObj)) { switch (dir) { case 0: // west - v->xPos--; + screenObj->xPos--; if (--count) continue; dir = 1; break; case 1: // south - v->yPos++; + screenObj->yPos++; if (--count) continue; dir = 2; size++; break; case 2: // east - v->xPos++; + screenObj->xPos++; if (--count) continue; dir = 3; break; case 3: // north - v->yPos--; + screenObj->yPos--; if (--count) continue; dir = 0; @@ -331,7 +347,7 @@ void AgiEngine::fixPosition(int n) { count = size; } - debugC(4, kDebugLevelSprites, "view table entry #%d position adjusted to (%d,%d)", n, v->xPos, v->yPos); + debugC(4, kDebugLevelSprites, "view table entry #%d position adjusted to (%d,%d)", screenObj->objectNr, screenObj->xPos, screenObj->yPos); } } // End of namespace Agi diff --git a/engines/agi/console.cpp b/engines/agi/console.cpp index 89838b3be9..d8ecb2a0d6 100644 --- a/engines/agi/console.cpp +++ b/engines/agi/console.cpp @@ -22,6 +22,7 @@ #include "agi/agi.h" #include "agi/opcodes.h" +#include "agi/graphics.h" #include "agi/preagi.h" #include "agi/preagi_mickey.h" @@ -49,6 +50,10 @@ Console::Console(AgiEngine *vm) : GUI::Debugger() { registerCmd("setobj", WRAP_METHOD(Console, Cmd_SetObj)); registerCmd("room", WRAP_METHOD(Console, Cmd_Room)); registerCmd("bt", WRAP_METHOD(Console, Cmd_BT)); + registerCmd("show_map", WRAP_METHOD(Console, Cmd_ShowMap)); + registerCmd("screenobj", WRAP_METHOD(Console, Cmd_ScreenObj)); + registerCmd("vmvars", WRAP_METHOD(Console, Cmd_VmVars)); + registerCmd("vmflags", WRAP_METHOD(Console, Cmd_VmFlags)); } bool Console::Cmd_SetVar(int argc, const char **argv) { @@ -58,7 +63,7 @@ bool Console::Cmd_SetVar(int argc, const char **argv) { } int p1 = (int)atoi(argv[1]); int p2 = (int)atoi(argv[2]); - _vm->setvar(p1, p2); + _vm->setVar(p1, p2); return true; } @@ -158,13 +163,13 @@ bool Console::Cmd_Version(int argc, const char **argv) { // We do this by scanning through all script texts // This is the best we can do about it. There is no special location for the game version number. // There are multiple variations, like "ver. X.XX", "ver X.XX" and even "verion X.XX". - for (scriptNr = 0; scriptNr < MAX_DIRS; scriptNr++) { + for (scriptNr = 0; scriptNr < MAX_DIRECTORY_ENTRIES; scriptNr++) { if (game->dirLogic[scriptNr].offset != _EMPTY) { // Script is supposed to exist? scriptLoadedByUs = false; if (!(game->dirLogic[scriptNr].flags & RES_LOADED)) { // But not currently loaded? -> load it now - if (_vm->agiLoadResource(rLOGIC, scriptNr) != errOK) { + if (_vm->agiLoadResource(RESOURCETYPE_LOGIC, scriptNr) != errOK) { // In case we can't load the source, skip it continue; } @@ -263,7 +268,7 @@ bool Console::Cmd_Version(int argc, const char **argv) { } if (scriptLoadedByUs) { - _vm->agiUnloadResource(rLOGIC, scriptNr); + _vm->agiUnloadResource(RESOURCETYPE_LOGIC, scriptNr); } } } @@ -298,7 +303,7 @@ bool Console::Cmd_Vars(int argc, const char **argv) { for (i = 0; i < 255;) { for (j = 0; j < 5; j++, i++) { - debugPrintf("%03d:%3d ", i, _vm->getvar(i)); + debugPrintf("%03d:%3d ", i, _vm->getVar(i)); } debugPrintf("\n"); } @@ -380,7 +385,7 @@ bool Console::Cmd_Room(int argc, const char **argv) { _vm->newRoom(strtoul(argv[1], NULL, 0)); } - debugPrintf("Current room: %d\n", _vm->getvar(0)); + debugPrintf("Current room: %d\n", _vm->getVar(0)); return true; } @@ -412,6 +417,182 @@ bool Console::Cmd_BT(int argc, const char **argv) { return true; } +bool Console::Cmd_ShowMap(int argc, const char **argv) { + if (argc != 2) { + debugPrintf("Switches to one of the following screen maps\n"); + debugPrintf("Usage: %s <screen map>\n", argv[0]); + debugPrintf("Screen maps:\n"); + debugPrintf("- 0: visual map\n"); + debugPrintf("- 1: priority map\n"); + return true; + } + + int map = atoi(argv[1]); + + switch (map) { + case 0: + case 1: + _vm->_gfx->debugShowMap(map); + break; + + default: + debugPrintf("Map %d is not available.\n", map); + return true; + } + return cmdExit(0, 0); +} + +bool Console::Cmd_ScreenObj(int argc, const char **argv) { + if (argc != 2) { + debugPrintf("Shows information about a specific screen object\n"); + debugPrintf("Usage: %s <screenobj number>\n", argv[0]); + return true; + } + + int16 screenObjNr = atoi(argv[1]); + + if ((screenObjNr >= 0) && (screenObjNr < SCREENOBJECTS_MAX)) { + ScreenObjEntry *screenObj = &_vm->_game.screenObjTable[screenObjNr]; + + debugPrintf("Screen Object ID %d\n", screenObj->objectNr); + debugPrintf("current view: %d, loop: %d, cel: %d\n", screenObj->currentViewNr, screenObj->currentLoopNr, screenObj->currentCelNr); + debugPrintf("flags: %x\n", screenObj->flags); + debugPrintf("\n"); + debugPrintf("xPos: %d, yPos: %d, xSize: %d, ySize: %d\n", screenObj->xPos, screenObj->yPos, screenObj->xSize, screenObj->ySize); + debugPrintf("previous: xPos: %d, yPos: %d, xSize: %d, ySize: %d\n", screenObj->xPos_prev, screenObj->yPos_prev, screenObj->xSize_prev, screenObj->ySize_prev); + debugPrintf("direction: %d, priority: %d\n", screenObj->direction, screenObj->priority); + debugPrintf("stepTime: %d, timeCount: %d, size: %d\n", screenObj->stepTime, screenObj->stepTimeCount, screenObj->stepSize); + debugPrintf("cycleTime: %d, timeCount: %d\n", screenObj->cycleTime, screenObj->cycleTimeCount); + + switch(screenObj->motionType) { + case kMotionNormal: + debugPrintf("motion: normal\n"); + break; + case kMotionWander: + debugPrintf("motion: wander\n"); + debugPrintf("wanderCount: %d\n", screenObj->wander_count); + break; + case kMotionFollowEgo: + debugPrintf("motion: follow ego\n"); + debugPrintf("stepSize: %d, flag: %x, count: %d", screenObj->follow_stepSize, screenObj->follow_flag, screenObj->follow_count); + break; + case kMotionMoveObj: + case kMotionEgo: + if (screenObj->motionType == kMotionMoveObj) { + debugPrintf("motion: move obj\n"); + } else { + debugPrintf("motion: ego\n"); + } + debugPrintf("x: %d, y: %d, stepSize: %d, flag: %x\n", screenObj->move_x, screenObj->move_y, screenObj->move_stepSize, screenObj->move_flag); + break; + } + } +#if 0 + CycleType cycle; +#endif + return true; +} + +bool Console::Cmd_VmVars(int argc, const char **argv) { + if (argc < 2) { + debugPrintf("Shows the content of a VM variable / sets it\n"); + debugPrintf("Usage: %s <variable number> [<value>]\n", argv[0]); + return true; + } + + int varNr = 0; + int newValue = 0; + + if (!parseInteger(argv[1], varNr)) + return true; + + if ((varNr < 0) || (varNr > 255)) { + debugPrintf("invalid variable number\n"); + return true; + } + + if (argc < 3) { + // show contents + debugPrintf("variable %d == %d\n", varNr, _vm->getVar(varNr)); + } else { + if (!parseInteger(argv[2], newValue)) + return true; + + _vm->setVar(varNr, newValue); + + debugPrintf("value set.\n"); + } + return true; +} + +bool Console::Cmd_VmFlags(int argc, const char **argv) { + if (argc < 2) { + debugPrintf("Shows the content of a VM flag / sets it\n"); + debugPrintf("Usage: %s <flag number> [<value>]\n", argv[0]); + return true; + } + + int flagNr = 0; + int newFlagState = 0; + + if (!parseInteger(argv[1], flagNr)) + return true; + + if ((flagNr < 0) || (flagNr > 255)) { + debugPrintf("invalid flag number\n"); + return true; + } + + if (argc < 3) { + // show contents + if (_vm->getflag(flagNr)) { + debugPrintf("flag %d == set\n", flagNr); + } else { + debugPrintf("flag %d == not set\n", flagNr); + } + } else { + if (!parseInteger(argv[2], newFlagState)) + return true; + + if ((newFlagState != 0) && (newFlagState != 1)) { + debugPrintf("new state must bei either 0 or 1\n"); + return true; + } + + if (!newFlagState) { + _vm->setflag(flagNr, 0); + debugPrintf("flag %d reset.\n", flagNr); + } else { + _vm->setflag(flagNr, 1); + debugPrintf("flag %d set.\n", flagNr); + } + } + return true; +} + +bool Console::parseInteger(const char *argument, int &result) { + char *endPtr = 0; + int idxLen = strlen(argument); + const char *lastChar = argument + idxLen - (idxLen == 0 ? 0 : 1); + + if ((strncmp(argument, "0x", 2) == 0) || (*lastChar == 'h')) { + // hexadecimal number + result = strtol(argument, &endPtr, 16); + if ((*endPtr != 0) && (*endPtr != 'h')) { + debugPrintf("Invalid hexadecimal number '%s'\n", argument); + return false; + } + } else { + // decimal number + result = strtol(argument, &endPtr, 10); + if (*endPtr != 0) { + debugPrintf("Invalid decimal number '%s'\n", argument); + return false; + } + } + return true; +} + MickeyConsole::MickeyConsole(MickeyEngine *mickey) : GUI::Debugger() { _mickey = mickey; diff --git a/engines/agi/console.h b/engines/agi/console.h index c650e143a0..41dc9ddabc 100644 --- a/engines/agi/console.h +++ b/engines/agi/console.h @@ -62,6 +62,12 @@ private: bool Cmd_Cont(int argc, const char **argv); bool Cmd_Room(int argc, const char **argv); bool Cmd_BT(int argc, const char **argv); + bool Cmd_ShowMap(int argc, const char **argv); + bool Cmd_ScreenObj(int argc, const char **argv); + bool Cmd_VmVars(int argc, const char **argv); + bool Cmd_VmFlags(int argc, const char **argv); + + bool parseInteger(const char *argument, int &result); private: AgiEngine *_vm; diff --git a/engines/agi/cycle.cpp b/engines/agi/cycle.cpp index 145b827160..98f29ec0e1 100644 --- a/engines/agi/cycle.cpp +++ b/engines/agi/cycle.cpp @@ -20,11 +20,16 @@ * */ +#include "common/config-manager.h" + #include "agi/agi.h" #include "agi/sprite.h" #include "agi/graphics.h" +#include "agi/inv.h" +#include "agi/text.h" #include "agi/keyboard.h" #include "agi/menu.h" +#include "agi/systemui.h" namespace Agi { @@ -33,124 +38,133 @@ namespace Agi { * This function is called when ego enters a new room. * @param n room number */ -void AgiEngine::newRoom(int n) { - VtEntry *v; +void AgiEngine::newRoom(int16 newRoomNr) { + ScreenObjEntry *screenObj; + ScreenObjEntry *screenObjEgo = &_game.screenObjTable[SCREENOBJECTS_EGO_ENTRY]; int i; - // Simulate slowww computer. - // Many effects rely on it. - pause(kPauseRoom); + // Loading trigger + loadingTrigger_NewRoom(newRoomNr); - debugC(4, kDebugLevelMain, "*** room %d ***", n); + debugC(4, kDebugLevelMain, "*** room %d ***", newRoomNr); _sound->stopSound(); i = 0; - for (v = _game.viewTable; v < &_game.viewTable[MAX_VIEWTABLE]; v++) { - v->entry = i++; - v->flags &= ~(fAnimated | fDrawn); - v->flags |= fUpdate; - v->stepTime = 1; - v->stepTimeCount = 1; - v->cycleTime = 1; - v->cycleTimeCount = 1; - v->stepSize = 1; + for (screenObj = _game.screenObjTable; screenObj < &_game.screenObjTable[SCREENOBJECTS_MAX]; screenObj++) { + screenObj->objectNr = i++; + screenObj->flags &= ~(fAnimated | fDrawn); + screenObj->flags |= fUpdate; + screenObj->stepTime = 1; + screenObj->stepTimeCount = 1; + screenObj->cycleTime = 1; + screenObj->cycleTimeCount = 1; + screenObj->stepSize = 1; } agiUnloadResources(); _game.playerControl = true; _game.block.active = false; _game.horizon = 36; - _game.vars[vPrevRoom] = _game.vars[vCurRoom]; - _game.vars[vCurRoom] = n; - _game.vars[vBorderTouchObj] = 0; - _game.vars[vBorderCode] = 0; - _game.vars[vEgoViewResource] = _game.viewTable[0].currentView; + _game.vars[VM_VAR_PREVIOUS_ROOM] = _game.vars[VM_VAR_CURRENT_ROOM]; + _game.vars[VM_VAR_CURRENT_ROOM] = newRoomNr; + _game.vars[VM_VAR_BORDER_TOUCH_OBJECT] = 0; + _game.vars[VM_VAR_BORDER_CODE] = 0; + _game.vars[VM_VAR_EGO_VIEW_RESOURCE] = screenObjEgo->currentViewNr; - agiLoadResource(rLOGIC, n); + agiLoadResource(RESOURCETYPE_LOGIC, newRoomNr); // Reposition ego in the new room - switch (_game.vars[vBorderTouchEgo]) { + switch (_game.vars[VM_VAR_BORDER_TOUCH_EGO]) { case 1: - _game.viewTable[0].yPos = _HEIGHT - 1; + screenObjEgo->yPos = SCRIPT_HEIGHT - 1; break; case 2: - _game.viewTable[0].xPos = 0; + screenObjEgo->xPos = 0; break; case 3: - _game.viewTable[0].yPos = HORIZON + 1; + screenObjEgo->yPos = _game.horizon + 1; break; case 4: - _game.viewTable[0].xPos = _WIDTH - _game.viewTable[0].xSize; + screenObjEgo->xPos = SCRIPT_WIDTH - screenObjEgo->xSize; break; } if (getVersion() < 0x2000) { - warning("STUB: NewRoom(%d)", n); + warning("STUB: NewRoom(%d)", newRoomNr); - v->flags &= ~fDidntMove; + screenObjEgo->flags &= ~fDidntMove; // animateObject(0); - agiLoadResource(rVIEW, _game.viewTable[0].currentView); - setView(&_game.viewTable[0], _game.viewTable[0].currentView); + agiLoadResource(RESOURCETYPE_VIEW, screenObjEgo->currentViewNr); + setView(screenObjEgo, screenObjEgo->currentViewNr); } else { - _game.vars[vBorderTouchEgo] = 0; - setflag(fNewRoomExec, true); + if (screenObjEgo->motionType == kMotionEgo) { + screenObjEgo->motionType = kMotionNormal; + _game.vars[VM_VAR_EGO_DIRECTION] = 0; + } + + _game.vars[VM_VAR_BORDER_TOUCH_EGO] = 0; + setflag(VM_FLAG_NEW_ROOM_EXEC, true); _game.exitAllLogics = true; - writeStatus(); - writePrompt(); + _game._vm->_text->statusDraw(); + _game._vm->_text->promptRedraw(); } } void AgiEngine::resetControllers() { int i; - for (i = 0; i < MAX_DIRS; i++) { + for (i = 0; i < MAX_CONTROLLERS; i++) { _game.controllerOccured[i] = false; } } void AgiEngine::interpretCycle() { + ScreenObjEntry *screenObjEgo = &_game.screenObjTable[SCREENOBJECTS_EGO_ENTRY]; int oldSound, oldScore; if (_game.playerControl) - _game.vars[vEgoDir] = _game.viewTable[0].direction; + _game.vars[VM_VAR_EGO_DIRECTION] = screenObjEgo->direction; else - _game.viewTable[0].direction = _game.vars[vEgoDir]; + screenObjEgo->direction = _game.vars[VM_VAR_EGO_DIRECTION]; checkAllMotions(); - oldScore = _game.vars[vScore]; - oldSound = getflag(fSoundOn); + oldScore = _game.vars[VM_VAR_SCORE]; + oldSound = getflag(VM_FLAG_SOUND_ON); _game.exitAllLogics = false; while (runLogic(0) == 0 && !(shouldQuit() || _restartGame)) { - _game.vars[vWordNotFound] = 0; - _game.vars[vBorderTouchObj] = 0; - _game.vars[vBorderCode] = 0; - oldScore = _game.vars[vScore]; - setflag(fEnteredCli, false); + _game.vars[VM_VAR_WORD_NOT_FOUND] = 0; + _game.vars[VM_VAR_BORDER_TOUCH_OBJECT] = 0; + _game.vars[VM_VAR_BORDER_CODE] = 0; + oldScore = _game.vars[VM_VAR_SCORE]; + setflag(VM_FLAG_ENTERED_CLI, false); _game.exitAllLogics = false; + nonBlockingText_CycleDone(); resetControllers(); } + nonBlockingText_CycleDone(); resetControllers(); - _game.viewTable[0].direction = _game.vars[vEgoDir]; + screenObjEgo->direction = _game.vars[VM_VAR_EGO_DIRECTION]; - if (_game.vars[vScore] != oldScore || getflag(fSoundOn) != oldSound) - writeStatus(); + if (_game.vars[VM_VAR_SCORE] != oldScore || getflag(VM_FLAG_SOUND_ON) != oldSound) + _game._vm->_text->statusDraw(); - _game.vars[vBorderTouchObj] = 0; - _game.vars[vBorderCode] = 0; - setflag(fNewRoomExec, false); - setflag(fRestartGame, false); - setflag(fRestoreJustRan, false); + _game.vars[VM_VAR_BORDER_TOUCH_OBJECT] = 0; + _game.vars[VM_VAR_BORDER_CODE] = 0; + setflag(VM_FLAG_NEW_ROOM_EXEC, false); + setflag(VM_FLAG_RESTART_GAME, false); + setflag(VM_FLAG_RESTORE_JUST_RAN, false); if (_game.gfxMode) { - updateViewtable(); - _gfx->doUpdate(); + updateScreenObjTable(); } + _gfx->updateScreen(); + //_gfx->doUpdate(); } /** @@ -166,27 +180,27 @@ void AgiEngine::updateTimer() { if (!_game.clockEnabled) return; - setvar(vSeconds, getvar(vSeconds) + 1); - if (getvar(vSeconds) < 60) + setVar(VM_VAR_SECONDS, getVar(VM_VAR_SECONDS) + 1); + if (getVar(VM_VAR_SECONDS) < 60) return; - setvar(vSeconds, 0); - setvar(vMinutes, getvar(vMinutes) + 1); - if (getvar(vMinutes) < 60) + setVar(VM_VAR_SECONDS, 0); + setVar(VM_VAR_MINUTES, getVar(VM_VAR_MINUTES) + 1); + if (getVar(VM_VAR_MINUTES) < 60) return; - setvar(vMinutes, 0); - setvar(vHours, getvar(vHours) + 1); - if (getvar(vHours) < 24) + setVar(VM_VAR_MINUTES, 0); + setVar(VM_VAR_HOURS, getVar(VM_VAR_HOURS) + 1); + if (getVar(VM_VAR_HOURS) < 24) return; - setvar(vHours, 0); - setvar(vDays, getvar(vDays) + 1); + setVar(VM_VAR_HOURS, 0); + setVar(VM_VAR_DAYS, getVar(VM_VAR_DAYS) + 1); } void AgiEngine::newInputMode(InputMode mode) { - if (mode == INPUT_MENU && !getflag(fMenusWork) && !(getFeatures() & GF_MENUS)) - return; + //if (mode == INPUTMODE_MENU && !getflag(VM_FLAG_MENUS_WORK) && !(getFeatures() & GF_MENUS)) + // return; _oldMode = _game.inputMode; _game.inputMode = mode; @@ -198,14 +212,19 @@ void AgiEngine::oldInputMode() { // If main_cycle returns false, don't process more events! int AgiEngine::mainCycle(bool onlyCheckForEvents) { - unsigned int key, kascii; - VtEntry *v = &_game.viewTable[0]; + uint16 key; + byte keyAscii; + ScreenObjEntry *screenObjEgo = &_game.screenObjTable[SCREENOBJECTS_EGO_ENTRY]; if (!onlyCheckForEvents) { pollTimer(); updateTimer(); } + if (_menu->delayedExecuteActive()) { + _menu->execute(); + } + key = doPollKeyboard(); // In AGI Mouse emulation mode we must update the mouse-related @@ -217,98 +236,106 @@ int AgiEngine::mainCycle(bool onlyCheckForEvents) { _game.vars[29] = _mouse.y; //} - if (key == KEY_STATUSLN) { // F11 - _debug.statusline = !_debug.statusline; - writeStatus(); - key = 0; - } - - if (key == KEY_PRIORITY) { // F12 - _sprites->eraseBoth(); - _debug.priority = !_debug.priority; - _picture->showPic(); - _sprites->blitBoth(); - _sprites->commitBoth(); - key = 0; + switch (_game.inputMode) { + case INPUTMODE_NORMAL: + case INPUTMODE_NONE: + // Click-to-walk mouse interface + if (_game.playerControl && (screenObjEgo->flags & fAdjEgoXY)) { + int toX = screenObjEgo->move_x; + int toY = screenObjEgo->move_y; + + // AGI Mouse games use ego's sprite's bottom left corner for mouse walking target. + // Amiga games use ego's sprite's bottom center for mouse walking target. + // Atari ST and Apple II GS seem to use the bottom left + if (getPlatform() == Common::kPlatformAmiga) + toX -= (screenObjEgo->xSize / 2); // Center ego's sprite horizontally + + // Adjust ego's sprite's mouse walking target position (These parameters are + // controlled with the adj.ego.move.to.x.y-command). Note that these values rely + // on the horizontal centering of the ego's sprite at least on the Amiga platform. + toX += _game.adjMouseX; + toY += _game.adjMouseY; + + screenObjEgo->direction = getDirection(screenObjEgo->xPos, screenObjEgo->yPos, toX, toY, screenObjEgo->stepSize); + + if (screenObjEgo->direction == 0) + inDestination(screenObjEgo); + } + break; + default: + break; } - // Click-to-walk mouse interface - if (_game.playerControl && (v->flags & fAdjEgoXY)) { - int toX = v->parm1; - int toY = v->parm2; - - // AGI Mouse games use ego's sprite's bottom left corner for mouse walking target. - // Amiga games use ego's sprite's bottom center for mouse walking target. - // TODO: Check what Atari ST AGI and Apple IIGS AGI use for mouse walking target. - if (getPlatform() == Common::kPlatformAmiga) - toX -= (v->xSize / 2); // Center ego's sprite horizontally - - // Adjust ego's sprite's mouse walking target position (These parameters are - // controlled with the adj.ego.move.to.x.y-command). Note that these values rely - // on the horizontal centering of the ego's sprite at least on the Amiga platform. - toX += _game.adjMouseX; - toY += _game.adjMouseY; - - v->direction = getDirection(v->xPos, v->yPos, toX, toY, v->stepSize); - - if (v->direction == 0) - inDestination(v); + keyAscii = key & 0xFF; + if (keyAscii) { + setVar(VM_VAR_KEY, keyAscii); } - kascii = KEY_ASCII(key); - - if (kascii) - setvar(vKey, kascii); - - bool restartProcessKey; - do { - restartProcessKey = false; - + if (!cycleInnerLoopIsActive()) { + // no inner loop active at the moment, regular processing switch (_game.inputMode) { - case INPUT_NORMAL: + case INPUTMODE_NORMAL: if (!handleController(key)) { - if (key == 0 || !_game.inputEnabled) + if (key == 0 || (!_text->promptIsEnabled())) break; - handleKeys(key); - - // if ESC pressed, activate menu before - // accept.input from the interpreter cycle - // sets the input mode to normal again - // (closes: #540856) - if (key == KEY_ESCAPE) { - key = 0; - restartProcessKey = true; - } - - // commented out to close Sarien bug #438872 - //if (key) - // _game.keypress = key; + + _text->promptCharPress(key); } break; - case INPUT_GETSTRING: - handleController(key); - handleGetstring(key); - setvar(vKey, 0); // clear ENTER key - break; - case INPUT_MENU: - _menu->keyhandler(key); - _gfx->doUpdate(); - return false; - case INPUT_NONE: + case INPUTMODE_NONE: handleController(key); if (key) _game.keypress = key; break; + default: + break; } - } while (restartProcessKey); - if (!onlyCheckForEvents) { - _gfx->doUpdate(); + } else { + // inner loop active + // call specific workers + setVar(VM_VAR_KEY, 0); // clear keys, they must not be passed to the scripts + _game.keypress = 0; + + switch (_game.cycleInnerLoopType) { + case CYCLE_INNERLOOP_GETSTRING: // loop called from TextMgr::stringEdit() + case CYCLE_INNERLOOP_GETNUMBER: + //handleController(key); + if (key) { + _text->stringCharPress(key); + } + break; + + case CYCLE_INNERLOOP_INVENTORY: // loop called from InventoryMgr::show() + if (key) { + _inventory->charPress(key); + } + break; + + case CYCLE_INNERLOOP_MENU: + if (key) { + _menu->charPress(key); + } + return false; + + case CYCLE_INNERLOOP_SYSTEMUI_SELECTSAVEDGAMESLOT: + if (key) { + _systemUI->savedGameSlot_CharPress(key); + } + break; + + default: + break; + } + } + if (!onlyCheckForEvents) { if (_game.msgBoxTicks > 0) _game.msgBoxTicks--; } + _gfx->updateScreen(); + return true; } @@ -319,19 +346,24 @@ int AgiEngine::playGame() { debugC(2, kDebugLevelMain, "game version = 0x%x", getVersion()); _sound->stopSound(); - _gfx->clearScreen(0); - _game.horizon = HORIZON; + // We need to do this accurately and reset the AGI priorityscreen to 4 + // otherwise at least the fan game Nick's Quest will go into an endless + // loop, because the game draws views before it draws the first background picture. + // For further study see bug #3451122 + _gfx->clear(0, 4); + + _game.horizon = 36; _game.playerControl = false; - setflag(fLogicZeroFirsttime, true); // not in 2.917 - setflag(fNewRoomExec, true); // needed for MUMG and SQ2! - setflag(fSoundOn, true); // enable sound - setvar(vTimeDelay, 2); // "normal" speed + setflag(VM_FLAG_LOGIC_ZERO_FIRST_TIME, true); // not in 2.917 + setflag(VM_FLAG_NEW_ROOM_EXEC, true); // needed for MUMG and SQ2! + setflag(VM_FLAG_SOUND_ON, true); // enable sound + setVar(VM_VAR_TIME_DELAY, 2); // "normal" speed _game.gfxMode = true; _game.clockEnabled = true; - _game.lineUserInput = 22; + _text->promptRow_Set(22); // We run AGIMOUSE always as a side effect //if (getFeatures() & GF_AGIMOUSE) @@ -342,25 +374,34 @@ int AgiEngine::playGame() { debug(0, "Running AGI script.\n"); - setflag(fEnteredCli, false); - setflag(fSaidAcceptedInput, false); - _game.vars[vWordNotFound] = 0; - _game.vars[vKey] = 0; + setflag(VM_FLAG_ENTERED_CLI, false); + setflag(VM_FLAG_SAID_ACCEPTED_INPUT, false); + _game.vars[VM_VAR_WORD_NOT_FOUND] = 0; + _game.vars[VM_VAR_KEY] = 0; debugC(2, kDebugLevelMain, "Entering main loop"); - bool firstLoop = !getflag(fRestartGame); // Do not restore on game restart + bool firstLoop = !getflag(VM_FLAG_RESTART_GAME); // Do not restore on game restart + + if (firstLoop) { + if (ConfMan.hasKey("save_slot")) { + // quick restore enabled + _game.automaticRestoreGame = true; + } + } + + nonBlockingText_Forget(); do { if (!mainCycle()) continue; - if (getvar(vTimeDelay) == 0 || (1 + _clockCount) % getvar(vTimeDelay) == 0) { - if (!_game.hasPrompt && _game.inputMode == INPUT_NORMAL) { - writePrompt(); + if (getVar(VM_VAR_TIME_DELAY) == 0 || (1 + _clockCount) % getVar(VM_VAR_TIME_DELAY) == 0) { + if (!_game.hasPrompt && _game.inputMode == INPUTMODE_NORMAL) { + _text->promptRedraw(); _game.hasPrompt = 1; - } else if (_game.hasPrompt && _game.inputMode == INPUT_NONE) { - writePrompt(); + } else if (_game.hasPrompt && _game.inputMode == INPUTMODE_NONE) { + _text->promptRedraw(); _game.hasPrompt = 0; } @@ -368,15 +409,15 @@ int AgiEngine::playGame() { // Check if the user has asked to load a game from the command line // or the launcher - if (firstLoop) { + if (_game.automaticRestoreGame) { + _game.automaticRestoreGame = false; checkQuickLoad(); - firstLoop = false; } - setflag(fEnteredCli, false); - setflag(fSaidAcceptedInput, false); - _game.vars[vWordNotFound] = 0; - _game.vars[vKey] = 0; + setflag(VM_FLAG_ENTERED_CLI, false); + setflag(VM_FLAG_SAID_ACCEPTED_INPUT, false); + _game.vars[VM_VAR_WORD_NOT_FOUND] = 0; + _game.vars[VM_VAR_KEY] = 0; } if (shouldPerformAutoSave(_lastSaveTime)) { @@ -393,6 +434,23 @@ int AgiEngine::playGame() { int AgiEngine::runGame() { int ec = errOK; + // figure out the expected menu trigger for the current platform + // need to trigger the menu via mouse and via keyboard for platforms except PC + if (!(getFeatures() & GF_ESCPAUSE)) { + switch (getPlatform()) { + case Common::kPlatformAmiga: + case Common::kPlatformApple2GS: + _game.specialMenuTriggerKey = AGI_MENU_TRIGGER_APPLE2GS; + break; + case Common::kPlatformAtariST: + _game.specialMenuTriggerKey = AGI_MENU_TRIGGER_ATARIST; + break; + // Macintosh games seem to use ESC key just like PC versions do + default: + break; + } + } + // Execute the game do { debugC(2, kDebugLevelMain, "game loop"); @@ -402,61 +460,62 @@ int AgiEngine::runGame() { break; if (_restartGame) { - setflag(fRestartGame, true); - setvar(vTimeDelay, 2); // "normal" speed + setflag(VM_FLAG_RESTART_GAME, true); + setVar(VM_VAR_TIME_DELAY, 2); // "normal" speed _restartGame = false; } // Set computer type (v20 i.e. vComputer) and sound type switch (getPlatform()) { case Common::kPlatformAtariST: - setvar(vComputer, kAgiComputerAtariST); - setvar(vSoundgen, kAgiSoundPC); + setVar(VM_VAR_COMPUTER, kAgiComputerAtariST); + setVar(VM_VAR_SOUNDGENERATOR, kAgiSoundPC); break; case Common::kPlatformAmiga: if (getFeatures() & GF_OLDAMIGAV20) - setvar(vComputer, kAgiComputerAmigaOld); + setVar(VM_VAR_COMPUTER, kAgiComputerAmigaOld); else - setvar(vComputer, kAgiComputerAmiga); - setvar(vSoundgen, kAgiSoundTandy); + setVar(VM_VAR_COMPUTER, kAgiComputerAmiga); + setVar(VM_VAR_SOUNDGENERATOR, kAgiSoundTandy); break; case Common::kPlatformApple2GS: - setvar(vComputer, kAgiComputerApple2GS); + setVar(VM_VAR_COMPUTER, kAgiComputerApple2GS); if (getFeatures() & GF_2GSOLDSOUND) - setvar(vSoundgen, kAgiSound2GSOld); + setVar(VM_VAR_SOUNDGENERATOR, kAgiSound2GSOld); else - setvar(vSoundgen, kAgiSoundTandy); + setVar(VM_VAR_SOUNDGENERATOR, kAgiSoundTandy); break; case Common::kPlatformDOS: default: - setvar(vComputer, kAgiComputerPC); - setvar(vSoundgen, kAgiSoundPC); + setVar(VM_VAR_COMPUTER, kAgiComputerPC); + setVar(VM_VAR_SOUNDGENERATOR, kAgiSoundPC); break; } // Set monitor type (v26 i.e. vMonitor) switch (_renderMode) { - case Common::kRenderCGA: - setvar(vMonitor, kAgiMonitorCga); + case RENDERMODE_CGA: + setVar(VM_VAR_MONITOR, kAgiMonitorCga); break; - case Common::kRenderHercG: - case Common::kRenderHercA: - setvar(vMonitor, kAgiMonitorHercules); + case RENDERMODE_HERCULES: + setVar(VM_VAR_MONITOR, kAgiMonitorHercules); break; // Don't know if Amiga AGI games use a different value than kAgiMonitorEga // for vMonitor so I just use kAgiMonitorEga for them (As was done before too). - case Common::kRenderAmiga: - case Common::kRenderDefault: - case Common::kRenderEGA: + case RENDERMODE_AMIGA: + case RENDERMODE_APPLE_II_GS: + case RENDERMODE_ATARI_ST: + case RENDERMODE_EGA: + case RENDERMODE_VGA: default: - setvar(vMonitor, kAgiMonitorEga); + setVar(VM_VAR_MONITOR, kAgiMonitorEga); break; } - setvar(vFreePages, 180); // Set amount of free memory to realistic value - setvar(vMaxInputChars, 38); - _game.inputMode = INPUT_NONE; - _game.inputEnabled = false; + setVar(VM_VAR_FREE_PAGES, 180); // Set amount of free memory to realistic value + setVar(VM_VAR_MAX_INPUT_CHARACTERS, 38); + _game.inputMode = INPUTMODE_NONE; + _text->promptDisable(); _game.hasPrompt = 0; _game.state = STATE_RUNNING; diff --git a/engines/agi/detection.cpp b/engines/agi/detection.cpp index 971b562aec..17358e0a40 100644 --- a/engines/agi/detection.cpp +++ b/engines/agi/detection.cpp @@ -257,7 +257,6 @@ SaveStateList AgiMetaEngine::listSaves(const char *target) const { const uint32 AGIflag = MKTAG('A','G','I',':'); Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); Common::StringArray filenames; - char saveDesc[31]; Common::String pattern = target; pattern += ".###"; @@ -267,16 +266,35 @@ SaveStateList AgiMetaEngine::listSaves(const char *target) const { SaveStateList saveList; for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { // Obtain the last 3 digits of the filename, since they correspond to the save slot - int slotNum = atoi(file->c_str() + file->size() - 3); + int slotNr = atoi(file->c_str() + file->size() - 3); - if (slotNum >= 0 && slotNum <= 999) { + if (slotNr >= 0 && slotNr <= 999) { Common::InSaveFile *in = saveFileMan->openForLoading(*file); if (in) { uint32 type = in->readUint32BE(); - if (type == AGIflag) - in->read(saveDesc, 31); - saveList.push_back(SaveStateDescriptor(slotNum, saveDesc)); + char description[31]; + + if (type == AGIflag) { + uint16 descriptionPos = 0; + + in->read(description, 31); + + // Security-check, if saveDescription has a terminating NUL + while (description[descriptionPos]) { + descriptionPos++; + if (descriptionPos >= sizeof(description)) + break; + } + if (descriptionPos >= sizeof(description)) { + strcpy(description, "[broken saved game]"); + } + } else { + strcpy(description, "[not an AGI saved game]"); + } + delete in; + + saveList.push_back(SaveStateDescriptor(slotNr, description)); } } } @@ -291,9 +309,9 @@ void AgiMetaEngine::removeSaveState(const char *target, int slot) const { g_system->getSavefileManager()->removeSavefile(fileName); } -SaveStateDescriptor AgiMetaEngine::querySaveMetaInfos(const char *target, int slot) const { +SaveStateDescriptor AgiMetaEngine::querySaveMetaInfos(const char *target, int slotNr) const { const uint32 AGIflag = MKTAG('A','G','I',':'); - Common::String fileName = Common::String::format("%s.%03d", target, slot); + Common::String fileName = Common::String::format("%s.%03d", target, slotNr); Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(fileName); @@ -303,49 +321,73 @@ SaveStateDescriptor AgiMetaEngine::querySaveMetaInfos(const char *target, int sl return SaveStateDescriptor(); } - char name[32]; - in->read(name, 31); + char description[31]; + uint16 descriptionPos = 0; + + in->read(description, 31); + + while (description[descriptionPos]) { + descriptionPos++; + if (descriptionPos >= sizeof(description)) + break; + } + if (descriptionPos >= sizeof(description)) { + // broken description, ignore it + delete in; + + SaveStateDescriptor descriptor(slotNr, "[broken saved game]"); + return descriptor; + } - SaveStateDescriptor desc(slot, name); + SaveStateDescriptor descriptor(slotNr, description); // Do not allow save slot 0 (used for auto-saving) to be deleted or // overwritten. - desc.setDeletableFlag(slot != 0); - desc.setWriteProtectedFlag(slot == 0); + if (slotNr == 0) { + descriptor.setWriteProtectedFlag(true); + descriptor.setDeletableFlag(false); + } else { + descriptor.setWriteProtectedFlag(false); + descriptor.setDeletableFlag(true); + } char saveVersion = in->readByte(); if (saveVersion >= 4) { Graphics::Surface *const thumbnail = Graphics::loadThumbnail(*in); - desc.setThumbnail(thumbnail); + descriptor.setThumbnail(thumbnail); uint32 saveDate = in->readUint32BE(); uint16 saveTime = in->readUint16BE(); if (saveVersion >= 6) { uint32 playTime = in->readUint32BE(); - desc.setPlayTime(playTime * 1000); + descriptor.setPlayTime(playTime * 1000); } int day = (saveDate >> 24) & 0xFF; int month = (saveDate >> 16) & 0xFF; int year = saveDate & 0xFFFF; - desc.setSaveDate(year, month, day); + descriptor.setSaveDate(year, month, day); int hour = (saveTime >> 8) & 0xFF; int minutes = saveTime & 0xFF; - desc.setSaveTime(hour, minutes); + descriptor.setSaveTime(hour, minutes); } - delete in; - return desc; + return descriptor; + } else { SaveStateDescriptor emptySave; // Do not allow save slot 0 (used for auto-saving) to be overwritten. - emptySave.setWriteProtectedFlag(slot == 0); + if (slotNr == 0) { + emptySave.setWriteProtectedFlag(true); + } else { + emptySave.setWriteProtectedFlag(false); + } return emptySave; } } @@ -528,14 +570,17 @@ const ADGameDescription *AgiMetaEngine::fallbackDetect(const FileMap &allFilesXX namespace Agi { bool AgiBase::canLoadGameStateCurrently() { - return (!(getGameType() == GType_PreAGI) && getflag(fMenusWork) && !_noSaveLoadAllowed); + return (!(getGameType() == GType_PreAGI) && getflag(VM_FLAG_MENUS_WORK) && !_noSaveLoadAllowed); } bool AgiBase::canSaveGameStateCurrently() { + bool promptEnabled = false; + if (getGameID() == GID_BC) // Technically in Black Cauldron we may save anytime return true; - return (!(getGameType() == GType_PreAGI) && getflag(fMenusWork) && !_noSaveLoadAllowed && _game.inputEnabled); + promptEnabled = promptIsEnabled(); + return (!(getGameType() == GType_PreAGI) && getflag(VM_FLAG_MENUS_WORK) && !_noSaveLoadAllowed && promptEnabled); } int AgiEngine::agiDetectGame() { diff --git a/engines/agi/font.h b/engines/agi/font.h index 0e6b15f06b..6b6381ccd7 100644 --- a/engines/agi/font.h +++ b/engines/agi/font.h @@ -25,6 +25,181 @@ namespace Agi { +// Arrow to the right character, used for original saved game dialogs +// Needs to get patched into at least the Apple IIgs font, because the font didn't support +// that character and original AGI on Apple IIgs used Apple II menus for saving/restoring. +static const uint8 fontData_ArrowRightCharacter[8] = { + 0x00, 0x18, 0x0C, 0xFE, 0x0C, 0x18, 0x00, 0x00, +}; + +// topaz data, same as in engines\parallaction\staticres.cpp +// seems to have been recreated and is not the original amiga font +static const uint8 fontData_AmigaPseudoTopaz[2600] = { + 0x00, 0x00, 0x03, 0xf3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x79, 0x00, 0x00, 0x03, 0xe9, 0x00, 0x00, 0x02, 0x79, + 0x70, 0xff, 0x4e, 0x75, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x00, 0x1a, 0x0f, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x45, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x09, 0x74, 0x00, 0x08, + 0x00, 0x40, 0x00, 0x08, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x20, 0xff, 0x00, 0x00, 0x00, 0x6e, + 0x00, 0xbe, 0x00, 0x00, 0x06, 0x5e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, + 0x6c, 0x6c, 0x18, 0x00, 0x38, 0x18, 0x0c, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x3c, 0x18, + 0x3c, 0x3c, 0x1c, 0x7e, 0x1c, 0x7e, 0x3c, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x7c, 0x3c, + 0x7c, 0x1e, 0x78, 0x7e, 0x7e, 0x3c, 0x66, 0x3c, 0x06, 0xc6, 0x60, 0xc6, 0xc6, 0x3c, 0x7c, 0x78, + 0x7c, 0x3c, 0x7e, 0x66, 0x66, 0xc6, 0xc3, 0xc3, 0xfe, 0x3c, 0xc0, 0x3c, 0x10, 0x00, 0x18, 0x00, + 0x60, 0x00, 0x06, 0x00, 0x1c, 0x00, 0x60, 0x18, 0x0c, 0x60, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x18, 0x70, 0x72, 0x0f, 0x00, 0x18, + 0x00, 0x1c, 0x42, 0xc3, 0x18, 0x3c, 0x66, 0x7e, 0x1c, 0x00, 0x3e, 0x7e, 0x7e, 0x3c, 0x18, 0x78, + 0x78, 0x18, 0x00, 0x3e, 0x00, 0x00, 0x30, 0x38, 0x00, 0x40, 0x40, 0xc0, 0x18, 0x3c, 0x3c, 0x7e, + 0x06, 0x66, 0x18, 0x7e, 0x7e, 0x36, 0x0c, 0x0c, 0x18, 0x3c, 0xc6, 0x3c, 0x60, 0x76, 0x18, 0x00, + 0x0c, 0x7e, 0x71, 0x66, 0x00, 0x66, 0x60, 0x0e, 0x7e, 0x66, 0x18, 0x6e, 0x3c, 0x00, 0x18, 0x7e, + 0x06, 0x66, 0x18, 0x00, 0x7e, 0x34, 0x0c, 0x0c, 0x18, 0x0c, 0x60, 0x00, 0x18, 0x3c, 0x0c, 0x00, + 0x0c, 0x00, 0x71, 0x00, 0x00, 0x00, 0x18, 0x0c, 0x7e, 0x00, 0x18, 0x3c, 0x00, 0x18, 0x6c, 0x6c, + 0x3e, 0x66, 0x6c, 0x18, 0x18, 0x18, 0x66, 0x18, 0x00, 0x00, 0x00, 0x06, 0x66, 0x38, 0x66, 0x66, + 0x3c, 0x60, 0x30, 0x06, 0x66, 0x66, 0x18, 0x18, 0x06, 0x00, 0x60, 0x66, 0xc6, 0x66, 0x66, 0x30, + 0x6c, 0x60, 0x60, 0x66, 0x66, 0x18, 0x06, 0xcc, 0x60, 0xee, 0xe6, 0x66, 0x66, 0xcc, 0x66, 0x66, + 0x18, 0x66, 0x66, 0xc6, 0x66, 0x66, 0x0c, 0x30, 0x60, 0x0c, 0x38, 0x00, 0x18, 0x00, 0x60, 0x00, + 0x06, 0x00, 0x30, 0x00, 0x60, 0x00, 0x00, 0x60, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x9c, 0x3c, 0x7e, 0x00, 0x0c, 0x36, + 0x3c, 0x66, 0x18, 0x60, 0x66, 0x81, 0x24, 0x33, 0x06, 0x81, 0x00, 0x66, 0x18, 0x0c, 0x0c, 0x30, + 0x00, 0x7a, 0x00, 0x00, 0x70, 0x44, 0xcc, 0xc6, 0xc6, 0x23, 0x00, 0x66, 0x18, 0x00, 0x1c, 0x00, + 0x24, 0x60, 0x00, 0x1c, 0x18, 0x18, 0x00, 0x66, 0xcc, 0x00, 0x60, 0x3c, 0x30, 0xc6, 0x18, 0x00, + 0x8e, 0x00, 0xc6, 0x66, 0x60, 0x38, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x00, 0x00, 0x00, 0x0c, 0x00, + 0x24, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x18, 0x66, 0x3c, 0x18, 0x18, 0x18, 0x00, 0x18, 0x7e, + 0x8e, 0x66, 0x18, 0x00, 0x18, 0x18, 0x00, 0x66, 0x00, 0x18, 0x00, 0x18, 0x00, 0xfe, 0x60, 0xac, + 0x68, 0x30, 0x30, 0x0c, 0x3c, 0x18, 0x00, 0x00, 0x00, 0x0c, 0x6e, 0x78, 0x06, 0x06, 0x6c, 0x7c, + 0x60, 0x06, 0x66, 0x66, 0x18, 0x18, 0x18, 0x7e, 0x18, 0x06, 0xde, 0x66, 0x66, 0x60, 0x66, 0x60, + 0x60, 0x60, 0x66, 0x18, 0x06, 0xd8, 0x60, 0xfe, 0xf6, 0x66, 0x66, 0xcc, 0x66, 0x70, 0x18, 0x66, + 0x66, 0xc6, 0x3c, 0x3c, 0x18, 0x30, 0x30, 0x0c, 0x6c, 0x00, 0x0c, 0x3c, 0x7c, 0x3c, 0x3e, 0x3c, + 0x7c, 0x3e, 0x7c, 0x18, 0x0c, 0x66, 0x18, 0xec, 0x7c, 0x3c, 0x7c, 0x3e, 0x7c, 0x3c, 0x7c, 0x66, + 0x66, 0xc6, 0xc6, 0x66, 0x7e, 0x18, 0x18, 0x18, 0x00, 0xf0, 0x66, 0x18, 0x3e, 0x30, 0x66, 0x3c, + 0x18, 0x3c, 0x00, 0x9d, 0x44, 0x66, 0x00, 0xb9, 0x00, 0x3c, 0x7e, 0x18, 0x18, 0x60, 0x66, 0x7a, + 0x18, 0x00, 0x30, 0x44, 0x66, 0x4c, 0x4c, 0x66, 0x18, 0x66, 0x18, 0x3c, 0x3c, 0x3c, 0x3c, 0x60, + 0x7e, 0x3c, 0x7e, 0x7e, 0x7e, 0x60, 0xd8, 0x3c, 0x60, 0x66, 0xc6, 0xe6, 0x3c, 0x3c, 0x3c, 0x3c, + 0x6c, 0x66, 0x6c, 0x66, 0x66, 0x66, 0x7e, 0x7e, 0x66, 0x3c, 0x18, 0x3c, 0x18, 0x3c, 0x3c, 0x3c, + 0x3c, 0x18, 0x3c, 0x7e, 0x3c, 0x3e, 0x6c, 0x00, 0x18, 0x3c, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x66, 0x1e, 0x3c, 0x66, 0x00, 0x7e, 0x7e, 0x00, 0x18, 0x00, 0x6c, 0x3c, 0xd8, 0x76, 0x00, + 0x30, 0x0c, 0xff, 0x7e, 0x00, 0x7e, 0x00, 0x18, 0x7e, 0x18, 0x0c, 0x1c, 0xcc, 0x06, 0x7c, 0x0c, + 0x3c, 0x3e, 0x00, 0x00, 0x60, 0x00, 0x06, 0x0c, 0xd6, 0x7e, 0x7c, 0x60, 0x66, 0x78, 0x78, 0x6e, + 0x7e, 0x18, 0x06, 0xf0, 0x60, 0xd6, 0xde, 0x66, 0x7c, 0xcc, 0x7c, 0x3c, 0x18, 0x66, 0x66, 0xd6, + 0x18, 0x18, 0x30, 0x30, 0x18, 0x0c, 0xc6, 0x00, 0x00, 0x06, 0x66, 0x60, 0x66, 0x66, 0x30, 0x66, + 0x66, 0x18, 0x0c, 0x6c, 0x18, 0xfe, 0x66, 0x66, 0x66, 0x66, 0x66, 0x60, 0x30, 0x66, 0x66, 0xc6, + 0x6c, 0x66, 0x0c, 0x70, 0x18, 0x0e, 0x00, 0xc3, 0x66, 0x18, 0x6c, 0x78, 0x3c, 0x18, 0x00, 0x66, + 0x00, 0xb1, 0x3c, 0xcc, 0x00, 0xa5, 0x00, 0x00, 0x18, 0x30, 0x0c, 0x00, 0x66, 0x3a, 0x18, 0x00, + 0x30, 0x38, 0x33, 0x58, 0x58, 0x2c, 0x30, 0x7e, 0x18, 0x66, 0x66, 0x66, 0x66, 0x78, 0x60, 0x66, + 0x60, 0x4c, 0x60, 0x6e, 0xf0, 0x18, 0x60, 0x30, 0xe6, 0xf6, 0x66, 0x66, 0x66, 0x66, 0x38, 0x66, + 0x70, 0x30, 0x66, 0x66, 0x4c, 0x4c, 0x6c, 0x06, 0x18, 0x06, 0x3c, 0x06, 0x06, 0x66, 0x66, 0x3c, + 0x66, 0x0c, 0x66, 0x66, 0x78, 0x18, 0x18, 0x60, 0x7c, 0x66, 0x3c, 0x3c, 0x3c, 0x3c, 0x7e, 0x66, + 0x78, 0x60, 0x66, 0x66, 0x0c, 0x0c, 0x00, 0x18, 0x00, 0xfe, 0x06, 0x36, 0xdc, 0x00, 0x30, 0x0c, + 0x3c, 0x18, 0x00, 0x00, 0x00, 0x30, 0x76, 0x18, 0x18, 0x06, 0xfe, 0x06, 0x66, 0x18, 0x66, 0x06, + 0x00, 0x00, 0x18, 0x7e, 0x18, 0x18, 0xde, 0x66, 0x66, 0x60, 0x66, 0x60, 0x60, 0x66, 0x66, 0x18, + 0x06, 0xd8, 0x60, 0xc6, 0xce, 0x66, 0x60, 0xcc, 0x6c, 0x0e, 0x18, 0x66, 0x3c, 0xfe, 0x3c, 0x18, + 0x60, 0x30, 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x3e, 0x66, 0x60, 0x66, 0x7e, 0x30, 0x66, 0x66, 0x18, + 0x0c, 0x78, 0x18, 0xd6, 0x66, 0x66, 0x66, 0x66, 0x60, 0x3c, 0x30, 0x66, 0x66, 0xd6, 0x38, 0x66, + 0x18, 0x18, 0x18, 0x18, 0x00, 0x0f, 0x66, 0x18, 0x3e, 0x30, 0x42, 0x3c, 0x18, 0x3c, 0x00, 0x9d, + 0x00, 0x66, 0x00, 0xb9, 0x00, 0x00, 0x18, 0x7c, 0x78, 0x00, 0x66, 0x0a, 0x00, 0x00, 0x30, 0x00, + 0x66, 0x32, 0x3e, 0xd9, 0x60, 0x66, 0x18, 0x7e, 0x40, 0x7e, 0x7e, 0x60, 0x78, 0x40, 0x78, 0x18, + 0x78, 0x66, 0xd8, 0x18, 0x60, 0x0c, 0xf6, 0xde, 0x66, 0x66, 0x66, 0x66, 0x6c, 0x66, 0xe0, 0x0c, + 0x66, 0x66, 0x18, 0x18, 0x66, 0x3e, 0x18, 0x3e, 0x60, 0x3e, 0x3e, 0x7e, 0x7e, 0x60, 0x7e, 0x18, + 0x7e, 0x66, 0x6c, 0x18, 0x18, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x66, 0x18, 0x3c, + 0x66, 0x66, 0x18, 0x18, 0x00, 0x00, 0x00, 0x6c, 0x7c, 0x6a, 0xce, 0x00, 0x18, 0x18, 0x66, 0x18, + 0x18, 0x00, 0x18, 0x60, 0x66, 0x18, 0x30, 0x66, 0x0c, 0x66, 0x66, 0x18, 0x66, 0x0c, 0x18, 0x18, + 0x06, 0x00, 0x60, 0x00, 0xc0, 0x66, 0x66, 0x30, 0x6c, 0x60, 0x60, 0x66, 0x66, 0x18, 0x66, 0xcc, + 0x60, 0xc6, 0xc6, 0x66, 0x60, 0xdc, 0x66, 0x66, 0x18, 0x66, 0x3c, 0xee, 0x66, 0x18, 0xc0, 0x30, + 0x06, 0x0c, 0x00, 0x00, 0x00, 0x66, 0x66, 0x60, 0x66, 0x60, 0x30, 0x3e, 0x66, 0x18, 0x0c, 0x6c, + 0x18, 0xc6, 0x66, 0x66, 0x7c, 0x3e, 0x60, 0x06, 0x30, 0x66, 0x3c, 0xfe, 0x6c, 0x3c, 0x30, 0x18, + 0x18, 0x18, 0x00, 0x3c, 0x66, 0x18, 0x0c, 0x30, 0x00, 0x18, 0x18, 0x06, 0x00, 0x81, 0x7e, 0x33, + 0x00, 0xa5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x0a, 0x00, 0x00, 0x00, 0x7c, 0xcc, 0x66, + 0x62, 0x33, 0x66, 0x66, 0x18, 0x66, 0x66, 0x66, 0x66, 0x60, 0x60, 0x66, 0x60, 0x32, 0x60, 0x3e, + 0xcc, 0x18, 0x7e, 0x66, 0xde, 0xce, 0x66, 0x66, 0x66, 0x66, 0xc6, 0x66, 0x60, 0x66, 0x66, 0x66, + 0x32, 0x32, 0x66, 0x66, 0x18, 0x66, 0x60, 0x66, 0x66, 0x60, 0x60, 0x60, 0x60, 0x30, 0x60, 0x3e, + 0x66, 0x18, 0x18, 0x06, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x18, 0x66, 0x18, 0x06, 0x66, 0x66, + 0x30, 0x30, 0x00, 0x18, 0x00, 0x6c, 0x18, 0xcc, 0x7b, 0x00, 0x0c, 0x30, 0x00, 0x00, 0x18, 0x00, + 0x18, 0xc0, 0x3c, 0x18, 0x7e, 0x3c, 0x0c, 0x3c, 0x3c, 0x18, 0x3c, 0x38, 0x18, 0x18, 0x00, 0x00, + 0x00, 0x18, 0x78, 0x66, 0x7c, 0x1e, 0x78, 0x7e, 0x60, 0x3e, 0x66, 0x3c, 0x3c, 0xc6, 0x7e, 0xc6, + 0xc6, 0x3c, 0x60, 0x7e, 0x66, 0x3c, 0x18, 0x3c, 0x18, 0xc6, 0xc3, 0x18, 0xfe, 0x3c, 0x03, 0x3c, + 0x00, 0x00, 0x00, 0x3e, 0x7c, 0x3c, 0x3e, 0x3c, 0x30, 0x06, 0x66, 0x0c, 0x0c, 0x66, 0x0c, 0xc6, + 0x66, 0x3c, 0x60, 0x06, 0x60, 0x7c, 0x1c, 0x3e, 0x18, 0x6c, 0xc6, 0x18, 0x7e, 0x0e, 0x18, 0x70, + 0x00, 0xf0, 0x7e, 0x18, 0x00, 0x7e, 0x00, 0x18, 0x18, 0x3c, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x81, + 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x7f, 0x0a, 0x00, 0x18, 0x00, 0x00, 0x00, 0xcf, 0xc4, 0x67, + 0x3c, 0x67, 0x3e, 0x66, 0x3c, 0x66, 0x66, 0x7f, 0x7e, 0x3c, 0x7e, 0x7e, 0x7e, 0x18, 0x30, 0x3c, + 0x18, 0x3c, 0xce, 0x18, 0x3c, 0x3c, 0x3c, 0x3c, 0x00, 0x3f, 0x7e, 0x3c, 0x3c, 0x3c, 0x7e, 0x7e, + 0x6c, 0x3f, 0x1e, 0x3e, 0x3c, 0x3e, 0x3e, 0x3c, 0x3c, 0x3c, 0x3c, 0x7e, 0x3c, 0x06, 0x18, 0x0c, + 0x0c, 0x7c, 0x66, 0x18, 0x3c, 0x3c, 0x3c, 0x3c, 0x00, 0x3f, 0x0c, 0x7c, 0x3e, 0x3e, 0x7e, 0x7e, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x60, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x02, 0x0e, 0x01, 0x00, 0x03, + 0x06, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x60, 0x00, 0x30, 0x00, + 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x03, + 0x06, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x30, 0x00, 0x18, 0x00, + 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x10, 0x00, 0x08, 0x00, 0x18, 0x00, 0x08, 0x00, 0x20, + 0x00, 0x08, 0x00, 0x28, 0x00, 0x08, 0x00, 0x30, 0x00, 0x08, 0x00, 0x38, 0x00, 0x08, 0x00, 0x40, + 0x00, 0x08, 0x00, 0x48, 0x00, 0x08, 0x00, 0x50, 0x00, 0x08, 0x00, 0x58, 0x00, 0x08, 0x00, 0x60, + 0x00, 0x08, 0x00, 0x68, 0x00, 0x08, 0x00, 0x70, 0x00, 0x08, 0x00, 0x78, 0x00, 0x08, 0x00, 0x80, + 0x00, 0x08, 0x00, 0x88, 0x00, 0x08, 0x00, 0x90, 0x00, 0x08, 0x00, 0x98, 0x00, 0x08, 0x00, 0xa0, + 0x00, 0x08, 0x00, 0xa8, 0x00, 0x08, 0x00, 0xb0, 0x00, 0x08, 0x00, 0xb8, 0x00, 0x08, 0x00, 0xc0, + 0x00, 0x08, 0x00, 0xc8, 0x00, 0x08, 0x00, 0xd0, 0x00, 0x08, 0x00, 0xd8, 0x00, 0x08, 0x00, 0xe0, + 0x00, 0x08, 0x00, 0xe8, 0x00, 0x08, 0x00, 0xf0, 0x00, 0x08, 0x00, 0xf8, 0x00, 0x08, 0x01, 0x00, + 0x00, 0x08, 0x01, 0x08, 0x00, 0x08, 0x01, 0x10, 0x00, 0x08, 0x01, 0x18, 0x00, 0x08, 0x01, 0x20, + 0x00, 0x08, 0x01, 0x28, 0x00, 0x08, 0x01, 0x30, 0x00, 0x08, 0x01, 0x38, 0x00, 0x08, 0x01, 0x40, + 0x00, 0x08, 0x01, 0x48, 0x00, 0x08, 0x01, 0x50, 0x00, 0x08, 0x01, 0x58, 0x00, 0x08, 0x01, 0x60, + 0x00, 0x08, 0x01, 0x68, 0x00, 0x08, 0x01, 0x70, 0x00, 0x08, 0x01, 0x78, 0x00, 0x08, 0x01, 0x80, + 0x00, 0x08, 0x01, 0x88, 0x00, 0x08, 0x01, 0x90, 0x00, 0x08, 0x01, 0x98, 0x00, 0x08, 0x01, 0xa0, + 0x00, 0x08, 0x01, 0xa8, 0x00, 0x08, 0x01, 0xb0, 0x00, 0x08, 0x01, 0xb8, 0x00, 0x08, 0x01, 0xc0, + 0x00, 0x08, 0x01, 0xc8, 0x00, 0x08, 0x01, 0xd0, 0x00, 0x08, 0x01, 0xd8, 0x00, 0x08, 0x01, 0xe0, + 0x00, 0x08, 0x01, 0xe8, 0x00, 0x08, 0x01, 0xf0, 0x00, 0x08, 0x01, 0xf8, 0x00, 0x08, 0x02, 0x00, + 0x00, 0x08, 0x02, 0x08, 0x00, 0x08, 0x02, 0x10, 0x00, 0x08, 0x02, 0x18, 0x00, 0x08, 0x02, 0x20, + 0x00, 0x08, 0x02, 0x28, 0x00, 0x08, 0x02, 0x30, 0x00, 0x08, 0x02, 0x38, 0x00, 0x08, 0x02, 0x40, + 0x00, 0x08, 0x02, 0x48, 0x00, 0x08, 0x02, 0x50, 0x00, 0x08, 0x02, 0x58, 0x00, 0x08, 0x02, 0x60, + 0x00, 0x08, 0x02, 0x68, 0x00, 0x08, 0x02, 0x70, 0x00, 0x08, 0x02, 0x78, 0x00, 0x08, 0x02, 0x80, + 0x00, 0x08, 0x02, 0x88, 0x00, 0x08, 0x02, 0x90, 0x00, 0x08, 0x02, 0x98, 0x00, 0x08, 0x02, 0xa0, + 0x00, 0x08, 0x02, 0xa8, 0x00, 0x08, 0x02, 0xb0, 0x00, 0x08, 0x02, 0xb8, 0x00, 0x08, 0x02, 0xc0, + 0x00, 0x08, 0x02, 0xc8, 0x00, 0x08, 0x02, 0xd0, 0x00, 0x08, 0x02, 0xd8, 0x00, 0x08, 0x02, 0xe0, + 0x00, 0x08, 0x02, 0xe8, 0x00, 0x08, 0x02, 0xf0, 0x00, 0x08, 0x02, 0xf8, 0x00, 0x08, 0x03, 0x00, + 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, + 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, + 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, + 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, + 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, + 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, + 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, + 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x00, 0x00, + 0x00, 0x08, 0x03, 0x08, 0x00, 0x08, 0x03, 0x10, 0x00, 0x08, 0x03, 0x18, 0x00, 0x08, 0x03, 0x20, + 0x00, 0x08, 0x03, 0x28, 0x00, 0x08, 0x03, 0x30, 0x00, 0x08, 0x03, 0x38, 0x00, 0x08, 0x03, 0x40, + 0x00, 0x08, 0x03, 0x48, 0x00, 0x08, 0x03, 0x50, 0x00, 0x08, 0x03, 0x58, 0x00, 0x08, 0x03, 0x60, + 0x00, 0x08, 0x00, 0x68, 0x00, 0x08, 0x03, 0x68, 0x00, 0x08, 0x03, 0x70, 0x00, 0x08, 0x03, 0x78, + 0x00, 0x08, 0x03, 0x80, 0x00, 0x08, 0x03, 0x88, 0x00, 0x08, 0x03, 0x90, 0x00, 0x08, 0x03, 0x98, + 0x00, 0x08, 0x03, 0xa0, 0x00, 0x08, 0x03, 0xa8, 0x00, 0x08, 0x03, 0xb0, 0x00, 0x08, 0x03, 0xb8, + 0x00, 0x08, 0x03, 0xc0, 0x00, 0x08, 0x03, 0xc8, 0x00, 0x08, 0x03, 0xd0, 0x00, 0x08, 0x03, 0xd8, + 0x00, 0x08, 0x03, 0xe0, 0x00, 0x08, 0x03, 0xe8, 0x00, 0x08, 0x03, 0xf0, 0x00, 0x08, 0x03, 0xf8, + 0x00, 0x08, 0x04, 0x00, 0x00, 0x08, 0x04, 0x08, 0x00, 0x08, 0x04, 0x10, 0x00, 0x08, 0x04, 0x18, + 0x00, 0x08, 0x04, 0x20, 0x00, 0x08, 0x04, 0x28, 0x00, 0x08, 0x04, 0x30, 0x00, 0x08, 0x04, 0x38, + 0x00, 0x08, 0x04, 0x40, 0x00, 0x08, 0x04, 0x48, 0x00, 0x08, 0x04, 0x50, 0x00, 0x08, 0x04, 0x58, + 0x00, 0x08, 0x04, 0x60, 0x00, 0x08, 0x04, 0x68, 0x00, 0x08, 0x04, 0x70, 0x00, 0x08, 0x04, 0x78, + 0x00, 0x08, 0x04, 0x80, 0x00, 0x08, 0x04, 0x88, 0x00, 0x08, 0x04, 0x90, 0x00, 0x08, 0x04, 0x98, + 0x00, 0x08, 0x04, 0xa0, 0x00, 0x08, 0x04, 0xa8, 0x00, 0x08, 0x04, 0xb0, 0x00, 0x08, 0x04, 0xb8, + 0x00, 0x08, 0x04, 0xc0, 0x00, 0x08, 0x04, 0xc8, 0x00, 0x08, 0x04, 0xd0, 0x00, 0x08, 0x04, 0xd8, + 0x00, 0x08, 0x04, 0xe0, 0x00, 0x08, 0x04, 0xe8, 0x00, 0x08, 0x04, 0xf0, 0x00, 0x08, 0x04, 0xf8, + 0x00, 0x08, 0x05, 0x00, 0x00, 0x08, 0x05, 0x08, 0x00, 0x08, 0x05, 0x10, 0x00, 0x08, 0x05, 0x18, + 0x00, 0x08, 0x05, 0x20, 0x00, 0x08, 0x05, 0x28, 0x00, 0x08, 0x05, 0x30, 0x00, 0x08, 0x05, 0x38, + 0x00, 0x08, 0x05, 0x40, 0x00, 0x08, 0x05, 0x48, 0x00, 0x08, 0x05, 0x50, 0x00, 0x08, 0x05, 0x58, + 0x00, 0x08, 0x05, 0x60, 0x00, 0x08, 0x05, 0x68, 0x00, 0x08, 0x05, 0x70, 0x00, 0x08, 0x05, 0x78, + 0x00, 0x08, 0x05, 0x80, 0x00, 0x08, 0x05, 0x88, 0x00, 0x08, 0x05, 0x90, 0x00, 0x08, 0x05, 0x98, + 0x00, 0x08, 0x05, 0xa0, 0x00, 0x08, 0x05, 0xa8, 0x00, 0x08, 0x05, 0xb0, 0x00, 0x08, 0x05, 0xb8, + 0x00, 0x08, 0x05, 0xc0, 0x00, 0x08, 0x05, 0xc8, 0x00, 0x08, 0x05, 0xd0, 0x00, 0x08, 0x05, 0xd8, + 0x00, 0x08, 0x05, 0xe0, 0x00, 0x08, 0x05, 0xe8, 0x00, 0x08, 0x00, 0x38, 0x00, 0x08, 0x03, 0x00, + 0x00, 0x08, 0x03, 0x00, 0x00, 0x00, 0x03, 0xec, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x62, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf2 +}; + // 8x8 font patterns static const uint8 fontData_Sierra[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -546,6 +721,7 @@ static const uint8 fontData_FanGames[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x7E, 0x00 }; +#if 0 static const uint8 fontData_Mickey[] = { 0x00, 0x36, 0x7F, 0x7F, 0x3E, 0x1C, 0x08, 0x00, 0x00, 0x00, 0x3F, 0x20, 0x2F, 0x28, 0x28, 0x28, @@ -804,6 +980,7 @@ static const uint8 fontData_Mickey[] = { 0x08, 0x18, 0x38, 0x78, 0x38, 0x18, 0x08, 0x00, 0x10, 0x18, 0x1C, 0x1E, 0x1C, 0x18, 0x10, 0x00, }; +#endif static const uint8 fontData_IBM[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, diff --git a/engines/agi/global.cpp b/engines/agi/global.cpp index 7d55316d7b..bf6356dbcd 100644 --- a/engines/agi/global.cpp +++ b/engines/agi/global.cpp @@ -48,17 +48,17 @@ void AgiBase::flipflag(int n) { *set ^= 1 << (n & 0x07); // flip bit } -void AgiEngine::setvar(int var, int val) { - _game.vars[var] = val; +void AgiEngine::setVar(int16 varNr, int val) { + _game.vars[varNr] = val; - if (var == vVolume) { + if (varNr == VM_VAR_VOLUME) { _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, val * 17); _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, val * 17); } } -int AgiEngine::getvar(int var) { - return _game.vars[var]; +int AgiEngine::getVar(int16 varNr) { + return _game.vars[varNr]; } void AgiEngine::decrypt(uint8 *mem, int len) { 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 diff --git a/engines/agi/graphics.h b/engines/agi/graphics.h index 506a9d93d6..a89a7fdabc 100644 --- a/engines/agi/graphics.h +++ b/engines/agi/graphics.h @@ -27,6 +27,11 @@ namespace Agi { +#define SCRIPT_WIDTH 160 +#define SCRIPT_HEIGHT 168 +#define DISPLAY_WIDTH 320 +#define DISPLAY_HEIGHT 200 + #define GFX_WIDTH 320 #define GFX_HEIGHT 200 #define CHAR_COLS 8 @@ -34,66 +39,115 @@ namespace Agi { class AgiEngine; +enum GfxScreenMasks { + GFX_SCREEN_MASK_VISUAL = 1, + GFX_SCREEN_MASK_PRIORITY = 2, + GFX_SCREEN_MASK_ALL = GFX_SCREEN_MASK_VISUAL|GFX_SCREEN_MASK_PRIORITY +}; + +struct MouseCursorData { + const byte *bitmapData; + uint16 width; + uint16 height; + int hotspotX; + int hotspotY; +}; + class GfxMgr { private: AgiBase *_vm; - uint8 _palette[256 * 3]; - uint8 *_agiScreen; - unsigned char *_screen; - - uint8 *_shakeH, *_shakeV; + uint8 _paletteGfxMode[256 * 3]; + uint8 _paletteTextMode[256 * 3]; uint8 _agipalPalette[16 * 3]; int _agipalFileNum; - int _currentCursorPalette; // 0 - palette not set, 1 - PC, 2 - Amiga - -private: - void rawDrawButton(int x, int y, const char *s, int fgcolor, int bgcolor, bool border, int textOffset); public: GfxMgr(AgiBase *vm); - void gfxPutBlock(int x1, int y1, int x2, int y2); - - void putTextCharacter(int, int, int, unsigned char, int, int, bool checkerboard = false, const uint8 *font = fontData_Sierra); - void shakeScreen(int); - void shakeStart(); - void shakeEnd(); - void saveScreen(); - void restoreScreen(); - int initVideo(); int deinitVideo(); - void scheduleUpdate(int, int, int, int); - void doUpdate(); - void putScreen(); - void flushBlock(int, int, int, int); - void flushBlockA(int, int, int, int); - void putPixelsA(int, int, int, uint8 *); - void flushScreen(); - void clearScreen(int); - void clearConsoleScreen(int); - void drawBox(int, int, int, int, int, int, int); - void drawDefaultStyleButton(int, int, const char *, int, int, int fgcolor = 0, int bgcolor = 0); - void drawCurrentStyleButton(int x, int y, const char *label, bool hasFocus, bool pressed = false, bool positive = true); - int testButton(int, int, const char *); - void drawRectangle(int, int, int, int, int); - void saveBlock(int, int, int, int, uint8 *); - void restoreBlock(int, int, int, int, uint8 *); - void initPalette(const uint8 *p, uint colorCount = 16, uint fromBits = 6, uint toBits = 8); + void initPalette(uint8 *destPalette, const uint8 *paletteData, uint colorCount = 16, uint fromBits = 6, uint toBits = 8); void setAGIPal(int); int getAGIPalFileNum(); - void drawFrame(int x1, int y1, int x2, int y2, int c1, int c2); + void setPalette(bool GfxModePalette); - void putBlock(int x1, int y1, int x2, int y2); - void gfxSetPalette(); - void setCursor(bool amigaStyleCursor = false, bool busy = false); - void setCursorPalette(bool amigaStylePalette = false); + void initMouseCursor(MouseCursorData *mouseCursor, const byte *bitmapData, uint16 width, uint16 height, int hotspotX, int hotspotY); + void setMouseCursor(bool busy = false); - void printCharacter(int, int, char, int, int); int initMachine(); int deinitMachine(); + + void setRenderStartOffset(uint16 offsetY); + +private: + uint _pixels; + //uint16 _displayWidth; + //uint16 _displayHeight; + uint _displayPixels; + + byte *_activeScreen; + byte *_visualScreen; // 160x168 + byte *_priorityScreen; // 160x168 + byte *_displayScreen; // 320x200 + + bool _priorityTableSet; + uint8 _priorityTable[SCRIPT_HEIGHT]; /**< priority table */ + + MouseCursorData _mouseCursor; + MouseCursorData _mouseCursorBusy; + + uint16 _renderStartOffsetY; + +public: + void debugShowMap(int mapNr); + + void clear(byte color, byte priority); + void clearDisplay(byte color, bool copyToScreen = true); + void putPixel(int16 x, int16 y, byte drawMask, byte color, byte priority); + void putPixelOnDisplay(int16 x, int16 y, byte color); + + byte getColor(int16 x, int16 y); + byte getPriority(int16 x, int16 y); + bool checkControlPixel(int16 x, int16 y, byte newPriority); + + byte getCGAMixtureColor(byte color); + + void render_Block(int16 x, int16 y, int16 width, int16 height, bool copyToScreen = true); + bool render_Clip(int16 &x, int16 &y, int16 &width, int16 &height, int16 clipAgainstWidth = SCRIPT_WIDTH, int16 clipAgainstHeight = SCRIPT_HEIGHT); + +private: + void render_BlockEGA(int16 x, int16 y, int16 width, int16 height, bool copyToScreen); + void render_BlockCGA(int16 x, int16 y, int16 width, int16 height, bool copyToScreen); + +public: + void transition_Amiga(); + void transition_AtariSt(); + + void block_save(int16 x, int16 y, int16 width, int16 height, byte *bufferPtr); + void block_restore(int16 x, int16 y, int16 width, int16 height, byte *bufferPtr); + + void copyDisplayRectToScreen(int16 x, int16 y, int16 width, int16 height); + + void drawBox(int16 x, int16 y, int16 width, int16 height, byte backgroundColor, byte lineColor); + void drawRect(int16 x, int16 y, int16 width, int16 height, byte color); + void drawDisplayRect(int16 x, int16 y, int16 width, int16 height, byte color); +private: + void drawDisplayRectEGA(int16 x, int16 y, int16 width, int16 height, byte color); + void drawDisplayRectCGA(int16 x, int16 y, int16 width, int16 height, byte color); + +public: + void drawCharacter(int16 row, int16 column, byte character, byte foreground, byte background, bool disabledLook); + + void shakeScreen(int16 repeatCount); + void updateScreen(); + + void initPriorityTable(); + void setPriorityTable(int16 priorityBase); + void setPriority(int16 yPos, int16 priority); + int16 priorityToY(int16 priority); + int16 priorityFromY(int16 yPos); }; } // End of namespace Agi diff --git a/engines/agi/inv.cpp b/engines/agi/inv.cpp index f1e4e5094b..614b016463 100644 --- a/engines/agi/inv.cpp +++ b/engines/agi/inv.cpp @@ -22,221 +22,193 @@ #include "agi/agi.h" #include "agi/graphics.h" +#include "agi/inv.h" +#include "agi/text.h" #include "agi/keyboard.h" +#include "agi/systemui.h" namespace Agi { -// -// Messages and coordinates -// +InventoryMgr::InventoryMgr(AgiEngine *agi, GfxMgr *gfx, TextMgr *text, SystemUI *systemUI) { + _vm = agi; + _gfx = gfx; + _text = text; + _systemUI = systemUI; +} -#define NOTHING_X 16 -#define NOTHING_Y 3 -#define NOTHING_MSG "nothing" +InventoryMgr::~InventoryMgr() { +} -#define ANY_KEY_X 4 -#define ANY_KEY_Y 24 -#define ANY_KEY_MSG "Press a key to return to the game" +void InventoryMgr::getPlayerInventory() { + AgiGame game = _vm->_game; + int16 selectedInventoryItem = _vm->getVar(VM_VAR_SELECTED_INVENTORY_ITEM); + uint16 objectNr = 0; + int16 curRow = 2; // starting at position 2,1 + int16 curColumn = 1; + + _array.clear(); + _activeItemNr = 0; + + for (objectNr = 0; objectNr < game.numObjects; objectNr++) { + if (_vm->objectGetLocation(objectNr) == EGO_OWNED) { + // item is in the possession of ego, so add it to our internal list + if (objectNr == selectedInventoryItem) { + // it's the currently selected inventory item, remember that + _activeItemNr = _array.size(); + } -#define YOUHAVE_X 11 -#define YOUHAVE_Y 0 -#define YOUHAVE_MSG "You are carrying:" + InventoryEntry inventoryEntry; -#define SELECT_X 2 -#define SELECT_Y 24 -#define SELECT_MSG "Press ENTER to select, ESC to cancel" + inventoryEntry.objectNr = objectNr; + inventoryEntry.name = _vm->objectName(objectNr); + inventoryEntry.row = curRow; + inventoryEntry.column = curColumn; + if (inventoryEntry.column > 1) { + // right side, adjust column accordingly + inventoryEntry.column -= strlen( inventoryEntry.name ); + } + _array.push_back(inventoryEntry); + + // go to next position + if (curColumn == 1) { + // current position is left side, go to right side + curColumn = 39; + } else { + // current position is right side, so go to left side again and new row + curColumn = 1; + curRow++; + } + } + } -#define NOTHING_X_RU 16 -#define NOTHING_Y_RU 3 -#define NOTHING_MSG_RU "\xad\xa8\xe7\xa5\xa3\xae" + if (_array.size() == 0) { + // empty inventory + InventoryEntry inventoryEntry; -#define ANY_KEY_X_RU 4 -#define ANY_KEY_Y_RU 24 -#define ANY_KEY_MSG_RU "\x8b\xee\xa1\xa0\xef \xaa\xab\xa0\xa2\xa8\xe8\xa0 - \xa2\xae\xa7\xa2\xe0\xa0\xe2 \xa2 \xa8\xa3\xe0\xe3." + inventoryEntry.objectNr = 0; + inventoryEntry.name = _systemUI->getInventoryTextNothing(); + inventoryEntry.row = 2; + inventoryEntry.column = 19 - (strlen(inventoryEntry.name) / 2); + _array.push_back(inventoryEntry); + } +} -#define YOUHAVE_X_RU 11 -#define YOUHAVE_Y_RU 0 -#define YOUHAVE_MSG_RU " \x93 \xa2\xa0\xe1 \xa5\xe1\xe2\xec: " +void InventoryMgr::drawAll() { + int16 inventoryCount = _array.size(); + int16 inventoryNr = 0; -#define SELECT_X_RU 2 -#define SELECT_Y_RU 24 -#define SELECT_MSG_RU "ENTER - \xa2\xeb\xa1\xe0\xa0\xe2\xec, ESC - \xae\xe2\xac\xa5\xad\xa8\xe2\xec." + _text->charPos_Set(0, 11); + _text->displayText(_systemUI->getInventoryTextYouAreCarrying()); -void AgiEngine::printItem(int n, int fg, int bg) { - printText(objectName(_intobj[n]), 0, ((n % 2) ? 39 - strlen(objectName(_intobj[n])) : 1), - (n / 2) + 2, 40, fg, bg); + for (inventoryNr = 0; inventoryNr < inventoryCount; inventoryNr++) { + drawItem(inventoryNr); + } } -int AgiEngine::findItem() { - int r, c; +void InventoryMgr::drawItem(int16 itemNr) { + if (itemNr == _activeItemNr) { + _text->charAttrib_Set(15, 0); + } else { + _text->charAttrib_Set(0, 15); + } - r = _mouse.y / CHAR_LINES; - c = _mouse.x / CHAR_COLS; + _text->charPos_Set(_array[itemNr].row, _array[itemNr].column); + // original interpreter used printf here + // this doesn't really make sense, because for length calculation it's using strlen without printf + // which means right-aligned inventory items on the right side would not be displayed properly + // in case printf-formatting was actually used + // I have to assume that no game uses this, because behavior in original interpreter would have been buggy. + _text->displayText(_array[itemNr].name); +} - debugC(6, kDebugLevelInventory, "r = %d, c = %d", r, c); +void InventoryMgr::show() { + bool selectItems = false; - if (r < 2) - return -1; + // figure out current inventory of the player + getPlayerInventory(); - return (r - 2) * 2 + (c > 20); -} + if (_vm->getflag(VM_FLAG_STATUS_SELECTS_ITEMS)) { + selectItems = true; + } else{ + _activeItemNr = -1; // so that none is shown as active + } -int AgiEngine::showItems() { - unsigned int x, i; + drawAll(); - for (x = i = 0; x < _game.numObjects; x++) { - if (objectGetLocation(x) == EGO_OWNED) { - // add object to our list! - _intobj[i] = x; - printItem(i, STATUS_FG, STATUS_BG); - i++; - } + _text->charAttrib_Set(0, 15); + if (selectItems) { + _text->charPos_Set(24, 2); + _text->displayText(_systemUI->getInventoryTextSelectItems()); + } else { + _text->charPos_Set(24, 4); + _text->displayText(_systemUI->getInventoryTextReturnToGame()); } - if (i == 0) { - switch (getLanguage()) { - case Common::RU_RUS: - printText(NOTHING_MSG_RU, 0, NOTHING_X_RU, NOTHING_Y_RU, 40, STATUS_FG, STATUS_BG); - break; - default: - printText(NOTHING_MSG, 0, NOTHING_X, NOTHING_Y, 40, STATUS_FG, STATUS_BG); - break; - } - } + if (selectItems) { + _vm->cycleInnerLoopActive(CYCLE_INNERLOOP_INVENTORY); - return i; -} + do { + _vm->mainCycle(); + } while (_vm->cycleInnerLoopIsActive() && !(_vm->shouldQuit() || _vm->_restartGame)); -void AgiEngine::selectItems(int n) { - int fsel = 0; - bool exit_select = false; - - while (!exit_select && !(shouldQuit() || _restartGame)) { - if (n > 0) - printItem(fsel, STATUS_BG, STATUS_FG); - - switch (waitAnyKey()) { - case KEY_ENTER: - setvar(vSelItem, _intobj[fsel]); - exit_select = true; - break; - case KEY_ESCAPE: - setvar(vSelItem, 0xff); - exit_select = true; - break; - case KEY_UP: - if (fsel >= 2) - fsel -= 2; - break; - case KEY_DOWN: - if (fsel + 2 < n) - fsel += 2; - break; - case KEY_LEFT: - if (fsel % 2 == 1) - fsel--; - break; - case KEY_RIGHT: - if (fsel % 2 == 0 && fsel + 1 < n) - fsel++; - break; - case BUTTON_LEFT:{ - int i = findItem(); - if (i >= 0 && i < n) { - setvar(vSelItem, _intobj[fsel = i]); - debugC(6, kDebugLevelInventory, "item found: %d", fsel); - showItems(); - printItem(fsel, STATUS_BG, STATUS_FG); - _gfx->doUpdate(); - exit_select = true; - } - break; - } - default: - break; + if (_activeItemNr >= 0) { + // pass selected object number + _vm->setVar(VM_VAR_SELECTED_INVENTORY_ITEM, _array[_activeItemNr].objectNr); + } else { + // nothing was selected + _vm->setVar(VM_VAR_SELECTED_INVENTORY_ITEM, 0xff); } - if (!exit_select) { - showItems(); - _gfx->doUpdate(); - } + } else { + // no selection is supposed to be possible, just wait for key and exit + _vm->waitAnyKey(); } - - debugC(6, kDebugLevelInventory, "selected: %d", fsel); } -/* - * Public functions - */ - -/** - * Display inventory items. - */ -void AgiEngine::inventory() { - int oldFg, oldBg; - int n; - - // screen is white with black text - oldFg = _game.colorFg; - oldBg = _game.colorBg; - _game.colorFg = 0; - _game.colorBg = 15; - _gfx->clearScreen(_game.colorBg); - - switch (getLanguage()) { - case Common::RU_RUS: - printText(YOUHAVE_MSG_RU, 0, YOUHAVE_X_RU, YOUHAVE_Y_RU, 40, STATUS_FG, STATUS_BG); - break; - default: - printText(YOUHAVE_MSG, 0, YOUHAVE_X, YOUHAVE_Y, 40, STATUS_FG, STATUS_BG); +void InventoryMgr::charPress(int16 newChar) { + switch (newChar) { + case AGI_KEY_ENTER: { + _vm->cycleInnerLoopInactive(); // exit show-loop break; } - // FIXME: doesn't check if objects overflow off screen... - - _intobj = (uint8 *)malloc(4 + _game.numObjects); - memset(_intobj, 0, (4 + _game.numObjects)); - - n = showItems(); + case AGI_KEY_ESCAPE: { + _vm->cycleInnerLoopInactive(); // exit show-loop + _activeItemNr = -1; // no item selected + break; + } - switch (getLanguage()) { - case Common::RU_RUS: - if (getflag(fStatusSelectsItems)) { - printText(SELECT_MSG_RU, 0, SELECT_X_RU, SELECT_Y_RU, 40, STATUS_FG, STATUS_BG); - } else { - printText(ANY_KEY_MSG_RU, 0, ANY_KEY_X_RU, ANY_KEY_Y_RU, 40, STATUS_FG, STATUS_BG); - } + case AGI_KEY_UP: + changeActiveItem(-2); break; + case AGI_KEY_DOWN: + changeActiveItem(+2); + break; + case AGI_KEY_LEFT: + changeActiveItem(-1); + break; + case AGI_KEY_RIGHT: + changeActiveItem(+1); + break; + default: - if (getflag(fStatusSelectsItems)) { - printText(SELECT_MSG, 0, SELECT_X, SELECT_Y, 40, STATUS_FG, STATUS_BG); - } else { - printText(ANY_KEY_MSG, 0, ANY_KEY_X, ANY_KEY_Y, 40, STATUS_FG, STATUS_BG); - } break; } +} - _gfx->flushScreen(); - - // If flag 13 is set, we want to highlight & select an item. - // opon selection, put objnum in var 25. Then on esc put in - // var 25 = 0xff. - - if (getflag(fStatusSelectsItems)) - selectItems(n); - - free(_intobj); +void InventoryMgr::changeActiveItem(int16 direction) { + int16 orgItemNr = _activeItemNr; - if (!getflag(fStatusSelectsItems)) - waitAnyKey(); + _activeItemNr += direction; - _gfx->clearScreen(0); - writeStatus(); - _picture->showPic(); - _game.colorFg = oldFg; - _game.colorBg = oldBg; - _game.hasPrompt = 0; - flushLines(_game.lineUserInput, 24); + if ((_activeItemNr >= 0) && (_activeItemNr < (int16)_array.size())) { + // within bounds + drawItem(orgItemNr); + drawItem(_activeItemNr); + } else { + // out of bounds, revert change + _activeItemNr = orgItemNr; + } } } // End of namespace Agi diff --git a/engines/agi/inv.h b/engines/agi/inv.h new file mode 100644 index 0000000000..0c89275b7c --- /dev/null +++ b/engines/agi/inv.h @@ -0,0 +1,61 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef AGI_INV_H +#define AGI_INV_H + +namespace Agi { + +struct InventoryEntry { + uint16 objectNr; + int16 row; + int16 column; + const char *name; +}; +typedef Common::Array<InventoryEntry> InventoryArray; + +class InventoryMgr { +private: + GfxMgr *_gfx; + TextMgr *_text; + AgiEngine *_vm; + SystemUI *_systemUI; + + InventoryArray _array; + int16 _activeItemNr; + +public: + InventoryMgr(AgiEngine *agi, GfxMgr *gfx, TextMgr *text, SystemUI *systemUI); + ~InventoryMgr(); + + void getPlayerInventory(); + void drawAll(); + void drawItem(int16 itemNr); + void show(); + + void charPress(int16 newChar); + void changeActiveItem(int16 direction); +}; + +} // End of namespace Agi + +#endif /* AGI_INV_H */ diff --git a/engines/agi/keyboard.cpp b/engines/agi/keyboard.cpp index 0aa521bdc8..c307b077b1 100644 --- a/engines/agi/keyboard.cpp +++ b/engines/agi/keyboard.cpp @@ -20,12 +20,13 @@ * */ +#include "common/events.h" + #include "agi/agi.h" #include "agi/graphics.h" #include "agi/keyboard.h" -#ifdef __DS__ -#include "wordcompletion.h" -#endif +#include "agi/menu.h" +#include "agi/text.h" namespace Agi { @@ -61,21 +62,230 @@ const uint8 scancodeTable[26] = { 44 // Z }; -void AgiEngine::initWords() { - _game.numEgoWords = 0; -} +void AgiEngine::processEvents() { + Common::Event event; + int key = 0; -void AgiEngine::cleanInput() { - while (_game.numEgoWords) - free(_game.egoWords[--_game.numEgoWords].word); -} + while (_eventMan->pollEvent(event)) { + switch (event.type) { + case Common::EVENT_PREDICTIVE_DIALOG: { + GUI::PredictiveDialog _predictiveDialog; + _predictiveDialog.runModal(); +#if 0 + strcpy(_predictiveResult, _predictiveDialog.getResult()); + if (strcmp(_predictiveResult, "")) { + if (_game.inputMode == INPUTMODE_NORMAL) { + //strcpy((char *)_game.inputBuffer, _predictiveResult); + //handleKeys(KEY_ENTER); + // TODO: repair predictive + } else if (_game.inputMode == INPUTMODE_GETSTRING) { + strcpy(_game.strings[_stringdata.str], _predictiveResult); + newInputMode(INPUTMODE_NORMAL); + //_gfx->printCharacter(_stringdata.x + strlen(_game.strings[_stringdata.str]) + 1, + // _stringdata.y, ' ', 15, 0); + } else if (_game.inputMode == INPUTMODE_NONE) { + for (int n = 0; _predictiveResult[n]; n++) + keyEnqueue(_predictiveResult[n]); + } + } +#endif + /* + if (predictiveDialog()) { + if (_game.inputMode == INPUT_NORMAL) { + strcpy((char *)_game.inputBuffer, _predictiveResult); + handleKeys(KEY_ENTER); + } else if (_game.inputMode == INPUT_GETSTRING) { + strcpy(_game.strings[_stringdata.str], _predictiveResult); + newInputMode(INPUT_NORMAL); + _gfx->printCharacter(_stringdata.x + strlen(_game.strings[_stringdata.str]) + 1, + _stringdata.y, ' ', _game.colorFg, _game.colorBg); + } else if (_game.inputMode == INPUT_NONE) { + for (int n = 0; _predictiveResult[n]; n++) + keyEnqueue(_predictiveResult[n]); + } + } + */ + } + break; + case Common::EVENT_LBUTTONDOWN: + if (_game.mouseEnabled) { + key = AGI_MOUSE_BUTTON_LEFT; + _mouse.button = kAgiMouseButtonLeft; + keyEnqueue(key); + _mouse.x = event.mouse.x; + _mouse.y = event.mouse.y; + } + break; + case Common::EVENT_RBUTTONDOWN: + if (_game.mouseEnabled) { + key = AGI_MOUSE_BUTTON_RIGHT; + _mouse.button = kAgiMouseButtonRight; + keyEnqueue(key); + _mouse.x = event.mouse.x; + _mouse.y = event.mouse.y; + } + break; + case Common::EVENT_WHEELUP: + if (_game.mouseEnabled) { + key = AGI_MOUSE_WHEEL_UP; + keyEnqueue(key); + } + break; + case Common::EVENT_WHEELDOWN: + if (_game.mouseEnabled) { + key = AGI_MOUSE_WHEEL_DOWN; + keyEnqueue(key); + } + break; + case Common::EVENT_MOUSEMOVE: + if (_game.mouseEnabled) { + _mouse.x = event.mouse.x; + _mouse.y = event.mouse.y; + + if (!_game.mouseFence.isEmpty()) { + if (_mouse.x < _game.mouseFence.left) + _mouse.x = _game.mouseFence.left; + if (_mouse.x > _game.mouseFence.right) + _mouse.x = _game.mouseFence.right; + if (_mouse.y < _game.mouseFence.top) + _mouse.y = _game.mouseFence.top; + if (_mouse.y > _game.mouseFence.bottom) + _mouse.y = _game.mouseFence.bottom; + + g_system->warpMouse(_mouse.x, _mouse.y); + } + } + + break; + case Common::EVENT_LBUTTONUP: + case Common::EVENT_RBUTTONUP: + if (_game.mouseEnabled) { + _mouse.button = kAgiMouseButtonUp; + _mouse.x = event.mouse.x; + _mouse.y = event.mouse.y; + } + break; + case Common::EVENT_KEYDOWN: + if (event.kbd.hasFlags(Common::KBD_CTRL) && event.kbd.keycode == Common::KEYCODE_d) { + _console->attach(); + break; + } -void AgiEngine::getString(int x, int y, int len, int str) { - newInputMode(INPUT_GETSTRING); - _stringdata.x = x; - _stringdata.y = y; - _stringdata.len = len; - _stringdata.str = str; + if ((event.kbd.ascii) && (event.kbd.ascii <= 0xFF)) { + // No special key, directly accept it + // Is ISO-8859-1, we need lower 128 characters only, which is plain ASCII, so no mapping required + key = event.kbd.ascii; + + if (Common::isAlpha(key)) { + // Key is A-Z. + // Map Ctrl-A to 1, Ctrl-B to 2, etc. + if (event.kbd.flags & Common::KBD_CTRL) { + key = toupper(key) - 'A' + 1; + } else if (event.kbd.flags & Common::KBD_ALT) { + // Map Alt-A, Alt-B etc. to special scancode values according to an internal scancode table. + key = scancodeTable[toupper(key) - 'A'] << 8; + } + } + } else { + switch (key = event.kbd.keycode) { + case Common::KEYCODE_LEFT: + case Common::KEYCODE_KP4: + if (_allowSynthetic || !event.synthetic) + key = AGI_KEY_LEFT; + break; + case Common::KEYCODE_RIGHT: + case Common::KEYCODE_KP6: + if (_allowSynthetic || !event.synthetic) + key = AGI_KEY_RIGHT; + break; + case Common::KEYCODE_UP: + case Common::KEYCODE_KP8: + if (_allowSynthetic || !event.synthetic) + key = AGI_KEY_UP; + break; + case Common::KEYCODE_DOWN: + case Common::KEYCODE_KP2: + if (_allowSynthetic || !event.synthetic) + key = AGI_KEY_DOWN; + break; + case Common::KEYCODE_PAGEUP: + case Common::KEYCODE_KP9: + if (_allowSynthetic || !event.synthetic) + key = AGI_KEY_UP_RIGHT; + break; + case Common::KEYCODE_PAGEDOWN: + case Common::KEYCODE_KP3: + if (_allowSynthetic || !event.synthetic) + key = AGI_KEY_DOWN_RIGHT; + break; + case Common::KEYCODE_HOME: + case Common::KEYCODE_KP7: + if (_allowSynthetic || !event.synthetic) + key = AGI_KEY_UP_LEFT; + break; + case Common::KEYCODE_END: + case Common::KEYCODE_KP1: + if (_allowSynthetic || !event.synthetic) + key = AGI_KEY_DOWN_LEFT; + break; + case Common::KEYCODE_KP5: + key = AGI_KEY_STATIONARY; + break; + case Common::KEYCODE_F1: + key = AGI_KEY_F1; + break; + case Common::KEYCODE_F2: + key = AGI_KEY_F2; + break; + case Common::KEYCODE_F3: + key = AGI_KEY_F3; + break; + case Common::KEYCODE_F4: + key = AGI_KEY_F4; + break; + case Common::KEYCODE_F5: + key = AGI_KEY_F5; + break; + case Common::KEYCODE_F6: + key = AGI_KEY_F6; + break; + case Common::KEYCODE_F7: + key = AGI_KEY_F7; + break; + case Common::KEYCODE_F8: + key = AGI_KEY_F8; + break; + case Common::KEYCODE_F9: + key = AGI_KEY_F9; + break; + case Common::KEYCODE_F10: + key = AGI_KEY_F10; + break; + case Common::KEYCODE_F11: + key = AGI_KEY_F11; + break; + case Common::KEYCODE_F12: + key = AGI_KEY_F12; + break; + case Common::KEYCODE_KP_ENTER: + key = AGI_KEY_ENTER; + break; + default: + break; + } + } + if (key) + keyEnqueue(key); + break; + + case Common::EVENT_KEYUP: + if (_egoHoldKey) + _game.screenObjTable[SCREENOBJECTS_EGO_ENTRY].direction = 0; + + default: + break; + } + } } /** @@ -97,54 +307,128 @@ int AgiEngine::doPollKeyboard() { return key; } -int AgiEngine::handleController(int key) { - VtEntry *v = &_game.viewTable[0]; - int i; +int16 AgiEngine::getSpecialMenuControllerSlot() { + int16 controllerSlotESC = -1; + int16 controllerSlotSpecial = -1; + + for (uint16 curMapping = 0; curMapping < MAX_CONTROLLER_KEYMAPPINGS; curMapping++) { + if (_game.controllerKeyMapping[curMapping].keycode == _game.specialMenuTriggerKey) { + if (controllerSlotSpecial < 0) { + controllerSlotSpecial = _game.controllerKeyMapping[curMapping].controllerSlot; + } + } + if (_game.controllerKeyMapping[curMapping].keycode == AGI_MENU_TRIGGER_PC) { + if (controllerSlotESC < 0) { + controllerSlotESC = _game.controllerKeyMapping[curMapping].controllerSlot; + } + } + } + if (controllerSlotSpecial >= 0) { + // special menu controller slot found + if (controllerSlotSpecial != controllerSlotESC) { + // not the same as the ESC slot (is the same in Manhunter AppleIIgs, we need to replace "pause" + if (controllerSlotSpecial >= 10) { + // slot needs to be at least 10 + // Atari ST SQ1 maps the special key, but doesn't trigger any menu with it + // the controller slot in this case is 8. + return controllerSlotSpecial; + } + } + } + return -1; +} + +bool AgiEngine::handleController(uint16 key) { + ScreenObjEntry *screenObjEgo = &_game.screenObjTable[SCREENOBJECTS_EGO_ENTRY]; + + if (key == 0) // nothing pressed + return false; + + // This previously skipped processing, when ESC was pressed and called menu directly. + // This original approach was bad, because games check different flags before actually allowing the + // user to enter the menu. We checked a few common flags, like for example the availability of the prompt. + // But this stopped the user being able to enter the menu, when the original interpreter actually allowed it. + // We now instead implement this feature using another way for those platforms. + if (key == AGI_KEY_ESCAPE) { + // Escape pressed, user probably wants to trigger the menu + // For PC, just passing ASCII code for ESC will normally trigger a controller + // and the scripts will then trigger the menu + // For other platforms, ESC was handled by platforms to trigger "pause game" instead + // We need to change ESC to a platform specific code to make it work properly. + // + // There are exceptions though. Mixed Up Mother Goose on AppleIIgs for example actually sets up + // ESC for pause only. That's why we also check, if the key is actually mapped to a controller. + // For this special case, we actually replace the pause function with a menu trigger. + // Replacing "pause" all the time wouldn't work out as well, becaue games like KQ1 on Apple IIgs + // actually disable "pause" when ego has been killed, which means we wouldn't be able to access + // the menu anymore in that case. + if (_menu->isAvailable()) { + // menu is actually available + if (_game.specialMenuTriggerKey) { + int16 specialMenuControllerSlot = getSpecialMenuControllerSlot(); + + if (specialMenuControllerSlot >= 0) { + // menu trigger found, trigger it now + _game.controllerOccured[specialMenuControllerSlot] = true; + return true; + } + } + // Otherwise go on and look for the ESC controller + } + } + // AGI 3.149 games, The Black Cauldron and King's Quest 4 need KEY_ESCAPE to use menus // Games with the GF_ESCPAUSE flag need KEY_ESCAPE to pause the game - if (key == 0 || - (key == KEY_ESCAPE && getVersion() != 0x3149 && getGameID() != GID_BC && getGameID() != GID_KQ4 && !(getFeatures() & GF_ESCPAUSE)) ) - return false; + // (key == KEY_ESCAPE && getVersion() != 0x3149 && getGameID() != GID_BC && getGameID() != GID_KQ4 && !(getFeatures() & GF_ESCPAUSE)) ) + // return false; - if ((getGameID() == GID_MH1 || getGameID() == GID_MH2) && (key == KEY_ENTER) && - (_game.inputMode == INPUT_NONE)) { + if ((getGameID() == GID_MH1 || getGameID() == GID_MH2) && (key == AGI_KEY_ENTER) && + (_game.inputMode == INPUTMODE_NONE)) { key = 0x20; // Set Enter key to Space in Manhunter when there's no text input } debugC(3, kDebugLevelInput, "key = %04x", key); - for (i = 0; i < MAX_CONTROLLERS; i++) { - if (_game.controllers[i].keycode == key) { - debugC(3, kDebugLevelInput, "event %d: key press", _game.controllers[i].controller); - _game.controllerOccured[_game.controllers[i].controller] = true; + for (uint16 curMapping = 0; curMapping < MAX_CONTROLLER_KEYMAPPINGS; curMapping++) { + if (_game.controllerKeyMapping[curMapping].keycode == key) { + debugC(3, kDebugLevelInput, "event %d: key press", _game.controllerKeyMapping[curMapping].controllerSlot); + _game.controllerOccured[_game.controllerKeyMapping[curMapping].controllerSlot] = true; return true; } } - if (key == BUTTON_LEFT) { - if ((getflag(fMenusWork) || (getFeatures() & GF_MENUS)) && _mouse.y <= CHAR_LINES) { - newInputMode(INPUT_MENU); - return true; - } + if (key == AGI_MOUSE_BUTTON_LEFT) { + // call mouse when click is done on status bar + // TODO + // This should be done in a better way as in simulate ESC key + // Sierra seems to have hardcoded it in some way, but we would have to verify, what flags + // they checked. The previous way wasn't accurate. Mouse support for menu is missing atm anyway. + //if ((getflag(VM_FLAG_MENUS_WORK) || (getFeatures() & GF_MENUS)) && _mouse.y <= CHAR_LINES) { + // newInputMode(INPUTMODE_MENU); + // return true; + //} } // Show predictive dialog if the user clicks on input area - if (key == BUTTON_LEFT && - (int)_mouse.y >= _game.lineUserInput * CHAR_LINES && - (int)_mouse.y <= (_game.lineUserInput + 1) * CHAR_LINES) { + if (key == AGI_MOUSE_BUTTON_LEFT && + (int)_mouse.y >= _text->promptRow_Get() * FONT_DISPLAY_HEIGHT && + (int)_mouse.y <= (_text->promptRow_Get() + 1) * FONT_DISPLAY_HEIGHT) { GUI::PredictiveDialog _predictiveDialog; _predictiveDialog.runModal(); +#if 0 strcpy(_predictiveResult, _predictiveDialog.getResult()); if (strcmp(_predictiveResult, "")) { - if (_game.inputMode == INPUT_NONE) { + if (_game.inputMode == INPUTMODE_NONE) { for (int n = 0; _predictiveResult[n]; n++) keyEnqueue(_predictiveResult[n]); } else { - strcpy((char *)_game.inputBuffer, _predictiveResult); - handleKeys(KEY_ENTER); + //strcpy((char *)_game.inputBuffer, _predictiveResult); + //handleKeys(KEY_ENTER); + // TODO } } +#endif /* if (predictiveDialog()) { if (_game.inputMode == INPUT_NONE) { @@ -160,57 +444,73 @@ int AgiEngine::handleController(int key) { } if (_game.playerControl) { - int d = 0; + int16 newDirection = 0; - if (!KEY_ASCII(key)) { - switch (key) { - case KEY_UP: - d = 1; - break; - case KEY_DOWN: - d = 5; - break; - case KEY_LEFT: - d = 7; - break; - case KEY_RIGHT: - d = 3; - break; - case KEY_UP_RIGHT: - d = 2; - break; - case KEY_DOWN_RIGHT: - d = 4; - break; - case KEY_UP_LEFT: - d = 8; - break; - case KEY_DOWN_LEFT: - d = 6; - break; - } + switch (key) { + case AGI_KEY_UP: + newDirection = 1; + break; + case AGI_KEY_DOWN: + newDirection = 5; + break; + case AGI_KEY_LEFT: + newDirection = 7; + break; + case AGI_KEY_RIGHT: + newDirection = 3; + break; + case AGI_KEY_UP_RIGHT: + newDirection = 2; + break; + case AGI_KEY_DOWN_RIGHT: + newDirection = 4; + break; + case AGI_KEY_UP_LEFT: + newDirection = 8; + break; + case AGI_KEY_DOWN_LEFT: + newDirection = 6; + break; + default: + break; } if (!(getFeatures() & GF_AGIMOUSE)) { // Handle mouse button events - if (key == BUTTON_LEFT) { - if (getGameID() == GID_PQ1 && _game.vars[vCurRoom] == 116) { - // WORKAROUND: Special handling for mouse clicks in the newspaper - // screen of PQ1. Fixes bug #3018770. - d = 3; // fake a right arrow key (next page) - } else { - // Click-to-walk mouse interface - v->flags |= fAdjEgoXY; - v->parm1 = WIN_TO_PIC_X(_mouse.x); - v->parm2 = WIN_TO_PIC_Y(_mouse.y); - return true; + if (!_game.mouseHidden) { + if (key == AGI_MOUSE_BUTTON_LEFT) { + if (getGameID() == GID_PQ1 && _game.vars[VM_VAR_CURRENT_ROOM] == 116) { + // WORKAROUND: Special handling for mouse clicks in the newspaper + // screen of PQ1. Fixes bug #3018770. + newDirection = 3; // fake a right arrow key (next page) + + } else { + // Click-to-walk mouse interface + //v->flags |= fAdjEgoXY; + // setting fAdjEgoXY here will at least break "climbing the log" in SQ2 + // in case you walked to the log by using the mouse, so don't!!! + int16 egoDestinationX = _mouse.x; + int16 egoDestinationY = _mouse.y; + adjustPosToGameScreen(egoDestinationX, egoDestinationY); + + screenObjEgo->motionType = kMotionEgo; + if (egoDestinationX < (screenObjEgo->xSize / 2)) { + screenObjEgo->move_x = -1; + } else { + screenObjEgo->move_x = egoDestinationX - (screenObjEgo->xSize / 2); + } + screenObjEgo->move_y = egoDestinationY; + screenObjEgo->move_stepSize = screenObjEgo->stepSize; + return true; + } } } } - if (d || key == KEY_STATIONARY) { - v->flags &= ~fAdjEgoXY; - v->direction = v->direction == d ? 0 : d; + if (newDirection || key == AGI_KEY_STATIONARY) { + screenObjEgo->flags &= ~fAdjEgoXY; + screenObjEgo->direction = screenObjEgo->direction == newDirection ? 0 : newDirection; + screenObjEgo->motionType = kMotionNormal; return true; } } @@ -218,205 +518,22 @@ int AgiEngine::handleController(int key) { return false; } -void AgiEngine::handleGetstring(int key) { - static int pos = 0; // Cursor position - static char buf[40]; - - if (KEY_ASCII(key) == 0) - return; - - debugC(3, kDebugLevelInput, "handling key: %02x", key); - - switch (key) { - case BUTTON_LEFT: - if ((int)_mouse.y >= _stringdata.y * CHAR_LINES && - (int)_mouse.y <= (_stringdata.y + 1) * CHAR_LINES) { - GUI::PredictiveDialog _predictiveDialog; - _predictiveDialog.runModal(); - strcpy(_predictiveResult, _predictiveDialog.getResult()); - if (strcmp(_predictiveResult, "")) { - strcpy(_game.strings[_stringdata.str], _predictiveResult); - newInputMode(INPUT_NORMAL); - _gfx->printCharacter(_stringdata.x + strlen(_game.strings[_stringdata.str]) + 1, - _stringdata.y, ' ', _game.colorFg, _game.colorBg); - return; - } - /* - if (predictiveDialog()) { - strcpy(_game.strings[_stringdata.str], _predictiveResult); - newInputMode(INPUT_NORMAL); - _gfx->printCharacter(_stringdata.x + strlen(_game.strings[_stringdata.str]) + 1, - _stringdata.y, ' ', _game.colorFg, _game.colorBg); - return; - } - */ - } - break; - case KEY_ENTER: - debugC(3, kDebugLevelInput, "KEY_ENTER"); - _game.hasPrompt = 0; - buf[pos] = 0; - - strcpy(_game.strings[_stringdata.str], buf); - debugC(3, kDebugLevelInput, "buffer=[%s]", buf); - buf[pos = 0] = 0; - - newInputMode(INPUT_NORMAL); - _gfx->printCharacter(_stringdata.x + strlen(_game.strings[_stringdata.str]) + 1, - _stringdata.y, ' ', _game.colorFg, _game.colorBg); - return; - case KEY_ESCAPE: - debugC(3, kDebugLevelInput, "KEY_ESCAPE"); - _game.hasPrompt = 0; - buf[pos = 0] = 0; - - strcpy(_game.strings[_stringdata.str], buf); - newInputMode(INPUT_NORMAL); - - // newInputMode(INPUT_MENU); - break; - case KEY_BACKSPACE: // 0x08 - if (!pos) - break; - - _gfx->printCharacter(_stringdata.x + (pos + 1), _stringdata.y, - ' ', _game.colorFg, _game.colorBg); - pos--; - buf[pos] = 0; - break; - default: - if (key < 0x20 || key > 0x7f) - break; - - if (pos >= _stringdata.len) - break; - - buf[pos++] = key; - buf[pos] = 0; - - // Echo - _gfx->printCharacter(_stringdata.x + pos, _stringdata.y, buf[pos - 1], - _game.colorFg, _game.colorBg); - - break; - } - - // print cursor - _gfx->printCharacter(_stringdata.x + pos + 1, _stringdata.y, - (char)_game.cursorChar, _game.colorFg, _game.colorBg); -} - -void AgiEngine::handleKeys(int key) { - uint8 *p = NULL; - int c = 0; - static uint8 formattedEntry[40]; - int l = _game.lineUserInput; - int fg = _game.colorFg, bg = _game.colorBg; - int promptLength = strlen(agiSprintf(_game.strings[0])); - - setvar(vWordNotFound, 0); - - debugC(3, kDebugLevelInput, "handling key: %02x", key); - - switch (key) { - case KEY_ENTER: - debugC(3, kDebugLevelInput, "KEY_ENTER"); - _game.keypress = 0; - - // Remove all leading spaces - for (p = _game.inputBuffer; *p && *p == 0x20; p++) - ; - - // Copy to internal buffer - for (; *p && c < 40-1; p++) { - // Squash spaces - if (*p == 0x20 && *(p + 1) == 0x20) { - p++; - continue; - } - formattedEntry[c++] = tolower(*p); - } - formattedEntry[c++] = 0; - - // Handle string only if it's not empty - if (formattedEntry[0]) { - strcpy((char *)_game.echoBuffer, (const char *)_game.inputBuffer); - strcpy(_lastSentence, (const char *)formattedEntry); - dictionaryWords(_lastSentence); - } - - // Clear to start a new line - _game.hasPrompt = 0; - _game.inputBuffer[_game.cursorPos = 0] = 0; - debugC(3, kDebugLevelInput | kDebugLevelText, "clear lines"); - clearLines(l, l + 1, bg); - flushLines(l, l + 1); -#ifdef __DS__ - DS::findWordCompletions((char *) _game.inputBuffer); -#endif - - break; - case KEY_ESCAPE: - debugC(3, kDebugLevelInput, "KEY_ESCAPE"); - newInputMode(INPUT_MENU); - break; - case KEY_BACKSPACE: - // Ignore backspace at start of line - if (_game.cursorPos == 0) - break; - - // erase cursor - _gfx->printCharacter(_game.cursorPos + promptLength, l, ' ', fg, bg); - _game.inputBuffer[--_game.cursorPos] = 0; - - // Print cursor - _gfx->printCharacter(_game.cursorPos + promptLength, l, _game.cursorChar, fg, bg); - -#ifdef __DS__ - DS::findWordCompletions((char *) _game.inputBuffer); -#endif - break; - default: - // Ignore invalid keystrokes - if (key < 0x20 || key > 0x7f) - break; - - // Maximum input size reached - if (_game.cursorPos >= getvar(vMaxInputChars)) - break; - - _game.inputBuffer[_game.cursorPos++] = key; - _game.inputBuffer[_game.cursorPos] = 0; - -#ifdef __DS__ - DS::findWordCompletions((char *) _game.inputBuffer); -#endif - - // echo - _gfx->printCharacter(_game.cursorPos + promptLength - 1, l, _game.inputBuffer[_game.cursorPos - 1], fg, bg); - - // Print cursor - _gfx->printCharacter(_game.cursorPos + promptLength, l, _game.cursorChar, fg, bg); - break; - } -} - int AgiEngine::waitKey() { int key = 0; clearKeyQueue(); debugC(3, kDebugLevelInput, "waiting..."); - while (!(shouldQuit() || _restartGame || getflag(fRestoreJustRan))) { + while (!(shouldQuit() || _restartGame || getflag(VM_FLAG_RESTORE_JUST_RAN))) { pollTimer(); key = doPollKeyboard(); - if (key == KEY_ENTER || key == KEY_ESCAPE || key == BUTTON_LEFT) + if (key == AGI_KEY_ENTER || key == AGI_KEY_ESCAPE || key == AGI_MOUSE_BUTTON_LEFT) break; pollTimer(); updateTimer(); - _gfx->doUpdate(); + g_system->updateScreen(); } // Have to clear it as original did not set this variable, and we do it in doPollKeyboard() @@ -437,7 +554,7 @@ int AgiEngine::waitAnyKey() { key = doPollKeyboard(); if (key) break; - _gfx->doUpdate(); + g_system->updateScreen(); } // Have to clear it as original did not set this variable, and we do it in doPollKeyboard() diff --git a/engines/agi/keyboard.h b/engines/agi/keyboard.h index 89d6a89ce3..e5cf955a07 100644 --- a/engines/agi/keyboard.h +++ b/engines/agi/keyboard.h @@ -46,49 +46,50 @@ public: } }; -// QNX4 has a KEY_DOWN defined which we don't need to care about -#undef KEY_DOWN - -// Allegro defines these -#undef KEY_BACKSPACE -#undef KEY_ENTER -#undef KEY_LEFT -#undef KEY_RIGHT -#undef KEY_UP -#undef KEY_PGUP -#undef KEY_PGDN -#undef KEY_HOME -#undef KEY_END - -#define KEY_BACKSPACE 0x08 -#define KEY_ESCAPE 0x1B -#define KEY_ENTER 0x0D -#define KEY_UP 0x4800 -#define KEY_DOWN 0x5000 -#define KEY_LEFT 0x4B00 -#define KEY_STATIONARY 0x4C00 -#define KEY_RIGHT 0x4D00 - -#define KEY_DOWN_LEFT 0x4F00 -#define KEY_DOWN_RIGHT 0x5100 -#define KEY_UP_LEFT 0x4700 -#define KEY_UP_RIGHT 0x4900 - -#define KEY_STATUSLN 0xd900 // F11 -#define KEY_PRIORITY 0xda00 // F12 - -#define KEY_PGUP 0x4900 // Page Up (fixed by Ziv Barber) -#define KEY_PGDN 0x5100 // Page Down -#define KEY_HOME 0x4700 // Home -#define KEY_END 0x4f00 // End * - -#define BUTTON_LEFT 0xF101 // Left mouse button -#define BUTTON_RIGHT 0xF202 // Right mouse button -#define WHEEL_UP 0xF203 // Mouse wheel up -#define WHEEL_DOWN 0xF204 // Mouse wheel down - -#define KEY_SCAN(k) (k >> 8) -#define KEY_ASCII(k) (k & 0xff) +#define AGI_KEY_BACKSPACE 0x08 +#define AGI_KEY_ESCAPE 0x1B +#define AGI_KEY_ENTER 0x0D +#define AGI_KEY_UP 0x4800 +#define AGI_KEY_DOWN 0x5000 +#define AGI_KEY_LEFT 0x4B00 +#define AGI_KEY_STATIONARY 0x4C00 +#define AGI_KEY_RIGHT 0x4D00 + +#define AGI_KEY_DOWN_LEFT 0x4F00 +#define AGI_KEY_DOWN_RIGHT 0x5100 +#define AGI_KEY_UP_LEFT 0x4700 +#define AGI_KEY_UP_RIGHT 0x4900 + +#define AGI_KEY_F1 0x3B00 +#define AGI_KEY_F2 0x3C00 +#define AGI_KEY_F3 0x3D00 +#define AGI_KEY_F4 0x3E00 +#define AGI_KEY_F5 0x3F00 +#define AGI_KEY_F6 0x4000 +#define AGI_KEY_F7 0x4000 +#define AGI_KEY_F8 0x4100 +#define AGI_KEY_F9 0x4200 +#define AGI_KEY_F10 0x4300 +#define AGI_KEY_F11 0xd900 // F11 +#define AGI_KEY_F12 0xda00 // F12 + +#define AGI_KEY_PAGE_UP 0x4900 // Page Up (fixed by Ziv Barber) +#define AGI_KEY_PAGE_DOWN 0x5100 // Page Down +#define AGI_KEY_HOME 0x4700 // Home +#define AGI_KEY_END 0x4f00 // End * + +#define AGI_MOUSE_BUTTON_LEFT 0xF101 // Left mouse button +#define AGI_MOUSE_BUTTON_RIGHT 0xF202 // Right mouse button +#define AGI_MOUSE_WHEEL_UP 0xF203 // Mouse wheel up +#define AGI_MOUSE_WHEEL_DOWN 0xF204 // Mouse wheel down + +// special menu triggers +// Attention: at least Mixed Up Mother Goose on Apple IIgs actually hooks ESC for menu only +// Which is why we have to check, if the corresponding trigger is hooked before changing it +// And otherwise simply use the regular ESC. +#define AGI_MENU_TRIGGER_PC 0x001B // will trigger menu for PC +#define AGI_MENU_TRIGGER_APPLE2GS 0x0301 // will trigger menu for AppleIIgs + Amiga +#define AGI_MENU_TRIGGER_ATARIST 0x0101 // will trigger menu for Atari ST extern const uint8 scancodeTable[]; diff --git a/engines/agi/loader_v1.cpp b/engines/agi/loader_v1.cpp index 404fb6ee0e..fd418f3d59 100644 --- a/engines/agi/loader_v1.cpp +++ b/engines/agi/loader_v1.cpp @@ -21,6 +21,8 @@ */ #include "agi/agi.h" +#include "agi/words.h" + #include "common/md5.h" #define IMAGE_SIZE 368640 // = 40 * 2 * 9 * 512 = tracks * sides * sectors * sector size @@ -69,7 +71,7 @@ int AgiLoader_v1::loadDir_DDP(AgiDir *agid, int offset, int max) { return errBadFileOpen; // Cleanup - for (int i = 0; i < MAX_DIRS; i++) { + for (int i = 0; i < MAX_DIRECTORY_ENTRIES; i++) { agid[i].volume = 0xFF; agid[i].offset = _EMPTY; } @@ -103,7 +105,7 @@ int AgiLoader_v1::loadDir_BC(AgiDir *agid, int offset, int max) { return errBadFileOpen; // Cleanup - for (int i = 0; i < MAX_DIRS; i++) { + for (int i = 0; i < MAX_DIRECTORY_ENTRIES; i++) { agid[i].volume = 0xFF; agid[i].offset = _EMPTY; } @@ -197,84 +199,84 @@ uint8 *AgiLoader_v1::loadVolRes(struct AgiDir *agid) { return data; } -int AgiLoader_v1::loadResource(int t, int n) { +int AgiLoader_v1::loadResource(int16 resourceType, int16 resourceNr) { int ec = errOK; uint8 *data = NULL; - debugC(3, kDebugLevelResources, "(t = %d, n = %d)", t, n); - if (n >= MAX_DIRS) + debugC(3, kDebugLevelResources, "(t = %d, n = %d)", resourceType, resourceNr); + if (resourceNr >= MAX_DIRECTORY_ENTRIES) return errBadResource; - switch (t) { - case rLOGIC: - if (~_vm->_game.dirLogic[n].flags & RES_LOADED) { - debugC(3, kDebugLevelResources, "loading logic resource %d", n); - unloadResource(rLOGIC, n); + switch (resourceType) { + case RESOURCETYPE_LOGIC: + if (~_vm->_game.dirLogic[resourceNr].flags & RES_LOADED) { + debugC(3, kDebugLevelResources, "loading logic resource %d", resourceNr); + unloadResource(RESOURCETYPE_LOGIC, resourceNr); // load raw resource into data - data = loadVolRes(&_vm->_game.dirLogic[n]); + data = loadVolRes(&_vm->_game.dirLogic[resourceNr]); - _vm->_game.logics[n].data = data; - ec = data ? _vm->decodeLogic(n) : errBadResource; + _vm->_game.logics[resourceNr].data = data; + ec = data ? _vm->decodeLogic(resourceNr) : errBadResource; - _vm->_game.logics[n].sIP = 2; + _vm->_game.logics[resourceNr].sIP = 2; } // if logic was cached, we get here // reset code pointers incase it was cached - _vm->_game.logics[n].cIP = _vm->_game.logics[n].sIP; + _vm->_game.logics[resourceNr].cIP = _vm->_game.logics[resourceNr].sIP; break; - case rPICTURE: + case RESOURCETYPE_PICTURE: // if picture is currently NOT loaded *OR* cacheing is off, // unload the resource (caching == off) and reload it - debugC(3, kDebugLevelResources, "loading picture resource %d", n); - if (_vm->_game.dirPic[n].flags & RES_LOADED) + debugC(3, kDebugLevelResources, "loading picture resource %d", resourceNr); + if (_vm->_game.dirPic[resourceNr].flags & RES_LOADED) break; // if loaded but not cached, unload it // if cached but not loaded, etc - unloadResource(rPICTURE, n); - data = loadVolRes(&_vm->_game.dirPic[n]); + unloadResource(RESOURCETYPE_PICTURE, resourceNr); + data = loadVolRes(&_vm->_game.dirPic[resourceNr]); if (data != NULL) { - _vm->_game.pictures[n].rdata = data; - _vm->_game.dirPic[n].flags |= RES_LOADED; + _vm->_game.pictures[resourceNr].rdata = data; + _vm->_game.dirPic[resourceNr].flags |= RES_LOADED; } else { ec = errBadResource; } break; - case rSOUND: - debugC(3, kDebugLevelResources, "loading sound resource %d", n); - if (_vm->_game.dirSound[n].flags & RES_LOADED) + case RESOURCETYPE_SOUND: + debugC(3, kDebugLevelResources, "loading sound resource %d", resourceNr); + if (_vm->_game.dirSound[resourceNr].flags & RES_LOADED) break; - data = loadVolRes(&_vm->_game.dirSound[n]); + data = loadVolRes(&_vm->_game.dirSound[resourceNr]); if (data != NULL) { // Freeing of the raw resource from memory is delegated to the createFromRawResource-function - _vm->_game.sounds[n] = AgiSound::createFromRawResource(data, _vm->_game.dirSound[n].len, n, _vm->_soundemu); - _vm->_game.dirSound[n].flags |= RES_LOADED; + _vm->_game.sounds[resourceNr] = AgiSound::createFromRawResource(data, _vm->_game.dirSound[resourceNr].len, resourceNr, _vm->_soundemu); + _vm->_game.dirSound[resourceNr].flags |= RES_LOADED; } else { ec = errBadResource; } break; - case rVIEW: + case RESOURCETYPE_VIEW: // Load a VIEW resource into memory... // Since VIEWS alter the view table ALL the time // can we cache the view? or must we reload it all // the time? - if (_vm->_game.dirView[n].flags & RES_LOADED) + if (_vm->_game.dirView[resourceNr].flags & RES_LOADED) break; - debugC(3, kDebugLevelResources, "loading view resource %d", n); - unloadResource(rVIEW, n); - data = loadVolRes(&_vm->_game.dirView[n]); + debugC(3, kDebugLevelResources, "loading view resource %d", resourceNr); + unloadResource(RESOURCETYPE_VIEW, resourceNr); + data = loadVolRes(&_vm->_game.dirView[resourceNr]); if (data) { - _vm->_game.views[n].rdata = data; - _vm->_game.dirView[n].flags |= RES_LOADED; - ec = _vm->decodeView(n); + _vm->_game.dirView[resourceNr].flags |= RES_LOADED; + ec = _vm->decodeView(data, _vm->_game.dirView[resourceNr].len, resourceNr); + free(data); } else { ec = errBadResource; } @@ -287,19 +289,19 @@ int AgiLoader_v1::loadResource(int t, int n) { return ec; } -int AgiLoader_v1::unloadResource(int t, int n) { - switch (t) { - case rLOGIC: - _vm->unloadLogic(n); +int AgiLoader_v1::unloadResource(int16 resourceType, int16 resourceNr) { + switch (resourceType) { + case RESOURCETYPE_LOGIC: + _vm->unloadLogic(resourceNr); break; - case rPICTURE: - _vm->_picture->unloadPicture(n); + case RESOURCETYPE_PICTURE: + _vm->_picture->unloadPicture(resourceNr); break; - case rVIEW: - _vm->unloadView(n); + case RESOURCETYPE_VIEW: + _vm->unloadView(resourceNr); break; - case rSOUND: - _vm->_sound->unloadSound(n); + case RESOURCETYPE_SOUND: + _vm->_sound->unloadSound(resourceNr); break; } @@ -321,7 +323,7 @@ int AgiLoader_v1::loadWords(const char *fname) { Common::File f; f.open(_filenameDisk0); f.seek(BC_WORDS, SEEK_SET); - return _vm->loadWords_v1(f); + return _vm->_words->loadDictionary_v1(f); } return errOK; } diff --git a/engines/agi/loader_v2.cpp b/engines/agi/loader_v2.cpp index 693c53c2bf..76f6900e44 100644 --- a/engines/agi/loader_v2.cpp +++ b/engines/agi/loader_v2.cpp @@ -23,6 +23,7 @@ #include "common/textconsole.h" #include "agi/agi.h" +#include "agi/words.h" namespace Agi { @@ -60,7 +61,7 @@ int AgiLoader_v2::loadDir(AgiDir *agid, const char *fname) { fp.read(mem, flen); // set all directory resources to gone - for (i = 0; i < MAX_DIRS; i++) { + for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) { agid[i].volume = 0xff; agid[i].offset = _EMPTY; } @@ -107,21 +108,21 @@ int AgiLoader_v2::deinit() { return ec; } -int AgiLoader_v2::unloadResource(int t, int n) { +int AgiLoader_v2::unloadResource(int16 resourceType, int16 resourceNr) { debugC(3, kDebugLevelResources, "unload resource"); - switch (t) { - case rLOGIC: - _vm->unloadLogic(n); + switch (resourceType) { + case RESOURCETYPE_LOGIC: + _vm->unloadLogic(resourceNr); break; - case rPICTURE: - _vm->_picture->unloadPicture(n); + case RESOURCETYPE_PICTURE: + _vm->_picture->unloadPicture(resourceNr); break; - case rVIEW: - _vm->unloadView(n); + case RESOURCETYPE_VIEW: + _vm->unloadView(resourceNr); break; - case rSOUND: - _vm->_sound->unloadSound(n); + case RESOURCETYPE_SOUND: + _vm->_sound->unloadSound(resourceNr); break; } @@ -129,7 +130,7 @@ int AgiLoader_v2::unloadResource(int t, int n) { } /** - * This function does noting but load a raw resource into memory, + * This function loads a raw resource into memory, * if further decoding is required, it must be done by another * routine. NULL is returned if unsucsessfull. */ @@ -173,84 +174,84 @@ uint8 *AgiLoader_v2::loadVolRes(struct AgiDir *agid) { * Loads a resource into memory, a raw resource is loaded in * with above routine, then further decoded here. */ -int AgiLoader_v2::loadResource(int t, int n) { +int AgiLoader_v2::loadResource(int16 resourceType, int16 resourceNr) { int ec = errOK; uint8 *data = NULL; - debugC(3, kDebugLevelResources, "(t = %d, n = %d)", t, n); - if (n >= MAX_DIRS) + debugC(3, kDebugLevelResources, "(t = %d, n = %d)", resourceType, resourceNr); + if (resourceNr >= MAX_DIRECTORY_ENTRIES) return errBadResource; - switch (t) { - case rLOGIC: - if (~_vm->_game.dirLogic[n].flags & RES_LOADED) { - debugC(3, kDebugLevelResources, "loading logic resource %d", n); - unloadResource(rLOGIC, n); + switch (resourceType) { + case RESOURCETYPE_LOGIC: + if (~_vm->_game.dirLogic[resourceNr].flags & RES_LOADED) { + debugC(3, kDebugLevelResources, "loading logic resource %d", resourceNr); + unloadResource(RESOURCETYPE_LOGIC, resourceNr); // load raw resource into data - data = loadVolRes(&_vm->_game.dirLogic[n]); + data = loadVolRes(&_vm->_game.dirLogic[resourceNr]); - _vm->_game.logics[n].data = data; - ec = data ? _vm->decodeLogic(n) : errBadResource; + _vm->_game.logics[resourceNr].data = data; + ec = data ? _vm->decodeLogic(resourceNr) : errBadResource; - _vm->_game.logics[n].sIP = 2; + _vm->_game.logics[resourceNr].sIP = 2; } // if logic was cached, we get here // reset code pointers incase it was cached - _vm->_game.logics[n].cIP = _vm->_game.logics[n].sIP; + _vm->_game.logics[resourceNr].cIP = _vm->_game.logics[resourceNr].sIP; break; - case rPICTURE: + case RESOURCETYPE_PICTURE: // if picture is currently NOT loaded *OR* cacheing is off, // unload the resource (caching == off) and reload it - debugC(3, kDebugLevelResources, "loading picture resource %d", n); - if (_vm->_game.dirPic[n].flags & RES_LOADED) + debugC(3, kDebugLevelResources, "loading picture resource %d", resourceNr); + if (_vm->_game.dirPic[resourceNr].flags & RES_LOADED) break; // if loaded but not cached, unload it // if cached but not loaded, etc - unloadResource(rPICTURE, n); - data = loadVolRes(&_vm->_game.dirPic[n]); + unloadResource(RESOURCETYPE_PICTURE, resourceNr); + data = loadVolRes(&_vm->_game.dirPic[resourceNr]); if (data != NULL) { - _vm->_game.pictures[n].rdata = data; - _vm->_game.dirPic[n].flags |= RES_LOADED; + _vm->_game.pictures[resourceNr].rdata = data; + _vm->_game.dirPic[resourceNr].flags |= RES_LOADED; } else { ec = errBadResource; } break; - case rSOUND: - debugC(3, kDebugLevelResources, "loading sound resource %d", n); - if (_vm->_game.dirSound[n].flags & RES_LOADED) + case RESOURCETYPE_SOUND: + debugC(3, kDebugLevelResources, "loading sound resource %d", resourceNr); + if (_vm->_game.dirSound[resourceNr].flags & RES_LOADED) break; - data = loadVolRes(&_vm->_game.dirSound[n]); + data = loadVolRes(&_vm->_game.dirSound[resourceNr]); if (data != NULL) { // Freeing of the raw resource from memory is delegated to the createFromRawResource-function - _vm->_game.sounds[n] = AgiSound::createFromRawResource(data, _vm->_game.dirSound[n].len, n, _vm->_soundemu); - _vm->_game.dirSound[n].flags |= RES_LOADED; + _vm->_game.sounds[resourceNr] = AgiSound::createFromRawResource(data, _vm->_game.dirSound[resourceNr].len, resourceNr, _vm->_soundemu); + _vm->_game.dirSound[resourceNr].flags |= RES_LOADED; } else { ec = errBadResource; } break; - case rVIEW: + case RESOURCETYPE_VIEW: // Load a VIEW resource into memory... // Since VIEWS alter the view table ALL the time // can we cache the view? or must we reload it all // the time? - if (_vm->_game.dirView[n].flags & RES_LOADED) + if (_vm->_game.dirView[resourceNr].flags & RES_LOADED) break; - debugC(3, kDebugLevelResources, "loading view resource %d", n); - unloadResource(rVIEW, n); - data = loadVolRes(&_vm->_game.dirView[n]); + debugC(3, kDebugLevelResources, "loading view resource %d", resourceNr); + unloadResource(RESOURCETYPE_VIEW, resourceNr); + data = loadVolRes(&_vm->_game.dirView[resourceNr]); if (data) { - _vm->_game.views[n].rdata = data; - _vm->_game.dirView[n].flags |= RES_LOADED; - ec = _vm->decodeView(n); + _vm->_game.dirView[resourceNr].flags |= RES_LOADED; + ec = _vm->decodeView(data, _vm->_game.dirView[resourceNr].len, resourceNr); + free(data); } else { ec = errBadResource; } @@ -268,7 +269,7 @@ int AgiLoader_v2::loadObjects(const char *fname) { } int AgiLoader_v2::loadWords(const char *fname) { - return _vm->loadWords(fname); + return _vm->_words->loadDictionary(fname); } } // End of namespace Agi diff --git a/engines/agi/loader_v3.cpp b/engines/agi/loader_v3.cpp index 39759b4649..d7abbac0bd 100644 --- a/engines/agi/loader_v3.cpp +++ b/engines/agi/loader_v3.cpp @@ -22,6 +22,7 @@ #include "agi/agi.h" #include "agi/lzw.h" +#include "agi/words.h" #include "common/config-manager.h" #include "common/fs.h" @@ -76,7 +77,7 @@ int AgiLoader_v3::loadDir(struct AgiDir *agid, Common::File *fp, fp->read(mem, len); // set all directory resources to gone - for (i = 0; i < MAX_DIRS; i++) { + for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) { agid[i].volume = 0xff; agid[i].offset = _EMPTY; } @@ -171,19 +172,19 @@ int AgiLoader_v3::deinit() { return ec; } -int AgiLoader_v3::unloadResource(int t, int n) { - switch (t) { - case rLOGIC: - _vm->unloadLogic(n); +int AgiLoader_v3::unloadResource(int16 resourceType, int16 resourceNr) { + switch (resourceType) { + case RESOURCETYPE_LOGIC: + _vm->unloadLogic(resourceNr); break; - case rPICTURE: - _vm->_picture->unloadPicture(n); + case RESOURCETYPE_PICTURE: + _vm->_picture->unloadPicture(resourceNr); break; - case rVIEW: - _vm->unloadView(n); + case RESOURCETYPE_VIEW: + _vm->unloadView(resourceNr); break; - case rSOUND: - _vm->_sound->unloadSound(n); + case RESOURCETYPE_SOUND: + _vm->_sound->unloadSound(resourceNr); break; } @@ -191,7 +192,7 @@ int AgiLoader_v3::unloadResource(int t, int n) { } /** - * This function does noting but load a raw resource into memory. + * This function loads a raw resource into memory. * If further decoding is required, it must be done by another * routine. * @@ -225,7 +226,11 @@ uint8 *AgiLoader_v3::loadVolRes(AgiDir *agid) { fp.read(compBuffer, agid->clen); if (x[2] & 0x80) { // compressed pic - data = _vm->_picture->convertV3Pic(compBuffer, agid->clen); + // effectively uncompressed, but having only 4-bit parameters for F0 / F2 commands + // Manhunter 2 uses such pictures + data = compBuffer; + agid->flags |= RES_PICTURE_V3_NIBBLE_PARM; + //data = _vm->_picture->convertV3Pic(compBuffer, agid->clen); // compBuffer has been freed inside convertV3Pic() } else if (agid->len == agid->clen) { // do not decompress @@ -252,31 +257,31 @@ uint8 *AgiLoader_v3::loadVolRes(AgiDir *agid) { * Loads a resource into memory, a raw resource is loaded in * with above routine, then further decoded here. */ -int AgiLoader_v3::loadResource(int t, int n) { +int AgiLoader_v3::loadResource(int16 resourceType, int16 resourceNr) { int ec = errOK; uint8 *data = NULL; - if (n >= MAX_DIRS) + if (resourceNr >= MAX_DIRECTORY_ENTRIES) return errBadResource; - switch (t) { - case rLOGIC: + switch (resourceType) { + case RESOURCETYPE_LOGIC: // load resource into memory, decrypt messages at the end // and build the message list (if logic is in memory) - if (~_vm->_game.dirLogic[n].flags & RES_LOADED) { + if (~_vm->_game.dirLogic[resourceNr].flags & RES_LOADED) { // if logic is already in memory, unload it - unloadResource(rLOGIC, n); + unloadResource(RESOURCETYPE_LOGIC, resourceNr); // load raw resource into data - data = loadVolRes(&_vm->_game.dirLogic[n]); - _vm->_game.logics[n].data = data; + data = loadVolRes(&_vm->_game.dirLogic[resourceNr]); + _vm->_game.logics[resourceNr].data = data; // uncompressed logic files need to be decrypted if (data != NULL) { // resloaded flag gets set by decode logic // needed to build string table - ec = _vm->decodeLogic(n); - _vm->_game.logics[n].sIP = 2; + ec = _vm->decodeLogic(resourceNr); + _vm->_game.logics[resourceNr].sIP = 2; } else { ec = errBadResource; } @@ -284,56 +289,56 @@ int AgiLoader_v3::loadResource(int t, int n) { // logics[n].sIP=2; // saved IP = 2 // logics[n].cIP=2; // current IP = 2 - _vm->_game.logics[n].cIP = _vm->_game.logics[n].sIP; + _vm->_game.logics[resourceNr].cIP = _vm->_game.logics[resourceNr].sIP; } // if logic was cached, we get here // reset code pointers incase it was cached - _vm->_game.logics[n].cIP = _vm->_game.logics[n].sIP; + _vm->_game.logics[resourceNr].cIP = _vm->_game.logics[resourceNr].sIP; break; - case rPICTURE: + case RESOURCETYPE_PICTURE: // if picture is currently NOT loaded *OR* cacheing is off, // unload the resource (caching==off) and reload it - if (~_vm->_game.dirPic[n].flags & RES_LOADED) { - unloadResource(rPICTURE, n); - data = loadVolRes(&_vm->_game.dirPic[n]); + if (~_vm->_game.dirPic[resourceNr].flags & RES_LOADED) { + unloadResource(RESOURCETYPE_PICTURE, resourceNr); + data = loadVolRes(&_vm->_game.dirPic[resourceNr]); if (data != NULL) { - _vm->_game.pictures[n].rdata = data; - _vm->_game.dirPic[n].flags |= RES_LOADED; + _vm->_game.pictures[resourceNr].rdata = data; + _vm->_game.dirPic[resourceNr].flags |= RES_LOADED; } else { ec = errBadResource; } } break; - case rSOUND: - if (_vm->_game.dirSound[n].flags & RES_LOADED) + case RESOURCETYPE_SOUND: + if (_vm->_game.dirSound[resourceNr].flags & RES_LOADED) break; - data = loadVolRes(&_vm->_game.dirSound[n]); + data = loadVolRes(&_vm->_game.dirSound[resourceNr]); if (data != NULL) { // Freeing of the raw resource from memory is delegated to the createFromRawResource-function - _vm->_game.sounds[n] = AgiSound::createFromRawResource(data, _vm->_game.dirSound[n].len, n, _vm->_soundemu); - _vm->_game.dirSound[n].flags |= RES_LOADED; + _vm->_game.sounds[resourceNr] = AgiSound::createFromRawResource(data, _vm->_game.dirSound[resourceNr].len, resourceNr, _vm->_soundemu); + _vm->_game.dirSound[resourceNr].flags |= RES_LOADED; } else { ec = errBadResource; } break; - case rVIEW: + case RESOURCETYPE_VIEW: // Load a VIEW resource into memory... // Since VIEWS alter the view table ALL the time can we // cache the view? or must we reload it all the time? // // load a raw view from a VOL file into data - if (_vm->_game.dirView[n].flags & RES_LOADED) + if (_vm->_game.dirView[resourceNr].flags & RES_LOADED) break; - unloadResource(rVIEW, n); - data = loadVolRes(&_vm->_game.dirView[n]); + unloadResource(RESOURCETYPE_VIEW, resourceNr); + data = loadVolRes(&_vm->_game.dirView[resourceNr]); if (data != NULL) { - _vm->_game.views[n].rdata = data; - _vm->_game.dirView[n].flags |= RES_LOADED; - ec = _vm->decodeView(n); + _vm->_game.dirView[resourceNr].flags |= RES_LOADED; + ec = _vm->decodeView(data, _vm->_game.dirView[resourceNr].len, resourceNr); + free(data); } else { ec = errBadResource; } @@ -351,7 +356,7 @@ int AgiLoader_v3::loadObjects(const char *fname) { } int AgiLoader_v3::loadWords(const char *fname) { - return _vm->loadWords(fname); + return _vm->_words->loadDictionary(fname); } } // End of namespace Agi diff --git a/engines/agi/logic.cpp b/engines/agi/logic.cpp index 7429b117c8..1aeb7cc3de 100644 --- a/engines/agi/logic.cpp +++ b/engines/agi/logic.cpp @@ -30,15 +30,16 @@ namespace Agi { * into a message list. * @param n The number of the logic resource to decode. */ -int AgiEngine::decodeLogic(int n) { +int AgiEngine::decodeLogic(int16 logicNr) { int ec = errOK; int mstart, mend, mc; uint8 *m0; + AgiLogic *curLogic = &_game.logics[logicNr]; // decrypt messages at end of logic + build message list // report ("decoding logic #%d\n", n); - m0 = _game.logics[n].data; + m0 = curLogic->data; mstart = READ_LE_UINT16(m0) + 2; mc = *(m0 + mstart); @@ -48,38 +49,38 @@ int AgiEngine::decodeLogic(int n) { // if the logic was not compressed, decrypt the text messages // only if there are more than 0 messages - if ((~_game.dirLogic[n].flags & RES_COMPRESSED) && mc > 0) + if ((~_game.dirLogic[logicNr].flags & RES_COMPRESSED) && mc > 0) decrypt(m0 + mstart, mend - mstart); // decrypt messages // build message list - m0 = _game.logics[n].data; + m0 = curLogic->data; mstart = READ_LE_UINT16(m0) + 2; // +2 covers pointer - _game.logics[n].numTexts = *(m0 + mstart); + _game.logics[logicNr].numTexts = *(m0 + mstart); // resetp logic pointers - _game.logics[n].sIP = 2; - _game.logics[n].cIP = 2; - _game.logics[n].size = READ_LE_UINT16(m0) + 2; // logic end pointer + curLogic->sIP = 2; + curLogic->cIP = 2; + curLogic->size = READ_LE_UINT16(m0) + 2; // logic end pointer // allocate list of pointers to point into our data - _game.logics[n].texts = (const char **)calloc(1 + _game.logics[n].numTexts, sizeof(char *)); + curLogic->texts = (const char **)calloc(1 + curLogic->numTexts, sizeof(char *)); // cover header info m0 += mstart + 3; - if (_game.logics[n].texts != NULL) { + if (curLogic->texts != NULL) { // move list of strings into list to make real pointers - for (mc = 0; mc < _game.logics[n].numTexts; mc++) { + for (mc = 0; mc < curLogic->numTexts; mc++) { mend = READ_LE_UINT16(m0 + mc * 2); - _game.logics[n].texts[mc] = mend ? (const char *)m0 + mend - 2 : (const char *)""; + _game.logics[logicNr].texts[mc] = mend ? (const char *)m0 + mend - 2 : (const char *)""; } // set loaded flag now its all completly loaded - _game.dirLogic[n].flags |= RES_LOADED; + _game.dirLogic[logicNr].flags |= RES_LOADED; } else { // unload data // Note that not every logic has text - free(_game.logics[n].data); + free(curLogic->data); ec = errNotEnoughMemory; } @@ -92,18 +93,17 @@ int AgiEngine::decodeLogic(int n) { * memory chunks allocated for this resource. * @param n The number of the logic resource to unload */ -void AgiEngine::unloadLogic(int n) { - if (_game.dirLogic[n].flags & RES_LOADED) { - free(_game.logics[n].data); - if (_game.logics[n].numTexts) - free(_game.logics[n].texts); - _game.logics[n].numTexts = 0; - _game.dirLogic[n].flags &= ~RES_LOADED; +void AgiEngine::unloadLogic(int16 logicNr) { + if (_game.dirLogic[logicNr].flags & RES_LOADED) { + free(_game.logics[logicNr].data); + free(_game.logics[logicNr].texts); + _game.logics[logicNr].numTexts = 0; + _game.dirLogic[logicNr].flags &= ~RES_LOADED; } // if cached, we end up here - _game.logics[n].sIP = 2; - _game.logics[n].cIP = 2; + _game.logics[logicNr].sIP = 2; + _game.logics[logicNr].cIP = 2; } } // End of namespace Agi diff --git a/engines/agi/menu.cpp b/engines/agi/menu.cpp index 008c208c82..411cd002fd 100644 --- a/engines/agi/menu.cpp +++ b/engines/agi/menu.cpp @@ -22,534 +22,456 @@ #include "agi/agi.h" #include "agi/graphics.h" +#include "agi/text.h" #include "agi/keyboard.h" #include "agi/menu.h" namespace Agi { -// TODO: add constructor/destructor for agi_menu, agi_menu_option - -struct AgiMenuOption { - int enabled; /**< option is enabled or disabled */ - int event; /**< menu event */ - int index; /**< number of option in this menu */ - char *text; /**< text of menu option */ -}; - -struct AgiMenu { - MenuOptionList down; /**< list head for menu options */ - int index; /**< number of menu in menubar */ - int width; /**< width of menu in characters */ - int height; /**< height of menu in characters */ - int col; /**< column of menubar entry */ - int wincol; /**< column of menu window */ - char *text; /**< menu name */ -}; - -AgiMenu *Menu::getMenu(int i) { - MenuList::iterator iter; - for (iter = _menubar.begin(); iter != _menubar.end(); ++iter) { - AgiMenu *m = *iter; - if (m->index == i) - return m; - } - return NULL; -} - -AgiMenuOption *Menu::getMenuOption(int i, int j) { - AgiMenu *m = getMenu(i); - MenuOptionList::iterator iter; - - for (iter = m->down.begin(); iter != m->down.end(); ++iter) { - AgiMenuOption* d = *iter; - if (d->index == j) - return d; - } - - return NULL; -} +GfxMenu::GfxMenu(AgiEngine *vm, GfxMgr *gfx, PictureMgr *picture, TextMgr *text) { + _vm = vm; + _gfx = gfx; + _picture = picture; + _text = text; -void Menu::drawMenuBar() { - _vm->clearLines(0, 0, MENU_BG); - _vm->flushLines(0, 0); + _allowed = true; + _submitted = false; + _delayedExecute = false; - MenuList::iterator iter; - for (iter = _menubar.begin(); iter != _menubar.end(); ++iter) { - AgiMenu *m = *iter; - - _vm->printText(m->text, 0, m->col, 0, 40, MENU_FG, MENU_BG); - } + _setupMenuColumn = 1; + _setupMenuItemColumn = 1; + _selectedMenuNr = 0; + _selectedMenuHeight = 0; + _selectedMenuWidth = 0; + _selectedMenuRow = 0; + _selectedMenuColumn = 0; } -void Menu::drawMenuHilite(int curMenu) { - AgiMenu *m = getMenu(curMenu); - - debugC(6, kDebugLevelMenu, "[%s]", m->text); +GfxMenu::~GfxMenu() { + for (GuiMenuArray::iterator itemIter = _array.begin(); itemIter != _array.end(); ++itemIter) + delete *itemIter; + _array.clear(); - _vm->printText(m->text, 0, m->col, 0, 40, MENU_BG, MENU_FG); - _vm->flushLines(0, 0); + for (GuiMenuItemArray::iterator menuIter = _itemArray.begin(); menuIter != _itemArray.end(); ++menuIter) + delete *menuIter; + _itemArray.clear(); } -// draw box and pulldowns. -void Menu::drawMenuOption(int hMenu) { - // find which vertical menu it is - AgiMenu *m = getMenu(hMenu); - - _gfx->drawBox(m->wincol * CHAR_COLS, 1 * CHAR_LINES, (m->wincol + m->width + 2) * CHAR_COLS, - (1 + m->height + 2) * CHAR_LINES, MENU_BG, MENU_LINE, 0); +void GfxMenu::addMenu(const char *menuText) { + // already submitted? in that case no further changes possible + if (_submitted) + return; - MenuOptionList::iterator iter; + GuiMenuEntry *menuEntry = new GuiMenuEntry(); - for (iter = m->down.begin(); iter != m->down.end(); ++iter) { - AgiMenuOption* d = *iter; + menuEntry->text = menuText; + menuEntry->textLen = menuEntry->text.size(); + menuEntry->row = 0; + menuEntry->column = _setupMenuColumn; + menuEntry->itemCount = 0; + menuEntry->firstItemNr = _itemArray.size(); + menuEntry->selectedItemNr = menuEntry->firstItemNr; + menuEntry->maxItemTextLen = 0; + _array.push_back(menuEntry); - _vm->printText(d->text, 0, m->wincol + 1, d->index + 2, m->width + 2, - MENU_FG, MENU_BG, !d->enabled); - } + _setupMenuColumn += menuEntry->textLen + 1; } -void Menu::drawMenuOptionHilite(int hMenu, int vMenu) { - AgiMenu *m = getMenu(hMenu); - AgiMenuOption *d = getMenuOption(hMenu, vMenu); +void GfxMenu::addMenuItem(const char *menuItemText, uint16 controllerSlot) { + int16 arrayCount = _array.size(); - // Disabled menu items are "greyed out" with a checkerboard effect, - // rather than having a different color. -- dsymonds - _vm->printText(d->text, 0, m->wincol + 1, vMenu + 2, m->width + 2, - MENU_BG, MENU_FG, !d->enabled); -} + // already submitted? in that case no further changes possible + if (_submitted) + return; -void Menu::newMenuSelected(int i) { - _picture->showPic(); - drawMenuBar(); - drawMenuHilite(i); - drawMenuOption(i); -} + if (arrayCount == 0) + error("tried to add a menu item before adding an actual menu"); -bool Menu::mouseOverText(int line, int col, char *s) { - if (_vm->_mouse.x < col * CHAR_COLS) - return false; + // go to latest menu entry + GuiMenuEntry *curMenuEntry = _array.back(); - if (_vm->_mouse.x > (int)(col + strlen(s)) * CHAR_COLS) - return false; + GuiMenuItemEntry *menuItemEntry = new GuiMenuItemEntry(); - if (_vm->_mouse.y < line * CHAR_LINES) - return false; + menuItemEntry->enabled = true; + menuItemEntry->text = menuItemText; + menuItemEntry->textLen = menuItemEntry->text.size(); + menuItemEntry->controllerSlot = controllerSlot; - if (_vm->_mouse.y >= (line + 1) * CHAR_LINES) - return false; + // Original interpreter on PC used the length of the first item for drawing + // At least in KQ2 on Apple IIgs follow-up items are longer, which would result in graphic glitches. + // That's why we remember the longest item and draw according to that + if (curMenuEntry->maxItemTextLen < menuItemEntry->textLen) { + curMenuEntry->maxItemTextLen = menuItemEntry->textLen; + } - return true; -} + if (curMenuEntry->itemCount == 0) { + // for first menu item of menu calculated column + if (menuItemEntry->textLen + curMenuEntry->column < (FONT_COLUMN_CHARACTERS - 1)) { + _setupMenuItemColumn = curMenuEntry->column; + } else { + _setupMenuItemColumn = ( FONT_COLUMN_CHARACTERS - 1 ) - menuItemEntry->textLen; + } + } -#if 0 -static void add_about_option() { - const char *text = "About AGI engine"; - - agi_menu_option *d = new agi_menu_option; - d->text = strdup(text); - d->enabled = true; - d->event = 255; - d->index = (v_max_menu[0] += 1); - - agi_menu *m = *menubar.begin(); - m->down.push_back(d); - m->height++; - if (m->width < (int)strlen(text)) - m->width = strlen(text); -} -#endif + menuItemEntry->row = 2 + curMenuEntry->itemCount; + menuItemEntry->column = _setupMenuItemColumn; -/* - * Public functions - */ + _itemArray.push_back(menuItemEntry); -Menu::Menu(AgiEngine *vm, GfxMgr *gfx, PictureMgr *picture) { - _vm = vm; - _gfx = gfx; - _picture = picture; - _hIndex = 0; - _hCol = 1; - _hMaxMenu = 0; - _hCurMenu = 0; - _vCurMenu = 0; + curMenuEntry->itemCount++; } -Menu::~Menu() { - MenuList::iterator iterh; - for (iterh = _menubar.reverse_begin(); iterh != _menubar.end(); ) { - AgiMenu *m = *iterh; - - debugC(3, kDebugLevelMenu, "deiniting hmenu %s", m->text); - - MenuOptionList::iterator iterv; - - for (iterv = m->down.reverse_begin(); iterv != m->down.end(); ) { - AgiMenuOption *d = *iterv; - - debugC(3, kDebugLevelMenu, " deiniting vmenu %s", d->text); - - free(d->text); - delete d; +void GfxMenu::submit() { + GuiMenuEntry *menuEntry = nullptr; + GuiMenuItemEntry *menuItemEntry = nullptr; + int16 menuCount = _array.size(); + int16 menuNr = 0; + int16 menuItemNr = 0; + int16 menuItemLastNr = 0; + + if ((_array.size() == 0) || (_itemArray.size() == 0)) + return; + + _submitted = true; + + // WORKAROUND: For Apple II gs we try to fix the menu text + // On this platform it seems a system font was used and the menu was drawn differently (probably system menu?) + // Still the text was misaligned anyway, but it looks worse in our (the original PC) implementation + // Atari ST SQ1 had one bad menu entry as well, we fix that too. + switch (_vm->getPlatform()) { + case Common::kPlatformApple2GS: + case Common::kPlatformAtariST: + // Go through all menus + for (menuNr = 0; menuNr < menuCount; menuNr++) { + menuEntry = _array[menuNr]; + menuItemLastNr = menuEntry->firstItemNr + menuEntry->itemCount; + + // Go through all items of current menu + for (menuItemNr = menuEntry->firstItemNr; menuItemNr < menuItemLastNr; menuItemNr++) { + menuItemEntry = _itemArray[menuItemNr]; + + if (menuItemEntry->textLen < menuEntry->maxItemTextLen) { + // current item text is shorter than the maximum? + int16 missingCharCount = menuEntry->maxItemTextLen - menuItemEntry->textLen; + + if (menuItemEntry->text.contains('>')) { + // text contains '>', we now try to find a '<' + // and then add spaces in case this item is shorter than the first item + int16 textPos = menuItemEntry->textLen - 1; + + while (textPos > 0) { + if (menuItemEntry->text[textPos] == '<') + break; + textPos--; + } + + if (textPos > 0) { + while (missingCharCount) { + menuItemEntry->text.insertChar(' ', textPos); + missingCharCount--; + } + } + } else { + // Also check if text consists only of '-', which is the separator + // These were sometimes also too small + int16 separatorCount = 0; + int16 charPos = 0; + + while (charPos < menuItemEntry->textLen) { + if (menuItemEntry->text[charPos] != '-') + break; + separatorCount++; + charPos++; + } + + if (separatorCount == menuItemEntry->textLen) { + // Separator detected + while (missingCharCount) { + menuItemEntry->text.insertChar('-', 0); + missingCharCount--; + } + } else { + // Append spaces to the end to fill it up + int16 textPos = menuItemEntry->textLen; + while (missingCharCount) { + menuItemEntry->text.insertChar(' ', textPos); + textPos++; + missingCharCount--; + } + } + } - iterv = m->down.reverse_erase(iterv); + menuItemEntry->textLen = menuItemEntry->text.size(); + } + } } - free(m->text); - delete m; - - iterh = _menubar.reverse_erase(iterh); + break; + default: + break; } } -void Menu::add(const char *s) { - AgiMenu *m = new AgiMenu; - m->text = strdup(s); - - while (m->text[strlen(m->text) - 1] == ' ') - m->text[strlen(m->text) - 1] = 0; - - m->width = 0; - m->height = 0; - m->index = _hIndex++; - m->col = _hCol; - m->wincol = _hCol - 1; - _vIndex = 0; - _vMaxMenu[m->index] = 0; - _hCol += strlen(m->text) + 1; - _hMaxMenu = m->index; - - debugC(3, kDebugLevelMenu, "add menu: '%s' %02x", s, m->text[strlen(m->text)]); - _menubar.push_back(m); +void GfxMenu::itemEnable(uint16 controllerSlot) { + itemEnableDisable(controllerSlot, true); } -void Menu::addItem(const char *s, int code) { - int l; - - AgiMenuOption* d = new AgiMenuOption; - - d->text = strdup(s); - d->enabled = true; - d->event = code; - d->index = _vIndex++; - - // add to last menu in list - assert(_menubar.reverse_begin() != _menubar.end()); - AgiMenu *m = *_menubar.reverse_begin(); - m->height++; - - _vMaxMenu[m->index] = d->index; - - l = strlen(d->text); - if (l > 40) - l = 38; - if (m->wincol + l > 38) - m->wincol = 38 - l; - if (l > m->width) - m->width = l; - - debugC(3, kDebugLevelMenu, "Adding menu item: %s (size = %d)", s, m->height); - - m->down.push_back(d); +void GfxMenu::itemDisable(uint16 controllerSlot) { + itemEnableDisable(controllerSlot, false); } -void Menu::submit() { - debugC(3, kDebugLevelMenu, "Submitting menu"); - - // add_about_option (); - - // If a menu has no options, delete it - MenuList::iterator iter; - for (iter = _menubar.reverse_begin(); iter != _menubar.end(); ) { - AgiMenu *m = *iter; +void GfxMenu::itemEnableDisable(uint16 controllerSlot, bool enabled) { + GuiMenuItemArray::iterator listIterator; + GuiMenuItemArray::iterator listEnd = _itemArray.end(); + GuiMenuItemEntry *menuItemEntry; - if (m->down.empty()) { - free(m->text); - delete m; - - _hMaxMenu--; - - iter = _menubar.reverse_erase(iter); - } else { - --iter; + listIterator = _itemArray.begin(); + while (listIterator != listEnd) { + menuItemEntry = *listIterator; + if (menuItemEntry->controllerSlot == controllerSlot) { + menuItemEntry->enabled = enabled; } + + listIterator++; } } -bool Menu::keyhandler(int key) { - static int clockVal; - static int menuActive = false; - static int buttonUsed = 0; - bool exitMenu = false; +void GfxMenu::itemEnableAll() { + GuiMenuItemArray::iterator listIterator; + GuiMenuItemArray::iterator listEnd = _itemArray.end(); + GuiMenuItemEntry *menuItemEntry; - if (!_vm->getflag(fMenusWork) && !(_vm->getFeatures() & GF_MENUS)) - return false; + listIterator = _itemArray.begin(); + while (listIterator != listEnd) { + menuItemEntry = *listIterator; + menuItemEntry->enabled = true; - if (!menuActive) { - clockVal = _vm->_game.clockEnabled; - _vm->_game.clockEnabled = false; - drawMenuBar(); + listIterator++; } +} - // Mouse handling - if (_vm->_mouse.button) { - int hmenu, vmenu; - - buttonUsed = 1; // Button has been used at least once - - if (_vm->_mouse.y <= CHAR_LINES) { - // on the menubar - hmenu = 0; +// return true, in case a menu was actually created and submitted by the scripts +bool GfxMenu::isAvailable() { + return _submitted; +} - MenuList::iterator iterh; +void GfxMenu::accessAllow() { + _allowed = true; +} - for (iterh = _menubar.begin(); iterh != _menubar.end(); ++iterh) { - AgiMenu *m = *iterh; +void GfxMenu::accessDeny() { + _allowed = false; +} - if (mouseOverText(0, m->col, m->text)) { - break; - } else { - hmenu++; - } - } +void GfxMenu::delayedExecute() { + _delayedExecute = true; +} - if (hmenu <= _hMaxMenu) { - if (_hCurMenu != hmenu) { - _vCurMenu = -1; - newMenuSelected(hmenu); - } - _hCurMenu = hmenu; - } - } else { - // not in menubar - vmenu = 0; +bool GfxMenu::delayedExecuteActive() { + return _delayedExecute; +} - AgiMenu *m = getMenu(_hCurMenu); +void GfxMenu::execute() { + _delayedExecute = false; - MenuOptionList::iterator iterv; + // got submitted? -> safety check + if (!_submitted) + return; - for (iterv = m->down.begin(); iterv != m->down.end(); ++iterv) { - AgiMenuOption *do1 = *iterv; + // access allowed at the moment? + if (!_allowed) + return; - if (mouseOverText(2 + do1->index, m->wincol + 1, do1->text)) { - break; - } else { - vmenu++; - } - } + _text->charPos_Push(); + _text->charAttrib_Push(); + _text->clearLine(0, _text->calculateTextBackground(15)); - if (vmenu <= _vMaxMenu[_hCurMenu]) { - if (_vCurMenu != vmenu) { - drawMenuOption(_hCurMenu); - drawMenuOptionHilite(_hCurMenu, vmenu); - } - _vCurMenu = vmenu; - } - } - } else if (buttonUsed) { - // Button released - buttonUsed = 0; + // Draw all menus + for (uint16 menuNr = 0; menuNr < _array.size(); menuNr++) { + drawMenuName(menuNr, false); + } + drawActiveMenu(); - debugC(6, kDebugLevelMenu | kDebugLevelInput, "button released!"); + _vm->cycleInnerLoopActive(CYCLE_INNERLOOP_MENU); + do { + _vm->mainCycle(); + } while (_vm->cycleInnerLoopIsActive() && !(_vm->shouldQuit() || _vm->_restartGame)); - if (_vCurMenu < 0) - _vCurMenu = 0; + removeActiveMenu(); - drawMenuOptionHilite(_hCurMenu, _vCurMenu); + _text->charAttrib_Pop(); + _text->charPos_Pop(); - if (_vm->_mouse.y <= CHAR_LINES) { - // on the menubar - } else { - // see which option we selected - AgiMenu *m = getMenu(_hCurMenu); - MenuOptionList::iterator iterv; - - for (iterv = m->down.begin(); iterv != m->down.end(); ++iterv) { - AgiMenuOption *d = *iterv; - - if (mouseOverText(2 + d->index, m->wincol + 1, d->text)) { - // activate that option - if (d->enabled) { - debugC(6, kDebugLevelMenu | kDebugLevelInput, "event %d registered", d->event); - _vm->_game.controllerOccured[d->event] = true; - _vm->_menuSelected = true; - break; - } - } - } - exitMenu = true; - } + // Restore status line + if (_text->statusEnabled()) { + _text->statusDraw(); + } else { + _text->clearLine(0, 0); } +} - if (!exitMenu) { - if (!menuActive) { - if (_hCurMenu >= 0) { - drawMenuHilite(_hCurMenu); - drawMenuOption(_hCurMenu); - if (!buttonUsed && _vCurMenu >= 0) - drawMenuOptionHilite(_hCurMenu, _vCurMenu); - } - menuActive = true; - } +void GfxMenu::drawMenuName(int16 menuNr, bool inverted) { + GuiMenuEntry *menuEntry = _array[menuNr]; + bool disabledLook = false; - switch (key) { - case KEY_ESCAPE: - debugC(6, kDebugLevelMenu | kDebugLevelInput, "KEY_ESCAPE"); - exitMenu = true; - break; - case KEY_ENTER: - { - debugC(6, kDebugLevelMenu | kDebugLevelInput, "KEY_ENTER"); - AgiMenuOption* d = getMenuOption(_hCurMenu, _vCurMenu); - - if (d->enabled) { - debugC(6, kDebugLevelMenu | kDebugLevelInput, "event %d registered", d->event); - _vm->_game.controllerOccured[d->event] = true; - _vm->_menuSelected = true; - exitMenu = true; - } - break; - } - case KEY_DOWN: - case KEY_UP: - _vCurMenu += key == KEY_DOWN ? 1 : -1; - - if (_vCurMenu < 0) - _vCurMenu = _vMaxMenu[_hCurMenu]; - if (_vCurMenu > _vMaxMenu[_hCurMenu]) - _vCurMenu = 0; - - drawMenuOption(_hCurMenu); - drawMenuOptionHilite(_hCurMenu, _vCurMenu); - break; - case KEY_RIGHT: - case KEY_LEFT: - _hCurMenu += key == KEY_RIGHT ? 1 : -1; - - if (_hCurMenu < 0) - _hCurMenu = _hMaxMenu; - if (_hCurMenu > _hMaxMenu) - _hCurMenu = 0; - - _vCurMenu = 0; - newMenuSelected(_hCurMenu); - drawMenuOptionHilite(_hCurMenu, _vCurMenu); - break; - } + if (!inverted) { + _text->charAttrib_Set(0, _text->calculateTextBackground(15)); + } else { + _text->charAttrib_Set(15, _text->calculateTextBackground(0)); } - if (exitMenu) { - buttonUsed = 0; - _picture->showPic(); - _vm->writeStatus(); - - _vm->setvar(vKey, 0); - _vm->_game.keypress = 0; - _vm->_game.clockEnabled = clockVal; - _vm->oldInputMode(); + _text->charPos_Set(menuEntry->row, menuEntry->column); - debugC(3, kDebugLevelMenu, "exit_menu: input mode reset to %d", _vm->_game.inputMode); - menuActive = false; - } + if (menuEntry->itemCount == 0) + disabledLook = true; - return true; + _text->displayText(menuEntry->text.c_str(), disabledLook); } -void Menu::setItem(int event, int state) { - // scan all menus for event number # +void GfxMenu::drawItemName(int16 itemNr, bool inverted) { + GuiMenuItemEntry *itemEntry = _itemArray[itemNr]; + bool disabledLook = false; - debugC(6, kDebugLevelMenu, "event = %d, state = %d", event, state); - MenuList::iterator iterh; + if (!inverted) { + _text->charAttrib_Set(0, _text->calculateTextBackground(15)); + } else { + _text->charAttrib_Set(15, _text->calculateTextBackground(0)); + } - for (iterh = _menubar.begin(); iterh != _menubar.end(); ++iterh) { - AgiMenu *m = *iterh; - MenuOptionList::iterator iterv; + _text->charPos_Set(itemEntry->row, itemEntry->column); - for (iterv = m->down.begin(); iterv != m->down.end(); ++iterv) { - AgiMenuOption *d = *iterv; + if (itemEntry->enabled == false) + disabledLook = true; - if (d->event == event) { - d->enabled = state; - // keep going; we need to set the state of every menu item - // with this event code. -- dsymonds - } - } - } + _text->displayText(itemEntry->text.c_str(), disabledLook); } -void Menu::enableAll() { - MenuList::iterator iterh; - for (iterh = _menubar.begin(); iterh != _menubar.end(); ++iterh) { - AgiMenu *m = *iterh; - MenuOptionList::iterator iterv; - - for (iterv = m->down.begin(); iterv != m->down.end(); ++iterv) { - AgiMenuOption *d = *iterv; - - d->enabled = true; +void GfxMenu::drawActiveMenu() { + GuiMenuEntry *menuEntry = _array[_selectedMenuNr]; + GuiMenuItemEntry *itemEntry = _itemArray[menuEntry->firstItemNr]; + int16 itemNr = menuEntry->firstItemNr; + int16 itemCount = menuEntry->itemCount; + + // draw menu name as inverted + drawMenuName(_selectedMenuNr, true); + + // calculate active menu dimensions + _selectedMenuHeight = (menuEntry->itemCount + 2) * FONT_VISUAL_HEIGHT; + _selectedMenuWidth = (menuEntry->maxItemTextLen * FONT_VISUAL_WIDTH) + 8; + _selectedMenuRow = (menuEntry->itemCount + 3 - _text->getWindowRowMin()) * FONT_VISUAL_HEIGHT - 1; + _selectedMenuColumn = (itemEntry->column - 1) * FONT_VISUAL_WIDTH; + + _gfx->drawBox(_selectedMenuColumn, _selectedMenuRow, _selectedMenuWidth, _selectedMenuHeight, 15, 0); + + while (itemCount) { + if (itemNr == menuEntry->selectedItemNr) { + drawItemName(itemNr, true); + } else { + drawItemName(itemNr, false); } + itemNr++; + itemCount--; } } +void GfxMenu::removeActiveMenu() { + // draw menu name normally again + drawMenuName(_selectedMenuNr, false); -AgiTextColor AgiButtonStyle::getColor(bool hasFocus, bool pressed, bool positive) const { - if (_amigaStyle) { - if (positive) { - if (pressed) { // Positive pressed Amiga-style button - if (_olderAgi) { - return AgiTextColor(amigaBlack, amigaOrange); - } else { - return AgiTextColor(amigaBlack, amigaPurple); - } - } else { // Positive unpressed Amiga-style button - return AgiTextColor(amigaWhite, amigaGreen); - } - } else { // _amigaStyle && !positive - if (pressed) { // Negative pressed Amiga-style button - return AgiTextColor(amigaBlack, amigaCyan); - } else { // Negative unpressed Amiga-style button - return AgiTextColor(amigaWhite, amigaRed); - } - } - } else { // PC-style button - if (hasFocus || pressed) { // A pressed or in focus PC-style button - return AgiTextColor(pcWhite, pcBlack); - } else { // An unpressed PC-style button without focus - return AgiTextColor(pcBlack, pcWhite); - } - } + // overwrite actual menu items by rendering play screen + _gfx->render_Block(_selectedMenuColumn, _selectedMenuRow, _selectedMenuWidth, _selectedMenuHeight); } -AgiTextColor AgiButtonStyle::getColor(bool hasFocus, bool pressed, int baseFgColor, int baseBgColor) const { - return getColor(hasFocus, pressed, AgiTextColor(baseFgColor, baseBgColor)); -} +void GfxMenu::charPress(int16 newChar) { + GuiMenuEntry *menuEntry = _array[_selectedMenuNr]; + GuiMenuItemEntry *itemEntry = _itemArray[menuEntry->selectedItemNr]; + int16 newMenuNr = _selectedMenuNr; + int16 newItemNr = menuEntry->selectedItemNr; + + switch (newChar) { + case AGI_KEY_ENTER: + // check, if current item is actually enabled + if (!itemEntry->enabled) + return; + + // Trigger controller + _vm->_game.controllerOccured[itemEntry->controllerSlot] = true; + + _vm->cycleInnerLoopInactive(); // exit execute-loop + break; + case AGI_KEY_ESCAPE: + _vm->cycleInnerLoopInactive(); // exit execute-loop + break; + + // these here change menu item + case AGI_KEY_UP: + newItemNr--; + break; + case AGI_KEY_DOWN: + newItemNr++; + break; + case AGI_KEY_PAGE_UP: + // select first item of current menu + newItemNr = menuEntry->firstItemNr; + break; + case AGI_KEY_PAGE_DOWN: + // select last item of current menu + newItemNr = menuEntry->firstItemNr + menuEntry->itemCount - 1; + break; + + case AGI_KEY_LEFT: + newMenuNr--; + break; + case AGI_KEY_RIGHT: + newMenuNr++; + break; + case AGI_KEY_HOME: + // select first menu + newMenuNr = 0; + break; + case AGI_KEY_END: + // select last menu + newMenuNr = _array.size() - 1; + break; + + default: + break; + } -AgiTextColor AgiButtonStyle::getColor(bool hasFocus, bool pressed, const AgiTextColor &baseColor) const { - if (hasFocus || pressed) - return baseColor.swap(); - else - return baseColor; -} + if (newMenuNr != _selectedMenuNr) { + // selected menu was changed + int16 lastMenuNr = _array.size() - 1; -int AgiButtonStyle::getTextOffset(bool hasFocus, bool pressed) const { - return (pressed && !_amigaStyle) ? 1 : 0; -} + if (newMenuNr < 0) { + newMenuNr = lastMenuNr; + } else if (newMenuNr > lastMenuNr) { + newMenuNr = 0; + } -bool AgiButtonStyle::getBorder(bool hasFocus, bool pressed) const { - return _amigaStyle && !_authenticAmiga && (hasFocus || pressed); -} + if (newMenuNr != _selectedMenuNr) { + removeActiveMenu(); + _selectedMenuNr = newMenuNr; + drawActiveMenu(); + } + } -void AgiButtonStyle::setAmigaStyle(bool amigaStyle, bool olderAgi, bool authenticAmiga) { - _amigaStyle = amigaStyle; - _olderAgi = olderAgi; - _authenticAmiga = authenticAmiga; -} + if (newItemNr != menuEntry->selectedItemNr) { + // selected item was changed + int16 lastItemNr = menuEntry->firstItemNr + menuEntry->itemCount - 1; -void AgiButtonStyle::setPcStyle(bool pcStyle) { - setAmigaStyle(!pcStyle); -} + if (newItemNr < menuEntry->firstItemNr) { + newItemNr = lastItemNr; + } else if (newItemNr > lastItemNr) { + newItemNr = menuEntry->firstItemNr; + } -AgiButtonStyle::AgiButtonStyle(Common::RenderMode renderMode) { - setAmigaStyle(renderMode == Common::kRenderAmiga); + if (newItemNr != menuEntry->selectedItemNr) { + // still changed after clip -> draw changes + drawItemName(menuEntry->selectedItemNr, false); + drawItemName(newItemNr, true); + menuEntry->selectedItemNr = newItemNr; + } + } } } // End of namespace Agi diff --git a/engines/agi/menu.h b/engines/agi/menu.h index 000973db23..1781704996 100644 --- a/engines/agi/menu.h +++ b/engines/agi/menu.h @@ -25,6 +25,88 @@ namespace Agi { +struct GuiMenuEntry { + Common::String text; + int16 textLen; + + int16 row; + int16 column; + + int16 itemCount; + int16 firstItemNr; + int16 selectedItemNr; + + int16 maxItemTextLen; +}; +typedef Common::Array<GuiMenuEntry *> GuiMenuArray; + +struct GuiMenuItemEntry { + Common::String text; + int16 textLen; + + int16 row; + int16 column; + + bool enabled; + uint16 controllerSlot; +}; +typedef Common::Array<GuiMenuItemEntry *> GuiMenuItemArray; + +class GfxMenu { +public: + GfxMenu(AgiEngine *vm, GfxMgr *gfx, PictureMgr *picture, TextMgr *text); + ~GfxMenu(); + + void addMenu(const char *menuText); + void addMenuItem(const char *menuText, uint16 controlCode); + void submit(); + void itemEnable(uint16 controllerSlot); + void itemDisable(uint16 controllerSlot); + void itemEnableAll(); + void charPress(int16 newChar); + + bool isAvailable(); + + void accessAllow(); + void accessDeny(); + + void delayedExecute(); + bool delayedExecuteActive(); + void execute(); + +private: + void itemEnableDisable(uint16 controllerSlot, bool enabled); + + void drawMenuName(int16 menuNr, bool inverted); + void drawItemName(int16 itemNr, bool inverted); + void drawActiveMenu(); + void removeActiveMenu(); + + AgiEngine *_vm; + GfxMgr *_gfx; + PictureMgr *_picture; + TextMgr *_text; + + bool _allowed; + bool _submitted; + bool _delayedExecute; + + // for initial setup of the menu + int16 _setupMenuColumn; + int16 _setupMenuItemColumn; + + GuiMenuArray _array; + GuiMenuItemArray _itemArray; + + int16 _selectedMenuNr; + + uint16 _selectedMenuHeight; + uint16 _selectedMenuWidth; + int16 _selectedMenuRow; + int16 _selectedMenuColumn; +}; + +#if 0 #define MENU_BG 0x0f // White #define MENU_DISABLED 0x07 // Grey @@ -44,6 +126,7 @@ private: AgiEngine *_vm; GfxMgr *_gfx; PictureMgr *_picture; + TextMgr *_text; public: Menu(AgiEngine *vm, GfxMgr *gfx, PictureMgr *picture); @@ -78,6 +161,7 @@ private: bool mouseOverText(int line, int col, char *s); }; +#endif } // End of namespace Agi diff --git a/engines/agi/module.mk b/engines/agi/module.mk index 331a10c16e..b6cf6fc531 100644 --- a/engines/agi/module.mk +++ b/engines/agi/module.mk @@ -36,6 +36,7 @@ MODULE_OBJS := \ sound_pcjr.o \ sound_sarien.o \ sprite.o \ + systemui.o \ text.o \ view.o \ wagparser.o \ diff --git a/engines/agi/motion.cpp b/engines/agi/motion.cpp index 363291ac0b..3f85059b56 100644 --- a/engines/agi/motion.cpp +++ b/engines/agi/motion.cpp @@ -29,7 +29,7 @@ int AgiEngine::checkStep(int delta, int step) { return (-step >= delta) ? 0 : (step <= delta) ? 2 : 1; } -int AgiEngine::checkBlock(int x, int y) { +bool AgiEngine::checkBlock(int16 x, int16 y) { if (x <= _game.block.x1 || x >= _game.block.x2) return false; @@ -39,87 +39,90 @@ int AgiEngine::checkBlock(int x, int y) { return true; } -void AgiEngine::changePos(VtEntry *v) { - int b, x, y; +void AgiEngine::changePos(ScreenObjEntry *screenObj) { + bool insideBlock; + int16 x, y; int dx[9] = { 0, 0, 1, 1, 1, 0, -1, -1, -1 }; int dy[9] = { 0, -1, -1, 0, 1, 1, 1, 0, -1 }; - x = v->xPos; - y = v->yPos; - b = checkBlock(x, y); + x = screenObj->xPos; + y = screenObj->yPos; + insideBlock = checkBlock(x, y); - x += v->stepSize * dx[v->direction]; - y += v->stepSize * dy[v->direction]; + x += screenObj->stepSize * dx[screenObj->direction]; + y += screenObj->stepSize * dy[screenObj->direction]; - if (checkBlock(x, y) == b) { - v->flags &= ~fMotion; + if (checkBlock(x, y) == insideBlock) { + screenObj->flags &= ~fMotion; } else { - v->flags |= fMotion; - v->direction = 0; - if (isEgoView(v)) - _game.vars[vEgoDir] = 0; + screenObj->flags |= fMotion; + screenObj->direction = 0; + if (isEgoView(screenObj)) + _game.vars[VM_VAR_EGO_DIRECTION] = 0; } } -void AgiEngine::motionWander(VtEntry *v) { - if (v->parm1--) { - if (~v->flags & fDidntMove) - return; - } +void AgiEngine::motionWander(ScreenObjEntry *screenObj) { + uint8 originalWanderCount = screenObj->wander_count; - v->direction = _rnd->getRandomNumber(8); + screenObj->wander_count--; + if ((originalWanderCount == 0) || (screenObj->flags & fDidntMove)) { + screenObj->direction = _rnd->getRandomNumber(8); - if (isEgoView(v)) { - _game.vars[vEgoDir] = v->direction; - while (v->parm1 < 6) { - v->parm1 = _rnd->getRandomNumber(50); // huh? + if (isEgoView(screenObj)) { + _game.vars[VM_VAR_EGO_DIRECTION] = screenObj->direction; + } + + while (screenObj->wander_count < 6) { + screenObj->wander_count = _rnd->getRandomNumber(50); // huh? } } } -void AgiEngine::motionFollowEgo(VtEntry *v) { +void AgiEngine::motionFollowEgo(ScreenObjEntry *screenObj) { + ScreenObjEntry *screenObjEgo = &_game.screenObjTable[SCREENOBJECTS_EGO_ENTRY]; int egoX, egoY; int objX, objY; int dir; - egoX = _game.viewTable[0].xPos + _game.viewTable[0].xSize / 2; - egoY = _game.viewTable[0].yPos; + egoX = screenObjEgo->xPos + screenObjEgo->xSize / 2; + egoY = screenObjEgo->yPos; - objX = v->xPos + v->xSize / 2; - objY = v->yPos; + objX = screenObj->xPos + screenObj->xSize / 2; + objY = screenObj->yPos; // Get direction to reach ego - dir = getDirection(objX, objY, egoX, egoY, v->parm1); + dir = getDirection(objX, objY, egoX, egoY, screenObj->follow_stepSize); // Already at ego coordinates if (dir == 0) { - v->direction = 0; - v->motion = kMotionNormal; - setflag(v->parm2, true); + screenObj->direction = 0; + screenObj->motionType = kMotionNormal; + setflag(screenObj->follow_flag, true); return; } - if (v->parm3 == 0xff) { - v->parm3 = 0; - } else if (v->flags & fDidntMove) { + if (screenObj->follow_count == 0xff) { + screenObj->follow_count = 0; + } else if (screenObj->flags & fDidntMove) { int d; - while ((v->direction = _rnd->getRandomNumber(8)) == 0) { + while ((screenObj->direction = _rnd->getRandomNumber(8)) == 0) { } d = (ABS(egoY - objY) + ABS(egoX - objX)) / 2; - if (d < v->stepSize) { - v->parm3 = v->stepSize; + if (d < screenObj->stepSize) { + screenObj->follow_count = screenObj->stepSize; return; } - while ((v->parm3 = _rnd->getRandomNumber(d)) < v->stepSize) { + while ((screenObj->follow_count = _rnd->getRandomNumber(d)) < screenObj->stepSize) { } return; } - if (v->parm3 != 0) { + if (screenObj->follow_count != 0) { int k; // DF: this is ugly and I dont know why this works, but @@ -128,45 +131,46 @@ void AgiEngine::motionFollowEgo(VtEntry *v) { // if (((int8)v->parm3 -= v->step_size) < 0) // v->parm3 = 0; - k = v->parm3; - k -= v->stepSize; - v->parm3 = k; + k = screenObj->follow_count; + k -= screenObj->stepSize; + screenObj->follow_count = k; - if ((int8) v->parm3 < 0) - v->parm3 = 0; + if ((int8) screenObj->follow_count < 0) + screenObj->follow_count = 0; } else { - v->direction = dir; + screenObj->direction = dir; } } -void AgiEngine::motionMoveObj(VtEntry *v) { - v->direction = getDirection(v->xPos, v->yPos, v->parm1, v->parm2, v->stepSize); +void AgiEngine::motionMoveObj(ScreenObjEntry *screenObj) { + screenObj->direction = getDirection(screenObj->xPos, screenObj->yPos, screenObj->move_x, screenObj->move_y, screenObj->stepSize); // Update V6 if ego - if (isEgoView(v)) - _game.vars[vEgoDir] = v->direction; + if (isEgoView(screenObj)) + _game.vars[VM_VAR_EGO_DIRECTION] = screenObj->direction; - if (v->direction == 0) - inDestination(v); + if (screenObj->direction == 0) + motionMoveObjStop(screenObj); } -void AgiEngine::checkMotion(VtEntry *v) { - switch (v->motion) { +void AgiEngine::checkMotion(ScreenObjEntry *screenObj) { + switch (screenObj->motionType) { case kMotionNormal: break; case kMotionWander: - motionWander(v); + motionWander(screenObj); break; case kMotionFollowEgo: - motionFollowEgo(v); + motionFollowEgo(screenObj); break; + case kMotionEgo: case kMotionMoveObj: - motionMoveObj(v); + motionMoveObj(screenObj); break; } - if ((_game.block.active && (~v->flags & fIgnoreBlocks)) && v->direction) - changePos(v); + if ((_game.block.active && (~screenObj->flags & fIgnoreBlocks)) && screenObj->direction) + changePos(screenObj); } /* @@ -177,12 +181,12 @@ void AgiEngine::checkMotion(VtEntry *v) { * */ void AgiEngine::checkAllMotions() { - VtEntry *v; + ScreenObjEntry *screenObj; - for (v = _game.viewTable; v < &_game.viewTable[MAX_VIEWTABLE]; v++) { - if ((v->flags & (fAnimated | fUpdate | fDrawn)) == (fAnimated | fUpdate | fDrawn) - && v->stepTimeCount == 1) { - checkMotion(v); + for (screenObj = _game.screenObjTable; screenObj < &_game.screenObjTable[SCREENOBJECTS_MAX]; screenObj++) { + if ((screenObj->flags & (fAnimated | fUpdate | fDrawn)) == (fAnimated | fUpdate | fDrawn) + && screenObj->stepTimeCount == 1) { + checkMotion(screenObj); } } } @@ -193,14 +197,27 @@ void AgiEngine::checkAllMotions() { * type motion that * has reached its final destination coordinates. * @param v Pointer to view table entry */ -void AgiEngine::inDestination(VtEntry *v) { - if (v->motion == kMotionMoveObj) { - v->stepSize = v->parm3; - setflag(v->parm4, true); +void AgiEngine::inDestination(ScreenObjEntry *screenObj) { + if (screenObj->motionType == kMotionMoveObj) { + screenObj->stepSize = screenObj->move_stepSize; + setflag(screenObj->move_flag, true); + } + screenObj->motionType = kMotionNormal; + if (isEgoView(screenObj)) + _game.playerControl = true; +} + +void AgiEngine::motionMoveObjStop(ScreenObjEntry *screenObj) { + screenObj->stepSize = screenObj->move_stepSize; + if (screenObj->motionType != kMotionEgo) { + setflag(screenObj->move_flag, true); } - v->motion = kMotionNormal; - if (isEgoView(v)) + + screenObj->motionType = kMotionNormal; + if (isEgoView(screenObj)) { _game.playerControl = true; + _game.vars[VM_VAR_EGO_DIRECTION] = 0; + } } /** @@ -209,8 +226,8 @@ void AgiEngine::inDestination(VtEntry *v) { * after setting the motion mode to kMotionMoveObj. * @param v Pointer to view table entry */ -void AgiEngine::moveObj(VtEntry *v) { - motionMoveObj(v); +void AgiEngine::moveObj(ScreenObjEntry *screenObj) { + motionMoveObj(screenObj); } /** @@ -223,9 +240,9 @@ void AgiEngine::moveObj(VtEntry *v) { * @param y y coordinate of the object * @param s step size */ -int AgiEngine::getDirection(int x0, int y0, int x, int y, int s) { +int AgiEngine::getDirection(int16 objX, int16 objY, int16 destX, int16 destY, int16 stepSize) { int dirTable[9] = { 8, 1, 2, 7, 0, 3, 6, 5, 4 }; - return dirTable[checkStep(x - x0, s) + 3 * checkStep(y - y0, s)]; + return dirTable[checkStep(destX - objX, stepSize) + 3 * checkStep(destY - objY, stepSize)]; } } // End of namespace Agi diff --git a/engines/agi/mouse_cursor.h b/engines/agi/mouse_cursor.h new file mode 100644 index 0000000000..1883960451 --- /dev/null +++ b/engines/agi/mouse_cursor.h @@ -0,0 +1,183 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef AGI_MOUSE_CURSOR_H +#define AGI_MOUSE_CURSOR_H + +namespace Agi { + +/** + * RGB-palette for the Amiga-style arrow cursor + * and the Amiga-style busy cursor. + */ +static const byte MOUSECURSOR_PALETTE[] = { + 0x00, 0x00, 0x00, // Black + 0xFF, 0xFF, 0xFF, // White + 0xDE, 0x20, 0x21, // Red + 0xFF, 0xCF, 0xAD // Light red +}; + +/** + * 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 MOUSECURSOR_SCI[] = { + 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 +}; + +/** + * A black and white SCI-style busy cursor (15x16). + * 0 = Transparent. + * 1 = Black (#000000 in 24-bit RGB). + * 2 = White (#FFFFFF in 24-bit RGB). + */ +static const byte MOUSECURSOR_SCI_BUSY[] = { + 0,0,0,0,0,0,0,1,1,1,0,0,0,0,0, + 0,0,0,0,1,1,1,2,2,1,1,1,0,0,0, + 0,0,0,1,2,2,1,2,2,1,2,2,1,0,0, + 0,1,1,1,2,2,1,2,2,1,2,2,1,0,0, + 1,2,2,1,2,2,1,2,2,1,2,2,1,0,0, + 1,2,2,1,2,2,1,2,2,1,2,2,1,0,0, + 1,2,2,1,2,2,1,2,2,1,2,1,2,1,0, + 1,2,2,1,2,2,1,2,2,1,1,2,2,1,1, + 1,2,2,1,2,2,1,2,2,1,1,2,2,2,1, + 1,2,2,2,2,2,2,2,2,1,1,2,2,2,1, + 1,2,2,2,2,2,2,2,1,2,2,2,2,1,0, + 1,2,2,2,2,2,2,1,2,2,2,2,2,1,0, + 0,1,2,2,2,2,2,1,2,2,2,2,1,0,0, + 0,1,2,2,2,2,2,2,2,2,2,2,1,0,0, + 0,0,1,2,2,2,2,2,2,2,2,1,0,0,0, + 0,0,0,1,1,1,1,1,1,1,1,0,0,0,0 +}; + +/** + * A black and white Atari ST style arrow cursor (11x16). + * 0 = Transparent. + * 1 = Black (#000000 in 24-bit RGB). + * 2 = White (#FFFFFF in 24-bit RGB). + */ +static const byte MOUSECURSOR_ATARI_ST[] = { + 2,2,0,0,0,0,0,0,0,0,0, + 2,1,2,0,0,0,0,0,0,0,0, + 2,1,1,2,0,0,0,0,0,0,0, + 2,1,1,1,2,0,0,0,0,0,0, + 2,1,1,1,1,2,0,0,0,0,0, + 2,1,1,1,1,1,2,0,0,0,0, + 2,1,1,1,1,1,1,2,0,0,0, + 2,1,1,1,1,1,1,1,2,0,0, + 2,1,1,1,1,1,1,1,1,2,0, + 2,1,1,1,1,1,2,2,2,2,2, + 2,1,1,2,1,1,2,0,0,0,0, + 2,1,2,0,2,1,1,2,0,0,0, + 2,2,0,0,2,1,1,2,0,0,0, + 2,0,0,0,0,2,1,1,2,0,0, + 0,0,0,0,0,2,1,1,2,0,0, + 0,0,0,0,0,0,2,2,2,0,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 MOUSECURSOR_APPLE_II_GS[] = { + 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 +}; + +/** + * An Amiga-style arrow cursor (8x11). + * 0 = Transparent. + * 1 = Black (#000000 in 24-bit RGB). + * 3 = Red (#DE2021 in 24-bit RGB). + * 4 = Light red (#FFCFAD in 24-bit RGB). + */ +static const byte MOUSECURSOR_AMIGA[] = { + 3,4,1,0,0,0,0,0, + 3,3,4,1,0,0,0,0, + 3,3,3,4,1,0,0,0, + 3,3,3,3,4,1,0,0, + 3,3,3,3,3,4,1,0, + 3,3,3,3,3,3,4,1, + 3,0,3,3,4,1,0,0, + 0,0,0,3,4,1,0,0, + 0,0,0,3,3,4,1,0, + 0,0,0,0,3,4,1,0, + 0,0,0,0,3,3,4,1 +}; + +/** + * 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 MOUSECURSOR_AMIGA_BUSY[] = { + 1,1,1,1,1,1,1,1,1,1,1,1,1, + 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, + 0,1,4,4,4,4,4,4,4,4,4,1,0, + 0,0,1,4,4,4,4,4,4,4,1,0,0, + 0,0,0,1,4,4,4,4,4,1,0,0,0, + 0,0,0,0,1,4,4,4,1,0,0,0,0, + 0,0,0,0,0,1,4,1,0,0,0,0,0, + 0,0,0,0,0,1,4,1,0,0,0,0,0, + 0,0,0,0,1,3,4,3,1,0,0,0,0, + 0,0,0,1,3,3,4,3,3,1,0,0,0, + 0,0,1,3,3,3,4,3,3,3,1,0,0, + 0,1,3,3,3,4,4,4,3,3,3,1,0, + 1,4,4,4,4,4,4,4,4,4,4,4,1, + 1,4,4,4,4,4,4,4,4,4,4,4,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1 +}; + +} // End of namespace Agi + +#endif /* AGI_MOUSE_CURSOR_H */ diff --git a/engines/agi/objects.cpp b/engines/agi/objects.cpp index 27cde61065..b6c628c32d 100644 --- a/engines/agi/objects.cpp +++ b/engines/agi/objects.cpp @@ -144,28 +144,28 @@ void AgiEngine::unloadObjects() { } } -void AgiEngine::objectSetLocation(unsigned int n, int i) { - if (n >= _game.numObjects) { - warning("AgiEngine::objectSetLocation: Can't access object %d.\n", n); +void AgiEngine::objectSetLocation(uint16 objectNr, int i) { + if (objectNr >= _game.numObjects) { + warning("AgiEngine::objectSetLocation: Can't access object %d.\n", objectNr); return; } - _objects[n].location = i; + _objects[objectNr].location = i; } -int AgiEngine::objectGetLocation(unsigned int n) { - if (n >= _game.numObjects) { - warning("AgiEngine::objectGetLocation: Can't access object %d.\n", n); +int AgiEngine::objectGetLocation(uint16 objectNr) { + if (objectNr >= _game.numObjects) { + warning("AgiEngine::objectGetLocation: Can't access object %d.\n", objectNr); return 0; } - return _objects[n].location; + return _objects[objectNr].location; } -const char *AgiEngine::objectName(unsigned int n) { - if (n >= _game.numObjects) { - warning("AgiEngine::objectName: Can't access object %d.\n", n); +const char *AgiEngine::objectName(uint16 objectNr) { + if (objectNr >= _game.numObjects) { + warning("AgiEngine::objectName: Can't access object %d.\n", objectNr); return ""; } - return _objects[n].name; + return _objects[objectNr].name; } } // End of namespace Agi diff --git a/engines/agi/op_cmd.cpp b/engines/agi/op_cmd.cpp index bf2a2ed77b..bb10c27d6e 100644 --- a/engines/agi/op_cmd.cpp +++ b/engines/agi/op_cmd.cpp @@ -23,55 +23,49 @@ #include "base/version.h" #include "agi/agi.h" +#include "agi/inv.h" #include "agi/sprite.h" +#include "agi/text.h" #include "agi/graphics.h" #include "agi/opcodes.h" #include "agi/menu.h" +#include "agi/systemui.h" +#include "agi/words.h" #include "common/random.h" #include "common/textconsole.h" namespace Agi { -#define p0 (p[0]) -#define p1 (p[1]) -#define p2 (p[2]) -#define p3 (p[3]) -#define p4 (p[4]) -#define p5 (p[5]) -#define p6 (p[6]) - -#define code state->_curLogic->data -#define ip state->_curLogic->cIP -#define vt state->viewTable[p0] -#define vt_v state->viewTable[state->vars[p0]] - -#define _v state->vars - #define getGameID() state->_vm->getGameID() #define getFeatures() state->_vm->getFeatures() #define getVersion() state->_vm->getVersion() #define getLanguage() state->_vm->getLanguage() -#define setflag(a,b) state->_vm->setflag(a,b) -#define getflag(a) state->_vm->getflag(a) -void cmdIncrement(AgiGame *state, uint8 *p) { +void cmdIncrement(AgiGame *state, uint8 *parameter) { + uint16 varNr = parameter[0]; + if (getVersion() < 0x2000) { - if (_v[p0] < 0xf0) - ++_v[p0]; + if (state->vars[varNr] < 0xf0) + ++state->vars[varNr]; } else { - if (_v[p0] != 0xff) - ++_v[p0]; + if (state->vars[varNr] != 0xff) + ++state->vars[varNr]; } } -void cmdDecrement(AgiGame *state, uint8 *p) { - if (_v[p0] != 0) - --_v[p0]; +void cmdDecrement(AgiGame *state, uint8 *parameter) { + uint16 varNr = parameter[0]; + + if (state->vars[varNr] != 0) + --state->vars[varNr]; } -void cmdAssignN(AgiGame *state, uint8 *p) { - _v[p0] = p1; +void cmdAssignN(AgiGame *state, uint8 *parameter) { + uint16 varNr = parameter[0]; + uint16 value = parameter[1]; + + state->vars[varNr] = value; // WORKAROUND for a bug in fan game "Get outta SQ" // Total number of points is stored in variable 7, which @@ -80,104 +74,170 @@ void cmdAssignN(AgiGame *state, uint8 *p) { // variable to the correct value here // Fixes bug #1942476 - "AGI: Fan(Get Outta SQ) - Score // is lost on restart" - if (getGameID() == GID_GETOUTTASQ && p0 == 7) - _v[p0] = 8; + if (getGameID() == GID_GETOUTTASQ && varNr == 7) + state->vars[varNr] = 8; } -void cmdAddN(AgiGame *state, uint8 *p) { - _v[p0] += p1; +void cmdAddN(AgiGame *state, uint8 *parameter) { + uint16 varNr = parameter[0]; + uint16 value = parameter[1]; + + state->vars[varNr] += value; } -void cmdSubN(AgiGame *state, uint8 *p) { - _v[p0] -= p1; +void cmdSubN(AgiGame *state, uint8 *parameter) { + uint16 varNr = parameter[0]; + uint16 value = parameter[1]; + + state->vars[varNr] -= value; } -void cmdAssignV(AgiGame *state, uint8 *p) { - _v[p0] = _v[p1]; +void cmdAssignV(AgiGame *state, uint8 *parameter) { + uint16 varNr1 = parameter[0]; + uint16 varNr2 = parameter[1]; + + state->vars[varNr1] = state->vars[varNr2]; } -void cmdAddV(AgiGame *state, uint8 *p) { - _v[p0] += _v[p1]; +void cmdAddV(AgiGame *state, uint8 *parameter) { + uint16 varNr1 = parameter[0]; + uint16 varNr2 = parameter[1]; + + state->vars[varNr1] += state->vars[varNr2]; } -void cmdSubV(AgiGame *state, uint8 *p) { - _v[p0] -= _v[p1]; +void cmdSubV(AgiGame *state, uint8 *parameter) { + uint16 varNr1 = parameter[0]; + uint16 varNr2 = parameter[1]; + + state->vars[varNr1] -= state->vars[varNr2]; } -void cmdMulN(AgiGame *state, uint8 *p) { - _v[p0] *= p1; +void cmdMulN(AgiGame *state, uint8 *parameter) { + uint16 varNr = parameter[0]; + uint16 value = parameter[1]; + + state->vars[varNr] *= value; } -void cmdMulV(AgiGame *state, uint8 *p) { - _v[p0] *= _v[p1]; +void cmdMulV(AgiGame *state, uint8 *parameter) { + uint16 varNr1 = parameter[0]; + uint16 varNr2 = parameter[1]; + + state->vars[varNr1] *= state->vars[varNr2]; } -void cmdDivN(AgiGame *state, uint8 *p) { - _v[p0] /= p1; +void cmdDivN(AgiGame *state, uint8 *parameter) { + uint16 varNr = parameter[0]; + uint16 value = parameter[1]; + + state->vars[varNr] /= value; } -void cmdDivV(AgiGame *state, uint8 *p) { - _v[p0] /= _v[p1]; +void cmdDivV(AgiGame *state, uint8 *parameter) { + uint16 varNr1 = parameter[0]; + uint16 varNr2 = parameter[1]; + + state->vars[varNr1] /= state->vars[varNr2]; } -void cmdRandomV1(AgiGame *state, uint8 *p) { - _v[p0] = state->_vm->_rnd->getRandomNumber(250); +void cmdRandomV1(AgiGame *state, uint8 *parameter) { + uint16 varNr = parameter[0]; + + state->vars[varNr] = state->_vm->_rnd->getRandomNumber(250); } -void cmdRandom(AgiGame *state, uint8 *p) { - _v[p2] = state->_vm->_rnd->getRandomNumber(p1 - p0) + p0; +void cmdRandom(AgiGame *state, uint8 *parameter) { + uint16 valueMin = parameter[0]; + uint16 valueMax = parameter[1]; + uint16 varNr = parameter[2]; + + state->vars[varNr] = state->_vm->_rnd->getRandomNumber(valueMax - valueMin) + valueMin; } -void cmdLindirectN(AgiGame *state, uint8 *p) { - _v[_v[p0]] = p1; +void cmdLindirectN(AgiGame *state, uint8 *parameter) { + uint16 varNr = parameter[0]; + uint16 value = parameter[1]; + + state->vars[state->vars[varNr]] = value; } -void cmdLindirectV(AgiGame *state, uint8 *p) { - _v[_v[p0]] = _v[p1]; +void cmdLindirectV(AgiGame *state, uint8 *parameter) { + uint16 varNr1 = parameter[0]; + uint16 varNr2 = parameter[1]; + + state->vars[state->vars[varNr1]] = state->vars[varNr2]; } -void cmdRindirect(AgiGame *state, uint8 *p) { - _v[p0] = _v[_v[p1]]; +void cmdRindirect(AgiGame *state, uint8 *parameter) { + uint16 varNr1 = parameter[0]; + uint16 varNr2 = parameter[1]; + + state->vars[varNr1] = state->vars[state->vars[varNr2]]; } -void cmdSet(AgiGame *state, uint8 *p) { - setflag(*p, true); +void cmdSet(AgiGame *state, uint8 *parameter) { + uint16 flagNr = parameter[0]; + + state->_vm->setflag(flagNr, true); } -void cmdReset(AgiGame *state, uint8 *p) { - setflag(*p, false); +void cmdReset(AgiGame *state, uint8 *parameter) { + uint16 flagNr = parameter[0]; + + state->_vm->setflag(flagNr, false); } -void cmdToggle(AgiGame *state, uint8 *p) { - setflag(*p, !getflag(*p)); +void cmdToggle(AgiGame *state, uint8 *parameter) { + AgiEngine *vm = state->_vm; + uint16 flagNr = parameter[0]; + bool curFlagState = vm->getflag(flagNr); + + vm->setflag(flagNr, !curFlagState); } -void cmdSetV(AgiGame *state, uint8 *p) { +void cmdSetV(AgiGame *state, uint8 *parameter) { + uint16 flagNr = parameter[0]; + if (getVersion() < 0x2000) { - _v[p0] = 1; + state->vars[flagNr] = 1; } else { - setflag(_v[p0], true); + flagNr = state->vars[flagNr]; + + state->_vm->setflag(flagNr, true); } } -void cmdResetV(AgiGame *state, uint8 *p) { +void cmdResetV(AgiGame *state, uint8 *parameter) { + uint16 flagNr = parameter[0]; + if (getVersion() < 0x2000) { - _v[p0] = 0; + state->vars[flagNr] = 0; } else { - setflag(_v[p0], false); + flagNr = state->vars[flagNr]; + + state->_vm->setflag(flagNr, false); } } -void cmdToggleV(AgiGame *state, uint8 *p) { +void cmdToggleV(AgiGame *state, uint8 *parameter) { + uint16 flagNr = parameter[0]; + if (getVersion() < 0x2000) { - _v[p0] ^= 1; + state->vars[flagNr] ^= 1; } else { - setflag(_v[p0], !getflag(_v[p0])); + AgiEngine *vm = state->_vm; + flagNr = state->vars[flagNr]; + bool curFlagState = vm->getflag(flagNr); + + vm->setflag(flagNr, !curFlagState); } } -void cmdNewRoom(AgiGame *state, uint8 *p) { - state->_vm->newRoom(p0); +void cmdNewRoom(AgiGame *state, uint8 *parameter) { + uint16 newRoomNr = parameter[0]; + + state->_vm->newRoom(newRoomNr); // WORKAROUND: Works around intro skipping bug (#1737343) in Gold Rush. // Intro was skipped because the enter-keypress finalizing the entering @@ -188,351 +248,524 @@ void cmdNewRoom(AgiGame *state, uint8 *p) { // keyboard buffer when the intro sequence's first room (Room 73) is // loaded so that no keys from the copy protection scene can be left // over to cause the intro to skip to the game's start. - if (getGameID() == GID_GOLDRUSH && p0 == 73) + if (getGameID() == GID_GOLDRUSH && newRoomNr == 73) state->keypress = 0; } -void cmdNewRoomF(AgiGame *state, uint8 *p) { - state->_vm->newRoom(_v[p0]); +void cmdNewRoomF(AgiGame *state, uint8 *parameter) { + uint16 varNr = parameter[0]; + + state->_vm->newRoom(state->vars[varNr]); } -void cmdLoadView(AgiGame *state, uint8 *p) { - state->_vm->agiLoadResource(rVIEW, p0); +void cmdLoadView(AgiGame *state, uint8 *parameter) { + uint16 resourceNr = parameter[0]; + + state->_vm->agiLoadResource(RESOURCETYPE_VIEW, resourceNr); } -void cmdLoadLogic(AgiGame *state, uint8 *p) { - state->_vm->agiLoadResource(rLOGIC, p0); +void cmdLoadLogic(AgiGame *state, uint8 *parameter) { + uint16 resourceNr = parameter[0]; + + state->_vm->agiLoadResource(RESOURCETYPE_LOGIC, resourceNr); } -void cmdLoadSound(AgiGame *state, uint8 *p) { - state->_vm->agiLoadResource(rSOUND, p0); +void cmdLoadSound(AgiGame *state, uint8 *parameter) { + uint16 resourceNr = parameter[0]; + + state->_vm->agiLoadResource(RESOURCETYPE_SOUND, resourceNr); } -void cmdLoadViewF(AgiGame *state, uint8 *p) { - state->_vm->agiLoadResource(rVIEW, _v[p0]); +void cmdLoadViewF(AgiGame *state, uint8 *parameter) { + uint16 varNr = parameter[0]; + + state->_vm->agiLoadResource(RESOURCETYPE_VIEW, state->vars[varNr]); } -void cmdLoadLogicF(AgiGame *state, uint8 *p) { - state->_vm->agiLoadResource(rLOGIC, _v[p0]); +void cmdLoadLogicF(AgiGame *state, uint8 *parameter) { + uint16 varNr = parameter[0]; + + state->_vm->agiLoadResource(RESOURCETYPE_LOGIC, state->vars[varNr]); } -void cmdDiscardView(AgiGame *state, uint8 *p) { - state->_vm->agiUnloadResource(rVIEW, p0); +void cmdDiscardView(AgiGame *state, uint8 *parameter) { + uint16 resourceNr = parameter[0]; + + state->_vm->agiUnloadResource(RESOURCETYPE_VIEW, resourceNr); } -void cmdObjectOnAnything(AgiGame *state, uint8 *p) { - vt.flags &= ~(fOnWater | fOnLand); +void cmdObjectOnAnything(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + screenObj->flags &= ~(fOnWater | fOnLand); } -void cmdObjectOnLand(AgiGame *state, uint8 *p) { - vt.flags |= fOnLand; +void cmdObjectOnLand(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + screenObj->flags |= fOnLand; } -void cmdObjectOnWater(AgiGame *state, uint8 *p) { - vt.flags |= fOnWater; +void cmdObjectOnWater(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + screenObj->flags |= fOnWater; } -void cmdObserveHorizon(AgiGame *state, uint8 *p) { - vt.flags &= ~fIgnoreHorizon; +void cmdObserveHorizon(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + screenObj->flags &= ~fIgnoreHorizon; } -void cmdIgnoreHorizon(AgiGame *state, uint8 *p) { - vt.flags |= fIgnoreHorizon; +void cmdIgnoreHorizon(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + screenObj->flags |= fIgnoreHorizon; } -void cmdObserveObjs(AgiGame *state, uint8 *p) { - vt.flags &= ~fIgnoreObjects; +void cmdObserveObjs(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + screenObj->flags &= ~fIgnoreObjects; } -void cmdIgnoreObjs(AgiGame *state, uint8 *p) { - vt.flags |= fIgnoreObjects; +void cmdIgnoreObjs(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + screenObj->flags |= fIgnoreObjects; } -void cmdObserveBlocks(AgiGame *state, uint8 *p) { - vt.flags &= ~fIgnoreBlocks; +void cmdObserveBlocks(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + screenObj->flags &= ~fIgnoreBlocks; } -void cmdIgnoreBlocks(AgiGame *state, uint8 *p) { - vt.flags |= fIgnoreBlocks; +void cmdIgnoreBlocks(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + screenObj->flags |= fIgnoreBlocks; } -void cmdSetHorizon(AgiGame *state, uint8 *p) { - state->horizon = p0; +void cmdSetHorizon(AgiGame *state, uint8 *parameter) { + uint16 horizonValue = parameter[0]; + + state->horizon = horizonValue; } -void cmdGetPriority(AgiGame *state, uint8 *p) { - _v[p1] = vt.priority; +void cmdGetPriority(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 varNr = parameter[1]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + state->vars[varNr] = screenObj->priority; } -void cmdSetPriority(AgiGame *state, uint8 *p) { - vt.flags |= fFixedPriority; - vt.priority = p1; +void cmdSetPriority(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 priority = parameter[1]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; - // WORKAROUND: this fixes bug #1712585 in KQ4 (dwarf sprite priority) - // For this scene, ego (Rosella) hasn't got a fixed priority till script 54 - // explicitly sets priority 8 for her, so that she can walk back to the table - // without being drawn over the other dwarfs - // It seems that in this scene, ego's priority is set to 8, but the priority of - // the last dwarf with the soup bowls (view 152) is also set to 8, which causes - // the dwarf to be drawn behind ego - // With this workaround, when the game scripts set the priority of view 152 - // (seventh dwarf with soup bowls), ego's priority is set to 7 - // The game script itself sets priotity 8 for ego before she starts walking, - // and then releases the fixed priority set on ego after ego is seated - // Therefore, this workaround only affects that specific part of this scene - // Ego is set to object 19 by script 54 - if (getGameID() == GID_KQ4 && vt.currentView == 152) { - state->viewTable[19].flags |= fFixedPriority; - state->viewTable[19].priority = 7; - } + screenObj->flags |= fFixedPriority; + screenObj->priority = priority; } -void cmdSetPriorityF(AgiGame *state, uint8 *p) { - vt.flags |= fFixedPriority; - vt.priority = _v[p1]; +void cmdSetPriorityF(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 varNr = parameter[1]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + screenObj->flags |= fFixedPriority; + screenObj->priority = state->vars[varNr]; } -void cmdReleasePriority(AgiGame *state, uint8 *p) { - vt.flags &= ~fFixedPriority; +void cmdReleasePriority(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + screenObj->flags &= ~fFixedPriority; } -void cmdSetUpperLeft(AgiGame *state, uint8 *p) { // do nothing (AGI 2.917) +void cmdSetUpperLeft(AgiGame *state, uint8 *parameter) { // do nothing (AGI 2.917) } -void cmdStartUpdate(AgiGame *state, uint8 *p) { - state->_vm->startUpdate(&vt); +void cmdStartUpdate(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + state->_vm->startUpdate(screenObj); } -void cmdStopUpdate(AgiGame *state, uint8 *p) { - state->_vm->stopUpdate(&vt); +void cmdStopUpdate(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + state->_vm->stopUpdate(screenObj); } -void cmdCurrentView(AgiGame *state, uint8 *p) { - _v[p1] = vt.currentView; +void cmdCurrentView(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 varNr = parameter[1]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + state->vars[varNr] = screenObj->currentViewNr; } -void cmdCurrentCel(AgiGame *state, uint8 *p) { - _v[p1] = vt.currentCel; - debugC(4, kDebugLevelScripts, "v%d=%d", p1, _v[p1]); +void cmdCurrentCel(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 varNr = parameter[1]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + state->vars[varNr] = screenObj->currentCelNr; + debugC(4, kDebugLevelScripts, "v%d=%d", varNr, state->vars[varNr]); } -void cmdCurrentLoop(AgiGame *state, uint8 *p) { - _v[p1] = vt.currentLoop; +void cmdCurrentLoop(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 varNr = parameter[1]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + state->vars[varNr] = screenObj->currentLoopNr; } -void cmdLastCel(AgiGame *state, uint8 *p) { - _v[p1] = vt.loopData->numCels - 1; +void cmdLastCel(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 varNr = parameter[1]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + state->vars[varNr] = screenObj->loopData->celCount - 1; } -void cmdSetCel(AgiGame *state, uint8 *p) { - state->_vm->setCel(&vt, p1); +void cmdSetCel(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 celNr = parameter[1]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + state->_vm->setCel(screenObj, celNr); if (getVersion() >= 0x2000) { - vt.flags &= ~fDontupdate; + screenObj->flags &= ~fDontupdate; } } -void cmdSetCelF(AgiGame *state, uint8 *p) { - state->_vm->setCel(&vt, _v[p1]); - vt.flags &= ~fDontupdate; +void cmdSetCelF(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 varNr = parameter[1]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + state->_vm->setCel(screenObj, state->vars[varNr]); + screenObj->flags &= ~fDontupdate; } -void cmdSetView(AgiGame *state, uint8 *p) { - state->_vm->setView(&vt, p1); +void cmdSetView(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 viewNr = parameter[1]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + state->_vm->setView(screenObj, viewNr); } -void cmdSetViewF(AgiGame *state, uint8 *p) { - state->_vm->setView(&vt, _v[p1]); +void cmdSetViewF(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 varNr = parameter[1]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + state->_vm->setView(screenObj, state->vars[varNr]); } -void cmdSetLoop(AgiGame *state, uint8 *p) { - state->_vm->setLoop(&vt, p1); +void cmdSetLoop(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 loopNr = parameter[1]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + state->_vm->setLoop(screenObj, loopNr); } -void cmdSetLoopF(AgiGame *state, uint8 *p) { - state->_vm->setLoop(&vt, _v[p1]); +void cmdSetLoopF(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 varNr = parameter[1]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + state->_vm->setLoop(screenObj, state->vars[varNr]); } -void cmdNumberOfLoops(AgiGame *state, uint8 *p) { - _v[p1] = vt.numLoops; +void cmdNumberOfLoops(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 varNr = parameter[1]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + state->vars[varNr] = screenObj->loopCount; } -void cmdFixLoop(AgiGame *state, uint8 *p) { - vt.flags |= fFixLoop; +void cmdFixLoop(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + screenObj->flags |= fFixLoop; } -void cmdReleaseLoop(AgiGame *state, uint8 *p) { - vt.flags &= ~fFixLoop; +void cmdReleaseLoop(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + screenObj->flags &= ~fFixLoop; } -void cmdStepSize(AgiGame *state, uint8 *p) { - vt.stepSize = _v[p1]; +void cmdStepSize(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 varNr = parameter[1]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + screenObj->stepSize = state->vars[varNr]; } -void cmdStepTime(AgiGame *state, uint8 *p) { - vt.stepTime = vt.stepTimeCount = _v[p1]; +void cmdStepTime(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 varNr = parameter[1]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + screenObj->stepTime = screenObj->stepTimeCount = state->vars[varNr]; } -void cmdCycleTime(AgiGame *state, uint8 *p) { - vt.cycleTime = vt.cycleTimeCount = _v[p1]; +void cmdCycleTime(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 varNr = parameter[1]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + screenObj->cycleTime = screenObj->cycleTimeCount = state->vars[varNr]; } -void cmdStopCycling(AgiGame *state, uint8 *p) { - vt.flags &= ~fCycling; +void cmdStopCycling(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + screenObj->flags &= ~fCycling; } -void cmdStartCycling(AgiGame *state, uint8 *p) { - vt.flags |= fCycling; +void cmdStartCycling(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + screenObj->flags |= fCycling; } -void cmdNormalCycle(AgiGame *state, uint8 *p) { - vt.cycle = kCycleNormal; - vt.flags |= fCycling; +void cmdNormalCycle(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + screenObj->cycle = kCycleNormal; + screenObj->flags |= fCycling; } -void cmdReverseCycle(AgiGame *state, uint8 *p) { - vt.cycle = kCycleReverse; - vt.flags |= fCycling; +void cmdReverseCycle(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + screenObj->cycle = kCycleReverse; + screenObj->flags |= fCycling; } -void cmdSetDir(AgiGame *state, uint8 *p) { - vt.direction = _v[p1]; +void cmdSetDir(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 varNr = parameter[1]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + screenObj->direction = state->vars[varNr]; } -void cmdGetDir(AgiGame *state, uint8 *p) { - _v[p1] = vt.direction; +void cmdGetDir(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 varNr = parameter[1]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + state->vars[varNr] = screenObj->direction; } -void cmdGetRoomF(AgiGame *state, uint8 *p) { - _v[p1] = state->_vm->objectGetLocation(_v[p0]); +void cmdGetRoomF(AgiGame *state, uint8 *parameter) { + uint16 varNr1 = parameter[0]; + uint16 varNr2 = parameter[1]; + + state->vars[varNr2] = state->_vm->objectGetLocation(state->vars[varNr1]); } -void cmdPut(AgiGame *state, uint8 *p) { - state->_vm->objectSetLocation(p0, _v[p1]); +void cmdPut(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 varNr = parameter[1]; + + state->_vm->objectSetLocation(objectNr, state->vars[varNr]); } -void cmdPutF(AgiGame *state, uint8 *p) { - state->_vm->objectSetLocation(_v[p0], _v[p1]); +void cmdPutF(AgiGame *state, uint8 *parameter) { + uint16 varNr1 = parameter[0]; + uint16 varNr2 = parameter[1]; + + state->_vm->objectSetLocation(state->vars[varNr1], state->vars[varNr2]); } -void cmdDrop(AgiGame *state, uint8 *p) { - state->_vm->objectSetLocation(p0, 0); +void cmdDrop(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + + state->_vm->objectSetLocation(objectNr, 0); } -void cmdGet(AgiGame *state, uint8 *p) { - state->_vm->objectSetLocation(p0, EGO_OWNED); +void cmdGet(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + + state->_vm->objectSetLocation(objectNr, EGO_OWNED); } -void cmdGetV1(AgiGame *state, uint8 *p) { - state->_vm->objectSetLocation(p0, EGO_OWNED_V1); +void cmdGetV1(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + + state->_vm->objectSetLocation(objectNr, EGO_OWNED_V1); } -void cmdGetF(AgiGame *state, uint8 *p) { - state->_vm->objectSetLocation(_v[p0], EGO_OWNED); +void cmdGetF(AgiGame *state, uint8 *parameter) { + uint16 varNr = parameter[0]; + + state->_vm->objectSetLocation(state->vars[varNr], EGO_OWNED); } -void cmdWordToString(AgiGame *state, uint8 *p) { - strcpy(state->strings[p0], state->egoWords[p1].word); +void cmdWordToString(AgiGame *state, uint8 *parameter) { + uint16 stringNr = parameter[0]; + uint16 wordNr = parameter[1]; + + strcpy(state->strings[stringNr], state->_vm->_words->getEgoWord(wordNr)); } -void cmdOpenDialogue(AgiGame *state, uint8 *p) { - state->hasWindow = true; +void cmdOpenDialogue(AgiGame *state, uint8 *parameter) { + state->_vm->_text->dialogueOpen(); } -void cmdCloseDialogue(AgiGame *state, uint8 *p) { - state->hasWindow = false; +void cmdCloseDialogue(AgiGame *state, uint8 *parameter) { + state->_vm->_text->dialogueClose(); } -void cmdCloseWindow(AgiGame *state, uint8 *p) { - state->_vm->closeWindow(); +void cmdCloseWindow(AgiGame *state, uint8 *parameter) { + state->_vm->_text->closeWindow(); } -void cmdStatusLineOn(AgiGame *state, uint8 *p) { - state->statusLine = true; - state->_vm->writeStatus(); +void cmdStatusLineOn(AgiGame *state, uint8 *parameter) { + TextMgr *text = state->_vm->_text; + + text->statusEnable(); + text->statusDraw(); } -void cmdStatusLineOff(AgiGame *state, uint8 *p) { - state->statusLine = false; - state->_vm->writeStatus(); +void cmdStatusLineOff(AgiGame *state, uint8 *parameter) { + TextMgr *text = state->_vm->_text; + + text->statusDisable(); + state->_vm->_text->statusClear(); } -void cmdShowObj(AgiGame *state, uint8 *p) { - state->_vm->_sprites->showObj(p0); +void cmdShowObj(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + + state->_vm->_sprites->showObject(objectNr); } -void cmdShowObjV(AgiGame *state, uint8 *p) { - state->_vm->_sprites->showObj(_v[p0]); +void cmdShowObjV(AgiGame *state, uint8 *parameter) { + uint16 varNr = parameter[0]; + + state->_vm->_sprites->showObject(state->vars[varNr]); } -void cmdSound(AgiGame *state, uint8 *p) { - state->_vm->_sound->startSound(p0, p1); +void cmdSound(AgiGame *state, uint8 *parameter) { + uint16 resourceNr = parameter[0]; + uint16 flagNr = parameter[1]; + + state->_vm->_sound->startSound(resourceNr, flagNr); } -void cmdStopSound(AgiGame *state, uint8 *p) { +void cmdStopSound(AgiGame *state, uint8 *parameter) { state->_vm->_sound->stopSound(); } -void cmdMenuInput(AgiGame *state, uint8 *p) { - state->_vm->newInputMode(INPUT_MENU); +void cmdMenuInput(AgiGame *state, uint8 *parameter) { + AgiEngine *vm = state->_vm; + + if (vm->getflag(VM_FLAG_MENUS_WORK)) { + vm->_menu->delayedExecute(); + } } -void cmdEnableItem(AgiGame *state, uint8 *p) { - state->_vm->_menu->setItem(p0, true); +void cmdEnableItem(AgiGame *state, uint8 *parameter) { + uint16 controlCode = parameter[0]; + + state->_vm->_menu->itemEnable(controlCode); } -void cmdDisableItem(AgiGame *state, uint8 *p) { - state->_vm->_menu->setItem(p0, false); +void cmdDisableItem(AgiGame *state, uint8 *parameter) { + uint16 controlCode = parameter[0]; + + state->_vm->_menu->itemDisable(controlCode); } -void cmdSubmitMenu(AgiGame *state, uint8 *p) { +void cmdSubmitMenu(AgiGame *state, uint8 *parameter) { state->_vm->_menu->submit(); } -void cmdSetScanStart(AgiGame *state, uint8 *p) { +void cmdSetScanStart(AgiGame *state, uint8 *parameter) { state->_curLogic->sIP = state->_curLogic->cIP; } -void cmdResetScanStart(AgiGame *state, uint8 *p) { +void cmdResetScanStart(AgiGame *state, uint8 *parameter) { state->_curLogic->sIP = 2; } -void cmdSaveGame(AgiGame *state, uint8 *p) { - state->simpleSave ? state->_vm->saveGameSimple() : state->_vm->saveGameDialog(); -} - -void cmdLoadGame(AgiGame *state, uint8 *p) { - assert(1); - state->simpleSave ? state->_vm->loadGameSimple() : state->_vm->loadGameDialog(); +void cmdSaveGame(AgiGame *state, uint8 *parameter) { + if (state->automaticSave) { + if (state->_vm->saveGameAutomatic()) { + // automatic save succeded + return; + } + // fall back to regular dialog otherwise + } + state->_vm->saveGameDialog(); } -void cmdInitDisk(AgiGame *state, uint8 *p) { // do nothing +void cmdLoadGame(AgiGame *state, uint8 *parameter) { + if (state->automaticSave) { + if (state->_vm->loadGameAutomatic()) { + // automatic restore succeded + return; + } + // fall back to regular dialog otherwise + } + state->_vm->loadGameDialog(); } -void cmdLog(AgiGame *state, uint8 *p) { // do nothing +void cmdInitDisk(AgiGame *state, uint8 *parameter) { // do nothing } -void cmdTraceOn(AgiGame *state, uint8 *p) { // do nothing +void cmdLog(AgiGame *state, uint8 *parameter) { // do nothing } -void cmdTraceInfo(AgiGame *state, uint8 *p) { // do nothing +void cmdTraceOn(AgiGame *state, uint8 *parameter) { // do nothing } -void cmdShowMem(AgiGame *state, uint8 *p) { - state->_vm->messageBox("Enough memory"); +void cmdTraceInfo(AgiGame *state, uint8 *parameter) { // do nothing } -void cmdInitJoy(AgiGame *state, uint8 *p) { // do nothing +void cmdShowMem(AgiGame *state, uint8 *parameter) { + state->_vm->_text->messageBox("Enough memory"); } -void cmdScriptSize(AgiGame *state, uint8 *p) { - debug(0, "script.size(%d)", p0); +void cmdInitJoy(AgiGame *state, uint8 *parameter) { // do nothing } -void cmdCancelLine(AgiGame *state, uint8 *p) { - state->inputBuffer[0] = 0; - state->cursorPos = 0; - state->_vm->writePrompt(); +void cmdScriptSize(AgiGame *state, uint8 *parameter) { + debug(0, "script.size(%d)", parameter[0]); } // This implementation is based on observations of Amiga's Gold Rush. @@ -545,13 +778,16 @@ void cmdCancelLine(AgiGame *state, uint8 *p) { // 4051 (When ego is stationary), // 471 (When walking on the first screen's bridge), // 71 (When walking around, using the mouse or the keyboard). -void cmdObjStatusF(AgiGame *state, uint8 *p) { +void cmdObjStatusF(AgiGame *state, uint8 *parameter) { + uint16 varNr = parameter[0]; + ScreenObjEntry *screenObj = &state->screenObjTable[state->vars[varNr]]; + const char *cycleDesc; // Object's cycle description line const char *motionDesc; // Object's motion description line char msg[256]; // The whole object status message // Generate cycle description line - switch (vt_v.cycle) { + switch (screenObj->cycle) { case kCycleNormal: cycleDesc = "normal cycle"; break; @@ -570,7 +806,7 @@ void cmdObjStatusF(AgiGame *state, uint8 *p) { } // Generate motion description line - switch (vt_v.motion) { + switch (screenObj->motionType) { case kMotionNormal: motionDesc = "normal motion"; break; @@ -599,14 +835,14 @@ void cmdObjStatusF(AgiGame *state, uint8 *p) { "stepsize: %d\n" \ "%s\n" \ "%s", - _v[p0], - vt_v.xPos, vt_v.xSize, - vt_v.yPos, vt_v.ySize, - vt_v.priority, - vt_v.stepSize, + state->vars[varNr], + screenObj->xPos, screenObj->xSize, + screenObj->yPos, screenObj->ySize, + screenObj->priority, + screenObj->stepSize, cycleDesc, motionDesc); - state->_vm->messageBox(msg); + state->_vm->_text->messageBox(msg); } // unknown commands: @@ -617,49 +853,67 @@ void cmdObjStatusF(AgiGame *state, uint8 *p) { // unk_174: Change priority table (used in KQ4) -- j5 // unk_177: Disable menus completely -- j5 // unk_181: Deactivate keypressed control (default control of ego) -void cmdSetSimple(AgiGame *state, uint8 *p) { +void cmdSetSimple(AgiGame *state, uint8 *parameter) { if (!(getFeatures() & (GF_AGI256 | GF_AGI256_2))) { - state->simpleSave = true; + int16 stringNr = parameter[0]; + const char *textPtr = nullptr; + + state->automaticSave = false; + + // Try to get description for automatic saves +// if (state->strings_curLogic->texts && state->_curLogic->numTexts >= textNr) { + textPtr = state->strings[stringNr]; + strncpy(state->automaticSaveDescription, textPtr, sizeof(state->automaticSaveDescription)); + if (state->automaticSaveDescription[0]) { + // We got it and it's set, so enable automatic saving + state->automaticSave = true; + } +// } + } else { // AGI256 and AGI256-2 use this unknown170 command to load 256 color pictures. // Load the picture. Similar to void cmdLoad_pic(AgiGame *state, uint8 *p). - state->_vm->_sprites->eraseBoth(); - state->_vm->agiLoadResource(rPICTURE, _v[p0]); + SpritesMgr *spritesMgr = state->_vm->_sprites; + uint16 varNr = parameter[0]; + uint16 resourceNr = state->vars[varNr]; + + spritesMgr->eraseSprites(); + state->_vm->agiLoadResource(RESOURCETYPE_PICTURE, resourceNr); // Draw the picture. Similar to void cmdDraw_pic(AgiGame *state, uint8 *p). - state->_vm->_picture->decodePicture(_v[p0], false, true); - state->_vm->_sprites->blitBoth(); - state->pictureShown = 0; + state->_vm->_picture->decodePicture(resourceNr, false, true); + spritesMgr->drawAllSpriteLists(); + state->pictureShown = false; // Show the picture. Similar to void cmdShow_pic(AgiGame *state, uint8 *p). - setflag(fOutputMode, false); - state->_vm->closeWindow(); + state->_vm->setflag(VM_FLAG_OUTPUT_MODE, false); + state->_vm->_text->closeWindow(); state->_vm->_picture->showPic(); - state->pictureShown = 1; + state->pictureShown = true; - // Simulate slowww computer. Many effects rely on this - state->_vm->pause(kPausePicture); + // Loading trigger + state->_vm->loadingTrigger_DrawPicture(); } } -void cmdPopScript(AgiGame *state, uint8 *p) { +void cmdPopScript(AgiGame *state, uint8 *parameter) { if (getVersion() >= 0x2915) { debug(0, "pop.script"); } } -void cmdHoldKey(AgiGame *state, uint8 *p) { +void cmdHoldKey(AgiGame *state, uint8 *parameter) { if (getVersion() >= 0x3098) { state->_vm->_egoHoldKey = true; } } -void cmdDiscardSound(AgiGame *state, uint8 *p) { +void cmdDiscardSound(AgiGame *state, uint8 *parameter) { if (getVersion() >= 0x2936) { debug(0, "discard.sound"); } } -void cmdHideMouse(AgiGame *state, uint8 *p) { +void cmdHideMouse(AgiGame *state, uint8 *parameter) { // WORKAROUND: Turns off current movement that's being caused with the mouse. // This fixes problems with too many popup boxes appearing in the Amiga // Gold Rush's copy protection failure scene (i.e. the hanging scene, logic.192). @@ -667,34 +921,48 @@ void cmdHideMouse(AgiGame *state, uint8 *p) { // to walk somewhere else than to the right using the mouse. // FIXME: Write a proper implementation using disassembly and // apply it to other games as well if applicable. - state->viewTable[0].flags &= ~fAdjEgoXY; + //state->screenObjTable[SCREENOBJECTS_EGO_ENTRY].flags &= ~fAdjEgoXY; + state->_vm->_game.mouseHidden = true; g_system->showMouse(false); } -void cmdAllowMenu(AgiGame *state, uint8 *p) { +void cmdAllowMenu(AgiGame *state, uint8 *parameter) { + uint16 allowed = parameter[0]; + if (getVersion() >= 0x3098) { - setflag(fMenusWork, ((p0 != 0) ? true : false)); + if (allowed) { + state->_vm->_menu->accessAllow(); + } else { + state->_vm->_menu->accessDeny(); + } } } -void cmdShowMouse(AgiGame *state, uint8 *p) { +void cmdShowMouse(AgiGame *state, uint8 *parameter) { + state->_vm->_game.mouseHidden = false; + g_system->showMouse(true); } -void cmdFenceMouse(AgiGame *state, uint8 *p) { - state->mouseFence.moveTo(p0, p1); - state->mouseFence.setWidth(p2 - p0); - state->mouseFence.setHeight(p3 - p1); +void cmdFenceMouse(AgiGame *state, uint8 *parameter) { + uint16 varNr1 = parameter[0]; + uint16 varNr2 = parameter[1]; + uint16 varNr3 = parameter[2]; + uint16 varNr4 = parameter[3]; + + state->mouseFence.moveTo(varNr1, varNr2); + state->mouseFence.setWidth(varNr3 - varNr1); + state->mouseFence.setHeight(varNr4 - varNr1); } -void cmdReleaseKey(AgiGame *state, uint8 *p) { +void cmdReleaseKey(AgiGame *state, uint8 *parameter) { if (getVersion() >= 0x3098) { state->_vm->_egoHoldKey = false; } } -void cmdAdjEgoMoveToXY(AgiGame *state, uint8 *p) { +void cmdAdjEgoMoveToXY(AgiGame *state, uint8 *parameter) { int8 x, y; switch (logicNamesCmd[182].argumentsLength()) { @@ -703,8 +971,8 @@ void cmdAdjEgoMoveToXY(AgiGame *state, uint8 *p) { // (Using arguments (0, 0), (0, 7), (0, 8), (9, 9) and (-9, 9)). case 2: // Both arguments are signed 8-bit (i.e. in range -128 to +127). - x = (int8) p0; - y = (int8) p1; + x = (int8) parameter[0]; + y = (int8) parameter[1]; // Turn off ego's current movement caused with the mouse if // adj.ego.move.to.x.y is called with other arguments than previously. @@ -717,7 +985,7 @@ void cmdAdjEgoMoveToXY(AgiGame *state, uint8 *p) { // by something else because this command doesn't do any flag manipulations // in the Amiga version - checked it with disassembly). if (x != state->adjMouseX || y != state->adjMouseY) - state->viewTable[EGO_VIEW_TABLE].flags &= ~fAdjEgoXY; + state->screenObjTable[SCREENOBJECTS_EGO_ENTRY].flags &= ~fAdjEgoXY; state->adjMouseX = x; state->adjMouseY = y; @@ -727,20 +995,24 @@ void cmdAdjEgoMoveToXY(AgiGame *state, uint8 *p) { // TODO: Check where (if anywhere) the 0 arguments version is used case 0: default: - state->viewTable[0].flags |= fAdjEgoXY; + state->screenObjTable[SCREENOBJECTS_EGO_ENTRY].flags |= fAdjEgoXY; break; } } -void cmdParse(AgiGame *state, uint8 *p) { - _v[vWordNotFound] = 0; - setflag(fEnteredCli, false); - setflag(fSaidAcceptedInput, false); +void cmdParse(AgiGame *state, uint8 *parameter) { + TextMgr *text = state->_vm->_text; + uint16 stringNr = parameter[0]; + + state->vars[VM_VAR_WORD_NOT_FOUND] = 0; + state->_vm->setflag(VM_FLAG_ENTERED_CLI, false); + state->_vm->setflag(VM_FLAG_SAID_ACCEPTED_INPUT, false); - state->_vm->dictionaryWords(state->_vm->agiSprintf(state->strings[p0])); + state->_vm->_words->parseUsingDictionary(text->stringPrintf(state->strings[stringNr])); } -void cmdCall(AgiGame *state, uint8 *p) { +void cmdCall(AgiGame *state, uint8 *parameter) { + uint16 logicNr = parameter[0]; int oldCIP; int oldLognum; @@ -749,35 +1021,48 @@ void cmdCall(AgiGame *state, uint8 *p) { oldCIP = state->_curLogic->cIP; oldLognum = state->lognum; - state->_vm->runLogic(p0); + state->_vm->runLogic(logicNr); state->lognum = oldLognum; state->_curLogic = &state->logics[state->lognum]; state->_curLogic->cIP = oldCIP; } -void cmdCallF(AgiGame *state, uint8 *p) { - cmdCall(state, &_v[p0]); +void cmdCallF(AgiGame *state, uint8 *parameter) { + uint16 varNr = parameter[0]; + + cmdCall(state, &state->vars[varNr]); } -void cmdDrawPicV1(AgiGame *state, uint8 *p) { - debugC(6, kDebugLevelScripts, "=== draw pic V1 %d ===", _v[p0]); - state->_vm->_picture->decodePicture(_v[p0], true); +void cmdDrawPicV1(AgiGame *state, uint8 *parameter) { + uint16 varNr = parameter[0]; + uint16 resourceNr = state->vars[varNr]; + + debugC(6, kDebugLevelScripts, "=== draw pic V1 %d ===", resourceNr); + state->_vm->_picture->decodePicture(resourceNr, true); - state->_vm->clearPrompt(); + // TODO: check, if this was really done + state->_vm->_text->promptClear(); - // Simulate slowww computer. Many effects rely on this - state->_vm->pause(kPausePicture); + // Loading trigger + state->_vm->loadingTrigger_DrawPicture(); } -void cmdDrawPic(AgiGame *state, uint8 *p) { - debugC(6, kDebugLevelScripts, "=== draw pic %d ===", _v[p0]); - state->_vm->_sprites->eraseBoth(); - state->_vm->_picture->decodePicture(_v[p0], true); - state->_vm->_sprites->blitBoth(); - state->_vm->_sprites->commitBoth(); - state->pictureShown = 0; - debugC(6, kDebugLevelScripts, "--- end of draw pic %d ---", _v[p0]); +void cmdDrawPic(AgiGame *state, uint8 *parameter) { + SpritesMgr *spritesMgr = state->_vm->_sprites; + uint16 varNr = parameter[0]; + uint16 resourceNr = state->vars[varNr]; + + debugC(6, kDebugLevelScripts, "=== draw pic %d ===", resourceNr); + + spritesMgr->eraseSprites(); // === CHANGED === + state->_vm->_picture->decodePicture(resourceNr, true); + spritesMgr->buildAllSpriteLists(); + spritesMgr->drawAllSpriteLists(); // === CHANGED === + //state->_vm->_sprites->blitBoth(); + //state->_vm->_sprites->commitBoth(); + state->pictureShown = false; + debugC(6, kDebugLevelScripts, "--- end of draw pic %d ---", resourceNr); // WORKAROUND for a script bug which exists in SQ1, logic scripts // 20 and 110. Flag 103 is not reset correctly, which leads to erroneous @@ -790,484 +1075,640 @@ void cmdDrawPic(AgiGame *state, uint8 *p) { // With this workaround, when the player goes back to picture 20 (1 screen // above the ground), flag 103 is reset, thereby fixing this issue. Note // that this is a script bug and occurs in the original interpreter as well. - // Fixes bug #1658514: AGI: SQ1 (2.2 DOS ENG) bizzare exploding roger - if (getGameID() == GID_SQ1 && _v[p0] == 20) - setflag(103, false); + // Fixes bug #3056: AGI: SQ1 (2.2 DOS ENG) bizzare exploding roger + if (getGameID() == GID_SQ1 && resourceNr == 20) + state->_vm->setflag(103, false); - // Simulate slowww computer. Many effects rely on this - state->_vm->pause(kPausePicture); + // Loading trigger + state->_vm->loadingTrigger_DrawPicture(); } -void cmdShowPic(AgiGame *state, uint8 *p) { +void cmdShowPic(AgiGame *state, uint8 *parameter) { debugC(6, kDebugLevelScripts, "=== show pic ==="); - setflag(fOutputMode, false); - state->_vm->closeWindow(); - state->_vm->_picture->showPic(); - state->pictureShown = 1; + state->_vm->setflag(VM_FLAG_OUTPUT_MODE, false); + state->_vm->_text->closeWindow(); + state->_vm->_picture->showPicWithTransition(); + state->pictureShown = true; debugC(6, kDebugLevelScripts, "--- end of show pic ---"); } -void cmdLoadPic(AgiGame *state, uint8 *p) { - state->_vm->_sprites->eraseBoth(); - state->_vm->agiLoadResource(rPICTURE, _v[p0]); - state->_vm->_sprites->blitBoth(); - state->_vm->_sprites->commitBoth(); +void cmdLoadPic(AgiGame *state, uint8 *parameter) { + SpritesMgr *spritesMgr = state->_vm->_sprites; + uint16 varNr = parameter[0]; + uint16 resourceNr = state->vars[varNr]; + + spritesMgr->eraseSprites(); + state->_vm->agiLoadResource(RESOURCETYPE_PICTURE, resourceNr); + spritesMgr->buildAllSpriteLists(); + spritesMgr->drawAllSpriteLists(); } -void cmdLoadPicV1(AgiGame *state, uint8 *p) { - state->_vm->agiLoadResource(rPICTURE, _v[p0]); +void cmdLoadPicV1(AgiGame *state, uint8 *parameter) { + uint16 varNr = parameter[0]; + uint16 resourceNr = state->vars[varNr]; + + state->_vm->agiLoadResource(RESOURCETYPE_PICTURE, resourceNr); } -void cmdDiscardPic(AgiGame *state, uint8 *p) { +void cmdDiscardPic(AgiGame *state, uint8 *parameter) { debugC(6, kDebugLevelScripts, "--- discard pic ---"); // do nothing } -void cmdOverlayPic(AgiGame *state, uint8 *p) { +void cmdOverlayPic(AgiGame *state, uint8 *parameter) { + SpritesMgr *spritesMgr = state->_vm->_sprites; + uint16 varNr = parameter[0]; + uint16 resourceNr = state->vars[varNr]; + debugC(6, kDebugLevelScripts, "--- overlay pic ---"); - state->_vm->_sprites->eraseBoth(); - state->_vm->_picture->decodePicture(_v[p0], false); - state->_vm->_sprites->blitBoth(); - state->pictureShown = 0; - state->_vm->_sprites->commitBoth(); + spritesMgr->eraseSprites(); + state->_vm->_picture->decodePicture(resourceNr, false); + spritesMgr->buildAllSpriteLists(); + spritesMgr->drawAllSpriteLists(); + spritesMgr->showAllSpriteLists(); + state->pictureShown = false; - // Simulate slowww computer. Many effects rely on this - state->_vm->pause(kPausePicture); + // Loading trigger + state->_vm->loadingTrigger_DrawPicture(); } -void cmdShowPriScreen(AgiGame *state, uint8 *p) { - state->_vm->_debug.priority = 1; - state->_vm->_sprites->eraseBoth(); - state->_vm->_picture->showPic(); - state->_vm->_sprites->blitBoth(); +void cmdShowPriScreen(AgiGame *state, uint8 *parameter) { + GfxMgr *gfx = state->_vm->_gfx; + + gfx->debugShowMap(1); // switch to priority map state->_vm->waitKey(); - state->_vm->_debug.priority = 0; - state->_vm->_sprites->eraseBoth(); - state->_vm->_picture->showPic(); - state->_vm->_sprites->blitBoth(); + gfx->debugShowMap(0); // switch back to visual map } -void cmdAnimateObj(AgiGame *state, uint8 *p) { +void cmdAnimateObj(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + if (getVersion() < 0x2000) { - if (vt.flags & fDidntMove) + if (screenObj->flags & fDidntMove) return; } else { - if (vt.flags & fAnimated) + if (screenObj->flags & fAnimated) return; } - debugC(4, kDebugLevelScripts, "animate vt entry #%d", p0); - vt.flags = fAnimated | fUpdate | fCycling; + debugC(4, kDebugLevelScripts, "animate vt entry #%d", objectNr); + screenObj->flags = fAnimated | fUpdate | fCycling; if (getVersion() < 0x2000) { - vt.flags |= fDidntMove; + screenObj->flags |= fDidntMove; } - vt.motion = kMotionNormal; - vt.cycle = kCycleNormal; - vt.direction = 0; + screenObj->motionType = kMotionNormal; + screenObj->cycle = kCycleNormal; + screenObj->direction = 0; } -void cmdUnanimateAll(AgiGame *state, uint8 *p) { +void cmdUnanimateAll(AgiGame *state, uint8 *parameter) { int i; - for (i = 0; i < MAX_VIEWTABLE; i++) - state->viewTable[i].flags &= ~(fAnimated | fDrawn); + state->_vm->_sprites->eraseSprites(); + + for (i = 0; i < SCREENOBJECTS_MAX; i++) + state->screenObjTable[i].flags &= ~(fAnimated | fDrawn); } -void cmdDraw(AgiGame *state, uint8 *p) { - if (vt.flags & fDrawn) - return; +void cmdDraw(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; - if (vt.ySize <= 0 || vt.xSize <= 0) + if (screenObj->flags & fDrawn) return; - debugC(4, kDebugLevelScripts, "draw entry %d", vt.entry); +// if (vt.ySize <= 0 || vt.xSize <= 0) +// return; + + debugC(4, kDebugLevelScripts, "draw entry %d", screenObj->objectNr); - vt.flags |= fUpdate; + screenObj->flags |= fUpdate; if (getVersion() >= 0x3000) { - state->_vm->setLoop(&vt, vt.currentLoop); - state->_vm->setCel(&vt, vt.currentCel); + state->_vm->setLoop(screenObj, screenObj->currentLoopNr); + state->_vm->setCel(screenObj, screenObj->currentCelNr); } - state->_vm->fixPosition(p0); - vt.xPos2 = vt.xPos; - vt.yPos2 = vt.yPos; - vt.celData2 = vt.celData; - state->_vm->_sprites->eraseUpdSprites(); - vt.flags |= fDrawn; - - // WORKAROUND: This fixes a bug with AGI Fanmade game Space Trek. - // The original workaround checked if AGI version was <= 2.440, which could - // cause regressions with some AGI games. The original workaround no longer - // works for Space Trek in ScummVM, as all fanmade games are set to use - // AGI version 2.917, but it applies to all other games where AGI version is - // <= 2.440, which was not the original purpose of this workaround. It is - // assumed that this bug is caused by AGI Studio, so this applies to all - // fanmade games only. - // TODO: Investigate this further and check if any other fanmade AGI - // games are affected. If yes, then it'd be best to set this for Space - // Trek only - if (getFeatures() & GF_FANMADE) // See Sarien bug #546562 - vt.flags |= fAnimated; - - state->_vm->_sprites->blitUpdSprites(); - vt.flags &= ~fDontupdate; - - state->_vm->_sprites->commitBlock(vt.xPos, vt.yPos - vt.ySize + 1, vt.xPos + vt.xSize - 1, vt.yPos, true); - - debugC(4, kDebugLevelScripts, "vt entry #%d flags = %02x", p0, vt.flags); -} - -void cmdErase(AgiGame *state, uint8 *p) { - if (~vt.flags & fDrawn) - return; + SpritesMgr *sprites = state->_vm->_sprites; - state->_vm->_sprites->eraseUpdSprites(); + state->_vm->fixPosition(objectNr); + screenObj->xPos_prev = screenObj->xPos; + screenObj->yPos_prev = screenObj->yPos; + screenObj->xSize_prev = screenObj->xSize; + screenObj->ySize_prev = screenObj->ySize; + //screenObj->celData2 = screenObj->celData; + sprites->eraseRegularSprites(); + screenObj->flags |= fDrawn; + sprites->buildRegularSpriteList(); + sprites->drawRegularSpriteList(); + sprites->showSprite(screenObj); + screenObj->flags &= ~fDontupdate; - if (vt.flags & fUpdate) { - vt.flags &= ~fDrawn; - } else { - state->_vm->_sprites->eraseNonupdSprites(); - vt.flags &= ~fDrawn; - state->_vm->_sprites->blitNonupdSprites(); - } - state->_vm->_sprites->blitUpdSprites(); + debugC(4, kDebugLevelScripts, "vt entry #%d flags = %02x", objectNr, screenObj->flags); +} + +void cmdErase(AgiGame *state, uint8 *parameter) { + SpritesMgr *sprites = state->_vm->_sprites; + uint16 objectNr = parameter[0]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; - int x1, y1, x2, y2; + bool noUpdateFlag = false; + + if (!(screenObj->flags & fDrawn)) + return; + + sprites->eraseRegularSprites(); + if ((screenObj->flags & fUpdate) == 0) { + noUpdateFlag = true; + sprites->eraseStaticSprites(); + } - x1 = MIN((int)MIN(vt.xPos, vt.xPos2), MIN(vt.xPos + vt.celData->width, vt.xPos2 + vt.celData2->width)); - x2 = MAX((int)MAX(vt.xPos, vt.xPos2), MAX(vt.xPos + vt.celData->width, vt.xPos2 + vt.celData2->width)); - y1 = MIN((int)MIN(vt.yPos, vt.yPos2), MIN(vt.yPos - vt.celData->height, vt.yPos2 - vt.celData2->height)); - y2 = MAX((int)MAX(vt.yPos, vt.yPos2), MAX(vt.yPos - vt.celData->height, vt.yPos2 - vt.celData2->height)); + screenObj->flags &= ~fDrawn; - state->_vm->_sprites->commitBlock(x1, y1, x2, y2, true); + if (noUpdateFlag) { + sprites->buildStaticSpriteList(); + sprites->drawStaticSpriteList(); + } + sprites->buildRegularSpriteList(); + sprites->drawRegularSpriteList(); + sprites->showSprite(screenObj); } -void cmdPosition(AgiGame *state, uint8 *p) { - vt.xPos = vt.xPos2 = p1; - vt.yPos = vt.yPos2 = p2; +void cmdPosition(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 xPos = parameter[1]; + uint16 yPos = parameter[2]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; - // WORKAROUND: Part of the fix for bug #1659209 "AGI: Space Trek sprite duplication" - // with an accompanying identical workaround in position.v-command (i.e. command 0x26). - // These two workarounds together make up the whole fix. The bug was caused by - // wrongly written script data in Space Trek v1.0's scripts (At least logics 4 and 11). - // Position-command was called with horizontal values over 200 (Outside the screen!). - // Clipping the coordinates so the views stay wholly on-screen seems to fix the problems. - // It is probable (Would have to check better with disassembly to be completely sure) - // that AGI 2.440 clipped its coordinates in its position and position.v-commands - // although AGI 2.917 certainly doesn't (Checked that with disassembly) and that's why - // Space Trek may have worked better with AGI 2.440 than with some other AGI versions. - // I haven't checked but if Space Trek solely abuses the position-command we wouldn't - // strictly need the identical workaround in the position.v-command but it does make - // for a nice symmetry. - if (getFeatures() & GF_CLIPCOORDS) - state->_vm->clipViewCoordinates(&vt); + screenObj->xPos = screenObj->xPos_prev = xPos; + screenObj->yPos = screenObj->yPos_prev = yPos; } -void cmdPositionV1(AgiGame *state, uint8 *p) { - vt.xPos = p1; - vt.yPos = p2; +void cmdPositionV1(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 xPos = parameter[1]; + uint16 yPos = parameter[2]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + screenObj->xPos = xPos; + screenObj->yPos = yPos; } -void cmdPositionF(AgiGame *state, uint8 *p) { - vt.xPos = vt.xPos2 = _v[p1]; - vt.yPos = vt.yPos2 = _v[p2]; +void cmdPositionF(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 varNr1 = parameter[1]; + uint16 varNr2 = parameter[2]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; - // WORKAROUND: Part of the fix for bug #1659209 "AGI: Space Trek sprite duplication" - // with an accompanying identical workaround in position-command (i.e. command 0x25). - // See that workaround's comment for more in-depth information. - if (getFeatures() & GF_CLIPCOORDS) - state->_vm->clipViewCoordinates(&vt); + screenObj->xPos = screenObj->xPos_prev = state->vars[varNr1]; + screenObj->yPos = screenObj->yPos_prev = state->vars[varNr2]; } -void cmdPositionFV1(AgiGame *state, uint8 *p) { - vt.xPos = _v[p1]; - vt.yPos = _v[p2]; +void cmdPositionFV1(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 varNr1 = parameter[1]; + uint16 varNr2 = parameter[2]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + screenObj->xPos = state->vars[varNr1]; + screenObj->yPos = state->vars[varNr2]; } -void cmdGetPosn(AgiGame *state, uint8 *p) { - state->vars[p1] = (unsigned char)vt.xPos; - state->vars[p2] = (unsigned char)vt.yPos; +void cmdGetPosn(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 varNr1 = parameter[1]; + uint16 varNr2 = parameter[2]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + state->vars[varNr1] = (unsigned char)screenObj->xPos; + state->vars[varNr2] = (unsigned char)screenObj->yPos; } -void cmdReposition(AgiGame *state, uint8 *p) { - int dx = (int8) _v[p1], dy = (int8) _v[p2]; +void cmdReposition(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 varNr1 = parameter[1]; + uint16 varNr2 = parameter[2]; + int16 dx = (int8) state->vars[varNr1]; + int16 dy = (int8) state->vars[varNr2]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; debugC(4, kDebugLevelScripts, "dx=%d, dy=%d", dx, dy); - vt.flags |= fUpdatePos; + screenObj->flags |= fUpdatePos; - if (dx < 0 && vt.xPos < -dx) - vt.xPos = 0; + if (dx < 0 && screenObj->xPos < -dx) + screenObj->xPos = 0; else - vt.xPos += dx; + screenObj->xPos += dx; - if (dy < 0 && vt.yPos < -dy) - vt.yPos = 0; + if (dy < 0 && screenObj->yPos < -dy) + screenObj->yPos = 0; else - vt.yPos += dy; + screenObj->yPos += dy; - state->_vm->fixPosition(p0); + state->_vm->fixPosition(objectNr); } -void cmdRepositionV1(AgiGame *state, uint8 *p) { - vt.xPos2 = vt.xPos; - vt.yPos2 = vt.yPos; - vt.flags |= fUpdatePos; +void cmdRepositionV1(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 xPosPlus = parameter[1]; + uint16 yPosPlus = parameter[2]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; - vt.xPos = (vt.xPos + p1) & 0xff; - vt.yPos = (vt.yPos + p2) & 0xff; + screenObj->xPos_prev = screenObj->xPos; + screenObj->yPos_prev = screenObj->yPos; + screenObj->flags |= fUpdatePos; + + screenObj->xPos = (screenObj->xPos + xPosPlus) & 0xff; + screenObj->yPos = (screenObj->yPos + yPosPlus) & 0xff; } -void cmdRepositionTo(AgiGame *state, uint8 *p) { - vt.xPos = p1; - vt.yPos = p2; - vt.flags |= fUpdatePos; - state->_vm->fixPosition(p0); +void cmdRepositionTo(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 xPos = parameter[1]; + uint16 yPos = parameter[2]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + screenObj->xPos = xPos; + screenObj->yPos = yPos; + screenObj->flags |= fUpdatePos; + state->_vm->fixPosition(objectNr); } -void cmdRepositionToF(AgiGame *state, uint8 *p) { - vt.xPos = _v[p1]; - vt.yPos = _v[p2]; - vt.flags |= fUpdatePos; - state->_vm->fixPosition(p0); +void cmdRepositionToF(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 varNr1 = parameter[1]; + uint16 varNr2 = parameter[2]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + screenObj->xPos = state->vars[varNr1]; + screenObj->yPos = state->vars[varNr2]; + screenObj->flags |= fUpdatePos; + state->_vm->fixPosition(objectNr); } -void cmdAddToPic(AgiGame *state, uint8 *p) { - state->_vm->_sprites->addToPic(p0, p1, p2, p3, p4, p5, p6); +void cmdAddToPic(AgiGame *state, uint8 *parameter) { + uint16 viewNr = parameter[0]; + uint16 loopNr = parameter[1]; + uint16 celNr = parameter[2]; + uint16 xPos = parameter[3]; + uint16 yPos = parameter[4]; + uint16 priority = parameter[5]; + uint16 border = parameter[6]; + + state->_vm->_sprites->addToPic(viewNr, loopNr, celNr, xPos, yPos, priority, border); } -void cmdAddToPicV1(AgiGame *state, uint8 *p) { - state->_vm->_sprites->addToPic(p0, p1, p2, p3, p4, p5, -1); +void cmdAddToPicV1(AgiGame *state, uint8 *parameter) { + uint16 viewNr = parameter[0]; + uint16 loopNr = parameter[1]; + uint16 celNr = parameter[2]; + uint16 xPos = parameter[3]; + uint16 yPos = parameter[4]; + uint16 priority = parameter[5]; + + state->_vm->_sprites->addToPic(viewNr, loopNr, celNr, xPos, yPos, priority, -1); } -void cmdAddToPicF(AgiGame *state, uint8 *p) { - state->_vm->_sprites->addToPic(_v[p0], _v[p1], _v[p2], _v[p3], _v[p4], _v[p5], _v[p6]); +void cmdAddToPicF(AgiGame *state, uint8 *parameter) { + uint16 viewNr = state->vars[parameter[0]]; + uint16 loopNr = state->vars[parameter[1]]; + uint16 celNr = state->vars[parameter[2]]; + uint16 xPos = state->vars[parameter[3]]; + uint16 yPos = state->vars[parameter[4]]; + uint16 priority = state->vars[parameter[5]]; + uint16 border = state->vars[parameter[6]]; + + state->_vm->_sprites->addToPic(viewNr, loopNr, celNr, xPos, yPos, priority, border); } -void cmdForceUpdate(AgiGame *state, uint8 *p) { - state->_vm->_sprites->eraseBoth(); - state->_vm->_sprites->blitBoth(); - state->_vm->_sprites->commitBoth(); +void cmdForceUpdate(AgiGame *state, uint8 *parameter) { + SpritesMgr *spritesMgr = state->_vm->_sprites; + + spritesMgr->eraseSprites(); + spritesMgr->buildAllSpriteLists(); + spritesMgr->drawAllSpriteLists(); + spritesMgr->showAllSpriteLists(); } -void cmdReverseLoop(AgiGame *state, uint8 *p) { - debugC(4, kDebugLevelScripts, "o%d, f%d", p0, p1); - vt.cycle = kCycleRevLoop; - vt.flags |= (fDontupdate | fUpdate | fCycling); - vt.parm1 = p1; - setflag(p1, false); +void cmdReverseLoop(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 loopFlag = parameter[1]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + debugC(4, kDebugLevelScripts, "o%d, f%d", objectNr, loopFlag); + screenObj->cycle = kCycleRevLoop; + screenObj->flags |= (fDontupdate | fUpdate | fCycling); + screenObj->loop_flag = loopFlag; + state->_vm->setflag(screenObj->loop_flag, false); } -void cmdReverseLoopV1(AgiGame *state, uint8 *p) { - debugC(4, kDebugLevelScripts, "o%d, f%d", p0, p1); - vt.cycle = kCycleRevLoop; - state->_vm->setCel(&vt, 0); - vt.flags |= (fDontupdate | fUpdate | fCycling); - vt.parm1 = p1; - vt.parm3 = 0; +void cmdReverseLoopV1(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 loopFlag = parameter[1]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + debugC(4, kDebugLevelScripts, "o%d, f%d", objectNr, loopFlag); + screenObj->cycle = kCycleRevLoop; + state->_vm->setCel(screenObj, 0); + screenObj->flags |= (fDontupdate | fUpdate | fCycling); + screenObj->loop_flag = loopFlag; + //screenObj->parm3 = 0; } -void cmdEndOfLoop(AgiGame *state, uint8 *p) { - debugC(4, kDebugLevelScripts, "o%d, f%d", p0, p1); - vt.cycle = kCycleEndOfLoop; - vt.flags |= (fDontupdate | fUpdate | fCycling); - vt.parm1 = p1; - setflag(p1, false); +void cmdEndOfLoop(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 loopFlag = parameter[1]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + debugC(4, kDebugLevelScripts, "o%d, f%d", objectNr, loopFlag); + screenObj->cycle = kCycleEndOfLoop; + screenObj->flags |= (fDontupdate | fUpdate | fCycling); + screenObj->loop_flag = loopFlag; + state->_vm->setflag(screenObj->loop_flag, false); } -void cmdEndOfLoopV1(AgiGame *state, uint8 *p) { - debugC(4, kDebugLevelScripts, "o%d, f%d", p0, p1); - vt.cycle = kCycleEndOfLoop; - state->_vm->setCel(&vt, 0); - vt.flags |= (fDontupdate | fUpdate | fCycling); - vt.parm1 = p1; - vt.parm3 = 0; +void cmdEndOfLoopV1(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 loopFlag = parameter[1]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + debugC(4, kDebugLevelScripts, "o%d, f%d", objectNr, loopFlag); + screenObj->cycle = kCycleEndOfLoop; + state->_vm->setCel(screenObj, 0); + screenObj->flags |= (fDontupdate | fUpdate | fCycling); + screenObj->loop_flag = loopFlag; + //screenObj->parm3 = 0; } -void cmdBlock(AgiGame *state, uint8 *p) { - debugC(4, kDebugLevelScripts, "x1=%d, y1=%d, x2=%d, y2=%d", p0, p1, p2, p3); +void cmdBlock(AgiGame *state, uint8 *parameter) { + uint16 x1 = parameter[0]; + uint16 y1 = parameter[1]; + uint16 x2 = parameter[2]; + uint16 y2 = parameter[3]; + + debugC(4, kDebugLevelScripts, "x1=%d, y1=%d, x2=%d, y2=%d", x1, y1, x2, y2); state->block.active = true; - state->block.x1 = p0; - state->block.y1 = p1; - state->block.x2 = p2; - state->block.y2 = p3; + state->block.x1 = x1; + state->block.y1 = y1; + state->block.x2 = x2; + state->block.y2 = y2; } -void cmdUnblock(AgiGame *state, uint8 *p) { +void cmdUnblock(AgiGame *state, uint8 *parameter) { state->block.active = false; } -void cmdNormalMotion(AgiGame *state, uint8 *p) { - vt.motion = kMotionNormal; +void cmdNormalMotion(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + screenObj->motionType = kMotionNormal; } -void cmdStopMotion(AgiGame *state, uint8 *p) { - vt.direction = 0; - vt.motion = kMotionNormal; - if (p0 == 0) { // ego only - _v[vEgoDir] = 0; +void cmdStopMotion(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + screenObj->direction = 0; + screenObj->motionType = kMotionNormal; + if (objectNr == 0) { // ego only + state->vars[VM_VAR_EGO_DIRECTION] = 0; state->playerControl = false; } } -void cmdStopMotionV1(AgiGame *state, uint8 *p) { - vt.flags &= ~fAnimated; +void cmdStopMotionV1(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + screenObj->flags &= ~fAnimated; } -void cmdStartMotion(AgiGame *state, uint8 *p) { - vt.motion = kMotionNormal; - if (p0 == 0) { // ego only - _v[vEgoDir] = 0; +void cmdStartMotion(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + screenObj->motionType = kMotionNormal; + if (objectNr == 0) { // ego only + state->vars[VM_VAR_EGO_DIRECTION] = 0; state->playerControl = true; } } -void cmdStartMotionV1(AgiGame *state, uint8 *p) { - vt.flags |= fAnimated; +void cmdStartMotionV1(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + screenObj->flags |= fAnimated; } -void cmdPlayerControl(AgiGame *state, uint8 *p) { +void cmdPlayerControl(AgiGame *state, uint8 *parameter) { + ScreenObjEntry *screenObjEgo = &state->screenObjTable[SCREENOBJECTS_EGO_ENTRY]; + state->playerControl = true; - state->viewTable[0].motion = kMotionNormal; + + if (screenObjEgo->motionType != kMotionEgo) + screenObjEgo->motionType = kMotionNormal; } -void cmdProgramControl(AgiGame *state, uint8 *p) { +void cmdProgramControl(AgiGame *state, uint8 *parameter) { state->playerControl = false; } -void cmdFollowEgo(AgiGame *state, uint8 *p) { - vt.motion = kMotionFollowEgo; - vt.parm1 = p1 > vt.stepSize ? p1 : vt.stepSize; - vt.parm2 = p2; - vt.parm3 = 0xff; +void cmdFollowEgo(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 followStepSize = parameter[1]; + uint16 followFlag = parameter[2]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + screenObj->motionType = kMotionFollowEgo; + if (followStepSize <= screenObj->stepSize) { + screenObj->follow_stepSize = screenObj->stepSize; + } else { + screenObj->follow_stepSize = followStepSize; + } + screenObj->follow_flag = followFlag; + screenObj->follow_count = 255; if (getVersion() < 0x2000) { - _v[p2] = 0; - vt.flags |= fUpdate | fAnimated; + state->vars[screenObj->follow_flag] = 0; + screenObj->flags |= fUpdate | fAnimated; } else { - setflag(p2, false); - vt.flags |= fUpdate; + state->_vm->setflag(screenObj->follow_flag, false); + screenObj->flags |= fUpdate; } } -void cmdMoveObj(AgiGame *state, uint8 *p) { +void cmdMoveObj(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 moveX = parameter[1]; + uint16 moveY = parameter[2]; + uint16 stepSize = parameter[3]; + uint16 moveFlag = parameter[4]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; // _D (_D_WARN "o=%d, x=%d, y=%d, s=%d, f=%d", p0, p1, p2, p3, p4); - vt.motion = kMotionMoveObj; - vt.parm1 = p1; - vt.parm2 = p2; - vt.parm3 = vt.stepSize; - vt.parm4 = p4; + screenObj->motionType = kMotionMoveObj; + screenObj->move_x = moveX; + screenObj->move_y = moveY; + screenObj->move_stepSize = screenObj->stepSize; + screenObj->move_flag = moveFlag; - if (p3 != 0) - vt.stepSize = p3; + if (stepSize != 0) + screenObj->stepSize = stepSize; if (getVersion() < 0x2000) { - _v[p4] = 0; - vt.flags |= fUpdate | fAnimated; + state->vars[moveFlag] = 0; + screenObj->flags |= fUpdate | fAnimated; } else { - setflag(p4, false); - vt.flags |= fUpdate; + state->_vm->setflag(screenObj->move_flag, false); + screenObj->flags |= fUpdate; } - if (p0 == 0) + if (objectNr == 0) state->playerControl = false; // AGI 2.272 (ddp, xmas) doesn't call move_obj! if (getVersion() > 0x2272) - state->_vm->moveObj(&vt); + state->_vm->moveObj(screenObj); } -void cmdMoveObjF(AgiGame *state, uint8 *p) { - vt.motion = kMotionMoveObj; - vt.parm1 = _v[p1]; - vt.parm2 = _v[p2]; - vt.parm3 = vt.stepSize; - vt.parm4 = p4; +void cmdMoveObjF(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + uint16 moveX = state->vars[parameter[1]]; + uint16 moveY = state->vars[parameter[2]]; + uint16 stepSize = state->vars[parameter[3]]; + uint16 moveFlag = parameter[4]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + screenObj->motionType = kMotionMoveObj; + screenObj->move_x = moveX; + screenObj->move_y = moveY; + screenObj->move_stepSize = screenObj->stepSize; + screenObj->move_flag = moveFlag; - if (_v[p3] != 0) - vt.stepSize = _v[p3]; + if (stepSize != 0) + screenObj->stepSize = stepSize; - setflag(p4, false); - vt.flags |= fUpdate; + state->_vm->setflag(screenObj->move_flag, false); + screenObj->flags |= fUpdate; - if (p0 == 0) + if (objectNr == 0) state->playerControl = false; // AGI 2.272 (ddp, xmas) doesn't call move_obj! if (getVersion() > 0x2272) - state->_vm->moveObj(&vt); + state->_vm->moveObj(screenObj); } -void cmdWander(AgiGame *state, uint8 *p) { - if (p0 == 0) +void cmdWander(AgiGame *state, uint8 *parameter) { + uint16 objectNr = parameter[0]; + ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + + if (objectNr == 0) state->playerControl = false; - vt.motion = kMotionWander; + screenObj->motionType = kMotionWander; if (getVersion() < 0x2000) { - vt.flags |= fUpdate | fAnimated; + screenObj->flags |= fUpdate | fAnimated; } else { - vt.flags |= fUpdate; + screenObj->flags |= fUpdate; } } -void cmdSetGameID(AgiGame *state, uint8 *p) { - if (state->_curLogic->texts && (p0 - 1) <= state->_curLogic->numTexts) - Common::strlcpy(state->id, state->_curLogic->texts[p0 - 1], 8); +void cmdSetGameID(AgiGame *state, uint8 *parameter) { + uint16 textNr = parameter[0]; + + if (state->_curLogic->texts && (textNr - 1) <= state->_curLogic->numTexts) + Common::strlcpy(state->id, state->_curLogic->texts[textNr - 1], 8); else state->id[0] = 0; debug(0, "Game ID: \"%s\"", state->id); } -void cmdPause(AgiGame *state, uint8 *p) { - int tmp = state->clockEnabled; - const char *b[] = { "Continue", NULL }; - const char *b_ru[] = { "\x8f\xe0\xae\xa4\xae\xab\xa6\xa8\xe2\xec", NULL }; +void cmdPause(AgiGame *state, uint8 *parameter) { + AgiEngine *vm = state->_vm; + int originalClockState = state->clockEnabled; + bool skipPause = false; state->clockEnabled = false; - switch (getLanguage()) { - case Common::RU_RUS: - state->_vm->selectionBox(" \x88\xa3\xe0\xa0 \xae\xe1\xe2\xa0\xad\xae\xa2\xab\xa5\xad\xa0. \n\n\n", b_ru); - break; - default: - state->_vm->selectionBox(" Game is paused. \n\n\n", b); - break; + // We check in here, if a special key was specified to trigger menus. + // If that's the case, normally triggering the menu should be handled inside handleController() + // For the rare cases, where this approach doesn't work because the trigger is not mapped to a controller, + // we trigger the menu in here. + // + // for further study read the comments for handleController() + // + // This is needed for at least Mixed Up Mother Goose for Apple IIgs. + if (state->specialMenuTriggerKey) { + if (vm->_menu->isAvailable()) { + // Pulldown-menu is actually available (was submitted) + skipPause = true; + + // Check, if special trigger key is currently NOT mapped. + if (vm->getSpecialMenuControllerSlot() < 0) { + // menu trigger is not mapped, trigger menu + vm->_menu->delayedExecute(); + } else { + // menu trigger is mapped, do not replace "pause" + skipPause = false; + } + } else { + warning("menu is not available, doing regular pause game instead"); + } + } + + if (!skipPause) { + // Show pause message box + state->_vm->_systemUI->pauseDialog(); } - state->clockEnabled = tmp; + + state->clockEnabled = originalClockState; } -void cmdSetMenu(AgiGame *state, uint8 *p) { - debugC(4, kDebugLevelScripts, "text %02x of %02x", p0, state->_curLogic->numTexts); +void cmdSetMenu(AgiGame *state, uint8 *parameter) { + uint16 textNr = parameter[0]; + + debugC(4, kDebugLevelScripts, "text %02x of %02x", textNr, state->_curLogic->numTexts); - if (state->_curLogic->texts != NULL && p0 <= state->_curLogic->numTexts) - state->_vm->_menu->add(state->_curLogic->texts[p0 - 1]); + if (state->_curLogic->texts != NULL && (textNr - 1) <= state->_curLogic->numTexts) { + const char *menuText = state->_curLogic->texts[textNr - 1]; + + state->_vm->_menu->addMenu(menuText); + } } -void cmdSetMenuItem(AgiGame *state, uint8 *p) { - debugC(4, kDebugLevelScripts, "text %02x of %02x", p0, state->_curLogic->numTexts); +void cmdSetMenuItem(AgiGame *state, uint8 *parameter) { + uint16 textNr = parameter[0] - 1; + uint16 controllerSlot = parameter[1]; - if (state->_curLogic->texts != NULL && p0 <= state->_curLogic->numTexts) - state->_vm->_menu->addItem(state->_curLogic->texts[p0 - 1], p1); + debugC(4, kDebugLevelScripts, "text %02x of %02x", textNr, state->_curLogic->numTexts); + + if (state->_curLogic->texts != NULL && textNr <= state->_curLogic->numTexts) { + const char *menuItemText = state->_curLogic->texts[textNr]; + + state->_vm->_menu->addMenuItem(menuItemText, controllerSlot); + } } -void cmdVersion(AgiGame *state, uint8 *p) { +void cmdVersion(AgiGame *state, uint8 *parameter) { char ver2Msg[] = "\n" " \n\n" - " Emulating Sierra AGI v%x.%03x\n"; + " ScummVM Sierra AGI v%x.%03x"; char ver3Msg[] = "\n" " \n\n" - " Emulating AGI v%x.002.%03x\n"; - // no Sierra as it wraps textbox + "ScummVM Sierra AGI v%x.002.%03x"; Common::String verMsg = TITLE " v%s"; @@ -1278,98 +1719,113 @@ void cmdVersion(AgiGame *state, uint8 *p) { verMsg += (maj == 2 ? ver2Msg : ver3Msg); verMsg = Common::String::format(verMsg.c_str(), gScummVMVersion, maj, min); - state->_vm->messageBox(verMsg.c_str()); + state->_vm->_text->messageBox(verMsg.c_str()); } -void cmdConfigureScreen(AgiGame *state, uint8 *p) { - state->lineMinPrint = p0; - state->lineUserInput = p1; - state->lineStatus = p2; +void cmdConfigureScreen(AgiGame *state, uint8 *parameter) { + TextMgr *textMgr = state->_vm->_text; + uint16 lineMinPrint = parameter[0]; + uint16 promptRow = parameter[1]; + uint16 statusRow = parameter[2]; + + state->_vm->_text->configureScreen(lineMinPrint); + + textMgr->statusRow_Set(statusRow); + textMgr->promptRow_Set(promptRow); } -void cmdTextScreen(AgiGame *state, uint8 *p) { - debugC(4, kDebugLevelScripts, "switching to text mode"); - state->gfxMode = false; +void cmdTextScreen(AgiGame *state, uint8 *parameter) { + GfxMgr *gfxMgr = state->_vm->_gfx; + TextMgr *textMgr = state->_vm->_text; - // Simulates the "bright background bit" of the PC video - // controller. - if (state->colorBg) - state->colorBg |= 0x08; + debugC(4, kDebugLevelScripts, "switching to text mode"); - state->_vm->_gfx->clearScreen(state->colorBg); + state->gfxMode = false; + gfxMgr->setPalette(false); // set text-mode palette + textMgr->charAttrib_Set(textMgr->_textAttrib.foreground, textMgr->_textAttrib.background); + gfxMgr->clearDisplay(0); + textMgr->clearLines(0, 24, textMgr->_textAttrib.combinedBackground); } -void cmdGraphics(AgiGame *state, uint8 *p) { +void cmdGraphics(AgiGame *state, uint8 *parameter) { debugC(4, kDebugLevelScripts, "switching to graphics mode"); - if (!state->gfxMode) { - state->gfxMode = true; - state->_vm->_gfx->clearScreen(0); - state->_vm->_picture->showPic(); - state->_vm->writeStatus(); - state->_vm->writePrompt(); - } + state->_vm->redrawScreen(); } -void cmdSetTextAttribute(AgiGame *state, uint8 *p) { - state->colorFg = p0; - state->colorBg = p1; - - if (state->gfxMode) { - if (state->colorBg != 0) { - state->colorFg = 0; - state->colorBg = 15; - } - } +void cmdSetTextAttribute(AgiGame *state, uint8 *parameter) { + int16 foreground = parameter[0]; + int16 background = parameter[1]; + state->_vm->_text->charAttrib_Set(foreground, background); } -void cmdStatus(AgiGame *state, uint8 *p) { - state->_vm->inventory(); +void cmdStatus(AgiGame *state, uint8 *parameter) { + TextMgr *textMgr = state->_vm->_text; + InventoryMgr *inventoryMgr = state->_vm->_inventory; + + textMgr->inputEditOn(); + textMgr->charAttrib_Push(); + textMgr->charAttrib_Set(0, 15); + + cmdTextScreen(state, parameter); + + inventoryMgr->show(); + + //invent_state = 0; + textMgr->charAttrib_Pop(); + state->_vm->redrawScreen(); } -void cmdQuit(AgiGame *state, uint8 *p) { - const char *buttons[] = { "Quit", "Continue", NULL }; +void cmdQuit(AgiGame *state, uint8 *parameter) { + uint16 withoutPrompt = parameter[0]; +// const char *buttons[] = { "Quit", "Continue", NULL }; state->_vm->_sound->stopSound(); - if (p0) { + if (withoutPrompt) { state->_vm->quitGame(); } else { - if (state->_vm->selectionBox(" Quit the game, or continue? \n\n\n", buttons) == 0) { + if (state->_vm->_systemUI->quitDialog()) { state->_vm->quitGame(); } } } -void cmdQuitV1(AgiGame *state, uint8 *p) { +void cmdQuitV1(AgiGame *state, uint8 *parameter) { state->_vm->_sound->stopSound(); state->_vm->quitGame(); } -void cmdRestartGame(AgiGame *state, uint8 *p) { - const char *buttons[] = { "Restart", "Continue", NULL }; - int sel; +void cmdRestartGame(AgiGame *state, uint8 *parameter) { + bool doRestart = false; state->_vm->_sound->stopSound(); - sel = getflag(fAutoRestart) ? 0 : - state->_vm->selectionBox(" Restart game, or continue? \n\n\n", buttons); - if (sel == 0) { + if (state->_vm->getflag(VM_FLAG_AUTO_RESTART)) { + doRestart = true; + } else { + doRestart = state->_vm->_systemUI->restartDialog(); + } + + if (doRestart) { state->_vm->_restartGame = true; - setflag(fRestartGame, true); - state->_vm->_menu->enableAll(); + state->_vm->setflag(VM_FLAG_RESTART_GAME, true); + state->_vm->_menu->itemEnableAll(); } } -void cmdDistance(AgiGame *state, uint8 *p) { +void cmdDistance(AgiGame *state, uint8 *parameter) { + uint16 objectNr1 = parameter[0]; + uint16 objectNr2 = parameter[1]; + uint16 destVarNr = parameter[2]; int16 x1, y1, x2, y2, d; - VtEntry *v0 = &state->viewTable[p0]; - VtEntry *v1 = &state->viewTable[p1]; - - if (v0->flags & fDrawn && v1->flags & fDrawn) { - x1 = v0->xPos + v0->xSize / 2; - y1 = v0->yPos; - x2 = v1->xPos + v1->xSize / 2; - y2 = v1->yPos; + ScreenObjEntry *screenObj1 = &state->screenObjTable[objectNr1]; + ScreenObjEntry *screenObj2 = &state->screenObjTable[objectNr2]; + + if (screenObj1->flags & fDrawn && screenObj2->flags & fDrawn) { + x1 = screenObj1->xPos + screenObj1->xSize / 2; + y1 = screenObj1->yPos; + x2 = screenObj2->xPos + screenObj2->xSize / 2; + y2 = screenObj2->yPos; d = ABS(x1 - x2) + ABS(y1 - y2); if (d > 0xfe) d = 0xfe; @@ -1377,7 +1833,8 @@ void cmdDistance(AgiGame *state, uint8 *p) { d = 0xff; } - // WORKAROUND: Fixes King's Quest IV's script bug #1660424 (KQ4: Zombie bug). + // WORKAROUND: Fixes King's Quest IV's script bug #3067 (KQ4: Zombie bug). + // This bug also happens in the original interpreter. // In the graveyard (Rooms 16 and 18) at night if you had the Obsidian Scarab (Item 4) // and you were very close to a spot where a zombie was going to rise up from the // ground you could reproduce the bug. Just standing there and letting the zombie @@ -1386,7 +1843,7 @@ void cmdDistance(AgiGame *state, uint8 *p) { // wouldn't chase Rosella around anymore. If it had worked correctly the zombie // wouldn't have come up at all or it would have come up and gone back down // immediately. The latter approach is the one implemented here. - if (getGameID() == GID_KQ4 && (_v[vCurRoom] == 16 || _v[vCurRoom] == 18) && p2 >= 221 && p2 <= 223) { + if (getGameID() == GID_KQ4 && (state->vars[VM_VAR_CURRENT_ROOM] == 16 || state->vars[VM_VAR_CURRENT_ROOM] == 18) && destVarNr >= 221 && destVarNr <= 223) { // Rooms 16 and 18 are graveyards where three zombies come up at night. They use logics 16 and 18. // Variables 221-223 are used to save the distance between each zombie and Rosella. // Variables 155, 156 and 162 are used to save the state of each zombie in room 16. @@ -1399,228 +1856,271 @@ void cmdDistance(AgiGame *state, uint8 *p) { // a zombie or the zombie getting turned away by the scarab) we make it appear the // zombie is far away from Rosella if the zombie is not already up and chasing her. enum zombieStates {ZOMBIE_SET_TO_RISE_UP, ZOMBIE_RISING_UP, ZOMBIE_CHASING_EGO}; - uint8 zombieStateVarNumList[] = {155, 156, (uint8)((_v[vCurRoom] == 16) ? 162 : 158)}; - uint8 zombieNum = p2 - 221; // Zombie's number (In range 0-2) + uint8 zombieStateVarNumList[] = {155, 156, (uint8)((state->vars[VM_VAR_CURRENT_ROOM] == 16) ? 162 : 158)}; + uint8 zombieNum = destVarNr - 221; // Zombie's number (In range 0-2) uint8 zombieStateVarNum = zombieStateVarNumList[zombieNum]; // Number of the variable containing zombie's state - uint8 zombieState = _v[zombieStateVarNum]; // Zombie's state + uint8 zombieState = state->vars[zombieStateVarNum]; // Zombie's state // If zombie is not chasing Rosella then set its distance from Rosella to the maximum if (zombieState != ZOMBIE_CHASING_EGO) d = 0xff; } - _v[p2] = (unsigned char)d; + state->vars[destVarNr] = (unsigned char)d; } -void cmdAcceptInput(AgiGame *state, uint8 *p) { +void cmdAcceptInput(AgiGame *state, uint8 *parameter) { + TextMgr *textMgr = state->_vm->_text; + debugC(4, kDebugLevelScripts | kDebugLevelInput, "input normal"); - state->_vm->newInputMode(INPUT_NORMAL); - state->inputEnabled = true; - state->_vm->writePrompt(); + state->_vm->newInputMode(INPUTMODE_NORMAL); + + textMgr->promptEnable(); + textMgr->promptRedraw(); } -void cmdPreventInput(AgiGame *state, uint8 *p) { +void cmdPreventInput(AgiGame *state, uint8 *parameter) { + TextMgr *textMgr = state->_vm->_text; + debugC(4, kDebugLevelScripts | kDebugLevelInput, "no input"); - state->_vm->newInputMode(INPUT_NONE); - state->inputEnabled = false; + state->_vm->newInputMode(INPUTMODE_NONE); + + textMgr->promptDisable(); + + textMgr->inputEditOn(); + textMgr->clearLine(textMgr->promptRow_Get(), 0); +} + +void cmdCancelLine(AgiGame *state, uint8 *parameter) { + state->_vm->_text->promptCancelLine(); +} + +void cmdEchoLine(AgiGame *state, uint8 *parameter) { + TextMgr *textMgr = state->_vm->_text; - // Always clear with black background. Fixes bug #3080041. - state->_vm->clearPrompt(true); + if (textMgr->promptIsEnabled()) { + textMgr->promptEchoLine(); + } } -void cmdGetString(AgiGame *state, uint8 *p) { - int tex, row, col; +void cmdGetString(AgiGame *state, uint8 *parameter) { + TextMgr *textMgr = state->_vm->_text; + int16 stringDestNr = parameter[0]; + int16 leadInTextNr = parameter[1] - 1; + int16 stringRow = parameter[2]; + int16 stringColumn = parameter[3]; + int16 stringMaxLen = parameter[4]; + bool previousEditState = false; + const char *leadInTextPtr = nullptr; + + if (stringMaxLen > TEXT_STRING_MAX_SIZE) + stringMaxLen = TEXT_STRING_MAX_SIZE; - debugC(4, kDebugLevelScripts, "%d %d %d %d %d", p0, p1, p2, p3, p4); + debugC(4, kDebugLevelScripts, "%d %d %d %d %d", stringDestNr, leadInTextNr, stringRow, stringColumn, stringMaxLen); - tex = p1 - 1; - row = p2; - col = p3; + previousEditState = textMgr->inputGetEditStatus(); + + textMgr->charPos_Push(); + textMgr->inputEditOn(); // Workaround for SQLC bug. // See Sarien bug #792125 for details - if (row > 24) - row = 24; - if (col > 39) - col = 39; +// if (promptRow > 24) +// promptRow = 24; +// if (promptColumn > 39) +// promptColumn = 39; - state->_vm->newInputMode(INPUT_GETSTRING); + if (stringRow < 25) { + textMgr->charPos_Set(stringRow, stringColumn); + } - if (state->_curLogic->texts != NULL && state->_curLogic->numTexts >= tex) { - int len = strlen(state->_curLogic->texts[tex]); + if (state->_curLogic->texts && state->_curLogic->numTexts >= leadInTextNr) { + leadInTextPtr = state->_curLogic->texts[leadInTextNr]; - state->_vm->printText(state->_curLogic->texts[tex], 0, col, row, len, state->colorFg, state->colorBg); - state->_vm->getString(col + len - 1, row, p4, p0); + leadInTextPtr = textMgr->stringPrintf(leadInTextPtr); + leadInTextPtr = textMgr->stringWordWrap(leadInTextPtr, 40); // ?? not absolutely sure - // SGEO: display input char - state->_vm->_gfx->printCharacter((col + len), row, state->cursorChar, state->colorFg, state->colorBg); + textMgr->displayText(leadInTextPtr); } - do { - state->_vm->mainCycle(); - } while (state->inputMode == INPUT_GETSTRING && !(state->_vm->shouldQuit() || state->_vm->_restartGame)); + state->_vm->cycleInnerLoopActive(CYCLE_INNERLOOP_GETSTRING); + + textMgr->stringSet(""); + textMgr->stringEdit(stringMaxLen); + + // copy string to destination + // TODO: not sure if set all the time or only when ENTER is pressed + strcpy(&state->_vm->_game.strings[stringDestNr][0], (char *)textMgr->_inputString); + + textMgr->charPos_Pop(); + + if (!previousEditState) { + textMgr->inputEditOff(); + } } -void cmdGetNum(AgiGame *state, uint8 *p) { - debugC(4, kDebugLevelScripts, "%d %d", p0, p1); +void cmdGetNum(AgiGame *state, uint8 *parameter) { + TextMgr *textMgr = state->_vm->_text; + int16 leadInTextNr = parameter[0] - 1; + int16 numberDestVarNr = parameter[1]; + const char *leadInTextPtr = nullptr; + + debugC(4, kDebugLevelScripts, "%d %d", leadInTextNr, numberDestVarNr); - state->_vm->newInputMode(INPUT_GETSTRING); + textMgr->inputEditOn(); + textMgr->charPos_Set(textMgr->promptRow_Get(), 0); - if (state->_curLogic->texts != NULL && state->_curLogic->numTexts >= (p0 - 1)) { - int len = strlen(state->_curLogic->texts[p0 - 1]); + if (state->_curLogic->texts && state->_curLogic->numTexts >= leadInTextNr) { + leadInTextPtr = state->_curLogic->texts[leadInTextNr]; - state->_vm->printText(state->_curLogic->texts[p0 - 1], 0, 0, 22, len, state->colorFg, state->colorBg); - state->_vm->getString(len - 1, 22, 3, MAX_STRINGS); + leadInTextPtr = textMgr->stringPrintf(leadInTextPtr); + leadInTextPtr = textMgr->stringWordWrap(leadInTextPtr, 40); // ?? not absolutely sure - // CM: display input char - state->_vm->_gfx->printCharacter((p3 + len), 22, state->cursorChar, state->colorFg, state->colorBg); + textMgr->displayText(leadInTextPtr); } - do { - state->_vm->mainCycle(); - } while (state->inputMode == INPUT_GETSTRING && !(state->_vm->shouldQuit() || state->_vm->_restartGame)); + textMgr->inputEditOff(); + + state->_vm->cycleInnerLoopActive(CYCLE_INNERLOOP_GETNUMBER); - _v[p1] = atoi(state->strings[MAX_STRINGS]); + textMgr->stringSet(""); + textMgr->stringEdit(3); - debugC(4, kDebugLevelScripts, "[%s] -> %d", state->strings[MAX_STRINGS], _v[p1]); + textMgr->promptRedraw(); - state->_vm->clearLines(22, 22, state->colorBg); - state->_vm->flushLines(22, 22); + state->vars[numberDestVarNr] = atoi((char *)textMgr->_inputString); + + debugC(4, kDebugLevelScripts, "[%s] -> %d", state->strings[MAX_STRINGS], state->vars[numberDestVarNr]); } -void cmdSetCursorChar(AgiGame *state, uint8 *p) { - if (state->_curLogic->texts != NULL && (p0 - 1) <= state->_curLogic->numTexts) { - state->cursorChar = *state->_curLogic->texts[p0 - 1]; +void cmdSetCursorChar(AgiGame *state, uint8 *parameter) { + TextMgr *textMgr = state->_vm->_text; + uint16 textNr = parameter[0] - 1; + + if (state->_curLogic->texts != NULL && textNr <= state->_curLogic->numTexts) { + textMgr->inputSetCursorChar(*state->_curLogic->texts[textNr]); } else { // default - state->cursorChar = '_'; + textMgr->inputSetCursorChar('_'); } } -void cmdSetKey(AgiGame *state, uint8 *p) { - int key = 256 * p1 + p0; - int slot = -1; +void cmdSetKey(AgiGame *state, uint8 *parameter) { + uint16 key = parameter[0] + (parameter[1] << 8); + uint16 controllerSlot = parameter[2]; + int16 keyMappingSlot = -1; - for (int i = 0; i < MAX_CONTROLLERS; i++) { - if (slot == -1 && !state->controllers[i].keycode) - slot = i; + for (int i = 0; i < MAX_CONTROLLER_KEYMAPPINGS; i++) { + if (keyMappingSlot == -1 && !state->controllerKeyMapping[i].keycode) + keyMappingSlot = i; - if (state->controllers[i].keycode == key && state->controllers[i].controller == p2) + if (state->controllerKeyMapping[i].keycode == key && state->controllerKeyMapping[i].controllerSlot == controllerSlot) return; } - if (slot == -1) { - warning("Number of set.keys exceeded %d", MAX_CONTROLLERS); + if (keyMappingSlot == -1) { + warning("Number of set.keys exceeded %d", MAX_CONTROLLER_KEYMAPPINGS); return; } - debugC(4, kDebugLevelScripts, "cmdSetKey: %d %d %d", p0, p1, p2); - state->controllers[slot].keycode = key; - state->controllers[slot].controller = p2; + debugC(4, kDebugLevelScripts, "cmdSetKey: %d %d %d", parameter[0], parameter[1], controllerSlot); + state->controllerKeyMapping[keyMappingSlot].keycode = key; + state->controllerKeyMapping[keyMappingSlot].controllerSlot = controllerSlot; - state->controllerOccured[p2] = false; + state->controllerOccured[controllerSlot] = false; } -void cmdSetString(AgiGame *state, uint8 *p) { +void cmdSetString(AgiGame *state, uint8 *parameter) { + uint16 stringNr = parameter[0]; + uint16 textNr = parameter[1] - 1; // CM: to avoid crash in Groza (str = 150) - if (p0 > MAX_STRINGS) + if (stringNr > MAX_STRINGS) return; - strcpy(state->strings[p0], state->_curLogic->texts[p1 - 1]); + strcpy(state->strings[stringNr], state->_curLogic->texts[textNr]); } -void cmdDisplay(AgiGame *state, uint8 *p) { +void cmdDisplay(AgiGame *state, uint8 *parameter) { // V1 has 4 args - int t = (getVersion() >= 0x2000 ? p2 : p3); - int len = 40; - - char *s = state->_vm->wordWrapString(state->_curLogic->texts[t - 1], &len); - - state->_vm->printText(s, p1, 0, p0, 40, state->colorFg, state->colorBg); + int16 textNr = (getVersion() >= 0x2000 ? parameter[2] : parameter[3]); + int16 textRow = parameter[0]; + int16 textColumn = parameter[1]; - free(s); + state->_vm->_text->display(textNr, textRow, textColumn); } -void cmdDisplayF(AgiGame *state, uint8 *p) { - state->_vm->printText(state->_curLogic->texts[_v[p2] - 1], _v[p1], 0, _v[p0], 40, state->colorFg, state->colorBg); -} - -void cmdClearTextRect(AgiGame *state, uint8 *p) { - int c, x1, y1, x2, y2; +void cmdDisplayF(AgiGame *state, uint8 *parameter) { + int16 textRow = state->vars[parameter[0]]; + int16 textColumn = state->vars[parameter[1]]; + int16 textNr = state->vars[parameter[2]]; - if ((c = p4) != 0) - c = 15; - - x1 = p1 * CHAR_COLS; - y1 = p0 * CHAR_LINES; - x2 = (p3 + 1) * CHAR_COLS - 1; - y2 = (p2 + 1) * CHAR_LINES - 1; + state->_vm->_text->display(textNr, textRow, textColumn); +} - // Added to prevent crash with x2 = 40 in the iigs demo - if (x1 > GFX_WIDTH) - x1 = GFX_WIDTH - 1; - if (x2 > GFX_WIDTH) - x2 = GFX_WIDTH - 1; - if (y1 > GFX_HEIGHT) - y1 = GFX_HEIGHT - 1; - if (y2 > GFX_HEIGHT) - y2 = GFX_HEIGHT - 1; +void cmdClearTextRect(AgiGame *state, uint8 *parameter) { + int16 textUpperRow = parameter[0]; + int16 textUpperColumn = parameter[1]; + int16 textLowerRow = parameter[2]; + int16 textLowerColumn = parameter[3]; + int16 color = state->_vm->_text->calculateTextBackground(parameter[4]); - state->_vm->_gfx->drawRectangle(x1, y1, x2, y2, c); - state->_vm->_gfx->flushBlock(x1, y1, x2, y2); + state->_vm->_text->clearBlock(textUpperRow, textUpperColumn, textLowerRow, textLowerColumn, color); } -void cmdToggleMonitor(AgiGame *state, uint8 *p) { +void cmdToggleMonitor(AgiGame *state, uint8 *parameter) { debug(0, "toggle.monitor"); } -void cmdEchoLine(AgiGame *state, uint8 *p) { - strcpy((char *)state->inputBuffer, (const char *)state->echoBuffer); - state->cursorPos = strlen((char *)state->inputBuffer); - state->hasPrompt = 0; -} - -void cmdClearLines(AgiGame *state, uint8 *p) { - uint8 l; +void cmdClearLines(AgiGame *state, uint8 *parameter) { + int16 textRowUpper = parameter[0]; + int16 textRowLower = parameter[1]; + int16 color = state->_vm->_text->calculateTextBackground(parameter[2]); // Residence 44 calls clear.lines(24,0,0), see Sarien bug #558423 - l = p1 ? p1 : p0; - // Agent06 incorrectly calls clear.lines(1,150,0), see ScummVM bugs // #1935838 and #1935842 - l = (l <= 24) ? l : 24; - - state->_vm->clearLines(p0, l, p2); - state->_vm->flushLines(p0, l); + if (textRowUpper > textRowLower) { + warning("cmdClearLines: RowUpper higher than RowLower"); + textRowLower = textRowUpper; + } + state->_vm->_text->clearLines(textRowUpper, textRowLower, color); } -void cmdPrint(AgiGame *state, uint8 *p) { - int n = p0 < 1 ? 1 : p0; +void cmdPrint(AgiGame *state, uint8 *parameter) { + int16 textNr = parameter[0]; - state->_vm->print(state->_curLogic->texts[n - 1], 0, 0, 0); + state->_vm->_text->print(textNr); } -void cmdPrintF(AgiGame *state, uint8 *p) { - int n = _v[p0] < 1 ? 1 : _v[p0]; +void cmdPrintF(AgiGame *state, uint8 *parameter) { + int16 textNr = state->vars[parameter[0]]; - state->_vm->print(state->_curLogic->texts[n - 1], 0, 0, 0); + state->_vm->_text->print(textNr); } -void cmdPrintAt(AgiGame *state, uint8 *p) { - int n = p0 < 1 ? 1 : p0; +void cmdPrintAt(AgiGame *state, uint8 *parameter) { + int16 textNr = parameter[0]; + int16 textRow = parameter[1]; + int16 textColumn = parameter[2]; + int16 textWidth = parameter[3]; - debugC(4, kDebugLevelScripts, "%d %d %d %d", p0, p1, p2, p3); + debugC(4, kDebugLevelScripts, "%d %d %d %d", textNr, textRow, textColumn, textWidth); - state->_vm->print(state->_curLogic->texts[n - 1], p1, p2, p3); + state->_vm->_text->printAt(textNr, textRow, textColumn, textWidth); } -void cmdPrintAtV(AgiGame *state, uint8 *p) { - int n = _v[p0] < 1 ? 1 : _v[p0]; +void cmdPrintAtV(AgiGame *state, uint8 *parameter) { + int16 textNr = state->vars[parameter[0]]; + int16 textRow = parameter[1]; + int16 textColumn = parameter[2]; + int16 textWidth = parameter[3]; + + debugC(4, kDebugLevelScripts, "%d %d %d %d", textNr, textRow, textColumn, textWidth); - state->_vm->print(state->_curLogic->texts[n - 1], p1, p2, p3); + state->_vm->_text->printAt(textNr, textRow, textColumn, textWidth); } -void cmdPushScript(AgiGame *state, uint8 *p) { +void cmdPushScript(AgiGame *state, uint8 *parameter) { // We run AGIMOUSE always as a side effect //if (getFeatures() & GF_AGIMOUSE || true) { state->vars[27] = state->_vm->_mouse.button; @@ -1633,86 +2133,91 @@ void cmdPushScript(AgiGame *state, uint8 *p) { }*/ } -void cmdSetPriBase(AgiGame *state, uint8 *p) { - int i, x, pri; - - debug(0, "Priority base set to %d", p0); +void cmdSetPriBase(AgiGame *state, uint8 *parameter) { + uint16 priorityBase = parameter[0]; - // state->alt_pri = true; - x = (_HEIGHT - p0) * _HEIGHT / 10; + debug(0, "Priority base set to %d", priorityBase); - for (i = 0; i < _HEIGHT; i++) { - pri = (i - p0) < 0 ? 4 : (i - p0) * _HEIGHT / x + 5; - if (pri > 15) - pri = 15; - state->priTable[i] = pri; - } + state->_vm->_gfx->setPriorityTable(priorityBase); } -void cmdMousePosn(AgiGame *state, uint8 *p) { - _v[p0] = WIN_TO_PIC_X(state->_vm->_mouse.x); - _v[p1] = WIN_TO_PIC_Y(state->_vm->_mouse.y); +void cmdMousePosn(AgiGame *state, uint8 *parameter) { + uint16 destVarNr1 = parameter[0]; + uint16 destVarNr2 = parameter[1]; + int16 mouseX = state->_vm->_mouse.x; + int16 mouseY = state->_vm->_mouse.y; + + state->_vm->adjustPosToGameScreen(mouseX, mouseY); + + state->vars[destVarNr1] = mouseX; + state->vars[destVarNr2] = mouseY; } -void cmdShakeScreen(AgiGame *state, uint8 *p) { - int i; +void cmdShakeScreen(AgiGame *state, uint8 *parameter) { + uint16 shakeCount = parameter[0]; // AGIPAL uses shake.screen values between 100 and 109 to set the palette // (Checked the original AGIPAL-hack's shake.screen-routine's disassembly). - if (p0 >= 100 && p0 < 110) { + if (shakeCount >= 100 && shakeCount < 110) { if (getFeatures() & GF_AGIPAL) { - state->_vm->_gfx->setAGIPal(p0); + state->_vm->_gfx->setAGIPal(shakeCount); return; } else { warning("It looks like GF_AGIPAL flag is missing"); } } - // Disables input while shaking to prevent bug - // #1678230: AGI: Entering text while screen is shaking - bool originalValue = state->inputEnabled; - state->inputEnabled = false; - - state->_vm->_gfx->shakeStart(); - - state->_vm->_sprites->commitBoth(); // Fixes SQ1 demo - for (i = 4 * p0; i; i--) { - state->_vm->_gfx->shakeScreen(i & 1); - state->_vm->_gfx->flushBlock(0, 0, GFX_WIDTH - 1, GFX_HEIGHT - 1); - state->_vm->mainCycle(); - } - state->_vm->_gfx->shakeEnd(); - - // Sets input back to what it was - state->inputEnabled = originalValue; + state->_vm->_gfx->shakeScreen(shakeCount); } -void cmdSetSpeed(AgiGame *state, uint8 *p) { +void cmdSetSpeed(AgiGame *state, uint8 *parameter) { // V1 command (void)state; - (void)p; + (void)parameter; // speed = _v[p0]; } -void cmdSetItemView(AgiGame *state, uint8 *p) { +void cmdSetItemView(AgiGame *state, uint8 *parameter) { // V1 command (void)state; - (void)p; + (void)parameter; } -void cmdCallV1(AgiGame *state, uint8 *p) { - state->_vm->agiLoadResource(rLOGIC, p0); +void cmdCallV1(AgiGame *state, uint8 *parameter) { + uint16 resourceNr = parameter[0]; + + state->_vm->agiLoadResource(RESOURCETYPE_LOGIC, resourceNr); // FIXME: The following instruction looks incomplete. // Maybe something is meant to be assigned to, or read from, // the logic_list entry? // state->logic_list[++state->max_logics]; // For now, just do the increment, to silence a clang warning ++state->max_logics; - _v[13] = 1; + state->vars[13] = 1; +} + +void cmdNewRoomV1(AgiGame *state, uint8 *parameter) { + uint16 resourceNr = parameter[0]; + + warning("cmdNewRoomV1()"); + state->_vm->agiLoadResource(RESOURCETYPE_LOGIC, resourceNr); + state->max_logics = 1; + state->logic_list[1] = resourceNr; + state->vars[13] = 1; +} + +void cmdNewRoomVV1(AgiGame *state, uint8 *parameter) { + uint16 resourceNr = state->vars[parameter[0]]; + + warning("cmdNewRoomVV1()"); + state->_vm->agiLoadResource(RESOURCETYPE_LOGIC, resourceNr); + state->max_logics = 1; + state->logic_list[1] = resourceNr; + state->vars[13] = 1; } -void cmdUnknown(AgiGame *state, uint8 *p) { - warning("Skipping unknown opcode %2X", *(code + ip - 1)); +void cmdUnknown(AgiGame *state, uint8 *parameter) { + warning("Skipping unknown opcode %2X", *(state->_curLogic->data + state->_curLogic->cIP - 1)); } /** @@ -1740,7 +2245,7 @@ int AgiEngine::runLogic(int n) { // If logic not loaded, load it if (~_game.dirLogic[n].flags & RES_LOADED) { debugC(4, kDebugLevelScripts, "logic %d not loaded!", n); - agiLoadResource(rLOGIC, n); + agiLoadResource(RESOURCETYPE_LOGIC, n); } _game.lognum = n; @@ -1749,7 +2254,9 @@ int AgiEngine::runLogic(int n) { _game._curLogic->cIP = _game._curLogic->sIP; _timerHack = 0; - while (ip < _game.logics[n].size && !(shouldQuit() || _restartGame)) { + while (state->_curLogic->cIP < _game.logics[n].size && !(shouldQuit() || _restartGame)) { + // TODO: old code, needs to be adjusted +#if 0 if (_debug.enabled) { if (_debug.steps > 0) { if (_debug.logic0 || n) { @@ -1762,24 +2269,25 @@ int AgiEngine::runLogic(int n) { do { mainCycle(); } while (!_debug.steps && _debug.enabled); - _sprites->eraseBoth(); + _sprites->eraseAllSprites(); } } +#endif - _game.execStack.back().curIP = ip; + _game.execStack.back().curIP = state->_curLogic->cIP; char st[101]; int sz = MIN(_game.execStack.size(), 100u); memset(st, '.', sz); st[sz] = 0; - switch (op = *(code + ip++)) { + switch (op = *(state->_curLogic->data + state->_curLogic->cIP++)) { case 0xff: // if (open/close) testIfCode(n); break; case 0xfe: // goto // +2 covers goto size - ip += 2 + ((int16)READ_LE_UINT16(code + ip)); + state->_curLogic->cIP += 2 + ((int16)READ_LE_UINT16(state->_curLogic->data + state->_curLogic->cIP)); // timer must keep running even in goto loops, // but AGI engine can't do that :( @@ -1809,20 +2317,20 @@ int AgiEngine::runLogic(int n) { return 1; default: num = logicNamesCmd[op].argumentsLength(); - memmove(p, code + ip, num); + memmove(p, state->_curLogic->data + state->_curLogic->cIP, num); memset(p + num, 0, CMD_BSIZE - num); debugC(2, kDebugLevelScripts, "%s%s(%d %d %d)", st, logicNamesCmd[op].name, p[0], p[1], p[2]); _agiCommands[op](&_game, p); - ip += num; + state->_curLogic->cIP += num; } // if ((op == 0x0B || op == 0x3F || op == 0x40) && logic_index < state->max_logics) { // n = state->logic_list[++logic_index]; // state->_curLogic = &state->logics[n]; // state->lognum = n; -// ip = 2; +// state->_curLogic_cIP = 2; // warning("running logic %d\n", n); // } diff --git a/engines/agi/op_dbg.cpp b/engines/agi/op_dbg.cpp index 997da9db7d..92af2c63f1 100644 --- a/engines/agi/op_dbg.cpp +++ b/engines/agi/op_dbg.cpp @@ -93,7 +93,7 @@ void AgiEngine::debugConsole(int lognum, int mode, const char *str) { if (*c == 'n') { debugN(0, "%d", *(code + (ip + z))); } else { - debugN(0, "v%d[%d]", *(code + (ip + z)), getvar(*(code + (ip + z)))); + debugN(0, "v%d[%d]", *(code + (ip + z)), getVar(*(code + (ip + z)))); } c++; z++; diff --git a/engines/agi/op_test.cpp b/engines/agi/op_test.cpp index 9839f0ec90..afb1ddb820 100644 --- a/engines/agi/op_test.cpp +++ b/engines/agi/op_test.cpp @@ -23,6 +23,8 @@ #include "agi/agi.h" #include "agi/opcodes.h" +#include "agi/words.h" + #include "common/endian.h" namespace Agi { @@ -30,16 +32,14 @@ namespace Agi { #define ip (state->_curLogic->cIP) #define code (state->_curLogic->data) -#define getvar(a) state->_vm->getvar(a) -#define getflag(a) state->_vm->getflag(a) +#define getVar(a) state->_vm->getVar(a) -#define testEqual(v1, v2) (getvar(v1) == (v2)) -#define testLess(v1, v2) (getvar(v1) < (v2)) -#define testGreater(v1, v2) (getvar(v1) > (v2)) -#define testIsSet(flag) (getflag(flag)) +#define testEqual(v1, v2) (getVar(v1) == (v2)) +#define testLess(v1, v2) (getVar(v1) < (v2)) +#define testGreater(v1, v2) (getVar(v1) > (v2)) #define testHas(obj) (state->_vm->objectGetLocation(obj) == EGO_OWNED) #define testHasV1(obj) (state->_vm->objectGetLocation(obj) == EGO_OWNED_V1) -#define testObjInRoom(obj, v) (state->_vm->objectGetLocation(obj) == getvar(v)) +#define testObjInRoom(obj, v) (state->_vm->objectGetLocation(obj) == getVar(v)) void condEqual(AgiGame *state, uint8 *p) { if (p[0] == 11) @@ -50,7 +50,7 @@ void condEqual(AgiGame *state, uint8 *p) { void condEqualV(AgiGame *state, uint8 *p) { if (p[0] == 11 || p[1] == 11) state->_vm->_timerHack++; - state->testResult = testEqual(p[0], getvar(p[1])); + state->testResult = testEqual(p[0], getVar(p[1])); } void condLess(AgiGame *state, uint8 *p) { @@ -62,7 +62,7 @@ void condLess(AgiGame *state, uint8 *p) { void condLessV(AgiGame *state, uint8 *p) { if (p[0] == 11 || p[1] == 11) state->_vm->_timerHack++; - state->testResult = testLess(p[0], getvar(p[1])); + state->testResult = testLess(p[0], getVar(p[1])); } void condGreater(AgiGame *state, uint8 *p) { @@ -74,19 +74,19 @@ void condGreater(AgiGame *state, uint8 *p) { void condGreaterV(AgiGame *state, uint8 *p) { if (p[0] == 11 || p[1] == 11) state->_vm->_timerHack++; - state->testResult = testGreater(p[0], getvar(p[1])); + state->testResult = testGreater(p[0], getVar(p[1])); } void condIsSet(AgiGame *state, uint8 *p) { - state->testResult = testIsSet(p[0]); + state->testResult = state->_vm->getflag(p[0]); } void condIsSetV(AgiGame *state, uint8 *p) { - state->testResult = testIsSet(getvar(p[0])); + state->testResult = state->_vm->getflag(getVar(p[0])); } void condIsSetV1(AgiGame *state, uint8 *p) { - state->testResult = getvar(p[0]) > 0; + state->testResult = getVar(p[0]) > 0; } void condHas(AgiGame *state, uint8 *p) { @@ -121,47 +121,47 @@ void condSaid(AgiGame *state, uint8 *p) { void condSaid1(AgiGame *state, uint8 *p) { state->testResult = false; - if (!getflag(fEnteredCli)) + if (!state->_vm->getflag(VM_FLAG_ENTERED_CLI)) return; int id0 = READ_LE_UINT16(p); - if ((id0 == 1 || id0 == state->egoWords[0].id)) + if ((id0 == 1 || id0 == state->_vm->_words->getEgoWordId(0))) state->testResult = true; } void condSaid2(AgiGame *state, uint8 *p) { state->testResult = false; - if (!getflag(fEnteredCli)) + if (!state->_vm->getflag(VM_FLAG_ENTERED_CLI)) return; int id0 = READ_LE_UINT16(p); int id1 = READ_LE_UINT16(p + 2); - if ((id0 == 1 || id0 == state->egoWords[0].id) && - (id1 == 1 || id1 == state->egoWords[1].id)) + if ((id0 == 1 || id0 == state->_vm->_words->getEgoWordId(0)) && + (id1 == 1 || id1 == state->_vm->_words->getEgoWordId(1))) state->testResult = true; } void condSaid3(AgiGame *state, uint8 *p) { state->testResult = false; - if (!getflag(fEnteredCli)) + if (!state->_vm->getflag(VM_FLAG_ENTERED_CLI)) return; int id0 = READ_LE_UINT16(p); int id1 = READ_LE_UINT16(p + 2); int id2 = READ_LE_UINT16(p + 4); - if ((id0 == 1 || id0 == state->egoWords[0].id) && - (id1 == 1 || id1 == state->egoWords[1].id) && - (id2 == 1 || id2 == state->egoWords[2].id)) + if ((id0 == 1 || id0 == state->_vm->_words->getEgoWordId(0)) && + (id1 == 1 || id1 == state->_vm->_words->getEgoWordId(1)) && + (id2 == 1 || id2 == state->_vm->_words->getEgoWordId(2))) state->testResult = true; } void condBit(AgiGame *state, uint8 *p) { - state->testResult = (getvar(p[1]) >> p[0]) & 1; + state->testResult = (getVar(p[1]) >> p[0]) & 1; } void condCompareStrings(AgiGame *state, uint8 *p) { @@ -188,7 +188,7 @@ void condUnknown13(AgiGame *state, uint8 *p) { // This command is used at least in the Amiga version of Gold Rush! v2.05 1989-03-09 // (AGI 2.316) in logics 1, 3, 5, 6, 137 and 192 (Logic.192 revealed this command's nature). // TODO: Check this command's implementation using disassembly just to be sure. - int ec = state->viewTable[0].flags & fAdjEgoXY; + int ec = state->screenObjTable[SCREENOBJECTS_EGO_ENTRY].flags & fAdjEgoXY; debugC(7, kDebugLevelScripts, "op_test: in.motion.using.mouse = %s (Amiga-specific testcase 19)", ec ? "true" : "false"); state->testResult = ec; } @@ -221,7 +221,7 @@ uint8 AgiEngine::testCompareStrings(uint8 s1, uint8 s2) { break; default: - ms1[j++] = toupper(ms1[k]); + ms1[j++] = tolower(ms1[k]); break; } } @@ -242,7 +242,7 @@ uint8 AgiEngine::testCompareStrings(uint8 s1, uint8 s2) { break; default: - ms2[j++] = toupper(ms2[k]); + ms2[j++] = tolower(ms2[k]); break; } } @@ -258,7 +258,7 @@ uint8 AgiEngine::testKeypressed() { if (!x) { InputMode mode = _game.inputMode; - _game.inputMode = INPUT_NONE; + _game.inputMode = INPUTMODE_NONE; // Only check for events here, without updating the game cycle, // otherwise the animations in some games are drawn too quickly // like, for example, Manannan's lightnings in the intro of KQ3 @@ -275,11 +275,11 @@ uint8 AgiEngine::testKeypressed() { } uint8 AgiEngine::testController(uint8 cont) { - return (_game.controllerOccured[cont] ? 1 : 0); + return (_game.controllerOccured[cont] ? true : false); } uint8 AgiEngine::testPosn(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) { - VtEntry *v = &_game.viewTable[n]; + ScreenObjEntry *v = &_game.screenObjTable[n]; uint8 r; r = v->xPos >= x1 && v->yPos >= y1 && v->xPos <= x2 && v->yPos <= y2; @@ -290,7 +290,7 @@ uint8 AgiEngine::testPosn(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) { } uint8 AgiEngine::testObjInBox(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) { - VtEntry *v = &_game.viewTable[n]; + ScreenObjEntry *v = &_game.screenObjTable[n]; return v->xPos >= x1 && v->yPos >= y1 && v->xPos + v->xSize - 1 <= x2 && v->yPos <= y2; @@ -298,7 +298,7 @@ uint8 AgiEngine::testObjInBox(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) { // if n is in center of box uint8 AgiEngine::testObjCenter(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) { - VtEntry *v = &_game.viewTable[n]; + ScreenObjEntry *v = &_game.screenObjTable[n]; return v->xPos + v->xSize / 2 >= x1 && v->xPos + v->xSize / 2 <= x2 && v->yPos >= y1 && v->yPos <= y2; @@ -306,7 +306,7 @@ uint8 AgiEngine::testObjCenter(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) // if nect N is in right corner uint8 AgiEngine::testObjRight(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) { - VtEntry *v = &_game.viewTable[n]; + ScreenObjEntry *v = &_game.screenObjTable[n]; return v->xPos + v->xSize - 1 >= x1 && v->xPos + v->xSize - 1 <= x2 && v->yPos >= y1 && v->yPos <= y2; @@ -315,10 +315,12 @@ uint8 AgiEngine::testObjRight(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) { // When player has entered something, it is parsed elsewhere uint8 AgiEngine::testSaid(uint8 nwords, uint8 *cc) { AgiGame *state = &_game; - int c, n = _game.numEgoWords; + AgiEngine *vm = state->_vm; + Words *words = vm->_words; + int c, n = words->getEgoWordCount(); int z = 0; - if (getflag(fSaidAcceptedInput) || !getflag(fEnteredCli)) + if (vm->getflag(VM_FLAG_SAID_ACCEPTED_INPUT) || !vm->getflag(VM_FLAG_ENTERED_CLI)) return false; // FR: @@ -349,7 +351,7 @@ uint8 AgiEngine::testSaid(uint8 nwords, uint8 *cc) { case 1: // any word break; default: - if (_game.egoWords[c].id != z) + if (words->getEgoWordId(c) != z) return false; break; } @@ -364,7 +366,7 @@ uint8 AgiEngine::testSaid(uint8 nwords, uint8 *cc) { if (nwords != 0 && READ_LE_UINT16(cc) != 9999) return false; - setflag(fSaidAcceptedInput, true); + setflag(VM_FLAG_SAID_ACCEPTED_INPUT, true); return true; } diff --git a/engines/agi/opcodes.cpp b/engines/agi/opcodes.cpp index 0d7d180ec9..a20e51a5fc 100644 --- a/engines/agi/opcodes.cpp +++ b/engines/agi/opcodes.cpp @@ -173,189 +173,189 @@ AgiInstruction insV2Test[] = { }; AgiInstruction insV2[] = { - { "return", "", NULL }, - { "increment", "v", &cmdIncrement }, - { "decrement", "v", &cmdDecrement }, - { "assignn", "vn", &cmdAssignN }, - { "assignv", "vv", &cmdAssignV }, - { "addn", "vn", &cmdAddN }, - { "addv", "vv", &cmdAddV }, - { "subn", "vn", &cmdSubN }, - { "subv", "vv", &cmdSubV }, - { "lindirectv", "vv", &cmdLindirectV }, - { "lindirect", "vv", &cmdRindirect }, - { "lindirectn", "vn", &cmdLindirectN }, - { "set", "n", &cmdSet }, - { "reset", "n", &cmdReset }, - { "toggle", "n", &cmdToggle }, - { "set.v", "v", &cmdSetV }, - { "reset.v", "v", &cmdResetV }, - { "toggle.v", "v", &cmdToggleV }, - { "new.room", "n", &cmdNewRoom }, - { "new.room.v", "v", &cmdNewRoomF }, - { "load.logics", "n", &cmdLoadLogic }, - { "load.logics.v", "v", &cmdLoadLogicF }, - { "call", "n", &cmdCall }, - { "call.v", "v", &cmdCallF }, - { "load.pic", "v", &cmdLoadPic }, - { "draw.pic", "v", &cmdDrawPic }, - { "show.pic", "", &cmdShowPic }, - { "discard.pic", "v", &cmdDiscardPic }, - { "overlay.pic", "v", &cmdOverlayPic }, - { "show.pri.screen", "", &cmdShowPriScreen }, - { "load.view", "n", &cmdLoadView }, - { "load.view.v", "v", &cmdLoadViewF }, - { "discard.view", "n", &cmdDiscardView }, - { "animate.obj", "n", &cmdAnimateObj }, - { "unanimate.all", "", &cmdUnanimateAll }, - { "draw", "n", &cmdDraw }, - { "erase", "n", &cmdErase }, - { "position", "nnn", &cmdPosition }, - { "position.v", "nvv", &cmdPositionF }, - { "get.posn", "nvv", &cmdGetPosn }, - { "reposition", "nvv", &cmdReposition }, - { "set.view", "nn", &cmdSetView }, - { "set.view.v", "nv", &cmdSetViewF }, - { "set.loop", "nn", &cmdSetLoop }, - { "set.loop.v", "nv", &cmdSetLoopF }, - { "fix.loop", "n", &cmdFixLoop }, - { "release.loop", "n", &cmdReleaseLoop }, - { "set.cel", "nn", &cmdSetCel }, - { "set.cel.v", "nv", &cmdSetCelF }, - { "last.cel", "nv", &cmdLastCel }, - { "current.cel", "nv", &cmdCurrentCel }, - { "current.loop", "nv", &cmdCurrentLoop }, - { "current.view", "nv", &cmdCurrentView }, - { "number.of.loops", "nv", &cmdNumberOfLoops }, - { "set.priority", "nn", &cmdSetPriority }, - { "set.priority.v", "nv", &cmdSetPriorityF }, - { "release.priority", "n", &cmdReleasePriority }, - { "get.priority", "nn", &cmdGetPriority }, - { "stop.update", "n", &cmdStopUpdate }, - { "start.update", "n", &cmdStartUpdate }, - { "force.update", "n", &cmdForceUpdate }, - { "ignore.horizon", "n", &cmdIgnoreHorizon }, - { "observe.horizon", "n", &cmdObserveHorizon }, - { "set.horizon", "n", &cmdSetHorizon }, - { "object.on.water", "n", &cmdObjectOnWater }, - { "object.on.land", "n", &cmdObjectOnLand }, - { "object.on.anything", "n", &cmdObjectOnAnything }, - { "ignore.objs", "n", &cmdIgnoreObjs }, - { "observe.objs", "n", &cmdObserveObjs }, - { "distance", "nnv", &cmdDistance }, - { "stop.cycling", "n", &cmdStopCycling }, - { "start.cycling", "n", &cmdStartCycling }, - { "normal.cycle", "n", &cmdNormalCycle }, - { "end.of.loop", "nn", &cmdEndOfLoop }, - { "reverse.cycle", "n", &cmdReverseCycle }, - { "reverse.loop", "nn", &cmdReverseLoop }, - { "cycle.time", "nv", &cmdCycleTime }, - { "stop.motion", "n", &cmdStopMotion }, - { "start.motion", "n", &cmdStartMotion }, - { "step.size", "nv", &cmdStepSize }, - { "step.time", "nv", &cmdStepTime }, - { "move.obj", "nnnnn", &cmdMoveObj }, - { "move.obj.v", "nvvvv", &cmdMoveObjF }, - { "follow.ego", "nnn", &cmdFollowEgo }, - { "wander", "n", &cmdWander }, - { "normal.motion", "n", &cmdNormalMotion }, - { "set.dir", "nv", &cmdSetDir }, - { "get.dir", "nv", &cmdGetDir }, - { "ignore.blocks", "n", &cmdIgnoreBlocks }, - { "observe.blocks", "n", &cmdObserveBlocks }, - { "block", "nnnn", &cmdBlock }, - { "unblock", "", &cmdUnblock }, - { "get", "n", &cmdGet }, - { "get.v", "v", &cmdGetF }, - { "drop", "n", &cmdDrop }, - { "put", "nn", &cmdPut }, - { "put.v", "vv", &cmdPutF }, - { "get.room.v", "vv", &cmdGetRoomF }, - { "load.sound", "n", &cmdLoadSound }, - { "sound", "nn", &cmdSound }, - { "stop.sound", "", &cmdStopSound }, - { "print", "s", &cmdPrint }, - { "print.v", "v", &cmdPrintF }, - { "display", "nns", &cmdDisplay }, - { "display.v", "vvv", &cmdDisplayF }, - { "clear.lines", "nns", &cmdClearLines }, - { "text.screen", "", &cmdTextScreen }, - { "graphics", "", &cmdGraphics }, - { "set.cursor.char", "s", &cmdSetCursorChar }, - { "set.text.attribute", "nn", &cmdSetTextAttribute }, - { "shake.screen", "n", &cmdShakeScreen }, - { "configure.screen", "nnn", &cmdConfigureScreen }, - { "status.line.on", "", &cmdStatusLineOn }, - { "status.line.off", "", &cmdStatusLineOff }, - { "set.string", "ns", &cmdSetString }, - { "get.string", "nsnnn", &cmdGetString }, - { "word.to.string", "nn", &cmdWordToString }, - { "parse", "n", &cmdParse }, - { "get.num", "nv", &cmdGetNum }, - { "prevent.input", "", &cmdPreventInput }, - { "accept.input", "", &cmdAcceptInput }, - { "set.key", "nnn", &cmdSetKey }, - { "add.to.pic", "nnnnnnn", &cmdAddToPic }, - { "add.to.pic.v", "vvvvvvv", &cmdAddToPicF }, - { "status", "", &cmdStatus }, - { "save.game", "", &cmdSaveGame }, - { "restore.game", "", &cmdLoadGame }, - { "init.disk", "", &cmdInitDisk }, - { "restart.game", "", &cmdRestartGame }, - { "show.obj", "n", &cmdShowObj }, - { "random", "nnv", &cmdRandom }, - { "program.control", "", &cmdProgramControl }, - { "player.control", "", &cmdPlayerControl }, - { "obj.status.v", "v", &cmdObjStatusF }, - { "quit", "n", &cmdQuit }, // 0 args for AGI version 2.089 - { "show.mem", "", &cmdShowMem }, - { "pause", "", &cmdPause }, - { "echo.line", "", &cmdEchoLine }, - { "cancel.line", "", &cmdCancelLine }, - { "init.joy", "", &cmdInitJoy }, - { "toggle.monitor", "", &cmdToggleMonitor }, - { "version", "", &cmdVersion }, - { "script.size", "n", &cmdScriptSize }, - { "set.game.id", "s", &cmdSetGameID }, - { "log", "s", &cmdLog }, - { "set.scan.start", "", &cmdSetScanStart }, - { "reset.scan.start", "", &cmdResetScanStart }, - { "reposition.to", "nnn", &cmdRepositionTo }, - { "reposition.to.v", "nvv", &cmdRepositionToF }, - { "trace.on", "", &cmdTraceOn }, - { "trace.info", "nnn", &cmdTraceInfo }, + { "return", "", NULL }, // 00 + { "increment", "v", &cmdIncrement }, // 01 + { "decrement", "v", &cmdDecrement }, // 02 + { "assignn", "vn", &cmdAssignN }, // 03 + { "assignv", "vv", &cmdAssignV }, // 04 + { "addn", "vn", &cmdAddN }, // 05 + { "addv", "vv", &cmdAddV }, // 06 + { "subn", "vn", &cmdSubN }, // 07 + { "subv", "vv", &cmdSubV }, // 08 + { "lindirectv", "vv", &cmdLindirectV }, // 09 + { "lindirect", "vv", &cmdRindirect }, // 0A + { "lindirectn", "vn", &cmdLindirectN }, // 0B + { "set", "n", &cmdSet }, // 0C + { "reset", "n", &cmdReset }, // 0D + { "toggle", "n", &cmdToggle }, // 0E + { "set.v", "v", &cmdSetV }, // 0F + { "reset.v", "v", &cmdResetV }, // 10 + { "toggle.v", "v", &cmdToggleV }, // 11 + { "new.room", "n", &cmdNewRoom }, // 12 + { "new.room.v", "v", &cmdNewRoomF }, // 13 + { "load.logics", "n", &cmdLoadLogic }, // 14 + { "load.logics.v", "v", &cmdLoadLogicF }, // 15 + { "call", "n", &cmdCall }, // 16 + { "call.v", "v", &cmdCallF }, // 17 + { "load.pic", "v", &cmdLoadPic }, // 18 + { "draw.pic", "v", &cmdDrawPic }, // 19 + { "show.pic", "", &cmdShowPic }, // 1A + { "discard.pic", "v", &cmdDiscardPic }, // 1B + { "overlay.pic", "v", &cmdOverlayPic }, // 1C + { "show.pri.screen", "", &cmdShowPriScreen }, // 1D + { "load.view", "n", &cmdLoadView }, // 1E + { "load.view.v", "v", &cmdLoadViewF }, // 1F + { "discard.view", "n", &cmdDiscardView }, // 20 + { "animate.obj", "n", &cmdAnimateObj }, // 21 + { "unanimate.all", "", &cmdUnanimateAll }, // 22 + { "draw", "n", &cmdDraw }, // 23 + { "erase", "n", &cmdErase }, // 24 + { "position", "nnn", &cmdPosition }, // 25 + { "position.v", "nvv", &cmdPositionF }, // 26 + { "get.posn", "nvv", &cmdGetPosn }, // 27 + { "reposition", "nvv", &cmdReposition }, // 28 + { "set.view", "nn", &cmdSetView }, // 29 + { "set.view.v", "nv", &cmdSetViewF }, // 2A + { "set.loop", "nn", &cmdSetLoop }, // 2B + { "set.loop.v", "nv", &cmdSetLoopF }, // 2C + { "fix.loop", "n", &cmdFixLoop }, // 2D + { "release.loop", "n", &cmdReleaseLoop }, // 2E + { "set.cel", "nn", &cmdSetCel }, // 2F + { "set.cel.v", "nv", &cmdSetCelF }, // 30 + { "last.cel", "nv", &cmdLastCel }, // 31 + { "current.cel", "nv", &cmdCurrentCel }, // 32 + { "current.loop", "nv", &cmdCurrentLoop }, // 33 + { "current.view", "nv", &cmdCurrentView }, // 34 + { "number.of.loops", "nv", &cmdNumberOfLoops }, // 35 + { "set.priority", "nn", &cmdSetPriority }, // 36 + { "set.priority.v", "nv", &cmdSetPriorityF }, // 37 + { "release.priority", "n", &cmdReleasePriority }, // 38 + { "get.priority", "nn", &cmdGetPriority }, // 39 + { "stop.update", "n", &cmdStopUpdate }, // 3A + { "start.update", "n", &cmdStartUpdate }, // 3B + { "force.update", "n", &cmdForceUpdate }, // 3C + { "ignore.horizon", "n", &cmdIgnoreHorizon }, // 3D + { "observe.horizon", "n", &cmdObserveHorizon }, // 3E + { "set.horizon", "n", &cmdSetHorizon }, // 3F + { "object.on.water", "n", &cmdObjectOnWater }, // 40 + { "object.on.land", "n", &cmdObjectOnLand }, // 41 + { "object.on.anything", "n", &cmdObjectOnAnything }, // 42 + { "ignore.objs", "n", &cmdIgnoreObjs }, // 43 + { "observe.objs", "n", &cmdObserveObjs }, // 44 + { "distance", "nnv", &cmdDistance }, // 45 + { "stop.cycling", "n", &cmdStopCycling }, // 46 + { "start.cycling", "n", &cmdStartCycling }, // 47 + { "normal.cycle", "n", &cmdNormalCycle }, // 48 + { "end.of.loop", "nn", &cmdEndOfLoop }, // 49 + { "reverse.cycle", "n", &cmdReverseCycle }, // 5A + { "reverse.loop", "nn", &cmdReverseLoop }, // 5B + { "cycle.time", "nv", &cmdCycleTime }, // 5C + { "stop.motion", "n", &cmdStopMotion }, // 5D + { "start.motion", "n", &cmdStartMotion }, // 5E + { "step.size", "nv", &cmdStepSize }, // 5F + { "step.time", "nv", &cmdStepTime }, // 60 + { "move.obj", "nnnnn", &cmdMoveObj }, // 61 + { "move.obj.v", "nvvvv", &cmdMoveObjF }, // 62 + { "follow.ego", "nnn", &cmdFollowEgo }, // 63 + { "wander", "n", &cmdWander }, // 64 + { "normal.motion", "n", &cmdNormalMotion }, // 65 + { "set.dir", "nv", &cmdSetDir }, // 66 + { "get.dir", "nv", &cmdGetDir }, // 67 + { "ignore.blocks", "n", &cmdIgnoreBlocks }, // 68 + { "observe.blocks", "n", &cmdObserveBlocks }, // 69 + { "block", "nnnn", &cmdBlock }, // 6A + { "unblock", "", &cmdUnblock }, // 6B + { "get", "n", &cmdGet }, // 6C + { "get.v", "v", &cmdGetF }, // 6D + { "drop", "n", &cmdDrop }, // 6E + { "put", "nn", &cmdPut }, // 6F + { "put.v", "vv", &cmdPutF }, // 70 + { "get.room.v", "vv", &cmdGetRoomF }, // 71 + { "load.sound", "n", &cmdLoadSound }, // 72 + { "sound", "nn", &cmdSound }, // 73 + { "stop.sound", "", &cmdStopSound }, // 74 + { "print", "s", &cmdPrint }, // 75 + { "print.v", "v", &cmdPrintF }, // 76 + { "display", "nns", &cmdDisplay }, // 77 + { "display.v", "vvv", &cmdDisplayF }, // 78 + { "clear.lines", "nns", &cmdClearLines }, // 79 + { "text.screen", "", &cmdTextScreen }, // 7A + { "graphics", "", &cmdGraphics }, // 7B + { "set.cursor.char", "s", &cmdSetCursorChar }, // 7C + { "set.text.attribute", "nn", &cmdSetTextAttribute }, // 7D + { "shake.screen", "n", &cmdShakeScreen }, // 7E + { "configure.screen", "nnn", &cmdConfigureScreen }, // 7F + { "status.line.on", "", &cmdStatusLineOn }, // 80 + { "status.line.off", "", &cmdStatusLineOff }, // 81 + { "set.string", "ns", &cmdSetString }, // 82 + { "get.string", "nsnnn", &cmdGetString }, // 83 + { "word.to.string", "nn", &cmdWordToString }, // 84 + { "parse", "n", &cmdParse }, // 85 + { "get.num", "nv", &cmdGetNum }, // 86 + { "prevent.input", "", &cmdPreventInput }, // 87 + { "accept.input", "", &cmdAcceptInput }, // 88 + { "set.key", "nnn", &cmdSetKey }, // 89 + { "add.to.pic", "nnnnnnn", &cmdAddToPic }, // 8A + { "add.to.pic.v", "vvvvvvv", &cmdAddToPicF }, // 8B + { "status", "", &cmdStatus }, // 8C + { "save.game", "", &cmdSaveGame }, // 8D + { "restore.game", "", &cmdLoadGame }, // 8E + { "init.disk", "", &cmdInitDisk }, // 8F + { "restart.game", "", &cmdRestartGame }, // 90 + { "show.obj", "n", &cmdShowObj }, // 91 + { "random", "nnv", &cmdRandom }, // 92 + { "program.control", "", &cmdProgramControl }, // 93 + { "player.control", "", &cmdPlayerControl }, // 94 + { "obj.status.v", "v", &cmdObjStatusF }, // 95 + { "quit", "n", &cmdQuit }, // 96 0 args for AGI version 2.089 + { "show.mem", "", &cmdShowMem }, // 97 + { "pause", "", &cmdPause }, // 98 + { "echo.line", "", &cmdEchoLine }, // 99 + { "cancel.line", "", &cmdCancelLine }, // 9A + { "init.joy", "", &cmdInitJoy }, // 9B + { "toggle.monitor", "", &cmdToggleMonitor }, // 9C + { "version", "", &cmdVersion }, // 9D + { "script.size", "n", &cmdScriptSize }, // 9E + { "set.game.id", "s", &cmdSetGameID }, // 9F + { "log", "s", &cmdLog }, // A0 + { "set.scan.start", "", &cmdSetScanStart }, // A1 + { "reset.scan.start", "", &cmdResetScanStart }, // A2 + { "reposition.to", "nnn", &cmdRepositionTo }, // A3 + { "reposition.to.v", "nvv", &cmdRepositionToF }, // A4 + { "trace.on", "", &cmdTraceOn }, // A5 + { "trace.info", "nnn", &cmdTraceInfo }, // A6 { "print.at", "snnn", &cmdPrintAt }, // 3 args for AGI versions before 2.440 - { "print.at.v", "vnnn", &cmdPrintAtV }, - { "discard.view.v", "v", &cmdDiscardView}, - { "clear.text.rect", "nnnnn", &cmdClearTextRect }, - { "set.upper.left", "nn", &cmdSetUpperLeft }, - { "set.menu", "s", &cmdSetMenu }, - { "set.menu.item", "sn", &cmdSetMenuItem }, - { "submit.menu", "", &cmdSubmitMenu }, - { "enable.item", "n", &cmdEnableItem }, - { "disable.item", "n", &cmdDisableItem }, - { "menu.input", "", &cmdMenuInput }, - { "show.obj.v", "v", &cmdShowObjV }, - { "open.dialogue", "", &cmdOpenDialogue }, - { "close.dialogue", "", &cmdCloseDialogue }, - { "mul.n", "vn", &cmdMulN }, - { "mul.v", "vv", &cmdMulV }, - { "div.n", "vn", &cmdDivN }, - { "div.v", "vv", &cmdDivV }, - { "close.window", "", &cmdCloseWindow }, - { "set.simple", "n", &cmdSetSimple }, - { "push.script", "", &cmdPushScript }, - { "pop.script", "", &cmdPopScript }, - { "hold.key", "", &cmdHoldKey }, - { "set.pri.base", "n", &cmdSetPriBase }, - { "discard.sound", "n", &cmdDiscardSound }, + { "print.at.v", "vnnn", &cmdPrintAtV }, // A8 + { "discard.view.v", "v", &cmdDiscardView}, // A9 + { "clear.text.rect", "nnnnn", &cmdClearTextRect }, // AA + { "set.upper.left", "nn", &cmdSetUpperLeft }, // AB + { "set.menu", "s", &cmdSetMenu }, // AC + { "set.menu.item", "sn", &cmdSetMenuItem }, // AD + { "submit.menu", "", &cmdSubmitMenu }, // AE + { "enable.item", "n", &cmdEnableItem }, // AF + { "disable.item", "n", &cmdDisableItem }, // B0 + { "menu.input", "", &cmdMenuInput }, // B1 + { "show.obj.v", "v", &cmdShowObjV }, // B2 + { "open.dialogue", "", &cmdOpenDialogue }, // B3 + { "close.dialogue", "", &cmdCloseDialogue }, // B4 + { "mul.n", "vn", &cmdMulN }, // B5 + { "mul.v", "vv", &cmdMulV }, // B6 + { "div.n", "vn", &cmdDivN }, // B7 + { "div.v", "vv", &cmdDivV }, // B8 + { "close.window", "", &cmdCloseWindow }, // B9 + { "set.simple", "n", &cmdSetSimple }, // BA + { "push.script", "", &cmdPushScript }, // BB + { "pop.script", "", &cmdPopScript }, // BC + { "hold.key", "", &cmdHoldKey }, // BD + { "set.pri.base", "n", &cmdSetPriBase }, // BE + { "discard.sound", "n", &cmdDiscardSound }, // BF { "hide.mouse", "", &cmdHideMouse }, // 1 arg for AGI version 3.002.086 - { "allow.menu", "n", &cmdAllowMenu }, - { "show.mouse", "", &cmdShowMouse }, - { "fence.mouse", "nnnn", &cmdFenceMouse }, - { "mouse.posn", "vv", &cmdMousePosn }, + { "allow.menu", "n", &cmdAllowMenu }, // C1 + { "show.mouse", "", &cmdShowMouse }, // C2 + { "fence.mouse", "nnnn", &cmdFenceMouse }, // C3 + { "mouse.posn", "vv", &cmdMousePosn }, // C4 { "release.key", "", &cmdReleaseKey }, // 2 args for at least the Amiga GR (v2.05 1989-03-09) using AGI 2.316 - { "adj.ego.move.to.xy", "", &cmdAdjEgoMoveToXY } + { "adj.ego.move.to.xy", "", &cmdAdjEgoMoveToXY } // C6 }; void AgiEngine::setupOpcodes() { diff --git a/engines/agi/palette.h b/engines/agi/palette.h new file mode 100644 index 0000000000..88ccce7441 --- /dev/null +++ b/engines/agi/palette.h @@ -0,0 +1,311 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef AGI_PALETTE_H +#define AGI_PALETTE_H + +namespace Agi { + +/** + * 16 color RGB palette. + * This array contains the 6-bit RGB values of the EGA palette exported + * to the console drivers. + */ +static const uint8 PALETTE_EGA[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 +}; + +/** + * 4 color CGA palette. + */ +static const uint8 PALETTE_CGA[4 * 3] = { + 0x00, 0x00, 0x00, // black + 0x55, 0xff, 0xff, // cyan + 0xff, 0x55, 0xff, // magenta + 0xff, 0xff, 0xff +}; + +/** + * 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. + */ +static const uint8 PALETTE_ATARI_ST[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 +}; + +/** + * 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)) + */ +// *NOT* identical to Amiga generation 2 palette +static const uint8 PALETTE_APPLE_II_GS[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, // difference between Amiga v2 palette and Apple II GS palette, gotten from emulator (SQ2) + 0xE, 0xE, 0x0, + 0xF, 0xF, 0xF +}; + +/** + * 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 PALETTE_AMIGA_V1[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 +}; + +/** + * 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 PALETTE_AMIGA_V2[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 +}; + +/** + * 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 PALETTE_AMIGA_V3[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 +}; + +/** + * 16 color amiga-ish palette. + */ +static const uint8 PALETTE_AMIGA_ALT[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 +}; + +/** + * 256 color palette used with AGI256 and AGI256-2 games. + * Uses full 8 bits per color component. + * This is NOT the standard VGA palette. + */ +static const uint8 PALETTE_VGA[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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +} // End of namespace Agi + +#endif /* AGI_PALETTE_H */ diff --git a/engines/agi/picture.cpp b/engines/agi/picture.cpp index 58dfb9db68..951d67a0c0 100644 --- a/engines/agi/picture.cpp +++ b/engines/agi/picture.cpp @@ -31,8 +31,11 @@ PictureMgr::PictureMgr(AgiBase *agi, GfxMgr *gfx) { _vm = agi; _gfx = gfx; + _resourceNr = 0; _data = NULL; - _flen = _foffs = 0; + _dataSize = 0; + _dataOffset = 0; + _dataOffsetNibble = false; _patCode = _patNum = _priOn = _scrOn = _scrColor = _priColor = 0; _xOffset = _yOffset = 0; @@ -44,257 +47,38 @@ PictureMgr::PictureMgr(AgiBase *agi, GfxMgr *gfx) { } void PictureMgr::putVirtPixel(int x, int y) { - uint8 *p; - - x += _xOffset; - y += _yOffset; + byte drawMask = 0; if (x < 0 || y < 0 || x >= _width || y >= _height) return; - p = &_vm->_game.sbuf16c[y * _width + x]; + x += _xOffset; + y += _yOffset; if (_priOn) - *p = (_priColor << 4) | (*p & 0x0f); + drawMask |= GFX_SCREEN_MASK_PRIORITY; if (_scrOn) - *p = _scrColor | (*p & 0xf0); -} + drawMask |= GFX_SCREEN_MASK_VISUAL; -#if 0 -static void drawProc(int x, int y, int c, void *data) { - ((PictureMgr *)data)->putVirtPixel(x, y); + _gfx->putPixel(x, y, drawMask, _scrColor, _priColor); } -#endif - -/** - * Draw an AGI line. - * A line drawing routine sent by Joshua Neal, modified by Stuart George - * (fixed >>2 to >>1 and some other bugs like x1 instead of y1, etc.) - * @param x1 x coordinate of start point - * @param y1 y coordinate of start point - * @param x2 x coordinate of end point - * @param y2 y coordinate of end point - */ -void PictureMgr::drawLine(int x1, int y1, int x2, int y2) { - x1 = CLIP(x1, 0, _width - 1); - x2 = CLIP(x2, 0, _width - 1); - y1 = CLIP(y1, 0, _height - 1); - y2 = CLIP(y2, 0, _height - 1); - -#if 0 - Graphics::drawLine(x1, y1, x2, y2, 0, drawProc, this); -#else - int i, x, y, deltaX, deltaY, stepX, stepY, errorX, errorY, detdelta; - - // Vertical line - - if (x1 == x2) { - if (y1 > y2) { - SWAP(y1, y2); - } - - for (; y1 <= y2; y1++) - putVirtPixel(x1, y1); - - return; - } - - // Horizontal line - - if (y1 == y2) { - if (x1 > x2) { - SWAP(x1, x2); - } - for (; x1 <= x2; x1++) - putVirtPixel(x1, y1); - return; - } - - y = y1; - x = x1; - stepY = 1; - deltaY = y2 - y1; - if (deltaY < 0) { - stepY = -1; - deltaY = -deltaY; - } - - stepX = 1; - deltaX = x2 - x1; - if (deltaX < 0) { - stepX = -1; - deltaX = -deltaX; - } - - if (deltaY > deltaX) { - i = deltaY; - detdelta = deltaY; - errorX = deltaY / 2; - errorY = 0; +byte PictureMgr::getNextByte() { + if (!_dataOffsetNibble) { + return _data[_dataOffset++]; } else { - i = deltaX; - detdelta = deltaX; - errorX = 0; - errorY = deltaX / 2; + byte curByte = _data[_dataOffset++] << 4; + return (_data[_dataOffset] >> 4) | curByte; } - - putVirtPixel(x, y); - - do { - errorY += deltaY; - if (errorY >= detdelta) { - errorY -= detdelta; - y += stepY; - } - - errorX += deltaX; - if (errorX >= detdelta) { - errorX -= detdelta; - x += stepX; - } - - putVirtPixel(x, y); - i--; - } while (i > 0); -#endif } -/** - * Draw a relative AGI line. - * Draws short lines relative to last position. (drawing action 0xF7) - */ -void PictureMgr::dynamicDrawLine() { - int x1, y1, disp, dx, dy; - - if ((x1 = nextByte()) >= _minCommand || - (y1 = nextByte()) >= _minCommand) { - _foffs--; - return; - } - - putVirtPixel(x1, y1); - - for (;;) { - if ((disp = nextByte()) >= _minCommand) - break; - - dx = ((disp & 0xf0) >> 4) & 0x0f; - dy = (disp & 0x0f); - - if (dx & 0x08) - dx = -(dx & 0x07); - if (dy & 0x08) - dy = -(dy & 0x07); - - drawLine(x1, y1, x1 + dx, y1 + dy); - x1 += dx; - y1 += dy; - } - _foffs--; -} - -/************************************************************************** -** absoluteLine -** -** Draws long lines to actual locations (cf. relative) (drawing action 0xF6) -**************************************************************************/ -void PictureMgr::absoluteDrawLine() { - int x1, y1, x2, y2; - - if ((x1 = nextByte()) >= _minCommand || - (y1 = nextByte()) >= _minCommand) { - _foffs--; - return; - } - - putVirtPixel(x1, y1); - - for (;;) { - if ((x2 = nextByte()) >= _minCommand) - break; - - if ((y2 = nextByte()) >= _minCommand) - break; - - drawLine(x1, y1, x2, y2); - x1 = x2; - y1 = y2; - } - _foffs--; -} - -/************************************************************************** -** okToFill -**************************************************************************/ -int PictureMgr::isOkFillHere(int x, int y) { - uint8 p; - - x += _xOffset; - y += _yOffset; - - if (x < 0 || x >= _width || y < 0 || y >= _height) - return false; - - p = _vm->_game.sbuf16c[y * _width + x]; - - if (_flags & kPicFTrollMode) - return ((p & 0x0f) != 11 && (p & 0x0f) != _scrColor); - - if (!_priOn && _scrOn && _scrColor != 15) - return (p & 0x0f) == 15; - - if (_priOn && !_scrOn && _priColor != 4) - return (p >> 4) == 4; - - return (_scrOn && (p & 0x0f) == 15 && _scrColor != 15); -} - -/************************************************************************** -** agi_fill -**************************************************************************/ -void PictureMgr::agiFill(unsigned int x, unsigned int y) { - if (!_scrOn && !_priOn) - return; - - // Push initial pixel on the stack - Common::Stack<Common::Point> stack; - stack.push(Common::Point(x,y)); - - // Exit if stack is empty - while (!stack.empty()) { - Common::Point p = stack.pop(); - unsigned int c; - int newspanUp, newspanDown; - - if (!isOkFillHere(p.x, p.y)) - continue; - - // Scan for left border - for (c = p.x - 1; isOkFillHere(c, p.y); c--) - ; - - newspanUp = newspanDown = 1; - for (c++; isOkFillHere(c, p.y); c++) { - putVirtPixel(c, p.y); - if (isOkFillHere(c, p.y - 1)) { - if (newspanUp) { - stack.push(Common::Point(c,p.y-1)); - newspanUp = 0; - } - } else { - newspanUp = 1; - } - - if (isOkFillHere(c, p.y + 1)) { - if (newspanDown) { - stack.push(Common::Point(c,p.y+1)); - newspanDown = 0; - } - } else { - newspanDown = 1; - } - } +byte PictureMgr::getNextNibble() { + if (!_dataOffsetNibble) { + _dataOffsetNibble = true; + return _data[_dataOffset] >> 4; + } else { + _dataOffsetNibble = false; + return _data[_dataOffset++] & 0x0F; } } @@ -303,43 +87,43 @@ void PictureMgr::agiFill(unsigned int x, unsigned int y) { ** ** Draws an xCorner (drawing action 0xF5) **************************************************************************/ -void PictureMgr::xCorner(bool skipOtherCoords) { +void PictureMgr::draw_xCorner(bool skipOtherCoords) { int x1, x2, y1, y2; - if ((x1 = nextByte()) >= _minCommand || - (y1 = nextByte()) >= _minCommand) { - _foffs--; + if ((x1 = getNextByte()) >= _minCommand || + (y1 = getNextByte()) >= _minCommand) { + _dataOffset--; return; } putVirtPixel(x1, y1); for (;;) { - x2 = nextByte(); + x2 = getNextByte(); if (x2 >= _minCommand) break; if (skipOtherCoords) - if (nextByte() >= _minCommand) + if (getNextByte() >= _minCommand) break; - drawLine(x1, y1, x2, y1); + draw_Line(x1, y1, x2, y1); x1 = x2; if (skipOtherCoords) - if (nextByte() >= _minCommand) + if (getNextByte() >= _minCommand) break; - y2 = nextByte(); + y2 = getNextByte(); if (y2 >= _minCommand) break; - drawLine(x1, y1, x1, y2); + draw_Line(x1, y1, x1, y2); y1 = y2; } - _foffs--; + _dataOffset--; } /************************************************************************** @@ -350,9 +134,9 @@ void PictureMgr::xCorner(bool skipOtherCoords) { void PictureMgr::yCorner(bool skipOtherCoords) { int x1, x2, y1, y2; - if ((x1 = nextByte()) >= _minCommand || - (y1 = nextByte()) >= _minCommand) { - _foffs--; + if ((x1 = getNextByte()) >= _minCommand || + (y1 = getNextByte()) >= _minCommand) { + _dataOffset--; return; } @@ -360,44 +144,30 @@ void PictureMgr::yCorner(bool skipOtherCoords) { for (;;) { if (skipOtherCoords) - if (nextByte() >= _minCommand) + if (getNextByte() >= _minCommand) break; - y2 = nextByte(); + y2 = getNextByte(); if (y2 >= _minCommand) break; - drawLine(x1, y1, x1, y2); + draw_Line(x1, y1, x1, y2); y1 = y2; - x2 = nextByte(); + x2 = getNextByte(); if (x2 >= _minCommand) break; if (skipOtherCoords) - if (nextByte() >= _minCommand) + if (getNextByte() >= _minCommand) break; - drawLine(x1, y1, x2, y1); + draw_Line(x1, y1, x2, y1); x1 = x2; } - _foffs--; -} - -/************************************************************************** -** fill -** -** AGI flood fill. (drawing action 0xF8) -**************************************************************************/ -void PictureMgr::fill() { - int x1, y1; - - while ((x1 = nextByte()) < _minCommand && (y1 = nextByte()) < _minCommand) - agiFill(x1, y1); - - _foffs--; + _dataOffset--; } /************************************************************************** @@ -406,7 +176,6 @@ void PictureMgr::fill() { ** Draws pixels, circles, squares, or splatter brush patterns depending ** on the pattern code. **************************************************************************/ - void PictureMgr::plotPattern(int x, int y) { static const uint16 binary_list[] = {0x8000, 0x4000, 0x2000, 0x1000, 0x800, 0x400, 0x200, 0x100, 0x80, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1}; @@ -532,267 +301,600 @@ void PictureMgr::plotBrush() { for (;;) { if (_patCode & 0x20) { - if ((_patNum = nextByte()) >= _minCommand) + if ((_patNum = getNextByte()) >= _minCommand) break; _patNum = (_patNum >> 1) & 0x7f; } - if ((x1 = nextByte()) >= _minCommand) + if ((x1 = getNextByte()) >= _minCommand) break; - if ((y1 = nextByte()) >= _minCommand) + if ((y1 = getNextByte()) >= _minCommand) break; plotPattern(x1, y1); } - _foffs--; + _dataOffset--; } /************************************************************************** ** Draw AGI picture **************************************************************************/ - void PictureMgr::drawPicture() { - uint8 act; - int drawing; - int iteration = 0; - _patCode = 0; _patNum = 0; - _priOn = _scrOn = false; - _scrColor = (_pictureVersion == AGIPIC_C64) ? 0x0 : 0xf; - _priColor = 0x4; + _priOn = false; + _scrOn = false; + _scrColor = 15; + _priColor = 4; + + switch (_pictureVersion) { + case AGIPIC_C64: + drawPictureC64(); + break; + case AGIPIC_V1: + drawPictureV1(); + break; + case AGIPIC_V15: + drawPictureV15(); + break; + case AGIPIC_V2: + drawPictureV2(); + break; + case AGIPIC_256: + drawPictureAGI256(); + break; + default: + break; + } +} - drawing = 1; +void PictureMgr::drawPictureC64() { + byte curByte; - debugC(8, kDebugLevelMain, "Drawing v2 picture"); - for (drawing = 1; drawing && _foffs < _flen;) { - act = nextByte(); + debugC(8, kDebugLevelMain, "Drawing C64 picture"); - if (_pictureVersion == AGIPIC_C64 && act >= 0xf0 && act <= 0xfe) { - _scrColor = act - 0xf0; + _scrColor = 0x0; + + while (_dataOffset < _dataSize) { + curByte = getNextByte(); + + if ((curByte >= 0xF0) && (curByte <= 0xFE)) { + _scrColor = curByte & 0x0F; continue; } - switch (act) { - case 0xe0: // x-corner (C64) - xCorner(); + switch (curByte) { + case 0xe0: // x-corner + draw_xCorner(); break; - case 0xe1: // y-corner (C64) + case 0xe1: // y-corner yCorner(); break; - case 0xe2: // dynamic draw lines (C64) - dynamicDrawLine(); + case 0xe2: // dynamic draw lines + draw_LineShort(); break; - case 0xe3: // absolute draw lines (C64) - absoluteDrawLine(); + case 0xe3: // absolute draw lines + draw_LineAbsolute(); break; - case 0xe4: // fill (C64) - _scrColor = nextByte(); - _scrColor &= 0xF; // for v3 drawing diff - fill(); + case 0xe4: // fill + draw_SetColor(); + draw_Fill(); break; - case 0xe5: // enable screen drawing (C64) + case 0xe5: // enable screen drawing _scrOn = true; break; - case 0xe6: // plot brush (C64) - _patCode = nextByte(); + case 0xe6: // plot brush + _patCode = getNextByte(); plotBrush(); break; - case 0xf0: // set color on screen (AGI pic v2) - if (_pictureVersion == AGIPIC_V15) - break; + case 0xfb: + draw_LineShort(); + break; + case 0xff: // end of data + return; + default: + warning("Unknown picture opcode (%x) at (%x)", curByte, _dataOffset - 1); + break; + } + } +} + +void PictureMgr::drawPictureV1() { + byte curByte; + + debugC(8, kDebugLevelMain, "Drawing V1 picture"); - _scrColor = nextByte(); - _scrColor &= 0xF; // for v3 drawing diff + while (_dataOffset < _dataSize) { + curByte = getNextByte(); + + switch (curByte) { + case 0xf1: + draw_SetColor(); _scrOn = true; + _priOn = false; break; - case 0xf1: - if (_pictureVersion == AGIPIC_V1) { - _scrColor = nextByte(); - _scrColor &= 0xF; // for v3 drawing diff - _scrOn = true; - _priOn = false; - } else if (_pictureVersion == AGIPIC_V15) { // set color on screen - _scrColor = nextByte(); - _scrColor &= 0xF; - _scrOn = true; - } else if (_pictureVersion == AGIPIC_V2) { // disable screen drawing - _scrOn = false; - } + case 0xf3: + draw_SetColor(); + _scrOn = true; + draw_SetPriority(); + _priOn = true; break; - case 0xf2: // set color on priority (AGI pic v2) - if (_pictureVersion == AGIPIC_V15) - break; - - _priColor = nextByte(); - _priColor &= 0xf; // for v3 drawing diff + case 0xfa: + _scrOn = false; _priOn = true; + draw_LineAbsolute(); + _scrOn = true; + _priOn = false; break; - case 0xf3: - if (_pictureVersion == AGIPIC_V1) { - _scrColor = nextByte(); - _scrColor &= 0xF; // for v3 drawing diff - _scrOn = true; - _priColor = nextByte(); - _priColor &= 0xf; // for v3 drawing diff - _priOn = true; - } + case 0xff: // end of data + return; + default: + warning("Unknown picture opcode (%x) at (%x)", curByte, _dataOffset - 1); + break; + } + } +} - if (_pictureVersion == AGIPIC_V15 && (_flags & kPicFf3Stop)) - drawing = 0; +void PictureMgr::drawPictureV15() { + byte curByte; - if (_pictureVersion == AGIPIC_V2) // disable priority screen - _priOn = false; - break; - case 0xf4: // y-corner - if (_pictureVersion == AGIPIC_V15) - break; + debugC(8, kDebugLevelMain, "Drawing V1.5 picture"); - yCorner(); - break; - case 0xf5: // x-corner - if (_pictureVersion == AGIPIC_V15) - break; + while (_dataOffset < _dataSize) { + curByte = getNextByte(); - xCorner(); + switch (curByte) { + case 0xf0: + // happens in all Troll's Tale pictures + // TODO: figure out what it was meant for break; - case 0xf6: // absolute draw lines - if (_pictureVersion == AGIPIC_V15) - break; - - absoluteDrawLine(); + case 0xf1: + draw_SetColor(); + _scrOn = true; break; - case 0xf7: // dynamic draw lines - if (_pictureVersion == AGIPIC_V15) - break; - - dynamicDrawLine(); + case 0xf3: + if (_flags & kPicFf3Stop) + return; break; - case 0xf8: // fill - if (_pictureVersion == AGIPIC_V15) { - yCorner(true); - } else if (_pictureVersion == AGIPIC_V2) { - fill(); - } + case 0xf8: + yCorner(true); break; - case 0xf9: // set pattern - if (_pictureVersion == AGIPIC_V15) { - xCorner(true); - } else if (_pictureVersion == AGIPIC_V2) { - _patCode = nextByte(); + case 0xf9: + draw_xCorner(true); + break; + case 0xfa: + // TODO: is this really correct? + draw_LineAbsolute(); + break; + case 0xfb: + // TODO: is this really correct? + draw_LineAbsolute(); + break; + case 0xfe: + draw_SetColor(); + _scrOn = true; + draw_Fill(); + break; + case 0xff: // end of data + return; + default: + warning("Unknown picture opcode (%x) at (%x)", curByte, _dataOffset - 1); + break; + } + } +} - if (_vm->getGameType() == GType_PreAGI) - plotBrush(); +void PictureMgr::drawPictureV2() { + byte curByte; + bool nibbleMode = false; + bool mickeyCrystalAnimation = false; + int mickeyIteration = 0; + + debugC(8, kDebugLevelMain, "Drawing V2/V3 picture"); + + if (_vm->_game.dirPic[_resourceNr].flags & RES_PICTURE_V3_NIBBLE_PARM) { + // check, if this resource uses nibble mode (0xF0 + 0xF2 commands take nibbles instead of bytes) + nibbleMode = true; + } + + if ((_flags & kPicFStep) && _vm->getGameType() == GType_PreAGI) { + mickeyCrystalAnimation = true; + } + + while (_dataOffset < _dataSize) { + curByte = getNextByte(); + + switch (curByte) { + case 0xf0: + if (!nibbleMode) { + draw_SetColor(); + } else { + draw_SetNibbleColor(); } + _scrOn = true; break; - case 0xfa: // plot brush - if (_pictureVersion == AGIPIC_V1) { - _scrOn = false; - _priOn = true; - absoluteDrawLine(); - _scrOn = true; - _priOn = false; - } else if (_pictureVersion == AGIPIC_V15) { - absoluteDrawLine(); - } else if (_pictureVersion == AGIPIC_V2) { - plotBrush(); - } + case 0xf1: + _scrOn = false; break; - case 0xfb: - if (_pictureVersion == AGIPIC_V1) { - dynamicDrawLine(); - } else if (_pictureVersion == AGIPIC_V15) { - absoluteDrawLine(); + case 0xf2: + if (!nibbleMode) { + draw_SetPriority(); + } else { + draw_SetNibblePriority(); } + _priOn = true; break; - case 0xfc: // fill (AGI pic v1) - if (_pictureVersion == AGIPIC_V15) - break; + case 0xf3: + _priOn = false; + break; + case 0xf4: + yCorner(); + break; + case 0xf5: + draw_xCorner(); + break; + case 0xf6: + draw_LineAbsolute(); + break; + case 0xf7: + draw_LineShort(); + break; + case 0xf8: + draw_Fill(); + break; + case 0xf9: + _patCode = getNextByte(); - _scrColor = nextByte(); - _scrColor &= 0xF; - _priColor = nextByte(); - _priColor &= 0xf; - fill(); + if (_vm->getGameType() == GType_PreAGI) + plotBrush(); break; - case 0xfe: // fill (AGI pic v1.5) - _scrColor = nextByte(); - _scrColor &= 0xF; - _scrOn = true; - fill(); + case 0xfa: + plotBrush(); break; - case 0xff: // end of pic data - drawing = 0; + case 0xfc: + draw_SetColor(); + draw_SetPriority(); + draw_Fill(); break; + case 0xff: // end of data + return; default: - warning("Unknown picture opcode (%x) at (%x)", act, _foffs - 1); + warning("Unknown picture opcode (%x) at (%x)", curByte, _dataOffset - 1); + break; } // This is used by Mickey for the crystal animation // One frame of the crystal animation is shown on each iteration, based on _currentStep - if ((_flags & kPicFStep) && _vm->getGameType() == GType_PreAGI && _currentStep == iteration) { - int storedXOffset = _xOffset; - int storedYOffset = _yOffset; - // Note that picture coordinates are correct for Mickey only - showPic(10, 0, _width, _height); - _xOffset = storedXOffset; - _yOffset = storedYOffset; - _currentStep++; - if (_currentStep > 14) // crystal animation is 15 frames - _currentStep = 0; - // reset the picture step flag - it will be set when the next frame of the crystal animation is drawn - _flags &= ~kPicFStep; - return; // return back to the game loop + if (mickeyCrystalAnimation) { + if (_currentStep == mickeyIteration) { + int storedXOffset = _xOffset; + int storedYOffset = _yOffset; + // Note that picture coordinates are correct for Mickey only + showPic(10, 0, _width, _height); + _xOffset = storedXOffset; + _yOffset = storedYOffset; + _currentStep++; + if (_currentStep > 14) // crystal animation is 15 frames + _currentStep = 0; + // reset the picture step flag - it will be set when the next frame of the crystal animation is drawn + _flags &= ~kPicFStep; + return; // return back to the game loop + } + mickeyIteration++; + } + } +} + +void PictureMgr::drawPictureAGI256() { + const uint32 maxFlen = _width * _height; + int16 x = 0; + int16 y = 0; + byte *dataPtr = _data; + byte *dataEndPtr = _data + _dataSize; + byte color = 0; + + debugC(8, kDebugLevelMain, "Drawing AGI256 picture"); + + while (dataPtr < dataEndPtr) { + color = *dataPtr++; + _gfx->putPixel(x, y, GFX_SCREEN_MASK_VISUAL, color, 0); + + x++; + if (x >= _width) { + x = 0; + y++; + if (y >= _height) { + break; + } + } + } + + if (_dataSize < maxFlen) { + warning("Undersized AGI256 picture resource %d, using it anyway. Filling rest with white.", _resourceNr); + while (_dataSize < maxFlen) { + x++; + if (x >= _width) { + x = 0; + y++; + if (y >= _height) + break; + } + _gfx->putPixel(x, y, GFX_SCREEN_MASK_VISUAL, 15, 0); } + } else if (_dataSize > maxFlen) + warning("Oversized AGI256 picture resource %d, decoding only %ux%u part of it", _resourceNr, _width, _height); +} + +void PictureMgr::draw_SetColor() { + _scrColor = getNextByte(); + + // For CGA, replace the color with its mixture color + switch (_vm->_renderMode) { + case RENDERMODE_CGA: + _scrColor = _gfx->getCGAMixtureColor(_scrColor); + break; + default: + break; + } +} + +void PictureMgr::draw_SetPriority() { + _priColor = getNextByte(); +} - iteration++; +// this gets a nibble instead of a full byte +// used by some V3 games, special resource flag RES_PICTURE_V3_NIBBLE_PARM is set +void PictureMgr::draw_SetNibbleColor() { + _scrColor = getNextNibble(); + + // For CGA, replace the color with its mixture color + switch (_vm->_renderMode) { + case RENDERMODE_CGA: + _scrColor = _gfx->getCGAMixtureColor(_scrColor); + break; + default: + break; } } +void PictureMgr::draw_SetNibblePriority() { + _priColor = getNextNibble(); +} + /** - * convert AGI v3 format picture to AGI v2 format + * Draw an AGI line. + * A line drawing routine sent by Joshua Neal, modified by Stuart George + * (fixed >>2 to >>1 and some other bugs like x1 instead of y1, etc.) + * @param x1 x coordinate of start point + * @param y1 y coordinate of start point + * @param x2 x coordinate of end point + * @param y2 y coordinate of end point */ -uint8 *PictureMgr::convertV3Pic(uint8 *src, uint32 len) { - uint8 d, old = 0, x, *in, *xdata, *out, mode = 0; - uint32 i, ulen; +void PictureMgr::draw_Line(int16 x1, int16 y1, int16 x2, int16 y2) { + x1 = CLIP<int16>(x1, 0, _width - 1); + x2 = CLIP<int16>(x2, 0, _width - 1); + y1 = CLIP<int16>(y1, 0, _height - 1); + y2 = CLIP<int16>(y2, 0, _height - 1); + + int i, x, y, deltaX, deltaY, stepX, stepY, errorX, errorY, detdelta; - xdata = (uint8 *)malloc(len + len / 2); + // Vertical line + + if (x1 == x2) { + if (y1 > y2) { + SWAP(y1, y2); + } - out = xdata; - in = src; + for (; y1 <= y2; y1++) + putVirtPixel(x1, y1); - for (i = ulen = 0; i < len; i++, ulen++) { - d = *in++; + return; + } - *out++ = x = mode ? ((d & 0xF0) >> 4) + ((old & 0x0F) << 4) : d; + // Horizontal line - if (x == 0xFF) { - ulen++; - break; + if (y1 == y2) { + if (x1 > x2) { + SWAP(x1, x2); + } + for (; x1 <= x2; x1++) + putVirtPixel(x1, y1); + return; + } + + y = y1; + x = x1; + + stepY = 1; + deltaY = y2 - y1; + if (deltaY < 0) { + stepY = -1; + deltaY = -deltaY; + } + + stepX = 1; + deltaX = x2 - x1; + if (deltaX < 0) { + stepX = -1; + deltaX = -deltaX; + } + + if (deltaY > deltaX) { + i = deltaY; + detdelta = deltaY; + errorX = deltaY / 2; + errorY = 0; + } else { + i = deltaX; + detdelta = deltaX; + errorX = 0; + errorY = deltaX / 2; + } + + putVirtPixel(x, y); + + do { + errorY += deltaY; + if (errorY >= detdelta) { + errorY -= detdelta; + y += stepY; } - if (x == 0xf0 || x == 0xf2) { - if (mode) { - *out++ = d & 0x0F; - ulen++; + errorX += deltaX; + if (errorX >= detdelta) { + errorX -= detdelta; + x += stepX; + } + + putVirtPixel(x, y); + i--; + } while (i > 0); +} + +/** + * Draw a relative AGI line. + * Draws short lines relative to last position. (drawing action 0xF7) + */ +void PictureMgr::draw_LineShort() { + int x1, y1, disp, dx, dy; + + if ((x1 = getNextByte()) >= _minCommand || + (y1 = getNextByte()) >= _minCommand) { + _dataOffset--; + return; + } + + putVirtPixel(x1, y1); + + for (;;) { + if ((disp = getNextByte()) >= _minCommand) + break; + + dx = ((disp & 0xf0) >> 4) & 0x0f; + dy = (disp & 0x0f); + + if (dx & 0x08) + dx = -(dx & 0x07); + if (dy & 0x08) + dy = -(dy & 0x07); + + draw_Line(x1, y1, x1 + dx, y1 + dy); + x1 += dx; + y1 += dy; + } + _dataOffset--; +} + +/************************************************************************** +** absoluteLine +** +** Draws long lines to actual locations (cf. relative) (drawing action 0xF6) +**************************************************************************/ +void PictureMgr::draw_LineAbsolute() { + int16 x1, y1, x2, y2; + + if ((x1 = getNextByte()) >= _minCommand || + (y1 = getNextByte()) >= _minCommand) { + _dataOffset--; + return; + } + + putVirtPixel(x1, y1); + + for (;;) { + if ((x2 = getNextByte()) >= _minCommand) + break; + + if ((y2 = getNextByte()) >= _minCommand) + break; + + draw_Line(x1, y1, x2, y2); + x1 = x2; + y1 = y2; + } + _dataOffset--; +} + +// flood fill +void PictureMgr::draw_Fill() { + int16 x1, y1; + + while ((x1 = getNextByte()) < _minCommand && (y1 = getNextByte()) < _minCommand) + draw_Fill(x1, y1); + + _dataOffset--; +} + +void PictureMgr::draw_Fill(int16 x, int16 y) { + if (!_scrOn && !_priOn) + return; + + // Push initial pixel on the stack + Common::Stack<Common::Point> stack; + stack.push(Common::Point(x,y)); + + // Exit if stack is empty + while (!stack.empty()) { + Common::Point p = stack.pop(); + unsigned int c; + bool newspanUp, newspanDown; + + if (!draw_FillCheck(p.x, p.y)) + continue; + + // Scan for left border + for (c = p.x - 1; draw_FillCheck(c, p.y); c--) + ; + + newspanUp = newspanDown = true; + for (c++; draw_FillCheck(c, p.y); c++) { + putVirtPixel(c, p.y); + if (draw_FillCheck(c, p.y - 1)) { + if (newspanUp) { + stack.push(Common::Point(c,p.y-1)); + newspanUp = false; + } } else { - d = *in++; - *out++ = (d & 0xF0) >> 4; - i++, ulen++; + newspanUp = true; } - mode = !mode; + if (draw_FillCheck(c, p.y + 1)) { + if (newspanDown) { + stack.push(Common::Point(c,p.y+1)); + newspanDown = false; + } + } else { + newspanDown = true; + } } - - old = d; } +} - free(src); - xdata = (uint8 *)realloc(xdata, ulen); +int PictureMgr::draw_FillCheck(int16 x, int16 y) { + byte screenColor; + byte screenPriority; - return xdata; + if (x < 0 || x >= _width || y < 0 || y >= _height) + return false; + + x += _xOffset; + y += _yOffset; + + screenColor = _gfx->getColor(x, y); + screenPriority = _gfx->getPriority(x, y); + + if (_flags & kPicFTrollMode) + return ((screenColor != 11) && (screenColor != _scrColor)); + + if (!_priOn && _scrOn && _scrColor != 15) + return (screenColor == 15); + + if (_priOn && !_scrOn && _priColor != 4) + return screenPriority == 4; + + return (_scrOn && screenColor == 15 && _scrColor != 15); } /** @@ -804,8 +906,8 @@ uint8 *PictureMgr::convertV3Pic(uint8 *src, uint32 len) { * @param clear clear AGI screen before drawing * @param agi256 load an AGI256 picture resource */ -int PictureMgr::decodePicture(int n, int clr, bool agi256, int pic_width, int pic_height) { - debugC(8, kDebugLevelResources, "(%d)", n); +int PictureMgr::decodePicture(int16 resourceNr, bool clear, bool agi256, int16 pic_width, int16 pic_height) { + debugC(8, kDebugLevelResources, "(%d)", resourceNr); _patCode = 0; _patNum = 0; @@ -813,32 +915,28 @@ int PictureMgr::decodePicture(int n, int clr, bool agi256, int pic_width, int pi _scrColor = 0xF; _priColor = 0x4; - _data = _vm->_game.pictures[n].rdata; - _flen = _vm->_game.dirPic[n].len; - _foffs = 0; + _resourceNr = resourceNr; + _data = _vm->_game.pictures[resourceNr].rdata; + _dataSize = _vm->_game.dirPic[resourceNr].len; + _dataOffset = 0; + _dataOffsetNibble = false; _width = pic_width; _height = pic_height; - if (clr && !agi256) // 256 color pictures should always fill the whole screen, so no clearing for them. - memset(_vm->_game.sbuf16c, 0x4f, _width * _height); // Clear 16 color AGI screen (Priority 4, color white). + if (clear && !agi256) { // 256 color pictures should always fill the whole screen, so no clearing for them. + _gfx->clear(15, 4); // Clear 16 color AGI screen (Priority 4, color white). + } if (!agi256) { drawPicture(); // Draw 16 color picture. } else { - const uint32 maxFlen = _width * _height; - memcpy(_vm->_game.sbuf256c, _data, MIN(_flen, maxFlen)); // Draw 256 color picture. - - if (_flen < maxFlen) { - warning("Undersized AGI256 picture resource %d, using it anyway. Filling rest with white.", n); - memset(_vm->_game.sbuf256c + _flen, 0x0f, maxFlen - _flen); // Fill missing area with white. - } else if (_flen > maxFlen) - warning("Oversized AGI256 picture resource %d, decoding only %ux%u part of it", n, _width, _height); + drawPictureAGI256(); } - if (clr) + if (clear) _vm->clearImageStack(); - _vm->recordImageStackCall(ADD_PIC, n, clr, agi256, 0, 0, 0, 0); + _vm->recordImageStackCall(ADD_PIC, resourceNr, clear, agi256, 0, 0, 0, 0); return errOK; } @@ -860,14 +958,15 @@ int PictureMgr::decodePicture(byte* data, uint32 length, int clr, int pic_width, _priColor = 0x4; _data = data; - _flen = length; - _foffs = 0; + _dataSize = length; + _dataOffset = 0; + _dataOffsetNibble = false; _width = pic_width; _height = pic_height; if (clr) // 256 color pictures should always fill the whole screen, so no clearing for them. - memset(_vm->_game.sbuf16c, 0x4f, _width * _height); // Clear 16 color AGI screen (Priority 4, color white). + clear(); drawPicture(); // Draw 16 color picture. @@ -891,29 +990,66 @@ int PictureMgr::unloadPicture(int n) { } void PictureMgr::clear() { - memset(_vm->_game.sbuf16c, 0x4f, _width * _height); + _gfx->clear(15, 4); // Clear 16 color AGI screen (Priority 4, color white). +} + +void PictureMgr::showPic() { + debugC(8, kDebugLevelMain, "Show picture!"); + + _gfx->render_Block(0, 167, SCRIPT_WIDTH, SCRIPT_HEIGHT); } /** * Show AGI picture. * This function copies a ``hidden'' AGI picture to the output device. */ -void PictureMgr::showPic(int x, int y, int pic_width, int pic_height) { - int i, y1; - int offset; +void PictureMgr::showPic(int16 x, int16 y, int16 pic_width, int16 pic_height) { _width = pic_width; _height = pic_height; debugC(8, kDebugLevelMain, "Show picture!"); - i = 0; - offset = _vm->_game.lineMinPrint * CHAR_LINES; - for (y1 = y; y1 < y + _height; y1++) { - _gfx->putPixelsA(x, y1 + offset, _width, &_vm->_game.sbuf16c[i]); - i += _width; + // render block requires lower left coordinate! + _gfx->render_Block(x, pic_height + y - 1, pic_width, pic_height); +} + +void PictureMgr::showPicWithTransition() { + _width = SCRIPT_WIDTH; + _height = SCRIPT_HEIGHT; + + debugC(8, kDebugLevelMain, "Show picture!"); + + if (!_vm->_game.automaticRestoreGame) { + // only do transitions when we are not restoring a saved game + + if (!_vm->_game.gfxMode) { + // if we are not yet in graphics mode, set graphics mode palette now + // TODO: maybe change text mode to use different colors for drawing + // so that we don't have to change palettes at all + _gfx->setPalette(true); + } + + switch (_vm->_renderMode) { + case RENDERMODE_AMIGA: + case RENDERMODE_APPLE_II_GS: + // Platform Amiga/Apple II GS -> render and do Amiga transition + _gfx->render_Block(0, 167, SCRIPT_WIDTH, SCRIPT_HEIGHT, false); + _gfx->transition_Amiga(); + return; + break; + case RENDERMODE_ATARI_ST: + // Platform Atari ST used a different transition, looks "high-res" (full 320x168) + _gfx->render_Block(0, 167, SCRIPT_WIDTH, SCRIPT_HEIGHT, false); + _gfx->transition_AtariSt(); + return; + default: + // Platform PC -> render directly + // Macintosh AGI also doesn't seem to have any transitions + break; + } } - _gfx->flushScreen(); + _gfx->render_Block(0, 167, SCRIPT_WIDTH, SCRIPT_HEIGHT); } // preagi needed functions (for plotPattern) @@ -933,8 +1069,9 @@ void PictureMgr::setPictureVersion(AgiPictureVersion version) { void PictureMgr::setPictureData(uint8 *data, int len) { _data = data; - _flen = len; - _foffs = 0; + _dataSize = len; + _dataOffset = 0; + _dataOffsetNibble = false; _flags = 0; } diff --git a/engines/agi/picture.h b/engines/agi/picture.h index 9ff1e742f4..b1a272bedb 100644 --- a/engines/agi/picture.h +++ b/engines/agi/picture.h @@ -41,7 +41,8 @@ enum AgiPictureVersion { AGIPIC_C64, AGIPIC_V1, AGIPIC_V15, - AGIPIC_V2 + AGIPIC_V2, + AGIPIC_256 }; enum AgiPictureFlags { @@ -60,31 +61,49 @@ class PictureMgr { AgiBase *_vm; GfxMgr *_gfx; -private: +public: + PictureMgr(AgiBase *agi, GfxMgr *gfx); - void drawLine(int x1, int y1, int x2, int y2); - void dynamicDrawLine(); - void absoluteDrawLine(); - int isOkFillHere(int x, int y); - void agiFill(unsigned int x, unsigned int y); - void xCorner(bool skipOtherCoords = false); +private: + void draw_xCorner(bool skipOtherCoords = false); void yCorner(bool skipOtherCoords = false); - void fill(); int plotPatternPoint(int x, int y, int bitpos); void plotBrush(); - uint8 nextByte() { return _data[_foffs++]; } + byte getNextByte(); + byte getNextNibble(); public: - PictureMgr(AgiBase *agi, GfxMgr *gfx); - void putVirtPixel(int x, int y); - int decodePicture(int n, int clear, bool agi256 = false, int pic_width = _DEFAULT_WIDTH, int pic_height = _DEFAULT_HEIGHT); + int decodePicture(int16 resourceNr, bool clear, bool agi256 = false, int16 pic_width = _DEFAULT_WIDTH, int16 pic_height = _DEFAULT_HEIGHT); int decodePicture(byte* data, uint32 length, int clear, int pic_width = _DEFAULT_WIDTH, int pic_height = _DEFAULT_HEIGHT); int unloadPicture(int); void drawPicture(); - void showPic(int x = 0, int y = 0, int pic_width = _DEFAULT_WIDTH, int pic_height = _DEFAULT_HEIGHT); +private: + void drawPictureC64(); + void drawPictureV1(); + void drawPictureV15(); + void drawPictureV2(); + void drawPictureAGI256(); + + void draw_SetColor(); + void draw_SetPriority(); + void draw_SetNibbleColor(); + void draw_SetNibblePriority(); + + void draw_Line(int16 x1, int16 y1, int16 x2, int16 y2); + void draw_LineShort(); + void draw_LineAbsolute(); + + int draw_FillCheck(int16 x, int16 y); + void draw_Fill(int16 x, int16 y); + void draw_Fill(); + +public: + void showPic(); // <-- for regular AGI games + void showPic(int16 x, int16 y, int16 pic_width, int16 pic_height); // <-- for preAGI games + void showPicWithTransition(); uint8 *convertV3Pic(uint8 *src, uint32 len); void plotPattern(int x, int y); // public because it's used directly by preagi @@ -108,19 +127,12 @@ public: _height = h; } - void putPixel(int x, int y, uint8 color) { - _scrColor = color; - _priOn = false; - _scrOn = true; - putVirtPixel(x, y); - } - - bool isPictureLoaded() { return _data != NULL; } - private: + int16 _resourceNr; uint8 *_data; - uint32 _flen; - uint32 _foffs; + uint32 _dataSize; + uint32 _dataOffset; + bool _dataOffsetNibble; uint8 _patCode; uint8 _patNum; @@ -132,8 +144,8 @@ private: uint8 _minCommand; AgiPictureVersion _pictureVersion; - int _width, _height; - int _xOffset, _yOffset; + int16 _width, _height; + int16 _xOffset, _yOffset; int _flags; int _currentStep; diff --git a/engines/agi/preagi.cpp b/engines/agi/preagi.cpp index c368c7b195..c0a53e7608 100644 --- a/engines/agi/preagi.cpp +++ b/engines/agi/preagi.cpp @@ -29,6 +29,7 @@ #include "agi/preagi.h" #include "agi/graphics.h" #include "agi/keyboard.h" +#include "agi/text.h" namespace Agi { @@ -55,32 +56,22 @@ PreAgiEngine::PreAgiEngine(OSystem *syst, const AGIGameDescription *gameDesc) : void PreAgiEngine::initialize() { initRenderMode(); + initFont(); _gfx = new GfxMgr(this); _picture = new PictureMgr(this, _gfx); - if (getGameID() == GID_MICKEY) { - _fontData = fontData_Mickey; - } else { - _fontData = fontData_IBM; - } - _gfx->initMachine(); _game.gameFlags = 0; - _game.colorFg = 15; - _game.colorBg = 0; + //_game._vm->_text->charAttrib_Set(15, 0); _defaultColor = 0xF; _game.name[0] = '\0'; - _game.sbufOrig = (uint8 *)calloc(_WIDTH, _HEIGHT * 2); // Allocate space for two AGI screens vertically - _game.sbuf16c = _game.sbufOrig + SBUF16_OFFSET; // Make sbuf16c point to the 16 color (+control line & priority info) AGI screen - _game.sbuf = _game.sbuf16c; // Make sbuf point to the 16 color (+control line & priority info) AGI screen by default - - _game.lineMinPrint = 0; // hardcoded + //_game._vm->_text->configureScreen(0); // hardcoded _gfx->initVideo(); @@ -91,7 +82,7 @@ void PreAgiEngine::initialize() { debugC(2, kDebugLevelMain, "Detect game"); // clear all resources and events - for (int i = 0; i < MAX_DIRS; i++) { + for (int i = 0; i < MAX_DIRECTORY_ENTRIES; i++) { memset(&_game.pictures[i], 0, sizeof(struct AgiPicture)); memset(&_game.sounds[i], 0, sizeof(class AgiSound *)); // _game.sounds contains pointers now memset(&_game.dirPic[i], 0, sizeof(struct AgiDir)); @@ -113,11 +104,11 @@ void PreAgiEngine::clearScreen(int attr, bool overrideDefault) { if (overrideDefault) _defaultColor = attr; - _gfx->clearScreen((attr & 0xF0) / 0x10); + _gfx->clearDisplay((attr & 0xF0) / 0x10); } void PreAgiEngine::clearGfxScreen(int attr) { - _gfx->drawRectangle(0, 0, GFX_WIDTH - 1, IDI_MAX_ROW_PIC * 8 -1, (attr & 0xF0) / 0x10); + _gfx->drawDisplayRect(0, 0, GFX_WIDTH - 1, IDI_MAX_ROW_PIC * 8 -1, (attr & 0xF0) / 0x10); } // String functions @@ -143,7 +134,7 @@ void PreAgiEngine::drawStr(int row, int col, int attr, const char *buffer) { break; default: - _gfx->putTextCharacter(1, col * 8 , row * 8, static_cast<char>(code), attr & 0x0f, (attr & 0xf0) / 0x10, false, _fontData); + _gfx->drawCharacter(row, col, code, attr & 0x0f, attr >> 4, false); if (++col == 320 / 8) { col = 0; @@ -176,7 +167,7 @@ void PreAgiEngine::clearRow(int row) { void PreAgiEngine::printStr(const char* szMsg) { clearTextArea(); drawStr(21, 0, IDA_DEFAULT, szMsg); - _gfx->doUpdate(); + g_system->updateScreen(); } void PreAgiEngine::XOR80(char *buffer) { @@ -274,7 +265,7 @@ void PreAgiEngine::waitForTimer(int msec_delay) { uint32 start_time = _system->getMillis(); while (_system->getMillis() < start_time + msec_delay) { - _gfx->doUpdate(); + g_system->updateScreen(); _system->delayMillis(10); } } diff --git a/engines/agi/preagi_mickey.cpp b/engines/agi/preagi_mickey.cpp index 883107a957..02c48b090b 100644 --- a/engines/agi/preagi_mickey.cpp +++ b/engines/agi/preagi_mickey.cpp @@ -149,7 +149,7 @@ void MickeyEngine::printStr(char *buffer) { } // Show the string on screen - _gfx->doUpdate(); + _gfx->updateScreen(); } void MickeyEngine::printLine(const char *buffer) { @@ -158,7 +158,7 @@ void MickeyEngine::printLine(const char *buffer) { drawStr(22, 18 - strlen(buffer) / 2, IDA_DEFAULT, buffer); // Show the string on screen - _gfx->doUpdate(); + _gfx->updateScreen(); waitAnyKey(true); } @@ -281,7 +281,7 @@ void MickeyEngine::drawMenu(MSA_MENU menu, int sel0, int sel1) { } // Menu created, show it on screen - _gfx->doUpdate(); + _gfx->updateScreen(); } void MickeyEngine::getMouseMenuSelRow(MSA_MENU menu, int *sel0, int *sel1, int iRow, int x, int y) { @@ -373,18 +373,19 @@ bool MickeyEngine::getMenuSelRow(MSA_MENU menu, int *sel0, int *sel1, int iRow) // Change cursor if (northIndex >= 0 && (event.mouse.x >= 20 && event.mouse.x <= (IDI_MSA_PIC_WIDTH + 10) * 2) && (event.mouse.y >= 0 && event.mouse.y <= 10)) { - _gfx->setCursorPalette(true); + //_gfx->setCursorPalette(true); + // TODO:????? } else if (southIndex >= 0 && (event.mouse.x >= 20 && event.mouse.x <= (IDI_MSA_PIC_WIDTH + 10) * 2) && (event.mouse.y >= IDI_MSA_PIC_HEIGHT - 10 && event.mouse.y <= IDI_MSA_PIC_HEIGHT)) { - _gfx->setCursorPalette(true); + //_gfx->setCursorPalette(true); } else if (westIndex >= 0 && (event.mouse.y >= 0 && event.mouse.y <= IDI_MSA_PIC_HEIGHT) && (event.mouse.x >= 20 && event.mouse.x <= 30)) { - _gfx->setCursorPalette(true); + //_gfx->setCursorPalette(true); } else if (eastIndex >= 0 && (event.mouse.y >= 0 && event.mouse.y <= IDI_MSA_PIC_HEIGHT) && (event.mouse.x >= IDI_MSA_PIC_WIDTH * 2 && event.mouse.x <= (IDI_MSA_PIC_WIDTH + 10) * 2)) { - _gfx->setCursorPalette(true); + //_gfx->setCursorPalette(true); } else { - _gfx->setCursorPalette(false); + //_gfx->setCursorPalette(false); } } break; @@ -397,7 +398,8 @@ bool MickeyEngine::getMenuSelRow(MSA_MENU menu, int *sel0, int *sel1, int iRow) drawMenu(menu, *sel0, *sel1); - _gfx->setCursorPalette(false); + //_gfx->setCursorPalette(false); + // TODO??? _clickToMove = true; } else if (southIndex >= 0 && (event.mouse.x >= 20 && event.mouse.x <= (IDI_MSA_PIC_WIDTH + 10) * 2) && (event.mouse.y >= IDI_MSA_PIC_HEIGHT - 10 && event.mouse.y <= IDI_MSA_PIC_HEIGHT)) { @@ -406,7 +408,8 @@ bool MickeyEngine::getMenuSelRow(MSA_MENU menu, int *sel0, int *sel1, int iRow) drawMenu(menu, *sel0, *sel1); - _gfx->setCursorPalette(false); + //_gfx->setCursorPalette(false); + // TODO??? _clickToMove = true; } else if (westIndex >= 0 && (event.mouse.y >= 0 && event.mouse.y <= IDI_MSA_PIC_HEIGHT) && (event.mouse.x >= 20 && event.mouse.x <= 30)) { @@ -415,7 +418,8 @@ bool MickeyEngine::getMenuSelRow(MSA_MENU menu, int *sel0, int *sel1, int iRow) drawMenu(menu, *sel0, *sel1); - _gfx->setCursorPalette(false); + //_gfx->setCursorPalette(false); + // TODO??? _clickToMove = true; } else if (eastIndex >= 0 && (event.mouse.y >= 0 && event.mouse.y <= IDI_MSA_PIC_HEIGHT) && (event.mouse.x >= IDI_MSA_PIC_WIDTH * 2 && event.mouse.x <= (IDI_MSA_PIC_WIDTH + 10) * 2)) { @@ -424,10 +428,12 @@ bool MickeyEngine::getMenuSelRow(MSA_MENU menu, int *sel0, int *sel1, int iRow) drawMenu(menu, *sel0, *sel1); - _gfx->setCursorPalette(false); + //_gfx->setCursorPalette(false); + // TODO??? _clickToMove = true; } else { - _gfx->setCursorPalette(false); + //_gfx->setCursorPalette(false); + // TODO??? } return true; case Common::EVENT_RBUTTONUP: @@ -486,7 +492,7 @@ bool MickeyEngine::getMenuSelRow(MSA_MENU menu, int *sel0, int *sel1, int iRow) return false; case Common::KEYCODE_s: - flipflag(fSoundOn); + flipflag(VM_FLAG_SOUND_ON); break; case Common::KEYCODE_c: inventory(); @@ -666,7 +672,7 @@ void MickeyEngine::playNote(MSA_SND_NOTE note) { } void MickeyEngine::playSound(ENUM_MSA_SOUND iSound) { - if (!getflag(fSoundOn)) + if (!getflag(VM_FLAG_SOUND_ON)) return; Common::Event event; @@ -755,7 +761,9 @@ void MickeyEngine::drawPic(int iPic) { file.close(); // Note that decodePicture clears the screen + _picture->setOffset(10, 0); _picture->decodePicture(buffer, size, true, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT); + _picture->setOffset(0, 0); _picture->showPic(10, 0, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT); } @@ -892,75 +900,65 @@ void MickeyEngine::drawRoom() { drawRoomAnimation(); } -#if 0 -const uint8 colorBCG[16][2] = { - { 0x00, 0x00 }, // 0 (black, black) - { 0, 0 }, - { 0x00, 0x0D }, // 2 (black, purple) - { 0x00, 0xFF }, // 3 (black, white) - { 0, 0 }, - { 0, 0 }, - { 0, 0 }, - { 0, 0 }, - { 0x0D, 0x00 }, // 8 (purple, black) - { 0, 0 }, - { 0x0D, 0x0D }, // A (purple, purple) - { 0, 0 }, - { 0xFF, 0x00 }, // C (white, black) - { 0, 0 }, - { 0, 0 }, - { 0xFF, 0xFF } // F (white, white) +// Straight mapping, CGA colors to CGA +const byte BCGColorMappingCGAToCGA[4] = { + 0, 1, 2, 3 +}; + +// Mapping table to map CGA colors to EGA +const byte BCGColorMappingCGAToEGA[4] = { + 0, 11, 13, 15 }; -#endif void MickeyEngine::drawLogo() { - // TODO: clean this up and make it work properly, the logo is drawn way off to the right -#if 0 - char szFile[256] = {0}; - uint8 *buffer = new uint8[16384]; - const int w = 150; - const int h = 80; - const int xoffset = 30; // FIXME: remove this - uint8 bitmap[w][h]; - uint8 color, color2, color3, color4; - - // read in logos.bcg - sprintf(szFile, IDS_MSA_PATH_LOGO); + const int width = 80; + const int height = 85 * 2; + byte color1, color2, color3, color4; + byte *fileBuffer = nullptr; + uint32 fileBufferSize = 0; + byte *dataBuffer; + byte curByte; + const byte *BCGColorMapping = BCGColorMappingCGAToEGA; + + // disable color mapping in case we are in CGA mode + if (_renderMode == RENDERMODE_CGA) + BCGColorMapping = BCGColorMappingCGAToCGA; + + // read logos.bcg Common::File infile; - if (!infile.open(szFile)) + if (!infile.open(IDS_MSA_PATH_LOGO)) return; - infile.read(buffer, infile.size()); + fileBufferSize = infile.size(); + fileBuffer = new byte[fileBufferSize]; + infile.read(fileBuffer, fileBufferSize); infile.close(); - // draw logo bitmap - memcpy(bitmap, buffer, sizeof(bitmap)); - - _picture->setDimensions(w, h); + if (fileBufferSize < (width * height / 4)) + error("logos.bcg: unexpected end of file"); // Show BCG picture - for (int y = 0; y < h; y++) { - for (int x = xoffset; x < w; x++) { - color = colorBCG[(bitmap[y][x] & 0xf0) / 0x10][0]; // background - color2 = colorBCG[(bitmap[y][x] & 0xf0) / 0x10][1]; // background - color3 = colorBCG[ bitmap[y][x] & 0x0f][0]; // foreground - color4 = colorBCG[ bitmap[y][x] & 0x0f][1]; // foreground - - _picture->putPixel(x * 4 - xoffset, y, color); - _picture->putPixel(x * 4 + 1 - xoffset, y, color2); - _picture->putPixel(x * 4 + 2 - xoffset, y, color3); - _picture->putPixel(x * 4 + 3 - xoffset, y, color4); - _picture->putPixel(x * 4 - xoffset, y + 1, color); - _picture->putPixel(x * 4 + 1 - xoffset, y + 1, color2); - _picture->putPixel(x * 4 + 2 - xoffset, y + 1, color3); - _picture->putPixel(x * 4 + 3 - xoffset, y + 1, color4); + // It's basically uncompressed CGA 4-color data (4 pixels per byte) + dataBuffer = fileBuffer; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + curByte = *dataBuffer++; + + color1 = BCGColorMapping[(curByte >> 6) & 0x03]; + color2 = BCGColorMapping[(curByte >> 4) & 0x03]; + color3 = BCGColorMapping[(curByte >> 2) & 0x03]; + color4 = BCGColorMapping[(curByte >> 0) & 0x03]; + + _gfx->putPixelOnDisplay(x * 4 + 0, y, color1); + _gfx->putPixelOnDisplay(x * 4 + 1, y, color2); + _gfx->putPixelOnDisplay(x * 4 + 2, y, color3); + _gfx->putPixelOnDisplay(x * 4 + 3, y, color4); } } - _picture->showPic(10, 10, w, h); + _gfx->copyDisplayRectToScreen(0, 0, DISPLAY_WIDTH, height); - delete[] buffer; -#endif + delete[] fileBuffer; } void MickeyEngine::animate() { @@ -1221,8 +1219,8 @@ void MickeyEngine::printStory() { waitAnyKey(); //Set back to black - _gfx->clearScreen(0); - _gfx->doUpdate(); + _gfx->clearDisplay(0); + _gfx->updateScreen(); drawRoom(); @@ -1399,8 +1397,8 @@ void MickeyEngine::inventory() { void MickeyEngine::intro() { // Draw Sierra logo - //drawLogo(); // Original does not even show this, so we skip it too - //waitAnyKey(); // Not in the original, but needed so that the logo is visible + drawLogo(); // Original does not even show this, so we skip it too + waitAnyKey(); // Not in the original, but needed so that the logo is visible // draw title picture _gameStateMickey.iRoom = IDI_MSA_PIC_TITLE; @@ -1448,14 +1446,14 @@ void MickeyEngine::intro() { playSound(IDI_MSA_SND_PRESS_BLUE); //Set screen to white - _gfx->clearScreen(15); - _gfx->doUpdate(); + _gfx->clearDisplay(15); + _gfx->updateScreen(); _system->delayMillis(IDI_MSA_ANIM_DELAY); //Set back to black - _gfx->clearScreen(0); - _gfx->doUpdate(); + _gfx->clearDisplay(0); + _gfx->updateScreen(); drawRoom(); printDesc(_gameStateMickey.iRoom); @@ -2201,7 +2199,7 @@ void MickeyEngine::waitAnyKey(bool anim) { Common::Event event; if (!anim) - _gfx->doUpdate(); + _gfx->updateScreen(); while (!shouldQuit()) { while (_system->getEventManager()->pollEvent(event)) { @@ -2219,10 +2217,9 @@ void MickeyEngine::waitAnyKey(bool anim) { if (anim) { animate(); - _gfx->doUpdate(); } - _system->updateScreen(); + _gfx->updateScreen(); _system->delayMillis(10); } } @@ -2300,7 +2297,7 @@ void MickeyEngine::init() { #endif - setflag(fSoundOn, true); // enable sound + setflag(VM_FLAG_SOUND_ON, true); // enable sound } Common::Error MickeyEngine::go() { diff --git a/engines/agi/preagi_troll.cpp b/engines/agi/preagi_troll.cpp index 2889407c85..6d82c62987 100644 --- a/engines/agi/preagi_troll.cpp +++ b/engines/agi/preagi_troll.cpp @@ -41,7 +41,7 @@ TrollEngine::~TrollEngine() { void TrollEngine::pressAnyKey(int col) { drawStr(24, col, kColorDefault, IDS_TRO_PRESSANYKEY); - _gfx->doUpdate(); + g_system->updateScreen(); getSelection(kSelAnyKey); } @@ -49,7 +49,7 @@ void TrollEngine::drawMenu(const char *szMenu, int iSel) { clearTextArea(); drawStr(21, 0, kColorDefault, szMenu); drawStr(22 + iSel, 0, kColorDefault, " *"); - _gfx->doUpdate(); + g_system->updateScreen(); } bool TrollEngine::getMenuSel(const char *szMenu, int *iSel, int nSel) { @@ -155,8 +155,8 @@ void TrollEngine::drawPic(int iPic, bool f3IsCont, bool clr, bool troll) { _picture->drawPicture(); - _picture->showPic(); - _gfx->doUpdate(); + _picture->showPic(); // TODO: *HAVE* to add coordinates + height/width!! + g_system->updateScreen(); } // Game Logic @@ -223,11 +223,11 @@ void TrollEngine::waitAnyKeyIntro() { // fall through case 0: drawStr(22, 3, kColorDefault, IDS_TRO_INTRO_2); - _gfx->doUpdate(); + g_system->updateScreen(); break; case 100: drawStr(22, 3, kColorDefault, IDS_TRO_INTRO_3); - _gfx->doUpdate(); + g_system->updateScreen(); break; } @@ -262,7 +262,7 @@ void TrollEngine::credits() { drawStr(17, 7, 12, IDS_TRO_CREDITS_5); drawStr(19, 2, 14, IDS_TRO_CREDITS_6); - _gfx->doUpdate(); + g_system->updateScreen(); pressAnyKey(); } @@ -288,11 +288,11 @@ void TrollEngine::tutorial() { switch (iSel) { case IDI_TRO_SEL_OPTION_1: clearScreen(0x22, false); - _gfx->doUpdate(); + g_system->updateScreen(); break; case IDI_TRO_SEL_OPTION_2: clearScreen(0x00, false); - _gfx->doUpdate(); + g_system->updateScreen(); break; case IDI_TRO_SEL_OPTION_3: done = true; @@ -304,7 +304,7 @@ void TrollEngine::tutorial() { clearScreen(0x4F); drawStr(7, 4, kColorDefault, IDS_TRO_TUTORIAL_5); drawStr(9, 4, kColorDefault, IDS_TRO_TUTORIAL_6); - _gfx->doUpdate(); + g_system->updateScreen(); if (!getSelection(kSelYesNo)) break; @@ -314,37 +314,37 @@ void TrollEngine::tutorial() { clearScreen(0x5F); drawStr(4, 1, kColorDefault, IDS_TRO_TUTORIAL_7); drawStr(5, 1, kColorDefault, IDS_TRO_TUTORIAL_8); - _gfx->doUpdate(); + g_system->updateScreen(); pressAnyKey(); clearScreen(0x2F); drawStr(6, 1, kColorDefault, IDS_TRO_TUTORIAL_9); - _gfx->doUpdate(); + g_system->updateScreen(); pressAnyKey(); clearScreen(0x19); drawStr(7, 1, kColorDefault, IDS_TRO_TUTORIAL_10); drawStr(8, 1, kColorDefault, IDS_TRO_TUTORIAL_11); - _gfx->doUpdate(); + g_system->updateScreen(); pressAnyKey(); clearScreen(0x6E); drawStr(9, 1, kColorDefault, IDS_TRO_TUTORIAL_12); drawStr(10, 1, kColorDefault, IDS_TRO_TUTORIAL_13); - _gfx->doUpdate(); + g_system->updateScreen(); pressAnyKey(); clearScreen(0x4C); drawStr(11, 1, kColorDefault, IDS_TRO_TUTORIAL_14); drawStr(12, 1, kColorDefault, IDS_TRO_TUTORIAL_15); - _gfx->doUpdate(); + g_system->updateScreen(); pressAnyKey(); clearScreen(0x5D); drawStr(13, 1, kColorDefault, IDS_TRO_TUTORIAL_16); drawStr(14, 1, kColorDefault, IDS_TRO_TUTORIAL_17); drawStr(15, 1, kColorDefault, IDS_TRO_TUTORIAL_18); - _gfx->doUpdate(); + g_system->updateScreen(); pressAnyKey(); // show treasures @@ -353,7 +353,7 @@ void TrollEngine::tutorial() { for (int i = 0; i < IDI_TRO_MAX_TREASURE; i++) drawStr(19 - i, 11, kColorDefault, _items[i].name); - _gfx->doUpdate(); + g_system->updateScreen(); pressAnyKey(); } @@ -363,7 +363,7 @@ void TrollEngine::intro() { clearScreen(0x2F); drawStr(9, 10, kColorDefault, IDS_TRO_INTRO_0); drawStr(14, 15, kColorDefault, IDS_TRO_INTRO_1); - _gfx->doUpdate(); + g_system->updateScreen(); _system->delayMillis(3200); CursorMan.showMouse(true); @@ -371,7 +371,7 @@ void TrollEngine::intro() { // Draw logo setDefaultTextColor(0x0f); drawPic(45, false, true); - _gfx->doUpdate(); + g_system->updateScreen(); // wait for keypress and alternate message waitAnyKeyIntro(); @@ -379,7 +379,7 @@ void TrollEngine::intro() { // have you played this game before? drawStr(22, 3, kColorDefault, IDS_TRO_INTRO_4); drawStr(23, 6, kColorDefault, IDS_TRO_INTRO_5); - _gfx->doUpdate(); + g_system->updateScreen(); if (!getSelection(kSelYesNo)) tutorial(); @@ -411,7 +411,7 @@ void TrollEngine::gameOver() { sprintf(szMoves, IDS_TRO_GAMEOVER_0, _moves); drawStr(21, 1, kColorDefault, szMoves); drawStr(22, 1, kColorDefault, IDS_TRO_GAMEOVER_1); - _gfx->doUpdate(); + g_system->updateScreen(); pressAnyKey(); } @@ -443,7 +443,7 @@ int TrollEngine::drawRoom(char *menu) { } drawPic(_currentRoom, contFlag, true); - _gfx->doUpdate(); + g_system->updateScreen(); if (_currentRoom == 42) { drawPic(44, false, false); // don't clear @@ -454,7 +454,7 @@ int TrollEngine::drawRoom(char *menu) { } } - _gfx->doUpdate(); + g_system->updateScreen(); char tmp[10]; strncat(menu, (char *)_gameData + _locMessagesIdx[_currentRoom], 39); @@ -498,7 +498,7 @@ void TrollEngine::pickupTreasure(int treasureId) { if (_currentRoom != 24) { clearTextArea(); drawPic(_currentRoom, false, true); - _gfx->doUpdate(); + g_system->updateScreen(); } printUserMessage(treasureId + 16); diff --git a/engines/agi/preagi_winnie.cpp b/engines/agi/preagi_winnie.cpp index a91ad24fc6..596f417140 100644 --- a/engines/agi/preagi_winnie.cpp +++ b/engines/agi/preagi_winnie.cpp @@ -192,16 +192,16 @@ void WinnieEngine::randomize() { void WinnieEngine::intro() { drawPic(IDS_WTP_FILE_LOGO); printStr(IDS_WTP_INTRO_0); - _gfx->doUpdate(); + g_system->updateScreen(); _system->delayMillis(0x640); if (getPlatform() == Common::kPlatformAmiga) - _gfx->clearScreen(0); + _gfx->clearDisplay(0); drawPic(IDS_WTP_FILE_TITLE); printStr(IDS_WTP_INTRO_1); - _gfx->doUpdate(); + g_system->updateScreen(); _system->delayMillis(0x640); if (!playSound(IDI_WTP_SND_POOH_0)) @@ -452,7 +452,7 @@ int WinnieEngine::parser(int pc, int index, uint8 *buffer) { if (iBlock == 1) return IDI_WTP_PAR_OK; - _gfx->doUpdate(); + g_system->updateScreen(); } return IDI_WTP_PAR_OK; @@ -477,7 +477,7 @@ void WinnieEngine::inventory() { Common::String missing = Common::String::format(IDS_WTP_INVENTORY_1, _gameStateWinnie.nObjMiss); drawStr(IDI_WTP_ROW_OPTION_4, IDI_WTP_COL_MENU, IDA_DEFAULT, missing.c_str()); - _gfx->doUpdate(); + g_system->updateScreen(); getSelection(kSelAnyKey); } @@ -755,7 +755,7 @@ void WinnieEngine::drawMenu(char *szMenu, int iSel, int fCanSel[]) { break; } drawStr(iRow, iCol - 1, IDA_DEFAULT, ">"); - _gfx->doUpdate(); + g_system->updateScreen(); } void WinnieEngine::incMenuSel(int *iSel, int fCanSel[]) { @@ -821,15 +821,16 @@ void WinnieEngine::getMenuSel(char *szMenu, int *iSel, int fCanSel[]) { // Change cursor if (fCanSel[IDI_WTP_SEL_NORTH] && hotspotNorth.contains(event.mouse.x, event.mouse.y)) { - _gfx->setCursorPalette(true); + //_gfx->setCursorPalette(true); + // ???? } else if (fCanSel[IDI_WTP_SEL_SOUTH] && hotspotSouth.contains(event.mouse.x, event.mouse.y)) { - _gfx->setCursorPalette(true); + //_gfx->setCursorPalette(true); } else if (fCanSel[IDI_WTP_SEL_WEST] && hotspotWest.contains(event.mouse.x, event.mouse.y)) { - _gfx->setCursorPalette(true); + //_gfx->setCursorPalette(true); } else if (fCanSel[IDI_WTP_SEL_EAST] && hotspotEast.contains(event.mouse.x, event.mouse.y)) { - _gfx->setCursorPalette(true); + //_gfx->setCursorPalette(true); } else { - _gfx->setCursorPalette(false); + //_gfx->setCursorPalette(false); } break; @@ -838,25 +839,30 @@ void WinnieEngine::getMenuSel(char *szMenu, int *iSel, int fCanSel[]) { if (fCanSel[IDI_WTP_SEL_NORTH] && hotspotNorth.contains(event.mouse.x, event.mouse.y)) { *iSel = IDI_WTP_SEL_NORTH; makeSel(iSel, fCanSel); - _gfx->setCursorPalette(false); + //_gfx->setCursorPalette(false); + // TODO??? return; } else if (fCanSel[IDI_WTP_SEL_SOUTH] && hotspotSouth.contains(event.mouse.x, event.mouse.y)) { *iSel = IDI_WTP_SEL_SOUTH; makeSel(iSel, fCanSel); - _gfx->setCursorPalette(false); + //_gfx->setCursorPalette(false); + // TODO??? return; } else if (fCanSel[IDI_WTP_SEL_WEST] && hotspotWest.contains(event.mouse.x, event.mouse.y)) { *iSel = IDI_WTP_SEL_WEST; makeSel(iSel, fCanSel); - _gfx->setCursorPalette(false); + //_gfx->setCursorPalette(false); + // TODO??? return; } else if (fCanSel[IDI_WTP_SEL_EAST] && hotspotEast.contains(event.mouse.x, event.mouse.y)) { *iSel = IDI_WTP_SEL_EAST; makeSel(iSel, fCanSel); - _gfx->setCursorPalette(false); + //_gfx->setCursorPalette(false); + // TODO??? return; } else { - _gfx->setCursorPalette(false); + //_gfx->setCursorPalette(false); + // TODO??? } switch (*iSel) { @@ -941,7 +947,7 @@ void WinnieEngine::getMenuSel(char *szMenu, int *iSel, int fCanSel[]) { break; case Common::KEYCODE_s: if (event.kbd.flags & Common::KBD_CTRL) { - flipflag(fSoundOn); + flipflag(VM_FLAG_SOUND_ON); } else { *iSel = IDI_WTP_SEL_SOUTH; makeSel(iSel, fCanSel); @@ -1016,7 +1022,7 @@ void WinnieEngine::gameLoop() { readRoom(_room, roomdata, hdr); drawRoomPic(); - _gfx->doUpdate(); + g_system->updateScreen(); decodePhase = 1; } @@ -1102,7 +1108,7 @@ void WinnieEngine::drawRoomPic() { int iObj = getObjInRoom(_room); // clear gfx screen - _gfx->clearScreen(0); + _gfx->clearDisplay(0); // read room picture readRoom(_room, buffer, roomhdr); @@ -1175,7 +1181,8 @@ void WinnieEngine::clrMenuSel(int *iSel, int fCanSel[]) { while (!fCanSel[*iSel]) { *iSel += 1; } - _gfx->setCursorPalette(false); + //_gfx->setCursorPalette(false); + // TODO??? } void WinnieEngine::printRoomStr(int iRoom, int iStr) { @@ -1335,7 +1342,7 @@ void WinnieEngine::init() { } _sound = new SoundMgr(this, _mixer); - setflag(fSoundOn, true); // enable sound + setflag(VM_FLAG_SOUND_ON, true); // enable sound memset(&_gameStateWinnie, 0, sizeof(_gameStateWinnie)); _gameStateWinnie.fSound = 1; diff --git a/engines/agi/saveload.cpp b/engines/agi/saveload.cpp index 41a7a943ff..b11927c542 100644 --- a/engines/agi/saveload.cpp +++ b/engines/agi/saveload.cpp @@ -38,11 +38,14 @@ #include "agi/agi.h" #include "agi/graphics.h" +#include "agi/text.h" #include "agi/sprite.h" #include "agi/keyboard.h" #include "agi/menu.h" +#include "agi/systemui.h" +#include "agi/words.h" -#define SAVEGAME_VERSION 6 +#define SAVEGAME_CURRENT_VERSION 7 // // Version 0 (Sarien): view table has 64 entries @@ -52,19 +55,23 @@ // Version 4 (ScummVM): added thumbnails and save creation date/time // Version 5 (ScummVM): Added game md5 // Version 6 (ScummVM): Added game played time +// Version 7 (ScummVM): Added controller key mappings +// required for some games for quick-loading from ScummVM main menu +// for games, that do not set all key mappings right at the start +// Added automatic save data (for command SetSimple) // namespace Agi { static const uint32 AGIflag = MKTAG('A','G','I',':'); -int AgiEngine::saveGame(const Common::String &fileName, const Common::String &description) { +int AgiEngine::saveGame(const Common::String &fileName, const Common::String &descriptionString) { char gameIDstring[8] = "gameIDX"; int i; Common::OutSaveFile *out; int result = errOK; - debugC(3, kDebugLevelMain | kDebugLevelSavegame, "AgiEngine::saveGame(%s, %s)", fileName.c_str(), description.c_str()); + debugC(3, kDebugLevelMain | kDebugLevelSavegame, "AgiEngine::saveGame(%s, %s)", fileName.c_str(), descriptionString.c_str()); if (!(out = _saveFileMan->openForSaving(fileName))) { warning("Can't create file '%s', game not saved", fileName.c_str()); return errBadFileOpen; @@ -73,10 +80,17 @@ int AgiEngine::saveGame(const Common::String &fileName, const Common::String &de } out->writeUint32BE(AGIflag); - out->write(description.c_str(), 31); - out->writeByte(SAVEGAME_VERSION); - debugC(5, kDebugLevelMain | kDebugLevelSavegame, "Writing save game version (%d)", SAVEGAME_VERSION); + // Write description of saved game, limited to SAVEDGAME_DESCRIPTION_LEN characters + terminating NUL + char description[SAVEDGAME_DESCRIPTION_LEN + 1]; + + memset(description, 0, sizeof(description)); + strncpy(description, descriptionString.c_str(), SAVEDGAME_DESCRIPTION_LEN); + assert(SAVEDGAME_DESCRIPTION_LEN + 1 == 31); // safety + out->write(description, 31); + + out->writeByte(SAVEGAME_CURRENT_VERSION); + debugC(5, kDebugLevelMain | kDebugLevelSavegame, "Writing save game version (%d)", SAVEGAME_CURRENT_VERSION); // Thumbnail Graphics::saveThumbnail(*out); @@ -116,37 +130,50 @@ int AgiEngine::saveGame(const Common::String &fileName, const Common::String &de out->writeByte(tmp[i]); } + // Version 7+: Save automatic saving state (set.simple opcode) + out->writeByte(_game.automaticSave); + out->write(_game.automaticSaveDescription, 31); + for (i = 0; i < MAX_FLAGS; i++) out->writeByte(_game.flags[i]); for (i = 0; i < MAX_VARS; i++) out->writeByte(_game.vars[i]); out->writeSint16BE((int8)_game.horizon); - out->writeSint16BE((int16)_game.lineStatus); - out->writeSint16BE((int16)_game.lineUserInput); - out->writeSint16BE((int16)_game.lineMinPrint); + out->writeSint16BE((int16)_text->statusRow_Get()); + out->writeSint16BE((int16)_text->promptRow_Get()); + out->writeSint16BE((int16)_text->getWindowRowMin()); out->writeSint16BE((int16)_game.inputMode); out->writeSint16BE((int16)_game.lognum); out->writeSint16BE((int16)_game.playerControl); out->writeSint16BE((int16)shouldQuit()); - out->writeSint16BE((int16)_game.statusLine); + if (_text->statusEnabled()) { + out->writeSint16BE(0x7FFF); + } else { + out->writeSint16BE(0); + } out->writeSint16BE((int16)_game.clockEnabled); out->writeSint16BE((int16)_game.exitAllLogics); out->writeSint16BE((int16)_game.pictureShown); out->writeSint16BE((int16)_game.hasPrompt); out->writeSint16BE((int16)_game.gameFlags); - out->writeSint16BE(_game.inputEnabled); + if (_text->promptIsEnabled()) { + out->writeSint16BE(0x7FFF); + } else { + out->writeSint16BE(0); + } - for (i = 0; i < _HEIGHT; i++) - out->writeByte(_game.priTable[i]); + // TODO: save if priority table was modified + for (i = 0; i < SCRIPT_HEIGHT; i++) + out->writeByte(_gfx->priorityFromY(i)); out->writeSint16BE((int16)_game.gfxMode); - out->writeByte(_game.cursorChar); - out->writeSint16BE((int16)_game.colorFg); - out->writeSint16BE((int16)_game.colorBg); + out->writeByte(_text->inputGetCursorChar()); + out->writeSint16BE((int16)_text->charAttrib_GetForeground()); + out->writeSint16BE((int16)_text->charAttrib_GetBackground()); // game.hires // game.sbuf @@ -157,21 +184,29 @@ int AgiEngine::saveGame(const Common::String &fileName, const Common::String &de for (i = 0; i < (int16)_game.numObjects; i++) out->writeSint16BE((int16)objectGetLocation(i)); + // Version 7+: save controller key mappings + // required for games, that do not set all key mappings right at the start + // when quick restoring is used from ScummVM menu, only 1 cycle is executed + for (i = 0; i < MAX_CONTROLLER_KEYMAPPINGS; i++) { + out->writeUint16BE(_game.controllerKeyMapping[i].keycode); + out->writeByte(_game.controllerKeyMapping[i].controllerSlot); + } + // game.ev_keyp for (i = 0; i < MAX_STRINGS; i++) out->write(_game.strings[i], MAX_STRINGLEN); // record info about loaded resources - for (i = 0; i < MAX_DIRS; i++) { + for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) { out->writeByte(_game.dirLogic[i].flags); out->writeSint16BE((int16)_game.logics[i].sIP); out->writeSint16BE((int16)_game.logics[i].cIP); } - for (i = 0; i < MAX_DIRS; i++) + for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) out->writeByte(_game.dirPic[i].flags); - for (i = 0; i < MAX_DIRS; i++) + for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) out->writeByte(_game.dirView[i].flags); - for (i = 0; i < MAX_DIRS; i++) + for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) out->writeByte(_game.dirSound[i].flags); // game.pictures @@ -179,51 +214,77 @@ int AgiEngine::saveGame(const Common::String &fileName, const Common::String &de // game.views // game.sounds - for (i = 0; i < MAX_VIEWTABLE; i++) { - VtEntry *v = &_game.viewTable[i]; + for (i = 0; i < SCREENOBJECTS_MAX; i++) { + ScreenObjEntry *screenObj = &_game.screenObjTable[i]; - out->writeByte(v->stepTime); - out->writeByte(v->stepTimeCount); - out->writeByte(v->entry); - out->writeSint16BE(v->xPos); - out->writeSint16BE(v->yPos); - out->writeByte(v->currentView); + out->writeByte(screenObj->stepTime); + out->writeByte(screenObj->stepTimeCount); + out->writeByte(screenObj->objectNr); + out->writeSint16BE(screenObj->xPos); + out->writeSint16BE(screenObj->yPos); + out->writeByte(screenObj->currentViewNr); // v->view_data - out->writeByte(v->currentLoop); - out->writeByte(v->numLoops); + out->writeByte(screenObj->currentLoopNr); + out->writeByte(screenObj->loopCount); // v->loop_data - out->writeByte(v->currentCel); - out->writeByte(v->numCels); + out->writeByte(screenObj->currentCelNr); + out->writeByte(screenObj->celCount); // v->cel_data // v->cel_data_2 - out->writeSint16BE(v->xPos2); - out->writeSint16BE(v->yPos2); + out->writeSint16BE(screenObj->xPos_prev); + out->writeSint16BE(screenObj->yPos_prev); // v->s - out->writeSint16BE(v->xSize); - out->writeSint16BE(v->ySize); - out->writeByte(v->stepSize); - out->writeByte(v->cycleTime); - out->writeByte(v->cycleTimeCount); - out->writeByte(v->direction); + out->writeSint16BE(screenObj->xSize); + out->writeSint16BE(screenObj->ySize); + out->writeByte(screenObj->stepSize); + out->writeByte(screenObj->cycleTime); + out->writeByte(screenObj->cycleTimeCount); + out->writeByte(screenObj->direction); - out->writeByte(v->motion); - out->writeByte(v->cycle); - out->writeByte(v->priority); + out->writeByte(screenObj->motionType); + out->writeByte(screenObj->cycle); + out->writeByte(screenObj->priority); - out->writeUint16BE(v->flags); + out->writeUint16BE(screenObj->flags); - out->writeByte(v->parm1); - out->writeByte(v->parm2); - out->writeByte(v->parm3); - out->writeByte(v->parm4); + // this was done so that saved games compatibility isn't broken + switch (screenObj->motionType) { + case kMotionNormal: + out->writeByte(0); + out->writeByte(0); + out->writeByte(0); + out->writeByte(0); + break; + case kMotionWander: + out->writeByte(screenObj->wander_count); + out->writeByte(0); + out->writeByte(0); + out->writeByte(0); + break; + case kMotionFollowEgo: + out->writeByte(screenObj->follow_stepSize); + out->writeByte(screenObj->follow_flag); + out->writeByte(screenObj->follow_count); + out->writeByte(0); + break; + case kMotionEgo: + case kMotionMoveObj: + out->writeByte((byte)screenObj->move_x); // problematic! int16 -> byte + out->writeByte((byte)screenObj->move_y); + out->writeByte(screenObj->move_stepSize); + out->writeByte(screenObj->move_flag); + break; + default: + error("unknown motion-type"); + } } // Save image stack @@ -249,7 +310,7 @@ int AgiEngine::saveGame(const Common::String &fileName, const Common::String &de warning("Can't write file '%s'. (Disk full?)", fileName.c_str()); result = errIOError; } else - debugC(1, kDebugLevelMain | kDebugLevelSavegame, "Saved game %s in file %s", description.c_str(), fileName.c_str()); + debugC(1, kDebugLevelMain | kDebugLevelSavegame, "Saved game %s in file %s", descriptionString.c_str(), fileName.c_str()); delete out; debugC(3, kDebugLevelMain | kDebugLevelSavegame, "Closed %s", fileName.c_str()); @@ -260,8 +321,10 @@ int AgiEngine::saveGame(const Common::String &fileName, const Common::String &de } int AgiEngine::loadGame(const Common::String &fileName, bool checkId) { - char description[31], saveVersion, loadId[8]; - int i, vtEntries = MAX_VIEWTABLE; + char description[SAVEDGAME_DESCRIPTION_LEN + 1]; + byte saveVersion = 0; + char loadId[8]; + int i, vtEntries = SCREENOBJECTS_MAX; uint8 t; int16 parm[7]; Common::InSaveFile *in; @@ -284,17 +347,28 @@ int AgiEngine::loadGame(const Common::String &fileName, bool checkId) { return errOK; } - in->read(description, 31); + assert(SAVEDGAME_DESCRIPTION_LEN + 1 == 31); // safety + in->read(description, 31); // skip description + // check, if there is a terminating NUL inside description + uint16 descriptionPos = 0; + while (description[descriptionPos]) { + descriptionPos++; + if (descriptionPos >= sizeof(description)) + error("saved game description is corrupt"); + } debugC(6, kDebugLevelMain | kDebugLevelSavegame, "Description is: %s", description); saveVersion = in->readByte(); if (saveVersion < 2) // is the save game pre-ScummVM? - warning("Old save game version (%d, current version is %d). Will try and read anyway, but don't be surprised if bad things happen", saveVersion, SAVEGAME_VERSION); + warning("Old save game version (%d, current version is %d). Will try and read anyway, but don't be surprised if bad things happen", saveVersion, SAVEGAME_CURRENT_VERSION); if (saveVersion < 3) warning("This save game contains no AGIPAL data, if the game is using the AGIPAL hack, it won't work correctly"); + if (saveVersion > SAVEGAME_CURRENT_VERSION) + error("Saved game was created with a newer version of ScummVM. Unable to load."); + if (saveVersion >= 4) { // We don't need the thumbnail here, so just read it and discard it Graphics::skipThumbnail(*in); @@ -348,53 +422,74 @@ int AgiEngine::loadGame(const Common::String &fileName, bool checkId) { } } + if (saveVersion >= 7) { + // Restore automatic saving state (set.simple opcode) + _game.automaticSave = in->readByte(); + in->read(_game.automaticSaveDescription, 31); + } else { + _game.automaticSave = false; + _game.automaticSaveDescription[0] = 0; + } + for (i = 0; i < MAX_FLAGS; i++) _game.flags[i] = in->readByte(); for (i = 0; i < MAX_VARS; i++) _game.vars[i] = in->readByte(); - setvar(vFreePages, 180); // Set amount of free memory to realistic value (Overwriting the just loaded value) + setVar(VM_VAR_FREE_PAGES, 180); // Set amount of free memory to realistic value (Overwriting the just loaded value) _game.horizon = in->readSint16BE(); - _game.lineStatus = in->readSint16BE(); - _game.lineUserInput = in->readSint16BE(); - _game.lineMinPrint = in->readSint16BE(); + _text->statusRow_Set(in->readSint16BE()); + _text->promptRow_Set(in->readSint16BE()); + _text->configureScreen(in->readSint16BE()); // These are never saved - _game.cursorPos = 0; - _game.inputBuffer[0] = 0; - _game.echoBuffer[0] = 0; + _text->promptReset(); + _game.keypress = 0; _game.inputMode = (InputMode)in->readSint16BE(); + if ((_game.inputMode != INPUTMODE_NORMAL) && (_game.inputMode != INPUTMODE_NONE)) { + // other input modes were removed + _game.inputMode = INPUTMODE_NORMAL; + } + _game.lognum = in->readSint16BE(); _game.playerControl = in->readSint16BE(); if (in->readSint16BE()) quitGame(); - _game.statusLine = in->readSint16BE(); + if (in->readSint16BE()) { + _text->statusEnable(); + } else { + _text->statusDisable(); + } _game.clockEnabled = in->readSint16BE(); _game.exitAllLogics = in->readSint16BE(); - _game.pictureShown = in->readSint16BE(); + in->readSint16BE(); // was _game.pictureShown + //_game.pictureShown = in->readSint16BE(); _game.hasPrompt = in->readSint16BE(); _game.gameFlags = in->readSint16BE(); - _game.inputEnabled = in->readSint16BE(); + if (in->readSint16BE()) { + _text->promptEnable(); + } else { + _text->promptDisable(); + } - for (i = 0; i < _HEIGHT; i++) - _game.priTable[i] = in->readByte(); + for (i = 0; i < SCRIPT_HEIGHT; i++) + _gfx->setPriority(i, in->readByte()); - if (_game.hasWindow) - closeWindow(); + _text->closeWindow(); _game.msgBoxTicks = 0; _game.block.active = false; - // game.window - fixed by close_window() - // game.has_window - fixed by close_window() _game.gfxMode = in->readSint16BE(); - _game.cursorChar = in->readByte(); - _game.colorFg = in->readSint16BE(); - _game.colorBg = in->readSint16BE(); + _text->inputSetCursorChar(in->readByte()); + + int16 textForeground = in->readSint16BE(); + int16 textBackground = in->readSint16BE(); + _text->charAttrib_Set(textForeground, textBackground); // game.hires - rebuilt from image stack // game.sbuf - rebuilt from image stack @@ -407,41 +502,49 @@ int AgiEngine::loadGame(const Common::String &fileName, bool checkId) { objectSetLocation(i, in->readSint16BE()); // Those are not serialized - for (i = 0; i < MAX_DIRS; i++) { + for (i = 0; i < MAX_CONTROLLERS; i++) { _game.controllerOccured[i] = false; } + if (saveVersion >= 7) { + // For old saves, we just keep the current controllers + for (i = 0; i < MAX_CONTROLLER_KEYMAPPINGS; i++) { + _game.controllerKeyMapping[i].keycode = in->readUint16BE(); + _game.controllerKeyMapping[i].controllerSlot = in->readByte(); + } + } + for (i = 0; i < MAX_STRINGS; i++) in->read(_game.strings[i], MAX_STRINGLEN); - for (i = 0; i < MAX_DIRS; i++) { + for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) { if (in->readByte() & RES_LOADED) - agiLoadResource(rLOGIC, i); + agiLoadResource(RESOURCETYPE_LOGIC, i); else - agiUnloadResource(rLOGIC, i); + agiUnloadResource(RESOURCETYPE_LOGIC, i); _game.logics[i].sIP = in->readSint16BE(); _game.logics[i].cIP = in->readSint16BE(); } - for (i = 0; i < MAX_DIRS; i++) { + for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) { if (in->readByte() & RES_LOADED) - agiLoadResource(rPICTURE, i); + agiLoadResource(RESOURCETYPE_PICTURE, i); else - agiUnloadResource(rPICTURE, i); + agiUnloadResource(RESOURCETYPE_PICTURE, i); } - for (i = 0; i < MAX_DIRS; i++) { + for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) { if (in->readByte() & RES_LOADED) - agiLoadResource(rVIEW, i); + agiLoadResource(RESOURCETYPE_VIEW, i); else - agiUnloadResource(rVIEW, i); + agiUnloadResource(RESOURCETYPE_VIEW, i); } - for (i = 0; i < MAX_DIRS; i++) { + for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) { if (in->readByte() & RES_LOADED) - agiLoadResource(rSOUND, i); + agiLoadResource(RESOURCETYPE_SOUND, i); else - agiUnloadResource(rSOUND, i); + agiUnloadResource(RESOURCETYPE_SOUND, i); } // game.pictures - loaded above @@ -450,78 +553,102 @@ int AgiEngine::loadGame(const Common::String &fileName, bool checkId) { // game.sounds - loaded above for (i = 0; i < vtEntries; i++) { - VtEntry *v = &_game.viewTable[i]; - - v->stepTime = in->readByte(); - v->stepTimeCount = in->readByte(); - v->entry = in->readByte(); - v->xPos = in->readSint16BE(); - v->yPos = in->readSint16BE(); - v->currentView = in->readByte(); - - // v->view_data - fixed below - - v->currentLoop = in->readByte(); - v->numLoops = in->readByte(); - - // v->loop_data - fixed below - - v->currentCel = in->readByte(); - v->numCels = in->readByte(); - - // v->cel_data - fixed below - // v->cel_data_2 - fixed below - - v->xPos2 = in->readSint16BE(); - v->yPos2 = in->readSint16BE(); - - // v->s - fixed below - - v->xSize = in->readSint16BE(); - v->ySize = in->readSint16BE(); - v->stepSize = in->readByte(); - v->cycleTime = in->readByte(); - v->cycleTimeCount = in->readByte(); - v->direction = in->readByte(); - - v->motion = (MotionType)in->readByte(); - v->cycle = (CycleType)in->readByte(); - v->priority = in->readByte(); - - v->flags = in->readUint16BE(); - - v->parm1 = in->readByte(); - v->parm2 = in->readByte(); - v->parm3 = in->readByte(); - v->parm4 = in->readByte(); + ScreenObjEntry *screenObj = &_game.screenObjTable[i]; + + screenObj->stepTime = in->readByte(); + screenObj->stepTimeCount = in->readByte(); + screenObj->objectNr = in->readByte(); + screenObj->xPos = in->readSint16BE(); + screenObj->yPos = in->readSint16BE(); + screenObj->currentViewNr = in->readByte(); + + // screenObj->view_data - fixed below + + screenObj->currentLoopNr = in->readByte(); + screenObj->loopCount = in->readByte(); + + // screenObj->loop_data - fixed below + + screenObj->currentCelNr = in->readByte(); + screenObj->celCount = in->readByte(); + + // screenObj->cel_data - fixed below + // screenObj->cel_data_2 - fixed below + + screenObj->xPos_prev = in->readSint16BE(); + screenObj->yPos_prev = in->readSint16BE(); + + // screenObj->s - fixed below + + screenObj->xSize = in->readSint16BE(); + screenObj->ySize = in->readSint16BE(); + screenObj->stepSize = in->readByte(); + screenObj->cycleTime = in->readByte(); + screenObj->cycleTimeCount = in->readByte(); + screenObj->direction = in->readByte(); + + screenObj->motionType = (MotionType)in->readByte(); + screenObj->cycle = (CycleType)in->readByte(); + screenObj->priority = in->readByte(); + + screenObj->flags = in->readUint16BE(); + + // this was done so that saved games compatibility isn't broken + switch (screenObj->motionType) { + case kMotionNormal: + in->readByte(); + in->readByte(); + in->readByte(); + in->readByte(); + break; + case kMotionWander: + screenObj->wander_count = in->readByte(); + in->readByte(); + in->readByte(); + in->readByte(); + break; + case kMotionFollowEgo: + screenObj->follow_stepSize = in->readByte(); + screenObj->follow_flag = in->readByte(); + screenObj->follow_count = in->readByte(); + in->readByte(); + break; + case kMotionEgo: + case kMotionMoveObj: + screenObj->move_x = in->readByte(); // problematic! int16 -> byte + screenObj->move_y = in->readByte(); + screenObj->move_stepSize = in->readByte(); + screenObj->move_flag = in->readByte(); + break; + default: + error("unknown motion-type"); + } } - for (i = vtEntries; i < MAX_VIEWTABLE; i++) { - memset(&_game.viewTable[i], 0, sizeof(VtEntry)); + for (i = vtEntries; i < SCREENOBJECTS_MAX; i++) { + memset(&_game.screenObjTable[i], 0, sizeof(ScreenObjEntry)); } - // Fix some pointers in viewtable + // Fix some pointers in screenObjTable - for (i = 0; i < MAX_VIEWTABLE; i++) { - VtEntry *v = &_game.viewTable[i]; + for (i = 0; i < SCREENOBJECTS_MAX; i++) { + ScreenObjEntry *screenObj = &_game.screenObjTable[i]; - if (_game.dirView[v->currentView].offset == _EMPTY) + if (_game.dirView[screenObj->currentViewNr].offset == _EMPTY) continue; - if (!(_game.dirView[v->currentView].flags & RES_LOADED)) - agiLoadResource(rVIEW, v->currentView); + if (!(_game.dirView[screenObj->currentViewNr].flags & RES_LOADED)) + agiLoadResource(RESOURCETYPE_VIEW, screenObj->currentViewNr); - setView(v, v->currentView); // Fix v->view_data - setLoop(v, v->currentLoop); // Fix v->loop_data - setCel(v, v->currentCel); // Fix v->cel_data - v->celData2 = v->celData; - v->s = NULL; // not sure if it is used... + setView(screenObj, screenObj->currentViewNr); // Fix v->view_data + setLoop(screenObj, screenObj->currentLoopNr); // Fix v->loop_data + setCel(screenObj, screenObj->currentCelNr); // Fix v->cel_data } - _sprites->eraseBoth(); + _sprites->eraseSprites(); + + _game.pictureShown = false; - // Clear input line - _gfx->clearScreen(0); - writeStatus(); + _gfx->clearDisplay(0, false); // clear display screen, but not copy it to actual screen for now b/c transition // Recreate background from saved image stack clearImageStack(); @@ -539,259 +666,26 @@ int AgiEngine::loadGame(const Common::String &fileName, bool checkId) { delete in; debugC(3, kDebugLevelMain | kDebugLevelSavegame, "Closed %s", fileName.c_str()); - setflag(fRestoreJustRan, true); + setflag(VM_FLAG_RESTORE_JUST_RAN, true); _game.hasPrompt = 0; // force input line repaint if necessary - cleanInput(); - - _sprites->eraseBoth(); - _sprites->blitBoth(); - _sprites->commitBoth(); - _picture->showPic(); - _gfx->doUpdate(); - - return errOK; -} - -#define NUM_SLOTS 100 -#define NUM_VISIBLE_SLOTS 12 - -Common::String AgiEngine::getSavegameFilename(int num) const { - Common::String saveLoadSlot = _targetName; - saveLoadSlot += Common::String::format(".%.3d", num); - return saveLoadSlot; -} - -void AgiEngine::getSavegameDescription(int num, char *buf, bool showEmpty) { - Common::InSaveFile *in; - Common::String fileName = getSavegameFilename(num); - - debugC(4, kDebugLevelMain | kDebugLevelSavegame, "Current game id is %s", _targetName.c_str()); - - if (!(in = _saveFileMan->openForLoading(fileName))) { - debugC(4, kDebugLevelMain | kDebugLevelSavegame, "File %s does not exist", fileName.c_str()); - - if (showEmpty) - strcpy(buf, " (empty slot)"); - else - *buf = 0; - } else { - debugC(4, kDebugLevelMain | kDebugLevelSavegame, "Successfully opened %s for reading", fileName.c_str()); - - uint32 type = in->readUint32BE(); - - if (type == AGIflag) { - debugC(6, kDebugLevelMain | kDebugLevelSavegame, "Has AGI flag, good start"); - in->read(buf, 31); - } else { - warning("This doesn't appear to be an AGI savegame"); - strcpy(buf, "(corrupt file)"); - } - - delete in; - } -} - -int AgiEngine::selectSlot() { - int i, key, active = 0; - int rc = -1; - int hm = 1, vm = 3; // box margins - int xmin, xmax, slotClicked; - char desc[NUM_VISIBLE_SLOTS][40]; - int textCenter, buttonLength, buttonX[2], buttonY; - const char *buttonText[] = { " OK ", "Cancel", NULL }; - - _noSaveLoadAllowed = true; - - for (i = 0; i < NUM_VISIBLE_SLOTS; i++) { - getSavegameDescription(_firstSlot + i, desc[i]); - } - - textCenter = GFX_WIDTH / CHAR_LINES / 2; - buttonLength = 6; - buttonX[0] = (textCenter - 3 * buttonLength / 2) * CHAR_COLS; - buttonX[1] = (textCenter + buttonLength / 2) * CHAR_COLS; - buttonY = (vm + 17) * CHAR_LINES; - - for (i = 0; i < 2; i++) - _gfx->drawCurrentStyleButton(buttonX[i], buttonY, buttonText[i], false, false, i == 0); - - AllowSyntheticEvents on(this); - int oldFirstSlot = _firstSlot + 1; - int oldActive = active + 1; - bool exitSelectSlot = false; - while (!exitSelectSlot && !(shouldQuit() || _restartGame)) { - int sbPos = 0; - - // Use the extreme scrollbar positions only if the extreme - // slots are in sight. (We have to calculate this even if we - // don't redraw the save slots, because it's also used for - // clicking in the scrollbar. - - if (_firstSlot == 0) - sbPos = 1; - else if (_firstSlot == NUM_SLOTS - NUM_VISIBLE_SLOTS) - sbPos = NUM_VISIBLE_SLOTS - 2; - else { - sbPos = 2 + (_firstSlot * (NUM_VISIBLE_SLOTS - 4)) / (NUM_SLOTS - NUM_VISIBLE_SLOTS - 1); - if (sbPos >= NUM_VISIBLE_SLOTS - 3) - sbPos = NUM_VISIBLE_SLOTS - 3; - } - - if (oldFirstSlot != _firstSlot || oldActive != active) { - char dstr[64]; - for (i = 0; i < NUM_VISIBLE_SLOTS; i++) { - sprintf(dstr, "[%2d. %-28.28s]", i + _firstSlot, desc[i]); - printText(dstr, 0, hm + 1, vm + 4 + i, - (40 - 2 * hm) - 1, i == active ? MSG_BOX_COLOR : MSG_BOX_TEXT, - i == active ? MSG_BOX_TEXT : MSG_BOX_COLOR); - } - - char upArrow[] = "^"; - char downArrow[] = "v"; - char scrollBar[] = " "; - - for (i = 1; i < NUM_VISIBLE_SLOTS - 1; i++) - printText(scrollBar, 35, hm + 1, vm + 4 + i, 1, MSG_BOX_COLOR, 7, true); - - printText(upArrow, 35, hm + 1, vm + 4, 1, 8, 7); - printText(downArrow, 35, hm + 1, vm + 4 + NUM_VISIBLE_SLOTS - 1, 1, 8, 7); - printText(scrollBar, 35, hm + 1, vm + 4 + sbPos, 1, MSG_BOX_COLOR, MSG_BOX_TEXT); - - oldActive = active; - oldFirstSlot = _firstSlot; - } - - pollTimer(); - key = doPollKeyboard(); - - // It may happen that somebody will open GMM while - // this dialog is open, and load a game - // We are processing it here, effectively jumping - // out of the dead loop - if (getflag(fRestoreJustRan)) { - rc = -2; - exitSelectSlot = true; - } + _words->clearEgoWords(); - if (!exitSelectSlot) { - switch (key) { - case KEY_ENTER: - rc = active; - Common::strlcpy(_game.strings[MAX_STRINGS], desc[i], MAX_STRINGLEN); - debugC(8, kDebugLevelMain | kDebugLevelInput, "Button pressed: %d", rc); - exitSelectSlot = true; - break; - case KEY_ESCAPE: - rc = -1; - exitSelectSlot = true; - break; - case BUTTON_LEFT: - if (_gfx->testButton(buttonX[0], buttonY, buttonText[0])) { - rc = active; - strncpy(_game.strings[MAX_STRINGS], desc[i], MAX_STRINGLEN); - debugC(8, kDebugLevelMain | kDebugLevelInput, "Button pressed: %d", rc); - exitSelectSlot = true; - } else if (_gfx->testButton(buttonX[1], buttonY, buttonText[1])) { - rc = -1; - exitSelectSlot = true; - } else { - slotClicked = ((int)_mouse.y - 1) / CHAR_COLS - (vm + 4); - xmin = (hm + 1) * CHAR_COLS; - xmax = xmin + CHAR_COLS * 34; - if ((int)_mouse.x >= xmin && (int)_mouse.x <= xmax) { - if (slotClicked >= 0 && slotClicked < NUM_VISIBLE_SLOTS) - active = slotClicked; - } - xmin = (hm + 36) * CHAR_COLS; - xmax = xmin + CHAR_COLS; - if ((int)_mouse.x >= xmin && (int)_mouse.x <= xmax) { - if (slotClicked >= 0 && slotClicked < NUM_VISIBLE_SLOTS) { - if (slotClicked == 0) - keyEnqueue(KEY_UP); - else if (slotClicked == NUM_VISIBLE_SLOTS - 1) - keyEnqueue(KEY_DOWN); - else if (slotClicked < sbPos) - keyEnqueue(KEY_UP_RIGHT); - else if (slotClicked > sbPos) - keyEnqueue(KEY_DOWN_RIGHT); - } - } - } - break; + // don't delay anything right after restoring a game + nonBlockingText_Forget(); - case KEY_DOWN: - active++; - if (active >= NUM_VISIBLE_SLOTS) { - if (_firstSlot + NUM_VISIBLE_SLOTS < NUM_SLOTS) { - _firstSlot++; - for (i = 1; i < NUM_VISIBLE_SLOTS; i++) - memcpy(desc[i - 1], desc[i], sizeof(desc[0])); - getSavegameDescription(_firstSlot + NUM_VISIBLE_SLOTS - 1, desc[NUM_VISIBLE_SLOTS - 1]); - } - active = NUM_VISIBLE_SLOTS - 1; - } - break; - case KEY_UP: - active--; - if (active < 0) { - active = 0; - if (_firstSlot > 0) { - _firstSlot--; - for (i = NUM_VISIBLE_SLOTS - 1; i > 0; i--) - memcpy(desc[i], desc[i - 1], sizeof(desc[0])); - getSavegameDescription(_firstSlot, desc[0]); - } - } - break; + _sprites->eraseSprites(); + _sprites->buildAllSpriteLists(); + _sprites->drawAllSpriteLists(); + _picture->showPicWithTransition(); + _game.pictureShown = true; + _text->statusDraw(); + _text->promptRedraw(); - // Page Up/Down and mouse wheel scrolling all leave 'active' - // unchanged so that a visible slot will remain selected. + // copy everything over (we should probably only copy over the remaining parts of the screen w/o play screen + _gfx->copyDisplayRectToScreen(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT); - case WHEEL_DOWN: - if (_firstSlot < NUM_SLOTS - NUM_VISIBLE_SLOTS) { - _firstSlot++; - for (i = 1; i < NUM_VISIBLE_SLOTS; i++) - memcpy(desc[i - 1], desc[i], sizeof(desc[0])); - getSavegameDescription(_firstSlot + NUM_VISIBLE_SLOTS - 1, desc[NUM_VISIBLE_SLOTS - 1]); - } - break; - case WHEEL_UP: - if (_firstSlot > 0) { - _firstSlot--; - for (i = NUM_VISIBLE_SLOTS - 1; i > 0; i--) - memcpy(desc[i], desc[i - 1], sizeof(desc[0])); - getSavegameDescription(_firstSlot, desc[0]); - } - break; - case KEY_DOWN_RIGHT: - // This is probably triggered by Page Down. - _firstSlot += NUM_VISIBLE_SLOTS; - if (_firstSlot > NUM_SLOTS - NUM_VISIBLE_SLOTS) { - _firstSlot = NUM_SLOTS - NUM_VISIBLE_SLOTS; - } - for (i = 0; i < NUM_VISIBLE_SLOTS; i++) - getSavegameDescription(_firstSlot + i, desc[i]); - break; - case KEY_UP_RIGHT: - // This is probably triggered by Page Up. - _firstSlot -= NUM_VISIBLE_SLOTS; - if (_firstSlot < 0) { - _firstSlot = 0; - } - for (i = 0; i < NUM_VISIBLE_SLOTS; i++) - getSavegameDescription(_firstSlot + i, desc[i]); - break; - } - } - _gfx->doUpdate(); - } - - closeWindow(); - - _noSaveLoadAllowed = false; - - return rc; + return errOK; } int AgiEngine::scummVMSaveLoadDialog(bool isSave) { @@ -834,7 +728,8 @@ int AgiEngine::doSave(int slot, const Common::String &desc) { // Make sure all graphics was blitted to screen. This fixes bug // #2960567: "AGI: Ego partly erased in Load/Save thumbnails" - _gfx->doUpdate(); + _gfx->updateScreen(); +// _gfx->doUpdate(); return saveGame(fileName, desc); } @@ -843,160 +738,213 @@ int AgiEngine::doLoad(int slot, bool showMessages) { Common::String fileName = getSavegameFilename(slot); debugC(8, kDebugLevelMain | kDebugLevelResources, "file is [%s]", fileName.c_str()); - _sprites->eraseBoth(); + _sprites->eraseSprites(); _sound->stopSound(); - closeWindow(); + _text->closeWindow(); int result = loadGame(fileName); if (result == errOK) { - if (showMessages) - messageBox("Game restored."); _game.exitAllLogics = 1; - _menu->enableAll(); + _menu->itemEnableAll(); } else { if (showMessages) - messageBox("Error restoring game."); + _text->messageBox("Error restoring game."); } return result; } -int AgiEngine::saveGameDialog() { - if (!ConfMan.getBool("originalsaveload")) - return scummVMSaveLoadDialog(true); +SavedGameSlotIdArray AgiEngine::getSavegameSlotIds() { + Common::StringArray filenames; + int16 numberPos = _targetName.size() + 1; + int16 slotId = 0; + SavedGameSlotIdArray slotIdArray; - char *desc; - const char *buttons[] = { "Do as I say!", "I regret", NULL }; - char dstr[200]; - int rc, slot = 0; - int hm, vm, hp, vp; - int w; - - hm = 1; - vm = 3; - hp = hm * CHAR_COLS; - vp = vm * CHAR_LINES; - w = (40 - 2 * hm) - 1; - - do { - drawWindow(hp, vp, GFX_WIDTH - hp, GFX_HEIGHT - vp); - printText("Select a slot in which you wish to\nsave the game:", - 0, hm + 1, vm + 1, w, MSG_BOX_TEXT, MSG_BOX_COLOR); - slot = selectSlot(); - if (slot + _firstSlot == 0) - messageBox("That slot is for Autosave only."); - else if (slot < 0) - return errOK; - } while (slot + _firstSlot == 0); - - drawWindow(hp, vp + 5 * CHAR_LINES, GFX_WIDTH - hp, - GFX_HEIGHT - vp - 9 * CHAR_LINES); - printText("Enter a description for this game:", - 0, hm + 1, vm + 6, w, MSG_BOX_TEXT, MSG_BOX_COLOR); - _gfx->drawRectangle(3 * CHAR_COLS, 11 * CHAR_LINES - 1, - 37 * CHAR_COLS, 12 * CHAR_LINES, MSG_BOX_TEXT); - _gfx->flushBlock(3 * CHAR_COLS, 11 * CHAR_LINES - 1, - 37 * CHAR_COLS, 12 * CHAR_LINES); - - // The description field of the save/restore dialog holds 32 characters - // but we use four of them for the slot number. The input field is a - // bit wider than that, so we don't have to worry about leaving space - // for the cursor. - - getString(2, 11, 28, MAX_STRINGS); - - // If we're saving over an old slot, show the old description. We can't - // access that buffer directly, so we have to feed the characters to - // the input handler one at a time. - - char name[40]; - int numChars; - - getSavegameDescription(_firstSlot + slot, name, false); - - for (numChars = 0; numChars < 28 && name[numChars]; numChars++) - handleGetstring(name[numChars]); - - _gfx->printCharacter(numChars + 3, 11, _game.cursorChar, MSG_BOX_COLOR, MSG_BOX_TEXT); - do { - mainCycle(); - } while (_game.inputMode == INPUT_GETSTRING); - closeWindow(); - - desc = _game.strings[MAX_STRINGS]; - sprintf(dstr, "Are you sure you want to save the game " - "described as:\n\n%s\n\nin slot %d?\n\n\n", desc, _firstSlot + slot); - - rc = selectionBox(dstr, buttons); - - if (rc != 0) { - messageBox("Game NOT saved."); - return errOK; + // search for saved game filenames... + filenames = _saveFileMan->listSavefiles(_targetName + ".###"); + + Common::StringArray::iterator it; + Common::StringArray::iterator end = filenames.end();; + + // convert to lower-case, just to be sure + for (it = filenames.begin(); it != end; it++) { + it->toLowercase(); } + // sort + Common::sort(filenames.begin(), filenames.end()); - int result = doSave(_firstSlot + slot, desc); + // now extract slot-Ids + for (it = filenames.begin(); it != end; it++) { + slotId = atoi(it->c_str() + numberPos); - if (result == errOK) - messageBox("Game saved."); - else - messageBox("Error saving game."); + slotIdArray.push_back(slotId); + } + return slotIdArray; +} - return result; +Common::String AgiEngine::getSavegameFilename(int16 slotId) const { + Common::String saveLoadSlot = _targetName; + saveLoadSlot += Common::String::format(".%.3d", slotId); + return saveLoadSlot; } -int AgiEngine::saveGameSimple() { - if (!ConfMan.getBool("originalsaveload")) - return scummVMSaveLoadDialog(true); +bool AgiEngine::getSavegameInformation(int16 slotId, Common::String &saveDescription, uint32 &saveDate, uint16 &saveTime, bool &saveIsValid) { + Common::InSaveFile *in; + Common::String fileName = getSavegameFilename(slotId); + char saveGameDescription[31]; + int16 curPos = 0; + byte saveVersion = 0; - Common::String fileName = getSavegameFilename(0); + saveDescription.clear(); + saveDate = 0; + saveTime = 0; + saveIsValid = false; - int result = saveGame(fileName, "Default savegame"); - if (result != errOK) - messageBox("Error saving game."); - return result; -} + debugC(4, kDebugLevelMain | kDebugLevelSavegame, "Current game id is %s", _targetName.c_str()); -int AgiEngine::loadGameDialog() { - if (!ConfMan.getBool("originalsaveload")) - return scummVMSaveLoadDialog(false); + if (!(in = _saveFileMan->openForLoading(fileName))) { + debugC(4, kDebugLevelMain | kDebugLevelSavegame, "File %s does not exist", fileName.c_str()); + return false; + + } else { + debugC(4, kDebugLevelMain | kDebugLevelSavegame, "Successfully opened %s for reading", fileName.c_str()); - int slot = 0; - int hm, vm, hp, vp; // box margins - int w; + uint32 type = in->readUint32BE(); - hm = 1; - vm = 3; - hp = hm * CHAR_COLS; - vp = vm * CHAR_LINES; - w = (40 - 2 * hm) - 1; + if (type != AGIflag) { + warning("This doesn't appear to be an AGI savegame"); + saveDescription += "[ScummVM: not an AGI save]"; + delete in; + return true; + } - _sprites->eraseBoth(); - _sound->stopSound(); + debugC(6, kDebugLevelMain | kDebugLevelSavegame, "Has AGI flag, good start"); + if (in->read(saveGameDescription, 31) != 31) { + warning("unexpected EOF"); + delete in; + saveDescription += "[ScummVM: invalid save]"; + return true; + } - drawWindow(hp, vp, GFX_WIDTH - hp, GFX_HEIGHT - vp); - printText("Select a game which you wish to\nrestore:", - 0, hm + 1, vm + 1, w, MSG_BOX_TEXT, MSG_BOX_COLOR); + for (curPos = 0; curPos < 31; curPos++) { + if (!saveGameDescription[curPos]) + break; + } + if (curPos >= 31) { + warning("corrupted description"); + delete in; + saveDescription += "[ScummVM: invalid save]"; + return true; + } - slot = selectSlot(); + saveVersion = in->readByte(); + if (saveVersion > SAVEGAME_CURRENT_VERSION) { + warning("save from a future ScummVM, not supported"); + delete in; + saveDescription += "[ScummVM: not supported]"; + return true; + } - if (slot < 0) { - if (slot == -1) // slot = -2 when GMM was launched - messageBox("Game NOT restored."); + if (saveVersion >= 4) { + // We don't need the thumbnail here, so just read it and discard it + Graphics::skipThumbnail(*in); - return errOK; + saveDate = in->readUint32BE(); + saveTime = in->readUint16BE(); + + // save date is DDMMYYYY, we need a proper format + byte saveDateDay = saveDate >> 24; + byte saveDateMonth = (saveDate >> 16) & 0xFF; + uint16 saveDateYear = saveDate & 0xFFFF; + + saveDate = (saveDateYear << 16) | (saveDateMonth << 8) | saveDateDay; + + } else { + saveDate = 0; + saveTime = 0; + } + + saveDescription += saveGameDescription; + saveIsValid = true; + + delete in; + return true; } +} - return doLoad(_firstSlot + slot, true); +bool AgiEngine::loadGameAutomatic() { + int16 automaticRestoreGameSlotId = 0; + + automaticRestoreGameSlotId = _systemUI->figureOutAutomaticRestoreGameSlot(_game.automaticSaveDescription); + if (automaticRestoreGameSlotId >= 0) { + if (doLoad(automaticRestoreGameSlotId, true) == errOK) { + return true; + } + } + return false; } -int AgiEngine::loadGameSimple() { +bool AgiEngine::loadGameDialog() { + int16 restoreGameSlotId = 0; + if (!ConfMan.getBool("originalsaveload")) return scummVMSaveLoadDialog(false); - else - return doLoad(0, true); + + restoreGameSlotId = _systemUI->askForRestoreGameSlot(); + if (restoreGameSlotId >= 0) { + if (doLoad(restoreGameSlotId, true) == errOK) { + return true; + } + } + return errOK; +} + +// Try to figure out either the slot, that is currently using the automatic saved game description +// or get a new slot. +// If we fail, return false, so that the regular saved game dialog is called +// Original AGI was limited to 12 saves, we are effectively limited to 100 saves at the moment. +// +// btw. this also means that entering an existant name in Mixed Up Mother Goose will effectively overwrite +// that saved game. This is also what original AGI did. +bool AgiEngine::saveGameAutomatic() { + int16 automaticSaveGameSlotId = 0; + + automaticSaveGameSlotId = _systemUI->figureOutAutomaticSaveGameSlot(_game.automaticSaveDescription); + if (automaticSaveGameSlotId >= 0) { + Common::String slotDescription(_game.automaticSaveDescription); + + // WORKAROUND: Remove window in case one is currently shown, otherwise it would get saved in the thumbnail + // Happens for Mixed Up Mother Goose. The scripts close the window after saving. + // Original interpreter obviously did not do this, but original interpreter also did not save thumbnails. + _text->closeWindow(); + + if (doSave(automaticSaveGameSlotId, slotDescription) == errOK) { + return true; + } + } + return false; } +bool AgiEngine::saveGameDialog() { + int16 saveGameSlotId = 0; + Common::String slotDescription; + + if (!ConfMan.getBool("originalsaveload")) + return scummVMSaveLoadDialog(true); + + saveGameSlotId = _systemUI->askForSaveGameSlot(); + if (saveGameSlotId >= 0) { + if (_systemUI->askForSaveGameDescription(saveGameSlotId, slotDescription)) { + if (doSave(saveGameSlotId, slotDescription) == errOK) { + return true; + } + } + } + return false; +} + + void AgiEngine::recordImageStackCall(uint8 type, int16 p1, int16 p2, int16 p3, int16 p4, int16 p5, int16 p6, int16 p7) { ImageStackElement pnew; @@ -1019,11 +967,11 @@ void AgiEngine::replayImageStackCall(uint8 type, int16 p1, int16 p2, int16 p3, switch (type) { case ADD_PIC: debugC(8, kDebugLevelMain, "--- decoding picture %d ---", p1); - agiLoadResource(rPICTURE, p1); + agiLoadResource(RESOURCETYPE_PICTURE, p1); _picture->decodePicture(p1, p2, p3 != 0); break; case ADD_VIEW: - agiLoadResource(rVIEW, p1); + agiLoadResource(RESOURCETYPE_VIEW, p1); _sprites->addToPic(p1, p2, p3, p4, p5, p6, p7); break; } @@ -1041,12 +989,12 @@ void AgiEngine::checkQuickLoad() { if (ConfMan.hasKey("save_slot")) { Common::String saveNameBuffer = getSavegameFilename(ConfMan.getInt("save_slot")); - _sprites->eraseBoth(); + _sprites->eraseSprites(); _sound->stopSound(); if (loadGame(saveNameBuffer, false) == errOK) { // Do not check game id _game.exitAllLogics = 1; - _menu->enableAll(); + _menu->itemEnableAll(); } } } @@ -1054,21 +1002,21 @@ void AgiEngine::checkQuickLoad() { Common::Error AgiEngine::loadGameState(int slot) { Common::String saveLoadSlot = getSavegameFilename(slot); - _sprites->eraseBoth(); + _sprites->eraseSprites(); _sound->stopSound(); if (loadGame(saveLoadSlot) == errOK) { _game.exitAllLogics = 1; - _menu->enableAll(); + _menu->itemEnableAll(); return Common::kNoError; } else { return Common::kUnknownError; } } -Common::Error AgiEngine::saveGameState(int slot, const Common::String &desc) { +Common::Error AgiEngine::saveGameState(int slot, const Common::String &description) { Common::String saveLoadSlot = getSavegameFilename(slot); - if (saveGame(saveLoadSlot, desc) == errOK) + if (saveGame(saveLoadSlot, description) == errOK) return Common::kNoError; else return Common::kUnknownError; diff --git a/engines/agi/sound_pcjr.cpp b/engines/agi/sound_pcjr.cpp index ea7a2789e0..d875fa0b97 100644 --- a/engines/agi/sound_pcjr.cpp +++ b/engines/agi/sound_pcjr.cpp @@ -236,7 +236,7 @@ int SoundGenPCJr::getNextNote_v2(int ch) { assert(ch < CHAN_MAX); - if (!_vm->getflag(fSoundOn)) + if (!_vm->getflag(VM_FLAG_SOUND_ON)) return -1; tpcm = &_tchannel[ch]; diff --git a/engines/agi/sound_sarien.cpp b/engines/agi/sound_sarien.cpp index 98479f3edc..d197f85832 100644 --- a/engines/agi/sound_sarien.cpp +++ b/engines/agi/sound_sarien.cpp @@ -161,7 +161,7 @@ void SoundGenSarien::stopNote(int i) { } void SoundGenSarien::playNote(int i, int freq, int vol) { - if (!_vm->getflag(fSoundOn)) + if (!_vm->getflag(VM_FLAG_SOUND_ON)) vol = 0; else if (vol && _vm->_soundemu == SOUND_EMU_PC) vol = 160; diff --git a/engines/agi/sprite.cpp b/engines/agi/sprite.cpp index 92f88d8fcb..1124aef742 100644 --- a/engines/agi/sprite.cpp +++ b/engines/agi/sprite.cpp @@ -23,530 +23,392 @@ #include "agi/agi.h" #include "agi/sprite.h" #include "agi/graphics.h" +#include "agi/text.h" namespace Agi { -/** - * Sprite structure. - * This structure holds information on visible and priority data of - * a rectangular area of the AGI screen. Sprites are chained in two - * circular lists, one for updating and other for non-updating sprites. - */ -struct Sprite { - VtEntry *v; /**< pointer to view table entry */ - int16 xPos; /**< x coordinate of the sprite */ - int16 yPos; /**< y coordinate of the sprite */ - int16 xSize; /**< width of the sprite */ - int16 ySize; /**< height of the sprite */ - uint8 *buffer; /**< buffer to store background data */ -}; - -/* - * Sprite pool replaces dynamic allocation - */ -#undef ALLOC_DEBUG - - -#define POOL_SIZE 68000 // Gold Rush mine room needs > 50000 - // Speeder bike challenge needs > 67000 - -void *SpritesMgr::poolAlloc(int size) { - uint8 *x; - - // Adjust size to sizeof(void *) boundary to prevent data misalignment - // errors. - const int alignPadding = sizeof(void *) - 1; - size = (size + alignPadding) & ~alignPadding; - - x = _poolTop; - _poolTop += size; - - if (_poolTop >= (uint8 *)_spritePool + POOL_SIZE) { - debugC(1, kDebugLevelMain | kDebugLevelResources, "not enough memory"); - _poolTop = x; - return NULL; - } - - return x; +SpritesMgr::SpritesMgr(AgiEngine *agi, GfxMgr *gfx) { + _vm = agi; + _gfx = gfx; } -// Note: it's critical that pool_release() is called in the exact -// reverse order of pool_alloc() -void SpritesMgr::poolRelease(void *s) { - _poolTop = (uint8 *)s; +SpritesMgr::~SpritesMgr() { + _spriteRegularList.clear(); + _spriteStaticList.clear(); } -/* - * Blitter functions - */ - -// Blit one pixel considering the priorities -void SpritesMgr::blitPixel(uint8 *p, uint8 *end, uint8 col, int spr, int width, int *hidden) { - int epr = 0, pr = 0; // effective and real priorities - - // CM: priority 15 overrides control lines and is ignored when - // tracking effective priority. This tweak is needed to fix - // Sarien bug #451768, and should not affect Sierra games because - // sprites shouldn't have priority 15 (like the AGI Mouse - // demo "mouse pointer") - // - // Update: this solution breaks other games, and can't be used. - - if (p >= end) - return; - - // Check if we're on a control line - if ((pr = *p & 0xf0) < 0x30) { - uint8 *p1; - // Yes, get effective priority going down - for (p1 = p; p1 < end && (epr = *p1 & 0xf0) < 0x30; p1 += width) - ; - if (p1 >= end) - epr = 0x40; - } else { - epr = pr; - } - - if (spr >= epr) { - // Keep control line information visible, but put our - // priority over water (0x30) surface - if (_vm->getFeatures() & (GF_AGI256 | GF_AGI256_2)) - *(p + FROM_SBUF16_TO_SBUF256_OFFSET) = col; // Write to 256 color buffer - else - *p = (pr < 0x30 ? pr : spr) | col; // Write to 16 color (+control line/priority info) buffer - - *hidden = false; - - // Except if our priority is 15, which should never happen - // (fixes Sarien bug #451768) - // - // Update: breaks other games, can't be used - // - // if (spr == 0xf0) - // *p = spr | col; +static bool sortSpriteHelper(const Sprite &entry1, const Sprite &entry2) { + if (entry1.sortOrder == entry2.sortOrder) { + // If sort-order is the same, we sort according to given order + // which makes this sort stable. + return entry1.givenOrderNr < entry2.givenOrderNr; } + return entry1.sortOrder < entry2.sortOrder; } +void SpritesMgr::buildRegularSpriteList() { + ScreenObjEntry *screenObj = NULL; + uint16 givenOrderNr = 0; -int SpritesMgr::blitCel(int x, int y, int spr, ViewCel *c, bool agi256_2) { - uint8 *p0, *p, *q = NULL, *end; - int i, j, t, m, col; - int hidden = true; - - // Fixes Sarien bug #477841 (crash in PQ1 map C4 when y == -2) - if (y < 0) - y = 0; - if (x < 0) - x = 0; - if (y >= _HEIGHT) - y = _HEIGHT - 1; - if (x >= _WIDTH) - x = _WIDTH - 1; - - q = c->data; - t = c->transparency; - m = c->mirror; - spr <<= 4; - p0 = &_vm->_game.sbuf16c[x + y * _WIDTH + m * (c->width - 1)]; - - end = _vm->_game.sbuf16c + _WIDTH * _HEIGHT; - - for (i = 0; i < c->height; i++) { - p = p0; - while (*q) { - col = agi256_2 ? *q : (*q & 0xf0) >> 4; // Uses whole byte for color info with AGI256-2 - for (j = agi256_2 ? 1 : *q & 0x0f; j; j--, p += 1 - 2 * m) { // No RLE with AGI256-2 - if (col != t) { - blitPixel(p, end, col, spr, _WIDTH, &hidden); - } - } - q++; + freeList(_spriteRegularList); + for (screenObj = _vm->_game.screenObjTable; screenObj < &_vm->_game.screenObjTable[SCREENOBJECTS_MAX]; screenObj++) { + if ((screenObj->flags & (fAnimated | fUpdate | fDrawn)) == (fAnimated | fUpdate | fDrawn)) { + buildSpriteListAdd(givenOrderNr, screenObj, _spriteRegularList); + givenOrderNr++; } - p0 += _WIDTH; - q++; } - return hidden; + // Now sort this list + Common::sort(_spriteRegularList.begin(), _spriteRegularList.end(), sortSpriteHelper); +// warning("buildRegular: %d", _spriteRegularList.size()); } -void SpritesMgr::objsSaveArea(Sprite *s) { - int y; - int16 xPos = s->xPos, yPos = s->yPos; - int16 xSize = s->xSize, ySize = s->ySize; - uint8 *p0, *q; +void SpritesMgr::buildStaticSpriteList() { + ScreenObjEntry *screenObj = NULL; + uint16 givenOrderNr = 0; - if (xPos + xSize > _WIDTH) - xSize = _WIDTH - xPos; - - if (xPos < 0) { - xSize += xPos; - xPos = 0; + freeList(_spriteStaticList); + for (screenObj = _vm->_game.screenObjTable; screenObj < &_vm->_game.screenObjTable[SCREENOBJECTS_MAX]; screenObj++) { + if ((screenObj->flags & (fAnimated | fUpdate | fDrawn)) == (fAnimated | fDrawn)) { // DIFFERENCE IN HERE! + buildSpriteListAdd(givenOrderNr, screenObj, _spriteStaticList); + givenOrderNr++; + } } - if (yPos + ySize > _HEIGHT) - ySize = _HEIGHT - yPos; + // Now sort this list + Common::sort(_spriteStaticList.begin(), _spriteStaticList.end(), sortSpriteHelper); +} - if (yPos < 0) { - ySize += yPos; - yPos = 0; - } +void SpritesMgr::buildAllSpriteLists() { + buildStaticSpriteList(); + buildRegularSpriteList(); +} + +void SpritesMgr::buildSpriteListAdd(uint16 givenOrderNr, ScreenObjEntry *screenObj, SpriteList &spriteList) { + Sprite spriteEntry; - if (xSize <= 0 || ySize <= 0) + // Check, if screen object points to currently loaded view, if not don't add it + if (!(_vm->_game.dirView[screenObj->currentViewNr].flags & RES_LOADED)) return; - p0 = &_vm->_game.sbuf[xPos + yPos * _WIDTH]; - q = s->buffer; - for (y = 0; y < ySize; y++) { - memcpy(q, p0, xSize); - q += xSize; - p0 += _WIDTH; + spriteEntry.givenOrderNr = givenOrderNr; +// warning("sprite add objNr %d", screenObjPtr->objectNr); + if (screenObj->flags & fFixedPriority) { + spriteEntry.sortOrder = _gfx->priorityToY(screenObj->priority); +// warning(" - priorityToY (fixed) %d -> %d", screenObj->priority, spriteEntry.sortOrder); + } else { + spriteEntry.sortOrder = screenObj->yPos; +// warning(" - Ypos %d -> %d", screenObjPtr->yPos, spriteEntry.sortOrder); } -} -void SpritesMgr::objsRestoreArea(Sprite *s) { - int y, offset; - int16 xPos = s->xPos, yPos = s->yPos; - int16 xSize = s->xSize, ySize = s->ySize; - uint8 *q; - uint32 pos0; + spriteEntry.screenObjPtr = screenObj; + spriteEntry.xPos = screenObj->xPos; + spriteEntry.yPos = (screenObj->yPos) - (screenObj->ySize) + 1; + spriteEntry.xSize = screenObj->xSize; + spriteEntry.ySize = screenObj->ySize; +// warning("list-add: %d, %d, original yPos: %d, ySize: %d", spriteEntry.xPos, spriteEntry.yPos, screenObj->yPos, screenObj->ySize); + spriteEntry.backgroundBuffer = (uint8 *)malloc(spriteEntry.xSize * spriteEntry.ySize * 2); // for visual + priority data + assert(spriteEntry.backgroundBuffer); + spriteList.push_back(spriteEntry); +} - if (xPos + xSize > _WIDTH) - xSize = _WIDTH - xPos; +void SpritesMgr::freeList(SpriteList &spriteList) { + SpriteList::iterator iter; + for (iter = spriteList.reverse_begin(); iter != spriteList.end(); iter--) { + Sprite &sprite = *iter; - if (xPos < 0) { - xSize += xPos; - xPos = 0; + free(sprite.backgroundBuffer); } + spriteList.clear(); +} - if (yPos + ySize > _HEIGHT) - ySize = _HEIGHT - yPos; +void SpritesMgr::freeRegularSprites() { + freeList(_spriteRegularList); +} - if (yPos < 0) { - ySize += yPos; - yPos = 0; - } +void SpritesMgr::freeStaticSprites() { + freeList(_spriteStaticList); +} - if (xSize <= 0 || ySize <= 0) - return; +void SpritesMgr::freeAllSprites() { + freeList(_spriteRegularList); + freeList(_spriteStaticList); +} - pos0 = xPos + yPos * _WIDTH; - q = s->buffer; - offset = _vm->_game.lineMinPrint * CHAR_LINES; - for (y = 0; y < ySize; y++) { - memcpy(&_vm->_game.sbuf[pos0], q, xSize); - _gfx->putPixelsA(xPos, yPos + y + offset, xSize, &_vm->_game.sbuf16c[pos0]); - q += xSize; - pos0 += _WIDTH; +void SpritesMgr::eraseSprites(SpriteList &spriteList) { + SpriteList::iterator iter; +// warning("eraseSprites - count %d", spriteList.size()); + for (iter = spriteList.reverse_begin(); iter != spriteList.end(); iter--) { + Sprite &sprite = *iter; + _gfx->block_restore(sprite.xPos, sprite.yPos, sprite.xSize, sprite.ySize, sprite.backgroundBuffer); } - // WORKAROUND (see ScummVM bug #1945716) - // When set.view command is called, current code cannot detect this situation while updating - // Thus we force removal of the old sprite - if (s->v && s->v->viewReplaced) { - commitBlock(xPos, yPos, xPos + xSize, yPos + ySize); - s->v->viewReplaced = false; - } + freeList(spriteList); } - /** - * Condition to determine whether a sprite will be in the 'updating' list. + * Erase updating sprites. + * This function follows the list of all updating sprites and restores + * the visible and priority data of their background buffers back to + * the AGI screen. + * + * @see erase_nonupd_sprites() + * @see erase_both() */ -bool SpritesMgr::testUpdating(VtEntry *v, AgiEngine *agi) { - // Sanity check (see Sarien bug #779302) - if (~agi->_game.dirView[v->currentView].flags & RES_LOADED) - return false; - - return (v->flags & (fAnimated | fUpdate | fDrawn)) == (fAnimated | fUpdate | fDrawn); +void SpritesMgr::eraseRegularSprites() { + eraseSprites(_spriteRegularList); } -/** - * Condition to determine whether a sprite will be in the 'non-updating' list. - */ -bool SpritesMgr::testNotUpdating(VtEntry *v, AgiEngine *vm) { - // Sanity check (see Sarien bug #779302) - if (~vm->_game.dirView[v->currentView].flags & RES_LOADED) - return false; +void SpritesMgr::eraseStaticSprites() { + eraseSprites(_spriteStaticList); +} - return (v->flags & (fAnimated | fUpdate | fDrawn)) == (fAnimated | fDrawn); +void SpritesMgr::eraseSprites() { + eraseSprites(_spriteRegularList); + eraseSprites(_spriteStaticList); } /** - * Convert sprite priority to y value. + * Draw all sprites in the given list. */ -int SpritesMgr::prioToY(int p) { - int i; +void SpritesMgr::drawSprites(SpriteList &spriteList) { + SpriteList::iterator iter; +// warning("drawSprites"); - if (p == 0) - return -1; + for (iter = spriteList.begin(); iter != spriteList.end(); ++iter) { + Sprite &sprite = *iter; + ScreenObjEntry *screenObj = sprite.screenObjPtr; - for (i = 167; i >= 0; i--) { - if (_vm->_game.priTable[i] < p) - return i; + _gfx->block_save(sprite.xPos, sprite.yPos, sprite.xSize, sprite.ySize, sprite.backgroundBuffer); + //debugC(8, kDebugLevelSprites, "drawSprites(): s->v->entry = %d (prio %d)", s->viewPtr->entry, s->viewPtr->priority); +// warning("sprite %d (view %d), priority %d, sort %d, givenOrder %d", screenObj->objectNr, screenObj->currentView, screenObj->priority, sprite.sortOrder, sprite.givenOrderNr); + drawCel(screenObj); } - - return -1; // (p - 5) * 12 + 48; } /** - * Create and initialize a new sprite structure. - */ -Sprite *SpritesMgr::newSprite(VtEntry *v) { - Sprite *s; - s = (Sprite *)poolAlloc(sizeof(Sprite)); - if (s == NULL) - return NULL; - - s->v = v; // link sprite to associated view table entry - s->xPos = v->xPos; - s->yPos = v->yPos - v->ySize + 1; - s->xSize = v->xSize; - s->ySize = v->ySize; - s->buffer = (uint8 *)poolAlloc(s->xSize * s->ySize); - v->s = s; // link view table entry to this sprite - - return s; -} - -/** - * Insert sprite in the specified sprite list. + * Blit updating sprites. + * This function follows the list of all updating sprites and blits + * them on the AGI screen. + * + * @see blit_nonupd_sprites() + * @see blit_both() */ -void SpritesMgr::sprAddlist(SpriteList &l, VtEntry *v) { - Sprite *s = newSprite(v); - l.push_back(s); -} +void SpritesMgr::drawRegularSpriteList() { + debugC(7, kDebugLevelSprites, "drawRegularSpriteList()"); + drawSprites(_spriteRegularList); +} + +void SpritesMgr::drawStaticSpriteList() { + //debugC(7, kDebugLevelSprites, "drawRegularSpriteList()"); + drawSprites(_spriteStaticList); +} + +void SpritesMgr::drawAllSpriteLists() { + drawSprites(_spriteStaticList); + drawSprites(_spriteRegularList); +} + +void SpritesMgr::drawCel(ScreenObjEntry *screenObj) { + int16 curX = screenObj->xPos; + int16 baseX = screenObj->xPos; + int16 curY = screenObj->yPos; + AgiViewCel *celPtr = screenObj->celData; + byte *celDataPtr = celPtr->rawBitmap; + uint8 remainingCelHeight = celPtr->height; + uint8 celWidth = celPtr->width; + byte celClearKey = celPtr->clearKey; + byte viewPriority = screenObj->priority; + byte screenPriority = 0; + byte curColor = 0; + byte isViewHidden = true; + + // Adjust vertical position, given yPos is lower left, but we need upper left + curY = curY - celPtr->height + 1; + + while (remainingCelHeight) { + for (int16 loopX = 0; loopX < celWidth; loopX++) { + curColor = *celDataPtr++; + + if (curColor != celClearKey) { + screenPriority = _gfx->getPriority(curX, curY); + if (screenPriority <= 2) { + // control data found + if (_gfx->checkControlPixel(curX, curY, viewPriority)) { + _gfx->putPixel(curX, curY, GFX_SCREEN_MASK_VISUAL, curColor, 0); + isViewHidden = false; + } + } else if (screenPriority <= viewPriority) { + _gfx->putPixel(curX, curY, GFX_SCREEN_MASK_ALL, curColor, viewPriority); + isViewHidden = false; + } -/** - * Sort sprites from lower y values to build a sprite list. - */ -void SpritesMgr::buildList(SpriteList &l, bool (*test)(VtEntry *, AgiEngine *)) { - int i, j, k; - VtEntry *v; - VtEntry *entry[0x100]; - int yVal[0x100]; - int minY = 0xff, minIndex = 0; - - // fill the arrays with all sprites that satisfy the 'test' - // condition and their y values - i = 0; - for (v = _vm->_game.viewTable; v < &_vm->_game.viewTable[MAX_VIEWTABLE]; v++) { - if ((*test)(v, _vm)) { - entry[i] = v; - yVal[i] = v->flags & fFixedPriority ? prioToY(v->priority) : v->yPos; - i++; + } + curX++; } + + // go to next vertical position + remainingCelHeight--; + curX = baseX; + curY++; } - debugC(5, kDebugLevelSprites, "buildList() --> entries %d", i); + if (screenObj->objectNr == 0) { // if ego, update if ego is visible at the moment + _vm->setflag(VM_FLAG_EGO_INVISIBLE, isViewHidden); + } +} - // now look for the smallest y value in the array and put that - // sprite in the list - for (j = 0; j < i; j++) { - minY = 0xff; - for (k = 0; k < i; k++) { - if (yVal[k] < minY) { - minIndex = k; - minY = yVal[k]; - } - } +void SpritesMgr::showSprite(ScreenObjEntry *screenObj) { + int16 x = 0; + int16 y = 0; + int16 width = 0; + int16 height = 0; - yVal[minIndex] = 0xff; - sprAddlist(l, entry[minIndex]); - } -} + int16 view_height_prev = 0; + int16 view_width_prev = 0; -/** - * Build list of updating sprites. - */ -void SpritesMgr::buildUpdBlitlist() { - buildList(_sprUpd, testUpdating); -} + int16 y2 = 0; + int16 height1 = 0; + int16 height2 = 0; -/** - * Build list of non-updating sprites. - */ -void SpritesMgr::buildNonupdBlitlist() { - buildList(_sprNonupd, testNotUpdating); -} + int16 x2 = 0; + int16 width1 = 0; + int16 width2 = 0; -/** - * Clear the given sprite list. - */ -void SpritesMgr::freeList(SpriteList &l) { - SpriteList::iterator iter; - for (iter = l.reverse_begin(); iter != l.end(); ) { - Sprite* s = *iter; + if (!_vm->_game.pictureShown) + return; - poolRelease(s->buffer); - poolRelease(s); - iter = l.reverse_erase(iter); - } -} + view_height_prev = screenObj->ySize_prev; + view_width_prev = screenObj->xSize_prev; -/** - * Copy sprites from the pic buffer to the screen buffer, and check if - * sprites of the given list have moved. - */ -void SpritesMgr::commitSprites(SpriteList &l, bool immediate) { - SpriteList::iterator iter; - for (iter = l.begin(); iter != l.end(); ++iter) { - Sprite *s = *iter; - int x1, y1, x2, y2; + screenObj->ySize_prev = screenObj->ySize; + screenObj->xSize_prev = screenObj->xSize; - x1 = MIN((int)MIN(s->v->xPos, s->v->xPos2), MIN(s->v->xPos + s->v->celData->width, s->v->xPos2 + s->v->celData2->width)); - x2 = MAX((int)MAX(s->v->xPos, s->v->xPos2), MAX(s->v->xPos + s->v->celData->width, s->v->xPos2 + s->v->celData2->width)); - y1 = MIN((int)MIN(s->v->yPos, s->v->yPos2), MIN(s->v->yPos - s->v->celData->height, s->v->yPos2 - s->v->celData2->height)); - y2 = MAX((int)MAX(s->v->yPos, s->v->yPos2), MAX(s->v->yPos - s->v->celData->height, s->v->yPos2 - s->v->celData2->height)); + if (screenObj->yPos < screenObj->yPos_prev) { + y = screenObj->yPos_prev; + y2 = screenObj->yPos; - s->v->celData2 = s->v->celData; + height1 = view_height_prev; + height2 = screenObj->ySize; + } else { + y = screenObj->yPos; + y2 = screenObj->yPos_prev; - commitBlock(x1, y1, x2, y2, immediate); + height1 = screenObj->ySize; + height2 = view_height_prev; + } - if (s->v->stepTimeCount != s->v->stepTime) - continue; + if ((y2 - height2) > (y - height1)) { + height = height1; + } else { + height = y - y2 + height2; + } - if (s->v->xPos == s->v->xPos2 && s->v->yPos == s->v->yPos2) { - s->v->flags |= fDidntMove; - continue; - } + if (screenObj->xPos > screenObj->xPos_prev) { + x = screenObj->xPos_prev; + x2 = screenObj->xPos; + width1 = view_width_prev; + width2 = screenObj->xSize; + } else { + x = screenObj->xPos; + x2 = screenObj->xPos_prev; + width1 = screenObj->xSize; + width2 = view_width_prev; + } - s->v->xPos2 = s->v->xPos; - s->v->yPos2 = s->v->yPos; - s->v->flags &= ~fDidntMove; + if ((x2 + width2) < (x + width1)) { + width = width1; + } else { + width = width2 + x2 - x; } -} -/** - * Erase all sprites in the given list. - */ -void SpritesMgr::eraseSprites(SpriteList &l) { - SpriteList::iterator iter; - for (iter = l.reverse_begin(); iter != l.end(); --iter) { - Sprite *s = *iter; - objsRestoreArea(s); + if ((x + width) > 161) { + width = 161 - x; + } + + if (1 < (height - y)) { + height = y + 1; } - freeList(l); + // render this block + _gfx->render_Block(x, y, width, height); } -/** - * Blit all sprites in the given list. - */ -void SpritesMgr::blitSprites(SpriteList& l) { - int hidden; +void SpritesMgr::showSprites(SpriteList &spriteList) { SpriteList::iterator iter; + ScreenObjEntry *screenObjPtr = NULL; - for (iter = l.begin(); iter != l.end(); ++iter) { - Sprite *s = *iter; + for (iter = spriteList.begin(); iter != spriteList.end(); ++iter) { + Sprite &sprite = *iter; + screenObjPtr = sprite.screenObjPtr; - objsSaveArea(s); - debugC(8, kDebugLevelSprites, "blitSprites(): s->v->entry = %d (prio %d)", s->v->entry, s->v->priority); - hidden = blitCel(s->xPos, s->yPos, s->v->priority, s->v->celData, s->v->viewData->agi256_2); + showSprite(screenObjPtr); - if (s->v->entry == 0) { // if ego, update f1 - _vm->setflag(fEgoInvisible, hidden); + if (screenObjPtr->stepTimeCount == screenObjPtr->stepTime) { + if ((screenObjPtr->xPos == screenObjPtr->xPos_prev) && (screenObjPtr->yPos == screenObjPtr->yPos_prev)) { + screenObjPtr->flags |= fDidntMove; + } else { + screenObjPtr->xPos_prev = screenObjPtr->xPos; + screenObjPtr->yPos_prev = screenObjPtr->yPos; + screenObjPtr->flags &= ~fDidntMove; + } } } + g_system->updateScreen(); + //g_system->delayMillis(20); } -/* - * Public functions - */ - -void SpritesMgr::commitUpdSprites() { - commitSprites(_sprUpd); +void SpritesMgr::showRegularSpriteList() { + debugC(7, kDebugLevelSprites, "showRegularSpriteList()"); + showSprites(_spriteRegularList); } -void SpritesMgr::commitNonupdSprites() { - commitSprites(_sprNonupd); +void SpritesMgr::showStaticSpriteList() { + debugC(7, kDebugLevelSprites, "showStaticSpriteList()"); + showSprites(_spriteStaticList); } -// check moves in both lists -void SpritesMgr::commitBoth() { - commitUpdSprites(); - commitNonupdSprites(); +void SpritesMgr::showAllSpriteLists() { + showSprites(_spriteStaticList); + showSprites(_spriteRegularList); } /** - * Erase updating sprites. - * This function follows the list of all updating sprites and restores - * the visible and priority data of their background buffers back to - * the AGI screen. - * - * @see erase_nonupd_sprites() - * @see erase_both() + * Show object and description + * This function shows an object from the player's inventory, displaying + * a message box with the object description. + * @param n Number of the object to show */ -void SpritesMgr::eraseUpdSprites() { - eraseSprites(_sprUpd); -} +void SpritesMgr::showObject(int16 viewNr) { + ScreenObjEntry screenObj; + uint8 *backgroundBuffer = NULL; -/** - * Erase non-updating sprites. - * This function follows the list of all non-updating sprites and restores - * the visible and priority data of their background buffers back to - * the AGI screen. - * - * @see erase_upd_sprites() - * @see erase_both() - */ -void SpritesMgr::eraseNonupdSprites() { - eraseSprites(_sprNonupd); -} + _vm->agiLoadResource(RESOURCETYPE_VIEW, viewNr); + _vm->setView(&screenObj, viewNr); -/** - * Erase all sprites. - * This function follows the lists of all updating and non-updating - * sprites and restores the visible and priority data of their background - * buffers back to the AGI screen. - * - * @see erase_upd_sprites() - * @see erase_nonupd_sprites() - */ -void SpritesMgr::eraseBoth() { - eraseUpdSprites(); - eraseNonupdSprites(); -} + screenObj.ySize_prev = screenObj.celData->height; + screenObj.xSize_prev = screenObj.celData->width; + screenObj.xPos_prev = ((SCRIPT_WIDTH - 1) - screenObj.xSize) / 2; + screenObj.xPos = screenObj.xPos_prev; + screenObj.yPos_prev = SCRIPT_HEIGHT - 1; + screenObj.yPos = screenObj.yPos_prev; + screenObj.priority = 15; + screenObj.flags |= fFixedPriority; + screenObj.objectNr = 255; // ??? -/** - * Blit updating sprites. - * This function follows the list of all updating sprites and blits - * them on the AGI screen. - * - * @see blit_nonupd_sprites() - * @see blit_both() - */ -void SpritesMgr::blitUpdSprites() { - debugC(7, kDebugLevelSprites, "blitUpdSprites()"); - buildUpdBlitlist(); - blitSprites(_sprUpd); -} + backgroundBuffer = (uint8 *)malloc(screenObj.xSize * screenObj.ySize * 2); // for visual + priority data -/** - * Blit non-updating sprites. - * This function follows the list of all non-updating sprites and blits - * them on the AGI screen. - * - * @see blit_upd_sprites() - * @see blit_both() - */ -void SpritesMgr::blitNonupdSprites() { - debugC(7, kDebugLevelSprites, "blitNonupdSprites()"); - buildNonupdBlitlist(); - blitSprites(_sprNonupd); -} + _gfx->block_save(screenObj.xPos, (screenObj.yPos - screenObj.ySize + 1), screenObj.xSize, screenObj.ySize, backgroundBuffer); + drawCel(&screenObj); + showSprite(&screenObj); -/** - * Blit all sprites. - * This function follows the lists of all updating and non-updating - * sprites and blits them on the AGI screen. - * - * @see blit_upd_sprites() - * @see blit_nonupd_sprites() - */ -void SpritesMgr::blitBoth() { - blitNonupdSprites(); - blitUpdSprites(); + _vm->_text->messageBox((char *)_vm->_game.views[viewNr].description); + + _gfx->block_restore(screenObj.xPos, (screenObj.yPos - screenObj.ySize + 1), screenObj.xSize, screenObj.ySize, backgroundBuffer); + showSprite(&screenObj); + + free(backgroundBuffer); } /** @@ -562,188 +424,107 @@ void SpritesMgr::blitBoth() { * @param pri priority to use * @param mar if < 4, create a margin around the the base of the cel */ -void SpritesMgr::addToPic(int view, int loop, int cel, int x, int y, int pri, int mar) { - ViewCel *c = NULL; - int x1, y1, x2, y2, y3; - uint8 *p1, *p2; - - debugC(3, kDebugLevelSprites, "addToPic(view=%d, loop=%d, cel=%d, x=%d, y=%d, pri=%d, mar=%d)", view, loop, cel, x, y, pri, mar); - - _vm->recordImageStackCall(ADD_VIEW, view, loop, cel, x, y, pri, mar); - - // Was hardcoded to 8, changed to pri_table[y] to fix Gold - // Rush (see Sarien bug #587558) - if (pri == 0) - pri = _vm->_game.priTable[y]; - - c = &_vm->_game.views[view].loop[loop].cel[cel]; - - x1 = x; - y1 = y - c->height + 1; - x2 = x + c->width - 1; - y2 = y; - - if (x1 < 0) { - x2 -= x1; - x1 = 0; - } - if (y1 < 0) { - y2 -= y1; - y1 = 0; +void SpritesMgr::addToPic(int16 viewNr, int16 loopNr, int16 celNr, int16 xPos, int16 yPos, int16 priority, int16 border) { + debugC(3, kDebugLevelSprites, "addToPic(view=%d, loop=%d, cel=%d, x=%d, y=%d, pri=%d, border=%d)", viewNr, loopNr, celNr, xPos, yPos, priority, border); + + _vm->recordImageStackCall(ADD_VIEW, viewNr, loopNr, celNr, xPos, yPos, priority, border); + + ScreenObjEntry *screenObj = &_vm->_game.addToPicView; + screenObj->objectNr = -1; // addToPic-view + + _vm->setView(screenObj, viewNr); + _vm->setLoop(screenObj, loopNr); + _vm->setCel(screenObj, celNr); + + screenObj->xSize_prev = screenObj->xSize; + screenObj->ySize_prev = screenObj->ySize; + screenObj->xPos_prev = xPos; + screenObj->xPos = xPos; + screenObj->yPos_prev = yPos; + screenObj->yPos = yPos; + screenObj->flags = fIgnoreObjects | fIgnoreHorizon | fFixedPriority; + screenObj->priority = 15; + _vm->fixPosition(screenObj); + if (priority == 0) { + screenObj->flags = fIgnoreHorizon; } - if (x2 >= _WIDTH) - x2 = _WIDTH - 1; - if (y2 >= _HEIGHT) - y2 = _HEIGHT - 1; - - eraseBoth(); - - debugC(4, kDebugLevelSprites, "blitCel(%d, %d, %d, c)", x, y, pri); - blitCel(x1, y1, pri, c, _vm->_game.views[view].agi256_2); - - // If margin is 0, 1, 2, or 3, the base of the cel is - // surrounded with a rectangle of the corresponding priority. - // If margin >= 4, this extra margin is not shown. - // - // -1 indicates ignore and is set for V1 - if (mar < 4 && mar != -1) { - // add rectangle around object, don't clobber control - // info in priority data. The box extends to the end of - // its priority band! - y3 = (y2 / 12) * 12; - - // SQ1 needs +1 (see Sarien bug #810331) - if (_vm->getGameID() == GID_SQ1) - y3++; - - // don't let box extend below y. - if (y3 > y2) y3 = y2; - - p1 = &_vm->_game.sbuf16c[x1 + y3 * _WIDTH]; - p2 = &_vm->_game.sbuf16c[x2 + y3 * _WIDTH]; - - for (y = y3; y <= y2; y++) { - if ((*p1 >> 4) >= 4) - *p1 = (mar << 4) | (*p1 & 0x0f); - - if ((*p2 >> 4) >= 4) - *p2 = (mar << 4) | (*p2 & 0x0f); - - p1 += _WIDTH; - p2 += _WIDTH; - } - - debugC(4, kDebugLevelSprites, "pri box: %d %d %d %d (%d)", x1, y3, x2, y2, mar); - p1 = &_vm->_game.sbuf16c[x1 + y3 * _WIDTH]; - p2 = &_vm->_game.sbuf16c[x1 + y2 * _WIDTH]; - for (x = x1; x <= x2; x++) { - if ((*p1 >> 4) >= 4) - *p1 = (mar << 4) | (*p1 & 0x0f); + screenObj->priority = priority; - if ((*p2 >> 4) >= 4) - *p2 = (mar << 4) | (*p2 & 0x0f); - - p1++; - p2++; - } + eraseSprites(); + + // bugs related to this code: required by Gold Rush (see Sarien bug #587558) + if (screenObj->priority == 0) { + screenObj->priority = _gfx->priorityFromY(screenObj->yPos); } + drawCel(screenObj); - blitBoth(); - - commitBlock(x1, y1, x2, y2, true); -} - -/** - * Show object and description - * This function shows an object from the player's inventory, displaying - * a message box with the object description. - * @param n Number of the object to show - */ -void SpritesMgr::showObj(int n) { - ViewCel *c; - Sprite s; - int x1, y1, x2, y2; - - _vm->agiLoadResource(rVIEW, n); - if (!(c = &_vm->_game.views[n].loop[0].cel[0])) - return; - - x1 = (_WIDTH - c->width) / 2; - y1 = 112; - x2 = x1 + c->width - 1; - y2 = y1 + c->height - 1; - - s.xPos = x1; - s.yPos = y1; - s.xSize = c->width; - s.ySize = c->height; - s.buffer = (uint8 *)malloc(s.xSize * s.ySize); - s.v = 0; - - objsSaveArea(&s); - blitCel(x1, y1, 15, c, _vm->_game.views[n].agi256_2); - commitBlock(x1, y1, x2, y2, true); - _vm->messageBox(_vm->_game.views[n].descr); - objsRestoreArea(&s); - commitBlock(x1, y1, x2, y2, true); - - free(s.buffer); -} - -void SpritesMgr::commitBlock(int x1, int y1, int x2, int y2, bool immediate) { - int i, w, offset; - uint8 *q; - - if (!_vm->_game.pictureShown) - return; + if (border <= 3) { + // Create priority-box + addToPicDrawPriorityBox(screenObj, border); + } + buildAllSpriteLists(); + drawAllSpriteLists(); + showSprite(screenObj); +} + +// bugs previously related to this: +// Sarien bug #247) +void SpritesMgr::addToPicDrawPriorityBox(ScreenObjEntry *screenObj, int16 border) { + int16 priorityFromY = _gfx->priorityFromY(screenObj->yPos); + int16 priorityHeight = 0; + int16 curY = 0; + int16 curX = 0; + int16 height = 0; + int16 width = 0; + int16 offsetX = 0; + + // Figure out the height of the box + curY = screenObj->yPos; + do { + priorityHeight++; + if (curY <= 0) + break; + curY--; + } while (_gfx->priorityFromY(curY) == priorityFromY); + + // box height may not be larger than the actual view + if (screenObj->ySize < priorityHeight) + priorityHeight = screenObj->ySize; + + // now actually draw lower horizontal line + curY = screenObj->yPos; + curX = screenObj->xPos; + + width = screenObj->xSize; + while (width) { + _gfx->putPixel(curX, curY, GFX_SCREEN_MASK_PRIORITY, 0, border); + curX++; + width--; + } - x1 = CLIP(x1, 0, _WIDTH - 1); - x2 = CLIP(x2, 0, _WIDTH - 1); - y1 = CLIP(y1, 0, _HEIGHT - 1); - y2 = CLIP(y2, 0, _HEIGHT - 1); - - // Check if a window is active, and clip the block commited to exclude the - // window's contents. Fixes bug #3295652, and partially fixes bug #3080415. - AgiBlock &window = _vm->_game.window; - if (window.active) { - if (y1 < window.y2 && y2 > window.y2 && (x1 < window.x2 || x2 > window.x1)) { - // The top of the block covers the bottom of the window - y1 = window.y2; + if (priorityHeight > 1) { + // Actual rectangle is needed + curY = screenObj->yPos; + curX = screenObj->xPos; + offsetX = screenObj->xSize - 1; + + height = priorityHeight - 1; + while (height) { + curY--; + height--; + _gfx->putPixel(curX, curY, GFX_SCREEN_MASK_PRIORITY, 0, border); // left line + _gfx->putPixel(curX + offsetX, curY, GFX_SCREEN_MASK_PRIORITY, 0, border); // right line } - if (y1 < window.y1 && y2 > window.y1 && (x1 < window.x2 || x2 > window.x1)) { - // The bottom of the block covers the top of the window - y2 = window.y1; + // and finally the upper horizontal line + width = screenObj->xSize - 2; + curX++; + while (width > 0) { + _gfx->putPixel(curX, curY, GFX_SCREEN_MASK_PRIORITY, 0, border); + curX++; + width--; } } - - debugC(7, kDebugLevelSprites, "commitBlock(%d, %d, %d, %d)", x1, y1, x2, y2); - - w = x2 - x1 + 1; - q = &_vm->_game.sbuf16c[x1 + _WIDTH * y1]; - offset = _vm->_game.lineMinPrint * CHAR_LINES; - - for (i = y1; i <= y2; i++) { - _gfx->putPixelsA(x1, i + offset, w, q); - q += _WIDTH; - } - - _gfx->flushBlockA(x1, y1 + offset, x2, y2 + offset); - - if (immediate) - _gfx->doUpdate(); -} - -SpritesMgr::SpritesMgr(AgiEngine *agi, GfxMgr *gfx) { - _vm = agi; - _gfx = gfx; - - _spritePool = (uint8 *)malloc(POOL_SIZE); - _poolTop = _spritePool; -} - -SpritesMgr::~SpritesMgr() { - free(_spritePool); } } // End of namespace Agi diff --git a/engines/agi/sprite.h b/engines/agi/sprite.h index 549eb59832..0bb0547650 100644 --- a/engines/agi/sprite.h +++ b/engines/agi/sprite.h @@ -25,9 +25,25 @@ namespace Agi { +/** + * Sprite structure. + * This structure holds information on visible and priority data of + * a rectangular area of the AGI screen. Sprites are chained in two + * circular lists, one for updating and other for non-updating sprites. + */ +struct Sprite { + uint16 givenOrderNr; + uint16 sortOrder; + ScreenObjEntry *screenObjPtr; /**< pointer to view table entry */ + int16 xPos; /**< x coordinate of the sprite */ + int16 yPos; /**< y coordinate of the sprite */ + int16 xSize; /**< width of the sprite */ + int16 ySize; /**< height of the sprite */ + byte *backgroundBuffer; /**< buffer to store background data */ +}; -struct Sprite; -typedef Common::List<Sprite *> SpriteList; +typedef Common::List<Sprite> SpriteList; +typedef Common::List<Sprite *> SpritePtrList; class AgiEngine; class GfxMgr; @@ -38,35 +54,42 @@ private: GfxMgr *_gfx; AgiEngine *_vm; - uint8 *_spritePool; - uint8 *_poolTop; - // // Sprite management functions // - SpriteList _sprUpd; - SpriteList _sprNonupd; - - void *poolAlloc(int size); - void poolRelease(void *s); - void blitPixel(uint8 *p, uint8 *end, uint8 col, int spr, int width, int *hidden); - int blitCel(int x, int y, int spr, ViewCel *c, bool agi256_2); - void objsSaveArea(Sprite *s); - void objsRestoreArea(Sprite *s); - - int prioToY(int p); - Sprite *newSprite(VtEntry *v); - void sprAddlist(SpriteList &l, VtEntry *v); - void buildList(SpriteList &l, bool (*test)(VtEntry *, AgiEngine *)); - void buildUpdBlitlist(); - void buildNonupdBlitlist(); - void freeList(SpriteList &l); - void commitSprites(SpriteList &l, bool immediate = false); - void eraseSprites(SpriteList &l); - void blitSprites(SpriteList &l); - static bool testUpdating(VtEntry *v, AgiEngine *); - static bool testNotUpdating(VtEntry *v, AgiEngine *); + SpriteList _spriteRegularList; + SpriteList _spriteStaticList; + +public: + void buildRegularSpriteList(); + void buildStaticSpriteList(); + void buildAllSpriteLists(); + void buildSpriteListAdd(uint16 givenOrderNr, ScreenObjEntry *screenObj, SpriteList &spriteList); + void freeList(SpriteList &spriteList); + void freeRegularSprites(); + void freeStaticSprites(); + void freeAllSprites(); + + void eraseSprites(SpriteList &spriteList); + void eraseRegularSprites(); + void eraseStaticSprites(); + void eraseSprites(); + + void drawSprites(SpriteList &spriteList); + void drawRegularSpriteList(); + void drawStaticSpriteList(); + void drawAllSpriteLists(); + + void drawCel(ScreenObjEntry *screenObj); + + void showSprite(ScreenObjEntry *screenObj); + void showSprites(SpriteList &spriteList); + void showRegularSpriteList(); + void showStaticSpriteList(); + void showAllSpriteLists(); + + void showObject(int16 viewNr); public: SpritesMgr(AgiEngine *agi, GfxMgr *gfx); @@ -74,18 +97,8 @@ public: int initSprites(); void deinitSprites(); - void eraseUpdSprites(); - void eraseNonupdSprites(); - void eraseBoth(); - void blitUpdSprites(); - void blitNonupdSprites(); - void blitBoth(); - void commitUpdSprites(); - void commitNonupdSprites(); - void commitBoth(); - void addToPic(int, int, int, int, int, int, int); - void showObj(int); - void commitBlock(int x1, int y1, int x2, int y2, bool immediate = false); + void addToPic(int16 viewNr, int16 loopNr, int16 celNr, int16 xPos, int16 yPos, int16 priority, int16 border); + void addToPicDrawPriorityBox(ScreenObjEntry *screenObj, int16 border); }; } // End of namespace Agi diff --git a/engines/agi/systemui.cpp b/engines/agi/systemui.cpp new file mode 100644 index 0000000000..dc83c4d393 --- /dev/null +++ b/engines/agi/systemui.cpp @@ -0,0 +1,661 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "agi/agi.h" +#include "agi/graphics.h" +#include "agi/text.h" +#include "agi/keyboard.h" +#include "agi/systemui.h" + +namespace Agi { + +SystemUI::SystemUI(AgiEngine *vm, GfxMgr *gfx, TextMgr *text) { + _vm = vm; + _gfx = gfx; + _text = text; + + _textStatusScore = "Score:%v3 of %v7"; + _textStatusSoundOn = "Sound:on"; + _textStatusSoundOff = "Sound:off"; + + _textPause = " Game paused.\nPress Enter to continue."; + _textRestart = "Press ENTER to restart\nthe game.\n\nPress ESC to continue\nthis game."; + _textQuit = "Press ENTER to quit.\nPress ESC to keep playing."; + + _textInventoryYouAreCarrying = "You are carrying:"; + _textInventoryNothing = "nothing"; + _textInventorySelectItems = "Press ENTER to select, ESC to cancel"; + _textInventoryReturnToGame = "Press a key to return to the game"; + + _textSaveGameSelectSlot = "Use the arrow keys to select the slot in which you wish to save the game. Press ENTER to save in the slot, ESC to not save a game."; + _textSaveGameEnterDescription = "How would you like to describe this saved game?\n\n"; + _textSaveGameVerify = "About to save the game\ndescribed as:\n\n%s\n\nPress ENTER to continue.\nPress ESC to cancel."; + + _textRestoreGameNoSlots = "There are no games to\nrestore in\n\n ScummVM saved game directory\n\nPress ENTER to continue."; + _textRestoreGameSelectSlot = "Use the arrow keys to select the game which you wish to restore. Press ENTER to restore the game, ESC to not restore a game."; + _textRestoreGameError = "Error in restoring game.\nPress ENTER to quit."; + _textRestoreGameVerify = "About to restore the game\ndescribed as:\n\n%s\n\nPress ENTER to continue.\nPress ESC to cancel."; + + // Replace with translated text, when needed + switch (_vm->getLanguage()) { + case Common::RU_RUS: + _textStatusScore = "\x91\xE7\xA5\xE2: %v3 \xA8\xA7 %v7"; + _textStatusSoundOn = "\x87\xA2\xE3\xAA: \xA2\xAA\xAB"; + _textStatusSoundOff = "\x87\xA2\xE3\xAA: \xA2\xEB\xAA\xAB"; + + _textPause = " \x88\xA3\xE0\xA0 \xAE\xE1\xE2\xA0\xAD\xAE\xA2\xAB\xA5\xAD\xA0\nENTER - \xAF\xE0\xAE\xA4\xAE\xAB\xA6\xA5\xAD\xA8\xA5."; + //_textPause = " \x88\xa3\xe0\xa0 \xae\xe1\xe2\xa0\xad\xae\xa2\xab\xa5\xad\xa0. \n\n\n"; <- mouse + // pause button text "\x8f\xe0\xae\xa4\xae\xab\xa6\xa8\xe2\xec" + _textRestart = "ENTER - \xAF\xA5\xE0\xA5\xA7\xA0\xAF\xE3\xE1\xE2\xA8\xE2\xEC \xA8\xA3\xE0\xE3.\n\nESC - \xAF\xE0\xAE\xA4\xAE\xAB\xA6\xA5\xAD\xA8\xA5 \xA8\xA3\xE0\xEB."; + _textQuit = "ENTER-\xA2\xEB\xE5\xAE\xA4 \xA8\xA7 \xA8\xA3\xE0\xEB.\nESC - \xA8\xA3\xE0\xA0\xE2\xEC \xA4\xA0\xAB\xEC\xE8\xA5."; + + _textInventoryYouAreCarrying = " \x93 \xA2\xA0\xE1 \xA5\xE1\xE2\xEC\x3A "; + _textInventoryNothing = "\xAD\xA8\xE7\xA5\xA3\xAE"; + _textInventorySelectItems = "ENTER - \xA2\xEB\xA1\xAE\xE0, ESC - \xAE\xE2\xAC\xA5\xAD\xA0."; + _textInventoryReturnToGame = "\x8B\xEE\xA1\xA0\xEF \xAA\xAB\xA0\xA2\xA8\xE8\xA0 - \xA2\xAE\xA7\xA2\xE0\xA0\xE2 \xA2 \xA8\xA3\xE0\xE3"; + + _textSaveGameSelectSlot ="\x91 \xAF\xAE\xAC\xAE\xE9\xEC\xEE \xAA\xAB\xA0\xA2\xA8\xE8 \xAA\xE3\xE0\xE1\xAE\xE0\xA0 \xA2\xEB\xA1\xA5\xE0\xA8\xE2\xA5 \xE1\xE2\xE0\xAE\xAA\xE3, \xA2 \xAA\xAE\xE2\xAE\xE0\xE3\xEE \xA2\xEB \xA6\xA5\xAB\xA0\xA5\xE2\xA5 \xA7\xA0\xAF\xA8\xE1\xA0\xE2\xEC \xA8\xA3\xE0\xE3. \x8D\xA0\xA6\xAC\xA8\xE2\xA5 ENTER \xA4\xAB\xEF \xA7\xA0\xAF\xA8\xE1\xA8 \xA8\xA3\xE0\xEB, ESC - \xAE\xE2\xAC\xA5\xAD\xA0 \xA7\xA0\xAF\xA8\xE1\xA8."; + _textSaveGameEnterDescription = "\x8A\xA0\xAA \xA2\xEB \xA6\xA5\xAB\xA0\xA5\xE2\xA5 \xAD\xA0\xA7\xA2\xA0\xE2\xEC \xED\xE2\xE3 \xA7\xA0\xAF\xA8\xE1\xEB\xA2\xA0\xA5\xAC\xE3\xEE \xA8\xA3\xE0\xE3?\n\n"; + _textSaveGameVerify = "\x83\xAE\xE2\xAE\xA2 \xAA \xA7\xA0\xAF\xA8\xE1\xA8 \xA8\xA3\xE0\xEB, \n\xAE\xAF\xA8\xE1\xA0\xAD\xAD\xAE\xA9 \xAA\xA0\xAA:\n\n%s\n\n\x84\xAB\xEF \xAF\xE0\xAE\xA4\xAE\xAB\xA6\xA5\xAD\xA8\xEF \xAD\xA0\xA6\xAC\xA8\xE2\xA5 ENTER.\nESC - \xAE\xE2\xAC\xA5\xAD\xA0."; + + _textRestoreGameNoSlots ="\x82 \xAA\xA0\xE2\xA0\xAB\xAE\xA3\xA5\n\n ScummVM saved game directory\n\n\xAD\xA5\xE2 \xA7\xA0\xAF\xA8\xE1\xA0\xAD\xAD\xEB\xE5 \xA8\xA3\xE0.\n\nENTER - \xAF\xE0\xAE\xA4\xAE\xAB\xA6\xA5\xAD\xA8\xA5."; + _textRestoreGameSelectSlot = "\x91 \xAF\xAE\xAC\xAE\xE9\xEC\xEE \xAA\xAB\xA0\xA2\xA8\xE8 \xAA\xE3\xE0\xE1\xAE\xE0\xA0 \xA2\xEB\xA1\xA5\xE0\xA8\xE2\xA5 \xA8\xA3\xE0\xE3, \xAA\xAE\xE2\xAE\xE0\xE3\xEE \xA2\xEB \xA6\xA5\xAB\xA0\xA5\xE2\xA5 \xE1\xE7\xA8\xE2\xA0\xE2\xEC. \x8D\xA0\xA6\xAC\xA8\xE2\xA5 ENTER \xA4\xAB\xEF \xE1\xE7\xA8\xE2\xEB\xA2\xA0\xAD\xA8\xEF \xA8\xA3\xE0\xEB, ESC - \xA4\xAB\xEF \xAE\xE2\xAC\xA5\xAD\xEB."; + _textRestoreGameError ="\x8E\xE8\xA8\xA1\xAA\xA0 \xA2 \xA7\xA0\xAF\xA8\xE1\xA0\xAD\xAD\xAE\xA9 \xA8\xA3\xE0\xA5.\nENTER - \xA2\xEB\xE5\xAE\xA4."; + _textRestoreGameVerify = "\x83\xAE\xE2\xAE\xA2 \xAA \xE1\xE7\xA8\xE2\xEB\xA2\xA0\xAD\xA8\xEE \xA8\xA3\xE0\xEB\x2C\n\xAE\xAF\xA8\xE1\xA0\xAD\xAD\xAE\xA9 \xAA\xA0\xAA.\n\n%s\n\n\x84\xAB\xEF \xAF\xE0\xAE\xA4\xAE\xAB\xA6\xA5\xAD\xA8\xEF \xAD\xA0\xA6\xAC\xA8\xE2\xA5 ENTER.\nESC - \xAE\xE2\xAC\xA5\xAD\xA0."; + +//Press ENTER to continue... +//"\x84\xAB\xEF \xAF\xE0\xAE\xA4\xAE\xAB\xA6\xA5\xAD\xA8\xEF \xAD\xA0\xA6\xAC\xA8\xE2\xA5 ENTER\nESC - \xAE\xE2\xAC\xA5\xAD\xA0."; + +// nothing inventory +// "\xAD\xA8\xE7\xA5\xA3\xAE + +// you are carrying +// " \x93 \xA2\xA0\xE1 \xA5\xE1\xE2\xEC\x3A " + +// Press enter to select +// "ENTER - \xA2\xEB\xA1\xAE\xE0, ESC - \xAE\xE2\xAC\xA5\xAD\xA0."; + +// Press a key to return +// "\x8B\xEE\xA1\xA0\xEF \xAA\xAB\xA0\xA2\xA8\xE8\xA0 - \xA2\xAE\xA7\xA2\xE0\xA0\xE2 \xA2 \xA8\xA3\xE0\xE3"; + +// Score %d %d +// "\x91\xE7\xA5\xE2\x3A %d \xA8\xA7 %d " + +// Sound: on/off +// "\x87\xA2\xE3\xAA\x3A\xA2%s" +// "\xAA\xAB " +// "\xEB\xAA\xAB" + + + break; + default: + break; + } +} + +SystemUI::~SystemUI() { + clearSavedGameSlots(); +} + +const char *SystemUI::getStatusTextScore() { + return _textStatusScore; +} +const char *SystemUI::getStatusTextSoundOn() { + return _textStatusSoundOn; +} +const char *SystemUI::getStatusTextSoundOff() { + return _textStatusSoundOff; +} + +void SystemUI::pauseDialog() { + _vm->_text->messageBox(_textPause); +} + +bool SystemUI::restartDialog() { + return _vm->_text->messageBox(_textRestart); +} + +bool SystemUI::quitDialog() { + return _vm->_text->messageBox(_textQuit); +} + +const char *SystemUI::getInventoryTextNothing() { + return _textInventoryNothing; +} +const char *SystemUI::getInventoryTextYouAreCarrying() { + return _textInventoryYouAreCarrying; +} +const char *SystemUI::getInventoryTextSelectItems() { + return _textInventorySelectItems; +} +const char *SystemUI::getInventoryTextReturnToGame() { + return _textInventoryReturnToGame; +} + +int16 SystemUI::figureOutAutomaticSaveGameSlot(const char *automaticSaveDescription) { + int16 matchedGameSlotId = -1; + int16 freshGameSlotId = -1; + + // Fill saved game slot cache + readSavedGameSlots(false, false); // don't filter, but also don't include auto-save slot + + // Walk through saved game slots + // check, if description matches and return the slot + // if no match can be found, return the first non-existant slot + // if all slots exist, return -1 + figureOutAutomaticSavedGameSlot(automaticSaveDescription, matchedGameSlotId, freshGameSlotId); + + if (matchedGameSlotId >= 0) + return matchedGameSlotId; // return matched slot + + if (freshGameSlotId >= 0) + return freshGameSlotId; // return first non-existant slot + + return -1; // no slots exist, not match found +} + +int16 SystemUI::figureOutAutomaticRestoreGameSlot(const char *automaticSaveDescription) { + int16 matchedGameSlotId = -1; + int16 freshGameSlotId = -1; + + // Fill saved game slot cache + readSavedGameSlots(true, false); // filter non-existant/invalid saves, also don't include auto-save slot + + // Walk through saved game slots + // check, if description matches and return the slot. Otherwise return -1 + figureOutAutomaticSavedGameSlot(automaticSaveDescription, matchedGameSlotId, freshGameSlotId); + + if (matchedGameSlotId >= 0) + return matchedGameSlotId; // return matched slot + return -1; // no match found +} + +int16 SystemUI::askForSaveGameSlot() { + int16 saveGameSlotNr = -1; + + // Fill saved game slot cache + readSavedGameSlots(false, false); // don't filter, but also don't include auto-save slot + + saveGameSlotNr = askForSavedGameSlot(_textSaveGameSelectSlot); + + if (saveGameSlotNr < 0) { + // User cancelled? exit now + return -1; + } + + // return actual slot number of the saved game + return _savedGameArray[saveGameSlotNr].slotId; +} + +bool SystemUI::askForSaveGameDescription(int16 slotId, Common::String &newDescription) { + // Let user enter new description + bool previousEditState = _text->inputGetEditStatus(); + byte previousEditCursor = _text->inputGetCursorChar(); + + _text->drawMessageBox(_textSaveGameEnterDescription, 0, 31, true); + + _text->charPos_Push(); + _text->charAttrib_Push(); + _text->inputEditOn(); + + _text->charPos_SetInsideWindow(3, 0); + _text->charAttrib_Set(15, 0); + _text->clearBlockInsideWindow(3, 0, 31, 0); // input line is supposed to be black + _text->inputSetCursorChar('_'); + + // figure out the current description of the slot + _text->stringSet(""); + for (uint16 slotNr = 0; slotNr < _savedGameArray.size(); slotNr++) { + if (_savedGameArray[slotNr].slotId == slotId) { + // found slotId + if (_savedGameArray[slotNr].isValid) { + // and also valid, so use its description + _text->stringSet(_savedGameArray[slotNr].description); + } + } + } + + _vm->cycleInnerLoopActive(CYCLE_INNERLOOP_GETSTRING); + _text->stringEdit(30); // only allow up to 30 characters + + _text->charAttrib_Pop(); + _text->charPos_Pop(); + _text->inputSetCursorChar(previousEditCursor); + if (!previousEditState) { + _text->inputEditOff(); + } + + _text->closeWindow(); + + if (!_text->stringWasEntered()) { + // User cancelled? exit now + return false; + } + + // Now verify that the user really wants to do this + char userInput[SYSTEMUI_SAVEDGAME_DISPLAYTEXT_LEN + 1]; + + createSavedGameDisplayText(userInput, (char *)_text->_inputString, slotId); + + if (!askForSavedGameVerification(_textSaveGameVerify, userInput)) { + return false; + } + + newDescription.clear(); + newDescription += (char *)_text->_inputString; + return true; +} + +int16 SystemUI::askForRestoreGameSlot() { + int16 restoreGameSlotNr = -1; + + // Fill saved game slot cache + readSavedGameSlots(true, true); // filter empty/corrupt slots, but including auto-save slot + + if (_savedGameArray.size() == 0) { + // no saved games + _vm->_text->messageBox(_textRestoreGameNoSlots); + return -1; + } + + restoreGameSlotNr = askForSavedGameSlot(_textRestoreGameSelectSlot); + + // User cancelled? exit now + if (restoreGameSlotNr < 0) + return -1; + + // Check, if selected saved game was marked as valid + if (!_savedGameArray[restoreGameSlotNr].isValid) { + _vm->_text->messageBox(_textRestoreGameError); + return -1; + } + + // Now ask user about this specific saved game + char userActionVerify[200]; + + sprintf(userActionVerify, _textRestoreGameVerify, _savedGameArray[restoreGameSlotNr].displayText); + if (!_vm->_text->messageBox(userActionVerify)) { + return -1; + } + // return actual slot number of the saved game + return _savedGameArray[restoreGameSlotNr].slotId; +} + +int16 SystemUI::askForSavedGameSlot(const char *slotListText) { + int16 messageBoxHeight = 0; + int16 slotsCount = _savedGameArray.size(); + + if (slotsCount > SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN) { + messageBoxHeight = 5 + SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN; + } else { + messageBoxHeight = 5 + slotsCount; + } + _text->drawMessageBox(slotListText, messageBoxHeight, 34, true); + + drawSavedGameSlots(); + drawSavedGameSlotSelector(true); + + _vm->cycleInnerLoopActive(CYCLE_INNERLOOP_SYSTEMUI_SELECTSAVEDGAMESLOT); + do { + _vm->mainCycle(); + } while (_vm->cycleInnerLoopIsActive() && !(_vm->shouldQuit() || _vm->_restartGame)); + + _text->closeWindow(); + + return _savedGameSelectedSlotNr; +} + +void SystemUI::savedGameSlot_CharPress(int16 newChar) { + int16 slotCount = _savedGameArray.size(); + int16 newUpmostSlotNr = _savedGameUpmostSlotNr; + int16 newSelectedSlotNr = _savedGameSelectedSlotNr; + bool slotsScrolled = false; + + switch (newChar) { + case AGI_KEY_ENTER: + _vm->cycleInnerLoopInactive(); // exit savedGameSlot-loop + return; + break; + + case AGI_KEY_ESCAPE: + _savedGameSelectedSlotNr = -1; + _vm->cycleInnerLoopInactive(); // exit savedGameSlot-loop + return; + break; + case AGI_KEY_UP: // previous slot + newSelectedSlotNr--; + break; + case AGI_KEY_DOWN: // next slot + newSelectedSlotNr++; + break; + // FEATURE: any scroll functionality was not available in original AGI + // Original AGI was in fact limited to a total of 12 save slots + case AGI_KEY_PAGE_UP: // scroll up + newUpmostSlotNr -= SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN; + break; + case AGI_KEY_PAGE_DOWN: // scroll down + newUpmostSlotNr += SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN; + break; + case AGI_KEY_HOME: // scroll all the way up + newUpmostSlotNr = 0; + break; + case AGI_KEY_END: // scroll all the way down + newUpmostSlotNr = (slotCount - 1) - (SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN - 1); + break; + + default: + break; + } + + if (newUpmostSlotNr != _savedGameUpmostSlotNr) { + // Make sure, upmost slot number is valid + if (newUpmostSlotNr < 0) { + newUpmostSlotNr = 0; + } + if (newUpmostSlotNr + (SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN - 1) >= slotCount) { + newUpmostSlotNr = (slotCount - 1) - (SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN - 1); + if (newUpmostSlotNr < 0) + newUpmostSlotNr = 0; + } + + if (newUpmostSlotNr != _savedGameUpmostSlotNr) { + // Still different? then actually force a slot number change in any case + slotsScrolled = true; + + // also adjust selected slot number now + int16 slotDifference = _savedGameSelectedSlotNr - _savedGameUpmostSlotNr; + newSelectedSlotNr = newUpmostSlotNr + slotDifference; + } + } + + if ((newSelectedSlotNr != _savedGameSelectedSlotNr) || slotsScrolled) { + // slot number was changed + + // Make slot number valid and scroll in case it's needed + if (newSelectedSlotNr < 0) { + newSelectedSlotNr = slotCount - 1; + newUpmostSlotNr = newSelectedSlotNr - SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN; + if (newUpmostSlotNr < 0) + newUpmostSlotNr = 0; + } + if (newSelectedSlotNr >= slotCount) { + newSelectedSlotNr = 0; + newUpmostSlotNr = 0; + } + + if (newSelectedSlotNr < newUpmostSlotNr) { + // scroll up when needed + newUpmostSlotNr = newSelectedSlotNr; + } + + if (newSelectedSlotNr >= newUpmostSlotNr + SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN) { + // scroll down when needed + newUpmostSlotNr = newSelectedSlotNr - (SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN - 1); + } + + bool drawSlots = false; + + // remove selector + drawSavedGameSlotSelector(false); + + if (newUpmostSlotNr != _savedGameUpmostSlotNr) { + drawSlots = true; + } + + _savedGameUpmostSlotNr = newUpmostSlotNr; + _savedGameSelectedSlotNr = newSelectedSlotNr; + if (drawSlots) { + drawSavedGameSlots(); + } + drawSavedGameSlotSelector(true); + } +} + +void SystemUI::clearSavedGameSlots() { + _savedGameArray.clear(); + _savedGameUpmostSlotNr = 0; + _savedGameSelectedSlotNr = 0; +} + +void SystemUI::createSavedGameDisplayText(char *destDisplayText, const char *actualDescription, int16 slotId) { + char slotIdChar[3]; + int16 actualDescriptionLen = 0; + + // clear with spaces + memset(destDisplayText, ' ', SYSTEMUI_SAVEDGAME_DISPLAYTEXT_LEN); + + // create fixed prefix (" 1:", "10:", etc.) + sprintf(slotIdChar, "%02d", slotId); + memcpy(destDisplayText, slotIdChar, 2); + destDisplayText[2] = ':'; + + actualDescriptionLen = strlen(actualDescription); + if (actualDescriptionLen > (SYSTEMUI_SAVEDGAME_DISPLAYTEXT_LEN - SYSTEMUI_SAVEDGAME_DISPLAYTEXT_PREFIX_LEN)) { + actualDescriptionLen = SYSTEMUI_SAVEDGAME_DISPLAYTEXT_LEN - SYSTEMUI_SAVEDGAME_DISPLAYTEXT_PREFIX_LEN; + } + + if (actualDescriptionLen > 0) { + memcpy(destDisplayText + SYSTEMUI_SAVEDGAME_DISPLAYTEXT_PREFIX_LEN, actualDescription, actualDescriptionLen); + } + destDisplayText[SYSTEMUI_SAVEDGAME_DISPLAYTEXT_LEN] = 0; // terminator +} + +void SystemUI::readSavedGameSlots(bool filterNonexistant, bool withAutoSaveSlot) { + SavedGameSlotIdArray slotIdArray; + int16 lastSlotId = -1; + int16 curSlotId = 0; + int16 loopSlotId = 0; + SystemUISavedGameEntry savedGameEntry; + Common::String saveDescription; + uint32 saveDate = 0; + uint16 saveTime = 0; + bool saveIsValid = false; + + int16 mostRecentSlotNr = -1; + uint32 mostRecentSlotSaveDate = 0; + uint16 mostRecentSlotSaveTime = 0; + + clearSavedGameSlots(); + + // we assume that the Slot-Ids are in order + slotIdArray = _vm->getSavegameSlotIds(); + slotIdArray.push_back(SYSTEMUI_SAVEDGAME_MAXIMUM_SLOTS); // so that the loop will process all slots + + SavedGameSlotIdArray::iterator it; + SavedGameSlotIdArray::iterator end = slotIdArray.end();; + + for (it = slotIdArray.begin(); it != end; it++) { + curSlotId = *it; + + assert(curSlotId > lastSlotId); // safety check + + if (curSlotId == 0) { + // Skip over auto-save slot, if not requested + if (!withAutoSaveSlot) + continue; + } + + // only allow slot-ids 000 up to 099 + if (curSlotId >= SYSTEMUI_SAVEDGAME_MAXIMUM_SLOTS) + curSlotId = SYSTEMUI_SAVEDGAME_MAXIMUM_SLOTS; + + if (!filterNonexistant) { + // add slot-ids from last one up to current one (not including the current one) + lastSlotId++; + for (loopSlotId = lastSlotId; loopSlotId < curSlotId; loopSlotId++) { + if (loopSlotId == 0) { + // Skip over auto-save slot, if not requested + if (!withAutoSaveSlot) + continue; + } + + savedGameEntry.slotId = loopSlotId; + savedGameEntry.exists = false; + savedGameEntry.isValid = false; + memset(savedGameEntry.description, 0, sizeof(savedGameEntry.description)); + createSavedGameDisplayText(savedGameEntry.displayText, "", loopSlotId); + + _savedGameArray.push_back(savedGameEntry); + } + } + + if (curSlotId >= SYSTEMUI_SAVEDGAME_MAXIMUM_SLOTS) + break; // force an exit, limit reached + + savedGameEntry.slotId = curSlotId; + if (_vm->getSavegameInformation(curSlotId, saveDescription, saveDate, saveTime, saveIsValid)) { + if (saveIsValid) { + // saved game is valid + // check, if this is the latest slot + if (saveDate > mostRecentSlotSaveDate) { + mostRecentSlotNr = _savedGameArray.size(); + mostRecentSlotSaveDate = saveDate; + mostRecentSlotSaveTime = saveTime; + } else if ((saveDate == mostRecentSlotSaveDate) && (saveTime >= mostRecentSlotSaveTime)) { + // larger or equal is on purpose, so that we use the last slot in case there are multiple saves + // with the exact same date+time + mostRecentSlotNr = _savedGameArray.size(); + mostRecentSlotSaveDate = saveDate; + mostRecentSlotSaveTime = saveTime; + } + } + } else { + // slot doesn't exist + if (filterNonexistant) { + continue; + } + } + + savedGameEntry.exists = true; + savedGameEntry.isValid = saveIsValid; + memset(savedGameEntry.description, 0, sizeof(savedGameEntry.description)); + strncpy(savedGameEntry.description, saveDescription.c_str(), SYSTEMUI_SAVEDGAME_DESCRIPTION_LEN); + createSavedGameDisplayText(savedGameEntry.displayText, saveDescription.c_str(), curSlotId); + + _savedGameArray.push_back(savedGameEntry); + + lastSlotId = curSlotId; + } + + if (mostRecentSlotNr >= 0) { + // valid slot found, we use it as default + // Sierra AGI seems to have done the same + _savedGameSelectedSlotNr = mostRecentSlotNr; + if (mostRecentSlotNr < SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN) { + // available without scrolling, so keep upmost slot 0 + _savedGameUpmostSlotNr = 0; + } else { + // we need to scroll, try to have the slot in the middle + int16 slotCount = _savedGameArray.size(); + + _savedGameUpmostSlotNr = mostRecentSlotNr - (SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN / 2); + if ((_savedGameUpmostSlotNr + (SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN - 1)) >= slotCount) { + // current upmost would result in empty lines, because it's at the end, push it upwards + _savedGameUpmostSlotNr = (slotCount - 1) - (SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN - 1); + } + } + } +} + +void SystemUI::figureOutAutomaticSavedGameSlot(const char *automaticSaveDescription, int16 &matchedGameSlotId, int16 &freshGameSlotId) { + bool foundFresh = false; + + for (uint16 slotNr = 0; slotNr < _savedGameArray.size(); slotNr++) { + SystemUISavedGameEntry *savedGameEntry = &_savedGameArray[slotNr]; + + if (savedGameEntry->isValid) { + // valid saved game + if (strcmp(savedGameEntry->description, automaticSaveDescription) == 0) { + // we got a match + matchedGameSlotId = savedGameEntry->slotId; + return; + } + } + if (!foundFresh) { + // no new slot found yet + if (!savedGameEntry->exists) { + // and current slot doesn't exist + if (slotNr) { + // and slot is not the auto-save slot -> remember this slot + freshGameSlotId = savedGameEntry->slotId; + foundFresh = true; + } + } + } + } + return; +} + +void SystemUI::drawSavedGameSlots() { + int16 slotsToDrawCount = _savedGameArray.size() - _savedGameUpmostSlotNr; + int16 slotNr = 0; + + if (slotsToDrawCount > SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN) { + slotsToDrawCount = SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN; + } + _text->charAttrib_Push(); + _text->charAttrib_Set(0, 15); + + for (slotNr = 0; slotNr < slotsToDrawCount; slotNr++) { + _text->displayTextInsideWindow("-", 5 + slotNr, 1); + _text->displayTextInsideWindow(_savedGameArray[_savedGameUpmostSlotNr + slotNr].displayText, 5 + slotNr, 3); + } + _text->charAttrib_Pop(); +} + +void SystemUI::drawSavedGameSlotSelector(bool active) { + int16 windowRow = 5 + (_savedGameSelectedSlotNr - _savedGameUpmostSlotNr); + + _text->charAttrib_Push(); + _text->charAttrib_Set(0, 15); + if (active) { + _text->displayTextInsideWindow("\x1a", windowRow, 0); + } else { + _text->displayTextInsideWindow(" ", windowRow, 0); + } + _text->charAttrib_Pop(); +} + +bool SystemUI::askForSavedGameVerification(const char *verifyText, const char *description) { + char userActionVerify[200]; + int16 userKey = 0; + + sprintf(userActionVerify, verifyText, description); + + _text->drawMessageBox(userActionVerify, 0, 35); + + userKey = _vm->waitKey(); + + _text->closeWindow(); + + switch (userKey) { + case AGI_KEY_ENTER: + case AGI_KEY_LEFT: + return true; + break; + default: + return false; + break; + } +} + +} // End of namespace Agi diff --git a/engines/agi/systemui.h b/engines/agi/systemui.h new file mode 100644 index 0000000000..da9f09f5be --- /dev/null +++ b/engines/agi/systemui.h @@ -0,0 +1,119 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef AGI_SYSTEMUI_H +#define AGI_SYSTEMUI_H + +namespace Agi { + +#define SYSTEMUI_SAVEDGAME_MAXIMUM_SLOTS 100 +#define SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN 12 +#define SYSTEMUI_SAVEDGAME_DESCRIPTION_LEN 30 +#define SYSTEMUI_SAVEDGAME_DISPLAYTEXT_LEN 31 +#define SYSTEMUI_SAVEDGAME_DISPLAYTEXT_PREFIX_LEN 3 + +struct SystemUISavedGameEntry { + int16 slotId; + bool exists; + bool isValid; + char description[SYSTEMUI_SAVEDGAME_DESCRIPTION_LEN + 1]; // actual description + char displayText[SYSTEMUI_SAVEDGAME_DISPLAYTEXT_LEN + 1]; // modified description, meant for display purposes only +}; +typedef Common::Array<SystemUISavedGameEntry> SystemUISavedGameArray; + +class SystemUI { +public: + SystemUI(AgiEngine *vm, GfxMgr *gfx, TextMgr *text); + ~SystemUI(); + +private: + AgiEngine *_vm; + GfxMgr *_gfx; + PictureMgr *_picture; + TextMgr *_text; + +public: + const char *getStatusTextScore(); + const char *getStatusTextSoundOn(); + const char *getStatusTextSoundOff(); + + void pauseDialog(); + bool restartDialog(); + bool quitDialog(); + + const char *getInventoryTextNothing(); + const char *getInventoryTextYouAreCarrying(); + const char *getInventoryTextSelectItems(); + const char *getInventoryTextReturnToGame(); + + int16 figureOutAutomaticSaveGameSlot(const char *automaticSaveDescription); + int16 figureOutAutomaticRestoreGameSlot(const char *automaticSaveDescription); + + int16 askForSaveGameSlot(); + int16 askForRestoreGameSlot(); + bool askForSaveGameDescription(int16 slotId, Common::String &newDescription); + + void savedGameSlot_CharPress(int16 newChar); + +private: + int16 askForSavedGameSlot(const char *slotListText); + bool askForSavedGameVerification(const char *verifyText, const char *description); + + void createSavedGameDisplayText(char *destDisplayText, const char *actualDescription, int16 slotId); + void clearSavedGameSlots(); + void readSavedGameSlots(bool filterNonexistant, bool withAutoSaveSlot); + void figureOutAutomaticSavedGameSlot(const char *automaticSaveDescription, int16 &matchedGameSlotId, int16 &freshGameSlotId); + + void drawSavedGameSlots(); + void drawSavedGameSlotSelector(bool active); + + SystemUISavedGameArray _savedGameArray; + int16 _savedGameUpmostSlotNr; + int16 _savedGameSelectedSlotNr; + +private: + const char *_textStatusScore; + const char *_textStatusSoundOn; + const char *_textStatusSoundOff; + + const char *_textPause; + const char *_textRestart; + const char *_textQuit; + + const char *_textInventoryNothing; + const char *_textInventoryYouAreCarrying; + const char *_textInventorySelectItems; + const char *_textInventoryReturnToGame; + + const char *_textSaveGameSelectSlot; + const char *_textSaveGameEnterDescription; + const char *_textSaveGameVerify; + + const char *_textRestoreGameNoSlots; + const char *_textRestoreGameSelectSlot; + const char *_textRestoreGameError; + const char *_textRestoreGameVerify; +}; + +} // End of namespace Agi + +#endif /* AGI_SYSTEMUI_H */ diff --git a/engines/agi/text.cpp b/engines/agi/text.cpp index 16c8284ce0..8367929932 100644 --- a/engines/agi/text.cpp +++ b/engines/agi/text.cpp @@ -24,493 +24,922 @@ #include "agi/sprite.h" // for commit_both() #include "agi/graphics.h" #include "agi/keyboard.h" +#include "agi/text.h" +#include "agi/systemui.h" +#include "agi/words.h" +#ifdef __DS__ +#include "wordcompletion.h" +#endif namespace Agi { -void AgiEngine::printText2(int l, const char *msg, int foff, int xoff, int yoff, - int len, int fg, int bg, bool checkerboard) { - int x1, y1; - int maxx, minx, ofoff; - int update; - // Note: Must be unsigned to use AGDS cyrillic characters! -#ifdef __DS__ - // On the DS, a compiler bug causes the text to render incorrectly, because - // GCC tries to optimisie out writes to this pointer (tested on DevkitARM v19b and v20) - // Making this pointer volatile fixes this. - volatile const unsigned char *m; -#else - const unsigned char *m; -#endif +TextMgr::TextMgr(AgiEngine *vm, Words *words, GfxMgr *gfx) { + _vm = vm; + _words = words; + _gfx = gfx; - // kludge! - update = 1; - if (l == 2) { - update = l = 0; - } + memset(&_messageState, 0, sizeof(_messageState)); + _textPos.row = 0; + _textPos.column = 0; + _reset_Column = 0; - // FR: strings with len == 1 were not printed - if (len == 1) { - _gfx->putTextCharacter(l, xoff + foff, yoff, *msg, fg, bg, checkerboard, _fontData); - maxx = 1; - minx = 0; - ofoff = foff; - y1 = 0; // Check this - } else { - maxx = 0; - minx = GFX_WIDTH; - ofoff = foff; + charAttrib_Set(15, 0); - for (m = (const unsigned char *)msg, x1 = y1 = 0; *m; m++) { + _messageState.wanted_TextPos.row = -1; + _messageState.wanted_TextPos.column = -1; + _messageState.wanted_Text_Width = -1; + + _textPosArrayCount = 0; + memset(&_textPosArray, 0, sizeof(_textPosArray)); + _textAttribArrayCount = 0; + memset(&_textAttribArray, 0, sizeof(_textAttribArray)); - // Note: there were extra checks for *m being a cursor character - // here (1, 2 or 3), which have been removed, as the cursor - // character is no longer printed via this function. - if (*m >= 0x20) { - int ypos = (y1 * CHAR_LINES) + yoff; + _inputEditEnabled = false; + _inputCursorChar = 0; - if ((x1 != (len - 1) || x1 == 39) && (ypos <= (GFX_HEIGHT - CHAR_LINES))) { - int xpos = (x1 * CHAR_COLS) + xoff + foff; + _statusEnabled = false; + _statusRow = 0; - if (xpos >= GFX_WIDTH) - continue; + _promptRow = 0; + promptDisable(); + promptReset(); - _gfx->putTextCharacter(l, xpos, ypos, *m, fg, bg, checkerboard, _fontData); + _inputStringMaxLen = 0; + _inputStringCursorPos = 0; + _inputString[0] = 0; - if (x1 > maxx) - maxx = x1; - if (x1 < minx) - minx = x1; - } + configureScreen(2); +} + +TextMgr::~TextMgr() { +} + +void TextMgr::init(SystemUI *systemUI) { + _systemUI = systemUI; +} + +void TextMgr::configureScreen(uint16 row_Min) { + _window_Row_Min = row_Min; + _window_Row_Max = row_Min + 21; + + // forward data to GfxMgr as well + _gfx->setRenderStartOffset(row_Min * FONT_DISPLAY_HEIGHT); +} +uint16 TextMgr::getWindowRowMin() { + return _window_Row_Min; +} - x1++; +void TextMgr::dialogueOpen() { + _messageState.dialogue_Open = true; +} - // Change line if we've reached the end of this one, unless the next - // character is a new line itself, or the end of the string - if (x1 == len && m[1] != '\n' && m[1] != 0) { - y1++; - x1 = foff = 0; +void TextMgr::dialogueClose() { + _messageState.dialogue_Open = false; +} + +void TextMgr::charPos_Clip(int16 &row, int16 &column) { + row = CLIP<int16>(row, 0, FONT_ROW_CHARACTERS - 1); + column = CLIP<int16>(column, 0, FONT_COLUMN_CHARACTERS - 1); +} + +void TextMgr::charPos_Set(int16 row, int16 column) { + _textPos.row = row; + _textPos.column = column; +} + +void TextMgr::charPos_Get(TextPos_Struct *posPtr) { + posPtr->row = _textPos.row; + posPtr->column = _textPos.column; +} + +void TextMgr::charPos_Set(TextPos_Struct *posPtr) { + _textPos.row = posPtr->row; + _textPos.column = posPtr->column; +} + +void TextMgr::charPos_Push() { + if (_textPosArrayCount < TEXTPOSARRAY_MAX) { + charPos_Get(&_textPosArray[_textPosArrayCount]); + _textPosArrayCount++; + } +} + +void TextMgr::charPos_Pop() { + if (_textPosArrayCount > 0) { + _textPosArrayCount--; + charPos_Set(&_textPosArray[_textPosArrayCount]); + } +} + +void TextMgr::charPos_SetInsideWindow(int16 windowRow, int16 windowColumn) { + if (!_messageState.window_Active) + return; + + _textPos.row = _messageState.textPos.row + windowRow; + _textPos.column = _messageState.textPos.column + windowColumn; +} + +static byte charAttrib_CGA_Conversion[] = { + 0, 1, 1, 1, 2, 2, 2, 3, 3, 1, 1, 1, 2, 2, 2 +}; + +void TextMgr::charAttrib_Set(byte foreground, byte background) { + _textAttrib.foreground = foreground; + _textAttrib.background = calculateTextBackground(background); + + if (!_vm->_game.gfxMode) { + // Text-mode: + // just use the given colors directly + _textAttrib.combinedForeground = foreground; + _textAttrib.combinedBackground = background; + } else { + // Graphics-mode: + switch (_vm->_renderMode) { + case RENDERMODE_CGA: + // CGA + if (background) { + _textAttrib.combinedForeground = 3; + _textAttrib.combinedBackground = 8; // enable invert of colors + } else if (foreground > 14) { + if (foreground > 14) { + _textAttrib.combinedForeground = 3; + } else { + _textAttrib.combinedForeground = charAttrib_CGA_Conversion[foreground & 0x0F]; } + _textAttrib.combinedBackground = 0; + } + break; + default: + // EGA-handling: + if (background) { + _textAttrib.combinedForeground = 15; + _textAttrib.combinedBackground = 8; // enable invert of colors } else { - if (m[1] != 0) { - y1++; - x1 = foff = 0; - } + _textAttrib.combinedForeground = foreground; + _textAttrib.combinedBackground = 0; } + break; } } +} - if (l) - return; - - if (maxx < minx) - return; +byte TextMgr::charAttrib_GetForeground() { + return _textAttrib.foreground; +} +byte TextMgr::charAttrib_GetBackground() { + return _textAttrib.background; +} - maxx *= CHAR_COLS; - minx *= CHAR_COLS; +void TextMgr::charAttrib_Push() { + if (_textAttribArrayCount < TEXTATTRIBARRAY_MAX) { + memcpy(&_textAttribArray[_textAttribArrayCount], &_textAttrib, sizeof(_textAttrib)); + _textAttribArrayCount++; + } +} - if (update) { - _gfx->scheduleUpdate(foff + xoff + minx, yoff, ofoff + xoff + maxx + CHAR_COLS - 1, - yoff + y1 * CHAR_LINES + CHAR_LINES + 1); +void TextMgr::charAttrib_Pop() { + if (_textAttribArrayCount > 0) { + _textAttribArrayCount--; + memcpy(&_textAttrib, &_textAttribArray[_textAttribArrayCount], sizeof(_textAttrib)); + } +} - // Making synchronous text updates reduces CPU load - // when updating status line and input area - _gfx->doUpdate(); +byte TextMgr::calculateTextBackground(byte background) { + if ((_vm->_game.gfxMode) && (background)) { + return 15; // interpreter sets 0xFF, but drawClearCharacter() would use upper 4 bits by shift } + return 0; } -// -// len is in characters, not pixels!! -// -void AgiEngine::blitTextbox(const char *p, int y, int x, int len) { - // if x | y = -1, then center the box - int xoff, yoff, lin, h, w; - char *msg, *m; +void TextMgr::display(int16 textNr, int16 textRow, int16 textColumn) { + const char *logicTextPtr = NULL; + char *processedTextPtr = NULL; - debugC(3, kDebugLevelText, "blitTextbox(): x=%d, y=%d, len=%d", x, y, len); - if (_game.window.active) - closeWindow(); + charPos_Push(); + charPos_Set(textRow, textColumn); + + if (textNr >= 1 && textNr <= _vm->_game._curLogic->numTexts) { + logicTextPtr = _vm->_game._curLogic->texts[textNr - 1]; + processedTextPtr = stringPrintf(logicTextPtr); + processedTextPtr = stringWordWrap(processedTextPtr, 40); + displayText(processedTextPtr); + + // Signal, that non-blocking text is shown at the moment + if (textRow > 0) { + // only signal, when it's not the status line (kq3) + _vm->nonBlockingText_IsShown(); + } + } + charPos_Pop(); +} + +void TextMgr::displayTextInsideWindow(const char *textPtr, int16 windowRow, int16 windowColumn) { + int16 textRow = 0; + int16 textColumn = 0; - if (x == 0 && y == 0 && len == 0) - x = y = -1; + if (!_messageState.window_Active) + return; - if (len <= 0) - len = 30; + charPos_Push(); + textRow = _messageState.textPos.row + windowRow; + textColumn = _messageState.textPos.column + windowColumn; + charPos_Set(textRow, textColumn); + displayText(textPtr); + charPos_Pop(); +} - xoff = x * CHAR_COLS; - yoff = y * CHAR_LINES; +void TextMgr::displayText(const char *textPtr, bool disabledLook) { + const char *curTextPtr = textPtr; + byte curCharacter = 0; - m = msg = wordWrapString(agiSprintf(p), &len); + while (1) { + curCharacter = *curTextPtr; + if (!curCharacter) + break; - for (lin = 1; *m; m++) { - // Test \r for MacOS 8 - if (*m == '\n' || *m == '\r') - lin++; + curTextPtr++; + displayCharacter(curCharacter, disabledLook); } +} - if (lin * CHAR_LINES > GFX_HEIGHT) - lin = (GFX_HEIGHT / CHAR_LINES); +void TextMgr::displayCharacter(byte character, bool disabledLook) { + TextPos_Struct charCurPos; - w = (len + 2) * CHAR_COLS; - h = (lin + 2) * CHAR_LINES; + charPos_Get(&charCurPos); - if (xoff < 0) - xoff = (GFX_WIDTH - w - CHAR_COLS) / 2; - else - xoff -= CHAR_COLS; + switch (character) { + case 0x08: // backspace + if (charCurPos.column) { + charCurPos.column--; + } else if (charCurPos.row > 21) { + charCurPos.column = (FONT_COLUMN_CHARACTERS - 1); + charCurPos.row--; + } + clearBlock(charCurPos.row, charCurPos.column, charCurPos.row, charCurPos.column, _textAttrib.background); + charPos_Set(&charCurPos); + break; - if (yoff < 0) - yoff = (GFX_HEIGHT - 3 * CHAR_LINES - h) / 2; + case 0x0D: + case 0x0A: // CR/LF + if (charCurPos.row < (FONT_ROW_CHARACTERS - 1)) + charCurPos.row++; + charCurPos.column = _reset_Column; + charPos_Set(&charCurPos); + break; + default: + // ch_attrib(state.text_comb, conversion); + _gfx->drawCharacter(charCurPos.row, charCurPos.column, character, _textAttrib.combinedForeground, _textAttrib.combinedBackground, disabledLook); + + charCurPos.column++; + if (charCurPos.column <= (FONT_COLUMN_CHARACTERS - 1)) { + charPos_Set(&charCurPos); + } else { + displayCharacter(0x0D); // go to next line + } + } +} - drawWindow(xoff, yoff, xoff + w - 1, yoff + h - 1); +void TextMgr::print(int16 textNr) { + const char *logicTextPtr = NULL; + if (textNr >= 1 && textNr <= _vm->_game._curLogic->numTexts) { + logicTextPtr = _vm->_game._curLogic->texts[textNr - 1]; + messageBox(logicTextPtr); + } +} + +void TextMgr::printAt(int16 textNr, int16 textPos_Row, int16 textPos_Column, int16 text_Width) { + // Sierra didn't do clipping, we do it for security + charPos_Clip(textPos_Row, textPos_Column); - printText2(2, msg, 0, CHAR_COLS + xoff, CHAR_LINES + yoff, - len + 1, MSG_BOX_TEXT, MSG_BOX_COLOR); + _messageState.wanted_TextPos.row = textPos_Row; + _messageState.wanted_TextPos.column = textPos_Column; + _messageState.wanted_Text_Width = text_Width; - free(msg); + if (_messageState.wanted_Text_Width == 0) { + _messageState.wanted_Text_Width = 30; + } + print(textNr); - _gfx->doUpdate(); + _messageState.wanted_TextPos.row = -1; + _messageState.wanted_TextPos.column = -1; + _messageState.wanted_Text_Width = -1; } -void AgiEngine::eraseTextbox() { - if (!_game.window.active) { - debugC(3, kDebugLevelText, "eraseTextbox(): no window active"); - return; +bool TextMgr::messageBox(const char *textPtr) { + drawMessageBox(textPtr); + + if (_vm->getflag(VM_FLAG_OUTPUT_MODE)) { + // non-blocking window + _vm->setflag(VM_FLAG_OUTPUT_MODE, false); + + // Signal, that non-blocking text is shown at the moment + _vm->nonBlockingText_IsShown(); + return true; } - debugC(4, kDebugLevelText, "eraseTextbox(): x1=%d, y1=%d, x2=%d, y2=%d", _game.window.x1, - _game.window.y1, _game.window.x2, _game.window.y2); + // blocking window + _vm->_noSaveLoadAllowed = true; + _vm->nonBlockingText_Forget(); - _gfx->restoreBlock(_game.window.x1, _game.window.y1, - _game.window.x2, _game.window.y2, _game.window.buffer); + if (_vm->_game.vars[VM_VAR_WINDOW_RESET] == 0) { + int userKey; + _vm->setVar(VM_VAR_KEY, 0); + userKey = _vm->waitKey(); + closeWindow(); - free(_game.window.buffer); - _game.window.active = false; + _vm->_noSaveLoadAllowed = false; + if (userKey == AGI_KEY_ENTER) + return true; + return false; + } - _gfx->doUpdate(); -} + // timed window + debugC(3, kDebugLevelText, "f15==0, v21==%d => timed", _vm->getVar(VM_VAR_WINDOW_RESET)); + _vm->_game.msgBoxTicks = _vm->getVar(VM_VAR_WINDOW_RESET) * 10; + _vm->setVar(VM_VAR_KEY, 0); -/* - * Public functions - */ + do { + if (_vm->getflag(VM_FLAG_RESTORE_JUST_RAN)) + break; -/** - * Print text in the AGI engine screen. - */ -void AgiEngine::printText(const char *msg, int f, int x, int y, int len, int fg, int bg, bool checkerboard) { - f *= CHAR_COLS; - x *= CHAR_COLS; - y *= CHAR_LINES; + _vm->mainCycle(); + if (_vm->_game.keypress == AGI_KEY_ENTER) { + debugC(4, kDebugLevelText, "KEY_ENTER"); + _vm->setVar(VM_VAR_WINDOW_RESET, 0); + _vm->_game.keypress = 0; + break; + } + } while (_vm->_game.msgBoxTicks > 0 && !(_vm->shouldQuit() || _vm->_restartGame)); - debugC(4, kDebugLevelText, "printText(): %s, %d, %d, %d, %d, %d, %d", msg, f, x, y, len, fg, bg); - printText2(0, agiSprintf(msg), f, x, y, len, fg, bg, checkerboard); + _vm->setVar(VM_VAR_WINDOW_RESET, 0); + closeWindow(); + _vm->_noSaveLoadAllowed = false; + return true; } -/** - * Print text in the AGI engine console. - */ -void AgiEngine::printTextConsole(const char *msg, int x, int y, int len, int fg, int bg) { - x *= CHAR_COLS; - y *= 10; +void TextMgr::drawMessageBox(const char *textPtr, int16 wantedHeight, int16 wantedWidth, bool wantedForced) { + int16 maxWidth = wantedWidth; + int16 startingRow = 0; + char *processedTextPtr; - debugC(4, kDebugLevelText, "printTextConsole(): %s, %d, %d, %d, %d, %d", msg, x, y, len, fg, bg); - printText2(1, msg, 0, x, y, len, fg, bg); -} + if (_messageState.window_Active) { + closeWindow(); + } + charAttrib_Push(); + charPos_Push(); + charAttrib_Set(0, 15); + + if ((_messageState.wanted_Text_Width == -1) && (maxWidth == 0)) { + maxWidth = 30; + } else if (_messageState.wanted_Text_Width != -1) { + maxWidth = _messageState.wanted_Text_Width; + } -/** - * Wrap text line to the specified width. - * @param str String to wrap. - * @param len Length of line. - * - * Based on GBAGI implementation with permission from the author - */ -char *AgiEngine::wordWrapString(const char *s, int *len) { - char *outStr, *msgBuf; - int maxWidth = *len; - const char *pWord; - int lnLen, wLen; + processedTextPtr = stringPrintf(textPtr); - // Allocate some extra space for the final buffer, as - // outStr may end up being longer than s - // 26 = 200 (screen height) / 8 (font height) + 1 - msgBuf = outStr = (char *)malloc(strlen(s) + 26); + int16 calculatedWidth = 0; + int16 calculatedHeight = 0; - int msgWidth = 0; + processedTextPtr = stringWordWrap(processedTextPtr, maxWidth, &calculatedWidth, &calculatedHeight); + _messageState.textSize_Width = calculatedWidth; + _messageState.textSize_Height = calculatedHeight; - lnLen = 0; + _messageState.printed_Height = _messageState.textSize_Height; - while (*s) { - pWord = s; + // Caller wants to force specified width/height? set it + if (wantedForced) { + if (wantedHeight) + _messageState.textSize_Height = wantedHeight; + if (wantedWidth) + _messageState.textSize_Width = wantedWidth; + } - while (*s != '\0' && *s != ' ' && *s != '\n' && *s != '\r') - s++; + if (_messageState.wanted_TextPos.row == -1) { + startingRow = ((HEIGHT_MAX - _messageState.textSize_Height - 1) / 2) + 1; + } else { + startingRow = _messageState.wanted_TextPos.row; + } + _messageState.textPos.row = startingRow + _window_Row_Min; + _messageState.textPos_Edge.row = _messageState.textSize_Height + _messageState.textPos.row - 1; - wLen = (int)(s - pWord); + if (_messageState.wanted_TextPos.column == -1) { + _messageState.textPos.column = (FONT_COLUMN_CHARACTERS - _messageState.textSize_Width) / 2; + } else { + _messageState.textPos.column = _messageState.wanted_TextPos.column; + } + _messageState.textPos_Edge.column = _messageState.textPos.column + _messageState.textSize_Width; - if (wLen && *s == '\n' && s[-1] == ' ') - wLen--; + charPos_Set(_messageState.textPos.row, _messageState.textPos.column); - if (wLen + lnLen >= maxWidth) { - // Check if outStr isn't msgBuf. If this is the case, outStr hasn't advanced - // yet, so no output has been written yet - if (outStr != msgBuf) { - if (outStr[-1] == ' ') - outStr[-1] = '\n'; - else - *outStr++ = '\n'; - } + _messageState.backgroundSize_Width = (_messageState.textSize_Width * FONT_VISUAL_WIDTH) + 10; + _messageState.backgroundSize_Height = (_messageState.textSize_Height * FONT_VISUAL_HEIGHT) + 10; + _messageState.backgroundPos_x = (_messageState.textPos.column * FONT_VISUAL_WIDTH) - 5; + _messageState.backgroundPos_y = (_messageState.textPos_Edge.row - _window_Row_Min + 1 ) * FONT_VISUAL_HEIGHT + 4; - lnLen = 0; + // Hardcoded colors: white background and red lines + _gfx->drawBox(_messageState.backgroundPos_x, _messageState.backgroundPos_y, _messageState.backgroundSize_Width, _messageState.backgroundSize_Height, 15, 4); - while (wLen >= maxWidth) { - msgWidth = maxWidth; + _messageState.window_Active = true; - memcpy(outStr, pWord, maxWidth); + _reset_Column = _messageState.textPos.column; + displayText(processedTextPtr); + _reset_Column = 0; - wLen -= maxWidth; - outStr += maxWidth; - pWord += maxWidth; - *outStr++ = '\n'; - } - } + charPos_Pop(); + charAttrib_Pop(); - if (wLen) { - memcpy(outStr, pWord, wLen); - outStr += wLen; - } - lnLen += wLen+1; + _messageState.dialogue_Open = true; +} + +void TextMgr::closeWindow() { + if (_messageState.window_Active) { + _gfx->render_Block(_messageState.backgroundPos_x, _messageState.backgroundPos_y, _messageState.backgroundSize_Width, _messageState.backgroundSize_Height); + } + _messageState.dialogue_Open = false; + _messageState.window_Active = false; +} - if (lnLen > msgWidth) { - msgWidth = lnLen; +void TextMgr::statusRow_Set(int16 row) { + _statusRow = row; +} +int16 TextMgr::statusRow_Get() { + return _statusRow; +} - if (*s == '\0' || *s == ' ' || *s == '\n' || *s == '\r') - msgWidth--; - } +void TextMgr::statusEnable() { + _statusEnabled = true; +} +void TextMgr::statusDisable() { + _statusEnabled = false; +} +bool TextMgr::statusEnabled() { + return _statusEnabled; +} + +void TextMgr::statusDraw() { + char *statusTextPtr = NULL; + + charAttrib_Push(); + charPos_Push(); + + if (_statusEnabled) { + clearLine(_statusRow, 15); - if (*s == '\n') - lnLen = 0; + charAttrib_Set(0, 15); + charPos_Set(_statusRow, 1); + statusTextPtr = stringPrintf(_systemUI->getStatusTextScore()); + displayText(statusTextPtr); - if (*s) - *outStr++ = *s++; + charPos_Set(_statusRow, 30); + if (_vm->getflag(VM_FLAG_SOUND_ON)) { + statusTextPtr = stringPrintf(_systemUI->getStatusTextSoundOn()); + } else { + statusTextPtr = stringPrintf(_systemUI->getStatusTextSoundOff()); + } + displayText(statusTextPtr); } - *outStr = '\0'; - *len = msgWidth; - return msgBuf; + charPos_Pop(); + charAttrib_Pop(); } -/** - * Remove existing window, if any. - */ -void AgiEngine::closeWindow() { - debugC(4, kDebugLevelText, "closeWindow()"); +void TextMgr::statusClear() { + clearLine(_statusRow, 0); +} - _sprites->eraseBoth(); - eraseTextbox(); // remove window, if any - _sprites->blitBoth(); - _sprites->commitBoth(); // redraw sprites - _game.hasWindow = false; +void TextMgr::clearLine(int16 row, byte color) { + clearLines(row, row, color); } -/** - * Display a message box. - * This function displays the specified message in a text box - * centered in the screen and waits until a key is pressed. - * @param p The text to be displayed - */ -int AgiEngine::messageBox(const char *s) { - int k; - - _sprites->eraseBoth(); - blitTextbox(s, -1, -1, -1); - _sprites->blitBoth(); - k = waitKey(); - debugC(4, kDebugLevelText, "messageBox(): wait_key returned %02x", k); - closeWindow(); +void TextMgr::clearLines(int16 row_Upper, int16 row_Lower, byte color) { + clearBlock(row_Upper, 0, row_Lower, FONT_COLUMN_CHARACTERS - 1, color); +} + +void TextMgr::clearBlock(int16 row_Upper, int16 column_Upper, int16 row_Lower, int16 column_Lower, byte color) { + // Sierra didn't do clipping of the coordinates, we do it for security + // and b/c there actually are some games, that call commands with invalid coordinates + // see cmdClearLines() comments. + charPos_Clip(row_Upper, column_Upper); + charPos_Clip(row_Lower, column_Lower); - return k; + int16 x = column_Upper * FONT_DISPLAY_WIDTH; + int16 y = row_Upper * FONT_DISPLAY_HEIGHT; + int16 width = (column_Lower + 1 - column_Upper) * FONT_DISPLAY_WIDTH; + int16 height = (row_Lower + 1 - row_Upper) * FONT_DISPLAY_HEIGHT; + + y = y + height - 1; // drawDisplayRect wants lower Y-coordinate + _gfx->drawDisplayRect(x, y, width, height, color); } -/** - * Display a message box with buttons. - * This function displays the specified message in a text box - * centered in the screen and waits until a button is pressed. - * @param p The text to be displayed - * @param b NULL-terminated list of button labels - */ -int AgiEngine::selectionBox(const char *m, const char **b) { - int numButtons = 0; - int x, y, i, s; - int bx[5], by[5]; +void TextMgr::clearBlockInsideWindow(int16 windowRow, int16 windowColumn, int16 width, byte color) { + int16 row; + int16 column; + if (!_messageState.window_Active) + return; - _noSaveLoadAllowed = true; + row = _messageState.textPos.row + windowRow; + column = _messageState.textPos.column + windowColumn; + clearBlock(row, column, row, column + width - 1, color); +} - _sprites->eraseBoth(); - blitTextbox(m, -1, -1, -1); +bool TextMgr::inputGetEditStatus() { + return _inputEditEnabled; +} - x = _game.window.x1 + 5 * CHAR_COLS / 2; - y = _game.window.y2 - 5 * CHAR_LINES / 2; - s = _game.window.x2 - _game.window.x1 + 1 - 5 * CHAR_COLS; - debugC(3, kDebugLevelText, "selectionBox(): s = %d", s); +void TextMgr::inputEditOn() { + if (!_inputEditEnabled) { + _inputEditEnabled = true; + if (_inputCursorChar) { + displayCharacter(0x08); // backspace + } + } +} - // Automatically position buttons - for (i = 0; b[i]; i++) { - numButtons++; - s -= CHAR_COLS * strlen(b[i]); +void TextMgr::inputEditOff() { + if (_inputEditEnabled) { + _inputEditEnabled = false; + if (_inputCursorChar) { + displayCharacter(_inputCursorChar); + } } +} + +void TextMgr::inputSetCursorChar(int16 cursorChar) { + _inputCursorChar = cursorChar; +} + +byte TextMgr::inputGetCursorChar() { + return _inputCursorChar; +} + +void TextMgr::promptRow_Set(int16 row) { + _promptRow = row; +} + +int16 TextMgr::promptRow_Get() { + return _promptRow; +} + +void TextMgr::promptReset() { + _promptCursorPos = 0; + memset(_prompt, 0, sizeof(_prompt)); + memset(_promptPrevious, 0, sizeof(_promptPrevious)); +} - if (i > 1) { - debugC(3, kDebugLevelText, "selectionBox(): s / %d = %d", i - 1, s / (i - 1)); - s /= (i - 1); +void TextMgr::promptEnable() { + _promptEnabled = true; +} +void TextMgr::promptDisable() { + _promptEnabled = false; +} +bool TextMgr::promptIsEnabled() { + return _promptEnabled; +} + +void TextMgr::promptCharPress(int16 newChar) { + int16 maxChars = 0; + int16 scriptsInputLen = _vm->getVar(VM_VAR_MAX_INPUT_CHARACTERS); + + if (_messageState.dialogue_Open) { + maxChars = TEXT_STRING_MAX_SIZE - 4; } else { - x += s / 2; + maxChars = TEXT_STRING_MAX_SIZE - strlen(_vm->_game.strings[0]); // string 0 is the prompt string prefix } - for (i = 0; b[i]; i++) { - bx[i] = x; - by[i] = y; - x += CHAR_COLS * strlen(b[i]) + s; - } + if (_promptCursorPos) + maxChars--; - _sprites->blitBoth(); + if (scriptsInputLen < maxChars) + maxChars = scriptsInputLen; - clearKeyQueue(); + inputEditOn(); - AllowSyntheticEvents on(this); + switch (newChar) { + case AGI_KEY_BACKSPACE: { + if (_promptCursorPos) { + _promptCursorPos--; + _prompt[_promptCursorPos] = 0; + displayCharacter(newChar); + + promptRememberForAutoComplete(); + } + break; + } + case 0x0A: // LF + break; + case AGI_KEY_ENTER: { + if (_promptCursorPos) { + // something got entered? -> process it and pass it to the scripts + promptRememberForAutoComplete(true); + + memcpy(&_promptPrevious, &_prompt, sizeof(_prompt)); + // parse text + _vm->_words->parseUsingDictionary((char *)&_prompt); + + _promptCursorPos = 0; + _prompt[0] = 0; + promptRedraw(); + } + break; + } + default: + if (maxChars > _promptCursorPos) { + bool acceptableInput = false; + + // FEATURE: Sierra didn't check for valid characters (filtered out umlauts etc.) + // In text-mode this sort of worked at least with the DOS interpreter + // but as soon as invalid characters were used in graphics mode they weren't properly shown + switch (_vm->getLanguage()) { + case Common::RU_RUS: + if (newChar >= 0x20) + acceptableInput = true; + break; + default: + if ((newChar >= 0x20) && (newChar <= 0x7f)) + acceptableInput = true; + break; + } - debugC(4, kDebugLevelText, "selectionBox(): waiting..."); - int key, active = 0; - int rc = -1; - while (rc == -1 && !(shouldQuit() || _restartGame)) { - for (i = 0; b[i]; i++) - _gfx->drawCurrentStyleButton(bx[i], by[i], b[i], i == active, false, i == 0); + if (acceptableInput) { + _prompt[_promptCursorPos] = newChar; + _promptCursorPos++; + _prompt[_promptCursorPos] = 0; + displayCharacter(newChar); - pollTimer(); - key = doPollKeyboard(); - switch (key) { - case KEY_ENTER: - rc = active; - debugC(4, kDebugLevelText, "selectionBox(): Button pressed: %d", rc); - break; - case KEY_RIGHT: - active++; - if (active >= numButtons) - active = 0; - break; - case KEY_LEFT: - active--; - if (active < 0) - active = numButtons - 1; - break; - case BUTTON_LEFT: - for (i = 0; b[i]; i++) { - if (_gfx->testButton(bx[i], by[i], b[i])) { - rc = active = i; - debugC(4, kDebugLevelText, "selectionBox(): Button pressed: %d", rc); - break; - } + promptRememberForAutoComplete(); } - break; - case 0x09: // Tab - debugC(3, kDebugLevelText, "selectionBox(): Focus change"); - active++; - active %= i; - break; } - _gfx->doUpdate(); + break; + } - if (key == KEY_ESCAPE) - break; + inputEditOff(); +} + +void TextMgr::promptCancelLine() { + while (_promptCursorPos) { + promptCharPress(0x08); // Backspace until prompt is empty } +} - closeWindow(); - debugC(2, kDebugLevelText, "selectionBox(): Result = %d", rc); +void TextMgr::promptEchoLine() { + int16 previousLen = strlen((char *)_promptPrevious); - _noSaveLoadAllowed = false; + if (_promptCursorPos < previousLen) { + inputEditOn(); + + while (_promptPrevious[_promptCursorPos]) { + promptCharPress(_promptPrevious[_promptCursorPos]); + } + promptRememberForAutoComplete(); - return rc; + inputEditOff(); + } } -/** - * - */ -int AgiEngine::print(const char *p, int lin, int col, int len) { - if (p == NULL) - return 0; +void TextMgr::promptRedraw() { + char *textPtr = nullptr; - debugC(4, kDebugLevelText, "print(): lin = %d, col = %d, len = %d", lin, col, len); + if (_promptEnabled) { + inputEditOn(); + clearLine(_promptRow, _textAttrib.background); + charPos_Set(_promptRow, 0); + // agi_printf(str_wordwrap(msg, state.string[0], 40) ); - blitTextbox(p, lin, col, len); + textPtr = _vm->_game.strings[0]; + textPtr = stringPrintf(textPtr); + textPtr = stringWordWrap(textPtr, 40); - if (getflag(fOutputMode)) { - // non-blocking window - setflag(fOutputMode, false); - return 1; + displayText(textPtr); + displayText((char *)&_prompt); + inputEditOff(); } +} - // blocking +// for AGI1 +void TextMgr::promptClear() { + clearLine(_promptRow, _textAttrib.background); +} - _noSaveLoadAllowed = true; +void TextMgr::promptRememberForAutoComplete(bool entered) { +#ifdef __DS__ + DS::findWordCompletions((char *)_prompt); +#endif +} - if (_game.vars[vWindowReset] == 0) { - int k; - setvar(vKey, 0); - k = waitKey(); - closeWindow(); +bool TextMgr::stringWasEntered() { + return _inputStringEntered; +} + +void TextMgr::stringSet(const char *text) { + strncpy((char *)_inputString, text, sizeof(_inputString)); + _inputString[sizeof(_inputString) - 1] = 0; // terminator +} - _noSaveLoadAllowed = false; +void TextMgr::stringEdit(int16 stringMaxLen) { + int16 inputStringLen = strlen((const char *)_inputString); - return k; + // Caller can set the input string + _inputStringCursorPos = 0; + while (_inputStringCursorPos < inputStringLen) { + displayCharacter(_inputString[_inputStringCursorPos]); + _inputStringCursorPos++; } - // timed window + // should never happen unless there is a coding glitch + assert(_inputStringCursorPos <= stringMaxLen); - debugC(3, kDebugLevelText, "f15==0, v21==%d => timed", getvar(21)); - _game.msgBoxTicks = getvar(vWindowReset) * 10; - setvar(vKey, 0); + _inputStringMaxLen = stringMaxLen; + _inputStringEntered = false; - _menuSelected = false; + inputEditOff(); do { - if (getflag(fRestoreJustRan)) - break; + _vm->mainCycle(); + } while (_vm->cycleInnerLoopIsActive() && !(_vm->shouldQuit() || _vm->_restartGame)); - if (_menuSelected) - break; + inputEditOn(); - mainCycle(); - if (_game.keypress == KEY_ENTER) { - debugC(4, kDebugLevelText, "KEY_ENTER"); - setvar(vWindowReset, 0); - _game.keypress = 0; - break; + // Forget non-blocking text, user was asked to enter something + _vm->nonBlockingText_Forget(); +} + +void TextMgr::stringCharPress(int16 newChar) { + inputEditOn(); + + switch (newChar) { + case 0x3: // ctrl-c + case 0x18: { // ctrl-x + // clear string + while (_inputStringCursorPos) { + _inputStringCursorPos--; + _inputString[_inputStringCursorPos] = 0; + displayCharacter(0x08); } - } while (_game.msgBoxTicks > 0 && !(shouldQuit() || _restartGame)); + break; + } - setvar(vWindowReset, 0); + case AGI_KEY_BACKSPACE: { + if (_inputStringCursorPos) { + _inputStringCursorPos--; + _inputString[_inputStringCursorPos] = 0; + displayCharacter(newChar); - closeWindow(); + stringRememberForAutoComplete(); + } + break; + } - _noSaveLoadAllowed = false; + case AGI_KEY_ENTER: { + stringRememberForAutoComplete(true); - return 0; + _inputStringEntered = true; + + _vm->cycleInnerLoopInactive(); // exit GetString-loop + break; + } + + case AGI_KEY_ESCAPE: { + _inputString[0] = 0; + _inputStringCursorPos = 0; + _inputStringEntered = false; + + _vm->cycleInnerLoopInactive(); // exit GetString-loop + break; + } + + default: + if (_inputStringMaxLen > _inputStringCursorPos) { + bool acceptableInput = false; + + // FEATURE: Sierra didn't check for valid characters (filtered out umlauts etc.) + // In text-mode this sort of worked at least with the DOS interpreter + // but as soon as invalid characters were used in graphics mode they weren't properly shown + switch (_vm->getLanguage()) { + case Common::RU_RUS: + if (newChar >= 0x20) + acceptableInput = true; + break; + default: + if ((newChar >= 0x20) && (newChar <= 0x7f)) + acceptableInput = true; + break; + } + + if (acceptableInput) { + if ((_vm->_game.cycleInnerLoopType == CYCLE_INNERLOOP_GETSTRING) || ((newChar >= '0') && (newChar <= '9'))) { + // Additionally check for GETNUMBER-mode, if character is a number + // Sierra also did not do this + _inputString[_inputStringCursorPos] = newChar; + _inputStringCursorPos++; + _inputString[_inputStringCursorPos] = 0; + displayCharacter(newChar); + + stringRememberForAutoComplete(); + } + } + } + break; + } + + inputEditOff(); +} + +void TextMgr::stringRememberForAutoComplete(bool entered) { +#ifdef __DS__ + DS::findWordCompletions((char *)_inputString); +#endif } /** + * Wrap text line to the specified width. + * @param str String to wrap. + * @param len Length of line. * + * Based on GBAGI implementation with permission from the author */ -void AgiEngine::printStatus(const char *message, ...) { - va_list args; +char *TextMgr::stringWordWrap(const char *originalText, int16 maxWidth, int16 *calculatedWidthPtr, int16 *calculatedHeightPtr) { + static char resultWrapBuffer[2000]; + char *outStr = resultWrapBuffer; + const char *wordStartPtr; + int16 lineLen = 0; + int16 wordLen = 0; + int curMaxWidth = 0; + int curHeight = 0; + + assert(maxWidth > 0); // this routine would create heap corruption in case maxWidth <= 0 + + while (*originalText) { + wordStartPtr = originalText; + + while (*originalText != '\0' && *originalText != ' ' && *originalText != '\n' && *originalText != '\r') + originalText++; + + wordLen = originalText - wordStartPtr; + + if (wordLen && *originalText == '\n' && originalText[-1] == ' ') + wordLen--; + + if (wordLen + lineLen >= maxWidth) { + // Check if outStr isn't msgBuf. If this is the case, outStr hasn't advanced + // yet, so no output has been written yet + if (outStr != resultWrapBuffer) { + if (outStr[-1] == ' ') + outStr[-1] = '\n'; + else + *outStr++ = '\n'; + } + curHeight++; + + lineLen = 0; + + while (wordLen >= maxWidth) { + curMaxWidth = maxWidth; + + memcpy(outStr, wordStartPtr, maxWidth); + + wordLen -= maxWidth; + outStr += maxWidth; + wordStartPtr += maxWidth; + *outStr++ = '\n'; + curHeight++; + } + } - va_start(args, message); + if (wordLen) { + memcpy(outStr, wordStartPtr, wordLen); + outStr += wordLen; + } + lineLen += wordLen + 1; - Common::String x = Common::String::vformat(message, args); + if (lineLen > curMaxWidth) { + curMaxWidth = lineLen; - va_end(args); + if (*originalText == '\0' || *originalText == ' ' || *originalText == '\n' || *originalText == '\r') + curMaxWidth--; + } + + if (*originalText == '\n') { + lineLen = 0; + curHeight++; + } + + if (*originalText) + *outStr++ = *originalText++; + } + *outStr = '\0'; + curHeight++; - debugC(4, kDebugLevelText, "fg=%d, bg=%d", STATUS_FG, STATUS_BG); - printText(x.c_str(), 0, 0, _game.lineStatus, 40, STATUS_FG, STATUS_BG); + if (calculatedWidthPtr) { + *calculatedWidthPtr = curMaxWidth; + } + if (calculatedHeightPtr) { + *calculatedHeightPtr = curHeight; + } + return resultWrapBuffer; } +// =============================================================== + static void safeStrcat(Common::String &p, const char *t) { if (t != NULL) p += t; @@ -523,31 +952,31 @@ static void safeStrcat(Common::String &p, const char *t) { * @param s string containing the format specifier * @param n logic number */ -char *AgiEngine::agiSprintf(const char *s) { - static char agiSprintf_buf[768]; - Common::String p; +char *TextMgr::stringPrintf(const char *originalText) { + static char resultPrintfBuffer[2000]; + Common::String resultString; char z[16]; - debugC(3, kDebugLevelText, "logic %d, '%s'", _game.lognum, s); + debugC(3, kDebugLevelText, "logic %d, '%s'", _vm->_game.lognum, originalText); - while (*s) { - switch (*s) { + while (*originalText) { + switch (*originalText) { case '%': - s++; - switch (*s++) { + originalText++; + switch (*originalText++) { int i; case 'v': - i = strtoul(s, NULL, 10); - while (*s >= '0' && *s <= '9') - s++; - sprintf(z, "%015i", getvar(i)); + i = strtoul(originalText, NULL, 10); + while (*originalText >= '0' && *originalText <= '9') + originalText++; + sprintf(z, "%015i", _vm->getVar(i)); i = 99; - if (*s == '|') { - s++; - i = strtoul(s, NULL, 10); - while (*s >= '0' && *s <= '9') - s++; + if (*originalText == '|') { + originalText++; + i = strtoul(originalText, NULL, 10); + while (*originalText >= '0' && *originalText <= '9') + originalText++; } if (i == 99) { @@ -558,173 +987,48 @@ char *AgiEngine::agiSprintf(const char *s) { } else { i = 15 - i; } - safeStrcat(p, z + i); + safeStrcat(resultString, z + i); break; case '0': - i = strtoul(s, NULL, 10) - 1; - safeStrcat(p, objectName(i)); + i = strtoul(originalText, NULL, 10) - 1; + safeStrcat(resultString, _vm->objectName(i)); break; case 'g': - i = strtoul(s, NULL, 10) - 1; - safeStrcat(p, _game.logics[0].texts[i]); + i = strtoul(originalText, NULL, 10) - 1; + safeStrcat(resultString, _vm->_game.logics[0].texts[i]); break; case 'w': - i = strtoul(s, NULL, 10) - 1; - safeStrcat(p, _game.egoWords[i].word); + i = strtoul(originalText, NULL, 10) - 1; + safeStrcat(resultString, _vm->_words->getEgoWord(i)); break; case 's': - i = strtoul(s, NULL, 10); - safeStrcat(p, agiSprintf(_game.strings[i])); + i = strtoul(originalText, NULL, 10); + safeStrcat(resultString, stringPrintf(_vm->_game.strings[i])); break; case 'm': - i = strtoul(s, NULL, 10) - 1; - if (_game.logics[_game.lognum].numTexts > i) - safeStrcat(p, agiSprintf(_game.logics[_game.lognum].texts[i])); + i = strtoul(originalText, NULL, 10) - 1; + if (_vm->_game.logics[_vm->_game.lognum].numTexts > i) + safeStrcat(resultString, stringPrintf(_vm->_game.logics[_vm->_game.lognum].texts[i])); break; } - while (*s >= '0' && *s <= '9') - s++; + while (*originalText >= '0' && *originalText <= '9') + originalText++; break; case '\\': - s++; + originalText++; // FALL THROUGH default: - p += *s++; + resultString += *originalText++; break; } } - assert(p.size() < sizeof(agiSprintf_buf)); - strcpy(agiSprintf_buf, p.c_str()); - return agiSprintf_buf; -} - -/** - * Write the status line. - */ -void AgiEngine::writeStatus() { - char x[64]; - - if (_debug.statusline) { - printStatus("%3d(%03d) %3d,%3d(%3d,%3d) ", - getvar(0), getvar(1), _game.viewTable[0].xPos, - _game.viewTable[0].yPos, WIN_TO_PIC_X(_mouse.x), - WIN_TO_PIC_Y(_mouse.y)); - return; - } - - if (!_game.statusLine) { - clearLines(_game.lineStatus, _game.lineStatus, 0); - flushLines(_game.lineStatus, _game.lineStatus); - -#if 0 - // FIXME: Breaks wrist watch prompt in SQ2 - - // Clear the user input line as well when clearing the status line - // Fixes bug #1893564 - AGI: Texts messed out in Naturette 1 - clearLines(_game.lineUserInput, _game.lineUserInput, 0); - flushLines(_game.lineUserInput, _game.lineUserInput); -#endif - return; - } - - switch (getLanguage()) { - case Common::RU_RUS: - sprintf(x, " \x91\xe7\xa5\xe2: %i \xa8\xa7 %-3i", _game.vars[vScore], _game.vars[vMaxScore]); - printStatus("%-17s \x87\xa2\xe3\xaa:%s", x, getflag(fSoundOn) ? "\xa2\xaa\xab " : "\xa2\xeb\xaa\xab"); - break; - default: - sprintf(x, " Score:%i of %-3i", _game.vars[vScore], _game.vars[vMaxScore]); - printStatus("%-17s Sound:%s ", x, getflag(fSoundOn) ? "on " : "off"); - break; - } -} - -/** - * Print user input prompt. - */ -void AgiEngine::writePrompt() { - int l, fg, bg, pos; - int promptLength = strlen(agiSprintf(_game.strings[0])); - - if (!_game.inputEnabled || _game.inputMode != INPUT_NORMAL) - return; - - l = _game.lineUserInput; - fg = _game.colorFg; - bg = _game.colorBg; - pos = _game.cursorPos; - - debugC(4, kDebugLevelText, "erase line %d", l); - clearLines(l, l, _game.colorBg); - - debugC(4, kDebugLevelText, "prompt = '%s'", agiSprintf(_game.strings[0])); - printText(_game.strings[0], 0, 0, l, promptLength + 1, fg, bg); - printText((char *)_game.inputBuffer, 0, promptLength, l, pos + 1, fg, bg); - _gfx->printCharacter(pos + promptLength, l, _game.cursorChar, fg, bg); - - flushLines(l, l); - _gfx->doUpdate(); -} - -void AgiEngine::clearPrompt(bool useBlackBg) { - int l; - - l = _game.lineUserInput; - clearLines(l, l, useBlackBg ? 0 : _game.colorBg); - flushLines(l, l); - - _gfx->doUpdate(); -} - -/** - * Clear text lines in the screen. - * @param l1 start line - * @param l2 end line - * @param c color - */ -void AgiEngine::clearLines(int l1, int l2, int c) { - // do we need to adjust for +8 on topline? - // inc for endline so it matches the correct num - // ie, from 22 to 24 is 3 lines, not 2 lines. - - debugC(4, kDebugLevelText, "clearLines(%d, %d, %d)", l1, l2, c); - - l1 *= CHAR_LINES; - l2 *= CHAR_LINES; - l2 += CHAR_LINES - 1; - - _gfx->drawRectangle(0, l1, GFX_WIDTH - 1, l2, c); -} - -/** - * - */ -void AgiEngine::flushLines(int l1, int l2) { - l1 *= CHAR_LINES; - l2 *= CHAR_LINES; - l2 += CHAR_LINES - 1; - - _gfx->flushBlock(0, l1, GFX_WIDTH - 1, l2); -} - -/** - * - */ -void AgiEngine::drawWindow(int x1, int y1, int x2, int y2) { - _game.window.active = true; - _game.window.x1 = x1; - _game.window.y1 = y1; - _game.window.x2 = x2; - _game.window.y2 = y2; - _game.window.buffer = (uint8 *)malloc((x2 - x1 + 1) * (y2 - y1 + 1)); - - debugC(4, kDebugLevelText, "x1=%d, y1=%d, x2=%d, y2=%d", x1, y1, x2, y2); - _gfx->saveBlock(x1, y1, x2, y2, _game.window.buffer); - _gfx->drawBox(x1, y1, x2, y2, MSG_BOX_COLOR, MSG_BOX_LINE, 2); + assert(resultString.size() < sizeof(resultPrintfBuffer)); + strcpy(resultPrintfBuffer, resultString.c_str()); + return resultPrintfBuffer; } } // End of namespace Agi diff --git a/engines/agi/text.h b/engines/agi/text.h new file mode 100644 index 0000000000..b42f0982d5 --- /dev/null +++ b/engines/agi/text.h @@ -0,0 +1,201 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef AGI_TEXT_H +#define AGI_TEXT_H + +namespace Agi { + +struct TextPos_Struct { + int16 row; + int16 column; +}; + +#define TEXTPOSARRAY_MAX 5 + +struct TextAttrib_Struct { + byte foreground; + byte background; + byte combinedForeground; + byte combinedBackground; +}; + +#define TEXTATTRIBARRAY_MAX 5 + +struct MessageState_Struct { + uint8 type; + int16 wanted_Text_Width; + TextPos_Struct wanted_TextPos; + bool dialogue_Open; + uint8 newline_Char; + bool window_Active; + TextPos_Struct textPos; + TextPos_Struct textPos_Edge; + int16 textSize_Width; + int16 textSize_Height; + uint16 printed_Height; + + int16 backgroundPos_x; + int16 backgroundPos_y; + int16 backgroundSize_Width; + int16 backgroundSize_Height; +}; + +// this defines here are for calculating character-size inside the visual-screen! +#define FONT_VISUAL_WIDTH 4 +#define FONT_VISUAL_HEIGHT 8 + +#define FONT_DISPLAY_WIDTH 8 +#define FONT_DISPLAY_HEIGHT 8 +#define FONT_ROW_CHARACTERS 25 +#define FONT_COLUMN_CHARACTERS 40 +#define FONT_BYTES_PER_CHARACTER 8 + +#define HEIGHT_MAX 20 + +#define TEXT_STRING_MAX_SIZE 40 + +class TextMgr { +private: + Words *_words; + GfxMgr *_gfx; + AgiEngine *_vm; + SystemUI *_systemUI; + +public: + TextMgr(AgiEngine *vm, Words *words, GfxMgr *gfx); + ~TextMgr(); + + void init(SystemUI *systemUI); + + TextPos_Struct _textPos; + int16 _textPosArrayCount; + TextPos_Struct _textPosArray[TEXTPOSARRAY_MAX]; + + TextAttrib_Struct _textAttrib; + int16 _textAttribArrayCount; + TextAttrib_Struct _textAttribArray[TEXTATTRIBARRAY_MAX]; + + uint16 _window_Row_Min; + uint16 _window_Row_Max; + int16 _reset_Column; + + void configureScreen(uint16 row_Min); + uint16 getWindowRowMin(); + + void dialogueOpen(); + void dialogueClose(); + + void charPos_Clip(int16 &row, int16 &column); + void charPos_Set(int16 row, int16 column); + void charPos_Get(TextPos_Struct *posPtr); + void charPos_Set(TextPos_Struct *posPtr); + void charPos_Push(); + void charPos_Pop(); + void charPos_SetInsideWindow(int16 windowRow, int16 windowColumn); + void charAttrib_Set(byte foreground, byte background); + byte charAttrib_GetForeground(); + byte charAttrib_GetBackground(); + void charAttrib_Push(); + void charAttrib_Pop(); + byte calculateTextBackground(byte background); + + void display(int16 textNr, int16 textRow, int16 textColumn); + void displayText(const char *textPtr, bool disabledLook = false); + void displayCharacter(byte character, bool disabledLook = false); + + void displayTextInsideWindow(const char *textPtr, int16 windowRow, int16 windowColumn); + + MessageState_Struct _messageState; + + void printAt(int16 textNr, int16 textPos_Row, int16 textPos_Column, int16 text_Width); + void print(int16 textNr); + bool messageBox(const char *textPtr); + void drawMessageBox(const char *textPtr, int16 wantedHeight = 0, int16 wantedWidth = 0, bool wantedForced = false); + void closeWindow(); + + void statusRow_Set(int16 row); + int16 statusRow_Get(); + + void statusEnable(); + void statusDisable(); + bool statusEnabled(); + + void statusDraw(); + void statusClear(); + + bool _statusEnabled; + int16 _statusRow; + + void clearLine(int16 row, byte color); + void clearLines(int16 row_Upper, int16 row_Lower, byte color); + void clearBlock(int16 row_Upper, int16 column_Upper, int16 row_Lower, int16 column_Lower, byte color); + + void clearBlockInsideWindow(int16 windowRow, int16 windowColumn, int16 width, byte color); + + bool _inputEditEnabled; + byte _inputCursorChar; + + bool _promptEnabled; + int16 _promptRow; + int16 _promptCursorPos; + byte _prompt[42]; + byte _promptPrevious[42]; + + bool inputGetEditStatus(); + void inputEditOn(); + void inputEditOff(); + void inputSetCursorChar(int16 cursorChar); + byte inputGetCursorChar(); + + void promptReset(); + void promptEnable(); + void promptDisable(); + bool promptIsEnabled(); + + void promptRow_Set(int16 row); + int16 promptRow_Get(); + void promptCharPress(int16 newChar); + void promptCancelLine(); + void promptEchoLine(); + void promptRedraw(); + void promptClear(); // for AGI1 + void promptRememberForAutoComplete(bool entered = false); // for auto-completion + + bool _inputStringEntered; + int16 _inputStringMaxLen; + int16 _inputStringCursorPos; + byte _inputString[42]; + + bool stringWasEntered(); + void stringSet(const char *text); + void stringEdit(int16 stringMaxLen); + void stringCharPress(int16 newChar); + void stringRememberForAutoComplete(bool entered = false); // for auto-completion + + char *stringPrintf(const char *originalText); + char *stringWordWrap(const char *originalText, int16 maxWidth, int16 *calculatedWidthPtr = nullptr, int16 *calculatedHeightPtr = nullptr); +}; + +} // End of namespace Agi + +#endif /* AGI_TEXT_H */ diff --git a/engines/agi/view.cpp b/engines/agi/view.cpp index 6a274a1b73..66508a6c27 100644 --- a/engines/agi/view.cpp +++ b/engines/agi/view.cpp @@ -21,107 +21,60 @@ */ #include "agi/agi.h" +#include "agi/graphics.h" #include "agi/sprite.h" namespace Agi { -void AgiEngine::lSetCel(VtEntry *v, int n) { - ViewLoop *currentVl; - ViewCel *currentVc; +void AgiEngine::updateView(ScreenObjEntry *screenObj) { + int16 celNr, lastCelNr; - v->currentCel = n; - - currentVl = &_game.views[v->currentView].loop[v->currentLoop]; - - // Added by Amit Vainsencher <amitv@subdimension.com> to prevent - // crash in KQ1 -- not in the Sierra interpreter - if (currentVl->numCels == 0) - return; - - // WORKAROUND: This is a very nasty hack to fix a bug in the KQ4 introduction - // In its original form, it caused a lot of regressions, including KQ4 bugs and crashes - // Refer to Sarien bug #588899 for the original issue - // Modifying this workaround to only work for a specific view in the KQ4 intro fixes several - // ScummVM bugs. Refer to bugs #1660486, #1660169, #1660192, #1660162 and #1660354 - // FIXME: Remove this workaround and investigate the reason for the erroneous actor behavior - // in the KQ4 introduction - // It seems there's either a bug with KQ4's logic script 120 (the intro script) - // or flag 64 is not set correctly, which causes the erroneous behavior from the actors - if (getGameID() == GID_KQ4 && !(v->flags & fUpdate) && (v->currentView == 172)) - return; - - currentVc = ¤tVl->cel[n]; - v->celData = currentVc; - v->xSize = currentVc->width; - v->ySize = currentVc->height; -} - -void AgiEngine::lSetLoop(VtEntry *v, int n) { - ViewLoop *currentVl; - debugC(7, kDebugLevelResources, "vt entry #%d, loop = %d", v->entry, n); - - // Added to avoid crash when leaving the arcade machine in MH1 - // -- not in AGI 2.917 - if (n >= v->numLoops) - n = 0; - - v->currentLoop = n; - currentVl = &_game.views[v->currentView].loop[v->currentLoop]; - - v->numCels = currentVl->numCels; - if (v->currentCel >= v->numCels) - v->currentCel = 0; - - v->loopData = &_game.views[v->currentView].loop[n]; -} - -void AgiEngine::updateView(VtEntry *v) { - int cel, lastCel; - - if (v->flags & fDontupdate) { - v->flags &= ~fDontupdate; + if (screenObj->flags & fDontupdate) { + screenObj->flags &= ~fDontupdate; return; } - cel = v->currentCel; - lastCel = v->numCels - 1; + celNr = screenObj->currentCelNr; + lastCelNr = screenObj->celCount - 1; - switch (v->cycle) { + switch (screenObj->cycle) { case kCycleNormal: - if (++cel > lastCel) - cel = 0; + celNr++; + if (celNr > lastCelNr) + celNr = 0; break; case kCycleEndOfLoop: - if (cel < lastCel) { - debugC(5, kDebugLevelResources, "cel %d (last = %d)", cel + 1, lastCel); - if (++cel != lastCel) + if (celNr < lastCelNr) { + debugC(5, kDebugLevelResources, "cel %d (last = %d)", celNr + 1, lastCelNr); + if (++celNr != lastCelNr) break; } - setflag(v->parm1, true); - v->flags &= ~fCycling; - v->direction = 0; - v->cycle = kCycleNormal; + setflag(screenObj->loop_flag, true); + screenObj->flags &= ~fCycling; + screenObj->direction = 0; + screenObj->cycle = kCycleNormal; break; case kCycleRevLoop: - if (cel) { - if (--cel) + if (celNr) { + celNr--; + if (celNr) break; } - setflag(v->parm1, true); - v->flags &= ~fCycling; - v->direction = 0; - v->cycle = kCycleNormal; + setflag(screenObj->loop_flag, true); + screenObj->flags &= ~fCycling; + screenObj->direction = 0; + screenObj->cycle = kCycleNormal; break; case kCycleReverse: - if (cel == 0) { - cel = lastCel; + if (celNr == 0) { + celNr = lastCelNr; } else { - cel--; + celNr--; } break; } - setCel(v, cel); + setCel(screenObj, celNr); } /* @@ -134,191 +87,475 @@ void AgiEngine::updateView(VtEntry *v) { * and fills the corresponding views array element. * @param n number of view resource to decode */ -int AgiEngine::decodeView(int n) { - int loop, cel; - uint8 *v, *lptr; - uint16 lofs, cofs; - ViewLoop *vl; - ViewCel *vc; +int AgiEngine::decodeView(byte *resourceData, uint16 resourceSize, int16 viewNr) { + AgiView *viewData = &_game.views[viewNr]; + uint16 headerId = 0; + byte headerStepSize = 0; + byte headerCycleTime = 0; + byte headerLoopCount = 0; + uint16 headerDescriptionOffset = 0; + bool isAGI256Data = false; + + AgiViewLoop *loopData = nullptr; + uint16 loopOffset = 0; + byte loopHeaderCelCount = 0; + + AgiViewCel *celData = nullptr; + uint16 celOffset = 0; + byte celHeaderWidth = 0; + byte celHeaderHeight = 0; + byte celHeaderTransparencyMirror = 0; + byte celHeaderClearKey = 0; + bool celHeaderMirrored = false; + byte celHeaderMirrorLoop = 0; + + byte *celCompressedData = nullptr; + uint16 celCompressedSize = 0; +// byte *rawBitmap = nullptr; + + debugC(5, kDebugLevelResources, "decode_view(%d)", viewNr); + + if (resourceSize < 5) + error("unexpected end of view data for view %d", viewNr); + + headerId = READ_LE_UINT16(resourceData); + if (getVersion() < 0x2000) { + headerStepSize = resourceData[0]; + headerCycleTime = resourceData[1]; + } + headerLoopCount = resourceData[2]; + headerDescriptionOffset = READ_LE_UINT16(resourceData + 3); + + if (headerId == 0xF00F) + isAGI256Data = true; // AGI 256-2 view detected, 256 color view + + viewData->headerStepSize = headerStepSize; + viewData->headerCycleTime = headerCycleTime; + viewData->loopCount = headerLoopCount; + viewData->description = nullptr; + viewData->loop = nullptr; + + if (headerDescriptionOffset) { + // Figure out length of description + uint16 descriptionPos = headerDescriptionOffset; + uint16 descriptionLen = 0; + while (descriptionPos < resourceSize) { + if (resourceData[descriptionPos] == 0) + break; + descriptionPos++; + descriptionLen++; + } + // Allocate memory for description + viewData->description = new byte[descriptionLen + 1]; + // Copy description over + memcpy(viewData->description, resourceData + headerDescriptionOffset, descriptionLen); + viewData->description[descriptionLen] = 0; // set terminator + } + + if (!viewData->loopCount) // no loops, exit now + return errOK; + + // Check, if at least the loop-offsets are available + if (resourceSize < 5 + (headerLoopCount * 2)) + error("unexpected end of view data for view %d", viewNr); + + // Allocate space for loop-information + loopData = new AgiViewLoop[headerLoopCount]; + viewData->loop = loopData; + + for (int16 loopNr = 0; loopNr < headerLoopCount; loopNr++) { + loopOffset = READ_LE_UINT16(resourceData + 5 + (loopNr * 2)); + + // Check, if at least the loop-header is available + if (resourceSize < (loopOffset + 1)) + error("unexpected end of view data for view %d", viewNr); + + // loop-header: + // celCount:BYTE + // relativeCelOffset[0]:WORD + // relativeCelOffset[1]:WORD + // etc. + loopHeaderCelCount = resourceData[loopOffset]; + + loopData->celCount = loopHeaderCelCount; + loopData->cel = nullptr; + + // Check, if at least the cel-offsets for current loop are available + if (resourceSize < (loopOffset + 1 + (loopHeaderCelCount * 2))) + error("unexpected end of view data for view %d", viewNr); + + if (loopHeaderCelCount) { + // Allocate space for cel-information of current loop + celData = new AgiViewCel[loopHeaderCelCount]; + loopData->cel = celData; + + for (int16 celNr = 0; celNr < loopHeaderCelCount; celNr++) { + celOffset = READ_LE_UINT16(resourceData + loopOffset + 1 + (celNr * 2)); + celOffset += loopOffset; // cel offset is relative to loop offset, so adjust accordingly + + // Check, if at least the cel-header is available + if (resourceSize < (celOffset + 3)) + error("unexpected end of view data for view %d", viewNr); + + // cel-header: + // width:BYTE + // height:BYTE + // Transparency + Mirroring:BYTE + // celData follows + celHeaderWidth = resourceData[celOffset + 0]; + celHeaderHeight = resourceData[celOffset + 1]; + celHeaderTransparencyMirror = resourceData[celOffset + 2]; + + if (!isAGI256Data) { + // regular AGI view data + // Transparency + Mirroring byte is as follows: + // Bit 0-3 - clear key + // Bit 4-6 - original loop, that is not supposed to be mirrored in any case + // Bit 7 - apply mirroring + celHeaderClearKey = celHeaderTransparencyMirror & 0x0F; // bit 0-3 is the clear key + celHeaderMirrored = false; + if (celHeaderTransparencyMirror & 0x80) { + // mirror bit is set + celHeaderMirrorLoop = (celHeaderTransparencyMirror >> 4) & 0x07; + if (celHeaderMirrorLoop != loopNr) { + // only set to mirror'd in case we are not the original loop + celHeaderMirrored = true; + } + } + } else { + // AGI256-2 view data + celHeaderClearKey = celHeaderTransparencyMirror; // full 8 bits for clear key + celHeaderMirrored = false; + } + + celData->width = celHeaderWidth; + celData->height = celHeaderHeight; + celData->clearKey = celHeaderClearKey; + celData->mirrored = celHeaderMirrored; + + // Now decompress cel-data + if ((celHeaderWidth == 0) && (celHeaderHeight == 0)) + error("view cel is 0x0"); + + celCompressedData = resourceData + celOffset + 3; + celCompressedSize = resourceSize - (celOffset + 3); + + if (!isAGI256Data) { + unpackViewCelData(celData, celCompressedData, celCompressedSize); + } else { + unpackViewCelDataAGI256(celData, celCompressedData, celCompressedSize); + } + celData++; + } + } + + loopData++; + } + + return errOK; +} - debugC(5, kDebugLevelResources, "decode_view(%d)", n); - v = _game.views[n].rdata; +void AgiEngine::unpackViewCelData(AgiViewCel *celData, byte *compressedData, uint16 compressedSize) { + byte *rawBitmap = new byte[celData->width * celData->height]; + int16 remainingHeight = celData->height; + int16 remainingWidth = celData->width; + bool isMirrored = celData->mirrored; + byte curByte; + byte curColor; + byte curChunkLen; + int16 adjustPreChangeSingle = 0; + int16 adjustAfterChangeSingle = +1; + + celData->rawBitmap = rawBitmap; + + if (isMirrored) { + adjustPreChangeSingle = -1; + adjustAfterChangeSingle = 0; + rawBitmap += celData->width; + } - assert(v != NULL); + while (remainingHeight) { + if (!compressedSize) + error("unexpected end of data, while unpacking AGI256 data"); - _game.views[n].agi256_2 = (READ_LE_UINT16(v) == 0xf00f); // Detect AGI256-2 views by their header bytes - _game.views[n].descr = READ_LE_UINT16(v + 3) ? (char *)(v + READ_LE_UINT16(v + 3)) : (char *)(v + 3); + curByte = *compressedData++; + compressedSize--; - // if no loops exist, return! - if ((_game.views[n].numLoops = *(v + 2)) == 0) - return errNoLoopsInView; + if (curByte == 0) { + curColor = celData->clearKey; + curChunkLen = remainingWidth; + } else { + curColor = curByte >> 4; + curChunkLen = curByte & 0x0F; + if (curChunkLen > remainingWidth) + error("invalid chunk in view data"); + } - // allocate memory for all views - _game.views[n].loop = (ViewLoop *)calloc(_game.views[n].numLoops, sizeof(ViewLoop)); + switch (curChunkLen) { + case 0: + break; + case 1: + rawBitmap += adjustPreChangeSingle; + *rawBitmap = curColor; + rawBitmap += adjustAfterChangeSingle; + break; + default: + if (isMirrored) + rawBitmap -= curChunkLen; + memset(rawBitmap, curColor, curChunkLen); + if (!isMirrored) + rawBitmap += curChunkLen; + break; + } - if (_game.views[n].loop == NULL) - return errNotEnoughMemory; + remainingWidth -= curChunkLen; + + if (curByte == 0) { + remainingWidth = celData->width; + remainingHeight--; - // decode all of the loops in this view - lptr = v + 5; // first loop address + if (isMirrored) + rawBitmap += celData->width * 2; + } + } - for (loop = 0; loop < _game.views[n].numLoops; loop++, lptr += 2) { - lofs = READ_LE_UINT16(lptr); // loop header offset - vl = &_game.views[n].loop[loop]; // the loop struct + // for CGA rendering, apply dithering + switch (_renderMode) { + case RENDERMODE_CGA: { + uint16 totalPixels = celData->width * celData->height; - vl->numCels = *(v + lofs); - debugC(6, kDebugLevelResources, "view %d, num_cels = %d", n, vl->numCels); - vl->cel = (ViewCel *)calloc(vl->numCels, sizeof(ViewCel)); + // dither clear key + celData->clearKey = _gfx->getCGAMixtureColor(celData->clearKey); - if (vl->cel == NULL) { - free(_game.views[n].loop); - _game.views[n].numLoops = 0; - return errNotEnoughMemory; + rawBitmap = celData->rawBitmap; + for (uint16 pixelNr = 0; pixelNr < totalPixels; pixelNr++) { + curColor = *rawBitmap; + *rawBitmap = _gfx->getCGAMixtureColor(curColor); + rawBitmap++; } + break; + } + default: + break; + } +} - // decode the cells - for (cel = 0; cel < vl->numCels; cel++) { - cofs = lofs + READ_LE_UINT16(v + lofs + 1 + (cel * 2)); - vc = &vl->cel[cel]; - - vc->width = *(v + cofs); - vc->height = *(v + cofs + 1); - - if (!_game.views[n].agi256_2) { - vc->transparency = *(v + cofs + 2) & 0xf; - vc->mirrorLoop = (*(v + cofs + 2) >> 4) & 0x7; - vc->mirror = (*(v + cofs + 2) >> 7) & 0x1; - } else { - // Mirroring is disabled for AGI256-2 views because - // AGI256-2 uses whole 8 bits for the transparency variable. - vc->transparency = *(v + cofs + 2); - vc->mirrorLoop = 0; - vc->mirror = 0; - } +void AgiEngine::unpackViewCelDataAGI256(AgiViewCel *celData, byte *compressedData, uint16 compressedSize) { + byte *rawBitmap = new byte[celData->width * celData->height]; + int16 remainingHeight = celData->height; + int16 remainingWidth = celData->width; + byte curByte; - // skip over width/height/trans|mirror data - cofs += 3; + celData->rawBitmap = rawBitmap; - vc->data = v + cofs; + while (remainingHeight) { + if (!compressedSize) + error("unexpected end of data, while unpacking AGI256 data"); - // If mirror_loop is pointing to the current loop, - // then this is the original. - if (vc->mirrorLoop == loop) - vc->mirror = 0; - } // cel - } // loop + curByte = *compressedData++; + compressedSize--; - return errOK; + if (curByte == 0) { + // Go to next vertical position + if (remainingWidth) { + // fill remaining bytes with clear key + memset(rawBitmap, celData->clearKey, remainingWidth); + rawBitmap += remainingWidth; + remainingWidth = 0; + } + } else { + *rawBitmap = curByte; + rawBitmap++; + } + + if (curByte == 0) { + remainingWidth = celData->width; + remainingHeight--; + } + } } /** * Unloads all data in a view resource * @param n number of view resource */ -void AgiEngine::unloadView(int n) { - int x; +void AgiEngine::unloadView(int16 viewNr) { + AgiView *viewData = &_game.views[viewNr]; - debugC(5, kDebugLevelResources, "discard view %d", n); - if (~_game.dirView[n].flags & RES_LOADED) + debugC(5, kDebugLevelResources, "discard view %d", viewNr); + if (!(_game.dirView[viewNr].flags & RES_LOADED)) return; // Rebuild sprite list, see Sarien bug #779302 - _sprites->eraseBoth(); - _sprites->blitBoth(); - _sprites->commitBoth(); + _sprites->eraseSprites(); - // free all the loops - for (x = 0; x < _game.views[n].numLoops; x++) - free(_game.views[n].loop[x].cel); + // free data + for (int16 loopNr = 0; loopNr < viewData->loopCount; loopNr++) { + AgiViewLoop *loopData = &viewData->loop[loopNr]; + for (int16 celNr = 0; celNr < loopData->celCount; celNr++) { + AgiViewCel *celData = &loopData->cel[celNr]; - free(_game.views[n].loop); - free(_game.views[n].rdata); + delete[] celData->rawBitmap; + } + delete[] loopData->cel; + } + delete[] viewData->loop; - _game.dirView[n].flags &= ~RES_LOADED; -} + if (viewData->description) + delete[] viewData->description; -/** - * Set a view table entry to use the specified cel of the current loop. - * @param v pointer to view table entry - * @param n number of cel - */ -void AgiEngine::setCel(VtEntry *v, int n) { - assert(v->viewData != NULL); - assert(v->numCels >= n); + viewData->headerCycleTime = 0; + viewData->headerStepSize = 0; + viewData->description = nullptr; + viewData->loop = nullptr; + viewData->loopCount = 0; - lSetCel(v, n); + // Mark this view as not loaded anymore + _game.dirView[viewNr].flags &= ~RES_LOADED; - // If position isn't appropriate, update it accordingly - clipViewCoordinates(v); + _sprites->buildAllSpriteLists(); + _sprites->drawAllSpriteLists(); } /** - * Restrict view table entry's position so it stays wholly inside the screen. - * Also take horizon into account when clipping if not set to ignore it. - * @param v pointer to view table entry + * Set a view table entry to use the specified view resource. + * @param screenObj pointer to screen object + * @param viewNr number of AGI view resource */ -void AgiEngine::clipViewCoordinates(VtEntry *v) { - if (v->xPos + v->xSize > _WIDTH) { - v->flags |= fUpdatePos; - v->xPos = _WIDTH - v->xSize; +void AgiEngine::setView(ScreenObjEntry *screenObj, int16 viewNr) { + screenObj->viewData = &_game.views[viewNr]; + screenObj->currentViewNr = viewNr; + screenObj->loopCount = screenObj->viewData->loopCount; + screenObj->viewReplaced = true; + + if (getVersion() < 0x2000) { + screenObj->stepSize = screenObj->viewData->headerStepSize; + screenObj->cycleTime = screenObj->viewData->headerCycleTime; + screenObj->cycleTimeCount = 0; } - if (v->yPos - v->ySize + 1 < 0) { - v->flags |= fUpdatePos; - v->yPos = v->ySize - 1; + if (screenObj->currentLoopNr >= screenObj->loopCount) { + setLoop(screenObj, 0); + } else { + setLoop(screenObj, screenObj->currentLoopNr); } - if (v->yPos <= _game.horizon && (~v->flags & fIgnoreHorizon)) { - v->flags |= fUpdatePos; - v->yPos = _game.horizon + 1; +} + +/** + * Set a view table entry to use the specified loop of the current view. + * @param screenObj pointer to screen object + * @param loopNr number of loop + */ +void AgiEngine::setLoop(ScreenObjEntry *screenObj, int16 loopNr) { + assert(screenObj->viewData != NULL); + + if (screenObj->loopCount == 0) { + warning("setLoop() called on screen object %d, which has no loops (view %d)", screenObj->objectNr, screenObj->currentViewNr); + return; } - if (getVersion() < 0x2000) { - v->flags |= fDontupdate; + if (loopNr >= screenObj->loopCount) { + // requested loop not existant + // instead of error()ing out, we instead clip it + // At least required for possibly Manhunter 1 according to previous comment when leaving the arcade machine + // TODO: check MH1 + int16 requestedLoopNr = loopNr; + + loopNr = screenObj->loopCount - 1; + + warning("Non-existant loop requested for screen object %d", screenObj->objectNr); + warning("view %d, requested loop %d -> clipped to loop %d", screenObj->currentViewNr, requestedLoopNr, loopNr); } + AgiViewLoop *curViewLoop = &_game.views[screenObj->currentViewNr].loop[loopNr]; + + screenObj->currentLoopNr = loopNr; + screenObj->loopData = curViewLoop; + screenObj->celCount = curViewLoop->celCount; + + if (screenObj->currentCelNr >= screenObj->celCount) { + setCel(screenObj, 0); + } else { + setCel(screenObj, screenObj->currentCelNr); + } } /** - * Set a view table entry to use the specified loop of the current view. - * @param v pointer to view table entry - * @param n number of loop + * Set a view table entry to use the specified cel of the current loop. + * @param screenObj pointer to screen object + * @param celNr number of cel */ -void AgiEngine::setLoop(VtEntry *v, int n) { - assert(v->viewData != NULL); - assert(v->numLoops >= n); - lSetLoop(v, n); - setCel(v, v->currentCel); +void AgiEngine::setCel(ScreenObjEntry *screenObj, int16 celNr) { + assert(screenObj->viewData != NULL); + + AgiViewLoop *curViewLoop = &_game.views[screenObj->currentViewNr].loop[screenObj->currentLoopNr]; + + // Added by Amit Vainsencher <amitv@subdimension.com> to prevent + // crash in KQ1 -- not in the Sierra interpreter + if (curViewLoop->celCount == 0) { + warning("setCel() called on screen object %d, which has no cels (view %d)", screenObj->objectNr, screenObj->currentViewNr); + return; + } + + if (celNr >= screenObj->celCount) { + // requested cel not existant + // instead of error()ing out, we instead clip it + // At least required for King's Quest 3 on Apple IIgs - walking the planks death cutscene + // see bug #5832, which is a game bug! + int16 requestedCelNr = celNr; + + celNr = screenObj->celCount - 1; + + warning("Non-existant cel requested for screen object %d", screenObj->objectNr); + warning("view %d, loop %d, requested cel %d -> clipped to cel %d", screenObj->currentViewNr, screenObj->currentLoopNr, requestedCelNr, celNr); + } + + screenObj->currentCelNr = celNr; + + AgiViewCel *curViewCel; + curViewCel = &curViewLoop->cel[celNr]; + screenObj->celData = curViewCel; + screenObj->xSize = curViewCel->width; + screenObj->ySize = curViewCel->height; + + // If position isn't appropriate, update it accordingly + clipViewCoordinates(screenObj); } /** - * Set a view table entry to use the specified view resource. + * Restrict view table entry's position so it stays wholly inside the screen. + * Also take horizon into account when clipping if not set to ignore it. * @param v pointer to view table entry - * @param n number of AGI view resource */ -void AgiEngine::setView(VtEntry *v, int n) { - v->viewData = &_game.views[n]; - v->currentView = n; - v->numLoops = v->viewData->numLoops; - v->viewReplaced = true; +void AgiEngine::clipViewCoordinates(ScreenObjEntry *screenObj) { + if (screenObj->xPos + screenObj->xSize > SCRIPT_WIDTH) { + screenObj->flags |= fUpdatePos; + screenObj->xPos = SCRIPT_WIDTH - screenObj->xSize; + } + if (screenObj->yPos - screenObj->ySize + 1 < 0) { + screenObj->flags |= fUpdatePos; + screenObj->yPos = screenObj->ySize - 1; + } + if (screenObj->yPos <= _game.horizon && (~screenObj->flags & fIgnoreHorizon)) { + screenObj->flags |= fUpdatePos; + screenObj->yPos = _game.horizon + 1; + } if (getVersion() < 0x2000) { - v->stepSize = v->viewData->rdata[0]; - v->cycleTime = v->viewData->rdata[1]; - v->cycleTimeCount = 0; + screenObj->flags |= fDontupdate; } - setLoop(v, v->currentLoop >= v->numLoops ? 0 : v->currentLoop); + } /** * Set the view table entry as updating. * @param v pointer to view table entry */ -void AgiEngine::startUpdate(VtEntry *v) { +void AgiEngine::startUpdate(ScreenObjEntry *v) { if (~v->flags & fUpdate) { - _sprites->eraseBoth(); - + _sprites->eraseSprites(); v->flags |= fUpdate; - _sprites->blitBoth(); - _sprites->commitBoth(); + _sprites->buildAllSpriteLists(); + _sprites->drawAllSpriteLists(); } } @@ -326,13 +563,12 @@ void AgiEngine::startUpdate(VtEntry *v) { * Set the view table entry as non-updating. * @param v pointer to view table entry */ -void AgiEngine::stopUpdate(VtEntry *v) { - if (v->flags & fUpdate) { - _sprites->eraseBoth(); - - v->flags &= ~fUpdate; - _sprites->blitBoth(); - _sprites->commitBoth(); +void AgiEngine::stopUpdate(ScreenObjEntry *viewPtr) { + if (viewPtr->flags & fUpdate) { + _sprites->eraseSprites(); + viewPtr->flags &= ~fUpdate; + _sprites->buildAllSpriteLists(); + _sprites->drawAllSpriteLists(); } } @@ -351,67 +587,68 @@ static int loopTable4[] = { * This function is called at the end of each interpreter cycle * to update the view table entries and blit the sprites. */ -void AgiEngine::updateViewtable() { - VtEntry *v; - int i, loop; +void AgiEngine::updateScreenObjTable() { + ScreenObjEntry *screenObj; + int16 changeCount, loopNr; - i = 0; - for (v = _game.viewTable; v < &_game.viewTable[MAX_VIEWTABLE]; v++) { - if ((v->flags & (fAnimated | fUpdate | fDrawn)) != (fAnimated | fUpdate | fDrawn)) { + changeCount = 0; + for (screenObj = _game.screenObjTable; screenObj < &_game.screenObjTable[SCREENOBJECTS_MAX]; screenObj++) { + if ((screenObj->flags & (fAnimated | fUpdate | fDrawn)) != (fAnimated | fUpdate | fDrawn)) { continue; } - i++; + changeCount++; - loop = 4; - if (~v->flags & fFixLoop) { - switch (v->numLoops) { + loopNr = 4; + if (!(screenObj->flags & fFixLoop)) { + switch (screenObj->loopCount) { case 2: case 3: - loop = loopTable2[v->direction]; + loopNr = loopTable2[screenObj->direction]; break; case 4: - loop = loopTable4[v->direction]; + loopNr = loopTable4[screenObj->direction]; break; default: // for KQ4 if (getVersion() == 0x3086 || getGameID() == GID_KQ4) - loop = loopTable4[v->direction]; + loopNr = loopTable4[screenObj->direction]; break; } } // AGI 2.272 (ddp, xmas) doesn't test step_time_count! - if (loop != 4 && loop != v->currentLoop) { + if (loopNr != 4 && loopNr != screenObj->currentLoopNr) { if (getVersion() <= 0x2272 || - v->stepTimeCount == 1) { - setLoop(v, loop); + screenObj->stepTimeCount == 1) { + setLoop(screenObj, loopNr); } } - if (~v->flags & fCycling) - continue; - - if (v->cycleTimeCount == 0) - continue; - - if (--v->cycleTimeCount == 0) { - updateView(v); - v->cycleTimeCount = v->cycleTime; + if (screenObj->flags & fCycling) { + if (screenObj->cycleTimeCount) { + screenObj->cycleTimeCount--; + if (screenObj->cycleTimeCount == 0) { + updateView(screenObj); + screenObj->cycleTimeCount = screenObj->cycleTime; + } + } } } - if (i) { - _sprites->eraseUpdSprites(); + if (changeCount) { + _sprites->eraseRegularSprites(); updatePosition(); - _sprites->blitUpdSprites(); - _sprites->commitUpdSprites(); - _game.viewTable[0].flags &= ~(fOnWater | fOnLand); + _sprites->buildRegularSpriteList(); + _sprites->drawRegularSpriteList(); + _sprites->showRegularSpriteList(); + + _game.screenObjTable[SCREENOBJECTS_EGO_ENTRY].flags &= ~(fOnWater | fOnLand); } } -bool AgiEngine::isEgoView(const VtEntry* v) { - return v == _game.viewTable; +bool AgiEngine::isEgoView(const ScreenObjEntry* screenObj) { + return screenObj == _game.screenObjTable; } } // End of namespace Agi diff --git a/engines/agi/view.h b/engines/agi/view.h index b82fbe04d7..3afe3dc84b 100644 --- a/engines/agi/view.h +++ b/engines/agi/view.h @@ -25,36 +25,40 @@ namespace Agi { -struct ViewCel { +struct AgiViewCel { uint8 height; uint8 width; - uint8 transparency; - uint8 mirrorLoop; - uint8 mirror; - uint8 *data; + uint8 clearKey; + bool mirrored; + byte *rawBitmap; }; -struct ViewLoop { - int numCels; - struct ViewCel *cel; +struct AgiViewLoop { + int16 celCount; + AgiViewCel *cel; }; /** * AGI view resource structure. */ struct AgiView { - int numLoops; - struct ViewLoop *loop; - bool agi256_2; - char *descr; - uint8 *rdata; + byte headerStepSize; + byte headerCycleTime; + byte *description; + int16 loopCount; + AgiViewLoop *loop; + + //struct ViewLoop *loop; + //bool agi256_2; + //byte *resourceData; }; enum MotionType { kMotionNormal = 0, kMotionWander = 1, kMotionFollowEgo = 2, - kMotionMoveObj = 3 + kMotionMoveObj = 3, + kMotionEgo = 4 }; enum CycleType { @@ -65,60 +69,73 @@ enum CycleType { }; enum ViewFlags { - fDrawn = (1 << 0), - fIgnoreBlocks = (1 << 1), - fFixedPriority = (1 << 2), - fIgnoreHorizon = (1 << 3), - fUpdate = (1 << 4), - fCycling = (1 << 5), - fAnimated = (1 << 6), - fMotion = (1 << 7), - fOnWater = (1 << 8), - fIgnoreObjects = (1 << 9), - fUpdatePos = (1 << 10), - fOnLand = (1 << 11), - fDontupdate = (1 << 12), - fFixLoop = (1 << 13), - fDidntMove = (1 << 14), - fAdjEgoXY = (1 << 15) + fDrawn = (1 << 0), // 0x0001 + fIgnoreBlocks = (1 << 1), // 0x0002 + fFixedPriority = (1 << 2), // 0x0004 + fIgnoreHorizon = (1 << 3), // 0x0008 + fUpdate = (1 << 4), // 0x0010 + fCycling = (1 << 5), // 0x0020 + fAnimated = (1 << 6), // 0x0040 + fMotion = (1 << 7), // 0x0080 + fOnWater = (1 << 8), // 0x0100 + fIgnoreObjects = (1 << 9), // 0x0200 + fUpdatePos = (1 << 10), // 0x0400 + fOnLand = (1 << 11), // 0x0800 + fDontupdate = (1 << 12), // 0x1000 + fFixLoop = (1 << 13), // 0x2000 + fDidntMove = (1 << 14), // 0x4000 + fAdjEgoXY = (1 << 15) // 0x8000 }; /** - * AGI view table entry + * AGI screen object table entry */ -struct VtEntry { +struct ScreenObjEntry { + int16 objectNr; // 0-255 -> regular screenObjTable, -1 -> addToPic-view uint8 stepTime; uint8 stepTimeCount; - uint8 entry; int16 xPos; int16 yPos; - uint8 currentView; + uint8 currentViewNr; bool viewReplaced; struct AgiView *viewData; - uint8 currentLoop; - uint8 numLoops; - struct ViewLoop *loopData; - uint8 currentCel; - uint8 numCels; - struct ViewCel *celData; - struct ViewCel *celData2; - int16 xPos2; - int16 yPos2; - void *s; + uint8 currentLoopNr; + uint8 loopCount; + struct AgiViewLoop *loopData; + uint8 currentCelNr; + uint8 celCount; + struct AgiViewCel *celData; + //int16 xPos2; + //int16 yPos2; int16 xSize; int16 ySize; + + int16 xPos_prev; + int16 yPos_prev; + int16 xSize_prev; + int16 ySize_prev; + uint8 stepSize; uint8 cycleTime; uint8 cycleTimeCount; uint8 direction; - MotionType motion; + MotionType motionType; CycleType cycle; uint8 priority; uint16 flags; - uint8 parm1; - uint8 parm2; - uint8 parm3; - uint8 parm4; + // kMotionMoveObj + int16 move_x; + int16 move_y; + uint8 move_stepSize; + uint8 move_flag; + // kMotionFollowEgo + uint8 follow_stepSize; + uint8 follow_flag; + uint8 follow_count; + // kMotionWander + uint8 wander_count; + // end of motion related variables + uint8 loop_flag; }; // struct vt_entry } // End of namespace Agi diff --git a/engines/agi/words.cpp b/engines/agi/words.cpp index 438c1ce354..7eca2b82c8 100644 --- a/engines/agi/words.cpp +++ b/engines/agi/words.cpp @@ -21,22 +21,23 @@ */ #include "agi/agi.h" +#include "agi/words.h" #include "common/textconsole.h" namespace Agi { -// -// Local implementation to avoid problems with strndup() used by -// gcc 3.2 Cygwin (see #635984) -// -static char *myStrndup(const char *src, int n) { - char *tmp = strncpy((char *)malloc(n + 1), src, n); - tmp[n] = 0; - return tmp; +Words::Words(AgiEngine *vm) { + _vm = vm; + + clearEgoWords(); +} + +Words::~Words() { + clearEgoWords(); } -int AgiEngine::loadWords_v1(Common::File &f) { +int Words::loadDictionary_v1(Common::File &f) { char str[64]; int k; @@ -55,18 +56,21 @@ int AgiEngine::loadWords_v1(Common::File &f) { // And store it in our internal dictionary if (k > 0) { - AgiWord *w = new AgiWord; - w->word = myStrndup(str, k + 1); - w->id = f.readUint16LE(); - _game.words[str[0] - 'a'].push_back(w); - debug(3, "'%s' (%d)", w->word, w->id); + WordEntry *newWord = new WordEntry; + byte firstCharNr = str[0] - 'a'; + + newWord->word = Common::String(str, k + 1); // myStrndup(str, k + 1); + newWord->id = f.readUint16LE(); + + _dictionaryWords[firstCharNr].push_back(newWord); + debug(3, "'%s' (%d)", newWord->word.c_str(), newWord->id); } } while((uint8)str[0] != 0xFF); return errOK; } -int AgiEngine::loadWords(const char *fname) { +int Words::loadDictionary(const char *fname) { Common::File fp; if (!fp.open(fname)) { @@ -99,10 +103,10 @@ int AgiEngine::loadWords(const char *fname) { // See bug #3615061 if (str[0] == 'a' + i) { // And store it in our internal dictionary - AgiWord *w = new AgiWord; - w->word = myStrndup(str, k); - w->id = fp.readUint16BE(); - _game.words[i].push_back(w); + WordEntry *newWord = new WordEntry; + newWord->word = Common::String(str, k); + newWord->id = fp.readUint16BE(); + _dictionaryWords[i].push_back(newWord); } k = fp.readByte(); @@ -119,115 +123,249 @@ int AgiEngine::loadWords(const char *fname) { return errOK; } -void AgiEngine::unloadWords() { - for (int i = 0; i < 26; i++) - _game.words[i].clear(); -} +void Words::unloadDictionary() { + for (int16 firstCharNr = 0; firstCharNr < 26; firstCharNr++) { + Common::Array<WordEntry *> &dictionary = _dictionaryWords[firstCharNr]; + int16 dictionarySize = dictionary.size(); -/** - * Find a word in the dictionary - * Uses an algorithm hopefully like the one Sierra used. Returns the ID - * of the word and the length in flen. Returns -1 if not found. - */ -int AgiEngine::findWord(const char *word, int *flen) { - int c; - int result = -1; - - debugC(2, kDebugLevelScripts, "find_word(%s)", word); - - if (word[0] >= 'a' && word[0] <= 'z') - c = word[0] - 'a'; - else - return -1; - - *flen = 0; - Common::Array<AgiWord *> &a = _game.words[c]; - - for (int i = 0; i < (int)a.size(); i++) { - int wlen = strlen(a[i]->word); - // Keep looking till we find the word itself, or the whole phrase. - // Try to find the best match (i.e. the longest matching phrase). - if (!strncmp(a[i]->word, word, wlen) && (word[wlen] == 0 || word[wlen] == 0x20) && wlen >= *flen) { - *flen = wlen; - result = a[i]->id; + for (int16 dictionaryWordNr = 0; dictionaryWordNr < dictionarySize; dictionaryWordNr++) { + delete dictionary[dictionaryWordNr]; } - } - return result; + _dictionaryWords[firstCharNr].clear(); + } } -void AgiEngine::dictionaryWords(char *msg) { - char *p = NULL; - char *q = NULL; - int wid, wlen; - - assert(msg); - - debugC(2, kDebugLevelScripts, "msg = \"%s\"", msg); +void Words::clearEgoWords() { + for (int16 wordNr = 0; wordNr < MAX_WORDS; wordNr++) { + _egoWords[wordNr].id = 0; + _egoWords[wordNr].word.clear(); + } + _egoWordCount = 0; +} - cleanInput(); - for (p = msg; *p && getvar(vWordNotFound) == 0;) { - if (*p == 0x20) - p++; +static bool isCharSeparator(const char curChar) { + switch (curChar) { + case ' ': + case ',': + case '.': + case '?': + case '!': + case '(': + case ')': + case ';': + case ':': + case '[': + case ']': + case '{': + case '}': + return true; + break; + default: + break; + } + return false; +} - if (*p == 0) - break; +static bool isCharInvalid(const char curChar) { + switch (curChar) { + case 0x27: // ' + case 0x60: // ` + case '-': + case '\\': + case '"': + return true; + break; + default: + break; + } + return false; +} - wid = findWord(p, &wlen); - debugC(2, kDebugLevelScripts, "find_word(p) == %d", wid); +void Words::cleanUpInput(const char *rawUserInput, Common::String &cleanInput) { + byte curChar = 0; - switch (wid) { - case -1: - debugC(2, kDebugLevelScripts, "unknown word"); - _game.egoWords[_game.numEgoWords].word = strdup(p); + cleanInput.clear(); - q = _game.egoWords[_game.numEgoWords].word; + curChar = *rawUserInput; + while (curChar) { + // skip separators / invalid characters + if (isCharSeparator(curChar) || isCharInvalid(curChar)) { + rawUserInput++; + curChar = *rawUserInput; + } else { + do { + if (!isCharInvalid(curChar)) { + // not invalid char, add it to the cleaned up input + cleanInput += curChar; + } + + rawUserInput++; + curChar = *rawUserInput; + + if (isCharSeparator(curChar)) { + cleanInput += ' '; + break; + } + } while (curChar); + } + } + if (cleanInput.hasSuffix(" ")) { + // ends with a space? remove it + cleanInput.deleteLastChar(); + } - _game.egoWords[_game.numEgoWords].id = 19999; - setvar(vWordNotFound, 1 + _game.numEgoWords); + // Sierra compared independent of upper case and lower case + cleanInput.toLowercase(); +} - _game.numEgoWords++; +int16 Words::findWordInDictionary(const Common::String &userInput, uint16 userInputLen, uint16 userInputPos, uint16 &foundWordLen) { + uint16 userInputLeft = userInputLen - userInputPos; + uint16 wordStartPos = userInputPos; + int16 wordId = DICTIONARY_RESULT_UNKNOWN; + byte firstChar = userInput[userInputPos]; + byte curUserInputChar = 0; + + foundWordLen = 0; + + if ((firstChar >= 'a') && (firstChar <= 'z')) { + // word has to start with a letter + if (((userInputPos + 1) == userInputLen) || (userInput[1] == ' ')) { + // current word is 1 char only? + if ((firstChar == 'a') || (firstChar == 'i')) { + // and it's "a" or "i"? -> then set current type to ignore + wordId = DICTIONARY_RESULT_IGNORE; + } + } - p += strlen(p); - break; - case 0: - // ignore this word - debugC(2, kDebugLevelScripts, "ignore word"); - p += wlen; - q = NULL; - break; - default: - // an OK word - debugC(3, kDebugLevelScripts, "ok word (%d)", wid); - _game.egoWords[_game.numEgoWords].id = wid; - _game.egoWords[_game.numEgoWords].word = myStrndup(p, wlen); - _game.numEgoWords++; - p += wlen; - break; + Common::Array<WordEntry *> &dictionary = _dictionaryWords[firstChar - 'a']; + int16 dictionarySize = dictionary.size(); + + for (int16 dictionaryWordNr = 0; dictionaryWordNr < dictionarySize; dictionaryWordNr++) { + WordEntry *dictionaryEntry = dictionary[dictionaryWordNr]; + uint16 dictionaryWordLen = dictionaryEntry->word.size(); + + if (dictionaryWordLen <= userInputLeft) { + // dictionary word is longer or same length as the remaining user input + uint16 curCompareLeft = dictionaryWordLen; + uint16 dictionaryWordPos = 0; + byte curDictionaryChar = 0; + + userInputPos = wordStartPos; + while (curCompareLeft) { + curUserInputChar = userInput[userInputPos]; + curDictionaryChar = dictionaryEntry->word[dictionaryWordPos]; + + if (curUserInputChar != curDictionaryChar) + break; + + userInputPos++; + dictionaryWordPos++; + curCompareLeft--; + } + + if (!curCompareLeft) { + // fully matched, remember match + wordId = dictionaryEntry->id; + foundWordLen = dictionaryWordLen; + + // follow-up character in user-input is a space? add that to the word length + if (userInputLeft == foundWordLen) { + // perfect match -> break + break; + } + } + } } + } - if (*p) { - debugC(2, kDebugLevelScripts, "p = %s", p); - *p = 0; - p++; + if (foundWordLen == 0) { + userInputPos = wordStartPos; + while (userInputPos < userInputLen) { + if (userInput[userInputPos] == ' ') { + break; + } + userInputPos++; } + foundWordLen = userInputPos - wordStartPos; + } + return wordId; +} - if (q != NULL) { - for (; (*q != 0 && *q != 0x20); q++) - ; - if (*q) { - *q = 0; - q++; +void Words::parseUsingDictionary(char *rawUserInput) { + Common::String userInput; + const char *userInputPtr = nullptr; + uint16 userInputLen; + uint16 userInputPos = 0; + uint16 foundWordPos; + int16 foundWordId; + uint16 foundWordLen = 0; + uint16 wordCount = 0; + + assert(rawUserInput); + debugC(2, kDebugLevelScripts, "parse: userinput = \"%s\"", rawUserInput); + + // Reset result + clearEgoWords(); + + // clean up user input + cleanUpInput(rawUserInput, userInput); + + userInputLen = userInput.size(); + userInputPtr = userInput.c_str(); + + while (userInputPos < userInputLen) { + // Skip trailing space + if (userInput[userInputPos] == ' ') + userInputPos++; + + foundWordPos = userInputPos; + foundWordId = findWordInDictionary(userInput, userInputLen, userInputPos, foundWordLen); + + if (foundWordId != DICTIONARY_RESULT_IGNORE) { + // word not supposed to get ignored + // add it now + if (foundWordId != DICTIONARY_RESULT_UNKNOWN) { + // known word + _egoWords[wordCount].id = foundWordId; + } + + _egoWords[wordCount].word = Common::String(userInputPtr + foundWordPos, foundWordLen); + debugC(2, kDebugLevelScripts, "found word %s (id %d)", _egoWords[wordCount].word.c_str(), _egoWords[wordCount].id); + wordCount++; + + if (foundWordId == DICTIONARY_RESULT_UNKNOWN) { + // unknown word + _vm->setVar(VM_VAR_WORD_NOT_FOUND, wordCount); + break; // and exit now } } + + userInputPos += foundWordLen; } - debugC(4, kDebugLevelScripts, "num_ego_words = %d", _game.numEgoWords); - if (_game.numEgoWords > 0) { - setflag(fEnteredCli, true); - setflag(fSaidAcceptedInput, false); + _egoWordCount = wordCount; + + debugC(4, kDebugLevelScripts, "ego word count = %d", _egoWordCount); + if (_egoWordCount > 0) { + _vm->setflag(VM_FLAG_ENTERED_CLI, true); + } else { + _vm->setflag(VM_FLAG_ENTERED_CLI, false); } + _vm->setflag(VM_FLAG_SAID_ACCEPTED_INPUT, false); +} + +uint16 Words::getEgoWordCount() { + return _egoWordCount; +} +const char *Words::getEgoWord(int16 wordNr) { + assert(wordNr >= 0 && wordNr < MAX_WORDS); + return _egoWords[wordNr].word.c_str(); +} +uint16 Words::getEgoWordId(int16 wordNr) { + assert(wordNr >= 0 && wordNr < MAX_WORDS); + return _egoWords[wordNr].id; } } // End of namespace Agi diff --git a/engines/agi/words.h b/engines/agi/words.h new file mode 100644 index 0000000000..c7bf4829c3 --- /dev/null +++ b/engines/agi/words.h @@ -0,0 +1,69 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef AGI_WORDS_H +#define AGI_WORDS_H + +namespace Agi { + +#define DICTIONARY_RESULT_UNKNOWN -1 +#define DICTIONARY_RESULT_IGNORE 0 + +struct WordEntry { + uint16 id; + Common::String word; +}; + +class Words { +public: + Words(AgiEngine *vm); + ~Words(); + +private: + AgiEngine *_vm; + + // Dictionary + Common::Array<WordEntry *> _dictionaryWords[26]; + + WordEntry _egoWords[MAX_WORDS]; + uint16 _egoWordCount; + +public: + uint16 getEgoWordCount(); + const char *getEgoWord(int16 wordNr); + uint16 getEgoWordId(int16 wordNr); + + int loadDictionary_v1(Common::File &f); + int loadDictionary(const char *fname); + void unloadDictionary(); + + void clearEgoWords(); + void parseUsingDictionary(char *rawUserInput); + +private: + void cleanUpInput(const char *userInput, Common::String &cleanInput); + int16 findWordInDictionary(const Common::String &userInput, uint16 userInputLen, uint16 userInputPos, uint16 &foundWordLen); +}; + +} // End of namespace Agi + +#endif /* AGI_WORDS_H */ |