/* ScummVM - Scumm Interpreter * Copyright (C) 2006 The ScummVM project * * Copyright (C) 1999-2003 Sarien Team * * 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. * * $URL$ * $Id$ * */ #include "common/stdafx.h" #include "common/file.h" #include "common/savefile.h" #include "common/config-manager.h" #include "base/plugins.h" #include "backends/fs/fs.h" #include "graphics/cursorman.h" #include "sound/mididrv.h" #include "sound/mixer.h" #include "agi/agi.h" #include "agi/text.h" #include "agi/graphics.h" #include "agi/sprite.h" #include "agi/opcodes.h" #include "agi/keyboard.h" #include "agi/menu.h" #include "agi/savegame.h" namespace Agi { void gfx_set_palette(); extern int optind; struct agi_options opt; struct game_id_list game_info; struct agi_game game; static struct agi_loader *loader; /* loader */ extern struct agi_loader agi_v2; extern struct agi_loader agi_v3; static volatile uint32 tick_timer = 0; #define TICK_SECONDS 20 static int key_control = 0; static int key_alt = 0; static Console *_console; #define KEY_QUEUE_SIZE 16 static int key_queue[KEY_QUEUE_SIZE]; static int key_queue_start = 0; static int key_queue_end = 0; #define key_enqueue(k) do { key_queue[key_queue_end++] = (k); \ key_queue_end %= KEY_QUEUE_SIZE; } while (0) #define key_dequeue(k) do { (k) = key_queue[key_queue_start++]; \ key_queue_start %= KEY_QUEUE_SIZE; } while (0) static void process_events() { OSystem::Event event; int key = 0; while (g_system->pollEvent(event)) { switch (event.type) { case OSystem::EVENT_QUIT: deinit_video(); deinit_machine(); g_system->quit(); break; case OSystem::EVENT_LBUTTONDOWN: key = BUTTON_LEFT; mouse.button = 1; key_enqueue(key); mouse.x = event.mouse.x; mouse.y = event.mouse.y; break; case OSystem::EVENT_RBUTTONDOWN: key = BUTTON_RIGHT; mouse.button = 2; key_enqueue(key); mouse.x = event.mouse.x; mouse.y = event.mouse.y; break; case OSystem::EVENT_MOUSEMOVE: mouse.x = event.mouse.x; mouse.y = event.mouse.y; break; case OSystem::EVENT_LBUTTONUP: case OSystem::EVENT_RBUTTONUP: mouse.button = 0; break; case OSystem::EVENT_KEYDOWN: key_control = 0; key_alt = 0; if (event.kbd.flags == OSystem::KBD_CTRL && event.kbd.keycode == 'd') { _console->attach(); break; } if (event.kbd.flags & OSystem::KBD_CTRL) key_control = 1; if (event.kbd.flags & OSystem::KBD_ALT) key_alt = 1; switch (key = event.kbd.keycode) { case 256 + 20: // left arrow case 260: // key pad 4 key = KEY_LEFT; break; case 256 + 19: // right arrow case 262: // key pad 6 key = KEY_RIGHT; break; case 256 + 17: // up arrow case 264: // key pad 8 key = KEY_UP; break; case 256 + 18: // down arrow case 258: // key pad 2 key = KEY_DOWN; break; case 256 + 24: // page up case 265: // key pad 9 key = KEY_UP_RIGHT; break; case 256 + 25: // page down case 259: // key pad 3 key = KEY_DOWN_RIGHT; break; case 256 + 22: // home case 263: // key pad 7 key = KEY_UP_LEFT; break; case 256 + 23: // end case 257: // key pad 1 key = KEY_DOWN_LEFT; break; case 261: // key pad 5 key = KEY_STATIONARY; break; case '+': key = '+'; break; case '-': key = '-'; break; case 9: key = 0x0009; break; case 282: key = 0x3b00; break; case 283: key = 0x3c00; break; case 284: key = 0x3d00; break; case 285: key = 0x3e00; break; case 286: key = 0x3f00; break; case 287: key = 0x4000; break; case 288: key = 0x4100; break; case 289: key = 0x4200; break; case 290: key = 0x4300; break; case 291: key = 0x4400; break; case 292: key = KEY_STATUSLN; break; case 293: key = KEY_PRIORITY; break; case 27: key = 0x1b; break; case '\n': case '\r': key = KEY_ENTER; break; default: if (key < 256 && !isalpha(key)) { key = event.kbd.ascii; break; } if (key_control) key = (key & ~0x20) - 0x40; else if (key_alt) key = scancode_table[(key & ~0x20) - 0x41] << 8; else if (event.kbd.flags & OSystem::KBD_SHIFT) key = event.kbd.ascii; break; } if (key) key_enqueue(key); break; default: break; } } } int agi_is_keypress_low() { process_events(); return key_queue_start != key_queue_end; } void agi_timer_low() { static uint32 m = 0; uint32 dm; if (tick_timer < m) m = 0; while ((dm = tick_timer - m) < 5) { process_events(); if (_console->isAttached()) _console->onFrame(); g_system->delayMillis(10); g_system->updateScreen(); } m = tick_timer; } int agi_get_keypress_low() { int k; while (key_queue_start == key_queue_end) /* block */ agi_timer_low(); key_dequeue(k); return k; } static uint32 agi_timer_function_low(uint32 i) { tick_timer++; return i; } static void init_pri_table() { int i, p, y = 0; for (p = 1; p < 15; p++) { for (i = 0; i < 12; i++) { game.pri_table[y++] = p < 4 ? 4 : p; } } } int agi_init() { int ec, i; debug(2, "initializing"); debug(2, "game.ver = 0x%x", game.ver); /* 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; /* clear all resources and events */ for (i = 0; i < MAX_DIRS; i++) { memset(&game.views[i], 0, sizeof(struct agi_view)); memset(&game.pictures[i], 0, sizeof(struct agi_picture)); memset(&game.logics[i], 0, sizeof(struct agi_logic)); memset(&game.sounds[i], 0, sizeof(struct agi_sound)); } /* clear view table */ for (i = 0; i < MAX_VIEWTABLE; i++) memset(&game.view_table[i], 0, sizeof(struct vt_entry)); init_words(); menu = new Menu(); init_pri_table(); /* clear string buffer */ for (i = 0; i < MAX_STRINGS; i++) game.strings[i][0] = 0; /* setup emulation */ switch (loader->int_version >> 12) { case 2: report("Emulating Sierra AGI v%x.%03x\n", (int)(loader->int_version >> 12) & 0xF, (int)(loader->int_version) & 0xFFF); break; case 3: report("Emulating Sierra AGI v%x.002.%03x\n", (int)(loader->int_version >> 12) & 0xF, (int)(loader->int_version) & 0xFFF); break; } game.game_flags |= opt.amiga ? ID_AMIGA : 0; game.game_flags |= opt.agds ? ID_AGDS : 0; if (game.game_flags & ID_AMIGA) report("Amiga padded game detected.\n"); if (game.game_flags & ID_AGDS) report("AGDS mode enabled.\n"); ec = loader->init(); /* load vol files, etc */ if (ec == err_OK) ec = loader->load_objects(OBJECTS); /* note: demogs has no words.tok */ if (ec == err_OK) ec = loader->load_words(WORDS); /* FIXME: load IIgs instruments and samples */ /* load_instruments("kq.sys16"); */ /* Load logic 0 into memory */ if (ec == err_OK) ec = loader->load_resource(rLOGIC, 0); return ec; } /* * Public functions */ void agi_unload_resources() { int i; /* Make sure logic 0 is always loaded */ for (i = 1; i < MAX_DIRS; i++) { loader->unload_resource(rLOGIC, i); } for (i = 0; i < MAX_DIRS; i++) { loader->unload_resource(rVIEW, i); loader->unload_resource(rPICTURE, i); loader->unload_resource(rSOUND, i); } } int agi_deinit() { int ec; clean_input(); /* remove all words from memory */ agi_unload_resources(); /* unload resources in memory */ loader->unload_resource(rLOGIC, 0); ec = loader->deinit(); unload_objects(); unload_words(); clear_image_stack(); return ec; } int agi_detect_game() { int ec = err_OK; loader = &agi_v2; ec = loader->detect_game(); if (ec != err_OK) { loader = &agi_v3; ec = loader->detect_game(); } return ec; } int agi_version() { return loader->version; } int agi_get_release() { return loader->int_version; } void agi_set_release(int n) { loader->int_version = n; } int agi_load_resource(int r, int n) { int i; i = loader->load_resource(r, n); #ifdef PATCH_LOGIC if (r == rLOGIC) patch_logic(n); #endif return i; } int agi_unload_resource(int r, int n) { return loader->unload_resource(r, n); } const char *_savePath; extern AGIMusic *g_agi_music; struct GameSettings { const char *gameid; const char *description; byte id; uint32 features; const char *detectname; }; static const GameSettings agi_settings[] = { {"agi", "AGI game", GID_AGI, MDT_ADLIB, "VIEWDIR"}, {NULL, NULL, 0, 0, NULL} }; Common::RandomSource * rnd; AgiEngine::AgiEngine(OSystem * syst) : Engine(syst) { // Setup mixer if (!_mixer->isReady()) { warning("Sound initialization failed."); } _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, ConfMan.getInt("sfx_volume")); _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, ConfMan.getInt("music_volume")); _savePath = _saveFileMan->getSavePath(); const GameSettings *g; const char *gameid = ConfMan.get("gameid").c_str(); for (g = agi_settings; g->gameid; ++g) if (!scumm_stricmp(g->gameid, gameid)) _gameId = g->id; rnd = new Common::RandomSource(); Common::addSpecialDebugLevel(kDebugLevelMain, "Main", "Generic debug level"); Common::addSpecialDebugLevel(kDebugLevelResources, "Resources", "Resources debugging"); Common::addSpecialDebugLevel(kDebugLevelSprites, "Sprites", "Sprites debugging"); Common::addSpecialDebugLevel(kDebugLevelInventory, "Inventory", "Inventory debugging"); Common::addSpecialDebugLevel(kDebugLevelInput, "Input", "Input events debugging"); Common::addSpecialDebugLevel(kDebugLevelMenu, "Menu", "Menu debugging"); Common::addSpecialDebugLevel(kDebugLevelScripts, "Scripts", "Scripts debugging"); Common::addSpecialDebugLevel(kDebugLevelSound, "Sound", "Sound debugging"); Common::addSpecialDebugLevel(kDebugLevelText, "Text", "Text output debugging"); game.clock_enabled = false; game.state = STATE_INIT; } void AgiEngine::initialize() { memset(&opt, 0, sizeof(struct agi_options)); opt.gamerun = GAMERUN_RUNGAME; opt.hires = true; // TODO: Some sound emulation modes do not fit our current music // drivers, and I'm not sure what they are. For now, they might // as well be called "PC Speaker" and "Not PC Speaker". switch (MidiDriver::detectMusicDriver(MDT_PCSPK)) { case MD_PCSPK: opt.soundemu = SOUND_EMU_PC; break; default: opt.soundemu = SOUND_EMU_NONE; break; } if (ConfMan.hasKey("render_mode")) opt.renderMode = Common::parseRenderMode(ConfMan.get("render_mode").c_str()); _console = new Console(this); init_machine(); game.color_fg = 15; game.color_bg = 0; *game.name = 0; game.sbuf = (uint8 *) calloc(_WIDTH, _HEIGHT); game.hires = (uint8 *) calloc(_WIDTH * 2, _HEIGHT); _sprites = new SpritesMan; _text = new TextMan; init_video(); tick_timer = 0; Common::g_timer->installTimerProc((Common::Timer::TimerProc) agi_timer_function_low, 10 * 1000, NULL); game.ver = -1; /* Don't display the conf file warning */ debugC(2, kDebugLevelMain, "Detect game"); if (agi_detect_game() == err_OK) { game.state = STATE_LOADED; debugC(2, kDebugLevelMain, "game loaded"); } else { report("Could not open AGI game"); } debugC(2, kDebugLevelMain, "Init sound"); init_sound(); g_agi_music = new AGIMusic(_mixer); } AgiEngine::~AgiEngine() { agi_deinit(); delete g_agi_music; deinit_sound(); deinit_video(); delete _sprites; free(game.hires); free(game.sbuf); deinit_machine(); delete rnd; delete _console; } void AgiEngine::errorString(const char *buf1, char *buf2) { strcpy(buf2, buf1); } int AgiEngine::init() { // Initialize backend _system->beginGFXTransaction(); initCommonGFX(false); _system->initSize(320, 200); _system->endGFXTransaction(); initialize(); gfx_set_palette(); return 0; } int AgiEngine::go() { CursorMan.showMouse(true); report(" \nAGI engine " VERSION " is ready.\n"); if (game.state < STATE_LOADED) { do { main_cycle(); } while (game.state < STATE_RUNNING); if (game.ver < 0) game.ver = 0; /* Enable conf file warning */ } run_game(); return 0; } } // End of namespace Agi GameList Engine_AGI_gameIDList() { GameList games; const Agi::GameSettings *g = Agi::agi_settings; while (g->gameid) { games.push_back(*g); g++; } return games; } GameDescriptor Engine_AGI_findGameID(const char *gameid) { const Agi::GameSettings *g = Agi::agi_settings; while (g->gameid) { if (0 == scumm_stricmp(gameid, g->gameid)) break; g++; } return *g; } DetectedGameList Engine_AGI_detectGames(const FSList &fslist) { DetectedGameList detectedGames; const Agi::GameSettings * g; for (g = Agi::agi_settings; g->gameid; ++g) { // Iterate over all files in the given directory for (FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) { const char *gameName = file->displayName().c_str(); if (0 == scumm_stricmp(g->detectname, gameName)) { // Match found, add to list of candidates, then abort inner loop. detectedGames.push_back(*g); break; } } } return detectedGames; } PluginError Engine_AGI_create(OSystem *syst, Engine **engine) { assert(engine); *engine = new Agi::AgiEngine(syst); return kNoError; } REGISTER_PLUGIN(AGI, "AGI Engine");