aboutsummaryrefslogtreecommitdiff
path: root/engines
diff options
context:
space:
mode:
Diffstat (limited to 'engines')
-rw-r--r--engines/avalanche/animation.cpp1459
-rw-r--r--engines/avalanche/animation.h170
-rw-r--r--engines/avalanche/avalanche.cpp530
-rw-r--r--engines/avalanche/avalanche.h340
-rw-r--r--engines/avalanche/avalot.cpp1744
-rw-r--r--engines/avalanche/avalot.h104
-rw-r--r--engines/avalanche/background.cpp369
-rw-r--r--engines/avalanche/background.h79
-rw-r--r--engines/avalanche/closing.cpp75
-rw-r--r--engines/avalanche/closing.h62
-rw-r--r--engines/avalanche/console.cpp54
-rw-r--r--engines/avalanche/console.h51
-rw-r--r--engines/avalanche/detection.cpp213
-rw-r--r--engines/avalanche/dialogs.cpp1196
-rw-r--r--engines/avalanche/dialogs.h116
-rw-r--r--engines/avalanche/enums.h133
-rw-r--r--engines/avalanche/graphics.cpp767
-rw-r--r--engines/avalanche/graphics.h142
-rw-r--r--engines/avalanche/menu.cpp834
-rw-r--r--engines/avalanche/menu.h181
-rw-r--r--engines/avalanche/module.mk26
-rw-r--r--engines/avalanche/parser.cpp2470
-rw-r--r--engines/avalanche/parser.h155
-rw-r--r--engines/avalanche/pingo.cpp106
-rw-r--r--engines/avalanche/pingo.h59
-rw-r--r--engines/avalanche/sequence.cpp228
-rw-r--r--engines/avalanche/sequence.h80
-rw-r--r--engines/avalanche/sound.cpp88
-rw-r--r--engines/avalanche/sound.h53
-rw-r--r--engines/avalanche/timer.cpp693
-rw-r--r--engines/avalanche/timer.h178
-rw-r--r--engines/configure.engines2
-rw-r--r--engines/draci/draci.cpp26
-rw-r--r--engines/draci/game.cpp53
-rw-r--r--engines/draci/sprite.cpp4
-rw-r--r--engines/draci/surface.cpp2
-rw-r--r--engines/draci/walking.cpp6
-rw-r--r--engines/draci/walking.h12
-rw-r--r--engines/drascula/animation.cpp4
-rw-r--r--engines/drascula/converse.cpp6
-rw-r--r--engines/drascula/drascula.cpp55
-rw-r--r--engines/drascula/graphics.cpp10
-rw-r--r--engines/drascula/objects.cpp5
-rw-r--r--engines/dreamweb/dreamweb.cpp7
-rw-r--r--engines/engines.mk10
-rw-r--r--engines/fullpipe/interaction.cpp2
-rw-r--r--engines/fullpipe/messages.cpp21
-rw-r--r--engines/fullpipe/messages.h2
-rw-r--r--engines/fullpipe/motion.cpp553
-rw-r--r--engines/fullpipe/motion.h56
-rw-r--r--engines/fullpipe/utils.h10
-rw-r--r--engines/lastexpress/game/inventory.cpp4
-rw-r--r--engines/lastexpress/game/inventory.h2
-rw-r--r--engines/neverhood/resourceman.cpp2
-rw-r--r--engines/plugins_table.h6
-rw-r--r--engines/testbed/config-params.cpp2
-rw-r--r--engines/testbed/config.h2
-rw-r--r--engines/testbed/events.cpp5
-rw-r--r--engines/tony/gfxcore.cpp1
-rw-r--r--engines/touche/touche.cpp22
-rw-r--r--engines/tsage/core.cpp37
-rw-r--r--engines/tsage/ringworld2/ringworld2_logic.cpp12
-rw-r--r--engines/tsage/ringworld2/ringworld2_logic.h1
-rw-r--r--engines/tsage/ringworld2/ringworld2_scenes0.cpp2
-rw-r--r--engines/tsage/ringworld2/ringworld2_scenes1.cpp10
-rw-r--r--engines/tsage/ringworld2/ringworld2_scenes1.h2
-rw-r--r--engines/tsage/sound.cpp164
-rw-r--r--engines/tsage/sound.h36
-rw-r--r--engines/wintermute/video/video_theora_player.h2
-rw-r--r--engines/zvision/actions.cpp394
-rw-r--r--engines/zvision/actions.h346
-rw-r--r--engines/zvision/animation_control.cpp263
-rw-r--r--engines/zvision/animation_control.h87
-rw-r--r--engines/zvision/clock.cpp70
-rw-r--r--engines/zvision/clock.h78
-rw-r--r--engines/zvision/console.cpp218
-rw-r--r--engines/zvision/console.h55
-rw-r--r--engines/zvision/control.cpp124
-rw-r--r--engines/zvision/control.h146
-rw-r--r--engines/zvision/cursor.cpp94
-rw-r--r--engines/zvision/cursor.h66
-rw-r--r--engines/zvision/cursor_manager.cpp151
-rw-r--r--engines/zvision/cursor_manager.h114
-rw-r--r--engines/zvision/detection.cpp272
-rw-r--r--engines/zvision/detection.h44
-rw-r--r--engines/zvision/events.cpp186
-rw-r--r--engines/zvision/input_control.cpp142
-rw-r--r--engines/zvision/input_control.h60
-rw-r--r--engines/zvision/inventory_manager.h28
-rw-r--r--engines/zvision/lever_control.cpp402
-rw-r--r--engines/zvision/lever_control.h127
-rw-r--r--engines/zvision/lzss_read_stream.cpp103
-rw-r--r--engines/zvision/lzss_read_stream.h72
-rw-r--r--engines/zvision/menu.h28
-rw-r--r--engines/zvision/module.mk43
-rw-r--r--engines/zvision/push_toggle_control.cpp98
-rw-r--r--engines/zvision/push_toggle_control.h67
-rw-r--r--engines/zvision/puzzle.h81
-rw-r--r--engines/zvision/render_manager.cpp526
-rw-r--r--engines/zvision/render_manager.h328
-rw-r--r--engines/zvision/render_table.cpp240
-rw-r--r--engines/zvision/render_table.h85
-rw-r--r--engines/zvision/rlf_animation.cpp331
-rw-r--r--engines/zvision/rlf_animation.h163
-rw-r--r--engines/zvision/save_manager.cpp206
-rw-r--r--engines/zvision/save_manager.h91
-rw-r--r--engines/zvision/scr_file_handling.cpp302
-rw-r--r--engines/zvision/script_manager.cpp446
-rw-r--r--engines/zvision/script_manager.h212
-rw-r--r--engines/zvision/single_value_container.cpp348
-rw-r--r--engines/zvision/single_value_container.h183
-rw-r--r--engines/zvision/string_manager.cpp255
-rw-r--r--engines/zvision/string_manager.h85
-rw-r--r--engines/zvision/subtitles.h29
-rw-r--r--engines/zvision/timer_node.cpp59
-rw-r--r--engines/zvision/timer_node.h54
-rw-r--r--engines/zvision/truetype_font.cpp116
-rw-r--r--engines/zvision/truetype_font.h81
-rw-r--r--engines/zvision/utility.cpp237
-rw-r--r--engines/zvision/utility.h115
-rw-r--r--engines/zvision/video.cpp171
-rw-r--r--engines/zvision/zfs_archive.cpp157
-rw-r--r--engines/zvision/zfs_archive.h126
-rw-r--r--engines/zvision/zork_avi_decoder.cpp53
-rw-r--r--engines/zvision/zork_avi_decoder.h60
-rw-r--r--engines/zvision/zork_raw.cpp209
-rw-r--r--engines/zvision/zork_raw.h120
-rw-r--r--engines/zvision/zvision.cpp183
-rw-r--r--engines/zvision/zvision.h145
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