diff options
Diffstat (limited to 'engines/agi/agi.cpp')
-rw-r--r-- | engines/agi/agi.cpp | 917 |
1 files changed, 539 insertions, 378 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 |