aboutsummaryrefslogtreecommitdiff
path: root/engines
diff options
context:
space:
mode:
Diffstat (limited to 'engines')
-rw-r--r--engines/agi/agi.cpp917
-rw-r--r--engines/agi/agi.h602
-rw-r--r--engines/agi/checks.cpp340
-rw-r--r--engines/agi/console.cpp193
-rw-r--r--engines/agi/console.h6
-rw-r--r--engines/agi/cycle.cpp435
-rw-r--r--engines/agi/detection.cpp89
-rw-r--r--engines/agi/font.h177
-rw-r--r--engines/agi/global.cpp10
-rw-r--r--engines/agi/graphics.cpp1701
-rw-r--r--engines/agi/graphics.h136
-rw-r--r--engines/agi/inv.cpp320
-rw-r--r--engines/agi/inv.h61
-rw-r--r--engines/agi/keyboard.cpp653
-rw-r--r--engines/agi/keyboard.h87
-rw-r--r--engines/agi/loader_v1.cpp96
-rw-r--r--engines/agi/loader_v2.cpp95
-rw-r--r--engines/agi/loader_v3.cpp93
-rw-r--r--engines/agi/logic.cpp46
-rw-r--r--engines/agi/menu.cpp800
-rw-r--r--engines/agi/menu.h84
-rw-r--r--engines/agi/module.mk1
-rw-r--r--engines/agi/motion.cpp167
-rw-r--r--engines/agi/mouse_cursor.h183
-rw-r--r--engines/agi/objects.cpp24
-rw-r--r--engines/agi/op_cmd.cpp2204
-rw-r--r--engines/agi/op_dbg.cpp2
-rw-r--r--engines/agi/op_test.cpp74
-rw-r--r--engines/agi/opcodes.cpp360
-rw-r--r--engines/agi/palette.h311
-rw-r--r--engines/agi/picture.cpp1101
-rw-r--r--engines/agi/picture.h66
-rw-r--r--engines/agi/preagi.cpp29
-rw-r--r--engines/agi/preagi_mickey.cpp161
-rw-r--r--engines/agi/preagi_troll.cpp48
-rw-r--r--engines/agi/preagi_winnie.cpp49
-rw-r--r--engines/agi/saveload.cpp984
-rw-r--r--engines/agi/sound_pcjr.cpp2
-rw-r--r--engines/agi/sound_sarien.cpp2
-rw-r--r--engines/agi/sprite.cpp981
-rw-r--r--engines/agi/sprite.h91
-rw-r--r--engines/agi/systemui.cpp661
-rw-r--r--engines/agi/systemui.h119
-rw-r--r--engines/agi/text.cpp1326
-rw-r--r--engines/agi/text.h201
-rw-r--r--engines/agi/view.cpp713
-rw-r--r--engines/agi/view.h115
-rw-r--r--engines/agi/words.cpp348
-rw-r--r--engines/agi/words.h69
49 files changed, 10174 insertions, 7159 deletions
diff --git a/engines/agi/agi.cpp b/engines/agi/agi.cpp
index e907d3835e..19e05959dc 100644
--- a/engines/agi/agi.cpp
+++ b/engines/agi/agi.cpp
@@ -21,7 +21,6 @@
*/
#include "common/md5.h"
-#include "common/events.h"
#include "common/file.h"
#include "common/memstream.h"
#include "common/savefile.h"
@@ -42,9 +41,13 @@
#include "agi/agi.h"
#include "agi/graphics.h"
+#include "agi/inv.h"
#include "agi/sprite.h"
+#include "agi/text.h"
#include "agi/keyboard.h"
#include "agi/menu.h"
+#include "agi/systemui.h"
+#include "agi/words.h"
#include "gui/predictivedialog.h"
@@ -54,241 +57,6 @@ void AgiEngine::allowSynthetic(bool allow) {
_allowSynthetic = allow;
}
-void AgiEngine::processEvents() {
- Common::Event event;
- int key = 0;
-
- while (_eventMan->pollEvent(event)) {
- switch (event.type) {
- case Common::EVENT_PREDICTIVE_DIALOG: {
- GUI::PredictiveDialog _predictiveDialog;
- _predictiveDialog.runModal();
- strcpy(_predictiveResult, _predictiveDialog.getResult());
- if (strcmp(_predictiveResult, "")) {
- if (_game.inputMode == INPUT_NORMAL) {
- strcpy((char *)_game.inputBuffer, _predictiveResult);
- handleKeys(KEY_ENTER);
- } else if (_game.inputMode == INPUT_GETSTRING) {
- strcpy(_game.strings[_stringdata.str], _predictiveResult);
- newInputMode(INPUT_NORMAL);
- _gfx->printCharacter(_stringdata.x + strlen(_game.strings[_stringdata.str]) + 1,
- _stringdata.y, ' ', _game.colorFg, _game.colorBg);
- } else if (_game.inputMode == INPUT_NONE) {
- for (int n = 0; _predictiveResult[n]; n++)
- keyEnqueue(_predictiveResult[n]);
- }
- }
- /*
- if (predictiveDialog()) {
- if (_game.inputMode == INPUT_NORMAL) {
- strcpy((char *)_game.inputBuffer, _predictiveResult);
- handleKeys(KEY_ENTER);
- } else if (_game.inputMode == INPUT_GETSTRING) {
- strcpy(_game.strings[_stringdata.str], _predictiveResult);
- newInputMode(INPUT_NORMAL);
- _gfx->printCharacter(_stringdata.x + strlen(_game.strings[_stringdata.str]) + 1,
- _stringdata.y, ' ', _game.colorFg, _game.colorBg);
- } else if (_game.inputMode == INPUT_NONE) {
- for (int n = 0; _predictiveResult[n]; n++)
- keyEnqueue(_predictiveResult[n]);
- }
- }
- */
- }
- break;
- case Common::EVENT_LBUTTONDOWN:
- if (_game.mouseEnabled) {
- key = BUTTON_LEFT;
- _mouse.button = kAgiMouseButtonLeft;
- keyEnqueue(key);
- _mouse.x = event.mouse.x;
- _mouse.y = event.mouse.y;
- }
- break;
- case Common::EVENT_RBUTTONDOWN:
- if (_game.mouseEnabled) {
- key = BUTTON_RIGHT;
- _mouse.button = kAgiMouseButtonRight;
- keyEnqueue(key);
- _mouse.x = event.mouse.x;
- _mouse.y = event.mouse.y;
- }
- break;
- case Common::EVENT_WHEELUP:
- if (_game.mouseEnabled) {
- key = WHEEL_UP;
- keyEnqueue(key);
- }
- break;
- case Common::EVENT_WHEELDOWN:
- if (_game.mouseEnabled) {
- key = WHEEL_DOWN;
- keyEnqueue(key);
- }
- break;
- case Common::EVENT_MOUSEMOVE:
- if (_game.mouseEnabled) {
- _mouse.x = event.mouse.x;
- _mouse.y = event.mouse.y;
-
- if (!_game.mouseFence.isEmpty()) {
- if (_mouse.x < _game.mouseFence.left)
- _mouse.x = _game.mouseFence.left;
- if (_mouse.x > _game.mouseFence.right)
- _mouse.x = _game.mouseFence.right;
- if (_mouse.y < _game.mouseFence.top)
- _mouse.y = _game.mouseFence.top;
- if (_mouse.y > _game.mouseFence.bottom)
- _mouse.y = _game.mouseFence.bottom;
-
- g_system->warpMouse(_mouse.x, _mouse.y);
- }
- }
-
- break;
- case Common::EVENT_LBUTTONUP:
- case Common::EVENT_RBUTTONUP:
- if (_game.mouseEnabled) {
- _mouse.button = kAgiMouseButtonUp;
- _mouse.x = event.mouse.x;
- _mouse.y = event.mouse.y;
- }
- break;
- case Common::EVENT_KEYDOWN:
- if (event.kbd.hasFlags(Common::KBD_CTRL) && event.kbd.keycode == Common::KEYCODE_d) {
- _console->attach();
- break;
- }
-
- switch (key = event.kbd.keycode) {
- case Common::KEYCODE_LEFT:
- case Common::KEYCODE_KP4:
- if (_allowSynthetic || !event.synthetic)
- key = KEY_LEFT;
- break;
- case Common::KEYCODE_RIGHT:
- case Common::KEYCODE_KP6:
- if (_allowSynthetic || !event.synthetic)
- key = KEY_RIGHT;
- break;
- case Common::KEYCODE_UP:
- case Common::KEYCODE_KP8:
- if (_allowSynthetic || !event.synthetic)
- key = KEY_UP;
- break;
- case Common::KEYCODE_DOWN:
- case Common::KEYCODE_KP2:
- if (_allowSynthetic || !event.synthetic)
- key = KEY_DOWN;
- break;
- case Common::KEYCODE_PAGEUP:
- case Common::KEYCODE_KP9:
- if (_allowSynthetic || !event.synthetic)
- key = KEY_UP_RIGHT;
- break;
- case Common::KEYCODE_PAGEDOWN:
- case Common::KEYCODE_KP3:
- if (_allowSynthetic || !event.synthetic)
- key = KEY_DOWN_RIGHT;
- break;
- case Common::KEYCODE_HOME:
- case Common::KEYCODE_KP7:
- if (_allowSynthetic || !event.synthetic)
- key = KEY_UP_LEFT;
- break;
- case Common::KEYCODE_END:
- case Common::KEYCODE_KP1:
- if (_allowSynthetic || !event.synthetic)
- key = KEY_DOWN_LEFT;
- break;
- case Common::KEYCODE_KP5:
- key = KEY_STATIONARY;
- break;
- case Common::KEYCODE_PLUS:
- key = '+';
- break;
- case Common::KEYCODE_MINUS:
- key = '-';
- break;
- case Common::KEYCODE_TAB:
- key = 0x0009;
- break;
- case Common::KEYCODE_F1:
- key = 0x3b00;
- break;
- case Common::KEYCODE_F2:
- key = 0x3c00;
- break;
- case Common::KEYCODE_F3:
- key = 0x3d00;
- break;
- case Common::KEYCODE_F4:
- key = 0x3e00;
- break;
- case Common::KEYCODE_F5:
- key = 0x3f00;
- break;
- case Common::KEYCODE_F6:
- key = 0x4000;
- break;
- case Common::KEYCODE_F7:
- key = 0x4100;
- break;
- case Common::KEYCODE_F8:
- key = 0x4200;
- break;
- case Common::KEYCODE_F9:
- key = 0x4300;
- break;
- case Common::KEYCODE_F10:
- key = 0x4400;
- break;
- case Common::KEYCODE_F11:
- key = KEY_STATUSLN;
- break;
- case Common::KEYCODE_F12:
- key = KEY_PRIORITY;
- break;
- case Common::KEYCODE_ESCAPE:
- key = 0x1b;
- break;
- case Common::KEYCODE_RETURN:
- case Common::KEYCODE_KP_ENTER:
- key = KEY_ENTER;
- break;
- case Common::KEYCODE_BACKSPACE:
- key = KEY_BACKSPACE;
- break;
- default:
- // Not a special key, so get the ASCII code for it
- key = event.kbd.ascii;
-
- if (Common::isAlpha(key)) {
- // Key is A-Z.
- // Map Ctrl-A to 1, Ctrl-B to 2, etc.
- if (event.kbd.flags & Common::KBD_CTRL) {
- key = toupper(key) - 'A' + 1;
- } else if (event.kbd.flags & Common::KBD_ALT) {
- // Map Alt-A, Alt-B etc. to special scancode values according to an internal scancode table.
- key = scancodeTable[toupper(key) - 'A'] << 8;
- }
- }
- break;
- }
- if (key)
- keyEnqueue(key);
- break;
-
- case Common::EVENT_KEYUP:
- if (_egoHoldKey)
- _game.viewTable[0].direction = 0;
-
- default:
- break;
- }
- }
-}
-
void AgiEngine::pollTimer() {
_lastTick += 50;
@@ -305,24 +73,14 @@ void AgiEngine::pollTimer() {
void AgiEngine::pause(uint32 msec) {
uint32 endTime = _system->getMillis() + msec;
- _gfx->setCursor(_renderMode == Common::kRenderAmiga, true);
+ _gfx->setMouseCursor(true); // Busy mouse cursor
while (_system->getMillis() < endTime) {
processEvents();
_system->updateScreen();
_system->delayMillis(10);
}
- _gfx->setCursor(_renderMode == Common::kRenderAmiga);
-}
-
-void AgiEngine::initPriTable() {
- int i, p, y = 0;
-
- for (p = 1; p < 15; p++) {
- for (i = 0; i < 12; i++) {
- _game.priTable[y++] = p < 4 ? 4 : p;
- }
- }
+ _gfx->setMouseCursor(); // regular mouse cursor
}
int AgiEngine::agiInit() {
@@ -341,7 +99,7 @@ int AgiEngine::agiInit() {
_game.vars[i] = 0;
// clear all resources and events
- for (i = 0; i < MAX_DIRS; i++) {
+ for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) {
memset(&_game.views[i], 0, sizeof(struct AgiView));
memset(&_game.pictures[i], 0, sizeof(struct AgiPicture));
memset(&_game.logics[i], 0, sizeof(struct AgiLogic));
@@ -353,15 +111,17 @@ int AgiEngine::agiInit() {
}
// clear view table
- for (i = 0; i < MAX_VIEWTABLE; i++)
- memset(&_game.viewTable[i], 0, sizeof(struct VtEntry));
+ for (i = 0; i < SCREENOBJECTS_MAX; i++)
+ memset(&_game.screenObjTable[i], 0, sizeof(struct ScreenObjEntry));
+
+ memset(&_game.addToPicView, 0, sizeof(struct ScreenObjEntry));
- initWords();
+ _words->clearEgoWords();
if (!_menu)
- _menu = new Menu(this, _gfx, _picture);
+ _menu = new GfxMenu(this, _gfx, _picture, _text);
- initPriTable();
+ _gfx->initPriorityTable();
// Clear the string buffer on startup, but not when the game restarts, as
// some scripts expect that the game strings remain unaffected after a
@@ -394,10 +154,6 @@ int AgiEngine::agiInit() {
if (getFeatures() & GF_AGDS)
_game.gameFlags |= ID_AGDS;
- // Make the 256 color AGI screen the default AGI screen when AGI256 or AGI256-2 is used
- if (getFeatures() & (GF_AGI256 | GF_AGI256_2))
- _game.sbuf = _game.sbuf256c;
-
if (_game.gameFlags & ID_AMIGA)
debug(1, "Amiga padded game detected.");
@@ -413,12 +169,9 @@ int AgiEngine::agiInit() {
if (ec == errOK)
ec = _loader->loadWords(WORDS);
- // FIXME: load IIgs instruments and samples
- // load_instruments("kq.sys16");
-
// Load logic 0 into memory
if (ec == errOK)
- ec = _loader->loadResource(rLOGIC, 0);
+ ec = _loader->loadResource(RESOURCETYPE_LOGIC, 0);
#ifdef __DS__
// Normally, the engine loads the predictive text dictionary when the predictive dialog
@@ -443,42 +196,42 @@ void AgiEngine::agiUnloadResources() {
int i;
// Make sure logic 0 is always loaded
- for (i = 1; i < MAX_DIRS; i++) {
- _loader->unloadResource(rLOGIC, i);
+ for (i = 1; i < MAX_DIRECTORY_ENTRIES; i++) {
+ _loader->unloadResource(RESOURCETYPE_LOGIC, i);
}
- for (i = 0; i < MAX_DIRS; i++) {
- _loader->unloadResource(rVIEW, i);
- _loader->unloadResource(rPICTURE, i);
- _loader->unloadResource(rSOUND, i);
+ for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) {
+ _loader->unloadResource(RESOURCETYPE_VIEW, i);
+ _loader->unloadResource(RESOURCETYPE_PICTURE, i);
+ _loader->unloadResource(RESOURCETYPE_SOUND, i);
}
}
int AgiEngine::agiDeinit() {
int ec;
- cleanInput(); // remove all words from memory
+ _words->clearEgoWords(); // remove all words from memory
agiUnloadResources(); // unload resources in memory
- _loader->unloadResource(rLOGIC, 0);
+ _loader->unloadResource(RESOURCETYPE_LOGIC, 0);
ec = _loader->deinit();
unloadObjects();
- unloadWords();
+ _words->unloadDictionary();
clearImageStack();
return ec;
}
-int AgiEngine::agiLoadResource(int r, int n) {
+int AgiEngine::agiLoadResource(int16 resourceType, int16 resourceNr) {
int i;
- i = _loader->loadResource(r, n);
+ i = _loader->loadResource(resourceType, resourceNr);
// WORKAROUND: Patches broken picture 147 in a corrupted Amiga version of Gold Rush! (v2.05 1989-03-09).
// The picture can be seen in room 147 after dropping through the outhouse's hole in room 146.
- if (i == errOK && getGameID() == GID_GOLDRUSH && r == rPICTURE && n == 147 && _game.dirPic[n].len == 1982) {
- uint8 *pic = _game.pictures[n].rdata;
- Common::MemoryReadStream picStream(pic, _game.dirPic[n].len);
- Common::String md5str = Common::computeStreamMD5AsString(picStream, _game.dirPic[n].len);
+ if (i == errOK && getGameID() == GID_GOLDRUSH && resourceType == RESOURCETYPE_PICTURE && resourceNr == 147 && _game.dirPic[resourceNr].len == 1982) {
+ uint8 *pic = _game.pictures[resourceNr].rdata;
+ Common::MemoryReadStream picStream(pic, _game.dirPic[resourceNr].len);
+ Common::String md5str = Common::computeStreamMD5AsString(picStream, _game.dirPic[resourceNr].len);
if (md5str == "1c685eb048656cedcee4eb6eca2cecea") {
pic[0x042] = 0x4B; // 0x49 -> 0x4B
pic[0x043] = 0x66; // 0x26 -> 0x66
@@ -492,8 +245,8 @@ int AgiEngine::agiLoadResource(int r, int n) {
return i;
}
-int AgiEngine::agiUnloadResource(int r, int n) {
- return _loader->unloadResource(r, n);
+int AgiEngine::agiUnloadResource(int16 resourceType, int16 resourceNr) {
+ return _loader->unloadResource(resourceType, resourceNr);
}
struct GameSettings {
@@ -510,7 +263,8 @@ AgiBase::AgiBase(OSystem *syst, const AGIGameDescription *gameDesc) : Engine(sys
_rnd = new Common::RandomSource("agi");
_sound = 0;
- _fontData = NULL;
+ _fontData = nullptr;
+ _fontDataAllocated = nullptr;
initFeatures();
initVersion();
@@ -520,30 +274,419 @@ AgiBase::~AgiBase() {
delete _rnd;
delete _sound;
+
+ if (_fontDataAllocated) {
+ free(_fontDataAllocated);
+ }
}
void AgiBase::initRenderMode() {
- _renderMode = Common::kRenderEGA;
+ Common::Platform platform = Common::parsePlatform(ConfMan.get("platform"));
+ Common::RenderMode configRenderMode = Common::parseRenderMode(ConfMan.get("render_mode").c_str());
+
+ // Default to EGA PC rendering
+ _renderMode = RENDERMODE_EGA;
+
+ switch (platform) {
+ case Common::kPlatformDOS:
+ switch (configRenderMode) {
+ case Common::kRenderCGA:
+ _renderMode = RENDERMODE_CGA;
+ break;
+ // Hercules is not supported atm
+ //case Common::kRenderHercA:
+ //case Common::kRenderHercG:
+ // _renderMode = RENDERMODE_HERCULES;
+ // break;
+ default:
+ break;
+ }
+ break;
+ case Common::kPlatformAmiga:
+ _renderMode = RENDERMODE_AMIGA;
+ break;
+ case Common::kPlatformApple2GS:
+ _renderMode = RENDERMODE_APPLE_II_GS;
+ break;
+ case Common::kPlatformAtariST:
+ _renderMode = RENDERMODE_ATARI_ST;
+ break;
+ default:
+ break;
+ }
- if (ConfMan.hasKey("platform")) {
- Common::Platform platform = Common::parsePlatform(ConfMan.get("platform"));
- _renderMode = (platform == Common::kPlatformAmiga) ? Common::kRenderAmiga : Common::kRenderEGA;
+ // If render mode is explicitly set, force rendermode
+ switch (configRenderMode) {
+ case Common::kRenderAmiga:
+ _renderMode = RENDERMODE_AMIGA;
+ break;
+ case Common::kRenderApple2GS:
+ _renderMode = RENDERMODE_APPLE_II_GS;
+ break;
+ case Common::kRenderAtariST:
+ _renderMode = RENDERMODE_ATARI_ST;
+ break;
+ default:
+ break;
}
- if (ConfMan.hasKey("render_mode")) {
- Common::RenderMode tmpMode = Common::parseRenderMode(ConfMan.get("render_mode").c_str());
- if (tmpMode != Common::kRenderDefault)
- _renderMode = tmpMode;
+ if (getFeatures() & (GF_AGI256 | GF_AGI256_2)) {
+ // If current game is AGI256, switch (force) to VGA render mode
+ _renderMode = RENDERMODE_VGA;
}
}
+void AgiBase::initFont() {
+ // We are currently using the custom font for all fanmade games
+ if (getFeatures() & (GF_FANMADE | GF_AGDS)) {
+ // fanmade game, use custom font for now
+ _fontData = fontData_FanGames; // our (own?) custom font, that supports umlauts etc.
+ return;
+ }
+
+ switch (_renderMode) {
+ case RENDERMODE_AMIGA:
+ loadFontAmigaPseudoTopaz();
+ //_fontData = fontData_Amiga; // use Amiga Topaz font
+ break;
+ case RENDERMODE_APPLE_II_GS:
+ // Special font, stored in file AGIFONT
+ loadFontAppleIIgs();
+ break;
+ case RENDERMODE_ATARI_ST:
+ // TODO: Atari ST uses another font
+ // Seems to be the standard Atari ST 8x8 system font
+
+ case RENDERMODE_CGA:
+ case RENDERMODE_EGA:
+ case RENDERMODE_VGA:
+ switch (getGameID()) {
+ case GID_MICKEY:
+ // load mickey mouse font from interpreter file
+ loadFontMickey();
+ break;
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (!_fontData) {
+ // no font asigned?
+ switch (getGameID()) {
+ case GID_MICKEY:
+ case GID_TROLL:
+ case GID_WINNIE:
+ // use IBM font for pre-AGI games as standard
+ _fontData = fontData_IBM;
+ break;
+ default:
+ // for everything else use Sierra PC font
+ _fontData = fontData_Sierra;
+ break;
+ }
+ }
+}
+
+// We load the Mickey Mouse font from MICKEY.EXE
+void AgiBase::loadFontMickey() {
+ Common::File interpreterFile;
+ int32 interpreterFileSize = 0;
+ byte *fontData = nullptr;
+
+ if (!interpreterFile.open("mickey.exe")) {
+ // Continue, if file not found
+ return;
+ }
+
+ interpreterFileSize = interpreterFile.size();
+ if (interpreterFileSize != 55136) {
+ // unexpected file size
+ interpreterFile.close();
+ warning("mickey.exe unexpected file size");
+ return;
+ }
+ interpreterFile.seek(32476); // offset of font data
+
+ // allocate space for font bitmap data
+ fontData = (uint8 *)calloc(256, 8);
+ _fontData = fontData;
+ _fontDataAllocated = fontData;
+
+ // read font data, is already in the format that we need (plain bitmap 8x8)
+ interpreterFile.read(fontData, 256 * 8);
+ interpreterFile.close();
+}
+
+// we create a bitmap out of the topaz data used in parallaction (which is normally found in staticres.cpp)
+// it's a recreation of the Amiga Topaz font but not really accurate
+void AgiBase::loadFontAmigaPseudoTopaz() {
+ const byte *topazStart = fontData_AmigaPseudoTopaz + 32;
+ const byte *topazHeader = topazStart + 78;
+ const byte *topazData = nullptr;
+ const byte *topazLocations = nullptr;
+ byte *fontData = nullptr;
+ uint16 topazHeight = 0;
+ uint16 topazWidth = 0;
+ uint16 topazModulo = 0;
+ uint32 topazDataOffset = 0;
+ uint32 topazLocationOffset = 0;
+ byte topazLowChar = 0;
+ byte topazHighChar = 0;
+ uint16 topazTotalChars = 0;
+ uint16 topazBitLength = 0;
+ uint16 topazBitOffset = 0;
+ uint16 topazByteOffset = 0;
+
+ // allocate space for font bitmap data
+ fontData = (uint8 *)calloc(256, 8);
+ _fontData = fontData;
+ _fontDataAllocated = fontData;
+
+ topazHeight = READ_BE_UINT16(topazHeader + 0);
+ topazWidth = READ_BE_UINT16(topazHeader + 4);
+
+ // we expect 8x8
+ assert(topazHeight == 8);
+ assert(topazWidth == 8);
+
+ topazLowChar = topazHeader[12];
+ topazHighChar = topazHeader[13];
+ topazTotalChars = topazHighChar - topazLowChar + 1;
+ topazDataOffset = READ_BE_UINT32(topazHeader + 14);
+ topazModulo = READ_BE_UINT16(topazHeader + 18);
+ topazLocationOffset = READ_BE_UINT32(topazHeader + 20);
+
+ // Security checks
+ assert(topazLowChar == ' ');
+ assert(topazHighChar == 0xFF);
+
+ // copy first 32 characters over
+ memcpy(fontData, fontData_Sierra, FONT_DISPLAY_WIDTH * 32);
+ fontData += FONT_DISPLAY_WIDTH * 32;
+
+ // now actually convert from topaz data
+ topazData = topazStart + topazDataOffset;
+ topazLocations = topazStart + topazLocationOffset;
+
+ for (uint16 curChar = 0; curChar < topazTotalChars; curChar++) {
+ topazBitOffset = READ_BE_UINT16(topazLocations + (curChar * 4) + 0);
+ topazBitLength = READ_BE_UINT16(topazLocations + (curChar * 4) + 2);
+
+ if (topazBitLength == 8) {
+ assert((topazBitOffset & 7) == 0);
+
+ topazByteOffset = topazBitOffset >> 3;
+ for (uint16 curHeight = 0; curHeight < topazHeight; curHeight++) {
+ *fontData = topazData[topazByteOffset];
+ fontData++;
+ topazByteOffset += topazModulo;
+ }
+ } else {
+ memset(fontData, 0, 8);
+ fontData += 8;
+ }
+ }
+}
+
+void AgiBase::loadFontAppleIIgs() {
+ Common::File fontFile;
+ uint16 headerIIgs_OffsetMacHeader = 0;
+ uint16 headerIIgs_Version = 0;
+ uint16 macRecord_FirstChar = 0;
+ uint16 macRecord_LastChar = 0;
+ int16 macRecord_MaxKern = 0;
+ uint16 macRecord_RectHeight = 0;
+ uint16 macRecord_StrikeWidth = 0;
+ uint16 strikeDataLen = 0;
+ byte *strikeDataPtr = nullptr;
+ uint16 actualCharacterCount = 0;
+ uint16 totalCharacterCount = 0;
+ uint16 *locationTablePtr = nullptr;
+ uint16 *offsetWidthTablePtr = nullptr;
+
+ uint16 curCharNr = 0;
+ uint16 curRow = 0;
+ uint16 curLocation = 0;
+ uint16 curLocationBytes = 0;
+ uint16 curLocationBits = 0;
+ uint16 curCharOffsetWidth = 0;
+ uint16 curCharOffset = 0;
+ uint16 curCharWidth = 0;
+ uint16 curStrikeWidth = 0;
+
+ uint16 curPixelNr = 0;
+ uint16 curBitMask = 0;
+ int16 positionAdjust = 0;
+ byte curByte = 0;
+ byte fontByte = 0;
+
+ uint16 strikeRowOffset = 0;
+ uint16 strikeCurOffset = 0;
+
+ byte *fontData = nullptr;
+
+ if (!fontFile.open("agifont")) {
+ // Continue,
+ // This also happens when the user selected Apple IIgs as render for the palette for non-AppleIIgs games
+ warning("could not open agifont for Apple IIgs font");
+ return;
+ }
+
+ // Apple IIgs header
+ headerIIgs_OffsetMacHeader = fontFile.readUint16LE();
+ fontFile.skip(2); // font family
+ fontFile.skip(2); // font style
+ fontFile.skip(2); // point size
+ headerIIgs_Version = fontFile.readUint16LE();
+ fontFile.skip(2); // bounds type
+ // end of Apple IIgs header
+ // Macintosh font record
+ fontFile.skip(2); // font type
+ macRecord_FirstChar = fontFile.readUint16LE();
+ macRecord_LastChar = fontFile.readUint16LE();
+ fontFile.skip(2); // max width
+ macRecord_MaxKern = fontFile.readSint16LE();
+ fontFile.skip(2); // negative descent
+ fontFile.skip(2); // rect width
+ macRecord_RectHeight = fontFile.readUint16LE();
+ fontFile.skip(2); // low word ptr table
+ fontFile.skip(2); // font ascent
+ fontFile.skip(2); // font descent
+ fontFile.skip(2); // leading
+ macRecord_StrikeWidth = fontFile.readUint16LE();
+
+ // security-checks
+ if (headerIIgs_OffsetMacHeader != 6)
+ error("AppleIIgs-font: unexpected header");
+ if (headerIIgs_Version != 0x0101)
+ error("AppleIIgs-font: not a 1.1 font");
+ if ((macRecord_FirstChar != 0) || (macRecord_LastChar != 255))
+ error("AppleIIgs-font: unexpected characters");
+ if (macRecord_RectHeight != 8)
+ error("AppleIIgs-font: expected 8x8 font");
+
+ // Calculate table sizes
+ strikeDataLen = macRecord_StrikeWidth * macRecord_RectHeight * 2;
+ actualCharacterCount = (macRecord_LastChar - macRecord_FirstChar + 1);
+ totalCharacterCount = actualCharacterCount + 2; // replacement-char + extra character
+
+ // Allocate memory for tables
+ strikeDataPtr = (byte *)calloc(strikeDataLen, 1);
+ locationTablePtr = (uint16 *)calloc(totalCharacterCount, 2); // 1 word per character
+ offsetWidthTablePtr = (uint16 *)calloc(totalCharacterCount, 2); // ditto
+
+ // read tables
+ fontFile.read(strikeDataPtr, strikeDataLen);
+ for (curCharNr = 0; curCharNr < totalCharacterCount; curCharNr++) {
+ locationTablePtr[curCharNr] = fontFile.readUint16LE();
+ }
+ for (curCharNr = 0; curCharNr < totalCharacterCount; curCharNr++) {
+ offsetWidthTablePtr[curCharNr] = fontFile.readUint16LE();
+ }
+ fontFile.close();
+
+ // allocate space for font bitmap data
+ fontData = (uint8 *)calloc(256, 8);
+ _fontData = fontData;
+ _fontDataAllocated = fontData;
+
+ // extract font bitmap data
+ for (curCharNr = 0; curCharNr < actualCharacterCount; curCharNr++) {
+ curCharOffsetWidth = offsetWidthTablePtr[curCharNr];
+ curLocation = locationTablePtr[curCharNr];
+ if (curCharOffsetWidth == 0xFFFF) {
+ // character does not exist in font, use replacement character instead
+ curCharOffsetWidth = offsetWidthTablePtr[actualCharacterCount];
+ curLocation = locationTablePtr[actualCharacterCount];
+ curStrikeWidth = locationTablePtr[actualCharacterCount + 1] - curLocation;
+ } else {
+ curStrikeWidth = locationTablePtr[curCharNr + 1] - curLocation;
+ }
+
+ // Figure out bytes + bits location
+ curLocationBytes = curLocation >> 3;
+ curLocationBits = curLocation & 0x0007;
+ curCharWidth = curCharOffsetWidth & 0x00FF; // isolate width
+ curCharOffset = curCharOffsetWidth >> 8; // isolate offset
+
+ if (!curCharWidth) {
+ fontData += 8; // skip over this character
+ continue;
+ }
+
+ if (curCharWidth != 8) {
+ if (curCharNr != 0x3B)
+ error("AppleIIgs-font: expected 8x8 font");
+ }
+
+ // Get all rows of the current character
+ strikeRowOffset = 0;
+ for (curRow = 0; curRow < macRecord_RectHeight; curRow++) {
+ strikeCurOffset = strikeRowOffset + curLocationBytes;
+
+ // Copy over bits
+ fontByte = 0;
+ curByte = strikeDataPtr[strikeCurOffset];
+ curBitMask = 0x80 >> curLocationBits;
+
+ for (curPixelNr = 0; curPixelNr < curStrikeWidth; curPixelNr++) {
+ fontByte = fontByte << 1;
+ if (curByte & curBitMask) {
+ fontByte |= 0x01;
+ }
+ curBitMask = curBitMask >> 1;
+ if (!curBitMask) {
+ curByte = strikeDataPtr[strikeCurOffset + 1];
+ curBitMask = 0x80;
+ }
+ }
+
+ // adjust, so that it's aligned to the left (starting at 0x80 bit)
+ fontByte = fontByte << (8 - curStrikeWidth);
+
+ // now adjust according to offset + MaxKern
+ positionAdjust = macRecord_MaxKern + curCharOffset;
+
+ // adjust may be negative for space, or 8 for "empty" characters
+ if (positionAdjust > 8)
+ error("AppleIIgs-font: invalid character spacing");
+
+ if (positionAdjust < 0) {
+ // negative adjust strangely happens for empty characters like space
+ if (curStrikeWidth)
+ error("AppleIIgs-font: invalid character spacing");
+ }
+
+ if (positionAdjust > 0) {
+ // move the amount of pixels to the right
+ fontByte = fontByte >> positionAdjust;
+ }
+
+ *fontData = fontByte;
+ fontData++;
+
+ strikeRowOffset += macRecord_StrikeWidth * 2;
+ }
+ }
+
+ free(offsetWidthTablePtr);
+ free(locationTablePtr);
+ free(strikeDataPtr);
+
+ // overwrite character 0x1A with the standard Sierra arrow to the right character
+ // required for the original save/restore dialogs
+ memcpy(_fontDataAllocated + (0x1A * 8), fontData_ArrowRightCharacter, sizeof(fontData_ArrowRightCharacter));
+}
+
AgiEngine::AgiEngine(OSystem *syst, const AGIGameDescription *gameDesc) : AgiBase(syst, gameDesc) {
// Setup mixer
syncSoundSettings();
- parseFeatures();
-
DebugMan.addDebugChannel(kDebugLevelMain, "Main", "Generic debug level");
DebugMan.addDebugChannel(kDebugLevelResources, "Resources", "Resources debugging");
DebugMan.addDebugChannel(kDebugLevelSprites, "Sprites", "Sprites debugging");
@@ -561,20 +704,21 @@ AgiEngine::AgiEngine(OSystem *syst, const AGIGameDescription *gameDesc) : AgiBas
memset(&_mouse, 0, sizeof(struct Mouse));
_game.mouseEnabled = true;
+ _game.mouseHidden = false;
+ // don't check for Amiga, Amiga doesn't allow disabling mouse support. It's mandatory.
if (!ConfMan.getBool("mousesupport")) {
// we effectively disable the mouse for games, that explicitly do not want mouse support to be enabled
_game.mouseEnabled = false;
+ _game.mouseHidden = true;
}
- // We are currently using the custom font for all fanmade games
- if (!(getFeatures() & (GF_FANMADE | GF_AGDS))) {
- _fontData = fontData_Sierra; // original Sierra font
- } else {
- _fontData = fontData_FanGames; // our (own?) custom font, that supports umlauts etc.
- }
+ _fontData = nullptr;
+ _fontDataAllocated = nullptr;
_game._vm = this;
+ _game.gfxMode = true;
+
_game.clockEnabled = false;
_game.state = STATE_INIT;
@@ -585,17 +729,13 @@ AgiEngine::AgiEngine(OSystem *syst, const AGIGameDescription *gameDesc) : AgiBas
_intobj = NULL;
- _menu = NULL;
- _menuSelected = false;
-
- _lastSentence[0] = 0;
memset(&_stringdata, 0, sizeof(struct StringData));
_objects = NULL;
_restartGame = false;
- _oldMode = INPUT_NONE;
+ _oldMode = INPUTMODE_NONE;
_firstSlot = 0;
@@ -609,16 +749,17 @@ AgiEngine::AgiEngine(OSystem *syst, const AGIGameDescription *gameDesc) : AgiBas
_lastTick = 0;
memset(_keyQueue, 0, sizeof(_keyQueue));
- memset(_predictiveResult, 0, sizeof(_predictiveResult));
+ _text = NULL;
_sprites = NULL;
_picture = NULL;
_loader = NULL;
_console = NULL;
+ _menu = NULL;
+ _gfx = NULL;
+ _systemUI = NULL;
_egoHoldKey = false;
-
-
}
void AgiEngine::initialize() {
@@ -657,29 +798,28 @@ void AgiEngine::initialize() {
}
initRenderMode();
+ initFont();
- _buttonStyle = AgiButtonStyle(_renderMode);
- _defaultButtonStyle = AgiButtonStyle();
_console = new Console(this);
+ _words = new Words(this);
_gfx = new GfxMgr(this);
_sound = new SoundMgr(this, _mixer);
_picture = new PictureMgr(this, _gfx);
_sprites = new SpritesMgr(this, _gfx);
+ _text = new TextMgr(this, _words, _gfx);
+ _systemUI = new SystemUI(this, _gfx, _text);
+ _inventory = new InventoryMgr(this, _gfx, _text, _systemUI);
+
+ _text->init(_systemUI);
_gfx->initMachine();
_game.gameFlags = 0;
- _game.colorFg = 15;
- _game.colorBg = 0;
+ _text->charAttrib_Set(15, 0);
_game.name[0] = '\0';
- _game.sbufOrig = (uint8 *)calloc(_WIDTH, _HEIGHT * 2); // Allocate space for two AGI screens vertically
- _game.sbuf16c = _game.sbufOrig + SBUF16_OFFSET; // Make sbuf16c point to the 16 color (+control line & priority info) AGI screen
- _game.sbuf256c = _game.sbufOrig + SBUF256_OFFSET; // Make sbuf256c point to the 256 color AGI screen
- _game.sbuf = _game.sbuf16c; // Make sbuf point to the 16 color (+control line & priority info) AGI screen by default
-
_gfx->initVideo();
_lastSaveTime = 0;
@@ -698,6 +838,30 @@ void AgiEngine::initialize() {
debugC(2, kDebugLevelMain, "Init sound");
}
+bool AgiEngine::promptIsEnabled() {
+ return _text->promptIsEnabled();
+}
+
+void AgiEngine::redrawScreen() {
+ _game.gfxMode = true; // enable graphics mode
+ _gfx->setPalette(true); // set graphics mode palette
+ _text->charAttrib_Set(_text->_textAttrib.foreground, _text->_textAttrib.background);
+ _gfx->clearDisplay(0);
+ _picture->showPic();
+ _text->statusDraw();
+ _text->promptRedraw();
+}
+
+// Adjust a given coordinate to the local game screen
+// Used on mouse cursor coordinates before passing them to scripts
+void AgiEngine::adjustPosToGameScreen(int16 &x, int16 &y) {
+ x = x / 2; // 320 -> 160
+ y = y - 8; // remove status bar line
+ if (y >= SCRIPT_HEIGHT) {
+ y = SCRIPT_HEIGHT + 1; // 1 beyond
+ }
+}
+
AgiEngine::~AgiEngine() {
// If the engine hasn't been initialized yet via
// AgiEngine::initialize(), don't attempt to free any resources, as
@@ -710,22 +874,25 @@ AgiEngine::~AgiEngine() {
agiDeinit();
delete _loader;
_gfx->deinitVideo();
+ delete _inventory;
+ delete _systemUI;
+ delete _text;
delete _sprites;
delete _picture;
- free(_game.sbufOrig);
_gfx->deinitMachine();
delete _gfx;
+ delete _words;
delete _console;
}
Common::Error AgiBase::init() {
// Initialize backend
- initGraphics(320, 200, false);
+ //initGraphics(320, 200, false);
initialize();
- _gfx->gfxSetPalette();
+ _gfx->setPalette(true);
return Common::kNoError;
}
@@ -747,74 +914,68 @@ Common::Error AgiEngine::go() {
return Common::kNoError;
}
-void AgiEngine::parseFeatures() {
-
- /* FIXME: Seems this method doesn't really do anything. It might
- be a leftover that could be removed, except that some of its
- intended purpose may still need to be reimplemented.
-
- [0:29] <Fingolfin> can you tell me what the point behind AgiEngine::parseFeatures() is?
- [0:30] <_sev> when games are created with WAGI studio
- [0:31] <_sev> it creates .wag site with game-specific features such as full game title, whether to use AGIMOUSE etc
- [0:32] <Fingolfin> ... and the "features" config key is created by our detector based on the wag file, I guess?
- [0:33] <_sev> yes
- [0:33] <Fingolfin> it's just that I cant seem to find a place we do that
- [0:33] <_sev> it is used for fallback
- [0:34] <_sev> ah, perhaps it was not updated
- [0:34] <Fingolfin> I only see us check the value, but never set it
- [0:34] <Fingolfin> maybe I am grepping wrong, who knows :)
- [0:44] <Fingolfin> _sev: so, unless I miss something, it seem that function does nothing right now
- [0:45] <_sev> Fingolfin: it could be unfinished. It was part of GSoC 3 years ago
- [0:45] <Fingolfin> well
- [0:45] <_sev> I just don't remember
- [0:45] <Fingolfin> but don't we just re-parse the wag when the game is loaded anyway?
- [0:45] <_sev> but it documents the format
- [0:45] <Fingolfin> the advanced meta engine would re-run the detector, wouldn't it?
- [0:45] <_sev> yep
- [0:47] <Fingolfin> so... shouldn't we at least add a comment to the function explaining what it does and that it's unfinished etc.? maybe add a TODO to the wiki?
- [0:47] <Fingolfin> otherwise it might stay as it is for another 3 years :)
- */
-
- if (!ConfMan.hasKey("features"))
- return;
+// Scenes that need this:
+//
+// Manhunter 1:
+// - intro text screen (DrawPic)
+// - MAD "zooming in..." during intro and other scenes, for example room 124 (NewRoom)
+// The NewRoom call is not done during the same cycle as the "zooming in..." print call.
+// Space Quest 1:
+// - right at the start of the game (NewRoom)
+// Space Quest 2
+// - right at the start of the game (NewRoom)
+// - after exiting the very first room, a message pops up, that isn't readable without it (NewRoom)
+
+
+// Games, that must not be triggered:
+//
+// Fanmade Voodoo Girl:
+// - waterfall (DrawPic, room 17)
+// - inside shop (NewRoom, changes to same room every new button, room 4)
+
+void AgiEngine::nonBlockingText_IsShown() {
+ _game.nonBlockingTextShown = true;
+ _game.nonBlockingTextCyclesLeft = 2; // 1 additional script cycle is counted too
+}
+void AgiEngine::nonBlockingText_Forget() {
+ _game.nonBlockingTextShown = false;
+ _game.nonBlockingTextCyclesLeft = 0;
+}
+void AgiEngine::nonBlockingText_CycleDone() {
+ if (_game.nonBlockingTextCyclesLeft) {
+ _game.nonBlockingTextCyclesLeft--;
+
+ if (!_game.nonBlockingTextCyclesLeft) {
+ // cycle count expired, we assume that non-blocking text won't be a problem for room / pic change
+ _game.nonBlockingTextShown = false;
+ }
+ }
+}
- char *features = strdup(ConfMan.get("features").c_str());
- const char *feature[100];
- int numFeatures = 0;
+void AgiEngine::loadingTrigger_NewRoom(int16 newRoomNr) {
+ if (_game.nonBlockingTextShown) {
+ _game.nonBlockingTextShown = false;
- char *tok = strtok(features, " ");
- if (tok) {
- do {
- feature[numFeatures++] = tok;
- } while ((tok = strtok(NULL, " ")) != NULL);
- } else {
- feature[numFeatures++] = features;
- }
-
- const struct Flags {
- const char *name;
- uint32 flag;
- } flags[] = {
- { "agimouse", GF_AGIMOUSE },
- { "agds", GF_AGDS },
- { "agi256", GF_AGI256 },
- { "agi256-2", GF_AGI256_2 },
- { "agipal", GF_AGIPAL },
- { 0, 0 }
- };
-
- for (int i = 0; i < numFeatures; i++) {
- for (const Flags *flag = flags; flag->name; flag++) {
- if (!scumm_stricmp(feature[i], flag->name)) {
- debug(2, "Added feature: %s", flag->name);
-
- setFeature(flag->flag);
- break;
+ int16 curRoomNr = _game.vars[VM_VAR_CURRENT_ROOM];
+
+ if (newRoomNr != curRoomNr) {
+ if (!_game.automaticRestoreGame) {
+ // wait a bit, we detected non-blocking text
+ pause(2000); // 2 seconds
}
}
}
+}
+
+void AgiEngine::loadingTrigger_DrawPicture() {
+ if (_game.nonBlockingTextShown) {
+ _game.nonBlockingTextShown = false;
- free(features);
+ if (!_game.automaticRestoreGame) {
+ // wait a bit, we detected non-blocking text
+ pause(2000); // 2 seconds
+ }
+ }
}
} // End of namespace Agi
diff --git a/engines/agi/agi.h b/engines/agi/agi.h
index 04e02dcf87..fd44afd910 100644
--- a/engines/agi/agi.h
+++ b/engines/agi/agi.h
@@ -79,14 +79,18 @@ typedef signed int Err;
#define OBJECTS "object"
#define WORDS "words.tok"
-#define MAX_DIRS 256
+#define MAX_DIRECTORY_ENTRIES 256
+#define MAX_CONTROLLERS 256
#define MAX_VARS 256
#define MAX_FLAGS (256 >> 3)
-#define MAX_VIEWTABLE 255 // KQ3 uses o255!
+#define SCREENOBJECTS_MAX 255 // KQ3 uses o255!
+#define SCREENOBJECTS_EGO_ENTRY 0 // first entry is ego
#define MAX_WORDS 20
#define MAX_STRINGS 24 // MAX_STRINGS + 1 used for get.num
#define MAX_STRINGLEN 40
-#define MAX_CONTROLLERS 39
+#define MAX_CONTROLLER_KEYMAPPINGS 39
+
+#define SAVEDGAME_DESCRIPTION_LEN 30
#define _EMPTY 0xfffff
#define EGO_OWNED 0xff
@@ -95,13 +99,6 @@ typedef signed int Err;
#define CRYPT_KEY_SIERRA "Avis Durgan"
#define CRYPT_KEY_AGDS "Alex Simkin"
-#define MSG_BOX_COLOR 0x0f // White
-#define MSG_BOX_TEXT 0x00 // Black
-#define MSG_BOX_LINE 0x04 // Red
-#define BUTTON_BORDER 0x00 // Black
-#define STATUS_FG 0x00 // Black
-#define STATUS_BG 0x0f // White
-
#define ADD_PIC 1
#define ADD_VIEW 2
@@ -128,7 +125,7 @@ enum AgiGameID {
GID_GETOUTTASQ, // Fanmade
GID_MICKEY, // PreAGI
GID_WINNIE, // PreAGI
- GID_TROLL // PreAGI
+ GID_TROLL // PreAGI
};
enum AgiGameType {
@@ -143,6 +140,16 @@ enum AgiGameType {
BooterDisk2 = 1
};
+enum AgiRenderMode {
+ RENDERMODE_EGA = 0,
+ RENDERMODE_CGA = 1,
+ RENDERMODE_VGA = 2,
+ RENDERMODE_HERCULES = 3,
+ RENDERMODE_AMIGA = 4,
+ RENDERMODE_APPLE_II_GS = 5,
+ RENDERMODE_ATARI_ST = 6
+};
+
//
// GF_OLDAMIGAV20 means that the interpreter is an old Amiga AGI interpreter that
// uses value 20 for the computer type (v20 i.e. vComputer) rather than the usual value 5.
@@ -206,15 +213,17 @@ enum kDebugLevels {
* AGI resources.
*/
enum {
- rLOGIC = 1,
- rSOUND,
- rVIEW,
- rPICTURE
+ RESOURCETYPE_LOGIC = 1,
+ RESOURCETYPE_SOUND,
+ RESOURCETYPE_VIEW,
+ RESOURCETYPE_PICTURE
};
enum {
- RES_LOADED = 1,
- RES_COMPRESSED = 0x40
+ RES_LOADED = 0x01,
+ RES_COMPRESSED = 0x40,
+ RES_PICTURE_V3_NIBBLE_PARM = 0x80 // Flag that gets set for picture resources,
+ // which use a nibble instead of a byte as F0+F2 parameters
};
enum {
@@ -244,44 +253,37 @@ enum AgiMouseButton {
kAgiMouseButtonMiddle // Middle mouse button
};
-enum GameId {
- GID_AGI = 1
-};
-
-#define WIN_TO_PIC_X(x) ((x) / 2)
-#define WIN_TO_PIC_Y(y) ((y) < 8 ? 999 : (y) >= (8 + _HEIGHT) ? 999 : (y) - 8)
-
/**
* AGI variables.
*/
enum {
- vCurRoom = 0, // 0
- vPrevRoom,
- vBorderTouchEgo,
- vScore,
- vBorderCode,
- vBorderTouchObj, // 5
- vEgoDir,
- vMaxScore,
- vFreePages,
- vWordNotFound,
- vTimeDelay, // 10
- vSeconds,
- vMinutes,
- vHours,
- vDays,
- vJoystickSensitivity, // 15
- vEgoViewResource,
- vAgiErrCode,
- vAgiErrCodeInfo,
- vKey,
- vComputer, // 20
- vWindowReset,
- vSoundgen,
- vVolume,
- vMaxInputChars,
- vSelItem, // 25
- vMonitor
+ VM_VAR_CURRENT_ROOM = 0, // 0
+ VM_VAR_PREVIOUS_ROOM, // 1
+ VM_VAR_BORDER_TOUCH_EGO, // 2
+ VM_VAR_SCORE, // 3
+ VM_VAR_BORDER_CODE, // 4
+ VM_VAR_BORDER_TOUCH_OBJECT, // 5
+ VM_VAR_EGO_DIRECTION, // 6
+ VM_VAR_MAX_SCORE, // 7
+ VM_VAR_FREE_PAGES, // 8
+ VM_VAR_WORD_NOT_FOUND, // 9
+ VM_VAR_TIME_DELAY, // 10
+ VM_VAR_SECONDS, // 11
+ VM_VAR_MINUTES, // 12
+ VM_VAR_HOURS, // 13
+ VM_VAR_DAYS, // 14
+ VM_VAR_JOYSTICK_SENSITIVITY, // 15
+ VM_VAR_EGO_VIEW_RESOURCE, // 16
+ VM_VAR_AGI_ERROR_CODE, // 17
+ VM_VAR_AGI_ERROR_INFO, // 18
+ VM_VAR_KEY, // 19
+ VM_VAR_COMPUTER, // 20
+ VM_VAR_WINDOW_RESET, // 21
+ VM_VAR_SOUNDGENERATOR, // 22
+ VM_VAR_VOLUME, // 23
+ VM_VAR_MAX_INPUT_CHARACTERS, // 24
+ VM_VAR_SELECTED_INVENTORY_ITEM, // 25
+ VM_VAR_MONITOR // 26
};
/**
@@ -335,33 +337,28 @@ enum AgiSoundType {
* AGI flags
*/
enum {
- fEgoWater = 0, // 0
- fEgoInvisible,
- fEnteredCli,
- fEgoTouchedP2,
- fSaidAcceptedInput,
- fNewRoomExec, // 5
- fRestartGame,
- fScriptBlocked,
- fJoySensitivity,
- fSoundOn,
- fDebuggerOn, // 10
- fLogicZeroFirsttime,
- fRestoreJustRan,
- fStatusSelectsItems,
- fMenusWork,
- fOutputMode, // 15
- fAutoRestart
-};
-
-enum AgiSlowliness {
- kPauseRoom = 1500,
- kPausePicture = 500
-};
-
-struct AgiController {
+ VM_FLAG_EGO_WATER = 0, // 0
+ VM_FLAG_EGO_INVISIBLE,
+ VM_FLAG_ENTERED_CLI,
+ VM_FLAG_EGO_TOUCHED_P2,
+ VM_FLAG_SAID_ACCEPTED_INPUT,
+ VM_FLAG_NEW_ROOM_EXEC, // 5
+ VM_FLAG_RESTART_GAME,
+ VM_FLAG_SCRIPT_BLOCKED,
+ VM_FLAG_JOY_SENSITIVITY,
+ VM_FLAG_SOUND_ON,
+ VM_FLAG_DEBUGGER_ON, // 10
+ VM_FLAG_LOGIC_ZERO_FIRST_TIME,
+ VM_FLAG_RESTORE_JUST_RAN,
+ VM_FLAG_STATUS_SELECTS_ITEMS,
+ VM_FLAG_MENUS_WORK,
+ VM_FLAG_OUTPUT_MODE, // 15
+ VM_FLAG_AUTO_RESTART
+};
+
+struct AgiControllerKeyMapping {
uint16 keycode;
- uint8 controller;
+ byte controllerSlot;
};
struct AgiObject {
@@ -369,11 +366,6 @@ struct AgiObject {
char *name;
};
-struct AgiWord {
- int id;
- char *word;
-};
-
struct AgiDir {
uint8 volume;
uint32 offset;
@@ -389,122 +381,9 @@ struct AgiDir {
};
struct AgiBlock {
- int active;
- int x1, y1;
- int x2, y2;
- uint8 *buffer; // used for window background
-};
-
-/** AGI text color (Background and foreground color). */
-struct AgiTextColor {
- /** Creates an AGI text color. Uses black text on white background by default. */
- AgiTextColor(int fgColor = 0x00, int bgColor = 0x0F) : fg(fgColor), bg(bgColor) {}
-
- /** Get an AGI text color with swapped foreground and background color. */
- AgiTextColor swap() const { return AgiTextColor(bg, fg); }
-
- int fg; ///< Foreground color (Used for text).
- int bg; ///< Background color (Used for text's background).
-};
-
-/**
- * AGI button style (Amiga or PC).
- *
- * Supports positive and negative button types (Used with Amiga-style only):
- * Positive buttons do what the dialog was opened for.
- * Negative buttons cancel what the dialog was opened for.
- * Restart-dialog example: Restart-button is positive, Cancel-button negative.
- * Paused-dialog example: Continue-button is positive.
- */
-struct AgiButtonStyle {
-// Public constants etc
-public:
- static const int
- // Amiga colors (Indexes into the Amiga-ish palette)
- amigaBlack = 0x00, ///< Accurate, is #000000 (24-bit RGB)
- amigaWhite = 0x0F, ///< Practically accurate, is close to #FFFFFF (24-bit RGB)
- amigaGreen = 0x02, ///< Quite accurate, should be #008A00 (24-bit RGB)
- amigaOrange = 0x0C, ///< Inaccurate, too much blue, should be #FF7500 (24-bit RGB)
- amigaPurple = 0x0D, ///< Inaccurate, too much green, should be #FF00FF (24-bit RGB)
- amigaRed = 0x04, ///< Quite accurate, should be #BD0000 (24-bit RGB)
- amigaCyan = 0x0B, ///< Inaccurate, too much red, should be #00FFDE (24-bit RGB)
- // PC colors (Indexes into the EGA-palette)
- pcBlack = 0x00,
- pcWhite = 0x0F;
-
-// Public methods
-public:
- /**
- * Get the color of the button with the given state and type using current style.
- *
- * @param hasFocus True if button has focus, false otherwise.
- * @param pressed True if button is being pressed, false otherwise.
- * @param positive True if button is positive, false if button is negative. Only matters for Amiga-style buttons.
- */
- AgiTextColor getColor(bool hasFocus, bool pressed, bool positive = true) const;
-
- /**
- * Get the color of a button with the given base color and state ignoring current style.
- * Swaps foreground and background color when the button has focus or is being pressed.
- *
- * @param hasFocus True if button has focus, false otherwise.
- * @param pressed True if button is being pressed, false otherwise.
- * @param baseFgColor Foreground color of the button when it has no focus and is not being pressed.
- * @param baseBgColor Background color of the button when it has no focus and is not being pressed.
- */
- AgiTextColor getColor(bool hasFocus, bool pressed, int baseFgColor, int baseBgColor) const;
-
- /**
- * Get the color of a button with the given base color and state ignoring current style.
- * Swaps foreground and background color when the button has focus or is being pressed.
- *
- * @param hasFocus True if button has focus, false otherwise.
- * @param pressed True if button is being pressed, false otherwise.
- * @param baseColor Color of the button when it has no focus and is not being pressed.
- */
- AgiTextColor getColor(bool hasFocus, bool pressed, const AgiTextColor &baseColor) const;
-
- /**
- * How many pixels to offset the shown text diagonally down and to the right.
- * Currently only used for pressed PC-style buttons.
- */
- int getTextOffset(bool hasFocus, bool pressed) const;
-
- /**
- * Show border around the button?
- * Currently border is only used for in focus or pressed Amiga-style buttons
- * when in inauthentic Amiga-style mode.
- */
- bool getBorder(bool hasFocus, bool pressed) const;
-
- /**
- * Set Amiga-button style.
- *
- * @param amigaStyle Set Amiga-button style if true, otherwise set PC-button style.
- * @param olderAgi If true then use older AGI style in Amiga-mode, otherwise use newer.
- * @param authenticAmiga If true then don't use a border around buttons in Amiga-mode, otherwise use.
- */
- void setAmigaStyle(bool amigaStyle = true, bool olderAgi = false, bool authenticAmiga = false);
-
- /**
- * Set PC-button style.
- * @param pcStyle Set PC-button style if true, otherwise set default Amiga-button style.
- */
- void setPcStyle(bool pcStyle = true);
-
-// Public constructors
-public:
- /**
- * Create a button style based on the given rendering mode.
- * @param renderMode If Common::kRenderAmiga then creates default Amiga-button style, otherwise PC-style.
- */
- AgiButtonStyle(Common::RenderMode renderMode = Common::kRenderDefault);
-
-// Private member variables
-private:
- bool _amigaStyle; ///< Use Amiga-style buttons if true, otherwise use PC-style buttons.
- bool _olderAgi; ///< Use older AGI style in Amiga-style mode.
- bool _authenticAmiga; ///< Don't use border around buttons in Amiga-style mode.
+ bool active;
+ int16 x1, y1;
+ int16 x2, y2;
};
struct ScriptPos {
@@ -512,18 +391,17 @@ struct ScriptPos {
int curIP;
};
-enum {
- EGO_VIEW_TABLE = 0,
- HORIZON = 36,
- _WIDTH = 160,
- _HEIGHT = 168
+enum InputMode {
+ INPUTMODE_NONE = 0x04,
+ INPUTMODE_NORMAL = 0x01 // prompt active
};
-enum InputMode {
- INPUT_NORMAL = 0x01,
- INPUT_GETSTRING = 0x02,
- INPUT_MENU = 0x03,
- INPUT_NONE = 0x04
+enum CycleInnerLoopType {
+ CYCLE_INNERLOOP_GETSTRING = 0,
+ CYCLE_INNERLOOP_GETNUMBER = 1,
+ CYCLE_INNERLOOP_INVENTORY = 2,
+ CYCLE_INNERLOOP_MENU = 3,
+ CYCLE_INNERLOOP_SYSTEMUI_SELECTSAVEDGAMESLOT = 4
};
enum State {
@@ -532,12 +410,7 @@ enum State {
STATE_RUNNING = 0x02
};
-enum {
- SBUF16_OFFSET = 0,
- SBUF256_OFFSET = ((_WIDTH) * (_HEIGHT)),
- FROM_SBUF16_TO_SBUF256_OFFSET = ((SBUF256_OFFSET) - (SBUF16_OFFSET)),
- FROM_SBUF256_TO_SBUF16_OFFSET = ((SBUF16_OFFSET) - (SBUF256_OFFSET))
-};
+typedef Common::Array<int16> SavedGameSlotIdArray;
/**
* AGI game structure.
@@ -563,94 +436,89 @@ struct AgiGame {
uint8 vars[MAX_VARS]; /**< 256 variables */
// internal variables
- int horizon; /**< horizon y coordinate */
- int lineStatus; /**< line number to put status on */
- int lineUserInput; /**< line to put user input on */
- int lineMinPrint; /**< num lines to print on */
- int cursorPos; /**< column where the input cursor is */
- byte inputBuffer[40]; /**< buffer for user input */
- byte echoBuffer[40]; /**< buffer for echo.line */
+ int16 horizon; /**< horizon y coordinate */
+
int keypress;
+ bool cycleInnerLoopActive;
+ int16 cycleInnerLoopType;
+
InputMode inputMode; /**< keyboard input mode */
- bool inputEnabled; /**< keyboard input enabled */
+
+ uint16 specialMenuTriggerKey; /**< key to trigger menu for platforms except PC */
+
int lognum; /**< current logic number */
Common::Array<ScriptPos> execStack;
// internal flags
int playerControl; /**< player is in control */
- int statusLine; /**< status line on/off */
int clockEnabled; /**< clock is on/off */
int exitAllLogics; /**< break cycle after new.room */
- int pictureShown; /**< show.pic has been issued */
+ bool pictureShown; /**< show.pic has been issued */
int hasPrompt; /**< input prompt has been printed */
#define ID_AGDS 0x00000001
#define ID_AMIGA 0x00000002
int gameFlags; /**< agi options flags */
- uint8 priTable[_HEIGHT];/**< priority table */
-
// windows
uint32 msgBoxTicks; /**< timed message box tick counter */
AgiBlock block;
- AgiBlock window;
- int hasWindow;
// graphics & text
- int gfxMode;
- char cursorChar;
- unsigned int colorFg;
- unsigned int colorBg;
-
- uint8 *sbufOrig; /**< Pointer to the 160x336 AGI screen buffer that contains vertically two 160x168 screens (16 color and 256 color). */
- uint8 *sbuf16c; /**< 160x168 16 color (+control line & priority information) AGI screen buffer. Points at sbufOrig + SBUF16_OFFSET. */
- uint8 *sbuf256c; /**< 160x168 256 color AGI screen buffer (For AGI256 and AGI256-2 support). Points at sbufOrig + SBUF256_OFFSET. */
- uint8 *sbuf; /**< Currently chosen AGI screen buffer (sbuf256c if AGI256 or AGI256-2 is used, otherwise sbuf16c). */
-
- // player command line
- AgiWord egoWords[MAX_WORDS];
- int numEgoWords;
+ bool gfxMode;
unsigned int numObjects;
- bool controllerOccured[MAX_DIRS]; /**< keyboard keypress events */
- AgiController controllers[MAX_CONTROLLERS];
+ bool controllerOccured[MAX_CONTROLLERS]; /**< keyboard keypress events */
+ AgiControllerKeyMapping controllerKeyMapping[MAX_CONTROLLER_KEYMAPPINGS];
char strings[MAX_STRINGS + 1][MAX_STRINGLEN]; /**< strings */
// directory entries for resources
- AgiDir dirLogic[MAX_DIRS];
- AgiDir dirPic[MAX_DIRS];
- AgiDir dirView[MAX_DIRS];
- AgiDir dirSound[MAX_DIRS];
+ AgiDir dirLogic[MAX_DIRECTORY_ENTRIES];
+ AgiDir dirPic[MAX_DIRECTORY_ENTRIES];
+ AgiDir dirView[MAX_DIRECTORY_ENTRIES];
+ AgiDir dirSound[MAX_DIRECTORY_ENTRIES];
// resources
- AgiPicture pictures[MAX_DIRS]; /**< AGI picture resources */
- AgiLogic logics[MAX_DIRS]; /**< AGI logic resources */
- AgiView views[MAX_DIRS]; /**< AGI view resources */
- AgiSound *sounds[MAX_DIRS]; /**< Pointers to AGI sound resources */
+ AgiPicture pictures[MAX_DIRECTORY_ENTRIES]; /**< AGI picture resources */
+ AgiLogic logics[MAX_DIRECTORY_ENTRIES]; /**< AGI logic resources */
+ AgiView views[MAX_DIRECTORY_ENTRIES]; /**< AGI view resources */
+ AgiSound *sounds[MAX_DIRECTORY_ENTRIES]; /**< Pointers to AGI sound resources */
AgiLogic *_curLogic;
- // words
- Common::Array<AgiWord *> words[26];
-
// view table
- VtEntry viewTable[MAX_VIEWTABLE];
+ ScreenObjEntry screenObjTable[SCREENOBJECTS_MAX];
+
+ ScreenObjEntry addToPicView;
int32 ver; /**< detected game version */
- int simpleSave; /**< select simple savegames */
+ bool automaticSave; /**< set by CmdSetSimple() */
+ char automaticSaveDescription[SAVEDGAME_DESCRIPTION_LEN + 1];
Common::Rect mouseFence; /**< rectangle set by fence.mouse command */
bool mouseEnabled; /**< if mouse is supposed to be active */
+ bool mouseHidden; /**< if mouse is currently hidden */
// IF condition handling
int testResult;
-
int max_logics;
int logic_list[256];
+
+ // used to detect situations, where the game shows some text and changes rooms right afterwards
+ // for example Space Quest 2 intro right at the start
+ // or Space Quest 2, when entering the vent also right at the start
+ // The developers assumed that loading the new room would take a bit.
+ // In ScummVM it's basically done in an instant, which means that
+ // the text would only get shown for a split second.
+ // We delay a bit as soon as such situations get detected.
+ bool nonBlockingTextShown;
+ int16 nonBlockingTextCyclesLeft;
+
+ bool automaticRestoreGame;
};
class AgiLoader {
@@ -662,8 +530,8 @@ public:
virtual int init() = 0;
virtual int deinit() = 0;
virtual int detectGame() = 0;
- virtual int loadResource(int, int) = 0;
- virtual int unloadResource(int, int) = 0;
+ virtual int loadResource(int16 resourceType, int16 resourceNr) = 0;
+ virtual int unloadResource(int16 resourceType, int16 resourceNr) = 0;
virtual int loadObjects(const char *) = 0;
virtual int loadWords(const char *) = 0;
};
@@ -684,8 +552,8 @@ public:
virtual int init();
virtual int deinit();
virtual int detectGame();
- virtual int loadResource(int, int);
- virtual int unloadResource(int, int);
+ virtual int loadResource(int16 resourceType, int16 resourceNr);
+ virtual int unloadResource(int16 resourceType, int16 resourceNr);
virtual int loadObjects(const char *);
virtual int loadWords(const char *);
};
@@ -706,8 +574,8 @@ public:
virtual int init();
virtual int deinit();
virtual int detectGame();
- virtual int loadResource(int, int);
- virtual int unloadResource(int, int);
+ virtual int loadResource(int16 resourceType, int16 resourceNr);
+ virtual int unloadResource(int16 resourceType, int16 resourceNr);
virtual int loadObjects(const char *);
virtual int loadWords(const char *);
};
@@ -728,8 +596,8 @@ public:
virtual int init();
virtual int deinit();
virtual int detectGame();
- virtual int loadResource(int, int);
- virtual int unloadResource(int, int);
+ virtual int loadResource(int16 resourceType, int16 resourceNr);
+ virtual int unloadResource(int16 resourceType, int16 resourceNr);
virtual int loadObjects(const char *);
virtual int loadWords(const char *);
};
@@ -737,7 +605,11 @@ public:
class GfxMgr;
class SpritesMgr;
-class Menu;
+class InventoryMgr;
+class TextMgr;
+class GfxMenu;
+class SystemUI;
+class Words;
// Image stack support
struct ImageStackElement {
@@ -780,15 +652,21 @@ protected:
virtual void initialize() = 0;
void initRenderMode();
+ void initFont();
+
+ void loadFontMickey();
+ void loadFontAmigaPseudoTopaz();
+ void loadFontAppleIIgs();
- const uint8 *_fontData;
+ const uint8 *_fontData; // pointer to the currently used font
+ uint8 *_fontDataAllocated;
public:
+ Words *_words;
+
GfxMgr *_gfx;
- AgiButtonStyle _defaultButtonStyle;
- AgiButtonStyle _buttonStyle;
- Common::RenderMode _renderMode;
+ AgiRenderMode _renderMode;
volatile uint32 _clockCount;
AgiDebug _debug;
AgiGame _game;
@@ -800,6 +678,10 @@ public:
bool _noSaveLoadAllowed;
+ virtual bool promptIsEnabled() {
+ return false;
+ }
+
virtual void pollTimer() = 0;
virtual int getKeypress() = 0;
virtual bool isKeypress() = 0;
@@ -844,6 +726,17 @@ public:
bool canSaveGameStateCurrently();
const uint8 *getFontData() { return _fontData; };
+
+ void cycleInnerLoopActive(int16 loopType) {
+ _game.cycleInnerLoopActive = true;
+ _game.cycleInnerLoopType = loopType;
+ };
+ void cycleInnerLoopInactive() {
+ _game.cycleInnerLoopActive = false;
+ };
+ bool cycleInnerLoopIsActive() {
+ return _game.cycleInnerLoopActive;
+ }
};
typedef void (*AgiCommand)(AgiGame *state, uint8 *p);
@@ -861,8 +754,12 @@ public:
AgiEngine(OSystem *syst, const AGIGameDescription *gameDesc);
virtual ~AgiEngine();
+ bool promptIsEnabled();
+
Common::Error loadGameState(int slot);
- Common::Error saveGameState(int slot, const Common::String &desc);
+ Common::Error saveGameState(int slot, const Common::String &description);
+
+ void adjustPosToGameScreen(int16 &x, int16 &y);
private:
uint32 _lastTick;
@@ -873,11 +770,9 @@ private:
bool _allowSynthetic;
- int checkPriority(VtEntry *v);
- int checkCollision(VtEntry *v);
- int checkPosition(VtEntry *v);
-
- void parseFeatures();
+ bool checkPriority(ScreenObjEntry *v);
+ bool checkCollision(ScreenObjEntry *v);
+ bool checkPosition(ScreenObjEntry *v);
int _firstSlot;
@@ -886,15 +781,16 @@ public:
StringData _stringdata;
- Common::String getSavegameFilename(int num) const;
- void getSavegameDescription(int num, char *buf, bool showEmpty = true);
- int selectSlot();
- int saveGame(const Common::String &fileName, const Common::String &saveName);
+ SavedGameSlotIdArray getSavegameSlotIds();
+ Common::String getSavegameFilename(int16 slotId) const;
+ bool getSavegameInformation(int16 slotId, Common::String &saveDescription, uint32 &saveDate, uint16 &saveTime, bool &saveIsValid);
+
+ int saveGame(const Common::String &fileName, const Common::String &descriptionString);
int loadGame(const Common::String &fileName, bool checkId = true);
- int saveGameDialog();
- int saveGameSimple();
- int loadGameDialog();
- int loadGameSimple();
+ bool saveGameDialog();
+ bool saveGameAutomatic();
+ bool loadGameDialog();
+ bool loadGameAutomatic();
int doSave(int slot, const Common::String &desc);
int doLoad(int slot, bool showMessages);
int scummVMSaveLoadDialog(bool isSave);
@@ -903,14 +799,13 @@ public:
InputMode _oldMode;
bool _restartGame;
- Menu* _menu;
- bool _menuSelected;
-
- char _lastSentence[40];
-
SpritesMgr *_sprites;
+ TextMgr *_text;
+ InventoryMgr *_inventory;
PictureMgr *_picture;
AgiLoader *_loader; // loader
+ GfxMenu *_menu;
+ SystemUI *_systemUI;
Common::Stack<ImageStackElement> _imageStack;
@@ -929,8 +824,8 @@ public:
int agiInit();
int agiDeinit();
int agiDetectGame();
- int agiLoadResource(int, int);
- int agiUnloadResource(int, int);
+ int agiLoadResource(int16 resourceType, int16 resourceNr);
+ int agiUnloadResource(int16 resourceType, int16 resourceNr);
void agiUnloadResources();
virtual void pollTimer();
@@ -938,35 +833,27 @@ public:
virtual bool isKeypress();
virtual void clearKeyQueue();
- void initPriTable();
-
void newInputMode(InputMode mode);
void oldInputMode();
- int getvar(int);
- void setvar(int, int);
+ int getVar(int16 varNr);
+ void setVar(int16 varNr, int);
void decrypt(uint8 *mem, int len);
void releaseSprites();
int mainCycle(bool onlyCheckForEvents = false);
int viewPictures();
int runGame();
- void inventory();
void updateTimer();
int getAppDir(char *appDir, unsigned int size);
int setupV2Game(int ver);
int setupV3Game(int ver);
- void newRoom(int n);
+ void newRoom(int16 newRoomNr);
void resetControllers();
void interpretCycle();
int playGame();
- void printItem(int n, int fg, int bg);
- int findItem();
- int showItems();
- void selectItems(int n);
-
void allowSynthetic(bool);
void processEvents();
void checkQuickLoad();
@@ -977,9 +864,9 @@ public:
int loadObjects(const char *fname);
int loadObjects(Common::File &fp);
void unloadObjects();
- const char *objectName(unsigned int);
- int objectGetLocation(unsigned int);
- void objectSetLocation(unsigned int, int);
+ const char *objectName(uint16 objectNr);
+ int objectGetLocation(uint16 objectNr);
+ void objectSetLocation(uint16 objectNr, int);
private:
int decodeObjects(uint8 *mem, uint32 flen);
int readObjects(Common::File &fp, int flen);
@@ -987,8 +874,8 @@ private:
// Logic
public:
- int decodeLogic(int);
- void unloadLogic(int);
+ int decodeLogic(int16 logicNr);
+ void unloadLogic(int16 logicNr);
int runLogic(int);
void debugConsole(int, int, const char *);
int testIfCode(int);
@@ -1010,93 +897,72 @@ public:
// View
private:
- void lSetCel(VtEntry *v, int n);
- void lSetLoop(VtEntry *v, int n);
- void updateView(VtEntry *v);
+ void lSetLoop(ScreenObjEntry *screenObj, int16 loopNr);
+ void updateView(ScreenObjEntry *screenObj);
public:
+ void setView(ScreenObjEntry *screenObj, int16 viewNr);
+ void setLoop(ScreenObjEntry *screenObj, int16 loopNr);
+ void setCel(ScreenObjEntry *screenObj, int16 celNr);
- void setCel(VtEntry *, int);
- void clipViewCoordinates(VtEntry *v);
- void setLoop(VtEntry *, int);
- void setView(VtEntry *, int);
- void startUpdate(VtEntry *);
- void stopUpdate(VtEntry *);
- void updateViewtable();
- void unloadView(int);
- int decodeView(int);
- void addToPic(int, int, int, int, int, int, int);
- void drawObj(int);
- bool isEgoView(const VtEntry *v);
+ void clipViewCoordinates(ScreenObjEntry *screenObj);
+
+ void startUpdate(ScreenObjEntry *);
+ void stopUpdate(ScreenObjEntry *);
+ void updateScreenObjTable();
+ void unloadView(int16 viewNr);
+ int decodeView(byte *resourceData, uint16 resourceSize, int16 viewNr);
+
+private:
+ void unpackViewCelData(AgiViewCel *celData, byte *compressedData, uint16 compressedSize);
+ void unpackViewCelDataAGI256(AgiViewCel *celData, byte *compressedData, uint16 compressedSize);
- // Words
public:
- int showWords();
- int loadWords(const char *);
- int loadWords_v1(Common::File &f);
- void unloadWords();
- int findWord(const char *word, int *flen);
- void dictionaryWords(char *);
+ void addToPic(int, int, int, int, int, int, int);
+ void drawObj(int);
+ bool isEgoView(const ScreenObjEntry *screenObj);
// Motion
private:
int checkStep(int delta, int step);
- int checkBlock(int x, int y);
- void changePos(VtEntry *v);
- void motionWander(VtEntry *v);
- void motionFollowEgo(VtEntry *v);
- void motionMoveObj(VtEntry *v);
- void checkMotion(VtEntry *v);
+ bool checkBlock(int16 x, int16 y);
+ void changePos(ScreenObjEntry *screenObj);
+ void motionWander(ScreenObjEntry *screenObj);
+ void motionFollowEgo(ScreenObjEntry *screenObj);
+ void motionMoveObj(ScreenObjEntry *screenObj);
+ void motionMoveObjStop(ScreenObjEntry *screenObj);
+ void checkMotion(ScreenObjEntry *screenObj);
public:
void checkAllMotions();
- void moveObj(VtEntry *);
- void inDestination(VtEntry *);
- void fixPosition(int);
+ void moveObj(ScreenObjEntry *screenObj);
+ void inDestination(ScreenObjEntry *screenObj);
+ void fixPosition(int16 screenObjNr);
+ void fixPosition(ScreenObjEntry *screenObj);
void updatePosition();
- int getDirection(int x0, int y0, int x, int y, int s);
+ int getDirection(int16 objX, int16 objY, int16 destX, int16 destY, int16 stepSize);
bool _egoHoldKey;
// Keyboard
- void initWords();
- void cleanInput();
int doPollKeyboard();
void cleanKeyboard();
- void handleKeys(int);
- void handleGetstring(int);
- int handleController(int);
- void getString(int, int, int, int);
+
+ int16 getSpecialMenuControllerSlot();
+ bool handleController(uint16 key);
uint16 agiGetKeypress();
int waitKey();
int waitAnyKey();
- // Text
-public:
- int messageBox(const char *);
- int selectionBox(const char *, const char **);
- void closeWindow();
- void drawWindow(int, int, int, int);
- void printText(const char *, int, int, int, int, int, int, bool checkerboard = false);
- void printTextConsole(const char *, int, int, int, int, int);
- int print(const char *, int, int, int);
- char *wordWrapString(const char *, int *);
- char *agiSprintf(const char *);
- void writeStatus();
- void writePrompt();
- void clearPrompt(bool useBlackBg = false);
- void clearLines(int, int, int);
- void flushLines(int, int);
+ void nonBlockingText_IsShown();
+ void nonBlockingText_Forget();
+ void nonBlockingText_CycleDone();
-private:
- void printStatus(const char *message, ...) GCC_PRINTF(2, 3);
- void printText2(int l, const char *msg, int foff, int xoff, int yoff, int len, int fg, int bg, bool checkerboard = false);
- void blitTextbox(const char *p, int y, int x, int len);
- void eraseTextbox();
- bool matchWord();
+ void loadingTrigger_NewRoom(int16 newRoomNr);
+ void loadingTrigger_DrawPicture();
public:
- char _predictiveResult[40];
+ void redrawScreen();
private:
AgiCommand _agiCommands[183];
diff --git a/engines/agi/checks.cpp b/engines/agi/checks.cpp
index e61146e901..8399c9834a 100644
--- a/engines/agi/checks.cpp
+++ b/engines/agi/checks.cpp
@@ -21,159 +21,156 @@
*/
#include "agi/agi.h"
+#include "agi/graphics.h"
namespace Agi {
-int AgiEngine::checkPosition(VtEntry *v) {
- debugC(4, kDebugLevelSprites, "check position @ %d, %d", v->xPos, v->yPos);
+bool AgiEngine::checkPosition(ScreenObjEntry *screenObj) {
+ bool result = true; // position is fine
+ debugC(4, kDebugLevelSprites, "check position @ %d, %d", screenObj->xPos, screenObj->yPos);
- if (v->xPos < 0 ||
- v->xPos + v->xSize > _WIDTH ||
- v->yPos - v->ySize + 1 < 0 ||
- v->yPos >= _HEIGHT ||
- ((~v->flags & fIgnoreHorizon) && v->yPos <= _game.horizon)) {
- debugC(4, kDebugLevelSprites, "check position failed: x=%d, y=%d, h=%d, w=%d",
- v->xPos, v->yPos, v->xSize, v->ySize);
- return 0;
- }
+ do {
+ if (screenObj->xPos < 0) {
+ result = false;
+ break;
+ }
+ if (screenObj->xPos + screenObj->xSize > SCRIPT_WIDTH) {
+ result = false;
+ break;
+ }
+ if (screenObj->yPos - screenObj->ySize < -1) {
+ result = false;
+ break;
+ }
+ if (screenObj->yPos >= SCRIPT_HEIGHT) {
+ result = false;
+ break;
+ }
+ if (((!(screenObj->flags & fIgnoreHorizon)) && screenObj->yPos <= _game.horizon)) {
+ result = false;
+ break;
+ }
+ } while (0);
// MH1 needs this, but it breaks LSL1
- if (getVersion() >= 0x3000) {
- if (v->yPos < v->ySize)
- return 0;
- }
+// TODO: *NOT* in disassembly of AGI3 .149, why was this needed?
+// if (getVersion() >= 0x3000) {
+// if (screenObj->yPos < screenObj->ySize)
+// result = false;
+// }
- return 1;
+ if (!result) {
+ debugC(4, kDebugLevelSprites, "check position failed: x=%d, y=%d, h=%d, w=%d",
+ screenObj->xPos, screenObj->yPos, screenObj->xSize, screenObj->ySize);
+ }
+ return result;
}
/**
* Check if there's another object on the way
*/
-int AgiEngine::checkCollision(VtEntry *v) {
- VtEntry *u;
+bool AgiEngine::checkCollision(ScreenObjEntry *screenObj) {
+ ScreenObjEntry *checkObj;
- if (v->flags & fIgnoreObjects)
- return 0;
+ if (screenObj->flags & fIgnoreObjects)
+ return false;
- for (u = _game.viewTable; u < &_game.viewTable[MAX_VIEWTABLE]; u++) {
- if ((u->flags & (fAnimated | fDrawn)) != (fAnimated | fDrawn))
+ for (checkObj = _game.screenObjTable; checkObj < &_game.screenObjTable[SCREENOBJECTS_MAX]; checkObj++) {
+ if ((checkObj->flags & (fAnimated | fDrawn)) != (fAnimated | fDrawn))
continue;
- if (u->flags & fIgnoreObjects)
+ if (checkObj->flags & fIgnoreObjects)
continue;
// Same object, check next
- if (v->entry == u->entry)
+ if (screenObj->objectNr == checkObj->objectNr)
continue;
// No horizontal overlap, check next
- if (v->xPos + v->xSize < u->xPos || v->xPos > u->xPos + u->xSize)
+ if (screenObj->xPos + screenObj->xSize < checkObj->xPos || screenObj->xPos > checkObj->xPos + checkObj->xSize)
continue;
// Same y, return error!
- if (v->yPos == u->yPos) {
- debugC(4, kDebugLevelSprites, "check returns 1 (object %d)", v->entry);
- return 1;
+ if (screenObj->yPos == checkObj->yPos) {
+ debugC(4, kDebugLevelSprites, "check returns 1 (object %d)", screenObj->objectNr);
+ return true;
}
// Crossed the baseline, return error!
- if ((v->yPos > u->yPos && v->yPos2 < u->yPos2) ||
- (v->yPos < u->yPos && v->yPos2 > u->yPos2)) {
- debugC(4, kDebugLevelSprites, "check returns 1 (object %d)", v->entry);
- return 1;
+ if ((screenObj->yPos > checkObj->yPos && screenObj->yPos_prev < checkObj->yPos_prev) ||
+ (screenObj->yPos < checkObj->yPos && screenObj->yPos_prev > checkObj->yPos_prev)) {
+ debugC(4, kDebugLevelSprites, "check returns 1 (object %d)", screenObj->objectNr);
+ return true;
}
}
-
- return 0;
-
+
+ return false;
}
-int AgiEngine::checkPriority(VtEntry *v) {
- int i, trigger, water, pass, pri;
- uint8 *p0;
+bool AgiEngine::checkPriority(ScreenObjEntry *screenObj) {
+ bool touchedWater = false;
+ bool touchedTrigger = false;
+ bool touchedControl = true;
+ int16 curX;
+ int16 curY;
+ int16 celX;
+ byte screenPriority = 0;
- if (~v->flags & fFixedPriority) {
+ if (!(screenObj->flags & fFixedPriority)) {
// Priority bands
- v->priority = _game.priTable[v->yPos];
- }
-
- trigger = 0;
- water = 0;
- pass = 1;
-
- if (v->priority == 0x0f) {
- // Check ego
- if (v->entry == 0) {
- setflag(fEgoTouchedP2, trigger ? true : false);
- setflag(fEgoWater, water ? true : false);
- }
-
- return pass;
- }
-
- water = 1;
-
- // Check if any picture is loaded before checking for priority below.
- // If no picture has been loaded, the priority buffer won't be initialized,
- // thus the check below will always fail. This case causes an infinite loop
- // in the fanmade game Nick's Quest (bug #3451122), as the game attempts to
- // draw a sprite (view 4, floating Nick) before it loads any picture. This
- // causes the checks below to always fail, and the engine keeps readjusting
- // the sprite's position in fixPosition() forever, as there is no valid
- // position to place it (the default visual and priority screen is set to
- // zero, i.e. unconditional black). To remedy this situation, we always
- // return true here if no picture has been loaded and no priority screen
- // has been set up.
- if (!_game._vm->_picture->isPictureLoaded()) {
- warning("checkPriority: no picture loaded");
- return pass;
+ screenObj->priority = _gfx->priorityFromY(screenObj->yPos);
}
- p0 = &_game.sbuf16c[v->xPos + v->yPos * _WIDTH];
-
- for (i = 0; i < v->xSize; i++, p0++) {
- pri = *p0 >> 4;
+ if (screenObj->priority != 0x0f) {
- if (pri == 0) { // unconditional black. no go at all!
- pass = 0;
- break;
- }
+ touchedWater = true;
- if (pri == 3) // water surface
- continue;
+ curX = screenObj->xPos;
+ curY = screenObj->yPos;
- water = 0;
+ for (celX = 0; celX < screenObj->xSize; celX++, curX++) {
+ screenPriority = _gfx->getPriority(curX, curY);
- if (pri == 1) { // conditional blue
- if (v->flags & fIgnoreBlocks)
- continue;
+ if (screenPriority == 0) { // unconditional black. no go at all!
+ touchedControl = 0;
+ break;
+ }
- debugC(4, kDebugLevelSprites, "Blocks observed!");
- pass = 0;
- break;
+ if (screenPriority != 3) { // not water surface
+ touchedWater = false;
+
+ if (screenPriority == 1) { // conditional blue
+ if (!(screenObj->flags & fIgnoreBlocks)) {
+ debugC(4, kDebugLevelSprites, "Blocks observed!");
+ touchedControl = false;
+ break;
+ }
+ } else if (screenPriority == 2) {
+ debugC(4, kDebugLevelSprites, "stepped on trigger");
+ if (!_debug.ignoretriggers)
+ touchedTrigger = true;
+ }
+ }
}
- if (pri == 2) { // trigger
- debugC(4, kDebugLevelSprites, "stepped on trigger");
- if (!_debug.ignoretriggers)
- trigger = 1;
+ if (touchedControl) {
+ if (!touchedWater) {
+ if (screenObj->flags & fOnWater)
+ touchedControl = false;
+ } else {
+ if (screenObj->flags & fOnLand)
+ touchedControl = false;
+ }
}
}
- if (pass) {
- if (!water && v->flags & fOnWater)
- pass = 0;
- if (water && v->flags & fOnLand)
- pass = 0;
- }
-
// Check ego
- if (v->entry == 0) {
- setflag(fEgoTouchedP2, trigger ? true : false);
- setflag(fEgoWater, water ? true : false);
+ if (screenObj->objectNr == 0) {
+ setflag(VM_FLAG_EGO_TOUCHED_P2, touchedTrigger ? true : false);
+ setflag(VM_FLAG_EGO_WATER, touchedWater ? true : false);
}
- return pass;
+ return touchedControl;
}
/*
@@ -188,91 +185,106 @@ int AgiEngine::checkPriority(VtEntry *v) {
* rules, otherwise the previous position will be kept.
*/
void AgiEngine::updatePosition() {
- VtEntry *v;
+ ScreenObjEntry *screenObj;
int x, y, oldX, oldY, border;
- _game.vars[vBorderCode] = 0;
- _game.vars[vBorderTouchEgo] = 0;
- _game.vars[vBorderTouchObj] = 0;
+ _game.vars[VM_VAR_BORDER_CODE] = 0;
+ _game.vars[VM_VAR_BORDER_TOUCH_EGO] = 0;
+ _game.vars[VM_VAR_BORDER_TOUCH_OBJECT] = 0;
- for (v = _game.viewTable; v < &_game.viewTable[MAX_VIEWTABLE]; v++) {
- if ((v->flags & (fAnimated | fUpdate | fDrawn)) != (fAnimated | fUpdate | fDrawn)) {
+ for (screenObj = _game.screenObjTable; screenObj < &_game.screenObjTable[SCREENOBJECTS_MAX]; screenObj++) {
+ if ((screenObj->flags & (fAnimated | fUpdate | fDrawn)) != (fAnimated | fUpdate | fDrawn)) {
continue;
}
- if (v->stepTimeCount != 0) {
- if (--v->stepTimeCount != 0)
- continue;
+ if (screenObj->stepTimeCount > 1) {
+ screenObj->stepTimeCount--;
+ continue;
}
- v->stepTimeCount = v->stepTime;
+ screenObj->stepTimeCount = screenObj->stepTime;
- x = oldX = v->xPos;
- y = oldY = v->yPos;
+ x = oldX = screenObj->xPos;
+ y = oldY = screenObj->yPos;
// If object has moved, update its position
- if (~v->flags & fUpdatePos) {
+ if (!(screenObj->flags & fUpdatePos)) {
int dx[9] = { 0, 0, 1, 1, 1, 0, -1, -1, -1 };
int dy[9] = { 0, -1, -1, 0, 1, 1, 1, 0, -1 };
- x += v->stepSize * dx[v->direction];
- y += v->stepSize * dy[v->direction];
+ x += screenObj->stepSize * dx[screenObj->direction];
+ y += screenObj->stepSize * dy[screenObj->direction];
}
// Now check if it touched the borders
border = 0;
// Check left/right borders
- if (x < 0) {
- x = 0;
- border = 4;
- } else if (x <= 0 && getVersion() == 0x3086) { // KQ4
- x = 0; // See Sarien bug #590462
- border = 4;
- } else if (v->entry == 0 && x == 0 && v->flags & fAdjEgoXY) {
- // Extra test to walk west clicking the mouse
- x = 0;
- border = 4;
- } else if (x + v->xSize > _WIDTH) {
- x = _WIDTH - v->xSize;
- border = 2;
+ if (getVersion() == 0x3086) {
+ // KQ4 interpreter does a different comparison on x
+ // The interpreter before (2.917) and after that (3.098) don't do them that way
+ // This difference is required for at least Sarien bug #192
+ // KQ4: room 135, hen moves from the center of the screen to the left border,
+ // but it doesn't disappear.
+ if (x <= 0) {
+ x = 0;
+ border = 4;
+ }
+ } else {
+ // regular comparison
+ if (x < 0) {
+ x = 0;
+ border = 4;
+ }
+ }
+
+// } else if (v->entry == 0 && x == 0 && v->flags & fAdjEgoXY) { // should not be required
+// // Extra test to walk west clicking the mouse
+// x = 0;
+// border = 4;
+
+ if (!border) {
+ if (x + screenObj->xSize > SCRIPT_WIDTH) {
+ x = SCRIPT_WIDTH - screenObj->xSize;
+ border = 2;
+ }
}
// Check top/bottom borders.
- if (y - v->ySize + 1 < 0) {
- y = v->ySize - 1;
+ if (y - screenObj->ySize < -1) {
+ y = screenObj->ySize - 1;
border = 1;
- } else if (y > _HEIGHT - 1) {
- y = _HEIGHT - 1;
+ } else if (y > SCRIPT_HEIGHT - 1) {
+ y = SCRIPT_HEIGHT - 1;
border = 3;
- } else if ((~v->flags & fIgnoreHorizon) && y <= _game.horizon) {
+ } else if ((!(screenObj->flags & fIgnoreHorizon)) && y <= _game.horizon) {
debugC(4, kDebugLevelSprites, "y = %d, horizon = %d", y, _game.horizon);
y = _game.horizon + 1;
border = 1;
}
// Test new position. rollback if test fails
- v->xPos = x;
- v->yPos = y;
- if (checkCollision(v) || !checkPriority(v)) {
- v->xPos = oldX;
- v->yPos = oldY;
+ screenObj->xPos = x;
+ screenObj->yPos = y;
+ if (checkCollision(screenObj) || !checkPriority(screenObj)) {
+ screenObj->xPos = oldX;
+ screenObj->yPos = oldY;
border = 0;
- fixPosition(v->entry);
+ fixPosition(screenObj->objectNr);
}
- if (border != 0) {
- if (isEgoView(v)) {
- _game.vars[vBorderTouchEgo] = border;
+ if (border) {
+ if (isEgoView(screenObj)) {
+ _game.vars[VM_VAR_BORDER_TOUCH_EGO] = border;
} else {
- _game.vars[vBorderCode] = v->entry;
- _game.vars[vBorderTouchObj] = border;
+ _game.vars[VM_VAR_BORDER_CODE] = screenObj->objectNr;
+ _game.vars[VM_VAR_BORDER_TOUCH_OBJECT] = border;
}
- if (v->motion == kMotionMoveObj) {
- inDestination(v);
+ if (screenObj->motionType == kMotionMoveObj) { // ANGEPASST
+ motionMoveObjStop(screenObj);
}
}
- v->flags &= ~fUpdatePos;
+ screenObj->flags &= ~fUpdatePos;
}
}
@@ -285,42 +297,46 @@ void AgiEngine::updatePosition() {
*
* @param n view table entry number
*/
-void AgiEngine::fixPosition(int n) {
- VtEntry *v = &_game.viewTable[n];
+void AgiEngine::fixPosition(int16 screenObjNr) {
+ ScreenObjEntry *screenObj = &_game.screenObjTable[screenObjNr];
+ fixPosition(screenObj);
+}
+
+void AgiEngine::fixPosition(ScreenObjEntry *screenObj) {
int count, dir, size;
- debugC(4, kDebugLevelSprites, "adjusting view table entry #%d (%d,%d)", n, v->xPos, v->yPos);
+ debugC(4, kDebugLevelSprites, "adjusting view table entry #%d (%d,%d)", screenObj->objectNr, screenObj->xPos, screenObj->yPos);
// test horizon
- if ((~v->flags & fIgnoreHorizon) && v->yPos <= _game.horizon)
- v->yPos = _game.horizon + 1;
+ if ((!(screenObj->flags & fIgnoreHorizon)) && screenObj->yPos <= _game.horizon)
+ screenObj->yPos = _game.horizon + 1;
dir = 0;
count = size = 1;
- while (!checkPosition(v) || checkCollision(v) || !checkPriority(v)) {
+ while (!checkPosition(screenObj) || checkCollision(screenObj) || !checkPriority(screenObj)) {
switch (dir) {
case 0: // west
- v->xPos--;
+ screenObj->xPos--;
if (--count)
continue;
dir = 1;
break;
case 1: // south
- v->yPos++;
+ screenObj->yPos++;
if (--count)
continue;
dir = 2;
size++;
break;
case 2: // east
- v->xPos++;
+ screenObj->xPos++;
if (--count)
continue;
dir = 3;
break;
case 3: // north
- v->yPos--;
+ screenObj->yPos--;
if (--count)
continue;
dir = 0;
@@ -331,7 +347,7 @@ void AgiEngine::fixPosition(int n) {
count = size;
}
- debugC(4, kDebugLevelSprites, "view table entry #%d position adjusted to (%d,%d)", n, v->xPos, v->yPos);
+ debugC(4, kDebugLevelSprites, "view table entry #%d position adjusted to (%d,%d)", screenObj->objectNr, screenObj->xPos, screenObj->yPos);
}
} // End of namespace Agi
diff --git a/engines/agi/console.cpp b/engines/agi/console.cpp
index 89838b3be9..d8ecb2a0d6 100644
--- a/engines/agi/console.cpp
+++ b/engines/agi/console.cpp
@@ -22,6 +22,7 @@
#include "agi/agi.h"
#include "agi/opcodes.h"
+#include "agi/graphics.h"
#include "agi/preagi.h"
#include "agi/preagi_mickey.h"
@@ -49,6 +50,10 @@ Console::Console(AgiEngine *vm) : GUI::Debugger() {
registerCmd("setobj", WRAP_METHOD(Console, Cmd_SetObj));
registerCmd("room", WRAP_METHOD(Console, Cmd_Room));
registerCmd("bt", WRAP_METHOD(Console, Cmd_BT));
+ registerCmd("show_map", WRAP_METHOD(Console, Cmd_ShowMap));
+ registerCmd("screenobj", WRAP_METHOD(Console, Cmd_ScreenObj));
+ registerCmd("vmvars", WRAP_METHOD(Console, Cmd_VmVars));
+ registerCmd("vmflags", WRAP_METHOD(Console, Cmd_VmFlags));
}
bool Console::Cmd_SetVar(int argc, const char **argv) {
@@ -58,7 +63,7 @@ bool Console::Cmd_SetVar(int argc, const char **argv) {
}
int p1 = (int)atoi(argv[1]);
int p2 = (int)atoi(argv[2]);
- _vm->setvar(p1, p2);
+ _vm->setVar(p1, p2);
return true;
}
@@ -158,13 +163,13 @@ bool Console::Cmd_Version(int argc, const char **argv) {
// We do this by scanning through all script texts
// This is the best we can do about it. There is no special location for the game version number.
// There are multiple variations, like "ver. X.XX", "ver X.XX" and even "verion X.XX".
- for (scriptNr = 0; scriptNr < MAX_DIRS; scriptNr++) {
+ for (scriptNr = 0; scriptNr < MAX_DIRECTORY_ENTRIES; scriptNr++) {
if (game->dirLogic[scriptNr].offset != _EMPTY) {
// Script is supposed to exist?
scriptLoadedByUs = false;
if (!(game->dirLogic[scriptNr].flags & RES_LOADED)) {
// But not currently loaded? -> load it now
- if (_vm->agiLoadResource(rLOGIC, scriptNr) != errOK) {
+ if (_vm->agiLoadResource(RESOURCETYPE_LOGIC, scriptNr) != errOK) {
// In case we can't load the source, skip it
continue;
}
@@ -263,7 +268,7 @@ bool Console::Cmd_Version(int argc, const char **argv) {
}
if (scriptLoadedByUs) {
- _vm->agiUnloadResource(rLOGIC, scriptNr);
+ _vm->agiUnloadResource(RESOURCETYPE_LOGIC, scriptNr);
}
}
}
@@ -298,7 +303,7 @@ bool Console::Cmd_Vars(int argc, const char **argv) {
for (i = 0; i < 255;) {
for (j = 0; j < 5; j++, i++) {
- debugPrintf("%03d:%3d ", i, _vm->getvar(i));
+ debugPrintf("%03d:%3d ", i, _vm->getVar(i));
}
debugPrintf("\n");
}
@@ -380,7 +385,7 @@ bool Console::Cmd_Room(int argc, const char **argv) {
_vm->newRoom(strtoul(argv[1], NULL, 0));
}
- debugPrintf("Current room: %d\n", _vm->getvar(0));
+ debugPrintf("Current room: %d\n", _vm->getVar(0));
return true;
}
@@ -412,6 +417,182 @@ bool Console::Cmd_BT(int argc, const char **argv) {
return true;
}
+bool Console::Cmd_ShowMap(int argc, const char **argv) {
+ if (argc != 2) {
+ debugPrintf("Switches to one of the following screen maps\n");
+ debugPrintf("Usage: %s <screen map>\n", argv[0]);
+ debugPrintf("Screen maps:\n");
+ debugPrintf("- 0: visual map\n");
+ debugPrintf("- 1: priority map\n");
+ return true;
+ }
+
+ int map = atoi(argv[1]);
+
+ switch (map) {
+ case 0:
+ case 1:
+ _vm->_gfx->debugShowMap(map);
+ break;
+
+ default:
+ debugPrintf("Map %d is not available.\n", map);
+ return true;
+ }
+ return cmdExit(0, 0);
+}
+
+bool Console::Cmd_ScreenObj(int argc, const char **argv) {
+ if (argc != 2) {
+ debugPrintf("Shows information about a specific screen object\n");
+ debugPrintf("Usage: %s <screenobj number>\n", argv[0]);
+ return true;
+ }
+
+ int16 screenObjNr = atoi(argv[1]);
+
+ if ((screenObjNr >= 0) && (screenObjNr < SCREENOBJECTS_MAX)) {
+ ScreenObjEntry *screenObj = &_vm->_game.screenObjTable[screenObjNr];
+
+ debugPrintf("Screen Object ID %d\n", screenObj->objectNr);
+ debugPrintf("current view: %d, loop: %d, cel: %d\n", screenObj->currentViewNr, screenObj->currentLoopNr, screenObj->currentCelNr);
+ debugPrintf("flags: %x\n", screenObj->flags);
+ debugPrintf("\n");
+ debugPrintf("xPos: %d, yPos: %d, xSize: %d, ySize: %d\n", screenObj->xPos, screenObj->yPos, screenObj->xSize, screenObj->ySize);
+ debugPrintf("previous: xPos: %d, yPos: %d, xSize: %d, ySize: %d\n", screenObj->xPos_prev, screenObj->yPos_prev, screenObj->xSize_prev, screenObj->ySize_prev);
+ debugPrintf("direction: %d, priority: %d\n", screenObj->direction, screenObj->priority);
+ debugPrintf("stepTime: %d, timeCount: %d, size: %d\n", screenObj->stepTime, screenObj->stepTimeCount, screenObj->stepSize);
+ debugPrintf("cycleTime: %d, timeCount: %d\n", screenObj->cycleTime, screenObj->cycleTimeCount);
+
+ switch(screenObj->motionType) {
+ case kMotionNormal:
+ debugPrintf("motion: normal\n");
+ break;
+ case kMotionWander:
+ debugPrintf("motion: wander\n");
+ debugPrintf("wanderCount: %d\n", screenObj->wander_count);
+ break;
+ case kMotionFollowEgo:
+ debugPrintf("motion: follow ego\n");
+ debugPrintf("stepSize: %d, flag: %x, count: %d", screenObj->follow_stepSize, screenObj->follow_flag, screenObj->follow_count);
+ break;
+ case kMotionMoveObj:
+ case kMotionEgo:
+ if (screenObj->motionType == kMotionMoveObj) {
+ debugPrintf("motion: move obj\n");
+ } else {
+ debugPrintf("motion: ego\n");
+ }
+ debugPrintf("x: %d, y: %d, stepSize: %d, flag: %x\n", screenObj->move_x, screenObj->move_y, screenObj->move_stepSize, screenObj->move_flag);
+ break;
+ }
+ }
+#if 0
+ CycleType cycle;
+#endif
+ return true;
+}
+
+bool Console::Cmd_VmVars(int argc, const char **argv) {
+ if (argc < 2) {
+ debugPrintf("Shows the content of a VM variable / sets it\n");
+ debugPrintf("Usage: %s <variable number> [<value>]\n", argv[0]);
+ return true;
+ }
+
+ int varNr = 0;
+ int newValue = 0;
+
+ if (!parseInteger(argv[1], varNr))
+ return true;
+
+ if ((varNr < 0) || (varNr > 255)) {
+ debugPrintf("invalid variable number\n");
+ return true;
+ }
+
+ if (argc < 3) {
+ // show contents
+ debugPrintf("variable %d == %d\n", varNr, _vm->getVar(varNr));
+ } else {
+ if (!parseInteger(argv[2], newValue))
+ return true;
+
+ _vm->setVar(varNr, newValue);
+
+ debugPrintf("value set.\n");
+ }
+ return true;
+}
+
+bool Console::Cmd_VmFlags(int argc, const char **argv) {
+ if (argc < 2) {
+ debugPrintf("Shows the content of a VM flag / sets it\n");
+ debugPrintf("Usage: %s <flag number> [<value>]\n", argv[0]);
+ return true;
+ }
+
+ int flagNr = 0;
+ int newFlagState = 0;
+
+ if (!parseInteger(argv[1], flagNr))
+ return true;
+
+ if ((flagNr < 0) || (flagNr > 255)) {
+ debugPrintf("invalid flag number\n");
+ return true;
+ }
+
+ if (argc < 3) {
+ // show contents
+ if (_vm->getflag(flagNr)) {
+ debugPrintf("flag %d == set\n", flagNr);
+ } else {
+ debugPrintf("flag %d == not set\n", flagNr);
+ }
+ } else {
+ if (!parseInteger(argv[2], newFlagState))
+ return true;
+
+ if ((newFlagState != 0) && (newFlagState != 1)) {
+ debugPrintf("new state must bei either 0 or 1\n");
+ return true;
+ }
+
+ if (!newFlagState) {
+ _vm->setflag(flagNr, 0);
+ debugPrintf("flag %d reset.\n", flagNr);
+ } else {
+ _vm->setflag(flagNr, 1);
+ debugPrintf("flag %d set.\n", flagNr);
+ }
+ }
+ return true;
+}
+
+bool Console::parseInteger(const char *argument, int &result) {
+ char *endPtr = 0;
+ int idxLen = strlen(argument);
+ const char *lastChar = argument + idxLen - (idxLen == 0 ? 0 : 1);
+
+ if ((strncmp(argument, "0x", 2) == 0) || (*lastChar == 'h')) {
+ // hexadecimal number
+ result = strtol(argument, &endPtr, 16);
+ if ((*endPtr != 0) && (*endPtr != 'h')) {
+ debugPrintf("Invalid hexadecimal number '%s'\n", argument);
+ return false;
+ }
+ } else {
+ // decimal number
+ result = strtol(argument, &endPtr, 10);
+ if (*endPtr != 0) {
+ debugPrintf("Invalid decimal number '%s'\n", argument);
+ return false;
+ }
+ }
+ return true;
+}
+
MickeyConsole::MickeyConsole(MickeyEngine *mickey) : GUI::Debugger() {
_mickey = mickey;
diff --git a/engines/agi/console.h b/engines/agi/console.h
index c650e143a0..41dc9ddabc 100644
--- a/engines/agi/console.h
+++ b/engines/agi/console.h
@@ -62,6 +62,12 @@ private:
bool Cmd_Cont(int argc, const char **argv);
bool Cmd_Room(int argc, const char **argv);
bool Cmd_BT(int argc, const char **argv);
+ bool Cmd_ShowMap(int argc, const char **argv);
+ bool Cmd_ScreenObj(int argc, const char **argv);
+ bool Cmd_VmVars(int argc, const char **argv);
+ bool Cmd_VmFlags(int argc, const char **argv);
+
+ bool parseInteger(const char *argument, int &result);
private:
AgiEngine *_vm;
diff --git a/engines/agi/cycle.cpp b/engines/agi/cycle.cpp
index 145b827160..98f29ec0e1 100644
--- a/engines/agi/cycle.cpp
+++ b/engines/agi/cycle.cpp
@@ -20,11 +20,16 @@
*
*/
+#include "common/config-manager.h"
+
#include "agi/agi.h"
#include "agi/sprite.h"
#include "agi/graphics.h"
+#include "agi/inv.h"
+#include "agi/text.h"
#include "agi/keyboard.h"
#include "agi/menu.h"
+#include "agi/systemui.h"
namespace Agi {
@@ -33,124 +38,133 @@ namespace Agi {
* This function is called when ego enters a new room.
* @param n room number
*/
-void AgiEngine::newRoom(int n) {
- VtEntry *v;
+void AgiEngine::newRoom(int16 newRoomNr) {
+ ScreenObjEntry *screenObj;
+ ScreenObjEntry *screenObjEgo = &_game.screenObjTable[SCREENOBJECTS_EGO_ENTRY];
int i;
- // Simulate slowww computer.
- // Many effects rely on it.
- pause(kPauseRoom);
+ // Loading trigger
+ loadingTrigger_NewRoom(newRoomNr);
- debugC(4, kDebugLevelMain, "*** room %d ***", n);
+ debugC(4, kDebugLevelMain, "*** room %d ***", newRoomNr);
_sound->stopSound();
i = 0;
- for (v = _game.viewTable; v < &_game.viewTable[MAX_VIEWTABLE]; v++) {
- v->entry = i++;
- v->flags &= ~(fAnimated | fDrawn);
- v->flags |= fUpdate;
- v->stepTime = 1;
- v->stepTimeCount = 1;
- v->cycleTime = 1;
- v->cycleTimeCount = 1;
- v->stepSize = 1;
+ for (screenObj = _game.screenObjTable; screenObj < &_game.screenObjTable[SCREENOBJECTS_MAX]; screenObj++) {
+ screenObj->objectNr = i++;
+ screenObj->flags &= ~(fAnimated | fDrawn);
+ screenObj->flags |= fUpdate;
+ screenObj->stepTime = 1;
+ screenObj->stepTimeCount = 1;
+ screenObj->cycleTime = 1;
+ screenObj->cycleTimeCount = 1;
+ screenObj->stepSize = 1;
}
agiUnloadResources();
_game.playerControl = true;
_game.block.active = false;
_game.horizon = 36;
- _game.vars[vPrevRoom] = _game.vars[vCurRoom];
- _game.vars[vCurRoom] = n;
- _game.vars[vBorderTouchObj] = 0;
- _game.vars[vBorderCode] = 0;
- _game.vars[vEgoViewResource] = _game.viewTable[0].currentView;
+ _game.vars[VM_VAR_PREVIOUS_ROOM] = _game.vars[VM_VAR_CURRENT_ROOM];
+ _game.vars[VM_VAR_CURRENT_ROOM] = newRoomNr;
+ _game.vars[VM_VAR_BORDER_TOUCH_OBJECT] = 0;
+ _game.vars[VM_VAR_BORDER_CODE] = 0;
+ _game.vars[VM_VAR_EGO_VIEW_RESOURCE] = screenObjEgo->currentViewNr;
- agiLoadResource(rLOGIC, n);
+ agiLoadResource(RESOURCETYPE_LOGIC, newRoomNr);
// Reposition ego in the new room
- switch (_game.vars[vBorderTouchEgo]) {
+ switch (_game.vars[VM_VAR_BORDER_TOUCH_EGO]) {
case 1:
- _game.viewTable[0].yPos = _HEIGHT - 1;
+ screenObjEgo->yPos = SCRIPT_HEIGHT - 1;
break;
case 2:
- _game.viewTable[0].xPos = 0;
+ screenObjEgo->xPos = 0;
break;
case 3:
- _game.viewTable[0].yPos = HORIZON + 1;
+ screenObjEgo->yPos = _game.horizon + 1;
break;
case 4:
- _game.viewTable[0].xPos = _WIDTH - _game.viewTable[0].xSize;
+ screenObjEgo->xPos = SCRIPT_WIDTH - screenObjEgo->xSize;
break;
}
if (getVersion() < 0x2000) {
- warning("STUB: NewRoom(%d)", n);
+ warning("STUB: NewRoom(%d)", newRoomNr);
- v->flags &= ~fDidntMove;
+ screenObjEgo->flags &= ~fDidntMove;
// animateObject(0);
- agiLoadResource(rVIEW, _game.viewTable[0].currentView);
- setView(&_game.viewTable[0], _game.viewTable[0].currentView);
+ agiLoadResource(RESOURCETYPE_VIEW, screenObjEgo->currentViewNr);
+ setView(screenObjEgo, screenObjEgo->currentViewNr);
} else {
- _game.vars[vBorderTouchEgo] = 0;
- setflag(fNewRoomExec, true);
+ if (screenObjEgo->motionType == kMotionEgo) {
+ screenObjEgo->motionType = kMotionNormal;
+ _game.vars[VM_VAR_EGO_DIRECTION] = 0;
+ }
+
+ _game.vars[VM_VAR_BORDER_TOUCH_EGO] = 0;
+ setflag(VM_FLAG_NEW_ROOM_EXEC, true);
_game.exitAllLogics = true;
- writeStatus();
- writePrompt();
+ _game._vm->_text->statusDraw();
+ _game._vm->_text->promptRedraw();
}
}
void AgiEngine::resetControllers() {
int i;
- for (i = 0; i < MAX_DIRS; i++) {
+ for (i = 0; i < MAX_CONTROLLERS; i++) {
_game.controllerOccured[i] = false;
}
}
void AgiEngine::interpretCycle() {
+ ScreenObjEntry *screenObjEgo = &_game.screenObjTable[SCREENOBJECTS_EGO_ENTRY];
int oldSound, oldScore;
if (_game.playerControl)
- _game.vars[vEgoDir] = _game.viewTable[0].direction;
+ _game.vars[VM_VAR_EGO_DIRECTION] = screenObjEgo->direction;
else
- _game.viewTable[0].direction = _game.vars[vEgoDir];
+ screenObjEgo->direction = _game.vars[VM_VAR_EGO_DIRECTION];
checkAllMotions();
- oldScore = _game.vars[vScore];
- oldSound = getflag(fSoundOn);
+ oldScore = _game.vars[VM_VAR_SCORE];
+ oldSound = getflag(VM_FLAG_SOUND_ON);
_game.exitAllLogics = false;
while (runLogic(0) == 0 && !(shouldQuit() || _restartGame)) {
- _game.vars[vWordNotFound] = 0;
- _game.vars[vBorderTouchObj] = 0;
- _game.vars[vBorderCode] = 0;
- oldScore = _game.vars[vScore];
- setflag(fEnteredCli, false);
+ _game.vars[VM_VAR_WORD_NOT_FOUND] = 0;
+ _game.vars[VM_VAR_BORDER_TOUCH_OBJECT] = 0;
+ _game.vars[VM_VAR_BORDER_CODE] = 0;
+ oldScore = _game.vars[VM_VAR_SCORE];
+ setflag(VM_FLAG_ENTERED_CLI, false);
_game.exitAllLogics = false;
+ nonBlockingText_CycleDone();
resetControllers();
}
+ nonBlockingText_CycleDone();
resetControllers();
- _game.viewTable[0].direction = _game.vars[vEgoDir];
+ screenObjEgo->direction = _game.vars[VM_VAR_EGO_DIRECTION];
- if (_game.vars[vScore] != oldScore || getflag(fSoundOn) != oldSound)
- writeStatus();
+ if (_game.vars[VM_VAR_SCORE] != oldScore || getflag(VM_FLAG_SOUND_ON) != oldSound)
+ _game._vm->_text->statusDraw();
- _game.vars[vBorderTouchObj] = 0;
- _game.vars[vBorderCode] = 0;
- setflag(fNewRoomExec, false);
- setflag(fRestartGame, false);
- setflag(fRestoreJustRan, false);
+ _game.vars[VM_VAR_BORDER_TOUCH_OBJECT] = 0;
+ _game.vars[VM_VAR_BORDER_CODE] = 0;
+ setflag(VM_FLAG_NEW_ROOM_EXEC, false);
+ setflag(VM_FLAG_RESTART_GAME, false);
+ setflag(VM_FLAG_RESTORE_JUST_RAN, false);
if (_game.gfxMode) {
- updateViewtable();
- _gfx->doUpdate();
+ updateScreenObjTable();
}
+ _gfx->updateScreen();
+ //_gfx->doUpdate();
}
/**
@@ -166,27 +180,27 @@ void AgiEngine::updateTimer() {
if (!_game.clockEnabled)
return;
- setvar(vSeconds, getvar(vSeconds) + 1);
- if (getvar(vSeconds) < 60)
+ setVar(VM_VAR_SECONDS, getVar(VM_VAR_SECONDS) + 1);
+ if (getVar(VM_VAR_SECONDS) < 60)
return;
- setvar(vSeconds, 0);
- setvar(vMinutes, getvar(vMinutes) + 1);
- if (getvar(vMinutes) < 60)
+ setVar(VM_VAR_SECONDS, 0);
+ setVar(VM_VAR_MINUTES, getVar(VM_VAR_MINUTES) + 1);
+ if (getVar(VM_VAR_MINUTES) < 60)
return;
- setvar(vMinutes, 0);
- setvar(vHours, getvar(vHours) + 1);
- if (getvar(vHours) < 24)
+ setVar(VM_VAR_MINUTES, 0);
+ setVar(VM_VAR_HOURS, getVar(VM_VAR_HOURS) + 1);
+ if (getVar(VM_VAR_HOURS) < 24)
return;
- setvar(vHours, 0);
- setvar(vDays, getvar(vDays) + 1);
+ setVar(VM_VAR_HOURS, 0);
+ setVar(VM_VAR_DAYS, getVar(VM_VAR_DAYS) + 1);
}
void AgiEngine::newInputMode(InputMode mode) {
- if (mode == INPUT_MENU && !getflag(fMenusWork) && !(getFeatures() & GF_MENUS))
- return;
+ //if (mode == INPUTMODE_MENU && !getflag(VM_FLAG_MENUS_WORK) && !(getFeatures() & GF_MENUS))
+ // return;
_oldMode = _game.inputMode;
_game.inputMode = mode;
@@ -198,14 +212,19 @@ void AgiEngine::oldInputMode() {
// If main_cycle returns false, don't process more events!
int AgiEngine::mainCycle(bool onlyCheckForEvents) {
- unsigned int key, kascii;
- VtEntry *v = &_game.viewTable[0];
+ uint16 key;
+ byte keyAscii;
+ ScreenObjEntry *screenObjEgo = &_game.screenObjTable[SCREENOBJECTS_EGO_ENTRY];
if (!onlyCheckForEvents) {
pollTimer();
updateTimer();
}
+ if (_menu->delayedExecuteActive()) {
+ _menu->execute();
+ }
+
key = doPollKeyboard();
// In AGI Mouse emulation mode we must update the mouse-related
@@ -217,98 +236,106 @@ int AgiEngine::mainCycle(bool onlyCheckForEvents) {
_game.vars[29] = _mouse.y;
//}
- if (key == KEY_STATUSLN) { // F11
- _debug.statusline = !_debug.statusline;
- writeStatus();
- key = 0;
- }
-
- if (key == KEY_PRIORITY) { // F12
- _sprites->eraseBoth();
- _debug.priority = !_debug.priority;
- _picture->showPic();
- _sprites->blitBoth();
- _sprites->commitBoth();
- key = 0;
+ switch (_game.inputMode) {
+ case INPUTMODE_NORMAL:
+ case INPUTMODE_NONE:
+ // Click-to-walk mouse interface
+ if (_game.playerControl && (screenObjEgo->flags & fAdjEgoXY)) {
+ int toX = screenObjEgo->move_x;
+ int toY = screenObjEgo->move_y;
+
+ // AGI Mouse games use ego's sprite's bottom left corner for mouse walking target.
+ // Amiga games use ego's sprite's bottom center for mouse walking target.
+ // Atari ST and Apple II GS seem to use the bottom left
+ if (getPlatform() == Common::kPlatformAmiga)
+ toX -= (screenObjEgo->xSize / 2); // Center ego's sprite horizontally
+
+ // Adjust ego's sprite's mouse walking target position (These parameters are
+ // controlled with the adj.ego.move.to.x.y-command). Note that these values rely
+ // on the horizontal centering of the ego's sprite at least on the Amiga platform.
+ toX += _game.adjMouseX;
+ toY += _game.adjMouseY;
+
+ screenObjEgo->direction = getDirection(screenObjEgo->xPos, screenObjEgo->yPos, toX, toY, screenObjEgo->stepSize);
+
+ if (screenObjEgo->direction == 0)
+ inDestination(screenObjEgo);
+ }
+ break;
+ default:
+ break;
}
- // Click-to-walk mouse interface
- if (_game.playerControl && (v->flags & fAdjEgoXY)) {
- int toX = v->parm1;
- int toY = v->parm2;
-
- // AGI Mouse games use ego's sprite's bottom left corner for mouse walking target.
- // Amiga games use ego's sprite's bottom center for mouse walking target.
- // TODO: Check what Atari ST AGI and Apple IIGS AGI use for mouse walking target.
- if (getPlatform() == Common::kPlatformAmiga)
- toX -= (v->xSize / 2); // Center ego's sprite horizontally
-
- // Adjust ego's sprite's mouse walking target position (These parameters are
- // controlled with the adj.ego.move.to.x.y-command). Note that these values rely
- // on the horizontal centering of the ego's sprite at least on the Amiga platform.
- toX += _game.adjMouseX;
- toY += _game.adjMouseY;
-
- v->direction = getDirection(v->xPos, v->yPos, toX, toY, v->stepSize);
-
- if (v->direction == 0)
- inDestination(v);
+ keyAscii = key & 0xFF;
+ if (keyAscii) {
+ setVar(VM_VAR_KEY, keyAscii);
}
- kascii = KEY_ASCII(key);
-
- if (kascii)
- setvar(vKey, kascii);
-
- bool restartProcessKey;
- do {
- restartProcessKey = false;
-
+ if (!cycleInnerLoopIsActive()) {
+ // no inner loop active at the moment, regular processing
switch (_game.inputMode) {
- case INPUT_NORMAL:
+ case INPUTMODE_NORMAL:
if (!handleController(key)) {
- if (key == 0 || !_game.inputEnabled)
+ if (key == 0 || (!_text->promptIsEnabled()))
break;
- handleKeys(key);
-
- // if ESC pressed, activate menu before
- // accept.input from the interpreter cycle
- // sets the input mode to normal again
- // (closes: #540856)
- if (key == KEY_ESCAPE) {
- key = 0;
- restartProcessKey = true;
- }
-
- // commented out to close Sarien bug #438872
- //if (key)
- // _game.keypress = key;
+
+ _text->promptCharPress(key);
}
break;
- case INPUT_GETSTRING:
- handleController(key);
- handleGetstring(key);
- setvar(vKey, 0); // clear ENTER key
- break;
- case INPUT_MENU:
- _menu->keyhandler(key);
- _gfx->doUpdate();
- return false;
- case INPUT_NONE:
+ case INPUTMODE_NONE:
handleController(key);
if (key)
_game.keypress = key;
break;
+ default:
+ break;
}
- } while (restartProcessKey);
- if (!onlyCheckForEvents) {
- _gfx->doUpdate();
+ } else {
+ // inner loop active
+ // call specific workers
+ setVar(VM_VAR_KEY, 0); // clear keys, they must not be passed to the scripts
+ _game.keypress = 0;
+
+ switch (_game.cycleInnerLoopType) {
+ case CYCLE_INNERLOOP_GETSTRING: // loop called from TextMgr::stringEdit()
+ case CYCLE_INNERLOOP_GETNUMBER:
+ //handleController(key);
+ if (key) {
+ _text->stringCharPress(key);
+ }
+ break;
+
+ case CYCLE_INNERLOOP_INVENTORY: // loop called from InventoryMgr::show()
+ if (key) {
+ _inventory->charPress(key);
+ }
+ break;
+
+ case CYCLE_INNERLOOP_MENU:
+ if (key) {
+ _menu->charPress(key);
+ }
+ return false;
+
+ case CYCLE_INNERLOOP_SYSTEMUI_SELECTSAVEDGAMESLOT:
+ if (key) {
+ _systemUI->savedGameSlot_CharPress(key);
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ if (!onlyCheckForEvents) {
if (_game.msgBoxTicks > 0)
_game.msgBoxTicks--;
}
+ _gfx->updateScreen();
+
return true;
}
@@ -319,19 +346,24 @@ int AgiEngine::playGame() {
debugC(2, kDebugLevelMain, "game version = 0x%x", getVersion());
_sound->stopSound();
- _gfx->clearScreen(0);
- _game.horizon = HORIZON;
+ // We need to do this accurately and reset the AGI priorityscreen to 4
+ // otherwise at least the fan game Nick's Quest will go into an endless
+ // loop, because the game draws views before it draws the first background picture.
+ // For further study see bug #3451122
+ _gfx->clear(0, 4);
+
+ _game.horizon = 36;
_game.playerControl = false;
- setflag(fLogicZeroFirsttime, true); // not in 2.917
- setflag(fNewRoomExec, true); // needed for MUMG and SQ2!
- setflag(fSoundOn, true); // enable sound
- setvar(vTimeDelay, 2); // "normal" speed
+ setflag(VM_FLAG_LOGIC_ZERO_FIRST_TIME, true); // not in 2.917
+ setflag(VM_FLAG_NEW_ROOM_EXEC, true); // needed for MUMG and SQ2!
+ setflag(VM_FLAG_SOUND_ON, true); // enable sound
+ setVar(VM_VAR_TIME_DELAY, 2); // "normal" speed
_game.gfxMode = true;
_game.clockEnabled = true;
- _game.lineUserInput = 22;
+ _text->promptRow_Set(22);
// We run AGIMOUSE always as a side effect
//if (getFeatures() & GF_AGIMOUSE)
@@ -342,25 +374,34 @@ int AgiEngine::playGame() {
debug(0, "Running AGI script.\n");
- setflag(fEnteredCli, false);
- setflag(fSaidAcceptedInput, false);
- _game.vars[vWordNotFound] = 0;
- _game.vars[vKey] = 0;
+ setflag(VM_FLAG_ENTERED_CLI, false);
+ setflag(VM_FLAG_SAID_ACCEPTED_INPUT, false);
+ _game.vars[VM_VAR_WORD_NOT_FOUND] = 0;
+ _game.vars[VM_VAR_KEY] = 0;
debugC(2, kDebugLevelMain, "Entering main loop");
- bool firstLoop = !getflag(fRestartGame); // Do not restore on game restart
+ bool firstLoop = !getflag(VM_FLAG_RESTART_GAME); // Do not restore on game restart
+
+ if (firstLoop) {
+ if (ConfMan.hasKey("save_slot")) {
+ // quick restore enabled
+ _game.automaticRestoreGame = true;
+ }
+ }
+
+ nonBlockingText_Forget();
do {
if (!mainCycle())
continue;
- if (getvar(vTimeDelay) == 0 || (1 + _clockCount) % getvar(vTimeDelay) == 0) {
- if (!_game.hasPrompt && _game.inputMode == INPUT_NORMAL) {
- writePrompt();
+ if (getVar(VM_VAR_TIME_DELAY) == 0 || (1 + _clockCount) % getVar(VM_VAR_TIME_DELAY) == 0) {
+ if (!_game.hasPrompt && _game.inputMode == INPUTMODE_NORMAL) {
+ _text->promptRedraw();
_game.hasPrompt = 1;
- } else if (_game.hasPrompt && _game.inputMode == INPUT_NONE) {
- writePrompt();
+ } else if (_game.hasPrompt && _game.inputMode == INPUTMODE_NONE) {
+ _text->promptRedraw();
_game.hasPrompt = 0;
}
@@ -368,15 +409,15 @@ int AgiEngine::playGame() {
// Check if the user has asked to load a game from the command line
// or the launcher
- if (firstLoop) {
+ if (_game.automaticRestoreGame) {
+ _game.automaticRestoreGame = false;
checkQuickLoad();
- firstLoop = false;
}
- setflag(fEnteredCli, false);
- setflag(fSaidAcceptedInput, false);
- _game.vars[vWordNotFound] = 0;
- _game.vars[vKey] = 0;
+ setflag(VM_FLAG_ENTERED_CLI, false);
+ setflag(VM_FLAG_SAID_ACCEPTED_INPUT, false);
+ _game.vars[VM_VAR_WORD_NOT_FOUND] = 0;
+ _game.vars[VM_VAR_KEY] = 0;
}
if (shouldPerformAutoSave(_lastSaveTime)) {
@@ -393,6 +434,23 @@ int AgiEngine::playGame() {
int AgiEngine::runGame() {
int ec = errOK;
+ // figure out the expected menu trigger for the current platform
+ // need to trigger the menu via mouse and via keyboard for platforms except PC
+ if (!(getFeatures() & GF_ESCPAUSE)) {
+ switch (getPlatform()) {
+ case Common::kPlatformAmiga:
+ case Common::kPlatformApple2GS:
+ _game.specialMenuTriggerKey = AGI_MENU_TRIGGER_APPLE2GS;
+ break;
+ case Common::kPlatformAtariST:
+ _game.specialMenuTriggerKey = AGI_MENU_TRIGGER_ATARIST;
+ break;
+ // Macintosh games seem to use ESC key just like PC versions do
+ default:
+ break;
+ }
+ }
+
// Execute the game
do {
debugC(2, kDebugLevelMain, "game loop");
@@ -402,61 +460,62 @@ int AgiEngine::runGame() {
break;
if (_restartGame) {
- setflag(fRestartGame, true);
- setvar(vTimeDelay, 2); // "normal" speed
+ setflag(VM_FLAG_RESTART_GAME, true);
+ setVar(VM_VAR_TIME_DELAY, 2); // "normal" speed
_restartGame = false;
}
// Set computer type (v20 i.e. vComputer) and sound type
switch (getPlatform()) {
case Common::kPlatformAtariST:
- setvar(vComputer, kAgiComputerAtariST);
- setvar(vSoundgen, kAgiSoundPC);
+ setVar(VM_VAR_COMPUTER, kAgiComputerAtariST);
+ setVar(VM_VAR_SOUNDGENERATOR, kAgiSoundPC);
break;
case Common::kPlatformAmiga:
if (getFeatures() & GF_OLDAMIGAV20)
- setvar(vComputer, kAgiComputerAmigaOld);
+ setVar(VM_VAR_COMPUTER, kAgiComputerAmigaOld);
else
- setvar(vComputer, kAgiComputerAmiga);
- setvar(vSoundgen, kAgiSoundTandy);
+ setVar(VM_VAR_COMPUTER, kAgiComputerAmiga);
+ setVar(VM_VAR_SOUNDGENERATOR, kAgiSoundTandy);
break;
case Common::kPlatformApple2GS:
- setvar(vComputer, kAgiComputerApple2GS);
+ setVar(VM_VAR_COMPUTER, kAgiComputerApple2GS);
if (getFeatures() & GF_2GSOLDSOUND)
- setvar(vSoundgen, kAgiSound2GSOld);
+ setVar(VM_VAR_SOUNDGENERATOR, kAgiSound2GSOld);
else
- setvar(vSoundgen, kAgiSoundTandy);
+ setVar(VM_VAR_SOUNDGENERATOR, kAgiSoundTandy);
break;
case Common::kPlatformDOS:
default:
- setvar(vComputer, kAgiComputerPC);
- setvar(vSoundgen, kAgiSoundPC);
+ setVar(VM_VAR_COMPUTER, kAgiComputerPC);
+ setVar(VM_VAR_SOUNDGENERATOR, kAgiSoundPC);
break;
}
// Set monitor type (v26 i.e. vMonitor)
switch (_renderMode) {
- case Common::kRenderCGA:
- setvar(vMonitor, kAgiMonitorCga);
+ case RENDERMODE_CGA:
+ setVar(VM_VAR_MONITOR, kAgiMonitorCga);
break;
- case Common::kRenderHercG:
- case Common::kRenderHercA:
- setvar(vMonitor, kAgiMonitorHercules);
+ case RENDERMODE_HERCULES:
+ setVar(VM_VAR_MONITOR, kAgiMonitorHercules);
break;
// Don't know if Amiga AGI games use a different value than kAgiMonitorEga
// for vMonitor so I just use kAgiMonitorEga for them (As was done before too).
- case Common::kRenderAmiga:
- case Common::kRenderDefault:
- case Common::kRenderEGA:
+ case RENDERMODE_AMIGA:
+ case RENDERMODE_APPLE_II_GS:
+ case RENDERMODE_ATARI_ST:
+ case RENDERMODE_EGA:
+ case RENDERMODE_VGA:
default:
- setvar(vMonitor, kAgiMonitorEga);
+ setVar(VM_VAR_MONITOR, kAgiMonitorEga);
break;
}
- setvar(vFreePages, 180); // Set amount of free memory to realistic value
- setvar(vMaxInputChars, 38);
- _game.inputMode = INPUT_NONE;
- _game.inputEnabled = false;
+ setVar(VM_VAR_FREE_PAGES, 180); // Set amount of free memory to realistic value
+ setVar(VM_VAR_MAX_INPUT_CHARACTERS, 38);
+ _game.inputMode = INPUTMODE_NONE;
+ _text->promptDisable();
_game.hasPrompt = 0;
_game.state = STATE_RUNNING;
diff --git a/engines/agi/detection.cpp b/engines/agi/detection.cpp
index 971b562aec..17358e0a40 100644
--- a/engines/agi/detection.cpp
+++ b/engines/agi/detection.cpp
@@ -257,7 +257,6 @@ SaveStateList AgiMetaEngine::listSaves(const char *target) const {
const uint32 AGIflag = MKTAG('A','G','I',':');
Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
Common::StringArray filenames;
- char saveDesc[31];
Common::String pattern = target;
pattern += ".###";
@@ -267,16 +266,35 @@ SaveStateList AgiMetaEngine::listSaves(const char *target) const {
SaveStateList saveList;
for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) {
// Obtain the last 3 digits of the filename, since they correspond to the save slot
- int slotNum = atoi(file->c_str() + file->size() - 3);
+ int slotNr = atoi(file->c_str() + file->size() - 3);
- if (slotNum >= 0 && slotNum <= 999) {
+ if (slotNr >= 0 && slotNr <= 999) {
Common::InSaveFile *in = saveFileMan->openForLoading(*file);
if (in) {
uint32 type = in->readUint32BE();
- if (type == AGIflag)
- in->read(saveDesc, 31);
- saveList.push_back(SaveStateDescriptor(slotNum, saveDesc));
+ char description[31];
+
+ if (type == AGIflag) {
+ uint16 descriptionPos = 0;
+
+ in->read(description, 31);
+
+ // Security-check, if saveDescription has a terminating NUL
+ while (description[descriptionPos]) {
+ descriptionPos++;
+ if (descriptionPos >= sizeof(description))
+ break;
+ }
+ if (descriptionPos >= sizeof(description)) {
+ strcpy(description, "[broken saved game]");
+ }
+ } else {
+ strcpy(description, "[not an AGI saved game]");
+ }
+
delete in;
+
+ saveList.push_back(SaveStateDescriptor(slotNr, description));
}
}
}
@@ -291,9 +309,9 @@ void AgiMetaEngine::removeSaveState(const char *target, int slot) const {
g_system->getSavefileManager()->removeSavefile(fileName);
}
-SaveStateDescriptor AgiMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
+SaveStateDescriptor AgiMetaEngine::querySaveMetaInfos(const char *target, int slotNr) const {
const uint32 AGIflag = MKTAG('A','G','I',':');
- Common::String fileName = Common::String::format("%s.%03d", target, slot);
+ Common::String fileName = Common::String::format("%s.%03d", target, slotNr);
Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(fileName);
@@ -303,49 +321,73 @@ SaveStateDescriptor AgiMetaEngine::querySaveMetaInfos(const char *target, int sl
return SaveStateDescriptor();
}
- char name[32];
- in->read(name, 31);
+ char description[31];
+ uint16 descriptionPos = 0;
+
+ in->read(description, 31);
+
+ while (description[descriptionPos]) {
+ descriptionPos++;
+ if (descriptionPos >= sizeof(description))
+ break;
+ }
+ if (descriptionPos >= sizeof(description)) {
+ // broken description, ignore it
+ delete in;
+
+ SaveStateDescriptor descriptor(slotNr, "[broken saved game]");
+ return descriptor;
+ }
- SaveStateDescriptor desc(slot, name);
+ SaveStateDescriptor descriptor(slotNr, description);
// Do not allow save slot 0 (used for auto-saving) to be deleted or
// overwritten.
- desc.setDeletableFlag(slot != 0);
- desc.setWriteProtectedFlag(slot == 0);
+ if (slotNr == 0) {
+ descriptor.setWriteProtectedFlag(true);
+ descriptor.setDeletableFlag(false);
+ } else {
+ descriptor.setWriteProtectedFlag(false);
+ descriptor.setDeletableFlag(true);
+ }
char saveVersion = in->readByte();
if (saveVersion >= 4) {
Graphics::Surface *const thumbnail = Graphics::loadThumbnail(*in);
- desc.setThumbnail(thumbnail);
+ descriptor.setThumbnail(thumbnail);
uint32 saveDate = in->readUint32BE();
uint16 saveTime = in->readUint16BE();
if (saveVersion >= 6) {
uint32 playTime = in->readUint32BE();
- desc.setPlayTime(playTime * 1000);
+ descriptor.setPlayTime(playTime * 1000);
}
int day = (saveDate >> 24) & 0xFF;
int month = (saveDate >> 16) & 0xFF;
int year = saveDate & 0xFFFF;
- desc.setSaveDate(year, month, day);
+ descriptor.setSaveDate(year, month, day);
int hour = (saveTime >> 8) & 0xFF;
int minutes = saveTime & 0xFF;
- desc.setSaveTime(hour, minutes);
+ descriptor.setSaveTime(hour, minutes);
}
-
delete in;
- return desc;
+ return descriptor;
+
} else {
SaveStateDescriptor emptySave;
// Do not allow save slot 0 (used for auto-saving) to be overwritten.
- emptySave.setWriteProtectedFlag(slot == 0);
+ if (slotNr == 0) {
+ emptySave.setWriteProtectedFlag(true);
+ } else {
+ emptySave.setWriteProtectedFlag(false);
+ }
return emptySave;
}
}
@@ -528,14 +570,17 @@ const ADGameDescription *AgiMetaEngine::fallbackDetect(const FileMap &allFilesXX
namespace Agi {
bool AgiBase::canLoadGameStateCurrently() {
- return (!(getGameType() == GType_PreAGI) && getflag(fMenusWork) && !_noSaveLoadAllowed);
+ return (!(getGameType() == GType_PreAGI) && getflag(VM_FLAG_MENUS_WORK) && !_noSaveLoadAllowed);
}
bool AgiBase::canSaveGameStateCurrently() {
+ bool promptEnabled = false;
+
if (getGameID() == GID_BC) // Technically in Black Cauldron we may save anytime
return true;
- return (!(getGameType() == GType_PreAGI) && getflag(fMenusWork) && !_noSaveLoadAllowed && _game.inputEnabled);
+ promptEnabled = promptIsEnabled();
+ return (!(getGameType() == GType_PreAGI) && getflag(VM_FLAG_MENUS_WORK) && !_noSaveLoadAllowed && promptEnabled);
}
int AgiEngine::agiDetectGame() {
diff --git a/engines/agi/font.h b/engines/agi/font.h
index 0e6b15f06b..6b6381ccd7 100644
--- a/engines/agi/font.h
+++ b/engines/agi/font.h
@@ -25,6 +25,181 @@
namespace Agi {
+// Arrow to the right character, used for original saved game dialogs
+// Needs to get patched into at least the Apple IIgs font, because the font didn't support
+// that character and original AGI on Apple IIgs used Apple II menus for saving/restoring.
+static const uint8 fontData_ArrowRightCharacter[8] = {
+ 0x00, 0x18, 0x0C, 0xFE, 0x0C, 0x18, 0x00, 0x00,
+};
+
+// topaz data, same as in engines\parallaction\staticres.cpp
+// seems to have been recreated and is not the original amiga font
+static const uint8 fontData_AmigaPseudoTopaz[2600] = {
+ 0x00, 0x00, 0x03, 0xf3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x79, 0x00, 0x00, 0x03, 0xe9, 0x00, 0x00, 0x02, 0x79,
+ 0x70, 0xff, 0x4e, 0x75, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x00, 0x1a, 0x0f, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x45, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x09, 0x74, 0x00, 0x08,
+ 0x00, 0x40, 0x00, 0x08, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x20, 0xff, 0x00, 0x00, 0x00, 0x6e,
+ 0x00, 0xbe, 0x00, 0x00, 0x06, 0x5e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18,
+ 0x6c, 0x6c, 0x18, 0x00, 0x38, 0x18, 0x0c, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x3c, 0x18,
+ 0x3c, 0x3c, 0x1c, 0x7e, 0x1c, 0x7e, 0x3c, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x7c, 0x3c,
+ 0x7c, 0x1e, 0x78, 0x7e, 0x7e, 0x3c, 0x66, 0x3c, 0x06, 0xc6, 0x60, 0xc6, 0xc6, 0x3c, 0x7c, 0x78,
+ 0x7c, 0x3c, 0x7e, 0x66, 0x66, 0xc6, 0xc3, 0xc3, 0xfe, 0x3c, 0xc0, 0x3c, 0x10, 0x00, 0x18, 0x00,
+ 0x60, 0x00, 0x06, 0x00, 0x1c, 0x00, 0x60, 0x18, 0x0c, 0x60, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x18, 0x70, 0x72, 0x0f, 0x00, 0x18,
+ 0x00, 0x1c, 0x42, 0xc3, 0x18, 0x3c, 0x66, 0x7e, 0x1c, 0x00, 0x3e, 0x7e, 0x7e, 0x3c, 0x18, 0x78,
+ 0x78, 0x18, 0x00, 0x3e, 0x00, 0x00, 0x30, 0x38, 0x00, 0x40, 0x40, 0xc0, 0x18, 0x3c, 0x3c, 0x7e,
+ 0x06, 0x66, 0x18, 0x7e, 0x7e, 0x36, 0x0c, 0x0c, 0x18, 0x3c, 0xc6, 0x3c, 0x60, 0x76, 0x18, 0x00,
+ 0x0c, 0x7e, 0x71, 0x66, 0x00, 0x66, 0x60, 0x0e, 0x7e, 0x66, 0x18, 0x6e, 0x3c, 0x00, 0x18, 0x7e,
+ 0x06, 0x66, 0x18, 0x00, 0x7e, 0x34, 0x0c, 0x0c, 0x18, 0x0c, 0x60, 0x00, 0x18, 0x3c, 0x0c, 0x00,
+ 0x0c, 0x00, 0x71, 0x00, 0x00, 0x00, 0x18, 0x0c, 0x7e, 0x00, 0x18, 0x3c, 0x00, 0x18, 0x6c, 0x6c,
+ 0x3e, 0x66, 0x6c, 0x18, 0x18, 0x18, 0x66, 0x18, 0x00, 0x00, 0x00, 0x06, 0x66, 0x38, 0x66, 0x66,
+ 0x3c, 0x60, 0x30, 0x06, 0x66, 0x66, 0x18, 0x18, 0x06, 0x00, 0x60, 0x66, 0xc6, 0x66, 0x66, 0x30,
+ 0x6c, 0x60, 0x60, 0x66, 0x66, 0x18, 0x06, 0xcc, 0x60, 0xee, 0xe6, 0x66, 0x66, 0xcc, 0x66, 0x66,
+ 0x18, 0x66, 0x66, 0xc6, 0x66, 0x66, 0x0c, 0x30, 0x60, 0x0c, 0x38, 0x00, 0x18, 0x00, 0x60, 0x00,
+ 0x06, 0x00, 0x30, 0x00, 0x60, 0x00, 0x00, 0x60, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x9c, 0x3c, 0x7e, 0x00, 0x0c, 0x36,
+ 0x3c, 0x66, 0x18, 0x60, 0x66, 0x81, 0x24, 0x33, 0x06, 0x81, 0x00, 0x66, 0x18, 0x0c, 0x0c, 0x30,
+ 0x00, 0x7a, 0x00, 0x00, 0x70, 0x44, 0xcc, 0xc6, 0xc6, 0x23, 0x00, 0x66, 0x18, 0x00, 0x1c, 0x00,
+ 0x24, 0x60, 0x00, 0x1c, 0x18, 0x18, 0x00, 0x66, 0xcc, 0x00, 0x60, 0x3c, 0x30, 0xc6, 0x18, 0x00,
+ 0x8e, 0x00, 0xc6, 0x66, 0x60, 0x38, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x00, 0x00, 0x00, 0x0c, 0x00,
+ 0x24, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x18, 0x66, 0x3c, 0x18, 0x18, 0x18, 0x00, 0x18, 0x7e,
+ 0x8e, 0x66, 0x18, 0x00, 0x18, 0x18, 0x00, 0x66, 0x00, 0x18, 0x00, 0x18, 0x00, 0xfe, 0x60, 0xac,
+ 0x68, 0x30, 0x30, 0x0c, 0x3c, 0x18, 0x00, 0x00, 0x00, 0x0c, 0x6e, 0x78, 0x06, 0x06, 0x6c, 0x7c,
+ 0x60, 0x06, 0x66, 0x66, 0x18, 0x18, 0x18, 0x7e, 0x18, 0x06, 0xde, 0x66, 0x66, 0x60, 0x66, 0x60,
+ 0x60, 0x60, 0x66, 0x18, 0x06, 0xd8, 0x60, 0xfe, 0xf6, 0x66, 0x66, 0xcc, 0x66, 0x70, 0x18, 0x66,
+ 0x66, 0xc6, 0x3c, 0x3c, 0x18, 0x30, 0x30, 0x0c, 0x6c, 0x00, 0x0c, 0x3c, 0x7c, 0x3c, 0x3e, 0x3c,
+ 0x7c, 0x3e, 0x7c, 0x18, 0x0c, 0x66, 0x18, 0xec, 0x7c, 0x3c, 0x7c, 0x3e, 0x7c, 0x3c, 0x7c, 0x66,
+ 0x66, 0xc6, 0xc6, 0x66, 0x7e, 0x18, 0x18, 0x18, 0x00, 0xf0, 0x66, 0x18, 0x3e, 0x30, 0x66, 0x3c,
+ 0x18, 0x3c, 0x00, 0x9d, 0x44, 0x66, 0x00, 0xb9, 0x00, 0x3c, 0x7e, 0x18, 0x18, 0x60, 0x66, 0x7a,
+ 0x18, 0x00, 0x30, 0x44, 0x66, 0x4c, 0x4c, 0x66, 0x18, 0x66, 0x18, 0x3c, 0x3c, 0x3c, 0x3c, 0x60,
+ 0x7e, 0x3c, 0x7e, 0x7e, 0x7e, 0x60, 0xd8, 0x3c, 0x60, 0x66, 0xc6, 0xe6, 0x3c, 0x3c, 0x3c, 0x3c,
+ 0x6c, 0x66, 0x6c, 0x66, 0x66, 0x66, 0x7e, 0x7e, 0x66, 0x3c, 0x18, 0x3c, 0x18, 0x3c, 0x3c, 0x3c,
+ 0x3c, 0x18, 0x3c, 0x7e, 0x3c, 0x3e, 0x6c, 0x00, 0x18, 0x3c, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x66, 0x1e, 0x3c, 0x66, 0x00, 0x7e, 0x7e, 0x00, 0x18, 0x00, 0x6c, 0x3c, 0xd8, 0x76, 0x00,
+ 0x30, 0x0c, 0xff, 0x7e, 0x00, 0x7e, 0x00, 0x18, 0x7e, 0x18, 0x0c, 0x1c, 0xcc, 0x06, 0x7c, 0x0c,
+ 0x3c, 0x3e, 0x00, 0x00, 0x60, 0x00, 0x06, 0x0c, 0xd6, 0x7e, 0x7c, 0x60, 0x66, 0x78, 0x78, 0x6e,
+ 0x7e, 0x18, 0x06, 0xf0, 0x60, 0xd6, 0xde, 0x66, 0x7c, 0xcc, 0x7c, 0x3c, 0x18, 0x66, 0x66, 0xd6,
+ 0x18, 0x18, 0x30, 0x30, 0x18, 0x0c, 0xc6, 0x00, 0x00, 0x06, 0x66, 0x60, 0x66, 0x66, 0x30, 0x66,
+ 0x66, 0x18, 0x0c, 0x6c, 0x18, 0xfe, 0x66, 0x66, 0x66, 0x66, 0x66, 0x60, 0x30, 0x66, 0x66, 0xc6,
+ 0x6c, 0x66, 0x0c, 0x70, 0x18, 0x0e, 0x00, 0xc3, 0x66, 0x18, 0x6c, 0x78, 0x3c, 0x18, 0x00, 0x66,
+ 0x00, 0xb1, 0x3c, 0xcc, 0x00, 0xa5, 0x00, 0x00, 0x18, 0x30, 0x0c, 0x00, 0x66, 0x3a, 0x18, 0x00,
+ 0x30, 0x38, 0x33, 0x58, 0x58, 0x2c, 0x30, 0x7e, 0x18, 0x66, 0x66, 0x66, 0x66, 0x78, 0x60, 0x66,
+ 0x60, 0x4c, 0x60, 0x6e, 0xf0, 0x18, 0x60, 0x30, 0xe6, 0xf6, 0x66, 0x66, 0x66, 0x66, 0x38, 0x66,
+ 0x70, 0x30, 0x66, 0x66, 0x4c, 0x4c, 0x6c, 0x06, 0x18, 0x06, 0x3c, 0x06, 0x06, 0x66, 0x66, 0x3c,
+ 0x66, 0x0c, 0x66, 0x66, 0x78, 0x18, 0x18, 0x60, 0x7c, 0x66, 0x3c, 0x3c, 0x3c, 0x3c, 0x7e, 0x66,
+ 0x78, 0x60, 0x66, 0x66, 0x0c, 0x0c, 0x00, 0x18, 0x00, 0xfe, 0x06, 0x36, 0xdc, 0x00, 0x30, 0x0c,
+ 0x3c, 0x18, 0x00, 0x00, 0x00, 0x30, 0x76, 0x18, 0x18, 0x06, 0xfe, 0x06, 0x66, 0x18, 0x66, 0x06,
+ 0x00, 0x00, 0x18, 0x7e, 0x18, 0x18, 0xde, 0x66, 0x66, 0x60, 0x66, 0x60, 0x60, 0x66, 0x66, 0x18,
+ 0x06, 0xd8, 0x60, 0xc6, 0xce, 0x66, 0x60, 0xcc, 0x6c, 0x0e, 0x18, 0x66, 0x3c, 0xfe, 0x3c, 0x18,
+ 0x60, 0x30, 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x3e, 0x66, 0x60, 0x66, 0x7e, 0x30, 0x66, 0x66, 0x18,
+ 0x0c, 0x78, 0x18, 0xd6, 0x66, 0x66, 0x66, 0x66, 0x60, 0x3c, 0x30, 0x66, 0x66, 0xd6, 0x38, 0x66,
+ 0x18, 0x18, 0x18, 0x18, 0x00, 0x0f, 0x66, 0x18, 0x3e, 0x30, 0x42, 0x3c, 0x18, 0x3c, 0x00, 0x9d,
+ 0x00, 0x66, 0x00, 0xb9, 0x00, 0x00, 0x18, 0x7c, 0x78, 0x00, 0x66, 0x0a, 0x00, 0x00, 0x30, 0x00,
+ 0x66, 0x32, 0x3e, 0xd9, 0x60, 0x66, 0x18, 0x7e, 0x40, 0x7e, 0x7e, 0x60, 0x78, 0x40, 0x78, 0x18,
+ 0x78, 0x66, 0xd8, 0x18, 0x60, 0x0c, 0xf6, 0xde, 0x66, 0x66, 0x66, 0x66, 0x6c, 0x66, 0xe0, 0x0c,
+ 0x66, 0x66, 0x18, 0x18, 0x66, 0x3e, 0x18, 0x3e, 0x60, 0x3e, 0x3e, 0x7e, 0x7e, 0x60, 0x7e, 0x18,
+ 0x7e, 0x66, 0x6c, 0x18, 0x18, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x66, 0x18, 0x3c,
+ 0x66, 0x66, 0x18, 0x18, 0x00, 0x00, 0x00, 0x6c, 0x7c, 0x6a, 0xce, 0x00, 0x18, 0x18, 0x66, 0x18,
+ 0x18, 0x00, 0x18, 0x60, 0x66, 0x18, 0x30, 0x66, 0x0c, 0x66, 0x66, 0x18, 0x66, 0x0c, 0x18, 0x18,
+ 0x06, 0x00, 0x60, 0x00, 0xc0, 0x66, 0x66, 0x30, 0x6c, 0x60, 0x60, 0x66, 0x66, 0x18, 0x66, 0xcc,
+ 0x60, 0xc6, 0xc6, 0x66, 0x60, 0xdc, 0x66, 0x66, 0x18, 0x66, 0x3c, 0xee, 0x66, 0x18, 0xc0, 0x30,
+ 0x06, 0x0c, 0x00, 0x00, 0x00, 0x66, 0x66, 0x60, 0x66, 0x60, 0x30, 0x3e, 0x66, 0x18, 0x0c, 0x6c,
+ 0x18, 0xc6, 0x66, 0x66, 0x7c, 0x3e, 0x60, 0x06, 0x30, 0x66, 0x3c, 0xfe, 0x6c, 0x3c, 0x30, 0x18,
+ 0x18, 0x18, 0x00, 0x3c, 0x66, 0x18, 0x0c, 0x30, 0x00, 0x18, 0x18, 0x06, 0x00, 0x81, 0x7e, 0x33,
+ 0x00, 0xa5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x0a, 0x00, 0x00, 0x00, 0x7c, 0xcc, 0x66,
+ 0x62, 0x33, 0x66, 0x66, 0x18, 0x66, 0x66, 0x66, 0x66, 0x60, 0x60, 0x66, 0x60, 0x32, 0x60, 0x3e,
+ 0xcc, 0x18, 0x7e, 0x66, 0xde, 0xce, 0x66, 0x66, 0x66, 0x66, 0xc6, 0x66, 0x60, 0x66, 0x66, 0x66,
+ 0x32, 0x32, 0x66, 0x66, 0x18, 0x66, 0x60, 0x66, 0x66, 0x60, 0x60, 0x60, 0x60, 0x30, 0x60, 0x3e,
+ 0x66, 0x18, 0x18, 0x06, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x18, 0x66, 0x18, 0x06, 0x66, 0x66,
+ 0x30, 0x30, 0x00, 0x18, 0x00, 0x6c, 0x18, 0xcc, 0x7b, 0x00, 0x0c, 0x30, 0x00, 0x00, 0x18, 0x00,
+ 0x18, 0xc0, 0x3c, 0x18, 0x7e, 0x3c, 0x0c, 0x3c, 0x3c, 0x18, 0x3c, 0x38, 0x18, 0x18, 0x00, 0x00,
+ 0x00, 0x18, 0x78, 0x66, 0x7c, 0x1e, 0x78, 0x7e, 0x60, 0x3e, 0x66, 0x3c, 0x3c, 0xc6, 0x7e, 0xc6,
+ 0xc6, 0x3c, 0x60, 0x7e, 0x66, 0x3c, 0x18, 0x3c, 0x18, 0xc6, 0xc3, 0x18, 0xfe, 0x3c, 0x03, 0x3c,
+ 0x00, 0x00, 0x00, 0x3e, 0x7c, 0x3c, 0x3e, 0x3c, 0x30, 0x06, 0x66, 0x0c, 0x0c, 0x66, 0x0c, 0xc6,
+ 0x66, 0x3c, 0x60, 0x06, 0x60, 0x7c, 0x1c, 0x3e, 0x18, 0x6c, 0xc6, 0x18, 0x7e, 0x0e, 0x18, 0x70,
+ 0x00, 0xf0, 0x7e, 0x18, 0x00, 0x7e, 0x00, 0x18, 0x18, 0x3c, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x81,
+ 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x7f, 0x0a, 0x00, 0x18, 0x00, 0x00, 0x00, 0xcf, 0xc4, 0x67,
+ 0x3c, 0x67, 0x3e, 0x66, 0x3c, 0x66, 0x66, 0x7f, 0x7e, 0x3c, 0x7e, 0x7e, 0x7e, 0x18, 0x30, 0x3c,
+ 0x18, 0x3c, 0xce, 0x18, 0x3c, 0x3c, 0x3c, 0x3c, 0x00, 0x3f, 0x7e, 0x3c, 0x3c, 0x3c, 0x7e, 0x7e,
+ 0x6c, 0x3f, 0x1e, 0x3e, 0x3c, 0x3e, 0x3e, 0x3c, 0x3c, 0x3c, 0x3c, 0x7e, 0x3c, 0x06, 0x18, 0x0c,
+ 0x0c, 0x7c, 0x66, 0x18, 0x3c, 0x3c, 0x3c, 0x3c, 0x00, 0x3f, 0x0c, 0x7c, 0x3e, 0x3e, 0x7e, 0x7e,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x60, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x02, 0x0e, 0x01, 0x00, 0x03,
+ 0x06, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x60, 0x00, 0x30, 0x00,
+ 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x03,
+ 0x06, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x30, 0x00, 0x18, 0x00,
+ 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x10, 0x00, 0x08, 0x00, 0x18, 0x00, 0x08, 0x00, 0x20,
+ 0x00, 0x08, 0x00, 0x28, 0x00, 0x08, 0x00, 0x30, 0x00, 0x08, 0x00, 0x38, 0x00, 0x08, 0x00, 0x40,
+ 0x00, 0x08, 0x00, 0x48, 0x00, 0x08, 0x00, 0x50, 0x00, 0x08, 0x00, 0x58, 0x00, 0x08, 0x00, 0x60,
+ 0x00, 0x08, 0x00, 0x68, 0x00, 0x08, 0x00, 0x70, 0x00, 0x08, 0x00, 0x78, 0x00, 0x08, 0x00, 0x80,
+ 0x00, 0x08, 0x00, 0x88, 0x00, 0x08, 0x00, 0x90, 0x00, 0x08, 0x00, 0x98, 0x00, 0x08, 0x00, 0xa0,
+ 0x00, 0x08, 0x00, 0xa8, 0x00, 0x08, 0x00, 0xb0, 0x00, 0x08, 0x00, 0xb8, 0x00, 0x08, 0x00, 0xc0,
+ 0x00, 0x08, 0x00, 0xc8, 0x00, 0x08, 0x00, 0xd0, 0x00, 0x08, 0x00, 0xd8, 0x00, 0x08, 0x00, 0xe0,
+ 0x00, 0x08, 0x00, 0xe8, 0x00, 0x08, 0x00, 0xf0, 0x00, 0x08, 0x00, 0xf8, 0x00, 0x08, 0x01, 0x00,
+ 0x00, 0x08, 0x01, 0x08, 0x00, 0x08, 0x01, 0x10, 0x00, 0x08, 0x01, 0x18, 0x00, 0x08, 0x01, 0x20,
+ 0x00, 0x08, 0x01, 0x28, 0x00, 0x08, 0x01, 0x30, 0x00, 0x08, 0x01, 0x38, 0x00, 0x08, 0x01, 0x40,
+ 0x00, 0x08, 0x01, 0x48, 0x00, 0x08, 0x01, 0x50, 0x00, 0x08, 0x01, 0x58, 0x00, 0x08, 0x01, 0x60,
+ 0x00, 0x08, 0x01, 0x68, 0x00, 0x08, 0x01, 0x70, 0x00, 0x08, 0x01, 0x78, 0x00, 0x08, 0x01, 0x80,
+ 0x00, 0x08, 0x01, 0x88, 0x00, 0x08, 0x01, 0x90, 0x00, 0x08, 0x01, 0x98, 0x00, 0x08, 0x01, 0xa0,
+ 0x00, 0x08, 0x01, 0xa8, 0x00, 0x08, 0x01, 0xb0, 0x00, 0x08, 0x01, 0xb8, 0x00, 0x08, 0x01, 0xc0,
+ 0x00, 0x08, 0x01, 0xc8, 0x00, 0x08, 0x01, 0xd0, 0x00, 0x08, 0x01, 0xd8, 0x00, 0x08, 0x01, 0xe0,
+ 0x00, 0x08, 0x01, 0xe8, 0x00, 0x08, 0x01, 0xf0, 0x00, 0x08, 0x01, 0xf8, 0x00, 0x08, 0x02, 0x00,
+ 0x00, 0x08, 0x02, 0x08, 0x00, 0x08, 0x02, 0x10, 0x00, 0x08, 0x02, 0x18, 0x00, 0x08, 0x02, 0x20,
+ 0x00, 0x08, 0x02, 0x28, 0x00, 0x08, 0x02, 0x30, 0x00, 0x08, 0x02, 0x38, 0x00, 0x08, 0x02, 0x40,
+ 0x00, 0x08, 0x02, 0x48, 0x00, 0x08, 0x02, 0x50, 0x00, 0x08, 0x02, 0x58, 0x00, 0x08, 0x02, 0x60,
+ 0x00, 0x08, 0x02, 0x68, 0x00, 0x08, 0x02, 0x70, 0x00, 0x08, 0x02, 0x78, 0x00, 0x08, 0x02, 0x80,
+ 0x00, 0x08, 0x02, 0x88, 0x00, 0x08, 0x02, 0x90, 0x00, 0x08, 0x02, 0x98, 0x00, 0x08, 0x02, 0xa0,
+ 0x00, 0x08, 0x02, 0xa8, 0x00, 0x08, 0x02, 0xb0, 0x00, 0x08, 0x02, 0xb8, 0x00, 0x08, 0x02, 0xc0,
+ 0x00, 0x08, 0x02, 0xc8, 0x00, 0x08, 0x02, 0xd0, 0x00, 0x08, 0x02, 0xd8, 0x00, 0x08, 0x02, 0xe0,
+ 0x00, 0x08, 0x02, 0xe8, 0x00, 0x08, 0x02, 0xf0, 0x00, 0x08, 0x02, 0xf8, 0x00, 0x08, 0x03, 0x00,
+ 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00,
+ 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00,
+ 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00,
+ 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00,
+ 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00,
+ 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00,
+ 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00,
+ 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x00, 0x00,
+ 0x00, 0x08, 0x03, 0x08, 0x00, 0x08, 0x03, 0x10, 0x00, 0x08, 0x03, 0x18, 0x00, 0x08, 0x03, 0x20,
+ 0x00, 0x08, 0x03, 0x28, 0x00, 0x08, 0x03, 0x30, 0x00, 0x08, 0x03, 0x38, 0x00, 0x08, 0x03, 0x40,
+ 0x00, 0x08, 0x03, 0x48, 0x00, 0x08, 0x03, 0x50, 0x00, 0x08, 0x03, 0x58, 0x00, 0x08, 0x03, 0x60,
+ 0x00, 0x08, 0x00, 0x68, 0x00, 0x08, 0x03, 0x68, 0x00, 0x08, 0x03, 0x70, 0x00, 0x08, 0x03, 0x78,
+ 0x00, 0x08, 0x03, 0x80, 0x00, 0x08, 0x03, 0x88, 0x00, 0x08, 0x03, 0x90, 0x00, 0x08, 0x03, 0x98,
+ 0x00, 0x08, 0x03, 0xa0, 0x00, 0x08, 0x03, 0xa8, 0x00, 0x08, 0x03, 0xb0, 0x00, 0x08, 0x03, 0xb8,
+ 0x00, 0x08, 0x03, 0xc0, 0x00, 0x08, 0x03, 0xc8, 0x00, 0x08, 0x03, 0xd0, 0x00, 0x08, 0x03, 0xd8,
+ 0x00, 0x08, 0x03, 0xe0, 0x00, 0x08, 0x03, 0xe8, 0x00, 0x08, 0x03, 0xf0, 0x00, 0x08, 0x03, 0xf8,
+ 0x00, 0x08, 0x04, 0x00, 0x00, 0x08, 0x04, 0x08, 0x00, 0x08, 0x04, 0x10, 0x00, 0x08, 0x04, 0x18,
+ 0x00, 0x08, 0x04, 0x20, 0x00, 0x08, 0x04, 0x28, 0x00, 0x08, 0x04, 0x30, 0x00, 0x08, 0x04, 0x38,
+ 0x00, 0x08, 0x04, 0x40, 0x00, 0x08, 0x04, 0x48, 0x00, 0x08, 0x04, 0x50, 0x00, 0x08, 0x04, 0x58,
+ 0x00, 0x08, 0x04, 0x60, 0x00, 0x08, 0x04, 0x68, 0x00, 0x08, 0x04, 0x70, 0x00, 0x08, 0x04, 0x78,
+ 0x00, 0x08, 0x04, 0x80, 0x00, 0x08, 0x04, 0x88, 0x00, 0x08, 0x04, 0x90, 0x00, 0x08, 0x04, 0x98,
+ 0x00, 0x08, 0x04, 0xa0, 0x00, 0x08, 0x04, 0xa8, 0x00, 0x08, 0x04, 0xb0, 0x00, 0x08, 0x04, 0xb8,
+ 0x00, 0x08, 0x04, 0xc0, 0x00, 0x08, 0x04, 0xc8, 0x00, 0x08, 0x04, 0xd0, 0x00, 0x08, 0x04, 0xd8,
+ 0x00, 0x08, 0x04, 0xe0, 0x00, 0x08, 0x04, 0xe8, 0x00, 0x08, 0x04, 0xf0, 0x00, 0x08, 0x04, 0xf8,
+ 0x00, 0x08, 0x05, 0x00, 0x00, 0x08, 0x05, 0x08, 0x00, 0x08, 0x05, 0x10, 0x00, 0x08, 0x05, 0x18,
+ 0x00, 0x08, 0x05, 0x20, 0x00, 0x08, 0x05, 0x28, 0x00, 0x08, 0x05, 0x30, 0x00, 0x08, 0x05, 0x38,
+ 0x00, 0x08, 0x05, 0x40, 0x00, 0x08, 0x05, 0x48, 0x00, 0x08, 0x05, 0x50, 0x00, 0x08, 0x05, 0x58,
+ 0x00, 0x08, 0x05, 0x60, 0x00, 0x08, 0x05, 0x68, 0x00, 0x08, 0x05, 0x70, 0x00, 0x08, 0x05, 0x78,
+ 0x00, 0x08, 0x05, 0x80, 0x00, 0x08, 0x05, 0x88, 0x00, 0x08, 0x05, 0x90, 0x00, 0x08, 0x05, 0x98,
+ 0x00, 0x08, 0x05, 0xa0, 0x00, 0x08, 0x05, 0xa8, 0x00, 0x08, 0x05, 0xb0, 0x00, 0x08, 0x05, 0xb8,
+ 0x00, 0x08, 0x05, 0xc0, 0x00, 0x08, 0x05, 0xc8, 0x00, 0x08, 0x05, 0xd0, 0x00, 0x08, 0x05, 0xd8,
+ 0x00, 0x08, 0x05, 0xe0, 0x00, 0x08, 0x05, 0xe8, 0x00, 0x08, 0x00, 0x38, 0x00, 0x08, 0x03, 0x00,
+ 0x00, 0x08, 0x03, 0x00, 0x00, 0x00, 0x03, 0xec, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x62,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf2
+};
+
// 8x8 font patterns
static const uint8 fontData_Sierra[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -546,6 +721,7 @@ static const uint8 fontData_FanGames[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x7E, 0x00
};
+#if 0
static const uint8 fontData_Mickey[] = {
0x00, 0x36, 0x7F, 0x7F, 0x3E, 0x1C, 0x08, 0x00,
0x00, 0x00, 0x3F, 0x20, 0x2F, 0x28, 0x28, 0x28,
@@ -804,6 +980,7 @@ static const uint8 fontData_Mickey[] = {
0x08, 0x18, 0x38, 0x78, 0x38, 0x18, 0x08, 0x00,
0x10, 0x18, 0x1C, 0x1E, 0x1C, 0x18, 0x10, 0x00,
};
+#endif
static const uint8 fontData_IBM[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
diff --git a/engines/agi/global.cpp b/engines/agi/global.cpp
index 7d55316d7b..bf6356dbcd 100644
--- a/engines/agi/global.cpp
+++ b/engines/agi/global.cpp
@@ -48,17 +48,17 @@ void AgiBase::flipflag(int n) {
*set ^= 1 << (n & 0x07); // flip bit
}
-void AgiEngine::setvar(int var, int val) {
- _game.vars[var] = val;
+void AgiEngine::setVar(int16 varNr, int val) {
+ _game.vars[varNr] = val;
- if (var == vVolume) {
+ if (varNr == VM_VAR_VOLUME) {
_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, val * 17);
_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, val * 17);
}
}
-int AgiEngine::getvar(int var) {
- return _game.vars[var];
+int AgiEngine::getVar(int16 varNr) {
+ return _game.vars[varNr];
}
void AgiEngine::decrypt(uint8 *mem, int len) {
diff --git a/engines/agi/graphics.cpp b/engines/agi/graphics.cpp
index b46e6f726c..e9ae1295e9 100644
--- a/engines/agi/graphics.cpp
+++ b/engines/agi/graphics.cpp
@@ -23,773 +23,739 @@
#include "common/config-manager.h"
#include "common/file.h"
#include "common/textconsole.h"
+#include "engines/util.h"
#include "graphics/cursorman.h"
#include "graphics/palette.h"
#include "agi/agi.h"
#include "agi/graphics.h"
+#include "agi/mouse_cursor.h"
+#include "agi/palette.h"
+#include "agi/picture.h"
+#include "agi/text.h"
namespace Agi {
-#define DEV_X0(x) ((x) << 1)
-#define DEV_X1(x) (((x) << 1) + 1)
-#define DEV_Y(x) (x)
+#include "agi/font.h"
-#ifndef MAX_INT
-# define MAX_INT (int)((unsigned)~0 >> 1)
-#endif
+GfxMgr::GfxMgr(AgiBase *vm) : _vm(vm) {
+ _agipalFileNum = 0;
-#include "agi/font.h"
+ memset(&_paletteGfxMode, 0, sizeof(_paletteGfxMode));
+ memset(&_paletteTextMode, 0, sizeof(_paletteTextMode));
-/**
- * 16 color RGB palette.
- * This array contains the 6-bit RGB values of the EGA palette exported
- * to the console drivers.
- */
-static const uint8 egaPalette[16 * 3] = {
- 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x2a,
- 0x00, 0x2a, 0x00,
- 0x00, 0x2a, 0x2a,
- 0x2a, 0x00, 0x00,
- 0x2a, 0x00, 0x2a,
- 0x2a, 0x15, 0x00,
- 0x2a, 0x2a, 0x2a,
- 0x15, 0x15, 0x15,
- 0x15, 0x15, 0x3f,
- 0x15, 0x3f, 0x15,
- 0x15, 0x3f, 0x3f,
- 0x3f, 0x15, 0x15,
- 0x3f, 0x15, 0x3f,
- 0x3f, 0x3f, 0x15,
- 0x3f, 0x3f, 0x3f
-};
+ memset(&_mouseCursor, 0, sizeof(_mouseCursor));
+ memset(&_mouseCursorBusy, 0, sizeof(_mouseCursorBusy));
-/**
- * Atari ST AGI palette.
- * Used by all of the tested Atari ST AGI games
- * from Donald Duck's Playground (1986) to Manhunter II (1989).
- * 16 RGB colors. 3 bits per color component.
- */
-#if 0
-static const uint8 atariStAgiPalette[16 * 3] = {
- 0x0, 0x0, 0x0,
- 0x0, 0x0, 0x7,
- 0x0, 0x4, 0x0,
- 0x0, 0x5, 0x4,
- 0x5, 0x0, 0x0,
- 0x5, 0x3, 0x6,
- 0x4, 0x3, 0x0,
- 0x5, 0x5, 0x5,
- 0x3, 0x3, 0x2,
- 0x0, 0x5, 0x7,
- 0x0, 0x6, 0x0,
- 0x0, 0x7, 0x6,
- 0x7, 0x2, 0x3,
- 0x7, 0x4, 0x7,
- 0x7, 0x7, 0x4,
- 0x7, 0x7, 0x7
-};
-#endif
+ initPriorityTable();
-/**
- * Second generation Apple IIGS AGI palette.
- * A 16-color, 12-bit RGB palette.
- *
- * Used by at least the following Apple IIGS AGI versions:
- * 1.003 (Leisure Suit Larry I v1.0E, intro says 1987)
- * 1.005 (AGI Demo 2 1987-06-30?)
- * 1.006 (King's Quest I v1.0S 1988-02-23)
- * 1.007 (Police Quest I v2.0B 1988-04-21 8:00am)
- * 1.013 (King's Quest II v2.0A 1988-06-16 (CE))
- * 1.013 (Mixed-Up Mother Goose v2.0A 1988-05-31 10:00am)
- * 1.014 (King's Quest III v2.0A 1988-08-28 (CE))
- * 1.014 (Space Quest II v2.0A, LOGIC.141 says 1988)
- * 2.004 (Manhunter I v2.0E 1988-10-05 (CE))
- * 2.006 (King's Quest IV v1.0K 1988-11-22 (CE))
- * 3.001 (Black Cauldron v1.0O 1989-02-24 (CE))
- * 3.003 (Gold Rush! v1.0M 1989-02-28 (CE))
- */
-#if 0
-// FIXME: Identical to amigaAgiPaletteV2
-static const uint8 appleIIgsAgiPaletteV2[16 * 3] = {
- 0x0, 0x0, 0x0,
- 0x0, 0x0, 0xF,
- 0x0, 0x8, 0x0,
- 0x0, 0xD, 0xB,
- 0xC, 0x0, 0x0,
- 0xB, 0x7, 0xD,
- 0x8, 0x5, 0x0,
- 0xB, 0xB, 0xB,
- 0x7, 0x7, 0x7,
- 0x0, 0xB, 0xF,
- 0x0, 0xE, 0x0,
- 0x0, 0xF, 0xD,
- 0xF, 0x9, 0x8,
- 0xD, 0x9, 0xF, // Only this differs from the 1st generation palette
- 0xE, 0xE, 0x0,
- 0xF, 0xF, 0xF
-};
-#endif
+ _renderStartOffsetY = 0;
+}
/**
- * First generation Amiga & Apple IIGS AGI palette.
- * A 16-color, 12-bit RGB palette.
- *
- * Used by at least the following Amiga AGI versions:
- * 2.082 (King's Quest I v1.0U 1986)
- * 2.082 (Space Quest I v1.2 1986)
- * 2.090 (King's Quest III v1.01 1986-11-08)
- * 2.107 (King's Quest II v2.0J 1987-01-29)
- * x.yyy (Black Cauldron v2.00 1987-06-14)
- * x.yyy (Larry I v1.05 1987-06-26)
+ * Initialize graphics device.
*
- * Also used by at least the following Apple IIGS AGI versions:
- * 1.002 (Space Quest I, intro says v2.2 1987)
+ * @see deinit_video()
*/
-static const uint8 amigaAgiPaletteV1[16 * 3] = {
- 0x0, 0x0, 0x0,
- 0x0, 0x0, 0xF,
- 0x0, 0x8, 0x0,
- 0x0, 0xD, 0xB,
- 0xC, 0x0, 0x0,
- 0xB, 0x7, 0xD,
- 0x8, 0x5, 0x0,
- 0xB, 0xB, 0xB,
- 0x7, 0x7, 0x7,
- 0x0, 0xB, 0xF,
- 0x0, 0xE, 0x0,
- 0x0, 0xF, 0xD,
- 0xF, 0x9, 0x8,
- 0xF, 0x7, 0x0,
- 0xE, 0xE, 0x0,
- 0xF, 0xF, 0xF
-};
+int GfxMgr::initVideo() {
+ // Set up palettes
+ initPalette(_paletteTextMode, PALETTE_EGA);
+
+ switch (_vm->_renderMode) {
+ case RENDERMODE_EGA:
+ initPalette(_paletteGfxMode, PALETTE_EGA);
+ break;
+ case RENDERMODE_CGA:
+ initPalette(_paletteGfxMode, PALETTE_CGA, 4, 8);
+ break;
+ case RENDERMODE_VGA:
+ initPalette(_paletteGfxMode, PALETTE_VGA, 256, 8);
+ break;
+ case RENDERMODE_AMIGA:
+ if (!ConfMan.getBool("altamigapalette")) {
+ // Set the correct Amiga palette depending on AGI interpreter version
+ if (_vm->getVersion() < 0x2936)
+ initPalette(_paletteGfxMode, PALETTE_AMIGA_V1, 16, 4);
+ else if (_vm->getVersion() == 0x2936)
+ initPalette(_paletteGfxMode, PALETTE_AMIGA_V2, 16, 4);
+ else if (_vm->getVersion() > 0x2936)
+ initPalette(_paletteGfxMode, PALETTE_AMIGA_V3, 16, 4);
+ } else {
+ // Set the old common alternative Amiga palette
+ initPalette(_paletteGfxMode, PALETTE_AMIGA_ALT);
+ }
+ break;
+ case RENDERMODE_APPLE_II_GS:
+ initPalette(_paletteGfxMode, PALETTE_APPLE_II_GS, 16, 4);
+ break;
+ case RENDERMODE_ATARI_ST:
+ initPalette(_paletteGfxMode, PALETTE_ATARI_ST, 16, 3);
+ break;
+ default:
+ error("initVideo: unsupported render mode");
+ break;
+ }
-/**
- * Second generation Amiga AGI palette.
- * A 16-color, 12-bit RGB palette.
- *
- * Used by at least the following Amiga AGI versions:
- * 2.202 (Space Quest II v2.0F. Intro says 1988. ScummVM 0.10.0 detects as 1986-12-09)
- */
-static const uint8 amigaAgiPaletteV2[16 * 3] = {
- 0x0, 0x0, 0x0,
- 0x0, 0x0, 0xF,
- 0x0, 0x8, 0x0,
- 0x0, 0xD, 0xB,
- 0xC, 0x0, 0x0,
- 0xB, 0x7, 0xD,
- 0x8, 0x5, 0x0,
- 0xB, 0xB, 0xB,
- 0x7, 0x7, 0x7,
- 0x0, 0xB, 0xF,
- 0x0, 0xE, 0x0,
- 0x0, 0xF, 0xD,
- 0xF, 0x9, 0x8,
- 0xD, 0x0, 0xF,
- 0xE, 0xE, 0x0,
- 0xF, 0xF, 0xF
-};
+ // set up mouse cursors
+ switch (_vm->_renderMode) {
+ case RENDERMODE_EGA:
+ case RENDERMODE_CGA:
+ case RENDERMODE_VGA:
+ initMouseCursor(&_mouseCursor, MOUSECURSOR_SCI, 11, 16, 1, 1);
+ initMouseCursor(&_mouseCursorBusy, MOUSECURSOR_SCI_BUSY, 15, 16, 7, 8);
+ break;
+ case RENDERMODE_AMIGA:
+ initMouseCursor(&_mouseCursor, MOUSECURSOR_AMIGA, 8, 11, 1, 1);
+ initMouseCursor(&_mouseCursorBusy, MOUSECURSOR_AMIGA_BUSY, 13, 16, 7, 8);
+ break;
+ case RENDERMODE_APPLE_II_GS:
+ // had no special busy mouse cursor
+ initMouseCursor(&_mouseCursor, MOUSECURSOR_APPLE_II_GS, 9, 11, 1, 1);
+ initMouseCursor(&_mouseCursorBusy, MOUSECURSOR_SCI_BUSY, 15, 16, 7, 8);
+ break;
+ case RENDERMODE_ATARI_ST:
+ initMouseCursor(&_mouseCursor, MOUSECURSOR_ATARI_ST, 11, 16, 1, 1);
+ initMouseCursor(&_mouseCursorBusy, MOUSECURSOR_SCI_BUSY, 15, 16, 7, 8);
+ break;
+ default:
+ error("initVideo: unsupported render mode");
+ break;
+ }
-/**
- * Third generation Amiga AGI palette.
- * A 16-color, 12-bit RGB palette.
- *
- * Used by at least the following Amiga AGI versions:
- * 2.310 (Police Quest I v2.0B 1989-02-22)
- * 2.316 (Gold Rush! v2.05 1989-03-09)
- * x.yyy (Manhunter I v1.06 1989-03-18)
- * 2.333 (Manhunter II v3.06 1989-08-17)
- * 2.333 (King's Quest III v2.15 1989-11-15)
- */
-static const uint8 amigaAgiPaletteV3[16 * 3] = {
- 0x0, 0x0, 0x0,
- 0x0, 0x0, 0xB,
- 0x0, 0xB, 0x0,
- 0x0, 0xB, 0xB,
- 0xB, 0x0, 0x0,
- 0xB, 0x0, 0xB,
- 0xC, 0x7, 0x0,
- 0xB, 0xB, 0xB,
- 0x7, 0x7, 0x7,
- 0x0, 0x0, 0xF,
- 0x0, 0xF, 0x0,
- 0x0, 0xF, 0xF,
- 0xF, 0x0, 0x0,
- 0xF, 0x0, 0xF,
- 0xF, 0xF, 0x0,
- 0xF, 0xF, 0xF
-};
+ _pixels = SCRIPT_WIDTH * SCRIPT_HEIGHT;
+ _visualScreen = (byte *)calloc(_pixels, 1);
+ _priorityScreen = (byte *)calloc(_pixels, 1);
+ _activeScreen = _visualScreen;
+ //_activeScreen = _priorityScreen;
-/**
- * 16 color amiga-ish palette.
- */
-static const uint8 altAmigaPalette[16 * 3] = {
- 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x3f,
- 0x00, 0x2A, 0x00,
- 0x00, 0x2A, 0x2A,
- 0x33, 0x00, 0x00,
- 0x2f, 0x1c, 0x37,
- 0x23, 0x14, 0x00,
- 0x2f, 0x2f, 0x2f,
- 0x15, 0x15, 0x15,
- 0x00, 0x2f, 0x3f,
- 0x00, 0x33, 0x15,
- 0x15, 0x3F, 0x3F,
- 0x3f, 0x27, 0x23,
- 0x3f, 0x15, 0x3f,
- 0x3b, 0x3b, 0x00,
- 0x3F, 0x3F, 0x3F
-};
+ _displayPixels = DISPLAY_WIDTH * DISPLAY_HEIGHT;
+ _displayScreen = (byte *)calloc(_displayPixels, 1);
+
+ initGraphics(DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_WIDTH > 320);
+
+ setPalette(true); // set gfx-mode palette
+
+ // set up mouse cursor palette
+ CursorMan.replaceCursorPalette(MOUSECURSOR_PALETTE, 1, ARRAYSIZE(MOUSECURSOR_PALETTE) / 3);
+ setMouseCursor();
+
+ return errOK;
+}
/**
- * 256 color palette used with AGI256 and AGI256-2 games.
- * Uses full 8 bits per color component.
+ * Deinitialize graphics device.
+ *
+ * @see init_video()
*/
-static const uint8 vgaPalette[256 * 3] = {
- 0x00, 0x00, 0x00,
- 0x00, 0x00, 0xA8,
- 0x00, 0xA8, 0x00,
- 0x00, 0xA8, 0xA8,
- 0xA8, 0x00, 0x00,
- 0xA8, 0x00, 0xA8,
- 0xA8, 0x54, 0x00,
- 0xA8, 0xA8, 0xA8,
- 0x54, 0x54, 0x54,
- 0x54, 0x54, 0xFC,
- 0x54, 0xFC, 0x54,
- 0x54, 0xFC, 0xFC,
- 0xFC, 0x54, 0x54,
- 0xFC, 0x54, 0xFC,
- 0xFC, 0xFC, 0x54,
- 0xFC, 0xFC, 0xFC,
- 0x00, 0x00, 0x00,
- 0x14, 0x14, 0x14,
- 0x20, 0x20, 0x20,
- 0x2C, 0x2C, 0x2C,
- 0x38, 0x38, 0x38,
- 0x44, 0x44, 0x44,
- 0x50, 0x50, 0x50,
- 0x60, 0x60, 0x60,
- 0x70, 0x70, 0x70,
- 0x80, 0x80, 0x80,
- 0x90, 0x90, 0x90,
- 0xA0, 0xA0, 0xA0,
- 0xB4, 0xB4, 0xB4,
- 0xC8, 0xC8, 0xC8,
- 0xE0, 0xE0, 0xE0,
- 0xFC, 0xFC, 0xFC,
- 0x00, 0x00, 0xFC,
- 0x40, 0x00, 0xFC,
- 0x7C, 0x00, 0xFC,
- 0xBC, 0x00, 0xFC,
- 0xFC, 0x00, 0xFC,
- 0xFC, 0x00, 0xBC,
- 0xFC, 0x00, 0x7C,
- 0xFC, 0x00, 0x40,
- 0xFC, 0x00, 0x00,
- 0xFC, 0x40, 0x00,
- 0xFC, 0x7C, 0x00,
- 0xFC, 0xBC, 0x00,
- 0xFC, 0xFC, 0x00,
- 0xBC, 0xFC, 0x00,
- 0x7C, 0xFC, 0x00,
- 0x40, 0xFC, 0x00,
- 0x00, 0xFC, 0x00,
- 0x00, 0xFC, 0x40,
- 0x00, 0xFC, 0x7C,
- 0x00, 0xFC, 0xBC,
- 0x00, 0xFC, 0xFC,
- 0x00, 0xBC, 0xFC,
- 0x00, 0x7C, 0xFC,
- 0x00, 0x40, 0xFC,
- 0x7C, 0x7C, 0xFC,
- 0x9C, 0x7C, 0xFC,
- 0xBC, 0x7C, 0xFC,
- 0xDC, 0x7C, 0xFC,
- 0xFC, 0x7C, 0xFC,
- 0xFC, 0x7C, 0xDC,
- 0xFC, 0x7C, 0xBC,
- 0xFC, 0x7C, 0x9C,
- 0xFC, 0x7C, 0x7C,
- 0xFC, 0x9C, 0x7C,
- 0xFC, 0xBC, 0x7C,
- 0xFC, 0xDC, 0x7C,
- 0xFC, 0xFC, 0x7C,
- 0xDC, 0xFC, 0x7C,
- 0xBC, 0xFC, 0x7C,
- 0x9C, 0xFC, 0x7C,
- 0x7C, 0xFC, 0x7C,
- 0x7C, 0xFC, 0x9C,
- 0x7C, 0xFC, 0xBC,
- 0x7C, 0xFC, 0xDC,
- 0x7C, 0xFC, 0xFC,
- 0x7C, 0xDC, 0xFC,
- 0x7C, 0xBC, 0xFC,
- 0x7C, 0x9C, 0xFC,
- 0xB4, 0xB4, 0xFC,
- 0xC4, 0xB4, 0xFC,
- 0xD8, 0xB4, 0xFC,
- 0xE8, 0xB4, 0xFC,
- 0xFC, 0xB4, 0xFC,
- 0xFC, 0xB4, 0xE8,
- 0xFC, 0xB4, 0xD8,
- 0xFC, 0xB4, 0xC4,
- 0xFC, 0xB4, 0xB4,
- 0xFC, 0xC4, 0xB4,
- 0xFC, 0xD8, 0xB4,
- 0xFC, 0xE8, 0xB4,
- 0xFC, 0xFC, 0xB4,
- 0xE8, 0xFC, 0xB4,
- 0xD8, 0xFC, 0xB4,
- 0xC4, 0xFC, 0xB4,
- 0xB4, 0xFC, 0xB4,
- 0xB4, 0xFC, 0xC4,
- 0xB4, 0xFC, 0xD8,
- 0xB4, 0xFC, 0xE8,
- 0xB4, 0xFC, 0xFC,
- 0xB4, 0xE8, 0xFC,
- 0xB4, 0xD8, 0xFC,
- 0xB4, 0xC4, 0xFC,
- 0x00, 0x00, 0x70,
- 0x1C, 0x00, 0x70,
- 0x38, 0x00, 0x70,
- 0x54, 0x00, 0x70,
- 0x70, 0x00, 0x70,
- 0x70, 0x00, 0x54,
- 0x70, 0x00, 0x38,
- 0x70, 0x00, 0x1C,
- 0x70, 0x00, 0x00,
- 0x70, 0x1C, 0x00,
- 0x70, 0x38, 0x00,
- 0x70, 0x54, 0x00,
- 0x70, 0x70, 0x00,
- 0x54, 0x70, 0x00,
- 0x38, 0x70, 0x00,
- 0x1C, 0x70, 0x00,
- 0x00, 0x70, 0x00,
- 0x00, 0x70, 0x1C,
- 0x00, 0x70, 0x38,
- 0x00, 0x70, 0x54,
- 0x00, 0x70, 0x70,
- 0x00, 0x54, 0x70,
- 0x00, 0x38, 0x70,
- 0x00, 0x1C, 0x70,
- 0x38, 0x38, 0x70,
- 0x44, 0x38, 0x70,
- 0x54, 0x38, 0x70,
- 0x60, 0x38, 0x70,
- 0x70, 0x38, 0x70,
- 0x70, 0x38, 0x60,
- 0x70, 0x38, 0x54,
- 0x70, 0x38, 0x44,
- 0x70, 0x38, 0x38,
- 0x70, 0x44, 0x38,
- 0x70, 0x54, 0x38,
- 0x70, 0x60, 0x38,
- 0x70, 0x70, 0x38,
- 0x60, 0x70, 0x38,
- 0x54, 0x70, 0x38,
- 0x44, 0x70, 0x38,
- 0x38, 0x70, 0x38,
- 0x38, 0x70, 0x44,
- 0x38, 0x70, 0x54,
- 0x38, 0x70, 0x60,
- 0x38, 0x70, 0x70,
- 0x38, 0x60, 0x70,
- 0x38, 0x54, 0x70,
- 0x38, 0x44, 0x70,
- 0x50, 0x50, 0x70,
- 0x58, 0x50, 0x70,
- 0x60, 0x50, 0x70,
- 0x68, 0x50, 0x70,
- 0x70, 0x50, 0x70,
- 0x70, 0x50, 0x68,
- 0x70, 0x50, 0x60,
- 0x70, 0x50, 0x58,
- 0x70, 0x50, 0x50,
- 0x70, 0x58, 0x50,
- 0x70, 0x60, 0x50,
- 0x70, 0x68, 0x50,
- 0x70, 0x70, 0x50,
- 0x68, 0x70, 0x50,
- 0x60, 0x70, 0x50,
- 0x58, 0x70, 0x50,
- 0x50, 0x70, 0x50,
- 0x50, 0x70, 0x58,
- 0x50, 0x70, 0x60,
- 0x50, 0x70, 0x68,
- 0x50, 0x70, 0x70,
- 0x50, 0x68, 0x70,
- 0x50, 0x60, 0x70,
- 0x50, 0x58, 0x70,
- 0x00, 0x00, 0x40,
- 0x10, 0x00, 0x40,
- 0x20, 0x00, 0x40,
- 0x30, 0x00, 0x40,
- 0x40, 0x00, 0x40,
- 0x40, 0x00, 0x30,
- 0x40, 0x00, 0x20,
- 0x40, 0x00, 0x10,
- 0x40, 0x00, 0x00,
- 0x40, 0x10, 0x00,
- 0x40, 0x20, 0x00,
- 0x40, 0x30, 0x00,
- 0x40, 0x40, 0x00,
- 0x30, 0x40, 0x00,
- 0x20, 0x40, 0x00,
- 0x10, 0x40, 0x00,
- 0x00, 0x40, 0x00,
- 0x00, 0x40, 0x10,
- 0x00, 0x40, 0x20,
- 0x00, 0x40, 0x30,
- 0x00, 0x40, 0x40,
- 0x00, 0x30, 0x40,
- 0x00, 0x20, 0x40,
- 0x00, 0x10, 0x40,
- 0x20, 0x20, 0x40,
- 0x28, 0x20, 0x40,
- 0x30, 0x20, 0x40,
- 0x38, 0x20, 0x40,
- 0x40, 0x20, 0x40,
- 0x40, 0x20, 0x38,
- 0x40, 0x20, 0x30,
- 0x40, 0x20, 0x28,
- 0x40, 0x20, 0x20,
- 0x40, 0x28, 0x20,
- 0x40, 0x30, 0x20,
- 0x40, 0x38, 0x20,
- 0x40, 0x40, 0x20,
- 0x38, 0x40, 0x20,
- 0x30, 0x40, 0x20,
- 0x28, 0x40, 0x20,
- 0x20, 0x40, 0x20,
- 0x20, 0x40, 0x28,
- 0x20, 0x40, 0x30,
- 0x20, 0x40, 0x38,
- 0x20, 0x40, 0x40,
- 0x20, 0x38, 0x40,
- 0x20, 0x30, 0x40,
- 0x20, 0x28, 0x40,
- 0x2C, 0x2C, 0x40,
- 0x30, 0x2C, 0x40,
- 0x34, 0x2C, 0x40,
- 0x3C, 0x2C, 0x40,
- 0x40, 0x2C, 0x40,
- 0x40, 0x2C, 0x3C,
- 0x40, 0x2C, 0x34,
- 0x40, 0x2C, 0x30,
- 0x40, 0x2C, 0x2C,
- 0x40, 0x30, 0x2C,
- 0x40, 0x34, 0x2C,
- 0x40, 0x3C, 0x2C,
- 0x40, 0x40, 0x2C,
- 0x3C, 0x40, 0x2C,
- 0x34, 0x40, 0x2C,
- 0x30, 0x40, 0x2C,
- 0x2C, 0x40, 0x2C,
- 0x2C, 0x40, 0x30,
- 0x2C, 0x40, 0x34,
- 0x2C, 0x40, 0x3C,
- 0x2C, 0x40, 0x40,
- 0x2C, 0x3C, 0x40,
- 0x2C, 0x34, 0x40,
- 0x2C, 0x30, 0x40,
- 0x40, 0x40, 0x40,
- 0x38, 0x38, 0x38,
- 0x30, 0x30, 0x30,
- 0x28, 0x28, 0x28,
- 0x24, 0x24, 0x24,
- 0x1C, 0x1C, 0x1C,
- 0x14, 0x14, 0x14,
- 0x0C, 0x0C, 0x0C
-};
+int GfxMgr::deinitVideo() {
+ free(_displayScreen);
+ free(_visualScreen);
+ free(_priorityScreen);
-static const uint16 cgaMap[16] = {
- 0x0000, // 0 - black
- 0x0d00, // 1 - blue
- 0x0b00, // 2 - green
- 0x0f00, // 3 - cyan
- 0x000b, // 4 - red
- 0x0b0d, // 5 - magenta
- 0x000d, // 6 - brown
- 0x0b0b, // 7 - gray
- 0x0d0d, // 8 - dark gray
- 0x0b0f, // 9 - light blue
- 0x0b0d, // 10 - light green
- 0x0f0d, // 11 - light cyan
- 0x0f0d, // 12 - light red
- 0x0f00, // 13 - light magenta
- 0x0f0b, // 14 - yellow
- 0x0f0f // 15 - white
-};
+ return errOK;
+}
-struct UpdateBlock {
- int x1, y1;
- int x2, y2;
-};
+int GfxMgr::initMachine() {
+ _vm->_clockCount = 0;
-static struct UpdateBlock update = {
- MAX_INT, MAX_INT, 0, 0
-};
+ return errOK;
+}
-GfxMgr::GfxMgr(AgiBase *vm) : _vm(vm) {
- _shakeH = NULL;
- _shakeV = NULL;
- _agipalFileNum = 0;
- _currentCursorPalette = 0; // cursor palette not set
+int GfxMgr::deinitMachine() {
+ return errOK;
}
+void GfxMgr::setRenderStartOffset(uint16 offsetY) {
+ if (offsetY >= (DISPLAY_HEIGHT - SCRIPT_HEIGHT))
+ error("invalid render start offset");
-//
-// Layer 4: 640x480? ================== User display
-// ^
-// | do_update(), put_block()
-// |
-// Layer 3: 640x480? ================== Framebuffer
-// ^
-// | flush_block(), put_pixels()
-// |
-// Layer 2: 320x200 ================== AGI engine screen (console), put_pixel()
-// |
-// Layer 1: 160x336 ================== AGI screen
-//
-// Upper half (160x168) of Layer 1 is for the normal 16 color & control line/priority info.
-// Lower half (160x168) of Layer 1 is for the 256 color information (Used with AGI256 & AGI256-2).
-//
-
-#define SHAKE_MAG 3
-
-void GfxMgr::shakeStart() {
- int i;
-
- if ((_shakeH = (uint8 *)malloc(GFX_WIDTH * SHAKE_MAG)) == NULL)
- return;
+ _renderStartOffsetY = offsetY;
+}
- if ((_shakeV = (uint8 *)malloc(SHAKE_MAG * (GFX_HEIGHT - SHAKE_MAG))) == NULL) {
- free(_shakeH);
- return;
+void GfxMgr::debugShowMap(int mapNr) {
+ switch (mapNr) {
+ case 0:
+ _activeScreen = _visualScreen;
+ break;
+ case 1:
+ _activeScreen = _priorityScreen;
+ break;
+ default:
+ break;
}
- for (i = 0; i < GFX_HEIGHT - SHAKE_MAG; i++) {
- memcpy(_shakeV + i * SHAKE_MAG, _agiScreen + i * GFX_WIDTH, SHAKE_MAG);
+ render_Block(0, 167, SCRIPT_WIDTH, SCRIPT_HEIGHT);
+}
+
+void GfxMgr::clear(byte color, byte priority) {
+ memset(_visualScreen, color, _pixels);
+ memset(_priorityScreen, priority, _pixels);
+}
+
+void GfxMgr::clearDisplay(byte color, bool copyToScreen) {
+ memset(_displayScreen, color, _displayPixels);
+
+ if (copyToScreen) {
+ g_system->copyRectToScreen(_displayScreen, DISPLAY_WIDTH, 0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT);
}
+}
+
+void GfxMgr::putPixel(int16 x, int16 y, byte drawMask, byte color, byte priority) {
+ int offset = y * SCRIPT_WIDTH + x;
- for (i = 0; i < SHAKE_MAG; i++) {
- memcpy(_shakeH + i * GFX_WIDTH, _agiScreen + i * GFX_WIDTH, GFX_WIDTH);
+ if (drawMask & GFX_SCREEN_MASK_VISUAL) {
+ _visualScreen[offset] = color;
+ }
+ if (drawMask & GFX_SCREEN_MASK_PRIORITY) {
+ _priorityScreen[offset] = priority;
}
}
-void GfxMgr::shakeScreen(int n) {
- int i;
+void GfxMgr::putPixelOnDisplay(int16 x, int16 y, byte color) {
+ int offset = y * DISPLAY_WIDTH + x;
- if (n == 0) {
- for (i = 0; i < (GFX_HEIGHT - SHAKE_MAG); i++) {
- memmove(&_agiScreen[GFX_WIDTH * i],
- &_agiScreen[GFX_WIDTH * (i + SHAKE_MAG) + SHAKE_MAG],
- GFX_WIDTH - SHAKE_MAG);
- }
- } else {
- for (i = GFX_HEIGHT - SHAKE_MAG - 1; i >= 0; i--) {
- memmove(&_agiScreen[GFX_WIDTH * (i + SHAKE_MAG) + SHAKE_MAG],
- &_agiScreen[GFX_WIDTH * i], GFX_WIDTH - SHAKE_MAG);
+ _displayScreen[offset] = color;
+}
+
+byte GfxMgr::getColor(int16 x, int16 y) {
+ int offset = y * SCRIPT_WIDTH + x;
+
+ return _visualScreen[offset];
+}
+
+byte GfxMgr::getPriority(int16 x, int16 y) {
+ int offset = y * SCRIPT_WIDTH + x;
+
+ return _priorityScreen[offset];
+}
+
+// used, when a control pixel is found
+// will search downwards and compare priority in case any is found
+bool GfxMgr::checkControlPixel(int16 x, int16 y, byte viewPriority) {
+ int offset = y * SCRIPT_WIDTH + x;
+ byte curPriority;
+
+ while (1) {
+ y++;
+ offset += SCRIPT_WIDTH;
+ if (y >= SCRIPT_HEIGHT) {
+ // end of screen, nothing but control pixels found
+ return true; // draw view pixel
}
+ curPriority = _priorityScreen[offset];
+ if (curPriority > 2) // valid priority found?
+ break;
+ }
+ if (curPriority <= viewPriority)
+ return true; // view priority is higher, draw
+ return false; // view priority is lower, don't draw
+}
+
+static byte CGA_MixtureColorTable[] = {
+ 0x00, 0x08, 0x04, 0x0C, 0x01, 0x09, 0x02, 0x05,
+ 0x0A, 0x0D, 0x06, 0x0E, 0x0B, 0x03, 0x07, 0x0F
+};
+
+byte GfxMgr::getCGAMixtureColor(byte color) {
+ return CGA_MixtureColorTable[color & 0x0F];
+}
+
+// Attention: y-coordinate points to the LOWER left!
+void GfxMgr::render_Block(int16 x, int16 y, int16 width, int16 height, bool copyToScreen) {
+ if (!render_Clip(x, y, width, height))
+ return;
+
+ switch (_vm->_renderMode) {
+ case RENDERMODE_CGA:
+ render_BlockCGA(x, y, width, height, copyToScreen);
+ break;
+ case RENDERMODE_EGA:
+ default:
+ render_BlockEGA(x, y, width, height, copyToScreen);
+ break;
+ }
+
+ if (copyToScreen) {
+ int16 upperY = y - height + 1 + _renderStartOffsetY;
+ g_system->copyRectToScreen(_displayScreen + upperY * DISPLAY_WIDTH + x * 2, DISPLAY_WIDTH, x * 2, upperY, width * 2, height);
}
}
-void GfxMgr::shakeEnd() {
- int i;
+bool GfxMgr::render_Clip(int16 &x, int16 &y, int16 &width, int16 &height, int16 clipAgainstWidth, int16 clipAgainstHeight) {
+ if ((x >= clipAgainstWidth) || ((x + width - 1) < 0) ||
+ (y < 0) || ((y - (height - 1)) >= clipAgainstHeight)) {
+ return false;
+ }
+
+ if ((y - height + 1) < 0)
+ height = y + 1;
- for (i = 0; i < GFX_HEIGHT - SHAKE_MAG; i++) {
- memcpy(_agiScreen + i * GFX_WIDTH, _shakeV + i * SHAKE_MAG, SHAKE_MAG);
+ if (y >= clipAgainstHeight) {
+ height -= y - (clipAgainstHeight - 1);
+ y = clipAgainstHeight - 1;
}
- for (i = 0; i < SHAKE_MAG; i++) {
- memcpy(_agiScreen + i * GFX_WIDTH, _shakeH + i * GFX_WIDTH, GFX_WIDTH);
+ if (x < 0) {
+ width += x;
+ x = 0;
}
- flushBlock(0, 0, GFX_WIDTH - 1, GFX_HEIGHT - 1);
+ if ((x + width - 1) >= clipAgainstWidth) {
+ width = clipAgainstWidth - x;
+ }
+ return true;
+}
+
+void GfxMgr::render_BlockEGA(int16 x, int16 y, int16 width, int16 height, bool copyToScreen) {
+ int offsetVisual = SCRIPT_WIDTH * y + x;
+ int offsetDisplay = (DISPLAY_WIDTH * (y + _renderStartOffsetY)) + x * 2;
+ int16 remainingWidth = width;
+ int16 remainingHeight = height;
+ byte curColor = 0;
+
+ while (remainingHeight) {
+ remainingWidth = width;
+ while (remainingWidth) {
+ curColor = _activeScreen[offsetVisual++];
+ _displayScreen[offsetDisplay++] = curColor;
+ _displayScreen[offsetDisplay++] = curColor;
+ remainingWidth--;
+ }
+ offsetVisual -= SCRIPT_WIDTH + width;
+ offsetDisplay -= DISPLAY_WIDTH + width * 2;
- free(_shakeV);
- free(_shakeH);
+ remainingHeight--;
+ }
}
-void GfxMgr::putTextCharacter(int l, int x, int y, unsigned char c, int fg, int bg, bool checkerboard, const uint8 *font) {
- int x1, y1, xx, yy, cc;
- const uint8 *p;
+void GfxMgr::render_BlockCGA(int16 x, int16 y, int16 width, int16 height, bool copyToScreen) {
+ int offsetVisual = SCRIPT_WIDTH * y + x;
+ int offsetDisplay = (DISPLAY_WIDTH * (y + _renderStartOffsetY)) + x * 2;
+ int16 remainingWidth = width;
+ int16 remainingHeight = height;
+ byte curColor = 0;
+
+ while (remainingHeight) {
+ remainingWidth = width;
+ while (remainingWidth) {
+ curColor = _activeScreen[offsetVisual++];
+ _displayScreen[offsetDisplay++] = curColor & 0x03; // we process CGA mixture
+ _displayScreen[offsetDisplay++] = curColor >> 2;
+ remainingWidth--;
+ }
+ offsetVisual -= SCRIPT_WIDTH + width;
+ offsetDisplay -= DISPLAY_WIDTH + width * 2;
+
+ remainingHeight--;
+ }
+}
- assert(font);
+void GfxMgr::transition_Amiga() {
+ uint16 screenPos = 1;
+ uint16 screenStepPos = 1;
+ int16 posY = 0, posX = 0;
+ int16 stepCount = 0;
+
+ // disable mouse while transition is taking place
+ if (_vm->_game.mouseEnabled) {
+ CursorMan.showMouse(false);
+ }
+
+ do {
+ if (screenPos & 1) {
+ screenPos = screenPos >> 1;
+ screenPos = screenPos ^ 0x3500; // 13568d
+ } else {
+ screenPos = screenPos >> 1;
+ }
- p = font + ((unsigned int)c * CHAR_LINES);
- for (y1 = 0; y1 < CHAR_LINES; y1++) {
- for (x1 = 0; x1 < CHAR_COLS; x1++) {
- xx = x + x1;
- yy = y + y1;
- cc = (*p & (1 << (7 - x1))) ? fg : bg;
- _agiScreen[xx + yy * GFX_WIDTH] = cc;
+ if ((screenPos < 13440) && (screenPos & 1)) {
+ screenStepPos = screenPos >> 1;
+ posY = screenStepPos / SCRIPT_WIDTH;
+ posX = screenStepPos - (posY * SCRIPT_WIDTH);
+
+ posY += _renderStartOffsetY; // adjust to only update the main area, not the status bar
+ posX *= 2; // adjust for display screen
+
+ screenStepPos = (screenStepPos * 2) + (_renderStartOffsetY * DISPLAY_WIDTH); // adjust here too for display screen
+ for (int16 multiPixel = 0; multiPixel < 4; multiPixel++) {
+ g_system->copyRectToScreen(_displayScreen + screenStepPos, DISPLAY_WIDTH, posX, posY, 2, 1);
+ screenStepPos += (0x1A40 * 2); // 6720d
+ posY += 42;
+ }
+ stepCount++;
+ if (stepCount == 220) {
+ // 30 times for the whole transition, so should take around 0.5 seconds
+ g_system->updateScreen();
+ g_system->delayMillis(16);
+ stepCount = 0;
+ }
}
+ } while (screenPos != 1);
- p++;
+ // Enable mouse again
+ if (_vm->_game.mouseEnabled) {
+ CursorMan.showMouse(true);
}
- // Simple checkerboard effect to simulate "greyed out" text.
- // This is what Sierra's interpreter does for things like menu items
- // that aren't selectable (such as separators). -- dsymonds
- if (checkerboard) {
- for (yy = y; yy < y + CHAR_LINES; yy++)
- for (xx = x + (~yy & 1); xx < x + CHAR_COLS; xx += 2)
- _agiScreen[xx + yy * GFX_WIDTH] = 15;
+ g_system->updateScreen();
+}
+
+// This transition code was not reverse engineered, but created based on the Amiga transition code
+// Atari ST definitely had a hi-res transition using the full resolution unlike the Amiga transition.
+void GfxMgr::transition_AtariSt() {
+ uint16 screenPos = 1;
+ uint16 screenStepPos = 1;
+ int16 posY = 0, posX = 0;
+ int16 stepCount = 0;
+
+ // disable mouse while transition is taking place
+ if (_vm->_game.mouseEnabled) {
+ CursorMan.showMouse(false);
+ }
+
+ do {
+ if (screenPos & 1) {
+ screenPos = screenPos >> 1;
+ screenPos = screenPos ^ 0x3500; // 13568d
+ } else {
+ screenPos = screenPos >> 1;
+ }
+
+ if ((screenPos < 13440) && (screenPos & 1)) {
+ screenStepPos = screenPos >> 1;
+ posY = screenStepPos / DISPLAY_WIDTH;
+ posX = screenStepPos - (posY * DISPLAY_WIDTH);
+
+ posY += _renderStartOffsetY; // adjust to only update the main area, not the status bar
+
+ screenStepPos = screenStepPos + (_renderStartOffsetY * DISPLAY_WIDTH); // adjust here too for display screen
+ for (int16 multiPixel = 0; multiPixel < 8; multiPixel++) {
+ g_system->copyRectToScreen(_displayScreen + screenStepPos, DISPLAY_WIDTH, posX, posY, 1, 1);
+ screenStepPos += 0x1a40; // 6720d
+ posY += 21;
+ }
+ stepCount++;
+ if (stepCount == 168) {
+ // 40 times for the whole transition, so should take around 0.7 seconds
+ // When using an Atari ST emulator, the transition seems to be even slower than this
+ // TODO: should get checked on real hardware
+ g_system->updateScreen();
+ g_system->delayMillis(16);
+ stepCount = 0;
+ }
+ }
+ } while (screenPos != 1);
+
+ // Enable mouse again
+ if (_vm->_game.mouseEnabled) {
+ CursorMan.showMouse(true);
}
- // FIXME: we don't want this when we're writing on the
- // console!
- flushBlock(x, y, x + CHAR_COLS - 1, y + CHAR_LINES - 1);
+ g_system->updateScreen();
}
-void GfxMgr::drawRectangle(int x1, int y1, int x2, int y2, int c) {
- int y, w, h;
- uint8 *p0;
-
- if (x1 >= GFX_WIDTH)
- x1 = GFX_WIDTH - 1;
- if (y1 >= GFX_HEIGHT)
- y1 = GFX_HEIGHT - 1;
- if (x2 >= GFX_WIDTH)
- x2 = GFX_WIDTH - 1;
- if (y2 >= GFX_HEIGHT)
- y2 = GFX_HEIGHT - 1;
-
- w = x2 - x1 + 1;
- h = y2 - y1 + 1;
- p0 = &_agiScreen[x1 + y1 * GFX_WIDTH];
- for (y = 0; y < h; y++) {
- memset(p0, c, w);
- p0 += GFX_WIDTH;
+// Attention: y coordinate is here supposed to be the upper one!
+void GfxMgr::block_save(int16 x, int16 y, int16 width, int16 height, byte *bufferPtr) {
+ int16 startOffset = y * SCRIPT_WIDTH + x;
+ int16 offset = startOffset;
+ int16 remainingHeight = height;
+ byte *curBufferPtr = bufferPtr;
+
+ //warning("block_save: %d, %d -> %d, %d", x, y, width, height);
+
+ while (remainingHeight) {
+ memcpy(curBufferPtr, _visualScreen + offset, width);
+ offset += SCRIPT_WIDTH;
+ curBufferPtr += width;
+ remainingHeight--;
+ }
+
+ remainingHeight = height;
+ offset = startOffset;
+ while (remainingHeight) {
+ memcpy(curBufferPtr, _priorityScreen + offset, width);
+ offset += SCRIPT_WIDTH;
+ curBufferPtr += width;
+ remainingHeight--;
}
}
-void GfxMgr::drawFrame(int x1, int y1, int x2, int y2, int c1, int c2) {
- int y, w;
- uint8 *p0;
+// Attention: y coordinate is here supposed to be the upper one!
+void GfxMgr::block_restore(int16 x, int16 y, int16 width, int16 height, byte *bufferPtr) {
+ int16 startOffset = y * SCRIPT_WIDTH + x;
+ int16 offset = startOffset;
+ int16 remainingHeight = height;
+ byte *curBufferPtr = bufferPtr;
- // top line
- w = x2 - x1 + 1;
- p0 = &_agiScreen[x1 + y1 * GFX_WIDTH];
- memset(p0, c1, w);
+ //warning("block_restore: %d, %d -> %d, %d", x, y, width, height);
- // bottom line
- p0 = &_agiScreen[x1 + y2 * GFX_WIDTH];
- memset(p0, c2, w);
+ while (remainingHeight) {
+ memcpy(_visualScreen + offset, curBufferPtr, width);
+ offset += SCRIPT_WIDTH;
+ curBufferPtr += width;
+ remainingHeight--;
+ }
- // side lines
- for (y = y1; y <= y2; y++) {
- _agiScreen[x1 + y * GFX_WIDTH] = c1;
- _agiScreen[x2 + y * GFX_WIDTH] = c2;
+ remainingHeight = height;
+ offset = startOffset;
+ while (remainingHeight) {
+ memcpy(_priorityScreen + offset, curBufferPtr, width);
+ offset += SCRIPT_WIDTH;
+ curBufferPtr += width;
+ remainingHeight--;
}
}
-void GfxMgr::drawBox(int x1, int y1, int x2, int y2, int color1, int color2, int m) {
- x1 += m;
- y1 += m;
- x2 -= m;
- y2 -= m;
+// Attention: uses visual screen coordinates!
+void GfxMgr::copyDisplayRectToScreen(int16 x, int16 y, int16 width, int16 height) {
+ g_system->copyRectToScreen(_displayScreen + y * DISPLAY_WIDTH + x, DISPLAY_WIDTH, x, y, width, height);
+}
+
+// coordinates are for visual screen, but are supposed to point somewhere inside the playscreen
+void GfxMgr::drawBox(int16 x, int16 y, int16 width, int16 height, byte backgroundColor, byte lineColor) {
+ drawRect(x, y, width, height, backgroundColor);
+ drawRect(x + 1, y - 1, width - 2, 1, lineColor);
+ drawRect(x + width - 2, y - 2, 1, height - 4, lineColor);
+ drawRect(x + 1, y - height + 2, width - 2, 1, lineColor);
+ drawRect(x + 1, y - 2, 1, height - 4, lineColor);
+}
+
+// coordinates for visual screen
+// attention: Clipping is done here against 160x200 instead of 160x168
+// Original interpreter didn't do any clipping, we do it for security.
+// Clipping against the regular script width/height must not be done,
+// because at least during the intro one message box goes beyond playscreen
+// Going beyond 160x168 will result in messageboxes not getting fully removed
+// In KQ4's case, the scripts clear the screen that's why it works.
+void GfxMgr::drawRect(int16 x, int16 y, int16 width, int16 height, byte color) {
+ if (!render_Clip(x, y, width, height, SCRIPT_WIDTH, DISPLAY_HEIGHT - _renderStartOffsetY))
+ return;
- drawRectangle(x1, y1, x2, y2, color1);
- drawFrame(x1 + 2, y1 + 2, x2 - 2, y2 - 2, color2, color2);
- flushBlock(x1, y1, x2, y2);
+ // coordinate translation: visual-screen -> display-screen
+ x = x * 2;
+ y = y + _renderStartOffsetY; // drawDisplayRect paints anywhere on the whole screen, our coordinate is within playscreen
+ width = width * 2; // width was given as visual width, we need display width
+ drawDisplayRect(x, y, width, height, color);
+}
+
+// coordinates are directly for display screen
+void GfxMgr::drawDisplayRect(int16 x, int16 y, int16 width, int16 height, byte color) {
+ switch (_vm->_renderMode) {
+ case RENDERMODE_CGA:
+ drawDisplayRectCGA(x, y, width, height, color);
+ break;
+ case RENDERMODE_EGA:
+ default:
+ drawDisplayRectEGA(x, y, width, height, color);
+ break;
+ }
+ int16 upperY = y - height + 1;
+ g_system->copyRectToScreen(_displayScreen + upperY * DISPLAY_WIDTH + x, DISPLAY_WIDTH, x, upperY, width, height);
}
-void GfxMgr::printCharacter(int x, int y, char c, int fg, int bg) {
- x *= CHAR_COLS;
- y *= CHAR_LINES;
+void GfxMgr::drawDisplayRectEGA(int16 x, int16 y, int16 width, int16 height, byte color) {
+ int offsetDisplay = (DISPLAY_WIDTH * y) + x;
+ int16 remainingHeight = height;
- putTextCharacter(0, x, y, c, fg, bg, false, _vm->getFontData());
- // redundant! already inside put_text_character!
- // flush_block (x, y, x + CHAR_COLS - 1, y + CHAR_LINES - 1);
+ while (remainingHeight) {
+ memset(_displayScreen + offsetDisplay, color, width);
+
+ offsetDisplay -= DISPLAY_WIDTH;
+ remainingHeight--;
+ }
}
-/**
- * Draw a default style button.
- * Swaps background and foreground color if button is in focus or being pressed.
- * @param x x coordinate of the button
- * @param y y coordinate of the button
- * @param a set if the button has focus
- * @param p set if the button is pressed
- * @param fgcolor foreground color of the button when it is neither in focus nor being pressed
- * @param bgcolor background color of the button when it is neither in focus nor being pressed
- */
-void GfxMgr::drawDefaultStyleButton(int x, int y, const char *s, int a, int p, int fgcolor, int bgcolor) {
- int textOffset = _vm->_defaultButtonStyle.getTextOffset(a > 0, p > 0);
- AgiTextColor color = _vm->_defaultButtonStyle.getColor (a > 0, p > 0, fgcolor, bgcolor);
- bool border = _vm->_defaultButtonStyle.getBorder (a > 0, p > 0);
+void GfxMgr::drawDisplayRectCGA(int16 x, int16 y, int16 width, int16 height, byte color) {
+ int offsetDisplay = (DISPLAY_WIDTH * y) + x;
+ int16 remainingHeight = height;
+ int16 remainingWidth = width;
+ byte CGAMixtureColor = getCGAMixtureColor(color);
+ byte *displayScreen = nullptr;
+
+ // we should never get an uneven width
+ assert((width & 1) == 0);
+
+ while (remainingHeight) {
+ remainingWidth = width;
+
+ // set up pointer
+ displayScreen = _displayScreen + offsetDisplay;
- rawDrawButton(x, y, s, color.fg, color.bg, border, textOffset);
+ while (remainingWidth) {
+ *displayScreen++ = CGAMixtureColor & 0x03;
+ *displayScreen++ = CGAMixtureColor >> 2;
+ remainingWidth -= 2;
+ }
+
+ offsetDisplay -= DISPLAY_WIDTH;
+ remainingHeight--;
+ }
}
-/**
- * Draw a button using the currently chosen style.
- * Amiga-style is used for the Amiga-rendering mode, PC-style is used otherwise.
- * @param x x coordinate of the button
- * @param y y coordinate of the button
- * @param hasFocus set if the button has focus
- * @param pressed set if the button is pressed
- * @param positive set if button is positive, otherwise button is negative (Only matters with Amiga-style buttons)
- * TODO: Make Amiga-style buttons a bit wider as they were in Amiga AGI games.
- */
-void GfxMgr::drawCurrentStyleButton(int x, int y, const char *label, bool hasFocus, bool pressed, bool positive) {
- int textOffset = _vm->_buttonStyle.getTextOffset(hasFocus, pressed);
- AgiTextColor color = _vm->_buttonStyle.getColor(hasFocus, pressed, positive);
- bool border = _vm->_buttonStyle.getBorder(hasFocus, pressed);
+// row + column are text-coordinates
+void GfxMgr::drawCharacter(int16 row, int16 column, byte character, byte foreground, byte background, bool disabledLook) {
+ int16 startX, startY;
+ int16 curX, curY;
+ const byte *fontData;
+ byte curByte = 0;
+ uint16 curBit;
+ byte curTransformXOR = 0;
+ byte curTransformOR = 0;
+
+ startX = column * FONT_DISPLAY_HEIGHT;
+ startY = row * FONT_DISPLAY_WIDTH;
+
+ // get font data of specified character
+ fontData = _vm->getFontData() + character * FONT_BYTES_PER_CHARACTER;
+
+ // Now figure out, if special handling needs to be done (for graphical mode only)
+ if (_vm->_game.gfxMode) {
+ if (background & 0x08) {
+ // invert enabled
+ background &= 0x07; // remove invert bit
+ curTransformXOR = 0xFF; // inverse all bits of the font
+ }
+ if (disabledLook) {
+ curTransformOR = 0xAA;
+ }
+ }
+
+ curBit = 0;
+ for (curY = 0; curY < FONT_DISPLAY_HEIGHT; curY++) {
+ for (curX = 0; curX < FONT_DISPLAY_WIDTH; curX++) {
+ if (!curBit) {
+ curByte = *fontData;
+ // do transformations in case they are needed (invert/disabled look)
+ curByte ^= curTransformXOR;
+ curByte |= curTransformOR;
+ fontData++;
+ curBit = 0x80;
+ }
+ if (curByte & curBit) {
+ putPixelOnDisplay(startX + curX, startY + curY, foreground);
+ } else {
+ putPixelOnDisplay(startX + curX, startY + curY, background);
+ }
+ curBit = curBit >> 1;
+ }
+ }
- rawDrawButton(x, y, label, color.fg, color.bg, border, textOffset);
+ copyDisplayRectToScreen(startX, startY, FONT_DISPLAY_WIDTH, FONT_DISPLAY_HEIGHT);
}
-void GfxMgr::rawDrawButton(int x, int y, const char *s, int fgcolor, int bgcolor, bool border, int textOffset) {
- int len = strlen(s);
- int x1, y1, x2, y2;
+#define SHAKE_VERTICAL_PIXELS 4
+#define SHAKE_HORIZONTAL_PIXELS 8
- x1 = x - 3;
- y1 = y - 3;
- x2 = x + CHAR_COLS * len + 2;
- y2 = y + CHAR_LINES + 2;
+// Sierra used some EGA port trickery to do it, we have to do it by copying pixels around
+void GfxMgr::shakeScreen(int16 repeatCount) {
+ int shakeNr, shakeCount;
+ uint8 *blackSpace;
- // Draw a filled rectangle that's larger than the button. Used for drawing
- // a border around the button as the button itself is drawn after this.
- drawRectangle(x1, y1, x2, y2, border ? BUTTON_BORDER : MSG_BOX_COLOR);
+ if ((blackSpace = (uint8 *)calloc(SHAKE_HORIZONTAL_PIXELS * DISPLAY_WIDTH, 1)) == NULL)
+ return;
- while (*s) {
- putTextCharacter(0, x + textOffset, y + textOffset, *s++, fgcolor, bgcolor, false, _vm->getFontData());
- x += CHAR_COLS;
+ shakeCount = repeatCount * 8; // effectively 4 shakes per repeat
+
+ // it's 4 pixels down and 8 pixels to the right
+ // and it's also filling the remaining space with black
+ for (shakeNr = 0; shakeNr < shakeCount; shakeNr++) {
+ if (shakeNr & 1) {
+ // move back
+ copyDisplayRectToScreen(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT);
+ } else {
+ g_system->copyRectToScreen(_displayScreen, DISPLAY_WIDTH, SHAKE_HORIZONTAL_PIXELS, SHAKE_VERTICAL_PIXELS, DISPLAY_WIDTH - SHAKE_HORIZONTAL_PIXELS, DISPLAY_HEIGHT - SHAKE_VERTICAL_PIXELS);
+ // additionally fill the remaining space with black
+ g_system->copyRectToScreen(blackSpace, DISPLAY_WIDTH, 0, 0, DISPLAY_WIDTH, SHAKE_VERTICAL_PIXELS);
+ g_system->copyRectToScreen(blackSpace, SHAKE_HORIZONTAL_PIXELS, 0, 0, SHAKE_HORIZONTAL_PIXELS, DISPLAY_HEIGHT);
+ }
+ g_system->updateScreen();
+ g_system->delayMillis(66); // Sierra waited for 4 V'Syncs, which is around 66 milliseconds
}
- x1 -= 2;
- y1 -= 2;
- x2 += 2;
- y2 += 2;
+ free(blackSpace);
+}
- flushBlock(x1, y1, x2, y2);
+void GfxMgr::updateScreen() {
+ g_system->updateScreen();
}
-int GfxMgr::testButton(int x, int y, const char *s) {
- int len = strlen(s);
- Common::Rect rect(x - 3, y - 3, x + CHAR_COLS * len + 3, y + CHAR_LINES + 3);
- return rect.contains(_vm->_mouse.x, _vm->_mouse.y);
+void GfxMgr::initPriorityTable() {
+ int16 priority, step;
+ int16 yPos = 0;
+
+ _priorityTableSet = false;
+ for (priority = 1; priority < 15; priority++) {
+ for (step = 0; step < 12; step++) {
+ _priorityTable[yPos++] = priority < 4 ? 4 : priority;
+ }
+ }
}
-void GfxMgr::putBlock(int x1, int y1, int x2, int y2) {
- gfxPutBlock(x1, y1, x2, y2);
+void GfxMgr::setPriorityTable(int16 priorityBase) {
+ int16 x, priorityY, priority;
+
+ _priorityTableSet = true;
+ x = (SCRIPT_HEIGHT - priorityBase) * SCRIPT_HEIGHT / 10;
+
+ for (priorityY = 0; priorityY < SCRIPT_HEIGHT; priorityY++) {
+ priority = (priorityY - priorityBase) < 0 ? 4 : (priorityY - priorityBase) * SCRIPT_HEIGHT / x + 5;
+ if (priority > 15)
+ priority = 15;
+ _priorityTable[priorityY] = priority;
+ }
}
-void GfxMgr::putScreen() {
- putBlock(0, 0, GFX_WIDTH - 1, GFX_HEIGHT - 1);
+// used for restoring
+void GfxMgr::setPriority(int16 yPos, int16 priority) {
+ assert(yPos < SCRIPT_HEIGHT);
+ _priorityTable[yPos] = priority;
}
-/*
- * Public functions
+/**
+ * Convert sprite priority to y value.
*/
+int16 GfxMgr::priorityToY(int16 priority) {
+ int16 currentY;
+
+ if (!_priorityTableSet) {
+ // priority table wasn't set by scripts? calculate directly
+ return (priority - 5) * 12 + 48;
+ }
+
+ // dynamic priority bands were introduced in 3.002.086 (effectively AGI3)
+ // It seems there was a glitch, that caused priority bands to not get calculated properly.
+ // It was caused by this function starting with Y = 168 instead of 167, which meant it always
+ // returned with 168 as result.
+ // This glitch is required in King's Quest 4 2.0, otherwise in room 54 ego will get drawn over
+ // the last dwarf, that enters the house.
+ // Dwarf is screen object 13 (view 152), gets fixed priority of 8, which would normally
+ // result in a Y of 101. Ego is priority (non-fixed) 8, which would mean that dwarf is
+ // drawn first, followed by ego, which would then draw ego over the dwarf.
+ // For more information see bug #1712585 (dwarf sprite priority)
+ //
+ // Priority bands were working properly in: 3.001.098 (Black Cauldron)
+ uint16 agiVersion = _vm->getVersion();
+
+ if (agiVersion <= 0x3086) {
+ return 168; // Buggy behavior, see above
+ }
+
+ currentY = 167;
+ while (_priorityTable[currentY] >= priority) {
+ currentY--;
+ if (currentY < 0) // Original AGI didn't do this, we abort in that case and return -1
+ break;
+ }
+ return currentY;
+}
+
+int16 GfxMgr::priorityFromY(int16 yPos) {
+ assert(yPos < SCRIPT_HEIGHT);
+ return _priorityTable[yPos];
+}
+
/**
* Initialize the color palette
@@ -800,18 +766,22 @@ void GfxMgr::putScreen() {
* @param fromBits Bits per source color component.
* @param toBits Bits per destination color component.
*/
-void GfxMgr::initPalette(const uint8 *p, uint colorCount, uint fromBits, uint toBits) {
+void GfxMgr::initPalette(uint8 *destPalette, const uint8 *paletteData, uint colorCount, uint fromBits, uint toBits) {
const uint srcMax = (1 << fromBits) - 1;
const uint destMax = (1 << toBits) - 1;
for (uint col = 0; col < colorCount; col++) {
for (uint comp = 0; comp < 3; comp++) { // Convert RGB components
- _palette[col * 3 + comp] = (p[col * 3 + comp] * destMax) / srcMax;
+ destPalette[col * 3 + comp] = (paletteData[col * 3 + comp] * destMax) / srcMax;
}
}
}
-void GfxMgr::gfxSetPalette() {
- g_system->getPaletteManager()->setPalette(_palette, 0, 256);
+void GfxMgr::setPalette(bool gfxModePalette) {
+ if (gfxModePalette) {
+ g_system->getPaletteManager()->setPalette(_paletteGfxMode, 0, 256);
+ } else {
+ g_system->getPaletteManager()->setPalette(_paletteTextMode, 0, 256);
+ }
}
//Gets AGIPAL Data
@@ -863,8 +833,8 @@ void GfxMgr::setAGIPal(int p0) {
_agipalFileNum = p0;
- initPalette(_agipalPalette);
- gfxSetPalette();
+ initPalette(_paletteGfxMode, _agipalPalette);
+ setPalette(true); // set gfx-mode palette
debug(1, "Using AGIPAL palette from '%s'", filename);
}
@@ -873,137 +843,33 @@ int GfxMgr::getAGIPalFileNum() {
return _agipalFileNum;
}
-// put a block onto the screen
-void GfxMgr::gfxPutBlock(int x1, int y1, int x2, int y2) {
- if (x1 >= GFX_WIDTH)
- x1 = GFX_WIDTH - 1;
- if (y1 >= GFX_HEIGHT)
- y1 = GFX_HEIGHT - 1;
- if (x2 >= GFX_WIDTH)
- x2 = GFX_WIDTH - 1;
- if (y2 >= GFX_HEIGHT)
- y2 = GFX_HEIGHT - 1;
-
- g_system->copyRectToScreen(_screen + y1 * 320 + x1, 320, x1, y1, x2 - x1 + 1, y2 - y1 + 1);
+void GfxMgr::initMouseCursor(MouseCursorData *mouseCursor, const byte *bitmapData, uint16 width, uint16 height, int hotspotX, int hotspotY) {
+ mouseCursor->bitmapData = bitmapData;
+ mouseCursor->width = width;
+ mouseCursor->height = height;
+ mouseCursor->hotspotX = hotspotX;
+ mouseCursor->hotspotY = hotspotY;
}
-/**
- * A black and white SCI-style arrow cursor (11x16).
- * 0 = Transparent.
- * 1 = Black (#000000 in 24-bit RGB).
- * 2 = White (#FFFFFF in 24-bit RGB).
- */
-static const byte sciMouseCursor[] = {
- 1,1,0,0,0,0,0,0,0,0,0,
- 1,2,1,0,0,0,0,0,0,0,0,
- 1,2,2,1,0,0,0,0,0,0,0,
- 1,2,2,2,1,0,0,0,0,0,0,
- 1,2,2,2,2,1,0,0,0,0,0,
- 1,2,2,2,2,2,1,0,0,0,0,
- 1,2,2,2,2,2,2,1,0,0,0,
- 1,2,2,2,2,2,2,2,1,0,0,
- 1,2,2,2,2,2,2,2,2,1,0,
- 1,2,2,2,2,2,2,2,2,2,1,
- 1,2,2,2,2,2,1,0,0,0,0,
- 1,2,1,0,1,2,2,1,0,0,0,
- 1,1,0,0,1,2,2,1,0,0,0,
- 0,0,0,0,0,1,2,2,1,0,0,
- 0,0,0,0,0,1,2,2,1,0,0,
- 0,0,0,0,0,0,1,2,2,1,0
-};
-
-#if 0
-/**
- * A black and white Apple IIGS style arrow cursor (9x11).
- * 0 = Transparent.
- * 1 = Black (#000000 in 24-bit RGB).
- * 2 = White (#FFFFFF in 24-bit RGB).
- */
-static const byte appleIIgsMouseCursor[] = {
- 2,2,0,0,0,0,0,0,0,
- 2,1,2,0,0,0,0,0,0,
- 2,1,1,2,0,0,0,0,0,
- 2,1,1,1,2,0,0,0,0,
- 2,1,1,1,1,2,0,0,0,
- 2,1,1,1,1,1,2,0,0,
- 2,1,1,1,1,1,1,2,0,
- 2,1,1,1,1,1,1,1,2,
- 2,1,1,2,1,1,2,2,0,
- 2,2,2,0,2,1,1,2,0,
- 0,0,0,0,0,2,2,2,0
-};
-#endif
-
-/**
- * RGB-palette for the black and white SCI and Apple IIGS arrow cursors.
- */
-static const byte sciMouseCursorPalette[] = {
- 0x00, 0x00, 0x00, // Black
- 0xFF, 0xFF, 0xFF // White
-};
-
-/**
- * An Amiga-style arrow cursor (8x11).
- * 0 = Transparent.
- * 1 = Black (#000000 in 24-bit RGB).
- * 2 = Red (#DE2021 in 24-bit RGB).
- * 3 = Light red (#FFCFAD in 24-bit RGB).
- */
-static const byte amigaMouseCursor[] = {
- 2,3,1,0,0,0,0,0,
- 2,2,3,1,0,0,0,0,
- 2,2,2,3,1,0,0,0,
- 2,2,2,2,3,1,0,0,
- 2,2,2,2,2,3,1,0,
- 2,2,2,2,2,2,3,1,
- 2,0,2,2,3,1,0,0,
- 0,0,0,2,3,1,0,0,
- 0,0,0,2,2,3,1,0,
- 0,0,0,0,2,3,1,0,
- 0,0,0,0,2,2,3,1
-};
+void GfxMgr::setMouseCursor(bool busy) {
+ MouseCursorData *mouseCursor = nullptr;
-/**
- * RGB-palette for the Amiga-style arrow cursor
- * and the Amiga-style busy cursor.
- */
-static const byte amigaMouseCursorPalette[] = {
- 0x00, 0x00, 0x00, // Black
- 0xDE, 0x20, 0x21, // Red
- 0xFF, 0xCF, 0xAD // Light red
-};
+ if (!busy) {
+ mouseCursor = &_mouseCursor;
+ } else {
+ mouseCursor = &_mouseCursorBusy;
+ }
-/**
- * An Amiga-style busy cursor showing an hourglass (13x16).
- * 0 = Transparent.
- * 1 = Black (#000000 in 24-bit RGB).
- * 2 = Red (#DE2021 in 24-bit RGB).
- * 3 = Light red (#FFCFAD in 24-bit RGB).
- */
-static const byte busyAmigaMouseCursor[] = {
- 1,1,1,1,1,1,1,1,1,1,1,1,1,
- 1,2,2,2,2,2,2,2,2,2,2,2,1,
- 1,2,2,2,2,2,2,2,2,2,2,2,1,
- 0,1,3,3,3,3,3,3,3,3,3,1,0,
- 0,0,1,3,3,3,3,3,3,3,1,0,0,
- 0,0,0,1,3,3,3,3,3,1,0,0,0,
- 0,0,0,0,1,3,3,3,1,0,0,0,0,
- 0,0,0,0,0,1,3,1,0,0,0,0,0,
- 0,0,0,0,0,1,3,1,0,0,0,0,0,
- 0,0,0,0,1,2,3,2,1,0,0,0,0,
- 0,0,0,1,2,2,3,2,2,1,0,0,0,
- 0,0,1,2,2,2,3,2,2,2,1,0,0,
- 0,1,2,2,2,3,3,3,2,2,2,1,0,
- 1,3,3,3,3,3,3,3,3,3,3,3,1,
- 1,3,3,3,3,3,3,3,3,3,3,3,1,
- 1,1,1,1,1,1,1,1,1,1,1,1,1
-};
+ if (mouseCursor) {
+ CursorMan.replaceCursor(mouseCursor->bitmapData, mouseCursor->width, mouseCursor->height, mouseCursor->hotspotX, mouseCursor->hotspotY, 0);
+ }
+}
+#if 0
void GfxMgr::setCursor(bool amigaStyleCursor, bool busy) {
if (busy) {
- CursorMan.replaceCursorPalette(amigaMouseCursorPalette, 1, ARRAYSIZE(amigaMouseCursorPalette) / 3);
- CursorMan.replaceCursor(busyAmigaMouseCursor, 13, 16, 7, 8, 0);
-
+ CursorMan.replaceCursorPalette(MOUSECURSOR_AMIGA_PALETTE, 1, ARRAYSIZE(MOUSECURSOR_AMIGA_PALETTE) / 3);
+ CursorMan.replaceCursor(MOUSECURSOR_AMIGA_BUSY, 13, 16, 7, 8, 0);
return;
}
@@ -1029,241 +895,6 @@ void GfxMgr::setCursorPalette(bool amigaStyleCursor) {
}
}
}
-
-/**
- * Initialize graphics device.
- *
- * @see deinit_video()
- */
-int GfxMgr::initVideo() {
- if (_vm->getFeatures() & (GF_AGI256 | GF_AGI256_2))
- initPalette(vgaPalette, 256, 8);
- else if (_vm->_renderMode == Common::kRenderEGA)
- initPalette(egaPalette);
- else if (_vm->_renderMode == Common::kRenderAmiga) {
- if (!ConfMan.getBool("altamigapalette")) {
- // Set the correct Amiga palette
- if (_vm->getVersion() < 0x2936)
- // TODO: This palette isn't used for Apple IIGS games yet, as
- // we don't set a separate render mode for them yet
- initPalette(amigaAgiPaletteV1, 16, 4);
- else if (_vm->getVersion() == 0x2936)
- initPalette(amigaAgiPaletteV2, 16, 4);
- else if (_vm->getVersion() > 0x2936)
- initPalette(amigaAgiPaletteV3, 16, 4);
- } else
- // Set the old common alternative Amiga palette
- initPalette(altAmigaPalette);
- } else
- error("initVideo: Unhandled render mode \"%s\"", Common::getRenderModeDescription(_vm->_renderMode));
-
- if ((_agiScreen = (uint8 *)calloc(GFX_WIDTH, GFX_HEIGHT)) == NULL)
- return errNotEnoughMemory;
-
- gfxSetPalette();
-
- setCursor(_vm->_renderMode == Common::kRenderAmiga);
-
- return errOK;
-}
-
-/**
- * Deinitialize graphics device.
- *
- * @see init_video()
- */
-int GfxMgr::deinitVideo() {
- free(_agiScreen);
-
- return errOK;
-}
-
-int GfxMgr::initMachine() {
- _screen = (unsigned char *)malloc(320 * 200);
- _vm->_clockCount = 0;
-
- return errOK;
-}
-
-int GfxMgr::deinitMachine() {
- free(_screen);
-
- return errOK;
-}
-
-/**
- * Write pixels on the output device.
- * This function writes a row of pixels on the output device. Only the
- * lower 4 bits of each pixel in the row will be used, making this
- * function suitable for use with rows from the AGI screen.
- * @param x x coordinate of the row start (AGI coord.)
- * @param y y coordinate of the row start (AGI coord.)
- * @param n number of pixels in the row
- * @param p pointer to the row start in the AGI screen (Always use sbuf16c as base, not sbuf256c)
- * FIXME: CGA rendering doesn't work correctly with AGI256 or AGI256-2.
- */
-void GfxMgr::putPixelsA(int x, int y, int n, uint8 *p) {
- const uint rShift = _vm->_debug.priority ? 4 : 0; // Priority information is in the top 4 bits of a byte taken from sbuf16c.
-
- // Choose the correct screen to read from. If AGI256 or AGI256-2 is used and we're not trying to show the priority information,
- // then choose the 256 color screen, otherwise choose the 16 color screen (Which also has the priority information).
- p += ((_vm->getFeatures() & (GF_AGI256 | GF_AGI256_2)) && !_vm->_debug.priority) ? FROM_SBUF16_TO_SBUF256_OFFSET : 0;
-
- if (_vm->_renderMode == Common::kRenderCGA) {
- for (x *= 2; n--; p++, x += 2) {
- register uint16 q = (cgaMap[(*p & 0xf0) >> 4] << 4) | cgaMap[*p & 0x0f];
- *(uint16 *)&_agiScreen[x + y * GFX_WIDTH] = (q >> rShift) & 0x0f0f;
- }
- } else {
- const uint16 mask = ((_vm->getFeatures() & (GF_AGI256 | GF_AGI256_2)) && !_vm->_debug.priority) ? 0xffff : 0x0f0f;
- for (x *= 2; n--; p++, x += 2) {
- register uint16 q = ((uint16)*p << 8) | *p;
- *(uint16 *)&_agiScreen[x + y * GFX_WIDTH] = (q >> rShift) & mask;
- }
- }
-}
-
-/**
- * Schedule blocks for blitting on the output device.
- * This function gets the coordinates of a block in the AGI screen and
- * schedule it to be updated in the output device.
- * @param x1 x coordinate of the upper left corner of the block (AGI coord.)
- * @param y1 y coordinate of the upper left corner of the block (AGI coord.)
- * @param x2 x coordinate of the lower right corner of the block (AGI coord.)
- * @param y2 y coordinate of the lower right corner of the block (AGI coord.)
- *
- * @see do_update()
- */
-void GfxMgr::scheduleUpdate(int x1, int y1, int x2, int y2) {
- if (x1 < update.x1)
- update.x1 = x1;
- if (y1 < update.y1)
- update.y1 = y1;
- if (x2 > update.x2)
- update.x2 = x2;
- if (y2 > update.y2)
- update.y2 = y2;
-}
-
-/**
- * Update scheduled blocks on the output device.
- * This function exposes the blocks scheduled for updating to the output
- * device. Blocks can be scheduled at any point of the AGI cycle.
- *
- * @see schedule_update()
- */
-void GfxMgr::doUpdate() {
- if (update.x1 <= update.x2 && update.y1 <= update.y2) {
- gfxPutBlock(update.x1, update.y1, update.x2, update.y2);
- }
-
- // reset update block variables
- update.x1 = MAX_INT;
- update.y1 = MAX_INT;
- update.x2 = 0;
- update.y2 = 0;
-
- g_system->updateScreen();
-}
-
-/**
- * Updates a block of the framebuffer with contents of the AGI engine screen.
- * This function updates a block in the output device with the contents of
- * the AGI engine screen, handling console transparency.
- * @param x1 x coordinate of the upper left corner of the block
- * @param y1 y coordinate of the upper left corner of the block
- * @param x2 x coordinate of the lower right corner of the block
- * @param y2 y coordinate of the lower right corner of the block
- *
- * @see flush_block_a()
- */
-void GfxMgr::flushBlock(int x1, int y1, int x2, int y2) {
- int y, w;
- uint8 *p0;
-
- scheduleUpdate(x1, y1, x2, y2);
-
- p0 = &_agiScreen[x1 + y1 * GFX_WIDTH];
- w = x2 - x1 + 1;
-
- for (y = y1; y <= y2; y++) {
- memcpy(_screen + 320 * y + x1, p0, w);
- p0 += GFX_WIDTH;
- }
-}
-
-/**
- * Updates a block of the framebuffer receiving AGI picture coordinates.
- * @param x1 x AGI picture coordinate of the upper left corner of the block
- * @param y1 y AGI picture coordinate of the upper left corner of the block
- * @param x2 x AGI picture coordinate of the lower right corner of the block
- * @param y2 y AGI picture coordinate of the lower right corner of the block
- *
- * @see flush_block()
- */
-void GfxMgr::flushBlockA(int x1, int y1, int x2, int y2) {
- //y1 += 8;
- //y2 += 8;
- flushBlock(DEV_X0(x1), DEV_Y(y1), DEV_X1(x2), DEV_Y(y2));
-}
-
-/**
- * Updates the framebuffer with contents of the AGI engine screen (console-aware).
- * This function updates the output device with the contents of the AGI
- * screen, handling console transparency.
- */
-void GfxMgr::flushScreen() {
- flushBlock(0, 0, GFX_WIDTH - 1, GFX_HEIGHT - 1);
-
- doUpdate();
-}
-
-/**
- * Clear the output device screen (console-aware).
- * This function clears the output device screen and updates the
- * output device. Contents of the AGI screen are left untouched. This
- * function can be used to simulate a switch to a text mode screen in
- * a graphic-only device.
- * @param c color to clear the screen
- */
-void GfxMgr::clearScreen(int c) {
- memset(_agiScreen, c, GFX_WIDTH * GFX_HEIGHT);
- flushScreen();
-}
-
-/**
- * Save a block of the AGI engine screen
- */
-void GfxMgr::saveBlock(int x1, int y1, int x2, int y2, uint8 *b) {
- uint8 *p0;
- int w, h;
-
- p0 = &_agiScreen[x1 + GFX_WIDTH * y1];
- w = x2 - x1 + 1;
- h = y2 - y1 + 1;
- while (h--) {
- memcpy(b, p0, w);
- b += w;
- p0 += GFX_WIDTH;
- }
-}
-
-/**
- * Restore a block of the AGI engine screen
- */
-void GfxMgr::restoreBlock(int x1, int y1, int x2, int y2, uint8 *b) {
- uint8 *p0;
- int w, h;
-
- p0 = &_agiScreen[x1 + GFX_WIDTH * y1];
- w = x2 - x1 + 1;
- h = y2 - y1 + 1;
- while (h--) {
- memcpy(p0, b, w);
- b += w;
- p0 += GFX_WIDTH;
- }
- flushBlock(x1, y1, x2, y2);
-}
+#endif
} // End of namespace Agi
diff --git a/engines/agi/graphics.h b/engines/agi/graphics.h
index 506a9d93d6..a89a7fdabc 100644
--- a/engines/agi/graphics.h
+++ b/engines/agi/graphics.h
@@ -27,6 +27,11 @@
namespace Agi {
+#define SCRIPT_WIDTH 160
+#define SCRIPT_HEIGHT 168
+#define DISPLAY_WIDTH 320
+#define DISPLAY_HEIGHT 200
+
#define GFX_WIDTH 320
#define GFX_HEIGHT 200
#define CHAR_COLS 8
@@ -34,66 +39,115 @@ namespace Agi {
class AgiEngine;
+enum GfxScreenMasks {
+ GFX_SCREEN_MASK_VISUAL = 1,
+ GFX_SCREEN_MASK_PRIORITY = 2,
+ GFX_SCREEN_MASK_ALL = GFX_SCREEN_MASK_VISUAL|GFX_SCREEN_MASK_PRIORITY
+};
+
+struct MouseCursorData {
+ const byte *bitmapData;
+ uint16 width;
+ uint16 height;
+ int hotspotX;
+ int hotspotY;
+};
+
class GfxMgr {
private:
AgiBase *_vm;
- uint8 _palette[256 * 3];
- uint8 *_agiScreen;
- unsigned char *_screen;
-
- uint8 *_shakeH, *_shakeV;
+ uint8 _paletteGfxMode[256 * 3];
+ uint8 _paletteTextMode[256 * 3];
uint8 _agipalPalette[16 * 3];
int _agipalFileNum;
- int _currentCursorPalette; // 0 - palette not set, 1 - PC, 2 - Amiga
-
-private:
- void rawDrawButton(int x, int y, const char *s, int fgcolor, int bgcolor, bool border, int textOffset);
public:
GfxMgr(AgiBase *vm);
- void gfxPutBlock(int x1, int y1, int x2, int y2);
-
- void putTextCharacter(int, int, int, unsigned char, int, int, bool checkerboard = false, const uint8 *font = fontData_Sierra);
- void shakeScreen(int);
- void shakeStart();
- void shakeEnd();
- void saveScreen();
- void restoreScreen();
-
int initVideo();
int deinitVideo();
- void scheduleUpdate(int, int, int, int);
- void doUpdate();
- void putScreen();
- void flushBlock(int, int, int, int);
- void flushBlockA(int, int, int, int);
- void putPixelsA(int, int, int, uint8 *);
- void flushScreen();
- void clearScreen(int);
- void clearConsoleScreen(int);
- void drawBox(int, int, int, int, int, int, int);
- void drawDefaultStyleButton(int, int, const char *, int, int, int fgcolor = 0, int bgcolor = 0);
- void drawCurrentStyleButton(int x, int y, const char *label, bool hasFocus, bool pressed = false, bool positive = true);
- int testButton(int, int, const char *);
- void drawRectangle(int, int, int, int, int);
- void saveBlock(int, int, int, int, uint8 *);
- void restoreBlock(int, int, int, int, uint8 *);
- void initPalette(const uint8 *p, uint colorCount = 16, uint fromBits = 6, uint toBits = 8);
+ void initPalette(uint8 *destPalette, const uint8 *paletteData, uint colorCount = 16, uint fromBits = 6, uint toBits = 8);
void setAGIPal(int);
int getAGIPalFileNum();
- void drawFrame(int x1, int y1, int x2, int y2, int c1, int c2);
+ void setPalette(bool GfxModePalette);
- void putBlock(int x1, int y1, int x2, int y2);
- void gfxSetPalette();
- void setCursor(bool amigaStyleCursor = false, bool busy = false);
- void setCursorPalette(bool amigaStylePalette = false);
+ void initMouseCursor(MouseCursorData *mouseCursor, const byte *bitmapData, uint16 width, uint16 height, int hotspotX, int hotspotY);
+ void setMouseCursor(bool busy = false);
- void printCharacter(int, int, char, int, int);
int initMachine();
int deinitMachine();
+
+ void setRenderStartOffset(uint16 offsetY);
+
+private:
+ uint _pixels;
+ //uint16 _displayWidth;
+ //uint16 _displayHeight;
+ uint _displayPixels;
+
+ byte *_activeScreen;
+ byte *_visualScreen; // 160x168
+ byte *_priorityScreen; // 160x168
+ byte *_displayScreen; // 320x200
+
+ bool _priorityTableSet;
+ uint8 _priorityTable[SCRIPT_HEIGHT]; /**< priority table */
+
+ MouseCursorData _mouseCursor;
+ MouseCursorData _mouseCursorBusy;
+
+ uint16 _renderStartOffsetY;
+
+public:
+ void debugShowMap(int mapNr);
+
+ void clear(byte color, byte priority);
+ void clearDisplay(byte color, bool copyToScreen = true);
+ void putPixel(int16 x, int16 y, byte drawMask, byte color, byte priority);
+ void putPixelOnDisplay(int16 x, int16 y, byte color);
+
+ byte getColor(int16 x, int16 y);
+ byte getPriority(int16 x, int16 y);
+ bool checkControlPixel(int16 x, int16 y, byte newPriority);
+
+ byte getCGAMixtureColor(byte color);
+
+ void render_Block(int16 x, int16 y, int16 width, int16 height, bool copyToScreen = true);
+ bool render_Clip(int16 &x, int16 &y, int16 &width, int16 &height, int16 clipAgainstWidth = SCRIPT_WIDTH, int16 clipAgainstHeight = SCRIPT_HEIGHT);
+
+private:
+ void render_BlockEGA(int16 x, int16 y, int16 width, int16 height, bool copyToScreen);
+ void render_BlockCGA(int16 x, int16 y, int16 width, int16 height, bool copyToScreen);
+
+public:
+ void transition_Amiga();
+ void transition_AtariSt();
+
+ void block_save(int16 x, int16 y, int16 width, int16 height, byte *bufferPtr);
+ void block_restore(int16 x, int16 y, int16 width, int16 height, byte *bufferPtr);
+
+ void copyDisplayRectToScreen(int16 x, int16 y, int16 width, int16 height);
+
+ void drawBox(int16 x, int16 y, int16 width, int16 height, byte backgroundColor, byte lineColor);
+ void drawRect(int16 x, int16 y, int16 width, int16 height, byte color);
+ void drawDisplayRect(int16 x, int16 y, int16 width, int16 height, byte color);
+private:
+ void drawDisplayRectEGA(int16 x, int16 y, int16 width, int16 height, byte color);
+ void drawDisplayRectCGA(int16 x, int16 y, int16 width, int16 height, byte color);
+
+public:
+ void drawCharacter(int16 row, int16 column, byte character, byte foreground, byte background, bool disabledLook);
+
+ void shakeScreen(int16 repeatCount);
+ void updateScreen();
+
+ void initPriorityTable();
+ void setPriorityTable(int16 priorityBase);
+ void setPriority(int16 yPos, int16 priority);
+ int16 priorityToY(int16 priority);
+ int16 priorityFromY(int16 yPos);
};
} // End of namespace Agi
diff --git a/engines/agi/inv.cpp b/engines/agi/inv.cpp
index f1e4e5094b..614b016463 100644
--- a/engines/agi/inv.cpp
+++ b/engines/agi/inv.cpp
@@ -22,221 +22,193 @@
#include "agi/agi.h"
#include "agi/graphics.h"
+#include "agi/inv.h"
+#include "agi/text.h"
#include "agi/keyboard.h"
+#include "agi/systemui.h"
namespace Agi {
-//
-// Messages and coordinates
-//
+InventoryMgr::InventoryMgr(AgiEngine *agi, GfxMgr *gfx, TextMgr *text, SystemUI *systemUI) {
+ _vm = agi;
+ _gfx = gfx;
+ _text = text;
+ _systemUI = systemUI;
+}
-#define NOTHING_X 16
-#define NOTHING_Y 3
-#define NOTHING_MSG "nothing"
+InventoryMgr::~InventoryMgr() {
+}
-#define ANY_KEY_X 4
-#define ANY_KEY_Y 24
-#define ANY_KEY_MSG "Press a key to return to the game"
+void InventoryMgr::getPlayerInventory() {
+ AgiGame game = _vm->_game;
+ int16 selectedInventoryItem = _vm->getVar(VM_VAR_SELECTED_INVENTORY_ITEM);
+ uint16 objectNr = 0;
+ int16 curRow = 2; // starting at position 2,1
+ int16 curColumn = 1;
+
+ _array.clear();
+ _activeItemNr = 0;
+
+ for (objectNr = 0; objectNr < game.numObjects; objectNr++) {
+ if (_vm->objectGetLocation(objectNr) == EGO_OWNED) {
+ // item is in the possession of ego, so add it to our internal list
+ if (objectNr == selectedInventoryItem) {
+ // it's the currently selected inventory item, remember that
+ _activeItemNr = _array.size();
+ }
-#define YOUHAVE_X 11
-#define YOUHAVE_Y 0
-#define YOUHAVE_MSG "You are carrying:"
+ InventoryEntry inventoryEntry;
-#define SELECT_X 2
-#define SELECT_Y 24
-#define SELECT_MSG "Press ENTER to select, ESC to cancel"
+ inventoryEntry.objectNr = objectNr;
+ inventoryEntry.name = _vm->objectName(objectNr);
+ inventoryEntry.row = curRow;
+ inventoryEntry.column = curColumn;
+ if (inventoryEntry.column > 1) {
+ // right side, adjust column accordingly
+ inventoryEntry.column -= strlen( inventoryEntry.name );
+ }
+ _array.push_back(inventoryEntry);
+
+ // go to next position
+ if (curColumn == 1) {
+ // current position is left side, go to right side
+ curColumn = 39;
+ } else {
+ // current position is right side, so go to left side again and new row
+ curColumn = 1;
+ curRow++;
+ }
+ }
+ }
-#define NOTHING_X_RU 16
-#define NOTHING_Y_RU 3
-#define NOTHING_MSG_RU "\xad\xa8\xe7\xa5\xa3\xae"
+ if (_array.size() == 0) {
+ // empty inventory
+ InventoryEntry inventoryEntry;
-#define ANY_KEY_X_RU 4
-#define ANY_KEY_Y_RU 24
-#define ANY_KEY_MSG_RU "\x8b\xee\xa1\xa0\xef \xaa\xab\xa0\xa2\xa8\xe8\xa0 - \xa2\xae\xa7\xa2\xe0\xa0\xe2 \xa2 \xa8\xa3\xe0\xe3."
+ inventoryEntry.objectNr = 0;
+ inventoryEntry.name = _systemUI->getInventoryTextNothing();
+ inventoryEntry.row = 2;
+ inventoryEntry.column = 19 - (strlen(inventoryEntry.name) / 2);
+ _array.push_back(inventoryEntry);
+ }
+}
-#define YOUHAVE_X_RU 11
-#define YOUHAVE_Y_RU 0
-#define YOUHAVE_MSG_RU " \x93 \xa2\xa0\xe1 \xa5\xe1\xe2\xec: "
+void InventoryMgr::drawAll() {
+ int16 inventoryCount = _array.size();
+ int16 inventoryNr = 0;
-#define SELECT_X_RU 2
-#define SELECT_Y_RU 24
-#define SELECT_MSG_RU "ENTER - \xa2\xeb\xa1\xe0\xa0\xe2\xec, ESC - \xae\xe2\xac\xa5\xad\xa8\xe2\xec."
+ _text->charPos_Set(0, 11);
+ _text->displayText(_systemUI->getInventoryTextYouAreCarrying());
-void AgiEngine::printItem(int n, int fg, int bg) {
- printText(objectName(_intobj[n]), 0, ((n % 2) ? 39 - strlen(objectName(_intobj[n])) : 1),
- (n / 2) + 2, 40, fg, bg);
+ for (inventoryNr = 0; inventoryNr < inventoryCount; inventoryNr++) {
+ drawItem(inventoryNr);
+ }
}
-int AgiEngine::findItem() {
- int r, c;
+void InventoryMgr::drawItem(int16 itemNr) {
+ if (itemNr == _activeItemNr) {
+ _text->charAttrib_Set(15, 0);
+ } else {
+ _text->charAttrib_Set(0, 15);
+ }
- r = _mouse.y / CHAR_LINES;
- c = _mouse.x / CHAR_COLS;
+ _text->charPos_Set(_array[itemNr].row, _array[itemNr].column);
+ // original interpreter used printf here
+ // this doesn't really make sense, because for length calculation it's using strlen without printf
+ // which means right-aligned inventory items on the right side would not be displayed properly
+ // in case printf-formatting was actually used
+ // I have to assume that no game uses this, because behavior in original interpreter would have been buggy.
+ _text->displayText(_array[itemNr].name);
+}
- debugC(6, kDebugLevelInventory, "r = %d, c = %d", r, c);
+void InventoryMgr::show() {
+ bool selectItems = false;
- if (r < 2)
- return -1;
+ // figure out current inventory of the player
+ getPlayerInventory();
- return (r - 2) * 2 + (c > 20);
-}
+ if (_vm->getflag(VM_FLAG_STATUS_SELECTS_ITEMS)) {
+ selectItems = true;
+ } else{
+ _activeItemNr = -1; // so that none is shown as active
+ }
-int AgiEngine::showItems() {
- unsigned int x, i;
+ drawAll();
- for (x = i = 0; x < _game.numObjects; x++) {
- if (objectGetLocation(x) == EGO_OWNED) {
- // add object to our list!
- _intobj[i] = x;
- printItem(i, STATUS_FG, STATUS_BG);
- i++;
- }
+ _text->charAttrib_Set(0, 15);
+ if (selectItems) {
+ _text->charPos_Set(24, 2);
+ _text->displayText(_systemUI->getInventoryTextSelectItems());
+ } else {
+ _text->charPos_Set(24, 4);
+ _text->displayText(_systemUI->getInventoryTextReturnToGame());
}
- if (i == 0) {
- switch (getLanguage()) {
- case Common::RU_RUS:
- printText(NOTHING_MSG_RU, 0, NOTHING_X_RU, NOTHING_Y_RU, 40, STATUS_FG, STATUS_BG);
- break;
- default:
- printText(NOTHING_MSG, 0, NOTHING_X, NOTHING_Y, 40, STATUS_FG, STATUS_BG);
- break;
- }
- }
+ if (selectItems) {
+ _vm->cycleInnerLoopActive(CYCLE_INNERLOOP_INVENTORY);
- return i;
-}
+ do {
+ _vm->mainCycle();
+ } while (_vm->cycleInnerLoopIsActive() && !(_vm->shouldQuit() || _vm->_restartGame));
-void AgiEngine::selectItems(int n) {
- int fsel = 0;
- bool exit_select = false;
-
- while (!exit_select && !(shouldQuit() || _restartGame)) {
- if (n > 0)
- printItem(fsel, STATUS_BG, STATUS_FG);
-
- switch (waitAnyKey()) {
- case KEY_ENTER:
- setvar(vSelItem, _intobj[fsel]);
- exit_select = true;
- break;
- case KEY_ESCAPE:
- setvar(vSelItem, 0xff);
- exit_select = true;
- break;
- case KEY_UP:
- if (fsel >= 2)
- fsel -= 2;
- break;
- case KEY_DOWN:
- if (fsel + 2 < n)
- fsel += 2;
- break;
- case KEY_LEFT:
- if (fsel % 2 == 1)
- fsel--;
- break;
- case KEY_RIGHT:
- if (fsel % 2 == 0 && fsel + 1 < n)
- fsel++;
- break;
- case BUTTON_LEFT:{
- int i = findItem();
- if (i >= 0 && i < n) {
- setvar(vSelItem, _intobj[fsel = i]);
- debugC(6, kDebugLevelInventory, "item found: %d", fsel);
- showItems();
- printItem(fsel, STATUS_BG, STATUS_FG);
- _gfx->doUpdate();
- exit_select = true;
- }
- break;
- }
- default:
- break;
+ if (_activeItemNr >= 0) {
+ // pass selected object number
+ _vm->setVar(VM_VAR_SELECTED_INVENTORY_ITEM, _array[_activeItemNr].objectNr);
+ } else {
+ // nothing was selected
+ _vm->setVar(VM_VAR_SELECTED_INVENTORY_ITEM, 0xff);
}
- if (!exit_select) {
- showItems();
- _gfx->doUpdate();
- }
+ } else {
+ // no selection is supposed to be possible, just wait for key and exit
+ _vm->waitAnyKey();
}
-
- debugC(6, kDebugLevelInventory, "selected: %d", fsel);
}
-/*
- * Public functions
- */
-
-/**
- * Display inventory items.
- */
-void AgiEngine::inventory() {
- int oldFg, oldBg;
- int n;
-
- // screen is white with black text
- oldFg = _game.colorFg;
- oldBg = _game.colorBg;
- _game.colorFg = 0;
- _game.colorBg = 15;
- _gfx->clearScreen(_game.colorBg);
-
- switch (getLanguage()) {
- case Common::RU_RUS:
- printText(YOUHAVE_MSG_RU, 0, YOUHAVE_X_RU, YOUHAVE_Y_RU, 40, STATUS_FG, STATUS_BG);
- break;
- default:
- printText(YOUHAVE_MSG, 0, YOUHAVE_X, YOUHAVE_Y, 40, STATUS_FG, STATUS_BG);
+void InventoryMgr::charPress(int16 newChar) {
+ switch (newChar) {
+ case AGI_KEY_ENTER: {
+ _vm->cycleInnerLoopInactive(); // exit show-loop
break;
}
- // FIXME: doesn't check if objects overflow off screen...
-
- _intobj = (uint8 *)malloc(4 + _game.numObjects);
- memset(_intobj, 0, (4 + _game.numObjects));
-
- n = showItems();
+ case AGI_KEY_ESCAPE: {
+ _vm->cycleInnerLoopInactive(); // exit show-loop
+ _activeItemNr = -1; // no item selected
+ break;
+ }
- switch (getLanguage()) {
- case Common::RU_RUS:
- if (getflag(fStatusSelectsItems)) {
- printText(SELECT_MSG_RU, 0, SELECT_X_RU, SELECT_Y_RU, 40, STATUS_FG, STATUS_BG);
- } else {
- printText(ANY_KEY_MSG_RU, 0, ANY_KEY_X_RU, ANY_KEY_Y_RU, 40, STATUS_FG, STATUS_BG);
- }
+ case AGI_KEY_UP:
+ changeActiveItem(-2);
break;
+ case AGI_KEY_DOWN:
+ changeActiveItem(+2);
+ break;
+ case AGI_KEY_LEFT:
+ changeActiveItem(-1);
+ break;
+ case AGI_KEY_RIGHT:
+ changeActiveItem(+1);
+ break;
+
default:
- if (getflag(fStatusSelectsItems)) {
- printText(SELECT_MSG, 0, SELECT_X, SELECT_Y, 40, STATUS_FG, STATUS_BG);
- } else {
- printText(ANY_KEY_MSG, 0, ANY_KEY_X, ANY_KEY_Y, 40, STATUS_FG, STATUS_BG);
- }
break;
}
+}
- _gfx->flushScreen();
-
- // If flag 13 is set, we want to highlight & select an item.
- // opon selection, put objnum in var 25. Then on esc put in
- // var 25 = 0xff.
-
- if (getflag(fStatusSelectsItems))
- selectItems(n);
-
- free(_intobj);
+void InventoryMgr::changeActiveItem(int16 direction) {
+ int16 orgItemNr = _activeItemNr;
- if (!getflag(fStatusSelectsItems))
- waitAnyKey();
+ _activeItemNr += direction;
- _gfx->clearScreen(0);
- writeStatus();
- _picture->showPic();
- _game.colorFg = oldFg;
- _game.colorBg = oldBg;
- _game.hasPrompt = 0;
- flushLines(_game.lineUserInput, 24);
+ if ((_activeItemNr >= 0) && (_activeItemNr < (int16)_array.size())) {
+ // within bounds
+ drawItem(orgItemNr);
+ drawItem(_activeItemNr);
+ } else {
+ // out of bounds, revert change
+ _activeItemNr = orgItemNr;
+ }
}
} // End of namespace Agi
diff --git a/engines/agi/inv.h b/engines/agi/inv.h
new file mode 100644
index 0000000000..0c89275b7c
--- /dev/null
+++ b/engines/agi/inv.h
@@ -0,0 +1,61 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef AGI_INV_H
+#define AGI_INV_H
+
+namespace Agi {
+
+struct InventoryEntry {
+ uint16 objectNr;
+ int16 row;
+ int16 column;
+ const char *name;
+};
+typedef Common::Array<InventoryEntry> InventoryArray;
+
+class InventoryMgr {
+private:
+ GfxMgr *_gfx;
+ TextMgr *_text;
+ AgiEngine *_vm;
+ SystemUI *_systemUI;
+
+ InventoryArray _array;
+ int16 _activeItemNr;
+
+public:
+ InventoryMgr(AgiEngine *agi, GfxMgr *gfx, TextMgr *text, SystemUI *systemUI);
+ ~InventoryMgr();
+
+ void getPlayerInventory();
+ void drawAll();
+ void drawItem(int16 itemNr);
+ void show();
+
+ void charPress(int16 newChar);
+ void changeActiveItem(int16 direction);
+};
+
+} // End of namespace Agi
+
+#endif /* AGI_INV_H */
diff --git a/engines/agi/keyboard.cpp b/engines/agi/keyboard.cpp
index 0aa521bdc8..c307b077b1 100644
--- a/engines/agi/keyboard.cpp
+++ b/engines/agi/keyboard.cpp
@@ -20,12 +20,13 @@
*
*/
+#include "common/events.h"
+
#include "agi/agi.h"
#include "agi/graphics.h"
#include "agi/keyboard.h"
-#ifdef __DS__
-#include "wordcompletion.h"
-#endif
+#include "agi/menu.h"
+#include "agi/text.h"
namespace Agi {
@@ -61,21 +62,230 @@ const uint8 scancodeTable[26] = {
44 // Z
};
-void AgiEngine::initWords() {
- _game.numEgoWords = 0;
-}
+void AgiEngine::processEvents() {
+ Common::Event event;
+ int key = 0;
-void AgiEngine::cleanInput() {
- while (_game.numEgoWords)
- free(_game.egoWords[--_game.numEgoWords].word);
-}
+ while (_eventMan->pollEvent(event)) {
+ switch (event.type) {
+ case Common::EVENT_PREDICTIVE_DIALOG: {
+ GUI::PredictiveDialog _predictiveDialog;
+ _predictiveDialog.runModal();
+#if 0
+ strcpy(_predictiveResult, _predictiveDialog.getResult());
+ if (strcmp(_predictiveResult, "")) {
+ if (_game.inputMode == INPUTMODE_NORMAL) {
+ //strcpy((char *)_game.inputBuffer, _predictiveResult);
+ //handleKeys(KEY_ENTER);
+ // TODO: repair predictive
+ } else if (_game.inputMode == INPUTMODE_GETSTRING) {
+ strcpy(_game.strings[_stringdata.str], _predictiveResult);
+ newInputMode(INPUTMODE_NORMAL);
+ //_gfx->printCharacter(_stringdata.x + strlen(_game.strings[_stringdata.str]) + 1,
+ // _stringdata.y, ' ', 15, 0);
+ } else if (_game.inputMode == INPUTMODE_NONE) {
+ for (int n = 0; _predictiveResult[n]; n++)
+ keyEnqueue(_predictiveResult[n]);
+ }
+ }
+#endif
+ /*
+ if (predictiveDialog()) {
+ if (_game.inputMode == INPUT_NORMAL) {
+ strcpy((char *)_game.inputBuffer, _predictiveResult);
+ handleKeys(KEY_ENTER);
+ } else if (_game.inputMode == INPUT_GETSTRING) {
+ strcpy(_game.strings[_stringdata.str], _predictiveResult);
+ newInputMode(INPUT_NORMAL);
+ _gfx->printCharacter(_stringdata.x + strlen(_game.strings[_stringdata.str]) + 1,
+ _stringdata.y, ' ', _game.colorFg, _game.colorBg);
+ } else if (_game.inputMode == INPUT_NONE) {
+ for (int n = 0; _predictiveResult[n]; n++)
+ keyEnqueue(_predictiveResult[n]);
+ }
+ }
+ */
+ }
+ break;
+ case Common::EVENT_LBUTTONDOWN:
+ if (_game.mouseEnabled) {
+ key = AGI_MOUSE_BUTTON_LEFT;
+ _mouse.button = kAgiMouseButtonLeft;
+ keyEnqueue(key);
+ _mouse.x = event.mouse.x;
+ _mouse.y = event.mouse.y;
+ }
+ break;
+ case Common::EVENT_RBUTTONDOWN:
+ if (_game.mouseEnabled) {
+ key = AGI_MOUSE_BUTTON_RIGHT;
+ _mouse.button = kAgiMouseButtonRight;
+ keyEnqueue(key);
+ _mouse.x = event.mouse.x;
+ _mouse.y = event.mouse.y;
+ }
+ break;
+ case Common::EVENT_WHEELUP:
+ if (_game.mouseEnabled) {
+ key = AGI_MOUSE_WHEEL_UP;
+ keyEnqueue(key);
+ }
+ break;
+ case Common::EVENT_WHEELDOWN:
+ if (_game.mouseEnabled) {
+ key = AGI_MOUSE_WHEEL_DOWN;
+ keyEnqueue(key);
+ }
+ break;
+ case Common::EVENT_MOUSEMOVE:
+ if (_game.mouseEnabled) {
+ _mouse.x = event.mouse.x;
+ _mouse.y = event.mouse.y;
+
+ if (!_game.mouseFence.isEmpty()) {
+ if (_mouse.x < _game.mouseFence.left)
+ _mouse.x = _game.mouseFence.left;
+ if (_mouse.x > _game.mouseFence.right)
+ _mouse.x = _game.mouseFence.right;
+ if (_mouse.y < _game.mouseFence.top)
+ _mouse.y = _game.mouseFence.top;
+ if (_mouse.y > _game.mouseFence.bottom)
+ _mouse.y = _game.mouseFence.bottom;
+
+ g_system->warpMouse(_mouse.x, _mouse.y);
+ }
+ }
+
+ break;
+ case Common::EVENT_LBUTTONUP:
+ case Common::EVENT_RBUTTONUP:
+ if (_game.mouseEnabled) {
+ _mouse.button = kAgiMouseButtonUp;
+ _mouse.x = event.mouse.x;
+ _mouse.y = event.mouse.y;
+ }
+ break;
+ case Common::EVENT_KEYDOWN:
+ if (event.kbd.hasFlags(Common::KBD_CTRL) && event.kbd.keycode == Common::KEYCODE_d) {
+ _console->attach();
+ break;
+ }
-void AgiEngine::getString(int x, int y, int len, int str) {
- newInputMode(INPUT_GETSTRING);
- _stringdata.x = x;
- _stringdata.y = y;
- _stringdata.len = len;
- _stringdata.str = str;
+ if ((event.kbd.ascii) && (event.kbd.ascii <= 0xFF)) {
+ // No special key, directly accept it
+ // Is ISO-8859-1, we need lower 128 characters only, which is plain ASCII, so no mapping required
+ key = event.kbd.ascii;
+
+ if (Common::isAlpha(key)) {
+ // Key is A-Z.
+ // Map Ctrl-A to 1, Ctrl-B to 2, etc.
+ if (event.kbd.flags & Common::KBD_CTRL) {
+ key = toupper(key) - 'A' + 1;
+ } else if (event.kbd.flags & Common::KBD_ALT) {
+ // Map Alt-A, Alt-B etc. to special scancode values according to an internal scancode table.
+ key = scancodeTable[toupper(key) - 'A'] << 8;
+ }
+ }
+ } else {
+ switch (key = event.kbd.keycode) {
+ case Common::KEYCODE_LEFT:
+ case Common::KEYCODE_KP4:
+ if (_allowSynthetic || !event.synthetic)
+ key = AGI_KEY_LEFT;
+ break;
+ case Common::KEYCODE_RIGHT:
+ case Common::KEYCODE_KP6:
+ if (_allowSynthetic || !event.synthetic)
+ key = AGI_KEY_RIGHT;
+ break;
+ case Common::KEYCODE_UP:
+ case Common::KEYCODE_KP8:
+ if (_allowSynthetic || !event.synthetic)
+ key = AGI_KEY_UP;
+ break;
+ case Common::KEYCODE_DOWN:
+ case Common::KEYCODE_KP2:
+ if (_allowSynthetic || !event.synthetic)
+ key = AGI_KEY_DOWN;
+ break;
+ case Common::KEYCODE_PAGEUP:
+ case Common::KEYCODE_KP9:
+ if (_allowSynthetic || !event.synthetic)
+ key = AGI_KEY_UP_RIGHT;
+ break;
+ case Common::KEYCODE_PAGEDOWN:
+ case Common::KEYCODE_KP3:
+ if (_allowSynthetic || !event.synthetic)
+ key = AGI_KEY_DOWN_RIGHT;
+ break;
+ case Common::KEYCODE_HOME:
+ case Common::KEYCODE_KP7:
+ if (_allowSynthetic || !event.synthetic)
+ key = AGI_KEY_UP_LEFT;
+ break;
+ case Common::KEYCODE_END:
+ case Common::KEYCODE_KP1:
+ if (_allowSynthetic || !event.synthetic)
+ key = AGI_KEY_DOWN_LEFT;
+ break;
+ case Common::KEYCODE_KP5:
+ key = AGI_KEY_STATIONARY;
+ break;
+ case Common::KEYCODE_F1:
+ key = AGI_KEY_F1;
+ break;
+ case Common::KEYCODE_F2:
+ key = AGI_KEY_F2;
+ break;
+ case Common::KEYCODE_F3:
+ key = AGI_KEY_F3;
+ break;
+ case Common::KEYCODE_F4:
+ key = AGI_KEY_F4;
+ break;
+ case Common::KEYCODE_F5:
+ key = AGI_KEY_F5;
+ break;
+ case Common::KEYCODE_F6:
+ key = AGI_KEY_F6;
+ break;
+ case Common::KEYCODE_F7:
+ key = AGI_KEY_F7;
+ break;
+ case Common::KEYCODE_F8:
+ key = AGI_KEY_F8;
+ break;
+ case Common::KEYCODE_F9:
+ key = AGI_KEY_F9;
+ break;
+ case Common::KEYCODE_F10:
+ key = AGI_KEY_F10;
+ break;
+ case Common::KEYCODE_F11:
+ key = AGI_KEY_F11;
+ break;
+ case Common::KEYCODE_F12:
+ key = AGI_KEY_F12;
+ break;
+ case Common::KEYCODE_KP_ENTER:
+ key = AGI_KEY_ENTER;
+ break;
+ default:
+ break;
+ }
+ }
+ if (key)
+ keyEnqueue(key);
+ break;
+
+ case Common::EVENT_KEYUP:
+ if (_egoHoldKey)
+ _game.screenObjTable[SCREENOBJECTS_EGO_ENTRY].direction = 0;
+
+ default:
+ break;
+ }
+ }
}
/**
@@ -97,54 +307,128 @@ int AgiEngine::doPollKeyboard() {
return key;
}
-int AgiEngine::handleController(int key) {
- VtEntry *v = &_game.viewTable[0];
- int i;
+int16 AgiEngine::getSpecialMenuControllerSlot() {
+ int16 controllerSlotESC = -1;
+ int16 controllerSlotSpecial = -1;
+
+ for (uint16 curMapping = 0; curMapping < MAX_CONTROLLER_KEYMAPPINGS; curMapping++) {
+ if (_game.controllerKeyMapping[curMapping].keycode == _game.specialMenuTriggerKey) {
+ if (controllerSlotSpecial < 0) {
+ controllerSlotSpecial = _game.controllerKeyMapping[curMapping].controllerSlot;
+ }
+ }
+ if (_game.controllerKeyMapping[curMapping].keycode == AGI_MENU_TRIGGER_PC) {
+ if (controllerSlotESC < 0) {
+ controllerSlotESC = _game.controllerKeyMapping[curMapping].controllerSlot;
+ }
+ }
+ }
+ if (controllerSlotSpecial >= 0) {
+ // special menu controller slot found
+ if (controllerSlotSpecial != controllerSlotESC) {
+ // not the same as the ESC slot (is the same in Manhunter AppleIIgs, we need to replace "pause"
+ if (controllerSlotSpecial >= 10) {
+ // slot needs to be at least 10
+ // Atari ST SQ1 maps the special key, but doesn't trigger any menu with it
+ // the controller slot in this case is 8.
+ return controllerSlotSpecial;
+ }
+ }
+ }
+ return -1;
+}
+
+bool AgiEngine::handleController(uint16 key) {
+ ScreenObjEntry *screenObjEgo = &_game.screenObjTable[SCREENOBJECTS_EGO_ENTRY];
+
+ if (key == 0) // nothing pressed
+ return false;
+
+ // This previously skipped processing, when ESC was pressed and called menu directly.
+ // This original approach was bad, because games check different flags before actually allowing the
+ // user to enter the menu. We checked a few common flags, like for example the availability of the prompt.
+ // But this stopped the user being able to enter the menu, when the original interpreter actually allowed it.
+ // We now instead implement this feature using another way for those platforms.
+ if (key == AGI_KEY_ESCAPE) {
+ // Escape pressed, user probably wants to trigger the menu
+ // For PC, just passing ASCII code for ESC will normally trigger a controller
+ // and the scripts will then trigger the menu
+ // For other platforms, ESC was handled by platforms to trigger "pause game" instead
+ // We need to change ESC to a platform specific code to make it work properly.
+ //
+ // There are exceptions though. Mixed Up Mother Goose on AppleIIgs for example actually sets up
+ // ESC for pause only. That's why we also check, if the key is actually mapped to a controller.
+ // For this special case, we actually replace the pause function with a menu trigger.
+ // Replacing "pause" all the time wouldn't work out as well, becaue games like KQ1 on Apple IIgs
+ // actually disable "pause" when ego has been killed, which means we wouldn't be able to access
+ // the menu anymore in that case.
+ if (_menu->isAvailable()) {
+ // menu is actually available
+ if (_game.specialMenuTriggerKey) {
+ int16 specialMenuControllerSlot = getSpecialMenuControllerSlot();
+
+ if (specialMenuControllerSlot >= 0) {
+ // menu trigger found, trigger it now
+ _game.controllerOccured[specialMenuControllerSlot] = true;
+ return true;
+ }
+ }
+ // Otherwise go on and look for the ESC controller
+ }
+ }
+
// AGI 3.149 games, The Black Cauldron and King's Quest 4 need KEY_ESCAPE to use menus
// Games with the GF_ESCPAUSE flag need KEY_ESCAPE to pause the game
- if (key == 0 ||
- (key == KEY_ESCAPE && getVersion() != 0x3149 && getGameID() != GID_BC && getGameID() != GID_KQ4 && !(getFeatures() & GF_ESCPAUSE)) )
- return false;
+ // (key == KEY_ESCAPE && getVersion() != 0x3149 && getGameID() != GID_BC && getGameID() != GID_KQ4 && !(getFeatures() & GF_ESCPAUSE)) )
+ // return false;
- if ((getGameID() == GID_MH1 || getGameID() == GID_MH2) && (key == KEY_ENTER) &&
- (_game.inputMode == INPUT_NONE)) {
+ if ((getGameID() == GID_MH1 || getGameID() == GID_MH2) && (key == AGI_KEY_ENTER) &&
+ (_game.inputMode == INPUTMODE_NONE)) {
key = 0x20; // Set Enter key to Space in Manhunter when there's no text input
}
debugC(3, kDebugLevelInput, "key = %04x", key);
- for (i = 0; i < MAX_CONTROLLERS; i++) {
- if (_game.controllers[i].keycode == key) {
- debugC(3, kDebugLevelInput, "event %d: key press", _game.controllers[i].controller);
- _game.controllerOccured[_game.controllers[i].controller] = true;
+ for (uint16 curMapping = 0; curMapping < MAX_CONTROLLER_KEYMAPPINGS; curMapping++) {
+ if (_game.controllerKeyMapping[curMapping].keycode == key) {
+ debugC(3, kDebugLevelInput, "event %d: key press", _game.controllerKeyMapping[curMapping].controllerSlot);
+ _game.controllerOccured[_game.controllerKeyMapping[curMapping].controllerSlot] = true;
return true;
}
}
- if (key == BUTTON_LEFT) {
- if ((getflag(fMenusWork) || (getFeatures() & GF_MENUS)) && _mouse.y <= CHAR_LINES) {
- newInputMode(INPUT_MENU);
- return true;
- }
+ if (key == AGI_MOUSE_BUTTON_LEFT) {
+ // call mouse when click is done on status bar
+ // TODO
+ // This should be done in a better way as in simulate ESC key
+ // Sierra seems to have hardcoded it in some way, but we would have to verify, what flags
+ // they checked. The previous way wasn't accurate. Mouse support for menu is missing atm anyway.
+ //if ((getflag(VM_FLAG_MENUS_WORK) || (getFeatures() & GF_MENUS)) && _mouse.y <= CHAR_LINES) {
+ // newInputMode(INPUTMODE_MENU);
+ // return true;
+ //}
}
// Show predictive dialog if the user clicks on input area
- if (key == BUTTON_LEFT &&
- (int)_mouse.y >= _game.lineUserInput * CHAR_LINES &&
- (int)_mouse.y <= (_game.lineUserInput + 1) * CHAR_LINES) {
+ if (key == AGI_MOUSE_BUTTON_LEFT &&
+ (int)_mouse.y >= _text->promptRow_Get() * FONT_DISPLAY_HEIGHT &&
+ (int)_mouse.y <= (_text->promptRow_Get() + 1) * FONT_DISPLAY_HEIGHT) {
GUI::PredictiveDialog _predictiveDialog;
_predictiveDialog.runModal();
+#if 0
strcpy(_predictiveResult, _predictiveDialog.getResult());
if (strcmp(_predictiveResult, "")) {
- if (_game.inputMode == INPUT_NONE) {
+ if (_game.inputMode == INPUTMODE_NONE) {
for (int n = 0; _predictiveResult[n]; n++)
keyEnqueue(_predictiveResult[n]);
} else {
- strcpy((char *)_game.inputBuffer, _predictiveResult);
- handleKeys(KEY_ENTER);
+ //strcpy((char *)_game.inputBuffer, _predictiveResult);
+ //handleKeys(KEY_ENTER);
+ // TODO
}
}
+#endif
/*
if (predictiveDialog()) {
if (_game.inputMode == INPUT_NONE) {
@@ -160,57 +444,73 @@ int AgiEngine::handleController(int key) {
}
if (_game.playerControl) {
- int d = 0;
+ int16 newDirection = 0;
- if (!KEY_ASCII(key)) {
- switch (key) {
- case KEY_UP:
- d = 1;
- break;
- case KEY_DOWN:
- d = 5;
- break;
- case KEY_LEFT:
- d = 7;
- break;
- case KEY_RIGHT:
- d = 3;
- break;
- case KEY_UP_RIGHT:
- d = 2;
- break;
- case KEY_DOWN_RIGHT:
- d = 4;
- break;
- case KEY_UP_LEFT:
- d = 8;
- break;
- case KEY_DOWN_LEFT:
- d = 6;
- break;
- }
+ switch (key) {
+ case AGI_KEY_UP:
+ newDirection = 1;
+ break;
+ case AGI_KEY_DOWN:
+ newDirection = 5;
+ break;
+ case AGI_KEY_LEFT:
+ newDirection = 7;
+ break;
+ case AGI_KEY_RIGHT:
+ newDirection = 3;
+ break;
+ case AGI_KEY_UP_RIGHT:
+ newDirection = 2;
+ break;
+ case AGI_KEY_DOWN_RIGHT:
+ newDirection = 4;
+ break;
+ case AGI_KEY_UP_LEFT:
+ newDirection = 8;
+ break;
+ case AGI_KEY_DOWN_LEFT:
+ newDirection = 6;
+ break;
+ default:
+ break;
}
if (!(getFeatures() & GF_AGIMOUSE)) {
// Handle mouse button events
- if (key == BUTTON_LEFT) {
- if (getGameID() == GID_PQ1 && _game.vars[vCurRoom] == 116) {
- // WORKAROUND: Special handling for mouse clicks in the newspaper
- // screen of PQ1. Fixes bug #3018770.
- d = 3; // fake a right arrow key (next page)
- } else {
- // Click-to-walk mouse interface
- v->flags |= fAdjEgoXY;
- v->parm1 = WIN_TO_PIC_X(_mouse.x);
- v->parm2 = WIN_TO_PIC_Y(_mouse.y);
- return true;
+ if (!_game.mouseHidden) {
+ if (key == AGI_MOUSE_BUTTON_LEFT) {
+ if (getGameID() == GID_PQ1 && _game.vars[VM_VAR_CURRENT_ROOM] == 116) {
+ // WORKAROUND: Special handling for mouse clicks in the newspaper
+ // screen of PQ1. Fixes bug #3018770.
+ newDirection = 3; // fake a right arrow key (next page)
+
+ } else {
+ // Click-to-walk mouse interface
+ //v->flags |= fAdjEgoXY;
+ // setting fAdjEgoXY here will at least break "climbing the log" in SQ2
+ // in case you walked to the log by using the mouse, so don't!!!
+ int16 egoDestinationX = _mouse.x;
+ int16 egoDestinationY = _mouse.y;
+ adjustPosToGameScreen(egoDestinationX, egoDestinationY);
+
+ screenObjEgo->motionType = kMotionEgo;
+ if (egoDestinationX < (screenObjEgo->xSize / 2)) {
+ screenObjEgo->move_x = -1;
+ } else {
+ screenObjEgo->move_x = egoDestinationX - (screenObjEgo->xSize / 2);
+ }
+ screenObjEgo->move_y = egoDestinationY;
+ screenObjEgo->move_stepSize = screenObjEgo->stepSize;
+ return true;
+ }
}
}
}
- if (d || key == KEY_STATIONARY) {
- v->flags &= ~fAdjEgoXY;
- v->direction = v->direction == d ? 0 : d;
+ if (newDirection || key == AGI_KEY_STATIONARY) {
+ screenObjEgo->flags &= ~fAdjEgoXY;
+ screenObjEgo->direction = screenObjEgo->direction == newDirection ? 0 : newDirection;
+ screenObjEgo->motionType = kMotionNormal;
return true;
}
}
@@ -218,205 +518,22 @@ int AgiEngine::handleController(int key) {
return false;
}
-void AgiEngine::handleGetstring(int key) {
- static int pos = 0; // Cursor position
- static char buf[40];
-
- if (KEY_ASCII(key) == 0)
- return;
-
- debugC(3, kDebugLevelInput, "handling key: %02x", key);
-
- switch (key) {
- case BUTTON_LEFT:
- if ((int)_mouse.y >= _stringdata.y * CHAR_LINES &&
- (int)_mouse.y <= (_stringdata.y + 1) * CHAR_LINES) {
- GUI::PredictiveDialog _predictiveDialog;
- _predictiveDialog.runModal();
- strcpy(_predictiveResult, _predictiveDialog.getResult());
- if (strcmp(_predictiveResult, "")) {
- strcpy(_game.strings[_stringdata.str], _predictiveResult);
- newInputMode(INPUT_NORMAL);
- _gfx->printCharacter(_stringdata.x + strlen(_game.strings[_stringdata.str]) + 1,
- _stringdata.y, ' ', _game.colorFg, _game.colorBg);
- return;
- }
- /*
- if (predictiveDialog()) {
- strcpy(_game.strings[_stringdata.str], _predictiveResult);
- newInputMode(INPUT_NORMAL);
- _gfx->printCharacter(_stringdata.x + strlen(_game.strings[_stringdata.str]) + 1,
- _stringdata.y, ' ', _game.colorFg, _game.colorBg);
- return;
- }
- */
- }
- break;
- case KEY_ENTER:
- debugC(3, kDebugLevelInput, "KEY_ENTER");
- _game.hasPrompt = 0;
- buf[pos] = 0;
-
- strcpy(_game.strings[_stringdata.str], buf);
- debugC(3, kDebugLevelInput, "buffer=[%s]", buf);
- buf[pos = 0] = 0;
-
- newInputMode(INPUT_NORMAL);
- _gfx->printCharacter(_stringdata.x + strlen(_game.strings[_stringdata.str]) + 1,
- _stringdata.y, ' ', _game.colorFg, _game.colorBg);
- return;
- case KEY_ESCAPE:
- debugC(3, kDebugLevelInput, "KEY_ESCAPE");
- _game.hasPrompt = 0;
- buf[pos = 0] = 0;
-
- strcpy(_game.strings[_stringdata.str], buf);
- newInputMode(INPUT_NORMAL);
-
- // newInputMode(INPUT_MENU);
- break;
- case KEY_BACKSPACE: // 0x08
- if (!pos)
- break;
-
- _gfx->printCharacter(_stringdata.x + (pos + 1), _stringdata.y,
- ' ', _game.colorFg, _game.colorBg);
- pos--;
- buf[pos] = 0;
- break;
- default:
- if (key < 0x20 || key > 0x7f)
- break;
-
- if (pos >= _stringdata.len)
- break;
-
- buf[pos++] = key;
- buf[pos] = 0;
-
- // Echo
- _gfx->printCharacter(_stringdata.x + pos, _stringdata.y, buf[pos - 1],
- _game.colorFg, _game.colorBg);
-
- break;
- }
-
- // print cursor
- _gfx->printCharacter(_stringdata.x + pos + 1, _stringdata.y,
- (char)_game.cursorChar, _game.colorFg, _game.colorBg);
-}
-
-void AgiEngine::handleKeys(int key) {
- uint8 *p = NULL;
- int c = 0;
- static uint8 formattedEntry[40];
- int l = _game.lineUserInput;
- int fg = _game.colorFg, bg = _game.colorBg;
- int promptLength = strlen(agiSprintf(_game.strings[0]));
-
- setvar(vWordNotFound, 0);
-
- debugC(3, kDebugLevelInput, "handling key: %02x", key);
-
- switch (key) {
- case KEY_ENTER:
- debugC(3, kDebugLevelInput, "KEY_ENTER");
- _game.keypress = 0;
-
- // Remove all leading spaces
- for (p = _game.inputBuffer; *p && *p == 0x20; p++)
- ;
-
- // Copy to internal buffer
- for (; *p && c < 40-1; p++) {
- // Squash spaces
- if (*p == 0x20 && *(p + 1) == 0x20) {
- p++;
- continue;
- }
- formattedEntry[c++] = tolower(*p);
- }
- formattedEntry[c++] = 0;
-
- // Handle string only if it's not empty
- if (formattedEntry[0]) {
- strcpy((char *)_game.echoBuffer, (const char *)_game.inputBuffer);
- strcpy(_lastSentence, (const char *)formattedEntry);
- dictionaryWords(_lastSentence);
- }
-
- // Clear to start a new line
- _game.hasPrompt = 0;
- _game.inputBuffer[_game.cursorPos = 0] = 0;
- debugC(3, kDebugLevelInput | kDebugLevelText, "clear lines");
- clearLines(l, l + 1, bg);
- flushLines(l, l + 1);
-#ifdef __DS__
- DS::findWordCompletions((char *) _game.inputBuffer);
-#endif
-
- break;
- case KEY_ESCAPE:
- debugC(3, kDebugLevelInput, "KEY_ESCAPE");
- newInputMode(INPUT_MENU);
- break;
- case KEY_BACKSPACE:
- // Ignore backspace at start of line
- if (_game.cursorPos == 0)
- break;
-
- // erase cursor
- _gfx->printCharacter(_game.cursorPos + promptLength, l, ' ', fg, bg);
- _game.inputBuffer[--_game.cursorPos] = 0;
-
- // Print cursor
- _gfx->printCharacter(_game.cursorPos + promptLength, l, _game.cursorChar, fg, bg);
-
-#ifdef __DS__
- DS::findWordCompletions((char *) _game.inputBuffer);
-#endif
- break;
- default:
- // Ignore invalid keystrokes
- if (key < 0x20 || key > 0x7f)
- break;
-
- // Maximum input size reached
- if (_game.cursorPos >= getvar(vMaxInputChars))
- break;
-
- _game.inputBuffer[_game.cursorPos++] = key;
- _game.inputBuffer[_game.cursorPos] = 0;
-
-#ifdef __DS__
- DS::findWordCompletions((char *) _game.inputBuffer);
-#endif
-
- // echo
- _gfx->printCharacter(_game.cursorPos + promptLength - 1, l, _game.inputBuffer[_game.cursorPos - 1], fg, bg);
-
- // Print cursor
- _gfx->printCharacter(_game.cursorPos + promptLength, l, _game.cursorChar, fg, bg);
- break;
- }
-}
-
int AgiEngine::waitKey() {
int key = 0;
clearKeyQueue();
debugC(3, kDebugLevelInput, "waiting...");
- while (!(shouldQuit() || _restartGame || getflag(fRestoreJustRan))) {
+ while (!(shouldQuit() || _restartGame || getflag(VM_FLAG_RESTORE_JUST_RAN))) {
pollTimer();
key = doPollKeyboard();
- if (key == KEY_ENTER || key == KEY_ESCAPE || key == BUTTON_LEFT)
+ if (key == AGI_KEY_ENTER || key == AGI_KEY_ESCAPE || key == AGI_MOUSE_BUTTON_LEFT)
break;
pollTimer();
updateTimer();
- _gfx->doUpdate();
+ g_system->updateScreen();
}
// Have to clear it as original did not set this variable, and we do it in doPollKeyboard()
@@ -437,7 +554,7 @@ int AgiEngine::waitAnyKey() {
key = doPollKeyboard();
if (key)
break;
- _gfx->doUpdate();
+ g_system->updateScreen();
}
// Have to clear it as original did not set this variable, and we do it in doPollKeyboard()
diff --git a/engines/agi/keyboard.h b/engines/agi/keyboard.h
index 89d6a89ce3..e5cf955a07 100644
--- a/engines/agi/keyboard.h
+++ b/engines/agi/keyboard.h
@@ -46,49 +46,50 @@ public:
}
};
-// QNX4 has a KEY_DOWN defined which we don't need to care about
-#undef KEY_DOWN
-
-// Allegro defines these
-#undef KEY_BACKSPACE
-#undef KEY_ENTER
-#undef KEY_LEFT
-#undef KEY_RIGHT
-#undef KEY_UP
-#undef KEY_PGUP
-#undef KEY_PGDN
-#undef KEY_HOME
-#undef KEY_END
-
-#define KEY_BACKSPACE 0x08
-#define KEY_ESCAPE 0x1B
-#define KEY_ENTER 0x0D
-#define KEY_UP 0x4800
-#define KEY_DOWN 0x5000
-#define KEY_LEFT 0x4B00
-#define KEY_STATIONARY 0x4C00
-#define KEY_RIGHT 0x4D00
-
-#define KEY_DOWN_LEFT 0x4F00
-#define KEY_DOWN_RIGHT 0x5100
-#define KEY_UP_LEFT 0x4700
-#define KEY_UP_RIGHT 0x4900
-
-#define KEY_STATUSLN 0xd900 // F11
-#define KEY_PRIORITY 0xda00 // F12
-
-#define KEY_PGUP 0x4900 // Page Up (fixed by Ziv Barber)
-#define KEY_PGDN 0x5100 // Page Down
-#define KEY_HOME 0x4700 // Home
-#define KEY_END 0x4f00 // End *
-
-#define BUTTON_LEFT 0xF101 // Left mouse button
-#define BUTTON_RIGHT 0xF202 // Right mouse button
-#define WHEEL_UP 0xF203 // Mouse wheel up
-#define WHEEL_DOWN 0xF204 // Mouse wheel down
-
-#define KEY_SCAN(k) (k >> 8)
-#define KEY_ASCII(k) (k & 0xff)
+#define AGI_KEY_BACKSPACE 0x08
+#define AGI_KEY_ESCAPE 0x1B
+#define AGI_KEY_ENTER 0x0D
+#define AGI_KEY_UP 0x4800
+#define AGI_KEY_DOWN 0x5000
+#define AGI_KEY_LEFT 0x4B00
+#define AGI_KEY_STATIONARY 0x4C00
+#define AGI_KEY_RIGHT 0x4D00
+
+#define AGI_KEY_DOWN_LEFT 0x4F00
+#define AGI_KEY_DOWN_RIGHT 0x5100
+#define AGI_KEY_UP_LEFT 0x4700
+#define AGI_KEY_UP_RIGHT 0x4900
+
+#define AGI_KEY_F1 0x3B00
+#define AGI_KEY_F2 0x3C00
+#define AGI_KEY_F3 0x3D00
+#define AGI_KEY_F4 0x3E00
+#define AGI_KEY_F5 0x3F00
+#define AGI_KEY_F6 0x4000
+#define AGI_KEY_F7 0x4000
+#define AGI_KEY_F8 0x4100
+#define AGI_KEY_F9 0x4200
+#define AGI_KEY_F10 0x4300
+#define AGI_KEY_F11 0xd900 // F11
+#define AGI_KEY_F12 0xda00 // F12
+
+#define AGI_KEY_PAGE_UP 0x4900 // Page Up (fixed by Ziv Barber)
+#define AGI_KEY_PAGE_DOWN 0x5100 // Page Down
+#define AGI_KEY_HOME 0x4700 // Home
+#define AGI_KEY_END 0x4f00 // End *
+
+#define AGI_MOUSE_BUTTON_LEFT 0xF101 // Left mouse button
+#define AGI_MOUSE_BUTTON_RIGHT 0xF202 // Right mouse button
+#define AGI_MOUSE_WHEEL_UP 0xF203 // Mouse wheel up
+#define AGI_MOUSE_WHEEL_DOWN 0xF204 // Mouse wheel down
+
+// special menu triggers
+// Attention: at least Mixed Up Mother Goose on Apple IIgs actually hooks ESC for menu only
+// Which is why we have to check, if the corresponding trigger is hooked before changing it
+// And otherwise simply use the regular ESC.
+#define AGI_MENU_TRIGGER_PC 0x001B // will trigger menu for PC
+#define AGI_MENU_TRIGGER_APPLE2GS 0x0301 // will trigger menu for AppleIIgs + Amiga
+#define AGI_MENU_TRIGGER_ATARIST 0x0101 // will trigger menu for Atari ST
extern const uint8 scancodeTable[];
diff --git a/engines/agi/loader_v1.cpp b/engines/agi/loader_v1.cpp
index 404fb6ee0e..fd418f3d59 100644
--- a/engines/agi/loader_v1.cpp
+++ b/engines/agi/loader_v1.cpp
@@ -21,6 +21,8 @@
*/
#include "agi/agi.h"
+#include "agi/words.h"
+
#include "common/md5.h"
#define IMAGE_SIZE 368640 // = 40 * 2 * 9 * 512 = tracks * sides * sectors * sector size
@@ -69,7 +71,7 @@ int AgiLoader_v1::loadDir_DDP(AgiDir *agid, int offset, int max) {
return errBadFileOpen;
// Cleanup
- for (int i = 0; i < MAX_DIRS; i++) {
+ for (int i = 0; i < MAX_DIRECTORY_ENTRIES; i++) {
agid[i].volume = 0xFF;
agid[i].offset = _EMPTY;
}
@@ -103,7 +105,7 @@ int AgiLoader_v1::loadDir_BC(AgiDir *agid, int offset, int max) {
return errBadFileOpen;
// Cleanup
- for (int i = 0; i < MAX_DIRS; i++) {
+ for (int i = 0; i < MAX_DIRECTORY_ENTRIES; i++) {
agid[i].volume = 0xFF;
agid[i].offset = _EMPTY;
}
@@ -197,84 +199,84 @@ uint8 *AgiLoader_v1::loadVolRes(struct AgiDir *agid) {
return data;
}
-int AgiLoader_v1::loadResource(int t, int n) {
+int AgiLoader_v1::loadResource(int16 resourceType, int16 resourceNr) {
int ec = errOK;
uint8 *data = NULL;
- debugC(3, kDebugLevelResources, "(t = %d, n = %d)", t, n);
- if (n >= MAX_DIRS)
+ debugC(3, kDebugLevelResources, "(t = %d, n = %d)", resourceType, resourceNr);
+ if (resourceNr >= MAX_DIRECTORY_ENTRIES)
return errBadResource;
- switch (t) {
- case rLOGIC:
- if (~_vm->_game.dirLogic[n].flags & RES_LOADED) {
- debugC(3, kDebugLevelResources, "loading logic resource %d", n);
- unloadResource(rLOGIC, n);
+ switch (resourceType) {
+ case RESOURCETYPE_LOGIC:
+ if (~_vm->_game.dirLogic[resourceNr].flags & RES_LOADED) {
+ debugC(3, kDebugLevelResources, "loading logic resource %d", resourceNr);
+ unloadResource(RESOURCETYPE_LOGIC, resourceNr);
// load raw resource into data
- data = loadVolRes(&_vm->_game.dirLogic[n]);
+ data = loadVolRes(&_vm->_game.dirLogic[resourceNr]);
- _vm->_game.logics[n].data = data;
- ec = data ? _vm->decodeLogic(n) : errBadResource;
+ _vm->_game.logics[resourceNr].data = data;
+ ec = data ? _vm->decodeLogic(resourceNr) : errBadResource;
- _vm->_game.logics[n].sIP = 2;
+ _vm->_game.logics[resourceNr].sIP = 2;
}
// if logic was cached, we get here
// reset code pointers incase it was cached
- _vm->_game.logics[n].cIP = _vm->_game.logics[n].sIP;
+ _vm->_game.logics[resourceNr].cIP = _vm->_game.logics[resourceNr].sIP;
break;
- case rPICTURE:
+ case RESOURCETYPE_PICTURE:
// if picture is currently NOT loaded *OR* cacheing is off,
// unload the resource (caching == off) and reload it
- debugC(3, kDebugLevelResources, "loading picture resource %d", n);
- if (_vm->_game.dirPic[n].flags & RES_LOADED)
+ debugC(3, kDebugLevelResources, "loading picture resource %d", resourceNr);
+ if (_vm->_game.dirPic[resourceNr].flags & RES_LOADED)
break;
// if loaded but not cached, unload it
// if cached but not loaded, etc
- unloadResource(rPICTURE, n);
- data = loadVolRes(&_vm->_game.dirPic[n]);
+ unloadResource(RESOURCETYPE_PICTURE, resourceNr);
+ data = loadVolRes(&_vm->_game.dirPic[resourceNr]);
if (data != NULL) {
- _vm->_game.pictures[n].rdata = data;
- _vm->_game.dirPic[n].flags |= RES_LOADED;
+ _vm->_game.pictures[resourceNr].rdata = data;
+ _vm->_game.dirPic[resourceNr].flags |= RES_LOADED;
} else {
ec = errBadResource;
}
break;
- case rSOUND:
- debugC(3, kDebugLevelResources, "loading sound resource %d", n);
- if (_vm->_game.dirSound[n].flags & RES_LOADED)
+ case RESOURCETYPE_SOUND:
+ debugC(3, kDebugLevelResources, "loading sound resource %d", resourceNr);
+ if (_vm->_game.dirSound[resourceNr].flags & RES_LOADED)
break;
- data = loadVolRes(&_vm->_game.dirSound[n]);
+ data = loadVolRes(&_vm->_game.dirSound[resourceNr]);
if (data != NULL) {
// Freeing of the raw resource from memory is delegated to the createFromRawResource-function
- _vm->_game.sounds[n] = AgiSound::createFromRawResource(data, _vm->_game.dirSound[n].len, n, _vm->_soundemu);
- _vm->_game.dirSound[n].flags |= RES_LOADED;
+ _vm->_game.sounds[resourceNr] = AgiSound::createFromRawResource(data, _vm->_game.dirSound[resourceNr].len, resourceNr, _vm->_soundemu);
+ _vm->_game.dirSound[resourceNr].flags |= RES_LOADED;
} else {
ec = errBadResource;
}
break;
- case rVIEW:
+ case RESOURCETYPE_VIEW:
// Load a VIEW resource into memory...
// Since VIEWS alter the view table ALL the time
// can we cache the view? or must we reload it all
// the time?
- if (_vm->_game.dirView[n].flags & RES_LOADED)
+ if (_vm->_game.dirView[resourceNr].flags & RES_LOADED)
break;
- debugC(3, kDebugLevelResources, "loading view resource %d", n);
- unloadResource(rVIEW, n);
- data = loadVolRes(&_vm->_game.dirView[n]);
+ debugC(3, kDebugLevelResources, "loading view resource %d", resourceNr);
+ unloadResource(RESOURCETYPE_VIEW, resourceNr);
+ data = loadVolRes(&_vm->_game.dirView[resourceNr]);
if (data) {
- _vm->_game.views[n].rdata = data;
- _vm->_game.dirView[n].flags |= RES_LOADED;
- ec = _vm->decodeView(n);
+ _vm->_game.dirView[resourceNr].flags |= RES_LOADED;
+ ec = _vm->decodeView(data, _vm->_game.dirView[resourceNr].len, resourceNr);
+ free(data);
} else {
ec = errBadResource;
}
@@ -287,19 +289,19 @@ int AgiLoader_v1::loadResource(int t, int n) {
return ec;
}
-int AgiLoader_v1::unloadResource(int t, int n) {
- switch (t) {
- case rLOGIC:
- _vm->unloadLogic(n);
+int AgiLoader_v1::unloadResource(int16 resourceType, int16 resourceNr) {
+ switch (resourceType) {
+ case RESOURCETYPE_LOGIC:
+ _vm->unloadLogic(resourceNr);
break;
- case rPICTURE:
- _vm->_picture->unloadPicture(n);
+ case RESOURCETYPE_PICTURE:
+ _vm->_picture->unloadPicture(resourceNr);
break;
- case rVIEW:
- _vm->unloadView(n);
+ case RESOURCETYPE_VIEW:
+ _vm->unloadView(resourceNr);
break;
- case rSOUND:
- _vm->_sound->unloadSound(n);
+ case RESOURCETYPE_SOUND:
+ _vm->_sound->unloadSound(resourceNr);
break;
}
@@ -321,7 +323,7 @@ int AgiLoader_v1::loadWords(const char *fname) {
Common::File f;
f.open(_filenameDisk0);
f.seek(BC_WORDS, SEEK_SET);
- return _vm->loadWords_v1(f);
+ return _vm->_words->loadDictionary_v1(f);
}
return errOK;
}
diff --git a/engines/agi/loader_v2.cpp b/engines/agi/loader_v2.cpp
index 693c53c2bf..76f6900e44 100644
--- a/engines/agi/loader_v2.cpp
+++ b/engines/agi/loader_v2.cpp
@@ -23,6 +23,7 @@
#include "common/textconsole.h"
#include "agi/agi.h"
+#include "agi/words.h"
namespace Agi {
@@ -60,7 +61,7 @@ int AgiLoader_v2::loadDir(AgiDir *agid, const char *fname) {
fp.read(mem, flen);
// set all directory resources to gone
- for (i = 0; i < MAX_DIRS; i++) {
+ for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) {
agid[i].volume = 0xff;
agid[i].offset = _EMPTY;
}
@@ -107,21 +108,21 @@ int AgiLoader_v2::deinit() {
return ec;
}
-int AgiLoader_v2::unloadResource(int t, int n) {
+int AgiLoader_v2::unloadResource(int16 resourceType, int16 resourceNr) {
debugC(3, kDebugLevelResources, "unload resource");
- switch (t) {
- case rLOGIC:
- _vm->unloadLogic(n);
+ switch (resourceType) {
+ case RESOURCETYPE_LOGIC:
+ _vm->unloadLogic(resourceNr);
break;
- case rPICTURE:
- _vm->_picture->unloadPicture(n);
+ case RESOURCETYPE_PICTURE:
+ _vm->_picture->unloadPicture(resourceNr);
break;
- case rVIEW:
- _vm->unloadView(n);
+ case RESOURCETYPE_VIEW:
+ _vm->unloadView(resourceNr);
break;
- case rSOUND:
- _vm->_sound->unloadSound(n);
+ case RESOURCETYPE_SOUND:
+ _vm->_sound->unloadSound(resourceNr);
break;
}
@@ -129,7 +130,7 @@ int AgiLoader_v2::unloadResource(int t, int n) {
}
/**
- * This function does noting but load a raw resource into memory,
+ * This function loads a raw resource into memory,
* if further decoding is required, it must be done by another
* routine. NULL is returned if unsucsessfull.
*/
@@ -173,84 +174,84 @@ uint8 *AgiLoader_v2::loadVolRes(struct AgiDir *agid) {
* Loads a resource into memory, a raw resource is loaded in
* with above routine, then further decoded here.
*/
-int AgiLoader_v2::loadResource(int t, int n) {
+int AgiLoader_v2::loadResource(int16 resourceType, int16 resourceNr) {
int ec = errOK;
uint8 *data = NULL;
- debugC(3, kDebugLevelResources, "(t = %d, n = %d)", t, n);
- if (n >= MAX_DIRS)
+ debugC(3, kDebugLevelResources, "(t = %d, n = %d)", resourceType, resourceNr);
+ if (resourceNr >= MAX_DIRECTORY_ENTRIES)
return errBadResource;
- switch (t) {
- case rLOGIC:
- if (~_vm->_game.dirLogic[n].flags & RES_LOADED) {
- debugC(3, kDebugLevelResources, "loading logic resource %d", n);
- unloadResource(rLOGIC, n);
+ switch (resourceType) {
+ case RESOURCETYPE_LOGIC:
+ if (~_vm->_game.dirLogic[resourceNr].flags & RES_LOADED) {
+ debugC(3, kDebugLevelResources, "loading logic resource %d", resourceNr);
+ unloadResource(RESOURCETYPE_LOGIC, resourceNr);
// load raw resource into data
- data = loadVolRes(&_vm->_game.dirLogic[n]);
+ data = loadVolRes(&_vm->_game.dirLogic[resourceNr]);
- _vm->_game.logics[n].data = data;
- ec = data ? _vm->decodeLogic(n) : errBadResource;
+ _vm->_game.logics[resourceNr].data = data;
+ ec = data ? _vm->decodeLogic(resourceNr) : errBadResource;
- _vm->_game.logics[n].sIP = 2;
+ _vm->_game.logics[resourceNr].sIP = 2;
}
// if logic was cached, we get here
// reset code pointers incase it was cached
- _vm->_game.logics[n].cIP = _vm->_game.logics[n].sIP;
+ _vm->_game.logics[resourceNr].cIP = _vm->_game.logics[resourceNr].sIP;
break;
- case rPICTURE:
+ case RESOURCETYPE_PICTURE:
// if picture is currently NOT loaded *OR* cacheing is off,
// unload the resource (caching == off) and reload it
- debugC(3, kDebugLevelResources, "loading picture resource %d", n);
- if (_vm->_game.dirPic[n].flags & RES_LOADED)
+ debugC(3, kDebugLevelResources, "loading picture resource %d", resourceNr);
+ if (_vm->_game.dirPic[resourceNr].flags & RES_LOADED)
break;
// if loaded but not cached, unload it
// if cached but not loaded, etc
- unloadResource(rPICTURE, n);
- data = loadVolRes(&_vm->_game.dirPic[n]);
+ unloadResource(RESOURCETYPE_PICTURE, resourceNr);
+ data = loadVolRes(&_vm->_game.dirPic[resourceNr]);
if (data != NULL) {
- _vm->_game.pictures[n].rdata = data;
- _vm->_game.dirPic[n].flags |= RES_LOADED;
+ _vm->_game.pictures[resourceNr].rdata = data;
+ _vm->_game.dirPic[resourceNr].flags |= RES_LOADED;
} else {
ec = errBadResource;
}
break;
- case rSOUND:
- debugC(3, kDebugLevelResources, "loading sound resource %d", n);
- if (_vm->_game.dirSound[n].flags & RES_LOADED)
+ case RESOURCETYPE_SOUND:
+ debugC(3, kDebugLevelResources, "loading sound resource %d", resourceNr);
+ if (_vm->_game.dirSound[resourceNr].flags & RES_LOADED)
break;
- data = loadVolRes(&_vm->_game.dirSound[n]);
+ data = loadVolRes(&_vm->_game.dirSound[resourceNr]);
if (data != NULL) {
// Freeing of the raw resource from memory is delegated to the createFromRawResource-function
- _vm->_game.sounds[n] = AgiSound::createFromRawResource(data, _vm->_game.dirSound[n].len, n, _vm->_soundemu);
- _vm->_game.dirSound[n].flags |= RES_LOADED;
+ _vm->_game.sounds[resourceNr] = AgiSound::createFromRawResource(data, _vm->_game.dirSound[resourceNr].len, resourceNr, _vm->_soundemu);
+ _vm->_game.dirSound[resourceNr].flags |= RES_LOADED;
} else {
ec = errBadResource;
}
break;
- case rVIEW:
+ case RESOURCETYPE_VIEW:
// Load a VIEW resource into memory...
// Since VIEWS alter the view table ALL the time
// can we cache the view? or must we reload it all
// the time?
- if (_vm->_game.dirView[n].flags & RES_LOADED)
+ if (_vm->_game.dirView[resourceNr].flags & RES_LOADED)
break;
- debugC(3, kDebugLevelResources, "loading view resource %d", n);
- unloadResource(rVIEW, n);
- data = loadVolRes(&_vm->_game.dirView[n]);
+ debugC(3, kDebugLevelResources, "loading view resource %d", resourceNr);
+ unloadResource(RESOURCETYPE_VIEW, resourceNr);
+ data = loadVolRes(&_vm->_game.dirView[resourceNr]);
if (data) {
- _vm->_game.views[n].rdata = data;
- _vm->_game.dirView[n].flags |= RES_LOADED;
- ec = _vm->decodeView(n);
+ _vm->_game.dirView[resourceNr].flags |= RES_LOADED;
+ ec = _vm->decodeView(data, _vm->_game.dirView[resourceNr].len, resourceNr);
+ free(data);
} else {
ec = errBadResource;
}
@@ -268,7 +269,7 @@ int AgiLoader_v2::loadObjects(const char *fname) {
}
int AgiLoader_v2::loadWords(const char *fname) {
- return _vm->loadWords(fname);
+ return _vm->_words->loadDictionary(fname);
}
} // End of namespace Agi
diff --git a/engines/agi/loader_v3.cpp b/engines/agi/loader_v3.cpp
index 39759b4649..d7abbac0bd 100644
--- a/engines/agi/loader_v3.cpp
+++ b/engines/agi/loader_v3.cpp
@@ -22,6 +22,7 @@
#include "agi/agi.h"
#include "agi/lzw.h"
+#include "agi/words.h"
#include "common/config-manager.h"
#include "common/fs.h"
@@ -76,7 +77,7 @@ int AgiLoader_v3::loadDir(struct AgiDir *agid, Common::File *fp,
fp->read(mem, len);
// set all directory resources to gone
- for (i = 0; i < MAX_DIRS; i++) {
+ for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) {
agid[i].volume = 0xff;
agid[i].offset = _EMPTY;
}
@@ -171,19 +172,19 @@ int AgiLoader_v3::deinit() {
return ec;
}
-int AgiLoader_v3::unloadResource(int t, int n) {
- switch (t) {
- case rLOGIC:
- _vm->unloadLogic(n);
+int AgiLoader_v3::unloadResource(int16 resourceType, int16 resourceNr) {
+ switch (resourceType) {
+ case RESOURCETYPE_LOGIC:
+ _vm->unloadLogic(resourceNr);
break;
- case rPICTURE:
- _vm->_picture->unloadPicture(n);
+ case RESOURCETYPE_PICTURE:
+ _vm->_picture->unloadPicture(resourceNr);
break;
- case rVIEW:
- _vm->unloadView(n);
+ case RESOURCETYPE_VIEW:
+ _vm->unloadView(resourceNr);
break;
- case rSOUND:
- _vm->_sound->unloadSound(n);
+ case RESOURCETYPE_SOUND:
+ _vm->_sound->unloadSound(resourceNr);
break;
}
@@ -191,7 +192,7 @@ int AgiLoader_v3::unloadResource(int t, int n) {
}
/**
- * This function does noting but load a raw resource into memory.
+ * This function loads a raw resource into memory.
* If further decoding is required, it must be done by another
* routine.
*
@@ -225,7 +226,11 @@ uint8 *AgiLoader_v3::loadVolRes(AgiDir *agid) {
fp.read(compBuffer, agid->clen);
if (x[2] & 0x80) { // compressed pic
- data = _vm->_picture->convertV3Pic(compBuffer, agid->clen);
+ // effectively uncompressed, but having only 4-bit parameters for F0 / F2 commands
+ // Manhunter 2 uses such pictures
+ data = compBuffer;
+ agid->flags |= RES_PICTURE_V3_NIBBLE_PARM;
+ //data = _vm->_picture->convertV3Pic(compBuffer, agid->clen);
// compBuffer has been freed inside convertV3Pic()
} else if (agid->len == agid->clen) {
// do not decompress
@@ -252,31 +257,31 @@ uint8 *AgiLoader_v3::loadVolRes(AgiDir *agid) {
* Loads a resource into memory, a raw resource is loaded in
* with above routine, then further decoded here.
*/
-int AgiLoader_v3::loadResource(int t, int n) {
+int AgiLoader_v3::loadResource(int16 resourceType, int16 resourceNr) {
int ec = errOK;
uint8 *data = NULL;
- if (n >= MAX_DIRS)
+ if (resourceNr >= MAX_DIRECTORY_ENTRIES)
return errBadResource;
- switch (t) {
- case rLOGIC:
+ switch (resourceType) {
+ case RESOURCETYPE_LOGIC:
// load resource into memory, decrypt messages at the end
// and build the message list (if logic is in memory)
- if (~_vm->_game.dirLogic[n].flags & RES_LOADED) {
+ if (~_vm->_game.dirLogic[resourceNr].flags & RES_LOADED) {
// if logic is already in memory, unload it
- unloadResource(rLOGIC, n);
+ unloadResource(RESOURCETYPE_LOGIC, resourceNr);
// load raw resource into data
- data = loadVolRes(&_vm->_game.dirLogic[n]);
- _vm->_game.logics[n].data = data;
+ data = loadVolRes(&_vm->_game.dirLogic[resourceNr]);
+ _vm->_game.logics[resourceNr].data = data;
// uncompressed logic files need to be decrypted
if (data != NULL) {
// resloaded flag gets set by decode logic
// needed to build string table
- ec = _vm->decodeLogic(n);
- _vm->_game.logics[n].sIP = 2;
+ ec = _vm->decodeLogic(resourceNr);
+ _vm->_game.logics[resourceNr].sIP = 2;
} else {
ec = errBadResource;
}
@@ -284,56 +289,56 @@ int AgiLoader_v3::loadResource(int t, int n) {
// logics[n].sIP=2; // saved IP = 2
// logics[n].cIP=2; // current IP = 2
- _vm->_game.logics[n].cIP = _vm->_game.logics[n].sIP;
+ _vm->_game.logics[resourceNr].cIP = _vm->_game.logics[resourceNr].sIP;
}
// if logic was cached, we get here
// reset code pointers incase it was cached
- _vm->_game.logics[n].cIP = _vm->_game.logics[n].sIP;
+ _vm->_game.logics[resourceNr].cIP = _vm->_game.logics[resourceNr].sIP;
break;
- case rPICTURE:
+ case RESOURCETYPE_PICTURE:
// if picture is currently NOT loaded *OR* cacheing is off,
// unload the resource (caching==off) and reload it
- if (~_vm->_game.dirPic[n].flags & RES_LOADED) {
- unloadResource(rPICTURE, n);
- data = loadVolRes(&_vm->_game.dirPic[n]);
+ if (~_vm->_game.dirPic[resourceNr].flags & RES_LOADED) {
+ unloadResource(RESOURCETYPE_PICTURE, resourceNr);
+ data = loadVolRes(&_vm->_game.dirPic[resourceNr]);
if (data != NULL) {
- _vm->_game.pictures[n].rdata = data;
- _vm->_game.dirPic[n].flags |= RES_LOADED;
+ _vm->_game.pictures[resourceNr].rdata = data;
+ _vm->_game.dirPic[resourceNr].flags |= RES_LOADED;
} else {
ec = errBadResource;
}
}
break;
- case rSOUND:
- if (_vm->_game.dirSound[n].flags & RES_LOADED)
+ case RESOURCETYPE_SOUND:
+ if (_vm->_game.dirSound[resourceNr].flags & RES_LOADED)
break;
- data = loadVolRes(&_vm->_game.dirSound[n]);
+ data = loadVolRes(&_vm->_game.dirSound[resourceNr]);
if (data != NULL) {
// Freeing of the raw resource from memory is delegated to the createFromRawResource-function
- _vm->_game.sounds[n] = AgiSound::createFromRawResource(data, _vm->_game.dirSound[n].len, n, _vm->_soundemu);
- _vm->_game.dirSound[n].flags |= RES_LOADED;
+ _vm->_game.sounds[resourceNr] = AgiSound::createFromRawResource(data, _vm->_game.dirSound[resourceNr].len, resourceNr, _vm->_soundemu);
+ _vm->_game.dirSound[resourceNr].flags |= RES_LOADED;
} else {
ec = errBadResource;
}
break;
- case rVIEW:
+ case RESOURCETYPE_VIEW:
// Load a VIEW resource into memory...
// Since VIEWS alter the view table ALL the time can we
// cache the view? or must we reload it all the time?
//
// load a raw view from a VOL file into data
- if (_vm->_game.dirView[n].flags & RES_LOADED)
+ if (_vm->_game.dirView[resourceNr].flags & RES_LOADED)
break;
- unloadResource(rVIEW, n);
- data = loadVolRes(&_vm->_game.dirView[n]);
+ unloadResource(RESOURCETYPE_VIEW, resourceNr);
+ data = loadVolRes(&_vm->_game.dirView[resourceNr]);
if (data != NULL) {
- _vm->_game.views[n].rdata = data;
- _vm->_game.dirView[n].flags |= RES_LOADED;
- ec = _vm->decodeView(n);
+ _vm->_game.dirView[resourceNr].flags |= RES_LOADED;
+ ec = _vm->decodeView(data, _vm->_game.dirView[resourceNr].len, resourceNr);
+ free(data);
} else {
ec = errBadResource;
}
@@ -351,7 +356,7 @@ int AgiLoader_v3::loadObjects(const char *fname) {
}
int AgiLoader_v3::loadWords(const char *fname) {
- return _vm->loadWords(fname);
+ return _vm->_words->loadDictionary(fname);
}
} // End of namespace Agi
diff --git a/engines/agi/logic.cpp b/engines/agi/logic.cpp
index 7429b117c8..1aeb7cc3de 100644
--- a/engines/agi/logic.cpp
+++ b/engines/agi/logic.cpp
@@ -30,15 +30,16 @@ namespace Agi {
* into a message list.
* @param n The number of the logic resource to decode.
*/
-int AgiEngine::decodeLogic(int n) {
+int AgiEngine::decodeLogic(int16 logicNr) {
int ec = errOK;
int mstart, mend, mc;
uint8 *m0;
+ AgiLogic *curLogic = &_game.logics[logicNr];
// decrypt messages at end of logic + build message list
// report ("decoding logic #%d\n", n);
- m0 = _game.logics[n].data;
+ m0 = curLogic->data;
mstart = READ_LE_UINT16(m0) + 2;
mc = *(m0 + mstart);
@@ -48,38 +49,38 @@ int AgiEngine::decodeLogic(int n) {
// if the logic was not compressed, decrypt the text messages
// only if there are more than 0 messages
- if ((~_game.dirLogic[n].flags & RES_COMPRESSED) && mc > 0)
+ if ((~_game.dirLogic[logicNr].flags & RES_COMPRESSED) && mc > 0)
decrypt(m0 + mstart, mend - mstart); // decrypt messages
// build message list
- m0 = _game.logics[n].data;
+ m0 = curLogic->data;
mstart = READ_LE_UINT16(m0) + 2; // +2 covers pointer
- _game.logics[n].numTexts = *(m0 + mstart);
+ _game.logics[logicNr].numTexts = *(m0 + mstart);
// resetp logic pointers
- _game.logics[n].sIP = 2;
- _game.logics[n].cIP = 2;
- _game.logics[n].size = READ_LE_UINT16(m0) + 2; // logic end pointer
+ curLogic->sIP = 2;
+ curLogic->cIP = 2;
+ curLogic->size = READ_LE_UINT16(m0) + 2; // logic end pointer
// allocate list of pointers to point into our data
- _game.logics[n].texts = (const char **)calloc(1 + _game.logics[n].numTexts, sizeof(char *));
+ curLogic->texts = (const char **)calloc(1 + curLogic->numTexts, sizeof(char *));
// cover header info
m0 += mstart + 3;
- if (_game.logics[n].texts != NULL) {
+ if (curLogic->texts != NULL) {
// move list of strings into list to make real pointers
- for (mc = 0; mc < _game.logics[n].numTexts; mc++) {
+ for (mc = 0; mc < curLogic->numTexts; mc++) {
mend = READ_LE_UINT16(m0 + mc * 2);
- _game.logics[n].texts[mc] = mend ? (const char *)m0 + mend - 2 : (const char *)"";
+ _game.logics[logicNr].texts[mc] = mend ? (const char *)m0 + mend - 2 : (const char *)"";
}
// set loaded flag now its all completly loaded
- _game.dirLogic[n].flags |= RES_LOADED;
+ _game.dirLogic[logicNr].flags |= RES_LOADED;
} else {
// unload data
// Note that not every logic has text
- free(_game.logics[n].data);
+ free(curLogic->data);
ec = errNotEnoughMemory;
}
@@ -92,18 +93,17 @@ int AgiEngine::decodeLogic(int n) {
* memory chunks allocated for this resource.
* @param n The number of the logic resource to unload
*/
-void AgiEngine::unloadLogic(int n) {
- if (_game.dirLogic[n].flags & RES_LOADED) {
- free(_game.logics[n].data);
- if (_game.logics[n].numTexts)
- free(_game.logics[n].texts);
- _game.logics[n].numTexts = 0;
- _game.dirLogic[n].flags &= ~RES_LOADED;
+void AgiEngine::unloadLogic(int16 logicNr) {
+ if (_game.dirLogic[logicNr].flags & RES_LOADED) {
+ free(_game.logics[logicNr].data);
+ free(_game.logics[logicNr].texts);
+ _game.logics[logicNr].numTexts = 0;
+ _game.dirLogic[logicNr].flags &= ~RES_LOADED;
}
// if cached, we end up here
- _game.logics[n].sIP = 2;
- _game.logics[n].cIP = 2;
+ _game.logics[logicNr].sIP = 2;
+ _game.logics[logicNr].cIP = 2;
}
} // End of namespace Agi
diff --git a/engines/agi/menu.cpp b/engines/agi/menu.cpp
index 008c208c82..411cd002fd 100644
--- a/engines/agi/menu.cpp
+++ b/engines/agi/menu.cpp
@@ -22,534 +22,456 @@
#include "agi/agi.h"
#include "agi/graphics.h"
+#include "agi/text.h"
#include "agi/keyboard.h"
#include "agi/menu.h"
namespace Agi {
-// TODO: add constructor/destructor for agi_menu, agi_menu_option
-
-struct AgiMenuOption {
- int enabled; /**< option is enabled or disabled */
- int event; /**< menu event */
- int index; /**< number of option in this menu */
- char *text; /**< text of menu option */
-};
-
-struct AgiMenu {
- MenuOptionList down; /**< list head for menu options */
- int index; /**< number of menu in menubar */
- int width; /**< width of menu in characters */
- int height; /**< height of menu in characters */
- int col; /**< column of menubar entry */
- int wincol; /**< column of menu window */
- char *text; /**< menu name */
-};
-
-AgiMenu *Menu::getMenu(int i) {
- MenuList::iterator iter;
- for (iter = _menubar.begin(); iter != _menubar.end(); ++iter) {
- AgiMenu *m = *iter;
- if (m->index == i)
- return m;
- }
- return NULL;
-}
-
-AgiMenuOption *Menu::getMenuOption(int i, int j) {
- AgiMenu *m = getMenu(i);
- MenuOptionList::iterator iter;
-
- for (iter = m->down.begin(); iter != m->down.end(); ++iter) {
- AgiMenuOption* d = *iter;
- if (d->index == j)
- return d;
- }
-
- return NULL;
-}
+GfxMenu::GfxMenu(AgiEngine *vm, GfxMgr *gfx, PictureMgr *picture, TextMgr *text) {
+ _vm = vm;
+ _gfx = gfx;
+ _picture = picture;
+ _text = text;
-void Menu::drawMenuBar() {
- _vm->clearLines(0, 0, MENU_BG);
- _vm->flushLines(0, 0);
+ _allowed = true;
+ _submitted = false;
+ _delayedExecute = false;
- MenuList::iterator iter;
- for (iter = _menubar.begin(); iter != _menubar.end(); ++iter) {
- AgiMenu *m = *iter;
-
- _vm->printText(m->text, 0, m->col, 0, 40, MENU_FG, MENU_BG);
- }
+ _setupMenuColumn = 1;
+ _setupMenuItemColumn = 1;
+ _selectedMenuNr = 0;
+ _selectedMenuHeight = 0;
+ _selectedMenuWidth = 0;
+ _selectedMenuRow = 0;
+ _selectedMenuColumn = 0;
}
-void Menu::drawMenuHilite(int curMenu) {
- AgiMenu *m = getMenu(curMenu);
-
- debugC(6, kDebugLevelMenu, "[%s]", m->text);
+GfxMenu::~GfxMenu() {
+ for (GuiMenuArray::iterator itemIter = _array.begin(); itemIter != _array.end(); ++itemIter)
+ delete *itemIter;
+ _array.clear();
- _vm->printText(m->text, 0, m->col, 0, 40, MENU_BG, MENU_FG);
- _vm->flushLines(0, 0);
+ for (GuiMenuItemArray::iterator menuIter = _itemArray.begin(); menuIter != _itemArray.end(); ++menuIter)
+ delete *menuIter;
+ _itemArray.clear();
}
-// draw box and pulldowns.
-void Menu::drawMenuOption(int hMenu) {
- // find which vertical menu it is
- AgiMenu *m = getMenu(hMenu);
-
- _gfx->drawBox(m->wincol * CHAR_COLS, 1 * CHAR_LINES, (m->wincol + m->width + 2) * CHAR_COLS,
- (1 + m->height + 2) * CHAR_LINES, MENU_BG, MENU_LINE, 0);
+void GfxMenu::addMenu(const char *menuText) {
+ // already submitted? in that case no further changes possible
+ if (_submitted)
+ return;
- MenuOptionList::iterator iter;
+ GuiMenuEntry *menuEntry = new GuiMenuEntry();
- for (iter = m->down.begin(); iter != m->down.end(); ++iter) {
- AgiMenuOption* d = *iter;
+ menuEntry->text = menuText;
+ menuEntry->textLen = menuEntry->text.size();
+ menuEntry->row = 0;
+ menuEntry->column = _setupMenuColumn;
+ menuEntry->itemCount = 0;
+ menuEntry->firstItemNr = _itemArray.size();
+ menuEntry->selectedItemNr = menuEntry->firstItemNr;
+ menuEntry->maxItemTextLen = 0;
+ _array.push_back(menuEntry);
- _vm->printText(d->text, 0, m->wincol + 1, d->index + 2, m->width + 2,
- MENU_FG, MENU_BG, !d->enabled);
- }
+ _setupMenuColumn += menuEntry->textLen + 1;
}
-void Menu::drawMenuOptionHilite(int hMenu, int vMenu) {
- AgiMenu *m = getMenu(hMenu);
- AgiMenuOption *d = getMenuOption(hMenu, vMenu);
+void GfxMenu::addMenuItem(const char *menuItemText, uint16 controllerSlot) {
+ int16 arrayCount = _array.size();
- // Disabled menu items are "greyed out" with a checkerboard effect,
- // rather than having a different color. -- dsymonds
- _vm->printText(d->text, 0, m->wincol + 1, vMenu + 2, m->width + 2,
- MENU_BG, MENU_FG, !d->enabled);
-}
+ // already submitted? in that case no further changes possible
+ if (_submitted)
+ return;
-void Menu::newMenuSelected(int i) {
- _picture->showPic();
- drawMenuBar();
- drawMenuHilite(i);
- drawMenuOption(i);
-}
+ if (arrayCount == 0)
+ error("tried to add a menu item before adding an actual menu");
-bool Menu::mouseOverText(int line, int col, char *s) {
- if (_vm->_mouse.x < col * CHAR_COLS)
- return false;
+ // go to latest menu entry
+ GuiMenuEntry *curMenuEntry = _array.back();
- if (_vm->_mouse.x > (int)(col + strlen(s)) * CHAR_COLS)
- return false;
+ GuiMenuItemEntry *menuItemEntry = new GuiMenuItemEntry();
- if (_vm->_mouse.y < line * CHAR_LINES)
- return false;
+ menuItemEntry->enabled = true;
+ menuItemEntry->text = menuItemText;
+ menuItemEntry->textLen = menuItemEntry->text.size();
+ menuItemEntry->controllerSlot = controllerSlot;
- if (_vm->_mouse.y >= (line + 1) * CHAR_LINES)
- return false;
+ // Original interpreter on PC used the length of the first item for drawing
+ // At least in KQ2 on Apple IIgs follow-up items are longer, which would result in graphic glitches.
+ // That's why we remember the longest item and draw according to that
+ if (curMenuEntry->maxItemTextLen < menuItemEntry->textLen) {
+ curMenuEntry->maxItemTextLen = menuItemEntry->textLen;
+ }
- return true;
-}
+ if (curMenuEntry->itemCount == 0) {
+ // for first menu item of menu calculated column
+ if (menuItemEntry->textLen + curMenuEntry->column < (FONT_COLUMN_CHARACTERS - 1)) {
+ _setupMenuItemColumn = curMenuEntry->column;
+ } else {
+ _setupMenuItemColumn = ( FONT_COLUMN_CHARACTERS - 1 ) - menuItemEntry->textLen;
+ }
+ }
-#if 0
-static void add_about_option() {
- const char *text = "About AGI engine";
-
- agi_menu_option *d = new agi_menu_option;
- d->text = strdup(text);
- d->enabled = true;
- d->event = 255;
- d->index = (v_max_menu[0] += 1);
-
- agi_menu *m = *menubar.begin();
- m->down.push_back(d);
- m->height++;
- if (m->width < (int)strlen(text))
- m->width = strlen(text);
-}
-#endif
+ menuItemEntry->row = 2 + curMenuEntry->itemCount;
+ menuItemEntry->column = _setupMenuItemColumn;
-/*
- * Public functions
- */
+ _itemArray.push_back(menuItemEntry);
-Menu::Menu(AgiEngine *vm, GfxMgr *gfx, PictureMgr *picture) {
- _vm = vm;
- _gfx = gfx;
- _picture = picture;
- _hIndex = 0;
- _hCol = 1;
- _hMaxMenu = 0;
- _hCurMenu = 0;
- _vCurMenu = 0;
+ curMenuEntry->itemCount++;
}
-Menu::~Menu() {
- MenuList::iterator iterh;
- for (iterh = _menubar.reverse_begin(); iterh != _menubar.end(); ) {
- AgiMenu *m = *iterh;
-
- debugC(3, kDebugLevelMenu, "deiniting hmenu %s", m->text);
-
- MenuOptionList::iterator iterv;
-
- for (iterv = m->down.reverse_begin(); iterv != m->down.end(); ) {
- AgiMenuOption *d = *iterv;
-
- debugC(3, kDebugLevelMenu, " deiniting vmenu %s", d->text);
-
- free(d->text);
- delete d;
+void GfxMenu::submit() {
+ GuiMenuEntry *menuEntry = nullptr;
+ GuiMenuItemEntry *menuItemEntry = nullptr;
+ int16 menuCount = _array.size();
+ int16 menuNr = 0;
+ int16 menuItemNr = 0;
+ int16 menuItemLastNr = 0;
+
+ if ((_array.size() == 0) || (_itemArray.size() == 0))
+ return;
+
+ _submitted = true;
+
+ // WORKAROUND: For Apple II gs we try to fix the menu text
+ // On this platform it seems a system font was used and the menu was drawn differently (probably system menu?)
+ // Still the text was misaligned anyway, but it looks worse in our (the original PC) implementation
+ // Atari ST SQ1 had one bad menu entry as well, we fix that too.
+ switch (_vm->getPlatform()) {
+ case Common::kPlatformApple2GS:
+ case Common::kPlatformAtariST:
+ // Go through all menus
+ for (menuNr = 0; menuNr < menuCount; menuNr++) {
+ menuEntry = _array[menuNr];
+ menuItemLastNr = menuEntry->firstItemNr + menuEntry->itemCount;
+
+ // Go through all items of current menu
+ for (menuItemNr = menuEntry->firstItemNr; menuItemNr < menuItemLastNr; menuItemNr++) {
+ menuItemEntry = _itemArray[menuItemNr];
+
+ if (menuItemEntry->textLen < menuEntry->maxItemTextLen) {
+ // current item text is shorter than the maximum?
+ int16 missingCharCount = menuEntry->maxItemTextLen - menuItemEntry->textLen;
+
+ if (menuItemEntry->text.contains('>')) {
+ // text contains '>', we now try to find a '<'
+ // and then add spaces in case this item is shorter than the first item
+ int16 textPos = menuItemEntry->textLen - 1;
+
+ while (textPos > 0) {
+ if (menuItemEntry->text[textPos] == '<')
+ break;
+ textPos--;
+ }
+
+ if (textPos > 0) {
+ while (missingCharCount) {
+ menuItemEntry->text.insertChar(' ', textPos);
+ missingCharCount--;
+ }
+ }
+ } else {
+ // Also check if text consists only of '-', which is the separator
+ // These were sometimes also too small
+ int16 separatorCount = 0;
+ int16 charPos = 0;
+
+ while (charPos < menuItemEntry->textLen) {
+ if (menuItemEntry->text[charPos] != '-')
+ break;
+ separatorCount++;
+ charPos++;
+ }
+
+ if (separatorCount == menuItemEntry->textLen) {
+ // Separator detected
+ while (missingCharCount) {
+ menuItemEntry->text.insertChar('-', 0);
+ missingCharCount--;
+ }
+ } else {
+ // Append spaces to the end to fill it up
+ int16 textPos = menuItemEntry->textLen;
+ while (missingCharCount) {
+ menuItemEntry->text.insertChar(' ', textPos);
+ textPos++;
+ missingCharCount--;
+ }
+ }
+ }
- iterv = m->down.reverse_erase(iterv);
+ menuItemEntry->textLen = menuItemEntry->text.size();
+ }
+ }
}
- free(m->text);
- delete m;
-
- iterh = _menubar.reverse_erase(iterh);
+ break;
+ default:
+ break;
}
}
-void Menu::add(const char *s) {
- AgiMenu *m = new AgiMenu;
- m->text = strdup(s);
-
- while (m->text[strlen(m->text) - 1] == ' ')
- m->text[strlen(m->text) - 1] = 0;
-
- m->width = 0;
- m->height = 0;
- m->index = _hIndex++;
- m->col = _hCol;
- m->wincol = _hCol - 1;
- _vIndex = 0;
- _vMaxMenu[m->index] = 0;
- _hCol += strlen(m->text) + 1;
- _hMaxMenu = m->index;
-
- debugC(3, kDebugLevelMenu, "add menu: '%s' %02x", s, m->text[strlen(m->text)]);
- _menubar.push_back(m);
+void GfxMenu::itemEnable(uint16 controllerSlot) {
+ itemEnableDisable(controllerSlot, true);
}
-void Menu::addItem(const char *s, int code) {
- int l;
-
- AgiMenuOption* d = new AgiMenuOption;
-
- d->text = strdup(s);
- d->enabled = true;
- d->event = code;
- d->index = _vIndex++;
-
- // add to last menu in list
- assert(_menubar.reverse_begin() != _menubar.end());
- AgiMenu *m = *_menubar.reverse_begin();
- m->height++;
-
- _vMaxMenu[m->index] = d->index;
-
- l = strlen(d->text);
- if (l > 40)
- l = 38;
- if (m->wincol + l > 38)
- m->wincol = 38 - l;
- if (l > m->width)
- m->width = l;
-
- debugC(3, kDebugLevelMenu, "Adding menu item: %s (size = %d)", s, m->height);
-
- m->down.push_back(d);
+void GfxMenu::itemDisable(uint16 controllerSlot) {
+ itemEnableDisable(controllerSlot, false);
}
-void Menu::submit() {
- debugC(3, kDebugLevelMenu, "Submitting menu");
-
- // add_about_option ();
-
- // If a menu has no options, delete it
- MenuList::iterator iter;
- for (iter = _menubar.reverse_begin(); iter != _menubar.end(); ) {
- AgiMenu *m = *iter;
+void GfxMenu::itemEnableDisable(uint16 controllerSlot, bool enabled) {
+ GuiMenuItemArray::iterator listIterator;
+ GuiMenuItemArray::iterator listEnd = _itemArray.end();
+ GuiMenuItemEntry *menuItemEntry;
- if (m->down.empty()) {
- free(m->text);
- delete m;
-
- _hMaxMenu--;
-
- iter = _menubar.reverse_erase(iter);
- } else {
- --iter;
+ listIterator = _itemArray.begin();
+ while (listIterator != listEnd) {
+ menuItemEntry = *listIterator;
+ if (menuItemEntry->controllerSlot == controllerSlot) {
+ menuItemEntry->enabled = enabled;
}
+
+ listIterator++;
}
}
-bool Menu::keyhandler(int key) {
- static int clockVal;
- static int menuActive = false;
- static int buttonUsed = 0;
- bool exitMenu = false;
+void GfxMenu::itemEnableAll() {
+ GuiMenuItemArray::iterator listIterator;
+ GuiMenuItemArray::iterator listEnd = _itemArray.end();
+ GuiMenuItemEntry *menuItemEntry;
- if (!_vm->getflag(fMenusWork) && !(_vm->getFeatures() & GF_MENUS))
- return false;
+ listIterator = _itemArray.begin();
+ while (listIterator != listEnd) {
+ menuItemEntry = *listIterator;
+ menuItemEntry->enabled = true;
- if (!menuActive) {
- clockVal = _vm->_game.clockEnabled;
- _vm->_game.clockEnabled = false;
- drawMenuBar();
+ listIterator++;
}
+}
- // Mouse handling
- if (_vm->_mouse.button) {
- int hmenu, vmenu;
-
- buttonUsed = 1; // Button has been used at least once
-
- if (_vm->_mouse.y <= CHAR_LINES) {
- // on the menubar
- hmenu = 0;
+// return true, in case a menu was actually created and submitted by the scripts
+bool GfxMenu::isAvailable() {
+ return _submitted;
+}
- MenuList::iterator iterh;
+void GfxMenu::accessAllow() {
+ _allowed = true;
+}
- for (iterh = _menubar.begin(); iterh != _menubar.end(); ++iterh) {
- AgiMenu *m = *iterh;
+void GfxMenu::accessDeny() {
+ _allowed = false;
+}
- if (mouseOverText(0, m->col, m->text)) {
- break;
- } else {
- hmenu++;
- }
- }
+void GfxMenu::delayedExecute() {
+ _delayedExecute = true;
+}
- if (hmenu <= _hMaxMenu) {
- if (_hCurMenu != hmenu) {
- _vCurMenu = -1;
- newMenuSelected(hmenu);
- }
- _hCurMenu = hmenu;
- }
- } else {
- // not in menubar
- vmenu = 0;
+bool GfxMenu::delayedExecuteActive() {
+ return _delayedExecute;
+}
- AgiMenu *m = getMenu(_hCurMenu);
+void GfxMenu::execute() {
+ _delayedExecute = false;
- MenuOptionList::iterator iterv;
+ // got submitted? -> safety check
+ if (!_submitted)
+ return;
- for (iterv = m->down.begin(); iterv != m->down.end(); ++iterv) {
- AgiMenuOption *do1 = *iterv;
+ // access allowed at the moment?
+ if (!_allowed)
+ return;
- if (mouseOverText(2 + do1->index, m->wincol + 1, do1->text)) {
- break;
- } else {
- vmenu++;
- }
- }
+ _text->charPos_Push();
+ _text->charAttrib_Push();
+ _text->clearLine(0, _text->calculateTextBackground(15));
- if (vmenu <= _vMaxMenu[_hCurMenu]) {
- if (_vCurMenu != vmenu) {
- drawMenuOption(_hCurMenu);
- drawMenuOptionHilite(_hCurMenu, vmenu);
- }
- _vCurMenu = vmenu;
- }
- }
- } else if (buttonUsed) {
- // Button released
- buttonUsed = 0;
+ // Draw all menus
+ for (uint16 menuNr = 0; menuNr < _array.size(); menuNr++) {
+ drawMenuName(menuNr, false);
+ }
+ drawActiveMenu();
- debugC(6, kDebugLevelMenu | kDebugLevelInput, "button released!");
+ _vm->cycleInnerLoopActive(CYCLE_INNERLOOP_MENU);
+ do {
+ _vm->mainCycle();
+ } while (_vm->cycleInnerLoopIsActive() && !(_vm->shouldQuit() || _vm->_restartGame));
- if (_vCurMenu < 0)
- _vCurMenu = 0;
+ removeActiveMenu();
- drawMenuOptionHilite(_hCurMenu, _vCurMenu);
+ _text->charAttrib_Pop();
+ _text->charPos_Pop();
- if (_vm->_mouse.y <= CHAR_LINES) {
- // on the menubar
- } else {
- // see which option we selected
- AgiMenu *m = getMenu(_hCurMenu);
- MenuOptionList::iterator iterv;
-
- for (iterv = m->down.begin(); iterv != m->down.end(); ++iterv) {
- AgiMenuOption *d = *iterv;
-
- if (mouseOverText(2 + d->index, m->wincol + 1, d->text)) {
- // activate that option
- if (d->enabled) {
- debugC(6, kDebugLevelMenu | kDebugLevelInput, "event %d registered", d->event);
- _vm->_game.controllerOccured[d->event] = true;
- _vm->_menuSelected = true;
- break;
- }
- }
- }
- exitMenu = true;
- }
+ // Restore status line
+ if (_text->statusEnabled()) {
+ _text->statusDraw();
+ } else {
+ _text->clearLine(0, 0);
}
+}
- if (!exitMenu) {
- if (!menuActive) {
- if (_hCurMenu >= 0) {
- drawMenuHilite(_hCurMenu);
- drawMenuOption(_hCurMenu);
- if (!buttonUsed && _vCurMenu >= 0)
- drawMenuOptionHilite(_hCurMenu, _vCurMenu);
- }
- menuActive = true;
- }
+void GfxMenu::drawMenuName(int16 menuNr, bool inverted) {
+ GuiMenuEntry *menuEntry = _array[menuNr];
+ bool disabledLook = false;
- switch (key) {
- case KEY_ESCAPE:
- debugC(6, kDebugLevelMenu | kDebugLevelInput, "KEY_ESCAPE");
- exitMenu = true;
- break;
- case KEY_ENTER:
- {
- debugC(6, kDebugLevelMenu | kDebugLevelInput, "KEY_ENTER");
- AgiMenuOption* d = getMenuOption(_hCurMenu, _vCurMenu);
-
- if (d->enabled) {
- debugC(6, kDebugLevelMenu | kDebugLevelInput, "event %d registered", d->event);
- _vm->_game.controllerOccured[d->event] = true;
- _vm->_menuSelected = true;
- exitMenu = true;
- }
- break;
- }
- case KEY_DOWN:
- case KEY_UP:
- _vCurMenu += key == KEY_DOWN ? 1 : -1;
-
- if (_vCurMenu < 0)
- _vCurMenu = _vMaxMenu[_hCurMenu];
- if (_vCurMenu > _vMaxMenu[_hCurMenu])
- _vCurMenu = 0;
-
- drawMenuOption(_hCurMenu);
- drawMenuOptionHilite(_hCurMenu, _vCurMenu);
- break;
- case KEY_RIGHT:
- case KEY_LEFT:
- _hCurMenu += key == KEY_RIGHT ? 1 : -1;
-
- if (_hCurMenu < 0)
- _hCurMenu = _hMaxMenu;
- if (_hCurMenu > _hMaxMenu)
- _hCurMenu = 0;
-
- _vCurMenu = 0;
- newMenuSelected(_hCurMenu);
- drawMenuOptionHilite(_hCurMenu, _vCurMenu);
- break;
- }
+ if (!inverted) {
+ _text->charAttrib_Set(0, _text->calculateTextBackground(15));
+ } else {
+ _text->charAttrib_Set(15, _text->calculateTextBackground(0));
}
- if (exitMenu) {
- buttonUsed = 0;
- _picture->showPic();
- _vm->writeStatus();
-
- _vm->setvar(vKey, 0);
- _vm->_game.keypress = 0;
- _vm->_game.clockEnabled = clockVal;
- _vm->oldInputMode();
+ _text->charPos_Set(menuEntry->row, menuEntry->column);
- debugC(3, kDebugLevelMenu, "exit_menu: input mode reset to %d", _vm->_game.inputMode);
- menuActive = false;
- }
+ if (menuEntry->itemCount == 0)
+ disabledLook = true;
- return true;
+ _text->displayText(menuEntry->text.c_str(), disabledLook);
}
-void Menu::setItem(int event, int state) {
- // scan all menus for event number #
+void GfxMenu::drawItemName(int16 itemNr, bool inverted) {
+ GuiMenuItemEntry *itemEntry = _itemArray[itemNr];
+ bool disabledLook = false;
- debugC(6, kDebugLevelMenu, "event = %d, state = %d", event, state);
- MenuList::iterator iterh;
+ if (!inverted) {
+ _text->charAttrib_Set(0, _text->calculateTextBackground(15));
+ } else {
+ _text->charAttrib_Set(15, _text->calculateTextBackground(0));
+ }
- for (iterh = _menubar.begin(); iterh != _menubar.end(); ++iterh) {
- AgiMenu *m = *iterh;
- MenuOptionList::iterator iterv;
+ _text->charPos_Set(itemEntry->row, itemEntry->column);
- for (iterv = m->down.begin(); iterv != m->down.end(); ++iterv) {
- AgiMenuOption *d = *iterv;
+ if (itemEntry->enabled == false)
+ disabledLook = true;
- if (d->event == event) {
- d->enabled = state;
- // keep going; we need to set the state of every menu item
- // with this event code. -- dsymonds
- }
- }
- }
+ _text->displayText(itemEntry->text.c_str(), disabledLook);
}
-void Menu::enableAll() {
- MenuList::iterator iterh;
- for (iterh = _menubar.begin(); iterh != _menubar.end(); ++iterh) {
- AgiMenu *m = *iterh;
- MenuOptionList::iterator iterv;
-
- for (iterv = m->down.begin(); iterv != m->down.end(); ++iterv) {
- AgiMenuOption *d = *iterv;
-
- d->enabled = true;
+void GfxMenu::drawActiveMenu() {
+ GuiMenuEntry *menuEntry = _array[_selectedMenuNr];
+ GuiMenuItemEntry *itemEntry = _itemArray[menuEntry->firstItemNr];
+ int16 itemNr = menuEntry->firstItemNr;
+ int16 itemCount = menuEntry->itemCount;
+
+ // draw menu name as inverted
+ drawMenuName(_selectedMenuNr, true);
+
+ // calculate active menu dimensions
+ _selectedMenuHeight = (menuEntry->itemCount + 2) * FONT_VISUAL_HEIGHT;
+ _selectedMenuWidth = (menuEntry->maxItemTextLen * FONT_VISUAL_WIDTH) + 8;
+ _selectedMenuRow = (menuEntry->itemCount + 3 - _text->getWindowRowMin()) * FONT_VISUAL_HEIGHT - 1;
+ _selectedMenuColumn = (itemEntry->column - 1) * FONT_VISUAL_WIDTH;
+
+ _gfx->drawBox(_selectedMenuColumn, _selectedMenuRow, _selectedMenuWidth, _selectedMenuHeight, 15, 0);
+
+ while (itemCount) {
+ if (itemNr == menuEntry->selectedItemNr) {
+ drawItemName(itemNr, true);
+ } else {
+ drawItemName(itemNr, false);
}
+ itemNr++;
+ itemCount--;
}
}
+void GfxMenu::removeActiveMenu() {
+ // draw menu name normally again
+ drawMenuName(_selectedMenuNr, false);
-AgiTextColor AgiButtonStyle::getColor(bool hasFocus, bool pressed, bool positive) const {
- if (_amigaStyle) {
- if (positive) {
- if (pressed) { // Positive pressed Amiga-style button
- if (_olderAgi) {
- return AgiTextColor(amigaBlack, amigaOrange);
- } else {
- return AgiTextColor(amigaBlack, amigaPurple);
- }
- } else { // Positive unpressed Amiga-style button
- return AgiTextColor(amigaWhite, amigaGreen);
- }
- } else { // _amigaStyle && !positive
- if (pressed) { // Negative pressed Amiga-style button
- return AgiTextColor(amigaBlack, amigaCyan);
- } else { // Negative unpressed Amiga-style button
- return AgiTextColor(amigaWhite, amigaRed);
- }
- }
- } else { // PC-style button
- if (hasFocus || pressed) { // A pressed or in focus PC-style button
- return AgiTextColor(pcWhite, pcBlack);
- } else { // An unpressed PC-style button without focus
- return AgiTextColor(pcBlack, pcWhite);
- }
- }
+ // overwrite actual menu items by rendering play screen
+ _gfx->render_Block(_selectedMenuColumn, _selectedMenuRow, _selectedMenuWidth, _selectedMenuHeight);
}
-AgiTextColor AgiButtonStyle::getColor(bool hasFocus, bool pressed, int baseFgColor, int baseBgColor) const {
- return getColor(hasFocus, pressed, AgiTextColor(baseFgColor, baseBgColor));
-}
+void GfxMenu::charPress(int16 newChar) {
+ GuiMenuEntry *menuEntry = _array[_selectedMenuNr];
+ GuiMenuItemEntry *itemEntry = _itemArray[menuEntry->selectedItemNr];
+ int16 newMenuNr = _selectedMenuNr;
+ int16 newItemNr = menuEntry->selectedItemNr;
+
+ switch (newChar) {
+ case AGI_KEY_ENTER:
+ // check, if current item is actually enabled
+ if (!itemEntry->enabled)
+ return;
+
+ // Trigger controller
+ _vm->_game.controllerOccured[itemEntry->controllerSlot] = true;
+
+ _vm->cycleInnerLoopInactive(); // exit execute-loop
+ break;
+ case AGI_KEY_ESCAPE:
+ _vm->cycleInnerLoopInactive(); // exit execute-loop
+ break;
+
+ // these here change menu item
+ case AGI_KEY_UP:
+ newItemNr--;
+ break;
+ case AGI_KEY_DOWN:
+ newItemNr++;
+ break;
+ case AGI_KEY_PAGE_UP:
+ // select first item of current menu
+ newItemNr = menuEntry->firstItemNr;
+ break;
+ case AGI_KEY_PAGE_DOWN:
+ // select last item of current menu
+ newItemNr = menuEntry->firstItemNr + menuEntry->itemCount - 1;
+ break;
+
+ case AGI_KEY_LEFT:
+ newMenuNr--;
+ break;
+ case AGI_KEY_RIGHT:
+ newMenuNr++;
+ break;
+ case AGI_KEY_HOME:
+ // select first menu
+ newMenuNr = 0;
+ break;
+ case AGI_KEY_END:
+ // select last menu
+ newMenuNr = _array.size() - 1;
+ break;
+
+ default:
+ break;
+ }
-AgiTextColor AgiButtonStyle::getColor(bool hasFocus, bool pressed, const AgiTextColor &baseColor) const {
- if (hasFocus || pressed)
- return baseColor.swap();
- else
- return baseColor;
-}
+ if (newMenuNr != _selectedMenuNr) {
+ // selected menu was changed
+ int16 lastMenuNr = _array.size() - 1;
-int AgiButtonStyle::getTextOffset(bool hasFocus, bool pressed) const {
- return (pressed && !_amigaStyle) ? 1 : 0;
-}
+ if (newMenuNr < 0) {
+ newMenuNr = lastMenuNr;
+ } else if (newMenuNr > lastMenuNr) {
+ newMenuNr = 0;
+ }
-bool AgiButtonStyle::getBorder(bool hasFocus, bool pressed) const {
- return _amigaStyle && !_authenticAmiga && (hasFocus || pressed);
-}
+ if (newMenuNr != _selectedMenuNr) {
+ removeActiveMenu();
+ _selectedMenuNr = newMenuNr;
+ drawActiveMenu();
+ }
+ }
-void AgiButtonStyle::setAmigaStyle(bool amigaStyle, bool olderAgi, bool authenticAmiga) {
- _amigaStyle = amigaStyle;
- _olderAgi = olderAgi;
- _authenticAmiga = authenticAmiga;
-}
+ if (newItemNr != menuEntry->selectedItemNr) {
+ // selected item was changed
+ int16 lastItemNr = menuEntry->firstItemNr + menuEntry->itemCount - 1;
-void AgiButtonStyle::setPcStyle(bool pcStyle) {
- setAmigaStyle(!pcStyle);
-}
+ if (newItemNr < menuEntry->firstItemNr) {
+ newItemNr = lastItemNr;
+ } else if (newItemNr > lastItemNr) {
+ newItemNr = menuEntry->firstItemNr;
+ }
-AgiButtonStyle::AgiButtonStyle(Common::RenderMode renderMode) {
- setAmigaStyle(renderMode == Common::kRenderAmiga);
+ if (newItemNr != menuEntry->selectedItemNr) {
+ // still changed after clip -> draw changes
+ drawItemName(menuEntry->selectedItemNr, false);
+ drawItemName(newItemNr, true);
+ menuEntry->selectedItemNr = newItemNr;
+ }
+ }
}
} // End of namespace Agi
diff --git a/engines/agi/menu.h b/engines/agi/menu.h
index 000973db23..1781704996 100644
--- a/engines/agi/menu.h
+++ b/engines/agi/menu.h
@@ -25,6 +25,88 @@
namespace Agi {
+struct GuiMenuEntry {
+ Common::String text;
+ int16 textLen;
+
+ int16 row;
+ int16 column;
+
+ int16 itemCount;
+ int16 firstItemNr;
+ int16 selectedItemNr;
+
+ int16 maxItemTextLen;
+};
+typedef Common::Array<GuiMenuEntry *> GuiMenuArray;
+
+struct GuiMenuItemEntry {
+ Common::String text;
+ int16 textLen;
+
+ int16 row;
+ int16 column;
+
+ bool enabled;
+ uint16 controllerSlot;
+};
+typedef Common::Array<GuiMenuItemEntry *> GuiMenuItemArray;
+
+class GfxMenu {
+public:
+ GfxMenu(AgiEngine *vm, GfxMgr *gfx, PictureMgr *picture, TextMgr *text);
+ ~GfxMenu();
+
+ void addMenu(const char *menuText);
+ void addMenuItem(const char *menuText, uint16 controlCode);
+ void submit();
+ void itemEnable(uint16 controllerSlot);
+ void itemDisable(uint16 controllerSlot);
+ void itemEnableAll();
+ void charPress(int16 newChar);
+
+ bool isAvailable();
+
+ void accessAllow();
+ void accessDeny();
+
+ void delayedExecute();
+ bool delayedExecuteActive();
+ void execute();
+
+private:
+ void itemEnableDisable(uint16 controllerSlot, bool enabled);
+
+ void drawMenuName(int16 menuNr, bool inverted);
+ void drawItemName(int16 itemNr, bool inverted);
+ void drawActiveMenu();
+ void removeActiveMenu();
+
+ AgiEngine *_vm;
+ GfxMgr *_gfx;
+ PictureMgr *_picture;
+ TextMgr *_text;
+
+ bool _allowed;
+ bool _submitted;
+ bool _delayedExecute;
+
+ // for initial setup of the menu
+ int16 _setupMenuColumn;
+ int16 _setupMenuItemColumn;
+
+ GuiMenuArray _array;
+ GuiMenuItemArray _itemArray;
+
+ int16 _selectedMenuNr;
+
+ uint16 _selectedMenuHeight;
+ uint16 _selectedMenuWidth;
+ int16 _selectedMenuRow;
+ int16 _selectedMenuColumn;
+};
+
+#if 0
#define MENU_BG 0x0f // White
#define MENU_DISABLED 0x07 // Grey
@@ -44,6 +126,7 @@ private:
AgiEngine *_vm;
GfxMgr *_gfx;
PictureMgr *_picture;
+ TextMgr *_text;
public:
Menu(AgiEngine *vm, GfxMgr *gfx, PictureMgr *picture);
@@ -78,6 +161,7 @@ private:
bool mouseOverText(int line, int col, char *s);
};
+#endif
} // End of namespace Agi
diff --git a/engines/agi/module.mk b/engines/agi/module.mk
index 331a10c16e..b6cf6fc531 100644
--- a/engines/agi/module.mk
+++ b/engines/agi/module.mk
@@ -36,6 +36,7 @@ MODULE_OBJS := \
sound_pcjr.o \
sound_sarien.o \
sprite.o \
+ systemui.o \
text.o \
view.o \
wagparser.o \
diff --git a/engines/agi/motion.cpp b/engines/agi/motion.cpp
index 363291ac0b..3f85059b56 100644
--- a/engines/agi/motion.cpp
+++ b/engines/agi/motion.cpp
@@ -29,7 +29,7 @@ int AgiEngine::checkStep(int delta, int step) {
return (-step >= delta) ? 0 : (step <= delta) ? 2 : 1;
}
-int AgiEngine::checkBlock(int x, int y) {
+bool AgiEngine::checkBlock(int16 x, int16 y) {
if (x <= _game.block.x1 || x >= _game.block.x2)
return false;
@@ -39,87 +39,90 @@ int AgiEngine::checkBlock(int x, int y) {
return true;
}
-void AgiEngine::changePos(VtEntry *v) {
- int b, x, y;
+void AgiEngine::changePos(ScreenObjEntry *screenObj) {
+ bool insideBlock;
+ int16 x, y;
int dx[9] = { 0, 0, 1, 1, 1, 0, -1, -1, -1 };
int dy[9] = { 0, -1, -1, 0, 1, 1, 1, 0, -1 };
- x = v->xPos;
- y = v->yPos;
- b = checkBlock(x, y);
+ x = screenObj->xPos;
+ y = screenObj->yPos;
+ insideBlock = checkBlock(x, y);
- x += v->stepSize * dx[v->direction];
- y += v->stepSize * dy[v->direction];
+ x += screenObj->stepSize * dx[screenObj->direction];
+ y += screenObj->stepSize * dy[screenObj->direction];
- if (checkBlock(x, y) == b) {
- v->flags &= ~fMotion;
+ if (checkBlock(x, y) == insideBlock) {
+ screenObj->flags &= ~fMotion;
} else {
- v->flags |= fMotion;
- v->direction = 0;
- if (isEgoView(v))
- _game.vars[vEgoDir] = 0;
+ screenObj->flags |= fMotion;
+ screenObj->direction = 0;
+ if (isEgoView(screenObj))
+ _game.vars[VM_VAR_EGO_DIRECTION] = 0;
}
}
-void AgiEngine::motionWander(VtEntry *v) {
- if (v->parm1--) {
- if (~v->flags & fDidntMove)
- return;
- }
+void AgiEngine::motionWander(ScreenObjEntry *screenObj) {
+ uint8 originalWanderCount = screenObj->wander_count;
- v->direction = _rnd->getRandomNumber(8);
+ screenObj->wander_count--;
+ if ((originalWanderCount == 0) || (screenObj->flags & fDidntMove)) {
+ screenObj->direction = _rnd->getRandomNumber(8);
- if (isEgoView(v)) {
- _game.vars[vEgoDir] = v->direction;
- while (v->parm1 < 6) {
- v->parm1 = _rnd->getRandomNumber(50); // huh?
+ if (isEgoView(screenObj)) {
+ _game.vars[VM_VAR_EGO_DIRECTION] = screenObj->direction;
+ }
+
+ while (screenObj->wander_count < 6) {
+ screenObj->wander_count = _rnd->getRandomNumber(50); // huh?
}
}
}
-void AgiEngine::motionFollowEgo(VtEntry *v) {
+void AgiEngine::motionFollowEgo(ScreenObjEntry *screenObj) {
+ ScreenObjEntry *screenObjEgo = &_game.screenObjTable[SCREENOBJECTS_EGO_ENTRY];
int egoX, egoY;
int objX, objY;
int dir;
- egoX = _game.viewTable[0].xPos + _game.viewTable[0].xSize / 2;
- egoY = _game.viewTable[0].yPos;
+ egoX = screenObjEgo->xPos + screenObjEgo->xSize / 2;
+ egoY = screenObjEgo->yPos;
- objX = v->xPos + v->xSize / 2;
- objY = v->yPos;
+ objX = screenObj->xPos + screenObj->xSize / 2;
+ objY = screenObj->yPos;
// Get direction to reach ego
- dir = getDirection(objX, objY, egoX, egoY, v->parm1);
+ dir = getDirection(objX, objY, egoX, egoY, screenObj->follow_stepSize);
// Already at ego coordinates
if (dir == 0) {
- v->direction = 0;
- v->motion = kMotionNormal;
- setflag(v->parm2, true);
+ screenObj->direction = 0;
+ screenObj->motionType = kMotionNormal;
+ setflag(screenObj->follow_flag, true);
return;
}
- if (v->parm3 == 0xff) {
- v->parm3 = 0;
- } else if (v->flags & fDidntMove) {
+ if (screenObj->follow_count == 0xff) {
+ screenObj->follow_count = 0;
+ } else if (screenObj->flags & fDidntMove) {
int d;
- while ((v->direction = _rnd->getRandomNumber(8)) == 0) {
+ while ((screenObj->direction = _rnd->getRandomNumber(8)) == 0) {
}
d = (ABS(egoY - objY) + ABS(egoX - objX)) / 2;
- if (d < v->stepSize) {
- v->parm3 = v->stepSize;
+ if (d < screenObj->stepSize) {
+ screenObj->follow_count = screenObj->stepSize;
return;
}
- while ((v->parm3 = _rnd->getRandomNumber(d)) < v->stepSize) {
+ while ((screenObj->follow_count = _rnd->getRandomNumber(d)) < screenObj->stepSize) {
}
return;
}
- if (v->parm3 != 0) {
+ if (screenObj->follow_count != 0) {
int k;
// DF: this is ugly and I dont know why this works, but
@@ -128,45 +131,46 @@ void AgiEngine::motionFollowEgo(VtEntry *v) {
// if (((int8)v->parm3 -= v->step_size) < 0)
// v->parm3 = 0;
- k = v->parm3;
- k -= v->stepSize;
- v->parm3 = k;
+ k = screenObj->follow_count;
+ k -= screenObj->stepSize;
+ screenObj->follow_count = k;
- if ((int8) v->parm3 < 0)
- v->parm3 = 0;
+ if ((int8) screenObj->follow_count < 0)
+ screenObj->follow_count = 0;
} else {
- v->direction = dir;
+ screenObj->direction = dir;
}
}
-void AgiEngine::motionMoveObj(VtEntry *v) {
- v->direction = getDirection(v->xPos, v->yPos, v->parm1, v->parm2, v->stepSize);
+void AgiEngine::motionMoveObj(ScreenObjEntry *screenObj) {
+ screenObj->direction = getDirection(screenObj->xPos, screenObj->yPos, screenObj->move_x, screenObj->move_y, screenObj->stepSize);
// Update V6 if ego
- if (isEgoView(v))
- _game.vars[vEgoDir] = v->direction;
+ if (isEgoView(screenObj))
+ _game.vars[VM_VAR_EGO_DIRECTION] = screenObj->direction;
- if (v->direction == 0)
- inDestination(v);
+ if (screenObj->direction == 0)
+ motionMoveObjStop(screenObj);
}
-void AgiEngine::checkMotion(VtEntry *v) {
- switch (v->motion) {
+void AgiEngine::checkMotion(ScreenObjEntry *screenObj) {
+ switch (screenObj->motionType) {
case kMotionNormal:
break;
case kMotionWander:
- motionWander(v);
+ motionWander(screenObj);
break;
case kMotionFollowEgo:
- motionFollowEgo(v);
+ motionFollowEgo(screenObj);
break;
+ case kMotionEgo:
case kMotionMoveObj:
- motionMoveObj(v);
+ motionMoveObj(screenObj);
break;
}
- if ((_game.block.active && (~v->flags & fIgnoreBlocks)) && v->direction)
- changePos(v);
+ if ((_game.block.active && (~screenObj->flags & fIgnoreBlocks)) && screenObj->direction)
+ changePos(screenObj);
}
/*
@@ -177,12 +181,12 @@ void AgiEngine::checkMotion(VtEntry *v) {
*
*/
void AgiEngine::checkAllMotions() {
- VtEntry *v;
+ ScreenObjEntry *screenObj;
- for (v = _game.viewTable; v < &_game.viewTable[MAX_VIEWTABLE]; v++) {
- if ((v->flags & (fAnimated | fUpdate | fDrawn)) == (fAnimated | fUpdate | fDrawn)
- && v->stepTimeCount == 1) {
- checkMotion(v);
+ for (screenObj = _game.screenObjTable; screenObj < &_game.screenObjTable[SCREENOBJECTS_MAX]; screenObj++) {
+ if ((screenObj->flags & (fAnimated | fUpdate | fDrawn)) == (fAnimated | fUpdate | fDrawn)
+ && screenObj->stepTimeCount == 1) {
+ checkMotion(screenObj);
}
}
}
@@ -193,14 +197,27 @@ void AgiEngine::checkAllMotions() {
* type motion that * has reached its final destination coordinates.
* @param v Pointer to view table entry
*/
-void AgiEngine::inDestination(VtEntry *v) {
- if (v->motion == kMotionMoveObj) {
- v->stepSize = v->parm3;
- setflag(v->parm4, true);
+void AgiEngine::inDestination(ScreenObjEntry *screenObj) {
+ if (screenObj->motionType == kMotionMoveObj) {
+ screenObj->stepSize = screenObj->move_stepSize;
+ setflag(screenObj->move_flag, true);
+ }
+ screenObj->motionType = kMotionNormal;
+ if (isEgoView(screenObj))
+ _game.playerControl = true;
+}
+
+void AgiEngine::motionMoveObjStop(ScreenObjEntry *screenObj) {
+ screenObj->stepSize = screenObj->move_stepSize;
+ if (screenObj->motionType != kMotionEgo) {
+ setflag(screenObj->move_flag, true);
}
- v->motion = kMotionNormal;
- if (isEgoView(v))
+
+ screenObj->motionType = kMotionNormal;
+ if (isEgoView(screenObj)) {
_game.playerControl = true;
+ _game.vars[VM_VAR_EGO_DIRECTION] = 0;
+ }
}
/**
@@ -209,8 +226,8 @@ void AgiEngine::inDestination(VtEntry *v) {
* after setting the motion mode to kMotionMoveObj.
* @param v Pointer to view table entry
*/
-void AgiEngine::moveObj(VtEntry *v) {
- motionMoveObj(v);
+void AgiEngine::moveObj(ScreenObjEntry *screenObj) {
+ motionMoveObj(screenObj);
}
/**
@@ -223,9 +240,9 @@ void AgiEngine::moveObj(VtEntry *v) {
* @param y y coordinate of the object
* @param s step size
*/
-int AgiEngine::getDirection(int x0, int y0, int x, int y, int s) {
+int AgiEngine::getDirection(int16 objX, int16 objY, int16 destX, int16 destY, int16 stepSize) {
int dirTable[9] = { 8, 1, 2, 7, 0, 3, 6, 5, 4 };
- return dirTable[checkStep(x - x0, s) + 3 * checkStep(y - y0, s)];
+ return dirTable[checkStep(destX - objX, stepSize) + 3 * checkStep(destY - objY, stepSize)];
}
} // End of namespace Agi
diff --git a/engines/agi/mouse_cursor.h b/engines/agi/mouse_cursor.h
new file mode 100644
index 0000000000..1883960451
--- /dev/null
+++ b/engines/agi/mouse_cursor.h
@@ -0,0 +1,183 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef AGI_MOUSE_CURSOR_H
+#define AGI_MOUSE_CURSOR_H
+
+namespace Agi {
+
+/**
+ * RGB-palette for the Amiga-style arrow cursor
+ * and the Amiga-style busy cursor.
+ */
+static const byte MOUSECURSOR_PALETTE[] = {
+ 0x00, 0x00, 0x00, // Black
+ 0xFF, 0xFF, 0xFF, // White
+ 0xDE, 0x20, 0x21, // Red
+ 0xFF, 0xCF, 0xAD // Light red
+};
+
+/**
+ * A black and white SCI-style arrow cursor (11x16).
+ * 0 = Transparent.
+ * 1 = Black (#000000 in 24-bit RGB).
+ * 2 = White (#FFFFFF in 24-bit RGB).
+ */
+static const byte MOUSECURSOR_SCI[] = {
+ 1,1,0,0,0,0,0,0,0,0,0,
+ 1,2,1,0,0,0,0,0,0,0,0,
+ 1,2,2,1,0,0,0,0,0,0,0,
+ 1,2,2,2,1,0,0,0,0,0,0,
+ 1,2,2,2,2,1,0,0,0,0,0,
+ 1,2,2,2,2,2,1,0,0,0,0,
+ 1,2,2,2,2,2,2,1,0,0,0,
+ 1,2,2,2,2,2,2,2,1,0,0,
+ 1,2,2,2,2,2,2,2,2,1,0,
+ 1,2,2,2,2,2,2,2,2,2,1,
+ 1,2,2,2,2,2,1,0,0,0,0,
+ 1,2,1,0,1,2,2,1,0,0,0,
+ 1,1,0,0,1,2,2,1,0,0,0,
+ 0,0,0,0,0,1,2,2,1,0,0,
+ 0,0,0,0,0,1,2,2,1,0,0,
+ 0,0,0,0,0,0,1,2,2,1,0
+};
+
+/**
+ * A black and white SCI-style busy cursor (15x16).
+ * 0 = Transparent.
+ * 1 = Black (#000000 in 24-bit RGB).
+ * 2 = White (#FFFFFF in 24-bit RGB).
+ */
+static const byte MOUSECURSOR_SCI_BUSY[] = {
+ 0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,
+ 0,0,0,0,1,1,1,2,2,1,1,1,0,0,0,
+ 0,0,0,1,2,2,1,2,2,1,2,2,1,0,0,
+ 0,1,1,1,2,2,1,2,2,1,2,2,1,0,0,
+ 1,2,2,1,2,2,1,2,2,1,2,2,1,0,0,
+ 1,2,2,1,2,2,1,2,2,1,2,2,1,0,0,
+ 1,2,2,1,2,2,1,2,2,1,2,1,2,1,0,
+ 1,2,2,1,2,2,1,2,2,1,1,2,2,1,1,
+ 1,2,2,1,2,2,1,2,2,1,1,2,2,2,1,
+ 1,2,2,2,2,2,2,2,2,1,1,2,2,2,1,
+ 1,2,2,2,2,2,2,2,1,2,2,2,2,1,0,
+ 1,2,2,2,2,2,2,1,2,2,2,2,2,1,0,
+ 0,1,2,2,2,2,2,1,2,2,2,2,1,0,0,
+ 0,1,2,2,2,2,2,2,2,2,2,2,1,0,0,
+ 0,0,1,2,2,2,2,2,2,2,2,1,0,0,0,
+ 0,0,0,1,1,1,1,1,1,1,1,0,0,0,0
+};
+
+/**
+ * A black and white Atari ST style arrow cursor (11x16).
+ * 0 = Transparent.
+ * 1 = Black (#000000 in 24-bit RGB).
+ * 2 = White (#FFFFFF in 24-bit RGB).
+ */
+static const byte MOUSECURSOR_ATARI_ST[] = {
+ 2,2,0,0,0,0,0,0,0,0,0,
+ 2,1,2,0,0,0,0,0,0,0,0,
+ 2,1,1,2,0,0,0,0,0,0,0,
+ 2,1,1,1,2,0,0,0,0,0,0,
+ 2,1,1,1,1,2,0,0,0,0,0,
+ 2,1,1,1,1,1,2,0,0,0,0,
+ 2,1,1,1,1,1,1,2,0,0,0,
+ 2,1,1,1,1,1,1,1,2,0,0,
+ 2,1,1,1,1,1,1,1,1,2,0,
+ 2,1,1,1,1,1,2,2,2,2,2,
+ 2,1,1,2,1,1,2,0,0,0,0,
+ 2,1,2,0,2,1,1,2,0,0,0,
+ 2,2,0,0,2,1,1,2,0,0,0,
+ 2,0,0,0,0,2,1,1,2,0,0,
+ 0,0,0,0,0,2,1,1,2,0,0,
+ 0,0,0,0,0,0,2,2,2,0,0
+};
+
+/**
+ * A black and white Apple IIGS style arrow cursor (9x11).
+ * 0 = Transparent.
+ * 1 = Black (#000000 in 24-bit RGB).
+ * 2 = White (#FFFFFF in 24-bit RGB).
+ */
+static const byte MOUSECURSOR_APPLE_II_GS[] = {
+ 2,2,0,0,0,0,0,0,0,
+ 2,1,2,0,0,0,0,0,0,
+ 2,1,1,2,0,0,0,0,0,
+ 2,1,1,1,2,0,0,0,0,
+ 2,1,1,1,1,2,0,0,0,
+ 2,1,1,1,1,1,2,0,0,
+ 2,1,1,1,1,1,1,2,0,
+ 2,1,1,1,1,1,1,1,2,
+ 2,1,1,2,1,1,2,2,0,
+ 2,2,2,0,2,1,1,2,0,
+ 0,0,0,0,0,2,2,2,0
+};
+
+/**
+ * An Amiga-style arrow cursor (8x11).
+ * 0 = Transparent.
+ * 1 = Black (#000000 in 24-bit RGB).
+ * 3 = Red (#DE2021 in 24-bit RGB).
+ * 4 = Light red (#FFCFAD in 24-bit RGB).
+ */
+static const byte MOUSECURSOR_AMIGA[] = {
+ 3,4,1,0,0,0,0,0,
+ 3,3,4,1,0,0,0,0,
+ 3,3,3,4,1,0,0,0,
+ 3,3,3,3,4,1,0,0,
+ 3,3,3,3,3,4,1,0,
+ 3,3,3,3,3,3,4,1,
+ 3,0,3,3,4,1,0,0,
+ 0,0,0,3,4,1,0,0,
+ 0,0,0,3,3,4,1,0,
+ 0,0,0,0,3,4,1,0,
+ 0,0,0,0,3,3,4,1
+};
+
+/**
+ * An Amiga-style busy cursor showing an hourglass (13x16).
+ * 0 = Transparent.
+ * 1 = Black (#000000 in 24-bit RGB).
+ * 2 = Red (#DE2021 in 24-bit RGB).
+ * 3 = Light red (#FFCFAD in 24-bit RGB).
+ */
+static const byte MOUSECURSOR_AMIGA_BUSY[] = {
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,3,3,3,3,3,3,3,3,3,3,3,1,
+ 1,3,3,3,3,3,3,3,3,3,3,3,1,
+ 0,1,4,4,4,4,4,4,4,4,4,1,0,
+ 0,0,1,4,4,4,4,4,4,4,1,0,0,
+ 0,0,0,1,4,4,4,4,4,1,0,0,0,
+ 0,0,0,0,1,4,4,4,1,0,0,0,0,
+ 0,0,0,0,0,1,4,1,0,0,0,0,0,
+ 0,0,0,0,0,1,4,1,0,0,0,0,0,
+ 0,0,0,0,1,3,4,3,1,0,0,0,0,
+ 0,0,0,1,3,3,4,3,3,1,0,0,0,
+ 0,0,1,3,3,3,4,3,3,3,1,0,0,
+ 0,1,3,3,3,4,4,4,3,3,3,1,0,
+ 1,4,4,4,4,4,4,4,4,4,4,4,1,
+ 1,4,4,4,4,4,4,4,4,4,4,4,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1
+};
+
+} // End of namespace Agi
+
+#endif /* AGI_MOUSE_CURSOR_H */
diff --git a/engines/agi/objects.cpp b/engines/agi/objects.cpp
index 27cde61065..b6c628c32d 100644
--- a/engines/agi/objects.cpp
+++ b/engines/agi/objects.cpp
@@ -144,28 +144,28 @@ void AgiEngine::unloadObjects() {
}
}
-void AgiEngine::objectSetLocation(unsigned int n, int i) {
- if (n >= _game.numObjects) {
- warning("AgiEngine::objectSetLocation: Can't access object %d.\n", n);
+void AgiEngine::objectSetLocation(uint16 objectNr, int i) {
+ if (objectNr >= _game.numObjects) {
+ warning("AgiEngine::objectSetLocation: Can't access object %d.\n", objectNr);
return;
}
- _objects[n].location = i;
+ _objects[objectNr].location = i;
}
-int AgiEngine::objectGetLocation(unsigned int n) {
- if (n >= _game.numObjects) {
- warning("AgiEngine::objectGetLocation: Can't access object %d.\n", n);
+int AgiEngine::objectGetLocation(uint16 objectNr) {
+ if (objectNr >= _game.numObjects) {
+ warning("AgiEngine::objectGetLocation: Can't access object %d.\n", objectNr);
return 0;
}
- return _objects[n].location;
+ return _objects[objectNr].location;
}
-const char *AgiEngine::objectName(unsigned int n) {
- if (n >= _game.numObjects) {
- warning("AgiEngine::objectName: Can't access object %d.\n", n);
+const char *AgiEngine::objectName(uint16 objectNr) {
+ if (objectNr >= _game.numObjects) {
+ warning("AgiEngine::objectName: Can't access object %d.\n", objectNr);
return "";
}
- return _objects[n].name;
+ return _objects[objectNr].name;
}
} // End of namespace Agi
diff --git a/engines/agi/op_cmd.cpp b/engines/agi/op_cmd.cpp
index bf2a2ed77b..bb10c27d6e 100644
--- a/engines/agi/op_cmd.cpp
+++ b/engines/agi/op_cmd.cpp
@@ -23,55 +23,49 @@
#include "base/version.h"
#include "agi/agi.h"
+#include "agi/inv.h"
#include "agi/sprite.h"
+#include "agi/text.h"
#include "agi/graphics.h"
#include "agi/opcodes.h"
#include "agi/menu.h"
+#include "agi/systemui.h"
+#include "agi/words.h"
#include "common/random.h"
#include "common/textconsole.h"
namespace Agi {
-#define p0 (p[0])
-#define p1 (p[1])
-#define p2 (p[2])
-#define p3 (p[3])
-#define p4 (p[4])
-#define p5 (p[5])
-#define p6 (p[6])
-
-#define code state->_curLogic->data
-#define ip state->_curLogic->cIP
-#define vt state->viewTable[p0]
-#define vt_v state->viewTable[state->vars[p0]]
-
-#define _v state->vars
-
#define getGameID() state->_vm->getGameID()
#define getFeatures() state->_vm->getFeatures()
#define getVersion() state->_vm->getVersion()
#define getLanguage() state->_vm->getLanguage()
-#define setflag(a,b) state->_vm->setflag(a,b)
-#define getflag(a) state->_vm->getflag(a)
-void cmdIncrement(AgiGame *state, uint8 *p) {
+void cmdIncrement(AgiGame *state, uint8 *parameter) {
+ uint16 varNr = parameter[0];
+
if (getVersion() < 0x2000) {
- if (_v[p0] < 0xf0)
- ++_v[p0];
+ if (state->vars[varNr] < 0xf0)
+ ++state->vars[varNr];
} else {
- if (_v[p0] != 0xff)
- ++_v[p0];
+ if (state->vars[varNr] != 0xff)
+ ++state->vars[varNr];
}
}
-void cmdDecrement(AgiGame *state, uint8 *p) {
- if (_v[p0] != 0)
- --_v[p0];
+void cmdDecrement(AgiGame *state, uint8 *parameter) {
+ uint16 varNr = parameter[0];
+
+ if (state->vars[varNr] != 0)
+ --state->vars[varNr];
}
-void cmdAssignN(AgiGame *state, uint8 *p) {
- _v[p0] = p1;
+void cmdAssignN(AgiGame *state, uint8 *parameter) {
+ uint16 varNr = parameter[0];
+ uint16 value = parameter[1];
+
+ state->vars[varNr] = value;
// WORKAROUND for a bug in fan game "Get outta SQ"
// Total number of points is stored in variable 7, which
@@ -80,104 +74,170 @@ void cmdAssignN(AgiGame *state, uint8 *p) {
// variable to the correct value here
// Fixes bug #1942476 - "AGI: Fan(Get Outta SQ) - Score
// is lost on restart"
- if (getGameID() == GID_GETOUTTASQ && p0 == 7)
- _v[p0] = 8;
+ if (getGameID() == GID_GETOUTTASQ && varNr == 7)
+ state->vars[varNr] = 8;
}
-void cmdAddN(AgiGame *state, uint8 *p) {
- _v[p0] += p1;
+void cmdAddN(AgiGame *state, uint8 *parameter) {
+ uint16 varNr = parameter[0];
+ uint16 value = parameter[1];
+
+ state->vars[varNr] += value;
}
-void cmdSubN(AgiGame *state, uint8 *p) {
- _v[p0] -= p1;
+void cmdSubN(AgiGame *state, uint8 *parameter) {
+ uint16 varNr = parameter[0];
+ uint16 value = parameter[1];
+
+ state->vars[varNr] -= value;
}
-void cmdAssignV(AgiGame *state, uint8 *p) {
- _v[p0] = _v[p1];
+void cmdAssignV(AgiGame *state, uint8 *parameter) {
+ uint16 varNr1 = parameter[0];
+ uint16 varNr2 = parameter[1];
+
+ state->vars[varNr1] = state->vars[varNr2];
}
-void cmdAddV(AgiGame *state, uint8 *p) {
- _v[p0] += _v[p1];
+void cmdAddV(AgiGame *state, uint8 *parameter) {
+ uint16 varNr1 = parameter[0];
+ uint16 varNr2 = parameter[1];
+
+ state->vars[varNr1] += state->vars[varNr2];
}
-void cmdSubV(AgiGame *state, uint8 *p) {
- _v[p0] -= _v[p1];
+void cmdSubV(AgiGame *state, uint8 *parameter) {
+ uint16 varNr1 = parameter[0];
+ uint16 varNr2 = parameter[1];
+
+ state->vars[varNr1] -= state->vars[varNr2];
}
-void cmdMulN(AgiGame *state, uint8 *p) {
- _v[p0] *= p1;
+void cmdMulN(AgiGame *state, uint8 *parameter) {
+ uint16 varNr = parameter[0];
+ uint16 value = parameter[1];
+
+ state->vars[varNr] *= value;
}
-void cmdMulV(AgiGame *state, uint8 *p) {
- _v[p0] *= _v[p1];
+void cmdMulV(AgiGame *state, uint8 *parameter) {
+ uint16 varNr1 = parameter[0];
+ uint16 varNr2 = parameter[1];
+
+ state->vars[varNr1] *= state->vars[varNr2];
}
-void cmdDivN(AgiGame *state, uint8 *p) {
- _v[p0] /= p1;
+void cmdDivN(AgiGame *state, uint8 *parameter) {
+ uint16 varNr = parameter[0];
+ uint16 value = parameter[1];
+
+ state->vars[varNr] /= value;
}
-void cmdDivV(AgiGame *state, uint8 *p) {
- _v[p0] /= _v[p1];
+void cmdDivV(AgiGame *state, uint8 *parameter) {
+ uint16 varNr1 = parameter[0];
+ uint16 varNr2 = parameter[1];
+
+ state->vars[varNr1] /= state->vars[varNr2];
}
-void cmdRandomV1(AgiGame *state, uint8 *p) {
- _v[p0] = state->_vm->_rnd->getRandomNumber(250);
+void cmdRandomV1(AgiGame *state, uint8 *parameter) {
+ uint16 varNr = parameter[0];
+
+ state->vars[varNr] = state->_vm->_rnd->getRandomNumber(250);
}
-void cmdRandom(AgiGame *state, uint8 *p) {
- _v[p2] = state->_vm->_rnd->getRandomNumber(p1 - p0) + p0;
+void cmdRandom(AgiGame *state, uint8 *parameter) {
+ uint16 valueMin = parameter[0];
+ uint16 valueMax = parameter[1];
+ uint16 varNr = parameter[2];
+
+ state->vars[varNr] = state->_vm->_rnd->getRandomNumber(valueMax - valueMin) + valueMin;
}
-void cmdLindirectN(AgiGame *state, uint8 *p) {
- _v[_v[p0]] = p1;
+void cmdLindirectN(AgiGame *state, uint8 *parameter) {
+ uint16 varNr = parameter[0];
+ uint16 value = parameter[1];
+
+ state->vars[state->vars[varNr]] = value;
}
-void cmdLindirectV(AgiGame *state, uint8 *p) {
- _v[_v[p0]] = _v[p1];
+void cmdLindirectV(AgiGame *state, uint8 *parameter) {
+ uint16 varNr1 = parameter[0];
+ uint16 varNr2 = parameter[1];
+
+ state->vars[state->vars[varNr1]] = state->vars[varNr2];
}
-void cmdRindirect(AgiGame *state, uint8 *p) {
- _v[p0] = _v[_v[p1]];
+void cmdRindirect(AgiGame *state, uint8 *parameter) {
+ uint16 varNr1 = parameter[0];
+ uint16 varNr2 = parameter[1];
+
+ state->vars[varNr1] = state->vars[state->vars[varNr2]];
}
-void cmdSet(AgiGame *state, uint8 *p) {
- setflag(*p, true);
+void cmdSet(AgiGame *state, uint8 *parameter) {
+ uint16 flagNr = parameter[0];
+
+ state->_vm->setflag(flagNr, true);
}
-void cmdReset(AgiGame *state, uint8 *p) {
- setflag(*p, false);
+void cmdReset(AgiGame *state, uint8 *parameter) {
+ uint16 flagNr = parameter[0];
+
+ state->_vm->setflag(flagNr, false);
}
-void cmdToggle(AgiGame *state, uint8 *p) {
- setflag(*p, !getflag(*p));
+void cmdToggle(AgiGame *state, uint8 *parameter) {
+ AgiEngine *vm = state->_vm;
+ uint16 flagNr = parameter[0];
+ bool curFlagState = vm->getflag(flagNr);
+
+ vm->setflag(flagNr, !curFlagState);
}
-void cmdSetV(AgiGame *state, uint8 *p) {
+void cmdSetV(AgiGame *state, uint8 *parameter) {
+ uint16 flagNr = parameter[0];
+
if (getVersion() < 0x2000) {
- _v[p0] = 1;
+ state->vars[flagNr] = 1;
} else {
- setflag(_v[p0], true);
+ flagNr = state->vars[flagNr];
+
+ state->_vm->setflag(flagNr, true);
}
}
-void cmdResetV(AgiGame *state, uint8 *p) {
+void cmdResetV(AgiGame *state, uint8 *parameter) {
+ uint16 flagNr = parameter[0];
+
if (getVersion() < 0x2000) {
- _v[p0] = 0;
+ state->vars[flagNr] = 0;
} else {
- setflag(_v[p0], false);
+ flagNr = state->vars[flagNr];
+
+ state->_vm->setflag(flagNr, false);
}
}
-void cmdToggleV(AgiGame *state, uint8 *p) {
+void cmdToggleV(AgiGame *state, uint8 *parameter) {
+ uint16 flagNr = parameter[0];
+
if (getVersion() < 0x2000) {
- _v[p0] ^= 1;
+ state->vars[flagNr] ^= 1;
} else {
- setflag(_v[p0], !getflag(_v[p0]));
+ AgiEngine *vm = state->_vm;
+ flagNr = state->vars[flagNr];
+ bool curFlagState = vm->getflag(flagNr);
+
+ vm->setflag(flagNr, !curFlagState);
}
}
-void cmdNewRoom(AgiGame *state, uint8 *p) {
- state->_vm->newRoom(p0);
+void cmdNewRoom(AgiGame *state, uint8 *parameter) {
+ uint16 newRoomNr = parameter[0];
+
+ state->_vm->newRoom(newRoomNr);
// WORKAROUND: Works around intro skipping bug (#1737343) in Gold Rush.
// Intro was skipped because the enter-keypress finalizing the entering
@@ -188,351 +248,524 @@ void cmdNewRoom(AgiGame *state, uint8 *p) {
// keyboard buffer when the intro sequence's first room (Room 73) is
// loaded so that no keys from the copy protection scene can be left
// over to cause the intro to skip to the game's start.
- if (getGameID() == GID_GOLDRUSH && p0 == 73)
+ if (getGameID() == GID_GOLDRUSH && newRoomNr == 73)
state->keypress = 0;
}
-void cmdNewRoomF(AgiGame *state, uint8 *p) {
- state->_vm->newRoom(_v[p0]);
+void cmdNewRoomF(AgiGame *state, uint8 *parameter) {
+ uint16 varNr = parameter[0];
+
+ state->_vm->newRoom(state->vars[varNr]);
}
-void cmdLoadView(AgiGame *state, uint8 *p) {
- state->_vm->agiLoadResource(rVIEW, p0);
+void cmdLoadView(AgiGame *state, uint8 *parameter) {
+ uint16 resourceNr = parameter[0];
+
+ state->_vm->agiLoadResource(RESOURCETYPE_VIEW, resourceNr);
}
-void cmdLoadLogic(AgiGame *state, uint8 *p) {
- state->_vm->agiLoadResource(rLOGIC, p0);
+void cmdLoadLogic(AgiGame *state, uint8 *parameter) {
+ uint16 resourceNr = parameter[0];
+
+ state->_vm->agiLoadResource(RESOURCETYPE_LOGIC, resourceNr);
}
-void cmdLoadSound(AgiGame *state, uint8 *p) {
- state->_vm->agiLoadResource(rSOUND, p0);
+void cmdLoadSound(AgiGame *state, uint8 *parameter) {
+ uint16 resourceNr = parameter[0];
+
+ state->_vm->agiLoadResource(RESOURCETYPE_SOUND, resourceNr);
}
-void cmdLoadViewF(AgiGame *state, uint8 *p) {
- state->_vm->agiLoadResource(rVIEW, _v[p0]);
+void cmdLoadViewF(AgiGame *state, uint8 *parameter) {
+ uint16 varNr = parameter[0];
+
+ state->_vm->agiLoadResource(RESOURCETYPE_VIEW, state->vars[varNr]);
}
-void cmdLoadLogicF(AgiGame *state, uint8 *p) {
- state->_vm->agiLoadResource(rLOGIC, _v[p0]);
+void cmdLoadLogicF(AgiGame *state, uint8 *parameter) {
+ uint16 varNr = parameter[0];
+
+ state->_vm->agiLoadResource(RESOURCETYPE_LOGIC, state->vars[varNr]);
}
-void cmdDiscardView(AgiGame *state, uint8 *p) {
- state->_vm->agiUnloadResource(rVIEW, p0);
+void cmdDiscardView(AgiGame *state, uint8 *parameter) {
+ uint16 resourceNr = parameter[0];
+
+ state->_vm->agiUnloadResource(RESOURCETYPE_VIEW, resourceNr);
}
-void cmdObjectOnAnything(AgiGame *state, uint8 *p) {
- vt.flags &= ~(fOnWater | fOnLand);
+void cmdObjectOnAnything(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ screenObj->flags &= ~(fOnWater | fOnLand);
}
-void cmdObjectOnLand(AgiGame *state, uint8 *p) {
- vt.flags |= fOnLand;
+void cmdObjectOnLand(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ screenObj->flags |= fOnLand;
}
-void cmdObjectOnWater(AgiGame *state, uint8 *p) {
- vt.flags |= fOnWater;
+void cmdObjectOnWater(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ screenObj->flags |= fOnWater;
}
-void cmdObserveHorizon(AgiGame *state, uint8 *p) {
- vt.flags &= ~fIgnoreHorizon;
+void cmdObserveHorizon(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ screenObj->flags &= ~fIgnoreHorizon;
}
-void cmdIgnoreHorizon(AgiGame *state, uint8 *p) {
- vt.flags |= fIgnoreHorizon;
+void cmdIgnoreHorizon(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ screenObj->flags |= fIgnoreHorizon;
}
-void cmdObserveObjs(AgiGame *state, uint8 *p) {
- vt.flags &= ~fIgnoreObjects;
+void cmdObserveObjs(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ screenObj->flags &= ~fIgnoreObjects;
}
-void cmdIgnoreObjs(AgiGame *state, uint8 *p) {
- vt.flags |= fIgnoreObjects;
+void cmdIgnoreObjs(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ screenObj->flags |= fIgnoreObjects;
}
-void cmdObserveBlocks(AgiGame *state, uint8 *p) {
- vt.flags &= ~fIgnoreBlocks;
+void cmdObserveBlocks(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ screenObj->flags &= ~fIgnoreBlocks;
}
-void cmdIgnoreBlocks(AgiGame *state, uint8 *p) {
- vt.flags |= fIgnoreBlocks;
+void cmdIgnoreBlocks(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ screenObj->flags |= fIgnoreBlocks;
}
-void cmdSetHorizon(AgiGame *state, uint8 *p) {
- state->horizon = p0;
+void cmdSetHorizon(AgiGame *state, uint8 *parameter) {
+ uint16 horizonValue = parameter[0];
+
+ state->horizon = horizonValue;
}
-void cmdGetPriority(AgiGame *state, uint8 *p) {
- _v[p1] = vt.priority;
+void cmdGetPriority(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 varNr = parameter[1];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ state->vars[varNr] = screenObj->priority;
}
-void cmdSetPriority(AgiGame *state, uint8 *p) {
- vt.flags |= fFixedPriority;
- vt.priority = p1;
+void cmdSetPriority(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 priority = parameter[1];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
- // WORKAROUND: this fixes bug #1712585 in KQ4 (dwarf sprite priority)
- // For this scene, ego (Rosella) hasn't got a fixed priority till script 54
- // explicitly sets priority 8 for her, so that she can walk back to the table
- // without being drawn over the other dwarfs
- // It seems that in this scene, ego's priority is set to 8, but the priority of
- // the last dwarf with the soup bowls (view 152) is also set to 8, which causes
- // the dwarf to be drawn behind ego
- // With this workaround, when the game scripts set the priority of view 152
- // (seventh dwarf with soup bowls), ego's priority is set to 7
- // The game script itself sets priotity 8 for ego before she starts walking,
- // and then releases the fixed priority set on ego after ego is seated
- // Therefore, this workaround only affects that specific part of this scene
- // Ego is set to object 19 by script 54
- if (getGameID() == GID_KQ4 && vt.currentView == 152) {
- state->viewTable[19].flags |= fFixedPriority;
- state->viewTable[19].priority = 7;
- }
+ screenObj->flags |= fFixedPriority;
+ screenObj->priority = priority;
}
-void cmdSetPriorityF(AgiGame *state, uint8 *p) {
- vt.flags |= fFixedPriority;
- vt.priority = _v[p1];
+void cmdSetPriorityF(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 varNr = parameter[1];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ screenObj->flags |= fFixedPriority;
+ screenObj->priority = state->vars[varNr];
}
-void cmdReleasePriority(AgiGame *state, uint8 *p) {
- vt.flags &= ~fFixedPriority;
+void cmdReleasePriority(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ screenObj->flags &= ~fFixedPriority;
}
-void cmdSetUpperLeft(AgiGame *state, uint8 *p) { // do nothing (AGI 2.917)
+void cmdSetUpperLeft(AgiGame *state, uint8 *parameter) { // do nothing (AGI 2.917)
}
-void cmdStartUpdate(AgiGame *state, uint8 *p) {
- state->_vm->startUpdate(&vt);
+void cmdStartUpdate(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ state->_vm->startUpdate(screenObj);
}
-void cmdStopUpdate(AgiGame *state, uint8 *p) {
- state->_vm->stopUpdate(&vt);
+void cmdStopUpdate(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ state->_vm->stopUpdate(screenObj);
}
-void cmdCurrentView(AgiGame *state, uint8 *p) {
- _v[p1] = vt.currentView;
+void cmdCurrentView(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 varNr = parameter[1];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ state->vars[varNr] = screenObj->currentViewNr;
}
-void cmdCurrentCel(AgiGame *state, uint8 *p) {
- _v[p1] = vt.currentCel;
- debugC(4, kDebugLevelScripts, "v%d=%d", p1, _v[p1]);
+void cmdCurrentCel(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 varNr = parameter[1];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ state->vars[varNr] = screenObj->currentCelNr;
+ debugC(4, kDebugLevelScripts, "v%d=%d", varNr, state->vars[varNr]);
}
-void cmdCurrentLoop(AgiGame *state, uint8 *p) {
- _v[p1] = vt.currentLoop;
+void cmdCurrentLoop(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 varNr = parameter[1];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ state->vars[varNr] = screenObj->currentLoopNr;
}
-void cmdLastCel(AgiGame *state, uint8 *p) {
- _v[p1] = vt.loopData->numCels - 1;
+void cmdLastCel(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 varNr = parameter[1];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ state->vars[varNr] = screenObj->loopData->celCount - 1;
}
-void cmdSetCel(AgiGame *state, uint8 *p) {
- state->_vm->setCel(&vt, p1);
+void cmdSetCel(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 celNr = parameter[1];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+ state->_vm->setCel(screenObj, celNr);
if (getVersion() >= 0x2000) {
- vt.flags &= ~fDontupdate;
+ screenObj->flags &= ~fDontupdate;
}
}
-void cmdSetCelF(AgiGame *state, uint8 *p) {
- state->_vm->setCel(&vt, _v[p1]);
- vt.flags &= ~fDontupdate;
+void cmdSetCelF(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 varNr = parameter[1];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ state->_vm->setCel(screenObj, state->vars[varNr]);
+ screenObj->flags &= ~fDontupdate;
}
-void cmdSetView(AgiGame *state, uint8 *p) {
- state->_vm->setView(&vt, p1);
+void cmdSetView(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 viewNr = parameter[1];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ state->_vm->setView(screenObj, viewNr);
}
-void cmdSetViewF(AgiGame *state, uint8 *p) {
- state->_vm->setView(&vt, _v[p1]);
+void cmdSetViewF(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 varNr = parameter[1];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ state->_vm->setView(screenObj, state->vars[varNr]);
}
-void cmdSetLoop(AgiGame *state, uint8 *p) {
- state->_vm->setLoop(&vt, p1);
+void cmdSetLoop(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 loopNr = parameter[1];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ state->_vm->setLoop(screenObj, loopNr);
}
-void cmdSetLoopF(AgiGame *state, uint8 *p) {
- state->_vm->setLoop(&vt, _v[p1]);
+void cmdSetLoopF(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 varNr = parameter[1];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ state->_vm->setLoop(screenObj, state->vars[varNr]);
}
-void cmdNumberOfLoops(AgiGame *state, uint8 *p) {
- _v[p1] = vt.numLoops;
+void cmdNumberOfLoops(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 varNr = parameter[1];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ state->vars[varNr] = screenObj->loopCount;
}
-void cmdFixLoop(AgiGame *state, uint8 *p) {
- vt.flags |= fFixLoop;
+void cmdFixLoop(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ screenObj->flags |= fFixLoop;
}
-void cmdReleaseLoop(AgiGame *state, uint8 *p) {
- vt.flags &= ~fFixLoop;
+void cmdReleaseLoop(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ screenObj->flags &= ~fFixLoop;
}
-void cmdStepSize(AgiGame *state, uint8 *p) {
- vt.stepSize = _v[p1];
+void cmdStepSize(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 varNr = parameter[1];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ screenObj->stepSize = state->vars[varNr];
}
-void cmdStepTime(AgiGame *state, uint8 *p) {
- vt.stepTime = vt.stepTimeCount = _v[p1];
+void cmdStepTime(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 varNr = parameter[1];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ screenObj->stepTime = screenObj->stepTimeCount = state->vars[varNr];
}
-void cmdCycleTime(AgiGame *state, uint8 *p) {
- vt.cycleTime = vt.cycleTimeCount = _v[p1];
+void cmdCycleTime(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 varNr = parameter[1];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ screenObj->cycleTime = screenObj->cycleTimeCount = state->vars[varNr];
}
-void cmdStopCycling(AgiGame *state, uint8 *p) {
- vt.flags &= ~fCycling;
+void cmdStopCycling(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ screenObj->flags &= ~fCycling;
}
-void cmdStartCycling(AgiGame *state, uint8 *p) {
- vt.flags |= fCycling;
+void cmdStartCycling(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ screenObj->flags |= fCycling;
}
-void cmdNormalCycle(AgiGame *state, uint8 *p) {
- vt.cycle = kCycleNormal;
- vt.flags |= fCycling;
+void cmdNormalCycle(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ screenObj->cycle = kCycleNormal;
+ screenObj->flags |= fCycling;
}
-void cmdReverseCycle(AgiGame *state, uint8 *p) {
- vt.cycle = kCycleReverse;
- vt.flags |= fCycling;
+void cmdReverseCycle(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ screenObj->cycle = kCycleReverse;
+ screenObj->flags |= fCycling;
}
-void cmdSetDir(AgiGame *state, uint8 *p) {
- vt.direction = _v[p1];
+void cmdSetDir(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 varNr = parameter[1];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ screenObj->direction = state->vars[varNr];
}
-void cmdGetDir(AgiGame *state, uint8 *p) {
- _v[p1] = vt.direction;
+void cmdGetDir(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 varNr = parameter[1];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ state->vars[varNr] = screenObj->direction;
}
-void cmdGetRoomF(AgiGame *state, uint8 *p) {
- _v[p1] = state->_vm->objectGetLocation(_v[p0]);
+void cmdGetRoomF(AgiGame *state, uint8 *parameter) {
+ uint16 varNr1 = parameter[0];
+ uint16 varNr2 = parameter[1];
+
+ state->vars[varNr2] = state->_vm->objectGetLocation(state->vars[varNr1]);
}
-void cmdPut(AgiGame *state, uint8 *p) {
- state->_vm->objectSetLocation(p0, _v[p1]);
+void cmdPut(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 varNr = parameter[1];
+
+ state->_vm->objectSetLocation(objectNr, state->vars[varNr]);
}
-void cmdPutF(AgiGame *state, uint8 *p) {
- state->_vm->objectSetLocation(_v[p0], _v[p1]);
+void cmdPutF(AgiGame *state, uint8 *parameter) {
+ uint16 varNr1 = parameter[0];
+ uint16 varNr2 = parameter[1];
+
+ state->_vm->objectSetLocation(state->vars[varNr1], state->vars[varNr2]);
}
-void cmdDrop(AgiGame *state, uint8 *p) {
- state->_vm->objectSetLocation(p0, 0);
+void cmdDrop(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+
+ state->_vm->objectSetLocation(objectNr, 0);
}
-void cmdGet(AgiGame *state, uint8 *p) {
- state->_vm->objectSetLocation(p0, EGO_OWNED);
+void cmdGet(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+
+ state->_vm->objectSetLocation(objectNr, EGO_OWNED);
}
-void cmdGetV1(AgiGame *state, uint8 *p) {
- state->_vm->objectSetLocation(p0, EGO_OWNED_V1);
+void cmdGetV1(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+
+ state->_vm->objectSetLocation(objectNr, EGO_OWNED_V1);
}
-void cmdGetF(AgiGame *state, uint8 *p) {
- state->_vm->objectSetLocation(_v[p0], EGO_OWNED);
+void cmdGetF(AgiGame *state, uint8 *parameter) {
+ uint16 varNr = parameter[0];
+
+ state->_vm->objectSetLocation(state->vars[varNr], EGO_OWNED);
}
-void cmdWordToString(AgiGame *state, uint8 *p) {
- strcpy(state->strings[p0], state->egoWords[p1].word);
+void cmdWordToString(AgiGame *state, uint8 *parameter) {
+ uint16 stringNr = parameter[0];
+ uint16 wordNr = parameter[1];
+
+ strcpy(state->strings[stringNr], state->_vm->_words->getEgoWord(wordNr));
}
-void cmdOpenDialogue(AgiGame *state, uint8 *p) {
- state->hasWindow = true;
+void cmdOpenDialogue(AgiGame *state, uint8 *parameter) {
+ state->_vm->_text->dialogueOpen();
}
-void cmdCloseDialogue(AgiGame *state, uint8 *p) {
- state->hasWindow = false;
+void cmdCloseDialogue(AgiGame *state, uint8 *parameter) {
+ state->_vm->_text->dialogueClose();
}
-void cmdCloseWindow(AgiGame *state, uint8 *p) {
- state->_vm->closeWindow();
+void cmdCloseWindow(AgiGame *state, uint8 *parameter) {
+ state->_vm->_text->closeWindow();
}
-void cmdStatusLineOn(AgiGame *state, uint8 *p) {
- state->statusLine = true;
- state->_vm->writeStatus();
+void cmdStatusLineOn(AgiGame *state, uint8 *parameter) {
+ TextMgr *text = state->_vm->_text;
+
+ text->statusEnable();
+ text->statusDraw();
}
-void cmdStatusLineOff(AgiGame *state, uint8 *p) {
- state->statusLine = false;
- state->_vm->writeStatus();
+void cmdStatusLineOff(AgiGame *state, uint8 *parameter) {
+ TextMgr *text = state->_vm->_text;
+
+ text->statusDisable();
+ state->_vm->_text->statusClear();
}
-void cmdShowObj(AgiGame *state, uint8 *p) {
- state->_vm->_sprites->showObj(p0);
+void cmdShowObj(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+
+ state->_vm->_sprites->showObject(objectNr);
}
-void cmdShowObjV(AgiGame *state, uint8 *p) {
- state->_vm->_sprites->showObj(_v[p0]);
+void cmdShowObjV(AgiGame *state, uint8 *parameter) {
+ uint16 varNr = parameter[0];
+
+ state->_vm->_sprites->showObject(state->vars[varNr]);
}
-void cmdSound(AgiGame *state, uint8 *p) {
- state->_vm->_sound->startSound(p0, p1);
+void cmdSound(AgiGame *state, uint8 *parameter) {
+ uint16 resourceNr = parameter[0];
+ uint16 flagNr = parameter[1];
+
+ state->_vm->_sound->startSound(resourceNr, flagNr);
}
-void cmdStopSound(AgiGame *state, uint8 *p) {
+void cmdStopSound(AgiGame *state, uint8 *parameter) {
state->_vm->_sound->stopSound();
}
-void cmdMenuInput(AgiGame *state, uint8 *p) {
- state->_vm->newInputMode(INPUT_MENU);
+void cmdMenuInput(AgiGame *state, uint8 *parameter) {
+ AgiEngine *vm = state->_vm;
+
+ if (vm->getflag(VM_FLAG_MENUS_WORK)) {
+ vm->_menu->delayedExecute();
+ }
}
-void cmdEnableItem(AgiGame *state, uint8 *p) {
- state->_vm->_menu->setItem(p0, true);
+void cmdEnableItem(AgiGame *state, uint8 *parameter) {
+ uint16 controlCode = parameter[0];
+
+ state->_vm->_menu->itemEnable(controlCode);
}
-void cmdDisableItem(AgiGame *state, uint8 *p) {
- state->_vm->_menu->setItem(p0, false);
+void cmdDisableItem(AgiGame *state, uint8 *parameter) {
+ uint16 controlCode = parameter[0];
+
+ state->_vm->_menu->itemDisable(controlCode);
}
-void cmdSubmitMenu(AgiGame *state, uint8 *p) {
+void cmdSubmitMenu(AgiGame *state, uint8 *parameter) {
state->_vm->_menu->submit();
}
-void cmdSetScanStart(AgiGame *state, uint8 *p) {
+void cmdSetScanStart(AgiGame *state, uint8 *parameter) {
state->_curLogic->sIP = state->_curLogic->cIP;
}
-void cmdResetScanStart(AgiGame *state, uint8 *p) {
+void cmdResetScanStart(AgiGame *state, uint8 *parameter) {
state->_curLogic->sIP = 2;
}
-void cmdSaveGame(AgiGame *state, uint8 *p) {
- state->simpleSave ? state->_vm->saveGameSimple() : state->_vm->saveGameDialog();
-}
-
-void cmdLoadGame(AgiGame *state, uint8 *p) {
- assert(1);
- state->simpleSave ? state->_vm->loadGameSimple() : state->_vm->loadGameDialog();
+void cmdSaveGame(AgiGame *state, uint8 *parameter) {
+ if (state->automaticSave) {
+ if (state->_vm->saveGameAutomatic()) {
+ // automatic save succeded
+ return;
+ }
+ // fall back to regular dialog otherwise
+ }
+ state->_vm->saveGameDialog();
}
-void cmdInitDisk(AgiGame *state, uint8 *p) { // do nothing
+void cmdLoadGame(AgiGame *state, uint8 *parameter) {
+ if (state->automaticSave) {
+ if (state->_vm->loadGameAutomatic()) {
+ // automatic restore succeded
+ return;
+ }
+ // fall back to regular dialog otherwise
+ }
+ state->_vm->loadGameDialog();
}
-void cmdLog(AgiGame *state, uint8 *p) { // do nothing
+void cmdInitDisk(AgiGame *state, uint8 *parameter) { // do nothing
}
-void cmdTraceOn(AgiGame *state, uint8 *p) { // do nothing
+void cmdLog(AgiGame *state, uint8 *parameter) { // do nothing
}
-void cmdTraceInfo(AgiGame *state, uint8 *p) { // do nothing
+void cmdTraceOn(AgiGame *state, uint8 *parameter) { // do nothing
}
-void cmdShowMem(AgiGame *state, uint8 *p) {
- state->_vm->messageBox("Enough memory");
+void cmdTraceInfo(AgiGame *state, uint8 *parameter) { // do nothing
}
-void cmdInitJoy(AgiGame *state, uint8 *p) { // do nothing
+void cmdShowMem(AgiGame *state, uint8 *parameter) {
+ state->_vm->_text->messageBox("Enough memory");
}
-void cmdScriptSize(AgiGame *state, uint8 *p) {
- debug(0, "script.size(%d)", p0);
+void cmdInitJoy(AgiGame *state, uint8 *parameter) { // do nothing
}
-void cmdCancelLine(AgiGame *state, uint8 *p) {
- state->inputBuffer[0] = 0;
- state->cursorPos = 0;
- state->_vm->writePrompt();
+void cmdScriptSize(AgiGame *state, uint8 *parameter) {
+ debug(0, "script.size(%d)", parameter[0]);
}
// This implementation is based on observations of Amiga's Gold Rush.
@@ -545,13 +778,16 @@ void cmdCancelLine(AgiGame *state, uint8 *p) {
// 4051 (When ego is stationary),
// 471 (When walking on the first screen's bridge),
// 71 (When walking around, using the mouse or the keyboard).
-void cmdObjStatusF(AgiGame *state, uint8 *p) {
+void cmdObjStatusF(AgiGame *state, uint8 *parameter) {
+ uint16 varNr = parameter[0];
+ ScreenObjEntry *screenObj = &state->screenObjTable[state->vars[varNr]];
+
const char *cycleDesc; // Object's cycle description line
const char *motionDesc; // Object's motion description line
char msg[256]; // The whole object status message
// Generate cycle description line
- switch (vt_v.cycle) {
+ switch (screenObj->cycle) {
case kCycleNormal:
cycleDesc = "normal cycle";
break;
@@ -570,7 +806,7 @@ void cmdObjStatusF(AgiGame *state, uint8 *p) {
}
// Generate motion description line
- switch (vt_v.motion) {
+ switch (screenObj->motionType) {
case kMotionNormal:
motionDesc = "normal motion";
break;
@@ -599,14 +835,14 @@ void cmdObjStatusF(AgiGame *state, uint8 *p) {
"stepsize: %d\n" \
"%s\n" \
"%s",
- _v[p0],
- vt_v.xPos, vt_v.xSize,
- vt_v.yPos, vt_v.ySize,
- vt_v.priority,
- vt_v.stepSize,
+ state->vars[varNr],
+ screenObj->xPos, screenObj->xSize,
+ screenObj->yPos, screenObj->ySize,
+ screenObj->priority,
+ screenObj->stepSize,
cycleDesc,
motionDesc);
- state->_vm->messageBox(msg);
+ state->_vm->_text->messageBox(msg);
}
// unknown commands:
@@ -617,49 +853,67 @@ void cmdObjStatusF(AgiGame *state, uint8 *p) {
// unk_174: Change priority table (used in KQ4) -- j5
// unk_177: Disable menus completely -- j5
// unk_181: Deactivate keypressed control (default control of ego)
-void cmdSetSimple(AgiGame *state, uint8 *p) {
+void cmdSetSimple(AgiGame *state, uint8 *parameter) {
if (!(getFeatures() & (GF_AGI256 | GF_AGI256_2))) {
- state->simpleSave = true;
+ int16 stringNr = parameter[0];
+ const char *textPtr = nullptr;
+
+ state->automaticSave = false;
+
+ // Try to get description for automatic saves
+// if (state->strings_curLogic->texts && state->_curLogic->numTexts >= textNr) {
+ textPtr = state->strings[stringNr];
+ strncpy(state->automaticSaveDescription, textPtr, sizeof(state->automaticSaveDescription));
+ if (state->automaticSaveDescription[0]) {
+ // We got it and it's set, so enable automatic saving
+ state->automaticSave = true;
+ }
+// }
+
} else { // AGI256 and AGI256-2 use this unknown170 command to load 256 color pictures.
// Load the picture. Similar to void cmdLoad_pic(AgiGame *state, uint8 *p).
- state->_vm->_sprites->eraseBoth();
- state->_vm->agiLoadResource(rPICTURE, _v[p0]);
+ SpritesMgr *spritesMgr = state->_vm->_sprites;
+ uint16 varNr = parameter[0];
+ uint16 resourceNr = state->vars[varNr];
+
+ spritesMgr->eraseSprites();
+ state->_vm->agiLoadResource(RESOURCETYPE_PICTURE, resourceNr);
// Draw the picture. Similar to void cmdDraw_pic(AgiGame *state, uint8 *p).
- state->_vm->_picture->decodePicture(_v[p0], false, true);
- state->_vm->_sprites->blitBoth();
- state->pictureShown = 0;
+ state->_vm->_picture->decodePicture(resourceNr, false, true);
+ spritesMgr->drawAllSpriteLists();
+ state->pictureShown = false;
// Show the picture. Similar to void cmdShow_pic(AgiGame *state, uint8 *p).
- setflag(fOutputMode, false);
- state->_vm->closeWindow();
+ state->_vm->setflag(VM_FLAG_OUTPUT_MODE, false);
+ state->_vm->_text->closeWindow();
state->_vm->_picture->showPic();
- state->pictureShown = 1;
+ state->pictureShown = true;
- // Simulate slowww computer. Many effects rely on this
- state->_vm->pause(kPausePicture);
+ // Loading trigger
+ state->_vm->loadingTrigger_DrawPicture();
}
}
-void cmdPopScript(AgiGame *state, uint8 *p) {
+void cmdPopScript(AgiGame *state, uint8 *parameter) {
if (getVersion() >= 0x2915) {
debug(0, "pop.script");
}
}
-void cmdHoldKey(AgiGame *state, uint8 *p) {
+void cmdHoldKey(AgiGame *state, uint8 *parameter) {
if (getVersion() >= 0x3098) {
state->_vm->_egoHoldKey = true;
}
}
-void cmdDiscardSound(AgiGame *state, uint8 *p) {
+void cmdDiscardSound(AgiGame *state, uint8 *parameter) {
if (getVersion() >= 0x2936) {
debug(0, "discard.sound");
}
}
-void cmdHideMouse(AgiGame *state, uint8 *p) {
+void cmdHideMouse(AgiGame *state, uint8 *parameter) {
// WORKAROUND: Turns off current movement that's being caused with the mouse.
// This fixes problems with too many popup boxes appearing in the Amiga
// Gold Rush's copy protection failure scene (i.e. the hanging scene, logic.192).
@@ -667,34 +921,48 @@ void cmdHideMouse(AgiGame *state, uint8 *p) {
// to walk somewhere else than to the right using the mouse.
// FIXME: Write a proper implementation using disassembly and
// apply it to other games as well if applicable.
- state->viewTable[0].flags &= ~fAdjEgoXY;
+ //state->screenObjTable[SCREENOBJECTS_EGO_ENTRY].flags &= ~fAdjEgoXY;
+ state->_vm->_game.mouseHidden = true;
g_system->showMouse(false);
}
-void cmdAllowMenu(AgiGame *state, uint8 *p) {
+void cmdAllowMenu(AgiGame *state, uint8 *parameter) {
+ uint16 allowed = parameter[0];
+
if (getVersion() >= 0x3098) {
- setflag(fMenusWork, ((p0 != 0) ? true : false));
+ if (allowed) {
+ state->_vm->_menu->accessAllow();
+ } else {
+ state->_vm->_menu->accessDeny();
+ }
}
}
-void cmdShowMouse(AgiGame *state, uint8 *p) {
+void cmdShowMouse(AgiGame *state, uint8 *parameter) {
+ state->_vm->_game.mouseHidden = false;
+
g_system->showMouse(true);
}
-void cmdFenceMouse(AgiGame *state, uint8 *p) {
- state->mouseFence.moveTo(p0, p1);
- state->mouseFence.setWidth(p2 - p0);
- state->mouseFence.setHeight(p3 - p1);
+void cmdFenceMouse(AgiGame *state, uint8 *parameter) {
+ uint16 varNr1 = parameter[0];
+ uint16 varNr2 = parameter[1];
+ uint16 varNr3 = parameter[2];
+ uint16 varNr4 = parameter[3];
+
+ state->mouseFence.moveTo(varNr1, varNr2);
+ state->mouseFence.setWidth(varNr3 - varNr1);
+ state->mouseFence.setHeight(varNr4 - varNr1);
}
-void cmdReleaseKey(AgiGame *state, uint8 *p) {
+void cmdReleaseKey(AgiGame *state, uint8 *parameter) {
if (getVersion() >= 0x3098) {
state->_vm->_egoHoldKey = false;
}
}
-void cmdAdjEgoMoveToXY(AgiGame *state, uint8 *p) {
+void cmdAdjEgoMoveToXY(AgiGame *state, uint8 *parameter) {
int8 x, y;
switch (logicNamesCmd[182].argumentsLength()) {
@@ -703,8 +971,8 @@ void cmdAdjEgoMoveToXY(AgiGame *state, uint8 *p) {
// (Using arguments (0, 0), (0, 7), (0, 8), (9, 9) and (-9, 9)).
case 2:
// Both arguments are signed 8-bit (i.e. in range -128 to +127).
- x = (int8) p0;
- y = (int8) p1;
+ x = (int8) parameter[0];
+ y = (int8) parameter[1];
// Turn off ego's current movement caused with the mouse if
// adj.ego.move.to.x.y is called with other arguments than previously.
@@ -717,7 +985,7 @@ void cmdAdjEgoMoveToXY(AgiGame *state, uint8 *p) {
// by something else because this command doesn't do any flag manipulations
// in the Amiga version - checked it with disassembly).
if (x != state->adjMouseX || y != state->adjMouseY)
- state->viewTable[EGO_VIEW_TABLE].flags &= ~fAdjEgoXY;
+ state->screenObjTable[SCREENOBJECTS_EGO_ENTRY].flags &= ~fAdjEgoXY;
state->adjMouseX = x;
state->adjMouseY = y;
@@ -727,20 +995,24 @@ void cmdAdjEgoMoveToXY(AgiGame *state, uint8 *p) {
// TODO: Check where (if anywhere) the 0 arguments version is used
case 0:
default:
- state->viewTable[0].flags |= fAdjEgoXY;
+ state->screenObjTable[SCREENOBJECTS_EGO_ENTRY].flags |= fAdjEgoXY;
break;
}
}
-void cmdParse(AgiGame *state, uint8 *p) {
- _v[vWordNotFound] = 0;
- setflag(fEnteredCli, false);
- setflag(fSaidAcceptedInput, false);
+void cmdParse(AgiGame *state, uint8 *parameter) {
+ TextMgr *text = state->_vm->_text;
+ uint16 stringNr = parameter[0];
+
+ state->vars[VM_VAR_WORD_NOT_FOUND] = 0;
+ state->_vm->setflag(VM_FLAG_ENTERED_CLI, false);
+ state->_vm->setflag(VM_FLAG_SAID_ACCEPTED_INPUT, false);
- state->_vm->dictionaryWords(state->_vm->agiSprintf(state->strings[p0]));
+ state->_vm->_words->parseUsingDictionary(text->stringPrintf(state->strings[stringNr]));
}
-void cmdCall(AgiGame *state, uint8 *p) {
+void cmdCall(AgiGame *state, uint8 *parameter) {
+ uint16 logicNr = parameter[0];
int oldCIP;
int oldLognum;
@@ -749,35 +1021,48 @@ void cmdCall(AgiGame *state, uint8 *p) {
oldCIP = state->_curLogic->cIP;
oldLognum = state->lognum;
- state->_vm->runLogic(p0);
+ state->_vm->runLogic(logicNr);
state->lognum = oldLognum;
state->_curLogic = &state->logics[state->lognum];
state->_curLogic->cIP = oldCIP;
}
-void cmdCallF(AgiGame *state, uint8 *p) {
- cmdCall(state, &_v[p0]);
+void cmdCallF(AgiGame *state, uint8 *parameter) {
+ uint16 varNr = parameter[0];
+
+ cmdCall(state, &state->vars[varNr]);
}
-void cmdDrawPicV1(AgiGame *state, uint8 *p) {
- debugC(6, kDebugLevelScripts, "=== draw pic V1 %d ===", _v[p0]);
- state->_vm->_picture->decodePicture(_v[p0], true);
+void cmdDrawPicV1(AgiGame *state, uint8 *parameter) {
+ uint16 varNr = parameter[0];
+ uint16 resourceNr = state->vars[varNr];
+
+ debugC(6, kDebugLevelScripts, "=== draw pic V1 %d ===", resourceNr);
+ state->_vm->_picture->decodePicture(resourceNr, true);
- state->_vm->clearPrompt();
+ // TODO: check, if this was really done
+ state->_vm->_text->promptClear();
- // Simulate slowww computer. Many effects rely on this
- state->_vm->pause(kPausePicture);
+ // Loading trigger
+ state->_vm->loadingTrigger_DrawPicture();
}
-void cmdDrawPic(AgiGame *state, uint8 *p) {
- debugC(6, kDebugLevelScripts, "=== draw pic %d ===", _v[p0]);
- state->_vm->_sprites->eraseBoth();
- state->_vm->_picture->decodePicture(_v[p0], true);
- state->_vm->_sprites->blitBoth();
- state->_vm->_sprites->commitBoth();
- state->pictureShown = 0;
- debugC(6, kDebugLevelScripts, "--- end of draw pic %d ---", _v[p0]);
+void cmdDrawPic(AgiGame *state, uint8 *parameter) {
+ SpritesMgr *spritesMgr = state->_vm->_sprites;
+ uint16 varNr = parameter[0];
+ uint16 resourceNr = state->vars[varNr];
+
+ debugC(6, kDebugLevelScripts, "=== draw pic %d ===", resourceNr);
+
+ spritesMgr->eraseSprites(); // === CHANGED ===
+ state->_vm->_picture->decodePicture(resourceNr, true);
+ spritesMgr->buildAllSpriteLists();
+ spritesMgr->drawAllSpriteLists(); // === CHANGED ===
+ //state->_vm->_sprites->blitBoth();
+ //state->_vm->_sprites->commitBoth();
+ state->pictureShown = false;
+ debugC(6, kDebugLevelScripts, "--- end of draw pic %d ---", resourceNr);
// WORKAROUND for a script bug which exists in SQ1, logic scripts
// 20 and 110. Flag 103 is not reset correctly, which leads to erroneous
@@ -790,484 +1075,640 @@ void cmdDrawPic(AgiGame *state, uint8 *p) {
// With this workaround, when the player goes back to picture 20 (1 screen
// above the ground), flag 103 is reset, thereby fixing this issue. Note
// that this is a script bug and occurs in the original interpreter as well.
- // Fixes bug #1658514: AGI: SQ1 (2.2 DOS ENG) bizzare exploding roger
- if (getGameID() == GID_SQ1 && _v[p0] == 20)
- setflag(103, false);
+ // Fixes bug #3056: AGI: SQ1 (2.2 DOS ENG) bizzare exploding roger
+ if (getGameID() == GID_SQ1 && resourceNr == 20)
+ state->_vm->setflag(103, false);
- // Simulate slowww computer. Many effects rely on this
- state->_vm->pause(kPausePicture);
+ // Loading trigger
+ state->_vm->loadingTrigger_DrawPicture();
}
-void cmdShowPic(AgiGame *state, uint8 *p) {
+void cmdShowPic(AgiGame *state, uint8 *parameter) {
debugC(6, kDebugLevelScripts, "=== show pic ===");
- setflag(fOutputMode, false);
- state->_vm->closeWindow();
- state->_vm->_picture->showPic();
- state->pictureShown = 1;
+ state->_vm->setflag(VM_FLAG_OUTPUT_MODE, false);
+ state->_vm->_text->closeWindow();
+ state->_vm->_picture->showPicWithTransition();
+ state->pictureShown = true;
debugC(6, kDebugLevelScripts, "--- end of show pic ---");
}
-void cmdLoadPic(AgiGame *state, uint8 *p) {
- state->_vm->_sprites->eraseBoth();
- state->_vm->agiLoadResource(rPICTURE, _v[p0]);
- state->_vm->_sprites->blitBoth();
- state->_vm->_sprites->commitBoth();
+void cmdLoadPic(AgiGame *state, uint8 *parameter) {
+ SpritesMgr *spritesMgr = state->_vm->_sprites;
+ uint16 varNr = parameter[0];
+ uint16 resourceNr = state->vars[varNr];
+
+ spritesMgr->eraseSprites();
+ state->_vm->agiLoadResource(RESOURCETYPE_PICTURE, resourceNr);
+ spritesMgr->buildAllSpriteLists();
+ spritesMgr->drawAllSpriteLists();
}
-void cmdLoadPicV1(AgiGame *state, uint8 *p) {
- state->_vm->agiLoadResource(rPICTURE, _v[p0]);
+void cmdLoadPicV1(AgiGame *state, uint8 *parameter) {
+ uint16 varNr = parameter[0];
+ uint16 resourceNr = state->vars[varNr];
+
+ state->_vm->agiLoadResource(RESOURCETYPE_PICTURE, resourceNr);
}
-void cmdDiscardPic(AgiGame *state, uint8 *p) {
+void cmdDiscardPic(AgiGame *state, uint8 *parameter) {
debugC(6, kDebugLevelScripts, "--- discard pic ---");
// do nothing
}
-void cmdOverlayPic(AgiGame *state, uint8 *p) {
+void cmdOverlayPic(AgiGame *state, uint8 *parameter) {
+ SpritesMgr *spritesMgr = state->_vm->_sprites;
+ uint16 varNr = parameter[0];
+ uint16 resourceNr = state->vars[varNr];
+
debugC(6, kDebugLevelScripts, "--- overlay pic ---");
- state->_vm->_sprites->eraseBoth();
- state->_vm->_picture->decodePicture(_v[p0], false);
- state->_vm->_sprites->blitBoth();
- state->pictureShown = 0;
- state->_vm->_sprites->commitBoth();
+ spritesMgr->eraseSprites();
+ state->_vm->_picture->decodePicture(resourceNr, false);
+ spritesMgr->buildAllSpriteLists();
+ spritesMgr->drawAllSpriteLists();
+ spritesMgr->showAllSpriteLists();
+ state->pictureShown = false;
- // Simulate slowww computer. Many effects rely on this
- state->_vm->pause(kPausePicture);
+ // Loading trigger
+ state->_vm->loadingTrigger_DrawPicture();
}
-void cmdShowPriScreen(AgiGame *state, uint8 *p) {
- state->_vm->_debug.priority = 1;
- state->_vm->_sprites->eraseBoth();
- state->_vm->_picture->showPic();
- state->_vm->_sprites->blitBoth();
+void cmdShowPriScreen(AgiGame *state, uint8 *parameter) {
+ GfxMgr *gfx = state->_vm->_gfx;
+
+ gfx->debugShowMap(1); // switch to priority map
state->_vm->waitKey();
- state->_vm->_debug.priority = 0;
- state->_vm->_sprites->eraseBoth();
- state->_vm->_picture->showPic();
- state->_vm->_sprites->blitBoth();
+ gfx->debugShowMap(0); // switch back to visual map
}
-void cmdAnimateObj(AgiGame *state, uint8 *p) {
+void cmdAnimateObj(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
if (getVersion() < 0x2000) {
- if (vt.flags & fDidntMove)
+ if (screenObj->flags & fDidntMove)
return;
} else {
- if (vt.flags & fAnimated)
+ if (screenObj->flags & fAnimated)
return;
}
- debugC(4, kDebugLevelScripts, "animate vt entry #%d", p0);
- vt.flags = fAnimated | fUpdate | fCycling;
+ debugC(4, kDebugLevelScripts, "animate vt entry #%d", objectNr);
+ screenObj->flags = fAnimated | fUpdate | fCycling;
if (getVersion() < 0x2000) {
- vt.flags |= fDidntMove;
+ screenObj->flags |= fDidntMove;
}
- vt.motion = kMotionNormal;
- vt.cycle = kCycleNormal;
- vt.direction = 0;
+ screenObj->motionType = kMotionNormal;
+ screenObj->cycle = kCycleNormal;
+ screenObj->direction = 0;
}
-void cmdUnanimateAll(AgiGame *state, uint8 *p) {
+void cmdUnanimateAll(AgiGame *state, uint8 *parameter) {
int i;
- for (i = 0; i < MAX_VIEWTABLE; i++)
- state->viewTable[i].flags &= ~(fAnimated | fDrawn);
+ state->_vm->_sprites->eraseSprites();
+
+ for (i = 0; i < SCREENOBJECTS_MAX; i++)
+ state->screenObjTable[i].flags &= ~(fAnimated | fDrawn);
}
-void cmdDraw(AgiGame *state, uint8 *p) {
- if (vt.flags & fDrawn)
- return;
+void cmdDraw(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
- if (vt.ySize <= 0 || vt.xSize <= 0)
+ if (screenObj->flags & fDrawn)
return;
- debugC(4, kDebugLevelScripts, "draw entry %d", vt.entry);
+// if (vt.ySize <= 0 || vt.xSize <= 0)
+// return;
+
+ debugC(4, kDebugLevelScripts, "draw entry %d", screenObj->objectNr);
- vt.flags |= fUpdate;
+ screenObj->flags |= fUpdate;
if (getVersion() >= 0x3000) {
- state->_vm->setLoop(&vt, vt.currentLoop);
- state->_vm->setCel(&vt, vt.currentCel);
+ state->_vm->setLoop(screenObj, screenObj->currentLoopNr);
+ state->_vm->setCel(screenObj, screenObj->currentCelNr);
}
- state->_vm->fixPosition(p0);
- vt.xPos2 = vt.xPos;
- vt.yPos2 = vt.yPos;
- vt.celData2 = vt.celData;
- state->_vm->_sprites->eraseUpdSprites();
- vt.flags |= fDrawn;
-
- // WORKAROUND: This fixes a bug with AGI Fanmade game Space Trek.
- // The original workaround checked if AGI version was <= 2.440, which could
- // cause regressions with some AGI games. The original workaround no longer
- // works for Space Trek in ScummVM, as all fanmade games are set to use
- // AGI version 2.917, but it applies to all other games where AGI version is
- // <= 2.440, which was not the original purpose of this workaround. It is
- // assumed that this bug is caused by AGI Studio, so this applies to all
- // fanmade games only.
- // TODO: Investigate this further and check if any other fanmade AGI
- // games are affected. If yes, then it'd be best to set this for Space
- // Trek only
- if (getFeatures() & GF_FANMADE) // See Sarien bug #546562
- vt.flags |= fAnimated;
-
- state->_vm->_sprites->blitUpdSprites();
- vt.flags &= ~fDontupdate;
-
- state->_vm->_sprites->commitBlock(vt.xPos, vt.yPos - vt.ySize + 1, vt.xPos + vt.xSize - 1, vt.yPos, true);
-
- debugC(4, kDebugLevelScripts, "vt entry #%d flags = %02x", p0, vt.flags);
-}
-
-void cmdErase(AgiGame *state, uint8 *p) {
- if (~vt.flags & fDrawn)
- return;
+ SpritesMgr *sprites = state->_vm->_sprites;
- state->_vm->_sprites->eraseUpdSprites();
+ state->_vm->fixPosition(objectNr);
+ screenObj->xPos_prev = screenObj->xPos;
+ screenObj->yPos_prev = screenObj->yPos;
+ screenObj->xSize_prev = screenObj->xSize;
+ screenObj->ySize_prev = screenObj->ySize;
+ //screenObj->celData2 = screenObj->celData;
+ sprites->eraseRegularSprites();
+ screenObj->flags |= fDrawn;
+ sprites->buildRegularSpriteList();
+ sprites->drawRegularSpriteList();
+ sprites->showSprite(screenObj);
+ screenObj->flags &= ~fDontupdate;
- if (vt.flags & fUpdate) {
- vt.flags &= ~fDrawn;
- } else {
- state->_vm->_sprites->eraseNonupdSprites();
- vt.flags &= ~fDrawn;
- state->_vm->_sprites->blitNonupdSprites();
- }
- state->_vm->_sprites->blitUpdSprites();
+ debugC(4, kDebugLevelScripts, "vt entry #%d flags = %02x", objectNr, screenObj->flags);
+}
+
+void cmdErase(AgiGame *state, uint8 *parameter) {
+ SpritesMgr *sprites = state->_vm->_sprites;
+ uint16 objectNr = parameter[0];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
- int x1, y1, x2, y2;
+ bool noUpdateFlag = false;
+
+ if (!(screenObj->flags & fDrawn))
+ return;
+
+ sprites->eraseRegularSprites();
+ if ((screenObj->flags & fUpdate) == 0) {
+ noUpdateFlag = true;
+ sprites->eraseStaticSprites();
+ }
- x1 = MIN((int)MIN(vt.xPos, vt.xPos2), MIN(vt.xPos + vt.celData->width, vt.xPos2 + vt.celData2->width));
- x2 = MAX((int)MAX(vt.xPos, vt.xPos2), MAX(vt.xPos + vt.celData->width, vt.xPos2 + vt.celData2->width));
- y1 = MIN((int)MIN(vt.yPos, vt.yPos2), MIN(vt.yPos - vt.celData->height, vt.yPos2 - vt.celData2->height));
- y2 = MAX((int)MAX(vt.yPos, vt.yPos2), MAX(vt.yPos - vt.celData->height, vt.yPos2 - vt.celData2->height));
+ screenObj->flags &= ~fDrawn;
- state->_vm->_sprites->commitBlock(x1, y1, x2, y2, true);
+ if (noUpdateFlag) {
+ sprites->buildStaticSpriteList();
+ sprites->drawStaticSpriteList();
+ }
+ sprites->buildRegularSpriteList();
+ sprites->drawRegularSpriteList();
+ sprites->showSprite(screenObj);
}
-void cmdPosition(AgiGame *state, uint8 *p) {
- vt.xPos = vt.xPos2 = p1;
- vt.yPos = vt.yPos2 = p2;
+void cmdPosition(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 xPos = parameter[1];
+ uint16 yPos = parameter[2];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
- // WORKAROUND: Part of the fix for bug #1659209 "AGI: Space Trek sprite duplication"
- // with an accompanying identical workaround in position.v-command (i.e. command 0x26).
- // These two workarounds together make up the whole fix. The bug was caused by
- // wrongly written script data in Space Trek v1.0's scripts (At least logics 4 and 11).
- // Position-command was called with horizontal values over 200 (Outside the screen!).
- // Clipping the coordinates so the views stay wholly on-screen seems to fix the problems.
- // It is probable (Would have to check better with disassembly to be completely sure)
- // that AGI 2.440 clipped its coordinates in its position and position.v-commands
- // although AGI 2.917 certainly doesn't (Checked that with disassembly) and that's why
- // Space Trek may have worked better with AGI 2.440 than with some other AGI versions.
- // I haven't checked but if Space Trek solely abuses the position-command we wouldn't
- // strictly need the identical workaround in the position.v-command but it does make
- // for a nice symmetry.
- if (getFeatures() & GF_CLIPCOORDS)
- state->_vm->clipViewCoordinates(&vt);
+ screenObj->xPos = screenObj->xPos_prev = xPos;
+ screenObj->yPos = screenObj->yPos_prev = yPos;
}
-void cmdPositionV1(AgiGame *state, uint8 *p) {
- vt.xPos = p1;
- vt.yPos = p2;
+void cmdPositionV1(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 xPos = parameter[1];
+ uint16 yPos = parameter[2];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ screenObj->xPos = xPos;
+ screenObj->yPos = yPos;
}
-void cmdPositionF(AgiGame *state, uint8 *p) {
- vt.xPos = vt.xPos2 = _v[p1];
- vt.yPos = vt.yPos2 = _v[p2];
+void cmdPositionF(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 varNr1 = parameter[1];
+ uint16 varNr2 = parameter[2];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
- // WORKAROUND: Part of the fix for bug #1659209 "AGI: Space Trek sprite duplication"
- // with an accompanying identical workaround in position-command (i.e. command 0x25).
- // See that workaround's comment for more in-depth information.
- if (getFeatures() & GF_CLIPCOORDS)
- state->_vm->clipViewCoordinates(&vt);
+ screenObj->xPos = screenObj->xPos_prev = state->vars[varNr1];
+ screenObj->yPos = screenObj->yPos_prev = state->vars[varNr2];
}
-void cmdPositionFV1(AgiGame *state, uint8 *p) {
- vt.xPos = _v[p1];
- vt.yPos = _v[p2];
+void cmdPositionFV1(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 varNr1 = parameter[1];
+ uint16 varNr2 = parameter[2];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ screenObj->xPos = state->vars[varNr1];
+ screenObj->yPos = state->vars[varNr2];
}
-void cmdGetPosn(AgiGame *state, uint8 *p) {
- state->vars[p1] = (unsigned char)vt.xPos;
- state->vars[p2] = (unsigned char)vt.yPos;
+void cmdGetPosn(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 varNr1 = parameter[1];
+ uint16 varNr2 = parameter[2];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ state->vars[varNr1] = (unsigned char)screenObj->xPos;
+ state->vars[varNr2] = (unsigned char)screenObj->yPos;
}
-void cmdReposition(AgiGame *state, uint8 *p) {
- int dx = (int8) _v[p1], dy = (int8) _v[p2];
+void cmdReposition(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 varNr1 = parameter[1];
+ uint16 varNr2 = parameter[2];
+ int16 dx = (int8) state->vars[varNr1];
+ int16 dy = (int8) state->vars[varNr2];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
debugC(4, kDebugLevelScripts, "dx=%d, dy=%d", dx, dy);
- vt.flags |= fUpdatePos;
+ screenObj->flags |= fUpdatePos;
- if (dx < 0 && vt.xPos < -dx)
- vt.xPos = 0;
+ if (dx < 0 && screenObj->xPos < -dx)
+ screenObj->xPos = 0;
else
- vt.xPos += dx;
+ screenObj->xPos += dx;
- if (dy < 0 && vt.yPos < -dy)
- vt.yPos = 0;
+ if (dy < 0 && screenObj->yPos < -dy)
+ screenObj->yPos = 0;
else
- vt.yPos += dy;
+ screenObj->yPos += dy;
- state->_vm->fixPosition(p0);
+ state->_vm->fixPosition(objectNr);
}
-void cmdRepositionV1(AgiGame *state, uint8 *p) {
- vt.xPos2 = vt.xPos;
- vt.yPos2 = vt.yPos;
- vt.flags |= fUpdatePos;
+void cmdRepositionV1(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 xPosPlus = parameter[1];
+ uint16 yPosPlus = parameter[2];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
- vt.xPos = (vt.xPos + p1) & 0xff;
- vt.yPos = (vt.yPos + p2) & 0xff;
+ screenObj->xPos_prev = screenObj->xPos;
+ screenObj->yPos_prev = screenObj->yPos;
+ screenObj->flags |= fUpdatePos;
+
+ screenObj->xPos = (screenObj->xPos + xPosPlus) & 0xff;
+ screenObj->yPos = (screenObj->yPos + yPosPlus) & 0xff;
}
-void cmdRepositionTo(AgiGame *state, uint8 *p) {
- vt.xPos = p1;
- vt.yPos = p2;
- vt.flags |= fUpdatePos;
- state->_vm->fixPosition(p0);
+void cmdRepositionTo(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 xPos = parameter[1];
+ uint16 yPos = parameter[2];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ screenObj->xPos = xPos;
+ screenObj->yPos = yPos;
+ screenObj->flags |= fUpdatePos;
+ state->_vm->fixPosition(objectNr);
}
-void cmdRepositionToF(AgiGame *state, uint8 *p) {
- vt.xPos = _v[p1];
- vt.yPos = _v[p2];
- vt.flags |= fUpdatePos;
- state->_vm->fixPosition(p0);
+void cmdRepositionToF(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 varNr1 = parameter[1];
+ uint16 varNr2 = parameter[2];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ screenObj->xPos = state->vars[varNr1];
+ screenObj->yPos = state->vars[varNr2];
+ screenObj->flags |= fUpdatePos;
+ state->_vm->fixPosition(objectNr);
}
-void cmdAddToPic(AgiGame *state, uint8 *p) {
- state->_vm->_sprites->addToPic(p0, p1, p2, p3, p4, p5, p6);
+void cmdAddToPic(AgiGame *state, uint8 *parameter) {
+ uint16 viewNr = parameter[0];
+ uint16 loopNr = parameter[1];
+ uint16 celNr = parameter[2];
+ uint16 xPos = parameter[3];
+ uint16 yPos = parameter[4];
+ uint16 priority = parameter[5];
+ uint16 border = parameter[6];
+
+ state->_vm->_sprites->addToPic(viewNr, loopNr, celNr, xPos, yPos, priority, border);
}
-void cmdAddToPicV1(AgiGame *state, uint8 *p) {
- state->_vm->_sprites->addToPic(p0, p1, p2, p3, p4, p5, -1);
+void cmdAddToPicV1(AgiGame *state, uint8 *parameter) {
+ uint16 viewNr = parameter[0];
+ uint16 loopNr = parameter[1];
+ uint16 celNr = parameter[2];
+ uint16 xPos = parameter[3];
+ uint16 yPos = parameter[4];
+ uint16 priority = parameter[5];
+
+ state->_vm->_sprites->addToPic(viewNr, loopNr, celNr, xPos, yPos, priority, -1);
}
-void cmdAddToPicF(AgiGame *state, uint8 *p) {
- state->_vm->_sprites->addToPic(_v[p0], _v[p1], _v[p2], _v[p3], _v[p4], _v[p5], _v[p6]);
+void cmdAddToPicF(AgiGame *state, uint8 *parameter) {
+ uint16 viewNr = state->vars[parameter[0]];
+ uint16 loopNr = state->vars[parameter[1]];
+ uint16 celNr = state->vars[parameter[2]];
+ uint16 xPos = state->vars[parameter[3]];
+ uint16 yPos = state->vars[parameter[4]];
+ uint16 priority = state->vars[parameter[5]];
+ uint16 border = state->vars[parameter[6]];
+
+ state->_vm->_sprites->addToPic(viewNr, loopNr, celNr, xPos, yPos, priority, border);
}
-void cmdForceUpdate(AgiGame *state, uint8 *p) {
- state->_vm->_sprites->eraseBoth();
- state->_vm->_sprites->blitBoth();
- state->_vm->_sprites->commitBoth();
+void cmdForceUpdate(AgiGame *state, uint8 *parameter) {
+ SpritesMgr *spritesMgr = state->_vm->_sprites;
+
+ spritesMgr->eraseSprites();
+ spritesMgr->buildAllSpriteLists();
+ spritesMgr->drawAllSpriteLists();
+ spritesMgr->showAllSpriteLists();
}
-void cmdReverseLoop(AgiGame *state, uint8 *p) {
- debugC(4, kDebugLevelScripts, "o%d, f%d", p0, p1);
- vt.cycle = kCycleRevLoop;
- vt.flags |= (fDontupdate | fUpdate | fCycling);
- vt.parm1 = p1;
- setflag(p1, false);
+void cmdReverseLoop(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 loopFlag = parameter[1];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ debugC(4, kDebugLevelScripts, "o%d, f%d", objectNr, loopFlag);
+ screenObj->cycle = kCycleRevLoop;
+ screenObj->flags |= (fDontupdate | fUpdate | fCycling);
+ screenObj->loop_flag = loopFlag;
+ state->_vm->setflag(screenObj->loop_flag, false);
}
-void cmdReverseLoopV1(AgiGame *state, uint8 *p) {
- debugC(4, kDebugLevelScripts, "o%d, f%d", p0, p1);
- vt.cycle = kCycleRevLoop;
- state->_vm->setCel(&vt, 0);
- vt.flags |= (fDontupdate | fUpdate | fCycling);
- vt.parm1 = p1;
- vt.parm3 = 0;
+void cmdReverseLoopV1(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 loopFlag = parameter[1];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ debugC(4, kDebugLevelScripts, "o%d, f%d", objectNr, loopFlag);
+ screenObj->cycle = kCycleRevLoop;
+ state->_vm->setCel(screenObj, 0);
+ screenObj->flags |= (fDontupdate | fUpdate | fCycling);
+ screenObj->loop_flag = loopFlag;
+ //screenObj->parm3 = 0;
}
-void cmdEndOfLoop(AgiGame *state, uint8 *p) {
- debugC(4, kDebugLevelScripts, "o%d, f%d", p0, p1);
- vt.cycle = kCycleEndOfLoop;
- vt.flags |= (fDontupdate | fUpdate | fCycling);
- vt.parm1 = p1;
- setflag(p1, false);
+void cmdEndOfLoop(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 loopFlag = parameter[1];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ debugC(4, kDebugLevelScripts, "o%d, f%d", objectNr, loopFlag);
+ screenObj->cycle = kCycleEndOfLoop;
+ screenObj->flags |= (fDontupdate | fUpdate | fCycling);
+ screenObj->loop_flag = loopFlag;
+ state->_vm->setflag(screenObj->loop_flag, false);
}
-void cmdEndOfLoopV1(AgiGame *state, uint8 *p) {
- debugC(4, kDebugLevelScripts, "o%d, f%d", p0, p1);
- vt.cycle = kCycleEndOfLoop;
- state->_vm->setCel(&vt, 0);
- vt.flags |= (fDontupdate | fUpdate | fCycling);
- vt.parm1 = p1;
- vt.parm3 = 0;
+void cmdEndOfLoopV1(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 loopFlag = parameter[1];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ debugC(4, kDebugLevelScripts, "o%d, f%d", objectNr, loopFlag);
+ screenObj->cycle = kCycleEndOfLoop;
+ state->_vm->setCel(screenObj, 0);
+ screenObj->flags |= (fDontupdate | fUpdate | fCycling);
+ screenObj->loop_flag = loopFlag;
+ //screenObj->parm3 = 0;
}
-void cmdBlock(AgiGame *state, uint8 *p) {
- debugC(4, kDebugLevelScripts, "x1=%d, y1=%d, x2=%d, y2=%d", p0, p1, p2, p3);
+void cmdBlock(AgiGame *state, uint8 *parameter) {
+ uint16 x1 = parameter[0];
+ uint16 y1 = parameter[1];
+ uint16 x2 = parameter[2];
+ uint16 y2 = parameter[3];
+
+ debugC(4, kDebugLevelScripts, "x1=%d, y1=%d, x2=%d, y2=%d", x1, y1, x2, y2);
state->block.active = true;
- state->block.x1 = p0;
- state->block.y1 = p1;
- state->block.x2 = p2;
- state->block.y2 = p3;
+ state->block.x1 = x1;
+ state->block.y1 = y1;
+ state->block.x2 = x2;
+ state->block.y2 = y2;
}
-void cmdUnblock(AgiGame *state, uint8 *p) {
+void cmdUnblock(AgiGame *state, uint8 *parameter) {
state->block.active = false;
}
-void cmdNormalMotion(AgiGame *state, uint8 *p) {
- vt.motion = kMotionNormal;
+void cmdNormalMotion(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ screenObj->motionType = kMotionNormal;
}
-void cmdStopMotion(AgiGame *state, uint8 *p) {
- vt.direction = 0;
- vt.motion = kMotionNormal;
- if (p0 == 0) { // ego only
- _v[vEgoDir] = 0;
+void cmdStopMotion(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ screenObj->direction = 0;
+ screenObj->motionType = kMotionNormal;
+ if (objectNr == 0) { // ego only
+ state->vars[VM_VAR_EGO_DIRECTION] = 0;
state->playerControl = false;
}
}
-void cmdStopMotionV1(AgiGame *state, uint8 *p) {
- vt.flags &= ~fAnimated;
+void cmdStopMotionV1(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ screenObj->flags &= ~fAnimated;
}
-void cmdStartMotion(AgiGame *state, uint8 *p) {
- vt.motion = kMotionNormal;
- if (p0 == 0) { // ego only
- _v[vEgoDir] = 0;
+void cmdStartMotion(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ screenObj->motionType = kMotionNormal;
+ if (objectNr == 0) { // ego only
+ state->vars[VM_VAR_EGO_DIRECTION] = 0;
state->playerControl = true;
}
}
-void cmdStartMotionV1(AgiGame *state, uint8 *p) {
- vt.flags |= fAnimated;
+void cmdStartMotionV1(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ screenObj->flags |= fAnimated;
}
-void cmdPlayerControl(AgiGame *state, uint8 *p) {
+void cmdPlayerControl(AgiGame *state, uint8 *parameter) {
+ ScreenObjEntry *screenObjEgo = &state->screenObjTable[SCREENOBJECTS_EGO_ENTRY];
+
state->playerControl = true;
- state->viewTable[0].motion = kMotionNormal;
+
+ if (screenObjEgo->motionType != kMotionEgo)
+ screenObjEgo->motionType = kMotionNormal;
}
-void cmdProgramControl(AgiGame *state, uint8 *p) {
+void cmdProgramControl(AgiGame *state, uint8 *parameter) {
state->playerControl = false;
}
-void cmdFollowEgo(AgiGame *state, uint8 *p) {
- vt.motion = kMotionFollowEgo;
- vt.parm1 = p1 > vt.stepSize ? p1 : vt.stepSize;
- vt.parm2 = p2;
- vt.parm3 = 0xff;
+void cmdFollowEgo(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 followStepSize = parameter[1];
+ uint16 followFlag = parameter[2];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ screenObj->motionType = kMotionFollowEgo;
+ if (followStepSize <= screenObj->stepSize) {
+ screenObj->follow_stepSize = screenObj->stepSize;
+ } else {
+ screenObj->follow_stepSize = followStepSize;
+ }
+ screenObj->follow_flag = followFlag;
+ screenObj->follow_count = 255;
if (getVersion() < 0x2000) {
- _v[p2] = 0;
- vt.flags |= fUpdate | fAnimated;
+ state->vars[screenObj->follow_flag] = 0;
+ screenObj->flags |= fUpdate | fAnimated;
} else {
- setflag(p2, false);
- vt.flags |= fUpdate;
+ state->_vm->setflag(screenObj->follow_flag, false);
+ screenObj->flags |= fUpdate;
}
}
-void cmdMoveObj(AgiGame *state, uint8 *p) {
+void cmdMoveObj(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 moveX = parameter[1];
+ uint16 moveY = parameter[2];
+ uint16 stepSize = parameter[3];
+ uint16 moveFlag = parameter[4];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
// _D (_D_WARN "o=%d, x=%d, y=%d, s=%d, f=%d", p0, p1, p2, p3, p4);
- vt.motion = kMotionMoveObj;
- vt.parm1 = p1;
- vt.parm2 = p2;
- vt.parm3 = vt.stepSize;
- vt.parm4 = p4;
+ screenObj->motionType = kMotionMoveObj;
+ screenObj->move_x = moveX;
+ screenObj->move_y = moveY;
+ screenObj->move_stepSize = screenObj->stepSize;
+ screenObj->move_flag = moveFlag;
- if (p3 != 0)
- vt.stepSize = p3;
+ if (stepSize != 0)
+ screenObj->stepSize = stepSize;
if (getVersion() < 0x2000) {
- _v[p4] = 0;
- vt.flags |= fUpdate | fAnimated;
+ state->vars[moveFlag] = 0;
+ screenObj->flags |= fUpdate | fAnimated;
} else {
- setflag(p4, false);
- vt.flags |= fUpdate;
+ state->_vm->setflag(screenObj->move_flag, false);
+ screenObj->flags |= fUpdate;
}
- if (p0 == 0)
+ if (objectNr == 0)
state->playerControl = false;
// AGI 2.272 (ddp, xmas) doesn't call move_obj!
if (getVersion() > 0x2272)
- state->_vm->moveObj(&vt);
+ state->_vm->moveObj(screenObj);
}
-void cmdMoveObjF(AgiGame *state, uint8 *p) {
- vt.motion = kMotionMoveObj;
- vt.parm1 = _v[p1];
- vt.parm2 = _v[p2];
- vt.parm3 = vt.stepSize;
- vt.parm4 = p4;
+void cmdMoveObjF(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ uint16 moveX = state->vars[parameter[1]];
+ uint16 moveY = state->vars[parameter[2]];
+ uint16 stepSize = state->vars[parameter[3]];
+ uint16 moveFlag = parameter[4];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ screenObj->motionType = kMotionMoveObj;
+ screenObj->move_x = moveX;
+ screenObj->move_y = moveY;
+ screenObj->move_stepSize = screenObj->stepSize;
+ screenObj->move_flag = moveFlag;
- if (_v[p3] != 0)
- vt.stepSize = _v[p3];
+ if (stepSize != 0)
+ screenObj->stepSize = stepSize;
- setflag(p4, false);
- vt.flags |= fUpdate;
+ state->_vm->setflag(screenObj->move_flag, false);
+ screenObj->flags |= fUpdate;
- if (p0 == 0)
+ if (objectNr == 0)
state->playerControl = false;
// AGI 2.272 (ddp, xmas) doesn't call move_obj!
if (getVersion() > 0x2272)
- state->_vm->moveObj(&vt);
+ state->_vm->moveObj(screenObj);
}
-void cmdWander(AgiGame *state, uint8 *p) {
- if (p0 == 0)
+void cmdWander(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr = parameter[0];
+ ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
+
+ if (objectNr == 0)
state->playerControl = false;
- vt.motion = kMotionWander;
+ screenObj->motionType = kMotionWander;
if (getVersion() < 0x2000) {
- vt.flags |= fUpdate | fAnimated;
+ screenObj->flags |= fUpdate | fAnimated;
} else {
- vt.flags |= fUpdate;
+ screenObj->flags |= fUpdate;
}
}
-void cmdSetGameID(AgiGame *state, uint8 *p) {
- if (state->_curLogic->texts && (p0 - 1) <= state->_curLogic->numTexts)
- Common::strlcpy(state->id, state->_curLogic->texts[p0 - 1], 8);
+void cmdSetGameID(AgiGame *state, uint8 *parameter) {
+ uint16 textNr = parameter[0];
+
+ if (state->_curLogic->texts && (textNr - 1) <= state->_curLogic->numTexts)
+ Common::strlcpy(state->id, state->_curLogic->texts[textNr - 1], 8);
else
state->id[0] = 0;
debug(0, "Game ID: \"%s\"", state->id);
}
-void cmdPause(AgiGame *state, uint8 *p) {
- int tmp = state->clockEnabled;
- const char *b[] = { "Continue", NULL };
- const char *b_ru[] = { "\x8f\xe0\xae\xa4\xae\xab\xa6\xa8\xe2\xec", NULL };
+void cmdPause(AgiGame *state, uint8 *parameter) {
+ AgiEngine *vm = state->_vm;
+ int originalClockState = state->clockEnabled;
+ bool skipPause = false;
state->clockEnabled = false;
- switch (getLanguage()) {
- case Common::RU_RUS:
- state->_vm->selectionBox(" \x88\xa3\xe0\xa0 \xae\xe1\xe2\xa0\xad\xae\xa2\xab\xa5\xad\xa0. \n\n\n", b_ru);
- break;
- default:
- state->_vm->selectionBox(" Game is paused. \n\n\n", b);
- break;
+ // We check in here, if a special key was specified to trigger menus.
+ // If that's the case, normally triggering the menu should be handled inside handleController()
+ // For the rare cases, where this approach doesn't work because the trigger is not mapped to a controller,
+ // we trigger the menu in here.
+ //
+ // for further study read the comments for handleController()
+ //
+ // This is needed for at least Mixed Up Mother Goose for Apple IIgs.
+ if (state->specialMenuTriggerKey) {
+ if (vm->_menu->isAvailable()) {
+ // Pulldown-menu is actually available (was submitted)
+ skipPause = true;
+
+ // Check, if special trigger key is currently NOT mapped.
+ if (vm->getSpecialMenuControllerSlot() < 0) {
+ // menu trigger is not mapped, trigger menu
+ vm->_menu->delayedExecute();
+ } else {
+ // menu trigger is mapped, do not replace "pause"
+ skipPause = false;
+ }
+ } else {
+ warning("menu is not available, doing regular pause game instead");
+ }
+ }
+
+ if (!skipPause) {
+ // Show pause message box
+ state->_vm->_systemUI->pauseDialog();
}
- state->clockEnabled = tmp;
+
+ state->clockEnabled = originalClockState;
}
-void cmdSetMenu(AgiGame *state, uint8 *p) {
- debugC(4, kDebugLevelScripts, "text %02x of %02x", p0, state->_curLogic->numTexts);
+void cmdSetMenu(AgiGame *state, uint8 *parameter) {
+ uint16 textNr = parameter[0];
+
+ debugC(4, kDebugLevelScripts, "text %02x of %02x", textNr, state->_curLogic->numTexts);
- if (state->_curLogic->texts != NULL && p0 <= state->_curLogic->numTexts)
- state->_vm->_menu->add(state->_curLogic->texts[p0 - 1]);
+ if (state->_curLogic->texts != NULL && (textNr - 1) <= state->_curLogic->numTexts) {
+ const char *menuText = state->_curLogic->texts[textNr - 1];
+
+ state->_vm->_menu->addMenu(menuText);
+ }
}
-void cmdSetMenuItem(AgiGame *state, uint8 *p) {
- debugC(4, kDebugLevelScripts, "text %02x of %02x", p0, state->_curLogic->numTexts);
+void cmdSetMenuItem(AgiGame *state, uint8 *parameter) {
+ uint16 textNr = parameter[0] - 1;
+ uint16 controllerSlot = parameter[1];
- if (state->_curLogic->texts != NULL && p0 <= state->_curLogic->numTexts)
- state->_vm->_menu->addItem(state->_curLogic->texts[p0 - 1], p1);
+ debugC(4, kDebugLevelScripts, "text %02x of %02x", textNr, state->_curLogic->numTexts);
+
+ if (state->_curLogic->texts != NULL && textNr <= state->_curLogic->numTexts) {
+ const char *menuItemText = state->_curLogic->texts[textNr];
+
+ state->_vm->_menu->addMenuItem(menuItemText, controllerSlot);
+ }
}
-void cmdVersion(AgiGame *state, uint8 *p) {
+void cmdVersion(AgiGame *state, uint8 *parameter) {
char ver2Msg[] =
"\n"
" \n\n"
- " Emulating Sierra AGI v%x.%03x\n";
+ " ScummVM Sierra AGI v%x.%03x";
char ver3Msg[] =
"\n"
" \n\n"
- " Emulating AGI v%x.002.%03x\n";
- // no Sierra as it wraps textbox
+ "ScummVM Sierra AGI v%x.002.%03x";
Common::String verMsg = TITLE " v%s";
@@ -1278,98 +1719,113 @@ void cmdVersion(AgiGame *state, uint8 *p) {
verMsg += (maj == 2 ? ver2Msg : ver3Msg);
verMsg = Common::String::format(verMsg.c_str(), gScummVMVersion, maj, min);
- state->_vm->messageBox(verMsg.c_str());
+ state->_vm->_text->messageBox(verMsg.c_str());
}
-void cmdConfigureScreen(AgiGame *state, uint8 *p) {
- state->lineMinPrint = p0;
- state->lineUserInput = p1;
- state->lineStatus = p2;
+void cmdConfigureScreen(AgiGame *state, uint8 *parameter) {
+ TextMgr *textMgr = state->_vm->_text;
+ uint16 lineMinPrint = parameter[0];
+ uint16 promptRow = parameter[1];
+ uint16 statusRow = parameter[2];
+
+ state->_vm->_text->configureScreen(lineMinPrint);
+
+ textMgr->statusRow_Set(statusRow);
+ textMgr->promptRow_Set(promptRow);
}
-void cmdTextScreen(AgiGame *state, uint8 *p) {
- debugC(4, kDebugLevelScripts, "switching to text mode");
- state->gfxMode = false;
+void cmdTextScreen(AgiGame *state, uint8 *parameter) {
+ GfxMgr *gfxMgr = state->_vm->_gfx;
+ TextMgr *textMgr = state->_vm->_text;
- // Simulates the "bright background bit" of the PC video
- // controller.
- if (state->colorBg)
- state->colorBg |= 0x08;
+ debugC(4, kDebugLevelScripts, "switching to text mode");
- state->_vm->_gfx->clearScreen(state->colorBg);
+ state->gfxMode = false;
+ gfxMgr->setPalette(false); // set text-mode palette
+ textMgr->charAttrib_Set(textMgr->_textAttrib.foreground, textMgr->_textAttrib.background);
+ gfxMgr->clearDisplay(0);
+ textMgr->clearLines(0, 24, textMgr->_textAttrib.combinedBackground);
}
-void cmdGraphics(AgiGame *state, uint8 *p) {
+void cmdGraphics(AgiGame *state, uint8 *parameter) {
debugC(4, kDebugLevelScripts, "switching to graphics mode");
- if (!state->gfxMode) {
- state->gfxMode = true;
- state->_vm->_gfx->clearScreen(0);
- state->_vm->_picture->showPic();
- state->_vm->writeStatus();
- state->_vm->writePrompt();
- }
+ state->_vm->redrawScreen();
}
-void cmdSetTextAttribute(AgiGame *state, uint8 *p) {
- state->colorFg = p0;
- state->colorBg = p1;
-
- if (state->gfxMode) {
- if (state->colorBg != 0) {
- state->colorFg = 0;
- state->colorBg = 15;
- }
- }
+void cmdSetTextAttribute(AgiGame *state, uint8 *parameter) {
+ int16 foreground = parameter[0];
+ int16 background = parameter[1];
+ state->_vm->_text->charAttrib_Set(foreground, background);
}
-void cmdStatus(AgiGame *state, uint8 *p) {
- state->_vm->inventory();
+void cmdStatus(AgiGame *state, uint8 *parameter) {
+ TextMgr *textMgr = state->_vm->_text;
+ InventoryMgr *inventoryMgr = state->_vm->_inventory;
+
+ textMgr->inputEditOn();
+ textMgr->charAttrib_Push();
+ textMgr->charAttrib_Set(0, 15);
+
+ cmdTextScreen(state, parameter);
+
+ inventoryMgr->show();
+
+ //invent_state = 0;
+ textMgr->charAttrib_Pop();
+ state->_vm->redrawScreen();
}
-void cmdQuit(AgiGame *state, uint8 *p) {
- const char *buttons[] = { "Quit", "Continue", NULL };
+void cmdQuit(AgiGame *state, uint8 *parameter) {
+ uint16 withoutPrompt = parameter[0];
+// const char *buttons[] = { "Quit", "Continue", NULL };
state->_vm->_sound->stopSound();
- if (p0) {
+ if (withoutPrompt) {
state->_vm->quitGame();
} else {
- if (state->_vm->selectionBox(" Quit the game, or continue? \n\n\n", buttons) == 0) {
+ if (state->_vm->_systemUI->quitDialog()) {
state->_vm->quitGame();
}
}
}
-void cmdQuitV1(AgiGame *state, uint8 *p) {
+void cmdQuitV1(AgiGame *state, uint8 *parameter) {
state->_vm->_sound->stopSound();
state->_vm->quitGame();
}
-void cmdRestartGame(AgiGame *state, uint8 *p) {
- const char *buttons[] = { "Restart", "Continue", NULL };
- int sel;
+void cmdRestartGame(AgiGame *state, uint8 *parameter) {
+ bool doRestart = false;
state->_vm->_sound->stopSound();
- sel = getflag(fAutoRestart) ? 0 :
- state->_vm->selectionBox(" Restart game, or continue? \n\n\n", buttons);
- if (sel == 0) {
+ if (state->_vm->getflag(VM_FLAG_AUTO_RESTART)) {
+ doRestart = true;
+ } else {
+ doRestart = state->_vm->_systemUI->restartDialog();
+ }
+
+ if (doRestart) {
state->_vm->_restartGame = true;
- setflag(fRestartGame, true);
- state->_vm->_menu->enableAll();
+ state->_vm->setflag(VM_FLAG_RESTART_GAME, true);
+ state->_vm->_menu->itemEnableAll();
}
}
-void cmdDistance(AgiGame *state, uint8 *p) {
+void cmdDistance(AgiGame *state, uint8 *parameter) {
+ uint16 objectNr1 = parameter[0];
+ uint16 objectNr2 = parameter[1];
+ uint16 destVarNr = parameter[2];
int16 x1, y1, x2, y2, d;
- VtEntry *v0 = &state->viewTable[p0];
- VtEntry *v1 = &state->viewTable[p1];
-
- if (v0->flags & fDrawn && v1->flags & fDrawn) {
- x1 = v0->xPos + v0->xSize / 2;
- y1 = v0->yPos;
- x2 = v1->xPos + v1->xSize / 2;
- y2 = v1->yPos;
+ ScreenObjEntry *screenObj1 = &state->screenObjTable[objectNr1];
+ ScreenObjEntry *screenObj2 = &state->screenObjTable[objectNr2];
+
+ if (screenObj1->flags & fDrawn && screenObj2->flags & fDrawn) {
+ x1 = screenObj1->xPos + screenObj1->xSize / 2;
+ y1 = screenObj1->yPos;
+ x2 = screenObj2->xPos + screenObj2->xSize / 2;
+ y2 = screenObj2->yPos;
d = ABS(x1 - x2) + ABS(y1 - y2);
if (d > 0xfe)
d = 0xfe;
@@ -1377,7 +1833,8 @@ void cmdDistance(AgiGame *state, uint8 *p) {
d = 0xff;
}
- // WORKAROUND: Fixes King's Quest IV's script bug #1660424 (KQ4: Zombie bug).
+ // WORKAROUND: Fixes King's Quest IV's script bug #3067 (KQ4: Zombie bug).
+ // This bug also happens in the original interpreter.
// In the graveyard (Rooms 16 and 18) at night if you had the Obsidian Scarab (Item 4)
// and you were very close to a spot where a zombie was going to rise up from the
// ground you could reproduce the bug. Just standing there and letting the zombie
@@ -1386,7 +1843,7 @@ void cmdDistance(AgiGame *state, uint8 *p) {
// wouldn't chase Rosella around anymore. If it had worked correctly the zombie
// wouldn't have come up at all or it would have come up and gone back down
// immediately. The latter approach is the one implemented here.
- if (getGameID() == GID_KQ4 && (_v[vCurRoom] == 16 || _v[vCurRoom] == 18) && p2 >= 221 && p2 <= 223) {
+ if (getGameID() == GID_KQ4 && (state->vars[VM_VAR_CURRENT_ROOM] == 16 || state->vars[VM_VAR_CURRENT_ROOM] == 18) && destVarNr >= 221 && destVarNr <= 223) {
// Rooms 16 and 18 are graveyards where three zombies come up at night. They use logics 16 and 18.
// Variables 221-223 are used to save the distance between each zombie and Rosella.
// Variables 155, 156 and 162 are used to save the state of each zombie in room 16.
@@ -1399,228 +1856,271 @@ void cmdDistance(AgiGame *state, uint8 *p) {
// a zombie or the zombie getting turned away by the scarab) we make it appear the
// zombie is far away from Rosella if the zombie is not already up and chasing her.
enum zombieStates {ZOMBIE_SET_TO_RISE_UP, ZOMBIE_RISING_UP, ZOMBIE_CHASING_EGO};
- uint8 zombieStateVarNumList[] = {155, 156, (uint8)((_v[vCurRoom] == 16) ? 162 : 158)};
- uint8 zombieNum = p2 - 221; // Zombie's number (In range 0-2)
+ uint8 zombieStateVarNumList[] = {155, 156, (uint8)((state->vars[VM_VAR_CURRENT_ROOM] == 16) ? 162 : 158)};
+ uint8 zombieNum = destVarNr - 221; // Zombie's number (In range 0-2)
uint8 zombieStateVarNum = zombieStateVarNumList[zombieNum]; // Number of the variable containing zombie's state
- uint8 zombieState = _v[zombieStateVarNum]; // Zombie's state
+ uint8 zombieState = state->vars[zombieStateVarNum]; // Zombie's state
// If zombie is not chasing Rosella then set its distance from Rosella to the maximum
if (zombieState != ZOMBIE_CHASING_EGO)
d = 0xff;
}
- _v[p2] = (unsigned char)d;
+ state->vars[destVarNr] = (unsigned char)d;
}
-void cmdAcceptInput(AgiGame *state, uint8 *p) {
+void cmdAcceptInput(AgiGame *state, uint8 *parameter) {
+ TextMgr *textMgr = state->_vm->_text;
+
debugC(4, kDebugLevelScripts | kDebugLevelInput, "input normal");
- state->_vm->newInputMode(INPUT_NORMAL);
- state->inputEnabled = true;
- state->_vm->writePrompt();
+ state->_vm->newInputMode(INPUTMODE_NORMAL);
+
+ textMgr->promptEnable();
+ textMgr->promptRedraw();
}
-void cmdPreventInput(AgiGame *state, uint8 *p) {
+void cmdPreventInput(AgiGame *state, uint8 *parameter) {
+ TextMgr *textMgr = state->_vm->_text;
+
debugC(4, kDebugLevelScripts | kDebugLevelInput, "no input");
- state->_vm->newInputMode(INPUT_NONE);
- state->inputEnabled = false;
+ state->_vm->newInputMode(INPUTMODE_NONE);
+
+ textMgr->promptDisable();
+
+ textMgr->inputEditOn();
+ textMgr->clearLine(textMgr->promptRow_Get(), 0);
+}
+
+void cmdCancelLine(AgiGame *state, uint8 *parameter) {
+ state->_vm->_text->promptCancelLine();
+}
+
+void cmdEchoLine(AgiGame *state, uint8 *parameter) {
+ TextMgr *textMgr = state->_vm->_text;
- // Always clear with black background. Fixes bug #3080041.
- state->_vm->clearPrompt(true);
+ if (textMgr->promptIsEnabled()) {
+ textMgr->promptEchoLine();
+ }
}
-void cmdGetString(AgiGame *state, uint8 *p) {
- int tex, row, col;
+void cmdGetString(AgiGame *state, uint8 *parameter) {
+ TextMgr *textMgr = state->_vm->_text;
+ int16 stringDestNr = parameter[0];
+ int16 leadInTextNr = parameter[1] - 1;
+ int16 stringRow = parameter[2];
+ int16 stringColumn = parameter[3];
+ int16 stringMaxLen = parameter[4];
+ bool previousEditState = false;
+ const char *leadInTextPtr = nullptr;
+
+ if (stringMaxLen > TEXT_STRING_MAX_SIZE)
+ stringMaxLen = TEXT_STRING_MAX_SIZE;
- debugC(4, kDebugLevelScripts, "%d %d %d %d %d", p0, p1, p2, p3, p4);
+ debugC(4, kDebugLevelScripts, "%d %d %d %d %d", stringDestNr, leadInTextNr, stringRow, stringColumn, stringMaxLen);
- tex = p1 - 1;
- row = p2;
- col = p3;
+ previousEditState = textMgr->inputGetEditStatus();
+
+ textMgr->charPos_Push();
+ textMgr->inputEditOn();
// Workaround for SQLC bug.
// See Sarien bug #792125 for details
- if (row > 24)
- row = 24;
- if (col > 39)
- col = 39;
+// if (promptRow > 24)
+// promptRow = 24;
+// if (promptColumn > 39)
+// promptColumn = 39;
- state->_vm->newInputMode(INPUT_GETSTRING);
+ if (stringRow < 25) {
+ textMgr->charPos_Set(stringRow, stringColumn);
+ }
- if (state->_curLogic->texts != NULL && state->_curLogic->numTexts >= tex) {
- int len = strlen(state->_curLogic->texts[tex]);
+ if (state->_curLogic->texts && state->_curLogic->numTexts >= leadInTextNr) {
+ leadInTextPtr = state->_curLogic->texts[leadInTextNr];
- state->_vm->printText(state->_curLogic->texts[tex], 0, col, row, len, state->colorFg, state->colorBg);
- state->_vm->getString(col + len - 1, row, p4, p0);
+ leadInTextPtr = textMgr->stringPrintf(leadInTextPtr);
+ leadInTextPtr = textMgr->stringWordWrap(leadInTextPtr, 40); // ?? not absolutely sure
- // SGEO: display input char
- state->_vm->_gfx->printCharacter((col + len), row, state->cursorChar, state->colorFg, state->colorBg);
+ textMgr->displayText(leadInTextPtr);
}
- do {
- state->_vm->mainCycle();
- } while (state->inputMode == INPUT_GETSTRING && !(state->_vm->shouldQuit() || state->_vm->_restartGame));
+ state->_vm->cycleInnerLoopActive(CYCLE_INNERLOOP_GETSTRING);
+
+ textMgr->stringSet("");
+ textMgr->stringEdit(stringMaxLen);
+
+ // copy string to destination
+ // TODO: not sure if set all the time or only when ENTER is pressed
+ strcpy(&state->_vm->_game.strings[stringDestNr][0], (char *)textMgr->_inputString);
+
+ textMgr->charPos_Pop();
+
+ if (!previousEditState) {
+ textMgr->inputEditOff();
+ }
}
-void cmdGetNum(AgiGame *state, uint8 *p) {
- debugC(4, kDebugLevelScripts, "%d %d", p0, p1);
+void cmdGetNum(AgiGame *state, uint8 *parameter) {
+ TextMgr *textMgr = state->_vm->_text;
+ int16 leadInTextNr = parameter[0] - 1;
+ int16 numberDestVarNr = parameter[1];
+ const char *leadInTextPtr = nullptr;
+
+ debugC(4, kDebugLevelScripts, "%d %d", leadInTextNr, numberDestVarNr);
- state->_vm->newInputMode(INPUT_GETSTRING);
+ textMgr->inputEditOn();
+ textMgr->charPos_Set(textMgr->promptRow_Get(), 0);
- if (state->_curLogic->texts != NULL && state->_curLogic->numTexts >= (p0 - 1)) {
- int len = strlen(state->_curLogic->texts[p0 - 1]);
+ if (state->_curLogic->texts && state->_curLogic->numTexts >= leadInTextNr) {
+ leadInTextPtr = state->_curLogic->texts[leadInTextNr];
- state->_vm->printText(state->_curLogic->texts[p0 - 1], 0, 0, 22, len, state->colorFg, state->colorBg);
- state->_vm->getString(len - 1, 22, 3, MAX_STRINGS);
+ leadInTextPtr = textMgr->stringPrintf(leadInTextPtr);
+ leadInTextPtr = textMgr->stringWordWrap(leadInTextPtr, 40); // ?? not absolutely sure
- // CM: display input char
- state->_vm->_gfx->printCharacter((p3 + len), 22, state->cursorChar, state->colorFg, state->colorBg);
+ textMgr->displayText(leadInTextPtr);
}
- do {
- state->_vm->mainCycle();
- } while (state->inputMode == INPUT_GETSTRING && !(state->_vm->shouldQuit() || state->_vm->_restartGame));
+ textMgr->inputEditOff();
+
+ state->_vm->cycleInnerLoopActive(CYCLE_INNERLOOP_GETNUMBER);
- _v[p1] = atoi(state->strings[MAX_STRINGS]);
+ textMgr->stringSet("");
+ textMgr->stringEdit(3);
- debugC(4, kDebugLevelScripts, "[%s] -> %d", state->strings[MAX_STRINGS], _v[p1]);
+ textMgr->promptRedraw();
- state->_vm->clearLines(22, 22, state->colorBg);
- state->_vm->flushLines(22, 22);
+ state->vars[numberDestVarNr] = atoi((char *)textMgr->_inputString);
+
+ debugC(4, kDebugLevelScripts, "[%s] -> %d", state->strings[MAX_STRINGS], state->vars[numberDestVarNr]);
}
-void cmdSetCursorChar(AgiGame *state, uint8 *p) {
- if (state->_curLogic->texts != NULL && (p0 - 1) <= state->_curLogic->numTexts) {
- state->cursorChar = *state->_curLogic->texts[p0 - 1];
+void cmdSetCursorChar(AgiGame *state, uint8 *parameter) {
+ TextMgr *textMgr = state->_vm->_text;
+ uint16 textNr = parameter[0] - 1;
+
+ if (state->_curLogic->texts != NULL && textNr <= state->_curLogic->numTexts) {
+ textMgr->inputSetCursorChar(*state->_curLogic->texts[textNr]);
} else {
// default
- state->cursorChar = '_';
+ textMgr->inputSetCursorChar('_');
}
}
-void cmdSetKey(AgiGame *state, uint8 *p) {
- int key = 256 * p1 + p0;
- int slot = -1;
+void cmdSetKey(AgiGame *state, uint8 *parameter) {
+ uint16 key = parameter[0] + (parameter[1] << 8);
+ uint16 controllerSlot = parameter[2];
+ int16 keyMappingSlot = -1;
- for (int i = 0; i < MAX_CONTROLLERS; i++) {
- if (slot == -1 && !state->controllers[i].keycode)
- slot = i;
+ for (int i = 0; i < MAX_CONTROLLER_KEYMAPPINGS; i++) {
+ if (keyMappingSlot == -1 && !state->controllerKeyMapping[i].keycode)
+ keyMappingSlot = i;
- if (state->controllers[i].keycode == key && state->controllers[i].controller == p2)
+ if (state->controllerKeyMapping[i].keycode == key && state->controllerKeyMapping[i].controllerSlot == controllerSlot)
return;
}
- if (slot == -1) {
- warning("Number of set.keys exceeded %d", MAX_CONTROLLERS);
+ if (keyMappingSlot == -1) {
+ warning("Number of set.keys exceeded %d", MAX_CONTROLLER_KEYMAPPINGS);
return;
}
- debugC(4, kDebugLevelScripts, "cmdSetKey: %d %d %d", p0, p1, p2);
- state->controllers[slot].keycode = key;
- state->controllers[slot].controller = p2;
+ debugC(4, kDebugLevelScripts, "cmdSetKey: %d %d %d", parameter[0], parameter[1], controllerSlot);
+ state->controllerKeyMapping[keyMappingSlot].keycode = key;
+ state->controllerKeyMapping[keyMappingSlot].controllerSlot = controllerSlot;
- state->controllerOccured[p2] = false;
+ state->controllerOccured[controllerSlot] = false;
}
-void cmdSetString(AgiGame *state, uint8 *p) {
+void cmdSetString(AgiGame *state, uint8 *parameter) {
+ uint16 stringNr = parameter[0];
+ uint16 textNr = parameter[1] - 1;
// CM: to avoid crash in Groza (str = 150)
- if (p0 > MAX_STRINGS)
+ if (stringNr > MAX_STRINGS)
return;
- strcpy(state->strings[p0], state->_curLogic->texts[p1 - 1]);
+ strcpy(state->strings[stringNr], state->_curLogic->texts[textNr]);
}
-void cmdDisplay(AgiGame *state, uint8 *p) {
+void cmdDisplay(AgiGame *state, uint8 *parameter) {
// V1 has 4 args
- int t = (getVersion() >= 0x2000 ? p2 : p3);
- int len = 40;
-
- char *s = state->_vm->wordWrapString(state->_curLogic->texts[t - 1], &len);
-
- state->_vm->printText(s, p1, 0, p0, 40, state->colorFg, state->colorBg);
+ int16 textNr = (getVersion() >= 0x2000 ? parameter[2] : parameter[3]);
+ int16 textRow = parameter[0];
+ int16 textColumn = parameter[1];
- free(s);
+ state->_vm->_text->display(textNr, textRow, textColumn);
}
-void cmdDisplayF(AgiGame *state, uint8 *p) {
- state->_vm->printText(state->_curLogic->texts[_v[p2] - 1], _v[p1], 0, _v[p0], 40, state->colorFg, state->colorBg);
-}
-
-void cmdClearTextRect(AgiGame *state, uint8 *p) {
- int c, x1, y1, x2, y2;
+void cmdDisplayF(AgiGame *state, uint8 *parameter) {
+ int16 textRow = state->vars[parameter[0]];
+ int16 textColumn = state->vars[parameter[1]];
+ int16 textNr = state->vars[parameter[2]];
- if ((c = p4) != 0)
- c = 15;
-
- x1 = p1 * CHAR_COLS;
- y1 = p0 * CHAR_LINES;
- x2 = (p3 + 1) * CHAR_COLS - 1;
- y2 = (p2 + 1) * CHAR_LINES - 1;
+ state->_vm->_text->display(textNr, textRow, textColumn);
+}
- // Added to prevent crash with x2 = 40 in the iigs demo
- if (x1 > GFX_WIDTH)
- x1 = GFX_WIDTH - 1;
- if (x2 > GFX_WIDTH)
- x2 = GFX_WIDTH - 1;
- if (y1 > GFX_HEIGHT)
- y1 = GFX_HEIGHT - 1;
- if (y2 > GFX_HEIGHT)
- y2 = GFX_HEIGHT - 1;
+void cmdClearTextRect(AgiGame *state, uint8 *parameter) {
+ int16 textUpperRow = parameter[0];
+ int16 textUpperColumn = parameter[1];
+ int16 textLowerRow = parameter[2];
+ int16 textLowerColumn = parameter[3];
+ int16 color = state->_vm->_text->calculateTextBackground(parameter[4]);
- state->_vm->_gfx->drawRectangle(x1, y1, x2, y2, c);
- state->_vm->_gfx->flushBlock(x1, y1, x2, y2);
+ state->_vm->_text->clearBlock(textUpperRow, textUpperColumn, textLowerRow, textLowerColumn, color);
}
-void cmdToggleMonitor(AgiGame *state, uint8 *p) {
+void cmdToggleMonitor(AgiGame *state, uint8 *parameter) {
debug(0, "toggle.monitor");
}
-void cmdEchoLine(AgiGame *state, uint8 *p) {
- strcpy((char *)state->inputBuffer, (const char *)state->echoBuffer);
- state->cursorPos = strlen((char *)state->inputBuffer);
- state->hasPrompt = 0;
-}
-
-void cmdClearLines(AgiGame *state, uint8 *p) {
- uint8 l;
+void cmdClearLines(AgiGame *state, uint8 *parameter) {
+ int16 textRowUpper = parameter[0];
+ int16 textRowLower = parameter[1];
+ int16 color = state->_vm->_text->calculateTextBackground(parameter[2]);
// Residence 44 calls clear.lines(24,0,0), see Sarien bug #558423
- l = p1 ? p1 : p0;
-
// Agent06 incorrectly calls clear.lines(1,150,0), see ScummVM bugs
// #1935838 and #1935842
- l = (l <= 24) ? l : 24;
-
- state->_vm->clearLines(p0, l, p2);
- state->_vm->flushLines(p0, l);
+ if (textRowUpper > textRowLower) {
+ warning("cmdClearLines: RowUpper higher than RowLower");
+ textRowLower = textRowUpper;
+ }
+ state->_vm->_text->clearLines(textRowUpper, textRowLower, color);
}
-void cmdPrint(AgiGame *state, uint8 *p) {
- int n = p0 < 1 ? 1 : p0;
+void cmdPrint(AgiGame *state, uint8 *parameter) {
+ int16 textNr = parameter[0];
- state->_vm->print(state->_curLogic->texts[n - 1], 0, 0, 0);
+ state->_vm->_text->print(textNr);
}
-void cmdPrintF(AgiGame *state, uint8 *p) {
- int n = _v[p0] < 1 ? 1 : _v[p0];
+void cmdPrintF(AgiGame *state, uint8 *parameter) {
+ int16 textNr = state->vars[parameter[0]];
- state->_vm->print(state->_curLogic->texts[n - 1], 0, 0, 0);
+ state->_vm->_text->print(textNr);
}
-void cmdPrintAt(AgiGame *state, uint8 *p) {
- int n = p0 < 1 ? 1 : p0;
+void cmdPrintAt(AgiGame *state, uint8 *parameter) {
+ int16 textNr = parameter[0];
+ int16 textRow = parameter[1];
+ int16 textColumn = parameter[2];
+ int16 textWidth = parameter[3];
- debugC(4, kDebugLevelScripts, "%d %d %d %d", p0, p1, p2, p3);
+ debugC(4, kDebugLevelScripts, "%d %d %d %d", textNr, textRow, textColumn, textWidth);
- state->_vm->print(state->_curLogic->texts[n - 1], p1, p2, p3);
+ state->_vm->_text->printAt(textNr, textRow, textColumn, textWidth);
}
-void cmdPrintAtV(AgiGame *state, uint8 *p) {
- int n = _v[p0] < 1 ? 1 : _v[p0];
+void cmdPrintAtV(AgiGame *state, uint8 *parameter) {
+ int16 textNr = state->vars[parameter[0]];
+ int16 textRow = parameter[1];
+ int16 textColumn = parameter[2];
+ int16 textWidth = parameter[3];
+
+ debugC(4, kDebugLevelScripts, "%d %d %d %d", textNr, textRow, textColumn, textWidth);
- state->_vm->print(state->_curLogic->texts[n - 1], p1, p2, p3);
+ state->_vm->_text->printAt(textNr, textRow, textColumn, textWidth);
}
-void cmdPushScript(AgiGame *state, uint8 *p) {
+void cmdPushScript(AgiGame *state, uint8 *parameter) {
// We run AGIMOUSE always as a side effect
//if (getFeatures() & GF_AGIMOUSE || true) {
state->vars[27] = state->_vm->_mouse.button;
@@ -1633,86 +2133,91 @@ void cmdPushScript(AgiGame *state, uint8 *p) {
}*/
}
-void cmdSetPriBase(AgiGame *state, uint8 *p) {
- int i, x, pri;
-
- debug(0, "Priority base set to %d", p0);
+void cmdSetPriBase(AgiGame *state, uint8 *parameter) {
+ uint16 priorityBase = parameter[0];
- // state->alt_pri = true;
- x = (_HEIGHT - p0) * _HEIGHT / 10;
+ debug(0, "Priority base set to %d", priorityBase);
- for (i = 0; i < _HEIGHT; i++) {
- pri = (i - p0) < 0 ? 4 : (i - p0) * _HEIGHT / x + 5;
- if (pri > 15)
- pri = 15;
- state->priTable[i] = pri;
- }
+ state->_vm->_gfx->setPriorityTable(priorityBase);
}
-void cmdMousePosn(AgiGame *state, uint8 *p) {
- _v[p0] = WIN_TO_PIC_X(state->_vm->_mouse.x);
- _v[p1] = WIN_TO_PIC_Y(state->_vm->_mouse.y);
+void cmdMousePosn(AgiGame *state, uint8 *parameter) {
+ uint16 destVarNr1 = parameter[0];
+ uint16 destVarNr2 = parameter[1];
+ int16 mouseX = state->_vm->_mouse.x;
+ int16 mouseY = state->_vm->_mouse.y;
+
+ state->_vm->adjustPosToGameScreen(mouseX, mouseY);
+
+ state->vars[destVarNr1] = mouseX;
+ state->vars[destVarNr2] = mouseY;
}
-void cmdShakeScreen(AgiGame *state, uint8 *p) {
- int i;
+void cmdShakeScreen(AgiGame *state, uint8 *parameter) {
+ uint16 shakeCount = parameter[0];
// AGIPAL uses shake.screen values between 100 and 109 to set the palette
// (Checked the original AGIPAL-hack's shake.screen-routine's disassembly).
- if (p0 >= 100 && p0 < 110) {
+ if (shakeCount >= 100 && shakeCount < 110) {
if (getFeatures() & GF_AGIPAL) {
- state->_vm->_gfx->setAGIPal(p0);
+ state->_vm->_gfx->setAGIPal(shakeCount);
return;
} else {
warning("It looks like GF_AGIPAL flag is missing");
}
}
- // Disables input while shaking to prevent bug
- // #1678230: AGI: Entering text while screen is shaking
- bool originalValue = state->inputEnabled;
- state->inputEnabled = false;
-
- state->_vm->_gfx->shakeStart();
-
- state->_vm->_sprites->commitBoth(); // Fixes SQ1 demo
- for (i = 4 * p0; i; i--) {
- state->_vm->_gfx->shakeScreen(i & 1);
- state->_vm->_gfx->flushBlock(0, 0, GFX_WIDTH - 1, GFX_HEIGHT - 1);
- state->_vm->mainCycle();
- }
- state->_vm->_gfx->shakeEnd();
-
- // Sets input back to what it was
- state->inputEnabled = originalValue;
+ state->_vm->_gfx->shakeScreen(shakeCount);
}
-void cmdSetSpeed(AgiGame *state, uint8 *p) {
+void cmdSetSpeed(AgiGame *state, uint8 *parameter) {
// V1 command
(void)state;
- (void)p;
+ (void)parameter;
// speed = _v[p0];
}
-void cmdSetItemView(AgiGame *state, uint8 *p) {
+void cmdSetItemView(AgiGame *state, uint8 *parameter) {
// V1 command
(void)state;
- (void)p;
+ (void)parameter;
}
-void cmdCallV1(AgiGame *state, uint8 *p) {
- state->_vm->agiLoadResource(rLOGIC, p0);
+void cmdCallV1(AgiGame *state, uint8 *parameter) {
+ uint16 resourceNr = parameter[0];
+
+ state->_vm->agiLoadResource(RESOURCETYPE_LOGIC, resourceNr);
// FIXME: The following instruction looks incomplete.
// Maybe something is meant to be assigned to, or read from,
// the logic_list entry?
// state->logic_list[++state->max_logics];
// For now, just do the increment, to silence a clang warning
++state->max_logics;
- _v[13] = 1;
+ state->vars[13] = 1;
+}
+
+void cmdNewRoomV1(AgiGame *state, uint8 *parameter) {
+ uint16 resourceNr = parameter[0];
+
+ warning("cmdNewRoomV1()");
+ state->_vm->agiLoadResource(RESOURCETYPE_LOGIC, resourceNr);
+ state->max_logics = 1;
+ state->logic_list[1] = resourceNr;
+ state->vars[13] = 1;
+}
+
+void cmdNewRoomVV1(AgiGame *state, uint8 *parameter) {
+ uint16 resourceNr = state->vars[parameter[0]];
+
+ warning("cmdNewRoomVV1()");
+ state->_vm->agiLoadResource(RESOURCETYPE_LOGIC, resourceNr);
+ state->max_logics = 1;
+ state->logic_list[1] = resourceNr;
+ state->vars[13] = 1;
}
-void cmdUnknown(AgiGame *state, uint8 *p) {
- warning("Skipping unknown opcode %2X", *(code + ip - 1));
+void cmdUnknown(AgiGame *state, uint8 *parameter) {
+ warning("Skipping unknown opcode %2X", *(state->_curLogic->data + state->_curLogic->cIP - 1));
}
/**
@@ -1740,7 +2245,7 @@ int AgiEngine::runLogic(int n) {
// If logic not loaded, load it
if (~_game.dirLogic[n].flags & RES_LOADED) {
debugC(4, kDebugLevelScripts, "logic %d not loaded!", n);
- agiLoadResource(rLOGIC, n);
+ agiLoadResource(RESOURCETYPE_LOGIC, n);
}
_game.lognum = n;
@@ -1749,7 +2254,9 @@ int AgiEngine::runLogic(int n) {
_game._curLogic->cIP = _game._curLogic->sIP;
_timerHack = 0;
- while (ip < _game.logics[n].size && !(shouldQuit() || _restartGame)) {
+ while (state->_curLogic->cIP < _game.logics[n].size && !(shouldQuit() || _restartGame)) {
+ // TODO: old code, needs to be adjusted
+#if 0
if (_debug.enabled) {
if (_debug.steps > 0) {
if (_debug.logic0 || n) {
@@ -1762,24 +2269,25 @@ int AgiEngine::runLogic(int n) {
do {
mainCycle();
} while (!_debug.steps && _debug.enabled);
- _sprites->eraseBoth();
+ _sprites->eraseAllSprites();
}
}
+#endif
- _game.execStack.back().curIP = ip;
+ _game.execStack.back().curIP = state->_curLogic->cIP;
char st[101];
int sz = MIN(_game.execStack.size(), 100u);
memset(st, '.', sz);
st[sz] = 0;
- switch (op = *(code + ip++)) {
+ switch (op = *(state->_curLogic->data + state->_curLogic->cIP++)) {
case 0xff: // if (open/close)
testIfCode(n);
break;
case 0xfe: // goto
// +2 covers goto size
- ip += 2 + ((int16)READ_LE_UINT16(code + ip));
+ state->_curLogic->cIP += 2 + ((int16)READ_LE_UINT16(state->_curLogic->data + state->_curLogic->cIP));
// timer must keep running even in goto loops,
// but AGI engine can't do that :(
@@ -1809,20 +2317,20 @@ int AgiEngine::runLogic(int n) {
return 1;
default:
num = logicNamesCmd[op].argumentsLength();
- memmove(p, code + ip, num);
+ memmove(p, state->_curLogic->data + state->_curLogic->cIP, num);
memset(p + num, 0, CMD_BSIZE - num);
debugC(2, kDebugLevelScripts, "%s%s(%d %d %d)", st, logicNamesCmd[op].name, p[0], p[1], p[2]);
_agiCommands[op](&_game, p);
- ip += num;
+ state->_curLogic->cIP += num;
}
// if ((op == 0x0B || op == 0x3F || op == 0x40) && logic_index < state->max_logics) {
// n = state->logic_list[++logic_index];
// state->_curLogic = &state->logics[n];
// state->lognum = n;
-// ip = 2;
+// state->_curLogic_cIP = 2;
// warning("running logic %d\n", n);
// }
diff --git a/engines/agi/op_dbg.cpp b/engines/agi/op_dbg.cpp
index 997da9db7d..92af2c63f1 100644
--- a/engines/agi/op_dbg.cpp
+++ b/engines/agi/op_dbg.cpp
@@ -93,7 +93,7 @@ void AgiEngine::debugConsole(int lognum, int mode, const char *str) {
if (*c == 'n') {
debugN(0, "%d", *(code + (ip + z)));
} else {
- debugN(0, "v%d[%d]", *(code + (ip + z)), getvar(*(code + (ip + z))));
+ debugN(0, "v%d[%d]", *(code + (ip + z)), getVar(*(code + (ip + z))));
}
c++;
z++;
diff --git a/engines/agi/op_test.cpp b/engines/agi/op_test.cpp
index 9839f0ec90..afb1ddb820 100644
--- a/engines/agi/op_test.cpp
+++ b/engines/agi/op_test.cpp
@@ -23,6 +23,8 @@
#include "agi/agi.h"
#include "agi/opcodes.h"
+#include "agi/words.h"
+
#include "common/endian.h"
namespace Agi {
@@ -30,16 +32,14 @@ namespace Agi {
#define ip (state->_curLogic->cIP)
#define code (state->_curLogic->data)
-#define getvar(a) state->_vm->getvar(a)
-#define getflag(a) state->_vm->getflag(a)
+#define getVar(a) state->_vm->getVar(a)
-#define testEqual(v1, v2) (getvar(v1) == (v2))
-#define testLess(v1, v2) (getvar(v1) < (v2))
-#define testGreater(v1, v2) (getvar(v1) > (v2))
-#define testIsSet(flag) (getflag(flag))
+#define testEqual(v1, v2) (getVar(v1) == (v2))
+#define testLess(v1, v2) (getVar(v1) < (v2))
+#define testGreater(v1, v2) (getVar(v1) > (v2))
#define testHas(obj) (state->_vm->objectGetLocation(obj) == EGO_OWNED)
#define testHasV1(obj) (state->_vm->objectGetLocation(obj) == EGO_OWNED_V1)
-#define testObjInRoom(obj, v) (state->_vm->objectGetLocation(obj) == getvar(v))
+#define testObjInRoom(obj, v) (state->_vm->objectGetLocation(obj) == getVar(v))
void condEqual(AgiGame *state, uint8 *p) {
if (p[0] == 11)
@@ -50,7 +50,7 @@ void condEqual(AgiGame *state, uint8 *p) {
void condEqualV(AgiGame *state, uint8 *p) {
if (p[0] == 11 || p[1] == 11)
state->_vm->_timerHack++;
- state->testResult = testEqual(p[0], getvar(p[1]));
+ state->testResult = testEqual(p[0], getVar(p[1]));
}
void condLess(AgiGame *state, uint8 *p) {
@@ -62,7 +62,7 @@ void condLess(AgiGame *state, uint8 *p) {
void condLessV(AgiGame *state, uint8 *p) {
if (p[0] == 11 || p[1] == 11)
state->_vm->_timerHack++;
- state->testResult = testLess(p[0], getvar(p[1]));
+ state->testResult = testLess(p[0], getVar(p[1]));
}
void condGreater(AgiGame *state, uint8 *p) {
@@ -74,19 +74,19 @@ void condGreater(AgiGame *state, uint8 *p) {
void condGreaterV(AgiGame *state, uint8 *p) {
if (p[0] == 11 || p[1] == 11)
state->_vm->_timerHack++;
- state->testResult = testGreater(p[0], getvar(p[1]));
+ state->testResult = testGreater(p[0], getVar(p[1]));
}
void condIsSet(AgiGame *state, uint8 *p) {
- state->testResult = testIsSet(p[0]);
+ state->testResult = state->_vm->getflag(p[0]);
}
void condIsSetV(AgiGame *state, uint8 *p) {
- state->testResult = testIsSet(getvar(p[0]));
+ state->testResult = state->_vm->getflag(getVar(p[0]));
}
void condIsSetV1(AgiGame *state, uint8 *p) {
- state->testResult = getvar(p[0]) > 0;
+ state->testResult = getVar(p[0]) > 0;
}
void condHas(AgiGame *state, uint8 *p) {
@@ -121,47 +121,47 @@ void condSaid(AgiGame *state, uint8 *p) {
void condSaid1(AgiGame *state, uint8 *p) {
state->testResult = false;
- if (!getflag(fEnteredCli))
+ if (!state->_vm->getflag(VM_FLAG_ENTERED_CLI))
return;
int id0 = READ_LE_UINT16(p);
- if ((id0 == 1 || id0 == state->egoWords[0].id))
+ if ((id0 == 1 || id0 == state->_vm->_words->getEgoWordId(0)))
state->testResult = true;
}
void condSaid2(AgiGame *state, uint8 *p) {
state->testResult = false;
- if (!getflag(fEnteredCli))
+ if (!state->_vm->getflag(VM_FLAG_ENTERED_CLI))
return;
int id0 = READ_LE_UINT16(p);
int id1 = READ_LE_UINT16(p + 2);
- if ((id0 == 1 || id0 == state->egoWords[0].id) &&
- (id1 == 1 || id1 == state->egoWords[1].id))
+ if ((id0 == 1 || id0 == state->_vm->_words->getEgoWordId(0)) &&
+ (id1 == 1 || id1 == state->_vm->_words->getEgoWordId(1)))
state->testResult = true;
}
void condSaid3(AgiGame *state, uint8 *p) {
state->testResult = false;
- if (!getflag(fEnteredCli))
+ if (!state->_vm->getflag(VM_FLAG_ENTERED_CLI))
return;
int id0 = READ_LE_UINT16(p);
int id1 = READ_LE_UINT16(p + 2);
int id2 = READ_LE_UINT16(p + 4);
- if ((id0 == 1 || id0 == state->egoWords[0].id) &&
- (id1 == 1 || id1 == state->egoWords[1].id) &&
- (id2 == 1 || id2 == state->egoWords[2].id))
+ if ((id0 == 1 || id0 == state->_vm->_words->getEgoWordId(0)) &&
+ (id1 == 1 || id1 == state->_vm->_words->getEgoWordId(1)) &&
+ (id2 == 1 || id2 == state->_vm->_words->getEgoWordId(2)))
state->testResult = true;
}
void condBit(AgiGame *state, uint8 *p) {
- state->testResult = (getvar(p[1]) >> p[0]) & 1;
+ state->testResult = (getVar(p[1]) >> p[0]) & 1;
}
void condCompareStrings(AgiGame *state, uint8 *p) {
@@ -188,7 +188,7 @@ void condUnknown13(AgiGame *state, uint8 *p) {
// This command is used at least in the Amiga version of Gold Rush! v2.05 1989-03-09
// (AGI 2.316) in logics 1, 3, 5, 6, 137 and 192 (Logic.192 revealed this command's nature).
// TODO: Check this command's implementation using disassembly just to be sure.
- int ec = state->viewTable[0].flags & fAdjEgoXY;
+ int ec = state->screenObjTable[SCREENOBJECTS_EGO_ENTRY].flags & fAdjEgoXY;
debugC(7, kDebugLevelScripts, "op_test: in.motion.using.mouse = %s (Amiga-specific testcase 19)", ec ? "true" : "false");
state->testResult = ec;
}
@@ -221,7 +221,7 @@ uint8 AgiEngine::testCompareStrings(uint8 s1, uint8 s2) {
break;
default:
- ms1[j++] = toupper(ms1[k]);
+ ms1[j++] = tolower(ms1[k]);
break;
}
}
@@ -242,7 +242,7 @@ uint8 AgiEngine::testCompareStrings(uint8 s1, uint8 s2) {
break;
default:
- ms2[j++] = toupper(ms2[k]);
+ ms2[j++] = tolower(ms2[k]);
break;
}
}
@@ -258,7 +258,7 @@ uint8 AgiEngine::testKeypressed() {
if (!x) {
InputMode mode = _game.inputMode;
- _game.inputMode = INPUT_NONE;
+ _game.inputMode = INPUTMODE_NONE;
// Only check for events here, without updating the game cycle,
// otherwise the animations in some games are drawn too quickly
// like, for example, Manannan's lightnings in the intro of KQ3
@@ -275,11 +275,11 @@ uint8 AgiEngine::testKeypressed() {
}
uint8 AgiEngine::testController(uint8 cont) {
- return (_game.controllerOccured[cont] ? 1 : 0);
+ return (_game.controllerOccured[cont] ? true : false);
}
uint8 AgiEngine::testPosn(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) {
- VtEntry *v = &_game.viewTable[n];
+ ScreenObjEntry *v = &_game.screenObjTable[n];
uint8 r;
r = v->xPos >= x1 && v->yPos >= y1 && v->xPos <= x2 && v->yPos <= y2;
@@ -290,7 +290,7 @@ uint8 AgiEngine::testPosn(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) {
}
uint8 AgiEngine::testObjInBox(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) {
- VtEntry *v = &_game.viewTable[n];
+ ScreenObjEntry *v = &_game.screenObjTable[n];
return v->xPos >= x1 &&
v->yPos >= y1 && v->xPos + v->xSize - 1 <= x2 && v->yPos <= y2;
@@ -298,7 +298,7 @@ uint8 AgiEngine::testObjInBox(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) {
// if n is in center of box
uint8 AgiEngine::testObjCenter(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) {
- VtEntry *v = &_game.viewTable[n];
+ ScreenObjEntry *v = &_game.screenObjTable[n];
return v->xPos + v->xSize / 2 >= x1 &&
v->xPos + v->xSize / 2 <= x2 && v->yPos >= y1 && v->yPos <= y2;
@@ -306,7 +306,7 @@ uint8 AgiEngine::testObjCenter(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2)
// if nect N is in right corner
uint8 AgiEngine::testObjRight(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) {
- VtEntry *v = &_game.viewTable[n];
+ ScreenObjEntry *v = &_game.screenObjTable[n];
return v->xPos + v->xSize - 1 >= x1 &&
v->xPos + v->xSize - 1 <= x2 && v->yPos >= y1 && v->yPos <= y2;
@@ -315,10 +315,12 @@ uint8 AgiEngine::testObjRight(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) {
// When player has entered something, it is parsed elsewhere
uint8 AgiEngine::testSaid(uint8 nwords, uint8 *cc) {
AgiGame *state = &_game;
- int c, n = _game.numEgoWords;
+ AgiEngine *vm = state->_vm;
+ Words *words = vm->_words;
+ int c, n = words->getEgoWordCount();
int z = 0;
- if (getflag(fSaidAcceptedInput) || !getflag(fEnteredCli))
+ if (vm->getflag(VM_FLAG_SAID_ACCEPTED_INPUT) || !vm->getflag(VM_FLAG_ENTERED_CLI))
return false;
// FR:
@@ -349,7 +351,7 @@ uint8 AgiEngine::testSaid(uint8 nwords, uint8 *cc) {
case 1: // any word
break;
default:
- if (_game.egoWords[c].id != z)
+ if (words->getEgoWordId(c) != z)
return false;
break;
}
@@ -364,7 +366,7 @@ uint8 AgiEngine::testSaid(uint8 nwords, uint8 *cc) {
if (nwords != 0 && READ_LE_UINT16(cc) != 9999)
return false;
- setflag(fSaidAcceptedInput, true);
+ setflag(VM_FLAG_SAID_ACCEPTED_INPUT, true);
return true;
}
diff --git a/engines/agi/opcodes.cpp b/engines/agi/opcodes.cpp
index 0d7d180ec9..a20e51a5fc 100644
--- a/engines/agi/opcodes.cpp
+++ b/engines/agi/opcodes.cpp
@@ -173,189 +173,189 @@ AgiInstruction insV2Test[] = {
};
AgiInstruction insV2[] = {
- { "return", "", NULL },
- { "increment", "v", &cmdIncrement },
- { "decrement", "v", &cmdDecrement },
- { "assignn", "vn", &cmdAssignN },
- { "assignv", "vv", &cmdAssignV },
- { "addn", "vn", &cmdAddN },
- { "addv", "vv", &cmdAddV },
- { "subn", "vn", &cmdSubN },
- { "subv", "vv", &cmdSubV },
- { "lindirectv", "vv", &cmdLindirectV },
- { "lindirect", "vv", &cmdRindirect },
- { "lindirectn", "vn", &cmdLindirectN },
- { "set", "n", &cmdSet },
- { "reset", "n", &cmdReset },
- { "toggle", "n", &cmdToggle },
- { "set.v", "v", &cmdSetV },
- { "reset.v", "v", &cmdResetV },
- { "toggle.v", "v", &cmdToggleV },
- { "new.room", "n", &cmdNewRoom },
- { "new.room.v", "v", &cmdNewRoomF },
- { "load.logics", "n", &cmdLoadLogic },
- { "load.logics.v", "v", &cmdLoadLogicF },
- { "call", "n", &cmdCall },
- { "call.v", "v", &cmdCallF },
- { "load.pic", "v", &cmdLoadPic },
- { "draw.pic", "v", &cmdDrawPic },
- { "show.pic", "", &cmdShowPic },
- { "discard.pic", "v", &cmdDiscardPic },
- { "overlay.pic", "v", &cmdOverlayPic },
- { "show.pri.screen", "", &cmdShowPriScreen },
- { "load.view", "n", &cmdLoadView },
- { "load.view.v", "v", &cmdLoadViewF },
- { "discard.view", "n", &cmdDiscardView },
- { "animate.obj", "n", &cmdAnimateObj },
- { "unanimate.all", "", &cmdUnanimateAll },
- { "draw", "n", &cmdDraw },
- { "erase", "n", &cmdErase },
- { "position", "nnn", &cmdPosition },
- { "position.v", "nvv", &cmdPositionF },
- { "get.posn", "nvv", &cmdGetPosn },
- { "reposition", "nvv", &cmdReposition },
- { "set.view", "nn", &cmdSetView },
- { "set.view.v", "nv", &cmdSetViewF },
- { "set.loop", "nn", &cmdSetLoop },
- { "set.loop.v", "nv", &cmdSetLoopF },
- { "fix.loop", "n", &cmdFixLoop },
- { "release.loop", "n", &cmdReleaseLoop },
- { "set.cel", "nn", &cmdSetCel },
- { "set.cel.v", "nv", &cmdSetCelF },
- { "last.cel", "nv", &cmdLastCel },
- { "current.cel", "nv", &cmdCurrentCel },
- { "current.loop", "nv", &cmdCurrentLoop },
- { "current.view", "nv", &cmdCurrentView },
- { "number.of.loops", "nv", &cmdNumberOfLoops },
- { "set.priority", "nn", &cmdSetPriority },
- { "set.priority.v", "nv", &cmdSetPriorityF },
- { "release.priority", "n", &cmdReleasePriority },
- { "get.priority", "nn", &cmdGetPriority },
- { "stop.update", "n", &cmdStopUpdate },
- { "start.update", "n", &cmdStartUpdate },
- { "force.update", "n", &cmdForceUpdate },
- { "ignore.horizon", "n", &cmdIgnoreHorizon },
- { "observe.horizon", "n", &cmdObserveHorizon },
- { "set.horizon", "n", &cmdSetHorizon },
- { "object.on.water", "n", &cmdObjectOnWater },
- { "object.on.land", "n", &cmdObjectOnLand },
- { "object.on.anything", "n", &cmdObjectOnAnything },
- { "ignore.objs", "n", &cmdIgnoreObjs },
- { "observe.objs", "n", &cmdObserveObjs },
- { "distance", "nnv", &cmdDistance },
- { "stop.cycling", "n", &cmdStopCycling },
- { "start.cycling", "n", &cmdStartCycling },
- { "normal.cycle", "n", &cmdNormalCycle },
- { "end.of.loop", "nn", &cmdEndOfLoop },
- { "reverse.cycle", "n", &cmdReverseCycle },
- { "reverse.loop", "nn", &cmdReverseLoop },
- { "cycle.time", "nv", &cmdCycleTime },
- { "stop.motion", "n", &cmdStopMotion },
- { "start.motion", "n", &cmdStartMotion },
- { "step.size", "nv", &cmdStepSize },
- { "step.time", "nv", &cmdStepTime },
- { "move.obj", "nnnnn", &cmdMoveObj },
- { "move.obj.v", "nvvvv", &cmdMoveObjF },
- { "follow.ego", "nnn", &cmdFollowEgo },
- { "wander", "n", &cmdWander },
- { "normal.motion", "n", &cmdNormalMotion },
- { "set.dir", "nv", &cmdSetDir },
- { "get.dir", "nv", &cmdGetDir },
- { "ignore.blocks", "n", &cmdIgnoreBlocks },
- { "observe.blocks", "n", &cmdObserveBlocks },
- { "block", "nnnn", &cmdBlock },
- { "unblock", "", &cmdUnblock },
- { "get", "n", &cmdGet },
- { "get.v", "v", &cmdGetF },
- { "drop", "n", &cmdDrop },
- { "put", "nn", &cmdPut },
- { "put.v", "vv", &cmdPutF },
- { "get.room.v", "vv", &cmdGetRoomF },
- { "load.sound", "n", &cmdLoadSound },
- { "sound", "nn", &cmdSound },
- { "stop.sound", "", &cmdStopSound },
- { "print", "s", &cmdPrint },
- { "print.v", "v", &cmdPrintF },
- { "display", "nns", &cmdDisplay },
- { "display.v", "vvv", &cmdDisplayF },
- { "clear.lines", "nns", &cmdClearLines },
- { "text.screen", "", &cmdTextScreen },
- { "graphics", "", &cmdGraphics },
- { "set.cursor.char", "s", &cmdSetCursorChar },
- { "set.text.attribute", "nn", &cmdSetTextAttribute },
- { "shake.screen", "n", &cmdShakeScreen },
- { "configure.screen", "nnn", &cmdConfigureScreen },
- { "status.line.on", "", &cmdStatusLineOn },
- { "status.line.off", "", &cmdStatusLineOff },
- { "set.string", "ns", &cmdSetString },
- { "get.string", "nsnnn", &cmdGetString },
- { "word.to.string", "nn", &cmdWordToString },
- { "parse", "n", &cmdParse },
- { "get.num", "nv", &cmdGetNum },
- { "prevent.input", "", &cmdPreventInput },
- { "accept.input", "", &cmdAcceptInput },
- { "set.key", "nnn", &cmdSetKey },
- { "add.to.pic", "nnnnnnn", &cmdAddToPic },
- { "add.to.pic.v", "vvvvvvv", &cmdAddToPicF },
- { "status", "", &cmdStatus },
- { "save.game", "", &cmdSaveGame },
- { "restore.game", "", &cmdLoadGame },
- { "init.disk", "", &cmdInitDisk },
- { "restart.game", "", &cmdRestartGame },
- { "show.obj", "n", &cmdShowObj },
- { "random", "nnv", &cmdRandom },
- { "program.control", "", &cmdProgramControl },
- { "player.control", "", &cmdPlayerControl },
- { "obj.status.v", "v", &cmdObjStatusF },
- { "quit", "n", &cmdQuit }, // 0 args for AGI version 2.089
- { "show.mem", "", &cmdShowMem },
- { "pause", "", &cmdPause },
- { "echo.line", "", &cmdEchoLine },
- { "cancel.line", "", &cmdCancelLine },
- { "init.joy", "", &cmdInitJoy },
- { "toggle.monitor", "", &cmdToggleMonitor },
- { "version", "", &cmdVersion },
- { "script.size", "n", &cmdScriptSize },
- { "set.game.id", "s", &cmdSetGameID },
- { "log", "s", &cmdLog },
- { "set.scan.start", "", &cmdSetScanStart },
- { "reset.scan.start", "", &cmdResetScanStart },
- { "reposition.to", "nnn", &cmdRepositionTo },
- { "reposition.to.v", "nvv", &cmdRepositionToF },
- { "trace.on", "", &cmdTraceOn },
- { "trace.info", "nnn", &cmdTraceInfo },
+ { "return", "", NULL }, // 00
+ { "increment", "v", &cmdIncrement }, // 01
+ { "decrement", "v", &cmdDecrement }, // 02
+ { "assignn", "vn", &cmdAssignN }, // 03
+ { "assignv", "vv", &cmdAssignV }, // 04
+ { "addn", "vn", &cmdAddN }, // 05
+ { "addv", "vv", &cmdAddV }, // 06
+ { "subn", "vn", &cmdSubN }, // 07
+ { "subv", "vv", &cmdSubV }, // 08
+ { "lindirectv", "vv", &cmdLindirectV }, // 09
+ { "lindirect", "vv", &cmdRindirect }, // 0A
+ { "lindirectn", "vn", &cmdLindirectN }, // 0B
+ { "set", "n", &cmdSet }, // 0C
+ { "reset", "n", &cmdReset }, // 0D
+ { "toggle", "n", &cmdToggle }, // 0E
+ { "set.v", "v", &cmdSetV }, // 0F
+ { "reset.v", "v", &cmdResetV }, // 10
+ { "toggle.v", "v", &cmdToggleV }, // 11
+ { "new.room", "n", &cmdNewRoom }, // 12
+ { "new.room.v", "v", &cmdNewRoomF }, // 13
+ { "load.logics", "n", &cmdLoadLogic }, // 14
+ { "load.logics.v", "v", &cmdLoadLogicF }, // 15
+ { "call", "n", &cmdCall }, // 16
+ { "call.v", "v", &cmdCallF }, // 17
+ { "load.pic", "v", &cmdLoadPic }, // 18
+ { "draw.pic", "v", &cmdDrawPic }, // 19
+ { "show.pic", "", &cmdShowPic }, // 1A
+ { "discard.pic", "v", &cmdDiscardPic }, // 1B
+ { "overlay.pic", "v", &cmdOverlayPic }, // 1C
+ { "show.pri.screen", "", &cmdShowPriScreen }, // 1D
+ { "load.view", "n", &cmdLoadView }, // 1E
+ { "load.view.v", "v", &cmdLoadViewF }, // 1F
+ { "discard.view", "n", &cmdDiscardView }, // 20
+ { "animate.obj", "n", &cmdAnimateObj }, // 21
+ { "unanimate.all", "", &cmdUnanimateAll }, // 22
+ { "draw", "n", &cmdDraw }, // 23
+ { "erase", "n", &cmdErase }, // 24
+ { "position", "nnn", &cmdPosition }, // 25
+ { "position.v", "nvv", &cmdPositionF }, // 26
+ { "get.posn", "nvv", &cmdGetPosn }, // 27
+ { "reposition", "nvv", &cmdReposition }, // 28
+ { "set.view", "nn", &cmdSetView }, // 29
+ { "set.view.v", "nv", &cmdSetViewF }, // 2A
+ { "set.loop", "nn", &cmdSetLoop }, // 2B
+ { "set.loop.v", "nv", &cmdSetLoopF }, // 2C
+ { "fix.loop", "n", &cmdFixLoop }, // 2D
+ { "release.loop", "n", &cmdReleaseLoop }, // 2E
+ { "set.cel", "nn", &cmdSetCel }, // 2F
+ { "set.cel.v", "nv", &cmdSetCelF }, // 30
+ { "last.cel", "nv", &cmdLastCel }, // 31
+ { "current.cel", "nv", &cmdCurrentCel }, // 32
+ { "current.loop", "nv", &cmdCurrentLoop }, // 33
+ { "current.view", "nv", &cmdCurrentView }, // 34
+ { "number.of.loops", "nv", &cmdNumberOfLoops }, // 35
+ { "set.priority", "nn", &cmdSetPriority }, // 36
+ { "set.priority.v", "nv", &cmdSetPriorityF }, // 37
+ { "release.priority", "n", &cmdReleasePriority }, // 38
+ { "get.priority", "nn", &cmdGetPriority }, // 39
+ { "stop.update", "n", &cmdStopUpdate }, // 3A
+ { "start.update", "n", &cmdStartUpdate }, // 3B
+ { "force.update", "n", &cmdForceUpdate }, // 3C
+ { "ignore.horizon", "n", &cmdIgnoreHorizon }, // 3D
+ { "observe.horizon", "n", &cmdObserveHorizon }, // 3E
+ { "set.horizon", "n", &cmdSetHorizon }, // 3F
+ { "object.on.water", "n", &cmdObjectOnWater }, // 40
+ { "object.on.land", "n", &cmdObjectOnLand }, // 41
+ { "object.on.anything", "n", &cmdObjectOnAnything }, // 42
+ { "ignore.objs", "n", &cmdIgnoreObjs }, // 43
+ { "observe.objs", "n", &cmdObserveObjs }, // 44
+ { "distance", "nnv", &cmdDistance }, // 45
+ { "stop.cycling", "n", &cmdStopCycling }, // 46
+ { "start.cycling", "n", &cmdStartCycling }, // 47
+ { "normal.cycle", "n", &cmdNormalCycle }, // 48
+ { "end.of.loop", "nn", &cmdEndOfLoop }, // 49
+ { "reverse.cycle", "n", &cmdReverseCycle }, // 5A
+ { "reverse.loop", "nn", &cmdReverseLoop }, // 5B
+ { "cycle.time", "nv", &cmdCycleTime }, // 5C
+ { "stop.motion", "n", &cmdStopMotion }, // 5D
+ { "start.motion", "n", &cmdStartMotion }, // 5E
+ { "step.size", "nv", &cmdStepSize }, // 5F
+ { "step.time", "nv", &cmdStepTime }, // 60
+ { "move.obj", "nnnnn", &cmdMoveObj }, // 61
+ { "move.obj.v", "nvvvv", &cmdMoveObjF }, // 62
+ { "follow.ego", "nnn", &cmdFollowEgo }, // 63
+ { "wander", "n", &cmdWander }, // 64
+ { "normal.motion", "n", &cmdNormalMotion }, // 65
+ { "set.dir", "nv", &cmdSetDir }, // 66
+ { "get.dir", "nv", &cmdGetDir }, // 67
+ { "ignore.blocks", "n", &cmdIgnoreBlocks }, // 68
+ { "observe.blocks", "n", &cmdObserveBlocks }, // 69
+ { "block", "nnnn", &cmdBlock }, // 6A
+ { "unblock", "", &cmdUnblock }, // 6B
+ { "get", "n", &cmdGet }, // 6C
+ { "get.v", "v", &cmdGetF }, // 6D
+ { "drop", "n", &cmdDrop }, // 6E
+ { "put", "nn", &cmdPut }, // 6F
+ { "put.v", "vv", &cmdPutF }, // 70
+ { "get.room.v", "vv", &cmdGetRoomF }, // 71
+ { "load.sound", "n", &cmdLoadSound }, // 72
+ { "sound", "nn", &cmdSound }, // 73
+ { "stop.sound", "", &cmdStopSound }, // 74
+ { "print", "s", &cmdPrint }, // 75
+ { "print.v", "v", &cmdPrintF }, // 76
+ { "display", "nns", &cmdDisplay }, // 77
+ { "display.v", "vvv", &cmdDisplayF }, // 78
+ { "clear.lines", "nns", &cmdClearLines }, // 79
+ { "text.screen", "", &cmdTextScreen }, // 7A
+ { "graphics", "", &cmdGraphics }, // 7B
+ { "set.cursor.char", "s", &cmdSetCursorChar }, // 7C
+ { "set.text.attribute", "nn", &cmdSetTextAttribute }, // 7D
+ { "shake.screen", "n", &cmdShakeScreen }, // 7E
+ { "configure.screen", "nnn", &cmdConfigureScreen }, // 7F
+ { "status.line.on", "", &cmdStatusLineOn }, // 80
+ { "status.line.off", "", &cmdStatusLineOff }, // 81
+ { "set.string", "ns", &cmdSetString }, // 82
+ { "get.string", "nsnnn", &cmdGetString }, // 83
+ { "word.to.string", "nn", &cmdWordToString }, // 84
+ { "parse", "n", &cmdParse }, // 85
+ { "get.num", "nv", &cmdGetNum }, // 86
+ { "prevent.input", "", &cmdPreventInput }, // 87
+ { "accept.input", "", &cmdAcceptInput }, // 88
+ { "set.key", "nnn", &cmdSetKey }, // 89
+ { "add.to.pic", "nnnnnnn", &cmdAddToPic }, // 8A
+ { "add.to.pic.v", "vvvvvvv", &cmdAddToPicF }, // 8B
+ { "status", "", &cmdStatus }, // 8C
+ { "save.game", "", &cmdSaveGame }, // 8D
+ { "restore.game", "", &cmdLoadGame }, // 8E
+ { "init.disk", "", &cmdInitDisk }, // 8F
+ { "restart.game", "", &cmdRestartGame }, // 90
+ { "show.obj", "n", &cmdShowObj }, // 91
+ { "random", "nnv", &cmdRandom }, // 92
+ { "program.control", "", &cmdProgramControl }, // 93
+ { "player.control", "", &cmdPlayerControl }, // 94
+ { "obj.status.v", "v", &cmdObjStatusF }, // 95
+ { "quit", "n", &cmdQuit }, // 96 0 args for AGI version 2.089
+ { "show.mem", "", &cmdShowMem }, // 97
+ { "pause", "", &cmdPause }, // 98
+ { "echo.line", "", &cmdEchoLine }, // 99
+ { "cancel.line", "", &cmdCancelLine }, // 9A
+ { "init.joy", "", &cmdInitJoy }, // 9B
+ { "toggle.monitor", "", &cmdToggleMonitor }, // 9C
+ { "version", "", &cmdVersion }, // 9D
+ { "script.size", "n", &cmdScriptSize }, // 9E
+ { "set.game.id", "s", &cmdSetGameID }, // 9F
+ { "log", "s", &cmdLog }, // A0
+ { "set.scan.start", "", &cmdSetScanStart }, // A1
+ { "reset.scan.start", "", &cmdResetScanStart }, // A2
+ { "reposition.to", "nnn", &cmdRepositionTo }, // A3
+ { "reposition.to.v", "nvv", &cmdRepositionToF }, // A4
+ { "trace.on", "", &cmdTraceOn }, // A5
+ { "trace.info", "nnn", &cmdTraceInfo }, // A6
{ "print.at", "snnn", &cmdPrintAt }, // 3 args for AGI versions before 2.440
- { "print.at.v", "vnnn", &cmdPrintAtV },
- { "discard.view.v", "v", &cmdDiscardView},
- { "clear.text.rect", "nnnnn", &cmdClearTextRect },
- { "set.upper.left", "nn", &cmdSetUpperLeft },
- { "set.menu", "s", &cmdSetMenu },
- { "set.menu.item", "sn", &cmdSetMenuItem },
- { "submit.menu", "", &cmdSubmitMenu },
- { "enable.item", "n", &cmdEnableItem },
- { "disable.item", "n", &cmdDisableItem },
- { "menu.input", "", &cmdMenuInput },
- { "show.obj.v", "v", &cmdShowObjV },
- { "open.dialogue", "", &cmdOpenDialogue },
- { "close.dialogue", "", &cmdCloseDialogue },
- { "mul.n", "vn", &cmdMulN },
- { "mul.v", "vv", &cmdMulV },
- { "div.n", "vn", &cmdDivN },
- { "div.v", "vv", &cmdDivV },
- { "close.window", "", &cmdCloseWindow },
- { "set.simple", "n", &cmdSetSimple },
- { "push.script", "", &cmdPushScript },
- { "pop.script", "", &cmdPopScript },
- { "hold.key", "", &cmdHoldKey },
- { "set.pri.base", "n", &cmdSetPriBase },
- { "discard.sound", "n", &cmdDiscardSound },
+ { "print.at.v", "vnnn", &cmdPrintAtV }, // A8
+ { "discard.view.v", "v", &cmdDiscardView}, // A9
+ { "clear.text.rect", "nnnnn", &cmdClearTextRect }, // AA
+ { "set.upper.left", "nn", &cmdSetUpperLeft }, // AB
+ { "set.menu", "s", &cmdSetMenu }, // AC
+ { "set.menu.item", "sn", &cmdSetMenuItem }, // AD
+ { "submit.menu", "", &cmdSubmitMenu }, // AE
+ { "enable.item", "n", &cmdEnableItem }, // AF
+ { "disable.item", "n", &cmdDisableItem }, // B0
+ { "menu.input", "", &cmdMenuInput }, // B1
+ { "show.obj.v", "v", &cmdShowObjV }, // B2
+ { "open.dialogue", "", &cmdOpenDialogue }, // B3
+ { "close.dialogue", "", &cmdCloseDialogue }, // B4
+ { "mul.n", "vn", &cmdMulN }, // B5
+ { "mul.v", "vv", &cmdMulV }, // B6
+ { "div.n", "vn", &cmdDivN }, // B7
+ { "div.v", "vv", &cmdDivV }, // B8
+ { "close.window", "", &cmdCloseWindow }, // B9
+ { "set.simple", "n", &cmdSetSimple }, // BA
+ { "push.script", "", &cmdPushScript }, // BB
+ { "pop.script", "", &cmdPopScript }, // BC
+ { "hold.key", "", &cmdHoldKey }, // BD
+ { "set.pri.base", "n", &cmdSetPriBase }, // BE
+ { "discard.sound", "n", &cmdDiscardSound }, // BF
{ "hide.mouse", "", &cmdHideMouse }, // 1 arg for AGI version 3.002.086
- { "allow.menu", "n", &cmdAllowMenu },
- { "show.mouse", "", &cmdShowMouse },
- { "fence.mouse", "nnnn", &cmdFenceMouse },
- { "mouse.posn", "vv", &cmdMousePosn },
+ { "allow.menu", "n", &cmdAllowMenu }, // C1
+ { "show.mouse", "", &cmdShowMouse }, // C2
+ { "fence.mouse", "nnnn", &cmdFenceMouse }, // C3
+ { "mouse.posn", "vv", &cmdMousePosn }, // C4
{ "release.key", "", &cmdReleaseKey }, // 2 args for at least the Amiga GR (v2.05 1989-03-09) using AGI 2.316
- { "adj.ego.move.to.xy", "", &cmdAdjEgoMoveToXY }
+ { "adj.ego.move.to.xy", "", &cmdAdjEgoMoveToXY } // C6
};
void AgiEngine::setupOpcodes() {
diff --git a/engines/agi/palette.h b/engines/agi/palette.h
new file mode 100644
index 0000000000..88ccce7441
--- /dev/null
+++ b/engines/agi/palette.h
@@ -0,0 +1,311 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef AGI_PALETTE_H
+#define AGI_PALETTE_H
+
+namespace Agi {
+
+/**
+ * 16 color RGB palette.
+ * This array contains the 6-bit RGB values of the EGA palette exported
+ * to the console drivers.
+ */
+static const uint8 PALETTE_EGA[16 * 3] = {
+ 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x2a,
+ 0x00, 0x2a, 0x00,
+ 0x00, 0x2a, 0x2a,
+ 0x2a, 0x00, 0x00,
+ 0x2a, 0x00, 0x2a,
+ 0x2a, 0x15, 0x00,
+ 0x2a, 0x2a, 0x2a,
+ 0x15, 0x15, 0x15,
+ 0x15, 0x15, 0x3f,
+ 0x15, 0x3f, 0x15,
+ 0x15, 0x3f, 0x3f,
+ 0x3f, 0x15, 0x15,
+ 0x3f, 0x15, 0x3f,
+ 0x3f, 0x3f, 0x15,
+ 0x3f, 0x3f, 0x3f
+};
+
+/**
+ * 4 color CGA palette.
+ */
+static const uint8 PALETTE_CGA[4 * 3] = {
+ 0x00, 0x00, 0x00, // black
+ 0x55, 0xff, 0xff, // cyan
+ 0xff, 0x55, 0xff, // magenta
+ 0xff, 0xff, 0xff
+};
+
+/**
+ * Atari ST AGI palette.
+ * Used by all of the tested Atari ST AGI games
+ * from Donald Duck's Playground (1986) to Manhunter II (1989).
+ * 16 RGB colors. 3 bits per color component.
+ */
+static const uint8 PALETTE_ATARI_ST[16 * 3] = {
+ 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x7,
+ 0x0, 0x4, 0x0,
+ 0x0, 0x5, 0x4,
+ 0x5, 0x0, 0x0,
+ 0x5, 0x3, 0x6,
+ 0x4, 0x3, 0x0,
+ 0x5, 0x5, 0x5,
+ 0x3, 0x3, 0x2,
+ 0x0, 0x5, 0x7,
+ 0x0, 0x6, 0x0,
+ 0x0, 0x7, 0x6,
+ 0x7, 0x2, 0x3,
+ 0x7, 0x4, 0x7,
+ 0x7, 0x7, 0x4,
+ 0x7, 0x7, 0x7
+};
+
+/**
+ * Second generation Apple IIGS AGI palette.
+ * A 16-color, 12-bit RGB palette.
+ *
+ * Used by at least the following Apple IIGS AGI versions:
+ * 1.003 (Leisure Suit Larry I v1.0E, intro says 1987)
+ * 1.005 (AGI Demo 2 1987-06-30?)
+ * 1.006 (King's Quest I v1.0S 1988-02-23)
+ * 1.007 (Police Quest I v2.0B 1988-04-21 8:00am)
+ * 1.013 (King's Quest II v2.0A 1988-06-16 (CE))
+ * 1.013 (Mixed-Up Mother Goose v2.0A 1988-05-31 10:00am)
+ * 1.014 (King's Quest III v2.0A 1988-08-28 (CE))
+ * 1.014 (Space Quest II v2.0A, LOGIC.141 says 1988)
+ * 2.004 (Manhunter I v2.0E 1988-10-05 (CE))
+ * 2.006 (King's Quest IV v1.0K 1988-11-22 (CE))
+ * 3.001 (Black Cauldron v1.0O 1989-02-24 (CE))
+ * 3.003 (Gold Rush! v1.0M 1989-02-28 (CE))
+ */
+// *NOT* identical to Amiga generation 2 palette
+static const uint8 PALETTE_APPLE_II_GS[16 * 3] = {
+ 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0xF,
+ 0x0, 0x8, 0x0,
+ 0x0, 0xD, 0xB,
+ 0xC, 0x0, 0x0,
+ 0xB, 0x7, 0xD,
+ 0x8, 0x5, 0x0,
+ 0xB, 0xB, 0xB,
+ 0x7, 0x7, 0x7,
+ 0x0, 0xB, 0xF,
+ 0x0, 0xE, 0x0,
+ 0x0, 0xF, 0xD,
+ 0xF, 0x9, 0x8,
+ 0xD, 0x9, 0xF, // difference between Amiga v2 palette and Apple II GS palette, gotten from emulator (SQ2)
+ 0xE, 0xE, 0x0,
+ 0xF, 0xF, 0xF
+};
+
+/**
+ * First generation Amiga & Apple IIGS AGI palette.
+ * A 16-color, 12-bit RGB palette.
+ *
+ * Used by at least the following Amiga AGI versions:
+ * 2.082 (King's Quest I v1.0U 1986)
+ * 2.082 (Space Quest I v1.2 1986)
+ * 2.090 (King's Quest III v1.01 1986-11-08)
+ * 2.107 (King's Quest II v2.0J 1987-01-29)
+ * x.yyy (Black Cauldron v2.00 1987-06-14)
+ * x.yyy (Larry I v1.05 1987-06-26)
+ *
+ * Also used by at least the following Apple IIGS AGI versions:
+ * 1.002 (Space Quest I, intro says v2.2 1987)
+ */
+static const uint8 PALETTE_AMIGA_V1[16 * 3] = {
+ 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0xF,
+ 0x0, 0x8, 0x0,
+ 0x0, 0xD, 0xB,
+ 0xC, 0x0, 0x0,
+ 0xB, 0x7, 0xD,
+ 0x8, 0x5, 0x0,
+ 0xB, 0xB, 0xB,
+ 0x7, 0x7, 0x7,
+ 0x0, 0xB, 0xF,
+ 0x0, 0xE, 0x0,
+ 0x0, 0xF, 0xD,
+ 0xF, 0x9, 0x8,
+ 0xF, 0x7, 0x0,
+ 0xE, 0xE, 0x0,
+ 0xF, 0xF, 0xF
+};
+
+/**
+ * Second generation Amiga AGI palette.
+ * A 16-color, 12-bit RGB palette.
+ *
+ * Used by at least the following Amiga AGI versions:
+ * 2.202 (Space Quest II v2.0F. Intro says 1988. ScummVM 0.10.0 detects as 1986-12-09)
+ */
+static const uint8 PALETTE_AMIGA_V2[16 * 3] = {
+ 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0xF,
+ 0x0, 0x8, 0x0,
+ 0x0, 0xD, 0xB,
+ 0xC, 0x0, 0x0,
+ 0xB, 0x7, 0xD,
+ 0x8, 0x5, 0x0,
+ 0xB, 0xB, 0xB,
+ 0x7, 0x7, 0x7,
+ 0x0, 0xB, 0xF,
+ 0x0, 0xE, 0x0,
+ 0x0, 0xF, 0xD,
+ 0xF, 0x9, 0x8,
+ 0xD, 0x0, 0xF,
+ 0xE, 0xE, 0x0,
+ 0xF, 0xF, 0xF
+};
+
+/**
+ * Third generation Amiga AGI palette.
+ * A 16-color, 12-bit RGB palette.
+ *
+ * Used by at least the following Amiga AGI versions:
+ * 2.310 (Police Quest I v2.0B 1989-02-22)
+ * 2.316 (Gold Rush! v2.05 1989-03-09)
+ * x.yyy (Manhunter I v1.06 1989-03-18)
+ * 2.333 (Manhunter II v3.06 1989-08-17)
+ * 2.333 (King's Quest III v2.15 1989-11-15)
+ */
+static const uint8 PALETTE_AMIGA_V3[16 * 3] = {
+ 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0xB,
+ 0x0, 0xB, 0x0,
+ 0x0, 0xB, 0xB,
+ 0xB, 0x0, 0x0,
+ 0xB, 0x0, 0xB,
+ 0xC, 0x7, 0x0,
+ 0xB, 0xB, 0xB,
+ 0x7, 0x7, 0x7,
+ 0x0, 0x0, 0xF,
+ 0x0, 0xF, 0x0,
+ 0x0, 0xF, 0xF,
+ 0xF, 0x0, 0x0,
+ 0xF, 0x0, 0xF,
+ 0xF, 0xF, 0x0,
+ 0xF, 0xF, 0xF
+};
+
+/**
+ * 16 color amiga-ish palette.
+ */
+static const uint8 PALETTE_AMIGA_ALT[16 * 3] = {
+ 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x3f,
+ 0x00, 0x2A, 0x00,
+ 0x00, 0x2A, 0x2A,
+ 0x33, 0x00, 0x00,
+ 0x2f, 0x1c, 0x37,
+ 0x23, 0x14, 0x00,
+ 0x2f, 0x2f, 0x2f,
+ 0x15, 0x15, 0x15,
+ 0x00, 0x2f, 0x3f,
+ 0x00, 0x33, 0x15,
+ 0x15, 0x3F, 0x3F,
+ 0x3f, 0x27, 0x23,
+ 0x3f, 0x15, 0x3f,
+ 0x3b, 0x3b, 0x00,
+ 0x3F, 0x3F, 0x3F
+};
+
+/**
+ * 256 color palette used with AGI256 and AGI256-2 games.
+ * Uses full 8 bits per color component.
+ * This is NOT the standard VGA palette.
+ */
+static const uint8 PALETTE_VGA[256 * 3] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xA8, 0x00, 0xA8, 0x00, 0x00, 0xA8, 0xA8,
+ 0xA8, 0x00, 0x00, 0xA8, 0x00, 0xA8, 0xA8, 0x54, 0x00, 0xA8, 0xA8, 0xA8,
+ 0x54, 0x54, 0x54, 0x54, 0x54, 0xFC, 0x54, 0xFC, 0x54, 0x54, 0xFC, 0xFC,
+ 0xFC, 0x54, 0x54, 0xFC, 0x54, 0xFC, 0xFC, 0xFC, 0x54, 0xFC, 0xFC, 0xFC,
+ 0x00, 0x00, 0x00, 0x14, 0x14, 0x14, 0x20, 0x20, 0x20, 0x2C, 0x2C, 0x2C,
+ 0x38, 0x38, 0x38, 0x44, 0x44, 0x44, 0x50, 0x50, 0x50, 0x60, 0x60, 0x60,
+ 0x70, 0x70, 0x70, 0x80, 0x80, 0x80, 0x90, 0x90, 0x90, 0xA0, 0xA0, 0xA0,
+ 0xB4, 0xB4, 0xB4, 0xC8, 0xC8, 0xC8, 0xE0, 0xE0, 0xE0, 0xFC, 0xFC, 0xFC,
+ 0x00, 0x00, 0xFC, 0x40, 0x00, 0xFC, 0x7C, 0x00, 0xFC, 0xBC, 0x00, 0xFC,
+ 0xFC, 0x00, 0xFC, 0xFC, 0x00, 0xBC, 0xFC, 0x00, 0x7C, 0xFC, 0x00, 0x40,
+ 0xFC, 0x00, 0x00, 0xFC, 0x40, 0x00, 0xFC, 0x7C, 0x00, 0xFC, 0xBC, 0x00,
+ 0xFC, 0xFC, 0x00, 0xBC, 0xFC, 0x00, 0x7C, 0xFC, 0x00, 0x40, 0xFC, 0x00,
+ 0x00, 0xFC, 0x00, 0x00, 0xFC, 0x40, 0x00, 0xFC, 0x7C, 0x00, 0xFC, 0xBC,
+ 0x00, 0xFC, 0xFC, 0x00, 0xBC, 0xFC, 0x00, 0x7C, 0xFC, 0x00, 0x40, 0xFC,
+ 0x7C, 0x7C, 0xFC, 0x9C, 0x7C, 0xFC, 0xBC, 0x7C, 0xFC, 0xDC, 0x7C, 0xFC,
+ 0xFC, 0x7C, 0xFC, 0xFC, 0x7C, 0xDC, 0xFC, 0x7C, 0xBC, 0xFC, 0x7C, 0x9C,
+ 0xFC, 0x7C, 0x7C, 0xFC, 0x9C, 0x7C, 0xFC, 0xBC, 0x7C, 0xFC, 0xDC, 0x7C,
+ 0xFC, 0xFC, 0x7C, 0xDC, 0xFC, 0x7C, 0xBC, 0xFC, 0x7C, 0x9C, 0xFC, 0x7C,
+ 0x7C, 0xFC, 0x7C, 0x7C, 0xFC, 0x9C, 0x7C, 0xFC, 0xBC, 0x7C, 0xFC, 0xDC,
+ 0x7C, 0xFC, 0xFC, 0x7C, 0xDC, 0xFC, 0x7C, 0xBC, 0xFC, 0x7C, 0x9C, 0xFC,
+ 0xB4, 0xB4, 0xFC, 0xC4, 0xB4, 0xFC, 0xD8, 0xB4, 0xFC, 0xE8, 0xB4, 0xFC,
+ 0xFC, 0xB4, 0xFC, 0xFC, 0xB4, 0xE8, 0xFC, 0xB4, 0xD8, 0xFC, 0xB4, 0xC4,
+ 0xFC, 0xB4, 0xB4, 0xFC, 0xC4, 0xB4, 0xFC, 0xD8, 0xB4, 0xFC, 0xE8, 0xB4,
+ 0xFC, 0xFC, 0xB4, 0xE8, 0xFC, 0xB4, 0xD8, 0xFC, 0xB4, 0xC4, 0xFC, 0xB4,
+ 0xB4, 0xFC, 0xB4, 0xB4, 0xFC, 0xC4, 0xB4, 0xFC, 0xD8, 0xB4, 0xFC, 0xE8,
+ 0xB4, 0xFC, 0xFC, 0xB4, 0xE8, 0xFC, 0xB4, 0xD8, 0xFC, 0xB4, 0xC4, 0xFC,
+ 0x00, 0x00, 0x70, 0x1C, 0x00, 0x70, 0x38, 0x00, 0x70, 0x54, 0x00, 0x70,
+ 0x70, 0x00, 0x70, 0x70, 0x00, 0x54, 0x70, 0x00, 0x38, 0x70, 0x00, 0x1C,
+ 0x70, 0x00, 0x00, 0x70, 0x1C, 0x00, 0x70, 0x38, 0x00, 0x70, 0x54, 0x00,
+ 0x70, 0x70, 0x00, 0x54, 0x70, 0x00, 0x38, 0x70, 0x00, 0x1C, 0x70, 0x00,
+ 0x00, 0x70, 0x00, 0x00, 0x70, 0x1C, 0x00, 0x70, 0x38, 0x00, 0x70, 0x54,
+ 0x00, 0x70, 0x70, 0x00, 0x54, 0x70, 0x00, 0x38, 0x70, 0x00, 0x1C, 0x70,
+ 0x38, 0x38, 0x70, 0x44, 0x38, 0x70, 0x54, 0x38, 0x70, 0x60, 0x38, 0x70,
+ 0x70, 0x38, 0x70, 0x70, 0x38, 0x60, 0x70, 0x38, 0x54, 0x70, 0x38, 0x44,
+ 0x70, 0x38, 0x38, 0x70, 0x44, 0x38, 0x70, 0x54, 0x38, 0x70, 0x60, 0x38,
+ 0x70, 0x70, 0x38, 0x60, 0x70, 0x38, 0x54, 0x70, 0x38, 0x44, 0x70, 0x38,
+ 0x38, 0x70, 0x38, 0x38, 0x70, 0x44, 0x38, 0x70, 0x54, 0x38, 0x70, 0x60,
+ 0x38, 0x70, 0x70, 0x38, 0x60, 0x70, 0x38, 0x54, 0x70, 0x38, 0x44, 0x70,
+ 0x50, 0x50, 0x70, 0x58, 0x50, 0x70, 0x60, 0x50, 0x70, 0x68, 0x50, 0x70,
+ 0x70, 0x50, 0x70, 0x70, 0x50, 0x68, 0x70, 0x50, 0x60, 0x70, 0x50, 0x58,
+ 0x70, 0x50, 0x50, 0x70, 0x58, 0x50, 0x70, 0x60, 0x50, 0x70, 0x68, 0x50,
+ 0x70, 0x70, 0x50, 0x68, 0x70, 0x50, 0x60, 0x70, 0x50, 0x58, 0x70, 0x50,
+ 0x50, 0x70, 0x50, 0x50, 0x70, 0x58, 0x50, 0x70, 0x60, 0x50, 0x70, 0x68,
+ 0x50, 0x70, 0x70, 0x50, 0x68, 0x70, 0x50, 0x60, 0x70, 0x50, 0x58, 0x70,
+ 0x00, 0x00, 0x40, 0x10, 0x00, 0x40, 0x20, 0x00, 0x40, 0x30, 0x00, 0x40,
+ 0x40, 0x00, 0x40, 0x40, 0x00, 0x30, 0x40, 0x00, 0x20, 0x40, 0x00, 0x10,
+ 0x40, 0x00, 0x00, 0x40, 0x10, 0x00, 0x40, 0x20, 0x00, 0x40, 0x30, 0x00,
+ 0x40, 0x40, 0x00, 0x30, 0x40, 0x00, 0x20, 0x40, 0x00, 0x10, 0x40, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x40, 0x10, 0x00, 0x40, 0x20, 0x00, 0x40, 0x30,
+ 0x00, 0x40, 0x40, 0x00, 0x30, 0x40, 0x00, 0x20, 0x40, 0x00, 0x10, 0x40,
+ 0x20, 0x20, 0x40, 0x28, 0x20, 0x40, 0x30, 0x20, 0x40, 0x38, 0x20, 0x40,
+ 0x40, 0x20, 0x40, 0x40, 0x20, 0x38, 0x40, 0x20, 0x30, 0x40, 0x20, 0x28,
+ 0x40, 0x20, 0x20, 0x40, 0x28, 0x20, 0x40, 0x30, 0x20, 0x40, 0x38, 0x20,
+ 0x40, 0x40, 0x20, 0x38, 0x40, 0x20, 0x30, 0x40, 0x20, 0x28, 0x40, 0x20,
+ 0x20, 0x40, 0x20, 0x20, 0x40, 0x28, 0x20, 0x40, 0x30, 0x20, 0x40, 0x38,
+ 0x20, 0x40, 0x40, 0x20, 0x38, 0x40, 0x20, 0x30, 0x40, 0x20, 0x28, 0x40,
+ 0x2C, 0x2C, 0x40, 0x30, 0x2C, 0x40, 0x34, 0x2C, 0x40, 0x3C, 0x2C, 0x40,
+ 0x40, 0x2C, 0x40, 0x40, 0x2C, 0x3C, 0x40, 0x2C, 0x34, 0x40, 0x2C, 0x30,
+ 0x40, 0x2C, 0x2C, 0x40, 0x30, 0x2C, 0x40, 0x34, 0x2C, 0x40, 0x3C, 0x2C,
+ 0x40, 0x40, 0x2C, 0x3C, 0x40, 0x2C, 0x34, 0x40, 0x2C, 0x30, 0x40, 0x2C,
+ 0x2C, 0x40, 0x2C, 0x2C, 0x40, 0x30, 0x2C, 0x40, 0x34, 0x2C, 0x40, 0x3C,
+ 0x2C, 0x40, 0x40, 0x2C, 0x3C, 0x40, 0x2C, 0x34, 0x40, 0x2C, 0x30, 0x40,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+} // End of namespace Agi
+
+#endif /* AGI_PALETTE_H */
diff --git a/engines/agi/picture.cpp b/engines/agi/picture.cpp
index 58dfb9db68..951d67a0c0 100644
--- a/engines/agi/picture.cpp
+++ b/engines/agi/picture.cpp
@@ -31,8 +31,11 @@ PictureMgr::PictureMgr(AgiBase *agi, GfxMgr *gfx) {
_vm = agi;
_gfx = gfx;
+ _resourceNr = 0;
_data = NULL;
- _flen = _foffs = 0;
+ _dataSize = 0;
+ _dataOffset = 0;
+ _dataOffsetNibble = false;
_patCode = _patNum = _priOn = _scrOn = _scrColor = _priColor = 0;
_xOffset = _yOffset = 0;
@@ -44,257 +47,38 @@ PictureMgr::PictureMgr(AgiBase *agi, GfxMgr *gfx) {
}
void PictureMgr::putVirtPixel(int x, int y) {
- uint8 *p;
-
- x += _xOffset;
- y += _yOffset;
+ byte drawMask = 0;
if (x < 0 || y < 0 || x >= _width || y >= _height)
return;
- p = &_vm->_game.sbuf16c[y * _width + x];
+ x += _xOffset;
+ y += _yOffset;
if (_priOn)
- *p = (_priColor << 4) | (*p & 0x0f);
+ drawMask |= GFX_SCREEN_MASK_PRIORITY;
if (_scrOn)
- *p = _scrColor | (*p & 0xf0);
-}
+ drawMask |= GFX_SCREEN_MASK_VISUAL;
-#if 0
-static void drawProc(int x, int y, int c, void *data) {
- ((PictureMgr *)data)->putVirtPixel(x, y);
+ _gfx->putPixel(x, y, drawMask, _scrColor, _priColor);
}
-#endif
-
-/**
- * Draw an AGI line.
- * A line drawing routine sent by Joshua Neal, modified by Stuart George
- * (fixed >>2 to >>1 and some other bugs like x1 instead of y1, etc.)
- * @param x1 x coordinate of start point
- * @param y1 y coordinate of start point
- * @param x2 x coordinate of end point
- * @param y2 y coordinate of end point
- */
-void PictureMgr::drawLine(int x1, int y1, int x2, int y2) {
- x1 = CLIP(x1, 0, _width - 1);
- x2 = CLIP(x2, 0, _width - 1);
- y1 = CLIP(y1, 0, _height - 1);
- y2 = CLIP(y2, 0, _height - 1);
-
-#if 0
- Graphics::drawLine(x1, y1, x2, y2, 0, drawProc, this);
-#else
- int i, x, y, deltaX, deltaY, stepX, stepY, errorX, errorY, detdelta;
-
- // Vertical line
-
- if (x1 == x2) {
- if (y1 > y2) {
- SWAP(y1, y2);
- }
-
- for (; y1 <= y2; y1++)
- putVirtPixel(x1, y1);
-
- return;
- }
-
- // Horizontal line
-
- if (y1 == y2) {
- if (x1 > x2) {
- SWAP(x1, x2);
- }
- for (; x1 <= x2; x1++)
- putVirtPixel(x1, y1);
- return;
- }
-
- y = y1;
- x = x1;
- stepY = 1;
- deltaY = y2 - y1;
- if (deltaY < 0) {
- stepY = -1;
- deltaY = -deltaY;
- }
-
- stepX = 1;
- deltaX = x2 - x1;
- if (deltaX < 0) {
- stepX = -1;
- deltaX = -deltaX;
- }
-
- if (deltaY > deltaX) {
- i = deltaY;
- detdelta = deltaY;
- errorX = deltaY / 2;
- errorY = 0;
+byte PictureMgr::getNextByte() {
+ if (!_dataOffsetNibble) {
+ return _data[_dataOffset++];
} else {
- i = deltaX;
- detdelta = deltaX;
- errorX = 0;
- errorY = deltaX / 2;
+ byte curByte = _data[_dataOffset++] << 4;
+ return (_data[_dataOffset] >> 4) | curByte;
}
-
- putVirtPixel(x, y);
-
- do {
- errorY += deltaY;
- if (errorY >= detdelta) {
- errorY -= detdelta;
- y += stepY;
- }
-
- errorX += deltaX;
- if (errorX >= detdelta) {
- errorX -= detdelta;
- x += stepX;
- }
-
- putVirtPixel(x, y);
- i--;
- } while (i > 0);
-#endif
}
-/**
- * Draw a relative AGI line.
- * Draws short lines relative to last position. (drawing action 0xF7)
- */
-void PictureMgr::dynamicDrawLine() {
- int x1, y1, disp, dx, dy;
-
- if ((x1 = nextByte()) >= _minCommand ||
- (y1 = nextByte()) >= _minCommand) {
- _foffs--;
- return;
- }
-
- putVirtPixel(x1, y1);
-
- for (;;) {
- if ((disp = nextByte()) >= _minCommand)
- break;
-
- dx = ((disp & 0xf0) >> 4) & 0x0f;
- dy = (disp & 0x0f);
-
- if (dx & 0x08)
- dx = -(dx & 0x07);
- if (dy & 0x08)
- dy = -(dy & 0x07);
-
- drawLine(x1, y1, x1 + dx, y1 + dy);
- x1 += dx;
- y1 += dy;
- }
- _foffs--;
-}
-
-/**************************************************************************
-** absoluteLine
-**
-** Draws long lines to actual locations (cf. relative) (drawing action 0xF6)
-**************************************************************************/
-void PictureMgr::absoluteDrawLine() {
- int x1, y1, x2, y2;
-
- if ((x1 = nextByte()) >= _minCommand ||
- (y1 = nextByte()) >= _minCommand) {
- _foffs--;
- return;
- }
-
- putVirtPixel(x1, y1);
-
- for (;;) {
- if ((x2 = nextByte()) >= _minCommand)
- break;
-
- if ((y2 = nextByte()) >= _minCommand)
- break;
-
- drawLine(x1, y1, x2, y2);
- x1 = x2;
- y1 = y2;
- }
- _foffs--;
-}
-
-/**************************************************************************
-** okToFill
-**************************************************************************/
-int PictureMgr::isOkFillHere(int x, int y) {
- uint8 p;
-
- x += _xOffset;
- y += _yOffset;
-
- if (x < 0 || x >= _width || y < 0 || y >= _height)
- return false;
-
- p = _vm->_game.sbuf16c[y * _width + x];
-
- if (_flags & kPicFTrollMode)
- return ((p & 0x0f) != 11 && (p & 0x0f) != _scrColor);
-
- if (!_priOn && _scrOn && _scrColor != 15)
- return (p & 0x0f) == 15;
-
- if (_priOn && !_scrOn && _priColor != 4)
- return (p >> 4) == 4;
-
- return (_scrOn && (p & 0x0f) == 15 && _scrColor != 15);
-}
-
-/**************************************************************************
-** agi_fill
-**************************************************************************/
-void PictureMgr::agiFill(unsigned int x, unsigned int y) {
- if (!_scrOn && !_priOn)
- return;
-
- // Push initial pixel on the stack
- Common::Stack<Common::Point> stack;
- stack.push(Common::Point(x,y));
-
- // Exit if stack is empty
- while (!stack.empty()) {
- Common::Point p = stack.pop();
- unsigned int c;
- int newspanUp, newspanDown;
-
- if (!isOkFillHere(p.x, p.y))
- continue;
-
- // Scan for left border
- for (c = p.x - 1; isOkFillHere(c, p.y); c--)
- ;
-
- newspanUp = newspanDown = 1;
- for (c++; isOkFillHere(c, p.y); c++) {
- putVirtPixel(c, p.y);
- if (isOkFillHere(c, p.y - 1)) {
- if (newspanUp) {
- stack.push(Common::Point(c,p.y-1));
- newspanUp = 0;
- }
- } else {
- newspanUp = 1;
- }
-
- if (isOkFillHere(c, p.y + 1)) {
- if (newspanDown) {
- stack.push(Common::Point(c,p.y+1));
- newspanDown = 0;
- }
- } else {
- newspanDown = 1;
- }
- }
+byte PictureMgr::getNextNibble() {
+ if (!_dataOffsetNibble) {
+ _dataOffsetNibble = true;
+ return _data[_dataOffset] >> 4;
+ } else {
+ _dataOffsetNibble = false;
+ return _data[_dataOffset++] & 0x0F;
}
}
@@ -303,43 +87,43 @@ void PictureMgr::agiFill(unsigned int x, unsigned int y) {
**
** Draws an xCorner (drawing action 0xF5)
**************************************************************************/
-void PictureMgr::xCorner(bool skipOtherCoords) {
+void PictureMgr::draw_xCorner(bool skipOtherCoords) {
int x1, x2, y1, y2;
- if ((x1 = nextByte()) >= _minCommand ||
- (y1 = nextByte()) >= _minCommand) {
- _foffs--;
+ if ((x1 = getNextByte()) >= _minCommand ||
+ (y1 = getNextByte()) >= _minCommand) {
+ _dataOffset--;
return;
}
putVirtPixel(x1, y1);
for (;;) {
- x2 = nextByte();
+ x2 = getNextByte();
if (x2 >= _minCommand)
break;
if (skipOtherCoords)
- if (nextByte() >= _minCommand)
+ if (getNextByte() >= _minCommand)
break;
- drawLine(x1, y1, x2, y1);
+ draw_Line(x1, y1, x2, y1);
x1 = x2;
if (skipOtherCoords)
- if (nextByte() >= _minCommand)
+ if (getNextByte() >= _minCommand)
break;
- y2 = nextByte();
+ y2 = getNextByte();
if (y2 >= _minCommand)
break;
- drawLine(x1, y1, x1, y2);
+ draw_Line(x1, y1, x1, y2);
y1 = y2;
}
- _foffs--;
+ _dataOffset--;
}
/**************************************************************************
@@ -350,9 +134,9 @@ void PictureMgr::xCorner(bool skipOtherCoords) {
void PictureMgr::yCorner(bool skipOtherCoords) {
int x1, x2, y1, y2;
- if ((x1 = nextByte()) >= _minCommand ||
- (y1 = nextByte()) >= _minCommand) {
- _foffs--;
+ if ((x1 = getNextByte()) >= _minCommand ||
+ (y1 = getNextByte()) >= _minCommand) {
+ _dataOffset--;
return;
}
@@ -360,44 +144,30 @@ void PictureMgr::yCorner(bool skipOtherCoords) {
for (;;) {
if (skipOtherCoords)
- if (nextByte() >= _minCommand)
+ if (getNextByte() >= _minCommand)
break;
- y2 = nextByte();
+ y2 = getNextByte();
if (y2 >= _minCommand)
break;
- drawLine(x1, y1, x1, y2);
+ draw_Line(x1, y1, x1, y2);
y1 = y2;
- x2 = nextByte();
+ x2 = getNextByte();
if (x2 >= _minCommand)
break;
if (skipOtherCoords)
- if (nextByte() >= _minCommand)
+ if (getNextByte() >= _minCommand)
break;
- drawLine(x1, y1, x2, y1);
+ draw_Line(x1, y1, x2, y1);
x1 = x2;
}
- _foffs--;
-}
-
-/**************************************************************************
-** fill
-**
-** AGI flood fill. (drawing action 0xF8)
-**************************************************************************/
-void PictureMgr::fill() {
- int x1, y1;
-
- while ((x1 = nextByte()) < _minCommand && (y1 = nextByte()) < _minCommand)
- agiFill(x1, y1);
-
- _foffs--;
+ _dataOffset--;
}
/**************************************************************************
@@ -406,7 +176,6 @@ void PictureMgr::fill() {
** Draws pixels, circles, squares, or splatter brush patterns depending
** on the pattern code.
**************************************************************************/
-
void PictureMgr::plotPattern(int x, int y) {
static const uint16 binary_list[] = {0x8000, 0x4000, 0x2000, 0x1000, 0x800, 0x400, 0x200, 0x100,
0x80, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1};
@@ -532,267 +301,600 @@ void PictureMgr::plotBrush() {
for (;;) {
if (_patCode & 0x20) {
- if ((_patNum = nextByte()) >= _minCommand)
+ if ((_patNum = getNextByte()) >= _minCommand)
break;
_patNum = (_patNum >> 1) & 0x7f;
}
- if ((x1 = nextByte()) >= _minCommand)
+ if ((x1 = getNextByte()) >= _minCommand)
break;
- if ((y1 = nextByte()) >= _minCommand)
+ if ((y1 = getNextByte()) >= _minCommand)
break;
plotPattern(x1, y1);
}
- _foffs--;
+ _dataOffset--;
}
/**************************************************************************
** Draw AGI picture
**************************************************************************/
-
void PictureMgr::drawPicture() {
- uint8 act;
- int drawing;
- int iteration = 0;
-
_patCode = 0;
_patNum = 0;
- _priOn = _scrOn = false;
- _scrColor = (_pictureVersion == AGIPIC_C64) ? 0x0 : 0xf;
- _priColor = 0x4;
+ _priOn = false;
+ _scrOn = false;
+ _scrColor = 15;
+ _priColor = 4;
+
+ switch (_pictureVersion) {
+ case AGIPIC_C64:
+ drawPictureC64();
+ break;
+ case AGIPIC_V1:
+ drawPictureV1();
+ break;
+ case AGIPIC_V15:
+ drawPictureV15();
+ break;
+ case AGIPIC_V2:
+ drawPictureV2();
+ break;
+ case AGIPIC_256:
+ drawPictureAGI256();
+ break;
+ default:
+ break;
+ }
+}
- drawing = 1;
+void PictureMgr::drawPictureC64() {
+ byte curByte;
- debugC(8, kDebugLevelMain, "Drawing v2 picture");
- for (drawing = 1; drawing && _foffs < _flen;) {
- act = nextByte();
+ debugC(8, kDebugLevelMain, "Drawing C64 picture");
- if (_pictureVersion == AGIPIC_C64 && act >= 0xf0 && act <= 0xfe) {
- _scrColor = act - 0xf0;
+ _scrColor = 0x0;
+
+ while (_dataOffset < _dataSize) {
+ curByte = getNextByte();
+
+ if ((curByte >= 0xF0) && (curByte <= 0xFE)) {
+ _scrColor = curByte & 0x0F;
continue;
}
- switch (act) {
- case 0xe0: // x-corner (C64)
- xCorner();
+ switch (curByte) {
+ case 0xe0: // x-corner
+ draw_xCorner();
break;
- case 0xe1: // y-corner (C64)
+ case 0xe1: // y-corner
yCorner();
break;
- case 0xe2: // dynamic draw lines (C64)
- dynamicDrawLine();
+ case 0xe2: // dynamic draw lines
+ draw_LineShort();
break;
- case 0xe3: // absolute draw lines (C64)
- absoluteDrawLine();
+ case 0xe3: // absolute draw lines
+ draw_LineAbsolute();
break;
- case 0xe4: // fill (C64)
- _scrColor = nextByte();
- _scrColor &= 0xF; // for v3 drawing diff
- fill();
+ case 0xe4: // fill
+ draw_SetColor();
+ draw_Fill();
break;
- case 0xe5: // enable screen drawing (C64)
+ case 0xe5: // enable screen drawing
_scrOn = true;
break;
- case 0xe6: // plot brush (C64)
- _patCode = nextByte();
+ case 0xe6: // plot brush
+ _patCode = getNextByte();
plotBrush();
break;
- case 0xf0: // set color on screen (AGI pic v2)
- if (_pictureVersion == AGIPIC_V15)
- break;
+ case 0xfb:
+ draw_LineShort();
+ break;
+ case 0xff: // end of data
+ return;
+ default:
+ warning("Unknown picture opcode (%x) at (%x)", curByte, _dataOffset - 1);
+ break;
+ }
+ }
+}
+
+void PictureMgr::drawPictureV1() {
+ byte curByte;
+
+ debugC(8, kDebugLevelMain, "Drawing V1 picture");
- _scrColor = nextByte();
- _scrColor &= 0xF; // for v3 drawing diff
+ while (_dataOffset < _dataSize) {
+ curByte = getNextByte();
+
+ switch (curByte) {
+ case 0xf1:
+ draw_SetColor();
_scrOn = true;
+ _priOn = false;
break;
- case 0xf1:
- if (_pictureVersion == AGIPIC_V1) {
- _scrColor = nextByte();
- _scrColor &= 0xF; // for v3 drawing diff
- _scrOn = true;
- _priOn = false;
- } else if (_pictureVersion == AGIPIC_V15) { // set color on screen
- _scrColor = nextByte();
- _scrColor &= 0xF;
- _scrOn = true;
- } else if (_pictureVersion == AGIPIC_V2) { // disable screen drawing
- _scrOn = false;
- }
+ case 0xf3:
+ draw_SetColor();
+ _scrOn = true;
+ draw_SetPriority();
+ _priOn = true;
break;
- case 0xf2: // set color on priority (AGI pic v2)
- if (_pictureVersion == AGIPIC_V15)
- break;
-
- _priColor = nextByte();
- _priColor &= 0xf; // for v3 drawing diff
+ case 0xfa:
+ _scrOn = false;
_priOn = true;
+ draw_LineAbsolute();
+ _scrOn = true;
+ _priOn = false;
break;
- case 0xf3:
- if (_pictureVersion == AGIPIC_V1) {
- _scrColor = nextByte();
- _scrColor &= 0xF; // for v3 drawing diff
- _scrOn = true;
- _priColor = nextByte();
- _priColor &= 0xf; // for v3 drawing diff
- _priOn = true;
- }
+ case 0xff: // end of data
+ return;
+ default:
+ warning("Unknown picture opcode (%x) at (%x)", curByte, _dataOffset - 1);
+ break;
+ }
+ }
+}
- if (_pictureVersion == AGIPIC_V15 && (_flags & kPicFf3Stop))
- drawing = 0;
+void PictureMgr::drawPictureV15() {
+ byte curByte;
- if (_pictureVersion == AGIPIC_V2) // disable priority screen
- _priOn = false;
- break;
- case 0xf4: // y-corner
- if (_pictureVersion == AGIPIC_V15)
- break;
+ debugC(8, kDebugLevelMain, "Drawing V1.5 picture");
- yCorner();
- break;
- case 0xf5: // x-corner
- if (_pictureVersion == AGIPIC_V15)
- break;
+ while (_dataOffset < _dataSize) {
+ curByte = getNextByte();
- xCorner();
+ switch (curByte) {
+ case 0xf0:
+ // happens in all Troll's Tale pictures
+ // TODO: figure out what it was meant for
break;
- case 0xf6: // absolute draw lines
- if (_pictureVersion == AGIPIC_V15)
- break;
-
- absoluteDrawLine();
+ case 0xf1:
+ draw_SetColor();
+ _scrOn = true;
break;
- case 0xf7: // dynamic draw lines
- if (_pictureVersion == AGIPIC_V15)
- break;
-
- dynamicDrawLine();
+ case 0xf3:
+ if (_flags & kPicFf3Stop)
+ return;
break;
- case 0xf8: // fill
- if (_pictureVersion == AGIPIC_V15) {
- yCorner(true);
- } else if (_pictureVersion == AGIPIC_V2) {
- fill();
- }
+ case 0xf8:
+ yCorner(true);
break;
- case 0xf9: // set pattern
- if (_pictureVersion == AGIPIC_V15) {
- xCorner(true);
- } else if (_pictureVersion == AGIPIC_V2) {
- _patCode = nextByte();
+ case 0xf9:
+ draw_xCorner(true);
+ break;
+ case 0xfa:
+ // TODO: is this really correct?
+ draw_LineAbsolute();
+ break;
+ case 0xfb:
+ // TODO: is this really correct?
+ draw_LineAbsolute();
+ break;
+ case 0xfe:
+ draw_SetColor();
+ _scrOn = true;
+ draw_Fill();
+ break;
+ case 0xff: // end of data
+ return;
+ default:
+ warning("Unknown picture opcode (%x) at (%x)", curByte, _dataOffset - 1);
+ break;
+ }
+ }
+}
- if (_vm->getGameType() == GType_PreAGI)
- plotBrush();
+void PictureMgr::drawPictureV2() {
+ byte curByte;
+ bool nibbleMode = false;
+ bool mickeyCrystalAnimation = false;
+ int mickeyIteration = 0;
+
+ debugC(8, kDebugLevelMain, "Drawing V2/V3 picture");
+
+ if (_vm->_game.dirPic[_resourceNr].flags & RES_PICTURE_V3_NIBBLE_PARM) {
+ // check, if this resource uses nibble mode (0xF0 + 0xF2 commands take nibbles instead of bytes)
+ nibbleMode = true;
+ }
+
+ if ((_flags & kPicFStep) && _vm->getGameType() == GType_PreAGI) {
+ mickeyCrystalAnimation = true;
+ }
+
+ while (_dataOffset < _dataSize) {
+ curByte = getNextByte();
+
+ switch (curByte) {
+ case 0xf0:
+ if (!nibbleMode) {
+ draw_SetColor();
+ } else {
+ draw_SetNibbleColor();
}
+ _scrOn = true;
break;
- case 0xfa: // plot brush
- if (_pictureVersion == AGIPIC_V1) {
- _scrOn = false;
- _priOn = true;
- absoluteDrawLine();
- _scrOn = true;
- _priOn = false;
- } else if (_pictureVersion == AGIPIC_V15) {
- absoluteDrawLine();
- } else if (_pictureVersion == AGIPIC_V2) {
- plotBrush();
- }
+ case 0xf1:
+ _scrOn = false;
break;
- case 0xfb:
- if (_pictureVersion == AGIPIC_V1) {
- dynamicDrawLine();
- } else if (_pictureVersion == AGIPIC_V15) {
- absoluteDrawLine();
+ case 0xf2:
+ if (!nibbleMode) {
+ draw_SetPriority();
+ } else {
+ draw_SetNibblePriority();
}
+ _priOn = true;
break;
- case 0xfc: // fill (AGI pic v1)
- if (_pictureVersion == AGIPIC_V15)
- break;
+ case 0xf3:
+ _priOn = false;
+ break;
+ case 0xf4:
+ yCorner();
+ break;
+ case 0xf5:
+ draw_xCorner();
+ break;
+ case 0xf6:
+ draw_LineAbsolute();
+ break;
+ case 0xf7:
+ draw_LineShort();
+ break;
+ case 0xf8:
+ draw_Fill();
+ break;
+ case 0xf9:
+ _patCode = getNextByte();
- _scrColor = nextByte();
- _scrColor &= 0xF;
- _priColor = nextByte();
- _priColor &= 0xf;
- fill();
+ if (_vm->getGameType() == GType_PreAGI)
+ plotBrush();
break;
- case 0xfe: // fill (AGI pic v1.5)
- _scrColor = nextByte();
- _scrColor &= 0xF;
- _scrOn = true;
- fill();
+ case 0xfa:
+ plotBrush();
break;
- case 0xff: // end of pic data
- drawing = 0;
+ case 0xfc:
+ draw_SetColor();
+ draw_SetPriority();
+ draw_Fill();
break;
+ case 0xff: // end of data
+ return;
default:
- warning("Unknown picture opcode (%x) at (%x)", act, _foffs - 1);
+ warning("Unknown picture opcode (%x) at (%x)", curByte, _dataOffset - 1);
+ break;
}
// This is used by Mickey for the crystal animation
// One frame of the crystal animation is shown on each iteration, based on _currentStep
- if ((_flags & kPicFStep) && _vm->getGameType() == GType_PreAGI && _currentStep == iteration) {
- int storedXOffset = _xOffset;
- int storedYOffset = _yOffset;
- // Note that picture coordinates are correct for Mickey only
- showPic(10, 0, _width, _height);
- _xOffset = storedXOffset;
- _yOffset = storedYOffset;
- _currentStep++;
- if (_currentStep > 14) // crystal animation is 15 frames
- _currentStep = 0;
- // reset the picture step flag - it will be set when the next frame of the crystal animation is drawn
- _flags &= ~kPicFStep;
- return; // return back to the game loop
+ if (mickeyCrystalAnimation) {
+ if (_currentStep == mickeyIteration) {
+ int storedXOffset = _xOffset;
+ int storedYOffset = _yOffset;
+ // Note that picture coordinates are correct for Mickey only
+ showPic(10, 0, _width, _height);
+ _xOffset = storedXOffset;
+ _yOffset = storedYOffset;
+ _currentStep++;
+ if (_currentStep > 14) // crystal animation is 15 frames
+ _currentStep = 0;
+ // reset the picture step flag - it will be set when the next frame of the crystal animation is drawn
+ _flags &= ~kPicFStep;
+ return; // return back to the game loop
+ }
+ mickeyIteration++;
+ }
+ }
+}
+
+void PictureMgr::drawPictureAGI256() {
+ const uint32 maxFlen = _width * _height;
+ int16 x = 0;
+ int16 y = 0;
+ byte *dataPtr = _data;
+ byte *dataEndPtr = _data + _dataSize;
+ byte color = 0;
+
+ debugC(8, kDebugLevelMain, "Drawing AGI256 picture");
+
+ while (dataPtr < dataEndPtr) {
+ color = *dataPtr++;
+ _gfx->putPixel(x, y, GFX_SCREEN_MASK_VISUAL, color, 0);
+
+ x++;
+ if (x >= _width) {
+ x = 0;
+ y++;
+ if (y >= _height) {
+ break;
+ }
+ }
+ }
+
+ if (_dataSize < maxFlen) {
+ warning("Undersized AGI256 picture resource %d, using it anyway. Filling rest with white.", _resourceNr);
+ while (_dataSize < maxFlen) {
+ x++;
+ if (x >= _width) {
+ x = 0;
+ y++;
+ if (y >= _height)
+ break;
+ }
+ _gfx->putPixel(x, y, GFX_SCREEN_MASK_VISUAL, 15, 0);
}
+ } else if (_dataSize > maxFlen)
+ warning("Oversized AGI256 picture resource %d, decoding only %ux%u part of it", _resourceNr, _width, _height);
+}
+
+void PictureMgr::draw_SetColor() {
+ _scrColor = getNextByte();
+
+ // For CGA, replace the color with its mixture color
+ switch (_vm->_renderMode) {
+ case RENDERMODE_CGA:
+ _scrColor = _gfx->getCGAMixtureColor(_scrColor);
+ break;
+ default:
+ break;
+ }
+}
+
+void PictureMgr::draw_SetPriority() {
+ _priColor = getNextByte();
+}
- iteration++;
+// this gets a nibble instead of a full byte
+// used by some V3 games, special resource flag RES_PICTURE_V3_NIBBLE_PARM is set
+void PictureMgr::draw_SetNibbleColor() {
+ _scrColor = getNextNibble();
+
+ // For CGA, replace the color with its mixture color
+ switch (_vm->_renderMode) {
+ case RENDERMODE_CGA:
+ _scrColor = _gfx->getCGAMixtureColor(_scrColor);
+ break;
+ default:
+ break;
}
}
+void PictureMgr::draw_SetNibblePriority() {
+ _priColor = getNextNibble();
+}
+
/**
- * convert AGI v3 format picture to AGI v2 format
+ * Draw an AGI line.
+ * A line drawing routine sent by Joshua Neal, modified by Stuart George
+ * (fixed >>2 to >>1 and some other bugs like x1 instead of y1, etc.)
+ * @param x1 x coordinate of start point
+ * @param y1 y coordinate of start point
+ * @param x2 x coordinate of end point
+ * @param y2 y coordinate of end point
*/
-uint8 *PictureMgr::convertV3Pic(uint8 *src, uint32 len) {
- uint8 d, old = 0, x, *in, *xdata, *out, mode = 0;
- uint32 i, ulen;
+void PictureMgr::draw_Line(int16 x1, int16 y1, int16 x2, int16 y2) {
+ x1 = CLIP<int16>(x1, 0, _width - 1);
+ x2 = CLIP<int16>(x2, 0, _width - 1);
+ y1 = CLIP<int16>(y1, 0, _height - 1);
+ y2 = CLIP<int16>(y2, 0, _height - 1);
+
+ int i, x, y, deltaX, deltaY, stepX, stepY, errorX, errorY, detdelta;
- xdata = (uint8 *)malloc(len + len / 2);
+ // Vertical line
+
+ if (x1 == x2) {
+ if (y1 > y2) {
+ SWAP(y1, y2);
+ }
- out = xdata;
- in = src;
+ for (; y1 <= y2; y1++)
+ putVirtPixel(x1, y1);
- for (i = ulen = 0; i < len; i++, ulen++) {
- d = *in++;
+ return;
+ }
- *out++ = x = mode ? ((d & 0xF0) >> 4) + ((old & 0x0F) << 4) : d;
+ // Horizontal line
- if (x == 0xFF) {
- ulen++;
- break;
+ if (y1 == y2) {
+ if (x1 > x2) {
+ SWAP(x1, x2);
+ }
+ for (; x1 <= x2; x1++)
+ putVirtPixel(x1, y1);
+ return;
+ }
+
+ y = y1;
+ x = x1;
+
+ stepY = 1;
+ deltaY = y2 - y1;
+ if (deltaY < 0) {
+ stepY = -1;
+ deltaY = -deltaY;
+ }
+
+ stepX = 1;
+ deltaX = x2 - x1;
+ if (deltaX < 0) {
+ stepX = -1;
+ deltaX = -deltaX;
+ }
+
+ if (deltaY > deltaX) {
+ i = deltaY;
+ detdelta = deltaY;
+ errorX = deltaY / 2;
+ errorY = 0;
+ } else {
+ i = deltaX;
+ detdelta = deltaX;
+ errorX = 0;
+ errorY = deltaX / 2;
+ }
+
+ putVirtPixel(x, y);
+
+ do {
+ errorY += deltaY;
+ if (errorY >= detdelta) {
+ errorY -= detdelta;
+ y += stepY;
}
- if (x == 0xf0 || x == 0xf2) {
- if (mode) {
- *out++ = d & 0x0F;
- ulen++;
+ errorX += deltaX;
+ if (errorX >= detdelta) {
+ errorX -= detdelta;
+ x += stepX;
+ }
+
+ putVirtPixel(x, y);
+ i--;
+ } while (i > 0);
+}
+
+/**
+ * Draw a relative AGI line.
+ * Draws short lines relative to last position. (drawing action 0xF7)
+ */
+void PictureMgr::draw_LineShort() {
+ int x1, y1, disp, dx, dy;
+
+ if ((x1 = getNextByte()) >= _minCommand ||
+ (y1 = getNextByte()) >= _minCommand) {
+ _dataOffset--;
+ return;
+ }
+
+ putVirtPixel(x1, y1);
+
+ for (;;) {
+ if ((disp = getNextByte()) >= _minCommand)
+ break;
+
+ dx = ((disp & 0xf0) >> 4) & 0x0f;
+ dy = (disp & 0x0f);
+
+ if (dx & 0x08)
+ dx = -(dx & 0x07);
+ if (dy & 0x08)
+ dy = -(dy & 0x07);
+
+ draw_Line(x1, y1, x1 + dx, y1 + dy);
+ x1 += dx;
+ y1 += dy;
+ }
+ _dataOffset--;
+}
+
+/**************************************************************************
+** absoluteLine
+**
+** Draws long lines to actual locations (cf. relative) (drawing action 0xF6)
+**************************************************************************/
+void PictureMgr::draw_LineAbsolute() {
+ int16 x1, y1, x2, y2;
+
+ if ((x1 = getNextByte()) >= _minCommand ||
+ (y1 = getNextByte()) >= _minCommand) {
+ _dataOffset--;
+ return;
+ }
+
+ putVirtPixel(x1, y1);
+
+ for (;;) {
+ if ((x2 = getNextByte()) >= _minCommand)
+ break;
+
+ if ((y2 = getNextByte()) >= _minCommand)
+ break;
+
+ draw_Line(x1, y1, x2, y2);
+ x1 = x2;
+ y1 = y2;
+ }
+ _dataOffset--;
+}
+
+// flood fill
+void PictureMgr::draw_Fill() {
+ int16 x1, y1;
+
+ while ((x1 = getNextByte()) < _minCommand && (y1 = getNextByte()) < _minCommand)
+ draw_Fill(x1, y1);
+
+ _dataOffset--;
+}
+
+void PictureMgr::draw_Fill(int16 x, int16 y) {
+ if (!_scrOn && !_priOn)
+ return;
+
+ // Push initial pixel on the stack
+ Common::Stack<Common::Point> stack;
+ stack.push(Common::Point(x,y));
+
+ // Exit if stack is empty
+ while (!stack.empty()) {
+ Common::Point p = stack.pop();
+ unsigned int c;
+ bool newspanUp, newspanDown;
+
+ if (!draw_FillCheck(p.x, p.y))
+ continue;
+
+ // Scan for left border
+ for (c = p.x - 1; draw_FillCheck(c, p.y); c--)
+ ;
+
+ newspanUp = newspanDown = true;
+ for (c++; draw_FillCheck(c, p.y); c++) {
+ putVirtPixel(c, p.y);
+ if (draw_FillCheck(c, p.y - 1)) {
+ if (newspanUp) {
+ stack.push(Common::Point(c,p.y-1));
+ newspanUp = false;
+ }
} else {
- d = *in++;
- *out++ = (d & 0xF0) >> 4;
- i++, ulen++;
+ newspanUp = true;
}
- mode = !mode;
+ if (draw_FillCheck(c, p.y + 1)) {
+ if (newspanDown) {
+ stack.push(Common::Point(c,p.y+1));
+ newspanDown = false;
+ }
+ } else {
+ newspanDown = true;
+ }
}
-
- old = d;
}
+}
- free(src);
- xdata = (uint8 *)realloc(xdata, ulen);
+int PictureMgr::draw_FillCheck(int16 x, int16 y) {
+ byte screenColor;
+ byte screenPriority;
- return xdata;
+ if (x < 0 || x >= _width || y < 0 || y >= _height)
+ return false;
+
+ x += _xOffset;
+ y += _yOffset;
+
+ screenColor = _gfx->getColor(x, y);
+ screenPriority = _gfx->getPriority(x, y);
+
+ if (_flags & kPicFTrollMode)
+ return ((screenColor != 11) && (screenColor != _scrColor));
+
+ if (!_priOn && _scrOn && _scrColor != 15)
+ return (screenColor == 15);
+
+ if (_priOn && !_scrOn && _priColor != 4)
+ return screenPriority == 4;
+
+ return (_scrOn && screenColor == 15 && _scrColor != 15);
}
/**
@@ -804,8 +906,8 @@ uint8 *PictureMgr::convertV3Pic(uint8 *src, uint32 len) {
* @param clear clear AGI screen before drawing
* @param agi256 load an AGI256 picture resource
*/
-int PictureMgr::decodePicture(int n, int clr, bool agi256, int pic_width, int pic_height) {
- debugC(8, kDebugLevelResources, "(%d)", n);
+int PictureMgr::decodePicture(int16 resourceNr, bool clear, bool agi256, int16 pic_width, int16 pic_height) {
+ debugC(8, kDebugLevelResources, "(%d)", resourceNr);
_patCode = 0;
_patNum = 0;
@@ -813,32 +915,28 @@ int PictureMgr::decodePicture(int n, int clr, bool agi256, int pic_width, int pi
_scrColor = 0xF;
_priColor = 0x4;
- _data = _vm->_game.pictures[n].rdata;
- _flen = _vm->_game.dirPic[n].len;
- _foffs = 0;
+ _resourceNr = resourceNr;
+ _data = _vm->_game.pictures[resourceNr].rdata;
+ _dataSize = _vm->_game.dirPic[resourceNr].len;
+ _dataOffset = 0;
+ _dataOffsetNibble = false;
_width = pic_width;
_height = pic_height;
- if (clr && !agi256) // 256 color pictures should always fill the whole screen, so no clearing for them.
- memset(_vm->_game.sbuf16c, 0x4f, _width * _height); // Clear 16 color AGI screen (Priority 4, color white).
+ if (clear && !agi256) { // 256 color pictures should always fill the whole screen, so no clearing for them.
+ _gfx->clear(15, 4); // Clear 16 color AGI screen (Priority 4, color white).
+ }
if (!agi256) {
drawPicture(); // Draw 16 color picture.
} else {
- const uint32 maxFlen = _width * _height;
- memcpy(_vm->_game.sbuf256c, _data, MIN(_flen, maxFlen)); // Draw 256 color picture.
-
- if (_flen < maxFlen) {
- warning("Undersized AGI256 picture resource %d, using it anyway. Filling rest with white.", n);
- memset(_vm->_game.sbuf256c + _flen, 0x0f, maxFlen - _flen); // Fill missing area with white.
- } else if (_flen > maxFlen)
- warning("Oversized AGI256 picture resource %d, decoding only %ux%u part of it", n, _width, _height);
+ drawPictureAGI256();
}
- if (clr)
+ if (clear)
_vm->clearImageStack();
- _vm->recordImageStackCall(ADD_PIC, n, clr, agi256, 0, 0, 0, 0);
+ _vm->recordImageStackCall(ADD_PIC, resourceNr, clear, agi256, 0, 0, 0, 0);
return errOK;
}
@@ -860,14 +958,15 @@ int PictureMgr::decodePicture(byte* data, uint32 length, int clr, int pic_width,
_priColor = 0x4;
_data = data;
- _flen = length;
- _foffs = 0;
+ _dataSize = length;
+ _dataOffset = 0;
+ _dataOffsetNibble = false;
_width = pic_width;
_height = pic_height;
if (clr) // 256 color pictures should always fill the whole screen, so no clearing for them.
- memset(_vm->_game.sbuf16c, 0x4f, _width * _height); // Clear 16 color AGI screen (Priority 4, color white).
+ clear();
drawPicture(); // Draw 16 color picture.
@@ -891,29 +990,66 @@ int PictureMgr::unloadPicture(int n) {
}
void PictureMgr::clear() {
- memset(_vm->_game.sbuf16c, 0x4f, _width * _height);
+ _gfx->clear(15, 4); // Clear 16 color AGI screen (Priority 4, color white).
+}
+
+void PictureMgr::showPic() {
+ debugC(8, kDebugLevelMain, "Show picture!");
+
+ _gfx->render_Block(0, 167, SCRIPT_WIDTH, SCRIPT_HEIGHT);
}
/**
* Show AGI picture.
* This function copies a ``hidden'' AGI picture to the output device.
*/
-void PictureMgr::showPic(int x, int y, int pic_width, int pic_height) {
- int i, y1;
- int offset;
+void PictureMgr::showPic(int16 x, int16 y, int16 pic_width, int16 pic_height) {
_width = pic_width;
_height = pic_height;
debugC(8, kDebugLevelMain, "Show picture!");
- i = 0;
- offset = _vm->_game.lineMinPrint * CHAR_LINES;
- for (y1 = y; y1 < y + _height; y1++) {
- _gfx->putPixelsA(x, y1 + offset, _width, &_vm->_game.sbuf16c[i]);
- i += _width;
+ // render block requires lower left coordinate!
+ _gfx->render_Block(x, pic_height + y - 1, pic_width, pic_height);
+}
+
+void PictureMgr::showPicWithTransition() {
+ _width = SCRIPT_WIDTH;
+ _height = SCRIPT_HEIGHT;
+
+ debugC(8, kDebugLevelMain, "Show picture!");
+
+ if (!_vm->_game.automaticRestoreGame) {
+ // only do transitions when we are not restoring a saved game
+
+ if (!_vm->_game.gfxMode) {
+ // if we are not yet in graphics mode, set graphics mode palette now
+ // TODO: maybe change text mode to use different colors for drawing
+ // so that we don't have to change palettes at all
+ _gfx->setPalette(true);
+ }
+
+ switch (_vm->_renderMode) {
+ case RENDERMODE_AMIGA:
+ case RENDERMODE_APPLE_II_GS:
+ // Platform Amiga/Apple II GS -> render and do Amiga transition
+ _gfx->render_Block(0, 167, SCRIPT_WIDTH, SCRIPT_HEIGHT, false);
+ _gfx->transition_Amiga();
+ return;
+ break;
+ case RENDERMODE_ATARI_ST:
+ // Platform Atari ST used a different transition, looks "high-res" (full 320x168)
+ _gfx->render_Block(0, 167, SCRIPT_WIDTH, SCRIPT_HEIGHT, false);
+ _gfx->transition_AtariSt();
+ return;
+ default:
+ // Platform PC -> render directly
+ // Macintosh AGI also doesn't seem to have any transitions
+ break;
+ }
}
- _gfx->flushScreen();
+ _gfx->render_Block(0, 167, SCRIPT_WIDTH, SCRIPT_HEIGHT);
}
// preagi needed functions (for plotPattern)
@@ -933,8 +1069,9 @@ void PictureMgr::setPictureVersion(AgiPictureVersion version) {
void PictureMgr::setPictureData(uint8 *data, int len) {
_data = data;
- _flen = len;
- _foffs = 0;
+ _dataSize = len;
+ _dataOffset = 0;
+ _dataOffsetNibble = false;
_flags = 0;
}
diff --git a/engines/agi/picture.h b/engines/agi/picture.h
index 9ff1e742f4..b1a272bedb 100644
--- a/engines/agi/picture.h
+++ b/engines/agi/picture.h
@@ -41,7 +41,8 @@ enum AgiPictureVersion {
AGIPIC_C64,
AGIPIC_V1,
AGIPIC_V15,
- AGIPIC_V2
+ AGIPIC_V2,
+ AGIPIC_256
};
enum AgiPictureFlags {
@@ -60,31 +61,49 @@ class PictureMgr {
AgiBase *_vm;
GfxMgr *_gfx;
-private:
+public:
+ PictureMgr(AgiBase *agi, GfxMgr *gfx);
- void drawLine(int x1, int y1, int x2, int y2);
- void dynamicDrawLine();
- void absoluteDrawLine();
- int isOkFillHere(int x, int y);
- void agiFill(unsigned int x, unsigned int y);
- void xCorner(bool skipOtherCoords = false);
+private:
+ void draw_xCorner(bool skipOtherCoords = false);
void yCorner(bool skipOtherCoords = false);
- void fill();
int plotPatternPoint(int x, int y, int bitpos);
void plotBrush();
- uint8 nextByte() { return _data[_foffs++]; }
+ byte getNextByte();
+ byte getNextNibble();
public:
- PictureMgr(AgiBase *agi, GfxMgr *gfx);
-
void putVirtPixel(int x, int y);
- int decodePicture(int n, int clear, bool agi256 = false, int pic_width = _DEFAULT_WIDTH, int pic_height = _DEFAULT_HEIGHT);
+ int decodePicture(int16 resourceNr, bool clear, bool agi256 = false, int16 pic_width = _DEFAULT_WIDTH, int16 pic_height = _DEFAULT_HEIGHT);
int decodePicture(byte* data, uint32 length, int clear, int pic_width = _DEFAULT_WIDTH, int pic_height = _DEFAULT_HEIGHT);
int unloadPicture(int);
void drawPicture();
- void showPic(int x = 0, int y = 0, int pic_width = _DEFAULT_WIDTH, int pic_height = _DEFAULT_HEIGHT);
+private:
+ void drawPictureC64();
+ void drawPictureV1();
+ void drawPictureV15();
+ void drawPictureV2();
+ void drawPictureAGI256();
+
+ void draw_SetColor();
+ void draw_SetPriority();
+ void draw_SetNibbleColor();
+ void draw_SetNibblePriority();
+
+ void draw_Line(int16 x1, int16 y1, int16 x2, int16 y2);
+ void draw_LineShort();
+ void draw_LineAbsolute();
+
+ int draw_FillCheck(int16 x, int16 y);
+ void draw_Fill(int16 x, int16 y);
+ void draw_Fill();
+
+public:
+ void showPic(); // <-- for regular AGI games
+ void showPic(int16 x, int16 y, int16 pic_width, int16 pic_height); // <-- for preAGI games
+ void showPicWithTransition();
uint8 *convertV3Pic(uint8 *src, uint32 len);
void plotPattern(int x, int y); // public because it's used directly by preagi
@@ -108,19 +127,12 @@ public:
_height = h;
}
- void putPixel(int x, int y, uint8 color) {
- _scrColor = color;
- _priOn = false;
- _scrOn = true;
- putVirtPixel(x, y);
- }
-
- bool isPictureLoaded() { return _data != NULL; }
-
private:
+ int16 _resourceNr;
uint8 *_data;
- uint32 _flen;
- uint32 _foffs;
+ uint32 _dataSize;
+ uint32 _dataOffset;
+ bool _dataOffsetNibble;
uint8 _patCode;
uint8 _patNum;
@@ -132,8 +144,8 @@ private:
uint8 _minCommand;
AgiPictureVersion _pictureVersion;
- int _width, _height;
- int _xOffset, _yOffset;
+ int16 _width, _height;
+ int16 _xOffset, _yOffset;
int _flags;
int _currentStep;
diff --git a/engines/agi/preagi.cpp b/engines/agi/preagi.cpp
index c368c7b195..c0a53e7608 100644
--- a/engines/agi/preagi.cpp
+++ b/engines/agi/preagi.cpp
@@ -29,6 +29,7 @@
#include "agi/preagi.h"
#include "agi/graphics.h"
#include "agi/keyboard.h"
+#include "agi/text.h"
namespace Agi {
@@ -55,32 +56,22 @@ PreAgiEngine::PreAgiEngine(OSystem *syst, const AGIGameDescription *gameDesc) :
void PreAgiEngine::initialize() {
initRenderMode();
+ initFont();
_gfx = new GfxMgr(this);
_picture = new PictureMgr(this, _gfx);
- if (getGameID() == GID_MICKEY) {
- _fontData = fontData_Mickey;
- } else {
- _fontData = fontData_IBM;
- }
-
_gfx->initMachine();
_game.gameFlags = 0;
- _game.colorFg = 15;
- _game.colorBg = 0;
+ //_game._vm->_text->charAttrib_Set(15, 0);
_defaultColor = 0xF;
_game.name[0] = '\0';
- _game.sbufOrig = (uint8 *)calloc(_WIDTH, _HEIGHT * 2); // Allocate space for two AGI screens vertically
- _game.sbuf16c = _game.sbufOrig + SBUF16_OFFSET; // Make sbuf16c point to the 16 color (+control line & priority info) AGI screen
- _game.sbuf = _game.sbuf16c; // Make sbuf point to the 16 color (+control line & priority info) AGI screen by default
-
- _game.lineMinPrint = 0; // hardcoded
+ //_game._vm->_text->configureScreen(0); // hardcoded
_gfx->initVideo();
@@ -91,7 +82,7 @@ void PreAgiEngine::initialize() {
debugC(2, kDebugLevelMain, "Detect game");
// clear all resources and events
- for (int i = 0; i < MAX_DIRS; i++) {
+ for (int i = 0; i < MAX_DIRECTORY_ENTRIES; i++) {
memset(&_game.pictures[i], 0, sizeof(struct AgiPicture));
memset(&_game.sounds[i], 0, sizeof(class AgiSound *)); // _game.sounds contains pointers now
memset(&_game.dirPic[i], 0, sizeof(struct AgiDir));
@@ -113,11 +104,11 @@ void PreAgiEngine::clearScreen(int attr, bool overrideDefault) {
if (overrideDefault)
_defaultColor = attr;
- _gfx->clearScreen((attr & 0xF0) / 0x10);
+ _gfx->clearDisplay((attr & 0xF0) / 0x10);
}
void PreAgiEngine::clearGfxScreen(int attr) {
- _gfx->drawRectangle(0, 0, GFX_WIDTH - 1, IDI_MAX_ROW_PIC * 8 -1, (attr & 0xF0) / 0x10);
+ _gfx->drawDisplayRect(0, 0, GFX_WIDTH - 1, IDI_MAX_ROW_PIC * 8 -1, (attr & 0xF0) / 0x10);
}
// String functions
@@ -143,7 +134,7 @@ void PreAgiEngine::drawStr(int row, int col, int attr, const char *buffer) {
break;
default:
- _gfx->putTextCharacter(1, col * 8 , row * 8, static_cast<char>(code), attr & 0x0f, (attr & 0xf0) / 0x10, false, _fontData);
+ _gfx->drawCharacter(row, col, code, attr & 0x0f, attr >> 4, false);
if (++col == 320 / 8) {
col = 0;
@@ -176,7 +167,7 @@ void PreAgiEngine::clearRow(int row) {
void PreAgiEngine::printStr(const char* szMsg) {
clearTextArea();
drawStr(21, 0, IDA_DEFAULT, szMsg);
- _gfx->doUpdate();
+ g_system->updateScreen();
}
void PreAgiEngine::XOR80(char *buffer) {
@@ -274,7 +265,7 @@ void PreAgiEngine::waitForTimer(int msec_delay) {
uint32 start_time = _system->getMillis();
while (_system->getMillis() < start_time + msec_delay) {
- _gfx->doUpdate();
+ g_system->updateScreen();
_system->delayMillis(10);
}
}
diff --git a/engines/agi/preagi_mickey.cpp b/engines/agi/preagi_mickey.cpp
index 883107a957..02c48b090b 100644
--- a/engines/agi/preagi_mickey.cpp
+++ b/engines/agi/preagi_mickey.cpp
@@ -149,7 +149,7 @@ void MickeyEngine::printStr(char *buffer) {
}
// Show the string on screen
- _gfx->doUpdate();
+ _gfx->updateScreen();
}
void MickeyEngine::printLine(const char *buffer) {
@@ -158,7 +158,7 @@ void MickeyEngine::printLine(const char *buffer) {
drawStr(22, 18 - strlen(buffer) / 2, IDA_DEFAULT, buffer);
// Show the string on screen
- _gfx->doUpdate();
+ _gfx->updateScreen();
waitAnyKey(true);
}
@@ -281,7 +281,7 @@ void MickeyEngine::drawMenu(MSA_MENU menu, int sel0, int sel1) {
}
// Menu created, show it on screen
- _gfx->doUpdate();
+ _gfx->updateScreen();
}
void MickeyEngine::getMouseMenuSelRow(MSA_MENU menu, int *sel0, int *sel1, int iRow, int x, int y) {
@@ -373,18 +373,19 @@ bool MickeyEngine::getMenuSelRow(MSA_MENU menu, int *sel0, int *sel1, int iRow)
// Change cursor
if (northIndex >= 0 && (event.mouse.x >= 20 && event.mouse.x <= (IDI_MSA_PIC_WIDTH + 10) * 2) &&
(event.mouse.y >= 0 && event.mouse.y <= 10)) {
- _gfx->setCursorPalette(true);
+ //_gfx->setCursorPalette(true);
+ // TODO:?????
} else if (southIndex >= 0 && (event.mouse.x >= 20 && event.mouse.x <= (IDI_MSA_PIC_WIDTH + 10) * 2) &&
(event.mouse.y >= IDI_MSA_PIC_HEIGHT - 10 && event.mouse.y <= IDI_MSA_PIC_HEIGHT)) {
- _gfx->setCursorPalette(true);
+ //_gfx->setCursorPalette(true);
} else if (westIndex >= 0 && (event.mouse.y >= 0 && event.mouse.y <= IDI_MSA_PIC_HEIGHT) &&
(event.mouse.x >= 20 && event.mouse.x <= 30)) {
- _gfx->setCursorPalette(true);
+ //_gfx->setCursorPalette(true);
} else if (eastIndex >= 0 && (event.mouse.y >= 0 && event.mouse.y <= IDI_MSA_PIC_HEIGHT) &&
(event.mouse.x >= IDI_MSA_PIC_WIDTH * 2 && event.mouse.x <= (IDI_MSA_PIC_WIDTH + 10) * 2)) {
- _gfx->setCursorPalette(true);
+ //_gfx->setCursorPalette(true);
} else {
- _gfx->setCursorPalette(false);
+ //_gfx->setCursorPalette(false);
}
}
break;
@@ -397,7 +398,8 @@ bool MickeyEngine::getMenuSelRow(MSA_MENU menu, int *sel0, int *sel1, int iRow)
drawMenu(menu, *sel0, *sel1);
- _gfx->setCursorPalette(false);
+ //_gfx->setCursorPalette(false);
+ // TODO???
_clickToMove = true;
} else if (southIndex >= 0 && (event.mouse.x >= 20 && event.mouse.x <= (IDI_MSA_PIC_WIDTH + 10) * 2) &&
(event.mouse.y >= IDI_MSA_PIC_HEIGHT - 10 && event.mouse.y <= IDI_MSA_PIC_HEIGHT)) {
@@ -406,7 +408,8 @@ bool MickeyEngine::getMenuSelRow(MSA_MENU menu, int *sel0, int *sel1, int iRow)
drawMenu(menu, *sel0, *sel1);
- _gfx->setCursorPalette(false);
+ //_gfx->setCursorPalette(false);
+ // TODO???
_clickToMove = true;
} else if (westIndex >= 0 && (event.mouse.y >= 0 && event.mouse.y <= IDI_MSA_PIC_HEIGHT) &&
(event.mouse.x >= 20 && event.mouse.x <= 30)) {
@@ -415,7 +418,8 @@ bool MickeyEngine::getMenuSelRow(MSA_MENU menu, int *sel0, int *sel1, int iRow)
drawMenu(menu, *sel0, *sel1);
- _gfx->setCursorPalette(false);
+ //_gfx->setCursorPalette(false);
+ // TODO???
_clickToMove = true;
} else if (eastIndex >= 0 && (event.mouse.y >= 0 && event.mouse.y <= IDI_MSA_PIC_HEIGHT) &&
(event.mouse.x >= IDI_MSA_PIC_WIDTH * 2 && event.mouse.x <= (IDI_MSA_PIC_WIDTH + 10) * 2)) {
@@ -424,10 +428,12 @@ bool MickeyEngine::getMenuSelRow(MSA_MENU menu, int *sel0, int *sel1, int iRow)
drawMenu(menu, *sel0, *sel1);
- _gfx->setCursorPalette(false);
+ //_gfx->setCursorPalette(false);
+ // TODO???
_clickToMove = true;
} else {
- _gfx->setCursorPalette(false);
+ //_gfx->setCursorPalette(false);
+ // TODO???
}
return true;
case Common::EVENT_RBUTTONUP:
@@ -486,7 +492,7 @@ bool MickeyEngine::getMenuSelRow(MSA_MENU menu, int *sel0, int *sel1, int iRow)
return false;
case Common::KEYCODE_s:
- flipflag(fSoundOn);
+ flipflag(VM_FLAG_SOUND_ON);
break;
case Common::KEYCODE_c:
inventory();
@@ -666,7 +672,7 @@ void MickeyEngine::playNote(MSA_SND_NOTE note) {
}
void MickeyEngine::playSound(ENUM_MSA_SOUND iSound) {
- if (!getflag(fSoundOn))
+ if (!getflag(VM_FLAG_SOUND_ON))
return;
Common::Event event;
@@ -755,7 +761,9 @@ void MickeyEngine::drawPic(int iPic) {
file.close();
// Note that decodePicture clears the screen
+ _picture->setOffset(10, 0);
_picture->decodePicture(buffer, size, true, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
+ _picture->setOffset(0, 0);
_picture->showPic(10, 0, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
}
@@ -892,75 +900,65 @@ void MickeyEngine::drawRoom() {
drawRoomAnimation();
}
-#if 0
-const uint8 colorBCG[16][2] = {
- { 0x00, 0x00 }, // 0 (black, black)
- { 0, 0 },
- { 0x00, 0x0D }, // 2 (black, purple)
- { 0x00, 0xFF }, // 3 (black, white)
- { 0, 0 },
- { 0, 0 },
- { 0, 0 },
- { 0, 0 },
- { 0x0D, 0x00 }, // 8 (purple, black)
- { 0, 0 },
- { 0x0D, 0x0D }, // A (purple, purple)
- { 0, 0 },
- { 0xFF, 0x00 }, // C (white, black)
- { 0, 0 },
- { 0, 0 },
- { 0xFF, 0xFF } // F (white, white)
+// Straight mapping, CGA colors to CGA
+const byte BCGColorMappingCGAToCGA[4] = {
+ 0, 1, 2, 3
+};
+
+// Mapping table to map CGA colors to EGA
+const byte BCGColorMappingCGAToEGA[4] = {
+ 0, 11, 13, 15
};
-#endif
void MickeyEngine::drawLogo() {
- // TODO: clean this up and make it work properly, the logo is drawn way off to the right
-#if 0
- char szFile[256] = {0};
- uint8 *buffer = new uint8[16384];
- const int w = 150;
- const int h = 80;
- const int xoffset = 30; // FIXME: remove this
- uint8 bitmap[w][h];
- uint8 color, color2, color3, color4;
-
- // read in logos.bcg
- sprintf(szFile, IDS_MSA_PATH_LOGO);
+ const int width = 80;
+ const int height = 85 * 2;
+ byte color1, color2, color3, color4;
+ byte *fileBuffer = nullptr;
+ uint32 fileBufferSize = 0;
+ byte *dataBuffer;
+ byte curByte;
+ const byte *BCGColorMapping = BCGColorMappingCGAToEGA;
+
+ // disable color mapping in case we are in CGA mode
+ if (_renderMode == RENDERMODE_CGA)
+ BCGColorMapping = BCGColorMappingCGAToCGA;
+
+ // read logos.bcg
Common::File infile;
- if (!infile.open(szFile))
+ if (!infile.open(IDS_MSA_PATH_LOGO))
return;
- infile.read(buffer, infile.size());
+ fileBufferSize = infile.size();
+ fileBuffer = new byte[fileBufferSize];
+ infile.read(fileBuffer, fileBufferSize);
infile.close();
- // draw logo bitmap
- memcpy(bitmap, buffer, sizeof(bitmap));
-
- _picture->setDimensions(w, h);
+ if (fileBufferSize < (width * height / 4))
+ error("logos.bcg: unexpected end of file");
// Show BCG picture
- for (int y = 0; y < h; y++) {
- for (int x = xoffset; x < w; x++) {
- color = colorBCG[(bitmap[y][x] & 0xf0) / 0x10][0]; // background
- color2 = colorBCG[(bitmap[y][x] & 0xf0) / 0x10][1]; // background
- color3 = colorBCG[ bitmap[y][x] & 0x0f][0]; // foreground
- color4 = colorBCG[ bitmap[y][x] & 0x0f][1]; // foreground
-
- _picture->putPixel(x * 4 - xoffset, y, color);
- _picture->putPixel(x * 4 + 1 - xoffset, y, color2);
- _picture->putPixel(x * 4 + 2 - xoffset, y, color3);
- _picture->putPixel(x * 4 + 3 - xoffset, y, color4);
- _picture->putPixel(x * 4 - xoffset, y + 1, color);
- _picture->putPixel(x * 4 + 1 - xoffset, y + 1, color2);
- _picture->putPixel(x * 4 + 2 - xoffset, y + 1, color3);
- _picture->putPixel(x * 4 + 3 - xoffset, y + 1, color4);
+ // It's basically uncompressed CGA 4-color data (4 pixels per byte)
+ dataBuffer = fileBuffer;
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ curByte = *dataBuffer++;
+
+ color1 = BCGColorMapping[(curByte >> 6) & 0x03];
+ color2 = BCGColorMapping[(curByte >> 4) & 0x03];
+ color3 = BCGColorMapping[(curByte >> 2) & 0x03];
+ color4 = BCGColorMapping[(curByte >> 0) & 0x03];
+
+ _gfx->putPixelOnDisplay(x * 4 + 0, y, color1);
+ _gfx->putPixelOnDisplay(x * 4 + 1, y, color2);
+ _gfx->putPixelOnDisplay(x * 4 + 2, y, color3);
+ _gfx->putPixelOnDisplay(x * 4 + 3, y, color4);
}
}
- _picture->showPic(10, 10, w, h);
+ _gfx->copyDisplayRectToScreen(0, 0, DISPLAY_WIDTH, height);
- delete[] buffer;
-#endif
+ delete[] fileBuffer;
}
void MickeyEngine::animate() {
@@ -1221,8 +1219,8 @@ void MickeyEngine::printStory() {
waitAnyKey();
//Set back to black
- _gfx->clearScreen(0);
- _gfx->doUpdate();
+ _gfx->clearDisplay(0);
+ _gfx->updateScreen();
drawRoom();
@@ -1399,8 +1397,8 @@ void MickeyEngine::inventory() {
void MickeyEngine::intro() {
// Draw Sierra logo
- //drawLogo(); // Original does not even show this, so we skip it too
- //waitAnyKey(); // Not in the original, but needed so that the logo is visible
+ drawLogo(); // Original does not even show this, so we skip it too
+ waitAnyKey(); // Not in the original, but needed so that the logo is visible
// draw title picture
_gameStateMickey.iRoom = IDI_MSA_PIC_TITLE;
@@ -1448,14 +1446,14 @@ void MickeyEngine::intro() {
playSound(IDI_MSA_SND_PRESS_BLUE);
//Set screen to white
- _gfx->clearScreen(15);
- _gfx->doUpdate();
+ _gfx->clearDisplay(15);
+ _gfx->updateScreen();
_system->delayMillis(IDI_MSA_ANIM_DELAY);
//Set back to black
- _gfx->clearScreen(0);
- _gfx->doUpdate();
+ _gfx->clearDisplay(0);
+ _gfx->updateScreen();
drawRoom();
printDesc(_gameStateMickey.iRoom);
@@ -2201,7 +2199,7 @@ void MickeyEngine::waitAnyKey(bool anim) {
Common::Event event;
if (!anim)
- _gfx->doUpdate();
+ _gfx->updateScreen();
while (!shouldQuit()) {
while (_system->getEventManager()->pollEvent(event)) {
@@ -2219,10 +2217,9 @@ void MickeyEngine::waitAnyKey(bool anim) {
if (anim) {
animate();
- _gfx->doUpdate();
}
- _system->updateScreen();
+ _gfx->updateScreen();
_system->delayMillis(10);
}
}
@@ -2300,7 +2297,7 @@ void MickeyEngine::init() {
#endif
- setflag(fSoundOn, true); // enable sound
+ setflag(VM_FLAG_SOUND_ON, true); // enable sound
}
Common::Error MickeyEngine::go() {
diff --git a/engines/agi/preagi_troll.cpp b/engines/agi/preagi_troll.cpp
index 2889407c85..6d82c62987 100644
--- a/engines/agi/preagi_troll.cpp
+++ b/engines/agi/preagi_troll.cpp
@@ -41,7 +41,7 @@ TrollEngine::~TrollEngine() {
void TrollEngine::pressAnyKey(int col) {
drawStr(24, col, kColorDefault, IDS_TRO_PRESSANYKEY);
- _gfx->doUpdate();
+ g_system->updateScreen();
getSelection(kSelAnyKey);
}
@@ -49,7 +49,7 @@ void TrollEngine::drawMenu(const char *szMenu, int iSel) {
clearTextArea();
drawStr(21, 0, kColorDefault, szMenu);
drawStr(22 + iSel, 0, kColorDefault, " *");
- _gfx->doUpdate();
+ g_system->updateScreen();
}
bool TrollEngine::getMenuSel(const char *szMenu, int *iSel, int nSel) {
@@ -155,8 +155,8 @@ void TrollEngine::drawPic(int iPic, bool f3IsCont, bool clr, bool troll) {
_picture->drawPicture();
- _picture->showPic();
- _gfx->doUpdate();
+ _picture->showPic(); // TODO: *HAVE* to add coordinates + height/width!!
+ g_system->updateScreen();
}
// Game Logic
@@ -223,11 +223,11 @@ void TrollEngine::waitAnyKeyIntro() {
// fall through
case 0:
drawStr(22, 3, kColorDefault, IDS_TRO_INTRO_2);
- _gfx->doUpdate();
+ g_system->updateScreen();
break;
case 100:
drawStr(22, 3, kColorDefault, IDS_TRO_INTRO_3);
- _gfx->doUpdate();
+ g_system->updateScreen();
break;
}
@@ -262,7 +262,7 @@ void TrollEngine::credits() {
drawStr(17, 7, 12, IDS_TRO_CREDITS_5);
drawStr(19, 2, 14, IDS_TRO_CREDITS_6);
- _gfx->doUpdate();
+ g_system->updateScreen();
pressAnyKey();
}
@@ -288,11 +288,11 @@ void TrollEngine::tutorial() {
switch (iSel) {
case IDI_TRO_SEL_OPTION_1:
clearScreen(0x22, false);
- _gfx->doUpdate();
+ g_system->updateScreen();
break;
case IDI_TRO_SEL_OPTION_2:
clearScreen(0x00, false);
- _gfx->doUpdate();
+ g_system->updateScreen();
break;
case IDI_TRO_SEL_OPTION_3:
done = true;
@@ -304,7 +304,7 @@ void TrollEngine::tutorial() {
clearScreen(0x4F);
drawStr(7, 4, kColorDefault, IDS_TRO_TUTORIAL_5);
drawStr(9, 4, kColorDefault, IDS_TRO_TUTORIAL_6);
- _gfx->doUpdate();
+ g_system->updateScreen();
if (!getSelection(kSelYesNo))
break;
@@ -314,37 +314,37 @@ void TrollEngine::tutorial() {
clearScreen(0x5F);
drawStr(4, 1, kColorDefault, IDS_TRO_TUTORIAL_7);
drawStr(5, 1, kColorDefault, IDS_TRO_TUTORIAL_8);
- _gfx->doUpdate();
+ g_system->updateScreen();
pressAnyKey();
clearScreen(0x2F);
drawStr(6, 1, kColorDefault, IDS_TRO_TUTORIAL_9);
- _gfx->doUpdate();
+ g_system->updateScreen();
pressAnyKey();
clearScreen(0x19);
drawStr(7, 1, kColorDefault, IDS_TRO_TUTORIAL_10);
drawStr(8, 1, kColorDefault, IDS_TRO_TUTORIAL_11);
- _gfx->doUpdate();
+ g_system->updateScreen();
pressAnyKey();
clearScreen(0x6E);
drawStr(9, 1, kColorDefault, IDS_TRO_TUTORIAL_12);
drawStr(10, 1, kColorDefault, IDS_TRO_TUTORIAL_13);
- _gfx->doUpdate();
+ g_system->updateScreen();
pressAnyKey();
clearScreen(0x4C);
drawStr(11, 1, kColorDefault, IDS_TRO_TUTORIAL_14);
drawStr(12, 1, kColorDefault, IDS_TRO_TUTORIAL_15);
- _gfx->doUpdate();
+ g_system->updateScreen();
pressAnyKey();
clearScreen(0x5D);
drawStr(13, 1, kColorDefault, IDS_TRO_TUTORIAL_16);
drawStr(14, 1, kColorDefault, IDS_TRO_TUTORIAL_17);
drawStr(15, 1, kColorDefault, IDS_TRO_TUTORIAL_18);
- _gfx->doUpdate();
+ g_system->updateScreen();
pressAnyKey();
// show treasures
@@ -353,7 +353,7 @@ void TrollEngine::tutorial() {
for (int i = 0; i < IDI_TRO_MAX_TREASURE; i++)
drawStr(19 - i, 11, kColorDefault, _items[i].name);
- _gfx->doUpdate();
+ g_system->updateScreen();
pressAnyKey();
}
@@ -363,7 +363,7 @@ void TrollEngine::intro() {
clearScreen(0x2F);
drawStr(9, 10, kColorDefault, IDS_TRO_INTRO_0);
drawStr(14, 15, kColorDefault, IDS_TRO_INTRO_1);
- _gfx->doUpdate();
+ g_system->updateScreen();
_system->delayMillis(3200);
CursorMan.showMouse(true);
@@ -371,7 +371,7 @@ void TrollEngine::intro() {
// Draw logo
setDefaultTextColor(0x0f);
drawPic(45, false, true);
- _gfx->doUpdate();
+ g_system->updateScreen();
// wait for keypress and alternate message
waitAnyKeyIntro();
@@ -379,7 +379,7 @@ void TrollEngine::intro() {
// have you played this game before?
drawStr(22, 3, kColorDefault, IDS_TRO_INTRO_4);
drawStr(23, 6, kColorDefault, IDS_TRO_INTRO_5);
- _gfx->doUpdate();
+ g_system->updateScreen();
if (!getSelection(kSelYesNo))
tutorial();
@@ -411,7 +411,7 @@ void TrollEngine::gameOver() {
sprintf(szMoves, IDS_TRO_GAMEOVER_0, _moves);
drawStr(21, 1, kColorDefault, szMoves);
drawStr(22, 1, kColorDefault, IDS_TRO_GAMEOVER_1);
- _gfx->doUpdate();
+ g_system->updateScreen();
pressAnyKey();
}
@@ -443,7 +443,7 @@ int TrollEngine::drawRoom(char *menu) {
}
drawPic(_currentRoom, contFlag, true);
- _gfx->doUpdate();
+ g_system->updateScreen();
if (_currentRoom == 42) {
drawPic(44, false, false); // don't clear
@@ -454,7 +454,7 @@ int TrollEngine::drawRoom(char *menu) {
}
}
- _gfx->doUpdate();
+ g_system->updateScreen();
char tmp[10];
strncat(menu, (char *)_gameData + _locMessagesIdx[_currentRoom], 39);
@@ -498,7 +498,7 @@ void TrollEngine::pickupTreasure(int treasureId) {
if (_currentRoom != 24) {
clearTextArea();
drawPic(_currentRoom, false, true);
- _gfx->doUpdate();
+ g_system->updateScreen();
}
printUserMessage(treasureId + 16);
diff --git a/engines/agi/preagi_winnie.cpp b/engines/agi/preagi_winnie.cpp
index a91ad24fc6..596f417140 100644
--- a/engines/agi/preagi_winnie.cpp
+++ b/engines/agi/preagi_winnie.cpp
@@ -192,16 +192,16 @@ void WinnieEngine::randomize() {
void WinnieEngine::intro() {
drawPic(IDS_WTP_FILE_LOGO);
printStr(IDS_WTP_INTRO_0);
- _gfx->doUpdate();
+ g_system->updateScreen();
_system->delayMillis(0x640);
if (getPlatform() == Common::kPlatformAmiga)
- _gfx->clearScreen(0);
+ _gfx->clearDisplay(0);
drawPic(IDS_WTP_FILE_TITLE);
printStr(IDS_WTP_INTRO_1);
- _gfx->doUpdate();
+ g_system->updateScreen();
_system->delayMillis(0x640);
if (!playSound(IDI_WTP_SND_POOH_0))
@@ -452,7 +452,7 @@ int WinnieEngine::parser(int pc, int index, uint8 *buffer) {
if (iBlock == 1)
return IDI_WTP_PAR_OK;
- _gfx->doUpdate();
+ g_system->updateScreen();
}
return IDI_WTP_PAR_OK;
@@ -477,7 +477,7 @@ void WinnieEngine::inventory() {
Common::String missing = Common::String::format(IDS_WTP_INVENTORY_1, _gameStateWinnie.nObjMiss);
drawStr(IDI_WTP_ROW_OPTION_4, IDI_WTP_COL_MENU, IDA_DEFAULT, missing.c_str());
- _gfx->doUpdate();
+ g_system->updateScreen();
getSelection(kSelAnyKey);
}
@@ -755,7 +755,7 @@ void WinnieEngine::drawMenu(char *szMenu, int iSel, int fCanSel[]) {
break;
}
drawStr(iRow, iCol - 1, IDA_DEFAULT, ">");
- _gfx->doUpdate();
+ g_system->updateScreen();
}
void WinnieEngine::incMenuSel(int *iSel, int fCanSel[]) {
@@ -821,15 +821,16 @@ void WinnieEngine::getMenuSel(char *szMenu, int *iSel, int fCanSel[]) {
// Change cursor
if (fCanSel[IDI_WTP_SEL_NORTH] && hotspotNorth.contains(event.mouse.x, event.mouse.y)) {
- _gfx->setCursorPalette(true);
+ //_gfx->setCursorPalette(true);
+ // ????
} else if (fCanSel[IDI_WTP_SEL_SOUTH] && hotspotSouth.contains(event.mouse.x, event.mouse.y)) {
- _gfx->setCursorPalette(true);
+ //_gfx->setCursorPalette(true);
} else if (fCanSel[IDI_WTP_SEL_WEST] && hotspotWest.contains(event.mouse.x, event.mouse.y)) {
- _gfx->setCursorPalette(true);
+ //_gfx->setCursorPalette(true);
} else if (fCanSel[IDI_WTP_SEL_EAST] && hotspotEast.contains(event.mouse.x, event.mouse.y)) {
- _gfx->setCursorPalette(true);
+ //_gfx->setCursorPalette(true);
} else {
- _gfx->setCursorPalette(false);
+ //_gfx->setCursorPalette(false);
}
break;
@@ -838,25 +839,30 @@ void WinnieEngine::getMenuSel(char *szMenu, int *iSel, int fCanSel[]) {
if (fCanSel[IDI_WTP_SEL_NORTH] && hotspotNorth.contains(event.mouse.x, event.mouse.y)) {
*iSel = IDI_WTP_SEL_NORTH;
makeSel(iSel, fCanSel);
- _gfx->setCursorPalette(false);
+ //_gfx->setCursorPalette(false);
+ // TODO???
return;
} else if (fCanSel[IDI_WTP_SEL_SOUTH] && hotspotSouth.contains(event.mouse.x, event.mouse.y)) {
*iSel = IDI_WTP_SEL_SOUTH;
makeSel(iSel, fCanSel);
- _gfx->setCursorPalette(false);
+ //_gfx->setCursorPalette(false);
+ // TODO???
return;
} else if (fCanSel[IDI_WTP_SEL_WEST] && hotspotWest.contains(event.mouse.x, event.mouse.y)) {
*iSel = IDI_WTP_SEL_WEST;
makeSel(iSel, fCanSel);
- _gfx->setCursorPalette(false);
+ //_gfx->setCursorPalette(false);
+ // TODO???
return;
} else if (fCanSel[IDI_WTP_SEL_EAST] && hotspotEast.contains(event.mouse.x, event.mouse.y)) {
*iSel = IDI_WTP_SEL_EAST;
makeSel(iSel, fCanSel);
- _gfx->setCursorPalette(false);
+ //_gfx->setCursorPalette(false);
+ // TODO???
return;
} else {
- _gfx->setCursorPalette(false);
+ //_gfx->setCursorPalette(false);
+ // TODO???
}
switch (*iSel) {
@@ -941,7 +947,7 @@ void WinnieEngine::getMenuSel(char *szMenu, int *iSel, int fCanSel[]) {
break;
case Common::KEYCODE_s:
if (event.kbd.flags & Common::KBD_CTRL) {
- flipflag(fSoundOn);
+ flipflag(VM_FLAG_SOUND_ON);
} else {
*iSel = IDI_WTP_SEL_SOUTH;
makeSel(iSel, fCanSel);
@@ -1016,7 +1022,7 @@ void WinnieEngine::gameLoop() {
readRoom(_room, roomdata, hdr);
drawRoomPic();
- _gfx->doUpdate();
+ g_system->updateScreen();
decodePhase = 1;
}
@@ -1102,7 +1108,7 @@ void WinnieEngine::drawRoomPic() {
int iObj = getObjInRoom(_room);
// clear gfx screen
- _gfx->clearScreen(0);
+ _gfx->clearDisplay(0);
// read room picture
readRoom(_room, buffer, roomhdr);
@@ -1175,7 +1181,8 @@ void WinnieEngine::clrMenuSel(int *iSel, int fCanSel[]) {
while (!fCanSel[*iSel]) {
*iSel += 1;
}
- _gfx->setCursorPalette(false);
+ //_gfx->setCursorPalette(false);
+ // TODO???
}
void WinnieEngine::printRoomStr(int iRoom, int iStr) {
@@ -1335,7 +1342,7 @@ void WinnieEngine::init() {
}
_sound = new SoundMgr(this, _mixer);
- setflag(fSoundOn, true); // enable sound
+ setflag(VM_FLAG_SOUND_ON, true); // enable sound
memset(&_gameStateWinnie, 0, sizeof(_gameStateWinnie));
_gameStateWinnie.fSound = 1;
diff --git a/engines/agi/saveload.cpp b/engines/agi/saveload.cpp
index 41a7a943ff..b11927c542 100644
--- a/engines/agi/saveload.cpp
+++ b/engines/agi/saveload.cpp
@@ -38,11 +38,14 @@
#include "agi/agi.h"
#include "agi/graphics.h"
+#include "agi/text.h"
#include "agi/sprite.h"
#include "agi/keyboard.h"
#include "agi/menu.h"
+#include "agi/systemui.h"
+#include "agi/words.h"
-#define SAVEGAME_VERSION 6
+#define SAVEGAME_CURRENT_VERSION 7
//
// Version 0 (Sarien): view table has 64 entries
@@ -52,19 +55,23 @@
// Version 4 (ScummVM): added thumbnails and save creation date/time
// Version 5 (ScummVM): Added game md5
// Version 6 (ScummVM): Added game played time
+// Version 7 (ScummVM): Added controller key mappings
+// required for some games for quick-loading from ScummVM main menu
+// for games, that do not set all key mappings right at the start
+// Added automatic save data (for command SetSimple)
//
namespace Agi {
static const uint32 AGIflag = MKTAG('A','G','I',':');
-int AgiEngine::saveGame(const Common::String &fileName, const Common::String &description) {
+int AgiEngine::saveGame(const Common::String &fileName, const Common::String &descriptionString) {
char gameIDstring[8] = "gameIDX";
int i;
Common::OutSaveFile *out;
int result = errOK;
- debugC(3, kDebugLevelMain | kDebugLevelSavegame, "AgiEngine::saveGame(%s, %s)", fileName.c_str(), description.c_str());
+ debugC(3, kDebugLevelMain | kDebugLevelSavegame, "AgiEngine::saveGame(%s, %s)", fileName.c_str(), descriptionString.c_str());
if (!(out = _saveFileMan->openForSaving(fileName))) {
warning("Can't create file '%s', game not saved", fileName.c_str());
return errBadFileOpen;
@@ -73,10 +80,17 @@ int AgiEngine::saveGame(const Common::String &fileName, const Common::String &de
}
out->writeUint32BE(AGIflag);
- out->write(description.c_str(), 31);
- out->writeByte(SAVEGAME_VERSION);
- debugC(5, kDebugLevelMain | kDebugLevelSavegame, "Writing save game version (%d)", SAVEGAME_VERSION);
+ // Write description of saved game, limited to SAVEDGAME_DESCRIPTION_LEN characters + terminating NUL
+ char description[SAVEDGAME_DESCRIPTION_LEN + 1];
+
+ memset(description, 0, sizeof(description));
+ strncpy(description, descriptionString.c_str(), SAVEDGAME_DESCRIPTION_LEN);
+ assert(SAVEDGAME_DESCRIPTION_LEN + 1 == 31); // safety
+ out->write(description, 31);
+
+ out->writeByte(SAVEGAME_CURRENT_VERSION);
+ debugC(5, kDebugLevelMain | kDebugLevelSavegame, "Writing save game version (%d)", SAVEGAME_CURRENT_VERSION);
// Thumbnail
Graphics::saveThumbnail(*out);
@@ -116,37 +130,50 @@ int AgiEngine::saveGame(const Common::String &fileName, const Common::String &de
out->writeByte(tmp[i]);
}
+ // Version 7+: Save automatic saving state (set.simple opcode)
+ out->writeByte(_game.automaticSave);
+ out->write(_game.automaticSaveDescription, 31);
+
for (i = 0; i < MAX_FLAGS; i++)
out->writeByte(_game.flags[i]);
for (i = 0; i < MAX_VARS; i++)
out->writeByte(_game.vars[i]);
out->writeSint16BE((int8)_game.horizon);
- out->writeSint16BE((int16)_game.lineStatus);
- out->writeSint16BE((int16)_game.lineUserInput);
- out->writeSint16BE((int16)_game.lineMinPrint);
+ out->writeSint16BE((int16)_text->statusRow_Get());
+ out->writeSint16BE((int16)_text->promptRow_Get());
+ out->writeSint16BE((int16)_text->getWindowRowMin());
out->writeSint16BE((int16)_game.inputMode);
out->writeSint16BE((int16)_game.lognum);
out->writeSint16BE((int16)_game.playerControl);
out->writeSint16BE((int16)shouldQuit());
- out->writeSint16BE((int16)_game.statusLine);
+ if (_text->statusEnabled()) {
+ out->writeSint16BE(0x7FFF);
+ } else {
+ out->writeSint16BE(0);
+ }
out->writeSint16BE((int16)_game.clockEnabled);
out->writeSint16BE((int16)_game.exitAllLogics);
out->writeSint16BE((int16)_game.pictureShown);
out->writeSint16BE((int16)_game.hasPrompt);
out->writeSint16BE((int16)_game.gameFlags);
- out->writeSint16BE(_game.inputEnabled);
+ if (_text->promptIsEnabled()) {
+ out->writeSint16BE(0x7FFF);
+ } else {
+ out->writeSint16BE(0);
+ }
- for (i = 0; i < _HEIGHT; i++)
- out->writeByte(_game.priTable[i]);
+ // TODO: save if priority table was modified
+ for (i = 0; i < SCRIPT_HEIGHT; i++)
+ out->writeByte(_gfx->priorityFromY(i));
out->writeSint16BE((int16)_game.gfxMode);
- out->writeByte(_game.cursorChar);
- out->writeSint16BE((int16)_game.colorFg);
- out->writeSint16BE((int16)_game.colorBg);
+ out->writeByte(_text->inputGetCursorChar());
+ out->writeSint16BE((int16)_text->charAttrib_GetForeground());
+ out->writeSint16BE((int16)_text->charAttrib_GetBackground());
// game.hires
// game.sbuf
@@ -157,21 +184,29 @@ int AgiEngine::saveGame(const Common::String &fileName, const Common::String &de
for (i = 0; i < (int16)_game.numObjects; i++)
out->writeSint16BE((int16)objectGetLocation(i));
+ // Version 7+: save controller key mappings
+ // required for games, that do not set all key mappings right at the start
+ // when quick restoring is used from ScummVM menu, only 1 cycle is executed
+ for (i = 0; i < MAX_CONTROLLER_KEYMAPPINGS; i++) {
+ out->writeUint16BE(_game.controllerKeyMapping[i].keycode);
+ out->writeByte(_game.controllerKeyMapping[i].controllerSlot);
+ }
+
// game.ev_keyp
for (i = 0; i < MAX_STRINGS; i++)
out->write(_game.strings[i], MAX_STRINGLEN);
// record info about loaded resources
- for (i = 0; i < MAX_DIRS; i++) {
+ for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) {
out->writeByte(_game.dirLogic[i].flags);
out->writeSint16BE((int16)_game.logics[i].sIP);
out->writeSint16BE((int16)_game.logics[i].cIP);
}
- for (i = 0; i < MAX_DIRS; i++)
+ for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++)
out->writeByte(_game.dirPic[i].flags);
- for (i = 0; i < MAX_DIRS; i++)
+ for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++)
out->writeByte(_game.dirView[i].flags);
- for (i = 0; i < MAX_DIRS; i++)
+ for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++)
out->writeByte(_game.dirSound[i].flags);
// game.pictures
@@ -179,51 +214,77 @@ int AgiEngine::saveGame(const Common::String &fileName, const Common::String &de
// game.views
// game.sounds
- for (i = 0; i < MAX_VIEWTABLE; i++) {
- VtEntry *v = &_game.viewTable[i];
+ for (i = 0; i < SCREENOBJECTS_MAX; i++) {
+ ScreenObjEntry *screenObj = &_game.screenObjTable[i];
- out->writeByte(v->stepTime);
- out->writeByte(v->stepTimeCount);
- out->writeByte(v->entry);
- out->writeSint16BE(v->xPos);
- out->writeSint16BE(v->yPos);
- out->writeByte(v->currentView);
+ out->writeByte(screenObj->stepTime);
+ out->writeByte(screenObj->stepTimeCount);
+ out->writeByte(screenObj->objectNr);
+ out->writeSint16BE(screenObj->xPos);
+ out->writeSint16BE(screenObj->yPos);
+ out->writeByte(screenObj->currentViewNr);
// v->view_data
- out->writeByte(v->currentLoop);
- out->writeByte(v->numLoops);
+ out->writeByte(screenObj->currentLoopNr);
+ out->writeByte(screenObj->loopCount);
// v->loop_data
- out->writeByte(v->currentCel);
- out->writeByte(v->numCels);
+ out->writeByte(screenObj->currentCelNr);
+ out->writeByte(screenObj->celCount);
// v->cel_data
// v->cel_data_2
- out->writeSint16BE(v->xPos2);
- out->writeSint16BE(v->yPos2);
+ out->writeSint16BE(screenObj->xPos_prev);
+ out->writeSint16BE(screenObj->yPos_prev);
// v->s
- out->writeSint16BE(v->xSize);
- out->writeSint16BE(v->ySize);
- out->writeByte(v->stepSize);
- out->writeByte(v->cycleTime);
- out->writeByte(v->cycleTimeCount);
- out->writeByte(v->direction);
+ out->writeSint16BE(screenObj->xSize);
+ out->writeSint16BE(screenObj->ySize);
+ out->writeByte(screenObj->stepSize);
+ out->writeByte(screenObj->cycleTime);
+ out->writeByte(screenObj->cycleTimeCount);
+ out->writeByte(screenObj->direction);
- out->writeByte(v->motion);
- out->writeByte(v->cycle);
- out->writeByte(v->priority);
+ out->writeByte(screenObj->motionType);
+ out->writeByte(screenObj->cycle);
+ out->writeByte(screenObj->priority);
- out->writeUint16BE(v->flags);
+ out->writeUint16BE(screenObj->flags);
- out->writeByte(v->parm1);
- out->writeByte(v->parm2);
- out->writeByte(v->parm3);
- out->writeByte(v->parm4);
+ // this was done so that saved games compatibility isn't broken
+ switch (screenObj->motionType) {
+ case kMotionNormal:
+ out->writeByte(0);
+ out->writeByte(0);
+ out->writeByte(0);
+ out->writeByte(0);
+ break;
+ case kMotionWander:
+ out->writeByte(screenObj->wander_count);
+ out->writeByte(0);
+ out->writeByte(0);
+ out->writeByte(0);
+ break;
+ case kMotionFollowEgo:
+ out->writeByte(screenObj->follow_stepSize);
+ out->writeByte(screenObj->follow_flag);
+ out->writeByte(screenObj->follow_count);
+ out->writeByte(0);
+ break;
+ case kMotionEgo:
+ case kMotionMoveObj:
+ out->writeByte((byte)screenObj->move_x); // problematic! int16 -> byte
+ out->writeByte((byte)screenObj->move_y);
+ out->writeByte(screenObj->move_stepSize);
+ out->writeByte(screenObj->move_flag);
+ break;
+ default:
+ error("unknown motion-type");
+ }
}
// Save image stack
@@ -249,7 +310,7 @@ int AgiEngine::saveGame(const Common::String &fileName, const Common::String &de
warning("Can't write file '%s'. (Disk full?)", fileName.c_str());
result = errIOError;
} else
- debugC(1, kDebugLevelMain | kDebugLevelSavegame, "Saved game %s in file %s", description.c_str(), fileName.c_str());
+ debugC(1, kDebugLevelMain | kDebugLevelSavegame, "Saved game %s in file %s", descriptionString.c_str(), fileName.c_str());
delete out;
debugC(3, kDebugLevelMain | kDebugLevelSavegame, "Closed %s", fileName.c_str());
@@ -260,8 +321,10 @@ int AgiEngine::saveGame(const Common::String &fileName, const Common::String &de
}
int AgiEngine::loadGame(const Common::String &fileName, bool checkId) {
- char description[31], saveVersion, loadId[8];
- int i, vtEntries = MAX_VIEWTABLE;
+ char description[SAVEDGAME_DESCRIPTION_LEN + 1];
+ byte saveVersion = 0;
+ char loadId[8];
+ int i, vtEntries = SCREENOBJECTS_MAX;
uint8 t;
int16 parm[7];
Common::InSaveFile *in;
@@ -284,17 +347,28 @@ int AgiEngine::loadGame(const Common::String &fileName, bool checkId) {
return errOK;
}
- in->read(description, 31);
+ assert(SAVEDGAME_DESCRIPTION_LEN + 1 == 31); // safety
+ in->read(description, 31); // skip description
+ // check, if there is a terminating NUL inside description
+ uint16 descriptionPos = 0;
+ while (description[descriptionPos]) {
+ descriptionPos++;
+ if (descriptionPos >= sizeof(description))
+ error("saved game description is corrupt");
+ }
debugC(6, kDebugLevelMain | kDebugLevelSavegame, "Description is: %s", description);
saveVersion = in->readByte();
if (saveVersion < 2) // is the save game pre-ScummVM?
- warning("Old save game version (%d, current version is %d). Will try and read anyway, but don't be surprised if bad things happen", saveVersion, SAVEGAME_VERSION);
+ warning("Old save game version (%d, current version is %d). Will try and read anyway, but don't be surprised if bad things happen", saveVersion, SAVEGAME_CURRENT_VERSION);
if (saveVersion < 3)
warning("This save game contains no AGIPAL data, if the game is using the AGIPAL hack, it won't work correctly");
+ if (saveVersion > SAVEGAME_CURRENT_VERSION)
+ error("Saved game was created with a newer version of ScummVM. Unable to load.");
+
if (saveVersion >= 4) {
// We don't need the thumbnail here, so just read it and discard it
Graphics::skipThumbnail(*in);
@@ -348,53 +422,74 @@ int AgiEngine::loadGame(const Common::String &fileName, bool checkId) {
}
}
+ if (saveVersion >= 7) {
+ // Restore automatic saving state (set.simple opcode)
+ _game.automaticSave = in->readByte();
+ in->read(_game.automaticSaveDescription, 31);
+ } else {
+ _game.automaticSave = false;
+ _game.automaticSaveDescription[0] = 0;
+ }
+
for (i = 0; i < MAX_FLAGS; i++)
_game.flags[i] = in->readByte();
for (i = 0; i < MAX_VARS; i++)
_game.vars[i] = in->readByte();
- setvar(vFreePages, 180); // Set amount of free memory to realistic value (Overwriting the just loaded value)
+ setVar(VM_VAR_FREE_PAGES, 180); // Set amount of free memory to realistic value (Overwriting the just loaded value)
_game.horizon = in->readSint16BE();
- _game.lineStatus = in->readSint16BE();
- _game.lineUserInput = in->readSint16BE();
- _game.lineMinPrint = in->readSint16BE();
+ _text->statusRow_Set(in->readSint16BE());
+ _text->promptRow_Set(in->readSint16BE());
+ _text->configureScreen(in->readSint16BE());
// These are never saved
- _game.cursorPos = 0;
- _game.inputBuffer[0] = 0;
- _game.echoBuffer[0] = 0;
+ _text->promptReset();
+
_game.keypress = 0;
_game.inputMode = (InputMode)in->readSint16BE();
+ if ((_game.inputMode != INPUTMODE_NORMAL) && (_game.inputMode != INPUTMODE_NONE)) {
+ // other input modes were removed
+ _game.inputMode = INPUTMODE_NORMAL;
+ }
+
_game.lognum = in->readSint16BE();
_game.playerControl = in->readSint16BE();
if (in->readSint16BE())
quitGame();
- _game.statusLine = in->readSint16BE();
+ if (in->readSint16BE()) {
+ _text->statusEnable();
+ } else {
+ _text->statusDisable();
+ }
_game.clockEnabled = in->readSint16BE();
_game.exitAllLogics = in->readSint16BE();
- _game.pictureShown = in->readSint16BE();
+ in->readSint16BE(); // was _game.pictureShown
+ //_game.pictureShown = in->readSint16BE();
_game.hasPrompt = in->readSint16BE();
_game.gameFlags = in->readSint16BE();
- _game.inputEnabled = in->readSint16BE();
+ if (in->readSint16BE()) {
+ _text->promptEnable();
+ } else {
+ _text->promptDisable();
+ }
- for (i = 0; i < _HEIGHT; i++)
- _game.priTable[i] = in->readByte();
+ for (i = 0; i < SCRIPT_HEIGHT; i++)
+ _gfx->setPriority(i, in->readByte());
- if (_game.hasWindow)
- closeWindow();
+ _text->closeWindow();
_game.msgBoxTicks = 0;
_game.block.active = false;
- // game.window - fixed by close_window()
- // game.has_window - fixed by close_window()
_game.gfxMode = in->readSint16BE();
- _game.cursorChar = in->readByte();
- _game.colorFg = in->readSint16BE();
- _game.colorBg = in->readSint16BE();
+ _text->inputSetCursorChar(in->readByte());
+
+ int16 textForeground = in->readSint16BE();
+ int16 textBackground = in->readSint16BE();
+ _text->charAttrib_Set(textForeground, textBackground);
// game.hires - rebuilt from image stack
// game.sbuf - rebuilt from image stack
@@ -407,41 +502,49 @@ int AgiEngine::loadGame(const Common::String &fileName, bool checkId) {
objectSetLocation(i, in->readSint16BE());
// Those are not serialized
- for (i = 0; i < MAX_DIRS; i++) {
+ for (i = 0; i < MAX_CONTROLLERS; i++) {
_game.controllerOccured[i] = false;
}
+ if (saveVersion >= 7) {
+ // For old saves, we just keep the current controllers
+ for (i = 0; i < MAX_CONTROLLER_KEYMAPPINGS; i++) {
+ _game.controllerKeyMapping[i].keycode = in->readUint16BE();
+ _game.controllerKeyMapping[i].controllerSlot = in->readByte();
+ }
+ }
+
for (i = 0; i < MAX_STRINGS; i++)
in->read(_game.strings[i], MAX_STRINGLEN);
- for (i = 0; i < MAX_DIRS; i++) {
+ for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) {
if (in->readByte() & RES_LOADED)
- agiLoadResource(rLOGIC, i);
+ agiLoadResource(RESOURCETYPE_LOGIC, i);
else
- agiUnloadResource(rLOGIC, i);
+ agiUnloadResource(RESOURCETYPE_LOGIC, i);
_game.logics[i].sIP = in->readSint16BE();
_game.logics[i].cIP = in->readSint16BE();
}
- for (i = 0; i < MAX_DIRS; i++) {
+ for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) {
if (in->readByte() & RES_LOADED)
- agiLoadResource(rPICTURE, i);
+ agiLoadResource(RESOURCETYPE_PICTURE, i);
else
- agiUnloadResource(rPICTURE, i);
+ agiUnloadResource(RESOURCETYPE_PICTURE, i);
}
- for (i = 0; i < MAX_DIRS; i++) {
+ for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) {
if (in->readByte() & RES_LOADED)
- agiLoadResource(rVIEW, i);
+ agiLoadResource(RESOURCETYPE_VIEW, i);
else
- agiUnloadResource(rVIEW, i);
+ agiUnloadResource(RESOURCETYPE_VIEW, i);
}
- for (i = 0; i < MAX_DIRS; i++) {
+ for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) {
if (in->readByte() & RES_LOADED)
- agiLoadResource(rSOUND, i);
+ agiLoadResource(RESOURCETYPE_SOUND, i);
else
- agiUnloadResource(rSOUND, i);
+ agiUnloadResource(RESOURCETYPE_SOUND, i);
}
// game.pictures - loaded above
@@ -450,78 +553,102 @@ int AgiEngine::loadGame(const Common::String &fileName, bool checkId) {
// game.sounds - loaded above
for (i = 0; i < vtEntries; i++) {
- VtEntry *v = &_game.viewTable[i];
-
- v->stepTime = in->readByte();
- v->stepTimeCount = in->readByte();
- v->entry = in->readByte();
- v->xPos = in->readSint16BE();
- v->yPos = in->readSint16BE();
- v->currentView = in->readByte();
-
- // v->view_data - fixed below
-
- v->currentLoop = in->readByte();
- v->numLoops = in->readByte();
-
- // v->loop_data - fixed below
-
- v->currentCel = in->readByte();
- v->numCels = in->readByte();
-
- // v->cel_data - fixed below
- // v->cel_data_2 - fixed below
-
- v->xPos2 = in->readSint16BE();
- v->yPos2 = in->readSint16BE();
-
- // v->s - fixed below
-
- v->xSize = in->readSint16BE();
- v->ySize = in->readSint16BE();
- v->stepSize = in->readByte();
- v->cycleTime = in->readByte();
- v->cycleTimeCount = in->readByte();
- v->direction = in->readByte();
-
- v->motion = (MotionType)in->readByte();
- v->cycle = (CycleType)in->readByte();
- v->priority = in->readByte();
-
- v->flags = in->readUint16BE();
-
- v->parm1 = in->readByte();
- v->parm2 = in->readByte();
- v->parm3 = in->readByte();
- v->parm4 = in->readByte();
+ ScreenObjEntry *screenObj = &_game.screenObjTable[i];
+
+ screenObj->stepTime = in->readByte();
+ screenObj->stepTimeCount = in->readByte();
+ screenObj->objectNr = in->readByte();
+ screenObj->xPos = in->readSint16BE();
+ screenObj->yPos = in->readSint16BE();
+ screenObj->currentViewNr = in->readByte();
+
+ // screenObj->view_data - fixed below
+
+ screenObj->currentLoopNr = in->readByte();
+ screenObj->loopCount = in->readByte();
+
+ // screenObj->loop_data - fixed below
+
+ screenObj->currentCelNr = in->readByte();
+ screenObj->celCount = in->readByte();
+
+ // screenObj->cel_data - fixed below
+ // screenObj->cel_data_2 - fixed below
+
+ screenObj->xPos_prev = in->readSint16BE();
+ screenObj->yPos_prev = in->readSint16BE();
+
+ // screenObj->s - fixed below
+
+ screenObj->xSize = in->readSint16BE();
+ screenObj->ySize = in->readSint16BE();
+ screenObj->stepSize = in->readByte();
+ screenObj->cycleTime = in->readByte();
+ screenObj->cycleTimeCount = in->readByte();
+ screenObj->direction = in->readByte();
+
+ screenObj->motionType = (MotionType)in->readByte();
+ screenObj->cycle = (CycleType)in->readByte();
+ screenObj->priority = in->readByte();
+
+ screenObj->flags = in->readUint16BE();
+
+ // this was done so that saved games compatibility isn't broken
+ switch (screenObj->motionType) {
+ case kMotionNormal:
+ in->readByte();
+ in->readByte();
+ in->readByte();
+ in->readByte();
+ break;
+ case kMotionWander:
+ screenObj->wander_count = in->readByte();
+ in->readByte();
+ in->readByte();
+ in->readByte();
+ break;
+ case kMotionFollowEgo:
+ screenObj->follow_stepSize = in->readByte();
+ screenObj->follow_flag = in->readByte();
+ screenObj->follow_count = in->readByte();
+ in->readByte();
+ break;
+ case kMotionEgo:
+ case kMotionMoveObj:
+ screenObj->move_x = in->readByte(); // problematic! int16 -> byte
+ screenObj->move_y = in->readByte();
+ screenObj->move_stepSize = in->readByte();
+ screenObj->move_flag = in->readByte();
+ break;
+ default:
+ error("unknown motion-type");
+ }
}
- for (i = vtEntries; i < MAX_VIEWTABLE; i++) {
- memset(&_game.viewTable[i], 0, sizeof(VtEntry));
+ for (i = vtEntries; i < SCREENOBJECTS_MAX; i++) {
+ memset(&_game.screenObjTable[i], 0, sizeof(ScreenObjEntry));
}
- // Fix some pointers in viewtable
+ // Fix some pointers in screenObjTable
- for (i = 0; i < MAX_VIEWTABLE; i++) {
- VtEntry *v = &_game.viewTable[i];
+ for (i = 0; i < SCREENOBJECTS_MAX; i++) {
+ ScreenObjEntry *screenObj = &_game.screenObjTable[i];
- if (_game.dirView[v->currentView].offset == _EMPTY)
+ if (_game.dirView[screenObj->currentViewNr].offset == _EMPTY)
continue;
- if (!(_game.dirView[v->currentView].flags & RES_LOADED))
- agiLoadResource(rVIEW, v->currentView);
+ if (!(_game.dirView[screenObj->currentViewNr].flags & RES_LOADED))
+ agiLoadResource(RESOURCETYPE_VIEW, screenObj->currentViewNr);
- setView(v, v->currentView); // Fix v->view_data
- setLoop(v, v->currentLoop); // Fix v->loop_data
- setCel(v, v->currentCel); // Fix v->cel_data
- v->celData2 = v->celData;
- v->s = NULL; // not sure if it is used...
+ setView(screenObj, screenObj->currentViewNr); // Fix v->view_data
+ setLoop(screenObj, screenObj->currentLoopNr); // Fix v->loop_data
+ setCel(screenObj, screenObj->currentCelNr); // Fix v->cel_data
}
- _sprites->eraseBoth();
+ _sprites->eraseSprites();
+
+ _game.pictureShown = false;
- // Clear input line
- _gfx->clearScreen(0);
- writeStatus();
+ _gfx->clearDisplay(0, false); // clear display screen, but not copy it to actual screen for now b/c transition
// Recreate background from saved image stack
clearImageStack();
@@ -539,259 +666,26 @@ int AgiEngine::loadGame(const Common::String &fileName, bool checkId) {
delete in;
debugC(3, kDebugLevelMain | kDebugLevelSavegame, "Closed %s", fileName.c_str());
- setflag(fRestoreJustRan, true);
+ setflag(VM_FLAG_RESTORE_JUST_RAN, true);
_game.hasPrompt = 0; // force input line repaint if necessary
- cleanInput();
-
- _sprites->eraseBoth();
- _sprites->blitBoth();
- _sprites->commitBoth();
- _picture->showPic();
- _gfx->doUpdate();
-
- return errOK;
-}
-
-#define NUM_SLOTS 100
-#define NUM_VISIBLE_SLOTS 12
-
-Common::String AgiEngine::getSavegameFilename(int num) const {
- Common::String saveLoadSlot = _targetName;
- saveLoadSlot += Common::String::format(".%.3d", num);
- return saveLoadSlot;
-}
-
-void AgiEngine::getSavegameDescription(int num, char *buf, bool showEmpty) {
- Common::InSaveFile *in;
- Common::String fileName = getSavegameFilename(num);
-
- debugC(4, kDebugLevelMain | kDebugLevelSavegame, "Current game id is %s", _targetName.c_str());
-
- if (!(in = _saveFileMan->openForLoading(fileName))) {
- debugC(4, kDebugLevelMain | kDebugLevelSavegame, "File %s does not exist", fileName.c_str());
-
- if (showEmpty)
- strcpy(buf, " (empty slot)");
- else
- *buf = 0;
- } else {
- debugC(4, kDebugLevelMain | kDebugLevelSavegame, "Successfully opened %s for reading", fileName.c_str());
-
- uint32 type = in->readUint32BE();
-
- if (type == AGIflag) {
- debugC(6, kDebugLevelMain | kDebugLevelSavegame, "Has AGI flag, good start");
- in->read(buf, 31);
- } else {
- warning("This doesn't appear to be an AGI savegame");
- strcpy(buf, "(corrupt file)");
- }
-
- delete in;
- }
-}
-
-int AgiEngine::selectSlot() {
- int i, key, active = 0;
- int rc = -1;
- int hm = 1, vm = 3; // box margins
- int xmin, xmax, slotClicked;
- char desc[NUM_VISIBLE_SLOTS][40];
- int textCenter, buttonLength, buttonX[2], buttonY;
- const char *buttonText[] = { " OK ", "Cancel", NULL };
-
- _noSaveLoadAllowed = true;
-
- for (i = 0; i < NUM_VISIBLE_SLOTS; i++) {
- getSavegameDescription(_firstSlot + i, desc[i]);
- }
-
- textCenter = GFX_WIDTH / CHAR_LINES / 2;
- buttonLength = 6;
- buttonX[0] = (textCenter - 3 * buttonLength / 2) * CHAR_COLS;
- buttonX[1] = (textCenter + buttonLength / 2) * CHAR_COLS;
- buttonY = (vm + 17) * CHAR_LINES;
-
- for (i = 0; i < 2; i++)
- _gfx->drawCurrentStyleButton(buttonX[i], buttonY, buttonText[i], false, false, i == 0);
-
- AllowSyntheticEvents on(this);
- int oldFirstSlot = _firstSlot + 1;
- int oldActive = active + 1;
- bool exitSelectSlot = false;
- while (!exitSelectSlot && !(shouldQuit() || _restartGame)) {
- int sbPos = 0;
-
- // Use the extreme scrollbar positions only if the extreme
- // slots are in sight. (We have to calculate this even if we
- // don't redraw the save slots, because it's also used for
- // clicking in the scrollbar.
-
- if (_firstSlot == 0)
- sbPos = 1;
- else if (_firstSlot == NUM_SLOTS - NUM_VISIBLE_SLOTS)
- sbPos = NUM_VISIBLE_SLOTS - 2;
- else {
- sbPos = 2 + (_firstSlot * (NUM_VISIBLE_SLOTS - 4)) / (NUM_SLOTS - NUM_VISIBLE_SLOTS - 1);
- if (sbPos >= NUM_VISIBLE_SLOTS - 3)
- sbPos = NUM_VISIBLE_SLOTS - 3;
- }
-
- if (oldFirstSlot != _firstSlot || oldActive != active) {
- char dstr[64];
- for (i = 0; i < NUM_VISIBLE_SLOTS; i++) {
- sprintf(dstr, "[%2d. %-28.28s]", i + _firstSlot, desc[i]);
- printText(dstr, 0, hm + 1, vm + 4 + i,
- (40 - 2 * hm) - 1, i == active ? MSG_BOX_COLOR : MSG_BOX_TEXT,
- i == active ? MSG_BOX_TEXT : MSG_BOX_COLOR);
- }
-
- char upArrow[] = "^";
- char downArrow[] = "v";
- char scrollBar[] = " ";
-
- for (i = 1; i < NUM_VISIBLE_SLOTS - 1; i++)
- printText(scrollBar, 35, hm + 1, vm + 4 + i, 1, MSG_BOX_COLOR, 7, true);
-
- printText(upArrow, 35, hm + 1, vm + 4, 1, 8, 7);
- printText(downArrow, 35, hm + 1, vm + 4 + NUM_VISIBLE_SLOTS - 1, 1, 8, 7);
- printText(scrollBar, 35, hm + 1, vm + 4 + sbPos, 1, MSG_BOX_COLOR, MSG_BOX_TEXT);
-
- oldActive = active;
- oldFirstSlot = _firstSlot;
- }
-
- pollTimer();
- key = doPollKeyboard();
-
- // It may happen that somebody will open GMM while
- // this dialog is open, and load a game
- // We are processing it here, effectively jumping
- // out of the dead loop
- if (getflag(fRestoreJustRan)) {
- rc = -2;
- exitSelectSlot = true;
- }
+ _words->clearEgoWords();
- if (!exitSelectSlot) {
- switch (key) {
- case KEY_ENTER:
- rc = active;
- Common::strlcpy(_game.strings[MAX_STRINGS], desc[i], MAX_STRINGLEN);
- debugC(8, kDebugLevelMain | kDebugLevelInput, "Button pressed: %d", rc);
- exitSelectSlot = true;
- break;
- case KEY_ESCAPE:
- rc = -1;
- exitSelectSlot = true;
- break;
- case BUTTON_LEFT:
- if (_gfx->testButton(buttonX[0], buttonY, buttonText[0])) {
- rc = active;
- strncpy(_game.strings[MAX_STRINGS], desc[i], MAX_STRINGLEN);
- debugC(8, kDebugLevelMain | kDebugLevelInput, "Button pressed: %d", rc);
- exitSelectSlot = true;
- } else if (_gfx->testButton(buttonX[1], buttonY, buttonText[1])) {
- rc = -1;
- exitSelectSlot = true;
- } else {
- slotClicked = ((int)_mouse.y - 1) / CHAR_COLS - (vm + 4);
- xmin = (hm + 1) * CHAR_COLS;
- xmax = xmin + CHAR_COLS * 34;
- if ((int)_mouse.x >= xmin && (int)_mouse.x <= xmax) {
- if (slotClicked >= 0 && slotClicked < NUM_VISIBLE_SLOTS)
- active = slotClicked;
- }
- xmin = (hm + 36) * CHAR_COLS;
- xmax = xmin + CHAR_COLS;
- if ((int)_mouse.x >= xmin && (int)_mouse.x <= xmax) {
- if (slotClicked >= 0 && slotClicked < NUM_VISIBLE_SLOTS) {
- if (slotClicked == 0)
- keyEnqueue(KEY_UP);
- else if (slotClicked == NUM_VISIBLE_SLOTS - 1)
- keyEnqueue(KEY_DOWN);
- else if (slotClicked < sbPos)
- keyEnqueue(KEY_UP_RIGHT);
- else if (slotClicked > sbPos)
- keyEnqueue(KEY_DOWN_RIGHT);
- }
- }
- }
- break;
+ // don't delay anything right after restoring a game
+ nonBlockingText_Forget();
- case KEY_DOWN:
- active++;
- if (active >= NUM_VISIBLE_SLOTS) {
- if (_firstSlot + NUM_VISIBLE_SLOTS < NUM_SLOTS) {
- _firstSlot++;
- for (i = 1; i < NUM_VISIBLE_SLOTS; i++)
- memcpy(desc[i - 1], desc[i], sizeof(desc[0]));
- getSavegameDescription(_firstSlot + NUM_VISIBLE_SLOTS - 1, desc[NUM_VISIBLE_SLOTS - 1]);
- }
- active = NUM_VISIBLE_SLOTS - 1;
- }
- break;
- case KEY_UP:
- active--;
- if (active < 0) {
- active = 0;
- if (_firstSlot > 0) {
- _firstSlot--;
- for (i = NUM_VISIBLE_SLOTS - 1; i > 0; i--)
- memcpy(desc[i], desc[i - 1], sizeof(desc[0]));
- getSavegameDescription(_firstSlot, desc[0]);
- }
- }
- break;
+ _sprites->eraseSprites();
+ _sprites->buildAllSpriteLists();
+ _sprites->drawAllSpriteLists();
+ _picture->showPicWithTransition();
+ _game.pictureShown = true;
+ _text->statusDraw();
+ _text->promptRedraw();
- // Page Up/Down and mouse wheel scrolling all leave 'active'
- // unchanged so that a visible slot will remain selected.
+ // copy everything over (we should probably only copy over the remaining parts of the screen w/o play screen
+ _gfx->copyDisplayRectToScreen(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT);
- case WHEEL_DOWN:
- if (_firstSlot < NUM_SLOTS - NUM_VISIBLE_SLOTS) {
- _firstSlot++;
- for (i = 1; i < NUM_VISIBLE_SLOTS; i++)
- memcpy(desc[i - 1], desc[i], sizeof(desc[0]));
- getSavegameDescription(_firstSlot + NUM_VISIBLE_SLOTS - 1, desc[NUM_VISIBLE_SLOTS - 1]);
- }
- break;
- case WHEEL_UP:
- if (_firstSlot > 0) {
- _firstSlot--;
- for (i = NUM_VISIBLE_SLOTS - 1; i > 0; i--)
- memcpy(desc[i], desc[i - 1], sizeof(desc[0]));
- getSavegameDescription(_firstSlot, desc[0]);
- }
- break;
- case KEY_DOWN_RIGHT:
- // This is probably triggered by Page Down.
- _firstSlot += NUM_VISIBLE_SLOTS;
- if (_firstSlot > NUM_SLOTS - NUM_VISIBLE_SLOTS) {
- _firstSlot = NUM_SLOTS - NUM_VISIBLE_SLOTS;
- }
- for (i = 0; i < NUM_VISIBLE_SLOTS; i++)
- getSavegameDescription(_firstSlot + i, desc[i]);
- break;
- case KEY_UP_RIGHT:
- // This is probably triggered by Page Up.
- _firstSlot -= NUM_VISIBLE_SLOTS;
- if (_firstSlot < 0) {
- _firstSlot = 0;
- }
- for (i = 0; i < NUM_VISIBLE_SLOTS; i++)
- getSavegameDescription(_firstSlot + i, desc[i]);
- break;
- }
- }
- _gfx->doUpdate();
- }
-
- closeWindow();
-
- _noSaveLoadAllowed = false;
-
- return rc;
+ return errOK;
}
int AgiEngine::scummVMSaveLoadDialog(bool isSave) {
@@ -834,7 +728,8 @@ int AgiEngine::doSave(int slot, const Common::String &desc) {
// Make sure all graphics was blitted to screen. This fixes bug
// #2960567: "AGI: Ego partly erased in Load/Save thumbnails"
- _gfx->doUpdate();
+ _gfx->updateScreen();
+// _gfx->doUpdate();
return saveGame(fileName, desc);
}
@@ -843,160 +738,213 @@ int AgiEngine::doLoad(int slot, bool showMessages) {
Common::String fileName = getSavegameFilename(slot);
debugC(8, kDebugLevelMain | kDebugLevelResources, "file is [%s]", fileName.c_str());
- _sprites->eraseBoth();
+ _sprites->eraseSprites();
_sound->stopSound();
- closeWindow();
+ _text->closeWindow();
int result = loadGame(fileName);
if (result == errOK) {
- if (showMessages)
- messageBox("Game restored.");
_game.exitAllLogics = 1;
- _menu->enableAll();
+ _menu->itemEnableAll();
} else {
if (showMessages)
- messageBox("Error restoring game.");
+ _text->messageBox("Error restoring game.");
}
return result;
}
-int AgiEngine::saveGameDialog() {
- if (!ConfMan.getBool("originalsaveload"))
- return scummVMSaveLoadDialog(true);
+SavedGameSlotIdArray AgiEngine::getSavegameSlotIds() {
+ Common::StringArray filenames;
+ int16 numberPos = _targetName.size() + 1;
+ int16 slotId = 0;
+ SavedGameSlotIdArray slotIdArray;
- char *desc;
- const char *buttons[] = { "Do as I say!", "I regret", NULL };
- char dstr[200];
- int rc, slot = 0;
- int hm, vm, hp, vp;
- int w;
-
- hm = 1;
- vm = 3;
- hp = hm * CHAR_COLS;
- vp = vm * CHAR_LINES;
- w = (40 - 2 * hm) - 1;
-
- do {
- drawWindow(hp, vp, GFX_WIDTH - hp, GFX_HEIGHT - vp);
- printText("Select a slot in which you wish to\nsave the game:",
- 0, hm + 1, vm + 1, w, MSG_BOX_TEXT, MSG_BOX_COLOR);
- slot = selectSlot();
- if (slot + _firstSlot == 0)
- messageBox("That slot is for Autosave only.");
- else if (slot < 0)
- return errOK;
- } while (slot + _firstSlot == 0);
-
- drawWindow(hp, vp + 5 * CHAR_LINES, GFX_WIDTH - hp,
- GFX_HEIGHT - vp - 9 * CHAR_LINES);
- printText("Enter a description for this game:",
- 0, hm + 1, vm + 6, w, MSG_BOX_TEXT, MSG_BOX_COLOR);
- _gfx->drawRectangle(3 * CHAR_COLS, 11 * CHAR_LINES - 1,
- 37 * CHAR_COLS, 12 * CHAR_LINES, MSG_BOX_TEXT);
- _gfx->flushBlock(3 * CHAR_COLS, 11 * CHAR_LINES - 1,
- 37 * CHAR_COLS, 12 * CHAR_LINES);
-
- // The description field of the save/restore dialog holds 32 characters
- // but we use four of them for the slot number. The input field is a
- // bit wider than that, so we don't have to worry about leaving space
- // for the cursor.
-
- getString(2, 11, 28, MAX_STRINGS);
-
- // If we're saving over an old slot, show the old description. We can't
- // access that buffer directly, so we have to feed the characters to
- // the input handler one at a time.
-
- char name[40];
- int numChars;
-
- getSavegameDescription(_firstSlot + slot, name, false);
-
- for (numChars = 0; numChars < 28 && name[numChars]; numChars++)
- handleGetstring(name[numChars]);
-
- _gfx->printCharacter(numChars + 3, 11, _game.cursorChar, MSG_BOX_COLOR, MSG_BOX_TEXT);
- do {
- mainCycle();
- } while (_game.inputMode == INPUT_GETSTRING);
- closeWindow();
-
- desc = _game.strings[MAX_STRINGS];
- sprintf(dstr, "Are you sure you want to save the game "
- "described as:\n\n%s\n\nin slot %d?\n\n\n", desc, _firstSlot + slot);
-
- rc = selectionBox(dstr, buttons);
-
- if (rc != 0) {
- messageBox("Game NOT saved.");
- return errOK;
+ // search for saved game filenames...
+ filenames = _saveFileMan->listSavefiles(_targetName + ".###");
+
+ Common::StringArray::iterator it;
+ Common::StringArray::iterator end = filenames.end();;
+
+ // convert to lower-case, just to be sure
+ for (it = filenames.begin(); it != end; it++) {
+ it->toLowercase();
}
+ // sort
+ Common::sort(filenames.begin(), filenames.end());
- int result = doSave(_firstSlot + slot, desc);
+ // now extract slot-Ids
+ for (it = filenames.begin(); it != end; it++) {
+ slotId = atoi(it->c_str() + numberPos);
- if (result == errOK)
- messageBox("Game saved.");
- else
- messageBox("Error saving game.");
+ slotIdArray.push_back(slotId);
+ }
+ return slotIdArray;
+}
- return result;
+Common::String AgiEngine::getSavegameFilename(int16 slotId) const {
+ Common::String saveLoadSlot = _targetName;
+ saveLoadSlot += Common::String::format(".%.3d", slotId);
+ return saveLoadSlot;
}
-int AgiEngine::saveGameSimple() {
- if (!ConfMan.getBool("originalsaveload"))
- return scummVMSaveLoadDialog(true);
+bool AgiEngine::getSavegameInformation(int16 slotId, Common::String &saveDescription, uint32 &saveDate, uint16 &saveTime, bool &saveIsValid) {
+ Common::InSaveFile *in;
+ Common::String fileName = getSavegameFilename(slotId);
+ char saveGameDescription[31];
+ int16 curPos = 0;
+ byte saveVersion = 0;
- Common::String fileName = getSavegameFilename(0);
+ saveDescription.clear();
+ saveDate = 0;
+ saveTime = 0;
+ saveIsValid = false;
- int result = saveGame(fileName, "Default savegame");
- if (result != errOK)
- messageBox("Error saving game.");
- return result;
-}
+ debugC(4, kDebugLevelMain | kDebugLevelSavegame, "Current game id is %s", _targetName.c_str());
-int AgiEngine::loadGameDialog() {
- if (!ConfMan.getBool("originalsaveload"))
- return scummVMSaveLoadDialog(false);
+ if (!(in = _saveFileMan->openForLoading(fileName))) {
+ debugC(4, kDebugLevelMain | kDebugLevelSavegame, "File %s does not exist", fileName.c_str());
+ return false;
+
+ } else {
+ debugC(4, kDebugLevelMain | kDebugLevelSavegame, "Successfully opened %s for reading", fileName.c_str());
- int slot = 0;
- int hm, vm, hp, vp; // box margins
- int w;
+ uint32 type = in->readUint32BE();
- hm = 1;
- vm = 3;
- hp = hm * CHAR_COLS;
- vp = vm * CHAR_LINES;
- w = (40 - 2 * hm) - 1;
+ if (type != AGIflag) {
+ warning("This doesn't appear to be an AGI savegame");
+ saveDescription += "[ScummVM: not an AGI save]";
+ delete in;
+ return true;
+ }
- _sprites->eraseBoth();
- _sound->stopSound();
+ debugC(6, kDebugLevelMain | kDebugLevelSavegame, "Has AGI flag, good start");
+ if (in->read(saveGameDescription, 31) != 31) {
+ warning("unexpected EOF");
+ delete in;
+ saveDescription += "[ScummVM: invalid save]";
+ return true;
+ }
- drawWindow(hp, vp, GFX_WIDTH - hp, GFX_HEIGHT - vp);
- printText("Select a game which you wish to\nrestore:",
- 0, hm + 1, vm + 1, w, MSG_BOX_TEXT, MSG_BOX_COLOR);
+ for (curPos = 0; curPos < 31; curPos++) {
+ if (!saveGameDescription[curPos])
+ break;
+ }
+ if (curPos >= 31) {
+ warning("corrupted description");
+ delete in;
+ saveDescription += "[ScummVM: invalid save]";
+ return true;
+ }
- slot = selectSlot();
+ saveVersion = in->readByte();
+ if (saveVersion > SAVEGAME_CURRENT_VERSION) {
+ warning("save from a future ScummVM, not supported");
+ delete in;
+ saveDescription += "[ScummVM: not supported]";
+ return true;
+ }
- if (slot < 0) {
- if (slot == -1) // slot = -2 when GMM was launched
- messageBox("Game NOT restored.");
+ if (saveVersion >= 4) {
+ // We don't need the thumbnail here, so just read it and discard it
+ Graphics::skipThumbnail(*in);
- return errOK;
+ saveDate = in->readUint32BE();
+ saveTime = in->readUint16BE();
+
+ // save date is DDMMYYYY, we need a proper format
+ byte saveDateDay = saveDate >> 24;
+ byte saveDateMonth = (saveDate >> 16) & 0xFF;
+ uint16 saveDateYear = saveDate & 0xFFFF;
+
+ saveDate = (saveDateYear << 16) | (saveDateMonth << 8) | saveDateDay;
+
+ } else {
+ saveDate = 0;
+ saveTime = 0;
+ }
+
+ saveDescription += saveGameDescription;
+ saveIsValid = true;
+
+ delete in;
+ return true;
}
+}
- return doLoad(_firstSlot + slot, true);
+bool AgiEngine::loadGameAutomatic() {
+ int16 automaticRestoreGameSlotId = 0;
+
+ automaticRestoreGameSlotId = _systemUI->figureOutAutomaticRestoreGameSlot(_game.automaticSaveDescription);
+ if (automaticRestoreGameSlotId >= 0) {
+ if (doLoad(automaticRestoreGameSlotId, true) == errOK) {
+ return true;
+ }
+ }
+ return false;
}
-int AgiEngine::loadGameSimple() {
+bool AgiEngine::loadGameDialog() {
+ int16 restoreGameSlotId = 0;
+
if (!ConfMan.getBool("originalsaveload"))
return scummVMSaveLoadDialog(false);
- else
- return doLoad(0, true);
+
+ restoreGameSlotId = _systemUI->askForRestoreGameSlot();
+ if (restoreGameSlotId >= 0) {
+ if (doLoad(restoreGameSlotId, true) == errOK) {
+ return true;
+ }
+ }
+ return errOK;
+}
+
+// Try to figure out either the slot, that is currently using the automatic saved game description
+// or get a new slot.
+// If we fail, return false, so that the regular saved game dialog is called
+// Original AGI was limited to 12 saves, we are effectively limited to 100 saves at the moment.
+//
+// btw. this also means that entering an existant name in Mixed Up Mother Goose will effectively overwrite
+// that saved game. This is also what original AGI did.
+bool AgiEngine::saveGameAutomatic() {
+ int16 automaticSaveGameSlotId = 0;
+
+ automaticSaveGameSlotId = _systemUI->figureOutAutomaticSaveGameSlot(_game.automaticSaveDescription);
+ if (automaticSaveGameSlotId >= 0) {
+ Common::String slotDescription(_game.automaticSaveDescription);
+
+ // WORKAROUND: Remove window in case one is currently shown, otherwise it would get saved in the thumbnail
+ // Happens for Mixed Up Mother Goose. The scripts close the window after saving.
+ // Original interpreter obviously did not do this, but original interpreter also did not save thumbnails.
+ _text->closeWindow();
+
+ if (doSave(automaticSaveGameSlotId, slotDescription) == errOK) {
+ return true;
+ }
+ }
+ return false;
}
+bool AgiEngine::saveGameDialog() {
+ int16 saveGameSlotId = 0;
+ Common::String slotDescription;
+
+ if (!ConfMan.getBool("originalsaveload"))
+ return scummVMSaveLoadDialog(true);
+
+ saveGameSlotId = _systemUI->askForSaveGameSlot();
+ if (saveGameSlotId >= 0) {
+ if (_systemUI->askForSaveGameDescription(saveGameSlotId, slotDescription)) {
+ if (doSave(saveGameSlotId, slotDescription) == errOK) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+
void AgiEngine::recordImageStackCall(uint8 type, int16 p1, int16 p2, int16 p3,
int16 p4, int16 p5, int16 p6, int16 p7) {
ImageStackElement pnew;
@@ -1019,11 +967,11 @@ void AgiEngine::replayImageStackCall(uint8 type, int16 p1, int16 p2, int16 p3,
switch (type) {
case ADD_PIC:
debugC(8, kDebugLevelMain, "--- decoding picture %d ---", p1);
- agiLoadResource(rPICTURE, p1);
+ agiLoadResource(RESOURCETYPE_PICTURE, p1);
_picture->decodePicture(p1, p2, p3 != 0);
break;
case ADD_VIEW:
- agiLoadResource(rVIEW, p1);
+ agiLoadResource(RESOURCETYPE_VIEW, p1);
_sprites->addToPic(p1, p2, p3, p4, p5, p6, p7);
break;
}
@@ -1041,12 +989,12 @@ void AgiEngine::checkQuickLoad() {
if (ConfMan.hasKey("save_slot")) {
Common::String saveNameBuffer = getSavegameFilename(ConfMan.getInt("save_slot"));
- _sprites->eraseBoth();
+ _sprites->eraseSprites();
_sound->stopSound();
if (loadGame(saveNameBuffer, false) == errOK) { // Do not check game id
_game.exitAllLogics = 1;
- _menu->enableAll();
+ _menu->itemEnableAll();
}
}
}
@@ -1054,21 +1002,21 @@ void AgiEngine::checkQuickLoad() {
Common::Error AgiEngine::loadGameState(int slot) {
Common::String saveLoadSlot = getSavegameFilename(slot);
- _sprites->eraseBoth();
+ _sprites->eraseSprites();
_sound->stopSound();
if (loadGame(saveLoadSlot) == errOK) {
_game.exitAllLogics = 1;
- _menu->enableAll();
+ _menu->itemEnableAll();
return Common::kNoError;
} else {
return Common::kUnknownError;
}
}
-Common::Error AgiEngine::saveGameState(int slot, const Common::String &desc) {
+Common::Error AgiEngine::saveGameState(int slot, const Common::String &description) {
Common::String saveLoadSlot = getSavegameFilename(slot);
- if (saveGame(saveLoadSlot, desc) == errOK)
+ if (saveGame(saveLoadSlot, description) == errOK)
return Common::kNoError;
else
return Common::kUnknownError;
diff --git a/engines/agi/sound_pcjr.cpp b/engines/agi/sound_pcjr.cpp
index ea7a2789e0..d875fa0b97 100644
--- a/engines/agi/sound_pcjr.cpp
+++ b/engines/agi/sound_pcjr.cpp
@@ -236,7 +236,7 @@ int SoundGenPCJr::getNextNote_v2(int ch) {
assert(ch < CHAN_MAX);
- if (!_vm->getflag(fSoundOn))
+ if (!_vm->getflag(VM_FLAG_SOUND_ON))
return -1;
tpcm = &_tchannel[ch];
diff --git a/engines/agi/sound_sarien.cpp b/engines/agi/sound_sarien.cpp
index 98479f3edc..d197f85832 100644
--- a/engines/agi/sound_sarien.cpp
+++ b/engines/agi/sound_sarien.cpp
@@ -161,7 +161,7 @@ void SoundGenSarien::stopNote(int i) {
}
void SoundGenSarien::playNote(int i, int freq, int vol) {
- if (!_vm->getflag(fSoundOn))
+ if (!_vm->getflag(VM_FLAG_SOUND_ON))
vol = 0;
else if (vol && _vm->_soundemu == SOUND_EMU_PC)
vol = 160;
diff --git a/engines/agi/sprite.cpp b/engines/agi/sprite.cpp
index 92f88d8fcb..1124aef742 100644
--- a/engines/agi/sprite.cpp
+++ b/engines/agi/sprite.cpp
@@ -23,530 +23,392 @@
#include "agi/agi.h"
#include "agi/sprite.h"
#include "agi/graphics.h"
+#include "agi/text.h"
namespace Agi {
-/**
- * Sprite structure.
- * This structure holds information on visible and priority data of
- * a rectangular area of the AGI screen. Sprites are chained in two
- * circular lists, one for updating and other for non-updating sprites.
- */
-struct Sprite {
- VtEntry *v; /**< pointer to view table entry */
- int16 xPos; /**< x coordinate of the sprite */
- int16 yPos; /**< y coordinate of the sprite */
- int16 xSize; /**< width of the sprite */
- int16 ySize; /**< height of the sprite */
- uint8 *buffer; /**< buffer to store background data */
-};
-
-/*
- * Sprite pool replaces dynamic allocation
- */
-#undef ALLOC_DEBUG
-
-
-#define POOL_SIZE 68000 // Gold Rush mine room needs > 50000
- // Speeder bike challenge needs > 67000
-
-void *SpritesMgr::poolAlloc(int size) {
- uint8 *x;
-
- // Adjust size to sizeof(void *) boundary to prevent data misalignment
- // errors.
- const int alignPadding = sizeof(void *) - 1;
- size = (size + alignPadding) & ~alignPadding;
-
- x = _poolTop;
- _poolTop += size;
-
- if (_poolTop >= (uint8 *)_spritePool + POOL_SIZE) {
- debugC(1, kDebugLevelMain | kDebugLevelResources, "not enough memory");
- _poolTop = x;
- return NULL;
- }
-
- return x;
+SpritesMgr::SpritesMgr(AgiEngine *agi, GfxMgr *gfx) {
+ _vm = agi;
+ _gfx = gfx;
}
-// Note: it's critical that pool_release() is called in the exact
-// reverse order of pool_alloc()
-void SpritesMgr::poolRelease(void *s) {
- _poolTop = (uint8 *)s;
+SpritesMgr::~SpritesMgr() {
+ _spriteRegularList.clear();
+ _spriteStaticList.clear();
}
-/*
- * Blitter functions
- */
-
-// Blit one pixel considering the priorities
-void SpritesMgr::blitPixel(uint8 *p, uint8 *end, uint8 col, int spr, int width, int *hidden) {
- int epr = 0, pr = 0; // effective and real priorities
-
- // CM: priority 15 overrides control lines and is ignored when
- // tracking effective priority. This tweak is needed to fix
- // Sarien bug #451768, and should not affect Sierra games because
- // sprites shouldn't have priority 15 (like the AGI Mouse
- // demo "mouse pointer")
- //
- // Update: this solution breaks other games, and can't be used.
-
- if (p >= end)
- return;
-
- // Check if we're on a control line
- if ((pr = *p & 0xf0) < 0x30) {
- uint8 *p1;
- // Yes, get effective priority going down
- for (p1 = p; p1 < end && (epr = *p1 & 0xf0) < 0x30; p1 += width)
- ;
- if (p1 >= end)
- epr = 0x40;
- } else {
- epr = pr;
- }
-
- if (spr >= epr) {
- // Keep control line information visible, but put our
- // priority over water (0x30) surface
- if (_vm->getFeatures() & (GF_AGI256 | GF_AGI256_2))
- *(p + FROM_SBUF16_TO_SBUF256_OFFSET) = col; // Write to 256 color buffer
- else
- *p = (pr < 0x30 ? pr : spr) | col; // Write to 16 color (+control line/priority info) buffer
-
- *hidden = false;
-
- // Except if our priority is 15, which should never happen
- // (fixes Sarien bug #451768)
- //
- // Update: breaks other games, can't be used
- //
- // if (spr == 0xf0)
- // *p = spr | col;
+static bool sortSpriteHelper(const Sprite &entry1, const Sprite &entry2) {
+ if (entry1.sortOrder == entry2.sortOrder) {
+ // If sort-order is the same, we sort according to given order
+ // which makes this sort stable.
+ return entry1.givenOrderNr < entry2.givenOrderNr;
}
+ return entry1.sortOrder < entry2.sortOrder;
}
+void SpritesMgr::buildRegularSpriteList() {
+ ScreenObjEntry *screenObj = NULL;
+ uint16 givenOrderNr = 0;
-int SpritesMgr::blitCel(int x, int y, int spr, ViewCel *c, bool agi256_2) {
- uint8 *p0, *p, *q = NULL, *end;
- int i, j, t, m, col;
- int hidden = true;
-
- // Fixes Sarien bug #477841 (crash in PQ1 map C4 when y == -2)
- if (y < 0)
- y = 0;
- if (x < 0)
- x = 0;
- if (y >= _HEIGHT)
- y = _HEIGHT - 1;
- if (x >= _WIDTH)
- x = _WIDTH - 1;
-
- q = c->data;
- t = c->transparency;
- m = c->mirror;
- spr <<= 4;
- p0 = &_vm->_game.sbuf16c[x + y * _WIDTH + m * (c->width - 1)];
-
- end = _vm->_game.sbuf16c + _WIDTH * _HEIGHT;
-
- for (i = 0; i < c->height; i++) {
- p = p0;
- while (*q) {
- col = agi256_2 ? *q : (*q & 0xf0) >> 4; // Uses whole byte for color info with AGI256-2
- for (j = agi256_2 ? 1 : *q & 0x0f; j; j--, p += 1 - 2 * m) { // No RLE with AGI256-2
- if (col != t) {
- blitPixel(p, end, col, spr, _WIDTH, &hidden);
- }
- }
- q++;
+ freeList(_spriteRegularList);
+ for (screenObj = _vm->_game.screenObjTable; screenObj < &_vm->_game.screenObjTable[SCREENOBJECTS_MAX]; screenObj++) {
+ if ((screenObj->flags & (fAnimated | fUpdate | fDrawn)) == (fAnimated | fUpdate | fDrawn)) {
+ buildSpriteListAdd(givenOrderNr, screenObj, _spriteRegularList);
+ givenOrderNr++;
}
- p0 += _WIDTH;
- q++;
}
- return hidden;
+ // Now sort this list
+ Common::sort(_spriteRegularList.begin(), _spriteRegularList.end(), sortSpriteHelper);
+// warning("buildRegular: %d", _spriteRegularList.size());
}
-void SpritesMgr::objsSaveArea(Sprite *s) {
- int y;
- int16 xPos = s->xPos, yPos = s->yPos;
- int16 xSize = s->xSize, ySize = s->ySize;
- uint8 *p0, *q;
+void SpritesMgr::buildStaticSpriteList() {
+ ScreenObjEntry *screenObj = NULL;
+ uint16 givenOrderNr = 0;
- if (xPos + xSize > _WIDTH)
- xSize = _WIDTH - xPos;
-
- if (xPos < 0) {
- xSize += xPos;
- xPos = 0;
+ freeList(_spriteStaticList);
+ for (screenObj = _vm->_game.screenObjTable; screenObj < &_vm->_game.screenObjTable[SCREENOBJECTS_MAX]; screenObj++) {
+ if ((screenObj->flags & (fAnimated | fUpdate | fDrawn)) == (fAnimated | fDrawn)) { // DIFFERENCE IN HERE!
+ buildSpriteListAdd(givenOrderNr, screenObj, _spriteStaticList);
+ givenOrderNr++;
+ }
}
- if (yPos + ySize > _HEIGHT)
- ySize = _HEIGHT - yPos;
+ // Now sort this list
+ Common::sort(_spriteStaticList.begin(), _spriteStaticList.end(), sortSpriteHelper);
+}
- if (yPos < 0) {
- ySize += yPos;
- yPos = 0;
- }
+void SpritesMgr::buildAllSpriteLists() {
+ buildStaticSpriteList();
+ buildRegularSpriteList();
+}
+
+void SpritesMgr::buildSpriteListAdd(uint16 givenOrderNr, ScreenObjEntry *screenObj, SpriteList &spriteList) {
+ Sprite spriteEntry;
- if (xSize <= 0 || ySize <= 0)
+ // Check, if screen object points to currently loaded view, if not don't add it
+ if (!(_vm->_game.dirView[screenObj->currentViewNr].flags & RES_LOADED))
return;
- p0 = &_vm->_game.sbuf[xPos + yPos * _WIDTH];
- q = s->buffer;
- for (y = 0; y < ySize; y++) {
- memcpy(q, p0, xSize);
- q += xSize;
- p0 += _WIDTH;
+ spriteEntry.givenOrderNr = givenOrderNr;
+// warning("sprite add objNr %d", screenObjPtr->objectNr);
+ if (screenObj->flags & fFixedPriority) {
+ spriteEntry.sortOrder = _gfx->priorityToY(screenObj->priority);
+// warning(" - priorityToY (fixed) %d -> %d", screenObj->priority, spriteEntry.sortOrder);
+ } else {
+ spriteEntry.sortOrder = screenObj->yPos;
+// warning(" - Ypos %d -> %d", screenObjPtr->yPos, spriteEntry.sortOrder);
}
-}
-void SpritesMgr::objsRestoreArea(Sprite *s) {
- int y, offset;
- int16 xPos = s->xPos, yPos = s->yPos;
- int16 xSize = s->xSize, ySize = s->ySize;
- uint8 *q;
- uint32 pos0;
+ spriteEntry.screenObjPtr = screenObj;
+ spriteEntry.xPos = screenObj->xPos;
+ spriteEntry.yPos = (screenObj->yPos) - (screenObj->ySize) + 1;
+ spriteEntry.xSize = screenObj->xSize;
+ spriteEntry.ySize = screenObj->ySize;
+// warning("list-add: %d, %d, original yPos: %d, ySize: %d", spriteEntry.xPos, spriteEntry.yPos, screenObj->yPos, screenObj->ySize);
+ spriteEntry.backgroundBuffer = (uint8 *)malloc(spriteEntry.xSize * spriteEntry.ySize * 2); // for visual + priority data
+ assert(spriteEntry.backgroundBuffer);
+ spriteList.push_back(spriteEntry);
+}
- if (xPos + xSize > _WIDTH)
- xSize = _WIDTH - xPos;
+void SpritesMgr::freeList(SpriteList &spriteList) {
+ SpriteList::iterator iter;
+ for (iter = spriteList.reverse_begin(); iter != spriteList.end(); iter--) {
+ Sprite &sprite = *iter;
- if (xPos < 0) {
- xSize += xPos;
- xPos = 0;
+ free(sprite.backgroundBuffer);
}
+ spriteList.clear();
+}
- if (yPos + ySize > _HEIGHT)
- ySize = _HEIGHT - yPos;
+void SpritesMgr::freeRegularSprites() {
+ freeList(_spriteRegularList);
+}
- if (yPos < 0) {
- ySize += yPos;
- yPos = 0;
- }
+void SpritesMgr::freeStaticSprites() {
+ freeList(_spriteStaticList);
+}
- if (xSize <= 0 || ySize <= 0)
- return;
+void SpritesMgr::freeAllSprites() {
+ freeList(_spriteRegularList);
+ freeList(_spriteStaticList);
+}
- pos0 = xPos + yPos * _WIDTH;
- q = s->buffer;
- offset = _vm->_game.lineMinPrint * CHAR_LINES;
- for (y = 0; y < ySize; y++) {
- memcpy(&_vm->_game.sbuf[pos0], q, xSize);
- _gfx->putPixelsA(xPos, yPos + y + offset, xSize, &_vm->_game.sbuf16c[pos0]);
- q += xSize;
- pos0 += _WIDTH;
+void SpritesMgr::eraseSprites(SpriteList &spriteList) {
+ SpriteList::iterator iter;
+// warning("eraseSprites - count %d", spriteList.size());
+ for (iter = spriteList.reverse_begin(); iter != spriteList.end(); iter--) {
+ Sprite &sprite = *iter;
+ _gfx->block_restore(sprite.xPos, sprite.yPos, sprite.xSize, sprite.ySize, sprite.backgroundBuffer);
}
- // WORKAROUND (see ScummVM bug #1945716)
- // When set.view command is called, current code cannot detect this situation while updating
- // Thus we force removal of the old sprite
- if (s->v && s->v->viewReplaced) {
- commitBlock(xPos, yPos, xPos + xSize, yPos + ySize);
- s->v->viewReplaced = false;
- }
+ freeList(spriteList);
}
-
/**
- * Condition to determine whether a sprite will be in the 'updating' list.
+ * Erase updating sprites.
+ * This function follows the list of all updating sprites and restores
+ * the visible and priority data of their background buffers back to
+ * the AGI screen.
+ *
+ * @see erase_nonupd_sprites()
+ * @see erase_both()
*/
-bool SpritesMgr::testUpdating(VtEntry *v, AgiEngine *agi) {
- // Sanity check (see Sarien bug #779302)
- if (~agi->_game.dirView[v->currentView].flags & RES_LOADED)
- return false;
-
- return (v->flags & (fAnimated | fUpdate | fDrawn)) == (fAnimated | fUpdate | fDrawn);
+void SpritesMgr::eraseRegularSprites() {
+ eraseSprites(_spriteRegularList);
}
-/**
- * Condition to determine whether a sprite will be in the 'non-updating' list.
- */
-bool SpritesMgr::testNotUpdating(VtEntry *v, AgiEngine *vm) {
- // Sanity check (see Sarien bug #779302)
- if (~vm->_game.dirView[v->currentView].flags & RES_LOADED)
- return false;
+void SpritesMgr::eraseStaticSprites() {
+ eraseSprites(_spriteStaticList);
+}
- return (v->flags & (fAnimated | fUpdate | fDrawn)) == (fAnimated | fDrawn);
+void SpritesMgr::eraseSprites() {
+ eraseSprites(_spriteRegularList);
+ eraseSprites(_spriteStaticList);
}
/**
- * Convert sprite priority to y value.
+ * Draw all sprites in the given list.
*/
-int SpritesMgr::prioToY(int p) {
- int i;
+void SpritesMgr::drawSprites(SpriteList &spriteList) {
+ SpriteList::iterator iter;
+// warning("drawSprites");
- if (p == 0)
- return -1;
+ for (iter = spriteList.begin(); iter != spriteList.end(); ++iter) {
+ Sprite &sprite = *iter;
+ ScreenObjEntry *screenObj = sprite.screenObjPtr;
- for (i = 167; i >= 0; i--) {
- if (_vm->_game.priTable[i] < p)
- return i;
+ _gfx->block_save(sprite.xPos, sprite.yPos, sprite.xSize, sprite.ySize, sprite.backgroundBuffer);
+ //debugC(8, kDebugLevelSprites, "drawSprites(): s->v->entry = %d (prio %d)", s->viewPtr->entry, s->viewPtr->priority);
+// warning("sprite %d (view %d), priority %d, sort %d, givenOrder %d", screenObj->objectNr, screenObj->currentView, screenObj->priority, sprite.sortOrder, sprite.givenOrderNr);
+ drawCel(screenObj);
}
-
- return -1; // (p - 5) * 12 + 48;
}
/**
- * Create and initialize a new sprite structure.
- */
-Sprite *SpritesMgr::newSprite(VtEntry *v) {
- Sprite *s;
- s = (Sprite *)poolAlloc(sizeof(Sprite));
- if (s == NULL)
- return NULL;
-
- s->v = v; // link sprite to associated view table entry
- s->xPos = v->xPos;
- s->yPos = v->yPos - v->ySize + 1;
- s->xSize = v->xSize;
- s->ySize = v->ySize;
- s->buffer = (uint8 *)poolAlloc(s->xSize * s->ySize);
- v->s = s; // link view table entry to this sprite
-
- return s;
-}
-
-/**
- * Insert sprite in the specified sprite list.
+ * Blit updating sprites.
+ * This function follows the list of all updating sprites and blits
+ * them on the AGI screen.
+ *
+ * @see blit_nonupd_sprites()
+ * @see blit_both()
*/
-void SpritesMgr::sprAddlist(SpriteList &l, VtEntry *v) {
- Sprite *s = newSprite(v);
- l.push_back(s);
-}
+void SpritesMgr::drawRegularSpriteList() {
+ debugC(7, kDebugLevelSprites, "drawRegularSpriteList()");
+ drawSprites(_spriteRegularList);
+}
+
+void SpritesMgr::drawStaticSpriteList() {
+ //debugC(7, kDebugLevelSprites, "drawRegularSpriteList()");
+ drawSprites(_spriteStaticList);
+}
+
+void SpritesMgr::drawAllSpriteLists() {
+ drawSprites(_spriteStaticList);
+ drawSprites(_spriteRegularList);
+}
+
+void SpritesMgr::drawCel(ScreenObjEntry *screenObj) {
+ int16 curX = screenObj->xPos;
+ int16 baseX = screenObj->xPos;
+ int16 curY = screenObj->yPos;
+ AgiViewCel *celPtr = screenObj->celData;
+ byte *celDataPtr = celPtr->rawBitmap;
+ uint8 remainingCelHeight = celPtr->height;
+ uint8 celWidth = celPtr->width;
+ byte celClearKey = celPtr->clearKey;
+ byte viewPriority = screenObj->priority;
+ byte screenPriority = 0;
+ byte curColor = 0;
+ byte isViewHidden = true;
+
+ // Adjust vertical position, given yPos is lower left, but we need upper left
+ curY = curY - celPtr->height + 1;
+
+ while (remainingCelHeight) {
+ for (int16 loopX = 0; loopX < celWidth; loopX++) {
+ curColor = *celDataPtr++;
+
+ if (curColor != celClearKey) {
+ screenPriority = _gfx->getPriority(curX, curY);
+ if (screenPriority <= 2) {
+ // control data found
+ if (_gfx->checkControlPixel(curX, curY, viewPriority)) {
+ _gfx->putPixel(curX, curY, GFX_SCREEN_MASK_VISUAL, curColor, 0);
+ isViewHidden = false;
+ }
+ } else if (screenPriority <= viewPriority) {
+ _gfx->putPixel(curX, curY, GFX_SCREEN_MASK_ALL, curColor, viewPriority);
+ isViewHidden = false;
+ }
-/**
- * Sort sprites from lower y values to build a sprite list.
- */
-void SpritesMgr::buildList(SpriteList &l, bool (*test)(VtEntry *, AgiEngine *)) {
- int i, j, k;
- VtEntry *v;
- VtEntry *entry[0x100];
- int yVal[0x100];
- int minY = 0xff, minIndex = 0;
-
- // fill the arrays with all sprites that satisfy the 'test'
- // condition and their y values
- i = 0;
- for (v = _vm->_game.viewTable; v < &_vm->_game.viewTable[MAX_VIEWTABLE]; v++) {
- if ((*test)(v, _vm)) {
- entry[i] = v;
- yVal[i] = v->flags & fFixedPriority ? prioToY(v->priority) : v->yPos;
- i++;
+ }
+ curX++;
}
+
+ // go to next vertical position
+ remainingCelHeight--;
+ curX = baseX;
+ curY++;
}
- debugC(5, kDebugLevelSprites, "buildList() --> entries %d", i);
+ if (screenObj->objectNr == 0) { // if ego, update if ego is visible at the moment
+ _vm->setflag(VM_FLAG_EGO_INVISIBLE, isViewHidden);
+ }
+}
- // now look for the smallest y value in the array and put that
- // sprite in the list
- for (j = 0; j < i; j++) {
- minY = 0xff;
- for (k = 0; k < i; k++) {
- if (yVal[k] < minY) {
- minIndex = k;
- minY = yVal[k];
- }
- }
+void SpritesMgr::showSprite(ScreenObjEntry *screenObj) {
+ int16 x = 0;
+ int16 y = 0;
+ int16 width = 0;
+ int16 height = 0;
- yVal[minIndex] = 0xff;
- sprAddlist(l, entry[minIndex]);
- }
-}
+ int16 view_height_prev = 0;
+ int16 view_width_prev = 0;
-/**
- * Build list of updating sprites.
- */
-void SpritesMgr::buildUpdBlitlist() {
- buildList(_sprUpd, testUpdating);
-}
+ int16 y2 = 0;
+ int16 height1 = 0;
+ int16 height2 = 0;
-/**
- * Build list of non-updating sprites.
- */
-void SpritesMgr::buildNonupdBlitlist() {
- buildList(_sprNonupd, testNotUpdating);
-}
+ int16 x2 = 0;
+ int16 width1 = 0;
+ int16 width2 = 0;
-/**
- * Clear the given sprite list.
- */
-void SpritesMgr::freeList(SpriteList &l) {
- SpriteList::iterator iter;
- for (iter = l.reverse_begin(); iter != l.end(); ) {
- Sprite* s = *iter;
+ if (!_vm->_game.pictureShown)
+ return;
- poolRelease(s->buffer);
- poolRelease(s);
- iter = l.reverse_erase(iter);
- }
-}
+ view_height_prev = screenObj->ySize_prev;
+ view_width_prev = screenObj->xSize_prev;
-/**
- * Copy sprites from the pic buffer to the screen buffer, and check if
- * sprites of the given list have moved.
- */
-void SpritesMgr::commitSprites(SpriteList &l, bool immediate) {
- SpriteList::iterator iter;
- for (iter = l.begin(); iter != l.end(); ++iter) {
- Sprite *s = *iter;
- int x1, y1, x2, y2;
+ screenObj->ySize_prev = screenObj->ySize;
+ screenObj->xSize_prev = screenObj->xSize;
- x1 = MIN((int)MIN(s->v->xPos, s->v->xPos2), MIN(s->v->xPos + s->v->celData->width, s->v->xPos2 + s->v->celData2->width));
- x2 = MAX((int)MAX(s->v->xPos, s->v->xPos2), MAX(s->v->xPos + s->v->celData->width, s->v->xPos2 + s->v->celData2->width));
- y1 = MIN((int)MIN(s->v->yPos, s->v->yPos2), MIN(s->v->yPos - s->v->celData->height, s->v->yPos2 - s->v->celData2->height));
- y2 = MAX((int)MAX(s->v->yPos, s->v->yPos2), MAX(s->v->yPos - s->v->celData->height, s->v->yPos2 - s->v->celData2->height));
+ if (screenObj->yPos < screenObj->yPos_prev) {
+ y = screenObj->yPos_prev;
+ y2 = screenObj->yPos;
- s->v->celData2 = s->v->celData;
+ height1 = view_height_prev;
+ height2 = screenObj->ySize;
+ } else {
+ y = screenObj->yPos;
+ y2 = screenObj->yPos_prev;
- commitBlock(x1, y1, x2, y2, immediate);
+ height1 = screenObj->ySize;
+ height2 = view_height_prev;
+ }
- if (s->v->stepTimeCount != s->v->stepTime)
- continue;
+ if ((y2 - height2) > (y - height1)) {
+ height = height1;
+ } else {
+ height = y - y2 + height2;
+ }
- if (s->v->xPos == s->v->xPos2 && s->v->yPos == s->v->yPos2) {
- s->v->flags |= fDidntMove;
- continue;
- }
+ if (screenObj->xPos > screenObj->xPos_prev) {
+ x = screenObj->xPos_prev;
+ x2 = screenObj->xPos;
+ width1 = view_width_prev;
+ width2 = screenObj->xSize;
+ } else {
+ x = screenObj->xPos;
+ x2 = screenObj->xPos_prev;
+ width1 = screenObj->xSize;
+ width2 = view_width_prev;
+ }
- s->v->xPos2 = s->v->xPos;
- s->v->yPos2 = s->v->yPos;
- s->v->flags &= ~fDidntMove;
+ if ((x2 + width2) < (x + width1)) {
+ width = width1;
+ } else {
+ width = width2 + x2 - x;
}
-}
-/**
- * Erase all sprites in the given list.
- */
-void SpritesMgr::eraseSprites(SpriteList &l) {
- SpriteList::iterator iter;
- for (iter = l.reverse_begin(); iter != l.end(); --iter) {
- Sprite *s = *iter;
- objsRestoreArea(s);
+ if ((x + width) > 161) {
+ width = 161 - x;
+ }
+
+ if (1 < (height - y)) {
+ height = y + 1;
}
- freeList(l);
+ // render this block
+ _gfx->render_Block(x, y, width, height);
}
-/**
- * Blit all sprites in the given list.
- */
-void SpritesMgr::blitSprites(SpriteList& l) {
- int hidden;
+void SpritesMgr::showSprites(SpriteList &spriteList) {
SpriteList::iterator iter;
+ ScreenObjEntry *screenObjPtr = NULL;
- for (iter = l.begin(); iter != l.end(); ++iter) {
- Sprite *s = *iter;
+ for (iter = spriteList.begin(); iter != spriteList.end(); ++iter) {
+ Sprite &sprite = *iter;
+ screenObjPtr = sprite.screenObjPtr;
- objsSaveArea(s);
- debugC(8, kDebugLevelSprites, "blitSprites(): s->v->entry = %d (prio %d)", s->v->entry, s->v->priority);
- hidden = blitCel(s->xPos, s->yPos, s->v->priority, s->v->celData, s->v->viewData->agi256_2);
+ showSprite(screenObjPtr);
- if (s->v->entry == 0) { // if ego, update f1
- _vm->setflag(fEgoInvisible, hidden);
+ if (screenObjPtr->stepTimeCount == screenObjPtr->stepTime) {
+ if ((screenObjPtr->xPos == screenObjPtr->xPos_prev) && (screenObjPtr->yPos == screenObjPtr->yPos_prev)) {
+ screenObjPtr->flags |= fDidntMove;
+ } else {
+ screenObjPtr->xPos_prev = screenObjPtr->xPos;
+ screenObjPtr->yPos_prev = screenObjPtr->yPos;
+ screenObjPtr->flags &= ~fDidntMove;
+ }
}
}
+ g_system->updateScreen();
+ //g_system->delayMillis(20);
}
-/*
- * Public functions
- */
-
-void SpritesMgr::commitUpdSprites() {
- commitSprites(_sprUpd);
+void SpritesMgr::showRegularSpriteList() {
+ debugC(7, kDebugLevelSprites, "showRegularSpriteList()");
+ showSprites(_spriteRegularList);
}
-void SpritesMgr::commitNonupdSprites() {
- commitSprites(_sprNonupd);
+void SpritesMgr::showStaticSpriteList() {
+ debugC(7, kDebugLevelSprites, "showStaticSpriteList()");
+ showSprites(_spriteStaticList);
}
-// check moves in both lists
-void SpritesMgr::commitBoth() {
- commitUpdSprites();
- commitNonupdSprites();
+void SpritesMgr::showAllSpriteLists() {
+ showSprites(_spriteStaticList);
+ showSprites(_spriteRegularList);
}
/**
- * Erase updating sprites.
- * This function follows the list of all updating sprites and restores
- * the visible and priority data of their background buffers back to
- * the AGI screen.
- *
- * @see erase_nonupd_sprites()
- * @see erase_both()
+ * Show object and description
+ * This function shows an object from the player's inventory, displaying
+ * a message box with the object description.
+ * @param n Number of the object to show
*/
-void SpritesMgr::eraseUpdSprites() {
- eraseSprites(_sprUpd);
-}
+void SpritesMgr::showObject(int16 viewNr) {
+ ScreenObjEntry screenObj;
+ uint8 *backgroundBuffer = NULL;
-/**
- * Erase non-updating sprites.
- * This function follows the list of all non-updating sprites and restores
- * the visible and priority data of their background buffers back to
- * the AGI screen.
- *
- * @see erase_upd_sprites()
- * @see erase_both()
- */
-void SpritesMgr::eraseNonupdSprites() {
- eraseSprites(_sprNonupd);
-}
+ _vm->agiLoadResource(RESOURCETYPE_VIEW, viewNr);
+ _vm->setView(&screenObj, viewNr);
-/**
- * Erase all sprites.
- * This function follows the lists of all updating and non-updating
- * sprites and restores the visible and priority data of their background
- * buffers back to the AGI screen.
- *
- * @see erase_upd_sprites()
- * @see erase_nonupd_sprites()
- */
-void SpritesMgr::eraseBoth() {
- eraseUpdSprites();
- eraseNonupdSprites();
-}
+ screenObj.ySize_prev = screenObj.celData->height;
+ screenObj.xSize_prev = screenObj.celData->width;
+ screenObj.xPos_prev = ((SCRIPT_WIDTH - 1) - screenObj.xSize) / 2;
+ screenObj.xPos = screenObj.xPos_prev;
+ screenObj.yPos_prev = SCRIPT_HEIGHT - 1;
+ screenObj.yPos = screenObj.yPos_prev;
+ screenObj.priority = 15;
+ screenObj.flags |= fFixedPriority;
+ screenObj.objectNr = 255; // ???
-/**
- * Blit updating sprites.
- * This function follows the list of all updating sprites and blits
- * them on the AGI screen.
- *
- * @see blit_nonupd_sprites()
- * @see blit_both()
- */
-void SpritesMgr::blitUpdSprites() {
- debugC(7, kDebugLevelSprites, "blitUpdSprites()");
- buildUpdBlitlist();
- blitSprites(_sprUpd);
-}
+ backgroundBuffer = (uint8 *)malloc(screenObj.xSize * screenObj.ySize * 2); // for visual + priority data
-/**
- * Blit non-updating sprites.
- * This function follows the list of all non-updating sprites and blits
- * them on the AGI screen.
- *
- * @see blit_upd_sprites()
- * @see blit_both()
- */
-void SpritesMgr::blitNonupdSprites() {
- debugC(7, kDebugLevelSprites, "blitNonupdSprites()");
- buildNonupdBlitlist();
- blitSprites(_sprNonupd);
-}
+ _gfx->block_save(screenObj.xPos, (screenObj.yPos - screenObj.ySize + 1), screenObj.xSize, screenObj.ySize, backgroundBuffer);
+ drawCel(&screenObj);
+ showSprite(&screenObj);
-/**
- * Blit all sprites.
- * This function follows the lists of all updating and non-updating
- * sprites and blits them on the AGI screen.
- *
- * @see blit_upd_sprites()
- * @see blit_nonupd_sprites()
- */
-void SpritesMgr::blitBoth() {
- blitNonupdSprites();
- blitUpdSprites();
+ _vm->_text->messageBox((char *)_vm->_game.views[viewNr].description);
+
+ _gfx->block_restore(screenObj.xPos, (screenObj.yPos - screenObj.ySize + 1), screenObj.xSize, screenObj.ySize, backgroundBuffer);
+ showSprite(&screenObj);
+
+ free(backgroundBuffer);
}
/**
@@ -562,188 +424,107 @@ void SpritesMgr::blitBoth() {
* @param pri priority to use
* @param mar if < 4, create a margin around the the base of the cel
*/
-void SpritesMgr::addToPic(int view, int loop, int cel, int x, int y, int pri, int mar) {
- ViewCel *c = NULL;
- int x1, y1, x2, y2, y3;
- uint8 *p1, *p2;
-
- debugC(3, kDebugLevelSprites, "addToPic(view=%d, loop=%d, cel=%d, x=%d, y=%d, pri=%d, mar=%d)", view, loop, cel, x, y, pri, mar);
-
- _vm->recordImageStackCall(ADD_VIEW, view, loop, cel, x, y, pri, mar);
-
- // Was hardcoded to 8, changed to pri_table[y] to fix Gold
- // Rush (see Sarien bug #587558)
- if (pri == 0)
- pri = _vm->_game.priTable[y];
-
- c = &_vm->_game.views[view].loop[loop].cel[cel];
-
- x1 = x;
- y1 = y - c->height + 1;
- x2 = x + c->width - 1;
- y2 = y;
-
- if (x1 < 0) {
- x2 -= x1;
- x1 = 0;
- }
- if (y1 < 0) {
- y2 -= y1;
- y1 = 0;
+void SpritesMgr::addToPic(int16 viewNr, int16 loopNr, int16 celNr, int16 xPos, int16 yPos, int16 priority, int16 border) {
+ debugC(3, kDebugLevelSprites, "addToPic(view=%d, loop=%d, cel=%d, x=%d, y=%d, pri=%d, border=%d)", viewNr, loopNr, celNr, xPos, yPos, priority, border);
+
+ _vm->recordImageStackCall(ADD_VIEW, viewNr, loopNr, celNr, xPos, yPos, priority, border);
+
+ ScreenObjEntry *screenObj = &_vm->_game.addToPicView;
+ screenObj->objectNr = -1; // addToPic-view
+
+ _vm->setView(screenObj, viewNr);
+ _vm->setLoop(screenObj, loopNr);
+ _vm->setCel(screenObj, celNr);
+
+ screenObj->xSize_prev = screenObj->xSize;
+ screenObj->ySize_prev = screenObj->ySize;
+ screenObj->xPos_prev = xPos;
+ screenObj->xPos = xPos;
+ screenObj->yPos_prev = yPos;
+ screenObj->yPos = yPos;
+ screenObj->flags = fIgnoreObjects | fIgnoreHorizon | fFixedPriority;
+ screenObj->priority = 15;
+ _vm->fixPosition(screenObj);
+ if (priority == 0) {
+ screenObj->flags = fIgnoreHorizon;
}
- if (x2 >= _WIDTH)
- x2 = _WIDTH - 1;
- if (y2 >= _HEIGHT)
- y2 = _HEIGHT - 1;
-
- eraseBoth();
-
- debugC(4, kDebugLevelSprites, "blitCel(%d, %d, %d, c)", x, y, pri);
- blitCel(x1, y1, pri, c, _vm->_game.views[view].agi256_2);
-
- // If margin is 0, 1, 2, or 3, the base of the cel is
- // surrounded with a rectangle of the corresponding priority.
- // If margin >= 4, this extra margin is not shown.
- //
- // -1 indicates ignore and is set for V1
- if (mar < 4 && mar != -1) {
- // add rectangle around object, don't clobber control
- // info in priority data. The box extends to the end of
- // its priority band!
- y3 = (y2 / 12) * 12;
-
- // SQ1 needs +1 (see Sarien bug #810331)
- if (_vm->getGameID() == GID_SQ1)
- y3++;
-
- // don't let box extend below y.
- if (y3 > y2) y3 = y2;
-
- p1 = &_vm->_game.sbuf16c[x1 + y3 * _WIDTH];
- p2 = &_vm->_game.sbuf16c[x2 + y3 * _WIDTH];
-
- for (y = y3; y <= y2; y++) {
- if ((*p1 >> 4) >= 4)
- *p1 = (mar << 4) | (*p1 & 0x0f);
-
- if ((*p2 >> 4) >= 4)
- *p2 = (mar << 4) | (*p2 & 0x0f);
-
- p1 += _WIDTH;
- p2 += _WIDTH;
- }
-
- debugC(4, kDebugLevelSprites, "pri box: %d %d %d %d (%d)", x1, y3, x2, y2, mar);
- p1 = &_vm->_game.sbuf16c[x1 + y3 * _WIDTH];
- p2 = &_vm->_game.sbuf16c[x1 + y2 * _WIDTH];
- for (x = x1; x <= x2; x++) {
- if ((*p1 >> 4) >= 4)
- *p1 = (mar << 4) | (*p1 & 0x0f);
+ screenObj->priority = priority;
- if ((*p2 >> 4) >= 4)
- *p2 = (mar << 4) | (*p2 & 0x0f);
-
- p1++;
- p2++;
- }
+ eraseSprites();
+
+ // bugs related to this code: required by Gold Rush (see Sarien bug #587558)
+ if (screenObj->priority == 0) {
+ screenObj->priority = _gfx->priorityFromY(screenObj->yPos);
}
+ drawCel(screenObj);
- blitBoth();
-
- commitBlock(x1, y1, x2, y2, true);
-}
-
-/**
- * Show object and description
- * This function shows an object from the player's inventory, displaying
- * a message box with the object description.
- * @param n Number of the object to show
- */
-void SpritesMgr::showObj(int n) {
- ViewCel *c;
- Sprite s;
- int x1, y1, x2, y2;
-
- _vm->agiLoadResource(rVIEW, n);
- if (!(c = &_vm->_game.views[n].loop[0].cel[0]))
- return;
-
- x1 = (_WIDTH - c->width) / 2;
- y1 = 112;
- x2 = x1 + c->width - 1;
- y2 = y1 + c->height - 1;
-
- s.xPos = x1;
- s.yPos = y1;
- s.xSize = c->width;
- s.ySize = c->height;
- s.buffer = (uint8 *)malloc(s.xSize * s.ySize);
- s.v = 0;
-
- objsSaveArea(&s);
- blitCel(x1, y1, 15, c, _vm->_game.views[n].agi256_2);
- commitBlock(x1, y1, x2, y2, true);
- _vm->messageBox(_vm->_game.views[n].descr);
- objsRestoreArea(&s);
- commitBlock(x1, y1, x2, y2, true);
-
- free(s.buffer);
-}
-
-void SpritesMgr::commitBlock(int x1, int y1, int x2, int y2, bool immediate) {
- int i, w, offset;
- uint8 *q;
-
- if (!_vm->_game.pictureShown)
- return;
+ if (border <= 3) {
+ // Create priority-box
+ addToPicDrawPriorityBox(screenObj, border);
+ }
+ buildAllSpriteLists();
+ drawAllSpriteLists();
+ showSprite(screenObj);
+}
+
+// bugs previously related to this:
+// Sarien bug #247)
+void SpritesMgr::addToPicDrawPriorityBox(ScreenObjEntry *screenObj, int16 border) {
+ int16 priorityFromY = _gfx->priorityFromY(screenObj->yPos);
+ int16 priorityHeight = 0;
+ int16 curY = 0;
+ int16 curX = 0;
+ int16 height = 0;
+ int16 width = 0;
+ int16 offsetX = 0;
+
+ // Figure out the height of the box
+ curY = screenObj->yPos;
+ do {
+ priorityHeight++;
+ if (curY <= 0)
+ break;
+ curY--;
+ } while (_gfx->priorityFromY(curY) == priorityFromY);
+
+ // box height may not be larger than the actual view
+ if (screenObj->ySize < priorityHeight)
+ priorityHeight = screenObj->ySize;
+
+ // now actually draw lower horizontal line
+ curY = screenObj->yPos;
+ curX = screenObj->xPos;
+
+ width = screenObj->xSize;
+ while (width) {
+ _gfx->putPixel(curX, curY, GFX_SCREEN_MASK_PRIORITY, 0, border);
+ curX++;
+ width--;
+ }
- x1 = CLIP(x1, 0, _WIDTH - 1);
- x2 = CLIP(x2, 0, _WIDTH - 1);
- y1 = CLIP(y1, 0, _HEIGHT - 1);
- y2 = CLIP(y2, 0, _HEIGHT - 1);
-
- // Check if a window is active, and clip the block commited to exclude the
- // window's contents. Fixes bug #3295652, and partially fixes bug #3080415.
- AgiBlock &window = _vm->_game.window;
- if (window.active) {
- if (y1 < window.y2 && y2 > window.y2 && (x1 < window.x2 || x2 > window.x1)) {
- // The top of the block covers the bottom of the window
- y1 = window.y2;
+ if (priorityHeight > 1) {
+ // Actual rectangle is needed
+ curY = screenObj->yPos;
+ curX = screenObj->xPos;
+ offsetX = screenObj->xSize - 1;
+
+ height = priorityHeight - 1;
+ while (height) {
+ curY--;
+ height--;
+ _gfx->putPixel(curX, curY, GFX_SCREEN_MASK_PRIORITY, 0, border); // left line
+ _gfx->putPixel(curX + offsetX, curY, GFX_SCREEN_MASK_PRIORITY, 0, border); // right line
}
- if (y1 < window.y1 && y2 > window.y1 && (x1 < window.x2 || x2 > window.x1)) {
- // The bottom of the block covers the top of the window
- y2 = window.y1;
+ // and finally the upper horizontal line
+ width = screenObj->xSize - 2;
+ curX++;
+ while (width > 0) {
+ _gfx->putPixel(curX, curY, GFX_SCREEN_MASK_PRIORITY, 0, border);
+ curX++;
+ width--;
}
}
-
- debugC(7, kDebugLevelSprites, "commitBlock(%d, %d, %d, %d)", x1, y1, x2, y2);
-
- w = x2 - x1 + 1;
- q = &_vm->_game.sbuf16c[x1 + _WIDTH * y1];
- offset = _vm->_game.lineMinPrint * CHAR_LINES;
-
- for (i = y1; i <= y2; i++) {
- _gfx->putPixelsA(x1, i + offset, w, q);
- q += _WIDTH;
- }
-
- _gfx->flushBlockA(x1, y1 + offset, x2, y2 + offset);
-
- if (immediate)
- _gfx->doUpdate();
-}
-
-SpritesMgr::SpritesMgr(AgiEngine *agi, GfxMgr *gfx) {
- _vm = agi;
- _gfx = gfx;
-
- _spritePool = (uint8 *)malloc(POOL_SIZE);
- _poolTop = _spritePool;
-}
-
-SpritesMgr::~SpritesMgr() {
- free(_spritePool);
}
} // End of namespace Agi
diff --git a/engines/agi/sprite.h b/engines/agi/sprite.h
index 549eb59832..0bb0547650 100644
--- a/engines/agi/sprite.h
+++ b/engines/agi/sprite.h
@@ -25,9 +25,25 @@
namespace Agi {
+/**
+ * Sprite structure.
+ * This structure holds information on visible and priority data of
+ * a rectangular area of the AGI screen. Sprites are chained in two
+ * circular lists, one for updating and other for non-updating sprites.
+ */
+struct Sprite {
+ uint16 givenOrderNr;
+ uint16 sortOrder;
+ ScreenObjEntry *screenObjPtr; /**< pointer to view table entry */
+ int16 xPos; /**< x coordinate of the sprite */
+ int16 yPos; /**< y coordinate of the sprite */
+ int16 xSize; /**< width of the sprite */
+ int16 ySize; /**< height of the sprite */
+ byte *backgroundBuffer; /**< buffer to store background data */
+};
-struct Sprite;
-typedef Common::List<Sprite *> SpriteList;
+typedef Common::List<Sprite> SpriteList;
+typedef Common::List<Sprite *> SpritePtrList;
class AgiEngine;
class GfxMgr;
@@ -38,35 +54,42 @@ private:
GfxMgr *_gfx;
AgiEngine *_vm;
- uint8 *_spritePool;
- uint8 *_poolTop;
-
//
// Sprite management functions
//
- SpriteList _sprUpd;
- SpriteList _sprNonupd;
-
- void *poolAlloc(int size);
- void poolRelease(void *s);
- void blitPixel(uint8 *p, uint8 *end, uint8 col, int spr, int width, int *hidden);
- int blitCel(int x, int y, int spr, ViewCel *c, bool agi256_2);
- void objsSaveArea(Sprite *s);
- void objsRestoreArea(Sprite *s);
-
- int prioToY(int p);
- Sprite *newSprite(VtEntry *v);
- void sprAddlist(SpriteList &l, VtEntry *v);
- void buildList(SpriteList &l, bool (*test)(VtEntry *, AgiEngine *));
- void buildUpdBlitlist();
- void buildNonupdBlitlist();
- void freeList(SpriteList &l);
- void commitSprites(SpriteList &l, bool immediate = false);
- void eraseSprites(SpriteList &l);
- void blitSprites(SpriteList &l);
- static bool testUpdating(VtEntry *v, AgiEngine *);
- static bool testNotUpdating(VtEntry *v, AgiEngine *);
+ SpriteList _spriteRegularList;
+ SpriteList _spriteStaticList;
+
+public:
+ void buildRegularSpriteList();
+ void buildStaticSpriteList();
+ void buildAllSpriteLists();
+ void buildSpriteListAdd(uint16 givenOrderNr, ScreenObjEntry *screenObj, SpriteList &spriteList);
+ void freeList(SpriteList &spriteList);
+ void freeRegularSprites();
+ void freeStaticSprites();
+ void freeAllSprites();
+
+ void eraseSprites(SpriteList &spriteList);
+ void eraseRegularSprites();
+ void eraseStaticSprites();
+ void eraseSprites();
+
+ void drawSprites(SpriteList &spriteList);
+ void drawRegularSpriteList();
+ void drawStaticSpriteList();
+ void drawAllSpriteLists();
+
+ void drawCel(ScreenObjEntry *screenObj);
+
+ void showSprite(ScreenObjEntry *screenObj);
+ void showSprites(SpriteList &spriteList);
+ void showRegularSpriteList();
+ void showStaticSpriteList();
+ void showAllSpriteLists();
+
+ void showObject(int16 viewNr);
public:
SpritesMgr(AgiEngine *agi, GfxMgr *gfx);
@@ -74,18 +97,8 @@ public:
int initSprites();
void deinitSprites();
- void eraseUpdSprites();
- void eraseNonupdSprites();
- void eraseBoth();
- void blitUpdSprites();
- void blitNonupdSprites();
- void blitBoth();
- void commitUpdSprites();
- void commitNonupdSprites();
- void commitBoth();
- void addToPic(int, int, int, int, int, int, int);
- void showObj(int);
- void commitBlock(int x1, int y1, int x2, int y2, bool immediate = false);
+ void addToPic(int16 viewNr, int16 loopNr, int16 celNr, int16 xPos, int16 yPos, int16 priority, int16 border);
+ void addToPicDrawPriorityBox(ScreenObjEntry *screenObj, int16 border);
};
} // End of namespace Agi
diff --git a/engines/agi/systemui.cpp b/engines/agi/systemui.cpp
new file mode 100644
index 0000000000..dc83c4d393
--- /dev/null
+++ b/engines/agi/systemui.cpp
@@ -0,0 +1,661 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "agi/agi.h"
+#include "agi/graphics.h"
+#include "agi/text.h"
+#include "agi/keyboard.h"
+#include "agi/systemui.h"
+
+namespace Agi {
+
+SystemUI::SystemUI(AgiEngine *vm, GfxMgr *gfx, TextMgr *text) {
+ _vm = vm;
+ _gfx = gfx;
+ _text = text;
+
+ _textStatusScore = "Score:%v3 of %v7";
+ _textStatusSoundOn = "Sound:on";
+ _textStatusSoundOff = "Sound:off";
+
+ _textPause = " Game paused.\nPress Enter to continue.";
+ _textRestart = "Press ENTER to restart\nthe game.\n\nPress ESC to continue\nthis game.";
+ _textQuit = "Press ENTER to quit.\nPress ESC to keep playing.";
+
+ _textInventoryYouAreCarrying = "You are carrying:";
+ _textInventoryNothing = "nothing";
+ _textInventorySelectItems = "Press ENTER to select, ESC to cancel";
+ _textInventoryReturnToGame = "Press a key to return to the game";
+
+ _textSaveGameSelectSlot = "Use the arrow keys to select the slot in which you wish to save the game. Press ENTER to save in the slot, ESC to not save a game.";
+ _textSaveGameEnterDescription = "How would you like to describe this saved game?\n\n";
+ _textSaveGameVerify = "About to save the game\ndescribed as:\n\n%s\n\nPress ENTER to continue.\nPress ESC to cancel.";
+
+ _textRestoreGameNoSlots = "There are no games to\nrestore in\n\n ScummVM saved game directory\n\nPress ENTER to continue.";
+ _textRestoreGameSelectSlot = "Use the arrow keys to select the game which you wish to restore. Press ENTER to restore the game, ESC to not restore a game.";
+ _textRestoreGameError = "Error in restoring game.\nPress ENTER to quit.";
+ _textRestoreGameVerify = "About to restore the game\ndescribed as:\n\n%s\n\nPress ENTER to continue.\nPress ESC to cancel.";
+
+ // Replace with translated text, when needed
+ switch (_vm->getLanguage()) {
+ case Common::RU_RUS:
+ _textStatusScore = "\x91\xE7\xA5\xE2: %v3 \xA8\xA7 %v7";
+ _textStatusSoundOn = "\x87\xA2\xE3\xAA: \xA2\xAA\xAB";
+ _textStatusSoundOff = "\x87\xA2\xE3\xAA: \xA2\xEB\xAA\xAB";
+
+ _textPause = " \x88\xA3\xE0\xA0 \xAE\xE1\xE2\xA0\xAD\xAE\xA2\xAB\xA5\xAD\xA0\nENTER - \xAF\xE0\xAE\xA4\xAE\xAB\xA6\xA5\xAD\xA8\xA5.";
+ //_textPause = " \x88\xa3\xe0\xa0 \xae\xe1\xe2\xa0\xad\xae\xa2\xab\xa5\xad\xa0. \n\n\n"; <- mouse
+ // pause button text "\x8f\xe0\xae\xa4\xae\xab\xa6\xa8\xe2\xec"
+ _textRestart = "ENTER - \xAF\xA5\xE0\xA5\xA7\xA0\xAF\xE3\xE1\xE2\xA8\xE2\xEC \xA8\xA3\xE0\xE3.\n\nESC - \xAF\xE0\xAE\xA4\xAE\xAB\xA6\xA5\xAD\xA8\xA5 \xA8\xA3\xE0\xEB.";
+ _textQuit = "ENTER-\xA2\xEB\xE5\xAE\xA4 \xA8\xA7 \xA8\xA3\xE0\xEB.\nESC - \xA8\xA3\xE0\xA0\xE2\xEC \xA4\xA0\xAB\xEC\xE8\xA5.";
+
+ _textInventoryYouAreCarrying = " \x93 \xA2\xA0\xE1 \xA5\xE1\xE2\xEC\x3A ";
+ _textInventoryNothing = "\xAD\xA8\xE7\xA5\xA3\xAE";
+ _textInventorySelectItems = "ENTER - \xA2\xEB\xA1\xAE\xE0, ESC - \xAE\xE2\xAC\xA5\xAD\xA0.";
+ _textInventoryReturnToGame = "\x8B\xEE\xA1\xA0\xEF \xAA\xAB\xA0\xA2\xA8\xE8\xA0 - \xA2\xAE\xA7\xA2\xE0\xA0\xE2 \xA2 \xA8\xA3\xE0\xE3";
+
+ _textSaveGameSelectSlot ="\x91 \xAF\xAE\xAC\xAE\xE9\xEC\xEE \xAA\xAB\xA0\xA2\xA8\xE8 \xAA\xE3\xE0\xE1\xAE\xE0\xA0 \xA2\xEB\xA1\xA5\xE0\xA8\xE2\xA5 \xE1\xE2\xE0\xAE\xAA\xE3, \xA2 \xAA\xAE\xE2\xAE\xE0\xE3\xEE \xA2\xEB \xA6\xA5\xAB\xA0\xA5\xE2\xA5 \xA7\xA0\xAF\xA8\xE1\xA0\xE2\xEC \xA8\xA3\xE0\xE3. \x8D\xA0\xA6\xAC\xA8\xE2\xA5 ENTER \xA4\xAB\xEF \xA7\xA0\xAF\xA8\xE1\xA8 \xA8\xA3\xE0\xEB, ESC - \xAE\xE2\xAC\xA5\xAD\xA0 \xA7\xA0\xAF\xA8\xE1\xA8.";
+ _textSaveGameEnterDescription = "\x8A\xA0\xAA \xA2\xEB \xA6\xA5\xAB\xA0\xA5\xE2\xA5 \xAD\xA0\xA7\xA2\xA0\xE2\xEC \xED\xE2\xE3 \xA7\xA0\xAF\xA8\xE1\xEB\xA2\xA0\xA5\xAC\xE3\xEE \xA8\xA3\xE0\xE3?\n\n";
+ _textSaveGameVerify = "\x83\xAE\xE2\xAE\xA2 \xAA \xA7\xA0\xAF\xA8\xE1\xA8 \xA8\xA3\xE0\xEB, \n\xAE\xAF\xA8\xE1\xA0\xAD\xAD\xAE\xA9 \xAA\xA0\xAA:\n\n%s\n\n\x84\xAB\xEF \xAF\xE0\xAE\xA4\xAE\xAB\xA6\xA5\xAD\xA8\xEF \xAD\xA0\xA6\xAC\xA8\xE2\xA5 ENTER.\nESC - \xAE\xE2\xAC\xA5\xAD\xA0.";
+
+ _textRestoreGameNoSlots ="\x82 \xAA\xA0\xE2\xA0\xAB\xAE\xA3\xA5\n\n ScummVM saved game directory\n\n\xAD\xA5\xE2 \xA7\xA0\xAF\xA8\xE1\xA0\xAD\xAD\xEB\xE5 \xA8\xA3\xE0.\n\nENTER - \xAF\xE0\xAE\xA4\xAE\xAB\xA6\xA5\xAD\xA8\xA5.";
+ _textRestoreGameSelectSlot = "\x91 \xAF\xAE\xAC\xAE\xE9\xEC\xEE \xAA\xAB\xA0\xA2\xA8\xE8 \xAA\xE3\xE0\xE1\xAE\xE0\xA0 \xA2\xEB\xA1\xA5\xE0\xA8\xE2\xA5 \xA8\xA3\xE0\xE3, \xAA\xAE\xE2\xAE\xE0\xE3\xEE \xA2\xEB \xA6\xA5\xAB\xA0\xA5\xE2\xA5 \xE1\xE7\xA8\xE2\xA0\xE2\xEC. \x8D\xA0\xA6\xAC\xA8\xE2\xA5 ENTER \xA4\xAB\xEF \xE1\xE7\xA8\xE2\xEB\xA2\xA0\xAD\xA8\xEF \xA8\xA3\xE0\xEB, ESC - \xA4\xAB\xEF \xAE\xE2\xAC\xA5\xAD\xEB.";
+ _textRestoreGameError ="\x8E\xE8\xA8\xA1\xAA\xA0 \xA2 \xA7\xA0\xAF\xA8\xE1\xA0\xAD\xAD\xAE\xA9 \xA8\xA3\xE0\xA5.\nENTER - \xA2\xEB\xE5\xAE\xA4.";
+ _textRestoreGameVerify = "\x83\xAE\xE2\xAE\xA2 \xAA \xE1\xE7\xA8\xE2\xEB\xA2\xA0\xAD\xA8\xEE \xA8\xA3\xE0\xEB\x2C\n\xAE\xAF\xA8\xE1\xA0\xAD\xAD\xAE\xA9 \xAA\xA0\xAA.\n\n%s\n\n\x84\xAB\xEF \xAF\xE0\xAE\xA4\xAE\xAB\xA6\xA5\xAD\xA8\xEF \xAD\xA0\xA6\xAC\xA8\xE2\xA5 ENTER.\nESC - \xAE\xE2\xAC\xA5\xAD\xA0.";
+
+//Press ENTER to continue...
+//"\x84\xAB\xEF \xAF\xE0\xAE\xA4\xAE\xAB\xA6\xA5\xAD\xA8\xEF \xAD\xA0\xA6\xAC\xA8\xE2\xA5 ENTER\nESC - \xAE\xE2\xAC\xA5\xAD\xA0.";
+
+// nothing inventory
+// "\xAD\xA8\xE7\xA5\xA3\xAE
+
+// you are carrying
+// " \x93 \xA2\xA0\xE1 \xA5\xE1\xE2\xEC\x3A "
+
+// Press enter to select
+// "ENTER - \xA2\xEB\xA1\xAE\xE0, ESC - \xAE\xE2\xAC\xA5\xAD\xA0.";
+
+// Press a key to return
+// "\x8B\xEE\xA1\xA0\xEF \xAA\xAB\xA0\xA2\xA8\xE8\xA0 - \xA2\xAE\xA7\xA2\xE0\xA0\xE2 \xA2 \xA8\xA3\xE0\xE3";
+
+// Score %d %d
+// "\x91\xE7\xA5\xE2\x3A %d \xA8\xA7 %d "
+
+// Sound: on/off
+// "\x87\xA2\xE3\xAA\x3A\xA2%s"
+// "\xAA\xAB "
+// "\xEB\xAA\xAB"
+
+
+ break;
+ default:
+ break;
+ }
+}
+
+SystemUI::~SystemUI() {
+ clearSavedGameSlots();
+}
+
+const char *SystemUI::getStatusTextScore() {
+ return _textStatusScore;
+}
+const char *SystemUI::getStatusTextSoundOn() {
+ return _textStatusSoundOn;
+}
+const char *SystemUI::getStatusTextSoundOff() {
+ return _textStatusSoundOff;
+}
+
+void SystemUI::pauseDialog() {
+ _vm->_text->messageBox(_textPause);
+}
+
+bool SystemUI::restartDialog() {
+ return _vm->_text->messageBox(_textRestart);
+}
+
+bool SystemUI::quitDialog() {
+ return _vm->_text->messageBox(_textQuit);
+}
+
+const char *SystemUI::getInventoryTextNothing() {
+ return _textInventoryNothing;
+}
+const char *SystemUI::getInventoryTextYouAreCarrying() {
+ return _textInventoryYouAreCarrying;
+}
+const char *SystemUI::getInventoryTextSelectItems() {
+ return _textInventorySelectItems;
+}
+const char *SystemUI::getInventoryTextReturnToGame() {
+ return _textInventoryReturnToGame;
+}
+
+int16 SystemUI::figureOutAutomaticSaveGameSlot(const char *automaticSaveDescription) {
+ int16 matchedGameSlotId = -1;
+ int16 freshGameSlotId = -1;
+
+ // Fill saved game slot cache
+ readSavedGameSlots(false, false); // don't filter, but also don't include auto-save slot
+
+ // Walk through saved game slots
+ // check, if description matches and return the slot
+ // if no match can be found, return the first non-existant slot
+ // if all slots exist, return -1
+ figureOutAutomaticSavedGameSlot(automaticSaveDescription, matchedGameSlotId, freshGameSlotId);
+
+ if (matchedGameSlotId >= 0)
+ return matchedGameSlotId; // return matched slot
+
+ if (freshGameSlotId >= 0)
+ return freshGameSlotId; // return first non-existant slot
+
+ return -1; // no slots exist, not match found
+}
+
+int16 SystemUI::figureOutAutomaticRestoreGameSlot(const char *automaticSaveDescription) {
+ int16 matchedGameSlotId = -1;
+ int16 freshGameSlotId = -1;
+
+ // Fill saved game slot cache
+ readSavedGameSlots(true, false); // filter non-existant/invalid saves, also don't include auto-save slot
+
+ // Walk through saved game slots
+ // check, if description matches and return the slot. Otherwise return -1
+ figureOutAutomaticSavedGameSlot(automaticSaveDescription, matchedGameSlotId, freshGameSlotId);
+
+ if (matchedGameSlotId >= 0)
+ return matchedGameSlotId; // return matched slot
+ return -1; // no match found
+}
+
+int16 SystemUI::askForSaveGameSlot() {
+ int16 saveGameSlotNr = -1;
+
+ // Fill saved game slot cache
+ readSavedGameSlots(false, false); // don't filter, but also don't include auto-save slot
+
+ saveGameSlotNr = askForSavedGameSlot(_textSaveGameSelectSlot);
+
+ if (saveGameSlotNr < 0) {
+ // User cancelled? exit now
+ return -1;
+ }
+
+ // return actual slot number of the saved game
+ return _savedGameArray[saveGameSlotNr].slotId;
+}
+
+bool SystemUI::askForSaveGameDescription(int16 slotId, Common::String &newDescription) {
+ // Let user enter new description
+ bool previousEditState = _text->inputGetEditStatus();
+ byte previousEditCursor = _text->inputGetCursorChar();
+
+ _text->drawMessageBox(_textSaveGameEnterDescription, 0, 31, true);
+
+ _text->charPos_Push();
+ _text->charAttrib_Push();
+ _text->inputEditOn();
+
+ _text->charPos_SetInsideWindow(3, 0);
+ _text->charAttrib_Set(15, 0);
+ _text->clearBlockInsideWindow(3, 0, 31, 0); // input line is supposed to be black
+ _text->inputSetCursorChar('_');
+
+ // figure out the current description of the slot
+ _text->stringSet("");
+ for (uint16 slotNr = 0; slotNr < _savedGameArray.size(); slotNr++) {
+ if (_savedGameArray[slotNr].slotId == slotId) {
+ // found slotId
+ if (_savedGameArray[slotNr].isValid) {
+ // and also valid, so use its description
+ _text->stringSet(_savedGameArray[slotNr].description);
+ }
+ }
+ }
+
+ _vm->cycleInnerLoopActive(CYCLE_INNERLOOP_GETSTRING);
+ _text->stringEdit(30); // only allow up to 30 characters
+
+ _text->charAttrib_Pop();
+ _text->charPos_Pop();
+ _text->inputSetCursorChar(previousEditCursor);
+ if (!previousEditState) {
+ _text->inputEditOff();
+ }
+
+ _text->closeWindow();
+
+ if (!_text->stringWasEntered()) {
+ // User cancelled? exit now
+ return false;
+ }
+
+ // Now verify that the user really wants to do this
+ char userInput[SYSTEMUI_SAVEDGAME_DISPLAYTEXT_LEN + 1];
+
+ createSavedGameDisplayText(userInput, (char *)_text->_inputString, slotId);
+
+ if (!askForSavedGameVerification(_textSaveGameVerify, userInput)) {
+ return false;
+ }
+
+ newDescription.clear();
+ newDescription += (char *)_text->_inputString;
+ return true;
+}
+
+int16 SystemUI::askForRestoreGameSlot() {
+ int16 restoreGameSlotNr = -1;
+
+ // Fill saved game slot cache
+ readSavedGameSlots(true, true); // filter empty/corrupt slots, but including auto-save slot
+
+ if (_savedGameArray.size() == 0) {
+ // no saved games
+ _vm->_text->messageBox(_textRestoreGameNoSlots);
+ return -1;
+ }
+
+ restoreGameSlotNr = askForSavedGameSlot(_textRestoreGameSelectSlot);
+
+ // User cancelled? exit now
+ if (restoreGameSlotNr < 0)
+ return -1;
+
+ // Check, if selected saved game was marked as valid
+ if (!_savedGameArray[restoreGameSlotNr].isValid) {
+ _vm->_text->messageBox(_textRestoreGameError);
+ return -1;
+ }
+
+ // Now ask user about this specific saved game
+ char userActionVerify[200];
+
+ sprintf(userActionVerify, _textRestoreGameVerify, _savedGameArray[restoreGameSlotNr].displayText);
+ if (!_vm->_text->messageBox(userActionVerify)) {
+ return -1;
+ }
+ // return actual slot number of the saved game
+ return _savedGameArray[restoreGameSlotNr].slotId;
+}
+
+int16 SystemUI::askForSavedGameSlot(const char *slotListText) {
+ int16 messageBoxHeight = 0;
+ int16 slotsCount = _savedGameArray.size();
+
+ if (slotsCount > SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN) {
+ messageBoxHeight = 5 + SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN;
+ } else {
+ messageBoxHeight = 5 + slotsCount;
+ }
+ _text->drawMessageBox(slotListText, messageBoxHeight, 34, true);
+
+ drawSavedGameSlots();
+ drawSavedGameSlotSelector(true);
+
+ _vm->cycleInnerLoopActive(CYCLE_INNERLOOP_SYSTEMUI_SELECTSAVEDGAMESLOT);
+ do {
+ _vm->mainCycle();
+ } while (_vm->cycleInnerLoopIsActive() && !(_vm->shouldQuit() || _vm->_restartGame));
+
+ _text->closeWindow();
+
+ return _savedGameSelectedSlotNr;
+}
+
+void SystemUI::savedGameSlot_CharPress(int16 newChar) {
+ int16 slotCount = _savedGameArray.size();
+ int16 newUpmostSlotNr = _savedGameUpmostSlotNr;
+ int16 newSelectedSlotNr = _savedGameSelectedSlotNr;
+ bool slotsScrolled = false;
+
+ switch (newChar) {
+ case AGI_KEY_ENTER:
+ _vm->cycleInnerLoopInactive(); // exit savedGameSlot-loop
+ return;
+ break;
+
+ case AGI_KEY_ESCAPE:
+ _savedGameSelectedSlotNr = -1;
+ _vm->cycleInnerLoopInactive(); // exit savedGameSlot-loop
+ return;
+ break;
+ case AGI_KEY_UP: // previous slot
+ newSelectedSlotNr--;
+ break;
+ case AGI_KEY_DOWN: // next slot
+ newSelectedSlotNr++;
+ break;
+ // FEATURE: any scroll functionality was not available in original AGI
+ // Original AGI was in fact limited to a total of 12 save slots
+ case AGI_KEY_PAGE_UP: // scroll up
+ newUpmostSlotNr -= SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN;
+ break;
+ case AGI_KEY_PAGE_DOWN: // scroll down
+ newUpmostSlotNr += SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN;
+ break;
+ case AGI_KEY_HOME: // scroll all the way up
+ newUpmostSlotNr = 0;
+ break;
+ case AGI_KEY_END: // scroll all the way down
+ newUpmostSlotNr = (slotCount - 1) - (SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN - 1);
+ break;
+
+ default:
+ break;
+ }
+
+ if (newUpmostSlotNr != _savedGameUpmostSlotNr) {
+ // Make sure, upmost slot number is valid
+ if (newUpmostSlotNr < 0) {
+ newUpmostSlotNr = 0;
+ }
+ if (newUpmostSlotNr + (SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN - 1) >= slotCount) {
+ newUpmostSlotNr = (slotCount - 1) - (SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN - 1);
+ if (newUpmostSlotNr < 0)
+ newUpmostSlotNr = 0;
+ }
+
+ if (newUpmostSlotNr != _savedGameUpmostSlotNr) {
+ // Still different? then actually force a slot number change in any case
+ slotsScrolled = true;
+
+ // also adjust selected slot number now
+ int16 slotDifference = _savedGameSelectedSlotNr - _savedGameUpmostSlotNr;
+ newSelectedSlotNr = newUpmostSlotNr + slotDifference;
+ }
+ }
+
+ if ((newSelectedSlotNr != _savedGameSelectedSlotNr) || slotsScrolled) {
+ // slot number was changed
+
+ // Make slot number valid and scroll in case it's needed
+ if (newSelectedSlotNr < 0) {
+ newSelectedSlotNr = slotCount - 1;
+ newUpmostSlotNr = newSelectedSlotNr - SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN;
+ if (newUpmostSlotNr < 0)
+ newUpmostSlotNr = 0;
+ }
+ if (newSelectedSlotNr >= slotCount) {
+ newSelectedSlotNr = 0;
+ newUpmostSlotNr = 0;
+ }
+
+ if (newSelectedSlotNr < newUpmostSlotNr) {
+ // scroll up when needed
+ newUpmostSlotNr = newSelectedSlotNr;
+ }
+
+ if (newSelectedSlotNr >= newUpmostSlotNr + SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN) {
+ // scroll down when needed
+ newUpmostSlotNr = newSelectedSlotNr - (SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN - 1);
+ }
+
+ bool drawSlots = false;
+
+ // remove selector
+ drawSavedGameSlotSelector(false);
+
+ if (newUpmostSlotNr != _savedGameUpmostSlotNr) {
+ drawSlots = true;
+ }
+
+ _savedGameUpmostSlotNr = newUpmostSlotNr;
+ _savedGameSelectedSlotNr = newSelectedSlotNr;
+ if (drawSlots) {
+ drawSavedGameSlots();
+ }
+ drawSavedGameSlotSelector(true);
+ }
+}
+
+void SystemUI::clearSavedGameSlots() {
+ _savedGameArray.clear();
+ _savedGameUpmostSlotNr = 0;
+ _savedGameSelectedSlotNr = 0;
+}
+
+void SystemUI::createSavedGameDisplayText(char *destDisplayText, const char *actualDescription, int16 slotId) {
+ char slotIdChar[3];
+ int16 actualDescriptionLen = 0;
+
+ // clear with spaces
+ memset(destDisplayText, ' ', SYSTEMUI_SAVEDGAME_DISPLAYTEXT_LEN);
+
+ // create fixed prefix (" 1:", "10:", etc.)
+ sprintf(slotIdChar, "%02d", slotId);
+ memcpy(destDisplayText, slotIdChar, 2);
+ destDisplayText[2] = ':';
+
+ actualDescriptionLen = strlen(actualDescription);
+ if (actualDescriptionLen > (SYSTEMUI_SAVEDGAME_DISPLAYTEXT_LEN - SYSTEMUI_SAVEDGAME_DISPLAYTEXT_PREFIX_LEN)) {
+ actualDescriptionLen = SYSTEMUI_SAVEDGAME_DISPLAYTEXT_LEN - SYSTEMUI_SAVEDGAME_DISPLAYTEXT_PREFIX_LEN;
+ }
+
+ if (actualDescriptionLen > 0) {
+ memcpy(destDisplayText + SYSTEMUI_SAVEDGAME_DISPLAYTEXT_PREFIX_LEN, actualDescription, actualDescriptionLen);
+ }
+ destDisplayText[SYSTEMUI_SAVEDGAME_DISPLAYTEXT_LEN] = 0; // terminator
+}
+
+void SystemUI::readSavedGameSlots(bool filterNonexistant, bool withAutoSaveSlot) {
+ SavedGameSlotIdArray slotIdArray;
+ int16 lastSlotId = -1;
+ int16 curSlotId = 0;
+ int16 loopSlotId = 0;
+ SystemUISavedGameEntry savedGameEntry;
+ Common::String saveDescription;
+ uint32 saveDate = 0;
+ uint16 saveTime = 0;
+ bool saveIsValid = false;
+
+ int16 mostRecentSlotNr = -1;
+ uint32 mostRecentSlotSaveDate = 0;
+ uint16 mostRecentSlotSaveTime = 0;
+
+ clearSavedGameSlots();
+
+ // we assume that the Slot-Ids are in order
+ slotIdArray = _vm->getSavegameSlotIds();
+ slotIdArray.push_back(SYSTEMUI_SAVEDGAME_MAXIMUM_SLOTS); // so that the loop will process all slots
+
+ SavedGameSlotIdArray::iterator it;
+ SavedGameSlotIdArray::iterator end = slotIdArray.end();;
+
+ for (it = slotIdArray.begin(); it != end; it++) {
+ curSlotId = *it;
+
+ assert(curSlotId > lastSlotId); // safety check
+
+ if (curSlotId == 0) {
+ // Skip over auto-save slot, if not requested
+ if (!withAutoSaveSlot)
+ continue;
+ }
+
+ // only allow slot-ids 000 up to 099
+ if (curSlotId >= SYSTEMUI_SAVEDGAME_MAXIMUM_SLOTS)
+ curSlotId = SYSTEMUI_SAVEDGAME_MAXIMUM_SLOTS;
+
+ if (!filterNonexistant) {
+ // add slot-ids from last one up to current one (not including the current one)
+ lastSlotId++;
+ for (loopSlotId = lastSlotId; loopSlotId < curSlotId; loopSlotId++) {
+ if (loopSlotId == 0) {
+ // Skip over auto-save slot, if not requested
+ if (!withAutoSaveSlot)
+ continue;
+ }
+
+ savedGameEntry.slotId = loopSlotId;
+ savedGameEntry.exists = false;
+ savedGameEntry.isValid = false;
+ memset(savedGameEntry.description, 0, sizeof(savedGameEntry.description));
+ createSavedGameDisplayText(savedGameEntry.displayText, "", loopSlotId);
+
+ _savedGameArray.push_back(savedGameEntry);
+ }
+ }
+
+ if (curSlotId >= SYSTEMUI_SAVEDGAME_MAXIMUM_SLOTS)
+ break; // force an exit, limit reached
+
+ savedGameEntry.slotId = curSlotId;
+ if (_vm->getSavegameInformation(curSlotId, saveDescription, saveDate, saveTime, saveIsValid)) {
+ if (saveIsValid) {
+ // saved game is valid
+ // check, if this is the latest slot
+ if (saveDate > mostRecentSlotSaveDate) {
+ mostRecentSlotNr = _savedGameArray.size();
+ mostRecentSlotSaveDate = saveDate;
+ mostRecentSlotSaveTime = saveTime;
+ } else if ((saveDate == mostRecentSlotSaveDate) && (saveTime >= mostRecentSlotSaveTime)) {
+ // larger or equal is on purpose, so that we use the last slot in case there are multiple saves
+ // with the exact same date+time
+ mostRecentSlotNr = _savedGameArray.size();
+ mostRecentSlotSaveDate = saveDate;
+ mostRecentSlotSaveTime = saveTime;
+ }
+ }
+ } else {
+ // slot doesn't exist
+ if (filterNonexistant) {
+ continue;
+ }
+ }
+
+ savedGameEntry.exists = true;
+ savedGameEntry.isValid = saveIsValid;
+ memset(savedGameEntry.description, 0, sizeof(savedGameEntry.description));
+ strncpy(savedGameEntry.description, saveDescription.c_str(), SYSTEMUI_SAVEDGAME_DESCRIPTION_LEN);
+ createSavedGameDisplayText(savedGameEntry.displayText, saveDescription.c_str(), curSlotId);
+
+ _savedGameArray.push_back(savedGameEntry);
+
+ lastSlotId = curSlotId;
+ }
+
+ if (mostRecentSlotNr >= 0) {
+ // valid slot found, we use it as default
+ // Sierra AGI seems to have done the same
+ _savedGameSelectedSlotNr = mostRecentSlotNr;
+ if (mostRecentSlotNr < SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN) {
+ // available without scrolling, so keep upmost slot 0
+ _savedGameUpmostSlotNr = 0;
+ } else {
+ // we need to scroll, try to have the slot in the middle
+ int16 slotCount = _savedGameArray.size();
+
+ _savedGameUpmostSlotNr = mostRecentSlotNr - (SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN / 2);
+ if ((_savedGameUpmostSlotNr + (SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN - 1)) >= slotCount) {
+ // current upmost would result in empty lines, because it's at the end, push it upwards
+ _savedGameUpmostSlotNr = (slotCount - 1) - (SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN - 1);
+ }
+ }
+ }
+}
+
+void SystemUI::figureOutAutomaticSavedGameSlot(const char *automaticSaveDescription, int16 &matchedGameSlotId, int16 &freshGameSlotId) {
+ bool foundFresh = false;
+
+ for (uint16 slotNr = 0; slotNr < _savedGameArray.size(); slotNr++) {
+ SystemUISavedGameEntry *savedGameEntry = &_savedGameArray[slotNr];
+
+ if (savedGameEntry->isValid) {
+ // valid saved game
+ if (strcmp(savedGameEntry->description, automaticSaveDescription) == 0) {
+ // we got a match
+ matchedGameSlotId = savedGameEntry->slotId;
+ return;
+ }
+ }
+ if (!foundFresh) {
+ // no new slot found yet
+ if (!savedGameEntry->exists) {
+ // and current slot doesn't exist
+ if (slotNr) {
+ // and slot is not the auto-save slot -> remember this slot
+ freshGameSlotId = savedGameEntry->slotId;
+ foundFresh = true;
+ }
+ }
+ }
+ }
+ return;
+}
+
+void SystemUI::drawSavedGameSlots() {
+ int16 slotsToDrawCount = _savedGameArray.size() - _savedGameUpmostSlotNr;
+ int16 slotNr = 0;
+
+ if (slotsToDrawCount > SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN) {
+ slotsToDrawCount = SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN;
+ }
+ _text->charAttrib_Push();
+ _text->charAttrib_Set(0, 15);
+
+ for (slotNr = 0; slotNr < slotsToDrawCount; slotNr++) {
+ _text->displayTextInsideWindow("-", 5 + slotNr, 1);
+ _text->displayTextInsideWindow(_savedGameArray[_savedGameUpmostSlotNr + slotNr].displayText, 5 + slotNr, 3);
+ }
+ _text->charAttrib_Pop();
+}
+
+void SystemUI::drawSavedGameSlotSelector(bool active) {
+ int16 windowRow = 5 + (_savedGameSelectedSlotNr - _savedGameUpmostSlotNr);
+
+ _text->charAttrib_Push();
+ _text->charAttrib_Set(0, 15);
+ if (active) {
+ _text->displayTextInsideWindow("\x1a", windowRow, 0);
+ } else {
+ _text->displayTextInsideWindow(" ", windowRow, 0);
+ }
+ _text->charAttrib_Pop();
+}
+
+bool SystemUI::askForSavedGameVerification(const char *verifyText, const char *description) {
+ char userActionVerify[200];
+ int16 userKey = 0;
+
+ sprintf(userActionVerify, verifyText, description);
+
+ _text->drawMessageBox(userActionVerify, 0, 35);
+
+ userKey = _vm->waitKey();
+
+ _text->closeWindow();
+
+ switch (userKey) {
+ case AGI_KEY_ENTER:
+ case AGI_KEY_LEFT:
+ return true;
+ break;
+ default:
+ return false;
+ break;
+ }
+}
+
+} // End of namespace Agi
diff --git a/engines/agi/systemui.h b/engines/agi/systemui.h
new file mode 100644
index 0000000000..da9f09f5be
--- /dev/null
+++ b/engines/agi/systemui.h
@@ -0,0 +1,119 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef AGI_SYSTEMUI_H
+#define AGI_SYSTEMUI_H
+
+namespace Agi {
+
+#define SYSTEMUI_SAVEDGAME_MAXIMUM_SLOTS 100
+#define SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN 12
+#define SYSTEMUI_SAVEDGAME_DESCRIPTION_LEN 30
+#define SYSTEMUI_SAVEDGAME_DISPLAYTEXT_LEN 31
+#define SYSTEMUI_SAVEDGAME_DISPLAYTEXT_PREFIX_LEN 3
+
+struct SystemUISavedGameEntry {
+ int16 slotId;
+ bool exists;
+ bool isValid;
+ char description[SYSTEMUI_SAVEDGAME_DESCRIPTION_LEN + 1]; // actual description
+ char displayText[SYSTEMUI_SAVEDGAME_DISPLAYTEXT_LEN + 1]; // modified description, meant for display purposes only
+};
+typedef Common::Array<SystemUISavedGameEntry> SystemUISavedGameArray;
+
+class SystemUI {
+public:
+ SystemUI(AgiEngine *vm, GfxMgr *gfx, TextMgr *text);
+ ~SystemUI();
+
+private:
+ AgiEngine *_vm;
+ GfxMgr *_gfx;
+ PictureMgr *_picture;
+ TextMgr *_text;
+
+public:
+ const char *getStatusTextScore();
+ const char *getStatusTextSoundOn();
+ const char *getStatusTextSoundOff();
+
+ void pauseDialog();
+ bool restartDialog();
+ bool quitDialog();
+
+ const char *getInventoryTextNothing();
+ const char *getInventoryTextYouAreCarrying();
+ const char *getInventoryTextSelectItems();
+ const char *getInventoryTextReturnToGame();
+
+ int16 figureOutAutomaticSaveGameSlot(const char *automaticSaveDescription);
+ int16 figureOutAutomaticRestoreGameSlot(const char *automaticSaveDescription);
+
+ int16 askForSaveGameSlot();
+ int16 askForRestoreGameSlot();
+ bool askForSaveGameDescription(int16 slotId, Common::String &newDescription);
+
+ void savedGameSlot_CharPress(int16 newChar);
+
+private:
+ int16 askForSavedGameSlot(const char *slotListText);
+ bool askForSavedGameVerification(const char *verifyText, const char *description);
+
+ void createSavedGameDisplayText(char *destDisplayText, const char *actualDescription, int16 slotId);
+ void clearSavedGameSlots();
+ void readSavedGameSlots(bool filterNonexistant, bool withAutoSaveSlot);
+ void figureOutAutomaticSavedGameSlot(const char *automaticSaveDescription, int16 &matchedGameSlotId, int16 &freshGameSlotId);
+
+ void drawSavedGameSlots();
+ void drawSavedGameSlotSelector(bool active);
+
+ SystemUISavedGameArray _savedGameArray;
+ int16 _savedGameUpmostSlotNr;
+ int16 _savedGameSelectedSlotNr;
+
+private:
+ const char *_textStatusScore;
+ const char *_textStatusSoundOn;
+ const char *_textStatusSoundOff;
+
+ const char *_textPause;
+ const char *_textRestart;
+ const char *_textQuit;
+
+ const char *_textInventoryNothing;
+ const char *_textInventoryYouAreCarrying;
+ const char *_textInventorySelectItems;
+ const char *_textInventoryReturnToGame;
+
+ const char *_textSaveGameSelectSlot;
+ const char *_textSaveGameEnterDescription;
+ const char *_textSaveGameVerify;
+
+ const char *_textRestoreGameNoSlots;
+ const char *_textRestoreGameSelectSlot;
+ const char *_textRestoreGameError;
+ const char *_textRestoreGameVerify;
+};
+
+} // End of namespace Agi
+
+#endif /* AGI_SYSTEMUI_H */
diff --git a/engines/agi/text.cpp b/engines/agi/text.cpp
index 16c8284ce0..8367929932 100644
--- a/engines/agi/text.cpp
+++ b/engines/agi/text.cpp
@@ -24,493 +24,922 @@
#include "agi/sprite.h" // for commit_both()
#include "agi/graphics.h"
#include "agi/keyboard.h"
+#include "agi/text.h"
+#include "agi/systemui.h"
+#include "agi/words.h"
+#ifdef __DS__
+#include "wordcompletion.h"
+#endif
namespace Agi {
-void AgiEngine::printText2(int l, const char *msg, int foff, int xoff, int yoff,
- int len, int fg, int bg, bool checkerboard) {
- int x1, y1;
- int maxx, minx, ofoff;
- int update;
- // Note: Must be unsigned to use AGDS cyrillic characters!
-#ifdef __DS__
- // On the DS, a compiler bug causes the text to render incorrectly, because
- // GCC tries to optimisie out writes to this pointer (tested on DevkitARM v19b and v20)
- // Making this pointer volatile fixes this.
- volatile const unsigned char *m;
-#else
- const unsigned char *m;
-#endif
+TextMgr::TextMgr(AgiEngine *vm, Words *words, GfxMgr *gfx) {
+ _vm = vm;
+ _words = words;
+ _gfx = gfx;
- // kludge!
- update = 1;
- if (l == 2) {
- update = l = 0;
- }
+ memset(&_messageState, 0, sizeof(_messageState));
+ _textPos.row = 0;
+ _textPos.column = 0;
+ _reset_Column = 0;
- // FR: strings with len == 1 were not printed
- if (len == 1) {
- _gfx->putTextCharacter(l, xoff + foff, yoff, *msg, fg, bg, checkerboard, _fontData);
- maxx = 1;
- minx = 0;
- ofoff = foff;
- y1 = 0; // Check this
- } else {
- maxx = 0;
- minx = GFX_WIDTH;
- ofoff = foff;
+ charAttrib_Set(15, 0);
- for (m = (const unsigned char *)msg, x1 = y1 = 0; *m; m++) {
+ _messageState.wanted_TextPos.row = -1;
+ _messageState.wanted_TextPos.column = -1;
+ _messageState.wanted_Text_Width = -1;
+
+ _textPosArrayCount = 0;
+ memset(&_textPosArray, 0, sizeof(_textPosArray));
+ _textAttribArrayCount = 0;
+ memset(&_textAttribArray, 0, sizeof(_textAttribArray));
- // Note: there were extra checks for *m being a cursor character
- // here (1, 2 or 3), which have been removed, as the cursor
- // character is no longer printed via this function.
- if (*m >= 0x20) {
- int ypos = (y1 * CHAR_LINES) + yoff;
+ _inputEditEnabled = false;
+ _inputCursorChar = 0;
- if ((x1 != (len - 1) || x1 == 39) && (ypos <= (GFX_HEIGHT - CHAR_LINES))) {
- int xpos = (x1 * CHAR_COLS) + xoff + foff;
+ _statusEnabled = false;
+ _statusRow = 0;
- if (xpos >= GFX_WIDTH)
- continue;
+ _promptRow = 0;
+ promptDisable();
+ promptReset();
- _gfx->putTextCharacter(l, xpos, ypos, *m, fg, bg, checkerboard, _fontData);
+ _inputStringMaxLen = 0;
+ _inputStringCursorPos = 0;
+ _inputString[0] = 0;
- if (x1 > maxx)
- maxx = x1;
- if (x1 < minx)
- minx = x1;
- }
+ configureScreen(2);
+}
+
+TextMgr::~TextMgr() {
+}
+
+void TextMgr::init(SystemUI *systemUI) {
+ _systemUI = systemUI;
+}
+
+void TextMgr::configureScreen(uint16 row_Min) {
+ _window_Row_Min = row_Min;
+ _window_Row_Max = row_Min + 21;
+
+ // forward data to GfxMgr as well
+ _gfx->setRenderStartOffset(row_Min * FONT_DISPLAY_HEIGHT);
+}
+uint16 TextMgr::getWindowRowMin() {
+ return _window_Row_Min;
+}
- x1++;
+void TextMgr::dialogueOpen() {
+ _messageState.dialogue_Open = true;
+}
- // Change line if we've reached the end of this one, unless the next
- // character is a new line itself, or the end of the string
- if (x1 == len && m[1] != '\n' && m[1] != 0) {
- y1++;
- x1 = foff = 0;
+void TextMgr::dialogueClose() {
+ _messageState.dialogue_Open = false;
+}
+
+void TextMgr::charPos_Clip(int16 &row, int16 &column) {
+ row = CLIP<int16>(row, 0, FONT_ROW_CHARACTERS - 1);
+ column = CLIP<int16>(column, 0, FONT_COLUMN_CHARACTERS - 1);
+}
+
+void TextMgr::charPos_Set(int16 row, int16 column) {
+ _textPos.row = row;
+ _textPos.column = column;
+}
+
+void TextMgr::charPos_Get(TextPos_Struct *posPtr) {
+ posPtr->row = _textPos.row;
+ posPtr->column = _textPos.column;
+}
+
+void TextMgr::charPos_Set(TextPos_Struct *posPtr) {
+ _textPos.row = posPtr->row;
+ _textPos.column = posPtr->column;
+}
+
+void TextMgr::charPos_Push() {
+ if (_textPosArrayCount < TEXTPOSARRAY_MAX) {
+ charPos_Get(&_textPosArray[_textPosArrayCount]);
+ _textPosArrayCount++;
+ }
+}
+
+void TextMgr::charPos_Pop() {
+ if (_textPosArrayCount > 0) {
+ _textPosArrayCount--;
+ charPos_Set(&_textPosArray[_textPosArrayCount]);
+ }
+}
+
+void TextMgr::charPos_SetInsideWindow(int16 windowRow, int16 windowColumn) {
+ if (!_messageState.window_Active)
+ return;
+
+ _textPos.row = _messageState.textPos.row + windowRow;
+ _textPos.column = _messageState.textPos.column + windowColumn;
+}
+
+static byte charAttrib_CGA_Conversion[] = {
+ 0, 1, 1, 1, 2, 2, 2, 3, 3, 1, 1, 1, 2, 2, 2
+};
+
+void TextMgr::charAttrib_Set(byte foreground, byte background) {
+ _textAttrib.foreground = foreground;
+ _textAttrib.background = calculateTextBackground(background);
+
+ if (!_vm->_game.gfxMode) {
+ // Text-mode:
+ // just use the given colors directly
+ _textAttrib.combinedForeground = foreground;
+ _textAttrib.combinedBackground = background;
+ } else {
+ // Graphics-mode:
+ switch (_vm->_renderMode) {
+ case RENDERMODE_CGA:
+ // CGA
+ if (background) {
+ _textAttrib.combinedForeground = 3;
+ _textAttrib.combinedBackground = 8; // enable invert of colors
+ } else if (foreground > 14) {
+ if (foreground > 14) {
+ _textAttrib.combinedForeground = 3;
+ } else {
+ _textAttrib.combinedForeground = charAttrib_CGA_Conversion[foreground & 0x0F];
}
+ _textAttrib.combinedBackground = 0;
+ }
+ break;
+ default:
+ // EGA-handling:
+ if (background) {
+ _textAttrib.combinedForeground = 15;
+ _textAttrib.combinedBackground = 8; // enable invert of colors
} else {
- if (m[1] != 0) {
- y1++;
- x1 = foff = 0;
- }
+ _textAttrib.combinedForeground = foreground;
+ _textAttrib.combinedBackground = 0;
}
+ break;
}
}
+}
- if (l)
- return;
-
- if (maxx < minx)
- return;
+byte TextMgr::charAttrib_GetForeground() {
+ return _textAttrib.foreground;
+}
+byte TextMgr::charAttrib_GetBackground() {
+ return _textAttrib.background;
+}
- maxx *= CHAR_COLS;
- minx *= CHAR_COLS;
+void TextMgr::charAttrib_Push() {
+ if (_textAttribArrayCount < TEXTATTRIBARRAY_MAX) {
+ memcpy(&_textAttribArray[_textAttribArrayCount], &_textAttrib, sizeof(_textAttrib));
+ _textAttribArrayCount++;
+ }
+}
- if (update) {
- _gfx->scheduleUpdate(foff + xoff + minx, yoff, ofoff + xoff + maxx + CHAR_COLS - 1,
- yoff + y1 * CHAR_LINES + CHAR_LINES + 1);
+void TextMgr::charAttrib_Pop() {
+ if (_textAttribArrayCount > 0) {
+ _textAttribArrayCount--;
+ memcpy(&_textAttrib, &_textAttribArray[_textAttribArrayCount], sizeof(_textAttrib));
+ }
+}
- // Making synchronous text updates reduces CPU load
- // when updating status line and input area
- _gfx->doUpdate();
+byte TextMgr::calculateTextBackground(byte background) {
+ if ((_vm->_game.gfxMode) && (background)) {
+ return 15; // interpreter sets 0xFF, but drawClearCharacter() would use upper 4 bits by shift
}
+ return 0;
}
-//
-// len is in characters, not pixels!!
-//
-void AgiEngine::blitTextbox(const char *p, int y, int x, int len) {
- // if x | y = -1, then center the box
- int xoff, yoff, lin, h, w;
- char *msg, *m;
+void TextMgr::display(int16 textNr, int16 textRow, int16 textColumn) {
+ const char *logicTextPtr = NULL;
+ char *processedTextPtr = NULL;
- debugC(3, kDebugLevelText, "blitTextbox(): x=%d, y=%d, len=%d", x, y, len);
- if (_game.window.active)
- closeWindow();
+ charPos_Push();
+ charPos_Set(textRow, textColumn);
+
+ if (textNr >= 1 && textNr <= _vm->_game._curLogic->numTexts) {
+ logicTextPtr = _vm->_game._curLogic->texts[textNr - 1];
+ processedTextPtr = stringPrintf(logicTextPtr);
+ processedTextPtr = stringWordWrap(processedTextPtr, 40);
+ displayText(processedTextPtr);
+
+ // Signal, that non-blocking text is shown at the moment
+ if (textRow > 0) {
+ // only signal, when it's not the status line (kq3)
+ _vm->nonBlockingText_IsShown();
+ }
+ }
+ charPos_Pop();
+}
+
+void TextMgr::displayTextInsideWindow(const char *textPtr, int16 windowRow, int16 windowColumn) {
+ int16 textRow = 0;
+ int16 textColumn = 0;
- if (x == 0 && y == 0 && len == 0)
- x = y = -1;
+ if (!_messageState.window_Active)
+ return;
- if (len <= 0)
- len = 30;
+ charPos_Push();
+ textRow = _messageState.textPos.row + windowRow;
+ textColumn = _messageState.textPos.column + windowColumn;
+ charPos_Set(textRow, textColumn);
+ displayText(textPtr);
+ charPos_Pop();
+}
- xoff = x * CHAR_COLS;
- yoff = y * CHAR_LINES;
+void TextMgr::displayText(const char *textPtr, bool disabledLook) {
+ const char *curTextPtr = textPtr;
+ byte curCharacter = 0;
- m = msg = wordWrapString(agiSprintf(p), &len);
+ while (1) {
+ curCharacter = *curTextPtr;
+ if (!curCharacter)
+ break;
- for (lin = 1; *m; m++) {
- // Test \r for MacOS 8
- if (*m == '\n' || *m == '\r')
- lin++;
+ curTextPtr++;
+ displayCharacter(curCharacter, disabledLook);
}
+}
- if (lin * CHAR_LINES > GFX_HEIGHT)
- lin = (GFX_HEIGHT / CHAR_LINES);
+void TextMgr::displayCharacter(byte character, bool disabledLook) {
+ TextPos_Struct charCurPos;
- w = (len + 2) * CHAR_COLS;
- h = (lin + 2) * CHAR_LINES;
+ charPos_Get(&charCurPos);
- if (xoff < 0)
- xoff = (GFX_WIDTH - w - CHAR_COLS) / 2;
- else
- xoff -= CHAR_COLS;
+ switch (character) {
+ case 0x08: // backspace
+ if (charCurPos.column) {
+ charCurPos.column--;
+ } else if (charCurPos.row > 21) {
+ charCurPos.column = (FONT_COLUMN_CHARACTERS - 1);
+ charCurPos.row--;
+ }
+ clearBlock(charCurPos.row, charCurPos.column, charCurPos.row, charCurPos.column, _textAttrib.background);
+ charPos_Set(&charCurPos);
+ break;
- if (yoff < 0)
- yoff = (GFX_HEIGHT - 3 * CHAR_LINES - h) / 2;
+ case 0x0D:
+ case 0x0A: // CR/LF
+ if (charCurPos.row < (FONT_ROW_CHARACTERS - 1))
+ charCurPos.row++;
+ charCurPos.column = _reset_Column;
+ charPos_Set(&charCurPos);
+ break;
+ default:
+ // ch_attrib(state.text_comb, conversion);
+ _gfx->drawCharacter(charCurPos.row, charCurPos.column, character, _textAttrib.combinedForeground, _textAttrib.combinedBackground, disabledLook);
+
+ charCurPos.column++;
+ if (charCurPos.column <= (FONT_COLUMN_CHARACTERS - 1)) {
+ charPos_Set(&charCurPos);
+ } else {
+ displayCharacter(0x0D); // go to next line
+ }
+ }
+}
- drawWindow(xoff, yoff, xoff + w - 1, yoff + h - 1);
+void TextMgr::print(int16 textNr) {
+ const char *logicTextPtr = NULL;
+ if (textNr >= 1 && textNr <= _vm->_game._curLogic->numTexts) {
+ logicTextPtr = _vm->_game._curLogic->texts[textNr - 1];
+ messageBox(logicTextPtr);
+ }
+}
+
+void TextMgr::printAt(int16 textNr, int16 textPos_Row, int16 textPos_Column, int16 text_Width) {
+ // Sierra didn't do clipping, we do it for security
+ charPos_Clip(textPos_Row, textPos_Column);
- printText2(2, msg, 0, CHAR_COLS + xoff, CHAR_LINES + yoff,
- len + 1, MSG_BOX_TEXT, MSG_BOX_COLOR);
+ _messageState.wanted_TextPos.row = textPos_Row;
+ _messageState.wanted_TextPos.column = textPos_Column;
+ _messageState.wanted_Text_Width = text_Width;
- free(msg);
+ if (_messageState.wanted_Text_Width == 0) {
+ _messageState.wanted_Text_Width = 30;
+ }
+ print(textNr);
- _gfx->doUpdate();
+ _messageState.wanted_TextPos.row = -1;
+ _messageState.wanted_TextPos.column = -1;
+ _messageState.wanted_Text_Width = -1;
}
-void AgiEngine::eraseTextbox() {
- if (!_game.window.active) {
- debugC(3, kDebugLevelText, "eraseTextbox(): no window active");
- return;
+bool TextMgr::messageBox(const char *textPtr) {
+ drawMessageBox(textPtr);
+
+ if (_vm->getflag(VM_FLAG_OUTPUT_MODE)) {
+ // non-blocking window
+ _vm->setflag(VM_FLAG_OUTPUT_MODE, false);
+
+ // Signal, that non-blocking text is shown at the moment
+ _vm->nonBlockingText_IsShown();
+ return true;
}
- debugC(4, kDebugLevelText, "eraseTextbox(): x1=%d, y1=%d, x2=%d, y2=%d", _game.window.x1,
- _game.window.y1, _game.window.x2, _game.window.y2);
+ // blocking window
+ _vm->_noSaveLoadAllowed = true;
+ _vm->nonBlockingText_Forget();
- _gfx->restoreBlock(_game.window.x1, _game.window.y1,
- _game.window.x2, _game.window.y2, _game.window.buffer);
+ if (_vm->_game.vars[VM_VAR_WINDOW_RESET] == 0) {
+ int userKey;
+ _vm->setVar(VM_VAR_KEY, 0);
+ userKey = _vm->waitKey();
+ closeWindow();
- free(_game.window.buffer);
- _game.window.active = false;
+ _vm->_noSaveLoadAllowed = false;
+ if (userKey == AGI_KEY_ENTER)
+ return true;
+ return false;
+ }
- _gfx->doUpdate();
-}
+ // timed window
+ debugC(3, kDebugLevelText, "f15==0, v21==%d => timed", _vm->getVar(VM_VAR_WINDOW_RESET));
+ _vm->_game.msgBoxTicks = _vm->getVar(VM_VAR_WINDOW_RESET) * 10;
+ _vm->setVar(VM_VAR_KEY, 0);
-/*
- * Public functions
- */
+ do {
+ if (_vm->getflag(VM_FLAG_RESTORE_JUST_RAN))
+ break;
-/**
- * Print text in the AGI engine screen.
- */
-void AgiEngine::printText(const char *msg, int f, int x, int y, int len, int fg, int bg, bool checkerboard) {
- f *= CHAR_COLS;
- x *= CHAR_COLS;
- y *= CHAR_LINES;
+ _vm->mainCycle();
+ if (_vm->_game.keypress == AGI_KEY_ENTER) {
+ debugC(4, kDebugLevelText, "KEY_ENTER");
+ _vm->setVar(VM_VAR_WINDOW_RESET, 0);
+ _vm->_game.keypress = 0;
+ break;
+ }
+ } while (_vm->_game.msgBoxTicks > 0 && !(_vm->shouldQuit() || _vm->_restartGame));
- debugC(4, kDebugLevelText, "printText(): %s, %d, %d, %d, %d, %d, %d", msg, f, x, y, len, fg, bg);
- printText2(0, agiSprintf(msg), f, x, y, len, fg, bg, checkerboard);
+ _vm->setVar(VM_VAR_WINDOW_RESET, 0);
+ closeWindow();
+ _vm->_noSaveLoadAllowed = false;
+ return true;
}
-/**
- * Print text in the AGI engine console.
- */
-void AgiEngine::printTextConsole(const char *msg, int x, int y, int len, int fg, int bg) {
- x *= CHAR_COLS;
- y *= 10;
+void TextMgr::drawMessageBox(const char *textPtr, int16 wantedHeight, int16 wantedWidth, bool wantedForced) {
+ int16 maxWidth = wantedWidth;
+ int16 startingRow = 0;
+ char *processedTextPtr;
- debugC(4, kDebugLevelText, "printTextConsole(): %s, %d, %d, %d, %d, %d", msg, x, y, len, fg, bg);
- printText2(1, msg, 0, x, y, len, fg, bg);
-}
+ if (_messageState.window_Active) {
+ closeWindow();
+ }
+ charAttrib_Push();
+ charPos_Push();
+ charAttrib_Set(0, 15);
+
+ if ((_messageState.wanted_Text_Width == -1) && (maxWidth == 0)) {
+ maxWidth = 30;
+ } else if (_messageState.wanted_Text_Width != -1) {
+ maxWidth = _messageState.wanted_Text_Width;
+ }
-/**
- * Wrap text line to the specified width.
- * @param str String to wrap.
- * @param len Length of line.
- *
- * Based on GBAGI implementation with permission from the author
- */
-char *AgiEngine::wordWrapString(const char *s, int *len) {
- char *outStr, *msgBuf;
- int maxWidth = *len;
- const char *pWord;
- int lnLen, wLen;
+ processedTextPtr = stringPrintf(textPtr);
- // Allocate some extra space for the final buffer, as
- // outStr may end up being longer than s
- // 26 = 200 (screen height) / 8 (font height) + 1
- msgBuf = outStr = (char *)malloc(strlen(s) + 26);
+ int16 calculatedWidth = 0;
+ int16 calculatedHeight = 0;
- int msgWidth = 0;
+ processedTextPtr = stringWordWrap(processedTextPtr, maxWidth, &calculatedWidth, &calculatedHeight);
+ _messageState.textSize_Width = calculatedWidth;
+ _messageState.textSize_Height = calculatedHeight;
- lnLen = 0;
+ _messageState.printed_Height = _messageState.textSize_Height;
- while (*s) {
- pWord = s;
+ // Caller wants to force specified width/height? set it
+ if (wantedForced) {
+ if (wantedHeight)
+ _messageState.textSize_Height = wantedHeight;
+ if (wantedWidth)
+ _messageState.textSize_Width = wantedWidth;
+ }
- while (*s != '\0' && *s != ' ' && *s != '\n' && *s != '\r')
- s++;
+ if (_messageState.wanted_TextPos.row == -1) {
+ startingRow = ((HEIGHT_MAX - _messageState.textSize_Height - 1) / 2) + 1;
+ } else {
+ startingRow = _messageState.wanted_TextPos.row;
+ }
+ _messageState.textPos.row = startingRow + _window_Row_Min;
+ _messageState.textPos_Edge.row = _messageState.textSize_Height + _messageState.textPos.row - 1;
- wLen = (int)(s - pWord);
+ if (_messageState.wanted_TextPos.column == -1) {
+ _messageState.textPos.column = (FONT_COLUMN_CHARACTERS - _messageState.textSize_Width) / 2;
+ } else {
+ _messageState.textPos.column = _messageState.wanted_TextPos.column;
+ }
+ _messageState.textPos_Edge.column = _messageState.textPos.column + _messageState.textSize_Width;
- if (wLen && *s == '\n' && s[-1] == ' ')
- wLen--;
+ charPos_Set(_messageState.textPos.row, _messageState.textPos.column);
- if (wLen + lnLen >= maxWidth) {
- // Check if outStr isn't msgBuf. If this is the case, outStr hasn't advanced
- // yet, so no output has been written yet
- if (outStr != msgBuf) {
- if (outStr[-1] == ' ')
- outStr[-1] = '\n';
- else
- *outStr++ = '\n';
- }
+ _messageState.backgroundSize_Width = (_messageState.textSize_Width * FONT_VISUAL_WIDTH) + 10;
+ _messageState.backgroundSize_Height = (_messageState.textSize_Height * FONT_VISUAL_HEIGHT) + 10;
+ _messageState.backgroundPos_x = (_messageState.textPos.column * FONT_VISUAL_WIDTH) - 5;
+ _messageState.backgroundPos_y = (_messageState.textPos_Edge.row - _window_Row_Min + 1 ) * FONT_VISUAL_HEIGHT + 4;
- lnLen = 0;
+ // Hardcoded colors: white background and red lines
+ _gfx->drawBox(_messageState.backgroundPos_x, _messageState.backgroundPos_y, _messageState.backgroundSize_Width, _messageState.backgroundSize_Height, 15, 4);
- while (wLen >= maxWidth) {
- msgWidth = maxWidth;
+ _messageState.window_Active = true;
- memcpy(outStr, pWord, maxWidth);
+ _reset_Column = _messageState.textPos.column;
+ displayText(processedTextPtr);
+ _reset_Column = 0;
- wLen -= maxWidth;
- outStr += maxWidth;
- pWord += maxWidth;
- *outStr++ = '\n';
- }
- }
+ charPos_Pop();
+ charAttrib_Pop();
- if (wLen) {
- memcpy(outStr, pWord, wLen);
- outStr += wLen;
- }
- lnLen += wLen+1;
+ _messageState.dialogue_Open = true;
+}
+
+void TextMgr::closeWindow() {
+ if (_messageState.window_Active) {
+ _gfx->render_Block(_messageState.backgroundPos_x, _messageState.backgroundPos_y, _messageState.backgroundSize_Width, _messageState.backgroundSize_Height);
+ }
+ _messageState.dialogue_Open = false;
+ _messageState.window_Active = false;
+}
- if (lnLen > msgWidth) {
- msgWidth = lnLen;
+void TextMgr::statusRow_Set(int16 row) {
+ _statusRow = row;
+}
+int16 TextMgr::statusRow_Get() {
+ return _statusRow;
+}
- if (*s == '\0' || *s == ' ' || *s == '\n' || *s == '\r')
- msgWidth--;
- }
+void TextMgr::statusEnable() {
+ _statusEnabled = true;
+}
+void TextMgr::statusDisable() {
+ _statusEnabled = false;
+}
+bool TextMgr::statusEnabled() {
+ return _statusEnabled;
+}
+
+void TextMgr::statusDraw() {
+ char *statusTextPtr = NULL;
+
+ charAttrib_Push();
+ charPos_Push();
+
+ if (_statusEnabled) {
+ clearLine(_statusRow, 15);
- if (*s == '\n')
- lnLen = 0;
+ charAttrib_Set(0, 15);
+ charPos_Set(_statusRow, 1);
+ statusTextPtr = stringPrintf(_systemUI->getStatusTextScore());
+ displayText(statusTextPtr);
- if (*s)
- *outStr++ = *s++;
+ charPos_Set(_statusRow, 30);
+ if (_vm->getflag(VM_FLAG_SOUND_ON)) {
+ statusTextPtr = stringPrintf(_systemUI->getStatusTextSoundOn());
+ } else {
+ statusTextPtr = stringPrintf(_systemUI->getStatusTextSoundOff());
+ }
+ displayText(statusTextPtr);
}
- *outStr = '\0';
- *len = msgWidth;
- return msgBuf;
+ charPos_Pop();
+ charAttrib_Pop();
}
-/**
- * Remove existing window, if any.
- */
-void AgiEngine::closeWindow() {
- debugC(4, kDebugLevelText, "closeWindow()");
+void TextMgr::statusClear() {
+ clearLine(_statusRow, 0);
+}
- _sprites->eraseBoth();
- eraseTextbox(); // remove window, if any
- _sprites->blitBoth();
- _sprites->commitBoth(); // redraw sprites
- _game.hasWindow = false;
+void TextMgr::clearLine(int16 row, byte color) {
+ clearLines(row, row, color);
}
-/**
- * Display a message box.
- * This function displays the specified message in a text box
- * centered in the screen and waits until a key is pressed.
- * @param p The text to be displayed
- */
-int AgiEngine::messageBox(const char *s) {
- int k;
-
- _sprites->eraseBoth();
- blitTextbox(s, -1, -1, -1);
- _sprites->blitBoth();
- k = waitKey();
- debugC(4, kDebugLevelText, "messageBox(): wait_key returned %02x", k);
- closeWindow();
+void TextMgr::clearLines(int16 row_Upper, int16 row_Lower, byte color) {
+ clearBlock(row_Upper, 0, row_Lower, FONT_COLUMN_CHARACTERS - 1, color);
+}
+
+void TextMgr::clearBlock(int16 row_Upper, int16 column_Upper, int16 row_Lower, int16 column_Lower, byte color) {
+ // Sierra didn't do clipping of the coordinates, we do it for security
+ // and b/c there actually are some games, that call commands with invalid coordinates
+ // see cmdClearLines() comments.
+ charPos_Clip(row_Upper, column_Upper);
+ charPos_Clip(row_Lower, column_Lower);
- return k;
+ int16 x = column_Upper * FONT_DISPLAY_WIDTH;
+ int16 y = row_Upper * FONT_DISPLAY_HEIGHT;
+ int16 width = (column_Lower + 1 - column_Upper) * FONT_DISPLAY_WIDTH;
+ int16 height = (row_Lower + 1 - row_Upper) * FONT_DISPLAY_HEIGHT;
+
+ y = y + height - 1; // drawDisplayRect wants lower Y-coordinate
+ _gfx->drawDisplayRect(x, y, width, height, color);
}
-/**
- * Display a message box with buttons.
- * This function displays the specified message in a text box
- * centered in the screen and waits until a button is pressed.
- * @param p The text to be displayed
- * @param b NULL-terminated list of button labels
- */
-int AgiEngine::selectionBox(const char *m, const char **b) {
- int numButtons = 0;
- int x, y, i, s;
- int bx[5], by[5];
+void TextMgr::clearBlockInsideWindow(int16 windowRow, int16 windowColumn, int16 width, byte color) {
+ int16 row;
+ int16 column;
+ if (!_messageState.window_Active)
+ return;
- _noSaveLoadAllowed = true;
+ row = _messageState.textPos.row + windowRow;
+ column = _messageState.textPos.column + windowColumn;
+ clearBlock(row, column, row, column + width - 1, color);
+}
- _sprites->eraseBoth();
- blitTextbox(m, -1, -1, -1);
+bool TextMgr::inputGetEditStatus() {
+ return _inputEditEnabled;
+}
- x = _game.window.x1 + 5 * CHAR_COLS / 2;
- y = _game.window.y2 - 5 * CHAR_LINES / 2;
- s = _game.window.x2 - _game.window.x1 + 1 - 5 * CHAR_COLS;
- debugC(3, kDebugLevelText, "selectionBox(): s = %d", s);
+void TextMgr::inputEditOn() {
+ if (!_inputEditEnabled) {
+ _inputEditEnabled = true;
+ if (_inputCursorChar) {
+ displayCharacter(0x08); // backspace
+ }
+ }
+}
- // Automatically position buttons
- for (i = 0; b[i]; i++) {
- numButtons++;
- s -= CHAR_COLS * strlen(b[i]);
+void TextMgr::inputEditOff() {
+ if (_inputEditEnabled) {
+ _inputEditEnabled = false;
+ if (_inputCursorChar) {
+ displayCharacter(_inputCursorChar);
+ }
}
+}
+
+void TextMgr::inputSetCursorChar(int16 cursorChar) {
+ _inputCursorChar = cursorChar;
+}
+
+byte TextMgr::inputGetCursorChar() {
+ return _inputCursorChar;
+}
+
+void TextMgr::promptRow_Set(int16 row) {
+ _promptRow = row;
+}
+
+int16 TextMgr::promptRow_Get() {
+ return _promptRow;
+}
+
+void TextMgr::promptReset() {
+ _promptCursorPos = 0;
+ memset(_prompt, 0, sizeof(_prompt));
+ memset(_promptPrevious, 0, sizeof(_promptPrevious));
+}
- if (i > 1) {
- debugC(3, kDebugLevelText, "selectionBox(): s / %d = %d", i - 1, s / (i - 1));
- s /= (i - 1);
+void TextMgr::promptEnable() {
+ _promptEnabled = true;
+}
+void TextMgr::promptDisable() {
+ _promptEnabled = false;
+}
+bool TextMgr::promptIsEnabled() {
+ return _promptEnabled;
+}
+
+void TextMgr::promptCharPress(int16 newChar) {
+ int16 maxChars = 0;
+ int16 scriptsInputLen = _vm->getVar(VM_VAR_MAX_INPUT_CHARACTERS);
+
+ if (_messageState.dialogue_Open) {
+ maxChars = TEXT_STRING_MAX_SIZE - 4;
} else {
- x += s / 2;
+ maxChars = TEXT_STRING_MAX_SIZE - strlen(_vm->_game.strings[0]); // string 0 is the prompt string prefix
}
- for (i = 0; b[i]; i++) {
- bx[i] = x;
- by[i] = y;
- x += CHAR_COLS * strlen(b[i]) + s;
- }
+ if (_promptCursorPos)
+ maxChars--;
- _sprites->blitBoth();
+ if (scriptsInputLen < maxChars)
+ maxChars = scriptsInputLen;
- clearKeyQueue();
+ inputEditOn();
- AllowSyntheticEvents on(this);
+ switch (newChar) {
+ case AGI_KEY_BACKSPACE: {
+ if (_promptCursorPos) {
+ _promptCursorPos--;
+ _prompt[_promptCursorPos] = 0;
+ displayCharacter(newChar);
+
+ promptRememberForAutoComplete();
+ }
+ break;
+ }
+ case 0x0A: // LF
+ break;
+ case AGI_KEY_ENTER: {
+ if (_promptCursorPos) {
+ // something got entered? -> process it and pass it to the scripts
+ promptRememberForAutoComplete(true);
+
+ memcpy(&_promptPrevious, &_prompt, sizeof(_prompt));
+ // parse text
+ _vm->_words->parseUsingDictionary((char *)&_prompt);
+
+ _promptCursorPos = 0;
+ _prompt[0] = 0;
+ promptRedraw();
+ }
+ break;
+ }
+ default:
+ if (maxChars > _promptCursorPos) {
+ bool acceptableInput = false;
+
+ // FEATURE: Sierra didn't check for valid characters (filtered out umlauts etc.)
+ // In text-mode this sort of worked at least with the DOS interpreter
+ // but as soon as invalid characters were used in graphics mode they weren't properly shown
+ switch (_vm->getLanguage()) {
+ case Common::RU_RUS:
+ if (newChar >= 0x20)
+ acceptableInput = true;
+ break;
+ default:
+ if ((newChar >= 0x20) && (newChar <= 0x7f))
+ acceptableInput = true;
+ break;
+ }
- debugC(4, kDebugLevelText, "selectionBox(): waiting...");
- int key, active = 0;
- int rc = -1;
- while (rc == -1 && !(shouldQuit() || _restartGame)) {
- for (i = 0; b[i]; i++)
- _gfx->drawCurrentStyleButton(bx[i], by[i], b[i], i == active, false, i == 0);
+ if (acceptableInput) {
+ _prompt[_promptCursorPos] = newChar;
+ _promptCursorPos++;
+ _prompt[_promptCursorPos] = 0;
+ displayCharacter(newChar);
- pollTimer();
- key = doPollKeyboard();
- switch (key) {
- case KEY_ENTER:
- rc = active;
- debugC(4, kDebugLevelText, "selectionBox(): Button pressed: %d", rc);
- break;
- case KEY_RIGHT:
- active++;
- if (active >= numButtons)
- active = 0;
- break;
- case KEY_LEFT:
- active--;
- if (active < 0)
- active = numButtons - 1;
- break;
- case BUTTON_LEFT:
- for (i = 0; b[i]; i++) {
- if (_gfx->testButton(bx[i], by[i], b[i])) {
- rc = active = i;
- debugC(4, kDebugLevelText, "selectionBox(): Button pressed: %d", rc);
- break;
- }
+ promptRememberForAutoComplete();
}
- break;
- case 0x09: // Tab
- debugC(3, kDebugLevelText, "selectionBox(): Focus change");
- active++;
- active %= i;
- break;
}
- _gfx->doUpdate();
+ break;
+ }
- if (key == KEY_ESCAPE)
- break;
+ inputEditOff();
+}
+
+void TextMgr::promptCancelLine() {
+ while (_promptCursorPos) {
+ promptCharPress(0x08); // Backspace until prompt is empty
}
+}
- closeWindow();
- debugC(2, kDebugLevelText, "selectionBox(): Result = %d", rc);
+void TextMgr::promptEchoLine() {
+ int16 previousLen = strlen((char *)_promptPrevious);
- _noSaveLoadAllowed = false;
+ if (_promptCursorPos < previousLen) {
+ inputEditOn();
+
+ while (_promptPrevious[_promptCursorPos]) {
+ promptCharPress(_promptPrevious[_promptCursorPos]);
+ }
+ promptRememberForAutoComplete();
- return rc;
+ inputEditOff();
+ }
}
-/**
- *
- */
-int AgiEngine::print(const char *p, int lin, int col, int len) {
- if (p == NULL)
- return 0;
+void TextMgr::promptRedraw() {
+ char *textPtr = nullptr;
- debugC(4, kDebugLevelText, "print(): lin = %d, col = %d, len = %d", lin, col, len);
+ if (_promptEnabled) {
+ inputEditOn();
+ clearLine(_promptRow, _textAttrib.background);
+ charPos_Set(_promptRow, 0);
+ // agi_printf(str_wordwrap(msg, state.string[0], 40) );
- blitTextbox(p, lin, col, len);
+ textPtr = _vm->_game.strings[0];
+ textPtr = stringPrintf(textPtr);
+ textPtr = stringWordWrap(textPtr, 40);
- if (getflag(fOutputMode)) {
- // non-blocking window
- setflag(fOutputMode, false);
- return 1;
+ displayText(textPtr);
+ displayText((char *)&_prompt);
+ inputEditOff();
}
+}
- // blocking
+// for AGI1
+void TextMgr::promptClear() {
+ clearLine(_promptRow, _textAttrib.background);
+}
- _noSaveLoadAllowed = true;
+void TextMgr::promptRememberForAutoComplete(bool entered) {
+#ifdef __DS__
+ DS::findWordCompletions((char *)_prompt);
+#endif
+}
- if (_game.vars[vWindowReset] == 0) {
- int k;
- setvar(vKey, 0);
- k = waitKey();
- closeWindow();
+bool TextMgr::stringWasEntered() {
+ return _inputStringEntered;
+}
+
+void TextMgr::stringSet(const char *text) {
+ strncpy((char *)_inputString, text, sizeof(_inputString));
+ _inputString[sizeof(_inputString) - 1] = 0; // terminator
+}
- _noSaveLoadAllowed = false;
+void TextMgr::stringEdit(int16 stringMaxLen) {
+ int16 inputStringLen = strlen((const char *)_inputString);
- return k;
+ // Caller can set the input string
+ _inputStringCursorPos = 0;
+ while (_inputStringCursorPos < inputStringLen) {
+ displayCharacter(_inputString[_inputStringCursorPos]);
+ _inputStringCursorPos++;
}
- // timed window
+ // should never happen unless there is a coding glitch
+ assert(_inputStringCursorPos <= stringMaxLen);
- debugC(3, kDebugLevelText, "f15==0, v21==%d => timed", getvar(21));
- _game.msgBoxTicks = getvar(vWindowReset) * 10;
- setvar(vKey, 0);
+ _inputStringMaxLen = stringMaxLen;
+ _inputStringEntered = false;
- _menuSelected = false;
+ inputEditOff();
do {
- if (getflag(fRestoreJustRan))
- break;
+ _vm->mainCycle();
+ } while (_vm->cycleInnerLoopIsActive() && !(_vm->shouldQuit() || _vm->_restartGame));
- if (_menuSelected)
- break;
+ inputEditOn();
- mainCycle();
- if (_game.keypress == KEY_ENTER) {
- debugC(4, kDebugLevelText, "KEY_ENTER");
- setvar(vWindowReset, 0);
- _game.keypress = 0;
- break;
+ // Forget non-blocking text, user was asked to enter something
+ _vm->nonBlockingText_Forget();
+}
+
+void TextMgr::stringCharPress(int16 newChar) {
+ inputEditOn();
+
+ switch (newChar) {
+ case 0x3: // ctrl-c
+ case 0x18: { // ctrl-x
+ // clear string
+ while (_inputStringCursorPos) {
+ _inputStringCursorPos--;
+ _inputString[_inputStringCursorPos] = 0;
+ displayCharacter(0x08);
}
- } while (_game.msgBoxTicks > 0 && !(shouldQuit() || _restartGame));
+ break;
+ }
- setvar(vWindowReset, 0);
+ case AGI_KEY_BACKSPACE: {
+ if (_inputStringCursorPos) {
+ _inputStringCursorPos--;
+ _inputString[_inputStringCursorPos] = 0;
+ displayCharacter(newChar);
- closeWindow();
+ stringRememberForAutoComplete();
+ }
+ break;
+ }
- _noSaveLoadAllowed = false;
+ case AGI_KEY_ENTER: {
+ stringRememberForAutoComplete(true);
- return 0;
+ _inputStringEntered = true;
+
+ _vm->cycleInnerLoopInactive(); // exit GetString-loop
+ break;
+ }
+
+ case AGI_KEY_ESCAPE: {
+ _inputString[0] = 0;
+ _inputStringCursorPos = 0;
+ _inputStringEntered = false;
+
+ _vm->cycleInnerLoopInactive(); // exit GetString-loop
+ break;
+ }
+
+ default:
+ if (_inputStringMaxLen > _inputStringCursorPos) {
+ bool acceptableInput = false;
+
+ // FEATURE: Sierra didn't check for valid characters (filtered out umlauts etc.)
+ // In text-mode this sort of worked at least with the DOS interpreter
+ // but as soon as invalid characters were used in graphics mode they weren't properly shown
+ switch (_vm->getLanguage()) {
+ case Common::RU_RUS:
+ if (newChar >= 0x20)
+ acceptableInput = true;
+ break;
+ default:
+ if ((newChar >= 0x20) && (newChar <= 0x7f))
+ acceptableInput = true;
+ break;
+ }
+
+ if (acceptableInput) {
+ if ((_vm->_game.cycleInnerLoopType == CYCLE_INNERLOOP_GETSTRING) || ((newChar >= '0') && (newChar <= '9'))) {
+ // Additionally check for GETNUMBER-mode, if character is a number
+ // Sierra also did not do this
+ _inputString[_inputStringCursorPos] = newChar;
+ _inputStringCursorPos++;
+ _inputString[_inputStringCursorPos] = 0;
+ displayCharacter(newChar);
+
+ stringRememberForAutoComplete();
+ }
+ }
+ }
+ break;
+ }
+
+ inputEditOff();
+}
+
+void TextMgr::stringRememberForAutoComplete(bool entered) {
+#ifdef __DS__
+ DS::findWordCompletions((char *)_inputString);
+#endif
}
/**
+ * Wrap text line to the specified width.
+ * @param str String to wrap.
+ * @param len Length of line.
*
+ * Based on GBAGI implementation with permission from the author
*/
-void AgiEngine::printStatus(const char *message, ...) {
- va_list args;
+char *TextMgr::stringWordWrap(const char *originalText, int16 maxWidth, int16 *calculatedWidthPtr, int16 *calculatedHeightPtr) {
+ static char resultWrapBuffer[2000];
+ char *outStr = resultWrapBuffer;
+ const char *wordStartPtr;
+ int16 lineLen = 0;
+ int16 wordLen = 0;
+ int curMaxWidth = 0;
+ int curHeight = 0;
+
+ assert(maxWidth > 0); // this routine would create heap corruption in case maxWidth <= 0
+
+ while (*originalText) {
+ wordStartPtr = originalText;
+
+ while (*originalText != '\0' && *originalText != ' ' && *originalText != '\n' && *originalText != '\r')
+ originalText++;
+
+ wordLen = originalText - wordStartPtr;
+
+ if (wordLen && *originalText == '\n' && originalText[-1] == ' ')
+ wordLen--;
+
+ if (wordLen + lineLen >= maxWidth) {
+ // Check if outStr isn't msgBuf. If this is the case, outStr hasn't advanced
+ // yet, so no output has been written yet
+ if (outStr != resultWrapBuffer) {
+ if (outStr[-1] == ' ')
+ outStr[-1] = '\n';
+ else
+ *outStr++ = '\n';
+ }
+ curHeight++;
+
+ lineLen = 0;
+
+ while (wordLen >= maxWidth) {
+ curMaxWidth = maxWidth;
+
+ memcpy(outStr, wordStartPtr, maxWidth);
+
+ wordLen -= maxWidth;
+ outStr += maxWidth;
+ wordStartPtr += maxWidth;
+ *outStr++ = '\n';
+ curHeight++;
+ }
+ }
- va_start(args, message);
+ if (wordLen) {
+ memcpy(outStr, wordStartPtr, wordLen);
+ outStr += wordLen;
+ }
+ lineLen += wordLen + 1;
- Common::String x = Common::String::vformat(message, args);
+ if (lineLen > curMaxWidth) {
+ curMaxWidth = lineLen;
- va_end(args);
+ if (*originalText == '\0' || *originalText == ' ' || *originalText == '\n' || *originalText == '\r')
+ curMaxWidth--;
+ }
+
+ if (*originalText == '\n') {
+ lineLen = 0;
+ curHeight++;
+ }
+
+ if (*originalText)
+ *outStr++ = *originalText++;
+ }
+ *outStr = '\0';
+ curHeight++;
- debugC(4, kDebugLevelText, "fg=%d, bg=%d", STATUS_FG, STATUS_BG);
- printText(x.c_str(), 0, 0, _game.lineStatus, 40, STATUS_FG, STATUS_BG);
+ if (calculatedWidthPtr) {
+ *calculatedWidthPtr = curMaxWidth;
+ }
+ if (calculatedHeightPtr) {
+ *calculatedHeightPtr = curHeight;
+ }
+ return resultWrapBuffer;
}
+// ===============================================================
+
static void safeStrcat(Common::String &p, const char *t) {
if (t != NULL)
p += t;
@@ -523,31 +952,31 @@ static void safeStrcat(Common::String &p, const char *t) {
* @param s string containing the format specifier
* @param n logic number
*/
-char *AgiEngine::agiSprintf(const char *s) {
- static char agiSprintf_buf[768];
- Common::String p;
+char *TextMgr::stringPrintf(const char *originalText) {
+ static char resultPrintfBuffer[2000];
+ Common::String resultString;
char z[16];
- debugC(3, kDebugLevelText, "logic %d, '%s'", _game.lognum, s);
+ debugC(3, kDebugLevelText, "logic %d, '%s'", _vm->_game.lognum, originalText);
- while (*s) {
- switch (*s) {
+ while (*originalText) {
+ switch (*originalText) {
case '%':
- s++;
- switch (*s++) {
+ originalText++;
+ switch (*originalText++) {
int i;
case 'v':
- i = strtoul(s, NULL, 10);
- while (*s >= '0' && *s <= '9')
- s++;
- sprintf(z, "%015i", getvar(i));
+ i = strtoul(originalText, NULL, 10);
+ while (*originalText >= '0' && *originalText <= '9')
+ originalText++;
+ sprintf(z, "%015i", _vm->getVar(i));
i = 99;
- if (*s == '|') {
- s++;
- i = strtoul(s, NULL, 10);
- while (*s >= '0' && *s <= '9')
- s++;
+ if (*originalText == '|') {
+ originalText++;
+ i = strtoul(originalText, NULL, 10);
+ while (*originalText >= '0' && *originalText <= '9')
+ originalText++;
}
if (i == 99) {
@@ -558,173 +987,48 @@ char *AgiEngine::agiSprintf(const char *s) {
} else {
i = 15 - i;
}
- safeStrcat(p, z + i);
+ safeStrcat(resultString, z + i);
break;
case '0':
- i = strtoul(s, NULL, 10) - 1;
- safeStrcat(p, objectName(i));
+ i = strtoul(originalText, NULL, 10) - 1;
+ safeStrcat(resultString, _vm->objectName(i));
break;
case 'g':
- i = strtoul(s, NULL, 10) - 1;
- safeStrcat(p, _game.logics[0].texts[i]);
+ i = strtoul(originalText, NULL, 10) - 1;
+ safeStrcat(resultString, _vm->_game.logics[0].texts[i]);
break;
case 'w':
- i = strtoul(s, NULL, 10) - 1;
- safeStrcat(p, _game.egoWords[i].word);
+ i = strtoul(originalText, NULL, 10) - 1;
+ safeStrcat(resultString, _vm->_words->getEgoWord(i));
break;
case 's':
- i = strtoul(s, NULL, 10);
- safeStrcat(p, agiSprintf(_game.strings[i]));
+ i = strtoul(originalText, NULL, 10);
+ safeStrcat(resultString, stringPrintf(_vm->_game.strings[i]));
break;
case 'm':
- i = strtoul(s, NULL, 10) - 1;
- if (_game.logics[_game.lognum].numTexts > i)
- safeStrcat(p, agiSprintf(_game.logics[_game.lognum].texts[i]));
+ i = strtoul(originalText, NULL, 10) - 1;
+ if (_vm->_game.logics[_vm->_game.lognum].numTexts > i)
+ safeStrcat(resultString, stringPrintf(_vm->_game.logics[_vm->_game.lognum].texts[i]));
break;
}
- while (*s >= '0' && *s <= '9')
- s++;
+ while (*originalText >= '0' && *originalText <= '9')
+ originalText++;
break;
case '\\':
- s++;
+ originalText++;
// FALL THROUGH
default:
- p += *s++;
+ resultString += *originalText++;
break;
}
}
- assert(p.size() < sizeof(agiSprintf_buf));
- strcpy(agiSprintf_buf, p.c_str());
- return agiSprintf_buf;
-}
-
-/**
- * Write the status line.
- */
-void AgiEngine::writeStatus() {
- char x[64];
-
- if (_debug.statusline) {
- printStatus("%3d(%03d) %3d,%3d(%3d,%3d) ",
- getvar(0), getvar(1), _game.viewTable[0].xPos,
- _game.viewTable[0].yPos, WIN_TO_PIC_X(_mouse.x),
- WIN_TO_PIC_Y(_mouse.y));
- return;
- }
-
- if (!_game.statusLine) {
- clearLines(_game.lineStatus, _game.lineStatus, 0);
- flushLines(_game.lineStatus, _game.lineStatus);
-
-#if 0
- // FIXME: Breaks wrist watch prompt in SQ2
-
- // Clear the user input line as well when clearing the status line
- // Fixes bug #1893564 - AGI: Texts messed out in Naturette 1
- clearLines(_game.lineUserInput, _game.lineUserInput, 0);
- flushLines(_game.lineUserInput, _game.lineUserInput);
-#endif
- return;
- }
-
- switch (getLanguage()) {
- case Common::RU_RUS:
- sprintf(x, " \x91\xe7\xa5\xe2: %i \xa8\xa7 %-3i", _game.vars[vScore], _game.vars[vMaxScore]);
- printStatus("%-17s \x87\xa2\xe3\xaa:%s", x, getflag(fSoundOn) ? "\xa2\xaa\xab " : "\xa2\xeb\xaa\xab");
- break;
- default:
- sprintf(x, " Score:%i of %-3i", _game.vars[vScore], _game.vars[vMaxScore]);
- printStatus("%-17s Sound:%s ", x, getflag(fSoundOn) ? "on " : "off");
- break;
- }
-}
-
-/**
- * Print user input prompt.
- */
-void AgiEngine::writePrompt() {
- int l, fg, bg, pos;
- int promptLength = strlen(agiSprintf(_game.strings[0]));
-
- if (!_game.inputEnabled || _game.inputMode != INPUT_NORMAL)
- return;
-
- l = _game.lineUserInput;
- fg = _game.colorFg;
- bg = _game.colorBg;
- pos = _game.cursorPos;
-
- debugC(4, kDebugLevelText, "erase line %d", l);
- clearLines(l, l, _game.colorBg);
-
- debugC(4, kDebugLevelText, "prompt = '%s'", agiSprintf(_game.strings[0]));
- printText(_game.strings[0], 0, 0, l, promptLength + 1, fg, bg);
- printText((char *)_game.inputBuffer, 0, promptLength, l, pos + 1, fg, bg);
- _gfx->printCharacter(pos + promptLength, l, _game.cursorChar, fg, bg);
-
- flushLines(l, l);
- _gfx->doUpdate();
-}
-
-void AgiEngine::clearPrompt(bool useBlackBg) {
- int l;
-
- l = _game.lineUserInput;
- clearLines(l, l, useBlackBg ? 0 : _game.colorBg);
- flushLines(l, l);
-
- _gfx->doUpdate();
-}
-
-/**
- * Clear text lines in the screen.
- * @param l1 start line
- * @param l2 end line
- * @param c color
- */
-void AgiEngine::clearLines(int l1, int l2, int c) {
- // do we need to adjust for +8 on topline?
- // inc for endline so it matches the correct num
- // ie, from 22 to 24 is 3 lines, not 2 lines.
-
- debugC(4, kDebugLevelText, "clearLines(%d, %d, %d)", l1, l2, c);
-
- l1 *= CHAR_LINES;
- l2 *= CHAR_LINES;
- l2 += CHAR_LINES - 1;
-
- _gfx->drawRectangle(0, l1, GFX_WIDTH - 1, l2, c);
-}
-
-/**
- *
- */
-void AgiEngine::flushLines(int l1, int l2) {
- l1 *= CHAR_LINES;
- l2 *= CHAR_LINES;
- l2 += CHAR_LINES - 1;
-
- _gfx->flushBlock(0, l1, GFX_WIDTH - 1, l2);
-}
-
-/**
- *
- */
-void AgiEngine::drawWindow(int x1, int y1, int x2, int y2) {
- _game.window.active = true;
- _game.window.x1 = x1;
- _game.window.y1 = y1;
- _game.window.x2 = x2;
- _game.window.y2 = y2;
- _game.window.buffer = (uint8 *)malloc((x2 - x1 + 1) * (y2 - y1 + 1));
-
- debugC(4, kDebugLevelText, "x1=%d, y1=%d, x2=%d, y2=%d", x1, y1, x2, y2);
- _gfx->saveBlock(x1, y1, x2, y2, _game.window.buffer);
- _gfx->drawBox(x1, y1, x2, y2, MSG_BOX_COLOR, MSG_BOX_LINE, 2);
+ assert(resultString.size() < sizeof(resultPrintfBuffer));
+ strcpy(resultPrintfBuffer, resultString.c_str());
+ return resultPrintfBuffer;
}
} // End of namespace Agi
diff --git a/engines/agi/text.h b/engines/agi/text.h
new file mode 100644
index 0000000000..b42f0982d5
--- /dev/null
+++ b/engines/agi/text.h
@@ -0,0 +1,201 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef AGI_TEXT_H
+#define AGI_TEXT_H
+
+namespace Agi {
+
+struct TextPos_Struct {
+ int16 row;
+ int16 column;
+};
+
+#define TEXTPOSARRAY_MAX 5
+
+struct TextAttrib_Struct {
+ byte foreground;
+ byte background;
+ byte combinedForeground;
+ byte combinedBackground;
+};
+
+#define TEXTATTRIBARRAY_MAX 5
+
+struct MessageState_Struct {
+ uint8 type;
+ int16 wanted_Text_Width;
+ TextPos_Struct wanted_TextPos;
+ bool dialogue_Open;
+ uint8 newline_Char;
+ bool window_Active;
+ TextPos_Struct textPos;
+ TextPos_Struct textPos_Edge;
+ int16 textSize_Width;
+ int16 textSize_Height;
+ uint16 printed_Height;
+
+ int16 backgroundPos_x;
+ int16 backgroundPos_y;
+ int16 backgroundSize_Width;
+ int16 backgroundSize_Height;
+};
+
+// this defines here are for calculating character-size inside the visual-screen!
+#define FONT_VISUAL_WIDTH 4
+#define FONT_VISUAL_HEIGHT 8
+
+#define FONT_DISPLAY_WIDTH 8
+#define FONT_DISPLAY_HEIGHT 8
+#define FONT_ROW_CHARACTERS 25
+#define FONT_COLUMN_CHARACTERS 40
+#define FONT_BYTES_PER_CHARACTER 8
+
+#define HEIGHT_MAX 20
+
+#define TEXT_STRING_MAX_SIZE 40
+
+class TextMgr {
+private:
+ Words *_words;
+ GfxMgr *_gfx;
+ AgiEngine *_vm;
+ SystemUI *_systemUI;
+
+public:
+ TextMgr(AgiEngine *vm, Words *words, GfxMgr *gfx);
+ ~TextMgr();
+
+ void init(SystemUI *systemUI);
+
+ TextPos_Struct _textPos;
+ int16 _textPosArrayCount;
+ TextPos_Struct _textPosArray[TEXTPOSARRAY_MAX];
+
+ TextAttrib_Struct _textAttrib;
+ int16 _textAttribArrayCount;
+ TextAttrib_Struct _textAttribArray[TEXTATTRIBARRAY_MAX];
+
+ uint16 _window_Row_Min;
+ uint16 _window_Row_Max;
+ int16 _reset_Column;
+
+ void configureScreen(uint16 row_Min);
+ uint16 getWindowRowMin();
+
+ void dialogueOpen();
+ void dialogueClose();
+
+ void charPos_Clip(int16 &row, int16 &column);
+ void charPos_Set(int16 row, int16 column);
+ void charPos_Get(TextPos_Struct *posPtr);
+ void charPos_Set(TextPos_Struct *posPtr);
+ void charPos_Push();
+ void charPos_Pop();
+ void charPos_SetInsideWindow(int16 windowRow, int16 windowColumn);
+ void charAttrib_Set(byte foreground, byte background);
+ byte charAttrib_GetForeground();
+ byte charAttrib_GetBackground();
+ void charAttrib_Push();
+ void charAttrib_Pop();
+ byte calculateTextBackground(byte background);
+
+ void display(int16 textNr, int16 textRow, int16 textColumn);
+ void displayText(const char *textPtr, bool disabledLook = false);
+ void displayCharacter(byte character, bool disabledLook = false);
+
+ void displayTextInsideWindow(const char *textPtr, int16 windowRow, int16 windowColumn);
+
+ MessageState_Struct _messageState;
+
+ void printAt(int16 textNr, int16 textPos_Row, int16 textPos_Column, int16 text_Width);
+ void print(int16 textNr);
+ bool messageBox(const char *textPtr);
+ void drawMessageBox(const char *textPtr, int16 wantedHeight = 0, int16 wantedWidth = 0, bool wantedForced = false);
+ void closeWindow();
+
+ void statusRow_Set(int16 row);
+ int16 statusRow_Get();
+
+ void statusEnable();
+ void statusDisable();
+ bool statusEnabled();
+
+ void statusDraw();
+ void statusClear();
+
+ bool _statusEnabled;
+ int16 _statusRow;
+
+ void clearLine(int16 row, byte color);
+ void clearLines(int16 row_Upper, int16 row_Lower, byte color);
+ void clearBlock(int16 row_Upper, int16 column_Upper, int16 row_Lower, int16 column_Lower, byte color);
+
+ void clearBlockInsideWindow(int16 windowRow, int16 windowColumn, int16 width, byte color);
+
+ bool _inputEditEnabled;
+ byte _inputCursorChar;
+
+ bool _promptEnabled;
+ int16 _promptRow;
+ int16 _promptCursorPos;
+ byte _prompt[42];
+ byte _promptPrevious[42];
+
+ bool inputGetEditStatus();
+ void inputEditOn();
+ void inputEditOff();
+ void inputSetCursorChar(int16 cursorChar);
+ byte inputGetCursorChar();
+
+ void promptReset();
+ void promptEnable();
+ void promptDisable();
+ bool promptIsEnabled();
+
+ void promptRow_Set(int16 row);
+ int16 promptRow_Get();
+ void promptCharPress(int16 newChar);
+ void promptCancelLine();
+ void promptEchoLine();
+ void promptRedraw();
+ void promptClear(); // for AGI1
+ void promptRememberForAutoComplete(bool entered = false); // for auto-completion
+
+ bool _inputStringEntered;
+ int16 _inputStringMaxLen;
+ int16 _inputStringCursorPos;
+ byte _inputString[42];
+
+ bool stringWasEntered();
+ void stringSet(const char *text);
+ void stringEdit(int16 stringMaxLen);
+ void stringCharPress(int16 newChar);
+ void stringRememberForAutoComplete(bool entered = false); // for auto-completion
+
+ char *stringPrintf(const char *originalText);
+ char *stringWordWrap(const char *originalText, int16 maxWidth, int16 *calculatedWidthPtr = nullptr, int16 *calculatedHeightPtr = nullptr);
+};
+
+} // End of namespace Agi
+
+#endif /* AGI_TEXT_H */
diff --git a/engines/agi/view.cpp b/engines/agi/view.cpp
index 6a274a1b73..66508a6c27 100644
--- a/engines/agi/view.cpp
+++ b/engines/agi/view.cpp
@@ -21,107 +21,60 @@
*/
#include "agi/agi.h"
+#include "agi/graphics.h"
#include "agi/sprite.h"
namespace Agi {
-void AgiEngine::lSetCel(VtEntry *v, int n) {
- ViewLoop *currentVl;
- ViewCel *currentVc;
+void AgiEngine::updateView(ScreenObjEntry *screenObj) {
+ int16 celNr, lastCelNr;
- v->currentCel = n;
-
- currentVl = &_game.views[v->currentView].loop[v->currentLoop];
-
- // Added by Amit Vainsencher <amitv@subdimension.com> to prevent
- // crash in KQ1 -- not in the Sierra interpreter
- if (currentVl->numCels == 0)
- return;
-
- // WORKAROUND: This is a very nasty hack to fix a bug in the KQ4 introduction
- // In its original form, it caused a lot of regressions, including KQ4 bugs and crashes
- // Refer to Sarien bug #588899 for the original issue
- // Modifying this workaround to only work for a specific view in the KQ4 intro fixes several
- // ScummVM bugs. Refer to bugs #1660486, #1660169, #1660192, #1660162 and #1660354
- // FIXME: Remove this workaround and investigate the reason for the erroneous actor behavior
- // in the KQ4 introduction
- // It seems there's either a bug with KQ4's logic script 120 (the intro script)
- // or flag 64 is not set correctly, which causes the erroneous behavior from the actors
- if (getGameID() == GID_KQ4 && !(v->flags & fUpdate) && (v->currentView == 172))
- return;
-
- currentVc = &currentVl->cel[n];
- v->celData = currentVc;
- v->xSize = currentVc->width;
- v->ySize = currentVc->height;
-}
-
-void AgiEngine::lSetLoop(VtEntry *v, int n) {
- ViewLoop *currentVl;
- debugC(7, kDebugLevelResources, "vt entry #%d, loop = %d", v->entry, n);
-
- // Added to avoid crash when leaving the arcade machine in MH1
- // -- not in AGI 2.917
- if (n >= v->numLoops)
- n = 0;
-
- v->currentLoop = n;
- currentVl = &_game.views[v->currentView].loop[v->currentLoop];
-
- v->numCels = currentVl->numCels;
- if (v->currentCel >= v->numCels)
- v->currentCel = 0;
-
- v->loopData = &_game.views[v->currentView].loop[n];
-}
-
-void AgiEngine::updateView(VtEntry *v) {
- int cel, lastCel;
-
- if (v->flags & fDontupdate) {
- v->flags &= ~fDontupdate;
+ if (screenObj->flags & fDontupdate) {
+ screenObj->flags &= ~fDontupdate;
return;
}
- cel = v->currentCel;
- lastCel = v->numCels - 1;
+ celNr = screenObj->currentCelNr;
+ lastCelNr = screenObj->celCount - 1;
- switch (v->cycle) {
+ switch (screenObj->cycle) {
case kCycleNormal:
- if (++cel > lastCel)
- cel = 0;
+ celNr++;
+ if (celNr > lastCelNr)
+ celNr = 0;
break;
case kCycleEndOfLoop:
- if (cel < lastCel) {
- debugC(5, kDebugLevelResources, "cel %d (last = %d)", cel + 1, lastCel);
- if (++cel != lastCel)
+ if (celNr < lastCelNr) {
+ debugC(5, kDebugLevelResources, "cel %d (last = %d)", celNr + 1, lastCelNr);
+ if (++celNr != lastCelNr)
break;
}
- setflag(v->parm1, true);
- v->flags &= ~fCycling;
- v->direction = 0;
- v->cycle = kCycleNormal;
+ setflag(screenObj->loop_flag, true);
+ screenObj->flags &= ~fCycling;
+ screenObj->direction = 0;
+ screenObj->cycle = kCycleNormal;
break;
case kCycleRevLoop:
- if (cel) {
- if (--cel)
+ if (celNr) {
+ celNr--;
+ if (celNr)
break;
}
- setflag(v->parm1, true);
- v->flags &= ~fCycling;
- v->direction = 0;
- v->cycle = kCycleNormal;
+ setflag(screenObj->loop_flag, true);
+ screenObj->flags &= ~fCycling;
+ screenObj->direction = 0;
+ screenObj->cycle = kCycleNormal;
break;
case kCycleReverse:
- if (cel == 0) {
- cel = lastCel;
+ if (celNr == 0) {
+ celNr = lastCelNr;
} else {
- cel--;
+ celNr--;
}
break;
}
- setCel(v, cel);
+ setCel(screenObj, celNr);
}
/*
@@ -134,191 +87,475 @@ void AgiEngine::updateView(VtEntry *v) {
* and fills the corresponding views array element.
* @param n number of view resource to decode
*/
-int AgiEngine::decodeView(int n) {
- int loop, cel;
- uint8 *v, *lptr;
- uint16 lofs, cofs;
- ViewLoop *vl;
- ViewCel *vc;
+int AgiEngine::decodeView(byte *resourceData, uint16 resourceSize, int16 viewNr) {
+ AgiView *viewData = &_game.views[viewNr];
+ uint16 headerId = 0;
+ byte headerStepSize = 0;
+ byte headerCycleTime = 0;
+ byte headerLoopCount = 0;
+ uint16 headerDescriptionOffset = 0;
+ bool isAGI256Data = false;
+
+ AgiViewLoop *loopData = nullptr;
+ uint16 loopOffset = 0;
+ byte loopHeaderCelCount = 0;
+
+ AgiViewCel *celData = nullptr;
+ uint16 celOffset = 0;
+ byte celHeaderWidth = 0;
+ byte celHeaderHeight = 0;
+ byte celHeaderTransparencyMirror = 0;
+ byte celHeaderClearKey = 0;
+ bool celHeaderMirrored = false;
+ byte celHeaderMirrorLoop = 0;
+
+ byte *celCompressedData = nullptr;
+ uint16 celCompressedSize = 0;
+// byte *rawBitmap = nullptr;
+
+ debugC(5, kDebugLevelResources, "decode_view(%d)", viewNr);
+
+ if (resourceSize < 5)
+ error("unexpected end of view data for view %d", viewNr);
+
+ headerId = READ_LE_UINT16(resourceData);
+ if (getVersion() < 0x2000) {
+ headerStepSize = resourceData[0];
+ headerCycleTime = resourceData[1];
+ }
+ headerLoopCount = resourceData[2];
+ headerDescriptionOffset = READ_LE_UINT16(resourceData + 3);
+
+ if (headerId == 0xF00F)
+ isAGI256Data = true; // AGI 256-2 view detected, 256 color view
+
+ viewData->headerStepSize = headerStepSize;
+ viewData->headerCycleTime = headerCycleTime;
+ viewData->loopCount = headerLoopCount;
+ viewData->description = nullptr;
+ viewData->loop = nullptr;
+
+ if (headerDescriptionOffset) {
+ // Figure out length of description
+ uint16 descriptionPos = headerDescriptionOffset;
+ uint16 descriptionLen = 0;
+ while (descriptionPos < resourceSize) {
+ if (resourceData[descriptionPos] == 0)
+ break;
+ descriptionPos++;
+ descriptionLen++;
+ }
+ // Allocate memory for description
+ viewData->description = new byte[descriptionLen + 1];
+ // Copy description over
+ memcpy(viewData->description, resourceData + headerDescriptionOffset, descriptionLen);
+ viewData->description[descriptionLen] = 0; // set terminator
+ }
+
+ if (!viewData->loopCount) // no loops, exit now
+ return errOK;
+
+ // Check, if at least the loop-offsets are available
+ if (resourceSize < 5 + (headerLoopCount * 2))
+ error("unexpected end of view data for view %d", viewNr);
+
+ // Allocate space for loop-information
+ loopData = new AgiViewLoop[headerLoopCount];
+ viewData->loop = loopData;
+
+ for (int16 loopNr = 0; loopNr < headerLoopCount; loopNr++) {
+ loopOffset = READ_LE_UINT16(resourceData + 5 + (loopNr * 2));
+
+ // Check, if at least the loop-header is available
+ if (resourceSize < (loopOffset + 1))
+ error("unexpected end of view data for view %d", viewNr);
+
+ // loop-header:
+ // celCount:BYTE
+ // relativeCelOffset[0]:WORD
+ // relativeCelOffset[1]:WORD
+ // etc.
+ loopHeaderCelCount = resourceData[loopOffset];
+
+ loopData->celCount = loopHeaderCelCount;
+ loopData->cel = nullptr;
+
+ // Check, if at least the cel-offsets for current loop are available
+ if (resourceSize < (loopOffset + 1 + (loopHeaderCelCount * 2)))
+ error("unexpected end of view data for view %d", viewNr);
+
+ if (loopHeaderCelCount) {
+ // Allocate space for cel-information of current loop
+ celData = new AgiViewCel[loopHeaderCelCount];
+ loopData->cel = celData;
+
+ for (int16 celNr = 0; celNr < loopHeaderCelCount; celNr++) {
+ celOffset = READ_LE_UINT16(resourceData + loopOffset + 1 + (celNr * 2));
+ celOffset += loopOffset; // cel offset is relative to loop offset, so adjust accordingly
+
+ // Check, if at least the cel-header is available
+ if (resourceSize < (celOffset + 3))
+ error("unexpected end of view data for view %d", viewNr);
+
+ // cel-header:
+ // width:BYTE
+ // height:BYTE
+ // Transparency + Mirroring:BYTE
+ // celData follows
+ celHeaderWidth = resourceData[celOffset + 0];
+ celHeaderHeight = resourceData[celOffset + 1];
+ celHeaderTransparencyMirror = resourceData[celOffset + 2];
+
+ if (!isAGI256Data) {
+ // regular AGI view data
+ // Transparency + Mirroring byte is as follows:
+ // Bit 0-3 - clear key
+ // Bit 4-6 - original loop, that is not supposed to be mirrored in any case
+ // Bit 7 - apply mirroring
+ celHeaderClearKey = celHeaderTransparencyMirror & 0x0F; // bit 0-3 is the clear key
+ celHeaderMirrored = false;
+ if (celHeaderTransparencyMirror & 0x80) {
+ // mirror bit is set
+ celHeaderMirrorLoop = (celHeaderTransparencyMirror >> 4) & 0x07;
+ if (celHeaderMirrorLoop != loopNr) {
+ // only set to mirror'd in case we are not the original loop
+ celHeaderMirrored = true;
+ }
+ }
+ } else {
+ // AGI256-2 view data
+ celHeaderClearKey = celHeaderTransparencyMirror; // full 8 bits for clear key
+ celHeaderMirrored = false;
+ }
+
+ celData->width = celHeaderWidth;
+ celData->height = celHeaderHeight;
+ celData->clearKey = celHeaderClearKey;
+ celData->mirrored = celHeaderMirrored;
+
+ // Now decompress cel-data
+ if ((celHeaderWidth == 0) && (celHeaderHeight == 0))
+ error("view cel is 0x0");
+
+ celCompressedData = resourceData + celOffset + 3;
+ celCompressedSize = resourceSize - (celOffset + 3);
+
+ if (!isAGI256Data) {
+ unpackViewCelData(celData, celCompressedData, celCompressedSize);
+ } else {
+ unpackViewCelDataAGI256(celData, celCompressedData, celCompressedSize);
+ }
+ celData++;
+ }
+ }
+
+ loopData++;
+ }
+
+ return errOK;
+}
- debugC(5, kDebugLevelResources, "decode_view(%d)", n);
- v = _game.views[n].rdata;
+void AgiEngine::unpackViewCelData(AgiViewCel *celData, byte *compressedData, uint16 compressedSize) {
+ byte *rawBitmap = new byte[celData->width * celData->height];
+ int16 remainingHeight = celData->height;
+ int16 remainingWidth = celData->width;
+ bool isMirrored = celData->mirrored;
+ byte curByte;
+ byte curColor;
+ byte curChunkLen;
+ int16 adjustPreChangeSingle = 0;
+ int16 adjustAfterChangeSingle = +1;
+
+ celData->rawBitmap = rawBitmap;
+
+ if (isMirrored) {
+ adjustPreChangeSingle = -1;
+ adjustAfterChangeSingle = 0;
+ rawBitmap += celData->width;
+ }
- assert(v != NULL);
+ while (remainingHeight) {
+ if (!compressedSize)
+ error("unexpected end of data, while unpacking AGI256 data");
- _game.views[n].agi256_2 = (READ_LE_UINT16(v) == 0xf00f); // Detect AGI256-2 views by their header bytes
- _game.views[n].descr = READ_LE_UINT16(v + 3) ? (char *)(v + READ_LE_UINT16(v + 3)) : (char *)(v + 3);
+ curByte = *compressedData++;
+ compressedSize--;
- // if no loops exist, return!
- if ((_game.views[n].numLoops = *(v + 2)) == 0)
- return errNoLoopsInView;
+ if (curByte == 0) {
+ curColor = celData->clearKey;
+ curChunkLen = remainingWidth;
+ } else {
+ curColor = curByte >> 4;
+ curChunkLen = curByte & 0x0F;
+ if (curChunkLen > remainingWidth)
+ error("invalid chunk in view data");
+ }
- // allocate memory for all views
- _game.views[n].loop = (ViewLoop *)calloc(_game.views[n].numLoops, sizeof(ViewLoop));
+ switch (curChunkLen) {
+ case 0:
+ break;
+ case 1:
+ rawBitmap += adjustPreChangeSingle;
+ *rawBitmap = curColor;
+ rawBitmap += adjustAfterChangeSingle;
+ break;
+ default:
+ if (isMirrored)
+ rawBitmap -= curChunkLen;
+ memset(rawBitmap, curColor, curChunkLen);
+ if (!isMirrored)
+ rawBitmap += curChunkLen;
+ break;
+ }
- if (_game.views[n].loop == NULL)
- return errNotEnoughMemory;
+ remainingWidth -= curChunkLen;
+
+ if (curByte == 0) {
+ remainingWidth = celData->width;
+ remainingHeight--;
- // decode all of the loops in this view
- lptr = v + 5; // first loop address
+ if (isMirrored)
+ rawBitmap += celData->width * 2;
+ }
+ }
- for (loop = 0; loop < _game.views[n].numLoops; loop++, lptr += 2) {
- lofs = READ_LE_UINT16(lptr); // loop header offset
- vl = &_game.views[n].loop[loop]; // the loop struct
+ // for CGA rendering, apply dithering
+ switch (_renderMode) {
+ case RENDERMODE_CGA: {
+ uint16 totalPixels = celData->width * celData->height;
- vl->numCels = *(v + lofs);
- debugC(6, kDebugLevelResources, "view %d, num_cels = %d", n, vl->numCels);
- vl->cel = (ViewCel *)calloc(vl->numCels, sizeof(ViewCel));
+ // dither clear key
+ celData->clearKey = _gfx->getCGAMixtureColor(celData->clearKey);
- if (vl->cel == NULL) {
- free(_game.views[n].loop);
- _game.views[n].numLoops = 0;
- return errNotEnoughMemory;
+ rawBitmap = celData->rawBitmap;
+ for (uint16 pixelNr = 0; pixelNr < totalPixels; pixelNr++) {
+ curColor = *rawBitmap;
+ *rawBitmap = _gfx->getCGAMixtureColor(curColor);
+ rawBitmap++;
}
+ break;
+ }
+ default:
+ break;
+ }
+}
- // decode the cells
- for (cel = 0; cel < vl->numCels; cel++) {
- cofs = lofs + READ_LE_UINT16(v + lofs + 1 + (cel * 2));
- vc = &vl->cel[cel];
-
- vc->width = *(v + cofs);
- vc->height = *(v + cofs + 1);
-
- if (!_game.views[n].agi256_2) {
- vc->transparency = *(v + cofs + 2) & 0xf;
- vc->mirrorLoop = (*(v + cofs + 2) >> 4) & 0x7;
- vc->mirror = (*(v + cofs + 2) >> 7) & 0x1;
- } else {
- // Mirroring is disabled for AGI256-2 views because
- // AGI256-2 uses whole 8 bits for the transparency variable.
- vc->transparency = *(v + cofs + 2);
- vc->mirrorLoop = 0;
- vc->mirror = 0;
- }
+void AgiEngine::unpackViewCelDataAGI256(AgiViewCel *celData, byte *compressedData, uint16 compressedSize) {
+ byte *rawBitmap = new byte[celData->width * celData->height];
+ int16 remainingHeight = celData->height;
+ int16 remainingWidth = celData->width;
+ byte curByte;
- // skip over width/height/trans|mirror data
- cofs += 3;
+ celData->rawBitmap = rawBitmap;
- vc->data = v + cofs;
+ while (remainingHeight) {
+ if (!compressedSize)
+ error("unexpected end of data, while unpacking AGI256 data");
- // If mirror_loop is pointing to the current loop,
- // then this is the original.
- if (vc->mirrorLoop == loop)
- vc->mirror = 0;
- } // cel
- } // loop
+ curByte = *compressedData++;
+ compressedSize--;
- return errOK;
+ if (curByte == 0) {
+ // Go to next vertical position
+ if (remainingWidth) {
+ // fill remaining bytes with clear key
+ memset(rawBitmap, celData->clearKey, remainingWidth);
+ rawBitmap += remainingWidth;
+ remainingWidth = 0;
+ }
+ } else {
+ *rawBitmap = curByte;
+ rawBitmap++;
+ }
+
+ if (curByte == 0) {
+ remainingWidth = celData->width;
+ remainingHeight--;
+ }
+ }
}
/**
* Unloads all data in a view resource
* @param n number of view resource
*/
-void AgiEngine::unloadView(int n) {
- int x;
+void AgiEngine::unloadView(int16 viewNr) {
+ AgiView *viewData = &_game.views[viewNr];
- debugC(5, kDebugLevelResources, "discard view %d", n);
- if (~_game.dirView[n].flags & RES_LOADED)
+ debugC(5, kDebugLevelResources, "discard view %d", viewNr);
+ if (!(_game.dirView[viewNr].flags & RES_LOADED))
return;
// Rebuild sprite list, see Sarien bug #779302
- _sprites->eraseBoth();
- _sprites->blitBoth();
- _sprites->commitBoth();
+ _sprites->eraseSprites();
- // free all the loops
- for (x = 0; x < _game.views[n].numLoops; x++)
- free(_game.views[n].loop[x].cel);
+ // free data
+ for (int16 loopNr = 0; loopNr < viewData->loopCount; loopNr++) {
+ AgiViewLoop *loopData = &viewData->loop[loopNr];
+ for (int16 celNr = 0; celNr < loopData->celCount; celNr++) {
+ AgiViewCel *celData = &loopData->cel[celNr];
- free(_game.views[n].loop);
- free(_game.views[n].rdata);
+ delete[] celData->rawBitmap;
+ }
+ delete[] loopData->cel;
+ }
+ delete[] viewData->loop;
- _game.dirView[n].flags &= ~RES_LOADED;
-}
+ if (viewData->description)
+ delete[] viewData->description;
-/**
- * Set a view table entry to use the specified cel of the current loop.
- * @param v pointer to view table entry
- * @param n number of cel
- */
-void AgiEngine::setCel(VtEntry *v, int n) {
- assert(v->viewData != NULL);
- assert(v->numCels >= n);
+ viewData->headerCycleTime = 0;
+ viewData->headerStepSize = 0;
+ viewData->description = nullptr;
+ viewData->loop = nullptr;
+ viewData->loopCount = 0;
- lSetCel(v, n);
+ // Mark this view as not loaded anymore
+ _game.dirView[viewNr].flags &= ~RES_LOADED;
- // If position isn't appropriate, update it accordingly
- clipViewCoordinates(v);
+ _sprites->buildAllSpriteLists();
+ _sprites->drawAllSpriteLists();
}
/**
- * Restrict view table entry's position so it stays wholly inside the screen.
- * Also take horizon into account when clipping if not set to ignore it.
- * @param v pointer to view table entry
+ * Set a view table entry to use the specified view resource.
+ * @param screenObj pointer to screen object
+ * @param viewNr number of AGI view resource
*/
-void AgiEngine::clipViewCoordinates(VtEntry *v) {
- if (v->xPos + v->xSize > _WIDTH) {
- v->flags |= fUpdatePos;
- v->xPos = _WIDTH - v->xSize;
+void AgiEngine::setView(ScreenObjEntry *screenObj, int16 viewNr) {
+ screenObj->viewData = &_game.views[viewNr];
+ screenObj->currentViewNr = viewNr;
+ screenObj->loopCount = screenObj->viewData->loopCount;
+ screenObj->viewReplaced = true;
+
+ if (getVersion() < 0x2000) {
+ screenObj->stepSize = screenObj->viewData->headerStepSize;
+ screenObj->cycleTime = screenObj->viewData->headerCycleTime;
+ screenObj->cycleTimeCount = 0;
}
- if (v->yPos - v->ySize + 1 < 0) {
- v->flags |= fUpdatePos;
- v->yPos = v->ySize - 1;
+ if (screenObj->currentLoopNr >= screenObj->loopCount) {
+ setLoop(screenObj, 0);
+ } else {
+ setLoop(screenObj, screenObj->currentLoopNr);
}
- if (v->yPos <= _game.horizon && (~v->flags & fIgnoreHorizon)) {
- v->flags |= fUpdatePos;
- v->yPos = _game.horizon + 1;
+}
+
+/**
+ * Set a view table entry to use the specified loop of the current view.
+ * @param screenObj pointer to screen object
+ * @param loopNr number of loop
+ */
+void AgiEngine::setLoop(ScreenObjEntry *screenObj, int16 loopNr) {
+ assert(screenObj->viewData != NULL);
+
+ if (screenObj->loopCount == 0) {
+ warning("setLoop() called on screen object %d, which has no loops (view %d)", screenObj->objectNr, screenObj->currentViewNr);
+ return;
}
- if (getVersion() < 0x2000) {
- v->flags |= fDontupdate;
+ if (loopNr >= screenObj->loopCount) {
+ // requested loop not existant
+ // instead of error()ing out, we instead clip it
+ // At least required for possibly Manhunter 1 according to previous comment when leaving the arcade machine
+ // TODO: check MH1
+ int16 requestedLoopNr = loopNr;
+
+ loopNr = screenObj->loopCount - 1;
+
+ warning("Non-existant loop requested for screen object %d", screenObj->objectNr);
+ warning("view %d, requested loop %d -> clipped to loop %d", screenObj->currentViewNr, requestedLoopNr, loopNr);
}
+ AgiViewLoop *curViewLoop = &_game.views[screenObj->currentViewNr].loop[loopNr];
+
+ screenObj->currentLoopNr = loopNr;
+ screenObj->loopData = curViewLoop;
+ screenObj->celCount = curViewLoop->celCount;
+
+ if (screenObj->currentCelNr >= screenObj->celCount) {
+ setCel(screenObj, 0);
+ } else {
+ setCel(screenObj, screenObj->currentCelNr);
+ }
}
/**
- * Set a view table entry to use the specified loop of the current view.
- * @param v pointer to view table entry
- * @param n number of loop
+ * Set a view table entry to use the specified cel of the current loop.
+ * @param screenObj pointer to screen object
+ * @param celNr number of cel
*/
-void AgiEngine::setLoop(VtEntry *v, int n) {
- assert(v->viewData != NULL);
- assert(v->numLoops >= n);
- lSetLoop(v, n);
- setCel(v, v->currentCel);
+void AgiEngine::setCel(ScreenObjEntry *screenObj, int16 celNr) {
+ assert(screenObj->viewData != NULL);
+
+ AgiViewLoop *curViewLoop = &_game.views[screenObj->currentViewNr].loop[screenObj->currentLoopNr];
+
+ // Added by Amit Vainsencher <amitv@subdimension.com> to prevent
+ // crash in KQ1 -- not in the Sierra interpreter
+ if (curViewLoop->celCount == 0) {
+ warning("setCel() called on screen object %d, which has no cels (view %d)", screenObj->objectNr, screenObj->currentViewNr);
+ return;
+ }
+
+ if (celNr >= screenObj->celCount) {
+ // requested cel not existant
+ // instead of error()ing out, we instead clip it
+ // At least required for King's Quest 3 on Apple IIgs - walking the planks death cutscene
+ // see bug #5832, which is a game bug!
+ int16 requestedCelNr = celNr;
+
+ celNr = screenObj->celCount - 1;
+
+ warning("Non-existant cel requested for screen object %d", screenObj->objectNr);
+ warning("view %d, loop %d, requested cel %d -> clipped to cel %d", screenObj->currentViewNr, screenObj->currentLoopNr, requestedCelNr, celNr);
+ }
+
+ screenObj->currentCelNr = celNr;
+
+ AgiViewCel *curViewCel;
+ curViewCel = &curViewLoop->cel[celNr];
+ screenObj->celData = curViewCel;
+ screenObj->xSize = curViewCel->width;
+ screenObj->ySize = curViewCel->height;
+
+ // If position isn't appropriate, update it accordingly
+ clipViewCoordinates(screenObj);
}
/**
- * Set a view table entry to use the specified view resource.
+ * Restrict view table entry's position so it stays wholly inside the screen.
+ * Also take horizon into account when clipping if not set to ignore it.
* @param v pointer to view table entry
- * @param n number of AGI view resource
*/
-void AgiEngine::setView(VtEntry *v, int n) {
- v->viewData = &_game.views[n];
- v->currentView = n;
- v->numLoops = v->viewData->numLoops;
- v->viewReplaced = true;
+void AgiEngine::clipViewCoordinates(ScreenObjEntry *screenObj) {
+ if (screenObj->xPos + screenObj->xSize > SCRIPT_WIDTH) {
+ screenObj->flags |= fUpdatePos;
+ screenObj->xPos = SCRIPT_WIDTH - screenObj->xSize;
+ }
+ if (screenObj->yPos - screenObj->ySize + 1 < 0) {
+ screenObj->flags |= fUpdatePos;
+ screenObj->yPos = screenObj->ySize - 1;
+ }
+ if (screenObj->yPos <= _game.horizon && (~screenObj->flags & fIgnoreHorizon)) {
+ screenObj->flags |= fUpdatePos;
+ screenObj->yPos = _game.horizon + 1;
+ }
if (getVersion() < 0x2000) {
- v->stepSize = v->viewData->rdata[0];
- v->cycleTime = v->viewData->rdata[1];
- v->cycleTimeCount = 0;
+ screenObj->flags |= fDontupdate;
}
- setLoop(v, v->currentLoop >= v->numLoops ? 0 : v->currentLoop);
+
}
/**
* Set the view table entry as updating.
* @param v pointer to view table entry
*/
-void AgiEngine::startUpdate(VtEntry *v) {
+void AgiEngine::startUpdate(ScreenObjEntry *v) {
if (~v->flags & fUpdate) {
- _sprites->eraseBoth();
-
+ _sprites->eraseSprites();
v->flags |= fUpdate;
- _sprites->blitBoth();
- _sprites->commitBoth();
+ _sprites->buildAllSpriteLists();
+ _sprites->drawAllSpriteLists();
}
}
@@ -326,13 +563,12 @@ void AgiEngine::startUpdate(VtEntry *v) {
* Set the view table entry as non-updating.
* @param v pointer to view table entry
*/
-void AgiEngine::stopUpdate(VtEntry *v) {
- if (v->flags & fUpdate) {
- _sprites->eraseBoth();
-
- v->flags &= ~fUpdate;
- _sprites->blitBoth();
- _sprites->commitBoth();
+void AgiEngine::stopUpdate(ScreenObjEntry *viewPtr) {
+ if (viewPtr->flags & fUpdate) {
+ _sprites->eraseSprites();
+ viewPtr->flags &= ~fUpdate;
+ _sprites->buildAllSpriteLists();
+ _sprites->drawAllSpriteLists();
}
}
@@ -351,67 +587,68 @@ static int loopTable4[] = {
* This function is called at the end of each interpreter cycle
* to update the view table entries and blit the sprites.
*/
-void AgiEngine::updateViewtable() {
- VtEntry *v;
- int i, loop;
+void AgiEngine::updateScreenObjTable() {
+ ScreenObjEntry *screenObj;
+ int16 changeCount, loopNr;
- i = 0;
- for (v = _game.viewTable; v < &_game.viewTable[MAX_VIEWTABLE]; v++) {
- if ((v->flags & (fAnimated | fUpdate | fDrawn)) != (fAnimated | fUpdate | fDrawn)) {
+ changeCount = 0;
+ for (screenObj = _game.screenObjTable; screenObj < &_game.screenObjTable[SCREENOBJECTS_MAX]; screenObj++) {
+ if ((screenObj->flags & (fAnimated | fUpdate | fDrawn)) != (fAnimated | fUpdate | fDrawn)) {
continue;
}
- i++;
+ changeCount++;
- loop = 4;
- if (~v->flags & fFixLoop) {
- switch (v->numLoops) {
+ loopNr = 4;
+ if (!(screenObj->flags & fFixLoop)) {
+ switch (screenObj->loopCount) {
case 2:
case 3:
- loop = loopTable2[v->direction];
+ loopNr = loopTable2[screenObj->direction];
break;
case 4:
- loop = loopTable4[v->direction];
+ loopNr = loopTable4[screenObj->direction];
break;
default:
// for KQ4
if (getVersion() == 0x3086 || getGameID() == GID_KQ4)
- loop = loopTable4[v->direction];
+ loopNr = loopTable4[screenObj->direction];
break;
}
}
// AGI 2.272 (ddp, xmas) doesn't test step_time_count!
- if (loop != 4 && loop != v->currentLoop) {
+ if (loopNr != 4 && loopNr != screenObj->currentLoopNr) {
if (getVersion() <= 0x2272 ||
- v->stepTimeCount == 1) {
- setLoop(v, loop);
+ screenObj->stepTimeCount == 1) {
+ setLoop(screenObj, loopNr);
}
}
- if (~v->flags & fCycling)
- continue;
-
- if (v->cycleTimeCount == 0)
- continue;
-
- if (--v->cycleTimeCount == 0) {
- updateView(v);
- v->cycleTimeCount = v->cycleTime;
+ if (screenObj->flags & fCycling) {
+ if (screenObj->cycleTimeCount) {
+ screenObj->cycleTimeCount--;
+ if (screenObj->cycleTimeCount == 0) {
+ updateView(screenObj);
+ screenObj->cycleTimeCount = screenObj->cycleTime;
+ }
+ }
}
}
- if (i) {
- _sprites->eraseUpdSprites();
+ if (changeCount) {
+ _sprites->eraseRegularSprites();
updatePosition();
- _sprites->blitUpdSprites();
- _sprites->commitUpdSprites();
- _game.viewTable[0].flags &= ~(fOnWater | fOnLand);
+ _sprites->buildRegularSpriteList();
+ _sprites->drawRegularSpriteList();
+ _sprites->showRegularSpriteList();
+
+ _game.screenObjTable[SCREENOBJECTS_EGO_ENTRY].flags &= ~(fOnWater | fOnLand);
}
}
-bool AgiEngine::isEgoView(const VtEntry* v) {
- return v == _game.viewTable;
+bool AgiEngine::isEgoView(const ScreenObjEntry* screenObj) {
+ return screenObj == _game.screenObjTable;
}
} // End of namespace Agi
diff --git a/engines/agi/view.h b/engines/agi/view.h
index b82fbe04d7..3afe3dc84b 100644
--- a/engines/agi/view.h
+++ b/engines/agi/view.h
@@ -25,36 +25,40 @@
namespace Agi {
-struct ViewCel {
+struct AgiViewCel {
uint8 height;
uint8 width;
- uint8 transparency;
- uint8 mirrorLoop;
- uint8 mirror;
- uint8 *data;
+ uint8 clearKey;
+ bool mirrored;
+ byte *rawBitmap;
};
-struct ViewLoop {
- int numCels;
- struct ViewCel *cel;
+struct AgiViewLoop {
+ int16 celCount;
+ AgiViewCel *cel;
};
/**
* AGI view resource structure.
*/
struct AgiView {
- int numLoops;
- struct ViewLoop *loop;
- bool agi256_2;
- char *descr;
- uint8 *rdata;
+ byte headerStepSize;
+ byte headerCycleTime;
+ byte *description;
+ int16 loopCount;
+ AgiViewLoop *loop;
+
+ //struct ViewLoop *loop;
+ //bool agi256_2;
+ //byte *resourceData;
};
enum MotionType {
kMotionNormal = 0,
kMotionWander = 1,
kMotionFollowEgo = 2,
- kMotionMoveObj = 3
+ kMotionMoveObj = 3,
+ kMotionEgo = 4
};
enum CycleType {
@@ -65,60 +69,73 @@ enum CycleType {
};
enum ViewFlags {
- fDrawn = (1 << 0),
- fIgnoreBlocks = (1 << 1),
- fFixedPriority = (1 << 2),
- fIgnoreHorizon = (1 << 3),
- fUpdate = (1 << 4),
- fCycling = (1 << 5),
- fAnimated = (1 << 6),
- fMotion = (1 << 7),
- fOnWater = (1 << 8),
- fIgnoreObjects = (1 << 9),
- fUpdatePos = (1 << 10),
- fOnLand = (1 << 11),
- fDontupdate = (1 << 12),
- fFixLoop = (1 << 13),
- fDidntMove = (1 << 14),
- fAdjEgoXY = (1 << 15)
+ fDrawn = (1 << 0), // 0x0001
+ fIgnoreBlocks = (1 << 1), // 0x0002
+ fFixedPriority = (1 << 2), // 0x0004
+ fIgnoreHorizon = (1 << 3), // 0x0008
+ fUpdate = (1 << 4), // 0x0010
+ fCycling = (1 << 5), // 0x0020
+ fAnimated = (1 << 6), // 0x0040
+ fMotion = (1 << 7), // 0x0080
+ fOnWater = (1 << 8), // 0x0100
+ fIgnoreObjects = (1 << 9), // 0x0200
+ fUpdatePos = (1 << 10), // 0x0400
+ fOnLand = (1 << 11), // 0x0800
+ fDontupdate = (1 << 12), // 0x1000
+ fFixLoop = (1 << 13), // 0x2000
+ fDidntMove = (1 << 14), // 0x4000
+ fAdjEgoXY = (1 << 15) // 0x8000
};
/**
- * AGI view table entry
+ * AGI screen object table entry
*/
-struct VtEntry {
+struct ScreenObjEntry {
+ int16 objectNr; // 0-255 -> regular screenObjTable, -1 -> addToPic-view
uint8 stepTime;
uint8 stepTimeCount;
- uint8 entry;
int16 xPos;
int16 yPos;
- uint8 currentView;
+ uint8 currentViewNr;
bool viewReplaced;
struct AgiView *viewData;
- uint8 currentLoop;
- uint8 numLoops;
- struct ViewLoop *loopData;
- uint8 currentCel;
- uint8 numCels;
- struct ViewCel *celData;
- struct ViewCel *celData2;
- int16 xPos2;
- int16 yPos2;
- void *s;
+ uint8 currentLoopNr;
+ uint8 loopCount;
+ struct AgiViewLoop *loopData;
+ uint8 currentCelNr;
+ uint8 celCount;
+ struct AgiViewCel *celData;
+ //int16 xPos2;
+ //int16 yPos2;
int16 xSize;
int16 ySize;
+
+ int16 xPos_prev;
+ int16 yPos_prev;
+ int16 xSize_prev;
+ int16 ySize_prev;
+
uint8 stepSize;
uint8 cycleTime;
uint8 cycleTimeCount;
uint8 direction;
- MotionType motion;
+ MotionType motionType;
CycleType cycle;
uint8 priority;
uint16 flags;
- uint8 parm1;
- uint8 parm2;
- uint8 parm3;
- uint8 parm4;
+ // kMotionMoveObj
+ int16 move_x;
+ int16 move_y;
+ uint8 move_stepSize;
+ uint8 move_flag;
+ // kMotionFollowEgo
+ uint8 follow_stepSize;
+ uint8 follow_flag;
+ uint8 follow_count;
+ // kMotionWander
+ uint8 wander_count;
+ // end of motion related variables
+ uint8 loop_flag;
}; // struct vt_entry
} // End of namespace Agi
diff --git a/engines/agi/words.cpp b/engines/agi/words.cpp
index 438c1ce354..7eca2b82c8 100644
--- a/engines/agi/words.cpp
+++ b/engines/agi/words.cpp
@@ -21,22 +21,23 @@
*/
#include "agi/agi.h"
+#include "agi/words.h"
#include "common/textconsole.h"
namespace Agi {
-//
-// Local implementation to avoid problems with strndup() used by
-// gcc 3.2 Cygwin (see #635984)
-//
-static char *myStrndup(const char *src, int n) {
- char *tmp = strncpy((char *)malloc(n + 1), src, n);
- tmp[n] = 0;
- return tmp;
+Words::Words(AgiEngine *vm) {
+ _vm = vm;
+
+ clearEgoWords();
+}
+
+Words::~Words() {
+ clearEgoWords();
}
-int AgiEngine::loadWords_v1(Common::File &f) {
+int Words::loadDictionary_v1(Common::File &f) {
char str[64];
int k;
@@ -55,18 +56,21 @@ int AgiEngine::loadWords_v1(Common::File &f) {
// And store it in our internal dictionary
if (k > 0) {
- AgiWord *w = new AgiWord;
- w->word = myStrndup(str, k + 1);
- w->id = f.readUint16LE();
- _game.words[str[0] - 'a'].push_back(w);
- debug(3, "'%s' (%d)", w->word, w->id);
+ WordEntry *newWord = new WordEntry;
+ byte firstCharNr = str[0] - 'a';
+
+ newWord->word = Common::String(str, k + 1); // myStrndup(str, k + 1);
+ newWord->id = f.readUint16LE();
+
+ _dictionaryWords[firstCharNr].push_back(newWord);
+ debug(3, "'%s' (%d)", newWord->word.c_str(), newWord->id);
}
} while((uint8)str[0] != 0xFF);
return errOK;
}
-int AgiEngine::loadWords(const char *fname) {
+int Words::loadDictionary(const char *fname) {
Common::File fp;
if (!fp.open(fname)) {
@@ -99,10 +103,10 @@ int AgiEngine::loadWords(const char *fname) {
// See bug #3615061
if (str[0] == 'a' + i) {
// And store it in our internal dictionary
- AgiWord *w = new AgiWord;
- w->word = myStrndup(str, k);
- w->id = fp.readUint16BE();
- _game.words[i].push_back(w);
+ WordEntry *newWord = new WordEntry;
+ newWord->word = Common::String(str, k);
+ newWord->id = fp.readUint16BE();
+ _dictionaryWords[i].push_back(newWord);
}
k = fp.readByte();
@@ -119,115 +123,249 @@ int AgiEngine::loadWords(const char *fname) {
return errOK;
}
-void AgiEngine::unloadWords() {
- for (int i = 0; i < 26; i++)
- _game.words[i].clear();
-}
+void Words::unloadDictionary() {
+ for (int16 firstCharNr = 0; firstCharNr < 26; firstCharNr++) {
+ Common::Array<WordEntry *> &dictionary = _dictionaryWords[firstCharNr];
+ int16 dictionarySize = dictionary.size();
-/**
- * Find a word in the dictionary
- * Uses an algorithm hopefully like the one Sierra used. Returns the ID
- * of the word and the length in flen. Returns -1 if not found.
- */
-int AgiEngine::findWord(const char *word, int *flen) {
- int c;
- int result = -1;
-
- debugC(2, kDebugLevelScripts, "find_word(%s)", word);
-
- if (word[0] >= 'a' && word[0] <= 'z')
- c = word[0] - 'a';
- else
- return -1;
-
- *flen = 0;
- Common::Array<AgiWord *> &a = _game.words[c];
-
- for (int i = 0; i < (int)a.size(); i++) {
- int wlen = strlen(a[i]->word);
- // Keep looking till we find the word itself, or the whole phrase.
- // Try to find the best match (i.e. the longest matching phrase).
- if (!strncmp(a[i]->word, word, wlen) && (word[wlen] == 0 || word[wlen] == 0x20) && wlen >= *flen) {
- *flen = wlen;
- result = a[i]->id;
+ for (int16 dictionaryWordNr = 0; dictionaryWordNr < dictionarySize; dictionaryWordNr++) {
+ delete dictionary[dictionaryWordNr];
}
- }
- return result;
+ _dictionaryWords[firstCharNr].clear();
+ }
}
-void AgiEngine::dictionaryWords(char *msg) {
- char *p = NULL;
- char *q = NULL;
- int wid, wlen;
-
- assert(msg);
-
- debugC(2, kDebugLevelScripts, "msg = \"%s\"", msg);
+void Words::clearEgoWords() {
+ for (int16 wordNr = 0; wordNr < MAX_WORDS; wordNr++) {
+ _egoWords[wordNr].id = 0;
+ _egoWords[wordNr].word.clear();
+ }
+ _egoWordCount = 0;
+}
- cleanInput();
- for (p = msg; *p && getvar(vWordNotFound) == 0;) {
- if (*p == 0x20)
- p++;
+static bool isCharSeparator(const char curChar) {
+ switch (curChar) {
+ case ' ':
+ case ',':
+ case '.':
+ case '?':
+ case '!':
+ case '(':
+ case ')':
+ case ';':
+ case ':':
+ case '[':
+ case ']':
+ case '{':
+ case '}':
+ return true;
+ break;
+ default:
+ break;
+ }
+ return false;
+}
- if (*p == 0)
- break;
+static bool isCharInvalid(const char curChar) {
+ switch (curChar) {
+ case 0x27: // '
+ case 0x60: // `
+ case '-':
+ case '\\':
+ case '"':
+ return true;
+ break;
+ default:
+ break;
+ }
+ return false;
+}
- wid = findWord(p, &wlen);
- debugC(2, kDebugLevelScripts, "find_word(p) == %d", wid);
+void Words::cleanUpInput(const char *rawUserInput, Common::String &cleanInput) {
+ byte curChar = 0;
- switch (wid) {
- case -1:
- debugC(2, kDebugLevelScripts, "unknown word");
- _game.egoWords[_game.numEgoWords].word = strdup(p);
+ cleanInput.clear();
- q = _game.egoWords[_game.numEgoWords].word;
+ curChar = *rawUserInput;
+ while (curChar) {
+ // skip separators / invalid characters
+ if (isCharSeparator(curChar) || isCharInvalid(curChar)) {
+ rawUserInput++;
+ curChar = *rawUserInput;
+ } else {
+ do {
+ if (!isCharInvalid(curChar)) {
+ // not invalid char, add it to the cleaned up input
+ cleanInput += curChar;
+ }
+
+ rawUserInput++;
+ curChar = *rawUserInput;
+
+ if (isCharSeparator(curChar)) {
+ cleanInput += ' ';
+ break;
+ }
+ } while (curChar);
+ }
+ }
+ if (cleanInput.hasSuffix(" ")) {
+ // ends with a space? remove it
+ cleanInput.deleteLastChar();
+ }
- _game.egoWords[_game.numEgoWords].id = 19999;
- setvar(vWordNotFound, 1 + _game.numEgoWords);
+ // Sierra compared independent of upper case and lower case
+ cleanInput.toLowercase();
+}
- _game.numEgoWords++;
+int16 Words::findWordInDictionary(const Common::String &userInput, uint16 userInputLen, uint16 userInputPos, uint16 &foundWordLen) {
+ uint16 userInputLeft = userInputLen - userInputPos;
+ uint16 wordStartPos = userInputPos;
+ int16 wordId = DICTIONARY_RESULT_UNKNOWN;
+ byte firstChar = userInput[userInputPos];
+ byte curUserInputChar = 0;
+
+ foundWordLen = 0;
+
+ if ((firstChar >= 'a') && (firstChar <= 'z')) {
+ // word has to start with a letter
+ if (((userInputPos + 1) == userInputLen) || (userInput[1] == ' ')) {
+ // current word is 1 char only?
+ if ((firstChar == 'a') || (firstChar == 'i')) {
+ // and it's "a" or "i"? -> then set current type to ignore
+ wordId = DICTIONARY_RESULT_IGNORE;
+ }
+ }
- p += strlen(p);
- break;
- case 0:
- // ignore this word
- debugC(2, kDebugLevelScripts, "ignore word");
- p += wlen;
- q = NULL;
- break;
- default:
- // an OK word
- debugC(3, kDebugLevelScripts, "ok word (%d)", wid);
- _game.egoWords[_game.numEgoWords].id = wid;
- _game.egoWords[_game.numEgoWords].word = myStrndup(p, wlen);
- _game.numEgoWords++;
- p += wlen;
- break;
+ Common::Array<WordEntry *> &dictionary = _dictionaryWords[firstChar - 'a'];
+ int16 dictionarySize = dictionary.size();
+
+ for (int16 dictionaryWordNr = 0; dictionaryWordNr < dictionarySize; dictionaryWordNr++) {
+ WordEntry *dictionaryEntry = dictionary[dictionaryWordNr];
+ uint16 dictionaryWordLen = dictionaryEntry->word.size();
+
+ if (dictionaryWordLen <= userInputLeft) {
+ // dictionary word is longer or same length as the remaining user input
+ uint16 curCompareLeft = dictionaryWordLen;
+ uint16 dictionaryWordPos = 0;
+ byte curDictionaryChar = 0;
+
+ userInputPos = wordStartPos;
+ while (curCompareLeft) {
+ curUserInputChar = userInput[userInputPos];
+ curDictionaryChar = dictionaryEntry->word[dictionaryWordPos];
+
+ if (curUserInputChar != curDictionaryChar)
+ break;
+
+ userInputPos++;
+ dictionaryWordPos++;
+ curCompareLeft--;
+ }
+
+ if (!curCompareLeft) {
+ // fully matched, remember match
+ wordId = dictionaryEntry->id;
+ foundWordLen = dictionaryWordLen;
+
+ // follow-up character in user-input is a space? add that to the word length
+ if (userInputLeft == foundWordLen) {
+ // perfect match -> break
+ break;
+ }
+ }
+ }
}
+ }
- if (*p) {
- debugC(2, kDebugLevelScripts, "p = %s", p);
- *p = 0;
- p++;
+ if (foundWordLen == 0) {
+ userInputPos = wordStartPos;
+ while (userInputPos < userInputLen) {
+ if (userInput[userInputPos] == ' ') {
+ break;
+ }
+ userInputPos++;
}
+ foundWordLen = userInputPos - wordStartPos;
+ }
+ return wordId;
+}
- if (q != NULL) {
- for (; (*q != 0 && *q != 0x20); q++)
- ;
- if (*q) {
- *q = 0;
- q++;
+void Words::parseUsingDictionary(char *rawUserInput) {
+ Common::String userInput;
+ const char *userInputPtr = nullptr;
+ uint16 userInputLen;
+ uint16 userInputPos = 0;
+ uint16 foundWordPos;
+ int16 foundWordId;
+ uint16 foundWordLen = 0;
+ uint16 wordCount = 0;
+
+ assert(rawUserInput);
+ debugC(2, kDebugLevelScripts, "parse: userinput = \"%s\"", rawUserInput);
+
+ // Reset result
+ clearEgoWords();
+
+ // clean up user input
+ cleanUpInput(rawUserInput, userInput);
+
+ userInputLen = userInput.size();
+ userInputPtr = userInput.c_str();
+
+ while (userInputPos < userInputLen) {
+ // Skip trailing space
+ if (userInput[userInputPos] == ' ')
+ userInputPos++;
+
+ foundWordPos = userInputPos;
+ foundWordId = findWordInDictionary(userInput, userInputLen, userInputPos, foundWordLen);
+
+ if (foundWordId != DICTIONARY_RESULT_IGNORE) {
+ // word not supposed to get ignored
+ // add it now
+ if (foundWordId != DICTIONARY_RESULT_UNKNOWN) {
+ // known word
+ _egoWords[wordCount].id = foundWordId;
+ }
+
+ _egoWords[wordCount].word = Common::String(userInputPtr + foundWordPos, foundWordLen);
+ debugC(2, kDebugLevelScripts, "found word %s (id %d)", _egoWords[wordCount].word.c_str(), _egoWords[wordCount].id);
+ wordCount++;
+
+ if (foundWordId == DICTIONARY_RESULT_UNKNOWN) {
+ // unknown word
+ _vm->setVar(VM_VAR_WORD_NOT_FOUND, wordCount);
+ break; // and exit now
}
}
+
+ userInputPos += foundWordLen;
}
- debugC(4, kDebugLevelScripts, "num_ego_words = %d", _game.numEgoWords);
- if (_game.numEgoWords > 0) {
- setflag(fEnteredCli, true);
- setflag(fSaidAcceptedInput, false);
+ _egoWordCount = wordCount;
+
+ debugC(4, kDebugLevelScripts, "ego word count = %d", _egoWordCount);
+ if (_egoWordCount > 0) {
+ _vm->setflag(VM_FLAG_ENTERED_CLI, true);
+ } else {
+ _vm->setflag(VM_FLAG_ENTERED_CLI, false);
}
+ _vm->setflag(VM_FLAG_SAID_ACCEPTED_INPUT, false);
+}
+
+uint16 Words::getEgoWordCount() {
+ return _egoWordCount;
+}
+const char *Words::getEgoWord(int16 wordNr) {
+ assert(wordNr >= 0 && wordNr < MAX_WORDS);
+ return _egoWords[wordNr].word.c_str();
+}
+uint16 Words::getEgoWordId(int16 wordNr) {
+ assert(wordNr >= 0 && wordNr < MAX_WORDS);
+ return _egoWords[wordNr].id;
}
} // End of namespace Agi
diff --git a/engines/agi/words.h b/engines/agi/words.h
new file mode 100644
index 0000000000..c7bf4829c3
--- /dev/null
+++ b/engines/agi/words.h
@@ -0,0 +1,69 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef AGI_WORDS_H
+#define AGI_WORDS_H
+
+namespace Agi {
+
+#define DICTIONARY_RESULT_UNKNOWN -1
+#define DICTIONARY_RESULT_IGNORE 0
+
+struct WordEntry {
+ uint16 id;
+ Common::String word;
+};
+
+class Words {
+public:
+ Words(AgiEngine *vm);
+ ~Words();
+
+private:
+ AgiEngine *_vm;
+
+ // Dictionary
+ Common::Array<WordEntry *> _dictionaryWords[26];
+
+ WordEntry _egoWords[MAX_WORDS];
+ uint16 _egoWordCount;
+
+public:
+ uint16 getEgoWordCount();
+ const char *getEgoWord(int16 wordNr);
+ uint16 getEgoWordId(int16 wordNr);
+
+ int loadDictionary_v1(Common::File &f);
+ int loadDictionary(const char *fname);
+ void unloadDictionary();
+
+ void clearEgoWords();
+ void parseUsingDictionary(char *rawUserInput);
+
+private:
+ void cleanUpInput(const char *userInput, Common::String &cleanInput);
+ int16 findWordInDictionary(const Common::String &userInput, uint16 userInputLen, uint16 userInputPos, uint16 &foundWordLen);
+};
+
+} // End of namespace Agi
+
+#endif /* AGI_WORDS_H */