diff options
137 files changed, 23403 insertions, 280 deletions
@@ -62,6 +62,10 @@ ScummVM Team Oliver Kiehl - (retired) Ludvig Strigeus - (retired) + AVALANCHE: + Peter Bozso + Arnaud Boutonne + CGE: Arnaud Boutonne Paul Gilbert @@ -240,6 +244,9 @@ ScummVM Team Wintermute: Einar Johan T. Somaaen + ZVision: + Adrian Astley + Backend Teams ------------- Android: @@ -610,6 +617,8 @@ Special thanks to Jimmi Thogersen - For ScummRev, and much obscure code/documentation Tristan - For additional work on the original MT-32 emulator James Woodcock - Soundtrack enhancements + Anton Yartsev - For the original re-implementation of the ZVision + engine Tony Warriner and everyone at Revolution Software Ltd. for sharing with us the source of some of their brilliant games, allowing us to release diff --git a/devtools/credits.pl b/devtools/credits.pl index ecb543b832..fda6f4782e 100755 --- a/devtools/credits.pl +++ b/devtools/credits.pl @@ -541,6 +541,11 @@ begin_credits("Credits"); add_person("Oliver Kiehl", "olki", "(retired)"); add_person("Ludvig Strigeus", "ludde", "(retired)"); end_section(); + + begin_section("AVALANCHE"); + add_person("Peter Bozsó", "uruk", ""); + add_person("Arnaud Boutonné", "Strangerke", ""); + end_section(); begin_section("CGE"); add_person("Arnaud Boutonné", "Strangerke", ""); @@ -754,6 +759,10 @@ begin_credits("Credits"); begin_section("Wintermute"); add_person("Einar Johan T. Sømåen", "somaen", ""); end_section(); + + begin_section("ZVision"); + add_person("Adrian Astley", "RichieSams", ""); + end_section(); end_section(); @@ -1155,6 +1164,7 @@ begin_credits("Credits"); add_person("Jimmi Thøgersen", "", "For ScummRev, and much obscure code/documentation"); add_person("", "Tristan", "For additional work on the original MT-32 emulator"); add_person("James Woodcock", "", "Soundtrack enhancements"); + add_person("Anton Yartsev", "Zidane", "For the original re-implementation of the ZVision engine"); end_persons(); add_paragraph( diff --git a/engines/avalanche/animation.cpp b/engines/avalanche/animation.cpp new file mode 100644 index 0000000000..ef30faa87c --- /dev/null +++ b/engines/avalanche/animation.cpp @@ -0,0 +1,1459 @@ +/* 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 the original source code of Lord Avalot d'Argent version 1.3. + * Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman. + */ + +/* TRIP5 Trippancy V - the sprite animation subsystem */ + +#include "avalanche/avalanche.h" +#include "avalanche/animation.h" + +namespace Avalanche { + +// Art gallery at 2,1; notice about this at 2,2. +const int32 Animation::kCatacombMap[8][8] = { + // Geida's room + // 1 2 3 4 5 6 7 8 + {0x204, 0x200, 0xd0f0, 0xf0ff, 0xff, 0xd20f, 0xd200, 0x200}, + {0x50f1, 0x20ff, 0x2ff, 0xff, 0xe0ff, 0x20ff, 0x200f, 0x7210}, + {0xe3f0, 0xe10f, 0x72f0, 0xff, 0xe0ff, 0xff, 0xff, 0x800f}, + {0x2201, 0x2030, 0x800f, 0x220, 0x20f, 0x30, 0xff, 0x23f}, // >> Oubliette + {0x5024, 0xf3, 0xff, 0x200f, 0x22f0, 0x20f, 0x200, 0x7260}, + {0xf0, 0x2ff, 0xe2ff, 0xff, 0x200f, 0x50f0, 0x72ff, 0x201f}, + {0xf6, 0x220f, 0x22f0, 0x30f, 0xf0, 0x20f, 0x8200, 0x2f0}, // <<< In here + {0x34, 0x200f, 0x51f0, 0x201f, 0xf1, 0x50ff, 0x902f, 0x2062} +}; + +AnimationType::AnimationType(Animation *anim) { + _anim = anim; +} + +/** + * Loads & sets up the sprite. + */ +void AnimationType::init(byte spritenum, bool doCheck) { + const int32 idshould = -1317732048; + + if (spritenum == 177) + return; // Already running! + + Common::File inf; + Common::String filename = Common::String::format("sprite%d.avd", spritenum); + if (!inf.open(filename)) + error("AVALANCHE: Trip: File not found: %s", filename.c_str()); + + inf.seek(177); + + int32 id = inf.readSint32LE(); + if (id != idshould) { + inf.close(); + return; + } + + // Replace variable named 'soa' in the original code. + inf.skip(2); + // Skip real name Size (1 byte) then fixed sized zone containing name (12 bytes) + inf.skip(1 + 12); + // Skip real comment size (1 byte) then fixed sized zone containing comment (16 bytes) + inf.skip(1 + 16); + + _frameNum = inf.readByte(); + _xLength = inf.readByte(); + _yLength = inf.readByte(); + _seq = inf.readByte(); + uint16 size = inf.readUint16LE(); + assert (size > 6); + _fgBubbleCol = (Color)inf.readByte(); + _bgBubbleCol = (Color)inf.readByte(); + _characterId = inf.readByte(); + + byte xWidth = _xLength / 8; + if ((_xLength % 8) > 0) + xWidth++; + for (int i = 0; i < _frameNum; i++) { + _sil[i] = new SilType[11 * (_yLength + 1)]; + _mani[i] = new ManiType[size - 6]; + for (int j = 0; j <= _yLength; j++) + inf.read((*_sil[i])[j], xWidth); + inf.read(*_mani[i], size - 6); + } + + _x = 0; + _y = 0; + _quick = true; + _visible = false; + _speedX = kWalk; + _speedY = 1; + _homing = false; + _moveX = 0; + _moveY = 0; + _stepNum = 0; + _doCheck = doCheck; + _count = 0; + _id = spritenum; + _vanishIfStill = false; + _callEachStepFl = false; + + inf.close(); +} + +/** + * Just sets 'quick' to false. + * @remarks Originally called 'original' + */ +void AnimationType::reset() { + _quick = false; + _id = 177; +} + +/** + * Drops sprite onto screen. + * @remarks Originally called 'andexor' + */ +void AnimationType::draw() { + if (_vanishIfStill && (_moveX == 0) && (_moveY == 0)) + return; + + byte picnum = _facingDir * _seq + _stepNum; + + _anim->_vm->_graphics->drawSprite(this, picnum, _x, _y); +} + +/** + * Turns character round. + */ +void AnimationType::turn(Direction whichway) { + if (whichway == 8) + _facingDir = kDirUp; + else + _facingDir = whichway; +} + +/** + * Switches it on. + */ +void AnimationType::appear(int16 wx, int16 wy, Direction wf) { + _x = (wx / 8) * 8; + _y = wy; + _oldX[_anim->_vm->_cp] = wx; + _oldY[_anim->_vm->_cp] = wy; + turn(wf); + _visible = true; + _moveX = 0; + _moveY = 0; +} + +/** + * Check collision + * @remarks Originally called 'collision_check' + */ +bool AnimationType::checkCollision() { + for (int i = 0; i < _anim->kSpriteNumbMax; i++) { + AnimationType *spr = _anim->_sprites[i]; + if (spr->_quick && (spr->_id != _id) && (_x + _xLength > spr->_x) && (_x < spr->_x + spr->_xLength) && (spr->_y == _y)) + return true; + } + + return false; +} + +/** + * Prepares for draw(), etc. + */ +void AnimationType::walk() { + if (!_anim->_vm->_doingSpriteRun) { + _oldX[_anim->_vm->_cp] = _x; + _oldY[_anim->_vm->_cp] = _y; + if (_homing) + homeStep(); + _x += _moveX; + _y += _moveY; + } + + if (_doCheck) { + if (checkCollision()) { + bounce(); + return; + } + + byte magicColor = _anim->checkFeet(_x, _x + _xLength, _oldY[_anim->_vm->_cp], _y, _yLength) - 1; + // -1 is because the modified array indexes of magics[] compared to Pascal . + + if ((magicColor != 255) & !_anim->_vm->_doingSpriteRun) { + MagicType *magic = &_anim->_vm->_magics[magicColor]; + switch (magic->_operation) { + case kMagicExclaim: + bounce(); + _anim->_mustExclaim = true; + _anim->_sayWhat = magic->_data; + break; + case kMagicBounce: + bounce(); + break; + case kMagicTransport: + _anim->_vm->flipRoom((Room)(magic->_data >> 8), magic->_data & 0xff); + break; + case kMagicUnfinished: { + bounce(); + Common::String tmpStr = Common::String::format("%c%cSorry.%cThis place is not available yet!", + kControlBell, kControlCenter, kControlRoman); + _anim->_vm->_dialogs->displayText(tmpStr); + } + break; + case kMagicSpecial: + _anim->callSpecial(magic->_data); + break; + case kMagicOpenDoor: + _anim->_vm->openDoor((Room)(magic->_data >> 8), magic->_data & 0xff, magicColor); + break; + } + } + } + + if (!_anim->_vm->_doingSpriteRun) { + _count++; + if (((_moveX != 0) || (_moveY != 0)) && (_count > 1)) { + _stepNum++; + if (_stepNum == _seq) + _stepNum = 0; + _count = 0; + } + } +} + +/** + * Bounces off walls + */ +void AnimationType::bounce() { + _x = _oldX[_anim->_vm->_cp]; + _y = _oldY[_anim->_vm->_cp]; + if (_doCheck) + _anim->stopWalking(); + else + stopWalk(); + _anim->_vm->drawDirection(); +} + +int8 AnimationType::getSign(int16 val) { + if (val > 0) + return 1; + else if (val < 0) + return -1; + else + return 0; +} + +/** + * Home in on a point. + */ +void AnimationType::walkTo(byte pedNum) { + PedType *curPed = &_anim->_vm->_peds[pedNum]; + + setSpeed(getSign(curPed->_x - _x) * 4, getSign(curPed->_y - _y)); + _homingX = curPed->_x - _xLength / 2; + _homingY = curPed->_y - _yLength; + _homing = true; +} + +void AnimationType::stopHoming() { + _homing = false; +} + +/** + * Calculates ix & iy for one homing step. + */ +void AnimationType::homeStep() { + int16 temp; + + if ((_homingX == _x) && (_homingY == _y)) { + // touching the target + stopWalk(); + return; + } + _moveX = 0; + _moveY = 0; + if (_homingY != _y) { + temp = _homingY - _y; + if (temp > 4) + _moveY = 4; + else if (temp < -4) + _moveY = -4; + else + _moveY = temp; + } + if (_homingX != _x) { + temp = _homingX - _x; + if (temp > 4) + _moveX = 4; + else if (temp < -4) + _moveX = -4; + else + _moveX = temp; + } +} + +/** + * Sets ix & iy, non-homing, etc. + */ +void AnimationType::setSpeed(int8 xx, int8 yy) { + _moveX = xx; + _moveY = yy; + if ((_moveX == 0) && (_moveY == 0)) + return; // no movement + if (_moveX == 0) { + // No horz movement + if (_moveY < 0) + turn(kDirUp); + else + turn(kDirDown); + } else { + if (_moveX < 0) + turn(kDirLeft); + else + turn(kDirRight); + } +} + +/** + * Stops the sprite from moving. + */ +void AnimationType::stopWalk() { + _moveX = 0; + _moveY = 0; + _homing = false; +} + +/** + * Sets up talk vars. + */ +void AnimationType::chatter() { + _anim->_vm->_dialogs->setTalkPos(_x + _xLength / 2, _y); + _anim->_vm->_graphics->setDialogColor(_bgBubbleCol, _fgBubbleCol); +} + +void AnimationType::remove() { + for (int i = 0; i < _frameNum; i++) { + delete[] _mani[i]; + delete[] _sil[i]; + } + + _quick = false; + _id = 177; +} + +Animation::Animation(AvalancheEngine *vm) { + _vm = vm; + _mustExclaim = false; + + for (int16 i = 0; i < kSpriteNumbMax; i++) { + _sprites[i] = new AnimationType(this); + } +} + +Animation::~Animation() { + for (int16 i = 0; i < kSpriteNumbMax; i++) { + AnimationType *curSpr = _sprites[i]; + + if (curSpr->_quick) + curSpr->remove(); + delete(curSpr); + } +} + +/** + * Resets Animation variables. + * @remarks Originally called 'loadtrip' + */ +void Animation::resetAnims() { + setDirection(kDirStopped); + for (int16 i = 0; i < kSpriteNumbMax; i++) + _sprites[i]->reset(); +} + +byte Animation::checkFeet(int16 x1, int16 x2, int16 oy, int16 y, byte yl) { + if (!_vm->_alive) + return 0; + + if (x1 < 0) + x1 = 0; + if (x2 > 639) + x2 = 639; + + int16 minY = MIN(oy, y) + yl; + int16 maxY = MAX(oy, y) + yl; + + return _vm->_graphics->getAlsoColor(x1, minY, x2, maxY); +} + +byte Animation::geidaPed(byte ped) { + switch (ped) { + case 1: + return 6; + case 2: + case 6: + return 7; + case 3: + case 5: + return 8; + case 4: + return 9; + default: + error("geidaPed(): Unhandled ped value %d", ped); + } +} + +/** + * When you enter a new position in the catacombs, this procedure should be + * called. It changes the 'also' codes so that they may match the picture + * on the screen. + */ +void Animation::catacombMove(byte ped) { + // XY_uint16 is _catacombX+_catacombY*256. Thus, every room in the + // catacombs has a different number for it. + uint16 xy = _vm->_catacombX + _vm->_catacombY * 256; + _geidaSpin = 0; + + switch (xy) { + case 1801: // Exit catacombs + _vm->flipRoom(kRoomLustiesRoom, 4); + _vm->_dialogs->displayText("Phew! Nice to be out of there!"); + return; + case 1033:{ // Oubliette + _vm->flipRoom(kRoomOubliette, 1); + Common::String tmpStr = Common::String::format("Oh, NO!%c1%c", kControlRegister, kControlSpeechBubble); + _vm->_dialogs->displayText(tmpStr); + } + return; + case 4: + _vm->flipRoom(kRoomGeidas, 1); + return; + case 2307: + _vm->flipRoom(kRoomLusties, 5); + _vm->_dialogs->displayText("Oh no... here we go again..."); + _vm->_userMovesAvvy = false; + _sprites[0]->_moveY = 1; + _sprites[0]->_moveX = 0; + return; + } + + if (!_vm->_enterCatacombsFromLustiesRoom) + _vm->loadRoom(29); + int32 here = kCatacombMap[_vm->_catacombY - 1][_vm->_catacombX - 1]; + + switch (here & 0xf) { // West. + case 0: // no connection (wall) + _vm->_magics[1]._operation = kMagicBounce; // Sloping wall. + _vm->_magics[2]._operation = kMagicNothing; // Straight wall. + _vm->_portals[4]._operation = kMagicNothing; // Door. + _vm->_background->draw(-1, -1, 27); + break; + case 0x1: // no connection (wall + shield), + _vm->_magics[1]._operation = kMagicBounce; // Sloping wall. + _vm->_magics[2]._operation = kMagicNothing; // Straight wall. + _vm->_portals[4]._operation = kMagicNothing; // Door. + _vm->_background->draw(-1, -1, 27); // Wall, plus... + _vm->_background->draw(-1, -1, 28); // ...shield. + break; + case 0x2: // wall with door + _vm->_magics[1]._operation = kMagicBounce; // Sloping wall. + _vm->_magics[2]._operation = kMagicNothing; // Straight wall. + _vm->_portals[4]._operation = kMagicSpecial; // Door. + _vm->_background->draw(-1, -1, 27); // Wall, plus... + _vm->_background->draw(-1, -1, 29); // ...door. + break; + case 0x3: // wall with door and shield + _vm->_magics[1]._operation = kMagicBounce; // Sloping wall. + _vm->_magics[2]._operation = kMagicNothing; // Straight wall. + _vm->_portals[4]._operation = kMagicSpecial; // Door. + _vm->_background->draw(-1, -1, 27); // Wall, plus... + _vm->_background->draw(-1, -1, 29); // ...door, and... + _vm->_background->draw(-1, -1, 28); // ...shield. + break; + case 0x4: // no connection (wall + window), + _vm->_magics[1]._operation = kMagicBounce; // Sloping wall. + _vm->_magics[2]._operation = kMagicNothing; // Straight wall. + _vm->_portals[4]._operation = kMagicNothing; // Door. + _vm->_background->draw(-1, -1, 27); // Wall, plus... + _vm->_background->draw(-1, -1, 4); // ...window. + break; + case 0x5: // wall with door and window + _vm->_magics[1]._operation = kMagicBounce; // Sloping wall. + _vm->_magics[2]._operation = kMagicNothing; // Straight wall. + _vm->_portals[4]._operation = kMagicSpecial; // Door. + _vm->_background->draw(-1, -1, 27); // Wall, plus... + _vm->_background->draw(-1, -1, 29); // ...door, and... + _vm->_background->draw(-1, -1, 4); // ...window. + break; + case 0x6: // no connection (wall + torches), + _vm->_magics[1]._operation = kMagicBounce; // Sloping wall. + _vm->_magics[2]._operation = kMagicNothing; // Straight wall. + _vm->_portals[4]._operation = kMagicNothing; // No door. + _vm->_background->draw(-1, -1, 27); // Wall, plus... + _vm->_background->draw(-1, -1, 6); // ...torches. + break; + case 0x7: // wall with door and torches + _vm->_magics[1]._operation = kMagicBounce; // Sloping wall. + _vm->_magics[2]._operation = kMagicNothing; // Straight wall. + _vm->_portals[4]._operation = kMagicSpecial; // Door. + _vm->_background->draw(-1, -1, 27); // Wall, plus... + _vm->_background->draw(-1, -1, 29); // ...door, and... + _vm->_background->draw(-1, -1, 6); // ...torches. + break; + case 0xf: // straight-through corridor. + _vm->_magics[1]._operation = kMagicNothing; // Sloping wall. + _vm->_magics[2]._operation = kMagicSpecial; // Straight wall. + break; + } + + /* ---- */ + + switch ((here & 0xf0) >> 4) { // East + case 0: // no connection (wall) + _vm->_magics[4]._operation = kMagicBounce; // Sloping wall. + _vm->_magics[5]._operation = kMagicNothing; // Straight wall. + _vm->_portals[6]._operation = kMagicNothing; // Door. + _vm->_background->draw(-1, -1, 18); + break; + case 0x1: // no connection (wall + window), + _vm->_magics[4]._operation = kMagicBounce; // Sloping wall. + _vm->_magics[5]._operation = kMagicNothing; // Straight wall. + _vm->_portals[6]._operation = kMagicNothing; // Door. + _vm->_background->draw(-1, -1, 18); // Wall, plus... + _vm->_background->draw(-1, -1, 19); // ...window. + break; + case 0x2: // wall with door + _vm->_magics[4]._operation = kMagicBounce; // Sloping wall. + _vm->_magics[5]._operation = kMagicNothing; // Straight wall. + _vm->_portals[6]._operation = kMagicSpecial; // Door. + _vm->_background->draw(-1, -1, 18); // Wall, plus... + _vm->_background->draw(-1, -1, 20); // ...door. + break; + case 0x3: // wall with door and window + _vm->_magics[4]._operation = kMagicBounce; // Sloping wall. + _vm->_magics[5]._operation = kMagicNothing; // Straight wall. + _vm->_portals[6]._operation = kMagicSpecial; // Door. + _vm->_background->draw(-1, -1, 18); // Wall, plus... + _vm->_background->draw(-1, -1, 19); // ...door, and... + _vm->_background->draw(-1, -1, 20); // ...window. + break; + case 0x6: // no connection (wall + torches), + _vm->_magics[4]._operation = kMagicBounce; // Sloping wall. + _vm->_magics[5]._operation = kMagicNothing; // Straight wall. + _vm->_portals[6]._operation = kMagicNothing; // No door. + _vm->_background->draw(-1, -1, 18); // Wall, plus... + _vm->_background->draw(-1, -1, 17); // ...torches. + break; + case 0x7: // wall with door and torches + _vm->_magics[4]._operation = kMagicBounce; // Sloping wall. + _vm->_magics[5]._operation = kMagicNothing; // Straight wall. + _vm->_portals[6]._operation = kMagicSpecial; // Door. + _vm->_background->draw(-1, -1, 18); // Wall, plus... + _vm->_background->draw(-1, -1, 20); // ...door, and... + _vm->_background->draw(-1, -1, 17); // ...torches. + break; + case 0xf: // straight-through corridor. + _vm->_magics[4]._operation = kMagicNothing; // Sloping wall. + _vm->_magics[5]._operation = kMagicSpecial; // Straight wall. + _vm->_portals[6]._operation = kMagicNothing; // Door. + break; + } + + switch ((here & 0xf00) >> 8) { // South + case 0: // No connection. + _vm->_magics[6]._operation = kMagicBounce; + _vm->_magics[11]._operation = kMagicBounce; + _vm->_magics[12]._operation = kMagicBounce; + break; + case 0x1: + _vm->_background->draw(-1, -1, 21); + + if ((xy == 2051) && _vm->_geidaFollows) + _vm->_magics[12]._operation = kMagicExclaim; + else + _vm->_magics[12]._operation = kMagicSpecial; // Right exit south. + + _vm->_magics[6]._operation = kMagicBounce; + _vm->_magics[11]._operation = kMagicBounce; + break; + case 0x2: + _vm->_background->draw(-1, -1, 22); + _vm->_magics[6]._operation = kMagicSpecial; // Middle exit south. + _vm->_magics[11]._operation = kMagicBounce; + _vm->_magics[12]._operation = kMagicBounce; + break; + case 0x3: + _vm->_background->draw(-1, -1, 23); + _vm->_magics[11]._operation = kMagicSpecial; // Left exit south. + _vm->_magics[6]._operation = kMagicBounce; + _vm->_magics[12]._operation = kMagicBounce; + break; + } + + switch ((here & 0xf000) >> 12) { // North + case 0: // No connection + _vm->_magics[0]._operation = kMagicBounce; + _vm->_portals[3]._operation = kMagicNothing; // Door. + break; + // LEFT handles: +#if 0 + case 0x1: + _vm->_celer->show_one(-1, -1, 4); + _vm->magics[1].op = _vm->bounces; // { Left exit north. } { Change magic number! } + _vm->portals[12].op = _vm->special; // { Door. } + break; +#endif + case 0x2: + _vm->_background->draw(-1, -1, 3); + _vm->_magics[0]._operation = kMagicBounce; // Middle exit north. + _vm->_portals[3]._operation = kMagicSpecial; // Door. + break; +#if 0 + case 0x3: + _vm->_celer->show_one(-1, -1, 4); + _vm->magics[1].op = _vm->bounces; // { Right exit north. } { Change magic number! } + _vm->portals[12].op = _vm->special; // { Door. } + break; + // RIGHT handles: + case 0x4: + _vm->_celer->show_one(-1, -1, 3); + _vm->magics[1].op = _vm->bounces; // { Left exit north. } { Change magic number! } + _vm->portals[12].op = _vm->special; // { Door. } + break; +#endif + case 0x5: + _vm->_background->draw(-1, -1, 2); + _vm->_magics[0]._operation = kMagicBounce; // Middle exit north. + _vm->_portals[3]._operation = kMagicSpecial; // Door. + break; +#if 0 + case 0x6: + _vm->_celer->show_one(-1, -1, 3); + _vm->magics[1].op = _vm->bounces; // { Right exit north. } + _vm->portals[12].op = _vm->special; // { Door. } + break; +#endif + // ARCHWAYS: + case 0x7: + case 0x8: + case 0x9: + _vm->_background->draw(-1, -1, 5); + + if (((here & 0xf000) >> 12) > 0x7) + _vm->_background->draw(-1, -1, 30); + if (((here & 0xf000) >> 12) == 0x9) + _vm->_background->draw(-1, -1, 31); + + _vm->_magics[0]._operation = kMagicSpecial; // Middle arch north. + _vm->_portals[3]._operation = kMagicNothing; // Door. + break; + // DECORATIONS: + case 0xd: // No connection + WINDOW + _vm->_magics[0]._operation = kMagicBounce; + _vm->_portals[3]._operation = kMagicNothing; // Door. + _vm->_background->draw(-1, -1, 13); + break; + case 0xe: // No connection + TORCH + _vm->_magics[0]._operation = kMagicBounce; + _vm->_portals[3]._operation = kMagicNothing; // Door. + _vm->_background->draw(-1, -1, 7); + break; + // Recessed door: + case 0xf: + _vm->_magics[0]._operation = kMagicNothing; // Door to Geida's room. + _vm->_background->draw(-1, -1, 0); + _vm->_portals[3]._operation = kMagicSpecial; // Door. + break; + } + + switch (xy) { + case 514: + _vm->_background->draw(-1, -1, 16); + break; // [2,2] : "Art Gallery" sign over door. + case 264: + _vm->_background->draw(-1, -1, 8); + break; // [8,1] : "The Wrong Way!" sign. + case 1797: + _vm->_background->draw(-1, -1, 1); + break; // [5,7] : "Ite Mingite" sign. + case 258: + for (int i = 0; i <= 2; i++) { // [2,1] : Art gallery - pictures + _vm->_background->draw(130 + i * 120, 70, 14); + _vm->_background->draw(184 + i * 120, 78, 15); + } + break; + case 1287: + for (int i = 10; i <= 13; i++) + _vm->_background->draw(-1, -1, i - 1); + break; // [7,5] : 4 candles. + case 776: + _vm->_background->draw(-1, -1, 9); + break; // [8,3] : 1 candle. + case 2049: + _vm->_background->draw(-1, -1, 10); + break; // [1,8] : another candle. + case 257: + _vm->_background->draw(-1, -1, 11); + _vm->_background->draw(-1, -1, 12); + break; // [1,1] : the other two. + } + + if (_vm->_geidaFollows && (ped > 0)) { + AnimationType *spr1 = _sprites[1]; + + if (!spr1->_quick) // If we don't already have her... + spr1->init(5, true); // ...Load Geida. + appearPed(1, geidaPed(ped)); + spr1->_callEachStepFl = true; + spr1->_eachStepProc = kProcGeida; + } +} + +/** + * This proc gets called whenever you touch a line defined as _vm->special. + */ +void Animation::dawnDelay() { + _vm->_timer->addTimer(2, Timer::kProcDawnDelay, Timer::kReasonDawndelay); +} + +void Animation::callSpecial(uint16 which) { + switch (which) { + case 1: // _vm->special 1: Room 22: top of stairs. + _vm->_background->draw(-1, -1, 0); + _vm->_brummieStairs = 1; + _vm->_magics[9]._operation = kMagicNothing; + _vm->_timer->addTimer(10, Timer::kProcStairs, Timer::kReasonBrummieStairs); + stopWalking(); + _vm->_userMovesAvvy = false; + break; + case 2: // _vm->special 2: Room 22: bottom of stairs. + _vm->_brummieStairs = 3; + _vm->_magics[10]._operation = kMagicNothing; + _vm->_magics[11]._operation = kMagicExclaim; + _vm->_magics[11]._data = 5; + _vm->_magics[3]._operation = kMagicBounce; // Now works as planned! + stopWalking(); + _vm->_dialogs->displayScrollChain('q', 26); + _vm->_userMovesAvvy = true; + break; + case 3: // _vm->special 3: Room 71: triggers dart. + _sprites[0]->bounce(); // Must include that. + + if (!_arrowTriggered) { + _arrowTriggered = true; + + AnimationType *spr1 = _sprites[1]; + appearPed(1, 3); // The dart starts at ped 4, and... + spr1->walkTo(4); // flies to ped 5 (- 1 for pascal to C conversion). + spr1->_facingDir = kDirUp; // Only face. + // Should call some kind of Eachstep procedure which will deallocate + // the sprite when it hits the wall, and replace it with the chunk + // graphic of the arrow buried in the plaster. */ + + // OK! + spr1->_callEachStepFl = true; + spr1->_eachStepProc = kProcArrow; + } + break; + case 4: // This is the ghost room link. + _vm->fadeOut(); + _sprites[0]->turn(kDirRight); // you'll see this after we get back from bootstrap + _vm->_timer->addTimer(1, Timer::kProcGhostRoomPhew, Timer::kReasonGhostRoomPhew); + //_vm->_enid->backToBootstrap(3); TODO: Replace it with proper ScummVM-friendly function(s)! Do not remove until then! + break; + case 5: + if (_vm->_friarWillTieYouUp) { + // _vm->special 5: Room 42: touched tree, and get tied up. + _vm->_magics[4]._operation = kMagicBounce; // Boundary effect is now working again. + _vm->_dialogs->displayScrollChain('q', 35); + _sprites[0]->remove(); + //tr[1].vanishifstill:=true; + + AnimationType *spr1 = _sprites[1]; + _vm->_background->draw(-1, -1, 1); + _vm->_dialogs->displayScrollChain('q', 36); + _vm->_tiedUp = true; + _vm->_friarWillTieYouUp = false; + spr1->walkTo(2); + spr1->_vanishIfStill = true; + spr1->_doCheck = true; // One of them must have Check_Me switched on. + _vm->setRoom(kPeopleFriarTuck, kRoomDummy); // Not here, then. + _vm->_timer->addTimer(364, Timer::kProcHangAround, Timer::kReasonHangingAround); + } + break; + case 6: { + // _vm->special 6: fall down oubliette. + AnimationType *avvy = _sprites[0]; + _vm->_userMovesAvvy = false; + avvy->_moveX = 3; + avvy->_moveY = 0; + avvy->_facingDir = kDirRight; + _vm->_timer->addTimer(1, Timer::kProcFallDownOubliette, Timer::kReasonFallingDownOubliette); + } + break; + case 7: // _vm->special 7: stop falling down oubliette. + _sprites[0]->_visible = false; + _vm->_magics[9]._operation = kMagicNothing; + stopWalking(); + _vm->_timer->loseTimer(Timer::kReasonFallingDownOubliette); + //_vm->mblit(12, 80, 38, 160, 3, 0); + //_vm->mblit(12, 80, 38, 160, 3, 1); + _vm->_dialogs->displayText("Oh dear, you seem to be down the bottom of an oubliette."); + _vm->_timer->addTimer(200, Timer::kProcMeetAvaroid, Timer::kReasonMeetingAvaroid); + break; + case 8: // _vm->special 8: leave du Lustie's room. + if (_vm->_geidaFollows && !_vm->_lustieIsAsleep) { + AnimationType *spr1 = _sprites[1]; + _vm->_dialogs->displayScrollChain('q', 63); + spr1->turn(kDirDown); + spr1->stopWalk(); + spr1->_callEachStepFl = false; // Geida + _vm->gameOver(); + } + break; + case 9: { + // _vm->special 9: lose Geida to Robin Hood... + if (!_vm->_geidaFollows) + return; // DOESN'T COUNT: no Geida. + AnimationType *spr1 = _sprites[1]; + spr1->_callEachStepFl = false; // She no longer follows Avvy around. + spr1->walkTo(3); // She walks to somewhere... + _sprites[0]->remove(); // Lose Avvy. + _vm->_userMovesAvvy = false; + _vm->_timer->addTimer(40, Timer::kProcRobinHoodAndGeida, Timer::kReasonRobinHoodAndGeida); + } + break; + case 10: // _vm->special 10: transfer north in catacombs. + if ((_vm->_catacombX == 4) && (_vm->_catacombY == 1)) { + // Into Geida's room. + if (_vm->_objects[kObjectKey - 1]) + _vm->_dialogs->displayScrollChain('q', 62); + else { + _vm->_dialogs->displayScrollChain('q', 61); + return; + } + } + _vm->fadeOut(); + _vm->_catacombY--; + catacombMove(4); + if (_vm->_room != kRoomCatacombs) + return; + switch ((kCatacombMap[_vm->_catacombY - 1][_vm->_catacombX - 1] & 0xf00) >> 8) { + case 0x1: + appearPed(0, 11); + break; + case 0x3: + appearPed(0, 10); + break; + default: + appearPed(0, 3); + } + dawnDelay(); + break; + case 11: // _vm->special 11: transfer east in catacombs. + _vm->fadeOut(); + _vm->_catacombX++; + catacombMove(1); + if (_vm->_room != kRoomCatacombs) + return; + appearPed(0, 0); + dawnDelay(); + break; + case 12: // _vm->special 12: transfer south in catacombs. + _vm->fadeOut(); + _vm->_catacombY++; + catacombMove(2); + if (_vm->_room != kRoomCatacombs) + return; + appearPed(0, 1); + dawnDelay(); + break; + case 13: // _vm->special 13: transfer west in catacombs. + _vm->fadeOut(); + _vm->_catacombX--; + catacombMove(3); + if (_vm->_room != kRoomCatacombs) + return; + appearPed(0, 2); + dawnDelay(); + break; + } +} + +void Animation::updateSpeed() { + AnimationType *avvy = _sprites[0]; + // Given that you've just changed the speed in _speedX, this adjusts _moveX. + avvy->_moveX = (avvy->_moveX / 3) * avvy->_speedX; + _vm->_graphics->drawSpeedBar(avvy->_speedX); +} + +void Animation::setMoveSpeed(byte t, Direction dir) { + AnimationType *spr = _sprites[t]; + switch (dir) { + case kDirUp: + spr->setSpeed(0, -spr->_speedY); + break; + case kDirDown: + spr->setSpeed(0, spr->_speedY); + break; + case kDirLeft: + spr->setSpeed(-spr->_speedX, 0); + break; + case kDirRight: + spr->setSpeed(spr->_speedX, 0); + break; + case kDirUpLeft: + spr->setSpeed(-spr->_speedX, -spr->_speedY); + break; + case kDirUpRight: + spr->setSpeed(spr->_speedX, -spr->_speedY); + break; + case kDirDownLeft: + spr->setSpeed(-spr->_speedX, spr->_speedY); + break; + case kDirDownRight: + spr->setSpeed(spr->_speedX, spr->_speedY); + break; + default: + break; + } +} + +void Animation::appearPed(byte sprNum, byte pedNum) { + AnimationType *curSpr = _sprites[sprNum]; + PedType *curPed = &_vm->_peds[pedNum]; + curSpr->appear(curPed->_x - curSpr->_xLength / 2, curPed->_y - curSpr->_yLength, curPed->_direction); + setMoveSpeed(sprNum, curPed->_direction); +} + +/** + * @remarks Originally called 'follow_avvy_y' + */ +void Animation::followAvalotY(byte tripnum) { + if (_sprites[0]->_facingDir == kDirLeft) + return; + + AnimationType *tripSpr = _sprites[tripnum]; + AnimationType *spr1 = _sprites[1]; + + if (tripSpr->_homing) + tripSpr->_homingY = spr1->_y; + else { + if (tripSpr->_y < spr1->_y) + tripSpr->_y++; + else if (tripSpr->_y > spr1->_y) + tripSpr->_y--; + else + return; + + if (tripSpr->_moveX == 0) { + tripSpr->_stepNum++; + if (tripSpr->_stepNum == tripSpr->_seq) + tripSpr->_stepNum = 0; + tripSpr->_count = 0; + } + } +} + +void Animation::backAndForth(byte tripnum) { + AnimationType *tripSpr = _sprites[tripnum]; + + if (!tripSpr->_homing) { + if (tripSpr->_facingDir == kDirRight) + tripSpr->walkTo(3); + else + tripSpr->walkTo(4); + } +} + +void Animation::faceAvvy(byte tripnum) { + AnimationType *tripSpr = _sprites[tripnum]; + + if (!tripSpr->_homing) { + if (_sprites[0]->_x >= tripSpr->_x) + tripSpr->_facingDir = kDirRight; + else + tripSpr->_facingDir = kDirLeft; + } +} + +void Animation::arrowProcs(byte tripnum) { + AnimationType *tripSpr = _sprites[tripnum]; + AnimationType *avvy = _sprites[tripnum]; + + if (tripSpr->_homing) { + // Arrow is still in flight. + // We must check whether or not the arrow has collided tr[tripnum] Avvy's head. + // This is so if: a) the bottom of the arrow is below Avvy's head, + // b) the left of the arrow is left of the right of Avvy's head, and + // c) the right of the arrow is right of the left of Avvy's head. + if ((tripSpr->_y + tripSpr->_yLength >= avvy->_y) // A + && (tripSpr->_x <= avvy->_x + avvy->_xLength) // B + && (tripSpr->_x + tripSpr->_xLength >= avvy->_x)) { // C + // OK, it's hit him... what now? + + _sprites[1]->_callEachStepFl = false; // prevent recursion. + _vm->_dialogs->displayScrollChain('Q', 47); // Complaint! + tripSpr->remove(); // Deallocate the arrow. + + _vm->gameOver(); + + _vm->_userMovesAvvy = false; // Stop the user from moving him. + _vm->_timer->addTimer(55, Timer::kProcNaughtyDuke, Timer::kReasonNaughtyDuke); + } + } else { // Arrow has hit the wall! + tripSpr->remove(); // Deallocate the arrow. + _vm->_background->draw(-1, -1, 2); // Show pic of arrow stuck into the door. + _vm->_arrowInTheDoor = true; // So that we can pick it up. + } +} + +void Animation::grabAvvy(byte tripnum) { // For Friar Tuck, in Nottingham. + AnimationType *tripSpr = _sprites[tripnum]; + AnimationType *avvy = _sprites[tripnum]; + + int16 tox = avvy->_x + 17; + int16 toy = avvy->_y - 1; + if ((tripSpr->_x == tox) && (tripSpr->_y == toy)) { + tripSpr->_callEachStepFl = false; + tripSpr->_facingDir = kDirLeft; + tripSpr->stopWalk(); + // ... whatever ... + } else { + // Still some way to go. + if (tripSpr->_x < tox) { + tripSpr->_x += 5; + if (tripSpr->_x > tox) + tripSpr->_x = tox; + } + if (tripSpr->_y < toy) + tripSpr->_y++; + tripSpr->_stepNum++; + if (tripSpr->_stepNum == tripSpr->_seq) + tripSpr->_stepNum = 0; + } +} + +void Animation::takeAStep(byte &tripnum) { + AnimationType *tripSpr = _sprites[tripnum]; + + if (tripSpr->_moveX == 0) { + tripSpr->_stepNum++; + if (tripSpr->_stepNum == tripSpr->_seq) + tripSpr->_stepNum = 0; + tripSpr->_count = 0; + } +} + +void Animation::spin(Direction dir, byte &tripnum) { + AnimationType *tripSpr = _sprites[tripnum]; + + if (tripSpr->_facingDir == dir) + return; + + tripSpr->_facingDir = dir; + if (tripSpr->_id == 2) + return; // Not for Spludwick + + _geidaSpin++; + _geidaTime = 20; + if (_geidaSpin == 5) { + _vm->_dialogs->displayText("Steady on, Avvy, you'll make the poor girl dizzy!"); + _geidaSpin = 0; + _geidaTime = 0; // knock out records + } +} + +void Animation::geidaProcs(byte tripnum) { + AnimationType *tripSpr = _sprites[tripnum]; + AnimationType *avvy = _sprites[0]; + + if (_geidaTime > 0) { + _geidaTime--; + if (_geidaTime == 0) + _geidaSpin = 0; + } + + if (tripSpr->_y < (avvy->_y - 2)) { + // Geida is further from the screen than Avvy. + spin(kDirDown, tripnum); + tripSpr->_moveY = 1; + tripSpr->_moveX = 0; + takeAStep(tripnum); + return; + } else if (tripSpr->_y > (avvy->_y + 2)) { + // Avvy is further from the screen than Geida. + spin(kDirUp, tripnum); + tripSpr->_moveY = -1; + tripSpr->_moveX = 0; + takeAStep(tripnum); + return; + } + + tripSpr->_moveY = 0; + // These 12-s are not in the original, I added them to make the following method more "smooth". + // Now the NPC which is following Avvy won't block his way and will walk next to him properly. + if (tripSpr->_x < avvy->_x - avvy->_speedX * 8 - 12) { + tripSpr->_moveX = avvy->_speedX; + spin(kDirRight, tripnum); + takeAStep(tripnum); + } else if (tripSpr->_x > avvy->_x + avvy->_speedX * 8 + 12) { + tripSpr->_moveX = -avvy->_speedX; + spin(kDirLeft, tripnum); + takeAStep(tripnum); + } else + tripSpr->_moveX = 0; +} + +/** + * @remarks Originally called 'call_andexors' + */ +void Animation::drawSprites() { + int8 order[5]; + byte temp; + bool ok; + + for (int i = 0; i < 5; i++) + order[i] = -1; + + for (int16 i = 0; i < kSpriteNumbMax; i++) { + AnimationType *curSpr = _sprites[i]; + if (curSpr->_quick && curSpr->_visible) + order[i] = i; + } + + do { + ok = true; + for (int i = 0; i < 4; i++) { + if ((order[i] != -1) && (order[i + 1] != -1) && (_sprites[order[i]]->_y > _sprites[order[i + 1]]->_y)) { + // Swap them! + temp = order[i]; + order[i] = order[i + 1]; + order[i + 1] = temp; + ok = false; + } + } + } while (!ok); + + _vm->_graphics->refreshBackground(); + + for (int i = 0; i < 5; i++) { + if (order[i] > -1) + _sprites[order[i]]->draw(); + } +} + +/** + * Animation links + * @remarks Originally called 'trippancy_link' + */ +void Animation::animLink() { + if (_vm->_menu->isActive() || _vm->_seeScroll) + return; + for (int16 i = 0; i < kSpriteNumbMax; i++) { + AnimationType *curSpr = _sprites[i]; + if (curSpr->_quick && curSpr->_visible) + curSpr->walk(); + } + + drawSprites(); + + for (int16 i = 0; i < kSpriteNumbMax; i++) { + AnimationType *curSpr = _sprites[i]; + if (curSpr->_quick && curSpr->_callEachStepFl) { + switch (curSpr->_eachStepProc) { + case kProcFollowAvvyY : + followAvalotY(i); + break; + case kProcBackAndForth : + backAndForth(i); + break; + case kProcFaceAvvy : + faceAvvy(i); + break; + case kProcArrow : + arrowProcs(i); + break; + // PROCSpludwick_procs : spludwick_procs(fv); + case kProcGrabAvvy : + grabAvvy(i); + break; + case kProcGeida : + geidaProcs(i); + break; + } + } + } + + if (_mustExclaim) { + _mustExclaim = false; + _vm->_dialogs->displayScrollChain('x', _sayWhat); + } +} + +void Animation::stopWalking() { + AnimationType *avvy = _sprites[0]; + + avvy->stopWalk(); + _direction = kDirStopped; + if (_vm->_alive) + avvy->_stepNum = 1; +} + +/** + * Hide in the cupboard + * @remarks Originally called 'hide_in_the_cupboard' + */ +void Animation::hideInCupboard() { + if (_vm->_avvysInTheCupboard) { + if (_vm->_parser->_wearing == kObjectDummy) { + Common::String tmpStr = Common::String::format("%cAVVY!%cGet dressed first!", kControlItalic, kControlRoman); + _vm->_dialogs->displayText(tmpStr); + } else { + _sprites[0]->_visible = true; + _vm->_userMovesAvvy = true; + appearPed(0, 2); // Walk out of the cupboard. + _vm->_dialogs->displayText("You leave the cupboard. Nice to be out of there!"); + _vm->_avvysInTheCupboard = false; + _vm->_sequence->startCupboardSeq(); + } + } else { + // Not hiding in the cupboard + _sprites[0]->_visible = false; + _vm->_userMovesAvvy = false; + Common::String tmpStr = Common::String::format("You walk into the room...%cIt seems to be an empty, " \ + "but dusty, cupboard. Hmmmm... you leave the door slightly open to avoid suffocation.", kControlParagraph); + _vm->_dialogs->displayText(tmpStr); + _vm->_avvysInTheCupboard = true; + _vm->_background->draw(-1, -1, 7); + } +} + +/** + * Returns true if you're within field "which". + */ +bool Animation::inField(byte which) { + AnimationType *avvy = _sprites[0]; + + FieldType *curField = &_vm->_fields[which]; + int16 yy = avvy->_y + avvy->_yLength; + + return (avvy->_x >= curField->_x1) && (avvy->_x <= curField->_x2) && (yy >= curField->_y1) && (yy <= curField->_y2); +} + +/** + * Returns True if you're near a door. + */ +bool Animation::nearDoor() { + if (_vm->_fieldNum < 8) + // there ARE no doors here! + return false; + + AnimationType *avvy = _sprites[0]; + + int16 ux = avvy->_x; + int16 uy = avvy->_y + avvy->_yLength; + + for (int i = 8; i < _vm->_fieldNum; i++) { + FieldType *curField = &_vm->_fields[i]; + if ((ux >= curField->_x1) && (ux <= curField->_x2) && (uy >= curField->_y1) && (uy <= curField->_y2)) + return true; + } + + return false; +} + +/** + * @remarks Originally called 'tripkey' + */ +void Animation::handleMoveKey(const Common::Event &event) { + if (!_vm->_userMovesAvvy) + return; + + if (_vm->_menu->_activeMenuItem._activeNow) + _vm->_parser->tryDropdown(); + else { + switch (event.kbd.keycode) { + case Common::KEYCODE_UP: + if (_direction != kDirUp) { + _direction = kDirUp; + setMoveSpeed(0, _direction); + } else + stopWalking(); + break; + case Common::KEYCODE_DOWN: + if (_direction != kDirDown) { + _direction = kDirDown; + setMoveSpeed(0, _direction); + } else + stopWalking(); + break; + case Common::KEYCODE_LEFT: + if (_direction != kDirLeft) { + _direction = kDirLeft; + setMoveSpeed(0, _direction); + } else + stopWalking(); + break; + case Common::KEYCODE_RIGHT: + if (_direction != kDirRight) { + _direction = kDirRight; + setMoveSpeed(0, _direction); + } else + stopWalking(); + break; + case Common::KEYCODE_PAGEUP: + if (_direction != kDirUpRight) { + _direction = kDirUpRight; + setMoveSpeed(0, _direction); + } else + stopWalking(); + break; + case Common::KEYCODE_PAGEDOWN: + if (_direction != kDirDownRight) { + _direction = kDirDownRight; + setMoveSpeed(0, _direction); + } else + stopWalking(); + break; + case Common::KEYCODE_END: + if (_direction != kDirDownLeft) { + _direction = kDirDownLeft; + setMoveSpeed(0, _direction); + } else + stopWalking(); + break; + case Common::KEYCODE_HOME: + if (_direction != kDirUpLeft) { + _direction = kDirUpLeft; + setMoveSpeed(0, _direction); + } else + stopWalking(); + break; + case Common::KEYCODE_KP5: + stopWalking(); + break; + default: + break; + } + } +} + +void Animation::setDirection(Direction dir) { + _direction = dir; +} + +void Animation::setOldDirection(Direction dir) { + _oldDirection = dir; +} + +Direction Animation::getDirection() { + return _direction; +} + +Direction Animation::getOldDirection() { + return _oldDirection; +} + +void Animation::setAvvyClothes(int id) { + AnimationType *spr = _sprites[0]; + if (spr->_id == id) + return; + + int16 x = spr->_x; + int16 y = spr->_y; + spr->remove(); + spr->init(id, true); + spr->appear(x, y, kDirLeft); + spr->_visible = false; +} + +int Animation::getAvvyClothes() { + return _sprites[0]->_id; +} + +void Animation::resetVariables() { + _geidaSpin = 0; + _geidaTime = 0; + _arrowTriggered = false; +} + +void Animation::synchronize(Common::Serializer &sz) { + sz.syncAsByte(_direction); + sz.syncAsByte(_geidaSpin); + sz.syncAsByte(_geidaTime); + + byte spriteNum = 0; + if (sz.isSaving()) { + for (int i = 0; i < kSpriteNumbMax; i++) { + if (_sprites[i]->_quick) + spriteNum++; + } + } + sz.syncAsByte(spriteNum); + + if (sz.isLoading()) { + for (int i = 0; i < kSpriteNumbMax; i++) { // Deallocate sprites. + AnimationType *spr = _sprites[i]; + if (spr->_quick) + spr->remove(); + } + } + + for (int i = 0; i < spriteNum; i++) { + AnimationType *spr = _sprites[i]; + sz.syncAsByte(spr->_id); + sz.syncAsByte(spr->_doCheck); + + if (sz.isLoading()) { + spr->_quick = true; + spr->init(spr->_id, spr->_doCheck); + } + + sz.syncAsByte(spr->_moveX); + sz.syncAsByte(spr->_moveY); + sz.syncAsByte(spr->_facingDir); + sz.syncAsByte(spr->_stepNum); + sz.syncAsByte(spr->_visible); + sz.syncAsByte(spr->_homing); + sz.syncAsByte(spr->_count); + sz.syncAsByte(spr->_speedX); + sz.syncAsByte(spr->_speedY); + sz.syncAsByte(spr->_frameNum); + sz.syncAsSint16LE(spr->_homingX); + sz.syncAsSint16LE(spr->_homingY); + sz.syncAsByte(spr->_callEachStepFl); + sz.syncAsByte(spr->_eachStepProc); + sz.syncAsByte(spr->_vanishIfStill); + sz.syncAsSint16LE(spr->_x); + sz.syncAsSint16LE(spr->_y); + + if (sz.isLoading() && spr->_visible) + spr->appear(spr->_x, spr->_y, spr->_facingDir); + } + + sz.syncAsByte(_arrowTriggered); +} + +} // End of namespace Avalanche. diff --git a/engines/avalanche/animation.h b/engines/avalanche/animation.h new file mode 100644 index 0000000000..33f6ab02a6 --- /dev/null +++ b/engines/avalanche/animation.h @@ -0,0 +1,170 @@ +/* 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 the original source code of Lord Avalot d'Argent version 1.3. + * Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman. + */ + +/* Original name: TRIP5 / Trippancy V - the sprite animation subsystem */ + +#ifndef AVALANCHE_ANIMATION_H +#define AVALANCHE_ANIMATION_H + +namespace Avalanche { +class AvalancheEngine; +class Animation; + +enum Direction { + kDirUp = 0, kDirRight, kDirDown, kDirLeft, + kDirUpRight, kDirDownRight, kDirDownLeft, kDirUpLeft, + kDirStopped, kDirNone = 177 +}; + +class AnimationType { +public: + byte _id; + + byte _xLength, _yLength; + ManiType *_mani[24]; + SilType *_sil[24]; + byte _frameNum; // Number of pictures. + byte _seq; // How many in one stride. + byte _characterId; // The number according to Acci. (1=Avvy, etc.) + byte _count; // Counts before changing step. + + Direction _facingDir; + byte _stepNum; + int16 _x, _y; // Current xy coords. + int8 _moveX, _moveY; // Amount to move sprite by, each step. + bool _quick, _visible, _homing, _doCheck; + int16 _homingX, _homingY; // Homing x & y coords. + byte _speedX, _speedY; + bool _vanishIfStill; + bool _callEachStepFl; + byte _eachStepProc; + + AnimationType(Animation *anim); + + void init(byte spritenum, bool doCheck); + void reset(); + void draw(); + void turn(Direction whichway); + void appear(int16 wx, int16 wy, Direction wf); + void bounce(); + void walk(); + void walkTo(byte pednum); + void stopHoming(); + void setSpeed(int8 xx, int8 yy); + void stopWalk(); + void chatter(); + void remove(); + +private: + Animation *_anim; + + int16 _oldX[2], _oldY[2]; // Last xy coords. + Color _fgBubbleCol, _bgBubbleCol; // Foreground & background bubble colors. + + bool checkCollision(); + int8 getSign(int16 val); + void homeStep(); +}; + +class Animation { +public: + friend class AnimationType; + + static const byte kSpriteNumbMax = 5; // current max no. of sprites + + enum Proc { + kProcFollowAvvyY = 1, + kProcBackAndForth, + kProcFaceAvvy, + kProcArrow, + kProcSpludwick, // Unused + kProcGrabAvvy, + kProcGeida // Spludwick uses it as well for homing! TODO: Unify it with kProcSpludwick. + }; + + AnimationType *_sprites[kSpriteNumbMax]; + + Animation(AvalancheEngine *vm); + ~Animation(); + + void animLink(); + void resetAnims(); + void callSpecial(uint16 which); + void catacombMove(byte ped); + void stopWalking(); + void setMoveSpeed(byte t, Direction dir); + void appearPed(byte sprNum, byte pedNum); + bool inField(byte which); + bool nearDoor(); + void updateSpeed(); + void handleMoveKey(const Common::Event &event); + void hideInCupboard(); + + void setDirection(Direction dir); + void setOldDirection(Direction dir); + Direction getDirection(); + Direction getOldDirection(); + + void setAvvyClothes(int id); + int getAvvyClothes(); + + void resetVariables(); + void synchronize(Common::Serializer &sz); +private: + Direction _direction; // The direction Avvy is currently facing. + Direction _oldDirection; + static const int32 kCatacombMap[8][8]; + bool _arrowTriggered; // And has the arrow been triggered? + bool _mustExclaim; + byte _geidaSpin, _geidaTime; // For the making "Geida dizzy" joke. + uint16 _sayWhat; + + AvalancheEngine *_vm; + + byte checkFeet(int16 x1, int16 x2, int16 oy, int16 y, byte yl); + byte geidaPed(byte ped); + void dawnDelay(); + + void grabAvvy(byte tripnum); + void arrowProcs(byte tripnum); + + // Different movements for NPCs: + void followAvalotY(byte tripnum); + void backAndForth(byte tripnum); + void faceAvvy(byte tripnum); + + // Movements for Homing NPCs: Spludwick and Geida. + void spin(Direction dir, byte &tripnum); + void takeAStep(byte &tripnum); + void geidaProcs(byte tripnum); + + void drawSprites(); +}; + +} // End of namespace Avalanche. + +#endif // AVALANCHE_ANIMATION_H diff --git a/engines/avalanche/avalanche.cpp b/engines/avalanche/avalanche.cpp new file mode 100644 index 0000000000..4f3868768a --- /dev/null +++ b/engines/avalanche/avalanche.cpp @@ -0,0 +1,530 @@ +/* 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 the original source code of Lord Avalot d'Argent version 1.3. + * Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman. + */ + +#include "avalanche/avalanche.h" + +#include "common/random.h" +#include "common/savefile.h" +#include "graphics/thumbnail.h" + +namespace Avalanche { + +AvalancheEngine::AvalancheEngine(OSystem *syst, const AvalancheGameDescription *gd) : Engine(syst), _gameDescription(gd), _fxHidden(false), _interrogation(0) { + _system = syst; + _console = new AvalancheConsole(this); + + _rnd = new Common::RandomSource("avalanche"); + TimeDate time; + _system->getTimeAndDate(time); + _rnd->setSeed(time.tm_sec + time.tm_min + time.tm_hour); + + // Needed because of Lucerna::load_also() + for (int i = 0; i < 31; i++) { + for (int j = 0; j < 2; j++) + _also[i][j] = nullptr; + } + + _totalTime = 0; + _showDebugLines = false; + + memset(_fxPal, 0, 16 * 16 * 3); +} + +AvalancheEngine::~AvalancheEngine() { + delete _console; + delete _rnd; + + delete _graphics; + delete _parser; + + delete _clock; + delete _pingo; + delete _dialogs; + delete _background; + delete _sequence; + delete _timer; + delete _animation; + delete _menu; + delete _closing; + delete _sound; + + for (int i = 0; i < 31; i++) { + for (int j = 0; j < 2; j++) { + if (_also[i][j] != nullptr) { + delete _also[i][j]; + _also[i][j] = nullptr; + } + } + } +} + +Common::ErrorCode AvalancheEngine::initialize() { + _graphics = new GraphicManager(this); + _parser = new Parser(this); + + _clock = new Clock(this); + _pingo = new Pingo(this); + _dialogs = new Dialogs(this); + _background = new Background(this); + _sequence = new Sequence(this); + _timer = new Timer(this); + _animation = new Animation(this); + _menu = new Menu(this); + _closing = new Closing(this); + _sound = new SoundHandler(this); + + _graphics->init(); + _dialogs->init(); + init(); + _parser->init(); + + return Common::kNoError; +} + +GUI::Debugger *AvalancheEngine::getDebugger() { + return _console; +} + +Common::Platform AvalancheEngine::getPlatform() const { + return _platform; +} + +bool AvalancheEngine::hasFeature(EngineFeature f) const { + return (f == kSupportsSavingDuringRuntime) || (f == kSupportsLoadingDuringRuntime); +} + +const char *AvalancheEngine::getCopyrightString() const { + return "Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman."; +} + +void AvalancheEngine::synchronize(Common::Serializer &sz) { + _animation->synchronize(sz); + _parser->synchronize(sz); + _sequence->synchronize(sz); + _background->synchronize(sz); + + sz.syncAsByte(_carryNum); + for (int i = 0; i < kObjectNum; i++) + sz.syncAsByte(_objects[i]); + sz.syncAsSint16LE(_dnascore); + sz.syncAsSint32LE(_money); + sz.syncAsByte(_room); + if (sz.isSaving()) + _saveNum++; + sz.syncAsByte(_saveNum); + sz.syncBytes(_roomCount, 100); + sz.syncAsByte(_wonNim); + sz.syncAsByte(_wineState); + sz.syncAsByte(_cwytalotGone); + sz.syncAsByte(_passwordNum); + sz.syncAsByte(_aylesIsAwake); + sz.syncAsByte(_drawbridgeOpen); + sz.syncAsByte(_avariciusTalk); + sz.syncAsByte(_rottenOnion); + sz.syncAsByte(_onionInVinegar); + sz.syncAsByte(_givenToSpludwick); + sz.syncAsByte(_brummieStairs); + sz.syncAsByte(_cardiffQuestionNum); + sz.syncAsByte(_passedCwytalotInHerts); + sz.syncAsByte(_avvyIsAwake); + sz.syncAsByte(_avvyInBed); + sz.syncAsByte(_userMovesAvvy); + sz.syncAsByte(_npcFacing); + sz.syncAsByte(_givenBadgeToIby); + sz.syncAsByte(_friarWillTieYouUp); + sz.syncAsByte(_tiedUp); + sz.syncAsByte(_boxContent); + sz.syncAsByte(_talkedToCrapulus); + sz.syncAsByte(_jacquesState); + sz.syncAsByte(_bellsAreRinging); + sz.syncAsByte(_standingOnDais); + sz.syncAsByte(_takenPen); + sz.syncAsByte(_arrowInTheDoor); + + if (sz.isSaving()) { + uint16 like2drinkSize = _favouriteDrink.size(); + sz.syncAsUint16LE(like2drinkSize); + for (uint16 i = 0; i < like2drinkSize; i++) { + char actChr = _favouriteDrink[i]; + sz.syncAsByte(actChr); + } + + uint16 favourite_songSize = _favouriteSong.size(); + sz.syncAsUint16LE(favourite_songSize); + for (uint16 i = 0; i < favourite_songSize; i++) { + char actChr = _favouriteSong[i]; + sz.syncAsByte(actChr); + } + + uint16 worst_place_on_earthSize = _worstPlaceOnEarth.size(); + sz.syncAsUint16LE(worst_place_on_earthSize); + for (uint16 i = 0; i < worst_place_on_earthSize; i++) { + char actChr = _worstPlaceOnEarth[i]; + sz.syncAsByte(actChr); + } + + uint16 spare_eveningSize = _spareEvening.size(); + sz.syncAsUint16LE(spare_eveningSize); + for (uint16 i = 0; i < spare_eveningSize; i++) { + char actChr = _spareEvening[i]; + sz.syncAsByte(actChr); + } + } else { + if (!_favouriteDrink.empty()) + _favouriteDrink.clear(); + uint16 like2drinkSize = 0; + char actChr = ' '; + sz.syncAsUint16LE(like2drinkSize); + for (uint16 i = 0; i < like2drinkSize; i++) { + sz.syncAsByte(actChr); + _favouriteDrink += actChr; + } + + if (!_favouriteSong.empty()) + _favouriteSong.clear(); + uint16 favourite_songSize = 0; + sz.syncAsUint16LE(favourite_songSize); + for (uint16 i = 0; i < favourite_songSize; i++) { + sz.syncAsByte(actChr); + _favouriteSong += actChr; + } + + if (!_worstPlaceOnEarth.empty()) + _worstPlaceOnEarth.clear(); + uint16 worst_place_on_earthSize = 0; + sz.syncAsUint16LE(worst_place_on_earthSize); + for (uint16 i = 0; i < worst_place_on_earthSize; i++) { + sz.syncAsByte(actChr); + _worstPlaceOnEarth += actChr; + } + + if (!_spareEvening.empty()) + _spareEvening.clear(); + uint16 spare_eveningSize = 0; + sz.syncAsUint16LE(spare_eveningSize); + for (uint16 i = 0; i < spare_eveningSize; i++) { + sz.syncAsByte(actChr); + _spareEvening += actChr; + } + } + + sz.syncAsSint32LE(_totalTime); + sz.syncAsByte(_jumpStatus); + sz.syncAsByte(_mushroomGrowing); + sz.syncAsByte(_spludwickAtHome); + sz.syncAsByte(_lastRoom); + sz.syncAsByte(_lastRoomNotMap); + sz.syncAsByte(_crapulusWillTell); + sz.syncAsByte(_enterCatacombsFromLustiesRoom); + sz.syncAsByte(_teetotal); + sz.syncAsByte(_malagauche); + sz.syncAsByte(_drinking); + sz.syncAsByte(_enteredLustiesRoomAsMonk); + sz.syncAsByte(_catacombX); + sz.syncAsByte(_catacombY); + sz.syncAsByte(_avvysInTheCupboard); + sz.syncAsByte(_geidaFollows); + sz.syncAsByte(_givenPotionToGeida); + sz.syncAsByte(_lustieIsAsleep); + sz.syncAsByte(_beenTiedUp); + sz.syncAsByte(_sittingInPub); + sz.syncAsByte(_spurgeTalkCount); + sz.syncAsByte(_metAvaroid); + sz.syncAsByte(_takenMushroom); + sz.syncAsByte(_givenPenToAyles); + sz.syncAsByte(_askedDogfoodAboutNim); + + for (int i = 0; i < 7; i++) { + sz.syncAsSint32LE(_timer->_times[i]._timeLeft); + sz.syncAsByte(_timer->_times[i]._action); + sz.syncAsByte(_timer->_times[i]._reason); + } + +} + +bool AvalancheEngine::canSaveGameStateCurrently() { // TODO: Refine these!!! + return (!_seeScroll && _alive); +} + +Common::Error AvalancheEngine::saveGameState(int slot, const Common::String &desc) { + return (saveGame(slot, desc) ? Common::kNoError : Common::kWritingFailed); +} + +bool AvalancheEngine::saveGame(const int16 slot, const Common::String &desc) { + Common::String fileName = getSaveFileName(slot); + Common::OutSaveFile *f = g_system->getSavefileManager()->openForSaving(fileName); + if (!f) { + warning("Can't create file '%s', game not saved.", fileName.c_str()); + return false; + } + + f->writeUint32LE(MKTAG('A', 'V', 'A', 'L')); + + // Write version. We can't restore from obsolete versions. + f->writeByte(kSavegameVersion); + + f->writeUint32LE(desc.size()); + f->write(desc.c_str(), desc.size()); + Graphics::saveThumbnail(*f); + + TimeDate t; + _system->getTimeAndDate(t); + f->writeSint16LE(t.tm_mday); + f->writeSint16LE(t.tm_mon); + f->writeSint16LE(t.tm_year); + + Common::Serializer sz(NULL, f); + synchronize(sz); + f->finalize(); + delete f; + + return true; +} + +Common::String AvalancheEngine::getSaveFileName(const int slot) { + return Common::String::format("%s.%03d", _targetName.c_str(), slot); +} + +bool AvalancheEngine::canLoadGameStateCurrently() { // TODO: Refine these!!! + return (!_seeScroll); +} + +Common::Error AvalancheEngine::loadGameState(int slot) { + return (loadGame(slot) ? Common::kNoError : Common::kReadingFailed); +} + +bool AvalancheEngine::loadGame(const int16 slot) { + Common::String fileName = getSaveFileName(slot); + Common::InSaveFile *f = g_system->getSavefileManager()->openForLoading(fileName); + if (!f) + return false; + + uint32 signature = f->readUint32LE(); + if (signature != MKTAG('A', 'V', 'A', 'L')) + return false; + + // Check version. We can't restore from obsolete versions. + byte saveVersion = f->readByte(); + if (saveVersion != kSavegameVersion) { + warning("Savegame of incompatible version!"); + delete f; + return false; + } + + // Read the description. + uint32 descSize = f->readUint32LE(); + Common::String description; + for (uint32 i = 0; i < descSize; i++) { + char actChar = f->readByte(); + description += actChar; + } + + description.toUppercase(); + Graphics::skipThumbnail(*f); + + // Read the time the game was saved. + TimeDate t; + t.tm_mday = f->readSint16LE(); + t.tm_mon = f->readSint16LE(); + t.tm_year = f->readSint16LE(); + + resetVariables(); + + Common::Serializer sz(f, NULL); + synchronize(sz); + delete f; + + _isLoaded = true; + _seeScroll = true; // This prevents display of the new sprites before the new picture is loaded. + + if (_holdTheDawn) { + _holdTheDawn = false; + fadeIn(); + } + + _background->release(); + minorRedraw(); + _menu->setup(); + setRoom(kPeopleAvalot, _room); + _alive = true; + refreshObjectList(); + _animation->updateSpeed(); + drawDirection(); + _animation->animLink(); + _background->update(); + + Common::String tmpStr = Common::String::format("%cLoaded: %c%s.ASG%c%c%c%s%c%csaved on %s.", + kControlItalic, kControlRoman, description.c_str(), kControlCenter, kControlNewLine, + kControlNewLine, _roomnName.c_str(), kControlNewLine, kControlNewLine, + expandDate(t.tm_mday, t.tm_mon, t.tm_year).c_str()); + _dialogs->displayText(tmpStr); + + AnimationType *avvy = _animation->_sprites[0]; + if (avvy->_quick && avvy->_visible) + _animation->setMoveSpeed(0, _animation->getDirection()); // We push Avvy in the right direction is he was moving. + + return true; +} + +Common::String AvalancheEngine::expandDate(int d, int m, int y) { + static const char months[12][10] = { + "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" + }; + + Common::String month = Common::String(months[m]); + Common::String day = intToStr(d); + + if (((1 <= d) && (d <= 9)) || ((21 <= d) && (d <= 31))) + switch (d % 10) { + case 1: + day += "st"; + break; + case 2: + day += "nd"; + break; + case 3: + day += "rd"; + break; + default: + day += "th"; + } + + return day + ' ' + month + ' ' + intToStr(y + 1900); +} + +void AvalancheEngine::updateEvents() { + Common::Event event; + + while (_eventMan->pollEvent(event)) { + switch (event.type) { + case Common::EVENT_LBUTTONDOWN: + _holdLeftMouse = true; // Used in checkclick() and Menu::menu_link(). + break; + case Common::EVENT_LBUTTONUP: + _holdLeftMouse = false; // Same as above. + break; + case Common::EVENT_KEYDOWN: + if ((event.kbd.keycode == Common::KEYCODE_d) && (event.kbd.flags & Common::KBD_CTRL)) { + // Attach to the debugger + _console->attach(); + _console->onFrame(); + } else + handleKeyDown(event); + break; + default: + break; + } + } +} + +bool AvalancheEngine::getEvent(Common::Event &event) { + return _eventMan->pollEvent(event); +} + +Common::Point AvalancheEngine::getMousePos() { + return _eventMan->getMousePos(); +} + +Common::Error AvalancheEngine::run() { + Common::ErrorCode err = initialize(); + if (err != Common::kNoError) + return err; + + do { + runAvalot(); + +#if 0 + switch (_storage._operation) { + case kRunShootemup: + run("seu.avx", kJsb, kBflight, kNormal); + break; + case kRunDosshell: + dosShell(); + break; + case kRunGhostroom: + run("g-room.avx", kJsb, kNoBflight, kNormal); + break; + case kRunGolden: + run("golden.avx", kJsb, kBflight, kMusical); + break; + } +#endif + + } while (!_letMeOut && !shouldQuit()); + + return Common::kNoError; +} + +#if 0 +void AvalancheEngine::run(Common::String what, bool withJsb, bool withBflight, Elm how) { + // Probably there'll be no need of this function, as all *.AVX-es will become classes. + warning("STUB: run(%s)", what.c_str()); +} + +Common::String AvalancheEngine::elmToStr(Elm how) { + switch (how) { + case kNormal: + case kMusical: + return Common::String("jsb"); + case kRegi: + return Common::String("REGI"); + case kElmpoyten: + return Common::String("ELMPOYTEN"); + // Useless, but silent a warning + default: + return Common::String(""); + } +} + +// Same as keypressed1(). +void AvalancheEngine::flushBuffer() { + warning("STUB: flushBuffer()"); +} + +void AvalancheEngine::dosShell() { + warning("STUB: dosShell()"); +} + +// Needed in dos_shell(). TODO: Remove later. +Common::String AvalancheEngine::commandCom() { + warning("STUB: commandCom()"); + return ("STUB: commandCom()"); +} + +// Needed for run_avalot()'s errors. TODO: Remove later. +void AvalancheEngine::explain(byte error) { + warning("STUB: explain()"); +} + +// Needed later. +void AvalancheEngine::quit() { + cursorOn(); +} + +#endif + +} // End of namespace Avalanche diff --git a/engines/avalanche/avalanche.h b/engines/avalanche/avalanche.h new file mode 100644 index 0000000000..cc9a34d82b --- /dev/null +++ b/engines/avalanche/avalanche.h @@ -0,0 +1,340 @@ +/* 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 the original source code of Lord Avalot d'Argent version 1.3. + * Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman. + */ + +#ifndef AVALANCHE_AVALANCHE_H +#define AVALANCHE_AVALANCHE_H + +#include "avalanche/console.h" +#include "avalanche/graphics.h" +#include "avalanche/parser.h" +#include "avalanche/avalot.h" +#include "avalanche/pingo.h" +#include "avalanche/dialogs.h" +#include "avalanche/background.h" +#include "avalanche/sequence.h" +#include "avalanche/timer.h" +#include "avalanche/animation.h" +#include "avalanche/menu.h" +#include "avalanche/closing.h" +#include "avalanche/sound.h" + +#include "common/serializer.h" + +#include "engines/engine.h" +#include "engines/advancedDetector.h" + +#include "graphics/cursorman.h" + +namespace Common { +class RandomSource; +} + +namespace Avalanche { + +struct AvalancheGameDescription; + +static const int kSavegameVersion = 1; + +enum Pitch { + kPitchInvalid, + kPitchLower, + kPitchSame, + kPitchHigher +}; + +class AvalancheEngine : public Engine { +public: + byte _saveNum; // number of times this game has been saved + + Clock *_clock; + GraphicManager *_graphics; + Parser *_parser; + Pingo *_pingo; + Dialogs *_dialogs; + Background *_background; + Sequence *_sequence; + Timer *_timer; + Animation *_animation; + Menu *_menu; + Closing *_closing; + SoundHandler *_sound; + + OSystem *_system; + + AvalancheEngine(OSystem *syst, const AvalancheGameDescription *gd); + ~AvalancheEngine(); + + Common::ErrorCode initialize(); + GUI::Debugger *getDebugger(); + + Common::RandomSource *_rnd; + + const AvalancheGameDescription *_gameDescription; + uint32 getFeatures() const; + const char *getGameId() const; + Common::Platform getPlatform() const; + bool hasFeature(EngineFeature f) const; + const char *getCopyrightString() const; + + void synchronize(Common::Serializer &sz); + virtual bool canSaveGameStateCurrently(); + Common::Error saveGameState(int slot, const Common::String &desc); + bool saveGame(const int16 slot, const Common::String &desc); + Common::String getSaveFileName(const int slot); + virtual bool canLoadGameStateCurrently(); + Common::Error loadGameState(int slot); + bool loadGame(const int16 slot); + Common::String expandDate(int d, int m, int y); + + void updateEvents(); + bool getEvent(Common::Event &event); // A wrapper around _eventMan->pollEvent(), so we can use it in Scrolls::normscroll() for example. + Common::Point getMousePos(); + +protected: + // Engine APIs + Common::Error run(); + +private: + AvalancheConsole *_console; + Common::Platform _platform; + +#if 0 + struct { + byte _operation; + uint16 _skellern; + byte _contents[1000]; + } _storage; + + static const int16 kRunShootemup = 1, kRunDosshell = 2, kRunGhostroom = 3, kRunGolden = 4; + static const int16 kReset = 0; + + static const bool kJsb = true, kNoJsb = false, kBflight = true, kNoBflight = false; + + // From bootstrp: + enum Elm {kNormal, kMusical, kElmpoyten, kRegi}; + + Common::String _argsWithNoFilename; + byte _originalMode; + byte *_old1c; + Common::String _segofs; + int32 _soundcard, _speed, _baseaddr, _irq, _dma; + bool _zoomy; + + void run(Common::String what, bool withJsb, bool withBflight, Elm how); + void bFlightOn(); + void bFlightOff(); + Common::String elmToStr(Elm how); + bool keyPressed(); + void flushBuffer(); + void dosShell(); + void bFlight(); + Common::String commandCom(); + void explain(byte error); + void cursorOff(); + void cursorOn(); + void quit(); +#endif + +public: + // For Thinkabout: + static const bool kThing = true; + static const bool kPerson = false; + + static const char kSpludwicksOrder[3]; + + static const uint16 kNotes[12]; + static const TuneType kTune; + + bool _holdLeftMouse; + + // If this is greater than zero, the next line you type is stored in the DNA in a position dictated by the value. + // If a scroll comes up, or you leave the room, it's automatically set to zero. + byte _interrogation; + + // Former DNA structure + byte _carryNum; // How many objects you're carrying... + bool _objects[kObjectNum]; // ...and which ones they are. + int16 _dnascore; // your score, of course + int32 _money; // your current amount of dosh + Room _room; // your current room + bool _wonNim; // Have you *won* Nim? (That's harder.) + byte _wineState; // 0=good (Notts), 1=passable(Argent) ... 3=vinegar. + bool _cwytalotGone; // Has Cwytalot rushed off to Jerusalem yet? + byte _passwordNum; // Number of the passw for this game. + bool _aylesIsAwake; // pretty obvious! + byte _drawbridgeOpen; // Between 0 (shut) and 4 (open). + byte _avariciusTalk; // How much Avaricius has said to you. + bool _rottenOnion; // And has it rotted? + bool _onionInVinegar; // Is the onion in the vinegar? + byte _givenToSpludwick; // 0 = nothing given, 1 = onion... + byte _brummieStairs; // Progression through the stairs trick. + byte _cardiffQuestionNum; // Things you get asked in Cardiff. + bool _avvyIsAwake; // Well? Is Avvy awake? (Screen 1 only.) + bool _avvyInBed; // True if Avvy's in bed, but awake. + bool _userMovesAvvy; // If this is false, the user has no control over Avvy's movements. + byte _npcFacing; // If there's an NPC in the current room which turns it's head according to Avvy's movement (keep looking at him), this variable tells which way it's facing at the moment. + bool _givenBadgeToIby; // Have you given the badge to Iby yet? + bool _friarWillTieYouUp; // If you're going to get tied up. + bool _tiedUp; // You ARE tied up! + byte _boxContent; // 0 = money (sixpence), 254 = empty, any other number implies the contents of the box. + bool _talkedToCrapulus; // Pretty self-explanatory. + byte _jacquesState; // 0=asleep, 1=awake, 2=gets up, 3=gone. + bool _bellsAreRinging; // Is Jacques ringing the bells? + bool _standingOnDais; // In room 71, inside Cardiff Castle. + bool _takenPen; // Have you taken the pen (in Cardiff?) + bool _arrowInTheDoor; // Did the arrow hit the wall? + Common::String _favouriteDrink, _favouriteSong, _worstPlaceOnEarth, _spareEvening; // Personalisation str's + uint32 _totalTime; // Your total time playing this game, in ticks. + byte _jumpStatus; // Fixes how high you're jumping. + bool _mushroomGrowing; // Is the mushroom growing in 42? + bool _crapulusWillTell; // Will Crapulus tell you about Spludwick being away? + bool _enterCatacombsFromLustiesRoom; + bool _teetotal; // Are we touching any more drinks? + byte _malagauche; // Position of Malagauche. See Celer for more info. + char _drinking; // What's he getting you? + bool _enteredLustiesRoomAsMonk; + byte _catacombX, _catacombY; // XY coords in the catacombs. + bool _avvysInTheCupboard; // On screen 22. + bool _geidaFollows; // Is Geida following you? + bool _givenPotionToGeida; // Does Geida have the potion? + bool _lustieIsAsleep; // Is BDL asleep? + bool _beenTiedUp; // In r__Robins. + bool _sittingInPub; // Are you sitting down in the pub? + byte _spurgeTalkCount; // Count for talking to Spurge. + bool _metAvaroid; + bool _takenMushroom, _givenPenToAyles, _askedDogfoodAboutNim; + // End of former DNA Structure + + bool _showDebugLines; + byte _lineNum; // Number of lines. + LineType _lines[50]; // For Also. + bool _dropsOk; + bool _cheat; // CHECKME: Currently unused + bool _letMeOut; + byte _thinks; + bool _thinkThing; + bool _seeScroll; // TODO: maybe this means we're interacting with the toolbar / a scroll? + char _objectList[10]; + // Called .free() for them in ~Gyro(). + + byte _currentMouse; // current mouse-void + Common::String *_also[31][2]; + PedType _peds[15]; + MagicType _magics[15]; + MagicType _portals[7]; + FieldType _fields[30]; + byte _fieldNum; + Common::String _listen; + byte _cp, _ledStatus; + FontType _font; + bool _alive; + byte _subjectNum; // The same thing. + People _him, _her; + byte _it; + uint32 _roomTime; // Set to 0 when you enter a room, added to in every loop. + + bool _doingSpriteRun; // Only set to True if we're doing a sprite_run at this moment. This stops the trippancy system from moving any of the sprites. + bool _isLoaded; // Is it a loaded gamestate? + bool _soundFx; + + void callVerb(VerbCode id); + void loadRoom(byte num); + void thinkAbout(byte object, bool type); // Hey!!! Get it and put it!!! + void incScore(byte num); // Add on no. of points + void fxToggle(); + void refreshObjectList(); + void errorLed(); + void fadeOut(); + void fadeIn(); + void drawDirection(); // Draws the little icon at the left end of the text input field. + void gameOver(); + uint16 bearing(byte whichPed); // Returns the bearing from ped 'whichped' to Avvy, in degrees. + + // There are two kinds of redraw: Major and Minor. Minor is what happens when you load a game, etc. Major redraws EVERYTHING. + void minorRedraw(); + void majorRedraw(); + + void spriteRun(); + + Common::String intToStr(int32 num); + void newGame(); // This sets up the DNA for a completely new game. + bool getFlag(char x); + bool decreaseMoney(uint16 amount); // Called pennycheck in the original. + + Common::String getName(People whose); + Common::String getItem(byte which); // Called get_better in the original. + Common::String f5Does(); // This procedure determines what f5 does. + + void openDoor(Room whither, byte ped, byte magicnum); // Handles slidey-open doors. + void flipRoom(Room room, byte ped); + + void setRoom(People persId, Room roomId); + Room getRoom(People persId); +private: + static const int16 kMaxSprites = 2; // Current max no. of sprites. + static Room _whereIs[29]; + + // Will be used in dusk() and dawn(). + bool _fxHidden; + byte _fxPal[16][16][3]; + + bool _spludwickAtHome; // Is Spludwick at home? + bool _passedCwytalotInHerts; // Have you passed Cwytalot in Herts? + bool _holdTheDawn; // If this is true, calling Dawn will do nothing. It's used, for example, at the start, to stop Load from dawning. + byte _lastRoom; + byte _lastRoomNotMap; + byte _roomCount[100]; // Add one to each every time you enter a room + Common::String _mouseText; + Common::String _flags; + Common::String _roomnName; // Name of actual room + int8 _scoreToDisplay[3]; + + Common::String readAlsoStringFromFile(Common::File &file); + void runAvalot(); + void init(); + void setup(); + void scram(Common::String &str); + void unScramble(); + void handleKeyDown(Common::Event &event); // To replace Basher::keyboard_link() and Basher::typein(). + void enterNewTown(); + void findPeople(byte room); + void putGeidaAt(byte whichPed, byte ped); + void guideAvvy(Common::Point cursorPos); + void enterRoom(Room room, byte ped); + void exitRoom(byte x); + void drawToolbar(); + void drawScore(); + void useCompass(const Common::Point &cursorPos); // Click on the compass on the toolbar to control Avvy's movement. + void checkClick(); + void fixFlashers(); + void loadAlso(byte num); + void resetVariables(); +}; + +} // End of namespace Avalanche + +#endif // AVALANCHE_AVALANCHE_H diff --git a/engines/avalanche/avalot.cpp b/engines/avalanche/avalot.cpp new file mode 100644 index 0000000000..8ef41a2c93 --- /dev/null +++ b/engines/avalanche/avalot.cpp @@ -0,0 +1,1744 @@ +/* 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 the original source code of Lord Avalot d'Argent version 1.3. + * Copyright (c) 1994-1995 Mike: Mark and Thomas Thurman. + */ + +/* AVALOT The kernel of the program. */ + +#include "avalanche/avalanche.h" + +#include "common/random.h" +#include "common/system.h" +#include "common/config-manager.h" +#include "graphics/palette.h" + +namespace Avalanche { + +// vv Stairs trap. + +/* Explanation: $NSEW. + Nibble N: North. + 0 = no connection, + 2 = (left,) middle(, right) door with left-hand handle, + 5 = (left,) middle(, right) door with right-hand handle, + 7 = arch, + 8 = arch and 1 north of it, + 9 = arch and 2 north of it, + D = no connection + WINDOW, + E = no connection + TORCH, + F = recessed door (to Geida's room.) + + Nibble S: South. + 0 = no connection, + 1,2,3 = left, middle, right door. + + Nibble E: East. + 0 = no connection (wall), + 1 = no connection (wall + window), + 2 = wall with door, + 3 = wall with door and window, + 6 = wall with candles, + 7 = wall with door and candles, + F = straight-through corridor. + + Nibble W: West. + 0 = no connection (wall), + 1 = no connection (wall + shield), + 2 = wall with door, + 3 = wall with door and shield, + 4 = no connection (window), + 5 = wall with door and window, + 6 = wall with candles, + 7 = wall with door and candles, + F = straight-through corridor. */ + +const char AvalancheEngine::kSpludwicksOrder[3] = {kObjectOnion, kObjectInk, kObjectMushroom}; +const uint16 AvalancheEngine::kNotes[12] = {196, 220, 247, 262, 294, 330, 350, 392, 440, 494, 523, 587}; +const TuneType AvalancheEngine::kTune = { + kPitchHigher, kPitchHigher, kPitchLower, kPitchSame, kPitchHigher, kPitchHigher, kPitchLower, kPitchHigher, kPitchHigher, kPitchHigher, + kPitchLower, kPitchHigher, kPitchHigher, kPitchSame, kPitchHigher, kPitchLower, kPitchLower, kPitchLower, kPitchLower, kPitchHigher, + kPitchHigher, kPitchLower, kPitchLower, kPitchLower, kPitchLower, kPitchSame, kPitchLower, kPitchHigher, kPitchSame, kPitchLower, kPitchHigher +}; + +Room AvalancheEngine::_whereIs[29] = { + // The Lads + kRoomYours, // Avvy + kRoomSpludwicks, // Spludwick + kRoomOutsideYours, // Crapulus + kRoomDucks, // Duck - r__DucksRoom's not defined yet. + kRoomArgentPub, // Malagauche + kRoomRobins, // Friar Tuck. + kRoomDummy, // Robin Hood - can't meet him at the start. + kRoomBrummieRoad, // Cwytalot + kRoomLustiesRoom, // Baron du Lustie. + kRoomOutsideCardiffCastle, // The Duke of Cardiff. + kRoomArgentPub, // Dogfood + kRoomOutsideDucks, // Trader + kRoomArgentPub, // Ibythneth + kRoomAylesOffice, // Ayles + kRoomNottsPub, // Port + kRoomNottsPub, // Spurge + kRoomMusicRoom, // Jacques + kRoomNowhere, + kRoomNowhere, + kRoomNowhere, + kRoomNowhere, + kRoomNowhere, + kRoomNowhere, + kRoomNowhere, + kRoomNowhere, + // The Lasses + kRoomYours, // Arkata + kRoomGeidas, // Geida + kRoomDummy, // nobody allocated here! + kRoomWiseWomans // The Wise Woman. +}; + +Clock::Clock(AvalancheEngine *vm) { + _vm = vm; + _oldHour = _oldHourAngle = _oldMinute = 17717; +} + +void Clock::update() { // TODO: Move variables from Gyro to here (or at least somewhere nearby), rename them. + TimeDate t; + _vm->_system->getTimeAndDate(t); + _hour = t.tm_hour; + _minute = t.tm_min; + _second = t.tm_sec; + + _hourAngle = (_hour % 12) * 30 + _minute / 2; + + if (_oldHour != _hour) { + plotHands(); + chime(); + } + + if (_oldMinute != _minute) + plotHands(); + + if ((_hour == 0) && (_oldHour != 0) && (_oldHour != 17717)) { + Common::String tmpStr = Common::String::format("Good morning!%c%cYes, it's just past " \ + "midnight. Are you having an all-night Avvy session? Glad you like the game that much!", + kControlNewLine, kControlNewLine); + _vm->_dialogs->displayText(tmpStr); + } + _oldHour = _hour; + _oldHourAngle = _hourAngle; + _oldMinute = _minute; +} + +Common::Point Clock::calcHand(uint16 angle, uint16 length, Color color) { + if (angle > 900) { + return(Common::Point(177, 177)); + } + + return(_vm->_graphics->drawScreenArc(kCenterX, kCenterY, 449 - angle, 450 - angle, length, color)); +} + +void Clock::drawHand(const Common::Point &endPoint, Color color) { + if (endPoint.x == 177) + return; + + _vm->_graphics->drawScreenLine(kCenterX, kCenterY, endPoint.x, endPoint.y, color); +} + +void Clock::plotHands() { + _clockHandHour = calcHand(_oldHourAngle, 14, kColorYellow); + _clockHandMinute = calcHand(_oldMinute * 6, 17, kColorYellow); + drawHand(_clockHandHour, kColorBrown); + drawHand(_clockHandMinute, kColorBrown); + + _clockHandHour = calcHand(_hourAngle, 14, kColorBrown); + _clockHandMinute = calcHand(_minute * 6, 17, kColorBrown); + drawHand(_clockHandHour, kColorYellow); + drawHand(_clockHandMinute, kColorYellow); +} + +void Clock::chime() { + if ((_oldHour == 17717) || (!_vm->_soundFx)) // Too high - must be first time around + return; + + byte hour = _hour % 12; + if (hour == 0) + hour = 12; + + _vm->_graphics->loadMouse(kCurWait); + + for (int i = 1; i <= hour; i++) { + for (int j = 1; j <= 3; j++) + _vm->_sound->playNote((i % 3) * 64 + 140 - j * 30, 50 - j * 12); + if (i != hour) + _vm->_system->delayMillis(100); + } +} + + +void AvalancheEngine::handleKeyDown(Common::Event &event) { + _sound->click(); + + if ((Common::KEYCODE_F1 <= event.kbd.keycode) && (event.kbd.keycode <= Common::KEYCODE_F15)) + _parser->handleFunctionKey(event); + else if ((32 <= event.kbd.ascii) && (event.kbd.ascii <= 128) && (event.kbd.ascii != 47)) + _parser->handleInputText(event); + else + switch (event.kbd.keycode) { // We can control Avvy with the numpad as well. + case Common::KEYCODE_KP8: + event.kbd.keycode = Common::KEYCODE_UP; + break; + case Common::KEYCODE_KP2: + event.kbd.keycode = Common::KEYCODE_DOWN; + break; + case Common::KEYCODE_KP6: + event.kbd.keycode = Common::KEYCODE_RIGHT; + break; + case Common::KEYCODE_KP4: + event.kbd.keycode = Common::KEYCODE_LEFT; + break; + case Common::KEYCODE_KP9: + event.kbd.keycode = Common::KEYCODE_PAGEUP; + break; + case Common::KEYCODE_KP3: + event.kbd.keycode = Common::KEYCODE_PAGEDOWN; + break; + case Common::KEYCODE_KP7: + event.kbd.keycode = Common::KEYCODE_HOME; + break; + case Common::KEYCODE_KP1: + event.kbd.keycode = Common::KEYCODE_END; + break; + default: + break; + } + + switch (event.kbd.keycode) { + case Common::KEYCODE_UP: + case Common::KEYCODE_DOWN: + case Common::KEYCODE_RIGHT: + case Common::KEYCODE_LEFT: + case Common::KEYCODE_PAGEUP: + case Common::KEYCODE_PAGEDOWN: + case Common::KEYCODE_HOME: + case Common::KEYCODE_END: + case Common::KEYCODE_KP5: + if (_alive && _avvyIsAwake) { + _animation->handleMoveKey(event); // Fallthroughs are intended. + drawDirection(); + return; + } + case Common::KEYCODE_BACKSPACE: + _parser->handleBackspace(); + break; + case Common::KEYCODE_RETURN: + _parser->handleReturn(); + break; + default: + break; + } + + drawDirection(); +} + +void AvalancheEngine::setup() { + init(); + + _dialogs->reset(); + fadeOut(); + _graphics->loadDigits(); + + _parser->_inputTextPos = 0; + _parser->_quote = true; + + _animation->resetAnims(); + + drawToolbar(); + _dialogs->setReadyLight(2); + + fadeIn(); + _parser->_cursorState = false; + _parser->cursorOn(); + _animation->_sprites[0]->_speedX = kWalk; + _animation->updateSpeed(); + + _menu->init(); + + int16 loadSlot = ConfMan.instance().getInt("save_slot"); + if (loadSlot >= 0) { + _thinks = 2; // You always have money. + thinkAbout(kObjectMoney, kThing); + + loadGame(loadSlot); + } else { + _isLoaded = false; // Set to true in _vm->loadGame(). + newGame(); + + _soundFx = !_soundFx; + fxToggle(); + thinkAbout(kObjectMoney, kThing); + + _dialogs->displayScrollChain('q', 83); // Info on the game, etc. + } +} + +void AvalancheEngine::runAvalot() { + setup(); + + do { + uint32 beginLoop = _system->getMillis(); + + updateEvents(); // The event handler. + + _clock->update(); + _menu->update(); + _background->update(); + _animation->animLink(); + checkClick(); + _timer->updateTimer(); + + _graphics->drawDebugLines(); + _graphics->refreshScreen(); + + uint32 delay = _system->getMillis() - beginLoop; + if (delay <= 55) + _system->delayMillis(55 - delay); // Replaces slowdown(); 55 comes from 18.2 Hz (B Flight). + } while (!_letMeOut && !shouldQuit()); + + warning("STUB: run()"); + + _closing->exitGame(); +} + +void AvalancheEngine::init() { + for (int i = 0; i < 31; i++) { + for (int j = 0; j < 2; j++) + _also[i][j] = nullptr; + } + +#if 0 + if (_vm->_enhanced->atbios) + atkey = "f1"; + else + atkey = "alt-"; +#endif + + _letMeOut = false; + _currentMouse = 177; + _dropsOk = true; + _mouseText = ""; + _cheat = false; + _cp = 0; + _ledStatus = 177; + for (int i = 0; i < 3; i++) + _scoreToDisplay[i] = -1; // Impossible digits. + _holdTheDawn = false; + + _graphics->loadMouse(kCurWait); + CursorMan.showMouse(true); +} + +/** + * Call a given Verb + * @remarks Originally called 'callverb' + */ +void AvalancheEngine::callVerb(VerbCode id) { + if (id == _parser->kPardon) { + Common::String tmpStr = Common::String::format("The f5 key lets you do a particular action in certain " \ + "situations. However, at the moment there is nothing assigned to it. You may press alt-A to see " \ + "what the current setting of this key is."); + _dialogs->displayText(tmpStr); + } else + _parser->doVerb(id); +} + +/** + * Check is it's possible to give something to Spludwick + * @remarks Originally called 'nextstring' + */ +Common::String AvalancheEngine::readAlsoStringFromFile(Common::File &file) { + Common::String str; + byte length = file.readByte(); + for (int i = 0; i < length; i++) + str += file.readByte(); + return str; +} + +void AvalancheEngine::scram(Common::String &str) { + for (uint i = 0; i < str.size(); i++) + str.setChar(str[i] ^ 177, i); +} + +void AvalancheEngine::unScramble() { + for (int i = 0; i < 31; i++) { + for (int j = 0; j < 2; j++) { + if (_also[i][j] != nullptr) + scram(*_also[i][j]); + } + } + scram(_listen); + scram(_flags); +} + +void AvalancheEngine::loadAlso(byte num) { + for (int i = 0; i < 31; i++) { + for (int j = 0; j < 2; j++) { + if (_also[i][j] != nullptr) { + delete _also[i][j]; + _also[i][j] = nullptr; + } + } + } + Common::String filename; + filename = Common::String::format("also%d.avd", num); + Common::File file; + if (!file.open(filename)) + error("AVALANCHE: File not found: %s", filename.c_str()); + + file.seek(128); + + byte alsoNum = file.readByte(); + Common::String tmpStr; + for (int i = 0; i <= alsoNum; i++) { + for (int j = 0; j < 2; j++) { + _also[i][j] = new Common::String; + *_also[i][j] = readAlsoStringFromFile(file); + } + tmpStr = Common::String::format("\x9D%s\x9D", _also[i][0]->c_str()); + *_also[i][0] = tmpStr; + } + + memset(_lines, 0xFF, sizeof(_lines)); + + _lineNum = file.readByte(); + for (int i = 0; i < _lineNum; i++) { + LineType *curLine = &_lines[i]; + curLine->_x1 = file.readSint16LE(); + curLine->_y1 = file.readSint16LE(); + curLine->_x2 = file.readSint16LE(); + curLine->_y2 = file.readSint16LE(); + curLine->_color = (Color)file.readByte(); + } + + memset(_peds, 177, sizeof(_peds)); + byte pedNum = file.readByte(); + for (int i = 0; i < pedNum; i++) { + PedType *curPed = &_peds[i]; + curPed->_x = file.readSint16LE(); + curPed->_y = file.readSint16LE(); + curPed->_direction = (Direction)file.readByte(); + } + + _fieldNum = file.readByte(); + for (int i = 0; i < _fieldNum; i++) { + FieldType *curField = &_fields[i]; + curField->_x1 = file.readSint16LE(); + curField->_y1 = file.readSint16LE(); + curField->_x2 = file.readSint16LE(); + curField->_y2 = file.readSint16LE(); + } + + for (int i = 0; i < 15; i++) { + MagicType *magic = &_magics[i]; + magic->_operation = file.readByte(); + magic->_data = file.readUint16LE(); + } + + for (int i = 0; i < 7; i++) { + MagicType *portal = &_portals[i]; + portal->_operation = file.readByte(); + portal->_data = file.readUint16LE(); + } + + _flags.clear(); + for (int i = 0; i < 26; i++) + _flags += file.readByte(); + + int16 size = file.readByte(); + _listen.clear(); + for (int i = 0; i < size; i++) + _listen += file.readByte(); + + _graphics->clearAlso(); + + CursorMan.showMouse(false); + for (int i = 0; i < _lineNum; i++) { + // We had to check if the lines are within the borders of the screen. + if ((_lines[i]._x1 >= 0) && (_lines[i]._x1 < kScreenWidth) && (_lines[i]._y1 >= 0) && (_lines[i]._y1 < kScreenHeight) + && (_lines[i]._x2 >= 0) && (_lines[i]._x2 < kScreenWidth) && (_lines[i]._y2 >= 0) && (_lines[i]._y2 < kScreenHeight)) + _graphics->setAlsoLine(_lines[i]._x1, _lines[i]._y1, _lines[i]._x2, _lines[i]._y2, _lines[i]._color); + } + CursorMan.showMouse(true); + + file.close(); + + unScramble(); + for (int i = 0; i <= alsoNum; i++) { + tmpStr = Common::String::format(",%s,", _also[i][0]->c_str()); + *_also[i][0] = tmpStr; + } +} + +void AvalancheEngine::loadRoom(byte num) { + CursorMan.showMouse(false); + + Common::String filename = Common::String::format("place%d.avd", num); + Common::File file; + if (!file.open(filename)) + error("AVALANCHE: File not found: %s", filename.c_str()); + + file.seek(146); + if (!_roomnName.empty()) + _roomnName.clear(); + for (int i = 0; i < 30; i++) { + char actChar = file.readByte(); + if ((32 <= actChar) && (actChar <= 126)) + _roomnName += actChar; + } + // Compression method byte follows this... + + file.seek(177); + + _graphics->loadBackground(file); + _graphics->refreshBackground(); + + file.close(); + + loadAlso(num); + _background->load(num); + CursorMan.showMouse(true); +} + +void AvalancheEngine::findPeople(byte room) { + for (int i = 1; i < 29; i++) { + if (_whereIs[i] == room) { + if (i < 25) + _him = (People)(150 + i); + else + _her = (People)(150 + i); + } + } +} + +void AvalancheEngine::exitRoom(byte x) { + _sound->stopSound(); + _background->release(); + _seeScroll = true; // This stops the trippancy system working over the length of this procedure. + + switch (x) { + case kRoomSpludwicks: + _timer->loseTimer(Timer::kReasonAvariciusTalks); + _avariciusTalk = 0; + // He doesn't HAVE to be talking for this to work. It just deletes it IF it exists. + break; + case kRoomBridge: + if (_drawbridgeOpen > 0) { + _drawbridgeOpen = 4; // Fully open. + _timer->loseTimer(Timer::kReasonDrawbridgeFalls); + } + break; + case kRoomOutsideCardiffCastle: + _timer->loseTimer(Timer::kReasonCardiffsurvey); + break; + case kRoomRobins: + _timer->loseTimer(Timer::kReasonGettingTiedUp); + break; + } + + _interrogation = 0; // Leaving the room cancels all the questions automatically. + _seeScroll = false; // Now it can work again! + + _lastRoom = _room; + if (_room != kRoomMap) + _lastRoomNotMap = _room; +} + + +/** + * Only when entering a NEW town! Not returning to the last one, + * but choosing another from the map. + * @remarks Originally called 'new_town' + */ +void AvalancheEngine::enterNewTown() { + _menu->setup(); + + switch (_room) { + case kRoomOutsideNottsPub: // Entry into Nottingham. + if ((_roomCount[kRoomRobins] > 0) && (_beenTiedUp) && (!_takenMushroom)) + _mushroomGrowing = true; + break; + case kRoomWiseWomans: // Entry into Argent. + if (_talkedToCrapulus && (!_lustieIsAsleep)) { + _spludwickAtHome = !((_roomCount[kRoomWiseWomans] % 3) == 1); + _crapulusWillTell = !_spludwickAtHome; + } else { + _spludwickAtHome = true; + _crapulusWillTell = false; + } + if (_boxContent == kObjectWine) + _wineState = 3; // Vinegar + break; + default: + break; + } + + if ((_room != kRoomOutsideDucks) && (_objects[kObjectOnion - 1]) && !(_onionInVinegar)) + _rottenOnion = true; // You're holding the onion +} + +void AvalancheEngine::putGeidaAt(byte whichPed, byte ped) { + if (ped == 0) + return; + AnimationType *spr1 = _animation->_sprites[1]; + + spr1->init(5, false); // load Geida + _animation->appearPed(1, whichPed); + spr1->_callEachStepFl = true; + spr1->_eachStepProc = Animation::kProcGeida; +} + +void AvalancheEngine::enterRoom(Room roomId, byte ped) { + _seeScroll = true; // This stops the trippancy system working over the length of this procedure. + + findPeople(roomId); + _room = roomId; + if (ped != 0) + _roomCount[roomId]++; + + loadRoom(roomId); + + if ((_roomCount[roomId] == 0) && (!getFlag('S'))) + incScore(1); + + _whereIs[kPeopleAvalot - 150] = _room; + + if (_geidaFollows) + _whereIs[kPeopleGeida - 150] = roomId; + + _roomTime = 0; + + + if ((_lastRoom == kRoomMap) && (_lastRoomNotMap != _room)) + enterNewTown(); + + switch (roomId) { + case kRoomYours: + if (_avvyInBed) { + _background->draw(-1, -1, 2); + _graphics->refreshBackground(); + _timer->addTimer(100, Timer::kProcArkataShouts, Timer::kReasonArkataShouts); + } + break; + + case kRoomOutsideYours: + if (ped > 0) { + AnimationType *spr1 = _animation->_sprites[1]; + if (!_talkedToCrapulus) { + _whereIs[kPeopleCrapulus - 150] = kRoomOutsideYours; + spr1->init(8, false); // load Crapulus + + if (_roomCount[kRoomOutsideYours] == 1) { + _animation->appearPed(1, 3); // Start on the right-hand side of the screen. + spr1->walkTo(4); // Walks up to greet you. + } else { + _animation->appearPed(1, 4); // Starts where he was before. + spr1->_facingDir = kDirLeft; + } + + spr1->_callEachStepFl = true; + spr1->_eachStepProc = Animation::kProcFaceAvvy; // He always faces Avvy. + + } else + _whereIs[kPeopleCrapulus - 150] = kRoomNowhere; + + if (_crapulusWillTell) { + spr1->init(8, false); + _animation->appearPed(1, 1); + spr1->walkTo(3); + _timer->addTimer(20, Timer::kProcCrapulusSpludOut, Timer::kReasonCrapulusSaysSpludwickOut); + _crapulusWillTell = false; + } + } + break; + + case kRoomOutsideSpludwicks: + if ((_roomCount[kRoomOutsideSpludwicks] == 1) && (ped == 1)) { + _timer->addTimer(20, Timer::kProcBang, Timer::kReasonExplosion); + _spludwickAtHome = true; + } + break; + + case kRoomSpludwicks: + if (_spludwickAtHome) { + AnimationType *spr1 = _animation->_sprites[1]; + if (ped > 0) { + spr1->init(2, false); // load Spludwick + _animation->appearPed(1, 1); + _whereIs[kPeopleSpludwick - 150] = kRoomSpludwicks; + } + + spr1->_callEachStepFl = true; + spr1->_eachStepProc = Animation::kProcGeida; + } else + _whereIs[kPeopleSpludwick - 150] = kRoomNowhere; + break; + + case kRoomBrummieRoad: + if (_geidaFollows) + putGeidaAt(4, ped); + if (_cwytalotGone) { + _magics[kColorLightred - 1]._operation = kMagicNothing; + _whereIs[kPeopleCwytalot - 150] = kRoomNowhere; + } else if (ped > 0) { + AnimationType *spr1 = _animation->_sprites[1]; + spr1->init(4, false); // 4 = Cwytalot + spr1->_callEachStepFl = true; + spr1->_eachStepProc = Animation::kProcFollowAvvyY; + _whereIs[kPeopleCwytalot - 150] = kRoomBrummieRoad; + + if (_roomCount[kRoomBrummieRoad] == 1) { // First time here... + _animation->appearPed(1, 1); // He appears on the right of the screen... + spr1->walkTo(3); // ...and he walks up... + } else { + // You've been here before. + _animation->appearPed(1, 3); // He's standing in your way straight away... + spr1->_facingDir = kDirLeft; + } + } + break; + + case kRoomArgentRoad: + if ((_cwytalotGone) && (!_passedCwytalotInHerts) && (ped == 2) && (_roomCount[kRoomArgentRoad] > 3)) { + AnimationType *spr1 = _animation->_sprites[1]; + spr1->init(4, false); // 4 = Cwytalot again + _animation->appearPed(1, 0); + spr1->walkTo(1); + spr1->_vanishIfStill = true; + _passedCwytalotInHerts = true; + // whereis[#157] = r__Nowhere; // can we fit this in? + _timer->addTimer(20, Timer::kProcCwytalotInHerts, Timer::kReasonCwytalotInHerts); + } + break; + + case kRoomBridge: + if (_drawbridgeOpen == 4) { // open + _background->draw(-1, -1, 2); // Position of drawbridge + _graphics->refreshBackground(); + _magics[kColorGreen - 1]._operation = kMagicNothing; // You may enter the drawbridge. + } + if (_geidaFollows) + putGeidaAt(ped + 2, ped); // load Geida + break; + + case kRoomRobins: + if ((ped > 0) && (!_beenTiedUp)) { + // A welcome party... or maybe not... + AnimationType *spr1 = _animation->_sprites[1]; + spr1->init(6, false); + _animation->appearPed(1, 1); + spr1->walkTo(2); + _timer->addTimer(36, Timer::kProcGetTiedUp, Timer::kReasonGettingTiedUp); + } + + if (_beenTiedUp) { + _whereIs[kPeopleRobinHood - 150] = kRoomNowhere; + _whereIs[kPeopleFriarTuck - 150] = kRoomNowhere; + } + + if (_tiedUp) + _background->draw(-1, -1, 1); + + if (!_mushroomGrowing) + _background->draw(-1, -1, 2); + _graphics->refreshBackground(); + break; + + case kRoomOutsideCardiffCastle: + if (ped > 0) { + AnimationType *spr1 = _animation->_sprites[1]; + switch (_cardiffQuestionNum) { + case 0 : // You've answered NONE of his questions. + spr1->init(9, false); + _animation->appearPed(1, 1); + spr1->walkTo(2); + _timer->addTimer(47, Timer::kProcCardiffSurvey, Timer::kReasonCardiffsurvey); + break; + case 5 : + _magics[1]._operation = kMagicNothing; + break; // You've answered ALL his questions. => nothing happens. + default: // You've answered SOME of his questions. + spr1->init(9, false); + _animation->appearPed(1, 2); + spr1->_facingDir = kDirRight; + _timer->addTimer(3, Timer::kProcCardiffReturn, Timer::kReasonCardiffsurvey); + } + } + + if (_cardiffQuestionNum < 5) + _interrogation = _cardiffQuestionNum; + else + _interrogation = 0; + break; + + case kRoomMap: + // You're entering the map. + fadeIn(); + if (ped > 0) + _graphics->zoomOut(_peds[ped - 1]._x, _peds[ped - 1]._y); + + if ((_objects[kObjectWine - 1]) && (_wineState != 3)) { + _dialogs->displayScrollChain('q', 9); // Don't want to waste the wine! + _objects[kObjectWine - 1] = false; + refreshObjectList(); + } + + _dialogs->displayScrollChain('q', 69); + break; + + case kRoomCatacombs: + if ((ped == 0) || (ped == 3) || (ped == 5) || (ped == 6)) { + + switch (ped) { + case 3: // Enter from oubliette + _catacombX = 8; + _catacombY = 4; + break; + case 5: // Enter from du Lustie's + _catacombX = 8; + _catacombY = 7; + break; + case 6: // Enter from Geida's + _catacombX = 4; + _catacombY = 1; + break; + } + + _enterCatacombsFromLustiesRoom = true; + _animation->catacombMove(ped); + _enterCatacombsFromLustiesRoom = false; + } + break; + + case kRoomArgentPub: + if (_wonNim) + _background->draw(-1, -1, 0); // No lute by the settle. + _malagauche = 0; // Ready to boot Malagauche + if (_givenBadgeToIby) { + _background->draw(-1, -1, 7); + _background->draw(-1, -1, 8); + } + _graphics->refreshBackground(); + break; + + case kRoomLustiesRoom: + _npcFacing = 1; // du Lustie. + if (_animation->getAvvyClothes() == 0) // Avvy in his normal clothes + _timer->addTimer(3, Timer::kProcCallsGuards, Timer::kReasonDuLustieTalks); + else if (!_enteredLustiesRoomAsMonk) // already + // Presumably, Avvy dressed as a monk. + _timer->addTimer(3, Timer::kProcGreetsMonk, Timer::kReasonDuLustieTalks); + + if (_geidaFollows) { + putGeidaAt(4, ped); + if (_lustieIsAsleep) { + _background->draw(-1, -1, 4); + _graphics->refreshBackground(); + } + } + break; + + case kRoomMusicRoom: + if (_jacquesState > 0) { + _jacquesState = 5; + _background->draw(-1, -1, 1); + _graphics->refreshBackground(); + _background->draw(-1, -1, 3); + _magics[kColorBrown - 1]._operation = kMagicNothing; + _whereIs[kPeopleJacques - 150] = kRoomNowhere; + } + if (ped != 0) { + _background->draw(-1, -1, 5); + _graphics->refreshBackground(); + _sequence->startMusicRoomSeq(); + } + break; + + case kRoomOutsideNottsPub: + if (ped == 2) { + _background->draw(-1, -1, 2); + _graphics->refreshBackground(); + _sequence->startDuckSeq(); + } + break; + + case kRoomOutsideArgentPub: + if (ped == 2) { + _background->draw(-1, -1, 5); + _graphics->refreshBackground(); + _sequence->startMusicRoomSeq(); + } + break; + + case kRoomWiseWomans: { + AnimationType *spr1 = _animation->_sprites[1]; + spr1->init(11, false); + if ((_roomCount[kRoomWiseWomans] == 1) && (ped > 0)) { + _animation->appearPed(1, 1); // Start on the right-hand side of the screen. + spr1->walkTo(3); // Walks up to greet you. + } else { + _animation->appearPed(1, 3); // Starts where she was before. + spr1->_facingDir = kDirLeft; + } + + spr1->_callEachStepFl = true; + spr1->_eachStepProc = Animation::kProcFaceAvvy; // She always faces Avvy. + } + break; + + case kRoomInsideCardiffCastle: + if (ped > 0) { + _animation->_sprites[1]->init(10, false); // Define the dart. + _background->draw(-1, -1, 0); + _graphics->refreshBackground(); + _sequence->startCardiffSeq2(); + } else { + _background->draw(-1, -1, 0); + if (_arrowInTheDoor) + _background->draw(-1, -1, 2); + else + _background->draw(-1, -1, 1); + _graphics->refreshBackground(); + } + break; + + case kRoomAvvysGarden: + if (ped == 1) { + _background->draw(-1, -1, 1); + _graphics->refreshBackground(); + _sequence->startGardenSeq(); + } + break; + + case kRoomEntranceHall: + case kRoomInsideAbbey: + case kRoomYourHall: + if (ped == 2) { +#if 0 + // It was the original: + _celer->show_one(-1, -1, 2); + _sequence->first_show(1); + _sequence->then_show(3); + _sequence->start_to_close(); +#endif + + _background->draw(-1, -1, 1); + _graphics->refreshBackground(); + _sequence->startGardenSeq(); + } + break; + + case kRoomAylesOffice: + if (_aylesIsAwake) + _background->draw(-1, -1, 1); + _graphics->refreshBackground(); + break; // Ayles awake. + + case kRoomGeidas: + putGeidaAt(1, ped); + break; // load Geida + + case kRoomEastHall: + case kRoomWestHall: + if (_geidaFollows) + putGeidaAt(ped + 1, ped); + break; + + case kRoomLusties: + if (_geidaFollows) + putGeidaAt(ped + 5, ped); + break; + + case kRoomNottsPub: + if (_sittingInPub) + _background->draw(-1, -1, 2); + _npcFacing = 1; // Port. + break; + + case kRoomOutsideDucks: + if (ped == 2) { + // Shut the door + _background->draw(-1, -1, 2); + _graphics->refreshBackground(); + _sequence->startDuckSeq(); + } + break; + + case kRoomDucks: + _npcFacing = 1; // Duck. + break; + + default: + break; + } + + _seeScroll = false; // Now it can work again! + _isLoaded = false; +} + +void AvalancheEngine::thinkAbout(byte object, bool type) { + _thinks = object; + object--; + + Common::String filename; + if (type == kThing) { + filename = "thinks.avd"; + } else { // kPerson + filename = "folk.avd"; + + object -= 149; + if (object >= 25) + object -= 8; + if (object == 20) + object--; // Last time... + } + + _graphics->loadMouse(kCurWait); + CursorMan.showMouse(false); + _graphics->drawThinkPic(filename, object); + CursorMan.showMouse(true); + + _thinkThing = type; +} + +void AvalancheEngine::drawToolbar() { + _graphics->drawToolbar(); + _animation->setOldDirection(kDirNone); + drawDirection(); +} + +void AvalancheEngine::drawScore() { + uint16 score = _dnascore; + int8 numbers[3] = {0, 0, 0}; + for (int i = 0; i < 2; i++) { + byte divisor = 1; + for (int j = 0; j < (2 - i); j++) + divisor *= 10; + numbers[i] = score / divisor; + score -= numbers[i] * divisor; + } + numbers[2] = score; + + CursorMan.showMouse(false); + + for (int i = 0; i < 3; i++) { + if (_scoreToDisplay[i] != numbers[i]) + _graphics->drawDigit(numbers[i], 250 + (i + 1) * 15, 177); + } + + CursorMan.showMouse(true); + + for (int i = 0; i < 3; i++) + _scoreToDisplay[i] = numbers[i]; +} + +void AvalancheEngine::incScore(byte num) { + for (int i = 1; i <= num; i++) { + _dnascore++; + + if (_soundFx) { + for (int j = 1; j <= 97; j++) + // Length os 2 is a guess, the original doesn't have a delay specified + _sound->playNote(177 + _dnascore * 3, 2); + } + } + warning("STUB: points()"); + + drawScore(); +} + +void AvalancheEngine::useCompass(const Common::Point &cursorPos) { + byte color = _graphics->getScreenColor(cursorPos); + + switch (color) { + case kColorGreen: + _animation->setDirection(kDirUp); + _animation->setMoveSpeed(0, kDirUp); + drawDirection(); + break; + case kColorBrown: + _animation->setDirection(kDirDown); + _animation->setMoveSpeed(0, kDirDown); + drawDirection(); + break; + case kColorCyan: + _animation->setDirection(kDirLeft); + _animation->setMoveSpeed(0, kDirLeft); + drawDirection(); + break; + case kColorLightmagenta: + _animation->setDirection(kDirRight); + _animation->setMoveSpeed(0, kDirRight); + drawDirection(); + break; + case kColorRed: + case kColorWhite: + case kColorLightcyan: + case kColorYellow: // Fall-throughs are intended. + _animation->stopWalking(); + drawDirection(); + break; + } +} + +void AvalancheEngine::fxToggle() { + warning("STUB: fxtoggle()"); +} + +void AvalancheEngine::refreshObjectList() { + _carryNum = 0; + if (_thinkThing && !_objects[_thinks - 1]) + thinkAbout(kObjectMoney, kThing); // you always have money + + for (int i = 0; i < kObjectNum; i++) { + if (_objects[i]) { + _objectList[_carryNum] = i + 1; + _carryNum++; + } + } +} + +/** + * @remarks Originally called 'verte' + */ +void AvalancheEngine::guideAvvy(Common::Point cursorPos) { + if (!_userMovesAvvy) + return; + + cursorPos.y /= 2; + byte what; + + // _animation->tr[0] is Avalot.) + AnimationType *avvy = _animation->_sprites[0]; + if (cursorPos.x < avvy->_x) + what = 1; + else if (cursorPos.x > (avvy->_x + avvy->_xLength)) + what = 2; + else + what = 0; // On top + + if (cursorPos.y < avvy->_y) + what += 3; + else if (cursorPos.y > (avvy->_y + avvy->_yLength)) + what += 6; + + switch (what) { + case 0: + _animation->stopWalking(); + break; // Clicked on Avvy: no movement. + case 1: + _animation->setMoveSpeed(0, kDirLeft); + break; + case 2: + _animation->setMoveSpeed(0, kDirRight); + break; + case 3: + _animation->setMoveSpeed(0, kDirUp); + break; + case 4: + _animation->setMoveSpeed(0, kDirUpLeft); + break; + case 5: + _animation->setMoveSpeed(0, kDirUpRight); + break; + case 6: + _animation->setMoveSpeed(0, kDirDown); + break; + case 7: + _animation->setMoveSpeed(0, kDirDownLeft); + break; + case 8: + _animation->setMoveSpeed(0, kDirDownRight); + break; + } // No other values are possible. + + drawDirection(); +} + +void AvalancheEngine::checkClick() { + Common::Point cursorPos = getMousePos(); + + /*if (mrelease > 0) + after_the_scroll = false;*/ + + if ((0 <= cursorPos.y) && (cursorPos.y <= 21)) + _graphics->loadMouse(kCurUpArrow); // up arrow + else if ((317 <= cursorPos.y) && (cursorPos.y <= 339)) + _graphics->loadMouse(kCurIBeam); //I-beam + else if ((340 <= cursorPos.y) && (cursorPos.y <= 399)) + _graphics->loadMouse(kCurScrewDriver); // screwdriver + else if (!_menu->isActive()) { // Dropdown can handle its own pointers. + if (_holdLeftMouse) { + _graphics->loadMouse(kCurCrosshair); // Mark's crosshairs + guideAvvy(cursorPos); // Normally, if you click on the picture, you're guiding Avvy around. + } else + _graphics->loadMouse(kCurFletch); // fletch + } + + if (_holdLeftMouse) { + if ((0 <= cursorPos.y) && (cursorPos.y <= 21)) { // Click on the dropdown menu. + if (_dropsOk) + _menu->update(); + } else if ((317 <= cursorPos.y) && (cursorPos.y <= 339)) { // Click on the command line. + _parser->_inputTextPos = (cursorPos.x - 23) / 8; + if (_parser->_inputTextPos > _parser->_inputText.size() + 1) + _parser->_inputTextPos = _parser->_inputText.size() + 1; + if (_parser->_inputTextPos < 1) + _parser->_inputTextPos = 1; + _parser->_inputTextPos--; + _parser->plotText(); + } else if ((340 <= cursorPos.y) && (cursorPos.y <= 399)) { // Check the toolbar. + if ((137 <= cursorPos.x) && (cursorPos.x <= 207)) { // Control Avvy with the compass. + if (_alive && _avvyIsAwake) + useCompass(cursorPos); + } else if ((208 <= cursorPos.x) && (cursorPos.x <= 260)) { // Examine the _thing. + do { + updateEvents(); + } while (_holdLeftMouse); + + if (_thinkThing) { + _parser->_thing = _thinks; + _parser->_thing += 49; + _parser->_person = kPeoplePardon; + } else { + _parser->_person = (People) _thinks; + _parser->_thing = _parser->kPardon; + } + callVerb(kVerbCodeExam); + } else if ((261 <= cursorPos.x) && (cursorPos.x <= 319)) { // Display the score. + do { + updateEvents(); + } while (_holdLeftMouse); + + callVerb(kVerbCodeScore); + } else if ((320 <= cursorPos.x) && (cursorPos.x <= 357)) { // Change speed. + _animation->_sprites[0]->_speedX = kWalk; + _animation->updateSpeed(); + } else if ((358 <= cursorPos.x) && (cursorPos.x <= 395)) { // Change speed. + _animation->_sprites[0]->_speedX = kRun; + _animation->updateSpeed(); + } else if ((396 <= cursorPos.x) && (cursorPos.x <= 483)) + fxToggle(); + else if ((535 <= cursorPos.x) && (cursorPos.x <= 640)) + _mouseText.insertChar(kControlNewLine, 0); + } else if (!_dropsOk) + _mouseText = Common::String(13) + _mouseText; + } +} + +void AvalancheEngine::errorLed() { + warning("STUB: errorled()"); +} + +/** + * Displays a fade out, full screen. + * This version is different to the one in the original, which was fading in 3 steps. + * @remarks Originally called 'dusk' + */ +void AvalancheEngine::fadeOut() { + byte pal[3], tmpPal[3]; + + _graphics->setBackgroundColor(kColorBlack); + if (_fxHidden) + return; + _fxHidden = true; + + for (int i = 0; i < 16; i++) { + for (int j = 0; j < 16; j++) { + g_system->getPaletteManager()->grabPalette((byte *)tmpPal, j, 1); + _fxPal[i][j][0] = tmpPal[0]; + _fxPal[i][j][1] = tmpPal[1]; + _fxPal[i][j][2] = tmpPal[2]; + if (tmpPal[0] >= 16) + pal[0] = tmpPal[0] - 16; + else + pal[0] = 0; + + if (tmpPal[1] >= 16) + pal[1] = tmpPal[1] - 16; + else + pal[1] = 0; + + if (tmpPal[2] >= 16) + pal[2] = tmpPal[2] - 16; + else + pal[2] = 0; + + g_system->getPaletteManager()->setPalette(pal, j, 1); + } + _system->delayMillis(10); + _graphics->refreshScreen(); + } +} + +/** + * Displays a fade in, full screen. + * This version is different to the one in the original, which was fading in 3 steps. + * @remarks Originally called 'dawn' + */ +void AvalancheEngine::fadeIn() { + if (_holdTheDawn || !_fxHidden) + return; + + _fxHidden = false; + + byte pal[3]; + for (int i = 15; i >= 0; i--) { + for (int j = 0; j < 16; j++) { + pal[0] = _fxPal[i][j][0]; + pal[1] = _fxPal[i][j][1]; + pal[2] = _fxPal[i][j][2]; + g_system->getPaletteManager()->setPalette(pal, j, 1); + } + _system->delayMillis(10); + _graphics->refreshScreen(); + } + + if ((_room == kRoomYours) && _avvyInBed && _teetotal) + _graphics->setBackgroundColor(kColorYellow); +} + +void AvalancheEngine::drawDirection() { // It's data is loaded in load_digits(). + if (_animation->getOldDirection() == _animation->getDirection()) + return; + + _animation->setOldDirection(_animation->getDirection()); + + CursorMan.showMouse(false); + _graphics->drawDirection(_animation->getDirection(), 0, 161); + CursorMan.showMouse(true); +} + + +void AvalancheEngine::gameOver() { + _userMovesAvvy = false; + + AnimationType *avvy = _animation->_sprites[0]; + int16 sx = avvy->_x; + int16 sy = avvy->_y; + + avvy->remove(); + avvy->init(12, true); // 12 = Avalot falls + avvy->_stepNum = 0; + avvy->appear(sx, sy, kDirUp); + + _timer->addTimer(3, Timer::kProcAvalotFalls, Timer::kReasonFallingOver); + _alive = false; +} + +void AvalancheEngine::minorRedraw() { + fadeOut(); + + enterRoom(_room, 0); // Ped unknown or non-existant. + + for (int i = 0; i < 3; i++) + _scoreToDisplay[i] = -1; // impossible digits + drawScore(); + + fadeIn(); +} + +void AvalancheEngine::majorRedraw() { + warning("STUB: major_redraw()"); +} + +uint16 AvalancheEngine::bearing(byte whichPed) { + AnimationType *avvy = _animation->_sprites[0]; + PedType *curPed = &_peds[whichPed]; + + if (avvy->_x == curPed->_x) + return 0; + + int16 deltaX = avvy->_x - curPed->_x; + int16 deltaY = avvy->_y - curPed->_y; + uint16 result = (uint16)(atan((float)(deltaY / deltaX)) * 180 / M_PI); + if (avvy->_x < curPed->_x) { + return result + 90; + } else { + return result + 270; + } +} + +/** + * @remarks Originally called 'sprite_run' + */ +void AvalancheEngine::spriteRun() { + _doingSpriteRun = true; + _animation->animLink(); + _doingSpriteRun = false; +} + +// CHECKME: Unused function +void AvalancheEngine::fixFlashers() { + _ledStatus = 177; + _animation->setOldDirection(kDirNone); + _dialogs->setReadyLight(2); + drawDirection(); +} + +Common::String AvalancheEngine::intToStr(int32 num) { + return Common::String::format("%d", num); +} + +void AvalancheEngine::resetVariables() { + _animation->setDirection(kDirUp); + _carryNum = 0; + for (int i = 0; i < kObjectNum; i++) + _objects[i] = false; + + _dnascore = 0; + _money = 0; + _room = kRoomNowhere; + _saveNum = 0; + for (int i = 0; i < 100; i++) + _roomCount[i] = 0; + + _wonNim = false; + _wineState = 0; + _cwytalotGone = false; + _passwordNum = 0; + _aylesIsAwake = false; + _drawbridgeOpen = 0; + _avariciusTalk = 0; + _rottenOnion = false; + _onionInVinegar = false; + _givenToSpludwick = 0; + _brummieStairs = 0; + _cardiffQuestionNum = 0; + _passedCwytalotInHerts = false; + _avvyIsAwake = false; + _avvyInBed = false; + _userMovesAvvy = false; + _npcFacing = 0; + _givenBadgeToIby = false; + _friarWillTieYouUp = false; + _tiedUp = false; + _boxContent = 0; + _talkedToCrapulus = false; + _jacquesState = 0; + _bellsAreRinging = false; + _standingOnDais = false; + _takenPen = false; + _arrowInTheDoor = false; + _favouriteDrink = ""; + _favouriteSong = ""; + _worstPlaceOnEarth = ""; + _spareEvening = ""; + _totalTime = 0; + _jumpStatus = 0; + _mushroomGrowing = false; + _spludwickAtHome = false; + _lastRoom = 0; + _lastRoomNotMap = 0; + _crapulusWillTell = false; + _enterCatacombsFromLustiesRoom = false; + _teetotal = false; + _malagauche = 0; + _drinking = 0; + _enteredLustiesRoomAsMonk = false; + _catacombX = 0; + _catacombY = 0; + _avvysInTheCupboard = false; + _geidaFollows = false; + _givenPotionToGeida = false; + _lustieIsAsleep = false; + _beenTiedUp = false; + _sittingInPub = false; + _spurgeTalkCount = 0; + _metAvaroid = false; + _takenMushroom = false; + _givenPenToAyles = false; + _askedDogfoodAboutNim = false; + + _parser->resetVariables(); + _animation->resetVariables(); + _sequence->resetVariables(); + _background->resetVariables(); + _menu->resetVariables(); +} + +void AvalancheEngine::newGame() { + for (int i = 0; i < kMaxSprites; i++) { + AnimationType *spr = _animation->_sprites[i]; + if (spr->_quick) + spr->remove(); + } + // Deallocate sprite. Sorry, beta testers! + + AnimationType *avvy = _animation->_sprites[0]; + avvy->init(0, true); + + _alive = true; + resetVariables(); + + _dialogs->setBubbleStateNatural(); + + _spareEvening = "answer a questionnaire"; + _favouriteDrink = "beer"; + _money = 30; // 2/6 + _animation->setDirection(kDirStopped); + _parser->_wearing = kObjectClothes; + _objects[kObjectMoney - 1] = true; + _objects[kObjectBodkin - 1] = true; + _objects[kObjectBell - 1] = true; + _objects[kObjectClothes - 1] = true; + + _thinkThing = true; + _thinks = 2; + refreshObjectList(); + _seeScroll = false; + + avvy->appear(300, 117, kDirRight); // Needed to initialize Avalot. + //for (gd = 0; gd <= 30; gd++) for (gm = 0; gm <= 1; gm++) also[gd][gm] = nil; + // fillchar(previous^,sizeof(previous^),#0); { blank out array } + _him = kPeoplePardon; + _her = kPeoplePardon; + _it = Parser::kPardon; + _passwordNum = _rnd->getRandomNumber(29) + 1; //Random(30) + 1; + _userMovesAvvy = false; + _doingSpriteRun = false; + _avvyInBed = true; + + enterRoom(kRoomYours, 1); + avvy->_visible = false; + drawScore(); + _menu->setup(); + _clock->update(); + spriteRun(); +} + +bool AvalancheEngine::getFlag(char x) { + for (uint16 i = 0; i < _flags.size(); i++) { + if (_flags[i] == x) + return true; + } + + return false; +} + +bool AvalancheEngine::decreaseMoney(uint16 amount) { + _money -= amount; + if (_money < 0) { + _dialogs->displayScrollChain('Q', 2); // "You are now denariusless!" + gameOver(); + return false; + } else + return true; +} + +Common::String AvalancheEngine::getName(People whose) { + static const char lads[17][20] = { + "Avalot", "Spludwick", "Crapulus", "Dr. Duck", "Malagauche", + "Friar Tuck", "Robin Hood", "Cwytalot", "du Lustie", "the Duke of Cardiff", + "Dogfood", "A trader", "Ibythneth", "Ayles", "Port", + "Spurge", "Jacques" + }; + + static const char lasses[4][15] = {"Arkata", "Geida", "\0xB1", "the Wise Woman"}; + + if (whose < kPeopleArkata) + return Common::String(lads[whose - kPeopleAvalot]); + else + return Common::String(lasses[whose - kPeopleArkata]); +} + +Common::String AvalancheEngine::getItem(byte which) { + static const char items[kObjectNum][18] = { + "some wine", "your money-bag", "your bodkin", "a potion", "a chastity belt", + "a crossbow bolt", "a crossbow", "a lute", "a pilgrim's badge", "a mushroom", + "a key", "a bell", "a scroll", "a pen", "some ink", + "your clothes", "a habit", "an onion" + }; + + Common::String result; + if (which > 150) + which -= 149; + + switch (which) { + case kObjectWine: + switch (_wineState) { + case 0: + case 1: + case 4: + result = Common::String(items[which - 1]); + break; + case 3: + result = "some vinegar"; + break; + } + break; + case kObjectOnion: + if (_rottenOnion) + result = "a rotten onion"; + else if (_onionInVinegar) + result = "a pickled onion (in the vinegar)"; + else + result = Common::String(items[which - 1]); + break; + default: + if ((which < kObjectNum) && (which > 0)) + result = Common::String(items[which - 1]); + else + result = ""; + } + return result; +} + +Common::String AvalancheEngine::f5Does() { + switch (_room) { + case kRoomYours: + if (!_avvyIsAwake) + return Common::String::format("%cWWake up", kVerbCodeWake); + else if (_avvyInBed) + return Common::String::format("%cGGet up", kVerbCodeStand); + break; + case kRoomInsideCardiffCastle: + if (_standingOnDais) + return Common::String::format("%cCClimb down", kVerbCodeClimb); + else + return Common::String::format("%cCClimb up", kVerbCodeClimb); + break; + case kRoomNottsPub: + if (_sittingInPub) + return Common::String::format("%cSStand up", kVerbCodeStand); + else + return Common::String::format("%cSSit down", kVerbCodeSit); + break; + case kRoomMusicRoom: + if (_animation->inField(5)) + return Common::String::format("%cPPlay the harp", kVerbCodePlay); + break; + default: + break; + } + + return Common::String::format("%c", kVerbCodePardon); // If all else fails... +} + +void AvalancheEngine::flipRoom(Room room, byte ped) { + assert((ped > 0) && (ped < 15)); + if (!_alive) { + // You can't leave the room if you're dead. + _animation->_sprites[0]->_moveX = 0; + _animation->_sprites[0]->_moveY = 0; // Stop him from moving. + return; + } + + if ((room == kRoomDummy) && (_room == kRoomLusties)) { + _animation->hideInCupboard(); + return; + } + + if ((_jumpStatus > 0) && (_room == kRoomInsideCardiffCastle)) { + // You can't *jump* out of Cardiff Castle! + _animation->_sprites[0]->_moveX = 0; + return; + } + + exitRoom(_room); + fadeOut(); + + for (int16 i = 1; i < _animation->kSpriteNumbMax; i++) { + if (_animation->_sprites[i]->_quick) + _animation->_sprites[i]->remove(); + } // Deallocate sprite + + if (_room == kRoomLustiesRoom) + _enterCatacombsFromLustiesRoom = true; + + enterRoom(room, ped); + _animation->appearPed(0, ped - 1); + _enterCatacombsFromLustiesRoom = false; + _animation->setOldDirection(_animation->getDirection()); + _animation->setDirection(_animation->_sprites[0]->_facingDir); + drawDirection(); + + fadeIn(); +} + +/** + * Open the Door. + * This slides the door open. The data really ought to be saved in + * the Also file, and will be next time. However, for now, they're + * here. + * @remarks Originally called 'open_the_door' + */ +void AvalancheEngine::openDoor(Room whither, byte ped, byte magicnum) { + switch (_room) { + case kRoomOutsideYours: + case kRoomOutsideNottsPub: + case kRoomOutsideDucks: + _sequence->startOutsideSeq(whither, ped); + break; + case kRoomInsideCardiffCastle: + _sequence->startCardiffSeq(whither, ped); + break; + case kRoomAvvysGarden: + case kRoomEntranceHall: + case kRoomInsideAbbey: + case kRoomYourHall: + _sequence->startHallSeq(whither, ped); + break; + case kRoomMusicRoom: + case kRoomOutsideArgentPub: + _sequence->startMusicRoomSeq2(whither, ped); + break; + case kRoomLusties: + switch (magicnum) { + case 14: + if (_avvysInTheCupboard) { + _animation->hideInCupboard(); + _sequence->startCupboardSeq(); + return; + } else { + _animation->appearPed(0, 5); + _animation->_sprites[0]->_facingDir = kDirRight; + _sequence->startLustiesSeq2(whither, ped); + } + break; + case 12: + _sequence->startLustiesSeq3(whither, ped); + break; + } + break; + default: + _sequence->startDummySeq(whither, ped); + } +} + +void AvalancheEngine::setRoom(People persId, Room roomId) { + _whereIs[persId - kPeopleAvalot] = roomId; +} + +Room AvalancheEngine::getRoom(People persId) { + return _whereIs[persId - kPeopleAvalot]; +} +} // End of namespace Avalanche diff --git a/engines/avalanche/avalot.h b/engines/avalanche/avalot.h new file mode 100644 index 0000000000..ab78f5c385 --- /dev/null +++ b/engines/avalanche/avalot.h @@ -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 the original source code of Lord Avalot d'Argent version 1.3. + * Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman. + */ + +/* AVALOT The kernel of the program. */ + +#ifndef AVALANCHE_AVALOT_H +#define AVALANCHE_AVALOT_H + +#include "avalanche/animation.h" + +namespace Avalanche { +class AvalancheEngine; + +class Clock { +public: + Clock(AvalancheEngine *vm); + + void update(); + +private: + static const int kCenterX = 510; + static const int kCenterY = 183; + + AvalancheEngine *_vm; + + uint16 _hour, _minute, _second, _hourAngle, _oldHour, _oldMinute, _oldHourAngle; + Common::Point _clockHandHour, _clockHandMinute; + + Common::Point calcHand(uint16 angle, uint16 length, Color color); + void drawHand(const Common::Point &endPoint, Color color); + void plotHands(); + void chime(); +}; + +static const byte kObjectNum = 18; // always preface with a # +static const int16 kCarryLimit = 12; // carry limit + +static const int16 kNumlockCode = 32; // Code for Num Lock +static const int16 kMouseSize = 134; + +struct PedType { + int16 _x, _y; + Direction _direction; +}; + +struct MagicType { + byte _operation; // one of the operations + uint16 _data; // data for them +}; + +struct FieldType { + int16 _x1, _y1, _x2, _y2; +}; + +struct LineType : public FieldType { + Color _color; +}; + +typedef int8 TuneType[31]; + +struct QuasipedType { + byte _whichPed; + Color _textColor; + Room _room; + Color _backgroundColor; + People _who; +}; + +#if 0 +struct Sundry { // Things which must be saved over a backtobootstrap, outside DNA. + Common::String _qEnidFilename; + bool _qSoundFx; + byte _qThinks; + bool _qThinkThing; +}; +#endif + +} // End of namespace Avalanche + +#endif // AVALANCHE_AVALOT_H diff --git a/engines/avalanche/background.cpp b/engines/avalanche/background.cpp new file mode 100644 index 0000000000..c84c049c8f --- /dev/null +++ b/engines/avalanche/background.cpp @@ -0,0 +1,369 @@ +/* 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 the original source code of Lord Avalot d'Argent version 1.3. + * Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman. + */ + +/* Original name: CELER The unit for updating the screen pics. */ + +#include "avalanche/avalanche.h" +#include "avalanche/background.h" + +namespace Avalanche { + +const int16 Background::kOnDisk = -1; + +Background::Background(AvalancheEngine *vm) { + _vm = vm; + _spriteNum = 0; +} + +Background::~Background() { + release(); +} + +/** + * @remarks Originally called 'pics_link' + */ +void Background::update() { + if (_vm->_menu->isActive()) + return; // No animation when the menus are up. + + switch (_vm->_room) { + case kRoomOutsideArgentPub: + if ((_vm->_roomTime % 12) == 0) + draw(-1, -1, (_vm->_roomTime / 12) % 4); + break; + case kRoomBrummieRoad: + if ((_vm->_roomTime % 2) == 0) + draw(-1, -1, (_vm->_roomTime / 2) % 4); + break; + case kRoomBridge: + if ((_vm->_roomTime % 2) == 0) + draw(-1, -1, 3 + (_vm->_roomTime / 2) % 4); + break; + case kRoomYours: + if ((!_vm->_avvyIsAwake) && ((_vm->_roomTime % 4) == 0)) + draw(-1, -1, (_vm->_roomTime / 12) % 2); + break; + case kRoomArgentPub: + if (((_vm->_roomTime % 7) == 1) && (_vm->_malagauche != 177)) { + // Malagauche cycle. + _vm->_malagauche++; + switch (_vm->_malagauche) { + case 1: + case 11: + case 21: + draw(-1, -1, 11); // Looks forwards. + break; + case 8: + case 18: + case 28: + case 32: + draw(-1, -1, 10); // Looks at you. + break; + case 30: + draw(-1, -1, 12); // Winks. + break; + case 33: + _vm->_malagauche = 0; + break; + } + } + + switch (_vm->_roomTime % 200) { + case 179: + case 197: + draw(-1, -1, 4); // Dogfood's drinking cycle. + break; + case 182: + case 194: + draw(-1, -1, 5); + break; + case 185: + draw(-1, -1, 6); + break; + case 199: + _vm->_npcFacing = 177; // Impossible value for this. + break; + default: + if (_vm->_roomTime % 200 <= 178) { // Normally. + byte direction = 1; + uint16 angle = _vm->bearing(1); + if (((angle >= 1) && (angle <= 90)) || ((angle >= 358) && (angle <= 360))) + direction = 3; + else if ((angle >= 293) && (angle <= 357)) + direction = 2; + else if ((angle >= 270) && (angle <= 292)) + direction = 4; + + if (direction != _vm->_npcFacing) { // Dogfood. + draw(-1, -1, direction - 1); + _vm->_npcFacing = direction; + } + } + } + break; + case kRoomWestHall: + if ((_vm->_roomTime % 3) == 0) { + switch ((_vm->_roomTime / 3) % 6) { + case 4: + draw(-1, -1, 0); + break; + case 1: + case 3: + case 5: + draw(-1, -1, 1); + break; + case 0: + case 2: + draw(-1, -1, 2); + break; + } + } + break; + case kRoomLustiesRoom: + if (!(_vm->_lustieIsAsleep)) { + byte direction = 0; + uint16 angle = _vm->bearing(1); + if ((_vm->_roomTime % 45) > 42) + direction = 4; // du Lustie blinks. + // Bearing of Avvy from du Lustie. + else if ((angle <= 45) || ((angle >= 315) && (angle <= 360))) + direction = 1; // Middle. + else if ((angle >= 45) && (angle <= 180)) + direction = 2; // Left. + else if ((angle >= 181) && (angle <= 314)) + direction = 3; // Right. + + if (direction != _vm->_npcFacing) { // du Lustie. + draw(-1, -1, direction - 1); + _vm->_npcFacing = direction; + } + } + break; + case kRoomAylesOffice: + if ((!_vm->_aylesIsAwake) && (_vm->_roomTime % 14 == 0)) { + switch ((_vm->_roomTime / 14) % 2) { + case 0: + draw(-1, -1, 0); // Frame 2: EGA. + break; + case 1: + draw(-1, -1, 2); // Frame 1: Natural. + break; + } + } + break; + case kRoomRobins: + if (_vm->_tiedUp) { + switch (_vm->_roomTime % 54) { + case 20: + draw(-1, -1, 3); // Frame 4: Avalot blinks. + break; + case 23: + draw(-1, -1, 1); // Frame 1: Back to normal. + break; + } + } + break; + case kRoomNottsPub: { + // Bearing of Avvy from Port. + byte direction = 0; + uint16 angle = _vm->bearing(4); + if ((angle <= 45) || ((angle >= 315) && (angle <= 360))) + direction = 2; // Middle. + else if ((angle >= 45) && (angle <= 180)) + direction = 6; // Left. + else if ((angle >= 181) && (angle <= 314)) + direction = 8; // Right. + + if ((_vm->_roomTime % 60) > 57) + direction--; // Blinks. + + if (direction != _vm->_npcFacing) { // Port. + draw(-1, -1, direction - 1); + _vm->_npcFacing = direction; + } + + switch (_vm->_roomTime % 50) { + case 45 : + draw(-1, -1, 8); // Spurge blinks. + break; + case 49 : + draw(-1, -1, 9); + break; + } + break; + } + case kRoomDucks: { + if ((_vm->_roomTime % 3) == 0) // The fire flickers. + draw(-1, -1, (_vm->_roomTime / 3) % 3); + + // Bearing of Avvy from Duck. + byte direction = 0; + uint16 angle = _vm->bearing(1); + if ((angle <= 45) || ((angle >= 315) && (angle <= 360))) + direction = 4; // Middle. + else if ((angle >= 45) && (angle <= 180)) + direction = 6; // Left. + else if ((angle >= 181) && (angle <= 314)) + direction = 8; // Right. + + if ((_vm->_roomTime % 45) > 42) + direction++; // Duck blinks. + + if (direction != _vm->_npcFacing) { // Duck. + draw(-1, -1, direction - 1); + _vm->_npcFacing = direction; + } + break; + } + default: + break; + } + + if ((_vm->_bellsAreRinging) && (_vm->getFlag('B'))) { + // They're ringing the bells. + switch (_vm->_roomTime % 4) { + case 1: + if (_nextBell < 5) + _nextBell = 12; + _nextBell--; + // CHECKME: 2 is a guess. No length in the original? + _vm->_sound->playNote(_vm->kNotes[_nextBell], 2); + break; + case 2: + _vm->_sound->stopSound(); + break; + } + } +} + +void Background::load(byte number) { + Common::File f; + _filename = _filename.format("chunk%d.avd", number); + if (!f.open(_filename)) + return; // We skip because some rooms don't have sprites in the background. + + f.seek(44); + _spriteNum = f.readByte(); + for (int i = 0; i < _spriteNum; i++) + _offsets[i] = f.readSint32LE(); + + for (int i = 0; i < _spriteNum; i++) { + f.seek(_offsets[i]); + + SpriteType sprite; + sprite._type = (PictureType)(f.readByte()); + sprite._x = f.readSint16LE(); + sprite._y = f.readSint16LE(); + sprite._xl = f.readSint16LE(); + sprite._yl = f.readSint16LE(); + sprite._size = f.readSint32LE(); + bool natural = f.readByte(); + bool memorize = f.readByte(); + + if (memorize) { + _sprites[i]._x = sprite._x; + _sprites[i]._xl = sprite._xl; + _sprites[i]._y = sprite._y; + _sprites[i]._yl = sprite._yl; + _sprites[i]._type = sprite._type; + + if (natural) + _vm->_graphics->getNaturalPicture(_sprites[i]); + else { + _sprites[i]._size = sprite._size; + _sprites[i]._picture = _vm->_graphics->loadPictureRaw(f, _sprites[i]._xl * 8, _sprites[i]._yl + 1); + } + } else + _sprites[i]._x = kOnDisk; + } + f.close(); +} + +void Background::release() { + for (int i = 0; i < _spriteNum; i++) { + if (_sprites[i]._x > kOnDisk) + _sprites[i]._picture.free(); + } +} + +/** + * Draw background animation + * @remarks Originally called 'show_one' + */ +void Background::draw(int16 destX, int16 destY, byte sprId) { + assert(sprId < 40); + + if (_sprites[sprId]._x > kOnDisk) { + if (destX < 0) { + destX = _sprites[sprId]._x * 8; + destY = _sprites[sprId]._y; + } + drawSprite(destX, destY, _sprites[sprId]); + } else { + Common::File f; + if (!f.open(_filename)) // Filename was set in loadBackgroundSprites(). + return; // We skip because some rooms don't have sprites in the background. + + f.seek(_offsets[sprId]); + + SpriteType sprite; + sprite._type = (PictureType)(f.readByte()); + sprite._x = f.readSint16LE(); + sprite._y = f.readSint16LE(); + sprite._xl = f.readSint16LE(); + sprite._yl = f.readSint16LE(); + sprite._size = f.readSint32LE(); + f.skip(2); // Natural and Memorize are used in Load() + sprite._picture = _vm->_graphics->loadPictureRaw(f, sprite._xl * 8, sprite._yl + 1); + + if (destX < 0) { + destX = sprite._x * 8; + destY = sprite._y; + } + drawSprite(destX, destY, sprite); + + sprite._picture.free(); + f.close(); + } +} + +/** + * @remarks Originally called 'display_it' + */ +void Background::drawSprite(int16 x, int16 y, SpriteType &sprite) { + // These pictures are practically parts of the background. -10 is for the drop-down menu. + _vm->_graphics->drawBackgroundSprite(x, y - 10, sprite); +} + +void Background::resetVariables() { + _nextBell = 0; +} + +void Background::synchronize(Common::Serializer &sz) { + sz.syncAsByte(_nextBell); +} +} // End of namespace Avalanche. diff --git a/engines/avalanche/background.h b/engines/avalanche/background.h new file mode 100644 index 0000000000..34d7a9a2cc --- /dev/null +++ b/engines/avalanche/background.h @@ -0,0 +1,79 @@ +/* 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 the original source code of Lord Avalot d'Argent version 1.3. + * Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman. + */ + +/* Original name: CELER The unit for updating the screen pics. */ + +#ifndef AVALANCHE_BACKGROUND_H +#define AVALANCHE_BACKGROUND_H + +#include "common/str.h" + +namespace Avalanche { +class AvalancheEngine; + +enum PictureType {kEga, kBgi, kNaturalImage}; + +struct SpriteType { + PictureType _type; + int16 _x, _y; + int16 _xl, _yl; + int32 _size; + Graphics::Surface _picture; +}; + +class Background { +public: + Background(AvalancheEngine *vm); + ~Background(); + + void update(); + void load(byte number); + void release(); + + // Setting the destination to negative coordinates means the picture should be drawn to it's original position. + // If you give it positive values, the picture will be plotted to the desired coordinates on the screen. + // By that we get rid of show_one_at(), which would be almost identical and cause a lot of code duplication. + void draw(int16 destX, int16 destY, byte sprId); + void resetVariables(); + void synchronize(Common::Serializer &sz); + +private: + AvalancheEngine *_vm; + + byte _nextBell; // For the ringing. + int32 _offsets[40]; + byte _spriteNum; + SpriteType _sprites[40]; + Common::String _filename; + static const int16 kOnDisk; // Value of _sprites[fv]._x when it's not in memory. + + void drawSprite(int16 x, int16 y, SpriteType &sprite); +}; + +} // End of namespace Avalanche. + +#endif // AVALANCHE_BACKGROUND_H diff --git a/engines/avalanche/closing.cpp b/engines/avalanche/closing.cpp new file mode 100644 index 0000000000..1cb2e84218 --- /dev/null +++ b/engines/avalanche/closing.cpp @@ -0,0 +1,75 @@ +/* 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 the original source code of Lord Avalot d'Argent version 1.3. + * Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman. + */ + +/* CLOSING The closing screen and error handler. */ + +#include "avalanche/avalanche.h" +#include "avalanche/closing.h" + +#include "common/random.h" + +namespace Avalanche { + +Closing::Closing(AvalancheEngine *vm) { + _vm = vm; + warning("STUB: Closing::Closing()"); +} + +void Closing::getScreen(ScreenType which) { + warning("STUB: Closing::getScreen()"); +} + +void Closing::showScreen() { + warning("STUB: Closing::showScreen()"); +} + +void Closing::putIn(Common::String str, uint16 where) { + warning("STUB: Closing::putIn()"); +} + +void Closing::exitGame() { + static const char nouns[12][14] = { + "sackbut", "harpsichord", "camel", "conscience", "ice-cream", "serf", + "abacus", "castle", "carrots", "megaphone", "manticore", "drawbridge" + }; + + static const char verbs[12][12] = { + "haunt", "daunt", "tickle", "gobble", "erase", "provoke", + "surprise", "ignore", "stare at", "shriek at", "frighten", "quieten" + }; + + _vm->_sound->stopSound(); + + getScreen(kScreenNagScreen); + byte nounId = _vm->_rnd->getRandomNumber(11); + byte verbId = _vm->_rnd->getRandomNumber(11); + Common::String result = Common::String::format("%s will %s you", nouns[nounId], verbs[verbId]); + putIn(result, 1628); + showScreen(); // No halt- it's already set up. +} + +} // End of namespace Avalanche. diff --git a/engines/avalanche/closing.h b/engines/avalanche/closing.h new file mode 100644 index 0000000000..25217e347e --- /dev/null +++ b/engines/avalanche/closing.h @@ -0,0 +1,62 @@ +/* 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 the original source code of Lord Avalot d'Argent version 1.3. + * Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman. + */ + +/* CLOSING The closing screen and error handler. */ + +#ifndef AVALANCHE_CLOSING_H +#define AVALANCHE_CLOSING_H + +#include "common/str.h" + +namespace Avalanche { +class AvalancheEngine; + +class Closing { +public: + Closing(AvalancheEngine *vm); + void exitGame(); + +private: + // Will be needed during implementation of Closing. + enum ScreenType { + kScreenBugAlert = 1, + kScreenRamCram = 2, + kScreenNagScreen = 3, + kScreenTwoCopies = 5 + }; + + AvalancheEngine *_vm; + + void getScreen(ScreenType which); + void showScreen(); + void putIn(Common::String str, uint16 where); + +}; + +} // End of namespace Avalanche. + +#endif // AVALANCHE_CLOSING_H diff --git a/engines/avalanche/console.cpp b/engines/avalanche/console.cpp new file mode 100644 index 0000000000..656cc1907c --- /dev/null +++ b/engines/avalanche/console.cpp @@ -0,0 +1,54 @@ +/* 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 the original source code of Lord Avalot d'Argent version 1.3. + * Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman. + */ + +#include "avalanche/console.h" +#include "avalanche/avalanche.h" + +namespace Avalanche { + +AvalancheConsole::AvalancheConsole(AvalancheEngine *vm) : GUI::Debugger(), _vm(vm) { + DCmd_Register("magic_lines", WRAP_METHOD(AvalancheConsole, Cmd_MagicLines)); +} + +AvalancheConsole::~AvalancheConsole() { +} + +/** + * This command loads up the specified new scene number + */ +bool AvalancheConsole::Cmd_MagicLines(int argc, const char **argv) { + if (argc != 1) { + DebugPrintf("Usage: %s\n", argv[0]); + return true; + } + + _vm->_showDebugLines = !_vm->_showDebugLines; + return false; +} + + +} // End of namespace Avalanche diff --git a/engines/avalanche/console.h b/engines/avalanche/console.h new file mode 100644 index 0000000000..166515d913 --- /dev/null +++ b/engines/avalanche/console.h @@ -0,0 +1,51 @@ +/* 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 the original source code of Lord Avalot d'Argent version 1.3. + * Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman. + */ + +#ifndef AVALANCHE_CONSOLE_H +#define AVALANCHE_CONSOLE_H + +#include "gui/debugger.h" + +namespace Avalanche { + +class AvalancheEngine; + +class AvalancheConsole : public GUI::Debugger { +public: + AvalancheConsole(AvalancheEngine *vm); + virtual ~AvalancheConsole(void); + +protected: + bool Cmd_MagicLines(int argc, const char **argv); + +private: + AvalancheEngine *_vm; +}; + +} // End of namespace Avalanche + +#endif // AVALANCHE_CONSOLE_H diff --git a/engines/avalanche/detection.cpp b/engines/avalanche/detection.cpp new file mode 100644 index 0000000000..428e71f35a --- /dev/null +++ b/engines/avalanche/detection.cpp @@ -0,0 +1,213 @@ +/* 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 the original source code of Lord Avalot d'Argent version 1.3. + * Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman. + */ + +#include "avalanche/avalanche.h" + +#include "common/system.h" +#include "common/savefile.h" + +#include "engines/advancedDetector.h" +#include "graphics/thumbnail.h" + +namespace Avalanche { + +struct AvalancheGameDescription { + ADGameDescription desc; +}; + +uint32 AvalancheEngine::getFeatures() const { + return _gameDescription->desc.flags; +} + +const char *AvalancheEngine::getGameId() const { + return _gameDescription->desc.gameid; +} + +static const PlainGameDescriptor avalancheGames[] = { + {"avalanche", "Lord Avalot d'Argent"}, + {0, 0} +}; + +static const ADGameDescription gameDescriptions[] = { + { + "avalanche", 0, + { + {"avalot.sez", 0, "de10eb353228013da3d3297784f81ff9", 48763}, + {"mainmenu.avd", 0, "89f31211af579a872045b175cc264298", 18880}, + AD_LISTEND + }, + Common::EN_ANY, + Common::kPlatformDOS, + ADGF_NO_FLAGS, + GUIO0() + }, + + AD_TABLE_END_MARKER +}; + +class AvalancheMetaEngine : public AdvancedMetaEngine { +public: + AvalancheMetaEngine() : AdvancedMetaEngine(gameDescriptions, sizeof(AvalancheGameDescription), avalancheGames) { + } + + const char *getName() const { + return "Avalanche"; + } + + const char *getOriginalCopyright() const { + return "Avalanche Engine Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman."; + } + + bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *gd) const; + bool hasFeature(MetaEngineFeature f) const; + + int getMaximumSaveSlot() const { return 99; } + SaveStateList listSaves(const char *target) const; + void removeSaveState(const char *target, int slot) const; + SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const; +}; + +bool AvalancheMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *gd) const { + if (gd) + *engine = new AvalancheEngine(syst, (const AvalancheGameDescription *)gd); + return gd != 0; +} + +bool AvalancheMetaEngine::hasFeature(MetaEngineFeature f) const { + return + (f == kSupportsListSaves) || + (f == kSupportsDeleteSave) || + (f == kSupportsLoadingDuringStartup) || + (f == kSavesSupportMetaInfo) || + (f == kSavesSupportThumbnail); +} + +SaveStateList AvalancheMetaEngine::listSaves(const char *target) const { + Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); + Common::StringArray filenames; + Common::String pattern = target; + pattern.toUppercase(); + 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) { + const Common::String &fname = *filename; + int slotNum = atoi(fname.c_str() + fname.size() - 3); + if (slotNum >= 0 && slotNum <= getMaximumSaveSlot()) { + Common::InSaveFile *file = saveFileMan->openForLoading(fname); + if (file) { + // Check for our signature. + uint32 signature = file->readUint32LE(); + if (signature != MKTAG('A', 'V', 'A', 'L')) { + warning("Savegame of incompatible type!"); + delete file; + continue; + } + + // Check version. + byte saveVersion = file->readByte(); + if (saveVersion != kSavegameVersion) { + warning("Savegame of incompatible version!"); + delete file; + continue; + } + + // Read name. + uint32 nameSize = file->readUint32LE(); + if (nameSize >= 255) { + delete file; + continue; + } + char *name = new char[nameSize + 1]; + file->read(name, nameSize); + name[nameSize] = 0; + + saveList.push_back(SaveStateDescriptor(slotNum, name)); + delete[] name; + delete file; + } + } + } + + return saveList; +} + +void AvalancheMetaEngine::removeSaveState(const char *target, int slot) const { + Common::String fileName = Common::String::format("%s.%03d", target, slot); + g_system->getSavefileManager()->removeSavefile(fileName); +} + +SaveStateDescriptor AvalancheMetaEngine::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) { + // Check for our signature. + uint32 signature = f->readUint32LE(); + if (signature != MKTAG('A', 'V', 'A', 'L')) { + warning("Savegame of incompatible type!"); + delete f; + return SaveStateDescriptor(); + } + + // Check version. + byte saveVersion = f->readByte(); + if (saveVersion > kSavegameVersion) { + warning("Savegame of a too recent version!"); + delete f; + return SaveStateDescriptor(); + } + + // Read the description. + uint32 descSize = f->readUint32LE(); + Common::String description; + for (uint32 i = 0; i < descSize; i++) { + char actChar = f->readByte(); + description += actChar; + } + + SaveStateDescriptor desc(slot, description); + + Graphics::Surface *const thumbnail = Graphics::loadThumbnail(*f); + desc.setThumbnail(thumbnail); + + delete f; + return desc; + } + return SaveStateDescriptor(); +} + +} // End of namespace Avalanche + +#if PLUGIN_ENABLED_DYNAMIC(AVALANCHE) + REGISTER_PLUGIN_DYNAMIC(AVALANCHE, PLUGIN_TYPE_ENGINE, Avalanche::AvalancheMetaEngine); +#else + REGISTER_PLUGIN_STATIC(AVALANCHE, PLUGIN_TYPE_ENGINE, Avalanche::AvalancheMetaEngine); +#endif diff --git a/engines/avalanche/dialogs.cpp b/engines/avalanche/dialogs.cpp new file mode 100644 index 0000000000..e5acd9cae2 --- /dev/null +++ b/engines/avalanche/dialogs.cpp @@ -0,0 +1,1196 @@ +/* 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 the original source code of Lord Avalot d'Argent version 1.3. + * Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman. + */ + + /* SCROLLS The scroll driver. */ + +#include "avalanche/avalanche.h" +#include "avalanche/dialogs.h" + +#include "common/random.h" + +namespace Avalanche { + +// A quasiped defines how people who aren't sprites talk. For example, quasiped +// "A" is Dogfood. The rooms aren't stored because I'm leaving that to context. +const QuasipedType Dialogs::kQuasipeds[16] = { + //_whichPed, _foregroundColor, _room, _backgroundColor, _who + {1, kColorLightgray, kRoomArgentPub, kColorBrown, kPeopleDogfood}, // A: Dogfood (screen 19). + {2, kColorGreen, kRoomArgentPub, kColorWhite, kPeopleIbythneth}, // B: Ibythneth (screen 19). + {2, kColorWhite, kRoomYours, kColorMagenta, kPeopleArkata}, // C: Arkata (screen 1). + {2, kColorBlack, kRoomLustiesRoom, kColorRed, kPeopleInvisible}, // D: Hawk (screen 23). + {2, kColorLightgreen, kRoomOutsideDucks, kColorBrown, kPeopleTrader}, // E: Trader (screen 50). + {5, kColorYellow, kRoomRobins, kColorRed, kPeopleAvalot}, // F: Avvy, tied up (scr.42) + {1, kColorBlue, kRoomAylesOffice, kColorWhite, kPeopleAyles}, // G: Ayles (screen 16). + {1, kColorBrown, kRoomMusicRoom, kColorWhite, kPeopleJacques}, // H: Jacques (screen 7). + {1, kColorLightgreen, kRoomNottsPub, kColorGreen, kPeopleSpurge}, // I: Spurge (screen 47). + {2, kColorYellow, kRoomNottsPub, kColorRed, kPeopleAvalot}, // J: Avalot (screen 47). + {1, kColorLightgray, kRoomLustiesRoom, kColorBlack, kPeopleDuLustie}, // K: du Lustie (screen 23). + {1, kColorYellow, kRoomOubliette, kColorRed, kPeopleAvalot}, // L: Avalot (screen 27). + {2, kColorWhite, kRoomOubliette, kColorRed, kPeopleInvisible}, // M: Avaroid (screen 27). + {3, kColorLightgray, kRoomArgentPub, kColorDarkgray, kPeopleMalagauche},// N: Malagauche (screen 19). + {4, kColorLightmagenta, kRoomNottsPub, kColorRed, kPeoplePort}, // O: Port (screen 47). + {1, kColorLightgreen, kRoomDucks, kColorDarkgray, kPeopleDrDuck} // P: Duck (screen 51). +}; + +Dialogs::Dialogs(AvalancheEngine *vm) { + _vm = vm; + _noError = true; +} + +void Dialogs::init() { + loadFont(); + resetScrollDriver(); +} + +/** + * Determine the color of the ready light and draw it + * @remarks Originally called 'state' + */ +void Dialogs::setReadyLight(byte state) { + if (_vm->_ledStatus == state) + return; // Already like that! + + Color color = kColorBlack; + switch (state) { + case 0: + color = kColorBlack; + break; // Off + case 1: + case 2: + case 3: + color = kColorGreen; + break; // Hit a key + } + warning("STUB: Dialogs::setReadyLight()"); + + CursorMan.showMouse(false); + _vm->_graphics->drawReadyLight(color); + CursorMan.showMouse(true); + _vm->_ledStatus = state; +} + +void Dialogs::easterEgg() { + warning("STUB: Scrolls::easterEgg()"); +} + +void Dialogs::say(int16 x, int16 y, Common::String z) { + FontType itw; + byte lz = z.size(); + + bool offset = x % 8 == 4; + x /= 8; + y++; + int16 i = 0; + for (int xx = 0; xx < lz; xx++) { + switch (z[xx]) { + case kControlRoman: + _currentFont = kFontStyleRoman; + break; + case kControlItalic: + _currentFont = kFontStyleItalic; + break; + default: { + for (int yy = 0; yy < 12; yy++) + itw[(byte)z[xx]][yy] = _fonts[_currentFont][(byte)z[xx]][yy + 2]; + + // We have to draw the characters one-by-one because of the accidental font changes. + i++; + Common::String chr(z[xx]); + _vm->_graphics->drawScrollText(chr, itw, 12, (x - 1) * 8 + offset * 4 + i * 8, y, kColorBlack); + } + } + } +} + +/** + * One of the 3 "Mode" functions passed as ScrollsFunctionType parameters. + * @remarks Originally called 'normscroll' + */ +void Dialogs::scrollModeNormal() { + // Original code is: + // egg : array[1..8] of char = ^P^L^U^G^H+'***'; + // this is not using kControl characters: it's the secret code to be entered to trigger the easter egg + // TODO: To be fixed when the Easter egg code is implemented + Common::String egg = Common::String::format("%c%c%c%c%c***", kControlParagraph, kControlLeftJustified, kControlNegative, kControlBell, kControlBackspace); + Common::String e = "(c) 1994"; + + setReadyLight(3); + _vm->_seeScroll = true; + _vm->_graphics->loadMouse(kCurFletch); + + _vm->_graphics->saveScreen(); + _vm->_graphics->showScroll(); + + Common::Event event; + bool escape = false; + while (!_vm->shouldQuit() && !escape) { + _vm->_graphics->refreshScreen(); + while (_vm->getEvent(event)) { + if ((event.type == Common::EVENT_LBUTTONUP) || + ((event.type == Common::EVENT_KEYDOWN) && ((event.kbd.keycode == Common::KEYCODE_ESCAPE) || + (event.kbd.keycode == Common::KEYCODE_RETURN) || (event.kbd.keycode == Common::KEYCODE_HASH) || + (event.kbd.keycode == Common::KEYCODE_PLUS)))) { + escape = true; + break; + } + } + } + + _vm->_graphics->restoreScreen(); + _vm->_graphics->removeBackup(); + + warning("STUB: scrollModeNormal() - Check Easter Egg trigger"); +#if 0 + char r; + bool oktoexit; + do { + do { + _vm->check(); // was "checkclick;" + +//#ifdef RECORD slowdown(); basher::count++; #endif + + if (_vm->_enhanced->keypressede()) + break; + } while (!((mrelease > 0) || (buttona1()) || (buttonb1()))); + + + if (mrelease == 0) { + inkey(); + if (aboutscroll) { + move(e[2 - 1], e[1 - 1], 7); + e[8 - 1] = inchar; + if (egg == e) + easteregg(); + } + oktoexit = set::of('\15', '\33', '+', '#', eos).has(inchar); + if (!oktoexit) errorled(); + } + + } while (!((oktoexit) || (mrelease > 0))); + +//#ifdef RECORD record_one(); #endif + + _vm->screturn = r == '#'; // "back door" +#endif + + setReadyLight(0); + _vm->_seeScroll = false; + _vm->_holdLeftMouse = false; // Used in Lucerna::checkclick(). + + warning("STUB: Scrolls::scrollModeNormal()"); +} + +/** + * One of the 3 "Mode" functions passed as ScrollsFunctionType parameters. + * The "asking" scroll. Used indirectly in diplayQuestion(). + * @remarks Originally called 'dialogue' + */ +void Dialogs::scrollModeDialogue() { + _vm->_graphics->loadMouse(kCurHand); + + _vm->_graphics->saveScreen(); + _vm->_graphics->showScroll(); + + Common::Event event; + while (!_vm->shouldQuit()) { + _vm->_graphics->refreshScreen(); + + _vm->getEvent(event); + + Common::Point cursorPos = _vm->getMousePos(); + cursorPos.y /= 2; + + char inChar = 0; + if ((event.type == Common::EVENT_KEYDOWN) && (event.kbd.ascii <= 122) && (event.kbd.ascii >= 97)) { + inChar = (char)event.kbd.ascii; + Common::String temp(inChar); + temp.toUppercase(); + inChar = temp[0]; + } + + if (_vm->shouldQuit() || (event.type == Common::EVENT_LBUTTONUP) || (event.type == Common::EVENT_KEYDOWN)) { + if (((cursorPos.x >= _shadowBoxX - 65) && (cursorPos.y >= _shadowBoxY - 24) && (cursorPos.x <= _shadowBoxX - 5) && (cursorPos.y <= _shadowBoxY - 10)) + || (inChar == 'Y') || (inChar == 'J') || (inChar == 'O')) { // Yes, Ja, Oui + _scReturn = true; + break; + } else if (((cursorPos.x >= _shadowBoxX + 5) && (cursorPos.y >= _shadowBoxY - 24) && (cursorPos.x <= _shadowBoxX + 65) && (cursorPos.y <= _shadowBoxY - 10)) + || (inChar == 'N')){ // No, Non, Nein + _scReturn = false; + break; + } + } + } + + _vm->_graphics->restoreScreen(); + _vm->_graphics->removeBackup(); +} + +void Dialogs::store(byte what, TuneType &played) { + memcpy(played, played + 1, sizeof(played) - 1); + played[30] = what; +} + +bool Dialogs::theyMatch(TuneType &played) { + byte mistakes = 0; + + for (unsigned int i = 0; i < sizeof(played); i++) { + if (played[i] != _vm->kTune[i]) + mistakes++; + } + + return mistakes < 5; +} + +/** + * One of the 3 "Mode" functions passed as ScrollsFunctionType parameters. + * Part of the harp mini-game. + * @remarks Originally called 'music_Scroll' + */ +void Dialogs::scrollModeMusic() { + setReadyLight(3); + _vm->_seeScroll = true; + CursorMan.showMouse(false); + _vm->_graphics->loadMouse(kCurFletch); + + TuneType played; + for (unsigned int i = 0; i < sizeof(played); i++) + played[i] = kPitchInvalid; + int8 lastOne = -1, thisOne = -1; // Invalid values. + + _vm->_seeScroll = true; + + _vm->_graphics->saveScreen(); + _vm->_graphics->showScroll(); + + Common::Event event; + while (!_vm->shouldQuit()) { + _vm->_graphics->refreshScreen(); + + _vm->getEvent(event); + + // When we stop playing? + if ((event.type == Common::EVENT_LBUTTONDOWN) || + ((event.type == Common::EVENT_KEYDOWN) && ((event.kbd.keycode == Common::KEYCODE_RETURN) || (event.kbd.keycode == Common::KEYCODE_ESCAPE)))) { + break; + } + + // When we DO play: + if ((event.type == Common::EVENT_KEYDOWN) + && ((event.kbd.keycode == Common::KEYCODE_q) || (event.kbd.keycode == Common::KEYCODE_w) + || (event.kbd.keycode == Common::KEYCODE_e) || (event.kbd.keycode == Common::KEYCODE_r) + || (event.kbd.keycode == Common::KEYCODE_t) || (event.kbd.keycode == Common::KEYCODE_y) + || (event.kbd.keycode == Common::KEYCODE_u) || (event.kbd.keycode == Common::KEYCODE_i) + || (event.kbd.keycode == Common::KEYCODE_o) || (event.kbd.keycode == Common::KEYCODE_p) + || (event.kbd.keycode == Common::KEYCODE_LEFTBRACKET) || (event.kbd.keycode == Common::KEYCODE_RIGHTBRACKET))) { + byte value; + switch (event.kbd.keycode) { + case Common::KEYCODE_q: + value = 0; + break; + case Common::KEYCODE_w: + value = 1; + break; + case Common::KEYCODE_e: + value = 2; + break; + case Common::KEYCODE_r: + value = 3; + break; + case Common::KEYCODE_t: + value = 4; + break; + case Common::KEYCODE_y: + value = 5; + break; + case Common::KEYCODE_u: + value = 6; + break; + case Common::KEYCODE_i: + value = 7; + break; + case Common::KEYCODE_o: + value = 8; + break; + case Common::KEYCODE_p: + value = 9; + break; + case Common::KEYCODE_LEFTBRACKET: + value = 10; + break; + case Common::KEYCODE_RIGHTBRACKET: + value = 11; + break; + default: + break; + } + + lastOne = thisOne; + thisOne = value; + + _vm->_sound->playNote(_vm->kNotes[thisOne], 100); + _vm->_system->delayMillis(200); + + if (!_vm->_bellsAreRinging) { // These handle playing the right tune. + if (thisOne < lastOne) + store(kPitchLower, played); + else if (thisOne == lastOne) + store(kPitchSame, played); + else + store(kPitchHigher, played); + } + + if (theyMatch(played)) { + setReadyLight(0); + _vm->_timer->addTimer(8, Timer::kProcJacquesWakesUp, Timer::kReasonJacquesWakingUp); + break; + } + } + } + + _vm->_graphics->restoreScreen(); + _vm->_graphics->removeBackup(); + + _vm->_seeScroll = false; + CursorMan.showMouse(true); +} + +void Dialogs::resetScrollDriver() { + _scrollBells = 0; + _currentFont = kFontStyleRoman; + _useIcon = 0; + _vm->_interrogation = 0; // Always reset after a scroll comes up. +} + +/** + * Rings the bell x times + * @remarks Originally called 'dingdongbell' + */ +void Dialogs::ringBell() { + for (int i = 0; i < _scrollBells; i++) + _vm->errorLed(); // Ring the bell "_scrollBells" times. +} + +/** + * This moves the mouse pointer off the scroll so that you can read it. + * @remarks Originally called 'dodgem' + */ +void Dialogs::dodgem() { + _dodgeCoord = _vm->getMousePos(); + g_system->warpMouse(_dodgeCoord.x, _underScroll); // Move the pointer off the scroll. +} + +/** + * This is the opposite of Dodgem. + * It moves the mouse pointer back, IF you haven't moved it in the meantime. + * @remarks Originally called 'undodgem' + */ +void Dialogs::unDodgem() { + Common::Point actCoord = _vm->getMousePos(); + if ((actCoord.x == _dodgeCoord.x) && (actCoord.y == _underScroll)) + g_system->warpMouse(_dodgeCoord.x, _dodgeCoord.y); // No change, so restore the pointer's original position. +} + +void Dialogs::drawScroll(DialogFunctionType modeFunc) { + int16 lx = 0; + int16 ly = (_maxLineNum + 1) * 6; + int16 ex; + for (int i = 0; i <= _maxLineNum; i++) { + ex = _scroll[i].size() * 8; + if (lx < ex) + lx = ex; + } + int16 mx = 320; + int16 my = 100; // Getmaxx & getmaxy div 2, both. + lx /= 2; + ly -= 2; + + if ((1 <= _useIcon) && (_useIcon <= 34)) + lx += kHalfIconWidth; + + CursorMan.showMouse(false); + _vm->_graphics->drawScroll(mx, lx, my, ly); + + mx -= lx; + my -= ly + 2; + + bool centre = false; + + byte iconIndent = 0; + switch (_useIcon) { + case 0: + iconIndent = 0; + break; // No icon. + case 34: + _vm->_graphics->drawSign("about", 28, 76, 15); + iconIndent = 0; + break; + case 35: + _vm->_graphics->drawSign("gameover", 52, 59, 71); + iconIndent = 0; + break; + } + + if ((1 <= _useIcon) && (_useIcon <= 33)) { // Standard icon. + _vm->_graphics->drawIcon(mx, my + ly / 2, _useIcon); + iconIndent = 53; + } + + for (int i = 0; i <= _maxLineNum; i++) { + if (!_scroll[i].empty()) + switch (_scroll[i][_scroll[i].size() - 1]) { + case kControlCenter: + centre = true; + _scroll[i].deleteLastChar(); + break; + case kControlLeftJustified: + centre = false; + _scroll[i].deleteLastChar(); + break; + case kControlQuestion: + _shadowBoxX = mx + lx; + _shadowBoxY = my + ly; + _scroll[i].setChar(' ', 0); + _vm->_graphics->drawShadowBox(_shadowBoxX - 65, _shadowBoxY - 24, _shadowBoxX - 5, _shadowBoxY - 10, "Yes."); + _vm->_graphics->drawShadowBox(_shadowBoxX + 5, _shadowBoxY - 24, _shadowBoxX + 65, _shadowBoxY - 10, "No."); + break; + } + + if (centre) + say(320 - _scroll[i].size() * 4 + iconIndent, my, _scroll[i]); + else + say(mx + iconIndent, my, _scroll[i]); + + my += 12; + } + + _underScroll = (my + 3) * 2; // Multiplying because of the doubled screen height. + ringBell(); + + _vm->_dropsOk = false; + dodgem(); + + (this->*modeFunc)(); + + unDodgem(); + _vm->_dropsOk = true; + + resetScrollDriver(); +} + +void Dialogs::drawBubble(DialogFunctionType modeFunc) { + Common::Point points[3]; + + CursorMan.showMouse(false); + int16 xl = 0; + int16 yl = (_maxLineNum + 1) * 5; + for (int i = 0; i <= _maxLineNum; i++) { + uint16 textWidth = _scroll[i].size() * 8; + if (textWidth > xl) + xl = textWidth; + } + xl /= 2; + + int16 xw = xl + 18; + int16 yw = yl + 7; + int16 my = yw * 2 - 2; + int16 xc = 0; + + if (_talkX - xw < 0) + xc = -(_talkX - xw); + if (_talkX + xw > 639) + xc = 639 - (_talkX + xw); + + // Compute triangle coords for the tail of the bubble + points[0].x = _talkX - 10; + points[0].y = yw; + points[1].x = _talkX + 10; + points[1].y = yw; + points[2].x = _talkX; + points[2].y = _talkY; + + _vm->_graphics->prepareBubble(xc, xw, my, points); + + // Draw the text of the bubble. The centering of the text was improved here compared to Pascal's settextjustify(). + // The font is not the same that outtextxy() uses in Pascal. I don't have that, so I used characters instead. + // It's almost the same, only notable differences are '?', '!', etc. + for (int i = 0; i <= _maxLineNum; i++) { + int16 x = xc + _talkX - _scroll[i].size() / 2 * 8; + bool offset = _scroll[i].size() % 2; + _vm->_graphics->drawScrollText(_scroll[i], _vm->_font, 8, x - offset * 4, (i * 10) + 12, _vm->_graphics->_talkFontColor); + } + + ringBell(); + CursorMan.showMouse(false); + _vm->_dropsOk = false; + + // This does the actual drawing to the screen. + (this->*modeFunc)(); + + _vm->_dropsOk = true; + CursorMan.showMouse(true); // sink; + resetScrollDriver(); +} + +void Dialogs::reset() { + _maxLineNum = 0; + for (int i = 0; i < 15; i++) { + if (!_scroll[i].empty()) + _scroll[i].clear(); + } +} + +/** + * Natural state of bubbles + * @remarks Originally called 'natural' + */ +void Dialogs::setBubbleStateNatural() { + _talkX = 320; + _talkY = 200; + _vm->_graphics->setDialogColor(kColorDarkgray, kColorWhite); +} + +Common::String Dialogs::displayMoney() { + Common::String result; + + if (_vm->_money < 12) { // just pence + result = Common::String::format("%dd", _vm->_money); + } else if (_vm->_money < 240) { // shillings & pence + if ((_vm->_money % 12) == 0) + result = Common::String::format("%d/-", _vm->_money / 12); + else + result = Common::String::format("%d/%d", _vm->_money / 12, _vm->_money % 12); + } else { // L, s & d + result = Common::String::format("\x9C%d.%d.%d", _vm->_money / 240, (_vm->_money / 12) % 20, + _vm->_money % 12); + } + if (_vm->_money > 12) { + Common::String extraStr = Common::String::format(" (that's %dd)", _vm->_money); + result += extraStr; + } + + return result; +} + +/** + * Strip trailing character in a string + * @remarks Originally called 'strip' + */ +void Dialogs::stripTrailingSpaces(Common::String &str) { + while (str.lastChar() == ' ') + str.deleteLastChar(); + // We don't use String::trim() here because we need the leading whitespaces. +} + +/** + * Does the word wrapping. + */ +void Dialogs::solidify(byte n) { + if (!_scroll[n].contains(' ')) + return; // No spaces. + + // So there MUST be a space there, somewhere... + do { + _scroll[n + 1] = _scroll[n][_scroll[n].size() - 1] + _scroll[n + 1]; + _scroll[n].deleteLastChar(); + } while (_scroll[n][_scroll[n].size() - 1] != ' '); + + stripTrailingSpaces(_scroll[n]); +} + +/** + * @remarks Originally called 'calldriver' + * Display text by calling the dialog driver. It unifies the function of the original + * 'calldriver' and 'display' by using Common::String instead of a private buffer. + */ +void Dialogs::displayText(Common::String text) { +// bool was_virtual; // Was the mouse cursor virtual on entry to this proc? + warning("STUB: Scrolls::calldrivers()"); + + _vm->_sound->stopSound(); + + setReadyLight(0); + _scReturn = false; + bool mouthnext = false; + bool callSpriteRun = true; // Only call sprite_run the FIRST time. + + switch (text.lastChar()) { + case kControlToBuffer: + text.deleteLastChar(); + break; // ^D = (D)on't include pagebreak + case kControlSpeechBubble: + case kControlQuestion: + break; // ^B = speech (B)ubble, ^Q = (Q)uestion in dialogue box + default: + text.insertChar(kControlParagraph, text.size()); + } + + for (uint16 i = 0; i < text.size(); i++) { + if (mouthnext) { + if (text[i] == kControlRegister) + _param = 0; + else if (('0' <= text[i]) && (text[i] <= '9')) + _param = text[i] - 48; + else if (('A' <= text[i]) && (text[i] <= 'Z')) + _param = text[i] - 55; + + mouthnext = false; + } else { + switch (text[i]) { + case kControlParagraph: + if ((_maxLineNum == 0) && (_scroll[0].empty())) + break; + + if (callSpriteRun) + _vm->spriteRun(); + callSpriteRun = false; + + drawScroll(&Avalanche::Dialogs::scrollModeNormal); + + reset(); + + if (_scReturn) + return; + break; + case kControlBell: + _scrollBells++; + break; + case kControlSpeechBubble: + if ((_maxLineNum == 0) && (_scroll[0].empty())) + break; + + if (callSpriteRun) + _vm->spriteRun(); + callSpriteRun = false; + + if (_param == 0) + setBubbleStateNatural(); + else if ((1 <= _param) && (_param <= 9)) { + AnimationType *spr = _vm->_animation->_sprites[_param - 1]; + if ((_param > _vm->_animation->kSpriteNumbMax) || (!spr->_quick)) { // Not valid. + _vm->errorLed(); + setBubbleStateNatural(); + } else + spr->chatter(); // Normal sprite talking routine. + } else if ((10 <= _param) && (_param <= 36)) { + // Quasi-peds. (This routine performs the same + // thing with QPs as triptype.chatter does with the + // sprites.) + PedType *quasiPed = &_vm->_peds[kQuasipeds[_param - 10]._whichPed]; + _talkX = quasiPed->_x; + _talkY = quasiPed->_y; // Position. + + _vm->_graphics->setDialogColor(kQuasipeds[_param - 10]._backgroundColor, kQuasipeds[_param - 10]._textColor); + } else { + _vm->errorLed(); // Not valid. + setBubbleStateNatural(); + } + + drawBubble(&Avalanche::Dialogs::scrollModeNormal); + + reset(); + + if (_scReturn) + return; + break; + + // CHECME: The whole kControlNegative block seems completely unused, as the only use (the easter egg check) is a false positive + case kControlNegative: + switch (_param) { + case 1: + displayText(displayMoney() + kControlToBuffer); // Insert cash balance. (Recursion) + break; + case 2: { + int pwdId = _vm->_parser->kFirstPassword + _vm->_passwordNum; + displayText(_vm->_parser->_vocabulary[pwdId]._word + kControlToBuffer); + } + break; + case 3: + displayText(_vm->_favouriteDrink + kControlToBuffer); + break; + case 4: + displayText(_vm->_favouriteSong + kControlToBuffer); + break; + case 5: + displayText(_vm->_worstPlaceOnEarth + kControlToBuffer); + break; + case 6: + displayText(_vm->_spareEvening + kControlToBuffer); + break; + case 9: { + Common::String tmpStr = Common::String::format("%d,%d%c",_vm->_catacombX, _vm->_catacombY, kControlToBuffer); + displayText(tmpStr); + } + break; + case 10: + switch (_vm->_boxContent) { + case 0: // Sixpence. + displayScrollChain('q', 37); // You find the sixpence. + _vm->_money += 6; + _vm->_boxContent = _vm->_parser->kNothing; + _vm->incScore(2); + return; + case Parser::kNothing: + displayText("nothing at all. It's completely empty."); + break; + default: + displayText(_vm->getItem(_vm->_boxContent) + '.'); + } + break; + case 11: + for (int j = 0; j < kObjectNum; j++) { + if (_vm->_objects[j]) + displayText(_vm->getItem(j) + ", " + kControlToBuffer); + } + break; + } + break; + case kControlIcon: + _useIcon = _param; + break; + case kControlNewLine: + _maxLineNum++; + break; + case kControlQuestion: + if (callSpriteRun) + _vm->spriteRun(); + callSpriteRun = false; + + _maxLineNum++; + _scroll[_maxLineNum] = kControlQuestion; + + drawScroll(&Avalanche::Dialogs::scrollModeDialogue); + reset(); + break; + case kControlRegister: + mouthnext = true; + break; + case kControlInsertSpaces: + for (int j = 0; j < 9; j++) + _scroll[_maxLineNum] += ' '; + break; + default: // Add new char. + if (_scroll[_maxLineNum].size() == 50) { + solidify(_maxLineNum); + _maxLineNum++; + } + _scroll[_maxLineNum] += text[i]; + break; + } + } + } +} + +void Dialogs::setTalkPos(int16 x, int16 y) { + _talkX = x; + _talkY = y; +} + +int16 Dialogs::getTalkPosX() { + return _talkX; +} + +bool Dialogs::displayQuestion(Common::String question) { + displayText(question + kControlNewLine + kControlQuestion); + + if (_scReturn && (_vm->_rnd->getRandomNumber(1) == 0)) { // Half-and-half chance. + Common::String tmpStr = Common::String::format("...Positive about that?%cI%c%c%c", kControlRegister, kControlIcon, kControlNewLine, kControlQuestion); + displayText(tmpStr); // Be annoying! + if (_scReturn && (_vm->_rnd->getRandomNumber(3) == 3)) { // Another 25% chance + // \? are used to avoid that ??! is parsed as a trigraph + tmpStr = Common::String::format("%c100%% certain\?\?!%c%c%c%c", kControlInsertSpaces, kControlInsertSpaces, kControlIcon, kControlNewLine, kControlQuestion); + displayText(tmpStr); // Be very annoying! + } + } + + return _scReturn; +} + +void Dialogs::loadFont() { + Common::File file; + + if (!file.open("avalot.fnt")) + error("AVALANCHE: Scrolls: File not found: avalot.fnt"); + + for (int16 i = 0; i < 256; i++) + file.read(_fonts[0][i], 16); + file.close(); + + if (!file.open("avitalic.fnt")) + error("AVALANCHE: Scrolls: File not found: avitalic.fnt"); + + for (int16 i = 0; i < 256; i++) + file.read(_fonts[1][i], 16); + file.close(); + + if (!file.open("ttsmall.fnt")) + error("AVALANCHE: Scrolls: File not found: ttsmall.fnt"); + + for (int16 i = 0; i < 256; i++) + file.read(_vm->_font[i],16); + file.close(); +} + +/** + * Practically this one is a mini-game which called when you play the harp in the monastery. + * @remarks Originally called 'musical_scroll' + */ +void Dialogs::displayMusicalScroll() { + Common::String tmpStr = Common::String::format("To play the harp...%c%cUse these keys:%c%cQ W E R T Y U I O P [ ]%c%cOr press Enter to stop playing.%c", + kControlNewLine, kControlNewLine, kControlNewLine, kControlInsertSpaces, kControlNewLine, kControlNewLine, kControlToBuffer); + displayText(tmpStr); + + _vm->spriteRun(); + CursorMan.showMouse(false); + drawScroll(&Avalanche::Dialogs::scrollModeMusic); + CursorMan.showMouse(true); + reset(); +} + +void Dialogs::unSkrimble(Common::String &text) { + for (uint16 i = 0; i < text.size(); i++) + text.setChar((~(text[i] - (i + 1))) % 256, i); +} + +void Dialogs::doTheBubble(Common::String &text) { + text.insertChar(kControlSpeechBubble, text.size()); + assert(text.size() < 2000); +} + +/** + * Display a string in a scroll + * @remarks Originally called 'dixi' + */ +void Dialogs::displayScrollChain(char block, byte point, bool report, bool bubbling) { + Common::File indexfile; + if (!indexfile.open("avalot.idx")) + error("AVALANCHE: Visa: File not found: avalot.idx"); + + bool error = false; + + indexfile.seek((toupper(block) - 65) * 2); + uint16 idx_offset = indexfile.readUint16LE(); + if (idx_offset == 0) + error = true; + + indexfile.seek(idx_offset + point * 2); + uint16 sez_offset = indexfile.readUint16LE(); + if (sez_offset == 0) + error = true; + + indexfile.close(); + + _noError = !error; + + if (error) { + if (report) { + Common::String todisplay = Common::String::format("%cError accessing scroll %c%d", kControlBell, block, point); + displayText(todisplay); + } + return; + } + + Common::File sezfile; + if (!sezfile.open("avalot.sez")) + ::error("AVALANCHE: Visa: File not found: avalot.sez"); + + sezfile.seek(sez_offset); + uint16 _bufSize = sezfile.readUint16LE(); + assert(_bufSize < 2000); + char *_buffer = new char[_bufSize]; + sezfile.read(_buffer, _bufSize); + sezfile.close(); + Common::String text(_buffer, _bufSize); + delete[] _buffer; + + unSkrimble(text); + if (bubbling) + doTheBubble(text); + displayText(text); +} + +/** + * Start speech + * @remarks Originally called 'speech' + */ +void Dialogs::speak(byte who, byte subject) { + if (subject == 0) { // No subject. + displayScrollChain('s', who, false, true); + return; + } + + // Subject given. + _noError = false; // Assume that until we know otherwise. + + Common::File indexfile; + if (!indexfile.open("converse.avd")) + error("AVALANCHE: Visa: File not found: converse.avd"); + + indexfile.seek(who * 2 - 2); + uint16 idx_offset = indexfile.readUint16LE(); + uint16 next_idx_offset = indexfile.readUint16LE(); + + if ((idx_offset == 0) || ((((next_idx_offset - idx_offset) / 2) - 1) < subject)) + return; + + indexfile.seek(idx_offset + subject * 2); + uint16 sezOffset = indexfile.readUint16LE(); + if ((sezOffset == 0) || (indexfile.err())) + return; + indexfile.close(); + + Common::File sezfile; + if (!sezfile.open("avalot.sez")) + error("AVALANCHE: Visa: File not found: avalot.sez"); + + sezfile.seek(sezOffset); + uint16 _bufSize = sezfile.readUint16LE(); + assert(_bufSize < 2000); + char *_buffer = new char[_bufSize]; + sezfile.read(_buffer, _bufSize); + sezfile.close(); + Common::String text(_buffer, _bufSize); + delete[] _buffer; + + unSkrimble(text); + doTheBubble(text); + displayText(text); + + _noError = true; +} + +void Dialogs::talkTo(byte whom) { + if (_vm->_parser->_person == kPeoplePardon) { + _vm->_parser->_person = (People)_vm->_subjectNum; + _vm->_subjectNum = 0; + } + + if (_vm->_subjectNum == 0) { + switch (whom) { + case kPeopleSpludwick: + if ((_vm->_lustieIsAsleep) & (!_vm->_objects[kObjectPotion - 1])) { + displayScrollChain('q', 68); + _vm->_objects[kObjectPotion - 1] = true; + _vm->refreshObjectList(); + _vm->incScore(3); + return; + } else if (_vm->_talkedToCrapulus) { + // Spludwick - what does he need? + // 0 - let it through to use normal routine. + switch (_vm->_givenToSpludwick) { + case 1: // Fallthrough is intended. + case 2: { + Common::String objStr = _vm->getItem(AvalancheEngine::kSpludwicksOrder[_vm->_givenToSpludwick]); + Common::String tmpStr = Common::String::format("Can you get me %s, please?%c2%c", + objStr.c_str(), kControlRegister, kControlSpeechBubble); + displayText(tmpStr); + } + return; + case 3: + displayScrollChain('q', 30); // Need any help with the game? + return; + } + } else { + displayScrollChain('q', 42); // Haven't talked to Crapulus. Go and talk to him. + return; + } + break; + case kPeopleIbythneth: + if (_vm->_givenBadgeToIby) { + displayScrollChain('q', 33); // Thanks a lot! + return; // And leave the proc. + } + break; // Or... just continue, 'cos he hasn't got it. + case kPeopleDogfood: + if (_vm->_wonNim) { // We've won the game. + displayScrollChain('q', 6); // "I'm Not Playing!" + return; // Zap back. + } else + _vm->_askedDogfoodAboutNim = true; + break; + case kPeopleAyles: + if (!_vm->_aylesIsAwake) { + displayScrollChain('q', 43); // He's fast asleep! + return; + } else if (!_vm->_givenPenToAyles) { + displayScrollChain('q', 44); // Can you get me a pen, Avvy? + return; + } + break; + + case kPeopleJacques: + displayScrollChain('q', 43); + return; + + case kPeopleGeida: + if (_vm->_givenPotionToGeida) + _vm->_geidaFollows = true; + else { + displayScrollChain('u', 17); + return; + } + break; + case kPeopleSpurge: + if (!_vm->_sittingInPub) { + displayScrollChain('q', 71); // Try going over and sitting down. + return; + } else { + if (_vm->_spurgeTalkCount < 5) + _vm->_spurgeTalkCount++; + if (_vm->_spurgeTalkCount > 1) { // no. 1 falls through + displayScrollChain('q', 70 + _vm->_spurgeTalkCount); + return; + } + } + break; + } + // On a subject. Is there any reason to block it? + } else if ((whom == kPeopleAyles) && (!_vm->_aylesIsAwake)) { + displayScrollChain('q', 43); // He's fast asleep! + return; + } + + if (whom > 149) + whom -= 149; + + bool noMatches = true; + for (int i = 0; i < _vm->_animation->kSpriteNumbMax; i++) { + if (_vm->_animation->_sprites[i]->_characterId == whom) { + Common::String tmpStr = Common::String::format("%c%c%c", kControlRegister, i + 49, kControlToBuffer); + displayText(tmpStr); + noMatches = false; + break; + } + } + + if (noMatches) { + Common::String tmpStr = Common::String::format("%c%c%c", kControlRegister, kControlRegister, kControlToBuffer); + displayText(tmpStr); + } + + speak(whom, _vm->_subjectNum); + + if (!_noError) + displayScrollChain('n', whom); // File not found! + + if ((_vm->_subjectNum == 0) && ((whom + 149) == kPeopleCrapulus)) { // Crapulus: get the badge - first time only + _vm->_objects[kObjectBadge - 1] = true; + _vm->refreshObjectList(); + displayScrollChain('q', 1); // Circular from Cardiff. + _vm->_talkedToCrapulus = true; + _vm->setRoom(kPeopleCrapulus, kRoomDummy); // Crapulus walks off. + + AnimationType *spr = _vm->_animation->_sprites[1]; + spr->_vanishIfStill = true; + spr->walkTo(2); // Walks away. + + _vm->incScore(2); + } +} + +/** + * This makes Avalot say the response. + * @remarks Originally called 'sayit' + */ +void Dialogs::sayIt(Common::String str) { + Common::String x = str; + x.setChar(toupper(x[0]), 0); + Common::String tmpStr = Common::String::format("%c1%s.%c%c2", kControlRegister, x.c_str(), kControlSpeechBubble, kControlRegister); + displayText(tmpStr); +} + +Common::String Dialogs::personSpeaks() { + if ((_vm->_parser->_person == kPeoplePardon) || (_vm->_parser->_person == kPeopleNone)) { + if ((_vm->_him == kPeoplePardon) || (_vm->getRoom(_vm->_him) != _vm->_room)) + _vm->_parser->_person = _vm->_her; + else + _vm->_parser->_person = _vm->_him; + } + + if (_vm->getRoom(_vm->_parser->_person) != _vm->_room) { + return Common::String::format("%c1", kControlRegister); // Avvy himself! + } + + bool found = false; // The _person we're looking for's code is in _person. + Common::String tmpStr; + + for (int i = 0; i < _vm->_animation->kSpriteNumbMax; i++) { + AnimationType *curSpr = _vm->_animation->_sprites[i]; + if (curSpr->_quick && (curSpr->_characterId + 149 == _vm->_parser->_person)) { + tmpStr += Common::String::format("%c%c", kControlRegister, '1' + i); + found = true; + } + } + + if (found) + return tmpStr; + + for (int i = 0; i < 16; i++) { + if ((kQuasipeds[i]._who == _vm->_parser->_person) && (kQuasipeds[i]._room == _vm->_room)) + tmpStr += Common::String::format("%c%c", kControlRegister, 'A' + i); + } + + return tmpStr; +} + +/** + * Display a message when (uselessly) giving an object away + * @remarks Originally called 'heythanks' + */ +void Dialogs::sayThanks(byte thing) { + Common::String tmpStr = personSpeaks(); + tmpStr += Common::String::format("Hey, thanks!%c(But now, you've lost it!)", kControlSpeechBubble); + displayText(tmpStr); + _vm->_objects[thing] = false; +} + +/** + * Display a 'Hello' message + */ +void Dialogs::sayHello() { + Common::String tmpStr = personSpeaks(); + tmpStr += Common::String::format("Hello.%c", kControlSpeechBubble); + displayText(tmpStr); +} + +/** + * Display a 'OK' message + */ +void Dialogs::sayOK() { + Common::String tmpStr = personSpeaks(); + tmpStr += Common::String::format("That's OK.%c", kControlSpeechBubble); + displayText(tmpStr); +} + +/** + * Display a 'Silly' message + * @remarks Originally called 'silly' + */ +void Dialogs::saySilly() { + displayText("Don't be silly!"); +} + +} // End of namespace Avalanche diff --git a/engines/avalanche/dialogs.h b/engines/avalanche/dialogs.h new file mode 100644 index 0000000000..43e6a4fec6 --- /dev/null +++ b/engines/avalanche/dialogs.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 the original source code of Lord Avalot d'Argent version 1.3. + * Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman. + */ + + /* SCROLLS The scroll driver. */ + +#ifndef AVALANCHE_DIALOGS_H +#define AVALANCHE_DIALOGS_H + +namespace Avalanche { +class AvalancheEngine; + +class Dialogs; + +typedef void (Dialogs::*DialogFunctionType)(); + +class Dialogs { +public: + bool _aboutBox; // Is this the about box? - Used in scrollModeNormal(), not yet fully implemented + FontType _fonts[2]; + + Dialogs(AvalancheEngine *vm); + + void init(); + void reset(); + void setReadyLight(byte state); + void displayText(Common::String text); + bool displayQuestion(Common::String question); + void setTalkPos(int16 x, int16 y); + int16 getTalkPosX(); + void setBubbleStateNatural(); + void displayMusicalScroll(); + void displayScrollChain(char block, byte point, bool report = true, bool bubbling = false); + void talkTo(byte whom); + void sayIt(Common::String str); + Common::String personSpeaks(); + void sayThanks(byte thing); + void sayHello(); + void sayOK(); + void saySilly(); +private: + AvalancheEngine *_vm; + int16 _talkX, _talkY; + + enum FontStyle { + kFontStyleRoman, + kFontStyleItalic + }; + + static const int16 kHalfIconWidth = 19; + static const QuasipedType kQuasipeds[16]; + + Common::String _scroll[15]; + Common::Point _dodgeCoord; + byte _maxLineNum; + bool _scReturn; + bool _noError; + byte _currentFont; + byte _param; // For using arguments code + byte _useIcon; + byte _scrollBells; // no. of times to ring the bell + int16 _underScroll; // Y-coord of just under the scroll text. + int16 _shadowBoxX, _shadowBoxY; + + void drawBubble(DialogFunctionType modeFunc); + void drawScroll(DialogFunctionType modeFunc); + void scrollModeNormal(); + void scrollModeDialogue(); + void scrollModeMusic(); + + // These 2 are used only in musicalScroll(). + void store(byte what, TuneType &played); + bool theyMatch(TuneType &played); + void stripTrailingSpaces(Common::String &str); + void solidify(byte n); + void dodgem(); + void unDodgem(); + + Common::String displayMoney(); + void easterEgg(); + void say(int16 x, int16 y, Common::String text); + void resetScrollDriver(); + void ringBell(); + void loadFont(); + + void unSkrimble(Common::String &text); + void doTheBubble(Common::String &text); + void speak(byte who, byte subject); +}; + +} // End of namespace Avalanche + +#endif // AVALANCHE_DIALOGS_H diff --git a/engines/avalanche/enums.h b/engines/avalanche/enums.h new file mode 100644 index 0000000000..604c62de84 --- /dev/null +++ b/engines/avalanche/enums.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 the original source code of Lord Avalot d'Argent version 1.3. + * Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman. + */ + +#ifndef AVALANCHE_ENUMS_H +#define AVALANCHE_ENUMS_H + +namespace Avalanche { +enum Color { + kColorBlack = 0, kColorBlue, kColorGreen, kColorCyan, kColorRed, + kColorMagenta = 5, kColorBrown, kColorLightgray, kColorDarkgray, kColorLightblue, + kColorLightgreen = 10, kColorLightcyan, kColorLightred, kColorLightmagenta, kColorYellow, + kColorWhite = 15 +}; + +// CHECKME: kRoomBossKey is a guess +enum Room { + kRoomNowhere = 0, kRoomYours = 1, kRoomOutsideYours = 2, kRoomOutsideSpludwicks = 3, + kRoomYourHall = 5, kRoomMusicRoom = 7, kRoomOutsideArgentPub = 9, kRoomArgentRoad = 10, + kRoomWiseWomans = 11, kRoomSpludwicks = 12, kRoomInsideAbbey = 13, kRoomOutsideAbbey = 14, + kRoomAvvysGarden = 15, kRoomAylesOffice = 16, kRoomArgentPub = 19, kRoomBrummieRoad = 20, + kRoomBridge = 21, kRoomLusties = 22, kRoomLustiesRoom = 23, kRoomWestHall = 25, + kRoomEastHall = 26, kRoomOubliette = 27, kRoomGeidas = 28, kRoomCatacombs = 29, + kRoomEntranceHall = 40, kRoomRobins = 42, kRoomOutsideNottsPub = 46, kRoomNottsPub = 47, + kRoomOutsideDucks = 50, kRoomDucks = 51, kRoomOutsideCardiffCastle = 70, kRoomInsideCardiffCastle = 71, + kRoomBossKey = 98, kRoomMap = 99, kRoomDummy = 177 // Dummy room +}; + +// Objects you can hold: +enum Object { + kObjectWine = 1, kObjectMoney, kObjectBodkin, kObjectPotion, kObjectChastity, + kObjectBolt, kObjectCrossbow, kObjectLute, kObjectBadge, kObjectMushroom, + kObjectKey, kObjectBell, kObjectPrescription, kObjectPen, kObjectInk, + kObjectClothes, kObjectHabit, kObjectOnion, kObjectDummy = 177 +}; + +// People who hang around this game. +enum People { + // Boys: + kPeopleAvalot = 150, kPeopleSpludwick = 151, kPeopleCrapulus = 152, kPeopleDrDuck = 153, + kPeopleMalagauche = 154, kPeopleFriarTuck = 155, kPeopleRobinHood = 156, kPeopleCwytalot = 157, + kPeopleDuLustie = 158, kPeopleDuke = 159, kPeopleDogfood = 160, kPeopleTrader = 161, + kPeopleIbythneth = 162, kPeopleAyles = 163, kPeoplePort = 164, kPeopleSpurge = 165, + kPeopleJacques = 166, + // Girls: + kPeopleArkata = 175, kPeopleGeida = 176, kPeopleInvisible = 177, kPeopleWisewoman = 178, + // + kPeoplePardon = 254, kPeopleNone = 0 +}; + +enum VerbCode { + kVerbCodeExam = 1, kVerbCodeOpen = 2, kVerbCodePause = 3, kVerbCodeGet = 4, kVerbCodeDrop = 5, + kVerbCodeInv = 6, kVerbCodeTalk = 7, kVerbCodeGive = 8, kVerbCodeDrink = 9, kVerbCodeLoad = 10, + kVerbCodeSave = 11, kVerbCodePay = 12, kVerbCodeLook = 13, kVerbCodeBreak = 14, kVerbCodeQuit = 15, + kVerbCodeSit = 16, kVerbCodeStand = 17, kVerbCodeGo = 18, kVerbCodeInfo = 19, kVerbCodeUndress = 20, + kVerbCodeWear = 21, kVerbCodePlay = 22, kVerbCodeRing = 23, kVerbCodeHelp = 24, kVerbCodeLarrypass = 25, + kVerbCodePhaon = 26, kVerbCodeBoss = 27, kVerbCodePee = 28, kVerbCodeCheat = 29, kVerbCodeMagic = 30, + kVerbCodeRestart = 31, kVerbCodeEat = 32, kVerbCodeListen = 33, kVerbCodeBuy = 34, kVerbCodeAttack = 35, + kVerbCodePasswd = 36, kVerbCodeDir = 37, kVerbCodeDie = 38, kVerbCodeScore = 39, kVerbCodePut = 40, + kVerbCodeKiss = 41, kVerbCodeClimb = 42, kVerbCodeJump = 43, kVerbCodeHiscores = 44, kVerbCodeWake = 45, + kVerbCodeHello = 46, kVerbCodeThanks = 47, + kVerbCodeSmartAlec = 249, kVerbCodeExpletive = 253, kVerbCodePardon = 254 +}; + +enum MouseCursor { + kCurUpArrow = 0, kCurScrewDriver = 1, kCurRightArrow = 2, kCurFletch = 3, kCurWait = 4, kCurHand = 5, + kCurCrosshair = 6, kCurIBeam = 7 +}; + +// Magic/portal constants: +enum Magics { + kMagicNothing, // Ignore it if this line is touched. + kMagicBounce, // Bounce off this line. Not valid for portals. + kMagicExclaim, // Put up a chain of scrolls. + kMagicTransport, // Enter new room. + kMagicUnfinished, // Unfinished connection. + kMagicSpecial, // Special function. + kMagicOpenDoor // Opening door. +}; + +// Constants to replace the command characters from Pascal. +// For more information, see: https://github.com/urukgit/avalot/wiki/Scrolldrivers +enum ControlCharacter { + kControlSpeechBubble = 2, // ^B + kControlCenter = 3, // ^C + kControlToBuffer = 4, // ^D + kControlItalic = 6, // ^F + kControlBell = 7, // ^G + kControlBackspace = 8, // ^H + kControlInsertSpaces = 9, // ^I + kControlLeftJustified = 12, // ^L + kControlNewLine = 13, // ^M + kControlParagraph = 16, // ^P + kControlQuestion = 17, // ^Q + kControlRoman = 18, // ^R + kControlRegister = 19, // ^S + kControlNegative = 21, // ^U + kControlIcon = 22 // ^V +}; + +static const int16 kScreenWidth = 640; +static const int16 kScreenHeight = 200; + +static const int16 kWalk = 3; +static const int16 kRun = 5; + + +} // End of namespace Avalanche + +#endif // AVALANCHE_ENUMS_H diff --git a/engines/avalanche/graphics.cpp b/engines/avalanche/graphics.cpp new file mode 100644 index 0000000000..25b01d65f3 --- /dev/null +++ b/engines/avalanche/graphics.cpp @@ -0,0 +1,767 @@ +/* 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 the original source code of Lord Avalot d'Argent version 1.3. + * Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman. + */ + +#include "avalanche/avalanche.h" +#include "avalanche/graphics.h" + +#include "engines/util.h" +#include "graphics/palette.h" + +namespace Avalanche { + +const byte GraphicManager::kEgaPaletteIndex[16] = {0, 1, 2, 3, 4, 5, 20, 7, 56, 57, 58, 59, 60, 61, 62, 63}; + +const MouseHotspotType GraphicManager::kMouseHotSpots[9] = { + {8,0}, // 0 - up-arrow + {0,0}, // 1 - screwdriver + {15,6}, // 2 - right-arrow + {0,0}, // 3 - fletch + {8,7}, // 4 - hourglass + {4,0}, // 5 - TTHand + {8,5}, // 6 - Mark's crosshairs + {8,7}, // 7 - I-beam + {0,0} // 8 - question mark +}; + +GraphicManager::GraphicManager(AvalancheEngine *vm) { + _vm = vm; +} + +GraphicManager::~GraphicManager() { + _surface.free(); + _magics.free(); + _background.free(); + _screen.free(); + _scrolls.free(); + _backup.free(); + + for (int i = 0; i < 10; i++) + _digits[i].free(); + for (int i = 0; i < 9; i++) + _directions[i].free(); +} + +void GraphicManager::init() { + initGraphics(kScreenWidth, kScreenHeight * 2, true); // Doubling the height. + + for (int i = 0; i < 64; ++i) { + _egaPalette[i][0] = (i >> 2 & 1) * 0xaa + (i >> 5 & 1) * 0x55; + _egaPalette[i][1] = (i >> 1 & 1) * 0xaa + (i >> 4 & 1) * 0x55; + _egaPalette[i][2] = (i & 1) * 0xaa + (i >> 3 & 1) * 0x55; + } + + for (int i = 0; i < 16; i++) + g_system->getPaletteManager()->setPalette(_egaPalette[kEgaPaletteIndex[i]], i, 1); + + // Set the "flesh colors": + g_system->getPaletteManager()->setPalette(_egaPalette[39], 13, 1); + g_system->getPaletteManager()->setPalette(_egaPalette[28], 5, 1); + + _surface.create(kScreenWidth, kScreenHeight, Graphics::PixelFormat::createFormatCLUT8()); + _magics.create(kScreenWidth, kScreenHeight, Graphics::PixelFormat::createFormatCLUT8()); + _screen.create(kScreenWidth, kScreenHeight * 2, Graphics::PixelFormat::createFormatCLUT8()); + _scrolls.create(kScreenWidth, kScreenHeight, Graphics::PixelFormat::createFormatCLUT8()); +} + +/** + * Load the scoring digits & rwlites + * @remarks Originally called 'load_digits' + */ +void GraphicManager::loadDigits() { + const byte digitsize = 134; + const byte rwlitesize = 126; + + Common::File file; + if (!file.open("digit.avd")) + error("AVALANCHE: File not found: digit.avd"); + + for (int i = 0; i < 10; i++) { + file.seek(i * digitsize); + _digits[i] = loadPictureGraphic(file); + } + + for (int i = 0; i < 9; i++) { + file.seek(10 * digitsize + i * rwlitesize); + _directions[i] = loadPictureGraphic(file); + } + + file.close(); +} + +void GraphicManager::loadMouse(byte which) { + if (which == _vm->_currentMouse) + return; + + _vm->_currentMouse = which; + + Common::File f; + if (!f.open("mice.avd")) + error("AVALANCHE: Gyro: File not found: mice.avd"); + + Graphics::Surface cursor; + cursor.create(16, 32, Graphics::PixelFormat::createFormatCLUT8()); + cursor.fillRect(Common::Rect(0, 0, 16, 32), 255); + + + // The AND mask. + f.seek(kMouseSize * 2 * which + 134); + + Graphics::Surface mask = loadPictureGraphic(f); + + for (int j = 0; j < mask.h; j++) { + for (int i = 0; i < mask.w; i++) { + byte pixel = *(byte *)mask.getBasePtr(i, j); + if (pixel == 0) { + *(byte *)cursor.getBasePtr(i, j * 2 ) = 0; + *(byte *)cursor.getBasePtr(i, j * 2 + 1) = 0; + } + } + } + + mask.free(); + + // The OR mask. + f.seek(kMouseSize * 2 * which + 134 * 2); + + mask = loadPictureGraphic(f); + + for (int j = 0; j < mask.h; j++) { + for (int i = 0; i < mask.w; i++) { + byte pixel = *(byte *)mask.getBasePtr(i, j); + if (pixel != 0) { + *(byte *)cursor.getBasePtr(i, j * 2 ) = pixel; + *(byte *)cursor.getBasePtr(i, j * 2 + 1) = pixel; + } + } + } + + mask.free(); + f.close(); + + CursorMan.replaceCursor(cursor.getPixels(), 16, 32, kMouseHotSpots[which]._horizontal, kMouseHotSpots[which]._vertical * 2, 255, false); + cursor.free(); +} + +void GraphicManager::drawThinkPic(Common::String filename, int id) { + static const int16 picSize = 966; + Common::File file; + if (!file.open(filename)) + error("drawThinkPic(): File not found: %s", filename.c_str()); + + file.seek(id * picSize + 65); + Graphics::Surface picture = loadPictureGraphic(file); + drawPicture(_surface, picture, 205, 170); + + picture.free(); + file.close(); +} + +void GraphicManager::drawToolbar() { + Common::File file; + if (!file.open("useful.avd")) + error("drawToolbar(): File not found: useful.avd"); + + file.seek(40); + + CursorMan.showMouse(false); + Graphics::Surface picture = loadPictureGraphic(file); + drawPicture(_surface, picture, 5, 169); + CursorMan.showMouse(true); + + picture.free(); + file.close(); +} + +Common::Point GraphicManager::drawArc(Graphics::Surface &surface, int16 x, int16 y, int16 stAngle, int16 endAngle, uint16 radius, Color color) { + Common::Point endPoint; + const float convfac = M_PI / 180.0; + + int32 xRadius = radius; + int32 yRadius = radius * kScreenWidth / (8 * kScreenHeight); // Just don't ask why... + + if (xRadius == 0) + xRadius++; + if (yRadius == 0) + yRadius++; + + // Check for an ellipse with negligable x and y radius. + if ((xRadius <= 1) && (yRadius <= 1)) { + *(byte *)_scrolls.getBasePtr(x, y) = color; + endPoint.x = x; + endPoint.y = y; + return endPoint; + } + + // Check if valid angles. + stAngle = stAngle % 361; + endAngle = endAngle % 361; + + // If impossible angles, then swap them! + if (endAngle < stAngle) { + uint16 tmpAngle=endAngle; + endAngle=stAngle; + stAngle=tmpAngle; + } + + // Approximate the number of pixels required by using the circumference equation of an ellipse. + uint16 numOfPixels = (uint16)floor(sqrt(3.0) * sqrt(pow(double(xRadius), 2) + pow(double(yRadius), 2)) + 0.5); + + // Calculate the angle precision required. + float delta = 90.0 / numOfPixels; + + // Always just go over the first 90 degrees. Could be optimized a + // bit if startAngle and endAngle lie in the same quadrant, left as an + // exercise for the reader. :) + float j = 0; + + // Calculate stop position, go 1 further than 90 because otherwise 1 pixel is sometimes not drawn. + uint16 deltaEnd = 91; + + // Set the end point. + float tempTerm = endAngle * convfac; + endPoint.x = (int16)floor(xRadius * cos(tempTerm) + 0.5) + x; + endPoint.y = (int16)floor(yRadius * sin(tempTerm + M_PI) + 0.5) + y; + + // Calculate points. + int16 xNext = xRadius; + int16 yNext = 0; + do { + int16 xTemp = xNext; + int16 yTemp = yNext; + // This is used by both sin and cos. + tempTerm = (j + delta) * convfac; + + xNext = (int16)floor(xRadius * cos(tempTerm) + 0.5); + yNext = (int16)floor(yRadius * sin(tempTerm + M_PI) + 0.5); + + int16 xp = x + xTemp; + int16 xm = x - xTemp; + int16 yp = y + yTemp; + int16 ym = y - yTemp; + + if ((j >= stAngle) && (j <= endAngle)) + *(byte *)_scrolls.getBasePtr(xp, yp) = color; + + if (((180 - j) >= stAngle) && ((180 - j) <= endAngle)) + *(byte *)_scrolls.getBasePtr(xm, yp) = color; + + if (((j + 180) >= stAngle) && ((j + 180) <= endAngle)) + *(byte *)_scrolls.getBasePtr(xm, ym) = color; + + if (((360 - j) >= stAngle) && ((360 - j) <= endAngle)) + *(byte *)_scrolls.getBasePtr(xp, ym) = color; + + j += delta; + } while (j <= deltaEnd); + + return endPoint; +} + +Common::Point GraphicManager::drawScreenArc(int16 x, int16 y, int16 stAngle, int16 endAngle, uint16 radius, Color color) { + return drawArc(_surface, x, y, stAngle, endAngle, radius, color); +} + +void GraphicManager::drawPieSlice(int16 x, int16 y, int16 stAngle, int16 endAngle, uint16 radius, Color color) { + while (radius > 0) + drawArc(_scrolls, x, y, stAngle, endAngle, radius--, color); +} + +void GraphicManager::drawTriangle(Common::Point *p, Color color) { + // Draw the borders with a marking color. + _scrolls.drawLine(p[0].x, p[0].y, p[1].x, p[1].y, 255); + _scrolls.drawLine(p[1].x, p[1].y, p[2].x, p[2].y, 255); + _scrolls.drawLine(p[2].x, p[2].y, p[0].x, p[0].y, 255); + + // Get the top and the bottom of the triangle. + uint16 maxY = p[0].y, minY = p[0].y; + for (int i = 1; i < 3; i++) { + if (p[i].y < minY) + minY = p[i].y; + if (p[i].y > maxY) + maxY = p[i].y; + } + + // Fill the triangle. + for (uint16 y = minY; y <= maxY; y++) { + uint16 x = 0; + while (*(byte *)_scrolls.getBasePtr(x, y) != 255) + x++; + uint16 minX = x; + uint16 maxX = x; + x++; + while ((*(byte *)_scrolls.getBasePtr(x, y) != 255) && (x != 639)) + x++; + if (x != 639) + maxX = x; + if (minX != maxX) + _scrolls.drawLine(minX, y, maxX, y, color); + } + + // Redraw the borders with the actual color. + _scrolls.drawLine(p[0].x, p[0].y, p[1].x, p[1].y, color); + _scrolls.drawLine(p[1].x, p[1].y, p[2].x, p[2].y, color); + _scrolls.drawLine(p[2].x, p[2].y, p[0].x, p[0].y, color); +} + +void GraphicManager::drawText(Graphics::Surface &surface, const Common::String text, FontType font, byte fontHeight, int16 x, int16 y, Color color) { + for (uint i = 0; i < text.size(); i++) { + for (int j = 0; j < fontHeight; j++) { + byte pixel = font[(byte)text[i]][j]; + for (int bit = 0; bit < 8; bit++) { + byte pixelBit = (pixel >> bit) & 1; + if (pixelBit) + *(byte *)surface.getBasePtr(x + i * 8 + 7 - bit, y + j) = color; + } + } + } +} + +void GraphicManager::drawNormalText(const Common::String text, FontType font, byte fontHeight, int16 x, int16 y, Color color) { + drawText(_surface, text, font, fontHeight, x, y, color); +} + +void GraphicManager::drawScrollText(const Common::String text, FontType font, byte fontHeight, int16 x, int16 y, Color color) { + drawText(_scrolls, text, font, fontHeight, x, y, color); +} + +void GraphicManager::drawDigit(int index, int x, int y) { + drawPicture(_surface, _digits[index], x, y); +} + +void GraphicManager::drawDirection(int index, int x, int y) { + drawPicture(_surface, _directions[index], x, y); +} + +void GraphicManager::drawScrollShadow(int16 x1, int16 y1, int16 x2, int16 y2) { + for (byte i = 0; i < 2; i ++) { + _scrolls.fillRect(Common::Rect(x1 + i, y1 + i, x1 + i + 1, y2 - i), kColorWhite); + _scrolls.fillRect(Common::Rect(x1 + i, y1 + i, x2 - i, y1 + i + 1), kColorWhite); + + _scrolls.fillRect(Common::Rect(x2 - i, y1 + i, x2 - i + 1, y2 - i + 1), kColorDarkgray); + _scrolls.fillRect(Common::Rect(x1 + i, y2 - i, x2 - i, y2 - i + 1), kColorDarkgray); + } +} + +void GraphicManager::drawShadowBox(int16 x1, int16 y1, int16 x2, int16 y2, Common::String text) { + CursorMan.showMouse(false); + + drawScrollShadow(x1, y1, x2, y2); + + bool offset = text.size() % 2; + x1 = (x2 - x1) / 2 + x1 - text.size() / 2 * 8 - offset * 3; + y1 = (y2 - y1) / 2 + y1 - 4; + drawScrollText(text, _vm->_font, 8, x1, y1, kColorBlue); + drawScrollText(Common::String('_'), _vm->_font, 8, x1, y1, kColorBlue); + + CursorMan.showMouse(true); +} + +void GraphicManager::drawMenuBar(Color color) { + _surface.fillRect(Common::Rect(0, 0, 640, 10), color); +} + +void GraphicManager::drawMenuBlock(int x1, int y1, int x2, int y2, Color color) { + _surface.fillRect(Common::Rect(x1, y1, x2, y2), color); +} + +void GraphicManager::drawMenuItem(int x1, int y1, int x2, int y2) { + _surface.fillRect(Common::Rect(x1, y1, x2, y2), kMenuBackgroundColor); + _surface.frameRect(Common::Rect(x1 - 1, y1 - 1, x2 + 1, y2 + 1), kMenuBorderColor); +} + +void GraphicManager::drawSpeedBar(int speed) { + if (speed == kRun) { + _surface.drawLine(336, 199, 338, 199, kColorLightblue); + _surface.drawLine(371, 199, 373, 199, kColorYellow); + } else { + _surface.drawLine(371, 199, 373, 199, kColorLightblue); + _surface.drawLine(336, 199, 338, 199, kColorYellow); + } +} +void GraphicManager::drawScroll(int mx, int lx, int my, int ly) { + _scrolls.copyFrom(_surface); + + // The right corners of the scroll. + drawPieSlice(mx + lx, my - ly, 0, 90, 15, kColorLightgray); + drawPieSlice(mx + lx, my + ly, 270, 360, 15, kColorLightgray); + drawArc(_scrolls, mx + lx, my - ly, 0, 90, 15, kColorRed); + drawArc(_scrolls, mx + lx, my + ly, 270, 360, 15, kColorRed); + + // The body of the scroll. + _scrolls.fillRect(Common::Rect(mx - lx - 30, my + ly, mx + lx, my + ly + 6), kColorLightgray); + _scrolls.fillRect(Common::Rect(mx - lx - 30, my - ly - 6, mx + lx, my - ly + 1), kColorLightgray); + _scrolls.fillRect(Common::Rect(mx - lx - 15, my - ly, mx + lx + 15, my + ly + 1), kColorLightgray); + + // The left corners of the scroll. + drawPieSlice(mx - lx - 31, my - ly, 0, 180, 15, kColorDarkgray); + drawArc(_scrolls, mx - lx - 31, my - ly, 0, 180, 15, kColorRed); + _scrolls.drawLine(mx - lx - 31 - 15, my - ly, mx - lx - 31 + 15, my - ly, kColorRed); + drawPieSlice(mx - lx - 31, my + ly, 180, 360, 15, kColorDarkgray); + drawArc(_scrolls, mx - lx - 31, my + ly, 180, 360, 15, kColorRed); + _scrolls.drawLine(mx - lx - 31 - 15, my + ly, mx - lx - 31 + 15, my + ly, kColorRed); + + // The rear borders of the scroll. + _scrolls.fillRect(Common::Rect(mx - lx - 30, my - ly - 6, mx + lx, my - ly - 5), kColorRed); + _scrolls.fillRect(Common::Rect(mx - lx - 30, my + ly + 6, mx + lx, my + ly + 7), kColorRed); + _scrolls.fillRect(Common::Rect(mx - lx - 15, my - ly, mx - lx - 14, my + ly), kColorRed); + _scrolls.fillRect(Common::Rect(mx + lx + 15, my - ly, mx + lx + 16, my + ly), kColorRed); +} + +void GraphicManager::drawBackgroundSprite(int16 x, int16 y, SpriteType &sprite) { + drawPicture(_background, sprite._picture, x, y); +} + +void GraphicManager::drawDebugLines() { + if (!_vm->_showDebugLines) + return; + + for (int i = 0; i < _vm->_lineNum; i++) { + LineType *curLine = &_vm->_lines[i]; + _surface.drawLine(curLine->_x1, curLine->_y1, curLine->_x2, curLine->_y2, curLine->_color); + } + + for (int i = 0; i < _vm->_fieldNum; i++) { + FieldType *curField = &_vm->_fields[i]; + if (curField->_x1 < 640) + _surface.frameRect(Common::Rect(curField->_x1, curField->_y1, curField->_x2, curField->_y2), kColorLightmagenta); + } +} + +/** + * This function mimics Pascal's getimage(). + */ +Graphics::Surface GraphicManager::loadPictureGraphic(Common::File &file) { + // The height and the width are stored in 2-2 bytes. We have to add 1 to each because Pascal stores the value of them -1. + uint16 width = file.readUint16LE() + 1; + uint16 height = file.readUint16LE() + 1; + + Graphics::Surface picture; // We make a Surface object for the picture itself. + picture.create(width, height, Graphics::PixelFormat::createFormatCLUT8()); + + // Produce the picture. We read it in row-by-row, and every row has 4 planes. + for (int y = 0; y < height; y++) { + for (int8 plane = 3; plane >= 0; plane--) { // The planes are in the opposite way. + for (uint16 x = 0; x < width; x += 8) { + byte pixel = file.readByte(); + for (int bit = 0; bit < 8; bit++) { + byte pixelBit = (pixel >> bit) & 1; + if (pixelBit != 0) + *(byte *)picture.getBasePtr(x + 7 - bit, y) += (pixelBit << plane); + } + } + } + } + return picture; +} + +/** + * Reads Row-planar EGA data. + * This function is our own creation, very much like the one above. The main differences are that + * we don't read the width and the height from the file, the planes are in a different order + * and we read the picture plane-by-plane. + */ +Graphics::Surface GraphicManager::loadPictureRaw(Common::File &file, uint16 width, uint16 height) { + Graphics::Surface picture; + picture.create(width, height, Graphics::PixelFormat::createFormatCLUT8()); + + for (int plane = 0; plane < 4; plane++) { + for (uint16 y = 0; y < height; y++) { + for (uint16 x = 0; x < width; x += 8) { + byte pixel = file.readByte(); + for (int i = 0; i < 8; i++) { + byte pixelBit = (pixel >> i) & 1; + *(byte *)picture.getBasePtr(x + 7 - i, y) += (pixelBit << plane); + } + } + } + } + + return picture; +} + +void GraphicManager::clearAlso() { + _magics.fillRect(Common::Rect(0, 0, 640, 200), 0); + _magics.frameRect(Common::Rect(0, 45, 640, 161), 15); +} + +void GraphicManager::clearTextBar() { + _surface.fillRect(Common::Rect(24, 161, 640, 169), kColorBlack); // Black out the line of the text. +} + +void GraphicManager::setAlsoLine(int x1, int y1, int x2, int y2, Color color) { + _magics.drawLine(x1, y1, x2, y2, color); +} + +void GraphicManager::drawScreenLine(int16 x, int16 y, int16 x2, int16 y2, Color color) { + _surface.drawLine(x, y, x2, y2, color); +} + +byte GraphicManager::getAlsoColor(int x1, int y1, int x2, int y2) { + byte returnColor = 0; + for (int16 i = x1; i <= x2; i++) { + for (int16 j = y1; j <= y2; j++) { + byte actColor = *(byte *)_magics.getBasePtr(i, j); + returnColor = MAX(returnColor, actColor); + } + } + + return returnColor; +} + +byte GraphicManager::getScreenColor(Common::Point pos) { + return *(byte *)_surface.getBasePtr(pos.x, pos.y / 2); +} + +void GraphicManager::drawSprite(AnimationType *sprite, byte picnum, int16 x, int16 y) { + // First we make the pixels of the sprite blank. + for (int j = 0; j < sprite->_yLength; j++) { + for (int i = 0; i < sprite->_xLength; i++) { + if ((x + i < _surface.w) && (y + j < _surface.h)) { + if (((*sprite->_sil[picnum])[j][i / 8] >> ((7 - i % 8)) & 1) == 0) + *(byte *)_surface.getBasePtr(x + i, y + j) = 0; + } + } + } + + // Then we draw the picture to the blank places. + uint16 maniPos = 0; // Because the original manitype starts at 5!!! See Graphics.h for definition. + + for (int j = 0; j < sprite->_yLength; j++) { + for (int8 plane = 3; plane >= 0; plane--) { // The planes are in the opposite way. + for (uint16 i = 0; i < sprite->_xLength; i += 8) { + byte pixel = (*sprite->_mani[picnum])[maniPos++]; + for (int bit = 0; bit < 8; bit++) { + if ((x + i + 7 < _surface.w) && (y + j < _surface.h)) { + byte pixelBit = (pixel >> bit) & 1; + *(byte *)_surface.getBasePtr(x + i + 7 - bit, y + j) += (pixelBit << plane); + } + } + } + } + } +} + +void GraphicManager::drawPicture(Graphics::Surface &target, const Graphics::Surface picture, uint16 destX, uint16 destY) { + // Copy the picture to the given place on the screen. + uint16 maxX = picture.w; + uint16 maxY = picture.h; + + if (destX + maxX > target.w) + maxX = target.w - destX; + + if (destY + maxY > target.h) + maxY = target.h - destY; + + for (uint16 y = 0; y < maxY; y++) { + for (uint16 x = 0; x < maxX; x++) + *(byte *)target.getBasePtr(x + destX, y + destY) = *(const byte *)picture.getBasePtr(x, y); + } +} + +void GraphicManager::drawCursor(byte pos) { + int pixPos = 24 + (pos * 8); + // Draw the '_' character. + for (int i = 0; i < 8; i++) + *(byte *)_surface.getBasePtr(pixPos + i, 168) = kColorWhite; +} + +void GraphicManager::drawReadyLight(Color color) { + _surface.fillRect(Common::Rect(419, 195, 438, 197), color); +} + +/** + * This is for drawing a big "about" or "gameover" picture loaded from a file into an empty scroll. + */ +void GraphicManager::drawSign(Common::String fn, int16 xl, int16 yl, int16 y) { + Common::File file; + Common::String filename = Common::String::format("%s.avd", fn.c_str()); + + if (!file.open(filename)) + error("AVALANCHE: Scrolls: File not found: %s", filename.c_str()); + + // I know it looks very similar to the loadPicture methods, but in truth it's the combination of the two. + uint16 width = xl * 8; + uint16 height = yl; + + Graphics::Surface sign; // We make a Surface object for the picture itself. + sign.create(width, height, Graphics::PixelFormat::createFormatCLUT8()); + + // Produce the picture. We read it in row-by-row, and every row has 4 planes. + for (int yy = 0; yy < height; yy++) { + for (int8 plane = 0; plane < 4; plane++) { // The planes are in the "right" order. + for (uint16 xx = 0; xx < width; xx += 8) { + byte pixel = file.readByte(); + for (int bit = 0; bit < 8; bit++) { + byte pixelBit = (pixel >> bit) & 1; + if (pixelBit != 0) + *(byte *)sign.getBasePtr(xx + 7 - bit, yy) += (pixelBit << plane); + } + } + } + } + + drawPicture(_scrolls, sign, kScreenWidth / 2 - width / 2, y); + + file.close(); +} + +/** + * Draws an icon to the current scroll. + * @remarks Originally called 'geticon' + */ +void GraphicManager::drawIcon(int16 x, int16 y, byte which) { + Common::File file; + + if (!file.open("icons.avd")) + error("AVALANCHE: Scrolls: File not found: icons.avd"); + + which--; + file.seek(which * 426); + + Graphics::Surface icon = loadPictureGraphic(file); + drawPicture(_scrolls, icon, x, y); + + icon.free(); + file.close(); +} + +void GraphicManager::prepareBubble(int xc, int xw, int my, Common::Point points[3]) { + // Backup the screen before drawing the bubble. + _scrolls.copyFrom(_surface); + + int16 talkX = _vm->_dialogs->getTalkPosX(); + // The body of the bubble. + _scrolls.fillRect(Common::Rect(xc + talkX - xw + 9, 7, talkX + xw - 8 + xc, my + 1), _talkBackgroundColor); + _scrolls.fillRect(Common::Rect(xc + talkX - xw - 1, 12, talkX + xw + xc + 2, my - 4), _talkBackgroundColor); + + // Top the 4 rounded corners of the bubble. + drawPieSlice(xc + talkX + xw - 10, 11, 0, 90, 9, _talkBackgroundColor); + drawPieSlice(xc + talkX + xw - 10, my - 4, 270, 360, 9, _talkBackgroundColor); + drawPieSlice(xc + talkX - xw + 10, 11, 90, 180, 9, _talkBackgroundColor); + drawPieSlice(xc + talkX - xw + 10, my - 4, 180, 270, 9, _talkBackgroundColor); + + // "Tail" of the speech bubble. + drawTriangle(points, _talkBackgroundColor); +} + +/** + * Set the background of the text to the desired color. + */ +void GraphicManager::wipeChar(int x, int y, Color color) { + for (int k = 0; k < 8; k++) + *(byte *)_surface.getBasePtr(x + k, y) = color; +} + +void GraphicManager::drawChar(byte ander, int x, int y, Color color) { + byte pixel = ander; + for (int bit = 0; bit < 8; bit++) { + byte pixelBit = (pixel >> bit) & 1; + if (pixelBit) + *(byte *)_surface.getBasePtr(x + 7 - bit, y) = color; + } +} +void GraphicManager::refreshScreen() { + // These cycles are for doubling the screen height. + for (uint16 y = 0; y < _screen.h / 2; y++) { + memcpy(_screen.getBasePtr(0, y * 2), _surface.getBasePtr(0, y), _screen.w); + memcpy(_screen.getBasePtr(0, y * 2 + 1), _surface.getBasePtr(0, y), _screen.w); + } + // Now we copy the stretched picture to the screen. + g_system->copyRectToScreen(_screen.getPixels(), _screen.pitch, 0, 0, kScreenWidth, kScreenHeight * 2); + g_system->updateScreen(); +} + +void GraphicManager::loadBackground(Common::File &file) { + _background.free(); + _background = loadPictureRaw(file, kBackgroundWidth, kBackgroundHeight); +} + +void GraphicManager::refreshBackground() { + drawPicture(_surface, _background, 0, 10); +} + +/** + * Only used when entering the map. + * @remarks Originally called 'zoomout' + */ +void GraphicManager::zoomOut(int16 x, int16 y) { + //setlinestyle(dottedln, 0, 1); TODO: Implement it with a dotted line style!!! + + saveScreen(); + for (byte i = 1; i <= 20; i ++) { + int16 x1 = x - (x / 20) * i; + int16 y1 = y - ((y - 10) / 20) * i; + int16 x2 = x + (((639 - x) / 20) * i); + int16 y2 = y + (((161 - y) / 20) * i); + + _surface.frameRect(Common::Rect(x1, y1, x2, y2), kColorWhite); + refreshScreen(); + _vm->_system->delayMillis(17); + + restoreScreen(); + } + removeBackup(); +} + +void GraphicManager::showScroll() { + _surface.copyFrom(_scrolls); // TODO: Rework it using getSubArea !!!!!!! +} + +void GraphicManager::getNaturalPicture(SpriteType &sprite) { + sprite._type = kNaturalImage; // We simply read from the screen and later, in drawSprite() we draw it right back. + sprite._size = sprite._xl * 8 * sprite._yl + 1; + sprite._picture.create(sprite._xl * 8, sprite._yl + 1, Graphics::PixelFormat::createFormatCLUT8()); + for (uint16 y = 0; y < sprite._yl + 1; y++) { + for (uint16 x = 0; x < sprite._xl * 8; x++) + *(byte *)sprite._picture.getBasePtr(x, y) = *(byte *)_vm->_graphics->_surface.getBasePtr(sprite._x * 8 + x, sprite._y + y); + } +} + +void GraphicManager::saveScreen() { + _backup.copyFrom(_surface); +} + +void GraphicManager::removeBackup() { + _backup.free(); +} + +void GraphicManager::restoreScreen() { + _surface.copyFrom(_backup); + refreshScreen(); +} + +void GraphicManager::setDialogColor(Color bg, Color text) { + _talkBackgroundColor = bg; + _talkFontColor = text; +} + +// Original name background() +void GraphicManager::setBackgroundColor(Color x) { + warning("STUB: setBackgroundColor()"); +} + +} // End of namespace Avalanche diff --git a/engines/avalanche/graphics.h b/engines/avalanche/graphics.h new file mode 100644 index 0000000000..4af6d4e8db --- /dev/null +++ b/engines/avalanche/graphics.h @@ -0,0 +1,142 @@ +/* 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 the original source code of Lord Avalot d'Argent version 1.3. + * Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman. + */ + +#ifndef AVALANCHE_GRAPHICS_H +#define AVALANCHE_GRAPHICS_H + +#include "avalanche/enums.h" + +#include "common/file.h" +#include "common/rect.h" +#include "graphics/surface.h" + +namespace Avalanche { +class AvalancheEngine; +class AnimationType; +struct SpriteType; + +typedef byte FontType[256][16]; +typedef byte ManiType[2049]; +typedef byte SilType[51][11]; // 35, 4 + +struct MouseHotspotType { + int16 _horizontal, _vertical; +}; + +class GraphicManager { +public: + static const MouseHotspotType kMouseHotSpots[9]; + Color _talkBackgroundColor, _talkFontColor; + + GraphicManager(AvalancheEngine *vm); + ~GraphicManager(); + void init(); + void loadDigits(); + void loadMouse(byte which); + + Common::Point drawScreenArc(int16 x, int16 y, int16 stAngle, int16 endAngle, uint16 radius, Color color); + void drawPieSlice(int16 x, int16 y, int16 stAngle, int16 endAngle, uint16 radius, Color color); + void drawTriangle(Common::Point *p, Color color); + void drawNormalText(const Common::String text, FontType font, byte fontHeight, int16 x, int16 y, Color color); + void drawScrollText(const Common::String text, FontType font, byte fontHeight, int16 x, int16 y, Color color); + void drawDigit(int index, int x, int y); + void drawDirection(int index, int x, int y); + void drawScrollShadow(int16 x1, int16 y1, int16 x2, int16 y2); + void drawShadowBox(int16 x1, int16 y1, int16 x2, int16 y2, Common::String text); + void drawScroll(int mx, int lx, int my, int ly); + void drawMenuBar(Color color); + void drawSpeedBar(int speed); + void drawBackgroundSprite(int16 x, int16 y, SpriteType &sprite); + void drawMenuBlock(int x1, int y1, int x2, int y2, Color color); + void drawMenuItem(int x1, int y1, int x2, int y2); + void wipeChar(int x, int y, Color color); + void drawChar(byte ander, int x, int y, Color color); + void drawDebugLines(); + + void clearAlso(); + void clearTextBar(); + void setAlsoLine(int x1, int y1, int x2, int y2, Color color); + byte getAlsoColor(int x1, int y1, int x2, int y2); + byte getScreenColor(Common::Point pos); + + // The caller has to .free() the returned Surfaces!!! + // Further information about these two: http://www.shikadi.net/moddingwiki/Raw_EGA_data + Graphics::Surface loadPictureRaw(Common::File &file, uint16 width, uint16 height); + + void drawSprite(AnimationType *sprite, byte picnum, int16 x, int16 y); + void drawPicture(Graphics::Surface &target, const Graphics::Surface picture, uint16 destX, uint16 destY); + void drawThinkPic(Common::String filename, int id); + void drawToolbar(); + void drawCursor(byte pos); + void drawReadyLight(Color color); + void drawSign(Common::String name, int16 xl, int16 yl, int16 y); + void drawIcon(int16 x, int16 y, byte which); + void drawScreenLine(int16 x, int16 y, int16 x2, int16 y2, Color color); + void prepareBubble(int xc, int xw, int my, Common::Point points[3]); + void refreshScreen(); + void loadBackground(Common::File &file); + void refreshBackground(); + void setBackgroundColor(Color x); + void setDialogColor(Color bg, Color text); + + void zoomOut(int16 x, int16 y); + void showScroll(); + void getNaturalPicture(SpriteType &sprite); + + void saveScreen(); + void removeBackup(); + void restoreScreen(); + +private: + static const uint16 kBackgroundWidth = kScreenWidth; + static const byte kEgaPaletteIndex[16]; + static const byte kBackgroundHeight = 8 * 12080 / kScreenWidth; // With 640 width it's 151. + // The 8 = number of bits in a byte, and 12080 comes from Lucerna::load(). + + Graphics::Surface _background; + Graphics::Surface _backup; + Graphics::Surface _digits[10]; // digitsize and rwlitesize are defined in loadDigits() !!! + Graphics::Surface _directions[9]; // Maybe it will be needed to move them to the class itself instead. + Graphics::Surface _magics; // Lucerna::draw_also_lines() draws the "magical" lines here. Further information: https://github.com/urukgit/avalot/wiki/Also + Graphics::Surface _screen; // Only used in refreshScreen() to make it more optimized. (No recreation of it at every call of the function.) + Graphics::Surface _scrolls; + Graphics::Surface _surface; + byte _egaPalette[64][3]; + + AvalancheEngine *_vm; + + Graphics::Surface loadPictureGraphic(Common::File &file); // Reads Graphic-planar EGA data. + void drawText(Graphics::Surface &surface, const Common::String text, FontType font, byte fontHeight, int16 x, int16 y, Color color); + // Taken from Free Pascal's Procedure InternalEllipseDefault. Used to replace Pascal's procedure arc. + // Returns the end point of the arc. (Needed in Clock.) + // TODO: Make it more accurate later. + Common::Point drawArc(Graphics::Surface &surface, int16 x, int16 y, int16 stAngle, int16 endAngle, uint16 radius, Color color); +}; + +} // End of namespace Avalanche + +#endif // AVALANCHE_GRAPHICS_H diff --git a/engines/avalanche/menu.cpp b/engines/avalanche/menu.cpp new file mode 100644 index 0000000000..bba8e862a9 --- /dev/null +++ b/engines/avalanche/menu.cpp @@ -0,0 +1,834 @@ +/* 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 the original source code of Lord Avalot d'Argent version 1.3. + * Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman. + */ + +/* Original name: DROPDOWN A customized version of Oopmenu (qv). */ + +#include "avalanche/avalanche.h" +#include "avalanche/menu.h" + +namespace Avalanche { + +void HeadType::init(char trig, char altTrig, Common::String title, byte pos, MenuFunc setupFunc, MenuFunc chooseFunc, Menu *menu) { + _trigger = trig; + _altTrigger = altTrig; + _title = title; + _position = pos; + _xpos = _position * _menu->kSpacing + _menu->kIndent; + _xright = (_position + 1) * _menu->kSpacing + _menu->kIndent; + _setupFunc = setupFunc; + _chooseFunc = chooseFunc; + + _menu = menu; +} + +void HeadType::draw() { + CursorMan.showMouse(false); + _menu->drawMenuText(_xpos, 1, _trigger, _title, true, false); + CursorMan.showMouse(true); +} + +void HeadType::highlight() { + CursorMan.showMouse(false); + + _menu->_vm->_sound->stopSound(); + _menu->drawMenuText(_xpos, 1, _trigger, _title, true, true); + + _menu->_activeMenuItem._left = _xpos; + _menu->_activeMenuItem._activeNow = true; + _menu->_activeMenuItem._activeNum = _position; + _menu->_menuActive = true; + + // Force reload and redraw of cursor. + _menu->_vm->_currentMouse = 177; + +} + +bool HeadType::parseAltTrigger(char key) { + if (key != _altTrigger) + return true; + return false; +} + +void MenuItem::init(Menu *menu) { + _menu = menu; + + _activeNow = false; + _activeNum = 1; + _menu->_menuActive = false; +} + +void MenuItem::reset() { + _optionNum = 0; + _width = 0; + _firstlix = false; + _oldY = 0; + _highlightNum = 0; +} + +void MenuItem::setupOption(Common::String title, char trigger, Common::String shortcut, bool valid) { + uint16 width = (title + shortcut).size() + 3; + if (_width < width) + _width = width; + + _options[_optionNum]._title = title; + _options[_optionNum]._trigger = trigger; + _options[_optionNum]._shortcut = shortcut; + _options[_optionNum]._valid = valid; + _optionNum++; +} + +void MenuItem::displayOption(byte y, bool highlit) { + Common::String text = _options[y]._title; + while (text.size() + _options[y]._shortcut.size() < _width) + text += ' '; // Pad _options[y] with spaces. + text += _options[y]._shortcut; + + Color backgroundColor; + if (highlit) + backgroundColor = kColorBlack; + else + backgroundColor = kColorLightgray; + + _menu->_vm->_graphics->drawMenuBlock((_flx1 + 1) * 8, 3 + (y + 1) * 10, (_flx2 + 1) * 8, 13 + (y + 1) * 10, backgroundColor); + _menu->drawMenuText(_left, 4 + (y + 1) * 10, _options[y]._trigger, text, _options[y]._valid, highlit); +} + +void MenuItem::display() { + CursorMan.showMouse(false); + + _firstlix = true; + _flx1 = _left - 2; + _flx2 = _left + _width; + _fly = 15 + _optionNum * 10; + _activeNow = true; + _menu->_menuActive = true; + + _menu->_vm->_graphics->drawMenuItem((_flx1 + 1) * 8, 12, (_flx2 + 1) * 8, _fly); + + displayOption(0, true); + for (int y = 1; y < _optionNum; y++) + displayOption(y, false); + + _menu->_vm->_currentMouse = 177; + + CursorMan.showMouse(true); // 4 = fletch +} + +void MenuItem::wipe() { + CursorMan.showMouse(false); + + _menu->drawMenuText(_menu->_menuBar._menuItems[_menu->_activeMenuItem._activeNum]._xpos, 1, + _menu->_menuBar._menuItems[_menu->_activeMenuItem._activeNum]._trigger, + _menu->_menuBar._menuItems[_menu->_activeMenuItem._activeNum]._title, true, false); + + _activeNow = false; + _menu->_menuActive = false; + _firstlix = false; + + CursorMan.showMouse(true); +} + +void MenuItem::moveHighlight(int8 inc) { + if (inc != 0) { + int8 highlightNum = _highlightNum + inc; + if ((highlightNum < 0) || (highlightNum >= _optionNum)) + return; + _highlightNum = highlightNum; + } + CursorMan.showMouse(false); + displayOption(_oldY, false); + displayOption(_highlightNum, true); + _oldY = _highlightNum; + CursorMan.showMouse(true); +} + +/** + * This makes the menu highlight follow the mouse. + * @remarks Originally called 'lightup' + */ +void MenuItem::lightUp(Common::Point cursorPos) { + if ((cursorPos.x < _flx1 * 8) || (cursorPos.x > _flx2 * 8) || (cursorPos.y <= 25) || (cursorPos.y > ((_fly - 3) * 2 + 1))) + return; + _highlightNum = (cursorPos.y - 26) / 20; + if (_highlightNum == _oldY) + return; + moveHighlight(0); +} + +void MenuItem::select(byte which) { + if (!_options[which]._valid) + return; + + _choiceNum = which; + wipe(); + + if (_choiceNum == _optionNum) + _choiceNum--; // Off the bottom. + if (_choiceNum > _optionNum) + _choiceNum = 0; // Off the top, I suppose. + + (_menu->*_menu->_menuBar._menuItems[_activeNum]._chooseFunc)(); +} + +void MenuItem::parseKey(char c) { + c = toupper(c); + bool found = false; + for (int i = 0; i < _optionNum; i++) { + if ((toupper(_options[i]._trigger) == c) && _options[i]._valid) { + select(i); + found = true; + } + } + if (!found) + _menu->_vm->_sound->blip(); +} + +void MenuBar::init(Menu *menu) { + _menu = menu; + _menuNum = 0; +} + +void MenuBar::createMenuItem(char trig, Common::String title, char altTrig, MenuFunc setupFunc, MenuFunc chooseFunc) { + _menuItems[_menuNum].init(trig, altTrig, title, _menuNum, setupFunc, chooseFunc, _menu); + _menuNum++; +} + +void MenuBar::draw() { + _menu->_vm->_graphics->drawMenuBar(kMenuBackgroundColor); + + byte savecp = _menu->_vm->_cp; + _menu->_vm->_cp = 3; + + for (int i = 0; i < _menuNum; i++) + _menuItems[i].draw(); + + _menu->_vm->_cp = savecp; +} + +void MenuBar::parseAltTrigger(char c) { + byte i = 0; + while ((i < _menuNum) && (_menuItems[i].parseAltTrigger(c))) + i++; + if (i == _menuNum) + return; + setupMenuItem(i); +} + +void MenuBar::setupMenuItem(byte which) { + if (_menu->_activeMenuItem._activeNow) { + _menu->_activeMenuItem.wipe(); // Get rid of menu. + if (_menu->_activeMenuItem._activeNum == _menuItems[which]._position) + return; // Clicked on own highlight. + } + _menuItems[which].highlight(); + (_menu->*_menuItems[which]._setupFunc)(); +} + +void MenuBar::chooseMenuItem(int16 x) { + for (int i = 0; i < _menuNum; i++) { + if ((x > _menuItems[i]._xpos * 8) && (x < _menuItems[i]._xright * 8)) { + setupMenuItem(i); + break; + } + } +} + +Menu::Menu(AvalancheEngine *vm) { + _vm = vm; + _activeMenuItem.init(this); + _menuBar.init(this); +} + +void Menu::findWhatYouCanDoWithIt() { + switch (_vm->_thinks) { + case kObjectWine: + case kObjectPotion: + case kObjectInk: + _verbStr = Common::String(kVerbCodeExam) + kVerbCodeDrink; + break; + case kObjectBell: + _verbStr = Common::String(kVerbCodeExam) + kVerbCodeRing; + break; + case kObjectChastity: + _verbStr = Common::String(kVerbCodeExam) + kVerbCodeWear; + break; + case kObjectLute: + _verbStr = Common::String(kVerbCodeExam) + kVerbCodePlay; + break; + case kObjectMushroom: + case kObjectOnion: + _verbStr = Common::String(kVerbCodeExam) + kVerbCodeEat; + break; + case kObjectClothes: + _verbStr = Common::String(kVerbCodeExam) + kVerbCodeWear; + break; + default: + _verbStr = kVerbCodeExam; // Anything else. + } +} + +void Menu::drawMenuText(int16 x, int16 y, char trigger, Common::String text, bool valid, bool highlighted) { + Color fontColor; + Color backgroundColor; + if (highlighted) { + fontColor = kColorWhite; + backgroundColor = kColorBlack; + } else { + fontColor = kColorBlack; + backgroundColor = kColorLightgray; + } + + byte ander; + if (valid) + ander = 255; + else + ander = 170; + + FontType font; + for (uint i = 0; i < text.size(); i++) { + for (int j = 0; j < 8; j++) { + byte idx = text[i]; + font[idx][j] = _vm->_font[idx][j] & ander; // Set the font. + // And set the background of the text to the desired color. + _vm->_graphics->wipeChar(x * 8 + i * 8, y + j, backgroundColor); + } + } + + _vm->_graphics->drawNormalText(text, font, 8, x * 8, y, fontColor); + + // Underline the selected character. + if ((trigger == 0) || !text.contains(trigger) ) + return; + else { + byte i; + for (i = 0; text[i] != trigger; i++) + ; // Search for the character in the string. + + _vm->_graphics->drawChar(ander, x * 8 + i * 8, y + 8, fontColor); + } + + _vm->_graphics->refreshScreen(); +} + +void Menu::bleep() { + _vm->_sound->playNote(177, 7); +} + +void Menu::parseKey(char r, char re) { +#if 0 + switch (r) { + case 0: + case 224: { + switch (re) { + case 'K': + if (_activeMenuItem._activeNum > 1) { + _activeMenuItem.wipe(); + _menuBar.setupMenuItem(_activeMenuItem._activeNum - 1); + } else { + // Get menu on the left-hand side. + _activeMenuItem.wipe(); + _menuBar.chooseMenuItem((_menuBar._menuNum - 1) * kSpacing + kIndent); + } + break; + case 'M': + if (_activeMenuItem._activeNum < _menuBar._menuNum) { + _activeMenuItem.wipe(); + _menuBar.setupMenuItem(_activeMenuItem._activeNum + 1); + } else { + // Get menu on the far right-hand side. + _activeMenuItem.wipe(); + _menuBar.chooseMenuItem(kIndent); + } + break; + case 'H': + _activeMenuItem.moveHighlight(-1); + break; + case 'P': + _activeMenuItem.moveHighlight(1); + break; + default: + _menuBar.parseAltTrigger(re); + } + } + break; + case 13: + _activeMenuItem.select(_activeMenuItem._highlightNum); + break; + default: + if (_activeMenuItem._activeNow) + _activeMenuItem.parseKey(r); + } +#endif + + warning("STUB: Dropdown::parseKey()"); // To be implemented properly later! Don't remove the comment above! +} + +Common::String Menu::selectGender(byte x) { + if (x < 175) + return "im"; + else + return "er"; +} + +void Menu::setupMenuGame() { + _activeMenuItem.reset(); + _activeMenuItem.setupOption("Help...", 'H', "f1", true); + _activeMenuItem.setupOption("Boss Key", 'B', "alt-B", false); + _activeMenuItem.setupOption("Untrash screen", 'U', "ctrl-f7", true); + _activeMenuItem.setupOption("Score and rank", 'S', "f9", true); + _activeMenuItem.setupOption("About Avvy...", 'A', "shift-f10", true); + _activeMenuItem.display(); +} + +void Menu::setupMenuFile() { + _activeMenuItem.reset(); + _activeMenuItem.setupOption("New game", 'N', "f4", true); + _activeMenuItem.setupOption("Load...", 'L', "^f3", true); + _activeMenuItem.setupOption("Save", 'S', "^f2", _vm->_alive); + _activeMenuItem.setupOption("Save As...", 'v', "", _vm->_alive); + _activeMenuItem.setupOption("DOS Shell", 'D', "alt-1", false); + _activeMenuItem.setupOption("Quit", 'Q', "alt-X", true); + _activeMenuItem.display(); +} + +void Menu::setupMenuAction() { + _activeMenuItem.reset(); + + Common::String f5Does = _vm->f5Does(); + for (int i = 0; i < 2; i++) + if (!f5Does.empty()) + f5Does.deleteChar(0); + if (f5Does.empty()) + _activeMenuItem.setupOption("Do something", 'D', "f5", false); + else + _activeMenuItem.setupOption(f5Does, f5Does[0], "f5", true); + _activeMenuItem.setupOption("Pause game", 'P', "f6", true); + if (_vm->_room == kRoomMap) + _activeMenuItem.setupOption("Journey thither", 'J', "f7", _vm->_animation->nearDoor()); + else + _activeMenuItem.setupOption("Open the door", 'O', "f7", _vm->_animation->nearDoor()); + _activeMenuItem.setupOption("Look around", 'L', "f8", true); + _activeMenuItem.setupOption("Inventory", 'I', "Tab", true); + if (_vm->_animation->_sprites[0]->_speedX == kWalk) + _activeMenuItem.setupOption("Run fast", 'R', "^R", true); + else + _activeMenuItem.setupOption("Walk slowly", 'W', "^W", true); + + _activeMenuItem.display(); +} + +void Menu::setupMenuPeople() { + if (!people.empty()) + people.clear(); + + _activeMenuItem.reset(); + + for (int i = kPeopleAvalot; i <= kPeopleWisewoman; i++) { + if (_vm->getRoom((People)i) == _vm->_room) { + _activeMenuItem.setupOption(_vm->getName((People)i), getNameChar((People)i), "", true); + people += i; + } + } + + _activeMenuItem.display(); +} + +void Menu::setupMenuObjects() { + _activeMenuItem.reset(); + for (int i = 0; i < kObjectNum; i++) { + if (_vm->_objects[i]) + _activeMenuItem.setupOption(getThing(i + 1), getThingChar(i + 1), "", true); + } + _activeMenuItem.display(); +} + +void Menu::setupMenuWith() { + _activeMenuItem.reset(); + + if (_vm->_thinkThing) { + findWhatYouCanDoWithIt(); + + for (uint i = 0; i < _verbStr.size(); i++) { + char vbchar; + Common::String verb; + + _vm->_parser->verbOpt(_verbStr[i], verb, vbchar); + _activeMenuItem.setupOption(verb, vbchar, "", true); + } + + // We disable the "give" option if: (a), you haven't selected anybody, (b), the _person you've selected isn't in the room, + // or (c), the _person you've selected is YOU! + + if ((_lastPerson == kPeopleAvalot) || (_lastPerson == _vm->_parser->kNothing) + || (_vm->getRoom(_lastPerson) != _vm->_room)) + _activeMenuItem.setupOption("Give to...", 'G', "", false); // Not here. + else { + _activeMenuItem.setupOption(Common::String("Give to ") + _vm->getName(_lastPerson), 'G', "", true); + _verbStr = _verbStr + kVerbCodeGive; + } + } else { + _activeMenuItem.setupOption("Examine", 'x', "", true); + _activeMenuItem.setupOption(Common::String("Talk to h") + selectGender(_vm->_thinks), 'T', "", true); + _verbStr = Common::String(kVerbCodeExam) + kVerbCodeTalk; + switch (_vm->_thinks) { + case kPeopleGeida: + case kPeopleArkata: + _activeMenuItem.setupOption("Kiss her", 'K', "", true); + _verbStr = _verbStr + kVerbCodeKiss; + break; + case kPeopleDogfood: + _activeMenuItem.setupOption("Play his game", 'P', "", !_vm->_wonNim); // True if you HAVEN'T won. + _verbStr = _verbStr + kVerbCodePlay; + break; + case kPeopleMalagauche: { + bool isSober = !_vm->_teetotal; + _activeMenuItem.setupOption("Buy some wine", 'w', "", !_vm->_objects[kObjectWine - 1]); + _activeMenuItem.setupOption("Buy some beer", 'b', "", isSober); + _activeMenuItem.setupOption("Buy some whisky", 'h', "", isSober); + _activeMenuItem.setupOption("Buy some cider", 'c', "", isSober); + _activeMenuItem.setupOption("Buy some mead", 'm', "", isSober); + _verbStr = _verbStr + 101 + 100 + 102 + 103 + 104; + } + break; + case kPeopleTrader: + _activeMenuItem.setupOption("Buy an onion", 'o', "", !_vm->_objects[kObjectOnion - 1]); + _verbStr = _verbStr + 105; + break; + } + } + _activeMenuItem.display(); +} + +void Menu::runMenuGame() { + // Help, boss, untrash screen. + switch (_activeMenuItem._choiceNum) { + case 0: + _vm->callVerb(kVerbCodeHelp); + break; + case 1: + _vm->callVerb(kVerbCodeBoss); + break; + case 2: + _vm->majorRedraw(); + break; + case 3: + _vm->callVerb(kVerbCodeScore); + break; + case 4: + _vm->callVerb(kVerbCodeInfo); + break; + } +} + +void Menu::runMenuFile() { + // New game, load, save, save as, DOS shell, about, quit. + switch (_activeMenuItem._choiceNum) { + case 0: + _vm->callVerb(kVerbCodeRestart); + break; + case 1: + if (!_vm->_parser->_realWords[1].empty()) + _vm->_parser->_realWords[1].clear(); + _vm->callVerb(kVerbCodeLoad); + break; + // Case 2 is 'Save', Case 3 is 'Save As'. Both triggers ScummVM save screen. + case 2: + case 3: + if (!_vm->_parser->_realWords[1].empty()) + _vm->_parser->_realWords[1].clear(); + _vm->callVerb(kVerbCodeSave); + break; + case 4: + // Command Prompt, disabled + break; + case 5: + _vm->callVerb(kVerbCodeQuit); + break; + } +} + +void Menu::runMenuAction() { + // Get up, pause game, open door, look, inventory, walk/run. + switch (_activeMenuItem._choiceNum) { + case 0: { + _vm->_parser->_person = kPeoplePardon; + _vm->_parser->_thing = _vm->_parser->kPardon; + Common::String f5Does = _vm->f5Does(); + VerbCode verb = (VerbCode)(byte)f5Does[0]; + _vm->callVerb(verb); + } + break; + case 1: + _vm->_parser->_thing = _vm->_parser->kPardon; + _vm->callVerb(kVerbCodePause); + break; + case 2: + _vm->callVerb(kVerbCodeOpen); + break; + case 3: + _vm->_parser->_thing = _vm->_parser->kPardon; + _vm->callVerb(kVerbCodeLook); + break; + case 4: + _vm->callVerb(kVerbCodeInv); + break; + case 5: { + AnimationType *avvy = _vm->_animation->_sprites[0]; + if (avvy->_speedX == kWalk) + avvy->_speedX = kRun; + else + avvy->_speedX = kWalk; + _vm->_animation->updateSpeed(); + } + break; + } +} + +void Menu::runMenuObjects() { + _vm->thinkAbout(_vm->_objectList[_activeMenuItem._choiceNum], AvalancheEngine::kThing); +} + +void Menu::runMenuPeople() { + _vm->thinkAbout(people[_activeMenuItem._choiceNum], AvalancheEngine::kPerson); + _lastPerson = (People)people[_activeMenuItem._choiceNum]; +} + +void Menu::runMenuWith() { + _vm->_parser->_thing = _vm->_thinks; + + if (_vm->_thinkThing) { + _vm->_parser->_thing += 49; + + if (_verbStr[_activeMenuItem._choiceNum] == kVerbCodeGive) + _vm->_parser->_person = _lastPerson; + else + _vm->_parser->_person = kPeoplePardon; + } else { + switch (_verbStr[_activeMenuItem._choiceNum]) { + case 100: // Beer + case 102: // Whisky + case 103: // Cider + _vm->_parser->_thing = _verbStr[_activeMenuItem._choiceNum]; + _vm->callVerb(kVerbCodeBuy); + return; + case 101: // Wine + _vm->_parser->_thing = 50; + _vm->callVerb(kVerbCodeBuy); + return; + case 104: // Mead + _vm->_parser->_thing = 107; + _vm->callVerb(kVerbCodeBuy); + return; + case 105: // Onion (trader) + _vm->_parser->_thing = 67; + _vm->callVerb(kVerbCodeBuy); + return; + default: + _vm->_parser->_person = (People)_vm->_parser->_thing; + _vm->_parser->_thing = Parser::kPardon; + _vm->_subjectNum = 0; + } + } + _vm->callVerb((VerbCode)(byte)_verbStr[_activeMenuItem._choiceNum]); +} + +void Menu::setup() { + _menuBar.init(this); + _activeMenuItem.init(this); + + _menuBar.createMenuItem('F', "File", '!', &Avalanche::Menu::setupMenuFile, &Avalanche::Menu::runMenuFile); + _menuBar.createMenuItem('G', "Game", 34, &Avalanche::Menu::setupMenuGame, &Avalanche::Menu::runMenuGame); + _menuBar.createMenuItem('A', "Action", 30, &Avalanche::Menu::setupMenuAction, &Avalanche::Menu::runMenuAction); + _menuBar.createMenuItem('O', "Objects", 24, &Avalanche::Menu::setupMenuObjects, &Avalanche::Menu::runMenuObjects); + _menuBar.createMenuItem('P', "People", 25, &Avalanche::Menu::setupMenuPeople, &Avalanche::Menu::runMenuPeople); + _menuBar.createMenuItem('W', "With", 17, &Avalanche::Menu::setupMenuWith, &Avalanche::Menu::runMenuWith); + + _menuBar.draw(); +} + +void Menu::update() { // TODO: Optimize it ASAP!!! It really needs it... + _vm->_graphics->saveScreen(); + + Common::Point cursorPos = _vm->getMousePos(); + while (!_activeMenuItem._activeNow && (cursorPos.y <= 21) && _vm->_holdLeftMouse) { + _menuBar.chooseMenuItem(cursorPos.x); + do + _vm->updateEvents(); + while (_vm->_holdLeftMouse && !_vm->shouldQuit()); + + while (!_vm->shouldQuit()) { + do { + _vm->updateEvents(); + + // We update the cursor's picture. + cursorPos = _vm->getMousePos(); + // Change arrow... + if ((0 <= cursorPos.y) && (cursorPos.y <= 21)) + _vm->_graphics->loadMouse(kCurUpArrow); // Up arrow + else if ((22 <= cursorPos.y) && (cursorPos.y <= 339)) { + if ((cursorPos.x >= _activeMenuItem._flx1 * 8) && (cursorPos.x <= _activeMenuItem._flx2 * 8) && (cursorPos.y > 21) && (cursorPos.y <= _activeMenuItem._fly * 2 + 1)) + _vm->_graphics->loadMouse(kCurRightArrow); // Right-arrow + else + _vm->_graphics->loadMouse(kCurFletch); // Fletch + } else if ((340 <= cursorPos.y) && (cursorPos.y <= 399)) + _vm->_graphics->loadMouse(kCurScrewDriver); // Screwdriver + + _activeMenuItem.lightUp(cursorPos); + + _vm->_graphics->refreshScreen(); + } while (!_vm->_holdLeftMouse && !_vm->shouldQuit()); + + if (_vm->_holdLeftMouse) { + if (cursorPos.y > 21) { + if (!((_activeMenuItem._firstlix) && ((cursorPos.x >= _activeMenuItem._flx1 * 8) && (cursorPos.x <= _activeMenuItem._flx2 * 8) + && (cursorPos.y >= 24) && (cursorPos.y <= (_activeMenuItem._fly * 2 + 1))))) { + // Clicked OUTSIDE the menu. + if (_activeMenuItem._activeNow) { + _activeMenuItem.wipe(); + _vm->_holdLeftMouse = false; + _vm->_graphics->removeBackup(); + return; + } // No "else"- clicking on menu has no effect (only releasing). + } + } else { + // Clicked on menu bar. + if (_activeMenuItem._activeNow) { + _activeMenuItem.wipe(); + _vm->_graphics->restoreScreen(); + + if (((_activeMenuItem._left * 8) <= cursorPos.x) && (cursorPos.x <= (_activeMenuItem._left * 8 + 80))) { // 80: the width of one menu item on the bar in pixels. + // If we clicked on the same menu item (the one that is already active) on the bar... + _vm->_holdLeftMouse = false; + _vm->_graphics->removeBackup(); + return; + } else { + _vm->_holdLeftMouse = true; + break; + } + } + } + + // NOT clicked button... + if ((_activeMenuItem._firstlix) && ((cursorPos.x >= _activeMenuItem._flx1 * 8) && (cursorPos.x <= _activeMenuItem._flx2 * 8) + && (cursorPos.y >= 12) && (cursorPos.y <= (_activeMenuItem._fly * 2 + 1)))) { + + // We act only if the button is released over a menu item. + while (!_vm->shouldQuit()) { + cursorPos = _vm->getMousePos(); + _activeMenuItem.lightUp(cursorPos); + _vm->_graphics->refreshScreen(); + + _vm->updateEvents(); + if (!_vm->_holdLeftMouse) + break; + } + + uint16 which = (cursorPos.y - 26) / 20; + _activeMenuItem.select(which); + if (_activeMenuItem._options[which]._valid) { // If the menu item wasn't active, we do nothing. + _vm->_graphics->removeBackup(); + return; + } + } + } + } + } + + _vm->_graphics->removeBackup(); +} + +char Menu::getThingChar(byte which) { + static const char thingsChar[] = "WMBParCLguKeSnIohn"; // V=Vinegar + + char result; + switch (which) { + case kObjectWine: + if (_vm->_wineState == 3) + result = 'V'; // Vinegar + else + result = thingsChar[which - 1]; + break; + default: + result = thingsChar[which - 1]; + } + return result; +} + +byte Menu::getNameChar(People whose) { + static const char ladChar[] = "ASCDMTRwLfgeIyPu"; + static const char lassChar[] = "kG\0xB1o"; + + if (whose < kPeopleArkata) + return ladChar[whose - kPeopleAvalot]; + else + return lassChar[whose - kPeopleArkata]; +} + +Common::String Menu::getThing(byte which) { + static const char things[kObjectNum][20] = { + "Wine", "Money-bag", "Bodkin", "Potion", "Chastity belt", + "Crossbow bolt", "Crossbow", "Lute", "Pilgrim's badge", "Mushroom", "Key", + "Bell", "Scroll", "Pen", "Ink", "Clothes", "Habit", "Onion" + }; + + Common::String result; + switch (which) { + case kObjectWine: + switch (_vm->_wineState) { + case 1: + case 4: + result = Common::String(things[which - 1]); + break; + case 3: + result = "Vinegar"; + break; + } + break; + case kObjectOnion: + if (_vm->_rottenOnion) + result = Common::String("rotten onion"); + else + result = Common::String(things[which - 1]); + break; + default: + result = Common::String(things[which - 1]); + } + return result; +} + +bool Menu::isActive() { + return _menuActive; +} + +void Menu::init() { + _menuActive = false; +} + +void Menu::resetVariables() { + _lastPerson = kPeoplePardon; +} +} // End of namespace Avalanche. diff --git a/engines/avalanche/menu.h b/engines/avalanche/menu.h new file mode 100644 index 0000000000..a7ec8bf2db --- /dev/null +++ b/engines/avalanche/menu.h @@ -0,0 +1,181 @@ +/* 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 the original source code of Lord Avalot d'Argent version 1.3. + * Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman. + */ + +/* Original name: DROPDOWN A customized version of Oopmenu (qv). */ + +#ifndef AVALANCHE_MENU_H +#define AVALANCHE_MENU_H + +#include "common/str.h" + +namespace Avalanche { +class AvalancheEngine; + +class Menu; + +typedef void (Menu::*MenuFunc)(); +static const Color kMenuBackgroundColor = kColorLightgray; +static const Color kMenuBorderColor = kColorBlack; + +class HeadType { +public: + Common::String _title; + char _trigger, _altTrigger; + byte _position; + int16 _xpos, _xright; + MenuFunc _setupFunc, _chooseFunc; + + void init(char trig, char alTtrig, Common::String title, byte pos, MenuFunc setupFunc, MenuFunc chooseFunc, Menu *menu); + void draw(); + void highlight(); + bool parseAltTrigger(char key); + +private: + Menu *_menu; +}; + +struct OptionType { + Common::String _title; + byte _trigger; + Common::String _shortcut; + bool _valid; +}; + +class MenuItem { +public: + OptionType _options[12]; + uint16 _width, _left; + bool _firstlix; + int16 _flx1, _flx2, _fly; + bool _activeNow; // Is there an active option now? + byte _activeNum; // And if so, which is it? + byte _choiceNum; // Your choice? + + void init(Menu *menu); + void reset(); + void setupOption(Common::String title, char trigger, Common::String shortcut, bool valid); + void display(); + void wipe(); + void lightUp(Common::Point cursorPos); + void select(byte which); + +private: + byte _oldY; // used by lightUp + byte _optionNum; + byte _highlightNum; + + Menu *_menu; + + void displayOption(byte y, bool highlit); + void moveHighlight(int8 inc); + + // CHECKME: Useless function? + void parseKey(char c); +}; + +class MenuBar { +public: + HeadType _menuItems[8]; + byte _menuNum; + + void init(Menu *menu); + void createMenuItem(char trig, Common::String title, char altTrig, MenuFunc setupFunc, MenuFunc chooseFunc); + void draw(); + void chooseMenuItem(int16 x); + +private: + Menu *_menu; + + void setupMenuItem(byte which); + // CHECKME: Useless function + void parseAltTrigger(char c); +}; + +class Menu { +public: + friend class HeadType; + friend class MenuItem; + friend class MenuBar; + + MenuItem _activeMenuItem; + MenuBar _menuBar; + + Menu(AvalancheEngine *vm); + + void update(); + void setup(); // Standard menu bar. + bool isActive(); + void init(); + void resetVariables(); + +private: + static const byte kIndent = 5; + static const byte kSpacing = 10; + +// Checkme: Useless constants? +// static const Color kMenuFontColor = kColorBlack; +// static const Color kHighlightBackgroundColor = kColorBlack; +// static const Color kHighlightFontColor = kColorWhite; +// static const Color kDisabledColor = kColorDarkgray; + + Common::String people; + Common::String _verbStr; // what you can do with your object. :-) + bool _menuActive; // Kludge so we don't have to keep referring to the menu. + People _lastPerson; // Last person to have been selected using the People menu. + + AvalancheEngine *_vm; + + Common::String selectGender(byte x); // Returns "im" for boys, and "er" for girls. + void findWhatYouCanDoWithIt(); + void drawMenuText(int16 x, int16 y, char trigger, Common::String text, bool valid, bool highlighted); + void bleep(); + + char getThingChar(byte which); + byte getNameChar(People whose); + Common::String getThing(byte which); + + void setupMenuGame(); + void setupMenuFile(); + void setupMenuAction(); + void setupMenuPeople(); + void setupMenuObjects(); + void setupMenuWith(); + + void runMenuGame(); + void runMenuFile(); + void runMenuAction(); + void runMenuObjects(); + void runMenuPeople(); + void runMenuWith(); + + // CHECKME: Useless function? + void parseKey(char r, char re); +}; + +} // End of namespace Avalanche. + +#endif // AVALANCHE_MENU_H diff --git a/engines/avalanche/module.mk b/engines/avalanche/module.mk new file mode 100644 index 0000000000..9c1205df02 --- /dev/null +++ b/engines/avalanche/module.mk @@ -0,0 +1,26 @@ +MODULE := engines/avalanche + +MODULE_OBJS = \ + animation.o \ + avalanche.o \ + avalot.o \ + background.o \ + closing.o \ + console.o \ + detection.o \ + graphics.o \ + menu.o \ + parser.o \ + pingo.o \ + dialogs.o \ + sequence.o \ + sound.o \ + timer.o + +# This module can be built as a plugin +ifeq ($(ENABLE_AVALANCHE), DYNAMIC_PLUGIN) +PLUGIN := 1 +endif + +# Include common rules +include $(srcdir)/rules.mk diff --git a/engines/avalanche/parser.cpp b/engines/avalanche/parser.cpp new file mode 100644 index 0000000000..fc176c78b0 --- /dev/null +++ b/engines/avalanche/parser.cpp @@ -0,0 +1,2470 @@ +/* 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 the original source code of Lord Avalot d'Argent version 1.3. + * Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman. + */ + +#include "avalanche/avalanche.h" +#include "avalanche/parser.h" + +#include "gui/saveload.h" + +namespace Avalanche { + +const char *Parser::kCopyright = "1995"; +const char *Parser::kVersionNum = "1.30"; + +Parser::Parser(AvalancheEngine *vm) { + _vm = vm; +} + +void Parser::init() { + if (!_inputText.empty()) + _inputText.clear(); + _inputTextPos = 0; + + _weirdWord = false; + + // Initailaze the vocabulary. + // Verbs: 1-49 + _vocabulary[0].init(1, "EXAMINE"); + _vocabulary[1].init(1, "READ"); + _vocabulary[2].init(1, "XAM"); + _vocabulary[3].init(2, "OPEN"); + _vocabulary[4].init(2, "LEAVE"); + _vocabulary[5].init(2, "UNLOCK"); + _vocabulary[6].init(3, "PAUSE"); + _vocabulary[7].init(47, "TA"); // Early to avoid Take and Talk. + _vocabulary[8].init(4, "TAKE"); + _vocabulary[9].init(4, "GET"); + _vocabulary[10].init(4, "PICK"); + _vocabulary[11].init(5, "DROP"); + _vocabulary[12].init(6, "INVENTORY"); + _vocabulary[13].init(7, "TALK"); + _vocabulary[14].init(7, "SAY"); + _vocabulary[15].init(7, "ASK"); + _vocabulary[16].init(8, "GIVE"); + _vocabulary[17].init(9, "DRINK"); + _vocabulary[18].init(9, "IMBIBE"); + _vocabulary[19].init(9, "DRAIN"); + _vocabulary[20].init(10, "LOAD"); + _vocabulary[21].init(10, "RESTORE"); + _vocabulary[22].init(11, "SAVE"); + _vocabulary[23].init(12, "BRIBE"); + _vocabulary[24].init(12, "PAY"); + _vocabulary[25].init(13, "LOOK"); + _vocabulary[26].init(14, "BREAK"); + _vocabulary[27].init(15, "QUIT"); + _vocabulary[28].init(15, "EXIT"); + _vocabulary[29].init(16, "SIT"); + _vocabulary[30].init(16, "SLEEP"); + _vocabulary[31].init(17, "STAND"); + + _vocabulary[32].init(18, "GO"); + _vocabulary[33].init(19, "INFO"); + _vocabulary[34].init(20, "UNDRESS"); + _vocabulary[35].init(20, "DOFF"); + _vocabulary[36].init(21, "DRESS"); + _vocabulary[37].init(21, "WEAR"); + _vocabulary[38].init(21, "DON"); + _vocabulary[39].init(22, "PLAY"); + _vocabulary[40].init(22, "STRUM"); + _vocabulary[41].init(23, "RING"); + _vocabulary[42].init(24, "HELP"); + _vocabulary[43].init(25, "KENDAL"); + _vocabulary[44].init(26, "CAPYBARA"); + _vocabulary[45].init(27, "BOSS"); + _vocabulary[46].init(255, "NINET"); // block for NINETY + _vocabulary[47].init(28, "URINATE"); + _vocabulary[48].init(28, "MINGITE"); + _vocabulary[49].init(29, "NINETY"); + _vocabulary[50].init(30, "ABRACADABRA"); + _vocabulary[51].init(30, "PLUGH"); + _vocabulary[52].init(30, "XYZZY"); + _vocabulary[53].init(30, "HOCUS"); + _vocabulary[54].init(30, "POCUS"); + _vocabulary[55].init(30, "IZZY"); + _vocabulary[56].init(30, "WIZZY"); + _vocabulary[57].init(30, "PLOVER"); + _vocabulary[58].init(30, "MELENKURION"); + _vocabulary[59].init(30, "ZORTON"); + _vocabulary[60].init(30, "BLERBI"); + _vocabulary[61].init(30, "THURB"); + _vocabulary[62].init(30, "SNOEZE"); + _vocabulary[63].init(30, "SAMOHT"); + _vocabulary[64].init(30, "NOSIDE"); + _vocabulary[65].init(30, "PHUGGG"); + _vocabulary[66].init(30, "KNERL"); + _vocabulary[67].init(30, "MAGIC"); + _vocabulary[68].init(30, "KLAETU"); + _vocabulary[69].init(30, "VODEL"); + _vocabulary[70].init(30, "BONESCROLLS"); + _vocabulary[71].init(30, "RADOF"); + + _vocabulary[72].init(31, "RESTART"); + _vocabulary[73].init(32, "SWALLOW"); + _vocabulary[74].init(32, "EAT"); + _vocabulary[75].init(33, "LISTEN"); + _vocabulary[76].init(33, "HEAR"); + _vocabulary[77].init(34, "BUY"); + _vocabulary[78].init(34, "PURCHASE"); + _vocabulary[79].init(34, "ORDER"); + _vocabulary[80].init(34, "DEMAND"); + _vocabulary[81].init(35, "ATTACK"); + _vocabulary[82].init(35, "HIT"); + _vocabulary[83].init(35, "KILL"); + _vocabulary[84].init(35, "PUNCH"); + _vocabulary[85].init(35, "KICK"); + _vocabulary[86].init(35, "SHOOT"); + _vocabulary[87].init(35, "FIRE"); + + // Passwords: 36 + _vocabulary[88].init(36, "TIROS"); + _vocabulary[89].init(36, "WORDY"); + _vocabulary[90].init(36, "STACK"); + _vocabulary[91].init(36, "SHADOW"); + _vocabulary[92].init(36, "OWL"); + _vocabulary[93].init(36, "ACORN"); + _vocabulary[94].init(36, "DOMESDAY"); + _vocabulary[95].init(36, "FLOPPY"); + _vocabulary[96].init(36, "DIODE"); + _vocabulary[97].init(36, "FIELD"); + _vocabulary[98].init(36, "COWSLIP"); + _vocabulary[99].init(36, "OSBYTE"); + _vocabulary[100].init(36, "OSCLI"); + _vocabulary[101].init(36, "TIMBER"); + _vocabulary[102].init(36, "ADVAL"); + _vocabulary[103].init(36, "NEUTRON"); + _vocabulary[104].init(36, "POSITRON"); + _vocabulary[105].init(36, "ELECTRON"); + _vocabulary[106].init(36, "CIRCUIT"); + _vocabulary[107].init(36, "AURUM"); + _vocabulary[108].init(36, "PETRIFY"); + _vocabulary[109].init(36, "EBBY"); + _vocabulary[110].init(36, "CATAPULT"); + _vocabulary[111].init(36, "GAMERS"); + _vocabulary[112].init(36, "FUDGE"); + _vocabulary[113].init(36, "CANDLE"); + _vocabulary[114].init(36, "BEEB"); + _vocabulary[115].init(36, "MICRO"); + _vocabulary[116].init(36, "SESAME"); + _vocabulary[117].init(36, "LORDSHIP"); + + _vocabulary[118].init(37, "DIR"); + _vocabulary[119].init(37, "LS"); + _vocabulary[120].init(38, "DIE"); + _vocabulary[121].init(39, "SCORE"); + _vocabulary[122].init(40, "PUT"); + _vocabulary[123].init(40, "INSERT"); + _vocabulary[124].init(41, "KISS"); + _vocabulary[125].init(41, "SNOG"); + _vocabulary[126].init(41, "CUDDLE"); + _vocabulary[127].init(42, "CLIMB"); + _vocabulary[128].init(42, "CLAMBER"); + _vocabulary[129].init(43, "JUMP"); + _vocabulary[130].init(44, "HIGHSCORES"); + _vocabulary[131].init(44, "HISCORES"); + _vocabulary[132].init(45, "WAKEN"); + _vocabulary[133].init(45, "AWAKEN"); + _vocabulary[134].init(46, "HELLO"); + _vocabulary[135].init(46, "HI"); + _vocabulary[136].init(46, "YO"); + _vocabulary[137].init(47, "THANKS"); // = 47, "ta", which was defined earlier. + + // Nouns - Objects: 50-100 + _vocabulary[138].init(50, "WINE"); + _vocabulary[139].init(50, "BOOZE"); + _vocabulary[140].init(50, "NASTY"); + _vocabulary[141].init(50, "VINEGAR"); + _vocabulary[142].init(51, "MONEYBAG"); + _vocabulary[143].init(51, "BAG"); + _vocabulary[144].init(51, "CASH"); + _vocabulary[145].init(51, "DOSH"); + _vocabulary[146].init(51, "WALLET"); + _vocabulary[147].init(52, "BODKIN"); + _vocabulary[148].init(52, "DAGGER"); + _vocabulary[149].init(53, "POTION"); + _vocabulary[150].init(54, "CHASTITY"); + _vocabulary[151].init(54, "BELT"); + _vocabulary[152].init(55, "BOLT"); + _vocabulary[153].init(55, "ARROW"); + _vocabulary[154].init(55, "DART"); + _vocabulary[155].init(56, "CROSSBOW"); + _vocabulary[156].init(56, "BOW"); + _vocabulary[157].init(57, "LUTE"); + _vocabulary[158].init(58, "PILGRIM"); + _vocabulary[159].init(58, "BADGE"); + _vocabulary[160].init(59, "MUSHROOMS"); + _vocabulary[161].init(59, "TOADSTOOLS"); + _vocabulary[162].init(60, "KEY"); + _vocabulary[163].init(61, "BELL"); + _vocabulary[164].init(62, "PRESCRIPT"); + _vocabulary[165].init(62, "SCROLL"); + _vocabulary[166].init(62, "MESSAGE"); + _vocabulary[167].init(63, "PEN"); + _vocabulary[168].init(63, "QUILL"); + _vocabulary[169].init(64, "INK"); + _vocabulary[170].init(64, "INKPOT"); + _vocabulary[171].init(65, "CLOTHES"); + _vocabulary[172].init(66, "HABIT"); + _vocabulary[173].init(66, "DISGUISE"); + _vocabulary[174].init(67, "ONION"); + + _vocabulary[175].init(99, "PASSWORD"); + + // Objects from Also are placed between 101 and 131. + // Nouns - People - Male: 150-174 + _vocabulary[176].init(150, "AVVY"); + _vocabulary[177].init(150, "AVALOT"); + _vocabulary[178].init(150, "YOURSELF"); + _vocabulary[179].init(150, "ME"); + _vocabulary[180].init(150, "MYSELF"); + _vocabulary[181].init(151, "SPLUDWICK"); + _vocabulary[182].init(151, "THOMAS"); + _vocabulary[183].init(151, "ALCHEMIST"); + _vocabulary[184].init(151, "CHEMIST"); + _vocabulary[185].init(152, "CRAPULUS"); + _vocabulary[186].init(152, "SERF"); + _vocabulary[187].init(152, "SLAVE"); + _vocabulary[188].init(158, "DU"); // Put in early for Baron DU Lustie to save confusion with Duck & Duke. + _vocabulary[189].init(152, "CRAPPY"); + _vocabulary[190].init(153, "DUCK"); + _vocabulary[191].init(153, "DOCTOR"); + _vocabulary[192].init(154, "MALAGAUCHE"); + _vocabulary[193].init(155, "FRIAR"); + _vocabulary[194].init(155, "TUCK"); + _vocabulary[195].init(156, "ROBIN"); + _vocabulary[196].init(156, "HOOD"); + _vocabulary[197].init(157, "CWYTALOT"); + _vocabulary[198].init(157, "GUARD"); + _vocabulary[199].init(157, "BRIDGEKEEP"); + _vocabulary[200].init(158, "BARON"); + _vocabulary[201].init(158, "LUSTIE"); + _vocabulary[202].init(159, "DUKE"); + _vocabulary[203].init(159, "GRACE"); + _vocabulary[204].init(160, "DOGFOOD"); + _vocabulary[205].init(160, "MINSTREL"); + _vocabulary[206].init(161, "TRADER"); + _vocabulary[207].init(161, "SHOPKEEPER"); + _vocabulary[208].init(161, "STALLHOLDER"); + _vocabulary[209].init(162, "PILGRIM"); + _vocabulary[210].init(162, "IBYTHNETH"); + _vocabulary[211].init(163, "ABBOT"); + _vocabulary[212].init(163, "AYLES"); + _vocabulary[213].init(164, "PORT"); + _vocabulary[214].init(165, "SPURGE"); + _vocabulary[215].init(166, "JACQUES"); + _vocabulary[216].init(166, "SLEEPER"); + _vocabulary[217].init(166, "RINGER"); + + // Nouns - People - Female: 175-199 + _vocabulary[218].init(175, "WIFE"); + _vocabulary[219].init(175, "ARKATA"); + _vocabulary[220].init(176, "GEDALODAVA"); + _vocabulary[221].init(176, "GEIDA"); + _vocabulary[222].init(176, "PRINCESS"); + _vocabulary[223].init(178, "WISE"); + _vocabulary[224].init(178, "WITCH"); + + // Pronouns: 200-224 + _vocabulary[225].init(200, "HIM"); + _vocabulary[226].init(200, "MAN"); + _vocabulary[227].init(200, "GUY"); + _vocabulary[228].init(200, "DUDE"); + _vocabulary[229].init(200, "CHAP"); + _vocabulary[230].init(200, "FELLOW"); + _vocabulary[231].init(201, "HER"); + _vocabulary[232].init(201, "GIRL"); + _vocabulary[233].init(201, "WOMAN"); + _vocabulary[234].init(202, "IT"); + _vocabulary[235].init(202, "THING"); + _vocabulary[236].init(203, "MONK"); + _vocabulary[237].init(204, "BARMAN"); + _vocabulary[238].init(204, "BARTENDER"); + + // Prepositions: 225-249 + _vocabulary[239].init(225, "TO"); + _vocabulary[240].init(226, "AT"); + _vocabulary[241].init(227, "UP"); + _vocabulary[242].init(228, "INTO"); + _vocabulary[243].init(228, "INSIDE"); + _vocabulary[244].init(229, "OFF"); + _vocabulary[245].init(230, "UP"); + _vocabulary[246].init(231, "DOWN"); + _vocabulary[247].init(232, "ON"); + + // Please: 251 + _vocabulary[248].init(251, "PLEASE"); + + // About: 252 + _vocabulary[249].init(252, "ABOUT"); + _vocabulary[250].init(252, "CONCERNING"); + + // Swear words: 253 + /* I M P O R T A N T M E S S A G E + + DO *NOT* READ THE LINES BELOW IF YOU ARE OF A SENSITIVE + DISPOSITION. THOMAS IS *NOT* RESPONSIBLE FOR THEM. + GOODNESS KNOWS WHO WROTE THEM. + READ THEM AT YOUR OWN RISK. BETTER STILL, DON'T READ THEM. + WHY ARE YOU SNOOPING AROUND IN MY PROGRAM, ANYWAY? */ + _vocabulary[251].init(253, "SHIT"); + _vocabulary[252].init(28 , "PISS"); + _vocabulary[253].init(28 , "PEE"); + _vocabulary[254].init(253, "FART"); + _vocabulary[255].init(253, "FUCK"); + _vocabulary[256].init(253, "BALLS"); + _vocabulary[257].init(253, "BLAST"); + _vocabulary[258].init(253, "BUGGER"); + _vocabulary[259].init(253, "KNICKERS"); + _vocabulary[260].init(253, "BLOODY"); + _vocabulary[261].init(253, "HELL"); + _vocabulary[262].init(253, "DAMN"); + _vocabulary[263].init(253, "SMEG"); + // ...and other even ruder words. You didn't read them, did you? Good. + + // Answer-back smart-alec words: 249 + _vocabulary[264].init(249, "YES"); + _vocabulary[265].init(249, "NO"); + _vocabulary[266].init(249, "BECAUSE"); + + // Noise words: 255 + _vocabulary[267].init(255, "THE"); + _vocabulary[268].init(255, "A"); + _vocabulary[269].init(255, "NOW"); + _vocabulary[270].init(255, "SOME"); + _vocabulary[271].init(255, "AND"); + _vocabulary[272].init(255, "THAT"); + _vocabulary[273].init(255, "POCUS"); + _vocabulary[274].init(255, "HIS"); + _vocabulary[275].init(255, "THIS"); + _vocabulary[276].init(255, "SENTINEL"); // for "Ken SENT Me" +} + +void Parser::handleInputText(const Common::Event &event) { + byte inChar = event.kbd.ascii; + warning("STUB: Parser::handleInputText()"); +// if (_vm->_menu->_activeMenuItem._activeNow) { +// _vm->_menu->parseKey(inChar, _vm->_enhanced->extd); +// } else { + if (_inputText.size() < 76) { + if ((inChar == '"') || (inChar == '`')) { + if (_quote) + inChar = '`'; + else + inChar = '"'; + _quote = !_quote; // quote - unquote + } + + _inputText.insertChar(inChar, _inputTextPos); + _inputTextPos++; + plotText(); + } else + _vm->_sound->blip(); +// } +} + +void Parser::handleBackspace() { + if (_vm->_menu->_activeMenuItem._activeNow) + return; + + if (_inputTextPos > 0) { + _inputTextPos--; + if ((_inputText[_inputTextPos] == '"') || (_inputText[_inputTextPos] == '`')) + _quote = !_quote; + _inputText.deleteChar(_inputTextPos); + plotText(); + } else + _vm->_sound->blip(); +} + +void Parser::handleReturn() { + if (_vm->_menu->_activeMenuItem._activeNow) + tryDropdown(); + else if (!_inputText.empty()) { + _inputTextBackup = _inputText; + parse(); + doThat(); + _inputText.clear(); + wipeText(); + } +} + +void Parser::handleFunctionKey(const Common::Event &event) { + switch (event.kbd.keycode) { + case Common::KEYCODE_F1: + _vm->callVerb(kVerbCodeHelp); + break; + case Common::KEYCODE_F2: + if (event.kbd.flags & Common::KBD_CTRL) { + clearWords(); + _vm->callVerb(kVerbCodeSave); + } else + _vm->_sound->toggleSound(); + break; + case Common::KEYCODE_F3: + if (event.kbd.flags & Common::KBD_CTRL) { + clearWords(); + _vm->callVerb(kVerbCodeLoad); + } else if (_inputText.size() < _inputTextBackup.size()) { + _inputText = _inputText + &(_inputTextBackup.c_str()[_inputText.size()]); + _inputTextPos = _inputText.size(); + plotText(); + } + break; + case Common::KEYCODE_F4: + if (event.kbd.flags & Common::KBD_ALT) + _vm->callVerb(kVerbCodeQuit); + else + _vm->callVerb(kVerbCodeRestart); + break; + case Common::KEYCODE_F5: { + _person = kPeoplePardon; + _thing = kPardon; + Common::String f5does = _vm->f5Does(); + VerbCode verb = (VerbCode)(byte)f5does[0]; + _vm->callVerb(verb); + } + break; + case Common::KEYCODE_F6: + _vm->callVerb(kVerbCodePause); + break; + case Common::KEYCODE_F7: + if (event.kbd.flags & Common::KBD_CTRL) + _vm->majorRedraw(); + else + _vm->callVerb(kVerbCodeOpen); + break; + case Common::KEYCODE_F8: + _vm->callVerb(kVerbCodeLook); + break; + case Common::KEYCODE_F9: + _vm->callVerb(kVerbCodeScore); + break; + case Common::KEYCODE_F10: + if (event.kbd.flags & Common::KBD_SHIFT) + _vm->callVerb(kVerbCodeInfo); + else + _vm->callVerb(kVerbCodeQuit); + break; + case Common::KEYCODE_F11: + clearWords(); + _vm->callVerb(kVerbCodeSave); + break; + case Common::KEYCODE_F12: + clearWords(); + _vm->callVerb(kVerbCodeLoad); + break; + default: + break; + } +} + +void Parser::plotText() { + CursorMan.showMouse(false); + cursorOff(); + + _vm->_graphics->clearTextBar(); + _vm->_graphics->drawNormalText(_inputText, _vm->_font, 8, 24, 161, kColorWhite); + + cursorOn(); + CursorMan.showMouse(true); +} + +void Parser::cursorOn() { + if (_cursorState == true) + return; + _vm->_graphics->drawCursor(_inputTextPos); + _cursorState = true; +} + +void Parser::cursorOff() { + if (_cursorState == false) + return; + _vm->_graphics->drawCursor(_inputTextPos); + _cursorState = false; +} + +void Parser::tryDropdown() { + warning("STUB: Parser::tryDropdown()"); // TODO: Implement at the same time with Dropdown's keyboard handling. +} + +int16 Parser::getPos(const Common::String &crit, const Common::String &src) { + if (src.contains(crit)) + return strstr(src.c_str(),crit.c_str()) - src.c_str(); + else + return -1; +} + +void Parser::wipeText() { + CursorMan.showMouse(false); + cursorOff(); + + _vm->_graphics->clearTextBar(); + + _quote = true; + _inputTextPos = 0; + + cursorOn(); + CursorMan.showMouse(true); +} + +void Parser::clearWords() { + for (int i = 0; i < 11; i++) { + if (!_realWords[i].empty()) + _realWords[i].clear(); + } +} + +byte Parser::wordNum(Common::String word) { + if (word.empty()) + return 0; + + for (int32 i = kParserWordsNum - 1; i >= 0; i--) { + if (_vocabulary[i]._word == word) + return _vocabulary[i]._number; + } + + // If not found as a whole, we look for it as a substring. + for (int32 i = kParserWordsNum - 1; i >= 0; i--) { + if (Common::String(_vocabulary[i]._word.c_str(), word.size()) == word) + return _vocabulary[i]._number; + } + + return kPardon; +} + +void Parser::replace(Common::String oldChars, byte newChar) { + int16 pos = getPos(oldChars, _thats); + while (pos != -1) { + if (newChar == 0) + _thats.deleteChar(pos); + else { + for (uint i = pos; i < pos + oldChars.size(); i++) + _thats.deleteChar(pos); + _thats.insertChar(newChar, pos); + } + pos = getPos(oldChars, _thats); + } +} + +Common::String Parser::rank() { + static const RankType ranks[9] = { + {0, "Beginner"}, {10, "Novice"}, + {20, "Improving"}, {35, "Not bad"}, + {50, "Passable"}, {65, "Good"}, + {80, "Experienced"}, {108, "The BEST!"}, + {32767, "copyright'93"} + }; + + for (int i = 0; i < 8; i++) { + if ((_vm->_dnascore >= ranks[i]._score) && (_vm->_dnascore < ranks[i + 1]._score)) + return Common::String(ranks[i]._title); + } + return ""; +} + +Common::String Parser::totalTime() { + uint16 h, m, s; + + h = (uint16)(_vm->_totalTime / 65535); + s = (uint16)(_vm->_totalTime % 65535); + m = s / 60; + s = s % 60; + + Common::String result = "You've been playing for "; + if (h > 0) + result += Common::String::format("%d hours, ", h); + if ((m > 0) || (h != 0)) + result += Common::String::format("%d minutes and ", m); + return result + Common::String::format("%d seconds", s); +} + +void Parser::cheatParse(Common::String codes) { + warning("STUB: Parser::cheatParse()"); +} + +void Parser::stripPunctuation(Common::String &word) { + const char punct[] = "~`!@#$%^&*()_+-={}[]:\"|;'\\,./<>?"; + + for (int i = 0; i < 32; i++) { + for (;;) { + int16 pos = getPos(Common::String(punct[i]), word); + if (pos == -1) + break; + word.deleteChar(pos); + } + } +} + +void Parser::displayWhat(byte target, bool animate, bool &ambiguous) { + if (target == kPardon) { + ambiguous = true; + if (animate) + _vm->_dialogs->displayText("Whom?"); + else + _vm->_dialogs->displayText("What?"); + } else { + if (animate) { + Common::String tmpStr = Common::String::format("{ %s }", _vm->getName((People)target).c_str()); + _vm->_dialogs->displayText(tmpStr); + } else { + Common::String z = _vm->getItem(target); + if (z != "") { + Common::String tmpStr = Common::String::format("{ %s }", z.c_str()); + _vm->_dialogs->displayText(tmpStr); + } + } + } +} + +bool Parser::doPronouns() { + bool ambiguous = false; + + for (uint i = 0; i < _thats.size(); i++) { + byte wordCode = _thats[i]; + switch (wordCode) { + case 200: + displayWhat(_vm->_him, true, ambiguous); + _thats.setChar(_vm->_him, i); + break; + case 201: + displayWhat(_vm->_her, true, ambiguous); + _thats.setChar(_vm->_her, i); + break; + case 202: + displayWhat(_vm->_it, false, ambiguous); + _thats.setChar(_vm->_it, i); + break; + } + } + + return ambiguous; +} + +void Parser::properNouns() { + _inputText.toLowercase(); + + // We set every word's first character to uppercase. + for (uint i = 1; i < (_inputText.size() - 1); i++) { + if (_inputText[i] == ' ') + _inputText.setChar(toupper(_inputText[i + 1]), i + 1); + } + + // And the first character as well. + _inputText.setChar(toupper(_inputText[0]), 0); +} + +void Parser::storeInterrogation(byte interrogation) { + if (_inputText.empty()) + return; + + // Strip _inputText: + while ((_inputText[0] == ' ') && (!_inputText.empty())) + _inputText.deleteChar(0); + while ((_inputText.lastChar() == ' ') && (!_inputText.empty())) + _inputText.deleteLastChar(); + + _vm->_timer->loseTimer(Timer::kReasonCardiffsurvey); // If you want to use any other timer, put this into the case statement. + + switch (interrogation) { + case 1: + _inputText.toLowercase(); + _vm->_dialogs->sayIt(_inputText); + _vm->_favouriteDrink = _inputText; + _vm->_cardiffQuestionNum = 2; + break; + case 2: + properNouns(); + _vm->_dialogs->sayIt(_inputText); + _vm->_favouriteSong = _inputText; + _vm->_cardiffQuestionNum = 3; + break; + case 3: + properNouns(); + _vm->_dialogs->sayIt(_inputText); + _vm->_worstPlaceOnEarth = _inputText; + _vm->_cardiffQuestionNum = 4; + break; + case 4: + _inputText.toLowercase(); + _vm->_dialogs->sayIt(_inputText); + if (!_vm->_spareEvening.empty()) + _vm->_spareEvening.clear(); + _vm->_spareEvening = _inputText; + _vm->_dialogs->displayScrollChain('z', 5); // His closing statement... + _vm->_animation->_sprites[1]->walkTo(3); // The end of the drawbridge + _vm->_animation->_sprites[1]->_vanishIfStill = true; // Then go away! + _vm->_magics[1]._operation = kMagicNothing; + _vm->_cardiffQuestionNum = 5; + break; + case 99: + //store_high(_inputText); + warning("STUB: Parser::store_interrogation()"); + break; + } + + if (interrogation < 4) + _vm->_timer->cardiffSurvey(); +} + + + +void Parser::parse() { + // First parsing - word identification + if (!_thats.empty()) + _thats.clear(); + + _polite = false; + _verb = kVerbCodePardon; + _thing = kPardon; + _thing2 = kPardon; + _person = kPeoplePardon; + clearWords(); + + + // A cheat mode attempt. + if (_inputText[0] == '.') { + cheatParse(_inputText); + _thats = kNothing; + return; + } + + // Are we being interrogated right now? + if (_vm->_interrogation > 0) { + storeInterrogation(_vm->_interrogation); + _weirdWord = true; + return; + } + + // Actually process the command. + Common::String inputText = _inputText + ' '; + Common::String inputTextUpper = inputText; + byte n = 0; + inputTextUpper.toUppercase(); + while (!inputTextUpper.empty()) { + while ((!inputTextUpper.empty()) && (inputTextUpper[0] == ' ')) { + inputTextUpper.deleteChar(0); + inputText.deleteChar(0); + } + if (inputTextUpper.empty()) + break; + + // Get the following word of the strings. + byte size = getPos(Common::String(' '), inputTextUpper) + 1; + char *subStr = new char[size]; + Common::strlcpy(subStr, inputTextUpper.c_str(), size); + Common::String thisword = subStr; + Common::strlcpy(subStr, inputText.c_str(), size); + _realWords[n] = subStr; + delete[] subStr; + + stripPunctuation(inputTextUpper); + + bool notfound = true; + + // Check also[] first, which contains words about the actual room. + if (!thisword.empty()) { + for (int i = 0; i < 31; i++) { + if ((_vm->_also[i][0]) && (getPos(',' + thisword, *_vm->_also[i][0]) > -1)) { + _thats += Common::String(99 + i); + notfound = false; + } + } + } + + // Check Accis's own table (words[]) for "global" commands. + if (notfound) { + byte answer = wordNum(thisword); + if (answer == kPardon) { + notfound = true; + _thats = _thats + kPardon; + } else + _thats = _thats + answer; + n++; + } + + // Delete words we already processed. + int16 spacePos = getPos(Common::String(' '), inputTextUpper); + if (spacePos > -1) { + for (int i = 0; i <= spacePos; i++) + inputTextUpper.deleteChar(0); + } + + spacePos = getPos(Common::String(' '), inputText); + if (spacePos > -1) { + for (int i = 0; i <= spacePos; i++) + inputText.deleteChar(0); + } + } + + Common::String unkString; + int16 pos = getPos(Common::String('\xFE'), _thats); + if (pos > -1) + unkString = _realWords[pos]; + else + unkString.clear(); + + // Replace words' codes that mean the same. + replace(Common::String("\xFF"), 0); // zap noise words + replace(Common::String("\xD\xE2"), 1); // "look at" = "examine" + replace(Common::String("\xD\xE4"), 1); // "look in" = "examine" + replace(Common::String("\x4\xE6"), 17); // "get up" = "stand" + replace(Common::String("\x4\xE7"), 17); // "get down" = "stand"... well, why not? + replace(Common::String("\x12\xE4"), 2); // "go in" = "open [door]" + replace(Common::String("\x1C\xE5"), 253); // "P' off" is a swear word + replace(Common::String("\x4\x6"), 6); // "Take inventory" (remember Colossal Adventure?) + replace(Common::String("\x28\xE8"), 21); // "put on" = "don" + replace(Common::String("\x4\xE5"), 20); // "take off" = "doff" + + // Words that could mean more than one _person + if (_vm->_room == kRoomNottsPub) + replace(Common::String('\xCC'), 164); // Barman = Port + else + replace(Common::String('\xCC'), 154); // Barman = Malagauche + + switch (_vm->_room) { + case kRoomAylesOffice: + replace(Common::String('\xCB'), 163); // Monk = Ayles + break; + case kRoomMusicRoom: + replace(Common::String('\xCB'), 166); // Monk = Jacques + break; + default: + replace(Common::String('\xCB'), 162); // Monk = Ibythneth + } + + if (doPronouns()) { + _weirdWord = true; + _thats = kNothing; + return; + } + + // Second parsing. + _vm->_subjectNum = 0; // Find subject of conversation. + + for (int i = 0; (i < 11) && !_realWords[i].empty(); i++) { + if ((_realWords[i][0] == '\'') || (_realWords[i][0] == '\"')) { + _vm->_subjectNum = (byte)_thats[i]; + _thats.setChar(kMoved, i); + break; + } + } + + if ((_vm->_subjectNum == 0) && !_thats.empty()) { // Still not found. + for (uint16 i = 0; i < _thats.size() - 1; i++) { + if ((byte)_thats[i] == 252) { // The word is "about", or something similar. + _vm->_subjectNum = (byte)_thats[i + 1]; + _thats.setChar(0, i + 1); + break; + } + } + } + + if ((_vm->_subjectNum == 0) && !_thats.empty()) { // STILL not found! Must be the word after "say". + for (uint16 i = 0; i < _thats.size() - 1; i++) { + if (((byte)_thats[i] == 7) && ((byte)_thats[i + 1] != 0) && !((225 <= (byte)_thats[i + 1]) && ((byte)_thats[i + 1] <= 229))) { + // SAY not followed by a preposition + _vm->_subjectNum = (byte)_thats[i + 1]; + _thats.setChar(0, i + 1); + break; + } + } + } + + for (int16 i = _thats.size() - 1; i >= 0; i--) { // Reverse order, so first will be used. + byte curChar = (byte)_thats[i]; + if ((curChar == 253) || (curChar == 249) || ((1 <= curChar) && (curChar <= 49))) + _verb = (VerbCode)curChar; + else if ((50 <= curChar) && (curChar <= 149)) { + _thing2 = _thing; + _thing = curChar; + } else if ((150 <= curChar) && (curChar <= 199)) + _person = (People)curChar; + else if (curChar == 251) + _polite = true; + } + + if ((!unkString.empty()) && (_verb != kVerbCodeExam) && (_verb != kVerbCodeTalk) && + (_verb != kVerbCodeSave) && (_verb != kVerbCodeLoad) && (_verb != kVerbCodeDir)) { + Common::String tmpStr = Common::String::format("Sorry, but I have no idea what \"%s\" means. Can you rephrase it?", unkString.c_str()); + _vm->_dialogs->displayText(tmpStr); + _weirdWord = true; + } else + _weirdWord = false; + + if (_thats.empty()) + _thats = kNothing; + + if (_thing != kPardon) + _vm->_it = _thing; + + if (_person != kPardon) { + if (_person < kPeopleArkata) + _vm->_him = _person; + else + _vm->_her = _person; + } +} + +void Parser::examineObject() { + if (_thing != _vm->_thinks) + _vm->thinkAbout(_thing, AvalancheEngine::kThing); + switch (_thing) { + case kObjectWine : + // 4 is perfect wine. 0 is not holding the wine. + switch (_vm->_wineState) { + case 1: + // Normal examine wine scroll + _vm->_dialogs->displayScrollChain('t', 1); + break; + case 2: + // Bad wine + _vm->_dialogs->displayScrollChain('d', 6); + break; + case 3: + // Vinegar + _vm->_dialogs->displayScrollChain('d', 7); + break; + } + break; + case kObjectOnion: + if (_vm->_rottenOnion) + // Yucky onion + _vm->_dialogs->displayScrollChain('q', 21); + else + // Normal onion + _vm->_dialogs->displayScrollChain('t', 18); + break; + default: + // Ordinarily + _vm->_dialogs->displayScrollChain('t', _thing); + } +} + +bool Parser::isPersonHere() { + // Person equivalent of "isHolding". + if ((_person == kPeoplePardon) || (_person == kPeopleNone) || (_vm->getRoom(_person) == _vm->_room)) + return true; + else { + Common::String tmpStr; + if (_person < kPeopleArkata) + tmpStr = "He isn't around at the moment."; + else + tmpStr = "She isn't around at the moment."; + _vm->_dialogs->displayText(tmpStr); + return false; + } +} + +void Parser::exampers() { + if (isPersonHere()) { + if (_thing != _vm->_thinks) + _vm->thinkAbout(_person, AvalancheEngine::kPerson); + + byte newPerson = _person - 149; + + if ((_person == kPeopleDogfood) && _vm->_wonNim) + // "I'm Not Playing!" + _vm->_dialogs->displayScrollChain('Q', 8); + else if ((_person == kPeopleDuLustie) && _vm->_lustieIsAsleep) + // He's asleep. + _vm->_dialogs->displayScrollChain('Q', 65); + else + _vm->_dialogs->displayScrollChain('p', newPerson); + + if ((_person == kPeopleAyles) && !_vm->_aylesIsAwake) + _vm->_dialogs->displayScrollChain('Q', 13); + + // CHECKME: Present in the original, but it doesn't make sense. + // _person = newPerson; + } +} + +/** + * Return whether Avvy is holding an object or not + * @remarks Originally called 'holding' + */ +bool Parser::isHolding() { + // Also object + if ((51 <= _thing) && (_thing <= 99)) + return true; + + bool holdingResult = false; + + if (_thing > 100) + _vm->_dialogs->displayText("Be reasonable!"); + else if (!_vm->_objects[_thing - 1]) + // Verbs that need "_thing" to be in the inventory. + _vm->_dialogs->displayText("You're not holding it, Avvy."); + else + holdingResult = true; + + return holdingResult; +} + +void Parser::openBox(bool isOpening) { + if ((_vm->_room == kRoomYours) && (_thing == 54)) { + _vm->_background->draw(-1, -1, 4); + + _vm->_background->update(); + _vm->_animation->animLink(); + _vm->_graphics->refreshScreen(); + + _vm->_system->delayMillis(55); + + if (!isOpening) { + _vm->_background->draw(-1, -1, 5); + _vm->_background->update(); + _vm->_animation->animLink(); + _vm->_graphics->refreshScreen(); + } + } +} + +void Parser::examine() { + // EITHER it's an object OR it's an Also OR it's a _person OR it's something else. + if ((_person == kPeoplePardon) && (_thing != kPardon)) { + if (isHolding()) { + // Remember: it's been slipped! Ie subtract 49. + if ((1 <= _thing) && (_thing <= 49)) + // Standard object + examineObject(); + else if ((50 <= _thing) && (_thing <= 100)) { + // Also _thing + openBox(true); + _vm->_dialogs->displayText(*_vm->_also[_thing - 50][1]); + openBox(false); + } + } + } else if (_person != kPardon) + exampers(); + else + // Don't know: guess. + _vm->_dialogs->displayText("It's just as it looks on the picture."); +} + +void Parser::inventory() { + byte itemNum = 0; + Common::String tmpStr = Common::String("You're carrying "); + + for (int i = 0; i < kObjectNum; i++) { + if (_vm->_objects[i]) { + itemNum++; + if (itemNum == _vm->_carryNum) + tmpStr += "and "; + + tmpStr += _vm->getItem(i + 1); + + if ((i + 1) == _wearing) + tmpStr += ", which you're wearing"; + + if (itemNum < _vm->_carryNum) + tmpStr += ", "; + } + } + + if (_wearing == kNothing) + tmpStr += Common::String::format("...%c%c...and you're stark naked!", kControlNewLine, kControlNewLine); + else + tmpStr += '.'; + + _vm->_dialogs->displayText(tmpStr); +} + +/** + * Eat something. + */ +void Parser::swallow() { + switch (_thing) { + case kObjectWine: + // _wineState == 4 for perfect wine + switch (_vm->_wineState) { + case 1: + if (_vm->_teetotal) { + _vm->_dialogs->displayScrollChain('D', 6); + return; + } + _vm->_dialogs->displayScrollChain('U', 1); + _vm->_pingo->wobble(); + _vm->_dialogs->displayScrollChain('U', 2); + _vm->_objects[kObjectWine - 1] = false; + _vm->refreshObjectList(); + drink(); + break; + case 2: + case 3: + // You can't drink it! + _vm->_dialogs->displayScrollChain('d', 8); + break; + } + break; + case kObjectPotion: + _vm->_graphics->setBackgroundColor(kColorRed); + _vm->_dialogs->displayScrollChain('U', 3); + _vm->gameOver(); + _vm->_graphics->setBackgroundColor(kColorBlack); + break; + case kObjectInk: + _vm->_dialogs->displayScrollChain('U', 4); + break; + case kObjectChastity: + _vm->_dialogs->displayScrollChain('U', 5); + break; + case kObjectMushroom: + _vm->_dialogs->displayScrollChain('U', 6); + _vm->gameOver(); + break; + case kObjectOnion: + if (_vm->_rottenOnion) + _vm->_dialogs->displayScrollChain('U', 11); + else { + _vm->_dialogs->displayScrollChain('U', 8); + _vm->_objects[kObjectOnion - 1] = false; + _vm->refreshObjectList(); + } + break; + default: + if ((_vm->_room == kRoomArgentPub) || (_vm->_room == kRoomNottsPub)) + _vm->_dialogs->displayText("Try BUYing things before you drink them!"); + else + _vm->_dialogs->displayText("The taste of it makes you retch!"); + } +} + +void Parser::peopleInRoom() { + // First compute the number of people in the room. + byte numPeople = 0; + for (int i = 151; i < 179; i++) { // Start at 1 so we don't list Avvy himself! + if (_vm->getRoom((People)i) == _vm->_room) + numPeople++; + } + + // If nobody's here, we can cut out straight away. + if (numPeople == 0) + return; + + Common::String tmpStr; + byte actPerson = 0; + for (int i = 151; i < 179; i++) { + if (_vm->getRoom((People)i) == _vm->_room) { + actPerson++; + if (actPerson == 1) + // Display first name on the list. + tmpStr = _vm->getName((People)i); + else if (actPerson < numPeople) + // Display one the names in the middle of the list + tmpStr += ", " + _vm->getName((People)i); + else + // Display the last name of the list + tmpStr += " and " + _vm->getName((People)i); + } + } + + if (numPeople == 1) + tmpStr += " is"; + else + tmpStr += " are"; + + _vm->_dialogs->displayText(tmpStr + " here."); +} + +void Parser::lookAround() { + _vm->_dialogs->displayText(*_vm->_also[0][1]); + switch (_vm->_room) { + case kRoomSpludwicks: + if (_vm->_avariciusTalk > 0) + _vm->_dialogs->displayScrollChain('q', 23); + else + peopleInRoom(); + break; + case kRoomRobins: + if (_vm->_tiedUp) + _vm->_dialogs->displayScrollChain('q', 38); + if (_vm->_mushroomGrowing) + _vm->_dialogs->displayScrollChain('q', 55); + break; + case kRoomInsideCardiffCastle: + if (!_vm->_takenPen) + _vm->_dialogs->displayScrollChain('q', 49); + break; + case kRoomLustiesRoom: + if (_vm->_lustieIsAsleep) + _vm->_dialogs->displayScrollChain('q', 65); + break; + case kRoomCatacombs: + switch (_vm->_catacombY * 256 + _vm->_catacombX) { + case 258 : + // Inside art gallery. + _vm->_dialogs->displayScrollChain('q', 80); + break; + case 514 : + // Outside ditto. + _vm->_dialogs->displayScrollChain('q', 81); + break; + case 260 : + // Outside Geida's room. + _vm->_dialogs->displayScrollChain('q', 82); + break; + } + break; + default: + peopleInRoom(); + } +} + +void Parser::openDoor() { + // Special cases. + switch (_vm->_room) { + case kRoomYours: + if (_vm->_animation->inField(1)) { + // Opening the box. + _thing = 54; // The box. + _person = kPeoplePardon; + examine(); + return; + } + break; + case kRoomSpludwicks: + if (_thing == 61) { + _vm->_dialogs->displayScrollChain('q', 85); + return; + } + break; + default: + break; + } + + if ((!_vm->_userMovesAvvy) && (_vm->_room != kRoomLusties)) + // No doors can open if you can't move Avvy. + return; + + for (int i = 0; i < 7; i++) { + if (_vm->_animation->inField(i + 8)) { + MagicType *portal = &_vm->_portals[i]; + switch (portal->_operation) { + case kMagicExclaim: + _vm->_animation->_sprites[0]->bounce(); + _vm->_dialogs->displayScrollChain('x', portal->_data); + break; + case kMagicTransport: + _vm->flipRoom((Room)((portal->_data) >> 8), portal->_data & 0x0F); + break; + case kMagicUnfinished: + _vm->_animation->_sprites[0]->bounce(); + _vm->_dialogs->displayText("Sorry. This place is not available yet!"); + break; + case kMagicSpecial: + _vm->_animation->callSpecial(portal->_data); + break; + case kMagicOpenDoor: + _vm->openDoor((Room)(portal->_data >> 8), portal->_data & 0x0F, i + 9); + break; + } + + return; + } + } + + if (_vm->_room == kRoomMap) + _vm->_dialogs->displayText("Avvy, you can complete the whole game without ever going " \ + "to anywhere other than Argent, Birmingham, Cardiff, Nottingham and Norwich."); + else + _vm->_dialogs->displayText("Door? What door?"); +} + +void Parser::putProc() { + if (!isHolding()) + return; + + // Slip the second object. + _thing2 -= 49; + char temp = _thing; + _thing = _thing2; + if (!isHolding()) + return; + _thing = temp; + + // Thing is the _thing which you're putting in. _thing2 is where you're putting it. + switch (_thing2) { + case kObjectWine: + if (_thing == kObjectOnion) { + if (_vm->_rottenOnion) + _vm->_dialogs->displayText("That's a bit like shutting the stable door after the horse has bolted!"); + else { + // Put onion into wine? + if (_vm->_wineState != 3) { + Common::String tmpStr = Common::String::format("%cOignon au vin%c is a bit too strong for your tastes!", + kControlItalic, kControlRoman); + _vm->_dialogs->displayText(tmpStr); + } else { + // Put onion into vinegar! Yes! + _vm->_onionInVinegar = true; + _vm->incScore(7); + _vm->_dialogs->displayScrollChain('u', 9); + } + } + } else + _vm->_dialogs->saySilly(); + break; + + case 54: + if (_vm->_room == kRoomYours) { + // Put something into the box. + if (_vm->_boxContent != kNothing) + _vm->_dialogs->displayText("There's something in the box already, Avvy. Try taking that out first."); + else { + switch (_thing) { + case kObjectMoney: + _vm->_dialogs->displayText("You'd better keep some ready cash on you!"); + break; + case kObjectBell: + _vm->_dialogs->displayText("That's a silly place to keep a bell."); + break; + case kObjectBodkin: + _vm->_dialogs->displayText("But you might need it!"); + break; + case kObjectOnion: + _vm->_dialogs->displayText("Just give it to Spludwick, Avvy!"); + break; + default: + // Put the object into the box... + if (_wearing == _thing) { + Common::String tmpStr = Common::String::format("You'd better take %s off first!", _vm->getItem(_thing).c_str()); + _vm->_dialogs->displayText(tmpStr); + } else { + // Open box. + openBox(true); + + _vm->_boxContent = _thing; + _vm->_objects[_thing - 1] = false; + _vm->refreshObjectList(); + _vm->_dialogs->displayText("OK, it's in the box."); + + // Shut box. + openBox(false); + } + } + } + } else + _vm->_dialogs->saySilly(); + break; + + default: + _vm->_dialogs->saySilly(); + } +} + +/** + * Display text when ingredients are not in the right order + * @remarks Originally called 'not_in_order' + */ +void Parser::notInOrder() { + Common::String itemStr = _vm->getItem(_vm->kSpludwicksOrder[_vm->_givenToSpludwick]); + Common::String tmpStr = Common::String::format("Sorry, I need the ingredients in the right order for this potion. " \ + "What I need next is %s%c2%c", itemStr.c_str(), kControlRegister, kControlSpeechBubble); + _vm->_dialogs->displayText(tmpStr); +} + +/** + * Move Spludwick to cauldron + * @remarks Originally called 'go_to_cauldron' + */ +void Parser::goToCauldron() { + // Stops Geida_Procs. + _vm->_animation->_sprites[1]->_callEachStepFl = false; + _vm->_timer->addTimer(1, Timer::kProcSpludwickGoesToCauldron, Timer::kReasonSpludwickWalk); + _vm->_animation->_sprites[1]->walkTo(1); +} + +/** + * Check is it's possible to give something to Spludwick + * @remarks Originally called 'give2spludwick' + */ +bool Parser::giveToSpludwick() { + if (_vm->kSpludwicksOrder[_vm->_givenToSpludwick] != _thing) { + notInOrder(); + return false; + } + + switch (_thing) { + case kObjectOnion: + _vm->_objects[kObjectOnion - 1] = false; + if (_vm->_rottenOnion) + _vm->_dialogs->displayScrollChain('q', 22); + else { + _vm->_givenToSpludwick++; + _vm->_dialogs->displayScrollChain('q', 20); + goToCauldron(); + _vm->incScore(3); + } + _vm->refreshObjectList(); + break; + case kObjectInk: + _vm->_objects[kObjectInk - 1] = false; + _vm->refreshObjectList(); + _vm->_givenToSpludwick++; + _vm->_dialogs->displayScrollChain('q', 24); + goToCauldron(); + _vm->incScore(3); + break; + case kObjectMushroom: + _vm->_objects[kObjectMushroom - 1] = false; + _vm->_dialogs->displayScrollChain('q', 25); + _vm->incScore(5); + _vm->_givenToSpludwick++; + goToCauldron(); + _vm->_objects[kObjectPotion - 1] = true; + _vm->refreshObjectList(); + break; + default: + return true; + } + return false; +} + +void Parser::drink() { + _alcoholLevel++; + if (_alcoholLevel == 5) { + // Get the key. + _vm->_objects[kObjectKey - 1] = true; + _vm->_teetotal = true; + _vm->_avvyIsAwake = false; + _vm->_avvyInBed = true; + _vm->refreshObjectList(); + _vm->fadeOut(); + _vm->flipRoom(kRoomYours, 1); + _vm->_graphics->setBackgroundColor(kColorYellow); + _vm->_animation->_sprites[0]->_visible = false; + } +} + +void Parser::cardiffClimbing() { + if (_vm->_standingOnDais) { + // Clamber up. + _vm->_dialogs->displayText("You climb down, back onto the floor."); + _vm->_standingOnDais = false; + _vm->_animation->appearPed(0, 2); + } else if (_vm->_animation->inField(0)) { + // Clamber down + _vm->_dialogs->displayText("You clamber up onto the dais."); + _vm->_standingOnDais = true; + _vm->_animation->appearPed(0, 1); + } else + _vm->_dialogs->displayText("Get a bit closer, Avvy."); +} + +void Parser::already() { + _vm->_dialogs->displayText("You're already standing!"); +} + +void Parser::standUp() { + switch (_vm->_room) { + case kRoomYours: + // Avvy isn't asleep. + if (_vm->_avvyIsAwake && _vm->_avvyInBed) { + // But he's in bed. + if (_vm->_teetotal) { + _vm->_dialogs->displayScrollChain('d', 12); + _vm->_graphics->setBackgroundColor(kColorBlack); + _vm->_dialogs->displayScrollChain('d', 14); + } + _vm->_animation->_sprites[0]->_visible = true; + _vm->_userMovesAvvy = true; + _vm->_animation->appearPed(0, 1); + _vm->_animation->setDirection(kDirLeft); + // Display a picture of empty pillow in the background. + _vm->_background->draw(-1, -1, 3); + _vm->incScore(1); + _vm->_avvyInBed = false; + _vm->_timer->loseTimer(Timer::kReasonArkataShouts); + } else + already(); + break; + + case kRoomInsideCardiffCastle: + cardiffClimbing(); + break; + + case kRoomNottsPub: + if (_vm->_sittingInPub) { + // Not sitting down. + _vm->_background->draw(-1, -1, 3); + // But standing up. + _vm->_animation->_sprites[0]->_visible = true; + // And walking away. + _vm->_animation->appearPed(0, 3); + // Really not sitting down. + _vm->_sittingInPub = false; + // And ambulant. + _vm->_userMovesAvvy = true; + } else + already(); + break; + default: + already(); + } +} + +void Parser::getProc(char thing) { + switch (_vm->_room) { + case kRoomYours: + if (_vm->_animation->inField(1)) { + if (_vm->_boxContent == thing) { + _vm->_background->draw(-1, -1, 4); + _vm->_dialogs->displayText("OK, I've got it."); + _vm->_objects[thing - 1] = true; + _vm->refreshObjectList(); + _vm->_boxContent = kNothing; + _vm->_background->draw(-1, -1, 5); + } else { + Common::String tmpStr = Common::String::format("I can't see %s in the box.", _vm->getItem(thing).c_str()); + _vm->_dialogs->displayText(tmpStr); + } + } else + _vm->_dialogs->displayScrollChain('q', 57); + break; + case kRoomInsideCardiffCastle: + switch (thing) { + case kObjectPen: + if (_vm->_animation->inField(1)) { + // Standing on the dais. + if (_vm->_takenPen) + _vm->_dialogs->displayText("It's not there, Avvy."); + else { + // OK: we're taking the pen, and it's there. + // No pen there now. + _vm->_background->draw(-1, -1, 3); + // Zap! + _vm->_animation->callSpecial(3); + _vm->_takenPen = true; + _vm->_objects[kObjectPen - 1] = true; + _vm->refreshObjectList(); + _vm->_dialogs->displayText("Taken."); + } + } else if (_vm->_standingOnDais) + _vm->_dialogs->displayScrollChain('q', 53); + else + _vm->_dialogs->displayScrollChain('q', 51); + break; + case kObjectBolt: + _vm->_dialogs->displayScrollChain('q', 52); + break; + default: + _vm->_dialogs->displayScrollChain('q', 57); + } + break; + case kRoomRobins: + if ((thing == kObjectMushroom) & (_vm->_animation->inField(0)) & (_vm->_mushroomGrowing)) { + _vm->_background->draw(-1, -1, 2); + _vm->_dialogs->displayText("Got it!"); + _vm->_mushroomGrowing = false; + _vm->_takenMushroom = true; + _vm->_objects[kObjectMushroom - 1] = true; + _vm->refreshObjectList(); + _vm->incScore(3); + } else + _vm->_dialogs->displayScrollChain('q', 57); + break; + default: + _vm->_dialogs->displayScrollChain('q', 57); + } +} + +/** + * Give the lute to Geida + * @remarks Originally called 'give_Geida_the_lute' + */ +void Parser::giveGeidaTheLute() { + if (_vm->_room != kRoomLustiesRoom) { + Common::String tmpStr = Common::String::format("Not yet. Try later!%c2%c", kControlRegister, kControlSpeechBubble); + _vm->_dialogs->displayText(tmpStr); + return; + } + _vm->_objects[kObjectLute - 1] = false; + _vm->refreshObjectList(); + // She plays it. + _vm->_dialogs->displayScrollChain('q', 64); + + _vm->_timer->addTimer(1, Timer::kProcGiveLuteToGeida, Timer::kReasonGeidaSings); + //_vm->_enid->backToBootstrap(4); TODO: Replace it with proper ScummVM-friendly function(s)! Do not remove until then! +} + +void Parser::playHarp() { + if (_vm->_animation->inField(6)) + _vm->_dialogs->displayMusicalScroll(); + else + _vm->_dialogs->displayText("Get a bit closer to it, Avvy!"); +} + +void Parser::winSequence() { + _vm->_dialogs->displayScrollChain('q', 78); + _vm->_sequence->startWinSeq(); + _vm->_timer->addTimer(30, Timer::kProcWinning, Timer::kReasonWinning); +} + +/** + * @remarks Originally called 'do_that' + */ +void Parser::doThat() { + static const char booze[8][8] = {"Bitter", "GIED", "Whisky", "Cider", "", "", "", "Mead"}; + static const char kWhat[] = "That's not possible!"; + + if (_thats == Common::String(kNothing)) { + if (!_thats.empty()) + _thats.clear(); + return; + } + + if (_weirdWord) + return; + + if (_thing < 200) + // "Slip" object + _thing -= 49; + + + if ((_verb != kVerbCodeLoad) && (_verb != kVerbCodeSave) && (_verb != kVerbCodeQuit) && (_verb != kVerbCodeInfo) && (_verb != kVerbCodeHelp) + && (_verb != kVerbCodeLarrypass) && (_verb != kVerbCodePhaon) && (_verb != kVerbCodeBoss) && (_verb != kVerbCodeCheat) && (_verb != kVerbCodeRestart) + && (_verb != kVerbCodeDir) && (_verb != kVerbCodeScore) && (_verb != kVerbCodeHiscores) && (_verb != kVerbCodeSmartAlec)) { + if (!_vm->_alive) { + _vm->_dialogs->displayText("You're dead, so don't talk. What are you, a ghost or something? " \ + "Try restarting, or restoring a saved game!"); + return; + } + if (!_vm->_avvyIsAwake && (_verb != kVerbCodeDie) && (_verb != kVerbCodeExpletive) && (_verb != kVerbCodeWake)) { + _vm->_dialogs->displayText("Talking in your sleep? Try waking up!"); + return; + } + } + + switch (_verb) { + case kVerbCodeExam: + examine(); + break; + case kVerbCodeOpen: + openDoor(); + break; + case kVerbCodePause: { + // Note that the original game doesn't care about the "O.K." box neither, it accepts + // clicks from everywhere on the screen to continue. Just like my code. + Common::String tmpStr = Common::String::format("Game paused.%c%c%cPress Enter, Esc, or click the mouse on the `O.K.\" " \ + "box to continue.", kControlCenter, kControlNewLine, kControlNewLine); + _vm->_dialogs->displayText(tmpStr); + } + break; + case kVerbCodeGet: + if (_thing != kPardon) { + // Legitimate try to pick something up. + if (_vm->_carryNum >= kCarryLimit) + _vm->_dialogs->displayText("You can't carry any more!"); + else + getProc(_thing); + } else { + // Not... ditto. + if (_person != kPeoplePardon) + _vm->_dialogs->displayText("You can't sweep folk off their feet!"); + else + _vm->_dialogs->displayText("I assure you, you don't need it."); + } + break; + case kVerbCodeDrop: + _vm->_dialogs->displayText("Two years ago you dropped a florin in the street. Three days " \ + "later it was gone! So now you never leave ANYTHING lying around. OK?"); + break; + case kVerbCodeInv: + inventory(); + break; + case kVerbCodeTalk: + if (_person == kPeoplePardon) { + if (_vm->_subjectNum == 99) { + // They typed "say password". + Common::String tmpStr = Common::String::format("Yes, but what %cis%c the password?", kControlItalic, kControlRoman); + _vm->_dialogs->displayText(tmpStr); + } else if (((1 <= _vm->_subjectNum) && (_vm->_subjectNum <= 49)) || (_vm->_subjectNum == 253) || (_vm->_subjectNum == 249)) { + _thats.deleteChar(0); + + for (int i = 0; i < 10; i++) + _realWords[i] = _realWords[i + 1]; + + _verb = (VerbCode)_vm->_subjectNum; + doThat(); + return; + } else { + _person = (People)_vm->_subjectNum; + _vm->_subjectNum = 0; + if ((_person == kPeopleNone) || (_person == kPeoplePardon)) + _vm->_dialogs->displayText("Talk to whom?"); + else if (isPersonHere()) + _vm->_dialogs->talkTo(_person); + } + } else if (isPersonHere()) + _vm->_dialogs->talkTo(_person); + break; + case kVerbCodeGive: + if (isHolding()) { + if (_person == kPeoplePardon) + _vm->_dialogs->displayText("Give to whom?"); + else if (isPersonHere()) { + switch (_thing) { + case kObjectMoney : + _vm->_dialogs->displayText("You can't bring yourself to give away your moneybag."); + break; + case kObjectBodkin: + case kObjectBell: + case kObjectClothes: + case kObjectHabit : + _vm->_dialogs->displayText("Don't give it away, it might be useful!"); + break; + default: + switch (_person) { + case kPeopleCrapulus: + if (_thing == kObjectWine) { + _vm->_dialogs->displayText("Crapulus grabs the wine and gulps it down."); + _vm->_objects[kObjectWine - 1] = false; + } else + _vm->_dialogs->sayThanks(_thing - 1); + break; + case kPeopleCwytalot: + if ((_thing == kObjectCrossbow) || (_thing == kObjectBolt)) + _vm->_dialogs->displayText("You might be able to influence Cwytalot more if you used it!"); + else + _vm->_dialogs->sayThanks(_thing - 1); + break; + case kPeopleSpludwick: + if (giveToSpludwick()) + _vm->_dialogs->sayThanks(_thing - 1); + break; + case kPeopleIbythneth: + if (_thing == kObjectBadge) { + _vm->_dialogs->displayScrollChain('q', 32); // Thanks! Wow! + _vm->incScore(3); + _vm->_objects[kObjectBadge - 1] = false; + _vm->_objects[kObjectHabit - 1] = true; + _vm->_givenBadgeToIby = true; + _vm->_background->draw(-1, -1, 7); + _vm->_background->draw(-1, -1, 8); + } else + _vm->_dialogs->sayThanks(_thing - 1); + break; + case kPeopleAyles: + if (_vm->_aylesIsAwake) { + if (_thing == kObjectPen) { + _vm->_objects[kObjectPen - 1] = false; + _vm->_dialogs->displayScrollChain('q', 54); + _vm->_objects[kObjectInk - 1] = true; + _vm->_givenPenToAyles = true; + _vm->refreshObjectList(); + _vm->incScore(2); + } else + _vm->_dialogs->sayThanks(_thing - 1); + } else + _vm->_dialogs->displayText("But he's asleep!"); + break; + case kPeopleGeida: + switch (_thing) { + case kObjectPotion: + _vm->_objects[kObjectPotion - 1] = false; + // She drinks it. + _vm->_dialogs->displayScrollChain('u', 16); + _vm->incScore(2); + _vm->_givenPotionToGeida = true; + _vm->refreshObjectList(); + break; + case kObjectLute: + giveGeidaTheLute(); + break; + default: + _vm->_dialogs->sayThanks(_thing - 1); + } + break; + case kPeopleArkata: + switch (_thing) { + case kObjectPotion: + if (_vm->_givenPotionToGeida) + winSequence(); + else + // That Geida woman! + _vm->_dialogs->displayScrollChain('q', 77); + break; + default: + _vm->_dialogs->sayThanks(_thing - 1); + } + break; + default: + _vm->_dialogs->sayThanks(_thing - 1); + } + } + } + // Just in case... + _vm->refreshObjectList(); + } + break; + + case kVerbCodeEat: + case kVerbCodeDrink: + if (isHolding()) + swallow(); + break; + + case kVerbCodeLoad: { + GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser("Restore game:", "Restore", false); + int16 savegameId = dialog->runModalWithCurrentTarget(); + delete dialog; + + if (savegameId < 0) + // dialog aborted, nothing to load + return; + + _vm->loadGame(savegameId); + } + break; + case kVerbCodeSave: { + GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser("Save game:", "Save", true); + int16 savegameId = dialog->runModalWithCurrentTarget(); + Common::String savegameDescription = dialog->getResultString(); + delete dialog; + + if (savegameId < 0) + // dialog aborted, nothing to save + return; + + _vm->saveGame(savegameId, savegameDescription); + } + break; + case kVerbCodePay: + _vm->_dialogs->displayText("No money need change hands."); + break; + case kVerbCodeLook: + lookAround(); + break; + case kVerbCodeBreak: + _vm->_dialogs->displayText("Vandalism is prohibited within this game!"); + break; + case kVerbCodeQuit: + if (!_polite) + _vm->_dialogs->displayText("How about a `please\", Avvy?"); + else { + Common::String tmpStr = Common::String::format("%cC%cDo you really want to quit?", kControlRegister, kControlIcon); + if (_vm->_dialogs->displayQuestion(tmpStr)) + _vm->_letMeOut = true; + } + break; + case kVerbCodeGo: + _vm->_dialogs->displayText("Just use the arrow keys to walk there."); + break; + case kVerbCodeInfo: { + _vm->_dialogs->_aboutBox = true; + + Common::String toDisplay; + for (int i = 0; i < 7; i++) + toDisplay += kControlNewLine; + toDisplay = toDisplay + "LORD AVALOT D'ARGENT" + kControlCenter + kControlNewLine + + "The medi\x91val descendant of" + kControlNewLine + + "Denarius Avaricius Sextus" + kControlNewLine + kControlNewLine + + "version " + kVersionNum + kControlNewLine + kControlNewLine + "Copyright \xEF " + + kCopyright + ", Mark, Mike and Thomas Thurman." + kControlRegister + 'Y' + kControlIcon; + _vm->_dialogs->displayText(toDisplay); + _vm->_dialogs->_aboutBox = false; + } + break; + case kVerbCodeUndress: + if (_wearing == kNothing) + _vm->_dialogs->displayText("You're already stark naked!"); + else if (_vm->_avvysInTheCupboard) { + Common::String tmpStr = Common::String::format("You take off %s.", _vm->getItem(_wearing).c_str()); + _vm->_dialogs->displayText(tmpStr); + _wearing = kNothing; + _vm->refreshObjectList(); + } else + _vm->_dialogs->displayText("Hadn't you better find somewhere more private, Avvy?"); + break; + case kVerbCodeWear: + if (isHolding()) { // Wear something. + switch (_thing) { + case kObjectChastity: + // \? are used to avoid that ??! is parsed as a trigraph + _vm->_dialogs->displayText("Hey, what kind of a weirdo are you\?\?!"); + break; + case kObjectClothes: + case kObjectHabit: { + // Change clothes! + if (_wearing != kNothing) { + if (_wearing == _thing) + _vm->_dialogs->displayText("You're already wearing that."); + else + _vm->_dialogs->displayText("You'll be rather warm wearing two sets of clothes!"); + return; + } else + _wearing = _thing; + + _vm->refreshObjectList(); + + byte i; + if (_thing == kObjectHabit) + i = 3; + else + i = 0; + + _vm->_animation->setAvvyClothes(i); + } + break; + default: + _vm->_dialogs->displayText(kWhat); + } + } + break; + case kVerbCodePlay: + if (_thing == kPardon) { + // They just typed "play"... + switch (_vm->_room) { + case kRoomArgentPub: + // ...in the pub, => play Nim. + warning("STUB: Parser::doThat() - case kVerbCodeplay - play_nim()"); + // play_nim(); + + // The following parts are copied from play_nim(). + // The player automatically wins the game everytime he wins, until I implement the mini-game. + if (_vm->_wonNim) { // Already won the game. + _vm->_dialogs->displayScrollChain('Q', 6); + return; + } + + if (!_vm->_askedDogfoodAboutNim) { + _vm->_dialogs->displayScrollChain('q', 84); + return; + } + + _vm->_dialogs->displayScrollChain('Q', 3); + _playedNim++; + + // You won - strange! + + // You won! Give us a lute! + _vm->_dialogs->displayScrollChain('Q', 7); + _vm->_objects[kObjectLute - 1] = true; + _vm->refreshObjectList(); + _vm->_wonNim = true; + // Show the settle with no lute on it. + _vm->_background->draw(-1, -1, 0); + // 7 points for winning! + _vm->incScore(7); + + if (_playedNim == 1) + // 3 points for playing your 1st game. + _vm->incScore(3); + + // A warning to the player that there should have been a mini-game. TODO: Remove it later!!! + _vm->_dialogs->displayText(Common::String("P.S.: There should have been the mini-game called \"Nim\", " \ + "but I haven't implemented it yet: you win and get the lute automatically.") + + kControlNewLine + kControlNewLine + "Peter (uruk)"); + break; + case kRoomMusicRoom: + playHarp(); + break; + default: + break; + } + } else if (isHolding()) { + switch (_thing) { + case kObjectLute : + _vm->_dialogs->displayScrollChain('U', 7); + + if (_vm->getRoom(kPeopleCwytalot) == _vm->_room) + _vm->_dialogs->displayScrollChain('U', 10); + + if (_vm->getRoom(kPeopleDuLustie) == _vm->_room) + _vm->_dialogs->displayScrollChain('U', 15); + break; + case 52: + if (_vm->_room == kRoomMusicRoom) + playHarp(); + else + _vm->_dialogs->displayText(kWhat); + break; + case 55: + if (_vm->_room == kRoomArgentPub) + // play_nim(); + warning("STUB: Parser::doThat() - case kVerbCodeplay - play_nim()"); + else + _vm->_dialogs->displayText(kWhat); + break; + default: + _vm->_dialogs->displayText(kWhat); + } + } + break; + case kVerbCodeRing: + if (isHolding()) { + if (_thing == kObjectBell) { + _vm->_dialogs->displayText("Ding, dong, ding, dong, ding, dong, ding, dong..."); + if ((_vm->_bellsAreRinging) & (_vm->getFlag('B'))) + // '\?' are used to avoid that '??!' is parsed as a trigraph + _vm->_dialogs->displayText("(Are you trying to join in, Avvy\?\?!)"); + } else + _vm->_dialogs->displayText(kWhat); + } + break; + case kVerbCodeHelp: + // boot_help(); + warning("STUB: Parser::doThat() - case kVerbCodehelp"); + break; + case kVerbCodeLarrypass: + _vm->_dialogs->displayText("Wrong game!"); + break; + case kVerbCodePhaon: + _vm->_dialogs->displayText("Hello, Phaon!"); + break; + case kVerbCodeBoss: + // bosskey(); + warning("STUB: Parser::doThat() - case kVerbCodeboss"); + break; + case kVerbCodePee: + if (_vm->getFlag('P')) { + _vm->_dialogs->displayText("Hmm, I don't think anyone will notice..."); + _vm->_timer->addTimer(4, Timer::kProcUrinate, Timer::kReasonGoToToilet); + } else { + Common::String tmpStr = Common::String::format("It would be %cVERY%c unwise to do that here, Avvy!", kControlItalic, kControlRoman); + _vm->_dialogs->displayText(tmpStr); + } + break; + case kVerbCodeCheat: { + Common::String tmpStr = Common::String::format("%cCheat mode now enabled.", kControlItalic); + _vm->_dialogs->displayText(tmpStr); + _vm->_cheat = true; + } + break; + case kVerbCodeMagic: + if (_vm->_avariciusTalk > 0) + _vm->_dialogs->displayScrollChain('q', 19); + else { + if ((_vm->_room == kRoomSpludwicks) & (_vm->_animation->inField(1))) { + // Avaricius appears! + _vm->_dialogs->displayScrollChain('q', 17); + if (_vm->getRoom(kPeopleSpludwick) == kRoomSpludwicks) + _vm->_dialogs->displayScrollChain('q', 18); + else { + Avalanche::AnimationType *spr = _vm->_animation->_sprites[1]; + // Avaricius + spr->init(1, false); + _vm->_animation->appearPed(1, 3); + spr->walkTo(4); + spr->_callEachStepFl = true; + spr->_eachStepProc = Animation::kProcBackAndForth; + _vm->_avariciusTalk = 14; + _vm->_timer->addTimer(177, Timer::kProcAvariciusTalks, Timer::kReasonAvariciusTalks); + } + } else + _vm->_dialogs->displayText("Nothing appears to happen..."); + } + break; + case kVerbCodeSmartAlec: + _vm->_dialogs->displayText("Listen, smart alec, that was just rhetoric."); + break; + case kVerbCodeExpletive: + switch (_sworeNum) { + case 0: { + Common::String tmpStr = Common::String::format("Avvy! Do you mind? There might be kids playing!%c%c" \ + "(I shouldn't say it again, if I were you!)", kControlNewLine, kControlNewLine); + _vm->_dialogs->displayText(tmpStr); + } + break; + case 1: { + Common::String tmpStr = Common::String::format("You hear a distant rumble of thunder. Must you always" \ + "do things I tell you not to?%c%cDon't do it again!", kControlNewLine, kControlNewLine); + _vm->_dialogs->displayText(tmpStr); + } + break; + default: { + _vm->_pingo->zonk(); + Common::String tmpStr = Common::String::format("A crack of lightning shoots from the sky, and fries you." \ + "%c%c(`Such is the anger of the gods, Avvy!\")", kControlNewLine, kControlNewLine); + _vm->_dialogs->displayText(tmpStr); + _vm->gameOver(); + } + } + _sworeNum++; + break; + case kVerbCodeListen: + if ((_vm->_bellsAreRinging) & (_vm->getFlag('B'))) + _vm->_dialogs->displayText("All other noise is drowned out by the ringing of the bells."); + else if (_vm->_listen.empty()) + _vm->_dialogs->displayText("You can't hear anything much at the moment, Avvy."); + else + _vm->_dialogs->displayText(_vm->_listen); + break; + case kVerbCodeBuy: + // What are they trying to buy? + switch (_vm->_room) { + case kRoomArgentPub: + // We're in a pub, and near the bar. + if (_vm->_animation->inField(5)) { + switch (_thing) { + case 51: + case 53: + case 54: + case 58: + // Beer, whisky, cider or mead. + if (_vm->_malagauche == 177) { + // Already getting us one. + _vm->_dialogs->displayScrollChain('D', 15); + return; + } + + if (_vm->_teetotal) { + _vm->_dialogs->displayScrollChain('D', 6); + return; + } + + if (_alcoholLevel == 0) + _vm->incScore(3); + + _vm->_background->draw(-1, -1, 11); + _vm->_dialogs->displayText(Common::String(booze[_thing - 51]) + ", please." + kControlRegister + '1' + kControlSpeechBubble); + _vm->_drinking = _thing; + + _vm->_background->draw(-1, -1, 9); + _vm->_malagauche = 177; + _vm->_timer->addTimer(27, Timer::kProcBuyDrinks, Timer::kReasonDrinks); + break; + case 52: + examine(); + break; // We have a right one here - buy Pepsi??! + case kObjectWine: + if (_vm->_objects[kObjectWine - 1]) + // We've already got the wine! + // 1 bottle's shufishent! + _vm->_dialogs->displayScrollChain('D', 2); + else { + if (_vm->_malagauche == 177) { + // Already getting us one. + _vm->_dialogs->displayScrollChain('D', 15); + return; + } + + if (_vm->_carryNum >= kCarryLimit) { + _vm->_dialogs->displayText("Your hands are full."); + return; + } + + _vm->_background->draw(-1, -1, 11); + Common::String tmpStr = Common::String::format("Wine, please.%c1%c", kControlRegister, kControlSpeechBubble); + _vm->_dialogs->displayText(tmpStr); + if (_alcoholLevel == 0) + _vm->incScore(3); + _vm->_background->draw(-1, -1, 9); + _vm->_malagauche = 177; + + _vm->_timer->addTimer(27, Timer::kProcBuyWine, Timer::kReasonDrinks); + } + break; + } + } else + // Go to the bar! + _vm->_dialogs->displayScrollChain('D', 5); + break; + + case kRoomOutsideDucks: + if (_vm->_animation->inField(5)) { + if (_thing == kObjectOnion) { + if (_vm->_objects[kObjectOnion - 1]) + // Not planning to juggle with the things! + _vm->_dialogs->displayScrollChain('D', 10); + else if (_vm->_carryNum >= kCarryLimit) + _vm->_dialogs->displayText("Before you ask, you remember that your hands are full."); + else { + if (_boughtOnion) + _vm->_dialogs->displayScrollChain('D', 11); + else { + _vm->_dialogs->displayScrollChain('D', 9); + _vm->incScore(3); + } + // It costs thruppence. + _vm->decreaseMoney(3); + _vm->_objects[kObjectOnion - 1] = true; + _vm->refreshObjectList(); + _boughtOnion = true; + // It's OK when it leaves the stall! + _vm->_rottenOnion = false; + _vm->_onionInVinegar = false; + } + } else + _vm->_dialogs->displayScrollChain('D', 0); + } else + _vm->_dialogs->displayScrollChain('D', 0); + break; + + case kRoomNottsPub: + // Can't sell to southerners. + _vm->_dialogs->displayScrollChain('n', 15); + break; + default: + // Can't buy that. + _vm->_dialogs->displayScrollChain('D', 0); + } + break; + case kVerbCodeAttack: + if ((_vm->_room == kRoomBrummieRoad) && + ((_person == kPeopleCwytalot) || (_thing == kObjectCrossbow) || (_thing == kObjectBolt)) && + (_vm->getRoom(kPeopleCwytalot) == _vm->_room)) { + switch (_vm->_objects[kObjectBolt - 1] + _vm->_objects[kObjectCrossbow - 1] * 2) { + // 0 = neither, 1 = only bolt, 2 = only crossbow, 3 = both. + case 0: + _vm->_dialogs->displayScrollChain('Q', 10); + _vm->_dialogs->displayText("(At the very least, don't use your bare hands!)"); + break; + case 1: + _vm->_dialogs->displayText("Attack _vm->him with only a crossbow bolt? Are you planning on playing darts?!"); + break; + case 2: + _vm->_dialogs->displayText("Come on, Avvy! You're not going to get very far with only a crossbow!"); + break; + case 3: + _vm->_dialogs->displayScrollChain('Q', 11); + _vm->_cwytalotGone = true; + _vm->_objects[kObjectBolt - 1] = false; + _vm->_objects[kObjectCrossbow - 1] = false; + _vm->refreshObjectList(); + _vm->_magics[11]._operation = kMagicNothing; + _vm->incScore(7); + _vm->_animation->_sprites[1]->walkTo(1); + _vm->_animation->_sprites[1]->_vanishIfStill = true; + _vm->_animation->_sprites[1]->_callEachStepFl = false; + _vm->setRoom(kPeopleCwytalot, kRoomDummy); + break; + default: + // Please try not to be so violent! + _vm->_dialogs->displayScrollChain('Q', 10); + } + } else + _vm->_dialogs->displayScrollChain('Q', 10); + break; + case kVerbCodePasswd: + if (_vm->_room != kRoomBridge) + _vm->_dialogs->displayScrollChain('Q', 12); + else { + bool ok = true; + for (uint i = 0; i < _thats.size(); i++) { + Common::String temp = _realWords[i]; + temp.toUppercase(); + int pwdId = _vm->_passwordNum + kFirstPassword; + for (uint j = 0; j < _vocabulary[pwdId]._word.size(); j++) { + if (_vocabulary[pwdId]._word[j] != temp[j]) + ok = false; + } + } + + if (ok) { + if (_vm->_drawbridgeOpen != 0) + _vm->_dialogs->displayText("Contrary to your expectations, the drawbridge fails to close again."); + else { + _vm->incScore(4); + _vm->_dialogs->displayText("The drawbridge opens!"); + _vm->_timer->addTimer(7, Timer::kProcOpenDrawbridge, Timer::kReasonDrawbridgeFalls); + _vm->_drawbridgeOpen = 1; + } + } else + _vm->_dialogs->displayScrollChain('Q', 12); + } + break; + case kVerbCodeDie: + _vm->gameOver(); + break; + case kVerbCodeScore: { + Common::String tmpStr = Common::String::format("Your score is %d,%c%cout of a possible 128.%c%c " \ + "This gives you a rank of %s.%c%c%s", _vm->_dnascore, kControlCenter, kControlNewLine, kControlNewLine, + kControlNewLine, rank().c_str(), kControlNewLine, kControlNewLine, totalTime().c_str()); + _vm->_dialogs->displayText(tmpStr); + } + break; + case kVerbCodePut: + putProc(); + break; + case kVerbCodeStand: + standUp(); + break; + case kVerbCodeKiss: + if (_person == kPeoplePardon) + _vm->_dialogs->displayText("Kiss whom?"); + else if (isPersonHere()) { + switch (_person) { + case kPeopleArkata: + _vm->_dialogs->displayScrollChain('U', 12); + break; + case kPeopleGeida: + _vm->_dialogs->displayScrollChain('U', 13); + break; + case kPeopleWisewoman: + _vm->_dialogs->displayScrollChain('U', 14); + break; + default: + // You WHAT? + _vm->_dialogs->displayScrollChain('U', 5); + } + } else if ((kPeopleAvalot <= _person) && (_person < kPeopleArkata)) + _vm->_dialogs->displayText("Hey, what kind of a weirdo are you??"); + + break; + case kVerbCodeClimb: + if (_vm->_room == kRoomInsideCardiffCastle) + cardiffClimbing(); + else + // In the wrong room! + _vm->_dialogs->displayText("Not with your head for heights, Avvy!"); + break; + case kVerbCodeJump: + _vm->_timer->addTimer(1, Timer::kProcJump, Timer::kReasonJumping); + _vm->_userMovesAvvy = false; + break; + case kVerbCodeHiscores: + // show_highs(); + warning("STUB: Parser::doThat() - case kVerbCodehighscores"); + break; + case kVerbCodeWake: + if (isPersonHere()) + switch (_person) { + case kPeoplePardon: + case kPeopleAvalot: + case 0: + if (!_vm->_avvyIsAwake) { + _vm->_avvyIsAwake = true; + _vm->incScore(1); + _vm->_avvyInBed = true; + // Picture of Avvy, awake in bed. + _vm->_background->draw(-1, -1, 2); + if (_vm->_teetotal) + _vm->_dialogs->displayScrollChain('d', 13); + } else + _vm->_dialogs->displayText("You're already awake, Avvy!"); + break; + case kPeopleAyles: + if (!_vm->_aylesIsAwake) + _vm->_dialogs->displayText("You can't seem to wake him by yourself."); + break; + case kPeopleJacques: { + Common::String tmpStr = Common::String::format("Brother Jacques, Brother Jacques, are you asleep?%c1%c" \ + "Hmmm... that doesn't seem to do any good...", kControlRegister, kControlSpeechBubble); + _vm->_dialogs->displayText(tmpStr); + } + break; + default: + _vm->_dialogs->displayText("It's difficult to awaken people who aren't asleep...!"); + } + break; + case kVerbCodeSit: + if (_vm->_room == kRoomNottsPub) { + if (_vm->_sittingInPub) + _vm->_dialogs->displayText("You're already sitting!"); + else { + // Move Avvy to the place, and sit him down. + _vm->_animation->_sprites[0]->walkTo(3); + _vm->_timer->addTimer(1, Timer::kProcAvvySitDown, Timer::kReasonSittingDown); + } + } else { + // Default doodah. + _vm->fadeOut(); + _vm->fadeIn(); + Common::String tmpStr = Common::String::format("A few hours later...%cnothing much has happened...", kControlParagraph); + _vm->_dialogs->displayText(tmpStr); + } + break; + case kVerbCodeRestart: + if (_vm->_dialogs->displayQuestion("Restart game and lose changes?")) { + _vm->fadeOut(); + _vm->newGame(); + _vm->fadeIn(); + } + break; + case kVerbCodePardon: + _vm->_dialogs->displayText("Hey, a verb would be helpful!"); + break; + case kVerbCodeHello: + _vm->_dialogs->sayHello(); + break; + case kVerbCodeThanks: + _vm->_dialogs->sayOK(); + break; + default: + Common::String tmpStr = Common::String::format("%cUnhandled verb: %d", kControlBell, _verb); + _vm->_dialogs->displayText(tmpStr); + } +} + +void Parser::verbOpt(byte verb, Common::String &answer, char &ansKey) { + // kVerbCodegive isn't dealt with by this procedure, but by ddm__with. + switch (verb) { + case kVerbCodeExam: + answer = "Examine"; + ansKey = 'x'; + break; + case kVerbCodeDrink: + answer = "Drink"; + ansKey = 'D'; + break; + case kVerbCodeWear: + answer = "Wear"; + ansKey = 'W'; + break; + case kVerbCodeRing: + answer = "Ring"; + ansKey = 'R'; + break; // Only the bell! + case kVerbCodePlay: + answer = "Play"; + ansKey = 'P'; + break; + case kVerbCodeEat: + answer = "Eat"; + ansKey = 'E'; + break; + default: + answer = "? Unknown!"; // Bug! + ansKey = '?'; + } +} + +void Parser::doVerb(VerbCode id) { + _weirdWord = false; + _polite = true; + _verb = id; + doThat(); +} + +void Parser::resetVariables() { + _wearing = 0; + _sworeNum = 0; + _alcoholLevel = 0; + _playedNim = 0; + _boughtOnion = false; +} + +void Parser::synchronize(Common::Serializer &sz) { + sz.syncAsByte(_wearing); + sz.syncAsByte(_sworeNum); + sz.syncAsByte(_alcoholLevel); + sz.syncAsByte(_playedNim); + sz.syncAsByte(_boughtOnion); +} + +} // End of namespace Avalanche diff --git a/engines/avalanche/parser.h b/engines/avalanche/parser.h new file mode 100644 index 0000000000..261e5ecefe --- /dev/null +++ b/engines/avalanche/parser.h @@ -0,0 +1,155 @@ +/* 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 the original source code of Lord Avalot d'Argent version 1.3. + * Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman. + */ + +#ifndef AVALANCHE_PARSER_H +#define AVALANCHE_PARSER_H + +#include "avalanche/enums.h" + +#include "common/events.h" +#include "common/str.h" +#include "common/serializer.h" + + +namespace Avalanche { +class AvalancheEngine; + +class Parser { +public: + static const byte kPardon = 254; // Didn't understand / wasn't given. + static const int16 kParserWordsNum = 277; // How many words does the parser know? + static const byte kNothing = 250; + static const byte kMoved = 0; // This word was moved. (Usually because it was the subject of conversation.) + static const int16 kFirstPassword = 88; // words[kFirstPassword] should equal "TIROS". + + struct VocabEntry { + byte _number; + Common::String _word; + + void init(byte number, Common::String word) { + _number = number; + _word = word; + } + }; + + VocabEntry _vocabulary[kParserWordsNum]; + + Common::String _realWords[11]; + VerbCode _verb; + byte _thing; + People _person; + bool _polite; + Common::String _inputText; // Original name: current + Common::String _inputTextBackup; + byte _inputTextPos; // Original name: curpos + bool _quote; // 66 or 99 next? + bool _cursorState; + bool _weirdWord; + + byte _wearing; // what you're wearing + + Parser(AvalancheEngine *vm); + + void init(); + void parse(); + void doThat(); + void verbOpt(byte verb, Common::String &answer, char &ansKey); + void drink(); + + void handleInputText(const Common::Event &event); + void handleBackspace(); + void handleReturn(); + void handleFunctionKey(const Common::Event &event); + void plotText(); + void cursorOn(); + void cursorOff(); + void tryDropdown(); // This asks the parsekey proc in Dropdown if it knows it. + int16 getPos(const Common::String &crit, const Common::String &src); // Returns the index of the first appearance of crit in src. + void doVerb(VerbCode id); + + void resetVariables(); + void synchronize(Common::Serializer &sz); + +private: + AvalancheEngine *_vm; + + struct RankType { + uint16 _score; + char _title[20]; + }; + + static const char *kCopyright; + static const char *kVersionNum; + + Common::String _thats; + byte _thing2; + byte _sworeNum; // number of times you've sworn + byte _alcoholLevel; // Your blood alcohol level. + byte _playedNim; // How many times you've played Nim. + bool _boughtOnion; // Have you bought an onion yet? + + byte wordNum(Common::String word); + void replace(Common::String oldChars, byte newChar); + + Common::String rank(); + Common::String totalTime(); + + void clearWords(); + void cheatParse(Common::String codes); + void stripPunctuation(Common::String &word); // Strips punctuation from word. + void displayWhat(byte target, bool animate, bool &ambiguous); // << It's an adjective! + bool doPronouns(); + void properNouns(); + void lookAround(); // This is called when you say "look". + void openDoor(); + void storeInterrogation(byte interrogation); + void examineObject(); // Examine a standard object-thing + bool isPersonHere(); + void exampers(); + bool isHolding(); + void openBox(bool isOpening); + void examine(); + void inventory(); + void swallow(); + void peopleInRoom(); // This lists the other people in the room. + void putProc(); // Called when you call kVerbCodeput. + void notInOrder(); + void goToCauldron(); + bool giveToSpludwick(); // The result of this fn is whether or not he says "Hey, thanks!". + void cardiffClimbing(); + void already(); + void standUp(); // Called when you ask Avvy to stand. + void getProc(char thing); + void giveGeidaTheLute(); + void playHarp(); + void winSequence(); + void wipeText(); +}; + +} // End of namespace Avalanche + +#endif // AVALANCHE_PARSER_H diff --git a/engines/avalanche/pingo.cpp b/engines/avalanche/pingo.cpp new file mode 100644 index 0000000000..433924f594 --- /dev/null +++ b/engines/avalanche/pingo.cpp @@ -0,0 +1,106 @@ +/* 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 the original source code of Lord Avalot d'Argent version 1.3. + * Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman. + */ + +/* PINGO Full-screen sub-parts of the game. */ + +#include "avalanche/avalanche.h" +#include "avalanche/pingo.h" + +namespace Avalanche { + +Pingo::Pingo(AvalancheEngine *vm) { + _vm = vm; +} + +void Pingo::dPlot(int16 x, int16 y, Common::String z) { + warning("STUB: Pingo::dPlot()"); +} + +void Pingo::bossKey() { + warning("STUB: Pingo::bossKey()"); +} + +void Pingo::copy02() { // taken from Wobble (below) + warning("STUB: Pingo::copy02()"); +} + +void Pingo::copy03() { // taken from Wobble (below) + warning("STUB: Pingo::copy03()"); +} + +void Pingo::copyPage(byte frp, byte top) { // taken from Copy02 (above) + warning("STUB: Pingo::copyPage()"); +} + +void Pingo::wobble() { + warning("STUB: Pingo::wobble()"); +} + +void Pingo::zl(int16 x1, int16 y1, int16 x2, int16 y2) { + warning("STUB: Pingo::zl()"); +} + +void Pingo::zonk() { + warning("STUB: Pingo::zonk()"); +} + +void Pingo::winningPic() { + Common::File f; + _vm->fadeOut(); + + if (!f.open("finale.avd")) + error("AVALANCHE: File not found: finale.avd"); + +#if 0 + for (int bit = 0; bit <= 3; bit++) { + port[0x3c4] = 2; + port[0x3ce] = 4; + port[0x3c5] = 1 << bit; + port[0x3cf] = bit; + blockread(f, mem[0xa000 * 0], 16000); + } +#endif + + f.close(); + + warning("STUB: Pingo::winningPic()"); + + _vm->fadeIn(); + +#if 0 + do { + _vm->check(); + } while (!(keypressed() || (mrelease > 0))); + while (keypressed()) + char r = readkey(); + major_redraw(); +#endif + + warning("STUB: Pingo::winningPic()"); +} + +} // End of namespace Avalanche. diff --git a/engines/avalanche/pingo.h b/engines/avalanche/pingo.h new file mode 100644 index 0000000000..72fdb54c2a --- /dev/null +++ b/engines/avalanche/pingo.h @@ -0,0 +1,59 @@ +/* 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 the original source code of Lord Avalot d'Argent version 1.3. + * Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman. + */ + +/* PINGO Full-screen sub-parts of the game. */ + +#ifndef AVALANCHE_PINGO_H +#define AVALANCHE_PINGO_H + +#include "common/str.h" + +namespace Avalanche { +class AvalancheEngine; + +class Pingo { +public: + Pingo(AvalancheEngine *vm); + + void bossKey(); + void copy02(); + void copy03(); + void copyPage(byte frp, byte top); + void wobble(); + void zonk(); + void winningPic(); + +private: + AvalancheEngine *_vm; + + void dPlot(int16 x, int16 y, Common::String z); + void zl(int16 x1, int16 y1, int16 x2, int16 y2); +}; + +} // End of namespace Avalanche. + +#endif // AVALANCHE_PINGO_H diff --git a/engines/avalanche/sequence.cpp b/engines/avalanche/sequence.cpp new file mode 100644 index 0000000000..10fa7f0a00 --- /dev/null +++ b/engines/avalanche/sequence.cpp @@ -0,0 +1,228 @@ +/* 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 the original source code of Lord Avalot d'Argent version 1.3. + * Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman. + */ + +/* SEQUENCE The sequencer. */ + +#include "avalanche/avalanche.h" +#include "avalanche/sequence.h" + +namespace Avalanche { + +Sequence::Sequence(AvalancheEngine *vm) { + _vm = vm; +} + +void Sequence::resetVariables() { + _flipToWhere = kRoomNowhere; + _flipToPed = 0; +} + +void Sequence::init(byte what) { + _seq[0] = what; + + for (int i = 1; i < kSeqLength; i++) + _seq[i] = 0; +} + +void Sequence::add(byte what) { + for (int16 i = 0; i < kSeqLength; i++) { + if (_seq[i] == 0) { + _seq[i] = what; + return; + } + } +} + +void Sequence::switchRoom(Room where, byte ped) { + add(kNowFlip); + + _flipToWhere = where; + _flipToPed = ped; +} + +void Sequence::startTimer() { + _vm->_timer->loseTimer(Timer::kReasonSequencer); + _vm->_timer->addTimer(7, Timer::kProcSequence, Timer::kReasonSequencer); +} + +void Sequence::startTimerImmobilized() { + // They can't move. + _vm->_userMovesAvvy = false; + // And they're not moving now. + _vm->_animation->stopWalking(); + // Apart from that, it's the same thing. + startTimer(); +} + +void Sequence::shoveLeft() { + for (uint i = 0; i < kSeqLength - 1; i++) + _seq[i] = _seq[i + 1]; + _seq[kSeqLength - 1] = 0; +} + +void Sequence::callSequencer() { + byte curSeq = _seq[0]; + + switch (curSeq) { + case 0: + // No more routines. + return; + break; + case kNowFlip: + // Flip room. + _vm->_userMovesAvvy = true; + _vm->flipRoom(_flipToWhere, _flipToPed); + shoveLeft(); + break; + } + + if (curSeq <= 176) { + // Show a frame. + _vm->_background->draw(-1, -1, curSeq - 1); + shoveLeft(); + } + + // Make sure this PROC gets called again. + startTimer(); +} + +void Sequence::startHallSeq(Room whither, byte ped) { + init(1); + add(2); + switchRoom(whither, ped); + startTimerImmobilized(); +} + +void Sequence::startOutsideSeq(Room whither, byte ped) { + init(1); + add(2); + add(3); + switchRoom(whither, ped); + startTimerImmobilized(); +} + +void Sequence::startCardiffSeq(Room whither, byte ped) { + init(1); + add(5); + switchRoom(whither, ped); + startTimerImmobilized(); +} + +void Sequence::startNaughtyDukeSeq() { + init(2); + startTimer(); +} + +void Sequence::startGardenSeq() { + init(2); + add(1); + add(3); + startTimer(); +} + +void Sequence::startDuckSeq() { + init(3); + add(2); + add(1); + add(4); + startTimer(); +} + +void Sequence::startLustiesSeq3(Room whither, byte ped) { + init(4); + add(5); + add(6); + switchRoom(whither, ped); + startTimerImmobilized(); +} + +void Sequence::startMusicRoomSeq2(Room whither, byte ped) { + init(5); + add(6); + switchRoom(whither, ped); + startTimerImmobilized(); +} + +void Sequence::startGeidaLuteSeq() { + init(5); + // He falls asleep... + add(6); + // Not really closing, but we're using the same procedure. + startTimer(); +} + +void Sequence::startMusicRoomSeq() { + init(6); + add(5); + add(7); + startTimer(); +} + +void Sequence::startWinSeq() { + init(7); + add(8); + add(9); + startTimer(); +} + +void Sequence::startCupboardSeq() { + init(8); + add(7); + startTimer(); +} + +void Sequence::startLustiesSeq2(Room whither, byte ped) { + init(8); + add(9); + switchRoom(whither, ped); + startTimerImmobilized(); +} + +void Sequence::startCardiffSeq2() { + init(1); + if (_vm->_arrowInTheDoor) + add(3); + else + add(2); + + if (_vm->_takenPen) + _vm->_background->draw(-1, -1, 3); + + startTimer(); +} + +void Sequence::startDummySeq(Room whither, byte ped) { + switchRoom(whither, ped); + startTimerImmobilized(); +} + +void Sequence::synchronize(Common::Serializer &sz) { + sz.syncBytes(_seq, kSeqLength); + sz.syncAsByte(_flipToWhere); + sz.syncAsByte(_flipToPed); +} +} // End of namespace Avalanche. diff --git a/engines/avalanche/sequence.h b/engines/avalanche/sequence.h new file mode 100644 index 0000000000..d3c1b54963 --- /dev/null +++ b/engines/avalanche/sequence.h @@ -0,0 +1,80 @@ +/* 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 the original source code of Lord Avalot d'Argent version 1.3. + * Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman. + */ + +/* SEQUENCE The sequencer. */ + +#ifndef AVALANCHE_SEQUENCE_H +#define AVALANCHE_SEQUENCE_H + +namespace Avalanche { +class AvalancheEngine; + +class Sequence { +private: + static const int16 kNowFlip = 177; + static const int16 kSeqLength = 10; + + byte _seq[kSeqLength]; + + Room _flipToWhere; + byte _flipToPed; + + AvalancheEngine *_vm; + + void shoveLeft(); // This is called by Timer when it's time to do another frame. It shifts everything to the left. + void init(byte what); + void add(byte what); + void switchRoom(Room where, byte ped); + void startTimer(); + void startTimerImmobilized(); + +public: + Sequence(AvalancheEngine *vm); + void synchronize(Common::Serializer &sz); + void resetVariables(); + void callSequencer(); + + void startCupboardSeq(); + void startMusicRoomSeq(); + void startMusicRoomSeq2(Room whither, byte ped); + void startGardenSeq(); + void startGeidaLuteSeq(); + void startWinSeq(); + void startNaughtyDukeSeq(); + void startLustiesSeq2(Room whither, byte ped); + void startLustiesSeq3(Room whither, byte ped); + void startHallSeq(Room whither, byte ped); + void startCardiffSeq(Room whither, byte ped); + void startOutsideSeq(Room whither, byte ped); + void startDuckSeq(); + void startCardiffSeq2(); + void startDummySeq(Room whither, byte ped); +}; + +} // End of namespace Avalanche. + +#endif // AVALANCHE_SEQUENCE_H diff --git a/engines/avalanche/sound.cpp b/engines/avalanche/sound.cpp new file mode 100644 index 0000000000..c324df4713 --- /dev/null +++ b/engines/avalanche/sound.cpp @@ -0,0 +1,88 @@ +/* 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 "avalanche/avalanche.h" +#include "avalanche/sound.h" + +#include "audio/audiostream.h" +#include "common/config-manager.h" + +namespace Avalanche { + +SoundHandler::SoundHandler(AvalancheEngine *vm) : _vm(vm) { + _soundFl = true; + _speakerStream = new Audio::PCSpeaker(_vm->_mixer->getOutputRate()); + _vm->_mixer->playStream(Audio::Mixer::kSFXSoundType, &_speakerHandle, + _speakerStream, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::YES, true); +} + +SoundHandler::~SoundHandler() { + _vm->_mixer->stopHandle(_speakerHandle); +} + +/** + * Stop any sound that might be playing + */ +void SoundHandler::stopSound() { + _vm->_mixer->stopAll(); +} + +/** + * Turn digitized sound on and off + */ +void SoundHandler::toggleSound() { + _soundFl = !_soundFl; +} + +void SoundHandler::syncVolume() { + int soundVolume; + + if (ConfMan.getBool("sfx_mute") || ConfMan.getBool("mute")) + soundVolume = -1; + else + soundVolume = MIN(255, ConfMan.getInt("sfx_volume")); + + _vm->_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, soundVolume); +} + +void SoundHandler::playNote(int freq, int length) { + // Does the user not want any sound? + if (!_soundFl || !_vm->_mixer->isReady()) + return; + + // Start a note playing (we will stop it when the timer expires). + _speakerStream->play(Audio::PCSpeaker::kWaveFormSquare, freq, length); +} + +void SoundHandler::click() { + _vm->_mixer->stopAll(); + + playNote(7177, 1); +} + +void SoundHandler::blip() { + _vm->_mixer->stopAll(); + + playNote(177, 77); +} + +} // End of namespace Avalanche diff --git a/engines/avalanche/sound.h b/engines/avalanche/sound.h new file mode 100644 index 0000000000..25b6b267d3 --- /dev/null +++ b/engines/avalanche/sound.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. + * + */ + +#ifndef AVALANCHE_SOUND_H +#define AVALANCHE_SOUND_H + +#include "audio/mixer.h" +#include "audio/softsynth/pcspk.h" + +namespace Avalanche { + +class SoundHandler { +public: + bool _soundFl; + + SoundHandler(AvalancheEngine *vm); + ~SoundHandler(); + + void toggleSound(); + void playNote(int freq, int length); + void click(); + void blip(); + void syncVolume(); + void stopSound(); + +private: + AvalancheEngine *_vm; + Audio::PCSpeaker *_speakerStream; + Audio::SoundHandle _speakerHandle; +}; + +} // End of namespace Avalanche + +#endif // AVALANCHE_SOUND_H diff --git a/engines/avalanche/timer.cpp b/engines/avalanche/timer.cpp new file mode 100644 index 0000000000..4e90c7fe48 --- /dev/null +++ b/engines/avalanche/timer.cpp @@ -0,0 +1,693 @@ +/* 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 the original source code of Lord Avalot d'Argent version 1.3. + * Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman. + */ + +/* Original name: TIMEOUT The scheduling unit. */ + +#include "avalanche/avalanche.h" +#include "avalanche/timer.h" + +namespace Avalanche { + +Timer::Timer(AvalancheEngine *vm) { + _vm = vm; + + for (int i = 0; i < 7; i++) { + _times[i]._timeLeft = 0; + _times[i]._action = 0; + _times[i]._reason = 0; + } + _timerLost = false; +} + +/** + * Add a nex timer + * @remarks Originally called 'set_up_timer' + */ +void Timer::addTimer(int32 duration, byte action, byte reason) { + if ((_vm->_isLoaded == false) || (_timerLost == true)) { + byte i = 0; + while ((i < 7) && (_times[i]._timeLeft != 0)) + i++; + + if (i == 7) + return; // Oh dear... No timer left + + // Everything's OK here! + _times[i]._timeLeft = duration; + _times[i]._action = action; + _times[i]._reason = reason; + } else { + _vm->_isLoaded = false; + return; + } +} + +/** + * Update the timers + * @remarks Originally called 'one_tick' + */ +void Timer::updateTimer() { + if (_vm->_menu->isActive()) + return; + + for (int i = 0; i < 7; i++) { + if (_times[i]._timeLeft <= 0) + continue; + + _times[i]._timeLeft--; + + if (_times[i]._timeLeft == 0) { + switch (_times[i]._action) { + case kProcOpenDrawbridge : + openDrawbridge(); + break; + case kProcAvariciusTalks : + avariciusTalks(); + break; + case kProcUrinate : + urinate(); + break; + case kProcToilet : + toilet(); + break; + case kProcBang: + bang(); + break; + case kProcBang2: + bang2(); + break; + case kProcStairs: + stairs(); + break; + case kProcCardiffSurvey: + cardiffSurvey(); + break; + case kProcCardiffReturn: + cardiffReturn(); + break; + case kProcCwytalotInHerts: + cwytalotInHerts(); + break; + case kProcGetTiedUp: + getTiedUp(); + break; + case kProcGetTiedUp2: + getTiedUp2(); + break; + case kProcHangAround: + hangAround(); + break; + case kProcHangAround2: + hangAround2(); + break; + case kProcAfterTheShootemup: + afterTheShootemup(); + break; + case kProcJacquesWakesUp: + jacquesWakesUp(); + break; + case kProcNaughtyDuke: + naughtyDuke(); + break; + case kProcNaughtyDuke2: + naughtyDuke2(); + break; + case kProcNaughtyDuke3: + naughtyDuke3(); + break; + case kProcJump: + jump(); + break; + case kProcSequence: + _vm->_sequence->callSequencer(); + break; + case kProcCrapulusSpludOut: + crapulusSaysSpludOut(); + break; + case kProcDawnDelay: + _vm->fadeIn(); + break; + case kProcBuyDrinks: + buyDrinks(); + break; + case kProcBuyWine: + buyWine(); + break; + case kProcCallsGuards: + callsGuards(); + break; + case kProcGreetsMonk: + greetsMonk(); + break; + case kProcFallDownOubliette: + fallDownOubliette(); + break; + case kProcMeetAvaroid: + meetAvaroid(); + break; + case kProcRiseUpOubliette: + riseUpOubliette(); + break; + case kProcRobinHoodAndGeida: + robinHoodAndGeida(); + break; + case kProcRobinHoodAndGeidaTalk: + robinHoodAndGeidaTalk(); + break; + case kProcAvalotReturns: + avalotReturns(); + break; + case kProcAvvySitDown: + avvySitDown(); + break; + case kProcGhostRoomPhew: + ghostRoomPhew(); + break; + case kProcArkataShouts: + arkataShouts(); + break; + case kProcWinning: + winning(); + break; + case kProcAvalotFalls: + avalotFalls(); + break; + case kProcSpludwickGoesToCauldron: + spludwickGoesToCauldron(); + break; + case kProcSpludwickLeavesCauldron: + spludwickLeavesCauldron(); + break; + case kProcGiveLuteToGeida: + giveLuteToGeida(); + break; + } + } + } + _vm->_roomTime++; // Cycles since you've been in this room. + _vm->_totalTime++; // Total amount of time for this game. +} + +void Timer::loseTimer(byte which) { + for (int i = 0; i < 7; i++) { + if (_times[i]._reason == which) + _times[i]._timeLeft = 0; // Cancel this one! + } + + _timerLost = true; +} + +void Timer::openDrawbridge() { + _vm->_drawbridgeOpen++; + _vm->_background->draw(-1, -1, _vm->_drawbridgeOpen - 2); + + if (_vm->_drawbridgeOpen == 4) + _vm->_magics[1]._operation = kMagicNothing; // You may enter the drawbridge. + else + addTimer(7, kProcOpenDrawbridge, kReasonDrawbridgeFalls); +} + +void Timer::avariciusTalks() { + _vm->_dialogs->displayScrollChain('q', _vm->_avariciusTalk); + _vm->_avariciusTalk++; + + if (_vm->_avariciusTalk < 17) + addTimer(177, kProcAvariciusTalks, kReasonAvariciusTalks); + else + _vm->incScore(3); +} + +void Timer::urinate() { + _vm->_animation->_sprites[0]->turn(kDirUp); + _vm->_animation->stopWalking(); + _vm->drawDirection(); + addTimer(14, kProcToilet, kReasonGoToToilet); +} + +void Timer::toilet() { + _vm->_dialogs->displayText("That's better!"); +} + +void Timer::bang() { + Common::String tmpStr = Common::String::format("%c< BANG! >", kControlItalic); + _vm->_dialogs->displayText(tmpStr); + addTimer(30, kProcBang2, kReasonExplosion); +} + +void Timer::bang2() { + _vm->_dialogs->displayText("Hmm... sounds like Spludwick's up to something..."); +} + +void Timer::stairs() { + _vm->_sound->blip(); + _vm->_animation->_sprites[0]->walkTo(3); + _vm->_background->draw(-1, -1, 1); + _vm->_brummieStairs = 2; + _vm->_magics[10]._operation = kMagicSpecial; + _vm->_magics[10]._data = 2; // Reached the bottom of the stairs. + _vm->_magics[3]._operation = kMagicNothing; // Stop them hitting the sides (or the game will hang.) +} + +void Timer::cardiffSurvey() { + if (_vm->_cardiffQuestionNum == 0) { + _vm->_cardiffQuestionNum++; + _vm->_dialogs->displayScrollChain('q', 27); + } + + _vm->_dialogs->displayScrollChain('z', _vm->_cardiffQuestionNum); + _vm->_interrogation = _vm->_cardiffQuestionNum; + addTimer(182, kProcCardiffSurvey, kReasonCardiffsurvey); +} + +void Timer::cardiffReturn() { + _vm->_dialogs->displayScrollChain('q', 28); + cardiffSurvey(); // Add end of question. +} + +void Timer::cwytalotInHerts() { + _vm->_dialogs->displayScrollChain('q', 29); +} + +void Timer::getTiedUp() { + _vm->_dialogs->displayScrollChain('q', 34); // ...Trouble! + _vm->_userMovesAvvy = false; + _vm->_beenTiedUp = true; + _vm->_animation->stopWalking(); + + AnimationType *spr = _vm->_animation->_sprites[1]; + spr->stopWalk(); + spr->stopHoming(); + spr->_callEachStepFl = true; + spr->_eachStepProc = Animation::kProcGrabAvvy; + addTimer(70, kProcGetTiedUp2, kReasonGettingTiedUp); +} + +void Timer::getTiedUp2() { + _vm->_animation->_sprites[0]->walkTo(3); + _vm->_animation->_sprites[1]->walkTo(4); + _vm->_magics[3]._operation = kMagicNothing; // No effect when you touch the boundaries. + _vm->_friarWillTieYouUp = true; +} + +void Timer::hangAround() { + _vm->_animation->_sprites[1]->_doCheck = false; + + AnimationType *avvy = _vm->_animation->_sprites[0]; + avvy->init(7, true); // Robin Hood + _vm->setRoom(kPeopleRobinHood, kRoomRobins); + _vm->_animation->appearPed(0, 1); + _vm->_dialogs->displayScrollChain('q', 39); + avvy->walkTo(6); + addTimer(55, kProcHangAround2, kReasonHangingAround); +} + +void Timer::hangAround2() { + _vm->_dialogs->displayScrollChain('q', 40); + AnimationType *spr = _vm->_animation->_sprites[1]; + spr->_vanishIfStill = false; + spr->walkTo(3); + _vm->setRoom(kPeopleFriarTuck, kRoomRobins); + _vm->_dialogs->displayScrollChain('q', 41); + _vm->_animation->_sprites[0]->remove(); + spr->remove(); // Get rid of Robin Hood and Friar Tuck. + + addTimer(1, kProcAfterTheShootemup, kReasonHangingAround); + // Immediately call the following proc (when you have a chance). + + _vm->_tiedUp = false; + + // _vm->_enid->backToBootstrap(1); Call the shoot-'em-up. TODO: Replace it with proper ScummVM-friendly function(s)! Do not remove until then! +} + +void Timer::afterTheShootemup() { + // Only placed this here to replace the minigame. TODO: Remove it when the shoot em' up is implemented! + _vm->flipRoom(_vm->_room, 1); + + _vm->_animation->_sprites[0]->init(0, true); // Avalot. + _vm->_animation->appearPed(0, 1); + _vm->_userMovesAvvy = true; + _vm->_objects[kObjectCrossbow - 1] = true; + _vm->refreshObjectList(); + + // Same as the added line above: TODO: Remove it later!!! + _vm->_dialogs->displayText(Common::String("P.S.: There should have been the mini-game called \"shoot em' up\", " \ + "but I haven't implemented it yet: you get the crossbow automatically.") + kControlNewLine + kControlNewLine + "Peter (uruk)"); + +#if 0 + byte shootscore, gain; + + shootscore = mem[storage_seg * storage_ofs]; + gain = (shootscore + 5) / 10; // Rounding up. + + display(string("\6Your score was ") + strf(shootscore) + '.' + "\r\rYou gain (" + + strf(shootscore) + " 0xF6 10) = " + strf(gain) + " points."); + + if (gain > 20) { + display("But we won't let you have more than 20 points!"); + points(20); + } else + points(gain); +#endif + + warning("STUB: Timer::after_the_shootemup()"); + + _vm->_dialogs->displayScrollChain('q', 70); +} + +void Timer::jacquesWakesUp() { + _vm->_jacquesState++; + + switch (_vm->_jacquesState) { // Additional pictures. + case 1 : + _vm->_background->draw(-1, -1, 0); // Eyes open. + _vm->_dialogs->displayScrollChain('Q', 45); + break; + case 2 : // Going through the door. + _vm->_background->draw(-1, -1, 1); // Not on the floor. + _vm->_background->draw(-1, -1, 2); // But going through the door. + _vm->_magics[5]._operation = kMagicNothing; // You can't wake him up now. + break; + case 3 : // Gone through the door. + _vm->_background->draw(-1, -1, 1); // Not on the floor, either. + _vm->_background->draw(-1, -1, 3); // He's gone... so the door's open. + _vm->setRoom(kPeopleJacques, kRoomNowhere); // Gone! + break; + } + + if (_vm->_jacquesState == 5) { + _vm->_bellsAreRinging = true; + _vm->_aylesIsAwake = true; + _vm->incScore(2); + } + + switch (_vm->_jacquesState) { + case 1: + case 2: + case 3: + addTimer(12, kProcJacquesWakesUp, kReasonJacquesWakingUp); + break; + case 4: + addTimer(24, kProcJacquesWakesUp, kReasonJacquesWakingUp); + break; + } +} + +void Timer::naughtyDuke() { // This is when the Duke comes in and takes your money. + AnimationType *spr = _vm->_animation->_sprites[1]; + spr->init(9, false); // Here comes the Duke. + _vm->_animation->appearPed(1, 0); // He starts at the door... + spr->walkTo(2); // He walks over to you. + + // Let's get the door opening. + _vm->_background->draw(-1, -1, 0); + _vm->_sequence->startNaughtyDukeSeq(); + + addTimer(50, kProcNaughtyDuke2, kReasonNaughtyDuke); +} + +void Timer::naughtyDuke2() { + AnimationType *spr = _vm->_animation->_sprites[1]; + _vm->_dialogs->displayScrollChain('q', 48); // "Ha ha, it worked again!" + spr->walkTo(0); // Walk to the door. + spr->_vanishIfStill = true; // Then go away! + + addTimer(32, kProcNaughtyDuke3, kReasonNaughtyDuke); +} + +void Timer::naughtyDuke3() { + _vm->_background->draw(-1, -1, 0); + _vm->_sequence->startNaughtyDukeSeq(); +} + +void Timer::jump() { + AnimationType *avvy = _vm->_animation->_sprites[0]; + + _vm->_jumpStatus++; + switch (_vm->_jumpStatus) { + case 1: + case 2: + case 3: + case 5: + case 7: + case 9: + avvy->_y--; + break; + case 12: + case 13: + case 14: + case 16: + case 18: + case 19: + avvy->_y++; + break; + } + + if (_vm->_jumpStatus == 20) { // End of jump. + _vm->_userMovesAvvy = true; + _vm->_jumpStatus = 0; + } else // Still jumping. + addTimer(1, kProcJump, kReasonJumping); + + if ((_vm->_jumpStatus == 10) // You're at the highest point of your jump. + && (_vm->_room == kRoomInsideCardiffCastle) + && (_vm->_arrowInTheDoor == true) + && (_vm->_animation->inField(2))) { // Beside the wall + // Grab the arrow! + if (_vm->_carryNum >= kCarryLimit) + _vm->_dialogs->displayText("You fail to grab it, because your hands are full."); + else { + _vm->_background->draw(-1, -1, 1); + _vm->_arrowInTheDoor = false; // You've got it. + _vm->_objects[kObjectBolt - 1] = true; + _vm->refreshObjectList(); + _vm->_dialogs->displayScrollChain('q', 50); + _vm->incScore(3); + } + } +} + +void Timer::crapulusSaysSpludOut() { + _vm->_dialogs->displayScrollChain('q', 56); + _vm->_crapulusWillTell = false; +} + +void Timer::buyDrinks() { + _vm->_background->draw(-1, -1, 10); // Malagauche gets up again. + _vm->_malagauche = 0; + + _vm->_dialogs->displayScrollChain('D', _vm->_drinking); // Display message about it. + _vm->_pingo->wobble(); // Do the special effects. + _vm->_dialogs->displayScrollChain('D', 1); // That'll be thruppence. + if (_vm->decreaseMoney(3)) // Pay 3d. + _vm->_dialogs->displayScrollChain('D', 3); // Tell 'em you paid up. + _vm->_parser->drink(); +} + +void Timer::buyWine() { + _vm->_background->draw(-1, -1, 10); // Malagauche gets up again. + _vm->_malagauche = 0; + + _vm->_dialogs->displayScrollChain('D', 50); // You buy the wine. + _vm->_dialogs->displayScrollChain('D', 1); // It'll be thruppence. + if (_vm->decreaseMoney(3)) { + _vm->_dialogs->displayScrollChain('D', 4); // You paid up. + _vm->_objects[kObjectWine - 1] = true; + _vm->refreshObjectList(); + _vm->_wineState = 1; // OK Wine. + } +} + +void Timer::callsGuards() { + _vm->_dialogs->displayScrollChain('Q', 58); // "GUARDS!!!" + _vm->gameOver(); +} + +void Timer::greetsMonk() { + _vm->_dialogs->displayScrollChain('Q', 59); + _vm->_enteredLustiesRoomAsMonk = true; +} + +void Timer::fallDownOubliette() { + _vm->_magics[8]._operation = kMagicNothing; + + AnimationType *avvy = _vm->_animation->_sprites[0]; + avvy->_moveY++; // Increments dx/dy! + avvy->_y += avvy->_moveY; // Dowwwn we go... + addTimer(3, kProcFallDownOubliette, kReasonFallingDownOubliette); +} + +void Timer::meetAvaroid() { + if (_vm->_metAvaroid) { + Common::String tmpStr = Common::String::format("You can't expect to be %cthat%c lucky twice in a row!", + kControlItalic, kControlRoman); + _vm->_dialogs->displayText(tmpStr); + _vm->gameOver(); + } else { + _vm->_dialogs->displayScrollChain('Q', 60); + _vm->_metAvaroid = true; + addTimer(1, kProcRiseUpOubliette, kReasonRisingUpOubliette); + + AnimationType *avvy = _vm->_animation->_sprites[0]; + avvy->_facingDir = kDirLeft; + avvy->_x = 151; + avvy->_moveX = -3; + avvy->_moveY = -5; + + _vm->_graphics->setBackgroundColor(kColorGreen); + } +} + +void Timer::riseUpOubliette() { + AnimationType *avvy = _vm->_animation->_sprites[0]; + avvy->_visible = true; + avvy->_moveY++; // Decrements dx/dy! + avvy->_y -= avvy->_moveY; // Uuuupppp we go... + if (avvy->_moveY > 0) + addTimer(3, kProcRiseUpOubliette, kReasonRisingUpOubliette); + else + _vm->_userMovesAvvy = true; +} + +void Timer::robinHoodAndGeida() { + AnimationType *avvy = _vm->_animation->_sprites[0]; + avvy->init(7, true); + _vm->_animation->appearPed(0, 6); + avvy->walkTo(5); + + AnimationType *spr = _vm->_animation->_sprites[1]; + spr->stopWalk(); + spr->_facingDir = kDirLeft; + addTimer(20, kProcRobinHoodAndGeidaTalk, kReasonRobinHoodAndGeida); + _vm->_geidaFollows = false; +} + +void Timer::robinHoodAndGeidaTalk() { + _vm->_dialogs->displayScrollChain('q', 66); + + AnimationType *avvy = _vm->_animation->_sprites[0]; + AnimationType *spr = _vm->_animation->_sprites[1]; + avvy->walkTo(1); + spr->walkTo(1); + avvy->_vanishIfStill = true; + spr->_vanishIfStill = true; + + addTimer(162, kProcAvalotReturns, kReasonRobinHoodAndGeida); +} + +void Timer::avalotReturns() { + AnimationType *avvy = _vm->_animation->_sprites[0]; + AnimationType *spr = _vm->_animation->_sprites[1]; + avvy->remove(); + spr->remove(); + avvy->init(0, true); + _vm->_animation->appearPed(0, 0); + _vm->_dialogs->displayScrollChain('q', 67); + _vm->_userMovesAvvy = true; +} + +/** + * This is used when you sit down in the pub in Notts. It loops around + * so that it will happen when Avvy stops walking. + * @remarks Originally called 'avvy_sit_down' + */ +void Timer::avvySitDown() { + AnimationType *avvy = _vm->_animation->_sprites[0]; + if (avvy->_homing) // Still walking. + addTimer(1, kProcAvvySitDown, kReasonSittingDown); + else { + _vm->_background->draw(-1, -1, 2); + _vm->_sittingInPub = true; + _vm->_userMovesAvvy = false; + avvy->_visible = false; + } +} + +void Timer::ghostRoomPhew() { + Common::String tmpStr = Common::String::format("%cPHEW!%c You're glad to get out of %cthere!", + kControlItalic, kControlRoman, kControlItalic); + _vm->_dialogs->displayText(tmpStr); +} + +void Timer::arkataShouts() { + if (_vm->_teetotal) + return; + + _vm->_dialogs->displayScrollChain('q', 76); + addTimer(160, kProcArkataShouts, kReasonArkataShouts); +} + +void Timer::winning() { + _vm->_dialogs->displayScrollChain('q', 79); + _vm->_pingo->winningPic(); + + warning("STUB: Timer::winning()"); +#if 0 + do { + _vm->checkclick(); + } while (!(_vm->mrelease == 0)); +#endif + // TODO: To be implemented with Pingo::winningPic(). + + _vm->callVerb(kVerbCodeScore); + _vm->_dialogs->displayText(" T H E E N D "); + _vm->_letMeOut = true; +} + +void Timer::avalotFalls() { + AnimationType *avvy = _vm->_animation->_sprites[0]; + if (avvy->_stepNum < 5) { + avvy->_stepNum++; + addTimer(3, kProcAvalotFalls, kReasonFallingOver); + } else { + Common::String toDisplay = Common::String::format("%c%c%c%c%c%c%c%c%c%c%c%c%cZ%c", + kControlNewLine, kControlNewLine, kControlNewLine, kControlNewLine, + kControlNewLine, kControlNewLine, kControlInsertSpaces, kControlInsertSpaces, + kControlInsertSpaces, kControlInsertSpaces, kControlInsertSpaces, + kControlInsertSpaces, kControlRegister, kControlIcon); + _vm->_dialogs->displayText(toDisplay); + } +} + +void Timer::spludwickGoesToCauldron() { + if (_vm->_animation->_sprites[1]->_homing) + addTimer(1, kProcSpludwickGoesToCauldron, kReasonSpludwickWalk); + else + addTimer(17, kProcSpludwickLeavesCauldron, kReasonSpludwickWalk); +} + +void Timer::spludwickLeavesCauldron() { + _vm->_animation->_sprites[1]->_callEachStepFl = true; // So that normal procs will continue. +} + +void Timer::giveLuteToGeida() { // Moved here from Acci. + _vm->_dialogs->displayScrollChain('Q', 86); + _vm->incScore(4); + _vm->_lustieIsAsleep = true; + _vm->_sequence->startGeidaLuteSeq(); +} + +} // End of namespace Avalanche. diff --git a/engines/avalanche/timer.h b/engines/avalanche/timer.h new file mode 100644 index 0000000000..6cd894b0a5 --- /dev/null +++ b/engines/avalanche/timer.h @@ -0,0 +1,178 @@ +/* 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 the original source code of Lord Avalot d'Argent version 1.3. + * Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman. + */ + +/* Original name: TIMEOUT The scheduling unit. */ + +#ifndef AVALANCHE_TIMER_H +#define AVALANCHE_TIMER_H + +namespace Avalanche { +class AvalancheEngine; + +class Timer { +public: + // Reason runs between 1 and 28. + enum Reason { + kReasonDrawbridgeFalls = 2, + kReasonAvariciusTalks = 3, + kReasonGoToToilet = 4, + kReasonExplosion = 5, + kReasonBrummieStairs = 6, + kReasonCardiffsurvey = 7, + kReasonCwytalotInHerts = 8, + kReasonGettingTiedUp = 9, + kReasonHangingAround = 10, // Tied to the tree in Nottingham. + kReasonJacquesWakingUp = 11, + kReasonNaughtyDuke = 12, + kReasonJumping = 13, + kReasonSequencer = 14, + kReasonCrapulusSaysSpludwickOut = 15, + kReasonDawndelay = 16, + kReasonDrinks = 17, + kReasonDuLustieTalks = 18, + kReasonFallingDownOubliette = 19, + kReasonMeetingAvaroid = 20, + kReasonRisingUpOubliette = 21, + kReasonRobinHoodAndGeida = 22, + kReasonSittingDown = 23, + kReasonGhostRoomPhew = 1, + kReasonArkataShouts = 24, + kReasonWinning = 25, + kReasonFallingOver = 26, + kReasonSpludwickWalk = 27, + kReasonGeidaSings = 28 + }; + + // Proc runs between 1 and 41. + enum Proc { + kProcOpenDrawbridge = 3, + kProcAvariciusTalks = 4, + kProcUrinate = 5, + kProcToilet = 6, + kProcBang = 7, + kProcBang2 = 8, + kProcStairs = 9, + kProcCardiffSurvey = 10, + kProcCardiffReturn = 11, + kProcCwytalotInHerts = 12, + kProcGetTiedUp = 13, + kProcGetTiedUp2 = 1, + kProcHangAround = 14, + kProcHangAround2 = 15, + kProcAfterTheShootemup = 32, + kProcJacquesWakesUp = 16, + kProcNaughtyDuke = 17, + kProcNaughtyDuke2 = 18, + kProcNaughtyDuke3 = 38, + kProcJump = 19, + kProcSequence = 20, + kProcCrapulusSpludOut = 21, + kProcDawnDelay = 22, + kProcBuyDrinks = 23, + kProcBuyWine = 24, + kProcCallsGuards = 25, + kProcGreetsMonk = 26, + kProcFallDownOubliette = 27, + kProcMeetAvaroid = 28, + kProcRiseUpOubliette = 29, + kProcRobinHoodAndGeida = 2, + kProcRobinHoodAndGeidaTalk = 30, + kProcAvalotReturns = 31, + kProcAvvySitDown = 33, // In Nottingham. + kProcGhostRoomPhew = 34, + kProcArkataShouts = 35, + kProcWinning = 36, + kProcAvalotFalls = 37, + kProcSpludwickGoesToCauldron = 39, + kProcSpludwickLeavesCauldron = 40, + kProcGiveLuteToGeida = 41 + }; + + struct TimerType { + int32 _timeLeft; + byte _action; + byte _reason; + }; + + TimerType _times[7]; + bool _timerLost; // Is the timer "lost"? (Because of using loseTimer()) + + Timer(AvalancheEngine *vm); + + void addTimer(int32 duration, byte action, byte reason); + void updateTimer(); + void loseTimer(byte which); + + // Procedures to do things at the end of amounts of time: + void openDrawbridge(); + void avariciusTalks(); + void urinate(); + void toilet(); + void bang(); + void bang2(); + void stairs(); + void cardiffSurvey(); + void cardiffReturn(); + void cwytalotInHerts(); + void getTiedUp(); + void getTiedUp2(); + void hangAround(); + void hangAround2(); + void afterTheShootemup(); + void jacquesWakesUp(); + void naughtyDuke(); + void naughtyDuke2(); + void naughtyDuke3(); + void jump(); + void crapulusSaysSpludOut(); + void buyDrinks(); + void buyWine(); + void callsGuards(); + void greetsMonk(); + void fallDownOubliette(); + void meetAvaroid(); + void riseUpOubliette(); + void robinHoodAndGeida(); + void robinHoodAndGeidaTalk(); + void avalotReturns(); + void avvySitDown(); + void ghostRoomPhew(); + void arkataShouts(); + void winning(); + void avalotFalls(); + void spludwickGoesToCauldron(); + void spludwickLeavesCauldron(); + void giveLuteToGeida(); + +private: + AvalancheEngine *_vm; + +}; + +} // End of namespace Avalanche. + +#endif // AVALANCHE_TIMER_H diff --git a/engines/configure.engines b/engines/configure.engines index fab0862407..195d0a3426 100644 --- a/engines/configure.engines +++ b/engines/configure.engines @@ -6,6 +6,7 @@ add_engine he "HE71+ games" yes add_engine agi "AGI" yes add_engine agos "AGOS" yes "agos2" "AGOS 1 games" add_engine agos2 "AGOS 2 games" yes +add_engine avalanche "Lord Avalot d'Argent" no add_engine cge "CGE" yes add_engine cine "Cinematique evo 1" yes add_engine composer "Magic Composer" yes @@ -54,3 +55,4 @@ add_engine tony "Tony Tough and the Night of Roasted Moths" yes "" "" "16bit" add_engine tsage "TsAGE" yes add_engine tucker "Bud Tucker in Double Trouble" yes add_engine wintermute "Wintermute" no "" "" "jpeg png zlib vorbis 16bit" +add_engine zvision "ZVision" no "" "" "freetype2 16bit" diff --git a/engines/draci/draci.cpp b/engines/draci/draci.cpp index 6aa8477887..06730cfba7 100644 --- a/engines/draci/draci.cpp +++ b/engines/draci/draci.cpp @@ -92,6 +92,32 @@ DraciEngine::DraciEngine(OSystem *syst, const ADGameDescription *gameDesc) DebugMan.addDebugChannel(kDraciWalkingDebugLevel, "walking", "Walking debug info"); _console = new DraciConsole(this); + + _screen = 0; + _mouse = 0; + _game = 0; + _script = 0; + _anims = 0; + _sound = 0; + _music = 0; + _smallFont = 0; + _bigFont = 0; + _iconsArchive = 0; + _objectsArchive = 0; + _spritesArchive = 0; + _paletteArchive = 0; + _roomsArchive = 0; + _overlaysArchive = 0; + _animationsArchive = 0; + _walkingMapsArchive = 0; + _itemsArchive = 0; + _itemImagesArchive = 0; + _initArchive = 0; + _stringsArchive = 0; + _soundsArchive = 0; + _dubbingArchive = 0; + _showWalkingMap = 0; + _pauseStartTime = 0; } bool DraciEngine::hasFeature(EngineFeature f) const { diff --git a/engines/draci/game.cpp b/engines/draci/game.cpp index c4108cc0c7..a5c8aa867f 100644 --- a/engines/draci/game.cpp +++ b/engines/draci/game.cpp @@ -52,6 +52,55 @@ enum { Game::Game(DraciEngine *vm) : _vm(vm), _walkingState(vm) { uint i; + _dialogueLinesNum = 0; + _blockNum = 0; + + for (i = 0; i < kDialogueLines; i++) + _dialogueAnims[0] = 0; + + _loopStatus = kStatusOrdinary; + _loopSubstatus = kOuterLoop; + _shouldQuit = 0; + _shouldExitLoop = 0; + _isReloaded = 0; + _speechTick = 0; + _speechDuration = 0; + _objUnderCursor = 0; + _animUnderCursor = 0; + _markedAnimationIndex = 0; + _scheduledPalette = 0; + _fadePhases = 0; + _fadePhase = 0; + _fadeTick = 0; + _mouseChangeTick = 0; + _enableQuickHero = 0; + _wantQuickHero = 0; + _enableSpeedText = 0; + _titleAnim = 0; + _inventoryAnim = 0; + _walkingMapOverlay = 0; + _walkingShortestPathOverlay = 0; + _walkingObliquePathOverlay = 0; + _currentItem = 0; + _itemUnderCursor = 0; + _previousItemPosition = 0; + + for (i = 0; i < kInventorySlots; i++) + _inventory[i] = 0; + + _newRoom = 0; + _newGate = 0; + _previousRoom = 0; + _pushedNewRoom = 0; + _pushedNewGate = 0; + _currentDialogue = 0; + _dialogueArchive = 0; + _dialogueBlocks = 0; + _dialogueBegin = 0; + _dialogueExit = 0; + _currentBlock = 0; + _lastBlock = 0; + BArchive *initArchive = _vm->_initArchive; const BAFile *file; @@ -951,9 +1000,9 @@ void Game::dialogueMenu(int dialogueID) { debugC(7, kDraciLogicDebugLevel, "hit: %d, _lines[hit]: %d, lastblock: %d, dialogueLines: %d, dialogueExit: %d", - hit, _lines[hit], _lastBlock, _dialogueLinesNum, _dialogueExit); + hit, (hit >= 0 ? _lines[hit] : -1), _lastBlock, _dialogueLinesNum, _dialogueExit); - if ((!_dialogueExit) && (hit != -1) && (_lines[hit] != -1)) { + if ((!_dialogueExit) && (hit >= 0) && (_lines[hit] != -1)) { if ((oldLines == 1) && (_dialogueLinesNum == 1) && (_lines[hit] == _lastBlock)) { break; } diff --git a/engines/draci/sprite.cpp b/engines/draci/sprite.cpp index 965cdabf3e..9a78904d25 100644 --- a/engines/draci/sprite.cpp +++ b/engines/draci/sprite.cpp @@ -38,9 +38,9 @@ const Displacement kNoDisplacement = { 0, 0, 1.0, 1.0 }; * height height of the image in the buffer */ static void transformToRows(byte *img, uint16 width, uint16 height) { - byte *buf = new byte[width * height]; + byte *buf = new byte[(uint)(width * height)]; byte *tmp = buf; - memcpy(buf, img, width * height); + memcpy(buf, img, (uint)(width * height)); for (uint16 i = 0; i < width; ++i) { for (uint16 j = 0; j < height; ++j) { diff --git a/engines/draci/surface.cpp b/engines/draci/surface.cpp index 3676c6edac..4156398070 100644 --- a/engines/draci/surface.cpp +++ b/engines/draci/surface.cpp @@ -82,7 +82,7 @@ void Surface::markClean() { void Surface::fill(uint color) { byte *ptr = (byte *)getPixels(); - memset(ptr, color, w * h); + memset(ptr, color, (uint)(w * h)); } /** diff --git a/engines/draci/walking.cpp b/engines/draci/walking.cpp index f1ae769d80..195b968860 100644 --- a/engines/draci/walking.cpp +++ b/engines/draci/walking.cpp @@ -556,9 +556,15 @@ bool WalkingState::alignHeroToEdge(const Common::Point &p1, const Common::Point } bool reachedEnd; if (movement == kMoveLeft || movement == kMoveRight) { + if (p2Diff.x == 0) { + error("Wrong value for horizontal movement"); + } reachedEnd = movement == kMoveLeft ? hero->x <= p2.x : hero->x >= p2.x; hero->y += hero->x * p2Diff.y / p2Diff.x - prevHero.x * p2Diff.y / p2Diff.x; } else { + if (p2Diff.y == 0) { + error("Wrong value for vertical movement"); + } reachedEnd = movement == kMoveUp ? hero->y <= p2.y : hero->y >= p2.y; hero->x += hero->y * p2Diff.x / p2Diff.y - prevHero.y * p2Diff.x / p2Diff.y; } diff --git a/engines/draci/walking.h b/engines/draci/walking.h index a43aeb272a..7e4a3184f5 100644 --- a/engines/draci/walking.h +++ b/engines/draci/walking.h @@ -103,7 +103,17 @@ struct GPL2Program; class WalkingState { public: - explicit WalkingState(DraciEngine *vm) : _vm(vm) { stopWalking(); } + explicit WalkingState(DraciEngine *vm) : _vm(vm) { + _dir = kDirectionLast; + _startingDirection = kMoveUndefined; + _segment = 0; + _lastAnimPhase = 0; + _turningFinished = 0; + _callbackOffset = 0; + + stopWalking(); + } + ~WalkingState() {} void stopWalking(); diff --git a/engines/drascula/animation.cpp b/engines/drascula/animation.cpp index 1145c8c3ff..ee981c36da 100644 --- a/engines/drascula/animation.cpp +++ b/engines/drascula/animation.cpp @@ -1645,10 +1645,10 @@ void DrasculaEngine::animation_9_6() { int v_cd; - animate("fin.bin", 14); + (void)animate("fin.bin", 14); playMusic(13); flags[5] = 1; - animate("drf.bin", 16); + (void)animate("drf.bin", 16); fadeToBlack(0); clearRoom(); curX = -1; diff --git a/engines/drascula/converse.cpp b/engines/drascula/converse.cpp index 95a5f7d87f..b3749445ec 100644 --- a/engines/drascula/converse.cpp +++ b/engines/drascula/converse.cpp @@ -168,19 +168,19 @@ void DrasculaEngine::converse(int index) { // delete stream; if (currentChapter == 2 && !strcmp(fileName, "op_5.cal") && flags[38] == 1 && flags[33] == 1) { - strcpy(phrase3, _text[405]); + Common::strlcpy(phrase3, _text[405], 128); strcpy(sound3, "405.als"); answer3 = 31; } if (currentChapter == 6 && !strcmp(fileName, "op_12.cal") && flags[7] == 1) { - strcpy(phrase3, _text[273]); + Common::strlcpy(phrase3, _text[273], 128); strcpy(sound3, "273.als"); answer3 = 14; } if (currentChapter == 6 && !strcmp(fileName, "op_12.cal") && flags[10] == 1) { - strcpy(phrase3, _text[274]); + Common::strlcpy(phrase3, _text[274], 128); strcpy(sound3, "274.als"); answer3 = 15; } diff --git a/engines/drascula/drascula.cpp b/engines/drascula/drascula.cpp index cde00baa32..9699dda021 100644 --- a/engines/drascula/drascula.cpp +++ b/engines/drascula/drascula.cpp @@ -89,6 +89,59 @@ DrasculaEngine::DrasculaEngine(OSystem *syst, const DrasculaGameDescription *gam _talkSequences = 0; _currentSaveSlot = 0; + bjX = 0; + bjY = 0; + trackBJ = 0; + framesWithoutAction = 0; + term_int = 0; + currentChapter = 0; + _loadedDifferentChapter = 0; + musicStopped = 0; + FrameSSN = 0; + globalSpeed = 0; + LastFrame = 0; + flag_tv = 0; + _charMapSize = 0; + _itemLocationsSize = 0; + _polXSize = 0; + _verbBarXSize = 0; + _x1dMenuSize = 0; + _frameXSize = 0; + _candleXSize = 0; + _pianistXSize = 0; + _drunkXSize = 0; + _roomPreUpdatesSize = 0; + _roomUpdatesSize = 0; + _roomActionsSize = 0; + _talkSequencesSize = 0; + _numLangs = 0; + feetHeight = 0; + floorX1 = 0; + floorY1 = 0; + floorX2 = 0; + floorY2 = 0; + lowerLimit = 0; + upperLimit = 0; + trackFinal = 0; + walkToObject = 0; + objExit = 0; + _startTime = 0; + hasAnswer = 0; + savedTime = 0; + breakOut = 0; + vonBraunX = 0; + trackVonBraun = 0; + vonBraunHasMoved = 0; + newHeight = 0; + newWidth = 0; + color_solo = 0; + igorX = 0; + igorY = 0; + trackIgor = 0; + drasculaX = 0; + drasculaY = 0; + trackDrascula = 0; + _color = 0; blinking = 0; _mouseX = 0; @@ -297,7 +350,7 @@ Common::Error DrasculaEngine::run() { memset(iconName, 0, sizeof(iconName)); for (i = 0; i < 6; i++) - strcpy(iconName[i + 1], _textverbs[i]); + Common::strlcpy(iconName[i + 1], _textverbs[i], 13); assignPalette(defaultPalette); diff --git a/engines/drascula/graphics.cpp b/engines/drascula/graphics.cpp index b28de669b6..fe954279c3 100644 --- a/engines/drascula/graphics.cpp +++ b/engines/drascula/graphics.cpp @@ -336,7 +336,7 @@ void DrasculaEngine::centerText(const char *message, int textX, int textY) { // original starts printing 4 lines above textY int y = CLIP<int>(textY - (4 * CHAR_HEIGHT), 0, 320); - strcpy(msg, message); + Common::strlcpy(msg, message, 200); // If the message fits on screen as-is, just print it here if (textFitsCentered(msg, textX)) { @@ -363,8 +363,8 @@ void DrasculaEngine::centerText(const char *message, int textX, int textY) { while (curWord != NULL) { // Check if the word and the current line fit on screen if (tmpMessageLine[0] != '\0') - strcat(tmpMessageLine, " "); - strcat(tmpMessageLine, curWord); + Common::strlcat(tmpMessageLine, " ", 200); + Common::strlcat(tmpMessageLine, curWord, 200); if (textFitsCentered(tmpMessageLine, textX)) { // Line fits, so add the word to the current message line strcpy(messageLine, tmpMessageLine); @@ -374,8 +374,8 @@ void DrasculaEngine::centerText(const char *message, int textX, int textY) { // If it goes off screen, print_abc will adjust it x = CLIP<int>(textX - strlen(messageLine) * CHAR_WIDTH / 2, 60, 255); print_abc(messageLine, x, y + curLine * CHAR_HEIGHT); - strcpy(messageLine, curWord); - strcpy(tmpMessageLine, curWord); + Common::strlcpy(messageLine, curWord, 200); + Common::strlcpy(tmpMessageLine, curWord, 200); curLine++; } diff --git a/engines/drascula/objects.cpp b/engines/drascula/objects.cpp index 35dfd3162a..519e919433 100644 --- a/engines/drascula/objects.cpp +++ b/engines/drascula/objects.cpp @@ -265,8 +265,9 @@ void DrasculaEngine::updateVisible() { } if (_roomNumber == 22 && flags[27] == 1) visible[3] = 0; - if (_roomNumber == 26 && flags[21] == 0) - strcpy(objName[2], _textmisc[0]); + if (_roomNumber == 26 && flags[21] == 0) { + Common::strlcpy(objName[2], _textmisc[0], 20); + } if (_roomNumber == 26 && flags[18] == 1) visible[2] = 0; if (_roomNumber == 26 && flags[12] == 1) diff --git a/engines/dreamweb/dreamweb.cpp b/engines/dreamweb/dreamweb.cpp index 08838a784a..6c6f5296f4 100644 --- a/engines/dreamweb/dreamweb.cpp +++ b/engines/dreamweb/dreamweb.cpp @@ -225,6 +225,13 @@ DreamWebEngine::DreamWebEngine(OSystem *syst, const DreamWebGameDescription *gam _linePointer = 0; _lineDirection = 0; _lineLength = 0; + + _subtitles = 0; + _foreignRelease = 0; + _wonGame = 0; + _hasSpeech = 0; + _roomsSample = 0; + _copyProtection = 0; } DreamWebEngine::~DreamWebEngine() { diff --git a/engines/engines.mk b/engines/engines.mk index 7ad9dd666d..c15b0ad43c 100644 --- a/engines/engines.mk +++ b/engines/engines.mk @@ -26,6 +26,11 @@ DEFINES += -DENABLE_AGOS2 endif endif +ifdef ENABLE_AVALANCHE +DEFINES += -DENABLE_AVALANCHE=$(ENABLE_AVALANCHE) +MODULES += engines/avalanche +endif + ifdef ENABLE_CGE DEFINES += -DENABLE_CGE=$(ENABLE_CGE) MODULES += engines/cge @@ -256,3 +261,8 @@ ifdef ENABLE_WINTERMUTE DEFINES += -DENABLE_WINTERMUTE=$(ENABLE_WINTERMUTE) MODULES += engines/wintermute endif + +ifdef ENABLE_ZVISION +DEFINES += -DENABLE_ZVISION=$(ENABLE_ZVISION) +MODULES += engines/zvision +endif diff --git a/engines/fullpipe/interaction.cpp b/engines/fullpipe/interaction.cpp index 9fd42c15ae..80cbce946b 100644 --- a/engines/fullpipe/interaction.cpp +++ b/engines/fullpipe/interaction.cpp @@ -137,7 +137,7 @@ bool InteractionController::handleInteraction(StaticANIObject *subj, GameObject obj->setPicAniInfo(&aniInfo); if (abs(xpos - subj->_ox) > 1 || abs(ypos - subj->_oy) > 1) { - mq = getSc2MctlCompoundBySceneId(g_fullpipe->_currentScene->_sceneId)->method4C(subj, xpos, ypos, 1, cinter->_staticsId2); + mq = getSc2MctlCompoundBySceneId(g_fullpipe->_currentScene->_sceneId)->doWalkTo(subj, xpos, ypos, 1, cinter->_staticsId2); if (mq) { dur = mq->calcDuration(subj); delete mq; diff --git a/engines/fullpipe/messages.cpp b/engines/fullpipe/messages.cpp index b5f2cb8303..d58212dc29 100644 --- a/engines/fullpipe/messages.cpp +++ b/engines/fullpipe/messages.cpp @@ -309,6 +309,10 @@ void MessageQueue::messageQueueCallback1(int par) { debug(3, "STUB: MessageQueue::messageQueueCallback1()"); } +void MessageQueue::addExCommand(ExCommand *ex) { + _exCommands.push_front(ex); +} + ExCommand *MessageQueue::getExCommandByIndex(uint idx) { if (idx > _exCommands.size()) return 0; @@ -323,6 +327,23 @@ ExCommand *MessageQueue::getExCommandByIndex(uint idx) { return *it; } +void MessageQueue::deleteExCommandByIndex(uint idx, bool doFree) { + if (idx > _exCommands.size()) + return; + + Common::List<ExCommand *>::iterator it = _exCommands.begin(); + + while (idx) { + ++it; + idx--; + } + + _exCommands.erase(it); + + if (doFree) + delete *it; +} + void MessageQueue::sendNextCommand() { if (_exCommands.size()) { if (!(_flags & 4) && (_flags & 1)) { diff --git a/engines/fullpipe/messages.h b/engines/fullpipe/messages.h index 6b72364323..a3533e1bd2 100644 --- a/engines/fullpipe/messages.h +++ b/engines/fullpipe/messages.h @@ -119,7 +119,9 @@ class MessageQueue : public CObject { uint getCount() { return _exCommands.size(); } + void addExCommand(ExCommand *ex); ExCommand *getExCommandByIndex(uint idx); + void deleteExCommandByIndex(uint idx, bool doFree); void replaceKeyCode(int key1, int key2); diff --git a/engines/fullpipe/motion.cpp b/engines/fullpipe/motion.cpp index 325d366a6c..f9158397c2 100644 --- a/engines/fullpipe/motion.cpp +++ b/engines/fullpipe/motion.cpp @@ -116,13 +116,13 @@ void MctlCompound::freeItems() { warning("STUB: MctlCompound::freeItems()"); } -MessageQueue *MctlCompound::method34(StaticANIObject *subj, int xpos, int ypos, int fuzzySearch, int staticsId) { +MessageQueue *MctlCompound::method34(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId) { warning("STUB: MctlCompound::method34()"); return 0; } -MessageQueue *MctlCompound::method4C(StaticANIObject *subj, int xpos, int ypos, int fuzzySearch, int staticsId) { +MessageQueue *MctlCompound::doWalkTo(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId) { int match1 = -1; int match2 = -1; @@ -154,14 +154,14 @@ MessageQueue *MctlCompound::method4C(StaticANIObject *subj, int xpos, int ypos, return 0; if (match1 == match2) - return _motionControllers[match1]->_motionControllerObj->method4C(subj, xpos, ypos, fuzzySearch, staticsId); + return _motionControllers[match1]->_motionControllerObj->doWalkTo(subj, xpos, ypos, fuzzyMatch, staticsId); MctlConnectionPoint *closestP = findClosestConnectionPoint(subj->_ox, subj->_oy, match1, xpos, ypos, match2, &match2); if (!closestP) return 0; - MessageQueue *mq = _motionControllers[match1]->_motionControllerObj->method4C(subj, closestP->_connectionX, closestP->_connectionY, 1, closestP->_field_14); + MessageQueue *mq = _motionControllers[match1]->_motionControllerObj->doWalkTo(subj, closestP->_connectionX, closestP->_connectionY, 1, closestP->_field_14); ExCommand *ex; @@ -174,7 +174,7 @@ MessageQueue *MctlCompound::method4C(StaticANIObject *subj, int xpos, int ypos, ex = new ExCommand(subj->_id, 51, 0, xpos, ypos, 0, 1, 0, 0, 0); - ex->_field_20 = fuzzySearch; + ex->_field_20 = fuzzyMatch; ex->_keyCode = subj->_okeyCode; ex->_excFlags |= 2; @@ -285,7 +285,7 @@ int MovGraph::method2C() { return 0; } -MessageQueue *MovGraph::method34(StaticANIObject *subj, int xpos, int ypos, int fuzzySearch, int staticsId) { +MessageQueue *MovGraph::method34(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId) { warning("STUB: MovGraph::method34()"); return 0; @@ -309,8 +309,8 @@ int MovGraph::method44() { return 0; } -MessageQueue *MovGraph::method4C(StaticANIObject *subj, int xpos, int ypos, int fuzzySearch, int staticsId) { - warning("STUB: MovGraph::method4C()"); +MessageQueue *MovGraph::doWalkTo(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId) { + warning("STUB: MovGraph::doWalkTo()"); return 0; } @@ -321,7 +321,7 @@ int MovGraph::method50() { return 0; } -double MovGraph::calcDistance(Common::Point *point, MovGraphLink *link, int fuzzySearch) { +double MovGraph::calcDistance(Common::Point *point, MovGraphLink *link, int fuzzyMatch) { int n1x = link->_movGraphNode1->_x; int n1y = link->_movGraphNode1->_y; int n2x = link->_movGraphNode2->_x; @@ -336,7 +336,7 @@ double MovGraph::calcDistance(Common::Point *point, MovGraphLink *link, int fuzz double res = sqrt(1.0 - dist2 * dist2) * dist1; if (dist2 <= 0.0 || distm >= link->_distance) { - if (fuzzySearch) { + if (fuzzyMatch) { if (dist2 > 0.0) { if (distm >= link->_distance) { point->x = n2x; @@ -365,6 +365,29 @@ int MovGraph2::getItemIndexByGameObjectId(int objectId) { return -1; } +int MovGraph2::getItemSubIndexByStaticsId(int idx, int staticsId) { + for (int i = 0; i < 4; i++) + if (_items[idx]->_subItems[i]._staticsId1 == staticsId || _items[idx]->_subItems[i]._staticsId2 == staticsId) + return i; + + return -1; +} + +int MovGraph2::getItemSubIndexByMovementId(int idx, int movId) { + for (int i = 0; i < 4; i++) + if (_items[idx]->_subItems[i]._walk[0]._movementId == movId || _items[idx]->_subItems[i]._turn[0]._movementId == movId || + _items[idx]->_subItems[i]._turnS[0]._movementId == movId) + return i; + + return -1; +} + +int MovGraph2::getItemSubIndexByMGM(int idx, StaticANIObject *ani) { + warning("STUB: MovGraph2::getItemSubIndexByMGM()"); + + return -1; +} + bool MovGraph2::initDirections(StaticANIObject *obj, MovGraph2Item *item) { item->_obj = obj; item->_objectId = obj->_id; @@ -512,6 +535,16 @@ void MovGraph2::addObject(StaticANIObject *obj) { } } +void MovGraph2::buildMovInfo1SubItems(MovInfo1 *movinfo, Common::Array<MovGraphLink *> *linkList, LinkInfo *lnkSrc, LinkInfo *lnkDst) { + warning("STUB: MovGraph2::buildMovInfo1SubItems()"); +} + +MessageQueue *MovGraph2::buildMovInfo1MessageQueue(MovInfo1 *movInfo) { + warning("STUB: MovGraph2::buildMovInfo1MessageQueue()"); + + return 0; +} + int MovGraph2::removeObject(StaticANIObject *obj) { warning("STUB: MovGraph2::removeObject()"); @@ -522,23 +555,20 @@ void MovGraph2::freeItems() { warning("STUB: MovGraph2::freeItems()"); } -MessageQueue *MovGraph2::method34(StaticANIObject *subj, int xpos, int ypos, int fuzzySearch, int staticsId) { +MessageQueue *MovGraph2::method34(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId) { warning("STUB: MovGraph2::method34()"); return 0; } -MessageQueue *MovGraph2::method4C(StaticANIObject *obj, int xpos, int ypos, int fuzzySearch, int staticsId) { - warning("STUB: MovGraph2::method4C()"); -#if 0 +MessageQueue *MovGraph2::doWalkTo(StaticANIObject *obj, int xpos, int ypos, int fuzzyMatch, int staticsId) { LinkInfo linkInfoDest; LinkInfo linkInfoSource; MovInfo1 movInfo1; PicAniInfo picAniInfo; - ObList tempLinkList; + Common::Point point; int idx = getItemIndexByGameObjectId(obj->_id); - ex = idx_; if (idx < 0) return 0; @@ -551,7 +581,7 @@ MessageQueue *MovGraph2::method4C(StaticANIObject *obj, int xpos, int ypos, int point.x = 0; - GameObject_getPicAniInfo(obj, &picAniInfo); + obj->getPicAniInfo(&picAniInfo); int idxsub; @@ -575,7 +605,7 @@ MessageQueue *MovGraph2::method4C(StaticANIObject *obj, int xpos, int ypos, int if (subMgm) { obj->_messageQueueId = 0; - obj->changeStatics2(_items[idx]->_subItems[idxsub]->_staticsId1); + obj->changeStatics2(_items[idx]->_subItems[idxsub]._staticsId1); newx = obj->_ox; newy = obj->_oy; } else { @@ -595,76 +625,48 @@ MessageQueue *MovGraph2::method4C(StaticANIObject *obj, int xpos, int ypos, int obj->setOXY(newx, newy); } - v25 = obj->_oy; - y = v25; - if (obj->_ox == xpos && obj->_oy == ypos) { - MessageQueue *mq = new MessageQueue(g_globalMessageQueueList->compact()); + g_fullpipe->_globalMessageQueueList->compact(); + + MessageQueue *mq = new MessageQueue(); if (staticsId && obj->_statics->_staticsId != staticsId) { - point.x = getItem1IndexByStaticsId(idx, staticsId); - if (point.x == -1) { - GameObject_setPicAniInfo(obj, &picAniInfo); + int idxwalk = getItemSubIndexByStaticsId(idx, staticsId); + if (idxwalk == -1) { + obj->setPicAniInfo(&picAniInfo); + + delete mq; + return 0; } - ex = (int)operator new(sizeof(ExCommand)); - v71.state = 1; - if (ex) - v27 = ExCommand_ctor( - (ExCommand *)ex, - picAniInfo.objectId, - 1, - *((_DWORD *)this->items.CObArray.m_pData[offsetof(MovGraph2, movGraph)] - + 186 * idx - + 46 * idxsub - + 4 * (point.x + 8)), - 0, - 0, - 0, - 1, - 0, - 0, - 0); - else - v27 = 0; - v28 = picAniInfo.field_8; - v27->msg.field_24 = 1; - v27->msg.keyCode = v28; - v27->excFlags |= 2u; - v71.state = -1; - CPtrList::AddTail(&v62->exCommands, v27); + + ExCommand *ex = new ExCommand(picAniInfo.objectId, 1, _items[idx]->_subItems[idxsub]._walk[idxwalk]._movementId, 0, 0, 0, 1, 0, 0, 0); + + ex->_field_24 = 1; + ex->_keyCode = picAniInfo.field_8; + ex->_excFlags |= 2; + + mq->_exCommands.push_back(ex); } else { - v29 = (ExCommand *)operator new(sizeof(ExCommand)); - point.x = (int)v29; - v71.state = 2; - if (v29) - v30 = ExCommand_ctor(v29, picAniInfo.objectId, 22, obj->statics->staticsId, 0, 0, 0, 1, 0, 0, 0); - else - v30 = 0; - v31 = v62; - v30->msg.keyCode = picAniInfo.field_8; - v32 = (int)&v31->exCommands; - v33 = v30->excFlags | 3; - v71.state = -1; - v30->excFlags = v33; - CPtrList::AddTail(&v31->exCommands, v30); - v34 = (ExCommand *)operator new(sizeof(ExCommand)); - point.x = (int)v34; - v71.state = 3; - if (v34) - v35 = ExCommand_ctor(v34, picAniInfo.objectId, 5, -1, obj->GameObject.ox, obj->GameObject.oy, 0, 1, 0, 0, 0); - else - v35 = 0; - v36 = v35->excFlags; - v35->msg.field_14 = -1; - v35->msg.keyCode = picAniInfo.field_8; - v71.state = -1; - v35->excFlags = v36 | 3; - CPtrList::AddTail(v32, v35); + ExCommand *ex = new ExCommand(picAniInfo.objectId, 22, obj->_statics->_staticsId, 0, 0, 0, 1, 0, 0, 0); + + ex->_keyCode = picAniInfo.field_8; + ex->_excFlags |= 3; + mq->_exCommands.push_back(ex); + + ex = new ExCommand(picAniInfo.objectId, 5, -1, obj->_ox, obj->_oy, 0, 1, 0, 0, 0); + + ex->_field_14 = -1; + ex->_keyCode = picAniInfo.field_8; + ex->_excFlags |= 3; + mq->_exCommands.push_back(ex); } - GameObject_setPicAniInfo(obj, &picAniInfo); - return v62; + + obj->setPicAniInfo(&picAniInfo); + + return mq; } + linkInfoSource.node = findNode(obj->_ox, obj->_oy, 0); if (!linkInfoSource.node) { @@ -674,7 +676,7 @@ MessageQueue *MovGraph2::method4C(StaticANIObject *obj, int xpos, int ypos, int linkInfoSource.link = findLink2(obj->_ox, obj->_oy); if (!linkInfoSource.link) { - obj->setPicAniInfo(picAniInfo); + obj->setPicAniInfo(&picAniInfo); return 0; } @@ -685,169 +687,294 @@ MessageQueue *MovGraph2::method4C(StaticANIObject *obj, int xpos, int ypos, int if (!linkInfoDest.node) { linkInfoDest.link = findLink1(xpos, ypos, idxsub, fuzzyMatch); + if (!linkInfoDest.link) { - obj->setPicAniInfo(picAniInfo); + obj->setPicAniInfo(&picAniInfo); return 0; } } - ObList_ctor(&tempLinkList, 10); + Common::Array<MovGraphLink *> tempLinkList; + double minPath = findMinPath(&linkInfoSource, &linkInfoDest, &tempLinkList); - MovGraph2_findLinks(this, &linkInfoSource, &linkInfoDest, (int)&tempLinkList); - if (v6 < 0.0 || (linkInfoSource.node != linkInfoDest.node || !linkInfoSource.node) && !tempLinkList.m_nCount) { - ObList_dtor(&tempLinkList); + debug(0, "MovGraph2::doWalkTo(): path: %lf parts: %d", minPath, tempLinkList.size()); + + if (minPath < 0.0 || ((linkInfoSource.node != linkInfoDest.node || !linkInfoSource.node) && !tempLinkList.size())) return 0; - } - memset(&movInfo1, 0, sizeof(movInfo1)); - v39 = y; + movInfo1.subIndex = idxsub; - v40 = point.x; - movInfo1.pt1.y = y; - movInfo1.pt1.x = point.x; + movInfo1.pt1.x = obj->_ox; + movInfo1.pt1.y = obj->_oy; + + int dx1 = obj->_ox; + int dy1 = obj->_oy; + int dx2, dy2; if (linkInfoSource.node) - v41 = linkInfoSource.node->distance; + movInfo1.distance1 = linkInfoSource.node->_distance; else - v41 = linkInfoSource.link->movGraphNode1->distance; - - movInfo1.distance1 = v41; + movInfo1.distance1 = linkInfoSource.link->_movGraphNode1->_distance; if (linkInfoDest.node) { - v42 = linkInfoDest.node->x; - movInfo1.pt2.x = linkInfoDest.node->x; - v43 = linkInfoDest.node->y; - movInfo1.pt2.y = linkInfoDest.node->y; - movInfo1.distance2 = linkInfoDest.node->distance; + dx2 = linkInfoDest.node->_x; + dy2 = linkInfoDest.node->_y; + + movInfo1.pt2.x = linkInfoDest.node->_x; + movInfo1.pt2.y = linkInfoDest.node->_y; + + movInfo1.distance2 = linkInfoDest.node->_distance; } else { movInfo1.pt2.x = xpos; movInfo1.pt2.y = ypos; - v44 = linkInfoDest.link->movGraphNode1; - v45 = v44->distance; - point.x = (ypos - v44->y) * (ypos - v44->y) + (xpos - v44->x) * (xpos - v44->x); - v46 = sqrt((double)point.x); - point.x = linkInfoDest.link->movGraphNode2->distance - v45; - movInfo1.distance2 = v45 + (unsigned __int64)(signed __int64)(v46 * (double)point / linkInfoDest.link->distance); - MovGraph_calcDistance((int)this, &movInfo1.pt2, linkInfoDest.link, 1); - v43 = movInfo1.pt2.y; - v42 = movInfo1.pt2.x; - v39 = movInfo1.pt1.y; - v40 = movInfo1.pt1.x; + + MovGraphNode *nod = linkInfoDest.link->_movGraphNode1; + double dst1 = sqrt((double)((ypos - nod->_y) * (ypos - nod->_y) + (xpos - nod->_x) * (xpos - nod->_x))); + int dst = linkInfoDest.link->_movGraphNode2->_distance - nod->_distance; + + movInfo1.distance2 = nod->_distance + (dst1 * (double)dst / linkInfoDest.link->_distance); + + calcDistance(&movInfo1.pt2, linkInfoDest.link, 1); + + dx1 = movInfo1.pt1.x; + dy1 = movInfo1.pt1.y; + dx2 = movInfo1.pt2.x; + dy2 = movInfo1.pt2.y; } if (staticsId) { - v47 = MovGraph2_getItem1IndexByStaticsId(this, ex, staticsId); - } else if (tempLinkList.m_nCount <= 1) { - if (tempLinkList.m_nCount == 1) - LOBYTE(v47) = MovGraph2_sub_456690( - this, - (int)&tempLinkList.m_pNodeHead->data->GameObject.CObject.vmt, - v42 - v40, - v43 - v39); + movInfo1.item1Index = getItemSubIndexByStaticsId(idx, staticsId); + } else if (tempLinkList.size() <= 1) { + if (tempLinkList.size() == 1) + movInfo1.item1Index = getShortSide(tempLinkList[0], dx2 - dx1, dy2 - dy1); else - LOBYTE(v47) = MovGraph2_sub_456690(this, 0, v42 - v40, v43 - v39); + movInfo1.item1Index = getShortSide(0, dx2 - dx1, dy2 - dy1); } else { - LOBYTE(v47) = MovGraph2_sub_456300(this, (int)&tempLinkList, tempLinkList.m_pNodeTail, 0, 0); + movInfo1.item1Index = findLink(&tempLinkList, tempLinkList.back(), 0, 0); } + movInfo1.flags = fuzzyMatch != 0; - movInfo1.item1Index = v47; - if (*((_DWORD *)this->items.CObArray.m_pData[offsetof(MovGraph2, movGraph)] - + 186 * movInfo1.field_0 - + 46 * movInfo1.subIndex - + 3) != (unsigned __int16)v62) { - v48 = movInfo1.flags; - LOBYTE(v48) = LOBYTE(movInfo1.flags) | 2; - movInfo1.flags = v48; + + if (_items[idx]->_subItems[idxsub]._staticsId1 != obj->_statics->_staticsId) + movInfo1.flags |= 2; + + buildMovInfo1SubItems(&movInfo1, &tempLinkList, &linkInfoSource, &linkInfoDest); + + MessageQueue *mq = buildMovInfo1MessageQueue(&movInfo1); + + linkInfoDest.node = findNode(movInfo1.pt2.x, movInfo1.pt2.y, fuzzyMatch); + + if (!linkInfoDest.node) + linkInfoDest.link = findLink1(movInfo1.pt2.x, movInfo1.pt2.y, movInfo1.item1Index, fuzzyMatch); + + if (fuzzyMatch || linkInfoDest.link || linkInfoDest.node) { + if (mq && mq->getCount() > 0 && picAniInfo.movementId) { + ExCommand *ex = mq->getExCommandByIndex(0); + + if (ex && (ex->_messageKind == 1 || ex->_messageKind == 20) + && picAniInfo.movementId == ex->_messageNum + && picAniInfo.someDynamicPhaseIndex == ex->_field_14) { + mq->deleteExCommandByIndex(0, 1); + } else { + ex = new ExCommand(picAniInfo.objectId, 5, ex->_messageNum, obj->_ox, obj->_oy, 0, 1, 0, 0, 0); + ex->_field_14 = -1; + ex->_keyCode = picAniInfo.field_8; + ex->_excFlags |= 2; + mq->addExCommand(ex); + + ex = new ExCommand(picAniInfo.objectId, 22, _items[idx]->_subItems[idxsub]._staticsId1, 0, 0, 0, 1, 0, 0, 0); + + ex->_keyCode = picAniInfo.field_8; + ex->_excFlags |= 3; + mq->addExCommand(ex); + } + } + } else { + if (mq) + delete mq; + mq = 0; } - MovGraph2_buildMovInfo1SubItems(this, (int)&movInfo1, (int)&tempLinkList, (int)&linkInfoSource, (int)&linkInfoDest); - v49 = MovGraph2_buildMovInfo1MessageQueue(this, (int)&movInfo1); - v50 = (MessageQueue *)v49; - v62 = (MessageQueue *)v49; - CObjectFree((void *)movInfo1.items); - v51 = MovGraph2_findNode(this, movInfo1.pt2.x, movInfo1.pt2.y, fuzzyMatch); - linkInfoDest.node = v51; - if (!v51) { - linkInfoDest.link = MovGraph2_findLink1(this, movInfo1.pt2.x, movInfo1.pt2.y, movInfo1.item1Index, fuzzyMatch); - v51 = linkInfoDest.node; + + obj->setPicAniInfo(&picAniInfo); + + return mq; +} + +MovGraphNode *MovGraph2::findNode(int x, int y, int fuzzyMatch) { + for (ObList::iterator i = _nodes.begin(); i != _nodes.end(); ++i) { + assert(((CObject *)*i)->_objtype == kObjTypeMovGraphNode); + + MovGraphNode *node = (MovGraphNode *)*i; + + if (fuzzyMatch) { + if (abs(node->_x - x) < 15 && abs(node->_y - y) < 15) + return node; + } else { + if (node->_x == x && node->_y == y) + return node; + } } - if (fuzzyMatch || (_DWORD)linkInfoDest.link || v51) { - if (v50 && MessageQueue_getCount(v50) > 0 && picAniInfo.movementId) { - v52 = MessageQueue_getExCommandByIndex(v50, 0); - point.x = (int)v52; - if (v52 - && ((v53 = v52->msg.messageKind, v53 == 1) || v53 == 20) - && picAniInfo.movementId == LOWORD(v52->messageNum) - && picAniInfo.someDynamicPhaseIndex == v52->msg.field_14) { - MessageQueue_deleteExCommandByIndex(v50, 0, 1); - } else { - v54 = (ExCommand *)operator new(sizeof(ExCommand)); - v63 = v54; - LOBYTE(v71.state) = 5; - if (v54) - v55 = ExCommand_ctor( - v54, - picAniInfo.objectId, - 5, - *(_DWORD *)(point.x + offsetof(ExCommand, messageNum)), - obj->GameObject.ox, - obj->GameObject.oy, - 0, - 1, - 0, - 0, - 0); - else - v55 = 0; - v55->msg.field_14 = -1; - v55->msg.keyCode = picAniInfo.field_8; - v56 = v55->excFlags | 2; - LOBYTE(v71.state) = 4; - v55->excFlags = v56; - MessageQueue_addExCommand(v50, v55); - v57 = (ExCommand *)operator new(sizeof(ExCommand)); - v63 = v57; - LOBYTE(v71.state) = 6; - if (v57) { - v58 = ExCommand_ctor( - v57, - picAniInfo.objectId, - 22, - *((_DWORD *)this->items.CObArray.m_pData[offsetof(MovGraph2, movGraph)] - + 186 * ex - + 46 * movInfo1.subIndex - + 3), - 0, - 0, - 0, - 1, - 0, - 0, - 0); - v50 = v62; + + return 0; +} + +int MovGraph2::getShortSide(MovGraphLink *lnk, int x, int y) { + warning("STUB: MovGraph2::getShortSide()"); + + return 0; +} + +int MovGraph2::findLink(Common::Array<MovGraphLink *> *linkList, MovGraphLink *lnk, Common::Rect *a3, Common::Point *a4) { + warning("STUB: MovGraphLink *MovGraph2::findLink()"); + + return 0; +} + +MovGraphLink *MovGraph2::findLink1(int x, int y, int idx, int fuzzyMatch) { + Common::Point point; + MovGraphLink *res = 0; + + for (ObList::iterator i = _links.begin(); i != _links.end(); ++i) { + assert(((CObject *)*i)->_objtype == kObjTypeMovGraphLink); + + MovGraphLink *lnk = (MovGraphLink *)*i; + + if (fuzzyMatch) { + point.x = x; + point.y = y; + double dst = calcDistance(&point, lnk, 0); + + if (dst >= 0.0 && dst < 2.0) + return lnk; + } else if (!(lnk->_flags & 0x20000000)) { + if (lnk->_movGraphReact->pointInRegion(x, y)) { + if (abs(lnk->_movGraphNode1->_x - lnk->_movGraphNode2->_x) <= abs(lnk->_movGraphNode1->_y - lnk->_movGraphNode2->_y)) { + if (idx == 2 || idx == 3) + return lnk; + res = lnk; + } else { + if (idx == 1 || !idx) + return lnk; + res = lnk; } - else - { - v58 = 0; + } + } + } + + return res; +} + +MovGraphLink *MovGraph2::findLink2(int x, int y) { + double mindist = 1.0e20; + MovGraphLink *res; + + for (ObList::iterator i = _links.begin(); i != _links.end(); ++i) { + assert(((CObject *)*i)->_objtype == kObjTypeMovGraphLink); + + MovGraphLink *lnk = (MovGraphLink *)*i; + + if (!(lnk->_flags & 0x20000000)) { + double n1x = lnk->_movGraphNode1->_x; + double n1y = lnk->_movGraphNode1->_y; + double n2x = lnk->_movGraphNode2->_x; + double n2y = lnk->_movGraphNode2->_y; + double n1dx = n1x - x; + double n1dy = n1y - y; + double dst1 = sqrt(n1dy * n1dy + n1dx * n1dx); + double coeff1 = ((n1y - n2y) * n1dy + (n2x - n1x) * n1dx) / lnk->_distance / dst1; + double dst3 = coeff1 * dst1; + double dst2 = sqrt(1.0 - coeff1 * coeff1) * dst1; + + if (coeff1 * dst1 < 0.0) { + dst3 = 0.0; + dst2 = sqrt(n1dy * n1dy + n1dx * n1dx); + } + if (dst3 > lnk->_distance) { + dst3 = lnk->_distance; + dst2 = sqrt((n2x - x) * (n2x - x) + (n2y - y) * (n2y - y)); + } + if (dst3 >= 0.0 && dst3 <= lnk->_distance && dst2 < mindist) { + mindist = dst2; + res = lnk; + } + } + } + + if (mindist < 1.0e20) + return res; + else + return 0; +} + +double MovGraph2::findMinPath(LinkInfo *linkInfoSource, LinkInfo *linkInfoDest, Common::Array<MovGraphLink *> *listObj) { + LinkInfo linkInfoWorkSource; + + if (linkInfoSource->link != linkInfoDest->link || linkInfoSource->node != linkInfoDest->node) { + double minDistance = -1.0; + + if (linkInfoSource->node) { + for (ObList::iterator i = _links.begin(); i != _links.end(); ++i) { + MovGraphLink *lnk = (MovGraphLink *)*i; + + if ((lnk->_movGraphNode1 == linkInfoSource->node || lnk->_movGraphNode2 == linkInfoSource->node) && !(lnk->_flags & 0xA0000000)) { + linkInfoWorkSource.node = 0; + linkInfoWorkSource.link = lnk; + + Common::Array<MovGraphLink *> tmpList; + + lnk->_flags |= 0x80000000; + + double newDistance = findMinPath(&linkInfoWorkSource, linkInfoDest, &tmpList); + + if (newDistance >= 0.0 && (minDistance < 0.0 || newDistance + lnk->_distance < minDistance)) { + listObj->clear(); + listObj->push_back(tmpList); + + minDistance = newDistance + lnk->_distance; } - v58->msg.keyCode = picAniInfo.field_8; - v59 = v58->excFlags | 3; - LOBYTE(v71.state) = 4; - v58->excFlags = v59; - MessageQueue_addExCommand(v50, v58); + + lnk->_flags &= 0x7FFFFFFF; + } + } + } else if (linkInfoSource->link) { + linkInfoWorkSource.node = linkInfoSource->link->_movGraphNode1; + linkInfoWorkSource.link = 0; + + Common::Array<MovGraphLink *> tmpList; + + double newDistance = findMinPath(&linkInfoWorkSource, linkInfoDest, &tmpList); + + if (newDistance >= 0.0) { + listObj->clear(); + + listObj->push_back(linkInfoSource->link); + listObj->push_back(tmpList); + + minDistance = newDistance; + } + + linkInfoWorkSource.link = 0; + linkInfoWorkSource.node = linkInfoSource->link->_movGraphNode2; + + tmpList.clear(); + + newDistance = findMinPath(&linkInfoWorkSource, linkInfoDest, &tmpList); + + if (newDistance >= 0 && (minDistance < 0.0 || newDistance < minDistance)) { + listObj->push_back(linkInfoSource->link); + listObj->push_back(tmpList); + + minDistance = newDistance; } } + + return minDistance; } else { - if (v50) - (*(void (__thiscall **)(MessageQueue *, signed int))(v50->CObject.vmt + 4))(v50, 1); - v50 = 0; - } - GameObject_setPicAniInfo(obj, &picAniInfo); - v71.state = -1; - ObList_dtor(&tempLinkList); - return v50; -#endif + if (linkInfoSource->link) + listObj->push_back(linkInfoSource->link); - return 0; + return 0.0; + } } MovGraphNode *MovGraph::calcOffset(int ox, int oy) { @@ -926,6 +1053,8 @@ MovGraphLink::MovGraphLink() { _field_38 = 0; _movGraphReact = 0; _name = 0; + + _objtype = kObjTypeMovGraphLink; } bool MovGraphLink::load(MfcArchive &file) { diff --git a/engines/fullpipe/motion.h b/engines/fullpipe/motion.h index 8754f07ba1..6901a7263a 100644 --- a/engines/fullpipe/motion.h +++ b/engines/fullpipe/motion.h @@ -52,13 +52,13 @@ public: virtual int method28() { return 0; } virtual int method2C() { return 0; } virtual int method30() { return 0; } - virtual MessageQueue *method34(StaticANIObject *subj, int xpos, int ypos, int fuzzySearch, int staticsId) { return 0; } + virtual MessageQueue *method34(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId) { return 0; } virtual int changeCallback() { return 0; } virtual int method3C() { return 0; } virtual int method40() { return 0; } virtual int method44() { return 0; } virtual int method48() { return -1; } - virtual MessageQueue *method4C(StaticANIObject *subj, int xpos, int ypos, int fuzzySearch, int staticsId) { return 0; } + virtual MessageQueue *doWalkTo(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId) { return 0; } }; class MovGraphReact : public CObject { @@ -106,8 +106,8 @@ class MctlCompound : public MotionController { virtual void addObject(StaticANIObject *obj); virtual int removeObject(StaticANIObject *obj); virtual void freeItems(); - virtual MessageQueue *method34(StaticANIObject *subj, int xpos, int ypos, int fuzzySearch, int staticsId); - virtual MessageQueue *method4C(StaticANIObject *subj, int xpos, int ypos, int fuzzySearch, int staticsId); + virtual MessageQueue *method34(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId); + virtual MessageQueue *doWalkTo(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId); void initMovGraph2(); MctlConnectionPoint *findClosestConnectionPoint(int ox, int oy, int destIndex, int connectionX, int connectionY, int sourceIndex, int *minDistancePtr); @@ -147,15 +147,15 @@ public: }; class MovGraphNode : public CObject { - public: +public: int _x; int _y; int _distance; int16 _field_10; int _field_14; - public: - MovGraphNode() : _x(0), _y(0), _distance(0), _field_10(0), _field_14(0) {} +public: + MovGraphNode() : _x(0), _y(0), _distance(0), _field_10(0), _field_14(0) { _objtype = kObjTypeMovGraphNode; } virtual bool load(MfcArchive &file); }; @@ -247,14 +247,14 @@ class MovGraph : public MotionController { virtual void freeItems(); virtual int method28(); virtual int method2C(); - virtual MessageQueue *method34(StaticANIObject *subj, int xpos, int ypos, int fuzzySearch, int staticsId); + virtual MessageQueue *method34(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId); virtual int changeCallback(); virtual int method3C(); virtual int method44(); - virtual MessageQueue *method4C(StaticANIObject *subj, int xpos, int ypos, int fuzzySearch, int staticsId); + virtual MessageQueue *doWalkTo(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId); virtual int method50(); - double calcDistance(Common::Point *point, MovGraphLink *link, int fuzzySearch); + double calcDistance(Common::Point *point, MovGraphLink *link, int fuzzyMatch); MovGraphNode *calcOffset(int ox, int oy); }; @@ -275,6 +275,24 @@ struct MovGraph2ItemSub { MG2I _turnS[4]; }; +struct LinkInfo { + MovGraphLink *link; + MovGraphNode *node; +}; + +struct MovInfo1 { + int field_0; + Common::Point pt1; + Common::Point pt2; + int distance1; + int distance2; + int subIndex; + int item1Index; + int items; + int itemsCount; + int flags; +}; + struct MovGraph2Item { int _objectId; StaticANIObject *_obj; @@ -289,11 +307,25 @@ public: virtual void addObject(StaticANIObject *obj); virtual int removeObject(StaticANIObject *obj); virtual void freeItems(); - virtual MessageQueue *method34(StaticANIObject *subj, int xpos, int ypos, int fuzzySearch, int staticsId); - virtual MessageQueue *method4C(StaticANIObject *subj, int xpos, int ypos, int fuzzySearch, int staticsId); + virtual MessageQueue *method34(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId); + virtual MessageQueue *doWalkTo(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId); int getItemIndexByGameObjectId(int objectId); + int getItemSubIndexByStaticsId(int index, int staticsId); + int getItemSubIndexByMovementId(int index, int movId); + int getItemSubIndexByMGM(int idx, StaticANIObject *ani); + + int getShortSide(MovGraphLink *lnk, int x, int y); + int findLink(Common::Array<MovGraphLink *> *linkList, MovGraphLink *lnk, Common::Rect *a3, Common::Point *a4); + bool initDirections(StaticANIObject *obj, MovGraph2Item *item); + void buildMovInfo1SubItems(MovInfo1 *movinfo, Common::Array<MovGraphLink *> *linkList, LinkInfo *lnkSrc, LinkInfo *lnkDst); + MessageQueue *buildMovInfo1MessageQueue(MovInfo1 *movInfo); + + MovGraphNode *findNode(int x, int y, int fuzzyMatch); + MovGraphLink *findLink1(int x, int y, int idx, int fuzzyMatch); + MovGraphLink *findLink2(int x, int y); + double findMinPath(LinkInfo *linkInfoSource, LinkInfo *linkInfoDest, Common::Array<MovGraphLink *> *listObj); }; class MctlConnectionPoint : public CObject { diff --git a/engines/fullpipe/utils.h b/engines/fullpipe/utils.h index e593bd9f18..64f56ced0a 100644 --- a/engines/fullpipe/utils.h +++ b/engines/fullpipe/utils.h @@ -66,15 +66,17 @@ class MfcArchive : public Common::SeekableReadStream { enum ObjType { kObjTypeDefault, + kObjTypeMovGraph, + kObjTypeMovGraphLink, + kObjTypeMovGraphNode, + kObjTypeMctlCompound, kObjTypeObjstateCommand, - kObjTypeStaticANIObject, kObjTypePictureObject, - kObjTypeMovGraph, - kObjTypeMctlCompound + kObjTypeStaticANIObject }; class CObject { - public: +public: ObjType _objtype; CObject() : _objtype(kObjTypeDefault) {} diff --git a/engines/lastexpress/game/inventory.cpp b/engines/lastexpress/game/inventory.cpp index 8edef90da3..2f1b0a8e76 100644 --- a/engines/lastexpress/game/inventory.cpp +++ b/engines/lastexpress/game/inventory.cpp @@ -44,7 +44,7 @@ namespace LastExpress { Inventory::Inventory(LastExpressEngine *engine) : _engine(engine), _selectedItem(kItemNone), _highlightedItemIndex(0), _itemsShown(0), - /*_showingHourGlass(false), */ _blinkingDirection(1), _blinkingBrightness(0), + _showingHourGlass(false), _blinkingDirection(1), _blinkingBrightness(0), _useMagnifier(false), _portraitHighlighted(false), _isOpened(false), _eggHightlighted(false), _itemScene(NULL) { //_inventoryRect = Common::Rect(0, 0, 32, 32); @@ -52,6 +52,8 @@ Inventory::Inventory(LastExpressEngine *engine) : _engine(engine), _selectedItem _selectedItemRect = Common::Rect(44, 0, 76, 32); init(); + + debug(9, "_showingHourGlass: %d", _showingHourGlass); } Inventory::~Inventory() { diff --git a/engines/lastexpress/game/inventory.h b/engines/lastexpress/game/inventory.h index 9b82ef031d..b1019a43c6 100644 --- a/engines/lastexpress/game/inventory.h +++ b/engines/lastexpress/game/inventory.h @@ -140,7 +140,7 @@ private: uint32 _itemsShown; - //bool _showingHourGlass; + bool _showingHourGlass; int16 _blinkingDirection; uint16 _blinkingBrightness; diff --git a/engines/neverhood/resourceman.cpp b/engines/neverhood/resourceman.cpp index a53bcc8393..b7d560bbb3 100644 --- a/engines/neverhood/resourceman.cpp +++ b/engines/neverhood/resourceman.cpp @@ -105,6 +105,8 @@ static const EntrySizeFix entrySizeFixes[] = { { 0x80283101, 13104841, 1961, 3712, 3511 }, // The first message from Willie { 0x00918480, 17676417, 581, 916, 706 }, // The first wall in the museum { 0x00800090C,16064875, 19555, 38518, 38526 }, // The first wall in the museum + { 0x058208810,46010519, 24852, 131874, 131776}, // The entry to hut with musical lock + // Fixes for the Russian "Fargus" version { 0x41137051, 758264, 29037, 49590, 49591 }, // "Options" menu header text { 0xc10b2015, 787304, 4414, 15848, 15853 }, // Text on option buttons diff --git a/engines/plugins_table.h b/engines/plugins_table.h index 85283b2e8a..de2a8069de 100644 --- a/engines/plugins_table.h +++ b/engines/plugins_table.h @@ -8,6 +8,9 @@ LINK_PLUGIN(AGI) #if PLUGIN_ENABLED_STATIC(AGOS) LINK_PLUGIN(AGOS) #endif +#if PLUGIN_ENABLED_STATIC(AVALANCHE) +LINK_PLUGIN(AVALANCHE) +#endif #if PLUGIN_ENABLED_STATIC(CGE) LINK_PLUGIN(CGE) #endif @@ -125,3 +128,6 @@ LINK_PLUGIN(WINTERMUTE) #if PLUGIN_ENABLED_STATIC(PRINCE) LINK_PLUGIN(PRINCE) #endif +#if PLUGIN_ENABLED_STATIC(ZVISION) +LINK_PLUGIN(ZVISION) +#endif diff --git a/engines/testbed/config-params.cpp b/engines/testbed/config-params.cpp index e89da0b07f..47e5dfa933 100644 --- a/engines/testbed/config-params.cpp +++ b/engines/testbed/config-params.cpp @@ -38,6 +38,8 @@ ConfigParams::ConfigParams() { _isInteractive = true; _isGameDataFound = true; _rerunTests = false; + + _testbedConfMan = 0; } void ConfigParams::initLogging(const char *dirname, const char *filename, bool enable) { diff --git a/engines/testbed/config.h b/engines/testbed/config.h index d611ae4ec3..7d479a74fd 100644 --- a/engines/testbed/config.h +++ b/engines/testbed/config.h @@ -113,7 +113,7 @@ private: class TestbedInteractionDialog : public GUI::Dialog { public: - TestbedInteractionDialog(uint x, uint y, uint w, uint h) : GUI::Dialog(x, y, w, h) {} + TestbedInteractionDialog(uint x, uint y, uint w, uint h) : GUI::Dialog(x, y, w, h), _xOffset(0), _yOffset(0) {} ~TestbedInteractionDialog() {} virtual void handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data); void addButton(uint w, uint h, const Common::String name, uint32 cmd, uint xOffset = 0, uint yPadding = 8); diff --git a/engines/testbed/events.cpp b/engines/testbed/events.cpp index 78de87e133..4b9ced2a53 100644 --- a/engines/testbed/events.cpp +++ b/engines/testbed/events.cpp @@ -83,11 +83,10 @@ struct keycodeToChar { char EventTests::keystrokeToChar() { Common::EventManager *eventMan = g_system->getEventManager(); - bool quitLoop = false; Common::Event event; // handle all keybd events - while (!quitLoop) { + while (true) { while (eventMan->pollEvent(event)) { // Quit if explicitly requested! if (Engine::shouldQuit()) { @@ -110,8 +109,6 @@ char EventTests::keystrokeToChar() { } } } - - return 0; } Common::Rect EventTests::drawFinishZone() { diff --git a/engines/tony/gfxcore.cpp b/engines/tony/gfxcore.cpp index 410f9b8971..3433ad3024 100644 --- a/engines/tony/gfxcore.cpp +++ b/engines/tony/gfxcore.cpp @@ -117,6 +117,7 @@ RMGfxBuffer::operator void *() { } RMGfxBuffer::RMGfxBuffer(int dimx, int dimy, int nBpp) { + _origBuf = _buf = NULL; create(dimx, dimy, nBpp); } diff --git a/engines/touche/touche.cpp b/engines/touche/touche.cpp index 5c133ccbc6..89cc1ad5af 100644 --- a/engines/touche/touche.cpp +++ b/engines/touche/touche.cpp @@ -51,6 +51,28 @@ ToucheEngine::ToucheEngine(OSystem *system, Common::Language language) _saveLoadCurrentSlot = 0; _hideInventoryTexts = false; + _numOpcodes = 0; + _compressedSpeechData = 0; + _textData = 0; + _backdropBuffer = 0; + _menuKitData = 0; + _convKitData = 0; + + for (int i = 0; i < NUM_SEQUENCES; i++) + _sequenceDataTable[i] = 0; + + _programData = 0; + _programDataSize = 0; + _mouseData = 0; + _iconData = 0; + _currentBitmapWidth = 0; + _currentBitmapHeight = 0; + _currentImageWidth = 0; + _currentImageHeight = 0; + _roomWidth = 0; + _programTextDataPtr = 0; + _offscreenBuffer = 0; + _screenRect = Common::Rect(kScreenWidth, kScreenHeight); _roomAreaRect = Common::Rect(kScreenWidth, kRoomHeight); diff --git a/engines/tsage/core.cpp b/engines/tsage/core.cpp index 488eacceab..3332b12cf6 100644 --- a/engines/tsage/core.cpp +++ b/engines/tsage/core.cpp @@ -1749,9 +1749,22 @@ void SceneItem::display(int resNum, int lineNum, ...) { } g_globals->_sceneText.fixPriority(255); + + // In Return to Ringworld, if in voice mode only, don't show the text + if ((g_vm->getGameID() == GType_Ringworld2) && (R2_GLOBALS._speechSubtitles & SPEECH_VOICE) + && !(R2_GLOBALS._speechSubtitles & SPEECH_TEXT)) + g_globals->_sceneText.hide(); + g_globals->_sceneObjects->draw(); } + // For Return to Ringworld, play the voice overs in sequence + if ((g_vm->getGameID() == GType_Ringworld2) && (R2_GLOBALS._speechSubtitles & SPEECH_VOICE) + && !playList.empty()) { + R2_GLOBALS._playStream.play(*playList.begin(), NULL); + playList.pop_front(); + } + // Unless the flag is set to keep the message on-screen, show it until a mouse or keypress, then remove it if (!keepOnscreen && !msg.empty()) { Event event; @@ -1761,14 +1774,23 @@ void SceneItem::display(int resNum, int lineNum, ...) { EVENT_BUTTON_DOWN | EVENT_KEYPRESS)) { GLOBALS._screenSurface.updateScreen(); g_system->delayMillis(10); - } - // For Return to Ringworld, play the voice overs in sequence - if ((g_vm->getGameID() == GType_Ringworld2) && !playList.empty() && !R2_GLOBALS._playStream.isPlaying()) { - R2_GLOBALS._playStream.play(*playList.begin(), NULL); - playList.pop_front(); + if ((g_vm->getGameID() == GType_Ringworld2) && (R2_GLOBALS._speechSubtitles & SPEECH_VOICE)) { + // Allow the play stream to do processing + R2_GLOBALS._playStream.dispatch(); + if (!R2_GLOBALS._playStream.isPlaying()) { + // Check if there are further voice samples to play + if (!playList.empty()) { + R2_GLOBALS._playStream.play(*playList.begin(), NULL); + playList.pop_front(); + } + } + } } + if (g_vm->getGameID() == GType_Ringworld2) + R2_GLOBALS._playStream.stop(); + g_globals->_sceneText.remove(); } @@ -2814,6 +2836,11 @@ void BackgroundSceneObject::setup2(int visage, int stripFrameNum, int frameNum, void BackgroundSceneObject::copySceneToBackground() { GLOBALS._sceneManager._scene->_backSurface.copyFrom(g_globals->gfxManager().getSurface(), 0, 0); + + // WORKAROUND: Since savegames don't store the active screen data, once we copy the + // foreground objects to the background, we have to prevent the scene being saved. + if (g_vm->getGameID() == GType_Ringworld2) + ((Ringworld2::SceneExt *)GLOBALS._sceneManager._scene)->_preventSaving = true; } /*--------------------------------------------------------------------------*/ diff --git a/engines/tsage/ringworld2/ringworld2_logic.cpp b/engines/tsage/ringworld2/ringworld2_logic.cpp index fc06b204cd..90df72ab32 100644 --- a/engines/tsage/ringworld2/ringworld2_logic.cpp +++ b/engines/tsage/ringworld2/ringworld2_logic.cpp @@ -321,9 +321,11 @@ bool Ringworld2Game::canLoadGameStateCurrently() { * Returns true if it is currently okay to save the game */ bool Ringworld2Game::canSaveGameStateCurrently() { - // Don't allow a game to be saved if a dialog is active or if an animation - // is playing - return g_globals->_gfxManagers.size() == 1 && R2_GLOBALS._animationCtr == 0; + // Don't allow a game to be saved if a dialog is active, or if an animation + // is playing, or if an active scene prevents it + return g_globals->_gfxManagers.size() == 1 && R2_GLOBALS._animationCtr == 0 && + (!R2_GLOBALS._sceneManager._scene || + !((SceneExt *)R2_GLOBALS._sceneManager._scene)->_preventSaving); } /*--------------------------------------------------------------------------*/ @@ -338,6 +340,7 @@ SceneExt::SceneExt(): Scene() { _savedPlayerEnabled = false; _savedUiEnabled = false; _savedCanWalk = false; + _preventSaving = false; // WORKAROUND: In the original, playing animations don't reset the global _animationCtr // counter as scene changes unless the playing animation explicitly finishes. For now, @@ -593,6 +596,9 @@ void SceneExt::loadBlankScene() { void SceneHandlerExt::postInit(SceneObjectList *OwnerList) { SceneHandler::postInit(OwnerList); + + if (!R2_GLOBALS._playStream.setFile("SND4K.RES")) + warning("Could not find SND4K.RES voice file"); } void SceneHandlerExt::process(Event &event) { diff --git a/engines/tsage/ringworld2/ringworld2_logic.h b/engines/tsage/ringworld2/ringworld2_logic.h index aeac2fdd6a..5c8af8d884 100644 --- a/engines/tsage/ringworld2/ringworld2_logic.h +++ b/engines/tsage/ringworld2/ringworld2_logic.h @@ -85,6 +85,7 @@ public: bool _savedPlayerEnabled; bool _savedUiEnabled; bool _savedCanWalk; + bool _preventSaving; Visage _cursorVisage; SynchronizedList<EventHandler *> _sceneAreas; diff --git a/engines/tsage/ringworld2/ringworld2_scenes0.cpp b/engines/tsage/ringworld2/ringworld2_scenes0.cpp index bdac160163..5e4b4e4191 100644 --- a/engines/tsage/ringworld2/ringworld2_scenes0.cpp +++ b/engines/tsage/ringworld2/ringworld2_scenes0.cpp @@ -836,7 +836,7 @@ void Scene125::process(Event &event) { void Scene125::dispatch() { if (_soundCount) - R2_GLOBALS._playStream.proc1(); + R2_GLOBALS._playStream.dispatch(); Scene::dispatch(); } diff --git a/engines/tsage/ringworld2/ringworld2_scenes1.cpp b/engines/tsage/ringworld2/ringworld2_scenes1.cpp index c99550978e..0932c70f04 100644 --- a/engines/tsage/ringworld2/ringworld2_scenes1.cpp +++ b/engines/tsage/ringworld2/ringworld2_scenes1.cpp @@ -1097,7 +1097,6 @@ void Scene1100::signal() { // Really nothing break; case 13: - _leftImpacts.setup(1113, 2, 1); _trooper.postInit(); R2_GLOBALS._scrollFollower = &_trooper; @@ -1276,6 +1275,15 @@ void Scene1100::signal() { } void Scene1100::dispatch() { + // WORKAROUND: This fixes a problem with an overhang that gets blasted re-appearing + if (_animation._frame > 5 && _sceneMode == 13) { + _animation._endFrame = 9; + if (_animation._frame == 9) + // Use one of the scene's background scene objects to copy the scene to the background. + // This fixes the problem with the cliff overhang still appearing during the cutscene + _rightLandslide.copySceneToBackground(); + } + if ((g_globals->_sceneObjects->contains(&_laserShot)) && (_laserShot._visage == 1102) && (_laserShot._strip == 4) && (_laserShot._frame == 1) && (_laserShot._flags & OBJFLAG_HIDING)) { if (_paletteRefreshStatus == 1) { _paletteRefreshStatus = 2; diff --git a/engines/tsage/ringworld2/ringworld2_scenes1.h b/engines/tsage/ringworld2/ringworld2_scenes1.h index 956328aea9..c0088236b4 100644 --- a/engines/tsage/ringworld2/ringworld2_scenes1.h +++ b/engines/tsage/ringworld2/ringworld2_scenes1.h @@ -111,7 +111,7 @@ public: SceneActor _shotImpact4; SceneActor _shotImpact5; SceneActor _laserShot; - SceneActor _animation; + SceneActor _animation; // Used for cliff collapse and ship theft SceneActor _leftImpacts; SceneActor _runningGuy1; SceneActor _runningGuy2; diff --git a/engines/tsage/sound.cpp b/engines/tsage/sound.cpp index b9567cece2..02abc58178 100644 --- a/engines/tsage/sound.cpp +++ b/engines/tsage/sound.cpp @@ -22,6 +22,8 @@ #include "audio/decoders/raw.h" #include "common/config-manager.h" +#include "audio/decoders/raw.h" +#include "audio/audiostream.h" #include "tsage/core.h" #include "tsage/globals.h" #include "tsage/debugger.h" @@ -2505,6 +2507,168 @@ void ASoundExt::changeSound(int soundNum) { /*--------------------------------------------------------------------------*/ +void PlayStream::ResFileData::load(Common::SeekableReadStream &stream) { + // Validate that it's the correct data file + char header[4]; + stream.read(&header[0], 4); + if (strncmp(header, "SPAM", 4)) + error("Invalid voice resource data"); + + _fileChunkSize = stream.readUint32LE(); + stream.skip(2); + _indexSize = stream.readUint16LE(); + _chunkSize = stream.readUint16LE(); + + stream.skip(18); +} + +PlayStream::PlayStream(): EventHandler() { + _index = NULL; + _endAction = NULL; +} + +PlayStream::~PlayStream() { + remove(); +} + +bool PlayStream::setFile(const Common::String &filename) { + remove(); + + // Open the resource file for access + if (!_file.open(filename)) + return false; + + // Load header + _resData.load(_file); + + // Load the index + _index = new uint16[_resData._indexSize / 2]; + for (uint i = 0; i < (_resData._indexSize / 2); ++i) + _index[i] = _file.readUint16LE(); + + return true; +} + +bool PlayStream::play(int voiceNum, EventHandler *endAction) { + uint32 offset = getFileOffset(_index, _resData._fileChunkSize, voiceNum); + if (offset) { + _voiceNum = 0; + if (_sound.isPlaying()) + _sound.stop(); + + // Move to the offset for the start of the voice + _file.seek(offset); + + // Read in the header and validate it + char header[4]; + _file.read(&header[0], 4); + if (strncmp(header, "FEED", 4)) + error("Invalid stream data"); + + // Get basic details of first sound chunk + uint chunkSize = _file.readUint16LE() - 16; + _file.skip(4); + int rate = _file.readUint16LE(); + _file.skip(4); + + // Create the stream + Audio::QueuingAudioStream *audioStream = Audio::makeQueuingAudioStream(rate, false); + + // Load in the first chunk + byte *data = (byte *)malloc(chunkSize); + _file.read(data, chunkSize); + audioStream->queueBuffer(data, chunkSize, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED); + + // If necessary, load further chunks of the voice in + while (chunkSize == (_resData._chunkSize - 16)) { + // Ensure the next chunk has the 'MORE' header + _file.read(&header[0], 4); + if (strncmp(header, "MORE", 4)) + error("Invalid stream data"); + + // Get the size of the chunk + chunkSize = _file.readUint16LE() - 16; + _file.skip(10); + + // Read in the data for this next chunk and queue it + data = (byte *)malloc(chunkSize); + _file.read(data, chunkSize); + audioStream->queueBuffer(data, chunkSize, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED); + } + + g_vm->_mixer->playStream(Audio::Mixer::kSpeechSoundType, &_soundHandle, + audioStream, DisposeAfterUse::YES); + + return true; + } + + // If it reaches this point, no valid voice data found + return false; +} + +void PlayStream::stop() { + g_vm->_mixer->stopHandle(_soundHandle); + + _voiceNum = 0; + _endAction = NULL; +} + +bool PlayStream::isPlaying() const { + return _voiceNum != 0 && g_vm->_mixer->isSoundHandleActive(_soundHandle); +} + +void PlayStream::remove() { + stop(); + _file.close(); + + // Free index + delete[] _index; + _index = NULL; + + _endAction = NULL; + _voiceNum = 0; +} + +void PlayStream::dispatch() { + if (_voiceNum != 0 && !isPlaying()) { + // If voice has finished playing, reset fields + EventHandler *endAction = _endAction; + _endAction = NULL; + _voiceNum = 0; + + if (endAction) + // Signal given end action + endAction->signal(); + } +} + +uint32 PlayStream::getFileOffset(const uint16 *data, int count, int voiceNum) { + assert(data); + int bitsIndex = voiceNum & 7; + int byteIndex = voiceNum >> 3; + int shiftAmount = bitsIndex * 2; + int bitMask = 3 << shiftAmount; + int v = (data[byteIndex] & bitMask) >> shiftAmount; + uint32 offset = 0; + + if (!v) + return 0; + + // Loop to figure out offsets from index words skipped over + for (int i = 0; i < (voiceNum >> 3); ++i) { + for (int bit = 0; bit < 16; bit += 2) + offset += ((data[i] >> bit) & 3) * count; + } + + // Bit index loop + for (int i = 0; i < bitsIndex; ++i) + offset += ((data[byteIndex] >> (i * 2)) & 3) * count; + + return offset; +} + +/*--------------------------------------------------------------------------*/ + SoundDriver::SoundDriver() { _driverResID = 0; _minVersion = _maxVersion = 0; diff --git a/engines/tsage/sound.h b/engines/tsage/sound.h index 2f59afb49b..83cd4753d5 100644 --- a/engines/tsage/sound.h +++ b/engines/tsage/sound.h @@ -259,6 +259,7 @@ public: class Sound: public EventHandler { private: void _prime(int soundResID, bool dontQueue); + void _primeBuffer(const byte *soundData); void _unPrime(); public: bool _stoppedAsynchronously; @@ -414,15 +415,36 @@ public: virtual void signal(); }; -class PlayStream { -public: +class PlayStream: public EventHandler { + class ResFileData { + public: + int _fileChunkSize; + uint _indexSize; + uint _chunkSize; + + void load(Common::SeekableReadStream &stream); + }; +private: + Common::File _file; + ResFileData _resData; + Audio::SoundHandle _soundHandle; Sound _sound; + uint16 *_index; + EventHandler *_endAction; + int _voiceNum; - void setFile(const Common::String &filename) {} - bool play(int soundNum, EventHandler *endAction) { return false; } - void stop() {} - void proc1() {} - bool isPlaying() const { return false; } + static uint32 getFileOffset(const uint16 *data, int count, int voiceNum); +public: + PlayStream(); + virtual ~PlayStream(); + + bool setFile(const Common::String &filename); + bool play(int voiceNum, EventHandler *endAction); + void stop(); + bool isPlaying() const; + + virtual void remove(); + virtual void dispatch(); }; #define ADLIB_CHANNEL_COUNT 9 diff --git a/engines/wintermute/video/video_theora_player.h b/engines/wintermute/video/video_theora_player.h index ddeba48bbc..a4f1b9edd6 100644 --- a/engines/wintermute/video/video_theora_player.h +++ b/engines/wintermute/video/video_theora_player.h @@ -90,7 +90,7 @@ public: BaseImage *_alphaImage; Common::String _alphaFilename; bool setAlphaImage(const Common::String &filename); - __inline byte getAlphaAt(int x, int y) const; + byte getAlphaAt(int x, int y) const; void writeAlpha(); bool seekToTime(uint32 Time); diff --git a/engines/zvision/actions.cpp b/engines/zvision/actions.cpp new file mode 100644 index 0000000000..eae4ec2ed5 --- /dev/null +++ b/engines/zvision/actions.cpp @@ -0,0 +1,394 @@ +/* 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 "common/scummsys.h" + +#include "zvision/actions.h" + +#include "zvision/zvision.h" +#include "zvision/script_manager.h" +#include "zvision/render_manager.h" +#include "zvision/zork_raw.h" +#include "zvision/zork_avi_decoder.h" +#include "zvision/timer_node.h" +#include "zvision/animation_control.h" + +#include "common/file.h" + +#include "audio/decoders/wave.h" + + +namespace ZVision { + +////////////////////////////////////////////////////////////////////////////// +// ActionAdd +////////////////////////////////////////////////////////////////////////////// + +ActionAdd::ActionAdd(const Common::String &line) { + sscanf(line.c_str(), "%*[^(](%u,%u)", &_key, &_value); +} + +bool ActionAdd::execute(ZVision *engine) { + engine->getScriptManager()->addToStateValue(_key, _value); + return true; +} + + +////////////////////////////////////////////////////////////////////////////// +// ActionAssign +////////////////////////////////////////////////////////////////////////////// + +ActionAssign::ActionAssign(const Common::String &line) { + sscanf(line.c_str(), "%*[^(](%u, %u)", &_key, &_value); +} + +bool ActionAssign::execute(ZVision *engine) { + engine->getScriptManager()->setStateValue(_key, _value); + return true; +} + + +////////////////////////////////////////////////////////////////////////////// +// ActionAttenuate +////////////////////////////////////////////////////////////////////////////// + +ActionAttenuate::ActionAttenuate(const Common::String &line) { + sscanf(line.c_str(), "%*[^(](%u, %d)", &_key, &_attenuation); +} + +bool ActionAttenuate::execute(ZVision *engine) { + // TODO: Implement + return true; +} + + +////////////////////////////////////////////////////////////////////////////// +// ActionChangeLocation +////////////////////////////////////////////////////////////////////////////// + +ActionChangeLocation::ActionChangeLocation(const Common::String &line) { + sscanf(line.c_str(), "%*[^(](%c,%c,%c%c,%u)", &_world, &_room, &_node, &_view, &_offset); +} + +bool ActionChangeLocation::execute(ZVision *engine) { + // We can't directly call ScriptManager::ChangeLocationIntern() because doing so clears all the Puzzles, and thus would corrupt the current puzzle checking + engine->getScriptManager()->changeLocation(_world, _room, _node, _view, _offset); + // Tell the puzzle system to stop checking any more puzzles + return false; +} + + +////////////////////////////////////////////////////////////////////////////// +// ActionCrossfade +////////////////////////////////////////////////////////////////////////////// + +ActionCrossfade::ActionCrossfade(const Common::String &line) { + sscanf(line.c_str(), + "%*[^(](%u %u %u %u %u %u %u)", + &_keyOne, &_keyTwo, &_oneStartVolume, &_twoStartVolume, &_oneEndVolume, &_twoEndVolume, &_timeInMillis); +} + +bool ActionCrossfade::execute(ZVision *engine) { + // TODO: Implement + return true; +} + + +////////////////////////////////////////////////////////////////////////////// +// ActionDisableControl +////////////////////////////////////////////////////////////////////////////// + +ActionDisableControl::ActionDisableControl(const Common::String &line) { + sscanf(line.c_str(), "%*[^(](%u)", &_key); +} + +bool ActionDisableControl::execute(ZVision *engine) { + debug("Disabling control %u", _key); + + engine->getScriptManager()->disableControl(_key); + + return true; +} + + +////////////////////////////////////////////////////////////////////////////// +// ActionEnableControl +////////////////////////////////////////////////////////////////////////////// + +ActionEnableControl::ActionEnableControl(const Common::String &line) { + sscanf(line.c_str(), "%*[^(](%u)", &_key); +} + +bool ActionEnableControl::execute(ZVision *engine) { + debug("Enabling control %u", _key); + + engine->getScriptManager()->enableControl(_key); + + return true; +} + + +////////////////////////////////////////////////////////////////////////////// +// ActionMusic +////////////////////////////////////////////////////////////////////////////// + +ActionMusic::ActionMusic(const Common::String &line) : _volume(255) { + uint type; + char fileNameBuffer[25]; + uint loop; + uint volume = 255; + + sscanf(line.c_str(), "%*[^:]:%*[^:]:%u(%u %25s %u %u)", &_key, &type, fileNameBuffer, &loop, &volume); + + // type 4 are midi sound effect files + if (type == 4) { + _soundType = Audio::Mixer::kSFXSoundType; + _fileName = Common::String::format("midi/%s/%u.wav", fileNameBuffer, loop); + _loop = false; + } else { + // TODO: See what the other types are so we can specify the correct Mixer::SoundType. In the meantime use kPlainSoundType + _soundType = Audio::Mixer::kPlainSoundType; + _fileName = Common::String(fileNameBuffer); + _loop = loop == 1 ? true : false; + } + + // Volume is optional. If it doesn't appear, assume full volume + if (volume != 255) { + // Volume in the script files is mapped to [0, 100], but the ScummVM mixer uses [0, 255] + _volume = volume * 255 / 100; + } +} + +bool ActionMusic::execute(ZVision *engine) { + Audio::RewindableAudioStream *audioStream; + + if (_fileName.contains(".wav")) { + Common::File *file = new Common::File(); + if (file->open(_fileName)) { + audioStream = Audio::makeWAVStream(file, DisposeAfterUse::YES); + } + } else { + audioStream = makeRawZorkStream(_fileName, engine); + } + + if (_loop) { + Audio::LoopingAudioStream *loopingAudioStream = new Audio::LoopingAudioStream(audioStream, 0, DisposeAfterUse::YES); + engine->_mixer->playStream(_soundType, 0, loopingAudioStream, -1, _volume); + } else { + engine->_mixer->playStream(_soundType, 0, audioStream, -1, _volume); + } + + return true; +} + + +////////////////////////////////////////////////////////////////////////////// +// ActionPreloadAnimation +////////////////////////////////////////////////////////////////////////////// + +ActionPreloadAnimation::ActionPreloadAnimation(const Common::String &line) { + char fileName[25]; + + // The two %*u are always 0 and dont seem to have a use + sscanf(line.c_str(), "%*[^:]:%*[^:]:%u(%25s %*u %*u %u %u)", &_key, fileName, &_mask, &_framerate); + + _fileName = Common::String(fileName); +} + +bool ActionPreloadAnimation::execute(ZVision *engine) { + // TODO: We ignore the mask and framerate atm. Mask refers to a key color used for binary alpha. We assume the framerate is the default framerate embedded in the videos + + // TODO: Check if the Control already exists + + // Create the control, but disable it until PlayPreload is called + engine->getScriptManager()->addControl(new AnimationControl(engine, _key, _fileName)); + engine->getScriptManager()->disableControl(_key); + return true; +} + + +////////////////////////////////////////////////////////////////////////////// +// ActionPlayAnimation +////////////////////////////////////////////////////////////////////////////// + +ActionPlayAnimation::ActionPlayAnimation(const Common::String &line) { + char fileName[25]; + + // The two %*u are always 0 and dont seem to have a use + sscanf(line.c_str(), + "%*[^:]:%*[^:]:%u(%25s %u %u %u %u %u %u %u %*u %*u %u %u)", + &_key, fileName, &_x, &_y, &_width, &_height, &_start, &_end, &_loopCount, &_mask, &_framerate); + + _fileName = Common::String(fileName); +} + +bool ActionPlayAnimation::execute(ZVision *engine) { + // TODO: Implement + return true; +} + + +////////////////////////////////////////////////////////////////////////////// +// ActionPlayPreloadAnimation +////////////////////////////////////////////////////////////////////////////// + +ActionPlayPreloadAnimation::ActionPlayPreloadAnimation(const Common::String &line) { + sscanf(line.c_str(), + "%*[^:]:%*[^:]:%u(%u %u %u %u %u %u %u %u)", + &_animationKey, &_controlKey, &_x1, &_y1, &_x2, &_y2, &_startFrame, &_endFrame, &_loopCount); +} + +bool ActionPlayPreloadAnimation::execute(ZVision *engine) { + // Find the control + AnimationControl *control = (AnimationControl *)engine->getScriptManager()->getControl(_controlKey); + + // Set the needed values within the control + control->setAnimationKey(_animationKey); + control->setLoopCount(_loopCount); + control->setXPos(_x1); + control->setYPost(_y1); + + // Enable the control. ScriptManager will take care of the rest + control->enable(); + + return true; +} + + +////////////////////////////////////////////////////////////////////////////// +// ActionQuit +////////////////////////////////////////////////////////////////////////////// + +bool ActionQuit::execute(ZVision *engine) { + engine->quitGame(); + + return true; +} + + +////////////////////////////////////////////////////////////////////////////// +// ActionRandom +////////////////////////////////////////////////////////////////////////////// + +ActionRandom::ActionRandom(const Common::String &line) { + sscanf(line.c_str(), "%*[^:]:%*[^:]:%u, %u)", &_key, &_max); +} + +bool ActionRandom::execute(ZVision *engine) { + uint randNumber = engine->getRandomSource()->getRandomNumber(_max); + engine->getScriptManager()->setStateValue(_key, randNumber); + return true; +} + + +////////////////////////////////////////////////////////////////////////////// +// ActionSetPartialScreen +////////////////////////////////////////////////////////////////////////////// + +ActionSetPartialScreen::ActionSetPartialScreen(const Common::String &line) { + char fileName[25]; + uint color; + + sscanf(line.c_str(), "%*[^(](%u %u %25s %*u %u)", &_x, &_y, fileName, &color); + + _fileName = Common::String(fileName); + + if (color > 0xFFFF) { + warning("Background color for ActionSetPartialScreen is bigger than a uint16"); + } + _backgroundColor = color; +} + +bool ActionSetPartialScreen::execute(ZVision *engine) { + RenderManager *renderManager = engine->getRenderManager(); + + if (_backgroundColor > 0) { + renderManager->clearWorkingWindowTo555Color(_backgroundColor); + } + renderManager->renderImageToScreen(_fileName, _x, _y); + + return true; +} + + +////////////////////////////////////////////////////////////////////////////// +// ActionSetScreen +////////////////////////////////////////////////////////////////////////////// + +ActionSetScreen::ActionSetScreen(const Common::String &line) { + char fileName[25]; + sscanf(line.c_str(), "%*[^(](%25[^)])", fileName); + + _fileName = Common::String(fileName); +} + +bool ActionSetScreen::execute(ZVision *engine) { + engine->getRenderManager()->setBackgroundImage(_fileName); + + return true; +} + + +////////////////////////////////////////////////////////////////////////////// +// ActionStreamVideo +////////////////////////////////////////////////////////////////////////////// + +ActionStreamVideo::ActionStreamVideo(const Common::String &line) { + char fileName[25]; + uint skippable; + + sscanf(line.c_str(), "%*[^(](%25s %u %u %u %u %u %u)", fileName, &_x1, &_y1, &_x2, &_y2, &_flags, &skippable); + + _fileName = Common::String(fileName); + _skippable = (skippable == 0) ? false : true; +} + +bool ActionStreamVideo::execute(ZVision *engine) { + ZorkAVIDecoder decoder; + if (!decoder.loadFile(_fileName)) { + return true; + } + + Common::Rect destRect; + if ((_flags & DIFFERENT_DIMENSIONS) == DIFFERENT_DIMENSIONS) { + destRect = Common::Rect(_x1, _y1, _x2, _y2); + } + + engine->playVideo(decoder, destRect, _skippable); + return true; +} + + +////////////////////////////////////////////////////////////////////////////// +// ActionTimer +////////////////////////////////////////////////////////////////////////////// + +ActionTimer::ActionTimer(const Common::String &line) { + sscanf(line.c_str(), "%*[^:]:%*[^:]:%u(%u)", &_key, &_time); +} + +bool ActionTimer::execute(ZVision *engine) { + engine->getScriptManager()->addControl(new TimerNode(engine, _key, _time)); + return true; +} + +} // End of namespace ZVision diff --git a/engines/zvision/actions.h b/engines/zvision/actions.h new file mode 100644 index 0000000000..afa3e3a2ac --- /dev/null +++ b/engines/zvision/actions.h @@ -0,0 +1,346 @@ +/* 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 ZVISION_ACTIONS_H +#define ZVISION_ACTIONS_H + +#include "common/str.h" + +#include "audio/mixer.h" + + +namespace ZVision { + +// Forward declaration of ZVision. This file is included before ZVision is declared +class ZVision; + +/** + * The base class that represents any action that a Puzzle can take. + * This class is purely virtual. + */ +class ResultAction { +public: + virtual ~ResultAction() {} + /** + * This is called by the script system whenever a Puzzle's criteria are found to be true. + * It should execute any necessary actions and return a value indicating whether the script + * system should continue to test puzzles. In 99% of cases this will be 'true'. + * + * @param engine A pointer to the base engine so the ResultAction can access all the necessary methods + * @return Should the script system continue to test any remaining puzzles (true) or immediately break and go on to the next frame (false) + */ + virtual bool execute(ZVision *engine) = 0; +}; + + +// The different types of actions +// DEBUG, +// DISABLE_CONTROL, +// DISABLE_VENUS, +// DISPLAY_MESSAGE, +// DISSOLVE, +// DISTORT, +// ENABLE_CONTROL, +// FLUSH_MOUSE_EVENTS, +// INVENTORY, +// KILL, +// MENU_BAR_ENABLE, +// MUSIC, +// PAN_TRACK, +// PLAY_PRELOAD, +// PREFERENCES, +// QUIT, +// RANDOM, +// REGION, +// RESTORE_GAME, +// ROTATE_TO, +// SAVE_GAME, +// SET_PARTIAL_SCREEN, +// SET_SCREEN, +// SET_VENUS, +// STOP, +// STREAM_VIDEO, +// SYNC_SOUND, +// TTY_TEXT, +// UNIVERSE_MUSIC, + +class ActionAdd : public ResultAction { +public: + ActionAdd(const Common::String &line); + bool execute(ZVision *engine); + +private: + uint32 _key; + uint _value; +}; + +class ActionAssign : public ResultAction { +public: + ActionAssign(const Common::String &line); + bool execute(ZVision *engine); + +private: + uint32 _key; + uint _value; +}; + +class ActionAttenuate : public ResultAction { +public: + ActionAttenuate(const Common::String &line); + bool execute(ZVision *engine); + +private: + uint32 _key; + int _attenuation; +}; + +class ActionChangeLocation : public ResultAction { +public: + ActionChangeLocation(const Common::String &line); + bool execute(ZVision *engine); + +private: + char _world; + char _room; + char _node; + char _view; + uint32 _offset; +}; + +class ActionCrossfade : public ResultAction { +public: + ActionCrossfade(const Common::String &line); + bool execute(ZVision *engine); + +private: + uint32 _keyOne; + uint32 _keyTwo; + uint _oneStartVolume; + uint _twoStartVolume; + uint _oneEndVolume; + uint _twoEndVolume; + uint _timeInMillis; +}; + +class ActionDebug : public ResultAction { +public: + ActionDebug(const Common::String &line); + bool execute(ZVision *engine); + +private: +}; + +class ActionDelayRender : public ResultAction { +public: + ActionDelayRender(const Common::String &line); + bool execute(ZVision *engine); + +private: + // TODO: Check if this should actually be frames or if it should be milliseconds/seconds + uint32 framesToDelay; +}; + +class ActionDisableControl : public ResultAction { +public: + ActionDisableControl(const Common::String &line); + bool execute(ZVision *engine); + +private: + uint32 _key; +}; + +class ActionDisableVenus : public ResultAction { +public: + ActionDisableVenus(const Common::String &line); + bool execute(ZVision *engine); + +private: +}; + +class ActionDisplayMessage : public ResultAction { +public: + ActionDisplayMessage(const Common::String &line); + bool execute(ZVision *engine); + +private: +}; + +class ActionDissolve : public ResultAction { +public: + ActionDissolve(); + bool execute(ZVision *engine); +}; + +class ActionDistort : public ResultAction { +public: + ActionDistort(const Common::String &line); + bool execute(ZVision *engine); + +private: +}; + +class ActionEnableControl : public ResultAction { +public: + ActionEnableControl(const Common::String &line); + bool execute(ZVision *engine); + +private: + uint32 _key; +}; + +class ActionMusic : public ResultAction { +public: + ActionMusic(const Common::String &line); + bool execute(ZVision *engine); + +private: + uint32 _key; + Audio::Mixer::SoundType _soundType; + Common::String _fileName; + bool _loop; + byte _volume; +}; + +class ActionPlayAnimation : public ResultAction { +public: + ActionPlayAnimation(const Common::String &line); + bool execute(ZVision *engine); + +private: + uint32 _key; + Common::String _fileName; + uint32 _x; + uint32 _y; + uint32 _width; + uint32 _height; + uint32 _start; + uint32 _end; + uint _mask; + uint _framerate; + uint _loopCount; +}; + +class ActionPlayPreloadAnimation : public ResultAction { +public: + ActionPlayPreloadAnimation(const Common::String &line); + bool execute(ZVision *engine); + +private: + uint32 _animationKey; + uint32 _controlKey; + uint32 _x1; + uint32 _y1; + uint32 _x2; + uint32 _y2; + uint _startFrame; + uint _endFrame; + uint _loopCount; +}; + +class ActionPreloadAnimation : public ResultAction { +public: + ActionPreloadAnimation(const Common::String &line); + bool execute(ZVision *engine); + +private: + uint32 _key; + Common::String _fileName; + uint _mask; + uint _framerate; +}; + +class ActionQuit : public ResultAction { +public: + ActionQuit() {} + bool execute(ZVision *engine); +}; + +// TODO: See if this exists in ZGI. It doesn't in ZNem +class ActionUnloadAnimation : public ResultAction { +public: + ActionUnloadAnimation(const Common::String &line); + bool execute(ZVision *engine); +}; + +class ActionRandom : public ResultAction { +public: + ActionRandom(const Common::String &line); + bool execute(ZVision *engine); + +private: + uint32 _key; + uint _max; +}; + +class ActionSetPartialScreen : public ResultAction { +public: + ActionSetPartialScreen(const Common::String &line); + bool execute(ZVision *engine); + +private: + uint _x; + uint _y; + Common::String _fileName; + uint16 _backgroundColor; +}; + +class ActionSetScreen : public ResultAction { +public: + ActionSetScreen(const Common::String &line); + bool execute(ZVision *engine); + +private: + Common::String _fileName; +}; + +class ActionStreamVideo : public ResultAction { +public: + ActionStreamVideo(const Common::String &line); + bool execute(ZVision *engine); + +private: + enum { + DIFFERENT_DIMENSIONS = 0x1 // 0x1 flags that the destRect dimensions are different from the original video dimensions + }; + + Common::String _fileName; + uint _x1; + uint _y1; + uint _x2; + uint _y2; + uint _flags; + bool _skippable; +}; + +class ActionTimer : public ResultAction { +public: + ActionTimer(const Common::String &line); + bool execute(ZVision *engine); + +private: + uint32 _key; + uint _time; +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/animation_control.cpp b/engines/zvision/animation_control.cpp new file mode 100644 index 0000000000..1143380501 --- /dev/null +++ b/engines/zvision/animation_control.cpp @@ -0,0 +1,263 @@ +/* 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 "common/scummsys.h" + +#include "zvision/animation_control.h" + +#include "zvision/zvision.h" +#include "zvision/render_manager.h" +#include "zvision/script_manager.h" +#include "zvision/rlf_animation.h" +#include "zvision/zork_avi_decoder.h" + +#include "video/video_decoder.h" + +#include "graphics/surface.h" + + +namespace ZVision { + +AnimationControl::AnimationControl(ZVision *engine, uint32 controlKey, const Common::String &fileName) + : Control(engine, controlKey), + _fileType(RLF), + _loopCount(1), + _currentLoop(0), + _accumulatedTime(0), + _cachedFrame(0), + _cachedFrameNeedsDeletion(false) { + if (fileName.hasSuffix(".rlf")) { + _fileType = RLF; + _animation.rlf = new RlfAnimation(fileName, false); + } else if (fileName.hasSuffix(".avi")) { + _fileType = AVI; + _animation.avi = new ZorkAVIDecoder(); + _animation.avi->loadFile(fileName); + } else { + warning("Unrecognized animation file type: %s", fileName.c_str()); + } + + _cachedFrame = new Graphics::Surface(); +} + +AnimationControl::~AnimationControl() { + if (_fileType == RLF) { + delete _animation.rlf; + } else if (_fileType == AVI) { + delete _animation.avi; + } + + _cachedFrame->free(); + delete _cachedFrame; +} + +bool AnimationControl::process(uint32 deltaTimeInMillis) { + if (!_enabled) { + return false; + } + + bool finished = false; + + if (_fileType == RLF) { + _accumulatedTime += deltaTimeInMillis; + + uint32 frameTime = _animation.rlf->frameTime(); + if (_accumulatedTime >= frameTime) { + while (_accumulatedTime >= frameTime) { + _accumulatedTime -= frameTime; + + // Make sure the frame is inside the working window + // If it's not, then just return + + RenderManager *renderManager = _engine->getRenderManager(); + Common::Point workingWindowPoint = renderManager->imageSpaceToWorkingWindowSpace(Common::Point(_x, _y)); + Common::Rect subRect(workingWindowPoint.x, workingWindowPoint.y, workingWindowPoint.x + _animation.rlf->width(), workingWindowPoint.y + _animation.rlf->height()); + + // If the clip returns false, it means the animation is outside the working window + if (!renderManager->clipRectToWorkingWindow(subRect)) { + return false; + } + + const Graphics::Surface *frame = _animation.rlf->getNextFrame(); + + // Animation frames for PANORAMAs are transposed, so un-transpose them + RenderTable::RenderState state = renderManager->getRenderTable()->getRenderState(); + if (state == RenderTable::PANORAMA) { + Graphics::Surface *tranposedFrame = RenderManager::tranposeSurface(frame); + + renderManager->copyRectToWorkingWindow((uint16 *)tranposedFrame->getBasePtr(tranposedFrame->w - subRect.width(), tranposedFrame->h - subRect.height()), subRect.left, subRect.top, _animation.rlf->width(), subRect.width(), subRect.height()); + + // If the background can move, we need to cache the last frame so it can be rendered during background movement + if (state == RenderTable::PANORAMA || state == RenderTable::TILT) { + if (_cachedFrameNeedsDeletion) { + _cachedFrame->free(); + delete _cachedFrame; + _cachedFrameNeedsDeletion = false; + } + _cachedFrame = tranposedFrame; + _cachedFrameNeedsDeletion = true; + } else { + // Cleanup + tranposedFrame->free(); + delete tranposedFrame; + } + } else { + renderManager->copyRectToWorkingWindow((const uint16 *)frame->getBasePtr(frame->w - subRect.width(), frame->h - subRect.height()), subRect.left, subRect.top, _animation.rlf->width(), subRect.width(), subRect.height()); + + // If the background can move, we need to cache the last frame so it can be rendered during background movement + if (state == RenderTable::PANORAMA || state == RenderTable::TILT) { + if (_cachedFrameNeedsDeletion) { + _cachedFrame->free(); + delete _cachedFrame; + _cachedFrameNeedsDeletion = false; + } + _cachedFrame->copyFrom(*frame); + } + } + + // Check if we should continue looping + if (_animation.rlf->endOfAnimation()) { + _animation.rlf->seekToFrame(-1); + if (_loopCount > 0) { + _currentLoop++; + if (_currentLoop >= _loopCount) { + finished = true; + } + } + } + } + } else { + // If the background can move, we have to keep rendering animation frames, otherwise the animation flickers during background movement + RenderManager *renderManager = _engine->getRenderManager(); + RenderTable::RenderState state = renderManager->getRenderTable()->getRenderState(); + + if (state == RenderTable::PANORAMA || state == RenderTable::TILT) { + Common::Point workingWindowPoint = renderManager->imageSpaceToWorkingWindowSpace(Common::Point(_x, _y)); + Common::Rect subRect(workingWindowPoint.x, workingWindowPoint.y, workingWindowPoint.x + _cachedFrame->w, workingWindowPoint.y + _cachedFrame->h); + + // If the clip returns false, it means the animation is outside the working window + if (!renderManager->clipRectToWorkingWindow(subRect)) { + return false; + } + + renderManager->copyRectToWorkingWindow((uint16 *)_cachedFrame->getBasePtr(_cachedFrame->w - subRect.width(), _cachedFrame->h - subRect.height()), subRect.left, subRect.top, _cachedFrame->w, subRect.width(), subRect.height()); + } + } + } else if (_fileType == AVI) { + if (!_animation.avi->isPlaying()) { + _animation.avi->start(); + } + + if (_animation.avi->needsUpdate()) { + const Graphics::Surface *frame = _animation.avi->decodeNextFrame(); + + if (frame) { + // Make sure the frame is inside the working window + // If it's not, then just return + + RenderManager *renderManager = _engine->getRenderManager(); + Common::Point workingWindowPoint = renderManager->imageSpaceToWorkingWindowSpace(Common::Point(_x, _y)); + Common::Rect subRect(workingWindowPoint.x, workingWindowPoint.y, workingWindowPoint.x + frame->w, workingWindowPoint.y + frame->h); + + // If the clip returns false, it means the animation is outside the working window + if (!renderManager->clipRectToWorkingWindow(subRect)) { + return false; + } + + // Animation frames for PANORAMAs are transposed, so un-transpose them + RenderTable::RenderState state = renderManager->getRenderTable()->getRenderState(); + if (state == RenderTable::PANORAMA) { + Graphics::Surface *tranposedFrame = RenderManager::tranposeSurface(frame); + + renderManager->copyRectToWorkingWindow((uint16 *)tranposedFrame->getBasePtr(tranposedFrame->w - subRect.width(), tranposedFrame->h - subRect.height()), subRect.left, subRect.top, frame->w, subRect.width(), subRect.height()); + + // If the background can move, we need to cache the last frame so it can be rendered during background movement + if (state == RenderTable::PANORAMA || state == RenderTable::TILT) { + if (_cachedFrameNeedsDeletion) { + _cachedFrame->free(); + delete _cachedFrame; + _cachedFrameNeedsDeletion = false; + } + _cachedFrame = tranposedFrame; + _cachedFrameNeedsDeletion = true; + } else { + // Cleanup + tranposedFrame->free(); + delete tranposedFrame; + } + } else { + renderManager->copyRectToWorkingWindow((const uint16 *)frame->getBasePtr(frame->w - subRect.width(), frame->h - subRect.height()), subRect.left, subRect.top, frame->w, subRect.width(), subRect.height()); + + // If the background can move, we need to cache the last frame so it can be rendered during background movement + if (state == RenderTable::PANORAMA || state == RenderTable::TILT) { + if (_cachedFrameNeedsDeletion) { + _cachedFrame->free(); + delete _cachedFrame; + _cachedFrameNeedsDeletion = false; + } + _cachedFrame->copyFrom(*frame); + } + } + } else { + // If the background can move, we have to keep rendering animation frames, otherwise the animation flickers during background movement + RenderManager *renderManager = _engine->getRenderManager(); + RenderTable::RenderState state = renderManager->getRenderTable()->getRenderState(); + + if (state == RenderTable::PANORAMA || state == RenderTable::TILT) { + Common::Point workingWindowPoint = renderManager->imageSpaceToWorkingWindowSpace(Common::Point(_x, _y)); + Common::Rect subRect(workingWindowPoint.x, workingWindowPoint.y, workingWindowPoint.x + _cachedFrame->w, workingWindowPoint.y + _cachedFrame->h); + + // If the clip returns false, it means the animation is outside the working window + if (!renderManager->clipRectToWorkingWindow(subRect)) { + return false; + } + + renderManager->copyRectToWorkingWindow((uint16 *)_cachedFrame->getBasePtr(_cachedFrame->w - subRect.width(), _cachedFrame->h - subRect.height()), subRect.left, subRect.top, _cachedFrame->w, subRect.width(), subRect.height()); + } + } + } + + // Check if we should continue looping + if (_animation.avi->endOfVideo()) { + _animation.avi->rewind(); + if (_loopCount > 0) { + _currentLoop++; + if (_currentLoop >= _loopCount) { + _animation.avi->stop(); + finished = true; + } + } + } + } + + // If we're done, set _animation key = 2 (Why 2? I don't know. It's just the value that they used) + // Then disable the control. DON'T delete it. It can be re-used + if (finished) { + _engine->getScriptManager()->setStateValue(_animationKey, 2); + disable(); + _currentLoop = 0; + } + + return false; +} + +} // End of namespace ZVision diff --git a/engines/zvision/animation_control.h b/engines/zvision/animation_control.h new file mode 100644 index 0000000000..2ac3691483 --- /dev/null +++ b/engines/zvision/animation_control.h @@ -0,0 +1,87 @@ +/* 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 ZVISION_ANIMATION_CONTROL_H +#define ZVISION_ANIMATION_CONTROL_H + +#include "zvision/control.h" + + +namespace Common { +class String; +} + +namespace Video { +class VideoDecoder; +} + +namespace Graphics { +struct Surface; +} + +namespace ZVision { + +class ZVision; +class RlfAnimation; + +class AnimationControl : public Control { +public: + AnimationControl(ZVision *engine, uint32 controlKey, const Common::String &fileName); + ~AnimationControl(); + +private: + enum FileType { + RLF = 1, + AVI = 2 + }; + +private: + uint32 _animationKey; + + union { + RlfAnimation *rlf; + Video::VideoDecoder *avi; + } _animation; + + FileType _fileType; + uint _loopCount; + int32 _x; + int32 _y; + + uint _accumulatedTime; + uint _currentLoop; + + Graphics::Surface *_cachedFrame; + bool _cachedFrameNeedsDeletion; + +public: + bool process(uint32 deltaTimeInMillis); + + void setAnimationKey(uint32 animationKey) { _animationKey = animationKey; } + void setLoopCount(uint loopCount) { _loopCount = loopCount; } + void setXPos(int32 x) { _x = x; } + void setYPost(int32 y) { _y = y; } +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/clock.cpp b/engines/zvision/clock.cpp new file mode 100644 index 0000000000..c8ee717a33 --- /dev/null +++ b/engines/zvision/clock.cpp @@ -0,0 +1,70 @@ +/* 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 "common/scummsys.h" + +#include "zvision/clock.h" + +#include "common/system.h" + + +namespace ZVision { + +Clock::Clock(OSystem *system) + : _system(system), + _lastTime(0), + _deltaTime(0), + _pausedTime(0), + _paused(false) { +} + +void Clock::update() { + uint32 currentTime = _system->getMillis(); + + _deltaTime = (currentTime - _lastTime); + if (_paused) { + _deltaTime -= (currentTime - _pausedTime); + } + + if (_deltaTime < 0) { + _deltaTime = 0; + } + + _lastTime = currentTime; +} + +void Clock::start() { + if (_paused) { + _lastTime = _system->getMillis(); + _paused = false; + } +} + +void Clock::stop() { + if (!_paused) { + _pausedTime = _system->getMillis(); + _paused = true; + } +} + +} // End of namespace ZVision diff --git a/engines/zvision/clock.h b/engines/zvision/clock.h new file mode 100644 index 0000000000..3939ba1612 --- /dev/null +++ b/engines/zvision/clock.h @@ -0,0 +1,78 @@ +/* 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 ZVISION_CLOCK_H +#define ZVISION_CLOCK_H + +#include "common/types.h" + +class OSystem; + +namespace ZVision { + +/* Class for handling frame to frame deltaTime while keeping track of time pauses/un-pauses */ +class Clock { +public: + Clock(OSystem *system); + +private: + OSystem *_system; + uint32 _lastTime; + int32 _deltaTime; + uint32 _pausedTime; + bool _paused; + +public: + /** + * Updates _deltaTime with the difference between the current time and + * when the last update() was called. + */ + void update(); + /** + * Get the delta time since the last frame. (The time between update() calls) + * + * @return Delta time since the last frame (in milliseconds) + */ + uint32 getDeltaTime() const { return _deltaTime; } + /** + * Get the time from the program starting to the last update() call + * + * @return Time from program start to last update() call (in milliseconds) + */ + uint32 getLastMeasuredTime() { return _lastTime; } + + /** + * Pause the clock. Any future delta times will take this pause into account. + * Has no effect if the clock is already paused. + */ + void start(); + /** + * Un-pause the clock. + * Has no effect if the clock is already un-paused. + */ + void stop(); +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/console.cpp b/engines/zvision/console.cpp new file mode 100644 index 0000000000..a095d3fa6a --- /dev/null +++ b/engines/zvision/console.cpp @@ -0,0 +1,218 @@ +/* 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 "common/scummsys.h" + +#include "zvision/console.h" + +#include "zvision/zvision.h" +#include "zvision/script_manager.h" +#include "zvision/render_manager.h" +#include "zvision/string_manager.h" +#include "zvision/zork_avi_decoder.h" +#include "zvision/zork_raw.h" +#include "zvision/utility.h" +#include "zvision/cursor.h" + +#include "common/system.h" +#include "common/file.h" +#include "common/bufferedstream.h" + +#include "gui/debugger.h" + +#include "audio/mixer.h" + + +namespace ZVision { + +Console::Console(ZVision *engine) : GUI::Debugger(), _engine(engine) { + DCmd_Register("loadimage", WRAP_METHOD(Console, cmdLoadImage)); + DCmd_Register("loadvideo", WRAP_METHOD(Console, cmdLoadVideo)); + DCmd_Register("loadsound", WRAP_METHOD(Console, cmdLoadSound)); + DCmd_Register("raw2wav", WRAP_METHOD(Console, cmdRawToWav)); + DCmd_Register("setrenderstate", WRAP_METHOD(Console, cmdSetRenderState)); + DCmd_Register("generaterendertable", WRAP_METHOD(Console, cmdGenerateRenderTable)); + DCmd_Register("setpanoramafov", WRAP_METHOD(Console, cmdSetPanoramaFoV)); + DCmd_Register("setpanoramascale", WRAP_METHOD(Console, cmdSetPanoramaScale)); + DCmd_Register("changelocation", WRAP_METHOD(Console, cmdChangeLocation)); + DCmd_Register("dumpfile", WRAP_METHOD(Console, cmdDumpFile)); + DCmd_Register("parseallscrfiles", WRAP_METHOD(Console, cmdParseAllScrFiles)); + DCmd_Register("rendertext", WRAP_METHOD(Console, cmdRenderText)); +} + +bool Console::cmdLoadImage(int argc, const char **argv) { + if (argc == 4) + _engine->getRenderManager()->renderImageToScreen(argv[1], atoi(argv[2]), atoi(argv[3])); + else { + DebugPrintf("Use loadimage <fileName> <destinationX> <destinationY> to load an image to the screen\n"); + return true; + } + + return true; +} + +bool Console::cmdLoadVideo(int argc, const char **argv) { + if (argc != 2) { + DebugPrintf("Use loadvideo <fileName> to load a video to the screen\n"); + return true; + } + + ZorkAVIDecoder videoDecoder; + if (videoDecoder.loadFile(argv[1])) { + _engine->playVideo(videoDecoder); + } + + return true; +} + +bool Console::cmdLoadSound(int argc, const char **argv) { + if (!Common::File::exists(argv[1])) { + DebugPrintf("File does not exist\n"); + return true; + } + + if (argc == 2) { + Audio::AudioStream *soundStream = makeRawZorkStream(argv[1], _engine); + Audio::SoundHandle handle; + _engine->_mixer->playStream(Audio::Mixer::kPlainSoundType, &handle, soundStream, -1, 100, 0, DisposeAfterUse::YES, false, false); + + } else if (argc == 4) { + int isStereo = atoi(argv[3]); + + Common::File *file = new Common::File(); + file->open(argv[1]); + + Audio::AudioStream *soundStream = makeRawZorkStream(file, atoi(argv[2]), isStereo == 0 ? false : true); + Audio::SoundHandle handle; + _engine->_mixer->playStream(Audio::Mixer::kPlainSoundType, &handle, soundStream, -1, 100, 0, DisposeAfterUse::YES, false, false); + } else { + DebugPrintf("Use loadsound <fileName> [<rate> <isStereo: 1 or 0>] to load a sound\n"); + return true; + } + + return true; +} + +bool Console::cmdRawToWav(int argc, const char **argv) { + if (argc != 3) { + DebugPrintf("Use raw2wav <rawFilePath> <wavFileName> to dump a .RAW file to .WAV\n"); + return true; + } + + convertRawToWav(argv[1], _engine, argv[2]); + return true; +} + +bool Console::cmdSetRenderState(int argc, const char **argv) { + if (argc != 2) { + DebugPrintf("Use setrenderstate <RenderState: panorama, tilt, flat> to change the current render state\n"); + return true; + } + + Common::String renderState(argv[1]); + + if (renderState.matchString("panorama", true)) + _engine->getRenderManager()->getRenderTable()->setRenderState(RenderTable::PANORAMA); + else if (renderState.matchString("tilt", true)) + _engine->getRenderManager()->getRenderTable()->setRenderState(RenderTable::TILT); + else if (renderState.matchString("flat", true)) + _engine->getRenderManager()->getRenderTable()->setRenderState(RenderTable::FLAT); + else + DebugPrintf("Use setrenderstate <RenderState: panorama, tilt, flat> to change the current render state\n"); + + return true; +} + +bool Console::cmdGenerateRenderTable(int argc, const char **argv) { + _engine->getRenderManager()->getRenderTable()->generateRenderTable(); + + return true; +} + +bool Console::cmdSetPanoramaFoV(int argc, const char **argv) { + if (argc != 2) { + DebugPrintf("Use setpanoramafov <fieldOfView> to change the current panorama field of view\n"); + return true; + } + + _engine->getRenderManager()->getRenderTable()->setPanoramaFoV(atof(argv[1])); + + return true; +} + +bool Console::cmdSetPanoramaScale(int argc, const char **argv) { + if (argc != 2) { + DebugPrintf("Use setpanoramascale <scale> to change the current panorama scale\n"); + return true; + } + + _engine->getRenderManager()->getRenderTable()->setPanoramaScale(atof(argv[1])); + + return true; +} + +bool Console::cmdChangeLocation(int argc, const char **argv) { + if (argc != 6) { + DebugPrintf("Use changelocation <char: world> <char: room> <char:node> <char:view> <int: x position> to change your location\n"); + return true; + } + + _engine->getScriptManager()->changeLocation(*(argv[1]), *(argv[2]), *(argv[3]), *(argv[4]), atoi(argv[5])); + + return true; +} + +bool Console::cmdDumpFile(int argc, const char **argv) { + if (argc != 2) { + DebugPrintf("Use dumpfile <fileName> to dump a file\n"); + return true; + } + + writeFileContentsToFile(argv[1], argv[1]); + + return true; +} + +bool Console::cmdParseAllScrFiles(int argc, const char **argv) { + Common::ArchiveMemberList list; + SearchMan.listMatchingMembers(list, "*.scr"); + + for (Common::ArchiveMemberList::iterator iter = list.begin(); iter != list.end(); ++iter) { + _engine->getScriptManager()->parseScrFile((*iter)->getName()); + } + + return true; +} + +bool Console::cmdRenderText(int argc, const char **argv) { + if (argc != 7) { + DebugPrintf("Use rendertext <text> <fontNumber> <destX> <destY> <maxWidth> <1 or 0: wrap> to render text\n"); + return true; + } + + StringManager::TextStyle style = _engine->getStringManager()->getTextStyle(atoi(argv[2])); + _engine->getRenderManager()->renderTextToWorkingWindow(333, Common::String(argv[1]), style.font, atoi(argv[3]), atoi(argv[4]), style.color, atoi(argv[5]), -1, Graphics::kTextAlignLeft, atoi(argv[6]) == 0 ? false : true); + + return true; +} + +} // End of namespace ZVision diff --git a/engines/zvision/console.h b/engines/zvision/console.h new file mode 100644 index 0000000000..0ca1b8cc70 --- /dev/null +++ b/engines/zvision/console.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. +* +*/ + +#ifndef ZVISION_CONSOLE_H +#define ZVISION_CONSOLE_H + +#include "gui/debugger.h" + +namespace ZVision { + +class ZVision; + +class Console : public GUI::Debugger { +public: + Console(ZVision *engine); + virtual ~Console() {} + +private: + ZVision *_engine; + + bool cmdLoadImage(int argc, const char **argv); + bool cmdLoadVideo(int argc, const char **argv); + bool cmdLoadSound(int argc, const char **argv); + bool cmdRawToWav(int argc, const char **argv); + bool cmdSetRenderState(int argc, const char **argv); + bool cmdGenerateRenderTable(int argc, const char **argv); + bool cmdSetPanoramaFoV(int argc, const char **argv); + bool cmdSetPanoramaScale(int argc, const char **argv); + bool cmdChangeLocation(int argc, const char **argv); + bool cmdDumpFile(int argc, const char **argv); + bool cmdParseAllScrFiles(int argc, const char **argv); + bool cmdRenderText(int argc, const char **argv); +}; + +} // End of namespace ZVision +#endif diff --git a/engines/zvision/control.cpp b/engines/zvision/control.cpp new file mode 100644 index 0000000000..bcbdabc143 --- /dev/null +++ b/engines/zvision/control.cpp @@ -0,0 +1,124 @@ +/* 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 "common/scummsys.h" + +#include "zvision/control.h" + +#include "zvision/zvision.h" +#include "zvision/render_manager.h" +#include "zvision/utility.h" + +#include "common/stream.h" + + +namespace ZVision { + +void Control::enable() { + if (!_enabled) { + _enabled = true; + return; + } + + debug("Control %u is already enabled", _key); +} + +void Control::disable() { + if (_enabled) { + _enabled = false; + return; + } + + debug("Control %u is already disabled", _key); +} + +void Control::parseFlatControl(ZVision *engine) { + engine->getRenderManager()->getRenderTable()->setRenderState(RenderTable::FLAT); +} + +void Control::parsePanoramaControl(ZVision *engine, Common::SeekableReadStream &stream) { + RenderTable *renderTable = engine->getRenderManager()->getRenderTable(); + renderTable->setRenderState(RenderTable::PANORAMA); + + // Loop until we find the closing brace + Common::String line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + + while (!stream.eos() && !line.contains('}')) { + if (line.matchString("angle*", true)) { + float fov; + sscanf(line.c_str(), "angle(%f)", &fov); + renderTable->setPanoramaFoV(fov); + } else if (line.matchString("linscale*", true)) { + float scale; + sscanf(line.c_str(), "linscale(%f)", &scale); + renderTable->setPanoramaScale(scale); + } else if (line.matchString("reversepana*", true)) { + uint reverse; + sscanf(line.c_str(), "reversepana(%u)", &reverse); + if (reverse == 1) { + renderTable->setPanoramaReverse(true); + } + } else if (line.matchString("zeropoint*", true)) { + // TODO: Implement + } + + line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + } + + renderTable->generateRenderTable(); +} + +void Control::parseTiltControl(ZVision *engine, Common::SeekableReadStream &stream) { + RenderTable *renderTable = engine->getRenderManager()->getRenderTable(); + renderTable->setRenderState(RenderTable::TILT); + + // Loop until we find the closing brace + Common::String line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + + while (!stream.eos() && !line.contains('}')) { + if (line.matchString("angle*", true)) { + float fov; + sscanf(line.c_str(), "angle(%f)", &fov); + renderTable->setTiltFoV(fov); + } else if (line.matchString("linscale*", true)) { + float scale; + sscanf(line.c_str(), "linscale(%f)", &scale); + renderTable->setTiltScale(scale); + } else if (line.matchString("reversepana*", true)) { + uint reverse; + sscanf(line.c_str(), "reversepana(%u)", &reverse); + if (reverse == 1) { + renderTable->setTiltReverse(true); + } + } + + line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + } + + renderTable->generateRenderTable(); +} + +} // End of namespace ZVision diff --git a/engines/zvision/control.h b/engines/zvision/control.h new file mode 100644 index 0000000000..770c540a12 --- /dev/null +++ b/engines/zvision/control.h @@ -0,0 +1,146 @@ +/* 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 ZVISION_CONTROL_H +#define ZVISION_CONTROL_H + +#include "common/keyboard.h" + + +namespace Common { +class SeekableReadStream; +struct Point; +class WriteStream; +} + +namespace ZVision { + +class ZVision; + +class Control { +public: + Control() : _engine(0), _key(0), _enabled(false) {} + Control(ZVision *engine, uint32 key) : _engine(engine), _key(key), _enabled(false) {} + virtual ~Control() {} + + uint32 getKey() { return _key; } + + virtual void enable(); + virtual void disable(); + virtual void focus() {} + virtual void unfocus() {} + /** + * Called when LeftMouse is pushed. Default is NOP. + * + * @param screenSpacePos The position of the mouse in screen space + * @param backgroundImageSpacePos The position of the mouse in background image space + */ + virtual void onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {} + /** + * Called when LeftMouse is lifted. Default is NOP. + * + * @param screenSpacePos The position of the mouse in screen space + * @param backgroundImageSpacePos The position of the mouse in background image space + */ + virtual void onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {} + /** + * Called on every MouseMove. Default is NOP. + * + * @param screenSpacePos The position of the mouse in screen space + * @param backgroundImageSpacePos The position of the mouse in background image space + * @return Was the cursor changed? + */ + virtual bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { return false; } + /** + * Called when a key is pressed. Default is NOP. + * + * @param keycode The key that was pressed + */ + virtual void onKeyDown(Common::KeyState keyState) {} + /** + * Called when a key is released. Default is NOP. + * + * @param keycode The key that was pressed + */ + virtual void onKeyUp(Common::KeyState keyState) {} + /** + * Processes the node given the deltaTime since last frame. Default is NOP. + * + * @param deltaTimeInMillis The number of milliseconds that have passed since last frame + * @return If true, the node can be deleted after process() finishes + */ + virtual bool process(uint32 deltaTimeInMillis) { return false; } + /** + * Serialize a Control for save game use. This should only be used if a Control needs + * to save values that would be different from initialization. AKA a TimerNode needs to + * store the amount of time left on the timer. Any Controls overriding this *MUST* write + * their key as the first data outputted. The default implementation is NOP. + * + * NOTE: If this method is overridden, you MUST also override deserialize() + * and needsSerialization() + * + * @param stream Stream to write any needed data to + */ + virtual void serialize(Common::WriteStream *stream) {} + /** + * De-serialize data from a save game stream. This should only be implemented if the + * Control also implements serialize(). The calling method assumes the size of the + * data read from the stream exactly equals that written in serialize(). The default + * implementation is NOP. + * + * NOTE: If this method is overridden, you MUST also override serialize() + * and needsSerialization() + * + * @param stream Save game file stream + */ + virtual void deserialize(Common::SeekableReadStream *stream) {} + /** + * If a Control overrides serialize() and deserialize(), this should return true + * + * @return Does the Control need save game serialization? + */ + virtual inline bool needsSerialization() { return false; } + +protected: + ZVision * _engine; + uint32 _key; + bool _enabled; + +// Static member functions +public: + static void parseFlatControl(ZVision *engine); + static void parsePanoramaControl(ZVision *engine, Common::SeekableReadStream &stream); + static void parseTiltControl(ZVision *engine, Common::SeekableReadStream &stream); +}; + +// TODO: Implement InputControl +// TODO: Implement SaveControl +// TODO: Implement SlotControl +// TODO: Implement SafeControl +// TODO: Implement FistControl +// TODO: Implement HotMovieControl +// TODO: Implement PaintControl +// TODO: Implement TilterControl + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/cursor.cpp b/engines/zvision/cursor.cpp new file mode 100644 index 0000000000..9023d97e0d --- /dev/null +++ b/engines/zvision/cursor.cpp @@ -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. +* +*/ + +#include "common/scummsys.h" + +#include "zvision/cursor.h" + +#include "common/str.h" +#include "common/file.h" + + +namespace ZVision { + +ZorkCursor::ZorkCursor() + : _width(0), + _height(0), + _hotspotX(0), + _hotspotY(0) { +} + +ZorkCursor::ZorkCursor(const Common::String &fileName) + : _width(0), + _height(0), + _hotspotX(0), + _hotspotY(0) { + Common::File file; + if (!file.open(fileName)) + return; + + uint32 magic = file.readUint32BE(); + if (magic != MKTAG('Z', 'C', 'R', '1')) { + warning("%s is not a Zork Cursor file", fileName.c_str()); + return; + } + + _hotspotX = file.readUint16LE(); + _hotspotY = file.readUint16LE(); + _width = file.readUint16LE(); + _height = file.readUint16LE(); + + uint dataSize = _width * _height * sizeof(uint16); + _surface.create(_width, _height, Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0)); + uint32 bytesRead = file.read(_surface.getPixels(), dataSize); + assert(bytesRead == dataSize); + + // Convert to RGB 565 + _surface.convertToInPlace(Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0)); +} + +ZorkCursor::ZorkCursor(const ZorkCursor &other) { + _width = other._width; + _height = other._height; + _hotspotX = other._hotspotX; + _hotspotY = other._hotspotY; + + _surface.copyFrom(other._surface); +} + +ZorkCursor &ZorkCursor::operator=(const ZorkCursor &other) { + _width = other._width; + _height = other._height; + _hotspotX = other._hotspotX; + _hotspotY = other._hotspotY; + + _surface.free(); + _surface.copyFrom(other._surface); + + return *this; +} + +ZorkCursor::~ZorkCursor() { + _surface.free(); +} + +} // End of namespace ZVision diff --git a/engines/zvision/cursor.h b/engines/zvision/cursor.h new file mode 100644 index 0000000000..18ac28ce8b --- /dev/null +++ b/engines/zvision/cursor.h @@ -0,0 +1,66 @@ +/* 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 ZVISION_CURSOR_H +#define ZVISION_CURSOR_H + +#include "graphics/surface.h" + + +namespace Common { +class String; +} + +namespace ZVision { + +/** + * Utility class to parse and hold cursor data + * Modeled off Graphics::Cursor + */ +class ZorkCursor { +public: + ZorkCursor(); + ZorkCursor(const Common::String &fileName); + ZorkCursor(const ZorkCursor &other); + ~ZorkCursor(); + +private: + uint16 _width; + uint16 _height; + uint16 _hotspotX; + uint16 _hotspotY; + Graphics::Surface _surface; + +public: + ZorkCursor &operator=(const ZorkCursor &other); + + uint16 getWidth() const { return _width; } + uint16 getHeight() const { return _height; } + uint16 getHotspotX() const { return _hotspotX; } + uint16 getHotspotY() const { return _hotspotY; } + byte getKeyColor() const { return 0; } + const byte *getSurface() const { return (const byte *)_surface.getPixels(); } +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/cursor_manager.cpp b/engines/zvision/cursor_manager.cpp new file mode 100644 index 0000000000..595e7946dd --- /dev/null +++ b/engines/zvision/cursor_manager.cpp @@ -0,0 +1,151 @@ +/* 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 "common/scummsys.h" + +#include "zvision/cursor_manager.h" + +#include "zvision/zvision.h" + +#include "common/system.h" + +#include "graphics/pixelformat.h" +#include "graphics/cursorman.h" + + +namespace ZVision { + +const char *CursorManager::_cursorNames[NUM_CURSORS] = { "active", "arrow", "backward", "downarrow", "forward", "handpt", "handpu", "hdown", "hleft", + "hright", "hup", "idle", "leftarrow", "rightarrow", "suggest_surround", "suggest_tilt", "turnaround", "zuparrow" }; + +const char *CursorManager::_zgiCursorFileNames[NUM_CURSORS] = { "g0gbc011.zcr", "g0gac001.zcr", "g0gac021.zcr", "g0gac031.zcr", "g0gac041.zcr", "g0gac051.zcr", "g0gac061.zcr", "g0gac071.zcr", "g0gac081.zcr", + "g0gac091.zcr", "g0gac101.zcr", "g0gac011.zcr", "g0gac111.zcr", "g0gac121.zcr", "g0gac131.zcr", "g0gac141.zcr", "g0gac151.zcr", "g0gac161.zcr" }; + +const char *CursorManager::_zNemCursorFileNames[NUM_CURSORS] = { "00act", "arrow", "back", "down", "forw", "handpt", "handpu", "hdown", "hleft", + "hright", "hup", "00idle", "left", "right", "ssurr", "stilt", "turn", "up" }; + + +CursorManager::CursorManager(ZVision *engine, const Graphics::PixelFormat *pixelFormat) + : _engine(engine), + _pixelFormat(pixelFormat), + _cursorIsPushed(false) { + // WARNING: The index IDLE_CURSOR_INDEX is hardcoded. If you change the order of _cursorNames/_zgiCursorFileNames/_zNemCursorFileNames, you HAVE to change the index accordingly + if (_engine->getGameId() == GID_NEMESIS) { + Common::String name(Common::String::format("%sa.zcr", _zNemCursorFileNames[IDLE_CURSOR_INDEX])); + _idleCursor = ZorkCursor(name); + } else if (_engine->getGameId() == GID_GRANDINQUISITOR) { + _idleCursor = ZorkCursor(_zgiCursorFileNames[IDLE_CURSOR_INDEX]); + } +} + +void CursorManager::initialize() { + revertToIdle(); + CursorMan.showMouse(true); +} + +void CursorManager::changeCursor(const Common::String &cursorName) { + changeCursor(cursorName, _cursorIsPushed); +} + +void CursorManager::changeCursor(const Common::String &cursorName, bool pushed) { + if (_currentCursor.equals(cursorName) && _cursorIsPushed == pushed) + return; + + if (_cursorIsPushed != pushed) + _cursorIsPushed = pushed; + + if (cursorName == "idle" && !pushed) { + CursorMan.replaceCursor(_idleCursor.getSurface(), _idleCursor.getWidth(), _idleCursor.getHeight(), _idleCursor.getHotspotX(), _idleCursor.getHotspotY(), _idleCursor.getKeyColor(), false, _pixelFormat); + return; + } + + for (int i = 0; i < NUM_CURSORS; ++i) { + if (_engine->getGameId() == GID_NEMESIS) { + if (cursorName.equals(_cursorNames[i])) { + _currentCursor = cursorName; + + // ZNem uses a/b at the end of the file to signify not pushed/pushed respectively + Common::String pushedFlag = pushed ? "b" : "a"; + Common::String name = Common::String::format("%s%s.zcr", _zNemCursorFileNames[i], pushedFlag.c_str()); + + changeCursor(ZorkCursor(name)); + return; + } + } else if (_engine->getGameId() == GID_GRANDINQUISITOR) { + if (cursorName.equals(_cursorNames[i])) { + _currentCursor = cursorName; + + if (!pushed) { + changeCursor(ZorkCursor(_zgiCursorFileNames[i])); + } else { + // ZGI flips not pushed/pushed between a/c and b/d + // It flips the 4th character of the name + char buffer[25]; + strcpy(buffer, _zgiCursorFileNames[i]); + buffer[3] += 2; + } + return; + } + } + } + + // If we get here, something went wrong + warning("No cursor found for identifier %s", cursorName.c_str()); +} + +void CursorManager::changeCursor(const ZorkCursor &cursor) { + CursorMan.replaceCursor(cursor.getSurface(), cursor.getWidth(), cursor.getHeight(), cursor.getHotspotX(), cursor.getHotspotY(), cursor.getKeyColor(), false, _pixelFormat); +} + +void CursorManager::cursorDown(bool pushed) { + if (_cursorIsPushed == pushed) + return; + + _cursorIsPushed = pushed; + changeCursor(_currentCursor, pushed); +} + +void CursorManager::setLeftCursor() { + changeCursor("leftarrow"); +} + +void CursorManager::setRightCursor() { + changeCursor("rightarrow"); +} + +void CursorManager::setUpCursor() { + changeCursor("zuparrow"); +} + +void CursorManager::setDownCursor() { + changeCursor("downarrow"); +} + +void CursorManager::revertToIdle() { + _currentCursor = "idle"; + if (!_cursorIsPushed) + CursorMan.replaceCursor(_idleCursor.getSurface(), _idleCursor.getWidth(), _idleCursor.getHeight(), _idleCursor.getHotspotX(), _idleCursor.getHotspotY(), _idleCursor.getKeyColor(), false, _pixelFormat); + else + changeCursor(_currentCursor, _cursorIsPushed); +} + +} // End of namespace ZVision diff --git a/engines/zvision/cursor_manager.h b/engines/zvision/cursor_manager.h new file mode 100644 index 0000000000..0a369aaf9e --- /dev/null +++ b/engines/zvision/cursor_manager.h @@ -0,0 +1,114 @@ +/* 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 ZVISION_CURSOR_MANAGER_H +#define ZVISION_CURSOR_MANAGER_H + +#include "zvision/cursor.h" + +#include "common/str.h" + + +namespace Graphics { +struct PixelFormat; +} + +namespace ZVision { + +class ZVision; + +/** + * Class to manage cursor changes. The actual changes have to be done + * through CursorMan. Otherwise the cursor will disappear after GMM + * or debug console. + * TODO: Figure out a way to get rid of the extraneous data copying due to having to use CursorMan + */ +class CursorManager { +public: + CursorManager(ZVision *engine, const Graphics::PixelFormat *pixelFormat); + +private: + enum { + NUM_CURSORS = 18, + // WARNING: The index 11 is hardcoded. If you change the order of _cursorNames/_zgiCursorFileNames/_zNemCursorFileNames, you HAVE to change the index accordingly + IDLE_CURSOR_INDEX = 11 + }; + + ZVision *_engine; + const Graphics::PixelFormat *_pixelFormat; + ZorkCursor _idleCursor; + Common::String _currentCursor; + bool _cursorIsPushed; + + static const char *_cursorNames[]; + static const char *_zgiCursorFileNames[]; + static const char *_zNemCursorFileNames[]; + +public: + /** Creates the idle cursor and shows it */ + void initialize(); + + /** + * Parses a cursor name into a cursor file then creates and shows that cursor. + * It will use the current _isCursorPushed state to choose the correct cursor + * + * @param cursorName The name of a cursor. This *HAS* to correspond to one of the entries in _cursorNames[] + */ + void changeCursor(const Common::String &cursorName); + /** + * Parses a cursor name into a cursor file then creates and shows that cursor. + * + * @param cursorName The name of a cursor. This *HAS* to correspond to one of the entries in _cursorNames[] + * @param pushed Should the cursor be pushed (true) or not pushed (false) (Another way to say it: down or up) + */ + void changeCursor(const Common::String &cursorName, bool pushed); + /** + * Change the cursor to a certain push state. If the cursor is already in the specified push state, nothing will happen. + * + * @param pushed Should the cursor be pushed (true) or not pushed (false) (Another way to say it: down or up) + */ + void cursorDown(bool pushed); + + /** Set the cursor to 'Left Arrow'. It will retain the current _isCursorPushed state */ + void setLeftCursor(); + /** Set the cursor to 'Right Arrow'. It will retain the current _isCursorPushed state */ + void setRightCursor(); + /** Set the cursor to 'Up Arrow'. It will retain the current _isCursorPushed state */ + void setUpCursor(); + /** Set the cursor to 'Down Arrow'. It will retain the current _isCursorPushed state */ + void setDownCursor(); + + /** Set the cursor to 'Idle'. It will retain the current _isCursorPushed state */ + void revertToIdle(); + +private: + /** + * Calls CursorMan.replaceCursor() using the data in cursor + * + * @param cursor The cursor to show + */ + void changeCursor(const ZorkCursor &cursor); +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/detection.cpp b/engines/zvision/detection.cpp new file mode 100644 index 0000000000..06e921dfa8 --- /dev/null +++ b/engines/zvision/detection.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. + * + * + */ + +#include "common/scummsys.h" + +#include "base/plugins.h" + +#include "zvision/zvision.h" +#include "zvision/detection.h" + +#include "common/translation.h" +#include "common/savefile.h" +#include "common/str-array.h" +#include "common/system.h" + + +namespace ZVision { + +uint32 ZVision::getFeatures() const { + return _gameDescription->desc.flags; +} + +Common::Language ZVision::getLanguage() const { + return _gameDescription->desc.language; +} + +} // End of namespace ZVision + + +static const PlainGameDescriptor zVisionGames[] = { + {"zvision", "ZVision Game"}, + {"znemesis", "Zork Nemesis: The Forbidden Lands"}, + {"zgi", "Zork: Grand Inquisitor"}, + {0, 0} +}; + + +namespace ZVision { + +static const ZVisionGameDescription gameDescriptions[] = { + + { + // Zork Nemesis English version + { + "znemesis", + 0, + AD_ENTRY1s("CSCR.ZFS", "88226e51a205d2e50c67a5237f3bd5f2", 2397741), + Common::EN_ANY, + Common::kPlatformDOS, + ADGF_NO_FLAGS, + GUIO1(GUIO_NONE) + }, + GID_NEMESIS + }, + + { + // Zork Grand Inquisitor English version + { + "zgi", + 0, + AD_ENTRY1s("SCRIPTS.ZFS", "81efd40ecc3d22531e211368b779f17f", 8336944), + Common::EN_ANY, + Common::kPlatformDOS, + ADGF_NO_FLAGS, + GUIO1(GUIO_NONE) + }, + GID_GRANDINQUISITOR + }, + + { + AD_TABLE_END_MARKER, + GID_NONE + } +}; + +} // End of namespace ZVision + +static const char *directoryGlobs[] = { + "znemscr", + 0 +}; + +static const ExtraGuiOption ZVisionExtraGuiOption = { + _s("Use original save/load screens"), + _s("Use the original save/load screens, instead of the ScummVM ones"), + "originalsaveload", + false +}; + +class ZVisionMetaEngine : public AdvancedMetaEngine { +public: + ZVisionMetaEngine() : AdvancedMetaEngine(ZVision::gameDescriptions, sizeof(ZVision::ZVisionGameDescription), zVisionGames) { + _maxScanDepth = 2; + _directoryGlobs = directoryGlobs; + _singleid = "zvision"; + } + + virtual const char *getName() const { + return "ZVision"; + } + + virtual const char *getOriginalCopyright() const { + return "ZVision Activision (C) 1996"; + } + + virtual bool hasFeature(MetaEngineFeature f) const; + virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const; + virtual const ExtraGuiOptions getExtraGuiOptions(const Common::String &target) const; + SaveStateList listSaves(const char *target) const; + virtual int getMaximumSaveSlot() const; + void removeSaveState(const char *target, int slot) const; + SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const; +}; + +bool ZVisionMetaEngine::hasFeature(MetaEngineFeature f) const { + return false; + /* + (f == kSupportsListSaves) || + (f == kSupportsLoadingDuringStartup) || + (f == kSupportsDeleteSave) || + (f == kSavesSupportMetaInfo) || + (f == kSavesSupportThumbnail) || + (f == kSavesSupportCreationDate) || + (f == kSavesSupportPlayTime); + */ +} + +/*bool ZVision::ZVision::hasFeature(EngineFeature f) const { + return + (f == kSupportsRTL) || + (f == kSupportsLoadingDuringRuntime) || + (f == kSupportsSavingDuringRuntime); +}*/ + +bool ZVisionMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const { + const ZVision::ZVisionGameDescription *gd = (const ZVision::ZVisionGameDescription *)desc; + if (gd) { + *engine = new ZVision::ZVision(syst, gd); + } + return gd != 0; +} + +const ExtraGuiOptions ZVisionMetaEngine::getExtraGuiOptions(const Common::String &target) const { + ExtraGuiOptions options; + options.push_back(ZVisionExtraGuiOption); + return options; +} + +SaveStateList ZVisionMetaEngine::listSaves(const char *target) const { + //Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); + /*ZVision::ZVision::SaveHeader header; + Common::String pattern = target; + pattern += ".???"; + + Common::StringArray filenames; + filenames = saveFileMan->listSavefiles(pattern.c_str()); + Common::sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..)*/ + + SaveStateList saveList; +/* for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); file++) { + // Obtain the last 3 digits of the filename, since they correspond to the save slot + int slotNum = atoi(file->c_str() + file->size() - 3); + + if (slotNum >= 0 && slotNum <= 999) { + Common::InSaveFile *in = saveFileMan->openForLoading(file->c_str()); + if (in) { + if (ZVision::ZVision::readSaveHeader(in, false, header) == ZVision::ZVision::kRSHENoError) { + saveList.push_back(SaveStateDescriptor(slotNum, header.description)); + } + delete in; + } + } + }*/ + + return saveList; +} + +int ZVisionMetaEngine::getMaximumSaveSlot() const { + return 999; +} + +void ZVisionMetaEngine::removeSaveState(const char *target, int slot) const { + /* + Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); + Common::String filename = ZVision::ZVision::getSavegameFilename(target, slot); + + saveFileMan->removeSavefile(filename.c_str()); + + Common::StringArray filenames; + Common::String pattern = target; + pattern += ".???"; + filenames = saveFileMan->listSavefiles(pattern.c_str()); + Common::sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) + + for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { + // Obtain the last 3 digits of the filename, since they correspond to the save slot + int slotNum = atoi(file->c_str() + file->size() - 3); + + // Rename every slot greater than the deleted slot, + if (slotNum > slot) { + saveFileMan->renameSavefile(file->c_str(), filename.c_str()); + filename = ZVision::ZVision::getSavegameFilename(target, ++slot); + } + } + */ +} + +SaveStateDescriptor ZVisionMetaEngine::querySaveMetaInfos(const char *target, int slot) const { + /* + Common::String filename = ZVision::ZVision::getSavegameFilename(target, slot); + Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(filename.c_str()); + + if (in) { + ZVision::ZVision::SaveHeader header; + ZVision::ZVision::kReadSaveHeaderError error; + + error = ZVision::ZVision::readSaveHeader(in, true, header); + delete in; + + if (error == ZVision::ZVision::kRSHENoError) { + SaveStateDescriptor desc(slot, header.description); + + desc.setThumbnail(header.thumbnail); + + if (header.version > 0) { + int day = (header.saveDate >> 24) & 0xFF; + int month = (header.saveDate >> 16) & 0xFF; + int year = header.saveDate & 0xFFFF; + + desc.setSaveDate(year, month, day); + + int hour = (header.saveTime >> 16) & 0xFF; + int minutes = (header.saveTime >> 8) & 0xFF; + + desc.setSaveTime(hour, minutes); + + desc.setPlayTime(header.playTime * 1000); + } + + return desc; + } + } + */ + + return SaveStateDescriptor(); +} + +#if PLUGIN_ENABLED_DYNAMIC(ZVISION) + REGISTER_PLUGIN_DYNAMIC(ZVISION, PLUGIN_TYPE_ENGINE, ZVisionMetaEngine); +#else + REGISTER_PLUGIN_STATIC(ZVISION, PLUGIN_TYPE_ENGINE, ZVisionMetaEngine); +#endif diff --git a/engines/zvision/detection.h b/engines/zvision/detection.h new file mode 100644 index 0000000000..34417601e8 --- /dev/null +++ b/engines/zvision/detection.h @@ -0,0 +1,44 @@ +/* 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 ZVISION_DETECTION_H +#define ZVISION_DETECTION_H + +#include "engines/advancedDetector.h" + + +namespace ZVision { + +enum ZVisionGameId { + GID_NONE = 0, + GID_NEMESIS = 1, + GID_GRANDINQUISITOR = 2 +}; + +struct ZVisionGameDescription { + ADGameDescription desc; + ZVisionGameId gameId; +}; + +} + +#endif diff --git a/engines/zvision/events.cpp b/engines/zvision/events.cpp new file mode 100644 index 0000000000..1103dc3000 --- /dev/null +++ b/engines/zvision/events.cpp @@ -0,0 +1,186 @@ +/* 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 "common/scummsys.h" + +#include "zvision/zvision.h" + +#include "zvision/console.h" +#include "zvision/cursor_manager.h" +#include "zvision/render_manager.h" +#include "zvision/script_manager.h" +#include "zvision/rlf_animation.h" + +#include "common/events.h" +#include "common/system.h" +#include "common/rational.h" + +#include "engines/util.h" + + +namespace ZVision { + +void ZVision::processEvents() { + while (_eventMan->pollEvent(_event)) { + switch (_event.type) { + case Common::EVENT_LBUTTONDOWN: + onMouseDown(_event.mouse); + break; + + case Common::EVENT_LBUTTONUP: + onMouseUp(_event.mouse); + break; + + case Common::EVENT_RBUTTONDOWN: + // TODO: Inventory logic + break; + + case Common::EVENT_MOUSEMOVE: + onMouseMove(_event.mouse); + break; + + case Common::EVENT_KEYDOWN: + switch (_event.kbd.keycode) { + case Common::KEYCODE_d: + if (_event.kbd.hasFlags(Common::KBD_CTRL)) { + // Start the debugger + _console->attach(); + _console->onFrame(); + } + break; + case Common::KEYCODE_q: + if (_event.kbd.hasFlags(Common::KBD_CTRL)) + quitGame(); + break; + default: + break; + } + + _scriptManager->onKeyDown(_event.kbd); + break; + case Common::EVENT_KEYUP: + _scriptManager->onKeyUp(_event.kbd); + break; + default: + break; + } + } +} + +void ZVision::onMouseDown(const Common::Point &pos) { + _cursorManager->cursorDown(true); + + Common::Point imageCoord(_renderManager->screenSpaceToImageSpace(pos)); + _scriptManager->onMouseDown(pos, imageCoord); +} + +void ZVision::onMouseUp(const Common::Point &pos) { + _cursorManager->cursorDown(false); + + Common::Point imageCoord(_renderManager->screenSpaceToImageSpace(pos)); + _scriptManager->onMouseUp(pos, imageCoord); +} + +void ZVision::onMouseMove(const Common::Point &pos) { + Common::Point imageCoord(_renderManager->screenSpaceToImageSpace(pos)); + + bool cursorWasChanged = _scriptManager->onMouseMove(pos, imageCoord); + + // Graph of the function governing rotation velocity: + // + // |---------------- working window ------------------| + // ^ |---------| + // | | + // +Max velocity | rotation screen edge offset + // | /| + // | / | + // | / | + // | / | + // | / | + // | / | + // | / | + // | / | + // | / | + // Zero velocity |______________________________ ______________________________/_________|__________________________> + // | Position -> | / + // | | / + // | | / + // | | / + // | | / + // | | / + // | | / + // | | / + // | | / + // -Max velocity | |/ + // | + // | + // ^ + + if (_workingWindow.contains(pos)) { + RenderTable::RenderState renderState = _renderManager->getRenderTable()->getRenderState(); + if (renderState == RenderTable::PANORAMA) { + if (pos.x >= _workingWindow.left && pos.x < _workingWindow.left + ROTATION_SCREEN_EDGE_OFFSET) { + // Linear function of distance to the left edge (y = -mx + b) + // We use fixed point math to get better accuracy + Common::Rational velocity = (Common::Rational(MAX_ROTATION_SPEED, ROTATION_SCREEN_EDGE_OFFSET) * (pos.x - _workingWindow.left)) - MAX_ROTATION_SPEED; + _renderManager->setBackgroundVelocity(velocity.toInt()); + _cursorManager->setLeftCursor(); + cursorWasChanged = true; + } else if (pos.x <= _workingWindow.right && pos.x > _workingWindow.right - ROTATION_SCREEN_EDGE_OFFSET) { + // Linear function of distance to the right edge (y = mx) + // We use fixed point math to get better accuracy + Common::Rational velocity = Common::Rational(MAX_ROTATION_SPEED, ROTATION_SCREEN_EDGE_OFFSET) * (pos.x - _workingWindow.right + ROTATION_SCREEN_EDGE_OFFSET); + _renderManager->setBackgroundVelocity(velocity.toInt()); + _cursorManager->setRightCursor(); + cursorWasChanged = true; + } else { + _renderManager->setBackgroundVelocity(0); + } + } else if (renderState == RenderTable::TILT) { + if (pos.y >= _workingWindow.top && pos.y < _workingWindow.top + ROTATION_SCREEN_EDGE_OFFSET) { + // Linear function of distance to top edge + // We use fixed point math to get better accuracy + Common::Rational velocity = (Common::Rational(MAX_ROTATION_SPEED, ROTATION_SCREEN_EDGE_OFFSET) * (pos.y - _workingWindow.top)) - MAX_ROTATION_SPEED; + _renderManager->setBackgroundVelocity(velocity.toInt()); + _cursorManager->setUpCursor(); + cursorWasChanged = true; + } else if (pos.y <= _workingWindow.bottom && pos.y > _workingWindow.bottom - ROTATION_SCREEN_EDGE_OFFSET) { + // Linear function of distance to the bottom edge (y = mx) + // We use fixed point math to get better accuracy + Common::Rational velocity = Common::Rational(MAX_ROTATION_SPEED, ROTATION_SCREEN_EDGE_OFFSET) * (pos.y - _workingWindow.bottom + ROTATION_SCREEN_EDGE_OFFSET); + _renderManager->setBackgroundVelocity(velocity.toInt()); + _cursorManager->setDownCursor(); + cursorWasChanged = true; + } else { + _renderManager->setBackgroundVelocity(0); + } + } + } else { + _renderManager->setBackgroundVelocity(0); + } + + if (!cursorWasChanged) { + _cursorManager->revertToIdle(); + } +} + +} // End of namespace ZVision diff --git a/engines/zvision/input_control.cpp b/engines/zvision/input_control.cpp new file mode 100644 index 0000000000..a445e1aae5 --- /dev/null +++ b/engines/zvision/input_control.cpp @@ -0,0 +1,142 @@ +/* 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 "common/scummsys.h" + +#include "zvision/input_control.h" + +#include "zvision/zvision.h" +#include "zvision/script_manager.h" +#include "zvision/string_manager.h" +#include "zvision/render_manager.h" +#include "zvision/utility.h" + +#include "common/str.h" +#include "common/stream.h" +#include "common/rect.h" + + +namespace ZVision { + +InputControl::InputControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream) + : Control(engine, key), + _nextTabstop(0), + _focused(false), + _textChanged(false), + _cursorOffset(0) { + // Loop until we find the closing brace + Common::String line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + + while (!stream.eos() && !line.contains('}')) { + if (line.matchString("*rectangle*", true)) { + int x1; + int y1; + int x2; + int y2; + + sscanf(line.c_str(), "%*[^(](%d %d %d %d)", &x1, &y1, &x2, &y2); + + _textRectangle = Common::Rect(x1, y1, x2, y2); + } else if (line.matchString("*aux_hotspot*", true)) { + int x1; + int y1; + int x2; + int y2; + + sscanf(line.c_str(), "%*[^(](%d %d %d %d)", &x1, &y1, &x2, &y2); + + _headerRectangle = Common::Rect(x1, y1, x2, y2); + } else if (line.matchString("*string_init*", true)) { + uint fontFormatNumber; + + sscanf(line.c_str(), "%*[^(](%u)", &fontFormatNumber); + + _textStyle = _engine->getStringManager()->getTextStyle(fontFormatNumber); + } else if (line.matchString("*next_tabstop*", true)) { + sscanf(line.c_str(), "%*[^(](%u)", &_nextTabstop); + } else if (line.matchString("*cursor_animation*", true)) { + char fileName[25]; + + sscanf(line.c_str(), "%*[^(](%25s %*u)", fileName); + + _cursorAnimationFileName = Common::String(fileName); + } else if (line.matchString("*cursor_dimensions*", true)) { + // Ignore, use the dimensions in the animation file + } else if (line.matchString("*cursor_animation_frames*", true)) { + // Ignore, use the frame count in the animation file + } else if (line.matchString("*focus*", true)) { + _focused = true; + } + + line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + } +} + +void InputControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + _engine->getScriptManager()->focusControl(_key); +} + +void InputControl::onKeyDown(Common::KeyState keyState) { + if (!_focused) { + return; + } + + if (keyState.keycode == Common::KEYCODE_BACKSPACE) { + _currentInputText.deleteLastChar(); + } else if (keyState.keycode == Common::KEYCODE_TAB) { + _focused = false; + // Focus the next input control + _engine->getScriptManager()->focusControl(_nextTabstop); + } else { + // Otherwise, append the new character to the end of the current text + + uint16 asciiValue = keyState.ascii; + // We only care about text values + if (asciiValue >= 32 && asciiValue <= 126) { + _currentInputText += (char)asciiValue; + _textChanged = true; + } + } +} + +bool InputControl::process(uint32 deltaTimeInMillis) { + if (!_focused) { + return false; + } + + // First see if we need to render the text + if (_textChanged) { + // Blit the text using the RenderManager + Common::Rect destRect = _engine->getRenderManager()->renderTextToWorkingWindow(_key, _currentInputText, _textStyle.font, _textRectangle.left, _textRectangle.top, _textStyle.color, _textRectangle.width()); + + _cursorOffset = destRect.left - _textRectangle.left; + } + + // Render the next frame of the animation + // TODO: Implement animation handling + + return false; +} + +} // End of namespace ZVision diff --git a/engines/zvision/input_control.h b/engines/zvision/input_control.h new file mode 100644 index 0000000000..aab2c991dc --- /dev/null +++ b/engines/zvision/input_control.h @@ -0,0 +1,60 @@ +/* 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 ZVISION_INPUT_CONTROL_H +#define ZVISION_INPUT_CONTROL_H + +#include "zvision/control.h" +#include "zvision/string_manager.h" + +#include "common/rect.h" + + +namespace ZVision { + +class InputControl : public Control { +public: + InputControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream); + +private: + Common::Rect _textRectangle; + Common::Rect _headerRectangle; + StringManager::TextStyle _textStyle; + uint32 _nextTabstop; + Common::String _cursorAnimationFileName; + bool _focused; + + Common::String _currentInputText; + bool _textChanged; + uint _cursorOffset; + +public: + void focus() { _focused = true; } + void unfocus() { _focused = false; } + void onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + void onKeyDown(Common::KeyState keyState); + bool process(uint32 deltaTimeInMillis); +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/inventory_manager.h b/engines/zvision/inventory_manager.h new file mode 100644 index 0000000000..ae6d116b18 --- /dev/null +++ b/engines/zvision/inventory_manager.h @@ -0,0 +1,28 @@ +/* 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 ZVISION_INVENTORY_MANAGER_H +#define ZVISION_INVENTORY_MANAGER_H + +// TODO: Implement InventoryManager + +#endif diff --git a/engines/zvision/lever_control.cpp b/engines/zvision/lever_control.cpp new file mode 100644 index 0000000000..79049b8fcb --- /dev/null +++ b/engines/zvision/lever_control.cpp @@ -0,0 +1,402 @@ +/* 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 "common/scummsys.h" + +#include "zvision/lever_control.h" + +#include "zvision/zvision.h" +#include "zvision/script_manager.h" +#include "zvision/render_manager.h" +#include "zvision/cursor_manager.h" +#include "zvision/rlf_animation.h" +#include "zvision/zork_avi_decoder.h" +#include "zvision/utility.h" + +#include "common/stream.h" +#include "common/file.h" +#include "common/tokenizer.h" +#include "common/system.h" + +#include "graphics/surface.h" + + +namespace ZVision { + +LeverControl::LeverControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream) + : Control(engine, key), + _frameInfo(0), + _frameCount(0), + _startFrame(0), + _currentFrame(0), + _lastRenderedFrame(0), + _mouseIsCaptured(false), + _isReturning(false), + _accumulatedTime(0), + _returnRoutesCurrentFrame(0) { + + // Loop until we find the closing brace + Common::String line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + + while (!stream.eos() && !line.contains('}')) { + if (line.matchString("*descfile*", true)) { + char levFileName[25]; + sscanf(line.c_str(), "%*[^(](%25[^)])", levFileName); + + parseLevFile(levFileName); + } else if (line.matchString("*cursor*", true)) { + char cursorName[25]; + sscanf(line.c_str(), "%*[^(](%25[^)])", cursorName); + + _cursorName = Common::String(cursorName); + } + + line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + } + + renderFrame(_currentFrame); +} + +LeverControl::~LeverControl() { + if (_fileType == AVI) { + delete _animation.avi; + } else if (_fileType == RLF) { + delete _animation.rlf; + } + + delete[] _frameInfo; +} + +void LeverControl::parseLevFile(const Common::String &fileName) { + Common::File file; + if (!file.open(fileName)) { + warning("LEV file %s could could be opened", fileName.c_str()); + return; + } + + Common::String line = file.readLine(); + + while (!file.eos()) { + if (line.matchString("*animation_id*", true)) { + // Not used + } else if (line.matchString("*filename*", true)) { + char fileNameBuffer[25]; + sscanf(line.c_str(), "%*[^:]:%25[^~]~", fileNameBuffer); + + Common::String animationFileName(fileNameBuffer); + + if (animationFileName.hasSuffix(".avi")) { + _animation.avi = new ZorkAVIDecoder(); + _animation.avi->loadFile(animationFileName); + _fileType = AVI; + } else if (animationFileName.hasSuffix(".rlf")) { + _animation.rlf = new RlfAnimation(animationFileName, false); + _fileType = RLF; + } + } else if (line.matchString("*skipcolor*", true)) { + // Not used + } else if (line.matchString("*anim_coords*", true)) { + int left, top, right, bottom; + sscanf(line.c_str(), "%*[^:]:%d %d %d %d~", &left, &top, &right, &bottom); + + _animationCoords.left = left; + _animationCoords.top = top; + _animationCoords.right = right; + _animationCoords.bottom = bottom; + } else if (line.matchString("*mirrored*", true)) { + uint mirrored; + sscanf(line.c_str(), "%*[^:]:%u~", &mirrored); + + _mirrored = mirrored == 0 ? false : true; + } else if (line.matchString("*frames*", true)) { + sscanf(line.c_str(), "%*[^:]:%u~", &_frameCount); + + _frameInfo = new FrameInfo[_frameCount]; + } else if (line.matchString("*elsewhere*", true)) { + // Not used + } else if (line.matchString("*out_of_control*", true)) { + // Not used + } else if (line.matchString("*start_pos*", true)) { + sscanf(line.c_str(), "%*[^:]:%u~", &_startFrame); + _currentFrame = _startFrame; + } else if (line.matchString("*hotspot_deltas*", true)) { + uint x; + uint y; + sscanf(line.c_str(), "%*[^:]:%u %u~", &x, &y); + + _hotspotDelta.x = x; + _hotspotDelta.y = y; + } else { + uint frameNumber; + uint x, y; + + if (sscanf(line.c_str(), "%u:%u %u", &frameNumber, &x, &y) == 3) { + _frameInfo[frameNumber].hotspot.left = x; + _frameInfo[frameNumber].hotspot.top = y; + _frameInfo[frameNumber].hotspot.right = x + _hotspotDelta.x; + _frameInfo[frameNumber].hotspot.bottom = y + _hotspotDelta.y; + } + + Common::StringTokenizer tokenizer(line, " ^=()"); + tokenizer.nextToken(); + tokenizer.nextToken(); + + Common::String token = tokenizer.nextToken(); + while (!tokenizer.empty()) { + if (token == "D") { + token = tokenizer.nextToken(); + + uint angle; + uint toFrame; + sscanf(token.c_str(), "%u,%u", &toFrame, &angle); + + _frameInfo[frameNumber].directions.push_back(Direction(angle, toFrame)); + } else if (token.hasPrefix("P")) { + // Format: P(<from> to <to>) + tokenizer.nextToken(); + tokenizer.nextToken(); + token = tokenizer.nextToken(); + uint to = atoi(token.c_str()); + + _frameInfo[frameNumber].returnRoute.push_back(to); + } + + token = tokenizer.nextToken(); + } + } + + line = file.readLine(); + } +} + +void LeverControl::onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (!_enabled) { + return; + } + + if (_frameInfo[_currentFrame].hotspot.contains(backgroundImageSpacePos)) { + _mouseIsCaptured = true; + _lastMousePos = backgroundImageSpacePos; + } +} + +void LeverControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (!_enabled) { + return; + } + + if (_mouseIsCaptured) { + _mouseIsCaptured = false; + _engine->getScriptManager()->setStateValue(_key, _currentFrame); + + _isReturning = true; + _returnRoutesCurrentProgress = _frameInfo[_currentFrame].returnRoute.begin(); + _returnRoutesCurrentFrame = _currentFrame; + } +} + +bool LeverControl::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (!_enabled) { + return false; + } + + bool cursorWasChanged = false; + + if (_mouseIsCaptured) { + // Make sure the square distance between the last point and the current point is greater than 64 + // This is a heuristic. This determines how responsive the lever is to mouse movement. + // TODO: Fiddle with the heuristic to get a good lever responsiveness 'feel' + if (_lastMousePos.sqrDist(backgroundImageSpacePos) >= 64) { + int angle = calculateVectorAngle(_lastMousePos, backgroundImageSpacePos); + _lastMousePos = backgroundImageSpacePos; + + for (Common::List<Direction>::iterator iter = _frameInfo[_currentFrame].directions.begin(); iter != _frameInfo[_currentFrame].directions.end(); ++iter) { + if (angle >= (int)iter->angle - ANGLE_DELTA && angle <= (int)iter->angle + ANGLE_DELTA) { + _currentFrame = iter->toFrame; + renderFrame(_currentFrame); + break; + } + } + } + } else if (_frameInfo[_currentFrame].hotspot.contains(backgroundImageSpacePos)) { + _engine->getCursorManager()->changeCursor(_cursorName); + cursorWasChanged = true; + } + + return cursorWasChanged; +} + +bool LeverControl::process(uint32 deltaTimeInMillis) { + if (!_enabled) { + return false; + } + + if (_isReturning) { + _accumulatedTime += deltaTimeInMillis; + while (_accumulatedTime >= ANIMATION_FRAME_TIME) { + _accumulatedTime -= ANIMATION_FRAME_TIME; + if (_returnRoutesCurrentFrame == *_returnRoutesCurrentProgress) { + _returnRoutesCurrentProgress++; + } + if (_returnRoutesCurrentProgress == _frameInfo[_currentFrame].returnRoute.end()) { + _isReturning = false; + _currentFrame = _returnRoutesCurrentFrame; + return false; + } + + uint toFrame = *_returnRoutesCurrentProgress; + if (_returnRoutesCurrentFrame < toFrame) { + _returnRoutesCurrentFrame++; + } else if (_returnRoutesCurrentFrame > toFrame) { + _returnRoutesCurrentFrame--; + } + + _engine->getScriptManager()->setStateValue(_key, _returnRoutesCurrentFrame); + renderFrame(_returnRoutesCurrentFrame); + } + } + + return false; +} + +int LeverControl::calculateVectorAngle(const Common::Point &pointOne, const Common::Point &pointTwo) { + // Check for the easy angles first + if (pointOne.x == pointTwo.x && pointOne.y == pointTwo.y) + return -1; // This should never happen + else if (pointOne.x == pointTwo.x) { + if (pointTwo.y < pointOne.y) + return 90; + else + return 270; + } else if (pointOne.y == pointTwo.y) { + if (pointTwo.x > pointOne.x) + return 0; + else + return 180; + } else { + // Calculate the angle with trig + int16 xDist = pointTwo.x - pointOne.x; + int16 yDist = pointTwo.y - pointOne.y; + + // Calculate the angle using arctan + // Then convert to degrees. (180 / 3.14159 = 57.2958) + int angle = int(atan((float)yDist / (float)xDist) * 57); + + // Calculate what quadrant pointTwo is in + uint quadrant = ((yDist > 0 ? 1 : 0) << 1) | (xDist < 0 ? 1 : 0); + + // Explanation of quadrants: + // + // yDist > 0 | xDist < 0 | Quadrant number + // 0 | 0 | 0 + // 0 | 1 | 1 + // 1 | 0 | 2 + // 1 | 1 | 3 + // + // Note: I know this doesn't line up with traditional mathematical quadrants + // but doing it this way allows you can use a switch and is a bit cleaner IMO. + // + // The graph below shows the 4 quadrants pointTwo can end up in as well + // as what the angle as calculated above refers to. + // Note: The calculated angle in quadrants 0 and 3 is negative + // due to arctan(-x) = -theta + // + // Origin => (pointOne.x, pointOne.y) + // * => (pointTwo.x, pointTwo.y) + // + // 90 | + // ^ | + // * | * | + // \ | / | + // \ | / | + // \ | / | + // Quadrant 1 \ | / Quadrant 0 | + // \ | / | + // \ | / | + // angle ( \|/ ) -angle | + // 180 <----------------------------------------> 0 | + // -angle ( /|\ ) angle | + // / | \ | + // / | \ | + // Quadrant 3 / | \ Quadrant 2 | + // / | \ | + // / | \ | + // / | \ | + // * | * | + // ^ | + // 270 | + + // Convert the local angles to unit circle angles + switch (quadrant) { + case 0: + angle = 180 + angle; + break; + case 1: + // Do nothing + break; + case 2: + angle = 180 + angle; + break; + case 3: + angle = 360 + angle; + break; + } + + return angle; + } +} + +void LeverControl::renderFrame(uint frameNumber) { + if (frameNumber == 0) { + _lastRenderedFrame = frameNumber; + } else if (frameNumber < _lastRenderedFrame && _mirrored) { + _lastRenderedFrame = frameNumber; + frameNumber = (_frameCount * 2) - frameNumber - 1; + } else { + _lastRenderedFrame = frameNumber; + } + + const uint16 *frameData; + int x = _animationCoords.left; + int y = _animationCoords.top; + int width; + int height; + + if (_fileType == RLF) { + // getFrameData() will automatically optimize to getNextFrame() / getPreviousFrame() if it can + frameData = (const uint16 *)_animation.rlf->getFrameData(frameNumber)->getPixels(); + width = _animation.rlf->width(); // Use the animation width instead of _animationCoords.width() + height = _animation.rlf->height(); // Use the animation height instead of _animationCoords.height() + } else if (_fileType == AVI) { + _animation.avi->seekToFrame(frameNumber); + const Graphics::Surface *surface = _animation.avi->decodeNextFrame(); + frameData = (const uint16 *)surface->getPixels(); + width = surface->w; + height = surface->h; + } + + _engine->getRenderManager()->copyRectToWorkingWindow(frameData, x, y, width, width, height); +} + +} // End of namespace ZVision diff --git a/engines/zvision/lever_control.h b/engines/zvision/lever_control.h new file mode 100644 index 0000000000..8ef8f06fec --- /dev/null +++ b/engines/zvision/lever_control.h @@ -0,0 +1,127 @@ +/* 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 ZVISION_LEVER_CONTROL_H +#define ZVISION_LEVER_CONTROL_H + +#include "zvision/control.h" + +#include "common/list.h" +#include "common/rect.h" + + +namespace ZVision { + +class ZorkAVIDecoder; +class RlfAnimation; + +class LeverControl : public Control { +public: + LeverControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream); + ~LeverControl(); + +private: + enum FileType { + RLF = 1, + AVI = 2 + }; + + struct Direction { + Direction(uint a, uint t) : angle(a), toFrame(t) {} + + uint angle; + uint toFrame; + }; + + struct FrameInfo { + Common::Rect hotspot; + Common::List<Direction> directions; + Common::List<uint> returnRoute; + }; + + enum { + ANGLE_DELTA = 30, // How far off a mouse angle can be and still be considered valid. This is in both directions, so the total buffer zone is (2 * ANGLE_DELTA) + ANIMATION_FRAME_TIME = 30 // In millis + }; + +private: + union { + RlfAnimation *rlf; + ZorkAVIDecoder *avi; + } _animation; + FileType _fileType; + + Common::String _cursorName; + Common::Rect _animationCoords; + bool _mirrored; + uint _frameCount; + uint _startFrame; + Common::Point _hotspotDelta; + FrameInfo *_frameInfo; + + uint _currentFrame; + uint _lastRenderedFrame; + bool _mouseIsCaptured; + bool _isReturning; + Common::Point _lastMousePos; + Common::List<uint>::iterator _returnRoutesCurrentProgress; + uint _returnRoutesCurrentFrame; + uint32 _accumulatedTime; + +public: + void onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + void onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + bool process(uint32 deltaTimeInMillis); + +private: + void parseLevFile(const Common::String &fileName); + /** + * Calculates the angle a vector makes with the negative y-axis + * + * 90 + * pointTwo * ^ + * \ | + * \ | + * \ | + * \ | + * angle ( \|pointOne + * 180 <-----------*-----------> 0 + * | + * | + * | + * | + * | + * ^ + * 270 + * + * @param pointOne The origin of the vector + * @param pointTwo The end of the vector + * @return The angle the vector makes with the negative y-axis + */ + static int calculateVectorAngle(const Common::Point &pointOne, const Common::Point &pointTwo); + void renderFrame(uint frameNumber); +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/lzss_read_stream.cpp b/engines/zvision/lzss_read_stream.cpp new file mode 100644 index 0000000000..bbbda6f526 --- /dev/null +++ b/engines/zvision/lzss_read_stream.cpp @@ -0,0 +1,103 @@ +/* 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 "common/scummsys.h" + +#include "zvision/lzss_read_stream.h" + + +namespace ZVision { + +LzssReadStream::LzssReadStream(Common::SeekableReadStream *source) + : _source(source), + // It's convention to set the starting cursor position to blockSize - 16 + _windowCursor(0x0FEE), + _eosFlag(false) { + // Clear the window to null + memset(_window, 0, BLOCK_SIZE); +} + +uint32 LzssReadStream::decompressBytes(byte *destination, uint32 numberOfBytes) { + uint32 destinationCursor = 0; + + while (destinationCursor < numberOfBytes) { + byte flagbyte = _source->readByte(); + if (_source->eos()) + break; + uint mask = 1; + + for (int i = 0; i < 8; ++i) { + if ((flagbyte & mask) == mask) { + byte data = _source->readByte(); + if (_source->eos()) { + return destinationCursor; + } + + _window[_windowCursor] = data; + destination[destinationCursor++] = data; + + // Increment and wrap the window cursor + _windowCursor = (_windowCursor + 1) & 0xFFF; + } else { + byte low = _source->readByte(); + if (_source->eos()) { + return destinationCursor; + } + + byte high = _source->readByte(); + if (_source->eos()) { + return destinationCursor; + } + + uint16 length = (high & 0xF) + 2; + uint16 offset = low | ((high & 0xF0)<<4); + + for(int j = 0; j <= length; ++j) { + byte temp = _window[(offset + j) & 0xFFF]; + _window[_windowCursor] = temp; + destination[destinationCursor++] = temp; + _windowCursor = (_windowCursor + 1) & 0xFFF; + } + } + + mask = mask << 1; + } + } + + return destinationCursor; +} + +bool LzssReadStream::eos() const { + return _eosFlag; +} + +uint32 LzssReadStream::read(void *dataPtr, uint32 dataSize) { + uint32 bytesRead = decompressBytes(static_cast<byte *>(dataPtr), dataSize); + if (bytesRead < dataSize) { + // Flag that we're at EOS + _eosFlag = true; + } + + return dataSize; +} + +} // End of namespace ZVision diff --git a/engines/zvision/lzss_read_stream.h b/engines/zvision/lzss_read_stream.h new file mode 100644 index 0000000000..f6b1eb1a65 --- /dev/null +++ b/engines/zvision/lzss_read_stream.h @@ -0,0 +1,72 @@ +/* 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 ZVISION_LZSS_STREAM_H +#define ZVISION_LZSS_STREAM_H + +#include "common/stream.h" +#include "common/array.h" + + +namespace Common { +class SeekableReadStream; +} + +namespace ZVision { + +class LzssReadStream : public Common::ReadStream { +public: + /** + * A class that decompresses LZSS data and implements ReadStream for easy access + * to the decompiled data. + * + * @param source The source data + */ + LzssReadStream(Common::SeekableReadStream *source); + +private: + enum { + BLOCK_SIZE = 0x1000 + }; + +private: + Common::SeekableReadStream *_source; + byte _window[BLOCK_SIZE]; + uint _windowCursor; + bool _eosFlag; + +public: + bool eos() const; + uint32 read(void *dataPtr, uint32 dataSize); + +private: + /** + * Decompress the next <numberOfBytes> from the source stream. Or until EOS + * + * @param numberOfBytes How many bytes to decompress. This is a count of source bytes, not destination bytes + */ + uint32 decompressBytes(byte* destination, uint32 numberOfBytes); +}; + +} + +#endif diff --git a/engines/zvision/menu.h b/engines/zvision/menu.h new file mode 100644 index 0000000000..affc69abd5 --- /dev/null +++ b/engines/zvision/menu.h @@ -0,0 +1,28 @@ +/* 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 ZVISION_MENU_H +#define ZVISION_MENU_H + +// TODO: Implement MenuHandler + +#endif diff --git a/engines/zvision/module.mk b/engines/zvision/module.mk new file mode 100644 index 0000000000..261168f133 --- /dev/null +++ b/engines/zvision/module.mk @@ -0,0 +1,43 @@ +MODULE := engines/zvision + +MODULE_OBJS := \ + actions.o \ + animation_control.o \ + clock.o \ + console.o \ + control.o \ + cursor.o \ + cursor_manager.o \ + detection.o \ + events.o \ + input_control.o \ + lever_control.o \ + lzss_read_stream.o \ + push_toggle_control.o \ + render_manager.o \ + render_table.o \ + rlf_animation.o \ + save_manager.o \ + scr_file_handling.o \ + script_manager.o \ + single_value_container.o \ + string_manager.o \ + timer_node.o \ + truetype_font.o \ + utility.o \ + video.o \ + zvision.o \ + zfs_archive.o \ + zork_avi_decoder.o \ + zork_raw.o + +MODULE_DIRS += \ + engines/zvision + +# This module can be built as a plugin +ifeq ($(ENABLE_ZVISION), DYNAMIC_PLUGIN) +PLUGIN := 1 +endif + +# Include common rules +include $(srcdir)/rules.mk diff --git a/engines/zvision/push_toggle_control.cpp b/engines/zvision/push_toggle_control.cpp new file mode 100644 index 0000000000..689311ba5b --- /dev/null +++ b/engines/zvision/push_toggle_control.cpp @@ -0,0 +1,98 @@ +/* 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 "common/scummsys.h" + +#include "zvision/push_toggle_control.h" + +#include "zvision/zvision.h" +#include "zvision/script_manager.h" +#include "zvision/cursor_manager.h" +#include "zvision/utility.h" + +#include "common/stream.h" + + +namespace ZVision { + +PushToggleControl::PushToggleControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream) + : Control(engine, key) { + // Loop until we find the closing brace + Common::String line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + + while (!stream.eos() && !line.contains('}')) { + if (line.matchString("*_hotspot*", true)) { + uint x; + uint y; + uint width; + uint height; + + sscanf(line.c_str(), "%*[^(](%u,%u,%u,%u)", &x, &y, &width, &height); + + _hotspot = Common::Rect(x, y, x + width, y + height); + } else if (line.matchString("cursor*", true)) { + char nameBuffer[25]; + + sscanf(line.c_str(), "%*[^(](%25[^)])", nameBuffer); + + _hoverCursor = Common::String(nameBuffer); + } + + line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + } + + if (_hotspot.isEmpty() || _hoverCursor.empty()) { + warning("Push_toggle cursor %u was parsed incorrectly", key); + } +} + +PushToggleControl::~PushToggleControl() { + // Clear the state value back to 0 + _engine->getScriptManager()->setStateValue(_key, 0); +} + +void PushToggleControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (!_enabled) { + return; + } + + if (_hotspot.contains(backgroundImageSpacePos)) { + _engine->getScriptManager()->setStateValue(_key, 1); + } +} + +bool PushToggleControl::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (!_enabled) { + return false; + } + + if (_hotspot.contains(backgroundImageSpacePos)) { + _engine->getCursorManager()->changeCursor(_hoverCursor); + return true; + } + + return false; +} + +} // End of namespace ZVision diff --git a/engines/zvision/push_toggle_control.h b/engines/zvision/push_toggle_control.h new file mode 100644 index 0000000000..2a407cada9 --- /dev/null +++ b/engines/zvision/push_toggle_control.h @@ -0,0 +1,67 @@ +/* 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 ZVISION_PUSH_TOGGLE_CONTROL_H +#define ZVISION_PUSH_TOGGLE_CONTROL_H + +#include "zvision/control.h" + +#include "common/rect.h" + + +namespace ZVision { + +class PushToggleControl : public Control { +public: + PushToggleControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream); + ~PushToggleControl(); + + /** + * Called when LeftMouse is lifted. Calls ScriptManager::setStateValue(_key, 1); + * + * @param screenSpacePos The position of the mouse in screen space + * @param backgroundImageSpacePos The position of the mouse in background image space + */ + void onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + /** + * Called on every MouseMove. Tests if the mouse is inside _hotspot, and if so, sets the cursor. + * + * @param engine The base engine + * @param screenSpacePos The position of the mouse in screen space + * @param backgroundImageSpacePos The position of the mouse in background image space + * @return Was the cursor changed? + */ + bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + +private: + /** + * The area that will trigger the event + * This is in image space coordinates, NOT screen space + */ + Common::Rect _hotspot; + /** The cursor to use when hovering over _hotspot */ + Common::String _hoverCursor; +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/puzzle.h b/engines/zvision/puzzle.h new file mode 100644 index 0000000000..1e730365dc --- /dev/null +++ b/engines/zvision/puzzle.h @@ -0,0 +1,81 @@ +/* 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 ZVISION_PUZZLE_H +#define ZVISION_PUZZLE_H + +#include "zvision/actions.h" + +#include "common/list.h" +#include "common/ptr.h" + + +namespace ZVision { + +struct Puzzle { + Puzzle() : key(0), flags(0) {} + + ~Puzzle() { + for (Common::List<ResultAction *>::iterator iter = resultActions.begin(); iter != resultActions.end(); ++iter) { + delete *iter; + } + } + + /** How criteria should be decided */ + enum CriteriaOperator { + EQUAL_TO, + NOT_EQUAL_TO, + GREATER_THAN, + LESS_THAN + }; + + /** Criteria for a Puzzle result to be fired */ + struct CriteriaEntry { + /** The key of a global state */ + uint32 key; + /** + * What we're comparing the value of the global state against + * This can either be a pure value or it can be the key of another global state + */ + uint32 argument; + /** How to do the comparison */ + CriteriaOperator criteriaOperator; + /** Whether 'argument' is the key of a global state (true) or a pure value (false) */ + bool argumentIsAKey; + }; + + enum StateFlags { + ONCE_PER_INST = 0x01, + DO_ME_NOW = 0x02, // Somewhat useless flag since anything that needs to be done immediately has no criteria + DISABLED = 0x04 + }; + + uint32 key; + Common::List<Common::List <CriteriaEntry> > criteriaList; + // This has to be list of pointers because ResultAction is abstract + Common::List<ResultAction *> resultActions; + uint flags; +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/render_manager.cpp b/engines/zvision/render_manager.cpp new file mode 100644 index 0000000000..af8ca7fd64 --- /dev/null +++ b/engines/zvision/render_manager.cpp @@ -0,0 +1,526 @@ +/* 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 "common/scummsys.h" + +#include "zvision/render_manager.h" + +#include "zvision/lzss_read_stream.h" + +#include "common/file.h" +#include "common/system.h" +#include "common/stream.h" + +#include "engines/util.h" + +#include "graphics/decoders/tga.h" + + +namespace ZVision { + +RenderManager::RenderManager(OSystem *system, uint32 windowWidth, uint32 windowHeight, const Common::Rect workingWindow, const Graphics::PixelFormat pixelFormat) + : _system(system), + _workingWidth(workingWindow.width()), + _workingHeight(workingWindow.height()), + _screenCenterX(_workingWidth / 2), + _screenCenterY(_workingHeight / 2), + _workingWindow(workingWindow), + _pixelFormat(pixelFormat), + _backgroundWidth(0), + _backgroundHeight(0), + _backgroundInverseVelocity(0), + _backgroundOffset(0, 0), + _accumulatedVelocityMilliseconds(0), + _renderTable(_workingWidth, _workingHeight) { + + _workingWindowBuffer.create(_workingWidth, _workingHeight, _pixelFormat); + _backBuffer.create(windowWidth, windowHeight, pixelFormat); +} + +RenderManager::~RenderManager() { + _workingWindowBuffer.free(); + _currentBackground.free(); + _backBuffer.free(); + + for (AlphaEntryMap::iterator iter = _alphaDataEntries.begin(); iter != _alphaDataEntries.end(); ++iter) { + iter->_value.data->free(); + delete iter->_value.data; + } +} + +void RenderManager::update(uint deltaTimeInMillis) { + // An inverse velocity of 0 would be infinitely fast, so we'll let 0 mean no velocity. + if (_backgroundInverseVelocity != 0) { + _accumulatedVelocityMilliseconds += deltaTimeInMillis; + + uint absVelocity = uint(abs(_backgroundInverseVelocity)); + + int numberOfSteps = 0; + while (_accumulatedVelocityMilliseconds >= absVelocity) { + _accumulatedVelocityMilliseconds -= absVelocity; + numberOfSteps++; + } + + // Choose the direction of movement using the sign of the velocity + moveBackground(_backgroundInverseVelocity < 0 ? -numberOfSteps : numberOfSteps); + } +} + +void RenderManager::renderBackbufferToScreen() { + if (!_workingWindowDirtyRect.isEmpty()) { + RenderTable::RenderState state = _renderTable.getRenderState(); + if (state == RenderTable::PANORAMA || state == RenderTable::TILT) { + _renderTable.mutateImage((uint16 *)_workingWindowBuffer.getPixels(), (uint16 *)_backBuffer.getBasePtr(_workingWindow.left + _workingWindowDirtyRect.left, _workingWindow.top + _workingWindowDirtyRect.top), _backBuffer.w, _workingWindowDirtyRect); + } else { + _backBuffer.copyRectToSurface(_workingWindowBuffer.getBasePtr(_workingWindowDirtyRect.left, _workingWindowDirtyRect.top), _workingWindowBuffer.pitch, _workingWindow.left + _workingWindowDirtyRect.left, _workingWindow.top + _workingWindowDirtyRect.top, _workingWindowDirtyRect.width(), _workingWindowDirtyRect.height()); + } + + // Translate the working window dirty rect to screen coords + _workingWindowDirtyRect.translate(_workingWindow.left, _workingWindow.top); + // Then extend the backbuffer dirty rect to contain it + if (_backBufferDirtyRect.isEmpty()) { + _backBufferDirtyRect = _workingWindowDirtyRect; + } else { + _backBufferDirtyRect.extend(_workingWindowDirtyRect); + } + + // Clear the dirty rect + _workingWindowDirtyRect = Common::Rect(); + } + + // TODO: Add menu rendering + + // Render alpha entries + processAlphaEntries(); + + if (!_backBufferDirtyRect.isEmpty()) { + _system->copyRectToScreen(_backBuffer.getBasePtr(_backBufferDirtyRect.left, _backBufferDirtyRect.top), _backBuffer.pitch, _backBufferDirtyRect.left, _backBufferDirtyRect.top, _backBufferDirtyRect.width(), _backBufferDirtyRect.height()); + _backBufferDirtyRect = Common::Rect(); + } +} + +void RenderManager::processAlphaEntries() { + // TODO: Add dirty rectangling support. AKA only draw an entry if the _backbufferDirtyRect intersects/contains the entry Rect + + for (AlphaEntryMap::iterator iter = _alphaDataEntries.begin(); iter != _alphaDataEntries.end(); ++iter) { + uint32 destOffset = 0; + uint32 sourceOffset = 0; + uint16 *backbufferPtr = (uint16 *)_backBuffer.getBasePtr(iter->_value.destX + _workingWindow.left, iter->_value.destY + _workingWindow.top); + uint16 *entryPtr = (uint16 *)iter->_value.data->getPixels(); + + for (int32 y = 0; y < iter->_value.height; ++y) { + for (int32 x = 0; x < iter->_value.width; ++x) { + uint16 color = entryPtr[sourceOffset + x]; + if (color != iter->_value.alphaColor) { + backbufferPtr[destOffset + x] = color; + } + } + + destOffset += _backBuffer.w; + sourceOffset += iter->_value.width; + } + + if (_backBufferDirtyRect.isEmpty()) { + _backBufferDirtyRect = Common::Rect(iter->_value.destX + _workingWindow.left, iter->_value.destY + _workingWindow.top, iter->_value.destX + _workingWindow.left + iter->_value.width, iter->_value.destY + _workingWindow.top + iter->_value.height); + } else { + _backBufferDirtyRect.extend(Common::Rect(iter->_value.destX + _workingWindow.left, iter->_value.destY + _workingWindow.top, iter->_value.destX + _workingWindow.left + iter->_value.width, iter->_value.destY + _workingWindow.top + iter->_value.height)); + } + } +} + +void RenderManager::clearWorkingWindowTo555Color(uint16 color) { + uint32 workingWindowSize = _workingWidth * _workingHeight; + byte r, g, b; + Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0).colorToRGB(color, r, g, b); + uint16 colorIn565 = _pixelFormat.RGBToColor(r, g, b); + uint16 *bufferPtr = (uint16 *)_workingWindowBuffer.getPixels(); + + for (uint32 i = 0; i < workingWindowSize; ++i) { + bufferPtr[i] = colorIn565; + } +} + +void RenderManager::renderSubRectToScreen(Graphics::Surface &surface, int16 destinationX, int16 destinationY, bool wrap) { + int16 subRectX = 0; + int16 subRectY = 0; + + // Take care of negative destinations + if (destinationX < 0) { + subRectX = -destinationX; + destinationX = 0; + } else if (destinationX >= surface.w) { + // Take care of extreme positive destinations + destinationX -= surface.w; + } + + // Take care of negative destinations + if (destinationY < 0) { + subRectY = -destinationY; + destinationY = 0; + } else if (destinationY >= surface.h) { + // Take care of extreme positive destinations + destinationY -= surface.h; + } + + if (wrap) { + _backgroundWidth = surface.w; + _backgroundHeight = surface.h; + + if (destinationX > 0) { + // Move destinationX to 0 + subRectX = surface.w - destinationX; + destinationX = 0; + } + + if (destinationY > 0) { + // Move destinationY to 0 + subRectY = surface.h - destinationY; + destinationY = 0; + } + } + + // Clip subRect to working window bounds + Common::Rect subRect(subRectX, subRectY, subRectX + _workingWidth, subRectY + _workingHeight); + + if (!wrap) { + // Clip to image bounds + subRect.clip(surface.w, surface.h); + } + + // Check destRect for validity + if (!subRect.isValidRect() || subRect.isEmpty()) + return; + + copyRectToWorkingWindow((const uint16 *)surface.getBasePtr(subRect.left, subRect.top), destinationX, destinationY, surface.w, subRect.width(), subRect.height()); +} + +void RenderManager::renderImageToScreen(const Common::String &fileName, int16 destinationX, int16 destinationY, bool wrap) { + Graphics::Surface surface; + readImageToSurface(fileName, surface); + + renderSubRectToScreen(surface, destinationX, destinationY, wrap); +} + +void RenderManager::renderImageToScreen(Graphics::Surface &surface, int16 destinationX, int16 destinationY, bool wrap) { + renderSubRectToScreen(surface, destinationX, destinationY, wrap); +} + +void RenderManager::readImageToSurface(const Common::String &fileName, Graphics::Surface &destination) { + Common::File file; + + if (!file.open(fileName)) { + warning("Could not open file %s", fileName.c_str()); + return; + } + + // Read the magic number + // Some files are true TGA, while others are TGZ + uint32 fileType = file.readUint32BE(); + + uint32 imageWidth; + uint32 imageHeight; + Graphics::TGADecoder tga; + uint16 *buffer; + bool isTransposed = _renderTable.getRenderState() == RenderTable::PANORAMA; + // All ZVision images are in RGB 555 + Graphics::PixelFormat pixelFormat555 = Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0); + destination.format = pixelFormat555; + + bool isTGZ; + + // Check for TGZ files + if (fileType == MKTAG('T', 'G', 'Z', '\0')) { + isTGZ = true; + + // TGZ files have a header and then Bitmap data that is compressed with LZSS + uint32 decompressedSize = file.readSint32LE(); + imageWidth = file.readSint32LE(); + imageHeight = file.readSint32LE(); + + LzssReadStream lzssStream(&file); + buffer = (uint16 *)(new uint16[decompressedSize]); + lzssStream.read(buffer, decompressedSize); + } else { + isTGZ = false; + + // Reset the cursor + file.seek(0); + + // Decode + if (!tga.loadStream(file)) { + warning("Error while reading TGA image"); + return; + } + + Graphics::Surface tgaSurface = *(tga.getSurface()); + imageWidth = tgaSurface.w; + imageHeight = tgaSurface.h; + + buffer = (uint16 *)tgaSurface.getPixels(); + } + + // Flip the width and height if transposed + if (isTransposed) { + uint16 temp = imageHeight; + imageHeight = imageWidth; + imageWidth = temp; + } + + // If the destination internal buffer is the same size as what we're copying into it, + // there is no need to free() and re-create + if (imageWidth != destination.w || imageHeight != destination.h) { + destination.create(imageWidth, imageHeight, pixelFormat555); + } + + // If transposed, 'un-transpose' the data while copying it to the destination + // Otherwise, just do a simple copy + if (isTransposed) { + uint16 *dest = (uint16 *)destination.getPixels(); + + for (uint32 y = 0; y < imageHeight; ++y) { + uint32 columnIndex = y * imageWidth; + + for (uint32 x = 0; x < imageWidth; ++x) { + dest[columnIndex + x] = buffer[x * imageHeight + y]; + } + } + } else { + memcpy(destination.getPixels(), buffer, imageWidth * imageHeight * _pixelFormat.bytesPerPixel); + } + + // Cleanup + if (isTGZ) { + delete[] buffer; + } else { + tga.destroy(); + } + + // Convert in place to RGB 565 from RGB 555 + destination.convertToInPlace(_pixelFormat); +} + +void RenderManager::copyRectToWorkingWindow(const uint16 *buffer, int32 destX, int32 destY, int32 imageWidth, int32 width, int32 height) { + uint32 destOffset = 0; + uint32 sourceOffset = 0; + uint16 *workingWindowBufferPtr = (uint16 *)_workingWindowBuffer.getBasePtr(destX, destY); + + for (int32 y = 0; y < height; ++y) { + for (int32 x = 0; x < width; ++x) { + workingWindowBufferPtr[destOffset + x] = buffer[sourceOffset + x]; + } + + destOffset += _workingWidth; + sourceOffset += imageWidth; + } + + if (_workingWindowDirtyRect.isEmpty()) { + _workingWindowDirtyRect = Common::Rect(destX, destY, destX + width, destY + height); + } else { + _workingWindowDirtyRect.extend(Common::Rect(destX, destY, destX + width, destY + height)); + } + + // TODO: Remove this from release. It's here to make sure code that uses this function clips their destinations correctly + assert(_workingWindowDirtyRect.width() <= _workingWidth && _workingWindowDirtyRect.height() <= _workingHeight); +} + +void RenderManager::copyRectToWorkingWindow(const uint16 *buffer, int32 destX, int32 destY, int32 imageWidth, int32 width, int32 height, int16 alphaColor, uint32 idNumber) { + AlphaDataEntry entry; + entry.alphaColor = alphaColor; + entry.data = new Graphics::Surface(); + entry.data->create(width, height, _pixelFormat); + entry.destX = destX; + entry.destY = destY; + entry.width = width; + entry.height = height; + + uint32 sourceOffset = 0; + uint32 destOffset = 0; + uint16 *surfacePtr = (uint16 *)entry.data->getPixels(); + + for (int32 y = 0; y < height; ++y) { + for (int32 x = 0; x < width; ++x) { + surfacePtr[destOffset + x] = buffer[sourceOffset + x]; + } + + destOffset += width; + sourceOffset += imageWidth; + } + + _alphaDataEntries[idNumber] = entry; +} + +Common::Rect RenderManager::renderTextToWorkingWindow(uint32 idNumber, const Common::String &text, TruetypeFont *font, int destX, int destY, uint16 textColor, int maxWidth, int maxHeight, Graphics::TextAlign align, bool wrap) { + AlphaDataEntry entry; + entry.alphaColor = 0; + entry.destX = destX; + entry.destY = destY; + + // Draw the text to the working window + entry.data = font->drawTextToSurface(text, textColor, maxWidth, maxHeight, align, wrap); + entry.width = entry.data->w; + entry.height = entry.data->h; + + _alphaDataEntries[idNumber] = entry; + + return Common::Rect(destX, destY, destX + entry.width, destY + entry.height); +} + +const Common::Point RenderManager::screenSpaceToImageSpace(const Common::Point &point) { + // Convert from screen space to working window space + Common::Point newPoint(point - Common::Point(_workingWindow.left, _workingWindow.top)); + + RenderTable::RenderState state = _renderTable.getRenderState(); + if (state == RenderTable::PANORAMA || state == RenderTable::TILT) { + newPoint = _renderTable.convertWarpedCoordToFlatCoord(newPoint); + } + + if (state == RenderTable::PANORAMA) { + newPoint -= (Common::Point(_screenCenterX, 0) - _backgroundOffset); + } else if (state == RenderTable::TILT) { + newPoint -= (Common::Point(0, _screenCenterY) - _backgroundOffset); + } + + if (newPoint.x < 0) + newPoint.x += _backgroundWidth; + else if (newPoint.x >= _backgroundWidth) + newPoint.x -= _backgroundWidth; + if (newPoint.y < 0) + newPoint.y += _backgroundHeight; + else if (newPoint.y >= _backgroundHeight) + newPoint.y -= _backgroundHeight; + + return newPoint; +} + +const Common::Point RenderManager::imageSpaceToWorkingWindowSpace(const Common::Point &point) { + Common::Point newPoint(point); + + RenderTable::RenderState state = _renderTable.getRenderState(); + if (state == RenderTable::PANORAMA) { + newPoint += (Common::Point(_screenCenterX, 0) - _backgroundOffset); + } else if (state == RenderTable::TILT) { + newPoint += (Common::Point(0, _screenCenterY) - _backgroundOffset); + } + + return newPoint; +} + +bool RenderManager::clipRectToWorkingWindow(Common::Rect &rect) { + if (!_workingWindow.contains(rect)) { + return false; + } + + // We can't clip against the actual working window rect because it's in screen space + // But rect is in working window space + rect.clip(_workingWidth, _workingHeight); + return true; +} + +RenderTable *RenderManager::getRenderTable() { + return &_renderTable; +} + +void RenderManager::setBackgroundImage(const Common::String &fileName) { + readImageToSurface(fileName, _currentBackground); + + moveBackground(0); +} + +void RenderManager::setBackgroundPosition(int offset) { + RenderTable::RenderState state = _renderTable.getRenderState(); + if (state == RenderTable::TILT) { + _backgroundOffset.x = 0; + _backgroundOffset.y = offset; + } else if (state == RenderTable::PANORAMA) { + _backgroundOffset.x = offset; + _backgroundOffset.y = 0; + } else { + _backgroundOffset.x = 0; + _backgroundOffset.y = 0; + } +} + +void RenderManager::setBackgroundVelocity(int velocity) { + // setBackgroundVelocity(0) will be called quite often, so make sure + // _backgroundInverseVelocity isn't already 0 to prevent an extraneous assignment + if (velocity == 0) { + if (_backgroundInverseVelocity != 0) { + _backgroundInverseVelocity = 0; + } + } else { + _backgroundInverseVelocity = 1000 / velocity; + } +} + +void RenderManager::moveBackground(int offset) { + RenderTable::RenderState state = _renderTable.getRenderState(); + if (state == RenderTable::TILT) { + _backgroundOffset += Common::Point(0, offset); + + _backgroundOffset.y = CLIP<int16>(_backgroundOffset.y, _screenCenterY, (int16)_backgroundHeight - _screenCenterY); + + renderImageToScreen(_currentBackground, 0, _screenCenterY - _backgroundOffset.y, true); + } else if (state == RenderTable::PANORAMA) { + _backgroundOffset += Common::Point(offset, 0); + + if (_backgroundOffset.x <= -_backgroundWidth) + _backgroundOffset.x += _backgroundWidth; + else if (_backgroundOffset.x >= _backgroundWidth) + _backgroundOffset.x -= _backgroundWidth; + + renderImageToScreen(_currentBackground, _screenCenterX - _backgroundOffset.x, 0, true); + } else { + renderImageToScreen(_currentBackground, 0, 0); + } +} + +uint32 RenderManager::getCurrentBackgroundOffset() { + RenderTable::RenderState state = _renderTable.getRenderState(); + + if (state == RenderTable::PANORAMA) { + return _backgroundOffset.x; + } else if (state == RenderTable::TILT) { + return _backgroundOffset.y; + } else { + return 0; + } +} + +Graphics::Surface *RenderManager::tranposeSurface(const Graphics::Surface *surface) { + Graphics::Surface *tranposedSurface = new Graphics::Surface(); + tranposedSurface->create(surface->h, surface->w, surface->format); + + const uint16 *source = (const uint16 *)surface->getPixels(); + uint16 *dest = (uint16 *)tranposedSurface->getPixels(); + + for (uint32 y = 0; y < tranposedSurface->h; ++y) { + uint32 columnIndex = y * tranposedSurface->w; + + for (uint32 x = 0; x < tranposedSurface->w; ++x) { + dest[columnIndex + x] = source[x * surface->w + y]; + } + } + + return tranposedSurface; +} + +} // End of namespace ZVision diff --git a/engines/zvision/render_manager.h b/engines/zvision/render_manager.h new file mode 100644 index 0000000000..111bf6276c --- /dev/null +++ b/engines/zvision/render_manager.h @@ -0,0 +1,328 @@ +/* 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 ZVISION_RENDER_MANAGER_H +#define ZVISION_RENDER_MANAGER_H + +#include "zvision/render_table.h" +#include "zvision/truetype_font.h" + +#include "common/rect.h" +#include "common/hashmap.h" + +#include "graphics/surface.h" + + +class OSystem; + +namespace Common { +class String; +class SeekableReadStream; +} + +namespace Video { +class VideoDecoder; +} + +namespace ZVision { + +class RenderManager { +public: + RenderManager(OSystem *system, uint32 windowWidth, uint32 windowHeight, const Common::Rect workingWindow, const Graphics::PixelFormat pixelFormat); + ~RenderManager(); + +private: + struct AlphaDataEntry { + Graphics::Surface *data; + uint16 alphaColor; + uint16 destX; + uint16 destY; + uint16 width; + uint16 height; + }; + + typedef Common::HashMap<uint32, AlphaDataEntry> AlphaEntryMap; + +private: + OSystem *_system; + const Graphics::PixelFormat _pixelFormat; + + // A buffer the exact same size as the workingWindow + // This buffer stores everything un-warped, then does a warp at the end of the frame + Graphics::Surface _workingWindowBuffer; + // A buffer representing the entire screen. Any graphical updates are first done with this buffer + // before actually being blitted to the screen + Graphics::Surface _backBuffer; + // A list of Alpha Entries that need to be blitted to the backbuffer + AlphaEntryMap _alphaDataEntries; + + // A rectangle representing the portion of the working window where the pixels have been changed since last frame + Common::Rect _workingWindowDirtyRect; + // A rectangle representing the portion of the backbuffer where the pixels have been changed since last frame + Common::Rect _backBufferDirtyRect; + + /** Width of the working window. Saved to prevent extraneous calls to _workingWindow.width() */ + const int _workingWidth; + /** Height of the working window. Saved to prevent extraneous calls to _workingWindow.height() */ + const int _workingHeight; + /** Center of the screen in the x direction */ + const int _screenCenterX; + /** Center of the screen in the y direction */ + const int _screenCenterY; + + /** + * A Rectangle centered inside the actual window. All in-game coordinates + * are given in this coordinate space. Also, all images are clipped to the + * edges of this Rectangle + */ + const Common::Rect _workingWindow; + /** Used to warp the background image */ + RenderTable _renderTable; + + Graphics::Surface _currentBackground; + /** The (x1,y1) coordinates of the subRectangle of the background that is currently displayed on the screen */ + Common::Point _backgroundOffset; + /** The width of the current background image */ + uint16 _backgroundWidth; + /** The height of the current background image */ + uint16 _backgroundHeight; + + /** + * The "velocity" at which the background image is panning. We actually store the inverse of velocity (ms/pixel instead of pixels/ms) + * because it allows you to accumulate whole pixels 'steps' instead of rounding pixels every frame + */ + int _backgroundInverseVelocity; + /** Holds any 'leftover' milliseconds between frames */ + uint _accumulatedVelocityMilliseconds; + +public: + void initialize(); + /** + * Rotates the background image in accordance to the current _backgroundInverseVelocity + * + * @param deltaTimeInMillis The amount of time that has passed since the last frame + */ + void update(uint deltaTimeInMillis); + + /** + * Renders the current state of the backbuffer to the screen + */ + void renderBackbufferToScreen(); + + /** + * Renders all AlphaEntries to the backbuffer + */ + void processAlphaEntries(); + /** + * Clears the AlphaEntry list + */ + void clearAlphaEntries() { _alphaDataEntries.clear(); } + /** + * Removes a specific AlphaEntry from the list + * + * @param idNumber The id number identifing the AlphaEntry + */ + void removeAlphaEntry(uint32 idNumber) { _alphaDataEntries.erase(idNumber); } + + /** + * Copies a sub-rectangle of a buffer to the working window + * + * @param buffer The pixel data to copy to the working window + * @param destX The X destination in the working window where the subRect of data should be put + * @param destY The Y destination in the working window where the subRect of data should be put + * @param imageWidth The width of the source image + * @param width The width of the sub rectangle + * @param height The height of the sub rectangle + */ + void copyRectToWorkingWindow(const uint16 *buffer, int32 destX, int32 destY, int32 imageWidth, int32 width, int32 height); + /** + * Copies a sub-rectangle of a buffer to the working window with binary alpha support. + * + * @param buffer The pixel data to copy to the working window + * @param destX The X destination in the working window where the subRect of data should be put + * @param destY The Y destination in the working window where the subRect of data should be put + * @param imageWidth The width of the source image + * @param width The width of the sub rectangle + * @param height The height of the sub rectangle + * @param alphaColor The color to interpret as meaning 'transparent' + * @param idNumber A unique identifier for the data being copied over. + */ + void copyRectToWorkingWindow(const uint16 *buffer, int32 destX, int32 destY, int32 imageWidth, int32 width, int32 height, int16 alphaColor, uint32 idNumber); + + /** + * Renders the supplied text to the working window + * + * @param idNumber A unique identifier for the text + * @param text The text to be rendered + * @param font The font to use to render the text + * @param destX The X destination in the working window where the text should be rendered + * @param destY The Y destination in the working window where the text should be rendered + * @param textColor The color to render the text with (in RBG 565) + * @param maxWidth The max width the text should take up. + * @param maxHeight The max height the text should take up. + * @param align The alignment of the text within the bounds of maxWidth + * @param wrap If true, any words extending past maxWidth will wrap to a new line. If false, ellipses will be rendered to show that the text didn't fit + * @return A rectangle representing where the text was drawn in the working window + */ + Common::Rect renderTextToWorkingWindow(uint32 idNumber, const Common::String &text, TruetypeFont *font, int destX, int destY, uint16 textColor, int maxWidth, int maxHeight = -1, Graphics::TextAlign align = Graphics::kTextAlignLeft, bool wrap = true); + + /** + * Fills the entire workingWindow with the specified color. Internally, the color + * will be converted to RGB 565 and then blitted. + * + * @param color The color to fill the working window with. (In RGB 555) + */ + void clearWorkingWindowTo555Color(uint16 color); + + /** + * Blits the image or a portion of the image to the backbuffer. Actual screen updates won't happen until the end of the frame. + * The image will be clipped to fit inside the working window. Coords are in working window space, not screen space! + * + * @param fileName Name of the image file + * @param destinationX X position where the image should be put. Coords are in working window space, not screen space! + * @param destinationY Y position where the image should be put. Coords are in working window space, not screen space! + */ + void renderImageToScreen(const Common::String &fileName, int16 destinationX, int16 destinationY, bool wrap = false); + + /** + * Blits the image or a portion of the image to the backbuffer. Actual screen updates won't happen until the end of the frame. + * The image will be clipped to fit inside the working window. Coords are in working window space, not screen space! + * + * @param stream Surface to read the image data from + * @param destinationX X position where the image should be put. Coords are in working window space, not screen space! + * @param destinationY Y position where the image should be put. Coords are in working window space, not screen space! + */ + void renderImageToScreen(Graphics::Surface &surface, int16 destinationX, int16 destinationY, bool wrap = false); + + /** + * Sets the current background image to be used by the RenderManager and immediately + * blits it to the screen. (It won't show up until the end of the frame) + * + * @param fileName The name of the image file + */ + void setBackgroundImage(const Common::String &fileName); + + /** + * Set the background position (_backgroundOffset). If the current RenderState is PANORAMA, the offset + * will be in the horizontal direction. If the current RenderState is TILT, the offset will be in the + * vertical direction. + * + * This method will not render anything on the screen. So if nothing else is called that renders the + * background, the change won't be seen until next frame. + * + * @param offset The amount to offset the background + */ + void setBackgroundPosition(int offset); + + /** + * Set the background scroll velocity. Negative velocities correspond to left / up scrolling and + * positive velocities correspond to right / down scrolling + * + * @param velocity Velocity + */ + void setBackgroundVelocity(int velocity); + + /** + * Converts a point in screen coordinate space to image coordinate space + * + * @param point Point in screen coordinate space + * @return Point in image coordinate space + */ + const Common::Point screenSpaceToImageSpace(const Common::Point &point); + /** + * Converts a point in image coordinate space to ***PRE-WARP*** + * working window coordinate space + * + * @param point Point in image coordinate space + * @return Point in PRE-WARP working window coordinate space + */ + const Common::Point imageSpaceToWorkingWindowSpace(const Common::Point &point); + + /** + * Clip a rectangle to the working window. If it returns false, the original rect + * is not inside the working window. + * + * @param rect The rectangle to clip against the working window + * @return Is rect at least partially inside the working window (true) or completely outside (false) + */ + bool clipRectToWorkingWindow(Common::Rect &rect); + + RenderTable *getRenderTable(); + uint32 getCurrentBackgroundOffset(); + const Graphics::Surface *getBackBuffer() { return &_backBuffer; } + + /** + * Creates a copy of surface and transposes the data. + * + * Note: The user is responsible for calling free() on the returned surface + * and then deleting it + * + * @param surface The data to be transposed + * @return A copy of the surface with the data transposed + */ + static Graphics::Surface *tranposeSurface(const Graphics::Surface *surface); + +private: + /** + * Renders a subRectangle of an image to the backbuffer. The destinationRect and SubRect + * will be clipped to image bound and to working window bounds + * + * @param buffer Pointer to (0, 0) of the image data + * @param imageWidth The width of the original image (not of the subRectangle) + * @param imageHeight The width of the original image (not of the subRectangle) + * @param horizontalPitch The horizontal pitch of the original image + * @param destinationX The x coordinate (in working window space) of where to put the final image + * @param destinationY The y coordinate (in working window space) of where to put the final image + * @param subRectangle A rectangle representing the part of the image that should be rendered + * @param wrap Should the image wrap (tile) if it doesn't completely fill the screen? + */ + void renderSubRectToScreen(Graphics::Surface &surface, int16 destinationX, int16 destinationY, bool wrap); + + /** + * Reads an image file pixel data into a Surface buffer. In the process + * it converts the pixel data from RGB 555 to RGB 565. Also, if the image + * is transposed, it will un-transpose the pixel data. The function will + * call destination::create() if the dimensions of destination do not match + * up with the dimensions of the image. + * + * @param fileName The name of a .tga file + * @param destination A reference to the Surface to store the pixel data in + */ + void readImageToSurface(const Common::String &fileName, Graphics::Surface &destination); + + /** + * Move the background image by an offset. If we are currently in Panorama mode, + * the offset will correspond to a horizontal motion. If we are currently in Tilt mode, + * the offset will correspond to a vertical motion. This function should not be called + * if we are in Flat mode. + * + * The RenderManager will take care of wrapping the image. + * Ex: If the image has width 1400px, it is legal to offset 1500px. + * + * @param offset The amount to move the background + */ + void moveBackground(int offset); +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/render_table.cpp b/engines/zvision/render_table.cpp new file mode 100644 index 0000000000..b6a6a3d2bb --- /dev/null +++ b/engines/zvision/render_table.cpp @@ -0,0 +1,240 @@ +/* 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 "common/scummsys.h" + +#include "zvision/render_table.h" + +#include "common/rect.h" + +#include "graphics/colormasks.h" + + +namespace ZVision { + +RenderTable::RenderTable(uint numColumns, uint numRows) + : _numRows(numRows), + _numColumns(numColumns), + _renderState(FLAT) { + assert(numRows != 0 && numColumns != 0); + + _internalBuffer = new Common::Point[numRows * numColumns]; +} + +RenderTable::~RenderTable() { + delete[] _internalBuffer; +} + +void RenderTable::setRenderState(RenderState newState) { + _renderState = newState; + + switch (newState) { + case PANORAMA: + _panoramaOptions.fieldOfView = 27.0f; + _panoramaOptions.linearScale = 0.55f; + _panoramaOptions.reverse = false; + break; + case TILT: + _tiltOptions.fieldOfView = 27.0f; + _tiltOptions.linearScale = 0.55f; + _tiltOptions.reverse = false; + break; + case FLAT: + // Intentionally left empty + break; + } +} + +const Common::Point RenderTable::convertWarpedCoordToFlatCoord(const Common::Point &point) { + // If we're outside the range of the RenderTable, no warping is happening. Return the maximum image coords + if (point.x >= (int16)_numColumns || point.y >= (int16)_numRows || point.x < 0 || point.y < 0) { + int16 x = CLIP<int16>(point.x, 0, (int16)_numColumns); + int16 y = CLIP<int16>(point.y, 0, (int16)_numRows); + return Common::Point(x, y); + } + + uint32 index = point.y * _numColumns + point.x; + + Common::Point newPoint(point); + newPoint.x += _internalBuffer[index].x; + newPoint.y += _internalBuffer[index].y; + + return newPoint; +} + +uint16 mixTwoRGB(uint16 colorOne, uint16 colorTwo, float percentColorOne) { + assert(percentColorOne < 1.0f); + + float rOne = float((colorOne & Graphics::ColorMasks<555>::kRedMask) >> Graphics::ColorMasks<555>::kRedShift); + float rTwo = float((colorTwo & Graphics::ColorMasks<555>::kRedMask) >> Graphics::ColorMasks<555>::kRedShift); + float gOne = float((colorOne & Graphics::ColorMasks<555>::kGreenMask) >> Graphics::ColorMasks<555>::kGreenShift); + float gTwo = float((colorTwo & Graphics::ColorMasks<555>::kGreenMask) >> Graphics::ColorMasks<555>::kGreenShift); + float bOne = float((colorOne & Graphics::ColorMasks<555>::kBlueMask) >> Graphics::ColorMasks<555>::kBlueShift); + float bTwo = float((colorTwo & Graphics::ColorMasks<555>::kBlueMask) >> Graphics::ColorMasks<555>::kBlueShift); + + float rFinal = rOne * percentColorOne + rTwo * (1.0f - percentColorOne); + float gFinal = gOne * percentColorOne + gTwo * (1.0f - percentColorOne); + float bFinal = bOne * percentColorOne + bTwo * (1.0f - percentColorOne); + + uint16 returnColor = (byte(rFinal + 0.5f) << Graphics::ColorMasks<555>::kRedShift) | + (byte(gFinal + 0.5f) << Graphics::ColorMasks<555>::kGreenShift) | + (byte(bFinal + 0.5f) << Graphics::ColorMasks<555>::kBlueShift); + + return returnColor; +} + +void RenderTable::mutateImage(uint16 *sourceBuffer, uint16* destBuffer, uint32 destWidth, const Common::Rect &subRect) { + uint32 destOffset = 0; + + for (int16 y = subRect.top; y < subRect.bottom; ++y) { + uint32 sourceOffset = y * _numColumns; + + for (int16 x = subRect.left; x < subRect.right; ++x) { + uint32 normalizedX = x - subRect.left; + uint32 index = sourceOffset + x; + + // RenderTable only stores offsets from the original coordinates + uint32 sourceYIndex = y + _internalBuffer[index].y; + uint32 sourceXIndex = x + _internalBuffer[index].x; + + destBuffer[destOffset + normalizedX] = sourceBuffer[sourceYIndex * _numColumns + sourceXIndex]; + } + + destOffset += destWidth; + } +} + +void RenderTable::generateRenderTable() { + switch (_renderState) { + case ZVision::RenderTable::PANORAMA: + generatePanoramaLookupTable(); + break; + case ZVision::RenderTable::TILT: + generateTiltLookupTable(); + break; + case ZVision::RenderTable::FLAT: + // Intentionally left empty + break; + } +} + +void RenderTable::generatePanoramaLookupTable() { + memset(_internalBuffer, 0, _numRows * _numColumns * sizeof(uint16)); + + float halfWidth = (float)_numColumns / 2.0f; + float halfHeight = (float)_numRows / 2.0f; + + float fovInRadians = (_panoramaOptions.fieldOfView * M_PI / 180.0f); + float cylinderRadius = halfHeight / tan(fovInRadians); + + for (uint x = 0; x < _numColumns; ++x) { + // Add an offset of 0.01 to overcome zero tan/atan issue (vertical line on half of screen) + // Alpha represents the horizontal angle between the viewer at the center of a cylinder and x + float alpha = atan(((float)x - halfWidth + 0.01f) / cylinderRadius); + + // To get x in cylinder coordinates, we just need to calculate the arc length + // We also scale it by _panoramaOptions.linearScale + int32 xInCylinderCoords = int32(floor((cylinderRadius * _panoramaOptions.linearScale * alpha) + halfWidth)); + + float cosAlpha = cos(alpha); + + for (uint y = 0; y < _numRows; ++y) { + // To calculate y in cylinder coordinates, we can do similar triangles comparison, + // comparing the triangle from the center to the screen and from the center to the edge of the cylinder + int32 yInCylinderCoords = int32(floor(halfHeight + ((float)y - halfHeight) * cosAlpha)); + + uint32 index = y * _numColumns + x; + + // Only store the (x,y) offsets instead of the absolute positions + _internalBuffer[index].x = xInCylinderCoords - x; + _internalBuffer[index].y = yInCylinderCoords - y; + } + } +} + +void RenderTable::generateTiltLookupTable() { + float halfWidth = (float)_numColumns / 2.0f; + float halfHeight = (float)_numRows / 2.0f; + + float fovInRadians = (_tiltOptions.fieldOfView * M_PI / 180.0f); + float cylinderRadius = halfWidth / tan(fovInRadians); + + for (uint y = 0; y < _numRows; ++y) { + + // Add an offset of 0.01 to overcome zero tan/atan issue (horizontal line on half of screen) + // Alpha represents the vertical angle between the viewer at the center of a cylinder and y + float alpha = atan(((float)y - halfHeight + 0.01f) / cylinderRadius); + + // To get y in cylinder coordinates, we just need to calculate the arc length + // We also scale it by _tiltOptions.linearScale + int32 yInCylinderCoords = int32(floor((cylinderRadius * _tiltOptions.linearScale * alpha) + halfHeight)); + + float cosAlpha = cos(alpha); + uint32 columnIndex = y * _numColumns; + + for (uint x = 0; x < _numColumns; ++x) { + // To calculate x in cylinder coordinates, we can do similar triangles comparison, + // comparing the triangle from the center to the screen and from the center to the edge of the cylinder + int32 xInCylinderCoords = int32(floor(halfWidth + ((float)x - halfWidth) * cosAlpha)); + + uint32 index = columnIndex + x; + + // Only store the (x,y) offsets instead of the absolute positions + _internalBuffer[index].x = xInCylinderCoords - x; + _internalBuffer[index].y = yInCylinderCoords - y; + } + } +} + +void RenderTable::setPanoramaFoV(float fov) { + assert(fov > 0.0f); + + _panoramaOptions.fieldOfView = fov; +} + +void RenderTable::setPanoramaScale(float scale) { + assert(scale > 0.0f); + + _panoramaOptions.linearScale = scale; +} + +void RenderTable::setPanoramaReverse(bool reverse) { + _panoramaOptions.reverse = reverse; +} + +void RenderTable::setTiltFoV(float fov) { + assert(fov > 0.0f); + + _tiltOptions.fieldOfView = fov; +} + +void RenderTable::setTiltScale(float scale) { + assert(scale > 0.0f); + + _tiltOptions.linearScale = scale; +} + +void RenderTable::setTiltReverse(bool reverse) { + _tiltOptions.reverse = reverse; +} + +} // End of namespace ZVision diff --git a/engines/zvision/render_table.h b/engines/zvision/render_table.h new file mode 100644 index 0000000000..898091193a --- /dev/null +++ b/engines/zvision/render_table.h @@ -0,0 +1,85 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ZVISION_RENDER_TABLE_H +#define ZVISION_RENDER_TABLE_H + +#include "common/rect.h" + + +namespace ZVision { + +class RenderTable { +public: + RenderTable(uint numRows, uint numColumns); + ~RenderTable(); + +public: + enum RenderState { + PANORAMA, + TILT, + FLAT + }; + +private: + uint _numColumns, _numRows; + Common::Point *_internalBuffer; + RenderState _renderState; + + struct { + float fieldOfView; + float linearScale; + bool reverse; + } _panoramaOptions; + + // TODO: See if tilt and panorama need to have separate options + struct { + float fieldOfView; + float linearScale; + bool reverse; + } _tiltOptions; + +public: + RenderState getRenderState() { return _renderState; } + void setRenderState(RenderState newState); + + const Common::Point convertWarpedCoordToFlatCoord(const Common::Point &point); + + void mutateImage(uint16 *sourceBuffer, uint16* destBuffer, uint32 destWidth, const Common::Rect &subRect); + void generateRenderTable(); + + void setPanoramaFoV(float fov); + void setPanoramaScale(float scale); + void setPanoramaReverse(bool reverse); + + void setTiltFoV(float fov); + void setTiltScale(float scale); + void setTiltReverse(bool reverse); + +private: + void generatePanoramaLookupTable(); + void generateTiltLookupTable(); +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/rlf_animation.cpp b/engines/zvision/rlf_animation.cpp new file mode 100644 index 0000000000..5f1d41daae --- /dev/null +++ b/engines/zvision/rlf_animation.cpp @@ -0,0 +1,331 @@ +/* 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 "common/scummsys.h" + +#include "zvision/rlf_animation.h" + +#include "common/str.h" +#include "common/file.h" +#include "common/textconsole.h" +#include "common/debug.h" +#include "common/endian.h" + +#include "graphics/colormasks.h" + + +namespace ZVision { + +RlfAnimation::RlfAnimation(const Common::String &fileName, bool stream) + : _stream(stream), + _lastFrameRead(0), + _frameCount(0), + _width(0), + _height(0), + _frameTime(0), + _frames(0), + _currentFrame(-1), + _frameBufferByteSize(0) { + if (!_file.open(fileName)) { + warning("RLF animation file %s could not be opened", fileName.c_str()); + return; + } + + if (!readHeader()) { + warning("%s is not a RLF animation file. Wrong magic number", fileName.c_str()); + return; + } + + _currentFrameBuffer.create(_width, _height, Graphics::createPixelFormat<565>()); + _frameBufferByteSize = _width * _height * sizeof(uint16); + + if (!stream) { + _frames = new Frame[_frameCount]; + + // Read in each frame + for (uint i = 0; i < _frameCount; ++i) { + _frames[i] = readNextFrame(); + } + } +} + +RlfAnimation::~RlfAnimation() { + for (uint i = 0; i < _frameCount; ++i) { + delete[] _frames[i].encodedData; + } + delete[] _frames; + _currentFrameBuffer.free(); +} + +bool RlfAnimation::readHeader() { + if (_file.readUint32BE() != MKTAG('F', 'E', 'L', 'R')) { + return false; + } + + // Read the header + _file.readUint32LE(); // Size1 + _file.readUint32LE(); // Unknown1 + _file.readUint32LE(); // Unknown2 + _frameCount = _file.readUint32LE(); // Frame count + + // Since we don't need any of the data, we can just seek right to the + // entries we need rather than read in all the individual entries. + _file.seek(136, SEEK_CUR); + + //// Read CIN header + //_file.readUint32BE(); // Magic number FNIC + //_file.readUint32LE(); // Size2 + //_file.readUint32LE(); // Unknown3 + //_file.readUint32LE(); // Unknown4 + //_file.readUint32LE(); // Unknown5 + //_file.seek(0x18, SEEK_CUR); // VRLE + //_file.readUint32LE(); // LRVD + //_file.readUint32LE(); // Unknown6 + //_file.seek(0x18, SEEK_CUR); // HRLE + //_file.readUint32LE(); // ELHD + //_file.readUint32LE(); // Unknown7 + //_file.seek(0x18, SEEK_CUR); // HKEY + //_file.readUint32LE(); // ELRH + + //// Read MIN info header + //_file.readUint32BE(); // Magic number FNIM + //_file.readUint32LE(); // Size3 + //_file.readUint32LE(); // OEDV + //_file.readUint32LE(); // Unknown8 + //_file.readUint32LE(); // Unknown9 + //_file.readUint32LE(); // Unknown10 + _width = _file.readUint32LE(); // Width + _height = _file.readUint32LE(); // Height + + // Read time header + _file.readUint32BE(); // Magic number EMIT + _file.readUint32LE(); // Size4 + _file.readUint32LE(); // Unknown11 + _frameTime = _file.readUint32LE() / 10; // Frame time in microseconds + + return true; +} + +RlfAnimation::Frame RlfAnimation::readNextFrame() { + RlfAnimation::Frame frame; + + _file.readUint32BE(); // Magic number MARF + uint32 size = _file.readUint32LE(); // Size + _file.readUint32LE(); // Unknown1 + _file.readUint32LE(); // Unknown2 + uint32 type = _file.readUint32BE(); // Either ELHD or ELRH + uint32 headerSize = _file.readUint32LE(); // Offset from the beginning of this frame to the frame data. Should always be 28 + _file.readUint32LE(); // Unknown3 + + frame.encodedSize = size - headerSize; + frame.encodedData = new int8[frame.encodedSize]; + _file.read(frame.encodedData, frame.encodedSize); + + if (type == MKTAG('E', 'L', 'H', 'D')) { + frame.type = Masked; + } else if (type == MKTAG('E', 'L', 'R', 'H')) { + frame.type = Simple; + _completeFrames.push_back(_lastFrameRead); + } else { + warning("Frame %u doesn't have type that can be decoded", _lastFrameRead); + } + + _lastFrameRead++; + return frame; +} + +void RlfAnimation::seekToFrame(int frameNumber) { + assert(!_stream); + assert(frameNumber < (int)_frameCount || frameNumber >= -1); + + if (frameNumber == -1) { + _currentFrame = -1; + return; + } + + int closestFrame = _currentFrame; + int distance = (int)frameNumber - _currentFrame; + for (uint i = 0; i < _completeFrames.size(); ++i) { + int newDistance = (int)frameNumber - (int)(_completeFrames[i]); + if (newDistance > 0 && (closestFrame == -1 || newDistance < distance)) { + closestFrame = _completeFrames[i]; + distance = newDistance; + } + } + + for (int i = closestFrame; i <= frameNumber; ++i) { + applyFrameToCurrent(i); + } + + _currentFrame = frameNumber; +} + +const Graphics::Surface *RlfAnimation::getFrameData(uint frameNumber) { + assert(!_stream); + assert(frameNumber < _frameCount); + + // Since this method is so expensive, first check to see if we can use + // getNextFrame() it's cheap. + if ((int)frameNumber == _currentFrame) { + return &_currentFrameBuffer; + } else if (_currentFrame + 1 == (int)frameNumber) { + return getNextFrame(); + } + + seekToFrame(frameNumber); + return &_currentFrameBuffer; +} + +const Graphics::Surface *RlfAnimation::getNextFrame() { + assert(_currentFrame + 1 < (int)_frameCount); + + if (_stream) { + applyFrameToCurrent(readNextFrame()); + } else { + applyFrameToCurrent(_currentFrame + 1); + } + + _currentFrame++; + return &_currentFrameBuffer; +} + +void RlfAnimation::applyFrameToCurrent(uint frameNumber) { + if (_frames[frameNumber].type == Masked) { + decodeMaskedRunLengthEncoding(_frames[frameNumber].encodedData, (int8 *)_currentFrameBuffer.getPixels(), _frames[frameNumber].encodedSize, _frameBufferByteSize); + } else if (_frames[frameNumber].type == Simple) { + decodeSimpleRunLengthEncoding(_frames[frameNumber].encodedData, (int8 *)_currentFrameBuffer.getPixels(), _frames[frameNumber].encodedSize, _frameBufferByteSize); + } +} + +void RlfAnimation::applyFrameToCurrent(const RlfAnimation::Frame &frame) { + if (frame.type == Masked) { + decodeMaskedRunLengthEncoding(frame.encodedData, (int8 *)_currentFrameBuffer.getPixels(), frame.encodedSize, _frameBufferByteSize); + } else if (frame.type == Simple) { + decodeSimpleRunLengthEncoding(frame.encodedData, (int8 *)_currentFrameBuffer.getPixels(), frame.encodedSize, _frameBufferByteSize); + } +} + +void RlfAnimation::decodeMaskedRunLengthEncoding(int8 *source, int8 *dest, uint32 sourceSize, uint32 destSize) const { + uint32 sourceOffset = 0; + uint32 destOffset = 0; + + while (sourceOffset < sourceSize) { + int8 numberOfSamples = source[sourceOffset]; + sourceOffset++; + + // If numberOfSamples is negative, the next abs(numberOfSamples) samples should + // be copied directly from source to dest + if (numberOfSamples < 0) { + numberOfSamples = ABS(numberOfSamples); + + while (numberOfSamples > 0) { + if (sourceOffset + 1 >= sourceSize) { + return; + } else if (destOffset + 1 >= destSize) { + debug(2, "Frame decoding overflow\n\tsourceOffset=%u\tsourceSize=%u\n\tdestOffset=%u\tdestSize=%u", sourceOffset, sourceSize, destOffset, destSize); + return; + } + + byte r, g, b; + Graphics::colorToRGB<Graphics::ColorMasks<555> >(READ_LE_UINT16(source + sourceOffset), r, g, b); + uint16 destColor = Graphics::RGBToColor<Graphics::ColorMasks<565> >(r, g, b); + WRITE_UINT16(dest + destOffset, destColor); + + sourceOffset += 2; + destOffset += 2; + numberOfSamples--; + } + + // If numberOfSamples is >= 0, move destOffset forward ((numberOfSamples * 2) + 2) + // This function assumes the dest buffer has been memset with 0's. + } else { + if (sourceOffset + 1 >= sourceSize) { + return; + } else if (destOffset + 1 >= destSize) { + debug(2, "Frame decoding overflow\n\tsourceOffset=%u\tsourceSize=%u\n\tdestOffset=%u\tdestSize=%u", sourceOffset, sourceSize, destOffset, destSize); + return; + } + + destOffset += (numberOfSamples * 2) + 2; + } + } +} + +void RlfAnimation::decodeSimpleRunLengthEncoding(int8 *source, int8 *dest, uint32 sourceSize, uint32 destSize) const { + uint32 sourceOffset = 0; + uint32 destOffset = 0; + + while (sourceOffset < sourceSize) { + int8 numberOfSamples = source[sourceOffset]; + sourceOffset++; + + // If numberOfSamples is negative, the next abs(numberOfSamples) samples should + // be copied directly from source to dest + if (numberOfSamples < 0) { + numberOfSamples = ABS(numberOfSamples); + + while (numberOfSamples > 0) { + if (sourceOffset + 1 >= sourceSize) { + return; + } else if (destOffset + 1 >= destSize) { + debug(2, "Frame decoding overflow\n\tsourceOffset=%u\tsourceSize=%u\n\tdestOffset=%u\tdestSize=%u", sourceOffset, sourceSize, destOffset, destSize); + return; + } + + byte r, g, b; + Graphics::colorToRGB<Graphics::ColorMasks<555> >(READ_LE_UINT16(source + sourceOffset), r, g, b); + uint16 destColor = Graphics::RGBToColor<Graphics::ColorMasks<565> >(r, g, b); + WRITE_UINT16(dest + destOffset, destColor); + + sourceOffset += 2; + destOffset += 2; + numberOfSamples--; + } + + // If numberOfSamples is >= 0, copy one sample from source to the + // next (numberOfSamples + 2) dest spots + } else { + if (sourceOffset + 1 >= sourceSize) { + return; + } + + byte r, g, b; + Graphics::colorToRGB<Graphics::ColorMasks<555> >(READ_LE_UINT16(source + sourceOffset), r, g, b); + uint16 sampleColor = Graphics::RGBToColor<Graphics::ColorMasks<565> >(r, g, b); + sourceOffset += 2; + + numberOfSamples += 2; + while (numberOfSamples > 0) { + if (destOffset + 1 >= destSize) { + debug(2, "Frame decoding overflow\n\tsourceOffset=%u\tsourceSize=%u\n\tdestOffset=%u\tdestSize=%u", sourceOffset, sourceSize, destOffset, destSize); + return; + } + + WRITE_UINT16(dest + destOffset, sampleColor); + destOffset += 2; + numberOfSamples--; + } + } + } +} + +} // End of namespace ZVision diff --git a/engines/zvision/rlf_animation.h b/engines/zvision/rlf_animation.h new file mode 100644 index 0000000000..fe5b0d68b4 --- /dev/null +++ b/engines/zvision/rlf_animation.h @@ -0,0 +1,163 @@ +/* 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 ZVISION_RLF_ANIMATION_H +#define ZVISION_RLF_ANIMATION_H + +#include "common/file.h" + +#include "graphics/surface.h" + + +namespace Common { +class String; +} + +namespace ZVision { + +class RlfAnimation { +public: + RlfAnimation(const Common::String &fileName, bool stream = true); + ~RlfAnimation(); + +private: + enum EncodingType { + Masked, + Simple + }; + + struct Frame { + EncodingType type; + int8 *encodedData; + uint32 encodedSize; + }; + +private: + Common::File _file; + bool _stream; + uint _lastFrameRead; + + uint _frameCount; + uint _width; + uint _height; + uint32 _frameTime; // In milliseconds + Frame *_frames; + Common::Array<uint> _completeFrames; + + int _currentFrame; + Graphics::Surface _currentFrameBuffer; + uint32 _frameBufferByteSize; + +public: + uint frameCount() { return _frameCount; } + uint width() { return _width; } + uint height() { return _height; } + uint32 frameTime() { return _frameTime; } + + /** + * Seeks to the frameNumber and updates the internal Surface with + * the new frame data. If frameNumber == -1, it only sets _currentFrame, + * the internal Surface is unchanged. This function requires _stream = false + * + * @param frameNumber The frame number to seek to + */ + void seekToFrame(int frameNumber); + + /** + * Returns the pixel data of the frame specified. It will try to use + * getNextFrame() if possible. If not, it uses seekToFrame() to + * update the internal Surface and then returns a pointer to it. + * This function requires _stream = false + * + * @param frameNumber The frame number to get data for + * @return A pointer to the pixel data. Do NOT delete this. + */ + const Graphics::Surface *getFrameData(uint frameNumber); + /** + * Returns the pixel data of the next frame. It is up to the user to + * check if the next frame is valid before calling this. + * IE. Use endOfAnimation() + * + * @return A pointer to the pixel data. Do NOT delete this. + */ + const Graphics::Surface *getNextFrame(); + + /** + * @return Is the currentFrame is the last frame in the animation? + */ + bool endOfAnimation() { return _currentFrame == (int)_frameCount - 1; } + +private: + /** + * Reads in the header of the RLF file + * + * @return Will return false if the header magic number is wrong + */ + bool readHeader(); + /** + * Reads the next frame from the RLF file, stores the data in + * a Frame object, then returns the object + * + * @return A Frame object representing the frame data + */ + Frame readNextFrame(); + + /** + * Applies the frame corresponding to frameNumber on top of _currentFrameBuffer. + * This function requires _stream = false so it can look up the Frame object + * referenced by frameNumber. + * + * @param frameNumber The frame number to apply to _currentFrameBuffer + */ + void applyFrameToCurrent(uint frameNumber); + /** + * Applies the data from a Frame object on top of a _currentFrameBuffer. + * + * @param frame A Frame object to apply to _currentFrameBuffer + */ + void applyFrameToCurrent(const RlfAnimation::Frame &frame); + + /** + * Decode frame data that uses masked run length encoding. This is the encoding + * used by P-frames. + * + * @param source The source pixel data + * @param dest The destination buffer + * @param sourceSize The size of the source pixel data + * @param destSize The size of the destination buffer + */ + void decodeMaskedRunLengthEncoding(int8 *source, int8 *dest, uint32 sourceSize, uint32 destSize) const; + /** + * Decode frame data that uses simple run length encoding. This is the encoding + * used by I-frames. + * + * @param source The source pixel data + * @param dest The destination buffer + * @param sourceSize The size of the source pixel data + * @param destSize The size of the destination buffer + */ + void decodeSimpleRunLengthEncoding(int8 *source, int8 *dest, uint32 sourceSize, uint32 destSize) const; +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/save_manager.cpp b/engines/zvision/save_manager.cpp new file mode 100644 index 0000000000..c3deadd703 --- /dev/null +++ b/engines/zvision/save_manager.cpp @@ -0,0 +1,206 @@ +/* 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 "common/scummsys.h" + +#include "zvision/save_manager.h" + +#include "zvision/zvision.h" +#include "zvision/script_manager.h" +#include "zvision/render_manager.h" + +#include "common/system.h" + +#include "graphics/surface.h" +#include "graphics/thumbnail.h" + +#include "gui/message.h" + + +namespace ZVision { + +const uint32 SaveManager::SAVEGAME_ID = MKTAG('Z', 'E', 'N', 'G'); + +void SaveManager::saveGame(uint slot, const Common::String &saveName) { + // The games only support 20 slots + assert(slot <= 1 && slot <= 20); + + Common::SaveFileManager *saveFileManager = g_system->getSavefileManager(); + Common::OutSaveFile *file = saveFileManager->openForSaving(_engine->generateSaveFileName(slot)); + + // Write out the savegame header + file->writeUint32BE(SAVEGAME_ID); + + // Write version + file->writeByte(SAVE_VERSION); + + // Write savegame name + file->writeString(saveName); + file->writeByte(0); + + // We can't call writeGameSaveData because the save menu is actually + // a room, so writeGameSaveData would save us in the save menu. + // However, an auto save is performed before each room change, so we + // can copy the data from there. We can guarantee that an auto save file will + // exist before this is called because the save menu can only be accessed + // after the first room (the main menu) has loaded. + Common::InSaveFile *autoSaveFile = saveFileManager->openForLoading(_engine->generateAutoSaveFileName()); + + // Skip over the header info + autoSaveFile->readSint32BE(); // SAVEGAME_ID + autoSaveFile->readByte(); // Version + autoSaveFile->seek(5, SEEK_CUR); // The string "auto" with terminating NULL + + // Read the rest to a buffer + uint32 size = autoSaveFile->size() - autoSaveFile->pos(); + byte *buffer = new byte[size]; + autoSaveFile->read(buffer, size); + + // Then write the buffer to the new file + file->write(buffer, size); + + // Cleanup + delete[] buffer; + file->finalize(); + delete file; +} + +void SaveManager::autoSave() { + Common::OutSaveFile *file = g_system->getSavefileManager()->openForSaving(_engine->generateAutoSaveFileName()); + + // Write out the savegame header + file->writeUint32BE(SAVEGAME_ID); + + // Version + file->writeByte(SAVE_VERSION); + + file->writeString("auto"); + file->writeByte(0); + + writeSaveGameData(file); + + // Cleanup + file->finalize(); + delete file; +} + +void SaveManager::writeSaveGameData(Common::OutSaveFile *file) { + // Create a thumbnail and save it + Graphics::saveThumbnail(*file); + + // Write out the save date/time + TimeDate td; + g_system->getTimeAndDate(td); + file->writeSint16LE(td.tm_year + 1900); + file->writeSint16LE(td.tm_mon + 1); + file->writeSint16LE(td.tm_mday); + file->writeSint16LE(td.tm_hour); + file->writeSint16LE(td.tm_min); + + ScriptManager *scriptManager = _engine->getScriptManager(); + // Write out the current location + Location currentLocation = scriptManager->getCurrentLocation(); + file->writeByte(currentLocation.world); + file->writeByte(currentLocation.room); + file->writeByte(currentLocation.node); + file->writeByte(currentLocation.view); + file->writeUint32LE(currentLocation.offset); + + // Write out the current state table values + scriptManager->serializeStateTable(file); + + // Write out any controls needing to save state + scriptManager->serializeControls(file); +} + +Common::Error SaveManager::loadGame(uint slot) { + // The games only support 20 slots + assert(slot <= 1 && slot <= 20); + + Common::InSaveFile *saveFile = g_system->getSavefileManager()->openForLoading(_engine->generateSaveFileName(slot)); + if (saveFile == 0) { + return Common::kPathDoesNotExist; + } + + // Read the header + SaveGameHeader header; + if (!readSaveGameHeader(saveFile, header)) { + return Common::kUnknownError; + } + + char world = (char)saveFile->readByte(); + char room = (char)saveFile->readByte(); + char node = (char)saveFile->readByte(); + char view = (char)saveFile->readByte(); + uint32 offset = (char)saveFile->readUint32LE(); + + ScriptManager *scriptManager = _engine->getScriptManager(); + // Update the state table values + scriptManager->deserializeStateTable(saveFile); + + // Load the room + scriptManager->changeLocation(world, room, node, view, offset); + + // Update the controls + scriptManager->deserializeControls(saveFile); + + return Common::kNoError; +} + +bool SaveManager::readSaveGameHeader(Common::InSaveFile *in, SaveGameHeader &header) { + if (in->readUint32BE() != SAVEGAME_ID) { + warning("File is not a ZVision save file. Aborting load"); + return false; + } + + // Read in the version + header.version = in->readByte(); + + // Check that the save version isn't newer than this binary + if (header.version > SAVE_VERSION) { + uint tempVersion = header.version; + GUI::MessageDialog dialog(Common::String::format("This save file uses version %u, but this engine only supports up to version %d. You will need an updated version of the engine to use this save file.", tempVersion, SAVE_VERSION), "OK"); + dialog.runModal(); + } + + // Read in the save name + 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; +} + +} // End of namespace ZVision diff --git a/engines/zvision/save_manager.h b/engines/zvision/save_manager.h new file mode 100644 index 0000000000..b4770e68b2 --- /dev/null +++ b/engines/zvision/save_manager.h @@ -0,0 +1,91 @@ +/* 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 ZVISION_SAVE_MANAGER_H +#define ZVISION_SAVE_MANAGER_H + +#include "common/savefile.h" + +namespace Common { +class String; +} + +namespace Graphics { +struct Surface; +} + +namespace ZVision { + +class ZVision; + +struct SaveGameHeader { + byte version; + Common::String saveName; + Graphics::Surface *thumbnail; + int saveYear, saveMonth, saveDay; + int saveHour, saveMinutes; +}; + +class SaveManager { +public: + SaveManager(ZVision *engine) : _engine(engine) {} + +private: + ZVision *_engine; + static const uint32 SAVEGAME_ID; + + enum { + SAVE_VERSION = 1 + }; + +public: + /** + * Called every room change. Saves the state of the room just before + * we switched rooms. Uses ZVision::generateAutoSaveFileName() to + * create the save file name. + */ + void autoSave(); + /** + * Copies the data from the last auto-save into a new save file. We + * can't use the current state data because the save menu *IS* a room. + * The file is named using ZVision::generateSaveFileName(slot) + * + * @param slot The save slot this save pertains to. Must be [1, 20] + * @param saveName The internal name for this save. This is NOT the name of the actual save file. + */ + void saveGame(uint slot, const Common::String &saveName); + /** + * Loads the state data from the save file that slot references. Uses + * ZVision::generateSaveFileName(slot) to get the save file name. + * + * @param slot The save slot to load. Must be [1, 20] + */ + Common::Error loadGame(uint slot); + +private: + void writeSaveGameData(Common::OutSaveFile *file); + bool readSaveGameHeader(Common::InSaveFile *in, SaveGameHeader &header); +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scr_file_handling.cpp b/engines/zvision/scr_file_handling.cpp new file mode 100644 index 0000000000..e90408cf0d --- /dev/null +++ b/engines/zvision/scr_file_handling.cpp @@ -0,0 +1,302 @@ +/* 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 "common/scummsys.h" + +#include "zvision/script_manager.h" + +#include "zvision/utility.h" +#include "zvision/puzzle.h" +#include "zvision/actions.h" +#include "zvision/push_toggle_control.h" +#include "zvision/lever_control.h" + +#include "common/textconsole.h" +#include "common/file.h" +#include "common/tokenizer.h" + + +namespace ZVision { + +void ScriptManager::parseScrFile(const Common::String &fileName, bool isGlobal) { + Common::File file; + if (!file.open(fileName)) { + warning("Script file not found: %s", fileName.c_str()); + return; + } + + while(!file.eos()) { + Common::String line = file.readLine(); + if (file.err()) { + warning("Error parsing scr file: %s", fileName.c_str()); + return; + } + + trimCommentsAndWhiteSpace(&line); + if (line.empty()) + continue; + + if (line.matchString("puzzle:*", true)) { + Puzzle *puzzle = new Puzzle(); + sscanf(line.c_str(),"puzzle:%u",&(puzzle->key)); + + parsePuzzle(puzzle, file); + if (isGlobal) { + _globalPuzzles.push_back(puzzle); + } else { + _activePuzzles.push_back(puzzle); + } + } else if (line.matchString("control:*", true)) { + parseControl(line, file); + } + } +} + +void ScriptManager::parsePuzzle(Puzzle *puzzle, Common::SeekableReadStream &stream) { + Common::String line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + + while (!stream.eos() && !line.contains('}')) { + if (line.matchString("criteria {", true)) { + parseCriteria(stream, puzzle->criteriaList); + } else if (line.matchString("results {", true)) { + parseResults(stream, puzzle->resultActions); + } else if (line.matchString("flags {", true)) { + puzzle->flags = parseFlags(stream); + } + + line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + } +} + +bool ScriptManager::parseCriteria(Common::SeekableReadStream &stream, Common::List<Common::List<Puzzle::CriteriaEntry> > &criteriaList) const { + // Loop until we find the closing brace + Common::String line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + + // Criteria can be empty + if (line.contains('}')) { + return false; + } + + // Create a new List to hold the CriteriaEntries + criteriaList.push_back(Common::List<Puzzle::CriteriaEntry>()); + + while (!stream.eos() && !line.contains('}')) { + Puzzle::CriteriaEntry entry; + + // Split the string into tokens using ' ' as a delimiter + Common::StringTokenizer tokenizer(line); + Common::String token; + + // Parse the id out of the first token + token = tokenizer.nextToken(); + sscanf(token.c_str(), "[%u]", &(entry.key)); + + // Parse the operator out of the second token + token = tokenizer.nextToken(); + if (token.c_str()[0] == '=') + entry.criteriaOperator = Puzzle::EQUAL_TO; + else if (token.c_str()[0] == '!') + entry.criteriaOperator = Puzzle::NOT_EQUAL_TO; + else if (token.c_str()[0] == '>') + entry.criteriaOperator = Puzzle::GREATER_THAN; + else if (token.c_str()[0] == '<') + entry.criteriaOperator = Puzzle::LESS_THAN; + + // First determine if the last token is an id or a value + // Then parse it into 'argument' + token = tokenizer.nextToken(); + if (token.contains('[')) { + sscanf(token.c_str(), "[%u]", &(entry.argument)); + entry.argumentIsAKey = true; + } else { + sscanf(token.c_str(), "%u", &(entry.argument)); + entry.argumentIsAKey = false; + } + + criteriaList.back().push_back(entry); + + line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + } + + return true; +} + +void ScriptManager::parseResults(Common::SeekableReadStream &stream, Common::List<ResultAction *> &actionList) const { + // Loop until we find the closing brace + Common::String line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + + // TODO: Re-order the if-then statements in order of highest occurrence + while (!stream.eos() && !line.contains('}')) { + if (line.empty()) { + line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + + continue; + } + + // Parse for the action type + if (line.matchString("*:add*", true)) { + actionList.push_back(new ActionAdd(line)); + } else if (line.matchString("*:animplay*", true)) { + actionList.push_back(new ActionPlayAnimation(line)); + } else if (line.matchString("*:animpreload*", true)) { + actionList.push_back(new ActionPreloadAnimation(line)); + } else if (line.matchString("*:animunload*", true)) { + //actionList.push_back(new ActionUnloadAnimation(line)); + } else if (line.matchString("*:attenuate*", true)) { + // TODO: Implement ActionAttenuate + } else if (line.matchString("*:assign*", true)) { + actionList.push_back(new ActionAssign(line)); + } else if (line.matchString("*:change_location*", true)) { + actionList.push_back(new ActionChangeLocation(line)); + } else if (line.matchString("*:crossfade*", true)) { + // TODO: Implement ActionCrossfade + } else if (line.matchString("*:debug*", true)) { + // TODO: Implement ActionDebug + } else if (line.matchString("*:delay_render*", true)) { + // TODO: Implement ActionDelayRender + } else if (line.matchString("*:disable_control*", true)) { + actionList.push_back(new ActionDisableControl(line)); + } else if (line.matchString("*:disable_venus*", true)) { + // TODO: Implement ActionDisableVenus + } else if (line.matchString("*:display_message*", true)) { + // TODO: Implement ActionDisplayMessage + } else if (line.matchString("*:dissolve*", true)) { + // TODO: Implement ActionDissolve + } else if (line.matchString("*:distort*", true)) { + // TODO: Implement ActionDistort + } else if (line.matchString("*:enable_control*", true)) { + actionList.push_back(new ActionEnableControl(line)); + } else if (line.matchString("*:flush_mouse_events*", true)) { + // TODO: Implement ActionFlushMouseEvents + } else if (line.matchString("*:inventory*", true)) { + // TODO: Implement ActionInventory + } else if (line.matchString("*:kill*", true)) { + // TODO: Implement ActionKill + } else if (line.matchString("*:menu_bar_enable*", true)) { + // TODO: Implement ActionMenuBarEnable + } else if (line.matchString("*:music*", true)) { + actionList.push_back(new ActionMusic(line)); + } else if (line.matchString("*:pan_track*", true)) { + // TODO: Implement ActionPanTrack + } else if (line.matchString("*:playpreload*", true)) { + actionList.push_back(new ActionPlayPreloadAnimation(line)); + } else if (line.matchString("*:preferences*", true)) { + // TODO: Implement ActionPreferences + } else if (line.matchString("*:quit*", true)) { + actionList.push_back(new ActionQuit()); + } else if (line.matchString("*:random*", true)) { + actionList.push_back(new ActionRandom(line)); + } else if (line.matchString("*:region*", true)) { + // TODO: Implement ActionRegion + } else if (line.matchString("*:restore_game*", true)) { + // TODO: Implement ActionRestoreGame + } else if (line.matchString("*:rotate_to*", true)) { + // TODO: Implement ActionRotateTo + } else if (line.matchString("*:save_game*", true)) { + // TODO: Implement ActionSaveGame + } else if (line.matchString("*:set_partial_screen*", true)) { + actionList.push_back(new ActionSetPartialScreen(line)); + } else if (line.matchString("*:set_screen*", true)) { + actionList.push_back(new ActionSetScreen(line)); + } else if (line.matchString("*:set_venus*", true)) { + // TODO: Implement ActionSetVenus + } else if (line.matchString("*:stop*", true)) { + // TODO: Implement ActionStop + } else if (line.matchString("*:streamvideo*", true)) { + actionList.push_back(new ActionStreamVideo(line)); + } else if (line.matchString("*:syncsound*", true)) { + // TODO: Implement ActionSyncSound + } else if (line.matchString("*:timer*", true)) { + actionList.push_back(new ActionTimer(line)); + } else if (line.matchString("*:ttytext*", true)) { + // TODO: Implement ActionTTYText + } else if (line.matchString("*:universe_music*", true)) { + // TODO: Implement ActionUniverseMusic + } else if (line.matchString("*:copy_file*", true)) { + // Not used. Purposely left empty + } else { + warning("Unhandled result action type: %s", line.c_str()); + } + + line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + } + + return; +} + +uint ScriptManager::parseFlags(Common::SeekableReadStream &stream) const { + uint flags = 0; + + // Loop until we find the closing brace + Common::String line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + + while (!stream.eos() && !line.contains('}')) { + if (line.matchString("ONCE_PER_INST", true)) { + flags |= Puzzle::ONCE_PER_INST; + } else if (line.matchString("DO_ME_NOW", true)) { + flags |= Puzzle::DO_ME_NOW; + } else if (line.matchString("DISABLED", true)) { + flags |= Puzzle::DISABLED; + } + + line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + } + + return flags; +} + +void ScriptManager::parseControl(Common::String &line, Common::SeekableReadStream &stream) { + uint32 key; + char controlTypeBuffer[20]; + + sscanf(line.c_str(), "control:%u %s {", &key, controlTypeBuffer); + + Common::String controlType(controlTypeBuffer); + + if (controlType.equalsIgnoreCase("push_toggle")) { + _activeControls.push_back(new PushToggleControl(_engine, key, stream)); + return; + } else if (controlType.equalsIgnoreCase("flat")) { + Control::parseFlatControl(_engine); + return; + } else if (controlType.equalsIgnoreCase("pana")) { + Control::parsePanoramaControl(_engine, stream); + return; + } else if (controlType.equalsIgnoreCase("tilt")) { + Control::parseTiltControl(_engine, stream); + return; + } else if (controlType.equalsIgnoreCase("lever")) { + _activeControls.push_back(new LeverControl(_engine, key, stream)); + return; + } +} + +} // End of namespace ZVision diff --git a/engines/zvision/script_manager.cpp b/engines/zvision/script_manager.cpp new file mode 100644 index 0000000000..66a54088fd --- /dev/null +++ b/engines/zvision/script_manager.cpp @@ -0,0 +1,446 @@ +/* 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 "common/scummsys.h" + +#include "zvision/script_manager.h" + +#include "zvision/zvision.h" +#include "zvision/render_manager.h" +#include "zvision/cursor_manager.h" +#include "zvision/save_manager.h" +#include "zvision/actions.h" +#include "zvision/utility.h" + +#include "common/algorithm.h" +#include "common/hashmap.h" +#include "common/debug.h" +#include "common/stream.h" + + +namespace ZVision { + +ScriptManager::ScriptManager(ZVision *engine) + : _engine(engine), + _currentlyFocusedControl(0) { +} + +ScriptManager::~ScriptManager() { + for (PuzzleList::iterator iter = _activePuzzles.begin(); iter != _activePuzzles.end(); ++iter) { + delete (*iter); + } + for (PuzzleList::iterator iter = _globalPuzzles.begin(); iter != _globalPuzzles.end(); ++iter) { + delete (*iter); + } + for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) { + delete (*iter); + } +} + +void ScriptManager::initialize() { + parseScrFile("universe.scr", true); + changeLocation('g', 'a', 'r', 'y', 0); +} + +void ScriptManager::update(uint deltaTimeMillis) { + updateNodes(deltaTimeMillis); + checkPuzzleCriteria(); +} + +void ScriptManager::createReferenceTable() { + // Iterate through each local Puzzle + for (PuzzleList::iterator activePuzzleIter = _activePuzzles.begin(); activePuzzleIter != _activePuzzles.end(); ++activePuzzleIter) { + Puzzle *puzzlePtr = (*activePuzzleIter); + + // Iterate through each CriteriaEntry and add a reference from the criteria key to the Puzzle + for (Common::List<Common::List<Puzzle::CriteriaEntry> >::iterator criteriaIter = (*activePuzzleIter)->criteriaList.begin(); criteriaIter != (*activePuzzleIter)->criteriaList.end(); ++criteriaIter) { + for (Common::List<Puzzle::CriteriaEntry>::iterator entryIter = criteriaIter->begin(); entryIter != criteriaIter->end(); ++entryIter) { + _referenceTable[entryIter->key].push_back(puzzlePtr); + + // If the argument is a key, add a reference to it as well + if (entryIter->argumentIsAKey) { + _referenceTable[entryIter->argument].push_back(puzzlePtr); + } + } + } + } + + // Iterate through each global Puzzle + for (PuzzleList::iterator globalPuzzleIter = _globalPuzzles.begin(); globalPuzzleIter != _globalPuzzles.end(); ++globalPuzzleIter) { + Puzzle *puzzlePtr = (*globalPuzzleIter); + + // Iterate through each CriteriaEntry and add a reference from the criteria key to the Puzzle + for (Common::List<Common::List<Puzzle::CriteriaEntry> >::iterator criteriaIter = (*globalPuzzleIter)->criteriaList.begin(); criteriaIter != (*globalPuzzleIter)->criteriaList.end(); ++criteriaIter) { + for (Common::List<Puzzle::CriteriaEntry>::iterator entryIter = criteriaIter->begin(); entryIter != criteriaIter->end(); ++entryIter) { + _referenceTable[entryIter->key].push_back(puzzlePtr); + + // If the argument is a key, add a reference to it as well + if (entryIter->argumentIsAKey) { + _referenceTable[entryIter->argument].push_back(puzzlePtr); + } + } + } + } + + // Remove duplicate entries + for (PuzzleMap::iterator referenceTableIter = _referenceTable.begin(); referenceTableIter != _referenceTable.end(); ++referenceTableIter) { + removeDuplicateEntries(referenceTableIter->_value); + } +} + +void ScriptManager::updateNodes(uint deltaTimeMillis) { + // If process() returns true, it means the node can be deleted + for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end();) { + if ((*iter)->process(deltaTimeMillis)) { + delete (*iter); + // Remove the node + iter = _activeControls.erase(iter); + } else { + ++iter; + } + } +} + +void ScriptManager::checkPuzzleCriteria() { + while (!_puzzlesToCheck.empty()) { + Puzzle *puzzle = _puzzlesToCheck.pop(); + + // Check if the puzzle is already finished + // Also check that the puzzle isn't disabled + if (getStateValue(puzzle->key) == 1 && + (puzzle->flags & Puzzle::DISABLED) == 0) { + continue; + } + + // Check each Criteria + + bool criteriaMet = false; + for (Common::List<Common::List<Puzzle::CriteriaEntry> >::iterator criteriaIter = puzzle->criteriaList.begin(); criteriaIter != puzzle->criteriaList.end(); ++criteriaIter) { + criteriaMet = false; + + for (Common::List<Puzzle::CriteriaEntry>::iterator entryIter = criteriaIter->begin(); entryIter != criteriaIter->end(); ++entryIter) { + // Get the value to compare against + uint argumentValue; + if (entryIter->argumentIsAKey) + argumentValue = getStateValue(entryIter->argument); + else + argumentValue = entryIter->argument; + + // Do the comparison + switch (entryIter->criteriaOperator) { + case Puzzle::EQUAL_TO: + criteriaMet = getStateValue(entryIter->key) == argumentValue; + break; + case Puzzle::NOT_EQUAL_TO: + criteriaMet = getStateValue(entryIter->key) != argumentValue; + break; + case Puzzle::GREATER_THAN: + criteriaMet = getStateValue(entryIter->key) > argumentValue; + break; + case Puzzle::LESS_THAN: + criteriaMet = getStateValue(entryIter->key) < argumentValue; + break; + } + + // If one check returns false, don't keep checking + if (!criteriaMet) { + break; + } + } + + // If any of the Criteria are *fully* met, then execute the results + if (criteriaMet) { + break; + } + } + + // criteriaList can be empty. Aka, the puzzle should be executed immediately + if (puzzle->criteriaList.empty() || criteriaMet) { + debug(1, "Puzzle %u criteria passed. Executing its ResultActions", puzzle->key); + + // Set the puzzle as completed + setStateValue(puzzle->key, 1); + + bool shouldContinue = true; + for (Common::List<ResultAction *>::iterator resultIter = puzzle->resultActions.begin(); resultIter != puzzle->resultActions.end(); ++resultIter) { + shouldContinue = shouldContinue && (*resultIter)->execute(_engine); + if (!shouldContinue) { + break; + } + } + + if (!shouldContinue) { + break; + } + } + } +} + +void ScriptManager::cleanStateTable() { + for (StateMap::iterator iter = _globalState.begin(); iter != _globalState.end(); ++iter) { + // If the value is equal to zero, we can purge it since getStateValue() + // will return zero if _globalState doesn't contain a key + if (iter->_value == 0) { + // Remove the node + _globalState.erase(iter); + } + } +} + +uint ScriptManager::getStateValue(uint32 key) { + if (_globalState.contains(key)) + return _globalState[key]; + else + return 0; +} + +void ScriptManager::setStateValue(uint32 key, uint value) { + _globalState[key] = value; + + if (_referenceTable.contains(key)) { + for (Common::Array<Puzzle *>::iterator iter = _referenceTable[key].begin(); iter != _referenceTable[key].end(); ++iter) { + _puzzlesToCheck.push((*iter)); + } + } +} + +void ScriptManager::addToStateValue(uint32 key, uint valueToAdd) { + _globalState[key] += valueToAdd; +} + +void ScriptManager::addControl(Control *control) { + _activeControls.push_back(control); +} + +Control *ScriptManager::getControl(uint32 key) { + for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) { + if ((*iter)->getKey() == key) { + return (*iter); + } + } + + return nullptr; +} + +void ScriptManager::enableControl(uint32 key) { + for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) { + if ((*iter)->getKey() == key) { + (*iter)->enable(); + break; + } + } +} + +void ScriptManager::disableControl(uint32 key) { + for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) { + if ((*iter)->getKey() == key) { + (*iter)->disable(); + break; + } + } +} + +void ScriptManager::focusControl(uint32 key) { + for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) { + uint32 controlKey = (*iter)->getKey(); + + if (controlKey == key) { + (*iter)->focus(); + } else if (controlKey == _currentlyFocusedControl) { + (*iter)->unfocus(); + } + } + + _currentlyFocusedControl = key; +} + +void ScriptManager::onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) { + (*iter)->onMouseDown(screenSpacePos, backgroundImageSpacePos); + } +} + +void ScriptManager::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) { + (*iter)->onMouseUp(screenSpacePos, backgroundImageSpacePos); + } +} + +bool ScriptManager::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + bool cursorWasChanged = false; + for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) { + cursorWasChanged = cursorWasChanged || (*iter)->onMouseMove(screenSpacePos, backgroundImageSpacePos); + } + + return cursorWasChanged; +} + +void ScriptManager::onKeyDown(Common::KeyState keyState) { + for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) { + (*iter)->onKeyDown(keyState); + } +} + +void ScriptManager::onKeyUp(Common::KeyState keyState) { + for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) { + (*iter)->onKeyUp(keyState); + } +} + +void ScriptManager::changeLocation(char world, char room, char node, char view, uint32 offset) { + assert(world != 0); + debug(1, "Changing location to: %c %c %c %c %u", world, room, node, view, offset); + + // Auto save + _engine->getSaveManager()->autoSave(); + + // Clear all the containers + _referenceTable.clear(); + _puzzlesToCheck.clear(); + for (PuzzleList::iterator iter = _activePuzzles.begin(); iter != _activePuzzles.end(); ++iter) { + delete (*iter); + } + _activePuzzles.clear(); + for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) { + delete (*iter); + } + _activeControls.clear(); + + // Revert to the idle cursor + _engine->getCursorManager()->revertToIdle(); + + // Reset the background velocity + _engine->getRenderManager()->setBackgroundVelocity(0); + + // Remove any alphaEntries + _engine->getRenderManager()->clearAlphaEntries(); + + // Clean the global state table + cleanStateTable(); + + // Parse into puzzles and controls + Common::String fileName = Common::String::format("%c%c%c%c.scr", world, room, node, view); + parseScrFile(fileName); + + // Change the background position + _engine->getRenderManager()->setBackgroundPosition(offset); + + // Enable all the controls + for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) { + (*iter)->enable(); + } + + // Add all the local puzzles to the queue to be checked + for (PuzzleList::iterator iter = _activePuzzles.begin(); iter != _activePuzzles.end(); ++iter) { + // Reset any Puzzles that have the flag ONCE_PER_INST + if (((*iter)->flags & Puzzle::ONCE_PER_INST) == Puzzle::ONCE_PER_INST) { + setStateValue((*iter)->key, 0); + } + + _puzzlesToCheck.push((*iter)); + } + + // Add all the global puzzles to the queue to be checked + for (PuzzleList::iterator iter = _globalPuzzles.begin(); iter != _globalPuzzles.end(); ++iter) { + // Reset any Puzzles that have the flag ONCE_PER_INST + if (((*iter)->flags & Puzzle::ONCE_PER_INST) == Puzzle::ONCE_PER_INST) { + setStateValue((*iter)->key, 0); + } + + _puzzlesToCheck.push((*iter)); + } + + // Create the puzzle reference table + createReferenceTable(); + + // Update _currentLocation + _currentLocation.world = world; + _currentLocation.room = room; + _currentLocation.node = node; + _currentLocation.view = view; + _currentLocation.offset = offset; +} + +void ScriptManager::serializeStateTable(Common::WriteStream *stream) { + // Write the number of state value entries + stream->writeUint32LE(_globalState.size()); + + for (StateMap::iterator iter = _globalState.begin(); iter != _globalState.end(); ++iter) { + // Write out the key/value pair + stream->writeUint32LE(iter->_key); + stream->writeUint32LE(iter->_value); + } +} + +void ScriptManager::deserializeStateTable(Common::SeekableReadStream *stream) { + // Clear out the current table values + _globalState.clear(); + + // Read the number of key/value pairs + uint32 numberOfPairs = stream->readUint32LE(); + + for (uint32 i = 0; i < numberOfPairs; ++i) { + uint32 key = stream->readUint32LE(); + uint32 value = stream->readUint32LE(); + // Directly access the state table so we don't trigger Puzzle checks + _globalState[key] = value; + } +} + +void ScriptManager::serializeControls(Common::WriteStream *stream) { + // Count how many controls need to save their data + // Because WriteStream isn't seekable + uint32 numberOfControlsNeedingSerialization = 0; + for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) { + if ((*iter)->needsSerialization()) { + numberOfControlsNeedingSerialization++; + } + } + stream->writeUint32LE(numberOfControlsNeedingSerialization); + + for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) { + (*iter)->serialize(stream); + } +} + +void ScriptManager::deserializeControls(Common::SeekableReadStream *stream) { + uint32 numberOfControls = stream->readUint32LE(); + + for (uint32 i = 0; i < numberOfControls; ++i) { + uint32 key = stream->readUint32LE(); + for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) { + if ((*iter)->getKey() == key) { + (*iter)->deserialize(stream); + break; + } + } + } +} + +Location ScriptManager::getCurrentLocation() const { + Location location = _currentLocation; + location.offset = _engine->getRenderManager()->getCurrentBackgroundOffset(); + + return location; +} + +} // End of namespace ZVision diff --git a/engines/zvision/script_manager.h b/engines/zvision/script_manager.h new file mode 100644 index 0000000000..388d0805e0 --- /dev/null +++ b/engines/zvision/script_manager.h @@ -0,0 +1,212 @@ +/* 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 ZVISION_SCRIPT_MANAGER_H +#define ZVISION_SCRIPT_MANAGER_H + +#include "zvision/puzzle.h" +#include "zvision/control.h" + +#include "common/hashmap.h" +#include "common/queue.h" + + +namespace Common { +class String; +class SeekableReadStream; +} + +namespace ZVision { + +class ZVision; + +struct Location { + Location() : world('g'), room('a'), node('r'), view('y'), offset(0) {} + + char world; + char room; + char node; + char view; + uint32 offset; +}; + +typedef Common::HashMap<uint32, Common::Array<Puzzle *> > PuzzleMap; +typedef Common::List<Puzzle *> PuzzleList; +typedef Common::Queue<Puzzle *> PuzzleQueue; +typedef Common::List<Control *> ControlList; +typedef Common::HashMap<uint32, uint32> StateMap; + +class ScriptManager { +public: + ScriptManager(ZVision *engine); + ~ScriptManager(); + +private: + ZVision *_engine; + /** + * Holds the global state variable. Do NOT directly modify this. Use the accessors and + * mutators getStateValue() and setStateValue(). This ensures that Puzzles that reference a + * particular state key are checked after the key is modified. + */ + StateMap _globalState; + /** References _globalState keys to Puzzles */ + PuzzleMap _referenceTable; + /** Holds the Puzzles that should be checked this frame */ + PuzzleQueue _puzzlesToCheck; + /** Holds the currently active puzzles */ + PuzzleList _activePuzzles; + /** Holds the global puzzles */ + PuzzleList _globalPuzzles; + /** Holds the currently active controls */ + ControlList _activeControls; + + Location _currentLocation; + + uint32 _currentlyFocusedControl; + +public: + void initialize(); + void update(uint deltaTimeMillis); + + uint getStateValue(uint32 key); + void setStateValue(uint32 key, uint value); + void addToStateValue(uint32 key, uint valueToAdd); + + void addControl(Control *control); + Control *getControl(uint32 key); + + void enableControl(uint32 key); + void disableControl(uint32 key); + + void focusControl(uint32 key); + + /** + * Called when LeftMouse is pushed. + * + * @param screenSpacePos The position of the mouse in screen space + * @param backgroundImageSpacePos The position of the mouse in background image space + */ + void onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + /** + * Called when LeftMouse is lifted. + * + * @param screenSpacePos The position of the mouse in screen space + * @param backgroundImageSpacePos The position of the mouse in background image space + */ + void onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + /** + * Called on every MouseMove. + * + * @param screenSpacePos The position of the mouse in screen space + * @param backgroundImageSpacePos The position of the mouse in background image space + * @return Was the cursor changed? + */ + bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + /** + * Called when a key is pressed. + * + * @param keycode The key that was pressed + */ + void onKeyDown(Common::KeyState keyState); + /** + * Called when a key is released. + * + * @param keycode The key that was pressed + */ + void onKeyUp(Common::KeyState keyState); + + void changeLocation(char world, char room, char node, char view, uint32 offset); + + void serializeStateTable(Common::WriteStream *stream); + void deserializeStateTable(Common::SeekableReadStream *stream); + void serializeControls(Common::WriteStream *stream); + void deserializeControls(Common::SeekableReadStream *stream); + + Location getCurrentLocation() const; + +private: + void createReferenceTable(); + void updateNodes(uint deltaTimeMillis); + void checkPuzzleCriteria(); + void cleanStateTable(); + +// TODO: Make this private. It was only made public so Console::cmdParseAllScrFiles() could use it +public: + /** + * Parses a script file into triggers and events + * + * @param fileName Name of the .scr file + * @param isGlobal Are the puzzles included in the file global (true). AKA, the won't be purged during location changes + */ + void parseScrFile(const Common::String &fileName, bool isGlobal = false); + +private: + /** + * Parses the stream into a Puzzle object + * Helper method for parseScrFile. + * + * @param puzzle The object to store what is parsed + * @param stream Scr file stream + */ + void parsePuzzle(Puzzle *puzzle, Common::SeekableReadStream &stream); + + /** + * Parses the stream into a Criteria object + * Helper method for parsePuzzle. + * + * @param criteria Pointer to the Criteria object to fill + * @param stream Scr file stream + * @return Whether any criteria were read + */ + bool parseCriteria(Common::SeekableReadStream &stream, Common::List<Common::List<Puzzle::CriteriaEntry> > &criteriaList) const; + + /** + * Parses the stream into a ResultAction objects + * Helper method for parsePuzzle. + * + * @param stream Scr file stream + * @param actionList The list where the results will be added + * @return Created Results object + */ + void parseResults(Common::SeekableReadStream &stream, Common::List<ResultAction *> &actionList) const; + + /** + * Helper method for parsePuzzle. Parses the stream into a bitwise or of the StateFlags enum + * + * @param stream Scr file stream + * @return Bitwise OR of all the flags set within the puzzle + */ + uint parseFlags(Common::SeekableReadStream &stream) const; + + /** + * Helper method for parseScrFile. Parses the stream into a Control object + * + * @param line The line initially read + * @param stream Scr file stream + */ + void parseControl(Common::String &line, Common::SeekableReadStream &stream); +}; + + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/single_value_container.cpp b/engines/zvision/single_value_container.cpp new file mode 100644 index 0000000000..837bd8d7fc --- /dev/null +++ b/engines/zvision/single_value_container.cpp @@ -0,0 +1,348 @@ +/* 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 "common/scummsys.h" + +#include "zvision/single_value_container.h" + +#include "common/textconsole.h" +#include "common/str.h" + + +namespace ZVision { + +SingleValueContainer::SingleValueContainer(ValueType type) : _objectType(type) { } + +SingleValueContainer::SingleValueContainer(bool value) : _objectType(BOOL) { + _value.boolVal = value; +} + +SingleValueContainer::SingleValueContainer(byte value) : _objectType(BYTE) { + _value.byteVal = value; +} + +SingleValueContainer::SingleValueContainer(int16 value) : _objectType(INT16) { + _value.int16Val = value; +} + +SingleValueContainer::SingleValueContainer(uint16 value) : _objectType(UINT16) { + _value.uint16Val = value; +} + +SingleValueContainer::SingleValueContainer(int32 value) : _objectType(INT32) { + _value.int32Val = value; +} + +SingleValueContainer::SingleValueContainer(uint32 value) : _objectType(UINT32) { + _value.uint32Val = value; +} + +SingleValueContainer::SingleValueContainer(float value) : _objectType(FLOAT) { + _value.floatVal = value; +} + +SingleValueContainer::SingleValueContainer(double value) : _objectType(DOUBLE) { + _value.doubleVal = value; +} + +SingleValueContainer::SingleValueContainer(Common::String value) : _objectType(BYTE) { + _value.stringVal = new char[value.size() + 1]; + memcpy(_value.stringVal, value.c_str(), value.size() + 1); +} + +SingleValueContainer::SingleValueContainer(const SingleValueContainer &other) { + _objectType = other._objectType; + + switch (_objectType) { + case BOOL: + _value.boolVal = other._value.boolVal; + break; + case BYTE: + _value.byteVal = other._value.byteVal; + break; + case INT16: + _value.int16Val = other._value.int16Val; + break; + case UINT16: + _value.uint16Val = other._value.uint16Val; + break; + case INT32: + _value.int32Val = other._value.int32Val; + break; + case UINT32: + _value.uint32Val = other._value.uint32Val; + break; + case FLOAT: + _value.floatVal = other._value.floatVal; + break; + case DOUBLE: + _value.doubleVal = other._value.doubleVal; + break; + case STRING: + uint32 length = strlen(other._value.stringVal); + _value.stringVal = new char[length + 1]; + memcpy(_value.stringVal, other._value.stringVal, length + 1); + break; + } +} + +SingleValueContainer::~SingleValueContainer() { + deleteCharPointer(); +} + +void SingleValueContainer::deleteCharPointer() { + if (_objectType == STRING) + delete[] _value.stringVal; +} + + +SingleValueContainer &SingleValueContainer::operator=(const bool &rhs) { + if (_objectType == BOOL) { + _value.boolVal = rhs; + return *this; + } + + deleteCharPointer(); + _objectType = BOOL; + _value.boolVal = rhs; + + return *this; +} + +SingleValueContainer &SingleValueContainer::operator=(const byte &rhs) { + if (_objectType == BYTE) { + _value.byteVal = rhs; + return *this; + } + + deleteCharPointer(); + _objectType = BYTE; + _value.byteVal = rhs; + + return *this; +} + +SingleValueContainer &SingleValueContainer::operator=(const int16 &rhs) { + if (_objectType == INT16) { + _value.int16Val = rhs; + return *this; + } + + deleteCharPointer(); + _objectType = INT16; + _value.int16Val = rhs; + + return *this; +} + +SingleValueContainer &SingleValueContainer::operator=(const uint16 &rhs) { + if (_objectType == UINT16) { + _value.uint16Val = rhs; + return *this; + } + + deleteCharPointer(); + _objectType = UINT16; + _value.uint16Val = rhs; + + return *this; +} + +SingleValueContainer &SingleValueContainer::operator=(const int32 &rhs) { + if (_objectType == INT32) { + _value.int32Val = rhs; + return *this; + } + + deleteCharPointer(); + _objectType = INT32; + _value.int32Val = rhs; + + return *this; +} + +SingleValueContainer &SingleValueContainer::operator=(const uint32 &rhs) { + if (_objectType == UINT32) { + _value.uint32Val = rhs; + return *this; + } + + deleteCharPointer(); + _objectType = UINT32; + _value.uint32Val = rhs; + + return *this; +} + +SingleValueContainer &SingleValueContainer::operator=(const float &rhs) { + if (_objectType == FLOAT) { + _value.floatVal = rhs; + return *this; + } + + deleteCharPointer(); + _objectType = FLOAT; + _value.floatVal = rhs; + + return *this; +} + +SingleValueContainer &SingleValueContainer::operator=(const double &rhs) { + if (_objectType == DOUBLE) { + _value.doubleVal = rhs; + return *this; + } + + deleteCharPointer(); + _objectType = DOUBLE; + _value.doubleVal = rhs; + + return *this; +} + +SingleValueContainer &SingleValueContainer::operator=(const Common::String &rhs) { + if (_objectType != STRING) { + _objectType = STRING; + _value.stringVal = new char[rhs.size() + 1]; + memcpy(_value.stringVal, rhs.c_str(), rhs.size() + 1); + + return *this; + } + + uint32 length = strlen(_value.stringVal); + if (length <= rhs.size() + 1) { + memcpy(_value.stringVal, rhs.c_str(), rhs.size() + 1); + } else { + delete[] _value.stringVal; + _value.stringVal = new char[rhs.size() + 1]; + memcpy(_value.stringVal, rhs.c_str(), rhs.size() + 1); + } + + return *this; +} + +SingleValueContainer &SingleValueContainer::operator=(const SingleValueContainer &rhs) { + switch (_objectType) { + case BOOL: + return operator=(rhs._value.boolVal); + case BYTE: + return operator=(rhs._value.byteVal); + case INT16: + return operator=(rhs._value.int16Val); + case UINT16: + return operator=(rhs._value.uint16Val); + case INT32: + return operator=(rhs._value.int32Val); + case UINT32: + return operator=(rhs._value.uint32Val); + case FLOAT: + return operator=(rhs._value.floatVal); + case DOUBLE: + return operator=(rhs._value.doubleVal); + case STRING: + uint32 length = strlen(rhs._value.stringVal); + + _value.stringVal = new char[length + 1]; + memcpy(_value.stringVal, rhs._value.stringVal, length + 1); + + return *this; + } + + return *this; +} + + +bool SingleValueContainer::getBoolValue(bool *returnValue) const { + if (_objectType != BOOL) { + warning("'Object' is not storing a bool."); + return false; + } + + *returnValue = _value.boolVal; + return true; +} + +bool SingleValueContainer::getByteValue(byte *returnValue) const { + if (_objectType != BYTE) + warning("'Object' is not storing a byte."); + + *returnValue = _value.byteVal; + return true; +} + +bool SingleValueContainer::getInt16Value(int16 *returnValue) const { + if (_objectType != INT16) + warning("'Object' is not storing an int16."); + + *returnValue = _value.int16Val; + return true; +} + +bool SingleValueContainer::getUInt16Value(uint16 *returnValue) const { + if (_objectType != UINT16) + warning("'Object' is not storing a uint16."); + + *returnValue = _value.uint16Val; + return true; +} + +bool SingleValueContainer::getInt32Value(int32 *returnValue) const { + if (_objectType != INT32) + warning("'Object' is not storing an int32."); + + *returnValue = _value.int32Val; + return true; +} + +bool SingleValueContainer::getUInt32Value(uint32 *returnValue) const { + if (_objectType != UINT32) + warning("'Object' is not storing a uint32."); + + *returnValue = _value.uint32Val; + return true; +} + +bool SingleValueContainer::getFloatValue(float *returnValue) const { + if (_objectType != FLOAT) + warning("'Object' is not storing a float."); + + *returnValue = _value.floatVal; + return true; +} + +bool SingleValueContainer::getDoubleValue(double *returnValue) const { + if (_objectType != DOUBLE) + warning("'Object' is not storing a double."); + + *returnValue = _value.doubleVal; + return true; +} + +bool SingleValueContainer::getStringValue(Common::String *returnValue) const { + if (_objectType != STRING) + warning("'Object' is not storing a Common::String."); + + *returnValue = _value.stringVal; + return true; +} + +} // End of namespace ZVision diff --git a/engines/zvision/single_value_container.h b/engines/zvision/single_value_container.h new file mode 100644 index 0000000000..45b5a89e95 --- /dev/null +++ b/engines/zvision/single_value_container.h @@ -0,0 +1,183 @@ +/* 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 ZVISION_SINGLE_VALUE_CONTAINER_H +#define ZVISION_SINGLE_VALUE_CONTAINER_H + +namespace Common { +class String; +} + +namespace ZVision { + +/** + * A generic single value storage class. It is useful for storing different + * value types in a single List, Hashmap, etc. + */ +class SingleValueContainer { +public: + enum ValueType { + BOOL, + BYTE, + INT16, + UINT16, + INT32, + UINT32, + FLOAT, + DOUBLE, + STRING + }; + + // Constructors + explicit SingleValueContainer(ValueType type); + explicit SingleValueContainer(bool value); + explicit SingleValueContainer(byte value); + explicit SingleValueContainer(int16 value); + explicit SingleValueContainer(uint16 value); + explicit SingleValueContainer(int32 value); + explicit SingleValueContainer(uint32 value); + explicit SingleValueContainer(float value); + explicit SingleValueContainer(double value); + explicit SingleValueContainer(Common::String value); + + // Copy constructor + explicit SingleValueContainer(const SingleValueContainer& other); + + // Destructor + ~SingleValueContainer(); + +private: + ValueType _objectType; + + union { + bool boolVal; + byte byteVal; + int16 int16Val; + uint16 uint16Val; + int32 int32Val; + uint32 uint32Val; + float floatVal; + double doubleVal; + char *stringVal; + } _value; + +public: + SingleValueContainer &operator=(const bool &rhs); + SingleValueContainer &operator=(const byte &rhs); + SingleValueContainer &operator=(const int16 &rhs); + SingleValueContainer &operator=(const uint16 &rhs); + SingleValueContainer &operator=(const int32 &rhs); + SingleValueContainer &operator=(const uint32 &rhs); + SingleValueContainer &operator=(const float &rhs); + SingleValueContainer &operator=(const double &rhs); + SingleValueContainer &operator=(const Common::String &rhs); + + SingleValueContainer& operator=(const SingleValueContainer &rhs); + + /** + * Retrieve a bool from the container. If the container is not storing a + * bool, this will return false and display a warning(). + * + * @param returnValue Pointer to where you want the value stored + * @return Value indicating whether the value assignment was successful + */ + bool getBoolValue(bool *returnValue) const; + /** + * Retrieve a byte from the container. If the container is not storing a + * byte, this will return false and display a warning(). + * + * @param returnValue Pointer to where you want the value stored + * @return Value indicating whether the value assignment was successful + */ + bool getByteValue(byte *returnValue) const; + /** + * Retrieve an int16 from the container. If the container is not storing an + * int16, this will return false and display a warning(). + * + * @param returnValue Pointer to where you want the value stored + * @return Value indicating whether the value assignment was successful + */ + bool getInt16Value(int16 *returnValue) const; + /** + * Retrieve a uint16 from the container. If the container is not storing a + * uint16, this will return false and display a warning(). + * + * @param returnValue Pointer to where you want the value stored + * @return Value indicating whether the value assignment was successful + */ + bool getUInt16Value(uint16 *returnValue) const; + /** + * Retrieve an int32 from the container. If the container is not storing an + * int32, this will return false and display a warning(). + * + * @param returnValue Pointer to where you want the value stored + * @return Value indicating whether the value assignment was successful + */ + bool getInt32Value(int32 *returnValue) const; + /** + * Retrieve a uint32 from the container. If the container is not storing a + * uint32, this will return false and display a warning(). + * + * @param returnValue Pointer to where you want the value stored + * @return Value indicating whether the value assignment was successful + */ + bool getUInt32Value(uint32 *returnValue) const; + /** + * Retrieve a float from the container. If the container is not storing a + * float, this will return false and display a warning(). + * + * @param returnValue Pointer to where you want the value stored + * @return Value indicating whether the value assignment was successful + */ + bool getFloatValue(float *returnValue) const; + /** + * Retrieve a double from the container. If the container is not storing a + * double, this will return false and display a warning(). + * + * @param returnValue Pointer to where you want the value stored + * @return Value indicating whether the value assignment was successful + */ + bool getDoubleValue(double *returnValue) const; + /** + * Retrieve a String from the container. If the container is not storing a + * string, this will return false and display a warning(). + * + * Caution: Strings are internally stored as char[]. getStringValue uses + * Common::String::operator=(char *) to do the assigment, which uses both + * strlen() AND memmove(). + * + * @param returnValue Pointer to where you want the value stored + * @return Value indicating whether the value assignment was successful + */ + bool getStringValue(Common::String *returnValue) const; + +private: + /** + * Helper method for destruction and assignment. It checks to see + * if the char pointer is being used, and if so calls delete on it + */ + void deleteCharPointer(); +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/string_manager.cpp b/engines/zvision/string_manager.cpp new file mode 100644 index 0000000000..ab42f3d3e0 --- /dev/null +++ b/engines/zvision/string_manager.cpp @@ -0,0 +1,255 @@ +/* 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 "common/scummsys.h" + +#include "zvision/string_manager.h" + +#include "zvision/truetype_font.h" + +#include "common/file.h" +#include "common/tokenizer.h" +#include "common/debug.h" + +#include "graphics/fontman.h" +#include "graphics/colormasks.h" + + +namespace ZVision { + +StringManager::StringManager(ZVision *engine) + : _engine(engine) { +} + +StringManager::~StringManager() { + for (Common::HashMap<Common::String, TruetypeFont *>::iterator iter = _fonts.begin(); iter != _fonts.end(); ++iter) { + delete iter->_value; + } +} + +void StringManager::initialize(ZVisionGameId gameId) { + if (gameId == GID_NEMESIS) { + // TODO: Check this hardcoded filename against all versions of Nemesis + parseStrFile("nemesis.str"); + } else if (gameId == GID_GRANDINQUISITOR) { + // TODO: Check this hardcoded filename against all versions of Grand Inquisitor + parseStrFile("inquis.str"); + } +} + +void StringManager::parseStrFile(const Common::String &fileName) { + Common::File file; + if (!file.open(fileName)) { + warning("%s does not exist. String parsing failed", fileName.c_str()); + return; + } + + uint lineNumber = 0; + while (!file.eos()) { + _lastStyle.align = Graphics::kTextAlignLeft; + _lastStyle.color = 0; + _lastStyle.font = nullptr; + + Common::String asciiLine = readWideLine(file); + if (asciiLine.empty()) { + continue; + } + + char tagString[150]; + uint tagStringCursor = 0; + char textString[150]; + uint textStringCursor = 0; + bool inTag = false; + + for (uint i = 0; i < asciiLine.size(); ++i) { + switch (asciiLine[i]) { + case '<': + inTag = true; + if (!_inGameText[lineNumber].fragments.empty()) { + _inGameText[lineNumber].fragments.back().text = Common::String(textString, textStringCursor); + textStringCursor = 0; + } + break; + case '>': + inTag = false; + parseTag(Common::String(tagString, tagStringCursor), lineNumber); + tagStringCursor = 0; + break; + default: + if (inTag) { + tagString[tagStringCursor] = asciiLine[i]; + tagStringCursor++; + } else { + textString[textStringCursor] = asciiLine[i]; + textStringCursor++; + } + break; + } + } + + if (textStringCursor > 0) { + _inGameText[lineNumber].fragments.back().text = Common::String(textString, textStringCursor); + } + + lineNumber++; + assert(lineNumber <= NUM_TEXT_LINES); + } +} + +void StringManager::parseTag(const Common::String &tagString, uint lineNumber) { + Common::StringTokenizer tokenizer(tagString); + + Common::String token = tokenizer.nextToken(); + + Common::String fontName; + bool bold = false; + Graphics::TextAlign align = _lastStyle.align; + int point = _lastStyle.font != nullptr ? _lastStyle.font->_fontHeight : 12; + int red = 0; + int green = 0; + int blue = 0; + + while (!token.empty()) { + if (token.matchString("font", true)) { + fontName = tokenizer.nextToken(); + } else if (token.matchString("bold", true)) { + token = tokenizer.nextToken(); + if (token.matchString("on", false)) { + bold = true; + } + } else if (token.matchString("justify", true)) { + token = tokenizer.nextToken(); + if (token.matchString("center", false)) { + align = Graphics::kTextAlignCenter; + } else if (token.matchString("right", false)) { + align = Graphics::kTextAlignRight; + } + } else if (token.matchString("point", true)) { + point = atoi(tokenizer.nextToken().c_str()); + } else if (token.matchString("red", true)) { + red = atoi(tokenizer.nextToken().c_str()); + } else if (token.matchString("green", true)) { + green = atoi(tokenizer.nextToken().c_str()); + } else if (token.matchString("blue", true)) { + blue = atoi(tokenizer.nextToken().c_str()); + } + + token = tokenizer.nextToken(); + } + + TextFragment fragment; + + if (fontName.empty()) { + fragment.style.font = _lastStyle.font; + } else { + Common::String newFontName; + if (fontName.matchString("*times new roman*", true)) { + if (bold) { + newFontName = "timesbd.ttf"; + } else { + newFontName = "times.ttf"; + } + } else if (fontName.matchString("*courier new*", true)) { + if (bold) { + newFontName = "courbd.ttf"; + } else { + newFontName = "cour.ttf"; + } + } else if (fontName.matchString("*century schoolbook*", true)) { + if (bold) { + newFontName = "censcbkbd.ttf"; + } else { + newFontName = "censcbk.ttf"; + } + } else if (fontName.matchString("*garamond*", true)) { + if (bold) { + newFontName = "garabd.ttf"; + } else { + newFontName = "gara.ttf"; + } + } else { + debug("Could not identify font: %s. Reverting to Arial", fontName.c_str()); + if (bold) { + newFontName = "zorknorm.ttf"; + } else { + newFontName = "arial.ttf"; + } + } + + Common::String fontKey = Common::String::format("%s-%d", newFontName.c_str(), point); + if (_fonts.contains(fontKey)) { + fragment.style.font = _fonts[fontKey]; + } else { + fragment.style.font = new TruetypeFont(_engine, point); + fragment.style.font->loadFile(newFontName); + _fonts[fontKey] = fragment.style.font; + } + } + + fragment.style.align = align; + fragment.style.color = Graphics::ARGBToColor<Graphics::ColorMasks<565> >(0, red, green, blue); + _inGameText[lineNumber].fragments.push_back(fragment); + + _lastStyle = fragment.style; +} + +Common::String StringManager::readWideLine(Common::SeekableReadStream &stream) { + Common::String asciiString; + + // Don't spam the user with warnings about UTF-16 support. + // Just do one warning per String + bool charOverflowWarning = false; + + uint16 value = stream.readUint16LE(); + while (!stream.eos()) { + // Check for CRLF + if (value == 0x0A0D) { + // Read in the extra NULL char + stream.readByte(); // \0 + // End of the line. Break + break; + } + + // Crush each octet pair to a single octet with a simple cast + if (value > 255) { + charOverflowWarning = true; + value = '?'; + } + char charValue = (char)value; + + asciiString += charValue; + + value = stream.readUint16LE(); + } + + if (charOverflowWarning) { + warning("UTF-16 is not supported. Characters greater than 255 are replaced with '?'"); + } + + return asciiString; +} + +StringManager::TextStyle StringManager::getTextStyle(uint stringNumber) { + return _inGameText[stringNumber].fragments.front().style; +} + +} // End of namespace ZVision diff --git a/engines/zvision/string_manager.h b/engines/zvision/string_manager.h new file mode 100644 index 0000000000..9cfed5261b --- /dev/null +++ b/engines/zvision/string_manager.h @@ -0,0 +1,85 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * + */ + +#ifndef ZVISION_STRING_MANAGER_H +#define ZVISION_STRING_MANAGER_H + +#include "zvision/detection.h" +#include "zvision/truetype_font.h" + + +namespace Graphics { +class FontManager; +} + +namespace ZVision { + +class ZVision; + +class StringManager { +public: + StringManager(ZVision *engine); + ~StringManager(); + +public: + struct TextStyle { + TruetypeFont *font; + uint16 color; // In RBG 565 + Graphics::TextAlign align; + }; + + struct TextFragment { + TextStyle style; + Common::String text; + }; + +private: + struct InGameText { + Common::List<TextFragment> fragments; + }; + + enum { + NUM_TEXT_LINES = 56 // Max number of lines in a .str file. We hardcode this number because we know ZNem uses 42 strings and ZGI uses 56 + }; + +private: + ZVision *_engine; + InGameText _inGameText[NUM_TEXT_LINES]; + Common::HashMap<Common::String, TruetypeFont *> _fonts; + + TextStyle _lastStyle; + +public: + void initialize(ZVisionGameId gameId); + StringManager::TextStyle getTextStyle(uint stringNumber); + +private: + void parseStrFile(const Common::String &fileName); + void parseTag(const Common::String &tagString, uint lineNumber); + + static Common::String readWideLine(Common::SeekableReadStream &stream); +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/subtitles.h b/engines/zvision/subtitles.h new file mode 100644 index 0000000000..13426e03e4 --- /dev/null +++ b/engines/zvision/subtitles.h @@ -0,0 +1,29 @@ +/* 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 ZVISION_SUBTITLES_H +#define ZVISION_SUBTITLES_H + +// TODO: Implement Subtitles + +#endif diff --git a/engines/zvision/timer_node.cpp b/engines/zvision/timer_node.cpp new file mode 100644 index 0000000000..55dfa51dfe --- /dev/null +++ b/engines/zvision/timer_node.cpp @@ -0,0 +1,59 @@ +/* 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 "common/scummsys.h" + +#include "zvision/timer_node.h" + +#include "zvision/zvision.h" +#include "zvision/script_manager.h" + +#include "common/stream.h" + + +namespace ZVision { + +TimerNode::TimerNode(ZVision *engine, uint32 key, uint timeInSeconds) + : Control(engine, key), _timeLeft(timeInSeconds * 1000) { +} + +bool TimerNode::process(uint32 deltaTimeInMillis) { + _timeLeft -= deltaTimeInMillis; + + if (_timeLeft <= 0) { + _engine->getScriptManager()->setStateValue(_key, 0); + return true; + } + + return false; +} + +void TimerNode::serialize(Common::WriteStream *stream) { + stream->writeUint32LE(_key); + stream->writeUint32LE(_timeLeft); +} + +void TimerNode::deserialize(Common::SeekableReadStream *stream) { + _timeLeft = stream->readUint32LE(); +} + +} // End of namespace ZVision diff --git a/engines/zvision/timer_node.h b/engines/zvision/timer_node.h new file mode 100644 index 0000000000..32dca71548 --- /dev/null +++ b/engines/zvision/timer_node.h @@ -0,0 +1,54 @@ +/* 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 ZVISION_TIMER_NODE_H +#define ZVISION_TIMER_NODE_H + +#include "zvision/control.h" + +namespace ZVision { + +class ZVision; + +class TimerNode : public Control { +public: + TimerNode(ZVision *engine, uint32 key, uint timeInSeconds); + + /** + * Decrement the timer by the delta time. If the timer is finished, set the status + * in _globalState and let this node be deleted + * + * @param deltaTimeInMillis The number of milliseconds that have passed since last frame + * @return If true, the node can be deleted after process() finishes + */ + bool process(uint32 deltaTimeInMillis); + void serialize(Common::WriteStream *stream); + void deserialize(Common::SeekableReadStream *stream); + inline bool needsSerialization() { return true; } + +private: + int32 _timeLeft; +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/truetype_font.cpp b/engines/zvision/truetype_font.cpp new file mode 100644 index 0000000000..289b5fbbaf --- /dev/null +++ b/engines/zvision/truetype_font.cpp @@ -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. + * + */ + +#include "common/scummsys.h" + +#include "zvision/truetype_font.h" + +#include "zvision/zvision.h" +#include "zvision/render_manager.h" + +#include "common/debug.h" +#include "common/file.h" +#include "common/system.h" + +#include "graphics/font.h" +#include "graphics/fonts/ttf.h" +#include "graphics/surface.h" + + +namespace ZVision { + +TruetypeFont::TruetypeFont(ZVision *engine, int32 fontHeight) + : _engine(engine), + _fontHeight(fontHeight), + _font(0), + _lineHeight(0), + _maxCharWidth(0), + _maxCharHeight(0) { +} + +TruetypeFont::~TruetypeFont(void) { + delete _font; +} + +bool TruetypeFont::loadFile(const Common::String &filename) { + Common::File file; + + bool fileOpened = false; + if (!Common::File::exists(filename)) { + debug("TTF font file %s was not found. Reverting to arial.ttf", filename.c_str()); + fileOpened = file.open("arial.ttf"); + } else { + fileOpened = file.open(filename); + } + + if (!fileOpened) { + debug("TTF file could not be opened"); + return false; + } + + _font = Graphics::loadTTFFont(file, _fontHeight); + _lineHeight = _font->getFontHeight(); + + return true; +} + +Graphics::Surface *TruetypeFont::drawTextToSurface(const Common::String &text, uint16 textColor, int maxWidth, int maxHeight, Graphics::TextAlign align, bool wrap) { + if (text.equals("")) { + return nullptr; + } + + Graphics::Surface *surface = new Graphics::Surface(); + + if (!wrap) { + int width = MIN(_font->getStringWidth(text), maxWidth); + surface->create(width, _lineHeight, Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0)); + // TODO: Add better alpha support by getting the pixels from the backbuffer. + // However doing that requires some kind of caching system so future text doesn't try to use this text as it's alpha background. + surface->fillRect(Common::Rect(0, 0, surface->w, surface->h), 0); + + _font->drawString(surface, text, 0, 0, maxWidth, textColor, align); + return surface; + } + + Common::Array<Common::String> lines; + _font->wordWrapText(text, maxWidth, lines); + + while (maxHeight > 0 && (int)lines.size() * _lineHeight > maxHeight) { + lines.pop_back(); + } + if (lines.size() == 0) { + return nullptr; + } + + surface->create(maxWidth, lines.size() * _lineHeight, Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0)); + surface->fillRect(Common::Rect(0, 0, surface->w, surface->h), 0); + + int heightOffset = 0; + for (Common::Array<Common::String>::iterator it = lines.begin(); it != lines.end(); it++) { + _font->drawString(surface, *it, 0, 0 + heightOffset, maxWidth, textColor, align); + heightOffset += _lineHeight; + } + + return surface; +} + +} // End of namespace ZVision diff --git a/engines/zvision/truetype_font.h b/engines/zvision/truetype_font.h new file mode 100644 index 0000000000..33f016cffd --- /dev/null +++ b/engines/zvision/truetype_font.h @@ -0,0 +1,81 @@ +/* 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 file is based on engines/wintermute/base/fonts/base_font_truetype.h/.cpp + +#ifndef ZVISION_TRUETYPE_FONT_H +#define ZVISION_TRUETYPE_FONT_H + +#include "graphics/font.h" +#include "graphics/pixelformat.h" + + +namespace Graphics { +struct Surface; +} + +namespace ZVision { + +class ZVision; + +class TruetypeFont { +public: + TruetypeFont(ZVision *engine, int32 fontHeight); + ~TruetypeFont(); + +private: + ZVision *_engine; + Graphics::Font *_font; + int _lineHeight; + + size_t _maxCharWidth; + size_t _maxCharHeight; + +public: + int32 _fontHeight; + +public: + /** + * Loads a .ttf file into memory. This must be called + * before any calls to drawTextToSurface + * + * @param filename The file name of the .ttf file to load + */ + bool loadFile(const Common::String &filename); + /** + * Renders the supplied text to a Surface using 0x0 as the + * background color. + * + * @param text The to render + * @param textColor The color to render the text with + * @param maxWidth The max width the text should take up. + * @param maxHeight The max height the text should take up. + * @param align The alignment of the text within the bounds of maxWidth + * @param wrap If true, any words extending past maxWidth will wrap to a new line. If false, ellipses will be rendered to show that the text didn't fit + * @return A Surface containing the rendered text + */ + Graphics::Surface *drawTextToSurface(const Common::String &text, uint16 textColor, int maxWidth, int maxHeight, Graphics::TextAlign align, bool wrap); +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/utility.cpp b/engines/zvision/utility.cpp new file mode 100644 index 0000000000..d973cb2f4c --- /dev/null +++ b/engines/zvision/utility.cpp @@ -0,0 +1,237 @@ +/* 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 "common/scummsys.h" + +#include "zvision/utility.h" + +#include "zvision/zvision.h" +#include "zvision/zork_raw.h" + +#include "common/tokenizer.h" +#include "common/file.h" + + +namespace ZVision { + +void writeFileContentsToFile(const Common::String &sourceFile, const Common::String &destFile) { + Common::File f; + if (!f.open(sourceFile)) { + return; + } + + byte* buffer = new byte[f.size()]; + f.read(buffer, f.size()); + + Common::DumpFile dumpFile; + dumpFile.open(destFile); + + dumpFile.write(buffer, f.size()); + dumpFile.flush(); + dumpFile.close(); + + delete[] buffer; +} + +void trimCommentsAndWhiteSpace(Common::String *string) { + for (int i = string->size() - 1; i >= 0; i--) { + if ((*string)[i] == '#') { + string->erase(i); + } + } + + string->trim(); +} + +void tryToDumpLine(const Common::String &key, + Common::String &line, + Common::HashMap<Common::String, byte> *count, + Common::HashMap<Common::String, bool> *fileAlreadyUsed, + Common::DumpFile &output) { + const byte numberOfExamplesPerType = 8; + + if ((*count)[key] < numberOfExamplesPerType && !(*fileAlreadyUsed)[key]) { + output.writeString(line); + output.writeByte('\n'); + (*count)[key]++; + (*fileAlreadyUsed)[key] = true; + } +} + +void dumpEveryResultAction(const Common::String &destFile) { + Common::HashMap<Common::String, byte> count; + Common::HashMap<Common::String, bool> fileAlreadyUsed; + + Common::DumpFile output; + output.open(destFile); + + // Find scr files + Common::ArchiveMemberList list; + SearchMan.listMatchingMembers(list, "*.scr"); + + for (Common::ArchiveMemberList::iterator iter = list.begin(); iter != list.end(); ++iter) { + Common::SeekableReadStream *stream = (*iter)->createReadStream(); + + Common::String line = stream->readLine(); + trimCommentsAndWhiteSpace(&line); + + while (!stream->eos()) { + if (line.matchString("*:add*", true)) { + tryToDumpLine("add", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:animplay*", true)) { + tryToDumpLine("animplay", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:animpreload*", true)) { + tryToDumpLine("animpreload", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:animunload*", true)) { + tryToDumpLine("animunload", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:attenuate*", true)) { + tryToDumpLine("attenuate", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:assign*", true)) { + tryToDumpLine("assign", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:change_location*", true)) { + tryToDumpLine("change_location", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:crossfade*", true) && !fileAlreadyUsed["add"]) { + tryToDumpLine("crossfade", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:debug*", true)) { + tryToDumpLine("debug", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:delay_render*", true)) { + tryToDumpLine("delay_render", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:disable_control*", true)) { + tryToDumpLine("disable_control", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:disable_venus*", true)) { + tryToDumpLine("disable_venus", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:display_message*", true)) { + tryToDumpLine("display_message", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:dissolve*", true)) { + tryToDumpLine("dissolve", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:distort*", true)) { + tryToDumpLine("distort", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:enable_control*", true)) { + tryToDumpLine("enable_control", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:flush_mouse_events*", true)) { + tryToDumpLine("flush_mouse_events", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:inventory*", true)) { + tryToDumpLine("inventory", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:kill*", true)) { + tryToDumpLine("kill", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:menu_bar_enable*", true)) { + tryToDumpLine("menu_bar_enable", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:music*", true)) { + tryToDumpLine("music", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:pan_track*", true)) { + tryToDumpLine("pan_track", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:playpreload*", true)) { + tryToDumpLine("playpreload", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:preferences*", true)) { + tryToDumpLine("preferences", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:quit*", true)) { + tryToDumpLine("quit", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:random*", true)) { + tryToDumpLine("random", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:region*", true)) { + tryToDumpLine("region", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:restore_game*", true)) { + tryToDumpLine("restore_game", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:rotate_to*", true)) { + tryToDumpLine("rotate_to", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:save_game*", true)) { + tryToDumpLine("save_game", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:set_partial_screen*", true)) { + tryToDumpLine("set_partial_screen", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:set_screen*", true)) { + tryToDumpLine("set_screen", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:set_venus*", true)) { + tryToDumpLine("set_venus", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:stop*", true)) { + tryToDumpLine("stop", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:streamvideo*", true)) { + tryToDumpLine("streamvideo", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:syncsound*", true)) { + tryToDumpLine("syncsound", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:timer*", true)) { + tryToDumpLine("timer", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:ttytext*", true)) { + tryToDumpLine("ttytext", line, &count, &fileAlreadyUsed, output); + } else if (line.matchString("*:universe_music*", true)) { + tryToDumpLine("universe_music", line, &count, &fileAlreadyUsed, output); + } + + line = stream->readLine(); + trimCommentsAndWhiteSpace(&line); + } + + for (Common::HashMap<Common::String, bool>::iterator fileUsedIter = fileAlreadyUsed.begin(); fileUsedIter != fileAlreadyUsed.end(); ++fileUsedIter) { + fileUsedIter->_value = false; + } + } + + output.close(); +} + +Common::String getFileName(const Common::String &fullPath) { + Common::StringTokenizer tokenizer(fullPath, "/\\"); + Common::String token; + while (!tokenizer.empty()) { + token = tokenizer.nextToken(); + } + + return token; +} + +void convertRawToWav(const Common::String &inputFile, ZVision *engine, const Common::String &outputFile) { + Common::File file; + if (!file.open(inputFile)) + return; + + Audio::AudioStream *audioStream = makeRawZorkStream(inputFile, engine); + + Common::DumpFile output; + output.open(outputFile); + + output.writeUint32BE(MKTAG('R', 'I', 'F', 'F')); + output.writeUint32LE(file.size() * 2 + 36); + output.writeUint32BE(MKTAG('W', 'A', 'V', 'E')); + output.writeUint32BE(MKTAG('f', 'm', 't', ' ')); + output.writeUint32LE(16); + output.writeUint16LE(1); + uint16 numChannels; + if (audioStream->isStereo()) { + numChannels = 2; + output.writeUint16LE(2); + } else { + numChannels = 1; + output.writeUint16LE(1); + } + output.writeUint32LE(audioStream->getRate()); + output.writeUint32LE(audioStream->getRate() * numChannels * 2); + output.writeUint16LE(numChannels * 2); + output.writeUint16LE(16); + output.writeUint32BE(MKTAG('d', 'a', 't', 'a')); + output.writeUint32LE(file.size() * 2); + int16 *buffer = new int16[file.size()]; + audioStream->readBuffer(buffer, file.size()); + output.write(buffer, file.size() * 2); + + delete[] buffer; +} + +} // End of namespace ZVision diff --git a/engines/zvision/utility.h b/engines/zvision/utility.h new file mode 100644 index 0000000000..fb571f3fe8 --- /dev/null +++ b/engines/zvision/utility.h @@ -0,0 +1,115 @@ +/* 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 ZVISION_UTILITY_H +#define ZVISION_UTILITY_H + +#include "common/array.h" + + +namespace Common { +class String; +} + +namespace ZVision { + +class ZVision; + +/** + * Opens the sourceFile utilizing Common::File (aka SearchMan) and writes the + * contents to destFile. destFile is created in the working directory + * + * @param sourceFile The 'file' you want the contents of + * @param destFile The name of the file where the content will be written to + */ +void writeFileContentsToFile(const Common::String &sourceFile, const Common::String &destFile); + +/** + * Removes any line comments using '#' as a sequence start. + * Then removes any trailing and leading 'whitespace' using String::trim() + * Note: String::trim uses isspace() to determine what is whitespace and what is not. + * + * @param string The string to modify. It is modified in place + */ +void trimCommentsAndWhiteSpace(Common::String *string); + +/** + * Searches through all the .scr files and dumps 'numberOfExamplesPerType' examples of each type of ResultAction + * ZVision::initialize() must have been called before this function can be used. + * + * @param destFile Where to write the examples + */ +void dumpEveryResultAction(const Common::String &destFile); + +/** + * Removes all duplicate entries from container. Relative order will be preserved. + * + * @param container The Array to remove duplicate entries from + */ +template<class T> +void removeDuplicateEntries(Common::Array<T> &container) { + // Length of modified array + uint newLength = 1; + uint j; + + for(uint i = 1; i < container.size(); i++) { + for(j = 0; j < newLength; j++) { + if (container[i] == container[j]) { + break; + } + } + + // If none of the values in index[0..j] of container are the same as array[i], + // then copy the current value to corresponding new position in array + if (j == newLength) { + container[newLength++] = container[i]; + } + } + + // Actually remove the unneeded space + while (container.size() < newLength) { + container.pop_back(); + } +} + +/** + * Gets the name of the file (including extension). Forward or back slashes + * are interpreted as directory changes + * + * @param fullPath A full or partial path to the file. Ex: folderOne/folderTwo/file.txt + * @return The name of the file without any preceding directories. Ex: file.txt + */ +Common::String getFileName(const Common::String &fullPath); + +/** + * Converts a ZVision .RAW file to a .WAV + * The .WAV will be created in the working directory and will overwrite any existing file + * + * @param inputFile The path to the input .RAW file + * @param outputFile The name of the output .WAV file + */ +void convertRawToWav(const Common::String &inputFile, ZVision *engine, const Common::String &outputFile); + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/video.cpp b/engines/zvision/video.cpp new file mode 100644 index 0000000000..cd11618e67 --- /dev/null +++ b/engines/zvision/video.cpp @@ -0,0 +1,171 @@ +/* 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 "common/scummsys.h" + +#include "zvision/zvision.h" + +#include "zvision/clock.h" +#include "zvision/render_manager.h" + +#include "common/system.h" + +#include "video/video_decoder.h" + +#include "engines/util.h" + +#include "graphics/surface.h" + + +namespace ZVision { + +// Taken/modified from SCI +void scaleBuffer(const byte *src, byte *dst, uint32 srcWidth, uint32 srcHeight, byte bytesPerPixel, uint scaleAmount) { + assert(bytesPerPixel == 1 || bytesPerPixel == 2); + + const uint32 newWidth = srcWidth * scaleAmount; + const uint32 pitch = newWidth * bytesPerPixel; + const byte *srcPtr = src; + + if (bytesPerPixel == 1) { + for (uint32 y = 0; y < srcHeight; ++y) { + for (uint32 x = 0; x < srcWidth; ++x) { + const byte color = *srcPtr++; + + for (uint i = 0; i < scaleAmount; ++i) { + dst[i] = color; + dst[pitch + i] = color; + } + dst += scaleAmount; + } + dst += pitch; + } + } else if (bytesPerPixel == 2) { + for (uint32 y = 0; y < srcHeight; ++y) { + for (uint32 x = 0; x < srcWidth; ++x) { + const byte color = *srcPtr++; + const byte color2 = *srcPtr++; + + for (uint i = 0; i < scaleAmount; ++i) { + uint index = i *2; + + dst[index] = color; + dst[index + 1] = color2; + dst[pitch + index] = color; + dst[pitch + index + 1] = color2; + } + dst += 2 * scaleAmount; + } + dst += pitch; + } + } +} + +void ZVision::playVideo(Video::VideoDecoder &videoDecoder, const Common::Rect &destRect, bool skippable) { + byte bytesPerPixel = videoDecoder.getPixelFormat().bytesPerPixel; + + uint16 origWidth = videoDecoder.getWidth(); + uint16 origHeight = videoDecoder.getHeight(); + + uint scale = 1; + // If destRect is empty, no specific scaling was requested. However, we may choose to do scaling anyway + if (destRect.isEmpty()) { + // Most videos are very small. Therefore we do a simple 2x scale + if (origWidth * 2 <= 640 && origHeight * 2 <= 480) { + scale = 2; + } + } else { + // Assume bilinear scaling. AKA calculate the scale from just the width. + // Also assume that the scaling is in integral intervals. AKA no 1.5x scaling + // TODO: Test ^these^ assumptions + scale = destRect.width() / origWidth; + + // TODO: Test if we need to support downscale. + } + + uint16 pitch = origWidth * bytesPerPixel; + + uint16 finalWidth = origWidth * scale; + uint16 finalHeight = origHeight * scale; + + byte *scaledVideoFrameBuffer; + if (scale != 1) { + scaledVideoFrameBuffer = new byte[finalWidth * finalHeight * bytesPerPixel]; + } + + uint16 x = ((WINDOW_WIDTH - finalWidth) / 2) + destRect.left; + uint16 y = ((WINDOW_HEIGHT - finalHeight) / 2) + destRect.top; + + _clock.stop(); + videoDecoder.start(); + + // Only continue while the video is still playing + while (!shouldQuit() && !videoDecoder.endOfVideo() && videoDecoder.isPlaying()) { + // Check for engine quit and video stop key presses + while (!videoDecoder.endOfVideo() && videoDecoder.isPlaying() && _eventMan->pollEvent(_event)) { + switch (_event.type) { + case Common::EVENT_KEYDOWN: + switch (_event.kbd.keycode) { + case Common::KEYCODE_q: + if (_event.kbd.hasFlags(Common::KBD_CTRL)) + quitGame(); + break; + case Common::KEYCODE_SPACE: + if (skippable) { + videoDecoder.stop(); + } + break; + default: + break; + } + default: + break; + } + } + + if (videoDecoder.needsUpdate()) { + const Graphics::Surface *frame = videoDecoder.decodeNextFrame(); + + if (frame) { + if (scale != 1) { + scaleBuffer((const byte *)frame->getPixels(), scaledVideoFrameBuffer, origWidth, origHeight, bytesPerPixel, scale); + _system->copyRectToScreen(scaledVideoFrameBuffer, pitch * 2, x, y, finalWidth, finalHeight); + } else { + _system->copyRectToScreen((const byte *)frame->getPixels(), pitch, x, y, finalWidth, finalHeight); + } + } + } + + // Always update the screen so the mouse continues to render + _system->updateScreen(); + + _system->delayMillis(videoDecoder.getTimeToNextFrame()); + } + + _clock.start(); + + if (scale != 1) { + delete[] scaledVideoFrameBuffer; + } +} + +} // End of namespace ZVision diff --git a/engines/zvision/zfs_archive.cpp b/engines/zvision/zfs_archive.cpp new file mode 100644 index 0000000000..24cff27fc4 --- /dev/null +++ b/engines/zvision/zfs_archive.cpp @@ -0,0 +1,157 @@ +/* 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 "common/scummsys.h" + +#include "zvision/zfs_archive.h" + +#include "common/memstream.h" +#include "common/debug.h" +#include "common/file.h" + +namespace ZVision { + +ZfsArchive::ZfsArchive(const Common::String &fileName) : _fileName(fileName) { + Common::File zfsFile; + + if (!zfsFile.open(_fileName)) { + warning("ZFSArchive::ZFSArchive(): Could not find the archive file"); + return; + } + + readHeaders(&zfsFile); + + debug(1, "ZfsArchive::ZfsArchive(%s): Located %d files", _fileName.c_str(), _entryHeaders.size()); +} + +ZfsArchive::ZfsArchive(const Common::String &fileName, Common::SeekableReadStream *stream) : _fileName(fileName) { + readHeaders(stream); + + debug(1, "ZfsArchive::ZfsArchive(%s): Located %d files", _fileName.c_str(), _entryHeaders.size()); +} + +ZfsArchive::~ZfsArchive() { + debug(1, "ZfsArchive Destructor Called"); + ZfsEntryHeaderMap::iterator it = _entryHeaders.begin(); + for ( ; it != _entryHeaders.end(); ++it) { + delete it->_value; + } +} + +void ZfsArchive::readHeaders(Common::SeekableReadStream *stream) { + // Don't do a straight struct cast since we can't guarantee endianness + _header.magic = stream->readUint32LE(); + _header.unknown1 = stream->readUint32LE(); + _header.maxNameLength = stream->readUint32LE(); + _header.filesPerBlock = stream->readUint32LE(); + _header.fileCount = stream->readUint32LE(); + _header.xorKey[0] = stream->readByte(); + _header.xorKey[1] = stream->readByte(); + _header.xorKey[2] = stream->readByte(); + _header.xorKey[3] = stream->readByte(); + _header.fileSectionOffset = stream->readUint32LE(); + + uint32 nextOffset; + + do { + // Read the offset to the next block + nextOffset = stream->readUint32LE(); + + // Read in each entry header + for (uint32 i = 0; i < _header.filesPerBlock; ++i) { + ZfsEntryHeader entryHeader; + + entryHeader.name = readEntryName(stream); + entryHeader.offset = stream->readUint32LE(); + entryHeader.id = stream->readUint32LE(); + entryHeader.size = stream->readUint32LE(); + entryHeader.time = stream->readUint32LE(); + entryHeader.unknown = stream->readUint32LE(); + + if (entryHeader.size != 0) + _entryHeaders[entryHeader.name] = new ZfsEntryHeader(entryHeader); + } + + // Seek to the next block of headers + stream->seek(nextOffset); + } while (nextOffset != 0); +} + +Common::String ZfsArchive::readEntryName(Common::SeekableReadStream *stream) const { + // Entry Names are at most 16 bytes and are null padded + char buffer[16]; + stream->read(buffer, 16); + + return Common::String(buffer); +} + +bool ZfsArchive::hasFile(const Common::String &name) const { + return _entryHeaders.contains(name); +} + +int ZfsArchive::listMembers(Common::ArchiveMemberList &list) const { + int matches = 0; + + for (ZfsEntryHeaderMap::const_iterator it = _entryHeaders.begin(); it != _entryHeaders.end(); ++it) { + list.push_back(Common::ArchiveMemberList::value_type(new Common::GenericArchiveMember(it->_value->name, this))); + matches++; + } + + return matches; +} + +const Common::ArchiveMemberPtr ZfsArchive::getMember(const Common::String &name) const { + if (!_entryHeaders.contains(name)) + return Common::ArchiveMemberPtr(); + + return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(name, this)); +} + +Common::SeekableReadStream *ZfsArchive::createReadStreamForMember(const Common::String &name) const { + if (!_entryHeaders.contains(name)) { + return 0; + } + + ZfsEntryHeader *entryHeader = _entryHeaders[name]; + + Common::File zfsArchive; + zfsArchive.open(_fileName); + zfsArchive.seek(entryHeader->offset); + + // This *HAS* to be malloc (not new[]) because MemoryReadStream uses free() to free the memory + byte* buffer = (byte *)malloc(entryHeader->size); + zfsArchive.read(buffer, entryHeader->size); + // Decrypt the data in place + if (_header.xorKey != 0) + unXor(buffer, entryHeader->size, _header.xorKey); + + return new Common::MemoryReadStream(buffer, entryHeader->size, DisposeAfterUse::YES); +} + +void ZfsArchive::unXor(byte *buffer, uint32 length, const byte *xorKey) const { + for (uint32 i = 0; i < length; ++i) + buffer[i] ^= xorKey[i % 4]; +} + +} // End of namespace ZVision + + diff --git a/engines/zvision/zfs_archive.h b/engines/zvision/zfs_archive.h new file mode 100644 index 0000000000..d7b45e4b47 --- /dev/null +++ b/engines/zvision/zfs_archive.h @@ -0,0 +1,126 @@ +/* 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 ZVISION_ZFS_ARCHIVE_H +#define ZVISION_ZFS_ARCHIVE_H + +#include "common/archive.h" +#include "common/hashmap.h" +#include "common/hash-str.h" + + +namespace Common { +class String; +} + +namespace ZVision { + +struct ZfsHeader { + uint32 magic; + uint32 unknown1; + uint32 maxNameLength; + uint32 filesPerBlock; + uint32 fileCount; + byte xorKey[4]; + uint32 fileSectionOffset; +}; + +struct ZfsEntryHeader { + Common::String name; + uint32 offset; + uint32 id; + uint32 size; + uint32 time; + uint32 unknown; +}; + +typedef Common::HashMap<Common::String, ZfsEntryHeader*, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> ZfsEntryHeaderMap; + +class ZfsArchive : public Common::Archive { +public: + ZfsArchive(const Common::String &fileName); + ZfsArchive(const Common::String &fileName, Common::SeekableReadStream *stream); + ~ZfsArchive(); + + /** + * Check if a member with the given name is present in the Archive. + * Patterns are not allowed, as this is meant to be a quick File::exists() + * replacement. + */ + bool hasFile(const Common::String &fileName) const; + + /** + * Add all members of the Archive to list. + * Must only append to list, and not remove elements from it. + * + * @return The number of names added to list + */ + int listMembers(Common::ArchiveMemberList &list) const; + + /** + * Returns a ArchiveMember representation of the given file. + */ + const Common::ArchiveMemberPtr getMember(const Common::String &name) const; + + /** + * Create a stream bound to a member with the specified name in the + * archive. If no member with this name exists, 0 is returned. + * + * @return The newly created input stream + */ + Common::SeekableReadStream *createReadStreamForMember(const Common::String &name) const; + +private: + const Common::String _fileName; + ZfsHeader _header; + ZfsEntryHeaderMap _entryHeaders; + + /** + * Parses the zfs file into file entry headers that can be used later + * to get the entry data. + * + * @param stream The contents of the zfs file + */ + void readHeaders(Common::SeekableReadStream *stream); + + /** + * Entry names are contained within a 16 byte block. This reads the block + * and converts it the name to a Common::String + * + * @param stream The zfs file stream + * @return The entry file name + */ + Common::String readEntryName(Common::SeekableReadStream *stream) const; + + /** + * ZFS file entries can be encrypted using XOR encoding. This method + * decodes the buffer in place using the supplied xorKey. + * + * @param buffer The data to decode + * @param length Length of buffer + */ + void unXor(byte *buffer, uint32 length, const byte *xorKey) const; +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/zork_avi_decoder.cpp b/engines/zvision/zork_avi_decoder.cpp new file mode 100644 index 0000000000..a614f77bb7 --- /dev/null +++ b/engines/zvision/zork_avi_decoder.cpp @@ -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. + * + * + */ + +#include "common/scummsys.h" + +#include "zvision/zork_avi_decoder.h" + +#include "zvision/zork_raw.h" + +#include "common/stream.h" + +#include "audio/audiostream.h" + + +namespace ZVision { + +Video::AVIDecoder::AVIAudioTrack *ZorkAVIDecoder::createAudioTrack(Video::AVIDecoder::AVIStreamHeader sHeader, Video::AVIDecoder::PCMWaveFormat wvInfo) { + ZorkAVIDecoder::ZorkAVIAudioTrack *audioTrack = new ZorkAVIDecoder::ZorkAVIAudioTrack(sHeader, wvInfo, _soundType); + return (Video::AVIDecoder::AVIAudioTrack *)audioTrack; +} + +void ZorkAVIDecoder::ZorkAVIAudioTrack::queueSound(Common::SeekableReadStream *stream) { + if (_audStream) { + if (_wvInfo.tag == kWaveFormatZorkPCM) { + assert(_wvInfo.size == 8); + _audStream->queueAudioStream(makeRawZorkStream(stream, _wvInfo.samplesPerSec, _audStream->isStereo(), DisposeAfterUse::YES), DisposeAfterUse::YES); + } + } else { + delete stream; + } +} + +} // End of namespace ZVision diff --git a/engines/zvision/zork_avi_decoder.h b/engines/zvision/zork_avi_decoder.h new file mode 100644 index 0000000000..ec2be1bb13 --- /dev/null +++ b/engines/zvision/zork_avi_decoder.h @@ -0,0 +1,60 @@ +/* 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 ZORK_AVI_DECODER_H +#define ZORK_AVI_DECODER_H + +#include "video/avi_decoder.h" + + +namespace ZVision { + +class ZorkAVIDecoder : public Video::AVIDecoder { +public: + ZorkAVIDecoder(Audio::Mixer::SoundType soundType = Audio::Mixer::kPlainSoundType) : + Video::AVIDecoder(soundType) {} + + virtual ~ZorkAVIDecoder() {} + +private: + class ZorkAVIAudioTrack : public Video::AVIDecoder::AVIAudioTrack { + public: + ZorkAVIAudioTrack(const AVIStreamHeader &streamHeader, const PCMWaveFormat &waveFormat, Audio::Mixer::SoundType soundType) : + Video::AVIDecoder::AVIAudioTrack(streamHeader, waveFormat, soundType) {} + virtual ~ZorkAVIAudioTrack() {} + + void queueSound(Common::SeekableReadStream *stream); + }; + + Video::AVIDecoder::AVIAudioTrack *createAudioTrack(Video::AVIDecoder::AVIStreamHeader sHeader, Video::AVIDecoder::PCMWaveFormat wvInfo); + +private: + // Audio Codecs + enum { + kWaveFormatZorkPCM = 17 // special Zork PCM audio format (clashes with MS IMA ADPCM) + }; +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/zork_raw.cpp b/engines/zvision/zork_raw.cpp new file mode 100644 index 0000000000..21613d9043 --- /dev/null +++ b/engines/zvision/zork_raw.cpp @@ -0,0 +1,209 @@ +/* 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 "common/scummsys.h" + +#include "zvision/zork_raw.h" + +#include "zvision/zvision.h" +#include "zvision/detection.h" +#include "zvision/utility.h" + +#include "common/file.h" +#include "common/str.h" +#include "common/stream.h" +#include "common/memstream.h" +#include "common/bufferedstream.h" +#include "common/util.h" + +#include "audio/audiostream.h" +#include "audio/decoders/raw.h" + + +namespace ZVision { + +const int16 RawZorkStream::_stepAdjustmentTable[8] = {-1, -1, -1, 1, 4, 7, 10, 12}; + +const int32 RawZorkStream::_amplitudeLookupTable[89] = {0x0007, 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, + 0x0010, 0x0011, 0x0013, 0x0015, 0x0017, 0x0019, 0x001C, 0x001F, + 0x0022, 0x0025, 0x0029, 0x002D, 0x0032, 0x0037, 0x003C, 0x0042, + 0x0049, 0x0050, 0x0058, 0x0061, 0x006B, 0x0076, 0x0082, 0x008F, + 0x009D, 0x00AD, 0x00BE, 0x00D1, 0x00E6, 0x00FD, 0x0117, 0x0133, + 0x0151, 0x0173, 0x0198, 0x01C1, 0x01EE, 0x0220, 0x0256, 0x0292, + 0x02D4, 0x031C, 0x036C, 0x03C3, 0x0424, 0x048E, 0x0502, 0x0583, + 0x0610, 0x06AB, 0x0756, 0x0812, 0x08E0, 0x09C3, 0x0ABD, 0x0BD0, + 0x0CFF, 0x0E4C, 0x0FBA, 0x114C, 0x1307, 0x14EE, 0x1706, 0x1954, + 0x1BDC, 0x1EA5, 0x21B6, 0x2515, 0x28CA, 0x2CDF, 0x315B, 0x364B, + 0x3BB9, 0x41B2, 0x4844, 0x4F7E, 0x5771, 0x602F, 0x69CE, 0x7462, 0x7FFF}; + +const SoundParams RawZorkStream::_zNemSoundParamLookupTable[6] = {{'6', 0x2B11, false, false}, + {'a', 0x5622, false, true}, + {'b', 0x5622, true, true}, + {'n', 0x2B11, false, true}, + {'s', 0x5622, false, true}, + {'t', 0x5622, true, true} +}; + +const SoundParams RawZorkStream::_zgiSoundParamLookupTable[5] = {{'a',0x5622, false, false}, + {'k',0x2B11, true, true}, + {'p',0x5622, false, true}, + {'q',0x5622, true, true}, + {'u',0xAC44, true, true} +}; + +RawZorkStream::RawZorkStream(uint32 rate, bool stereo, DisposeAfterUse::Flag disposeStream, Common::SeekableReadStream *stream) + : _rate(rate), + _stereo(0), + _stream(stream, disposeStream), + _endOfData(false) { + if (stereo) + _stereo = 1; + + _lastSample[0].index = 0; + _lastSample[0].sample = 0; + _lastSample[1].index = 0; + _lastSample[1].sample = 0; + + // Calculate the total playtime of the stream + if (stereo) + _playtime = Audio::Timestamp(0, _stream->size() / 2, rate); + else + _playtime = Audio::Timestamp(0, _stream->size(), rate); +} + +int RawZorkStream::readBuffer(int16 *buffer, const int numSamples) { + int bytesRead = 0; + + // 0: Left, 1: Right + uint channel = 0; + + while (bytesRead < numSamples) { + byte encodedSample = _stream->readByte(); + if (_stream->eos()) { + _endOfData = true; + return bytesRead; + } + bytesRead++; + + int16 index = _lastSample[channel].index; + uint32 lookUpSample = _amplitudeLookupTable[index]; + + int32 sample = 0; + + if (encodedSample & 0x40) + sample += lookUpSample; + if (encodedSample & 0x20) + sample += lookUpSample >> 1; + if (encodedSample & 0x10) + sample += lookUpSample >> 2; + if (encodedSample & 8) + sample += lookUpSample >> 3; + if (encodedSample & 4) + sample += lookUpSample >> 4; + if (encodedSample & 2) + sample += lookUpSample >> 5; + if (encodedSample & 1) + sample += lookUpSample >> 6; + if (encodedSample & 0x80) + sample = -sample; + + sample += _lastSample[channel].sample; + sample = CLIP<int32>(sample, -32768, 32767); + + buffer[bytesRead - 1] = (int16)sample; + + index += _stepAdjustmentTable[(encodedSample >> 4) & 7]; + index = CLIP<int16>(index, 0, 88); + + _lastSample[channel].sample = sample; + _lastSample[channel].index = index; + + // Increment and wrap the channel + channel = (channel + 1) & _stereo; + } + + return bytesRead; +} + +bool RawZorkStream::rewind() { + _stream->seek(0, 0); + _stream->clearErr(); + _endOfData = false; + _lastSample[0].index = 0; + _lastSample[0].sample = 0; + _lastSample[1].index = 0; + _lastSample[1].sample = 0; + + return true; +} + +Audio::RewindableAudioStream *makeRawZorkStream(Common::SeekableReadStream *stream, + int rate, + bool stereo, + DisposeAfterUse::Flag disposeAfterUse) { + if (stereo) + assert(stream->size() % 2 == 0); + + return new RawZorkStream(rate, stereo, disposeAfterUse, stream); +} + +Audio::RewindableAudioStream *makeRawZorkStream(const byte *buffer, uint32 size, + int rate, + bool stereo, + DisposeAfterUse::Flag disposeAfterUse) { + return makeRawZorkStream(new Common::MemoryReadStream(buffer, size, disposeAfterUse), rate, stereo, DisposeAfterUse::YES); +} + +Audio::RewindableAudioStream *makeRawZorkStream(const Common::String &filePath, ZVision *engine) { + Common::File *file = new Common::File(); + assert(file->open(filePath)); + + Common::String fileName = getFileName(filePath); + fileName.toLowercase(); + + SoundParams soundParams; + + if (engine->getGameId() == GID_NEMESIS) { + for (int i = 0; i < 6; ++i) { + if (RawZorkStream::_zNemSoundParamLookupTable[i].identifier == (fileName[6])) + soundParams = RawZorkStream::_zNemSoundParamLookupTable[i]; + } + } + else if (engine->getGameId() == GID_GRANDINQUISITOR) { + for (int i = 0; i < 6; ++i) { + if (RawZorkStream::_zgiSoundParamLookupTable[i].identifier == (fileName[7])) + soundParams = RawZorkStream::_zgiSoundParamLookupTable[i]; + } + } + + if (soundParams.packed) { + return makeRawZorkStream(wrapBufferedSeekableReadStream(file, 2048, DisposeAfterUse::YES), soundParams.rate, soundParams.stereo, DisposeAfterUse::YES); + } else { + byte flags = 0; + if (soundParams.stereo) + flags |= Audio::FLAG_STEREO; + + return Audio::makeRawStream(file, soundParams.rate, flags, DisposeAfterUse::YES); + } +} + +} // End of namespace ZVision diff --git a/engines/zvision/zork_raw.h b/engines/zvision/zork_raw.h new file mode 100644 index 0000000000..481ea79f2d --- /dev/null +++ b/engines/zvision/zork_raw.h @@ -0,0 +1,120 @@ +/* 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 ZVISION_ZORK_RAW_H +#define ZVISION_ZORK_RAW_H + +#include "audio/audiostream.h" + + +namespace Common { +class SeekableReadStream; +} + +namespace ZVision { + +class ZVision; + +struct SoundParams { + char identifier; + uint32 rate; + bool stereo; + bool packed; +}; + +/** + * This is a stream, which allows for playing raw ADPCM data from a stream. + */ +class RawZorkStream : public Audio::RewindableAudioStream { +public: + RawZorkStream(uint32 rate, bool stereo, DisposeAfterUse::Flag disposeStream, Common::SeekableReadStream *stream); + + ~RawZorkStream() { + } + +public: + static const SoundParams _zNemSoundParamLookupTable[6]; + static const SoundParams _zgiSoundParamLookupTable[5]; + +private: + const int _rate; // Sample rate of stream + Audio::Timestamp _playtime; // Calculated total play time + Common::DisposablePtr<Common::SeekableReadStream> _stream; // Stream to read data from + bool _endOfData; // Whether the stream end has been reached + uint _stereo; + + /** + * Holds the frequency and index from the last sample + * 0 holds the left channel, 1 holds the right channel + */ + struct { + int32 sample; + int16 index; + } _lastSample[2]; + + static const int16 _stepAdjustmentTable[8]; + static const int32 _amplitudeLookupTable[89]; + +public: + int readBuffer(int16 *buffer, const int numSamples); + + bool isStereo() const { return true; } + bool endOfData() const { return _endOfData; } + + int getRate() const { return _rate; } + Audio::Timestamp getLength() const { return _playtime; } + + bool rewind(); +}; + +/** + * Creates an audio stream, which plays from the given buffer. + * + * @param buffer Buffer to play from. + * @param size Size of the buffer in bytes. + * @param rate Rate of the sound data. + * @param dispose AfterUse Whether to free the buffer after use (with free!). + * @return The new SeekableAudioStream (or 0 on failure). + */ +Audio::RewindableAudioStream *makeRawZorkStream(const byte *buffer, uint32 size, + int rate, + bool stereo, + DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES); + +/** + * Creates an audio stream, which plays from the given stream. + * + * @param stream Stream object to play from. + * @param rate Rate of the sound data. + * @param dispose AfterUse Whether to delete the stream after use. + * @return The new SeekableAudioStream (or 0 on failure). + */ +Audio::RewindableAudioStream *makeRawZorkStream(Common::SeekableReadStream *stream, + int rate, + bool stereo, + DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES); + +Audio::RewindableAudioStream *makeRawZorkStream(const Common::String &filePath, ZVision *engine); + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/zvision.cpp b/engines/zvision/zvision.cpp new file mode 100644 index 0000000000..6d8ae6d5a7 --- /dev/null +++ b/engines/zvision/zvision.cpp @@ -0,0 +1,183 @@ +/* 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 "common/scummsys.h" + +#include "zvision/zvision.h" + +#include "zvision/console.h" +#include "zvision/script_manager.h" +#include "zvision/render_manager.h" +#include "zvision/cursor_manager.h" +#include "zvision/save_manager.h" +#include "zvision/string_manager.h" +#include "zvision/zfs_archive.h" +#include "zvision/detection.h" + +#include "common/config-manager.h" +#include "common/debug.h" +#include "common/debug-channels.h" +#include "common/textconsole.h" +#include "common/error.h" +#include "common/system.h" +#include "common/file.h" + +#include "engines/util.h" + +#include "audio/mixer.h" + + +namespace ZVision { + +ZVision::ZVision(OSystem *syst, const ZVisionGameDescription *gameDesc) + : Engine(syst), + _gameDescription(gameDesc), + _workingWindow((WINDOW_WIDTH - WORKING_WINDOW_WIDTH) / 2, (WINDOW_HEIGHT - WORKING_WINDOW_HEIGHT) / 2, ((WINDOW_WIDTH - WORKING_WINDOW_WIDTH) / 2) + WORKING_WINDOW_WIDTH, ((WINDOW_HEIGHT - WORKING_WINDOW_HEIGHT) / 2) + WORKING_WINDOW_HEIGHT), + _pixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0), /*RGB 565*/ + _desiredFrameTime(33), /* ~30 fps */ + _clock(_system), + _scriptManager(nullptr), + _renderManager(nullptr), + _saveManager(nullptr), + _stringManager(nullptr), + _cursorManager(nullptr) { + + debug(1, "ZVision::ZVision"); +} + +ZVision::~ZVision() { + debug(1, "ZVision::~ZVision"); + + // Dispose of resources + delete _console; + delete _cursorManager; + delete _stringManager; + delete _saveManager; + delete _renderManager; + delete _scriptManager; + delete _rnd; + + // Remove all of our debug levels + DebugMan.clearAllDebugChannels(); +} + +void ZVision::initialize() { + const Common::FSNode gameDataDir(ConfMan.get("path")); + // TODO: There are 10 file clashes when we flatten the directories. + // From a quick look, the files are exactly the same, so it shouldn't matter. + // But I'm noting it here just in-case it does become a problem. + SearchMan.addSubDirectoryMatching(gameDataDir, "data1", 0, 4, true); + SearchMan.addSubDirectoryMatching(gameDataDir, "data2", 0, 4, true); + SearchMan.addSubDirectoryMatching(gameDataDir, "data3", 0, 4, true); + SearchMan.addSubDirectoryMatching(gameDataDir, "zassets1", 0, 2, true); + SearchMan.addSubDirectoryMatching(gameDataDir, "zassets2", 0, 2, true); + SearchMan.addSubDirectoryMatching(gameDataDir, "znemmx", 0, 1, true); + SearchMan.addSubDirectoryMatching(gameDataDir, "zgi", 0, 4, true); + SearchMan.addSubDirectoryMatching(gameDataDir, "fonts", 0, 1, true); + + // Find zfs archive files + Common::ArchiveMemberList list; + SearchMan.listMatchingMembers(list, "*.zfs"); + + // Register the file entries within the zfs archives with the SearchMan + for (Common::ArchiveMemberList::iterator iter = list.begin(); iter != list.end(); ++iter) { + Common::String name = (*iter)->getName(); + Common::SeekableReadStream *stream = (*iter)->createReadStream(); + ZfsArchive *archive = new ZfsArchive(name, stream); + + delete stream; + + SearchMan.add(name, archive); + } + + initGraphics(WINDOW_WIDTH, WINDOW_HEIGHT, true, &_pixelFormat); + + // Register random source + _rnd = new Common::RandomSource("zvision"); + + // Create managers + _scriptManager = new ScriptManager(this); + _renderManager = new RenderManager(_system, WINDOW_WIDTH, WINDOW_HEIGHT, _workingWindow, _pixelFormat); + _saveManager = new SaveManager(this); + _stringManager = new StringManager(this); + _cursorManager = new CursorManager(this, &_pixelFormat); + + // Initialize the managers + _cursorManager->initialize(); + _scriptManager->initialize(); + _stringManager->initialize(_gameDescription->gameId); + + // Create debugger console. It requires GFX to be initialized + _console = new Console(this); +} + +Common::Error ZVision::run() { + initialize(); + + // Main loop + while (!shouldQuit()) { + _clock.update(); + uint32 currentTime = _clock.getLastMeasuredTime(); + uint32 deltaTime = _clock.getDeltaTime(); + + processEvents(); + + // Call _renderManager->update() first so the background renders + // before anything that puzzles/controls will render + _renderManager->update(deltaTime); + _scriptManager->update(deltaTime); + + // Render the backBuffer to the screen + _renderManager->renderBackbufferToScreen(); + + // Update the screen + _system->updateScreen(); + + // Calculate the frame delay based off a desired frame time + int delay = _desiredFrameTime - int32(_system->getMillis() - currentTime); + // Ensure non-negative + delay = delay < 0 ? 0 : delay; + _system->delayMillis(delay); + } + + return Common::kNoError; +} + +void ZVision::pauseEngineIntern(bool pause) { + _mixer->pauseAll(pause); + + if (pause) { + _clock.stop(); + } else { + _clock.start(); + } +} + +Common::String ZVision::generateSaveFileName(uint slot) { + return Common::String::format("%s.%02u", _targetName.c_str(), slot); +} + +Common::String ZVision::generateAutoSaveFileName() { + return Common::String::format("%s.auto", _targetName.c_str()); +} + +} // End of namespace ZVision diff --git a/engines/zvision/zvision.h b/engines/zvision/zvision.h new file mode 100644 index 0000000000..84784d9a89 --- /dev/null +++ b/engines/zvision/zvision.h @@ -0,0 +1,145 @@ +/* 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 ZVISION_ZVISION_H +#define ZVISION_ZVISION_H + +#include "zvision/detection.h" +#include "zvision/clock.h" + +#include "common/random.h" +#include "common/events.h" + +#include "engines/engine.h" + +#include "graphics/pixelformat.h" + +#include "gui/debugger.h" + + +namespace Video { +class VideoDecoder; +} + +namespace ZVision { + +struct ZVisionGameDescription; +class Console; +class ScriptManager; +class RenderManager; +class CursorManager; +class StringManager; +class SaveManager; +class RlfAnimation; + +class ZVision : public Engine { +public: + ZVision(OSystem *syst, const ZVisionGameDescription *gameDesc); + ~ZVision(); + +public: + /** + * A Rectangle centered inside the actual window. All in-game coordinates + * are given in this coordinate space. Also, all images are clipped to the + * edges of this Rectangle + */ + const Common::Rect _workingWindow; + const Graphics::PixelFormat _pixelFormat; + +private: + enum { + WINDOW_WIDTH = 640, + WINDOW_HEIGHT = 480, + WORKING_WINDOW_WIDTH = 512, + WORKING_WINDOW_HEIGHT = 320, + + ROTATION_SCREEN_EDGE_OFFSET = 60, + MAX_ROTATION_SPEED = 400 // Pixels per second + }; + + Console *_console; + const ZVisionGameDescription *_gameDescription; + + const int _desiredFrameTime; + + // We need random numbers + Common::RandomSource *_rnd; + + // Managers + ScriptManager *_scriptManager; + RenderManager *_renderManager; + CursorManager *_cursorManager; + SaveManager *_saveManager; + StringManager *_stringManager; + + // Clock + Clock _clock; + + // To prevent allocation every time we process events + Common::Event _event; + +public: + uint32 getFeatures() const; + Common::Language getLanguage() const; + Common::Error run(); + void pauseEngineIntern(bool pause); + + ScriptManager *getScriptManager() const { return _scriptManager; } + RenderManager *getRenderManager() const { return _renderManager; } + CursorManager *getCursorManager() const { return _cursorManager; } + SaveManager *getSaveManager() const { return _saveManager; } + StringManager *getStringManager() const { return _stringManager; } + Common::RandomSource *getRandomSource() const { return _rnd; } + ZVisionGameId getGameId() const { return _gameDescription->gameId; } + + /** + * Play a video until it is finished. This is a blocking call. It will call + * _clock.stop() when the video starts and _clock.start() when the video finishes. + * It will also consume all events during video playback. + * + * @param videoDecoder The video to play + * @param destRect Where to put the video. (In working window coords) + * @param skippable If true, the video can be skipped at any time using [Spacebar] + */ + void playVideo(Video::VideoDecoder &videoDecoder, const Common::Rect &destRect = Common::Rect(0, 0, 0, 0), bool skippable = true); + + Common::String generateSaveFileName(uint slot); + Common::String generateAutoSaveFileName(); + +private: + void initialize(); + void initFonts(); + + void parseStrFile(const Common::String fileName); + + /** Called every frame from ZVision::run() to process any events from EventMan */ + void processEvents(); + + void onMouseDown(const Common::Point &pos); + void onMouseUp(const Common::Point &pos); + void onMouseMove(const Common::Point &pos); +}; + +} // End of namespace ZVision + +#endif diff --git a/graphics/surface.cpp b/graphics/surface.cpp index 929157203e..777c1058fb 100644 --- a/graphics/surface.cpp +++ b/graphics/surface.cpp @@ -133,6 +133,30 @@ const Surface Surface::getSubArea(const Common::Rect &area) const { return subSurface; } +void Surface::copyRectToSurface(const void *buffer, int srcPitch, int destX, int destY, int width, int height) { + assert(buffer); + + assert(destX >= 0 && destX < w); + assert(destY >= 0 && destY < h); + assert(height > 0 && destY + height <= h); + assert(width > 0 && destX + width <= w); + + // Copy buffer data to internal buffer + const byte *src = (const byte *)buffer; + byte *dst = (byte *)getBasePtr(destX, destY); + for (int i = 0; i < height; i++) { + memcpy(dst, src, width * format.bytesPerPixel); + src += srcPitch; + dst += pitch; + } +} + +void Surface::copyRectToSurface(const Graphics::Surface &srcSurface, int destX, int destY, const Common::Rect subRect) { + assert(srcSurface.format == format); + + copyRectToSurface(srcSurface.getBasePtr(subRect.left, subRect.top), srcSurface.pitch, destX, destY, subRect.width(), subRect.height()); +} + void Surface::hLine(int x, int y, int x2, uint32 color) { // Clipping if (y < 0 || y >= h) diff --git a/graphics/surface.h b/graphics/surface.h index b08d4a5cb7..07e289b0bb 100644 --- a/graphics/surface.h +++ b/graphics/surface.h @@ -209,6 +209,30 @@ public: const Surface getSubArea(const Common::Rect &area) const; /** + * Copies a bitmap to the Surface internal buffer. The pixel format + * of buffer must match the pixel format of the Surface. + * + * @param buffer The buffer containing the graphics data source + * @param pitch The pitch of the buffer (number of bytes in a scanline) + * @param destX The x coordinate of the destination rectangle + * @param destY The y coordinate of the destination rectangle + * @param width The width of the destination rectangle + * @param height The height of the destination rectangle + */ + void copyRectToSurface(const void *buffer, int srcPitch, int destX, int destY, int width, int height); + /** + * Copies a bitmap to the Surface internal buffer. The pixel format + * of buffer must match the pixel format of the Surface. + * + * @param srcSurface The source of the bitmap data + * @param destX The x coordinate of the destination rectangle + * @param destY The y coordinate of the destination rectangle + * @param subRect The subRect of surface to be blitted + * @return + */ + void copyRectToSurface(const Graphics::Surface &srcSurface, int destX, int destY, const Common::Rect subRect); + + /** * Convert the data to another pixel format. * * This works in-place. This means it will not create an additional buffer diff --git a/gui/credits.h b/gui/credits.h index 423b48804a..aca3745631 100644 --- a/gui/credits.h +++ b/gui/credits.h @@ -77,6 +77,12 @@ static const char *credits[] = { "C0""Ludvig Strigeus", "C2""(retired)", "", +"C1""AVALANCHE", +"A0""Peter Bozso", +"C0""Peter Bozs\363", +"A0""Arnaud Boutonne", +"C0""Arnaud Boutonn\351", +"", "C1""CGE", "A0""Arnaud Boutonne", "C0""Arnaud Boutonn\351", @@ -297,6 +303,9 @@ static const char *credits[] = { "A0""Einar Johan T. Somaaen", "C0""Einar Johan T. S\370m\345en", "", +"C1""ZVision", +"C0""Adrian Astley", +"", "", "C1""Backend Teams", "C1""Android", @@ -771,6 +780,8 @@ static const char *credits[] = { "C2""For additional work on the original MT-32 emulator", "C0""James Woodcock", "C2""Soundtrack enhancements", +"C0""Anton Yartsev", +"C2""For the original re-implementation of the ZVision engine", "C0""Tony Warriner and everyone at Revolution Software Ltd. for sharing with us the source of some of their brilliant games, allowing us to release Beneath a Steel Sky as freeware... and generally being supportive above and beyond the call of duty.", "C0""", "C0""John Passfield and Steve Stamatiadis for sharing the source of their classic title, Flight of the Amazon Queen and also being incredibly supportive.", diff --git a/video/avi_decoder.cpp b/video/avi_decoder.cpp index 5e4b91d1bd..aee2d88988 100644 --- a/video/avi_decoder.cpp +++ b/video/avi_decoder.cpp @@ -101,6 +101,10 @@ AVIDecoder::~AVIDecoder() { close(); } +AVIDecoder::AVIAudioTrack *AVIDecoder::createAudioTrack(AVIStreamHeader sHeader, PCMWaveFormat wvInfo) { + return new AVIAudioTrack(sHeader, wvInfo, _soundType); +} + void AVIDecoder::initCommon() { _decodedHeader = false; _foundMovieList = false; @@ -293,7 +297,7 @@ void AVIDecoder::handleStreamHeader(uint32 size) { if (wvInfo.channels == 2) sHeader.sampleSize /= 2; - addTrack(new AVIAudioTrack(sHeader, wvInfo, _soundType)); + addTrack(createAudioTrack(sHeader, wvInfo)); } // Ensure that we're at the end of the chunk diff --git a/video/avi_decoder.h b/video/avi_decoder.h index fffcbfbe16..80c11b1e09 100644 --- a/video/avi_decoder.h +++ b/video/avi_decoder.h @@ -74,7 +74,6 @@ protected: void readNextPacket(); bool seekIntern(const Audio::Timestamp &time); -private: struct BitmapInfoHeader { uint32 size; uint32 width; @@ -203,7 +202,7 @@ private: AVIAudioTrack(const AVIStreamHeader &streamHeader, const PCMWaveFormat &waveFormat, Audio::Mixer::SoundType soundType); ~AVIAudioTrack(); - void queueSound(Common::SeekableReadStream *stream); + virtual void queueSound(Common::SeekableReadStream *stream); Audio::Mixer::SoundType getSoundType() const { return _soundType; } void skipAudio(const Audio::Timestamp &time, const Audio::Timestamp &frameTime); void resetStream(); @@ -214,7 +213,6 @@ private: protected: Audio::AudioStream *getAudioStream() const; - private: // Audio Codecs enum { kWaveFormatNone = 0, @@ -249,6 +247,9 @@ private: void handleStreamHeader(uint32 size); uint16 getStreamType(uint32 tag) const { return tag & 0xFFFF; } byte getStreamIndex(uint32 tag) const; + +public: + virtual AVIAudioTrack *createAudioTrack(AVIStreamHeader sHeader, PCMWaveFormat wvInfo); }; } // End of namespace Video diff --git a/video/codecs/truemotion1.h b/video/codecs/truemotion1.h index b2a35cf873..6ac09af24b 100644 --- a/video/codecs/truemotion1.h +++ b/video/codecs/truemotion1.h @@ -22,8 +22,8 @@ // Based on the TrueMotion 1 decoder by Alex Beregszaszi & Mike Melanson in FFmpeg -// Only compile if SCI32 is enabled or if we're building dynamic modules -#if defined(ENABLE_SCI32) || defined(DYNAMIC_MODULES) +// Only compile if SCI32 is enabled, ZVISION is enabled, or if we're building dynamic modules +#if defined(ENABLE_SCI32) || defined(ENABLE_ZVISION) || defined(DYNAMIC_MODULES) #ifndef VIDEO_CODECS_TRUEMOTION1_H #define VIDEO_CODECS_TRUEMOTION1_H |