diff options
39 files changed, 8849 insertions, 81 deletions
diff --git a/engines/cge/cge.h b/engines/cge/cge.h index a65069ff46..c43358f252 100644 --- a/engines/cge/cge.h +++ b/engines/cge/cge.h @@ -80,12 +80,6 @@ class Talk; #define kSayTheEnd 41 -enum GameType { - kGameTypeNone = 0, - kGameTypeSoltys, - kGameTypeSfinx -}; - // our engine debug channels enum { kCGEDebugBitmap = 1 << 0, diff --git a/engines/cge/detection.cpp b/engines/cge/detection.cpp index 4c2f81c1ae..60558f147e 100644 --- a/engines/cge/detection.cpp +++ b/engines/cge/detection.cpp @@ -33,7 +33,6 @@ namespace CGE { struct CgeGameDescription { ADGameDescription desc; - GameType gameType; }; #define GAMEOPTION_COLOR_BLIND_DEFAULT_OFF GUIO_GAMEOPTIONS1 @@ -42,117 +41,81 @@ struct CgeGameDescription { static const PlainGameDescriptor CGEGames[] = { { "soltys", "Soltys" }, - { "sfinx", "Sfinx" }, { 0, 0 } }; namespace CGE { -static const CgeGameDescription gameDescriptions[] = { - - { - { - "soltys", "", - { - {"vol.cat", 0, "0c33e2c304821a2444d297fc5e2d67c6", 50176}, - {"vol.dat", 0, "f9ae2e7f8f7cac91378cdafca43faf1e", 8437572}, - AD_LISTEND - }, - Common::PL_POL, Common::kPlatformDOS, ADGF_NO_FLAGS, GUIO0() - }, - kGameTypeSoltys - }, +static const ADGameDescription gameDescriptions[] = { { + "soltys", "", { - "soltys", "Soltys Freeware", - { - {"vol.cat", 0, "0c33e2c304821a2444d297fc5e2d67c6", 50176}, - {"vol.dat", 0, "f9ae2e7f8f7cac91378cdafca43faf1e", 8437676}, - AD_LISTEND - }, - Common::PL_POL, Common::kPlatformDOS, ADGF_NO_FLAGS, GUIO1(GAMEOPTION_COLOR_BLIND_DEFAULT_OFF) + {"vol.cat", 0, "0c33e2c304821a2444d297fc5e2d67c6", 50176}, + {"vol.dat", 0, "f9ae2e7f8f7cac91378cdafca43faf1e", 8437572}, + AD_LISTEND }, - kGameTypeSoltys + Common::PL_POL, Common::kPlatformDOS, ADGF_NO_FLAGS, GUIO0() }, { + "soltys", "Soltys Freeware", { - "soltys", "Soltys Demo (not supported)", - { - {"vol.cat", 0, "1e077c8ff58109a187f07ac54b0c873a", 18788}, - {"vol.dat", 0, "75d385a6074c58b69f7730481f256051", 1796710}, - AD_LISTEND - }, - Common::EN_ANY, Common::kPlatformDOS, ADGF_DEMO , GUIO1(GAMEOPTION_COLOR_BLIND_DEFAULT_OFF) + {"vol.cat", 0, "0c33e2c304821a2444d297fc5e2d67c6", 50176}, + {"vol.dat", 0, "f9ae2e7f8f7cac91378cdafca43faf1e", 8437676}, + AD_LISTEND }, - kGameTypeSoltys + Common::PL_POL, Common::kPlatformDOS, ADGF_NO_FLAGS, GUIO1(GAMEOPTION_COLOR_BLIND_DEFAULT_OFF) }, { + "soltys", "Soltys Demo (not supported)", { - "soltys", "Soltys Demo (not supported)", - { - {"vol.cat", 0, "f17987487fab1ebddd781d8d02fedecc", 7168}, - {"vol.dat", 0, "c5d9b15863cab61dc125551576dece04", 1075272}, - AD_LISTEND - }, - Common::PL_POL, Common::kPlatformDOS, ADGF_DEMO , GUIO1(GAMEOPTION_COLOR_BLIND_DEFAULT_OFF) + {"vol.cat", 0, "1e077c8ff58109a187f07ac54b0c873a", 18788}, + {"vol.dat", 0, "75d385a6074c58b69f7730481f256051", 1796710}, + AD_LISTEND }, - kGameTypeSoltys + Common::EN_ANY, Common::kPlatformDOS, ADGF_DEMO , GUIO1(GAMEOPTION_COLOR_BLIND_DEFAULT_OFF) }, { + "soltys", "Soltys Demo (not supported)", { - "soltys", "Soltys Freeware v1.0", - { - {"vol.cat", 0, "f1675684c68ab90272f5776f8f2c3974", 50176}, - {"vol.dat", 0, "4ffeff4abc99ac5999b55ccfc56ab1df", 8430868}, - AD_LISTEND - }, - Common::EN_ANY, Common::kPlatformDOS, ADGF_NO_FLAGS , GUIO1(GAMEOPTION_COLOR_BLIND_DEFAULT_OFF) + {"vol.cat", 0, "f17987487fab1ebddd781d8d02fedecc", 7168}, + {"vol.dat", 0, "c5d9b15863cab61dc125551576dece04", 1075272}, + AD_LISTEND }, - kGameTypeSoltys + Common::PL_POL, Common::kPlatformDOS, ADGF_DEMO , GUIO1(GAMEOPTION_COLOR_BLIND_DEFAULT_OFF) }, { + "soltys", "Soltys Freeware v1.0", { - "soltys", "Soltys Freeware v1.0", - { - {"vol.cat", 0, "20fdce799adb618100ef9ee2362be875", 50176}, - {"vol.dat", 0, "0e43331c846094d77f5dd201827e0a3b", 8439339}, - AD_LISTEND - }, - Common::PL_POL, Common::kPlatformDOS, ADGF_NO_FLAGS, GUIO1(GAMEOPTION_COLOR_BLIND_DEFAULT_OFF) + {"vol.cat", 0, "f1675684c68ab90272f5776f8f2c3974", 50176}, + {"vol.dat", 0, "4ffeff4abc99ac5999b55ccfc56ab1df", 8430868}, + AD_LISTEND }, - kGameTypeSoltys + Common::EN_ANY, Common::kPlatformDOS, ADGF_NO_FLAGS , GUIO1(GAMEOPTION_COLOR_BLIND_DEFAULT_OFF) }, { + "soltys", "Soltys Freeware v1.0", { - "soltys", "Soltys Freeware v1.0", - { - {"vol.cat", 0, "fcae86b20eaa5cedec17b24fa5e85eb4", 50176}, - {"vol.dat", 0, "ff10d54acc2c95696c57e05819b6906f", 8450151}, - AD_LISTEND - }, - Common::ES_ESP, Common::kPlatformDOS, ADGF_NO_FLAGS , GUIO1(GAMEOPTION_COLOR_BLIND_DEFAULT_OFF) + {"vol.cat", 0, "20fdce799adb618100ef9ee2362be875", 50176}, + {"vol.dat", 0, "0e43331c846094d77f5dd201827e0a3b", 8439339}, + AD_LISTEND }, - kGameTypeSoltys + Common::PL_POL, Common::kPlatformDOS, ADGF_NO_FLAGS, GUIO1(GAMEOPTION_COLOR_BLIND_DEFAULT_OFF) }, { + "soltys", "Soltys Freeware v1.0", { - // Polish version, provided by Strangerke - "sfinx", "Sfinx Freeware", - { - {"vol.cat", 0, "21197b287d397c53261b6616bf0dd880", 129024}, - {"vol.dat", 0, "de14291869a8eb7c2732ab783c7542ef", 34180844}, - AD_LISTEND - }, - Common::PL_POL, Common::kPlatformDOS, ADGF_NO_FLAGS, GUIO1(GAMEOPTION_COLOR_BLIND_DEFAULT_OFF) + {"vol.cat", 0, "fcae86b20eaa5cedec17b24fa5e85eb4", 50176}, + {"vol.dat", 0, "ff10d54acc2c95696c57e05819b6906f", 8450151}, + AD_LISTEND }, - kGameTypeSfinx + Common::ES_ESP, Common::kPlatformDOS, ADGF_NO_FLAGS , GUIO1(GAMEOPTION_COLOR_BLIND_DEFAULT_OFF) }, - {AD_TABLE_END_MARKER, kGameTypeNone} + AD_TABLE_END_MARKER }; static const ADFileBasedFallback fileBasedFallback[] = { - { &gameDescriptions[0].desc, { "vol.cat", "vol.dat", 0 } }, + { &gameDescriptions[0], { "vol.cat", "vol.dat", 0 } }, { 0, { 0 } } }; } // End of namespace CGE diff --git a/engines/cge2/bitmap.cpp b/engines/cge2/bitmap.cpp new file mode 100644 index 0000000000..01e97ebc53 --- /dev/null +++ b/engines/cge2/bitmap.cpp @@ -0,0 +1,458 @@ +/* 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. + * + */ + +/* + * This code is based on original Sfinx source code + * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon + */ + +#include "cge2/bitmap.h" +#include "cge2/cge2.h" +#include "cge2/vga13h.h" +#include "cge2/talk.h" +#include "common/system.h" +#include "common/debug.h" +#include "common/debug-channels.h" + +namespace CGE2 { + +Bitmap::Bitmap() : _w(0), _h(0), _v(nullptr), _b(nullptr), _map(0), _vm(nullptr) { +} + +void Bitmap::setVM(CGE2Engine *vm) { + _vm = vm; +} + +Bitmap::Bitmap(CGE2Engine *vm, const char *fname) : _v(nullptr), _b(nullptr), _map(0), _vm(vm) { + char pat[kMaxPath]; + + if (!strcmp(fname, "04tal201")) { + strcpy(pat, "04tal202"); + warning("Workaround for missing VBM: 04tal201"); + } else if (!strcmp(fname, "11oqlist-")) { + strcpy(pat, "11oqlist"); + warning("Workaround for wrong VBM name: 11oqlist-"); + } else + strcpy(pat, fname); + + strcpy(pat, setExtension(pat, ".VBM").c_str()); + + if (_vm->_resman->exist(pat)) { + EncryptedStream file(_vm, pat); + if (file.err()) + error("Unable to find VBM [%s]", fname); + if (!loadVBM(&file)) + error("Bad VBM [%s]", fname); + } else { + warning("Missing VBM [%s]", pat); + } +} + +Bitmap::Bitmap(CGE2Engine *vm, uint16 w, uint16 h, uint8 *map) : _w(w), _h(h), _v(nullptr), _map(0), _b(nullptr), _vm(vm) { + if (map) + code(map); +} + +// following routine creates filled rectangle +// immediately as VGA video chunks, in near memory as fast as possible, +// especially for text line real time display +Bitmap::Bitmap(CGE2Engine *vm, uint16 w, uint16 h, uint8 fill) + : _w((w + 3) & ~3), // only full uint32 allowed! + _h(h), _map(0), _b(nullptr), _vm(vm) { + + uint16 dsiz = _w >> 2; // data size (1 plane line size) + uint16 lsiz = 2 + dsiz + 2; // uint16 for line header, uint16 for gap + uint16 psiz = _h * lsiz; // - last gape, but + plane trailer + uint8 *v = new uint8[4 * psiz + _h * sizeof(*_b)];// the same for 4 planes + // + room for wash table + + WRITE_LE_UINT16(v, (kBmpCPY | dsiz)); // data chunk hader + memset(v + 2, fill, dsiz); // data bytes + WRITE_LE_UINT16(v + lsiz - 2, (kBmpSKP | ((kScrWidth / 4) - dsiz))); // gap + + // Replicate lines + byte *destP; + for (destP = v + lsiz; destP < (v + psiz); destP += lsiz) + Common::copy(v, v + lsiz, destP); + + WRITE_LE_UINT16(v + psiz - 2, kBmpEOI); // plane trailer uint16 + + // Replicate planes + for (destP = v + psiz; destP < (v + 4 * psiz); destP += psiz) + Common::copy(v, v + psiz, destP); + + HideDesc *b = (HideDesc *)(v + 4 * psiz); + b->_skip = (kScrWidth - _w) >> 2; + b->_hide = _w >> 2; + + // Replicate across the entire table + for (HideDesc *hdP = b + 1; hdP < (b + _h); hdP++) + *hdP = *b; + + b->_skip = 0; // fix the first entry + _v = v; + _b = b; +} + +Bitmap::Bitmap(CGE2Engine *vm, const Bitmap &bmp) : _w(bmp._w), _h(bmp._h), _v(nullptr), _map(0), _b(nullptr), _vm(vm) { + uint8 *v0 = bmp._v; + if (!v0) + return; + + uint16 vsiz = (uint8 *)(bmp._b) - (uint8 *)(v0); + uint16 siz = vsiz + _h * sizeof(HideDesc); + uint8 *v1 = new uint8[siz]; + memcpy(v1, v0, siz); + _b = (HideDesc *)((_v = v1) + vsiz); +} + +Bitmap::~Bitmap() { + release(); +} + +void Bitmap::release() { + if (_v != nullptr) + delete[] _v; + _v = nullptr; +} + +Bitmap &Bitmap::operator=(const Bitmap &bmp) { + if (this == &bmp) + return *this; + + uint8 *v0 = bmp._v; + _w = bmp._w; + _h = bmp._h; + _map = 0; + _vm = bmp._vm; + delete[] _v; + _v = nullptr; + + if (v0) { + uint16 vsiz = (uint8 *)bmp._b - (uint8 *)v0; + uint16 siz = vsiz + _h * sizeof(HideDesc); + uint8 *v1 = new uint8[siz]; + memcpy(v1, v0, siz); + _b = (HideDesc *)((_v = v1) + vsiz); + } + return *this; +} + +// Blatant rip from hopkins engine where it's ripped from gob engine. Hi DrMcCoy, hi Strangerke! ;> +Common::String Bitmap::setExtension(const Common::String &str, const Common::String &ext) { + if (str.empty()) + return str; + + const char *dot = strrchr(str.c_str(), '.'); + if (dot) + return Common::String(str.c_str(), dot - str.c_str()) + ext; + + return str + ext; +} + +BitmapPtr Bitmap::code(uint8 *map) { + if (!map) + return nullptr; + + uint16 cnt; + + if (_v) { // old X-map exists, so remove it + delete[] _v; + _v = nullptr; + } + + while (true) { // at most 2 times: for (V == NULL) & for allocated block; + uint8 *im = _v + 2; + uint16 *cp = (uint16 *) _v; + + if (_v) { // 2nd pass - fill the hide table + for (uint i = 0; i < _h; i++) { + _b[i]._skip = 0xFFFF; + _b[i]._hide = 0x0000; + } + } + for (int bpl = 0; bpl < 4; bpl++) { // once per each bitplane + uint8 *bm = map; + bool skip = (bm[bpl] == kPixelTransp); + uint16 j; + + cnt = 0; + for (uint i = 0; i < _h; i++) { // once per each line + uint8 pix; + for (j = bpl; j < _w; j += 4) { + pix = bm[j]; + if (_v && (pix != kPixelTransp)) { + if (j < _b[i]._skip) + _b[i]._skip = j; + + if (j >= _b[i]._hide) + _b[i]._hide = j + 1; + } + if (((pix == kPixelTransp) != skip) || (cnt >= 0x3FF0)) { // end of block + cnt |= (skip) ? kBmpSKP : kBmpCPY; + if (_v) + WRITE_LE_UINT16(cp, cnt); // store block description uint16 + + cp = (uint16 *) im; + im += 2; + skip = (pix == kPixelTransp); + cnt = 0; + } + if (!skip) { + if (_v) + *im = pix; + im++; + } + cnt++; + } + + bm += _w; + if (_w < kScrWidth) { + if (skip) + cnt += (kScrWidth - j + 3) / 4; + else { + cnt |= kBmpCPY; + if (_v) + WRITE_LE_UINT16(cp, cnt); + + cp = (uint16 *) im; + im += 2; + skip = true; + cnt = (kScrWidth - j + 3) / 4; + } + } + } + if (cnt && ! skip) { + cnt |= kBmpCPY; + if (_v) + WRITE_LE_UINT16(cp, cnt); + + cp = (uint16 *) im; + im += 2; + } + if (_v) + WRITE_LE_UINT16(cp, kBmpEOI); + cp = (uint16 *) im; + im += 2; + } + if (_v) + break; + + uint16 sizV = (uint16)(im - 2 - _v); + _v = new uint8[sizV + _h * sizeof(*_b)]; + _b = (HideDesc *)(_v + sizV); + } + cnt = 0; + for (uint i = 0; i < _h; i++) { + if (_b[i]._skip == 0xFFFF) { // whole line is skipped + _b[i]._skip = (cnt + kScrWidth) >> 2; + cnt = 0; + } else { + uint16 s = _b[i]._skip & ~3; + uint16 h = (_b[i]._hide + 3) & ~3; + _b[i]._skip = (cnt + s) >> 2; + _b[i]._hide = (h - s) >> 2; + cnt = kScrWidth - h; + } + } + + return this; +} + +bool Bitmap::solidAt(V2D pos) { + pos.x += _w >> 1; + pos.y = _h - pos.y; + + if (!pos.limited(V2D(_vm, _w, _h))) + return false; + + uint8 *m = _v; + uint16 r = static_cast<uint16>(pos.x) % 4; + uint16 n0 = (kScrWidth * pos.y + pos.x) / 4; + uint16 n = 0; + + while (r) { + uint16 w, t; + + w = READ_LE_UINT16(m); + m += 2; + t = w & 0xC000; + w &= 0x3FFF; + + switch (t) { + case kBmpEOI: + r--; + // No break on purpose + case kBmpSKP: + w = 0; + break; + case kBmpREP: + w = 1; + break; + } + m += w; + } + + while (true) { + uint16 w, t; + + w = READ_LE_UINT16(m); + m += 2; + t = w & 0xC000; + w &= 0x3FFF; + + if (n > n0) + return false; + + n += w; + switch (t) { + case kBmpEOI: + return false; + case kBmpSKP: + w = 0; + break; + case kBmpREP: + case kBmpCPY: + if ((n - w <= n0) && (n > n0)) + return true; + break; + } + m += ((t == kBmpREP) ? 1 : w); + } +} + +bool Bitmap::loadVBM(EncryptedStream *f) { + uint16 p = 0, n = 0; + if (!f->err()) + f->read((uint8 *)&p, sizeof(p)); + p = FROM_LE_16(p); + + if (!f->err()) + f->read((uint8 *)&n, sizeof(n)); + n = FROM_LE_16(n); + + if (!f->err()) + f->read((uint8 *)&_w, sizeof(_w)); + _w = FROM_LE_16(_w); + + if (!f->err()) + f->read((uint8 *)&_h, sizeof(_h)); + _h = FROM_LE_16(_h); + + if (!f->err()) { + if (p) { + if (_vm->_bitmapPalette) { + // Read in the palette + byte palData[kPalSize]; + f->read(palData, kPalSize); + + const byte *srcP = palData; + for (int idx = 0; idx < kPalCount; idx++, srcP += 3) { + _vm->_bitmapPalette[idx]._r = *srcP; + _vm->_bitmapPalette[idx]._g = *(srcP + 1); + _vm->_bitmapPalette[idx]._b = *(srcP + 2); + } + } else + f->seek(f->pos() + kPalSize); + } + } + _v = new uint8[n]; + + if (!f->err()) + f->read(_v, n); + + _b = (HideDesc *)(_v + n - _h * sizeof(HideDesc)); + return (!f->err()); +} + +void Bitmap::xLatPos(V2D& p) { + p.x -= (_w >> 1); + p.y = kWorldHeight - p.y - _h; +} + +#define _ kPixelTransp, +#define L 1, +#define G 2, +#define D 3, +#define kDesignSize 240 + +uint8 *Bitmap::makeSpeechBubbleTail(int which, uint8 colorSet[][4]) { + static const uint8 kSLDesign[kDesignSize] = { + G G G G G G G G G _ _ _ _ _ _ + L G G G G G G G G D _ _ _ _ _ + _ L G G G G G G G D _ _ _ _ _ + _ _ L G G G G G G G D _ _ _ _ + _ _ _ L G G G G G G D _ _ _ _ + _ _ _ _ L G G G G G D _ _ _ _ + _ _ _ _ _ L G G G G G D _ _ _ + _ _ _ _ _ _ L G G G G D _ _ _ + _ _ _ _ _ _ _ L G G G D _ _ _ + _ _ _ _ _ _ _ _ L G G G D _ _ + _ _ _ _ _ _ _ _ _ L G G D _ _ + _ _ _ _ _ _ _ _ _ _ L G D _ _ + _ _ _ _ _ _ _ _ _ _ _ L G D _ + _ _ _ _ _ _ _ _ _ _ _ _ L D _ + _ _ _ _ _ _ _ _ _ _ _ _ _ L D + _ _ _ _ _ _ _ _ _ _ _ _ _ _ D + }; + + static const uint8 kSRDesign[kDesignSize] = { + _ _ _ _ _ _ G G G G G G G G G + _ _ _ _ _ L G G G G G G G G D + _ _ _ _ _ L G G G G G G G D _ + _ _ _ _ L G G G G G G G D _ _ + _ _ _ _ L G G G G G G D _ _ _ + _ _ _ _ L G G G G G D _ _ _ _ + _ _ _ L G G G G G D _ _ _ _ _ + _ _ _ L G G G G D _ _ _ _ _ _ + _ _ _ L G G G D _ _ _ _ _ _ _ + _ _ L G G G D _ _ _ _ _ _ _ _ + _ _ L G G D _ _ _ _ _ _ _ _ _ + _ _ L G D _ _ _ _ _ _ _ _ _ _ + _ L G D _ _ _ _ _ _ _ _ _ _ _ + _ L D _ _ _ _ _ _ _ _ _ _ _ _ + L D _ _ _ _ _ _ _ _ _ _ _ _ _ + D _ _ _ _ _ _ _ _ _ _ _ _ _ _ + }; + + uint8 *des = new uint8[kDesignSize]; + switch (which) { + case 0: + memcpy(des, kSLDesign, sizeof(kSLDesign)); + break; + case 1: + memcpy(des, kSRDesign, sizeof(kSRDesign)); + break; + default: + error("Wrong parameter in Bitmap::makeSpeechBubbleTail!"); + break; + } + + for (int i = 0; i < kDesignSize; i++) { + if ((des[i] >= 1) && (des[i] <= 3)) + des[i] = colorSet[kCBSay][des[i]]; + } + + return des; +} + +} // End of namespace CGE2 diff --git a/engines/cge2/bitmap.h b/engines/cge2/bitmap.h new file mode 100644 index 0000000000..613222fc6e --- /dev/null +++ b/engines/cge2/bitmap.h @@ -0,0 +1,94 @@ +/* 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. + * + */ + +/* + * This code is based on original Sfinx source code + * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon + */ + +#ifndef CGE2_BITMAP_H +#define CGE2_BITMAP_H + +#include "cge2/general.h" +#include "common/file.h" + +namespace CGE2 { + +class CGE2Engine; +class EncryptedStream; +class V2D; + +#define kMaxPath 128 + +enum { + kBmpEOI = 0x0000, + kBmpSKP = 0x4000, + kBmpREP = 0x8000, + kBmpCPY = 0xC000 +}; + +#include "common/pack-start.h" + +struct HideDesc { + uint16 _skip; + uint16 _hide; +}; + +#include "common/pack-end.h" + +class Bitmap { + CGE2Engine *_vm; + + Common::String setExtension(const Common::String &str, const Common::String &ext); + bool loadVBM(EncryptedStream *f); +public: + uint16 _w; + uint16 _h; + uint8 *_v; + int32 _map; + HideDesc *_b; + + Bitmap(); + Bitmap(CGE2Engine *vm, const char *fname); + Bitmap(CGE2Engine *vm, uint16 w, uint16 h, uint8 *map); + Bitmap(CGE2Engine *vm, uint16 w, uint16 h, uint8 fill); + Bitmap(CGE2Engine *vm, const Bitmap &bmp); + ~Bitmap(); + + void setVM(CGE2Engine *vm); + Bitmap *code(uint8 *map); + Bitmap &operator=(const Bitmap &bmp); + void release(); + void hide(V2D pos); + void show(V2D pos); + bool solidAt(V2D pos); + void xLatPos(V2D &p); + + static uint8 *makeSpeechBubbleTail(int des, uint8 colorSet[][4]); +}; + + +typedef Bitmap *BitmapPtr; + +} // End of namespace CGE2 + +#endif // CGE2_BITMAP_H diff --git a/engines/cge2/cge2.cpp b/engines/cge2/cge2.cpp new file mode 100644 index 0000000000..f3bc1de44c --- /dev/null +++ b/engines/cge2/cge2.cpp @@ -0,0 +1,205 @@ +/* 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. + * + */ + +/* + * This code is based on original Sfinx source code + * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon + */ + +#include "engines/util.h" +#include "common/config-manager.h" +#include "common/debug.h" +#include "common/debug-channels.h" +#include "cge2/cge2.h" +#include "cge2/bitmap.h" +#include "cge2/vga13h.h" +#include "cge2/sound.h" +#include "cge2/text.h" +#include "cge2/hero.h" +#include "cge2/general.h" +#include "cge2/spare.h" +#include "cge2/talk.h" +#include "cge2/cge2_main.h" +#include "cge2/map.h" + +namespace CGE2 { + +CGE2Engine::CGE2Engine(OSystem *syst, const ADGameDescription *gameDescription) + : Engine(syst), _gameDescription(gameDescription), _randomSource("cge2") { + + // Debug/console setup + DebugMan.addDebugChannel(kCGE2DebugOpcode, "opcode", "CGE2 opcode debug channel"); + + _resman = nullptr; + _vga = nullptr; + _midiPlayer = nullptr; + _fx = nullptr; + _sound = nullptr; + _text = nullptr; + for (int i = 0; i < 2; i++) + _heroTab[i] = nullptr; + _eye = nullptr; + for (int i = 0; i < kSceneMax; i++) + _eyeTab[i] = nullptr; + _spare = nullptr; + _commandHandler = nullptr; + _commandHandlerTurbo = nullptr; + _font = nullptr; + _infoLine = nullptr; + _mouse = nullptr; + _keyboard = nullptr; + _talk = nullptr; + for (int i = 0; i < kMaxPoint; i++) + _point[i] = nullptr; + _sys = nullptr; + _busyPtr = nullptr; + for (int i = 0; i < 2; i++) + _vol[i] = nullptr; + _eventManager = nullptr; + _map = nullptr; + _quitFlag = false; + _bitmapPalette = nullptr; + _startupMode = 1; + _now = 1; + _sex = 1; + _mouseTop = kWorldHeight / 3; + _dark = false; + _lastFrame = 0; + _lastTick = 0; + _waitSeq = 0; + _waitRef = 0; + _soundStat._wait = nullptr; + _soundStat._ref[0] = 0; + _soundStat._ref[1] = 0; + _taken = false; + _endGame = false; + _req = 1; + _midiNotify = nullptr; + _spriteNotify = nullptr; + + _sayCap = ConfMan.getBool("subtitles"); + _sayVox = !ConfMan.getBool("speech_mute"); + if (ConfMan.getBool("mute")) { + _oldMusicVolume = _oldSfxVolume = 0; + _music = _sfx = false; + } else { + _oldMusicVolume = ConfMan.getInt("music_volume"); + _oldSfxVolume = ConfMan.getInt("sfx_volume"); + _music = _oldMusicVolume != 0; + _sfx = _oldSfxVolume != 0; + } +} + +void CGE2Engine::init() { + // Create debugger console + _console = new CGE2Console(this); + _resman = new ResourceManager(); + _vga = new Vga(this); + _fx = new Fx(this, 16); + _sound = new Sound(this); + _midiPlayer = new MusicPlayer(this); + _text = new Text(this, "CGE"); + + for (int i = 0; i < 2; i++) + _heroTab[i] = new HeroTab(this); + + _eye = new V3D(); + for (int i = 0; i < kSceneMax; i++) + _eyeTab[i] = new V3D(); + + _spare = new Spare(this); + _commandHandler = new CommandHandler(this, false); + _commandHandlerTurbo = new CommandHandler(this, true); + _font = new Font(this); + _infoLine = new InfoLine(this, kInfoW); + _mouse = new Mouse(this); + _keyboard = new Keyboard(this); + + for (int i = 0; i < kMaxPoint; i++) + _point[i] = new V3D(); + + _sys = new System(this); + _eventManager = new EventManager(this); + _map = new Map(this); + _startGameSlot = ConfMan.hasKey("save_slot") ? ConfMan.getInt("save_slot") : -1; +} + +void CGE2Engine::deinit() { + // Remove all of our debug levels here + DebugMan.clearAllDebugChannels(); + + delete _console; + + delete _spare; + delete _resman; + delete _vga; + delete _fx; + delete _sound; + delete _midiPlayer; + delete _text; + + for (int i = 0; i < 2; i++) + delete _heroTab[i]; + + for (int i = 0; i < kSceneMax; i++) + delete _eyeTab[i]; + + delete _eye; + delete _commandHandler; + delete _commandHandlerTurbo; + delete _font; + delete _infoLine; + delete _mouse; + delete _keyboard; + + if (_talk != nullptr) + delete _talk; + + for (int i = 0; i < kMaxPoint; i++) + delete _point[i]; + + delete _sys; + delete _eventManager; + delete _map; +} + +bool CGE2Engine::hasFeature(EngineFeature f) const { + return (f == kSupportsLoadingDuringRuntime) || (f == kSupportsSavingDuringRuntime) + || (f == kSupportsRTL); +} + +Common::Error CGE2Engine::run() { + syncSoundSettings(); + initGraphics(kScrWidth, kScrHeight, false); + + init(); + cge2_main(); + deinit(); + + ConfMan.setBool("subtitles", _sayCap); + ConfMan.setBool("speech_mute", !_sayVox); + ConfMan.flushToDisk(); + + return Common::kNoError; +} + +} // End of namespace CGE2 diff --git a/engines/cge2/cge2.h b/engines/cge2/cge2.h new file mode 100644 index 0000000000..4fc19a822d --- /dev/null +++ b/engines/cge2/cge2.h @@ -0,0 +1,338 @@ +/* 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. + * + */ + +/* + * This code is based on original Sfinx source code + * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon + */ + +#ifndef CGE2_H +#define CGE2_H + +#include "common/random.h" +#include "common/savefile.h" +#include "common/serializer.h" +#include "engines/engine.h" +#include "engines/advancedDetector.h" +#include "common/system.h" +#include "cge2/fileio.h" +#include "cge2/console.h" +#include "audio/mixer.h" + +namespace CGE2 { + +class Vga; +class Sprite; +class MusicPlayer; +class Fx; +class Sound; +class Text; +struct HeroTab; +class FXP; +class V3D; +class V2D; +struct Dac; +class Spare; +class CommandHandler; +class InfoLine; +class Mouse; +class Keyboard; +class Talk; +class Hero; +class Bitmap; +class System; +class EventManager; +class Font; +class Map; +struct SavegameHeader; + +#define kScrWidth 320 +#define kScrHeight 240 +#define kScrDepth 480 +#define kPanHeight 40 +#define kWorldHeight (kScrHeight - kPanHeight) +#define kMaxFile 128 +#define kPathMax 128 +#define kDimMax 8 +#define kWayMax 10 +#define kPocketMax 4 +#define kSceneMax 100 +#define kMaxPoint 4 +#define kInfoX 160 +#define kInfoY -11 +#define kInfoW 180 +#define kPocketsWidth 59 +#define kLineMax 512 + +#define kIntroExt ".I80" +#define kTabName "CGE.TAB" +#define kPocketFull 170 +#define kGameFrameDelay (750 / 50) +#define kGameTickDelay (750 / 62) + +#define kMusicRef 122 +#define kPowerRef 123 +#define kDvolRef 124 +#define kMvolRef 125 +#define kRef 126 +#define kBusyRef 127 +#define kCapRef 128 +#define kVoxRef 129 + +#define kOffUseCount 130 +#define kOffUseText 131 + +#define kSysTimeRate 6 // 12 Hz +#define kBlinkRate 4 // 3 Hz + +#define kQuitTitle 200 +#define kQuitText 201 +#define kNoQuitText 202 + +#define kSavegameVersion 1 +#define kSavegameStrSize 12 +#define kSavegameStr "SCUMMVM_CGE2" + +#define kColorNum 6 + +struct SavegameHeader { + uint8 version; + Common::String saveName; + Graphics::Surface *thumbnail; + int saveYear, saveMonth, saveDay; + int saveHour, saveMinutes; +}; + +enum ColorBank { kCBRel, kCBStd, kCBSay, kCBInf, kCBMnu, kCBWar }; + +// our engine debug channels +enum { + kCGE2DebugOpcode = 1 << 0 +}; + +enum CallbackType { + kNullCB = 0, kQGame, kXScene +}; + +enum Action { kNear, kMTake, kFTake, kActions }; + +typedef void (CGE2Engine::*NotifyFunctionType)(); + +class CGE2Engine : public Engine { +private: + uint32 _lastFrame, _lastTick; + void tick(); + + CGE2Console *_console; + void init(); + void deinit(); + + Common::String generateSaveName(int slot); + void writeSavegameHeader(Common::OutSaveFile *out, SavegameHeader &header); + void saveGame(int slotNumber, const Common::String &desc); + bool loadGame(int slotNumber); + void syncHeader(Common::Serializer &s); + void syncGame(Common::SeekableReadStream *readStream, Common::WriteStream *writeStream); + void resetGame(); +public: + CGE2Engine(OSystem *syst, const ADGameDescription *gameDescription); + virtual bool hasFeature(EngineFeature f) const; + virtual bool canSaveGameStateCurrently(); + virtual bool canLoadGameStateCurrently(); + virtual Common::Error saveGameState(int slot, const Common::String &desc); + virtual Common::Error loadGameState(int slot); + virtual Common::Error run(); + + static bool readSavegameHeader(Common::InSaveFile *in, SavegameHeader &header); + + GUI::Debugger *getDebugger() { + return _console; + } + + bool showTitle(const char *name); + void cge2_main(); + char *mergeExt(char *buf, const char *name, const char *ext); + void inf(const char *text, ColorBank col = kCBInf); + void movie(const char *ext); + void runGame(); + void loadHeroes(); + void loadScript(const char *fname, bool onlyToolbar = false); + Sprite *loadSprite(const char *fname, int ref, int scene, V3D &pos); + void badLab(const char *fn); + void sceneUp(int cav); + void sceneDown(); + void closePocket(); + void switchScene(int scene); + void storeHeroPos(); + void showBak(int ref); + void loadTab(); + int newRandom(int range); + void openPocket(); + void selectPocket(int n); + void busy(bool on); + void feedSnail(Sprite *spr, Action snq, Hero *hero); + int freePockets(int sx); + int findActivePocket(int ref); + void pocFul(); + void killText(); + void mainLoop(); + void handleFrame(); + Sprite *locate(int ref); + bool isHero(Sprite *spr); + void loadUser(); + void loadPos(); + void releasePocket(Sprite *spr); + void switchHero(int sex); + void offUse(); + void setAutoColors(); + bool cross(const V2D &a, const V2D &b, const V2D &c, const V2D &d); + bool contain(const V2D &a, const V2D &b, const V2D &p); + long det(const V2D &a, const V2D &b, const V2D &c); + int sgn(long n); + int mapCross(const V2D &a, const V2D &b); + Sprite *spriteAt(V2D pos); + void keyClick(); + void swapInPocket(Sprite *spr, Sprite *xspr); + void busyStep(); + + void optionTouch(int opt, uint16 mask); + void switchColorMode(); + void switchMusic(); + void quit(); + void setVolume(int idx, int cnt); + void checkVolumeSwitches(); + void switchCap(); + void switchVox(); + void switchSay(); + void initToolbar(); + void initVolumeSwitch(Sprite *volSwitch); + + void checkSounds(); + + void setEye(const V3D &e); + void setEye(const V2D& e2, int z = -kScrWidth); + void setEye(const char *s); + + int number(char *s); + char *token(char *s); + char *tail(char *s); + int takeEnum(const char **tab, const char *text); + ID ident(const char *s); + bool testBool(char *s); + + void snKill(Sprite *spr); + void snHide(Sprite *spr, int val); + void snMidi(int val); + void snSeq(Sprite *spr, int val); + void snRSeq(Sprite *spr, int val); + void snSend(Sprite *spr, int val); + void snSwap(Sprite *spr, int val); + void snCover(Sprite *spr, int val); + void snUncover(Sprite *spr, Sprite *spr2); + void snKeep(Sprite *spr, int val); + void snGive(Sprite *spr, int val); + void snGoto(Sprite *spr, int val); + void snPort(Sprite *spr, int port); + void snMouse(bool on); + void snNNext(Sprite *spr, Action act, int val); + void snRNNext(Sprite *spr, int val); + void snRMTNext(Sprite *spr, int val); + void snRFTNext(Sprite *spr, int val); + void snRmNear(Sprite *spr); + void snRmMTake(Sprite *spr); + void snRmFTake(Sprite *spr); + void snSetRef(Sprite *spr, int val); + void snFlash(bool on); + void snCycle(int cnt); + void snWalk(Sprite *spr, int val); + void snReach(Sprite *spr, int val); + void snSound(Sprite *spr, int wav, Audio::Mixer::SoundType soundType = Audio::Mixer::kSFXSoundType); + void snRoom(Sprite *spr, bool on); + void snGhost(Bitmap *bmp); + void snSay(Sprite *spr, int val); + + void hide1(Sprite *spr); + Sprite *expandSprite(Sprite *spr); + void qGame(); + void xScene(); + + const ADGameDescription *_gameDescription; + + Common::RandomSource _randomSource; + + bool _quitFlag; + Dac *_bitmapPalette; + int _startupMode; + int _now; + int _sex; + int _mouseTop; + bool _dark; + int _waitSeq; + int _waitRef; + + struct { + int *_wait; + int _ref[2]; + } _soundStat; + + bool _taken; + bool _endGame; + int _req; + NotifyFunctionType _midiNotify; + NotifyFunctionType _spriteNotify; + int _startGameSlot; + + bool _sayCap; + bool _sayVox; + int _oldMusicVolume; + int _oldSfxVolume; + bool _music; + bool _sfx; + + ResourceManager *_resman; + Vga *_vga; + MusicPlayer *_midiPlayer; + Fx *_fx; + Sound *_sound; + Text *_text; + HeroTab *_heroTab[2]; + V3D *_eye; + V3D *_eyeTab[kSceneMax]; + Spare *_spare; + CommandHandler *_commandHandler; + CommandHandler *_commandHandlerTurbo; + Font *_font; + InfoLine *_infoLine; + Mouse *_mouse; + Keyboard *_keyboard; + Talk *_talk; + V3D *_point[kMaxPoint]; + System *_sys; + Sprite *_busyPtr; + Sprite *_vol[2]; + EventManager *_eventManager; + Map *_map; +}; + +} // End of namespace CGE2 + +#endif // CGE2_H diff --git a/engines/cge2/cge2_main.cpp b/engines/cge2/cge2_main.cpp new file mode 100644 index 0000000000..7c4a111d1f --- /dev/null +++ b/engines/cge2/cge2_main.cpp @@ -0,0 +1,957 @@ +/* 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. + * + */ + +/* + * This code is based on original Sfinx source code + * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon + */ + +#include "sound.h" +#include "cge2/cge2_main.h" +#include "cge2/cge2.h" +#include "cge2/vga13h.h" +#include "cge2/text.h" +#include "cge2/snail.h" +#include "cge2/hero.h" +#include "cge2/spare.h" +#include "cge2/map.h" + +namespace CGE2 { + +System::System(CGE2Engine *vm) : Sprite(vm), _vm(vm) { + _blinkCounter = 0; + _blinkSprite = nullptr; + tick(); +} + +void System::touch(uint16 mask, V2D pos, Common::KeyCode keyCode) { + if (mask & kEventKeyb) { + if (keyCode == Common::KEYCODE_ESCAPE) { + // The original was calling keyClick() + // The sound is uselessly annoying and noisy, so it has been removed + _vm->killText(); + if (_vm->_startupMode == 1) { + _vm->_commandHandler->addCommand(kCmdClear, -1, 0, nullptr); + return; + } + } + } else { + if (_vm->_startupMode) + return; + _vm->_infoLine->setText(nullptr); + + if (mask & kMouseLeftUp) { + if (pos.y >= 0) { // world + if (!_vm->_talk && pos.y < _vm->_mouseTop) + _vm->_heroTab[_vm->_sex]->_ptr->walkTo(pos); + } else { // panel + if (_vm->_commandHandler->idle()) { + int sex = pos.x < kPocketsWidth; + if (sex || pos.x >= kScrWidth - kPocketsWidth) { + _vm->switchHero(sex); + if (_vm->_sex == sex) { + int dx = kPocketsWidth >> 1, + dy = 1 - (kPanHeight >> 1); + Sprite *s; + if (!sex) + pos.x -= kScrWidth - kPocketsWidth; + dx -= pos.x; + dy -= pos.y; + if (dx * dx + dy * dy > 10 * 10) { + int n = 0; + if (1 - pos.y >= (kPanHeight >> 1)) + n += 2; + if (pos.x >= (kPocketsWidth >> 1)) + ++n; + s = _vm->_heroTab[_vm->_sex]->_pocket[n]; + if (_vm->_sys->_blinkSprite) + _vm->_sys->_blinkSprite->_flags._hide = false; + if (_vm->_sys->_blinkSprite == s) + _vm->_sys->_blinkSprite = nullptr; + else + _vm->_sys->_blinkSprite = s; + } + } + } + } + } + } + } +} + +void System::tick() { + _time = kSysTimeRate; + + if (_blinkCounter) + --_blinkCounter; + else { + if (_blinkSprite) + _blinkSprite->_flags._hide ^= 1; + _blinkCounter = kBlinkRate; + } +} + +int CGE2Engine::number(char *str) { + char *s = token(str); + if (s == nullptr) + error("Wrong input for CGE2Engine::number()"); + int r = atoi(s); + char *pp = strchr(s, ':'); + if (pp) + r = (r << 8) + atoi(pp + 1); + return r; +} + +char *CGE2Engine::token(char *s) { + return strtok(s, " =\t,;/()"); +} + +char *CGE2Engine::tail(char *s) { + if (s && (*s == '=')) + s++; + return s; +} + +int CGE2Engine::takeEnum(const char **tab, const char *text) { + if (text) { + for (const char **e = tab; *e; e++) { + if (scumm_stricmp(text, *e) == 0) + return e - tab; + } + } + return -1; +} + +ID CGE2Engine::ident(const char *s) { + return ID(takeEnum(EncryptedStream::kIdTab, s)); +} + +bool CGE2Engine::testBool(char *s) { + return number(s) != 0; +} + +void CGE2Engine::badLab(const char *fn) { + error("Misplaced label in %s!", fn); +} + +Sprite *CGE2Engine::loadSprite(const char *fname, int ref, int scene, V3D &pos) { + int shpcnt = 0; + int seqcnt = 0; + int cnt[kActions]; + + for (int i = 0; i < kActions; i++) + cnt[i] = 0; + + ID section = kIdPhase; + bool frnt = true; + bool east = false; + bool port = false; + bool tran = false; + Hero *h; + ID id; + + char tmpStr[kLineMax + 1]; + mergeExt(tmpStr, fname, kSprExt); + + if (_resman->exist(tmpStr)) { // sprite description file exist + EncryptedStream sprf(this, tmpStr); + if (sprf.err()) + error("Bad SPR [%s]", tmpStr); + + int label = kNoByte; + Common::String line; + + for (line = sprf.readLine(); !sprf.eos(); line = sprf.readLine()){ + if (line.empty()) + continue; + Common::strlcpy(tmpStr, line.c_str(), sizeof(tmpStr)); + + char *p = token(tmpStr); + if (*p == '@') { + if (label != kNoByte) + badLab(fname); + label = atoi(p + 1); + continue; + } + + id = ident(p); + switch (id) { + case kIdName: // will be taken in Expand routine + if (label != kNoByte) + badLab(fname); + break; + case kIdType: + if (label != kNoByte) + badLab(fname); + break; + case kIdNear: + case kIdMTake: + case kIdFTake: + case kIdPhase: + case kIdSeq: + if (label != kNoByte) + badLab(fname); + section = id; + break; + case kIdFront: + if (label != kNoByte) + badLab(fname); + p = token(nullptr); + frnt = testBool(p); + break; + case kIdEast: + if (label != kNoByte) + badLab(fname); + p = token(nullptr); + east = testBool(p); + break; + case kIdPortable: + if (label != kNoByte) + badLab(fname); + p = token(nullptr); + port = testBool(p); + break; + case kIdTransparent: + if (label != kNoByte) + badLab(fname); + p = token(nullptr); + tran = testBool(p); + break; + default: + if (id >= kIdNear) + break; + switch (section) { + case kIdNear: + case kIdMTake: + case kIdFTake: + if (_commandHandler->getComId(p) >= 0) + ++cnt[section]; + else + error("Bad line %d [%s]", sprf.getLineCount(), tmpStr); + break; + case kIdPhase: + if (label != kNoByte) + badLab(fname); + ++shpcnt; + break; + case kIdSeq: + if (label != kNoByte) + badLab(fname); + ++seqcnt; + break; + default: + break; + } + break; + } + label = kNoByte; + } + + if (!shpcnt) + error("No shapes - %s", fname); + } else // No sprite description: mono-shaped sprite with only .BMP file. + ++shpcnt; + + // Make sprite of chosen type: + Sprite *sprite = nullptr; + char c = *fname | 0x20; + if (c >= 'a' && c <= 'z' && fname[1] == '0' && fname[2] == '\0') { + h = new Hero(this); + h->gotoxyz(pos); + sprite = h; + } else { + sprite = new Sprite(this); + sprite->gotoxyz(pos); + } + + if (sprite) { + sprite->_ref = ref; + sprite->_scene = scene; + + sprite->_flags._frnt = frnt; + sprite->_flags._east = east; + sprite->_flags._port = port; + sprite->_flags._tran = tran; + sprite->_flags._kill = true; + + // Extract the filename, without the extension + Common::strlcpy(sprite->_file, fname, sizeof(sprite->_file)); + char *p = strchr(sprite->_file, '.'); + if (p) + *p = '\0'; + + sprite->_shpCnt = shpcnt; + sprite->_seqCnt = seqcnt; + + for (int i = 0; i < kActions; i++) + sprite->_actionCtrl[i]._cnt = cnt[i]; + } + + return sprite; +} + +void CGE2Engine::loadScript(const char *fname, bool onlyToolbar) { + EncryptedStream scrf(this, fname); + + if (scrf.err()) + return; + + bool ok = true; + int lcnt = 0; + + char tmpStr[kLineMax + 1]; + Common::String line; + + for (line = scrf.readLine(); !scrf.eos(); line = scrf.readLine()) { + if (line.empty()) + continue; + + lcnt++; + Common::strlcpy(tmpStr, line.c_str(), sizeof(tmpStr)); + + ok = false; // not OK if break + + V3D P; + + // sprite ident number + int SpI = number(tmpStr); + + if (onlyToolbar && SpI >= 141) + return; + + // sprite file name + char *SpN; + if ((SpN = token(nullptr)) == nullptr) + break; + + // sprite scene + int SpA = number(nullptr); + + // sprite column + P._x = number(nullptr); + + // sprite row + P._y = number(nullptr); + + // sprite Z pos + P._z = number(nullptr); + + // sprite life + bool BkG = number(nullptr) == 0; + + ok = true; // no break: OK + + Sprite *sprite = loadSprite(SpN, SpI, SpA, P); + if (sprite) { + if (BkG) + sprite->_flags._back = true; + + int n = _spare->count(); + if (_spare->locate(sprite->_ref) == nullptr) + _spare->dispose(sprite); + else + delete sprite; + + if (_spare->count() == n) + error("Duplicate reference! %s", SpN); + } + } + + if (!ok) + error("Bad INI line %d [%s]", scrf.getLineCount(), fname); +} + +void CGE2Engine::movie(const char *ext) { + assert(ext); + + if (_quitFlag) + return; + + char fn[12]; + snprintf(fn, 12, "CGE%s", ext); + + if (_resman->exist(fn)) { + int now = _now; + _now = atoi(ext + 2); + loadScript(fn); + sceneUp(_now); + _keyboard->setClient(_sys); + + while (!_commandHandler->idle() && !_quitFlag) + mainLoop(); + + _keyboard->setClient(nullptr); + _commandHandler->addCommand(kCmdClear, -1, 0, nullptr); + _commandHandlerTurbo->addCommand(kCmdClear, -1, 0, nullptr); + _spare->clear(); + _vga->_showQ->clear(); + _now = now; + } +} + +void CGE2Engine::sceneUp(int cav) { + _now = cav; + int bakRef = _now << 8; + if (_music) + _midiPlayer->loadMidi(bakRef); + showBak(bakRef); + *_eye = *(_eyeTab[_now]); + _mouseTop = V2D(this, V3D(0, 1, kScrDepth)).y; + _map->load(_now); + _spare->takeScene(_now); + openPocket(); + + for (int i = 0; i < 2; i++) { + Hero *h = _heroTab[i]->_ptr; + if (h && h->_scene == _now) { + V2D p = *_heroTab[i]->_posTab[_now]; + h->gotoxyz(V3D(p.x, 0, p.y)); + h->clrHide(); + _vga->_showQ->insert(h); + h->park(); + h->setCurrent(); + h->setContact(); + } + } + + _sound->stop(); + _fx->clear(); + + selectPocket(-1); + _infoLine->setText(nullptr); + busy(false); + + if (!_dark) + _vga->sunset(); + + _vga->show(); + _vga->copyPage(1, 0); + _vga->show(); + _vga->sunrise(_vga->_sysPal); + + _dark = false; + + if (!_startupMode) + _mouse->on(); + + feedSnail(_vga->_showQ->locate(bakRef + 255), kNear, _heroTab[_sex]->_ptr); +} + +void CGE2Engine::sceneDown() { + busy(true); + _soundStat._wait = nullptr; // unlock snail + Sprite *spr = _vga->_showQ->locate((_now << 8) | 254); + + if (spr) + feedSnail(spr, kNear, _heroTab[_sex]->_ptr); + + while (!(_commandHandler->idle() && _commandHandlerTurbo->idle())) { + _commandHandlerTurbo->runCommand(); + _commandHandler->runCommand(); + } + + closePocket(); + for (int i = 0; i < 2; i++) + _spare->update(_vga->_showQ->remove(_heroTab[i]->_ptr)); + _spare->dispose(); +} + +void CGE2Engine::switchScene(int scene) { + if (scene == _now) + return; + + _req = scene; + storeHeroPos(); + *(_eyeTab[_now]) = *_eye; + + if (scene < 0) + _commandHandler->addCallback(kCmdExec, -1, 0, kQGame); // quit game + else { + if (_heroTab[_sex]->_ptr->_scene == _now) { + _heroTab[_sex]->_ptr->setScene(scene); + if (_heroTab[!_sex]->_ptr->_scene == _now) + _heroTab[!_sex]->_ptr->setScene(scene); + } + _mouse->off(); + if (_heroTab[_sex]->_ptr) + _heroTab[_sex]->_ptr->park(); + killText(); + _commandHandler->addCallback(kCmdExec, -1, 0, kXScene); // switch scene + } +} + +void CGE2Engine::storeHeroPos() { + for (int i = 0; i < 2; i++) { + Hero *h = _heroTab[i]->_ptr; + if (h->_scene == _now) { + delete _heroTab[i]->_posTab[_now]; + V2D *temp = new V2D(this, h->_pos3D._x.trunc(), h->_pos3D._z.trunc()); + _heroTab[i]->_posTab[_now] = temp; + } + } +} + +void CGE2Engine::showBak(int ref) { + Sprite *spr = _spare->locate(ref); + if (spr != nullptr) { + _bitmapPalette = _vga->_sysPal; + spr->expand(); + _bitmapPalette = nullptr; + spr->show(2); + _vga->copyPage(1, 2); + } +} + +void CGE2Engine::mainLoop() { + if (_startupMode == 0) + checkSounds(); + + _vga->show(); + _commandHandlerTurbo->runCommand(); + _commandHandler->runCommand(); + + // Handle a delay between game frames + handleFrame(); + + // Handle any pending events + _eventManager->poll(); + + // Check shouldQuit() + _quitFlag = shouldQuit(); +} + +void CGE2Engine::checkSounds() { + _sound->checkSoundHandles(); + checkVolumeSwitches(); + _midiPlayer->syncVolume(); +} + +void CGE2Engine::handleFrame() { + // Game frame delay + uint32 millis = g_system->getMillis(); + while (!_quitFlag && (millis < (_lastFrame + kGameFrameDelay))) { + // Handle any pending events + _eventManager->poll(); + + if (millis >= (_lastTick + kGameTickDelay)) { + // Dispatch the tick to any active objects + tick(); + _lastTick = millis; + } + + // Slight delay + g_system->delayMillis(5); + millis = g_system->getMillis(); + } + _lastFrame = millis; + + if (millis >= (_lastTick + kGameTickDelay)) { + // Dispatch the tick to any active objects + tick(); + _lastTick = millis; + } +} + +Sprite *CGE2Engine::locate(int ref) { + _taken = false; + Sprite *spr = _vga->_showQ->locate(ref); + if (!spr) { + spr = _spare->locate(ref); + if (spr) + _taken = true; + } + return spr; +} + +bool CGE2Engine::isHero(Sprite *spr) { + return spr && spr->_ref / 10 == 14; +} + +void CGE2Engine::tick() { + // system pseudo-sprite + if (_sys && _sys->_time && (--_sys->_time == 0)) + _sys->tick(); + + for (Sprite *spr = _vga->_showQ->first(); spr; spr = spr->_next) { + if (spr->_time && (--spr->_time == 0)) + spr->tick(); + + if (_waitRef && (_waitRef == spr->_ref) && spr->seqTest(_waitSeq)) + _waitRef = 0; + } + + _mouse->tick(); +} + +void CGE2Engine::busy(bool on) { + if (on) { + _spriteNotify = _midiNotify = &CGE2::CGE2Engine::busyStep; + busyStep(); + } else { + if (_busyPtr) + _busyPtr->step(0); + _spriteNotify = _midiNotify = nullptr; + } +} + +void CGE2Engine::busyStep() { + if (_busyPtr) { + _busyPtr->step((_busyPtr->_seqPtr) ? -1 : 1); + _busyPtr->show(0); + } +} + +void CGE2Engine::runGame() { + if (_quitFlag) + return; + + loadUser(); + sceneUp(_now); + initToolbar(); + + // main loop + while (!_endGame && !_quitFlag) + mainLoop(); + + // If leaving the game (close window, return to launcher, etc. + // - except finishing the game), explicitly save it's state: + if (!_endGame && canSaveGameStateCurrently()) + qGame(); + + _keyboard->setClient(nullptr); + _commandHandler->addCommand(kCmdClear, -1, 0, nullptr); + _commandHandlerTurbo->addCommand(kCmdClear, -1, 0, nullptr); + _mouse->off(); +} + +void CGE2Engine::loadUser() { + loadPos(); + + if (_startGameSlot != -1) + loadGame(_startGameSlot); + else { + loadScript("CGE.INI"); + loadHeroes(); + } +} + +void CGE2Engine::loadHeroes() { // Original name: loadGame() + // load sprites & pocket + + Sprite *s; + Hero *h = nullptr; + + // initialize Andzia/Anna + s = _spare->take(142); + if (s) { + h = new Hero(this); + *(Sprite*)h = *s; + delete s; + h->expand(); + _spare->update(h); + } + _heroTab[0]->_ptr = h; + s = _spare->locate(152); + _vga->_showQ->insert(s); + _heroTab[0]->_face = s; + + // initialize Wacek/Vincent + s = _spare->take(141); + if (s) { + h = new Hero(this); + *(Sprite*)h = *s; + delete s; + h->expand(); + _spare->update(h); + } + _heroTab[1]->_ptr = h; + s = _spare->locate(151); + _vga->_showQ->insert(s); + _heroTab[1]->_face = s; + + //--- start! + switchHero(_sex); +} + +void CGE2Engine::loadPos() { + if (_resman->exist("CGE.HXY")) { + for (int cav = 0; cav < kSceneMax; cav++) + _heroTab[1]->_posTab[cav] = new V2D(this, 180, 10); + + EncryptedStream file(this, "CGE.HXY"); + + for (int cav = 0; cav < kSceneMax; cav++) { + _heroTab[0]->_posTab[cav] = new V2D(this); + _heroTab[0]->_posTab[cav]->x = file.readSint16LE(); + _heroTab[0]->_posTab[cav]->y = file.readSint16LE(); + } + + for (int cav = 0; cav < 41; cav++) { // (564 - 400) / 4 = 41 + _heroTab[1]->_posTab[cav]->x = file.readSint16LE(); + _heroTab[1]->_posTab[cav]->y = file.readSint16LE(); + } + } else + error("Missing file: CGE.HXY"); +} + +void CGE2Engine::loadTab() { + setEye(_text->getText(240)); + for (int i = 0; i < kSceneMax; i++) + *(_eyeTab[i]) = *_eye; + + if (_resman->exist(kTabName)) { + EncryptedStream f(this, kTabName); + uint32 v; + + for (int i = 0; i < kSceneMax; i++) { + v = f.readUint32LE(); + _eyeTab[i]->_x = FXP(v >> 8, static_cast<int>((int8)(v & 0xff))); + + v = f.readUint32LE(); + _eyeTab[i]->_y = FXP(v >> 8, static_cast<int>((int8)(v & 0xff))); + + v = f.readUint32LE(); + _eyeTab[i]->_z = FXP(v >> 8, static_cast<int>((int8)(v & 0xff))); + } + } +} + +void CGE2Engine::cge2_main() { + loadTab(); + + if (_startGameSlot != -1) { + // Starting up a savegame from the launcher + runGame(); + return; + } + + if (showTitle("WELCOME")) { + movie(kIntroExt); + + if (_text->getText(255) != nullptr) { + runGame(); + _startupMode = 2; + } + + _vga->sunset(); + } else + _vga->sunset(); +} + +char *CGE2Engine::mergeExt(char *buf, const char *name, const char *ext) { + strcpy(buf, name); + char *dot = strrchr(buf, '.'); + if (!dot) + strcat(buf, ext); + + return buf; +} + +void CGE2Engine::setEye(const V3D &e) { + *_eye = e; +} + +void CGE2Engine::setEye(const V2D& e2, int z) { + _eye->_x = e2.x; + _eye->_y = e2.y; + _eye->_z = z; +} + +void CGE2Engine::setEye(const char *s) { + char tempStr[kLineMax]; + strcpy(tempStr, s); + _eye->_x = atoi(token(tempStr)); + _eye->_y = atoi(token(nullptr)); + _eye->_z = atoi(token(nullptr)); +} + +int CGE2Engine::newRandom(int range) { + if (!range) + return 0; + + return _randomSource.getRandomNumber(range - 1); +} + +bool CGE2Engine::showTitle(const char *name) { + if (_quitFlag) + return false; + + _bitmapPalette = _vga->_sysPal; + BitmapPtr LB = new Bitmap[1]; + LB[0] = Bitmap(this, name); + _bitmapPalette = nullptr; + + Sprite D(this, LB, 1); + D._flags._kill = true; + D.gotoxyz(kScrWidth >> 1, -(kPanHeight >> 1)); + + _vga->sunset(); + D.show(2); + _vga->copyPage(1, 2); + _vga->copyPage(0, 1); + _vga->sunrise(_vga->_sysPal); + _vga->update(); + + g_system->delayMillis(2500); + + return true; +} + +void CGE2Engine::killText() { + if (!_talk) + return; + + _commandHandlerTurbo->addCommand(kCmdKill, -1, 0, _talk); + _talk = nullptr; +} + +void CGE2Engine::switchHero(int sex) { + if (sex != _sex) { + int scene = _heroTab[sex]->_ptr->_scene; + if (_sys->_blinkSprite) { + _sys->_blinkSprite->_flags._hide = false; + _sys->_blinkSprite = nullptr; + } + + if (scene >= 0) { + _commandHandler->addCommand(kCmdSeq, -1, 2, _heroTab[_sex]->_face); + _sex ^= 1; + switchScene(scene); + } + } + Sprite *face = _heroTab[_sex]->_face; + if (face->_seqPtr == 0) + _commandHandler->addCommand(kCmdSeq, -1, 1, face); +} + +void Sprite::touch(uint16 mask, V2D pos, Common::KeyCode keyCode) { + if ((mask & kEventAttn) != 0) + return; + + if (!_vm->_startupMode) + _vm->_infoLine->setText(name()); + + if (_ref < 0) + return; // cannot access system sprites + + if (_ref / 10 == 12) { + _vm->optionTouch(_ref % 10, mask); + return; + } + + if ((mask & kMouseLeftUp) && _vm->_commandHandler->idle()) { + if (_vm->isHero(this) && !_vm->_sys->_blinkSprite) { + _vm->switchHero((this == _vm->_heroTab[1]->_ptr) ? 1 : 0); + } else if (_flags._kept) { // sprite in pocket + for (int sex = 0; sex < 2; ++sex) { + for (int p = 0; p < kPocketMax; ++p) { + if (_vm->_heroTab[sex]->_pocket[p] == this) { + _vm->switchHero(sex); + if (_vm->_sex == sex) { + if (_vm->_sys->_blinkSprite) + _vm->_sys->_blinkSprite->_flags._hide = false; + + if (_vm->_sys->_blinkSprite == this) + _vm->_sys->_blinkSprite = nullptr; + else + _vm->_sys->_blinkSprite = this; + } + } + } + } + } else { // sprite NOT in pocket + Hero *h = _vm->_heroTab[_vm->_sex]->_ptr; + if (!_vm->_talk) { + // the "+3" is a hack used to work around a script issue in scene 5 + if ((_ref & 0xFF) < 200 && h->distance(this) > (h->_maxDist << 1) + 3) + h->walkTo(this); + else if (_vm->_sys->_blinkSprite) { + if (works(_vm->_sys->_blinkSprite)) { + _vm->feedSnail(_vm->_sys->_blinkSprite, (_vm->_sex) ? kMTake : kFTake, _vm->_heroTab[_vm->_sex]->_ptr); + _vm->_sys->_blinkSprite->_flags._hide = false; + _vm->_sys->_blinkSprite = nullptr; + } else + _vm->offUse(); + + _vm->selectPocket(-1); + // else, no pocket sprite selected + } else if (_flags._port) { // portable + if (_vm->findActivePocket(-1) < 0) + _vm->pocFul(); + else { + _vm->_commandHandler->addCommand(kCmdReach, -2, _ref, nullptr); + _vm->_commandHandler->addCommand(kCmdKeep, -1, -1, this); + _flags._port = false; + } + } else { // non-portable + Action a = h->action(); + if (_actionCtrl[a]._cnt) { + CommandHandler::Command *cmdList = snList(a); + if (cmdList[_actionCtrl[a]._ptr]._commandType == kCmdNext) + _vm->offUse(); + else + _vm->feedSnail(this, a, h); + } else + _vm->offUse(); + } + } + } + } +} + +void CGE2Engine::keyClick() { + _commandHandlerTurbo->addCommand(kCmdSound, -1, 5, nullptr); +} + +void CGE2Engine::offUse() { + int seq = 0; + int offUseCount = atoi(_text->getText(kOffUseCount)); + + // This fixes the issue of empty speech bubbles in the original. + // Now we only let this cycle pass if it randoms a valid value for getText(). + int txt = 0; + do { + txt = kOffUseText + _sex * offUseCount + newRandom(offUseCount); + } while (_text->getText(txt) == nullptr); + + Hero *h = _heroTab[_sex]->_ptr; + h->park(); + _commandHandler->addCommand(kCmdWait, -1, -1, h); + _commandHandler->addCommand(kCmdSeq, -1, seq, h); + if (!_sayVox) + _commandHandler->addCommand(kCmdSound, -1, 6 + _sex, h); + _commandHandler->addCommand(kCmdWait, -1, -1, h); + _commandHandler->addCommand(kCmdSay, -1, txt, h); +} + +Sprite *CGE2Engine::spriteAt(V2D pos) { + Sprite *spr; + + for (spr = _vga->_showQ->last(); spr; spr = spr->_prev) { + if (!spr->_flags._hide && !spr->_flags._tran && (spr->getShp()->solidAt(pos - spr->_pos2D))) + break; + } + + return spr; +} + +} // End of namespace CGE2 diff --git a/engines/cge2/cge2_main.h b/engines/cge2/cge2_main.h new file mode 100644 index 0000000000..afbe7b3d23 --- /dev/null +++ b/engines/cge2/cge2_main.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. + * + */ + +/* + * This code is based on original Sfinx source code + * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon + */ + +#ifndef CGE2_MAIN_H +#define CGE2_MAIN_H + +#include "cge2/events.h" + +namespace CGE2 { + +#define kShowScummVMVersion 15 + +class System : public Sprite { +public: + int _funDel; + int _blinkCounter; + Sprite *_blinkSprite; + + System(CGE2Engine *vm); + + virtual void touch(uint16 mask, V2D pos, Common::KeyCode keyCode); + void tick(); +private: + CGE2Engine *_vm; +}; + +} // End of namespace CGE2 + +#endif // CGE2_MAIN_H diff --git a/engines/cge2/configure.engine b/engines/cge2/configure.engine new file mode 100644 index 0000000000..6ccbfee088 --- /dev/null +++ b/engines/cge2/configure.engine @@ -0,0 +1,3 @@ +# This file is included from the main "configure" script +# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] +add_engine cge2 "CGE2" no diff --git a/engines/cge2/console.cpp b/engines/cge2/console.cpp new file mode 100644 index 0000000000..c67c7ab788 --- /dev/null +++ b/engines/cge2/console.cpp @@ -0,0 +1,33 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "cge2/console.h" + +namespace CGE2 { + +CGE2Console::CGE2Console(CGE2Engine *vm) : GUI::Debugger() { +} + +CGE2Console::~CGE2Console() { +} + +} // End of namespace CGE diff --git a/engines/cge2/console.h b/engines/cge2/console.h new file mode 100644 index 0000000000..15956bf728 --- /dev/null +++ b/engines/cge2/console.h @@ -0,0 +1,40 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef CGE2_CONSOLE_H +#define CGE2_CONSOLE_H + +#include "gui/debugger.h" + +namespace CGE2 { + +class CGE2Engine; + +class CGE2Console : public GUI::Debugger { +public: + CGE2Console(CGE2Engine *vm); + virtual ~CGE2Console(); +}; + +} // End of namespace CGE + +#endif // CGE2_CONSOLE_H diff --git a/engines/cge2/detection.cpp b/engines/cge2/detection.cpp new file mode 100644 index 0000000000..c032e2348f --- /dev/null +++ b/engines/cge2/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. +* +*/ + +/* + * This code is based on original Sfinx source code + * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon + */ + +#include "cge2/cge2.h" +#include "engines/advancedDetector.h" +#include "common/translation.h" +#include "graphics/surface.h" + +namespace CGE2 { + +#define GAMEOPTION_COLOR_BLIND_DEFAULT_OFF GUIO_GAMEOPTIONS1 + +static const PlainGameDescriptor CGE2Games[] = { + { "sfinx", "Sfinx" }, + { 0, 0 } +}; + +static const ADGameDescription gameDescriptions[] = { + { + "sfinx", "Sfinx Freeware", + { + { "vol.cat", 0, "21197b287d397c53261b6616bf0dd880", 129024 }, + { "vol.dat", 0, "de14291869a8eb7c2732ab783c7542ef", 34180844 }, + AD_LISTEND + }, + Common::PL_POL, Common::kPlatformDOS, ADGF_NO_FLAGS, GUIO1(GAMEOPTION_COLOR_BLIND_DEFAULT_OFF) + }, + AD_TABLE_END_MARKER +}; + +static const ADExtraGuiOptionsMap optionsList[] = { + { + GAMEOPTION_COLOR_BLIND_DEFAULT_OFF, + { + _s("Color Blind Mode"), + _s("Enable Color Blind Mode by default"), + "enable_color_blind", + false + } + }, + + AD_EXTRA_GUI_OPTIONS_TERMINATOR +}; + +class CGE2MetaEngine : public AdvancedMetaEngine { +public: + CGE2MetaEngine() : AdvancedMetaEngine(gameDescriptions, sizeof(ADGameDescription), CGE2Games, optionsList) { + _singleid = "sfinx"; + } + + virtual const char *getName() const { + return "CGE2"; + } + + virtual const char *getOriginalCopyright() const { + return "Sfinx (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon"; + } + + virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const; + virtual bool hasFeature(MetaEngineFeature f) const; + virtual int getMaximumSaveSlot() const; + virtual SaveStateList listSaves(const char *target) const; + SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const; + virtual void removeSaveState(const char *target, int slot) const; + + const ADGameDescription *fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const; +}; + +bool CGE2MetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const { + if (desc) + *engine = new CGE2::CGE2Engine(syst, desc); + + return desc != 0; +} + +bool CGE2MetaEngine::hasFeature(MetaEngineFeature f) const { + return + (f == kSupportsDeleteSave) || + (f == kSavesSupportMetaInfo) || + (f == kSavesSupportThumbnail) || + (f == kSavesSupportCreationDate) || + (f == kSupportsListSaves) || + (f == kSupportsLoadingDuringStartup); +} + +const ADGameDescription *CGE2MetaEngine::fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const { + static ADGameDescription desc; + + for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) { + if (file->isDirectory()) + continue; + + if (file->getName().equalsIgnoreCase("lang.eng")) { + Common::File dataFile; + if (!dataFile.open(*file)) + continue; + + desc.gameid = "sfinx"; + desc.extra = "Sfinx English Alpha v0.1"; + desc.language = Common::EN_ANY; + desc.platform = Common::kPlatformDOS; + desc.flags = ADGF_NO_FLAGS; + desc.guioptions = GUIO1(GAMEOPTION_COLOR_BLIND_DEFAULT_OFF); + + return (const ADGameDescription *)&desc; + } + } + return 0; +} + +int CGE2MetaEngine::getMaximumSaveSlot() const { + return 99; +} + +SaveStateList CGE2MetaEngine::listSaves(const char *target) const { + Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); + Common::StringArray filenames; + Common::String pattern = target; + pattern += ".???"; + + filenames = saveFileMan->listSavefiles(pattern); + sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) + + SaveStateList saveList; + for (Common::StringArray::const_iterator filename = filenames.begin(); filename != filenames.end(); ++filename) { + // Obtain the last 3 digits of the filename, since they correspond to the save slot + int slotNum = atoi(filename->c_str() + filename->size() - 3); + + if (slotNum >= 0 && slotNum <= 99) { + + Common::InSaveFile *file = saveFileMan->openForLoading(*filename); + if (file) { + CGE2::SavegameHeader header; + + // Check to see if it's a ScummVM savegame or not + char buffer[kSavegameStrSize + 1]; + file->read(buffer, kSavegameStrSize + 1); + + if (!strncmp(buffer, kSavegameStr, kSavegameStrSize + 1)) { + // Valid savegame + if (CGE2::CGE2Engine::readSavegameHeader(file, header)) { + saveList.push_back(SaveStateDescriptor(slotNum, header.saveName)); + if (header.thumbnail) { + header.thumbnail->free(); + delete header.thumbnail; + } + } + } else { + // Must be an original format savegame + saveList.push_back(SaveStateDescriptor(slotNum, "Unknown")); + } + + delete file; + } + } + } + + return saveList; +} + +SaveStateDescriptor CGE2MetaEngine::querySaveMetaInfos(const char *target, int slot) const { + Common::String fileName = Common::String::format("%s.%03d", target, slot); + Common::InSaveFile *f = g_system->getSavefileManager()->openForLoading(fileName); + + if (f) { + CGE2::SavegameHeader header; + + // Check to see if it's a ScummVM savegame or not + char buffer[kSavegameStrSize + 1]; + f->read(buffer, kSavegameStrSize + 1); + + bool hasHeader = !strncmp(buffer, kSavegameStr, kSavegameStrSize + 1) && + CGE2::CGE2Engine::readSavegameHeader(f, header); + delete f; + + if (!hasHeader) { + // Original savegame perhaps? + SaveStateDescriptor desc(slot, "Unknown"); + return desc; + } else { + // Create the return descriptor + SaveStateDescriptor desc(slot, header.saveName); + desc.setThumbnail(header.thumbnail); + desc.setSaveDate(header.saveYear, header.saveMonth, header.saveDay); + desc.setSaveTime(header.saveHour, header.saveMinutes); + + // Slot 0 is used for the 'automatic save on exit' save in Soltys, thus + // we prevent it from being deleted or overwritten by accident. + desc.setDeletableFlag(slot != 0); + desc.setWriteProtectedFlag(slot == 0); + + return desc; + } + } + + return SaveStateDescriptor(); +} + +void CGE2MetaEngine::removeSaveState(const char *target, int slot) const { + Common::String fileName = Common::String::format("%s.%03d", target, slot); + g_system->getSavefileManager()->removeSavefile(fileName); +} + +} // End of namespace CGE2 + +#if PLUGIN_ENABLED_DYNAMIC(CGE2) + REGISTER_PLUGIN_DYNAMIC(CGE2, PLUGIN_TYPE_ENGINE, CGE2::CGE2MetaEngine); +#else + REGISTER_PLUGIN_STATIC(CGE2, PLUGIN_TYPE_ENGINE, CGE2::CGE2MetaEngine); +#endif diff --git a/engines/cge2/events.cpp b/engines/cge2/events.cpp new file mode 100644 index 0000000000..ed1ec66bb1 --- /dev/null +++ b/engines/cge2/events.cpp @@ -0,0 +1,293 @@ +/* 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. + * + */ + +/* + * This code is based on original Sfinx source code + * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon + */ + +#include "gui/saveload.h" +#include "gui/about.h" +#include "gui/message.h" +#include "common/config-manager.h" +#include "common/events.h" +#include "engines/advancedDetector.h" +#include "cge2/events.h" +#include "cge2/text.h" +#include "cge2/cge2_main.h" + +namespace CGE2 { + +/*----------------- KEYBOARD interface -----------------*/ + +Keyboard::Keyboard(CGE2Engine *vm) : _client(nullptr), _vm(vm) { +} + +Keyboard::~Keyboard() { +} + +Sprite *Keyboard::setClient(Sprite *spr) { + SWAP(_client, spr); + return spr; +} + +bool Keyboard::getKey(Common::Event &event) { + Common::KeyCode keycode = event.kbd.keycode; + + switch (keycode) { + case Common::KEYCODE_F1: + if (event.type == Common::EVENT_KEYUP) + return false; + // Display ScummVM version and translation strings + for (int i = 0; i < 3; i++) + _vm->_commandHandler->addCommand(kCmdInf, 1, kShowScummVMVersion + i, NULL); + return false; + case Common::KEYCODE_F5: + if (_vm->canSaveGameStateCurrently()) { + GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser("Save game:", "Save", true); + int16 savegameId = dialog->runModalWithCurrentTarget(); + Common::String savegameDescription = dialog->getResultString(); + delete dialog; + + if (savegameId != -1) + _vm->saveGameState(savegameId, savegameDescription); + } + return false; + case Common::KEYCODE_F7: + if (_vm->canLoadGameStateCurrently()) { + GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser("Restore game:", "Restore", false); + int16 savegameId = dialog->runModalWithCurrentTarget(); + delete dialog; + + if (savegameId != -1) + _vm->loadGameState(savegameId); + } + return false; + case Common::KEYCODE_d: + if (event.kbd.flags & Common::KBD_CTRL) { + // Start the debugger + _vm->getDebugger()->attach(); + _vm->getDebugger()->onFrame(); + return false; + } + break; + case Common::KEYCODE_x: + if (event.kbd.flags & Common::KBD_ALT) { + _vm->quit(); + return false; + } + break; + case Common::KEYCODE_F10: + if (_vm->_commandHandler->idle()) + _vm->switchScene(-1); // Exits the game. + return false; + default: + break; + } + + return true; +} + +void Keyboard::newKeyboard(Common::Event &event) { + if (!getKey(event)) + return; + + if ((event.type == Common::EVENT_KEYDOWN) && _client) { + CGE2Event &evt = _vm->_eventManager->getNextEvent(); + evt._x = 0; + evt._y = 0; + evt._keyCode = event.kbd.keycode; // Keycode + evt._mask = kEventKeyb; // Event mask + evt._spritePtr = _client; // Sprite pointer + } +} + +/*----------------- MOUSE interface -----------------*/ + +Mouse::Mouse(CGE2Engine *vm) : Sprite(vm), _busy(nullptr), _hold(nullptr), _hx(0), _point(vm), _vm(vm) { + _hold = nullptr; + _hx = 0; + _hy = 0; + _exist = true; + _buttons = 0; + _busy = nullptr; + _active = false; + _flags._kill = false; + + setSeq(_stdSeq8); + + BitmapPtr MC = new Bitmap[2]; + MC[0] = Bitmap(_vm, "MOUSE"); + MC[1] = Bitmap(_vm, "DUMMY"); + setShapeList(MC, 2); + + step(1); + on(); + off(); +} + +Mouse::~Mouse() { + off(); +} + +void Mouse::on() { + if (_seqPtr && _exist) { + _active = true; + step(0); + if (_busy) + _busy->step(0); + } +} + +void Mouse::off() { + if (_seqPtr == 0) { + if (_exist) + _active = false; + + step(1); + if (_busy) + _busy->step(1); + } +} + +void Mouse::newMouse(Common::Event &event) { + if (!_active) + return; + + CGE2Event &evt = _vm->_eventManager->getNextEvent(); + evt._x = event.mouse.x; + evt._y = event.mouse.y; + evt._keyCode = Common::KEYCODE_INVALID; + evt._spritePtr = _vm->spriteAt(V2D(_vm, evt._x, evt._y)); + + switch (event.type) { + case Common::EVENT_MOUSEMOVE: + evt._mask = kMouseRoll; + break; + case Common::EVENT_LBUTTONDOWN: + evt._mask = kMouseLeftDown; + _buttons |= 1; + break; + case Common::EVENT_LBUTTONUP: + evt._mask = kMouseLeftUp; + _buttons &= ~1; + break; + case Common::EVENT_RBUTTONDOWN: + evt._mask = kMouseRightDown; + _buttons |= 2; + break; + case Common::EVENT_RBUTTONUP: + evt._mask = kMouseRightUp; + _buttons &= ~2; + break; + default: + break; + } +} + +/*----------------- EventManager interface -----------------*/ + +EventManager::EventManager(CGE2Engine *vm) : _vm(vm) { + _eventQueueHead = 0; + _eventQueueTail = 0; + memset(&_eventQueue, 0, kEventMax * sizeof(CGE2Event)); + memset(&_event, 0, sizeof(Common::Event)); +} + +void EventManager::poll() { + while (g_system->getEventManager()->pollEvent(_event)) { + _event.mouse.y = kWorldHeight - _event.mouse.y; + switch (_event.type) { + case Common::EVENT_KEYDOWN: + case Common::EVENT_KEYUP: + // Handle keyboard events + _vm->_keyboard->newKeyboard(_event); + handleEvents(); + break; + case Common::EVENT_MOUSEMOVE: + case Common::EVENT_LBUTTONDOWN: + case Common::EVENT_LBUTTONUP: + case Common::EVENT_RBUTTONDOWN: + case Common::EVENT_RBUTTONUP: + // Handle mouse events + _vm->_mouse->newMouse(_event); + handleEvents(); + break; + default: + break; + } + } +} + +void EventManager::handleEvents() { + while (_eventQueueTail != _eventQueueHead) { + CGE2Event e = _eventQueue[_eventQueueTail]; + _vm->_mouse->_point = V2D(_vm, e._x, e._y); + if (e._mask) { + if (e._mask & kMouseMask) { + e._spritePtr = _vm->spriteAt(_vm->_mouse->_point); + e._x += (_vm->_mouse->_siz.x >> 1); + e._y -= _vm->_mouse->_siz.y; + if (_vm->_mouse->_hold && (e._spritePtr != _vm->_mouse->_hold)) { + _vm->_mouse->_hold->touch(e._mask | kEventAttn, + V2D(_vm, e._x - _vm->_mouse->_hold->_pos2D.x, e._y - _vm->_mouse->_hold->_pos2D.y), e._keyCode); + } + // update mouse cursor position + if (e._mask & kMouseRoll) + _vm->_mouse->gotoxyz(V2D(_vm, e._x, e._y)); + } + + // activate current touched SPRITE + if (e._spritePtr) { + if (e._mask & kEventKeyb) + e._spritePtr->touch(e._mask, _vm->_mouse->_point, e._keyCode); + else + e._spritePtr->touch(e._mask, _vm->_mouse->_point - e._spritePtr->_pos2D, e._keyCode); + } else if (_vm->_sys) + _vm->_sys->touch(e._mask, _vm->_mouse->_point, e._keyCode); + + // discard Text if button released + if (e._mask & (kMouseLeftUp | kMouseRightUp)) + _vm->killText(); + } + _eventQueueTail = (_eventQueueTail + 1) % kEventMax; + } +} + +void EventManager::clearEvent(Sprite *spr) { + if (spr) { + for (uint16 e = _eventQueueTail; e != _eventQueueHead; e = (e + 1) % kEventMax) { + if (_eventQueue[e]._spritePtr == spr) + _eventQueue[e]._mask = 0; + } + } else + _eventQueueTail = _eventQueueHead; +} + +CGE2Event &EventManager::getNextEvent() { + CGE2Event &evt = _eventQueue[_eventQueueHead]; + _eventQueueHead = (_eventQueueHead + 1) % kEventMax; + + return evt; +} + +} // End of namespace CGE2 diff --git a/engines/cge2/events.h b/engines/cge2/events.h new file mode 100644 index 0000000000..d1aaca2ded --- /dev/null +++ b/engines/cge2/events.h @@ -0,0 +1,116 @@ +/* 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. + * + */ + +/* + * This code is based on original Sfinx source code + * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon + */ + +#ifndef CGE2_EVENTS_H +#define CGE2_EVENTS_H + +#include "common/events.h" +#include "cge2/talk.h" +#include "cge2/vga13h.h" + +namespace CGE2 { + +/*----------------- KEYBOARD interface -----------------*/ + +#define kEventMax 256 + +enum EventMask { + kMouseRoll = 1 << 0, + kMouseLeftDown = 1 << 1, + kMouseLeftUp = 1 << 2, + kMouseRightDown = 1 << 3, + kMouseRightUp = 1 << 4, + kEventAttn = 1 << 5, + kMouseMask = (kMouseRoll | kMouseLeftDown | kMouseLeftUp | kMouseRightDown | kMouseRightUp), + kEventKeyb = 1 << 7 +}; + +class Keyboard { +private: + bool getKey(Common::Event &event); + CGE2Engine *_vm; +public: + Sprite *_client; + + void newKeyboard(Common::Event &event); + Sprite *setClient(Sprite *spr); + + Keyboard(CGE2Engine *vm); + ~Keyboard(); +}; + +/*----------------- MOUSE interface -----------------*/ + +struct CGE2Event { + uint16 _mask; + uint16 _x; + uint16 _y; + Common::KeyCode _keyCode; + Sprite *_spritePtr; +}; + +class Mouse : public Sprite { +public: + V2D _point; + Sprite *_hold; + bool _active; + int _hx; + int _hy; + bool _exist; + int _buttons; + Sprite *_busy; + Mouse(CGE2Engine *vm); + ~Mouse(); + void on(); + void off(); + void newMouse(Common::Event &event); +private: + CGE2Engine *_vm; +}; + +/*----------------- EventManager interface -----------------*/ + +class EventManager { +private: + CGE2Engine *_vm; + Common::Event _event; + CGE2Event _eventQueue[kEventMax]; + uint16 _eventQueueHead; + uint16 _eventQueueTail; + + void handleEvents(); +public: + EventManager(CGE2Engine *vm); + void poll(); + void clearEvent(Sprite *spr); + + CGE2Event &getNextEvent(); +}; + +} // End of namespace CGE + +#endif // #define CGE2_EVENTS_H diff --git a/engines/cge2/fileio.cpp b/engines/cge2/fileio.cpp new file mode 100644 index 0000000000..6f8009716b --- /dev/null +++ b/engines/cge2/fileio.cpp @@ -0,0 +1,272 @@ +/* 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. + * + */ + +/* + * This code is based on original Sfinx source code + * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon + */ + +#include "common/system.h" +#include "common/str.h" +#include "common/debug.h" +#include "common/debug-channels.h" +#include "common/memstream.h" +#include "cge2/cge2.h" +#include "cge2/fileio.h" + +namespace CGE2 { + +/*----------------------------------------------------------------------- + * BtPage + *-----------------------------------------------------------------------*/ +void BtPage::readBTree(Common::ReadStream &s) { + _header._count = s.readUint16LE(); + _header._down = s.readUint16LE(); + + if (_header._down == kBtValNone) { + // Leaf list + for (int i = 0; i < kBtLeafCount; ++i) { + s.read(_leaf[i]._key, kBtKeySize); + _leaf[i]._pos = s.readUint32LE(); + _leaf[i]._size = s.readUint32LE(); + } + } else { + // Root index + for (int i = 0; i < kBtInnerCount; ++i) { + s.read(_inner[i]._key, kBtKeySize); + _inner[i]._down = s.readUint16LE(); + } + } +} + +/*----------------------------------------------------------------------- + * ResourceManager + *-----------------------------------------------------------------------*/ +ResourceManager::ResourceManager() { + _datFile = new Common::File(); + _datFile->open(kDatName); + + _catFile = new Common::File(); + _catFile->open(kCatName); + + if (!_datFile->isOpen() || !_catFile->isOpen()) + error("Unable to open data files"); + + for (int i = 0; i < kBtLevel; i++) { + _buff[i]._page = new BtPage; + _buff[i]._pageNo = kBtValNone; + _buff[i]._index = -1; + assert(_buff[i]._page != nullptr); + } +} + +ResourceManager::~ResourceManager() { + _datFile->close(); + delete _datFile; + + _catFile->close(); + delete _catFile; + + for (int i = 0; i < kBtLevel; i++) + delete _buff[i]._page; +} + +void ResourceManager::xCrypt(byte *buf, uint16 length) { + byte *b = buf; + + for (uint16 i = 0; i < length; i++) + *b++ ^= kCryptSeed; +} + +bool ResourceManager::seek(int32 offs, int whence) { + return _datFile->seek(offs, whence); +} + +uint16 ResourceManager::read(byte *buf, uint16 length) { + if (!_datFile->isOpen()) + return 0; + + uint16 bytesRead = _datFile->read(buf, length); + if (!bytesRead) + error("Read %s - %d bytes", _datFile->getName(), length); + xCrypt(buf, length); + return bytesRead; +} + +BtPage *ResourceManager::getPage(int level, uint16 pageId) { + if (_buff[level]._pageNo != pageId) { + int32 pos = pageId * kBtSize; + _buff[level]._pageNo = pageId; + assert(_catFile->size() > pos); + // In the original, there was a check verifying if the + // purpose was to write a new file. This should only be + // to create a new file, thus it was removed. + _catFile->seek(pageId * kBtSize, SEEK_SET); + + // Read in the page + byte buffer[kBtSize]; + int bytesRead = catRead(buffer, kBtSize); + + // Unpack it into the page structure + Common::MemoryReadStream stream(buffer, bytesRead, DisposeAfterUse::NO); + _buff[level]._page->readBTree(stream); + _buff[level]._index = -1; + } + return _buff[level]._page; +} + +BtKeypack *ResourceManager::find(const char *key) { + int lev = 0; + uint16 nxt = kBtValRoot; + while (!_catFile->eos()) { + BtPage *pg = getPage(lev, nxt); + // search + if (pg->_header._down != kBtValNone) { + int i; + for (i = 0; i < pg->_header._count; i++) { + // Does this work, or does it have to compare the entire buffer? + if (scumm_strnicmp((const char *)key, (const char*)pg->_inner[i]._key, kBtKeySize) < 0) + break; + } + nxt = (i) ? pg->_inner[i - 1]._down : pg->_header._down; + _buff[lev]._index = i - 1; + lev++; + } else { + int i; + for (i = 0; i < pg->_header._count - 1; i++) { + if (scumm_stricmp((const char *)key, (const char *)pg->_leaf[i]._key) <= 0) + break; + } + + // Hack to work around a mix between 24piram_ and 24pirami + if (!strcmp(key, "24piram_.SPR") && (scumm_stricmp((const char *)key, (const char *)pg->_leaf[i]._key) < 0)) + ++i; + // + + _buff[lev]._index = i; + return &pg->_leaf[i]; + } + } + return nullptr; +} + +bool ResourceManager::exist(const char *name) { + return scumm_stricmp(find(name)->_key, name) == 0; +} + +uint16 ResourceManager::catRead(byte *buf, uint16 length) { + if (!_catFile->isOpen()) + return 0; + + uint16 bytesRead = _catFile->read(buf, length); + if (!bytesRead) + error("Read %s - %d bytes", _catFile->getName(), length); + xCrypt(buf, length); + return bytesRead; +} + +/*----------------------------------------------------------------------- + * EncryptedStream + *-----------------------------------------------------------------------*/ +EncryptedStream::EncryptedStream(CGE2Engine *vm, const char *name) : _vm(vm), _lineCount(0) { + _error = false; + BtKeypack *kp = _vm->_resman->find(name); + if (scumm_stricmp(kp->_key, name) != 0) + _error = true; + + _vm->_resman->seek(kp->_pos); + byte *dataBuffer; + int bufSize; + + if ((strlen(name) > 4) && (scumm_stricmp(name + strlen(name) - 4, ".SPR") == 0)) { + // SPR files have some inconsistencies. Some have extra 0x1A at the end, some others + // do not have a carriage return at the end of the last line + // Therefore, we remove this ending 0x1A and add extra new lines. + // This fixes bug #3537527 + dataBuffer = (byte *)malloc(kp->_size + 2); + _vm->_resman->read(dataBuffer, kp->_size); + if (dataBuffer[kp->_size - 1] == 0x1A) + dataBuffer[kp->_size - 1] = '\n'; + dataBuffer[kp->_size] = '\n'; + dataBuffer[kp->_size + 1] = '\n'; + bufSize = kp->_size + 2; + } else { + dataBuffer = (byte *)malloc(kp->_size); + _vm->_resman->read(dataBuffer, kp->_size); + bufSize = kp->_size; + } + + _readStream = new Common::MemoryReadStream(dataBuffer, bufSize, DisposeAfterUse::YES); +} + +uint32 EncryptedStream::read(byte *dataPtr, uint32 dataSize) { + return _readStream->read(dataPtr, dataSize); +} + +int16 EncryptedStream::readSint16LE() { + return _readStream->readSint16LE(); +} + +uint32 EncryptedStream::readUint32LE() { + return _readStream->readUint32LE(); +} + +bool EncryptedStream::err() { + return (_error || _readStream->err()); +} + +bool EncryptedStream::eos() { + return _readStream->eos(); +} + +bool EncryptedStream::seek(int32 offset) { + return _readStream->seek(offset); +} + +Common::String EncryptedStream::readLine() { + _lineCount++; + Common::String line = _readStream->readLine(); + if (!line.empty() && (line[0] == ';' || line[0] == '.' || line[0] == '*')) + line.clear(); // Returns an empty string, if the line is invalid. + return line; +} + +int32 EncryptedStream::size() { + return _readStream->size(); +} + +int32 EncryptedStream::pos() { + return _readStream->pos(); +} + +EncryptedStream::~EncryptedStream() { + delete _readStream; +} + +const char *EncryptedStream::kIdTab[] = { + "[near]", "[mtake]", "[ftake]", "[phase]", "[seq]", + "Name", "Type", "Front", "East", + "Portable", "Transparent", + nullptr +}; + +} // End of namespace CGE2 diff --git a/engines/cge2/fileio.h b/engines/cge2/fileio.h new file mode 100644 index 0000000000..e236c73b49 --- /dev/null +++ b/engines/cge2/fileio.h @@ -0,0 +1,133 @@ +/* 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. + * + */ + +/* + * This code is based on original Sfinx source code + * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon + */ + +#ifndef CGE2_FILEIO_H +#define CGE2_FILEIO_H + +#include "common/file.h" + +namespace CGE2 { + +class CGE2Engine; + +#define kBtSize 2048 +#define kBtKeySize 13 +#define kBtLevel 2 +#define kBtInnerCount ((kBtSize - 4 /*sizeof(Header) */) / (kBtKeySize + 2 /*sizeof(Inner) */)) +#define kBtLeafCount ((kBtSize - 4 /*sizeof(Header) */) / (kBtKeySize + 4 + 4 /*sizeof(BtKeypack) */)) +#define kBtValNone 0xFFFF +#define kBtValRoot 0 +#define kCatName "VOL.CAT" +#define kDatName "VOL.DAT" +#define kCryptSeed 0xA5 + +enum ID { + kIdNear, kIdMTake, kIdFTake, kIdPhase, kIdSeq, + kIdName, kIdType, kIdFront, kIdEast, + kIdPortable, kIdTransparent, + kIdNone = -1 +}; + +struct BtKeypack { + char _key[kBtKeySize]; + uint32 _pos; + uint32 _size; +}; + +struct Inner { + uint8 _key[kBtKeySize]; + uint16 _down; +}; + +struct Header { + uint16 _count; + uint16 _down; +}; + +struct BtPage { + Header _header; + union { + // dummy filler to make proper size of union + uint8 _data[kBtSize - 4]; /* 4 is the size of struct Header */ + // inner version of data: key + word-sized page link + Inner _inner[kBtInnerCount]; + // leaf version of data: key + all user data + BtKeypack _leaf[kBtLeafCount]; + }; + + void readBTree(Common::ReadStream &s); +}; + +class ResourceManager { +private: + struct { + BtPage *_page; + uint16 _pageNo; + int _index; + } _buff[kBtLevel]; + + BtPage *getPage(int level, uint16 pageId); + uint16 catRead(byte *buf, uint16 length); + Common::File *_catFile; + Common::File *_datFile; + void xCrypt(byte *buf, uint16 length); +public: + ResourceManager(); + ~ResourceManager(); + uint16 read(byte *buf, uint16 length); + bool seek(int32 offs, int whence = SEEK_SET); + + BtKeypack *find(const char *key); + bool exist(const char *name); +}; + +class EncryptedStream { +private: + CGE2Engine *_vm; + Common::SeekableReadStream *_readStream; + int _lineCount; + bool _error; +public: + EncryptedStream(CGE2Engine *vm, const char *name); + ~EncryptedStream(); + bool err(); + bool eos(); + bool seek(int32 offset); + int32 pos(); + int32 size(); + uint32 read(byte *dataPtr, uint32 dataSize); + int16 readSint16LE(); + uint32 readUint32LE(); + Common::String readLine(); + int getLineCount() { return _lineCount; } + + static const char *kIdTab[]; +}; + +} // End of namespace CGE2 + +#endif // CGE2_FILEIO_H diff --git a/engines/cge2/general.h b/engines/cge2/general.h new file mode 100644 index 0000000000..7213c2e24d --- /dev/null +++ b/engines/cge2/general.h @@ -0,0 +1,45 @@ +/* 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. + * + */ + +/* + * This code is based on original Sfinx source code + * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon + */ + +#ifndef CGE2_GENERAL_H +#define CGE2_GENERAL_H + +#include "common/file.h" + +namespace CGE2 { + +class CGE2Engine; + +struct Dac { + uint8 _r; + uint8 _g; + uint8 _b; +}; + +} // End of namespace CGE2 + +#endif // CGE2_GENERAL_H diff --git a/engines/cge2/hero.cpp b/engines/cge2/hero.cpp new file mode 100644 index 0000000000..e8267b5af2 --- /dev/null +++ b/engines/cge2/hero.cpp @@ -0,0 +1,619 @@ +/* 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. + * + */ + +/* + * This code is based on original Sfinx source code + * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon + */ + +#include "cge2/hero.h" +#include "cge2/text.h" +#include "cge2/map.h" + +namespace CGE2 { + +Hero::Hero(CGE2Engine *vm) + : Sprite(vm), _contact(nullptr), _dir(kNoDir), + _curDim(0), _tracePtr(-1), _ignoreMap(false) { + + for (int i = 0; i < kDimMax; i++) { + _dim[i] = nullptr; + } +} + +Hero::~Hero() { + contract(); +} + +Sprite *Hero::expand() { + if (_ext) + return this; + + char fname[kMaxPath]; + _vm->mergeExt(fname, _file, kSprExt); + + if (_ext != nullptr) + delete _ext; + + _ext = new SprExt(_vm); + + if (!*_file) + return this; + + for (int i = 0; i < kDimMax; i++) { + if (_dim[i] != nullptr) { + delete[] _dim[i]; + _dim[i] = nullptr; + } + } + for (int i = 0; i < kDimMax; i++) { + _dim[i] = new Bitmap[_shpCnt]; + for (int j = 0; j < _shpCnt; j++) + _dim[i][j].setVM(_vm); + } + + int cnt[kActions]; + + for (int i = 0; i < kActions; i++) + cnt[i] = 0; + + for (int i = 0; i < kActions; i++) { + byte n = _actionCtrl[i]._cnt; + if (n) + _ext->_actions[i] = new CommandHandler::Command[n]; + else + _ext->_actions[i] = nullptr; + } + + Seq *curSeq = nullptr; + if (_seqCnt) + curSeq = new Seq[_seqCnt]; + + if (_vm->_resman->exist(fname)) { // sprite description file exist + EncryptedStream sprf(_vm, fname); + if (sprf.err()) + error("Bad SPR [%s]", fname); + + ID section = kIdPhase; + ID id; + Common::String line; + char tmpStr[kLineMax + 1]; + int shpcnt = 0; + int seqcnt = 0; + int maxnow = 0; + int maxnxt = 0; + + for (line = sprf.readLine(); !sprf.eos(); line = sprf.readLine()) { + if (line.empty()) + continue; + Common::strlcpy(tmpStr, line.c_str(), sizeof(tmpStr)); + + char *p = _vm->token(tmpStr); + + id = _vm->ident(p); + switch (id) { + case kIdNear: + case kIdMTake: + case kIdFTake: + case kIdPhase: + case kIdSeq: + section = id; + break; + case kIdName: + Common::strlcpy(tmpStr, line.c_str(), sizeof(tmpStr)); + for (p = tmpStr; *p != '='; p++); // We search for the = + setName(_vm->tail(p)); + break; + default: + if (id >= kIdNear) + break; + Seq *s; + switch (section) { + case kIdNear: + case kIdMTake: + case kIdFTake: + id = (ID)_vm->_commandHandler->getComId(p); + if (_actionCtrl[section]._cnt) { + CommandHandler::Command *c = &_ext->_actions[section][cnt[section]++]; + c->_commandType = CommandType(id); + c->_ref = _vm->number(nullptr); + c->_val = _vm->number(nullptr); + c->_spritePtr = nullptr; + } + break; + case kIdSeq: + s = &curSeq[seqcnt++]; + s->_now = atoi(p); + if (s->_now > maxnow) + maxnow = s->_now; + s->_next = _vm->number(nullptr); + switch (s->_next) { + case 0xFF: + s->_next = seqcnt; + break; + case 0xFE: + s->_next = seqcnt - 1; + break; + } + if (s->_next > maxnxt) + maxnxt = s->_next; + s->_dx = _vm->number(nullptr); + s->_dy = _vm->number(nullptr); + s->_dz = _vm->number(nullptr); + s->_dly = _vm->number(nullptr); + break; + case kIdPhase: + for (int i = 0; i < kDimMax; i++) { + char *q = p; + q[1] = '0' + i; + Bitmap b(_vm, q); + _dim[i][shpcnt] = b; + if (!shpcnt) + _hig[i] = b._h; + } + ++shpcnt; + break; + default: + break; + } + } + } + + if (curSeq) { + if (maxnow >= shpcnt) + error("Bad PHASE in SEQ %s", fname); + if (maxnxt >= seqcnt) + error("Bad JUMP in SEQ %s", fname); + setSeq(curSeq); + } else + setSeq(_stdSeq8); + + setShapeList(_dim[0], shpcnt); + } + + Common::String str(_vm->_text->getText(_ref + 100)); + char text[kLineMax + 1]; + strcpy(text, str.c_str()); + _reachStart = atoi(_vm->token(text)); + _reachCycle = atoi(_vm->token(nullptr)); + _sayStart = atoi(_vm->token(nullptr)); + _funStart = atoi(_vm->token(nullptr)); + _funDel = _funDel0 = (72 / _ext->_seq[0]._dly) * atoi(_vm->token(nullptr)); + + int i = stepSize() / 2; + _maxDist = sqrt(double(i * i * 2)); + setCurrent(); + + return this; +} + +Sprite *Hero::contract() { + for (int i = 0; i < kDimMax; i++) { + if (_dim[i] != nullptr) { + delete[] _dim[i]; + if (_ext->_shpList == _dim[i]) + _ext->_shpList = nullptr; + _dim[i] = nullptr; + } + } + Sprite::contract(); + return this; +} + +void Hero::setCurrent() { + FXP m = _vm->_eye->_z / (_pos3D._z - _vm->_eye->_z); + FXP tmp = m * _siz.y; + int h = -(tmp.trunc()); + + int i = 0; + for (; i < kDimMax - 1; i++) { + if (h >= (_hig[i] + _hig[i + 1]) / 2) + break; + } + + _ext->_shpList = _dim[_curDim = i]; +} + +void Hero::hStep() { + if (!_ignoreMap && _ext) { + Seq *seq = _ext->_seq; + int ptr = seq[_seqPtr]._next; + seq += ptr; + if (seq->_dx | seq->_dz) { + V2D p0(_vm, _pos3D._x.round(), _pos3D._z.round()); + V2D p1(_vm, p0.x + seq->_dx, p0.y + seq->_dz); + if (mapCross(p0, p1)) { + park(); + return; + } + } + } + step(); +} + +Sprite *Hero::setContact() { + Sprite *spr; + int md = _maxDist << 1; + for (spr = _vm->_vga->_showQ->first(); spr; spr = spr->_next) { + if (spr->_actionCtrl[kNear]._cnt && ((spr->_ref & 255) != 255) && (distance(spr) <= md)) { + if (spr == _contact) + return nullptr; + else + break; + } + } + return (_contact = spr); +} + +void Hero::tick() { + int z = _pos3D._z.trunc(); + //-- maybe not exactly wid/2, but wid/3 ? + int d = ((_siz.x / 2) * _vm->_eye->_z.trunc()) / (_vm->_eye->_z.trunc() - z); + + if (_dir != kNoDir) { // just walking... + if (_flags._hold || _tracePtr < 0) + park(); + else { + Sprite *spr = setContact(); + if (spr) + _vm->feedSnail(spr, kNear, this); + } + } + //--------------------------------------------------------------- + if (_tracePtr >= 0) { + if (distance(_trace[_tracePtr]) <= _maxDist) + --_tracePtr; + + if (_tracePtr < 0) + park(); + else { + int stp = stepSize() / 2; + int dx = _trace[_tracePtr]._x.round() - _pos3D._x.round(); + int dz = _trace[_tracePtr]._z.round() - _pos3D._z.round(); + Dir dir = (dx > stp) ? kEE : ((-dx > stp) ? kWW : ((dz > stp) ? kNN : kSS)); + turn(dir); + } + } + + //--------------------------------------------------------------- + hStep(); + setCurrent(); + switch (_dir) { + case kSS: + if (_pos3D._z < stepSize() / 2) + park(); + break; + case kWW: + if (_pos2D.x <= d) + park(); + break; + case kNN: + if (_pos3D._z > kScrDepth) + park(); + break; + case kEE: + if (_pos2D.x >= kScrWidth - 1 - d) + park(); + break; + default: + break; + } + if (_flags._trim) + gotoxyz_(_pos2D); + + if (_pos3D._z.trunc() != z) + _flags._zmov = true; + + if (--_funDel == 0) + fun(); +} + +int Hero::distance(V3D pos) { + V3D di = _pos3D - pos; + int x = di._x.round(); + int z = di._z.round(); + int retval = (int)sqrt((long double)x * x + z * z); + return retval; +} + +int Hero::distance(Sprite *spr) { + V3D pos = spr->_pos3D; + int mdx = (spr->_siz.x >> 1) + (_siz.x >> 1); + int dx = (_pos3D._x - spr->_pos3D._x).round(); + if (dx < 0) { + mdx = -mdx; + if (dx > mdx) + pos._x = _pos3D._x; + else + pos._x += mdx; + } else if (dx < mdx) + pos._x = _pos3D._x; + else + pos._x += mdx; + + return distance(pos); +} + +void Hero::turn(Dir d) { + Dir dir = (_dir == kNoDir) ? kSS : _dir; + if (d != _dir) { + step((d == dir) ? 57 : (8 + 4 * dir + d)); + _dir = d; + } + resetFun(); +} + +void Hero::park() { + if (_dir != kNoDir) { + step(8 + 5 * _dir); + _dir = kNoDir; + _trace[0] = _pos3D; + _tracePtr = -1; + setCurrent(); + _flags._zmov = true; + } + _ignoreMap = false; + if (_time == 0) + ++_time; +} + +bool Hero::lower(Sprite * spr) { + return (spr->_pos3D._y + (spr->_siz.y >> 2) < 10); +} + +void Hero::reach(int mode) { + Sprite *spr = nullptr; + if (mode >= 4) { + spr = _vm->_vga->_showQ->locate(mode); + if (spr) { + mode = !spr->_flags._east; // 0-1 + if (lower(spr)) // 2-3 + mode += 2; + } + } + // note: insert SNAIL commands in reverse order + _vm->_commandHandler->insertCommand(kCmdPause, -1, 24, nullptr); + _vm->_commandHandler->insertCommand(kCmdSeq, -1, _reachStart + _reachCycle * mode, this); + if (spr) { + _vm->_commandHandler->insertCommand(kCmdWait, -1, -1, this); + _vm->_commandHandler->insertCommand(kCmdWalk, -1, spr->_ref, this); + } + // sequence is not finished, + // now it is just at sprite appear (disappear) point + resetFun(); +} + +void Hero::fun() { + if (_vm->_commandHandler->idle()) { + park(); + _vm->_commandHandler->addCommand(kCmdWait, -1, -1, this); + _vm->_commandHandler->addCommand(kCmdSeq, -1, _funStart, this); + } + _funDel = _funDel0 >> 2; +} + +int Hero::len(V2D v) { + return sqrt(double(v.x * v.x + v.y * v.y)); +} + +bool Hero::findWay(){ + V2D p0(_vm, _pos3D._x.round(), _pos3D._z.round()); + V2D p1(_vm, _trace[_tracePtr]._x.round(), _trace[_tracePtr]._z.round()); + V2D ph(_vm, p1.x, p0.y); + V2D pv(_vm, p0.x, p1.y); + bool pvOk = (!mapCross(p0, pv) && !mapCross(pv, p1)); + bool phOk = (!mapCross(p0, ph) && !mapCross(ph, p1)); + int md = (_maxDist >> 1); + if (pvOk && (len(ph - p0) <= md || len(p1 - ph) <= md)) + return true; + + if (phOk && (len(pv - p0) <= md || len(p1 - pv) <= md)) + return true; + + if (pvOk) { + _trace[++_tracePtr] = V3D(pv.x, 0, pv.y); + return true; + } + + if (phOk) { + _trace[++_tracePtr] = V3D(ph.x, 0, ph.y); + return true; + } + + return false; +} + +int Hero::snap(int p, int q, int grid) { + int d = abs(q - p) % grid; + if (d > (grid >> 1)) + d -= grid; + return (q >= p) ? (q - d) : (q + d); +} + +void Hero::walkTo(V3D pos) { + if (distance(pos) <= _maxDist) + return; + + int stp = stepSize(); + pos._x = snap(_pos3D._x.round(), pos._x.round(), stp); + pos._y = 0; + pos._z = snap(_pos3D._z.round(), pos._z.round(), stp); + + V2D p0(_vm, _pos3D._x.round(), _pos3D._z.round()); + V2D p1(_vm, pos._x.round(), pos._z.round()); + resetFun(); + int cnt = mapCross(p0, p1); + if ((cnt & 1) == 0) { // even == way exists + _trace[_tracePtr = 0] = pos; + if (!findWay()) { + int i; + ++_tracePtr; + for (i = stp; i < kMaxTry; i += stp) { + _trace[_tracePtr] = pos + V3D(i, 0, 0); + if (!mapCross(_trace[_tracePtr - 1], _trace[_tracePtr]) && findWay()) + break; + + _trace[_tracePtr] = pos + V3D(-i, 0, 0); + if (!mapCross(_trace[_tracePtr - 1], _trace[_tracePtr]) && findWay()) + break; + + _trace[_tracePtr] = pos + V3D(0, 0, i); + if (!mapCross(_trace[_tracePtr - 1], _trace[_tracePtr]) && findWay()) + break; + + _trace[_tracePtr] = pos + V3D(0, 0, -i); + if (!mapCross(_trace[_tracePtr - 1], _trace[_tracePtr]) && findWay()) + break; + } + if (i >= kMaxTry) + _trace[_tracePtr] = V3D(_pos3D._x, 0, pos._z); // not found + } + } +} + +void Hero::walkTo(Sprite *spr) { + int mdx = _siz.x >> 1; + int stp = (stepSize() + 1) / 2; + if (!spr->_flags._east) + mdx = -mdx; + walkTo(spr->_pos3D + V3D(mdx, 0, (!spr->_flags._frnt || spr->_pos3D._z < 8) ? stp : -stp)); +} + +V3D Hero::screenToGround(V2D pos) { + FXP z = _vm->_eye->_z + (_vm->_eye->_y * _vm->_eye->_z) / (FXP(pos.y) - _vm->_eye->_y); + FXP x = _vm->_eye->_x - ((FXP(pos.x) - _vm->_eye->_x) * (z - _vm->_eye->_z)) / _vm->_eye->_z; + return V3D(x.round(), 0, z.round()); +} + +int Hero::cross(const V2D &a, const V2D &b) { + int x = _pos3D._x.trunc(); + int z = _pos3D._z.trunc(); + int r = ((_siz.x / 3) * _vm->_eye->_z.trunc()) / (_vm->_eye->_z.trunc() - z); + return _vm->cross(a, b, V2D(_vm, x - r, z), V2D(_vm, x + r, z)) << 1; +} + +bool CGE2Engine::cross(const V2D &a, const V2D &b, const V2D &c, const V2D &d) { + if (contain(a, b, c) || contain(a, b, d) || contain(c, d, a) || contain(c, d, b)) + return true; + + return sgn(det(a, b, c)) != sgn(det(a, b, d)) && sgn(det(c, d, a)) != sgn(det(c, d, b)); +} + +bool CGE2Engine::contain(const V2D &a, const V2D &b, const V2D &p) { + if (det(a, b, p)) + return false; + + return ((long)(a.x - p.x) * (p.x - b.x) >= 0 && (long)(a.y - p.y) * (p.y - b.y) >= 0); +} + +long CGE2Engine::det(const V2D &a, const V2D &b, const V2D &c) { + return ((long)a.x * b.y + (long)b.x * c.y + (long)c.x * a.y) - ((long)c.x * b.y + (long)b.x * a.y + (long)a.x * c.y); +} + +int CGE2Engine::sgn(long n) { + return (n == 0) ? 0 : ((n > 0) ? 1 : -1); +} + +int Hero::mapCross(const V2D &a, const V2D &b) { + Hero *o = other(); + int n = (o->_scene == _scene) ? o->cross(a, b) : 0; + if (!_ignoreMap) + n += _vm->mapCross(a, b); + + return n; +} + +int Hero::mapCross(const V3D &a, const V3D &b) { + return mapCross(V2D(_vm, a._x.round(), a._z.round()), V2D(_vm, b._x.round(), b._z.round())); +} + +int CGE2Engine::mapCross(const V2D &a, const V2D &b) { + int cnt = 0; + V2D *n0 = nullptr; + V2D *p = nullptr; + for (int i = 0; i < _map->size(); i++) { + V2D *n = _map->getCoord(i); + if (p) { + if (cross(a, b, *n0, *n)) + ++cnt; + + if (*n == *p) + p = nullptr; + } else { + p = n; + } + n0 = n; + } + return cnt; +} + +void Hero::setScene(int c) { + Sprite::setScene(c); + resetFun(); +} + +void Hero::operator++() { + if (_curDim > 0) + _ext->_shpList = _dim[--_curDim]; +} + +void Hero::operator--() { + if (_curDim < kDimMax - 1) + _ext->_shpList = _dim[++_curDim]; +} + +bool Sprite::works(Sprite *spr) { + if (!spr || !spr->_ext) + return false; + + bool ok = false; + + Action a = _vm->_heroTab[_vm->_sex]->_ptr->action(); + CommandHandler::Command *ct = spr->_ext->_actions[a]; + if (ct) { + int i = spr->_actionCtrl[a]._ptr; + int n = spr->_actionCtrl[a]._cnt; + while (i < n && !ok) { + CommandHandler::Command *c = &ct[i++]; + if (c->_commandType != kCmdUse) + break; + ok = (c->_ref == _ref); + if (c->_val > 255) { + if (ok) { + int p = spr->labVal(a, c->_val >> 8); + if (p >= 0) + spr->_actionCtrl[a]._ptr = p; + else + ok = false; + } + } else { + if (c->_val && c->_val != _vm->_now) + ok = false; + break; + } + } + } + + return ok; +} + +} // End of namespace CGE2 diff --git a/engines/cge2/hero.h b/engines/cge2/hero.h new file mode 100644 index 0000000000..3b5329e12c --- /dev/null +++ b/engines/cge2/hero.h @@ -0,0 +1,114 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * 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. + * + */ + +/* + * This code is based on original Sfinx source code + * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon + */ + +#ifndef CGE2_HERO_H +#define CGE2_HERO_H + +#include "cge2/cge2.h" +#include "cge2/vga13h.h" +#include "cge2/snail.h" + +namespace CGE2 { + +#define kMaxTry 400 + +class Hero; + +struct HeroTab { + Hero *_ptr; + Sprite *_face; + Sprite *_pocket[kPocketMax + 1]; + int _downPocketId[kPocketMax + 1]; + int _pocPtr; + V2D *_posTab[kSceneMax]; + HeroTab(CGE2Engine *vm) { + _ptr = nullptr; + _face = nullptr; + for (int i = 0; i < kPocketMax + 1; i++) { + _pocket[i] = nullptr; + _downPocketId[i] = -1; + } + _pocPtr = 0; + for (int i = 0; i < kSceneMax; i++) + _posTab[i] = nullptr; + } + ~HeroTab() { + for (int i = 0; i < kSceneMax; i++) + delete _posTab[i]; + } +}; + +class Hero : public Sprite { + int _hig[kDimMax]; + Sprite *_contact; +public: + BitmapPtr _dim[kDimMax]; + V3D _trace[kWayMax]; + enum Dir { kNoDir = -1, kSS, kWW, kNN, kEE } _dir; + int _curDim; + int _tracePtr; + int _reachStart, _reachCycle, _sayStart, _funStart; + int _funDel0, _funDel; + int _maxDist; + bool _ignoreMap; + Hero(CGE2Engine *vm); + ~Hero(); + void tick(); + Sprite *expand(); + Sprite *contract(); + Sprite *setContact(); + int stepSize() { return _ext->_seq[7]._dx; } + int distance(V3D pos); + int distance(Sprite * spr); + void turn(Dir d); + void park(); + int len(V2D v); + bool findWay(); + static int snap(int p, int q, int grid); + void walkTo(V3D pos); + void walkTo(V2D pos) { walkTo(screenToGround(pos)); } + V3D screenToGround(V2D pos); + void walkTo(Sprite *spr); + void say() { step(_sayStart); } + void fun(); + void resetFun() { _funDel = _funDel0; } + void hStep(); + bool lower(Sprite * spr); + int cross(const V2D &a, const V2D &b); + int mapCross(const V2D &a, const V2D &b); + int mapCross(const V3D &a, const V3D &b); + Hero *other() { return _vm->_heroTab[!(_ref & 1)]->_ptr;} + Action action() { return (Action)(_ref % 10); } + void reach(int mode); + void setCurrent(); + void setScene(int c); + void operator++(); + void operator--(); +}; + +} // End of namespace CGE2 + +#endif // CGE2_HERO_H diff --git a/engines/cge2/inventory.cpp b/engines/cge2/inventory.cpp new file mode 100644 index 0000000000..e62aa01e99 --- /dev/null +++ b/engines/cge2/inventory.cpp @@ -0,0 +1,104 @@ +/* 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. + * + */ + +/* + * This code is based on original Sfinx source code + * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon + */ + +#include "cge2/cge2.h" +#include "cge2/hero.h" + +namespace CGE2 { + +int CGE2Engine::findActivePocket(int ref) { + for (int i = 0; i < kPocketMax; i++) { + Sprite *spr = _heroTab[_sex]->_pocket[i]; + if (ref >= 0) { + if (spr && (spr->_ref == ref)) + return i; + } else if (!spr) + return i; + } + return -1; +} + +void CGE2Engine::selectPocket(int n) { + Sprite **p = _heroTab[_sex]->_pocket; + int &pp = _heroTab[_sex]->_pocPtr; + if ((n < 0) || (pp == n)) { + n = findActivePocket(-1); + if (n >= 0) + pp = n; + } else if (p[n]) + pp = n; +} + +void CGE2Engine::pocFul() { + Hero *h = _heroTab[_sex]->_ptr; + h->park(); + _commandHandler->addCommand(kCmdWait, -1, -1, h); + _commandHandler->addCommand(kCmdSound, -1, 2, h); + _commandHandler->addCommand(kCmdSay, -1, kPocketFull + _sex, h); +} + +void CGE2Engine::releasePocket(Sprite *spr) { + for (int i = 0; i < 2; i++) { + for (int j = 0; j < kPocketMax; j++) { + Sprite *&poc = _heroTab[i]->_pocket[j]; + if (poc == spr) { + spr->_flags._kept = false; + poc = nullptr; + return; + } + } + } +} + +int CGE2Engine::freePockets(int sx) { + int n = 0; + for (int i = 0; i < kPocketMax; i++){ + if (_heroTab[sx]->_pocket[i] == nullptr) + ++n; + } + return n; +} + +void CGE2Engine::openPocket() { + for (int i = 0; i < 2; i++) { + for (int j = 0; j < kPocketMax + 1; j++) { + int ref = (int)_heroTab[i]->_downPocketId[j]; + _heroTab[i]->_pocket[j] = (ref == -1) ? nullptr : _vga->_showQ->locate(ref); + } + } +} + +void CGE2Engine::closePocket() { + for (int i = 0; i < 2; i++) { + for (int j = 0; j < kPocketMax + 1; j++) { + Sprite *spr = _heroTab[i]->_pocket[j]; + _heroTab[i]->_downPocketId[j] = (spr) ? spr->_ref : -1; + } + } +} + +} // End of namespace CGE2 diff --git a/engines/cge2/map.cpp b/engines/cge2/map.cpp new file mode 100644 index 0000000000..1ed0ea7daf --- /dev/null +++ b/engines/cge2/map.cpp @@ -0,0 +1,92 @@ +/* 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. + * + */ + +/* + * This code is based on original Sfinx source code + * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon + */ + +#include "cge2/map.h" + +namespace CGE2 { + +Map::Map(CGE2Engine *vm) :_vm(vm) {} + +Map::~Map() { + _container.clear(); +} + +void Map::clear() { + _container.clear(); +} + +void Map::load(int scene) { + clear(); + + char fname[] = "%.2d.MAP\0"; + Common::String fileName = Common::String::format(fname, scene); + if (!_vm->_resman->exist(fileName.c_str())) + return; + + EncryptedStream file(_vm, fileName.c_str()); + + Common::String line; + for (line = file.readLine(); !file.eos(); line = file.readLine()) { + if (line.empty()) + continue; + + char tmpStr[kLineMax + 1]; + Common::strlcpy(tmpStr, line.c_str(), sizeof(tmpStr)); + + char *currPos = tmpStr; + int x = nextNum(currPos); + while (true) { + int y = nextNum(nullptr); + _container.push_back(V2D(_vm, convertCoord(x), convertCoord(y))); + x = nextNum(nullptr); + if (x == -1) // We stop if there are no more data left to process in the current line. + break; + } + } +} + +int Map::nextNum(char *currPos) { + currPos = strtok(currPos, " (),"); + if (currPos == nullptr) + return -1; + int num = atoi(currPos); + return num; +} + +int Map::convertCoord(int coord) { + return (coord + (kMapGrid >> 1)) & kMapMask; +} + +int Map::size() { + return _container.size(); +} + +V2D *Map::getCoord(int idx) { + return &_container[idx]; +} + +} // End of namespace CGE2 diff --git a/engines/cge2/map.h b/engines/cge2/map.h new file mode 100644 index 0000000000..206479b929 --- /dev/null +++ b/engines/cge2/map.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. + * + */ + +/* + * This code is based on original Sfinx source code + * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon + */ + +#ifndef CGE2_MAP_H +#define CGE2_MAP_H + +#include "cge2/vga13h.h" + +namespace CGE2 { + +#define kMapGrid 4 +#define kMapMask (~(kMapGrid - 1)) + +class Map { + CGE2Engine *_vm; + Common::Array<V2D> _container; + + int convertCoord(int coord); + int nextNum(char *currPos); +public: + Map(CGE2Engine *vm); + ~Map(); + void clear(); + void load(int scene); + int size(); + V2D *getCoord(int idx); +}; + +} // End of namespace CGE2 + +#endif // CGE2_MAP_H diff --git a/engines/cge2/module.mk b/engines/cge2/module.mk new file mode 100644 index 0000000000..4b321d88a8 --- /dev/null +++ b/engines/cge2/module.mk @@ -0,0 +1,30 @@ +MODULE := engines/cge2 + +MODULE_OBJS = \ + cge2.o \ + detection.o \ + fileio.o \ + vga13h.o \ + bitmap.o \ + sound.o \ + cge2_main.o \ + text.o \ + hero.o \ + snail.o \ + spare.o \ + talk.o \ + events.o \ + map.o \ + vmenu.o \ + saveload.o \ + toolbar.o \ + inventory.o \ + console.o + +# This module can be built as a plugin +ifeq ($(ENABLE_CGE2), DYNAMIC_PLUGIN) +PLUGIN := 1 +endif + +# Include common rules +include $(srcdir)/rules.mk diff --git a/engines/cge2/saveload.cpp b/engines/cge2/saveload.cpp new file mode 100644 index 0000000000..c9cedff83f --- /dev/null +++ b/engines/cge2/saveload.cpp @@ -0,0 +1,279 @@ +/* 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. + * + */ + +/* + * This code is based on original Sfinx source code + * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon + */ + +#include "common/config-manager.h" +#include "common/savefile.h" +#include "common/system.h" +#include "graphics/thumbnail.h" +#include "graphics/surface.h" +#include "graphics/palette.h" +#include "graphics/scaler.h" +#include "cge2/events.h" +#include "cge2/snail.h" +#include "cge2/hero.h" +#include "cge2/text.h" +#include "cge2/sound.h" +#include "cge2/cge2_main.h" + +namespace CGE2 { + +#define kSavegameCheckSum (1997 + _now + _sex + kWorldHeight) +#define kBadSVG 99 + +bool CGE2Engine::canSaveGameStateCurrently() { + return (_startupMode == 0) && _mouse->_active && + _commandHandler->idle() && (_soundStat._wait == nullptr); +} + +Common::Error CGE2Engine::saveGameState(int slot, const Common::String &desc) { + storeHeroPos(); + saveGame(slot, desc); + sceneUp(_now); + return Common::kNoError; +} + +void CGE2Engine::saveGame(int slotNumber, const Common::String &desc) { + // Set up the serializer + Common::String slotName = generateSaveName(slotNumber); + Common::OutSaveFile *saveFile = g_system->getSavefileManager()->openForSaving(slotName); + + // Write out the ScummVM savegame header + SavegameHeader header; + header.saveName = desc; + header.version = kSavegameVersion; + writeSavegameHeader(saveFile, header); + + // Write out the data of the savegame + sceneDown(); + syncGame(nullptr, saveFile); + + // Finish writing out game data + saveFile->finalize(); + delete saveFile; +} + +bool CGE2Engine::canLoadGameStateCurrently() { + return (_startupMode == 0) && _mouse->_active; +} + +Common::Error CGE2Engine::loadGameState(int slot) { + _commandHandler->clear(); + _commandHandlerTurbo->clear(); + sceneDown(); + if (!loadGame(slot)) + return Common::kReadingFailed; + sceneUp(_now); + initToolbar(); + return Common::kNoError; +} + +bool CGE2Engine::loadGame(int slotNumber) { + Common::MemoryReadStream *readStream; + + // Open up the savegame file + Common::String slotName = generateSaveName(slotNumber); + Common::InSaveFile *saveFile = g_system->getSavefileManager()->openForLoading(slotName); + + // Read the data into a data buffer + int size = saveFile->size(); + byte *dataBuffer = (byte *)malloc(size); + saveFile->read(dataBuffer, size); + readStream = new Common::MemoryReadStream(dataBuffer, size, DisposeAfterUse::YES); + delete saveFile; + + // Check to see if it's a ScummVM savegame or not + char buffer[kSavegameStrSize + 1]; + readStream->read(buffer, kSavegameStrSize + 1); + + if (strncmp(buffer, kSavegameStr, kSavegameStrSize + 1) != 0) { + delete readStream; + return false; + } else { + SavegameHeader saveHeader; + + if (!readSavegameHeader(readStream, saveHeader)) { + delete readStream; + return false; + } + + // Delete the thumbnail + saveHeader.thumbnail->free(); + delete saveHeader.thumbnail; + } + + resetGame(); + + // Get in the savegame + syncGame(readStream, nullptr); + delete readStream; + + loadHeroes(); + + return true; +} + +void CGE2Engine::resetGame() { + _busyPtr = nullptr; + busy(false); + _spare->clear(); + _vga->_showQ->clear(); + loadScript("CGE.INI", true); + delete _infoLine; + _infoLine = new InfoLine(this, kInfoW); +} + +void CGE2Engine::writeSavegameHeader(Common::OutSaveFile *out, SavegameHeader &header) { + // Write out a savegame header + out->write(kSavegameStr, kSavegameStrSize + 1); + + out->writeByte(kSavegameVersion); + + // Write savegame name + out->write(header.saveName.c_str(), header.saveName.size() + 1); + + // Get the active palette + uint8 thumbPalette[256 * 3]; + g_system->getPaletteManager()->grabPalette(thumbPalette, 0, 256); + + // Stop the heroes from moving and redraw them before taking the picture. + for (int i = 0; i < 2; i++) + _heroTab[i]->_ptr->park(); + _vga->show(); + + // Create a thumbnail and save it + Graphics::Surface *thumb = new Graphics::Surface(); + Graphics::Surface *s = _vga->_page[0]; + ::createThumbnail(thumb, (const byte *)s->getPixels(), kScrWidth, kScrHeight, thumbPalette); + Graphics::saveThumbnail(*out, *thumb); + thumb->free(); + delete thumb; + + // Write out the save date/time + TimeDate td; + g_system->getTimeAndDate(td); + out->writeSint16LE(td.tm_year + 1900); + out->writeSint16LE(td.tm_mon + 1); + out->writeSint16LE(td.tm_mday); + out->writeSint16LE(td.tm_hour); + out->writeSint16LE(td.tm_min); +} + +bool CGE2Engine::readSavegameHeader(Common::InSaveFile *in, SavegameHeader &header) { + header.thumbnail = nullptr; + + // Get the savegame version + header.version = in->readByte(); + if (header.version > kSavegameVersion) + return false; + + // Read in the string + header.saveName.clear(); + char ch; + while ((ch = (char)in->readByte()) != '\0') + header.saveName += ch; + + // Get the thumbnail + header.thumbnail = Graphics::loadThumbnail(*in); + if (!header.thumbnail) + return false; + + // Read in save date/time + header.saveYear = in->readSint16LE(); + header.saveMonth = in->readSint16LE(); + header.saveDay = in->readSint16LE(); + header.saveHour = in->readSint16LE(); + header.saveMinutes = in->readSint16LE(); + + return true; +} + +void CGE2Engine::syncGame(Common::SeekableReadStream *readStream, Common::WriteStream *writeStream) { + Common::Serializer s(readStream, writeStream); + + // Synchronise header data + syncHeader(s); + + // Synchronise _spare + _spare->sync(s); + + if (s.isSaving()) { + // Save the references of the items in the heroes pockets: + for (int i = 0; i < 2; i++) { + for (int j = 0; j < kPocketMax; j++) { + int ref = _heroTab[i]->_downPocketId[j]; + s.syncAsSint16LE(ref); + } + } + } else { + // Load items to the pockets + for (int i = 0; i < 2; i++) { + for (int j = 0; j < kPocketMax; j++) { + int ref = 0; + s.syncAsSint16LE(ref); + _heroTab[i]->_downPocketId[j] = ref; + } + } + } + + // Heroes' _posTabs + for (int i = 0; i < 2; i++) { + for (int j = 0; j < kSceneMax; j++) { + s.syncAsSint16LE(_heroTab[i]->_posTab[j]->x); + s.syncAsSint16LE(_heroTab[i]->_posTab[j]->y); + } + } +} + +void CGE2Engine::syncHeader(Common::Serializer &s) { + s.syncAsUint16LE(_now); + s.syncAsUint16LE(_sex); + s.syncAsUint16LE(_vga->_rot._len); + s.syncAsUint16LE(_waitSeq); + s.syncAsUint16LE(_waitRef); + + if (s.isSaving()) { + // Write checksum + int checksum = kSavegameCheckSum; + s.syncAsUint16LE(checksum); + } else { + // Read checksum and validate it + uint16 checksum = 0; + s.syncAsUint16LE(checksum); + if (checksum != kSavegameCheckSum) + error("%s", _text->getText(kBadSVG)); + } +} + +/** +* Support method that generates a savegame name +* @param slot Slot number +*/ +Common::String CGE2Engine::generateSaveName(int slot) { + return Common::String::format("%s.%03d", _targetName.c_str(), slot); +} + +} // End of namespace CGE2 diff --git a/engines/cge2/snail.cpp b/engines/cge2/snail.cpp new file mode 100644 index 0000000000..2cd5aca493 --- /dev/null +++ b/engines/cge2/snail.cpp @@ -0,0 +1,865 @@ +/* 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. + * + */ + +/* + * This code is based on original Sfinx source code + * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon + */ + +#include "cge2/snail.h" +#include "cge2/fileio.h" +#include "cge2/hero.h" +#include "cge2/text.h" +#include "cge2/sound.h" +#include "cge2/events.h" + +namespace CGE2 { + +const char *CommandHandler::_commandText[] = { + "NOP", "USE", "PAUSE", "INF", "CAVE", "SETX", "SETY", "SETZ", "ADD", + "FLASH", "CYCLE", "CLEAR", "MOUSE", "MAP", "MIDI", ".DUMMY.", "WAIT", + "HIDE", "ROOM", "SAY", "SOUND", "KILL", "RSEQ", "SEQ", "SEND", "SWAP", + "KEEP", "GIVE", "GETPOS", "GOTO", "PORT", "NEXT", "NNEXT", "MTNEXT", + "FTNEXT", "RNNEXT", "RMTNEXT", "RFTNEXT", "RMNEAR", "RMMTAKE", "RMFTAKE", + "SETREF", "WALKTO", "REACH", "COVER", "UNCOVER", "EXEC", "GHOST", + nullptr }; + +CommandHandler::CommandHandler(CGE2Engine *vm, bool turbo) + : _turbo(turbo), _textDelay(false), _timerExpiry(0), _talkEnable(true), + _head(0), _tail(0), _commandList((Command *)malloc(sizeof(Command)* 256)), + _vm(vm) { +} + +CommandHandler::~CommandHandler() { + free(_commandList); +} + +void CommandHandler::runCommand() { + if (!_turbo && _vm->_soundStat._wait) { + if (*(_vm->_soundStat._wait)) + return; + + ++_vm->_soundStat._ref[0]; + if (_vm->_fx->exist(_vm->_soundStat._ref[1], _vm->_soundStat._ref[0])) { + int16 oldRepeat = _vm->_sound->getRepeat(); + _vm->_sound->setRepeat(1); + _vm->_sound->play(Audio::Mixer::kSFXSoundType, _vm->_fx->load(_vm->_soundStat._ref[1], _vm->_soundStat._ref[0]), _vm->_sound->_smpinf._span); + _vm->_sound->setRepeat(oldRepeat); + return; + } + _vm->_soundStat._wait = nullptr; + } + + uint8 tmpHead = _head; + while (_tail != tmpHead) { + Command tailCmd = _commandList[_tail]; + + if (!_turbo) { // only for the slower one + if (_vm->_waitRef) + break; + + if (_timerExpiry) { + // Delay in progress + if (_timerExpiry > g_system->getMillis()) + // Delay not yet ended + break; + + // Delay is finished + _timerExpiry = 0; + } else if (_textDelay) { + if (_vm->_talk) { + _vm->snKill((Sprite *)_vm->_talk); + _vm->_talk = nullptr; + } + _textDelay = false; + } + + if (_vm->_talk && tailCmd._commandType != kCmdPause) + break; + } + ++_tail; + _vm->_taken = false; + Sprite *spr = nullptr; + if (tailCmd._commandType > kCmdSpr) + spr = (tailCmd._ref < 0) ? ((Sprite *)tailCmd._spritePtr) : _vm->locate(tailCmd._ref); + + Common::String sprStr; + if (spr && spr->_file && (tailCmd._commandType != kCmdGhost)) + // In case of kCmdGhost _spritePtr stores a pointer to a Bitmap, not to a Sprite... + sprStr = Common::String(spr->_file); + else + sprStr = "None"; + + if (sprStr.empty()) + sprStr = "None"; + debugC(1, kCGE2DebugOpcode, "Command: %s; Ref: %d; Val: %d; Sprite: %s;", getComStr(tailCmd._commandType), tailCmd._ref, tailCmd._val, sprStr.c_str()); + + switch (tailCmd._commandType) { + case kCmdUse: + break; + case kCmdPause: + _timerExpiry = g_system->getMillis() + tailCmd._val * kCommandFrameDelay; + if (_vm->_talk) + _textDelay = true; + break; + case kCmdWait: + if (spr && spr->active() && (spr->_scene == _vm->_now || spr->_scene == 0)) { + _vm->_waitSeq = tailCmd._val; + _vm->_waitRef = spr->_ref; + } + break; + case kCmdHide: + _vm->snHide(spr, tailCmd._val); + break; + case kCmdSay: + _vm->snSay(spr, tailCmd._val); + break; + case kCmdInf: + if (_talkEnable) + _vm->inf(((tailCmd._val) >= 0) ? _vm->_text->getText(tailCmd._val) : (const char *)tailCmd._spritePtr); + break; + case kCmdCave: + _vm->switchScene(tailCmd._val); + break; + case kCmdMidi: + _vm->snMidi(tailCmd._val); + break; + case kCmdKill: + _vm->snKill(spr); + break; + case kCmdSeq: + _vm->snSeq(spr, tailCmd._val); + break; + case kCmdRSeq: + _vm->snRSeq(spr, tailCmd._val); + break; + case kCmdSend: + _vm->snSend(spr, tailCmd._val); + break; + case kCmdSwap: + _vm->snSwap(spr, tailCmd._val); + break; + case kCmdCover: + _vm->snCover(spr, tailCmd._val); + break; + case kCmdUncover: + _vm->snUncover(spr, (tailCmd._val >= 0) ? _vm->locate(tailCmd._val) : ((Sprite *)tailCmd._spritePtr)); + break; + case kCmdKeep: + _vm->snKeep(spr, tailCmd._val); + break; + case kCmdGive: + _vm->snGive(spr, tailCmd._val); + break; + case kCmdSetX: + _vm->_point[tailCmd._val]->_x = tailCmd._ref; + break; + case kCmdSetY: + _vm->_point[tailCmd._val]->_y = tailCmd._ref; + break; + case kCmdSetZ: + _vm->_point[tailCmd._val]->_z = tailCmd._ref; + break; + case kCmdAdd: + *(_vm->_point[tailCmd._ref]) = *(_vm->_point[tailCmd._ref]) + *(_vm->_point[tailCmd._val]); + break; + case kCmdGetPos: + if (spr) + *(_vm->_point[tailCmd._val]) = spr->_pos3D; + break; + case kCmdGoto: + _vm->snGoto(spr, tailCmd._val); + break; + case kCmdPort: + _vm->snPort(spr, tailCmd._val); + break; + case kCmdNext: + break; + case kCmdMouse: + _vm->snMouse(tailCmd._val != 0); + break; + case kCmdNNext: + _vm->snNNext(spr, kNear, tailCmd._val); + break; + case kCmdMTNext: + _vm->snNNext(spr, kMTake, tailCmd._val); + break; + case kCmdFTNext: + _vm->snNNext(spr, kFTake, tailCmd._val); + break; + case kCmdRNNext: + _vm->snRNNext(spr, tailCmd._val); + break; + case kCmdRMTNext: + _vm->snRMTNext(spr, tailCmd._val); + break; + case kCmdRFTNext: + _vm->snRFTNext(spr, tailCmd._val); + break; + case kCmdRMNear: + _vm->snRmNear(spr); + break; + case kCmdRMMTake: + _vm->snRmMTake(spr); + break; + case kCmdRMFTake: + _vm->snRmFTake(spr); + break; + case kCmdSetRef: + _vm->snSetRef(spr, tailCmd._val); + break; + case kCmdFlash: + _vm->snFlash(tailCmd._val != 0); + break; + case kCmdCycle: + _vm->snCycle(tailCmd._val); + break; + case kCmdWalk: + _vm->snWalk(spr, tailCmd._val); + break; + case kCmdReach: + _vm->snReach(spr, tailCmd._val); + break; + case kCmdSound: + _vm->snSound(spr, tailCmd._val); + _vm->_sound->setRepeat(1); + break; + case kCmdMap: + _vm->_heroTab[tailCmd._ref & 1]->_ptr->_ignoreMap = tailCmd._val == 0; + break; + case kCmdRoom: + _vm->snRoom(spr, tailCmd._val); + break; + case kCmdExec: + switch (tailCmd._cbType) { + case kQGame: + _vm->qGame(); + break; + case kXScene: + _vm->xScene(); + break; + default: + error("Unknown Callback Type in SNEXEC"); + break; + } + break; + case kCmdGhost: + _vm->snGhost((Bitmap *)tailCmd._spritePtr); + break; + case kCmdNop: // Do nothing. + break; + default: + warning("Unhandled command"); + break; + } + + if (_vm->_taken && spr) + _vm->_spare->dispose(spr); + + if (!_turbo) + break; + } +} + +void CGE2Engine::snKill(Sprite *spr) { + if (spr) { + if (spr->_flags._kept) + releasePocket(spr); + Sprite *nx = spr->_next; + hide1(spr); + _vga->_showQ->remove(spr); + _eventManager->clearEvent(spr); + if (spr->_flags._kill) { + _spare->take(spr->_ref); + delete spr; + } else { + spr->setScene(-1); + _spare->dispose(spr); + } + if (nx && nx->_flags._slav) + snKill(nx); + } +} + +void CGE2Engine::snHide(Sprite *spr, int val) { + if (spr) { + spr->_flags._hide = (val >= 0) ? (val != 0) : (!spr->_flags._hide); + if (spr->_flags._shad) + spr->_prev->_flags._hide = spr->_flags._hide; + } +} + +void CGE2Engine::snMidi(int val) { + if (val < 0) + _midiPlayer->killMidi(); + else if (_music) + _midiPlayer->loadMidi(val); +} + +void CGE2Engine::snSeq(Sprite *spr, int val) { + if (spr) { + if (isHero(spr) && (val == 0)) + ((Hero*)spr)->park(); + else + spr->step(val); + } +} + +void CGE2Engine::snRSeq(Sprite *spr, int val) { + if (spr) + snSeq(spr, spr->_seqPtr + val); +} + +void CGE2Engine::snSend(Sprite *spr, int val) { + if (!spr) + return; + + // Sending", spr->_file + // from scene", spr->_scene + // to scene", val + bool was1 = (_vga->_showQ->locate(spr->_ref) != nullptr); + bool val1 = (val == 0 || val == _now); + spr->_scene = val; + releasePocket(spr); + if (val1 != was1) { + if (was1) { + // deactivating + hide1(spr); + spr->_flags._slav = false; + if ((spr == _heroTab[_sex]->_ptr) && (_heroTab[!_sex]->_ptr->_scene == _now)) + switchHero(!_sex); + _spare->dispose(spr); + } else { + // activating + if (byte(spr->_ref) == 0) + _bitmapPalette = _vga->_sysPal; + _vga->_showQ->insert(spr); + if (isHero(spr)) { + V2D p = *_heroTab[spr->_ref & 1]->_posTab[val]; + spr->gotoxyz(V3D(p.x, 0, p.y)); + ((Hero*)spr)->setCurrent(); + } + _taken = false; + _bitmapPalette = nullptr; + } + } +} + +void CGE2Engine::snSwap(Sprite *spr, int val) { + bool tak = _taken; + Sprite *xspr = locate(val); + if (spr && xspr) { + bool was1 = (_vga->_showQ->locate(spr->_ref) != nullptr); + bool xwas1 = (_vga->_showQ->locate(val) != nullptr); + + int tmp = spr->_scene; + spr->setScene(xspr->_scene); + xspr->setScene(tmp); + + SWAP(spr->_pos2D, xspr->_pos2D); + SWAP(spr->_pos3D, xspr->_pos3D); + if (spr->_flags._kept) + swapInPocket(spr, xspr); + if (xwas1 != was1) { + if (was1) { + hide1(spr); + _spare->dispose(spr); + } else + expandSprite(spr); + if (xwas1) { + hide1(xspr); + _spare->dispose(xspr); + } else { + expandSprite(xspr); + _taken = false; + } + } + } + if (_taken) + _spare->dispose(xspr); + _taken = tak; +} + +void CGE2Engine::snCover(Sprite *spr, int val) { + bool tak = _taken; + Sprite *xspr = locate(val); + if (spr && xspr) { + spr->_flags._hide = true; + xspr->setScene(spr->_scene); + xspr->gotoxyz(spr->_pos3D); + expandSprite(xspr); + if ((xspr->_flags._shad = spr->_flags._shad) == true) { + _vga->_showQ->insert(_vga->_showQ->remove(spr->_prev), xspr); + spr->_flags._shad = false; + } + feedSnail(xspr, kNear, _heroTab[_sex]->_ptr); + _taken = false; + } + if (_taken) + _spare->dispose(xspr); + _taken = tak; +} + +void CGE2Engine::snUncover(Sprite *spr, Sprite *spr2) { + if (spr && spr2) { + spr->_flags._hide = false; + spr->setScene(spr2->_scene); + if ((spr->_flags._shad = spr2->_flags._shad) == true) { + _vga->_showQ->insert(_vga->_showQ->remove(spr2->_prev), spr); + spr2->_flags._shad = false; + } + spr->gotoxyz(spr2->_pos3D); + snSend(spr2, -1); + if (spr->_time == 0) + ++spr->_time; + } +} + +void CGE2Engine::snKeep(Sprite *spr, int stp) { + int sex = _sex; + if (stp > 127) { + _sex = stp & 1; // for another hero + stp = -1; + } + HeroTab *ht = _heroTab[_sex]; + selectPocket(-1); + int pp = ht->_pocPtr; + + if (spr && !spr->_flags._kept && ht->_pocket[pp] == nullptr) { + V3D pos(14, -10, -1); + int16 oldRepeat = _sound->getRepeat(); + _sound->setRepeat(1); + snSound(ht->_ptr, 3); + _sound->setRepeat(oldRepeat); + if (_taken) { + _vga->_showQ->insert(spr); + _taken = false; + } + ht->_pocket[pp] = spr; + spr->setScene(0); + spr->_flags._kept = true; + if (!_sex) + pos._x += kScrWidth - 58; + if (pp & 1) + pos._x += 29; + if (pp >> 1) + pos._y -= 20; + pos._y -= (spr->_siz.y / 2); + spr->gotoxyz(pos); + if (stp >= 0) + spr->step(stp); + } + _sex = sex; + selectPocket(-1); +} + +void CGE2Engine::snGive(Sprite *spr, int val) { + if (spr) { + int p = findActivePocket(spr->_ref); + if (p >= 0) { + releasePocket(spr); + spr->setScene(_now); + if (val >= 0) + spr->step(val); + } + } + selectPocket(-1); +} + +void CGE2Engine::snGoto(Sprite *spr, int val) { + if (spr) { + V3D eye = *_eye; + if (spr->_scene > 0) + setEye(*_eyeTab[spr->_scene]); + spr->gotoxyz(*_point[val]); + setEye(eye); + } +} + +void CGE2Engine::snPort(Sprite *spr, int port) { + if (spr) + spr->_flags._port = (port < 0) ? !spr->_flags._port : (port != 0); +} + +void CGE2Engine::snMouse(bool on) { + if (on) + _mouse->on(); + else + _mouse->off(); +} + +void CGE2Engine::snNNext(Sprite *spr, Action act, int val) { + if (spr) { + if (val > 255) + val = spr->labVal(act, val >> 8); + spr->_actionCtrl[act]._ptr = val; + } +} + +void CGE2Engine::snRNNext(Sprite *spr, int val) { + if (spr) + spr->_actionCtrl[kNear]._ptr += val; +} + +void CGE2Engine::snRMTNext(Sprite *spr, int val) { + if (spr) + spr->_actionCtrl[kMTake]._ptr += val; +} + +void CGE2Engine::snRFTNext(Sprite * spr, int val) { + if (spr) + spr->_actionCtrl[kFTake]._ptr += val; +} + +void CGE2Engine::snRmNear(Sprite *spr) { + if (spr) + spr->_actionCtrl[kNear]._cnt = 0; +} + +void CGE2Engine::snRmMTake(Sprite *spr) { + if (spr) + spr->_actionCtrl[kMTake]._cnt = 0; +} + +void CGE2Engine::snRmFTake(Sprite *spr) { + if (spr) + spr->_actionCtrl[kFTake]._cnt = 0; +} + +void CGE2Engine::snSetRef(Sprite *spr, int val) { + if (spr) + spr->_ref = val; +} + +void CGE2Engine::snFlash(bool on) { + if (on) { + Dac *pal = (Dac *)malloc(sizeof(Dac) * kPalCount); + if (pal) { + memcpy(pal, _vga->_sysPal, kPalSize); + for (int i = 0; i < kPalCount; i++) { + register int c; + c = pal[i]._r << 1; + pal[i]._r = (c < 64) ? c : 63; + c = pal[i]._g << 1; + pal[i]._g = (c < 64) ? c : 63; + c = pal[i]._b << 1; + pal[i]._b = (c < 64) ? c : 63; + } + _vga->setColors(pal, 64); + } + + free(pal); + } else + _vga->setColors(_vga->_sysPal, 64); + _dark = false; +} + +void CGE2Engine::snCycle(int cnt) { + _vga->_rot._len = cnt; +} + +void CGE2Engine::snWalk(Sprite *spr, int val) { + if (isHero(spr)) { + if (val < kMaxPoint) + ((Hero *)spr)->walkTo(*_point[val]); + else { + Sprite *s = _vga->_showQ->locate(val); + if (s) + ((Hero *)spr)->walkTo(s); + } + ((Hero *)spr)->_time = 1; + } +} + +void CGE2Engine::snReach(Sprite *spr, int val) { + if (isHero(spr)) + ((Hero *)spr)->reach(val); +} + +void CGE2Engine::snSound(Sprite *spr, int wav, Audio::Mixer::SoundType soundType) { + if (wav == -1) + _sound->stop(); + else { + if (_sound->_smpinf._counter && wav < 20) + return; + if (_soundStat._wait && ((wav & 255) > 80)) + return; + + _soundStat._ref[1] = wav; + _soundStat._ref[0] = !_fx->exist(_soundStat._ref[1]); + _sound->play(soundType, _fx->load(_soundStat._ref[1], _soundStat._ref[0]), + (spr) ? (spr->_pos2D.x / (kScrWidth / 16)) : 8); + } +} + +void CGE2Engine::snRoom(Sprite *spr, bool on) { + if (!isHero(spr)) + return; + + int sex = spr->_ref & 1; + Sprite **p = _heroTab[sex]->_pocket; + if (on) { + if (freePockets(sex) == 0 && p[kPocketMax] == nullptr) { + SWAP(p[kPocketMax], p[kPocketMax - 1]); + snHide(p[kPocketMax], 1); + } + } else if (p[kPocketMax]) { + for (int i = 0; i < kPocketMax; i++) { + if (p[i] == nullptr) { + snHide(p[kPocketMax], 0); + SWAP(p[kPocketMax], p[i]); + break; + } + } + } +} + +void CGE2Engine::snGhost(Bitmap *bmp) { + V2D p(this, bmp->_map & 0xFFFF, bmp->_map >> 16); + bmp->hide(p); + bmp->release(); + delete[] bmp->_b; + bmp->_b = nullptr; + delete bmp; + bmp = nullptr; +} + +void CGE2Engine::snSay(Sprite *spr, int val) { + if (spr && spr->active() && _commandHandler->_talkEnable) { + //-- mouth animation + if (isHero(spr) && spr->seqTest(-1)) + ((Hero *)spr)->say(); + if (_sayCap) + _text->say(_text->getText(val), spr); + if (_sayVox) { + int i = val; + if (i < 256) + i -= 100; + int16 oldRepeat = _sound->getRepeat(); + _sound->setRepeat(1); + snSound(spr, i, Audio::Mixer::kSpeechSoundType); + _sound->setRepeat(oldRepeat); + _soundStat._wait = &_sound->_smpinf._counter; + } + } +} + +void CGE2Engine::hide1(Sprite *spr) { + _commandHandlerTurbo->addCommand(kCmdGhost, -1, 0, spr->ghost()); +} + +void CGE2Engine::swapInPocket(Sprite *spr, Sprite *xspr) { + for (int i = 0; i < 2; i++) { + for (int j = 0; j < kPocketMax; j++) { + Sprite *&poc = _heroTab[i]->_pocket[j]; + if (poc == spr) { + spr->_flags._kept = false; + poc = xspr; + xspr->_flags._kept = true; + xspr->_flags._port = false; + return; + } + } + } +} + +Sprite *CGE2Engine::expandSprite(Sprite *spr) { + if (spr) + _vga->_showQ->insert(spr); + return spr; +} + +void CGE2Engine::qGame() { + // Write out the user's progress + saveGame(0, Common::String("Automatic Savegame")); + + busy(false); + _vga->sunset(); + _endGame = true; +} + +void CGE2Engine::xScene() { + sceneDown(); + sceneUp(_req); +} + +void CommandHandler::addCommand(CommandType com, int ref, int val, void *ptr) { + if (ref == -2) + ref = 142 - _vm->_sex; + Command *headCmd = &_commandList[_head++]; + headCmd->_commandType = com; + headCmd->_ref = ref; + headCmd->_val = val; + headCmd->_spritePtr = ptr; + headCmd->_cbType = kNullCB; + if (headCmd->_commandType == kCmdClear) { + clear(); + } +} + +void CommandHandler::addCallback(CommandType com, int ref, int val, CallbackType cbType) { + Command *headCmd = &_commandList[_head++]; + headCmd->_commandType = com; + headCmd->_ref = ref; + headCmd->_val = val; + headCmd->_spritePtr = nullptr; + headCmd->_cbType = cbType; + if (headCmd->_commandType == kCmdClear) { + _tail = _head; + _vm->killText(); + _timerExpiry = 0; + } +} + +void CommandHandler::insertCommand(CommandType com, int ref, int val, void *ptr) { + if (ref == -2) + ref = 142 - _vm->_sex; + --_tail; + Command *tailCmd = &_commandList[_tail]; + tailCmd->_commandType = com; + tailCmd->_ref = ref; + tailCmd->_val = val; + tailCmd->_spritePtr = ptr; + tailCmd->_cbType = kNullCB; + if (com == kCmdClear) { + _tail = _head; + _vm->killText(); + _timerExpiry = 0; + } +} + +bool CommandHandler::idle() { + return (!_vm->_waitRef && _head == _tail); +} + +void CommandHandler::clear() { + _tail = _head; + _vm->killText(); + _timerExpiry = 0; +} + +int CommandHandler::getComId(const char *com) { + int i = _vm->takeEnum(_commandText, com); + return (i < 0) ? i : i + kCmdCom0 + 1; +} + +const char *CommandHandler::getComStr(CommandType cmdType) { + return _commandText[cmdType - kCmdNop]; +} + +void CGE2Engine::feedSnail(Sprite *spr, Action snq, Hero *hero) { + if (!spr || !spr->active()) + return; + + int cnt = spr->_actionCtrl[snq]._cnt; + if (cnt) { + byte ptr = spr->_actionCtrl[snq]._ptr; + CommandHandler::Command *comtab = spr->snList(snq); + CommandHandler::Command *c = &comtab[ptr]; + CommandHandler::Command *q = &comtab[cnt]; + + if (hero != nullptr) { + int pocFre = freePockets(hero->_ref & 1); + int pocReq = 0; + CommandHandler::Command *p = c; + for (; p < q && p->_commandType != kCmdNext; p++) { // scan commands + // drop from pocket? + if ((p->_commandType == kCmdSend && p->_val != _now) + || p->_commandType == kCmdGive) { + int ref = p->_ref; + if (ref < 0) + ref = spr->_ref; + if (findActivePocket(ref) >= 0) + --pocReq; + } + // make/dispose additional room? + if (p->_commandType == kCmdRoom) { + if (p->_val == 0) + ++pocReq; + else + --pocReq; + } + // put into pocket? + if (p->_commandType == kCmdKeep) + ++pocReq; + // overloaded? + if (pocReq > pocFre) { + pocFul(); + return; + } + } + } + + while (c < q) { + if ((c->_val == -1) && (c->_commandType == kCmdWalk || c->_commandType == kCmdReach)) + c->_val = spr->_ref; + + if (c->_commandType == kCmdNext) { + Sprite *s; + + switch (c->_ref) { + case -2: + s = hero; + break; + case -1: + s = spr; + break; + default: + s = _vga->_showQ->locate(c->_ref); + break; + } + + if (s && s->_actionCtrl[snq]._cnt) { + int v; + switch (c->_val) { + case -1: + v = int(c - comtab + 1); + break; + case -2: + v = int(c - comtab); + break; + case -3: + v = -1; + break; + default: + v = c->_val; + if ((v > 255) && s) + v = s->labVal(snq, v >> 8); + break; + } + if (v >= 0) + s->_actionCtrl[snq]._ptr = v; + } + + if (s == spr) + break; + } + + _commandHandler->addCommand(c->_commandType, c->_ref, c->_val, spr); + + ++c; + } + } + +} + +} // End of namespace CGE2. diff --git a/engines/cge2/snail.h b/engines/cge2/snail.h new file mode 100644 index 0000000000..7e618daac8 --- /dev/null +++ b/engines/cge2/snail.h @@ -0,0 +1,129 @@ +/* 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. + * + */ + +/* + * This code is based on original Sfinx source code + * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon + */ + +#ifndef CGE2_SNAIL_H +#define CGE2_SNAIL_H + +#include "cge2/cge2.h" + +namespace CGE2 { + +#define kCommandFrameRate 80 +#define kCommandFrameDelay (1000 / kCommandFrameRate) +#define kNoByte -1 // Recheck this! We have no proof for it's original value. + + +enum CommandType { + kCmdCom0 = 128, + kCmdNop, // NOP :: do nothing + kCmdUse, // USE <spr> <cav>|<lab> :: hint for using + kCmdPause, // PAUSE -1 <dly> :: delay <dly>/72 seconds + kCmdInf, // INF -1 <ref> :: show text referrenced by <ref> + kCmdCave, // CAVE -1 <cav> :: go to board <cav> + kCmdSetX, // SETX <x> <idx> :: set sprite shift in x axis + kCmdSetY, // SETX <y> <idx> :: set sprite shift in y axis + kCmdSetZ, // SETX <z> <idx> :: set sprite shift in z axis + kCmdAdd, // ADD <idx1> <idx2> :: sum vectors + kCmdFlash, // FLASH -1 0|1 :: lighten whole image (on/off) + kCmdCycle, // CYCLE <cnt> :: rotate <cnt> colors from 1 + kCmdClear, // CLEAR -1 0 :: clear kCmdAIL queue + kCmdMouse, // MOUSE -1 0|1 :: enable mouse (on/off) + kCmdMap, // MAP 0|1 0 :: temporarily turn off map for hero + kCmdMidi, // MIDI -1 <midi> :: play MIDI referenced by <midi> (-1 = off) + + kCmdSpr, + + kCmdWait, // WAIT <spr> <seq>|-1 :: wait for SEQ <seq> (-1 = freeze) + kCmdHide, // HIDE <spr> 0|1 :: visibility of sprite + kCmdRoom, // ROOM <hero> 0|1 :: additional room in pocket (no/yes) + kCmdSay, // SAY <spr> <ref> :: say text referenced by <ref> + kCmdSound, // SOUND <spr> <ref> :: play sound effect referenced by <ref> + kCmdKill, // KILL <spr> 0 :: remove sprite + kCmdRSeq, // RSEQ <spr> <nr> :: relative jump SEQ <nr> lines + kCmdSeq, // SEQ <spr> <seq> :: jump to certain SEQ + kCmdSend, // SEND <spr> <cav> :: move sprite to board <cav> + kCmdSwap, // SWAP <spr1> spr2> :: sprite exchange + kCmdKeep, // KEEP <spr> <seq> :: take sprite into pocket and jump to <seq> + kCmdGive, // GIVE <spr> <seq> :: remove sprite from pocket and jump to <seq> + kCmdGetPos, // GETPOS <spr> <idx> :: take sprite's position + kCmdGoto, // GOTO <spr> <idx> :: move sprite to position + kCmdPort, // PORT <spr> 0|1 :: clear/set "takeability" of sprite + kCmdNext, // NEXT <spr> <nr> :: jump to <nr> - NEAR or TAKE + kCmdNNext, // NNEXT <spr> <nr> :: jump to <nr> - NEAR + kCmdMTNext, // MTNEXT <spr> <nr> :: jump to <nr> - TAKE + kCmdFTNext, // FTNEXT <spr> <nr> :: jump to <nr> - TAKE + kCmdRNNext, // RNNEXT <spr> <nr> :: relative jump to <nr> - NEAR + kCmdRMTNext, // RMTNEXT <spr> <nr> :: relative jump to <nr> - TAKE + kCmdRFTNext, // RFTNEXT <spr> <nr> :: relative jump to <nr> - TAKE + kCmdRMNear, // RMNEAR <spr> 0 :: remove NEAR list + kCmdRMMTake, // RMMTAKE <spr> 0 :: remove TAKE list + kCmdRMFTake, // RMFTAKE <spr> 0 :: remove TAKE list + kCmdSetRef, // SETREF <spr> <ref> :: change reference of sprite <spr> to <ref> + kCmdWalk, // WALKTO <hero> <ref>|<point> :: go close to the sprite or point + kCmdReach, // REACH <hero> <ref>|<m> :: reach the sprite or point with <m> method + kCmdCover, // COVER <sp1> <sp2> :: cover sprite <sp1> with sprite <sp2> + kCmdUncover, // UNCOVER <sp1> <sp2> :: restore the state before COVER + + kCmdExec, + kCmdGhost +}; + +class CommandHandler { +public: + struct Command { + CommandType _commandType; + byte _lab; + int _ref; + int _val; + void *_spritePtr; + CallbackType _cbType; + } *_commandList; + static const char *_commandText[]; + bool _talkEnable; + + CommandHandler(CGE2Engine *vm, bool turbo); + ~CommandHandler(); + void runCommand(); + void addCommand(CommandType com, int ref, int val, void *ptr); + void addCallback(CommandType com, int ref, int val, CallbackType cbType); + void insertCommand(CommandType com, int ref, int val, void *ptr); + bool idle(); + void clear(); + int getComId(const char *com); + const char *getComStr(CommandType cmdType); +private: + CGE2Engine *_vm; + bool _turbo; + uint8 _head; + uint8 _tail; + bool _textDelay; + uint32 _timerExpiry; // "pause" in the original. +}; + +} // End of namespace CGE2 + +#endif diff --git a/engines/cge2/sound.cpp b/engines/cge2/sound.cpp new file mode 100644 index 0000000000..c34eb00c01 --- /dev/null +++ b/engines/cge2/sound.cpp @@ -0,0 +1,273 @@ +/* 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. + * + */ + +/* + * This code is based on original Sfinx source code + * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon + */ + +#include "cge2/sound.h" +#include "common/config-manager.h" +#include "common/memstream.h" +#include "audio/decoders/raw.h" +#include "audio/audiostream.h" +#include "cge2/cge2.h" + +namespace CGE2 { + +DataCk::DataCk(byte *buf, int bufSize) { + _buf = buf; + _ckSize = bufSize; +} + +DataCk::~DataCk() { + free(_buf); +} + +Sound::Sound(CGE2Engine *vm) : _vm(vm) { + _audioStream = nullptr; + _soundRepeatCount = 1; + open(); +} + +Sound::~Sound() { + close(); +} + +void Sound::close() { + _vm->_midiPlayer->killMidi(); +} + +void Sound::open() { + setRepeat(1); + if (_vm->_commandHandlerTurbo != nullptr) + _vm->switchSay(); + play(Audio::Mixer::kSFXSoundType, _vm->_fx->load(99, 99)); +} + +void Sound::setRepeat(int16 count) { + _soundRepeatCount = count; +} + +int16 Sound::getRepeat() { + return _soundRepeatCount; +} + +void Sound::play(Audio::Mixer::SoundType soundType, DataCk *wav, int pan) { + if (wav) { + stop(); + _smpinf._saddr = &*(wav->addr()); + _smpinf._slen = (uint16)wav->size(); + _smpinf._span = pan; + _smpinf._counter = getRepeat(); + sndDigiStart(&_smpinf, soundType); + } +} + +void Sound::sndDigiStart(SmpInfo *PSmpInfo, Audio::Mixer::SoundType soundType) { + // Create an audio stream wrapper for sound + Common::MemoryReadStream *stream = new Common::MemoryReadStream(PSmpInfo->_saddr, + PSmpInfo->_slen, DisposeAfterUse::NO); + _audioStream = Audio::makeWAVStream(stream, DisposeAfterUse::YES); + + // Decide which handle to use + Audio::SoundHandle *handle = nullptr; + switch (soundType) { + case Audio::Mixer::kSFXSoundType: + handle = &_soundHandle; + break; + case Audio::Mixer::kSpeechSoundType: + handle = &_speechHandle; + break; + default: + break; + } + + // Start the new sound + _vm->_mixer->playStream(soundType, handle, + Audio::makeLoopingAudioStream(_audioStream, (uint)PSmpInfo->_counter)); + + // CGE pan: + // 8 = Center + // Less = Left + // More = Right + _vm->_mixer->setChannelBalance(*handle, (int8)CLIP(((PSmpInfo->_span - 8) * 16), -127, 127)); +} + +void Sound::stop() { + sndDigiStop(_soundHandle); + sndDigiStop(_speechHandle); + _audioStream = nullptr; +} + +void Sound::checkSoundHandles() { + if (!_vm->_mixer->isSoundHandleActive(_speechHandle) && !_vm->_mixer->isSoundHandleActive(_soundHandle)) + _smpinf._counter = 0; +} + +void Sound::sndDigiStop(Audio::SoundHandle &handle) { + if (_vm->_mixer->isSoundHandleActive(handle)) + _vm->_mixer->stopHandle(handle); +} + +Fx::Fx(CGE2Engine *vm, int size) : _current(nullptr), _vm(vm) { +} + +Fx::~Fx() { + clear(); +} + +void Fx::clear() { + if (_current) + delete _current; + _current = nullptr; +} + +Common::String Fx::name(int ref, int sub) { + char fxname[] = "%.2dfx%.2d.WAV\0"; + char subName[] = "%.2dfx%.2d?.WAV\0"; + char *p = (sub) ? subName : fxname; + Common::String filename = Common::String::format(p, ref >> 8, ref & 0xFF); + if (sub) + filename.setChar('@' + sub, 6); + return filename; +} + +bool Fx::exist(int ref, int sub) { + return _vm->_resman->exist(name(ref, sub).c_str()); +} + +DataCk *Fx::load(int ref, int sub) { + Common::String filename = name(ref, sub); + EncryptedStream file(_vm, filename.c_str()); + clear(); + return (_current = loadWave(&file)); +} + +DataCk *Fx::loadWave(EncryptedStream *file) { + byte *data = (byte *)malloc(file->size()); + + if (!data) + return 0; + + file->read(data, file->size()); + + return new DataCk(data, file->size()); +} + +MusicPlayer::MusicPlayer(CGE2Engine *vm) : _vm(vm) { + _data = nullptr; + _isGM = false; + + MidiPlayer::createDriver(); + + int ret = _driver->open(); + if (ret == 0) { + if (_nativeMT32) + _driver->sendMT32Reset(); + else + _driver->sendGMReset(); + + // TODO: Load cmf.ins with the instrument table. It seems that an + // interface for such an operation is supported for AdLib. Maybe for + // this card, setting instruments is necessary. + + _driver->setTimerCallback(this, &timerCallback); + } + _dataSize = -1; +} + +MusicPlayer::~MusicPlayer() { + killMidi(); +} + +void MusicPlayer::killMidi() { + Audio::MidiPlayer::stop(); + + free(_data); + _data = nullptr; +} + +void MusicPlayer::loadMidi(int ref) { + if (_vm->_midiNotify != nullptr) + (_vm->*_vm->_midiNotify)(); + + // Work out the filename and check the given MIDI file exists + Common::String filename = Common::String::format("%.2dSG%.2d.MID", ref >> 8, ref & 0xFF); + if (!_vm->_resman->exist(filename.c_str())) + return; + + // Stop any currently playing MIDI file + killMidi(); + + // Read in the data for the file + EncryptedStream mid(_vm, filename.c_str()); + _dataSize = mid.size(); + _data = (byte *)malloc(_dataSize); + mid.read(_data, _dataSize); + + // Start playing the music + sndMidiStart(); +} + +void MusicPlayer::sndMidiStart() { + _isGM = true; + + MidiParser *parser = MidiParser::createParser_SMF(); + if (parser->loadMusic(_data, _dataSize)) { + parser->setTrack(0); + parser->setMidiDriver(this); + parser->setTimerRate(_driver->getBaseTempo()); + parser->property(MidiParser::mpCenterPitchWheelOnUnload, 1); + + _parser = parser; + + syncVolume(); + + // Al the tracks are supposed to loop + _isLooping = true; + _isPlaying = true; + } +} + +void MusicPlayer::send(uint32 b) { + if (((b & 0xF0) == 0xC0) && !_isGM && !_nativeMT32) { + b = (b & 0xFFFF00FF) | MidiDriver::_mt32ToGm[(b >> 8) & 0xFF] << 8; + } + + Audio::MidiPlayer::send(b); +} + +void MusicPlayer::sendToChannel(byte channel, uint32 b) { + if (!_channelsTable[channel]) { + _channelsTable[channel] = (channel == 9) ? _driver->getPercussionChannel() : _driver->allocateChannel(); + // If a new channel is allocated during the playback, make sure + // its volume is correctly initialized. + if (_channelsTable[channel]) + _channelsTable[channel]->volume(_channelsVolume[channel] * _masterVolume / 255); + } + + if (_channelsTable[channel]) + _channelsTable[channel]->send(b); +} + +} // End of namespace CGE2 diff --git a/engines/cge2/sound.h b/engines/cge2/sound.h new file mode 100644 index 0000000000..2960dc32e4 --- /dev/null +++ b/engines/cge2/sound.h @@ -0,0 +1,131 @@ +/* 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. + * + */ + +/* + * This code is based on original Sfinx source code + * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon + */ + +#ifndef CGE2_SOUND_H +#define CGE2_SOUND_H + +#include "cge2/fileio.h" +#include "audio/audiostream.h" +#include "audio/decoders/wave.h" +#include "audio/fmopl.h" +#include "audio/mididrv.h" +#include "audio/midiparser.h" +#include "audio/midiplayer.h" +#include "audio/mixer.h" +#include "common/memstream.h" + +namespace CGE2 { + +class CGE2Engine; + +// sample info +struct SmpInfo { + const uint8 *_saddr; // address + uint16 _slen; // length + uint16 _span; // left/right pan (0-15) + int _counter; // number of time the sample should be played +}; + +class DataCk { + byte *_buf; + int _ckSize; +public: + DataCk(byte *buf, int bufSize); + ~DataCk(); + inline const byte *addr() { + return _buf; + } + inline int size() { + return _ckSize; + } +}; + +class Sound { +public: + SmpInfo _smpinf; + + Sound(CGE2Engine *vm); + ~Sound(); + void open(); + void close(); + void play(Audio::Mixer::SoundType soundType, DataCk *wav, int pan = 8); + int16 getRepeat(); + void setRepeat(int16 count); + void stop(); + void checkSoundHandles(); +private: + int _soundRepeatCount; + CGE2Engine *_vm; + Audio::SoundHandle _soundHandle; + Audio::SoundHandle _speechHandle; + Audio::RewindableAudioStream *_audioStream; + + void sndDigiStart(SmpInfo *PSmpInfo, Audio::Mixer::SoundType soundType); + void sndDigiStop(Audio::SoundHandle &handle); +}; + +class Fx { + CGE2Engine *_vm; + + DataCk *loadWave(EncryptedStream *file); + Common::String name(int ref, int sub); +public: + DataCk *_current; + + Fx(CGE2Engine *vm, int size); + ~Fx(); + void clear(); + bool exist(int ref, int sub = 0); + DataCk *load(int ref, int sub = 0); +}; + +class MusicPlayer: public Audio::MidiPlayer { +private: + CGE2Engine *_vm; + byte *_data; + int _dataSize; + bool _isGM; + + // Start MIDI File + void sndMidiStart(); + + // Stop MIDI File + void sndMidiStop(); +public: + MusicPlayer(CGE2Engine *vm); + ~MusicPlayer(); + + void loadMidi(int ref); + void killMidi(); + + virtual void send(uint32 b); + virtual void sendToChannel(byte channel, uint32 b); +}; + +} // End of namespace CGE2 + +#endif // CGE2_SOUND_H diff --git a/engines/cge2/spare.cpp b/engines/cge2/spare.cpp new file mode 100644 index 0000000000..53f99e4e67 --- /dev/null +++ b/engines/cge2/spare.cpp @@ -0,0 +1,129 @@ +/* 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. + * + */ + +/* + * This code is based on original Sfinx source code + * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon + */ + +#include "cge2/spare.h" + +namespace CGE2 { + +void Spare::sync(Common::Serializer &s) { + if (s.isSaving()) { + int size = 0; + for (uint i = 0; i < _container.size(); i++) + if (_container[i]->_ref >= 141) + size++; + s.syncAsSint16LE(size); + + for (uint i = 0; i < _container.size(); i++) { + if (_container[i]->_ref >= 141) + _container[i]->sync(s); + } + } else { + int size; + s.syncAsSint16LE(size); + + for (int i = 0; i < size; i++) { + Sprite *sprite = new Sprite(_vm); + sprite->sync(s); + update(sprite); + } + } +} + +void Spare::clear() { + for (uint i = 0; i < _container.size(); i++) + delete _container[i]; + + _container.clear(); +} + +Sprite *Spare::locate(int ref) { + for (uint i = 0; i < _container.size(); ++i) { + if (_container[i]->_ref == ref) + return _container[i]; + } + return nullptr; +} + +Sprite *Spare::take(int ref) { + Sprite *spr = nullptr; + if ((spr = locate(ref)) != nullptr) { + for (uint i = 0; i < _container.size(); ++i) { + if (spr == _container[i]) { + _container.remove_at(i); + break; + } + } + } + return spr; +} + +void Spare::takeScene(int cav) { + int bakRef = cav << 8; + Common::Array<Sprite*> tempCont = _container; + for (uint i = 0; i < tempCont.size(); ++i) { + Sprite *spr = tempCont[i]; + int c = spr->_scene; + if ((c == _vm->_now || c == 0) && spr->_ref != bakRef) { + spr = locate(spr->_ref); + _vm->_vga->_showQ->insert(spr); + } + } +} + +void Spare::store(Sprite *spr) { + _container.push_back(spr); +} + +void Spare::update(Sprite *spr) { + Sprite *sp = locate(spr->_ref); + if (sp == nullptr) + store(spr); + else { + sp->contract(); + *sp = *spr; + } +} + +void Spare::dispose(Sprite *spr) { + if (spr) { + _vm->_vga->_showQ->remove(spr); + update(spr->contract()); + } +} + +void Spare::dispose(int ref) { + dispose(_vm->_vga->_showQ->locate(ref)); +} + +void Spare::dispose() { + for (uint i = 0; i < _container.size(); ++i) { + if (_container[i]->_ref > 255) + dispose(_container[i]); + } +} + +} // End of namespace CGE2 diff --git a/engines/cge2/spare.h b/engines/cge2/spare.h new file mode 100644 index 0000000000..7dc6ce60f5 --- /dev/null +++ b/engines/cge2/spare.h @@ -0,0 +1,56 @@ +/* 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. + * + */ + +/* + * This code is based on original Sfinx source code + * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon + */ + +#ifndef CGE2_SPARE_H +#define CGE2_SPARE_H + +#include "cge2/vga13h.h" + +namespace CGE2 { + +class Spare { + CGE2Engine *_vm; + Common::Array<Sprite*> _container; +public: + Spare(CGE2Engine *vm) : _vm(vm) {} + ~Spare() { clear(); } + void store(Sprite *spr); + Sprite *locate(int ref); + Sprite *take(int ref); + void takeScene(int cav); + void update(Sprite *spr); + void dispose(Sprite *spr); + void dispose(int ref); + void dispose(); + void sync(Common::Serializer &s); + uint16 count() { return _container.size(); } + void clear(); +}; + +} // End of namespace CGE2 + +#endif // CGE2_SPARE_H diff --git a/engines/cge2/talk.cpp b/engines/cge2/talk.cpp new file mode 100644 index 0000000000..c39e064c22 --- /dev/null +++ b/engines/cge2/talk.cpp @@ -0,0 +1,312 @@ +/* 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. + * + */ + +/* + * This code is based on original Sfinx source code + * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon + */ + +#include "cge2/general.h" +#include "cge2/talk.h" + +namespace CGE2 { + +void CGE2Engine::setAutoColors() { + Dac def[4] = { + { 0, 0, 0 }, + { 220 >> 2, 220 >> 2, 220 >> 2 }, + { 190 >> 2, 190 >> 2, 190 >> 2 }, + { 160 >> 2, 160 >> 2, 160 >> 2 }, + }; + Dac pal[kPalCount]; + _vga->getColors(pal); + for (int i = 0; i < 4; i++) + _font->_colorSet[kCBRel][i] = _vga->closest(pal, def[i]); +} + +Font::Font(CGE2Engine *vm) : _vm(vm) { + _map = new uint8[kMapSize]; + _pos = new uint16[kPosSize]; + _widthArr = new uint8[kWidSize]; + + load(); +} + +Font::~Font() { + delete[] _map; + delete[] _pos; + delete[] _widthArr; +} + +void Font::load() { + char path[10]; + strcpy(path, "CGE.CFT"); + if (!_vm->_resman->exist(path)) + error("Missing Font file! %s", path); + + EncryptedStream fontFile(_vm, path); + assert(!fontFile.err()); + + fontFile.read(_widthArr, kWidSize); + assert(!fontFile.err()); + + uint16 p = 0; + for (uint16 i = 0; i < kPosSize; i++) { + _pos[i] = p; + p += _widthArr[i]; + } + fontFile.read(_map, p); + + strcpy(path, "CGE.TXC"); + if (!_vm->_resman->exist(path)) + error("Missing Color file! %s", path); + + // Reading in _colorSet: + EncryptedStream colorFile(_vm, path); + assert(!colorFile.err()); + + char tmpStr[kLineMax + 1]; + int n = 0; + + for (Common::String line = colorFile.readLine(); !colorFile.eos(); line = colorFile.readLine()){ + if (line.empty()) + continue; + Common::strlcpy(tmpStr, line.c_str(), sizeof(tmpStr)); + _colorSet[n][0] = _vm->number(tmpStr); + + for (int i = 1; i < 4; i++) + _colorSet[n][i] = _vm->number(nullptr); + + n++; + } +} + +uint16 Font::width(const char *text) { + uint16 w = 0; + if (!text) + return 0; + while (*text) + w += _widthArr[(unsigned char)*(text++)]; + return w; +} + +Talk::Talk(CGE2Engine *vm, const char *text, TextBoxStyle mode, ColorBank color, bool wideSpace) + : Sprite(vm), _mode(mode), _created(false), _wideSpace(wideSpace), _vm(vm) { + _color = _vm->_font->_colorSet[color]; + + if (color == kCBRel) + _vm->setAutoColors(); + update(text); +} + +Talk::Talk(CGE2Engine *vm, ColorBank color) + : Sprite(vm), _mode(kTBPure), _vm(vm) { + _color = _vm->_font->_colorSet[color]; + + if (color == kCBRel) + _vm->setAutoColors(); +} + +uint8 *Talk::box(V2D siz) { + uint16 n, r = (_mode == kTBRound) ? kTextRoundCorner : 0; + const byte lt = _color[1], bg = _color[2], dk = _color[3]; + + if (siz.x < 8) + siz.x = 8; + if (siz.y < 8) + siz.y = 8; + uint8 *b = new uint8[n = siz.area()]; + memset(b, bg, n); + + if (_mode) { + uint8 *p = b; + uint8 *q = b + n - siz.x; + memset(p, lt, siz.x); + memset(q, dk, siz.x); + while (p < q) { + p += siz.x; + *(p - 1) = dk; + *p = lt; + } + p = b; + for (int i = 0; i < r; i++) { + int j = 0; + for (; j < r - i; j++) { + p[j] = kPixelTransp; + p[siz.x - j - 1] = kPixelTransp; + q[j] = kPixelTransp; + q[siz.x - j - 1] = kPixelTransp; + } + p[j] = lt; + p[siz.x - j - 1] = dk; + q[j] = lt; + q[siz.x - j - 1] = dk; + p += siz.x; + q -= siz.x; + } + } + return b; +} + +void Talk::update(const char *text) { + const uint16 vmarg = (_mode) ? kTextVMargin : 0; + const uint16 hmarg = (_mode) ? kTextHMargin : 0; + uint16 mw; + uint16 mh; + uint16 ln = vmarg; + uint8 *m; + uint8 *map; + uint8 fg = _color[0]; + + if (_created) { + mw = _ext->_shpList->_w; + mh = _ext->_shpList->_h; + delete _ext->_shpList; + } else { + uint16 k = 2 * hmarg; + mh = 2 * vmarg + kFontHigh; + mw = 0; + for (const char *p = text; *p; p++) { + if ((*p == '|') || (*p == '\n')) { + mh += kFontHigh + kTextLineSpace; + if (k > mw) + mw = k; + k = 2 * hmarg; + } else if ((*p == 0x20) && (_vm->_font->_widthArr[(unsigned char)*p] > 4) && (!_wideSpace)) + k += _vm->_font->_widthArr[(unsigned char)*p] - 2; + else + k += _vm->_font->_widthArr[(unsigned char)*p]; + } + if (k > mw) + mw = k; + + _created = true; + } + + V2D sz(_vm, mw, mh); + map = box(sz); + + m = map + ln * mw + hmarg; + + while (*text) { + if ((*text == '|') || (*text == '\n')) + m = map + (ln += kFontHigh + kTextLineSpace) * mw + hmarg; + else { + int cw = _vm->_font->_widthArr[(unsigned char)*text]; + uint8 *f = _vm->_font->_map + _vm->_font->_pos[(unsigned char)*text]; + + // Handle properly space size, after it was enlarged to display properly + // 'F1' text. + int8 fontStart = 0; + if ((*text == 0x20) && (cw > 4) && (!_wideSpace)) + fontStart = 2; + + for (int i = fontStart; i < cw; i++) { + uint8 *pp = m; + uint16 n; + uint16 b = *(f++); + for (n = 0; n < kFontHigh; n++) { + if (b & 1) + *pp = fg; + b >>= 1; + pp += mw; + } + m++; + } + } + text++; + } + BitmapPtr b = new Bitmap[1]; + b[0] = Bitmap(_vm, sz.x, sz.y, map); + delete[] map; + setShapeList(b, 1); +} + +InfoLine::InfoLine(CGE2Engine *vm, uint16 w, ColorBank color) +: Talk(vm), _oldText(nullptr), _newText(nullptr), _realTime(false), _vm(vm) { + _wideSpace = false; + BitmapPtr b = new Bitmap[1]; + if (color == kCBRel) + _vm->setAutoColors(); + _color = _vm->_font->_colorSet[color]; + V2D siz = V2D(_vm, w, kFontHigh); + b[0] = Bitmap(_vm, siz.x, siz.y, _color[2]); + setShapeList(b, 1); +} + +void InfoLine::update(const char *text) { + if (!_realTime && (text == _oldText)) + return; + + _oldText = text; + + uint16 w = _ext->_shpList->_w; + uint16 h = _ext->_shpList->_h; + uint8 *v = _ext->_shpList->_v; + uint16 dsiz = w >> 2; // data size (1 plane line size) + uint16 lsiz = 2 + dsiz + 2; // uint16 for line header, uint16 for gap + uint16 psiz = h * lsiz; // - last gape, but + plane trailer + uint16 size = 4 * psiz; // whole map size + uint8 fg = _color[0]; + uint8 bg = _color[2]; + + // clear whole rectangle + memset(v + 2, bg, dsiz); // data bytes + for (byte *pDest = v + lsiz; pDest < (v + psiz); pDest += lsiz) { + Common::copy(v, v + lsiz, pDest); + } + *(uint16 *)(v + psiz - 2) = TO_LE_16(kBmpEOI); // plane trailer uint16 + for (byte *pDest = v + psiz; pDest < (v + 4 * psiz); pDest += psiz) { + Common::copy(v, v + psiz, pDest); + } + + // paint text line + if (_newText) { + uint8 *p = v + 2, *q = p + size; + + while (*text) { + uint16 cw = _vm->_font->_widthArr[(unsigned char)*text]; + uint8 *fp = _vm->_font->_map + _vm->_font->_pos[(unsigned char)*text]; + + // Handle properly space size, after it was enlarged to display properly + // 'F1' text. + int8 fontStart = 0; + if ((*text == 0x20) && (cw > 4) && (!_wideSpace)) + fontStart = 2; + + for (int i = fontStart; i < cw; i++) { + uint16 b = fp[i]; + for (uint16 n = 0; n < kFontHigh; n++) { + if (b & 1) + *p = fg; + b >>= 1; + p += lsiz; + } + if (p >= q) + p = p - size + 1; + } + text++; + } + } +} + +} // End of namespace CGE2 diff --git a/engines/cge2/talk.h b/engines/cge2/talk.h new file mode 100644 index 0000000000..d7484655cc --- /dev/null +++ b/engines/cge2/talk.h @@ -0,0 +1,94 @@ +/* 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. + * + */ + +/* + * This code is based on original Sfinx source code + * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon + */ + +#ifndef CGE2_TALK_H +#define CGE2_TALK_H + +#include "cge2/general.h" +#include "cge2/vga13h.h" + +namespace CGE2 { + +#define kTextHMargin (6&~1) // EVEN horizontal margins! +#define kTextVMargin 5 // vertical margins +#define kTextLineSpace 2 // line spacing +#define kTextRoundCorner 3 // rounded corners +#define kWidSize 256 +#define kPosSize 256 +#define kMapSize (256*8) +#define kFontHigh 8 +#define kCaptionSide 24 +#define kInfName 101 +#define kSayName 102 + +class Font { + void load(); + CGE2Engine *_vm; +public: + uint8 *_widthArr; + uint16 *_pos; + uint8 *_map; + uint8 _colorSet[kColorNum][4]; + Font(CGE2Engine *vm); + ~Font(); + uint16 width(const char *text); +}; + +enum TextBoxStyle { kTBPure, kTBRect, kTBRound }; + +class Talk : public Sprite { +protected: + TextBoxStyle _mode; + bool _created; + uint8 *box(V2D siz); + bool _wideSpace; +public: + uint8 *_color; + + Talk(CGE2Engine *vm, const char *text, TextBoxStyle mode = kTBPure, ColorBank color = kCBStd, bool wideSpace = false); + Talk(CGE2Engine *vm, ColorBank color = kCBStd); + + void update(const char *text); +private: + CGE2Engine *_vm; +}; + +class InfoLine : public Talk { + const char *_oldText, *_newText; +public: + bool _realTime; + InfoLine(CGE2Engine *vm, uint16 wid, ColorBank color = kCBStd); + void update(const char *text); + void update() { update(_newText); } + void setText(const char *txt) { _newText = txt; } +private: + CGE2Engine *_vm; +}; + +} // End of namespace CGE2 + +#endif // CGE2_TALK_H diff --git a/engines/cge2/text.cpp b/engines/cge2/text.cpp new file mode 100644 index 0000000000..d51c04843d --- /dev/null +++ b/engines/cge2/text.cpp @@ -0,0 +1,197 @@ +/* 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. + * + */ + +/* + * This code is based on original Sfinx source code + * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon + */ + +#include "cge2/text.h" +#include "common/str.h" + +namespace CGE2 { + +Text::Text(CGE2Engine *vm, const char *fname) : _vm(vm) { + _vm->mergeExt(_fileName, fname, kSayExt); + if (!_vm->_resman->exist(_fileName)) + error("No talk (%s)", _fileName); + _txtCount = count(); + if (_txtCount == -1) + error("Unable to read dialog file %s", _fileName); + + _txtCount += 2; + _cache = new Handler[_txtCount]; + for (_size = 0; _size < _txtCount; _size++) { + _cache[_size]._ref = 0; + _cache[_size]._text = nullptr; + } + load(); + + _cache[_txtCount - 1]._ref = -1; + _cache[_txtCount - 1]._text = new char[3]; + strcpy(_cache[_txtCount - 1]._text, ""); +} + +Text::~Text() { + clear(); + delete[] _cache; +} + +int16 Text::count() { + EncryptedStream tf(_vm, _fileName); + if (tf.err()) + return -1; + + Common::String line; + char tmpStr[kLineMax + 1]; + + int counter = 0; + + for (line = tf.readLine(); !tf.eos(); line = tf.readLine()) { + char *s; + assert(line.size() <= 513); + Common::strlcpy(tmpStr, line.c_str(), sizeof(tmpStr)); + if ((s = strtok(tmpStr, " =,;/\t\n")) == nullptr) + continue; + if (!Common::isDigit(*s)) + continue; + + counter++; + } + return counter; +} + +void Text::clear() { + for (int i = 0; i < _txtCount; i++) { + if (_cache[i]._ref) { + _cache[i]._ref = 0; + delete[] _cache[i]._text; + _cache[i]._text = nullptr; + } + } +} + +void Text::load() { + EncryptedStream tf(_vm, _fileName); + assert(!tf.err()); + + Common::String line; + char tmpStr[kLineMax + 1]; + int idx; + + for (idx = 0, line = tf.readLine(); !tf.eos(); line = tf.readLine()) { + int n = line.size(); + char *s; + Common::strlcpy(tmpStr, line.c_str(), sizeof(tmpStr)); + if ((s = strtok(tmpStr, " =,;/\t\n")) == nullptr) + continue; + if (!Common::isDigit(*s)) + continue; + + int r = _vm->number(s); + + s += strlen(s); + if (s < tmpStr + n) + ++s; + + _cache[idx]._ref = r; + _cache[idx]._text = new char[strlen(s) + 1]; + strcpy(_cache[idx]._text, s); + idx++; + } +} + +char *Text::getText(int ref) { + int i; + for (i = 0; (i < _size) && (_cache[i]._ref != ref); i++) + ; + + if (i < _size) + return _cache[i]._text; + + warning("getText: Unable to find ref %d:%d", ref / 256, ref % 256); + return nullptr; +} + +void Text::say(const char *text, Sprite *spr) { + _vm->killText(); + + _vm->_talk = new Talk(_vm, text, kTBRound, kCBSay); + + Speaker *speaker = new Speaker(_vm); + + bool east = spr->_flags._east; + V2D d(_vm, 20, spr->_siz.y - 2); + if (!east) + d.x = -d.x; + if (_vm->isHero(spr)) + d = d.scale(spr->_pos3D._z.trunc()); + V2D pos = spr->_pos2D + d; + uint16 sw = (speaker->_siz.x >> 1); + if (!east) + sw = -sw; + + if (east) { + if (pos.x + sw + kTextRoundCorner + kCaptionSide >= kScrWidth) + east = false; + } else if (pos.x <= kCaptionSide + kTextRoundCorner - sw) + east = true; + + if (east != (d.x > 0)) { + d.x = -d.x; + sw = -sw; + } + pos.x = spr->_pos2D.x + d.x + sw; + + _vm->_talk->_flags._kill = true; + _vm->_talk->setName(getText(kSayName)); + _vm->_talk->gotoxyz(pos.x, pos.y + speaker->_siz.y - 1, 0); + + speaker->gotoxyz(pos.x, _vm->_talk->_pos3D._y.trunc() - speaker->_siz.y + 1, 0); + speaker->_flags._slav = true; + speaker->_flags._kill = true; + speaker->setName(getText(kSayName)); + speaker->step(east); + + _vm->_vga->_showQ->append(_vm->_talk); + _vm->_vga->_showQ->append(speaker); +} + +void CGE2Engine::inf(const char *text, ColorBank col) { + killText(); + _talk = new Talk(this, text, kTBRect, col, true); + _talk->_flags._kill = true; + _talk->setName(_text->getText(kInfName)); + _talk->center(); + _vga->_showQ->append(_talk); +} + +void Text::sayTime(Sprite *spr) { + TimeDate curTime; + _vm->_system->getTimeAndDate(curTime); + + char t[6]; + snprintf(t, 6, "%d:%02d", curTime.tm_hour, curTime.tm_min); + say(t, spr); +} + +} // End of namespace CGE2 diff --git a/engines/cge2/text.h b/engines/cge2/text.h new file mode 100644 index 0000000000..88ed501158 --- /dev/null +++ b/engines/cge2/text.h @@ -0,0 +1,68 @@ +/* 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. + * + */ + +/* + * This code is based on original Sfinx source code + * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon + */ + +#ifndef CGE2_TEXT_H +#define CGE2_TEXT_H + +#include "cge2/talk.h" +#include "cge2/cge2.h" + +namespace CGE2 { + +#define kSayExt ".SAY" +#define kSysTextMax 1000 +#define kTextNoMouse 95 +#define kInfName 101 +#define kSayName 102 +#define kInfRef 301 +#define kSayRef 302 + + +class Text { + struct Handler { + int _ref; + char *_text; + } *_cache; + int _size; + int16 _txtCount; + char _fileName[kPathMax]; + void load(); + int16 count(); +public: + Text(CGE2Engine *vm, const char *fname); + ~Text(); + void clear(); + char *getText(int ref); + void say(const char *text, Sprite *spr); + void sayTime(Sprite *spr); +private: + CGE2Engine *_vm; +}; + +} // End of namespace CGE2 + +#endif // CGE2_TEXT_H diff --git a/engines/cge2/toolbar.cpp b/engines/cge2/toolbar.cpp new file mode 100644 index 0000000000..3972df9c67 --- /dev/null +++ b/engines/cge2/toolbar.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. + * + */ + +/* + * This code is based on original Sfinx source code + * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon + */ + +#include "sound.h" +#include "common/config-manager.h" +#include "cge2/cge2.h" +#include "cge2/events.h" +#include "cge2/vmenu.h" +#include "cge2/text.h" +#include "cge2/cge2_main.h" + +namespace CGE2 { + +#define kSoundNumtoStateRate 25.7 +// == 257 / 10; where 10 equals to the volume switches' number of states [0..9] +// and ScummVM has a scale of 257 different values for setting sounds. + +#define kSoundStatetoNumRate 28.45 +// == 256 / 9 + 0.1; where 256 is the positive range of numbers we can set the volume to +// and the 10 states of a switch cut this range up to 9 equally big parts. +// We don't take into account 0 regarding the 256 different values (it would be the 257th), +// since 0 * x == 0. +// 0.1 is only for correct rounding at the 10th state. + +void CGE2Engine::optionTouch(int opt, uint16 mask) { + bool notMuted = !ConfMan.getBool("mute"); + switch (opt) { + case 1: + if (mask & kMouseLeftUp) + switchColorMode(); + break; + case 2: + if ((mask & kMouseLeftUp) && notMuted) + switchMusic(); + break; + case 3: + if (mask & kMouseLeftUp) + quit(); + break; + case 4: + if ((mask & (kMouseLeftUp | kMouseRightUp)) && notMuted) + setVolume(opt - 4, (mask & kMouseLeftUp) ? 1 : -1); + break; + case 5: + if ((mask & (kMouseLeftUp | kMouseRightUp)) && notMuted) + setVolume(opt - 4, (mask & kMouseLeftUp) ? 1 : -1); + break; + case 8: + if ((mask & kMouseLeftUp) && notMuted) + switchCap(); + break; + case 9: + if ((mask & kMouseLeftUp) && notMuted) + switchVox(); + break; + default: + break; + } +} + +void CGE2Engine::switchColorMode() { + _commandHandlerTurbo->addCommand(kCmdSeq, 121, _vga->_mono = !_vga->_mono, nullptr); + ConfMan.setBool("enable_color_blind", _vga->_mono); + ConfMan.flushToDisk(); + keyClick(); + _vga->setColors(_vga->_sysPal, 64); +} + +void CGE2Engine::switchMusic() { + _music = !_music; + _mixer->muteSoundType(Audio::Mixer::kMusicSoundType, !_music); + _commandHandlerTurbo->addCommand(kCmdSeq, kMusicRef, _music, nullptr); + keyClick(); + _commandHandlerTurbo->addCommand(kCmdMidi, -1, _music ? (_now << 8) : -1, nullptr); +} + +void CGE2Engine::quit() { + if (_commandHandler->idle()) { + if (VMenu::_addr) { + _commandHandlerTurbo->addCommand(kCmdKill, -1, 0, VMenu::_addr); + ReturnToGameChoice rqsChoice(this); + rqsChoice.proc(); + } else { + Common::Array<Choice *> quitMenu; // Deleted in VMenu's destructor. + quitMenu.push_back(new ExitGameChoice(this)); + quitMenu.push_back(new ReturnToGameChoice(this)); + (new VMenu(this, quitMenu, V2D(this, -1, -1), kCBMnu))->setName(_text->getText(kQuitTitle)); + _commandHandlerTurbo->addCommand(kCmdSeq, kPowerRef, 0, nullptr); + keyClick(); + } + } +} + +void CGE2Engine::setVolume(int idx, int cnt) { + if (cnt && _vol[idx]) { + int p = _vol[idx]->_seqPtr + cnt; + if ((p >= 0) && (p < _vol[idx]->_seqCnt)) { + _vol[idx]->step(p); + int newVolume = p * kSoundStatetoNumRate; + switch (idx) { + case 0: + ConfMan.setInt("sfx_volume", newVolume); + break; + case 1: + if (newVolume == 0) + _oldMusicVolume = ConfMan.getInt("music_volume"); + ConfMan.setInt("music_volume", newVolume); + break; + default: + break; + } + } + } +} + +void CGE2Engine::checkVolumeSwitches() { + int musicVolume = ConfMan.getInt("music_volume"); + if (musicVolume != _oldMusicVolume) + _vol[1]->step(musicVolume / kSoundNumtoStateRate); + + int sfxVolume = ConfMan.getInt("sfx_volume"); + if (sfxVolume != _oldSfxVolume) { + _vol[0]->step(sfxVolume / kSoundNumtoStateRate); + _oldSfxVolume = sfxVolume; + _sfx = true; + } +} + +void CGE2Engine::switchCap() { + _sayCap = !_sayCap; + if (!_sayCap) + _sayVox = true; + keyClick(); + switchSay(); +} + +void CGE2Engine::switchVox() { + _mixer->muteSoundType(Audio::Mixer::kSpeechSoundType, _sayVox); + _sayVox = !_sayVox; + if (!_sayVox) + _sayCap = true; + keyClick(); + switchSay(); +} + +void CGE2Engine::switchSay() { + _commandHandlerTurbo->addCommand(kCmdSeq, 129, _sayVox, nullptr); + _commandHandlerTurbo->addCommand(kCmdSeq, 128, _sayCap, nullptr); +} + +void CGE2Engine::initToolbar() { + selectPocket(-1); + + _commandHandlerTurbo->addCommand(kCmdSeq, kMusicRef, _music, nullptr); + if (!_music) + _midiPlayer->killMidi(); + + switchSay(); + + _infoLine->gotoxyz(V3D(kInfoX, kInfoY, 0)); + _infoLine->setText(nullptr); + _vga->_showQ->insert(_infoLine); + + _startupMode = 0; + _mouse->center(); + _mouse->off(); + _mouse->on(); + + _keyboard->setClient(_sys); + _commandHandler->addCommand(kCmdSeq, kPowerRef, 1, nullptr); + + _busyPtr = _vga->_showQ->locate(kBusyRef); + + _vol[0] = _vga->_showQ->locate(kDvolRef); + _vol[1] = _vga->_showQ->locate(kMvolRef); + + if (_vol[0]) + initVolumeSwitch(_vol[0]); + + if (_vol[1]) + initVolumeSwitch(_vol[1]); +} + +void CGE2Engine::initVolumeSwitch(Sprite *volSwitch) { + int state = 0; + if (!ConfMan.getBool("mute")) + state = ConfMan.getInt("sfx_volume") / kSoundNumtoStateRate; + volSwitch->step(state); +} + +} // End of namespace CGE2 diff --git a/engines/cge2/vga13h.cpp b/engines/cge2/vga13h.cpp new file mode 100644 index 0000000000..330883141d --- /dev/null +++ b/engines/cge2/vga13h.cpp @@ -0,0 +1,1216 @@ +/* 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. + * + */ + +/* + * This code is based on original Sfinx source code + * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon + */ + +#include "common/array.h" +#include "common/config-manager.h" +#include "common/rect.h" +#include "graphics/palette.h" +#include "cge2/general.h" +#include "cge2/vga13h.h" +#include "cge2/bitmap.h" +#include "cge2/text.h" +#include "cge2/cge2_main.h" +#include "cge2/cge2.h" +#include "cge2/vga13h.h" + +namespace CGE2 { + +void V3D::sync(Common::Serializer &s) { + _x.sync(s); + _y.sync(s); + _z.sync(s); +} + +FXP FXP::operator*(const FXP& x) const { + FXP y; + int32 t1 = (v >> 8) * x.v; + int32 t2 = ((v & 0xFF) * x.v) >> 8; + + y.v = t1 + t2; + return y; +} + +FXP FXP::operator/(const FXP& x) const { + FXP y; + if (x.v != 0) { + int32 v1 = this->v; + int32 v2 = x.v; + bool negFlag = false; + + if (v1 < 0) { + v1 = -v1; + negFlag = true; + } + if (v2 < 0) { + v2 = -v2; + negFlag ^= true; + } + + int32 v3 = v1 / v2; + v1 -= v3 * v2; + v3 <<= 8; + + if (v1 < 0xFFFFFF) + v1 <<= 8; + else + v2 >>= 8; + + v3 += v1 / v2; + + if (negFlag) + v3 = -v3; + + y.v = v3; + } + + return y; +} + +void FXP::sync(Common::Serializer &s) { + s.syncAsSint32LE(v); +} + +Seq *getConstantSeq(bool seqFlag) { + const Seq seq1[] = { { 0, 0, 0, 0, 0, 0 } }; + const Seq seq2[] = { { 0, 1, 0, 0, 0, 0 }, { 1, 0, 0, 0, 0, 0 } }; + + Seq *seq; + if (seqFlag) { + seq = (Seq *)malloc(1 * sizeof(Seq)); + *seq = seq1[0]; + } else { + seq = (Seq *)malloc(2 * sizeof(Seq)); + seq[0] = seq2[0]; + seq[1] = seq2[1]; + } + + return seq; +} + +byte Sprite::_constY = 0; +byte Sprite::_follow = 0; + +Seq Sprite::_stdSeq8[] = +{ { 0, 0, 0, 0, 0, 0 }, + { 1, 1, 0, 0, 0, 0 }, + { 2, 2, 0, 0, 0, 0 }, + { 3, 3, 0, 0, 0, 0 }, + { 4, 4, 0, 0, 0, 0 }, + { 5, 5, 0, 0, 0, 0 }, + { 6, 6, 0, 0, 0, 0 }, + { 7, 7, 0, 0, 0, 0 }, +}; + +SprExt::SprExt(CGE2Engine *vm) + : _p0(vm, 0, 0), _p1(vm, 0, 0), + _b0(nullptr), _b1(nullptr), _shpList(nullptr), + _location(0), _seq(nullptr), _name(nullptr) { + for (int i = 0; i < kActions; i++) + _actions[i] = nullptr; +} + +Sprite::Sprite(CGE2Engine *vm) + : _siz(_vm, 0, 0), _seqPtr(kNoSeq), _seqCnt(0), _shpCnt(0), + _next(nullptr), _prev(nullptr), _time(0), + _ext(nullptr), _ref(-1), _scene(0), _vm(vm), + _pos2D(_vm, kScrWidth >> 1, 0), _pos3D(kScrWidth >> 1, 0, 0) { + memset(_actionCtrl, 0, sizeof(_actionCtrl)); + memset(_file, 0, sizeof(_file)); + memset(&_flags, 0, sizeof(_flags)); + _flags._frnt = 1; +} + +Sprite::Sprite(CGE2Engine *vm, BitmapPtr shpP, int cnt) + : _siz(_vm, 0, 0), _seqPtr(kNoSeq), _seqCnt(0), _shpCnt(0), + _next(nullptr), _prev(nullptr), _time(0), + _ext(nullptr), _ref(-1), _scene(0), _vm(vm), + _pos2D(_vm, kScrWidth >> 1, 0), _pos3D(kScrWidth >> 1, 0, 0) { + memset(_actionCtrl, 0, sizeof(_actionCtrl)); + memset(_file, 0, sizeof(_file)); + memset(&_flags, 0, sizeof(_flags)); + _flags._frnt = 1; + + setShapeList(shpP, cnt); +} + +Sprite::~Sprite() { + contract(); +} + +BitmapPtr Sprite::getShp() { + SprExt *e = _ext; + if (!e || !e->_seq) + return nullptr; + + int i = e->_seq[_seqPtr]._now; + if (i >= _shpCnt) + error("Invalid PHASE in SPRITE::Shp() %s - %d", _file, i); + return e->_shpList + i; +} + +void Sprite::setShapeList(BitmapPtr shp, int cnt) { + _shpCnt = cnt; + _siz.x = 0; + _siz.y = 0; + + if (shp) { + for (int i = 0; i < cnt; i++) { + BitmapPtr p = shp + i; + if (p->_w > _siz.x) + _siz.x = p->_w; + if (p->_h > _siz.y) + _siz.y = p->_h; + } + expand(); + _ext->_shpList = shp; + if (!_ext->_seq) { + setSeq(_stdSeq8); + _seqCnt = (cnt < ARRAYSIZE(_stdSeq8)) ? cnt : ARRAYSIZE(_stdSeq8); + } + } +} + +Seq *Sprite::setSeq(Seq *seq) { + expand(); + + Seq *s = _ext->_seq; + _ext->_seq = seq; + if (_seqPtr == kNoSeq) + step(0); + else if (_time == 0) + step(_seqPtr); + return s; +} + +bool Sprite::seqTest(int n) { + if (n >= 0) + return (_seqPtr == n); + if (_ext) + return (_ext->_seq[_seqPtr]._next == _seqPtr); + return true; +} + +void Sprite::setName(char *newName) { + if (!_ext) + return; + + if (_ext->_name) { + delete[] _ext->_name; + _ext->_name = nullptr; + } + if (newName) { + _ext->_name = new char[strlen(newName) + 1]; + strcpy(_ext->_name, newName); + } +} + +int Sprite::labVal(Action snq, int lab) { + int lv = -1; + if (active()) { + int count = _actionCtrl[snq]._cnt; + CommandHandler::Command *com = snList(snq); + + int i = 0; + for (; i < count; i++) { + if (com[i]._lab == lab) + break; + } + + if (i < count) + return i; + } else { + char tmpStr[kLineMax + 1]; + _vm->mergeExt(tmpStr, _file, kSprExt); + + if (_vm->_resman->exist(tmpStr)) { // sprite description file exist + EncryptedStream sprf(_vm, tmpStr); + if (sprf.err()) + error("Bad SPR [%s]", tmpStr); + + int cnt = 0; + int section = kIdPhase; + ID id; + Common::String line; + + while (lv == -1 && !sprf.eos()) { + line = sprf.readLine(); + if (line.empty()) + continue; + + Common::strlcpy(tmpStr, line.c_str(), sizeof(tmpStr)); + + char *p; + p = _vm->token(tmpStr); + + if (*p == '@') { + if (section == snq && atoi(p + 1) == lab) + lv = cnt; + } else { + id = _vm->ident(p); + switch (id) { + case kIdMTake: + case kIdFTake: + case kIdNear: + case kIdPhase: + case kIdSeq: + section = id; + break; + default: + if (id < 0 && section == snq) + ++cnt; + break; + } + } + } + } + } + return lv; +} + +CommandHandler::Command *Sprite::snList(Action type) { + SprExt *e = _ext; + return (e) ? e->_actions[type] : nullptr; +} + +Sprite *Sprite::expand() { + if (_ext) + return this; + + if (_vm->_spriteNotify != nullptr) + (_vm->*_vm->_spriteNotify)(); + + char fname[kPathMax]; + _vm->mergeExt(fname, _file, kSprExt); + + if (_ext != nullptr) + delete _ext; + _ext = new SprExt(_vm); + + if (!*_file) + return this; + + BitmapPtr shplist = new Bitmap[_shpCnt]; + + int cnt[kActions], + shpcnt = 0, + seqcnt = 0, + maxnow = 0, + maxnxt = 0; + + for (int i = 0; i < kActions; i++) + cnt[i] = 0; + + for (int i = 0; i < kActions; i++){ + byte n = _actionCtrl[i]._cnt; + if (n) + _ext->_actions[i] = new CommandHandler::Command[n]; + else + _ext->_actions[i] = nullptr; + } + + Seq *curSeq = nullptr; + if (_seqCnt) + curSeq = new Seq[_seqCnt]; + + if (_vm->_resman->exist(fname)) { // sprite description file exist + EncryptedStream sprf(_vm, fname); + if (sprf.err()) + error("Bad SPR [%s]", fname); + + int label = kNoByte; + ID section = kIdPhase; + ID id; + Common::String line; + char tmpStr[kLineMax + 1]; + + for (line = sprf.readLine(); !sprf.eos(); line = sprf.readLine()) { + if (line.empty()) + continue; + Common::strlcpy(tmpStr, line.c_str(), sizeof(tmpStr)); + + char *p = _vm->token(tmpStr); + if (*p == '@') { + label = atoi(p + 1); + continue; + } + + id = _vm->ident(p); + switch (id) { + case kIdType: + break; + case kIdNear: + case kIdMTake: + case kIdFTake: + case kIdPhase: + case kIdSeq: + section = id; + break; + case kIdName: + Common::strlcpy(tmpStr, line.c_str(), sizeof(tmpStr)); + for (p = tmpStr; *p != '='; p++); // We search for the = + setName(_vm->tail(p)); + break; + default: + if (id >= kIdNear) + break; + Seq *s; + switch (section) { + case kIdNear: + case kIdMTake: + case kIdFTake: + id = (ID)_vm->_commandHandler->getComId(p); + if (_actionCtrl[section]._cnt) { + CommandHandler::Command *c = &_ext->_actions[section][cnt[section]++]; + c->_commandType = CommandType(id); + c->_lab = label; + c->_ref = _vm->number(nullptr); + c->_val = _vm->number(nullptr); + c->_spritePtr = nullptr; + } + break; + case kIdSeq: + s = &curSeq[seqcnt++]; + s->_now = atoi(p); + if (s->_now > maxnow) + maxnow = s->_now; + s->_next = _vm->number(nullptr); + switch (s->_next) { + case 0xFF: + s->_next = seqcnt; + break; + case 0xFE: + s->_next = seqcnt - 1; + break; + } + if (s->_next > maxnxt) + maxnxt = s->_next; + s->_dx = _vm->number(nullptr); + s->_dy = _vm->number(nullptr); + s->_dz = _vm->number(nullptr); + s->_dly = _vm->number(nullptr); + break; + case kIdPhase: + shplist[shpcnt] = Bitmap(_vm, p); + shpcnt++; + break; + default: + break; + } + break; + } + label = kNoByte; + } + + if (!shpcnt) + error("No shapes - %s", fname); + } else // no sprite description: try to read immediately from .BMP + shplist[shpcnt++] = Bitmap(_vm, _file); + + if (curSeq) { + if (maxnow >= shpcnt) + error("Bad PHASE in SEQ %s", fname); + if (maxnxt && (maxnxt >= seqcnt)) + error("Bad JUMP in SEQ %s", fname); + setSeq(curSeq); + } else { + setSeq(_stdSeq8); + _seqCnt = (shpcnt < ARRAYSIZE(_stdSeq8)) ? shpcnt : ARRAYSIZE(_stdSeq8); + } + + setShapeList(shplist, shpcnt); + + if (_file[2] == '~') { // FLY-type sprite + Seq *nextSeq = _ext->_seq; + int x = (nextSeq + 1)->_dx, y = (nextSeq + 1)->_dy, z = (nextSeq + 1)->_dz; + // random position + nextSeq->_dx = _vm->newRandom(x + x) - x; + nextSeq->_dy = _vm->newRandom(y + y) - y; + nextSeq->_dz = _vm->newRandom(z + z) - z; + gotoxyz(_pos3D + V3D(nextSeq->_dx, nextSeq->_dy, nextSeq->_dz)); + } + + return this; +} + +Sprite *Sprite::contract() { + SprExt *e = _ext; + if (!e) + return this; + + if (_file[2] == '~') { // FLY-type sprite + Seq *curSeq = _ext->_seq; + // return to middle + gotoxyz(_pos3D - V3D(curSeq->_dx, curSeq->_dy, curSeq->_dz)); + curSeq->_dx = curSeq->_dy = curSeq->_dz = 0; + } + + if (_vm->_spriteNotify != nullptr) + (_vm->*_vm->_spriteNotify)(); + + if (e->_name) { + delete[] e->_name; + e->_name = nullptr; + } + + if (e->_shpList) { + for (int i = 0; i < _shpCnt; i++) + e->_shpList[i].release(); + delete[] e->_shpList; + e->_shpList = nullptr; + } + + if (e->_seq) { + if (e->_seq == _stdSeq8) + _seqCnt = 0; + else { + delete[] e->_seq; + e->_seq = nullptr; + } + } + + for (int i = 0; i < kActions; i++) { + if (e->_actions[i]) { + delete[] e->_actions[i]; + e->_actions[i] = nullptr; + } + } + + delete _ext; + _ext = nullptr; + + return this; +} + +void Sprite::backShow() { + expand(); + show(2); + show(1); + _vm->_spare->dispose(this); +} + +void Sprite::step(int nr) { + if (nr >= 0) + _seqPtr = nr; + + if (_ext) { + V3D p = _pos3D; + Seq *seq = nullptr; + + if (nr < 0) + _seqPtr = _ext->_seq[_seqPtr]._next; + + if (_file[2] == '~') { // FLY-type sprite + seq = _ext->_seq; + // return to middle + p._x -= seq->_dx; + p._y -= seq->_dy; + p._z -= seq->_dz; + // generate motion + if (_vm->newRandom(10) < 5) { + if ((seq + 1)->_dx) + seq->_dx += _vm->newRandom(3) - 1; + if ((seq + 1)->_dy) + seq->_dy += _vm->newRandom(3) - 1; + if ((seq + 1)->_dz) + seq->_dz += _vm->newRandom(3) - 1; + } + if (seq->_dx < -(seq + 1)->_dx) + seq->_dx += 2; + if (seq->_dx >= (seq + 1)->_dx) + seq->_dx -= 2; + if (seq->_dy < -(seq + 1)->_dy) + seq->_dy += 2; + if (seq->_dy >= (seq + 1)->_dy) + seq->_dy -= 2; + if (seq->_dz < -(seq + 1)->_dz) + seq->_dz += 2; + if (seq->_dz >= (seq + 1)->_dz) + seq->_dz -= 2; + p._x += seq->_dx; + p._y += seq->_dy; + p._z += seq->_dz; + gotoxyz(p); + } else { + seq = _ext->_seq + _seqPtr; + if (seq->_dz == 127 && seq->_dx != 0) { + _vm->_commandHandlerTurbo->addCommand(kCmdSound, -1, 256 * seq->_dy + seq->_dx, this); + } else { + p._x += seq->_dx; + p._y += seq->_dy; + p._z += seq->_dz; + gotoxyz(p); + } + } + if (seq && (seq->_dly >= 0)) + _time = seq->_dly; + } else if (_vm->_waitRef && _vm->_waitRef == _ref) + _vm->_waitRef = 0; +} + +void Sprite::tick() { + step(); +} + +void Sprite::setScene(int c) { + _scene = c; +} + +void Sprite::gotoxyz(int x, int y, int z) { + gotoxyz(V3D(x, y, z)); +} + +void Sprite::gotoxyz() { + gotoxyz(_pos3D); +} + +void Sprite::gotoxyz(V2D pos) { + V2D o = _pos2D; + int ctr = _siz.x >> 1; + int rem = _siz.x - ctr; + byte trim = 0; + + if (_ref / 10 == 14) { // HERO + int z = _pos3D._z.trunc(); + ctr = (ctr * _vm->_eye->_z.trunc()) / (_vm->_eye->_z.trunc() - z); + rem = (rem * _vm->_eye->_z.trunc()) / (_vm->_eye->_z.trunc() - z); + ctr = (ctr * 3) / 4; + rem = (rem * 3) / 4; + } + + if (pos.x - ctr < 0) { + pos.x = ctr; + ++trim; + } + if (pos.x + rem > kScrWidth) { + pos.x = kScrWidth - rem; + ++trim; + } + _pos2D.x = pos.x; + + if (pos.y < -kPanHeight) { + pos.y = -kPanHeight; + ++trim; + } + if (pos.y + _siz.y > kWorldHeight) { + pos.y = kWorldHeight - _siz.y; + ++trim; + } + _pos2D.y = pos.y; + + _flags._trim = (trim != 0); + + if (!_follow) { + FXP m = _vm->_eye->_z / (_pos3D._z - _vm->_eye->_z); + _pos3D._x = (_vm->_eye->_x + (_vm->_eye->_x - _pos2D.x) / m); + _pos3D._x.round(); + + if (!_constY) { + _pos3D._y = _vm->_eye->_y + (_vm->_eye->_y - _pos2D.y) / m; + _pos3D._y.round(); + } + } + + if (_next && _next->_flags._slav) + _next->gotoxyz(_next->_pos2D - o + _pos2D); + + if (_flags._shad) + _prev->gotoxyz(_prev->_pos2D - o + _pos2D); +} + +void Sprite::gotoxyz_(V2D pos) { + _constY++; + gotoxyz(pos); + --_constY; +} + +void Sprite::gotoxyz(V3D pos) { + _follow++; + if (pos._z != _pos3D._z) + _flags._zmov = true; + gotoxyz(V2D(_vm, _pos3D = pos)); + --_follow; +} + +void Sprite::center() { + gotoxyz(kScrWidth >> 1, (kWorldHeight - _siz.y) >> 1, 0); +} + +void Sprite::show() { + SprExt *e = _ext; + if (e) { + e->_p0 = e->_p1; + e->_b0 = e->_b1; + e->_p1 = _pos2D; + e->_b1 = getShp(); + + if (!_flags._hide) + e->_b1->show(e->_p1); + } +} + +void Sprite::show(uint16 pg) { + assert(pg < 4); + Graphics::Surface *a = _vm->_vga->_page[1]; + _vm->_vga->_page[1] = _vm->_vga->_page[pg]; + getShp()->show(_pos2D); + _vm->_vga->_page[1] = a; +} + +void Sprite::hide() { + SprExt *e = _ext; + if (e->_b0) + e->_b0->hide(e->_p0); +} + +BitmapPtr Sprite::ghost() { + SprExt *e = _ext; + if (!e->_b1) + return nullptr; + + BitmapPtr bmp = new Bitmap(_vm, 0, 0, (uint8 *)nullptr); + bmp->_w = e->_b1->_w; + bmp->_h = e->_b1->_h; + bmp->_b = new HideDesc[bmp->_h]; + memcpy(bmp->_b, e->_b1->_b, sizeof(HideDesc)* bmp->_h); + uint8 *v = new uint8[1]; + *v = (e->_p1.y << 16) + e->_p1.x; + bmp->_v = v; + bmp->_map = (e->_p1.y << 16) + e->_p1.x; + + return bmp; +} + +void Sprite::sync(Common::Serializer &s) { + s.syncAsUint16LE(_ref); + s.syncAsByte(_scene); + + // bitfield in-memory storage is unpredictable, so to avoid + // any issues, pack/unpack everything manually + uint16 flags = 0; + if (s.isLoading()) { + s.syncAsUint16LE(flags); + _flags._hide = flags & 0x0001; + _flags._drag = flags & 0x0002; + _flags._hold = flags & 0x0004; + _flags._trim = flags & 0x0008; + _flags._slav = flags & 0x0010; + _flags._kill = flags & 0x0020; + _flags._xlat = flags & 0x0040; + _flags._port = flags & 0x0080; + _flags._kept = flags & 0x0100; + _flags._frnt = flags & 0x0200; + _flags._east = flags & 0x0400; + _flags._near = flags & 0x0800; + _flags._shad = flags & 0x1000; + _flags._back = flags & 0x2000; + _flags._zmov = flags & 0x4000; + _flags._tran = flags & 0x8000; + } else { + flags = (flags << 1) | (_flags._tran ? 1 : 0); + flags = (flags << 1) | (_flags._zmov ? 1 : 0); + flags = (flags << 1) | (_flags._back ? 1 : 0); + flags = (flags << 1) | (_flags._shad ? 1 : 0); + flags = (flags << 1) | (_flags._near ? 1 : 0); + flags = (flags << 1) | (_flags._east ? 1 : 0); + flags = (flags << 1) | (_flags._frnt ? 1 : 0); + flags = (flags << 1) | (_flags._kept ? 1 : 0); + flags = (flags << 1) | (_flags._port ? 1 : 0); + flags = (flags << 1) | (_flags._xlat ? 1 : 0); + flags = (flags << 1) | (_flags._kill ? 1 : 0); + flags = (flags << 1) | (_flags._slav ? 1 : 0); + flags = (flags << 1) | (_flags._trim ? 1 : 0); + flags = (flags << 1) | (_flags._hold ? 1 : 0); + flags = (flags << 1) | (_flags._drag ? 1 : 0); + flags = (flags << 1) | (_flags._hide ? 1 : 0); + s.syncAsUint16LE(flags); + } + + s.syncAsSint16LE(_pos2D.x); + s.syncAsSint16LE(_pos2D.y); + + _pos3D.sync(s); + + s.syncAsSint16LE(_siz.x); + s.syncAsSint16LE(_siz.y); + + s.syncAsUint16LE(_time); + for (int i = 0; i < kActions; i++){ + s.syncAsByte(_actionCtrl[i]._ptr); + s.syncAsByte(_actionCtrl[i]._cnt); + } + s.syncAsSint16LE(_seqPtr); + s.syncAsSint16LE(_seqCnt); + s.syncAsUint16LE(_shpCnt); + s.syncBytes((byte *)&_file[0], 9); + _file[8] = '\0'; +} + +Queue::Queue(bool show) : _head(nullptr), _tail(nullptr) { +} + +void Queue::append(Sprite *spr) { + if (spr->_flags._back) + spr->backShow(); + else { + spr->expand(); + if (_tail) { + spr->_prev = _tail; + _tail->_next = spr; + } else + _head = spr; + + _tail = spr; + } +} + +void Queue::insert(Sprite *spr, Sprite *nxt) { + if (spr->_flags._back) + spr->backShow(); + else { + spr->expand(); + if (nxt == _head) { + spr->_next = _head; + _head = spr; + if (!_tail) + _tail = spr; + } else { + spr->_next = nxt; + spr->_prev = nxt->_prev; + if (spr->_prev) + spr->_prev->_next = spr; + } + if (spr->_next) + spr->_next->_prev = spr; + } +} + +void Queue::insert(Sprite *spr) { + if (locate(spr)) + return; // We only queue it if it's not already queued. + + Sprite *s; + for (s = _head; s; s = s->_next) { + if (s->_pos3D._z < spr->_pos3D._z) + break; + } + + if (s) + insert(spr, s); + else + append(spr); +} + +Sprite *Queue::remove(Sprite *spr) { + if (spr == _head) + _head = spr->_next; + + if (spr == _tail) + _tail = spr->_prev; + + if (spr->_next) + spr->_next->_prev = spr->_prev; + + if (spr->_prev) + spr->_prev->_next = spr->_next; + + spr->_prev = nullptr; + spr->_next = nullptr; + return spr; +} + +Sprite *Queue::locate(int ref) { + for (Sprite *spr = _head; spr; spr = spr->_next) { + if (spr->_ref == ref) + return spr; + } + return nullptr; +} + +bool Queue::locate(Sprite *spr) { + Sprite *s; + for (s = _head; s; s = s->_next) { + if (s == spr) + return true; + } + + return false; +} + +Vga::Vga(CGE2Engine *vm) : _frmCnt(0), _msg(nullptr), _name(nullptr), _setPal(false), _vm(vm) { + _rot._org = 1; + _rot._len = 0; + _rot._cnt = 0; + _rot._dly = 1; + + _oldColors = nullptr; + _newColors = nullptr; + _showQ = new Queue(true); + _sysPal = new Dac[kPalCount]; + + for (int idx = 0; idx < 4; idx++) { + _page[idx] = new Graphics::Surface(); + _page[idx]->create(kScrWidth, kScrHeight, Graphics::PixelFormat::createFormatCLUT8()); + } + + _mono = ConfMan.getBool("enable_color_blind"); + + _oldColors = (Dac *)malloc(sizeof(Dac) * kPalCount); + _newColors = (Dac *)malloc(sizeof(Dac) * kPalCount); + getColors(_oldColors); + sunset(); + setColors(); + clear(0); +} + +Vga::~Vga() { + Common::String buffer = ""; + + free(_oldColors); + free(_newColors); + if (_msg) + buffer = Common::String(_msg); + + if (_name) + buffer = buffer + " [" + _name + "]"; + + debugN("%s", buffer.c_str()); + + delete _showQ; + delete[] _sysPal; + + for (int idx = 0; idx < 4; idx++) { + _page[idx]->free(); + delete _page[idx]; + } +} + +void Vga::waitVR() { + // Since some of the game parts rely on using vertical sync as a delay mechanism, + // we're introducing a short delay to simulate it + g_system->delayMillis(5); +} + +void Vga::getColors(Dac *tab) { + byte palData[kPalSize]; + g_system->getPaletteManager()->grabPalette(palData, 0, kPalCount); + palToDac(palData, tab); +} + +uint8 Vga::closest(Dac *pal, const uint8 colR, const uint8 colG, const uint8 colB) { +#define f(col, lum) ((((uint16)(col)) << 8) / lum) + uint16 i, dif = 0xFFFF, found = 0; + uint16 L = colR + colG + colB; + if (!L) + L++; + uint16 R = f(colR, L), G = f(colG, L), B = f(colB, L); + for (i = 0; i < 256; i++) { + uint16 l = pal[i]._r + pal[i]._g + pal[i]._b; + if (!l) + l++; + int r = f(pal[i]._r, l), g = f(pal[i]._g, l), b = f(pal[i]._b, l); + uint16 D = ((r > R) ? (r - R) : (R - r)) + + ((g > G) ? (g - G) : (G - g)) + + ((b > B) ? (b - B) : (B - b)) + + ((l > L) ? (l - L) : (L - l)) * 10 ; + + if (D < dif) { + found = i; + dif = D; + if (D == 0) + break; // exact! + } + } + return found; +#undef f +} + +uint8 Vga::closest(Dac *pal, Dac x) { + int exp = (sizeof(long) * 8 - 1); + long D = (1 << exp) - 1; // Maximum value of long. + long R = x._r; + long G = x._g; + long B = x._b; + int idx = 255; + for (int n = 0; n < 256; n++) { + long dR = R - pal[n]._r; + long dG = G - pal[n]._g; + long dB = B - pal[n]._b, + d = dR * dR + dG * dG + dB * dB; + if (d < D) { + idx = n; + D = d; + if (!d) + break; + } + } + return idx; +} + +uint8 *Vga::glass(Dac *pal, const uint8 colR, const uint8 colG, const uint8 colB) { + uint8 *x = (uint8 *)malloc(256); + if (x) { + for (uint16 i = 0; i < 256; i++) { + x[i] = closest(pal, ((uint16)(pal[i]._r) * colR) / 255, + ((uint16)(pal[i]._g) * colG) / 255, + ((uint16)(pal[i]._b) * colB) / 255); + } + } + return x; +} + +void Vga::palToDac(const byte *palData, Dac *tab) { + const byte *colP = palData; + for (int idx = 0; idx < kPalCount; idx++, colP += 3) { + tab[idx]._r = *colP >> 2; + tab[idx]._g = *(colP + 1) >> 2; + tab[idx]._b = *(colP + 2) >> 2; + } +} + +void Vga::dacToPal(const Dac *tab, byte *palData) { + for (int idx = 0; idx < kPalCount; idx++, palData += 3) { + *palData = tab[idx]._r << 2; + *(palData + 1) = tab[idx]._g << 2; + *(palData + 2) = tab[idx]._b << 2; + } +} + +void Vga::setColors(Dac *tab, int lum) { + Dac *palP = tab, *destP = _newColors; + for (int idx = 0; idx < kPalCount; idx++, palP++, destP++) { + destP->_r = (palP->_r * lum) >> 6; + destP->_g = (palP->_g * lum) >> 6; + destP->_b = (palP->_b * lum) >> 6; + } + + if (_mono) { + destP = _newColors; + for (int idx = 0; idx < kPalCount; idx++, destP++) { + // Form a grayscale color from 30% R, 59% G, 11% B + uint8 intensity = (((int)destP->_r * 77) + ((int)destP->_g * 151) + ((int)destP->_b * 28)) >> 8; + destP->_r = intensity; + destP->_g = intensity; + destP->_b = intensity; + } + } + + _setPal = true; +} + +void Vga::setColors() { + memset(_newColors, 0, kPalSize); + updateColors(); +} + +void Vga::sunrise(Dac *tab) { + for (int i = 0; i <= 64; i += kFadeStep) { + setColors(tab, i); + waitVR(); + updateColors(); + g_system->updateScreen(); + } +} + +void Vga::sunset() { + Dac tab[256]; + getColors(tab); + for (int i = 64; i >= 0; i -= kFadeStep) { + setColors(tab, i); + waitVR(); + updateColors(); + g_system->updateScreen(); + } +} + +void Vga::show() { + _vm->_infoLine->update(); + + for (Sprite *spr = _showQ->first(); spr; spr = spr->_next) { + spr->show(); + } + + _vm->_mouse->show(); + update(); + rotate(); + + for (Sprite *spr = _showQ->first(); spr; spr = spr->_next) { + spr->hide(); + if (spr->_flags._zmov) { + Sprite *s = nullptr; + Sprite *p = spr->_prev; + Sprite *n = spr->_next; + + if (spr->_flags._shad) { + s = p; + p = s->_prev; + } + + if ((p && spr->_pos3D._z > p->_pos3D._z) || + (n && spr->_pos3D._z < n->_pos3D._z)) { + _showQ->insert(_showQ->remove(spr)); + } + spr->_flags._zmov = false; + } + } + _vm->_mouse->hide(); +} + +void Vga::updateColors() { + byte palData[kPalSize]; + dacToPal(_newColors, palData); + g_system->getPaletteManager()->setPalette(palData, 0, 256); +} + +void Vga::update() { + SWAP(Vga::_page[0], Vga::_page[1]); + + if (_setPal) { + updateColors(); + _setPal = false; + } + + g_system->copyRectToScreen(Vga::_page[0]->getPixels(), kScrWidth, 0, 0, kScrWidth, kScrHeight); + g_system->updateScreen(); +} + +void Vga::rotate() { + if (_rot._len) { + Dac c; + getColors(_newColors); + c = _newColors[_rot._org]; + memmove(_newColors + _rot._org, _newColors + _rot._org + 1, (_rot._len - 1) * sizeof(Dac)); + _newColors[_rot._org + _rot._len - 1] = c; + _setPal = true; + } +} + +void Vga::clear(uint8 color) { + for (int paneNum = 0; paneNum < 4; paneNum++) + _page[paneNum]->fillRect(Common::Rect(0, 0, kScrWidth, kScrHeight), color); +} + +void Vga::copyPage(uint16 d, uint16 s) { + _page[d]->copyFrom(*_page[s]); +} + +void Bitmap::show(V2D pos) { + xLatPos(pos); + + const byte *srcP = (const byte *)_v; + byte *screenStartP = (byte *)_vm->_vga->_page[1]->getPixels(); + byte *screenEndP = (byte *)_vm->_vga->_page[1]->getBasePtr(0, kScrHeight); + + // Loop through processing data for each plane. The game originally ran in plane mapped mode, where a + // given plane holds each fourth pixel sequentially. So to handle an entire picture, each plane's data + // must be decompressed and inserted into the surface + for (int planeCtr = 0; planeCtr < 4; planeCtr++) { + byte *destP = (byte *)_vm->_vga->_page[1]->getBasePtr(pos.x + planeCtr, pos.y); + + for (;;) { + uint16 v = READ_LE_UINT16(srcP); + srcP += 2; + int cmd = v >> 14; + int count = v & 0x3FFF; + + if (cmd == 0) { + // End of image + break; + } + + // Handle a set of pixels + while (count-- > 0) { + // Transfer operation + switch (cmd) { + case 1: + // SKIP + break; + case 2: + // REPEAT + if (destP >= screenStartP && destP < screenEndP) + *destP = *srcP; + break; + case 3: + // COPY + if (destP >= screenStartP && destP < screenEndP) + *destP = *srcP; + srcP++; + break; + } + + // Move to next dest position + destP += 4; + } + + if (cmd == 2) + srcP++; + } + } +} + +void Bitmap::hide(V2D pos) { + xLatPos(pos); + + // Perform clipping to screen + int w = MIN<int>(_w, kScrWidth - pos.x); + int h = MIN<int>(_h, kScrHeight - pos.y); + if (pos.x < 0) { + w -= -pos.x; + pos.x = 0; + if (w < 0) + return; + } + if (pos.y < 0) { + h -= -pos.y; + pos.y = 0; + if (h < 0) + return; + } + + // Perform copying of screen section + for (int yp = pos.y; yp < pos.y + h; yp++) { + if (yp >= 0 && yp < kScrHeight) { + const byte *srcP = (const byte *)_vm->_vga->_page[2]->getBasePtr(pos.x, yp); + byte *destP = (byte *)_vm->_vga->_page[1]->getBasePtr(pos.x, yp); + + Common::copy(srcP, srcP + w, destP); + } + } +} + +Speaker::Speaker(CGE2Engine *vm): Sprite(vm), _vm(vm) { + // Set the sprite list + BitmapPtr SP = new Bitmap[2]; + uint8 *map = Bitmap::makeSpeechBubbleTail(0, _vm->_font->_colorSet); + SP[0] = Bitmap(_vm, 15, 16, map); + delete[] map; + map = Bitmap::makeSpeechBubbleTail(1, _vm->_font->_colorSet); + SP[1] = Bitmap(_vm, 15, 16, map); + delete[] map; + setShapeList(SP, 2); +} + +} // End of namespace CGE2 diff --git a/engines/cge2/vga13h.h b/engines/cge2/vga13h.h new file mode 100644 index 0000000000..346d140e3f --- /dev/null +++ b/engines/cge2/vga13h.h @@ -0,0 +1,308 @@ +/* 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. + * + */ + +/* + * This code is based on original Sfinx source code + * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon + */ + +#ifndef CGE2_VGA13H_H +#define CGE2_VGA13H_H + +#include "common/serializer.h" +#include "common/events.h" +#include "graphics/surface.h" +#include "cge2/general.h" +#include "cge2/bitmap.h" +#include "cge2/snail.h" +#include "cge2/spare.h" +#include "cge2/cge2.h" + +namespace CGE2 { + +#define kFadeStep 2 +#define kVgaColDark 207 +#define kVgaColDarkGray 225 /*219*/ +#define kVgaColGray 231 +#define kVgaColLightGray 237 +#define kPixelTransp 0xFE +#define kNoSeq (-1) +#define kNoPtr ((uint8)-1) +#define kSprExt ".SPR" +#define kPalCount 256 +#define kPalSize (kPalCount * 3) + +class FXP { + int32 v; +public: + FXP(void) : v(0) {} + FXP (int i0, int f0 = 0) : v((i0 * 256) + ((i0 < 0) ? -f0 : f0)) {} + FXP operator=(const int &x) { v = x << 8; return *this; } + FXP operator+(const FXP &x) const { FXP y; y.v = v + x.v; return y; } + FXP operator-(const FXP &x) const { FXP y; y.v = v - x.v; return y; } + FXP operator*(const FXP &x) const; + FXP operator/(const FXP &x) const; + + friend int &operator+=(int &a, const FXP &b) { return a += b.trunc(); } + friend int &operator-=(int &a, const FXP &b) { return a -= b.trunc(); } + friend FXP &operator+=(FXP &a, const int &b) { a.v += b << 8; return a; } + friend FXP &operator-=(FXP &a, const int &b) { a.v -= b << 8; return a; } + friend bool operator==(const FXP &a, const FXP &b) { return a.v == b.v; } + friend bool operator!=(const FXP &a, const FXP &b) { return a.v != b.v; } + friend bool operator<(const FXP &a, const FXP &b) { return a.v < b.v; } + friend bool operator>(const FXP &a, const FXP &b) { return a.v > b.v; } + int trunc(void) const { return v >> 8; } + int round(void) const { return (v + 0x80) >> 8; } + bool empty() const { return v == 0; } + void sync(Common::Serializer &s); +}; + +class V3D { +public: + FXP _x, _y, _z; + V3D() { } + V3D(FXP x, FXP y, FXP z = 0) : _x(x), _y(y), _z(z) { } + V3D(const V3D &p) : _x(p._x), _y(p._y), _z(p._z) { } + V3D operator+(const V3D &p) const { return V3D(_x + p._x, _y + p._y, _z + p._z); } + V3D operator-(const V3D &p) const { return V3D(_x - p._x, _y - p._y, _z - p._z); } + V3D operator*(long n) const { return V3D(_x * n, _y * n, _z * n); } + V3D operator/ (long n) const { return V3D(_x / n, _y / n, _z / n); } + bool operator==(const V3D &p) const { return _x == p._x && _y == p._y && _z == p._z; } + bool operator!=(const V3D &p) const { return _x != p._x || _y != p._y || _z != p._z; } + V3D& operator+=(const V3D &x) { return *this = *this + x; } + V3D& operator-=(const V3D &x) { return *this = *this - x; } + void sync(Common::Serializer &s); +}; + +class V2D : public Common::Point { + CGE2Engine *_vm; +public: + V2D &operator=(const V3D &p3) { + FXP m = _vm->_eye->_z / (p3._z - _vm->_eye->_z); + FXP posx = _vm->_eye->_x + (_vm->_eye->_x - p3._x) * m; + x = posx.round(); + FXP posy = _vm->_eye->_y + (_vm->_eye->_y - p3._y) * m; + y = posy.round(); + return *this; + } + V2D(CGE2Engine *vm) : _vm(vm) { } + V2D(CGE2Engine *vm, const V3D &p3) : _vm(vm) { *this = p3; } + V2D(CGE2Engine *vm, int posx, int posy) : _vm(vm), Common::Point(posx, posy) { } + bool operator<(const V2D &p) const { return (x < p.x) && (y < p.y); } + bool operator<=(const V2D &p) const { return (x <= p.x) && (y <= p.y); } + bool operator>(const V2D &p) const { return (x > p.x) && (y > p.y); } + bool operator>=(const V2D &p) const { return (x >= p.x) && (y >= p.y); } + V2D operator+(const V2D &p) const { return V2D(_vm, x + p.x, y + p.y); } + V2D operator-(const V2D &p) const { return V2D(_vm, x - p.x, y - p.y); } + bool operator==(const V3D &p) const { V3D tmp(x, y); return tmp._x == p._x && tmp._y == p._y && tmp._z == p._z; } + bool operator!=(const V3D &p) const { V3D tmp(x, y); return tmp._x != p._x || tmp._y != p._y || tmp._z == p._z; } + bool operator==(const V2D &p) const { return x == p.x && y == p.y; } + uint16 area() { return x * y; } + bool limited(const V2D &p) { + return ((x < p.x) && (y < p.y)); + } + V2D scale (int z) { + FXP m = _vm->_eye->_z / (_vm->_eye->_z - z); + FXP posx = m * x; + FXP posy = m * y; + return V2D(_vm, posx.trunc(), posy.trunc()); + } +}; + +struct Seq { + uint8 _now; + uint8 _next; + int8 _dx; + int8 _dy; + int8 _dz; + int _dly; +}; + +class SprExt { +public: + V2D _p0; + V2D _p1; + BitmapPtr _b0; + BitmapPtr _b1; + BitmapPtr _shpList; + int _location; + Seq *_seq; + char *_name; + CommandHandler::Command *_actions[kActions]; + SprExt(CGE2Engine *vm); +}; + +class Sprite { +protected: + SprExt *_ext; + CGE2Engine *_vm; +public: + int _ref; + signed char _scene; + struct Flags { + bool _hide; // general visibility switch + bool _drag; // sprite is moveable + bool _hold; // sprite is held with mouse + bool _trim; // Trim flag + bool _slav; // slave object + bool _kill; // dispose memory after remove + bool _xlat; // 2nd way display: xlat table + bool _port; // portable + bool _kept; // kept in pocket + bool _frnt; // stay in front of sprite + bool _east; // talk to east (in opposite to west) + bool _near; // Near action lock + bool _shad; // shadow + bool _back; // 'send to background' request + bool _zmov; // sprite needs Z-update in queue + bool _tran; // transparent (untouchable) + } _flags; + V2D _pos2D; + V3D _pos3D; + V2D _siz; + uint16 _time; + struct { byte _ptr, _cnt; } _actionCtrl[kActions]; + int _seqPtr; + int _seqCnt; + int _shpCnt; + char _file[kMaxFile]; + // Following trailer is not saved with the game: + Sprite *_prev; + Sprite *_next; + static byte _constY; + static byte _follow; + static Seq _stdSeq8[]; + + bool works(Sprite *spr); + bool seqTest(int n); + inline bool active() { + return _ext != nullptr; + } + Sprite(CGE2Engine *vm); + Sprite(CGE2Engine *vm, BitmapPtr shp, int cnt); + virtual ~Sprite(); + BitmapPtr getShp(); + void setShapeList(BitmapPtr shp, int cnt); + void moveShapesHi(); + void moveShapesLo(); + int labVal(Action snq, int lab); + virtual Sprite *expand(); + virtual Sprite *contract(); + void backShow(); + void setName(char *newName); + inline char *name() { + return (_ext) ? _ext->_name : nullptr; + } + void gotoxyz(int x, int y, int z = 0); + void gotoxyz(); + void gotoxyz(V2D pos); + void gotoxyz_(V2D pos); + void gotoxyz(V3D pos); + void center(); + void show(uint16 pg); + void hide(uint16 pg); + void show(); + void hide(); + BitmapPtr ghost(); + void step(int nr = -1); + Seq *setSeq(Seq *seq); + CommandHandler::Command *snList(Action type); + virtual void touch(uint16 mask, V2D pos, Common::KeyCode keyCode); + virtual void tick(); + virtual void setScene(int c); + void clrHide() { if (_ext) _ext->_b0 = nullptr; } + + void sync(Common::Serializer &s); + + static void (*notify) (); +}; + +class Queue { + Sprite *_head; + Sprite *_tail; +public: + Queue(bool show); + + void append(Sprite *spr); + void insert(Sprite *spr, Sprite *nxt); + void insert(Sprite *spr); + Sprite *remove(Sprite *spr); + Sprite *first() { + return _head; + } + Sprite *last() { + return _tail; + } + Sprite *locate(int ref); + bool locate(Sprite *spr); + void clear() { _head = _tail = nullptr; } +}; + +class Vga { + CGE2Engine *_vm; + bool _setPal; + Dac *_oldColors; + Dac *_newColors; + const char *_msg; + const char *_name; + + void updateColors(); + void setColors(); + void waitVR(); + uint8 closest(Dac *pal, const uint8 colR, const uint8 colG, const uint8 colB); + +public: + uint32 _frmCnt; + Queue *_showQ; + bool _mono; + Graphics::Surface *_page[4]; + Dac *_sysPal; + struct { uint8 _org, _len, _cnt, _dly; } _rot; + + Vga(CGE2Engine *vm); + ~Vga(); + + uint8 *glass(Dac *pal, const uint8 colR, const uint8 colG, const uint8 colB); + void getColors(Dac *tab); + void setColors(Dac *tab, int lum); + void clear(uint8 color); + void copyPage(uint16 d, uint16 s); + void sunrise(Dac *tab); + void sunset(); + void show(); + void update(); + void rotate(); + uint8 closest(Dac *pal, Dac x); + + void palToDac(const byte *palData, Dac *tab); + void dacToPal(const Dac *tab, byte *palData); +}; + +class Speaker: public Sprite { + CGE2Engine *_vm; +public: + Speaker(CGE2Engine *vm); +}; + +} // End of namespace CGE2 + +#endif // CGE2_VGA13H_H diff --git a/engines/cge2/vmenu.cpp b/engines/cge2/vmenu.cpp new file mode 100644 index 0000000000..6afe5e9a61 --- /dev/null +++ b/engines/cge2/vmenu.cpp @@ -0,0 +1,162 @@ +/* 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. + * + */ + +/* + * This code is based on original Sfinx source code + * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon + */ + +#include "cge2/text.h" +#include "cge2/vmenu.h" +#include "cge2/events.h" + +namespace CGE2 { + +Choice::Choice(CGE2Engine *vm) : _vm(vm), _text(nullptr) {} + +ExitGameChoice::ExitGameChoice(CGE2Engine *vm) : Choice(vm) { + _text = _vm->_text->getText(kQuitText); +} + +void ExitGameChoice::proc() { + _vm->switchScene(-1); +} + +ReturnToGameChoice::ReturnToGameChoice(CGE2Engine *vm) : Choice(vm) { + _text = _vm->_text->getText(kNoQuitText); +} + +void ReturnToGameChoice::proc() { + _vm->_commandHandlerTurbo->addCommand(kCmdSeq, kPowerRef, 1, nullptr); + _vm->keyClick(); +} + +MenuBar::MenuBar(CGE2Engine *vm, uint16 w, byte *c) : Talk(vm) { + _color = c; + int h = kFontHigh + 2 * kMenuBarVerticalMargin, i = (w += 2 * kMenuBarHorizontalMargin) * h; + uint8 *p = new uint8[i]; + uint8 *p1; + uint8 *p2; + uint8 lt = _color[kLt]; + uint8 rb = _color[kRb]; + BitmapPtr b; + + memset(p + w, kPixelTransp, i - 2 * w); + memset(p, lt, w); + memset(p + i - w, rb, w); + p1 = p; + p2 = p + i - 1; + for (i = 0; i < h; i++) { + *p1 = lt; + *p2 = rb; + p1 += w; + p2 -= w; + } + b = new Bitmap[1]; + b[0] = Bitmap(vm, w, h, p); + delete[] p; + setShapeList(b, 1); + _flags._slav = true; + _flags._tran = true; + _flags._kill = true; +} + +VMenu *VMenu::_addr = nullptr; + +VMenu::VMenu(CGE2Engine *vm, Common::Array<Choice *> list, V2D pos, ColorBank col) + : Talk(vm, vmGather(list), kTBRect, col), _menu(list), _bar(nullptr), _items(list.size()), _vm(vm) { + delete[] _vmgt; // Lefotver of vmGather. + + _addr = this; + _recent = -1; + _flags._kill = true; + + if (pos.x < 0 || pos.y < 0) + center(); + else + gotoxyz(V2D(_vm, pos.x - _siz.x / 2, pos.y - (kTextVMargin + kFontHigh / 2))); + + _vm->_vga->_showQ->append(this); + _bar = new MenuBar(_vm, _siz.x - 2 * kTextHMargin, _color); + _bar->gotoxyz(V2D(_vm, _pos2D.x, _pos2D.y + kTextVMargin - kMenuBarVerticalMargin)); + _vm->_vga->_showQ->append(_bar); +} + +char *VMenu::vmGather(Common::Array<Choice *> list) { + int len = 0; + int h = 0; + + for (uint i = 0; i < list.size(); i++) { + len += strlen(list[i]->_text); + ++h; + } + _vmgt = new char[len + h]; + *_vmgt = '\0'; + for (uint i = 0; i < list.size(); i++) { + if (*_vmgt) + strcat(_vmgt, "|"); + strcat(_vmgt, list[i]->_text); + ++h; + } + + return _vmgt; +} + + +VMenu::~VMenu() { + _addr = nullptr; + + for (uint i = 0; i < _menu.size(); i++) { + delete _menu[i]; + } +} + +void VMenu::touch(uint16 mask, V2D pos, Common::KeyCode keyCode) { + if (_items) { + Sprite::touch(mask, pos, keyCode); + + int n = 0; + bool ok = false; + int h = kFontHigh + kTextLineSpace; + pos.y -= kTextVMargin - 1; + if (pos.y >= 0) { + if (pos.x < 0) + pos.x = -pos.x; + n = pos.y / h; + if (n < _items) + ok = (pos.x <= (_siz.x >> 1) - kTextHMargin); + else + n = _items - 1; + } + + _bar->gotoxyz(V2D(_vm, _pos2D.x, _pos2D.y + kTextVMargin + n * h - kMenuBarVerticalMargin)); + n = _items - 1 - n; + + if (ok && (mask & kMouseLeftUp)) { + _items = 0; + _vm->_commandHandlerTurbo->addCommand(kCmdKill, -1, 0, this); + _menu[_recent = n]->proc(); + } + } +} + +} // End of namespace CGE2 diff --git a/engines/cge2/vmenu.h b/engines/cge2/vmenu.h new file mode 100644 index 0000000000..f34812dcf4 --- /dev/null +++ b/engines/cge2/vmenu.h @@ -0,0 +1,89 @@ +/* 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. + * + */ + +/* + * This code is based on original Sfinx source code + * Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon + */ + +#ifndef CGE2_VMENU_H +#define CGE2_VMENU_H + +#define kMenuBarVerticalMargin 1 +#define kMenuBarHorizontalMargin 3 +#define kLt 3 +#define kRb 1 + +#include "cge2/cge2.h" +#include "cge2/talk.h" + +namespace CGE2 { + +class Choice { +protected: + CGE2Engine *_vm; +public: + char *_text; + + virtual void proc() = 0; + + Choice(CGE2Engine *vm); + virtual ~Choice() {}; +}; + +class ExitGameChoice : public Choice { +public: + ExitGameChoice(CGE2Engine *vm); + void proc(); +}; + +class ReturnToGameChoice : public Choice { +public: + ReturnToGameChoice(CGE2Engine *vm); + void proc(); +}; + +class MenuBar : public Talk { +public: + MenuBar(CGE2Engine *vm, uint16 w, byte *c); +}; + +class VMenu : public Talk { + CGE2Engine *_vm; + + uint16 _items; + Common::Array<Choice *> _menu; +public: + char *_vmgt; + static VMenu *_addr; + int _recent; + MenuBar *_bar; + + VMenu(CGE2Engine *vm, Common::Array<Choice *> list, V2D pos, ColorBank col); + ~VMenu(); + void touch(uint16 mask, V2D pos, Common::KeyCode keyCode); + char *vmGather(Common::Array<Choice *> list); +}; + +} // End of namespace CGE2 + +#endif // CGE2_VMENU_H |