From 107073537e4ad24e294e28a2a62f8d3ad33008ff Mon Sep 17 00:00:00 2001 From: PaweÅ‚ KoÅ‚odziejski Date: Tue, 23 May 2006 23:43:52 +0000 Subject: imported AGI engine svn-id: r22588 --- engines/agi/agi.cpp | 627 +++++++++++++++++++ engines/agi/agi.h | 545 +++++++++++++++++ engines/agi/agi_v2.cpp | 309 ++++++++++ engines/agi/agi_v3.cpp | 389 ++++++++++++ engines/agi/checks.cpp | 320 ++++++++++ engines/agi/console.cpp | 873 ++++++++++++++++++++++++++ engines/agi/console.h | 71 +++ engines/agi/cycle.cpp | 423 +++++++++++++ engines/agi/font.cpp | 295 +++++++++ engines/agi/global.cpp | 73 +++ engines/agi/graphics.cpp | 742 +++++++++++++++++++++++ engines/agi/graphics.h | 86 +++ engines/agi/id.cpp | 475 +++++++++++++++ engines/agi/inv.cpp | 212 +++++++ engines/agi/keyboard.cpp | 388 ++++++++++++ engines/agi/keyboard.h | 94 +++ engines/agi/list.h | 140 +++++ engines/agi/logic.cpp | 116 ++++ engines/agi/logic.h | 49 ++ engines/agi/lzw.cpp | 191 ++++++ engines/agi/lzw.h | 33 + engines/agi/menu.cpp | 503 +++++++++++++++ engines/agi/menu.h | 46 ++ engines/agi/module.mk | 42 ++ engines/agi/motion.cpp | 232 +++++++ engines/agi/objects.cpp | 167 +++++ engines/agi/op_cmd.cpp | 1513 ++++++++++++++++++++++++++++++++++++++++++++++ engines/agi/op_dbg.cpp | 353 +++++++++++ engines/agi/op_test.cpp | 419 +++++++++++++ engines/agi/opcodes.h | 55 ++ engines/agi/patches.cpp | 144 +++++ engines/agi/picture.cpp | 1034 +++++++++++++++++++++++++++++++ engines/agi/picture.h | 47 ++ engines/agi/savegame.cpp | 790 ++++++++++++++++++++++++ engines/agi/savegame.h | 50 ++ engines/agi/sound.cpp | 772 +++++++++++++++++++++++ engines/agi/sound.h | 167 +++++ engines/agi/sprite.cpp | 868 ++++++++++++++++++++++++++ engines/agi/sprite.h | 47 ++ engines/agi/text.cpp | 677 +++++++++++++++++++++ engines/agi/text.h | 48 ++ engines/agi/view.cpp | 396 ++++++++++++ engines/agi/view.h | 142 +++++ engines/agi/words.cpp | 213 +++++++ 44 files changed, 15176 insertions(+) create mode 100644 engines/agi/agi.cpp create mode 100644 engines/agi/agi.h create mode 100644 engines/agi/agi_v2.cpp create mode 100644 engines/agi/agi_v3.cpp create mode 100644 engines/agi/checks.cpp create mode 100644 engines/agi/console.cpp create mode 100644 engines/agi/console.h create mode 100644 engines/agi/cycle.cpp create mode 100644 engines/agi/font.cpp create mode 100644 engines/agi/global.cpp create mode 100644 engines/agi/graphics.cpp create mode 100644 engines/agi/graphics.h create mode 100644 engines/agi/id.cpp create mode 100644 engines/agi/inv.cpp create mode 100644 engines/agi/keyboard.cpp create mode 100644 engines/agi/keyboard.h create mode 100644 engines/agi/list.h create mode 100644 engines/agi/logic.cpp create mode 100644 engines/agi/logic.h create mode 100644 engines/agi/lzw.cpp create mode 100644 engines/agi/lzw.h create mode 100644 engines/agi/menu.cpp create mode 100644 engines/agi/menu.h create mode 100644 engines/agi/module.mk create mode 100644 engines/agi/motion.cpp create mode 100644 engines/agi/objects.cpp create mode 100644 engines/agi/op_cmd.cpp create mode 100644 engines/agi/op_dbg.cpp create mode 100644 engines/agi/op_test.cpp create mode 100644 engines/agi/opcodes.h create mode 100644 engines/agi/patches.cpp create mode 100644 engines/agi/picture.cpp create mode 100644 engines/agi/picture.h create mode 100644 engines/agi/savegame.cpp create mode 100644 engines/agi/savegame.h create mode 100644 engines/agi/sound.cpp create mode 100644 engines/agi/sound.h create mode 100644 engines/agi/sprite.cpp create mode 100644 engines/agi/sprite.h create mode 100644 engines/agi/text.cpp create mode 100644 engines/agi/text.h create mode 100644 engines/agi/view.cpp create mode 100644 engines/agi/view.h create mode 100644 engines/agi/words.cpp (limited to 'engines/agi') diff --git a/engines/agi/agi.cpp b/engines/agi/agi.cpp new file mode 100644 index 0000000000..7487cbecf9 --- /dev/null +++ b/engines/agi/agi.cpp @@ -0,0 +1,627 @@ +/* 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 "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; + +#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: + if (event.kbd.flags == OSystem::KBD_CTRL) { + key_control |= 1; + key = 0; + break; + } else if (event.kbd.flags == OSystem::KBD_ALT) { + key_alt |= 1; + key = 0; + break; + } else if (event.kbd.flags == OSystem::KBD_SHIFT) { + key = 0; + break; + } + + switch (key = event.kbd.keycode) { + case 256 + 20: // left arrow + key = KEY_LEFT; + break; + case 256 + 19: // right arrow + key = KEY_RIGHT; + break; + case 256 + 17: // up arrow + key = KEY_UP; + break; + case 256 + 18: // down arrow + key = KEY_DOWN; + break; + case 256 + 24: // page up + key = KEY_UP_RIGHT; + break; + case 256 + 25: // page down + key = KEY_DOWN_RIGHT; + break; + case 256 + 22: // home + key = KEY_UP_LEFT; + break; + case 256 + 23: // end + key = KEY_DOWN_LEFT; + 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 (!isalpha(key)) + break; + if (key_control) + key = (key & ~0x20) - 0x40; + else if (key_alt) + key = scancode_table[(key & ~0x20) - 0x41] << 8; + break; + } + if (key) + key_enqueue(key); + break; + case OSystem::EVENT_KEYUP: + if (event.kbd.flags == OSystem::KBD_CTRL) { + key_control &= ~1; + key = 0; + break; + } else if (event.kbd.flags == OSystem::KBD_ALT) { + key_alt &= ~1; + key = 0; + break; + } else if (event.kbd.flags == OSystem::KBD_SHIFT) { + key = 0; + break; + } + 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(); + 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_init(); + 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(); + + game.clock_enabled = false; + game.state = STATE_INIT; +} + +void AgiEngine::initialize() { + memset(&opt, 0, sizeof(struct agi_options)); + opt.gamerun = GAMERUN_RUNGAME; +#ifdef USE_HIRES + opt.hires = true; +#endif + opt.soundemu = SOUND_EMU_NONE; + + init_machine(); + + game.color_fg = 15; + game.color_bg = 0; + + *game.name = 0; + + game.sbuf = (uint8 *) calloc(_WIDTH, _HEIGHT); +#ifdef USE_HIRES + game.hires = (uint8 *) calloc(_WIDTH * 2, _HEIGHT); +#endif + + init_sprites(); + init_video(); + + tick_timer = 0; + Common::g_timer->installTimerProc((Common::Timer::TimerProc) agi_timer_function_low, 10 * 1000, NULL); + + console_init(); + + 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(); + deinit_sprites(); +#ifdef USE_HIRES + free(game.hires); +#endif + free(game.sbuf); + deinit_machine(); + delete rnd; +} + +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(); + + 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, "Scrpits", "Scripts debugging"); + Common::addSpecialDebugLevel(kDebugLevelSound, "Sound", "Sound debugging"); + Common::addSpecialDebugLevel(kDebugLevelText, "Text", "Text output debugging"); + + initialize(); + + gfx_set_palette(); + + return 0; +} + +int AgiEngine::go() { + _system->showMouse(true); + + report(" \nAGI engine " VERSION " is ready.\n"); + if (game.state < STATE_LOADED) { + console_prompt(); + 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"); diff --git a/engines/agi/agi.h b/engines/agi/agi.h new file mode 100644 index 0000000000..f6d890c80c --- /dev/null +++ b/engines/agi/agi.h @@ -0,0 +1,545 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2006 The ScummVM project + * + * Copyright (C) 1999-2001 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$ + * + */ + +#ifndef __AGI_H +#define __AGI_H + +#include "common/stdafx.h" +#include "common/scummsys.h" +#include "common/endian.h" +#include "common/util.h" +#include "common/file.h" +#include "common/savefile.h" +#include "common/system.h" + +#include "base/engine.h" + +namespace Agi { + +typedef signed int Err; + +/* + * Version and other definitions + */ +#define INLINE __inline +#define VERSION __DATE__ " " __TIME__ + +/* Default features -- can be overriden in portdefs.h */ +#define USE_CONSOLE +#define USE_PCM_SOUND +#define USE_IIGS_SOUND +#define USE_HIRES +#define USE_MOUSE +#define AGDS_SUPPORT + +#define TITLE "AGI engine" + +#define DIR_ "dir" +#define LOGDIR "logdir" +#define PICDIR "picdir" +#define VIEWDIR "viewdir" +#define SNDDIR "snddir" +#define OBJECTS "object" +#define WORDS "words.tok" + +#define MAX_DIRS 256 +#define MAX_VARS 256 +#define MAX_FLAGS (256 >> 3) +#define MAX_VIEWTABLE 255 /* KQ3 uses o255! */ +#define MAX_WORDS 20 +#define MAX_STRINGS 24 /* MAX_STRINGS + 1 used for get.num */ +#define MAX_STRINGLEN 40 +#ifndef MAX_PATH +#define MAX_PATH 260 +#endif + +#define _EMPTY 0xfffff +#define EGO_OWNED 0xff + +#define CRYPT_KEY_SIERRA "Avis Durgan" +#define CRYPT_KEY_AGDS "Alex Simkin" + +#ifndef INLINE +#define INLINE +#endif + +#ifndef USE_PCM_SOUND +#undef USE_IIGS_SOUND +#endif + +#define MSG_BOX_COLOUR 0x0f /* White */ +#define MSG_BOX_TEXT 0x00 /* Black */ +#define MSG_BOX_LINE 0x04 /* Red */ +#define STATUS_FG 0x00 /* Black */ +#define STATUS_BG 0x0f /* White */ +#define PATCH_LOGIC /* disable copy protection on some games */ + +} // End of namespace Agi + +/* AGI resources */ +#include "agi/console.h" +#include "agi/view.h" +#include "agi/picture.h" +#include "agi/logic.h" +#include "agi/sound.h" + +namespace Agi { + +int getflag(int); +void setflag(int, int); +void flipflag(int); +int getvar(int); +void setvar(int, int); +void decrypt(uint8 * mem, int len); +void release_sprites(void); +int main_cycle(void); +int view_pictures(void); +int parse_cli(int, char **); +int run_game(void); +int init_machine(void); +int deinit_machine(void); +int get_direction(int x, int y, int x0, int y0, int s); +void inventory(void); +void list_games(void); +uint32 match_crc(uint32, char *, int); +int v2id_game(void); +int v3id_game(void); +int v4id_game(uint32 ver); +void update_timer(void); +int get_app_dir(char *app_dir, unsigned int size); + +enum { + NO_GAMEDIR = 0, + GAMEDIR +}; + +enum AGIErrors { + err_OK = 0, + err_DoNothing, + err_BadCLISwitch, + err_InvalidAGIFile, + err_BadFileOpen, + err_NotEnoughMemory, + err_BadResource, + err_UnknownAGIVersion, + err_RestartGame, + err_NoLoopsInView, + err_ViewDataError, + err_NoGameList, + + err_Unk = 127 +}; + +enum kDebugLevels { + kDebugLevelMain = 1 << 0, + kDebugLevelResources = 1 << 1, + kDebugLevelSprites = 1 << 2, + kDebugLevelInventory = 1 << 3, + kDebugLevelInput = 1 << 4, + kDebugLevelMenu = 1 << 5, + kDebugLevelScripts = 1 << 6, + kDebugLevelSound = 1 << 7, + kDebugLevelText = 1 << 8 +}; + +/** + * AGI resources. + */ +enum { + rLOGIC = 1, + rSOUND, + rVIEW, + rPICTURE +}; + +enum { + RES_LOADED = 1, + RES_COMPRESSED = 0x40 +}; + +enum { + lCOMMAND_MODE = 1, + lTEST_MODE +}; + +struct game_id_list { + struct game_id_list *next; + uint32 version; + uint32 crc; + char *gName; + char *switches; +}; + +#ifdef USE_MOUSE +struct mouse { + int button; + unsigned int x; + unsigned int y; +}; +#endif + +/** + * Command-line options. + */ +struct agi_options { +#define GAMERUN_RUNGAME 0 +#define GAMERUN_PICVIEW 1 +#define GAMERUN_WORDS 2 +#define GAMERUN_OBJECTS 3 +#define GAMERUN_GAMES 4 +#define GAMERUN_CRC 5 + int gamerun; /**< game run mode*/ + int emuversion; /**< AGI version to emulate */ + int agds; /**< enable AGDS mode */ + int amiga; /**< enable Amiga mode */ + int fullscreen; /**< use full screen mode if available */ + int nosound; /**< disable sound */ + int egapal; /**< use PC EGA palette */ + int cgaemu; /**< use PC CGA emulation */ +#ifdef USE_HIRES + int hires; /**< use hi-res pictures */ +#endif + int soundemu; /**< sound emulation mode */ +#ifdef USE_MOUSE + int agimouse; /**< AGI Mouse 1.0 emulation */ +#endif +}; + +extern struct agi_options opt; +extern uint8 *exec_name; + +extern volatile uint32 clock_ticks; +extern volatile uint32 clock_count; +extern volatile uint32 msg_box_secs2; + +#ifdef USE_CONSOLE +extern struct agi_debug debug_; +#endif + +#ifdef USE_MOUSE +extern struct mouse mouse; +#endif + +int console_keyhandler(int); +int console_init(void); +void console_cycle(void); +void console_lock(void); +void console_prompt(void); +#define report printf + +enum GameId { + GID_AGI = 1 +}; + +extern Common::RandomSource * rnd; +extern const char *_savePath; + +class AgiEngine:public::Engine { + int _gameId; + + void errorString(const char *buf_input, char *buf_output); + +protected: + int init(); + int go(); + void shutdown(); + void initialize(); + + +public: + AgiEngine(OSystem * syst); + virtual ~ AgiEngine(); + int getGameId() { + return _gameId; +}}; + +#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 { + V_cur_room = 0, /* 0 */ + V_prev_room, + V_border_touch_ego, + V_score, + V_border_code, + V_border_touch_obj, /* 5 */ + V_ego_dir, + V_max_score, + V_free_pages, + V_word_not_found, + V_time_delay, /* 10 */ + V_seconds, + V_minutes, + V_hours, + V_days, + V_joystick_sensitivity, /* 15 */ + V_ego_view_resource, + V_agi_err_code, + V_agi_err_code_info, + V_key, + V_computer, /* 20 */ + V_window_reset, + V_soundgen, + V_volume, + V_max_input_chars, + V_sel_item, /* 25 */ + V_monitor +}; + +/** + * AGI flags + */ +enum { + F_ego_water = 0, /* 0 */ + F_ego_invisible, + F_entered_cli, + F_ego_touched_p2, + F_said_accepted_input, + F_new_room_exec, /* 5 */ + F_restart_game, + F_script_blocked, + F_joy_sensitivity, + F_sound_on, + F_debugger_on, /* 10 */ + F_logic_zero_firsttime, + F_restore_just_ran, + F_status_selects_items, + F_menus_work, + F_output_mode, /* 15 */ + F_auto_restart +}; + +struct agi_event { + uint16 data; + uint8 occured; +}; + +struct agi_object { + int location; + char *name; +}; + +struct agi_word { + int id; + char *word; +}; + +struct agi_dir { + uint8 volume; + uint32 offset; + uint32 len; + uint32 clen; + uint8 flags; + /* 0 = not in mem, can be freed + * 1 = in mem, can be released + * 2 = not in mem, cant be released + * 3 = in mem, cant be released + * 0x40 = was compressed + */ +}; + +struct agi_block { + int active; + int x1, y1; + int x2, y2; + uint8 *buffer; /* used for window background */ +}; + +#define EGO_VIEW_TABLE 0 +#define HORIZON 36 +#define _WIDTH 160 +#define _HEIGHT 168 + +/** + * AGI game structure. + * This structure contains all global data of an AGI game executed + * by the interpreter. + */ +struct agi_game { +#define STATE_INIT 0x00 +#define STATE_LOADED 0x01 +#define STATE_RUNNING 0x02 + int state; /**< state of the interpreter */ + + char name[8]; /**< lead in id (e.g. `GR' for goldrush) */ + char id[8]; /**< game id */ + uint32 crc; /**< game CRC */ + + /* game flags and variables */ + uint8 flags[MAX_FLAGS]; + /**< 256 1-bit flags */ + uint8 vars[MAX_VARS]; + /**< 256 variables */ + + /* internal variables */ + int horizon; /**< horizon y coordinate */ + int line_status; /**< line number to put status on */ + int line_user_input; + /**< line to put user input on */ + int line_min_print; + /**< num lines to print on */ + int cursor_pos; /**< column where the input cursor is */ + uint8 input_buffer[40]; + /**< buffer for user input */ + uint8 echo_buffer[40]; + /**< buffer for echo.line */ + int keypress; +#define INPUT_NORMAL 0x01 +#define INPUT_GETSTRING 0x02 +#define INPUT_MENU 0x03 +#define INPUT_NONE 0x04 + int input_mode; /**< keyboard input mode */ + int input_enabled; + /**< keyboard input enabled */ + int lognum; /**< current logic number */ + + /* internal flags */ + int player_control; + /**< player is in control */ + int quit_prog_now; + /**< quit now */ + int status_line; /**< status line on/off */ + int clock_enabled; + /**< clock is on/off */ + int exit_all_logics; + /**< break cycle after new.room */ + int picture_shown; + /**< show.pic has been issued */ + int has_prompt; /**< input prompt has been printed */ +#define ID_AGDS 0x00000001 +#define ID_AMIGA 0x00000002 + int game_flags; /**< agi options flags */ + + uint8 pri_table[_HEIGHT]; + /**< priority table */ + + /* windows */ + uint32 msg_box_ticks; + /**< timed message box tick counter */ + struct agi_block block; + struct agi_block window; + int has_window; + + /* graphics & text */ + int gfx_mode; + char cursor_char; + unsigned int color_fg; + unsigned int color_bg; + uint8 *sbuf; /**< 160x168 AGI screen buffer */ +#ifdef USE_HIRES + uint8 *hires; /**< 320x168 hi-res buffer */ +#endif + + /* player command line */ + struct agi_word ego_words[MAX_WORDS]; + int num_ego_words; + + unsigned int num_objects; + + struct agi_event ev_keyp[MAX_DIRS]; + /**< keyboard keypress events */ + char strings[MAX_STRINGS + 1][MAX_STRINGLEN]; + /**< strings */ + + /* directory entries for resources */ + struct agi_dir dir_logic[MAX_DIRS]; + struct agi_dir dir_pic[MAX_DIRS]; + struct agi_dir dir_view[MAX_DIRS]; + struct agi_dir dir_sound[MAX_DIRS]; + + /* resources */ + struct agi_picture pictures[MAX_DIRS]; + /**< AGI picture resources */ + struct agi_logic logics[MAX_DIRS]; + /**< AGI logic resources */ + struct agi_view views[MAX_DIRS]; /**< AGI view resources */ + struct agi_sound sounds[MAX_DIRS]; + /**< AGI sound resources */ + + /* view table */ + struct vt_entry view_table[MAX_VIEWTABLE]; + + int32 ver; /**< detected game version */ + + int simple_save; /**< select simple savegames */ +}; + +/** + * + */ +struct agi_loader { + int version; + int int_version; + int (*init) (void); + int (*deinit) (void); + int (*detect_game) (); + int (*load_resource) (int, int); + int (*unload_resource) (int, int); + int (*load_objects) (char *); + int (*load_words) (char *); +}; + +extern struct agi_game game; + +int agi_init(void); +int agi_deinit(void); +int agi_version(void); +int agi_get_release(void); +void agi_set_release(int); +int agi_detect_game(); +int agi_load_resource(int, int); +int agi_unload_resource(int, int); +void agi_unload_resources(void); + +/* words */ +int show_words(void); +int load_words(char *); +void unload_words(void); +int find_word(char *word, int *flen); +void dictionary_words(char *); + +/* objects */ +int show_objects(void); +int load_objects(char *fname); +int alloc_objects(int); +void unload_objects(void); +char *object_name(unsigned int); +int object_get_location(unsigned int); +void object_set_location(unsigned int, int); + +void new_input_mode(int); +void old_input_mode(void); + +int run_logic(int); + +void agi_timer_low(); +int agi_get_keypress_low(); +int agi_is_keypress_low(); + +} // End of namespace Agi + +#endif /* __AGI_H */ diff --git a/engines/agi/agi_v2.cpp b/engines/agi/agi_v2.cpp new file mode 100644 index 0000000000..deb849c200 --- /dev/null +++ b/engines/agi/agi_v2.cpp @@ -0,0 +1,309 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2006 The ScummVM project + * + * Copyright (C) 1999-2001 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 "agi/agi.h" + +namespace Agi { + +static int agi_v2_init(void); +static int agi_v2_deinit(void); +static int agi_v2_detect_game(); +static int agi_v2_load_resource(int, int); +static int agi_v2_unload_resource(int, int); +static int agi_v2_load_objects(char *); +static int agi_v2_load_words(char *); + +struct agi_loader agi_v2 = { + 2, + 0, + agi_v2_init, + agi_v2_deinit, + agi_v2_detect_game, + agi_v2_load_resource, + agi_v2_unload_resource, + agi_v2_load_objects, + agi_v2_load_words +}; + +static int agi_v2_detect_game() { + if (!Common::File::exists(LOGDIR) || + !Common::File::exists(PICDIR) || + !Common::File::exists(SNDDIR) || + !Common::File::exists(VIEWDIR)) + return err_InvalidAGIFile; + + agi_v2.int_version = 0x2917; /* setup for 2.917 */ + return v2id_game(); +} + +static int agi_v2_load_dir(struct agi_dir *agid, char *fname) { + Common::File fp; + uint8 *mem; + uint32 flen; + unsigned int i; + char *path; + + path = fname; + report("Loading directory: %s\n", path); + + if ((!fp.open(path))) { + return err_BadFileOpen; + } + + fp.seek(0, SEEK_END); + flen = fp.pos(); + fp.seek(0, SEEK_SET); + + if ((mem = (uint8 *) malloc(flen + 32)) == NULL) { + fp.close(); + return err_NotEnoughMemory; + } + + fp.read(mem, flen); + + /* set all directory resources to gone */ + for (i = 0; i < MAX_DIRS; i++) { + agid[i].volume = 0xff; + agid[i].offset = _EMPTY; + } + + /* build directory entries */ + for (i = 0; i < flen; i += 3) { + agid[i / 3].volume = *(mem + i) >> 4; + agid[i / 3].offset = READ_BE_UINT24(mem + i) & (uint32) _EMPTY; + debugC(3, kDebugLevelResources, "%d: volume %d, offset 0x%05x", i / 3, agid[i / 3].volume, agid[i / 3].offset); + } + + free(mem); + fp.close(); + + return err_OK; +} + +static int agi_v2_init() { + int ec = err_OK; + + /* load directory files */ + ec = agi_v2_load_dir(game.dir_logic, LOGDIR); + if (ec == err_OK) + ec = agi_v2_load_dir(game.dir_pic, PICDIR); + if (ec == err_OK) + ec = agi_v2_load_dir(game.dir_view, VIEWDIR); + if (ec == err_OK) + ec = agi_v2_load_dir(game.dir_sound, SNDDIR); + + return ec; +} + +static int agi_v2_deinit() { + int ec = err_OK; + +#if 0 + /* unload words */ + agi_v2_unload_words(); + + /* unload objects */ + agi_v2_unload_objects(); +#endif + + return ec; +} + +static int agi_v2_unload_resource(int t, int n) { + debugC(3, kDebugLevelResources, "unload resource"); + + switch (t) { + case rLOGIC: + unload_logic(n); + break; + case rPICTURE: + unload_picture(n); + break; + case rVIEW: + unload_view(n); + break; + case rSOUND: + unload_sound(n); + break; + } + + return err_OK; +} + +/* + * This function does noting but load a raw resource into memory, + * if further decoding is required, it must be done by another + * routine. NULL is returned if unsucsessfull. + */ +static uint8 *agi_v2_load_vol_res(struct agi_dir *agid) { + uint8 *data = NULL; + char x[MAX_PATH], *path; + Common::File fp; + unsigned int sig; + + sprintf(x, "vol.%i", agid->volume); + path = x; + debugC(3, kDebugLevelResources, "Vol res: path = %s", path); + + if (agid->offset != _EMPTY && fp.open(path)) { + debugC(3, kDebugLevelResources, "loading resource at offset %d", agid->offset); + fp.seek(agid->offset, SEEK_SET); + fp.read(&x, 5); + if ((sig = READ_BE_UINT16((uint8 *) x)) == 0x1234) { + agid->len = READ_LE_UINT16((uint8 *) x + 3); + data = (uint8 *) calloc(1, agid->len + 32); + if (data != NULL) { + fp.read(data, agid->len); + } else { + abort(); + } + } else { +#if 0 + /* FIXME: call some panic handler instead of + * deiniting directly + */ + deinit_video_mode(); +#endif + report("Error: bad signature %04x\n", sig); + // fprintf (stderr, "ACK! BAD RESOURCE!!!\n"); + return 0; + } + fp.close(); + } else { + /* we have a bad volume resource */ + /* set that resource to NA */ + agid->offset = _EMPTY; + } + + return data; +} + +/* + * Loads a resource into memory, a raw resource is loaded in + * with above routine, then further decoded here. + */ +int agi_v2_load_resource(int t, int n) { + int ec = err_OK; + uint8 *data = NULL; + + debugC(3, kDebugLevelResources, "(t = %d, n = %d)", t, n); + if (n > MAX_DIRS) + return err_BadResource; + + switch (t) { + case rLOGIC: + if (~game.dir_logic[n].flags & RES_LOADED) { + debugC(3, kDebugLevelResources, "loading logic resource %d", n); + agi_v2.unload_resource(rLOGIC, n); + + /* load raw resource into data */ + data = agi_v2_load_vol_res(&game.dir_logic[n]); + + game.logics[n].data = data; + ec = data ? decode_logic(n) : err_BadResource; + + game.logics[n].sIP = 2; + } + + /* if logic was cached, we get here */ + /* reset code pointers incase it was cached */ + + game.logics[n].cIP = game.logics[n].sIP; + break; + case rPICTURE: + /* 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 (game.dir_pic[n].flags & RES_LOADED) + break; + + /* if loaded but not cached, unload it */ + /* if cached but not loaded, etc */ + agi_v2.unload_resource(rPICTURE, n); + data = agi_v2_load_vol_res(&game.dir_pic[n]); + + if (data != NULL) { + game.pictures[n].rdata = data; + game.dir_pic[n].flags |= RES_LOADED; + } else { + ec = err_BadResource; + } + break; + case rSOUND: + debugC(3, kDebugLevelResources, "loading sound resource %d", n); + if (game.dir_sound[n].flags & RES_LOADED) + break; + + data = agi_v2_load_vol_res(&game.dir_sound[n]); + + if (data != NULL) { + game.sounds[n].rdata = data; + game.dir_sound[n].flags |= RES_LOADED; + decode_sound(n); + } else { + ec = err_BadResource; + } + break; + case rVIEW: + /* 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 (game.dir_view[n].flags & RES_LOADED) + break; + + debugC(3, kDebugLevelResources, "loading view resource %d", n); + agi_v2.unload_resource(rVIEW, n); + data = agi_v2_load_vol_res(&game.dir_view[n]); + if (data) { + game.views[n].rdata = data; + game.dir_view[n].flags |= RES_LOADED; + ec = decode_view(n); + } else { + ec = err_BadResource; + } + break; + default: + ec = err_BadResource; + break; + } + + return ec; +} + +static int agi_v2_load_objects(char *fname) { + return load_objects(fname); +} + +static int agi_v2_load_words(char *fname) { + return load_words(fname); +} + +} // End of namespace Agi diff --git a/engines/agi/agi_v3.cpp b/engines/agi/agi_v3.cpp new file mode 100644 index 0000000000..5c99472b78 --- /dev/null +++ b/engines/agi/agi_v3.cpp @@ -0,0 +1,389 @@ +/* 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 "agi/agi.h" +#include "agi/lzw.h" + +#include "backends/fs/fs.h" + +#include "common/config-manager.h" + +namespace Agi { + +static int agi_v3_init(void); +static int agi_v3_deinit(void); +static int agi_v3_detect_game(); +static int agi_v3_load_resource(int, int); +static int agi_v3_unload_resource(int, int); +static int agi_v3_load_objects(char *); +static int agi_v3_load_words(char *); + +struct agi_loader agi_v3 = { + 3, + 0, + agi_v3_init, + agi_v3_deinit, + agi_v3_detect_game, + agi_v3_load_resource, + agi_v3_unload_resource, + agi_v3_load_objects, + agi_v3_load_words +}; + +int agi_v3_detect_game() { + int ec = err_Unk; + bool found = false; + + FSList fslist; + FilesystemNode dir(ConfMan.get("path")); + + if (!dir.listDir(fslist, FilesystemNode::kListFilesOnly)) { + warning("AgiEngine: invalid game path '%s'", + dir.path().c_str()); + return err_InvalidAGIFile; + } + + for (FSList::const_iterator file = fslist.begin(); + file != fslist.end() && !found; ++file) { + Common::String f = file->displayName(); + f.toLowercase(); + + if (f.hasSuffix("vol.0")) { + strncpy(game.name, f.c_str(), f.size() > 5 ? f.size() - 5 : f.size()); + debugC(3, kDebugLevelMain, "game.name = %s", game.name); + agi_v3.int_version = 0x3149; // setup for 3.002.149 + ec = v3id_game(); + + found = true; + } + } + + if (!found) { + debugC(3, kDebugLevelMain, "not found"); + ec = err_InvalidAGIFile; + } + + return ec; +} + +static int agi_v3_load_dir(struct agi_dir *agid, Common::File *fp, + uint32 offs, uint32 len) { + int ec = err_OK; + uint8 *mem; + unsigned int i; + + fp->seek(offs, SEEK_SET); + if ((mem = (uint8 *) malloc(len + 32)) != NULL) { + fp->read(mem, len); + + /* set all directory resources to gone */ + for (i = 0; i < MAX_DIRS; i++) { + agid[i].volume = 0xff; + agid[i].offset = _EMPTY; + } + + /* build directory entries */ + for (i = 0; i < len; i += 3) { + agid[i / 3].volume = *(mem + i) >> 4; + agid[i / 3].offset = READ_BE_UINT24(mem + i) & (uint32) _EMPTY; + } + + free(mem); + } else { + ec = err_NotEnoughMemory; + } + + return ec; +} + +struct agi3vol { + uint32 sddr; + uint32 len; +}; + +int agi_v3_init(void) { + int ec = err_OK; + struct agi3vol agi_vol3[4]; + int i; + uint16 xd[4]; + Common::File fp; + Common::String path; + + path = Common::String(game.name) + DIR_; + + if (!fp.open(path)) { + printf("Failed to open \"%s\"\n", path.c_str()); + return err_BadFileOpen; + } + /* build offset table for v3 directory format */ + fp.read(&xd, 8); + fp.seek(0, SEEK_END); + + for (i = 0; i < 4; i++) + agi_vol3[i].sddr = READ_LE_UINT16((uint8 *) & xd[i]); + + agi_vol3[0].len = agi_vol3[1].sddr - agi_vol3[0].sddr; + agi_vol3[1].len = agi_vol3[2].sddr - agi_vol3[1].sddr; + agi_vol3[2].len = agi_vol3[3].sddr - agi_vol3[2].sddr; + agi_vol3[3].len = fp.pos() - agi_vol3[3].sddr; + + if (agi_vol3[3].len > 256 * 3) + agi_vol3[3].len = 256 * 3; + + fp.seek(0, SEEK_SET); + + /* read in directory files */ + ec = agi_v3_load_dir(game.dir_logic, &fp, agi_vol3[0].sddr, + agi_vol3[0].len); + + if (ec == err_OK) { + ec = agi_v3_load_dir(game.dir_pic, &fp, agi_vol3[1].sddr, agi_vol3[1].len); + } + + if (ec == err_OK) { + ec = agi_v3_load_dir(game.dir_view, &fp, agi_vol3[2].sddr, agi_vol3[2].len); + } + + if (ec == err_OK) { + ec = agi_v3_load_dir(game.dir_sound, &fp, agi_vol3[3].sddr, agi_vol3[3].len); + } + + return ec; +} + +int agi_v3_deinit() { + int ec = err_OK; + +#if 0 + /* unload words */ + agi_v3_unload_words(); + + /* unload objects */ + agi_v3_unload_objects(); +#endif + + return ec; +} + +int agi_v3_unload_resource(int t, int n) { + switch (t) { + case rLOGIC: + unload_logic(n); + break; + case rPICTURE: + unload_picture(n); + break; + case rVIEW: + unload_view(n); + break; + case rSOUND: + unload_sound(n); + break; + } + + return err_OK; +} + +/* + * This function does noting but load a raw resource into memory. + * If further decoding is required, it must be done by another + * routine. + * + * NULL is returned if unsucsessful. + */ +uint8 *agi_v3_load_vol_res(struct agi_dir *agid) { + char x[MAX_PATH]; + uint8 *data = NULL, *comp_buffer; + Common::File fp; + Common::String path; + + debugC(3, kDebugLevelResources, "(%p)", agid); + sprintf(x, "vol.%i", agid->volume); + path = Common::String(game.name) + x; + + if (agid->offset != _EMPTY && fp.open(path)) { + fp.seek(agid->offset, SEEK_SET); + fp.read(&x, 7); + + if (READ_BE_UINT16((uint8 *) x) != 0x1234) { +#if 0 + /* FIXME */ + deinit_video_mode(); +#endif + debugC(3, kDebugLevelResources, "path = %s", path.c_str()); + debugC(3, kDebugLevelResources, "offset = %d", agid->offset); + debugC(3, kDebugLevelResources, "x = %x %x", x[0], x[1]); + error("ACK! BAD RESOURCE"); + + g_system->quit(); + } + + agid->len = READ_LE_UINT16((uint8 *) x + 3); /* uncompressed size */ + agid->clen = READ_LE_UINT16((uint8 *) x + 5); /* compressed len */ + + comp_buffer = (uint8 *)calloc(1, agid->clen + 32); + fp.read(comp_buffer, agid->clen); + + if (x[2] & 0x80 || agid->len == agid->clen) { + /* do not decompress */ + data = comp_buffer; + +#if 0 + /* CM: added to avoid problems in + * convert_v2_v3_pic() when clen > len + * e.g. Sierra demo 4, first picture + * (Tue Mar 16 13:13:43 EST 1999) + */ + agid->len = agid->clen; + + /* Now removed to fix Gold Rush! in demo4 */ +#endif + } else { + /* it is compressed */ + data = (uint8 *)calloc(1, agid->len + 32); + LZW_expand(comp_buffer, data, agid->len); + free(comp_buffer); + agid->flags |= RES_COMPRESSED; + } + + fp.close(); + } else { + /* we have a bad volume resource */ + /* set that resource to NA */ + agid->offset = _EMPTY; + } + + return data; +} + +/* + * Loads a resource into memory, a raw resource is loaded in + * with above routine, then further decoded here. + */ +int agi_v3_load_resource(int t, int n) { + int ec = err_OK; + uint8 *data = NULL; + + if (n > MAX_DIRS) + return err_BadResource; + + switch (t) { + case rLOGIC: + /* load resource into memory, decrypt messages at the end + * and build the message list (if logic is in memory) + */ + if (~game.dir_logic[n].flags & RES_LOADED) { + /* if logic is already in memory, unload it */ + agi_v3.unload_resource(rLOGIC, n); + + /* load raw resource into data */ + data = agi_v3_load_vol_res(&game.dir_logic[n]); + game.logics[n].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 = decode_logic(n); + game.logics[n].sIP = 2; + } else { + ec = err_BadResource; + } + + /*logics[n].sIP=2; *//* saved IP = 2 */ + /*logics[n].cIP=2; *//* current IP = 2 */ + + game.logics[n].cIP = game.logics[n].sIP; + } + + /* if logic was cached, we get here */ + /* reset code pointers incase it was cached */ + + game.logics[n].cIP = game.logics[n].sIP; + break; + case rPICTURE: + /* if picture is currently NOT loaded *OR* cacheing is off, + * unload the resource (caching==off) and reload it + */ + if (~game.dir_pic[n].flags & RES_LOADED) { + agi_v3.unload_resource(rPICTURE, n); + data = agi_v3_load_vol_res(&game.dir_pic[n]); + if (data != NULL) { + data = convert_v3_pic(data, game.dir_pic[n].len); + game.pictures[n].rdata = data; + game.dir_pic[n].flags |= RES_LOADED; + } else { + ec = err_BadResource; + } + } + break; + case rSOUND: + if (game.dir_sound[n].flags & RES_LOADED) + break; + + if ((data = agi_v3_load_vol_res(&game.dir_sound[n])) != NULL) { + game.sounds[n].rdata = data; + game.dir_sound[n].flags |= RES_LOADED; + decode_sound(n); + } else { + ec = err_BadResource; + } + break; + case rVIEW: + /* 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 (game.dir_view[n].flags & RES_LOADED) + break; + + agi_v3.unload_resource(rVIEW, n); + if ((data = agi_v3_load_vol_res(&game.dir_view[n])) != NULL) { + game.views[n].rdata = data; + game.dir_view[n].flags |= RES_LOADED; + ec = decode_view(n); + } else { + ec = err_BadResource; + } + break; + default: + ec = err_BadResource; + break; + } + + return ec; +} + +static int agi_v3_load_objects(char *fname) { + return load_objects(fname); +} + +static int agi_v3_load_words(char *fname) { + return load_words(fname); +} + +} // End of namespace Agi diff --git a/engines/agi/checks.cpp b/engines/agi/checks.cpp new file mode 100644 index 0000000000..1042ef1579 --- /dev/null +++ b/engines/agi/checks.cpp @@ -0,0 +1,320 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2006 The ScummVM project + * + * Copyright (C) 1999-2001 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 "agi/agi.h" + +namespace Agi { + +static int check_position(struct vt_entry *v) { + debugC(4, kDebugLevelSprites, "check position @ %d, %d", v->x_pos, v->y_pos); + + if (v->x_pos < 0 || + v->x_pos + v->x_size > _WIDTH || + v->y_pos - v->y_size + 1 < 0 || + v->y_pos >= _HEIGHT || + ((~v->flags & IGNORE_HORIZON) && v->y_pos <= game.horizon)) { + debugC(4, kDebugLevelSprites, "check position failed: x=%d, y=%d, h=%d, w=%d", + v->x_pos, v->y_pos, v->x_size, v->y_size); + return 0; + } + + /* MH1 needs this, but it breaks LSL1 */ + if (agi_get_release() >= 0x3000) { + if (v->y_pos < v->y_size) + return 0; + } + + return 1; +} + +/** + * Check if there's another object on the way + */ +static int check_collision(struct vt_entry *v) { + struct vt_entry *u; + + if (v->flags & IGNORE_OBJECTS) + return 0; + + for (u = game.view_table; u < &game.view_table[MAX_VIEWTABLE]; u++) { + if ((u->flags & (ANIMATED | DRAWN)) != (ANIMATED | DRAWN)) + continue; + + if (u->flags & IGNORE_OBJECTS) + continue; + + /* Same object, check next */ + if (v->entry == u->entry) + continue; + + /* No horizontal overlap, check next */ + if (v->x_pos + v->x_size < u->x_pos || + v->x_pos > u->x_pos + u->x_size) + continue; + + /* Same y, return error! */ + if (v->y_pos == u->y_pos) + goto return_1; + + /* Crossed the baseline, return error! */ + if ((v->y_pos > u->y_pos && v->y_pos2 < u->y_pos2) || + (v->y_pos < u->y_pos && v->y_pos2 > u->y_pos2)) { + goto return_1; + } + } + + return 0; + + return_1: + debugC(4, kDebugLevelSprites, "check returns 1 (object %d)", v->entry); + return 1; +} + +static int check_priority(struct vt_entry *v) { + int i, trigger, water, pass, pri; + uint8 *p0; + + if (~v->flags & FIXED_PRIORITY) { + /* Priority bands */ + v->priority = game.pri_table[v->y_pos]; + } + + trigger = 0; + water = 0; + pass = 1; + + if (v->priority == 0x0f) + goto _check_ego; + + water = 1; + + p0 = &game.sbuf[v->x_pos + v->y_pos * _WIDTH]; + + for (i = 0; i < v->x_size; i++, p0++) { + pri = *p0 >> 4; + + if (pri == 0) { /* unconditional black. no go at all! */ + pass = 0; + break; + } + + if (pri == 3) /* water surface */ + continue; + + water = 0; + + if (pri == 1) { /* conditional blue */ + if (v->flags & IGNORE_BLOCKS) + continue; + + debugC(4, kDebugLevelSprites, "Blocks observed!"); + pass = 0; + break; + } + + if (pri == 2) { /* trigger */ + debugC(4, kDebugLevelSprites, "stepped on trigger"); +#ifdef USE_CONSOLE + if (!debug_.ignoretriggers) +#endif + trigger = 1; + } + } + + if (pass) { + if (!water && v->flags & ON_WATER) + pass = 0; + if (water && v->flags & ON_LAND) + pass = 0; + } + + _check_ego: + if (v->entry == 0) { + setflag(F_ego_touched_p2, trigger ? true : false); + setflag(F_ego_water, water ? true : false); + } + + return pass; +} + +/* + * Public functions + */ + +/** + * Update position of objects + * This function updates the position of all animated, updating view + * table entries according to its motion type, step size, etc. The + * new position must be valid according to the sprite positioning + * rules, otherwise the previous position will be kept. + */ +void update_position() { + struct vt_entry *v; + int x, y, old_x, old_y, border; + + game.vars[V_border_code] = 0; + game.vars[V_border_touch_ego] = 0; + game.vars[V_border_touch_obj] = 0; + + for (v = game.view_table; v < &game.view_table[MAX_VIEWTABLE]; v++) { + if ((v->flags & (ANIMATED | UPDATE | DRAWN)) != (ANIMATED | UPDATE | DRAWN)) { + continue; + } + + if (v->step_time_count != 0) { + if (--v->step_time_count != 0) + continue; + } + + v->step_time_count = v->step_time; + + x = old_x = v->x_pos; + y = old_y = v->y_pos; + + /* If object has moved, update its position */ + if (~v->flags & UPDATE_POS) { + 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->step_size * dx[v->direction]; + y += v->step_size * dy[v->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 && agi_get_release() == 0x3086) { /* KQ4 */ + x = 0; /* See bug #590462 */ + border = 4; + } else if (v->entry == 0 && x == 0 && v->flags & ADJ_EGO_XY) { + /* Extra test to walk west clicking the mouse */ + x = 0; + border = 4; + } else if (x + v->x_size > _WIDTH) { + x = _WIDTH - v->x_size; + border = 2; + } + + /* Check top/bottom borders. */ + if (y - v->y_size + 1 < 0) { + y = v->y_size - 1; + border = 1; + } else if (y > _HEIGHT - 1) { + y = _HEIGHT - 1; + border = 3; + } else if ((~v->flags & IGNORE_HORIZON) && 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->x_pos = x; + v->y_pos = y; + if (check_collision(v) || !check_priority(v)) { + v->x_pos = old_x; + v->y_pos = old_y; + border = 0; + fix_position(v->entry); + } + + if (border != 0) { + if (v == game.view_table) { + game.vars[V_border_touch_ego] = border; + } else { + game.vars[V_border_code] = v->entry; + game.vars[V_border_touch_obj] = border; + } + if (v->motion == MOTION_MOVE_OBJ) { + in_destination(v); + } + } + + v->flags &= ~UPDATE_POS; + } +} + +/** + * Adjust position of a sprite + * This function adjusts the position of a sprite moving it until + * certain criteria is matched. According to priority and control line + * data, a sprite may not always appear at the location we specified. + * This behaviour is also known as the "Budin-Sonneveld effect". + * + * @param n view table entry number + */ +void fix_position(int n) { + struct vt_entry *v = &game.view_table[n]; + int count, dir, size; + + debugC(4, kDebugLevelSprites, "adjusting view table entry #%d (%d,%d)", n, v->x_pos, v->y_pos); + + /* test horizon */ + if ((~v->flags & IGNORE_HORIZON) && v->y_pos <= game.horizon) + v->y_pos = game.horizon + 1; + + dir = 0; + count = size = 1; + + while (!check_position(v) || check_collision(v) || !check_priority(v)) { + switch (dir) { + case 0: /* west */ + v->x_pos--; + if (--count) + continue; + dir = 1; + break; + case 1: /* south */ + v->y_pos++; + if (--count) + continue; + dir = 2; + size++; + break; + case 2: /* east */ + v->x_pos++; + if (--count) + continue; + dir = 3; + break; + case 3: /* north */ + v->y_pos--; + if (--count) + continue; + dir = 0; + size++; + break; + } + + count = size; + } + + debugC(4, kDebugLevelSprites, "view table entry #%d position adjusted to (%d,%d)", n, v->x_pos, v->y_pos); +} + +} // End of namespace Agi diff --git a/engines/agi/console.cpp b/engines/agi/console.cpp new file mode 100644 index 0000000000..cbfa8b2b08 --- /dev/null +++ b/engines/agi/console.cpp @@ -0,0 +1,873 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2006 The ScummVM project + * + * Copyright (C) 1999-2002 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 "agi/agi.h" +#include "agi/graphics.h" +#include "agi/sprite.h" +#include "agi/text.h" +#include "agi/keyboard.h" +#include "agi/opcodes.h" +#include "agi/console.h" + +namespace Agi { + +#ifdef USE_CONSOLE + +/* + * The main interpreter engine has not been designed to have a console, and a few + * kludges were needed to make the console work. First of all, the main + * interpreter loop works at cycle level, not instruction level. To keep + * the illusion that the console is threaded, a console updating function + * is called from loops inside instructions such as get.string(). + */ + +#define CONSOLE_LINES_ONSCREEN 20 +#define CONSOLE_PROMPT "$" +#define CONSOLE_CURSOR_HOLLOW 1 +#define CONSOLE_CURSOR_SOLID 2 +#define CONSOLE_CURSOR_EMPTY 3 +#define CONSOLE_COLOR 14 +#define CONSOLE_SCROLLUP_KEY KEY_PGUP +#define CONSOLE_SCROLLDN_KEY KEY_PGDN +#define CONSOLE_START_KEY KEY_HOME +#define CONSOLE_END_KEY KEY_END +#define CONSOLE_INPUT_SIZE 39 + +struct console_command { + char cmd[8]; + char dsc[40]; + void (*handler) (void); +}; + +struct agi_console console; +struct agi_debug debug_; + +/* This used to be a linked list, but we reduce total memory footprint + * by implementing it as a statically allocated array. + */ +#define MAX_CCMD 16 +static struct console_command ccmd_list[MAX_CCMD]; +static int num_ccmd = 0; + +static uint8 has_console = 0; +static uint8 console_input = 0; + +static char *_p0, *_p1, *_p2, *_p3, *_p4, *_p5; /* FIXME: array */ +static char _p[80]; +static int _pn; + +/* + * Console line management + */ + +static int first_line = 0; + +static void add_console_line(char *s) { + int last_line; + + last_line = (CONSOLE_LINES_BUFFER - 1 + first_line) % CONSOLE_LINES_BUFFER; + + strncpy(console.line[last_line], s, CONSOLE_LINE_SIZE); + first_line %= CONSOLE_LINES_BUFFER; +} + +static char *get_console_line(int n) { + return console.line[(n + first_line) % CONSOLE_LINES_BUFFER]; +} + +static char *get_last_console_line() { + int last_line = (CONSOLE_LINES_BUFFER - 1 + first_line) % CONSOLE_LINES_BUFFER; + + return console.line[last_line]; +} + +/* + * Console command parsing + * 'o' commands added by Shaun Jackman , 11 Apr 2002 + */ + +static int console_parse(char *b) { + struct console_command *d; + int i; + + for (; *b && *b == ' '; b++) { + } /* eat spaces */ + for (; *b && b[strlen(b) - 1] == ' '; b[strlen(b) - 1] = 0) { + } + if (!*b) + return 0; + + /* get or set flag/var/obj values */ + if ((b[0] == 'f' || b[0] == 'v' || b[0] == 'o') && isdigit(b[1])) { + char *e; + int f = (int)strtoul(&b[1], &e, 10); + if (*e == 0) { + if (f >= 0 && f <= 255) { + switch (b[0]) { + case 'f': + report(getflag(f) ? "true\n" : "false\n"); + break; + case 'v': + report("%3d\n", getvar(f)); + break; + case 'o': + report("%3d]%-24s(%3d)\n", f, object_name(f), object_get_location(f)); + break; + } + return 0; + } + return -1; + } else if (*e == '=') { + int n = (int)strtoul(e + 1, NULL, 0); + switch (b[0]) { + case 'f': + setflag(f, !!n); + break; + case 'v': + setvar(f, n); + break; + case 'o': + object_set_location(f, n); + break; + } + return 0; + } + } + + /* tokenize the input line */ + if (strchr(b, ' ')) + strcpy(_p, strchr(b, ' ') + 1); + _p0 = strtok(b, " "); + _p1 = strtok(NULL, " "); + _p2 = strtok(NULL, " "); + _p3 = strtok(NULL, " "); + _p4 = strtok(NULL, " "); + _p5 = strtok(NULL, " "); + + /* set number of parameters. ugh, must rewrite this later */ + _pn = 5; + if (!_p5) + _pn = 4; + if (!_p4) + _pn = 3; + if (!_p3) + _pn = 2; + if (!_p2) + _pn = 1; + if (!_p1) + _pn = 0; + + debugC(5, kDebugLevelMain, "number of parameters: %d", _pn); + + for (i = 0; i < num_ccmd; i++) { + d = &ccmd_list[i]; + if (!strcmp(_p0, d->cmd) && d->handler) { + d->handler(); + return 0; + } + } + + for (i = 0; logic_names_cmd[i].name; i++) { + if (!strcmp(_p0, logic_names_cmd[i].name)) { + uint8 p[16]; + if (_pn != logic_names_cmd[i].num_args) { + report("AGI command wants %d arguments\n", logic_names_cmd[i].num_args); + return 0; + } + p[0] = _p1 ? (char)strtoul(_p1, NULL, 0) : 0; + p[1] = _p2 ? (char)strtoul(_p2, NULL, 0) : 0; + p[2] = _p3 ? (char)strtoul(_p3, NULL, 0) : 0; + p[3] = _p4 ? (char)strtoul(_p4, NULL, 0) : 0; + p[4] = _p5 ? (char)strtoul(_p5, NULL, 0) : 0; + + debugC(5, kDebugLevelMain, "ccmd: %s %d %d %d", logic_names_cmd[i].name, p[0], p[1], p[2]); + + execute_agi_command(i, p); + + return 0; + } + } + + return -1; +} + +/* + * Console commands + */ + +static void ccmd_help() { + int i; + + if (!_p1) { + report("Command Description\n"); + report("------- --------------------------------\n"); + for (i = 0; i < num_ccmd; i++) + report("%-8s%s\n", ccmd_list[i].cmd, ccmd_list[i].dsc); + return; + } + + for (i = 0; i < num_ccmd; i++) { + if (!strcmp(ccmd_list[i].cmd, _p1) && ccmd_list[i].handler) { + report("%s\n", ccmd_list[i].dsc); + return; + } + } + + report("Unknown command or no help available\n"); + + return; +} + +static void ccmd_ver() { + report(VERSION "\n"); + return; +} + +static void ccmd_crc() { + char name[80]; + report("0x%05x\n", game.crc); + if (match_crc(game.crc, name, 80)) + report("%s\n", name); + else + report("Unknown game\n"); + return; +} + +static void ccmd_quit() { + deinit_video(); + /* deinit_machine (); */ + exit(0); +} + +#if 0 +static void ccmd_exec() { + if (game.state < STATE_LOADED) { + report("No game loaded.\n"); + return; + } + + if (game.state >= STATE_RUNNING) { + report("Game already running.\n"); + return; + } + + report("Executing AGI game.\n"); + game.state = STATE_RUNNING; +} +#endif + +#ifdef USE_HIRES + +static void ccmd_hires() { + if (_pn != 1 || (strcmp(_p1, "on") && strcmp(_p1, "off"))) { + report("Usage: hires on|off\n"); + return; + } + + opt.hires = !strcmp(_p1, "on"); + erase_both(); + show_pic(); + blit_both(); + return; +} + +#endif + +static void ccmd_agiver() { + int ver, maj, min; + + ver = agi_get_release(); + maj = (ver >> 12) & 0xf; + min = ver & 0xfff; + + report(maj <= 2 ? "%x.%03x\n" : "%x.002.%03x\n", maj, min); + return; +} + +static void ccmd_flags() { + int i, j; + + report(" "); + for (j = 0; j < 10; j++) + report("%d ", j); + report("\n"); + + for (i = 0; i < 255;) { + report("%3d ", i); + for (j = 0; j < 10; j++, i++) { + report("%c ", getflag(i) ? 'T' : 'F'); + } + report("\n"); + } + return; +} + +static void ccmd_vars() { + int i, j; + + for (i = 0; i < 255;) { + for (j = 0; j < 5; j++, i++) { + report("%03d:%3d ", i, getvar(i)); + } + report("\n"); + } + return; +} + +#if 0 + +static void ccmd_say() { + setflag(F_entered_cli, true); + dictionary_words(_p); +} + +static void ccmd_inv() { + unsigned int i, j; + + for (j = i = 0; i < game.num_objects; i++) { + if (object_get_location(i) == EGO_OWNED) { + report("%3d]%-16.16s", i, object_name(i)); + if (j % 2) + report("\n"); + j++; + } + } + if (j == 0) { + report("none\n"); + return; + } + if (j % 2) + report("\n"); +} + +#endif + +static void ccmd_objs() { + unsigned int i; + + for (i = 0; i < game.num_objects; i++) { + report("%3d]%-24s(%3d)\n", i, object_name(i), object_get_location(i)); + } + return; +} + +static void ccmd_opcode() { + if (_pn != 1 || (strcmp(_p1, "on") && strcmp(_p1, "off"))) { + report("Usage: opcode on|off\n"); + return; + } + + debug_.opcodes = !strcmp(_p1, "on"); + return; +} + +static void ccmd_logic0() { + if (_pn != 1 || (strcmp(_p1, "on") && strcmp(_p1, "off"))) { + report("Usage: logic0 on|off\n"); + return; + } + + debug_.logic0 = !strcmp(_p1, "on"); + return; +} + +static void ccmd_trigger() { + if (_pn != 1 || (strcmp(_p1, "on") && strcmp(_p1, "off"))) { + report("Usage: trigger on|off\n"); + return; + } + + debug_.ignoretriggers = strcmp(_p1, "on"); + return; +} + +static void ccmd_step() { + debug_.enabled = 1; + + if (_pn == 0) { + debug_.steps = 1; + return; + } + + debug_.steps = strtoul(_p1, NULL, 0); + return; +} + +static void ccmd_debug() { + debug_.enabled = 1; + debug_.steps = 0; + return; +} + +static void ccmd_cont() { + debug_.enabled = 0; + debug_.steps = 0; + return; +} + +/* + * Register console commands + */ +static void console_cmd(char *cmd, char *dsc, void (*handler) (void)) { + assert(num_ccmd < MAX_CCMD); + + strcpy(ccmd_list[num_ccmd].cmd, cmd); + strcpy(ccmd_list[num_ccmd].dsc, dsc); + ccmd_list[num_ccmd].handler = handler; + + num_ccmd++; +} + +/* Console reporting */ + +/* A slightly modified strtok() for report() */ +static char *get_token(char *s, char d) { + static char *x; + char *n, *m; + + if (s) + x = s; + + m = x; + n = strchr(x, d); + + if (n) { + *n = 0; + x = n + 1; + } else { + x = strchr(x, 0); + } + + return m; +} + +static void build_console_lines(int n) { + int j, y1; + char *line; + + clear_console_screen(GFX_HEIGHT - n * 10); + + for (j = CONSOLE_LINES_ONSCREEN - n; j < CONSOLE_LINES_ONSCREEN; j++) { + line = get_console_line(console.first_line + j); + print_text_console(line, 0, j, strlen(line) + 1, CONSOLE_COLOR, 0); + } + + y1 = console.y - n * 10; + if (y1 < 0) + y1 = 0; + + /* CM: + * This will cause blinking when using menus+console, but this + * function is called by report() which can be called from any + * point with or without sprites placed. If we protect the + * flush_block() call with redraw/release sprites cloning will + * happen when activating the console. This could be fixed by + * keeping a control flag in redraw/release to not do the + * actions twice, but I don't want to do that -- not yet. + */ + flush_block(0, y1, GFX_WIDTH - 1, console.y); +} + +/* + * Public functions + */ + +int console_init() { + console_cmd("agiver", "Show emulated Sierra AGI version", ccmd_agiver); + console_cmd("cont", "Resume interpreter execution", ccmd_cont); + console_cmd("debug", "Stop interpreter execution", ccmd_debug); + console_cmd("crc", "Show AGI game CRC", ccmd_crc); +#if 0 + console_cmd("exec", "Execute loaded AGI game", ccmd_exec); +#endif + console_cmd("flags", "Dump all AGI flags", ccmd_flags); + console_cmd("help", "List available commands", ccmd_help); +#ifdef USE_HIRES + console_cmd("hires", "Turn hi-res mode on/off", ccmd_hires); +#endif + console_cmd("logic0", "Turn logic 0 debugging on/off", ccmd_logic0); + console_cmd("objs", "List all objects and locations", ccmd_objs); + console_cmd("opcode", "Turn opcodes on/off in debug", ccmd_opcode); + console_cmd("quit", "Quit the interpreter", ccmd_quit); +#if 0 + console_cmd("inv", "List current inventory", ccmd_inv); + console_cmd("say", "Pass argument to the AGI parser", ccmd_say); +#endif + console_cmd("step", "Execute the next AGI instruction", ccmd_step); + console_cmd("trigger", "Turn trigger lines on/off", ccmd_trigger); + console_cmd("vars", "Dump all AGI variables", ccmd_vars); + console_cmd("ver", "Show interpreter version", ccmd_ver); + + console.active = 1; + console.input_active = 1; + console.index = 0; + console.max_y = 150; + console.y = console.max_y; + console.first_line = CONSOLE_LINES_BUFFER - CONSOLE_LINES_ONSCREEN; + + debug_.enabled = 0; + debug_.opcodes = 0; + debug_.logic0 = 1; + debug_.priority = 0; + + has_console = 1; + clear_screen(0); + + return err_OK; +} + +static void build_console_layer() { + build_console_lines(console.max_y / 10); +} + +void report(char *message, ...) { + char x[512], y[512], z[64], *msg, *n; + va_list args; + int i, s, len; + + if (!has_console) + return; + + va_start(args, message); +#ifdef HAVE_VSNPRINTF + vsnprintf(y, 510, (char *)message, args); +#else + vsprintf(y, (char *)message, args); +#endif + va_end(args); + + if (console_input) { + strcpy(z, get_last_console_line()); + strcpy(x, ">"); + } else { + strcpy(x, get_last_console_line()); + } + + strcat(x, y); + + len = 40; + msg = n = word_wrap_string(x, &len); + + for (s = 1; *n; n++) + if (*n == '\n') + s++; + + add_console_line(get_token(msg, '\n')); + for (i = 1; i < s; i++) { + first_line++; + n = get_token(NULL, '\n'); + add_console_line(n); + } + + console.first_line = CONSOLE_LINES_BUFFER - CONSOLE_LINES_ONSCREEN; + + if (console_input) { + add_console_line(z); + } + + /* Build layer */ + if (console.y) { + if (s > 1) + build_console_layer(); + else + build_console_lines(1); + } + + free(msg); + + do_update(); +} + +void console_cycle() { + static int old_y = 0; + static int blink = 0; + static char cursor[] = " "; + + /* If no game has been loaded, keep the console visible! */ + if (game.state < STATE_RUNNING) { + console.active = 1; + console.count = 10; + } + + /* Initial console auto-hide timer */ + if (console.count > 0) + console.count--; + if (console.count == 0) { + console.active = 0; + console.count = -1; + } + + if (console.active) { + if (console.y < console.max_y) + console.y += 15; + else + console.y = console.max_y; + } else { + console.count = -1; + + if (console.y > 0) + console.y -= 15; + else + console.y = 0; + } + + /* console shading animation */ + if (old_y != console.y) { + int y = console.y; + if (old_y > console.y) { + /* going up */ + y = old_y; + } + flush_block(0, 0, GFX_WIDTH - 1, y); + old_y = console.y; + } + + blink++; + blink %= 16; + if (console.input_active) { + *cursor = blink < 8 ? + CONSOLE_CURSOR_SOLID : CONSOLE_CURSOR_EMPTY; + } else { + *cursor = CONSOLE_CURSOR_HOLLOW; + } + if (console.y > 0 + && console.first_line == + CONSOLE_LINES_BUFFER - CONSOLE_LINES_ONSCREEN) { + int16 y1 = console.y - 10, y2 = console.y - 1; + if (y1 < 0) + y1 = 0; + if (y2 < 0) + y2 = 0; + print_text_console(cursor, (1 + console.index), 19, 2, CONSOLE_COLOR, 0); + flush_block((1 + console.index) * 8, y1, (1 + console.index) * 8 + 7, y2 - 1); + } + + do_update(); +} + +/* Return true if key was handled */ +int console_keyhandler(int k) { + static char buffer[CONSOLE_INPUT_SIZE]; + int16 y1, y2; + char m[2]; + +#ifdef USE_MOUSE + /* Right button switches console on/off */ + if (!opt.agimouse) /* AGI Mouse uses right button */ + if (k == BUTTON_RIGHT) + k = CONSOLE_ACTIVATE_KEY; +#endif + + if (!console.active) { + if (k == CONSOLE_ACTIVATE_KEY) { + console.active = 1; + return true; + } + return false; + } + + if (!console.input_active) { + if (k == CONSOLE_SWITCH_KEY) { + console.input_active = 1; + return true; + } + if (k == CONSOLE_ACTIVATE_KEY) { + console.active = 0; + return true; + } + return false; + } + + y1 = console.y - 10; + y2 = console.y - 1; + + if (y1 < 0) + y1 = 0; + if (y2 < 0) + y2 = 0; + +#ifdef USE_MOUSE + /* Ignore left button in console */ + if (k == BUTTON_LEFT) + return true; +#endif + + /* this code breaks scrolling up, maybe it shoud only be executed + * in the default case? + */ + if (k) { + /* make sure it's not enter or a scroll key */ + if ((k != KEY_ENTER) && (k != CONSOLE_SCROLLUP_KEY) && (k != CONSOLE_SCROLLDN_KEY) + && (k != CONSOLE_START_KEY)) { + /* on any other input reset the console to the bottom */ + if (console.first_line != CONSOLE_LINES_BUFFER - CONSOLE_LINES_ONSCREEN) { + console.first_line = CONSOLE_LINES_BUFFER - CONSOLE_LINES_ONSCREEN; + build_console_layer(); + } + console.count = -1; + } + } + + switch (k) { + case KEY_ENTER: + console_lock(); + console.index = 0; + report("\n"); + + if (console_parse(buffer) != 0) + report("What? Where?\n"); + + buffer[0] = 0; + console_prompt(); + break; + case KEY_BACKSPACE:{ + char *x; + if (!console.index) + break; + x = get_last_console_line(); + x[console.index] = 0; + *m = CONSOLE_CURSOR_EMPTY; + print_text_console(m, (console.index + 1), 19, 2, CONSOLE_COLOR, 0); + flush_block((console.index + 1) * CHAR_COLS, y1, (console.index + 1 + 1) * CHAR_COLS - 1, y2); + console.index--; + buffer[console.index] = 0; + } + break; + case CONSOLE_ACTIVATE_KEY: + console.active = !console.active; + if (console.active) + build_console_layer(); + break; + case CONSOLE_SWITCH_KEY: + console.count = -1; + if (console.y) + console.input_active = !console.input_active; + break; + case CONSOLE_SCROLLUP_KEY: + console.count = -1; + if (!console.y) + break; + if (console.first_line > (CONSOLE_LINES_ONSCREEN / 2)) + console.first_line -= CONSOLE_LINES_ONSCREEN / 2; + else + console.first_line = 0; + build_console_layer(); + break; + case CONSOLE_SCROLLDN_KEY: + console.count = -1; + if (!console.y) + break; + if (console.first_line < (CONSOLE_LINES_BUFFER - CONSOLE_LINES_ONSCREEN - CONSOLE_LINES_ONSCREEN / 2)) + console.first_line += CONSOLE_LINES_ONSCREEN / 2; + else + console.first_line = CONSOLE_LINES_BUFFER - CONSOLE_LINES_ONSCREEN; + build_console_layer(); + break; + case CONSOLE_START_KEY: + console.count = -1; + if (console.y) + console.first_line = 0; + break; + case CONSOLE_END_KEY: + console.count = -1; + if (console.y) + console.first_line = CONSOLE_LINES_BUFFER - CONSOLE_LINES_ONSCREEN; + break; + default: + if (k >= 0x20 && k <= 0x7f && (console.index < CONSOLE_INPUT_SIZE - 2)) { + char l[42]; + buffer[console.index] = k; + *m = k; + m[1] = 0; + console.index++; + + sprintf(l, "%s%c", get_last_console_line(), k); + strncpy(get_last_console_line(), l, CONSOLE_LINE_SIZE); + + buffer[console.index] = 0; + print_text_console(m, console.index, 19, 2, CONSOLE_COLOR, 0); + flush_block(console.index * 8, y1, console.index * 8 + 7, y2); + } + break; + } + + do_update(); + + return true; +} + +void console_prompt() { + report(CONSOLE_PROMPT); + console_input = 1; +} + +void console_lock() { + console_input = 0; +} + +#else + +void *debug; + +void report(char *message, ...) { + /* dummy */ +} + +int console_init() { + return 0; +} + +/* Date: Sun, 14 Oct 2001 23:02:02 -0700 + * From: Vasyl Tsvirkunov + * + * This one was rather harmless and affected only builds without console. + * In SQ1&2 (and likely some others) name entry screen did not update + * properly. The bug caused by implicit assumption in cycle.c that + * console_cycle() updates the screen. Well, it does, if console is enabled. + * The fix is simple. In the second version of console_cycle() in console.c + * (the "dummy" one) add call to do_update(). The thing raises some + * questions about overall architecture of main cycle, but otherwise the fix + * works just fine. + */ +void console_cycle() { + do_update(); +} + +void console_lock() { + /* dummy */ +} + +void console_prompt() { + /* dummy */ +} + +int console_keyhandler(int i) { + return false; +} + +#endif /* USE_CONSOLE */ + +} // End of namespace Agi diff --git a/engines/agi/console.h b/engines/agi/console.h new file mode 100644 index 0000000000..1a211f9826 --- /dev/null +++ b/engines/agi/console.h @@ -0,0 +1,71 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2006 The ScummVM project + * + * Copyright (C) 1999-2001 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$ + * + */ + +#ifndef __AGI_CONSOLE_H +#define __AGI_CONSOLE_H + +namespace Agi { + +#ifdef USE_CONSOLE + +#define CONSOLE_LINES_BUFFER 80 +#define CONSOLE_LINE_SIZE (GFX_WIDTH / 8) +#define CONSOLE_ACTIVATE_KEY '`' +#define CONSOLE_SWITCH_KEY '~' + +struct agi_console { + int active; + int input_active; + int index; + int y; + int max_y; + int first_line; + int count; + char *line[CONSOLE_LINES_BUFFER]; +}; + +struct agi_debug { + int enabled; + int opcodes; + int logic0; + int steps; + int priority; + int statusline; + int ignoretriggers; +}; + +extern struct agi_console console; + +#endif /* USE_CONSOLE */ + +int console_keyhandler(int); +int console_init(void); +void console_cycle(void); +void console_lock(void); +void console_prompt(void); +void report(char *, ...); + +} // End of namespace Agi + +#endif /* __AGI_CONSOLE_H */ diff --git a/engines/agi/cycle.cpp b/engines/agi/cycle.cpp new file mode 100644 index 0000000000..1c5365e4d1 --- /dev/null +++ b/engines/agi/cycle.cpp @@ -0,0 +1,423 @@ +/* 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 "agi/agi.h" +#include "agi/text.h" +#include "agi/sprite.h" +#include "agi/graphics.h" +#include "agi/keyboard.h" +#include "agi/menu.h" +#include "agi/savegame.h" + +namespace Agi { + +#define TICK_SECONDS 20 + +#ifdef USE_MOUSE +struct mouse mouse; +#endif + +volatile uint32 clock_ticks; +volatile uint32 clock_count; + +/** + * Set up new room. + * This function is called when ego enters a new room. + * @param n room number + */ +void new_room(int n) { + struct vt_entry *v; + int i; + + debugC(4, kDebugLevelMain, "*** room %d ***", n); + stop_sound(); + + i = 0; + for (v = game.view_table; v < &game.view_table[MAX_VIEWTABLE]; v++) { + v->entry = i++; + v->flags &= ~(ANIMATED | DRAWN); + v->flags |= UPDATE; + v->step_time = 1; + v->step_time_count = 1; + v->cycle_time = 1; + v->cycle_time_count = 1; + v->step_size = 1; + } + agi_unload_resources(); + + game.player_control = true; + game.block.active = false; + game.horizon = 36; + game.vars[V_prev_room] = game.vars[V_cur_room]; + game.vars[V_cur_room] = n; + game.vars[V_border_touch_obj] = 0; + game.vars[V_border_code] = 0; + game.vars[V_ego_view_resource] = game.view_table[0].current_view; + + agi_load_resource(rLOGIC, n); + + /* Reposition ego in the new room */ + switch (game.vars[V_border_touch_ego]) { + case 1: + game.view_table[0].y_pos = _HEIGHT - 1; + break; + case 2: + game.view_table[0].x_pos = 0; + break; + case 3: + game.view_table[0].y_pos = HORIZON + 1; + break; + case 4: + game.view_table[0].x_pos = _WIDTH - game.view_table[0].x_size; + break; + } + + game.vars[V_border_touch_ego] = 0; + setflag(F_new_room_exec, true); + + game.exit_all_logics = true; + + write_status(); + write_prompt(); +} + +static void reset_controllers() { + int i; + + for (i = 0; i < MAX_DIRS; i++) { + game.ev_keyp[i].occured = false; + } +} + +static void interpret_cycle() { + int old_sound, old_score; + + if (game.player_control) + game.vars[V_ego_dir] = game.view_table[0].direction; + else + game.view_table[0].direction = game.vars[V_ego_dir]; + + check_all_motions(); + + old_score = game.vars[V_score]; + old_sound = getflag(F_sound_on); + + game.exit_all_logics = false; + while (run_logic(0) == 0 && !game.quit_prog_now) { + game.vars[V_word_not_found] = 0; + game.vars[V_border_touch_obj] = 0; + game.vars[V_border_code] = 0; + old_score = game.vars[V_score]; + setflag(F_entered_cli, false); + game.exit_all_logics = false; + reset_controllers(); + } + reset_controllers(); + + game.view_table[0].direction = game.vars[V_ego_dir]; + + if (game.vars[V_score] != old_score || getflag(F_sound_on) != old_sound) + write_status(); + + game.vars[V_border_touch_obj] = 0; + game.vars[V_border_code] = 0; + setflag(F_new_room_exec, false); + setflag(F_restart_game, false); + setflag(F_restore_just_ran, false); + + if (game.gfx_mode) { + update_viewtable(); + do_update(); + } +} + +/** + * Update AGI interpreter timer. + */ +void update_timer() { + clock_count++; + if (clock_count <= TICK_SECONDS) + return; + + clock_count -= TICK_SECONDS; + + if (!game.clock_enabled) + return; + + setvar(V_seconds, getvar(V_seconds) + 1); + if (getvar(V_seconds) < 60) + return; + + setvar(V_seconds, 0); + setvar(V_minutes, getvar(V_minutes) + 1); + if (getvar(V_minutes) < 60) + return; + + setvar(V_minutes, 0); + setvar(V_hours, getvar(V_hours) + 1); + if (getvar(V_hours) < 24) + return; + + setvar(V_hours, 0); + setvar(V_days, getvar(V_days) + 1); +} + +static int old_mode = -1; + +void new_input_mode(int i) { + old_mode = game.input_mode; + game.input_mode = i; +} + +void old_input_mode() { + game.input_mode = old_mode; +} + +/* If main_cycle returns false, don't process more events! */ +int main_cycle() { + unsigned int key, kascii; + struct vt_entry *v = &game.view_table[0]; + + poll_timer(); /* msdos driver -> does nothing */ + update_timer(); + + if (game.ver == 0) { + message_box("Warning: game CRC not listed, assuming AGI version 2.917."); + game.ver = -1; + } + + key = do_poll_keyboard(); + +#ifdef USE_MOUSE + /* In AGI Mouse emulation mode we must update the mouse-related + * vars in every interpreter cycle. + */ + if (opt.agimouse) { + game.vars[28] = mouse.x / 2; + game.vars[29] = mouse.y; + } +#endif + +#ifdef USE_CONSOLE + if (key == KEY_PRIORITY) { + erase_both(); + debug_.priority = !debug_.priority; + show_pic(); + blit_both(); + commit_both(); + key = 0; + } + + if (key == KEY_STATUSLN) { + debug_.statusline = !debug_.statusline; + write_status(); + key = 0; + } +#endif + + /* Click-to-walk mouse interface */ + if (game.player_control && v->flags & ADJ_EGO_XY) { + v->direction = get_direction(v->x_pos, v->y_pos, v->parm1, v->parm2, v->step_size); + + if (v->direction == 0) + in_destination(v); + } + + kascii = KEY_ASCII(key); + + if (!console_keyhandler(key)) { + if (kascii) + setvar(V_key, kascii); + process_key: + switch (game.input_mode) { + case INPUT_NORMAL: + if (!handle_controller(key)) { + if (key == 0 || !game.input_enabled) + break; + handle_keys(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; + goto process_key; + } + + /* commented out to close bug #438872 + * if (key) game.keypress = key; + */ + } + break; + case INPUT_GETSTRING: + handle_controller(key); + handle_getstring(key); + setvar(V_key, 0); /* clear ENTER key */ + break; + case INPUT_MENU: + menu_keyhandler(key); + console_cycle(); + return false; + case INPUT_NONE: + handle_controller(key); + if (key) + game.keypress = key; + break; + } + } else { + if (game.input_mode == INPUT_MENU) { + console_cycle(); + return false; + } + } + + console_cycle(); + + if (game.msg_box_ticks > 0) + game.msg_box_ticks--; + + return true; +} + +static int play_game() { + int ec = err_OK; + + debugC(2, kDebugLevelMain, "initializing..."); + debugC(2, kDebugLevelMain, "game.ver = 0x%x", game.ver); + + stop_sound(); + clear_screen(0); + + game.horizon = HORIZON; + game.player_control = false; + + setflag(F_logic_zero_firsttime, true); /* not in 2.917 */ + setflag(F_new_room_exec, true); /* needed for MUMG and SQ2! */ + setflag(F_sound_on, true); /* enable sound */ + setvar(V_time_delay, 2); /* "normal" speed */ + + game.gfx_mode = true; + game.quit_prog_now = false; + game.clock_enabled = true; + game.line_user_input = 22; + +#ifdef USE_MOUSE + if (opt.agimouse) + report("Using AGI Mouse 1.0 protocol\n"); +#endif + + report("Running AGI script.\n"); + +#ifdef USE_CONSOLE + console.count = 5; + console_prompt(); +#endif + + setflag(F_entered_cli, false); + setflag(F_said_accepted_input, false); + game.vars[V_word_not_found] = 0; + game.vars[V_key] = 0; + + debugC(2, kDebugLevelMain, "Entering main loop"); + do { + + if (!main_cycle()) + continue; + + if (getvar(V_time_delay) == 0 || + (1 + clock_count) % getvar(V_time_delay) == 0) { + if (!game.has_prompt && game.input_mode == INPUT_NORMAL) { + write_prompt(); + game.has_prompt = 1; + } else + if (game.has_prompt && game.input_mode == INPUT_NONE) { + write_prompt(); + game.has_prompt = 0; + } + + interpret_cycle(); + + setflag(F_entered_cli, false); + setflag(F_said_accepted_input, false); + game.vars[V_word_not_found] = 0; + game.vars[V_key] = 0; + } + + if (game.quit_prog_now == 0xff) + ec = err_RestartGame; + + } while (game.quit_prog_now == 0); + + stop_sound(); + + return ec; +} + +int run_game() { + int i, ec = err_OK; + +#ifdef USE_HIRES + if (opt.cgaemu) + opt.hires = 0; +#endif + + for (i = 0; i < MAX_DIRS; i++) + memset(&game.ev_keyp[i], 0, sizeof(struct agi_event)); + + /* Execute the game */ + do { + debugC(2, kDebugLevelMain, "game loop"); + debugC(2, kDebugLevelMain, "game.ver = 0x%x", game.ver); + + if (agi_init() != err_OK) + break; + if (ec == err_RestartGame) + setflag(F_restart_game, true); + + setvar(V_computer, 0); /* IBM PC (4 = Atari ST) */ + setvar(V_soundgen, 1); /* IBM PC SOUND */ + setvar(V_monitor, 0x3); /* EGA monitor */ + setvar(V_max_input_chars, 38); + game.input_mode = INPUT_NONE; + game.input_enabled = 0; + game.has_prompt = 0; + + game.state = STATE_RUNNING; + ec = play_game(); + game.state = STATE_LOADED; + agi_deinit(); + } while (ec == err_RestartGame); + + menu_deinit(); + + release_image_stack(); + + return ec; +} + +} // End of namespace Agi diff --git a/engines/agi/font.cpp b/engines/agi/font.cpp new file mode 100644 index 0000000000..487107dfe8 --- /dev/null +++ b/engines/agi/font.cpp @@ -0,0 +1,295 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2006 The ScummVM project + * + * Copyright (C) 1999-2001 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 "agi/agi.h" + +namespace Agi { + +/* 8x8 font patterns */ +uint8 cur_font[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7E, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x7E, /* cursor hollow */ + 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, /* cursor solid */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* cursor empty */ + 0x10, 0x38, 0x7C, 0xFE, 0x7C, 0x38, 0x10, 0x00, + 0x3C, 0x3C, 0x18, 0xFF, 0xE7, 0x18, 0x3C, 0x00, + 0x10, 0x38, 0x7C, 0xFE, 0xEE, 0x10, 0x38, 0x00, + 0x00, 0x00, 0x18, 0x3C, 0x3C, 0x18, 0x00, 0x00, + 0xFF, 0xFF, 0xE7, 0xC3, 0xC3, 0xE7, 0xFF, 0xFF, + 0x00, 0x3C, 0x66, 0x42, 0x42, 0x66, 0x3C, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* \n */ + 0x0F, 0x07, 0x0F, 0x7D, 0xCC, 0xCC, 0xCC, 0x78, + 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x7E, 0x18, + 0x08, 0x0C, 0x0A, 0x0A, 0x08, 0x78, 0xF0, 0x00, + 0x18, 0x14, 0x1A, 0x16, 0x72, 0xE2, 0x0E, 0x1C, + 0x10, 0x54, 0x38, 0xEE, 0x38, 0x54, 0x10, 0x00, + 0x80, 0xE0, 0xF8, 0xFE, 0xF8, 0xE0, 0x80, 0x00, + 0x02, 0x0E, 0x3E, 0xFE, 0x3E, 0x0E, 0x02, 0x00, + 0x18, 0x3C, 0x5A, 0x18, 0x5A, 0x3C, 0x18, 0x00, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x66, 0x00, + 0x7F, 0xDB, 0xDB, 0xDB, 0x7B, 0x1B, 0x1B, 0x00, + 0x1C, 0x22, 0x38, 0x44, 0x44, 0x38, 0x88, 0x70, + 0x00, 0x00, 0x00, 0x00, 0x7E, 0x7E, 0x7E, 0x00, + 0x18, 0x3C, 0x5A, 0x18, 0x5A, 0x3C, 0x18, 0x7E, + 0x18, 0x3C, 0x5A, 0x18, 0x18, 0x18, 0x18, 0x00, + 0x18, 0x18, 0x18, 0x18, 0x5A, 0x3C, 0x18, 0x00, + 0x00, 0x18, 0x0C, 0xFE, 0x0C, 0x18, 0x00, 0x00, + 0x00, 0x30, 0x60, 0xFE, 0x60, 0x30, 0x00, 0x00, + 0x00, 0x00, 0xC0, 0xC0, 0xC0, 0xFE, 0x00, 0x00, + 0x00, 0x24, 0x42, 0xFF, 0x42, 0x24, 0x00, 0x00, + 0x00, 0x10, 0x38, 0x7C, 0xFE, 0xFE, 0x00, 0x00, + 0x00, 0xFE, 0xFE, 0x7C, 0x38, 0x10, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00, + 0x6C, 0x24, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x6C, 0x6C, 0xFE, 0x6C, 0xFE, 0x6C, 0x6C, 0x00, + 0x10, 0x7C, 0xD0, 0x7C, 0x16, 0xFC, 0x10, 0x00, + 0x00, 0x66, 0xAC, 0xD8, 0x36, 0x6A, 0xCC, 0x00, + 0x38, 0x4C, 0x38, 0x78, 0xCE, 0xCC, 0x7A, 0x00, + 0x30, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x30, 0x60, 0x60, 0x60, 0x30, 0x18, 0x00, + 0x60, 0x30, 0x18, 0x18, 0x18, 0x30, 0x60, 0x00, + 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00, + 0x00, 0x30, 0x30, 0xFC, 0x30, 0x30, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x10, 0x20, + 0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, + 0x02, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0x00, + 0x7C, 0xCE, 0xDE, 0xF6, 0xE6, 0xE6, 0x7C, 0x00, + 0x18, 0x38, 0x78, 0x18, 0x18, 0x18, 0x7E, 0x00, + 0x7C, 0xC6, 0x06, 0x1C, 0x70, 0xC6, 0xFE, 0x00, + 0x7C, 0xC6, 0x06, 0x3C, 0x06, 0xC6, 0x7C, 0x00, + 0x1C, 0x3C, 0x6C, 0xCC, 0xFE, 0x0C, 0x1E, 0x00, + 0xFE, 0xC0, 0xFC, 0x06, 0x06, 0xC6, 0x7C, 0x00, + 0x7C, 0xC6, 0xC0, 0xFC, 0xC6, 0xC6, 0x7C, 0x00, + 0xFE, 0xC6, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x00, + 0x7C, 0xC6, 0xC6, 0x7C, 0xC6, 0xC6, 0x7C, 0x00, + 0x7C, 0xC6, 0xC6, 0x7E, 0x06, 0xC6, 0x7C, 0x00, + 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, + 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x10, 0x20, + 0x0C, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0C, 0x00, + 0x00, 0x00, 0x7E, 0x00, 0x00, 0x7E, 0x00, 0x00, + 0x60, 0x30, 0x18, 0x0C, 0x18, 0x30, 0x60, 0x00, + 0x78, 0xCC, 0x0C, 0x18, 0x30, 0x00, 0x30, 0x00, + 0x7C, 0x82, 0x9E, 0xA6, 0x9E, 0x80, 0x7C, 0x00, + 0x7C, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0xC6, 0x00, + 0xFC, 0x66, 0x66, 0x7C, 0x66, 0x66, 0xFC, 0x00, + 0x7C, 0xC6, 0xC0, 0xC0, 0xC0, 0xC6, 0x7C, 0x00, + 0xFC, 0x66, 0x66, 0x66, 0x66, 0x66, 0xFC, 0x00, + 0xFE, 0x62, 0x68, 0x78, 0x68, 0x62, 0xFE, 0x00, + 0xFE, 0x62, 0x68, 0x78, 0x68, 0x60, 0xF0, 0x00, + 0x7C, 0xC6, 0xC6, 0xC0, 0xCE, 0xC6, 0x7E, 0x00, + 0xC6, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0xC6, 0x00, + 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, + 0x1E, 0x0C, 0x0C, 0x0C, 0xCC, 0xCC, 0x78, 0x00, + 0xE6, 0x66, 0x6C, 0x78, 0x6C, 0x66, 0xE6, 0x00, + 0xF0, 0x60, 0x60, 0x60, 0x62, 0x66, 0xFE, 0x00, + 0x82, 0xC6, 0xEE, 0xFE, 0xD6, 0xC6, 0xC6, 0x00, + 0xC6, 0xE6, 0xF6, 0xDE, 0xCE, 0xC6, 0xC6, 0x00, + 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, + 0xFC, 0x66, 0x66, 0x7C, 0x60, 0x60, 0xF0, 0x00, + 0x7C, 0xC6, 0xC6, 0xC6, 0xD6, 0xDE, 0x7C, 0x06, + 0xFC, 0x66, 0x66, 0x7C, 0x66, 0x66, 0xE6, 0x00, + 0x7C, 0xC6, 0xC0, 0x7C, 0x06, 0xC6, 0x7C, 0x00, + 0x7E, 0x5A, 0x5A, 0x18, 0x18, 0x18, 0x3C, 0x00, + 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, + 0xC6, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x10, 0x00, + 0xC6, 0xC6, 0xD6, 0xFE, 0xEE, 0xC6, 0x82, 0x00, + 0xC6, 0x6C, 0x38, 0x38, 0x38, 0x6C, 0xC6, 0x00, + 0x66, 0x66, 0x66, 0x3C, 0x18, 0x18, 0x3C, 0x00, + 0xFE, 0xC6, 0x8C, 0x18, 0x32, 0x66, 0xFE, 0x00, + 0x78, 0x60, 0x60, 0x60, 0x60, 0x60, 0x78, 0x00, + 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x02, 0x00, + 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x78, 0x00, + 0x10, 0x38, 0x6C, 0xC6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x30, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x78, 0x0C, 0x7C, 0xCC, 0x76, 0x00, + 0xE0, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x7C, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0xC0, 0xC6, 0x7C, 0x00, + 0x1C, 0x0C, 0x0C, 0x7C, 0xCC, 0xCC, 0x76, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0xFE, 0xC0, 0x7C, 0x00, + 0x1C, 0x36, 0x30, 0x78, 0x30, 0x30, 0x78, 0x00, + 0x00, 0x00, 0x76, 0xCC, 0xCC, 0x7C, 0x0C, 0x78, + 0xE0, 0x60, 0x6C, 0x76, 0x66, 0x66, 0xE6, 0x00, + 0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x3C, 0x00, + 0x00, 0x0C, 0x00, 0x1C, 0x0C, 0x0C, 0xCC, 0x78, + 0xE0, 0x60, 0x66, 0x6C, 0x78, 0x6C, 0xE6, 0x00, + 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, + 0x00, 0x00, 0xCC, 0xFE, 0xD6, 0xD6, 0xD6, 0x00, + 0x00, 0x00, 0xDC, 0x66, 0x66, 0x66, 0x66, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, + 0x00, 0x00, 0xDC, 0x66, 0x66, 0x7C, 0x60, 0xF0, + 0x00, 0x00, 0x7C, 0xCC, 0xCC, 0x7C, 0x0C, 0x1E, + 0x00, 0x00, 0xDE, 0x76, 0x60, 0x60, 0xF0, 0x00, + 0x00, 0x00, 0x7C, 0xC0, 0x7C, 0x06, 0x7C, 0x00, + 0x10, 0x30, 0xFC, 0x30, 0x30, 0x34, 0x18, 0x00, + 0x00, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0x76, 0x00, + 0x00, 0x00, 0xC6, 0xC6, 0x6C, 0x38, 0x10, 0x00, + 0x00, 0x00, 0xC6, 0xD6, 0xD6, 0xFE, 0x6C, 0x00, + 0x00, 0x00, 0xC6, 0x6C, 0x38, 0x6C, 0xC6, 0x00, + 0x00, 0x00, 0xCC, 0xCC, 0xCC, 0x7C, 0x0C, 0xF8, + 0x00, 0x00, 0xFC, 0x98, 0x30, 0x64, 0xFC, 0x00, + 0x0E, 0x18, 0x18, 0x30, 0x18, 0x18, 0x0E, 0x00, + 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00, + 0xE0, 0x30, 0x30, 0x18, 0x30, 0x30, 0xE0, 0x00, + 0x76, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /*0x00, 0x10, 0x38, 0x6C, 0xC6, 0xC6, 0xFE, 0x00, */ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /*replacement 0x7F */ + +#ifdef AGDS_SUPPORT + 0x1E, 0x36, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x00, + 0x7C, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x7C, 0x00, + 0x7C, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x7C, 0x00, + 0x7E, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x00, + 0x38, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0xFE, 0xC6, + 0x7E, 0x60, 0x60, 0x7C, 0x60, 0x60, 0x7E, 0x00, + 0xDB, 0xDB, 0x7E, 0x3C, 0x7E, 0xDB, 0xDB, 0x00, + 0x3C, 0x66, 0x06, 0x1C, 0x06, 0x66, 0x3C, 0x00, + 0x66, 0x66, 0x6E, 0x7E, 0x76, 0x66, 0x66, 0x00, + 0x3C, 0x66, 0x6E, 0x7E, 0x76, 0x66, 0x66, 0x00, + 0x66, 0x6C, 0x78, 0x70, 0x78, 0x6C, 0x66, 0x00, + 0x1E, 0x36, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, + 0xC6, 0xEE, 0xFE, 0xFE, 0xD6, 0xC6, 0xC6, 0x00, + 0x66, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00, + 0x3C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00, + 0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, + 0x7C, 0x66, 0x66, 0x66, 0x7C, 0x60, 0x60, 0x00, + 0x3C, 0x66, 0x60, 0x60, 0x60, 0x66, 0x3C, 0x00, + 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, + 0x66, 0x66, 0x66, 0x3E, 0x06, 0x66, 0x3C, 0x00, + 0x7E, 0xDB, 0xDB, 0xDB, 0x7E, 0x18, 0x18, 0x00, + 0x66, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0x66, 0x00, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7F, 0x03, + 0x66, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x06, 0x00, + 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xFF, 0x00, + 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xFF, 0x03, + 0xE0, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x7C, 0x00, + 0xC6, 0xC6, 0xC6, 0xF6, 0xDE, 0xDE, 0xF6, 0x00, + 0x60, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x7C, 0x00, + 0x78, 0x8C, 0x06, 0x3E, 0x06, 0x8C, 0x78, 0x00, + 0xCE, 0xDB, 0xDB, 0xFB, 0xDB, 0xDB, 0xCE, 0x00, + 0x3E, 0x66, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x00, + 0x00, 0x00, 0x3C, 0x06, 0x3E, 0x66, 0x3A, 0x00, + 0x00, 0x3C, 0x60, 0x3C, 0x66, 0x66, 0x3C, 0x00, + 0x00, 0x00, 0x7C, 0x66, 0x7C, 0x66, 0x7C, 0x00, + 0x00, 0x00, 0x7E, 0x60, 0x60, 0x60, 0x60, 0x00, + 0x00, 0x00, 0x3C, 0x6C, 0x6C, 0x6C, 0xFE, 0xC6, + 0x00, 0x00, 0x3C, 0x66, 0x7E, 0x60, 0x3C, 0x00, + 0x00, 0x00, 0xDB, 0x7E, 0x3C, 0x7E, 0xDB, 0x00, + 0x00, 0x00, 0x3C, 0x66, 0x0C, 0x66, 0x3C, 0x00, + 0x00, 0x00, 0x66, 0x6E, 0x7E, 0x76, 0x66, 0x00, + 0x00, 0x18, 0x66, 0x6E, 0x7E, 0x76, 0x66, 0x00, + 0x00, 0x00, 0x66, 0x6C, 0x78, 0x6C, 0x66, 0x00, + 0x00, 0x00, 0x1E, 0x36, 0x66, 0x66, 0x66, 0x00, + 0x00, 0x00, 0xC6, 0xFE, 0xFE, 0xD6, 0xC6, 0x00, + 0x00, 0x00, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x00, + 0x00, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x00, + 0x00, 0x00, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x00, + 0x11, 0x44, 0x11, 0x44, 0x11, 0x44, 0x11, 0x44, + 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, + 0xDD, 0x77, 0xDD, 0x77, 0xDD, 0x77, 0xDD, 0x77, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0xF8, 0x18, 0x18, 0x18, 0x18, + 0x18, 0xF8, 0x18, 0xF8, 0x18, 0x18, 0x18, 0x18, + 0x36, 0x36, 0x36, 0xF6, 0x36, 0x36, 0x36, 0x36, + 0x00, 0x00, 0x00, 0xFE, 0x36, 0x36, 0x36, 0x36, + 0x00, 0xF8, 0x18, 0xF8, 0x18, 0x18, 0x18, 0x18, + 0x36, 0xF6, 0x06, 0xF6, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x00, 0xFE, 0x06, 0xF6, 0x36, 0x36, 0x36, 0x36, + 0x36, 0xF6, 0x06, 0xFE, 0x00, 0x00, 0x00, 0x00, + 0x36, 0x36, 0x36, 0xFE, 0x00, 0x00, 0x00, 0x00, + 0x18, 0xF8, 0x18, 0xF8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xF8, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x1F, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x18, 0x18, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xFF, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x1F, 0x18, 0x18, 0x18, 0x18, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x18, 0x18, 0xFF, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x1F, 0x18, 0x1F, 0x18, 0x18, 0x18, 0x18, + 0x36, 0x36, 0x36, 0x37, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x37, 0x30, 0x3F, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x3F, 0x30, 0x37, 0x36, 0x36, 0x36, 0x36, + 0x36, 0xF7, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0xF7, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x37, 0x30, 0x37, 0x36, 0x36, 0x36, 0x36, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x36, 0xF7, 0x00, 0xF7, 0x36, 0x36, 0x36, 0x36, + 0x18, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x36, 0x36, 0x36, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x18, 0x18, 0x18, 0x18, + 0x00, 0x00, 0x00, 0xFF, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x3F, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x1F, 0x18, 0x1F, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x1F, 0x18, 0x1F, 0x18, 0x18, 0x18, 0x18, + 0x00, 0x00, 0x00, 0x3F, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0xFF, 0x36, 0x36, 0x36, 0x36, + 0x18, 0xFF, 0x18, 0xFF, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0xF8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x1F, 0x18, 0x18, 0x18, 0x18, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0x66, 0x66, 0x7C, 0x60, 0x00, + 0x00, 0x00, 0x3C, 0x66, 0x60, 0x66, 0x3C, 0x00, + 0x00, 0x00, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x00, + 0x00, 0x00, 0x66, 0x66, 0x3E, 0x06, 0x7C, 0x00, + 0x00, 0x00, 0x7E, 0xDB, 0xDB, 0x7E, 0x18, 0x00, + 0x00, 0x00, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0x00, + 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x7F, 0x03, + 0x00, 0x00, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x00, + 0x00, 0x00, 0xDB, 0xDB, 0xDB, 0xDB, 0xFF, 0x00, + 0x00, 0x00, 0xDB, 0xDB, 0xDB, 0xDB, 0xFF, 0x03, + 0x00, 0x00, 0xE0, 0x60, 0x7C, 0x66, 0x7C, 0x00, + 0x00, 0x00, 0xC6, 0xC6, 0xF6, 0xDE, 0xF6, 0x00, + 0x00, 0x00, 0x60, 0x60, 0x7C, 0x66, 0x7C, 0x00, + 0x00, 0x00, 0x7C, 0x06, 0x3E, 0x06, 0x7C, 0x00, + 0x00, 0x00, 0xCE, 0xDB, 0xFB, 0xDB, 0xCE, 0x00, + 0x00, 0x00, 0x3E, 0x66, 0x3E, 0x36, 0x66, 0x00, + 0x00, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, + 0x10, 0x10, 0x7C, 0x10, 0x10, 0x00, 0x7C, 0x00, + 0x00, 0x30, 0x18, 0x0C, 0x06, 0x0C, 0x18, 0x30, + 0x00, 0x0C, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0C, + 0x0E, 0x1B, 0x1B, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0xD8, 0xD8, 0x70, + 0x00, 0x18, 0x18, 0x00, 0x7E, 0x00, 0x18, 0x18, + 0x00, 0x76, 0xDC, 0x00, 0x76, 0xDC, 0x00, 0x00, + 0x00, 0x38, 0x6C, 0x6C, 0x38, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x38, 0x38, 0x00, 0x00, 0x00, + 0x03, 0x02, 0x06, 0x04, 0xCC, 0x68, 0x38, 0x10, + 0x3C, 0x42, 0x99, 0xA1, 0xA1, 0x99, 0x42, 0x3C, + 0x30, 0x48, 0x10, 0x20, 0x78, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0x7C, 0x7C, 0x7C, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x7E, 0x00 +#endif /* AGDS_SUPPORT */ +}; + +} // End of namespace Agi diff --git a/engines/agi/global.cpp b/engines/agi/global.cpp new file mode 100644 index 0000000000..404726f2b7 --- /dev/null +++ b/engines/agi/global.cpp @@ -0,0 +1,73 @@ +/* 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 "agi/agi.h" + +namespace Agi { + +int getflag(int n) { + uint8 *set = (uint8 *) & game.flags; + + set += n >> 3; + return (*set & (1 << (n & 0x07))) != 0; +} + +void setflag(int n, int v) { + uint8 *set = (uint8 *) & game.flags; + + set += n >> 3; + if (v) + *set |= 1 << (n & 0x07); /* set bit */ + else + *set &= ~(1 << (n & 0x07)); /* clear bit */ +} + +void flipflag(int n) { + uint8 *set = (uint8 *) & game.flags; + + set += n >> 3; + *set ^= 1 << (n & 0x07); /* flip bit */ +} + +void setvar(int var, int val) { + game.vars[var] = val; +} + +int getvar(int var) { + return game.vars[var]; +} + +void decrypt(uint8 *mem, int len) { + uint8 *key; + int i; + + key = opt.agds ? (uint8 *)CRYPT_KEY_AGDS : (uint8 *)CRYPT_KEY_SIERRA; + + for (i = 0; i < len; i++) + *(mem + i) ^= *(key + (i % 11)); +} + +} // End of namespace Agi diff --git a/engines/agi/graphics.cpp b/engines/agi/graphics.cpp new file mode 100644 index 0000000000..fbe0228efa --- /dev/null +++ b/engines/agi/graphics.cpp @@ -0,0 +1,742 @@ +/* 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 "agi/agi.h" +#include "agi/graphics.h" + +namespace Agi { + +#define DEV_X0(x) ((x) << 1) +#define DEV_X1(x) (((x) << 1) + 1) +#define DEV_Y(x) (x) + +#ifndef MAX_INT +# define MAX_INT (int)((unsigned)~0 >> 1) +#endif + +static uint8 *agi_screen; +#ifdef USE_CONSOLE +static uint8 *console_screen; +#endif + +static unsigned char *screen; + +extern uint8 cur_font[]; + +/** + * 16 color RGB palette (plus 16 transparent colors). + * This array contains the 6-bit RGB values of the EGA palette exported + * to the console drivers. + */ +uint8 ega_palette[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 +}; + +/** + * 16 color amiga-ish palette. + */ +uint8 new_palette[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 +}; + +uint8 palette[32 * 3]; + +static uint16 cga_map[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 */ +}; + +struct update_block { + int x1, y1; + int x2, y2; +}; + +static struct update_block update = { + MAX_INT, MAX_INT, 0, 0 +}; + +struct gfx_driver *gfx; /* graphics driver */ + +/* + * 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: 160x168 ================== AGI screen + */ + +#ifdef USE_CONSOLE + +/** + * Draws a row of pixels in the output device framebuffer. + * This function adds the console layer using transparent colors if + * appropriate. + */ +static void put_pixels(const int x, const int y, const int w, uint8 *p) { + int i; + uint8 _b[GFX_WIDTH] = { 0 }; + uint8 *b, *c = NULL; + + if (console.y <= y) { + gfx_putpixels(x, y, w, p); + return; + } + + b = &_b[0]; + c = &console_screen[x + (y + GFX_HEIGHT - 1 - console.y) * GFX_WIDTH]; + + for (i = 0; i < w; i++, c++, p++) { + *b++ = *c ? *c : *p + 16; + } + + gfx_putpixels(x, y, w, _b); +} + +static void init_console() { + int i; + + /* Console */ + console.line[0] = (char *)calloc(CONSOLE_LINES_BUFFER, CONSOLE_LINE_SIZE + 1); + for (i = 1; i < CONSOLE_LINES_BUFFER; i++) + console.line[i] = console.line[i - 1] + CONSOLE_LINE_SIZE + 1; +} + +#else + +static void put_pixels(const int x, const int y, const int w, uint8 *p) { + gfx->put_pixels(x, y, w, p); +} + +static void init_console() +{ +} + +#endif /* USE_CONSOLE */ + +#define SHAKE_MAG 3 +static uint8 *shake_h, *shake_v; + +void shake_start() { + int i; + + if ((shake_h = (uint8 *)malloc(GFX_WIDTH * SHAKE_MAG)) == NULL) + return; + + if ((shake_v = (uint8 *)malloc(SHAKE_MAG * (GFX_HEIGHT - SHAKE_MAG))) == NULL) { + free(shake_h); + return; + } + + for (i = 0; i < GFX_HEIGHT - SHAKE_MAG; i++) { + memcpy(shake_v + i * SHAKE_MAG, agi_screen + i * GFX_WIDTH, SHAKE_MAG); + } + + for (i = 0; i < SHAKE_MAG; i++) { + memcpy(shake_h + i * GFX_WIDTH, agi_screen + i * GFX_WIDTH, GFX_WIDTH); + } +} + +void shake_screen(int n) { + int i; + + if (n == 0) { + for (i = 0; i < (GFX_HEIGHT - SHAKE_MAG); i++) { + memmove(&agi_screen[GFX_WIDTH * i], + &agi_screen[GFX_WIDTH * (i + SHAKE_MAG) + SHAKE_MAG], + GFX_WIDTH - SHAKE_MAG); + } + } else { + for (i = GFX_HEIGHT - SHAKE_MAG - 1; i >= 0; i--) { + memmove(&agi_screen[GFX_WIDTH * (i + SHAKE_MAG) + SHAKE_MAG], + &agi_screen[GFX_WIDTH * i], GFX_WIDTH - SHAKE_MAG); + } + } +} + +void shake_end() { + int i; + + for (i = 0; i < GFX_HEIGHT - SHAKE_MAG; i++) { + memcpy(agi_screen + i * GFX_WIDTH, shake_v + i * SHAKE_MAG, SHAKE_MAG); + } + + for (i = 0; i < SHAKE_MAG; i++) { + memcpy(agi_screen + i * GFX_WIDTH, shake_h + i * GFX_WIDTH, GFX_WIDTH); + } + + flush_block(0, 0, GFX_WIDTH - 1, GFX_HEIGHT - 1); + + free(shake_v); + free(shake_h); +} + +void put_text_character(int l, int x, int y, unsigned int c, int fg, int bg) { + int x1, y1, xx, yy, cc; + uint8 *p; + + p = cur_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; +#ifdef USE_CONSOLE + if (l) { + console_screen[xx + yy * GFX_WIDTH] = cc; + } else +#endif + { + agi_screen[xx + yy * GFX_WIDTH] = cc; + } + } + + p++; + } + /* FIXME: we don't want this when we're writing on the + * console! + */ + flush_block(x, y, x + CHAR_COLS - 1, y + CHAR_LINES - 1); +} + +void draw_rectangle(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 = &agi_screen[x1 + y1 * GFX_WIDTH]; + for (y = 0; y < h; y++) { + memset(p0, c, w); + p0 += GFX_WIDTH; + } +} + +static void draw_frame(int x1, int y1, int x2, int y2, int c1, int c2) { + int y, w; + uint8 *p0; + + /* top line */ + w = x2 - x1 + 1; + p0 = &agi_screen[x1 + y1 * GFX_WIDTH]; + memset(p0, c1, w); + + /* bottom line */ + p0 = &agi_screen[x1 + y2 * GFX_WIDTH]; + memset(p0, c2, w); + + /* side lines */ + for (y = y1; y <= y2; y++) { + agi_screen[x1 + y * GFX_WIDTH] = c1; + agi_screen[x2 + y * GFX_WIDTH] = c2; + } +} + +void draw_box(int x1, int y1, int x2, int y2, int colour1, int colour2, int m) { + x1 += m; + y1 += m; + x2 -= m; + y2 -= m; + + draw_rectangle(x1, y1, x2, y2, colour1); + draw_frame(x1 + 2, y1 + 2, x2 - 2, y2 - 2, colour2, colour2); + flush_block(x1, y1, x2, y2); +} + +void print_character(int x, int y, char c, int fg, int bg) { + x *= CHAR_COLS; + y *= CHAR_LINES; + + put_text_character(0, x, y, c, fg, bg); + // redundant! already inside put_text_character! + // flush_block (x, y, x + CHAR_COLS - 1, y + CHAR_LINES - 1); +} + +/** + * Draw button + * @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 + */ +void draw_button(int x, int y, char *s, int a, int p) { + int len = strlen(s); + int x1, y1, x2, y2; + + x1 = x - 3; + y1 = y - 3; + x2 = x + CHAR_COLS * len + 2; + y2 = y + CHAR_LINES + 2; + + while (*s) { + put_text_character(0, x + (!!p), y + (!!p), *s++, + a ? 15 : 0, a ? 0 : 15); + x += CHAR_COLS; + } + + x1 -= 2; + y1 -= 2; + x2 += 2; + y2 += 2; + + flush_block(x1, y1, x2, y2); +} + +#ifdef USE_MOUSE +int test_button(int x, int y, char *s) { + int len = strlen(s); + int x1, y1, x2, y2; + + x1 = x - 3; + y1 = y - 3; + x2 = x + CHAR_COLS * len + 2; + y2 = y + CHAR_LINES + 2; + + if ((int)mouse.x >= x1 && (int)mouse.y >= y1 + && (int)mouse.x <= x2 && (int)mouse.y <= y2) + return true; + + return false; +} +#endif + +void put_block(int x1, int y1, int x2, int y2) { + gfx_putblock(x1, y1, x2, y2); +} + +void put_screen() { + put_block(0, 0, GFX_WIDTH - 1, GFX_HEIGHT - 1); +} + +void poll_timer() { + agi_timer_low(); +} + +int get_key() { + return agi_get_keypress_low(); +} + +int keypress() { + return agi_is_keypress_low(); +} + +/* + * Public functions + */ + +/** + * Initialize the color palette + * This function initializes the color palette using the specified 16-color + * RGB palette and creates 16 extra palette entries with translucent colors + * for the interpreter console. + * @param p A pointer to the 16-color RGB palette. + */ +void init_palette(uint8 *p) { + int i; + + for (i = 0; i < 48; i++) { + palette[i] = p[i]; + palette[i + 48] = (p[i] + 0x30) >> 2; + } +} + +void gfx_set_palette() { + int i; + byte pal[32 * 4]; + + for (i = 0; i < 32; i++) { + pal[i * 4 + 0] = palette[i * 3 + 0] << 2; + pal[i * 4 + 1] = palette[i * 3 + 1] << 2; + pal[i * 4 + 2] = palette[i * 3 + 2] << 2; + pal[i * 4 + 3] = 0; + } + g_system->setPalette(pal, 0, 32); +} + +void gfx_putpixels(int x, int y, int w, uint8 *p) { + uint8 *p0 = screen + x + y * 320; + memcpy(p0, p, w); +} + +/* put a block onto the screen */ +void gfx_putblock(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; + + // force full update until fix wrong partial updates + g_system->copyRectToScreen(screen, 320, 0, 0, 320, 200); + //g_system->copyRectToScreen(screen, 320, x1, y1, x2 - x1 + 1, y2 - y1 + 1); + //g_system->updateScreen(); +} + +static const byte mouseCursorArrow[] = { + 0x00, 0x00, 0x40, 0x00, 0x60, 0x00, 0x70, 0x00, + 0x78, 0x00, 0x7C, 0x00, 0x7E, 0x00, 0x7F, 0x00, + 0x7F, 0x80, 0x7C, 0x00, 0x6C, 0x00, 0x46, 0x00, + 0x06, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, + 0xC0, 0x00, 0xE0, 0x00, 0xF0, 0x00, 0xF8, 0x00, + 0xFC, 0x00, 0xFE, 0x00, 0xFF, 0x00, 0xFF, 0x80, + 0xFF, 0xC0, 0xFF, 0xC0, 0xFE, 0x00, 0xFF, 0x00, + 0xCF, 0x00, 0x07, 0x80, 0x07, 0x80, 0x03, 0x80 +}; + +/** + * Initialize graphics device. + * + * @see deinit_video() + */ +int init_video() { + if (opt.egapal) + init_palette(ega_palette); + else + init_palette(new_palette); + + init_console(); + + if ((agi_screen = (uint8 *)calloc(GFX_WIDTH, GFX_HEIGHT)) == NULL) + return err_NotEnoughMemory; + +#ifdef USE_CONSOLE + if ((console_screen = (uint8 *)calloc(GFX_WIDTH, GFX_HEIGHT)) == NULL) { + free(agi_screen); + return err_NotEnoughMemory; + } +#endif + + gfx_set_palette(); + + byte mouseCursor[16 * 16]; + const byte *src = mouseCursorArrow; + for (int i = 0; i < 32; ++i) { + int offs = i * 8; + for (byte mask = 0x80; mask != 0; mask >>= 1) { + if (src[0] & mask) { + mouseCursor[offs] = 2; + } else if (src[32] & mask) { + mouseCursor[offs] = 0; + } else { + mouseCursor[offs] = 0xFF; + } + ++offs; + } + ++src; + } + g_system->setMouseCursor(mouseCursor, 16, 16, 1, 1); + + return err_OK; +} + +/** + * Deinitialize graphics device. + * + * @see init_video() + */ +int deinit_video() { + free(agi_screen); +#ifdef USE_CONSOLE + free(console_screen); +#endif + + return err_OK; +} + +int init_machine() { + screen = (unsigned char *)malloc(320 * 200); + clock_count = 0; + clock_ticks = 0; + + return err_OK; +} + +int deinit_machine() { + free(screen); + + return err_OK; +} + +/** + * 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 + */ +void put_pixels_a(int x, int y, int n, uint8 *p) { + if (opt.cgaemu) { + for (x *= 2; n--; p++, x += 2) { + register uint16 q = (cga_map[(*p & 0xf0) >> 4] << 4) | cga_map[*p & 0x0f]; +#ifdef USE_CONSOLE + if (debug_.priority) + q >>= 4; +#endif + *(uint16 *)&agi_screen[x + y * GFX_WIDTH] = q & 0x0f0f; + } + } else { + for (x *= 2; n--; p++, x += 2) { + register uint16 q = ((uint16) * p << 8) | *p; +#ifdef USE_CONSOLE + if (debug_.priority) + q >>= 4; +#endif + *(uint16 *)&agi_screen[x + y * GFX_WIDTH] = q & 0x0f0f; + } + } +} + +#ifdef USE_HIRES + +void put_pixels_hires(int x, int y, int n, uint8 *p) { + //y += CHAR_LINES; + for (; n--; p++, x++) { + uint8 q = *p; +#ifdef USE_CONSOLE + if (debug_.priority) + q >>= 4; +#endif + agi_screen[x + y * GFX_WIDTH] = q & 0x0f; + } +} + +#endif + +/** + * 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 schedule_update(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 do_update() { + if (update.x1 <= update.x2 && update.y1 <= update.y2) { + gfx_putblock(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; +} + +/** + * 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 flush_block(int x1, int y1, int x2, int y2) { + int y, w; + uint8 *p0; + + schedule_update(x1, y1, x2, y2); + + p0 = &agi_screen[x1 + y1 * GFX_WIDTH]; + w = x2 - x1 + 1; + + for (y = y1; y <= y2; y++) { + put_pixels(x1, y, w, p0); + 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 flush_block_a(int x1, int y1, int x2, int y2) { + //y1 += 8; + //y2 += 8; + flush_block(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 flush_screen() { + flush_block(0, 0, GFX_WIDTH - 1, GFX_HEIGHT - 1); +} + +/** + * 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 clear_screen(int c) { + memset(agi_screen, c, GFX_WIDTH * GFX_HEIGHT); + flush_screen(); +} + +#ifdef USE_CONSOLE +/** + * Clear the console screen. + * This function clears the top n lines of the console screen. + * @param n number of lines to clear (in pixels) + */ +void clear_console_screen(int n) { + memset(console_screen + n * GFX_WIDTH, 0, (GFX_HEIGHT - n) * GFX_WIDTH); +} +#endif + +/** + * Save a block of the AGI engine screen + */ +void save_block(int x1, int y1, int x2, int y2, uint8 *b) { + uint8 *p0; + int w, h; + + p0 = &agi_screen[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 restore_block(int x1, int y1, int x2, int y2, uint8 *b) { + uint8 *p0; + int w, h; + + p0 = &agi_screen[x1 + GFX_WIDTH * y1]; + w = x2 - x1 + 1; + h = y2 - y1 + 1; + while (h--) { + memcpy(p0, b, w); + b += w; + p0 += GFX_WIDTH; + } + flush_block(x1, y1, x2, y2); +} + +} // End of namespace Agi diff --git a/engines/agi/graphics.h b/engines/agi/graphics.h new file mode 100644 index 0000000000..3931677441 --- /dev/null +++ b/engines/agi/graphics.h @@ -0,0 +1,86 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2006 The ScummVM project + * + * Copyright (C) 1999-2001 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$ + * + */ + +#ifndef __AGI_GRAPHICS_H +#define __AGI_GRAPHICS_H + +#include "common/stdafx.h" + +#include "agi/agi.h" + +namespace Agi { + +#define GFX_WIDTH 320 +#define GFX_HEIGHT 200 +#define CHAR_COLS 8 +#define CHAR_LINES 8 + +extern uint8 palette[]; + +/* Transparent layer */ +extern uint8 layer1_data[]; +extern uint8 layer2_data[]; + +void gfx_putpixels(int x, int y, int w, uint8 * p); +void gfx_putblock(int x1, int y1, int x2, int y2); + +void put_text_character(int, int, int, unsigned int, int, int); +void shake_screen(int); +void shake_start(void); +void shake_end(void); +void save_screen(void); +void restore_screen(void); + +int init_video(void); +int deinit_video(void); +void schedule_update(int, int, int, int); +void do_update(void); +void put_screen(void); +void flush_block(int, int, int, int); +void flush_block_a(int, int, int, int); +void put_pixels_a(int, int, int, uint8 *); +void flush_screen(void); +void clear_screen(int); +void clear_console_screen(int); +void draw_box(int, int, int, int, int, int, int); +void draw_button(int, int, char *, int, int); +int test_button(int, int, char *); +void draw_rectangle(int, int, int, int, int); +void save_block(int, int, int, int, uint8 *); +void restore_block(int, int, int, int, uint8 *); +void init_palette(uint8 *); + +void put_pixel(int, int, int); + +#ifdef USE_HIRES +void put_pixels_hires(int x, int y, int n, uint8 * p); +#endif +int keypress(void); +int get_key(void); +void print_character(int, int, char, int, int); +void poll_timer(void); + +} // End of namespace Agi + +#endif /* __AGI_GRAPHICS_H */ diff --git a/engines/agi/id.cpp b/engines/agi/id.cpp new file mode 100644 index 0000000000..28c1c00200 --- /dev/null +++ b/engines/agi/id.cpp @@ -0,0 +1,475 @@ +/* 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 "agi/agi.h" +#include "agi/opcodes.h" + +namespace Agi { + +/* + * Determine what AGI v2 system to emulate, these are the major version + * to emulate, thus 2.915 comes under 2.917, 2.4xxx is 2.440, etc. + * + * 0x2089 + * 0x2272 + * 0x2440 + * 0x2917 + * 0x2936 + */ + +const char *ids_database = "\ +# CRC Int Ver [options] Game name # Comment \n\ + \n\ +#---------------------------------------------------------------------------- \n\ +# PC-DOS versions \n\ +#---------------------------------------------------------------------------- \n\ + \n\ +0x484AA 0x2440 AGI Demo 1 (PC) 05/87 [AGI 2.425] # A.K.A. Demo 5 \n\ +0x8CC43 0x2917 AGI Demo 2 (PC 5.25) 11/87 [v1] [AGI 2.915] # Demo 1 \n\ +0x8856C 0x2917 AGI Demo 2 (PC 3.5) 11/87 [AGI 2.915] \n\ +0x843AC 0x2917 AGI Demo 2 (PC 5.25) 01/88 [v2] [AGI 2.917] # Demo 3 \n\ +0x89592 0x3149 AGI Demo 3 (PC) 09/88 [AGI 3.002.102] # Demo 4 \n\ +0x24A18 0x2440 Black Cauldron (PC) 2.00 6/14/87 [AGI 2.439] \n\ +0x22B50 0x3149 Black Cauldron (PC 5.25) 2.10 11/10/88 [AGI 3.002.098] \n\ +0x23E0E 0x3149 Black Cauldron (PC 3.5) 2.10 11/10/88 [AGI 3.002.098] \n\ +0xB5A25 0x3149 Gold Rush! (PC 5.25) 2.01 12/22/88 [AGI 3.002.149] \n\ +0xB1C9E 0x3149 Gold Rush! (PC 3.5) 2.01 12/22/88 [AGI 3.002.149] \n\ +0x49EDA 0x2917 King's Quest 1 (PC 5.25/3.5) 2.0F [AGI 2.917] # also 2.425 \n\ +0x633CB 0x2440 King's Quest 2 (PC 5.25/3.5) 2.1 [AGI 2.411] \n\ +0x63338 0x2917 King's Quest 2 (PC 5.25/3.5) 2.2 [AGI 2.426] # also 2.917 \n\ +0x88673 0x2272 King's Quest 3 (PC) 1.01 11/08/86 [AGI 2.272] \n\ +0x840D3 0x2440 King's Quest 3 (PC 5.25) 2.00 5/25/87 [AGI 2.435] \n\ +0x83191 0x2440 King's Quest 3 (PC 3.5) 2.00 5/25/87 [AGI 2.435] \n\ +0x83695 0x2936 King's Quest 3 (PC 3.5) 2.14 3/15/88 [AGI 2.936] \n\ +0x8410B 0x2936 King's Quest 3 (PC 5.25) 2.14 3/15/88 [AGI 2.936] \n\ +0xB124B 0x3086 King's Quest 4 (PC 3.5) 2.0 7/27/88 [AGI 3.002.086] \n\ +0xB291F 0x3086 King's Quest 4 (PC 3.5) 2.2 9/27/88 [AGI 3.002.086] \n\ +0xB3722 0x3086 King's Quest 4 (PC 5.25) 2.3 9/27/88 [AGI 3.002.086] \n\ +0x9CB15 0x3149 King's Quest 4 demo (PC) [AGI 3.002.102] \n\ +0x6F5E1 0x2440 Leisure Suit Larry 1 (PC 5.25/3.5) 1.00 6/1/87 [AGI 2.440] \n\ +0x4C16D 0x3149 Manhunter NY (PC 5.25) 1.22 8/31/88 [AGI 3.002.107] # also 3.003.102 \n\ +0x49687 0x3149 Manhunter NY (PC 3.5) 1.22 8/31/88 [AGI 3.002.102] \n\ +0x53971 0x3149 Manhunter SF (PC 3.5) 3.02 7/26/89 [AGI 3.002.149] \n\ +0x584F9 0x3149 Manhunter SF (PC 5.25) 3.03 8/17/89 [AGI 3.002.149] \n\ +0x5D77C 0x2917 Mixed-Up Mother Goose (PC) [AGI 2.915] \n\ +0x5D7C6 0x2917 Mixed Up Mother Goose (PC) [AGI 2.915] (Broken) \n\ +0x7F18B 0x2917 Police Quest 1 (PC) 2.0A 10/23/87 [AGI 2.903/2.911] \n\ +0x7EF35 0x2917 Police Quest 1 (PC) 2.0E 11/17/87 [AGI 2.915] \n\ +0x7EF06 0x2917 Police Quest 1 (PC 5.25/ST) 2.0G 12/03/87 [AGI 2.917] \n\ +0x7E0BC 0x2917 Police Quest 1 (PC 3.5) 2.0G 12/03/87 [AGI 2.917] \n\ +0x67FCC 0x2089 Space Quest 1 (PC) 1.0 [AGI 2.089] \n\ +0x68036 0x2089 Space Quest 1 (PC) 1.0X [AGI 2.089] \n\ +0x67F6E 0x2272 Space Quest 1 (PC) 1.1A [AGI 2.272] \n\ +0x68244 0x2440 Space Quest 1 (PC 5.25/3.5) 2.2 [AGI 2.426/2.917] \n\ +0x8DB32 0x2917 Space Quest 2 (PC 5.25) 2.0A [AGI 2.912] \n\ +0x8D825 0x2917 Space Quest 2 (PC 3.5) 2.0A [AGI 2.912] \n\ +0x8DA3E 0x2917 Space Quest 2 (PC 5.25/ST) 2.0C/A [AGI 2.915] \n\ +0x8E6A7 0x2917 Space Quest 2 (PC 3.5) 2.0C/B [AGI 2.917] \n\ +0x8E29B 0x2936 Space Quest 2 (PC 3.5) 2.0F [AGI 2.936] \n\ +0x8DF84 0x2936 Space Quest 2 (PC 5.25) 2.0D [AGI 2.936] \n\ +0x8DE46 0x2936 Space Quest 2 (PC 3.5) 2.0D [AGI 2.936] \n\ +0x8E310 0x2936 Space Quest 2 (PC 5.25) 2.0F [AGI 2.936] \n\ +0x31677 0x2272 Xmas Card 1986 (PC) [AGI 2.272] \n\ + \n\ +#---------------------------------------------------------------------------- \n\ +# Apple //gs versions \n\ +# all guessed interpreter versions \n\ +# \n\ +# Notes: \n\ +# - (CE) in Apple IIgs versions stands for Carlos Escobar --PDD \n\ +#---------------------------------------------------------------------------- \n\ + \n\ +0x93260 0x2917 AGI Demo 2 (IIgs) 1.0C (Censored) \n\ +0x285FB 0x3149 Black Cauldron (Apple IIgs) 1.0O 2/24/89 (CE) # 2.24.89 (CE) \n\ +0xB6F67 0x3149 Gold Rush! (Apple IIgs) 1.0M 2/28/89 (CE) aka 2.01 12/22/88 \n\ +0x4A9E8 0x2272 King's Quest 1 (IIgs) 1.0S-88223 \n\ +0x79D1B 0x2917 King's Quest 2 (IIgs) 2.0A 6/16/88 (CE) \n\ +0x85CD4 0x2917 King's Quest 3 (IIgs) 2.0A 8/28/88 (CE) \n\ +0xAF778 0x3086 King's Quest 4 (IIgs) 1.0K 11/22/88 (CE) \n\ +0x6E41E 0x2440 Leisure Suit Larry 1 (IIgs) 1.0E \n\ +0x4C705 0x3149 Manhunter NY (IIgs) 2.0E 10/05/88 (CE) \n\ +0x5F4E8 0x2917 Mixed Up Mother Goose (IIgs) \n\ +0x7DB3F 0x2917 Police Quest 1 (IIgs) 2.0A-88318 \n\ +0x7DBE5 0x2917 Police Quest 1 (IIgs) 2.0B-88421 \n\ +0x69EC0 0x2917 Space Quest 1 (IIgs) 2.2 \n\ +0x8E983 0x2936 Space Quest 2 (IIgs) 2.0A 7/25/88 (CE) \n\ + \n\ +#---------------------------------------------------------------------------- \n\ +# Macintosh versions \n\ +# all guessed interpreter versions \n\ +#---------------------------------------------------------------------------- \n\ + \n\ +0x4C02C 0x2440 King's Quest 1 (Mac) 2.0C \n\ +0x6382E 0x2440 King's Quest 2 (Mac) 2.0R \n\ +0x8410B 0x2440 King's Quest 3 (Mac) 2.14 3/15/88 \n\ +0x78202 0x2440 Leisure Suit Larry 1 (Mac) 1.05 6/26/87 \n\ +0x7EF06 0x2440 Police Quest 1 (Mac) 2.0G 12/3/87 \n\ +0x6A277 0x2440 Space Quest 1 (Mac) 1.5D \n\ +0x8DF84 0x2936 Space Quest 2 (Mac) 2.0D \n\ + \n\ +#---------------------------------------------------------------------------- \n\ +# Atari ST versions \n\ +# all guessed interpreter versions \n\ +# \n\ +# Notes: \n\ +# - Chris Iden wrote the Atari ST port of AGI --PDD \n\ +#---------------------------------------------------------------------------- \n\ + \n\ +0x1A7AF 0x2272 Donald Duck's Playground (ST) 1.0A 8/8/86 \n\ +0x4A079 0x2272 King's Quest 1 (ST) 1.0V \n\ +0x882B7 0x2272 King's Quest 3 (ST) 1.02 11/18/86 \n\ +0xB68AB 0x3149 Gold Rush! (ST) 1.01 1/13/89 aka 2.01 12/22/88 \n\ +0x6F7E1 0x2440 Leisure Suit Larry 1 (ST) 1.04 6/18/87 \n\ +0x4CE19 0x3149 Manhunter NY (ST) 1.03 10/20/88 \n\ +0x5360B 0x3149 Manhunter SF (ST) 1.0 7/29/89 \n\ +0x69597 0x2440 Space Quest 1 (ST) 1.1A \n\ + \n\ +#---------------------------------------------------------------------------- \n\ +# Amiga versions \n\ +# Use option -A- to enable padding \n\ +# \n\ +# Notes: \n\ +# - Amiga KQ3 (2.333) seems to need interpreter version 3.002.086 \n\ +#---------------------------------------------------------------------------- \n\ + \n\ +0x25640 0x2440 [A] Black Cauldron (Amiga) 2.00 6/14/87 # guessed int \n\ +0x1AFBA 0x2272 [A] Donald Duck's Playground (Amiga) 1.0C # guessed int \n\ +0xB3E1A 0x3149 [A] Gold Rush! (Amiga) 1.01 1/13/89 aka 2.05 3/9/89 # 2.316 \n\ +0x49C6B 0x2440 [A] King's Quest 1 (Amiga) 1.0U # 2.082 \n\ +0x5D395 0x2440 [A] King's Quest 2 (Amiga) 2.0J # guessed int \n\ +0x5BCE6 0x2440 [A] King's Quest 2 (Amiga) 2.0J (Broken) \n\ +0x5F4B9 0x2440 [A] King's Quest 2 (Amiga) 2.0J (Broken) # 2.176 \n\ +0x888C1 0x2440 [A] King's Quest 3 (Amiga) 1.01 11/8/86 \n\ +0x84793 0x3086 [A] King's Quest 3 (Amiga) 2.15 11/15/89 # 2.333 \n\ +0x6FDDB 0x2440 [A] Leisure Suit Larry 1 (Amiga) 1.05 6/26/87 # x.yyy \n\ +0x4BA94 0x3149 [A] Manhunter NY (Amiga) 1.06 3/18/89 # x.yyy \n\ +0x53D51 0x3086 [A] Manhunter SF (Amiga) 3.06 8/17/89 # 2.333 \n\ +0x5CFB1 0x3086 [A] Mixed-Up Mother Goose (Amiga) 1.1 # guessed int \n\ +0x7F752 0x3149 [A] Police Quest 1 (Amiga) 2.0B 2/22/89 # 2.310 \n\ +0x696DD 0x2440 [A] Space Quest 1 (Amiga) 1.2 # 2.082 \n\ +0x8FEA6 0x2936 [A] Space Quest 2 (Amiga) 2.0F # 2.202 \n\ + \n\ +#---------------------------------------------------------------------------- \n\ +# CoCo versions \n\ +# what version of DOS AGI does CoCo 2.023 correspond with? \n\ +# guessing 2.272 because the PC version 1.0 is 2.272; doesn't fit date though \n\ +# \n\ +# Notes: \n\ +# - Chris Iden wrote the CoCo port of AGI --PDD \n\ +#---------------------------------------------------------------------------- \n\ + \n\ +0x7CBE8 0x2272 King's Quest 3 (CoCo3) 1.0C 6/27/88 # 2.023 \n\ +0x70D35 0x2440 Leisure Suit Larry 1 (CoCo3) \n\ + \n\ +#---------------------------------------------------------------------------- \n\ +# AGDS games \n\ +# Use option -a- for AGDS games \n\ +#---------------------------------------------------------------------------- \n\ + \n\ +0x5501A 0x2440 [a] Groza # AGDS sample game \n\ + \n\ +#---------------------------------------------------------------------------- \n\ +# Fan-made AGI games \n\ +#---------------------------------------------------------------------------- \n\ + \n\ +0x3F2F7 0x2917 [m] AGI Mouse 0.7 Demo \n\ +0x3F744 0x2917 [m] AGI Mouse 1.0 Demo # 2.917 6/24/00 \n\ +0x3F74F 0x2917 [m] AGI Mouse 1.1 Demo # 2.917 1/01/01 \n\ +0x17599 0x2917 [m] Sliding Tile Game v1.00 # 2.917 6/02/01 \n\ +0x785c4 0x2936 [m] Jolimie v0.6 # 2.936 2000 \n\ +#Jolimie uses AGIPal only and not AGIMouse; no way to separate these currently \n\ +0x40D80 0x2440 AGI Trek # 2.440 9/21/98 \n\ +0x64CB7 0x2440 Space Trek 1.0 # 2.440 12/13/98 \n\ +0x6596A 0x2917 Space Trek (remake) # 2.917 6/09/99 \n\ +0x96909 0x2917 Operation: RECON teaser 1.1 # \n\ +0x185A6 0x2917 AGI Piano v1.0 # ? 1998 \n\ +0x91ACF 0x2917 Dave's Quest .07 # ? \n\ +0x620F6 0x2917 Time Quest demo D0.2 # ? 1998 \n\ +0x7466F 0x2917 Tex McPhilip I # ? 2000 \n\ +0x9E400 0x2917 Tex McPhilip II # ? 2000 \n\ +0xAB9A8 0x2917 Justin Quest 1.0 # 2.917 \n\ +0x7D473 0x2917 The Ruby Cast demo 0.2 # 2.917 1998 \n\ +0xB4D7A 0x2917 Residence 44 Quest 1.0a # 2.917 1999 \n\ +0x5D077 0x2917 Escape Quest demo # ? 1998 \n\ +0x5A434 0x2917 Acidopolis (1.0) demo # ? \n\ +0x45CDF 0x2917 Go West, Young Hippie demo # 2.917 \n\ +0x4C9DC 0x2917 Speeder Bike Challenge v1.0 # ? \n\ +0x112BF9 0x2917 Space Quest 0: Replicated 1.04 # 6/27/2003 \n\ +0x6E70F 0x2917 Space Quest: The Lost Chapter v10.0 \n\ +0x5859E 0x2917 Phantasmagoria \n\ +0x7B5DF 0x2917 Dashiki demo \n\ +0x9405B 0x2917 Dashiki 256-color demo (Unsupported) \n\ +0x6ADCD 0x2917 Jen's Quest 0.1 demo \n\ +0x4EE64 0x2917 Monkey Man \n\ +"; + +int setup_v2_game(int ver, uint32 crc); +int setup_v3_game(int ver, uint32 crc); +int v4id_game(uint32 crc); + +uint32 match_crc(uint32 crc, char *name, int len) { + char *c, *t, buf[256]; + uint32 id, ver; + + Common::MemoryReadStream f((const byte *)ids_database, strlen(ids_database)); + + while (!f.eos()) { + f.readLine(buf, 256); + c = strchr(buf, '#'); + if (c) + *c = 0; + + /* Remove spaces at end of line */ + if (strlen(buf)) { + for (c = buf + strlen(buf) - 1; + *c == ' ' || *c == '\t'; *c-- = 0) { + } + } + + t = strtok(buf, " \t\r\n"); + if (t == NULL) + continue; + id = strtoul(t, NULL, 0); + + t = strtok(NULL, " \t\r\n"); + if (t == NULL) + continue; + ver = strtoul(t, NULL, 0); + + t = strtok(NULL, "\n\r"); + for (; *t == ' ' || *t == '\t'; t++); + + if (id == crc) { + /* Now we must check options enclosed in brackets + * like [A] for Amiga + */ + + if (*t == '[') { + while (*t != ']') { + switch (*t++) { + case 'A': + opt.amiga = true; + break; + case 'a': + opt.agds = true; + break; +#ifdef USE_MOUSE + case 'm': + opt.agimouse = true; + break; +#endif + } + } + t++; + + for (; (*t == ' ' || *t == '\t') && *t; t++) { + } + } + + strncpy(name, t, len); + return ver; + } + } + + return 0; +} + +static uint32 match_version(uint32 crc) { + int ver; + char name[80]; + + if ((ver = match_crc(crc, name, 80)) > 0) + report("AGI game detected: %s\n\n", name); + + return ver; +} + +int v2id_game() { + int y, ver; + uint32 len, c, crc; + uint8 *buff; + Common::File fp; + char *fn[] = { "viewdir", "logdir", "picdir", "snddir", "words.tok", "object", "" }; + + buff = (uint8 *)malloc(8192); + + for (crc = y = 0; fn[y][0]; y++) { + char *path = fn[y]; + if (fp.open(path)) { + for (len = 1; len > 0;) { + memset(buff, 0, 8192); + len = fp.read(buff, 8000); + for (c = 0; c < len; c++) + crc += *(buff + c); + } + fp.close(); + } + } + free(buff); + + report("Computed CRC: 0x%05x\n", crc); + ver = match_version(crc); + game.crc = crc; + game.ver = ver; + debugC(2, kDebugLevelMain, "game.ver = 0x%x", game.ver); + agi_set_release(ver); + return setup_v2_game(ver, crc); +} + +/* + * Currently, there is no known difference between v3.002.098 -> v3.002.149 + * So version emulated; + * + * 0x0086, + * 0x0149 + */ + +int v3id_game() { + int ec = err_OK, y, ver; + uint32 len, c, crc; + uint8 *buff; + Common::File fp; + char *fn[] = { "words.tok", "object", "" }; + Common::String path; + + buff = (uint8 *)malloc(8192); + + for (crc = 0, y = 0; fn[y][0] != 0x0; y++) { + path = fn[y]; + if (fp.open(path)) { + len = 1; + while (len > 0) { + memset(buff, 0, 8192); + len = fp.read(buff, 8000); + for (c = 0; c < len; c++) + crc += *(buff + c); + } + fp.close(); + } + } + + /* now do the directory file */ + + path = Common::String(game.name) + DIR_; + + if (fp.open(path)) { + for (len = 1; len > 0;) { + memset(buff, 0, 8192); + len = fp.read(buff, 8000); + for (c = 0; c < len; c++) + crc += *(buff + c); + } + fp.close(); + } + + free(buff); + + report("Computed CRC: 0x%05x\n", crc); + ver = match_version(crc); + game.crc = crc; + game.ver = ver; + agi_set_release(ver); + + ec = setup_v3_game(ver, crc); + + return ec; +} + +/** + * + */ +int setup_v2_game(int ver, uint32 crc) { + int ec = err_OK; + + if (ver == 0) { + report("Unknown v2 Sierra game: %08x\n\n", crc); + agi_set_release(0x2917); + } + + /* setup the differences in the opcodes and other bits in the + * AGI v2 specs + */ + if (opt.emuversion) + agi_set_release(opt.emuversion); + + if (opt.agds) + agi_set_release(0x2440); /* ALL AGDS games built for 2.440 */ + + switch (agi_get_release()) { + case 0x2089: + logic_names_cmd[0x86].num_args = 0; /* quit: 0 args */ + logic_names_cmd[0x97].num_args = 3; /* print.at: 3 args */ + logic_names_cmd[0x98].num_args = 3; /* print.at.v: 3 args */ + break; + case 0x2272: + /* KQ3 0x88673 (2.272) requires print.at with 4 arguments */ + break; + case 0x2440: + break; + case 0x2917: + break; + case 0x2936: + break; + default: + report("** Cannot setup for unknown version\n"); + ec = err_UnknownAGIVersion; + break; + } + + return ec; +} + +/** + * + */ +int setup_v3_game(int ver, uint32 crc) { + int ec = err_OK; + + if (ver == 0) { + report("Unknown v3 Sierra game: %08x\n\n", crc); + agi_set_release(ver = 0x3149); + } + + if (opt.emuversion) + agi_set_release(ver = opt.emuversion); + + switch (ver) { + case 0x3086: + logic_names_cmd[0xad].num_args = 1; /* 173 : 1 args */ + break; + case 0x3149: + logic_names_cmd[0xad].num_args = 0; /* 173 : 0 args */ + break; + default: + report("Error: cannot setup for unknown version\n"); + ec = err_UnknownAGIVersion; + break; + } + + return ec; +} + +} // End of namespace Agi diff --git a/engines/agi/inv.cpp b/engines/agi/inv.cpp new file mode 100644 index 0000000000..4d4014027e --- /dev/null +++ b/engines/agi/inv.cpp @@ -0,0 +1,212 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2006 The ScummVM project + * + * Copyright (C) 1999-2001 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 "agi/agi.h" +#include "agi/sprite.h" +#include "agi/graphics.h" +#include "agi/keyboard.h" +#include "agi/text.h" +#include "agi/keyboard.h" + +namespace Agi { + +/* + * Messages and coordinates + */ + +#define NOTHING_X 16 +#define NOTHING_Y 3 +#define NOTHING_MSG "nothing" + +#define ANY_KEY_X 4 +#define ANY_KEY_Y 24 +#define ANY_KEY_MSG "Press a key to return to the game" + +#define YOUHAVE_X 11 +#define YOUHAVE_Y 0 +#define YOUHAVE_MSG "You are carrying:" + +#define SELECT_X 2 +#define SELECT_Y 24 +#define SELECT_MSG "Press ENTER to select, ESC to cancel" + +static uint8 *intobj = NULL; + +static void print_item(int n, int fg, int bg) +{ + print_text(object_name(intobj[n]), 0, n % 2 ? 39 - strlen(object_name(intobj[n])) : 1, + (n / 2) + 2, 40, fg, bg); +} + +#ifdef USE_MOUSE +static int find_item() { + int r, c; + + r = mouse.y / CHAR_LINES; + c = mouse.x / CHAR_COLS; + + debugC(6, kDebugLevelInventory, "r = %d, c = %d", r, c); + + if (r < 2) + return -1; + + return (r - 2) * 2 + (c > 20); +} +#endif + +static int show_items() { + unsigned int x, i; + + for (x = i = 0; x < game.num_objects; x++) { + if (object_get_location(x) == EGO_OWNED) { + /* add object to our list! */ + intobj[i] = x; + print_item(i, STATUS_FG, STATUS_BG); + i++; + } + } + + if (i == 0) { + print_text(NOTHING_MSG, 0, NOTHING_X, NOTHING_Y, 40, STATUS_FG, STATUS_BG); + } + + return i; +} + +static void select_items(int n) { + int fsel = 0; + + while (42) { + if (n > 0) + print_item(fsel, STATUS_BG, STATUS_FG); + + switch (wait_any_key()) { + case KEY_ENTER: + setvar(V_sel_item, intobj[fsel]); + goto exit_select; + case KEY_ESCAPE: + setvar(V_sel_item, 0xff); + goto exit_select; + 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; +#ifdef USE_MOUSE + case BUTTON_LEFT:{ + int i = find_item(); + if (i >= 0 && i < n) { + setvar(V_sel_item, intobj[fsel = i]); + debugC(6, kDebugLevelInventory, "item found: %d", fsel); + show_items(); + print_item(fsel, STATUS_BG, STATUS_FG); + do_update(); + goto exit_select; + } + break; + } +#endif + default: + break; + } + + show_items(); + do_update(); + } + + exit_select: + debugC(6, kDebugLevelInventory, "selected: %d", fsel); +} + +/* + * Public functions + */ + +/** + * Display inventory items. + */ +void inventory() { + int old_fg, old_bg; + int n; + + /* screen is white with black text */ + old_fg = game.color_fg; + old_bg = game.color_bg; + game.color_fg = 0; + game.color_bg = 15; + clear_screen(game.color_bg); + + print_text(YOUHAVE_MSG, 0, YOUHAVE_X, YOUHAVE_Y, 40, STATUS_FG, STATUS_BG); + + /* FIXME: doesn't check if objects overflow off screen... */ + + intobj = (uint8 *) malloc(4 + game.num_objects); + memset(intobj, 0, (4 + game.num_objects)); + + n = show_items(); + + if (getflag(F_status_selects_items)) { + print_text(SELECT_MSG, 0, SELECT_X, SELECT_Y, 40, STATUS_FG, STATUS_BG); + } else { + print_text(ANY_KEY_MSG, 0, ANY_KEY_X, ANY_KEY_Y, 40, STATUS_FG, STATUS_BG); + } + + flush_screen(); + + /* 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(F_status_selects_items)) + select_items(n); + + free(intobj); + + if (!getflag(F_status_selects_items)) + wait_any_key(); + + clear_screen(0); + write_status(); + show_pic(); + game.color_fg = old_fg; + game.color_bg = old_bg; + game.has_prompt = 0; + flush_lines(game.line_user_input, 24); +} + +} // End of namespace Agi diff --git a/engines/agi/keyboard.cpp b/engines/agi/keyboard.cpp new file mode 100644 index 0000000000..998b95f1cd --- /dev/null +++ b/engines/agi/keyboard.cpp @@ -0,0 +1,388 @@ +/* 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 "agi/agi.h" +#include "agi/graphics.h" +#include "agi/keyboard.h" +#include "agi/menu.h" +#include "agi/text.h" /* remove later */ + +namespace Agi { + +char last_sentence[40]; + +#ifdef USE_CONSOLE +extern struct agi_console console; +#endif + +/* FIXME */ +extern int open_dialogue; + +struct string_data { + int x; + int y; + int len; + int str; +}; + +struct string_data stringdata; + +/* + * IBM-PC keyboard scancodes + */ +uint8 scancode_table[26] = { + 30, /* A */ + 48, /* B */ + 46, /* C */ + 32, /* D */ + 18, /* E */ + 33, /* F */ + 34, /* G */ + 35, /* H */ + 23, /* I */ + 36, /* J */ + 37, /* K */ + 38, /* L */ + 50, /* M */ + 49, /* N */ + 24, /* O */ + 25, /* P */ + 16, /* Q */ + 19, /* R */ + 31, /* S */ + 20, /* T */ + 22, /* U */ + 47, /* V */ + 17, /* W */ + 45, /* X */ + 21, /* Y */ + 44 /* Z */ +}; + +void init_words() { + game.num_ego_words = 0; +} + +void clean_input() { + while (game.num_ego_words) + free(game.ego_words[--game.num_ego_words].word); +} + +void get_string(int x, int y, int len, int str) { + new_input_mode(INPUT_GETSTRING); + stringdata.x = x; + stringdata.y = y; + stringdata.len = len; + stringdata.str = str; +} + +/** + * Raw key grabber. + * poll_keyboard() is the raw key grabber (above the gfx driver, that is). + * It handles console keys and insulates AGI from the console. In the main + * loop, handle_keys() handles keyboard input and ego movement. + */ +int do_poll_keyboard() { + int key = 0; + + /* If a key is ready, rip it */ + if (keypress()) { + key = get_key(); + debugC(3, kDebugLevelInput, "key %02x pressed", key); + } + + return key; +} + +int handle_controller(int key) { + struct vt_entry *v = &game.view_table[0]; + int i; + + /* The Black Cauldron needs KEY_ESCAPE to use menus */ + if (key == 0 /*|| key == KEY_ESCAPE */ ) + return false; + + debugC(3, kDebugLevelInput, "key = %04x", key); + + for (i = 0; i < MAX_DIRS; i++) { + if (game.ev_keyp[i].data == key) { + debugC(3, kDebugLevelInput, "event %d: key press", i); + game.ev_keyp[i].occured = true; + report("event AC:%i occured\n", i); + return true; + } + } + +#ifdef USE_MOUSE + if (key == BUTTON_LEFT) { + if (getflag(F_menus_work) && mouse.y <= CHAR_LINES) { + new_input_mode(INPUT_MENU); + return true; + } + } +#endif + + if (game.player_control) { + int d = 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; + } + } +#ifdef USE_MOUSE + if (!opt.agimouse) { + /* Handle mouse button events */ + if (key == BUTTON_LEFT) { + v->flags |= ADJ_EGO_XY; + v->parm1 = WIN_TO_PIC_X(mouse.x); + v->parm2 = WIN_TO_PIC_Y(mouse.y); + return true; + } + } +#endif + + v->flags &= ~ADJ_EGO_XY; + + if (d || key == KEY_STATIONARY) { + v->direction = v->direction == d ? 0 : d; + return true; + } + } + + return false; +} + +void handle_getstring(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 KEY_ENTER: + debugC(3, kDebugLevelInput, "KEY_ENTER"); + game.has_prompt = 0; + buf[pos] = 0; + strcpy(game.strings[stringdata.str], buf); + debugC(3, kDebugLevelInput, "buffer=[%s]", buf); + buf[pos = 0] = 0; + new_input_mode(INPUT_NORMAL); + print_character(stringdata.x + strlen(game.strings[stringdata.str]) + 1, + stringdata.y, ' ', game.color_fg, game.color_bg); + return; + case KEY_ESCAPE: + debugC(3, kDebugLevelInput, "KEY_ESCAPE"); + game.has_prompt = 0; + buf[pos = 0] = 0; + strcpy(game.strings[stringdata.str], buf); + new_input_mode(INPUT_NORMAL); + /* new_input_mode (INPUT_MENU); */ + break; + case KEY_BACKSPACE: /*0x08: */ + if (!pos) + break; + + print_character(stringdata.x + (pos + 1), stringdata.y, + ' ', game.color_fg, game.color_bg); + + pos--; + buf[pos] = 0; + break; + default: + if (key < 0x20 || key > 0x7f) + break; + + if (pos >= stringdata.len) + break; + + buf[pos++] = key; + buf[pos] = 0; + + /* Echo */ + print_character(stringdata.x + pos, stringdata.y, buf[pos - 1], + game.color_fg, game.color_bg); + + break; + } + + /* print cursor */ + print_character(stringdata.x + pos + 1, stringdata.y, + (char)game.cursor_char, game.color_fg, game.color_bg); +} + +void handle_keys(int key) { + uint8 *p = NULL; + int c = 0; + static uint8 formated_entry[256]; + int l = game.line_user_input; + int fg = game.color_fg, bg = game.color_bg; + + setvar(V_word_not_found, 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.input_buffer; *p && *p == 0x20; p++); + + /* Copy to internal buffer */ + for (; *p; p++) { + /* Squash spaces */ + if (*p == 0x20 && *(p + 1) == 0x20) { + p++; + continue; + } + formated_entry[c++] = tolower(*p); + } + formated_entry[c++] = 0; + + /* Handle string only if it's not empty */ + if (formated_entry[0]) { + strcpy((char *)game.echo_buffer, (const char *)game.input_buffer); + strcpy(last_sentence, (const char *)formated_entry); + dictionary_words(last_sentence); + } + + /* Clear to start a new line */ + game.has_prompt = 0; + game.input_buffer[game.cursor_pos = 0] = 0; + debugC(3, kDebugLevelInput, "clear lines"); + clear_lines(l, l + 1, bg); + flush_lines(l, l + 1); + + break; + case KEY_ESCAPE: + debugC(3, kDebugLevelInput, "KEY_ESCAPE"); + new_input_mode(INPUT_MENU); + break; + case KEY_BACKSPACE: + /* Ignore backspace at start of line */ + if (game.cursor_pos == 0) + break; + + /* erase cursor */ + print_character(game.cursor_pos + 1, l, ' ', fg, bg); + game.input_buffer[--game.cursor_pos] = 0; + /* Print cursor */ + print_character(game.cursor_pos + 1, l, game.cursor_char, fg, bg); + break; + default: + /* Ignore invalid keystrokes */ + if (key < 0x20 || key > 0x7f) + break; + + /* Maximum input size reached */ + if (game.cursor_pos >= getvar(V_max_input_chars)) + break; + + game.input_buffer[game.cursor_pos++] = key; + game.input_buffer[game.cursor_pos] = 0; + + /* echo */ + print_character(game.cursor_pos, l, game.input_buffer[game.cursor_pos - 1], fg, bg); + + /* Print cursor */ + print_character(game.cursor_pos + 1, l, game.cursor_char, fg, bg); + break; + } +} + +int wait_key() { + int key; + + /* clear key queue */ + while (keypress()) { + get_key(); + } + + debugC(3, kDebugLevelInput, "waiting..."); + while (42) { + poll_timer(); /* msdos driver -> does nothing */ + key = do_poll_keyboard(); + if (!console_keyhandler(key)) { + if (key == KEY_ENTER || key == KEY_ESCAPE +#ifdef USE_MOUSE + || key == BUTTON_LEFT +#endif + ) + break; + } + console_cycle(); + } + return key; +} + +int wait_any_key() { + int key; + + /* clear key queue */ + while (keypress()) { + get_key(); + } + + debugC(3, kDebugLevelInput, "waiting..."); + while (42) { + poll_timer(); /* msdos driver -> does nothing */ + key = do_poll_keyboard(); + if (!console_keyhandler(key) && key) + break; + console_cycle(); + } + return key; +} + +} // End of namespace Agi diff --git a/engines/agi/keyboard.h b/engines/agi/keyboard.h new file mode 100644 index 0000000000..7e507e5f97 --- /dev/null +++ b/engines/agi/keyboard.h @@ -0,0 +1,94 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2006 The ScummVM project + * + * Copyright (C) 1999-2001 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$ + * + */ + +#ifndef __AGI_KEYBOARD_H +#define __AGI_KEYBOARD_H + +#include "common/stdafx.h" + +#include "agi/agi.h" + +namespace Agi { + +/* 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 * */ + +#ifdef USE_MOUSE +#define BUTTON_LEFT 0xF101 /* Left mouse button */ +#define BUTTON_RIGHT 0xF202 /* Right mouse button */ +#endif + +#define KEY_SCAN(k) (k >> 8) +#define KEY_ASCII(k) (k & 0xff) + +extern uint8 scancode_table[]; + +void init_words(void); +void clean_input(void); +int do_poll_keyboard(void); +void clean_keyboard(void); +void handle_keys(int); +void handle_getstring(int); +int handle_controller(int); +void get_string(int, int, int, int); +uint16 agi_get_keypress(void); +int wait_key(void); +int wait_any_key(void); + +} // End of namespace Agi + +#endif /* __AGI_KEYBOARD_H */ diff --git a/engines/agi/list.h b/engines/agi/list.h new file mode 100644 index 0000000000..999c591ce0 --- /dev/null +++ b/engines/agi/list.h @@ -0,0 +1,140 @@ +/* + * $Id$ + * + * List management macros from the Linux kernel + */ + +#ifndef _LINUX_LIST_H +#define _LINUX_LIST_H + +#include "agi/agi.h" + +namespace Agi { + +/** + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when + * manipulating whole lists rather than single entries, as + * sometimes we already know the next/prev entries and we can + * generate better code by using them directly rather than + * using the generic single-entry routines. + */ + +struct list_head { + struct list_head *next, *prev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) \ + struct list_head name = LIST_HEAD_INIT(name) + +#define INIT_LIST_HEAD(ptr) do { \ + (ptr)->next = (ptr); (ptr)->prev = (ptr); \ +} while (0) + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static INLINE void __list_add(struct list_head *tnew, struct list_head *prev, struct list_head *next) { + next->prev = tnew; + tnew->next = next; + tnew->prev = prev; + prev->next = tnew; +} + +/** + * add a new entry + * Insert a new entry after the specified head. + * This is good for implementing stacks. + * + * @param new new entry to be added + * @param head list head to add it after + */ +static INLINE void list_add(struct list_head *tnew, struct list_head *head) { + __list_add(tnew, head, head->next); +} + +/** + * add a new entry + * Insert a new entry before the specified head. + * This is useful for implementing queues. + * + * @new: new entry to be added + * @head: list head to add it before + */ +static INLINE void list_add_tail(struct list_head *tnew, struct list_head *head) { + __list_add(tnew, head->prev, head); +} + +/** + * Delete a list entry (makes prev/next entries point to each other) + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static INLINE void __list_del(struct list_head *prev, struct list_head *next) { + next->prev = prev; + prev->next = next; +} + +/** + * deletes entry from list. + * @param entry the element to delete from the list. + */ +static INLINE void list_del(struct list_head *entry) { + __list_del(entry->prev, entry->next); +} + +/** + * tests whether a list is empty + * @param head the list to test. + */ +static INLINE int list_empty(struct list_head *head) { + return head->next == head; +} + +/** + * join two lists + * @param list the new list to add. + * @param head the place to add it in the first list. + */ +static INLINE void list_splice(struct list_head *list, struct list_head *head) { + struct list_head *first = list->next; + + if (first != list) { + struct list_head *last = list->prev; + struct list_head *at = head->next; + + first->prev = head; + head->next = first; + + last->next = at; + at->prev = last; + } +} + +/** + * get the struct for this entry + * @param ptr the &struct list_head pointer. + * @param type the type of the struct this is embedded in. + * @param member the name of the list_struct within the struct. + */ +#define list_entry(ptr, type, member) \ + ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) + +/** + * iterate over a list + * @param pos the &struct list_head to use as a loop counter. + * @param head the head for your list. + */ +#define list_for_each(pos, head, next) \ + for (pos = (head)->next; pos != (head); pos = pos->next) + +} // End of namespace Agi + +#endif diff --git a/engines/agi/logic.cpp b/engines/agi/logic.cpp new file mode 100644 index 0000000000..1929b3fb3c --- /dev/null +++ b/engines/agi/logic.cpp @@ -0,0 +1,116 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2006 The ScummVM project + * + * Copyright (C) 1999-2001 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 "agi/agi.h" + +namespace Agi { + +/** + * Decode logic resource + * This function decodes messages from the specified raw logic resource + * into a message list. + * @param n The number of the logic resource to decode. + */ +int decode_logic(int n) { + int ec = err_OK; + int mstart, mend, mc; + uint8 *m0; + + /* decrypt messages at end of logic + build message list */ + + /* report ("decoding logic #%d\n", n); */ + m0 = game.logics[n].data; + + mstart = READ_LE_UINT16(m0) + 2; + mc = *(m0 + mstart); + mend = READ_LE_UINT16(m0 + mstart + 1); + m0 += mstart + 3; /* cover header info */ + mstart = mc << 1; + + /* if the logic was not compressed, decrypt the text messages + * only if there are more than 0 messages + */ + if ((~game.dir_logic[n].flags & RES_COMPRESSED) && mc > 0) + decrypt(m0 + mstart, mend - mstart); /* decrypt messages */ + + /* build message list */ + m0 = game.logics[n].data; + mstart = READ_LE_UINT16(m0) + 2; /* +2 covers pointer */ + game.logics[n].num_texts = *(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 */ + + /* allocate list of pointers to point into our data */ + + game.logics[n].texts = (char **)calloc(1 + game.logics[n].num_texts, sizeof(char *)); + + /* cover header info */ + m0 += mstart + 3; + + if (game.logics[n].texts != NULL) { + /* move list of strings into list to make real pointers */ + for (mc = 0; mc < game.logics[n].num_texts; mc++) { + mend = READ_LE_UINT16(m0 + mc * 2); + game.logics[n].texts[mc] = mend ? (char *)m0 + mend - 2 : (char *)""; + } + /* set loaded flag now its all completly loaded */ + game.dir_logic[n].flags |= RES_LOADED; + } else { + /* unload data + * blah DF YA WANKER!!@!@# frag. i'm so dumb. not every logic + * has text + */ + free(game.logics[n].data); + ec = err_NotEnoughMemory; + } + + return ec; +} + +/** + * Unload logic resource + * This function unloads the specified logic resource, freeing any + * memory chunks allocated for this resource. + * @param n The number of the logic resource to unload + */ +void unload_logic(int n) { + if (game.dir_logic[n].flags & RES_LOADED) { + free(game.logics[n].data); + if (game.logics[n].num_texts) + free(game.logics[n].texts); + game.logics[n].num_texts = 0; + game.dir_logic[n].flags &= ~RES_LOADED; + } + + /* if cached, we end up here */ + game.logics[n].sIP = 2; + game.logics[n].cIP = 2; +} + +} // End of namespace Agi diff --git a/engines/agi/logic.h b/engines/agi/logic.h new file mode 100644 index 0000000000..cd6fbf752c --- /dev/null +++ b/engines/agi/logic.h @@ -0,0 +1,49 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2006 The ScummVM project + * + * Copyright (C) 1999-2001 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$ + * + */ + +#ifndef __AGI_LOGIC_H +#define __AGI_LOGIC_H + +#include "agi/agi.h" + +namespace Agi { + +/** + * AGI logic resource structure. + */ +struct agi_logic { + uint8 *data; /**< raw resource data */ + int size; /**< size of data */ + int sIP; /**< saved IP */ + int cIP; /**< current IP */ + int num_texts; /**< number of messages */ + char **texts; /**< message list */ +}; + +int decode_logic(int); +void unload_logic(int); + +} // End of namespace Agi + +#endif /* __AGI_LOGIC_H */ diff --git a/engines/agi/lzw.cpp b/engines/agi/lzw.cpp new file mode 100644 index 0000000000..29259a5bc9 --- /dev/null +++ b/engines/agi/lzw.cpp @@ -0,0 +1,191 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2006 The ScummVM project + * + * Copyright (C) 1999-2001 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$ + * + */ + +/*************************************************************************** +** decomp.c +** +** Routines that deal with AGI version 3 specific features. +** The original LZW code is from DJJ, October 1989, p.86. +** It has been modified to handle AGI compression. +** +** (c) 1997 Lance Ewing +***************************************************************************/ + +#include "common/stdafx.h" + +#include "agi/agi.h" +#include "agi/lzw.h" + +namespace Agi { + +#define MAXBITS 12 +#define TABLE_SIZE 18041 /* strange number */ +#define START_BITS 9 + +static int32 BITS, MAX_VALUE, MAX_CODE; +static uint32 *prefix_code; +static uint8 *append_character; +static uint8 *decode_stack; +static int32 input_bit_count = 0; /* Number of bits in input bit buffer */ +static uint32 input_bit_buffer = 0L; + +static void initLZW() { + decode_stack = (uint8 *)calloc(1, 8192); + prefix_code = (uint32 *)malloc(TABLE_SIZE * sizeof(uint32)); + append_character = (uint8 *)malloc(TABLE_SIZE * sizeof(uint8)); + input_bit_count = 0; /* Number of bits in input bit buffer */ + input_bit_buffer = 0L; +} + +static void closeLZW() { + free(decode_stack); + free(prefix_code); + free(append_character); +} + +/*************************************************************************** +** setBITS +** +** Purpose: To adjust the number of bits used to store codes to the value +** passed in. +***************************************************************************/ +int setBITS(int32 value) { + if (value == MAXBITS) + return true; + + BITS = value; + MAX_VALUE = (1 << BITS) - 1; + MAX_CODE = MAX_VALUE - 1; + + return false; +} + +/*************************************************************************** +** decode_string +** +** Purpose: To return the string that the code taken from the input buffer +** represents. The string is returned as a stack, i.e. the characters are +** in reverse order. +***************************************************************************/ +static uint8 *decode_string(uint8 *buffer, uint32 code) { + uint32 i; + + for (i = 0; code > 255;) { + *buffer++ = append_character[code]; + code = prefix_code[code]; + if (i++ >= 4000) { + fprintf(stderr, "lzw: error in code expansion.\n"); + abort(); + } + } + *buffer = code; + + return buffer; +} + +/*************************************************************************** +** input_code +** +** Purpose: To return the next code from the input buffer. +***************************************************************************/ +static uint32 input_code(uint8 **input) { + uint32 r; + + while (input_bit_count <= 24) { + input_bit_buffer |= (uint32) * (*input)++ << input_bit_count; + input_bit_count += 8; + } + r = (input_bit_buffer & 0x7FFF) % (1 << BITS); + input_bit_buffer >>= BITS; + input_bit_count -= BITS; + + return r; +} + +/*************************************************************************** +** expand +** +** Purpose: To uncompress the data contained in the input buffer and store +** the result in the output buffer. The fileLength parameter says how +** many bytes to uncompress. The compression itself is a form of LZW that +** adjusts the number of bits that it represents its codes in as it fills +** up the available codes. Two codes have special meaning: +** +** code 256 = start over +** code 257 = end of data +***************************************************************************/ +void LZW_expand(uint8 *in, uint8 *out, int32 len) { + int32 c, lzwnext, lzwnew, lzwold; + uint8 *s, *end; + + initLZW(); + + setBITS(START_BITS); /* Starts at 9-bits */ + lzwnext = 257; /* Next available code to define */ + + end = (unsigned char *)((long)out + (long)len); + + lzwold = input_code(&in); /* Read in the first code */ + c = lzwold; + lzwnew = input_code(&in); + + while ((out < end) && (lzwnew != 0x101)) { + if (lzwnew == 0x100) { + /* Code to "start over" */ + lzwnext = 258; + setBITS(START_BITS); + lzwold = input_code(&in); + c = lzwold; + *out++ = (char)c; + lzwnew = input_code(&in); + } else { + if (lzwnew >= lzwnext) { + /* Handles special LZW scenario */ + *decode_stack = c; + s = decode_string(decode_stack + 1, lzwold); + } else + s = decode_string(decode_stack, lzwnew); + + /* Reverse order of decoded string and + * store in out buffer + */ + c = *s; + while (s >= decode_stack) + *out++ = *s--; + + if (lzwnext > MAX_CODE) + setBITS(BITS + 1); + + prefix_code[lzwnext] = lzwold; + append_character[lzwnext] = c; + lzwnext++; + lzwold = lzwnew; + + lzwnew = input_code(&in); + } + } + closeLZW(); +} + +} // End of namespace Agi diff --git a/engines/agi/lzw.h b/engines/agi/lzw.h new file mode 100644 index 0000000000..5923f98055 --- /dev/null +++ b/engines/agi/lzw.h @@ -0,0 +1,33 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2006 The ScummVM project + * + * Copyright (C) 1999-2001 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$ + * + */ + +#ifndef __AGI_LZW_H +#define __AGI_LZW_H + +namespace Agi { + +void LZW_expand(uint8 *, uint8 *, int32); + +} // End of namespace Agi +#endif diff --git a/engines/agi/menu.cpp b/engines/agi/menu.cpp new file mode 100644 index 0000000000..950b054cb3 --- /dev/null +++ b/engines/agi/menu.cpp @@ -0,0 +1,503 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2006 The ScummVM project + * + * Copyright (C) 1999-2002 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 "agi/agi.h" +#include "agi/sprite.h" +#include "agi/graphics.h" +#include "agi/keyboard.h" +#include "agi/menu.h" +#include "agi/text.h" +#include "agi/list.h" + +namespace Agi { + +struct agi_menu { + struct list_head list; /**< list head for menubar list */ + struct list_head 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 */ +}; + +struct agi_menu_option { + struct list_head list; /**< list head for menu options */ + 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 */ +}; + +static LIST_HEAD(menubar); + +static int h_cur_menu; +static int v_cur_menu; + +static struct agi_menu *get_menu(int i) { + struct list_head *h; + struct agi_menu *m; + + list_for_each(h, &menubar, next) { + m = list_entry(h, struct agi_menu, list); + if (m->index == i) + return m; + } + return NULL; +} + +static struct agi_menu_option *get_menu_option(int i, int j) { + struct list_head *h; + struct agi_menu *m; + struct agi_menu_option *d; + + m = get_menu(i); + + list_for_each(h, &m->down, next) { + d = list_entry(h, struct agi_menu_option, list); + if (d->index == j) + return d; + } + + return NULL; +} + +static void draw_menu_bar() { + struct list_head *h; + struct agi_menu *m; + + clear_lines(0, 0, MENU_BG); + flush_lines(0, 0); + + list_for_each(h, &menubar, next) { + m = list_entry(h, struct agi_menu, list); + print_text(m->text, 0, m->col, 0, 40, MENU_FG, MENU_BG); + } + +} + +static void draw_menu_hilite(int cur_menu) { + struct agi_menu *m; + + m = get_menu(cur_menu); + debugC(6, kDebugLevelMenu, "[%s]", m->text); + print_text(m->text, 0, m->col, 0, 40, MENU_BG, MENU_FG); + flush_lines(0, 0); +} + +/* draw box and pulldowns. */ +static void draw_menu_option(int h_menu) { + struct list_head *h; + struct agi_menu *m = NULL; + struct agi_menu_option *d = NULL; + + /* find which vertical menu it is */ + m = get_menu(h_menu); + + draw_box(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); + + list_for_each(h, &m->down, next) { + d = list_entry(h, struct agi_menu_option, list); + print_text(d->text, 0, m->wincol + 1, d->index + 2, m->width + 2, + d->enabled ? MENU_FG : MENU_DISABLED, MENU_BG); + } +} + +static void draw_menu_option_hilite(int h_menu, int v_menu) { + struct agi_menu *m; + struct agi_menu_option *d; + + m = get_menu(h_menu); + d = get_menu_option(h_menu, v_menu); + + print_text(d->text, 0, m->wincol + 1, v_menu + 2, m->width + 2, + MENU_BG, d->enabled ? MENU_FG : MENU_DISABLED); +} + +static void new_menu_selected(int i) { + show_pic(); + draw_menu_bar(); + draw_menu_hilite(i); + draw_menu_option(i); +} + +#ifdef USE_MOUSE +static int mouse_over_text(unsigned int line, unsigned int col, char *s) { + if (mouse.x < col * CHAR_COLS) + return false; + + if (mouse.x > (col + strlen(s)) * CHAR_COLS) + return false; + + if (mouse.y < line * CHAR_LINES) + return false; + + if (mouse.y >= (line + 1) * CHAR_LINES) + return false; + + return true; +} +#endif + +static int h_index; +static int v_index; +static int h_col; +static int h_max_menu; +static int v_max_menu[10]; + +#if 0 +static void add_about_option() { + struct agi_menu *m; + struct agi_menu_option *d; + char text[] = "About AGI engine"; + + d = malloc(sizeof(struct agi_menu_option)); + d->text = strdup(text); + d->enabled = true; + d->event = 255; + d->index = (v_max_menu[0] += 1); + + m = list_entry(menubar.next, struct agi_menu, list); + list_add_tail(&d->list, &m->down); + m->height++; + if (m->width < strlen(text)) + m->width = strlen(text); +} +#endif + +/* + * Public functions + */ + +void menu_init() { + h_index = 0; + h_col = 1; + h_cur_menu = 0; + v_cur_menu = 0; +} + +void menu_deinit() { + struct list_head *h, *h2, *v, *v2; + struct agi_menu *m = NULL; + struct agi_menu_option *d = NULL; + + for (h = (&menubar)->prev; h != (&menubar); h = h2) { + m = list_entry(h, struct agi_menu, list); + h2 = h->prev; + debugC(3, kDebugLevelMenu, "deiniting hmenu %s", m->text); + for (v = (&m->down)->prev; v != (&m->down); v = v2) { + d = list_entry(v, struct agi_menu_option, list); + v2 = v->prev; + debugC(3, kDebugLevelMenu, " deiniting vmenu %s", d->text); + list_del(v); + free(d->text); + free(d); + } + list_del(h); + free(m->text); + free(m); + } +} + +void menu_add(char *s) { + struct agi_menu *m; + + m = (agi_menu *) malloc(sizeof(struct agi_menu)); + m->text = strdup(s); + while (m->text[strlen(m->text) - 1] == ' ') + m->text[strlen(m->text) - 1] = 0; + m->down.next = &m->down; + m->down.prev = &m->down; + m->width = 0; + m->height = 0; + m->index = h_index++; + m->col = h_col; + m->wincol = h_col - 1; + v_index = 0; + v_max_menu[m->index] = 0; + h_col += strlen(m->text) + 1; + h_max_menu = m->index; + + debugC(3, kDebugLevelMenu, "add menu: '%s' %02x", s, m->text[strlen(m->text)]); + list_add_tail(&m->list, &menubar); +} + +void menu_add_item(char *s, int code) { + struct agi_menu *m; + struct agi_menu_option *d; + int l; + + d = (agi_menu_option *) malloc(sizeof(struct agi_menu_option)); + d->text = strdup(s); + d->enabled = true; + d->event = code; + d->index = v_index++; + + m = list_entry(menubar.prev, struct agi_menu, list); + m->height++; + + v_max_menu[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); + list_add_tail(&d->list, &m->down); +} + +void menu_submit() { + struct list_head *h, *h2; + struct agi_menu *m = NULL; + + debugC(3, kDebugLevelMenu, "Submitting menu"); + + /* add_about_option (); */ + + /* If a menu has no options, delete it */ + for (h = (&menubar)->prev; h != (&menubar); h = h2) { + m = list_entry(h, struct agi_menu, list); + h2 = h->prev; + if ((&m->down)->prev == (&m->down)) { + list_del(h); + free(m->text); + free(m); + h_max_menu--; + } + } +} + +int menu_keyhandler(int key) { + static int clock_val; + static int menu_active = false; + struct agi_menu_option *d; + struct list_head *h; + struct agi_menu *m; + static int button_used = 0; + + if (!getflag(F_menus_work)) + return false; + + if (!menu_active) { + clock_val = game.clock_enabled; + game.clock_enabled = false; + draw_menu_bar(); + } +#ifdef USE_MOUSE + /* + * Mouse handling + */ + if (mouse.button) { + int hmenu, vmenu; + + button_used = 1; /* Button has been used at least once */ + if (mouse.y <= CHAR_LINES) { + /* on the menubar */ + hmenu = 0; + + list_for_each(h, &menubar, next) { + m = list_entry(h, struct agi_menu, list); + if (mouse_over_text(0, m->col, m->text)) { + break; + } else { + hmenu++; + } + } + + if (hmenu <= h_max_menu) { + if (h_cur_menu != hmenu) { + v_cur_menu = -1; + new_menu_selected(hmenu); + } + h_cur_menu = hmenu; + } + } else { + /* not in menubar */ + struct agi_menu_option *do1; + + vmenu = 0; + + m = get_menu(h_cur_menu); + list_for_each(h, &m->down, next) { + do1 = list_entry(h, struct agi_menu_option, list); + if (mouse_over_text(2 + do1->index, m->wincol + 1, do1->text)) { + break; + } else { + vmenu++; + } + } + + if (vmenu <= v_max_menu[h_cur_menu]) { + if (v_cur_menu != vmenu) { + draw_menu_option(h_cur_menu); + draw_menu_option_hilite(h_cur_menu, vmenu); + } + v_cur_menu = vmenu; + } + } + } else if (button_used) { + /* Button released */ + button_used = 0; + + debugC(6, kDebugLevelMenu | kDebugLevelInput, "button released!"); + + if (v_cur_menu < 0) + v_cur_menu = 0; + + draw_menu_option_hilite(h_cur_menu, v_cur_menu); + + if (mouse.y <= CHAR_LINES) { + /* on the menubar */ + } else { + /* see which option we selected */ + m = get_menu(h_cur_menu); + list_for_each(h, &m->down, next) { + d = list_entry(h, struct agi_menu_option, list); + if (mouse_over_text(2 + d->index, + m->wincol + 1, d->text)) { + /* activate that option */ + if (d->enabled) { + debugC(6, kDebugLevelMenu | kDebugLevelInput, "event %d registered", d->event); + game.ev_keyp[d->event].occured = true; + game.ev_keyp[d->event].data = d->event; + goto exit_menu; + } + } + } + goto exit_menu; + } + } +#endif /* USE_MOUSE */ + + if (!menu_active) { + if (h_cur_menu >= 0) { + draw_menu_hilite(h_cur_menu); + draw_menu_option(h_cur_menu); + if (!button_used && v_cur_menu >= 0) + draw_menu_option_hilite(h_cur_menu, v_cur_menu); + } + menu_active = true; + } + + switch (key) { + case KEY_ESCAPE: + debugC(6, kDebugLevelMenu | kDebugLevelInput, "KEY_ESCAPE"); + goto exit_menu; + case KEY_ENTER: + debugC(6, kDebugLevelMenu | kDebugLevelInput, "KEY_ENTER"); + d = get_menu_option(h_cur_menu, v_cur_menu); + if (d->enabled) { + debugC(6, kDebugLevelMenu | kDebugLevelInput, "event %d registered", d->event); + game.ev_keyp[d->event].occured = true; + goto exit_menu; + } + break; + case KEY_DOWN: + case KEY_UP: + v_cur_menu += key == KEY_DOWN ? 1 : -1; + + if (v_cur_menu < 0) + v_cur_menu = 0; + if (v_cur_menu > v_max_menu[h_cur_menu]) + v_cur_menu = v_max_menu[h_cur_menu]; + + draw_menu_option(h_cur_menu); + draw_menu_option_hilite(h_cur_menu, v_cur_menu); + break; + case KEY_RIGHT: + case KEY_LEFT: + h_cur_menu += key == KEY_RIGHT ? 1 : -1; + + if (h_cur_menu < 0) + h_cur_menu = h_max_menu; + if (h_cur_menu > h_max_menu) + h_cur_menu = 0; + + v_cur_menu = 0; + new_menu_selected(h_cur_menu); + draw_menu_option_hilite(h_cur_menu, v_cur_menu); + break; + } + + return true; + +exit_menu: + button_used = 0; + show_pic(); + write_status(); + + setvar(V_key, 0); + game.keypress = 0; + game.clock_enabled = clock_val; + old_input_mode(); + debugC(3, kDebugLevelMenu, "exit_menu: input mode reset to %d", game.input_mode); + menu_active = false; + + return true; +} + +void menu_set_item(int event, int state) { + struct list_head *h, *v; + struct agi_menu *m = NULL; + struct agi_menu_option *d = NULL; + + /* scan all menus for event number # */ + + debugC(6, kDebugLevelMenu, "event = %d, state = %d", event, state); + list_for_each(h, &menubar, next) { + m = list_entry(h, struct agi_menu, list); + list_for_each(v, &m->down, next) { + d = list_entry(v, struct agi_menu_option, list); + if (d->event == event) { + d->enabled = state; + return; + } + } + } +} + +void menu_enable_all() { + struct list_head *h, *v; + struct agi_menu *m = NULL; + struct agi_menu_option *d = NULL; + + list_for_each(h, &menubar, next) { + m = list_entry(h, struct agi_menu, list); + list_for_each(v, &m->down, next) { + d = list_entry(v, struct agi_menu_option, list); + d->enabled = true; + } + } + +} + +} // End of namespace Agi diff --git a/engines/agi/menu.h b/engines/agi/menu.h new file mode 100644 index 0000000000..095f802bd4 --- /dev/null +++ b/engines/agi/menu.h @@ -0,0 +1,46 @@ +/* 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$ + * + */ + +#ifndef __AGI_MENU_H +#define __AGI_MENU_H + +namespace Agi { + +#define MENU_BG 0x0f /* White */ +#define MENU_DISABLED 0x07 /* Grey */ + +#define MENU_FG 0x00 /* Black */ +#define MENU_LINE 0x00 /* Black */ + +void menu_init(void); +void menu_deinit(void); +void menu_add(char *); +void menu_add_item(char *, int); +void menu_submit(void); +void menu_set_item(int, int); +int menu_keyhandler(int); +void menu_enable_all(void); + +} // End of namespace Agi +#endif diff --git a/engines/agi/module.mk b/engines/agi/module.mk new file mode 100644 index 0000000000..a5a81f2b15 --- /dev/null +++ b/engines/agi/module.mk @@ -0,0 +1,42 @@ +MODULE := engines/agi + +MODULE_OBJS = \ + agi.o \ + agi_v2.o \ + agi_v3.o \ + checks.o \ + console.o \ + cycle.o \ + font.o \ + global.o \ + graphics.o \ + id.o \ + inv.o \ + keyboard.o \ + logic.o \ + lzw.o \ + menu.o \ + motion.o \ + objects.o \ + op_cmd.o \ + op_dbg.o \ + op_test.o \ + patches.o \ + picture.o \ + savegame.o \ + sound.o \ + sprite.o \ + text.o \ + view.o \ + words.o + +MODULE_DIRS += \ + engines/agi + +# This module can be built as a plugin +ifdef BUILD_PLUGINS +PLUGIN := 1 +endif + +# Include common rules +include $(srcdir)/common.rules diff --git a/engines/agi/motion.cpp b/engines/agi/motion.cpp new file mode 100644 index 0000000000..61d14dc1a3 --- /dev/null +++ b/engines/agi/motion.cpp @@ -0,0 +1,232 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2006 The ScummVM project + * + * Copyright (C) 1999-2001 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 "agi/agi.h" + +namespace Agi { + +static int check_step(int delta, int step) { + return (-step >= delta) ? 0 : (step <= delta) ? 2 : 1; +} + +static int check_block(int x, int y) { + if (x <= game.block.x1 || x >= game.block.x2) + return false; + + if (y <= game.block.y1 || y >= game.block.y2) + return false; + + return true; +} + +static void changepos(struct vt_entry *v) { + int b, 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->x_pos; + y = v->y_pos; + b = check_block(x, y); + + x += v->step_size * dx[v->direction]; + y += v->step_size * dy[v->direction]; + + if (check_block(x, y) == b) { + v->flags &= ~MOTION; + } else { + v->flags |= MOTION; + v->direction = 0; + if /*_is_ego_view*/ (v) + game.vars[V_ego_dir] = 0; + } +} + +static void motion_wander(struct vt_entry *v) { + if (v->parm1--) { + if (~v->flags & DIDNT_MOVE) + return; + } + + v->direction = rnd->getRandomNumber(8); + + if /*_is_ego_view */ (v) { + game.vars[V_ego_dir] = v->direction; + while (v->parm1 < 6) { + v->parm1 = rnd->getRandomNumber(50); /* huh? */ + } + } +} + +static void motion_followego(struct vt_entry *v) { + int ego_x, ego_y; + int obj_x, obj_y; + int dir; + + ego_x = game.view_table[0].x_pos + game.view_table[0].x_size / 2; + ego_y = game.view_table[0].y_pos; + + obj_x = v->x_pos + v->x_size / 2; + obj_y = v->y_pos; + + /* Get direction to reach ego */ + dir = get_direction(obj_x, obj_y, ego_x, ego_y, v->parm1); + + /* Already at ego coordinates */ + if (dir == 0) { + v->direction = 0; + v->motion = MOTION_NORMAL; + setflag(v->parm2, true); + return; + } + + if (v->parm3 == 0xff) { + v->parm3 = 0; + } else if (v->flags & DIDNT_MOVE) { + int d; + + while ((v->direction = rnd->getRandomNumber(8)) == 0) { + } + + d = (abs(ego_y - obj_y) + abs(ego_x - obj_x)) / 2; + + if (d < v->step_size) { + v->parm3 = v->step_size; + return; + } + + while ((v->parm3 = rnd->getRandomNumber(d)) < v->step_size) { + } + return; + } + + if (v->parm3 != 0) { + int k; + + /* DF: this is ugly and I dont know why this works, but + * other line does not! (watcom complained about lvalue) + * + * if (((int8)v->parm3 -= v->step_size) < 0) + * v->parm3 = 0; + */ + k = v->parm3; + k -= v->step_size; + v->parm3 = k; + + if ((int8) v->parm3 < 0) + v->parm3 = 0; + } else { + v->direction = dir; + } +} + +static void motion_moveobj(struct vt_entry *v) { + v->direction = get_direction(v->x_pos, v->y_pos, v->parm1, v->parm2, v->step_size); + + /* Update V6 if ego */ + if (v == game.view_table) + game.vars[V_ego_dir] = v->direction; + + if (v->direction == 0) + in_destination(v); +} + +static void check_motion(struct vt_entry *v) { + switch (v->motion) { + case MOTION_WANDER: + motion_wander(v); + break; + case MOTION_FOLLOW_EGO: + motion_followego(v); + break; + case MOTION_MOVE_OBJ: + motion_moveobj(v); + break; + } + + if ((game.block.active && (~v->flags & IGNORE_BLOCKS)) && v->direction) + changepos(v); +} + +/* + * Public functions + */ + +/** + * + */ +void check_all_motions() { + struct vt_entry *v; + + for (v = game.view_table; v < &game.view_table[MAX_VIEWTABLE]; v++) { + if ((v->flags & (ANIMATED | UPDATE | DRAWN)) == (ANIMATED | UPDATE | DRAWN) + && v->step_time_count == 1) { + check_motion(v); + } + } +} + +/** + * Check if given entry is at destination point. + * This function is used to updated the flags of an object with move.obj + * type motion that * has reached its final destination coordinates. + * @param v Pointer to view table entry + */ +void in_destination(struct vt_entry *v) { + if (v->motion == MOTION_MOVE_OBJ) { + v->step_size = v->parm3; + setflag(v->parm4, true); + } + v->motion = MOTION_NORMAL; + if (v == game.view_table) + game.player_control = true; +} + +/** + * Wrapper for static function motion_moveobj(). + * This function is used by cmd_move_object() in the first motion cycle + * after setting the motion mode to MOTION_MOVE_OBJ. + * @param v Pointer to view table entry + */ +void move_obj(struct vt_entry *v) { + motion_moveobj(v); +} + +/** + * Get direction from motion coordinates + * This function gets the motion direction from the current and previous + * object coordinates and the step size. + * @param x0 Original x coordinate of the object + * @param y0 Original y coordinate of the object + * @param x x coordinate of the object + * @param y y coordinate of the object + * @param s step size + */ +int get_direction(int x0, int y0, int x, int y, int s) { + int dir_table[9] = { 8, 1, 2, 7, 0, 3, 6, 5, 4 }; + return dir_table[check_step(x - x0, s) + 3 * check_step(y - y0, s)]; +} + +} // End of namespace Agi diff --git a/engines/agi/objects.cpp b/engines/agi/objects.cpp new file mode 100644 index 0000000000..352dabcf95 --- /dev/null +++ b/engines/agi/objects.cpp @@ -0,0 +1,167 @@ +/* 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 "agi/agi.h" + +namespace Agi { + +extern int decode_objects(uint8 *mem, uint32 flen); + +static struct agi_object *objects; /* objects in the game */ + +int alloc_objects(int n) { + if ((objects = (agi_object *) calloc(n, sizeof(struct agi_object))) == NULL) + return err_NotEnoughMemory; + + return err_OK; +} + +int decode_objects(uint8 *mem, uint32 flen) { + unsigned int i, so, padsize; + + padsize = game.game_flags & ID_AMIGA ? 4 : 3; + + game.num_objects = 0; + objects = NULL; + + /* check if first pointer exceeds file size + * if so, its encrypted, else it is not + */ + + if (READ_LE_UINT16(mem) > flen) { + report("Decrypting objects... "); + decrypt(mem, flen); + report("done.\n"); + } + + /* alloc memory for object list + * byte 3 = number of animated objects. this is ignored.. ?? + */ + if (READ_LE_UINT16(mem) / padsize >= 256) { +#ifdef AGDS_SUPPORT + /* die with no error! AGDS game needs not to die to work!! :( */ + return err_OK; +#else + /* no AGDS support, die with error */ + return err_BadResource; +#endif + } + + game.num_objects = READ_LE_UINT16(mem) / padsize; + debugC(5, kDebugLevelResources, "num_objects = %d (padsize = %d)", game.num_objects, padsize); + + if (alloc_objects(game.num_objects) != err_OK) + return err_NotEnoughMemory; + + /* build the object list */ + for (i = 0, so = padsize; i < game.num_objects; i++, so += padsize) { + int offset; + + (objects + i)->location = *(mem + so + 2); + offset = READ_LE_UINT16(mem + so) + padsize; + + if ((uint) offset < flen) { + (objects + i)->name = (char *)strdup((const char *)mem + offset); + } else { + printf("ERROR: object %i name beyond object filesize! " + "(%04x > %04x)\n", i, offset, flen); + (objects + i)->name = strdup(""); + } + } + report("Reading objects: %d objects read.\n", game.num_objects); + + return err_OK; + +} + +int load_objects(char *fname) { + Common::File fp; + uint32 flen; + uint8 *mem; + char *path; + + objects = NULL; + game.num_objects = 0; + + debugC(5, kDebugLevelResources, "(fname = %s)", fname); + path = fname; + report("Loading objects: %s\n", path); + + if (!fp.open(path)) + return err_BadFileOpen; + + fp.seek(0, SEEK_END); + flen = fp.pos(); + fp.seek(0, SEEK_SET); + + if ((mem = (uint8 *) calloc(1, flen + 32)) == NULL) { + fp.close(); + return err_NotEnoughMemory; + } + + fp.read(mem, flen); + fp.close(); + + decode_objects(mem, flen); + free(mem); + return err_OK; +} + +void unload_objects() { + unsigned int i; + + if (objects != NULL) { + for (i = 0; i < game.num_objects; i++) + free(objects[i].name); + free(objects); + } +} + +void object_set_location(unsigned int n, int i) { + if (n >= game.num_objects) { + report("Error: Can't access object %d.\n", n); + return; + } + objects[n].location = i; +} + +int object_get_location(unsigned int n) { + if (n >= game.num_objects) { + report("Error: Can't access object %d.\n", n); + return 0; + } + return objects[n].location; +} + +char *object_name(unsigned int n) { + if (n >= game.num_objects) { + report("Error: Can't access object %d.\n", n); + return ""; + } + return objects[n].name; +} + +} // End of namespace Agi diff --git a/engines/agi/op_cmd.cpp b/engines/agi/op_cmd.cpp new file mode 100644 index 0000000000..042556b09e --- /dev/null +++ b/engines/agi/op_cmd.cpp @@ -0,0 +1,1513 @@ +/* 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 "agi/agi.h" +#include "agi/sprite.h" +#include "agi/graphics.h" +#include "agi/keyboard.h" +#include "agi/opcodes.h" +#include "agi/menu.h" +#include "agi/savegame.h" +#include "agi/text.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 ip cur_logic->cIP +#define vt game.view_table[p0] + +static struct agi_logic *cur_logic; + +int timer_hack; /* Workaround for timer loop in MH1 */ + +#define _v game.vars +#define cmd(x) static void cmd_##x (uint8 *p) + +cmd(increment) { + if (_v[p0] != 0xff) + ++_v[p0]; +} + +cmd(decrement) { + if (_v[p0] != 0) + --_v[p0]; +} + +cmd(assignn) { + _v[p0] = p1; +} + +cmd(addn) { + _v[p0] += p1; +} + +cmd(subn) { + _v[p0] -= p1; +} + +cmd(assignv) { + _v[p0] = _v[p1]; +} + +cmd(addv) { + _v[p0] += _v[p1]; +} + +cmd(subv) { + _v[p0] -= _v[p1]; +} + +cmd(mul_n) { + _v[p0] *= p1; +} + +cmd(mul_v) { + _v[p0] *= _v[p1]; +} + +cmd(div_n) { + _v[p0] /= p1; +} + +cmd(div_v) { + _v[p0] /= _v[p1]; +} + +cmd(random) { + _v[p2] = rnd->getRandomNumber(p1 - p0) + p0; +} + +cmd(lindirectn) { + _v[_v[p0]] = p1; +} + +cmd(lindirectv) { + _v[_v[p0]] = _v[p1]; +} + +cmd(rindirect) { + _v[p0] = _v[_v[p1]]; +} + +cmd(set) { + setflag(*p, true); +} + +cmd(reset) { + setflag(*p, false); +} + +cmd(toggle) { + setflag(*p, !getflag(*p)); +} + +cmd(set_v) { + setflag(_v[p0], true); +} + +cmd(reset_v) { + setflag(_v[p0], false); +} + +cmd(toggle_v) { + setflag(_v[p0], !getflag(_v[p0])); +} + +cmd(new_room) { + new_room(p0); +} + +cmd(new_room_f) { + new_room(_v[p0]); +} + +cmd(load_view) { + agi_load_resource(rVIEW, p0); +} + +cmd(load_logic) { + agi_load_resource(rLOGIC, p0); +} + +cmd(load_sound) { + agi_load_resource(rSOUND, p0); +} + +cmd(load_view_f) { + agi_load_resource(rVIEW, _v[p0]); +} + +cmd(load_logic_f) { + agi_load_resource(rLOGIC, _v[p0]); +} + +cmd(discard_view) { + agi_unload_resource(rVIEW, p0); +} + +cmd(object_on_anything) { + vt.flags &= ~(ON_WATER | ON_LAND); +} + +cmd(object_on_land) { + debugC(4, kDebugLevelScripts, "p0 = %d", p0); + vt.flags |= ON_LAND; +} + +cmd(object_on_water) { + debugC(4, kDebugLevelScripts, "p0 = %d", p0); + vt.flags |= ON_WATER; +} + +cmd(observe_horizon) { + debugC(4, kDebugLevelScripts, "p0 = %d", p0); + vt.flags &= ~IGNORE_HORIZON; +} + +cmd(ignore_horizon) { + debugC(4, kDebugLevelScripts, "p0 = %d", p0); + vt.flags |= IGNORE_HORIZON; +} + +cmd(observe_objs) { + debugC(4, kDebugLevelScripts, "p0 = %d", p0); + vt.flags &= ~IGNORE_OBJECTS; +} + +cmd(ignore_objs) { + debugC(4, kDebugLevelScripts, "p0 = %d", p0); + vt.flags |= IGNORE_OBJECTS; +} + +cmd(observe_blocks) { + debugC(4, kDebugLevelScripts, "p0 = %d", p0); + vt.flags &= ~IGNORE_BLOCKS; +} + +cmd(ignore_blocks) { + debugC(4, kDebugLevelScripts, "p0 = %d", p0); + vt.flags |= IGNORE_BLOCKS; +} + +cmd(set_horizon) { + debugC(4, kDebugLevelScripts, "p0 = %d", p0); + game.horizon = p0; +} + +cmd(get_priority) { + _v[p1] = vt.priority; +} + +cmd(set_priority) { + vt.flags |= FIXED_PRIORITY; + vt.priority = p1; +} + +cmd(set_priority_f) { + vt.flags |= FIXED_PRIORITY; + vt.priority = _v[p1]; +} + +cmd(release_priority) { + vt.flags &= ~FIXED_PRIORITY; +} + +cmd(set_upper_left) { /* do nothing (AGI 2.917) */ +} + +cmd(start_update) { + start_update(&vt); +} + +cmd(stop_update) { + stop_update(&vt); +} + +cmd(current_view) { + _v[p1] = vt.current_view; +} + +cmd(current_cel) { + _v[p1] = vt.current_cel; + debugC(4, kDebugLevelScripts, "v%d=%d", p1, _v[p1]); +} + +cmd(current_loop) { + _v[p1] = vt.current_loop; +} + +cmd(last_cel) { + _v[p1] = vt.loop_data->num_cels - 1; +} + +cmd(set_cel) { + set_cel(&vt, p1); + vt.flags &= ~DONTUPDATE; +} + +cmd(set_cel_f) { + set_cel(&vt, _v[p1]); + vt.flags &= ~DONTUPDATE; +} + +cmd(set_view) { + debugC(4, kDebugLevelScripts, "o%d, %d", p0, p1); + set_view(&vt, p1); +} + +cmd(set_view_f) { + set_view(&vt, _v[p1]); +} + +cmd(set_loop) { + set_loop(&vt, p1); +} + +cmd(set_loop_f) { + set_loop(&vt, _v[p1]); +} + +cmd(number_of_loops) { + _v[p1] = vt.num_loops; +} + +cmd(fix_loop) { + vt.flags |= FIX_LOOP; +} + +cmd(release_loop) { + vt.flags &= ~FIX_LOOP; +} + +cmd(step_size) { + vt.step_size = _v[p1]; +} + +cmd(step_time) { + vt.step_time = vt.step_time_count = _v[p1]; +} + +cmd(cycle_time) { + vt.cycle_time = vt.cycle_time_count = _v[p1]; +} + +cmd(stop_cycling) { + vt.flags &= ~CYCLING; +} + +cmd(start_cycling) { + vt.flags |= CYCLING; +} + +cmd(normal_cycle) { + vt.cycle = CYCLE_NORMAL; + vt.flags |= CYCLING; +} + +cmd(reverse_cycle) { + vt.cycle = CYCLE_REVERSE; + vt.flags |= CYCLING; +} + +cmd(set_dir) { + vt.direction = _v[p1]; +} + +cmd(get_dir) { + _v[p1] = vt.direction; +} + +cmd(get_room_f) { + _v[p1] = object_get_location(_v[p0]); +} + +cmd(put) { + object_set_location(p0, _v[p1]); +} + +cmd(put_f) { + object_set_location(_v[p0], _v[p1]); +} + +cmd(drop) { + object_set_location(p0, 0); +} + +cmd(get) { + object_set_location(p0, EGO_OWNED); +} + +cmd(get_f) { + object_set_location(_v[p0], EGO_OWNED); +} + +cmd(word_to_string) { + strcpy(game.strings[p0], game.ego_words[p1].word); +} + +cmd(open_dialogue) { + debugC(4, kDebugLevelScripts, "p0 = %d", p0); + game.has_window = true; +} + +cmd(close_dialogue) { + debugC(4, kDebugLevelScripts, "p0 = %d", p0); + game.has_window = false; +} + +cmd(close_window) { + close_window(); +} + +cmd(status_line_on) { + game.status_line = true; + write_status(); +} + +cmd(status_line_off) { + game.status_line = false; + write_status(); +} + +cmd(show_obj) { + show_obj(p0); +} + +cmd(show_obj_v) { + show_obj(_v[p0]); +} + +cmd(sound) { + start_sound(p0, p1); +} + +cmd(stop_sound) { + stop_sound(); +} + +cmd(menu_input) { + new_input_mode(INPUT_MENU); +} + +cmd(enable_item) { + menu_set_item(p0, true); +} + +cmd(disable_item) { + menu_set_item(p0, false); +} + +cmd(submit_menu) { + menu_submit(); +} + +cmd(set_scan_start) { + cur_logic->sIP = cur_logic->cIP; +} + +cmd(reset_scan_start) { + cur_logic->sIP = 2; +} + +cmd(save_game) { + game.simple_save ? savegame_simple() : savegame_dialog(); +} + +cmd(load_game) { + game.simple_save ? loadgame_simple() : loadgame_dialog(); +} + +cmd(init_disk) { /* do nothing */ +} + +cmd(log) { /* do nothing */ +} + +cmd(trace_on) { /* do nothing */ +} + +cmd(trace_info) { /* do nothing */ +} + +cmd(show_mem) { + message_box("Enough memory"); +} + +cmd(init_joy) { /* do nothing */ ; +} + +cmd(script_size) { + report("script.size(%d)\n", p0); +} + +cmd(cancel_line) { + report("cancel.line\n"); +} + +cmd(obj_status_f) { + report("obj.status.f\n"); +} + +/* unknown commands: + * unk_170: Force savegame name -- j5 + * unk_171: script save -- j5 + * unk_172: script restore -- j5 + * unk_173: Activate keypressed control (ego only moves while key is pressed) + * unk_174: Change priority table (used in KQ4) -- j5 + * unk_177: Disable menus completely -- j5 + * unk_181: Deactivate keypressed control (default control of ego) + */ +cmd(set_simple) { + game.simple_save = true; +} + +cmd(pop_script) { + report("pop.script\n"); +} + +cmd(hold_key) { + report("hold.key\n"); +} + +cmd(discard_sound) { + report("discard.sound\n"); +} + +cmd(hide_mouse) { + report("hide.mouse\n"); +} + +cmd(allow_menu) { + report("allow.menu\n"); +} + +cmd(show_mouse) { + report("show.mouse\n"); +} + +cmd(fence_mouse) { + report("fence.mouse\n"); +} + +cmd(release_key) { + report("release.key\n"); +} + +cmd(adj_ego_move_to_x_y) { + game.view_table[0].flags |= ADJ_EGO_XY; +} + +cmd(parse) { + _v[V_word_not_found] = 0; + setflag(F_entered_cli, false); + setflag(F_said_accepted_input, false); + + dictionary_words(agi_sprintf(game.strings[p0])); +} + +cmd(call) { + int old_cIP; + int old_lognum; + + /* CM: we don't save sIP because set.scan.start can be + * used in a called script (fixes xmas demo) + */ + old_cIP = cur_logic->cIP; + old_lognum = game.lognum; + + run_logic(p0); + + game.lognum = old_lognum; + cur_logic = &game.logics[game.lognum]; + cur_logic->cIP = old_cIP; +} + +cmd(call_f) { + cmd_call(&_v[p0]); +} + +cmd(draw_pic) { + debugC(6, kDebugLevelScripts, "=== draw pic %d ===", _v[p0]); + erase_both(); + decode_picture(_v[p0], true); + blit_both(); + game.picture_shown = 0; + debugC(6, kDebugLevelScripts, "--- end of draw pic %d ---", _v[p0]); +} + +cmd(show_pic) { + debugC(6, kDebugLevelScripts, "=== show pic ==="); + setflag(F_output_mode, false); + cmd_close_window(NULL); + show_pic(); + game.picture_shown = 1; + debugC(6, kDebugLevelScripts, "--- end of show pic ---"); +} + +cmd(load_pic) { + erase_both(); + agi_load_resource(rPICTURE, _v[p0]); + blit_both(); +} + +cmd(discard_pic) { + debugC(6, kDebugLevelScripts, "--- discard pic ---"); + /* do nothing */ +} + +cmd(overlay_pic) { + debugC(6, kDebugLevelScripts, "--- overlay pic ---"); + erase_both(); + decode_picture(_v[p0], false); + blit_both(); + game.picture_shown = 0; + commit_both(); +} + +cmd(show_pri_screen) { +#ifdef USE_CONSOLE + debug_.priority = 1; + erase_both(); + show_pic(); + blit_both(); + wait_key(); + debug_.priority = 0; + erase_both(); + show_pic(); + blit_both(); +#endif +} + +cmd(animate_obj) { + if (vt.flags & ANIMATED) + return; + + debugC(4, kDebugLevelScripts, "animate vt entry #%d", p0); + vt.flags = ANIMATED | UPDATE | CYCLING; + vt.motion = MOTION_NORMAL; + vt.cycle = CYCLE_NORMAL; + vt.direction = 0; +} + +cmd(unanimate_all) { + int i; + for (i = 0; i < MAX_VIEWTABLE; i++) + game.view_table[i].flags &= ~(ANIMATED | DRAWN); +} + +cmd(draw) { + if (vt.flags & DRAWN) + return; + + if (vt.y_size <= 0 || vt.x_size <= 0) + return; + + debugC(4, kDebugLevelScripts, "draw entry %d", vt.entry); + + vt.flags |= UPDATE; + if (agi_get_release() >= 0x3000) { + set_loop(&vt, vt.current_loop); + set_cel(&vt, vt.current_cel); + } + fix_position(p0); + vt.x_pos2 = vt.x_pos; + vt.y_pos2 = vt.y_pos; + vt.cel_data_2 = vt.cel_data; + erase_upd_sprites(); + vt.flags |= DRAWN; + + if (agi_get_release() <= 0x2440) /* See bug #546562 */ + vt.flags |= ANIMATED; + + blit_upd_sprites(); + vt.flags &= ~DONTUPDATE; + + commit_block(vt.x_pos, vt.y_pos - vt.y_size + 1, vt.x_pos + vt.x_size - 1, vt.y_pos); + + debugC(4, kDebugLevelScripts, "vt entry #%d flags = %02x", p0, vt.flags); +} + +cmd(erase) { + if (~vt.flags & DRAWN) + return; + + erase_upd_sprites(); + if (vt.flags & UPDATE) { + vt.flags &= ~DRAWN; + } else { + erase_nonupd_sprites(); + vt.flags &= ~DRAWN; + blit_nonupd_sprites(); + } + blit_upd_sprites(); + + commit_block(vt.x_pos, vt.y_pos - vt.y_size + 1, vt.x_pos + vt.x_size - 1, vt.y_pos); +} + +cmd(position) { + vt.x_pos = vt.x_pos2 = p1; + vt.y_pos = vt.y_pos2 = p2; +} + +cmd(position_f) { + vt.x_pos = vt.x_pos2 = _v[p1]; + vt.y_pos = vt.y_pos2 = _v[p2]; +} + +cmd(get_posn) { + game.vars[p1] = (unsigned char)vt.x_pos; + game.vars[p2] = (unsigned char)vt.y_pos; +} + +cmd(reposition) { + int dx = (int8) _v[p1], dy = (int8) _v[p2]; + + debugC(4, kDebugLevelScripts, "dx=%d, dy=%d", dx, dy); + vt.flags |= UPDATE_POS; + + if (dx < 0 && vt.x_pos < -dx) + vt.x_pos = 0; + else + vt.x_pos += dx; + + if (dy < 0 && vt.y_pos < -dy) + vt.y_pos = 0; + else + vt.y_pos += dy; + + fix_position(p0); +} + +cmd(reposition_to) { + vt.x_pos = p1; + vt.y_pos = p2; + vt.flags |= UPDATE_POS; + fix_position(p0); +} + +cmd(reposition_to_f) { + vt.x_pos = _v[p1]; + vt.y_pos = _v[p2]; + vt.flags |= UPDATE_POS; + fix_position(p0); +} + +cmd(add_to_pic) { + add_to_pic(p0, p1, p2, p3, p4, p5, p6); +} + +cmd(add_to_pic_f) { + add_to_pic(_v[p0], _v[p1], _v[p2], _v[p3], _v[p4], _v[p5], _v[p6]); +} + +cmd(force_update) { + erase_both(); + blit_both(); + commit_both(); +} + +cmd(reverse_loop) { + debugC(4, kDebugLevelScripts, "o%d, f%d", p0, p1); + vt.cycle = CYCLE_REV_LOOP; + vt.flags |= (DONTUPDATE | UPDATE | CYCLING); + vt.parm1 = p1; + setflag(p1, false); +} + +cmd(end_of_loop) { + debugC(4, kDebugLevelScripts, "o%d, f%d", p0, p1); + vt.cycle = CYCLE_END_OF_LOOP; + vt.flags |= (DONTUPDATE | UPDATE | CYCLING); + vt.parm1 = p1; + setflag(p1, false); +} + +cmd(block) { + debugC(4, kDebugLevelScripts, "x1=%d, y1=%d, x2=%d, y2=%d", p0, p1, p2, p3); + game.block.active = true; + game.block.x1 = p0; + game.block.y1 = p1; + game.block.x2 = p2; + game.block.y2 = p3; +} + +cmd(unblock) { + game.block.active = false; +} + +cmd(normal_motion) { + vt.motion = MOTION_NORMAL; +} + +cmd(stop_motion) { + vt.direction = 0; + vt.motion = MOTION_NORMAL; + if (p0 == 0) { /* ego only */ + _v[V_ego_dir] = 0; + game.player_control = false; + } +} + +cmd(start_motion) { + vt.motion = MOTION_NORMAL; + if (p0 == 0) { /* ego only */ + _v[V_ego_dir] = 0; + game.player_control = true; + } +} + +cmd(player_control) { + game.player_control = true; + game.view_table[0].motion = MOTION_NORMAL; +} + +cmd(program_control) { + game.player_control = false; +} + +cmd(follow_ego) { + vt.motion = MOTION_FOLLOW_EGO; + vt.parm1 = p1 > vt.step_size ? p1 : vt.step_size; + vt.parm2 = p2; + vt.parm3 = 0xff; + setflag(p2, false); + vt.flags |= UPDATE; +} + +cmd(move_obj) { + /* _D (_D_WARN "o=%d, x=%d, y=%d, s=%d, f=%d", p0, p1, p2, p3, p4); */ + + vt.motion = MOTION_MOVE_OBJ; + vt.parm1 = p1; + vt.parm2 = p2; + vt.parm3 = vt.step_size; + vt.parm4 = p4; + + if (p3 != 0) + vt.step_size = p3; + + setflag(p4, false); + vt.flags |= UPDATE; + + if (p0 == 0) + game.player_control = false; + + /* AGI 2.272 (ddp, xmas) doesn't call move_obj! */ + if (agi_get_release() > 0x2272) + move_obj(&vt); +} + +cmd(move_obj_f) { + vt.motion = MOTION_MOVE_OBJ; + vt.parm1 = _v[p1]; + vt.parm2 = _v[p2]; + vt.parm3 = vt.step_size; + vt.parm4 = p4; + + if (_v[p3] != 0) + vt.step_size = _v[p3]; + + setflag(p4, false); + vt.flags |= UPDATE; + + if (p0 == 0) + game.player_control = false; + + /* AGI 2.272 (ddp, xmas) doesn't call move_obj! */ + if (agi_get_release() > 0x2272) + move_obj(&vt); +} + +cmd(wander) { + if (p0 == 0) + game.player_control = false; + vt.motion = MOTION_WANDER; + vt.flags |= UPDATE; +} + +cmd(set_game_id) { + if (cur_logic->texts && (p0 - 1) <= cur_logic->num_texts) + strncpy(game.id, cur_logic->texts[p0 - 1], 8); + else + game.id[0] = 0; + + report("Game ID: \"%s\"\n", game.id); +} + +cmd(pause) { + int tmp = game.clock_enabled; + char *b[] = { "Continue", NULL }; + + game.clock_enabled = false; + selection_box(" Game is paused. \n\n\n", b); + game.clock_enabled = tmp; +} + +cmd(set_menu) { + debugC(4, kDebugLevelScripts, "text %02x of %02x", p0, cur_logic->num_texts); + if (cur_logic->texts != NULL && p0 < cur_logic->num_texts) + menu_add(cur_logic->texts[p0 - 1]); +} + +cmd(set_menu_item) { + debugC(4, kDebugLevelScripts, "text %02x of %02x", p0, cur_logic->num_texts); + if (cur_logic->texts != NULL && p0 <= cur_logic->num_texts) + menu_add_item(cur_logic->texts[p0 - 1], p1); +} + +cmd(version) { + char ver_msg[] = TITLE " v" VERSION; + char ver2_msg[] = + "\n" + " \n\n" + "Emulating Sierra AGI v%x.%03x\n"; + char ver3_msg[] = + "\n" + " \n\n" + " Emulating AGI v%x.002.%03x\n"; + /* no Sierra as it wraps textbox */ + char *r, *q; + int ver, maj, min; + char msg[256]; + + ver = agi_get_release(); + maj = (ver >> 12) & 0xf; + min = ver & 0xfff; + + q = maj == 2 ? ver2_msg : ver3_msg; + r = strchr(q + 1, '\n'); + + strncpy(q + 1 + ((r - q > 0 ? r - q : 1) / 4), ver_msg, strlen(ver_msg)); + sprintf(msg, q, maj, min); + message_box(msg); +} + +cmd(configure_screen) { + game.line_min_print = p0; + game.line_user_input = p1; + game.line_status = p2; +} + +cmd(text_screen) { + debugC(4, kDebugLevelScripts, "switching to text mode"); + game.gfx_mode = false; + /* + * Simulates the "bright background bit" of the PC video + * controller. + */ + if (game.color_bg) + game.color_bg |= 0x08; + clear_screen(game.color_bg); +} + +cmd(graphics) { + debugC(4, kDebugLevelScripts, "switching to graphics mode"); + if (!game.gfx_mode) { + game.gfx_mode = true; + clear_screen(0); + show_pic(); + write_status(); + write_prompt(); + } +} + +cmd(set_text_attribute) { + game.color_fg = p0; + game.color_bg = p1; + + if (game.gfx_mode) { + if (game.color_bg != 0) { + game.color_fg = 0; + game.color_bg = 15; + } + } +} + +cmd(status) { + inventory(); +} + +cmd(quit) { + char *buttons[] = { "Quit", "Continue", NULL }; + + stop_sound(); + if (p0) { + game.quit_prog_now = true; + } else { + if (selection_box + (" Quit the game, or continue? \n\n\n", buttons) == 0) { + game.quit_prog_now = true; + } + } +} + +cmd(restart_game) { + char *buttons[] = { "Restart", "Continue", NULL }; + int sel; + + stop_sound(); + sel = getflag(F_auto_restart) ? 1 : + selection_box(" Restart game, or continue? \n\n\n", buttons); + + if (sel == 0) { + game.quit_prog_now = 0xff; + setflag(F_restart_game, true); + menu_enable_all(); + } +} + +cmd(distance) { + int16 x1, y1, x2, y2, d; + struct vt_entry *v0 = &game.view_table[p0]; + struct vt_entry *v1 = &game.view_table[p1]; + + if (v0->flags & DRAWN && v1->flags & DRAWN) { + x1 = v0->x_pos + v0->x_size / 2; + y1 = v0->y_pos; + x2 = v1->x_pos + v1->x_size / 2; + y2 = v1->y_pos; + d = abs(x1 - x2) + abs(y1 - y2); + if (d > 0xfe) + d = 0xfe; + } else { + d = 0xff; + } + _v[p2] = (unsigned char)d; +} + +cmd(accept_input) { + debugC(4, kDebugLevelScripts | kDebugLevelInput, "input normal"); + new_input_mode(INPUT_NORMAL); + game.input_enabled = true; +} + +cmd(prevent_input) { + debugC(4, kDebugLevelScripts | kDebugLevelInput, "no input"); + new_input_mode(INPUT_NONE); + game.input_enabled = false; +} + +cmd(get_string) { + int tex, row, col; + + debugC(4, kDebugLevelScripts, "%d %d %d %d %d", p0, p1, p2, p3, p4); + + tex = p1 - 1; + row = p2; + col = p3; + + /* Workaround for SQLC bug. + * See bug #792125 for details + */ + if (row > 24) + row = 24; + if (col > 39) + col = 39; + + new_input_mode(INPUT_GETSTRING); + + if (cur_logic->texts != NULL && cur_logic->num_texts >= tex) { + int len = strlen(cur_logic->texts[tex]); + print_text(cur_logic->texts[tex], 0, col, row, len, game.color_fg, game.color_bg); + get_string(col + len - 1, row, p4, p0); + + /* SGEO: display input char */ + print_character((col + len), row, game.cursor_char, game.color_fg, game.color_bg); + } + + do { + main_cycle(); + } while (game.input_mode == INPUT_GETSTRING); +} + +cmd(get_num) { + debugC(4, kDebugLevelScripts, "%d %d", p0, p1); + new_input_mode(INPUT_GETSTRING); + + if (cur_logic->texts != NULL && cur_logic->num_texts >= (p0 - 1)) { + int len = strlen(cur_logic->texts[p0 - 1]); + print_text(cur_logic->texts[p0 - 1], 0, 0, 22, len, game.color_fg, game.color_bg); + get_string(len - 1, 22, 3, MAX_STRINGS); + + /* CM: display input char */ + print_character((p3 + len), 22, game.cursor_char, game.color_fg, game.color_bg); + } + + do { + main_cycle(); + } while (game.input_mode == INPUT_GETSTRING); + + _v[p1] = atoi(game.strings[MAX_STRINGS]); + debugC(4, kDebugLevelScripts, "[%s] -> %d", game.strings[MAX_STRINGS], _v[p1]); + clear_lines(22, 22, game.color_bg); + flush_lines(22, 22); +} + +cmd(set_cursor_char) { + if (cur_logic->texts != NULL && (p0 - 1) <= cur_logic->num_texts) { + game.cursor_char = *cur_logic->texts[p0 - 1]; + } else { + /* default */ + game.cursor_char = '_'; + } +} + +cmd(set_key) { + int key; + + debugC(4, kDebugLevelScripts, "%d %d %d", p0, p1, p2); + + if (game.ev_keyp[p2].data != 0) /* TBC sets c23 (ESC) twice! */ + return; + + key = 256 * p1 + p0; + game.ev_keyp[p2].data = key; + game.ev_keyp[p2].occured = false; +} + +cmd(set_string) { + /* CM: to avoid crash in Groza (str = 150) */ + if (p0 > MAX_STRINGS) + return; + strcpy(game.strings[p0], cur_logic->texts[p1 - 1]); +} + +cmd(display) { + print_text(cur_logic->texts[p2 - 1], p1, 0, p0, 40, game.color_fg, game.color_bg); +} + +cmd(display_f) { + debugC(4, kDebugLevelScripts, "p0 = %d", p0); + print_text(cur_logic->texts[_v[p2] - 1], _v[p1], 0, _v[p0], 40, game.color_fg, game.color_bg); +} + +cmd(clear_text_rect) { + int c, x1, y1, x2, y2; + + 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; + + /* 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; + + draw_rectangle(x1, y1, x2, y2, c); + flush_block(x1, y1, x2, y2); +} + +cmd(toggle_monitor) { + report("toggle.monitor\n"); +#ifdef USE_HIRES + opt.hires = !opt.hires; + erase_both(); + show_pic(); + blit_both(); +#endif +} + +cmd(echo_line) { + strcpy((char *)game.input_buffer, (const char *)game.echo_buffer); + game.cursor_pos = strlen((char *)game.input_buffer); + game.has_prompt = 0; +} + +cmd(clear_lines) { + uint8 l; + + /* Residence 44 calls clear.lines(24,0,0), see bug #558423 */ + l = p1 ? p1 : p0; + + clear_lines(p0, l, p2); + flush_lines(p0, l); +} + +cmd(print) { + int n = p0 < 1 ? 1 : p0; + print(cur_logic->texts[n - 1], 0, 0, 0); +} + +cmd(print_f) { + int n = _v[p0] < 1 ? 1 : _v[p0]; + print(cur_logic->texts[n - 1], 0, 0, 0); +} + +cmd(print_at) { + int n = p0 < 1 ? 1 : p0; + debugC(4, kDebugLevelScripts, "%d %d %d %d", p0, p1, p2, p3); + print(cur_logic->texts[n - 1], p1, p2, p3); +} + +cmd(print_at_v) { + int n = _v[p0] < 1 ? 1 : _v[p0]; + print(cur_logic->texts[n - 1], p1, p2, p3); +} + +cmd(push_script) { +#ifdef USE_MOUSE + if (opt.agimouse) { + game.vars[27] = mouse.button; + game.vars[28] = mouse.x / 2; + game.vars[29] = mouse.y; + } else +#endif + report("push.script\n"); +} + +cmd(set_pri_base) { + int i, x, pri; + + report("Priority base set to %d\n", p0); + + /* game.alt_pri = true; */ + x = (_HEIGHT - p0) * _HEIGHT / 10; + + for (i = 0; i < _HEIGHT; i++) { + pri = (i - p0) < 0 ? 4 : (i - p0) * _HEIGHT / x + 5; + if (pri > 15) + pri = 15; + game.pri_table[i] = pri; + } +} + +cmd(mouse_posn) { +#ifdef USE_MOUSE + _v[p0] = WIN_TO_PIC_X(mouse.x); + _v[p1] = WIN_TO_PIC_Y(mouse.y); +#endif +} + +cmd(shake_screen) { + int i; + +#ifdef USE_MOUSE + /* AGI Mouse 1.1 uses shake.screen values between 100 and 109 to + * set the palette. + */ + if (opt.agimouse && p0 >= 100 && p0 < 110) { + report("not implemented: AGI Mouse palettes\n"); + return; + } else +#endif + shake_start(); + + commit_both(); /* Fixes SQ1 demo */ + for (i = 4 * p0; i; i--) { + shake_screen(i & 1); + flush_block(0, 0, GFX_WIDTH - 1, GFX_HEIGHT - 1); + main_cycle(); + } + shake_end(); +} + +static void (*agi_command[183]) (uint8 *) = { + NULL, /* 0x00 */ + cmd_increment, + cmd_decrement, + cmd_assignn, + cmd_assignv, + cmd_addn, + cmd_addv, + cmd_subn, + cmd_subv, /* 0x08 */ + cmd_lindirectv, + cmd_rindirect, + cmd_lindirectn, + cmd_set, + cmd_reset, + cmd_toggle, + cmd_set_v, + cmd_reset_v, /* 0x10 */ + cmd_toggle_v, + cmd_new_room, + cmd_new_room_f, + cmd_load_logic, + cmd_load_logic_f, + cmd_call, + cmd_call_f, + cmd_load_pic, /* 0x18 */ + cmd_draw_pic, + cmd_show_pic, + cmd_discard_pic, + cmd_overlay_pic, + cmd_show_pri_screen, + cmd_load_view, + cmd_load_view_f, + cmd_discard_view, /* 0x20 */ + cmd_animate_obj, + cmd_unanimate_all, + cmd_draw, + cmd_erase, + cmd_position, + cmd_position_f, + cmd_get_posn, + cmd_reposition, /* 0x28 */ + cmd_set_view, + cmd_set_view_f, + cmd_set_loop, + cmd_set_loop_f, + cmd_fix_loop, + cmd_release_loop, + cmd_set_cel, + cmd_set_cel_f, /* 0x30 */ + cmd_last_cel, + cmd_current_cel, + cmd_current_loop, + cmd_current_view, + cmd_number_of_loops, + cmd_set_priority, + cmd_set_priority_f, + cmd_release_priority, /* 0x38 */ + cmd_get_priority, + cmd_stop_update, + cmd_start_update, + cmd_force_update, + cmd_ignore_horizon, + cmd_observe_horizon, + cmd_set_horizon, + cmd_object_on_water, /* 0x40 */ + cmd_object_on_land, + cmd_object_on_anything, + cmd_ignore_objs, + cmd_observe_objs, + cmd_distance, + cmd_stop_cycling, + cmd_start_cycling, + cmd_normal_cycle, /* 0x48 */ + cmd_end_of_loop, + cmd_reverse_cycle, + cmd_reverse_loop, + cmd_cycle_time, + cmd_stop_motion, + cmd_start_motion, + cmd_step_size, + cmd_step_time, /* 0x50 */ + cmd_move_obj, + cmd_move_obj_f, + cmd_follow_ego, + cmd_wander, + cmd_normal_motion, + cmd_set_dir, + cmd_get_dir, + cmd_ignore_blocks, /* 0x58 */ + cmd_observe_blocks, + cmd_block, + cmd_unblock, + cmd_get, + cmd_get_f, + cmd_drop, + cmd_put, + cmd_put_f, /* 0x60 */ + cmd_get_room_f, + cmd_load_sound, + cmd_sound, + cmd_stop_sound, + cmd_print, + cmd_print_f, + cmd_display, + cmd_display_f, /* 0x68 */ + cmd_clear_lines, + cmd_text_screen, + cmd_graphics, + cmd_set_cursor_char, + cmd_set_text_attribute, + cmd_shake_screen, + cmd_configure_screen, + cmd_status_line_on, /* 0x70 */ + cmd_status_line_off, + cmd_set_string, + cmd_get_string, + cmd_word_to_string, + cmd_parse, + cmd_get_num, + cmd_prevent_input, + cmd_accept_input, /* 0x78 */ + cmd_set_key, + cmd_add_to_pic, + cmd_add_to_pic_f, + cmd_status, + cmd_save_game, + cmd_load_game, + cmd_init_disk, + cmd_restart_game, /* 0x80 */ + cmd_show_obj, + cmd_random, + cmd_program_control, + cmd_player_control, + cmd_obj_status_f, + cmd_quit, + cmd_show_mem, + cmd_pause, /* 0x88 */ + cmd_echo_line, + cmd_cancel_line, + cmd_init_joy, + cmd_toggle_monitor, + cmd_version, + cmd_script_size, + cmd_set_game_id, + cmd_log, /* 0x90 */ + cmd_set_scan_start, + cmd_reset_scan_start, + cmd_reposition_to, + cmd_reposition_to_f, + cmd_trace_on, + cmd_trace_info, + cmd_print_at, + cmd_print_at_v, /* 0x98 */ + cmd_discard_view, + cmd_clear_text_rect, + cmd_set_upper_left, + cmd_set_menu, + cmd_set_menu_item, + cmd_submit_menu, + cmd_enable_item, + cmd_disable_item, /* 0xa0 */ + cmd_menu_input, + cmd_show_obj_v, + cmd_open_dialogue, + cmd_close_dialogue, + cmd_mul_n, + cmd_mul_v, + cmd_div_n, + cmd_div_v, /* 0xa8 */ + cmd_close_window, + cmd_set_simple, + cmd_push_script, + cmd_pop_script, + cmd_hold_key, + cmd_set_pri_base, + cmd_discard_sound, + cmd_hide_mouse, /* 0xb0 */ + cmd_allow_menu, + cmd_show_mouse, + cmd_fence_mouse, + cmd_mouse_posn, + cmd_release_key, + cmd_adj_ego_move_to_x_y +}; + +#define CMD_BSIZE 12 + +/** + * Execute a logic script + * @param n Number of the logic resource to execute + */ +int run_logic(int n) { + uint8 op = 0; + uint8 p[CMD_BSIZE] = { 0 }; + uint8 *code = NULL; + int num = 0; + + /* If logic not loaded, load it */ + if (~game.dir_logic[n].flags & RES_LOADED) { + debugC(4, kDebugLevelScripts, "logic %d not loaded!", n); + agi_load_resource(rLOGIC, n); + } + + game.lognum = n; + cur_logic = &game.logics[game.lognum]; + + code = cur_logic->data; + cur_logic->cIP = cur_logic->sIP; + + timer_hack = 0; + while (ip < game.logics[n].size && !game.quit_prog_now) { +#ifdef USE_CONSOLE + if (debug_.enabled) { + if (debug_.steps > 0) { + if (debug_.logic0 || n) { + debug_console(n, lCOMMAND_MODE, NULL); + debug_.steps--; + } + } else { + blit_both(); + console_prompt(); + do { + main_cycle(); + } while (!debug_.steps && debug_.enabled); + console_lock(); + erase_both(); + } + } +#endif + + switch (op = *(code + ip++)) { + case 0xff: /* if (open/close) */ + test_if_code(n); + break; + case 0xfe: /* goto */ + /* +2 covers goto size */ + ip += 2 + ((int16)READ_LE_UINT16(code + ip)); + /* timer must keep running even in goto loops, + * but AGI engine can't do that :( + */ + if (timer_hack > 20) { + poll_timer(); + update_timer(); + timer_hack = 0; + } + break; + case 0x00: /* return */ + return 1; + default: + num = logic_names_cmd[op].num_args; + memmove(p, code + ip, num); + memset(p + num, 0, CMD_BSIZE - num); + agi_command[op] (p); + ip += num; + } + + if (game.exit_all_logics) + break; + } + + return 0; /* after executing new.room() */ +} + +void execute_agi_command(uint8 op, uint8 *p) { +#ifdef USE_CONSOLE + debugC(2, kDebugLevelScripts, "%s %d %d %d", logic_names_cmd[op].name, p[0], p[1], p[2]); +#endif + agi_command[op] (p); +} + +} // End of namespace Agi diff --git a/engines/agi/op_dbg.cpp b/engines/agi/op_dbg.cpp new file mode 100644 index 0000000000..2a8af3bc68 --- /dev/null +++ b/engines/agi/op_dbg.cpp @@ -0,0 +1,353 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2006 The ScummVM project + * + * Copyright (C) 1999-2001 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 "agi/agi.h" +#include "agi/opcodes.h" + +namespace Agi { + +#define ip (game.logics[lognum].cIP) +#define code (game.logics[lognum].data) + +#ifdef _L +#undef _L +#endif + +#ifdef USE_CONSOLE +#define _L(a,b,c) { a, b, c } +#else +#define _L(a,b,c) { b, c } +#endif + +struct agi_logicnames logic_names_test[] = { + _L("", 0, 0x00), + _L("equaln", 2, 0x80), + _L("equalv", 2, 0xC0), + _L("lessn", 2, 0x80), + _L("lessv", 2, 0xC0), + _L("greatern", 2, 0x80), + _L("greaterv", 2, 0xC0), + _L("isset", 1, 0x00), + _L("issetv", 1, 0x80), + _L("has", 1, 0x00), + _L("obj.in.room", 2, 0x40), + _L("posn", 5, 0x00), + _L("controller", 1, 0x00), + _L("have.key", 0, 0x00), + + /* Not 0 args. Has variable number. */ + _L("said", 0, 0x00), + + _L("compare.strings", 2, 0x00), + _L("obj.in.box", 5, 0x00), + _L("center.posn", 5, 0x00), + _L("right.posn", 5, 0x00) +}; + +struct agi_logicnames logic_names_if[] = { + _L("OR", 0, 0x00), + _L("NOT", 0, 0x00), + _L("ELSE", 0, 0x00), + _L("IF", 0, 0x00) +}; + +struct agi_logicnames logic_names_cmd[] = { + _L("return", 0, 0x00), /* 00 */ + _L("increment", 1, 0x80), /* 01 */ + _L("decrement", 1, 0x80), /* 02 */ + _L("assignn", 2, 0x80), /* 03 */ + _L("assignv", 2, 0xC0), /* 04 */ + _L("addn", 2, 0x80), /* 05 */ + _L("addv", 2, 0xC0), /* 06 */ + _L("subn", 2, 0x80), /* 07 */ + _L("subv", 2, 0xC0), /* 08 */ + _L("lindirectv", 2, 0xC0), /* 09 */ + _L("rindirect", 2, 0xC0), /* 0A */ + _L("lindirectn", 2, 0x80), /* 0B */ + _L("set", 1, 0x00), /* 0C */ + _L("reset", 1, 0x00), /* 0D */ + _L("toggle", 1, 0x00), /* 0E */ + _L("set.v", 1, 0x80), /* 0F */ + _L("reset.v", 1, 0x80), /* 10 */ + _L("toggle.v", 1, 0x80), /* 11 */ + _L("new.room", 1, 0x00), /* 12 */ + _L("new.room.v", 1, 0x80), /* 13 */ + _L("load.logics", 1, 0x00), /* 14 */ + _L("load.logics.v", 1, 0x80), /* 15 */ + _L("call", 1, 0x00), /* 16 */ + _L("call.v", 1, 0x80), /* 17 */ + _L("load.pic", 1, 0x80), /* 18 */ + _L("draw.pic", 1, 0x80), /* 19 */ + _L("show.pic", 0, 0x00), /* 1A */ + _L("discard.pic", 1, 0x80), /* 1B */ + _L("overlay.pic", 1, 0x80), /* 1C */ + _L("show.pri.screen", 0, 0x00), /* 1D */ + _L("load.view", 1, 0x00), /* 1E */ + _L("load.view.v", 1, 0x80), /* 1F */ + _L("discard.view", 1, 0x00), /* 20 */ + _L("animate.obj", 1, 0x00), /* 21 */ + _L("unanimate.all", 0, 0x00), /* 22 */ + _L("draw", 1, 0x00), /* 23 */ + _L("erase", 1, 0x00), /* 24 */ + _L("position", 3, 0x00), /* 25 */ + _L("position.v", 3, 0x60), /* 26 */ + _L("get.posn", 3, 0x60), /* 27 */ + _L("reposition", 3, 0x60), /* 28 */ + _L("set.view", 2, 0x00), /* 29 */ + _L("set.view.v", 2, 0x40), /* 2A */ + _L("set.loop", 2, 0x00), /* 2B */ + _L("set.loop.v", 2, 0x40), /* 2C */ + _L("fix.loop", 1, 0x00), /* 2D */ + _L("release.loop", 1, 0x00), /* 2E */ + _L("set.cel", 2, 0x00), /* 2F */ + _L("set.cel.v", 2, 0x40), /* 30 */ + _L("last.cel", 2, 0x40), /* 31 */ + _L("current.cel", 2, 0x40), /* 32 */ + _L("current.loop", 2, 0x40), /* 33 */ + _L("current.view", 2, 0x40), /* 34 */ + _L("number.of.loops", 2, 0x40), /* 35 */ + _L("set.priority", 2, 0x00), /* 36 */ + _L("set.priority.v", 2, 0x40), /* 37 */ + _L("release.priority", 1, 0x00), /* 38 */ + _L("get.priority", 2, 0x40), /* 39 */ + _L("stop.update", 1, 0x00), /* 3A */ + _L("start.update", 1, 0x00), /* 3B */ + _L("force.update", 1, 0x00), /* 3C */ + _L("ignore.horizon", 1, 0x00), /* 3D */ + _L("observe.horizon", 1, 0x00), /* 3E */ + _L("set.horizon", 1, 0x00), /* 3F */ + _L("object.on.water", 1, 0x00), /* 40 */ + _L("object.on.land", 1, 0x00), /* 41 */ + _L("object.on.anything", 1, 0x00), /* 42 */ + _L("ignore.objs", 1, 0x00), /* 43 */ + _L("observe.objs", 1, 0x00), /* 44 */ + _L("distance", 3, 0x20), /* 45 */ + _L("stop.cycling", 1, 0x00), /* 46 */ + _L("start.cycling", 1, 0x00), /* 47 */ + _L("normal.cycle", 1, 0x00), /* 48 */ + _L("end.of.loop", 2, 0x00), /* 49 */ + _L("reverse.cycle", 1, 0x00), /* 4A */ + _L("reverse.loop", 2, 0x00), /* 4B */ + _L("cycle.time", 2, 0x40), /* 4C */ + _L("stop.motion", 1, 0x00), /* 4D */ + _L("start.motion", 1, 0x00), /* 4E */ + _L("step.size", 2, 0x40), /* 4F */ + _L("step.time", 2, 0x40), /* 50 */ + _L("move.obj", 5, 0x00), /* 51 */ + _L("move.obj.v", 5, 0x70), /* 52 */ + _L("follow.ego", 3, 0x00), /* 53 */ + _L("wander", 1, 0x00), /* 54 */ + _L("normal.motion", 1, 0x00), /* 55 */ + _L("set.dir", 2, 0x40), /* 56 */ + _L("get.dir", 2, 0x40), /* 57 */ + _L("ignore.blocks", 1, 0x00), /* 58 */ + _L("observe.blocks", 1, 0x00), /* 59 */ + _L("block", 4, 0x00), /* 5A */ + _L("unblock", 0, 0x00), /* 5B */ + _L("get", 1, 0x00), /* 5C */ + _L("get.v", 1, 0x80), /* 5D */ + _L("drop", 1, 0x00), /* 5E */ + _L("put", 2, 0x00), /* 5F */ + _L("put.v", 2, 0x40), /* 60 */ + _L("get.room.v", 2, 0xC0), /* 61 */ + _L("load.sound", 1, 0x00), /* 62 */ + _L("sound", 2, 0x00), /* 63 */ + _L("stop.sound", 0, 0x00), /* 64 */ + _L("print", 1, 0x00), /* 65 */ + _L("print.v", 1, 0x80), /* 66 */ + _L("display", 3, 0x00), /* 67 */ + _L("display.v", 3, 0xE0), /* 68 */ + _L("clear.lines", 3, 0x00), /* 69 */ + _L("text.screen", 0, 0x00), /* 6A */ + _L("graphics", 0, 0x00), /* 6B */ + _L("set.cursor.char", 1, 0x00), /* 6C */ + _L("set.text.attribute", 2, 0x00), /* 6D */ + _L("shake.screen", 1, 0x00), /* 6E */ + _L("configure.screen", 3, 0x00), /* 6F */ + _L("status.line.on", 0, 0x00), /* 70 */ + _L("status.line.off", 0, 0x00), /* 71 */ + _L("set.string", 2, 0x00), /* 72 */ + _L("get.string", 5, 0x00), /* 73 */ + _L("word.to.string", 2, 0x00), /* 74 */ + _L("parse", 1, 0x00), /* 75 */ + _L("get.num", 2, 0x40), /* 76 */ + _L("prevent.input", 0, 0x00), /* 77 */ + _L("accept.input", 0, 0x00), /* 78 */ + _L("set.key", 3, 0x00), /* 79 */ + _L("add.to.pic", 7, 0x00), /* 7A */ + _L("add.to.pic.v", 7, 0xFE), /* 7B */ + _L("status", 0, 0x00), /* 7C */ + _L("save.game", 0, 0x00), /* 7D */ + _L("restore.game", 0, 0x00), /* 7E */ + _L("init.disk", 0, 0x00), /* 7F */ + _L("restart.game", 0, 0x00), /* 80 */ + _L("show.obj", 1, 0x00), /* 81 */ + _L("random", 3, 0x20), /* 82 */ + _L("program.control", 0, 0x00), /* 83 */ + _L("player.control", 0, 0x00), /* 84 */ + _L("obj.status.v", 1, 0x80), /* 85 */ + /* 0 args for AGI version 2.089 */ + _L("quit", 1, 0x00), /* 86 */ + + _L("show.mem", 0, 0x00), /* 87 */ + _L("pause", 0, 0x00), /* 88 */ + _L("echo.line", 0, 0x00), /* 89 */ + _L("cancel.line", 0, 0x00), /* 8A */ + _L("init.joy", 0, 0x00), /* 8B */ + _L("toggle.monitor", 0, 0x00), /* 8C */ + _L("version", 0, 0x00), /* 8D */ + _L("script.size", 1, 0x00), /* 8E */ + _L("set.game.id", 1, 0x00), /* 8F */ + _L("log", 1, 0x00), /* 90 */ + _L("set.scan.start", 0, 0x00), /* 91 */ + _L("reset.scan.start", 0, 0x00), /* 92 */ + _L("reposition.to", 3, 0x00), /* 93 */ + _L("reposition.to.v", 3, 0x60), /* 94 */ + _L("trace.on", 0, 0x00), /* 95 */ + _L("trace.info", 3, 0x00), /* 96 */ + + /* 3 args for AGI versions before 2.440 */ + _L("print.at", 4, 0x00), /* 97 */ + _L("print.at.v", 4, 0x80), /* 98 */ + + _L("discard.view.v", 1, 0x80), /* 99 */ + _L("clear.text.rect", 5, 0x00), /* 9A */ + _L("set.upper.left", 2, 0x00), /* 9B */ + _L("set.menu", 1, 0x00), /* 9C */ + _L("set.menu.item", 2, 0x00), /* 9D */ + _L("submit.menu", 0, 0x00), /* 9E */ + _L("enable.item", 1, 0x00), /* 9F */ + _L("disable.item", 1, 0x00), /* A0 */ + _L("menu.input", 0, 0x00), /* A1 */ + _L("show.obj.v", 1, 0x01), /* A2 */ + _L("open.dialogue", 0, 0x00), /* A3 */ + _L("close.dialogue", 0, 0x00), /* A4 */ + _L("mul.n", 2, 0x80), /* A5 */ + _L("mul.v", 2, 0xC0), /* A6 */ + _L("div.n", 2, 0x80), /* A7 */ + _L("div.v", 2, 0xC0), /* A8 */ + _L("close.window", 0, 0x00), /* A9 */ + + _L("set.simple", 1, 0x00), /* AA */ + _L("push.script", 0, 0x00), /* AB */ + _L("pop.script", 0, 0x00), /* AC */ + _L("hold.key", 0, 0x00), /* AD */ + _L("set.pri.base", 1, 0x00), /* AE */ + _L("discard.sound", 1, 0x00), /* AF */ + + /* 1 arg for AGI version 3.002.086 */ + _L("hide.mouse", 0, 0x00), /* B0 */ + + _L("allow.menu", 1, 0x00), /* B1 */ + _L("show.mouse", 0, 0x00), /* B2 */ + _L("fence.mouse", 4, 0x00), /* B3 */ + _L("mouse.posn", 2, 0x00), /* B4 */ + _L("release.key", 0, 0x00), /* B5 */ + _L("adj.ego.move.to.xy", 0, 0x00), /* B6 */ + _L(NULL, 0, 0x00) +}; + +#ifdef USE_CONSOLE + +void debug_console(int lognum, int mode, char *str) { + struct agi_logicnames *x; + uint8 a, c, z; + + if (str) { + report(" %s\n", str); + return; + } + + report("%03d:%04x ", lognum, ip); + + switch (*(code + ip)) { + case 0xFC: + case 0xFD: + case 0xFE: + case 0xFF: + x = logic_names_if; + + if (debug_.opcodes) { + report("%02X %02X %02X %02X %02X %02X %02X %02X %02X\n" + " ", + (uint8) * (code + (0 + ip)) & 0xFF, + (uint8) * (code + (1 + ip)) & 0xFF, + (uint8) * (code + (2 + ip)) & 0xFF, + (uint8) * (code + (3 + ip)) & 0xFF, + (uint8) * (code + (4 + ip)) & 0xFF, + (uint8) * (code + (5 + ip)) & 0xFF, + (uint8) * (code + (6 + ip)) & 0xFF, + (uint8) * (code + (7 + ip)) & 0xFF, + (uint8) * (code + (8 + ip)) & 0xFF); + } + report("%s ", (x + *(code + ip) - 0xFC)->name); + break; + default: + x = mode == lCOMMAND_MODE ? logic_names_cmd : logic_names_test; + a = (unsigned char)(x + *(code + ip))->num_args; + c = (unsigned char)(x + *(code + ip))->arg_mask; + + if (debug_.opcodes) { + report("%02X %02X %02X %02X %02X %02X %02X %02X %02X\n" + " ", + (uint8) * (code + (0 + ip)) & 0xFF, + (uint8) * (code + (1 + ip)) & 0xFF, + (uint8) * (code + (2 + ip)) & 0xFF, + (uint8) * (code + (3 + ip)) & 0xFF, + (uint8) * (code + (4 + ip)) & 0xFF, + (uint8) * (code + (5 + ip)) & 0xFF, + (uint8) * (code + (6 + ip)) & 0xFF, + (uint8) * (code + (7 + ip)) & 0xFF, + (uint8) * (code + (8 + ip)) & 0xFF); + } + report("%s ", (x + *(code + ip))->name); + + for (z = 1; a > 0;) { + if (~c & 0x80) { + report("%d", *(code + (ip + z))); + } else { + report("v%d[%d]", *(code + (ip + z)), getvar(*(code + (ip + z)))); + } + c <<= 1; + z++; + if (--a > 0) + report(","); + } + break; + } + + report("\n"); +} + +#else + +void debug_console(int lognum, int mode, char *str) { + /* dummy */ +} + +#endif /* USE_CONSOLE */ + +} // End of namespace Agi diff --git a/engines/agi/op_test.cpp b/engines/agi/op_test.cpp new file mode 100644 index 0000000000..d4a98bb0ac --- /dev/null +++ b/engines/agi/op_test.cpp @@ -0,0 +1,419 @@ +/* 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 "agi/agi.h" +#include "agi/keyboard.h" +#include "agi/opcodes.h" + +namespace Agi { + +static uint8 test_obj_right(uint8, uint8, uint8, uint8, uint8); +static uint8 test_obj_centre(uint8, uint8, uint8, uint8, uint8); +static uint8 test_obj_in_box(uint8, uint8, uint8, uint8, uint8); +static uint8 test_posn(uint8, uint8, uint8, uint8, uint8); +static uint8 test_said(uint8, uint8 *); +static uint8 test_controller(uint8); +static uint8 test_keypressed(void); +static uint8 test_compare_strings(uint8, uint8); + +#define ip (game.logics[lognum].cIP) +#define code (game.logics[lognum].data) + +#define test_equal(v1,v2) (getvar(v1) == (v2)) +#define test_less(v1,v2) (getvar(v1) < (v2)) +#define test_greater(v1,v2) (getvar(v1) > (v2)) +#define test_isset(flag) (getflag (flag)) +#define test_has(obj) (object_get_location (obj) == EGO_OWNED) +#define test_obj_in_room(obj,v) (object_get_location (obj) == getvar (v)) + +extern int timer_hack; /* For the timer loop in MH1 logic 153 */ + +static uint8 test_compare_strings(uint8 s1, uint8 s2) { + char ms1[MAX_STRINGLEN]; + char ms2[MAX_STRINGLEN]; + int j, k, l; + + strcpy(ms1, game.strings[s1]); + strcpy(ms2, game.strings[s2]); + + l = strlen(ms1); + for (k = 0, j = 0; k < l; k++) { + switch (ms1[k]) { + case 0x20: + case 0x09: + case '-': + case '.': + case ',': + case ':': + case ';': + case '!': + case '\'': + break; + + default: + ms1[j++] = toupper(ms1[k]); + break; + } + } + ms1[j] = 0x0; + + l = strlen(ms2); + for (k = 0, j = 0; k < l; k++) { + switch (ms2[k]) { + case 0x20: + case 0x09: + case '-': + case '.': + case ',': + case ':': + case ';': + case '!': + case '\'': + break; + + default: + ms2[j++] = toupper(ms2[k]); + break; + } + } + ms2[j] = 0x0; + + return !strcmp(ms1, ms2); +} + +static uint8 test_keypressed() { + int x = game.keypress; + + game.keypress = 0; + if (!x) { + int mode = game.input_mode; + game.input_mode = INPUT_NONE; + main_cycle(); + game.input_mode = mode; + } + + if (x) + debugC(5, kDebugLevelScripts | kDebugLevelInput, "keypress = %02x", x); + + return x; +} + +static uint8 test_controller(uint8 cont) { + return game.ev_keyp[cont].occured; +} + +static uint8 test_posn(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) { + struct vt_entry *v = &game.view_table[n]; + uint8 r; + + r = v->x_pos >= x1 && v->y_pos >= y1 && v->x_pos <= x2 && v->y_pos <= y2; + + debugC(7, kDebugLevelScripts, "(%d,%d) in (%d,%d,%d,%d): %s", v->x_pos, v->y_pos, x1, y1, x2, y2, r ? "true" : "false"); + + return r; +} + +static uint8 test_obj_in_box(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) { + struct vt_entry *v = &game.view_table[n]; + + return v->x_pos >= x1 && + v->y_pos >= y1 && v->x_pos + v->x_size - 1 <= x2 && v->y_pos <= y2; +} + +/* if n is in centre of box */ +static uint8 test_obj_centre(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) { + struct vt_entry *v = &game.view_table[n]; + + return v->x_pos + v->x_size / 2 >= x1 && + v->x_pos + v->x_size / 2 <= x2 && v->y_pos >= y1 && v->y_pos <= y2; +} + +/* if nect N is in right corner */ +static uint8 test_obj_right(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) { + struct vt_entry *v = &game.view_table[n]; + + return v->x_pos + v->x_size - 1 >= x1 && + v->x_pos + v->x_size - 1 <= x2 && v->y_pos >= y1 && v->y_pos <= y2; +} + +/* When player has entered something, it is parsed elsewhere */ +static uint8 test_said(uint8 nwords, uint8 *cc) { + int c, n = game.num_ego_words; + int z = 0; + + if (getflag(F_said_accepted_input) || !getflag(F_entered_cli)) + return false; + + /* FR: + * I think the reason for the code below is to add some speed.... + * + * if (nwords != num_ego_words) + * return false; + * + * In the disco scene in Larry 1 when you type "examine blonde", + * inside the logic is expected ( said("examine", "blonde", "rol") ) + * where word("rol") = 9999 + * + * According to the interpreter code 9999 means that whatever the + * user typed should be correct, but it looks like code 9999 means that + * if the string is empty at this point, the entry is also correct... + * + * With the removal of this code, the behaviour of the scene was + * corrected + */ + + for (c = 0; nwords && n; c++, nwords--, n--) { + z = READ_LE_UINT16(cc); + cc += 2; + + switch (z) { + case 9999: /* rest of line (empty string counts to...) */ + nwords = 1; + break; + case 1: /* any word */ + break; + default: + if (game.ego_words[c].id != z) + return false; + break; + } + } + + /* The entry string should be entirely parsed, or last word = 9999 */ + if (n && z != 9999) + return false; + + /* The interpreter string shouldn't be entirely parsed, but next + * word must be 9999. + */ + if (nwords != 0 && READ_LE_UINT16(cc) != 9999) + return false; + + setflag(F_said_accepted_input, true); + + return true; +} + +int test_if_code(int lognum) { + int ec = true; + int retval = true; + uint8 op = 0; + uint8 not_test = false; + uint8 or_test = false; + uint16 last_ip = ip; + uint8 p[16] = { 0 }; + + while (retval && !game.quit_prog_now) { +#ifdef USE_CONSOLE + if (debug_.enabled && (debug_.logic0 || lognum)) + debug_console(lognum, lTEST_MODE, NULL); +#endif + + last_ip = ip; + op = *(code + ip++); + memmove(p, (code + ip), 16); + + switch (op) { + case 0xFF: /* END IF, TEST true */ + goto end_test; + case 0xFD: + not_test = !not_test; + continue; + case 0xFC: /* OR */ + /* if or_test is ON and we hit 0xFC, end of OR, then + * or is STILL false so break. + */ + if (or_test) { + ec = false; + retval = false; + goto end_test; + } + + or_test = true; + continue; + + case 0x00: + /* return true? */ + goto end_test; + case 0x01: + ec = test_equal(p[0], p[1]); + if (p[0] == 11) + timer_hack++; + break; + case 0x02: + ec = test_equal(p[0], getvar(p[1])); + if (p[0] == 11 || p[1] == 11) + timer_hack++; + break; + case 0x03: + ec = test_less(p[0], p[1]); + if (p[0] == 11) + timer_hack++; + break; + case 0x04: + ec = test_less(p[0], getvar(p[1])); + if (p[0] == 11 || p[1] == 11) + timer_hack++; + break; + case 0x05: + ec = test_greater(p[0], p[1]); + if (p[0] == 11) + timer_hack++; + break; + case 0x06: + ec = test_greater(p[0], getvar(p[1])); + if (p[0] == 11 || p[1] == 11) + timer_hack++; + break; + case 0x07: + ec = test_isset(p[0]); + break; + case 0x08: + ec = test_isset(getvar(p[0])); + break; + case 0x09: + ec = test_has(p[0]); + break; + case 0x0A: + ec = test_obj_in_room(p[0], p[1]); + break; + case 0x0B: + ec = test_posn(p[0], p[1], p[2], p[3], p[4]); + break; + case 0x0C: + ec = test_controller(p[0]); + break; + case 0x0D: + ec = test_keypressed(); + break; + case 0x0E: + ec = test_said(p[0], (uint8 *) code + (ip + 1)); + ip = last_ip; + ip++; /* skip opcode */ + ip += p[0] * 2; /* skip num_words * 2 */ + ip++; /* skip num_words opcode */ + break; + case 0x0F: + debugC(7, kDebugLevelScripts, "comparing [%s], [%s]", game.strings[p[0]], game.strings[p[1]]); + ec = test_compare_strings(p[0], p[1]); + break; + case 0x10: + ec = test_obj_in_box(p[0], p[1], p[2], p[3], p[4]); + break; + case 0x11: + ec = test_obj_centre(p[0], p[1], p[2], p[3], p[4]); + break; + case 0x12: + ec = test_obj_right(p[0], p[1], p[2], p[3], p[4]); + break; + default: + ec = false; + goto end_test; + } + + if (op <= 0x12) + ip += logic_names_test[op].num_args; + + /* exchange ec value */ + if (not_test) + ec = !ec; + + /* not is only enabled for 1 test command */ + not_test = false; + + if (or_test && ec) { + /* a true inside an OR statement passes + * ENTIRE statement scan for end of OR + */ + + /* CM: test for opcode < 0xfc changed from 'op' to + * '*(code+ip)', to avoid problem with the 0xfd (NOT) + * opcode byte. Changed a bad ip += ... ip++ construct. + * This should fix the crash with Larry's logic.0 code: + * + * if ((isset(4) || + * !isset(2) || + * v30 == 2 || + * v30 == 1)) { + * goto Label1; + * } + * + * The bytecode is: + * ff fc 07 04 fd 07 02 01 1e 02 01 1e 01 fc ff + */ + + /* find end of OR */ + while (*(code + ip) != 0xFC) { + if (*(code + ip) == 0x0E) { /* said */ + ip++; + /* cover count + ^words */ + ip += 1 + ((*(code + ip)) * 2); + continue; + } + + if (*(code + ip) < 0xFC) + ip += logic_names_test[*(code + ip)].num_args; + ip++; + } + ip++; + + or_test = false; + retval = true; + } else { + retval = or_test ? retval || ec : retval && ec; + } + } + end_test: + + /* if false, scan for end of IP? */ + if (retval) + ip += 2; + else { + ip = last_ip; + while (*(code + ip) != 0xff) { + if (*(code + ip) == 0x0e) { + ip++; + ip += (*(code + ip)) * 2 + 1; + } else if (*(code + ip) < 0xfc) { + ip += logic_names_test[*(code + ip)].num_args; + ip++; + } else { + ip++; + } + } + ip++; /* skip over 0xFF */ + ip += READ_LE_UINT16(code + ip) + 2; + } + +#ifdef USE_CONSOLE + if (debug_.enabled && (debug_.logic0 || lognum)) + debug_console(lognum, 0xFF, retval ? (char *)"=true" : (char *)"=false"); +#endif + + return retval; +} + +} // End of namespace Agi diff --git a/engines/agi/opcodes.h b/engines/agi/opcodes.h new file mode 100644 index 0000000000..236ba19e68 --- /dev/null +++ b/engines/agi/opcodes.h @@ -0,0 +1,55 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2006 The ScummVM project + * + * Copyright (C) 1999-2001 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$ + * + */ + +#ifndef __AGI_OPCODES_H +#define __AGI_OPCODES_H + +#include "agi/agi.h" + +namespace Agi { + +struct agi_logicnames { +#ifdef USE_CONSOLE /* ifndef NO_DEBUG */ + char *name; +#endif + uint16 num_args; + uint16 arg_mask; +}; + +extern struct agi_logicnames logic_names_test[]; +extern struct agi_logicnames logic_names_cmd[]; +extern struct agi_logicnames logic_names_if[]; + +void debug_console(int, int, char *); +int test_if_code(int); +void new_room(int); +void execute_agi_command(uint8, uint8 *); + +#ifdef PATCH_LOGIC +void patch_logic(int); +#endif + +} // End of namespace Agi + +#endif /* __AGI_OPCODES_H */ diff --git a/engines/agi/patches.cpp b/engines/agi/patches.cpp new file mode 100644 index 0000000000..a666c84686 --- /dev/null +++ b/engines/agi/patches.cpp @@ -0,0 +1,144 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2006 The ScummVM project + * + * Copyright (C) 1999-2001 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 "agi/agi.h" +#include "agi/opcodes.h" + +namespace Agi { + +#ifdef PATCH_LOGIC + +#define ip (game.logics[n].cIP) +#define code (game.logics[n].data) +#define size (game.logics[n].size) + +/* + * Patches + */ + +static uint8 kq4data_find[] = { + 0x0C, 0x04, 0xFF, 0x07, 0x05, 0xFF, 0x15, 0x00, + 0x03, 0x0A, 0x00, 0x77, 0x83, 0x71, 0x0D, 0x97, + 0x03, 0x98, 0xCE, 0x18, 0x98, 0x19, 0x98, 0x1B, + 0x98, 0x0C, 0x5A, 0x1A, 0x00 +}; + +static uint8 kq4data_fix[] = { + /* v19 = 0 + * new.room(96) + * return + */ + 0x03, 0x13, 0x0, 0x12, 0x60, 0x00 +}; + +static uint8 grdata_find[] = { + 0x0C, 0x04, 0xFF, 0x07, 0x05, 0xFF, 0x16, 0x00, + 0x0C, 0x96, 0x03, 0x0A, 0x00, 0x77, 0x83, 0x71, + 0x0D, 0xD9, 0x03, 0xDC, 0xBF, 0x18, 0xDC, 0x19, + 0xDC, 0x1B, 0xDC, 0x0C, 0x95, 0x1A +}; + +static uint8 grdata_fix[] = { + /* reset(227) + * v19 = 0 + * v246 = 1 + * set(15) + * new.room(73) + */ + 0x0D, 0xE3, 0x03, 0x13, 0x00, 0x03, 0xF6, 0x01, + 0x0C, 0x0F, 0x12, 0x49 +}; + +#if 0 +static uint8 lsl1data_find[] = { + 0xFF, 0xFD, 0x07, 0x1E, 0xFC, 0x07, 0x6D, 0x01, + 0x5F, 0x03, 0xFC, 0xFF, 0x12, 0x00, 0x0C, 0x6D, + 0x78, 0x8A, 0x77, 0x69, 0x16, 0x18, 0x00, 0x0D, + 0x30, 0x0D, 0x55, 0x78, 0x65, 0x0A +}; + +static uint8 lsl1data_fix[] = { + /* set(109) + * reset(48) + * reset(85) + * accept.input() + * new.room(11) + */ + 0x0C, 0x6D, 0x0D, 0x30, 0x0D, 0x55, 0x78, 0x12, + 0x0B +}; +#endif + +static uint8 mh1data_find[] = { + 0xFF, 0x07, 0x05, 0xFF, 0xE6, 0x00, + 0x03, 0x0A, 0x02, 0x77, 0x83, 0x71, + 0x6F, 0x01, 0x17, 0x00, 0x03, 0x00, + 0x9F, 0x03, 0x37, 0x00, 0x03, 0x32, + 0x03, 0x03, 0x3B, 0x00, 0x6C, 0x03 +}; + +static uint8 mh1data_fix[] = { + 0x0C, 0x05, 0x16, 0x5A, 0x12, 0x99 +}; + +void patch_logic(int n) { + switch (n) { +#if 0 + /* ALT-X in the questions takes care of that */ + case 6: + /* lsl1 bypass questions */ + if (!strcmp(game.id, "LLLLL")) { + if (!memcmp(lsl1data_find, (code + ip), 30)) + memmove((code + ip), lsl1data_fix, 9); + } + break; +#endif + case 125: + /* gold rush code break */ + if (!strcmp(game.id, "GR")) { + if (!memcmp(grdata_find, (code + ip), 30)) + memmove((code + ip), grdata_fix, 12); + } + break; + case 140: + /* kings quest 4 code break */ + if (!strcmp(game.id, "KQ4")) { + if (memcmp(kq4data_find, (code + ip), 29) == 0) + memmove((code + ip), kq4data_fix, 6); + } + break; + case 159: + /* manhunter 1 amiga */ + if (ip + 30 < size && !memcmp(mh1data_find, (code + ip), 30)) { + memmove((code + ip), mh1data_fix, 6); + } + break; + } +} + +#endif + +} // End of namespace Agi diff --git a/engines/agi/picture.cpp b/engines/agi/picture.cpp new file mode 100644 index 0000000000..1b9ab22cdf --- /dev/null +++ b/engines/agi/picture.cpp @@ -0,0 +1,1034 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2006 The ScummVM project + * + * Copyright (C) 1999-2001 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 "agi/agi.h" +#include "agi/graphics.h" +#include "agi/savegame.h" + +namespace Agi { + +#define next_byte data[foffs++] + +static uint8 *data; +static uint32 flen; +static uint32 foffs; + +static uint8 pat_code; +static uint8 pat_num; +static uint8 pri_on; +static uint8 scr_on; +static uint8 scr_colour; +static uint8 pri_colour; + +static uint8 circles[][15] = { /* agi circle bitmaps */ + {0x80}, + {0xfc}, + {0x5f, 0xf4}, + {0x66, 0xff, 0xf6, 0x60}, + {0x23, 0xbf, 0xff, 0xff, 0xee, 0x20}, + {0x31, 0xe7, 0x9e, 0xff, 0xff, 0xde, 0x79, 0xe3, 0x00}, + {0x38, 0xf9, 0xf3, 0xef, 0xff, 0xff, 0xff, 0xfe, 0xf9, 0xf3, 0xe3, 0x80}, + {0x18, 0x3c, 0x7e, 0x7e, 0x7e, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7e, 0x7e, 0x7e, 0x3c, 0x18} +}; + +static uint8 splatter_map[32] = { /* splatter brush bitmaps */ + 0x20, 0x94, 0x02, 0x24, 0x90, 0x82, 0xa4, 0xa2, + 0x82, 0x09, 0x0a, 0x22, 0x12, 0x10, 0x42, 0x14, + 0x91, 0x4a, 0x91, 0x11, 0x08, 0x12, 0x25, 0x10, + 0x22, 0xa8, 0x14, 0x24, 0x00, 0x50, 0x24, 0x04 +}; + +static uint8 splatter_start[128] = { /* starting bit position */ + 0x00, 0x18, 0x30, 0xc4, 0xdc, 0x65, 0xeb, 0x48, + 0x60, 0xbd, 0x89, 0x05, 0x0a, 0xf4, 0x7d, 0x7d, + 0x85, 0xb0, 0x8e, 0x95, 0x1f, 0x22, 0x0d, 0xdf, + 0x2a, 0x78, 0xd5, 0x73, 0x1c, 0xb4, 0x40, 0xa1, + 0xb9, 0x3c, 0xca, 0x58, 0x92, 0x34, 0xcc, 0xce, + 0xd7, 0x42, 0x90, 0x0f, 0x8b, 0x7f, 0x32, 0xed, + 0x5c, 0x9d, 0xc8, 0x99, 0xad, 0x4e, 0x56, 0xa6, + 0xf7, 0x68, 0xb7, 0x25, 0x82, 0x37, 0x3a, 0x51, + 0x69, 0x26, 0x38, 0x52, 0x9e, 0x9a, 0x4f, 0xa7, + 0x43, 0x10, 0x80, 0xee, 0x3d, 0x59, 0x35, 0xcf, + 0x79, 0x74, 0xb5, 0xa2, 0xb1, 0x96, 0x23, 0xe0, + 0xbe, 0x05, 0xf5, 0x6e, 0x19, 0xc5, 0x66, 0x49, + 0xf0, 0xd1, 0x54, 0xa9, 0x70, 0x4b, 0xa4, 0xe2, + 0xe6, 0xe5, 0xab, 0xe4, 0xd2, 0xaa, 0x4c, 0xe3, + 0x06, 0x6f, 0xc6, 0x4a, 0xa4, 0x75, 0x97, 0xe1 +}; + +static void fix_pixel_bothsides(int x, int y); + +static void put_virt_pixel(int x, int y, int res) { + uint8 *p; + int width = _WIDTH * res; + + if (x < 0 || y < 0 || x >= width || y >= _HEIGHT) + return; + + p = +#ifdef USE_HIRES + res > 1 ? &game.hires[y * width + x] : +#endif + &game.sbuf[y * width + x]; + + if (pri_on) + *p = (pri_colour << 4) | (*p & 0x0f); + if (scr_on) + *p = scr_colour | (*p & 0xf0); +} + +/* For the flood fill routines */ + +/* MH2 needs stack size > 300 */ +#define STACK_SIZE 512 +static unsigned int stack_ptr; +static uint16 stack[STACK_SIZE]; + +static INLINE void _PUSH(uint16 c) { + assert(stack_ptr < STACK_SIZE); + + stack[stack_ptr] = c; + stack_ptr++; +} + +static INLINE uint16 _POP() { + if (stack_ptr == 0) + return 0xffff; + + stack_ptr--; + return stack[stack_ptr]; +} + +/** + * 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 + * @param res horizontal resolution multiplier + */ +static void draw_line(int x1, int y1, int x2, int y2, int res) { + int i, x, y, deltaX, deltaY, stepX, stepY, errorX, errorY, detdelta; + int width = _WIDTH * res; + + /* CM: Do clipping */ +#define clip(x, y) if((x)>=(y)) (x)=(y) + clip(x1, width - 1); + clip(x2, width - 1); + clip(y1, _HEIGHT - 1); + clip(y2, _HEIGHT - 1); + + /* Vertical line */ + + if (x1 == x2) { + if (y1 > y2) { + y = y1; + y1 = y2; + y2 = y; + } + + for (; y1 <= y2; y1++) { + put_virt_pixel(x1, y1, res); +#ifdef USE_HIRES + if (res > 1) + fix_pixel_bothsides(x1, y1); +#endif + } + + return; + } + + /* Horizontal line */ + + if (y1 == y2) { + if (x1 > x2) { + x = x1; + x1 = x2; + x2 = x; + } +#ifdef USE_HIRES + if (res > 1) + fix_pixel_bothsides(x1, y1); +#endif + + for (; x1 <= x2; x1++) + put_virt_pixel(x1, y1, res); + +#ifdef USE_HIRES + if (res > 1) { + put_virt_pixel(x1, y1, res); + fix_pixel_bothsides(x1, y1); + } +#endif + + 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; + } + + put_virt_pixel(x, y, res); +#ifdef USE_HIRES + if (res > 1) + fix_pixel_bothsides(x, y); +#endif + + do { + errorY += deltaY; + if (errorY >= detdelta) { + errorY -= detdelta; + y += stepY; + } + + errorX += deltaX; + if (errorX >= detdelta) { + errorX -= detdelta; + x += stepX; + } + + put_virt_pixel(x, y, res); +#ifdef USE_HIRES + if (res > 1) + fix_pixel_bothsides(x, y); +#endif + i--; + } while (i > 0); + +#ifdef USE_HIRES + if (res > 1) { + put_virt_pixel(x, y, res); + fix_pixel_bothsides(x, y); + } +#endif +} + +/** + * Draw a relative AGI line. + * Draws short lines relative to last position. (drawing action 0xF7) + * @param res horizontal resolution multiplier + */ +static void dynamic_draw_line(int res) { + int x1, y1, disp, dx, dy; + + x1 = next_byte * res; + y1 = next_byte; + + put_virt_pixel(x1, y1, res); + + while (42) { + if ((disp = next_byte) >= 0xf0) + break; + + dx = ((disp & 0xf0) >> 4) & 0x0f; + dy = (disp & 0x0f); + + if (dx & 0x08) + dx = -(dx & 0x07); + if (dy & 0x08) + dy = -(dy & 0x07); + + dx *= res; + + draw_line(x1, y1, x1 + dx, y1 + dy, res); + x1 += dx; + y1 += dy; + } + foffs--; +} + +/************************************************************************** +** absoluteLine +** +** Draws long lines to actual locations (cf. relative) (drawing action 0xF6) +**************************************************************************/ +static void absolute_draw_line(int res) { + int x1, y1, x2, y2; + + x1 = next_byte * res; + y1 = next_byte; + put_virt_pixel(x1, y1, res); + + while (42) { + if ((x2 = next_byte) >= 0xf0) + break; + + if ((y2 = next_byte) >= 0xf0) + break; + + x2 *= res; + + draw_line(x1, y1, x2, y2, res); + x1 = x2; + y1 = y2; + } + foffs--; +} + +/************************************************************************** +** okToFill +**************************************************************************/ +static INLINE int is_ok_fill_here(int x, int y) { + uint8 p; + + if (x < 0 || x >= _WIDTH || y < 0 || y >= _HEIGHT) + return false; + + if (!scr_on && !pri_on) + return false; + + p = game.sbuf[y * _WIDTH + x]; + + if (!pri_on && scr_on && scr_colour != 15) + return (p & 0x0f) == 15; + + if (pri_on && !scr_on && pri_colour != 4) + return (p >> 4) == 4; + + return (scr_on && (p & 0x0f) == 15 && scr_colour != 15); +} + +/************************************************************************** +** agi_fill +**************************************************************************/ +static void fill_scanline(int x, int y) { + unsigned int c; + int newspan_up, newspan_down; + + if (!is_ok_fill_here(x, y)) + return; + + /* Scan for left border */ + for (c = x - 1; is_ok_fill_here(c, y); c--); + + newspan_up = newspan_down = 1; + for (c++; is_ok_fill_here(c, y); c++) { + put_virt_pixel(c, y, 1); + if (is_ok_fill_here(c, y - 1)) { + if (newspan_up) { + _PUSH(c + 320 * (y - 1)); + newspan_up = 0; + } + } else { + newspan_up = 1; + } + + if (is_ok_fill_here(c, y + 1)) { + if (newspan_down) { + _PUSH(c + 320 * (y + 1)); + newspan_down = 0; + } + } else { + newspan_down = 1; + } + } +} + +static void agi_fill(unsigned int x, unsigned int y) { + _PUSH(x + 320 * y); + + while (42) { + uint16 c = _POP(); + + /* Exit if stack is empty */ + if (c == 0xffff) + break; + + x = c % 320; + y = c / 320; + + fill_scanline(x, y); + } + + stack_ptr = 0; +} + +/************************************************************************** +** xCorner +** +** Draws an xCorner (drawing action 0xF5) +**************************************************************************/ +static void x_corner(int res) { + int x1, x2, y1, y2; + + x1 = next_byte * res; + y1 = next_byte; + put_virt_pixel(x1, y1, res); + + while (42) { + x2 = next_byte; + + if (x2 >= 0xf0) + break; + + x2 *= res; + + draw_line(x1, y1, x2, y1, res); + x1 = x2; + y2 = next_byte; + + if (y2 >= 0xf0) + break; + + draw_line(x1, y1, x1, y2, res); + y1 = y2; + } + foffs--; +} + +/************************************************************************** +** yCorner +** +** Draws an yCorner (drawing action 0xF4) +**************************************************************************/ +static void y_corner(int res) { + int x1, x2, y1, y2; + + x1 = next_byte * res; + y1 = next_byte; + put_virt_pixel(x1, y1, res); + + while (42) { + y2 = next_byte; + + if (y2 >= 0xF0) + break; + + draw_line(x1, y1, x1, y2, res); + y1 = y2; + x2 = next_byte; + + if (x2 >= 0xf0) + break; + + x2 *= res; + + draw_line(x1, y1, x2, y1, res); + x1 = x2; + } + + foffs--; +} + +/************************************************************************** +** fill +** +** AGI flood fill. (drawing action 0xF8) +**************************************************************************/ +static void fill() { + int x1, y1; + + while ((x1 = next_byte) < 0xF0 && (y1 = next_byte) < 0xf0) + agi_fill(x1, y1); + + foffs--; +} + +/************************************************************************** +** plotPattern +** +** Draws pixels, circles, squares, or splatter brush patterns depending +** on the pattern code. +**************************************************************************/ + +static int plot_pattern_point(int x, int y, int bitpos, int res) { + if (pat_code & 0x20) { + if ((splatter_map[bitpos >> 3] >> (7 - (bitpos & 7))) & 1) { +#ifdef USE_HIRES + if (res > 1) { + /* extra randomness in hi-res brush fill + */ + if (rnd->getRandomNumber(3)) + put_virt_pixel(x * 2, y, 2); + if (!rnd->getRandomNumber(3)) + put_virt_pixel(x * 2 + 1, y, 2); + } else +#endif + { + put_virt_pixel(x, y, 1); + } + } + bitpos++; + if (bitpos == 0xff) + bitpos = 0; + } else { +#ifdef USE_HIRES + if (res > 1) { + /* double width pixels make MUMG and others + * look nicer + */ + put_virt_pixel(x * 2, y, 2); + put_virt_pixel(x * 2 + 1, y, 2); + } else +#endif + { + put_virt_pixel(x, y, 1); + } + } + + return bitpos; +} + +static void plot_pattern(int x, int y, int res) { + int32 circlePos = 0; + uint32 x1, y1, pensize, bitpos = splatter_start[pat_num]; + + pensize = (pat_code & 7); + + if (x < (int)pensize) + x = pensize - 1; + if (y < (int)pensize) + y = pensize; + + for (y1 = y - pensize; y1 <= y + pensize; y1++) { + for (x1 = x - (pensize + 1) / 2; x1 <= x + pensize / 2; x1++) { + if (pat_code & 0x10) { /* Square */ + bitpos = plot_pattern_point (x1, y1, bitpos, res); + } else { /* Circle */ + if ((circles[pat_code & 7][circlePos >> 3] >> (7 - (circlePos & 7))) & 1) { + bitpos = plot_pattern_point(x1, y1, bitpos, res); + } + circlePos++; + } + } + } +} + +/************************************************************************** +** plotBrush +** +** Plots points and various brush patterns. +**************************************************************************/ +static void plot_brush(int res) { + int x1, y1; + + while (42) { + if (pat_code & 0x20) { + if ((pat_num = next_byte) >= 0xF0) + break; + pat_num = (pat_num >> 1) & 0x7f; + } + + if ((x1 = next_byte) >= 0xf0) + break; + + if ((y1 = next_byte) >= 0xf0) + break; + + plot_pattern(x1, y1, res); + } + + foffs--; +} + +#ifdef USE_HIRES + +static void fix_pixel_bothsides(int x, int y) { + uint8 *p, *s; + + if (x >= (_WIDTH * 2) - 2) + return; + + /* Sometimes a solid color area in the lo-res pic is made + * with lines, and we want to keep this effect in the + * hi-res pic. + */ + p = &game.hires[y * (_WIDTH * 2) + x]; + if ((*(p - 2) & 0x0f) == scr_colour) + put_virt_pixel(x - 1, y, 2); + if ((*(p + 2) & 0x0f) == scr_colour) + put_virt_pixel(x + 1, y, 2); + + /* If two lines are contiguous in the lo-res pic, make them + * contiguous in the hi-res pic. This condition is needed + * in some scenes like in front of Lefty's in LSL1, to draw + * the pole. Note: it adds artifacts in some cases. + */ + s = &game.sbuf[y * _WIDTH + x / 2]; + if ((*(p - 1) & 0x0f) != (*(s - 1) & 0x0f)) + put_virt_pixel(x - 1, y, 2); +} + +/************************************************************************** +** okToFill +**************************************************************************/ +static INLINE int hires_fill_here(int x, int y) { + uint8 *p, *s; + + if (x < 0 || x >= _WIDTH || y < 0 || y >= _HEIGHT) + return false; + + if (!scr_on && !pri_on) + return false; + + p = &game.hires[(int32) y * (_WIDTH * 2) + x * 2]; + s = &game.sbuf[y * _WIDTH + x]; + + if (scr_on) { + if (scr_colour == 0x0f) + return false; + if ((*p & 0x0f) != 0x0f || (*(p + 1) & 0x0f) != 0x0f) + return false; + if ((*s & 0x0f) != scr_colour) + return false; + } + + if (pri_on) { + if (pri_colour == 0x04) + return false; + if ((*p >> 4) != 0x04 || (*(p + 1) >> 4) != 0x04) + return false; + if ((*s >> 4) != pri_colour) + return false; + } + + return true; +} + +static void fix_pixel_left(int x, int y) { + uint8 *p; + + if (!scr_on) + return; + + p = &game.hires[y * (_WIDTH * 2) + x * 2 + 1]; + if ((*p & 0x0f) == 0x0f) + put_virt_pixel(2 * x + 1, y, 2); + else if ((*p & 0x0f) == (*(p - 1) & 0x0f)) + put_virt_pixel(2 * x + 1, y, 2); +} + +static void fix_pixel_right(int x, int y) { + int idx = y * (_WIDTH * 2) + x * 2; + + if (idx >= 160 * 168) + return; + + if (scr_on && (game.hires[idx] & 0x0f) == 0x0f) + put_virt_pixel(2 * x, y, 2); +} + +static void fix_pixel_here(int x, int y) { + uint8 p; + + p = game.hires[y * (_WIDTH * 2) + x * 2 + 1]; + if (scr_on && (p & 0x0f) == 0x0f) + put_virt_pixel(2 * x + 1, y, 2); +} + +/************************************************************************** +** agiFill +**************************************************************************/ +static void hires_fill_scanline(int x, int y) { + unsigned int c; + int newspan_up, newspan_down; + + if (!hires_fill_here(x, y)) + return; + + /* Scan for left border */ + for (c = x - 1; c > 0 && hires_fill_here(c, y); c--); + fix_pixel_left(c, y); + + newspan_up = newspan_down = 1; + for (c++; hires_fill_here(c, y); c++) { + put_virt_pixel(c * 2, y, 2); + fix_pixel_here(c, y); + + if (hires_fill_here(c, y - 1)) { + if (newspan_up) { + _PUSH(c + 320 * (y - 1)); + newspan_up = 0; + } + } else { + newspan_up = 1; + } + + if (hires_fill_here(c, y + 1)) { + if (newspan_down) { + _PUSH(c + 320 * (y + 1)); + newspan_down = 0; + } + } else { + newspan_down = 1; + } + } + + fix_pixel_right(c, y); +} + +static void _hires_fill(unsigned int x, unsigned int y) { + _PUSH(x + 320 * y); + + while (42) { + uint16 c = _POP(); + + /* Exit if stack is empty */ + if (c == 0xffff) + break; + + x = c % 320; + y = c / 320; + + hires_fill_scanline(x, y); + } + + stack_ptr = 0; +} + +/************************************************************************** +** fill +** +** AGI flood fill. (drawing action 0xF8) +**************************************************************************/ +static void hires_fill() { + int x1, y1; + + while ((x1 = next_byte) < 0xf0 && (y1 = next_byte) < 0xf0) { + _hires_fill(x1, y1); + } + + foffs--; +} + +/** + * Show AGI picture. + * This function copies a ``hidden'' AGI picture to the output device. + */ +void show_hires_pic() { + int y, offset; + int32 i; + + i = 0; + offset = game.line_min_print * CHAR_LINES; + for (y = 0; y < _HEIGHT; y++) { + put_pixels_hires(0, y + offset, _WIDTH * 2, &game.hires[i]); + i += _WIDTH * 2; + } + + flush_screen(); +} + +void fix_hires_picture() { + uint8 *p, *b; + int i; + + p = game.hires; + b = game.sbuf; + + for (i = 0; p < &game.hires[_WIDTH * _HEIGHT * 2] - 1; p++, i++) { + if ((*p & 0x0f) == 0x0f && (*b & 0x0f) != 0x0f) { + if ((*(p + 1) & 0x0f) != 0x0f) + *p = *(p + 1); + else + *p = *b; + } + if ((*p >> 4) == 4 && (*b >> 4) != 4 && (*(b + 1) >> 4) != 4) { + *p = (*p & 0x0f) | (*b & 0xf0); + } + b += (i & 1); + } +} + +#endif /* USE_HIRES */ + +static void draw_picture() { + uint8 act; + int drawing; +#ifdef USE_HIRES + int save_foffs; +#endif + + pat_code = 0; + pat_num = 0; + pri_on = scr_on = false; + scr_colour = 0xf; + pri_colour = 0x4; + + drawing = 1; + + debugC(8, kDebugLevelMain, "Drawing picture"); + for (drawing = 1; drawing && foffs < flen;) { + +#ifdef USE_HIRES + save_foffs = foffs; +#endif + + act = next_byte; + switch (act) { + case 0xf0: /* set colour on screen */ + scr_colour = next_byte; + scr_colour &= 0xF; /* for v3 drawing diff */ + scr_on = true; + break; + case 0xf1: /* disable screen drawing */ + scr_on = false; + break; + case 0xf2: /* set colour on priority */ + pri_colour = next_byte; + pri_colour &= 0xf; /* for v3 drawing diff */ + pri_on = true; + break; + case 0xf3: /* disable priority screen */ + pri_on = false; + break; + case 0xf4: /* y-corner */ + y_corner(1); + break; + case 0xf5: /* x-corner */ + x_corner(1); + break; + case 0xf6: /* absolute draw lines */ + absolute_draw_line(1); + break; + case 0xf7: /* dynamic draw lines */ + dynamic_draw_line(1); + break; + case 0xf8: /* fill */ + fill(); + break; + case 0xf9: /* set pattern */ + pat_code = next_byte; + break; + case 0xfA: /* plot brush */ + plot_brush(1); + break; + case 0xFF: /* end of pic data */ + default: + drawing = 0; + break; + } + +#ifdef USE_HIRES + foffs = save_foffs; + + act = next_byte; + switch (act) { + case 0xf0: /* set colour on screen */ + scr_colour = next_byte; + scr_colour &= 0xF; /* for v3 drawing diff */ + scr_on = true; + break; + case 0xf1: /* disable screen drawing */ + scr_on = false; + break; + case 0xf2: /* set colour on priority */ + pri_colour = next_byte; + pri_colour &= 0xf; /* for v3 drawing diff */ + pri_on = true; + break; + case 0xf3: /* disable priority screen */ + pri_on = false; + break; + case 0xf4: /* y-corner */ + y_corner(2); + break; + case 0xf5: /* x-corner */ + x_corner(2); + break; + case 0xf6: /* absolute draw lines */ + absolute_draw_line(2); + break; + case 0xf7: /* dynamic draw lines */ + dynamic_draw_line(2); + break; + case 0xf8: /* fill */ + hires_fill(); + break; + case 0xf9: /* set pattern */ + pat_code = next_byte; + break; + case 0xfA: /* plot brush */ + plot_brush(2); + break; + case 0xFF: /* end of pic data */ + default: + drawing = 0; + break; + } +#endif + } +} + +/* + * Public functions + */ + +/** + * + */ +uint8 *convert_v3_pic(uint8 *data, uint32 len) { + uint8 d, old = 0, x, *in, *xdata, *out, mode = 0; + uint32 i, ulen; + + xdata = (uint8 *) malloc(len + len / 2); + + out = xdata; + in = data; + + for (i = ulen = 0; i < len; i++, ulen++) { + d = *in++; + + *out++ = x = mode ? ((d & 0xF0) >> 4) + ((old & 0x0F) << 4) : d; + + if (x == 0xFF) { + ulen++; + break; + } + + if (x == 0xf0 || x == 0xf2) { + if (mode) { + *out++ = d & 0x0F; + ulen++; + } else { + d = *in++; + *out++ = (d & 0xF0) >> 4; + i++, ulen++; + } + + mode = !mode; + } + + old = d; + } + + free(data); + xdata = (uint8 *)realloc(xdata, ulen); + + return xdata; +} + +/** + * Decode an AGI picture resource. + * This function decodes an AGI picture resource into the correct slot + * and draws it on the AGI screen, optionally cleaning the screen before + * drawing. + * @param n AGI picture resource number + * @param clear clear AGI screen before drawing + */ +int decode_picture(int n, int clear) { + debugC(8, kDebugLevelResources, "(%d)", n); + + pat_code = 0; + pat_num = 0; + pri_on = scr_on = false; + scr_colour = 0xF; + pri_colour = 0x4; + + data = game.pictures[n].rdata; + flen = game.dir_pic[n].len; + foffs = 0; + + if (clear) { + memset(game.sbuf, 0x4f, _WIDTH * _HEIGHT); +#ifdef USE_HIRES + memset(game.hires, 0x4f, _WIDTH * 2 * _HEIGHT); +#endif + } + + draw_picture(); + +#ifdef USE_HIRES + fix_hires_picture(); +#endif + + if (clear) + clear_image_stack(); + record_image_stack_call(ADD_PIC, n, clear, 0, 0, 0, 0, 0); + + return err_OK; +} + +/** + * Unload an AGI picture resource. + * This function unloads an AGI picture resource and deallocates + * resource data. + * @param n AGI picture resource number + */ +int unload_picture(int n) { + /* remove visual buffer & priority buffer if they exist */ + if (game.dir_pic[n].flags & RES_LOADED) { + free(game.pictures[n].rdata); + game.dir_pic[n].flags &= ~RES_LOADED; + } + + return err_OK; +} + +/** + * Show AGI picture. + * This function copies a ``hidden'' AGI picture to the output device. + */ +void show_pic() { + int i, y; + int offset; + + debugC(8, kDebugLevelMain, "Show picture!"); +#ifdef USE_HIRES + if (opt.hires) { + show_hires_pic(); + return; + } +#endif + + i = 0; + offset = game.line_min_print * CHAR_LINES; + for (y = 0; y < _HEIGHT; y++) { + put_pixels_a(0, y + offset, _WIDTH, &game.sbuf[i]); + i += _WIDTH; + } + + flush_screen(); +} + +} // End of namespace Agi diff --git a/engines/agi/picture.h b/engines/agi/picture.h new file mode 100644 index 0000000000..0828cdecc1 --- /dev/null +++ b/engines/agi/picture.h @@ -0,0 +1,47 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2006 The ScummVM project + * + * Copyright (C) 1999-2001 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$ + * + */ + +#ifndef __AGI_PICTURE_H +#define __AGI_PICTURE_H + +#include "agi/agi.h" + +namespace Agi { + +/** + * AGI picture resource. + */ +struct agi_picture { + uint32 flen; /**< size of raw data */ + uint8 *rdata; /**< raw vector image data */ +}; + +int decode_picture(int, int); +int unload_picture(int); +void show_pic(void); +uint8 *convert_v3_pic(uint8 *data, uint32 len); + +} // End of namespace Agi + +#endif /* __AGI_PICTURE_H */ diff --git a/engines/agi/savegame.cpp b/engines/agi/savegame.cpp new file mode 100644 index 0000000000..e4efc4402f --- /dev/null +++ b/engines/agi/savegame.cpp @@ -0,0 +1,790 @@ +/* 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$ + * + */ + +/* + * Savegame support by Vasyl Tsvirkunov + * Multi-slots by Claudio Matsuoka + */ + +#include "common/stdafx.h" + +#include "agi/agi.h" +#include "agi/graphics.h" +#include "agi/sprite.h" +#include "agi/text.h" +#include "agi/savegame.h" +#include "agi/keyboard.h" +#include "agi/menu.h" + +namespace Agi { + +#if defined(WIN32) +#define MKDIR(a,b) mkdir(a) +#else +#define MKDIR(a,b) mkdir(a,b) +#endif + +/* Image stack support */ +struct image_stack_element { + uint8 type; + uint8 pad; + int16 parm1; + int16 parm2; + int16 parm3; + int16 parm4; + int16 parm5; + int16 parm6; + int16 parm7; +}; + +#define INITIAL_IMAGE_STACK_SIZE 32 +static int stack_size = 0; +static struct image_stack_element *image_stack = NULL; +static int image_stack_pointer = 0; + +void clear_image_stack(void) { + image_stack_pointer = 0; +} + +void release_image_stack(void) { + if (image_stack) + free(image_stack); + image_stack = NULL; + stack_size = image_stack_pointer = 0; +} + +void record_image_stack_call(uint8 type, int16 p1, int16 p2, int16 p3, + int16 p4, int16 p5, int16 p6, int16 p7) { + struct image_stack_element *pnew; + + if (image_stack_pointer == stack_size) { + if (stack_size == 0) { /* first call */ + image_stack = (struct image_stack_element *) + malloc(INITIAL_IMAGE_STACK_SIZE * sizeof(struct image_stack_element)); + stack_size = INITIAL_IMAGE_STACK_SIZE; + } else { /* has to grow */ + struct image_stack_element *new_stack; + new_stack = (struct image_stack_element *) + malloc(2 * stack_size * sizeof(struct image_stack_element)); + memcpy(new_stack, image_stack, stack_size * sizeof(struct image_stack_element)); + free(image_stack); + image_stack = new_stack; + stack_size *= 2; + } + } + + pnew = &image_stack[image_stack_pointer]; + image_stack_pointer++; + + pnew->type = type; + pnew->parm1 = p1; + pnew->parm2 = p2; + pnew->parm3 = p3; + pnew->parm4 = p4; + pnew->parm5 = p5; + pnew->parm6 = p6; + pnew->parm7 = p7; +} + +void replay_image_stack_call(uint8 type, int16 p1, int16 p2, int16 p3, + int16 p4, int16 p5, int16 p6, int16 p7) { + switch (type) { + case ADD_PIC: + debugC(8, kDebugLevelMain, "--- decoding picture %d ---", p1); + agi_load_resource(rPICTURE, p1); + decode_picture(p1, p2); + break; + case ADD_VIEW: + agi_load_resource(rVIEW, p1); + add_to_pic(p1, p2, p3, p4, p5, p6, p7); + break; + } +} + +/* */ + +static char *strSig = "AGI:"; + +static void write_uint8(Common::File *f, int8 val) { + f->write(&val, 1); +} + +static void write_sint16(Common::File *f, int16 val) { + static uint8 buf[2]; + buf[0] = (uint8) ((val >> 8) & 255); + buf[1] = (uint8) (val & 255); + f->write(buf, 2); +} + +static void write_uint16(Common::File *f, uint16 val) { + static uint8 buf[2]; + buf[0] = (uint8) ((val >> 8) & 255); + buf[1] = (uint8) (val & 255); + f->write(buf, 2); +} + +static uint8 read_uint8(Common::File *f) { + static uint8 buf[1]; + f->read(buf, 1); + return buf[0]; +} + +static uint16 read_uint16(Common::File *f) { + static uint8 buf[2]; + f->read(buf, 2); + return (buf[0] << 8) | buf[1]; +} + +static int16 read_sint16(Common::File *f) { + static uint8 buf[2]; + f->read(buf, 2); + return (int16) ((buf[0] << 8) | buf[1]); +} + +static void write_string(Common::File *f, char *s) { + write_sint16(f, (int16) strlen(s)); + f->write(s, strlen(s)); +} + +static void read_string(Common::File *f, char *s) { + int16 size = read_sint16(f); + f->read(s, size); + s[size] = (char)0; +} + +static void write_bytes(Common::File *f, char *s, int16 size) { + f->write(s, size); +} + +static void read_bytes(Common::File *f, char *s, int16 size) { + f->read(s, size); +} + +/* + * Version 0: view table has 64 entries + * Version 1: view table has 256 entries (needed in KQ3) + */ +#define SAVEGAME_VERSION 1 + +int save_game(char *s, char *d) { + int16 i; + struct image_stack_element *ptr = image_stack; + Common::File f; + + f.open(s, Common::File::kFileWriteMode); + + if (!f.isOpen()) + return err_BadFileOpen; + + write_bytes(&f, strSig, 8); + write_string(&f, d); + + write_uint8(&f, (uint8) SAVEGAME_VERSION); + write_uint8(&f, (uint8) game.state); + /* game.name */ + write_string(&f, game.id); + /* game.crc */ + + for (i = 0; i < MAX_FLAGS; i++) + write_uint8(&f, game.flags[i]); + for (i = 0; i < MAX_VARS; i++) + write_uint8(&f, game.vars[i]); + + write_sint16(&f, (int8) game.horizon); + write_sint16(&f, (int16) game.line_status); + write_sint16(&f, (int16) game.line_user_input); + write_sint16(&f, (int16) game.line_min_print); + /* game.cursor_pos */ + /* game.input_buffer */ + /* game.echo_buffer */ + /* game.keypress */ + write_sint16(&f, (int16) game.input_mode); + write_sint16(&f, (int16) game.lognum); + + write_sint16(&f, (int16) game.player_control); + write_sint16(&f, (int16) game.quit_prog_now); + write_sint16(&f, (int16) game.status_line); + write_sint16(&f, (int16) game.clock_enabled); + write_sint16(&f, (int16) game.exit_all_logics); + write_sint16(&f, (int16) game.picture_shown); + write_sint16(&f, (int16) game.has_prompt); + write_sint16(&f, (int16) game.game_flags); + + /* Reversed to keep compatibility with old savegames */ + write_sint16(&f, (int16)!game.input_enabled); + + for (i = 0; i < _HEIGHT; i++) + write_uint8(&f, game.pri_table[i]); + + /* game.msg_box_ticks */ + /* game.block */ + /* game.window */ + /* game.has_window */ + + write_sint16(&f, (int16)game.gfx_mode); + write_uint8(&f, game.cursor_char); + write_sint16(&f, (int16)game.color_fg); + write_sint16(&f, (int16)game.color_bg); + + /* game.hires (#ifdef USE_HIRES) */ + /* game.sbuf */ + /* game.ego_words */ + /* game.num_ego_words */ + + write_sint16(&f, (int16)game.num_objects); + for (i = 0; i < (int16)game.num_objects; i++) + write_sint16(&f, (int16)object_get_location(i)); + + /* game.ev_keyp */ + for (i = 0; i < MAX_STRINGS; i++) + write_string(&f, game.strings[i]); + + /* record info about loaded resources */ + for (i = 0; i < MAX_DIRS; i++) { + write_uint8(&f, game.dir_logic[i].flags); + write_sint16(&f, (int16)game.logics[i].sIP); + write_sint16(&f, (int16)game.logics[i].cIP); + } + for (i = 0; i < MAX_DIRS; i++) + write_uint8(&f, game.dir_pic[i].flags); + for (i = 0; i < MAX_DIRS; i++) + write_uint8(&f, game.dir_view[i].flags); + for (i = 0; i < MAX_DIRS; i++) + write_uint8(&f, game.dir_sound[i].flags); + + /* game.pictures */ + /* game.logics */ + /* game.views */ + /* game.sounds */ + + for (i = 0; i < MAX_VIEWTABLE; i++) { + struct vt_entry *v = &game.view_table[i]; + + write_uint8(&f, v->step_time); + write_uint8(&f, v->step_time_count); + write_uint8(&f, v->entry); + write_sint16(&f, v->x_pos); + write_sint16(&f, v->y_pos); + write_uint8(&f, v->current_view); + + /* v->view_data */ + + write_uint8(&f, v->current_loop); + write_uint8(&f, v->num_loops); + + /* v->loop_data */ + + write_uint8(&f, v->current_cel); + write_uint8(&f, v->num_cels); + + /* v->cel_data */ + /* v->cel_data_2 */ + + write_sint16(&f, v->x_pos2); + write_sint16(&f, v->y_pos2); + + /* v->s */ + + write_sint16(&f, v->x_size); + write_sint16(&f, v->y_size); + write_uint8(&f, v->step_size); + write_uint8(&f, v->cycle_time); + write_uint8(&f, v->cycle_time_count); + write_uint8(&f, v->direction); + + write_uint8(&f, v->motion); + write_uint8(&f, v->cycle); + write_uint8(&f, v->priority); + + write_uint16(&f, v->flags); + + write_uint8(&f, v->parm1); + write_uint8(&f, v->parm2); + write_uint8(&f, v->parm3); + write_uint8(&f, v->parm4); + } + + /* Save image stack */ + + for (i = 0; i < image_stack_pointer; i++) { + ptr = &image_stack[i]; + write_uint8(&f, ptr->type); + write_sint16(&f, ptr->parm1); + write_sint16(&f, ptr->parm2); + write_sint16(&f, ptr->parm3); + write_sint16(&f, ptr->parm4); + write_sint16(&f, ptr->parm5); + write_sint16(&f, ptr->parm6); + write_sint16(&f, ptr->parm7); + } + write_uint8(&f, 0); + + f.close(); + + return err_OK; +} + +int load_game(char *s) { + int i, ver, vt_entries = MAX_VIEWTABLE; + uint8 t; + int16 parm[7]; + char sig[8]; + char id[8]; + char description[256]; + Common::File f; + + f.open(s); + + if (!f.isOpen()) + return err_BadFileOpen; + + read_bytes(&f, sig, 8); + if (strncmp(sig, strSig, 8)) { + f.close(); + return err_BadFileOpen; + } + + read_string(&f, description); + + ver = read_uint8(&f); + if (ver == 0) + vt_entries = 64; + game.state = read_uint8(&f); + /* game.name - not saved */ + read_string(&f, id); + if (strcmp(id, game.id)) { + f.close(); + return err_BadFileOpen; + } + /* game.crc - not saved */ + + for (i = 0; i < MAX_FLAGS; i++) + game.flags[i] = read_uint8(&f); + for (i = 0; i < MAX_VARS; i++) + game.vars[i] = read_uint8(&f); + + game.horizon = read_sint16(&f); + game.line_status = read_sint16(&f); + game.line_user_input = read_sint16(&f); + game.line_min_print = read_sint16(&f); + + /* These are never saved */ + game.cursor_pos = 0; + game.input_buffer[0] = 0; + game.echo_buffer[0] = 0; + game.keypress = 0; + + game.input_mode = read_sint16(&f); + game.lognum = read_sint16(&f); + + game.player_control = read_sint16(&f); + game.quit_prog_now = read_sint16(&f); + game.status_line = read_sint16(&f); + game.clock_enabled = read_sint16(&f); + game.exit_all_logics = read_sint16(&f); + game.picture_shown = read_sint16(&f); + game.has_prompt = read_sint16(&f); + game.game_flags = read_sint16(&f); + game.input_enabled = !read_sint16(&f); + + for (i = 0; i < _HEIGHT; i++) + game.pri_table[i] = read_uint8(&f); + + if (game.has_window) + close_window(); + game.msg_box_ticks = 0; + game.block.active = false; + /* game.window - fixed by close_window() */ + /* game.has_window - fixed by close_window() */ + + game.gfx_mode = read_sint16(&f); + game.cursor_char = read_uint8(&f); + game.color_fg = read_sint16(&f); + game.color_bg = read_sint16(&f); + + /* game.hires (#ifdef USE_HIRES) - rebuilt from image stack */ + /* game.sbuf - rebuilt from image stack */ + + /* game.ego_words - fixed by clean_input */ + /* game.num_ego_words - fixed by clean_input */ + + game.num_objects = read_sint16(&f); + for (i = 0; i < (int16) game.num_objects; i++) + object_set_location(i, read_sint16(&f)); + + /* Those are not serialized */ + for (i = 0; i < MAX_DIRS; i++) { + game.ev_keyp[i].occured = false; + } + + for (i = 0; i < MAX_STRINGS; i++) + read_string(&f, game.strings[i]); + + for (i = 0; i < MAX_DIRS; i++) { + if (read_uint8(&f) & RES_LOADED) + agi_load_resource(rLOGIC, i); + else + agi_unload_resource(rLOGIC, i); + game.logics[i].sIP = read_sint16(&f); + game.logics[i].cIP = read_sint16(&f); + } + + for (i = 0; i < MAX_DIRS; i++) { + if (read_uint8(&f) & RES_LOADED) + agi_load_resource(rPICTURE, i); + else + agi_unload_resource(rPICTURE, i); + } + + for (i = 0; i < MAX_DIRS; i++) { + if (read_uint8(&f) & RES_LOADED) + agi_load_resource(rVIEW, i); + else + agi_unload_resource(rVIEW, i); + } + + for (i = 0; i < MAX_DIRS; i++) { + if (read_uint8(&f) & RES_LOADED) + agi_load_resource(rSOUND, i); + else + agi_unload_resource(rSOUND, i); + } + + /* game.pictures - loaded above */ + /* game.logics - loaded above */ + /* game.views - loaded above */ + /* game.sounds - loaded above */ + + for (i = 0; i < vt_entries; i++) { + struct vt_entry *v = &game.view_table[i]; + + v->step_time = read_uint8(&f); + v->step_time_count = read_uint8(&f); + v->entry = read_uint8(&f); + v->x_pos = read_sint16(&f); + v->y_pos = read_sint16(&f); + v->current_view = read_uint8(&f); + + /* v->view_data - fixed below */ + + v->current_loop = read_uint8(&f); + v->num_loops = read_uint8(&f); + + /* v->loop_data - fixed below */ + + v->current_cel = read_uint8(&f); + v->num_cels = read_uint8(&f); + + /* v->cel_data - fixed below */ + /* v->cel_data_2 - fixed below */ + + v->x_pos2 = read_sint16(&f); + v->y_pos2 = read_sint16(&f); + + /* v->s - fixed below */ + + v->x_size = read_sint16(&f); + v->y_size = read_sint16(&f); + v->step_size = read_uint8(&f); + v->cycle_time = read_uint8(&f); + v->cycle_time_count = read_uint8(&f); + v->direction = read_uint8(&f); + + v->motion = read_uint8(&f); + v->cycle = read_uint8(&f); + v->priority = read_uint8(&f); + + v->flags = read_uint16(&f); + + v->parm1 = read_uint8(&f); + v->parm2 = read_uint8(&f); + v->parm3 = read_uint8(&f); + v->parm4 = read_uint8(&f); + } + for (i = vt_entries; i < MAX_VIEWTABLE; i++) { + memset(&game.view_table[i], 0, sizeof(struct vt_entry)); + } + + /* Fix some pointers in viewtable */ + + for (i = 0; i < MAX_VIEWTABLE; i++) { + struct vt_entry *v = &game.view_table[i]; + + if (game.dir_view[v->current_view].offset == _EMPTY) + continue; + + if (!(game.dir_view[v->current_view].flags & RES_LOADED)) + agi_load_resource(rVIEW, v->current_view); + + set_view(v, v->current_view); /* Fix v->view_data */ + set_loop(v, v->current_loop); /* Fix v->loop_data */ + set_cel(v, v->current_cel); /* Fix v->cel_data */ + v->cel_data_2 = v->cel_data; + v->s = NULL; /* not sure if it is used... */ + } + + erase_both(); + + /* Clear input line */ + clear_screen(0); + write_status(); + + /* Recreate background from saved image stack */ + clear_image_stack(); + while ((t = read_uint8(&f)) != 0) { + for (i = 0; i < 7; i++) + parm[i] = read_sint16(&f); + replay_image_stack_call(t, parm[0], parm[1], parm[2], + parm[3], parm[4], parm[5], parm[6]); + } + + f.close(); + + setflag(F_restore_just_ran, true); + + game.has_prompt = 0; /* force input line repaint if necessary */ + clean_input(); + + erase_both(); + blit_both(); + commit_both(); + show_pic(); + do_update(); + + return err_OK; +} + +#define NUM_SLOTS 12 + +static int select_slot() { + int i, key, active = 0; + int rc = -1; + int hm = 2, vm = 3; /* box margins */ + char desc[NUM_SLOTS][40]; + + for (i = 0; i < NUM_SLOTS; i++) { + char name[MAX_PATH]; + Common::File f; + char sig[8]; + sprintf(name, "%s/%05X_%s_%02d.sav", _savePath, game.crc, game.id, i); + f.open(name); + if (!f.isOpen()) { + strcpy(desc[i], " (empty slot)"); + } else { + read_bytes(&f, sig, 8); + if (strncmp(sig, strSig, 8)) { + strcpy(desc[i], "(corrupt file)"); + } else { + read_string(&f, desc[i]); + } + f.close(); + } + } + + while (42) { + char dstr[64]; + for (i = 0; i < NUM_SLOTS; i++) { + sprintf(dstr, "[%-32.32s]", desc[i]); + print_text(dstr, 0, hm + 1, vm + 4 + i, + (40 - 2 * hm) - 1, i == active ? MSG_BOX_COLOUR : MSG_BOX_TEXT, + i == active ? MSG_BOX_TEXT : MSG_BOX_COLOUR); + + } + + poll_timer(); /* msdos driver -> does nothing */ + key = do_poll_keyboard(); + if (!console_keyhandler(key)) { + switch (key) { + case KEY_ENTER: + rc = active; + strncpy(game.strings[MAX_STRINGS], desc[i], MAX_STRINGLEN); + goto press; + case KEY_ESCAPE: + rc = -1; + goto getout; +#ifdef USE_MOUSE + case BUTTON_LEFT: + break; +#endif + case KEY_DOWN: + active++; + active %= NUM_SLOTS; + break; + case KEY_UP: + active--; + if (active < 0) + active = NUM_SLOTS - 1; + break; + } + } + console_cycle(); + } + +press: + debugC(8, kDebugLevelMain | kDebugLevelInput, "Button pressed: %d", rc); + +getout: + close_window(); + return rc; +} + +int savegame_simple() { + char path[MAX_PATH]; + + sprintf(path, "%s/%05X_%s_%02d.sav", _savePath, game.crc, game.id, 0); + save_game(path, "Default savegame"); + + return err_OK; +} + +int savegame_dialog() { + char path[MAX_PATH]; + char *desc; + char *buttons[] = { "Do as I say!", "I regret", NULL }; + char dstr[200]; + int rc, slot = 0; + int hm, vm, hp, vp; /* box margins */ + int w; + + hm = 2; + vm = 3; + hp = hm * CHAR_COLS; + vp = vm * CHAR_LINES; + w = (40 - 2 * hm) - 1; + + sprintf(path, "%s/%05X_%s_%02d.sav", _savePath, game.crc, game.id, slot); + + draw_window(hp, vp, GFX_WIDTH - hp, GFX_HEIGHT - vp); + print_text("Select a slot in which you wish to save the game:", + 0, hm + 1, vm + 1, w, MSG_BOX_TEXT, MSG_BOX_COLOUR); + print_text("Press ENTER to select, ESC cancels", + 0, hm + 1, vm + 17, w, MSG_BOX_TEXT, MSG_BOX_COLOUR); + + slot = select_slot(); + if (slot < 0) /* ESC pressed */ + return err_OK; + + /* Get savegame description */ + draw_window(hp, vp + 5 * CHAR_LINES, GFX_WIDTH - hp, + GFX_HEIGHT - vp - 9 * CHAR_LINES); + print_text("Enter a description for this game:", + 0, hm + 1, vm + 6, w, MSG_BOX_TEXT, MSG_BOX_COLOUR); + draw_rectangle(3 * CHAR_COLS, 11 * CHAR_LINES - 1, + 37 * CHAR_COLS, 12 * CHAR_LINES, MSG_BOX_TEXT); + flush_block(3 * CHAR_COLS, 11 * CHAR_LINES - 1, + 37 * CHAR_COLS, 12 * CHAR_LINES); + + get_string(2, 11, 33, MAX_STRINGS); + print_character(3, 11, game.cursor_char, MSG_BOX_COLOUR, MSG_BOX_TEXT); + do { + main_cycle(); + } while (game.input_mode == INPUT_GETSTRING); + close_window(); + + 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, slot); + + rc = selection_box(dstr, buttons); + + if (rc != 0) { + message_box("Game NOT saved."); + return err_OK; + } + + sprintf(path, "%s/%05X_%s_%02d.sav", _savePath, game.crc, game.id, slot); + debugC(8, kDebugLevelMain | kDebugLevelResources, "file is [%s]", path); + + save_game(path, desc); + + message_box("Game saved."); + + return err_OK; +} + +int loadgame_simple() { + char path[MAX_PATH]; + int rc = 0; + + sprintf(path, "%s/%05X_%s_%02d.sav", _savePath, game.crc, game.id, 0); + + erase_both(); + stop_sound(); + close_window(); + + if ((rc = load_game(path)) == err_OK) { + message_box("Game restored."); + game.exit_all_logics = 1; + menu_enable_all(); + } else { + message_box("Error restoring game."); + } + + return rc; +} + +int loadgame_dialog() { + char path[MAX_PATH]; + int rc, slot = 0; + int hm, vm, hp, vp; /* box margins */ + int w; + + hm = 2; + vm = 3; + hp = hm * CHAR_COLS; + vp = vm * CHAR_LINES; + w = (40 - 2 * hm) - 1; + + sprintf(path, "%s/%05X_%s_%02d.sav", _savePath, game.crc, game.id, slot); + + erase_both(); + stop_sound(); + + draw_window(hp, vp, GFX_WIDTH - hp, GFX_HEIGHT - vp); + print_text("Select a game which you wish to\nrestore:", + 0, hm + 1, vm + 1, w, MSG_BOX_TEXT, MSG_BOX_COLOUR); + print_text("Press ENTER to select, ESC cancels", + 0, hm + 1, vm + 17, w, MSG_BOX_TEXT, MSG_BOX_COLOUR); + + slot = select_slot(); + + if (slot < 0) { + message_box("Game NOT restored."); + return err_OK; + } + + sprintf(path, "%s/%05X_%s_%02d.sav", _savePath, game.crc, game.id, slot); + + if ((rc = load_game(path)) == err_OK) { + message_box("Game restored."); + game.exit_all_logics = 1; + menu_enable_all(); + } else { + message_box("Error restoring game."); + } + + return rc; +} + +} // End of namespace Agi diff --git a/engines/agi/savegame.h b/engines/agi/savegame.h new file mode 100644 index 0000000000..ac62bb66e7 --- /dev/null +++ b/engines/agi/savegame.h @@ -0,0 +1,50 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2006 The ScummVM project + * + * Copyright (C) 1999-2001 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$ + * + */ + +#ifndef __AGI_SAVEGAME_H +#define __AGI_SAVEGAME_H + +#include "agi/agi.h" + +namespace Agi { + +int savegame_dialog(void); +int loadgame_dialog(void); +int savegame_simple(void); +int loadgame_simple(void); + +/* Image stack support */ +#define ADD_PIC 1 +#define ADD_VIEW 2 + +void clear_image_stack(void); +void record_image_stack_call(uint8 type, int16 p1, int16 p2, int16 p3, + int16 p4, int16 p5, int16 p6, int16 p7); +void replay_image_stack_call(uint8 type, int16 p1, int16 p2, int16 p3, + int16 p4, int16 p5, int16 p6, int16 p7); +void release_image_stack(void); + +} // End of namespace Agi + +#endif diff --git a/engines/agi/sound.cpp b/engines/agi/sound.cpp new file mode 100644 index 0000000000..963e55c0b3 --- /dev/null +++ b/engines/agi/sound.cpp @@ -0,0 +1,772 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2006 The ScummVM project + * + * Copyright (C) 1999-2001 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 "sound/mixer.h" + +#include "agi/agi.h" + +namespace Agi { + +#define USE_INTERPOLATION +#define USE_CHORUS + +/* TODO: add support for variable sampling rate in the output device + */ + +#ifdef USE_IIGS_SOUND + +/** + * AGI engine sound envelope structure. + */ +struct sound_envelope { + uint8 bp; + uint8 inc_hi; + uint8 inc_lo; +}; + +struct sound_wavelist { + uint8 top; + uint8 addr; + uint8 size; + uint8 mode; + uint8 rel_hi; + uint8 rel_lo; +}; + +struct sound_instrument { + struct sound_envelope env[8]; + uint8 relseg; + uint8 priority; + uint8 bendrange; + uint8 vibdepth; + uint8 vibspeed; + uint8 spare; + uint8 wac; + uint8 wbc; + struct sound_wavelist wal[8]; + struct sound_wavelist wbl[8]; +}; + +struct sound_iigs_sample { + uint8 type_lo; + uint8 type_hi; + uint8 srate_lo; + uint8 srate_hi; + uint16 unknown[2]; + uint8 size_lo; + uint8 size_hi; + uint16 unknown2[13]; +}; + +#if 0 +static struct sound_instrument *instruments; +static int num_instruments; +static uint8 *wave; +#endif + +#endif + +static int playing; +static struct channel_info chn[NUM_CHANNELS]; +static int endflag = -1; +static int playing_sound = -1; +static uint8 *song; +static uint8 env; + +struct sound_driver *snd; + +extern struct sound_driver sound_dummy; + +static void stop_note(int i); +static void play_note(int i, int freq, int vol); + +#ifdef USE_PCM_SOUND + +int16 *snd_buffer; +static int16 *waveform; + +static int16 waveform_ramp[WAVEFORM_SIZE] = { + 0, 8, 16, 24, 32, 40, 48, 56, + 64, 72, 80, 88, 96, 104, 112, 120, + 128, 136, 144, 152, 160, 168, 176, 184, + 192, 200, 208, 216, 224, 232, 240, 255, + 0, -248, -240, -232, -224, -216, -208, -200, + -192, -184, -176, -168, -160, -152, -144, -136, + -128, -120, -112, -104, -96, -88, -80, -72, + -64, -56, -48, -40, -32, -24, -16, -8 /* Ramp up */ +}; + +static int16 waveform_square[WAVEFORM_SIZE] = { + 255, 230, 220, 220, 220, 220, 220, 220, + 220, 220, 220, 220, 220, 220, 220, 220, + 220, 220, 220, 220, 220, 220, 220, 220, + 220, 220, 220, 220, 220, 220, 220, 110, + -255, -230, -220, -220, -220, -220, -220, -220, + -220, -220, -220, -220, -220, -220, -220, -220, + -220, -220, -220, -220, -220, -220, -220, -220, + -220, -220, -220, -110, 0, 0, 0, 0 /* Square */ +}; + +static int16 waveform_mac[WAVEFORM_SIZE] = { + 45, 110, 135, 161, 167, 173, 175, 176, + 156, 137, 123, 110, 91, 72, 35, -2, + -60, -118, -142, -165, -170, -176, -177, -179, + -177, -176, -164, -152, -117, -82, -17, 47, + 92, 137, 151, 166, 170, 173, 171, 169, + 151, 133, 116, 100, 72, 43, -7, -57, + -99, -141, -156, -170, -174, -177, -178, -179, + -175, -172, -165, -159, -137, -114, -67, -19 +}; + +#endif /* USE_PCM_SOUND */ + +#ifdef USE_IIGS_SOUND + +static uint16 period[] = { + 1024, 1085, 1149, 1218, 1290, 1367, + 1448, 1534, 1625, 1722, 1825, 1933 +}; + +static struct agi_note play_sample[] = { + {0xff, 0x7f, 0x18, 0x00, 0x7f}, + {0xff, 0xff, 0x00, 0x00, 0x00}, + {0xff, 0xff, 0x00, 0x00, 0x00}, + {0xff, 0xff, 0x00, 0x00, 0x00} +}; + +static int note_to_period(int note) { + return 10 * (period[note % 12] >> (note / 12 - 3)); +} + +#endif /* USE_IIGS_SOUND */ + +void unload_sound(int resnum) { + if (game.dir_sound[resnum].flags & RES_LOADED) { + if (game.sounds[resnum].flags & SOUND_PLAYING) + /* FIXME: Stop playing */ + ; + + /* Release RAW data for sound */ + free(game.sounds[resnum].rdata); + game.sounds[resnum].rdata = NULL; + game.dir_sound[resnum].flags &= ~RES_LOADED; + } +} + +void decode_sound(int resnum) { +#ifdef USE_IIGS_SOUND + int type, size; + int16 *buf; + uint8 *src; + struct sound_iigs_sample *smp; + + debugC(3, kDebugLevelSound, "(%d)", resnum); + type = READ_LE_UINT16(game.sounds[resnum].rdata); + + if (type == AGI_SOUND_SAMPLE) { + /* Convert sample data to 16 bit signed format + */ + smp = (struct sound_iigs_sample *)game.sounds[resnum].rdata; + size = ((int)smp->size_hi << 8) + smp->size_lo; + src = (uint8 *) game.sounds[resnum].rdata; + buf = (int16 *) calloc(1, 54 + (size << 1) + 100); /* FIXME */ + memcpy(buf, src, 54); + for (; size--; buf[size + 54] = ((int16) src[size + 54] - 0x80) << 4); /* FIXME */ + game.sounds[resnum].rdata = (uint8 *) buf; + free(src); + } +#endif /* USE_IIGS_SOUND */ +} + +void start_sound(int resnum, int flag) { + int i, type; +#ifdef USE_IIGS_SOUND + struct sound_iigs_sample *smp; +#endif + + if (game.sounds[resnum].flags & SOUND_PLAYING) + return; + + stop_sound(); + + if (game.sounds[resnum].rdata == NULL) + return; + + type = READ_LE_UINT16(game.sounds[resnum].rdata); + + if (type != AGI_SOUND_SAMPLE && type != AGI_SOUND_MIDI && type != AGI_SOUND_4CHN) + return; + + game.sounds[resnum].flags |= SOUND_PLAYING; + game.sounds[resnum].type = type; + playing_sound = resnum; + song = (uint8 *) game.sounds[resnum].rdata; + + switch (type) { +#ifdef USE_IIGS_SOUND + case AGI_SOUND_SAMPLE: + debugC(3, kDebugLevelSound, "IIGS sample"); + smp = (struct sound_iigs_sample *)game.sounds[resnum].rdata; + for (i = 0; i < NUM_CHANNELS; i++) { + chn[i].type = type; + chn[i].flags = 0; + chn[i].ins = (int16 *) & game.sounds[resnum].rdata[54]; + chn[i].size = ((int)smp->size_hi << 8) + smp->size_lo; + chn[i].ptr = &play_sample[i]; + chn[i].timer = 0; + chn[i].vol = 0; + chn[i].end = 0; + } + break; + case AGI_SOUND_MIDI: + debugC(3, kDebugLevelSound, "IIGS MIDI sequence"); + + for (i = 0; i < NUM_CHANNELS; i++) { + chn[i].type = type; + chn[i].flags = AGI_SOUND_LOOP | AGI_SOUND_ENVELOPE; + chn[i].ins = waveform; + chn[i].size = WAVEFORM_SIZE; + chn[i].vol = 0; + chn[i].end = 0; + } + + chn[0].timer = *(song + 2); + chn[0].ptr = (struct agi_note *)(song + 3); + break; +#endif + case AGI_SOUND_4CHN: + /* Initialize channel info */ + for (i = 0; i < NUM_CHANNELS; i++) { + chn[i].type = type; + chn[i].flags = AGI_SOUND_LOOP; + if (env) { + chn[i].flags |= AGI_SOUND_ENVELOPE; + chn[i].adsr = AGI_SOUND_ENV_ATTACK; + } +#ifdef USE_PCM_SOUND + chn[i].ins = waveform; + chn[i].size = WAVEFORM_SIZE; +#endif + chn[i].ptr = (struct agi_note *)(song + (song[i << 1] | (song[(i << 1) + 1] << 8))); + chn[i].timer = 0; + chn[i].vol = 0; + chn[i].end = 0; + } + break; + } + +#ifdef USE_PCM_SOUND + memset(snd_buffer, 0, BUFFER_SIZE << 1); +#endif + endflag = flag; + + /* Nat Budin reports that the flag should be reset when sound starts + */ + setflag(endflag, false); + + /* FIXME: should wait for sound time instead of setting the flag + * immediately + */ + if (opt.nosound) { + setflag(endflag, true); + stop_sound(); + } +} + +void stop_sound() { + int i; + + endflag = -1; + for (i = 0; i < NUM_CHANNELS; i++) + stop_note(i); + + if (playing_sound != -1) { + game.sounds[playing_sound].flags &= ~SOUND_PLAYING; + playing_sound = -1; + } +} + +static int16 *buffer; + +int init_sound() { + int r = -1; + +#ifdef USE_PCM_SOUND + buffer = snd_buffer = (int16 *) calloc(2, BUFFER_SIZE); +#endif + + env = false; + +#ifdef USE_PCM_SOUND + switch (opt.soundemu) { + case SOUND_EMU_NONE: + waveform = waveform_ramp; + env = true; + break; + case SOUND_EMU_AMIGA: + case SOUND_EMU_PC: + waveform = waveform_square; + break; + case SOUND_EMU_MAC: + waveform = waveform_mac; + break; + } +#endif + + report("Initializing sound:\n"); + + report("sound: envelopes "); + if (env) { + report("enabled (decay=%d, sustain=%d)\n", ENV_DECAY, ENV_SUSTAIN); + } else { + report("disabled\n"); + } + +#ifdef USE_IIGS_SOUND + /*load_instruments ("demo.sys"); */ +#endif + + return r; +} + +void deinit_sound(void) { + debugC(3, kDebugLevelSound, "()"); + if (snd) + snd->deinit(); +#ifdef USE_PCM_SOUND + free(snd_buffer); +#endif +} + +static void stop_note(int i) { + chn[i].adsr = AGI_SOUND_ENV_RELEASE; + +#ifdef USE_CHORUS + /* Stop chorus ;) */ + if (chn[i].type == AGI_SOUND_4CHN && + opt.soundemu == SOUND_EMU_NONE && i < 3) { + stop_note(i + 4); + } +#endif + +#ifdef __TURBOC__ + if (i == 0) + nosound(); +#endif +} + +static void play_note(int i, int freq, int vol) { + if (!getflag(F_sound_on)) + vol = 0; + else if (vol && opt.soundemu == SOUND_EMU_PC) + vol = 160; + +#ifdef USE_PCM_SOUND + chn[i].phase = 0; +#endif + + chn[i].freq = freq; + chn[i].vol = vol; + chn[i].env = 0x10000; + chn[i].adsr = AGI_SOUND_ENV_ATTACK; + +#ifdef USE_CHORUS + /* Add chorus ;) */ + if (chn[i].type == AGI_SOUND_4CHN && + opt.soundemu == SOUND_EMU_NONE && i < 3) { + int newfreq = freq * 1007 / 1000; + if (freq == newfreq) + newfreq++; + play_note(i + 4, newfreq, vol * 2 / 3); + } +#endif + +#ifdef __TURBOC__ + if (i == 0) + sound(freq); +#endif +} + +#ifdef USE_IIGS_SOUND + +void play_midi_sound() { + uint8 *p; + uint8 parm1, parm2; + static uint8 cmd, ch; + + playing = 1; + + if (chn[0].timer > 0) { + chn[0].timer -= 2; + return; + } + + p = (uint8 *) chn[0].ptr; + + if (*p & 0x80) { + cmd = *p++; + ch = cmd & 0x0f; + cmd >>= 4; + } + + switch (cmd) { + case 0x08: + parm1 = *p++; + parm2 = *p++; + if (ch < NUM_CHANNELS) + stop_note(ch); + break; + case 0x09: + parm1 = *p++; + parm2 = *p++; + if (ch < NUM_CHANNELS) + play_note(ch, note_to_period(parm1), 127); + break; + case 0x0b: + parm1 = *p++; + parm2 = *p++; + debugC(3, kDebugLevelSound, "controller %02x, ch %02x, val %02x", parm1, ch, parm2); + break; + case 0x0c: + parm1 = *p++; +#if 0 + if (ch < NUM_CHANNELS) { + chn[ch].ins = (uint16 *) & wave[waveaddr[parm1]]; + chn[ch].size = wavesize[parm1]; + } + debugC(3, kDebugLevelSound, "set patch %02x (%d,%d), ch %02x", + parm1, waveaddr[parm1], wavesize[parm1], ch); +#endif + break; + } + + chn[0].timer = *p++; + chn[0].ptr = (struct agi_note *)p; + + if (*p >= 0xfc) { + debugC(3, kDebugLevelSound, "end of sequence"); + playing = 0; + return; + } +} + +void play_sample_sound() { + play_note(0, 11025 * 10, 200); + playing = 1; +} + +#endif /* USE_IIGS_SOUND */ + +void play_agi_sound() { + int i, freq; + + for (playing = i = 0; i < (opt.soundemu == SOUND_EMU_PC ? 1 : 4); i++) { + playing |= !chn[i].end; + + if (chn[i].end) + continue; + + if ((--chn[i].timer) <= 0) { + stop_note(i); + freq = ((chn[i].ptr->frq_0 & 0x3f) << 4) | (int)(chn[i].ptr->frq_1 & 0x0f); + + if (freq) { + uint8 v = chn[i].ptr->vol & 0x0f; + play_note(i, freq * 10, v == 0xf ? 0 : 0xff - (v << 1)); + } + + chn[i].timer = ((int)chn[i].ptr->dur_hi << 8) | chn[i].ptr->dur_lo; + + if (chn[i].timer == 0xffff) { + chn[i].end = 1; + chn[i].vol = 0; + chn[i].env = 0; +#ifdef USE_CHORUS + /* chorus */ + if (chn[i].type == AGI_SOUND_4CHN && opt.soundemu == SOUND_EMU_NONE && i < 3) { + chn[i + 4].vol = 0; + chn[i + 4].env = 0; + } +#endif + } + chn[i].ptr++; + } + } +} + +void play_sound() { + int i; + + if (endflag == -1) + return; + +#ifdef USE_IIGS_SOUND + if (chn[0].type == AGI_SOUND_MIDI) { + /* play_midi_sound (); */ + playing = 0; + } else if (chn[0].type == AGI_SOUND_SAMPLE) { + play_sample_sound(); + } else +#endif + play_agi_sound(); + + if (!playing) { + for (i = 0; i < NUM_CHANNELS; chn[i++].vol = 0); + + if (endflag != -1) + setflag(endflag, true); + + if (playing_sound != -1) + game.sounds[playing_sound].flags &= ~SOUND_PLAYING; + playing_sound = -1; + endflag = -1; + } +} + +#ifdef USE_PCM_SOUND + +uint32 mix_sound(void) { + register int i, p; + int16 *src; + int c, b, m; + + memset(snd_buffer, 0, BUFFER_SIZE << 1); + + for (c = 0; c < NUM_CHANNELS; c++) { + if (!chn[c].vol) + continue; + + m = chn[c].flags & AGI_SOUND_ENVELOPE ? + chn[c].vol * chn[c].env >> 16 : chn[c].vol; + + if (chn[c].type != AGI_SOUND_4CHN || c != 3) { + src = chn[c].ins; + + p = chn[c].phase; + for (i = 0; i < BUFFER_SIZE; i++) { + b = src[p >> 8]; +#ifdef USE_INTERPOLATION + b += ((src[((p >> 8) + 1) % chn[c].size] - src[p >> 8]) * (p & 0xff)) >> 8; +#endif + snd_buffer[i] += (b * m) >> 4; + + p += (uint32) 118600 *4 / chn[c].freq; + + /* FIXME */ + if (chn[c].flags & AGI_SOUND_LOOP) { + p %= chn[c].size << 8; + } else { + if (p >= chn[c].size << 8) { + p = chn[c].vol = 0; + chn[c].end = 1; + break; + } + } + + } + chn[c].phase = p; + } else { + /* Add white noise */ + for (i = 0; i < BUFFER_SIZE; i++) { + b = rnd->getRandomNumber(255) - 128; + snd_buffer[i] += (b * m) >> 4; + } + } + + switch (chn[c].adsr) { + case AGI_SOUND_ENV_ATTACK: + /* not implemented */ + chn[c].adsr = AGI_SOUND_ENV_DECAY; + break; + case AGI_SOUND_ENV_DECAY: + if (chn[c].env > chn[c].vol * ENV_SUSTAIN + ENV_DECAY) { + chn[c].env -= ENV_DECAY; + } else { + chn[c].env = chn[c].vol * ENV_SUSTAIN; + chn[c].adsr = AGI_SOUND_ENV_SUSTAIN; + } + break; + case AGI_SOUND_ENV_SUSTAIN: + break; + case AGI_SOUND_ENV_RELEASE: + if (chn[c].env >= ENV_RELEASE) { + chn[c].env -= ENV_RELEASE; + } else { + chn[c].env = 0; + } + } + } + + return BUFFER_SIZE; +} + +#ifdef USE_IIGS_SOUND + +#if 0 +int load_instruments(char *fname) { + Common::File fp; + int i, j, k; + struct sound_instrument ai; + int num_wav; + char *path; + + path = "sierrast"; + + if (!fp.open(path)) + return err_BadFileOpen; + report("Loading samples: %s\n", path); + + if ((wave = malloc(0x10000 * 2)) == NULL) + return err_NotEnoughMemory; + + fp.read(wave, 0x10000); + fp.close(); + for (i = 0x10000; i--;) { + ((int16 *) wave)[i] = 2 * ((int16) wave[i] - 128); + } + + fp = fopen("bla", "w"); + fwrite(wave, 2, 0x10000, fp); + fclose(fp); + + report("Loading instruments: %s\n", path); + + if ((fp = fopen(path, "rb")) == NULL) + return err_BadFileOpen; + + fseek(fp, 0x8469, SEEK_SET); + + for (num_wav = j = 0; j < 40; j++) { + fread(&ai, 1, 32, fp); + + if (ai.env[0].bp > 0x7f) + break; + +#if 0 + printf("Instrument %d loaded ----------------\n", j); + printf("Envelope:\n"); + for (i = 0; i < 8; i++) + printf("[seg %d]: BP %02x Inc %04x\n", i, ai.env[i].bp, + ((int)ai.env[i].inc_hi << 8) | ai.env[i].inc_lo); + printf("rel seg: %d, pri inc: %d, bend range: %d, vib dep: %d, " + "vib spd: %d\n", ai.relseg, ai.priority, + ai.bendrange, ai.vibdepth, ai.vibspeed); + printf("A wave count: %d, B wave count: %d\n", ai.wac, ai.wbc); +#endif + + for (k = 0; k < ai.wac; k++, num_wav++) { + fread(&ai.wal[k], 1, 6, fp); +#if 0 + printf("[A %d of %d] top: %02x, wave address: %02x, " + "size: %02x, mode: %02x, relPitch: %04x\n", k + 1, + ai.wac, ai.wal[k].top, ai.wal[k].addr, ai.wal[k].size, + ai.wal[k].mode, ((int)ai.wal[k].rel_hi << 8) | ai.wal[k].rel_lo); +#endif + } + + for (k = 0; k < ai.wbc; k++, num_wav++) { + fread(&ai.wbl[k], 1, 6, fp); +#if 0 + printf("[B %d of %d] top: %02x, wave address: %02x, " + "size: %02x, mode: %02x, relPitch: %04x\n", k + 1, ai.wbc, + ai.wbl[k].top, ai.wbl[k].addr, ai.wbl[k].size, + ai.wbl[k].mode, ((int)ai.wbl[k].rel_hi << 8) | ai.wbl[k].rel_lo); +#endif + } + waveaddr[j] = 256 * ai.wal[0].addr; + wavesize[j] = 256 * (1 << ((ai.wal[0].size) & 0x07)); +#if 1 + printf("%d addr = %d\n", j, waveaddr[j]); + printf(" size = %d\n", wavesize[j]); +#endif + } + + num_instruments = j; + printf("%d Ensoniq 5503 instruments loaded. (%d waveforms)\n", num_instruments, num_wav); + + fclose(fp); + + return err_OK; +} + +void unload_instruments() { + free(instruments); +} +#endif + +#endif /* USE_IIGS_SOUND */ + +static void fill_audio(void *udata, int16 * stream, uint len) { + int16 *origData = stream; + len <<= 2; + uint origLen = len; + + uint32 p = 0; + static uint32 n = 0, s = 0; + + debugC(5, kDebugLevelSound, "(%p, %p, %d)", udata, stream, len); + memcpy(stream, (uint8 *) buffer + s, p = n); + for (n = 0, len -= p; n < len; p += n, len -= n) { + play_sound(); + n = mix_sound() << 1; + if (len < n) { + memcpy((uint8 *) stream + p, buffer, len); + s = len; + n -= s; + return; + } else { + memcpy((uint8 *) stream + p, buffer, n); + } + } + play_sound(); + n = mix_sound() << 1; + memcpy((uint8 *) stream + p, buffer, s = len); + n -= s; +} + +AGIMusic::AGIMusic(Audio::Mixer *pMixer) { + _mixer = pMixer; + _sampleRate = pMixer->getOutputRate(); + _mixer->setupPremix(this); +} + +void AGIMusic::premixerCall(int16 *data, uint len) { + Agi::fill_audio(NULL, data, len); +} + +void AGIMusic::setVolume(uint8 volume) { + // TODO +} + +AGIMusic::~AGIMusic(void) { + _mixer->setupPremix(NULL); +} + +AGIMusic *g_agi_music; + +#endif /* USE_PCM_SOUND */ + +} // End of namespace Agi diff --git a/engines/agi/sound.h b/engines/agi/sound.h new file mode 100644 index 0000000000..5af9fe1a5f --- /dev/null +++ b/engines/agi/sound.h @@ -0,0 +1,167 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2006 The ScummVM project + * + * Copyright (C) 1999-2001 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$ + * + */ + +#ifndef __AGI_SOUND_H +#define __AGI_SOUND_H + +#include "agi/agi.h" + +namespace Agi { + +#define BUFFER_SIZE 410 + +#define SOUND_EMU_NONE 0 +#define SOUND_EMU_PC 1 +#define SOUND_EMU_TANDY 2 +#define SOUND_EMU_MAC 3 +#define SOUND_EMU_AMIGA 4 + +#define SOUND_PLAYING 0x01 +#define WAVEFORM_SIZE 64 +#define ENV_ATTACK 10000 /**< envelope attack rate */ +#define ENV_DECAY 1000 /**< envelope decay rate */ +#define ENV_SUSTAIN 100 /**< envelope sustain level */ +#define ENV_RELEASE 7500 /**< envelope release rate */ +#define NUM_CHANNELS 7 /**< number of sound channels */ + +/** + * AGI engine sound driver structure. + */ +struct sound_driver { + char *description; + int (*init) (int16 * buffer); + void (*deinit) (void); +}; + +/** + * AGI sound resource structure. + */ +struct agi_sound { + uint32 flen; /**< size of raw data */ + uint8 *rdata; /**< raw sound data */ + uint8 flags; /**< sound flags */ + uint16 type; /**< sound resource type */ +}; + +/** + * AGI sound note structure. + */ +struct agi_note { + uint8 dur_lo; /**< LSB of note duration */ + uint8 dur_hi; /**< MSB of note duration */ + uint8 frq_0; /**< LSB of note frequency */ + uint8 frq_1; /**< MSB of note frequency */ + uint8 vol; /**< note volume */ +}; + +/** + * AGI engine sound channel structure. + */ +struct channel_info { +#define AGI_SOUND_SAMPLE 0x0001 +#define AGI_SOUND_MIDI 0x0002 +#define AGI_SOUND_4CHN 0x0008 + uint32 type; + struct agi_note *ptr; +#ifdef USE_PCM_SOUND + int16 *ins; + int32 size; + uint32 phase; +#endif +#define AGI_SOUND_LOOP 0x0001 +#define AGI_SOUND_ENVELOPE 0x0002 + uint32 flags; +#define AGI_SOUND_ENV_ATTACK 3 +#define AGI_SOUND_ENV_DECAY 2 +#define AGI_SOUND_ENV_SUSTAIN 1 +#define AGI_SOUND_ENV_RELEASE 0 + uint32 adsr; + int32 timer; + uint32 end; + uint32 freq; + uint32 vol; + uint32 env; +}; + +void decode_sound(int); +void unload_sound(int); +void play_sound(void); +int init_sound(void); +void deinit_sound(void); +void start_sound(int, int); +void stop_sound(void); +uint32 mix_sound(void); +void __init_sound(void); +int load_instruments(char *fname); + +extern struct sound_driver *snd; + +#endif /* __AGI_SOUND_H */ + +#ifdef USE_PCM_SOUND + +} // End of namespace Agi + +#include "sound/audiostream.h" + +namespace Audio { +class Mixer; +} // End of namespace Audio + +namespace Agi { + +class AGIMusic : public Audio::AudioStream { +public: + AGIMusic(Audio::Mixer * pMixer); + ~AGIMusic(void); + virtual void setVolume(uint8 volume); + + // AudioStream API + int readBuffer(int16 * buffer, const int numSamples) { + premixerCall(buffer, numSamples / 2); + return numSamples; + } + + bool isStereo() const { + return false; + } + + bool endOfData() const { + return false; + } + + int getRate() const { + return _sampleRate; + } + +private: + Audio::Mixer * _mixer; + uint32 _sampleRate; + + void premixerCall(int16 * buf, uint len); +}; + +} // End of namespace Agi + +#endif diff --git a/engines/agi/sprite.cpp b/engines/agi/sprite.cpp new file mode 100644 index 0000000000..3a3ba7354e --- /dev/null +++ b/engines/agi/sprite.cpp @@ -0,0 +1,868 @@ +/* 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 "agi/agi.h" +#include "agi/list.h" +#include "agi/sprite.h" +#include "agi/graphics.h" +#include "agi/text.h" +#include "agi/savegame.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 { + struct list_head list; + struct vt_entry *v; /**< pointer to view table entry */ + int16 x_pos; /**< x coordinate of the sprite */ + int16 y_pos; /**< y coordinate of the sprite */ + int16 x_size; /**< width of the sprite */ + int16 y_size; /**< height of the sprite */ + uint8 *buffer; /**< buffer to store background data */ +#ifdef USE_HIRES + uint8 *hires; /**< buffer for hi-res background */ +#endif +}; + +/* + * Sprite pool replaces dynamic allocation + */ +#undef ALLOC_DEBUG + +#ifdef USE_HIRES +#define POOL_SIZE 68000 /* Gold Rush mine room needs > 50000 */ + /* Speeder bike challenge needs > 67000 */ +#else +#define POOL_SIZE 25000 +#endif +static uint8 *sprite_pool; +static uint8 *pool_top; + +static void *pool_alloc(int size) { + uint8 *x; + + /* Adjust size to 32-bit boundary to prevent data misalignment + * errors. + */ + size = (size + 3) & ~3; + + x = pool_top; + pool_top += size; + + if (pool_top >= (uint8 *)sprite_pool + POOL_SIZE) { + debugC(1, kDebugLevelMain | kDebugLevelResources, "not enough memory"); + pool_top = x; + return NULL; + } + + return x; +} + +static void pool_release(void *s) { + pool_top = (uint8 *)s; +} + +/* + * Blitter functions + */ + +/* Blit one pixel considering the priorities */ + +static void blit_pixel(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 + * 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 + */ + *p = (pr < 0x30 ? pr : spr) | col; + *hidden = false; + + /* Except if our priority is 15, which should never happen + * (fixes bug #451768) + * + * Update: breaks other games, can't be used + * + * if (spr == 0xf0) + * *p = spr | col; + */ + } +} + +#ifdef USE_HIRES + +#define X_FACT 2 /* Horizontal hires factor */ + +static int blit_hires_cel(int x, int y, int spr, struct view_cel *c) { + uint8 *q = NULL; + uint8 *h0, *h, *end; + int i, j, t, m, col; + int hidden = true; + + q = c->data; + t = c->transparency; + m = c->mirror; + spr <<= 4; + h0 = &game.hires[(x + y * _WIDTH + m * (c->width - 1)) * X_FACT]; + + end = game.hires + _WIDTH * X_FACT * _HEIGHT; + + for (i = 0; i < c->height; i++) { + h = h0; + while (*q) { + col = (*q & 0xf0) >> 4; + for (j = *q & 0x0f; j; j--, h += X_FACT * (1 - 2 * m)) { + if (col != t) { + blit_pixel(h, end, col, spr, _WIDTH * X_FACT, &hidden); + blit_pixel(h + 1, end, col, spr, _WIDTH * X_FACT, &hidden); + } + } + q++; + } + h0 += _WIDTH * X_FACT; + q++; + } + return hidden; +} + +#endif + +static int blit_cel(int x, int y, int spr, struct view_cel *c) { + uint8 *p0, *p, *q = NULL, *end; + int i, j, t, m, col; + int hidden = true; + + /* Fixes 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; + +#ifdef USE_HIRES + if (opt.hires) + blit_hires_cel(x, y, spr, c); +#endif + + q = c->data; + t = c->transparency; + m = c->mirror; + spr <<= 4; + p0 = &game.sbuf[x + y * _WIDTH + m * (c->width - 1)]; + + end = game.sbuf + _WIDTH * _HEIGHT; + + for (i = 0; i < c->height; i++) { + p = p0; + while (*q) { + col = (*q & 0xf0) >> 4; + for (j = *q & 0x0f; j; j--, p += 1 - 2 * m) { + if (col != t) { + blit_pixel(p, end, col, spr, _WIDTH, &hidden); + } + } + q++; + } + p0 += _WIDTH; + q++; + } + + return hidden; +} + +static void objs_savearea(struct sprite *s) { + int y; + int16 x_pos = s->x_pos, y_pos = s->y_pos; + int16 x_size = s->x_size, y_size = s->y_size; + uint8 *p0, *q; +#ifdef USE_HIRES + uint8 *h0, *k; +#endif + + if (x_pos + x_size > _WIDTH) + x_size = _WIDTH - x_pos; + + if (x_pos < 0) { + x_size += x_pos; + x_pos = 0; + } + + if (y_pos + y_size > _HEIGHT) + y_size = _HEIGHT - y_pos; + + if (y_pos < 0) { + y_size += y_pos; + y_pos = 0; + } + + if (x_size <= 0 || y_size <= 0) + return; + + p0 = &game.sbuf[x_pos + y_pos * _WIDTH]; + q = s->buffer; +#ifdef USE_HIRES + h0 = &game.hires[(x_pos + y_pos * _WIDTH) * 2]; + k = s->hires; +#endif + for (y = 0; y < y_size; y++) { + memcpy(q, p0, x_size); + q += x_size; + p0 += _WIDTH; +#ifdef USE_HIRES + memcpy(k, h0, x_size * 2); + k += x_size * 2; + h0 += _WIDTH * 2; +#endif + } +} + +static void objs_restorearea(struct sprite *s) { + int y, offset; + int16 x_pos = s->x_pos, y_pos = s->y_pos; + int16 x_size = s->x_size, y_size = s->y_size; + uint8 *p0, *q; +#ifdef USE_HIRES + uint8 *h0, *k; +#endif + + if (x_pos + x_size > _WIDTH) + x_size = _WIDTH - x_pos; + + if (x_pos < 0) { + x_size += x_pos; + x_pos = 0; + } + + if (y_pos + y_size > _HEIGHT) + y_size = _HEIGHT - y_pos; + + if (y_pos < 0) { + y_size += y_pos; + y_pos = 0; + } + + if (x_size <= 0 || y_size <= 0) + return; + + p0 = &game.sbuf[x_pos + y_pos * _WIDTH]; + q = s->buffer; +#ifdef USE_HIRES + h0 = &game.hires[(x_pos + y_pos * _WIDTH) * 2]; + k = s->hires; +#endif + offset = game.line_min_print * CHAR_LINES; + for (y = 0; y < y_size; y++) { + memcpy(p0, q, x_size); + put_pixels_a(x_pos, y_pos + y + offset, x_size, p0); + q += x_size; + p0 += _WIDTH; +#ifdef USE_HIRES + memcpy(h0, k, x_size * 2); + if (opt.hires) { + put_pixels_hires(x_pos * 2, y_pos + y + offset, x_size * 2, h0); + } + k += x_size * 2; + h0 += _WIDTH * 2; +#endif + } +} + +/* + * Sprite management functions + */ + +static LIST_HEAD(spr_upd_head); +static LIST_HEAD(spr_nonupd_head); + +/** + * Condition to determine whether a sprite will be in the 'updating' list. + */ +static int test_updating(struct vt_entry *v) { + /* Sanity check (see bug #779302) */ + if (~game.dir_view[v->current_view].flags & RES_LOADED) + return 0; + + return (v->flags & (ANIMATED | UPDATE | DRAWN)) == (ANIMATED | UPDATE | DRAWN); +} + +/** + * Condition to determine whether a sprite will be in the 'non-updating' list. + */ +static int test_not_updating(struct vt_entry *v) { + /* Sanity check (see bug #779302) */ + if (~game.dir_view[v->current_view].flags & RES_LOADED) + return 0; + + return (v->flags & (ANIMATED | UPDATE | DRAWN)) == (ANIMATED | DRAWN); +} + +/** + * Convert sprite priority to y value. + */ +static INLINE int prio_to_y(int p) { + int i; + + if (p == 0) + return -1; + + for (i = 167; i >= 0; i--) { + if (game.pri_table[i] < p) + return i; + } + + return -1; /* (p - 5) * 12 + 48; */ +} + +/** + * Create and initialize a new sprite structure. + */ +static struct sprite *new_sprite(struct vt_entry *v) { + struct sprite *s; + + s = (struct sprite *)pool_alloc(sizeof(struct sprite)); + if (s == NULL) + return NULL; + + s->v = v; /* link sprite to associated view table entry */ + s->x_pos = v->x_pos; + s->y_pos = v->y_pos - v->y_size + 1; + s->x_size = v->x_size; + s->y_size = v->y_size; + s->buffer = (uint8 *) pool_alloc(s->x_size * s->y_size); +#ifdef USE_HIRES + s->hires = (uint8 *) pool_alloc(s->x_size * s->y_size * 2); +#endif + v->s = s; /* link view table entry to this sprite */ + + return s; +} + +/** + * Insert sprite in the specified sprite list. + */ +static void spr_addlist(struct list_head *head, struct vt_entry *v) { + struct sprite *s; + + s = new_sprite(v); + list_add_tail(&s->list, head); +} + +/** + * Sort sprites from lower y values to build a sprite list. + */ +static struct list_head *build_list(struct list_head *head, + int (*test) (struct vt_entry *)) { + int i, j, k; + struct vt_entry *v; + struct vt_entry *entry[0x100]; + int y_val[0x100]; + int min_y = 0xff, min_index = 0; + + /* fill the arrays with all sprites that satisfy the 'test' + * condition and their y values + */ + i = 0; + for (v = game.view_table; v < &game.view_table[MAX_VIEWTABLE]; v++) { + if (test(v)) { + entry[i] = v; + y_val[i] = v->flags & FIXED_PRIORITY ? prio_to_y(v->priority) : v->y_pos; + i++; + } + } + + /* now look for the smallest y value in the array and put that + * sprite in the list + */ + for (j = 0; j < i; j++) { + min_y = 0xff; + for (k = 0; k < i; k++) { + if (y_val[k] < min_y) { + min_index = k; + min_y = y_val[k]; + } + } + + y_val[min_index] = 0xff; + spr_addlist(head, entry[min_index]); + } + + return head; +} + +/** + * Build list of updating sprites. + */ +static struct list_head *build_upd_blitlist() { + return build_list(&spr_upd_head, test_updating); +} + +/** + * Build list of non-updating sprites. + */ +static struct list_head *build_nonupd_blitlist() { + return build_list(&spr_nonupd_head, test_not_updating); +} + +/** + * Clear the given sprite list. + */ +static void free_list(struct list_head *head) { + struct list_head *h; + struct sprite *s; + + list_for_each(h, head, prev) { + s = list_entry(h, struct sprite, list); + list_del(h); +#ifdef USE_HIRES + pool_release(s->hires); +#endif + pool_release(s->buffer); + pool_release(s); + } +} + +/** + * Copy sprites from the pic buffer to the screen buffer, and check if + * sprites of the given list have moved. + */ +static void commit_sprites(struct list_head *head) { + struct list_head *h; + + list_for_each(h, head, next) { + struct sprite *s = list_entry(h, struct sprite, list); + int x1, y1, x2, y2, w, h; + + w = (s->v->cel_data->width > s->v->cel_data_2->width) ? + s->v->cel_data->width : s->v->cel_data_2->width; + + h = (s->v->cel_data->height > + s->v->cel_data_2->height) ? s->v->cel_data-> + height : s->v->cel_data_2->height; + + s->v->cel_data_2 = s->v->cel_data; + + if (s->v->x_pos < s->v->x_pos2) { + x1 = s->v->x_pos; + x2 = s->v->x_pos2 + w - 1; + } else { + x1 = s->v->x_pos2; + x2 = s->v->x_pos + w - 1; + } + + if (s->v->y_pos < s->v->y_pos2) { + y1 = s->v->y_pos - h + 1; + y2 = s->v->y_pos2; + } else { + y1 = s->v->y_pos2 - h + 1; + y2 = s->v->y_pos; + } + + commit_block(x1, y1, x2, y2); + + if (s->v->step_time_count != s->v->step_time) + continue; + + if (s->v->x_pos == s->v->x_pos2 && s->v->y_pos == s->v->y_pos2) { + s->v->flags |= DIDNT_MOVE; + continue; + } + + s->v->x_pos2 = s->v->x_pos; + s->v->y_pos2 = s->v->y_pos; + s->v->flags &= ~DIDNT_MOVE; + } + +#ifdef USE_CONSOLE + if (debug_.statusline) + write_status(); +#endif +} + +/** + * Erase all sprites in the given list. + */ +static void erase_sprites(struct list_head *head) { + struct list_head *h; + + list_for_each(h, head, prev) { + struct sprite *s = list_entry(h, struct sprite, list); + objs_restorearea(s); + } + + free_list(head); +} + +/** + * Blit all sprites in the given list. + */ +static void blit_sprites(struct list_head *head) { + struct list_head *h = NULL; + int hidden; + + list_for_each(h, head, next) { + struct sprite *s = list_entry(h, struct sprite, list); + objs_savearea(s); + debugC(8, kDebugLevelSprites, "s->v->entry = %d (prio %d)", s->v->entry, s->v->priority); + hidden = blit_cel(s->x_pos, s->y_pos, s->v->priority, s->v->cel_data); + if (s->v->entry == 0) { /* if ego, update f1 */ + setflag(F_ego_invisible, hidden); + } + } +} + +/* + * Public functions + */ + +void commit_upd_sprites() { + commit_sprites(&spr_upd_head); +} + +void commit_nonupd_sprites() { + commit_sprites(&spr_nonupd_head); +} + +/* check moves in both lists */ +void commit_both() { + commit_upd_sprites(); + commit_nonupd_sprites(); +} + +/** + * 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() + */ +void erase_upd_sprites() { + erase_sprites(&spr_upd_head); +} + +/** + * 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 erase_nonupd_sprites() { + erase_sprites(&spr_nonupd_head); +} + +/** + * 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 erase_both() { + erase_upd_sprites(); + erase_nonupd_sprites(); +} + +/** + * 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 blit_upd_sprites() { + debugC(7, kDebugLevelSprites, "blit updating"); + blit_sprites(build_upd_blitlist()); +} + +/** + * 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 blit_nonupd_sprites() { + debugC(7, kDebugLevelSprites, "blit non-updating"); + blit_sprites(build_nonupd_blitlist()); +} + +/** + * 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 blit_both() { + blit_nonupd_sprites(); + blit_upd_sprites(); +} + +/** + * Add view to picture. + * This function is used to implement the add.to.pic AGI command. It + * copies the specified cel from a view resource on the current picture. + * This cel is not a sprite, it can't be moved or removed. + * @param view number of view resource + * @param loop number of loop in the specified view resource + * @param cel number of cel in the specified loop + * @param x x coordinate to place the view + * @param y y coordinate to place the view + * @param pri priority to use + * @param mar if < 4, create a margin around the the base of the cel + */ +void add_to_pic(int view, int loop, int cel, int x, int y, int pri, int mar) { + struct view_cel *c = NULL; + int x1, y1, x2, y2, y3; + uint8 *p1, *p2; + + debugC(3, kDebugLevelSprites, "v=%d, l=%d, c=%d, x=%d, y=%d, p=%d, m=%d", view, loop, cel, x, y, pri, mar); + + record_image_stack_call(ADD_VIEW, view, loop, cel, x, y, pri, mar); + + /* + * Was hardcoded to 8, changed to pri_table[y] to fix Gold + * Rush (see bug #587558) + */ + if (pri == 0) + pri = game.pri_table[y]; + + c = &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; + } + if (x2 >= _WIDTH) + x2 = _WIDTH - 1; + if (y2 >= _HEIGHT) + y2 = _HEIGHT - 1; + + erase_both(); + + debugC(4, kDebugLevelSprites, "blit_cel (%d, %d, %d, c)", x, y, pri); + blit_cel(x1, y1, pri, c); + + /* 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. + */ + if (mar < 4) { + /* add rectangle around object, don't clobber control + * info in priority data. The box extends to the end of + * its priority band! + * + * SQ1 needs +1 (see bug #810331) + */ + y3 = (y2 / 12) * 12 + 1; + + p1 = &game.sbuf[x1 + y3 * _WIDTH]; + p2 = &game.sbuf[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 = &game.sbuf[x1 + y3 * _WIDTH]; + p2 = &game.sbuf[x1 + y2 * _WIDTH]; + for (x = x1; x <= x2; x++) { + if ((*p1 >> 4) >= 4) + *p1 = (mar << 4) | (*p1 & 0x0f); + if ((*p2 >> 4) >= 4) + *p2 = (mar << 4) | (*p2 & 0x0f); + p1++; + p2++; + } + } + + blit_both(); + + debugC(4, kDebugLevelSprites, "commit_block (%d, %d, %d, %d)", x1, y1, x2, y2); + commit_block(x1, y1, x2, y2); +} + +/** + * 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 show_obj(int n) { + struct view_cel *c; + struct sprite s; + int x1, y1, x2, y2; + + agi_load_resource(rVIEW, n); + if (!(c = &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.x_pos = x1; + s.y_pos = y1; + s.x_size = c->width; + s.y_size = c->height; + s.buffer = (uint8 *)malloc(s.x_size * s.y_size); +#ifdef USE_HIRES + s.hires = (uint8 *)malloc(s.x_size * s.y_size * 2); +#endif + + objs_savearea(&s); + blit_cel(x1, y1, s.x_size, c); + commit_block(x1, y1, x2, y2); + message_box(game.views[n].descr); + objs_restorearea(&s); + commit_block(x1, y1, x2, y2); + + free(s.buffer); + + /* Added to fix a memory leak --Vasyl */ +#ifdef USE_HIRES + free(s.hires); +#endif +} + +void commit_block(int x1, int y1, int x2, int y2) { + int i, w, offset; + uint8 *q; +#ifdef USE_HIRES + uint8 *h; +#endif + + if (!game.picture_shown) + return; + + /* Clipping */ + if (x1 < 0) + x1 = 0; + if (x2 < 0) + x2 = 0; + if (y1 < 0) + y1 = 0; + if (y2 < 0) + y2 = 0; + if (x1 >= _WIDTH) + x1 = _WIDTH - 1; + if (x2 >= _WIDTH) + x2 = _WIDTH - 1; + if (y1 >= _HEIGHT) + y1 = _HEIGHT - 1; + if (y2 >= _HEIGHT) + y2 = _HEIGHT - 1; + + debugC(7, kDebugLevelSprites, "%d, %d, %d, %d", x1, y1, x2, y2); + + w = x2 - x1 + 1; + q = &game.sbuf[x1 + _WIDTH * y1]; +#ifdef USE_HIRES + h = &game.hires[(x1 + _WIDTH * y1) * 2]; +#endif + offset = game.line_min_print * CHAR_LINES; + for (i = y1; i <= y2; i++) { + put_pixels_a(x1, i + offset, w, q); + q += _WIDTH; +#ifdef USE_HIRES + if (opt.hires) { + put_pixels_hires(x1 * 2, i + offset, w * 2, h); + } + h += _WIDTH * 2; +#endif + } + + flush_block_a(x1, y1 + offset, x2, y2 + offset); +} + +int init_sprites() { + if ((sprite_pool = (uint8 *)malloc(POOL_SIZE)) == NULL) + return err_NotEnoughMemory; + + pool_top = sprite_pool; + + return err_OK; +} + +void deinit_sprites() { + free(sprite_pool); +} + +} // End of namespace Agi diff --git a/engines/agi/sprite.h b/engines/agi/sprite.h new file mode 100644 index 0000000000..a3d0d8b871 --- /dev/null +++ b/engines/agi/sprite.h @@ -0,0 +1,47 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2006 The ScummVM project + * + * Copyright (C) 1999-2001 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$ + * + */ + +#ifndef __AGI_SPRITE_H +#define __AGI_SPRITE_H + +namespace Agi { + +int init_sprites(void); +void deinit_sprites(void); +void erase_upd_sprites(void); +void erase_nonupd_sprites(void); +void erase_both(void); +void blit_upd_sprites(void); +void blit_nonupd_sprites(void); +void blit_both(void); +void commit_upd_sprites(void); +void commit_nonupd_sprites(void); +void commit_both(void); +void add_to_pic(int, int, int, int, int, int, int); +void show_obj(int); +void commit_block(int, int, int, int); + +} // End of namespace Agi + +#endif /* __AGI_SPRITE_H */ diff --git a/engines/agi/text.cpp b/engines/agi/text.cpp new file mode 100644 index 0000000000..5619e1cf02 --- /dev/null +++ b/engines/agi/text.cpp @@ -0,0 +1,677 @@ +/* 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 "agi/agi.h" +#include "agi/sprite.h" /* for commit_both() */ +#include "agi/graphics.h" +#include "agi/keyboard.h" +#include "agi/text.h" + +namespace Agi { + +static void print_text2(int l, char *msg, int foff, int xoff, int yoff, + int len, int fg, int bg) { + int x1, y1; + int maxx, minx, ofoff; + int update; + /* Note: Must be unsigned to use AGDS cyrillic characters! */ + unsigned char *m; + + /* kludge! */ + update = 1; + if (l == 2) { + update = l = 0; + } + + /* FR: strings with len == 1 were not printed + */ + if (len == 1) { + put_text_character(l, xoff + foff, yoff, *msg, fg, bg); + maxx = 1; + minx = 0; + ofoff = foff; + y1 = 0; /* Check this */ + } else { + maxx = 0; + minx = GFX_WIDTH; + ofoff = foff; + + for (m = (unsigned char *)msg, x1 = y1 = 0; *m; m++) { + + if (*m >= 0x20 || *m == 1 || *m == 2 || *m == 3) { + /* FIXME */ + int ypos; + + ypos = (y1 * CHAR_LINES) + yoff; + + if ((x1 != (len - 1) || x1 == 39) && (ypos <= (GFX_HEIGHT - CHAR_LINES))) { + int xpos; + + xpos = (x1 * CHAR_COLS) + xoff + foff; + + if (xpos >= GFX_WIDTH) + continue; + + put_text_character(l, xpos, ypos, *m, fg, bg); + + if (x1 > maxx) + maxx = x1; + if (x1 < minx) + minx = x1; + } + + x1++; + /* DF: changed the len-1 to len... */ + if (x1 == len && m[1] != '\n') + y1++, x1 = foff = 0; + } else { + y1++; + x1 = foff = 0; + } + } + } + + if (l) + return; + + if (maxx < minx) + return; + + maxx *= CHAR_COLS; + minx *= CHAR_COLS; + + if (update) { + schedule_update(foff + xoff + minx, yoff, ofoff + xoff + maxx + CHAR_COLS - 1, + yoff + y1 * CHAR_LINES + CHAR_LINES + 1); + /* Making synchronous text updates reduces CPU load + * when updating status line and input area + */ + do_update(); + } +} + +/* len is in characters, not pixels!! + */ +static void blit_textbox(char *p, int y, int x, int len) { + /* if x | y = -1, then centre the box */ + int xoff, yoff, lin, h, w; + char *msg, *m; + + debugC(3, kDebugLevelText, "x=%d, y=%d, len=%d", x, y, len); + if (game.window.active) + close_window(); + + if (x == 0 && y == 0 && len == 0) + x = y = -1; + + if (len <= 0 || len >= 40) + len = 32; + + xoff = x * CHAR_COLS; + yoff = y * CHAR_LINES; + len--; + + m = msg = word_wrap_string(agi_sprintf(p), &len); + + for (lin = 1; *m; m++) { + /* Test \r for MacOS 8 */ + if (*m == '\n' || *m == '\r') + lin++; + } + + if (lin * CHAR_LINES > GFX_HEIGHT) + lin = (GFX_HEIGHT / CHAR_LINES); + + w = (len + 2) * CHAR_COLS; + h = (lin + 2) * CHAR_LINES; + + if (xoff < 0) + xoff = (GFX_WIDTH - w - CHAR_COLS) / 2; + else + xoff -= CHAR_COLS; + + if (yoff < 0) + yoff = (GFX_HEIGHT - 3 * CHAR_LINES - h) / 2; + + draw_window(xoff, yoff, xoff + w - 1, yoff + h - 1); + + print_text2(2, msg, 0, CHAR_COLS + xoff, CHAR_LINES + yoff, + len + 1, MSG_BOX_TEXT, MSG_BOX_COLOUR); + + free(msg); + + do_update(); +} + +static void erase_textbox() { + if (!game.window.active) { + debugC(3, kDebugLevelText, "no window active"); + return; + } + + debugC(4, kDebugLevelText, "x1=%d, y1=%d, x2=%d, y2=%d", game.window.x1, + game.window.y1, game.window.x2, game.window.y2); + + restore_block(game.window.x1, game.window.y1, + game.window.x2, game.window.y2, game.window.buffer); + + free(game.window.buffer); + game.window.active = false; + + do_update(); +} + +/* + * Public functions + */ + +/** + * Print text in the AGI engine screen. + */ +void print_text(char *msg, int f, int x, int y, int len, int fg, int bg) { + f *= CHAR_COLS; + x *= CHAR_COLS; + y *= CHAR_LINES; + + debugC(4, kDebugLevelText, "%s, %d, %d, %d, %d, %d, %d", msg, f, x, y, len, fg, bg); + print_text2(0, agi_sprintf(msg), f, x, y, len, fg, bg); +} + +/** + * Print text in the AGI engine console. + */ +void print_text_console(char *msg, int x, int y, int len, int fg, int bg) { + x *= CHAR_COLS; + y *= 10; + + print_text2(1, msg, 0, x, y, len, fg, bg); +} + +/** + * Wrap text line to the specified width. + * @param str String to wrap. + * @param len Length of line. + */ +char *word_wrap_string(char *str, int *len) { + /* If the message has a long word (longer than 31 character) then + * loop in line 239 (for (; *v != ' '; v--, c--);) can wrap + * around 0 and write large number in c. This causes returned + * length to be negative (!) and eventually crashes in calling + * code. The fix is simple -- remove unsigned in maxc, c, l + * declaration. --Vasyl + */ + char *msg, *v, *e; + int maxc, c, l = *len; + + v = msg = strdup(str); + e = msg + strlen(msg); + maxc = 0; + + while (42) { + debugC(3, kDebugLevelText, "[%s], %d", msg, maxc); + if (strchr(v, ' ') == NULL && (int)strlen(v) > l) { + debugC(1, kDebugLevelText | kDebugLevelMain, "Word too long in message"); + l = strlen(v); + } + /* Must include \r for MacOS 8 */ + while ((c = strcspn(v, "\n\r")) <= l) { + debugC(3, kDebugLevelText, "c = %d, maxc = %d", c, maxc); + if (c > maxc) + maxc = c; + if ((v += c + 1) >= e) + goto end; + } + c = l; + if ((v += l) >= e) + break; + + /* The same line that caused that bug I mentioned + * should also do another check: + * for (; *v != ' ' && *v != '\n'; v--, c--); + * While this does not matter in most cases, in the case of + * long words it caused extra \n inserted in the line + * preceding long word. This one is definitely non-critical; + * one might argue that the function is not supposed to deal + * with long words. BTW, that condition at the beginning of + * the while loop that checks word length does not make much + * sense -- it verifies the length of the first word but for + * the rest it does something odd. Overall, even with these + * changes the function is still not completely robust. + * --Vasyl + */ + if (*v != ' ') + for (; *v != ' ' && *v != '\n' && *v != '\r'; + v--, c--); + if (c > maxc) + maxc = c; + *v++ = '\n'; + } + end: + *len = maxc; + return msg; +} + +/** + * Remove existing window, if any. + */ +void close_window() { + debugC(4, kDebugLevelText, "close window"); + erase_both(); + erase_textbox(); /* remove window, if any */ + blit_both(); + commit_both(); /* redraw sprites */ + game.has_window = false; +} + +/** + * 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 message_box(char *s) { + int k; + + erase_both(); + blit_textbox(s, -1, -1, -1); + blit_both(); + k = wait_key(); + debugC(4, kDebugLevelText, "wait_key returned %02x", k); + close_window(); + + return k; +} + +/** + * 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 selection_box(char *m, char **b) { + int x, y, i, s; + int key, active = 0; + int rc = -1; + int bx[5], by[5]; + + erase_both(); + blit_textbox(m, -1, -1, -1); + + 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, "s = %d", s); + + /* Automatically position buttons */ + for (i = 0; b[i]; i++) { + s -= CHAR_COLS * strlen(b[i]); + } + + if (i > 1) { + debugC(3, kDebugLevelText, "s / %d = %d", i - 1, s / (i - 1)); + s /= (i - 1); + } else { + x += s / 2; + } + + for (i = 0; b[i]; i++) { + bx[i] = x; + by[i] = y; + x += CHAR_COLS * strlen(b[i]) + s; + } + + blit_both(); + + /* clear key queue */ + while (keypress()) { + get_key(); + } + + debugC(4, kDebugLevelText, "waiting..."); + while (42) { + for (i = 0; b[i]; i++) + draw_button(bx[i], by[i], b[i], i == active, 0); + + poll_timer(); /* msdos driver -> does nothing */ + key = do_poll_keyboard(); + if (!console_keyhandler(key)) { + switch (key) { + case KEY_ENTER: + rc = active; + goto press; + case KEY_ESCAPE: + rc = -1; + goto getout; +#ifdef USE_MOUSE + case BUTTON_LEFT: + for (i = 0; b[i]; i++) { + if (test_button(bx[i], by[i], b[i])) { + rc = active = i; + goto press; + } + } + break; +#endif + case 0x09: /* Tab */ + debugC(3, kDebugLevelText, "Focus change"); + active++; + active %= i; + break; + } + } + console_cycle(); + } + + press: + debugC(4, kDebugLevelText, "Button pressed: %d", rc); + + getout: + close_window(); + debugC(2, kDebugLevelText, "Result = %d", rc); + + return rc; +} + +/** + * + */ +int print(char *p, int lin, int col, int len) { + if (p == NULL) + return 0; + + debugC(4, kDebugLevelText, "lin = %d, col = %d, len = %d", lin, col, len); + + if (col == 0 && lin == 0 && len == 0) + lin = col = -1; + + if (len == 0) + len = 30; + + blit_textbox(p, lin, col, len); + + if (getflag(F_output_mode)) { + /* non-blocking window */ + setflag(F_output_mode, false); + return 1; + } + + /* blocking */ + + if (game.vars[V_window_reset] == 0) { + int k; + setvar(V_key, 0); + k = wait_key(); + close_window(); + return k; + } + + /* timed window */ + + debugC(3, kDebugLevelText, "f15==0, v21==%d => timed", getvar(21)); + game.msg_box_ticks = getvar(V_window_reset) * 10; + setvar(V_key, 0); + + do { + main_cycle(); + if (game.keypress == KEY_ENTER) { + debugC(4, kDebugLevelText, "KEY_ENTER"); + setvar(V_window_reset, 0); + game.keypress = 0; + break; + } + } while (game.msg_box_ticks > 0); + + setvar(V_window_reset, 0); + + close_window(); + + return 0; +} + +/** + * + */ +static void print_status(char *message, ...) { + char x[42]; + va_list args; + + va_start(args, message); + +#ifdef HAVE_VSNPRINTF + vsnprintf(x, 41, message, args); +#else + vsprintf(x, message, args); +#endif + + va_end(args); + + debugC(4, kDebugLevelText, "fg=%d, bg=%d", STATUS_FG, STATUS_BG); + print_text(x, 0, 0, game.line_status, 40, STATUS_FG, STATUS_BG); +} + +static char *safe_strcat(char *s, const char *t) { + if (t != NULL) + strcat(s, t); + + return s; +} + +/** + * Formats AGI string. + * This function turns a AGI string into a real string expanding values + * according to the AGI format specifiers. + * @param s string containing the format specifier + * @param n logic number + */ +#define MAX_LEN 768 +char *agi_sprintf(char *s) { + static char y[MAX_LEN]; + char x[MAX_LEN]; + char z[16], *p; + + debugC(3, kDebugLevelText, "logic %d, '%s'", game.lognum, s); + p = x; + + for (*p = 0; *s;) { + switch (*s) { + case '\\': + s++; + goto literal; + case '%': + s++; + switch (*s++) { + int i; + case 'v': + i = strtoul(s, NULL, 10); + while (*s >= '0' && *s <= '9') + s++; + sprintf(z, "%015i", getvar(i)); + + i = 99; + if (*s == '|') { + s++; + i = strtoul(s, NULL, 10); + while (*s >= '0' && *s <= '9') + s++; + } + + if (i == 99) { + /* remove all leading 0 */ + /* don't remove the 3rd zero if 000 */ + for (i = 0; + z[i] == '0' && i < 14; i++); + } else { + i = 15 - i; + } + safe_strcat(p, z + i); + break; + case '0': + i = strtoul(s, NULL, 10) - 1; + safe_strcat(p, object_name(i)); + break; + case 'g': + i = strtoul(s, NULL, 10) - 1; + safe_strcat(p, game.logics[0].texts[i]); + break; + case 'w': + i = strtoul(s, NULL, 10) - 1; + safe_strcat(p, game.ego_words[i].word); + break; + case 's': + i = strtoul(s, NULL, 10); + safe_strcat(p, game.strings[i]); + break; + case 'm': + i = strtoul(s, NULL, 10) - 1; + if (game.logics[game.lognum].num_texts > i) + safe_strcat(p, agi_sprintf(game. logics[game.lognum].texts[i])); + break; + } + + while (*s >= '0' && *s <= '9') + s++; + while (*p) + p++; + break; + + default: + literal: + assert(p < x + MAX_LEN); + *p++ = *s++; + *p = 0; + break; + } + } + + strcpy(y, x); + return y; +} + +/** + * Write the status line. + */ +void write_status() { + char x[64]; + +#ifdef USE_CONSOLE + if (debug_.statusline) { +#ifdef USE_MOUSE + print_status("%3d(%03d) %3d,%3d(%3d,%3d) ", + getvar(0), getvar(1), game.view_table[0].x_pos, + game.view_table[0].y_pos, WIN_TO_PIC_X(mouse.x), + WIN_TO_PIC_Y(mouse.y)); +#else + print_status("%3d(%03d) %3d,%3d ", + getvar(0), getvar(1), game.view_table[0].x_pos, + game.view_table[0].y_pos); +#endif + return; + } +#endif /* USE_CONSOLE */ + + if (!game.status_line) { + int l = game.line_status; + clear_lines(l, l, 0); + flush_lines(l, l); + return; + } + + sprintf(x, " Score:%i of %-3i", game.vars[V_score], game.vars[V_max_score]); + print_status("%-17s Sound:%s ", x, getflag(F_sound_on) ? "on " : "off"); +} + +/** + * Print user input prompt. + */ +void write_prompt() { + int l, fg, bg, pos; + + if (!game.input_enabled || game.input_mode != INPUT_NORMAL) + return; + + l = game.line_user_input; + fg = game.color_fg; + bg = game.color_bg; + pos = game.cursor_pos; + + debugC(4, kDebugLevelText, "erase line %d", l); + clear_lines(l, l, game.color_bg); + + debugC(4, kDebugLevelText, "prompt = '%s'", agi_sprintf(game.strings[0])); + print_text(game.strings[0], 0, 0, l, 1, fg, bg); + print_text((char *)game.input_buffer, 0, 1, l, pos + 1, fg, bg); + print_character(pos + 1, l, game.cursor_char, fg, bg); + + flush_lines(l, l); + do_update(); +} + +/** + * Clear text lines in the screen. + * @param l1 start line + * @param l2 end line + * @param c color + */ +void clear_lines(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. + */ + + l1 *= CHAR_LINES; + l2 *= CHAR_LINES; + l2 += CHAR_LINES - 1; + + draw_rectangle(0, l1, GFX_WIDTH - 1, l2, c); +} + +/** + * + */ +void flush_lines(int l1, int l2) { + l1 *= CHAR_LINES; + l2 *= CHAR_LINES; + l2 += CHAR_LINES - 1; + + flush_block(0, l1, GFX_WIDTH - 1, l2); +} + +/** + * + */ +void draw_window(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); + save_block(x1, y1, x2, y2, game.window.buffer); + draw_box(x1, y1, x2, y2, MSG_BOX_COLOUR, MSG_BOX_LINE, 2); +} + +} // End of namespace Agi diff --git a/engines/agi/text.h b/engines/agi/text.h new file mode 100644 index 0000000000..4ce37bc7fb --- /dev/null +++ b/engines/agi/text.h @@ -0,0 +1,48 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2006 The ScummVM project + * + * Copyright (C) 1999-2001 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$ + * + */ + +#ifndef __AGI_TEXT_H +#define __AGI_TEXT_H + +#include "agi/agi.h" + +namespace Agi { + +int message_box(char *); +int selection_box(char *, char **); +void close_window(void); +void draw_window(int, int, int, int); +void print_text(char *, int, int, int, int, int, int); +void print_text_console(char *, int, int, int, int, int); +int print(char *, int, int, int); +char *word_wrap_string(char *, int *); +char *agi_sprintf(char *); +void write_status(void); +void write_prompt(void); +void clear_lines(int, int, int); +void flush_lines(int, int); + +} // End of namespace Agi + +#endif /* __AGI_TEXT_H */ diff --git a/engines/agi/view.cpp b/engines/agi/view.cpp new file mode 100644 index 0000000000..b8fc559b84 --- /dev/null +++ b/engines/agi/view.cpp @@ -0,0 +1,396 @@ +/* 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 "agi/agi.h" +#include "agi/sprite.h" + +namespace Agi { + +static void _set_cel(struct vt_entry *v, int n) { + struct view_loop *current_vl; + struct view_cel *current_vc; + + v->current_cel = n; + + current_vl = &game.views[v->current_view].loop[v->current_loop]; + + /* Added by Amit Vainsencher to prevent + * crash in KQ1 -- not in the Sierra interpreter + */ + if (current_vl->num_cels == 0) + return; + + if (!(v->flags & UPDATE) + && (agi_get_release() >= 0x3000)) + return; + + current_vc = ¤t_vl->cel[n]; + v->cel_data = current_vc; + v->x_size = current_vc->width; + v->y_size = current_vc->height; +} + +static void _set_loop(struct vt_entry *v, int n) { + struct view_loop *current_vl; + 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->num_loops) + n = 0; + + v->current_loop = n; + current_vl = &game.views[v->current_view].loop[v->current_loop]; + + v->num_cels = current_vl->num_cels; + if (v->current_cel >= v->num_cels) + v->current_cel = 0; + + if (!(v->flags & UPDATE) && (agi_get_release() >= 0x3000)) + return; + + v->loop_data = &game.views[v->current_view].loop[n]; +} + +static void update_view(struct vt_entry *v) { + int cel, last_cel; + + if (v->flags & DONTUPDATE) { + v->flags &= ~DONTUPDATE; + return; + } + + cel = v->current_cel; + last_cel = v->num_cels - 1; + + switch (v->cycle) { + case CYCLE_NORMAL: + if (++cel > last_cel) + cel = 0; + break; + case CYCLE_END_OF_LOOP: + if (cel < last_cel) { + debugC(5, kDebugLevelResources, "cel %d (last = %d)", cel + 1, last_cel); + if (++cel != last_cel) + break; + } + setflag(v->parm1, true); + v->flags &= ~CYCLING; + v->direction = 0; + v->cycle = CYCLE_NORMAL; + break; + case CYCLE_REV_LOOP: + if (cel) { + if (--cel) + break; + } + setflag(v->parm1, true); + v->flags &= ~CYCLING; + v->direction = 0; + v->cycle = CYCLE_NORMAL; + break; + case CYCLE_REVERSE: + if (cel == 0) { + cel = last_cel; + } else { + cel--; + } + break; + } + + set_cel(v, cel); +} + +/* + * Public functions + */ + +/** + * Decode an AGI view resource. + * This function decodes the raw data of the specified AGI view resource + * and fills the corresponding views array element. + * @param n number of view resource to decode + */ +int decode_view(int n) { + int loop, cel; + uint8 *v, *lptr; + uint16 lofs, cofs; + struct view_loop *vl; + struct view_cel *vc; + + debugC(5, kDebugLevelResources, "decode_view(%d)", n); + v = game.views[n].rdata; + + assert(v != NULL); + + game.views[n].descr = READ_LE_UINT16(v + 3) ? (char *)(v + READ_LE_UINT16(v + 3)) : (char *)(v + 3); + + /* if no loops exist, return! */ + if ((game.views[n].num_loops = *(v + 2)) == 0) + return err_NoLoopsInView; + + /* allocate memory for all views */ + game.views[n].loop = (view_loop *) + calloc(game.views[n].num_loops, sizeof(struct view_loop)); + + if (game.views[n].loop == NULL) + return err_NotEnoughMemory; + + /* decode all of the loops in this view */ + lptr = v + 5; /* first loop address */ + + for (loop = 0; loop < game.views[n].num_loops; loop++, lptr += 2) { + lofs = READ_LE_UINT16(lptr); /* loop header offset */ + vl = &game.views[n].loop[loop]; /* the loop struct */ + + vl->num_cels = *(v + lofs); + debugC(6, kDebugLevelResources, "view %d, num_cels = %d", n, vl->num_cels); + vl->cel = (view_cel *) calloc(vl->num_cels, sizeof(struct view_cel)); + if (vl->cel == NULL) { + free(game.views[n].loop); + game.views[n].num_loops = 0; + return err_NotEnoughMemory; + } + + /* decode the cells */ + for (cel = 0; cel < vl->num_cels; cel++) { + cofs = lofs + READ_LE_UINT16(v + lofs + 1 + (cel * 2)); + vc = &vl->cel[cel]; + + vc->width = *(v + cofs); + vc->height = *(v + cofs + 1); + vc->transparency = *(v + cofs + 2) & 0xf; + vc->mirror_loop = (*(v + cofs + 2) >> 4) & 0x7; + vc->mirror = (*(v + cofs + 2) >> 7) & 0x1; + + /* skip over width/height/trans|mirror data */ + cofs += 3; + + vc->data = v + cofs; + /* If mirror_loop is pointing to the current loop, + * then this is the original. + */ + if (vc->mirror_loop == loop) + vc->mirror = 0; + } /* cel */ + } /* loop */ + + return err_OK; +} + +/** + * Unloads all data in a view resource + * @param n number of view resource + */ +void unload_view(int n) { + int x; + + debugC(5, kDebugLevelResources, "discard view %d", n); + if (~game.dir_view[n].flags & RES_LOADED) + return; + + /* Rebuild sprite list, see bug #779302 */ + erase_both(); + blit_both(); + commit_both(); + + /* free all the loops */ + for (x = 0; x < game.views[n].num_loops; x++) + free(game.views[n].loop[x].cel); + + free(game.views[n].loop); + free(game.views[n].rdata); + + game.dir_view[n].flags &= ~RES_LOADED; +} + +/** + * 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 set_cel(struct vt_entry *v, int n) { + assert(v->view_data != NULL); + assert(v->num_cels >= n); + + _set_cel(v, n); + + /* If position isn't appropriate, update it accordingly */ + if (v->x_pos + v->x_size > _WIDTH) { + v->flags |= UPDATE_POS; + v->x_pos = _WIDTH - v->x_size; + } + if (v->y_pos - v->y_size + 1 < 0) { + v->flags |= UPDATE_POS; + v->y_pos = v->y_size - 1; + } + if (v->y_pos <= game.horizon && (~v->flags & IGNORE_HORIZON)) { + v->flags |= UPDATE_POS; + v->y_pos = game.horizon + 1; + } +} + +/** + * 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 + */ +void set_loop(struct vt_entry *v, int n) { + assert(v->view_data != NULL); + assert(v->num_loops >= n); + _set_loop(v, n); + set_cel(v, v->current_cel); +} + +/** + * Set a view table entry to use the specified view resource. + * @param v pointer to view table entry + * @param n number of AGI view resource + */ +void set_view(struct vt_entry *v, int n) { + v->view_data = &game.views[n]; + v->current_view = n; + v->num_loops = v->view_data->num_loops; + set_loop(v, v->current_loop >= v->num_loops ? 0 : v->current_loop); +} + +/** + * Set the view table entry as updating. + * @param v pointer to view table entry + */ +void start_update(struct vt_entry *v) { + if (~v->flags & UPDATE) { + erase_both(); + v->flags |= UPDATE; + blit_both(); + } +} + +/** + * Set the view table entry as non-updating. + * @param v pointer to view table entry + */ +void stop_update(struct vt_entry *v) { + if (v->flags & UPDATE) { + erase_both(); + v->flags &= ~UPDATE; + blit_both(); + } +} + +/* loops to use according to direction and number of loops in + * the view resource + */ +static int loop_table_2[] = { + 0x04, 0x04, 0x00, 0x00, 0x00, 0x04, 0x01, 0x01, 0x01 +}; + +static int loop_table_4[] = { + 0x04, 0x03, 0x00, 0x00, 0x00, 0x02, 0x01, 0x01, 0x01 +}; + +/** + * Update view table entries. + * This function is called at the end of each interpreter cycle + * to update the view table entries and blit the sprites. + */ +void update_viewtable() { + struct vt_entry *v; + int i, loop; + + i = 0; + for (v = game.view_table; v < &game.view_table[MAX_VIEWTABLE]; v++) { + if ((v->flags & (ANIMATED | UPDATE | DRAWN)) != (ANIMATED | UPDATE | DRAWN)) { + continue; + } + + i++; + + loop = 4; + if (~v->flags & FIX_LOOP) { + switch (v->num_loops) { + case 2: + case 3: + loop = loop_table_2[v->direction]; + break; + case 4: + loop = loop_table_4[v->direction]; + break; + default: + /* for KQ4 */ + if (agi_get_release() == 0x3086) + loop = loop_table_4[v->direction]; + break; + } + } + + /* AGI 2.272 (ddp, xmas) doesn't test step_time_count! */ + if (loop != 4 && loop != v->current_loop) { + if (agi_get_release() <= 0x2272 || + v->step_time_count == 1) { + set_loop(v, loop); + } + } + + if (~v->flags & CYCLING) + continue; + + if (v->cycle_time_count == 0) + continue; + + if (--v->cycle_time_count == 0) { + update_view(v); + v->cycle_time_count = v->cycle_time; + } + } + + if (i) { +#ifdef USE_CONSOLE + /* To correctly update sprites when we use the console + * we must work with all sprites. + */ + if (console.y > 0) { + erase_both(); + update_position(); + blit_both(); + commit_both(); + } else +#endif + /* If we're not using the console, updating only + * the active sprites lets us save some CPU cycles. + * This is how the original Sierra AGI works. + */ + { + erase_upd_sprites(); + update_position(); + blit_upd_sprites(); + commit_upd_sprites(); + } + + game.view_table[0].flags &= ~(ON_WATER | ON_LAND); + } +} + +} // End of namespace Agi diff --git a/engines/agi/view.h b/engines/agi/view.h new file mode 100644 index 0000000000..3394c8609a --- /dev/null +++ b/engines/agi/view.h @@ -0,0 +1,142 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2006 The ScummVM project + * + * Copyright (C) 1999-2001 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$ + * + */ + +#ifndef __AGI_VIEW_H +#define __AGI_VIEW_H + +namespace Agi { + +struct view_cel { + uint8 height; + uint8 width; + uint8 transparency; + uint8 mirror_loop; + uint8 mirror; + uint8 *data; +}; + +struct view_loop { + int num_cels; + struct view_cel *cel; +}; + +/** + * AGI view resource structure. + */ +struct agi_view { + int num_loops; + struct view_loop *loop; + char *descr; + uint8 *rdata; +}; + +/** + * AGI view table entry + */ +struct vt_entry { + uint8 step_time; + uint8 step_time_count; + uint8 entry; + int16 x_pos; + int16 y_pos; + uint8 current_view; + struct agi_view *view_data; + uint8 current_loop; + uint8 num_loops; + struct view_loop *loop_data; + uint8 current_cel; + uint8 num_cels; + struct view_cel *cel_data; + struct view_cel *cel_data_2; + int16 x_pos2; + int16 y_pos2; + void *s; + int16 x_size; + int16 y_size; + uint8 step_size; + uint8 cycle_time; + uint8 cycle_time_count; + uint8 direction; + +#define MOTION_NORMAL 0 +#define MOTION_WANDER 1 +#define MOTION_FOLLOW_EGO 2 +#define MOTION_MOVE_OBJ 3 + uint8 motion; + +#define CYCLE_NORMAL 0 +#define CYCLE_END_OF_LOOP 1 +#define CYCLE_REV_LOOP 2 +#define CYCLE_REVERSE 3 + uint8 cycle; + + uint8 priority; + +#define DRAWN 0x0001 +#define IGNORE_BLOCKS 0x0002 +#define FIXED_PRIORITY 0x0004 +#define IGNORE_HORIZON 0x0008 +#define UPDATE 0x0010 +#define CYCLING 0x0020 +#define ANIMATED 0x0040 +#define MOTION 0x0080 +#define ON_WATER 0x0100 +#define IGNORE_OBJECTS 0x0200 +#define UPDATE_POS 0x0400 +#define ON_LAND 0x0800 +#define DONTUPDATE 0x1000 +#define FIX_LOOP 0x2000 +#define DIDNT_MOVE 0x4000 +#define ADJ_EGO_XY 0x8000 + uint16 flags; + + uint8 parm1; + uint8 parm2; + uint8 parm3; + uint8 parm4; +}; /* struct vt_entry */ + +/* Motion */ +void check_all_motions(void); +void move_obj(struct vt_entry *); +void in_destination(struct vt_entry *); +void fix_position(int); +void update_position(void); + +/* View table management */ +void set_cel(struct vt_entry *, int); +void set_loop(struct vt_entry *, int); +void set_view(struct vt_entry *, int); +void start_update(struct vt_entry *); +void stop_update(struct vt_entry *); +void update_viewtable(void); + +void unload_view(int); +int decode_view(int); +void add_to_pic(int, int, int, int, int, int, int); +void draw_obj(int); + +} // End of namespace Agi + +#endif /* __AGI_VIEW_H */ diff --git a/engines/agi/words.cpp b/engines/agi/words.cpp new file mode 100644 index 0000000000..77cdd97667 --- /dev/null +++ b/engines/agi/words.cpp @@ -0,0 +1,213 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2006 The ScummVM project + * + * Copyright (C) 1999-2002 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$ + * + */ + +/* + * New find_word algorithm by Thomas Akesson + */ + +#include "agi/agi.h" +#include "agi/keyboard.h" /* for clean_input() */ + +namespace Agi { + +static uint8 *words; /* words in the game */ +static uint32 words_flen; /* length of word memory */ + +/* + * Local implementation to avoid problems with strndup() used by + * gcc 3.2 Cygwin (see #635984) + */ +static char *my_strndup(char *src, int n) { + char *tmp = strncpy((char *)malloc(n + 1), src, n); + tmp[n] = 0; + return tmp; +} + +int load_words(char *fname) { + Common::File fp; + uint32 flen; + uint8 *mem = NULL; + char *path = NULL; + + words = NULL; + + path = fname; + + if (!fp.open(path)) { + report("Warning: can't open %s\n", path); + return err_OK /*err_BadFileOpen */ ; + } + report("Loading dictionary: %s\n", path); + + fp.seek(0, SEEK_END); + flen = fp.pos(); + words_flen = flen; + fp.seek(0, SEEK_SET); + + if ((mem = (uint8 *)calloc(1, flen + 32)) == NULL) { + fp.close(); + return err_NotEnoughMemory; + } + + fp.read(mem, flen); + fp.close(); + + words = mem; + + return err_OK; +} + +void unload_words() { + if (words != NULL) { + free(words); + words = NULL; + } +} + +/** + * 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. + * + * Thomas Åkesson, November 2001 + */ +int find_word(char *word, int *flen) { + int mchr = 0; /* matched chars */ + int len, fchr, id = -1; + uint8 *p = words; + uint8 *q = words + words_flen; + *flen = 0; + + debugC(2, kDebugLevelScripts, "find_word(%s)", word); + if (word[0] >= 'a' && word[0] <= 'z') + fchr = word[0] - 'a'; + else + return -1; + + len = strlen(word); + + /* Get the offset to the first word beginning with the + * right character + */ + p += READ_BE_UINT16(p + 2 * fchr); + + while (p[0] >= mchr) { + if (p[0] == mchr) { + p++; + /* Loop through all matching characters */ + while ((p[0] ^ word[mchr]) == 0x7F && mchr < len) { + mchr++; + p++; + } + /* Check if this is the last character of the word + * and if it matches + */ + if ((p[0] ^ word[mchr]) == 0xFF && mchr < len) { + mchr++; + if (word[mchr] == 0 || word[mchr] == 0x20) { + id = READ_BE_UINT16(p + 1); + *flen = mchr; + } + } + } + if (p >= q) + return -1; + + /* Step to the next word */ + while (p[0] < 0x80) + p++; + p += 3; + } + + return id; +} + +void dictionary_words(char *msg) { + char *p = NULL; + char *q = NULL; + int wid, wlen; + + debugC(2, kDebugLevelScripts, "msg = \"%s\"", msg); + + clean_input(); + + for (p = msg; p && *p && getvar(V_word_not_found) == 0;) { + if (*p == 0x20) + p++; + + if (*p == 0) + break; + + wid = find_word(p, &wlen); + debugC(2, kDebugLevelScripts, "find_word(p) == %d", wid); + + switch (wid) { + case -1: + debugC(2, kDebugLevelScripts, "unknown word"); + game.ego_words[game.num_ego_words].word = strdup(p); + q = game.ego_words[game.num_ego_words].word; + game.ego_words[game.num_ego_words].id = 19999; + setvar(V_word_not_found, 1 + game.num_ego_words); + game.num_ego_words++; + 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.ego_words[game.num_ego_words].id = wid; + game.ego_words[game.num_ego_words].word = my_strndup(p, wlen); + game.num_ego_words++; + p += wlen; + break; + } + + if (p != NULL && *p) { + debugC(2, kDebugLevelScripts, "p = %s", p); + *p = 0; + p++; + } + + if (q != NULL) { + for (; (*q != 0 && *q != 0x20); q++); + if (*q) { + *q = 0; + q++; + } + } + } + + debugC(4, kDebugLevelScripts, "num_ego_words = %d", game.num_ego_words); + if (game.num_ego_words > 0) { + setflag(F_entered_cli, true); + setflag(F_said_accepted_input, false); + } +} + +} // End of namespace Agi -- cgit v1.2.3