diff options
Diffstat (limited to 'engines')
129 files changed, 23314 insertions, 274 deletions
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 |