diff options
author | Eugene Sandulenko | 2010-08-17 09:28:20 +0000 |
---|---|---|
committer | Eugene Sandulenko | 2010-08-17 09:28:20 +0000 |
commit | 06960d33e15bc80f9912fa92f11dd82388199bd6 (patch) | |
tree | da4750e75d343227f9e522bbc1c5d983de831e85 /engines | |
parent | e075f0539568c5d14a61889a0233558376cdb5ce (diff) | |
download | scummvm-rg350-06960d33e15bc80f9912fa92f11dd82388199bd6.tar.gz scummvm-rg350-06960d33e15bc80f9912fa92f11dd82388199bd6.tar.bz2 scummvm-rg350-06960d33e15bc80f9912fa92f11dd82388199bd6.zip |
HUGO: Adding engine to the main tree
svn-id: r52137
Diffstat (limited to 'engines')
29 files changed, 9237 insertions, 0 deletions
diff --git a/engines/engines.mk b/engines/engines.mk index 2c1378290c..e542ffd933 100644 --- a/engines/engines.mk +++ b/engines/engines.mk @@ -60,6 +60,11 @@ DEFINES += -DENABLE_GROOVIE2 endif endif +ifdef ENABLE_HUGO +DEFINES += -DENABLE_HUGO=$(ENABLE_HUGO) +MODULES += engines/hugo +endif + ifdef ENABLE_KYRA DEFINES += -DENABLE_KYRA=$(ENABLE_KYRA) MODULES += engines/kyra diff --git a/engines/hugo/detection.cpp b/engines/hugo/detection.cpp new file mode 100755 index 0000000000..d780dda6d8 --- /dev/null +++ b/engines/hugo/detection.cpp @@ -0,0 +1,234 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "engines/advancedDetector.h" + +#include "hugo/hugo.h" +#include "hugo/intro.h" + +namespace Hugo { + +struct HugoGameDescription { + ADGameDescription desc; + GameType gameType; +}; + +uint32 HugoEngine::getFeatures() const { + return _gameDescription->desc.flags; +} + +static const PlainGameDescriptor hugoGames[] = { + // Games + {"hugo1", "Hugo 1: Hugo's House of Horrors"}, + {"hugo2", "Hugo 2: Hugo's Mystery Adventure"}, + {"hugo3", "Hugo 3: Hugo's Amazon Adventure"}, + + {0, 0} +}; + +static const HugoGameDescription gameDescriptions[] = { + + // Hugo1 DOS + { + { + "hugo1", 0, + AD_ENTRY1s("house.art", "c9403b2fe539185c9fd569b6cc4ff5ca", 14811), + Common::EN_ANY, + Common::kPlatformPC, + ADGF_NO_FLAGS, + Common::GUIO_NONE + }, + kGameTypeHugo1 + }, + // Hugo1 Windows + { + { + "hugo1", 0, + AD_ENTRY1s("objects.dat", "3ba0f108f7690a05a34c56a02fbe644a", 126488), + Common::EN_ANY, + Common::kPlatformWindows, + GF_PACKED, + Common::GUIO_NONE + }, + kGameTypeHugo1 + }, + // Hugo2 DOS + { + { + "hugo2", 0, + AD_ENTRY1s("objects.dat", "88a718cc0ff2b3b25d49aaaa69d6d52c", 155240), + Common::EN_ANY, + Common::kPlatformPC, + GF_PACKED, + Common::GUIO_NONE + }, + kGameTypeHugo2 + }, + // Hugo2 Windows + { + { + "hugo2", 0, + AD_ENTRY1s("objects.dat", "5df4ffc851e66a544c0e95e4e084a806", 158480), + Common::EN_ANY, + Common::kPlatformWindows, + GF_PACKED, + Common::GUIO_NONE + }, + kGameTypeHugo2 + }, + // Hugo3 DOS + { + { + "hugo3", 0, + AD_ENTRY1s("objects.dat", "bb1b061538a445f2eb99b682c0f506cc", 136419), + Common::EN_ANY, + Common::kPlatformPC, + GF_PACKED, + Common::GUIO_NONE + }, + kGameTypeHugo3 + }, + // Hugo3 Windows + { + { + "hugo3", 0, + AD_ENTRY1s("objects.dat", "c9a8af7aa14cc907434eecee3ddd06d3", 136638), + Common::EN_ANY, + Common::kPlatformWindows, + GF_PACKED, + Common::GUIO_NONE + }, + kGameTypeHugo3 + }, + + {AD_TABLE_END_MARKER, kGameTypeNone} +}; + +static const ADParams detectionParams = { + // Pointer to ADGameDescription or its superset structure + (const byte *)gameDescriptions, + // Size of that superset structure + sizeof(HugoGameDescription), + // Number of bytes to compute MD5 sum for + 5000, + // List of all engine targets + hugoGames, + // Structure for autoupgrading obsolete targets + 0, + // Name of single gameid (optional) + 0, + // List of files for file-based fallback detection (optional) + 0, + // Flags + 0, + // Additional GUI options (for every game} + Common::GUIO_NONE, + // Maximum directory depth + 1, + // List of directory globs + 0 +}; + +class HugoMetaEngine : public AdvancedMetaEngine { +public: + HugoMetaEngine() : AdvancedMetaEngine(detectionParams) {} + + const char *getName() const { + return "Hugo Engine"; + } + + const char *getOriginalCopyright() const { + return "Hugo Engine (C) 1989-1997 David P. Gray"; + } + + bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *gd) const; + + bool hasFeature(MetaEngineFeature f) const; +}; + +bool HugoMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *gd) const { + if (gd) { + *engine = new HugoEngine(syst, (const HugoGameDescription *)gd); + ((HugoEngine *)*engine)->initGame((const HugoGameDescription *)gd); + ((HugoEngine *)*engine)->initGamePart((const HugoGameDescription *)gd); + } + return gd != 0; +} + +bool HugoMetaEngine::hasFeature(MetaEngineFeature f) const { + return false; +} + +} // End of namespace Hugo + +#if PLUGIN_ENABLED_DYNAMIC(HUGO) +REGISTER_PLUGIN_DYNAMIC(HUGO, PLUGIN_TYPE_ENGINE, Hugo::HugoMetaEngine); +#else +REGISTER_PLUGIN_STATIC(HUGO, PLUGIN_TYPE_ENGINE, Hugo::HugoMetaEngine); +#endif + +namespace Hugo { + +void HugoEngine::initGame(const HugoGameDescription *gd) { + _gameType = gd->gameType; + _platform = gd->desc.platform; + _packedFl = (getFeatures() & GF_PACKED); +} + +void HugoEngine::initGamePart(const HugoGameDescription *gd) { + char tmpStr[8]; + _gameVariant = _gameType - 1 + (_platform == Common::kPlatformWindows ? 0 : 3); + +//Generate filenames + if (gd->desc.platform == Common::kPlatformWindows) + sprintf(tmpStr, "%s%c", gd->desc.gameid, 'w'); + else + sprintf(tmpStr, "%s%c", gd->desc.gameid, 'd'); + + sprintf(_initFilename, "%s-00.SAV", tmpStr); + sprintf(_saveFilename, "%s-%s.SAV", tmpStr, "%d"); + + switch (_gameVariant) { + case 0: + _introHandler = new intro_1w(*this); + break; + case 1: + _introHandler = new intro_2w(*this); + break; + case 2: + _introHandler = new intro_3w(*this); + break; + case 3: + _introHandler = new intro_1d(*this); + break; + case 4: + _introHandler = new intro_2d(*this); + break; + case 5: + _introHandler = new intro_3d(*this); + break; + } +} +} // End of namespace Gob diff --git a/engines/hugo/display.cpp b/engines/hugo/display.cpp new file mode 100755 index 0000000000..fde97c8f00 --- /dev/null +++ b/engines/hugo/display.cpp @@ -0,0 +1,509 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on original Hugo Trilogy source code + * + * Copyright (c) 1989-1995 David P. Gray + * + */ + +// Display.c - DIB related code for HUGOWIN + +#include "common/system.h" + +#include "hugo/game.h" +#include "hugo/hugo.h" +#include "hugo/display.h" +#include "hugo/file.h" +#include "hugo/util.h" + +namespace Hugo { + +#define CENTER -1 // Used to center text in x +#define NUM_COLORS 16 // Num colors to save in palette +#define DMAX 16 // Size of add/restore rect lists +#define BMAX (DMAX * 2) // Size of dirty rect blit list + +#define INX(X, B) (X >= B->x && X <= B->x + B->dx) +#define INY(Y, B) (Y >= B->y && Y <= B->y + B->dy) +#define OVERLAP(A, B) ((INX(A->x, B) || INX(A->x + A->dx, B) || INX(B->x, A) || INX(B->x + B->dx, A)) && (INY(A->y, B) || INY(A->y + A->dy, B) || INY(B->y, A) || INY(B->y + B->dy, A))) + +struct rect_t { // Rectangle used in Display list + int16 x; // Position in dib + int16 y; // Position in dib + int16 dx; // width + int16 dy; // height +}; + +Screen::Screen(HugoEngine &vm) : _vm(vm) { + +} + +void Screen::createPal() { + debugC(1, kDebugDisplay, "createPal"); + + g_system->setPalette(_vm._palette, 0, NUM_COLORS); +} + +// Translate from our 16-color palette to Windows logical palette index +uint32 Screen::GetPalIndex(byte color) { + debugC(1, kDebugDisplay, "getPalIndex(%d)", color); + + warning("STUB: GetPalIndex()"); + return 0; + //return(PALETTEINDEX(ctab[color])); +} + +// Create DIB headers and init palette +void Screen::initDisplay() { + debugC(1, kDebugDisplay, "initDisplay"); + // Create logical palette + createPal(); +} + +// Move an image from source to destination +void Screen::moveImage(image_pt srcImage, uint16 x1, uint16 y1, uint16 dx, uint16 dy, uint16 width1, image_pt dstImage, uint16 x2, uint16 y2, uint16 width2) { + int16 wrap_src = width1 - dx; // Wrap to next src row + int16 wrap_dst = width2 - dx; // Wrap to next dst row + int16 x; + + debugC(3, kDebugDisplay, "moveImage(srcImage, %d, %d, %d, %d, %d, dstImage, %d, %d, %d)", x1, y1, dx, dy, width1, x2, y2, width2); + + srcImage += y1 * width1 + x1; // Offset into src image + dstImage += y2 * width2 + x2; // offset into dst image + + while (dy--) { // For each row + for (x = dx; x--;) // For each column + *dstImage++ = *srcImage++; + srcImage += wrap_src; // Wrap to next line + dstImage += wrap_dst; + } +} + +void Screen::displayBackground() { + debugC(1, kDebugDisplay, "displayBackground"); + + g_system->copyRectToScreen(_frontBuffer, 320, 0, 0, 320, 200); +} + +// Blit the supplied rectangle from _frontBuffer to the screen +void Screen::displayRect(int16 x, int16 y, int16 dx, int16 dy) { + + /* TODO: Suppress this commented block if it's confirmed to be useless + // Find destination rectangle from current scaling + int16 sx = (int16)((int32)config.cx * x / XPIX); + int16 sy = (int16)((int32)config.cy * (y - DIBOFF_Y) / VIEW_DY); + int16 dsx = (int16)((int32)config.cx * dx / XPIX); + int16 dsy = (int16)((int32)config.cy * dy / VIEW_DY); + */ + debugC(3, kDebugDisplay, "displayRect(%d, %d, %d, %d)", x, y, dx, dy); + + g_system->copyRectToScreen(&_frontBuffer[x + y * 320], 320, x, y, dx, dy); +} + +void Screen::remapPal(uint16 oldIndex, uint16 newIndex) { +// Change a color by remapping supplied palette index with new index + debugC(1, kDebugDisplay, "Remap_pal(%d, %d)", oldIndex, newIndex); + + warning("STUB: Remap_pal()"); + //bminfo.bmiColors[oldIndex] = ctab[newIndex]; +} + +void Screen::savePal(Common::WriteStream *f) { + debugC(1, kDebugDisplay, "savePal"); + + warning("STUB: savePal()"); + //fwrite(bminfo.bmiColors, sizeof(bminfo.bmiColors), 1, f); +} + +void Screen::restorePal(Common::SeekableReadStream *f) { + debugC(1, kDebugDisplay, "restorePal"); + + warning("STUB: restorePal()"); + //fread(bminfo.bmiColors, sizeof(bminfo.bmiColors), 1, f); +} + + +// Set the new background color +void Screen::setBackgroundColor(long color) { + debugC(1, kDebugDisplay, "setBackgroundColor(%ld)", color); + + // How??? Translate existing pixels in dib before objects rendered? +} + +// Write the supplied character in the supplied color to x,y pixel coords +void Screen::writeChar(int16 x, int16 y, char c, byte color) { + debugC(1, kDebugDisplay, "writeChar(%d, %d, %c, %d)", x, y, c, color); + + warning("STUB: writeChar()"); + // x = (int16)((long) x * config.cx / XPIX); + // y = (int16)((long) y * config.cy / YPIX); + // SetTextColor(hDC, GetPalIndex(color)); + // TextOut(hDC, x, y, &c, 1); +} + +// Clear prompt line for next command +void Screen::clearPromptLine() { + debugC(1, kDebugDisplay, "clearPromptLine"); +} + + +// Return the overlay state (Foreground/Background) of the currently +// processed object by looking down the current column for an overlay +// base bit set (in which case the object is foreground). +overlayState_t Screen::findOvl(seq_t *seq_p, image_pt dst_p, uint16 y) { + debugC(4, kDebugDisplay, "findOvl"); + + for (; y < seq_p->lines; y++) { // Each line in object + image_pt ovb_p = _vm.getBaseBoundaryOverlay() + ((uint16)(dst_p - _frontBuffer) >> 3); // Ptr into overlay bits + if (*ovb_p & (0x80 >> ((uint16)(dst_p - _frontBuffer) & 7))) // Overlay bit is set + return FG; // Found a bit - must be foreground + dst_p += XPIX; + } + + return BG; // No bits set, must be background +} + +// Merge an object frame into _frontBuffer at sx, sy and update rectangle list. +// If fore TRUE, force object above any overlay +void Screen::displayFrame(int sx, int sy, seq_t *seq, bool foreFl) { + overlayState_t overlayState = UNDEF; // Overlay state of object + image_pt image; // Ptr to object image data + image_pt subFrontBuffer; // Ptr to offset in _frontBuffer + image_pt overlay; // Ptr to overlay data + int16 frontBufferwrap; // Wrap dst_p to next line + int16 imageWrap; // Wrap src_p to next line + uint16 x, y; // Index into object data + + debugC(3, kDebugDisplay, "displayFrame(%d, %d, seq, %d)", sx, sy, (foreFl) ? 1 : 0); + + image = seq->imagePtr; // Source ptr + subFrontBuffer = &_frontBuffer[sy * XPIX + sx]; // Destination ptr + overlay = &_vm.getFirstOverlay()[(sy * XPIX + sx) >> 3]; // Overlay ptr + frontBufferwrap = XPIX - seq->x2 - 1; // Wraps dest_p after each line + imageWrap = seq->bytesPerLine8 - seq->x2 - 1; + + for (y = 0; y < seq->lines; y++) { // Each line in object + for (x = 0; x <= seq->x2; x++) { + if (*image) { // Non-transparent + overlay = _vm.getFirstOverlay() + ((uint16)(subFrontBuffer - _frontBuffer) >> 3); // Ptr into overlay bits + if (*overlay & (0x80 >> ((uint16)(subFrontBuffer - _frontBuffer) & 7))) { // Overlay bit is set + if (overlayState == UNDEF) // Overlay defined yet? + overlayState = findOvl(seq, subFrontBuffer, y);// No, find it. + if (foreFl || overlayState == FG) // Object foreground + *subFrontBuffer = *image; // Copy pixel + } else // No overlay + *subFrontBuffer = *image; // Copy pixel + } + image++; + subFrontBuffer++; + } + image += imageWrap; + subFrontBuffer += frontBufferwrap; + } + + // Add this rectangle to the display list + displayList(D_ADD, sx, sy, seq->x2 + 1, seq->lines); +} + +// Merge rectangles A,B leaving result in B +void Screen::merge(rect_t *rectA, rect_t *rectB) { + debugC(6, kDebugDisplay, "merge"); + + int16 xa = rectA->x + rectA->dx; // Find x2,y2 for each rectangle + int16 xb = rectB->x + rectB->dx; + int16 ya = rectA->y + rectA->dy; + int16 yb = rectB->y + rectB->dy; + + rectB->x = MIN(rectA->x, rectB->x); // Minimum x,y + rectB->y = MIN(rectA->y, rectB->y); + rectB->dx = MAX(xa, xb) - rectB->x; // Maximum dx,dy + rectB->dy = MAX(ya, yb) - rectB->y; +} + +// Coalesce the rectangles in the restore/add list into one unified +// blist. len is the sizes of alist or rlist. blen is current length +// of blist. bmax is the max size of the blist. Note that blist can +// have holes, in which case dx = 0. Returns used length of blist. +int16 Screen::mergeLists(rect_t *list, rect_t *blist, int16 len, int16 blen, int16 bmax) { + int16 coalesce[BMAX]; // List of overlapping rects + + debugC(4, kDebugDisplay, "mergeLists"); + + // Process the list + for (int16 a = 0; a < len; a++, list++) { + // Compile list of overlapping rectangles in blit list + int16 c = 0; + rect_t *bp = blist; + for (int16 b = 0; b < blen; b++, bp++) + if (bp->dx) // blist entry used + if (OVERLAP(list, bp)) + coalesce[c++] = b; + + // Any overlapping blit rects? + if (c == 0) // None, add a new entry + blist[blen++] = *list; + else { // At least one overlapping + // Merge add-list entry with first blist entry + bp = &blist[coalesce[0]]; + merge(list, bp); + + // Merge any more blist entries + while (--c) { + rect_t *cp = &blist[coalesce[c]]; + merge(cp, bp); + cp->dx = 0; // Delete entry + } + } + } + return blen; +} + +// Process the display list +// Trailing args are int16 x,y,dx,dy for the D_ADD operation +void Screen::displayList(dupdate_t update, ...) { + static int16 addIndex, restoreIndex; // Index into add/restore lists + static rect_t restoreList[DMAX]; // The restore list + static rect_t addList[DMAX]; // The add list + static rect_t blistList[BMAX]; // The blit list + int16 blitLength = 0; // Length of blit list + rect_t *p; // Ptr to dlist entry + va_list marker; // Args used for D_ADD operation + + debugC(6, kDebugDisplay, "displayList"); + + switch (update) { + case D_INIT: // Init lists, restore whole screen + addIndex = restoreIndex = 0; + memcpy(_frontBuffer, _backBuffer, sizeof(_frontBuffer)); + break; + case D_ADD: // Add a rectangle to list + if (addIndex >= DMAX) { + Utils::Warn(false, "Display list exceeded"); + return; + } + va_start(marker, update); // Initialize variable arguments + p = &addList[addIndex]; + p->x = va_arg(marker, int); // x + p->y = va_arg(marker, int); // y + p->dx = va_arg(marker, int); // dx + p->dy = va_arg(marker, int); // dy + va_end(marker); // Reset variable arguments + addIndex++; + break; + case D_DISPLAY: // Display whole list + // Don't blit if newscreen just loaded because _frontBuffer will + // get blitted via InvalidateRect() at end of this cycle + // and blitting here causes objects to appear too soon. + if (_vm.getGameStatus().newScreenFl) { + _vm.getGameStatus().newScreenFl = false; + break; + } + + // Coalesce restore-list, add-list into combined blit-list + blitLength = mergeLists(restoreList, blistList, restoreIndex, blitLength, BMAX); + blitLength = mergeLists(addList, blistList, addIndex, blitLength, BMAX); + + // Blit the combined blit-list + for (restoreIndex = 0, p = blistList; restoreIndex < blitLength; restoreIndex++, p++) + if (p->dx) // Marks a used entry + displayRect(p->x, p->y, p->dx, p->dy); + break; + case D_RESTORE: // Restore each rectangle + for (restoreIndex = 0, p = addList; restoreIndex < addIndex; restoreIndex++, p++) { + // Restoring from _backBuffer to _frontBuffer + restoreList[restoreIndex] = *p; // Copy add-list to restore-list + moveImage(_backBuffer, p->x, p->y, p->dx, p->dy, XPIX, _frontBuffer, p->x, p->y, XPIX); + } + addIndex = 0; // Reset add-list + break; + } +} + +void Screen::writeChr(int sx, int sy, byte color, char *local_fontdata) { + /* + Write supplied character (font data) at sx,sy in supplied color + Font data as follows: + + *(fontdata+1) = Font Height (pixels) + *(fontdata+1) = Font Width (pixels) + *(fontdata+x) = Font Bitmap (monochrome) + */ + + debugC(2, kDebugDisplay, "writeChr(%d, %d, %d, %d)", sx, sy, color, local_fontdata[0]); + + byte height = local_fontdata[0]; + byte width = 8; //local_fontdata[1]; + + //warning("STUB: writechr(sx %u, sy %u, color %u, height %u, width %u)", sx, sy, color, height, width); + + // This can probably be optimized quite a bit... + for (int y = 0; y < height; ++y) + for (int x = 0; x < width; ++x) { + int pixel = y * width + x; + int bitpos = pixel % 8; + int offset = pixel / 8; + byte bitTest = (1 << bitpos); + if ((local_fontdata[2 + offset] & bitTest) == bitTest) + _frontBuffer[(sy + y) * 320 + sx + x] = color; + //printf("offset: %u, bitpos %u\n", offset, bitpos); + } +} + +// Returns height of characters in current font +int16 Screen::fontHeight() { + debugC(2, kDebugDisplay, "fontHeight"); + + static int16 height[NUM_FONTS] = {5, 7, 8}; + return(height[_fnt - FIRST_FONT]); +} + +/* TODO: Suppress block if it's confirmed to be useless */ +// static int16 Char_len (char c) { +// /* Returns length of single character in pixels */ +// return (*(_font[_fnt][c] + 1) + 1); +// } + + +// Returns length of supplied string in pixels +int16 Screen::stringLength(char *s) { + int16 sum; + byte **fontArr = _font[_fnt]; + + debugC(2, kDebugDisplay, "stringLength(%s)", s); + + for (sum = 0; *s; s++) + sum += *(fontArr[*s] + 1) + 1; + + return(sum); +} + +// Return x which would center supplied string +int16 Screen::center(char *s) { + debugC(1, kDebugDisplay, "center(%s)", s); + + return ((int16)((XPIX - stringLength(s)) >> 1)); +} + +// Write string at sx,sy in supplied color in current font +// If sx == CENTER, center it +void Screen::writeStr(int16 sx, int16 sy, char *s, byte color) { + byte **font = _font[_fnt]; + + debugC(2, kDebugDisplay, "writeStr(%d, %d, %s, %d)", sx, sy, s, color); + + if (sx == CENTER) + sx = center(s); + + for (; *s; s++) { + writeChr(sx, sy, color, (char *)font[*s]); + sx += *(font[*s] + 1) + 1; + } +} + +// Shadowed version of writestr +void Screen::shadowStr(int16 sx, int16 sy, char *s, byte color) { + debugC(1, kDebugDisplay, "shadowStr(%d, %d, %s, %d)", sx, sy, s, color); + + if (sx == CENTER) + sx = center(s); + + writeStr(sx + 1, sy + 1, s, _TBLACK); + writeStr(sx, sy, s, color); +} + +// Load font file, construct font ptrs and reverse data bytes +void Screen::loadFont(int16 fontId) { + byte height, width; + static bool fontLoadedFl[NUM_FONTS] = {0, 0, 0}; + + debugC(2, kDebugDisplay, "loadFont(%d)", fontId); + + _fnt = fontId - FIRST_FONT; // Set current font number + + if (fontLoadedFl[_fnt]) // If already loaded, return + return; + + fontLoadedFl[_fnt] = true; + _vm.file().readUIFItem(fontId, _fontdata[_fnt]); + + // Compile font ptrs. Note: First ptr points to height,width of font + _font[_fnt][0] = _fontdata[_fnt]; // Store height,width of fonts + + int16 offset = 2; // Start at fontdata[2] ([0],[1] used for height,width) + + // Setup the font array (127 characters) + for (int i = 1; i < 128; i++) { + _font[_fnt][i] = _fontdata[_fnt] + offset; + height = *(_fontdata[_fnt] + offset); + width = *(_fontdata[_fnt] + offset + 1); + + int16 size = height * ((width + 7) >> 3); + for (int j = 0; j < size; j++) + Utils::reverseByte(&_fontdata[_fnt][offset + 2 + j]); + + offset += 2 + size; + } + + // for (i = 0; i < 128; ++i) { + // if( (char)i != 'f' && (char)i != '\\'){ + // continue; + // } + // int myHeight = _font[_fnt][i][0]; + // int myWidth = _font[_fnt][i][1]; + // printf("\n\nFor the letter %c, (%u, %u):\n", i, myWidth, myHeight); + // for (int y = 0; y < myHeight; ++y) { + // for (int x = 0; x < 8; ++x) { + // int pixel = y * (8) + x; + // int bitpos = pixel % 8; + // int offset = pixel / 8; + // byte bitTest = (1 << bitpos); + // if ((_font[_fnt][i][2 + offset] & bitTest) == bitTest) + // printf("1"); + // else + // printf("0"); + // } + // printf("\n"); + // } + // } +} + +void Screen::userHelp() { +// Introduce user to the game +// DOS versions Only + Utils::Box(BOX_ANY , "F1 - Press F1 again\n" + " for instructions\n" + "F2 - Sound on/off\n" + "F3 - Recall last line\n" + "F4 - Save game\n" + "F5 - Restore game\n" + "F6 - Inventory\n" + "F8 - Turbo button\n" + "F9 - Boss button\n\n" + "ESC - Return to game"); +} + +} // end of namespace Hugo diff --git a/engines/hugo/display.h b/engines/hugo/display.h new file mode 100755 index 0000000000..ceb87481b0 --- /dev/null +++ b/engines/hugo/display.h @@ -0,0 +1,109 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on original Hugo Trilogy source code + * + * Copyright (c) 1989-1995 David P. Gray + * + */ + +#ifndef HUGO_DISPLAY_H +#define HUGO_DISPLAY_H +namespace Hugo { + +enum overlayState_t {UNDEF, FG, BG}; // Overlay state +struct rect_t; + +class Screen { +public: + Screen(HugoEngine &vm); + + int16 fontHeight(); + int16 stringLength(char *s); + + void displayBackground(); + void displayFrame(int sx, int sy, seq_t *seq, bool foreFl); + void displayList(dupdate_t update, ...); + void displayRect(int16 x, int16 y, int16 dx, int16 dy); + void initDisplay(); + void loadFont(int16 fontId); + void moveImage(image_pt srcImage, uint16 x1, uint16 y1, uint16 dx, uint16 dy, uint16 width1, image_pt dstImage, uint16 x2, uint16 y2, uint16 width2); + void remapPal(uint16 oldIndex, uint16 newIndex); + void restorePal(Common::SeekableReadStream *f); + void savePal(Common::WriteStream *f); + void setBackgroundColor(long color); + void shadowStr(int16 sx, int16 sy, char *s, byte color); + void userHelp(); + void writeChar(int16 x, int16 y, char c, byte color); + void writeStr(int16 sx, int16 sy, char *s, byte color); + + icondib_t &getIconBuffer() { + return _iconBuffer; + } + viewdib_t &getBackBuffer() { + return _backBuffer; + } + viewdib_t &getBackBufferBackup() { + return _backBufferBackup; + } + viewdib_t &getFrontBuffer() { + return _frontBuffer; + } + viewdib_t &getGUIBuffer() { + return _GUIBuffer; + } + +private: + HugoEngine &_vm; + + // Fonts used in dib (non-GDI) + byte _fnt; // Current font number + byte _fontdata[NUM_FONTS][FONTSIZE]; // Font data + byte *_font[NUM_FONTS][FONT_LEN]; // Ptrs to each char + + viewdib_t _frontBuffer; + viewdib_t _backBuffer; + viewdib_t _GUIBuffer; // User interface images + viewdib_t _backBufferBackup; // Backup _backBuffer during inventory + icondib_t _iconBuffer; // Inventory icon DIB + + void createPal(); + overlayState_t findOvl(seq_t *seq_p, image_pt dst_p, uint16 y); + void merge(rect_t *rectA, rect_t *rectB); + int16 mergeLists(rect_t *list, rect_t *blist, int16 len, int16 blen, int16 bmax); + void writeChr(int sx, int sy, byte color, char *local_fontdata); + int16 center(char *s); + +// Also used in rout.cpp when DEBUG_ROUTE is defined + unsigned int GetPalIndex(byte color); + +// Useless ? + void clearPromptLine(); + +}; + +} // end of namespace Hugo +#endif //HUGO_DISPLAY_H diff --git a/engines/hugo/engine.cpp b/engines/hugo/engine.cpp new file mode 100755 index 0000000000..ef93ef6279 --- /dev/null +++ b/engines/hugo/engine.cpp @@ -0,0 +1,993 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on original Hugo 1-3 Trilogy source code + * + * Copyright (c) 1989-1995 David P. Gray + * + */ + +#include "common/system.h" +#include "common/random.h" +#include "common/EventRecorder.h" + +#include "hugo/game.h" +#include "hugo/hugo.h" +#include "hugo/engine.h" +#include "hugo/global.h" +#include "hugo/file.h" +#include "hugo/schedule.h" +#include "hugo/display.h" +#include "hugo/parser.h" +#include "hugo/route.h" +#include "hugo/util.h" +#include "hugo/sound.h" + +namespace Hugo { + +#define EDGE 10 // Closest object can get to edge of screen +#define EDGE2 (EDGE * 2) // Push object further back on edge collision +#define SHIFT 8 // Place hero this far inside bounding box +#define MAX_OBJECTS 128 // Used in Update_images() +#define BOUND(X, Y) ((_boundary[Y * XBYTES + X / 8] & (0x80 >> X % 8)) != 0) // Boundary bit set + +config_t _config; // User's config +maze_t _maze = {false, 0, 0, 0, 0, 0, 0, 0, 0}; // Default to not in maze +hugo_boot_t _boot; // Boot info structure file +char _textBoxBuffer[MAX_BOX]; // Buffer for text box +command_t _line = ""; // Line of user text input + + +// Sets the playlist to be the default tune selection +void HugoEngine::initPlaylist(bool playlist[MAX_TUNES]) { + debugC(1, kDebugEngine, "initPlaylist"); + + for (int16 i = 0; i < MAX_TUNES; i++) + playlist[i] = false; + for (int16 i = 0; _defltTunes[i] != -1; i++) + playlist[_defltTunes[i]] = true; +} + +// Initialize the dynamic game status +void HugoEngine::initStatus() { + debugC(1, kDebugEngine, "initStatus"); + _status.initSaveFl = false; // Don't force initial save + _status.storyModeFl = false; // Not in story mode + _status.gameOverFl = false; // Hero not knobbled yet + _status.recordFl = false; // Not record mode + _status.playbackFl = false; // Not playback mode + _status.demoFl = false; // Not demo mode + _status.textBoxFl = false; // Not processing a text box +// Strangerke - Not used ? +// _status.mmtime = false; // Multimedia timer support + _status.lookFl = false; // Toolbar "look" button + _status.recallFl = false; // Toolbar "recall" button + _status.leftButtonFl = false; // Left mouse button pressed + _status.rightButtonFl = false; // Right mouse button pressed + _status.newScreenFl = false; // Screen not just loaded + _status.jumpExitFl = false; // Can't jump to a screen exit + _status.godModeFl = false; // No special cheats allowed + _status.helpFl = false; // Not calling WinHelp() + _status.path[0] = 0; // Path to write files + _status.saveSlot = 0; // Slot to save/restore game + _status.screenWidth = 0; // Desktop screen width + + // Initialize every start of new game + _status.tick = 0; // Tick count + _status.saveTick = 0; // Time of last save + _status.viewState = V_IDLE; // View state + _status.inventoryState = I_OFF; // Inventory icon bar state + _status.inventoryHeight = 0; // Inventory icon bar pos + _status.inventoryObjId = -1; // Inventory object selected (none) + _status.routeIndex = -1; // Hero not following a route + _status.go_for = GO_SPACE; // Hero walking to space + _status.go_id = -1; // Hero not walking to anything +} + +// Initialize default config values. Must be done before Initialize(). +// Reset needed to save config.cx,cy which get splatted during OnFileNew() +void HugoEngine::initConfig(inst_t action) { + static int16 cx, cy; // Save window size, pos + int16 i; + + debugC(1, kDebugEngine, "initConfig(%d)", action); + + switch (action) { + case INSTALL: + _config.musicFl = true; // Music state initially on + _config.soundFl = true; // Sound state initially on + _config.turboFl = false; // Turbo state initially off + _config.backgroundMusicFl = false; // No music when inactive + _config.cx = VIEW_DX * 2; // Window view size + _config.cy = VIEW_DY * 2; + +// _config.wx = 0; +// _config.wy = 0; + + _config.musicVolume = 85; // Music volume % + _config.soundVolume = 100; // Sound volume % + initPlaylist(_config.playlist); // Initialize default tune playlist + + HugoEngine::get().file().readBootFile(); // Read startup structure + HugoEngine::get().file().readConfig(); // Read user's saved config + + cx = _config.cx; // Save these around OnFileNew() + cy = _config.cy; +// wx = _config.wx; +// wy = _config.wy; + break; + case RESET: + _config.cx = cx; // Restore cx, cy + _config.cy = cy; +// _config.wx = wx; +// _config.wy = wy; + + // Find first tune and play it + for (i = 0; i < MAX_TUNES; i++) + if (_config.playlist[i]) { + sound().playMusic(i); + break; + } + + HugoEngine::get().file().initSavedGame(); // Initialize saved game + break; + case RESTORE: + warning("Unhandled action RESTORE"); + break; + } +} +void HugoEngine::initialize() { + debugC(1, kDebugEngine, "initialize"); + + sound().initSound(INSTALL); + HugoEngine::get().scheduler().initEventQueue(); // Init scheduler stuff + screen().initDisplay(); // Create Dibs and palette + HugoEngine::get().file().openDatabaseFiles(); // Open database files + calcMaxScore(); // Initialise maxscore + + _rnd = new Common::RandomSource(); + g_eventRec.registerRandomSource(*_rnd, "hugo"); + + _rnd->setSeed(42); // Kick random number generator + + switch (getGameType()) { + case kGameTypeHugo1: + _episode = "\"HUGO'S HOUSE OF HORRORS\""; + _picDir = ""; + break; + case kGameTypeHugo2: + _episode = "\"Hugo's Mystery Adventure\""; + _picDir = "hugo2/"; + break; + case kGameTypeHugo3: + _episode = "\"Hugo's Amazon Adventure\""; + _picDir = "hugo3/"; + break; + default: + error("Unknown game"); + } +} + +// Restore all resources before termination +void HugoEngine::shutdown() { + debugC(1, kDebugEngine, "shutdown"); + + sound().initSound(RESTORE); + + HugoEngine::get().file().closeDatabaseFiles(); + if (_status.recordFl || _status.playbackFl) + HugoEngine::get().file().closePlaybackFile(); + freeObjects(); +} + +void HugoEngine::readObjectImages() { + debugC(1, kDebugEngine, "readObjectImages"); + + for (int i = 0; i < _numObj; i++) + HugoEngine::get().file().readImage(i, &_objects[i]); +} + +// Read the uif image file (inventory icons) +void HugoEngine::readUIFImages() { + debugC(1, kDebugEngine, "readUIFImages"); + + HugoEngine::get().file().readUIFItem(UIF_IMAGES, screen().getGUIBuffer()); // Read all uif images +} + +// Read scenery, overlay files for given screen number +void HugoEngine::readScreenFiles(int screenNum) { + debugC(1, kDebugEngine, "readScreenFiles(%d)", screenNum); + + HugoEngine::get().file().readBackground(screenNum); // Scenery file + memcpy(screen().getBackBuffer(), screen().getFrontBuffer(), sizeof(screen().getFrontBuffer()));// Make a copy + HugoEngine::get().file().readOverlay(screenNum, _boundary, BOUNDARY); // Boundary file + HugoEngine::get().file().readOverlay(screenNum, _overlay, OVERLAY); // Overlay file + HugoEngine::get().file().readOverlay(screenNum, _ovlBase, OVLBASE); // Overlay base file +} + +// Update all object positions. Process object 'local' events +// including boundary events and collisions +void HugoEngine::moveObjects() { + object_t *obj; + seq_t *currImage; + int x1, x2, y1, y2; // object coordinates + int dx, dy; // Allowable motion wrt boundary + char radius; // Radius for chase (8 bit signed) + + debugC(4, kDebugEngine, "moveObjects"); + + // If route mode enabled, do special route processing + if (_status.routeIndex >= 0) + route().processRoute(); + + // Perform any adjustments to velocity based on special path types + // and store all (visible) object baselines into the boundary file. + // Don't store foreground or background objects + for (int i = 0; i < _numObj; i++) { + obj = &_objects[i]; // Get pointer to object + currImage = obj->currImagePtr; // Get ptr to current image + if (obj->screenIndex == *_screen_p) { + switch (obj->pathType) { + case CHASE: + case CHASE2: + radius = obj->radius; // Default to object's radius + if (radius < 0) // If radius infinity, use closer value + radius = DX; + + dx = _hero->x + _hero->currImagePtr->x1 - obj->x - currImage->x1; + dy = _hero->y + _hero->currImagePtr->y2 - obj->y - currImage->y2 - 1; + if (abs(dx) <= radius) + obj->vx = 0; + else + obj->vx = dx > 0 ? MIN(dx, obj->vxPath) : MAX(dx, -obj->vxPath); + if (abs(dy) <= radius) + obj->vy = 0; + else + obj->vy = dy > 0 ? MIN(dy, obj->vyPath) : MAX(dy, -obj->vyPath); + + // Set first image in sequence (if multi-seq object) + switch (obj->seqNumb) { + case 4: + if (!obj->vx) { // Got 4 directions + if (obj->vx != obj->oldvx) // vx just stopped + if (dy >= 0) + obj->currImagePtr = obj->seqList[DOWN].seqPtr; + else + obj->currImagePtr = obj->seqList[_UP].seqPtr; + } else if (obj->vx != obj->oldvx) + if (dx > 0) + obj->currImagePtr = obj->seqList[RIGHT].seqPtr; + else + obj->currImagePtr = obj->seqList[LEFT].seqPtr; + break; + case 3: + case 2: + if (obj->vx != obj->oldvx) // vx just stopped + if (dx > 0) // Left & right only + obj->currImagePtr = obj->seqList[RIGHT].seqPtr; + else + obj->currImagePtr = obj->seqList[LEFT].seqPtr; + break; + } + + if (obj->vx || obj->vy) + obj->cycling = CYCLE_FORWARD; + else { + obj->cycling = NOT_CYCLING; + boundaryCollision(obj); // Must have got hero! + } + obj->oldvx = obj->vx; + obj->oldvy = obj->vy; + currImage = obj->currImagePtr; // Get (new) ptr to current image + break; + case WANDER2: + case WANDER: + if (!_rnd->getRandomNumber(3 * NORMAL_TPS)) { // Kick on random interval + obj->vx = _rnd->getRandomNumber(obj->vxPath << 1) - obj->vxPath; + obj->vy = _rnd->getRandomNumber(obj->vyPath << 1) - obj->vyPath; + + // Set first image in sequence (if multi-seq object) + if (obj->seqNumb > 1) { + if (!obj->vx && (obj->seqNumb >= 4)) { + if (obj->vx != obj->oldvx) // vx just stopped + if (obj->vy > 0) + obj->currImagePtr = obj->seqList[DOWN].seqPtr; + else + obj->currImagePtr = obj->seqList[_UP].seqPtr; + } else if (obj->vx != obj->oldvx) + if (obj->vx > 0) + obj->currImagePtr = obj->seqList[RIGHT].seqPtr; + else + obj->currImagePtr = obj->seqList[LEFT].seqPtr; + } + obj->oldvx = obj->vx; + obj->oldvy = obj->vy; + currImage = obj->currImagePtr; // Get (new) ptr to current image + } + if (obj->vx || obj->vy) + obj->cycling = CYCLE_FORWARD; + break; + default: + ; // Really, nothing + } + // Store boundaries + if ((obj->cycling > ALMOST_INVISIBLE) && (obj->priority == FLOATING)) + storeBoundary(obj->x + currImage->x1, obj->x + currImage->x2, obj->y + currImage->y2); + } + } + + // Move objects, allowing for boundaries + for (int i = 0; i < _numObj; i++) { + obj = &_objects[i]; // Get pointer to object + if ((obj->screenIndex == *_screen_p) && (obj->vx || obj->vy)) { + // Only process if it's moving + + // Do object movement. Delta_x,y return allowed movement in x,y + // to move as close to a boundary as possible without crossing it. + currImage = obj->currImagePtr; // Get ptr to current image + x1 = obj->x + currImage->x1; // Left edge of object + x2 = obj->x + currImage->x2; // Right edge + y1 = obj->y + currImage->y1; // Top edge + y2 = obj->y + currImage->y2; // Bottom edge + + if ((obj->cycling > ALMOST_INVISIBLE) && (obj->priority == FLOATING)) + clearBoundary(x1, x2, y2); // Clear our own boundary + dx = deltaX(x1, x2, obj->vx, y2); + if (dx != obj->vx) { + // An object boundary collision! + boundaryCollision(obj); + obj->vx = 0; + } + + dy = deltaY(x1, x2, obj->vy, y2); + + if (dy != obj->vy) { + // An object boundary collision! + boundaryCollision(obj); + obj->vy = 0; + } + + if ((obj->cycling > ALMOST_INVISIBLE) && (obj->priority == FLOATING)) + storeBoundary(x1, x2, y2); // Re-store our own boundary + + obj->x += dx; // Update object position + obj->y += dy; + + // Don't let object go outside screen + if (x1 < EDGE) + obj->x = EDGE2; + if (x2 > (XPIX - EDGE)) + obj->x = XPIX - EDGE2 - (x2 - x1); + if (y1 < EDGE) + obj->y = EDGE2; + if (y2 > (YPIX - EDGE)) + obj->y = YPIX - EDGE2 - (y2 - y1); + + if ((obj->vx == 0) && (obj->vy == 0) && (obj->pathType != WANDER2) && (obj->pathType != CHASE2)) + obj->cycling = NOT_CYCLING; + } + } + + // Clear all object baselines from the boundary file. + for (int i = 0; i < _numObj; i++) { + obj = &_objects[i]; // Get pointer to object + currImage = obj->currImagePtr; // Get ptr to current image + if ((obj->screenIndex == *_screen_p) && (obj->cycling > ALMOST_INVISIBLE) && (obj->priority == FLOATING)) + clearBoundary(obj->oldx + currImage->x1, obj->oldx + currImage->x2, obj->oldy + currImage->y2); + } + + // If maze mode is enabled, do special maze processing + if (_maze.enabledFl) + processMaze(); +} + +// Return maximum allowed movement (from zero to vx) such that object does +// not cross a boundary (either background or another object) +int HugoEngine::deltaX(int x1, int x2, int vx, int y) { +// Explanation of algorithm: The boundaries are drawn as contiguous +// lines 1 pixel wide. Since DX,DY are not necessarily 1, we must +// detect boundary crossing. If vx positive, examine each pixel from +// x1 old to x2 new, else x2 old to x1 new, both at the y2 line. +// If vx zero, no need to check. If vy non-zero then examine each +// pixel on the line segment x1 to x2 from y old to y new. +// Fix from Hugo I v1.5: +// Note the diff is munged in the return statement to cater for a special +// cases arising from differences in image widths from one sequence to +// another. The problem occurs reversing direction at a wall where the +// new image intersects before the object can move away. This is cured +// by comparing the intersection with half the object width pos. If the +// intersection is in the other half wrt the intended direction, use the +// desired vx, else use the computed delta. i.e. believe the desired vx + int b; + + debugC(3, kDebugEngine, "deltaX(%d, %d, %d, %d)", x1, x2, vx, y); + + if (vx == 0) + return(0); // Object stationary + + y *= XBYTES; // Offset into boundary file + if (vx > 0) { + // Moving to right + for (int i = x1 >> 3; i <= (x2 + vx) >> 3; i++) // Search by byte + if ((b = Utils::firstBit((byte)(_boundary[y + i] | _objBound[y + i]))) < 8) { // b is index or 8 + // Compute x of boundary and test if intersection + b += i << 3; + if ((b >= x1) && (b <= x2 + vx)) + return((b < x1 + ((x2 - x1) >> 1)) ? vx : b - x2 - 1); // return dx + } + } else { + // Moving to left + for (int i = x2 >> 3; i >= (x1 + vx) >> 3; i--)// Search by byte + if ((b = Utils::lastBit((byte)(_boundary[y + i] | _objBound[y + i]))) < 8) { // b is index or 8 + // Compute x of boundary and test if intersection + b += i << 3; + if ((b >= x1 + vx) && (b <= x2)) + return((b > x1 + ((x2 - x1) >> 1)) ? vx : b - x1 + 1); // return dx + } + } + return(vx); +} + +// Similar to Delta_x, but for movement in y direction. Special case of +// bytes at end of line segment; must only count boundary bits falling on +// line segment. +int HugoEngine::deltaY(int x1, int x2, int vy, int y) { + int inc, i, j, b; + + debugC(3, kDebugEngine, "deltaY(%d, %d, %d, %d)", x1, x2, vy, y); + + if (vy == 0) + return(0); // Object stationary + + inc = (vy > 0 ? 1 : -1); + for (j = y + inc; j != (y + vy + inc); j += inc) //Search by byte + for (i = x1 >> 3; i <= x2 >> 3; i++) + if (b = _boundary[j * XBYTES + i] | _objBound[j * XBYTES + i]) { // Any bit set + // Make sure boundary bits fall on line segment + if (i == (x2 >> 3)) // Adjust right end + b &= 0xff << ((i << 3) + 7 - x2); + else if (i == (x1 >> 3)) // Adjust left end + b &= 0xff >> (x1 - (i << 3)); + if (b) + return(j - y - inc); + } + return(vy); +} + +// Store a horizontal line segment in the object boundary file +void HugoEngine::storeBoundary(int x1, int x2, int y) { + byte *b; // ptr to boundary byte + + debugC(5, kDebugEngine, "storeBoundary(%d, %d, %d)", x1, x2, y); + + for (int i = x1 >> 3; i <= x2 >> 3; i++) { // For each byte in line + b = &_objBound[y * XBYTES + i]; // get boundary byte + if (i == x2 >> 3) // Adjust right end + *b |= 0xff << ((i << 3) + 7 - x2); + else if (i == x1 >> 3) // Adjust left end + *b |= 0xff >> (x1 - (i << 3)); + else + *b = 0xff; + } +} + +// Clear a horizontal line segment in the object boundary file +void HugoEngine::clearBoundary(int x1, int x2, int y) { + int i; + byte *b; // ptr to boundary byte + + debugC(5, kDebugEngine, "clearBoundary(%d, %d, %d)", x1, x2, y); + + for (i = x1 >> 3; i <= x2 >> 3; i++) { // For each byte in line + b = &_objBound[y * XBYTES + i]; // get boundary byte + if (i == x2 >> 3) // Adjust right end + *b &= ~(0xff << ((i << 3) + 7 - x2)); + else if (i == x1 >> 3) // Adjust left end + *b &= ~(0xff >> (x1 - (i << 3))); + else + *b = 0; + } +} + +// Maze mode is enabled. Check to see whether hero has crossed the maze +// bounding box, if so, go to the next room */ +void HugoEngine::processMaze() { + seq_t *currImage; + int x1, x2, y1, y2; // hero coordinates + + debugC(1, kDebugEngine, "processMaze"); + + //actlist alnewscr = {&aheroxy,&astophero,&aherostop,&anewscr,NULL}; + //actlist_pt alist = &alnewscr[0]; + + currImage = _hero->currImagePtr; // Get ptr to current image + x1 = _hero->x + currImage->x1; // Left edge of object + x2 = _hero->x + currImage->x2; // Right edge + y1 = _hero->y + currImage->y1; // Top edge + y2 = _hero->y + currImage->y2; // Bottom edge + + if (x1 < _maze.x1) { + // Exit west +// anewscr.screen = *_screen_p - 1; + _actListArr[_alNewscrIndex][3].a8.screenIndex = *_screen_p - 1; +// aheroxy.x = _maze.x2 - SHIFT - (x2 - x1); + _actListArr[_alNewscrIndex][0].a2.x = _maze.x2 - SHIFT - (x2 - x1); +// aheroxy.y = _hero_p->y; + _actListArr[_alNewscrIndex][0].a2.y = _hero->y; + _status.routeIndex = -1; + HugoEngine::get().scheduler().insertActionList(_alNewscrIndex); + } else if (x2 > _maze.x2) { + // Exit east +// anewscr.screen = *_screen_p + 1; + _actListArr[_alNewscrIndex][3].a8.screenIndex = *_screen_p + 1; +// aheroxy.x = _maze.x1 + SHIFT; + _actListArr[_alNewscrIndex][0].a2.x = _maze.x1 + SHIFT; +// aheroxy.y = _hero_p->y; + _actListArr[_alNewscrIndex][0].a2.y = _hero->y; + _status.routeIndex = -1; + HugoEngine::get().scheduler().insertActionList(_alNewscrIndex); + } else if (y1 < _maze.y1 - SHIFT) { + // Exit north +// anewscr.screen = *_screen_p - _maze.size; + _actListArr[_alNewscrIndex][3].a8.screenIndex = *_screen_p - _maze.size; +// aheroxy.x = _maze.x3; // special offset for perspective + _actListArr[_alNewscrIndex][0].a2.x = _maze.x3; +// aheroxy.y = _maze.y2 - SHIFT - (y2 - y1); + _actListArr[_alNewscrIndex][0].a2.y = _maze.y2 - SHIFT - (y2 - y1); + _status.routeIndex = -1; + HugoEngine::get().scheduler().insertActionList(_alNewscrIndex); + } else if (y2 > _maze.y2 - SHIFT / 2) { + // Exit south +// anewscr.screen = *_screen_p + _maze.size; + _actListArr[_alNewscrIndex][3].a8.screenIndex = *_screen_p + _maze.size; +// aheroxy.x = _maze.x4; // special offset for perspective + _actListArr[_alNewscrIndex][0].a2.x = _maze.x4; +// aheroxy.y = _maze.y1 + SHIFT; + _actListArr[_alNewscrIndex][0].a2.y = _maze.y1 + SHIFT; + _status.routeIndex = -1; + HugoEngine::get().scheduler().insertActionList(_alNewscrIndex); + } +} + +// Compare function for the quicksort. The sort is to order the objects in +// increasing vertical position, using y+y2 as the baseline +// Returns -1 if ay2 < by2 else 1 if ay2 > by2 else 0 +int y2comp(const void *a, const void *b) { + int ay2, by2; + + debugC(6, kDebugEngine, "y2comp"); + + const object_t *p1 = &HugoEngine::get()._objects[*(const byte *)a]; + const object_t *p2 = &HugoEngine::get()._objects[*(const byte *)b]; + + if (p1 == p2) + // Why does qsort try the same indexes? + return (0); + + if (p1->priority == BACKGROUND) + return (-1); + + if (p2->priority == BACKGROUND) + return (1); + + if (p1->priority == FOREGROUND) + return (1); + + if (p2->priority == FOREGROUND) + return (-1); + + ay2 = p1->y + p1->currImagePtr->y2; + by2 = p2->y + p2->currImagePtr->y2; + + return(ay2 - by2); +} + +// Draw all objects on screen as follows: +// 1. Sort 'FLOATING' objects in order of y2 (base of object) +// 2. Display new object frames/positions in dib +// Finally, cycle any animating objects to next frame +void HugoEngine::updateImages() { + int i, j, num_objs; + object_t *obj; // Pointer to object + seq_t *seqPtr; // Save curr_seq_p + byte objindex[MAX_OBJECTS]; // Array of indeces to objects + + debugC(5, kDebugEngine, "updateImages"); + + // Initialise the index array to visible objects in current screen + for (i = 0, num_objs = 0; i < _numObj; i++) { + obj = &_objects[i]; + if ((obj->screenIndex == *_screen_p) && (obj->cycling >= ALMOST_INVISIBLE)) + objindex[num_objs++] = i; + } + + // Sort the objects into increasing y+y2 (painter's algorithm) + qsort(objindex, num_objs, sizeof(objindex[0]), y2comp); + + // Add each visible object to display list + for (i = 0; i < num_objs; i++) { + obj = &_objects[objindex[i]]; + // Count down inter-frame timer + if (obj->frameTimer) + obj->frameTimer--; + + if (obj->cycling > ALMOST_INVISIBLE) // Only if visible + switch (obj->cycling) { + case NOT_CYCLING: + screen().displayFrame(obj->x, obj->y, obj->currImagePtr, obj->priority == OVEROVL); + break; + case CYCLE_FORWARD: + if (obj->frameTimer) // Not time to see next frame yet + screen().displayFrame(obj->x, obj->y, obj->currImagePtr, obj->priority == OVEROVL); + else + screen().displayFrame(obj->x, obj->y, obj->currImagePtr->nextSeqPtr, obj->priority == OVEROVL); + break; + case CYCLE_BACKWARD: + seqPtr = obj->currImagePtr; + if (!obj->frameTimer) // Show next frame + while (seqPtr->nextSeqPtr != obj->currImagePtr) + seqPtr = seqPtr->nextSeqPtr; + screen().displayFrame(obj->x, obj->y, seqPtr, obj->priority == OVEROVL); + break; + default: + break; + } + } + + // Cycle any animating objects + for (i = 0; i < num_objs; i++) { + obj = &_objects[objindex[i]]; + if (obj->cycling != INVISIBLE) { + // Only if it's visible + if (obj->cycling == ALMOST_INVISIBLE) + obj->cycling = INVISIBLE; + + // Now Rotate to next picture in sequence + switch (obj->cycling) { + case NOT_CYCLING: + break; + case CYCLE_FORWARD: + if (!obj->frameTimer) { + // Time to step to next frame + obj->currImagePtr = obj->currImagePtr->nextSeqPtr; + // Find out if this is last frame of sequence + // If so, reset frame_timer and decrement n_cycle + if (obj->frameInterval || obj->cycleNumb) { + obj->frameTimer = obj->frameInterval; + for (j = 0; j < obj->seqNumb; j++) + if (obj->currImagePtr->nextSeqPtr == obj->seqList[j].seqPtr) + if (obj->cycleNumb) // Decr cycleNumb if Non-continous + if (!--obj->cycleNumb) + obj->cycling = NOT_CYCLING; + } + } + break; + case CYCLE_BACKWARD: + if (!obj->frameTimer) { + // Time to step to prev frame + seqPtr = obj->currImagePtr; + while (obj->currImagePtr->nextSeqPtr != seqPtr) + obj->currImagePtr = obj->currImagePtr->nextSeqPtr; + // Find out if this is first frame of sequence + // If so, reset frame_timer and decrement n_cycle + if (obj->frameInterval || obj->cycleNumb) { + obj->frameTimer = obj->frameInterval; + for (j = 0; j < obj->seqNumb; j++) + if (obj->currImagePtr == obj->seqList[j].seqPtr) + if (obj->cycleNumb) // Decr cycleNumb if Non-continous + if (!--obj->cycleNumb) + obj->cycling = NOT_CYCLING; + } + } + break; + default: + break; + } + obj->oldx = obj->x; + obj->oldy = obj->y; + } + } +} + +// Return object index of the topmost object under the cursor, or -1 if none +// Objects are filtered if not "useful" +int16 HugoEngine::findObject(uint16 x, uint16 y) { + object_t *obj; + seq_t *curImage; + int16 objIndex = -1; // Index of found object + uint16 y2Max = 0; // Greatest y2 + int i; + + debugC(3, kDebugEngine, "findObject(%d, %d)", x, y); + + // Check objects on screen + for (i = 0, obj = _objects; i < _numObj; i++, obj++) { + // Object must be in current screen and "useful" + if (obj->screenIndex == *_screen_p && (obj->genericCmd || obj->objValue || obj->cmdIndex)) { + curImage = obj->currImagePtr; + // Object must have a visible image... + if (curImage != NULL && obj->cycling != INVISIBLE) { + // If cursor inside object + if (x >= (uint16)obj->x && x <= obj->x + curImage->x2 && y >= (uint16)obj->y && y <= obj->y + curImage->y2) + // If object is closest so far + if (obj->y + curImage->y2 > y2Max) { + y2Max = obj->y + curImage->y2; + objIndex = i; // Found an object! + } + } else + // ...or a dummy object that has a hotspot rectangle + if (curImage == NULL && obj->vxPath != 0 && !obj->carriedFl) { + // If cursor inside special rectangle + if ((int16)x >= obj->oldx && (int16)x < obj->oldx + obj->vxPath && (int16)y >= obj->oldy && (int16)y < obj->oldy + obj->vyPath) + // If object is closest so far + if (obj->oldy + obj->vyPath - 1 > (int16)y2Max) { + y2Max = obj->oldy + obj->vyPath - 1; + objIndex = i; // Found an object! + } + } + } + } + return objIndex; +} + +// Find a clear space around supplied object that hero can walk to +bool HugoEngine::findObjectSpace(object_t *obj, int16 *destx, int16 *desty) { +// bool found = false; // TRUE if we found a clear space + bool foundFl; + seq_t *curImage = obj->currImagePtr; + int16 x; + int16 y = obj->y + curImage->y2 - 1; + + debugC(1, kDebugEngine, "findObjectSpace(obj, %d, %d)", *destx, *desty); + +// if (!found) // Try left rear corner + for (foundFl = true, *destx = x = obj->x + curImage->x1; x < *destx + HERO_MAX_WIDTH; x++) + if (BOUND(x, y)) + foundFl = false; + + if (!foundFl) // Try right rear corner + for (foundFl = true, *destx = x = obj->x + curImage->x2 - HERO_MAX_WIDTH + 1; x <= obj->x + (int16)curImage->x2; x++) + if (BOUND(x, y)) + foundFl = false; + + if (!foundFl) // Try left front corner + for (foundFl = true, y += 2, *destx = x = obj->x + curImage->x1; x < *destx + HERO_MAX_WIDTH; x++) + if (BOUND(x, y)) + foundFl = false; + + if (!foundFl) // Try right rear corner + for (foundFl = true, *destx = x = obj->x + curImage->x2 - HERO_MAX_WIDTH + 1; x <= obj->x + (int16)curImage->x2; x++) + if (BOUND(x, y)) + foundFl = false; + + *desty = y; + return(foundFl); +} + +// Search background command list for this screen for supplied object. +// Return first associated verb (not "look") or NULL if none found. +char *HugoEngine::useBG(char *name) { + int i; + objectList_t p = _backgroundObjects[*_screen_p]; + + debugC(1, kDebugEngine, "useBG(%s)", name); + + for (i = 0; *_arrayVerbs[p[i].verbIndex]; i++) + if ((name == _arrayNouns[p[i].nounIndex][0] && + p[i].verbIndex != _look) && + ((p[i].roomState == DONT_CARE) || (p[i].roomState == _screenStates[*_screen_p]))) + return (_arrayVerbs[p[i].verbIndex][0]); + + return (NULL); +} + +// If status.objid = -1, pick up objid, else use status.objid on objid, +// if objid can't be picked up, use it directly +void HugoEngine::useObject(int16 objId) { + object_t *obj = &_objects[objId]; // Ptr to object + uses_t *use; // Ptr to use entry + target_t *target; // Ptr to target entry + bool foundFl; // TRUE if found target entry + char *verb; // Background verb to use directly + + debugC(1, kDebugEngine, "useObject(%d)", objId); + + if (_status.inventoryObjId == -1) { + // Get or use objid directly + if ((obj->genericCmd & TAKE) || obj->objValue) // Get collectible item + sprintf(_line, "%s %s", _arrayVerbs[_take][0], _arrayNouns[obj->nounIndex][0]); + else if (obj->cmdIndex != 0) // Use non-collectible item if able + sprintf(_line, "%s %s", _arrayVerbs[_cmdList[obj->cmdIndex][1].verbIndex][0], _arrayNouns[obj->nounIndex][0]); + else if ((verb = useBG(_arrayNouns[obj->nounIndex][0])) != NULL) + sprintf(_line, "%s %s", verb, _arrayNouns[obj->nounIndex][0]); + else + return; // Can't use object directly + } else { + // Use status.objid on objid + // Default to first cmd verb + sprintf(_line, "%s %s %s", _arrayVerbs[_cmdList[_objects[_status.inventoryObjId].cmdIndex][1].verbIndex][0], _arrayNouns[_objects[_status.inventoryObjId].nounIndex][0], _arrayNouns[obj->nounIndex][0]); + + // Check valid use of objects and override verb if necessary + for (use = _uses; use->objId != _numObj; use++) + if (_status.inventoryObjId == use->objId) { + // Look for secondary object, if found use matching verb + for (foundFl = false, target = use->targets; _arrayNouns[target->nounIndex] != NULL; target++) + if (_arrayNouns[target->nounIndex][0] == _arrayNouns[obj->nounIndex][0]) { + foundFl = true; + sprintf(_line, "%s %s %s", _arrayVerbs[target->verbIndex][0], _arrayNouns[_objects[_status.inventoryObjId].nounIndex][0], _arrayNouns[obj->nounIndex][0]); + } + + // No valid use of objects found, print failure string + if (!foundFl) { + // Deselect dragged icon if inventory not active + if (_status.inventoryState != I_ACTIVE) + _status.inventoryObjId = -1; + Utils::Box(BOX_ANY, HugoEngine::get()._textData[use->dataIndex]); + return; + } + } + } + + if (_status.inventoryState == I_ACTIVE) // If inventory active, remove it + _status.inventoryState = I_UP; + _status.inventoryObjId = -1; // Deselect any dragged icon + parser().lineHandler(); // and process command +} + +// Issue "Look at <object>" command +// Note special case of swapped hero image +void HugoEngine::lookObject(object_t *obj) { + debugC(1, kDebugEngine, "lookObject"); + + if (obj == _hero) { + // Hero swapped - look at other + obj = &_objects[_heroImage]; + } + parser().command("%s %s", _arrayVerbs[_look][0], _arrayNouns[obj->nounIndex][0]); +} + +// Free all object images +void HugoEngine::freeObjects() { + object_t *obj; + seq_t *seq; + + debugC(1, kDebugEngine, "freeObjects"); + + // Nothing to do if not allocated yet + if (_hero->seqList[0].seqPtr == NULL) + return; + + // Free all sequence lists and image data + for (int i = 0; i < _numObj; i++) { + obj = &_objects[i]; + for (int j = 0; j < obj->seqNumb; j++) { // for each sequence + seq = obj->seqList[j].seqPtr; // Free image + if (seq == NULL) // Failure during database load + break; + do { + free(seq->imagePtr); + seq = seq->nextSeqPtr; + } while (seq != obj->seqList[j].seqPtr); + free(seq); // Free sequence record + } + } +} + +// Add action lists for this screen to event queue +void HugoEngine::screenActions(int screenNum) { + uint16 *screenAct = _screenActs[screenNum]; + + debugC(1, kDebugEngine, "screenActions(%d)", screenNum); + + if (screenAct) { + for (int i = 0; screenAct[i]; i++) + HugoEngine::get().scheduler().insertActionList(screenAct[i]); + } +} + +// Set the new screen number into the hero object and any carried objects +void HugoEngine::setNewScreen(int screenNum) { + debugC(1, kDebugEngine, "setNewScreen(%d)", screenNum); + + *_screen_p = screenNum; // HERO object + for (int i = HERO + 1; i < _numObj; i++) // Any others + if (_objects[i].carriedFl) // being carried + _objects[i].screenIndex = screenNum; +} + +// An object has collided with a boundary. See if any actions are required +void HugoEngine::boundaryCollision(object_t *obj) { + int x, y, dx, dy; + char radius; // 8 bits signed + hotspot_t *hotspot; + + debugC(1, kDebugEngine, "boundaryCollision"); + + if (obj == _hero) { + // Hotspots only relevant to HERO + if (obj->vx > 0) + x = obj->x + obj->currImagePtr->x2; + else + x = obj->x + obj->currImagePtr->x1; + y = obj->y + obj->currImagePtr->y2; + + for (int i = 0; _hotspots[i].screenIndex >= 0; i++) { + hotspot = &_hotspots[i]; + if (hotspot->screenIndex == obj->screenIndex) + if ((x >= hotspot->x1) && (x <= hotspot->x2) && (y >= hotspot->y1) && (y <= hotspot->y2)) { + HugoEngine::get().scheduler().insertActionList(hotspot->actIndex); + break; + } + } + } else { + // Check whether an object collided with HERO + dx = _hero->x + _hero->currImagePtr->x1 - obj->x - obj->currImagePtr->x1; + dy = _hero->y + _hero->currImagePtr->y2 - obj->y - obj->currImagePtr->y2; + // If object's radius is infinity, use a closer value + radius = obj->radius; + if (radius < 0) + radius = DX * 2; + if ((abs(dx) <= radius) && (abs(dy) <= radius)) + HugoEngine::get().scheduler().insertActionList(obj->actIndex); + } +} + +// Initialize screen components and display results +void HugoEngine::initNewScreenDisplay() { + debugC(1, kDebugEngine, "initNewScreenDisplay"); + + screen().displayList(D_INIT); + screen().setBackgroundColor(_TBLACK); + screen().displayBackground(); + + // Stop premature object display in Display_list(D_DISPLAY) + _status.newScreenFl = true; +} + +// Add up all the object values and all the bonus points +void HugoEngine::calcMaxScore() { + int i; + + debugC(1, kDebugEngine, "calcMaxScore"); + + for (i = 0; i < _numObj; i++) + _maxscore += _objects[i].objValue; + + for (i = 0; i < _numBonuses; i++) + _maxscore += _points[i].score; +} + +// Exit game, advertise trilogy, show copyright +void HugoEngine::endGame() { + debugC(1, kDebugEngine, "endGame"); + + if (!_boot.registered) + Utils::Box(BOX_ANY, HugoEngine::get()._textEngine[kEsAdvertise]); + Utils::Box(BOX_ANY, "%s\n%s", _episode, COPYRIGHT); + _status.viewState = V_EXIT; +} + +} // end of namespace Hugo diff --git a/engines/hugo/engine.h b/engines/hugo/engine.h new file mode 100755 index 0000000000..fa4eba4fbb --- /dev/null +++ b/engines/hugo/engine.h @@ -0,0 +1,43 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on original Hugo 1-3 Trilogy source code + * + * Copyright (c) 1989-1995 David P. Gray + * + */ + +#ifndef HUGO_ENGINE_H +#define HUGO_ENGINE_H +namespace Hugo { + +enum seqTextEngine { + // Strings used by the engine + kEsAdvertise = 0 +}; + +} // end of namespace Hugo +#endif // HUGO_ENGINE_H diff --git a/engines/hugo/file.cpp b/engines/hugo/file.cpp new file mode 100755 index 0000000000..0a20c8a67b --- /dev/null +++ b/engines/hugo/file.cpp @@ -0,0 +1,924 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on original Hugo Trilogy source code + * + * Copyright (c) 1989-1995 David P. Gray + * + */ + +#include "common/system.h" +#include "common/file.h" +#include "common/savefile.h" + +#include "hugo/game.h" +#include "hugo/hugo.h" +#include "hugo/file.h" +#include "hugo/global.h" +#include "hugo/schedule.h" +#include "hugo/display.h" +#include "hugo/util.h" + +namespace Hugo { +FileManager::FileManager(HugoEngine &vm) : _vm(vm) { + +} + +byte *FileManager::convertPCC(byte *p, uint16 y, uint16 bpl, image_pt dataPtr) { +// Convert 4 planes (RGBI) data to 8-bit DIB format +// Return original plane data ptr + uint16 r, g, b, i; // Byte index within each plane + char bit; // Bit index within a byte + + debugC(2, kDebugFile, "convertPCC(byte *p, %d, %d, image_pt data_p)", y, bpl); + + dataPtr += y * bpl * 8; // Point to correct DIB line + for (r = 0, g = bpl, b = g + bpl, i = b + bpl; r < bpl; r++, g++, b++, i++) // Each byte in all planes + for (bit = 7; bit >= 0; bit--) // Each bit in byte + *dataPtr++ = (((p[r] >> bit & 1) << 0) | + ((p[g] >> bit & 1) << 1) | + ((p[b] >> bit & 1) << 2) | + ((p[i] >> bit & 1) << 3)); + return p; +} + +seq_t *FileManager::readPCX(Common::File &f, seq_t *seqPtr, byte *imagePtr, bool firstFl, const char *name) { +// Read a pcx file of length len. Use supplied seq_p and image_p or +// allocate space if NULL. Name used for errors. Returns address of seq_p +// Set first TRUE to initialize b_index (i.e. not reading a sequential image in file). + + struct { // Structure of PCX file header + byte mfctr, vers, enc, bpx; + uint16 x1, y1, x2, y2; // bounding box + uint16 xres, yres; + byte palette[48]; // EGA color palette + byte vmode, planes; + uint16 bytesPerLine; // Bytes per line + byte fill2[60]; + } PCC_header; // Header of a PCC file + + byte c, d; // code and data bytes from PCX file + byte pline[XPIX]; // Hold 4 planes of data + byte *p = pline; // Ptr to above + byte i; // PCX repeat count + uint16 bytesPerLine4; // BPL in 4-bit format + uint16 size; // Size of image + uint16 y = 0; // Current line index + + debugC(1, kDebugFile, "readPCX(..., %s)", name); + + // Read in the PCC header and check consistency + PCC_header.mfctr = f.readByte(); + PCC_header.vers = f.readByte(); + PCC_header.enc = f.readByte(); + PCC_header.bpx = f.readByte(); + PCC_header.x1 = f.readUint16LE(); + PCC_header.y1 = f.readUint16LE(); + PCC_header.x2 = f.readUint16LE(); + PCC_header.y2 = f.readUint16LE(); + PCC_header.xres = f.readUint16LE(); + PCC_header.yres = f.readUint16LE(); + f.read(PCC_header.palette, sizeof(PCC_header.palette)); + PCC_header.vmode = f.readByte(); + PCC_header.planes = f.readByte(); + PCC_header.bytesPerLine = f.readUint16LE(); + f.read(PCC_header.fill2, sizeof(PCC_header.fill2)); + + if (PCC_header.mfctr != 10) + Utils::Error(PCCH_ERR, name); + + // Allocate memory for seq_t if NULL + if (seqPtr == NULL) + if ((seqPtr = (seq_t *)malloc(sizeof(seq_t))) == NULL) + Utils::Error(HEAP_ERR, name); + + // Find size of image data in 8-bit DIB format + // Note save of x2 - marks end of valid data before garbage + bytesPerLine4 = PCC_header.bytesPerLine * 4; // 4-bit bpl + seqPtr->bytesPerLine8 = bytesPerLine4 * 2; // 8-bit bpl + seqPtr->lines = PCC_header.y2 - PCC_header.y1 + 1; + seqPtr->x2 = PCC_header.x2 - PCC_header.x1 + 1; + size = seqPtr->lines * seqPtr->bytesPerLine8; + + // Allocate memory for image data if NULL + if (imagePtr == NULL) + if ((imagePtr = (byte *)malloc((size_t) size)) == NULL) + Utils::Error(HEAP_ERR, name); + seqPtr->imagePtr = imagePtr; + + // Process the image data, converting to 8-bit DIB format + while (y < seqPtr->lines) { + c = f.readByte(); + if ((c & REP_MASK) == REP_MASK) { + d = f.readByte(); // Read data byte + for (i = 0; i < (c & LEN_MASK); i++) { + *p++ = d; + if ((uint16)(p - pline) == bytesPerLine4) + p = convertPCC(pline, y++, PCC_header.bytesPerLine, imagePtr); + } + } else { + *p++ = c; + if ((uint16)(p - pline) == bytesPerLine4) + p = convertPCC(pline, y++, PCC_header.bytesPerLine, imagePtr); + } + } + return seqPtr; +} + +void FileManager::readImage(int objNum, object_t *objPtr) { +// Read object file of PCC images into object supplied + byte x, y, j, k; + uint16 x2; // Limit on x in image data + seq_t *seqPtr; // Ptr to sequence structure + image_pt dibPtr; // Ptr to DIB data + objBlock_t objBlock; // Info on file within database + bool firstFl = true; // Initializes pcx read function + + debugC(1, kDebugFile, "readImage(%d, object_t *objPtr)", objNum); + + if (!objPtr->seqNumb) // This object has no images + return; + + if (_vm.isPacked()) { + _objectsArchive.seek((uint32)objNum * sizeof(objBlock_t), SEEK_SET); + + objBlock.objOffset = _objectsArchive.readUint32LE(); + objBlock.objLength = _objectsArchive.readUint32LE(); + + _objectsArchive.seek(objBlock.objOffset, SEEK_SET); + } else { + char *buf = (char *) malloc(2048 + 1); // Buffer for file access + strcat(strcat(strcpy(buf, _vm._picDir), _vm._arrayNouns[objPtr->nounIndex][0]), OBJEXT); + if (!_objectsArchive.open(buf)) { + warning("File %s not found, trying again with %s%s", buf, _vm._arrayNouns[objPtr->nounIndex][0], OBJEXT); + strcat(strcpy(buf, _vm._arrayNouns[objPtr->nounIndex][0]), OBJEXT); + if (!_objectsArchive.open(buf)) + Utils::Error(FILE_ERR, buf); + } + } + + // Now read the images into an images list + for (j = 0; j < objPtr->seqNumb; j++) { // for each sequence + for (k = 0; k < objPtr->seqList[j].imageNbr; k++) { // each image + if (k == 0) { // First image + // Read this image - allocate both seq and image memory + seqPtr = readPCX(_objectsArchive, NULL, NULL, firstFl, _vm._arrayNouns[objPtr->nounIndex][0]); + objPtr->seqList[j].seqPtr = seqPtr; + firstFl = false; + } else { // Subsequent image + // Read this image - allocate both seq and image memory + seqPtr->nextSeqPtr = readPCX(_objectsArchive, NULL, NULL, firstFl, _vm._arrayNouns[objPtr->nounIndex][0]); + seqPtr = seqPtr->nextSeqPtr; + } + + // Compute the bounding box - x1, x2, y1, y2 + // Note use of x2 - marks end of valid data in row + x2 = seqPtr->x2; + seqPtr->x1 = seqPtr->x2; + seqPtr->x2 = 0; + seqPtr->y1 = seqPtr->lines; + seqPtr->y2 = 0; + dibPtr = seqPtr->imagePtr; + for (y = 0; y < seqPtr->lines; y++, dibPtr += seqPtr->bytesPerLine8 - x2) + for (x = 0; x < x2; x++) + if (*dibPtr++) { // Some data found + if (x < seqPtr->x1) + seqPtr->x1 = x; + if (x > seqPtr->x2) + seqPtr->x2 = x; + if (y < seqPtr->y1) + seqPtr->y1 = y; + if (y > seqPtr->y2) + seqPtr->y2 = y; + } + } + seqPtr->nextSeqPtr = objPtr->seqList[j].seqPtr; // loop linked list to head + } + + // Set the current image sequence to first or last + switch (objPtr->cycling) { + case INVISIBLE: // (May become visible later) + case ALMOST_INVISIBLE: + case NOT_CYCLING: + case CYCLE_FORWARD: + objPtr->currImagePtr = objPtr->seqList[0].seqPtr; + break; + case CYCLE_BACKWARD: + objPtr->currImagePtr = seqPtr; + break; + } + + if (!_vm.isPacked()) + _objectsArchive.close(); +} + +void FileManager::readBackground(int screenIndex) { +// Read a PCX image into dib_a + seq_t seq; // Image sequence structure for Read_pcx + sceneBlock_t sceneBlock; // Read a database header entry + + debugC(1, kDebugFile, "readBackground(%d)", screenIndex); + + if (_vm.isPacked()) { + _sceneryArchive.seek((uint32) screenIndex * sizeof(sceneBlock_t), SEEK_SET); + + sceneBlock.scene_off = _sceneryArchive.readUint32LE(); + sceneBlock.scene_len = _sceneryArchive.readUint32LE(); + sceneBlock.b_off = _sceneryArchive.readUint32LE(); + sceneBlock.b_len = _sceneryArchive.readUint32LE(); + sceneBlock.o_off = _sceneryArchive.readUint32LE(); + sceneBlock.o_len = _sceneryArchive.readUint32LE(); + sceneBlock.ob_off = _sceneryArchive.readUint32LE(); + sceneBlock.ob_len = _sceneryArchive.readUint32LE(); + + _sceneryArchive.seek(sceneBlock.scene_off, SEEK_SET); + } else { + char *buf = (char *) malloc(2048 + 1); // Buffer for file access + strcat(strcat(strcpy(buf, _vm._picDir), _vm._screenNames[screenIndex]), BKGEXT); + if (!_sceneryArchive.open(buf)) { + warning("File %s not found, trying again with %s.ART", buf, _vm._screenNames[screenIndex]); + strcat(strcpy(buf, _vm._screenNames[screenIndex]), ".ART"); + if (!_sceneryArchive.open(buf)) + Utils::Error(FILE_ERR, buf); + } + } + + // Read the image into dummy seq and static dib_a + readPCX(_sceneryArchive, &seq, _vm.screen().getFrontBuffer(), true, _vm._screenNames[screenIndex]); + + if (!_vm.isPacked()) + _sceneryArchive.close(); +} + +sound_pt FileManager::getSound(int16 sound, uint16 *size) { +// Read sound (or music) file data. Call with SILENCE to free-up +// any allocated memory. Also returns size of data + + static sound_hdr_t s_hdr[MAX_SOUNDS]; // Sound lookup table + sound_pt soundPtr; // Ptr to sound data + Common::File fp; // Handle to SOUND_FILE +// bool music = sound < NUM_TUNES; // TRUE if music, else sound file + + debugC(1, kDebugFile, "getSound(%d, %d)", sound, *size); + + // No more to do if SILENCE (called for cleanup purposes) + if (sound == _vm._soundSilence) + return(NULL); + + // Open sounds file + if (!fp.open(SOUND_FILE)) { +// Error(FILE_ERR, SOUND_FILE); + warning("Hugo Error: File not found %s", SOUND_FILE); + return(NULL); + } + + // If this is the first call, read the lookup table + static bool has_read_header = false; + if (!has_read_header) { + if (fp.read(s_hdr, sizeof(s_hdr)) != sizeof(s_hdr)) + Utils::Error(FILE_ERR, SOUND_FILE); + has_read_header = true; + } + + *size = s_hdr[sound].size; + if (*size == 0) + Utils::Error(SOUND_ERR, SOUND_FILE); + + // Allocate memory for sound or music, if possible + if ((soundPtr = (byte *)malloc(s_hdr[sound].size)) == 0) { + Utils::Warn(false, "Low on memory"); + return(NULL); + } + + // Seek to data and read it + fp.seek(s_hdr[sound].offset, SEEK_SET); + if (fp.read(soundPtr, s_hdr[sound].size) != s_hdr[sound].size) + Utils::Error(FILE_ERR, SOUND_FILE); + + fp.close(); + + return soundPtr; +} + +bool FileManager::fileExists(char *filename) { +// Return whether file exists or not + Common::File f; + if (f.open(filename)) { + f.close(); + return true; + } + return false; +} + +void FileManager::readOverlay(int screenNum, image_pt image, ovl_t overlayType) { +// Open and read in an overlay file, close file + uint32 i; + int16 j, k; + char data; // Must be 8 bits signed + image_pt tmpImage = image; // temp ptr to overlay file + sceneBlock_t sceneBlock; // Database header entry + + debugC(1, kDebugFile, "readOverlay(%d, ...)", screenNum); + + if (_vm.isPacked()) { + _sceneryArchive.seek((uint32)screenNum * sizeof(sceneBlock_t), SEEK_SET); + + sceneBlock.scene_off = _sceneryArchive.readUint32LE(); + sceneBlock.scene_len = _sceneryArchive.readUint32LE(); + sceneBlock.b_off = _sceneryArchive.readUint32LE(); + sceneBlock.b_len = _sceneryArchive.readUint32LE(); + sceneBlock.o_off = _sceneryArchive.readUint32LE(); + sceneBlock.o_len = _sceneryArchive.readUint32LE(); + sceneBlock.ob_off = _sceneryArchive.readUint32LE(); + sceneBlock.ob_len = _sceneryArchive.readUint32LE(); + + switch (overlayType) { + case BOUNDARY: + _sceneryArchive.seek(sceneBlock.b_off, SEEK_SET); + i = sceneBlock.b_len; + break; + case OVERLAY: + _sceneryArchive.seek(sceneBlock.o_off, SEEK_SET); + i = sceneBlock.o_len; + break; + case OVLBASE: + _sceneryArchive.seek(sceneBlock.ob_off, SEEK_SET); + i = sceneBlock.ob_len; + break; + default: + Utils::Error(FILE_ERR, "Bad ovl_type"); + break; + } + if (i == 0) { + for (i = 0; i < OVL_SIZE; i++) + image[i] = 0; + return; + } + } else { + const char *ovl_ext[] = {".b", ".o", ".ob"}; + char *buf = (char *) malloc(2048 + 1); // Buffer for file access + + strcat(strcpy(buf, _vm._screenNames[screenNum]), ovl_ext[overlayType]); + + if (!fileExists(buf)) { + for (i = 0; i < OVL_SIZE; i++) + image[i] = 0; + return; + } + + if (!_sceneryArchive.open(buf)) + Utils::Error(FILE_ERR, buf); + +// if (eof(f_scenery)) { +// _lclose(f_scenery); +// return; +// } + } + + switch (_vm._gameVariant) { + case 0: // Hugo 1 DOS and WIN don't pack data + case 3: + _sceneryArchive.read(tmpImage, OVL_SIZE); + break; + default: + // Read in the overlay file using MAC Packbits. (We're not proud!) + k = 0; // byte count + do { + data = _sceneryArchive.readByte(); // Read a code byte + if ((byte)data == 0x80) // Noop + k = k; + else if (data >= 0) { // Copy next data+1 literally + for (i = 0; i <= (byte)data; i++, k++) + *tmpImage++ = _sceneryArchive.readByte(); + } else { // Repeat next byte -data+1 times + j = _sceneryArchive.readByte(); + + for (i = 0; i < (byte)(-data + 1); i++, k++) + *tmpImage++ = j; + } + } while (k < OVL_SIZE); + break; + } + + if (!_vm.isPacked()) + _sceneryArchive.close(); +} + +void FileManager::saveSeq(object_t *obj) { +// Save sequence number and image number in given object + byte j, k; + seq_t *q; + bool found; + + debugC(1, kDebugFile, "saveSeq"); + + for (j = 0, found = false; !found && (j < obj->seqNumb); j++) { + q = obj->seqList[j].seqPtr; + for (k = 0; !found && (k < obj->seqList[j].imageNbr); k++) { + if (obj->currImagePtr == q) { + found = true; + obj->curSeqNum = j; + obj->curImageNum = k; + } else + q = q->nextSeqPtr; + } + } +} + +void FileManager::restoreSeq(object_t *obj) { +// Set up cur_seq_p from stored sequence and image number in object + int j; + seq_t *q; + + debugC(1, kDebugFile, "restoreSeq"); + + q = obj->seqList[obj->curSeqNum].seqPtr; + for (j = 0; j < obj->curImageNum; j++) + q = q->nextSeqPtr; + obj->currImagePtr = q; +} + +void FileManager::saveGame(int16 slot, const char *descrip) { +// Save game to supplied slot (-1 is INITFILE) + int i; + char path[256]; // Full path of saved game + + debugC(1, kDebugFile, "saveGame(%d, %s)", slot, descrip); + + // Get full path of saved game file - note test for INITFILE + if (slot == -1) + sprintf(path, "%s", _vm._initFilename); + else + sprintf(path, _vm._saveFilename, slot); + + Common::WriteStream *out = 0; + if (!(out = _vm.getSaveFileManager()->openForSaving(path))) { + warning("Can't create file '%s', game not saved", path); + return; + } + + // Write version. We can't restore from obsolete versions + out->write(&kSavegameVersion, sizeof(kSavegameVersion)); + + // Save description of saved game + out->write(descrip, DESCRIPLEN); + + // Save objects + for (i = 0; i < _vm._numObj; i++) { + // Save where curr_seq_p is pointing to + saveSeq(&_vm._objects[i]); + out->write(&_vm._objects[i], sizeof(object_t)); + } + + const status_t &gameStatus = _vm.getGameStatus(); + + // Save whether hero image is swapped + out->write(&_vm._heroImage, sizeof(_vm._heroImage)); + + // Save score + int score = _vm.getScore(); + out->write(&score, sizeof(score)); + + // Save story mode + out->write(&gameStatus.storyModeFl, sizeof(gameStatus.storyModeFl)); + + // Save jumpexit mode + out->write(&gameStatus.jumpExitFl, sizeof(gameStatus.jumpExitFl)); + + // Save gameover status + out->write(&gameStatus.gameOverFl, sizeof(gameStatus.gameOverFl)); + + // Save screen states + out->write(_vm._screenStates, sizeof(*_vm._screenStates) * _vm._numScreens); + + // Save points table + out->write(_vm._points, sizeof(point_t) * _vm._numBonuses); + + // Now save current time and all current events in event queue + _vm.scheduler().saveEvents(out); + + // Save palette table + _vm.screen().savePal(out); + + // Save maze status + out->write(&_maze, sizeof(maze_t)); + + out->finalize(); + + delete out; +} + +void FileManager::restoreGame(int16 slot) { +// Restore game from supplied slot number (-1 is INITFILE) + int i; + char path[256]; // Full path of saved game + object_t *p; + seqList_t seqList[MAX_SEQUENCES]; +// cmdList *cmds; // Save command list pointer + uint16 cmdIndex; // Save command list pointer +// char ver[sizeof(VER)]; // Compare versions + + debugC(1, kDebugFile, "restoreGame(%d)", slot); + + // Initialize new-game status + _vm.initStatus(); + + // Get full path of saved game file - note test for INITFILE + if (slot == -1) + sprintf(path, "%s", _vm._initFilename); + else + sprintf(path, _vm._saveFilename, slot); + + Common::SeekableReadStream *in = 0; + if (!(in = _vm.getSaveFileManager()->openForLoading(path))) + return; + + // Check version, can't restore from different versions + int saveVersion; + in->read(&saveVersion, sizeof(saveVersion)); + if (saveVersion != kSavegameVersion) { + Utils::Error(GEN_ERR, "Savegame of incompatible version"); + return; + } + + // Skip over description + in->seek(DESCRIPLEN, SEEK_CUR); + + // If hero image is currently swapped, swap it back before restore + if (_vm._heroImage != HERO) + _vm.scheduler().swapImages(HERO, _vm._heroImage); + + // Restore objects, retain current seqList which points to dynamic mem + // Also, retain cmnd_t pointers + for (i = 0; i < _vm._numObj; i++) { + p = &_vm._objects[i]; + memcpy(seqList, p->seqList, sizeof(seqList_t)); + cmdIndex = p->cmdIndex; + in->read(p, sizeof(object_t)); + p->cmdIndex = cmdIndex; + memcpy(p->seqList, seqList, sizeof(seqList_t)); + } + + in->read(&_vm._heroImage, sizeof(_vm._heroImage)); + + // If hero swapped in saved game, swap it + if ((i = _vm._heroImage) != HERO) + _vm.scheduler().swapImages(HERO, _vm._heroImage); + _vm._heroImage = i; + + status_t &gameStatus = _vm.getGameStatus(); + + int score; + in->read(&score, sizeof(score)); + _vm.setScore(score); + + in->read(&gameStatus.storyModeFl, sizeof(gameStatus.storyModeFl)); + in->read(&gameStatus.jumpExitFl, sizeof(gameStatus.jumpExitFl)); + in->read(&gameStatus.gameOverFl, sizeof(gameStatus.gameOverFl)); + in->read(_vm._screenStates, sizeof(*_vm._screenStates) * _vm._numScreens); + + // Restore points table + in->read(_vm._points, sizeof(point_t) * _vm._numBonuses); + + // Restore ptrs to currently loaded objects + for (i = 0; i < _vm._numObj; i++) + restoreSeq(&_vm._objects[i]); + + // Now restore time of the save and the event queue + _vm.scheduler().restoreEvents(in); + + // Restore palette and change it if necessary + _vm.screen().restorePal(in); + + // Restore maze status + in->read(&_maze, sizeof(maze_t)); + + delete in; +} + +void FileManager::initSavedGame() { +// Initialize the size of a saved game (from the fixed initial game). +// If status.initsave is TRUE, or the initial saved game is not found, +// force a save to create one. Normally the game will be shipped with +// the initial game file but useful to force a write during development +// when the size is changeable. +// The net result is a valid INITFILE, with status.savesize initialized. + Common::File f; // Handle of saved game file + char path[256]; // Full path of INITFILE + + debugC(1, kDebugFile, "initSavedGame"); + + // Get full path of INITFILE + sprintf(path, "%s", _vm._initFilename); + + + // Force save of initial game + if (_vm.getGameStatus().initSaveFl) + saveGame(-1, ""); + + // If initial game doesn't exist, create it + Common::SeekableReadStream *in = 0; + if (!(in = _vm.getSaveFileManager()->openForLoading(path))) { + saveGame(-1, ""); + if (!(in = _vm.getSaveFileManager()->openForLoading(path))) { + Utils::Error(WRITE_ERR, path); + return; + } + } + + // Must have an open saved game now + _vm.getGameStatus().saveSize = in->size(); + delete in; + + // Check sanity - maybe disk full or path set to read-only drive? + if (_vm.getGameStatus().saveSize == -1) + Utils::Error(WRITE_ERR, path); +} + +// Record and playback handling stuff: +typedef struct { +// int key; // Character + uint32 time; // Time at which character was pressed +} pbdata_t; +static pbdata_t pbdata; +FILE *fpb; + +void FileManager::openPlaybackFile(bool playbackFl, bool recordFl) { + debugC(1, kDebugFile, "openPlaybackFile(%d, %d)", (playbackFl) ? 1 : 0, (recordFl) ? 1 : 0); + + if (playbackFl) { + if (!(fpb = fopen(PBFILE, "r+b"))) + Utils::Error(FILE_ERR, PBFILE); + } else if (recordFl) + fpb = fopen(PBFILE, "wb"); + pbdata.time = 0; // Say no key available +} + +void FileManager::closePlaybackFile() { + fclose(fpb); +} + +void FileManager::openDatabaseFiles() { +//TODO : HUGO 1 DOS uses _stringtData instead of a strings.dat +//This should be tested adequately and should be handled by an error and not by a warning. + debugC(1, kDebugFile, "openDatabaseFiles"); + + if (!_stringArchive.open(STRING_FILE)) +// Error(FILE_ERR, STRING_FILE); + warning("Hugo Error: File not found %s", STRING_FILE); + if (_vm.isPacked()) { + if (!_sceneryArchive.open(SCENERY_FILE)) + Utils::Error(FILE_ERR, SCENERY_FILE); + if (!_objectsArchive.open(OBJECTS_FILE)) + Utils::Error(FILE_ERR, OBJECTS_FILE); + } +} + +void FileManager::closeDatabaseFiles() { +// TODO: stringArchive shouldn't be closed in Hugo 1 DOS + debugC(1, kDebugFile, "closeDatabaseFiles"); + + _stringArchive.close(); + if (_vm.isPacked()) { + _sceneryArchive.close(); + _objectsArchive.close(); + } +} + +char *FileManager::fetchString(int index) { +//TODO : HUGO 1 DOS uses _stringtData instead of a strings.dat +// Fetch string from file, decode and return ptr to string in memory + uint32 off1, off2; + + debugC(1, kDebugFile, "fetchString(%d)", index); + + // Get offset to string[index] (and next for length calculation) + _stringArchive.seek((uint32)index * sizeof(uint32), SEEK_SET); + if (_stringArchive.read((char *)&off1, sizeof(uint32)) == 0) + Utils::Error(FILE_ERR, "String offset"); + if (_stringArchive.read((char *)&off2, sizeof(uint32)) == 0) + Utils::Error(FILE_ERR, "String offset"); + + // Check size of string + if ((off2 - off1) >= MAX_BOX) + Utils::Error(FILE_ERR, "Fetched string too long!"); + + // Position to string and read it into gen purpose _textBoxBuffer + _stringArchive.seek(off1, SEEK_SET); + if (_stringArchive.read(_textBoxBuffer, (uint16)(off2 - off1)) == 0) + Utils::Error(FILE_ERR, "Fetch_string"); + + // Null terminate, decode and return it + _textBoxBuffer[off2-off1] = '\0'; + _vm.scheduler().decodeString(_textBoxBuffer); + return _textBoxBuffer; +} + +void FileManager::printBootText() { +// Read the encrypted text from the boot file and print it + Common::File ofp; + int i; + char *buf; + + debugC(1, kDebugFile, "printBootText"); + + if (!ofp.open(BOOTFILE)) + Utils::Error(FILE_ERR, BOOTFILE); + + // Allocate space for the text and print it + buf = (char *)malloc(_boot.exit_len + 1); + if (buf) { + // Skip over the boot structure (already read) and read exit text + ofp.seek((long)sizeof(_boot), SEEK_SET); + if (ofp.read(buf, _boot.exit_len) != (size_t)_boot.exit_len) + Utils::Error(FILE_ERR, BOOTFILE); + + // Decrypt the exit text, using CRYPT substring + for (i = 0; i < _boot.exit_len; i++) + buf[i] ^= CRYPT[i % strlen(CRYPT)]; + + buf[i] = '\0'; + //Box(BOX_OK, buf_p); + //MessageBox(hwnd, buf_p, "License", MB_ICONINFORMATION); + warning("printBootText(): License: %s", buf); + } + + free(buf); + ofp.close(); +} + +void FileManager::readBootFile() { +// Reads boot file for program environment. Fatal error if not there or +// file checksum is bad. De-crypts structure while checking checksum + byte checksum; + byte *p; + Common::File ofp; + uint32 i; + + debugC(1, kDebugFile, "readBootFile"); + + if (!ofp.open(BOOTFILE)) + Utils::Error(FILE_ERR, BOOTFILE); + + if (ofp.size() < (int32)sizeof(_boot)) + Utils::Error(FILE_ERR, BOOTFILE); + + _boot.checksum = ofp.readByte(); + _boot.registered = ofp.readByte(); + ofp.read(_boot.pbswitch, sizeof(_boot.pbswitch)); + ofp.read(_boot.distrib, sizeof(_boot.distrib)); + _boot.exit_len = ofp.readUint16LE(); + + p = (byte *)&_boot; + for (i = 0, checksum = 0; i < sizeof(_boot); i++) { + checksum ^= p[i]; + p[i] ^= CRYPT[i % strlen(CRYPT)]; + } + ofp.close(); + + if (checksum) + Utils::Error(GEN_ERR, "Program startup file invalid"); +} + +void FileManager::readConfig() { +// Read the user's config if it exists + Common::File f; + fpath_t path; + config_t tmpConfig = _config; + + debugC(1, kDebugFile, "readConfig"); + + sprintf(path, "%s%s", _vm.getGameStatus().path, CONFIGFILE); + if (f.open(path)) { + // If config format changed, ignore it and use defaults + if (f.read(&_config, sizeof(_config)) != sizeof(_config)) + _config = tmpConfig; + + f.close(); + } +} + +void FileManager::writeConfig() { +// Write the user's config + FILE *f; + fpath_t path; + + debugC(1, kDebugFile, "writeConfig"); + + // Write user's config + // No error checking in case CD-ROM with no alternate path specified + sprintf(path, "%s%s", _vm.getGameStatus().path, CONFIGFILE); + if ((f = fopen(path, "w+")) != NULL) + fwrite(&_config, sizeof(_config), 1, f); + + fclose(f); +} + +uif_hdr_t *FileManager::getUIFHeader(uif_t id) { +// Returns address of uif_hdr[id], reading it in if first call + static uif_hdr_t UIFHeader[MAX_UIFS]; // Lookup for uif fonts/images + static bool firstFl = true; + Common::File ip; // Image data file + + debugC(1, kDebugFile, "getUIFHeader(%d)", id); + + // Initialize offset lookup if not read yet + if (firstFl) { + firstFl = false; + // Open unbuffered to do far read + if (!ip.open(UIF_FILE)) + Utils::Error(FILE_ERR, UIF_FILE); + + if (ip.size() < (int32)sizeof(UIFHeader)) + Utils::Error(FILE_ERR, UIF_FILE); + + for (int i = 0; i < MAX_UIFS; ++i) { + UIFHeader[i].size = ip.readUint16LE(); + UIFHeader[i].offset = ip.readUint32LE(); + } + + ip.close(); + } + return &UIFHeader[id]; +} + +void FileManager::readUIFItem(int16 id, byte *buf) { +// Read uif item into supplied buffer. + Common::File ip; // UIF_FILE handle + uif_hdr_t *UIFHeaderPtr; // Lookup table of items + seq_t seq; // Dummy seq_t for image data + + debugC(1, kDebugFile, "readUIFItem(%d, ...)", id); + + // Open uif file to read data + if (!ip.open(UIF_FILE)) + Utils::Error(FILE_ERR, UIF_FILE); + + // Seek to data + UIFHeaderPtr = getUIFHeader((uif_t)id); + ip.seek(UIFHeaderPtr->offset, SEEK_SET); + + // We support pcx images and straight data + switch (id) { + case UIF_IMAGES: // Read uif images file + readPCX(ip, &seq, buf, true, UIF_FILE); + break; + default: // Read file data into supplied array + if (ip.read(buf, UIFHeaderPtr->size) != UIFHeaderPtr->size) + Utils::Error(FILE_ERR, UIF_FILE); + break; + } + + ip.close(); +} + +void FileManager::instructions() { +// Simple instructions given when F1 pressed twice in a row +// Only in DOS versions +#define HELPFILE "help.dat" +#define EOP '#' /* Marks end of a page in help file */ + + Common::File f; + char line[1024], *wrkLine; + char readBuf[2]; + + wrkLine = line; + if (!f.open(UIF_FILE)) + Utils::Error(FILE_ERR, HELPFILE); + + while (f.read(readBuf, 1)) { + wrkLine[0] = readBuf[0]; + do { + f.read(wrkLine, 1); + } while (*wrkLine++ != EOP); + wrkLine[-2] = '\0'; /* Remove EOP and previous CR */ + Utils::Box(BOX_ANY, line); + f.read(wrkLine, 1); /* Remove CR after EOP */ + } + f.close(); +} + +} // end of namespace Hugo diff --git a/engines/hugo/file.h b/engines/hugo/file.h new file mode 100755 index 0000000000..50f4b505c3 --- /dev/null +++ b/engines/hugo/file.h @@ -0,0 +1,86 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on original Hugo Trilogy source code + * + * Copyright (c) 1989-1995 David P. Gray + * + */ + +#ifndef HUGO_FILE_H +#define HUGO_FILE_H + +namespace Hugo { + +class FileManager { +public: + FileManager(HugoEngine &vm); + + + bool fileExists(char *filename); + + char *fetchString(int index); + + sound_pt getSound(short sound, uint16 *size); + + void closePlaybackFile(); + void closeDatabaseFiles(); + void initSavedGame(); + void instructions(); + void openDatabaseFiles(); + void readBackground(int screenIndex); + void readBootFile(); + void readConfig(); + void readImage(int objNum, object_t *objPtr); + void readOverlay(int screenNum, image_pt image, ovl_t overlayType); + void readUIFItem(short id, byte *buf); + void restoreGame(short slot); + void restoreSeq(object_t *obj); + void saveGame(short slot, const char *descrip); + void saveSeq(object_t *obj); + void writeConfig(); + +private: + HugoEngine &_vm; + + Common::File _stringArchive; /* Handle for string file */ + Common::File _sceneryArchive; /* Handle for scenery file */ + Common::File _objectsArchive; /* Handle for objects file */ + + byte *convertPCC(byte *p, uint16 y, uint16 bpl, image_pt data_p); + seq_t *readPCX(Common::File &f, seq_t *seqPtr, byte *imagePtr, bool firstFl, const char *name); + uif_hdr_t *getUIFHeader(uif_t id); + +//Strangerke : Not used? + void openPlaybackFile(bool playbackFl, bool recordFl); + void printBootText(); +// bool pkkey(); +// char pbget(); +}; + + +} // end of namespace Hugo +#endif //HUGO_FILE_H diff --git a/engines/hugo/game.h b/engines/hugo/game.h new file mode 100755 index 0000000000..e2e685c193 --- /dev/null +++ b/engines/hugo/game.h @@ -0,0 +1,880 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on original Hugo Trilogy source code + * + * Copyright (c) 1989-1995 David P. Gray + * + */ + +#ifndef HUGO_GAME_H +#define HUGO_GAME_H + +#include "common/keyboard.h" + +namespace Common { +class WriteStream; +class SeekableReadStream; +} + +namespace Hugo { + +// WARNING!! Run the program at least once before release to +// generate the initial save file! (Using the -i cmd switch) +// Set EPISODE_NUM & build. Build pictures.mak and run "Tools/Hugo N". +// Copy helpedit\hugow_?.hlp to .\hugowin?.hlp +// Type "PPG" in the game to enter cheat mode. + +#define DEBUG false // Allow me to do special things (EDIT, GOTO) +#define STORY true // Skip any intro stuff if FALSE +//#define DATABASE TRUE // Use database instead of individual files. Individual files are used by Hugo1 DOS! +//#define BETA FALSE // Puts big msg in intro/about box +#define EPISODE_NUM 1 // Episode we are building +#define TITLE "Hugo for Windows" +#define COPYRIGHT "Copyright © 1995-97, David P. Gray" +// Started code on 04/01/95 +// Don't forget to update Hugowin.rc2 with version info +//#define VER "1.0" // 10/01/95 Initial Release +//#define VER "1.1" // 10/06/95 Restore system volume levels on exit +//#define VER "v1.2"// 10/12/95 Added "background music" checkbox in volume dlg +//#define VER "v1.3"// 10/23/95 Support game 1 as shareware +//#define VER "v1.4"// 12/06/95 Faster graphics, logical palette +#define VER "v1.5" // 10/07/97 Added order form, new web site + +// Game specific equates +#define MAX_TUNES 16 // Max number of tunes +#define NORMAL_TPS 9 // Number of ticks (frames) per second +#define TURBO_TPS 16 // This many in turbo mode +#define DX 5 // Num pixels moved in x by HERO per step +#define DY 4 // Num pixels moved in y by HERO per step +#define XBYTES 40 // number of bytes in a compressed line +#define XPIX 320 // Width of pcx background file +#define YPIX 200 // Height of pcx background file +#define VIEW_DX XPIX // Width of window view +#define VIEW_DY 184 // Height of window view +#define INV_DX 32 // Width of an inventory icon +#define INV_DY 32 // Height of inventory icon +#define DIBOFF_Y 8 // Offset into dib SrcY (old status line area) +#define OVL_SIZE (XBYTES * YPIX) // Size of an overlay file +#define MAX_SEQUENCES 4 // Number of sequences of images in object +#define MAX_CHARS (XBYTES - 2) // Max length of user input line +#define NUM_ROWS 25 // Number of text lines in display +#define MAX_BOX (MAX_CHARS * NUM_ROWS) // Max chars on screen +#define DONT_CARE 0xFF // Any state allowed in command verb +#define MAX_FPATH 256 // Max length of a full path name +#define HERO_MAX_WIDTH 24 // Maximum width of hero +#define HERO_MIN_WIDTH 16 // Minimum width of hero +#define LOOK_NAME 1 // Index of name used in showing takeables +#define TAKE_NAME 2 // Index of name used in confirming take +#define TAKE_TEXT "Picked up the %s ok." +#define REP_MASK 0xC0 // Top 2 bits mean a repeat code +#define MAX_STRLEN 1024 +#define WARNLEN 512 +#define ERRLEN 512 +#define STEP_DY 8 // Pixels per step movement + +// Only for non-database +#define BKGEXT ".PCX" // Extension of background files +#define OBJEXT ".PIX" // Extension of object picture files +#define NAME_LEN 12 // Max length of a DOS file name + +// Definitions of 'generic' commands: Max # depends on size of gencmd in +// the object_t record since each requires 1 bit. Currently up to 16 +#define LOOK 1 +#define TAKE 2 +#define DROP 4 +#define LOOK_S 8 // Description depends on state of object + +// Macros: +#define TPS (_config.turboFl ? TURBO_TPS : NORMAL_TPS) + + +enum TEXTCOLORS { + _TBLACK, _TBLUE, _TGREEN, _TCYAN, + _TRED, _TMAGENTA, _TBROWN, _TWHITE, + _TGRAY, _TLIGHTBLUE, _TLIGHTGREEN, _TLIGHTCYAN, + _TLIGHTRED, _TLIGHTMAGENTA, _TLIGHTYELLOW, _TBRIGHTWHITE +}; + +#define CRYPT "Copyright 1992, David P Gray, Gray Design Associates" + +struct hugo_boot_t { // Common HUGO boot file + char checksum; // Checksum for boot structure (not exit text) + char registered; // TRUE if registered version, else FALSE + char pbswitch[8]; // Playback switch string + char distrib[32]; // Distributor branding string + uint16 exit_len; // Length of exit text (next in file) +}; + +#define MAX_UIFS 32 // Max possible uif items in hdr +#define NUM_FONTS 3 // Number of dib fonts +#define FIRST_FONT U_FONT5 +#define FONT_LEN 128 // Number of chars in font +#define FONTSIZE 1200 // Max size of font data + +struct uif_hdr_t { // UIF font/image look up + uint16 size; // Size of uif item + uint32 offset; // Offset of item in file +}; + +enum uif_t {U_FONT5, U_FONT6, U_FONT8, UIF_IMAGES, NUM_UIF_ITEMS}; + +// Game specific type definitions +typedef byte *image_pt; // ptr to an object image (sprite) +typedef byte *sound_pt; // ptr to sound (or music) data + +// Enumerate overlay file types +enum ovl_t {BOUNDARY, OVERLAY, OVLBASE}; + +// Enumerate error types +enum { + GEN_ERR, FILE_ERR, WRITE_ERR, PCCH_ERR, HEAP_ERR, EVNT_ERR, SOUND_ERR + //MOUSE_ERR, VID_ERR, FONT_ERR, ARG_ERR, CHK_ERR, TIMER_ERR, VBX_ERR +}; + +// Enumerate ways of cycling a sequence of frames +enum cycle_t {INVISIBLE, ALMOST_INVISIBLE, NOT_CYCLING, CYCLE_FORWARD, CYCLE_BACKWARD}; + +// Enumerate sequence index matching direction of travel +enum {RIGHT, LEFT, DOWN, _UP}; + +// Channel requirement during sound effect +enum stereo_t {BOTH_CHANNELS, RIGHT_CHANNEL, LEFT_CHANNEL}; + +// Priority for sound effect +enum priority_t {LOW_PRI, MED_PRI, HIGH_PRI}; + +// Enumerate the different path types for an object +enum path_t { + USER, // User has control of object via cursor keys + AUTO, // Computer has control, controlled by action lists + QUIET, // Computer has control and no commands allowed + CHASE, // Computer has control, object is chasing hero + CHASE2, // Same as CHASE, except keeps cycling when stationary + WANDER, // Computer has control, object is wandering randomly + WANDER2 // Same as WANDER, except keeps cycling when stationary +}; + +// Enumerate whether object is foreground, background or 'floating' +// If floating, HERO can collide with it and fore/back ground is determined +// by relative y-coord of object base. This is the general case. +// If fore or background, no collisions can take place and object is either +// behind or in front of all others, although can still be hidden by the +// the overlay plane. OVEROVL means the object is FLOATING (to other +// objects) but is never hidden by the overlay plane +enum {FOREGROUND, BACKGROUND, FLOATING, OVEROVL}; + +// Game view state machine +enum vstate_t {V_IDLE, V_INTROINIT, V_INTRO, V_PLAY, V_INVENT, V_EXIT}; + +enum font_t {LARGE_ROMAN, MED_ROMAN, NUM_GDI_FONTS, INIT_FONTS, DEL_FONTS}; + +// Ways to dismiss a text/prompt box +enum box_t {BOX_ANY, BOX_OK, BOX_PROMPT, BOX_YESNO}; + +// Standard viewport sizes +enum wsize_t {SIZE_DEF, SIZE_1, SIZE_2, SIZE_3}; + +// Display list functions +enum dupdate_t {D_INIT, D_ADD, D_DISPLAY, D_RESTORE}; + +// General device installation commands +enum inst_t {INSTALL, RESTORE, RESET}; + +// Inventory icon bar states +enum istate_t {I_OFF, I_UP, I_DOWN, I_ACTIVE}; + +// Actions for Process_inventory() +enum invact_t {INV_INIT, INV_LEFT, INV_RIGHT, INV_GET}; + +// Purpose of an automatic route +enum go_t {GO_SPACE, GO_EXIT, GO_LOOK, GO_GET}; + +// Following are points for achieving certain actions. +struct point_t { + byte score; // The value of the point + bool scoredFl; // Whether scored yet +}; + +// Structure for initializing maze processing +struct maze_t { + bool enabledFl; // TRUE when maze processing enabled + byte size; // Size of (square) maze matrix + int x1, y1, x2, y2; // maze hotspot bounding box + int x3, x4; // north, south x entry coordinates + byte firstScreenIndex; // index of first screen in maze +}; + +// Following defines the action types and action list +enum action_t { // Parameters: + ANULL = 0xff, // Special NOP used to 'delete' events in DEL_EVENTS + ASCHEDULE = 0, // 0 - Ptr to action list to be rescheduled + START_OBJ, // 1 - Object number + INIT_OBJXY, // 2 - Object number, x,y + PROMPT, // 3 - index of prompt & response string, ptrs to action + // lists. First if response matches, 2nd if not. + BKGD_COLOR, // 4 - new background color + INIT_OBJVXY, // 5 - Object number, vx, vy + INIT_CARRY, // 6 - Object number, carried status + INIT_HF_COORD, // 7 - Object number (gets hero's 'feet' coordinates) + NEW_SCREEN, // 8 - New screen number + INIT_OBJSTATE, // 9 - Object number, new object state + INIT_PATH, // 10 - Object number, new path type + COND_R, // 11 - Conditional on object state - req state, 2 act_lists + TEXT, // 12 - Simple text box + SWAP_IMAGES, // 13 - Swap 2 object images + COND_SCR, // 14 - Conditional on current screen + AUTOPILOT, // 15 - Set object to home in on another (stationary) object + INIT_OBJ_SEQ, // 16 - Object number, sequence index to set curr_seq_p to + SET_STATE_BITS, // 17 - Objnum, mask to OR with obj states word + CLEAR_STATE_BITS, // 18 - Objnum, mask to ~AND with obj states word + TEST_STATE_BITS, // 19 - Objnum, mask to test obj states word + DEL_EVENTS, // 20 - Action type to delete all occurrences of + GAMEOVER, // 21 - Disable hero & commands. Game is over + INIT_HH_COORD, // 22 - Object number (gets hero's actual coordinates) + EXIT, // 23 - Exit game back to DOS + BONUS, // 24 - Get score bonus for an action + COND_BOX, // 25 - Conditional on object within bounding box + SOUND, // 26 - Set currently playing sound + ADD_SCORE, // 27 - Add object's value to current score + SUB_SCORE, // 28 - Subtract object's value from current score + COND_CARRY, // 29 - Conditional on carrying object + INIT_MAZE, // 30 - Start special maze hotspot processing + EXIT_MAZE, // 31 - Exit special maze processing + INIT_PRIORITY, // 32 - Initialize fbg field + INIT_SCREEN, // 33 - Initialise screen field of object + AGSCHEDULE, // 34 - Global schedule - lasts over new screen + REMAPPAL, // 35 - Remappe palette - palette index, color + COND_NOUN, // 36 - Conditional on noun appearing in line + SCREEN_STATE, // 37 - Set new screen state - used for comments + INIT_LIPS, // 38 - Position lips object for supplied object + INIT_STORY_MODE, // 39 - Set story mode TRUE/FALSE (user can't type) + WARN, // 40 - Same as TEXT but can't dismiss box by typing + COND_BONUS, // 41 - Conditional on bonus having been scored + TEXT_TAKE, // 42 - Issue text box with "take" info string + YESNO, // 43 - Prompt user for Yes or No + STOP_ROUTE, // 44 - Skip any route in progress (hero still walks) + COND_ROUTE, // 45 - Conditional on route in progress + INIT_JUMPEXIT, // 46 - Initialize status.jumpexit + INIT_VIEW, // 47 - Initialize viewx, viewy, dir + INIT_OBJ_FRAME, // 48 - Object number, seq,frame to set curr_seq_p to + OLD_SONG = 49 // Added by Strangerke - Set currently playing sound, old way: that is, using a string index instead of a reference in a file +}; + + +struct act0 { // Type 0 - Schedule + action_t actType; // The type of action + int timer; // Time to set off the action + uint16 actIndex; // Ptr to an action list +}; + +struct act1 { // Type 1 - Start an object + action_t actType; // The type of action + int timer; // Time to set off the action + int objNumb; // The object number + int cycleNumb; // Number of times to cycle + cycle_t cycle; // Direction to start cycling +}; + +struct act2 { // Type 2 - Initialise an object coords + action_t actType; // The type of action + int timer; // Time to set off the action + int objNumb; // The object number + int x, y; // Coordinates +}; + +struct act3 { // Type 3 - Prompt user for text + action_t actType; // The type of action + int timer; // Time to set off the action + uint16 promptIndex; // Index of prompt string + int *responsePtr; // Array of indexes to valid response + // string(s) (terminate list with -1) + uint16 actPassIndex; // Ptr to action list if success + uint16 actFailIndex; // Ptr to action list if failure + bool encodedFl; // (HUGO 1 DOS ONLY) Whether response is encoded or not +}; + +struct act4 { // Type 4 - Set new background color + action_t actType; // The type of action + int timer; // Time to set off the action + long newBackgroundColor; // New color +}; + +struct act5 { // Type 5 - Initialise an object velocity + action_t actType; // The type of action + int timer; // Time to set off the action + int objNumb; // The object number + int vx, vy; // velocity +}; + +struct act6 { // Type 6 - Initialise an object carrying + action_t actType; // The type of action + int timer; // Time to set off the action + int objNumb; // The object number + bool carriedFl; // carrying +}; + +struct act7 { // Type 7 - Initialise an object to hero's coords + action_t actType; // The type of action + int timer; // Time to set off the action + int objNumb; // The object number +}; + +struct act8 { // Type 8 - switch to new screen + action_t actType; // The type of action + int timer; // Time to set off the action + int screenIndex; // The new screen number +}; + +struct act9 { // Type 9 - Initialise an object state + action_t actType; // The type of action + int timer; // Time to set off the action + int objNumb; // The object number + byte newState; // New state +}; + +struct act10 { // Type 10 - Initialise an object path type + action_t actType; // The type of action + int timer; // Time to set off the action + int objNumb; // The object number + int newPathType; // New path type + char vxPath, vyPath; // Max delta velocities e.g. for CHASE +}; + +struct act11 { // Type 11 - Conditional on object's state + action_t actType; // The type of action + int timer; // Time to set off the action + int objNumb; // The object number + byte stateReq; // Required state + uint16 actPassIndex; // Ptr to action list if success + uint16 actFailIndex; // Ptr to action list if failure +}; + +struct act12 { // Type 12 - Simple text box + action_t actType; // The type of action + int timer; // Time to set off the action + int stringIndex; // Index (enum) of string in strings.dat +}; + +struct act13 { // Type 13 - Swap first object image with second + action_t actType; // The type of action + int timer; // Time to set off the action + int obj1; // Index of first object + int obj2; // 2nd +}; + +struct act14 { // Type 14 - Conditional on current screen + action_t actType; // The type of action + int timer; // Time to set off the action + int objNumb; // The required object + int screenReq; // The required screen number + uint16 actPassIndex; // Ptr to action list if success + uint16 actFailIndex; // Ptr to action list if failure +}; + +struct act15 { // Type 15 - Home in on an object + action_t actType; // The type of action + int timer; // Time to set off the action + int obj1; // The object number homing in + int obj2; // The object number to home in on + char dx, dy; // Max delta velocities +}; +// Note: Don't set a sequence at time 0 of a new screen, it causes +// problems clearing the boundary bits of the object! timer > 0 is safe +struct act16 { // Type 16 - Set curr_seq_p to seq + action_t actType; // The type of action + int timer; // Time to set off the action + int objNumb; // The object number + int seqIndex; // The index of seq array to set to +}; + +struct act17 { // Type 17 - SET obj individual state bits + action_t actType; // The type of action + int timer; // Time to set off the action + int objNumb; // The object number + int stateMask; // The mask to OR with current obj state +}; + +struct act18 { // Type 18 - CLEAR obj individual state bits + action_t actType; // The type of action + int timer; // Time to set off the action + int objNumb; // The object number + int stateMask; // The mask to ~AND with current obj state +}; + +struct act19 { // Type 19 - TEST obj individual state bits + action_t actType; // The type of action + int timer; // Time to set off the action + int objNumb; // The object number + int stateMask; // The mask to AND with current obj state + uint16 actPassIndex; // Ptr to action list (all bits set) + uint16 actFailIndex; // Ptr to action list (not all set) +}; + +struct act20 { // Type 20 - Remove all events with this type of action + action_t actType; // The type of action + int timer; // Time to set off the action + action_t actTypeDel; // The action type to remove +}; + +struct act21 { // Type 21 - Gameover. Disable hero & commands + action_t actType; // The type of action + int timer; // Time to set off the action +}; + +struct act22 { // Type 22 - Initialise an object to hero's coords + action_t actType; // The type of action + int timer; // Time to set off the action + int objNumb; // The object number +}; + +struct act23 { // Type 23 - Exit game back to DOS + action_t actType; // The type of action + int timer; // Time to set off the action +}; + +struct act24 { // Type 24 - Get bonus score + action_t actType; // The type of action + int timer; // Time to set off the action + int pointIndex; // Index into points array +}; + +struct act25 { // Type 25 - Conditional on bounding box + action_t actType; // The type of action + int timer; // Time to set off the action + int objNumb; // The required object number + int x1, y1, x2, y2; // The bounding box + uint16 actPassIndex; // Ptr to action list if success + uint16 actFailIndex; // Ptr to action list if failure +}; + +struct act26 { // Type 26 - Play a sound + action_t actType; // The type of action + int timer; // Time to set off the action + int16 soundIndex; // Sound index in data file +}; + +struct act27 { // Type 27 - Add object's value to score + action_t actType; // The type of action + int timer; // Time to set off the action + int objNumb; // object number +}; + +struct act28 { // Type 28 - Subtract object's value from score + action_t actType; // The type of action + int timer; // Time to set off the action + int objNumb; // object number +}; + +struct act29 { // Type 29 - Conditional on object carried + action_t actType; // The type of action + int timer; // Time to set off the action + int objNumb; // The required object number + uint16 actPassIndex; // Ptr to action list if success + uint16 actFailIndex; // Ptr to action list if failure +}; + +struct act30 { // Type 30 - Start special maze processing + action_t actType; // The type of action + int timer; // Time to set off the action + byte mazeSize; // Size of (square) maze + int x1, y1, x2, y2; // Bounding box of maze + int x3, x4; // Extra x points for perspective correction + byte firstScreenIndex; // First (top left) screen of maze +}; + +struct act31 { // Type 31 - Exit special maze processing + action_t actType; // The type of action + int timer; // Time to set off the action +}; + +struct act32 { // Type 32 - Init fbg field of object + action_t actType; // The type of action + int timer; // Time to set off the action + int objNumb; // The object number + byte priority; // Value of foreground/background field +}; + +struct act33 { // Type 33 - Init screen field of object + action_t actType; // The type of action + int timer; // Time to set off the action + int objNumb; // The object number + int screenIndex; // Screen number +}; + +struct act34 { // Type 34 - Global Schedule + action_t actType; // The type of action + int timer; // Time to set off the action + uint16 actIndex; // Ptr to an action list +}; + +struct act35 { // Type 35 - Remappe palette + action_t actType; // The type of action + int timer; // Time to set off the action + int16 oldColorIndex; // Old color index, 0..15 + int16 newColorIndex; // New color index, 0..15 +}; + +struct act36 { // Type 36 - Conditional on noun mentioned + action_t actType; // The type of action + int timer; // Time to set off the action + uint16 nounIndex; // The required noun (list) + uint16 actPassIndex; // Ptr to action list if success + uint16 actFailIndex; // Ptr to action list if failure +}; + +struct act37 { // Type 37 - Set new screen state + action_t actType; // The type of action + int timer; // Time to set off the action + int screenIndex; // The screen number + byte newState; // The new state +}; + +struct act38 { // Type 38 - Position lips + action_t actType; // The type of action + int timer; // Time to set off the action + int lipsObjNumb; // The LIPS object + int objNumb; // The object to speak + byte dxLips; // Relative offset of x + byte dyLips; // Relative offset of y +}; + +struct act39 { // Type 39 - Init story mode + action_t actType; // The type of action + int timer; // Time to set off the action + bool storyModeFl; // New state of story_mode flag +}; + +struct act40 { // Type 40 - Unsolicited text box + action_t actType; // The type of action + int timer; // Time to set off the action + int stringIndex; // Index (enum) of string in strings.dat +}; + +struct act41 { // Type 41 - Conditional on bonus scored + action_t actType; // The type of action + int timer; // Time to set off the action + int BonusIndex; // Index into bonus list + uint16 actPassIndex; // Index of the action list if scored for the first time + uint16 actFailIndex; // Index of the action list if already scored +}; + +struct act42 { // Type 42 - Text box with "take" string + action_t actType; // The type of action + int timer; // Time to set off the action + int objNumb; // The object taken +}; + +struct act43 { // Type 43 - Prompt user for Yes or No + action_t actType; // The type of action + int timer; // Time to set off the action + int promptIndex; // index of prompt string + uint16 actYesIndex; // Ptr to action list if YES + uint16 actNoIndex; // Ptr to action list if NO +}; + +struct act44 { // Type 44 - Stop any route in progress + action_t actType; // The type of action + int timer; // Time to set off the action +}; + +struct act45 { // Type 45 - Conditional on route in progress + action_t actType; // The type of action + int timer; // Time to set off the action + int routeIndex; // Must be >= current status.rindex + uint16 actPassIndex; // Ptr to action list if en-route + uint16 actFailIndex; // Ptr to action list if not +}; + +struct act46 { // Type 46 - Init status.jumpexit + action_t actType; // The type of action + int timer; // Time to set off the action + bool jumpExitFl; // New state of jumpexit flag +}; + +struct act47 { // Type 47 - Init viewx,viewy,dir + action_t actType; // The type of action + int timer; // Time to set off the action + int objNumb; // The object + int16 viewx; // object.viewx + int16 viewy; // object.viewy + int16 direction; // object.dir +}; + +struct act48 { // Type 48 - Set curr_seq_p to frame n + action_t actType; // The type of action + int timer; // Time to set off the action + int objNumb; // The object number + int seqIndex; // The index of seq array to set to + int frameIndex; // The index of frame to set to +}; + +struct act49 { // Added by Strangerke - Type 79 - Play a sound (DOS way) + action_t actType; // The type of action + int timer; // Time to set off the action + uint16 soundIndex; // Sound index in string array +}; + +union act { + act0 a0; + act1 a1; + act2 a2; + act3 a3; + act4 a4; + act5 a5; + act6 a6; + act7 a7; + act8 a8; + act9 a9; + act10 a10; + act11 a11; + act12 a12; + act13 a13; + act14 a14; + act15 a15; + act16 a16; + act17 a17; + act18 a18; + act19 a19; + act20 a20; + act21 a21; + act22 a22; + act23 a23; + act24 a24; + act25 a25; + act26 a26; + act27 a27; + act28 a28; + act29 a29; + act30 a30; + act31 a31; + act32 a32; + act33 a33; + act34 a34; + act35 a35; + act36 a36; + act37 a37; + act38 a38; + act39 a39; + act40 a40; + act41 a41; + act42 a42; + act43 a43; + act44 a44; + act45 a45; + act46 a46; + act47 a47; + act48 a48; + act49 a49; +}; + +// The following determines how a verb is acted on, for an object +struct cmd { + uint16 verbIndex; // the verb + uint16 reqIndex; // ptr to list of required objects + uint16 textDataNoCarryIndex; // ptr to string if any of above not carried + byte reqState; // required state for verb to be done + byte newState; // new states if verb done + uint16 textDataWrongIndex; // ptr to string if wrong state + uint16 textDataDoneIndex; // ptr to string if verb done + uint16 actIndex; // Ptr to action list if verb done +}; + +// The following is a linked list of images in an animation sequence +// The image data is in 8-bit DIB format, i.e. 1 byte = 1 pixel +struct seq_t { // Linked list of images + byte *imagePtr; // ptr to image + uint16 bytesPerLine8; // bytes per line (8bits) + uint16 lines; // lines + uint16 x1, x2, y1, y2; // Offsets from x,y: data bounding box + seq_t *nextSeqPtr; // ptr to next record +}; + +// The following is an array of structures of above sequences +struct seqList_t { + uint16 imageNbr; // Number of images in sequence + seq_t *seqPtr; // Ptr to sequence structure +}; + +// Following is definition of object attributes +struct object_t { + uint16 nounIndex; // String identifying object + uint16 dataIndex; // String describing the object + uint16 *stateDataIndex; // Added by Strangerke to handle the LOOK_S state-dependant descriptions + path_t pathType; // Describe path object follows + int vxPath, vyPath; // Delta velocities (e.g. for CHASE) + uint16 actIndex; // Action list to do on collision with hero + byte seqNumb; // Number of sequences in list + seq_t *currImagePtr; // Sequence image currently in use + seqList_t seqList[MAX_SEQUENCES]; // Array of sequence structure ptrs and lengths + cycle_t cycling; // Whether cycling, forward or backward + byte cycleNumb; // No. of times to cycle + byte frameInterval; // Interval (in ticks) between frames + byte frameTimer; // Decrementing timer for above + char radius; // Defines sphere of influence by hero + byte screenIndex; // Screen in which object resides + int x, y; // Current coordinates of object + int oldx, oldy; // Previous coordinates of object + char vx, vy; // Velocity + byte objValue; // Value of object + int genericCmd; // Bit mask of 'generic' commands for object + uint16 cmdIndex; // ptr to list of cmd structures for verbs + bool carriedFl; // TRUE if object being carried + byte state; // state referenced in cmd list + bool verbOnlyFl; // TRUE if verb-only cmds allowed e.g. sit,look + byte priority; // Whether object fore, background or floating + int16 viewx, viewy; // Position to view object from (or 0 or -1) + int16 direction; // Direction to view object from + byte curSeqNum; // Save which seq number currently in use + byte curImageNum; // Save which image of sequence currently in use + char oldvx; // Previous vx (used in wandering) + char oldvy; // Previous vy +}; + +// Following is structure of verbs and nouns for 'background' objects +// These are objects that appear in the various screens, but nothing +// interesting ever happens with them. Rather than just be dumb and say +// "don't understand" we produce an interesting msg to keep user sane. +struct background_t { + uint16 verbIndex; + uint16 nounIndex; + int commentIndex; // Index of comment produced on match + bool matchFl; // TRUE if noun must match when present + byte roomState; // "State" of room. Comments might differ. + byte bonusIndex; // Index of bonus score (0 = no bonus) +}; + +typedef background_t *objectList_t; + +typedef byte overlay_t[OVL_SIZE]; // Overlay file +typedef byte viewdib_t[(long)XPIX *YPIX]; // Viewport dib +typedef byte icondib_t[XPIX *INV_DY]; // Icon bar dib + +typedef char command_t[MAX_CHARS + 8]; // Command line (+spare for prompt,cursor) +typedef char fpath_t[MAX_FPATH]; // File path + +// Structure to define an EXIT or other collision-activated hotspot +struct hotspot_t { + int screenIndex; // Screen in which hotspot appears + int x1, y1, x2, y2; // Bounding box of hotspot + uint16 actIndex; // Actions to carry out if a 'hit' + int16 viewx, viewy, direction; // Used in auto-route mode +}; + +struct status_t { // Game status (not saved) + bool initSaveFl; // Force save of initial game + bool storyModeFl; // Game is telling story - no commands + bool gameOverFl; // Game is over - hero knobbled + bool playbackFl; // Game is in playback mode + bool recordFl; // Game is in record mode + bool demoFl; // Game is in demo mode + bool debugFl; // Game is in debug mode + bool textBoxFl; // Game is (halted) in text box +// Strangerke - Not used ? +// bool mmtimeFl; // Multimedia timer supported + bool lookFl; // Toolbar "look" button pressed + bool recallFl; // Toolbar "recall" button pressed + bool leftButtonFl; // Left mouse button pressed + bool rightButtonFl; // Right button pressed + bool newScreenFl; // New screen just loaded in dib_a + bool jumpExitFl; // Allowed to jump to a screen exit + bool godModeFl; // Allow DEBUG features in live version + bool helpFl; // Calling WinHelp (don't disable music) + uint32 tick; // Current time in ticks + uint32 saveTick; // Time of last save in ticks + vstate_t viewState; // View state machine + istate_t inventoryState; // Inventory icon bar state + int16 inventoryHeight; // Inventory icon bar height + int16 inventoryObjId; // Inventory object selected, or -1 + int16 routeIndex; // Index into route list, or -1 + go_t go_for; // Purpose of an automatic route + int16 go_id; // Index of exit of object walking to + fpath_t path; // Alternate path for saved files + long saveSize; // Size of a saved game + int16 saveSlot; // Current slot to save/restore game + int16 screenWidth; // Desktop screen width + int16 song; // Current song + int16 cx, cy; // Cursor position (dib coords) +}; + +struct config_t { // User's config (saved) + bool musicFl; // State of Music button/menu item + bool soundFl; // State of Sound button/menu item + bool turboFl; // State of Turbo button/menu item +// int16 wx, wy; // Position of viewport + int16 cx, cy; // Size of viewport + bool backgroundMusicFl; // Continue music when task inactive + byte musicVolume; // Music volume percentage + byte soundVolume; // Sound volume percentage + bool playlist[MAX_TUNES]; // Tune playlist +}; + +struct target_t { // Secondary target for action + uint16 nounIndex; // Secondary object + uint16 verbIndex; // Action on secondary object +}; + +struct uses_t { // Define uses of certain objects + int16 objId; // Primary object + uint16 dataIndex; // String if no secondary object matches + target_t *targets; // List of secondary targets +}; + +// Global externs +extern config_t _config; // User's config +extern maze_t _maze; // Maze control structure +extern hugo_boot_t _boot; // Boot info structure +extern char _textBoxBuffer[]; // Useful box text buffer +extern command_t _line; // Line of user text input + +/* Structure of scenery file lookup entry */ +struct sceneBlock_t { + uint32 scene_off; + uint32 scene_len; + uint32 b_off; + uint32 b_len; + uint32 o_off; + uint32 o_len; + uint32 ob_off; + uint32 ob_len; +}; + +/* Structure of object file lookup entry */ +struct objBlock_t { + uint32 objOffset; + uint32 objLength; +}; + +#include "common/pack-start.h" // START STRUCT PACKING +struct sound_hdr_t { // Sound file lookup entry + uint16 size; // Size of sound data in bytes + uint32 offset; // Offset of sound data in file +} PACKED_STRUCT; +#include "common/pack-end.h" // END STRUCT PACKING + +} + +#endif diff --git a/engines/hugo/global.h b/engines/hugo/global.h new file mode 100755 index 0000000000..fc39474ad6 --- /dev/null +++ b/engines/hugo/global.h @@ -0,0 +1,55 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on original Hugo Trilogy source code + * + * Copyright (c) 1989-1995 David P. Gray + * + */ + +namespace Hugo { + +#define HERO 0 // In all enums, HERO is the first element + +#define DESCRIPLEN 32 /* Length of description string */ +#define MAX_SOUNDS 64 /* Max number of sounds */ +#define BOOTFILE "HUGO.BSF" /* Name of boot structure file */ +#define CONFIGFILE "CONFIG.DAT" /* Name of config file */ +#define LEN_MASK 0x3F /* Lower 6 bits are length */ +#define PBFILE "playback.dat" + +/* Name scenery and objects picture databases */ +#define SCENERY_FILE "scenery.dat" +#define OBJECTS_FILE "objects.dat" +#define STRING_FILE "strings.dat" +#define SOUND_FILE "sounds.dat" + +/* User interface database (Windows Only) */ +#define UIF_FILE "uif.dat" + +static const int kSavegameVersion = 1; +} // Namespace Hugo + diff --git a/engines/hugo/hugo.cpp b/engines/hugo/hugo.cpp new file mode 100755 index 0000000000..2221ebd77b --- /dev/null +++ b/engines/hugo/hugo.cpp @@ -0,0 +1,1446 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "common/system.h" +#include "common/events.h" +#include "common/debug-channels.h" + +#include "hugo/hugo.h" +#include "hugo/global.h" +#include "hugo/game.h" +#include "hugo/file.h" +#include "hugo/schedule.h" +#include "hugo/display.h" +#include "hugo/mouse.h" +#include "hugo/inventory.h" +#include "hugo/parser.h" +#include "hugo/route.h" +#include "hugo/sound.h" +#include "hugo/intro.h" + +#include "engines/util.h" + +namespace Hugo { + +HugoEngine *HugoEngine::s_Engine = NULL; + +overlay_t HugoEngine::_boundary; +overlay_t HugoEngine::_overlay; +overlay_t HugoEngine::_ovlBase; +overlay_t HugoEngine::_objBound; + +HugoEngine::HugoEngine(OSystem *syst, const HugoGameDescription *gd) : Engine(syst), _gameDescription(gd), _mouseX(0), _mouseY(0), + _textData(0), _stringtData(0), _screenNames(0), _textEngine(0), _textIntro(0), _textMouse(0), _textParser(0), _textSchedule(0), _textUtil(0), + _arrayNouns(0), _arrayVerbs(0), _arrayReqs(0), _hotspots(0), _invent(0), _uses(0), _catchallList(0), _backgroundObjects(0), + _points(0), _cmdList(0), _screenActs(0), _objects(0), _actListArr(0), _heroImage(0), _defltTunes(0), _palette(0), _introX(0), + _introY(0), _maxInvent(0), _numBonuses(0), _numScreens(0), _tunesNbr(0), _soundSilence(0), _soundTest(0), _screenStates(0), _numObj(0), + _score(0), _maxscore(0) + +{ + DebugMan.addDebugChannel(kDebugSchedule, "Schedule", "Script Schedule debug level"); + DebugMan.addDebugChannel(kDebugEngine, "Engine", "Engine debug level"); + DebugMan.addDebugChannel(kDebugDisplay, "Display", "Display debug level"); + DebugMan.addDebugChannel(kDebugMouse, "Mouse", "Mouse debug level"); + DebugMan.addDebugChannel(kDebugParser, "Parser", "Parser debug level"); + DebugMan.addDebugChannel(kDebugFile, "File", "File IO debug level"); + DebugMan.addDebugChannel(kDebugRoute, "Route", "Route debug level"); + DebugMan.addDebugChannel(kDebugInventory, "Inventory", "Inventory debug level"); + + _fileManager = new FileManager(*this); + _scheduler = new Scheduler(*this); + _screen = new Screen(*this); + _mouseHandler = new MouseHandler(*this); + _inventoryHandler = new InventoryHandler(*this); + _parser = new Parser(*this); + _route = new Route(*this); + _soundHandler = new SoundHandler(*this); +} + +HugoEngine::~HugoEngine() { + delete _soundHandler; + delete _route; + delete _parser; + delete _inventoryHandler; + delete _mouseHandler; + delete _screen; + delete _scheduler; + delete _fileManager; + + free(_palette); + free(_introX); + free(_introY); + +#if 0 + freeTexts(_textData); + freeTexts(_stringtData); + freeTexts(_textEngine); + freeTexts(_textIntro); + freeTexts(_textMouse); + freeTexts(_textParser); + freeTexts(_textSchedule); + freeTexts(_textUtil); +#endif + free(_textData); + free(_stringtData); + free(_screenNames); + free(_textEngine); + free(_textIntro); + free(_textMouse); + free(_textParser); + free(_textSchedule); + free(_textUtil); + + warning("Missing: free _arrayNouns"); + warning("Missing: free _arrayVerbs"); + + free(_arrayReqs); + free(_hotspots); + free(_invent); + free(_uses); + free(_catchallList); + + warning("Missing: free _background_objects"); + + free(_points); + + warning("Missing: free _cmdList"); + warning("Missing: free _screenActs"); + warning("Missing: free _objects"); + + free(_defltTunes); + free(_screenStates); +} + +GameType HugoEngine::getGameType() const { + return _gameType; +} + +Common::Platform HugoEngine::getPlatform() const { + return _platform; +} + +bool HugoEngine::isPacked() const { + return _packedFl; +} + +Common::Error HugoEngine::run() { + s_Engine = this; + initGraphics(320, 200, false); + + if (!loadHugoDat()) + return Common::kUnknownError; + + // Interesting situation: We have no cursor to show, since + // the DOS version had none, and the Windows version just used + // the windows default one. Meaning this call will just use whatever + // was used last, i.e. the launcher GUI cursor. What to do? + g_system->showMouse(true); + + initStatus(); // Initialize game status + initConfig(INSTALL); // Initialize user's config + initialize(); + initConfig(RESET); // Reset user's config + + file().restoreGame(-1); + + initMachine(); + + // Start the state machine + _status.viewState = V_INTROINIT; + + bool doQuitFl = false; + + while (!doQuitFl) { + g_system->updateScreen(); + runMachine(); + // Handle input + Common::Event event; + while (_eventMan->pollEvent(event)) { + switch (event.type) { + case Common::EVENT_KEYDOWN: + parser().keyHandler(event.kbd.keycode, 0); + break; + case Common::EVENT_MOUSEMOVE: + _mouseX = event.mouse.x; + _mouseY = event.mouse.y; + break; + case Common::EVENT_LBUTTONDOWN: + _status.leftButtonFl = true; + break; + case Common::EVENT_LBUTTONUP: + _status.leftButtonFl = false; + break; + case Common::EVENT_RBUTTONDOWN: + _status.rightButtonFl = true; + break; + case Common::EVENT_RBUTTONUP: + _status.rightButtonFl = false; + break; + case Common::EVENT_QUIT: + doQuitFl = true; + break; + default: + break; + } + } + } + return Common::kNoError; +} + +void HugoEngine::initMachine() { + file().readBackground(_numScreens - 1); // Splash screen + readObjectImages(); // Read all object images + if (_platform == Common::kPlatformWindows) + readUIFImages(); // Read all uif images (only in Win versions) +} + +void HugoEngine::runMachine() { +// Hugo game state machine - called during onIdle + static uint32 lastTime; + + status_t &gameStatus = getGameStatus(); + + // Don't process if we're in a textbox + if (gameStatus.textBoxFl) + return; + + // Don't process if gameover + if (gameStatus.gameOverFl) + return; + + // Process machine once every tick + if (g_system->getMillis() - lastTime < (uint32)(1000 / TPS)) + return; + lastTime = g_system->getMillis(); + + switch (gameStatus.viewState) { + case V_IDLE: // Not processing state machine + intro().preNewGame(); // Any processing before New Game selected + break; + case V_INTROINIT: // Initialization before intro begins + intro().introInit(); + gameStatus.viewState = V_INTRO; + break; + case V_INTRO: // Do any game-dependant preamble + if (intro().introPlay()) { // Process intro screen + scheduler().newScreen(0); // Initialize first screen + gameStatus.viewState = V_PLAY; + } + break; + case V_PLAY: // Playing game + parser().charHandler(); // Process user cmd input + moveObjects(); // Process object movement + scheduler().runScheduler(); // Process any actions + screen().displayList(D_RESTORE); // Restore previous background + updateImages(); // Draw into _frontBuffer, compile display list + mouse().mouseHandler(); // Mouse activity - adds to display list + parser().drawStatusText(); + screen().displayList(D_DISPLAY); // Blit the display list to screen + break; + case V_INVENT: // Accessing inventory + inventory().runInventory(); // Process Inventory state machine + break; + case V_EXIT: // Game over or user exited + gameStatus.viewState = V_IDLE; + break; + } +} + +bool HugoEngine::loadHugoDat() { + Common::File in; + int numElem, numSubElem, numSubAct; + char buf[256]; + int majVer, minVer; + + in.open("hugo.dat"); + + if (!in.isOpen()) { + Common::String errorMessage = "You're missing the 'hugo.dat' file. Get it from the ScummVM website"; + GUIErrorMessage(errorMessage); + warning("%s", errorMessage.c_str()); + return false; + } + + // Read header + in.read(buf, 4); + buf[4] = '\0'; + + if (strcmp(buf, "HUGO")) { + Common::String errorMessage = "File 'hugo.dat' is corrupt. Get it from the ScummVM website"; + GUIErrorMessage(errorMessage); + warning("%s", errorMessage.c_str()); + return false; + } + + majVer = in.readByte(); + minVer = in.readByte(); + + if ((majVer != HUGO_DAT_VER_MAJ) || (minVer != HUGO_DAT_VER_MIN)) { + snprintf(buf, 256, "File 'hugo.dat' is wrong version. Expected %d.%d but got %d.%d. Get it from the ScummVM website", HUGO_DAT_VER_MAJ, HUGO_DAT_VER_MIN, majVer, minVer); + GUIErrorMessage(buf); + warning("%s", buf); + + return false; + } + + _numVariant = in.readUint16BE(); + + // Read textData + _textData = loadTextsVariante(in, 0); + + // Read stringtData + // Only Hugo 1 DOS should use this array + _stringtData = loadTextsVariante(in, 0); + + // Read arrayNouns + _arrayNouns = loadTextsArray(in); + + // Read arrayVerbs + _arrayVerbs = loadTextsArray(in); + + // Read screenNames + _screenNames = loadTextsVariante(in, &_numScreens); + + // Read palette + _paletteSize = in.readUint16BE(); + _palette = (byte *)malloc(sizeof(byte) * _paletteSize); + for (int i = 0; i < _paletteSize; i++) { + _palette[i] = in.readByte(); + } + + // Read textEngine + _textEngine = loadTexts(in); + + // Read textIntro + _textIntro = loadTexts(in); + + // Read x_intro and y_intro + _introXSize = in.readUint16BE(); + _introX = (byte *)malloc(sizeof(byte) * _introXSize); + _introY = (byte *)malloc(sizeof(byte) * _introXSize); + for (int i = 0; i < _introXSize; i++) { + _introX[i] = in.readByte(); + _introY[i] = in.readByte(); + } + + // Read textMouse + _textMouse = loadTexts(in); + + // Read textParser + _textParser = loadTexts(in); + + // Read textSchedule + _textSchedule = loadTexts(in); + + // Read textUtil + _textUtil = loadTexts(in); + + // Read _arrayReqs + _arrayReqs = loadLongArray(in); + + // Read _hotspots + for (int varnt = 0; varnt < _numVariant; varnt++) { + int numRows = in.readUint16BE(); + if (varnt == _gameVariant) { + _hotspots = (hotspot_t *)malloc(sizeof(hotspot_t) * numRows); + for (int i = 0; i < numRows; i++) { + _hotspots[i].screenIndex = in.readSint16BE(); + _hotspots[i].x1 = in.readSint16BE(); + _hotspots[i].y1 = in.readSint16BE(); + _hotspots[i].x2 = in.readSint16BE(); + _hotspots[i].y2 = in.readSint16BE(); + _hotspots[i].actIndex = in.readUint16BE(); + _hotspots[i].viewx = in.readSint16BE(); + _hotspots[i].viewy = in.readSint16BE(); + _hotspots[i].direction = in.readSint16BE(); + } + } else { + for (int i = 0; i < numRows; i++) { + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + in.readUint16BE(); + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + } + } + } + + //Read _invent + for (int varnt = 0; varnt < _numVariant; varnt++) { + numElem = in.readUint16BE(); + if (varnt == _gameVariant) { + _maxInvent = numElem; + _invent = (int16 *)malloc(sizeof(int16) * numElem); + for (int i = 0; i < numElem; i++) + _invent[i] = in.readSint16BE(); + } else { + for (int i = 0; i < numElem; i++) + in.readSint16BE(); + } + } + + //Read _uses + for (int varnt = 0; varnt < _numVariant; varnt++) { + numElem = in.readUint16BE(); + if (varnt == _gameVariant) { + _uses = (uses_t *)malloc(sizeof(uses_t) * numElem); + for (int i = 0; i < numElem; i++) { + _uses[i].objId = in.readSint16BE(); + _uses[i].dataIndex = in.readUint16BE(); + numSubElem = in.readUint16BE(); + _uses[i].targets = (target_t *)malloc(sizeof(target_t) * numSubElem); + for (int j = 0; j < numSubElem; j++) { + _uses[i].targets[j].nounIndex = in.readUint16BE(); + _uses[i].targets[j].verbIndex = in.readUint16BE(); + } + } + } else { + for (int i = 0; i < numElem; i++) { + in.readSint16BE(); + in.readUint16BE(); + numSubElem = in.readUint16BE(); + for (int j = 0; j < numSubElem; j++) { + in.readUint16BE(); + in.readUint16BE(); + } + } + } + } + + //Read _catchallList + for (int varnt = 0; varnt < _numVariant; varnt++) { + numElem = in.readUint16BE(); + if (varnt == _gameVariant) { + _catchallList = (background_t *)malloc(sizeof(background_t) * numElem); + for (int i = 0; i < numElem; i++) { + _catchallList[i].verbIndex = in.readUint16BE(); + _catchallList[i].nounIndex = in.readUint16BE(); + _catchallList[i].commentIndex = in.readSint16BE(); + _catchallList[i].matchFl = (in.readByte() != 0); + _catchallList[i].roomState = in.readByte(); + _catchallList[i].bonusIndex = in.readByte(); + } + } else { + for (int i = 0; i < numElem; i++) { + in.readUint16BE(); + in.readUint16BE(); + in.readSint16BE(); + in.readByte(); + in.readByte(); + in.readByte(); + } + } + } + +// Read _background_objects + for (int varnt = 0; varnt < _numVariant; varnt++) { + numElem = in.readUint16BE(); + if (varnt == _gameVariant) { + _backgroundObjects = (background_t **)malloc(sizeof(background_t *) * numElem); + for (int i = 0; i < numElem; i++) { + numSubElem = in.readUint16BE(); + _backgroundObjects[i] = (background_t *)malloc(sizeof(background_t) * numSubElem); + for (int j = 0; j < numSubElem; j++) { + _backgroundObjects[i][j].verbIndex = in.readUint16BE(); + _backgroundObjects[i][j].nounIndex = in.readUint16BE(); + _backgroundObjects[i][j].commentIndex = in.readSint16BE(); + _backgroundObjects[i][j].matchFl = (in.readByte() != 0); + _backgroundObjects[i][j].roomState = in.readByte(); + _backgroundObjects[i][j].bonusIndex = in.readByte(); + } + } + } else { + for (int i = 0; i < numElem; i++) { + numSubElem = in.readUint16BE(); + for (int j = 0; j < numSubElem; j++) { + in.readUint16BE();; + in.readUint16BE();; + in.readSint16BE();; + in.readByte(); + in.readByte(); + in.readByte(); + } + } + } + } + + // Read _points + for (int varnt = 0; varnt < _numVariant; varnt++) { + numElem = in.readUint16BE(); + if (varnt == _gameVariant) { + _numBonuses = numElem; + _points = (point_t *)malloc(sizeof(point_t) * _numBonuses); + for (int i = 0; i < _numBonuses; i++) { + _points[i].score = in.readByte(); + _points[i].scoredFl = false; + } + } else { + for (int i = 0; i < numElem; i++) + in.readByte(); + } + } + + // Read _cmdList + for (int varnt = 0; varnt < _numVariant; varnt++) { + numElem = in.readUint16BE(); + if (varnt == _gameVariant) { + _cmdList = (cmd **)malloc(sizeof(cmd *) * numElem); + for (int i = 0; i < numElem; i++) { + numSubElem = in.readUint16BE(); + _cmdList[i] = (cmd *)malloc(sizeof(cmd) * numSubElem); + for (int j = 0; j < numSubElem; j++) { + _cmdList[i][j].verbIndex = in.readUint16BE(); + _cmdList[i][j].reqIndex = in.readUint16BE(); + _cmdList[i][j].textDataNoCarryIndex = in.readUint16BE(); + _cmdList[i][j].reqState = in.readByte(); + _cmdList[i][j].newState = in.readByte(); + _cmdList[i][j].textDataWrongIndex = in.readUint16BE(); + _cmdList[i][j].textDataDoneIndex = in.readUint16BE(); + _cmdList[i][j].actIndex = in.readUint16BE(); + } + } + } else { + for (int i = 0; i < numElem; i++) { + numSubElem = in.readUint16BE(); + for (int j = 0; j < numSubElem; j++) { + in.readUint16BE(); + in.readUint16BE(); + in.readUint16BE(); + in.readByte(); + in.readByte(); + in.readUint16BE(); + in.readUint16BE(); + in.readUint16BE(); + } + } + } + } + +// TODO: For Hugo2 and Hugo3, if not in story mode, increment _screenActs[0][0] (ex: kALcrashStory + 1 == kALcrashNoStory) + // Read _screenActs + for (int varnt = 0; varnt < _numVariant; varnt++) { + numElem = in.readUint16BE(); + if (varnt == _gameVariant) { + _screenActs = (uint16 **)malloc(sizeof(uint16 *) * numElem); + for (int i = 0; i < numElem; i++) { + numSubElem = in.readUint16BE(); + if (numSubElem == 0) + _screenActs[i] = 0; + else { + _screenActs[i] = (uint16 *)malloc(sizeof(uint16) * numSubElem); + for (int j = 0; j < numSubElem; j++) + _screenActs[i][j] = in.readUint16BE(); + } + } + } else { + for (int i = 0; i < numElem; i++) { + numSubElem = in.readUint16BE(); + for (int j = 0; j < numSubElem; j++) + in.readUint16BE(); + } + } + + } + +// TODO: For Hugo3, if not in story mode, set _objects[2].state to 3 + for (int varnt = 0; varnt < _numVariant; varnt++) { + numElem = in.readUint16BE(); + if (varnt == _gameVariant) { + _objects = (object_t *)malloc(sizeof(object_t) * numElem); + for (int i = 0; i < numElem; i++) { + _objects[i].nounIndex = in.readUint16BE(); + _objects[i].dataIndex = in.readUint16BE(); + numSubElem = in.readUint16BE(); + if (numSubElem == 0) + _objects[i].stateDataIndex = 0; + else + _objects[i].stateDataIndex = (uint16 *)malloc(sizeof(uint16) * numSubElem); + for (int j = 0; j < numSubElem; j++) + _objects[i].stateDataIndex[j] = in.readUint16BE(); + _objects[i].pathType = (path_t) in.readSint16BE(); + _objects[i].vxPath = in.readSint16BE(); + _objects[i].vyPath = in.readSint16BE(); + _objects[i].actIndex = in.readUint16BE(); + _objects[i].seqNumb = in.readByte(); + _objects[i].currImagePtr = 0; + if (_objects[i].seqNumb == 0) { + _objects[i].seqList[0].imageNbr = 0; + _objects[i].seqList[0].seqPtr = 0; + } + for (int j = 0; j < _objects[i].seqNumb; j++) { + _objects[i].seqList[j].imageNbr = in.readUint16BE(); + _objects[i].seqList[j].seqPtr = 0; + } + _objects[i].cycling = (cycle_t)in.readByte(); + _objects[i].cycleNumb = in.readByte(); + _objects[i].frameInterval = in.readByte(); + _objects[i].frameTimer = in.readByte(); + _objects[i].radius = in.readByte(); + _objects[i].screenIndex = in.readByte(); + _objects[i].x = in.readSint16BE(); + _objects[i].y = in.readSint16BE(); + _objects[i].oldx = in.readSint16BE(); + _objects[i].oldy = in.readSint16BE(); + _objects[i].vx = in.readByte(); + _objects[i].vy = in.readByte(); + _objects[i].objValue = in.readByte(); + _objects[i].genericCmd = in.readSint16BE(); + _objects[i].cmdIndex = in.readUint16BE(); + _objects[i].carriedFl = (in.readByte() != 0); + _objects[i].state = in.readByte(); + _objects[i].verbOnlyFl = (in.readByte() != 0); + _objects[i].priority = in.readByte(); + _objects[i].viewx = in.readSint16BE(); + _objects[i].viewy = in.readSint16BE(); + _objects[i].direction = in.readSint16BE(); + _objects[i].curSeqNum = in.readByte(); + _objects[i].curImageNum = in.readByte(); + _objects[i].oldvx = in.readByte(); + _objects[i].oldvy = in.readByte(); + } + } else { + for (int i = 0; i < numElem; i++) { + in.readUint16BE(); + in.readUint16BE(); + numSubElem = in.readUint16BE(); + for (int j = 0; j < numSubElem; j++) + in.readUint16BE(); + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + in.readUint16BE(); + numSubElem = in.readByte(); + for (int j = 0; j < numSubElem; j++) + in.readUint16BE(); + in.readByte(); + in.readByte(); + in.readByte(); + in.readByte(); + in.readByte(); + in.readByte(); + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + in.readByte(); + in.readByte(); + in.readByte(); + in.readSint16BE(); + in.readUint16BE(); + in.readByte(); + in.readByte(); + in.readByte(); + in.readByte(); + in.readSint16BE(); + in.readSint16BE(); + in.readUint16BE(); + in.readByte(); + in.readByte(); + in.readByte(); + in.readByte(); + } + } + } +//#define HERO 0 + _hero = &_objects[HERO]; // This always points to hero + _screen_p = &(_objects[HERO].screenIndex); // Current screen is hero's + _heroImage = HERO; // Current in use hero image + +//read _actListArr + for (int varnt = 0; varnt < _numVariant; varnt++) { + numElem = in.readUint16BE(); + if (varnt == _gameVariant) { + _actListArr = (act **)malloc(sizeof(act *) * numElem); + for (int i = 0; i < numElem; i++) { + numSubElem = in.readUint16BE(); + _actListArr[i] = (act *) malloc(sizeof(act) * (numSubElem + 1)); + for (int j = 0; j < numSubElem; j++) { + _actListArr[i][j].a0.actType = (action_t) in.readByte(); + switch (_actListArr[i][j].a0.actType) { + case ANULL: // -1 + break; + case ASCHEDULE: // 0 + _actListArr[i][j].a0.timer = in.readSint16BE(); + _actListArr[i][j].a0.actIndex = in.readUint16BE(); + break; + case START_OBJ: // 1 + _actListArr[i][j].a1.timer = in.readSint16BE(); + _actListArr[i][j].a1.objNumb = in.readSint16BE(); + _actListArr[i][j].a1.cycleNumb = in.readSint16BE(); + _actListArr[i][j].a1.cycle = (cycle_t) in.readByte(); + break; + case INIT_OBJXY: // 2 + _actListArr[i][j].a2.timer = in.readSint16BE(); + _actListArr[i][j].a2.objNumb = in.readSint16BE(); + _actListArr[i][j].a2.x = in.readSint16BE(); + _actListArr[i][j].a2.y = in.readSint16BE(); + break; + case PROMPT: // 3 + _actListArr[i][j].a3.timer = in.readSint16BE(); + _actListArr[i][j].a3.promptIndex = in.readSint16BE(); + numSubAct = in.readUint16BE(); + _actListArr[i][j].a3.responsePtr = (int *) malloc(sizeof(int) * numSubAct); + for (int k = 0; k < numSubAct; k++) + _actListArr[i][j].a3.responsePtr[k] = in.readSint16BE(); + _actListArr[i][j].a3.actPassIndex = in.readUint16BE(); + _actListArr[i][j].a3.actFailIndex = in.readUint16BE(); + _actListArr[i][j].a3.encodedFl = (in.readByte() == 1) ? true : false; + break; + case BKGD_COLOR: // 4 + _actListArr[i][j].a4.timer = in.readSint16BE(); + _actListArr[i][j].a4.newBackgroundColor = in.readUint32BE(); + break; + case INIT_OBJVXY: // 5 + _actListArr[i][j].a5.timer = in.readSint16BE(); + _actListArr[i][j].a5.objNumb = in.readSint16BE(); + _actListArr[i][j].a5.vx = in.readSint16BE(); + _actListArr[i][j].a5.vy = in.readSint16BE(); + break; + case INIT_CARRY: // 6 + _actListArr[i][j].a6.timer = in.readSint16BE(); + _actListArr[i][j].a6.objNumb = in.readSint16BE(); + _actListArr[i][j].a6.carriedFl = (in.readByte() == 1) ? true : false; + break; + case INIT_HF_COORD: // 7 + _actListArr[i][j].a7.timer = in.readSint16BE(); + _actListArr[i][j].a7.objNumb = in.readSint16BE(); + break; + case NEW_SCREEN: // 8 + _actListArr[i][j].a8.timer = in.readSint16BE(); + _actListArr[i][j].a8.screenIndex = in.readSint16BE(); + break; + case INIT_OBJSTATE: // 9 + _actListArr[i][j].a9.timer = in.readSint16BE(); + _actListArr[i][j].a9.objNumb = in.readSint16BE(); + _actListArr[i][j].a9.newState = in.readByte(); + break; + case INIT_PATH: // 10 + _actListArr[i][j].a10.timer = in.readSint16BE(); + _actListArr[i][j].a10.objNumb = in.readSint16BE(); + _actListArr[i][j].a10.newPathType = in.readSint16BE(); + _actListArr[i][j].a10.vxPath = in.readByte(); + _actListArr[i][j].a10.vyPath = in.readByte(); + break; + case COND_R: // 11 + _actListArr[i][j].a11.timer = in.readSint16BE(); + _actListArr[i][j].a11.objNumb = in.readSint16BE(); + _actListArr[i][j].a11.stateReq = in.readByte(); + _actListArr[i][j].a11.actPassIndex = in.readUint16BE(); + _actListArr[i][j].a11.actFailIndex = in.readUint16BE(); + break; + case TEXT: // 12 + _actListArr[i][j].a12.timer = in.readSint16BE(); + _actListArr[i][j].a12.stringIndex = in.readSint16BE(); + break; + case SWAP_IMAGES: // 13 + _actListArr[i][j].a13.timer = in.readSint16BE(); + _actListArr[i][j].a13.obj1 = in.readSint16BE(); + _actListArr[i][j].a13.obj2 = in.readSint16BE(); + break; + case COND_SCR: // 14 + _actListArr[i][j].a14.timer = in.readSint16BE(); + _actListArr[i][j].a14.objNumb = in.readSint16BE(); + _actListArr[i][j].a14.screenReq = in.readSint16BE(); + _actListArr[i][j].a14.actPassIndex = in.readUint16BE(); + _actListArr[i][j].a14.actFailIndex = in.readUint16BE(); + break; + case AUTOPILOT: // 15 + _actListArr[i][j].a15.timer = in.readSint16BE(); + _actListArr[i][j].a15.obj1 = in.readSint16BE(); + _actListArr[i][j].a15.obj2 = in.readSint16BE(); + _actListArr[i][j].a15.dx = in.readByte(); + _actListArr[i][j].a15.dy = in.readByte(); + break; + case INIT_OBJ_SEQ: // 16 + _actListArr[i][j].a16.timer = in.readSint16BE(); + _actListArr[i][j].a16.objNumb = in.readSint16BE(); + _actListArr[i][j].a16.seqIndex = in.readSint16BE(); + break; + case SET_STATE_BITS: // 17 + _actListArr[i][j].a17.timer = in.readSint16BE(); + _actListArr[i][j].a17.objNumb = in.readSint16BE(); + _actListArr[i][j].a17.stateMask = in.readSint16BE(); + break; + case CLEAR_STATE_BITS: // 18 + _actListArr[i][j].a18.timer = in.readSint16BE(); + _actListArr[i][j].a18.objNumb = in.readSint16BE(); + _actListArr[i][j].a18.stateMask = in.readSint16BE(); + break; + case TEST_STATE_BITS: // 19 + _actListArr[i][j].a19.timer = in.readSint16BE(); + _actListArr[i][j].a19.objNumb = in.readSint16BE(); + _actListArr[i][j].a19.stateMask = in.readSint16BE(); + _actListArr[i][j].a19.actPassIndex = in.readUint16BE(); + _actListArr[i][j].a19.actFailIndex = in.readUint16BE(); + break; + case DEL_EVENTS: // 20 + _actListArr[i][j].a20.timer = in.readSint16BE(); + _actListArr[i][j].a20.actTypeDel = (action_t) in.readByte(); + break; + case GAMEOVER: // 21 + _actListArr[i][j].a21.timer = in.readSint16BE(); + break; + case INIT_HH_COORD: // 22 + _actListArr[i][j].a22.timer = in.readSint16BE(); + _actListArr[i][j].a22.objNumb = in.readSint16BE(); + break; + case EXIT: // 23 + _actListArr[i][j].a23.timer = in.readSint16BE(); + break; + case BONUS: // 24 + _actListArr[i][j].a24.timer = in.readSint16BE(); + _actListArr[i][j].a24.pointIndex = in.readSint16BE(); + break; + case COND_BOX: // 25 + _actListArr[i][j].a25.timer = in.readSint16BE(); + _actListArr[i][j].a25.objNumb = in.readSint16BE(); + _actListArr[i][j].a25.x1 = in.readSint16BE(); + _actListArr[i][j].a25.y1 = in.readSint16BE(); + _actListArr[i][j].a25.x2 = in.readSint16BE(); + _actListArr[i][j].a25.y2 = in.readSint16BE(); + _actListArr[i][j].a25.actPassIndex = in.readUint16BE(); + _actListArr[i][j].a25.actFailIndex = in.readUint16BE(); + break; + case SOUND: // 26 + _actListArr[i][j].a26.timer = in.readSint16BE(); + _actListArr[i][j].a26.soundIndex = in.readSint16BE(); + break; + case ADD_SCORE: // 27 + _actListArr[i][j].a27.timer = in.readSint16BE(); + _actListArr[i][j].a27.objNumb = in.readSint16BE(); + break; + case SUB_SCORE: // 28 + _actListArr[i][j].a28.timer = in.readSint16BE(); + _actListArr[i][j].a28.objNumb = in.readSint16BE(); + break; + case COND_CARRY: // 29 + _actListArr[i][j].a29.timer = in.readSint16BE(); + _actListArr[i][j].a29.objNumb = in.readSint16BE(); + _actListArr[i][j].a29.actPassIndex = in.readUint16BE(); + _actListArr[i][j].a29.actFailIndex = in.readUint16BE(); + break; + case INIT_MAZE: // 30 + _actListArr[i][j].a30.timer = in.readSint16BE(); + _actListArr[i][j].a30.mazeSize = in.readByte(); + _actListArr[i][j].a30.y1 = in.readSint16BE(); + _actListArr[i][j].a30.x2 = in.readSint16BE(); + _actListArr[i][j].a30.y2 = in.readSint16BE(); + _actListArr[i][j].a30.x3 = in.readSint16BE(); + _actListArr[i][j].a30.x4 = in.readSint16BE(); + _actListArr[i][j].a30.firstScreenIndex = in.readByte(); + break; + case EXIT_MAZE: // 31 + _actListArr[i][j].a31.timer = in.readSint16BE(); + break; + case INIT_PRIORITY: // 32 + _actListArr[i][j].a32.timer = in.readSint16BE(); + _actListArr[i][j].a32.objNumb = in.readSint16BE(); + _actListArr[i][j].a32.priority = in.readByte(); + break; + case INIT_SCREEN: // 33 + _actListArr[i][j].a33.timer = in.readSint16BE(); + _actListArr[i][j].a33.objNumb = in.readSint16BE(); + _actListArr[i][j].a33.screenIndex = in.readSint16BE(); + break; + case AGSCHEDULE: // 34 + _actListArr[i][j].a34.timer = in.readSint16BE(); + _actListArr[i][j].a34.actIndex = in.readUint16BE(); + break; + case REMAPPAL: // 35 + _actListArr[i][j].a35.timer = in.readSint16BE(); + _actListArr[i][j].a35.oldColorIndex = in.readSint16BE(); + _actListArr[i][j].a35.newColorIndex = in.readSint16BE(); + break; + case COND_NOUN: // 36 + _actListArr[i][j].a36.timer = in.readSint16BE(); + _actListArr[i][j].a36.nounIndex = in.readUint16BE(); + _actListArr[i][j].a36.actPassIndex = in.readUint16BE(); + _actListArr[i][j].a36.actFailIndex = in.readUint16BE(); + break; + case SCREEN_STATE: // 37 + _actListArr[i][j].a37.timer = in.readSint16BE(); + _actListArr[i][j].a37.screenIndex = in.readSint16BE(); + _actListArr[i][j].a37.newState = in.readByte(); + break; + case INIT_LIPS: // 38 + _actListArr[i][j].a38.timer = in.readSint16BE(); + _actListArr[i][j].a38.lipsObjNumb = in.readSint16BE(); + _actListArr[i][j].a38.objNumb = in.readSint16BE(); + _actListArr[i][j].a38.dxLips = in.readByte(); + _actListArr[i][j].a38.dyLips = in.readByte(); + break; + case INIT_STORY_MODE: // 39 + _actListArr[i][j].a39.timer = in.readSint16BE(); + _actListArr[i][j].a39.storyModeFl = (in.readByte() == 1); + break; + case WARN: // 40 + _actListArr[i][j].a40.timer = in.readSint16BE(); + _actListArr[i][j].a40.stringIndex = in.readSint16BE(); + break; + case COND_BONUS: // 41 + _actListArr[i][j].a41.timer = in.readSint16BE(); + _actListArr[i][j].a41.BonusIndex = in.readSint16BE(); + _actListArr[i][j].a41.actPassIndex = in.readUint16BE(); + _actListArr[i][j].a41.actFailIndex = in.readUint16BE(); + break; + case TEXT_TAKE: // 42 + _actListArr[i][j].a42.timer = in.readSint16BE(); + _actListArr[i][j].a42.objNumb = in.readSint16BE(); + break; + case YESNO: // 43 + _actListArr[i][j].a43.timer = in.readSint16BE(); + _actListArr[i][j].a43.promptIndex = in.readSint16BE(); + _actListArr[i][j].a43.actYesIndex = in.readUint16BE(); + _actListArr[i][j].a43.actNoIndex = in.readUint16BE(); + break; + case STOP_ROUTE: // 44 + _actListArr[i][j].a44.timer = in.readSint16BE(); + break; + case COND_ROUTE: // 45 + _actListArr[i][j].a45.timer = in.readSint16BE(); + _actListArr[i][j].a45.routeIndex = in.readSint16BE(); + _actListArr[i][j].a45.actPassIndex = in.readUint16BE(); + _actListArr[i][j].a45.actFailIndex = in.readUint16BE(); + break; + case INIT_JUMPEXIT: // 46 + _actListArr[i][j].a46.timer = in.readSint16BE(); + _actListArr[i][j].a46.jumpExitFl = (in.readByte() == 1); + break; + case INIT_VIEW: // 47 + _actListArr[i][j].a47.timer = in.readSint16BE(); + _actListArr[i][j].a47.objNumb = in.readSint16BE(); + _actListArr[i][j].a47.viewx = in.readSint16BE(); + _actListArr[i][j].a47.viewy = in.readSint16BE(); + _actListArr[i][j].a47.direction = in.readSint16BE(); + break; + case INIT_OBJ_FRAME: // 48 + _actListArr[i][j].a48.timer = in.readSint16BE(); + _actListArr[i][j].a48.objNumb = in.readSint16BE(); + _actListArr[i][j].a48.seqIndex = in.readSint16BE(); + _actListArr[i][j].a48.frameIndex = in.readSint16BE(); + break; + case OLD_SONG: //49 + _actListArr[i][j].a49.timer = in.readSint16BE(); + _actListArr[i][j].a49.soundIndex = in.readUint16BE(); + break; + default: + error("Engine - Unknown action type encountered: %d", _actListArr[i][j].a0.actType); + } + } + _actListArr[i][numSubElem].a0.actType = ANULL; + } + } else { + for (int i = 0; i < numElem; i++) { + numSubElem = in.readUint16BE(); + for (int j = 0; j < numSubElem; j++) { + numSubAct = in.readByte(); + switch (numSubAct) { + case ANULL: // -1 + break; + case ASCHEDULE: // 0 + in.readSint16BE(); + in.readUint16BE(); + break; + case START_OBJ: // 1 + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + in.readByte(); + break; + case INIT_OBJXY: // 2 + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + break; + case PROMPT: // 3 + in.readSint16BE(); + in.readSint16BE(); + numSubAct = in.readUint16BE(); + for (int k = 0; k < numSubAct; k++) + in.readSint16BE(); + in.readUint16BE(); + in.readUint16BE(); + in.readByte(); + break; + case BKGD_COLOR: // 4 + in.readSint16BE(); + in.readUint32BE(); + break; + case INIT_OBJVXY: // 5 + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + break; + case INIT_CARRY: // 6 + in.readSint16BE(); + in.readSint16BE(); + in.readByte(); + break; + case INIT_HF_COORD: // 7 + in.readSint16BE(); + in.readSint16BE(); + break; + case NEW_SCREEN: // 8 + in.readSint16BE(); + in.readSint16BE(); + break; + case INIT_OBJSTATE: // 9 + in.readSint16BE(); + in.readSint16BE(); + in.readByte(); + break; + case INIT_PATH: // 10 + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + in.readByte(); + in.readByte(); + break; + case COND_R: // 11 + in.readSint16BE(); + in.readSint16BE(); + in.readByte(); + in.readUint16BE(); + in.readUint16BE(); + break; + case TEXT: // 12 + in.readSint16BE(); + in.readSint16BE(); + break; + case SWAP_IMAGES: // 13 + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + break; + case COND_SCR: // 14 + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + in.readUint16BE(); + in.readUint16BE(); + break; + case AUTOPILOT: // 15 + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + in.readByte(); + in.readByte(); + break; + case INIT_OBJ_SEQ: // 16 + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + break; + case SET_STATE_BITS: // 17 + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + break; + case CLEAR_STATE_BITS: // 18 + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + break; + case TEST_STATE_BITS: // 19 + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + in.readUint16BE(); + in.readUint16BE(); + break; + case DEL_EVENTS: // 20 + in.readSint16BE(); + in.readByte(); + break; + case GAMEOVER: // 21 + in.readSint16BE(); + break; + case INIT_HH_COORD: // 22 + in.readSint16BE(); + in.readSint16BE(); + break; + case EXIT: // 23 + in.readSint16BE(); + break; + case BONUS: // 24 + in.readSint16BE(); + in.readSint16BE(); + break; + case COND_BOX: // 25 + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + in.readUint16BE(); + in.readUint16BE(); + break; + case SOUND: // 26 + in.readSint16BE(); + in.readSint16BE(); + break; + case ADD_SCORE: // 27 + in.readSint16BE(); + in.readSint16BE(); + break; + case SUB_SCORE: // 28 + in.readSint16BE(); + in.readSint16BE(); + break; + case COND_CARRY: // 29 + in.readSint16BE(); + in.readSint16BE(); + in.readUint16BE(); + in.readUint16BE(); + break; + case INIT_MAZE: // 30 + in.readSint16BE(); + in.readByte(); + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + in.readByte(); + break; + case EXIT_MAZE: // 31 + in.readSint16BE(); + break; + case INIT_PRIORITY: // 32 + in.readSint16BE(); + in.readSint16BE(); + in.readByte(); + break; + case INIT_SCREEN: // 33 + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + break; + case AGSCHEDULE: // 34 + in.readSint16BE(); + in.readUint16BE(); + break; + case REMAPPAL: // 35 + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + break; + case COND_NOUN: // 36 + in.readSint16BE(); + in.readUint16BE(); + in.readUint16BE(); + in.readUint16BE(); + break; + case SCREEN_STATE: // 37 + in.readSint16BE(); + in.readSint16BE(); + in.readByte(); + break; + case INIT_LIPS: // 38 + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + in.readByte(); + in.readByte(); + break; + case INIT_STORY_MODE: // 39 + in.readSint16BE(); + in.readByte(); + break; + case WARN: // 40 + in.readSint16BE(); + in.readSint16BE(); + break; + case COND_BONUS: // 41 + in.readSint16BE(); + in.readSint16BE(); + in.readUint16BE(); + in.readUint16BE(); + break; + case TEXT_TAKE: // 42 + in.readSint16BE(); + in.readSint16BE(); + break; + case YESNO: // 43 + in.readSint16BE(); + in.readSint16BE(); + in.readUint16BE(); + in.readUint16BE(); + break; + case STOP_ROUTE: // 44 + in.readSint16BE(); + break; + case COND_ROUTE: // 45 + in.readSint16BE(); + in.readSint16BE(); + in.readUint16BE(); + in.readUint16BE(); + break; + case INIT_JUMPEXIT: // 46 + in.readSint16BE(); + in.readByte(); + break; + case INIT_VIEW: // 47 + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + break; + case INIT_OBJ_FRAME: // 48 + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + in.readSint16BE(); + break; + case OLD_SONG: //49 + in.readSint16BE(); + in.readUint16BE(); + break; + default: + error("Engine - Unknown action type encountered %d - variante %d pos %d.%d", numSubAct, varnt, i, j); + } + } + } + } + } + for (int varnt = 0; varnt < _numVariant; varnt++) { + if (varnt == _gameVariant) { + _tunesNbr = in.readByte(); + _soundSilence = in.readByte(); + _soundTest = in.readByte(); + } else { + in.readByte(); + in.readByte(); + in.readByte(); + } + } + + //Read _defltTunes + for (int varnt = 0; varnt < _numVariant; varnt++) { + numElem = in.readUint16BE(); + if (varnt == _gameVariant) { + _maxInvent = numElem; + _defltTunes = (int16 *)malloc(sizeof(int16) * numElem); + for (int i = 0; i < numElem; i++) + _defltTunes[i] = in.readSint16BE(); + } else { + for (int i = 0; i < numElem; i++) + in.readSint16BE(); + } + } + + //Read _screenStates size + for (int varnt = 0; varnt < _numVariant; varnt++) { + numElem = in.readUint16BE(); + if (varnt == _gameVariant) { + _screenStates = (byte *)malloc(sizeof(byte) * numElem); + for (int i = 0; i < numElem; i++) + _screenStates[i] = 0; + } + } + + //Read look, take and drop special verbs indexes + for (int varnt = 0; varnt < _numVariant; varnt++) { + if (varnt == _gameVariant) { + _look = in.readUint16BE(); + _take = in.readUint16BE(); + _drop = in.readUint16BE(); + } else { + in.readUint16BE(); + in.readUint16BE(); + in.readUint16BE(); + } + } + + //Read LASTOBJ + for (int varnt = 0; varnt < _numVariant; varnt++) { + numElem = in.readUint16BE(); + if (varnt == _gameVariant) + _numObj = numElem; + } + + //Read kALnewscr used by maze (Hugo 2) + for (int varnt = 0; varnt < _numVariant; varnt++) { + numElem = in.readUint16BE(); + if (varnt == _gameVariant) + _alNewscrIndex = numElem; + } + + return true; +} + +char **HugoEngine::loadTextsVariante(Common::File &in, uint16 *arraySize) { + int numTexts; + int entryLen; + int len; + char **res = 0; + char *pos = 0; + + for (int varnt = 0; varnt < _numVariant; varnt++) { + numTexts = in.readUint16BE(); + entryLen = in.readUint16BE(); + pos = (char *)malloc(entryLen); + if (varnt == _gameVariant) { + if (arraySize) + *arraySize = numTexts; + res = (char **)malloc(sizeof(char *) * numTexts); + res[0] = pos; + in.read(res[0], entryLen); + } else { + in.read(pos, entryLen); + } + + pos += DATAALIGNMENT; + + for (int i = 1; i < numTexts; i++) { + pos -= 2; + + len = READ_BE_UINT16(pos); + pos += 2 + len; + + if (varnt == _gameVariant) + res[i] = pos; + } + } + + return res; +} + +uint16 **HugoEngine::loadLongArray(Common::File &in) { + uint16 **resArray = 0; + uint16 *resRow = 0; + uint16 dummy, numRows, numElems; + + for (int varnt = 0; varnt < _numVariant; varnt++) { + numRows = in.readUint16BE(); + if (varnt == _gameVariant) { + resArray = (uint16 **)malloc(sizeof(uint16 *) * (numRows + 1)); + resArray[numRows] = 0; + } + for (int i = 0; i < numRows; i++) { + numElems = in.readUint16BE(); + if (varnt == _gameVariant) { + resRow = (uint16 *)malloc(sizeof(uint16) * numElems); + for (int j = 0; j < numElems; j++) + resRow[j] = in.readUint16BE(); + resArray[i] = resRow; + } else { + for (int j = 0; j < numElems; j++) + dummy = in.readUint16BE(); + } + } + } + return resArray; +} + +char ***HugoEngine::loadTextsArray(Common::File &in) { + int numNouns; + int numTexts; + int entryLen; + int len; + char ***resArray = 0; + char **res = 0; + char *pos = 0; + + for (int varnt = 0; varnt < _numVariant; varnt++) { + numNouns = in.readUint16BE(); + if (varnt == _gameVariant) { + resArray = (char ** *)malloc(sizeof(char **) * (numNouns + 1)); + resArray[numNouns] = 0; + } + for (int i = 0; i < numNouns; i++) { + numTexts = in.readUint16BE(); + entryLen = in.readUint16BE(); + pos = (char *)malloc(entryLen); + if (varnt == _gameVariant) { + res = (char **)malloc(sizeof(char *) * numTexts); + res[0] = pos; + in.read(res[0], entryLen); + } else { + in.read(pos, entryLen); + } + + pos += DATAALIGNMENT; + + for (int j = 0; j < numTexts; j++) { + if (varnt == _gameVariant) + res[j] = pos; + + pos -= 2; + len = READ_BE_UINT16(pos); + pos += 2 + len; + } + + if (varnt == _gameVariant) + resArray[i] = res; + } + } + + return resArray; +} + +char **HugoEngine::loadTexts(Common::File &in) { + int numTexts = in.readUint16BE(); + char **res = (char **)malloc(sizeof(char *) * numTexts); + int entryLen; + char *pos = 0; + int len; + + entryLen = in.readUint16BE(); + pos = (char *)malloc(entryLen); + + in.read(pos, entryLen); + + pos += DATAALIGNMENT; + res[0] = pos; + + for (int i = 1; i < numTexts; i++) { + pos -= 2; + len = READ_BE_UINT16(pos); + pos += 2 + len; + res[i] = pos; + } + + return res; +} + +void HugoEngine::freeTexts(char **ptr) { + if (!ptr) + return; + + free(*ptr); + free(ptr); +} + + +} // End of namespace Hugo diff --git a/engines/hugo/hugo.h b/engines/hugo/hugo.h new file mode 100755 index 0000000000..21705b1070 --- /dev/null +++ b/engines/hugo/hugo.h @@ -0,0 +1,310 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef HUGO_H +#define HUGO_H + +#include "engines/engine.h" +#include "common/file.h" + +// This include is here temporarily while the engine is being refactored. +#include "hugo/game.h" + +#define HUGO_DAT_VER_MAJ 0 // 1 byte +#define HUGO_DAT_VER_MIN 16 // 1 byte +#define DATAALIGNMENT 4 + +namespace Common { +class RandomSource; +} + +namespace Hugo { +enum GameType { + kGameTypeNone = 0, + kGameTypeHugo1, + kGameTypeHugo2, + kGameTypeHugo3 +}; + +enum HugoebugChannels { + kDebugSchedule = 1 << 0, + kDebugEngine = 1 << 1, + kDebugDisplay = 1 << 2, + kDebugMouse = 1 << 3, + kDebugParser = 1 << 4, + kDebugFile = 1 << 5, + kDebugRoute = 1 << 6, + kDebugInventory = 1 << 7 +}; + +enum HugoGameFeatures { + GF_PACKED = (1 << 0) // Database +}; + +struct HugoGameDescription; + +class FileManager; +class Scheduler; +class Screen; +class MouseHandler; +class InventoryHandler; +class Parser; +class Route; +class SoundHandler; +class IntroHandler; + +class HugoEngine : public Engine { +public: + HugoEngine(OSystem *syst, const HugoGameDescription *gd); + ~HugoEngine(); + + byte _numVariant; + byte _gameVariant; + byte _maxInvent; + byte _numBonuses; + byte _soundSilence; + byte _soundTest; + byte _tunesNbr; + uint16 _numScreens; + + object_t *_hero; + byte *_screen_p; + byte _heroImage; + + byte *_palette; + byte *_introX; + byte *_introY; + byte *_screenStates; + char **_textData; + char **_stringtData; + char **_screenNames; + char **_textEngine; + char **_textIntro; + char **_textMouse; + char **_textParser; + char **_textSchedule; + char **_textUtil; + char ***_arrayNouns; + char ***_arrayVerbs; + uint16 **_arrayReqs; + hotspot_t *_hotspots; + int16 *_invent; + uses_t *_uses; + background_t *_catchallList; + background_t **_backgroundObjects; + point_t *_points; + cmd **_cmdList; + uint16 **_screenActs; + object_t *_objects; + act **_actListArr; + int16 *_defltTunes; + uint16 _look; + uint16 _take; + uint16 _drop; + uint16 _numObj; + uint16 _alNewscrIndex; + + Common::RandomSource *_rnd; + + const char *_episode; + const char *_picDir; + + char _initFilename[20]; + char _saveFilename[20]; + + const HugoGameDescription *_gameDescription; + uint32 getFeatures() const; + + GameType getGameType() const; + Common::Platform getPlatform() const; + bool isPacked() const; + + // Temporary, until the engine is fully objectified. + static HugoEngine &get() { + assert(s_Engine != NULL); + return *s_Engine; + } + + FileManager &file() { + return *_fileManager; + } + Scheduler &scheduler() { + return *_scheduler; + } + Screen &screen() { + return *_screen; + } + MouseHandler &mouse() { + return *_mouseHandler; + } + InventoryHandler &inventory() { + return *_inventoryHandler; + } + Parser &parser() { + return *_parser; + } + Route &route() { + return *_route; + } + SoundHandler &sound() { + return *_soundHandler; + } + IntroHandler &intro() { + return *_introHandler; + } + + void initGame(const HugoGameDescription *gd); + void initGamePart(const HugoGameDescription *gd); + bool loadHugoDat(); + + int getMouseX() const { + return _mouseX; + } + int getMouseY() const { + return _mouseY; + } + + void initStatus(); + void readObjectImages(); + void readUIFImages(); + void updateImages(); + void moveObjects(); + void useObject(int16 objId); + bool findObjectSpace(object_t *obj, int16 *destx, int16 *desty); + int16 findObject(uint16 x, uint16 y); + void lookObject(object_t *obj); + void storeBoundary(int x1, int x2, int y); + void clearBoundary(int x1, int x2, int y); + void endGame(); + void readScreenFiles(int screen); + void setNewScreen(int screen); + void initNewScreenDisplay(); + void screenActions(int screen); + void shutdown(); + + overlay_t &getBoundaryOverlay() { + return _boundary; + } + overlay_t &getObjectBoundaryOverlay() { + return _objBound; + } + overlay_t &getBaseBoundaryOverlay() { + return _ovlBase; + } + overlay_t &getFirstOverlay() { + return _overlay; + } + + status_t &getGameStatus() { + return _status; + } + + int getScore() const { + return _score; + } + void setScore(int newScore) { + _score = newScore; + } + void adjustScore(int adjustment) { + _score += adjustment; + } + int getMaxScore() const { + return _maxscore; + } + void setMaxScore(int newScore) { + _maxscore = newScore; + } + byte getIntroSize() { + return _introXSize; + } + +protected: + + // Engine APIs + Common::Error run(); + +private: + int _mouseX; + int _mouseY; + byte _paletteSize; + byte _introXSize; + status_t _status; // Game status structure + + static HugoEngine *s_Engine; + +// The following are bit plane display overlays which mark travel boundaries, +// foreground stationary objects and baselines for those objects (used to +// determine foreground/background wrt moving objects) + +// Vinterstum: These shouldn't be static, but we get weird pathfinding issues (and Valgrind warnings) without. +// Needs more investigation. Alignment issues? + + static overlay_t _boundary; // Boundary overlay file + static overlay_t _overlay; // First overlay file + static overlay_t _ovlBase; // First overlay base file + static overlay_t _objBound; // Boundary file marks object baselines + + GameType _gameType; + Common::Platform _platform; + bool _packedFl; + + FileManager *_fileManager; + Scheduler *_scheduler; + Screen *_screen; + MouseHandler *_mouseHandler; + InventoryHandler *_inventoryHandler; + Parser *_parser; + Route *_route; + SoundHandler *_soundHandler; + IntroHandler *_introHandler; + + int _score; // Holds current score + int _maxscore; // Holds maximum score + + char **loadTextsVariante(Common::File &in, uint16 *arraySize); + char ***loadTextsArray(Common::File &in); + uint16 **loadLongArray(Common::File &in); + char **loadTexts(Common::File &in); + void freeTexts(char **ptr); + + void initPlaylist(bool playlist[MAX_TUNES]); + void initConfig(inst_t action); + void initialize(); + int deltaX(int x1, int x2, int vx, int y); + int deltaY(int x1, int x2, int vy, int y); + void processMaze(); + //int y2comp (const void *a, const void *b); + char *useBG(char *name); + void freeObjects(); + void boundaryCollision(object_t *obj); + void calcMaxScore(); + + void initMachine(); + void runMachine(); +}; + +} // End of namespace Hugo + +#endif // Hugo_H diff --git a/engines/hugo/intro.cpp b/engines/hugo/intro.cpp new file mode 100755 index 0000000000..eac0339660 --- /dev/null +++ b/engines/hugo/intro.cpp @@ -0,0 +1,187 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on original Hugo Trilogy source code + * + * Copyright (c) 1989-1995 David P. Gray + * + */ + +#include "common/system.h" + +#include "hugo/game.h" +#include "hugo/hugo.h" +#include "hugo/intro.h" +#include "hugo/file.h" +#include "hugo/display.h" +#include "hugo/util.h" + + +namespace Hugo { + +IntroHandler::IntroHandler(HugoEngine &vm) : _vm(vm) { +} + +IntroHandler::~IntroHandler() { +} + +intro_1w::intro_1w(HugoEngine &vm) : IntroHandler(_vm) { +} + +intro_1w::~intro_1w() { +} + +void intro_1w::preNewGame() { + // Auto-start a new game + _vm.file().restoreGame(-1); + _vm.getGameStatus().viewState = V_INTROINIT; +} + +void intro_1w::introInit() { +} + +bool intro_1w::introPlay() { + return true; +} + +intro_2w::intro_2w(HugoEngine &vm) : IntroHandler(_vm) { +} + +intro_2w::~intro_2w() { +} + +void intro_2w::preNewGame() { +} + +void intro_2w::introInit() { +} + +bool intro_2w::introPlay() { + return true; +} + +intro_3w::intro_3w(HugoEngine &vm) : IntroHandler(_vm) { +} + +intro_3w::~intro_3w() { +} + +void intro_3w::preNewGame() { +} + +void intro_3w::introInit() { +// Hugo 3 - show map and set up for introPlay() +//#if STORY + _vm.file().readBackground(INTRO_2_FILE); + _vm.screen().displayBackground(); + introTicks = 0; +//#endif +} + +bool intro_3w::introPlay() { + byte introSize = _vm.getIntroSize(); + +// Hugo 3 - Preamble screen before going into game. Draws path of Hugo's plane. +// Called every tick. Returns TRUE when complete +//TODO : Add proper check of story mode +//#if STORY +// SetBkMode(TRANSPARENT); + if (introTicks < introSize) { + // Scale viewport x_intro,y_intro to screen (offsetting y) + _vm.screen().writeChar(_vm._introX[introTicks], _vm._introY[introTicks] - DIBOFF_Y, 'x', _TBRIGHTWHITE); + + // Text boxes at various times + switch (introTicks) { + case 4: + Utils::Box(BOX_OK, _vm._textIntro[kIntro1]); + break; + case 9: + Utils::Box(BOX_OK, _vm._textIntro[kIntro2]); + break; + case 35: + Utils::Box(BOX_OK, _vm._textIntro[kIntro3]); + break; + } + } + + return (++introTicks >= introSize); +//#else //STORY +// return true; +//#endif //STORY +} + +intro_1d::intro_1d(HugoEngine &vm) : IntroHandler(_vm) { +} + +intro_1d::~intro_1d() { +} + +void intro_1d::preNewGame() { +} + +void intro_1d::introInit() { +} + +bool intro_1d::introPlay() { + warning("STUB: intro_1d::introPlay()"); + return true; +} +//TODO : Add code for intro H2 DOS +intro_2d::intro_2d(HugoEngine &vm) : IntroHandler(_vm) { +} + +intro_2d::~intro_2d() { +} + +void intro_2d::preNewGame() { +} + +void intro_2d::introInit() { +} + +bool intro_2d::introPlay() { + return true; +} + +//TODO : Add code for intro H3 DOS +intro_3d::intro_3d(HugoEngine &vm) : IntroHandler(_vm) { +} + +intro_3d::~intro_3d() { +} + +void intro_3d::preNewGame() { +} + +void intro_3d::introInit() { +} + +bool intro_3d::introPlay() { + return true; +} + +} // end of namespace Hugo + diff --git a/engines/hugo/intro.h b/engines/hugo/intro.h new file mode 100755 index 0000000000..202507f975 --- /dev/null +++ b/engines/hugo/intro.h @@ -0,0 +1,130 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on original Hugo Trilogy source code + * + * Copyright (c) 1989-1995 David P. Gray + * + */ + +#ifndef INTRO_H +#define INTRO_H + +namespace Hugo { +// TODO : Remove this enum. Only used in Hugo 3w intro. +// Enumerate picture files. All screens must have an entry here, in order +enum screenid_3w { + CRASH, WEB, BRIDGE_3w, BRIDGE2, CLIFFTOP, WFALL, + WFALL_B, WBASE, STREAM_3w, STREAM2, PATH_UL, VILLAGE, + HUT_OUT, HUT_IN, GARDEN_3w, OLDMAN_3w, CLIFF, SLOPE, + CAMP, SUNSET, TURN, PLANE, MAP, PATH, + CAVE, FINTRO, NUM_PICS +}; +#define INTRO_2_FILE MAP + +enum seqTextIntro { + kIntro1 = 0, + kIntro2 = 1, + kIntro3 = 2 +}; + +class IntroHandler { +public: + IntroHandler(HugoEngine &vm); + virtual ~IntroHandler(); + + virtual void preNewGame() = 0; + virtual void introInit() = 0; + virtual bool introPlay() = 0; + +protected: + HugoEngine &_vm; + int16 introTicks; // Count calls to introPlay() +}; + +class intro_1w : public IntroHandler { +public: + intro_1w(HugoEngine &vm); + ~intro_1w(); + + void preNewGame(); + void introInit(); + bool introPlay(); +}; + +class intro_1d : public IntroHandler { +public: + intro_1d(HugoEngine &vm); + ~intro_1d(); + + void preNewGame(); + void introInit(); + bool introPlay(); +}; + +class intro_2w : public IntroHandler { +public: + intro_2w(HugoEngine &vm); + ~intro_2w(); + + void preNewGame(); + void introInit(); + bool introPlay(); +}; + +class intro_2d : public IntroHandler { +public: + intro_2d(HugoEngine &vm); + ~intro_2d(); + + void preNewGame(); + void introInit(); + bool introPlay(); +}; + +class intro_3w : public IntroHandler { +public: + intro_3w(HugoEngine &vm); + ~intro_3w(); + + void preNewGame(); + void introInit(); + bool introPlay(); +}; + +class intro_3d : public IntroHandler { +public: + intro_3d(HugoEngine &vm); + ~intro_3d(); + + void preNewGame(); + void introInit(); + bool introPlay(); +}; + + +} // Namespace Hugo +#endif diff --git a/engines/hugo/inventory.cpp b/engines/hugo/inventory.cpp new file mode 100755 index 0000000000..1988e9b1cb --- /dev/null +++ b/engines/hugo/inventory.cpp @@ -0,0 +1,229 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on original Hugo Trilogy source code + * + * Copyright (c) 1989-1995 David P. Gray + * + */ + +#include "common/system.h" + +#include "hugo/hugo.h" +#include "hugo/game.h" +#include "hugo/file.h" +#include "hugo/schedule.h" +#include "hugo/display.h" +#include "hugo/mouse.h" +#include "hugo/inventory.h" +#include "hugo/parser.h" + +namespace Hugo { + +#define MAX_DISP (XPIX / INV_DX) // Max icons displayable + +InventoryHandler::InventoryHandler(HugoEngine &vm) : _vm(vm) { +} + +// Construct the inventory scrollbar in dib_i +// imageTotNumb is total number of inventory icons +// displayNumb is number requested for display +// scrollFl is TRUE if scroll arrows required +// firstObjId is index of first (scrolled) inventory object to display +void InventoryHandler::constructInventory(int16 imageTotNumb, int displayNumb, bool scrollFl, int16 firstObjId) { + int16 ux, uy, ix; // Coordinates of icons + + debugC(1, kDebugInventory, "constructInventory(%d, %d, %d, %d)", imageTotNumb, displayNumb, (scrollFl) ? 0 : 1, firstObjId); + + // Clear out icon buffer + memset(_vm.screen().getIconBuffer(), 0, sizeof(_vm.screen().getIconBuffer())); + + // If needed, copy arrows - reduce number of icons displayable + if (scrollFl) { // Display at first and last icon positions + _vm.screen().moveImage(_vm.screen().getGUIBuffer(), 0, 0, INV_DX, INV_DY, XPIX, _vm.screen().getIconBuffer(), 0, 0, XPIX); + _vm.screen().moveImage(_vm.screen().getGUIBuffer(), INV_DX, 0, INV_DX, INV_DY, XPIX, _vm.screen().getIconBuffer(), INV_DX *(MAX_DISP - 1), 0, XPIX); + displayNumb = MIN(displayNumb, MAX_DISP - NUM_ARROWS); + } else // No, override first index - we can show 'em all! + firstObjId = 0; + + // Copy inventory icons to remaining positions + int16 displayed = 0; + int16 carried = 0; + for (int16 i = 0; i < imageTotNumb; i++) { + if (_vm._objects[_vm._invent[i]].carriedFl) { + // Check still room to display and past first scroll index + if (displayed < displayNumb && carried >= firstObjId) { + // Compute source coordinates in dib_u + ux = (i + NUM_ARROWS) * INV_DX % XPIX; + uy = (i + NUM_ARROWS) * INV_DX / XPIX * INV_DY; + + // Compute dest coordinates in dib_i + ix = ((scrollFl) ? displayed + 1 : displayed) * INV_DX; + displayed++; // Count number displayed + + // Copy the icon + _vm.screen().moveImage(_vm.screen().getGUIBuffer(), ux, uy, INV_DX, INV_DY, XPIX, _vm.screen().getIconBuffer(), ix, 0, XPIX); + } + carried++; // Count number carried + } + } +} + +// Process required action for inventory +// Returns objId under cursor (or -1) for INV_GET +int16 InventoryHandler::processInventory(invact_t action, ...) { + static int16 firstIconId = 0; // Index of first icon to display + int16 i, j; + int16 objId = -1; // Return objid under cursor + int16 imageNumb; // Total number of inventory items + int displayNumb; // Total number displayed/carried + int16 cursorx, cursory; // Current cursor position + bool scrollFl; // TRUE if scroll arrows needed + va_list marker; // Args used for D_ADD operation + + debugC(1, kDebugInventory, "processInventory(invact_t action, ...)"); + + // Compute total number and number displayed, i.e. number carried + for (imageNumb = 0, displayNumb = 0; imageNumb < _vm._maxInvent && _vm._invent[imageNumb] != -1; imageNumb++) + if (_vm._objects[_vm._invent[imageNumb]].carriedFl) + displayNumb++; + + // Will we need the scroll arrows? + scrollFl = displayNumb > MAX_DISP; + + switch (action) { + case INV_INIT: // Initialize inventory display + constructInventory(imageNumb, displayNumb, scrollFl, firstIconId); + break; + case INV_LEFT: // Scroll left by one icon + firstIconId = MAX(0, firstIconId - 1); + constructInventory(imageNumb, displayNumb, scrollFl, firstIconId); + break; + case INV_RIGHT: // Scroll right by one icon + firstIconId = MIN(displayNumb, firstIconId + 1); + constructInventory(imageNumb, displayNumb, scrollFl, firstIconId); + break; + case INV_GET: // Return object id under cursor + // Get cursor position from variable argument list + va_start(marker, action); // Initialize variable arguments + cursorx = va_arg(marker, int); // Cursor x + cursory = va_arg(marker, int); // Cursor y + va_end(marker); // Reset variable arguments + + cursory -= DIBOFF_Y; // Icon bar is at true zero + if (cursory > 0 && cursory < INV_DY) { // Within icon bar? + i = cursorx / INV_DX; // Compute icon index + if (scrollFl) // Scroll buttons displayed + if (i == 0) // Left scroll button + objId = LEFT_ARROW; + else { + if (i == MAX_DISP - 1) // Right scroll button + objId = RIGHT_ARROW; + else // Adjust for scroll + i += firstIconId - 1; // i is icon index + } + + // If not an arrow, find object id - limit to valid range + if (objId == -1 && i < displayNumb) + // Find objid by counting # carried objects == i+1 + for (j = 0, i++; i > 0 && j < _vm._numObj; j++) + if (_vm._objects[j].carriedFl) + if (--i == 0) + objId = j; + } + break; + } + return objId; // For the INV_GET action +} + +void InventoryHandler::runInventory() { + status_t &gameStatus = _vm.getGameStatus(); + + debugC(1, kDebugInventory, "runInventory"); + +// Process inventory state machine + switch (gameStatus.inventoryState) { + case I_OFF: // Icon bar off screen + break; + case I_UP: // Icon bar moving up + gameStatus.inventoryHeight -= STEP_DY; // Move the icon bar up + if (gameStatus.inventoryHeight <= 0) // Limit travel + gameStatus.inventoryHeight = 0; + + // Move visible portion to _frontBuffer, restore uncovered portion, display results + _vm.screen().moveImage(_vm.screen().getIconBuffer(), 0, 0, XPIX, gameStatus.inventoryHeight, XPIX, _vm.screen().getFrontBuffer(), 0, DIBOFF_Y, XPIX); + _vm.screen().moveImage(_vm.screen().getBackBufferBackup(), 0, gameStatus.inventoryHeight + DIBOFF_Y, XPIX, STEP_DY, XPIX, _vm.screen().getFrontBuffer(), 0, gameStatus.inventoryHeight + DIBOFF_Y, XPIX); + _vm.screen().displayRect(0, DIBOFF_Y, XPIX, gameStatus.inventoryHeight + STEP_DY); + + if (gameStatus.inventoryHeight == 0) { // Finished moving up? + // Yes, restore dibs and exit back to game state machine + _vm.screen().moveImage(_vm.screen().getBackBufferBackup(), 0, 0, XPIX, YPIX, XPIX, _vm.screen().getBackBuffer(), 0, 0, XPIX); + _vm.screen().moveImage(_vm.screen().getBackBuffer(), 0, 0, XPIX, YPIX, XPIX, _vm.screen().getFrontBuffer(), 0, 0, XPIX); + _vm.updateImages(); // Add objects back into display list for restore + gameStatus.inventoryState = I_OFF; + gameStatus.viewState = V_PLAY; + } + break; + case I_DOWN: // Icon bar moving down + // If this is the first step, initialize dib_i + // and get any icon/text out of _frontBuffer + if (gameStatus.inventoryHeight == 0) { + processInventory(INV_INIT); // Initialize dib_i + _vm.screen().displayList(D_RESTORE); // Restore _frontBuffer + _vm.updateImages(); // Rebuild _frontBuffer without icons/text + _vm.screen().displayList(D_DISPLAY); // Blit display list to screen + } + + gameStatus.inventoryHeight += STEP_DY; // Move the icon bar down + if (gameStatus.inventoryHeight >= INV_DY) // Limit travel + gameStatus.inventoryHeight = INV_DY; + + // Move visible portion to _frontBuffer, display results + _vm.screen().moveImage(_vm.screen().getIconBuffer(), 0, 0, XPIX, gameStatus.inventoryHeight, XPIX, _vm.screen().getFrontBuffer(), 0, DIBOFF_Y, XPIX); + _vm.screen().displayRect(0, DIBOFF_Y, XPIX, gameStatus.inventoryHeight); + + if (gameStatus.inventoryHeight == INV_DY) { // Finished moving down? + // Yes, prepare view dibs for special inventory display since + // we can't refresh objects while icon bar overlayed... + // 1. Save backing store _backBuffer in temporary dib_c + // 2. Make snapshot of _frontBuffer the new _backBuffer backing store + // 3. Reset the display list + _vm.screen().moveImage(_vm.screen().getBackBuffer(), 0, 0, XPIX, YPIX, XPIX, _vm.screen().getBackBufferBackup(), 0, 0, XPIX); + _vm.screen().moveImage(_vm.screen().getFrontBuffer(), 0, 0, XPIX, YPIX, XPIX, _vm.screen().getBackBuffer(), 0, 0, XPIX); + _vm.screen().displayList(D_INIT); + gameStatus.inventoryState = I_ACTIVE; + } + break; + case I_ACTIVE: // Inventory active + _vm.parser().charHandler(); // Still allow commands + _vm.screen().displayList(D_RESTORE); // Restore previous background + _vm.mouse().mouseHandler(); // Mouse activity - adds to display list + _vm.screen().displayList(D_DISPLAY); // Blit the display list to screen + break; + } +} + +} // End of namespace Hugo diff --git a/engines/hugo/inventory.h b/engines/hugo/inventory.h new file mode 100755 index 0000000000..e04d1ba187 --- /dev/null +++ b/engines/hugo/inventory.h @@ -0,0 +1,55 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on original Hugo Trilogy source code + * + * Copyright (c) 1989-1995 David P. Gray + * + */ + +#ifndef HUGO_INVENTORY_H +#define HUGO_INVENTORY_H +namespace Hugo { + +#define NUM_ARROWS 2 // Number of arrows (left/right) +#define LEFT_ARROW -2 // Cursor over Left arrow in inventory icon bar +#define RIGHT_ARROW -3 // Cursor over Right arrow in inventory icon bar + +class InventoryHandler { +public: + InventoryHandler(HugoEngine &vm); + + int16 processInventory(invact_t action, ...); + void runInventory(); + +private: + HugoEngine &_vm; + + void constructInventory(int16 imageTotNumb, int displayNumb, bool scrollFl, int16 firstObjId); +}; + +} // end of namespace Hugo +#endif // HUGO_INVENTORY_H diff --git a/engines/hugo/module.mk b/engines/hugo/module.mk new file mode 100755 index 0000000000..f7aa45a2c2 --- /dev/null +++ b/engines/hugo/module.mk @@ -0,0 +1,24 @@ +MODULE := engines/hugo + +MODULE_OBJS := \ + detection.o \ + display.o \ + engine.o \ + file.o \ + hugo.o \ + intro.o \ + inventory.o \ + mouse.o \ + parser.o \ + route.o \ + schedule.o \ + sound.o \ + util.o + +# This module can be built as a plugin +ifeq ($(ENABLE_HUGO), DYNAMIC_PLUGIN) +PLUGIN := 1 +endif + +# Include common rules +include $(srcdir)/rules.mk diff --git a/engines/hugo/mouse.cpp b/engines/hugo/mouse.cpp new file mode 100755 index 0000000000..581a455469 --- /dev/null +++ b/engines/hugo/mouse.cpp @@ -0,0 +1,309 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on original Hugo Trilogy source code + * + * Copyright (c) 1989-1995 David P. Gray + * + */ + +// mouse.cpp : Handle all mouse activity + +#include "common/system.h" + +#include "hugo/game.h" +#include "hugo/hugo.h" +#include "hugo/mouse.h" +#include "hugo/global.h" +#include "hugo/schedule.h" +#include "hugo/display.h" +#include "hugo/inventory.h" +#include "hugo/route.h" +#include "hugo/util.h" + +namespace Hugo { + +#define EXIT_HOTSPOT -4 // Cursor over Exit hotspot +#define CURSOR_NAME 2 // Index of name used under cursor +#define CURSOR_NOCHAR '~' // Don't show name of object under cursor +#define SX_OFF 10 // Cursor offset to name string +#define SY_OFF -2 // Cursor offset to name string +#define IX_OFF 8 // Cursor to icon image (dib coords) +#define IY_OFF 10 // Cursor to icon image (dib coords) + +enum seqTextMouse { + kMsNoWayText = 0, + kMsExit = 1 +}; + +MouseHandler::MouseHandler(HugoEngine &vm) : _vm(vm) { +} + +// Shadow-blit supplied string into dib_a at cx,cy and add to display list +void MouseHandler::cursorText(char *buffer, int16 cx, int16 cy, uif_t fontId, int16 color) { + + debugC(1, kDebugMouse, "cursorText(%s, %d, %d, %d, %d)", buffer, cx, cy, fontId, color); + + if (_vm.getPlatform() == Common::kPlatformWindows) + _vm.screen().loadFont(fontId); + + // Find bounding rect for string + int16 sdx = _vm.screen().stringLength(buffer); + int16 sdy = _vm.screen().fontHeight() + 1; // + 1 for shadow + int16 sx = (cx < XPIX / 2) ? cx + SX_OFF : cx - sdx - SX_OFF / 2; + int16 sy = cy + SY_OFF; + + // Display the string and add rect to display list + _vm.screen().shadowStr(sx, sy, buffer, _TBRIGHTWHITE); + _vm.screen().displayList(D_ADD, sx, sy, sdx, sdy); +} + + +// Find the exit hotspot containing cx, cy. +// Return hotspot index or -1 if not found. +int16 MouseHandler::findExit(int16 cx, int16 cy) { + int i; + hotspot_t *hotspot; + + debugC(2, kDebugMouse, "findExit(%d, %d)", cx, cy); + + for (i = 0, hotspot = _vm._hotspots; hotspot->screenIndex >= 0; i++, hotspot++) + if (hotspot->screenIndex == *_vm._screen_p) + if (cx >= hotspot->x1 && cx <= hotspot->x2 && cy >= hotspot->y1 && cy <= hotspot->y2) + return(i); + return(-1); +} + +// Process a mouse right click at coord cx, cy over object objid +void MouseHandler::processRightClick(int16 objId, int16 cx, int16 cy) { + object_t *obj; + int16 x, y; + bool foundFl = false; // TRUE if route found to object + + debugC(1, kDebugMouse, "Process_rclick(%d, %d, %d)", objId, cx, cy); + + status_t &gameStatus = _vm.getGameStatus(); + + if (gameStatus.storyModeFl || _vm._hero->pathType == QUIET) // Make sure user has control + return; + + // Check if this was over iconbar + if (gameStatus.inventoryState == I_ACTIVE && cy < INV_DY + DIBOFF_Y) { // Clicked over iconbar object + if (gameStatus.inventoryObjId == -1) + gameStatus.inventoryObjId = objId; // Not using so select new object + else if (gameStatus.inventoryObjId == objId) + gameStatus.inventoryObjId = -1; // Same icon - deselect it + else + _vm.useObject(objId); // Use status.objid on object + } else { // Clicked over viewport object + obj = &_vm._objects[objId]; + switch (obj->viewx) { // Where to walk to + case -1: // Walk to object position + if (_vm.findObjectSpace(obj, &x, &y)) + foundFl = _vm.route().startRoute(GO_GET, objId, x, y); + if (!foundFl) // Can't get there, try to use from here + _vm.useObject(objId); + break; + case 0: // Immediate use + _vm.useObject(objId); // Pick up or use object + break; + default: // Walk to view point if possible + if (!_vm.route().startRoute(GO_GET, objId, obj->viewx, obj->viewy)) + if (_vm._hero->cycling == INVISIBLE) // If invisible do + _vm.useObject(objId); // immediate use + else + Utils::Box(BOX_ANY, _vm._textMouse[kMsNoWayText]); // Can't get there + break; + } + } +} + +// Process a left mouse click over: +// 1. An icon - show description +// 2. An object - walk to and show description +// 3. An icon scroll arrow - scroll the iconbar +// 4. Nothing - attempt to walk there +// 5. Exit - walk to exit hotspot +void MouseHandler::processLeftClick(int16 objId, int16 cx, int16 cy) { + int16 i, x, y; + object_t *obj; + bool foundFl = false; // TRUE if route found to object + + debugC(1, kDebugMouse, "Process_lclick(%d, %d, %d)", objId, cx, cy); + + status_t &gameStatus = _vm.getGameStatus(); + + if (gameStatus.storyModeFl || _vm._hero->pathType == QUIET) // Make sure user has control + return; + + switch (objId) { + case -1: // Empty space - attempt to walk there + _vm.route().startRoute(GO_SPACE, 0, cx, cy); + break; + case LEFT_ARROW: // A scroll arrow - scroll the iconbar + case RIGHT_ARROW: + // Scroll the iconbar and display results + _vm.inventory().processInventory(objId == LEFT_ARROW ? INV_LEFT : INV_RIGHT); + _vm.screen().moveImage(_vm.screen().getIconBuffer(), 0, 0, XPIX, INV_DY, XPIX, _vm.screen().getFrontBuffer(), 0, DIBOFF_Y, XPIX); + _vm.screen().moveImage(_vm.screen().getIconBuffer(), 0, 0, XPIX, INV_DY, XPIX, _vm.screen().getBackBuffer(), 0, DIBOFF_Y, XPIX); + _vm.screen().displayList(D_ADD, 0, DIBOFF_Y, XPIX, INV_DY); + break; + case EXIT_HOTSPOT: // Walk to exit hotspot + i = findExit(cx, cy); + x = _vm._hotspots[i].viewx; + y = _vm._hotspots[i].viewy; + if (x >= 0) { // Hotspot refers to an exit + // Special case of immediate exit + if (gameStatus.jumpExitFl) { + // Get rid of iconbar if necessary + if (gameStatus.inventoryState != I_OFF) + gameStatus.inventoryState = I_UP; + _vm.scheduler().insertActionList(_vm._hotspots[i].actIndex); + } else { // Set up route to exit spot + if (_vm._hotspots[i].direction == Common::KEYCODE_RIGHT) + x -= HERO_MAX_WIDTH; + else if (_vm._hotspots[i].direction == Common::KEYCODE_LEFT) + x += HERO_MAX_WIDTH; + if (!_vm.route().startRoute(GO_EXIT, i, x, y)) + Utils::Box(BOX_ANY, _vm._textMouse[kMsNoWayText]); // Can't get there + } + + // Get rid of any attached icon + gameStatus.inventoryObjId = -1; + } + break; + default: // Look at an icon or object + obj = &_vm._objects[objId]; + + // Over iconbar - immediate description + if (gameStatus.inventoryState == I_ACTIVE && cy < INV_DY + DIBOFF_Y) + _vm.lookObject(obj); + else { + switch (obj->viewx) { // Clicked over viewport object + case -1: // Walk to object position + if (_vm.findObjectSpace(obj, &x, &y)) + foundFl = _vm.route().startRoute(GO_LOOK, objId, x, y); + if (!foundFl) // Can't get there, immediate description + _vm.lookObject(obj); + break; + case 0: // Immediate description + _vm.lookObject(obj); + break; + default: // Walk to view point if possible + if (!_vm.route().startRoute(GO_LOOK, objId, obj->viewx, obj->viewy)) + if (_vm._hero->cycling == INVISIBLE) // If invisible do + _vm.lookObject(obj); // immediate decription + else + Utils::Box(BOX_ANY, _vm._textMouse[kMsNoWayText]); // Can't get there + break; + } + } + break; + } +} + +// Process mouse activity +void MouseHandler::mouseHandler() { + int16 iconId; // Find index of dragged icon + int iconx, icony; // Icon position (in dib_a) + int16 ux, uy; // Icon position (in dib_u) + int16 objId = -1; // Current source object + char *name; // Name of object to display + + debugC(2, kDebugMouse, "mouseHandler"); + + int16 cx = _vm.getMouseX(); + int16 cy = _vm.getMouseY(); + + status_t &gameStatus = _vm.getGameStatus(); + + gameStatus.cx = cx; // Save cursor coords + gameStatus.cy = cy; + + // Don't process if outside client area + if (cx < 0 || cx > XPIX || cy < DIBOFF_Y || cy > VIEW_DY + DIBOFF_Y) + return; + + // Display dragged inventory icon if one currently selected + if (gameStatus.inventoryObjId != -1) { + // Find index of icon + for (iconId = 0; iconId < _vm._maxInvent; iconId++) + if (gameStatus.inventoryObjId == _vm._invent[iconId]) + break; + + // Compute source coordinates in dib_u + ux = (iconId + NUM_ARROWS) * INV_DX % XPIX; + uy = (iconId + NUM_ARROWS) * INV_DX / XPIX * INV_DY; + + // Compute destination coordinates in dib_a + iconx = cx + IX_OFF; + icony = cy + IY_OFF; + iconx = MAX(iconx, 0); // Keep within dib_a bounds + iconx = MIN(iconx, XPIX - INV_DX); + icony = MAX(icony, 0); + icony = MIN(icony, YPIX - INV_DY); + + // Copy the icon and add to display list + _vm.screen().moveImage(_vm.screen().getGUIBuffer(), ux, uy, INV_DX, INV_DY, XPIX, _vm.screen().getFrontBuffer(), iconx, icony, XPIX); + _vm.screen().displayList(D_ADD, iconx, icony, INV_DX, INV_DY); + } + + // Process cursor over an object or icon + if (gameStatus.inventoryState == I_ACTIVE) // Check inventory icon bar first + objId = _vm.inventory().processInventory(INV_GET, cx, cy); + if (objId == -1) // No match, check rest of view + objId = _vm.findObject(cx, cy); + if (objId >= 0) { // Got a match + // Display object name next to cursor (unless CURSOR_NOCHAR) + // Note test for swapped hero name + name = _vm._arrayNouns[_vm._objects[objId == HERO ? _vm._heroImage : objId].nounIndex][CURSOR_NAME]; + if (name[0] != CURSOR_NOCHAR) + cursorText(name, cx, cy, U_FONT8, _TBRIGHTWHITE); + + // Process right click over object in view or iconbar + if (gameStatus.rightButtonFl) + processRightClick(objId, cx, cy); + } + + // Process cursor over an exit hotspot + if (objId == -1) { + int i = findExit(cx, cy); + if (i != -1 && _vm._hotspots[i].viewx >= 0) { + objId = EXIT_HOTSPOT; + cursorText(_vm._textMouse[kMsExit], cx, cy, U_FONT8, _TBRIGHTWHITE); + } + } + + // Left click over icon, object or to move somewhere + if (gameStatus.leftButtonFl) + processLeftClick(objId, cx, cy); + + // Clear mouse click states + gameStatus.leftButtonFl = false; + gameStatus.rightButtonFl = false; +} + +} // end of namespace Hugo diff --git a/engines/hugo/mouse.h b/engines/hugo/mouse.h new file mode 100755 index 0000000000..79b4bc3ef8 --- /dev/null +++ b/engines/hugo/mouse.h @@ -0,0 +1,53 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on original Hugo Trilogy source code + * + * Copyright (c) 1989-1995 David P. Gray + * + */ + +#ifndef HUGO_MOUSE_H +#define HUGO_MOUSE_H +namespace Hugo { + +class MouseHandler { +public: + MouseHandler(HugoEngine &vm); + + void mouseHandler(); + +private: + HugoEngine &_vm; + + void cursorText(char *buffer, int16 cx, int16 cy, uif_t fontId, int16 color); + int16 findExit(int16 cx, int16 cy); + void processRightClick(int16 objId, int16 cx, int16 cy); + void processLeftClick(int16 objId, int16 cx, int16 cy); +}; + +} // end of namespace Hugo +#endif //HUGO_MOUSE_H diff --git a/engines/hugo/parser.cpp b/engines/hugo/parser.cpp new file mode 100755 index 0000000000..f9800af2cc --- /dev/null +++ b/engines/hugo/parser.cpp @@ -0,0 +1,676 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on original Hugo Trilogy source code + * + * Copyright (c) 1989-1995 David P. Gray + * + */ + +// parser.c - handles all keyboard/command input + +#include "common/system.h" +#include "common/keyboard.h" + +#include "hugo/game.h" +#include "hugo/hugo.h" +#include "hugo/parser.h" +#include "hugo/global.h" +#include "hugo/file.h" +#include "hugo/schedule.h" +#include "hugo/display.h" +#include "hugo/route.h" +#include "hugo/util.h" +#include "hugo/sound.h" + +namespace Hugo { + +#define BLINKS 2 // Cursor blinks per second +#define CX(X) LOWORD(X) +#define CY(Y) HIWORD(Y) + +Parser::Parser(HugoEngine &vm) : + _vm(vm), _putIndex(0), _getIndex(0) { +} + +void Parser::keyHandler(uint16 nChar, uint16 nFlags) { + status_t &gameStatus = _vm.getGameStatus(); + bool repeatedFl = (nFlags & 0x4000); // TRUE if key is a repeat + + debugC(1, kDebugParser, "keyHandler(%d, %d)", nChar, nFlags); + +// Process key down event - called from OnKeyDown() + switch (nChar) { // Set various toggle states + case Common::KEYCODE_ESCAPE: // Escape key, may want to QUIT + if (gameStatus.inventoryState == I_ACTIVE) // Remove inventory, if displayed + gameStatus.inventoryState = I_UP; + gameStatus.inventoryObjId = -1; // Deselect any dragged icon + break; + case Common::KEYCODE_END: + case Common::KEYCODE_HOME: + case Common::KEYCODE_LEFT: + case Common::KEYCODE_RIGHT: + case Common::KEYCODE_UP: + case Common::KEYCODE_DOWN: + if (!repeatedFl) { + gameStatus.routeIndex = -1; // Stop any automatic route + _vm.route().setWalk(nChar); // Direction of hero travel + } + break; + case Common::KEYCODE_F1: // User Help (DOS) + if (repeatedFl) { + _vm.file().instructions(); + nChar = '\0'; + } else + _vm.screen().userHelp(); + break; + case Common::KEYCODE_F2: // Toggle sound + case Common::KEYCODE_F3: // Repeat last line + case Common::KEYCODE_F4: // Save game + case Common::KEYCODE_F5: // Restore game + case Common::KEYCODE_F6: // Inventory + case Common::KEYCODE_F8: // Turbo mode + case Common::KEYCODE_F9: // Boss button + warning("STUB: KeyHandler() - F2-F9 (DOS)"); + break; + default: // Any other key + if (!gameStatus.storyModeFl) { // Keyboard disabled + // Add printable keys to ring buffer + + uint16 bnext = _putIndex + 1; + if (bnext >= sizeof(_ringBuffer)) + bnext = 0; + if (bnext != _getIndex) { + _ringBuffer[_putIndex] = nChar; + _putIndex = bnext; + } + } + break; + } +} + +// Add any new chars to line buffer and display them. +// If CR pressed, pass line to Line_handler() +void Parser::charHandler() { + static int16 lineIndex = 0; // Index into line + static uint32 tick = 0; // For flashing cursor + static char cursor = '_'; + char c; + static command_t cmdLine; // Build command line + status_t &gameStatus = _vm.getGameStatus(); +// Strangerke : Useless ? +// bool updateFl = (_getIndex != _putIndex); // TRUE if any chars processed +// command_t status_line; // Includes prompt, cursor + +//Strangerke : Useless ? +// bool updateFl = (_getIndex != _putIndex); // TRUE if any chars processed + //command_t status_line; // Includes prompt, cursor + + debugC(4, kDebugParser, "charHandler"); + + // Check for one or more characters in ring buffer + while (_getIndex != _putIndex) { + c = _ringBuffer[_getIndex++]; + if (_getIndex >= sizeof(_ringBuffer)) + _getIndex = 0; + + switch (c) { + case Common::KEYCODE_BACKSPACE: // Rubout key + if (lineIndex) + cmdLine[--lineIndex] = '\0'; + break; + case Common::KEYCODE_RETURN: // EOL, pass line to line handler + if (lineIndex && (_vm._hero->pathType != QUIET)) { + // Remove inventory bar if active + if (gameStatus.inventoryState == I_ACTIVE) + gameStatus.inventoryState = I_UP; + // Call Line handler and reset line + command(cmdLine); + cmdLine[lineIndex = 0] = '\0'; + } + break; + default: // Normal text key, add to line + if (lineIndex >= MAX_CHARS) { + //MessageBeep(MB_ICONASTERISK); + warning("STUB: MessageBeep(MB_ICONASTERISK);"); + } else if (isprint(c)) { + cmdLine[lineIndex++] = c; + cmdLine[lineIndex] = '\0'; + } + break; + } + } + + // See if time to blink cursor, set cursor character + if ((tick++ % (TPS / BLINKS)) == 0) { +// Strangerke : Useless ? +// updateFl = true; // Force an update + cursor = cursor == '_' ? ' ' : '_'; + } + + // See if recall button pressed + if (gameStatus.recallFl) { + // Copy previous line to current cmdline + gameStatus.recallFl = false; + strcpy(cmdLine, _line); + lineIndex = strlen(cmdLine); + } + + sprintf(_statusLine, ">%s%c", cmdLine, cursor); + sprintf(_scoreLine, "Score: %d of %d", _vm.getScore(), _vm.getMaxScore()); + + // See if "look" button pressed + if (gameStatus.lookFl) { + command("look around"); + gameStatus.lookFl = false; + } +} + +void Parser::drawStatusText() { + debugC(4, kDebugParser, "drawStatusText"); + + if (_vm.getPlatform() == Common::kPlatformWindows) + _vm.screen().loadFont(U_FONT8); + uint16 sdx = _vm.screen().stringLength(_statusLine); + uint16 sdy = _vm.screen().fontHeight() + 1; // + 1 for shadow + uint16 posX = 0; + uint16 posY = YPIX - sdy; + // Display the string and add rect to display list + _vm.screen().writeStr(posX, posY, _statusLine, _TLIGHTYELLOW); + _vm.screen().displayList(D_ADD, posX, posY, sdx, sdy); + + sdx = _vm.screen().stringLength(_scoreLine); + posY = 0; + _vm.screen().writeStr(posX, posY, _scoreLine, _TCYAN); + _vm.screen().displayList(D_ADD, posX, posY, sdx, sdy); +} + +// Perform an immediate command. Takes parameters a la sprintf +// Assumes final string will not overrun line[] length +void Parser::command(const char *format, ...) { + va_list marker; + + debugC(1, kDebugParser, "Command(%s, ...)", format); + + va_start(marker, format); + vsprintf(_line, format, marker); + va_end(marker); + + lineHandler(); +} + +char *Parser::strlwr(char *buffer) { + char *result = buffer; + + debugC(1, kDebugParser, "strlwr(%s)", buffer); + + while (*buffer != '\0') { + if (isupper(*buffer)) + *buffer = tolower(*buffer); + buffer++; + } + + return result; +} + +// Parse the user's line of text input. Generate events as necessary +void Parser::lineHandler() { + char *noun, *verb; // ptrs to noun and verb strings +// int i; + object_t *obj; + char farComment[XBYTES * 5] = ""; // hold 5 line comment if object not nearby + char contextComment[XBYTES * 5] = ""; // Unused comment for context objects + status_t &gameStatus = _vm.getGameStatus(); + + + debugC(1, kDebugParser, "lineHandler"); + + // Toggle God Mode + if (!strncmp(_line, "PPG", 3)) { + _vm.sound().playSound(!_vm._soundTest, BOTH_CHANNELS, HIGH_PRI); + gameStatus.godModeFl ^= 1; + return; + } + + strlwr(_line); // Convert to lower case + + // God Mode cheat commands: + // goto <screen> Takes hero to named screen + // fetch <object name> Hero carries named object + // fetch all Hero carries all possible objects + // find <object name> Takes hero to screen containing named object + if (DEBUG || gameStatus.godModeFl) { + // Special code to allow me to go straight to any screen + if (strstr(_line, "goto")) + for (int i = 0; i < _vm._numScreens; i++) + if (!strcmp(&_line[strlen("goto") + 1], _vm._screenNames[i])) { + _vm.scheduler().newScreen(i); + return; + } + + // Special code to allow me to get objects from anywhere + if (strstr(_line, "fetch all")) { + for (int i = 0; i < _vm._numObj; i++) + if (_vm._objects[i].genericCmd & TAKE) + takeObject(&_vm._objects[i]); + return; + } + + if (strstr(_line, "fetch")) { + for (int i = 0; i < _vm._numObj; i++) + if (!strcmp(&_line[strlen("fetch") + 1], _vm._arrayNouns[_vm._objects[i].nounIndex][0])) { + takeObject(&_vm._objects[i]); + return; + } + } + + // Special code to allow me to goto objects + if (strstr(_line, "find")) + for (int i = 0; i < _vm._numObj; i++) + if (!strcmp(&_line[strlen("find") + 1], _vm._arrayNouns[_vm._objects[i].nounIndex][0])) { + _vm.scheduler().newScreen(_vm._objects[i].screenIndex); + return; + } + } + + // Special meta commands + // EXIT/QUIT + if (!strcmp("exit", _line) || strstr(_line, "quit")) { + Utils::Box(BOX_ANY, _vm._textParser[kTBExit]); + return; + } + + // SAVE/RESTORE + if (!strcmp("save", _line) && gameStatus.viewState == V_PLAY) { + _vm.file().saveGame(gameStatus.saveSlot, "Current game"); + return; + } + + if (!strcmp("restore", _line) && gameStatus.viewState == V_PLAY || gameStatus.viewState == V_IDLE) { + _vm.file().restoreGame(gameStatus.saveSlot); + _vm.scheduler().restoreScreen(*_vm._screen_p); + gameStatus.viewState = V_PLAY; + return; + } + + // Empty line + if (*_line == '\0') // Empty line + return; + if (strspn(_line, " ") == strlen(_line)) // Nothing but spaces! + return; + + if (gameStatus.gameOverFl) { + // No commands allowed! + Utils::gameOverMsg(); + return; + } + + // Test for nearby objects referenced explicitly + for (int i = 0; i < _vm._numObj; i++) { + obj = &_vm._objects[i]; + if (isWordPresent(_vm._arrayNouns[obj->nounIndex])) + if (isObjectVerb(obj, _line, farComment) || isGenericVerb(obj, _line, farComment)) + return; + } + + // Test for nearby objects that only require a verb + // Note comment is unused if not near. + for (int i = 0; i < _vm._numObj; i++) { + obj = &_vm._objects[i]; + if (obj->verbOnlyFl) + if (isObjectVerb(obj, _line, contextComment) || isGenericVerb(obj, _line, contextComment)) + return; + } + + // No objects match command line, try background and catchall commands + if (isBackgroundWord(_vm._backgroundObjects[*_vm._screen_p], _line)) + return; + if (isCatchallVerb(_vm._backgroundObjects[*_vm._screen_p], _line)) + return; + if (isBackgroundWord(_vm._catchallList, _line)) + return; + if (isCatchallVerb(_vm._catchallList, _line)) + return; + + // If a not-near comment was generated, print it + if (*farComment != '\0') { + Utils::Box(BOX_ANY, farComment); + return; + } + + // Nothing matches. Report recognition success to user. + verb = findVerb(_line); + noun = findNoun(_line); + if (verb == _vm._arrayVerbs[_vm._look][0] && _maze.enabledFl) { + Utils::Box(BOX_ANY, _vm._textParser[kTBMaze]); + showTakeables(); + } else if (verb && noun) // A combination I didn't think of + Utils::Box(BOX_ANY, _vm._textParser[kTBNoPoint]); + else if (noun) + Utils::Box(BOX_ANY, _vm._textParser[kTBNoun]); + else if (verb) + Utils::Box(BOX_ANY, _vm._textParser[kTBVerb]); + else + Utils::Box(BOX_ANY, _vm._textParser[kTBEh]); +} + +// Search for matching verb/noun pairs in background command list +// Print text for possible background object. Return TRUE if match found +bool Parser::isBackgroundWord(objectList_t obj, char *line) { + debugC(1, kDebugParser, "isBackgroundWord(object_list_t obj, %s)", line); + + for (int i = 0; obj[i].verbIndex != 0; i++) + if (isWordPresent(_vm._arrayVerbs[obj[i].verbIndex]) && + isWordPresent(_vm._arrayNouns[obj[i].nounIndex]) && + ((obj[i].roomState == DONT_CARE) || + (obj[i].roomState == _vm._screenStates[*_vm._screen_p]))) { + Utils::Box(BOX_ANY, _vm.file().fetchString(obj[i].commentIndex)); + _vm.scheduler().processBonus(obj[i].bonusIndex); + return true; + } + return false; +} + +// Search for matching verbs in background command list. +// Noun is not required. Return TRUE if match found +// Note that if the background command list has match set TRUE then do not +// print text if there are any recognizable nouns in the command line +bool Parser::isCatchallVerb(objectList_t obj, char *line) { + debugC(1, kDebugParser, "isCatchallVerb(object_list_t obj, %s)", line); + + for (int i = 0; obj[i].verbIndex != 0; i++) + if (isWordPresent(_vm._arrayVerbs[obj[i].verbIndex]) && obj[i].nounIndex == 0 && + (!obj[i].matchFl || !findNoun(line)) && + ((obj[i].roomState == DONT_CARE) || + (obj[i].roomState == _vm._screenStates[*_vm._screen_p]))) { + Utils::Box(BOX_ANY, _vm.file().fetchString(obj[i].commentIndex)); + _vm.scheduler().processBonus(obj[i].bonusIndex); + + // If this is LOOK (without a noun), show any takeable objects + if (*(_vm._arrayVerbs[obj[i].verbIndex]) == _vm._arrayVerbs[_vm._look][0]) + showTakeables(); + + return(true); + } + return false; +} + +// Test whether hero is close to object. Return TRUE or FALSE +// If object not near, return suitable comment; may be another object close +// If radius is -1, treat radius as infinity +// Verb is included to determine correct comment if not near +bool Parser::isNear(object_t *obj, char *verb, char *comment) { + debugC(1, kDebugParser, "isNear(object_t *obj, %s, %s)", verb, comment); + + if (obj->carriedFl) // Object is being carried + return(true); + + if (obj->screenIndex != *_vm._screen_p) { + // Not in same screen + if (obj->objValue) + strcpy(comment, _vm._textParser[kCmtAny1]); + else + strcpy(comment, _vm._textParser[kCmtAny2]); + return(false); + } + + if (obj->cycling == INVISIBLE) + if (obj->seqNumb) { + // There is an image + strcpy(comment, _vm._textParser[kCmtAny3]); + return(false); + } else + // No image, assume visible + if ((obj->radius < 0) || + ((abs(obj->x - _vm._hero->x) <= obj->radius) && + (abs(obj->y - _vm._hero->y - _vm._hero->currImagePtr->y2) <= obj->radius))) + return(true); + else { + // User is not close enough + if (obj->objValue && (verb != _vm._arrayVerbs[_vm._take][0])) + strcpy(comment, _vm._textParser[kCmtAny1]); + else + strcpy(comment, _vm._textParser[kCmtClose]); + return(false); + } + + if ((obj->radius < 0) || + ((abs(obj->x - _vm._hero->x) <= obj->radius) && + (abs(obj->y + obj->currImagePtr->y2 - _vm._hero->y - _vm._hero->currImagePtr->y2) <= obj->radius))) + return(true); + else { + // User is not close enough + if (obj->objValue && (verb != _vm._arrayVerbs[_vm._take][0])) + strcpy(comment, _vm._textParser[kCmtAny1]); + else + strcpy(comment, _vm._textParser[kCmtClose]); + return(false); + } + return true; +} + +// Locate any member of object name list appearing in command line +bool Parser::isWordPresent(char **wordArr) { + debugC(1, kDebugParser, "isWordPresent(%s)", wordArr[0]); + + if (wordArr != NULL) { + for (int i = 0; strlen(wordArr[i]); i++) + if (strstr(_line, wordArr[i])) + return(true); + } + + return false; +} + +// Locate word in list of nouns and return ptr to first string in noun list +char *Parser::findNoun(char *line) { + debugC(1, kDebugParser, "findNoun(%s)", line); + + for (int i = 0; _vm._arrayNouns[i]; i++) + for (int j = 0; strlen(_vm._arrayNouns[i][j]); j++) + if (strstr(line, _vm._arrayNouns[i][j])) + return(_vm._arrayNouns[i][0]); + return NULL; +} + +// Locate word in list of verbs and return ptr to first string in verb list +char *Parser::findVerb(char *line) { + debugC(1, kDebugParser, "findVerb(%s)", line); + + for (int i = 0; _vm._arrayVerbs[i]; i++) + for (int j = 0; strlen(_vm._arrayVerbs[i][j]); j++) + if (strstr(line, _vm._arrayVerbs[i][j])) + return(_vm._arrayVerbs[i][0]); + return NULL; +} + +// Describe any takeable objects visible in this screen +void Parser::showTakeables() { + object_t *obj; + + debugC(1, kDebugParser, "showTakeables"); + + for (int j = 0; j < _vm._numObj; j++) { + obj = &_vm._objects[j]; + if ((obj->cycling != INVISIBLE) && + (obj->screenIndex == *_vm._screen_p) && + (((TAKE & obj->genericCmd) == TAKE) || obj->objValue)) { + sprintf(_textBoxBuffer, "You can also see:\n%s.", _vm._arrayNouns[obj->nounIndex][LOOK_NAME]); + Utils::Box(BOX_ANY, _textBoxBuffer); + } + } +} + +// Do all things necessary to carry an object +void Parser::takeObject(object_t *obj) { + debugC(1, kDebugParser, "takeObject(object_t *obj)"); + + obj->carriedFl = true; + if (obj->seqNumb) { // Don't change if no image to display + obj->cycling = INVISIBLE; + if (_vm.getPlatform() != Common::kPlatformWindows) + warning("takeObject : DOS version should use ALMOST_INVISIBLE"); + } + _vm.adjustScore(obj->objValue); + + if (obj->seqNumb > 0) // If object has an image, force walk to dropped + obj->viewx = -1; // (possibly moved) object next time taken! + Utils::Box(BOX_ANY, TAKE_TEXT, _vm._arrayNouns[obj->nounIndex][TAKE_NAME]); +} + +// Do all necessary things to drop an object +void Parser::dropObject(object_t *obj) { + debugC(1, kDebugParser, "dropObject(object_t *obj)"); + + obj->carriedFl = false; + obj->screenIndex = *_vm._screen_p; + if ((obj->seqNumb > 1) || (obj->seqList[0].imageNbr > 1)) + obj->cycling = CYCLE_FORWARD; + else + obj->cycling = NOT_CYCLING; + obj->x = _vm._hero->x - 1; + obj->y = _vm._hero->y + _vm._hero->currImagePtr->y2 - 1; + obj->y = (obj->y + obj->currImagePtr->y2 < YPIX) ? obj->y : YPIX - obj->currImagePtr->y2 - 10; + _vm.adjustScore(-obj->objValue); + Utils::Box(BOX_ANY, _vm._textParser[kTBOk]); +} + +// Test whether command line contains one of the generic actions +bool Parser::isGenericVerb(object_t *obj, char *line, char *comment) { + debugC(1, kDebugParser, "isGenericVerb(object_t *obj, %s, %s)", line, comment); + + if (!obj->genericCmd) + return false; + + // Following is equivalent to switch, but couldn't do one + if (isWordPresent(_vm._arrayVerbs[_vm._look]) && isNear(obj, _vm._arrayVerbs[_vm._look][0], comment)) { + // Test state-dependent look before general look + if ((obj->genericCmd & LOOK_S) == LOOK_S) { + Utils::Box(BOX_ANY, _vm._textData[obj->stateDataIndex[obj->state]]); + warning("isGenericVerb: use of state dependant look - To be validated"); + } else { + if ((LOOK & obj->genericCmd) == LOOK) + if (_vm._textData[obj->dataIndex]) + Utils::Box(BOX_ANY, _vm._textData[obj->dataIndex]); + else + return(false); + else + Utils::Box(BOX_ANY, _vm._textParser[kTBUnusual]); + } + } else if (isWordPresent(_vm._arrayVerbs[_vm._take]) && isNear(obj, _vm._arrayVerbs[_vm._take][0], comment)) { + if (obj->carriedFl) + Utils::Box(BOX_ANY, _vm._textParser[kTBHave]); + else if ((TAKE & obj->genericCmd) == TAKE) + takeObject(obj); + else if (obj->cmdIndex != 0) // No comment if possible commands + return false; + else if (!obj->verbOnlyFl && (TAKE & obj->genericCmd) == TAKE) // Make sure not taking object in context! + Utils::Box(BOX_ANY, _vm._textParser[kTBNoUse]); + else + return false; + } else if (isWordPresent(_vm._arrayVerbs[_vm._drop])) { + if (!obj->carriedFl && ((DROP & obj->genericCmd) == DROP)) + Utils::Box(BOX_ANY, _vm._textParser[kTBDontHave]); + else if (obj->carriedFl && ((DROP & obj->genericCmd) == DROP)) + dropObject(obj); + else if (obj->cmdIndex == 0) + Utils::Box(BOX_ANY, _vm._textParser[kTBNeed]); + else + return false; + } else // It was not a generic cmd + return false; + + return true; +} + +// Return TRUE if object being carried by hero +bool Parser::isCarrying(uint16 wordIndex) { + debugC(1, kDebugParser, "isCarrying(%d)", wordIndex); + + for (int i = 0; i < _vm._numObj; i++) + if ((wordIndex == _vm._objects[i].nounIndex) && _vm._objects[i].carriedFl) + return true; + return false; +} + +// Test whether command line contains a verb allowed by this object. +// If it does, and the object is near and passes the tests in the command +// list then carry out the actions in the action list and return TRUE +bool Parser::isObjectVerb(object_t *obj, char *line, char *comment) { + int i; + cmd *cmnd; + char *verb; + uint16 *reqs; + uint16 cmdIndex; + + debugC(1, kDebugParser, "isObjectVerb(object_t *obj, %s, %s)", line, comment); + + // First, find matching verb in cmd list + cmdIndex = obj->cmdIndex; // ptr to list of commands + if (cmdIndex == 0) // No commands for this obj + return false; + + for (i = 0; _vm._cmdList[cmdIndex][i].verbIndex != 0; i++) // For each cmd + if (isWordPresent(_vm._arrayVerbs[_vm._cmdList[cmdIndex][i].verbIndex])) // Was this verb used? + break; + if (_vm._cmdList[cmdIndex][i].verbIndex == 0) // No verbs used. + return false; + + // Verb match found. Check if object is Near + verb = *_vm._arrayVerbs[_vm._cmdList[cmdIndex][i].verbIndex]; + if (!isNear(obj, verb, comment)) + return(false); + + // Check all required objects are being carried + cmnd = &_vm._cmdList[cmdIndex][i]; // ptr to struct cmd + if (cmnd->reqIndex) { // At least 1 thing in list + reqs = _vm._arrayReqs[cmnd->reqIndex]; // ptr to list of required objects + for (i = 0; reqs[i]; i++) // for each obj + if (!isCarrying(reqs[i])) { + Utils::Box(BOX_ANY, _vm._textData[cmnd->textDataNoCarryIndex]); + return true; + } + } + + // Required objects are present, now check state is correct + if ((obj->state != cmnd->reqState) && (cmnd->reqState != DONT_CARE)) { + Utils::Box(BOX_ANY, _vm._textData[cmnd->textDataWrongIndex]); + return true; + } + + // Everything checked. Change the state and carry out any actions + if (cmnd->reqState != DONT_CARE) // Don't change new state if required state didn't care + obj->state = cmnd->newState; + Utils::Box(BOX_ANY, _vm._textData[cmnd->textDataDoneIndex]); + _vm.scheduler().insertActionList(cmnd->actIndex); + + // See if any additional generic actions + if ((verb == _vm._arrayVerbs[_vm._look][0]) || (verb == _vm._arrayVerbs[_vm._take][0]) || (verb == _vm._arrayVerbs[_vm._drop][0])) + isGenericVerb(obj, line, comment); + return true; +} + +} // end of namespace Hugo diff --git a/engines/hugo/parser.h b/engines/hugo/parser.h new file mode 100755 index 0000000000..0bfbe32715 --- /dev/null +++ b/engines/hugo/parser.h @@ -0,0 +1,95 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on original Hugo Trilogy source code + * + * Copyright (c) 1989-1995 David P. Gray + * + */ + +#ifndef HUGO_PARSER_H +#define HUGO_PARSER_H +namespace Hugo { + +enum seqTextParser { + kTBExit = 0, + kTBMaze = 1, + kTBNoPoint = 2, + kTBNoun = 3, + kTBVerb = 4, + kTBEh = 5, + kTBUnusual = 6, + kTBHave = 7, + kTBNoUse = 8, + kTBDontHave = 9, + kTBNeed = 10, + kTBOk = 11, + kCmtAny1 = 12, + kCmtAny2 = 13, + kCmtAny3 = 14, + kCmtClose = 15 +}; + +class Parser { +public: + Parser(HugoEngine &vm); + + bool isWordPresent(char **wordArr); + + void charHandler(); + void command(const char *format, ...); + void drawStatusText(); + void keyHandler(uint16 nChar, uint16 nFlags); + void lineHandler(); + +private: + HugoEngine &_vm; + + char _ringBuffer[32]; // Ring buffer + uint16 _putIndex; + uint16 _getIndex; // Index into ring buffer + + command_t _statusLine; + command_t _scoreLine; + + bool isBackgroundWord(objectList_t obj, char *line); + bool isCarrying(uint16 wordIndex); + bool isCatchallVerb(objectList_t obj, char *line); + bool isGenericVerb(object_t *obj, char *line, char *comment); + bool isNear(object_t *obj, char *verb, char *comment); + bool isObjectVerb(object_t *obj, char *line, char *comment); + + char *findNoun(char *line); + char *findVerb(char *line); + char *strlwr(char *buffer); + + void dropObject(object_t *obj); + void showTakeables(); + void takeObject(object_t *obj); +}; + +} // end of namespace Hugo +#endif //HUGO_PARSER_H diff --git a/engines/hugo/route.cpp b/engines/hugo/route.cpp new file mode 100755 index 0000000000..88208e0034 --- /dev/null +++ b/engines/hugo/route.cpp @@ -0,0 +1,509 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on original Hugo Trilogy source code + * + * Copyright (c) 1989-1995 David P. Gray + * + */ + +// Find shortest route from hero to destination + +#include "common/system.h" + +#include "hugo/hugo.h" +#include "hugo/game.h" +#include "hugo/route.h" +#include "hugo/global.h" + +namespace Hugo { +Route::Route(HugoEngine &vm) : _vm(vm) { +} + +// Face hero in new direction, based on cursor key input by user. +void Route::setDirection(uint16 keyCode) { + object_t *obj = _vm._hero; // Pointer to hero object + + debugC(1, kDebugRoute, "setDirection(%d)", keyCode); + + // Set first image in sequence + switch (keyCode) { + case Common::KEYCODE_UP: + obj->currImagePtr = obj->seqList[_UP].seqPtr; + break; + case Common::KEYCODE_DOWN: + obj->currImagePtr = obj->seqList[DOWN].seqPtr; + break; + case Common::KEYCODE_LEFT: + obj->currImagePtr = obj->seqList[LEFT].seqPtr; + break; + case Common::KEYCODE_RIGHT: + obj->currImagePtr = obj->seqList[RIGHT].seqPtr; + break; + case Common::KEYCODE_HOME: + obj->currImagePtr = obj->seqList[LEFT].seqPtr; + break; + case Common::KEYCODE_END: + obj->currImagePtr = obj->seqList[LEFT].seqPtr; + break; +// case Common::KEYCODE_PRIOR: +// obj->currImagePtr = obj->seqList[RIGHT].seqPtr; +// break; +// case Common::KEYCODE_NEXT: +// obj->currImagePtr = obj->seqList[RIGHT].seqPtr; +// break; + } +} + +// Set hero walking, based on cursor key input by user. +// Hitting same key twice will stop hero. +void Route::setWalk(uint16 direction) { + object_t *obj = _vm._hero; // Pointer to hero object + static uint16 oldDirection = 0; // Last direction char + + debugC(1, kDebugRoute, "setWalk(%d)", direction); + + if (_vm.getGameStatus().storyModeFl || obj->pathType != USER) // Make sure user has control + return; + + if (!obj->vx && !obj->vy) + oldDirection = 0; // Fix for consistant restarts + + if (direction != oldDirection) { + // Direction has changed + setDirection(direction); // Face new direction + obj->vx = obj->vy = 0; + switch (direction) { // And set correct velocity + case Common::KEYCODE_UP: + obj->vy = -DY; + break; + case Common::KEYCODE_DOWN: + obj->vy = DY; + break; + case Common::KEYCODE_LEFT: + obj->vx = -DX; + break; + case Common::KEYCODE_RIGHT: + obj->vx = DX; + break; + case Common::KEYCODE_HOME: + obj->vx = -DX; + obj->vy = -DY / 2; + break; + case Common::KEYCODE_END: + obj->vx = -DX; + obj->vy = DY / 2; + break; +// case Common::KEYCODE_PRIOR: +// obj->vx = DX; +// obj->vy = -DY / 2; +// break; +// case Common::KEYCODE_NEXT: +// obj->vx = DX; +// obj->vy = DY / 2; +// break; + } + oldDirection = direction; + obj->cycling = CYCLE_FORWARD; + } else { + // Same key twice - halt hero + obj->vy = 0; + obj->vx = 0; + oldDirection = 0; + obj->cycling = NOT_CYCLING; + } +} + +// Recursive algorithm! Searches from hero to dest_x, dest_y +// Find horizontal line segment about supplied point and recursively +// find line segments for each point above and below that segment. +// When destination point found in segment, start surfacing and leave +// a trail in segment[] from destination back to hero. +// +// Note: there is a bug which allows a route through a 1-pixel high +// narrow gap if between 2 segments wide enough for hero. To work +// around this, make sure any narrow gaps are 2 or more pixels high. +// An example of this was the blocking guard in Hugo1/Dead-End. +void Route::segment(int16 x, int16 y) { + int16 x1, x2; // Range of segment +// Note use of static - can't waste stack + static image_pt p; // Ptr to _boundaryMap[y] + static segment_t *seg_p; // Ptr to segment + + debugC(1, kDebugRoute, "segment(%d, %d)", x, y); + + // Bomb out if stack exhausted + // Vinterstum: Is this just a safeguard, or actually used? + //_fullStackFl = _stackavail () < 256; + _fullStackFl = false; + + // Find and fill on either side of point + p = _boundaryMap[y]; + for (x1 = x; x1 > 0; x1--) + if (p[x1] == 0) { +#if DEBUG_ROUTE + SetPixel(hDC, (int16)((long)config.cx * x1 / XPIX), (int16)((long)config.cy *(y - DIBOFF_Y) / VIEW_DY), GetPalIndex(_TLIGHTMAGENTA)); +#endif + p[x1] = kMapFill; + } else + break; + for (x2 = x + 1; x2 < XPIX; x2++) + if (p[x2] == 0) { +#if DEBUG_ROUTE + SetPixel(hDC, (int16)((long)config.cx * x2 / XPIX), (int16)((long)config.cy *(y - DIBOFF_Y) / VIEW_DY), GetPalIndex(_TLIGHTGREEN)); +#endif + p[x2] = kMapFill; + } else + break; + x1++; + x2--; + + // Discard path if not wide enough for hero - dead end + if (_heroWidth > x2 - x1 + 1) + return; + + // Have we found the destination yet? + if (y == _destY && x1 <= _destX && x2 >= _destX) + _routeFoundFl = true; + + // Bounds check y in case no boundary around screen + if (y <= 0 || y >= YPIX - 1) + return; +#if FALSE + // Find all segments above and below current + if (hero_p->x < x1 || hero_p->x + HERO_MAX_WIDTH > x2) { + // Hero x not in segment, search x1..x2 + // Find all segments above current + for (x = x1; !(_routeFoundFl | _fullStackFl | _fullSegmentFl) && x <= x2; x++) + if (_boundaryMap[y - 1][x] == 0) + segment(x, y - 1); + + // Find all segments below current + for (x = x1; !(_routeFoundFl | _fullStackFl | _fullSegmentFl) && x <= x2; x++) + if (_boundaryMap[y + 1][x] == 0) + segment(x, y + 1); + } +#endif + if (_vm._hero->x < x1) { + // Hero x not in segment, search x1..x2 + // Find all segments above current + for (x = x1; !(_routeFoundFl | _fullStackFl | _fullSegmentFl) && x <= x2; x++) + if (_boundaryMap[y - 1][x] == 0) + segment(x, y - 1); + + // Find all segments below current + for (x = x1; !(_routeFoundFl | _fullStackFl | _fullSegmentFl) && x <= x2; x++) + if (_boundaryMap[y + 1][x] == 0) + segment(x, y + 1); + } else if (_vm._hero->x + HERO_MAX_WIDTH > x2) { + // Hero x not in segment, search x1..x2 + // Find all segments above current + for (x = x2; !(_routeFoundFl | _fullStackFl | _fullSegmentFl) && x >= x1; x--) + if (_boundaryMap[y - 1][x] == 0) + segment(x, y - 1); + + // Find all segments below current + for (x = x2; !(_routeFoundFl | _fullStackFl | _fullSegmentFl) && x >= x1; x--) + if (_boundaryMap[y + 1][x] == 0) + segment(x, y + 1); + } else { + // Organize search around hero x position - this gives + // better chance for more direct route. + for (x = _vm._hero->x; !(_routeFoundFl | _fullStackFl | _fullSegmentFl) && x <= x2; x++) + if (_boundaryMap[y - 1][x] == 0) + segment(x, y - 1); + for (x = x1; !(_routeFoundFl | _fullStackFl | _fullSegmentFl) && x < _vm._hero->x; x++) + if (_boundaryMap[y - 1][x] == 0) + segment(x, y - 1); + for (x = _vm._hero->x; !(_routeFoundFl | _fullStackFl | _fullSegmentFl) && x <= x2; x++) + if (_boundaryMap[y + 1][x] == 0) + segment(x, y + 1); + for (x = x1; !(_routeFoundFl | _fullStackFl | _fullSegmentFl) && x < _vm._hero->x; x++) + if (_boundaryMap[y + 1][x] == 0) + segment(x, y + 1); + } + + // If found, surface, leaving trail back to hero + if (_routeFoundFl) { + // Bomb out if too many segments (leave one spare) + if (_segmentNumb >= kMaxSeg - 1) + _fullSegmentFl = true; + else { + // Create segment + seg_p = &_segment[_segmentNumb]; + seg_p->y = y; + seg_p->x1 = x1; + seg_p->x2 = x2; + _segmentNumb++; + } + } +} + +// Create and return ptr to new node. Initialize with previous node. +// Returns NULL if MAX_NODES exceeded +Point *Route::newNode() { + debugC(1, kDebugRoute, "newNode"); + + if (_routeListIndex >= kMaxNodes) // Too many nodes + return(NULL); // Incomplete route - failure + _routeListIndex++; + _route[_routeListIndex] = _route[_routeListIndex - 1]; // Initialize with previous node + return(&_route[_routeListIndex]); +} + +// Construct route to cx, cy. Return TRUE if successful. +// 1. Copy boundary bitmap to local byte map (include object bases) +// 2. Construct list of segments segment[] from hero to destination +// 3. Compress to shortest route in route[] +bool Route::findRoute(int16 cx, int16 cy) { + int16 i, j, x, y; // Loop on coordinates + int16 x1, x2, dx; // Overlap between segments + int16 herox1, herox2, heroy; // Current hero baseline + object_t *obj; // Ptr to object + segment_t *seg_p; // Ptr to segment + Point *routeNode; // Ptr to route node + + debugC(1, kDebugRoute, "findRoute(%d, %d)", cx, cy); + + // Initialize for search + _routeFoundFl = false; // Path not found yet + _fullStackFl = false; // Stack not exhausted + _fullSegmentFl = false; // Segments not exhausted + _segmentNumb = 0; // Segment index + _heroWidth = HERO_MIN_WIDTH; // Minimum width of hero + _destY = cy; // Destination coords + _destX = cx; // Destination coords + herox1 = _vm._hero->x + _vm._hero->currImagePtr->x1; // Hero baseline + herox2 = _vm._hero->x + _vm._hero->currImagePtr->x2; // Hero baseline + heroy = _vm._hero->y + _vm._hero->currImagePtr->y2; // Hero baseline + + // Store all object baselines into objbound (except hero's = [0]) + for (i = 1, obj = &_vm._objects[i]; i < _vm._numObj; i++, obj++) + if ((obj->screenIndex == *_vm._screen_p) && (obj->cycling != INVISIBLE) && (obj->priority == FLOATING)) + _vm.storeBoundary(obj->oldx + obj->currImagePtr->x1, obj->oldx + obj->currImagePtr->x2, obj->oldy + obj->currImagePtr->y2); + + // Combine objbound and boundary bitmaps to local byte map + for (y = 0; y < YPIX; y++) + for (x = 0; x < XBYTES; x++) + for (i = 0; i < 8; i++) + _boundaryMap[y][x * 8 + i] = ((_vm.getObjectBoundaryOverlay()[y * XBYTES + x] | _vm.getBoundaryOverlay()[y * XBYTES + x]) & (0x80 >> i)) ? kMapBound : 0; + + // Clear all object baselines from objbound + for (i = 0, obj = _vm._objects; i < _vm._numObj; i++, obj++) + if ((obj->screenIndex == *_vm._screen_p) && (obj->cycling != INVISIBLE) && (obj->priority == FLOATING)) + _vm.clearBoundary(obj->oldx + obj->currImagePtr->x1, obj->oldx + obj->currImagePtr->x2, obj->oldy + obj->currImagePtr->y2); + +#if DEBUG_ROUTE + { +// hDC = GetDC(hview); + for (y = 0; y < YPIX; y++) + for (x = 0; x < XPIX; x++) + if (_boundaryMap[y][x]) + SetPixel(hDC, (int16)((long)config.cx * x / XPIX), (int16)((long)config.cy *(y - DIBOFF_Y) / VIEW_DY), GetPalIndex(_TBRIGHTWHITE)); + } +#endif + + // Search from hero to destination + segment(herox1, heroy); + +//#if DEBUG_ROUTE +// ReleaseDC(hview, hDC); +//#endif + + // Not found or not enough stack or MAX_SEG exceeded + if (!_routeFoundFl || _fullStackFl || _fullSegmentFl) { +#if DEBUG_ROUTE + Box(BOX_ANY, "%s", (_fullStackFl) ? "Stack blown!" : (_fullSegmentFl) ? "Ran out of segments!" : "No Route!"); +#endif + return(false); + } + + // Now find the route of nodes from destination back to hero + // Assign first node as destination + _route[0].x = _destX; + _route[0].y = _destY; + + // Make a final segment for hero's base (we left a spare) + _segment[_segmentNumb].y = heroy; + _segment[_segmentNumb].x1 = herox1; + _segment[_segmentNumb].x2 = herox2; + _segmentNumb++; + + // Look in segments[] for straight lines from destination to hero + for (i = 0, _routeListIndex = 0; i < _segmentNumb - 1; i++) { + if ((routeNode = newNode()) == NULL) // New node for new segment + return(false); // Too many nodes + routeNode->y = _segment[i].y; + + // Look ahead for furthest straight line + for (j = i + 1; j < _segmentNumb; j++) { + seg_p = &_segment[j]; + // Can we get to this segment from previous node? + if (seg_p->x1 <= routeNode->x && seg_p->x2 >= routeNode->x + _heroWidth - 1) + routeNode->y = seg_p->y; // Yes, keep updating node + else { + // No, create another node on previous segment to reach it + if ((routeNode = newNode()) == NULL) // Add new route node + return (false); // Too many nodes + + // Find overlap between old and new segments + x1 = MAX(_segment[j - 1].x1, seg_p->x1); + x2 = MIN(_segment[j - 1].x2, seg_p->x2); + + // If room, add a little offset to reduce staircase effect + dx = HERO_MAX_WIDTH >> 1; + if (x2 - x1 < _heroWidth + dx) + dx = 0; + + // Bear toward final hero position + if (j == _segmentNumb - 1) + routeNode->x = herox1; + else if (herox1 < x1) + routeNode->x = x1 + dx; + else if (herox1 > x2 - _heroWidth + 1) + routeNode->x = x2 - _heroWidth - dx; + else + routeNode->x = herox1; + i = j - 2; // Restart segment (-1 to offset auto increment) + break; + } + } + + // Terminate loop if we've reached hero + if (routeNode->x == herox1 && routeNode->y == heroy) + break; + } + return true; +} + +// Process hero in route mode - called from Move_objects() +void Route::processRoute() { + int16 herox, heroy; // Hero position + Point *routeNode; // Ptr to current route node + static bool turnedFl = false; // Used to get extra cylce for turning + + status_t &gameStatus = _vm.getGameStatus(); + + debugC(1, kDebugRoute, "processRoute"); + + // Current hero position + herox = _vm._hero->x + _vm._hero->currImagePtr->x1; + heroy = _vm._hero->y + _vm._hero->currImagePtr->y2; + routeNode = &_route[gameStatus.routeIndex]; + + // Arrived at node? + if (abs(herox - routeNode->x) < DX + 1 && abs(heroy - routeNode->y) < DY) { + // DX too low + // Close enough - position hero exactly + _vm._hero->x = _vm._hero->oldx = routeNode->x - _vm._hero->currImagePtr->x1; + _vm._hero->y = _vm._hero->oldy = routeNode->y - _vm._hero->currImagePtr->y2; + _vm._hero->vx = _vm._hero->vy = 0; + _vm._hero->cycling = NOT_CYCLING; + + // Arrived at final node? + if (--gameStatus.routeIndex < 0) { + // See why we walked here + switch (gameStatus.go_for) { + case GO_EXIT: // Walked to an exit, proceed into it + setWalk(_vm._hotspots[gameStatus.go_id].direction); + break; + case GO_LOOK: // Look at an object + if (turnedFl) { + _vm.lookObject(&_vm._objects[gameStatus.go_id]); + turnedFl = false; + } else { + setDirection(_vm._objects[gameStatus.go_id].direction); + gameStatus.routeIndex++; // Come round again + turnedFl = true; + } + break; + case GO_GET: // Get (or use) an object + if (turnedFl) { + _vm.useObject(gameStatus.go_id); + turnedFl = false; + } else { + setDirection(_vm._objects[gameStatus.go_id].direction); + gameStatus.routeIndex++; // Come round again + turnedFl = true; + } + break; + case GO_SPACE: + warning("Unhandled gameStatus.go_for GO_STATUS"); + break; + } + } + } else if (_vm._hero->vx == 0 && _vm._hero->vy == 0) { + // Set direction of travel if at a node + // Note realignment when changing to (thinner) up/down sprite, + // otherwise hero could bump into boundaries along route. + if (herox < routeNode->x) + setWalk(Common::KEYCODE_RIGHT); + else if (herox > routeNode->x) + setWalk(Common::KEYCODE_LEFT); + else if (heroy < routeNode->y) { + setWalk(Common::KEYCODE_DOWN); + _vm._hero->x = _vm._hero->oldx = routeNode->x - _vm._hero->currImagePtr->x1; + } else if (heroy > routeNode->y) { + setWalk(Common::KEYCODE_UP); + _vm._hero->x = _vm._hero->oldx = routeNode->x - _vm._hero->currImagePtr->x1; + } + } +} + +// Start a new route from hero to cx, cy +// go_for is the purpose, id indexes the exit or object to walk to +// Returns FALSE if route not found +bool Route::startRoute(go_t go_for, int16 id, int16 cx, int16 cy) { + bool foundFl = false; // TRUE if route found ok + + status_t &gameStatus = _vm.getGameStatus(); + + debugC(1, kDebugRoute, "startRoute(%d, %d, %d, %d)", go_for, id, cx, cy); + + // Don't attempt to walk if user does not have control + if (_vm._hero->pathType != USER) + return false; + + // if inventory showing, make it go away + if (gameStatus.inventoryState != I_OFF) + gameStatus.inventoryState = I_UP; + + gameStatus.go_for = go_for; // Purpose of trip + gameStatus.go_id = id; // Index of exit/object + + // Adjust destination to center hero if walking to cursor + if (gameStatus.go_for == GO_SPACE) + cx -= HERO_MIN_WIDTH / 2; + + if ((foundFl = findRoute(cx, cy))) { // Found a route? + gameStatus.routeIndex = _routeListIndex; // Node index + _vm._hero->vx = _vm._hero->vy = 0; // Stop manual motion + } + + return foundFl; +} + +} // end of namespace Hugo diff --git a/engines/hugo/route.h b/engines/hugo/route.h new file mode 100755 index 0000000000..b21aadbbbd --- /dev/null +++ b/engines/hugo/route.h @@ -0,0 +1,84 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on original Hugo Trilogy source code + * + * Copyright (c) 1989-1995 David P. Gray + * + */ + +#ifndef HUGO_ROUTE_H +#define HUGO_ROUTE_H + +namespace Hugo { + +#define kMapBound 1 // Mark a boundary outline +#define kMapFill 2 // Mark a boundary filled +#define kMaxSeg 256 // Maximum number of segments +#define kMaxNodes 256 // Maximum nodes in route +#define DEBUG_ROUTE FALSE + +struct Point { + int x; + int y; +}; + +struct segment_t { // Search segment + int16 y; // y position + int16 x1, x2; // Range of segment +}; + +class Route { +public: + Route(HugoEngine &vm); + + void processRoute(); + bool startRoute(go_t go_for, short id, short cx, short cy); + void setDirection(uint16 keyCode); + void setWalk(uint16 direction); + +private: + HugoEngine &_vm; + + byte _boundaryMap[YPIX][XPIX]; // Boundary byte map + segment_t _segment[kMaxSeg]; // List of points in fill-path + Point _route[kMaxNodes]; // List of nodes in route (global) + int16 _segmentNumb; // Count number of segments + int16 _routeListIndex; // Index into route list + int16 _destX; + int16 _destY; + int16 _heroWidth; // Hero width + bool _routeFoundFl; // TRUE when path found + bool _fullStackFl; // TRUE if stack exhausted + bool _fullSegmentFl; // Segments exhausted + + void segment(int16 x, int16 y); + bool findRoute(int16 cx, int16 cy); + Point *newNode(); +}; + +} // end of namespace Hugo +#endif //HUGO_ROUTE_H diff --git a/engines/hugo/schedule.cpp b/engines/hugo/schedule.cpp new file mode 100755 index 0000000000..b14fef10f3 --- /dev/null +++ b/engines/hugo/schedule.cpp @@ -0,0 +1,677 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on original Hugo Trilogy source code + * + * Copyright (c) 1989-1995 David P. Gray + * + */ + +// This module contains all the scheduling and timing stuff + +#include "common/system.h" +#include "common/stream.h" + +#include "hugo/game.h" +#include "hugo/hugo.h" +#include "hugo/schedule.h" +#include "hugo/global.h" +#include "hugo/file.h" +#include "hugo/display.h" +#include "hugo/parser.h" +#include "hugo/util.h" +#include "hugo/sound.h" + +namespace Hugo { + +#define SIGN(X) (X < 0 ? -1 : 1) + +Scheduler::Scheduler(HugoEngine &vm) : _vm(vm) { +} + +// Initialise the timer event queue +void Scheduler::initEventQueue() { + debugC(1, kDebugSchedule, "initEventQueue"); + + // Chain next_p from first to last + for (int i = kMaxEvents; --i;) + _events[i - 1].nextEvent = &_events[i]; + _events[kMaxEvents - 1].nextEvent = 0; + + // Chain prev_p from last to first + for (int i = 1; i < kMaxEvents; i++) + _events[i].prevEvent = &_events[i - 1]; + _events[0].prevEvent = 0; + + _headEvent = _tailEvent = 0; // Event list is empty + _freeEvent = _events; // Free list is full +} + +// Return a ptr to an event structure from the free list +event_t *Scheduler::getQueue() { + debugC(4, kDebugSchedule, "getQueue"); + event_t *resEvent; + + if (!_freeEvent) // Error: no more events available + Utils::Error(EVNT_ERR, "getQueue"); + resEvent = _freeEvent; + _freeEvent = _freeEvent->nextEvent; + resEvent->nextEvent = 0; + return resEvent; +} + +// Delete an event structure (i.e. return it to the free list) +// Historical note: Originally event p was assumed to be at head of queue +// (i.e. earliest) since all events were deleted in order when proceeding to +// a new screen. To delete an event from the middle of the queue, the action +// was overwritten to be ANULL. With the advent of GLOBAL events, Del_queue +// was modified to allow deletes anywhere in the list, and the DEL_EVENT +// action was modified to perform the actual delete. +void Scheduler::delQueue(event_t *curEvent) { + debugC(4, kDebugSchedule, "delQueue"); + if (curEvent == _headEvent) // If p was the head ptr + _headEvent = curEvent->nextEvent; // then make new head_p + else { // Unlink p + curEvent->prevEvent->nextEvent = curEvent->nextEvent; + if (curEvent->nextEvent) + curEvent->nextEvent->prevEvent = curEvent->prevEvent; + else + _tailEvent = curEvent->prevEvent; + } + + if (_headEvent) + _headEvent->prevEvent = 0; // Mark end of list + else + _tailEvent = 0; // Empty queue + + curEvent->nextEvent = _freeEvent; // Return p to free list + if (_freeEvent) // Special case, if free list was empty + _freeEvent->prevEvent = curEvent; + _freeEvent = curEvent; +} + +// Insert the action pointed to by p into the timer event queue +// The queue goes from head (earliest) to tail (latest) timewise +void Scheduler::insertAction(act *action) { + debugC(1, kDebugSchedule, "insertAction - Action type A%d", action->a0.actType); + + // First, get and initialise the event structure + event_t *curEvent = getQueue(); + curEvent->action = action; + switch (action->a0.actType) { // Assign whether local or global + case AGSCHEDULE: + curEvent->localActionFl = false; // Lasts over a new screen + break; + default: + curEvent->localActionFl = true; // Rest are for current screen only + break; + } + + curEvent->time = action->a0.timer + getTicks(); // Convert rel to abs time + + // Now find the place to insert the event + if (!_tailEvent) { // Empty queue + _tailEvent = _headEvent = curEvent; + curEvent->nextEvent = curEvent->prevEvent = NULL; + } else { + event_t *wrkEvent = _tailEvent; // Search from latest time back + bool found = false; + + while (wrkEvent && !found) { + if (wrkEvent->time <= curEvent->time) { // Found if new event later + found = true; + if (wrkEvent == _tailEvent) // New latest in list + _tailEvent = curEvent; + else + wrkEvent->nextEvent->prevEvent = curEvent; + curEvent->nextEvent = wrkEvent->nextEvent; + wrkEvent->nextEvent = curEvent; + curEvent->prevEvent = wrkEvent; + } + wrkEvent = wrkEvent->prevEvent; + } + + if (!found) { // Must be earliest in list + _headEvent->prevEvent = curEvent; // So insert as new head + curEvent->nextEvent = _headEvent; + curEvent->prevEvent = NULL; + _headEvent = curEvent; + } + } +} + +void Scheduler::insertActionList(uint16 actIndex) { +// Call Insert_action for each action in the list supplied + debugC(1, kDebugSchedule, "insertActionList(%d)", actIndex); + + if (_vm._actListArr[actIndex]) + for (int i = 0; _vm._actListArr[actIndex][i].a0.actType != ANULL; i++) + insertAction(&_vm._actListArr[actIndex][i]); +} + +void Scheduler::decodeString(char *line) { +// Decode a string + debugC(1, kDebugSchedule, "decodeString(%s)", line); + + static char cypher[] = "Copyright 1992, Gray Design Associates"; + + for (uint16 i = 0; i < strlen(line); i++) + line[i] -= cypher[i % strlen(cypher)]; + debugC(1, kDebugSchedule, "result : %s", line); +} + +event_t *Scheduler::doAction(event_t *curEvent) { +// This function performs the action in the event structure pointed to by p +// It dequeues the event and returns it to the free list. It returns a ptr +// to the next action in the list, except special case of NEW_SCREEN + event_t *wrkEvent; // Save ev_p->next_p for return + event_t *saveEvent; // Used in DEL_EVENTS + char *response; // User's response string + object_t *obj1; + object_t *obj2; + int dx, dy; + act *action; // Ptr to action structure + + status_t &gameStatus = _vm.getGameStatus(); + + action = curEvent->action; + debugC(1, kDebugSchedule, "doAction - Event action type : %d", action->a0.actType); + + switch (action->a0.actType) { + case ANULL: // Big NOP from DEL_EVENTS + break; + case ASCHEDULE: // act0: Schedule an action list + insertActionList(action->a0.actIndex); + break; + case START_OBJ: // act1: Start an object cycling + _vm._objects[action->a1.objNumb].cycleNumb = action->a1.cycleNumb; + _vm._objects[action->a1.objNumb].cycling = action->a1.cycle; + break; + case INIT_OBJXY: // act2: Initialise an object + _vm._objects[action->a2.objNumb].x = action->a2.x; // Coordinates + _vm._objects[action->a2.objNumb].y = action->a2.y; + break; + case PROMPT: // act3: Prompt user for key phrase +// TODO : Add specific code for Hugo 1 DOS, which is handled differently, + response = Utils::Box(BOX_PROMPT, _vm.file().fetchString(action->a3.promptIndex)); + + warning("STUB: doAction(act3), expecting answer %s", response); + +// TODO : The answer of the player is not handled currently! Once it'll be read in the messageBox, uncomment this block +#if 0 + bool found; + char *tmpStr; // General purpose string ptr + + for (found = false, dx = 0; !found && (action->a3.responsePtr[dx] != -1); dx++) { + tmpStr = _vm.file().Fetch_string(action->a3.responsePtr[dx]); + if (strstr(_vm.parser().strlwr(response) , tmpStr)) + found = true; + } + + if (found) + insertActionList(action->a3.actPassIndex); + else + insertActionList(action->a3.actFailIndex); +#endif + +//HACK: As the answer is not read, currently it's always considered correct + insertActionList(action->a3.actPassIndex); + break; + case BKGD_COLOR: // act4: Set new background color + HugoEngine::get().screen().setBackgroundColor(action->a4.newBackgroundColor); + break; + case INIT_OBJVXY: // act5: Initialise an object + _vm._objects[action->a5.objNumb].vx = action->a5.vx; // velocities + _vm._objects[action->a5.objNumb].vy = action->a5.vy; + break; + case INIT_CARRY: // act6: Initialise an object + _vm._objects[action->a6.objNumb].carriedFl = action->a6.carriedFl; // carried status + break; + case INIT_HF_COORD: // act7: Initialise an object to hero's "feet" coords + _vm._objects[action->a7.objNumb].x = _vm._hero->x - 1; + _vm._objects[action->a7.objNumb].y = _vm._hero->y + _vm._hero->currImagePtr->y2 - 1; + _vm._objects[action->a7.objNumb].screenIndex = *_vm._screen_p; // Don't forget screen! + break; + case NEW_SCREEN: // act8: Start new screen + newScreen(action->a8.screenIndex); + break; + case INIT_OBJSTATE: // act9: Initialise an object state + _vm._objects[action->a9.objNumb].state = action->a9.newState; + break; + case INIT_PATH: // act10: Initialise an object path and velocity + _vm._objects[action->a10.objNumb].pathType = (path_t) action->a10.newPathType; + _vm._objects[action->a10.objNumb].vxPath = action->a10.vxPath; + _vm._objects[action->a10.objNumb].vyPath = action->a10.vyPath; + break; + case COND_R: // act11: action lists conditional on object state + if (_vm._objects[action->a11.objNumb].state == action->a11.stateReq) + insertActionList(action->a11.actPassIndex); + else + insertActionList(action->a11.actFailIndex); + break; + case TEXT: // act12: Text box (CF WARN) + Utils::Box(BOX_ANY, _vm.file().fetchString(action->a12.stringIndex)); // Fetch string from file + break; + case SWAP_IMAGES: // act13: Swap 2 object images + swapImages(action->a13.obj1, action->a13.obj2); + break; + case COND_SCR: // act14: Conditional on current screen + if (_vm._objects[action->a14.objNumb].screenIndex == action->a14.screenReq) + insertActionList(action->a14.actPassIndex); + else + insertActionList(action->a14.actFailIndex); + break; + case AUTOPILOT: // act15: Home in on a (stationary) object + // object p1 will home in on object p2 + obj1 = &_vm._objects[action->a15.obj1]; + obj2 = &_vm._objects[action->a15.obj2]; + obj1->pathType = AUTO; + dx = obj1->x + obj1->currImagePtr->x1 - obj2->x - obj2->currImagePtr->x1; + dy = obj1->y + obj1->currImagePtr->y1 - obj2->y - obj2->currImagePtr->y1; + + if (dx == 0) // Don't EVER divide by zero! + dx = 1; + if (dy == 0) + dy = 1; + + if (abs(dx) > abs(dy)) { + obj1->vx = action->a15.dx * -SIGN(dx); + obj1->vy = abs((action->a15.dy * dy) / dx) * -SIGN(dy); + } else { + obj1->vy = action->a15.dy * -SIGN(dy); + obj1->vx = abs((action->a15.dx * dx) / dy) * -SIGN(dx); + } + break; + case INIT_OBJ_SEQ: // act16: Set sequence number to use + // Note: Don't set a sequence at time 0 of a new screen, it causes + // problems clearing the boundary bits of the object! t>0 is safe + _vm._objects[action->a16.objNumb].currImagePtr = _vm._objects[action->a16.objNumb].seqList[action->a16.seqIndex].seqPtr; + break; + case SET_STATE_BITS: // act17: OR mask with curr obj state + _vm._objects[action->a17.objNumb].state |= action->a17.stateMask; + break; + case CLEAR_STATE_BITS: // act18: AND ~mask with curr obj state + _vm._objects[action->a18.objNumb].state &= ~action->a18.stateMask; + break; + case TEST_STATE_BITS: // act19: If all bits set, do apass else afail + if ((_vm._objects[action->a19.objNumb].state & action->a19.stateMask) == action->a19.stateMask) + insertActionList(action->a19.actPassIndex); + else + insertActionList(action->a19.actFailIndex); + break; + case DEL_EVENTS: // act20: Remove all events of this action type + // Note: actions are not deleted here, simply turned into NOPs! + wrkEvent = _headEvent; // The earliest event + while (wrkEvent) { // While events found in list + saveEvent = wrkEvent->nextEvent; + if (wrkEvent->action->a20.actType == action->a20.actTypeDel) + delQueue(wrkEvent); + wrkEvent = saveEvent; + } + break; + case GAMEOVER: // act21: Game over! + // NOTE: Must wait at least 1 tick before issuing this action if + // any objects are to be made invisible! + gameStatus.gameOverFl = true; + break; + case INIT_HH_COORD: // act22: Initialise an object to hero's actual coords + _vm._objects[action->a22.objNumb].x = _vm._hero->x; + _vm._objects[action->a22.objNumb].y = _vm._hero->y; + _vm._objects[action->a22.objNumb].screenIndex = *_vm._screen_p;// Don't forget screen! + break; + case EXIT: // act23: Exit game back to DOS + _vm.endGame(); + break; + case BONUS: // act24: Get bonus score for action + processBonus(action->a24.pointIndex); + break; + case COND_BOX: // act25: Conditional on bounding box + obj1 = &_vm._objects[action->a25.objNumb]; + dx = obj1->x + obj1->currImagePtr->x1; + dy = obj1->y + obj1->currImagePtr->y2; + if ((dx >= action->a25.x1) && (dx <= action->a25.x2) && + (dy >= action->a25.y1) && (dy <= action->a25.y2)) + insertActionList(action->a25.actPassIndex); + else + insertActionList(action->a25.actFailIndex); + break; + case SOUND: // act26: Play a sound (or tune) + if (action->a26.soundIndex < _vm._tunesNbr) + _vm.sound().playMusic(action->a26.soundIndex); + else + _vm.sound().playSound(action->a26.soundIndex, BOTH_CHANNELS, MED_PRI); + break; + case ADD_SCORE: // act27: Add object's value to score + _vm.adjustScore(_vm._objects[action->a27.objNumb].objValue); + break; + case SUB_SCORE: // act28: Subtract object's value from score + _vm.adjustScore(-_vm._objects[action->a28.objNumb].objValue); + break; + case COND_CARRY: // act29: Conditional on object being carried + if (_vm._objects[action->a29.objNumb].carriedFl) + insertActionList(action->a29.actPassIndex); + else + insertActionList(action->a29.actFailIndex); + break; + case INIT_MAZE: // act30: Enable and init maze structure + _maze.enabledFl = true; + _maze.size = action->a30.mazeSize; + _maze.x1 = action->a30.x1; + _maze.y1 = action->a30.y1; + _maze.x2 = action->a30.x2; + _maze.y2 = action->a30.y2; + _maze.x3 = action->a30.x3; + _maze.x4 = action->a30.x4; + _maze.firstScreenIndex = action->a30.firstScreenIndex; + break; + case EXIT_MAZE: // act31: Disable maze mode + _maze.enabledFl = false; + break; + case INIT_PRIORITY: + _vm._objects[action->a32.objNumb].priority = action->a32.priority; + break; + case INIT_SCREEN: + _vm._objects[action->a33.objNumb].screenIndex = action->a33.screenIndex; + break; + case AGSCHEDULE: // act34: Schedule a (global) action list + insertActionList(action->a34.actIndex); + break; + case REMAPPAL: // act35: Remap a palette color + HugoEngine::get().screen().remapPal(action->a35.oldColorIndex, action->a35.newColorIndex); + break; + case COND_NOUN: // act36: Conditional on noun mentioned + if (_vm.parser().isWordPresent(_vm._arrayNouns[action->a36.nounIndex])) + insertActionList(action->a36.actPassIndex); + else + insertActionList(action->a36.actFailIndex); + break; + case SCREEN_STATE: // act37: Set new screen state + _vm._screenStates[action->a37.screenIndex] = action->a37.newState; + break; + case INIT_LIPS: // act38: Position lips on object + _vm._objects[action->a38.lipsObjNumb].x = _vm._objects[action->a38.objNumb].x + action->a38.dxLips; + _vm._objects[action->a38.lipsObjNumb].y = _vm._objects[action->a38.objNumb].y + action->a38.dyLips; + _vm._objects[action->a38.lipsObjNumb].screenIndex = *_vm._screen_p; // Don't forget screen! + _vm._objects[action->a38.lipsObjNumb].cycling = CYCLE_FORWARD; + break; + case INIT_STORY_MODE: // act39: Init story_mode flag + // This is similar to the QUIET path mode, except that it is + // independant of it and it additionally disables the ">" prompt + gameStatus.storyModeFl = action->a39.storyModeFl; + + // End the game after story if this is special vendor demo mode + if (gameStatus.demoFl && action->a39.storyModeFl == false) + _vm.endGame(); + break; + case WARN: // act40: Text box (CF TEXT) + Utils::Box(BOX_OK, _vm.file().fetchString(action->a40.stringIndex)); + break; + case COND_BONUS: // act41: Perform action if got bonus + if (_vm._points[action->a41.BonusIndex].scoredFl) + insertActionList(action->a41.actPassIndex); + else + insertActionList(action->a41.actFailIndex); + break; + case TEXT_TAKE: // act42: Text box with "take" message + Utils::Box(BOX_ANY, TAKE_TEXT, _vm._arrayNouns[_vm._objects[action->a42.objNumb].nounIndex][TAKE_NAME]); + break; + case YESNO: // act43: Prompt user for Yes or No + warning("doAction(act43) - Yes/No Box"); + if (Utils::Box(BOX_YESNO, _vm.file().fetchString(action->a43.promptIndex)) != NULL) + insertActionList(action->a43.actYesIndex); + else + insertActionList(action->a43.actNoIndex); + break; + case STOP_ROUTE: // act44: Stop any route in progress + gameStatus.routeIndex = -1; + break; + case COND_ROUTE: // act45: Conditional on route in progress + if (gameStatus.routeIndex >= action->a45.routeIndex) + insertActionList(action->a45.actPassIndex); + else + insertActionList(action->a45.actFailIndex); + break; + case INIT_JUMPEXIT: // act46: Init status.jumpexit flag + // This is to allow left click on exit to get there immediately + // For example the plane crash in Hugo2 where hero is invisible + // Couldn't use INVISIBLE flag since conflicts with boat in Hugo1 + gameStatus.jumpExitFl = action->a46.jumpExitFl; + break; + case INIT_VIEW: // act47: Init object.viewx, viewy, dir + _vm._objects[action->a47.objNumb].viewx = action->a47.viewx; + _vm._objects[action->a47.objNumb].viewy = action->a47.viewy; + _vm._objects[action->a47.objNumb].direction = action->a47.direction; + break; + case INIT_OBJ_FRAME: // act48: Set seq,frame number to use + // Note: Don't set a sequence at time 0 of a new screen, it causes + // problems clearing the boundary bits of the object! t>0 is safe + _vm._objects[action->a48.objNumb].currImagePtr = _vm._objects[action->a48.objNumb].seqList[action->a48.seqIndex].seqPtr; + for (dx = 0; dx < action->a48.frameIndex; dx++) + _vm._objects[action->a48.objNumb].currImagePtr = _vm._objects[action->a48.objNumb].currImagePtr->nextSeqPtr; + break; + case OLD_SONG: + //TODO For Hugo 1 and Hugo2 DOS: The songs were not stored in a DAT file, but directly as + //strings. the current play_music should be modified to use a strings instead of reading + //the file, in those cases. This replaces, for those DOS versions, act26. + warning("STUB: doAction(act49)"); + break; + default: + Utils::Error(EVNT_ERR, "doAction"); + break; + } + + if (action->a0.actType == NEW_SCREEN) // New_screen() deletes entire list + return (NULL); // next_p = NULL since list now empty + else { + wrkEvent = curEvent->nextEvent; + delQueue(curEvent); // Return event to free list + return(wrkEvent); // Return next event ptr + } +} + +// This is the scheduler which runs every tick. It examines the event queue +// for any events whose time has come. It dequeues these events and performs +// the action associated with the event, returning it to the free queue +void Scheduler::runScheduler() { + debugC(6, kDebugSchedule, "runScheduler"); + + status_t &gameStatus = _vm.getGameStatus(); + + event_t *curEvent = _headEvent; // The earliest event + while (curEvent && curEvent->time <= gameStatus.tick) // While mature events found + curEvent = doAction(curEvent); // Perform the action (returns next_p) + gameStatus.tick++; // Accessed elsewhere via getTicks() +} + +uint32 Scheduler::getTicks() { +// Return system time in ticks. A tick is 1/TICKS_PER_SEC mS + debugC(3, kDebugSchedule, "getTicks"); + + return _vm.getGameStatus().tick; +} + +void Scheduler::processBonus(int bonusIndex) { +// Add indecated bonus to score if not added already + debugC(1, kDebugSchedule, "processBonus(%d)", bonusIndex); + + if (!_vm._points[bonusIndex].scoredFl) { + _vm.adjustScore(_vm._points[bonusIndex].score); + _vm._points[bonusIndex].scoredFl = true; + } +} + +// Transition to a new screen as follows: +// 1. Clear out all non-global events from event list. +// 2. Set the new screen (in the hero object and any carried objects) +// 3. Read in the screen files for the new screen +// 4. Schedule action list for new screen +// 5. Initialise prompt line and status line +void Scheduler::newScreen(int screenIndex) { + debugC(1, kDebugSchedule, "newScreen(%d)", screenIndex); + + // Make sure the background file exists! + if (!_vm.isPacked()) { + char line[32]; + if (!_vm.file().fileExists(strcat(strncat(strcpy(line, _vm._picDir), _vm._screenNames[screenIndex], NAME_LEN), BKGEXT)) && + !_vm.file().fileExists(strcat(strcpy(line, _vm._screenNames[screenIndex]), ".ART"))) { + Utils::Box(BOX_ANY, _vm._textSchedule[kSsNoBackground]); + return; + } + } + + // 1. Clear out all local events + event_t *curEvent = _headEvent; // The earliest event + event_t *wrkEvent; // Event ptr + while (curEvent) { // While mature events found + wrkEvent = curEvent->nextEvent; // Save p (becomes undefined after Del) + if (curEvent->localActionFl) + delQueue(curEvent); // Return event to free list + curEvent = wrkEvent; + } + + // 2. Set the new screen in the hero object and any being carried + _vm.setNewScreen(screenIndex); + + // 3. Read in new screen files + _vm.readScreenFiles(screenIndex); + + // 4. Schedule action list for this screen + _vm.screenActions(screenIndex); + + // 5. Initialise prompt line and status line + _vm.initNewScreenDisplay(); +} + +// Write the event queue to the file with handle f +// Note that we convert all the event structure ptrs to indexes +// using -1 for NULL. We can't convert the action ptrs to indexes +// so we save address of first dummy action ptr to compare on restore. +void Scheduler::saveEvents(Common::WriteStream *f) { + uint32 curTime; + event_t saveEvents_[kMaxEvents]; // Convert event ptrs to indexes + event_t *wrkEvent; // Event ptr + int16 freeIndex; // Free list index + int16 headIndex; // Head of list index + int16 tailIndex; // Tail of list index + + debugC(1, kDebugSchedule, "saveEvents"); + + curTime = getTicks(); + + // Convert event ptrs to indexes + for (int16 i = 0; i < kMaxEvents; i++) { + wrkEvent = &_events[i]; + saveEvents_[i] = *wrkEvent; + saveEvents_[i].prevEvent = (wrkEvent->prevEvent == NULL) ? (event_t *) - 1 : (event_t *)(wrkEvent->prevEvent - _events); + saveEvents_[i].nextEvent = (wrkEvent->nextEvent == NULL) ? (event_t *) - 1 : (event_t *)(wrkEvent->nextEvent - _events); + } + freeIndex = (_freeEvent == 0) ? -1 : _freeEvent - _events; + headIndex = (_headEvent == 0) ? -1 : _headEvent - _events; + tailIndex = (_tailEvent == 0) ? -1 : _tailEvent - _events; + + f->write(&curTime, sizeof(curTime)); + f->write(&freeIndex, sizeof(freeIndex)); + f->write(&headIndex, sizeof(headIndex)); + f->write(&tailIndex, sizeof(tailIndex)); + f->write(saveEvents_, sizeof(saveEvents_)); +} + +// Restore the event list from file with handle f +void Scheduler::restoreEvents(Common::SeekableReadStream *f) { + uint32 curTime, saveTime; + event_t *wrkEvent; // Event ptr + event_t savedEvents[kMaxEvents]; // Convert event ptrs to indexes + int16 freeIndex; // Free list index + int16 headIndex; // Head of list index + int16 tailIndex; // Tail of list index + + debugC(1, kDebugSchedule, "restoreEvents"); + + f->read(&saveTime, sizeof(saveTime)); // time of save + f->read(&freeIndex, sizeof(freeIndex)); + f->read(&headIndex, sizeof(headIndex)); + f->read(&tailIndex, sizeof(tailIndex)); + f->read(savedEvents, sizeof(savedEvents)); + + // Restore events indexes to pointers + for (int i = 0; i < kMaxEvents; i++) { + wrkEvent = &savedEvents[i]; + _events[i] = *wrkEvent; + _events[i].prevEvent = (wrkEvent->prevEvent == (event_t *) - 1) ? (event_t *)0 : &_events[(size_t)wrkEvent->prevEvent ]; + _events[i].nextEvent = (wrkEvent->nextEvent == (event_t *) - 1) ? (event_t *)0 : &_events[(size_t)wrkEvent->nextEvent ]; + } + _freeEvent = (freeIndex == -1) ? NULL : &_events[freeIndex]; + _headEvent = (headIndex == -1) ? NULL : &_events[headIndex]; + _tailEvent = (tailIndex == -1) ? NULL : &_events[tailIndex]; + + // Adjust times to fit our time + curTime = getTicks(); + wrkEvent = _headEvent; // The earliest event + while (wrkEvent) { // While mature events found + wrkEvent->time = wrkEvent->time - saveTime + curTime; + wrkEvent = wrkEvent->nextEvent; + } +} + +void Scheduler::restoreScreen(int screenIndex) { +// Transition to a new screen as follows: +// 1. Set the new screen (in the hero object and any carried objects) +// 2. Read in the screen files for the new screen +// 3. Initialise prompt line and status line + + debugC(1, kDebugSchedule, "restoreScreen(%d)", screenIndex); + + // 1. Set the new screen in the hero object and any being carried + _vm.setNewScreen(screenIndex); + + // 2. Read in new screen files + _vm.readScreenFiles(screenIndex); + + // 3. Initialise prompt line and status line + _vm.initNewScreenDisplay(); +} + +void Scheduler::swapImages(int objNumb1, int objNumb2) { +// Swap all the images of one object with another. Set hero_image (we make +// the assumption for now that the first obj is always the HERO) to the object +// number of the swapped image + seqList_t tmpSeqList[MAX_SEQUENCES]; + + debugC(1, kDebugSchedule, "swapImages(%d, %d)", objNumb1, objNumb2); + + _vm.file().saveSeq(&_vm._objects[objNumb1]); + memcpy(tmpSeqList, _vm._objects[objNumb1].seqList, sizeof(seqList_t)); + memcpy(_vm._objects[objNumb1].seqList, _vm._objects[objNumb2].seqList, sizeof(seqList_t)); + memcpy(_vm._objects[objNumb2].seqList, tmpSeqList, sizeof(seqList_t)); + _vm.file().restoreSeq(&_vm._objects[objNumb1]); + _vm._objects[objNumb2].currImagePtr = _vm._objects[objNumb2].seqList[0].seqPtr; + _vm._heroImage = (_vm._heroImage == HERO) ? objNumb2 : HERO; + + // Make sure baseline stays constant + _vm._objects[objNumb1].y += _vm._objects[objNumb2].currImagePtr->y2 - _vm._objects[objNumb1].currImagePtr->y2; +} + +} // end of namespace Hugo diff --git a/engines/hugo/schedule.h b/engines/hugo/schedule.h new file mode 100755 index 0000000000..e24bcc0c64 --- /dev/null +++ b/engines/hugo/schedule.h @@ -0,0 +1,85 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on original Hugo Trilogy source code + * + * Copyright (c) 1989-1995 David P. Gray + * + */ + +#ifndef HUGO_SCHEDULE_H +#define HUGO_SCHEDULE_H + +namespace Hugo { + +#define kMaxEvents 50 /* Max events in event queue */ + +struct event_t { + act *action; /* Ptr to action to perform */ + bool localActionFl; /* TRUE if action is only for this screen */ + uint32 time; /* (absolute) time to perform action */ + struct event_t *prevEvent; /* Chain to previous event */ + struct event_t *nextEvent; /* Chain to next event */ +}; + +class Scheduler { +public: + Scheduler(HugoEngine &vm); + + void initEventQueue(); + void insertAction(act *action); + void insertActionList(uint16 actIndex); + void decodeString(char *line); + void runScheduler(); + uint32 getTicks(); + void processBonus(int bonusIndex); + void newScreen(int screenIndex); + void restoreEvents(Common::SeekableReadStream *f); + void saveEvents(Common::WriteStream *f); + void restoreScreen(int screenIndex); + void swapImages(int objNumb1, int objNumb2); + +private: + enum seqTextSchedule { + kSsNoBackground = 0, + kSsBadSaveGame = 1 + }; + + HugoEngine &_vm; + + event_t _events[kMaxEvents]; /* Statically declare event structures */ + + event_t *_freeEvent; /* Free list of event structures */ + event_t *_headEvent; /* Head of list (earliest time) */ + event_t *_tailEvent; /* Tail of list (latest time) */ + + event_t *getQueue(); + void delQueue(event_t *curEvent); + event_t *doAction(event_t *curEvent); +}; + +} // end of namespace Hugo +#endif //HUGO_SCHEDULE_H diff --git a/engines/hugo/sound.cpp b/engines/hugo/sound.cpp new file mode 100755 index 0000000000..3673bda7a6 --- /dev/null +++ b/engines/hugo/sound.cpp @@ -0,0 +1,189 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on original Hugo Trilogy source code + * + * Copyright (c) 1989-1995 David P. Gray + * + */ + +/* sound.c - sound effects and music support */ + +#include "common/system.h" + +#include "sound/decoders/raw.h" +#include "sound/audiostream.h" + +#include "hugo/hugo.h" +#include "hugo/game.h" +#include "hugo/file.h" +#include "hugo/sound.h" + +namespace Hugo { + +uint16 SeqID; // Device id of (MIDI) sequencer +uint16 SeqVolID; // Low level id to set midi volume +uint16 WavID = 0; // Device id of waveaudio + +//HWAVEOUT hwav; // Handle of waveaudio +//LPWAVEHDR lphdr; // WaveOut structure ptr + +SoundHandler::SoundHandler(HugoEngine &vm) : _vm(vm) { +} + +void SoundHandler::setMusicVolume() { + /* Set the FM music volume from config.mvolume (0..100%) */ + warning("STUB: setMusicVolume()"); + + // uint32 dwVolume; + // + // if (config.music) { + // dwVolume = config.mvolume * 0xffffL / 100; // Convert % to 0..0xffff + // dwVolume |= dwVolume << 16; // Set volume in both stereo words + // midiOutSetVolume(SeqVolID, dwVolume); + // } +} + +void SoundHandler::stopSound() { + /* Stop any sound that might be playing */ + warning("STUB: stopSound()"); + + // waveOutReset(hwav); + // waveOutUnprepareHeader(hwav, lphdr, sizeof(WAVEHDR)); +} + +void SoundHandler::stopMusic() { + /* Stop any tune that might be playing */ + warning("STUB: stopMusic()"); + //mciSendCommand(SeqID, MCI_CLOSE, MCI_WAIT, 0); +} + +void SoundHandler::toggleMusic() { +// Turn music on and off + if (_config.musicFl) + stopMusic(); + _config.musicFl = !_config.musicFl; + initSound(RESET); +} + +void SoundHandler::toggleSound() { +// Turn digitized sound on and off + _config.soundFl = !_config.soundFl; + initSound(RESET); +} + +void SoundHandler::playMIDI(sound_pt seq_p, uint16 size) { +// Write supplied midi data to a temp file for MCI interface +// If seq_p is NULL, delete temp file + + warning("STUB: playMIDI()"); +} + + +void SoundHandler::playMusic(int16 tune) { + /* Read a tune sequence from the sound database and start playing it */ + sound_pt seqPtr; // Sequence data from file + uint16 size; // Size of sequence data + + if (_config.musicFl) { + _vm.getGameStatus().song = tune; + seqPtr = _vm.file().getSound(tune, &size); + playMIDI(seqPtr, size); + } +} + + +void SoundHandler::playSound(int16 sound, stereo_t channel, byte priority) { + /* Produce various sound effects on supplied stereo channel(s) */ + /* Override currently playing sound only if lower or same priority */ + + // uint32 dwVolume; // Left, right volume of sound + sound_pt sound_p; // Sound data + uint16 size; // Size of data + static byte curPriority = 0; // Priority of currently playing sound + // + /* Sound disabled */ + if (!_config.soundFl || !_vm._mixer->isReady()) + return; + // + // // See if last wave still playing - if so, check priority + // if (waveOutUnprepareHeader(hwav, lphdr, sizeof(WAVEHDR)) == WAVERR_STILLPLAYING) + // if (priority < curPriority) // Don't override unless priority >= current + // return; + // else + // Stop_sound(); + curPriority = priority; + // + /* Get sound data */ + if ((sound_p = _vm.file().getSound(sound, &size)) == NULL) + return; + + Audio::AudioStream *stream = Audio::makeRawStream(sound_p, size, 11025, Audio::FLAG_UNSIGNED); + _vm._mixer->playStream(Audio::Mixer::kSpeechSoundType, &_soundHandle, stream); + +} + +void SoundHandler::initSound(inst_t action) { + /* Initialize for MCI sound and midi */ + + warning("STUB: initSound()"); +} + +void SoundHandler::pauseSound(bool activeFl, int hTask) { +// Pause and restore music, sound on losing activity to hTask +// Don't stop music if we are parent of new task, i.e. WinHelp() +// or config.music_bkg is TRUE. + +//TODO: Is 'hTask' still useful ? + + static bool firstFl = true; + static bool musicFl, soundFl; + + if (firstFl) { + firstFl = false; + musicFl = _config.musicFl; + soundFl = _config.soundFl; + } + + // Kill or restore music, sound + if (activeFl) { // Remember states, reset WinHelp flag + _config.musicFl = musicFl; + _config.soundFl = soundFl; + _vm.getGameStatus().helpFl = false; + } else { // Store states and disable + musicFl = _config.musicFl; + soundFl = _config.soundFl; + + // Don't disable music during WinHelp() or config.music_bkg + if (!_vm.getGameStatus().helpFl && !_config.backgroundMusicFl) { + _config.musicFl = false; + _config.soundFl = false; + } + } + initSound(RESET); +} + +} // end of namespace Hugo diff --git a/engines/hugo/sound.h b/engines/hugo/sound.h new file mode 100755 index 0000000000..a9136b99e1 --- /dev/null +++ b/engines/hugo/sound.h @@ -0,0 +1,63 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on original Hugo Trilogy source code + * + * Copyright (c) 1989-1995 David P. Gray + * + */ + +#ifndef HUGO_SOUND_H +#define HUGO_SOUND_H + +#include "sound/mixer.h" + +namespace Hugo { + +class SoundHandler { +public: + SoundHandler(HugoEngine &vm); + + void toggleMusic(); + void toggleSound(); + void setMusicVolume(); + void playMusic(short tune); + void playSound(short sound, stereo_t channel, byte priority); + void initSound(inst_t action); + +private: + HugoEngine &_vm; + Audio::SoundHandle _soundHandle; + + void stopSound(); + void stopMusic(); + void playMIDI(sound_pt seq_p, uint16 size); + void pauseSound(bool activeFl, int hTask); + +}; + +} // end of namespace Hugo +#endif //HUGO_SOUND_H diff --git a/engines/hugo/util.cpp b/engines/hugo/util.cpp new file mode 100755 index 0000000000..f623583fd1 --- /dev/null +++ b/engines/hugo/util.cpp @@ -0,0 +1,215 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on original Hugo Trilogy source code + * + * Copyright (c) 1989-1995 David P. Gray + * + */ + +#include "common/system.h" +#include "gui/message.h" + +#include "hugo/game.h" +#include "hugo/hugo.h" +#include "hugo/util.h" +#include "hugo/sound.h" + +namespace Hugo { + +int Utils::firstBit(byte data) { + /* Returns index (0 to 7) of first 1 in supplied byte, or 8 if not found */ + int i; + + if (!data) + return(8); + + for (i = 0; i < 8; i++) { + if ((data << i) & 0x80) + break; + } + + return(i); +} + +int Utils::lastBit(byte data) { + /* Returns index (0 to 7) of last 1 in supplied byte, or 8 if not found */ + int i; + if (!data) + return(8); + + for (i = 7; i >= 0; i--) { + if ((data << i) & 0x80) + break; + } + + return(i); +} + +void Utils::reverseByte(byte *data) { + /* Reverse the bit order in supplied byte */ + byte maskIn = 0x80; + byte maskOut = 0x01; + byte result = 0; + + for (byte i = 0; i < 8; i++, maskIn >>= 1, maskOut <<= 1) + if (*data & maskIn) + result |= maskOut; + + *data = result; +} + +char *Utils::Box(box_t dismiss, const char *s, ...) { + static char buffer[MAX_STRLEN + 1]; // Format text into this + va_list marker; + + if (!s) return(NULL); // NULL strings catered for + + if (s[0] == '\0') + return(NULL); + + if (strlen(s) > MAX_STRLEN - 100) { // Test length + Warn(false, "String too big:\n%s", s); + return(NULL); + } + + va_start(marker, s); + vsprintf(buffer, s, marker); // Format string into buffer + va_end(marker); + + //Warn(false, "BOX: %s", buffer); + int boxTime = strlen(buffer) * 30; + GUI::TimedMessageDialog dialog(buffer, MAX(1500, boxTime)); + dialog.runModal(); + + // TODO: Some boxes (i.e. the combination code for the shed), needs to return an input. + return buffer; +} + +void Utils::Warn(bool technote, const char *format, ...) { + /* Warning handler. Print supplied message and continue */ + /* Arguments are same as printf */ + /* technote TRUE if we are to refer user to technote file */ + char buffer[WARNLEN]; + bool soundFl = _config.soundFl; + va_list marker; + + _config.soundFl = false; // Kill sound to allow beep sound + HugoEngine::get().sound().initSound(RESET); + + va_start(marker, format); + vsnprintf(buffer, WARNLEN, format, marker); +//// if (technote) +//// strcat (buffer, sTech); + //MessageBeep(MB_ICONEXCLAMATION); + //MessageBox(hwnd, buffer, "HugoWin Warning", MB_OK | MB_ICONEXCLAMATION); + warning("Hugo warning: %s", buffer); + va_end(marker); + + //sndPlaySound(NULL, 0); // Stop beep and restore sound + + _config.soundFl = soundFl; + HugoEngine::get().sound().initSound(RESET); +} + +void Utils::Error(int error_type, const char *format, ...) { + /* Fatal error handler. Reset environment, print error and exit */ + /* Arguments are same as printf */ + va_list marker; + char buffer[ERRLEN + 1]; + bool fatal = true; // Fatal error, else continue + + switch (error_type) { + case FILE_ERR: +// case FONT_ERR: + strcpy(buffer, HugoEngine::get()._textUtil[kErr1]); + break; + case WRITE_ERR: + strcpy(buffer, HugoEngine::get()._textUtil[kErr2]); + fatal = false; // Allow continuation + break; + case PCCH_ERR: + strcpy(buffer, HugoEngine::get()._textUtil[kErr3]); + break; + case HEAP_ERR: + strcpy(buffer, HugoEngine::get()._textUtil[kErr4]); + break; + case SOUND_ERR: + strcpy(buffer, HugoEngine::get()._textUtil[kErr5]); + break; +// case TIMER_ERR: +// strcpy(buffer, HugoEngine::get()._textUtil[kObsoleteErr1]); +// break; +// case VBX_ERR: +// strcpy(buffer, HugoEngine::get()._textUtil[kObsoleteErr2]); +// break; + default: + strcpy(buffer, HugoEngine::get()._textUtil[kErr6]); + break; + } + + if (fatal) + HugoEngine::get().shutdown(); // Restore any devices before exit + + va_start(marker, format); + snprintf(&buffer[strlen(buffer)], ERRLEN - strlen(buffer), format, marker); + //MessageBeep(MB_ICONEXCLAMATION); + //MessageBox(hwnd, buffer, "HugoWin Error", MB_OK | MB_ICONEXCLAMATION); + warning("Hugo Error: %s", buffer); + va_end(marker); + + if (fatal) + exit(1); +} + +void Utils::gameOverMsg(void) { + // Print options for user when dead + //MessageBox(hwnd, gameoverstring, "Be more careful next time!", MB_OK | MB_ICONINFORMATION); + warning("STUB: Gameover_msg(): %s", HugoEngine::get()._textUtil[kGameOver]); +} + +#if 0 +// Strangerke: Useless? +void Utils::Debug_out(char *format, ...) { + /* Write debug info to file */ + static FILE *fp = NULL; + va_list marker; + + if (HugoEngine::get().getGameStatus().debugFl) { + /* Create/truncate if first call, else append */ + if ((fp = fopen("debug.txt", fp == NULL ? "w" : "a")) == NULL) { + Error(WRITE_ERR, "debug.txt"); + return; + } + + va_start(marker, format); + vfprintf(fp, format, marker); + va_end(marker); + fclose(fp); + } +} +#endif +} // end of namespace Hugo diff --git a/engines/hugo/util.h b/engines/hugo/util.h new file mode 100755 index 0000000000..aa58c5e2f1 --- /dev/null +++ b/engines/hugo/util.h @@ -0,0 +1,63 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on original Hugo Trilogy source code + * + * Copyright (c) 1989-1995 David P. Gray + * + */ + +#ifndef HUGO_UTIL_H +#define HUGO_UTIL_H + +namespace Hugo { + +enum seqTextUtil { + kTech = 0, + kErr1 = 1, + kErr2 = 2, + kErr3 = 3, + kErr4 = 4, + kErr5 = 5, + kErr6 = 6, + kGameOver = 7 +// kObsoleteErr1 = 8, +// kObsoleteErr2 = 9 +}; + +namespace Utils { +int firstBit(byte data); +int lastBit(byte data); +void reverseByte(byte *data); +void Warn(bool technote, const char *format, ...); +void Error(int code, const char *format, ...); +void gameOverMsg(); +// void Debug_out(char *format, ...); +char *Box(box_t, const char *, ...); +} + +} // Namespace Hugo +#endif |