aboutsummaryrefslogtreecommitdiff
path: root/engines/agi
diff options
context:
space:
mode:
Diffstat (limited to 'engines/agi')
-rw-r--r--engines/agi/agi.cpp100
-rw-r--r--engines/agi/agi.h21
-rw-r--r--engines/agi/agi_v3.cpp2
-rw-r--r--engines/agi/checks.cpp2
-rw-r--r--engines/agi/detection.cpp217
-rw-r--r--engines/agi/graphics.cpp297
-rw-r--r--engines/agi/module.mk1
-rw-r--r--engines/agi/op_cmd.cpp19
-rw-r--r--engines/agi/picture.cpp24
-rw-r--r--engines/agi/picture.h2
-rw-r--r--engines/agi/predictive.cpp565
-rw-r--r--engines/agi/sprite.cpp30
-rw-r--r--engines/agi/sprite.h2
-rw-r--r--engines/agi/view.cpp16
-rw-r--r--engines/agi/view.h1
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;
};