diff options
Diffstat (limited to 'engines/agi/agi.cpp')
-rw-r--r-- | engines/agi/agi.cpp | 784 |
1 files changed, 334 insertions, 450 deletions
diff --git a/engines/agi/agi.cpp b/engines/agi/agi.cpp index 2b5d7137bc..60c8d1f3ef 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" @@ -38,13 +37,17 @@ #include "graphics/cursorman.h" #include "audio/mididrv.h" -#include "audio/mixer.h" #include "agi/agi.h" +#include "agi/font.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,274 +57,22 @@ 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; +void AgiEngine::wait(uint32 msec, bool busy) { + uint32 endTime = _system->getMillis() + msec; - default: - break; - } + if (busy) { + _gfx->setMouseCursor(true); // Busy mouse cursor } -} - -void AgiEngine::pollTimer() { - _lastTick += 50; - while (_system->getMillis() < _lastTick) { - processEvents(); + do { + processScummVMEvents(); _console->onFrame(); - _system->delayMillis(10); - _system->updateScreen(); - } - - _lastTick = _system->getMillis(); -} - -void AgiEngine::pause(uint32 msec) { - uint32 endTime = _system->getMillis() + msec; - - _gfx->setCursor(_renderMode == Common::kRenderAmiga, true); - - while (_system->getMillis() < endTime) { - processEvents(); _system->updateScreen(); _system->delayMillis(10); - } - _gfx->setCursor(_renderMode == Common::kRenderAmiga); -} + } while (_system->getMillis() < endTime); -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; - } + if (busy) { + _gfx->setMouseCursor(); // regular mouse cursor } } @@ -335,13 +86,11 @@ int AgiEngine::agiInit() { _game.adjMouseX = _game.adjMouseY = 0; // reset all flags to false and all variables to 0 - for (i = 0; i < MAX_FLAGS; i++) - _game.flags[i] = 0; - for (i = 0; i < MAX_VARS; i++) - _game.vars[i] = 0; + memset(_game.flags, 0, sizeof(_game.flags)); + memset(_game.vars, 0, sizeof(_game.vars)); // 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 +102,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)); - initWords(); + memset(&_game.addToPicView, 0, sizeof(struct ScreenObjEntry)); + + _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 @@ -378,13 +129,13 @@ int AgiEngine::agiInit() { switch (getVersion() >> 12) { case 2: debug("Emulating Sierra AGI v%x.%03x", - (int)(getVersion() >> 12) & 0xF, - (int)(getVersion()) & 0xFFF); + (int)(getVersion() >> 12) & 0xF, + (int)(getVersion()) & 0xFFF); break; case 3: debug("Emulating Sierra AGI v%x.002.%03x", - (int)(getVersion() >> 12) & 0xF, - (int)(getVersion()) & 0xFFF); + (int)(getVersion() >> 12) & 0xF, + (int)(getVersion()) & 0xFFF); break; } @@ -394,17 +145,13 @@ 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."); if (_game.gameFlags & ID_AGDS) debug(1, "AGDS mode enabled."); - ec = _loader->init(); // load vol files, etc + ec = _loader->init(); // load vol files, etc if (ec == errOK) ec = _loader->loadObjects(OBJECTS); @@ -413,12 +160,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 @@ -428,10 +172,16 @@ int AgiEngine::agiInit() { // GUI Predictive Dialog, but DS Word Completion is probably broken due to this... #endif - _egoHoldKey = false; + _keyHoldMode = false; _game.mouseFence.setWidth(0); // Reset + // Reset in-game timer + inGameTimerReset(); + + // Sync volume settings from ScummVM system settings + setVolumeViaSystemSetting(); + return ec; } @@ -443,42 +193,45 @@ 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 - agiUnloadResources(); // unload resources in memory - _loader->unloadResource(rLOGIC, 0); + if (!_loader) + return errOK; + + _words->clearEgoWords(); // remove all words from memory + agiUnloadResources(); // unload resources in memory + _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 { @@ -505,18 +258,11 @@ struct GameSettings { }; AgiBase::AgiBase(OSystem *syst, const AGIGameDescription *gameDesc) : Engine(syst), _gameDescription(gameDesc) { - // Assign default values to the config manager, in case settings are missing - ConfMan.registerDefault("originalsaveload", "false"); - ConfMan.registerDefault("altamigapalette", "false"); - ConfMan.registerDefault("mousesupport", "true"); - _noSaveLoadAllowed = false; _rnd = new Common::RandomSource("agi"); _sound = 0; - _fontData = NULL; - initFeatures(); initVersion(); } @@ -528,27 +274,79 @@ AgiBase::~AgiBase() { } void AgiBase::initRenderMode() { + 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 = Common::kRenderEGA; - if (ConfMan.hasKey("platform")) { - Common::Platform platform = Common::parsePlatform(ConfMan.get("platform")); - _renderMode = (platform == Common::kPlatformAmiga) ? Common::kRenderAmiga : Common::kRenderEGA; + switch (platform) { + case Common::kPlatformDOS: + // Keep EGA + break; + case Common::kPlatformAmiga: + _renderMode = Common::kRenderAmiga; + break; + case Common::kPlatformApple2GS: + _renderMode = Common::kRenderApple2GS; + break; + case Common::kPlatformAtariST: + _renderMode = Common::kRenderAtariST; + break; + case Common::kPlatformMacintosh: + _renderMode = Common::kRenderMacintosh; + break; + default: + break; + } + + // If render mode is explicitly set, force rendermode + switch (configRenderMode) { + case Common::kRenderCGA: + _renderMode = Common::kRenderCGA; + break; + case Common::kRenderEGA: + _renderMode = Common::kRenderEGA; + break; + case Common::kRenderVGA: + _renderMode = Common::kRenderVGA; + break; + case Common::kRenderHercG: + _renderMode = Common::kRenderHercG; + break; + case Common::kRenderHercA: + _renderMode = Common::kRenderHercA; + break; + case Common::kRenderAmiga: + _renderMode = Common::kRenderAmiga; + break; + case Common::kRenderApple2GS: + _renderMode = Common::kRenderApple2GS; + break; + case Common::kRenderAtariST: + _renderMode = Common::kRenderAtariST; + break; + case Common::kRenderMacintosh: + _renderMode = Common::kRenderMacintosh; + 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 = Common::kRenderVGA; } } -AgiEngine::AgiEngine(OSystem *syst, const AGIGameDescription *gameDesc) : AgiBase(syst, gameDesc) { +const byte *AgiBase::getFontData() { + return _font->getFontData(); +} +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"); @@ -566,22 +364,17 @@ 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; - } - - // 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. + _game.mouseHidden = true; } _game._vm = this; - _game.clockEnabled = false; - _game.state = STATE_INIT; + _game.gfxMode = true; _keyQueueStart = 0; _keyQueueEnd = 0; @@ -590,40 +383,49 @@ 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; - _firstSlot = 0; resetControllers(); setupOpcodes(); _game._curLogic = NULL; - _timerHack = 0; + _veryFirstInitialCycle = true; + _instructionCounter = 0; + resetGetVarSecondsHeuristic(); - _lastSaveTime = 0; - _lastTick = 0; - - memset(_keyQueue, 0, sizeof(_keyQueue)); - memset(_predictiveResult, 0, sizeof(_predictiveResult)); + _setVolumeBrokenFangame = false; // for further study see AgiEngine::setVolumeViaScripts() - _sprites = NULL; - _picture = NULL; - _loader = NULL; - _console = NULL; + _lastSaveTime = 0; - _egoHoldKey = false; + _playTimeInSecondsAdjust = 0; + _lastUsedPlayTimeInCycles = 0; + _lastUsedPlayTimeInSeconds = 0; + _passedPlayTimeCycles = 0; + memset(_keyQueue, 0, sizeof(_keyQueue)); + _console = nullptr; + _font = nullptr; + _gfx = nullptr; + _sound = nullptr; + _picture = nullptr; + _sprites = nullptr; + _text = nullptr; + _loader = nullptr; + _menu = nullptr; + _systemUI = nullptr; + _inventory = nullptr; + + _keyHoldMode = false; + + _artificialDelayCurrentRoom = 0; + _artificialDelayCurrentPicture = 0; } void AgiEngine::initialize() { @@ -641,7 +443,7 @@ void AgiEngine::initialize() { // Default sound is the proper PCJr emulation _soundemu = SOUND_EMU_PCJR; } else { - switch (MidiDriver::getMusicType(MidiDriver::detectDevice(MDT_PCSPK|MDT_AMIGA|MDT_ADLIB|MDT_PCJR|MDT_MIDI))) { + switch (MidiDriver::getMusicType(MidiDriver::detectDevice(MDT_PCSPK | MDT_AMIGA | MDT_ADLIB | MDT_PCJR | MDT_MIDI))) { case MT_PCSPK: _soundemu = SOUND_EMU_PC; break; @@ -663,38 +465,33 @@ void AgiEngine::initialize() { initRenderMode(); - _buttonStyle = AgiButtonStyle(_renderMode); - _defaultButtonStyle = AgiButtonStyle(); _console = new Console(this); - _gfx = new GfxMgr(this); + _words = new Words(this); + _font = new GfxFont(this); + _gfx = new GfxMgr(this, _font); _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); - _gfx->initMachine(); + _font->init(); + _gfx->initVideo(); + + _text->init(_systemUI); _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; - _lastTick = _system->getMillis(); - debugC(2, kDebugLevelMain, "Detect game"); if (agiDetectGame() == errOK) { - _game.state = STATE_LOADED; debugC(2, kDebugLevelMain, "game loaded"); } else { warning("Could not open AGI game"); @@ -703,34 +500,42 @@ void AgiEngine::initialize() { debugC(2, kDebugLevelMain, "Init sound"); } -AgiEngine::~AgiEngine() { - // If the engine hasn't been initialized yet via - // AgiEngine::initialize(), don't attempt to free any resources, as - // they haven't been allocated. Fixes bug #1742432 - AGI: Engine - // crashes if no game is detected - if (_game.state == STATE_INIT) { - return; - } +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(); +} + +AgiEngine::~AgiEngine() { agiDeinit(); delete _loader; - _gfx->deinitVideo(); + if (_gfx) { + _gfx->deinitVideo(); + } + delete _inventory; + delete _systemUI; + delete _menu; + delete _text; delete _sprites; delete _picture; - free(_game.sbufOrig); - _gfx->deinitMachine(); delete _gfx; + delete _font; + delete _words; delete _console; } Common::Error AgiBase::init() { - - // Initialize backend - initGraphics(320, 200, false); - initialize(); - _gfx->gfxSetPalette(); + _gfx->setPalette(true); return Common::kNoError; } @@ -739,87 +544,166 @@ Common::Error AgiEngine::go() { if (_game.mouseEnabled) { CursorMan.showMouse(true); } - setTotalPlayTime(0); - - if (_game.state < STATE_LOADED) { - do { - mainCycle(); - } while (_game.state < STATE_RUNNING); - } + inGameTimerReset(); runGame(); 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; - - char *features = strdup(ConfMan.get("features").c_str()); - const char *feature[100]; - int numFeatures = 0; - - char *tok = strtok(features, " "); - if (tok) { - do { - feature[numFeatures++] = tok; - } while ((tok = strtok(NULL, " ")) != NULL); - } else { - feature[numFeatures++] = features; +void AgiEngine::syncSoundSettings() { + Engine::syncSoundSettings(); + + setVolumeViaSystemSetting(); +} + +// WORKAROUND: +// Sometimes Sierra printed some text on the screen and did a room change immediately afterwards expecting the +// interpreter to load the data for a bit of time. This of course doesn't happen in our AGI, so we try to +// detect such situations via heuristic and then delay the game for a bit. +// In those cases a wait mouse cursor will be shown. +// +// Scenes that need this: +// +// Gold Rush: +// - During Stagecoach path, after getting solving the steep hill "Congratulations!!!" (NewRoom) +// - when following your mule "Yet right on his tail!!!" (NewRoom/NewPicture - but room 123 stays the same) +// 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) +// - right at the end of the asteroids "That was mighty close!" (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) +// - Climbing into shuttle on planet Labion. "You open the hatch and head on in." (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::artificialDelay_Reset() { + nonBlockingText_Forget(); + _artificialDelayCurrentRoom = -1; + _artificialDelayCurrentPicture = -1; +} + +void AgiEngine::artificialDelay_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; + } + } +} + +// WORKAROUND: +// On Apple IIgs, there are situations like for example the Police Quest 1 intro, where music is playing +// and then the scripts switch to a new room, expecting it to load for a bit of time. In ScummVM this results +// in music getting cut off, because our loading is basically done in an instant. This also happens in the +// original interpreter, when you use a faster CPU in emulation. +// +// That's why there is an additional table, where one can add such situations to it. +// These issues are basically impossible to detect, because sometimes music is also supposed to play throughout +// multiple rooms. +// +// Normally all text-based issues should get detected by the current heuristic. Do not add those in here. + +// script, description, signature patch +static const AgiArtificialDelayEntry artificialDelayTable[] = { + { GID_GOLDRUSH, Common::kPlatformApple2GS, ARTIFICIALDELAYTYPE_NEWROOM, 14, 21, 2200 }, // Stagecoach path: right after getting on it in Brooklyn + { GID_PQ1, Common::kPlatformApple2GS, ARTIFICIALDELAYTYPE_NEWPICTURE, 1, 2, 2200 }, // Intro: music track is supposed to finish before credits screen. Developers must have assumed that room loading would take that long. + { GID_MH1, Common::kPlatformApple2GS, ARTIFICIALDELAYTYPE_NEWPICTURE, 155, 183, 2200 }, // Happens, when hitting fingers at bar + { GID_AGIDEMO, Common::kPlatformUnknown, ARTIFICIALDELAYTYPE_END, -1, -1, 0 } +}; + +uint16 AgiEngine::artificialDelay_SearchTable(AgiArtificialDelayTriggerType triggerType, int16 orgNr, int16 newNr) { + if (getPlatform() != Common::kPlatformApple2GS) { + return 0; + } + + const AgiArtificialDelayEntry *delayEntry = artificialDelayTable; + + while (delayEntry->triggerType != ARTIFICIALDELAYTYPE_END) { + if (triggerType == delayEntry->triggerType) { + if ((orgNr == delayEntry->orgNr) && (newNr == delayEntry->newNr)) { + if ((getGameID() == delayEntry->gameId) && (getPlatform() == delayEntry->platform)) { + warning("artificial delay forced"); + return delayEntry->millisecondsDelay; + } + } + } + + delayEntry++; } + return 0; +} + +void AgiEngine::artificialDelayTrigger_NewRoom(int16 newRoomNr) { + uint16 millisecondsDelay = 0; + + //warning("artificial delay trigger: room %d -> new room %d", _artificialDelayCurrentRoom, newRoomNr); - 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; + if (!_game.automaticRestoreGame) { + millisecondsDelay = artificialDelay_SearchTable(ARTIFICIALDELAYTYPE_NEWROOM, _artificialDelayCurrentRoom, newRoomNr); + + if (_game.nonBlockingTextShown) { + if (newRoomNr != _artificialDelayCurrentRoom) { + if (millisecondsDelay < 2000) { + // wait a bit, we detected non-blocking text + millisecondsDelay = 2000; // 2 seconds + } } } + + if (millisecondsDelay) { + wait(millisecondsDelay, true); // set busy mouse cursor + _game.nonBlockingTextShown = false; + } } - free(features); + _artificialDelayCurrentRoom = newRoomNr; +} + +void AgiEngine::artificialDelayTrigger_DrawPicture(int16 newPictureNr) { + uint16 millisecondsDelay = 0; + + //warning("artificial delay trigger: picture %d -> new picture %d", _artificialDelayCurrentPicture, newPictureNr); + + if (!_game.automaticRestoreGame) { + millisecondsDelay = artificialDelay_SearchTable(ARTIFICIALDELAYTYPE_NEWPICTURE, _artificialDelayCurrentPicture, newPictureNr); + + if (_game.nonBlockingTextShown) { + if (newPictureNr != _artificialDelayCurrentPicture) { + if (millisecondsDelay < 2000) { + // wait a bit, we detected non-blocking text + millisecondsDelay = 2000; // 2 seconds, set busy + } + } + } + + if (millisecondsDelay) { + wait(millisecondsDelay, true); // set busy mouse cursor + _game.nonBlockingTextShown = false; + } + } + _artificialDelayCurrentPicture = newPictureNr; } } // End of namespace Agi |