diff options
Diffstat (limited to 'engines/agi')
| -rw-r--r-- | engines/agi/agi.cpp | 100 | ||||
| -rw-r--r-- | engines/agi/agi.h | 21 | ||||
| -rw-r--r-- | engines/agi/agi_v3.cpp | 2 | ||||
| -rw-r--r-- | engines/agi/checks.cpp | 2 | ||||
| -rw-r--r-- | engines/agi/detection.cpp | 217 | ||||
| -rw-r--r-- | engines/agi/graphics.cpp | 297 | ||||
| -rw-r--r-- | engines/agi/module.mk | 1 | ||||
| -rw-r--r-- | engines/agi/op_cmd.cpp | 19 | ||||
| -rw-r--r-- | engines/agi/picture.cpp | 24 | ||||
| -rw-r--r-- | engines/agi/picture.h | 2 | ||||
| -rw-r--r-- | engines/agi/predictive.cpp | 565 | ||||
| -rw-r--r-- | engines/agi/sprite.cpp | 30 | ||||
| -rw-r--r-- | engines/agi/sprite.h | 2 | ||||
| -rw-r--r-- | engines/agi/view.cpp | 16 | ||||
| -rw-r--r-- | engines/agi/view.h | 1 |
15 files changed, 949 insertions, 350 deletions
diff --git a/engines/agi/agi.cpp b/engines/agi/agi.cpp index a0496bf00b..74795271fc 100644 --- a/engines/agi/agi.cpp +++ b/engines/agi/agi.cpp @@ -69,6 +69,8 @@ void AgiEngine::processEvents() { _system->quit(); break; case Common::EVENT_PREDICTIVE_DIALOG: + if (_predictiveDialogRunning) + break; if (_game.playerControl && predictiveDialog()) { if (_game.inputMode == INPUT_NORMAL) { strcpy((char *)_game.inputBuffer, _predictiveResult); @@ -115,7 +117,7 @@ void AgiEngine::processEvents() { _keyControl = 0; _keyAlt = 0; - if (event.kbd.flags == Common::KBD_CTRL && event.kbd.keycode == 'd') { + if (event.kbd.flags == Common::KBD_CTRL && event.kbd.keycode == Common::KEYCODE_d) { _console->attach(); break; } @@ -127,106 +129,107 @@ void AgiEngine::processEvents() { _keyAlt = 1; switch (key = event.kbd.keycode) { - case 256 + 20: // left arrow - case 260: // key pad 4 + case Common::KEYCODE_LEFT: + case Common::KEYCODE_KP4: if (_allowSynthetic || !event.synthetic) key = KEY_LEFT; break; - case 256 + 19: // right arrow - case 262: // key pad 6 + case Common::KEYCODE_RIGHT: + case Common::KEYCODE_KP6: if (_allowSynthetic || !event.synthetic) key = KEY_RIGHT; break; - case 256 + 17: // up arrow - case 264: // key pad 8 + case Common::KEYCODE_UP: + case Common::KEYCODE_KP8: if (_allowSynthetic || !event.synthetic) key = KEY_UP; break; - case 256 + 18: // down arrow - case 258: // key pad 2 + case Common::KEYCODE_DOWN: + case Common::KEYCODE_KP2: if (_allowSynthetic || !event.synthetic) key = KEY_DOWN; break; - case 256 + 24: // page up - case 265: // key pad 9 + case Common::KEYCODE_PAGEUP: + case Common::KEYCODE_KP9: if (_allowSynthetic || !event.synthetic) key = KEY_UP_RIGHT; break; - case 256 + 25: // page down - case 259: // key pad 3 + case Common::KEYCODE_PAGEDOWN: + case Common::KEYCODE_KP3: if (_allowSynthetic || !event.synthetic) key = KEY_DOWN_RIGHT; break; - case 256 + 22: // home - case 263: // key pad 7 + case Common::KEYCODE_HOME: + case Common::KEYCODE_KP7: if (_allowSynthetic || !event.synthetic) key = KEY_UP_LEFT; break; - case 256 + 23: // end - case 257: // key pad 1 + case Common::KEYCODE_END: + case Common::KEYCODE_KP1: if (_allowSynthetic || !event.synthetic) key = KEY_DOWN_LEFT; break; - case 261: // key pad 5 + case Common::KEYCODE_KP5: key = KEY_STATIONARY; break; - case '+': + case Common::KEYCODE_PLUS: key = '+'; break; - case '-': + case Common::KEYCODE_MINUS: key = '-'; break; - case 9: + case Common::KEYCODE_9: key = 0x0009; break; - case 282: + case Common::KEYCODE_F1: key = 0x3b00; break; - case 283: + case Common::KEYCODE_F2: key = 0x3c00; break; - case 284: + case Common::KEYCODE_F3: key = 0x3d00; break; - case 285: + case Common::KEYCODE_F4: key = 0x3e00; break; - case 286: + case Common::KEYCODE_F5: key = 0x3f00; break; - case 287: + case Common::KEYCODE_F6: key = 0x4000; break; - case 288: + case Common::KEYCODE_F7: key = 0x4100; break; - case 289: + case Common::KEYCODE_F8: key = 0x4200; break; - case 290: + case Common::KEYCODE_F9: key = 0x4300; break; - case 291: + case Common::KEYCODE_F10: key = 0x4400; break; - case 292: + case Common::KEYCODE_F11: key = KEY_STATUSLN; break; - case 293: + case Common::KEYCODE_F12: key = KEY_PRIORITY; break; - case 27: + case Common::KEYCODE_ESCAPE: key = 0x1b; break; - case '\n': - case '\r': + case Common::KEYCODE_RETURN: + case Common::KEYCODE_KP_ENTER: key = KEY_ENTER; break; + case Common::KEYCODE_BACKSPACE: + key = KEY_BACKSPACE; + break; default: if (key < 256 && !isalpha(key)) { - // Make sure backspace works right (this fixes a small issue on OS X) - if (key != 8) - key = event.kbd.ascii; + key = event.kbd.ascii; break; } if (_keyControl) @@ -410,6 +413,10 @@ 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) report("Amiga padded game detected.\n"); @@ -591,7 +598,10 @@ AgiEngine::AgiEngine(OSystem *syst) : Engine(syst) { _oldMode = -1; - _searchTreeRoot = 0; + _predictiveDialogRunning = false; + _predictiveDictText = NULL; + _predictiveDictLine = NULL; + _predictiveDictLineCount = 0; _firstSlot = 0; } @@ -640,7 +650,10 @@ void AgiEngine::initialize() { _game.name[0] = '\0'; - _game.sbuf = (uint8 *)calloc(_WIDTH, _HEIGHT); + _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(); _sound->initSound(); @@ -668,10 +681,13 @@ AgiEngine::~AgiEngine() { delete _sound; _gfx->deinitVideo(); delete _sprites; - free(_game.sbuf); + free(_game.sbufOrig); _gfx->deinitMachine(); delete _rnd; delete _console; + + free(_predictiveDictLine); + free(_predictiveDictText); } int AgiEngine::init() { diff --git a/engines/agi/agi.h b/engines/agi/agi.h index 0167609514..56ae1288b8 100644 --- a/engines/agi/agi.h +++ b/engines/agi/agi.h @@ -371,7 +371,14 @@ struct AgiGame { char cursorChar; unsigned int colorFg; unsigned int colorBg; - uint8 *sbuf; /**< 160x168 AGI screen buffer */ +#define SBUF16_OFFSET 0 +#define SBUF256_OFFSET ((_WIDTH) * (_HEIGHT)) +#define FROM_SBUF16_TO_SBUF256_OFFSET ((SBUF256_OFFSET) - (SBUF16_OFFSET)) +#define FROM_SBUF256_TO_SBUF16_OFFSET ((SBUF16_OFFSET) - (SBUF256_OFFSET)) + 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]; @@ -767,14 +774,16 @@ private: void loadDict(void); bool matchWord(void); - SearchTree *_searchTreeRoot; - SearchTree *_activeTreeNode; - - void insertSearchNode(const char *word); - + // Predictive dialog + // TODO: Move this to a separate class + char *_predictiveDictText; + char **_predictiveDictLine; + int32 _predictiveDictLineCount; + char *_predictiveDictActLine; String _currentCode; String _currentWord; int _wordNumber; + bool _predictiveDialogRunning; public: char _predictiveResult[40]; }; diff --git a/engines/agi/agi_v3.cpp b/engines/agi/agi_v3.cpp index 33f670e779..69a8698ecb 100644 --- a/engines/agi/agi_v3.cpp +++ b/engines/agi/agi_v3.cpp @@ -64,7 +64,7 @@ int AgiLoader_v3::detectGame() { if (f.hasSuffix("vol.0")) { memset(_vm->_game.name, 0, 8); - strncpy(_vm->_game.name, f.c_str(), f.size() > 5 ? f.size() - 5 : f.size()); + strncpy(_vm->_game.name, f.c_str(), MIN((uint)8, f.size() > 5 ? f.size() - 5 : f.size())); debugC(3, kDebugLevelMain, "game.name = %s", _vm->_game.name); _intVersion = 0x3149; // setup for 3.002.149 ec = _vm->v3IdGame(); diff --git a/engines/agi/checks.cpp b/engines/agi/checks.cpp index c9fafbbbbf..5d71af10cf 100644 --- a/engines/agi/checks.cpp +++ b/engines/agi/checks.cpp @@ -117,7 +117,7 @@ int AgiEngine::checkPriority(VtEntry *v) { water = 1; - p0 = &_game.sbuf[v->xPos + v->yPos * _WIDTH]; + p0 = &_game.sbuf16c[v->xPos + v->yPos * _WIDTH]; for (i = 0; i < v->xSize; i++, p0++) { pri = *p0 >> 4; diff --git a/engines/agi/detection.cpp b/engines/agi/detection.cpp index b7f8c12730..1ab59c0806 100644 --- a/engines/agi/detection.cpp +++ b/engines/agi/detection.cpp @@ -31,6 +31,7 @@ #include "common/file.h" #include "agi/agi.h" +#include "agi/wagparser.h" namespace Agi { @@ -1612,8 +1613,8 @@ static const AGIGameDescription gameDescriptions[] = { FANMADE("AGI Quest (v1.46-TJ0)", "1cf1a5307c1a0a405f5039354f679814"), FANMADE_I("tetris", "", "7a874e2db2162e7a4ce31c9130248d8a"), FANMADE_V("AGI Trek (Demo)", "c02882b8a8245b629c91caf7eb78eafe", 0x2440), - FANMADE("AGI256 Demo", "79261ac143b2e2773b2753674733b0d5"), - FANMADE("AGI256-2 Demo", "3cad9b3aff1467cebf0c5c5b110985c5"), + FANMADE_F("AGI256 Demo", "79261ac143b2e2773b2753674733b0d5", GF_AGI256), + FANMADE_F("AGI256-2 Demo", "3cad9b3aff1467cebf0c5c5b110985c5", GF_AGI256_2), FANMADE_LF("Abrah: L'orphelin de l'espace (v1.2)", "b7b6d1539e14d5a26fa3088288e1badc", Common::FR_FRA, GF_AGIPAL), FANMADE("Acidopolis", "7017db1a4b726d0d59e65e9020f7d9f7"), FANMADE("Agent 0055 (v1.0)", "c2b34a0c77acb05482781dda32895f24"), @@ -1648,7 +1649,7 @@ static const AGIGameDescription gameDescriptions[] = { FANMADE("DG: The Adventure Game (English v1.1)", "0d6376d493fa7a21ec4da1a063e12b25"), FANMADE_L("DG: The Adventure Game (French v1.1)", "258bdb3bb8e61c92b71f2f456cc69e23", Common::FR_FRA), FANMADE("Dashiki (16 Colors)", "9b2c7b9b0283ab9f12bedc0cb6770a07"), - FANMADE_F("Dashiki (256 Colors)", "c68052bb209e23b39b55ff3d759958e6", GF_AGIMOUSE), + FANMADE_F("Dashiki (256 Colors)", "c68052bb209e23b39b55ff3d759958e6", GF_AGIMOUSE|GF_AGI256), FANMADE("Date Quest 1 (v1.0)", "ba3dcb2600645be53a13170aa1a12e69"), FANMADE("Date Quest 2 (v1.0 Demo)", "1602d6a2874856e928d9a8c8d2d166e9"), FANMADE("Date Quest 2 (v1.0)", "f13f6fc85aa3e6e02b0c20408fb63b47"), @@ -1825,112 +1826,178 @@ static const AGIGameDescription gameDescriptions[] = { { AD_TABLE_END_MARKER, 0, 0, 0, 0 } }; -static const AGIGameDescription fallbackDescs[] = { - { - { - "agi-fanmade", - "Unknown v2 Game", - AD_ENTRY1(0, 0), - Common::UNK_LANG, - Common::kPlatformPC, - Common::ADGF_NO_FLAGS - }, - GID_FANMADE, - GType_V2, - GF_FANMADE, - 0x2917, - }, - { - { - "agi-fanmade", - "Unknown v2 AGIPAL Game", - AD_ENTRY1(0, 0), - Common::UNK_LANG, - Common::kPlatformPC, - Common::ADGF_NO_FLAGS - }, - GID_FANMADE, - GType_V2, - GF_FANMADE | GF_AGIPAL, - 0x2917, - }, - { - { - "agi-fanmade", - "Unknown v3 Game", - AD_ENTRY1(0, 0), - Common::UNK_LANG, - Common::kPlatformPC, - Common::ADGF_NO_FLAGS - }, - GID_FANMADE, - GType_V3, - GF_FANMADE, - 0x3149, - }, +/** + * The fallback game descriptor used by the AGI engine's fallbackDetector. + * Contents of this struct are to be overwritten by the fallbackDetector. + */ +static AGIGameDescription g_fallbackDesc = { + { + "", // Not used by the fallback descriptor, it uses the EncapsulatedADGameDesc's gameid + "", // Not used by the fallback descriptor, it uses the EncapsulatedADGameDesc's extra + AD_ENTRY1(0, 0), // This should always be AD_ENTRY1(0, 0) in the fallback descriptor + Common::UNK_LANG, + Common::kPlatformPC, + Common::ADGF_NO_FLAGS + }, + GID_FANMADE, + GType_V2, + GF_FANMADE, + 0x2917, }; -Common::ADGameDescList fallbackDetector(const FSList *fslist) { - Common::String tstr; +Common::EncapsulatedADGameDesc fallbackDetector(const FSList *fslist) { typedef Common::HashMap<Common::String, int32, Common::CaseSensitiveString_Hash, Common::CaseSensitiveString_EqualTo> IntMap; IntMap allFiles; - Common::ADGameDescList matched; - int matchedNum = -1; + bool matchedUsingFilenames = false; + bool matchedUsingWag = false; + int wagFileCount = 0; + WagFileParser wagFileParser; + Common::String wagFilePath; + Common::String gameid("agi-fanmade"), description, extra; // Set the defaults for gameid, description and extra + FSList fslistCurrentDir; // Only used if fslist == NULL + + // Use the current directory for searching if fslist == NULL + if (fslist == NULL) { + FilesystemNode fsCurrentDir("."); + fslistCurrentDir.push_back(fsCurrentDir); + fslist = &fslistCurrentDir; + } - // TODO: - // WinAGI produces *.wag file with interpreter version, game name - // and other parameters. Add support for this once specs are known + // Set the default values for the fallback descriptor's ADGameDescription part. + g_fallbackDesc.desc.language = Common::UNK_LANG; + g_fallbackDesc.desc.platform = Common::kPlatformPC; + g_fallbackDesc.desc.flags = Common::ADGF_NO_FLAGS; + // Set default values for the fallback descriptor's AGIGameDescription part. + g_fallbackDesc.gameID = GID_FANMADE; + g_fallbackDesc.features = GF_FANMADE; + g_fallbackDesc.version = 0x2917; - // First grab all filenames + // First grab all filenames and at the same time count the number of *.wag files for (FSList::const_iterator file = fslist->begin(); file != fslist->end(); ++file) { if (file->isDirectory()) continue; - tstr = file->getName(); - tstr.toLowercase(); - - allFiles[tstr] = true; + Common::String filename = file->getName(); + filename.toLowercase(); + allFiles[filename] = true; // Save the filename in a hash table + + if (filename.hasSuffix(".wag")) { + // Save latest found *.wag file's path (Can be used to open the file, the name can't) + wagFilePath = file->getPath(); + wagFileCount++; // Count found *.wag files + } } - // Now check for v2 if (allFiles.contains("logdir") && allFiles.contains("object") && allFiles.contains("picdir") && allFiles.contains("snddir") && allFiles.contains("viewdir") && allFiles.contains("vol.0") && - allFiles.contains("words.tok")) { - matchedNum = 0; - - // Check if it is AGIPAL - if (allFiles.contains("pal.101")) - matchedNum = 1; + allFiles.contains("words.tok")) { // Check for v2 + + // The default AGI interpreter version 0x2917 is okay for v2 games + // so we don't have to change it here. + matchedUsingFilenames = true; + + if (allFiles.contains("pal.101")) { // Check if it is AGIPAL + description = "Unknown v2 AGIPAL Game"; + g_fallbackDesc.features |= GF_AGIPAL; // Add AGIPAL feature flag + } else { // Not AGIPAL so just plain v2 + description = "Unknown v2 Game"; + } } else { // Try v3 char name[8]; for (IntMap::const_iterator f = allFiles.begin(); f != allFiles.end(); ++f) { if (f->_key.hasSuffix("vol.0")) { memset(name, 0, 8); - strncpy(name, f->_key.c_str(), f->_key.size() > 5 ? f->_key.size() - 5 : f->_key.size()); + strncpy(name, f->_key.c_str(), MIN((uint)8, f->_key.size() > 5 ? f->_key.size() - 5 : f->_key.size())); if (allFiles.contains("object") && allFiles.contains("words.tok") && allFiles.contains(Common::String(name) + "dir")) { - matchedNum = 2; + matchedUsingFilenames = true; + description = "Unknown v3 Game"; + g_fallbackDesc.version = 0x3149; // Set the default AGI version for an AGI v3 game break; } } } } - - if (matchedNum != -1) { - matched.push_back(&fallbackDescs[matchedNum].desc); + + // WinAGI produces *.wag file with interpreter version, game name and other parameters. + // If there's exactly one *.wag file and it parses successfully then we'll use its information. + if (wagFileCount == 1 && wagFileParser.parse(wagFilePath.c_str())) { + matchedUsingWag = true; + + const WagProperty *wagAgiVer = wagFileParser.getProperty(WagProperty::PC_INTVERSION); + const WagProperty *wagGameID = wagFileParser.getProperty(WagProperty::PC_GAMEID); + const WagProperty *wagGameDesc = wagFileParser.getProperty(WagProperty::PC_GAMEDESC); + const WagProperty *wagGameVer = wagFileParser.getProperty(WagProperty::PC_GAMEVERSION); + const WagProperty *wagGameLastEdit = wagFileParser.getProperty(WagProperty::PC_GAMELAST); + + // If there is an AGI version number in the *.wag file then let's use it + if (wagAgiVer != NULL && wagFileParser.checkAgiVersionProperty(*wagAgiVer)) { + // TODO/FIXME: Check that version number is something we support before trying to use it. + // If the version number is unsupported then it'll get switched to 0x2917 later. + // But there's the possibility that file based detection has detected something else + // than a v2 AGI game. So there's a possibility for conflicting information. + g_fallbackDesc.version = wagFileParser.convertToAgiVersionNumber(*wagAgiVer); + } + + // Set gameid according to *.wag file information if it's present and it doesn't contain whitespace. + if (wagGameID != NULL && !Common::String(wagGameID->getData()).contains(" ")) { + gameid = wagGameID->getData(); + debug(3, "Agi::fallbackDetector: Using game id (%s) from WAG file", gameid.c_str()); + } + + // Set game description and extra according to *.wag file information if they're present + if (wagGameDesc != NULL) { + description = wagGameDesc->getData(); + debug(3, "Agi::fallbackDetector: Game description (%s) from WAG file", wagGameDesc->getData()); + + // If there's game version in the *.wag file, set extra to it + if (wagGameVer != NULL) { + extra = wagGameVer->getData(); + debug(3, "Agi::fallbackDetector: Game version (%s) from WAG file", wagGameVer->getData()); + } + + // If there's game last edit date in the *.wag file, add it to extra + if (wagGameLastEdit != NULL) { + if (!extra.empty() ) extra += " "; + extra += wagGameLastEdit->getData(); + debug(3, "Agi::fallbackDetector: Game's last edit date (%s) from WAG file", wagGameLastEdit->getData()); + } + } + } else if (wagFileCount > 1) { // More than one *.wag file, confusing! So let's not use them. + warning("More than one (%d) *.wag files found. WAG files ignored", wagFileCount); + } + + // Check that the AGI interpreter version is a supported one + if (!(g_fallbackDesc.version >= 0x2000 && g_fallbackDesc.version < 0x4000)) { + warning("Unsupported AGI interpreter version 0x%x in AGI's fallback detection. Using default 0x2917", g_fallbackDesc.version); + g_fallbackDesc.version = 0x2917; + } + + // Set game type (v2 or v3) according to the AGI interpreter version number + if (g_fallbackDesc.version >= 0x2000 && g_fallbackDesc.version < 0x3000) + g_fallbackDesc.gameType = GType_V2; + else if (g_fallbackDesc.version >= 0x3000 && g_fallbackDesc.version < 0x4000) + g_fallbackDesc.gameType = GType_V3; + + // Check if we found a match with any of the fallback methods + Common::EncapsulatedADGameDesc result; + if (matchedUsingWag || matchedUsingFilenames) { + extra = description + " " + extra; // Let's combine the description and extra + result = Common::EncapsulatedADGameDesc((const Common::ADGameDescription *)&g_fallbackDesc, gameid, extra); printf("Your game version has been detected using fallback matching as a\n"); - printf("variant of %s (%s).\n", fallbackDescs[matchedNum].desc.gameid, fallbackDescs[matchedNum].desc.extra); + printf("variant of %s (%s).\n", result.getGameID(), result.getExtra()); printf("If this is an original and unmodified version or new made Fanmade game,\n"); printf("please report any, information previously printed by ScummVM to the team.\n"); + } - return matched; + return result; } -} +} // End of namespace Agi static const Common::ADParams detectionParams = { // Pointer to ADGameDescription or its superset structure @@ -1960,7 +2027,9 @@ REGISTER_PLUGIN(AGI, "AGI v2 + v3 Engine", "Sierra AGI Engine (C) Sierra On-Line namespace Agi { bool AgiEngine::initGame() { - _gameDescription = (const AGIGameDescription *)Common::AdvancedDetector::detectBestMatchingGame(detectionParams); + Common::EncapsulatedADGameDesc encapsulatedDesc = Common::AdvancedDetector::detectBestMatchingGame(detectionParams); + _gameDescription = (const AGIGameDescription *)(encapsulatedDesc.realDesc); + return (_gameDescription != 0); } diff --git a/engines/agi/graphics.cpp b/engines/agi/graphics.cpp index 8f62ccc08d..2d64a4352e 100644 --- a/engines/agi/graphics.cpp +++ b/engines/agi/graphics.cpp @@ -89,6 +89,269 @@ uint8 newPalette[16 * 3] = { 0x3F, 0x3F, 0x3F }; +/** + * 256 color palette used with AGI256 and AGI256-2 games. + * Uses full 8 bits per color component. + */ +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 +}; + static uint16 cgaMap[16] = { 0x0000, /* 0 - black */ 0x0d00, /* 1 - blue */ @@ -135,7 +398,10 @@ GfxMgr::GfxMgr(AgiEngine *vm) : _vm(vm) { * | * Layer 2: 320x200 ================== AGI engine screen (console), put_pixel() * | - * Layer 1: 160x168 ================== AGI screen + * 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 @@ -373,15 +639,25 @@ void GfxMgr::initPalette(uint8 *p) { void GfxMgr::gfxSetPalette() { int i; - byte pal[32 * 4]; - - for (i = 0; i < 32; i++) { - pal[i * 4 + 0] = _palette[i * 3 + 0] << 2; - pal[i * 4 + 1] = _palette[i * 3 + 1] << 2; - pal[i * 4 + 2] = _palette[i * 3 + 2] << 2; - pal[i * 4 + 3] = 0; + byte pal[256 * 4]; + + if (!(_vm->getFeatures() & (GF_AGI256 | GF_AGI256_2))) { + for (i = 0; i < 32; i++) { + pal[i * 4 + 0] = _palette[i * 3 + 0] << 2; + pal[i * 4 + 1] = _palette[i * 3 + 1] << 2; + pal[i * 4 + 2] = _palette[i * 3 + 2] << 2; + pal[i * 4 + 3] = 0; + } + g_system->setPalette(pal, 0, 32); + } else { + for (i = 0; i < 256; i++) { + pal[i * 4 + 0] = vgaPalette[i * 3 + 0]; + pal[i * 4 + 1] = vgaPalette[i * 3 + 1]; + pal[i * 4 + 2] = vgaPalette[i * 3 + 2]; + pal[i * 4 + 3] = 0; + } + g_system->setPalette(pal, 0, 256); } - g_system->setPalette(pal, 0, 32); } //Gets AGIPAL Data @@ -539,11 +815,12 @@ void GfxMgr::putPixelsA(int x, int y, int n, uint8 *p) { *(uint16 *)&_agiScreen[x + y * GFX_WIDTH] = q & 0x0f0f; } } else { + const uint16 mask = _vm->getFeatures() & (GF_AGI256 | GF_AGI256_2) ? 0xffff : 0x0f0f; for (x *= 2; n--; p++, x += 2) { register uint16 q = ((uint16) * p << 8) | *p; if (_vm->_debug.priority) q >>= 4; - *(uint16 *)&_agiScreen[x + y * GFX_WIDTH] = q & 0x0f0f; + *(uint16 *)&_agiScreen[x + y * GFX_WIDTH] = q & mask; } } } diff --git a/engines/agi/module.mk b/engines/agi/module.mk index 86b3e59a44..d74eba034a 100644 --- a/engines/agi/module.mk +++ b/engines/agi/module.mk @@ -28,6 +28,7 @@ MODULE_OBJS = \ sprite.o \ text.o \ view.o \ + wagparser.o \ words.o diff --git a/engines/agi/op_cmd.cpp b/engines/agi/op_cmd.cpp index a51f536b49..c51514f237 100644 --- a/engines/agi/op_cmd.cpp +++ b/engines/agi/op_cmd.cpp @@ -515,7 +515,24 @@ cmd(obj_status_f) { * unk_181: Deactivate keypressed control (default control of ego) */ cmd(set_simple) { - game.simpleSave = true; + if (!(g_agi->getFeatures() & (GF_AGI256 | GF_AGI256_2))) { + game.simpleSave = true; + } else { // AGI256 and AGI256-2 use this unknown170 command to load 256 color pictures. + // Load the picture. Similar to cmd(load_pic). + g_sprites->eraseBoth(); + g_agi->agiLoadResource(rPICTURE, _v[p0]); + + // Draw the picture. Similar to cmd(draw_pic). + g_picture->decodePicture(_v[p0], false, true); + g_sprites->blitBoth(); + game.pictureShown = 0; + + // Show the picture. Similar to cmd(show_pic). + g_agi->setflag(fOutputMode, false); + cmd_close_window(NULL); + g_picture->showPic(); + game.pictureShown = 1; + } } cmd(pop_script) { diff --git a/engines/agi/picture.cpp b/engines/agi/picture.cpp index 9a16d4ab20..cd8ef83de0 100644 --- a/engines/agi/picture.cpp +++ b/engines/agi/picture.cpp @@ -86,7 +86,7 @@ void PictureMgr::putVirtPixel(int x, int y) { if (x < 0 || y < 0 || x >= _WIDTH || y >= _HEIGHT) return; - p = &_vm->_game.sbuf[y * _WIDTH + x]; + p = &_vm->_game.sbuf16c[y * _WIDTH + x]; if (priOn) *p = (priColour << 4) | (*p & 0x0f); @@ -281,7 +281,7 @@ INLINE int PictureMgr::isOkFillHere(int x, int y) { if (!scrOn && !priOn) return false; - p = _vm->_game.sbuf[y * _WIDTH + x]; + p = _vm->_game.sbuf16c[y * _WIDTH + x]; if (!priOn && scrOn && scrColour != 15) return (p & 0x0f) == 15; @@ -619,8 +619,9 @@ uint8 *PictureMgr::convertV3Pic(uint8 *src, uint32 len) { * drawing. * @param n AGI picture resource number * @param clear clear AGI screen before drawing + * @param agi256 load an AGI256 picture resource */ -int PictureMgr::decodePicture(int n, int clear) { +int PictureMgr::decodePicture(int n, int clear, bool agi256) { debugC(8, kDebugLevelResources, "(%d)", n); patCode = 0; @@ -633,10 +634,21 @@ int PictureMgr::decodePicture(int n, int clear) { flen = _vm->_game.dirPic[n].len; foffs = 0; - if (clear) - memset(_vm->_game.sbuf, 0x4f, _WIDTH * _HEIGHT); + if (clear && !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). - drawPicture(); + 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); + } if (clear) _vm->clearImageStack(); diff --git a/engines/agi/picture.h b/engines/agi/picture.h index 6a3f641eca..0f8584f3b2 100644 --- a/engines/agi/picture.h +++ b/engines/agi/picture.h @@ -68,7 +68,7 @@ public: _gfx = gfx; } - int decodePicture(int, int); + int decodePicture(int n, int clear, bool agi256 = false); int unloadPicture(int); void showPic(); uint8 *convertV3Pic(uint8 *src, uint32 len); diff --git a/engines/agi/predictive.cpp b/engines/agi/predictive.cpp index 5e086de2bb..67ebed5f05 100644 --- a/engines/agi/predictive.cpp +++ b/engines/agi/predictive.cpp @@ -28,6 +28,7 @@ #include "agi/keyboard.h" #include "common/func.h" +#include "common/config-manager.h" namespace Agi { @@ -35,99 +36,76 @@ namespace Agi { #define kModeNum 1 #define kModeAbc 2 -static byte s_asciiToNumTable[256]; +#define MAXLINELEN 80 + +uint8 countWordsInString(char *str) { + // Count the number of (space separated) words in the given string. + char *ptr; + + if (!str) + return 0; -void setAsciiToNumTableBatch(const char *chars, byte value) { - while (*chars) { - s_asciiToNumTable[tolower(*chars)] = value; - s_asciiToNumTable[toupper(*chars)] = value; - chars++; + ptr = strchr(str, ' '); + if (!ptr) { + debug("Invalid dictionary line"); + return 0; } -} -void initAsciiToNumTable() { - memset(s_asciiToNumTable, 0, sizeof(s_asciiToNumTable)); - - setAsciiToNumTableBatch("1'-.&", 1); - setAsciiToNumTableBatch("2abc", 2); - setAsciiToNumTableBatch("3def", 3); - setAsciiToNumTableBatch("4ghi", 4); - setAsciiToNumTableBatch("5jkl", 5); - setAsciiToNumTableBatch("6mno", 6); - setAsciiToNumTableBatch("7pqrs", 7); - setAsciiToNumTableBatch("8tuv", 8); - setAsciiToNumTableBatch("9wxyz", 9); + uint8 num = 1; + ptr++; + while ((ptr = strchr(ptr, ' '))) { + ptr++; + num++; + } + return num; } -class SearchTree { -public: - //byte val; - //SearchTree *parent; // TODO: Could be used to speed up re-searches - SearchTree *children[10]; +void bringWordtoTop(char *str, int wordnum) { + // This function reorders the words on the given pred.dic line + // by moving the word at position 'wordnum' to the front (that is, right behind + // right behind the numerical code word at the start of the line). Common::StringList words; - - SearchTree() { - memset(children, 0, sizeof(children)); - } - - SearchTree *getChild(byte val) { - assert(val < 10); - if (children[val] == 0) { - children[val] = new SearchTree(); - } - return children[val]; - } + char buf[MAXLINELEN]; - SearchTree *findChildWithWords() { - if (!words.empty()) - return this; - - SearchTree *child = 0; - for (int i = 0; i < 10 && !child; ++i) { - if (children[i]) - child = children[i]->findChildWithWords(); - } - - return child; - } - -}; - - -void AgiEngine::insertSearchNode(const char *word) { - // Insert the word into the tree - SearchTree *tree = _searchTreeRoot; - assert(tree); - for (int i = 0; word[i] != 0; ++i) { - byte key = s_asciiToNumTable[(int)word[i]]; - if (key == 0) - return; // abort! - tree = tree->getChild(key); + if (!str) + return; + strncpy(buf, str, MAXLINELEN); + char *word = strtok(buf, " "); + if (!word) { + debug("Invalid dictionary line"); + return; } - // TODO: Sort words, remove duplicates... ? - tree->words.push_back(word); + words.push_back(word); + while ((word = strtok(NULL, " ")) != NULL) + words.push_back(word); + words.insert_at(1, words.remove_at(wordnum + 1)); + + Common::String tmp; + for (uint8 i = 0; i < words.size(); i++) + tmp += words[i] + " "; + tmp.deleteLastChar(); + memcpy(str, tmp.c_str(), strlen(str)); } bool AgiEngine::predictiveDialog(void) { - int key, active = 0; + int key = 0, active = -1, lastactive = 0; bool rc = false; - int x; + uint8 x; int y; int bx[17], by[17]; - String prefix = ""; - char temp[MAXWORDLEN + 1]; + String prefix; + char temp[MAXWORDLEN + 1], repeatcount[MAXWORDLEN]; AgiBlock tmpwindow; - - // FIXME: Move this to a more appropriate place. - initAsciiToNumTable(); + bool navigationwithkeys = false; + bool processkey; const char *buttonStr[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" }; const char *buttons[] = { "(1)'-.&", "(2)abc", "(3)def", "(4)ghi", "(5)jkl", "(6)mno", "(7)pqrs", "(8)tuv", "(9)wxyz", - "next", "add", + "(#)next", "add", "<", "Cancel", "OK", "Pre", "(0) ", NULL @@ -141,13 +119,21 @@ bool AgiEngine::predictiveDialog(void) { 15, 0, 15, 0, 14, 0, 15, 0, 0, 0 }; - const char *modes[] = { "Pre", "123", "Abc" }; + const char *modes[] = { "(*)Pre", "(*)123", "(*)Abc" }; - if (!_searchTreeRoot) { + // FIXME: Move this to a more appropriate place. + if (!_predictiveDictText) { loadDict(); - if (!_searchTreeRoot) + if (!_predictiveDictText) return false; } + _predictiveDictActLine = NULL; + uint8 numMatchingWords = 0; + + _predictiveDialogRunning = true; + _system->setFeatureState(OSystem::kFeatureDisableKeyFiltering, true); + + memset(repeatcount, 0, MAXWORDLEN); // show the predictive dialog. // if another window is already in display, save its state into tmpwindow @@ -158,13 +144,11 @@ bool AgiEngine::predictiveDialog(void) { _gfx->drawRectangle(62, 54, 249, 66, MSG_BOX_TEXT); _gfx->flushBlock(62, 54, 249, 66); - _gfx->printCharacter(3, 11, _game.cursorChar, MSG_BOX_COLOUR, MSG_BOX_TEXT); - bx[15] = 73; // Zero/space by[15] = 120; - bx[9] = 120; // next + bx[9] = 110; // next by[9] = 120; - bx[10] = 160; // add + bx[10] = 172; // add by[10] = 120; bx[14] = 200; // Mode by[14] = 120; @@ -188,14 +172,13 @@ bool AgiEngine::predictiveDialog(void) { } /* clear key queue */ - while (_gfx->keypress()) { + while (_gfx->keypress()) _gfx->getKey(); - } - _currentCode = ""; - _currentWord = ""; + prefix.clear(); + _currentCode.clear(); + _currentWord.clear(); _wordNumber = 0; - _activeTreeNode = 0; int mode = kModePre; @@ -207,7 +190,8 @@ bool AgiEngine::predictiveDialog(void) { int color1 = colors[i * 2]; int color2 = colors[i * 2 + 1]; - if (i == 9 && !(_activeTreeNode && _activeTreeNode->words.size() > 1)) { // Next + if (i == 9 && !((mode != kModeAbc && _predictiveDictActLine && numMatchingWords > 1) + || (mode == kModeAbc && _currentWord.size() && _currentWord.lastChar() != ' '))) { // Next color2 = 7; } @@ -222,94 +206,253 @@ bool AgiEngine::predictiveDialog(void) { } } - if (_currentWord != "") { - temp[MAXWORDLEN] = 0; + temp[MAXWORDLEN] = 0; - strncpy(temp, prefix.c_str(), MAXWORDLEN); - strncat(temp, _currentWord.c_str(), MAXWORDLEN); + strncpy(temp, prefix.c_str(), MAXWORDLEN); + strncat(temp, _currentWord.c_str(), MAXWORDLEN); - for (int i = prefix.size() + _currentCode.size(); i < MAXWORDLEN; i++) - temp[i] = ' '; + for (int i = prefix.size() + _currentCode.size(); i < MAXWORDLEN; i++) + temp[i] = ' '; - printText(temp, 0, 8, 7, MAXWORDLEN, 15, 0); - _gfx->flushBlock(62, 54, 249, 66); - } + printText(temp, 0, 8, 7, MAXWORDLEN, 15, 0); + _gfx->flushBlock(62, 54, 249, 66); + + if (active >= 0 && !navigationwithkeys) { + // provide visual feedback only when not navigating with the arrows + // so that the user can see the active button. + active = -1; + needRefresh = true; + } else + needRefresh = false; + + _gfx->doUpdate(); } _gfx->pollTimer(); /* msdos driver -> does nothing */ key = doPollKeyboard(); + processkey = false; switch (key) { case KEY_ENTER: - rc = true; - goto press; + if (navigationwithkeys) { + // when the user has utilized arrow key navigation, + // interpret enter as 'click' on the active button + active = lastactive; + } else { + // else it is a shortcut for 'Ok' + active = 13; + } + processkey = true; + break; case KEY_ESCAPE: rc = false; goto getout; case BUTTON_LEFT: + navigationwithkeys = false; for (int i = 0; buttons[i]; i++) { if (_gfx->testButton(bx[i], by[i], buttons[i])) { - needRefresh = true; active = i; + processkey = true; + break; + } + } + break; + case KEY_BACKSPACE: + active = 11; + processkey = true; + break; + case '#': + active = 9; + processkey = true; + break; + case '*': + active = 14; + processkey = true; + break; + case 0x09: /* Tab */ + navigationwithkeys = true; + debugC(3, kDebugLevelText, "Focus change"); + lastactive = active = lastactive + 1; + active %= ARRAYSIZE(buttons) - 1; + needRefresh = true; + break; + case KEY_LEFT: + navigationwithkeys = true; + if (lastactive == 0 || lastactive == 3 || lastactive == 6) + active = lastactive + 2; + else if (lastactive == 9) + active = 15; + else if (lastactive == 11) + active = 11; + else if (lastactive == 12) + active = 13; + else if (lastactive == 14) + active = 10; + else + active = lastactive - 1; + lastactive = active; + needRefresh = true; + break; + case KEY_RIGHT: + navigationwithkeys = true; + if (lastactive == 2 || lastactive == 5 || lastactive == 8) + active = lastactive - 2; + else if (lastactive == 10) + active = 14; + else if (lastactive == 11) + active = 11; + else if (lastactive == 13) + active = 12; + else if (lastactive == 15) + active = 9; + else + active = lastactive + 1; + lastactive = active; + needRefresh = true; + break; + case KEY_UP: + navigationwithkeys = true; + if (lastactive <= 2) + active = 11; + else if (lastactive == 9 || lastactive == 10) + active = lastactive - 2; + else if (lastactive == 11) + active = 13; + else if (lastactive == 14) + active = 8; + else if (lastactive == 15) + active = 6; + else + active = lastactive - 3; + lastactive = active; + needRefresh = true; + break; + case KEY_DOWN: + navigationwithkeys = true; + if (lastactive == 6) + active = 15; + else if (lastactive == 7 || lastactive == 8) + active = lastactive + 2; + else if (lastactive == 11) + active = 0; + else if (lastactive == 12 || lastactive == 13) + active = 11; + else if (lastactive == 14 || lastactive == 15) + active = lastactive - 2; + else + active = lastactive + 3; + lastactive = active; + needRefresh = true; + break; + default: + // handle numeric buttons + if (key >= '1' && key <= '9') { + active = key - '1'; + processkey = true; + } else if (key == '0') { + active = 15; + processkey = true; + } + break; + } - if (active == 15 && mode != kModeNum) { // Space - strncpy(temp, _currentWord.c_str(), _currentCode.size()); - - temp[_currentCode.size()] = 0; - - prefix += temp; - prefix += " "; - _currentCode = ""; - } if (active < 9 || active == 11 || active == 15) { // number or backspace - if (active == 11) { // backspace - if (_currentCode.size()) { - _currentCode.deleteLastChar(); - } else { - if (prefix.size()) - prefix.deleteLastChar(); - } - } else if (active == 15) { // zero + if (processkey) { + if (active >= 0) { + needRefresh = true; + lastactive = active; + if (active == 15 && mode != kModeNum) { // Space + // bring MRU word at the top of the list when changing words + if (mode == kModePre && _predictiveDictActLine && numMatchingWords > 1 && _wordNumber != 0) + bringWordtoTop(_predictiveDictActLine, _wordNumber); + strncpy(temp, _currentWord.c_str(), _currentCode.size()); + temp[_currentCode.size()] = 0; + prefix += temp; + prefix += " "; + _currentCode.clear(); + _currentWord.clear(); + numMatchingWords = 0; + memset(repeatcount, 0, MAXWORDLEN); + } else if (active < 9 || active == 11 || active == 15) { // number or backspace + if (active == 11) { // backspace + if (_currentCode.size()) { + repeatcount[_currentCode.size() - 1] = 0; + _currentCode.deleteLastChar(); + } else { + if (prefix.size()) + prefix.deleteLastChar(); + } + } else if (prefix.size() + _currentCode.size() < MAXWORDLEN - 1) { // don't overflow the dialog line + if (active == 15) { // zero _currentCode += buttonStr[9]; } else { _currentCode += buttonStr[active]; } + } - if (mode == kModeNum) { + switch (mode) { + case kModeNum: _currentWord = _currentCode; - } else if (mode == kModePre) { + break; + case kModePre: if (!matchWord() && _currentCode.size()) { _currentCode.deleteLastChar(); matchWord(); } + numMatchingWords = countWordsInString(_predictiveDictActLine); + break; + case kModeAbc: + for (x = 0; x < _currentCode.size(); x++) + if (_currentCode[x] >= '1') + temp[x] = buttons[_currentCode[x] - '1'][3 + repeatcount[x]]; + temp[_currentCode.size()] = 0; + _currentWord = temp; + } + } else if (active == 9) { // next + if (mode == kModePre) { + if (_predictiveDictActLine && numMatchingWords > 1) { + _wordNumber = (_wordNumber + 1) % numMatchingWords; + char tmp[MAXLINELEN]; + strncpy(tmp, _predictiveDictActLine, MAXLINELEN); + char *tok = strtok(tmp, " "); + for (uint8 i = 0; i <= _wordNumber; i++) + tok = strtok(NULL, " "); + _currentWord = String(tok, _currentCode.size()); } - } else if (active == 9) { // next - int totalWordsNumber = _activeTreeNode ? _activeTreeNode->words.size() : 0; - if (totalWordsNumber > 0) { - _wordNumber = (_wordNumber + 1) % totalWordsNumber; - _currentWord = String(_activeTreeNode->words[_wordNumber].c_str(), _currentCode.size()); + } else if (mode == kModeAbc){ + x = _currentCode.size(); + if (x) { + if (_currentCode.lastChar() == '1' || _currentCode.lastChar() == '7' || _currentCode.lastChar() == '9') + repeatcount[x - 1] = (repeatcount[x - 1] + 1) % 4; + else + repeatcount[x - 1] = (repeatcount[x - 1] + 1) % 3; + if (_currentCode.lastChar() >= '1') + _currentWord[x - 1] = buttons[_currentCode[x - 1] - '1'][3 + repeatcount[x - 1]]; } - } else if (active == 10) { // add - debug(0, "add"); - } else if (active == 13) { // Ok - rc = true; - goto press; - } else if (active == 14) { // Mode - mode++; - if (mode > kModeAbc) - mode = kModePre; - } else { - goto press; } + } else if (active == 10) { // add + debug(0, "add"); + } else if (active == 13) { // Ok + // bring MRU word at the top of the list when ok'ed out of the dialog + if (mode == kModePre && _predictiveDictActLine && numMatchingWords > 1 && _wordNumber != 0) + bringWordtoTop(_predictiveDictActLine, _wordNumber); + rc = true; + goto press; + } else if (active == 14) { // Mode + mode++; + if (mode > kModeAbc) + mode = kModePre; + + // truncate current input at mode change + strncpy(temp, _currentWord.c_str(), _currentCode.size()); + temp[_currentCode.size()] = 0; + prefix += temp; + _currentCode.clear(); + _currentWord.clear(); + memset(repeatcount, 0, MAXWORDLEN); + } else { + goto press; } } - break; - case 0x09: /* Tab */ - debugC(3, kDebugLevelText, "Focus change"); - active++; - active %= ARRAYSIZE(buttons) - 1; - needRefresh = true; - break; } - _gfx->doUpdate(); } press: @@ -330,80 +473,120 @@ bool AgiEngine::predictiveDialog(void) { _gfx->doUpdate(); } + _system->setFeatureState(OSystem::kFeatureDisableKeyFiltering, false); + _predictiveDialogRunning = false; + return rc; } -#define MAXLINELEN 80 - void AgiEngine::loadDict(void) { - Common::File in; - char buf[MAXLINELEN]; - int words = 0, lines = 0; + Common::File inFile; + int lines = 0; - if (!in.open("pred.txt")) - return; + ConfMan.registerDefault("predictive_dictionary", "pred.dic"); - _searchTreeRoot = new SearchTree(); - words = 0; + uint32 time1 = _system->getMillis(); + if (!inFile.open(ConfMan.get("predictive_dictionary"))) + return; - while (!in.eos() && in.readLine(buf, MAXLINELEN)) { - // Skip leading & trailing whitespaces - char *word = Common::trim(buf); + char *ptr; + int size = inFile.size(); - // Skip empty lines - if (*word == 0) - continue; - + _predictiveDictText = (char *)malloc(size + 1); + if (!_predictiveDictText) { + warning("Not enough memory to load the predictive dictionary"); + return; + } + inFile.read(_predictiveDictText, size); + _predictiveDictText[size] = 0; + uint32 time2 = _system->getMillis(); + debug("Time to read %s: %d bytes, %d ms", inFile.name(), size, time2-time1); + inFile.close(); + + ptr = _predictiveDictText; + lines = 1; + while ((ptr = strchr(ptr, '\n'))) { lines++; + ptr++; + } - // The lines are of the form: "1234 word word" - // I.e. first comes a T9 number, then a space separated list of - // words with that T9 code. We simply ignore the T9 code, and then - // insert the words in order of occurance. - char *tok = strtok(word, " \t"); - if (tok) { - while ((tok = strtok(NULL, " ")) != NULL) { - insertSearchNode(tok); - words++; - } - } + _predictiveDictLine = (char **)calloc(1, sizeof(char *) * lines); + if (_predictiveDictLine == NULL) { + warning("Cannot allocate memory for line index buffer."); + return; + } + _predictiveDictLine[0] = _predictiveDictText; + ptr = _predictiveDictText; + int i = 1; + while ((ptr = strchr(ptr, '\n'))) { + *ptr = 0; + ptr++; + _predictiveDictLine[i++] = ptr; } + if (_predictiveDictLine[lines - 1][0] == 0) + lines--; - debug(0, "Loaded %d lines with %d words", lines, words); + _predictiveDictLineCount = lines; + debug("Loaded %d lines", _predictiveDictLineCount); + + uint32 time3 = _system->getMillis(); + printf("Time to parse pred.dic: %d, total: %d\n", time3-time2, time3-time1); } bool AgiEngine::matchWord(void) { if (_currentCode.empty()) { return false; } - - // Lookup word in the search tree - SearchTree *tree = _searchTreeRoot; - assert(tree); - for (uint i = 0; i < _currentCode.size(); ++i) { - int key = _currentCode[i] - '0'; - if (key < 1 || key > 9) { - tree = 0; - break; // Invalid key/code value, abort! - } - tree = tree->children[key]; - if (!tree) - break; // No matching entry in the search tree, abort! + // Lookup word in the dictionary + int line, span, cmpRes, len; + char target[MAXWORDLEN]; + + strncpy(target, _currentCode.c_str(), MAXWORDLEN); + strcat(target, " "); + + // do the search at most two times: + // first try to match the exact code, by matching also the space after the code + // if there is not an exact match, do it once more for the best matching prefix (drop the space) + len = _currentCode.size() + 1; + for (int i = 0; i < 2; ++i) { + line = (_predictiveDictLineCount + 1) / 2 - 1; + // find out the 2^upper_int(log2(_predictiveDictLineCount)) + for (span = 1; span < _predictiveDictLineCount; span <<= 1) + ; + span >>= 1; + do { + cmpRes = strncmp(_predictiveDictLine[line], target, len); + if (cmpRes > 0) { + span /= 2; + line -= span; + if (line < 0) + line = 0; + } else if (cmpRes < 0) { + span /= 2; + line += span; + if (line >= _predictiveDictLineCount) + line = _predictiveDictLineCount - 1; + } + } while (cmpRes && span); + if (cmpRes == 0) // Exact match found? -> stop now + break; + len--; // Remove the trailing space } - - if (tree) - tree = tree->findChildWithWords(); - _wordNumber = 0; - _activeTreeNode = tree; _currentWord.clear(); - - - if (tree) { - _currentWord = String(_activeTreeNode->words[_wordNumber].c_str(), _currentCode.size()); + _wordNumber = 0; + if (!strncmp(_predictiveDictLine[line], target, len)) { + _predictiveDictActLine = _predictiveDictLine[line]; + char tmp[MAXLINELEN]; + strncpy(tmp, _predictiveDictActLine, MAXLINELEN); + char *tok = strtok(tmp, " "); + tok = strtok(NULL, " "); + _currentWord = String(tok, _currentCode.size()); + return true; + } else { + _predictiveDictActLine = NULL; + return false; } - - return tree != 0; } } // End of namespace Agi diff --git a/engines/agi/sprite.cpp b/engines/agi/sprite.cpp index 2d8bb38741..3d69968075 100644 --- a/engines/agi/sprite.cpp +++ b/engines/agi/sprite.cpp @@ -117,7 +117,11 @@ void SpritesMgr::blitPixel(uint8 *p, uint8 *end, uint8 col, int spr, int width, /* Keep control line information visible, but put our * priority over water (0x30) surface */ - *p = (pr < 0x30 ? pr : spr) | col; + 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 @@ -132,7 +136,7 @@ void SpritesMgr::blitPixel(uint8 *p, uint8 *end, uint8 col, int spr, int width, } -int SpritesMgr::blitCel(int x, int y, int spr, ViewCel *c) { +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; @@ -151,15 +155,15 @@ int SpritesMgr::blitCel(int x, int y, int spr, ViewCel *c) { t = c->transparency; m = c->mirror; spr <<= 4; - p0 = &_vm->_game.sbuf[x + y * _WIDTH + m * (c->width - 1)]; + p0 = &_vm->_game.sbuf16c[x + y * _WIDTH + m * (c->width - 1)]; - end = _vm->_game.sbuf + _WIDTH * _HEIGHT; + end = _vm->_game.sbuf16c + _WIDTH * _HEIGHT; for (i = 0; i < c->height; i++) { p = p0; while (*q) { - col = (*q & 0xf0) >> 4; - for (j = *q & 0x0f; j; j--, p += 1 - 2 * m) { + 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); } @@ -451,7 +455,7 @@ void SpritesMgr::blitSprites(SpriteList& l) { Sprite *s = *iter; objsSaveArea(s); debugC(8, kDebugLevelSprites, "s->v->entry = %d (prio %d)", s->v->entry, s->v->priority); - hidden = blitCel(s->xPos, s->yPos, s->v->priority, s->v->celData); + hidden = blitCel(s->xPos, s->yPos, s->v->priority, s->v->celData, s->v->viewData->agi256_2); if (s->v->entry == 0) { /* if ego, update f1 */ _vm->setflag(fEgoInvisible, hidden); } @@ -609,7 +613,7 @@ void SpritesMgr::addToPic(int view, int loop, int cel, int x, int y, int pri, in eraseBoth(); debugC(4, kDebugLevelSprites, "blit_cel (%d, %d, %d, c)", x, y, pri); - blitCel(x1, y1, pri, c); + 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. @@ -629,8 +633,8 @@ void SpritesMgr::addToPic(int view, int loop, int cel, int x, int y, int pri, in // don't let box extend below y. if (y3 > y2) y3 = y2; - p1 = &_vm->_game.sbuf[x1 + y3 * _WIDTH]; - p2 = &_vm->_game.sbuf[x2 + y3 * _WIDTH]; + p1 = &_vm->_game.sbuf16c[x1 + y3 * _WIDTH]; + p2 = &_vm->_game.sbuf16c[x2 + y3 * _WIDTH]; for (y = y3; y <= y2; y++) { if ((*p1 >> 4) >= 4) @@ -642,8 +646,8 @@ void SpritesMgr::addToPic(int view, int loop, int cel, int x, int y, int pri, in } debugC(4, kDebugLevelSprites, "pri box: %d %d %d %d (%d)", x1, y3, x2, y2, mar); - p1 = &_vm->_game.sbuf[x1 + y3 * _WIDTH]; - p2 = &_vm->_game.sbuf[x1 + y2 * _WIDTH]; + 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); @@ -687,7 +691,7 @@ void SpritesMgr::showObj(int n) { s.buffer = (uint8 *)malloc(s.xSize * s.ySize); objsSaveArea(&s); - blitCel(x1, y1, s.xSize, c); + blitCel(x1, y1, s.xSize, c, _vm->_game.views[n].agi256_2); commitBlock(x1, y1, x2, y2); _vm->messageBox(_vm->_game.views[n].descr); objsRestoreArea(&s); diff --git a/engines/agi/sprite.h b/engines/agi/sprite.h index 2f703e352a..401446249b 100644 --- a/engines/agi/sprite.h +++ b/engines/agi/sprite.h @@ -56,7 +56,7 @@ private: 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); + int blitCel(int x, int y, int spr, ViewCel *c, bool agi256_2); void objsSaveArea(Sprite *s); void objsRestoreArea(Sprite *s); diff --git a/engines/agi/view.cpp b/engines/agi/view.cpp index f80e4b6447..48e3ca5e3f 100644 --- a/engines/agi/view.cpp +++ b/engines/agi/view.cpp @@ -151,6 +151,7 @@ int AgiEngine::decodeView(int n) { assert(v != NULL); + _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); /* if no loops exist, return! */ @@ -187,9 +188,18 @@ int AgiEngine::decodeView(int n) { vc->width = *(v + cofs); vc->height = *(v + cofs + 1); - vc->transparency = *(v + cofs + 2) & 0xf; - vc->mirrorLoop = (*(v + cofs + 2) >> 4) & 0x7; - vc->mirror = (*(v + cofs + 2) >> 7) & 0x1; + + 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; + } /* skip over width/height/trans|mirror data */ cofs += 3; diff --git a/engines/agi/view.h b/engines/agi/view.h index a20d63793a..f2efce0ec3 100644 --- a/engines/agi/view.h +++ b/engines/agi/view.h @@ -51,6 +51,7 @@ struct ViewLoop { struct AgiView { int numLoops; struct ViewLoop *loop; + bool agi256_2; char *descr; uint8 *rdata; }; |
