aboutsummaryrefslogtreecommitdiff
path: root/engines/sword2
diff options
context:
space:
mode:
Diffstat (limited to 'engines/sword2')
-rw-r--r--engines/sword2/_mouse.cpp247
-rw-r--r--engines/sword2/animation.cpp535
-rw-r--r--engines/sword2/animation.h103
-rw-r--r--engines/sword2/anims.cpp308
-rw-r--r--engines/sword2/build_display.cpp1070
-rw-r--r--engines/sword2/build_display.h441
-rw-r--r--engines/sword2/console.cpp826
-rw-r--r--engines/sword2/console.h127
-rw-r--r--engines/sword2/controls.cpp1416
-rw-r--r--engines/sword2/controls.h183
-rw-r--r--engines/sword2/d_draw.cpp71
-rw-r--r--engines/sword2/debug.cpp377
-rw-r--r--engines/sword2/debug.h33
-rw-r--r--engines/sword2/defs.h205
-rw-r--r--engines/sword2/events.cpp99
-rw-r--r--engines/sword2/function.cpp2479
-rw-r--r--engines/sword2/header.h502
-rw-r--r--engines/sword2/icons.cpp216
-rw-r--r--engines/sword2/icons.h41
-rw-r--r--engines/sword2/interpreter.cpp753
-rw-r--r--engines/sword2/interpreter.h96
-rw-r--r--engines/sword2/layers.cpp191
-rw-r--r--engines/sword2/layers.h29
-rw-r--r--engines/sword2/logic.cpp267
-rw-r--r--engines/sword2/logic.h316
-rw-r--r--engines/sword2/maketext.cpp579
-rw-r--r--engines/sword2/maketext.h119
-rw-r--r--engines/sword2/memory.cpp244
-rw-r--r--engines/sword2/memory.h70
-rw-r--r--engines/sword2/menu.cpp291
-rw-r--r--engines/sword2/module.mk48
-rw-r--r--engines/sword2/mouse.cpp1437
-rw-r--r--engines/sword2/mouse.h257
-rw-r--r--engines/sword2/music.cpp856
-rw-r--r--engines/sword2/object.h342
-rw-r--r--engines/sword2/palette.cpp267
-rw-r--r--engines/sword2/protocol.cpp216
-rw-r--r--engines/sword2/rdwin.cpp118
-rw-r--r--engines/sword2/render.cpp584
-rw-r--r--engines/sword2/resman.cpp633
-rw-r--r--engines/sword2/resman.h134
-rw-r--r--engines/sword2/router.cpp2506
-rw-r--r--engines/sword2/router.h255
-rw-r--r--engines/sword2/save_rest.cpp414
-rw-r--r--engines/sword2/save_rest.h47
-rw-r--r--engines/sword2/scroll.cpp151
-rw-r--r--engines/sword2/sound.cpp319
-rw-r--r--engines/sword2/sound.h272
-rw-r--r--engines/sword2/speech.cpp229
-rw-r--r--engines/sword2/sprite.cpp655
-rw-r--r--engines/sword2/startup.cpp173
-rw-r--r--engines/sword2/sword2.cpp689
-rw-r--r--engines/sword2/sword2.h240
-rw-r--r--engines/sword2/sync.cpp76
-rw-r--r--engines/sword2/walker.cpp454
55 files changed, 23606 insertions, 0 deletions
diff --git a/engines/sword2/_mouse.cpp b/engines/sword2/_mouse.cpp
new file mode 100644
index 0000000000..db0cf00f73
--- /dev/null
+++ b/engines/sword2/_mouse.cpp
@@ -0,0 +1,247 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "common/stdafx.h"
+#include "common/system.h"
+#include "common/stream.h"
+
+#include "sword2/sword2.h"
+#include "sword2/defs.h"
+#include "sword2/mouse.h"
+
+namespace Sword2 {
+
+// This is the maximum mouse cursor size in the SDL backend
+#define MAX_MOUSE_W 80
+#define MAX_MOUSE_H 80
+
+#define MOUSEFLASHFRAME 6
+
+void Mouse::decompressMouse(byte *decomp, byte *comp, uint8 frame, int width, int height, int pitch, int xOff, int yOff) {
+ int32 size = width * height;
+ int32 i = 0;
+ int x = 0;
+ int y = 0;
+
+ comp = comp + READ_LE_UINT32(comp + frame * 4) - MOUSE_ANIM_HEADER_SIZE;
+
+ while (i < size) {
+ if (*comp > 183) {
+ decomp[(y + yOff) * pitch + x + xOff] = *comp++;
+ if (++x >= width) {
+ x = 0;
+ y++;
+ }
+ i++;
+ } else {
+ x += *comp;
+ while (x >= width) {
+ y++;
+ x -= width;
+ }
+ i += *comp++;
+ }
+ }
+}
+
+void Mouse::drawMouse() {
+ byte mouseData[MAX_MOUSE_W * MAX_MOUSE_H];
+
+ if (!_mouseAnim.data && !_luggageAnim.data)
+ return;
+
+ // When an object is used in the game, the mouse cursor should be a
+ // combination of a standard mouse cursor and a luggage cursor.
+ //
+ // However, judging by the original code luggage cursors can also
+ // appear on their own. I have no idea which cases though.
+
+ uint16 mouse_width = 0;
+ uint16 mouse_height = 0;
+ uint16 hotspot_x = 0;
+ uint16 hotspot_y = 0;
+ int deltaX = 0;
+ int deltaY = 0;
+
+ if (_mouseAnim.data) {
+ hotspot_x = _mouseAnim.xHotSpot;
+ hotspot_y = _mouseAnim.yHotSpot;
+ mouse_width = _mouseAnim.mousew;
+ mouse_height = _mouseAnim.mouseh;
+ }
+
+ if (_luggageAnim.data) {
+ if (!_mouseAnim.data) {
+ hotspot_x = _luggageAnim.xHotSpot;
+ hotspot_y = _luggageAnim.yHotSpot;
+ }
+ if (_luggageAnim.mousew > mouse_width)
+ mouse_width = _luggageAnim.mousew;
+ if (_luggageAnim.mouseh > mouse_height)
+ mouse_height = _luggageAnim.mouseh;
+ }
+
+ if (_mouseAnim.data && _luggageAnim.data) {
+ deltaX = _mouseAnim.xHotSpot - _luggageAnim.xHotSpot;
+ deltaY = _mouseAnim.yHotSpot - _luggageAnim.yHotSpot;
+ }
+
+ assert(deltaX >= 0);
+ assert(deltaY >= 0);
+
+ // HACK for maximum cursor size. (The SDL backend imposes this
+ // restriction)
+
+ if (mouse_width + deltaX > MAX_MOUSE_W)
+ deltaX = 80 - mouse_width;
+ if (mouse_height + deltaY > MAX_MOUSE_H)
+ deltaY = 80 - mouse_height;
+
+ mouse_width += deltaX;
+ mouse_height += deltaY;
+
+ if ((uint32)(mouse_width * mouse_height) > sizeof(mouseData)) {
+ warning("Mouse cursor too large");
+ return;
+ }
+
+ memset(mouseData, 0, mouse_width * mouse_height);
+
+ if (_luggageAnim.data)
+ decompressMouse(mouseData, _luggageAnim.data, 0,
+ _luggageAnim.mousew, _luggageAnim.mouseh,
+ mouse_width, deltaX, deltaY);
+
+ if (_mouseAnim.data)
+ decompressMouse(mouseData, _mouseAnim.data, _mouseFrame,
+ _mouseAnim.mousew, _mouseAnim.mouseh, mouse_width);
+
+ _vm->_system->setMouseCursor(mouseData, mouse_width, mouse_height, hotspot_x, hotspot_y, 0);
+}
+
+/**
+ * Animates the current mouse pointer
+ */
+
+int32 Mouse::animateMouse() {
+ uint8 prevMouseFrame = _mouseFrame;
+
+ if (!_mouseAnim.data)
+ return RDERR_UNKNOWN;
+
+ if (++_mouseFrame == _mouseAnim.noAnimFrames)
+ _mouseFrame = MOUSEFLASHFRAME;
+
+ if (_mouseFrame != prevMouseFrame)
+ drawMouse();
+
+ return RD_OK;
+}
+
+/**
+ * Sets the mouse cursor animation.
+ * @param ma a pointer to the animation data, or NULL to clear the current one
+ * @param size the size of the mouse animation data
+ * @param mouseFlash RDMOUSE_FLASH or RDMOUSE_NOFLASH, depending on whether
+ * or not there is a lead-in animation
+ */
+
+int32 Mouse::setMouseAnim(byte *ma, int32 size, int32 mouseFlash) {
+ free(_mouseAnim.data);
+ _mouseAnim.data = NULL;
+
+ if (ma) {
+ if (mouseFlash == RDMOUSE_FLASH)
+ _mouseFrame = 0;
+ else
+ _mouseFrame = MOUSEFLASHFRAME;
+
+ Common::MemoryReadStream readS(ma, size);
+
+ _mouseAnim.runTimeComp = readS.readByte();
+ _mouseAnim.noAnimFrames = readS.readByte();
+ _mouseAnim.xHotSpot = readS.readSByte();
+ _mouseAnim.yHotSpot = readS.readSByte();
+ _mouseAnim.mousew = readS.readByte();
+ _mouseAnim.mouseh = readS.readByte();
+
+ _mouseAnim.data = (byte *)malloc(size - MOUSE_ANIM_HEADER_SIZE);
+ if (!_mouseAnim.data)
+ return RDERR_OUTOFMEMORY;
+
+ readS.read(_mouseAnim.data, size - MOUSE_ANIM_HEADER_SIZE);
+
+ animateMouse();
+ drawMouse();
+
+ _vm->_system->showMouse(true);
+ } else {
+ if (_luggageAnim.data)
+ drawMouse();
+ else
+ _vm->_system->showMouse(false);
+ }
+
+ return RD_OK;
+}
+
+/**
+ * Sets the "luggage" animation to accompany the mouse animation. Luggage
+ * sprites are of the same format as mouse sprites.
+ * @param ma a pointer to the animation data, or NULL to clear the current one
+ * @param size the size of the animation data
+ */
+
+int32 Mouse::setLuggageAnim(byte *ma, int32 size) {
+ free(_luggageAnim.data);
+ _luggageAnim.data = NULL;
+
+ if (ma) {
+ Common::MemoryReadStream readS(ma, size);
+
+ _luggageAnim.runTimeComp = readS.readByte();
+ _luggageAnim.noAnimFrames = readS.readByte();
+ _luggageAnim.xHotSpot = readS.readSByte();
+ _luggageAnim.yHotSpot = readS.readSByte();
+ _luggageAnim.mousew = readS.readByte();
+ _luggageAnim.mouseh = readS.readByte();
+
+ _luggageAnim.data = (byte *)malloc(size - MOUSE_ANIM_HEADER_SIZE);
+ if (!_luggageAnim.data)
+ return RDERR_OUTOFMEMORY;
+
+ readS.read(_luggageAnim.data, size - MOUSE_ANIM_HEADER_SIZE);
+
+ animateMouse();
+ drawMouse();
+
+ _vm->_system->showMouse(true);
+ } else {
+ if (_mouseAnim.data)
+ drawMouse();
+ else
+ _vm->_system->showMouse(false);
+ }
+
+ return RD_OK;
+}
+
+} // End of namespace Sword2
diff --git a/engines/sword2/animation.cpp b/engines/sword2/animation.cpp
new file mode 100644
index 0000000000..2ba5df6792
--- /dev/null
+++ b/engines/sword2/animation.cpp
@@ -0,0 +1,535 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "common/stdafx.h"
+#include "common/file.h"
+#include "common/config-manager.h"
+#include "common/system.h"
+#include "sound/vorbis.h"
+#include "sound/mp3.h"
+
+#include "sword2/sword2.h"
+#include "sword2/defs.h"
+#include "sword2/maketext.h"
+#include "sword2/resman.h"
+#include "sword2/sound.h"
+#include "sword2/animation.h"
+
+namespace Sword2 {
+
+AnimationState::AnimationState(Sword2Engine *vm)
+ : BaseAnimationState(vm->_mixer, vm->_system, 640, 480), _vm(vm) {
+}
+
+AnimationState::~AnimationState() {
+}
+
+#ifdef BACKEND_8BIT
+
+void AnimationState::setPalette(byte *pal) {
+ _vm->_screen->setPalette(0, 256, pal, RDPAL_INSTANT);
+}
+
+#else
+
+void AnimationState::drawTextObject(SpriteInfo *s, byte *src) {
+ OverlayColor *dst = _overlay + RENDERWIDE * s->y + s->x;
+
+ // FIXME: These aren't the "right" colours, but look good to me.
+
+ OverlayColor pen = _sys->RGBToColor(255, 255, 255);
+ OverlayColor border = _sys->RGBToColor(0, 0, 0);
+
+ for (int y = 0; y < s->h; y++) {
+ for (int x = 0; x < s->w; x++) {
+ switch (src[x]) {
+ case 1:
+ dst[x] = border;
+ break;
+ case 255:
+ dst[x] = pen;
+ break;
+ default:
+ break;
+ }
+ }
+ dst += RENDERWIDE;
+ src += s->w;
+ }
+}
+
+#endif
+
+void AnimationState::clearScreen() {
+#ifdef BACKEND_8BIT
+ memset(_vm->_screen->getScreen(), 0, _movieWidth * _movieHeight);
+#else
+ OverlayColor black = _sys->RGBToColor(0, 0, 0);
+
+ for (int i = 0; i < _movieWidth * _movieHeight; i++)
+ _overlay[i] = black;
+#endif
+}
+
+void AnimationState::updateScreen() {
+#ifdef BACKEND_8BIT
+ byte *buf = _vm->_screen->getScreen() + ((480 - _movieHeight) / 2) * RENDERWIDE + (640 - _movieWidth) / 2;
+
+ _vm->_system->copyRectToScreen(buf, _movieWidth, (640 - _movieWidth) / 2, (480 - _movieHeight) / 2, _movieWidth, _movieHeight);
+#else
+ _sys->copyRectToOverlay(_overlay, _movieWidth, 0, 0, _movieWidth, _movieHeight);
+#endif
+ _vm->_system->updateScreen();
+}
+
+void AnimationState::drawYUV(int width, int height, byte *const *dat) {
+#ifdef BACKEND_8BIT
+ _vm->_screen->plotYUV(_lut, width, height, dat);
+#else
+ plotYUV(width, height, dat);
+#endif
+}
+
+MovieInfo MoviePlayer::_movies[] = {
+ { "carib", 222, false },
+ { "escape", 187, false },
+ { "eye", 248, false },
+ { "finale", 1485, false },
+ { "guard", 75, false },
+ { "intro", 1800, false },
+ { "jungle", 186, false },
+ { "museum", 167, false },
+ { "pablo", 75, false },
+ { "pyramid", 60, false },
+ { "quaram", 184, false },
+ { "river", 656, false },
+ { "sailing", 138, false },
+ { "shaman", 788, true },
+ { "stone1", 34, false },
+ { "stone2", 282, false },
+ { "stone3", 65, false },
+ { "demo", 60, false },
+ { "enddemo", 110, false }
+};
+
+MoviePlayer::MoviePlayer(Sword2Engine *vm)
+ : _vm(vm), _snd(_vm->_mixer), _sys(_vm->_system), _textSurface(NULL) {
+}
+
+void MoviePlayer::openTextObject(MovieTextObject *obj) {
+ if (obj->textSprite)
+ _vm->_screen->createSurface(obj->textSprite, &_textSurface);
+}
+
+void MoviePlayer::closeTextObject(MovieTextObject *obj) {
+ if (_textSurface) {
+ _vm->_screen->deleteSurface(_textSurface);
+ _textSurface = NULL;
+ }
+}
+
+void MoviePlayer::drawTextObject(AnimationState *anim, MovieTextObject *obj) {
+ if (obj->textSprite && _textSurface) {
+#ifdef BACKEND_8BIT
+ _vm->_screen->drawSurface(obj->textSprite, _textSurface);
+#else
+ if (anim)
+ anim->drawTextObject(obj->textSprite, _textSurface);
+ else
+ _vm->_screen->drawSurface(obj->textSprite, _textSurface);
+#endif
+ }
+}
+
+/**
+ * Plays an animated cutscene.
+ * @param filename the file name of the cutscene file
+ * @param text the subtitles and voiceovers for the cutscene
+ * @param leadInRes lead-in music resource id
+ * @param leadOutRes lead-out music resource id
+ */
+
+int32 MoviePlayer::play(const char *filename, MovieTextObject *text[], int32 leadInRes, int32 leadOutRes) {
+ Audio::SoundHandle leadInHandle;
+
+ // This happens if the user quits during the "eye" smacker
+ if (_vm->_quit)
+ return RD_OK;
+
+ if (leadInRes) {
+ byte *leadIn = _vm->_resman->openResource(leadInRes);
+ uint32 leadInLen = _vm->_resman->fetchLen(leadInRes) - ResHeader::size();
+
+ assert(_vm->_resman->fetchType(leadIn) == WAV_FILE);
+
+ leadIn += ResHeader::size();
+
+ _vm->_sound->playFx(&leadInHandle, leadIn, leadInLen, Audio::Mixer::kMaxChannelVolume, 0, false, Audio::Mixer::kMusicSoundType);
+ }
+
+ byte *leadOut = NULL;
+ uint32 leadOutLen = 0;
+
+ if (leadOutRes) {
+ leadOut = _vm->_resman->openResource(leadOutRes);
+ leadOutLen = _vm->_resman->fetchLen(leadOutRes) - ResHeader::size();
+
+ assert(_vm->_resman->fetchType(leadOut) == WAV_FILE);
+
+ leadOut += ResHeader::size();
+ }
+
+ _leadOutFrame = (uint)-1;
+
+ int i;
+
+ for (i = 0; i < ARRAYSIZE(_movies); i++) {
+ if (scumm_stricmp(filename, _movies[i].name) == 0) {
+ _seamless = _movies[i].seamless;
+ if (_movies[i].frames > 60)
+ _leadOutFrame = _movies[i].frames - 60;
+ break;
+ }
+ }
+
+ if (i == ARRAYSIZE(_movies))
+ warning("Unknown movie, '%s'", filename);
+
+#ifdef USE_MPEG2
+ playMPEG(filename, text, leadOut, leadOutLen);
+#else
+ // No MPEG2? Use the old 'Narration Only' hack
+ playDummy(filename, text, leadOut, leadOutLen);
+#endif
+
+ _snd->stopHandle(leadInHandle);
+
+ // Wait for the lead-out to stop, if there is any. Though don't do it
+ // for seamless movies, since they are meant to blend into the rest of
+ // the game.
+
+ if (!_seamless) {
+ while (_vm->_mixer->isSoundHandleActive(_leadOutHandle)) {
+ _vm->_screen->updateDisplay();
+ _vm->_system->delayMillis(30);
+ }
+ }
+
+ if (leadInRes)
+ _vm->_resman->closeResource(leadInRes);
+
+ if (leadOutRes)
+ _vm->_resman->closeResource(leadOutRes);
+
+ return RD_OK;
+}
+
+void MoviePlayer::playMPEG(const char *filename, MovieTextObject *text[], byte *leadOut, uint32 leadOutLen) {
+ uint frameCounter = 0, textCounter = 0;
+ Audio::SoundHandle handle;
+ bool skipCutscene = false, textVisible = false;
+ uint32 flags = Audio::Mixer::FLAG_16BITS;
+ bool startNextText = false;
+
+ byte oldPal[256 * 4];
+ memcpy(oldPal, _vm->_screen->getPalette(), sizeof(oldPal));
+
+ AnimationState *anim = new AnimationState(_vm);
+
+ if (!anim->init(filename)) {
+ delete anim;
+ // Missing Files? Use the old 'Narration Only' hack
+ playDummy(filename, text, leadOut, leadOutLen);
+ return;
+ }
+
+ // Clear the screen, because whatever is on it will be visible when the
+ // overlay is removed. And if there isn't an overlay, we don't want it
+ // to be visible during the cutscene. (Not all cutscenes cover the
+ // entire screen.)
+ _vm->_screen->clearScene();
+ _vm->_screen->updateDisplay();
+
+#ifndef SCUMM_BIG_ENDIAN
+ flags |= Audio::Mixer::FLAG_LITTLE_ENDIAN;
+#endif
+
+ while (!skipCutscene && anim->decodeFrame()) {
+ // The frame has been drawn. Now draw the subtitles, if any,
+ // before updating the screen.
+
+ if (text && text[textCounter]) {
+ if (frameCounter == text[textCounter]->startFrame) {
+ openTextObject(text[textCounter]);
+ textVisible = true;
+
+ if (text[textCounter]->speech) {
+ startNextText = true;
+ }
+ }
+
+ if (startNextText && !_snd->isSoundHandleActive(handle)) {
+ _snd->playRaw(&handle, text[textCounter]->speech, text[textCounter]->speechBufferSize, 22050, flags);
+ startNextText = false;
+ }
+
+ if (frameCounter == text[textCounter]->endFrame) {
+ closeTextObject(text[textCounter]);
+ textCounter++;
+ textVisible = false;
+ }
+
+ if (textVisible)
+ drawTextObject(anim, text[textCounter]);
+ }
+
+ anim->updateScreen();
+ frameCounter++;
+
+ if (frameCounter == _leadOutFrame && leadOut)
+ _vm->_sound->playFx(&_leadOutHandle, leadOut, leadOutLen, Audio::Mixer::kMaxChannelVolume, 0, false, Audio::Mixer::kMusicSoundType);
+
+ OSystem::Event event;
+ while (_sys->pollEvent(event)) {
+ switch (event.type) {
+#ifndef BACKEND_8BIT
+ case OSystem::EVENT_SCREEN_CHANGED:
+ anim->buildLookup();
+ break;
+#endif
+ case OSystem::EVENT_KEYDOWN:
+ if (event.kbd.keycode == 27)
+ skipCutscene = true;
+ break;
+ case OSystem::EVENT_QUIT:
+ _vm->closeGame();
+ skipCutscene = true;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ if (!skipCutscene) {
+ // Sleep for one frame so that the last frame is displayed.
+ _sys->delayMillis(1000 / 12);
+ }
+
+ if (!_seamless) {
+ // Most movies fade to black on their own, but not all of them.
+ // Since we may be hanging around in the cutscene player for a
+ // while longer, waiting for the lead-out sound to finish,
+ // paint the overlay black.
+
+ anim->clearScreen();
+ }
+
+ // If the speech is still playing, redraw the subtitles. At least in
+ // the English version this is most noticeable in the "carib" cutscene.
+
+ if (textVisible && _snd->isSoundHandleActive(handle))
+ drawTextObject(anim, text[textCounter]);
+
+ if (text)
+ closeTextObject(text[textCounter]);
+
+ anim->updateScreen();
+
+ // Wait for the voice to stop playing. This is to make sure that we
+ // don't cut off the speech in mid-sentence, and - even more
+ // importantly - that we don't free the sound buffer while it's in use.
+
+ if (skipCutscene)
+ _snd->stopHandle(handle);
+
+ while (_snd->isSoundHandleActive(handle)) {
+ _vm->_screen->updateDisplay(false);
+ _sys->delayMillis(100);
+ }
+
+ if (!_seamless) {
+ // Clear the screen again
+ anim->clearScreen();
+ anim->updateScreen();
+ }
+
+ _vm->_screen->setPalette(0, 256, oldPal, RDPAL_INSTANT);
+
+ delete anim;
+}
+
+/**
+ * This just plays the cutscene with voiceovers / subtitles, in case the files
+ * are missing.
+ */
+
+void MoviePlayer::playDummy(const char *filename, MovieTextObject *text[], byte *leadOut, uint32 leadOutLen) {
+ if (!text)
+ return;
+
+ int frameCounter = 0, textCounter = 0;
+
+ byte oldPal[256 * 4];
+ byte tmpPal[256 * 4];
+
+ _vm->_screen->clearScene();
+
+ // HACK: Draw instructions
+ //
+ // I'm using the the menu area, because that's unlikely to be touched
+ // by anything else during the cutscene.
+
+ memset(_vm->_screen->getScreen(), 0, _vm->_screen->getScreenWide() * MENUDEEP);
+
+ byte *data;
+
+ // Russian version substituted latin characters with cyrillic. That's
+ // why it renders completely unreadable with default message
+ if (Common::parseLanguage(ConfMan.get("language")) == Common::RU_RUS) {
+ byte msg[] = "Po\344uk - to\344\345ko pev\345: hagmute k\344abuwy Ucke\343n, u\344u nocetute ca\343t npoekta u ckava\343te budeo po\344uku";
+ data = _vm->_fontRenderer->makeTextSprite(msg, RENDERWIDE, 255, _vm->_speechFontId);
+ } else {
+ // TODO: Translate message to other languages?
+#ifdef USE_MPEG2
+ byte msg[] = "Cutscene - Narration Only: Press ESC to exit, or visit www.scummvm.org to download cutscene videos";
+#else
+ byte msg[] = "Cutscene - Narration Only: Press ESC to exit, or recompile ScummVM with MPEG2 support";
+#endif
+
+ data = _vm->_fontRenderer->makeTextSprite(msg, RENDERWIDE, 255, _vm->_speechFontId);
+ }
+
+ FrameHeader frame_head;
+ SpriteInfo msgSprite;
+ byte *msgSurface;
+
+ frame_head.read(data);
+
+ msgSprite.x = _vm->_screen->getScreenWide() / 2 - frame_head.width / 2;
+ msgSprite.y = MENUDEEP / 2 - frame_head.height / 2;
+ msgSprite.w = frame_head.width;
+ msgSprite.h = frame_head.height;
+ msgSprite.type = RDSPR_NOCOMPRESSION;
+ msgSprite.data = data + FrameHeader::size();
+
+ _vm->_screen->createSurface(&msgSprite, &msgSurface);
+ _vm->_screen->drawSurface(&msgSprite, msgSurface);
+ _vm->_screen->deleteSurface(msgSurface);
+
+ free(data);
+
+ // In case the cutscene has a long lead-in, start just before the first
+ // line of text.
+
+ frameCounter = text[0]->startFrame - 12;
+
+ // Fake a palette that will hopefully make the text visible. In the
+ // opening cutscene it seems to use colours 1 (black) and 255 (white).
+
+ memcpy(oldPal, _vm->_screen->getPalette(), sizeof(oldPal));
+ memset(tmpPal, 0, sizeof(tmpPal));
+ tmpPal[255 * 4 + 0] = 255;
+ tmpPal[255 * 4 + 1] = 255;
+ tmpPal[255 * 4 + 2] = 255;
+ _vm->_screen->setPalette(0, 256, tmpPal, RDPAL_INSTANT);
+
+ Audio::SoundHandle handle;
+
+ bool skipCutscene = false;
+
+ uint32 flags = Audio::Mixer::FLAG_16BITS;
+
+#ifndef SCUMM_BIG_ENDIAN
+ flags |= Audio::Mixer::FLAG_LITTLE_ENDIAN;
+#endif
+
+ while (1) {
+ if (!text[textCounter])
+ break;
+
+ if (frameCounter == text[textCounter]->startFrame) {
+ _vm->_screen->clearScene();
+ openTextObject(text[textCounter]);
+ drawTextObject(NULL, text[textCounter]);
+ if (text[textCounter]->speech) {
+ _snd->playRaw(&handle, text[textCounter]->speech, text[textCounter]->speechBufferSize, 22050, flags);
+ }
+ }
+
+ if (frameCounter == text[textCounter]->endFrame) {
+ closeTextObject(text[textCounter]);
+ _vm->_screen->clearScene();
+ _vm->_screen->setNeedFullRedraw();
+ textCounter++;
+ }
+
+ frameCounter++;
+ _vm->_screen->updateDisplay();
+
+ KeyboardEvent *ke = _vm->keyboardEvent();
+
+ if ((ke && ke->keycode == 27) || _vm->_quit) {
+ _snd->stopHandle(handle);
+ skipCutscene = true;
+ break;
+ }
+
+ // Simulate ~12 frames per second. I don't know what frame rate
+ // the original movies had, or even if it was constant, but
+ // this seems to work reasonably.
+
+ _sys->delayMillis(90);
+ }
+
+ // Wait for the voice to stop playing. This is to make sure that we
+ // don't cut off the speech in mid-sentence, and - even more
+ // importantly - that we don't free the sound buffer while it's in use.
+
+ while (_snd->isSoundHandleActive(handle)) {
+ _vm->_screen->updateDisplay(false);
+ _sys->delayMillis(100);
+ }
+
+ closeTextObject(text[textCounter]);
+
+ _vm->_screen->clearScene();
+ _vm->_screen->setNeedFullRedraw();
+
+ // HACK: Remove the instructions created above
+ Common::Rect r;
+
+ memset(_vm->_screen->getScreen(), 0, _vm->_screen->getScreenWide() * MENUDEEP);
+ r.left = r.top = 0;
+ r.right = _vm->_screen->getScreenWide();
+ r.bottom = MENUDEEP;
+ _vm->_screen->updateRect(&r);
+
+ // FIXME: For now, only play the lead-out music for cutscenes that have
+ // subtitles.
+
+ if (!skipCutscene && leadOut)
+ _vm->_sound->playFx(&_leadOutHandle, leadOut, leadOutLen, Audio::Mixer::kMaxChannelVolume, 0, false, Audio::Mixer::kMusicSoundType);
+
+ _vm->_screen->setPalette(0, 256, oldPal, RDPAL_INSTANT);
+}
+
+} // End of namespace Sword2
diff --git a/engines/sword2/animation.h b/engines/sword2/animation.h
new file mode 100644
index 0000000000..e3a554ccd7
--- /dev/null
+++ b/engines/sword2/animation.h
@@ -0,0 +1,103 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef ANIMATION_H
+#define ANIMATION_H
+
+#include "graphics/animation.h"
+#include "sound/mixer.h"
+
+namespace Sword2 {
+
+struct SpriteInfo;
+
+// This is the structure which is passed to the sequence player. It includes
+// the smack to play, and any text lines which are to be displayed over the top
+// of the sequence.
+
+struct MovieTextObject {
+ uint16 startFrame;
+ uint16 endFrame;
+ SpriteInfo *textSprite;
+ uint32 speechBufferSize;
+ uint16 *speech;
+};
+
+class AnimationState : public ::Graphics::BaseAnimationState {
+private:
+ Sword2Engine *_vm;
+
+public:
+ AnimationState(Sword2Engine *vm);
+ ~AnimationState();
+
+#ifndef BACKEND_8BIT
+ void drawTextObject(SpriteInfo *s, byte *src);
+#endif
+
+ void clearScreen();
+ void updateScreen();
+
+private:
+ void drawYUV(int width, int height, byte *const *dat);
+
+#ifdef BACKEND_8BIT
+ void setPalette(byte *pal);
+#endif
+};
+
+struct MovieInfo {
+ char name[9];
+ uint frames;
+ bool seamless;
+};
+
+class MoviePlayer {
+private:
+ Sword2Engine *_vm;
+ Audio::Mixer *_snd;
+ OSystem *_sys;
+
+ byte *_textSurface;
+
+ Audio::SoundHandle _leadOutHandle;
+
+ uint _leadOutFrame;
+ bool _seamless;
+
+ static struct MovieInfo _movies[];
+
+ void openTextObject(MovieTextObject *obj);
+ void closeTextObject(MovieTextObject *obj);
+ void drawTextObject(AnimationState *anim, MovieTextObject *obj);
+
+ void playMPEG(const char *filename, MovieTextObject *text[], byte *leadOut, uint32 leadOutLen);
+ void playDummy(const char *filename, MovieTextObject *text[], byte *leadOut, uint32 leadOutLen);
+
+public:
+ MoviePlayer(Sword2Engine *vm);
+ int32 play(const char *filename, MovieTextObject *text[], int32 leadInRes, int32 leadOutRes);
+};
+
+} // End of namespace Sword2
+
+#endif
diff --git a/engines/sword2/anims.cpp b/engines/sword2/anims.cpp
new file mode 100644
index 0000000000..e64903a61f
--- /dev/null
+++ b/engines/sword2/anims.cpp
@@ -0,0 +1,308 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+// ---------------------------------------------------------------------------
+// A more intelligent version of the old ANIMS.C
+// All this stuff by James
+// DON'T TOUCH!
+// ---------------------------------------------------------------------------
+
+#include "common/stdafx.h"
+#include "common/file.h"
+
+#include "sword2/sword2.h"
+#include "sword2/defs.h"
+#include "sword2/build_display.h"
+#include "sword2/interpreter.h"
+#include "sword2/logic.h"
+#include "sword2/maketext.h"
+#include "sword2/resman.h"
+#include "sword2/router.h"
+#include "sword2/sound.h"
+#include "sword2/animation.h"
+
+namespace Sword2 {
+
+int Router::doAnimate(byte *ob_logic, byte *ob_graph, int32 animRes, bool reverse) {
+ AnimHeader anim_head;
+ byte *anim_file;
+
+ ObjectLogic obLogic(ob_logic);
+ ObjectGraphic obGraph(ob_graph);
+
+ if (obLogic.getLooping() == 0) {
+ byte *ptr;
+
+ // This is the start of the anim - set up the first frame
+
+ // For testing all anims!
+ // A script loop can send every resource number to the anim
+ // function & it will only run the valid ones. See
+ // 'testing_routines' object in George's Player Character
+ // section of linc
+
+ if (_vm->_logic->readVar(SYSTEM_TESTING_ANIMS)) {
+ if (!_vm->_resman->checkValid(animRes)) {
+ // Not a valid resource number. Switch off
+ // the sprite. Don't animate - just continue
+ // script next cycle.
+ setSpriteStatus(ob_graph, NO_SPRITE);
+ return IR_STOP;
+ }
+
+ ptr = _vm->_resman->openResource(animRes);
+
+ // if it's not an animation file
+ if (_vm->_resman->fetchType(animRes) != ANIMATION_FILE) {
+ _vm->_resman->closeResource(animRes);
+
+ // switch off the sprite
+ // don't animate - just continue
+ // script next cycle
+ setSpriteStatus(ob_graph, NO_SPRITE);
+ return IR_STOP;
+ }
+
+ _vm->_resman->closeResource(animRes);
+
+ // switch on the sprite
+ setSpriteStatus(ob_graph, SORT_SPRITE);
+ }
+
+ assert(animRes);
+
+ // open anim file
+ anim_file = _vm->_resman->openResource(animRes);
+
+ assert(_vm->_resman->fetchType(animRes) == ANIMATION_FILE);
+
+ // point to anim header
+ anim_head.read(_vm->fetchAnimHeader(anim_file));
+
+ // now running an anim, looping back to this call again
+ obLogic.setLooping(1);
+ obGraph.setAnimResource(animRes);
+
+ if (reverse)
+ obGraph.setAnimPc(anim_head.noAnimFrames - 1);
+ else
+ obGraph.setAnimPc(0);
+ } else if (_vm->_logic->getSync() != -1) {
+ // We've received a sync - return to script immediately
+ debug(5, "**sync stopped %d**", _vm->_logic->readVar(ID));
+
+ // If sync received, anim finishes right now (remaining on
+ // last frame). Quit animation, but continue script.
+ obLogic.setLooping(0);
+ return IR_CONT;
+ } else {
+ // Not first frame, and no sync received - set up the next
+ // frame of the anim.
+
+ // open anim file and point to anim header
+ anim_file = _vm->_resman->openResource(obGraph.getAnimResource());
+ anim_head.read(_vm->fetchAnimHeader(anim_file));
+
+ if (reverse)
+ obGraph.setAnimPc(obGraph.getAnimPc() - 1);
+ else
+ obGraph.setAnimPc(obGraph.getAnimPc() + 1);
+ }
+
+ // check for end of anim
+
+ if (reverse) {
+ if (obGraph.getAnimPc() == 0)
+ obLogic.setLooping(0);
+ } else {
+ if (obGraph.getAnimPc() == anim_head.noAnimFrames - 1)
+ obLogic.setLooping(0);
+ }
+
+ // close the anim file
+ _vm->_resman->closeResource(obGraph.getAnimResource());
+
+ // check if we want the script to loop back & call this function again
+ return obLogic.getLooping() ? IR_REPEAT : IR_STOP;
+}
+
+int Router::megaTableAnimate(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *animTable, bool reverse) {
+ int32 animRes = 0;
+
+ // If this is the start of the anim, read the anim table to get the
+ // appropriate anim resource
+
+ ObjectLogic obLogic(ob_logic);
+
+ if (obLogic.getLooping() == 0) {
+ ObjectMega obMega(ob_mega);
+
+ // Appropriate anim resource is in 'table[direction]'
+ animRes = READ_LE_UINT32(animTable + 4 * obMega.getCurDir());
+ }
+
+ return doAnimate(ob_logic, ob_graph, animRes, reverse);
+}
+
+void Router::setSpriteStatus(byte *ob_graph, uint32 type) {
+ ObjectGraphic obGraph(ob_graph);
+
+ // Remove the previous status, but don't affect the shading upper-word
+ obGraph.setType((obGraph.getType() & 0xffff0000) | type);
+}
+
+void Router::setSpriteShading(byte *ob_graph, uint32 type) {
+ ObjectGraphic obGraph(ob_graph);
+
+ // Remove the previous shading, but don't affect the status lower-word.
+ // Note that mega frames may still be shaded automatically, even when
+ // not sent 'RDSPR_SHADOW'.
+ obGraph.setType((obGraph.getType() & 0x0000ffff) | type);
+}
+
+void Logic::createSequenceSpeech(MovieTextObject *sequenceText[]) {
+ uint32 line;
+ uint32 local_text;
+ uint32 text_res;
+ byte *text;
+ uint32 wavId; // ie. offical text number (actor text number)
+ bool speechRunning;
+
+ // for each sequence text line that's been logged
+ for (line = 0; line < _sequenceTextLines; line++) {
+ // allocate this structure
+ sequenceText[line] = new MovieTextObject;
+
+ sequenceText[line]->startFrame = _sequenceTextList[line].startFrame;
+ sequenceText[line]->endFrame = _sequenceTextList[line].endFrame;
+
+ // pull out the text line to get the official text number
+ // (for wav id)
+
+ text_res = _sequenceTextList[line].textNumber / SIZE;
+ local_text = _sequenceTextList[line].textNumber & 0xffff;
+
+ // open text resource & get the line
+ text = _vm->fetchTextLine(_vm->_resman->openResource(text_res), local_text);
+ wavId = (int32)READ_LE_UINT16(text);
+
+ // now ok to close the text file
+ _vm->_resman->closeResource(text_res);
+
+ // 1st word of text line is the official line number
+ debug(5,"(%d) SEQUENCE TEXT: %s", READ_LE_UINT16(text), text + 2);
+
+ // is it to be speech or subtitles or both?
+ // assume speech is not running until know otherwise
+
+ speechRunning = false;
+ _sequenceTextList[line].speech_mem = NULL;
+ sequenceText[line]->speech = NULL;
+
+ if (!_vm->_sound->isSpeechMute()) {
+ _sequenceTextList[line].speechBufferSize = _vm->_sound->preFetchCompSpeech(wavId, &_sequenceTextList[line].speech_mem);
+ if (_sequenceTextList[line].speechBufferSize) {
+ // ok, we've got speech!
+ speechRunning = true;
+ }
+ }
+
+ // if we want subtitles, or speech failed to load
+
+ if (_vm->getSubtitles() || !speechRunning) {
+ // open text resource & get the line
+ text = _vm->fetchTextLine(_vm->_resman->openResource(text_res), local_text);
+ // make the sprite
+ // 'text+2' to skip the first 2 bytes which form the
+ // line reference number
+
+ // NB. The mem block containing the text sprite is
+ // currently FLOATING!
+
+ // When rendering text over a sequence we need a
+ // different colour for the border.
+
+ _sequenceTextList[line].text_mem = _vm->_fontRenderer->makeTextSprite(text + 2, 600, 255, _vm->_speechFontId, 1);
+
+ // ok to close the text resource now
+ _vm->_resman->closeResource(text_res);
+ } else {
+ _sequenceTextList[line].text_mem = NULL;
+ sequenceText[line]->textSprite = NULL;
+ }
+ }
+
+ // for drivers: NULL-terminate the array of pointers to
+ // MovieTextObject's
+ sequenceText[_sequenceTextLines] = NULL;
+
+ for (line = 0; line < _sequenceTextLines; line++) {
+ // if we've made a text sprite for this line...
+
+ if (_sequenceTextList[line].text_mem) {
+ // now fill out the SpriteInfo structure in the
+ // MovieTextObjectStructure
+ FrameHeader frame;
+
+ frame.read(_sequenceTextList[line].text_mem);
+
+ sequenceText[line]->textSprite = new SpriteInfo;
+
+ // center text at bottom of screen
+ sequenceText[line]->textSprite->x = 320 - frame.width / 2;
+ sequenceText[line]->textSprite->y = 440 - frame.height;
+ sequenceText[line]->textSprite->w = frame.width;
+ sequenceText[line]->textSprite->h = frame.height;
+ sequenceText[line]->textSprite->type = RDSPR_DISPLAYALIGN | RDSPR_NOCOMPRESSION;
+ sequenceText[line]->textSprite->data = _sequenceTextList[line].text_mem + FrameHeader::size();
+ }
+
+ // if we've loaded a speech sample for this line...
+
+ if (_sequenceTextList[line].speech_mem) {
+ // for drivers: set up pointer to decompressed wav in
+ // memory
+
+ sequenceText[line]->speechBufferSize = _sequenceTextList[line].speechBufferSize;
+ sequenceText[line]->speech = _sequenceTextList[line].speech_mem;
+ }
+ }
+}
+
+void Logic::clearSequenceSpeech(MovieTextObject *sequenceText[]) {
+ for (uint i = 0; i < _sequenceTextLines; i++) {
+ // free up the memory used by this MovieTextObject
+ delete sequenceText[i];
+
+ // free up the mem block containing this text sprite
+ if (_sequenceTextList[i].text_mem)
+ free(_sequenceTextList[i].text_mem);
+
+ // free up the mem block containing this speech sample
+ if (_sequenceTextList[i].speech_mem)
+ free(_sequenceTextList[i].speech_mem);
+ }
+
+ // IMPORTANT! Reset the line count ready for the next sequence!
+ _sequenceTextLines = 0;
+}
+
+} // End of namespace Sword2
diff --git a/engines/sword2/build_display.cpp b/engines/sword2/build_display.cpp
new file mode 100644
index 0000000000..bc5a31a1f9
--- /dev/null
+++ b/engines/sword2/build_display.cpp
@@ -0,0 +1,1070 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+// ---------------------------------------------------------------------------
+// BUILD_DISPLAY.CPP like the old spr_engi but slightly more aptly named
+// ---------------------------------------------------------------------------
+
+#include "common/stdafx.h"
+#include "common/system.h"
+
+#include "sword2/sword2.h"
+#include "sword2/console.h"
+#include "sword2/defs.h"
+#include "sword2/logic.h"
+#include "sword2/maketext.h"
+#include "sword2/mouse.h"
+#include "sword2/resman.h"
+#include "sword2/sound.h"
+
+namespace Sword2 {
+
+Screen::Screen(Sword2Engine *vm, int16 width, int16 height) {
+ _vm = vm;
+
+ _dirtyGrid = _buffer = NULL;
+
+ _vm->_system->initSize(width, height);
+
+ _screenWide = width;
+ _screenDeep = height;
+
+ _gridWide = width / CELLWIDE;
+ _gridDeep = height / CELLDEEP;
+
+ if ((width % CELLWIDE) || (height % CELLDEEP))
+ error("Bad cell size");
+
+ _dirtyGrid = (byte *)calloc(_gridWide, _gridDeep);
+ if (!_dirtyGrid)
+ error("Could not initialise dirty grid");
+
+ _buffer = (byte *)malloc(width * height);
+ if (!_buffer)
+ error("Could not initialise display");
+
+ for (int i = 0; i < ARRAYSIZE(_blockSurfaces); i++)
+ _blockSurfaces[i] = NULL;
+
+ _lightMask = NULL;
+ _needFullRedraw = false;
+
+ memset(&_thisScreen, 0, sizeof(_thisScreen));
+
+ _fps = 0;
+ _frameCount = 0;
+ _cycleTime = 0;
+
+ _lastPaletteRes = 0;
+
+ _scrollFraction = 16;
+
+ _largestLayerArea = 0;
+ _largestSpriteArea = 0;
+
+ strcpy(_largestLayerInfo, "largest layer: none registered");
+ strcpy(_largestSpriteInfo, "largest sprite: none registered");
+
+ _fadeStatus = RDFADE_NONE;
+ _renderAverageTime = 60;
+
+ _layer = 0;
+}
+
+Screen::~Screen() {
+ free(_buffer);
+ free(_dirtyGrid);
+ closeBackgroundLayer();
+ free(_lightMask);
+}
+
+void Screen::buildDisplay() {
+ if (_thisScreen.new_palette) {
+ // start the layer palette fading up
+ startNewPalette();
+
+ // should be reset to zero at start of each screen change
+ _largestLayerArea = 0;
+ _largestSpriteArea = 0;
+ }
+
+ // Does this ever happen?
+ if (!_thisScreen.background_layer_id)
+ return;
+
+ // there is a valid screen to run
+
+ setScrollTarget(_thisScreen.scroll_offset_x, _thisScreen.scroll_offset_y);
+ _vm->_mouse->animateMouse();
+ startRenderCycle();
+
+ byte *file = _vm->_resman->openResource(_thisScreen.background_layer_id);
+
+ MultiScreenHeader screenLayerTable;
+
+ screenLayerTable.read(file + ResHeader::size());
+
+ // Render at least one frame, but if the screen is scrolling, and if
+ // there is time left, we will render extra frames to smooth out the
+ // scrolling.
+
+ do {
+ // first background parallax + related anims
+ if (screenLayerTable.bg_parallax[0]) {
+ renderParallax(_vm->fetchBackgroundParallaxLayer(file, 0), 0);
+ drawBackPar0Frames();
+ }
+
+ // second background parallax + related anims
+ if (screenLayerTable.bg_parallax[1]) {
+ renderParallax(_vm->fetchBackgroundParallaxLayer(file, 1), 1);
+ drawBackPar1Frames();
+ }
+
+ // normal backround layer (just the one!)
+ renderParallax(_vm->fetchBackgroundLayer(file), 2);
+
+ // sprites & layers
+ drawBackFrames(); // background sprites
+ drawSortFrames(file); // sorted sprites & layers
+ drawForeFrames(); // foreground sprites
+
+ // first foreground parallax + related anims
+
+ if (screenLayerTable.fg_parallax[0]) {
+ renderParallax(_vm->fetchForegroundParallaxLayer(file, 0), 3);
+ drawForePar0Frames();
+ }
+
+ // second foreground parallax + related anims
+
+ if (screenLayerTable.fg_parallax[1]) {
+ renderParallax(_vm->fetchForegroundParallaxLayer(file, 1), 4);
+ drawForePar1Frames();
+ }
+
+ _vm->_debugger->drawDebugGraphics();
+ _vm->_fontRenderer->printTextBlocs();
+ _vm->_mouse->processMenu();
+
+ updateDisplay();
+
+ _frameCount++;
+ if (_vm->getMillis() > _cycleTime) {
+ _fps = _frameCount;
+ _frameCount = 0;
+ _cycleTime = _vm->getMillis() + 1000;
+ }
+ } while (!endRenderCycle());
+
+ _vm->_resman->closeResource(_thisScreen.background_layer_id);
+}
+
+/**
+ * Fades down and displays a message on the screen.
+ * @param text The message
+ * @param time The number of seconds to display the message, or 0 to display it
+ * until the user clicks the mouse or presses a key.
+ */
+
+void Screen::displayMsg(byte *text, int time) {
+ byte pal[256 * 4];
+ byte oldPal[256 * 4];
+
+ debug(2, "DisplayMsg: %s", text);
+
+ if (getFadeStatus() != RDFADE_BLACK) {
+ fadeDown();
+ waitForFade();
+ }
+
+ _vm->_mouse->setMouse(0);
+ _vm->_mouse->setLuggage(0);
+ _vm->_mouse->closeMenuImmediately();
+
+ clearScene();
+
+ byte *text_spr = _vm->_fontRenderer->makeTextSprite(text, 640, 187, _vm->_speechFontId);
+
+ FrameHeader frame;
+
+ frame.read(text_spr);
+
+ SpriteInfo spriteInfo;
+
+ spriteInfo.x = _screenWide / 2 - frame.width / 2;
+ if (!time)
+ spriteInfo.y = _screenDeep / 2 - frame.height / 2 - MENUDEEP;
+ else
+ spriteInfo.y = 400 - frame.height;
+ spriteInfo.w = frame.width;
+ spriteInfo.h = frame.height;
+ spriteInfo.scale = 0;
+ spriteInfo.scaledWidth = 0;
+ spriteInfo.scaledHeight = 0;
+ spriteInfo.type = RDSPR_DISPLAYALIGN | RDSPR_NOCOMPRESSION | RDSPR_TRANS;
+ spriteInfo.blend = 0;
+ spriteInfo.data = text_spr + FrameHeader::size();
+ spriteInfo.colourTable = 0;
+
+ uint32 rv = drawSprite(&spriteInfo);
+ if (rv)
+ error("Driver Error %.8x (in DisplayMsg)", rv);
+
+ memcpy(oldPal, _palette, sizeof(oldPal));
+ memset(pal, 0, sizeof(pal));
+
+ pal[187 * 4 + 0] = 255;
+ pal[187 * 4 + 1] = 255;
+ pal[187 * 4 + 2] = 255;
+
+ setPalette(0, 256, pal, RDPAL_FADE);
+ fadeUp();
+ free(text_spr);
+ waitForFade();
+
+ if (time > 0) {
+ uint32 targetTime = _vm->getMillis() + (time * 1000);
+ _vm->sleepUntil(targetTime);
+ } else {
+ while (!_vm->_quit) {
+ MouseEvent *me = _vm->mouseEvent();
+ if (me && (me->buttons & (RD_LEFTBUTTONDOWN | RD_RIGHTBUTTONDOWN)))
+ break;
+
+ if (_vm->keyboardEvent())
+ break;
+
+ updateDisplay();
+ _vm->_system->delayMillis(50);
+ }
+ }
+
+ fadeDown();
+ waitForFade();
+ clearScene();
+ setPalette(0, 256, oldPal, RDPAL_FADE);
+ fadeUp();
+}
+
+void Screen::drawBackPar0Frames() {
+ // frame attached to 1st background parallax
+ for (uint i = 0; i < _curBgp0; i++)
+ processImage(&_bgp0List[i]);
+}
+
+void Screen::drawBackPar1Frames() {
+ // frame attached to 2nd background parallax
+ for (uint i = 0; i < _curBgp1; i++)
+ processImage(&_bgp1List[i]);
+}
+
+void Screen::drawBackFrames() {
+ // background sprite, fixed to main background
+ for (uint i = 0; i < _curBack; i++)
+ processImage(&_backList[i]);
+}
+
+void Screen::drawSortFrames(byte *file) {
+ uint i, j;
+
+ // Sort the sort list. Used to be a separate function, but it was only
+ // called once, right before calling drawSortFrames().
+
+ if (_curSort > 1) {
+ for (i = 0; i < _curSort - 1; i++) {
+ for (j = 0; j < _curSort - 1; j++) {
+ if (_sortList[_sortOrder[j]].sort_y > _sortList[_sortOrder[j + 1]].sort_y) {
+ SWAP(_sortOrder[j], _sortOrder[j + 1]);
+ }
+ }
+ }
+ }
+
+ // Draw the sorted frames - layers, shrinkers & normal flat sprites
+
+ for (i = 0; i < _curSort; i++) {
+ if (_sortList[_sortOrder[i]].layer_number) {
+ // it's a layer - minus 1 for true layer number
+ // we need to know from the BuildUnit because the
+ // layers will have been sorted in random order
+ processLayer(file, _sortList[_sortOrder[i]].layer_number - 1);
+ } else {
+ // it's a sprite
+ processImage(&_sortList[_sortOrder[i]]);
+ }
+ }
+}
+
+void Screen::drawForeFrames() {
+ // foreground sprite, fixed to main background
+ for (uint i = 0; i < _curFore; i++)
+ processImage(&_foreList[i]);
+}
+
+void Screen::drawForePar0Frames() {
+ // frame attached to 1st foreground parallax
+ for (uint i = 0; i < _curFgp0; i++)
+ processImage(&_fgp0List[i]);
+}
+
+void Screen::drawForePar1Frames() {
+ // frame attached to 2nd foreground parallax
+ for (uint i = 0; i < _curFgp1; i++)
+ processImage(&_fgp1List[i]);
+}
+
+void Screen::processLayer(byte *file, uint32 layer_number) {
+ LayerHeader layer_head;
+
+ layer_head.read(_vm->fetchLayerHeader(file, layer_number));
+
+ SpriteInfo spriteInfo;
+
+ spriteInfo.x = layer_head.x;
+ spriteInfo.y = layer_head.y;
+ spriteInfo.w = layer_head.width;
+ spriteInfo.scale = 0;
+ spriteInfo.scaledWidth = 0;
+ spriteInfo.scaledHeight = 0;
+ spriteInfo.h = layer_head.height;
+ spriteInfo.type = RDSPR_TRANS | RDSPR_RLE256FAST;
+ spriteInfo.blend = 0;
+ spriteInfo.data = file + ResHeader::size() + layer_head.offset;
+ spriteInfo.colourTable = 0;
+
+ // check for largest layer for debug info
+
+ uint32 current_layer_area = layer_head.width * layer_head.height;
+
+ if (current_layer_area > _largestLayerArea) {
+ byte buf[NAME_LEN];
+
+ _largestLayerArea = current_layer_area;
+ sprintf(_largestLayerInfo,
+ "largest layer: %s layer(%d) is %dx%d",
+ _vm->_resman->fetchName(_thisScreen.background_layer_id, buf),
+ layer_number, layer_head.width, layer_head.height);
+ }
+
+ uint32 rv = drawSprite(&spriteInfo);
+ if (rv)
+ error("Driver Error %.8x in processLayer(%d)", rv, layer_number);
+}
+
+void Screen::processImage(BuildUnit *build_unit) {
+ byte *file = _vm->_resman->openResource(build_unit->anim_resource);
+ byte *colTablePtr = NULL;
+
+ byte *frame = _vm->fetchFrameHeader(file, build_unit->anim_pc);
+
+ AnimHeader anim_head;
+ CdtEntry cdt_entry;
+ FrameHeader frame_head;
+
+ anim_head.read(_vm->fetchAnimHeader(file));
+ cdt_entry.read(_vm->fetchCdtEntry(file, build_unit->anim_pc));
+ frame_head.read(frame);
+
+ // so that 0-colour is transparent
+ uint32 spriteType = RDSPR_TRANS;
+
+ if (anim_head.blend)
+ spriteType |= RDSPR_BLEND;
+
+ // if the frame is to be flipped (only really applicable to frames
+ // using offsets)
+ if (cdt_entry.frameType & FRAME_FLIPPED)
+ spriteType |= RDSPR_FLIP;
+
+ if (cdt_entry.frameType & FRAME_256_FAST) {
+ // scaling, shading & blending don't work with RLE256FAST
+ // but the same compression can be decompressed using the
+ // RLE256 routines!
+
+ // NOTE: If this restriction refers to drawSprite(), I don't
+ // think we have it any more. But I'm not sure.
+
+ if (build_unit->scale || anim_head.blend || build_unit->shadingFlag)
+ spriteType |= RDSPR_RLE256;
+ else
+ spriteType |= RDSPR_RLE256FAST;
+ } else {
+ switch (anim_head.runTimeComp) {
+ case NONE:
+ spriteType |= RDSPR_NOCOMPRESSION;
+ break;
+ case RLE256:
+ spriteType |= RDSPR_RLE256;
+ break;
+ case RLE16:
+ spriteType |= RDSPR_RLE16;
+ // points to just after last cdt_entry, ie.
+ // start of colour table
+ colTablePtr = _vm->fetchAnimHeader(file) + AnimHeader::size() + anim_head.noAnimFrames * CdtEntry::size();
+ break;
+ }
+ }
+
+ // if we want this frame to be affected by the shading mask,
+ // add the status bit
+ if (build_unit->shadingFlag)
+ spriteType |= RDSPR_SHADOW;
+
+ SpriteInfo spriteInfo;
+
+ spriteInfo.x = build_unit->x;
+ spriteInfo.y = build_unit->y;
+ spriteInfo.w = frame_head.width;
+ spriteInfo.h = frame_head.height;
+ spriteInfo.scale = build_unit->scale;
+ spriteInfo.scaledWidth = build_unit->scaled_width;
+ spriteInfo.scaledHeight = build_unit->scaled_height;
+ spriteInfo.type = spriteType;
+ spriteInfo.blend = anim_head.blend;
+ // points to just after frame header, ie. start of sprite data
+ spriteInfo.data = frame + FrameHeader::size();
+ spriteInfo.colourTable = colTablePtr;
+
+ // check for largest layer for debug info
+ uint32 current_sprite_area = frame_head.width * frame_head.height;
+
+ if (current_sprite_area > _largestSpriteArea) {
+ byte buf[NAME_LEN];
+
+ _largestSpriteArea = current_sprite_area;
+ sprintf(_largestSpriteInfo,
+ "largest sprite: %s frame(%d) is %dx%d",
+ _vm->_resman->fetchName(build_unit->anim_resource, buf),
+ build_unit->anim_pc,
+ frame_head.width,
+ frame_head.height);
+ }
+
+ if (_vm->_logic->readVar(SYSTEM_TESTING_ANIMS)) { // see anims.cpp
+ // bring the anim into the visible screen
+ // but leave extra pixel at edge for box
+ if (spriteInfo.x + spriteInfo.scaledWidth >= 639)
+ spriteInfo.x = 639 - spriteInfo.scaledWidth;
+
+ if (spriteInfo.y + spriteInfo.scaledHeight >= 399)
+ spriteInfo.y = 399 - spriteInfo.scaledHeight;
+
+ if (spriteInfo.x < 1)
+ spriteInfo.x = 1;
+
+ if (spriteInfo.y < 1)
+ spriteInfo.y = 1;
+
+ // create box to surround sprite - just outside sprite box
+ _vm->_debugger->_rectX1 = spriteInfo.x - 1;
+ _vm->_debugger->_rectY1 = spriteInfo.y - 1;
+ _vm->_debugger->_rectX2 = spriteInfo.x + spriteInfo.scaledWidth;
+ _vm->_debugger->_rectY2 = spriteInfo.y + spriteInfo.scaledHeight;
+ }
+
+ uint32 rv = drawSprite(&spriteInfo);
+ if (rv) {
+ byte buf[NAME_LEN];
+
+ error("Driver Error %.8x with sprite %s (%d) in processImage",
+ rv,
+ _vm->_resman->fetchName(build_unit->anim_resource, buf),
+ build_unit->anim_resource);
+ }
+
+ // release the anim resource
+ _vm->_resman->closeResource(build_unit->anim_resource);
+}
+
+void Screen::resetRenderLists() {
+ // reset the sort lists - do this before a logic loop
+ // takes into account the fact that the start of the list is pre-built
+ // with the special sortable layers
+
+ _curBgp0 = 0;
+ _curBgp1 = 0;
+ _curBack = 0;
+ // beginning of sort list is setup with the special sort layers
+ _curSort = _thisScreen.number_of_layers;
+ _curFore = 0;
+ _curFgp0 = 0;
+ _curFgp1 = 0;
+
+ if (_curSort) {
+ // there are some layers - so rebuild the sort order list
+ for (uint i = 0; i < _curSort; i++)
+ _sortOrder[i] = i;
+ }
+}
+
+void Screen::registerFrame(byte *ob_mouse, byte *ob_graph, byte *ob_mega, BuildUnit *build_unit) {
+ ObjectGraphic obGraph(ob_graph);
+ ObjectMega obMega(ob_mega);
+
+ assert(obGraph.getAnimResource());
+
+ byte *file = _vm->_resman->openResource(obGraph.getAnimResource());
+
+ AnimHeader anim_head;
+ CdtEntry cdt_entry;
+ FrameHeader frame_head;
+
+ anim_head.read(_vm->fetchAnimHeader(file));
+ cdt_entry.read(_vm->fetchCdtEntry(file, obGraph.getAnimPc()));
+ frame_head.read(_vm->fetchFrameHeader(file, obGraph.getAnimPc()));
+
+ // update player graphic details for on-screen debug info
+ if (_vm->_logic->readVar(ID) == CUR_PLAYER_ID) {
+ _vm->_debugger->_graphType = obGraph.getType();
+ _vm->_debugger->_graphAnimRes = obGraph.getAnimResource();
+ // counting 1st frame as 'frame 1'
+ _vm->_debugger->_graphAnimPc = obGraph.getAnimPc() + 1;
+ _vm->_debugger->_graphNoFrames = anim_head.noAnimFrames;
+ }
+
+ // fill in the BuildUnit structure for this frame
+
+ build_unit->anim_resource = obGraph.getAnimResource();
+ build_unit->anim_pc = obGraph.getAnimPc();
+ build_unit->layer_number = 0;
+
+ // Affected by shading mask?
+ if (obGraph.getType() & SHADED_SPRITE)
+ build_unit->shadingFlag = true;
+ else
+ build_unit->shadingFlag = false;
+
+ // Check if this frame has offsets ie. this is a scalable mega frame
+
+ int scale = 0;
+
+ if (cdt_entry.frameType & FRAME_OFFSET) {
+ scale = obMega.calcScale();
+
+ // calc final render coordinates (top-left of sprite), based
+ // on feet coords & scaled offsets
+
+ // add scaled offsets to feet coords
+ build_unit->x = obMega.getFeetX() + (cdt_entry.x * scale) / 256;
+ build_unit->y = obMega.getFeetY() + (cdt_entry.y * scale) / 256;
+
+ // Work out new width and height. Always divide by 256 after
+ // everything else, to maintain accurary
+ build_unit->scaled_width = ((scale * frame_head.width) / 256);
+ build_unit->scaled_height = ((scale * frame_head.height) / 256);
+ } else {
+ // It's a non-scaling anim. Get render coords for sprite, from cdt
+ build_unit->x = cdt_entry.x;
+ build_unit->y = cdt_entry.y;
+
+ // Get width and height
+ build_unit->scaled_width = frame_head.width;
+ build_unit->scaled_height = frame_head.height;
+ }
+
+ // either 0 or required scale, depending on whether 'scale' computed
+ build_unit->scale = scale;
+
+ // calc the bottom y-coord for sorting purposes
+ build_unit->sort_y = build_unit->y + build_unit->scaled_height - 1;
+
+ if (ob_mouse) {
+ // passed a mouse structure, so add to the _mouseList
+ _vm->_mouse->registerMouse(ob_mouse, build_unit);
+
+ }
+
+ _vm->_resman->closeResource(obGraph.getAnimResource());
+}
+
+void Screen::registerFrame(byte *ob_mouse, byte *ob_graph, byte *ob_mega) {
+ ObjectGraphic obGraph(ob_graph);
+
+ // check low word for sprite type
+ switch (obGraph.getType() & 0x0000ffff) {
+ case BGP0_SPRITE:
+ assert(_curBgp0 < MAX_bgp0_sprites);
+ registerFrame(ob_mouse, ob_graph, ob_mega, &_bgp0List[_curBgp0]);
+ _curBgp0++;
+ break;
+ case BGP1_SPRITE:
+ assert(_curBgp1 < MAX_bgp1_sprites);
+ registerFrame(ob_mouse, ob_graph, ob_mega, &_bgp1List[_curBgp1]);
+ _curBgp1++;
+ break;
+ case BACK_SPRITE:
+ assert(_curBack < MAX_back_sprites);
+ registerFrame(ob_mouse, ob_graph, ob_mega, &_backList[_curBack]);
+ _curBack++;
+ break;
+ case SORT_SPRITE:
+ assert(_curSort < MAX_sort_sprites);
+ _sortOrder[_curSort] = _curSort;
+ registerFrame(ob_mouse, ob_graph, ob_mega, &_sortList[_curSort]);
+ _curSort++;
+ break;
+ case FORE_SPRITE:
+ assert(_curFore < MAX_fore_sprites);
+ registerFrame(ob_mouse, ob_graph, ob_mega, &_foreList[_curFore]);
+ _curFore++;
+ break;
+ case FGP0_SPRITE:
+ assert(_curFgp0 < MAX_fgp0_sprites);
+ registerFrame(ob_mouse, ob_graph, ob_mega, &_fgp0List[_curFgp0]);
+ _curFgp0++;
+ break;
+ case FGP1_SPRITE:
+ assert(_curFgp1 < MAX_fgp1_sprites);
+ registerFrame(ob_mouse, ob_graph, ob_mega, &_fgp1List[_curFgp1]);
+ _curFgp1++;
+ break;
+ default:
+ // NO_SPRITE no registering!
+ break;
+ }
+}
+
+// FIXME:
+//
+// The original credits used a different font. I think it's stored in the
+// font.clu file, but I don't know how to interpret it.
+//
+// The original used the entire screen. This version cuts off the top and
+// bottom of the screen, because that's where the menus would usually be.
+//
+// The original had some sort of smoke effect at the bottom of the screen.
+
+enum {
+ LINE_LEFT,
+ LINE_CENTER,
+ LINE_RIGHT
+};
+
+struct CreditsLine {
+ char *str;
+ byte type;
+ int top;
+ int height;
+ byte *sprite;
+
+ CreditsLine() {
+ str = NULL;
+ sprite = NULL;
+ };
+
+ ~CreditsLine() {
+ free(str);
+ free(sprite);
+ str = NULL;
+ sprite = NULL;
+ }
+};
+
+#define CREDITS_FONT_HEIGHT 25
+#define CREDITS_LINE_SPACING 20
+
+void Screen::rollCredits() {
+ uint32 loopingMusicId = _vm->_sound->getLoopingMusicId();
+
+ // Prepare for the credits by fading down, stoping the music, etc.
+
+ _vm->_mouse->setMouse(0);
+
+ _vm->_sound->muteFx(true);
+ _vm->_sound->muteSpeech(true);
+
+ waitForFade();
+ fadeDown();
+ waitForFade();
+
+ _vm->_mouse->closeMenuImmediately();
+
+ // There are three files which I believe are involved in showing the
+ // credits:
+ //
+ // credits.bmp - The "Smacker" logo, stored as follows:
+ //
+ // width 2 bytes, little endian
+ // height 2 bytes, little endian
+ // palette 3 * 256 bytes
+ // data width * height bytes
+ //
+ // Note that the maximum colour component in the palette is 0x3F.
+ // This is the same resolution as the _paletteMatch table. I doubt
+ // that this is a coincidence, but let's use the image palette
+ // directly anyway, just to be safe.
+ //
+ // credits.clu - The credits text
+ //
+ // This is simply a text file with CRLF line endings.
+ // '^' is not shown, but used to mark the center of the line.
+ // '@' is used as a placeholder for the "Smacker" logo. At least
+ // when it appears alone.
+ // Remaining lines are centered.
+ // The German version also contains character code 9 for no
+ // apparent reason. We ignore them.
+ //
+ // fonts.clu - The credits font?
+ //
+ // FIXME: At this time I don't know how to interpret fonts.clu. For
+ // now, let's just the standard speech font instead.
+
+ SpriteInfo spriteInfo;
+ Common::File f;
+ int i;
+
+ // Read the "Smacker" logo
+
+ uint16 logoWidth = 0;
+ uint16 logoHeight = 0;
+ byte *logoData = NULL;
+ byte palette[256 * 4];
+
+ if (f.open("credits.bmp")) {
+ logoWidth = f.readUint16LE();
+ logoHeight = f.readUint16LE();
+
+ for (i = 0; i < 256; i++) {
+ palette[i * 4 + 0] = f.readByte() << 2;
+ palette[i * 4 + 1] = f.readByte() << 2;
+ palette[i * 4 + 2] = f.readByte() << 2;
+ palette[i * 4 + 3] = 0;
+ }
+
+ logoData = (byte *)malloc(logoWidth * logoHeight);
+
+ f.read(logoData, logoWidth * logoHeight);
+ f.close();
+ } else {
+ warning("Can't find credits.bmp");
+ memset(palette, 0, sizeof(palette));
+ palette[14 * 4 + 0] = 252;
+ palette[14 * 4 + 1] = 252;
+ palette[14 * 4 + 2] = 252;
+ palette[14 * 4 + 3] = 0;
+ }
+
+ setPalette(0, 256, palette, RDPAL_INSTANT);
+
+ // Read the credits text
+
+ Common::Array<CreditsLine *> creditsLines;
+
+ int lineCount = 0;
+ int lineTop = 400;
+ int paragraphStart = 0;
+ bool hasCenterMark = false;
+
+ if (!f.open("credits.clu")) {
+ warning("Can't find credits.clu");
+ return;
+ }
+
+ while (1) {
+ char buffer[80];
+ char *line = f.readLine(buffer, sizeof(buffer));
+
+ if (!line || *line == 0) {
+ if (!hasCenterMark) {
+ for (i = paragraphStart; i < lineCount; i++)
+ creditsLines[i]->type = LINE_CENTER;
+ }
+ paragraphStart = lineCount;
+ hasCenterMark = false;
+ if (paragraphStart == lineCount)
+ lineTop += CREDITS_LINE_SPACING;
+
+ if (!line)
+ break;
+
+ continue;
+ }
+
+ // The German credits contains character code 9. We don't want
+ // the credits to show the 'dud' symbol, so we replace them
+ // with spaces.
+
+ for (char *ptr = line; *ptr; ptr++) {
+ if (*ptr < 32)
+ *ptr = 32;
+ }
+
+ char *center_mark = strchr(line, '^');
+
+ if (center_mark) {
+ // The current paragraph has at least one center mark.
+ hasCenterMark = true;
+
+ if (center_mark != line) {
+ creditsLines.push_back(new CreditsLine);
+
+ // The center mark is somewhere inside the
+ // line. Split it into left and right side.
+ *center_mark = 0;
+
+ creditsLines[lineCount]->top = lineTop;
+ creditsLines[lineCount]->height = CREDITS_FONT_HEIGHT;
+ creditsLines[lineCount]->type = LINE_LEFT;
+ creditsLines[lineCount]->str = strdup(line);
+
+ lineCount++;
+ *center_mark = '^';
+ }
+
+ line = center_mark;
+ }
+
+ creditsLines.push_back(new CreditsLine);
+
+ creditsLines[lineCount]->top = lineTop;
+
+ if (*line == '^') {
+ creditsLines[lineCount]->type = LINE_RIGHT;
+ line++;
+ } else
+ creditsLines[lineCount]->type = LINE_LEFT;
+
+ if (strcmp(line, "@") == 0) {
+ creditsLines[lineCount]->height = logoHeight;
+ lineTop += logoHeight;
+ } else {
+ creditsLines[lineCount]->height = CREDITS_FONT_HEIGHT;
+ lineTop += CREDITS_LINE_SPACING;
+ }
+
+ creditsLines[lineCount]->str = strdup(line);
+ lineCount++;
+ }
+
+ f.close();
+
+ // We could easily add some ScummVM stuff to the credits, if we wanted
+ // to. On the other hand, anyone with the attention span to actually
+ // read all the credits probably already knows. :-)
+
+ // Start the music and roll the credits
+
+ // The credits music (which can also be heard briefly in the "carib"
+ // cutscene) is played once.
+
+ _vm->_sound->streamCompMusic(309, false);
+
+ clearScene();
+ fadeUp(0);
+
+ spriteInfo.scale = 0;
+ spriteInfo.scaledWidth = 0;
+ spriteInfo.scaledHeight = 0;
+ spriteInfo.type = RDSPR_DISPLAYALIGN | RDSPR_NOCOMPRESSION | RDSPR_TRANS;
+ spriteInfo.blend = 0;
+
+ int startLine = 0;
+ int scrollPos = 0;
+
+ bool abortCredits = false;
+
+ int scrollSteps = lineTop + CREDITS_FONT_HEIGHT;
+ uint32 musicStart = _vm->getMillis();
+
+ // Ideally the music should last just a tiny bit longer than the
+ // credits. Note that musicTimeRemaining() will return 0 if the music
+ // is muted, so we need a sensible fallback for that case.
+
+ uint32 musicLength = MAX((int32)(1000 * (_vm->_sound->musicTimeRemaining() - 3)), 25 * (int32)scrollSteps);
+
+ while (scrollPos < scrollSteps && !_vm->_quit) {
+ clearScene();
+
+ for (i = startLine; i < lineCount; i++) {
+ if (!creditsLines[i])
+ continue;
+
+ // Free any sprites that have scrolled off the screen
+
+ if (creditsLines[i]->top + creditsLines[i]->height < scrollPos) {
+ debug(2, "Freeing line %d: '%s'", i, creditsLines[i]->str);
+
+ delete creditsLines[i];
+ creditsLines[i] = NULL;
+
+ startLine = i + 1;
+ } else if (creditsLines[i]->top < scrollPos + 400) {
+ if (!creditsLines[i]->sprite) {
+ debug(2, "Creating line %d: '%s'", i, creditsLines[i]->str);
+ creditsLines[i]->sprite = _vm->_fontRenderer->makeTextSprite((byte *)creditsLines[i]->str, 600, 14, _vm->_speechFontId, 0);
+ }
+
+ FrameHeader frame;
+
+ frame.read(creditsLines[i]->sprite);
+
+ spriteInfo.y = creditsLines[i]->top - scrollPos;
+ spriteInfo.w = frame.width;
+ spriteInfo.h = frame.height;
+ spriteInfo.data = creditsLines[i]->sprite + FrameHeader::size();
+
+ switch (creditsLines[i]->type) {
+ case LINE_LEFT:
+ spriteInfo.x = RENDERWIDE / 2 - 5 - frame.width;
+ break;
+ case LINE_RIGHT:
+ spriteInfo.x = RENDERWIDE / 2 + 5;
+ break;
+ case LINE_CENTER:
+ if (strcmp(creditsLines[i]->str, "@") == 0) {
+ spriteInfo.data = logoData;
+ spriteInfo.x = (RENDERWIDE - logoWidth) / 2;
+ spriteInfo.w = logoWidth;
+ spriteInfo.h = logoHeight;
+ } else
+ spriteInfo.x = (RENDERWIDE - frame.width) / 2;
+ break;
+ }
+
+ if (spriteInfo.data)
+ drawSprite(&spriteInfo);
+ } else
+ break;
+ }
+
+ updateDisplay();
+
+ KeyboardEvent *ke = _vm->keyboardEvent();
+
+ if (ke && ke->keycode == 27) {
+ if (!abortCredits) {
+ abortCredits = true;
+ fadeDown();
+ }
+ }
+
+ if (abortCredits && getFadeStatus() == RDFADE_BLACK)
+ break;
+
+ _vm->sleepUntil(musicStart + (musicLength * scrollPos) / scrollSteps);
+ scrollPos++;
+ }
+
+ // We're done. Clean up and try to put everything back where it was
+ // before the credits.
+
+ for (i = 0; i < lineCount; i++) {
+ delete creditsLines[i];
+ }
+
+ free(logoData);
+
+ if (!abortCredits) {
+ fadeDown();
+
+ // The music should either have stopped or be about to stop, so
+ // wait for it to really happen.
+
+ while (_vm->_sound->musicTimeRemaining() && !_vm->_quit) {
+ updateDisplay(false);
+ _vm->_system->delayMillis(100);
+ }
+ }
+
+ if (_vm->_quit)
+ return;
+
+ waitForFade();
+
+ _vm->_sound->muteFx(false);
+ _vm->_sound->muteSpeech(false);
+
+ if (loopingMusicId)
+ _vm->_sound->streamCompMusic(loopingMusicId, true);
+ else
+ _vm->_sound->stopMusic(false);
+
+ if (!_vm->_mouse->getMouseStatus() || _vm->_mouse->isChoosing())
+ _vm->_mouse->setMouse(NORMAL_MOUSE_ID);
+
+ if (_vm->_logic->readVar(DEAD))
+ _vm->_mouse->buildSystemMenu();
+}
+
+// This image used to be shown by CacheNewCluster() while copying a data file
+// from the CD to the hard disk. ScummVM doesn't do that, so the image is never
+// shown. It'd be nice if we could do something useful with it some day...
+
+void Screen::splashScreen() {
+ byte *bgfile = _vm->_resman->openResource(2950);
+
+ initialiseBackgroundLayer(NULL);
+ initialiseBackgroundLayer(NULL);
+ initialiseBackgroundLayer(_vm->fetchBackgroundLayer(bgfile));
+ initialiseBackgroundLayer(NULL);
+ initialiseBackgroundLayer(NULL);
+
+ setPalette(0, 256, _vm->fetchPalette(bgfile), RDPAL_FADE);
+ renderParallax(_vm->fetchBackgroundLayer(bgfile), 2);
+
+ closeBackgroundLayer();
+
+ byte *loadingBar = _vm->_resman->openResource(2951);
+ byte *frame = _vm->fetchFrameHeader(loadingBar, 0);
+
+ AnimHeader animHead;
+ CdtEntry cdt;
+ FrameHeader frame_head;
+
+ animHead.read(_vm->fetchAnimHeader(loadingBar));
+ cdt.read(_vm->fetchCdtEntry(loadingBar, 0));
+ frame_head.read(_vm->fetchFrameHeader(loadingBar, 0));
+
+ SpriteInfo barSprite;
+
+ barSprite.x = cdt.x;
+ barSprite.y = cdt.y;
+ barSprite.w = frame_head.width;
+ barSprite.h = frame_head.height;
+ barSprite.scale = 0;
+ barSprite.scaledWidth = 0;
+ barSprite.scaledHeight = 0;
+ barSprite.type = RDSPR_RLE256FAST | RDSPR_TRANS;
+ barSprite.blend = 0;
+ barSprite.colourTable = 0;
+ barSprite.data = frame + FrameHeader::size();
+
+ drawSprite(&barSprite);
+
+ fadeUp();
+ waitForFade();
+
+ for (int i = 0; i < animHead.noAnimFrames; i++) {
+ frame = _vm->fetchFrameHeader(loadingBar, i);
+ barSprite.data = frame + FrameHeader::size();
+ drawSprite(&barSprite);
+ updateDisplay();
+ _vm->_system->delayMillis(30);
+ }
+
+ _vm->_resman->closeResource(2951);
+
+ fadeDown();
+ waitForFade();
+}
+
+} // End of namespace Sword2
diff --git a/engines/sword2/build_display.h b/engines/sword2/build_display.h
new file mode 100644
index 0000000000..1a362da137
--- /dev/null
+++ b/engines/sword2/build_display.h
@@ -0,0 +1,441 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#ifndef _BUILD_DISPLAY
+#define _BUILD_DISPLAY
+
+#include "common/rect.h"
+#include "common/stream.h"
+
+#define MAX_bgp0_sprites 6
+#define MAX_bgp1_sprites 6
+#define MAX_back_sprites 30
+#define MAX_sort_sprites 30
+#define MAX_fore_sprites 30
+#define MAX_fgp0_sprites 6
+#define MAX_fgp1_sprites 6
+
+#define PALTABLESIZE (64 * 64 * 64)
+
+#define BLOCKWIDTH 64
+#define BLOCKHEIGHT 64
+#define MAXLAYERS 5
+
+#define MENUDEEP 40
+#define RENDERWIDE 640
+#define RENDERDEEP (480 - (MENUDEEP * 2))
+
+// Maximum scaled size of a sprite
+#define SCALE_MAXWIDTH 512
+#define SCALE_MAXHEIGHT 512
+
+// Dirty grid cell size
+#define CELLWIDE 10
+#define CELLDEEP 20
+
+namespace Sword2 {
+
+class Sword2Engine;
+
+// Sprite defines
+
+enum {
+ // This is the low byte part of the sprite type.
+
+ RDSPR_TRANS = 0x0001,
+ RDSPR_BLEND = 0x0004,
+ RDSPR_FLIP = 0x0008,
+ RDSPR_SHADOW = 0x0010,
+ RDSPR_DISPLAYALIGN = 0x0020,
+ RDSPR_NOCOMPRESSION = 0x0040,
+ RDSPR_EDGEBLEND = 0x0080, // Unused
+
+ // This is the high byte part of the sprite type, which defines what
+ // type of compression is used. Unless RDSPR_NOCOMPRESSION is set.
+
+ RDSPR_RLE16 = 0x0000,
+ RDSPR_RLE256 = 0x0100,
+ RDSPR_RLE256FAST = 0x0200
+};
+
+// Fading defines
+
+enum {
+ RDFADE_NONE,
+ RDFADE_UP,
+ RDFADE_DOWN,
+ RDFADE_BLACK
+};
+
+// Palette defines
+
+enum {
+ RDPAL_FADE,
+ RDPAL_INSTANT
+};
+
+// Blitting FX defines
+
+enum {
+ RDBLTFX_SPRITEBLEND = 0x01,
+ RDBLTFX_SHADOWBLEND = 0x02,
+ RDBLTFX_EDGEBLEND = 0x04
+};
+
+// Structure filled out by each object to register its graphic printing
+// requrements
+
+struct BuildUnit {
+ int16 x;
+ int16 y;
+ uint16 scaled_width;
+ uint16 scaled_height;
+ int16 sort_y;
+ uint32 anim_resource;
+ uint16 anim_pc;
+
+ // Denotes a scaling sprite at print time - and holds the scaling value
+ // for the shrink routine
+
+ uint16 scale;
+
+ // Non-zero means this item is a layer - retrieve from background layer
+ // and send to special renderer
+
+ uint16 layer_number;
+
+ // True means we want this frame to be affected by the shading mask
+
+ bool shadingFlag;
+};
+
+struct ScreenInfo {
+ uint16 scroll_offset_x; // Position x
+ uint16 scroll_offset_y; // Position y
+ uint16 max_scroll_offset_x; // Calc'ed in fnInitBackground
+ uint16 max_scroll_offset_y;
+ int16 player_feet_x; // Feet coordinates to use - cant just
+ int16 player_feet_y; // fetch the player compact anymore
+ int16 feet_x; // Special offset-to-player position -
+ int16 feet_y; // tweek as desired - always set in
+ // screen manager object startup
+ uint16 screen_wide; // Size of background layer - hence
+ uint16 screen_deep; // size of back buffer itself (Paul
+ // actually malloc's it)
+ uint32 background_layer_id; // Id of the normal background layer
+ // from the header of the main
+ // background layer
+ uint16 number_of_layers;
+ uint8 new_palette; // Set to non zero to start the
+ // palette held within layer file
+ // fading up after a build_display
+ uint8 scroll_flag; // Scroll mode 0 off 1 on
+ bool mask_flag; // Using shading mask
+};
+
+// The SpriteInfo structure is used to tell the driver96 code what attributes
+// are linked to a sprite for drawing. These include position, scaling and
+// compression.
+
+struct SpriteInfo {
+ int16 x; // coords for top-left of sprite
+ int16 y;
+ uint16 w; // dimensions of sprite (before scaling)
+ uint16 h;
+ uint16 scale; // scale at which to draw, given in 256ths ['0' or '256' MEANS DON'T SCALE]
+ uint16 scaledWidth; // new dimensions (we calc these for the mouse area, so may as well pass to you to save time)
+ uint16 scaledHeight; //
+ uint16 type; // mask containing 'RDSPR_' bits specifying compression type, flip, transparency, etc
+ uint16 blend; // holds the blending values.
+ byte *data; // pointer to the sprite data
+ byte *colourTable; // pointer to 16-byte colour table, only applicable to 16-col compression type
+};
+
+struct BlockSurface {
+ byte data[BLOCKWIDTH * BLOCKHEIGHT];
+ bool transparent;
+};
+
+struct Parallax {
+ uint16 w;
+ uint16 h;
+
+ // The dimensions are followed by an offset table, but we don't know in
+ // advance how big it is. See initializeBackgroundLayer().
+
+ static const int size() {
+ return 4;
+ }
+
+ void read(byte *addr) {
+ Common::MemoryReadStream readS(addr, size());
+
+ w = readS.readUint16LE();
+ h = readS.readUint16LE();
+ }
+
+ void write(byte *addr) {
+ Common::MemoryWriteStream writeS(addr, size());
+
+ writeS.writeUint16LE(w);
+ writeS.writeUint16LE(h);
+ }
+};
+
+class Screen {
+private:
+ Sword2Engine *_vm;
+
+ // _thisScreen describes the current back buffer and its in-game scroll
+ // positions, etc.
+
+ ScreenInfo _thisScreen;
+
+ int32 _renderCaps;
+ int8 _renderLevel;
+
+ byte *_buffer;
+ byte *_lightMask;
+
+ // Game screen metrics
+ int16 _screenWide;
+ int16 _screenDeep;
+
+ bool _needFullRedraw;
+
+ // Scroll variables. _scrollX and _scrollY hold the current scroll
+ // position, and _scrollXTarget and _scrollYTarget are the target
+ // position for the end of the game cycle.
+
+ int16 _scrollX;
+ int16 _scrollY;
+
+ int16 _scrollXTarget;
+ int16 _scrollYTarget;
+ int16 _scrollXOld;
+ int16 _scrollYOld;
+
+ int16 _parallaxScrollX; // current x offset to link a sprite to the
+ // parallax layer
+ int16 _parallaxScrollY; // current y offset to link a sprite to the
+ // parallax layer
+ int16 _locationWide;
+ int16 _locationDeep;
+
+ // Dirty grid handling
+ byte *_dirtyGrid;
+
+ uint16 _gridWide;
+ uint16 _gridDeep;
+
+ byte _palette[256 * 4];
+ byte _paletteMatch[PALTABLESIZE];
+
+ uint8 _fadeStatus;
+ int32 _fadeStartTime;
+ int32 _fadeTotalTime;
+
+ // 'frames per second' counting stuff
+ uint32 _fps;
+ uint32 _cycleTime;
+ uint32 _frameCount;
+
+ int32 _initialTime;
+ int32 _startTime;
+ int32 _totalTime;
+ int32 _renderAverageTime;
+ int32 _framesPerGameCycle;
+ bool _renderTooSlow;
+
+ void startNewPalette();
+
+ void resetRenderEngine();
+
+ void startRenderCycle();
+ bool endRenderCycle();
+
+ // Holds the order of the sort list, i.e. the list stays static and we
+ // sort this array.
+
+ uint16 _sortOrder[MAX_sort_sprites];
+
+ BuildUnit _bgp0List[MAX_bgp0_sprites];
+ BuildUnit _bgp1List[MAX_bgp1_sprites];
+ BuildUnit _backList[MAX_back_sprites];
+ BuildUnit _sortList[MAX_sort_sprites];
+ BuildUnit _foreList[MAX_fore_sprites];
+ BuildUnit _fgp0List[MAX_fgp0_sprites];
+ BuildUnit _fgp1List[MAX_fgp1_sprites];
+
+ uint32 _curBgp0;
+ uint32 _curBgp1;
+ uint32 _curBack;
+ uint32 _curSort;
+ uint32 _curFore;
+ uint32 _curFgp0;
+ uint32 _curFgp1;
+
+ void drawBackPar0Frames();
+ void drawBackPar1Frames();
+ void drawBackFrames();
+ void drawSortFrames(byte *file);
+ void drawForeFrames();
+ void drawForePar0Frames();
+ void drawForePar1Frames();
+
+ void processLayer(byte *file, uint32 layer_number);
+ void processImage(BuildUnit *build_unit);
+
+ uint8 _scrollFraction;
+
+ // Last palette used - so that we can restore the correct one after a
+ // pause (which dims the screen) and it's not always the main screen
+ // palette that we want, eg. during the eclipse
+
+ // This flag gets set in startNewPalette() and setFullPalette()
+
+ uint32 _lastPaletteRes;
+
+ // Debugging stuff
+ uint32 _largestLayerArea;
+ uint32 _largestSpriteArea;
+ char _largestLayerInfo[128];
+ char _largestSpriteInfo[128];
+
+ void registerFrame(byte *ob_mouse, byte *ob_graph, byte *ob_mega, BuildUnit *build_unit);
+
+ void mirrorSprite(byte *dst, byte *src, int16 w, int16 h);
+ int32 decompressRLE256(byte *dst, byte *src, int32 decompSize);
+ void unwindRaw16(byte *dst, byte *src, uint8 blockSize, byte *colTable);
+ int32 decompressRLE16(byte *dst, byte *src, int32 decompSize, byte *colTable);
+ void renderParallax(byte *ptr, int16 layer);
+
+ void markAsDirty(int16 x0, int16 y0, int16 x1, int16 y1);
+
+ uint8 _xBlocks[MAXLAYERS];
+ uint8 _yBlocks[MAXLAYERS];
+
+ // An array of sub-blocks, one for each of the parallax layers.
+
+ BlockSurface **_blockSurfaces[MAXLAYERS];
+
+ uint16 _xScale[SCALE_MAXWIDTH];
+ uint16 _yScale[SCALE_MAXHEIGHT];
+
+ void blitBlockSurface(BlockSurface *s, Common::Rect *r, Common::Rect *clipRect);
+
+ uint16 _layer;
+
+public:
+ Screen(Sword2Engine *vm, int16 width, int16 height);
+ ~Screen();
+
+ int8 getRenderLevel();
+ void setRenderLevel(int8 level);
+
+ byte *getScreen() { return _buffer; }
+ byte *getPalette() { return _palette; }
+ ScreenInfo *getScreenInfo() { return &_thisScreen; }
+
+ int16 getScreenWide() { return _screenWide; }
+ int16 getScreenDeep() { return _screenDeep; }
+
+ uint32 getCurBgp0() { return _curBgp0; }
+ uint32 getCurBgp1() { return _curBgp1; }
+ uint32 getCurBack() { return _curBack; }
+ uint32 getCurSort() { return _curSort; }
+ uint32 getCurFore() { return _curFore; }
+ uint32 getCurFgp0() { return _curFgp0; }
+ uint32 getCurFgp1() { return _curFgp1; }
+
+ uint32 getFps() { return _fps; }
+
+ uint32 getLargestLayerArea() { return _largestLayerArea; }
+ uint32 getLargestSpriteArea() { return _largestSpriteArea; }
+ char *getLargestLayerInfo() { return _largestLayerInfo; }
+ char *getLargestSpriteInfo() { return _largestSpriteInfo; }
+
+ void setNeedFullRedraw();
+
+ void clearScene();
+
+ void resetRenderLists();
+
+ void setLocationMetrics(uint16 w, uint16 h);
+ int32 initialiseBackgroundLayer(byte *parallax);
+ void closeBackgroundLayer();
+
+ void initialiseRenderCycle();
+
+ void initBackground(int32 res, int32 new_palette);
+ void registerFrame(byte *ob_mouse, byte *ob_graph, byte *ob_mega);
+
+ void setScrollFraction(uint8 f) { _scrollFraction = f; }
+ void setScrollTarget(int16 x, int16 y);
+ void setScrolling();
+
+ void setFullPalette(int32 palRes);
+ void setPalette(int16 startEntry, int16 noEntries, byte *palette, uint8 setNow);
+ uint8 quickMatch(uint8 r, uint8 g, uint8 b);
+ int32 fadeUp(float time = 0.75);
+ int32 fadeDown(float time = 0.75);
+ uint8 getFadeStatus();
+ void dimPalette();
+ void waitForFade();
+ void fadeServer();
+
+ void updateDisplay(bool redrawScene = true);
+
+ void displayMsg(byte *text, int time);
+
+ int32 createSurface(SpriteInfo *s, byte **surface);
+ void drawSurface(SpriteInfo *s, byte *surface, Common::Rect *clipRect = NULL);
+ void deleteSurface(byte *surface);
+ int32 drawSprite(SpriteInfo *s);
+
+ void scaleImageFast(byte *dst, uint16 dstPitch, uint16 dstWidth,
+ uint16 dstHeight, byte *src, uint16 srcPitch, uint16 srcWidth,
+ uint16 srcHeight);
+ void scaleImageGood(byte *dst, uint16 dstPitch, uint16 dstWidth,
+ uint16 dstHeight, byte *src, uint16 srcPitch, uint16 srcWidth,
+ uint16 srcHeight, byte *backbuf);
+
+ void updateRect(Common::Rect *r);
+
+ int32 openLightMask(SpriteInfo *s);
+ int32 closeLightMask();
+
+ void buildDisplay();
+
+ void plotPoint(int x, int y, uint8 colour);
+ void drawLine(int x0, int y0, int x1, int y1, uint8 colour);
+
+#ifdef BACKEND_8BIT
+ void plotYUV(byte *lut, int width, int height, byte *const *dat);
+#endif
+
+ void rollCredits();
+ void splashScreen();
+};
+
+} // End of namespace Sword2
+
+#endif
diff --git a/engines/sword2/console.cpp b/engines/sword2/console.cpp
new file mode 100644
index 0000000000..b465dc369f
--- /dev/null
+++ b/engines/sword2/console.cpp
@@ -0,0 +1,826 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "common/stdafx.h"
+#include "sword2/sword2.h"
+#include "sword2/defs.h"
+#include "sword2/console.h"
+#include "sword2/logic.h"
+#include "sword2/maketext.h"
+#include "sword2/memory.h"
+#include "sword2/mouse.h"
+#include "sword2/resman.h"
+#include "sword2/sound.h"
+
+#include "common/debugger.cpp"
+
+namespace Sword2 {
+
+Debugger::Debugger(Sword2Engine *vm)
+ : Common::Debugger<Debugger>() {
+ _vm = vm;
+
+ memset(_debugTextBlocks, 0, sizeof(_debugTextBlocks));
+ memset(_showVar, 0, sizeof(_showVar));
+
+ _displayDebugText = false; // "INFO"
+ _displayWalkGrid = false; // "WALKGRID"
+ _displayMouseMarker = false; // "MOUSE"
+ _displayTime = false; // "TIME"
+ _displayPlayerMarker = false; // "PLAYER"
+ _displayTextNumbers = false; // "TEXT"
+
+ _definingRectangles = false; // "RECT"
+ _draggingRectangle = 0; // 0 = waiting to start new rect
+ // 1 = currently dragging a rectangle
+
+ _rectX1 = _rectY1 = 0;
+ _rectX2 = _rectY2 = 0;
+ _rectFlicker = false;
+
+ _testingSnR = false; // "SAVEREST" - for system to kill all
+ // object resources (except player) in
+ // fnAddHuman()
+
+ _speechScriptWaiting = 0; // The id of whoever we're waiting for
+ // in a speech script. See fnTheyDo(),
+ // fnTheyDoWeWait(), fnWeWait(), and
+ // fnTimedWait().
+
+ _startTime = 0; // "TIMEON" & "TIMEOFF" - system start
+ // time
+
+ _textNumber = 0; // Current system text line number
+
+ _graphNoFrames = 0; // No. of frames in currently displayed
+ // anim
+
+ // Register commands
+
+ DCmd_Register("continue", &Debugger::Cmd_Exit);
+ DCmd_Register("exit", &Debugger::Cmd_Exit);
+ DCmd_Register("quit", &Debugger::Cmd_Exit);
+ DCmd_Register("q", &Debugger::Cmd_Exit);
+ DCmd_Register("help", &Debugger::Cmd_Help);
+ DCmd_Register("mem", &Debugger::Cmd_Mem);
+ DCmd_Register("tony", &Debugger::Cmd_Tony);
+ DCmd_Register("res", &Debugger::Cmd_Res);
+ DCmd_Register("reslist", &Debugger::Cmd_ResList);
+ DCmd_Register("starts", &Debugger::Cmd_Starts);
+ DCmd_Register("start", &Debugger::Cmd_Start);
+ DCmd_Register("s", &Debugger::Cmd_Start);
+ DCmd_Register("info", &Debugger::Cmd_Info);
+ DCmd_Register("walkgrid", &Debugger::Cmd_WalkGrid);
+ DCmd_Register("mouse", &Debugger::Cmd_Mouse);
+ DCmd_Register("player", &Debugger::Cmd_Player);
+ DCmd_Register("reslook", &Debugger::Cmd_ResLook);
+ DCmd_Register("cur", &Debugger::Cmd_CurrentInfo);
+ DCmd_Register("runlist", &Debugger::Cmd_RunList);
+ DCmd_Register("kill", &Debugger::Cmd_Kill);
+ DCmd_Register("nuke", &Debugger::Cmd_Nuke);
+ DCmd_Register("var", &Debugger::Cmd_Var);
+ DCmd_Register("rect", &Debugger::Cmd_Rect);
+ DCmd_Register("clear", &Debugger::Cmd_Clear);
+ DCmd_Register("debugon", &Debugger::Cmd_DebugOn);
+ DCmd_Register("debugoff", &Debugger::Cmd_DebugOff);
+ DCmd_Register("saverest", &Debugger::Cmd_SaveRest);
+ DCmd_Register("timeon", &Debugger::Cmd_TimeOn);
+ DCmd_Register("timeoff", &Debugger::Cmd_TimeOff);
+ DCmd_Register("text", &Debugger::Cmd_Text);
+ DCmd_Register("showvar", &Debugger::Cmd_ShowVar);
+ DCmd_Register("hidevar", &Debugger::Cmd_HideVar);
+ DCmd_Register("version", &Debugger::Cmd_Version);
+ DCmd_Register("animtest", &Debugger::Cmd_AnimTest);
+ DCmd_Register("texttest", &Debugger::Cmd_TextTest);
+ DCmd_Register("linetest", &Debugger::Cmd_LineTest);
+ DCmd_Register("events", &Debugger::Cmd_Events);
+ DCmd_Register("sfx", &Debugger::Cmd_Sfx);
+ DCmd_Register("english", &Debugger::Cmd_English);
+ DCmd_Register("finnish", &Debugger::Cmd_Finnish);
+ DCmd_Register("polish", &Debugger::Cmd_Polish);
+}
+
+void Debugger::varGet(int var) {
+ DebugPrintf("%d\n", _vm->_logic->readVar(var));
+}
+
+void Debugger::varSet(int var, int val) {
+ DebugPrintf("was %d, ", _vm->_logic->readVar(var));
+ _vm->_logic->writeVar(var, val);
+ DebugPrintf("now %d\n", _vm->_logic->readVar(var));
+}
+
+void Debugger::preEnter() {
+ // Pause sound output
+ if (_vm->_sound) {
+ _vm->_sound->pauseFx();
+ _vm->_sound->pauseSpeech();
+ _vm->_sound->pauseMusic();
+ }
+}
+
+void Debugger::postEnter() {
+ if (_vm->_sound) {
+ // Resume previous sound state
+ _vm->_sound->unpauseFx();
+ _vm->_sound->unpauseSpeech();
+ _vm->_sound->unpauseMusic();
+ }
+
+ if (_vm->_mouse) {
+ // Restore old mouse cursor
+ _vm->_mouse->drawMouse();
+ }
+}
+
+// Now the fun stuff: Commands
+
+bool Debugger::Cmd_Exit(int argc, const char **argv) {
+ _detach_now = true;
+ _vm->clearInputEvents();
+ return false;
+}
+
+bool Debugger::Cmd_Help(int argc, const char **argv) {
+ // console normally has 78 line width
+ // wrap around nicely
+ int width = 0;
+
+ DebugPrintf("Commands are:\n");
+ for (int i = 0 ; i < _dcmd_count ; i++) {
+ int size = strlen(_dcmds[i].name) + 1;
+
+ if (width + size >= 75) {
+ DebugPrintf("\n");
+ width = size;
+ } else
+ width += size;
+
+ DebugPrintf("%s ", _dcmds[i].name);
+ }
+
+ DebugPrintf("\n");
+ return true;
+}
+
+static int compare_blocks(const void *p1, const void *p2) {
+ const MemBlock *m1 = *(const MemBlock * const *)p1;
+ const MemBlock *m2 = *(const MemBlock * const *)p2;
+
+ if (m1->size < m2->size)
+ return 1;
+ if (m1->size > m2->size)
+ return -1;
+ return 0;
+}
+
+bool Debugger::Cmd_Mem(int argc, const char **argv) {
+ int16 numBlocks = _vm->_memory->getNumBlocks();
+ MemBlock *memBlocks = _vm->_memory->getMemBlocks();
+
+ MemBlock **blocks = (MemBlock **)malloc(numBlocks * sizeof(MemBlock));
+
+ int i, j;
+
+ for (i = 0, j = 0; i < MAX_MEMORY_BLOCKS; i++) {
+ if (memBlocks[i].ptr)
+ blocks[j++] = &memBlocks[i];
+ }
+
+ qsort(blocks, numBlocks, sizeof(MemBlock *), compare_blocks);
+
+ DebugPrintf(" size id res type name\n");
+ DebugPrintf("---------------------------------------------------------------------------\n");
+
+ for (i = 0; i < numBlocks; i++) {
+ const char *type;
+
+ switch (_vm->_resman->fetchType(blocks[i]->ptr)) {
+ case ANIMATION_FILE:
+ type = "ANIMATION_FILE";
+ break;
+ case SCREEN_FILE:
+ type = "SCREEN_FILE";
+ break;
+ case GAME_OBJECT:
+ type = "GAME_OBJECT";
+ break;
+ case WALK_GRID_FILE:
+ type = "WALK_GRID_FILE";
+ break;
+ case GLOBAL_VAR_FILE:
+ type = "GLOBAL_VAR_FILE";
+ break;
+ case PARALLAX_FILE_null:
+ type = "PARALLAX_FILE_null";
+ break;
+ case RUN_LIST:
+ type = "RUN_LIST";
+ break;
+ case TEXT_FILE:
+ type = "TEXT_FILE";
+ break;
+ case SCREEN_MANAGER:
+ type = "SCREEN_MANAGER";
+ break;
+ case MOUSE_FILE:
+ type = "MOUSE_FILE";
+ break;
+ case WAV_FILE:
+ type = "WAV_FILE";
+ break;
+ case ICON_FILE:
+ type = "ICON_FILE";
+ break;
+ case PALETTE_FILE:
+ type = "PALETTE_FILE";
+ break;
+ default:
+ type = "<unknown>";
+ break;
+ }
+
+ DebugPrintf("%9ld %-3d %-4d %-20s %s\n", blocks[i]->size, blocks[i]->id, blocks[i]->uid, type, _vm->_resman->fetchName(blocks[i]->ptr));
+ }
+
+ free(blocks);
+
+ DebugPrintf("---------------------------------------------------------------------------\n");
+ DebugPrintf("%9ld\n", _vm->_memory->getTotAlloc());
+
+ return true;
+}
+
+bool Debugger::Cmd_Tony(int argc, const char **argv) {
+ DebugPrintf("What about him?\n");
+ return true;
+}
+
+bool Debugger::Cmd_Res(int argc, const char **argv) {
+ uint32 numClusters = _vm->_resman->getNumClusters();
+
+ if (!numClusters) {
+ DebugPrintf("Argh! No resources!\n");
+ return true;
+ }
+
+ ResourceFile *resFiles = _vm->_resman->getResFiles();
+
+ for (uint i = 0; i < numClusters; i++) {
+ const char *locStr[3] = { "HDD", "CD1", "CD2" };
+
+ DebugPrintf("%-20s %d\n", resFiles[i].fileName, locStr[resFiles[i].cd]);
+ }
+
+ DebugPrintf("%d resources\n", _vm->_resman->getNumResFiles());
+ return true;
+}
+
+bool Debugger::Cmd_ResList(int argc, const char **argv) {
+ // By default, list only resources that are being held open.
+ uint minCount = 1;
+
+ if (argc > 1)
+ minCount = atoi(argv[1]);
+
+ uint32 numResFiles = _vm->_resman->getNumResFiles();
+ Resource *resList = _vm->_resman->getResList();
+
+ for (uint i = 0; i < numResFiles; i++) {
+ if (resList[i].ptr && resList[i].refCount >= minCount) {
+ DebugPrintf("%-4d: %-35s refCount: %-3d\n", i, _vm->_resman->fetchName(resList[i].ptr), resList[i].refCount);
+ }
+ }
+
+ return true;
+}
+
+bool Debugger::Cmd_Starts(int argc, const char **argv) {
+ uint32 numStarts = _vm->getNumStarts();
+
+ if (!numStarts) {
+ DebugPrintf("Sorry - no startup positions registered?\n");
+
+ uint32 numScreenManagers = _vm->getNumScreenManagers();
+
+ if (!numScreenManagers)
+ DebugPrintf("There is a problem with startup.inf\n");
+ else
+ DebugPrintf(" (%d screen managers found in startup.inf)\n", numScreenManagers);
+ return true;
+ }
+
+ StartUp *startList = _vm->getStartList();
+
+ for (uint i = 0; i < numStarts; i++)
+ DebugPrintf("%d (%s)\n", i, startList[i].description);
+
+ return true;
+}
+
+bool Debugger::Cmd_Start(int argc, const char **argv) {
+ uint8 pal[4] = { 255, 255, 255, 0 };
+
+ if (argc != 2) {
+ DebugPrintf("Usage: %s number\n", argv[0]);
+ return true;
+ }
+
+ uint32 numStarts = _vm->getNumStarts();
+
+ if (!numStarts) {
+ DebugPrintf("Sorry - there are no startups!\n");
+ return true;
+ }
+
+ int start = atoi(argv[1]);
+
+ if (start < 0 || start >= (int)numStarts) {
+ DebugPrintf("Not a legal start position\n");
+ return true;
+ }
+
+ DebugPrintf("Running start %d\n", start);
+
+ _vm->runStart(start);
+ _vm->_screen->setPalette(187, 1, pal, RDPAL_INSTANT);
+ return true;
+}
+
+bool Debugger::Cmd_Info(int argc, const char **argv) {
+ _displayDebugText = !_displayDebugText;
+
+ if (_displayDebugText)
+ DebugPrintf("Info text on\n");
+ else
+ DebugPrintf("Info Text off\n");
+
+ return true;
+}
+
+bool Debugger::Cmd_WalkGrid(int argc, const char **argv) {
+ _displayWalkGrid = !_displayWalkGrid;
+
+ if (_displayWalkGrid)
+ DebugPrintf("Walk-grid display on\n");
+ else
+ DebugPrintf("Walk-grid display off\n");
+
+ return true;
+}
+
+bool Debugger::Cmd_Mouse(int argc, const char **argv) {
+ _displayMouseMarker = !_displayMouseMarker;
+
+ if (_displayMouseMarker)
+ DebugPrintf("Mouse marker on\n");
+ else
+ DebugPrintf("Mouse marker off\n");
+
+ return true;
+}
+
+bool Debugger::Cmd_Player(int argc, const char **argv) {
+ _displayPlayerMarker = !_displayPlayerMarker;
+
+ if (_displayPlayerMarker)
+ DebugPrintf("Player feet marker on\n");
+ else
+ DebugPrintf("Player feet marker off\n");
+
+ return true;
+}
+
+bool Debugger::Cmd_ResLook(int argc, const char **argv) {
+ if (argc != 2) {
+ DebugPrintf("Usage: %s number\n", argv[0]);
+ return true;
+ }
+
+ int res = atoi(argv[1]);
+ uint32 numResFiles = _vm->_resman->getNumResFiles();
+
+ if (res < 0 || res >= (int)numResFiles) {
+ DebugPrintf("Illegal resource %d. There are %d resources, 0-%d.\n",
+ res, numResFiles, numResFiles - 1);
+ return true;
+ }
+
+ if (!_vm->_resman->checkValid(res)) {
+ DebugPrintf("%d is a null & void resource number\n", res);
+ return true;
+ }
+
+ // Open up the resource and take a look inside!
+ uint8 type = _vm->_resman->fetchType(res);;
+ byte name[NAME_LEN];
+
+ _vm->_resman->fetchName(res, name);
+
+ switch (type) {
+ case ANIMATION_FILE:
+ DebugPrintf("<anim> %s\n", name);
+ break;
+ case SCREEN_FILE:
+ DebugPrintf("<layer> %s\n", name);
+ break;
+ case GAME_OBJECT:
+ DebugPrintf("<game object> %s\n", name);
+ break;
+ case WALK_GRID_FILE:
+ DebugPrintf("<walk grid> %s\n", name);
+ break;
+ case GLOBAL_VAR_FILE:
+ DebugPrintf("<global variables> %s\n", name);
+ break;
+ case PARALLAX_FILE_null:
+ DebugPrintf("<parallax file NOT USED!> %s\n", name);
+ break;
+ case RUN_LIST:
+ DebugPrintf("<run list> %s\n", name);
+ break;
+ case TEXT_FILE:
+ DebugPrintf("<text file> %s\n", name);
+ break;
+ case SCREEN_MANAGER:
+ DebugPrintf("<screen manager> %s\n", name);
+ break;
+ case MOUSE_FILE:
+ DebugPrintf("<mouse pointer> %s\n", name);
+ break;
+ case ICON_FILE:
+ DebugPrintf("<menu icon> %s\n", name);
+ break;
+ default:
+ DebugPrintf("unrecognised fileType %d\n", type);
+ break;
+ }
+
+ return true;
+}
+
+bool Debugger::Cmd_CurrentInfo(int argc, const char **argv) {
+ // prints general stuff about the screen, etc.
+ ScreenInfo *screenInfo = _vm->_screen->getScreenInfo();
+
+ if (screenInfo->background_layer_id) {
+ DebugPrintf("background layer id %d\n", screenInfo->background_layer_id);
+ DebugPrintf("%d wide, %d high\n", screenInfo->screen_wide, screenInfo->screen_deep);
+ DebugPrintf("%d normal layers\n", screenInfo->number_of_layers);
+
+ Cmd_RunList(argc, argv);
+ } else
+ DebugPrintf("No screen\n");
+ return true;
+}
+
+bool Debugger::Cmd_RunList(int argc, const char **argv) {
+ uint32 runList = _vm->_logic->getRunList();
+
+ if (runList) {
+ Common::MemoryReadStream readS(_vm->_resman->openResource(runList), _vm->_resman->fetchLen(runList));
+
+ readS.seek(ResHeader::size());
+
+ DebugPrintf("Runlist number %d\n", runList);
+
+ while (1) {
+ uint32 res = readS.readUint32LE();
+ if (!res)
+ break;
+
+ byte name[NAME_LEN];
+
+ DebugPrintf("%d %s\n", res, _vm->_resman->fetchName(res, name));
+ }
+
+ _vm->_resman->closeResource(runList);
+ } else
+ DebugPrintf("No run list set\n");
+
+ return true;
+}
+
+bool Debugger::Cmd_Kill(int argc, const char **argv) {
+ if (argc != 2) {
+ DebugPrintf("Usage: %s number\n", argv[0]);
+ return true;
+ }
+
+ int res = atoi(argv[1]);
+ uint32 numResFiles = _vm->_resman->getNumResFiles();
+
+ if (res < 0 || res >= (int)numResFiles) {
+ DebugPrintf("Illegal resource %d. There are %d resources, 0-%d.\n",
+ res, numResFiles, numResFiles - 1);
+ return true;
+ }
+
+ Resource *resList = _vm->_resman->getResList();
+
+ if (!resList[res].ptr) {
+ DebugPrintf("Resource %d is not in memory\n", res);
+ return true;
+ }
+
+ if (resList[res].refCount) {
+ DebugPrintf("Resource %d is open - cannot remove\n", res);
+ return true;
+ }
+
+ _vm->_resman->remove(res);
+ DebugPrintf("Trashed %d\n", res);
+ return true;
+}
+
+bool Debugger::Cmd_Nuke(int argc, const char **argv) {
+ DebugPrintf("Killing all resources except variable file and player object\n");
+ _vm->_resman->killAll(true);
+ return true;
+}
+
+bool Debugger::Cmd_Var(int argc, const char **argv) {
+ switch (argc) {
+ case 2:
+ varGet(atoi(argv[1]));
+ break;
+ case 3:
+ varSet(atoi(argv[1]), atoi(argv[2]));
+ break;
+ default:
+ DebugPrintf("Usage: %s number value\n", argv[0]);
+ break;
+ }
+
+ return true;
+}
+
+bool Debugger::Cmd_Rect(int argc, const char **argv) {
+ uint32 filter = _vm->setInputEventFilter(0);
+
+ _definingRectangles = !_definingRectangles;
+
+ if (_definingRectangles) {
+ _vm->setInputEventFilter(filter & ~(RD_LEFTBUTTONUP | RD_RIGHTBUTTONUP));
+ DebugPrintf("Mouse rectangles enabled\n");
+ } else {
+ _vm->setInputEventFilter(filter | RD_LEFTBUTTONUP | RD_RIGHTBUTTONUP);
+ DebugPrintf("Mouse rectangles disabled\n");
+ }
+
+ _draggingRectangle = 0;
+ return true;
+}
+
+bool Debugger::Cmd_Clear(int argc, const char **argv) {
+ _vm->_resman->killAllObjects(true);
+ return true;
+}
+
+bool Debugger::Cmd_DebugOn(int argc, const char **argv) {
+ _displayDebugText = true;
+ _displayWalkGrid = true;
+ _displayMouseMarker = true;
+ _displayPlayerMarker = true;
+ _displayTextNumbers = true;
+ DebugPrintf("Enabled all on-screen debug info\n");
+ return true;
+}
+
+bool Debugger::Cmd_DebugOff(int argc, const char **argv) {
+ _displayDebugText = false;
+ _displayWalkGrid = false;
+ _displayMouseMarker = false;
+ _displayPlayerMarker = false;
+ _displayTextNumbers = false;
+ DebugPrintf("Disabled all on-screen debug info\n");
+ return true;
+}
+
+bool Debugger::Cmd_SaveRest(int argc, const char **argv) {
+ _testingSnR = !_testingSnR;
+
+ if (_testingSnR)
+ DebugPrintf("Enabled S&R logic_script stability checking\n");
+ else
+ DebugPrintf("Disabled S&R logic_script stability checking\n");
+
+ return true;
+}
+
+bool Debugger::Cmd_TimeOn(int argc, const char **argv) {
+ if (argc == 2)
+ _startTime = _vm->_system->getMillis() - atoi(argv[1]) * 1000;
+ else if (_startTime == 0)
+ _startTime = _vm->_system->getMillis();
+ _displayTime = true;
+ DebugPrintf("Timer display on\n");
+ return true;
+}
+
+bool Debugger::Cmd_TimeOff(int argc, const char **argv) {
+ _displayTime = false;
+ DebugPrintf("Timer display off\n");
+ return true;
+}
+
+bool Debugger::Cmd_Text(int argc, const char **argv) {
+ _displayTextNumbers = !_displayTextNumbers;
+
+ if (_displayTextNumbers)
+ DebugPrintf("Text numbers on\n");
+ else
+ DebugPrintf("Text numbers off\n");
+
+ return true;
+}
+
+bool Debugger::Cmd_ShowVar(int argc, const char **argv) {
+ int32 showVarNo = 0;
+ int32 varNo;
+
+ if (argc != 2) {
+ DebugPrintf("Usage: %s number\n", argv[0]);
+ return true;
+ }
+
+ varNo = atoi(argv[1]);
+
+ // search for a spare slot in the watch-list, but also watch out for
+ // this variable already being in the list
+
+ while (showVarNo < MAX_SHOWVARS && _showVar[showVarNo] != 0 && _showVar[showVarNo] != varNo)
+ showVarNo++;
+
+ // if we've found a spare slot or the variable's already there
+ if (showVarNo < MAX_SHOWVARS) {
+ if (_showVar[showVarNo] == 0) {
+ // empty slot - add it to the list at this slot
+ _showVar[showVarNo] = varNo;
+ DebugPrintf("var(%d) added to the watch-list\n", varNo);
+ } else
+ DebugPrintf("var(%d) already in the watch-list!\n", varNo);
+ } else
+ DebugPrintf("Sorry - no more allowed - hide one or extend the system watch-list\n");
+
+ return true;
+}
+
+bool Debugger::Cmd_HideVar(int argc, const char **argv) {
+ int32 showVarNo = 0;
+ int32 varNo;
+
+ if (argc != 2) {
+ DebugPrintf("Usage: %s number\n", argv[0]);
+ return true;
+ }
+
+ varNo = atoi(argv[1]);
+
+ // search for 'varNo' in the watch-list
+ while (showVarNo < MAX_SHOWVARS && _showVar[showVarNo] != varNo)
+ showVarNo++;
+
+ if (showVarNo < MAX_SHOWVARS) {
+ // We've found 'varNo' in the list - clear this slot
+ _showVar[showVarNo] = 0;
+ DebugPrintf("var(%d) removed from watch-list\n", varNo);
+ } else
+ DebugPrintf("Sorry - can't find var(%d) in the list\n", varNo);
+
+ return true;
+}
+
+bool Debugger::Cmd_Version(int argc, const char **argv) {
+ // This function used to print more information, but nothing we
+ // particularly care about.
+
+ DebugPrintf("\"Broken Sword II\" (c) Revolution Software 1997.\n");
+ return true;
+}
+
+bool Debugger::Cmd_AnimTest(int argc, const char **argv) {
+ if (argc != 2) {
+ DebugPrintf("Usage: %s value\n", argv[0]);
+ return true;
+ }
+
+ // Automatically do "s 32" to run the animation testing start script
+ _vm->runStart(32);
+
+ // Same as typing "VAR 912 <value>" at the console
+ varSet(912, atoi(argv[1]));
+
+ DebugPrintf("Setting flag 'system_testing_anims'\n");
+ return true;
+}
+
+bool Debugger::Cmd_TextTest(int argc, const char **argv) {
+ if (argc != 2) {
+ DebugPrintf("Usage: %s value\n", argv[0]);
+ return true;
+ }
+
+ // Automatically do "s 33" to run the text/speech testing start script
+ _vm->runStart(33);
+
+ // Same as typing "VAR 1230 <value>" at the console
+ varSet(1230, atoi(argv[1]));
+
+ _displayTextNumbers = true;
+
+ DebugPrintf("Setting flag 'system_testing_text'\n");
+ DebugPrintf("Text numbers on\n");
+ return true;
+}
+
+bool Debugger::Cmd_LineTest(int argc, const char **argv) {
+ if (argc != 3) {
+ DebugPrintf("Usage: %s value1 value2\n", argv[0]);
+ return true;
+ }
+
+ // Automatically do "s 33" to run the text/speech testing start script
+ _vm->runStart(33);
+
+ // Same as typing "VAR 1230 <value>" at the console
+ varSet(1230, atoi(argv[1]));
+
+ // Same as typing "VAR 1264 <value>" at the console
+ varSet(1264, atoi(argv[2]));
+
+ _displayTextNumbers = true;
+
+ DebugPrintf("Setting flag 'system_testing_text'\n");
+ DebugPrintf("Setting flag 'system_test_line_no'\n");
+ DebugPrintf("Text numbers on\n");
+ return true;
+}
+
+bool Debugger::Cmd_Events(int argc, const char **argv) {
+ EventUnit *eventList = _vm->_logic->getEventList();
+
+ DebugPrintf("EVENT LIST:\n");
+
+ for (uint32 i = 0; i < MAX_events; i++) {
+ if (eventList[i].id) {
+ byte buf[NAME_LEN];
+ uint32 target = eventList[i].id;
+ uint32 script = eventList[i].interact_id;
+
+ DebugPrintf("slot %2d: id = %s (%d)\n", i, _vm->_resman->fetchName(target, buf), target);
+ DebugPrintf(" script = %s (%d) pos %d\n", _vm->_resman->fetchName(script / 65536, buf), script / 65536, script % 65536);
+ }
+ }
+
+ return true;
+}
+
+bool Debugger::Cmd_Sfx(int argc, const char **argv) {
+ _vm->_wantSfxDebug = !_vm->_wantSfxDebug;
+
+ if (_vm->_wantSfxDebug)
+ DebugPrintf("SFX logging activated\n");
+ else
+ DebugPrintf("SFX logging deactivated\n");
+
+ return true;
+}
+
+bool Debugger::Cmd_English(int argc, const char **argv) {
+ _vm->initialiseFontResourceFlags(DEFAULT_TEXT);
+ DebugPrintf("Default fonts selected\n");
+ return true;
+}
+
+bool Debugger::Cmd_Finnish(int argc, const char **argv) {
+ _vm->initialiseFontResourceFlags(FINNISH_TEXT);
+ DebugPrintf("Finnish fonts selected\n");
+ return true;
+}
+
+bool Debugger::Cmd_Polish(int argc, const char **argv) {
+ _vm->initialiseFontResourceFlags(POLISH_TEXT);
+ DebugPrintf("Polish fonts selected\n");
+ return true;
+}
+
+} // End of namespace Sword2
diff --git a/engines/sword2/console.h b/engines/sword2/console.h
new file mode 100644
index 0000000000..95bfbe946d
--- /dev/null
+++ b/engines/sword2/console.h
@@ -0,0 +1,127 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#ifndef C_ONSOLE_H
+#define C_ONSOLE_H
+
+#include "common/debugger.h"
+#include "sword2/debug.h"
+
+namespace Sword2 {
+
+class Debugger : public Common::Debugger<Debugger> {
+private:
+ void varGet(int var);
+ void varSet(int var, int val);
+
+ bool _displayDebugText;
+ bool _displayWalkGrid;
+ bool _displayMouseMarker;
+ bool _displayTime;
+ bool _displayPlayerMarker;
+ bool _displayTextNumbers;
+
+ bool _rectFlicker;
+
+ int32 _startTime;
+
+ int32 _showVar[MAX_SHOWVARS];
+
+ byte _debugTextBlocks[MAX_DEBUG_TEXTS];
+
+ void clearDebugTextBlocks();
+ void makeDebugTextBlock(char *text, int16 x, int16 y);
+
+ void plotCrossHair(int16 x, int16 y, uint8 pen);
+ void drawRect(int16 x, int16 y, int16 x2, int16 y2, uint8 pen);
+
+public:
+ Debugger(Sword2Engine *vm);
+
+ int16 _rectX1, _rectY1;
+ int16 _rectX2, _rectY2;
+
+ uint8 _draggingRectangle;
+ bool _definingRectangles;
+
+ bool _testingSnR;
+
+ int32 _speechScriptWaiting;
+
+ int32 _textNumber;
+
+ int32 _graphType;
+ int32 _graphAnimRes;
+ int32 _graphAnimPc;
+ uint32 _graphNoFrames;
+
+ void buildDebugText();
+ void drawDebugGraphics();
+
+protected:
+ Sword2Engine *_vm;
+
+ virtual void preEnter();
+ virtual void postEnter();
+
+ // Commands
+ bool Cmd_Exit(int argc, const char **argv);
+ bool Cmd_Help(int argc, const char **argv);
+ bool Cmd_Mem(int argc, const char **argv);
+ bool Cmd_Tony(int argc, const char **argv);
+ bool Cmd_Res(int argc, const char **argv);
+ bool Cmd_ResList(int argc, const char **argv);
+ bool Cmd_Starts(int argc, const char **argv);
+ bool Cmd_Start(int argc, const char **argv);
+ bool Cmd_Info(int argc, const char **argv);
+ bool Cmd_WalkGrid(int argc, const char **argv);
+ bool Cmd_Mouse(int argc, const char **argv);
+ bool Cmd_Player(int argc, const char **argv);
+ bool Cmd_ResLook(int argc, const char **argv);
+ bool Cmd_CurrentInfo(int argc, const char **argv);
+ bool Cmd_RunList(int argc, const char **argv);
+ bool Cmd_Kill(int argc, const char **argv);
+ bool Cmd_Nuke(int argc, const char **argv);
+ bool Cmd_Var(int argc, const char **argv);
+ bool Cmd_Rect(int argc, const char **argv);
+ bool Cmd_Clear(int argc, const char **argv);
+ bool Cmd_DebugOn(int argc, const char **argv);
+ bool Cmd_DebugOff(int argc, const char **argv);
+ bool Cmd_SaveRest(int argc, const char **argv);
+ bool Cmd_TimeOn(int argc, const char **argv);
+ bool Cmd_TimeOff(int argc, const char **argv);
+ bool Cmd_Text(int argc, const char **argv);
+ bool Cmd_ShowVar(int argc, const char **argv);
+ bool Cmd_HideVar(int argc, const char **argv);
+ bool Cmd_Version(int argc, const char **argv);
+ bool Cmd_AnimTest(int argc, const char **argv);
+ bool Cmd_TextTest(int argc, const char **argv);
+ bool Cmd_LineTest(int argc, const char **argv);
+ bool Cmd_Events(int argc, const char **argv);
+ bool Cmd_Sfx(int argc, const char **argv);
+ bool Cmd_English(int argc, const char **argv);
+ bool Cmd_Finnish(int argc, const char **argv);
+ bool Cmd_Polish(int argc, const char **argv);
+};
+
+} // End of namespace Sword2
+
+#endif
diff --git a/engines/sword2/controls.cpp b/engines/sword2/controls.cpp
new file mode 100644
index 0000000000..df1b38c83e
--- /dev/null
+++ b/engines/sword2/controls.cpp
@@ -0,0 +1,1416 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "common/stdafx.h"
+#include "common/rect.h"
+#include "common/config-manager.h"
+#include "common/system.h"
+
+#include "sword2/sword2.h"
+#include "sword2/defs.h"
+#include "sword2/controls.h"
+#include "sword2/mouse.h"
+#include "sword2/resman.h"
+#include "sword2/sound.h"
+
+#define MAX_STRING_LEN 64 // 20 was too low; better to be safe ;)
+#define CHARACTER_OVERLAP 2 // overlap characters by 3 pixels
+
+// our fonts start on SPACE character (32)
+#define SIZE_OF_CHAR_SET (256 - 32)
+
+namespace Sword2 {
+
+static int baseSlot = 0;
+
+class Widget;
+
+/**
+ * Base class for all widgets.
+ */
+
+class Widget {
+protected:
+ Sword2Engine *_vm;
+ Dialog *_parent;
+
+ SpriteInfo *_sprites;
+
+ struct WidgetSurface {
+ byte *_surface;
+ bool _original;
+ };
+
+ WidgetSurface *_surfaces;
+ int _numStates;
+ int _state;
+
+ Common::Rect _hitRect;
+
+public:
+ Widget(Dialog *parent, int states);
+
+ virtual ~Widget();
+
+ void createSurfaceImage(int state, uint32 res, int x, int y, uint32 pc);
+ void linkSurfaceImage(Widget *from, int state, int x, int y);
+
+ void createSurfaceImages(uint32 res, int x, int y);
+ void linkSurfaceImages(Widget *from, int x, int y);
+
+ void setHitRect(int x, int y, int width, int height);
+ bool isHit(int16 x, int16 y);
+
+ void setState(int state);
+ int getState();
+
+ virtual void paint(Common::Rect *clipRect = NULL);
+
+ virtual void onMouseEnter() {}
+ virtual void onMouseExit() {}
+ virtual void onMouseMove(int x, int y) {}
+ virtual void onMouseDown(int x, int y) {}
+ virtual void onMouseUp(int x, int y) {}
+ virtual void onWheelUp(int x, int y) {}
+ virtual void onWheelDown(int x, int y) {}
+ virtual void onKey(KeyboardEvent *ke) {}
+ virtual void onTick() {}
+
+ virtual void releaseMouse(int x, int y) {}
+};
+
+/**
+ * This class is used to draw text in dialogs, buttons, etc.
+ */
+
+class FontRendererGui {
+private:
+ Sword2Engine *_vm;
+
+ struct Glyph {
+ byte *_data;
+ int _width;
+ int _height;
+ };
+
+ Glyph _glyph[SIZE_OF_CHAR_SET];
+
+ int _fontId;
+
+public:
+ enum {
+ kAlignLeft,
+ kAlignRight,
+ kAlignCenter
+ };
+
+ FontRendererGui(Sword2Engine *vm, int fontId);
+ ~FontRendererGui();
+
+ void fetchText(uint32 textId, byte *buf);
+
+ int getCharWidth(byte c);
+ int getCharHeight(byte c);
+
+ int getTextWidth(byte *text);
+ int getTextWidth(uint32 textId);
+
+ void drawText(byte *text, int x, int y, int alignment = kAlignLeft);
+ void drawText(uint32 textId, int x, int y, int alignment = kAlignLeft);
+};
+
+FontRendererGui::FontRendererGui(Sword2Engine *vm, int fontId)
+ : _vm(vm), _fontId(fontId) {
+ byte *font = _vm->_resman->openResource(fontId);
+ SpriteInfo sprite;
+
+ sprite.type = RDSPR_NOCOMPRESSION | RDSPR_TRANS;
+
+ for (int i = 0; i < SIZE_OF_CHAR_SET; i++) {
+ byte *frame = _vm->fetchFrameHeader(font, i);
+
+ FrameHeader frame_head;
+
+ frame_head.read(frame);
+
+ sprite.data = frame + FrameHeader::size();
+ sprite.w = frame_head.width;
+ sprite.h = frame_head.height;
+ _vm->_screen->createSurface(&sprite, &_glyph[i]._data);
+ _glyph[i]._width = frame_head.width;
+ _glyph[i]._height = frame_head.height;
+ }
+
+ _vm->_resman->closeResource(fontId);
+}
+
+FontRendererGui::~FontRendererGui() {
+ for (int i = 0; i < SIZE_OF_CHAR_SET; i++)
+ _vm->_screen->deleteSurface(_glyph[i]._data);
+}
+
+void FontRendererGui::fetchText(uint32 textId, byte *buf) {
+ byte *data = _vm->fetchTextLine(_vm->_resman->openResource(textId / SIZE), textId & 0xffff);
+ int i;
+
+ for (i = 0; data[i + 2]; i++) {
+ if (buf)
+ buf[i] = data[i + 2];
+ }
+
+ buf[i] = 0;
+ _vm->_resman->closeResource(textId / SIZE);
+}
+
+int FontRendererGui::getCharWidth(byte c) {
+ if (c < 32)
+ return 0;
+ return _glyph[c - 32]._width;
+}
+
+int FontRendererGui::getCharHeight(byte c) {
+ if (c < 32)
+ return 0;
+ return _glyph[c - 32]._height;
+}
+
+int FontRendererGui::getTextWidth(byte *text) {
+ int textWidth = 0;
+
+ for (int i = 0; text[i]; i++)
+ if (text[i] >= ' ')
+ textWidth += (getCharWidth(text[i]) - CHARACTER_OVERLAP);
+ return textWidth;
+}
+
+int FontRendererGui::getTextWidth(uint32 textId) {
+ byte text[MAX_STRING_LEN];
+
+ fetchText(textId, text);
+ return getTextWidth(text);
+}
+
+void FontRendererGui::drawText(byte *text, int x, int y, int alignment) {
+ SpriteInfo sprite;
+ int i;
+
+ if (alignment != kAlignLeft) {
+ int textWidth = getTextWidth(text);
+
+ switch (alignment) {
+ case kAlignRight:
+ x -= textWidth;
+ break;
+ case kAlignCenter:
+ x -= (textWidth / 2);
+ break;
+ }
+ }
+
+ sprite.x = x;
+ sprite.y = y;
+
+ for (i = 0; text[i]; i++) {
+ if (text[i] >= ' ') {
+ sprite.w = getCharWidth(text[i]);
+ sprite.h = getCharHeight(text[i]);
+
+ _vm->_screen->drawSurface(&sprite, _glyph[text[i] - 32]._data);
+
+ sprite.x += (getCharWidth(text[i]) - CHARACTER_OVERLAP);
+ }
+ }
+}
+
+void FontRendererGui::drawText(uint32 textId, int x, int y, int alignment) {
+ byte text[MAX_STRING_LEN];
+
+ fetchText(textId, text);
+ drawText(text, x, y, alignment);
+}
+
+//
+// Dialog class functions
+//
+
+Dialog::Dialog(Sword2Engine *vm)
+ : _numWidgets(0), _finish(false), _result(0), _vm(vm) {
+ _vm->_screen->setFullPalette(CONTROL_PANEL_PALETTE);
+ _vm->_screen->clearScene();
+ _vm->_screen->updateDisplay();
+
+ // Usually the mouse pointer will already be "normal", but not always.
+ _vm->_mouse->setMouse(NORMAL_MOUSE_ID);
+}
+
+Dialog::~Dialog() {
+ for (int i = 0; i < _numWidgets; i++)
+ delete _widgets[i];
+ _vm->_screen->clearScene();
+ _vm->_screen->updateDisplay();
+}
+
+void Dialog::registerWidget(Widget *widget) {
+ if (_numWidgets < MAX_WIDGETS)
+ _widgets[_numWidgets++] = widget;
+}
+
+void Dialog::paint() {
+ _vm->_screen->clearScene();
+ for (int i = 0; i < _numWidgets; i++)
+ _widgets[i]->paint();
+}
+
+void Dialog::setResult(int result) {
+ _result = result;
+ _finish = true;
+}
+
+int Dialog::runModal() {
+ uint32 oldFilter = _vm->setInputEventFilter(0);
+
+ int i;
+
+ paint();
+
+ int oldMouseX = -1;
+ int oldMouseY = -1;
+
+ while (!_finish) {
+ // So that the menu icons will reach their full size
+ _vm->_mouse->processMenu();
+ _vm->_screen->updateDisplay(false);
+
+ int newMouseX, newMouseY;
+
+ _vm->_mouse->getPos(newMouseX, newMouseY);
+
+ newMouseY += 40;
+
+ MouseEvent *me = _vm->mouseEvent();
+ KeyboardEvent *ke = _vm->keyboardEvent();
+
+ if (ke) {
+ if (ke->keycode == 27)
+ setResult(0);
+ else if (ke->keycode == '\n' || ke->keycode == '\r')
+ setResult(1);
+ }
+
+ int oldHit = -1;
+ int newHit = -1;
+
+ // Find out which widget the mouse was over the last time, and
+ // which it is currently over. This assumes the widgets do not
+ // overlap.
+
+ for (i = 0; i < _numWidgets; i++) {
+ if (_widgets[i]->isHit(oldMouseX, oldMouseY))
+ oldHit = i;
+ if (_widgets[i]->isHit(newMouseX, newMouseY))
+ newHit = i;
+ }
+
+ // Was the mouse inside a widget the last time?
+
+ if (oldHit >= 0) {
+ if (newHit != oldHit)
+ _widgets[oldHit]->onMouseExit();
+ }
+
+ // Is the mouse currently in a widget?
+
+ if (newHit >= 0) {
+ if (newHit != oldHit)
+ _widgets[newHit]->onMouseEnter();
+
+ if (me) {
+ switch (me->buttons) {
+ case RD_LEFTBUTTONDOWN:
+ _widgets[newHit]->onMouseDown(newMouseX, newMouseY);
+ break;
+ case RD_LEFTBUTTONUP:
+ _widgets[newHit]->onMouseUp(newMouseX, newMouseY);
+ break;
+ case RD_WHEELUP:
+ _widgets[newHit]->onWheelUp(newMouseX, newMouseY);
+ break;
+ case RD_WHEELDOWN:
+ _widgets[newHit]->onWheelDown(newMouseX, newMouseY);
+ break;
+ }
+ }
+ }
+
+ // Some events are passed to the widgets regardless of where
+ // the mouse cursor is.
+
+ for (i = 0; i < _numWidgets; i++) {
+ if (me && me->buttons == RD_LEFTBUTTONUP) {
+ // So that slider widgets will know when the
+ // user releases the mouse button, even if the
+ // cursor is outside of the slider's hit area.
+ _widgets[i]->releaseMouse(newMouseX, newMouseY);
+ }
+
+ // This is to make it easier to drag the slider widget
+
+ if (newMouseX != oldMouseX || newMouseY != oldMouseY)
+ _widgets[i]->onMouseMove(newMouseX, newMouseY);
+
+ if (ke)
+ _widgets[i]->onKey(ke);
+
+ _widgets[i]->onTick();
+ }
+
+ oldMouseX = newMouseX;
+ oldMouseY = newMouseY;
+
+ _vm->_system->delayMillis(20);
+
+ if (_vm->_quit)
+ setResult(0);
+ }
+
+ _vm->setInputEventFilter(oldFilter);
+ return _result;
+}
+
+//
+// Widget functions
+//
+
+Widget::Widget(Dialog *parent, int states)
+ : _vm(parent->_vm), _parent(parent), _numStates(states), _state(0) {
+ _sprites = (SpriteInfo *)calloc(states, sizeof(SpriteInfo));
+ _surfaces = (WidgetSurface *)calloc(states, sizeof(WidgetSurface));
+
+ _hitRect.left = _hitRect.right = _hitRect.top = _hitRect.bottom = -1;
+}
+
+Widget::~Widget() {
+ for (int i = 0; i < _numStates; i++) {
+ if (_surfaces[i]._original)
+ _vm->_screen->deleteSurface(_surfaces[i]._surface);
+ }
+ free(_sprites);
+ free(_surfaces);
+}
+
+void Widget::createSurfaceImage(int state, uint32 res, int x, int y, uint32 pc) {
+ byte *file, *colTablePtr = NULL;
+ AnimHeader anim_head;
+ FrameHeader frame_head;
+ CdtEntry cdt_entry;
+ uint32 spriteType = RDSPR_TRANS;
+
+ // open anim resource file, point to base
+ file = _vm->_resman->openResource(res);
+
+ byte *frame = _vm->fetchFrameHeader(file, pc);
+
+ anim_head.read(_vm->fetchAnimHeader(file));
+ cdt_entry.read(_vm->fetchCdtEntry(file, pc));
+ frame_head.read(frame);
+
+ // If the frame is flipped. (Only really applicable to frames using
+ // offsets.)
+
+ if (cdt_entry.frameType & FRAME_FLIPPED)
+ spriteType |= RDSPR_FLIP;
+
+ // Which compression was used?
+
+ switch (anim_head.runTimeComp) {
+ case NONE:
+ spriteType |= RDSPR_NOCOMPRESSION;
+ break;
+ case RLE256:
+ spriteType |= RDSPR_RLE256;
+ break;
+ case RLE16:
+ spriteType |= RDSPR_RLE256;
+ // Points to just after last cdt_entry, i.e. start of colour
+ // table
+ colTablePtr = _vm->fetchAnimHeader(file) + AnimHeader::size()
+ + anim_head.noAnimFrames * CdtEntry::size();
+ break;
+ }
+
+ _sprites[state].x = x;
+ _sprites[state].y = y;
+ _sprites[state].w = frame_head.width;
+ _sprites[state].h = frame_head.height;
+ _sprites[state].scale = 0;
+ _sprites[state].type = spriteType;
+ _sprites[state].blend = anim_head.blend;
+
+ // Points to just after frame header, ie. start of sprite data
+ _sprites[state].data = frame + FrameHeader::size();
+
+ _vm->_screen->createSurface(&_sprites[state], &_surfaces[state]._surface);
+ _surfaces[state]._original = true;
+
+ // Release the anim resource
+ _vm->_resman->closeResource(res);
+}
+
+void Widget::linkSurfaceImage(Widget *from, int state, int x, int y) {
+ _sprites[state].x = x;
+ _sprites[state].y = y;
+ _sprites[state].w = from->_sprites[state].w;
+ _sprites[state].h = from->_sprites[state].h;
+ _sprites[state].scale = from->_sprites[state].scale;
+ _sprites[state].type = from->_sprites[state].type;
+ _sprites[state].blend = from->_sprites[state].blend;
+
+ _surfaces[state]._surface = from->_surfaces[state]._surface;
+ _surfaces[state]._original = false;
+}
+
+void Widget::createSurfaceImages(uint32 res, int x, int y) {
+ for (int i = 0; i < _numStates; i++)
+ createSurfaceImage(i, res, x, y, i);
+}
+
+void Widget::linkSurfaceImages(Widget *from, int x, int y) {
+ for (int i = 0; i < from->_numStates; i++)
+ linkSurfaceImage(from, i, x, y);
+}
+
+void Widget::setHitRect(int x, int y, int width, int height) {
+ _hitRect.left = x;
+ _hitRect.right = x + width;
+ _hitRect.top = y;
+ _hitRect.bottom = y + height;
+}
+
+bool Widget::isHit(int16 x, int16 y) {
+ return _hitRect.left >= 0 && _hitRect.contains(x, y);
+}
+
+void Widget::setState(int state) {
+ if (state != _state) {
+ _state = state;
+ paint();
+ }
+}
+
+int Widget::getState() {
+ return _state;
+}
+
+void Widget::paint(Common::Rect *clipRect) {
+ _vm->_screen->drawSurface(&_sprites[_state], _surfaces[_state]._surface, clipRect);
+}
+
+/**
+ * Standard button class.
+ */
+
+class Button : public Widget {
+public:
+ Button(Dialog *parent, int x, int y, int w, int h)
+ : Widget(parent, 2) {
+ setHitRect(x, y, w, h);
+ }
+
+ virtual void onMouseExit() {
+ setState(0);
+ }
+
+ virtual void onMouseDown(int x, int y) {
+ setState(1);
+ }
+
+ virtual void onMouseUp(int x, int y) {
+ if (getState() != 0) {
+ setState(0);
+ _parent->onAction(this);
+ }
+ }
+};
+
+/**
+ * Scroll buttons are used to scroll the savegame list. The difference between
+ * this and a normal button is that we want this to repeat.
+ */
+
+class ScrollButton : public Widget {
+private:
+ uint32 _holdCounter;
+
+public:
+ ScrollButton(Dialog *parent, int x, int y, int w, int h)
+ : Widget(parent, 2), _holdCounter(0) {
+ setHitRect(x, y, w, h);
+ }
+
+ virtual void onMouseExit() {
+ setState(0);
+ }
+
+ virtual void onMouseDown(int x, int y) {
+ setState(1);
+ _parent->onAction(this);
+ _holdCounter = 0;
+ }
+
+ virtual void onMouseUp(int x, int y) {
+ setState(0);
+ }
+
+ virtual void onTick() {
+ if (getState() != 0) {
+ _holdCounter++;
+ if (_holdCounter > 16 && (_holdCounter % 4) == 0)
+ _parent->onAction(this);
+ }
+ }
+};
+
+/**
+ * A switch is a button that changes state when clicked, and keeps that state
+ * until clicked again.
+ */
+
+class Switch : public Widget {
+private:
+ bool _holding, _value;
+ int _upState, _downState;
+
+public:
+ Switch(Dialog *parent, int x, int y, int w, int h)
+ : Widget(parent, 2), _holding(false), _value(false),
+ _upState(0), _downState(1) {
+ setHitRect(x, y, w, h);
+ }
+
+ // The sound mute switches have 0 as their "down" state and 1 as
+ // their "up" state, so this function is needed to get consistent
+ // behaviour.
+
+ void reverseStates() {
+ _upState = 1;
+ _downState = 0;
+ }
+
+ void setValue(bool value) {
+ _value = value;
+ if (_value)
+ setState(_downState);
+ else
+ setState(_upState);
+ }
+
+ bool getValue() {
+ return _value;
+ }
+
+ virtual void onMouseExit() {
+ if (_holding && !_value)
+ setState(_upState);
+ _holding = false;
+ }
+
+ virtual void onMouseDown(int x, int y) {
+ _holding = true;
+ setState(_downState);
+ }
+
+ virtual void onMouseUp(int x, int y) {
+ if (_holding) {
+ _holding = false;
+ _value = !_value;
+ if (_value)
+ setState(_downState);
+ else
+ setState(_upState);
+ _parent->onAction(this, getState());
+ }
+ }
+};
+
+/**
+ * A slider is used to specify a value within a pre-defined range.
+ */
+
+class Slider : public Widget {
+private:
+ Widget *_background;
+ bool _dragging;
+ int _value, _targetValue;
+ int _maxValue;
+ int _valueStep;
+ int _dragOffset;
+
+ int posFromValue(int value) {
+ return _hitRect.left + (value * (_hitRect.width() - 38)) / _maxValue;
+ }
+
+ int valueFromPos(int x) {
+ return (int)((double)(_maxValue * (x - _hitRect.left)) / (double)(_hitRect.width() - 38) + 0.5);
+ }
+
+public:
+ Slider(Dialog *parent, Widget *background, int max,
+ int x, int y, int w, int h, int step, Widget *base = NULL)
+ : Widget(parent, 1), _background(background),
+ _dragging(false), _value(0), _targetValue(0),
+ _maxValue(max), _valueStep(step) {
+ setHitRect(x, y, w, h);
+
+ if (_valueStep <= 0)
+ _valueStep = 1;
+
+ if (base)
+ linkSurfaceImages(base, x, y);
+ else
+ createSurfaceImages(3406, x, y);
+ }
+
+ virtual void paint(Common::Rect *clipRect = NULL) {
+ // This will redraw a bit more than is strictly necessary,
+ // but I doubt that will make any noticeable difference.
+
+ _background->paint(&_hitRect);
+ Widget::paint(clipRect);
+ }
+
+ void setValue(int value) {
+ _value = value;
+ _targetValue = value;
+ _sprites[0].x = posFromValue(_value);
+ paint();
+ }
+
+ int getValue() {
+ return _value;
+ }
+
+ virtual void onMouseMove(int x, int y) {
+ if (_dragging) {
+ int newX = x - _dragOffset;
+ int newValue;
+
+ if (newX < _hitRect.left)
+ newX = _hitRect.left;
+ else if (newX + 38 > _hitRect.right)
+ newX = _hitRect.right - 38;
+
+ _sprites[0].x = newX;
+
+ newValue = valueFromPos(newX);
+ if (newValue != _value) {
+ _value = newValue;
+ _targetValue = newValue;
+ _parent->onAction(this, newValue);
+ }
+
+ paint();
+ }
+ }
+
+ virtual void onMouseDown(int x, int y) {
+ if (x >= _sprites[0].x && x < _sprites[0].x + 38) {
+ _dragging = true;
+ _dragOffset = x - _sprites[0].x;
+ } else if (x < _sprites[0].x) {
+ if (_targetValue >= _valueStep)
+ _targetValue -= _valueStep;
+ else
+ _targetValue = 0;
+ } else {
+ if (_targetValue < _maxValue - _valueStep)
+ _targetValue += _valueStep;
+ else
+ _targetValue = _maxValue;
+ }
+ }
+
+ virtual void releaseMouse(int x, int y) {
+ if (_dragging)
+ _dragging = false;
+ }
+
+ virtual void onTick() {
+ if (!_dragging) {
+ int target = posFromValue(_targetValue);
+
+ if (target != _sprites[0].x) {
+ if (target < _sprites[0].x) {
+ _sprites[0].x -= 4;
+ if (_sprites[0].x < target)
+ _sprites[0].x = target;
+ } else if (target > _sprites[0].x) {
+ _sprites[0].x += 4;
+ if (_sprites[0].x > target)
+ _sprites[0].x = target;
+ }
+
+ int newValue = valueFromPos(_sprites[0].x);
+ if (newValue != _value) {
+ _value = newValue;
+ _parent->onAction(this, newValue);
+ }
+
+ paint();
+ }
+ }
+ }
+};
+
+/**
+ * The "mini" dialog.
+ */
+
+MiniDialog::MiniDialog(Sword2Engine *vm, uint32 headerTextId, uint32 okTextId, uint32 cancelTextId) : Dialog(vm) {
+ _headerTextId = headerTextId;
+ _okTextId = okTextId;
+ _cancelTextId = cancelTextId;
+
+ _fr = new FontRendererGui(_vm, _vm->_controlsFontId);
+
+ _panel = new Widget(this, 1);
+ _panel->createSurfaceImages(1996, 203, 104);
+
+ _okButton = new Button(this, 243, 214, 24, 24);
+ _okButton->createSurfaceImages(2002, 243, 214);
+
+ _cancelButton = new Button(this, 243, 276, 24, 24);
+ _cancelButton->linkSurfaceImages(_okButton, 243, 276);
+
+ registerWidget(_panel);
+ registerWidget(_okButton);
+ registerWidget(_cancelButton);
+}
+
+MiniDialog::~MiniDialog() {
+ delete _fr;
+}
+
+void MiniDialog::paint() {
+ Dialog::paint();
+
+ if (_headerTextId)
+ _fr->drawText(_headerTextId, 310, 134, FontRendererGui::kAlignCenter);
+ _fr->drawText(_okTextId, 270, 214);
+ _fr->drawText(_cancelTextId, 270, 276);
+}
+
+void MiniDialog::onAction(Widget *widget, int result) {
+ if (widget == _okButton)
+ setResult(1);
+ else if (widget == _cancelButton)
+ setResult(0);
+}
+
+StartDialog::StartDialog(Sword2Engine *vm) : MiniDialog(vm, 0) {}
+
+int StartDialog::runModal() {
+ while (1) {
+ MiniDialog startDialog(_vm, 0, TEXT_RESTART, TEXT_RESTORE);
+
+ if (startDialog.runModal())
+ return 1;
+
+ if (_vm->_quit)
+ return 0;
+
+ RestoreDialog restoreDialog(_vm);
+
+ if (restoreDialog.runModal())
+ return 0;
+
+ if (_vm->_quit)
+ return 0;
+ }
+
+ return 1;
+}
+
+/**
+ * The restart dialog.
+ */
+
+RestartDialog::RestartDialog(Sword2Engine *vm) : MiniDialog(vm, TEXT_RESTART) {}
+
+int RestartDialog::runModal() {
+ int result = MiniDialog::runModal();
+
+ if (result)
+ _vm->restartGame();
+
+ return result;
+}
+
+/**
+ * The quit dialog.
+ */
+
+QuitDialog::QuitDialog(Sword2Engine *vm) : MiniDialog(vm, TEXT_QUIT) {}
+
+int QuitDialog::runModal() {
+ int result = MiniDialog::runModal();
+
+ if (result)
+ _vm->closeGame();
+
+ return result;
+}
+
+/**
+ * The game settings dialog.
+ */
+
+OptionsDialog::OptionsDialog(Sword2Engine *vm) : Dialog(vm) {
+ _fr = new FontRendererGui(_vm, _vm->_controlsFontId);
+
+ _mixer = _vm->_mixer;
+
+ _panel = new Widget(this, 1);
+ _panel->createSurfaceImages(3405, 0, 40);
+
+ _objectLabelsSwitch = new Switch(this, 304, 100, 53, 32);
+ _objectLabelsSwitch->createSurfaceImages(3687, 304, 100);
+
+ _subtitlesSwitch = new Switch(this, 510, 100, 53, 32);
+ _subtitlesSwitch->linkSurfaceImages(_objectLabelsSwitch, 510, 100);
+
+ _reverseStereoSwitch = new Switch(this, 304, 293, 53, 32);
+ _reverseStereoSwitch->linkSurfaceImages(_objectLabelsSwitch, 304, 293);
+
+ _musicSwitch = new Switch(this, 516, 157, 40, 32);
+ _musicSwitch->createSurfaceImages(3315, 516, 157);
+ _musicSwitch->reverseStates();
+
+ _speechSwitch = new Switch(this, 516, 205, 40, 32);
+ _speechSwitch->linkSurfaceImages(_musicSwitch, 516, 205);
+ _speechSwitch->reverseStates();
+
+ _fxSwitch = new Switch(this, 516, 250, 40, 32);
+ _fxSwitch->linkSurfaceImages(_musicSwitch, 516, 250);
+ _fxSwitch->reverseStates();
+
+ int volStep = Audio::Mixer::kMaxMixerVolume / 10;
+
+ _musicSlider = new Slider(this, _panel, Audio::Mixer::kMaxMixerVolume, 309, 161, 170, 27, volStep);
+ _speechSlider = new Slider(this, _panel, Audio::Mixer::kMaxMixerVolume, 309, 208, 170, 27, volStep, _musicSlider);
+ _fxSlider = new Slider(this, _panel, Audio::Mixer::kMaxMixerVolume, 309, 254, 170, 27, volStep, _musicSlider);
+ _gfxSlider = new Slider(this, _panel, 3, 309, 341, 170, 27, 1, _musicSlider);
+
+ _gfxPreview = new Widget(this, 4);
+ _gfxPreview->createSurfaceImages(256, 495, 310);
+
+ _okButton = new Button(this, 203, 382, 53, 32);
+ _okButton->createSurfaceImages(901, 203, 382);
+
+ _cancelButton = new Button(this, 395, 382, 53, 32);
+ _cancelButton->linkSurfaceImages(_okButton, 395, 382);
+
+ registerWidget(_panel);
+ registerWidget(_objectLabelsSwitch);
+ registerWidget(_subtitlesSwitch);
+ registerWidget(_reverseStereoSwitch);
+ registerWidget(_musicSwitch);
+ registerWidget(_speechSwitch);
+ registerWidget(_fxSwitch);
+ registerWidget(_musicSlider);
+ registerWidget(_speechSlider);
+ registerWidget(_fxSlider);
+ registerWidget(_gfxSlider);
+ registerWidget(_gfxPreview);
+ registerWidget(_okButton);
+ registerWidget(_cancelButton);
+
+ _objectLabelsSwitch->setValue(_vm->_mouse->getObjectLabels());
+ _subtitlesSwitch->setValue(_vm->getSubtitles());
+ _reverseStereoSwitch->setValue(_vm->_sound->isReverseStereo());
+ _musicSwitch->setValue(!_vm->_sound->isMusicMute());
+ _speechSwitch->setValue(!_vm->_sound->isSpeechMute());
+ _fxSwitch->setValue(!_vm->_sound->isFxMute());
+
+ _musicSlider->setValue(_mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType));
+ _speechSlider->setValue(_mixer->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType));
+ _fxSlider->setValue(_mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType));
+
+ _gfxSlider->setValue(_vm->_screen->getRenderLevel());
+ _gfxPreview->setState(_vm->_screen->getRenderLevel());
+}
+
+OptionsDialog::~OptionsDialog() {
+ delete _fr;
+}
+
+void OptionsDialog::paint() {
+ Dialog::paint();
+
+ int maxWidth = 0;
+ int width;
+
+ uint32 alignTextIds[] = {
+ TEXT_OBJECT_LABELS,
+ TEXT_MUSIC_VOLUME,
+ TEXT_SPEECH_VOLUME,
+ TEXT_FX_VOLUME,
+ TEXT_GFX_QUALITY,
+ TEXT_REVERSE_STEREO
+ };
+
+ for (int i = 0; i < ARRAYSIZE(alignTextIds); i++) {
+ width = _fr->getTextWidth(alignTextIds[i]);
+ if (width > maxWidth)
+ maxWidth = width;
+ }
+
+ _fr->drawText(TEXT_OPTIONS, 321, 55, FontRendererGui::kAlignCenter);
+ _fr->drawText(TEXT_SUBTITLES, 500, 103, FontRendererGui::kAlignRight);
+ _fr->drawText(TEXT_OBJECT_LABELS, 299 - maxWidth, 103);
+ _fr->drawText(TEXT_MUSIC_VOLUME, 299 - maxWidth, 161);
+ _fr->drawText(TEXT_SPEECH_VOLUME, 299 - maxWidth, 208);
+ _fr->drawText(TEXT_FX_VOLUME, 299 - maxWidth, 254);
+ _fr->drawText(TEXT_REVERSE_STEREO, 299 - maxWidth, 296);
+ _fr->drawText(TEXT_GFX_QUALITY, 299 - maxWidth, 341);
+ _fr->drawText(TEXT_OK, 193, 382, FontRendererGui::kAlignRight);
+ _fr->drawText(TEXT_CANCEL, 385, 382, FontRendererGui::kAlignRight);
+}
+
+void OptionsDialog::onAction(Widget *widget, int result) {
+ // Since there is music playing while the dialog is displayed we need
+ // to update music volume immediately.
+
+ if (widget == _musicSwitch) {
+ _vm->_sound->muteMusic(result != 0);
+ } else if (widget == _musicSlider) {
+ _vm->_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, result);
+ _vm->_sound->muteMusic(result == 0);
+ _musicSwitch->setValue(result != 0);
+ } else if (widget == _speechSlider) {
+ _speechSwitch->setValue(result != 0);
+ } else if (widget == _fxSlider) {
+ _fxSwitch->setValue(result != 0);
+ } else if (widget == _gfxSlider) {
+ _gfxPreview->setState(result);
+ _vm->_screen->setRenderLevel(result);
+ } else if (widget == _okButton) {
+ // Apply the changes
+ _vm->setSubtitles(_subtitlesSwitch->getValue());
+ _vm->_mouse->setObjectLabels(_objectLabelsSwitch->getValue());
+ _vm->_sound->muteMusic(!_musicSwitch->getValue());
+ _vm->_sound->muteSpeech(!_speechSwitch->getValue());
+ _vm->_sound->muteFx(!_fxSwitch->getValue());
+ _vm->_sound->setReverseStereo(_reverseStereoSwitch->getValue());
+ _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, _musicSlider->getValue());
+ _mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, _speechSlider->getValue());
+ _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, _fxSlider->getValue());
+ _vm->_screen->setRenderLevel(_gfxSlider->getValue());
+
+ _vm->writeSettings();
+ setResult(1);
+ } else if (widget == _cancelButton) {
+ // Revert the changes
+ _vm->readSettings();
+ setResult(0);
+ }
+}
+
+// Slot button actions. Note that keyboard input generates positive actions
+
+enum {
+ kSelectSlot = -1,
+ kDeselectSlot = -2,
+ kWheelDown = -3,
+ kWheelUp = -4,
+ kStartEditing = -5,
+ kCursorTick = -6
+};
+
+class Slot : public Widget {
+private:
+ int _mode;
+ FontRendererGui *_fr;
+ byte _text[SAVE_DESCRIPTION_LEN];
+ bool _clickable;
+ bool _editable;
+
+public:
+ Slot(Dialog *parent, int x, int y, int w, int h)
+ : Widget(parent, 2), _clickable(false), _editable(false) {
+ setHitRect(x, y, w, h);
+ _text[0] = 0;
+ }
+
+ void setMode(int mode) {
+ _mode = mode;
+ }
+
+ void setClickable(bool clickable) {
+ _clickable = clickable;
+ }
+
+ void setEditable(bool editable) {
+ _editable = editable;
+ _vm->_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, editable);
+ }
+
+ bool isEditable() {
+ return _editable;
+ }
+
+ void setText(FontRendererGui *fr, int slot, byte *text) {
+ _fr = fr;
+ if (text)
+ sprintf((char *)_text, "%d. %s", slot, text);
+ else
+ sprintf((char *)_text, "%d. ", slot);
+ }
+
+ byte *getText() {
+ return &_text[0];
+ }
+
+ virtual void paint(Common::Rect *clipRect = NULL) {
+ Widget::paint();
+
+ // HACK: The main dialog is responsible for drawing the text
+ // when in editing mode.
+
+ if (!_editable)
+ _fr->drawText(_text, _sprites[0].x + 16, _sprites[0].y + 4 + 2 * getState());
+ }
+
+ virtual void onMouseDown(int x, int y) {
+ if (_clickable) {
+ if (getState() == 0) {
+ setState(1);
+ _parent->onAction(this, kSelectSlot);
+ if (_mode == kSaveDialog)
+ _parent->onAction(this, kStartEditing);
+ } else if (_mode == kRestoreDialog) {
+ setState(0);
+ _parent->onAction(this, kDeselectSlot);
+ }
+ }
+ }
+
+ virtual void onWheelUp(int x, int y) {
+ _parent->onAction(this, kWheelUp);
+ }
+
+ virtual void onWheelDown(int x, int y) {
+ _parent->onAction(this, kWheelDown);
+ }
+
+ virtual void onKey(KeyboardEvent *ke) {
+ if (_editable) {
+ if (ke->keycode == 8)
+ _parent->onAction(this, 8);
+ else if (ke->ascii >= ' ' && ke->ascii <= 255) {
+ // Accept the character if the font renderer
+ // has what looks like a valid glyph for it.
+ if (_fr->getCharWidth(ke->ascii))
+ _parent->onAction(this, ke->ascii);
+ }
+ }
+ }
+
+ virtual void onTick() {
+ if (_editable)
+ _parent->onAction(this, kCursorTick);
+ }
+
+ void setY(int y) {
+ for (int i = 0; i < _numStates; i++)
+ _sprites[i].y = y;
+ setHitRect(_hitRect.left, y, _hitRect.width(), _hitRect.height());
+ }
+
+ int getY() {
+ return _sprites[0].y;
+ }
+};
+
+SaveRestoreDialog::SaveRestoreDialog(Sword2Engine *vm, int mode) : Dialog(vm) {
+ int i;
+
+ _mode = mode;
+ _selectedSlot = -1;
+
+ // FIXME: The "control font" and the "red font" are currently always
+ // the same font, so one should be eliminated.
+
+ _fr1 = new FontRendererGui(_vm, _vm->_controlsFontId);
+ _fr2 = new FontRendererGui(_vm, _vm->_redFontId);
+
+ _panel = new Widget(this, 1);
+ _panel->createSurfaceImages(2016, 0, 40);
+
+ for (i = 0; i < 4; i++) {
+ _slotButton[i] = new Slot(this, 114, 0, 384, 36);
+ _slotButton[i]->createSurfaceImages(2006 + i, 114, 0);
+ _slotButton[i]->setMode(mode);
+ _slotButton[i + 4] = new Slot(this, 114, 0, 384, 36);
+ _slotButton[i + 4]->linkSurfaceImages(_slotButton[i], 114, 0);
+ _slotButton[i + 4]->setMode(mode);
+ }
+
+ updateSlots();
+
+ _zupButton = new ScrollButton(this, 516, 65, 17, 17);
+ _zupButton->createSurfaceImages(1982, 516, 65);
+
+ _upButton = new ScrollButton(this, 516, 85, 17, 17);
+ _upButton->createSurfaceImages(2067, 516, 85);
+
+ _downButton = new ScrollButton(this, 516, 329, 17, 17);
+ _downButton->createSurfaceImages(1986, 516, 329);
+
+ _zdownButton = new ScrollButton(this, 516, 350, 17, 17);
+ _zdownButton->createSurfaceImages(1988, 516, 350);
+
+ _okButton = new Button(this, 130, 377, 24, 24);
+ _okButton->createSurfaceImages(2002, 130, 377);
+
+ _cancelButton = new Button(this, 350, 377, 24, 24);
+ _cancelButton->linkSurfaceImages(_okButton, 350, 377);
+
+ registerWidget(_panel);
+
+ for (i = 0; i < 8; i++)
+ registerWidget(_slotButton[i]);
+
+ registerWidget(_zupButton);
+ registerWidget(_upButton);
+ registerWidget(_downButton);
+ registerWidget(_zdownButton);
+ registerWidget(_okButton);
+ registerWidget(_cancelButton);
+}
+
+SaveRestoreDialog::~SaveRestoreDialog() {
+ delete _fr1;
+ delete _fr2;
+}
+
+// There aren't really a hundred different button objects of course, there are
+// only eight. Re-arrange them to simulate scrolling.
+
+void SaveRestoreDialog::updateSlots() {
+ for (int i = 0; i < 8; i++) {
+ Slot *slot = _slotButton[(baseSlot + i) % 8];
+ FontRendererGui *fr;
+ byte description[SAVE_DESCRIPTION_LEN];
+
+ slot->setY(72 + i * 36);
+
+ if (baseSlot + i == _selectedSlot) {
+ slot->setEditable(_mode == kSaveDialog);
+ slot->setState(1);
+ fr = _fr2;
+ } else {
+ slot->setEditable(false);
+ slot->setState(0);
+ fr = _fr1;
+ }
+
+ if (_vm->getSaveDescription(baseSlot + i, description) == SR_OK) {
+ slot->setText(fr, baseSlot + i, description);
+ slot->setClickable(true);
+ } else {
+ slot->setText(fr, baseSlot + i, NULL);
+ slot->setClickable(_mode == kSaveDialog);
+ }
+
+ if (slot->isEditable())
+ drawEditBuffer(slot);
+ else
+ slot->paint();
+ }
+}
+
+void SaveRestoreDialog::drawEditBuffer(Slot *slot) {
+ if (_selectedSlot == -1)
+ return;
+
+ // This will redraw a bit more than is strictly necessary, but I doubt
+ // that will make any noticeable difference.
+
+ slot->paint();
+ _fr2->drawText(_editBuffer, 130, 78 + (_selectedSlot - baseSlot) * 36);
+}
+
+void SaveRestoreDialog::onAction(Widget *widget, int result) {
+ if (widget == _zupButton) {
+ if (baseSlot > 0) {
+ if (baseSlot >= 8)
+ baseSlot -= 8;
+ else
+ baseSlot = 0;
+ updateSlots();
+ }
+ } else if (widget == _upButton) {
+ if (baseSlot > 0) {
+ baseSlot--;
+ updateSlots();
+ }
+ } else if (widget == _downButton) {
+ if (baseSlot < 92) {
+ baseSlot++;
+ updateSlots();
+ }
+ } else if (widget == _zdownButton) {
+ if (baseSlot < 92) {
+ if (baseSlot <= 84)
+ baseSlot += 8;
+ else
+ baseSlot = 92;
+ updateSlots();
+ }
+ } else if (widget == _okButton) {
+ setResult(1);
+ } else if (widget == _cancelButton) {
+ setResult(0);
+ } else {
+ Slot *slot = (Slot *)widget;
+ int textWidth;
+ byte tmp;
+ int i;
+ int j;
+
+ switch (result) {
+ case kWheelUp:
+ onAction(_upButton);
+ break;
+ case kWheelDown:
+ onAction(_downButton);
+ break;
+ case kSelectSlot:
+ case kDeselectSlot:
+ if (result == kSelectSlot)
+ _selectedSlot = baseSlot + (slot->getY() - 72) / 35;
+ else if (result == kDeselectSlot)
+ _selectedSlot = -1;
+
+ for (i = 0; i < 8; i++)
+ if (widget == _slotButton[i])
+ break;
+
+ for (j = 0; j < 8; j++) {
+ if (j != i) {
+ _slotButton[j]->setEditable(false);
+ _slotButton[j]->setState(0);
+ }
+ }
+ break;
+ case kStartEditing:
+ if (_selectedSlot >= 10)
+ _firstPos = 5;
+ else
+ _firstPos = 4;
+
+ strcpy((char *)_editBuffer, (char *)slot->getText());
+ _editPos = strlen((char *)_editBuffer);
+ _cursorTick = 0;
+ _editBuffer[_editPos] = '_';
+ _editBuffer[_editPos + 1] = 0;
+ slot->setEditable(true);
+ drawEditBuffer(slot);
+ break;
+ case kCursorTick:
+ _cursorTick++;
+ if (_cursorTick == 7) {
+ _editBuffer[_editPos] = ' ';
+ drawEditBuffer(slot);
+ } else if (_cursorTick == 14) {
+ _cursorTick = 0;
+ _editBuffer[_editPos] = '_';
+ drawEditBuffer(slot);
+ }
+ break;
+ case 8:
+ if (_editPos > _firstPos) {
+ _editBuffer[_editPos - 1] = _editBuffer[_editPos];
+ _editBuffer[_editPos--] = 0;
+ drawEditBuffer(slot);
+ }
+ break;
+ default:
+ tmp = _editBuffer[_editPos];
+ _editBuffer[_editPos] = 0;
+ textWidth = _fr2->getTextWidth(_editBuffer);
+ _editBuffer[_editPos] = tmp;
+
+ if (textWidth < 340 && _editPos < SAVE_DESCRIPTION_LEN - 2) {
+ _editBuffer[_editPos + 1] = _editBuffer[_editPos];
+ _editBuffer[_editPos + 2] = 0;
+ _editBuffer[_editPos++] = result;
+ drawEditBuffer(slot);
+ }
+ break;
+ }
+ }
+}
+
+void SaveRestoreDialog::paint() {
+ Dialog::paint();
+
+ _fr1->drawText((_mode == kRestoreDialog) ? TEXT_RESTORE : TEXT_SAVE, 165, 377);
+ _fr1->drawText(TEXT_CANCEL, 382, 377);
+}
+
+void SaveRestoreDialog::setResult(int result) {
+ if (result) {
+ if (_selectedSlot == -1)
+ return;
+
+ if (_mode == kSaveDialog) {
+ if (_editPos <= _firstPos)
+ return;
+ }
+ }
+
+ Dialog::setResult(result);
+}
+
+int SaveRestoreDialog::runModal() {
+ int result = Dialog::runModal();
+
+ if (result) {
+ switch (_mode) {
+ case kSaveDialog:
+ // Remove the cursor character from the savegame name
+ _editBuffer[_editPos] = 0;
+
+ if (_vm->saveGame(_selectedSlot, (byte *)&_editBuffer[_firstPos]) != SR_OK)
+ result = 0;
+ break;
+ case kRestoreDialog:
+ if (_vm->restoreGame(_selectedSlot) != SR_OK)
+ result = 0;
+ break;
+ }
+ }
+
+ return result;
+}
+
+} // End of namespace Sword2
diff --git a/engines/sword2/controls.h b/engines/sword2/controls.h
new file mode 100644
index 0000000000..719489b3ae
--- /dev/null
+++ b/engines/sword2/controls.h
@@ -0,0 +1,183 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#ifndef _CONTROL_S
+#define _CONTROL_S
+
+#include "sword2/defs.h"
+
+#define MAX_WIDGETS 25
+
+namespace Sword2 {
+
+class Sword2Engine;
+class FontRendererGui;
+class Widget;
+class Switch;
+class Slider;
+class Button;
+class ScrollButton;
+class Slot;
+
+enum {
+ kSaveDialog,
+ kRestoreDialog
+};
+
+/**
+ * Base class for all dialogs.
+ */
+
+class Dialog {
+private:
+ int _numWidgets;
+ Widget *_widgets[MAX_WIDGETS];
+ bool _finish;
+ int _result;
+
+public:
+ Sword2Engine *_vm;
+
+ Dialog(Sword2Engine *vm);
+ virtual ~Dialog();
+
+ void registerWidget(Widget *widget);
+
+ virtual void paint();
+ virtual void setResult(int result);
+
+ virtual int runModal();
+
+ virtual void onAction(Widget *widget, int result = 0) {}
+};
+
+class OptionsDialog : public Dialog {
+private:
+ FontRendererGui *_fr;
+ Widget *_panel;
+ Switch *_objectLabelsSwitch;
+ Switch *_subtitlesSwitch;
+ Switch *_reverseStereoSwitch;
+ Switch *_musicSwitch;
+ Switch *_speechSwitch;
+ Switch *_fxSwitch;
+ Slider *_musicSlider;
+ Slider *_speechSlider;
+ Slider *_fxSlider;
+ Slider *_gfxSlider;
+ Widget *_gfxPreview;
+ Button *_okButton;
+ Button *_cancelButton;
+
+ Audio::Mixer *_mixer;
+
+public:
+ OptionsDialog(Sword2Engine *vm);
+ ~OptionsDialog();
+
+ virtual void paint();
+ virtual void onAction(Widget *widget, int result = 0);
+};
+
+class SaveRestoreDialog : public Dialog {
+private:
+ int _mode, _selectedSlot;
+ byte _editBuffer[SAVE_DESCRIPTION_LEN];
+ int _editPos, _firstPos;
+ int _cursorTick;
+
+ FontRendererGui *_fr1;
+ FontRendererGui *_fr2;
+ Widget *_panel;
+ Slot *_slotButton[8];
+ ScrollButton *_zupButton;
+ ScrollButton *_upButton;
+ ScrollButton *_downButton;
+ ScrollButton *_zdownButton;
+ Button *_okButton;
+ Button *_cancelButton;
+
+public:
+ SaveRestoreDialog(Sword2Engine *vm, int mode);
+ ~SaveRestoreDialog();
+
+ void updateSlots();
+ void drawEditBuffer(Slot *slot);
+
+ virtual void onAction(Widget *widget, int result = 0);
+ virtual void paint();
+ virtual void setResult(int result);
+ virtual int runModal();
+};
+
+/**
+ * A "mini" dialog is usually a yes/no question, but also used for the
+ * restart/restore dialog at the beginning of the game.
+ */
+
+class MiniDialog : public Dialog {
+private:
+ uint32 _headerTextId;
+ uint32 _okTextId;
+ uint32 _cancelTextId;
+ FontRendererGui *_fr;
+ Widget *_panel;
+ Button *_okButton;
+ Button *_cancelButton;
+
+public:
+ MiniDialog(Sword2Engine *vm, uint32 headerTextId, uint32 okTextId = TEXT_OK, uint32 cancelTextId = TEXT_CANCEL);
+ virtual ~MiniDialog();
+ virtual void paint();
+ virtual void onAction(Widget *widget, int result = 0);
+};
+
+class StartDialog : public MiniDialog {
+public:
+ StartDialog(Sword2Engine *vm);
+ virtual int runModal();
+};
+
+class RestartDialog : public MiniDialog {
+public:
+ RestartDialog(Sword2Engine *vm);
+ virtual int runModal();
+};
+
+class QuitDialog : public MiniDialog {
+public:
+ QuitDialog(Sword2Engine *vm);
+ virtual int runModal();
+};
+
+class SaveDialog : public SaveRestoreDialog {
+public:
+ SaveDialog(Sword2Engine *vm) : SaveRestoreDialog(vm, kSaveDialog) {}
+};
+
+class RestoreDialog : public SaveRestoreDialog {
+public:
+ RestoreDialog(Sword2Engine *vm) : SaveRestoreDialog(vm, kRestoreDialog) {}
+};
+
+} // End of namespace Sword2
+
+#endif
diff --git a/engines/sword2/d_draw.cpp b/engines/sword2/d_draw.cpp
new file mode 100644
index 0000000000..7f278996fb
--- /dev/null
+++ b/engines/sword2/d_draw.cpp
@@ -0,0 +1,71 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "common/stdafx.h"
+#include "common/system.h"
+#include "sword2/sword2.h"
+#include "sword2/mouse.h"
+
+namespace Sword2 {
+
+/**
+ * @return the graphics detail setting
+ */
+
+int8 Screen::getRenderLevel() {
+ return _renderLevel;
+}
+
+void Screen::setRenderLevel(int8 level) {
+ _renderLevel = level;
+
+ switch (_renderLevel) {
+ case 0:
+ // Lowest setting: no fancy stuff
+ _renderCaps = 0;
+ break;
+ case 1:
+ // Medium-low setting: transparency-blending
+ _renderCaps = RDBLTFX_SPRITEBLEND;
+ break;
+ case 2:
+ // Medium-high setting: transparency-blending + shading
+ _renderCaps = RDBLTFX_SPRITEBLEND | RDBLTFX_SHADOWBLEND;
+ break;
+ case 3:
+ // Highest setting: transparency-blending + shading +
+ // edge-blending + improved stretching
+ _renderCaps = RDBLTFX_SPRITEBLEND | RDBLTFX_SHADOWBLEND | RDBLTFX_EDGEBLEND;
+ break;
+ }
+}
+
+/**
+ * Fill the screen buffer with palette colour zero. Note that it does not
+ * touch the menu areas of the screen.
+ */
+
+void Screen::clearScene() {
+ memset(_buffer + MENUDEEP * _screenWide, 0, _screenWide * RENDERDEEP);
+ _needFullRedraw = true;
+}
+
+} // End of namespace Sword2
diff --git a/engines/sword2/debug.cpp b/engines/sword2/debug.cpp
new file mode 100644
index 0000000000..53fc200241
--- /dev/null
+++ b/engines/sword2/debug.cpp
@@ -0,0 +1,377 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "common/stdafx.h"
+#include "sword2/sword2.h"
+#include "sword2/console.h"
+#include "sword2/defs.h"
+#include "sword2/logic.h"
+#include "sword2/maketext.h"
+#include "sword2/memory.h"
+#include "sword2/mouse.h"
+#include "sword2/resman.h"
+#include "sword2/router.h"
+
+namespace Sword2 {
+
+void Debugger::clearDebugTextBlocks() {
+ uint8 blockNo = 0;
+
+ while (blockNo < MAX_DEBUG_TEXTS && _debugTextBlocks[blockNo] > 0) {
+ // kill the system text block
+ _vm->_fontRenderer->killTextBloc(_debugTextBlocks[blockNo]);
+
+ // clear this element of our array of block numbers
+ _debugTextBlocks[blockNo] = 0;
+
+ blockNo++;
+ }
+}
+
+void Debugger::makeDebugTextBlock(char *text, int16 x, int16 y) {
+ uint8 blockNo = 0;
+
+ while (blockNo < MAX_DEBUG_TEXTS && _debugTextBlocks[blockNo] > 0)
+ blockNo++;
+
+ assert(blockNo < MAX_DEBUG_TEXTS);
+
+ _debugTextBlocks[blockNo] = _vm->_fontRenderer->buildNewBloc((byte *)text, x, y, 640 - x, 0, RDSPR_DISPLAYALIGN, CONSOLE_FONT_ID, NO_JUSTIFICATION);
+}
+
+void Debugger::buildDebugText() {
+ char buf[128];
+
+ int32 showVarNo; // for variable watching
+ int32 showVarPos;
+ int32 varNo;
+
+ ScreenInfo *screenInfo = _vm->_screen->getScreenInfo();
+
+ // clear the array of text block numbers for the debug text
+ clearDebugTextBlocks();
+
+ // mouse coords
+ // print mouse coords beside mouse-marker, if it's being displayed
+ if (_displayMouseMarker) {
+ int mouseX, mouseY;
+
+ _vm->_mouse->getPos(mouseX, mouseY);
+
+ sprintf(buf, "%d,%d", mouseX + screenInfo->scroll_offset_x, mouseY + screenInfo->scroll_offset_y);
+ if (mouseX > 560)
+ makeDebugTextBlock(buf, mouseX - 50, mouseY - 15);
+ else
+ makeDebugTextBlock(buf, mouseX + 5, mouseY - 15);
+ }
+
+ // mouse area coords
+
+ // defining a mouse area the easy way, by creating a box on-screen
+ if (_draggingRectangle || _vm->_logic->readVar(SYSTEM_TESTING_ANIMS)) {
+ // so we can see what's behind the lines
+ _rectFlicker = !_rectFlicker;
+
+ sprintf(buf, "x1=%d", _rectX1);
+ makeDebugTextBlock(buf, 0, 120);
+
+ sprintf(buf, "y1=%d", _rectY1);
+ makeDebugTextBlock(buf, 0, 135);
+
+ sprintf(buf, "x2=%d", _rectX2);
+ makeDebugTextBlock(buf, 0, 150);
+
+ sprintf(buf, "y2=%d", _rectY2);
+ makeDebugTextBlock(buf, 0, 165);
+ }
+
+ // testingSnR indicator
+
+ if (_testingSnR) { // see fnAddHuman()
+ sprintf(buf, "TESTING LOGIC STABILITY!");
+ makeDebugTextBlock(buf, 0, 105);
+ }
+
+#ifdef SWORD2_DEBUG
+ // speed-up indicator
+
+ if (_vm->_renderSkip) { // see sword2.cpp
+ sprintf(buf, "SKIPPING FRAMES FOR SPEED-UP!");
+ makeDebugTextBlock(buf, 0, 120);
+ }
+#endif
+
+ // debug info at top of screen - enabled/disabled as one complete unit
+
+ if (_displayTime) {
+ int32 time = _vm->getMillis();
+
+ if ((time - _startTime) / 1000 >= 10000)
+ _startTime = time;
+
+ time -= _startTime;
+ sprintf(buf, "Time %.2d:%.2d:%.2d.%.3d", (time / 3600000) % 60, (time / 60000) % 60, (time / 1000) % 60, time % 1000);
+ makeDebugTextBlock(buf, 500, 360);
+ sprintf(buf, "Game %d", _vm->_gameCycle);
+ makeDebugTextBlock(buf, 500, 380);
+ }
+
+ // current text number & speech-sample resource id
+
+ if (_displayTextNumbers) {
+ if (_textNumber) {
+ if (_vm->_logic->readVar(SYSTEM_TESTING_TEXT)) {
+ if (_vm->_logic->readVar(SYSTEM_WANT_PREVIOUS_LINE))
+ sprintf(buf, "backwards");
+ else
+ sprintf(buf, "forwards");
+
+ makeDebugTextBlock(buf, 0, 340);
+ }
+
+ sprintf(buf, "res: %d", _textNumber / SIZE);
+ makeDebugTextBlock(buf, 0, 355);
+
+ sprintf(buf, "pos: %d", _textNumber & 0xffff);
+ makeDebugTextBlock(buf, 0, 370);
+
+ sprintf(buf, "TEXT: %d", _vm->_logic->_officialTextNumber);
+ makeDebugTextBlock(buf, 0, 385);
+ }
+ }
+
+ // resource number currently being checking for animation
+
+ if (_vm->_logic->readVar(SYSTEM_TESTING_ANIMS)) {
+ sprintf(buf, "trying resource %d", _vm->_logic->readVar(SYSTEM_TESTING_ANIMS));
+ makeDebugTextBlock(buf, 0, 90);
+ }
+
+ // general debug info
+
+ if (_displayDebugText) {
+ byte name[NAME_LEN];
+
+/*
+ // CD in use
+ sprintf(buf, "CD-%d", currentCD);
+ makeDebugTextBlock(buf, 0, 0);
+*/
+
+ // mouse coords & object pointed to
+
+ if (_vm->_logic->readVar(CLICKED_ID))
+ sprintf(buf, "last click at %d,%d (id %d: %s)",
+ _vm->_logic->readVar(MOUSE_X),
+ _vm->_logic->readVar(MOUSE_Y),
+ _vm->_logic->readVar(CLICKED_ID),
+ _vm->_resman->fetchName(_vm->_logic->readVar(CLICKED_ID), name));
+ else
+ sprintf(buf, "last click at %d,%d (---)",
+ _vm->_logic->readVar(MOUSE_X),
+ _vm->_logic->readVar(MOUSE_Y));
+
+ makeDebugTextBlock(buf, 0, 15);
+
+ uint32 mouseTouching = _vm->_mouse->getMouseTouching();
+
+ int mouseX, mouseY;
+
+ _vm->_mouse->getPos(mouseX, mouseY);
+
+ if (mouseTouching)
+ sprintf(buf, "mouse %d,%d (id %d: %s)",
+ mouseX + screenInfo->scroll_offset_x,
+ mouseY + screenInfo->scroll_offset_y,
+ mouseTouching,
+ _vm->_resman->fetchName(mouseTouching, name));
+ else
+ sprintf(buf, "mouse %d,%d (not touching)",
+ mouseX + screenInfo->scroll_offset_x,
+ mouseY + screenInfo->scroll_offset_y);
+
+ makeDebugTextBlock(buf, 0, 30);
+
+ // player coords & graphic info
+ // if player objct has a graphic
+
+ if (_graphAnimRes)
+ sprintf(buf, "player %d,%d %s (%d) #%d/%d",
+ screenInfo->player_feet_x,
+ screenInfo->player_feet_y,
+ _vm->_resman->fetchName(_graphAnimRes, name),
+ _graphAnimRes,
+ _graphAnimPc,
+ _graphNoFrames);
+ else
+ sprintf(buf, "player %d,%d --- %d",
+ screenInfo->player_feet_x,
+ screenInfo->player_feet_y,
+ _graphAnimPc);
+
+ makeDebugTextBlock(buf, 0, 45);
+
+ // frames-per-second counter
+
+ sprintf(buf, "fps %d", _vm->_screen->getFps());
+ makeDebugTextBlock(buf, 440, 0);
+
+ // location number
+
+ sprintf(buf, "location=%d", _vm->_logic->readVar(LOCATION));
+ makeDebugTextBlock(buf, 440, 15);
+
+ // "result" variable
+
+ sprintf(buf, "result=%d", _vm->_logic->readVar(RESULT));
+ makeDebugTextBlock(buf, 440, 30);
+
+ // no. of events in event list
+
+ sprintf(buf, "events=%d", _vm->_logic->countEvents());
+ makeDebugTextBlock(buf, 440, 45);
+
+ // sprite list usage
+
+ sprintf(buf, "bgp0: %d/%d", _vm->_screen->getCurBgp0(), MAX_bgp0_sprites);
+ makeDebugTextBlock(buf, 560, 0);
+
+ sprintf(buf, "bgp1: %d/%d", _vm->_screen->getCurBgp1(), MAX_bgp1_sprites);
+ makeDebugTextBlock(buf, 560, 15);
+
+ sprintf(buf, "back: %d/%d", _vm->_screen->getCurBack(), MAX_back_sprites);
+ makeDebugTextBlock(buf, 560, 30);
+
+ sprintf(buf, "sort: %d/%d", _vm->_screen->getCurSort(), MAX_sort_sprites);
+ makeDebugTextBlock(buf, 560, 45);
+
+ sprintf(buf, "fore: %d/%d", _vm->_screen->getCurFore(), MAX_fore_sprites);
+ makeDebugTextBlock(buf, 560, 60);
+
+ sprintf(buf, "fgp0: %d/%d", _vm->_screen->getCurFgp0(), MAX_fgp0_sprites);
+ makeDebugTextBlock(buf, 560, 75);
+
+ sprintf(buf, "fgp1: %d/%d", _vm->_screen->getCurFgp1(), MAX_fgp1_sprites);
+ makeDebugTextBlock(buf, 560, 90);
+
+ // largest layer & sprite
+
+ // NB. Strings already constructed in Build_display.cpp
+ makeDebugTextBlock(_vm->_screen->getLargestLayerInfo(), 0, 60);
+ makeDebugTextBlock(_vm->_screen->getLargestSpriteInfo(), 0, 75);
+
+ // "waiting for person" indicator - set form fnTheyDo and
+ // fnTheyDoWeWait
+
+ if (_speechScriptWaiting) {
+ sprintf(buf, "script waiting for %s (%d)",
+ _vm->_resman->fetchName(_speechScriptWaiting, name),
+ _speechScriptWaiting);
+ makeDebugTextBlock(buf, 0, 90);
+ }
+
+ // variable watch display
+
+ showVarPos = 115; // y-coord for first showVar
+
+ for (showVarNo = 0; showVarNo < MAX_SHOWVARS; showVarNo++) {
+ varNo = _showVar[showVarNo]; // get variable number
+
+ // if non-zero ie. cannot watch 'id' but not needed
+ // anyway because it changes throughout the logic loop
+
+ if (varNo) {
+ sprintf(buf, "var(%d) = %d", varNo, _vm->_logic->readVar(varNo));
+ makeDebugTextBlock(buf, 530, showVarPos);
+ showVarPos += 15; // next line down
+ }
+ }
+
+ // memory indicator - this should come last, to show all the
+ // sprite blocks above!
+
+ uint32 totAlloc = _vm->_memory->getTotAlloc();
+ int16 numBlocks = _vm->_memory->getNumBlocks();
+
+ if (totAlloc < 1024)
+ sprintf(buf, "%u bytes in %d memory blocks", totAlloc, numBlocks);
+ else if (totAlloc < 1024 * 1024)
+ sprintf(buf, "%uK in %d memory blocks", totAlloc / 1024, numBlocks);
+ else
+ sprintf(buf, "%.02fM in %d memory blocks", totAlloc / 1048576., numBlocks);
+
+ makeDebugTextBlock(buf, 0, 0);
+ }
+}
+
+void Debugger::drawDebugGraphics() {
+ ScreenInfo *screenInfo = _vm->_screen->getScreenInfo();
+ // walk-grid
+
+ if (_displayWalkGrid)
+ _vm->_logic->_router->plotWalkGrid();
+
+ // player feet coord marker
+
+ if (_displayPlayerMarker)
+ plotCrossHair(screenInfo->player_feet_x, screenInfo->player_feet_y, 215);
+
+ // mouse marker & coords
+
+ if (_displayMouseMarker) {
+ int mouseX, mouseY;
+
+ _vm->_mouse->getPos(mouseX, mouseY);
+
+ plotCrossHair(mouseX + screenInfo->scroll_offset_x, mouseY + screenInfo->scroll_offset_y, 215);
+ }
+
+ // mouse area rectangle / sprite box rectangle when testing anims
+
+ if (_vm->_logic->readVar(SYSTEM_TESTING_ANIMS)) {
+ // draw box around current frame
+ drawRect(_rectX1, _rectY1, _rectX2, _rectY2, 184);
+ } else if (_draggingRectangle) {
+ // defining a mouse area the easy way, by creating a box
+ // on-screen
+ if (_rectFlicker)
+ drawRect(_rectX1, _rectY1, _rectX2, _rectY2, 184);
+ }
+}
+
+void Debugger::plotCrossHair(int16 x, int16 y, uint8 pen) {
+ _vm->_screen->plotPoint(x, y, pen);
+
+ _vm->_screen->drawLine(x - 2, y, x - 5, y, pen);
+ _vm->_screen->drawLine(x + 2, y, x + 5, y, pen);
+
+ _vm->_screen->drawLine(x, y - 2, x, y - 5, pen);
+ _vm->_screen->drawLine(x, y + 2, x, y + 5, pen);
+}
+
+void Debugger::drawRect(int16 x1, int16 y1, int16 x2, int16 y2, uint8 pen) {
+ _vm->_screen->drawLine(x1, y1, x2, y1, pen); // top edge
+ _vm->_screen->drawLine(x1, y2, x2, y2, pen); // bottom edge
+ _vm->_screen->drawLine(x1, y1, x1, y2, pen); // left edge
+ _vm->_screen->drawLine(x2, y1, x2, y2, pen); // right edge
+}
+
+} // End of namespace Sword2
diff --git a/engines/sword2/debug.h b/engines/sword2/debug.h
new file mode 100644
index 0000000000..9b0b40f9ec
--- /dev/null
+++ b/engines/sword2/debug.h
@@ -0,0 +1,33 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#ifndef D_DEBUG
+#define D_DEBUG
+
+// FIXME: I don't know how large this constant used to be
+#define MAX_DEBUG_TEXTS 55
+
+#define MAX_SHOWVARS 15
+
+namespace Sword2 {
+} // End of namespace Sword2
+
+#endif
diff --git a/engines/sword2/defs.h b/engines/sword2/defs.h
new file mode 100644
index 0000000000..4acb484f34
--- /dev/null
+++ b/engines/sword2/defs.h
@@ -0,0 +1,205 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#ifndef DEFS
+#define DEFS
+
+#define SIZE 0x10000 // 65536 items per section
+#define NuSIZE 0xffff // & with this
+
+// Return codes
+
+enum {
+ // Generic error codes
+ RD_OK = 0x00000000,
+ RDERR_UNKNOWN = 0x00000001,
+ RDERR_OUTOFMEMORY = 0x00000003,
+ RDERR_INVALIDFILENAME = 0x00000004,
+
+ // Drawing error codes
+ RDERR_DECOMPRESSION = 0x00010007,
+
+ // Sprite drawing error codes
+ RDERR_NOTIMPLEMENTED = 0x00060001,
+ RDERR_NOTCLOSED = 0x00050005,
+ RDERR_NOTOPEN = 0x00050006,
+
+ // Menubar error codes
+ RDERR_INVALIDMENU = 0x00060000,
+ RDERR_INVALIDPOCKET = 0x00060001,
+ RDERR_INVALIDCOMMAND = 0x00060002,
+
+ // Palette fading error codes
+ RDERR_FADEINCOMPLETE = 0x00070000,
+
+ // Sound engine error codes
+ RDERR_SPEECHPLAYING = 0x00080004,
+ RDERR_SPEECHNOTPLAYING = 0x00080005,
+ RDERR_INVALIDWAV = 0x00080006,
+ RDERR_FXALREADYOPEN = 0x00080009,
+ RDERR_FXNOTOPEN = 0x0008000B,
+ RDERR_INVALIDID = 0x0008000D
+};
+
+// Text ids for the control panel etc.
+
+enum {
+ TEXT_OK = 0x08EB0000,
+ TEXT_CANCEL = 0x08EB0001,
+ TEXT_RESTORE = 0x08EB0002,
+ TEXT_SAVE = 0x08EB0003,
+ TEXT_QUIT = 0x08EB0004,
+ TEXT_RESTART = 0x08EB0005,
+ TEXT_OPTIONS = 0x08EB000A,
+ TEXT_SUBTITLES = 0x08EB000B,
+ TEXT_OBJECT_LABELS = 0x08EB000C,
+ TEXT_MUSIC_VOLUME = 0x08EB000E,
+ TEXT_SPEECH_VOLUME = 0x08EB000F,
+ TEXT_FX_VOLUME = 0x08EB0010,
+ TEXT_GFX_QUALITY = 0x08EB0011,
+ TEXT_REVERSE_STEREO = 0x08EB0015,
+ TEXT_RESTORE_CANT_OPEN = 0x0CBA017E,
+ TEXT_RESTORE_INCOMPATIBLE = 0x0CBA017F,
+ TEXT_RESTORE_FAILED = 0x0CBA0181,
+ TEXT_SAVE_CANT_OPEN = 0x0CBA0182,
+ TEXT_SAVE_FAILED = 0x0CBA0184
+};
+
+// Always 8 (George object used for Nico player character as well)
+#define CUR_PLAYER_ID 8
+
+// Global variable references
+
+enum {
+ ID = 0,
+ RESULT = 1,
+ PLAYER_ACTION = 2,
+ // CUR_PLAYER_ID = 3,
+ PLAYER_ID = 305,
+ TALK_FLAG = 13,
+
+ MOUSE_X = 4,
+ MOUSE_Y = 5,
+ LEFT_BUTTON = 109,
+ RIGHT_BUTTON = 110,
+ CLICKED_ID = 178,
+
+ IN_SUBJECT = 6,
+ COMBINE_BASE = 7,
+ OBJECT_HELD = 14,
+
+ SPEECH_ID = 9,
+ INS1 = 10,
+ INS2 = 11,
+ INS3 = 12,
+ INS4 = 60,
+ INS5 = 61,
+ INS_COMMAND = 59,
+
+ PLAYER_FEET_X = 141,
+ PLAYER_FEET_Y = 142,
+ PLAYER_CUR_DIR = 937,
+
+ // for debug.cpp
+ LOCATION = 62,
+
+ // so scripts can force scroll offsets
+ SCROLL_X = 345,
+ SCROLL_Y = 346,
+
+ EXIT_CLICK_ID = 710,
+ EXIT_FADING = 713,
+
+ SYSTEM_TESTING_ANIMS = 912,
+ SYSTEM_TESTING_TEXT = 1230,
+ SYSTEM_WANT_PREVIOUS_LINE = 1245,
+
+ // 1=on 0=off (set in fnAddHuman and fnNoHuman)
+ MOUSE_AVAILABLE = 686,
+
+ // used in fnChoose
+ AUTO_SELECTED = 1115,
+
+ // see fnStartConversation and fnChooser
+ CHOOSER_COUNT_FLAG = 15,
+
+ // signifies a demo mode
+ DEMO = 1153,
+
+ // Indicates to script whether this is the Playstation version.
+ // PSXFLAG = 1173,
+
+ // for the poor PSX so it knows what language is running.
+ // GAME_LANGUAGE = 111,
+
+ // 1 = dead
+ DEAD = 1256,
+
+ // If set indicates that the speech anim is to run through only once.
+ SPEECHANIMFLAG = 1278,
+
+ // for the engine
+ SCROLL_OFFSET_X = 1314
+};
+
+// Resource IDs
+
+enum {
+ // mouse mointers - It's pretty much safe to do it like this
+ NORMAL_MOUSE_ID = 17,
+ SCROLL_LEFT_MOUSE_ID = 1440,
+ SCROLL_RIGHT_MOUSE_ID = 1441,
+
+ // Console Font - does not use game text - only English required
+ CONSOLE_FONT_ID = 340,
+
+ // Speech Font
+ ENGLISH_SPEECH_FONT_ID = 341,
+ FINNISH_SPEECH_FONT_ID = 956,
+ POLISH_SPEECH_FONT_ID = 955,
+
+ // Control Panel Font (and un-selected savegame descriptions)
+ ENGLISH_CONTROLS_FONT_ID = 2005,
+ FINNISH_CONTROLS_FONT_ID = 959,
+ POLISH_CONTROLS_FONT_ID = 3686,
+
+ // Red Font (for selected savegame descriptions)
+ // BS2 doesn't draw selected savegames in red, so I guess this is a
+ // left-over from BS1
+ ENGLISH_RED_FONT_ID = 2005, // 1998 // Redfont
+ FINNISH_RED_FONT_ID = 959, // 960 // FinRedFn
+ POLISH_RED_FONT_ID = 3686, // 3688 // PolRedFn
+
+ // Control panel palette resource id
+ CONTROL_PANEL_PALETTE = 261,
+
+ // res id's of the system menu icons
+ OPTIONS_ICON = 344,
+ QUIT_ICON = 335,
+ SAVE_ICON = 366,
+ RESTORE_ICON = 364,
+ RESTART_ICON = 342,
+
+ // conversation exit icon, 'EXIT' menu icon (used in fnChoose)
+ EXIT_ICON = 65
+};
+
+#endif
diff --git a/engines/sword2/events.cpp b/engines/sword2/events.cpp
new file mode 100644
index 0000000000..e8090414aa
--- /dev/null
+++ b/engines/sword2/events.cpp
@@ -0,0 +1,99 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "common/stdafx.h"
+#include "sword2/sword2.h"
+#include "sword2/defs.h"
+#include "sword2/logic.h"
+
+namespace Sword2 {
+
+void Logic::sendEvent(uint32 id, uint32 interact_id) {
+ for (int i = 0; i < ARRAYSIZE(_eventList); i++) {
+ if (_eventList[i].id == id || !_eventList[i].id) {
+ _eventList[i].id = id;
+ _eventList[i].interact_id = interact_id;
+ return;
+ }
+ }
+
+ error("sendEvent() ran out of event slots");
+}
+
+void Logic::setPlayerActionEvent(uint32 id, uint32 interact_id) {
+ // Full script id of action script number 2
+ sendEvent(id, (interact_id << 16) | 2);
+}
+
+int Logic::checkEventWaiting() {
+ for (int i = 0; i < ARRAYSIZE(_eventList); i++) {
+ if (_eventList[i].id == readVar(ID))
+ return 1;
+ }
+
+ return 0;
+}
+
+void Logic::startEvent() {
+ // call this from stuff like fnWalk
+ // you must follow with a return IR_TERMINATE
+
+ for (int i = 0; i < ARRAYSIZE(_eventList); i++) {
+ if (_eventList[i].id == readVar(ID)) {
+ logicOne(_eventList[i].interact_id);
+ _eventList[i].id = 0;
+ return;
+ }
+ }
+
+ error("startEvent() can't find event for id %d", readVar(ID));
+}
+
+void Logic::clearEvent(uint32 id) {
+ for (int i = 0; i < ARRAYSIZE(_eventList); i++) {
+ if (_eventList[i].id == id) {
+ _eventList[i].id = 0;
+ return;
+ }
+ }
+}
+
+void Logic::killAllIdsEvents(uint32 id) {
+ for (int i = 0; i < ARRAYSIZE(_eventList); i++) {
+ if (_eventList[i].id == id)
+ _eventList[i].id = 0;
+ }
+}
+
+// For the debugger
+
+uint32 Logic::countEvents() {
+ uint32 count = 0;
+
+ for (int i = 0; i < ARRAYSIZE(_eventList); i++) {
+ if (_eventList[i].id)
+ count++;
+ }
+
+ return count;
+}
+
+} // End of namespace Sword2
diff --git a/engines/sword2/function.cpp b/engines/sword2/function.cpp
new file mode 100644
index 0000000000..e749e7b497
--- /dev/null
+++ b/engines/sword2/function.cpp
@@ -0,0 +1,2479 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "common/stdafx.h"
+#include "common/system.h"
+#include "common/file.h"
+
+#include "sword2/sword2.h"
+#include "sword2/defs.h"
+#include "sword2/build_display.h"
+#include "sword2/console.h"
+#include "sword2/interpreter.h"
+#include "sword2/logic.h"
+#include "sword2/maketext.h"
+#include "sword2/mouse.h"
+#include "sword2/resman.h"
+#include "sword2/router.h"
+#include "sword2/sound.h"
+#include "sword2/animation.h"
+
+namespace Sword2 {
+
+int32 Logic::fnTestFunction(int32 *params) {
+ // params: 0 address of a flag
+ return IR_CONT;
+}
+
+int32 Logic::fnTestFlags(int32 *params) {
+ // params: 0 value of flag
+ return IR_CONT;
+}
+
+int32 Logic::fnRegisterStartPoint(int32 *params) {
+ // params: 0 id of startup script to call - key
+ // 1 pointer to ascii message
+
+ int32 key = params[0];
+ char *name = (char *)decodePtr(params[1]);
+
+ _vm->registerStartPoint(key, name);
+ return IR_CONT;
+}
+
+int32 Logic::fnInitBackground(int32 *params) {
+ // this screen defines the size of the back buffer
+
+ // params: 0 res id of normal background layer - cannot be 0
+ // 1 1 yes 0 no for a new palette
+
+ _vm->_screen->initBackground(params[0], params[1]);
+ return IR_CONT;
+}
+
+/**
+ * This function is used by start scripts.
+ */
+
+int32 Logic::fnSetSession(int32 *params) {
+ // params: 0 id of new run list
+
+ expressChangeSession(params[0]);
+ return IR_CONT;
+}
+
+int32 Logic::fnBackSprite(int32 *params) {
+ // params: 0 pointer to object's graphic structure
+ _router->setSpriteStatus(decodePtr(params[0]), BACK_SPRITE);
+ return IR_CONT;
+}
+
+int32 Logic::fnSortSprite(int32 *params) {
+ // params: 0 pointer to object's graphic structure
+ _router->setSpriteStatus(decodePtr(params[0]), SORT_SPRITE);
+ return IR_CONT;
+}
+
+int32 Logic::fnForeSprite(int32 *params) {
+ // params: 0 pointer to object's graphic structure
+ _router->setSpriteStatus(decodePtr(params[0]), FORE_SPRITE);
+ return IR_CONT;
+}
+
+int32 Logic::fnRegisterMouse(int32 *params) {
+ // this call would be made from an objects service script 0
+ // the object would be one with no graphic but with a mouse - i.e. a
+ // floor or one whose mouse area is manually defined rather than
+ // intended to fit sprite shape
+
+ // params: 0 pointer to ObjectMouse or 0 for no write to mouse
+ // list
+
+ _vm->_mouse->registerMouse(decodePtr(params[0]), NULL);
+ return IR_CONT;
+}
+
+int32 Logic::fnAnim(int32 *params) {
+ // params: 0 pointer to object's logic structure
+ // 1 pointer to object's graphic structure
+ // 2 resource id of animation file
+
+ // Normal forward animation
+ return _router->doAnimate(
+ decodePtr(params[0]),
+ decodePtr(params[1]),
+ params[2], false);
+}
+
+int32 Logic::fnRandom(int32 *params) {
+ // params: 0 min
+ // 1 max
+
+ writeVar(RESULT, _vm->_rnd.getRandomNumberRng(params[0], params[1]));
+ return IR_CONT;
+}
+
+int32 Logic::fnPreLoad(int32 *params) {
+ // Forces a resource into memory before it's "officially" opened for
+ // use. eg. if an anim needs to run on smoothly from another,
+ // "preloading" gets it into memory in advance to avoid the cacheing
+ // delay that normally occurs before the first frame.
+
+ // params: 0 resource to preload
+
+ _vm->_resman->openResource(params[0]);
+ _vm->_resman->closeResource(params[0]);
+ return IR_CONT;
+}
+
+int32 Logic::fnAddSubject(int32 *params) {
+ // params: 0 id
+ // 1 daves reference number
+ _vm->_mouse->addSubject(params[0], params[1]);
+ return IR_CONT;
+}
+
+int32 Logic::fnInteract(int32 *params) {
+ // Run targets action on a subroutine. Called by player on his base
+ // level 0 idle, for example.
+
+ // params: 0 id of target from which we derive action script
+ // reference
+
+ writeVar(PLAYER_ACTION, 0); // must clear this
+ logicUp((params[0] << 16) | 2); // 3rd script of clicked on id
+
+ // Out, up and around again - pc is saved for current level to be
+ // returned to.
+ return IR_GOSUB;
+}
+
+int32 Logic::fnChoose(int32 *params) {
+ // params: none
+
+ // This opcode is used to open the conversation menu. The human is
+ // switched off so there will be no normal mouse engine.
+
+ // The player's choice is piggy-backed on the standard opcode return
+ // values, to be used with the CP_JUMP_ON_RETURNED opcode. As far as I
+ // can tell, this is the only function that uses that feature.
+
+ uint32 response = _vm->_mouse->chooseMouse();
+
+ if (response == (uint32)-1)
+ return IR_REPEAT;
+
+ return IR_CONT | (response << 3);
+}
+
+/**
+ * Walk mega to (x,y,dir). Set RESULT to 0 if it succeeded. Otherwise, set
+ * RESULT to 1.
+ */
+
+int32 Logic::fnWalk(int32 *params) {
+ // params: 0 pointer to object's logic structure
+ // 1 pointer to object's graphic structure
+ // 2 pointer to object's mega structure
+ // 3 pointer to object's walkdata structure
+ // 4 target x-coord
+ // 5 target y-coord
+ // 6 target direction (8 means end walk on ANY direction)
+
+ return _router->doWalk(
+ decodePtr(params[0]),
+ decodePtr(params[1]),
+ decodePtr(params[2]),
+ decodePtr(params[3]),
+ params[4], params[5], params[6]);
+}
+
+/**
+ * Walk mega to start position of anim
+ */
+
+int32 Logic::fnWalkToAnim(int32 *params) {
+ // params: 0 pointer to object's logic structure
+ // 1 pointer to object's graphic structure
+ // 2 pointer to object's mega structure
+ // 3 pointer to object's walkdata structure
+ // 4 anim resource id
+
+ return _router->walkToAnim(
+ decodePtr(params[0]),
+ decodePtr(params[1]),
+ decodePtr(params[2]),
+ decodePtr(params[3]),
+ params[4]);
+}
+
+/**
+ * Turn mega to the specified direction.
+ */
+
+int32 Logic::fnTurn(int32 *params) {
+ // params: 0 pointer to object's logic structure
+ // 1 pointer to object's graphic structure
+ // 2 pointer to object's mega structure
+ // 3 pointer to object's walkdata structure
+ // 4 target direction
+
+ return _router->doFace(
+ decodePtr(params[0]),
+ decodePtr(params[1]),
+ decodePtr(params[2]),
+ decodePtr(params[3]),
+ params[4]);
+}
+
+/**
+ * Stand mega at (x,y,dir)
+ * Sets up the graphic object, but also needs to set the new 'current_dir' in
+ * the mega object, so the router knows in future
+ */
+
+int32 Logic::fnStandAt(int32 *params) {
+ // params: 0 pointer to object's graphic structure
+ // 1 pointer to object's mega structure
+ // 2 target x-coord
+ // 3 target y-coord
+ // 4 target direction
+
+ _router->standAt(
+ decodePtr(params[0]),
+ decodePtr(params[1]),
+ params[2], params[3], params[4]);
+ return IR_CONT;
+}
+
+/**
+ * Stand mega into the specified direction at current feet coords.
+ * Just needs to call standAt() with current feet coords.
+ */
+
+int32 Logic::fnStand(int32 *params) {
+ // params: 0 pointer to object's graphic structure
+ // 1 pointer to object's mega structure
+ // 2 target direction
+ byte *ob_mega = decodePtr(params[1]);
+
+ ObjectMega obMega(ob_mega);
+
+ _router->standAt(
+ decodePtr(params[0]),
+ ob_mega, obMega.getFeetX(), obMega.getFeetY(), params[2]);
+ return IR_CONT;
+}
+
+/**
+ * stand mega at end position of anim
+ */
+
+int32 Logic::fnStandAfterAnim(int32 *params) {
+ // params: 0 pointer to object's graphic structure
+ // 1 pointer to object's mega structure
+ // 2 anim resource id
+
+ _router->standAfterAnim(
+ decodePtr(params[0]),
+ decodePtr(params[1]),
+ params[2]);
+ return IR_CONT;
+}
+
+int32 Logic::fnPause(int32 *params) {
+ // params: 0 pointer to object's logic structure
+ // 1 number of game-cycles to pause
+
+ // NB. Pause-value of 0 causes script to continue, 1 causes a 1-cycle
+ // quit, 2 gives 2 cycles, etc.
+
+ ObjectLogic obLogic(decodePtr(params[0]));
+
+ if (obLogic.getLooping() == 0) {
+ obLogic.setLooping(1);
+ obLogic.setPause(params[1]);
+ }
+
+ if (obLogic.getPause()) {
+ obLogic.setPause(obLogic.getPause() - 1);
+ return IR_REPEAT;
+ }
+
+ obLogic.setLooping(0);
+ return IR_CONT;
+}
+
+int32 Logic::fnMegaTableAnim(int32 *params) {
+ // params: 0 pointer to object's logic structure
+ // 1 pointer to object's graphic structure
+ // 2 pointer to object's mega structure
+ // 3 pointer to animation table
+
+ // Normal forward anim
+ return _router->megaTableAnimate(
+ decodePtr(params[0]),
+ decodePtr(params[1]),
+ decodePtr(params[2]),
+ decodePtr(params[3]),
+ false);
+}
+
+int32 Logic::fnAddMenuObject(int32 *params) {
+ // params: 0 pointer to a MenuObject structure to copy down
+
+ _vm->_mouse->addMenuObject(decodePtr(params[0]));
+ return IR_CONT;
+}
+
+/**
+ * Start a conversation.
+ *
+ * Note that fnStartConversation() might accidentally be called every time the
+ * script loops back for another chooser, but we only want to reset the chooser
+ * count flag the first time this function is called, i.e. when the talk flag
+ * is zero.
+ */
+
+int32 Logic::fnStartConversation(int32 *params) {
+ // params: none
+
+ _vm->_mouse->startConversation();
+ return IR_CONT;
+}
+
+/**
+ * End a conversation.
+ */
+
+int32 Logic::fnEndConversation(int32 *params) {
+ // params: none
+
+ _vm->_mouse->endConversation();
+ return IR_CONT;
+}
+
+int32 Logic::fnSetFrame(int32 *params) {
+ // params: 0 pointer to object's graphic structure
+ // 1 resource id of animation file
+ // 2 frame flag (0=first 1=last)
+
+ int32 res = params[1];
+ assert(res);
+
+ // open the resource (& check it's valid)
+ byte *anim_file = _vm->_resman->openResource(res);
+
+ assert(_vm->_resman->fetchType(res) == ANIMATION_FILE);
+
+ // set up pointer to the animation header
+ AnimHeader anim_head;
+
+ anim_head.read(_vm->fetchAnimHeader(anim_file));
+
+ // set up anim resource in graphic object
+ ObjectGraphic obGraph(decodePtr(params[0]));
+
+ obGraph.setAnimResource(res);
+ obGraph.setAnimPc(params[2] ? anim_head.noAnimFrames - 1 : 0);
+
+ // Close the anim file and drop out of script
+ _vm->_resman->closeResource(obGraph.getAnimResource());
+ return IR_CONT;
+}
+
+int32 Logic::fnRandomPause(int32 *params) {
+ // params: 0 pointer to object's logic structure
+ // 1 minimum number of game-cycles to pause
+ // 2 maximum number of game-cycles to pause
+
+ ObjectLogic obLogic(decodePtr(params[0]));
+ int32 pars[2];
+
+ if (obLogic.getLooping() == 0) {
+ pars[0] = params[1];
+ pars[1] = params[2];
+ fnRandom(pars);
+ pars[1] = readVar(RESULT);
+ }
+
+ pars[0] = params[0];
+ return fnPause(pars);
+}
+
+int32 Logic::fnRegisterFrame(int32 *params) {
+ // this call would be made from an objects service script 0
+
+ // params: 0 pointer to mouse structure or NULL for no write to
+ // mouse list (non-zero means write sprite-shape to
+ // mouse list)
+ // 1 pointer to graphic structure
+ // 2 pointer to mega structure or NULL if not a mega
+
+ _vm->_screen->registerFrame(
+ decodePtr(params[0]),
+ decodePtr(params[1]),
+ decodePtr(params[2]));
+ return IR_CONT;
+}
+
+int32 Logic::fnNoSprite(int32 *params) {
+ // params: 0 pointer to object's graphic structure
+ _router->setSpriteStatus(decodePtr(params[0]), NO_SPRITE);
+ return IR_CONT;
+}
+
+int32 Logic::fnSendSync(int32 *params) {
+ // params: 0 sync's recipient
+ // 1 sync value
+
+ sendSync(params[0], params[1]);
+ return IR_CONT;
+}
+
+int32 Logic::fnUpdatePlayerStats(int32 *params) {
+ // engine needs to know certain info about the player
+
+ // params: 0 pointer to mega structure
+
+ ObjectMega obMega(decodePtr(params[0]));
+
+ ScreenInfo *screenInfo = _vm->_screen->getScreenInfo();
+
+ screenInfo->player_feet_x = obMega.getFeetX();
+ screenInfo->player_feet_y = obMega.getFeetY();
+
+ // for the script
+ writeVar(PLAYER_FEET_X, obMega.getFeetX());
+ writeVar(PLAYER_FEET_Y, obMega.getFeetY());
+ writeVar(PLAYER_CUR_DIR, obMega.getCurDir());
+ writeVar(SCROLL_OFFSET_X, screenInfo->scroll_offset_x);
+
+ debug(5, "fnUpdatePlayerStats: %d %d",
+ obMega.getFeetX(), obMega.getFeetY());
+
+ return IR_CONT;
+}
+
+int32 Logic::fnPassGraph(int32 *params) {
+ // makes an engine local copy of passed ObjectGraphic - run script 4
+ // of an object to request this used by fnTurnTo(id) etc
+ //
+ // remember, we cannot simply read a compact any longer but instead
+ // must request it from the object itself
+
+ // params: 0 pointer to an ObjectGraphic structure
+
+ warning("fnPassGraph() is a no-op now");
+ return IR_CONT;
+}
+
+int32 Logic::fnInitFloorMouse(int32 *params) {
+ // params: 0 pointer to object's mouse structure
+
+ byte *ob_mouse = decodePtr(params[0]);
+ ScreenInfo *screenInfo = _vm->_screen->getScreenInfo();
+
+ // floor is always lowest priority
+
+ ObjectMouse mouse;
+
+ mouse.x1 = 0;
+ mouse.y1 = 0;
+ mouse.x2 = screenInfo->screen_wide - 1;
+ mouse.y2 = screenInfo->screen_deep - 1;
+ mouse.priority = 9;
+ mouse.pointer = NORMAL_MOUSE_ID;
+
+ mouse.write(ob_mouse);
+ return IR_CONT;
+}
+
+int32 Logic::fnPassMega(int32 *params) {
+ // makes an engine local copy of passed mega_structure - run script 4
+ // of an object to request this used by fnTurnTo(id) etc
+ //
+ // remember, we cannot simply read a compact any longer but instead
+ // must request it from the object itself
+
+ // params: 0 pointer to a mega structure
+
+ memcpy(_engineMega, decodePtr(params[0]), ObjectMega::size());
+ return IR_CONT;
+}
+
+/**
+ * Turn mega to face point (x,y) on the floor
+ */
+
+int32 Logic::fnFaceXY(int32 *params) {
+ // params: 0 pointer to object's logic structure
+ // 1 pointer to object's graphic structure
+ // 2 pointer to object's mega structure
+ // 3 pointer to object's walkdata structure
+ // 4 target x-coord
+ // 5 target y-coord
+
+ return _router->faceXY(
+ decodePtr(params[0]),
+ decodePtr(params[1]),
+ decodePtr(params[2]),
+ decodePtr(params[3]),
+ params[4], params[5]);
+}
+
+/**
+ * Causes no more objects in this logic loop to be processed. The logic engine
+ * will restart at the beginning of the new list. The current screen will not
+ * be drawn!
+ */
+
+int32 Logic::fnEndSession(int32 *params) {
+ // params: 0 id of new run-list
+
+ // terminate current and change to next run-list
+ expressChangeSession(params[0]);
+
+ // stop the script - logic engine will now go around and the new
+ // screen will begin
+ return IR_STOP;
+}
+
+int32 Logic::fnNoHuman(int32 *params) {
+ // params: none
+
+ _vm->_mouse->noHuman();
+ return IR_CONT;
+}
+
+int32 Logic::fnAddHuman(int32 *params) {
+ // params: none
+
+ _vm->_mouse->addHuman();
+ return IR_CONT;
+}
+
+/**
+ * Wait for a target to become waiting, i.e. not busy.
+ */
+
+int32 Logic::fnWeWait(int32 *params) {
+ // params: 0 target
+
+ assert(_vm->_resman->fetchType(params[0]) == GAME_OBJECT);
+
+ // Run the target's get-speech-state script
+ runResScript(params[0], 5);
+
+ if (readVar(RESULT) == 0) {
+ // The target is busy. Try again.
+ _vm->_debugger->_speechScriptWaiting = params[0];
+ return IR_REPEAT;
+ }
+
+ // The target is waiting, i.e. not busy.
+
+ _vm->_debugger->_speechScriptWaiting = 0;
+ return IR_CONT;
+}
+
+/**
+ * Wait for a target to become waiting, i.e. not busy, send a command to it,
+ * then wait for it to finish.
+ */
+
+int32 Logic::fnTheyDoWeWait(int32 *params) {
+ // params: 0 pointer to ob_logic
+ // 1 target
+ // 2 command
+ // 3 ins1
+ // 4 ins2
+ // 5 ins3
+ // 6 ins4
+ // 7 ins5
+
+ assert(_vm->_resman->fetchType(params[1]) == GAME_OBJECT);
+
+ // Run the target's get-speech-state script
+ runResScript(params[1], 5);
+
+ ObjectLogic obLogic(decodePtr(params[0]));
+
+ if (readVar(RESULT) == 1 && readVar(INS_COMMAND) == 0 && obLogic.getLooping() == 0) {
+ // The target is waiting, i.e. not busy, and there is no other
+ // command queued. We haven't sent the command yet, so do it.
+
+ debug(5, "fnTheyDoWeWait: sending command to %d", params[1]);
+
+ _vm->_debugger->_speechScriptWaiting = params[1];
+ obLogic.setLooping(1);
+
+ writeVar(SPEECH_ID, params[1]);
+ writeVar(INS_COMMAND, params[2]);
+ writeVar(INS1, params[3]);
+ writeVar(INS2, params[4]);
+ writeVar(INS3, params[5]);
+ writeVar(INS4, params[6]);
+ writeVar(INS5, params[7]);
+
+ return IR_REPEAT;
+ }
+
+ if (obLogic.getLooping() == 0) {
+ // The command has not been sent yet. Keep waiting.
+ _vm->_debugger->_speechScriptWaiting = params[1];
+ return IR_REPEAT;
+ }
+
+ if (readVar(RESULT) == 0) {
+ // The command has been sent, and the target is busy doing it.
+ // Wait for it to finish.
+
+ debug(5, "fnTheyDoWeWait: Waiting for %d to finish", params[1]);
+
+ _vm->_debugger->_speechScriptWaiting = params[1];
+ return IR_REPEAT;
+ }
+
+ debug(5, "fnTheyDoWeWait: %d finished", params[1]);
+
+ obLogic.setLooping(0);
+ _vm->_debugger->_speechScriptWaiting = 0;
+ return IR_CONT;
+}
+
+/**
+ * Wait for a target to become waiting, i.e. not busy, then send a command to
+ * it.
+ */
+
+int32 Logic::fnTheyDo(int32 *params) {
+ // params: 0 target
+ // 1 command
+ // 2 ins1
+ // 3 ins2
+ // 4 ins3
+ // 5 ins4
+ // 6 ins5
+
+ assert(_vm->_resman->fetchType(params[0]) == GAME_OBJECT);
+
+ // Run the target's get-speech-state script
+ runResScript(params[0], 5);
+
+ if (readVar(RESULT) == 1 && !readVar(INS_COMMAND)) {
+ // The target is waiting, i.e. not busy, and there is no other
+ // command queued. Send the command.
+
+ debug(5, "fnTheyDo: sending command to %d", params[0]);
+
+ _vm->_debugger->_speechScriptWaiting = 0;
+
+ writeVar(SPEECH_ID, params[0]);
+ writeVar(INS_COMMAND, params[1]);
+ writeVar(INS1, params[2]);
+ writeVar(INS2, params[3]);
+ writeVar(INS3, params[4]);
+ writeVar(INS4, params[5]);
+ writeVar(INS5, params[6]);
+
+ return IR_CONT;
+ }
+
+ // The target is busy. Come back again next cycle.
+
+ _vm->_debugger->_speechScriptWaiting = params[0];
+ return IR_REPEAT;
+}
+
+/**
+ * Route to the left or right hand side of target id, if possible.
+ */
+
+int32 Logic::fnWalkToTalkToMega(int32 *params) {
+ // params: 0 pointer to object's logic structure
+ // 1 pointer to object's graphic structure
+ // 2 pointer to object's mega structure
+ // 3 pointer to object's walkdata structure
+ // 4 id of target mega to face
+ // 5 separation
+
+ return _router->walkToTalkToMega(
+ decodePtr(params[0]),
+ decodePtr(params[1]),
+ decodePtr(params[2]),
+ decodePtr(params[3]),
+ params[4], params[5]);
+}
+
+int32 Logic::fnFadeDown(int32 *params) {
+ // NONE means up! can only be called when screen is fully faded up -
+ // multiple calls wont have strange effects
+
+ // params: none
+
+ if (_vm->_screen->getFadeStatus() == RDFADE_NONE)
+ _vm->_screen->fadeDown();
+
+ return IR_CONT;
+}
+
+enum {
+ S_OB_GRAPHIC = 0,
+ S_OB_SPEECH = 1,
+ S_OB_LOGIC = 2,
+ S_OB_MEGA = 3,
+
+ S_TEXT = 4,
+ S_WAV = 5,
+ S_ANIM = 6,
+ S_DIR_TABLE = 7,
+ S_ANIM_MODE = 8
+};
+
+/**
+ * It's the super versatile fnSpeak. Text and wavs can be selected in any
+ * combination.
+ *
+ * @note We can assume no human - there should be no human, at least!
+ */
+
+int32 Logic::fnISpeak(int32 *params) {
+ // params: 0 pointer to ob_graphic
+ // 1 pointer to ob_speech
+ // 2 pointer to ob_logic
+ // 3 pointer to ob_mega
+ // 4 encoded text number
+ // 5 wav res id
+ // 6 anim res id
+ // 7 anim table res id
+ // 8 animation mode 0 lip synced,
+ // 1 just straight animation
+
+ static bool cycle_skip = false;
+ static bool speechRunning;
+
+ // Set up the pointers which we know we'll always need
+
+ ObjectLogic obLogic(decodePtr(params[S_OB_LOGIC]));
+ ObjectGraphic obGraph(decodePtr(params[S_OB_GRAPHIC]));
+
+ // FIRST TIME ONLY: create the text, load the wav, set up the anim,
+ // etc.
+
+ if (obLogic.getLooping() == 0) {
+ // New fudge to wait for smacker samples to finish
+ // since they can over-run into the game
+
+ if (_vm->_sound->getSpeechStatus() != RDSE_SAMPLEFINISHED)
+ return IR_REPEAT;
+
+ // New fudge for 'fx' subtitles: If subtitles switched off, and
+ // we don't want to use a wav for this line either, then just
+ // quit back to script right now!
+
+ if (!_vm->getSubtitles() && !wantSpeechForLine(params[S_WAV]))
+ return IR_CONT;
+
+ // Drop out for 1st cycle to allow walks/anims to end and
+ // display last frame before system locks while speech loaded
+
+ if (!cycle_skip) {
+ cycle_skip = true;
+ return IR_REPEAT;
+ }
+
+ cycle_skip = false;
+
+ _vm->_debugger->_textNumber = params[S_TEXT];
+
+ // Pull out the text line to get the official text number
+ // (for wav id). Once the wav id's go into all script text
+ // commands, we'll only need this for debugging.
+
+ uint32 text_res = params[S_TEXT] / SIZE;
+ uint32 local_text = params[S_TEXT] & 0xffff;
+
+ // For testing all text & speech!
+ //
+ // A script loop can send any text number to fnISpeak and it
+ // will only run the valid ones or return with 'result' equal
+ // to '1' or '2' to mean 'invalid text resource' and 'text
+ // number out of range' respectively
+ //
+ // See 'testing_routines' object in George's Player Character
+ // section of linc
+
+ if (readVar(SYSTEM_TESTING_TEXT)) {
+ if (!_vm->_resman->checkValid(text_res)) {
+ // Not a valid resource number - invalid (null
+ // resource)
+ writeVar(RESULT, 1);
+ return IR_CONT;
+ }
+
+ if (_vm->_resman->fetchType(text_res) != TEXT_FILE) {
+ // Invalid - not a text resource
+ _vm->_resman->closeResource(text_res);
+ writeVar(RESULT, 1);
+ return IR_CONT;
+ }
+
+ if (!_vm->checkTextLine(_vm->_resman->openResource(text_res), local_text)) {
+ // Line number out of range
+ _vm->_resman->closeResource(text_res);
+ writeVar(RESULT, 2);
+ return IR_CONT;
+ }
+
+ _vm->_resman->closeResource(text_res);
+ writeVar(RESULT, 0);
+ }
+
+ byte *text = _vm->fetchTextLine(_vm->_resman->openResource(text_res), local_text);
+ _officialTextNumber = READ_LE_UINT16(text);
+ _vm->_resman->closeResource(text_res);
+
+ // Prevent dud lines from appearing while testing text & speech
+ // since these will not occur in the game anyway
+
+ if (readVar(SYSTEM_TESTING_TEXT)) {
+ // If actor number is 0 and text line is just a 'dash'
+ // character
+ if (_officialTextNumber == 0 && text[2] == '-' && text[3] == 0) {
+ writeVar(RESULT, 3);
+ return IR_CONT;
+ }
+ }
+
+ // Set the 'looping_flag' and the text-click-delays. We can
+ // left-click past the text after half a second, and
+ // right-click past it after a quarter of a second.
+
+ obLogic.setLooping(1);
+ _leftClickDelay = 6;
+ _rightClickDelay = 3;
+
+ if (readVar(PLAYER_ID) != CUR_PLAYER_ID)
+ debug(5, "(%d) Nico: %s", _officialTextNumber, text + 2);
+ else {
+ byte buf[NAME_LEN];
+
+ debug(5, "(%d) %s: %s", _officialTextNumber, _vm->_resman->fetchName(readVar(ID), buf), text + 2);
+ }
+
+ // Set up the speech animation
+
+ if (params[S_ANIM]) {
+ // Just a straight anim.
+ _animId = params[6];
+ } else if (params[S_DIR_TABLE]) {
+ // Use this direction table to derive the anim
+ // NB. ASSUMES WE HAVE A MEGA OBJECT!!
+
+ ObjectMega obMega(decodePtr(params[S_OB_MEGA]));
+ byte *anim_table = decodePtr(params[S_DIR_TABLE]);
+
+ _animId = READ_LE_UINT32(anim_table + 4 * obMega.getCurDir());
+ } else {
+ // No animation choosen
+ _animId = 0;
+ }
+
+ if (_animId) {
+ // Set the talker's graphic to the first frame of this
+ // speech anim for now.
+
+ _speechAnimType = readVar(SPEECHANIMFLAG);
+ obGraph.setAnimResource(_animId);
+ obGraph.setAnimPc(0);
+ }
+
+ // Default back to looped lip synced anims.
+ writeVar(SPEECHANIMFLAG, 0);
+
+ // Set up _textX and _textY for speech panning and/or text
+ // sprite position.
+
+ locateTalker(params);
+
+ // Is it to be speech or subtitles or both?
+
+ // Assume not running until know otherwise
+ speechRunning = false;
+
+ // New fudge for 'fx' subtitles: If speech is selected, and
+ // this line is allowed speech (not if it's an fx subtitle!)
+
+ if (!_vm->_sound->isSpeechMute() && wantSpeechForLine(_officialTextNumber)) {
+ // If the wavId parameter is zero because not yet
+ // compiled into speech command, we can still get it
+ // from the 1st 2 chars of the text line.
+
+ if (!params[S_WAV])
+ params[S_WAV] = (int32)_officialTextNumber;
+
+ // Panning goes from -16 (left) to 16 (right)
+ int8 speech_pan = ((_textX - 320) * 16) / 320;
+
+ if (speech_pan < -16)
+ speech_pan = -16;
+ else if (speech_pan > 16)
+ speech_pan = 16;
+
+ uint32 rv = _vm->_sound->playCompSpeech(params[S_WAV], 16, speech_pan);
+
+ if (rv == RD_OK) {
+ // Ok, we've got something to play. Set it
+ // playing now. (We might want to do this the
+ // next cycle, don't know yet.)
+
+ speechRunning = true;
+ _vm->_sound->unpauseSpeech();
+ } else {
+ debug(5, "ERROR: PlayCompSpeech(wav=%d (res=%d pos=%d)) returned %.8x", params[S_WAV], text_res, local_text, rv);
+ }
+ }
+
+ if (_vm->getSubtitles() || !speechRunning) {
+ // We want subtitles, or the speech failed to load.
+ // Either way, we're going to show the text so create
+ // the text sprite.
+
+ formText(params);
+ }
+ }
+
+ // EVERY TIME: run a cycle of animation, if there is one
+
+ if (_animId) {
+ // There is an animation - Increment the anim frame number.
+ obGraph.setAnimPc(obGraph.getAnimPc() + 1);
+
+ byte *anim_file = _vm->_resman->openResource(obGraph.getAnimResource());
+ AnimHeader anim_head;
+
+ anim_head.read(_vm->fetchAnimHeader(anim_file));
+
+ if (!_speechAnimType) {
+ // ANIM IS TO BE LIP-SYNC'ED & REPEATING
+
+ if (obGraph.getAnimPc() == (int32)anim_head.noAnimFrames) {
+ // End of animation - restart from frame 0
+ obGraph.setAnimPc(0);
+ } else if (speechRunning && _vm->_sound->amISpeaking() == RDSE_QUIET) {
+ // The speech is running, but we're at a quiet
+ // bit. Restart from frame 0 (closed mouth).
+ obGraph.setAnimPc(0);
+ }
+ } else {
+ // ANIM IS TO PLAY ONCE ONLY
+ if (obGraph.getAnimPc() == (int32)anim_head.noAnimFrames - 1) {
+ // Reached the last frame of the anim. Hold
+ // anim on this last frame
+ _animId = 0;
+ }
+ }
+
+ _vm->_resman->closeResource(obGraph.getAnimResource());
+ } else if (_speechAnimType) {
+ // Placed here so we actually display the last frame of the
+ // anim.
+ _speechAnimType = 0;
+ }
+
+ // EVERY TIME: FIND OUT IF WE NEED TO STOP THE SPEECH NOW...
+
+ // If there is a wav then we're using that to end the speech naturally
+
+ bool speechFinished = false;
+
+ // If playing a sample
+
+ if (speechRunning) {
+ // Has it finished?
+ if (_vm->_sound->getSpeechStatus() == RDSE_SAMPLEFINISHED)
+ speechFinished = true;
+ } else if (!speechRunning && _speechTime) {
+ // Counting down text time because there is no sample - this
+ // ends the speech
+
+ // if no sample then we're using _speechTime to end speech
+ // naturally
+
+ _speechTime--;
+ if (!_speechTime)
+ speechFinished = true;
+ }
+
+ // Ok, all is running along smoothly - but a click means stop
+ // unnaturally
+
+ int mouseX, mouseY;
+
+ _vm->_mouse->getPos(mouseX, mouseY);
+
+ // So that we can go to the options panel while text & speech is
+ // being tested
+ if (readVar(SYSTEM_TESTING_TEXT) == 0 || mouseY > 0) {
+ MouseEvent *me = _vm->mouseEvent();
+
+ // Note that we now have TWO click-delays - one for LEFT
+ // button, one for RIGHT BUTTON
+
+ if ((!_leftClickDelay && me && (me->buttons & RD_LEFTBUTTONDOWN)) ||
+ (!_rightClickDelay && me && (me->buttons & RD_RIGHTBUTTONDOWN))) {
+ // Mouse click, after click_delay has expired -> end
+ // the speech.
+
+ // if testing text & speech
+ if (readVar(SYSTEM_TESTING_TEXT)) {
+ // and RB used to click past text
+ if (me->buttons & RD_RIGHTBUTTONDOWN) {
+ // then we want the previous line again
+ writeVar(SYSTEM_WANT_PREVIOUS_LINE, 1);
+ } else {
+ // LB just want next line again
+ writeVar(SYSTEM_WANT_PREVIOUS_LINE, 0);
+ }
+ }
+
+ speechFinished = true;
+
+ // if speech sample playing, halt it prematurely
+ if (speechRunning)
+ _vm->_sound->stopSpeech();
+ }
+ }
+
+ // If we are finishing the speech this cycle, do the business
+
+ // !speechAnimType, as we want an anim which is playing once to have
+ // finished.
+
+ if (speechFinished && !_speechAnimType) {
+ // If there is text, kill it
+ if (_speechTextBlocNo) {
+ _vm->_fontRenderer->killTextBloc(_speechTextBlocNo);
+ _speechTextBlocNo = 0;
+ }
+
+ // if there is a speech anim, end it on closed mouth frame
+ if (_animId) {
+ _animId = 0;
+ obGraph.setAnimPc(0);
+ }
+
+ speechRunning = false;
+
+ // no longer in a script function loop
+ obLogic.setLooping(0);
+
+ _vm->_debugger->_textNumber = 0;
+
+ // reset to zero, in case text line not even extracted (since
+ // this number comes from the text line)
+ _officialTextNumber = 0;
+
+ writeVar(RESULT, 0);
+ return IR_CONT;
+ }
+
+ // Speech still going, so decrement the click_delay if it's still
+ // active
+
+ if (_leftClickDelay)
+ _leftClickDelay--;
+
+ if (_rightClickDelay)
+ _rightClickDelay--;
+
+ return IR_REPEAT;
+}
+
+/**
+ * Reset the object and restart script 1 on level 0
+ */
+
+int32 Logic::fnTotalRestart(int32 *params) {
+ // mega runs this to restart its base logic again - like being cached
+ // in again
+
+ // params: none
+
+ _curObjectHub.setLogicLevel(0);
+ _curObjectHub.setScriptPc(0, 1);
+
+ return IR_TERMINATE;
+}
+
+int32 Logic::fnSetWalkGrid(int32 *params) {
+ // params: none
+
+ warning("fnSetWalkGrid() is no longer a valid opcode");
+ return IR_CONT;
+}
+
+/**
+ * Receive and sequence the commands sent from the conversation script. We have
+ * to do this in a slightly tweeky manner as we can no longer have generic
+ * scripts.
+ */
+
+enum {
+ INS_talk = 1,
+ INS_anim = 2,
+ INS_reverse_anim = 3,
+ INS_walk = 4,
+ INS_turn = 5,
+ INS_face = 6,
+ INS_trace = 7,
+ INS_no_sprite = 8,
+ INS_sort = 9,
+ INS_foreground = 10,
+ INS_background = 11,
+ INS_table_anim = 12,
+ INS_reverse_table_anim = 13,
+ INS_walk_to_anim = 14,
+ INS_set_frame = 15,
+ INS_stand_after_anim = 16,
+ INS_quit = 42
+};
+
+int32 Logic::fnSpeechProcess(int32 *params) {
+ // params: 0 pointer to ob_graphic
+ // 1 pointer to ob_speech
+ // 2 pointer to ob_logic
+ // 3 pointer to ob_mega
+ // 4 pointer to ob_walkdata
+
+ ObjectSpeech obSpeech(decodePtr(params[1]));
+
+ while (1) {
+ int32 pars[9];
+
+ // Check which command we're waiting for, and call the
+ // appropriate function. Once we're done, clear the command
+ // and set wait_state to 1.
+ //
+ // Note: we could save a var and ditch wait_state and check
+ // 'command' for non zero means busy
+ //
+ // Note: I can't see that we ever check the value of wait_state
+ // but perhaps it accesses that memory location directly?
+
+ switch (obSpeech.getCommand()) {
+ case 0:
+ break;
+ case INS_talk:
+ pars[0] = params[0]; // ob_graphic
+ pars[1] = params[1]; // ob_speech
+ pars[2] = params[2]; // ob_logic
+ pars[3] = params[3]; // ob_mega
+ pars[4] = obSpeech.getIns1(); // encoded text number
+ pars[5] = obSpeech.getIns2(); // wav res id
+ pars[6] = obSpeech.getIns3(); // anim res id
+ pars[7] = obSpeech.getIns4(); // anim table res id
+ pars[8] = obSpeech.getIns5(); // animation mode - 0 lip synced, 1 just straight animation
+
+ if (fnISpeak(pars) != IR_REPEAT) {
+ obSpeech.setCommand(0);
+ obSpeech.setWaitState(1);
+ }
+
+ return IR_REPEAT;
+ case INS_turn:
+ pars[0] = params[2]; // ob_logic
+ pars[1] = params[0]; // ob_graphic
+ pars[2] = params[3]; // ob_mega
+ pars[3] = params[4]; // ob_walkdata
+ pars[4] = obSpeech.getIns1(); // direction to turn to
+
+ if (fnTurn(pars) != IR_REPEAT) {
+ obSpeech.setCommand(0);
+ obSpeech.setWaitState(1);
+ }
+
+ return IR_REPEAT;
+ case INS_face:
+ pars[0] = params[2]; // ob_logic
+ pars[1] = params[0]; // ob_graphic
+ pars[2] = params[3]; // ob_mega
+ pars[3] = params[4]; // ob_walkdata
+ pars[4] = obSpeech.getIns1(); // target
+
+ if (fnFaceMega(pars) != IR_REPEAT) {
+ obSpeech.setCommand(0);
+ obSpeech.setWaitState(1);
+ }
+
+ return IR_REPEAT;
+ case INS_anim:
+ pars[0] = params[2]; // ob_logic
+ pars[1] = params[0]; // ob_graphic
+ pars[2] = obSpeech.getIns1(); // anim res
+
+ if (fnAnim(pars) != IR_REPEAT) {
+ obSpeech.setCommand(0);
+ obSpeech.setWaitState(1);
+ }
+
+ return IR_REPEAT;
+ case INS_reverse_anim:
+ pars[0] = params[2]; // ob_logic
+ pars[1] = params[0]; // ob_graphic
+ pars[2] = obSpeech.getIns1(); // anim res
+
+ if (fnReverseAnim(pars) != IR_REPEAT) {
+ obSpeech.setCommand(0);
+ obSpeech.setWaitState(1);
+ }
+
+ return IR_REPEAT;
+ case INS_table_anim:
+ pars[0] = params[2]; // ob_logic
+ pars[1] = params[0]; // ob_graphic
+ pars[2] = params[3]; // ob_mega
+ pars[3] = obSpeech.getIns1(); // pointer to anim table
+
+ if (fnMegaTableAnim(pars) != IR_REPEAT) {
+ obSpeech.setCommand(0);
+ obSpeech.setWaitState(1);
+ }
+
+ return IR_REPEAT;
+ case INS_reverse_table_anim:
+ pars[0] = params[2]; // ob_logic
+ pars[1] = params[0]; // ob_graphic
+ pars[2] = params[3]; // ob_mega
+ pars[3] = obSpeech.getIns1(); // pointer to anim table
+
+ if (fnReverseMegaTableAnim(pars) != IR_REPEAT) {
+ obSpeech.setCommand(0);
+ obSpeech.setWaitState(1);
+ }
+
+ return IR_REPEAT;
+ case INS_no_sprite:
+ fnNoSprite(params); // ob_graphic
+
+ obSpeech.setCommand(0);
+ obSpeech.setWaitState(1);
+ return IR_REPEAT ;
+ case INS_sort:
+ fnSortSprite(params); // ob_graphic
+
+ obSpeech.setCommand(0);
+ obSpeech.setWaitState(1);
+ return IR_REPEAT;
+ case INS_foreground:
+ fnForeSprite(params); // ob_graphic
+
+ obSpeech.setCommand(0);
+ obSpeech.setWaitState(1);
+ return IR_REPEAT;
+ case INS_background:
+ fnBackSprite(params); // ob_graphic
+
+ obSpeech.setCommand(0);
+ obSpeech.setWaitState(1);
+ return IR_REPEAT;
+ case INS_walk:
+ pars[0] = params[2]; // ob_logic
+ pars[1] = params[0]; // ob_graphic
+ pars[2] = params[3]; // ob_mega
+ pars[3] = params[4]; // ob_walkdata
+ pars[4] = obSpeech.getIns1(); // target x
+ pars[5] = obSpeech.getIns2(); // target y
+ pars[6] = obSpeech.getIns3(); // target direction
+
+ if (fnWalk(pars) != IR_REPEAT) {
+ obSpeech.setCommand(0);
+ obSpeech.setWaitState(1);
+ }
+
+ return IR_REPEAT;
+ case INS_walk_to_anim:
+ pars[0] = params[2]; // ob_logic
+ pars[1] = params[0]; // ob_graphic
+ pars[2] = params[3]; // ob_mega
+ pars[3] = params[4]; // ob_walkdata
+ pars[4] = obSpeech.getIns1(); // anim resource
+
+ if (fnWalkToAnim(pars) != IR_REPEAT) {
+ obSpeech.setCommand(0);
+ obSpeech.setWaitState(1);
+ }
+
+ return IR_REPEAT;
+ case INS_stand_after_anim:
+ pars[0] = params[0]; // ob_graphic
+ pars[1] = params[3]; // ob_mega
+ pars[2] = obSpeech.getIns1(); // anim resource
+
+ fnStandAfterAnim(pars);
+
+ obSpeech.setCommand(0);
+ obSpeech.setWaitState(1);
+ return IR_REPEAT;
+ case INS_set_frame:
+ pars[0] = params[0]; // ob_graphic
+ pars[1] = obSpeech.getIns1(); // anim_resource
+ pars[2] = obSpeech.getIns2(); // FIRST_FRAME or LAST_FRAME
+ fnSetFrame(pars);
+
+ obSpeech.setCommand(0);
+ obSpeech.setWaitState(1);
+ return IR_REPEAT;
+ case INS_quit:
+ // That's it - we're finished with this
+ obSpeech.setCommand(0);
+ // obSpeech.setWaitState(0);
+ return IR_CONT;
+ default:
+ // Unimplemented command - just cancel
+ obSpeech.setCommand(0);
+ obSpeech.setWaitState(1);
+ break;
+ }
+
+ if (readVar(SPEECH_ID) == readVar(ID)) {
+ // There's a new command for us! Grab the command -
+ // potentially we only have this cycle to do this - and
+ // set things up so that the command will be picked up
+ // on the next iteration of the while loop.
+
+ debug(5, "fnSpeechProcess: Received new command %d", readVar(INS_COMMAND));
+
+ writeVar(SPEECH_ID, 0);
+
+ obSpeech.setCommand(readVar(INS_COMMAND));
+ obSpeech.setIns1(readVar(INS1));
+ obSpeech.setIns2(readVar(INS2));
+ obSpeech.setIns3(readVar(INS3));
+ obSpeech.setIns4(readVar(INS4));
+ obSpeech.setIns5(readVar(INS5));
+ obSpeech.setWaitState(0);
+
+ writeVar(INS_COMMAND, 0);
+ } else {
+ // No new command. We could run a blink anim (or
+ // something) here.
+
+ obSpeech.setWaitState(1);
+ return IR_REPEAT;
+ }
+ }
+}
+
+int32 Logic::fnSetScaling(int32 *params) {
+ // params: 0 pointer to object's mega structure
+ // 1 scale constant A
+ // 2 scale constant B
+
+ // 256 * s = A * y + B
+
+ // Where s is system scale, which itself is (256 * actual_scale) ie.
+ // s == 128 is half size
+
+ ObjectMega obMega(decodePtr(params[0]));
+
+ obMega.setScaleA(params[1]);
+ obMega.setScaleB(params[2]);
+
+ return IR_CONT;
+}
+
+int32 Logic::fnStartEvent(int32 *params) {
+ // params: none
+
+ startEvent();
+ return IR_TERMINATE;
+}
+
+int32 Logic::fnCheckEventWaiting(int32 *params) {
+ // params: none
+
+ writeVar(RESULT, checkEventWaiting());
+ return IR_CONT;
+}
+
+int32 Logic::fnRequestSpeech(int32 *params) {
+ // change current script - must be followed by a TERMINATE script
+ // directive
+
+ // params: 0 id of target to catch the event and startup speech
+ // servicing
+
+ // Full script id to interact with - megas run their own 7th script
+ sendEvent(params[0], (params[0] << 16) | 6);
+ return IR_CONT;
+}
+
+int32 Logic::fnGosub(int32 *params) {
+ // params: 0 id of script
+
+ // Hurray, script subroutines. Logic goes up - pc is saved for current
+ // level.
+ logicUp(params[0]);
+ return IR_GOSUB;
+}
+
+/**
+ * Wait for a target to become waiting, i.e. not busy, or until we time out.
+ * This is useful when clicking on a target to talk to it, and it doesn't
+ * reply. This way, we won't lock up.
+ *
+ * If the target becomes waiting, RESULT is set to 0. If we time out, RESULT is
+ * set to 1.
+ */
+
+int32 Logic::fnTimedWait(int32 *params) {
+ // params: 0 ob_logic
+ // 1 target
+ // 2 number of cycles before give up
+
+ assert(_vm->_resman->fetchType(params[1]) == GAME_OBJECT);
+
+ ObjectLogic obLogic(decodePtr(params[0]));
+
+ if (obLogic.getLooping() == 0) {
+ // This is the first time, so set up the time-out.
+ obLogic.setLooping(params[2]);
+ }
+
+ // Run the target's get-speech-state script
+ runResScript(params[1], 5);
+
+ if (readVar(RESULT) == 1) {
+ // The target is waiting, i.e. not busy
+
+ _vm->_debugger->_speechScriptWaiting = 0;
+
+ obLogic.setLooping(0);
+ writeVar(RESULT, 0);
+ return IR_CONT;
+ }
+
+ obLogic.setLooping(obLogic.getLooping() - 1);
+
+ if (obLogic.getLooping() == 0) {
+ // Time's up.
+
+ debug(5, "fnTimedWait: Timed out waiting for %d", params[1]);
+ _vm->_debugger->_speechScriptWaiting = 0;
+
+ // Clear the event that hasn't been picked up - in theory,
+ // none of this should ever happen.
+
+ killAllIdsEvents(params[1]);
+ writeVar(RESULT, 1);
+ return IR_CONT;
+ }
+
+ // Target is busy. Keep trying.
+
+ _vm->_debugger->_speechScriptWaiting = params[1];
+ return IR_REPEAT;
+}
+
+int32 Logic::fnPlayFx(int32 *params) {
+ // params: 0 sample resource id
+ // 1 type (FX_SPOT, FX_RANDOM, FX_LOOP)
+ // 2 delay (0..65535)
+ // 3 volume (0..16)
+ // 4 pan (-16..16)
+
+ // example script:
+ // fnPlayFx (FXWATER, FX_LOOP, 0, 10, 15);
+ // // fx_water is just a local script flag
+ // fx_water = result;
+ // .
+ // .
+ // .
+ // fnStopFx (fx_water);
+
+ int32 res = params[0];
+ int32 type = params[1];
+ int32 delay = params[2];
+ int32 volume = params[3];
+ int32 pan = params[4];
+
+ _vm->_sound->queueFx(res, type, delay, volume, pan);
+ return IR_CONT;
+}
+
+int32 Logic::fnStopFx(int32 *params) {
+ // params: 0 position in queue
+ if (_vm->_sound->stopFx(params[0]) != RD_OK)
+ debug(5, "SFX ERROR: Trying to stop an inactive sound slot");
+
+ return IR_CONT;
+}
+
+/**
+ * Start a tune playing, to play once or to loop until stopped or next one
+ * played.
+ */
+
+int32 Logic::fnPlayMusic(int32 *params) {
+ // params: 0 tune id
+ // 1 loop flag (0 or 1)
+
+ char filename[128];
+ bool loopFlag;
+ uint32 rv;
+
+ loopFlag = (params[1] == FX_LOOP);
+
+ rv = _vm->_sound->streamCompMusic(params[0], loopFlag);
+
+ if (rv)
+ debug(5, "ERROR: streamCompMusic(%s, %d, %d) returned error 0x%.8x", filename, params[0], loopFlag, rv);
+
+ return IR_CONT;
+}
+
+int32 Logic::fnStopMusic(int32 *params) {
+ // params: none
+
+ _vm->_sound->stopMusic(false);
+ return IR_CONT;
+}
+
+int32 Logic::fnSetValue(int32 *params) {
+ // temp. function!
+
+ // used for setting far-referenced megaset resource field in mega
+ // object, from start script
+
+ // params: 0 pointer to object's mega structure
+ // 1 value to set it to
+
+ ObjectMega obMega(decodePtr(params[0]));
+
+ obMega.setMegasetRes(params[1]);
+ return IR_CONT;
+}
+
+int32 Logic::fnNewScript(int32 *params) {
+ // change current script - must be followed by a TERMINATE script
+ // directive
+
+ // params: 0 id of script
+
+ writeVar(PLAYER_ACTION, 0); // must clear this
+ logicReplace(params[0]);
+ return IR_TERMINATE;
+}
+
+/**
+ * Like getSync(), but called from scripts. Sets the RESULT variable to
+ * the sync value, or 0 if none is found.
+ */
+
+int32 Logic::fnGetSync(int32 *params) {
+ // params: none
+
+ int slot = getSync();
+
+ writeVar(RESULT, (slot != -1) ? _syncList[slot].sync : 0);
+ return IR_CONT;
+}
+
+/**
+ * Wait for sync to happen. Sets the RESULT variable to the sync value, once
+ * it has been found.
+ */
+
+int32 Logic::fnWaitSync(int32 *params) {
+ // params: none
+
+ debug(6, "fnWaitSync: %d waits", readVar(ID));
+
+ int slot = getSync();
+
+ if (slot == -1)
+ return IR_REPEAT;
+
+ debug(5, "fnWaitSync: %d got sync %d", readVar(ID), _syncList[slot].sync);
+ writeVar(RESULT, _syncList[slot].sync);
+ return IR_CONT;
+}
+
+int32 Logic::fnRegisterWalkGrid(int32 *params) {
+ // params: none
+
+ warning("fnRegisterWalkGrid() is no longer a valid opcode");
+ return IR_CONT;
+}
+
+int32 Logic::fnReverseMegaTableAnim(int32 *params) {
+ // params: 0 pointer to object's logic structure
+ // 1 pointer to object's graphic structure
+ // 2 pointer to object's mega structure
+ // 3 pointer to animation table
+
+ // Reverse anim
+ return _router->megaTableAnimate(
+ decodePtr(params[0]),
+ decodePtr(params[1]),
+ decodePtr(params[2]),
+ decodePtr(params[3]),
+ true);
+}
+
+int32 Logic::fnReverseAnim(int32 *params) {
+ // params: 0 pointer to object's logic structure
+ // 1 pointer to object's graphic structure
+ // 2 resource id of animation file
+
+ // Reverse anim
+ return _router->doAnimate(
+ decodePtr(params[0]),
+ decodePtr(params[1]),
+ params[2], true);
+}
+
+/**
+ * Mark this object for killing - to be killed when player leaves this screen.
+ * Object reloads and script restarts upon re-entry to screen, which causes
+ * this object's startup logic to be re-run every time we enter the screen.
+ * "Which is nice."
+ *
+ * @note Call ONCE from object's logic script, i.e. in startup code, so not
+ * re-called every time script frops off and restarts!
+ */
+
+int32 Logic::fnAddToKillList(int32 *params) {
+ // params: none
+ uint32 id = readVar(ID);
+
+ // DON'T EVER KILL GEORGE!
+ if (id == CUR_PLAYER_ID)
+ return IR_CONT;
+
+ // Scan the list to see if it's already included
+
+ for (uint32 i = 0; i < _kills; i++) {
+ if (_objectKillList[i] == id)
+ return IR_CONT;
+ }
+
+ assert(_kills < OBJECT_KILL_LIST_SIZE); // no room at the inn
+
+ _objectKillList[_kills++] = id;
+
+ // "another one bites the dust"
+
+ // When we leave the screen, all these object resources are to be
+ // cleaned out of memory and the kill list emptied by doing
+ // '_kills = 0', ensuring that all resources are in fact still in
+ // memory and, more importantly, closed before killing!
+
+ return IR_CONT;
+}
+
+/**
+ * Set the standby walk coords to be used by fnWalkToAnim() and
+ * fnStandAfterAnim() when the anim header's start/end coords are zero.
+ * Useful during development; can stay in final game anyway.
+ */
+
+int32 Logic::fnSetStandbyCoords(int32 *params) {
+ // params: 0 x-coord
+ // 1 y-coord
+ // 2 direction (0..7)
+
+ _router->setStandbyCoords(params[0], params[1], params[2]);
+ return IR_CONT;
+}
+
+int32 Logic::fnBackPar0Sprite(int32 *params) {
+ // params: 0 pointer to object's graphic structure
+ _router->setSpriteStatus(decodePtr(params[0]), BGP0_SPRITE);
+ return IR_CONT;
+}
+
+int32 Logic::fnBackPar1Sprite(int32 *params) {
+ // params: 0 pointer to object's graphic structure
+ _router->setSpriteStatus(decodePtr(params[0]), BGP1_SPRITE);
+ return IR_CONT;
+}
+
+int32 Logic::fnForePar0Sprite(int32 *params) {
+ // params: 0 pointer to object's graphic structure
+ _router->setSpriteStatus(decodePtr(params[0]), FGP0_SPRITE);
+ return IR_CONT;
+}
+
+int32 Logic::fnForePar1Sprite(int32 *params) {
+ // params: 0 pointer to object's graphic structure
+ _router->setSpriteStatus(decodePtr(params[0]), FGP1_SPRITE);
+ return IR_CONT;
+}
+
+int32 Logic::fnSetPlayerActionEvent(int32 *params) {
+ // we want to intercept the player character and have him interact
+ // with an object - from script this code is the same as the mouse
+ // engine calls when you click on an object - here, a third party
+ // does the clicking IYSWIM
+
+ // note - this routine used CUR_PLAYER_ID as the target
+
+ // params: 0 id to interact with
+
+ setPlayerActionEvent(CUR_PLAYER_ID, params[0]);
+ return IR_CONT;
+}
+
+/**
+ * Set the special scroll offset variables
+ *
+ * Call when starting screens and to change the camera within screens
+ *
+ * call AFTER fnInitBackground() to override the defaults
+ */
+
+int32 Logic::fnSetScrollCoordinate(int32 *params) {
+ // params: 0 feet_x value
+ // 1 feet_y value
+
+ // Called feet_x and feet_y to retain intellectual compatibility with
+ // Sword1!
+ //
+ // feet_x & feet_y refer to the physical screen coords where the
+ // system will try to maintain George's feet
+
+ ScreenInfo *screenInfo = _vm->_screen->getScreenInfo();
+
+ screenInfo->feet_x = params[0];
+ screenInfo->feet_y = params[1];
+ return IR_CONT;
+}
+
+/**
+ * Stand mega at start position of anim
+ */
+
+int32 Logic::fnStandAtAnim(int32 *params) {
+ // params: 0 pointer to object's graphic structure
+ // 1 pointer to object's mega structure
+ // 2 anim resource id
+
+ _router->standAtAnim(
+ decodePtr(params[0]),
+ decodePtr(params[1]),
+ params[2]);
+ return IR_CONT;
+}
+
+#define SCROLL_MOUSE_WIDTH 20
+
+int32 Logic::fnSetScrollLeftMouse(int32 *params) {
+ // params: 0 pointer to object's mouse structure
+
+ byte *ob_mouse = decodePtr(params[0]);
+ ScreenInfo *screenInfo = _vm->_screen->getScreenInfo();
+
+ // Highest priority
+
+ ObjectMouse mouse;
+
+ mouse.x1 = 0;
+ mouse.y1 = 0;
+ mouse.x2 = screenInfo->scroll_offset_x + SCROLL_MOUSE_WIDTH;
+ mouse.y2 = screenInfo->screen_deep - 1;
+ mouse.priority = 0;
+
+ if (screenInfo->scroll_offset_x > 0) {
+ // not fully scrolled to the left
+ mouse.pointer = SCROLL_LEFT_MOUSE_ID;
+ } else {
+ // so the mouse area doesn't get registered
+ mouse.pointer = 0;
+ }
+
+ mouse.write(ob_mouse);
+ return IR_CONT;
+}
+
+int32 Logic::fnSetScrollRightMouse(int32 *params) {
+ // params: 0 pointer to object's mouse structure
+
+ byte *ob_mouse = decodePtr(params[0]);
+ ScreenInfo *screenInfo = _vm->_screen->getScreenInfo();
+
+ // Highest priority
+
+ ObjectMouse mouse;
+
+ mouse.x1 = screenInfo->scroll_offset_x + _vm->_screen->getScreenWide() - SCROLL_MOUSE_WIDTH;
+ mouse.y1 = 0;
+ mouse.x2 = screenInfo->screen_wide - 1;
+ mouse.y2 = screenInfo->screen_deep - 1;
+ mouse.priority = 0;
+
+ if (screenInfo->scroll_offset_x < screenInfo->max_scroll_offset_x) {
+ // not fully scrolled to the right
+ mouse.pointer = SCROLL_RIGHT_MOUSE_ID;
+ } else {
+ // so the mouse area doesn't get registered
+ mouse.pointer = 0;
+ }
+
+ mouse.write(ob_mouse);
+ return IR_CONT;
+}
+
+int32 Logic::fnColour(int32 *params) {
+ // set border colour - useful during script development
+ // eg. set to colour during a timer situation, then black when timed
+ // out
+
+ // params 0: colour (see defines above)
+
+#ifdef SWORD2_DEBUG
+ // what colour?
+ switch (params[0]) {
+ case BLACK:
+ _vm->_screen->setPalette(0, 1, black, RDPAL_INSTANT);
+ break;
+ case WHITE:
+ _vm->_screen->setPalette(0, 1, white, RDPAL_INSTANT);
+ break;
+ case RED:
+ _vm->_screen->setPalette(0, 1, red, RDPAL_INSTANT);
+ break;
+ case GREEN:
+ _vm->_screen->setPalette(0, 1, green, RDPAL_INSTANT);
+ break;
+ case BLUE:
+ _vm->_screen->setPalette(0, 1, blue, RDPAL_INSTANT);
+ break;
+ }
+#endif
+
+ return IR_CONT;
+}
+
+#ifdef SWORD2_DEBUG
+#define BLACK 0
+#define WHITE 1
+#define RED 2
+#define GREEN 3
+#define BLUE 4
+
+static uint8 black[4] = { 0, 0, 0, 0 };
+static uint8 white[4] = { 255, 255, 255, 0 };
+static uint8 red[4] = { 255, 0, 0, 0 };
+static uint8 green[4] = { 0, 255, 0, 0 };
+static uint8 blue[4] = { 0, 0, 255, 0 };
+#endif
+
+int32 Logic::fnFlash(int32 *params) {
+ // flash colour 0 (ie. border) - useful during script development
+ // eg. fnFlash(BLUE) where a text line is missed; RED when some code
+ // missing, etc
+
+ // params: 0 colour to flash
+
+#ifdef SWORD2_DEBUG
+ // what colour?
+ switch (params[0]) {
+ case WHITE:
+ _vm->_screen->setPalette(0, 1, white, RDPAL_INSTANT);
+ break;
+ case RED:
+ _vm->_screen->setPalette(0, 1, red, RDPAL_INSTANT);
+ break;
+ case GREEN:
+ _vm->_screen->setPalette(0, 1, green, RDPAL_INSTANT);
+ break;
+ case BLUE:
+ _vm->_screen->setPalette(0, 1, blue, RDPAL_INSTANT);
+ break;
+ }
+
+ // There used to be a busy-wait loop here, so I don't know how long
+ // the delay was meant to be. Probably doesn't matter much.
+
+ _vm->_screen->updateDisplay();
+ _vm->_system->delayMillis(250);
+ _vm->_screen->setPalette(0, 1, black, RDPAL_INSTANT);
+#endif
+
+ return IR_CONT;
+}
+
+int32 Logic::fnPreFetch(int32 *params) {
+ // Go fetch resource in the background.
+
+ // params: 0 resource to fetch [guess]
+
+ return IR_CONT;
+}
+
+/**
+ * Reverse of fnPassPlayerSaveData() - run script 8 of player object.
+ */
+
+int32 Logic::fnGetPlayerSaveData(int32 *params) {
+ // params: 0 pointer to object's logic structure
+ // 1 pointer to object's graphic structure
+ // 2 pointer to object's mega structure
+
+ byte *ob_logic = decodePtr(params[0]);
+ byte *ob_graph = decodePtr(params[1]);
+ byte *ob_mega = decodePtr(params[2]);
+
+ // Copy from savegame buffers to player object
+
+ memcpy(ob_logic, _saveLogic, ObjectLogic::size());
+ memcpy(ob_graph, _saveGraphic, ObjectGraphic::size());
+ memcpy(ob_mega, _saveMega, ObjectMega::size());
+
+ // Any walk-data must be cleared - the player will be set to stand if
+ // he was walking when saved.
+
+ ObjectMega obMega(ob_mega);
+
+ if (obMega.getIsWalking()) {
+ ObjectLogic obLogic(ob_logic);
+
+ obMega.setIsWalking(0);
+
+ int32 pars[3];
+
+ pars[0] = params[1]; // ob_graphic;
+ pars[1] = params[2]; // ob_mega
+ pars[2] = obMega.getCurDir();
+
+ fnStand(pars);
+
+ // Reset looping flag (which would have been 1 during fnWalk)
+ obLogic.setLooping(0);
+ }
+
+ return IR_CONT;
+}
+
+/**
+ * Copies the 4 essential player structures into the savegame header - run
+ * script 7 of player object to request this.
+ *
+ * Remember, we cannot simply read a compact any longer but instead must
+ * request it from the object itself.
+ */
+
+int32 Logic::fnPassPlayerSaveData(int32 *params) {
+ // params: 0 pointer to object's logic structure
+ // 1 pointer to object's graphic structure
+ // 2 pointer to object's mega structure
+
+ // Copy from player object to savegame buffers
+
+ memcpy(_saveLogic, decodePtr(params[0]), ObjectLogic::size());
+ memcpy(_saveGraphic, decodePtr(params[1]), ObjectGraphic::size());
+ memcpy(_saveMega, decodePtr(params[2]), ObjectMega::size());
+
+ return IR_CONT;
+}
+
+int32 Logic::fnSendEvent(int32 *params) {
+ // we want to intercept the player character and have him interact
+ // with an object - from script
+
+ // params: 0 id to receive event
+ // 1 script to run
+
+ sendEvent(params[0], params[1]);
+ return IR_CONT;
+}
+
+/**
+ * Add this walkgrid resource to the list of those used for routing in this
+ * location. Note that this is ignored if the resource is already in the list.
+ */
+
+int32 Logic::fnAddWalkGrid(int32 *params) {
+ // params: 0 id of walkgrid resource
+
+ // All objects that add walkgrids must be restarted whenever we
+ // re-enter a location.
+
+ // DON'T EVER KILL GEORGE!
+ if (readVar(ID) != CUR_PLAYER_ID) {
+ // Need to call this in case it wasn't called in script!
+ fnAddToKillList(NULL);
+ }
+
+ _router->addWalkGrid(params[0]);
+ fnPreLoad(params);
+ return IR_CONT;
+}
+
+/**
+ * Remove this walkgrid resource from the list of those used for routing in
+ * this location. Note that this is ignored if the resource isn't actually
+ * in the list.
+ */
+
+int32 Logic::fnRemoveWalkGrid(int32 *params) {
+ // params: 0 id of walkgrid resource
+
+ _router->removeWalkGrid(params[0]);
+ return IR_CONT;
+}
+
+// like fnCheckEventWaiting, but starts the event rather than setting RESULT
+// to 1
+
+int32 Logic::fnCheckForEvent(int32 *params) {
+ // params: none
+
+ if (checkEventWaiting()) {
+ startEvent();
+ return IR_TERMINATE;
+ }
+
+ return IR_CONT;
+}
+
+// combination of fnPause and fnCheckForEvent
+// - ie. does a pause, but also checks for event each cycle
+
+int32 Logic::fnPauseForEvent(int32 *params) {
+ // params: 0 pointer to object's logic structure
+ // 1 number of game-cycles to pause
+
+ ObjectLogic obLogic(decodePtr(params[0]));
+
+ if (checkEventWaiting()) {
+ obLogic.setLooping(0);
+ startEvent();
+ return IR_TERMINATE;
+ }
+
+ return fnPause(params);
+}
+
+int32 Logic::fnClearEvent(int32 *params) {
+ // params: none
+
+ clearEvent(readVar(ID));
+ return IR_CONT;
+}
+
+int32 Logic::fnFaceMega(int32 *params) {
+ // params: 0 pointer to object's logic structure
+ // 1 pointer to object's graphic structure
+ // 2 pointer to object's mega structure
+ // 3 pointer to object's walkdata structure
+ // 4 id of target mega to face
+
+ return _router->faceMega(
+ decodePtr(params[0]),
+ decodePtr(params[1]),
+ decodePtr(params[2]),
+ decodePtr(params[3]),
+ params[4]);
+}
+
+int32 Logic::fnPlaySequence(int32 *params) {
+ // params: 0 pointer to null-terminated ascii filename
+ // 1 number of frames in the sequence, used for PSX.
+
+ char filename[30];
+ MovieTextObject *sequenceSpeechArray[MAX_SEQUENCE_TEXT_LINES + 1];
+
+ // The original code had some #ifdef blocks for skipping or muting the
+ // cutscenes - fondly described as "the biggest fudge in the history
+ // of computer games" - but at the very least we want to show the
+ // cutscene subtitles, so I removed them.
+
+ debug(5, "fnPlaySequence(\"%s\");", (const char *)decodePtr(params[0]));
+
+ // add the appropriate file extension & play it
+
+ strcpy(filename, (const char *)decodePtr(params[0]));
+
+ // Write to walkthrough file (zebug0.txt)
+ debug(5, "PLAYING SEQUENCE \"%s\"", filename);
+
+ // now create the text sprites, if any
+
+ if (_sequenceTextLines)
+ createSequenceSpeech(sequenceSpeechArray);
+
+ // don't want to carry on streaming game music when smacker starts!
+ fnStopMusic(NULL);
+
+ // pause sfx during sequence
+ _vm->_sound->pauseFx();
+
+ MoviePlayer player(_vm);
+ uint32 rv;
+
+ if (_sequenceTextLines && !readVar(DEMO))
+ rv = player.play(filename, sequenceSpeechArray, _smackerLeadIn, _smackerLeadOut);
+ else
+ rv = player.play(filename, NULL, _smackerLeadIn, _smackerLeadOut);
+
+ // check the error return-value
+ if (rv)
+ debug(5, "MoviePlayer.play(\"%s\") returned 0x%.8x", filename, rv);
+
+ // unpause sound fx again, in case we're staying in same location
+ _vm->_sound->unpauseFx();
+
+ _smackerLeadIn = 0;
+ _smackerLeadOut = 0;
+
+ // now clear the text sprites, if any
+
+ if (_sequenceTextLines)
+ clearSequenceSpeech(sequenceSpeechArray);
+
+ // now clear the screen in case the Sequence was quitted (using ESC)
+ // rather than fading down to black
+
+ _vm->_screen->clearScene();
+
+ // zero the entire palette in case we're about to fade up!
+
+ byte pal[4 * 256];
+
+ memset(pal, 0, sizeof(pal));
+ _vm->_screen->setPalette(0, 256, pal, RDPAL_INSTANT);
+
+ debug(5, "fnPlaySequence FINISHED");
+ return IR_CONT;
+}
+
+int32 Logic::fnShadedSprite(int32 *params) {
+ // params: 0 pointer to object's graphic structure
+ _router->setSpriteShading(decodePtr(params[0]), SHADED_SPRITE);
+ return IR_CONT;
+}
+
+int32 Logic::fnUnshadedSprite(int32 *params) {
+ // params: 0 pointer to object's graphic structure
+ _router->setSpriteShading(decodePtr(params[0]), UNSHADED_SPRITE);
+ return IR_CONT;
+}
+
+int32 Logic::fnFadeUp(int32 *params) {
+ // params: none
+
+ _vm->_screen->waitForFade();
+
+ if (_vm->_screen->getFadeStatus() == RDFADE_BLACK)
+ _vm->_screen->fadeUp();
+
+ return IR_CONT;
+}
+
+int32 Logic::fnDisplayMsg(int32 *params) {
+ // Display a message to the user on the screen.
+
+ // params: 0 Text number of message to be displayed.
+
+ uint32 local_text = params[0] & 0xffff;
+ uint32 text_res = params[0] / SIZE;
+
+ // Display message for three seconds.
+
+ // +2 to skip the encoded text number in the first 2 chars; 3 is
+ // duration in seconds
+
+ _vm->_screen->displayMsg(_vm->fetchTextLine(_vm->_resman->openResource(text_res), local_text) + 2, 3);
+ _vm->_resman->closeResource(text_res);
+
+ return IR_CONT;
+}
+
+int32 Logic::fnSetObjectHeld(int32 *params) {
+ // params: 0 luggage icon to set
+ uint32 res = (uint32)params[0];
+
+ _vm->_mouse->setObjectHeld(res);
+ return IR_CONT;
+}
+
+int32 Logic::fnAddSequenceText(int32 *params) {
+ // params: 0 text number
+ // 1 frame number to start the text displaying
+ // 2 frame number to stop the text dispalying
+
+ assert(_sequenceTextLines < MAX_SEQUENCE_TEXT_LINES);
+
+ _sequenceTextList[_sequenceTextLines].textNumber = params[0];
+ _sequenceTextList[_sequenceTextLines].startFrame = params[1];
+ _sequenceTextList[_sequenceTextLines].endFrame = params[2];
+ _sequenceTextLines++;
+ return IR_CONT;
+}
+
+int32 Logic::fnResetGlobals(int32 *params) {
+ // fnResetGlobals is used by the demo - so it can loop back & restart
+ // itself
+
+ // params: none
+
+ ScreenInfo *screenInfo = _vm->_screen->getScreenInfo();
+
+ byte *globals = _vm->_resman->openResource(1) + ResHeader::size();
+ int32 size = _vm->_resman->fetchLen(1) - ResHeader::size();
+
+ debug(5, "globals size: %d", size);
+
+ // blank each global variable
+ memset(globals, 0, size);
+
+ _vm->_resman->closeResource(1);
+
+ // all objects but george
+ _vm->_resman->killAllObjects(false);
+
+ // FOR THE DEMO - FORCE THE SCROLLING TO BE RESET!
+ // - this is taken from fnInitBackground
+
+ // switch on scrolling (2 means first time on screen)
+ screenInfo->scroll_flag = 2;
+
+ // Used to be IR_CONT, but that's a bad idea. We may just have killed
+ // our own script resource -- continuing will cause a bad memory read
+ // access.
+ return IR_STOP;
+}
+
+int32 Logic::fnSetPalette(int32 *params) {
+ // params: 0 resource number of palette file, or 0 if it's to be
+ // the palette from the current screen
+
+ _vm->_screen->setFullPalette(params[0]);
+ return IR_CONT;
+}
+
+// use this in the object's service script prior to registering the mouse area
+// ie. before fnRegisterMouse or fnRegisterFrame
+// - best if kept at very top of service script
+
+int32 Logic::fnRegisterPointerText(int32 *params) {
+ // params: 0 local id of text line to use as pointer text
+
+ _vm->_mouse->registerPointerText(params[0]);
+ return IR_CONT;
+}
+
+int32 Logic::fnFetchWait(int32 *params) {
+ // Fetches a resource in the background but prevents the script from
+ // continuing until the resource is in memory.
+
+ // params: 0 resource to fetch [guess]
+
+ return IR_CONT;
+}
+
+int32 Logic::fnRelease(int32 *params) {
+ // Releases a resource from memory. Used for freeing memory for
+ // sprites that have just been used and will not be used again.
+ // Sometimes it is better to kick out a sprite straight away so that
+ // the memory can be used for more frequent animations.
+
+ // params: 0 resource to release [guess]
+
+ return IR_CONT;
+}
+
+int32 Logic::fnPrepareMusic(int32 *params) {
+ // params: 1 id of music to prepare [guess]
+ return IR_CONT;
+}
+
+int32 Logic::fnSoundFetch(int32 *params) {
+ // params: 0 id of sound to fetch [guess]
+ return IR_CONT;
+}
+
+int32 Logic::fnSmackerLeadIn(int32 *params) {
+ // params: 0 id of lead-in music
+
+ // ready for use in fnPlaySequence
+ _smackerLeadIn = params[0];
+ return IR_CONT;
+}
+
+int32 Logic::fnSmackerLeadOut(int32 *params) {
+ // params: 0 id of lead-out music
+
+ // ready for use in fnPlaySequence
+ _smackerLeadOut = params[0];
+ return IR_CONT;
+}
+
+/**
+ * Stops all FX and clears the entire FX queue.
+ */
+
+int32 Logic::fnStopAllFx(int32 *params) {
+ // params: none
+
+ _vm->_sound->clearFxQueue();
+ return IR_CONT;
+}
+
+int32 Logic::fnCheckPlayerActivity(int32 *params) {
+ // Used to decide when to trigger music cues described as "no player
+ // activity for a while"
+
+ // params: 0 threshold delay in seconds, ie. what we want to
+ // check the actual delay against
+
+ uint32 seconds = (uint32)params[0];
+
+ _vm->_mouse->checkPlayerActivity(seconds);
+ return IR_CONT;
+}
+
+int32 Logic::fnResetPlayerActivityDelay(int32 *params) {
+ // Use if you want to deliberately reset the "no player activity"
+ // counter for any reason
+
+ // params: none
+
+ _vm->_mouse->resetPlayerActivityDelay();
+ return IR_CONT;
+}
+
+int32 Logic::fnCheckMusicPlaying(int32 *params) {
+ // params: none
+
+ // sets result to no. of seconds of current tune remaining
+ // or 0 if no music playing
+
+ // in seconds, rounded up to the nearest second
+ writeVar(RESULT, _vm->_sound->musicTimeRemaining());
+ return IR_CONT;
+}
+
+int32 Logic::fnPlayCredits(int32 *params) {
+ // This function just quits the game if this is the playable demo, ie.
+ // credits are NOT played in the demo any more!
+
+ // params: none
+
+ if (readVar(DEMO)) {
+ _vm->closeGame();
+ return IR_STOP;
+ }
+
+ _vm->_screen->rollCredits();
+ return IR_CONT;
+}
+
+int32 Logic::fnSetScrollSpeedNormal(int32 *params) {
+ // params: none
+
+ _vm->_screen->setScrollFraction(16);
+ return IR_CONT;
+}
+
+int32 Logic::fnSetScrollSpeedSlow(int32 *params) {
+ // params: none
+
+ _vm->_screen->setScrollFraction(32);
+ return IR_CONT;
+}
+
+// Called from speech scripts to remove the chooser bar when it's not
+// appropriate to keep it displayed
+
+int32 Logic::fnRemoveChooser(int32 *params) {
+ // params: none
+
+ _vm->_mouse->hideMenu(RDMENU_BOTTOM);
+ return IR_CONT;
+}
+
+/**
+ * Alter the volume and pan of a currently playing FX
+ */
+
+int32 Logic::fnSetFxVolAndPan(int32 *params) {
+ // params: 0 id of fx (ie. the id returned in 'result' from
+ // fnPlayFx
+ // 1 new volume (0..16)
+ // 2 new pan (-16..16)
+
+ debug(5, "fnSetFxVolAndPan(%d, %d, %d)", params[0], params[1], params[2]);
+
+ _vm->_sound->setFxIdVolumePan(params[0], params[1], params[2]);
+ return IR_CONT;
+}
+
+/**
+ * Alter the volume of a currently playing FX
+ */
+
+int32 Logic::fnSetFxVol(int32 *params) {
+ // params: 0 id of fx (ie. the id returned in 'result' from
+ // fnPlayFx
+ // 1 new volume (0..16)
+
+ _vm->_sound->setFxIdVolumePan(params[0], params[1]);
+ return IR_CONT;
+}
+
+int32 Logic::fnRestoreGame(int32 *params) {
+ // params: none
+ return IR_CONT;
+}
+
+int32 Logic::fnRefreshInventory(int32 *params) {
+ // Called from 'menu_look_or_combine' script in 'menu_master' object
+ // to update the menu to display a combined object while George runs
+ // voice-over. Note that 'object_held' must be set to the graphic of
+ // the combined object
+
+ // params: none
+
+ _vm->_mouse->refreshInventory();
+ return IR_CONT;
+}
+
+int32 Logic::fnChangeShadows(int32 *params) {
+ // params: none
+ ScreenInfo *screenInfo = _vm->_screen->getScreenInfo();
+
+ // if last screen was using a shading mask (see below)
+ if (screenInfo->mask_flag) {
+ uint32 rv = _vm->_screen->closeLightMask();
+ if (rv)
+ error("Driver Error %.8x", rv);
+ screenInfo->mask_flag = false;
+ }
+
+ return IR_CONT;
+}
+
+} // End of namespace Sword2
diff --git a/engines/sword2/header.h b/engines/sword2/header.h
new file mode 100644
index 0000000000..9aefa52832
--- /dev/null
+++ b/engines/sword2/header.h
@@ -0,0 +1,502 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#ifndef _HEADER
+#define _HEADER
+
+#include "common/stream.h"
+
+namespace Sword2 {
+
+//----------------------------------------------------------
+// SYSTEM FILE & FRAME HEADERS
+//----------------------------------------------------------
+
+//----------------------------------------------------------
+// ALL FILES
+//----------------------------------------------------------
+
+// Standard File Header
+
+#define NAME_LEN 34
+
+struct ResHeader {
+ uint8 fileType; // Byte to define file type (see below)
+ uint8 compType; // Type of file compression used ie.
+ // on whole file (see below)
+ uint32 compSize; // Length of compressed file (ie.
+ // length on disk)
+ uint32 decompSize; // Length of decompressed file held in
+ // memory (NB. frames still held
+ // compressed)
+ byte name[NAME_LEN]; // Name of object
+
+ static const int size() {
+ return 44;
+ }
+
+ void read(byte *addr) {
+ Common::MemoryReadStream readS(addr, size());
+
+ fileType = readS.readByte();
+ compType = readS.readByte();
+ compSize = readS.readUint32LE();
+ decompSize = readS.readUint32LE();
+ readS.read(name, NAME_LEN);
+ }
+
+ void write(byte *addr) {
+ Common::MemoryWriteStream writeS(addr, size());
+
+ writeS.writeByte(fileType);
+ writeS.writeByte(compType);
+ writeS.writeUint32LE(compSize);
+ writeS.writeUint32LE(decompSize);
+ writeS.write(name, NAME_LEN);
+ }
+};
+
+// fileType
+
+enum {
+ // 0 something's wrong!
+ ANIMATION_FILE = 1, // All normal animations & sprites
+ // including mega-sets & font files
+ // which are the same format (but all
+ // frames always uncompressed)
+ SCREEN_FILE = 2, // Each contains background, palette,
+ // layer sprites, parallax layers &
+ // shading mask
+ GAME_OBJECT = 3, // Each contains object hub +
+ // structures + script data
+ WALK_GRID_FILE = 4, // Walk-grid data
+ GLOBAL_VAR_FILE = 5, // All the global script variables in
+ // one file; "there can be only one"
+ PARALLAX_FILE_null = 6, // NOT USED
+ RUN_LIST = 7, // Each contains a list of object
+ // resource id's
+ TEXT_FILE = 8, // Each contains all the lines of text
+ // for a location or a character's
+ // conversation script
+ SCREEN_MANAGER = 9, // One for each location; this contains
+ // special startup scripts
+ MOUSE_FILE = 10, // Mouse pointers and luggage icons
+ // (sprites in General / Mouse pointers
+ // & Luggage icons)
+ WAV_FILE = 11, // WAV file
+ ICON_FILE = 12, // Menu icon (sprites in General / Menu
+ // icons)
+ PALETTE_FILE = 13 // separate palette file (see also
+ // _paletteHeader)
+};
+
+// compType
+
+enum {
+ NO_COMPRESSION = 0,
+ FILE_COMPRESSION = 1 // standard whole-file compression
+ // (not yet devised!)
+};
+
+//----------------------------------------------------------
+// (1) ANIMATION FILES
+//----------------------------------------------------------
+
+// an animation file consists of:
+//
+// standard file header
+// animation header
+// a string of CDT entries (one per frame of the anim)
+// a 16-byte colour table ONLY if (runTimeComp==RLE16)
+// a string of groups of (frame header + frame data)
+
+// Animation Header
+
+struct AnimHeader {
+ uint8 runTimeComp; // Type of runtime compression used for the
+ // frame data (see below)
+ uint16 noAnimFrames; // Number of frames in the anim (ie. no. of
+ // CDT entries)
+ uint16 feetStartX; // Start coords for mega to walk to, before
+ uint16 feetStartY; // running anim
+ uint8 feetStartDir; // Direction to start in before running anim
+ uint16 feetEndX; // End coords for mega to stand at after
+ uint16 feetEndY; // running anim (vital if anim starts from an
+ // off-screen position, or ends in a different
+ // place from the start)
+ uint8 feetEndDir; // Direction to start in after running anim
+ uint16 blend;
+
+ static const int size() {
+ return 15;
+ }
+
+ void read(byte *addr) {
+ Common::MemoryReadStream readS(addr, size());
+
+ runTimeComp = readS.readByte();
+ noAnimFrames = readS.readUint16LE();
+ feetStartX = readS.readUint16LE();
+ feetStartY = readS.readUint16LE();
+ feetStartDir = readS.readByte();
+ feetEndX = readS.readUint16LE();
+ feetEndY = readS.readUint16LE();
+ feetEndDir = readS.readByte();
+ blend = readS.readUint16LE();
+ }
+
+ void write(byte *addr) {
+ Common::MemoryWriteStream writeS(addr, size());
+
+ writeS.writeByte(runTimeComp);
+ writeS.writeUint16LE(noAnimFrames);
+ writeS.writeUint16LE(feetStartX);
+ writeS.writeUint16LE(feetStartY);
+ writeS.writeByte(feetStartDir);
+ writeS.writeUint16LE(feetEndX);
+ writeS.writeUint16LE(feetEndY);
+ writeS.writeByte(feetEndDir);
+ writeS.writeUint16LE(blend);
+ }
+
+};
+
+// runtimeComp - compression used on each frame of the anim
+
+enum {
+ NONE = 0, // No frame compression
+ RLE256 = 1, // James's RLE for 256-colour sprites
+ RLE16 = 2 // James's RLE for 16- or 17-colour sprites
+ // (raw blocks have max 16 colours for 2 pixels
+ // per byte, so '0's are encoded only as FLAT
+ // for 17-colour sprites eg. George's mega-set)
+};
+
+// CDT Entry
+
+struct CdtEntry {
+ int16 x; // sprite x-coord OR offset to add to mega's
+ // feet x-coord to calc sprite y-coord
+ int16 y; // sprite y-coord OR offset to add to mega's
+ // feet y-coord to calc sprite y-coord
+ uint32 frameOffset; // points to start of frame header (from start
+ // of file header)
+ uint8 frameType; // 0 = print sprite normally with top-left
+ // corner at (x,y), otherwise see below...
+
+ static const int size() {
+ return 9;
+ }
+
+ void read(byte *addr) {
+ Common::MemoryReadStream readS(addr, size());
+
+ x = readS.readUint16LE();
+ y = readS.readUint16LE();
+ frameOffset = readS.readUint32LE();
+ frameType = readS.readByte();
+ }
+
+ void write(byte *addr) {
+ Common::MemoryWriteStream writeS(addr, size());
+
+ writeS.writeUint16LE(x);
+ writeS.writeUint16LE(y);
+ writeS.writeUint32LE(frameOffset);
+ writeS.writeByte(frameType);
+ }
+};
+
+// 'frameType' bit values
+
+enum {
+ FRAME_OFFSET = 1, // Print at (feetX + x, feetY + y), with
+ // scaling according to feetY
+ FRAME_FLIPPED = 2, // Print the frame flipped Left->Right
+ FRAME_256_FAST = 4 // Frame has been compressed using Pauls fast
+ // RLE 256 compression.
+};
+
+// Frame Header
+
+struct FrameHeader {
+ uint32 compSize; // Compressed size of frame - NB. compression
+ // type is now in Anim Header
+ uint16 width; // Dimensions of frame
+ uint16 height;
+
+ static const int size() {
+ return 8;
+ }
+
+ void read(byte *addr) {
+ Common::MemoryReadStream readS(addr, size());
+
+ compSize = readS.readUint32LE();
+ width = readS.readUint16LE();
+ height = readS.readUint16LE();
+ }
+
+ void write(byte *addr) {
+ Common::MemoryWriteStream writeS(addr, size());
+
+ writeS.writeUint32LE(compSize);
+ writeS.writeUint16LE(width);
+ writeS.writeUint16LE(height);
+ }
+};
+
+//----------------------------------------------------------
+// (2) SCREEN FILES
+//----------------------------------------------------------
+// a screen file consists of:
+//
+// standard file header
+// multi screen header
+// 4 * 256 bytes of palette data
+// 256k palette match table
+// 2 background parallax layers
+// 1 background layer with screen header
+// 2 foreground parallax layers
+// a string of layer headers
+// a string of layer masks
+
+// Multi screen header
+// Goes at the beginning of a screen file after the standard header.
+// Gives offsets from start of table of each of the components
+
+struct MultiScreenHeader {
+ uint32 palette;
+ uint32 bg_parallax[2];
+ uint32 screen;
+ uint32 fg_parallax[2];
+ uint32 layers;
+ uint32 paletteTable;
+ uint32 maskOffset;
+
+ static const int size() {
+ return 36;
+ }
+
+ void read(byte *addr) {
+ Common::MemoryReadStream readS(addr, size());
+
+ palette = readS.readUint32LE();
+ bg_parallax[0] = readS.readUint32LE();
+ bg_parallax[1] = readS.readUint32LE();
+ screen = readS.readUint32LE();
+ fg_parallax[0] = readS.readUint32LE();
+ fg_parallax[1] = readS.readUint32LE();
+ layers = readS.readUint32LE();
+ paletteTable = readS.readUint32LE();
+ maskOffset = readS.readUint32LE();
+ }
+
+ void write(byte *addr) {
+ Common::MemoryWriteStream writeS(addr, size());
+
+ writeS.writeUint32LE(palette);
+ writeS.writeUint32LE(bg_parallax[0]);
+ writeS.writeUint32LE(bg_parallax[1]);
+ writeS.writeUint32LE(screen);
+ writeS.writeUint32LE(fg_parallax[0]);
+ writeS.writeUint32LE(fg_parallax[1]);
+ writeS.writeUint32LE(layers);
+ writeS.writeUint32LE(paletteTable);
+ writeS.writeUint32LE(maskOffset);
+ }
+};
+
+// Screen Header
+
+struct ScreenHeader {
+ uint16 width; // dimensions of the background screen
+ uint16 height;
+ uint16 noLayers; // number of layer areas
+
+ static const int size() {
+ return 6;
+ }
+
+ void read(byte *addr) {
+ Common::MemoryReadStream readS(addr, size());
+
+ width = readS.readUint16LE();
+ height = readS.readUint16LE();
+ noLayers = readS.readUint16LE();
+ }
+
+ void write(byte *addr) {
+ Common::MemoryWriteStream writeS(addr, size());
+
+ writeS.writeUint16LE(width);
+ writeS.writeUint16LE(height);
+ writeS.writeUint16LE(noLayers);
+ }
+};
+
+// Layer Header
+
+// Note that all the layer headers are kept together, rather than being placed
+// before each layer mask, in order to simplify the sort routine.
+
+struct LayerHeader {
+ uint16 x; // coordinates of top-left pixel of area
+ uint16 y;
+ uint16 width;
+ uint16 height;
+ uint32 maskSize;
+ uint32 offset; // where to find mask data (from start of
+ // standard file header)
+
+ static const int size() {
+ return 16;
+ }
+
+ void read(byte *addr) {
+ Common::MemoryReadStream readS(addr, size());
+
+ x = readS.readUint16LE();
+ y = readS.readUint16LE();
+ width = readS.readUint16LE();
+ height = readS.readUint16LE();
+ maskSize = readS.readUint32LE();
+ offset = readS.readUint32LE();
+ }
+
+ void write(byte *addr) {
+ Common::MemoryWriteStream writeS(addr, size());
+
+ writeS.writeUint16LE(x);
+ writeS.writeUint16LE(y);
+ writeS.writeUint16LE(width);
+ writeS.writeUint16LE(height);
+ writeS.writeUint32LE(maskSize);
+ writeS.writeUint32LE(offset);
+ }
+};
+
+//----------------------------------------------------------
+// (3) SCRIPT OBJECT FILES
+//----------------------------------------------------------
+// a script object file consists of:
+//
+// standard file header
+// script object header
+// script object data
+
+//----------------------------------------------------------
+// (5) PALETTE FILES
+//----------------------------------------------------------
+// a palette file consists of:
+//
+// standard file header
+// 4 * 256 bytes of palette data
+// 256k palette match table
+
+// an object hub - which represents all that remains of the compact concept
+
+class ObjectHub {
+ // int32 type; // type of object
+ // uint32 logic_level; // what level?
+ // uint32 logic[3] // NOT USED
+ // uint32 script_id[3] // need this if script
+ // uint32 script_pc[3] // need this also
+
+private:
+ byte *_addr;
+
+public:
+ ObjectHub() {
+ _addr = NULL;
+ }
+
+ static const int size() {
+ return 44;
+ }
+
+ byte *data() {
+ return _addr;
+ }
+
+ void setAddress(byte *addr) {
+ _addr = addr;
+ }
+
+ byte *getScriptPcPtr(int level) {
+ return _addr + 32 + 4 * level;
+ }
+
+ uint32 getLogicLevel() {
+ return READ_LE_UINT32(_addr + 4);
+ }
+ uint32 getScriptId(int level) {
+ return READ_LE_UINT32(_addr + 20 + 4 * level);
+ }
+ uint32 getScriptPc(int level) {
+ return READ_LE_UINT32(_addr + 32 + 4 * level);
+ }
+
+ void setLogicLevel(uint32 x) {
+ WRITE_LE_UINT32(_addr + 4, x);
+ }
+ void setScriptId(int level, uint32 x) {
+ WRITE_LE_UINT32(_addr + 20 + 4 * level, x);
+ }
+ void setScriptPc(int level, uint32 x) {
+ WRITE_LE_UINT32(_addr + 32 + 4 * level, x);
+ }
+};
+
+// (6) text module header
+
+struct TextHeader {
+ uint32 noOfLines; // how many lines of text are there in this
+ // module
+
+ static const int size() {
+ return 4;
+ }
+
+ void read(byte *addr) {
+ Common::MemoryReadStream readS(addr, size());
+
+ noOfLines = readS.readUint32LE();
+ }
+
+ void write(byte *addr) {
+ Common::MemoryWriteStream writeS(addr, size());
+
+ writeS.writeUint32LE(noOfLines);
+ }
+};
+
+// a text file has:
+//
+// ResHeader
+// TextHeader
+// look up table, to
+// line of text,0
+// line of text,0
+
+} // End of namespace Sword2
+
+#endif
diff --git a/engines/sword2/icons.cpp b/engines/sword2/icons.cpp
new file mode 100644
index 0000000000..980c20e3b4
--- /dev/null
+++ b/engines/sword2/icons.cpp
@@ -0,0 +1,216 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "common/stdafx.h"
+#include "common/stream.h"
+#include "sword2/sword2.h"
+#include "sword2/defs.h"
+#include "sword2/logic.h"
+#include "sword2/mouse.h"
+#include "sword2/resman.h"
+
+namespace Sword2 {
+
+void Mouse::addMenuObject(byte *ptr) {
+ assert(_totalTemp < TOTAL_engine_pockets);
+
+ Common::MemoryReadStream readS(ptr, 2 * sizeof(int32));
+
+ _tempList[_totalTemp].icon_resource = readS.readSint32LE();
+ _tempList[_totalTemp].luggage_resource = readS.readSint32LE();
+ _totalTemp++;
+}
+
+void Mouse::addSubject(int32 id, int32 ref) {
+ uint32 in_subject = _vm->_logic->readVar(IN_SUBJECT);
+
+ if (in_subject == 0) {
+ // This is the start of the new subject list. Set the default
+ // repsonse id to zero in case we're never passed one.
+ _defaultResponseId = 0;
+ }
+
+ if (id == -1) {
+ // Id -1 is used for setting the default response, i.e. the
+ // response when someone uses an object on a person and he
+ // doesn't know anything about it. See fnChoose().
+
+ _defaultResponseId = ref;
+ } else {
+ debug(5, "fnAddSubject res %d, uid %d", id, ref);
+ _subjectList[in_subject].res = id;
+ _subjectList[in_subject].ref = ref;
+ _vm->_logic->writeVar(IN_SUBJECT, in_subject + 1);
+ }
+}
+
+/**
+ * Create and start the inventory (bottom) menu
+ */
+
+void Mouse::buildMenu() {
+ uint32 i, j;
+
+ // Clear the temporary inventory list, since we are going to build a
+ // new one from scratch.
+
+ for (i = 0; i < TOTAL_engine_pockets; i++)
+ _tempList[i].icon_resource = 0;
+
+ _totalTemp = 0;
+
+ // Run the 'build_menu' script in the 'menu_master' object. This will
+ // register all carried menu objects.
+
+ _vm->_logic->runResScript(MENU_MASTER_OBJECT, 0);
+
+ // Create a new master list based on the old master inventory list and
+ // the new temporary inventory list. The purpose of all this is, as
+ // far as I can tell, that the new list is ordered in the same way as
+ // the old list, with new objects added to the end of it.
+
+ // Compare new with old. Anything in master thats not in new gets
+ // removed from master - if found in new too, remove from temp
+
+ for (i = 0; i < _totalMasters; i++) {
+ bool found_in_temp = false;
+
+ for (j = 0; j < TOTAL_engine_pockets; j++) {
+ if (_masterMenuList[i].icon_resource == _tempList[j].icon_resource) {
+ // We alread know about this object, so kill it
+ // in the temporary list.
+ _tempList[j].icon_resource = 0;
+ found_in_temp = true;
+ break;
+ }
+ }
+
+ if (!found_in_temp) {
+ // The object is in the master list, but not in the
+ // temporary list. The player must have lost the object
+ // since the last time we checked, so kill it in the
+ // master list.
+ _masterMenuList[i].icon_resource = 0;
+ }
+ }
+
+ // Eliminate blank entries from the master list.
+
+ _totalMasters = 0;
+
+ for (i = 0; i < TOTAL_engine_pockets; i++) {
+ if (_masterMenuList[i].icon_resource) {
+ if (i != _totalMasters) {
+ memcpy(&_masterMenuList[_totalMasters], &_masterMenuList[i], sizeof(MenuObject));
+ _masterMenuList[i].icon_resource = 0;
+ }
+ _totalMasters++;
+ }
+ }
+
+ // Add the new objects - i.e. the ones still in the temporary list but
+ // not yet in the master list - to the end of the master.
+
+ for (i = 0; i < TOTAL_engine_pockets; i++) {
+ if (_tempList[i].icon_resource) {
+ memcpy(&_masterMenuList[_totalMasters++], &_tempList[i], sizeof(MenuObject));
+ }
+ }
+
+ // Initialise the menu from the master list.
+
+ for (i = 0; i < 15; i++) {
+ uint32 res = _masterMenuList[i].icon_resource;
+ byte *icon = NULL;
+
+ if (res) {
+ bool icon_coloured;
+
+ uint32 object_held = _vm->_logic->readVar(OBJECT_HELD);
+ uint32 combine_base = _vm->_logic->readVar(COMBINE_BASE);
+
+ if (_examiningMenuIcon) {
+ // When examining an object, that object is
+ // coloured. The rest are greyed out.
+ icon_coloured = (res == object_held);
+ } else if (combine_base) {
+ // When combining two menu object (i.e. using
+ // one on another), both are coloured. The rest
+ // are greyed out.
+ icon_coloured = (res == object_held || combine_base);
+ } else {
+ // If an object is selected but we are not yet
+ // doing anything with it, the selected object
+ // is greyed out. The rest are coloured.
+ icon_coloured = (res != object_held);
+ }
+
+ icon = _vm->_resman->openResource(res) + ResHeader::size();
+
+ // The coloured icon is stored directly after the
+ // greyed out one.
+
+ if (icon_coloured)
+ icon += (RDMENU_ICONWIDE * RDMENU_ICONDEEP);
+ }
+
+ setMenuIcon(RDMENU_BOTTOM, i, icon);
+
+ if (res)
+ _vm->_resman->closeResource(res);
+ }
+
+ showMenu(RDMENU_BOTTOM);
+}
+
+/**
+ * Build a fresh system (top) menu.
+ */
+
+void Mouse::buildSystemMenu() {
+ uint32 icon_list[5] = {
+ OPTIONS_ICON,
+ QUIT_ICON,
+ SAVE_ICON,
+ RESTORE_ICON,
+ RESTART_ICON
+ };
+
+ // Build them all high in full colour - when one is clicked on all the
+ // rest will grey out.
+
+ for (int i = 0; i < ARRAYSIZE(icon_list); i++) {
+ byte *icon = _vm->_resman->openResource(icon_list[i]) + ResHeader::size();
+
+ // The only case when an icon is grayed is when the player
+ // is dead. Then SAVE is not available.
+
+ if (!_vm->_logic->readVar(DEAD) || icon_list[i] != SAVE_ICON)
+ icon += (RDMENU_ICONWIDE * RDMENU_ICONDEEP);
+
+ setMenuIcon(RDMENU_TOP, i, icon);
+ _vm->_resman->closeResource(icon_list[i]);
+ }
+
+ showMenu(RDMENU_TOP);
+}
+
+} // End of namespace Sword2
diff --git a/engines/sword2/icons.h b/engines/sword2/icons.h
new file mode 100644
index 0000000000..ab5ea578ca
--- /dev/null
+++ b/engines/sword2/icons.h
@@ -0,0 +1,41 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#ifndef _ICONS
+#define _ICONS
+
+#define MENU_MASTER_OBJECT 44
+#define TOTAL_subjects (375 - 256 + 1) // the speech subject bar
+#define TOTAL_engine_pockets (15 + 10) // +10 for overflow
+
+namespace Sword2 {
+
+// define these in a script and then register them with the system
+
+struct MenuObject {
+ int32 icon_resource; // icon graphic graphic
+ int32 luggage_resource; // luggage icon resource (for attaching to
+ // mouse pointer)
+};
+
+} // End of namespace Sword2
+
+#endif
diff --git a/engines/sword2/interpreter.cpp b/engines/sword2/interpreter.cpp
new file mode 100644
index 0000000000..1a6e7080b8
--- /dev/null
+++ b/engines/sword2/interpreter.cpp
@@ -0,0 +1,753 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "common/stdafx.h"
+#include "common/util.h"
+#include "sword2/sword2.h"
+#include "sword2/defs.h"
+#include "sword2/interpreter.h"
+#include "sword2/logic.h"
+#include "sword2/memory.h"
+#include "sword2/resman.h"
+
+namespace Sword2 {
+
+#define STACK_SIZE 10
+
+// The machine code table
+
+#ifndef REDUCE_MEMORY_USAGE
+# define OPCODE(x) { &Logic::x, #x }
+#else
+# define OPCODE(x) { &Logic::x, "" }
+#endif
+
+typedef int32 (Logic::*OpcodeProc)(int32 *);
+struct OpcodeEntry {
+ OpcodeProc proc;
+ const char *desc;
+};
+
+static const OpcodeEntry opcodes[] = {
+ /* 00 */
+ OPCODE(fnTestFunction),
+ OPCODE(fnTestFlags),
+ OPCODE(fnRegisterStartPoint),
+ OPCODE(fnInitBackground),
+ /* 04 */
+ OPCODE(fnSetSession),
+ OPCODE(fnBackSprite),
+ OPCODE(fnSortSprite),
+ OPCODE(fnForeSprite),
+ /* 08 */
+ OPCODE(fnRegisterMouse),
+ OPCODE(fnAnim),
+ OPCODE(fnRandom),
+ OPCODE(fnPreLoad),
+ /* 0C */
+ OPCODE(fnAddSubject),
+ OPCODE(fnInteract),
+ OPCODE(fnChoose),
+ OPCODE(fnWalk),
+ /* 10 */
+ OPCODE(fnWalkToAnim),
+ OPCODE(fnTurn),
+ OPCODE(fnStandAt),
+ OPCODE(fnStand),
+ /* 14 */
+ OPCODE(fnStandAfterAnim),
+ OPCODE(fnPause),
+ OPCODE(fnMegaTableAnim),
+ OPCODE(fnAddMenuObject),
+ /* 18 */
+ OPCODE(fnStartConversation),
+ OPCODE(fnEndConversation),
+ OPCODE(fnSetFrame),
+ OPCODE(fnRandomPause),
+ /* 1C */
+ OPCODE(fnRegisterFrame),
+ OPCODE(fnNoSprite),
+ OPCODE(fnSendSync),
+ OPCODE(fnUpdatePlayerStats),
+ /* 20 */
+ OPCODE(fnPassGraph),
+ OPCODE(fnInitFloorMouse),
+ OPCODE(fnPassMega),
+ OPCODE(fnFaceXY),
+ /* 24 */
+ OPCODE(fnEndSession),
+ OPCODE(fnNoHuman),
+ OPCODE(fnAddHuman),
+ OPCODE(fnWeWait),
+ /* 28 */
+ OPCODE(fnTheyDoWeWait),
+ OPCODE(fnTheyDo),
+ OPCODE(fnWalkToTalkToMega),
+ OPCODE(fnFadeDown),
+ /* 2C */
+ OPCODE(fnISpeak),
+ OPCODE(fnTotalRestart),
+ OPCODE(fnSetWalkGrid),
+ OPCODE(fnSpeechProcess),
+ /* 30 */
+ OPCODE(fnSetScaling),
+ OPCODE(fnStartEvent),
+ OPCODE(fnCheckEventWaiting),
+ OPCODE(fnRequestSpeech),
+ /* 34 */
+ OPCODE(fnGosub),
+ OPCODE(fnTimedWait),
+ OPCODE(fnPlayFx),
+ OPCODE(fnStopFx),
+ /* 38 */
+ OPCODE(fnPlayMusic),
+ OPCODE(fnStopMusic),
+ OPCODE(fnSetValue),
+ OPCODE(fnNewScript),
+ /* 3C */
+ OPCODE(fnGetSync),
+ OPCODE(fnWaitSync),
+ OPCODE(fnRegisterWalkGrid),
+ OPCODE(fnReverseMegaTableAnim),
+ /* 40 */
+ OPCODE(fnReverseAnim),
+ OPCODE(fnAddToKillList),
+ OPCODE(fnSetStandbyCoords),
+ OPCODE(fnBackPar0Sprite),
+ /* 44 */
+ OPCODE(fnBackPar1Sprite),
+ OPCODE(fnForePar0Sprite),
+ OPCODE(fnForePar1Sprite),
+ OPCODE(fnSetPlayerActionEvent),
+ /* 48 */
+ OPCODE(fnSetScrollCoordinate),
+ OPCODE(fnStandAtAnim),
+ OPCODE(fnSetScrollLeftMouse),
+ OPCODE(fnSetScrollRightMouse),
+ /* 4C */
+ OPCODE(fnColour),
+ OPCODE(fnFlash),
+ OPCODE(fnPreFetch),
+ OPCODE(fnGetPlayerSaveData),
+ /* 50 */
+ OPCODE(fnPassPlayerSaveData),
+ OPCODE(fnSendEvent),
+ OPCODE(fnAddWalkGrid),
+ OPCODE(fnRemoveWalkGrid),
+ /* 54 */
+ OPCODE(fnCheckForEvent),
+ OPCODE(fnPauseForEvent),
+ OPCODE(fnClearEvent),
+ OPCODE(fnFaceMega),
+ /* 58 */
+ OPCODE(fnPlaySequence),
+ OPCODE(fnShadedSprite),
+ OPCODE(fnUnshadedSprite),
+ OPCODE(fnFadeUp),
+ /* 5C */
+ OPCODE(fnDisplayMsg),
+ OPCODE(fnSetObjectHeld),
+ OPCODE(fnAddSequenceText),
+ OPCODE(fnResetGlobals),
+ /* 60 */
+ OPCODE(fnSetPalette),
+ OPCODE(fnRegisterPointerText),
+ OPCODE(fnFetchWait),
+ OPCODE(fnRelease),
+ /* 64 */
+ OPCODE(fnPrepareMusic),
+ OPCODE(fnSoundFetch),
+ OPCODE(fnPrepareMusic), // Again, apparently
+ OPCODE(fnSmackerLeadIn),
+ /* 68 */
+ OPCODE(fnSmackerLeadOut),
+ OPCODE(fnStopAllFx),
+ OPCODE(fnCheckPlayerActivity),
+ OPCODE(fnResetPlayerActivityDelay),
+ /* 6C */
+ OPCODE(fnCheckMusicPlaying),
+ OPCODE(fnPlayCredits),
+ OPCODE(fnSetScrollSpeedNormal),
+ OPCODE(fnSetScrollSpeedSlow),
+ /* 70 */
+ OPCODE(fnRemoveChooser),
+ OPCODE(fnSetFxVolAndPan),
+ OPCODE(fnSetFxVol),
+ OPCODE(fnRestoreGame),
+ /* 74 */
+ OPCODE(fnRefreshInventory),
+ OPCODE(fnChangeShadows)
+};
+
+#define push(value) \
+do { \
+ assert(stackPtr < ARRAYSIZE(stack)); \
+ stack[stackPtr++] = (value); \
+} while (false)
+
+#define push_ptr(ptr) push(_vm->_memory->encodePtr(ptr))
+
+#define pop() (assert(stackPtr < ARRAYSIZE(stack)), stack[--stackPtr])
+
+int Logic::runResScript(uint32 scriptRes, uint32 offset) {
+ byte *scriptAddr;
+ int result;
+
+ scriptAddr = _vm->_resman->openResource(scriptRes);
+ result = runScript(scriptAddr, scriptAddr, offset);
+ _vm->_resman->closeResource(scriptRes);
+
+ return result;
+}
+
+int Logic::runResObjScript(uint32 scriptRes, uint32 objRes, uint32 offset) {
+ byte *scriptAddr, *objAddr;
+ int result;
+
+ scriptAddr = _vm->_resman->openResource(scriptRes);
+ objAddr = _vm->_resman->openResource(objRes);
+ result = runScript(scriptAddr, objAddr, offset);
+ _vm->_resman->closeResource(objRes);
+ _vm->_resman->closeResource(scriptRes);
+
+ return result;
+}
+
+int Logic::runScript(byte *scriptData, byte *objectData, uint32 offset) {
+ byte pc[4];
+
+ WRITE_LE_UINT32(pc, offset);
+ return runScript2(scriptData, objectData, pc);
+}
+
+// This form of the runScript function is only called directly from
+// the processSession() function, which uses it to update the script PC in the
+// current object hub. For reasons which I do not understand, I couldn't get it
+// to work if I called the function first with a dummy offset variable, and
+// and then updated the object hub myself.
+
+int Logic::runScript2(byte *scriptData, byte *objectData, byte *offsetPtr) {
+ // Interestingly, unlike our BASS engine the stack is a local variable.
+ // I don't know whether or not this is relevant to the working of the
+ // BS2 engine.
+
+ int32 stack[STACK_SIZE];
+ int32 stackPtr = 0;
+
+ uint32 offset = READ_LE_UINT32(offsetPtr);
+
+ ResHeader header;
+
+ header.read(scriptData);
+
+ scriptData += ResHeader::size() + ObjectHub::size();
+
+ // The script data format:
+ // int32_TYPE 1 Size of variable space in bytes
+ // ... The variable space
+ // int32_TYPE 1 numberOfScripts
+ // int32_TYPE numberOfScripts The offsets for each script
+
+ // Initialise some stuff
+
+ uint32 ip = 0; // Code pointer
+ int scriptNumber;
+
+ // Get the start of variables and start of code
+
+ byte *localVars = scriptData + 4;
+ byte *code = scriptData + READ_LE_UINT32(scriptData) + 4;
+ uint32 noScripts = READ_LE_UINT32(code);
+
+ code += 4;
+
+ byte *offsetTable = code;
+
+ if (offset < noScripts) {
+ ip = READ_LE_UINT32(offsetTable + offset * 4);
+ scriptNumber = offset;
+ debug(8, "Starting script %d from %d", scriptNumber, ip);
+ } else {
+ uint i;
+
+ ip = offset;
+
+ for (i = 1; i < noScripts; i++) {
+ if (READ_LE_UINT32(offsetTable + 4 * i) >= ip)
+ break;
+ }
+
+ scriptNumber = i - 1;
+ debug(8, "Resuming script %d from %d", scriptNumber, ip);
+ }
+
+ // There are a couple of known script bugs related to interacting with
+ // certain objects. We try to work around a few of them.
+
+ bool checkMopBug = false;
+ bool checkPyramidBug = false;
+ bool checkElevatorBug = false;
+
+ if (scriptNumber == 2) {
+ if (strcmp((char *)header.name, "mop_73") == 0)
+ checkMopBug = true;
+ else if (strcmp((char *)header.name, "titipoco_81") == 0)
+ checkPyramidBug = true;
+ else if (strcmp((char *)header.name, "lift_82") == 0)
+ checkElevatorBug = true;
+ }
+
+ code += noScripts * 4;
+
+ // Code should now be pointing at an identifier and a checksum
+ byte *checksumBlock = code;
+
+ code += 4 * 3;
+
+ if (READ_LE_UINT32(checksumBlock) != 12345678) {
+ error("Invalid script in object %s", header.name);
+ return 0;
+ }
+
+ int32 codeLen = READ_LE_UINT32(checksumBlock + 4);
+ int32 checksum = 0;
+
+ for (int i = 0; i < codeLen; i++)
+ checksum += (unsigned char) code[i];
+
+ if (checksum != (int32)READ_LE_UINT32(checksumBlock + 8)) {
+ debug(1, "Checksum error in object %s", header.name);
+ // This could be bad, but there has been a report about someone
+ // who had problems running the German version because of
+ // checksum errors. Could there be a version where checksums
+ // weren't properly calculated?
+ }
+
+ bool runningScript = true;
+
+ int parameterReturnedFromMcodeFunction = 0; // Allow scripts to return things
+ int savedStartOfMcode = 0; // For saving start of mcode commands
+
+ while (runningScript) {
+ int i;
+ int32 a, b;
+ int curCommand, parameter, value; // Command and parameter variables
+ int retVal;
+ int caseCount;
+ bool foundCase;
+ byte *ptr;
+
+ curCommand = code[ip++];
+
+ switch (curCommand) {
+
+ // Script-related opcodes
+
+ case CP_END_SCRIPT:
+ // End the script
+ runningScript = false;
+
+ // WORKAROUND: The dreaded pyramid bug makes the torch
+ // untakeable when you speak to Titipoco. This is
+ // because one of the conditions for the torch to be
+ // takeable is that Titipoco isn't doing anything out
+ // of the ordinary. Global variable 913 has to be 0 to
+ // signify that he is in his "idle" state.
+ //
+ // Unfortunately, simply the act of speaking to him
+ // sets variable 913 to 1 (probably to stop him from
+ // turning around every now and then). The script may
+ // then go on to set the variable to different values
+ // to trigger various behaviours in him, but if you
+ // have run out of these cases the script won't ever
+ // set it back to 0 again.
+ //
+ // So if his click hander finishes, and variable 913 is
+ // 1, we set it back to 0 manually.
+
+ if (checkPyramidBug && readVar(913) == 1) {
+ warning("Working around pyramid bug: Resetting Titipoco's state");
+ writeVar(913, 0);
+ }
+
+ // WORKAROUND: The not-so-known-but-should-be-dreaded
+ // elevator bug.
+ //
+ // The click handler for the top of the elevator only
+ // handles using the elevator, not examining it. When
+ // examining it, the mouse cursor is removed but never
+ // restored.
+
+ if (checkElevatorBug && readVar(RIGHT_BUTTON)) {
+ warning("Working around elevator bug: Restoring mouse pointer");
+ fnAddHuman(NULL);
+ }
+
+ debug(9, "CP_END_SCRIPT");
+ break;
+ case CP_QUIT:
+ // Quit out for a cycle
+ WRITE_LE_UINT32(offsetPtr, ip);
+ debug(9, "CP_QUIT");
+ return 0;
+ case CP_TERMINATE:
+ // Quit out immediately without affecting the offset
+ // pointer
+ debug(9, "CP_TERMINATE");
+ return 3;
+ case CP_RESTART_SCRIPT:
+ // Start the script again
+ ip = FROM_LE_32(offsetTable[scriptNumber]);
+ debug(9, "CP_RESTART_SCRIPT");
+ break;
+
+ // Stack-related opcodes
+
+ case CP_PUSH_INT32:
+ // Push a long word value on to the stack
+ Read32ip(parameter);
+ push(parameter);
+ debug(9, "CP_PUSH_INT32: %d", parameter);
+ break;
+ case CP_PUSH_LOCAL_VAR32:
+ // Push the contents of a local variable
+ Read16ip(parameter);
+ push(READ_LE_UINT32(localVars + parameter));
+ debug(9, "CP_PUSH_LOCAL_VAR32: localVars[%d] => %d", parameter / 4, READ_LE_UINT32(localVars + parameter));
+ break;
+ case CP_PUSH_GLOBAL_VAR32:
+ // Push a global variable
+ Read16ip(parameter);
+ push(readVar(parameter));
+ debug(9, "CP_PUSH_GLOBAL_VAR32: scriptVars[%d] => %d", parameter, readVar(parameter));
+ break;
+ case CP_PUSH_LOCAL_ADDR:
+ // Push the address of a local variable
+
+ // From what I understand, some scripts store data
+ // (e.g. mouse pointers) in their local variable space
+ // from the very beginning, and use this mechanism to
+ // pass that data to the opcode function. I don't yet
+ // know the conceptual difference between this and the
+ // CP_PUSH_DEREFERENCED_STRUCTURE opcode.
+
+ Read16ip(parameter);
+ push_ptr(localVars + parameter);
+ debug(9, "CP_PUSH_LOCAL_ADDR: &localVars[%d] => %p", parameter / 4, localVars + parameter);
+ break;
+ case CP_PUSH_STRING:
+ // Push the address of a string on to the stack
+ // Get the string size
+ Read8ip(parameter);
+
+ // ip now points to the string
+ ptr = code + ip;
+ push_ptr(ptr);
+ debug(9, "CP_PUSH_STRING: \"%s\"", ptr);
+ ip += (parameter + 1);
+ break;
+ case CP_PUSH_DEREFERENCED_STRUCTURE:
+ // Push the address of a dereferenced structure
+ Read32ip(parameter);
+ ptr = objectData + 4 + ResHeader::size() + ObjectHub::size() + parameter;
+ push_ptr(ptr);
+ debug(9, "CP_PUSH_DEREFERENCED_STRUCTURE: %d => %p", parameter, ptr);
+ break;
+ case CP_POP_LOCAL_VAR32:
+ // Pop a value into a local word variable
+ Read16ip(parameter);
+ value = pop();
+ WRITE_LE_UINT32(localVars + parameter, value);
+ debug(9, "CP_POP_LOCAL_VAR32: localVars[%d] = %d", parameter / 4, value);
+ break;
+ case CP_POP_GLOBAL_VAR32:
+ // Pop a global variable
+ Read16ip(parameter);
+ value = pop();
+
+ // WORKAROUND for bug #1214168: The not-at-all dreaded
+ // mop bug.
+ //
+ // At the London Docks, global variable 1003 keeps
+ // track of Nico:
+ //
+ // 0: Hiding behind the first crate.
+ // 1: Hiding behind the second crate.
+ // 2: Standing in plain view on the deck.
+ // 3: Hiding on the roof.
+ //
+ // The bug happens when trying to pick up the mop while
+ // hiding on the roof. Nico climbs down, the mop is
+ // picked up, but the variable remains set to 3.
+ // Visually, everything looks ok. But as far as the
+ // scripts are concerned, she's still hiding up on the
+ // roof. This is not fatal, but leads to a number of
+ // glitches until the state is corrected. E.g. trying
+ // to climb back up the ladder will cause Nico to climb
+ // down again.
+ //
+ // Global variable 1017 keeps track of the mop. Setting
+ // it to 2 means that the mop has been picked up. We
+ // use that as the signal that Nico's state needs to be
+ // updated as well.
+
+ if (checkMopBug && parameter == 1017 && readVar(1003) != 2) {
+ warning("Working around mop bug: Setting Nico's state");
+ writeVar(1003, 2);
+ }
+
+ writeVar(parameter, value);
+ debug(9, "CP_POP_GLOBAL_VAR32: scriptsVars[%d] = %d", parameter, value);
+ break;
+ case CP_ADDNPOP_LOCAL_VAR32:
+ Read16ip(parameter);
+ value = READ_LE_UINT32(localVars + parameter) + pop();
+ WRITE_LE_UINT32(localVars + parameter, value);
+ debug(9, "CP_ADDNPOP_LOCAL_VAR32: localVars[%d] => %d", parameter / 4, value);
+ break;
+ case CP_SUBNPOP_LOCAL_VAR32:
+ Read16ip(parameter);
+ value = READ_LE_UINT32(localVars + parameter) - pop();
+ WRITE_LE_UINT32(localVars + parameter, value);
+ debug(9, "CP_SUBNPOP_LOCAL_VAR32: localVars[%d] => %d", parameter / 4, value);
+ break;
+ case CP_ADDNPOP_GLOBAL_VAR32:
+ // Add and pop a global variable
+ Read16ip(parameter);
+ value = readVar(parameter) + pop();
+ writeVar(parameter, value);
+ debug(9, "CP_ADDNPOP_GLOBAL_VAR32: scriptVars[%d] => %d", parameter, value);
+ break;
+ case CP_SUBNPOP_GLOBAL_VAR32:
+ // Sub and pop a global variable
+ Read16ip(parameter);
+ value = readVar(parameter) - pop();
+ writeVar(parameter, value);
+ debug(9, "CP_SUBNPOP_GLOBAL_VAR32: scriptVars[%d] => %d", parameter, value);
+ break;
+
+ // Jump opcodes
+
+ case CP_SKIPONTRUE:
+ // Skip if the value on the stack is true
+ Read32ipLeaveip(parameter);
+ value = pop();
+ if (!value) {
+ ip += 4;
+ debug(9, "CP_SKIPONTRUE: %d (IS FALSE (NOT SKIPPED))", parameter);
+ } else {
+ ip += parameter;
+ debug(9, "CP_SKIPONTRUE: %d (IS TRUE (SKIPPED))", parameter);
+ }
+ break;
+ case CP_SKIPONFALSE:
+ // Skip if the value on the stack is false
+ Read32ipLeaveip(parameter);
+ value = pop();
+ if (value) {
+ ip += 4;
+ debug(9, "CP_SKIPONFALSE: %d (IS TRUE (NOT SKIPPED))", parameter);
+ } else {
+ ip += parameter;
+ debug(9, "CP_SKIPONFALSE: %d (IS FALSE (SKIPPED))", parameter);
+ }
+ break;
+ case CP_SKIPALWAYS:
+ // skip a block
+ Read32ipLeaveip(parameter);
+ ip += parameter;
+ debug(9, "CP_SKIPALWAYS: %d", parameter);
+ break;
+ case CP_SWITCH:
+ // switch
+ value = pop();
+ Read32ip(caseCount);
+
+ // Search the cases
+ foundCase = false;
+ for (i = 0; i < caseCount && !foundCase; i++) {
+ if (value == (int32)READ_LE_UINT32(code + ip)) {
+ // We have found the case, so lets
+ // jump to it
+ foundCase = true;
+ ip += READ_LE_UINT32(code + ip + 4);
+ } else
+ ip += 4 * 2;
+ }
+
+ // If we found no matching case then use the default
+
+ if (!foundCase)
+ ip += READ_LE_UINT32(code + ip);
+
+ debug(9, "CP_SWITCH: [SORRY, NO DEBUG INFO]");
+ break;
+ case CP_SAVE_MCODE_START:
+ // Save the start position on an mcode instruction in
+ // case we need to restart it again
+ savedStartOfMcode = ip - 1;
+ debug(9, "CP_SAVE_MCODE_START");
+ break;
+ case CP_CALL_MCODE:
+ // Call an mcode routine
+ Read16ip(parameter);
+ assert(parameter < ARRAYSIZE(opcodes));
+ // amount to adjust stack by (no of parameters)
+ Read8ip(value);
+ debug(9, "CP_CALL_MCODE: '%s', %d", opcodes[parameter].desc, value);
+ stackPtr -= value;
+ assert(stackPtr >= 0);
+ retVal = (this->*opcodes[parameter].proc)(&stack[stackPtr]);
+
+ switch (retVal & 7) {
+ case IR_STOP:
+ // Quit out for a cycle
+ WRITE_LE_UINT32(offsetPtr, ip);
+ return 0;
+ case IR_CONT:
+ // Continue as normal
+ break;
+ case IR_TERMINATE:
+ // Return without updating the offset
+ return 2;
+ case IR_REPEAT:
+ // Return setting offset to start of this
+ // function call
+ WRITE_LE_UINT32(offsetPtr, savedStartOfMcode);
+ return 0;
+ case IR_GOSUB:
+ // that's really neat
+ WRITE_LE_UINT32(offsetPtr, ip);
+ return 2;
+ default:
+ error("Bad return code (%d) from '%s'", retVal & 7, opcodes[parameter].desc);
+ }
+ parameterReturnedFromMcodeFunction = retVal >> 3;
+ break;
+ case CP_JUMP_ON_RETURNED:
+ // Jump to a part of the script depending on
+ // the return value from an mcode routine
+
+ // Get the maximum value
+ Read8ip(parameter);
+ debug(9, "CP_JUMP_ON_RETURNED: %d => %d",
+ parameterReturnedFromMcodeFunction,
+ READ_LE_UINT32(code + ip + parameterReturnedFromMcodeFunction * 4));
+ ip += READ_LE_UINT32(code + ip + parameterReturnedFromMcodeFunction * 4);
+ break;
+
+ // Operators
+
+ case OP_ISEQUAL:
+ b = pop();
+ a = pop();
+ push(a == b);
+ debug(9, "OP_ISEQUAL: RESULT = %d", a == b);
+ break;
+ case OP_NOTEQUAL:
+ b = pop();
+ a = pop();
+ push(a != b);
+ debug(9, "OP_NOTEQUAL: RESULT = %d", a != b);
+ break;
+ case OP_GTTHAN:
+ b = pop();
+ a = pop();
+ push(a > b);
+ debug(9, "OP_GTTHAN: RESULT = %d", a > b);
+ break;
+ case OP_LSTHAN:
+ b = pop();
+ a = pop();
+ push(a < b);
+ debug(9, "OP_LSTHAN: RESULT = %d", a < b);
+ break;
+ case OP_GTTHANE:
+ b = pop();
+ a = pop();
+ push(a >= b);
+ debug(9, "OP_GTTHANE: RESULT = %d", a >= b);
+ break;
+ case OP_LSTHANE:
+ b = pop();
+ a = pop();
+ push(a <= b);
+ debug(9, "OP_LSTHANE: RESULT = %d", a <= b);
+ break;
+ case OP_PLUS:
+ b = pop();
+ a = pop();
+ push(a + b);
+ debug(9, "OP_PLUS: RESULT = %d", a + b);
+ break;
+ case OP_MINUS:
+ b = pop();
+ a = pop();
+ push(a - b);
+ debug(9, "OP_MINUS: RESULT = %d", a - b);
+ break;
+ case OP_TIMES:
+ b = pop();
+ a = pop();
+ push(a * b);
+ debug(9, "OP_TIMES: RESULT = %d", a * b);
+ break;
+ case OP_DIVIDE:
+ b = pop();
+ a = pop();
+ push(a / b);
+ debug(9, "OP_DIVIDE: RESULT = %d", a / b);
+ break;
+ case OP_ANDAND:
+ b = pop();
+ a = pop();
+ push(a && b);
+ debug(9, "OP_ANDAND: RESULT = %d", a && b);
+ break;
+ case OP_OROR:
+ b = pop();
+ a = pop();
+ push(a || b);
+ debug(9, "OP_OROR: RESULT = %d", a || b);
+ break;
+
+ // Debugging opcodes, I think
+
+ case CP_DEBUGON:
+ debug(9, "CP_DEBUGON");
+ break;
+ case CP_DEBUGOFF:
+ debug(9, "CP_DEBUGOFF");
+ break;
+ case CP_TEMP_TEXT_PROCESS:
+ Read32ip(parameter);
+ debug(9, "CP_TEMP_TEXT_PROCESS: %d", parameter);
+ break;
+ default:
+ error("Invalid script command %d", curCommand);
+ return 3;
+ }
+ }
+
+ return 1;
+}
+
+} // End of namespace Sword2
diff --git a/engines/sword2/interpreter.h b/engines/sword2/interpreter.h
new file mode 100644
index 0000000000..365f818a98
--- /dev/null
+++ b/engines/sword2/interpreter.h
@@ -0,0 +1,96 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#ifndef _INTERPRETER
+#define _INTERPRETER
+
+namespace Sword2 {
+
+// Interpreter return codes
+
+enum {
+ IR_STOP = 0, // Quit for a cycle
+ IR_CONT = 1, // Continue as normal
+ IR_TERMINATE = 2, // Return without updating the offset
+ IR_REPEAT = 3, // Return; offset at start of function call
+ IR_GOSUB = 4 // Return with updated offset
+};
+
+// Get parameter fix so that the playstation version can handle words not on
+// word boundaries
+
+#define Read8ip(var) { var = code[ip]; ip++; }
+#define Read16ip(var) { var = (int16)READ_LE_UINT16(code + ip); ip += 2; }
+#define Read32ip(var) { var = (int32)READ_LE_UINT32(code + ip); ip += 4; }
+#define Read32ipLeaveip(var) { var = (int32)READ_LE_UINT32(code + ip); }
+
+enum {
+ // Compiled tokens
+
+ CP_END_SCRIPT = 0,
+ CP_PUSH_LOCAL_VAR32 = 1, // Push a local variable on to the stack
+ CP_PUSH_GLOBAL_VAR32 = 2, // Push a global variable
+ CP_POP_LOCAL_VAR32 = 3, // Pop a local variable from the stack
+ CP_CALL_MCODE = 4, // Call a machine code function
+ CP_PUSH_LOCAL_ADDR = 5, // Push the address of a local variable
+ CP_PUSH_INT32 = 6, // Adjust the stack after calling an fn function
+ CP_SKIPONFALSE = 7, // Skip if the bottom value on the stack is false
+ CP_SKIPALWAYS = 8, // Skip a block of code
+ CP_SWITCH = 9, // Switch on last stack value
+ CP_ADDNPOP_LOCAL_VAR32 = 10, // Add to a local varible
+ CP_SUBNPOP_LOCAL_VAR32 = 11, // Subtract from a local variable
+ CP_SKIPONTRUE = 12, // Skip if the bottom value on the stack is true
+ CP_POP_GLOBAL_VAR32 = 13, // Pop a global variable
+ CP_ADDNPOP_GLOBAL_VAR32 = 14, // Add to a global variable
+ CP_SUBNPOP_GLOBAL_VAR32 = 15, // Subtract from a global variable
+ CP_DEBUGON = 16, // Turn debugging on
+ CP_DEBUGOFF = 17, // Turn debugging off
+ CP_QUIT = 18, // Quit for a cycle
+ CP_TERMINATE = 19, // Quit script completely
+
+ // Operators
+
+ OP_ISEQUAL = 20, // '=='
+ OP_PLUS = 21, // '+'
+ OP_MINUS = 22, // '-'
+ OP_TIMES = 23, // '*'
+ OP_DIVIDE = 24, // '/'
+ OP_NOTEQUAL = 25, // '=='
+ OP_ANDAND = 26, // '&&'
+ OP_GTTHAN = 27, // '>'
+ OP_LSTHAN = 28, // '<'
+
+ // More tokens, mixed types
+
+ CP_JUMP_ON_RETURNED = 29, // Use table of jumps with value returned from fn_mcode
+ CP_TEMP_TEXT_PROCESS = 30, // A dummy text process command for me
+ CP_SAVE_MCODE_START = 31, // Save the mcode code start for restarting when necessary
+ CP_RESTART_SCRIPT = 32, // Start the script from the beginning
+ CP_PUSH_STRING = 33, // Push a pointer to a string on the stack
+ CP_PUSH_DEREFERENCED_STRUCTURE = 34, // Push the address of a structure thing
+ OP_GTTHANE = 35, // >=
+ OP_LSTHANE = 36, // <=
+ OP_OROR = 37 // || or OR
+};
+
+} // End of namespace Sword2
+
+#endif
diff --git a/engines/sword2/layers.cpp b/engines/sword2/layers.cpp
new file mode 100644
index 0000000000..0b59b5a9b1
--- /dev/null
+++ b/engines/sword2/layers.cpp
@@ -0,0 +1,191 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+// high level layer initialising
+
+// the system supports:
+// 1 optional background parallax layer
+// 1 not optional normal backdrop layer
+// 3 normal sorted layers
+// up to 2 foreground parallax layers
+
+#include "common/stdafx.h"
+
+#include "sword2/sword2.h"
+#include "sword2/defs.h"
+#include "sword2/logic.h"
+#include "sword2/mouse.h"
+#include "sword2/resman.h"
+#include "sword2/sound.h"
+
+namespace Sword2 {
+
+/**
+ * This function is called when entering a new room.
+ * @param res resource id of the normal background layer
+ * @param new_palette 1 for new palette, otherwise 0
+ */
+
+void Screen::initBackground(int32 res, int32 new_palette) {
+ byte buf[NAME_LEN];
+ int i;
+
+ assert(res);
+
+ _vm->_sound->clearFxQueue();
+ waitForFade();
+
+ debug(1, "CHANGED TO LOCATION \"%s\"", _vm->_resman->fetchName(res, buf));
+
+ // if last screen was using a shading mask (see below)
+ if (_thisScreen.mask_flag) {
+ if (closeLightMask() != RD_OK)
+ error("Could not close light mask");
+ }
+
+ // Close the previous screen, if one is open
+ if (_thisScreen.background_layer_id)
+ closeBackgroundLayer();
+
+ _thisScreen.background_layer_id = res;
+ _thisScreen.new_palette = new_palette;
+
+ // ok, now read the resource and pull out all the normal sort layer
+ // info/and set them up at the beginning of the sort list - why do it
+ // each cycle
+
+ byte *file = _vm->_resman->openResource(_thisScreen.background_layer_id);
+ ScreenHeader screen_head;
+
+ screen_head.read(_vm->fetchScreenHeader(file));
+
+ // set number of special sort layers
+ _thisScreen.number_of_layers = screen_head.noLayers;
+ _thisScreen.screen_wide = screen_head.width;
+ _thisScreen.screen_deep = screen_head.height;
+
+ debug(2, "layers=%d width=%d depth=%d", screen_head.noLayers, screen_head.width, screen_head.height);
+
+ // initialise the driver back buffer
+ setLocationMetrics(screen_head.width, screen_head.height);
+
+ for (i = 0; i < screen_head.noLayers; i++) {
+ debug(3, "init layer %d", i);
+
+ LayerHeader layer;
+
+ layer.read(_vm->fetchLayerHeader(file, i));
+
+ // Add the layer to the sort list. We only provide just enough
+ // information so that it's clear that it's a layer, and where
+ // to sort it in relation to other things in the list.
+
+ _sortList[i].layer_number = i + 1;
+ _sortList[i].sort_y = layer.y + layer.height;
+ }
+
+ // reset scroll offsets
+ _thisScreen.scroll_offset_x = 0;
+ _thisScreen.scroll_offset_y = 0;
+
+ if (screen_head.width > _screenWide || screen_head.height > _screenDeep) {
+ // The layer is larger than the physical screen. Switch on
+ // scrolling. (2 means first time on screen)
+ _thisScreen.scroll_flag = 2;
+
+ // Note: if we've already set the player up then we could do
+ // the initial scroll set here
+
+ // Calculate the maximum scroll offsets to prevent scrolling
+ // off the edge. The minimum offsets are both 0.
+
+ _thisScreen.max_scroll_offset_x = screen_head.width - _screenWide;
+ _thisScreen.max_scroll_offset_y = screen_head.height - (_screenDeep - (MENUDEEP * 2));
+ } else {
+ // The later fits on the phyiscal screen. Switch off scrolling.
+ _thisScreen.scroll_flag = 0;
+ }
+
+ resetRenderEngine();
+
+ // These are the physical screen coords where the system will try to
+ // maintain George's actual feet coords.
+
+ _thisScreen.feet_x = 320;
+ _thisScreen.feet_y = 340;
+
+ // shading mask
+
+ MultiScreenHeader screenLayerTable;
+
+ screenLayerTable.read(file + ResHeader::size());
+
+ if (screenLayerTable.maskOffset) {
+ SpriteInfo spriteInfo;
+
+ spriteInfo.x = 0;
+ spriteInfo.y = 0;
+ spriteInfo.w = screen_head.width;
+ spriteInfo.h = screen_head.height;
+ spriteInfo.scale = 0;
+ spriteInfo.scaledWidth = 0;
+ spriteInfo.scaledHeight = 0;
+ spriteInfo.type = 0;
+ spriteInfo.blend = 0;
+ spriteInfo.data = _vm->fetchShadingMask(file);
+ spriteInfo.colourTable = 0;
+
+ if (openLightMask(&spriteInfo) != RD_OK)
+ error("Could not open light mask");
+
+ // so we know to close it later! (see above)
+ _thisScreen.mask_flag = true;
+ } else {
+ // no need to close a mask later
+ _thisScreen.mask_flag = false;
+ }
+
+ // Background parallax layers
+
+ for (i = 0; i < 2; i++) {
+ if (screenLayerTable.bg_parallax[i])
+ initialiseBackgroundLayer(_vm->fetchBackgroundParallaxLayer(file, i));
+ else
+ initialiseBackgroundLayer(NULL);
+ }
+
+ // Normal backround layer
+
+ initialiseBackgroundLayer(_vm->fetchBackgroundLayer(file));
+
+ // Foreground parallax layers
+
+ for (i = 0; i < 2; i++) {
+ if (screenLayerTable.fg_parallax[i])
+ initialiseBackgroundLayer(_vm->fetchForegroundParallaxLayer(file, i));
+ else
+ initialiseBackgroundLayer(NULL);
+ }
+
+ _vm->_resman->closeResource(_thisScreen.background_layer_id);
+}
+
+} // End of namespace Sword2
diff --git a/engines/sword2/layers.h b/engines/sword2/layers.h
new file mode 100644
index 0000000000..88aff933b3
--- /dev/null
+++ b/engines/sword2/layers.h
@@ -0,0 +1,29 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#ifndef _LAYERS
+#define _LAYERS
+
+namespace Sword2 {
+
+} // End of namespace Sword2
+
+#endif
diff --git a/engines/sword2/logic.cpp b/engines/sword2/logic.cpp
new file mode 100644
index 0000000000..c26d5615b9
--- /dev/null
+++ b/engines/sword2/logic.cpp
@@ -0,0 +1,267 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "common/stdafx.h"
+#include "sword2/sword2.h"
+#include "sword2/defs.h"
+#include "sword2/logic.h"
+#include "sword2/resman.h"
+#include "sword2/router.h"
+#include "sword2/sound.h"
+
+namespace Sword2 {
+
+Logic::Logic(Sword2Engine *vm) :
+ _vm(vm), _kills(0), _currentRunList(0), _smackerLeadIn(0),
+ _smackerLeadOut(0), _sequenceTextLines(0), _speechTime(0), _animId(0),
+ _speechAnimType(0), _leftClickDelay(0), _rightClickDelay(0),
+ _officialTextNumber(0), _speechTextBlocNo(0) {
+
+ _scriptVars = NULL;
+ memset(_eventList, 0, sizeof(_eventList));
+ memset(_syncList, 0, sizeof(_syncList));
+ _router = new Router(_vm);
+}
+
+Logic::~Logic() {
+ delete _router;
+}
+
+/**
+ * Do one cycle of the current session.
+ */
+
+int Logic::processSession() {
+ // might change during the session, so take a copy here
+ uint32 run_list = _currentRunList;
+
+ _pc = 0; // first object in list
+
+ // by minusing the pc we can cause an immediate cessation of logic
+ // processing on the current list
+
+ while (_pc != 0xffffffff) {
+ byte *game_object_list, *head, *raw_script_ad, *raw_data_ad;
+ uint32 level, ret, script, id;
+
+ game_object_list = _vm->_resman->openResource(run_list) + ResHeader::size();
+
+ assert(_vm->_resman->fetchType(run_list) == RUN_LIST);
+
+ // read the next id
+ id = READ_LE_UINT32(game_object_list + 4 * _pc);
+ _pc++;
+
+ writeVar(ID, id);
+
+ _vm->_resman->closeResource(run_list);
+
+ if (!id) {
+ // End of list - end the session naturally
+ return 0;
+ }
+
+ assert(_vm->_resman->fetchType(id) == GAME_OBJECT);
+
+ head = _vm->_resman->openResource(id);
+ _curObjectHub.setAddress(head + ResHeader::size());
+
+ level = _curObjectHub.getLogicLevel();
+
+ debug(5, "Level %d id(%d) pc(%d)",
+ level,
+ _curObjectHub.getScriptId(level),
+ _curObjectHub.getScriptPc(level));
+
+ // Do the logic for this object. We keep going until a function
+ // says to stop - remember, system operations are run via
+ // function calls to drivers now.
+
+ do {
+ // There is a distinction between running one of our
+ // own scripts and that of another object.
+
+ level = _curObjectHub.getLogicLevel();
+ script = _curObjectHub.getScriptId(level);
+
+ if (script / SIZE == readVar(ID)) {
+ // It's our own script
+
+ debug(5, "Run script %d pc=%d",
+ script / SIZE,
+ _curObjectHub.getScriptPc(level));
+
+ // This is the script data. Script and data
+ // object are the same.
+
+ raw_script_ad = head;
+
+ ret = runScript2(raw_script_ad, raw_script_ad, _curObjectHub.getScriptPcPtr(level));
+ } else {
+ // We're running the script of another game
+ // object - get our data object address.
+
+ uint8 type = _vm->_resman->fetchType(script / SIZE);
+
+ assert(type == GAME_OBJECT || type == SCREEN_MANAGER);
+
+ raw_script_ad = _vm->_resman->openResource(script / SIZE);
+ raw_data_ad = head;
+
+ ret = runScript2(raw_script_ad, raw_data_ad, _curObjectHub.getScriptPcPtr(level));
+
+ _vm->_resman->closeResource(script / SIZE);
+
+ // reset to us for service script
+ raw_script_ad = raw_data_ad;
+ }
+
+ if (ret == 1) {
+ level = _curObjectHub.getLogicLevel();
+
+ // The script finished - drop down a level
+ if (level) {
+ _curObjectHub.setLogicLevel(level - 1);
+ } else {
+ // Hmmm, level 0 terminated :-| Let's
+ // be different this time and simply
+ // let it restart next go :-)
+
+ // Note that this really does happen a
+ // lot, so don't make it a warning.
+
+ debug(5, "object %d script 0 terminated!", id);
+
+ // reset to rerun, drop out for a cycle
+ _curObjectHub.setScriptPc(level, _curObjectHub.getScriptId(level) & 0xffff);
+ ret = 0;
+ }
+ } else if (ret > 2) {
+ error("processSession: illegal script return type %d", ret);
+ }
+
+ // if ret == 2 then we simply go around again - a new
+ // script or subroutine will kick in and run
+
+ // keep processing scripts until 0 for quit is returned
+ } while (ret);
+
+ // Any post logic system requests to go here
+
+ // Clear any syncs that were waiting for this character - it
+ // has used them or now looses them
+
+ clearSyncs(readVar(ID));
+
+ if (_pc != 0xffffffff) {
+ // The session is still valid so run the graphics/mouse
+ // service script
+ runScript(raw_script_ad, raw_script_ad, 0);
+ }
+
+ // and that's it so close the object resource
+
+ _vm->_resman->closeResource(readVar(ID));
+ }
+
+ // Leaving a room so remove all ids that must reboot correctly. Then
+ // restart the loop.
+
+ for (uint32 i = 0; i < _kills; i++)
+ _vm->_resman->remove(_objectKillList[i]);
+
+ resetKillList();
+ return 1;
+}
+
+/**
+ * Bring an immediate halt to the session and cause a new one to start without
+ * a screen update.
+ */
+
+void Logic::expressChangeSession(uint32 sesh_id) {
+ // Set new session and force the old one to quit.
+ _currentRunList = sesh_id;
+ _pc = 0xffffffff;
+
+ // Reset now in case we double-clicked an exit prior to changing screen
+ writeVar(EXIT_FADING, 0);
+
+ // We're trashing the list - presumably to change room. In theory,
+ // sync waiting in the list could be left behind and never removed -
+ // so we trash the lot
+ memset(_syncList, 0, sizeof(_syncList));
+
+ // Various clean-ups
+ _router->clearWalkGridList();
+ _vm->_sound->clearFxQueue();
+ _router->freeAllRouteMem();
+}
+
+/**
+ * @return The private _currentRunList variable.
+ */
+
+uint32 Logic::getRunList() {
+ return _currentRunList;
+}
+
+/**
+ * Move the current object up a level. Called by fnGosub command. Remember:
+ * only the logic object has access to _curObjectHub.
+ */
+
+void Logic::logicUp(uint32 new_script) {
+ debug(5, "new pc = %d", new_script & 0xffff);
+
+ // going up a level - and we'll keep going this cycle
+ _curObjectHub.setLogicLevel(_curObjectHub.getLogicLevel() + 1);
+
+ assert(_curObjectHub.getLogicLevel() < 3); // Can be 0, 1, 2
+ logicReplace(new_script);
+}
+
+/**
+ * Force the level to one.
+ */
+
+void Logic::logicOne(uint32 new_script) {
+ _curObjectHub.setLogicLevel(1);
+ logicReplace(new_script);
+}
+
+/**
+ * Change current logic. Script must quit with a TERMINATE directive, which
+ * does not write to &pc
+ */
+
+void Logic::logicReplace(uint32 new_script) {
+ uint32 level = _curObjectHub.getLogicLevel();
+
+ _curObjectHub.setScriptId(level, new_script);
+ _curObjectHub.setScriptPc(level, new_script & 0xffff);
+}
+
+void Logic::resetKillList() {
+ _kills = 0;
+}
+
+} // End of namespace Sword2
diff --git a/engines/sword2/logic.h b/engines/sword2/logic.h
new file mode 100644
index 0000000000..9e264f603c
--- /dev/null
+++ b/engines/sword2/logic.h
@@ -0,0 +1,316 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+// logic management
+
+#ifndef _LOGIC
+#define _LOGIC
+
+#include "sword2/memory.h"
+
+namespace Sword2 {
+
+struct MovieTextObject;
+
+#define MAX_events 10
+
+#define TREE_SIZE 3
+
+// This must allow for the largest number of objects in a screen
+#define OBJECT_KILL_LIST_SIZE 50
+
+#define MAX_SEQUENCE_TEXT_LINES 15
+
+class Sword2Engine;
+class Router;
+
+struct EventUnit {
+ uint32 id;
+ uint32 interact_id;
+};
+
+class Logic {
+private:
+ Sword2Engine *_vm;
+
+ inline byte *decodePtr(int32 n) {
+ return _vm->_memory->decodePtr(n);
+ }
+
+ uint32 _objectKillList[OBJECT_KILL_LIST_SIZE];
+
+ // keeps note of no. of objects in the kill list
+ uint32 _kills;
+
+ // denotes the res id of the game-object-list in current use
+ uint32 _currentRunList;
+
+ //pc during logic loop
+ uint32 _pc;
+
+ // each object has one of these tacked onto the beginning
+ ObjectHub _curObjectHub;
+
+ EventUnit _eventList[MAX_events];
+
+ // Resource id of the wav to use as lead-in/lead-out from smacker
+ uint32 _smackerLeadIn;
+ uint32 _smackerLeadOut;
+
+ // keeps count of number of text lines to disaply during the sequence
+ uint32 _sequenceTextLines;
+
+ // FOR TEXT LINES IN SEQUENCE PLAYER
+
+ struct SequenceTextInfo {
+ uint32 textNumber;
+ uint16 startFrame;
+ uint16 endFrame;
+ byte *text_mem;
+ uint32 speechBufferSize;
+ uint16 *speech_mem;
+ };
+
+ SequenceTextInfo _sequenceTextList[MAX_SEQUENCE_TEXT_LINES];
+
+ void createSequenceSpeech(MovieTextObject *sequenceText[]);
+ void clearSequenceSpeech(MovieTextObject *sequenceText[]);
+
+ // when not playing a wav we calculate the speech time based upon
+ // length of ascii
+
+ uint32 _speechTime;
+
+ uint32 _animId;
+
+ // 0 lip synced and repeating - 1 normal once through
+ uint32 _speechAnimType;
+
+ uint32 _leftClickDelay; // click-delay for LEFT mouse button
+ uint32 _rightClickDelay; // click-delay for RIGHT mouse button
+
+ // calculated by locateTalker() for use in speech-panning & text-sprite
+ // positioning
+
+ int16 _textX, _textY;
+
+ void locateTalker(int32 *params);
+ void formText(int32 *params);
+ bool wantSpeechForLine(uint32 wavId);
+
+ // Set by fnPassMega()
+ byte _engineMega[56];
+
+public:
+ Logic(Sword2Engine *vm);
+ ~Logic();
+
+ EventUnit *getEventList() { return _eventList; }
+ byte *getEngineMega() { return _engineMega; }
+
+ byte _saveLogic[8];
+ byte _saveGraphic[12];
+ byte _saveMega[56];
+
+ // Point to the global variable data
+ byte *_scriptVars;
+
+ // "TEXT" - current official text line number - will match the wav
+ // filenames
+
+ int16 _officialTextNumber;
+
+ // so speech text cleared when running a new start-script
+ uint32 _speechTextBlocNo;
+
+ uint32 readVar(int n) {
+ return READ_LE_UINT32(_scriptVars + 4 * n);
+ }
+
+ void writeVar(int n, uint32 value) {
+ WRITE_LE_UINT32(_scriptVars + 4 * n, value);
+ }
+
+ int runResScript(uint32 scriptRes, uint32 offset);
+ int runResObjScript(uint32 scriptRes, uint32 objRes, uint32 offset);
+ int runScript(byte *scriptData, byte *objectData, uint32 offset);
+ int runScript2(byte *scriptData, byte *objectData, byte *offset);
+
+ void sendEvent(uint32 id, uint32 interact_id);
+ void setPlayerActionEvent(uint32 id, uint32 interact_id);
+ void startEvent();
+ int checkEventWaiting();
+ void clearEvent(uint32 id);
+ void killAllIdsEvents(uint32 id);
+
+ uint32 countEvents();
+
+ struct SyncUnit {
+ uint32 id;
+ uint32 sync;
+ };
+
+ // There won't be many, will there? Probably 2 at most i reckon
+ SyncUnit _syncList[10];
+
+ void clearSyncs(uint32 id);
+ void sendSync(uint32 id, uint32 sync);
+ int getSync();
+
+ Router *_router;
+
+ int32 fnTestFunction(int32 *params);
+ int32 fnTestFlags(int32 *params);
+ int32 fnRegisterStartPoint(int32 *params);
+ int32 fnInitBackground(int32 *params);
+ int32 fnSetSession(int32 *params);
+ int32 fnBackSprite(int32 *params);
+ int32 fnSortSprite(int32 *params);
+ int32 fnForeSprite(int32 *params);
+ int32 fnRegisterMouse(int32 *params);
+ int32 fnAnim(int32 *params);
+ int32 fnRandom(int32 *params);
+ int32 fnPreLoad(int32 *params);
+ int32 fnAddSubject(int32 *params);
+ int32 fnInteract(int32 *params);
+ int32 fnChoose(int32 *params);
+ int32 fnWalk(int32 *params);
+ int32 fnWalkToAnim(int32 *params);
+ int32 fnTurn(int32 *params);
+ int32 fnStandAt(int32 *params);
+ int32 fnStand(int32 *params);
+ int32 fnStandAfterAnim(int32 *params);
+ int32 fnPause(int32 *params);
+ int32 fnMegaTableAnim(int32 *params);
+ int32 fnAddMenuObject(int32 *params);
+ int32 fnStartConversation(int32 *params);
+ int32 fnEndConversation(int32 *params);
+ int32 fnSetFrame(int32 *params);
+ int32 fnRandomPause(int32 *params);
+ int32 fnRegisterFrame(int32 *params);
+ int32 fnNoSprite(int32 *params);
+ int32 fnSendSync(int32 *params);
+ int32 fnUpdatePlayerStats(int32 *params);
+ int32 fnPassGraph(int32 *params);
+ int32 fnInitFloorMouse(int32 *params);
+ int32 fnPassMega(int32 *params);
+ int32 fnFaceXY(int32 *params);
+ int32 fnEndSession(int32 *params);
+ int32 fnNoHuman(int32 *params);
+ int32 fnAddHuman(int32 *params);
+ int32 fnWeWait(int32 *params);
+ int32 fnTheyDoWeWait(int32 *params);
+ int32 fnTheyDo(int32 *params);
+ int32 fnWalkToTalkToMega(int32 *params);
+ int32 fnFadeDown(int32 *params);
+ int32 fnISpeak(int32 *params);
+ int32 fnTotalRestart(int32 *params);
+ int32 fnSetWalkGrid(int32 *params);
+ int32 fnSpeechProcess(int32 *params);
+ int32 fnSetScaling(int32 *params);
+ int32 fnStartEvent(int32 *params);
+ int32 fnCheckEventWaiting(int32 *params);
+ int32 fnRequestSpeech(int32 *params);
+ int32 fnGosub(int32 *params);
+ int32 fnTimedWait(int32 *params);
+ int32 fnPlayFx(int32 *params);
+ int32 fnStopFx(int32 *params);
+ int32 fnPlayMusic(int32 *params);
+ int32 fnStopMusic(int32 *params);
+ int32 fnSetValue(int32 *params);
+ int32 fnNewScript(int32 *params);
+ int32 fnGetSync(int32 *params);
+ int32 fnWaitSync(int32 *params);
+ int32 fnRegisterWalkGrid(int32 *params);
+ int32 fnReverseMegaTableAnim(int32 *params);
+ int32 fnReverseAnim(int32 *params);
+ int32 fnAddToKillList(int32 *params);
+ int32 fnSetStandbyCoords(int32 *params);
+ int32 fnBackPar0Sprite(int32 *params);
+ int32 fnBackPar1Sprite(int32 *params);
+ int32 fnForePar0Sprite(int32 *params);
+ int32 fnForePar1Sprite(int32 *params);
+ int32 fnSetPlayerActionEvent(int32 *params);
+ int32 fnSetScrollCoordinate(int32 *params);
+ int32 fnStandAtAnim(int32 *params);
+ int32 fnSetScrollLeftMouse(int32 *params);
+ int32 fnSetScrollRightMouse(int32 *params);
+ int32 fnColour(int32 *params);
+ int32 fnFlash(int32 *params);
+ int32 fnPreFetch(int32 *params);
+ int32 fnGetPlayerSaveData(int32 *params);
+ int32 fnPassPlayerSaveData(int32 *params);
+ int32 fnSendEvent(int32 *params);
+ int32 fnAddWalkGrid(int32 *params);
+ int32 fnRemoveWalkGrid(int32 *params);
+ int32 fnCheckForEvent(int32 *params);
+ int32 fnPauseForEvent(int32 *params);
+ int32 fnClearEvent(int32 *params);
+ int32 fnFaceMega(int32 *params);
+ int32 fnPlaySequence(int32 *params);
+ int32 fnShadedSprite(int32 *params);
+ int32 fnUnshadedSprite(int32 *params);
+ int32 fnFadeUp(int32 *params);
+ int32 fnDisplayMsg(int32 *params);
+ int32 fnSetObjectHeld(int32 *params);
+ int32 fnAddSequenceText(int32 *params);
+ int32 fnResetGlobals(int32 *params);
+ int32 fnSetPalette(int32 *params);
+ int32 fnRegisterPointerText(int32 *params);
+ int32 fnFetchWait(int32 *params);
+ int32 fnRelease(int32 *params);
+ int32 fnPrepareMusic(int32 *params);
+ int32 fnSoundFetch(int32 *params);
+ int32 fnSmackerLeadIn(int32 *params);
+ int32 fnSmackerLeadOut(int32 *params);
+ int32 fnStopAllFx(int32 *params);
+ int32 fnCheckPlayerActivity(int32 *params);
+ int32 fnResetPlayerActivityDelay(int32 *params);
+ int32 fnCheckMusicPlaying(int32 *params);
+ int32 fnPlayCredits(int32 *params);
+ int32 fnSetScrollSpeedNormal(int32 *params);
+ int32 fnSetScrollSpeedSlow(int32 *params);
+ int32 fnRemoveChooser(int32 *params);
+ int32 fnSetFxVolAndPan(int32 *params);
+ int32 fnSetFxVol(int32 *params);
+ int32 fnRestoreGame(int32 *params);
+ int32 fnRefreshInventory(int32 *params);
+ int32 fnChangeShadows(int32 *params);
+
+ // do one cycle of the current session
+ int processSession();
+
+ // cause the logic loop to terminate and drop out
+ void expressChangeSession(uint32 sesh_id);
+
+ uint32 getRunList();
+
+ // setup script_id and script_pc in _curObjectHub - called by fnGosub()
+ void logicUp(uint32 new_script);
+
+ void logicReplace(uint32 new_script);
+ void logicOne(uint32 new_script);
+ void resetKillList();
+};
+
+} // End of namespace Sword2
+
+#endif
diff --git a/engines/sword2/maketext.cpp b/engines/sword2/maketext.cpp
new file mode 100644
index 0000000000..5edaf5e41e
--- /dev/null
+++ b/engines/sword2/maketext.cpp
@@ -0,0 +1,579 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+// MAKETEXT - Constructs a single-frame text sprite: returns a handle to a
+// FLOATING memory block containing the sprite, given a
+// null-terminated string, max width allowed, pen colour and
+// pointer to required character set.
+//
+// NB 1) The routine does not create a standard file header or
+// an anim header for the text sprite - the data simply begins
+// with the frame header.
+//
+// NB 2) If pen colour is zero, it copies the characters into
+// the sprite without remapping the colours.
+// ie. It can handle both the standard 2-colour font for speech
+// and any multicoloured fonts for control panels, etc.
+//
+// Based on textsprt.c as used for Broken Sword 1, but updated
+// for new system by JEL on 9oct96 and updated again (for font
+// as a resource) on 5dec96.
+
+#include "common/stdafx.h"
+#include "common/system.h"
+#include "sword2/sword2.h"
+#include "sword2/defs.h"
+#include "sword2/logic.h"
+#include "sword2/maketext.h"
+#include "sword2/resman.h"
+
+namespace Sword2 {
+
+#define MAX_LINES 30 // max character lines in output sprite
+
+#define BORDER_COL 200 // source colour for character border (only
+ // needed for remapping colours)
+#define LETTER_COL 193 // source colour for bulk of character ( " )
+#define SPACE ' '
+#define FIRST_CHAR SPACE // first character in character set
+#define LAST_CHAR 255 // last character in character set
+#define DUD 64 // the first "chequered flag" (dud) symbol in
+ // our character set is in the '@' position
+
+/**
+ * This function creates a new text sprite. The sprite data contains a
+ * FrameHeader, but not a standard file header.
+ *
+ * @param sentence pointer to a null-terminated string
+ * @param maxWidth the maximum allowed text sprite width in pixels
+ * @param pen the text colour, or zero to use the source colours
+ * @param fontRes the font resource id
+ * @param border the border colour; black by default
+ * @return a handle to a floating memory block containing the text sprite
+ * @note The sentence must contain no leading, trailing or extra spaces.
+ * Out-of-range characters in the string are replaced by a special
+ * error-signal character (chequered flag)
+ */
+
+byte *FontRenderer::makeTextSprite(byte *sentence, uint16 maxWidth, uint8 pen, uint32 fontRes, uint8 border) {
+ debug(5, "makeTextSprite(\"%s\", maxWidth=%u)", sentence, maxWidth);
+
+ _borderPen = border;
+
+ // Line- and character spacing are hard-wired, rather than being part
+ // of the resource.
+
+ if (fontRes == _vm->_speechFontId) {
+ _lineSpacing = -6;
+ _charSpacing = -3;
+ } else if (fontRes == CONSOLE_FONT_ID) {
+ _lineSpacing = 0;
+ _charSpacing = 1;
+ } else {
+ _lineSpacing = 0;
+ _charSpacing = 0;
+ }
+
+ // Allocate memory for array of lineInfo structures
+
+ byte *line = (byte *)malloc(MAX_LINES * sizeof(LineInfo));
+
+ // Get details of sentence breakdown into array of LineInfo structures
+ // and get the number of lines involved
+
+ uint16 noOfLines = analyseSentence(sentence, maxWidth, fontRes, (LineInfo *)line);
+
+ // Construct the sprite based on the info gathered - returns floating
+ // mem block
+
+ byte *textSprite = buildTextSprite(sentence, fontRes, pen, (LineInfo *)line, noOfLines);
+
+ free(line);
+ return textSprite;
+}
+
+uint16 FontRenderer::analyseSentence(byte *sentence, uint16 maxWidth, uint32 fontRes, LineInfo *line) {
+ // joinWidth = how much extra space is needed to append a word to a
+ // line. NB. SPACE requires TWICE the '_charSpacing' to join a word
+ // to line
+
+ uint16 joinWidth = charWidth(SPACE, fontRes) + 2 * _charSpacing;
+
+ uint16 lineNo = 0;
+ uint16 pos = 0;
+ bool firstWord = true;
+
+ byte ch;
+
+ do {
+ uint16 wordWidth = 0;
+ uint16 wordLength = 0;
+
+ // Calculate the width of the word.
+
+ ch = sentence[pos++];
+
+ while (ch && ch != SPACE) {
+ wordWidth += charWidth(ch, fontRes) + _charSpacing;
+ wordLength++;
+ ch = sentence[pos++];
+ }
+
+ // Don't include any character spacing at the end of the word.
+ wordWidth -= _charSpacing;
+
+ // 'ch' is now the SPACE or NULL following the word
+ // 'pos' indexes to the position following 'ch'
+
+ if (firstWord) {
+ // This is the first word on the line, so no separating
+ // space is needed.
+
+ line[0].width = wordWidth;
+ line[0].length = wordLength;
+ firstWord = false;
+ } else {
+ // See how much extra space this word will need to
+ // fit on current line (with a separating space
+ // character - also overlapped)
+
+ uint16 spaceNeeded = joinWidth + wordWidth;
+
+ if (line[lineNo].width + spaceNeeded <= maxWidth) {
+ // The word fits on this line.
+ line[lineNo].width += spaceNeeded;
+ line[lineNo].length += (1 + wordLength);
+ } else {
+ // The word spills over to the next line, i.e.
+ // no separating space.
+
+ lineNo++;
+
+ assert(lineNo < MAX_LINES);
+
+ line[lineNo].width = wordWidth;
+ line[lineNo].length = wordLength;
+ }
+ }
+ } while (ch);
+
+ return lineNo + 1;
+}
+
+/**
+ * This function creates a new text sprite in a movable memory block. It must
+ * be locked before use, i.e. lock, draw sprite, unlock/free. The sprite data
+ * contains a FrameHeader, but not a standard file header.
+ *
+ * @param sentence pointer to a null-terminated string
+ * @param fontRes the font resource id
+ * @param pen the text colour, or zero to use the source colours
+ * @param line array of LineInfo structures, created by analyseSentence()
+ * @param noOfLines the number of lines, i.e. the number of elements in 'line'
+ * @return a handle to a floating memory block containing the text sprite
+ * @note The sentence must contain no leading, trailing or extra spaces.
+ * Out-of-range characters in the string are replaced by a special
+ * error-signal character (chequered flag)
+ */
+
+byte *FontRenderer::buildTextSprite(byte *sentence, uint32 fontRes, uint8 pen, LineInfo *line, uint16 noOfLines) {
+ uint16 i;
+
+ // Find the width of the widest line in the output text
+
+ uint16 spriteWidth = 0;
+
+ for (i = 0; i < noOfLines; i++)
+ if (line[i].width > spriteWidth)
+ spriteWidth = line[i].width;
+
+ // Find the total height of the text sprite: the total height of the
+ // text lines, plus the total height of the spacing between them.
+
+ uint16 char_height = charHeight(fontRes);
+ uint16 spriteHeight = char_height * noOfLines + _lineSpacing * (noOfLines - 1);
+
+ // Allocate memory for the text sprite
+
+ uint32 sizeOfSprite = spriteWidth * spriteHeight;
+ byte *textSprite = (byte *)malloc(FrameHeader::size() + sizeOfSprite);
+
+ // At this stage, textSprite points to an unmovable memory block. Set
+ // up the frame header.
+
+ FrameHeader frame_head;
+
+ frame_head.compSize = 0;
+ frame_head.width = spriteWidth;
+ frame_head.height = spriteHeight;
+
+ frame_head.write(textSprite);
+
+ debug(4, "Text sprite size: %ux%u", spriteWidth, spriteHeight);
+
+ // Clear the entire sprite to make it transparent.
+
+ byte *linePtr = textSprite + FrameHeader::size();
+ memset(linePtr, 0, sizeOfSprite);
+
+ byte *charSet = _vm->_resman->openResource(fontRes);
+
+ // Build the sprite, one line at a time
+
+ uint16 pos = 0;
+
+ for (i = 0; i < noOfLines; i++) {
+ // Center each line
+ byte *spritePtr = linePtr + (spriteWidth - line[i].width) / 2;
+
+ // copy the sprite for each character in this line to the
+ // text sprite and inc the sprite ptr by the character's
+ // width minus the 'overlap'
+
+ for (uint j = 0; j < line[i].length; j++) {
+ byte *charPtr = findChar(sentence[pos++], charSet);
+
+ frame_head.read(charPtr);
+
+ assert(frame_head.height == char_height);
+ copyChar(charPtr, spritePtr, spriteWidth, pen);
+ spritePtr += frame_head.width + _charSpacing;
+ }
+
+ // Skip space at end of last word in this line
+ pos++;
+
+ linePtr += (char_height + _lineSpacing) * spriteWidth;
+ }
+
+ _vm->_resman->closeResource(fontRes);
+
+ return textSprite;
+}
+
+/**
+ * @param ch the ASCII code of the character
+ * @param fontRes the font resource id
+ * @return the width of the character
+ */
+
+uint16 FontRenderer::charWidth(byte ch, uint32 fontRes) {
+ byte *charSet = _vm->_resman->openResource(fontRes);
+
+ FrameHeader frame_head;
+
+ frame_head.read(findChar(ch, charSet));
+ _vm->_resman->closeResource(fontRes);
+
+ return frame_head.width;
+}
+
+/**
+ * @param fontRes the font resource id
+ * @return the height of a character sprite
+ * @note All characters in a font are assumed to have the same height, so
+ * there is no need to specify which one to look at.
+ */
+
+// Returns the height of a character sprite, given the character's ASCII code
+// and a pointer to the start of the character set.
+
+uint16 FontRenderer::charHeight(uint32 fontRes) {
+ byte *charSet = _vm->_resman->openResource(fontRes);
+
+ FrameHeader frame_head;
+
+ frame_head.read(findChar(FIRST_CHAR, charSet));
+ _vm->_resman->closeResource(fontRes);
+
+ return frame_head.height;
+}
+
+/**
+ * @param ch the ASCII code of the character to find
+ * @param charSet pointer to the start of the character set
+ * @return pointer to the requested character or, if it's out of range, the
+ * 'dud' character (chequered flag)
+ */
+
+byte *FontRenderer::findChar(byte ch, byte *charSet) {
+ if (ch < FIRST_CHAR)
+ ch = DUD;
+ return _vm->fetchFrameHeader(charSet, ch - FIRST_CHAR);
+}
+
+/**
+ * Copies a character sprite to the sprite buffer.
+ * @param charPtr pointer to the character sprite
+ * @param spritePtr pointer to the sprite buffer
+ * @param spriteWidth the width of the character
+ * @param pen If zero, copy the data directly. Otherwise remap the
+ * sprite's colours from BORDER_COL to _borderPen and from
+ * LETTER_COL to pen.
+ */
+
+void FontRenderer::copyChar(byte *charPtr, byte *spritePtr, uint16 spriteWidth, uint8 pen) {
+ FrameHeader frame;
+
+ frame.read(charPtr);
+
+ byte *source = charPtr + FrameHeader::size();
+ byte *rowPtr = spritePtr;
+
+ for (uint i = 0; i < frame.height; i++) {
+ byte *dest = rowPtr;
+
+ if (pen) {
+ // Use the specified colours
+ for (uint j = 0; j < frame.width; j++) {
+ switch (*source++) {
+ case LETTER_COL:
+ *dest = pen;
+ break;
+ case BORDER_COL:
+ // Don't do a border pixel if there's
+ // already a bit of another character
+ // underneath (for overlapping!)
+ if (!*dest)
+ *dest = _borderPen;
+ break;
+ default:
+ // Do nothing if source pixel is zero,
+ // ie. transparent
+ break;
+ }
+ dest++;
+ }
+ } else {
+ // Pen is zero, so just copy character sprites
+ // directly into text sprite without remapping colours.
+ // Apparently overlapping is never considered here?
+ memcpy(dest, source, frame.width);
+ source += frame.width;
+ }
+ rowPtr += spriteWidth;
+ }
+}
+
+// Distance to keep speech text from edges of screen
+#define TEXT_MARGIN 12
+
+/**
+ * Creates a text bloc in the list and returns the bloc number. The list of
+ * blocs is read and blitted at render time. Choose alignment type
+ * RDSPR_DISPLAYALIGN or 0
+ */
+
+uint32 FontRenderer::buildNewBloc(byte *ascii, int16 x, int16 y, uint16 width, uint8 pen, uint32 type, uint32 fontRes, uint8 justification) {
+ uint32 i = 0;
+
+ while (i < MAX_text_blocs && _blocList[i].text_mem)
+ i++;
+
+ assert(i < MAX_text_blocs);
+
+ // Create and position the sprite
+
+ _blocList[i].text_mem = makeTextSprite(ascii, width, pen, fontRes);
+
+ // 'NO_JUSTIFICATION' means print sprite with top-left at (x,y)
+ // without margin checking - used for debug text
+
+ if (justification != NO_JUSTIFICATION) {
+ FrameHeader frame_head;
+
+ frame_head.read(_blocList[i].text_mem);
+
+ switch (justification) {
+ case POSITION_AT_CENTRE_OF_BASE:
+ // This one is always used for SPEECH TEXT; possibly
+ // also for pointer text
+ x -= (frame_head.width / 2);
+ y -= frame_head.height;
+ break;
+ case POSITION_AT_CENTRE_OF_TOP:
+ x -= (frame_head.width / 2);
+ break;
+ case POSITION_AT_LEFT_OF_TOP:
+ // The given coords are already correct for this!
+ break;
+ case POSITION_AT_RIGHT_OF_TOP:
+ x -= frame_head.width;
+ break;
+ case POSITION_AT_LEFT_OF_BASE:
+ y -= frame_head.height;
+ break;
+ case POSITION_AT_RIGHT_OF_BASE:
+ x -= frame_head.width;
+ y -= frame_head.height;
+ break;
+ case POSITION_AT_LEFT_OF_CENTRE:
+ y -= (frame_head.height / 2);
+ break;
+ case POSITION_AT_RIGHT_OF_CENTRE:
+ x -= frame_head.width;
+ y -= (frame_head.height) / 2;
+ break;
+ }
+
+ // Ensure text sprite is a few pixels inside the visible screen
+ // remember - it's RDSPR_DISPLAYALIGN
+
+ uint16 text_left_margin = TEXT_MARGIN;
+ uint16 text_right_margin = 640 - TEXT_MARGIN - frame_head.width;
+ uint16 text_top_margin = TEXT_MARGIN;
+ uint16 text_bottom_margin = 400 - TEXT_MARGIN - frame_head.height;
+
+ // Move if too far left or too far right
+
+ if (x < text_left_margin)
+ x = text_left_margin;
+ else if (x > text_right_margin)
+ x = text_right_margin;
+
+ // Move if too high or too low
+
+ if (y < text_top_margin)
+ y = text_top_margin;
+ else if (y > text_bottom_margin)
+ y = text_bottom_margin;
+ }
+
+ // The sprite is always uncompressed
+ _blocList[i].type = type | RDSPR_NOCOMPRESSION;
+
+ _blocList[i].x = x;
+ _blocList[i].y = y;
+
+ return i + 1;
+}
+
+/**
+ * Called by buildDisplay()
+ */
+
+void FontRenderer::printTextBlocs() {
+ for (uint i = 0; i < MAX_text_blocs; i++) {
+ if (_blocList[i].text_mem) {
+ FrameHeader frame_head;
+ SpriteInfo spriteInfo;
+
+ frame_head.read(_blocList[i].text_mem);
+
+ spriteInfo.x = _blocList[i].x;
+ spriteInfo.y = _blocList[i].y;
+ spriteInfo.w = frame_head.width;
+ spriteInfo.h = frame_head.height;
+ spriteInfo.scale = 0;
+ spriteInfo.scaledWidth = 0;
+ spriteInfo.scaledHeight = 0;
+ spriteInfo.type = _blocList[i].type;
+ spriteInfo.blend = 0;
+ spriteInfo.data = _blocList[i].text_mem + FrameHeader::size();
+ spriteInfo.colourTable = 0;
+
+ uint32 rv = _vm->_screen->drawSprite(&spriteInfo);
+ if (rv)
+ error("Driver Error %.8x in printTextBlocs", rv);
+ }
+ }
+}
+
+void FontRenderer::killTextBloc(uint32 bloc_number) {
+ bloc_number--;
+ free(_blocList[bloc_number].text_mem);
+ _blocList[bloc_number].text_mem = NULL;
+}
+
+// Resource 3258 contains text from location script for 152 (install, save &
+// restore text, etc)
+
+#define TEXT_RES 3258
+
+// Local line number of "save" (actor no. 1826)
+
+#define SAVE_LINE_NO 1
+
+void Sword2Engine::initialiseFontResourceFlags() {
+ byte *textFile = _resman->openResource(TEXT_RES);
+
+ // If language is Polish or Finnish it requires alternate fonts.
+ // Otherwise, use regular fonts
+
+ // "tallenna" Finnish for "save"
+ // "zapisz" Polish for "save"
+
+ // Get the text line (& skip the 2 chars containing the wavId)
+ char *textLine = (char *)fetchTextLine(textFile, SAVE_LINE_NO) + 2;
+
+ if (strcmp(textLine, "tallenna") == 0)
+ initialiseFontResourceFlags(FINNISH_TEXT);
+ else if (strcmp(textLine, "zapisz") == 0)
+ initialiseFontResourceFlags(POLISH_TEXT);
+ else
+ initialiseFontResourceFlags(DEFAULT_TEXT);
+
+ // Get the game name for the windows application
+
+ // According to the GetNameFunction(), which was never called and has
+ // therefore been removed, the name of the game is:
+ //
+ // ENGLISH: "Broken Sword II"
+ // AMERICAN: "Circle of Blood II"
+ // GERMAN: "Baphomet's Fluch II"
+ // default: "Some game or other, part 86"
+ //
+ // But we get it from the text resource instead.
+
+ if (_logic->readVar(DEMO))
+ textLine = (char *)fetchTextLine(textFile, 451) + 2;
+ else
+ textLine = (char *)fetchTextLine(textFile, 54) + 2;
+
+ _system->setWindowCaption(textLine);
+ _resman->closeResource(TEXT_RES);
+}
+
+/**
+ * Called from initialiseFontResourceFlags(), and also from console.cpp
+ */
+
+void Sword2Engine::initialiseFontResourceFlags(uint8 language) {
+ switch (language) {
+ case FINNISH_TEXT:
+ _speechFontId = FINNISH_SPEECH_FONT_ID;
+ _controlsFontId = FINNISH_CONTROLS_FONT_ID;
+ _redFontId = FINNISH_RED_FONT_ID;
+ break;
+ case POLISH_TEXT:
+ _speechFontId = POLISH_SPEECH_FONT_ID;
+ _controlsFontId = POLISH_CONTROLS_FONT_ID;
+ _redFontId = POLISH_RED_FONT_ID;
+ break;
+ default:
+ _speechFontId = ENGLISH_SPEECH_FONT_ID;
+ _controlsFontId = ENGLISH_CONTROLS_FONT_ID;
+ _redFontId = ENGLISH_RED_FONT_ID;
+ break;
+ }
+}
+
+} // End of namespace Sword2
diff --git a/engines/sword2/maketext.h b/engines/sword2/maketext.h
new file mode 100644
index 0000000000..364a412857
--- /dev/null
+++ b/engines/sword2/maketext.h
@@ -0,0 +1,119 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#ifndef _MAKETEXT_H
+#define _MAKETEXT_H
+
+#include "sword2/debug.h"
+
+namespace Sword2 {
+
+// Output colour for character border - should be be black but note that we
+// have to use a different pen number during sequences
+
+#define BORDER_PEN 194
+
+// Usually the only texts on screen are the subtitles and the mouse-over text,
+// but there can also be a considerable number of debugging messages...
+
+#define MAX_text_blocs MAX_DEBUG_TEXTS + 1
+
+enum {
+ // Doesn't keep the text inside the screen - only for debug text!
+ NO_JUSTIFICATION = 0,
+
+ // These all force text inside the screen edge margin when necessary
+ POSITION_AT_CENTRE_OF_BASE = 1,
+ POSITION_AT_CENTRE_OF_TOP = 2,
+ POSITION_AT_LEFT_OF_TOP = 3,
+ POSITION_AT_RIGHT_OF_TOP = 4,
+ POSITION_AT_LEFT_OF_BASE = 5,
+ POSITION_AT_RIGHT_OF_BASE = 6,
+ POSITION_AT_LEFT_OF_CENTRE = 7,
+ POSITION_AT_RIGHT_OF_CENTRE = 8
+};
+
+enum {
+ DEFAULT_TEXT = 0,
+ FINNISH_TEXT = 1,
+ POLISH_TEXT = 2
+};
+
+// Info about the text, used to create the SpriteInfo struct
+
+ struct TextBloc {
+ int16 x;
+ int16 y;
+ uint16 type;
+ byte *text_mem;
+};
+
+// Info for each line of words in the output text sprite
+
+struct LineInfo {
+ uint16 width; // Width in pixels
+ uint16 length; // Length in characters
+};
+
+class FontRenderer {
+private:
+ Sword2Engine *_vm;
+ TextBloc _blocList[MAX_text_blocs];
+
+ // Layout variables - these used to be defines, but now we're dealing
+ // with three character sets
+
+ int8 _lineSpacing; // no. of pixels to separate lines of
+ // characters in the output sprite - negative
+ // for overlap
+ int8 _charSpacing; // no. of pixels to separate characters along
+ // each line - negative for overlap
+ uint8 _borderPen; // output pen colour of character borders
+
+ uint16 analyseSentence(byte *sentence, uint16 maxWidth, uint32 fontRes, LineInfo *line);
+ byte *buildTextSprite(byte *sentence, uint32 fontRes, uint8 pen, LineInfo *line, uint16 noOfLines);
+ uint16 charWidth(byte ch, uint32 fontRes);
+ uint16 charHeight(uint32 fontRes);
+ byte *findChar(byte ch, byte *charSet);
+ void copyChar(byte *charPtr, byte *spritePtr, uint16 spriteWidth, uint8 pen);
+
+public:
+ FontRenderer(Sword2Engine *vm) : _vm(vm) {
+ for (int i = 0; i < MAX_text_blocs; i++)
+ _blocList[i].text_mem = NULL;
+ }
+
+ ~FontRenderer() {
+ for (int i = 0; i < MAX_text_blocs; i++)
+ free(_blocList[i].text_mem);
+ }
+
+ byte *makeTextSprite(byte *sentence, uint16 maxWidth, uint8 pen, uint32 fontRes, uint8 border = BORDER_PEN);
+
+ void killTextBloc(uint32 bloc_number);
+ void printTextBlocs();
+
+ uint32 buildNewBloc(byte *ascii, int16 x, int16 y, uint16 width, uint8 pen, uint32 type, uint32 fontRes, uint8 justification);
+};
+
+} // End of namespace Sword2
+
+#endif
diff --git a/engines/sword2/memory.cpp b/engines/sword2/memory.cpp
new file mode 100644
index 0000000000..7da4e86b51
--- /dev/null
+++ b/engines/sword2/memory.cpp
@@ -0,0 +1,244 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+// The new memory manager, now only used by the resource manager. The original
+// one would allocated a 12 MB memory pool at startup, which may have been
+// appropriate for the original Playstation version but didn't work very well
+// with our PocketPC version.
+//
+// There is one thing that prevents us from replacing the whole memory manager
+// with the standard memory allocation functions: Broken Sword 2 absolutely,
+// positively needs to be able to encode pointers as 32-bit integers. The
+// original engine did this simply by casting between pointers and integers,
+// but as far as I know that's not a very portable thing to do.
+//
+// If it had only used pointers as opcode parameters it would have been
+// possible, albeit messy, to extend the stack data type. However, there is
+// code in walker.cpp that obviously violates that assumption, and there are
+// probably other cases as well.
+//
+// Instead, we take advantage of the fact that the original memory manager
+// could only handle up to 999 blocks of memory. That means we can encode a
+// pointer as a 10-bit id and a 22-bit offset into the block. Judging by early
+// testing, both should be plenty.
+//
+// The number zero is used to represent the NULL pointer.
+
+#include "common/stdafx.h"
+#include "sword2/sword2.h"
+#include "sword2/memory.h"
+
+namespace Sword2 {
+
+MemoryManager::MemoryManager(Sword2Engine *vm) : _vm(vm) {
+ // The id stack contains all the possible ids for the memory blocks.
+ // We use this to ensure that no two blocks ever have the same id.
+
+ // The memory blocks are stored in an array, indexed on the block's
+ // id. This means that given a block id we can find the pointer with a
+ // simple array lookup.
+
+ // The memory block index is an array of pointers to the memory block
+ // array, sorted on the memory block's pointer. This means that given
+ // a pointer into a memory block we can find its id with binary
+ // searching.
+ //
+ // A balanced tree might have been more efficient - the index has to
+ // be re-sorted every time a block is allocated or freed - but such
+ // beasts are tricky to implement. Anyway, it wouldn't have made
+ // encoding or decoding pointers any faster, and these are by far the
+ // most common operations.
+
+ _idStack = (int16 *)malloc(MAX_MEMORY_BLOCKS * sizeof(int16));
+ _memBlocks = (MemBlock *)malloc(MAX_MEMORY_BLOCKS * sizeof(MemBlock));
+ _memBlockIndex = (MemBlock **)malloc(MAX_MEMORY_BLOCKS * sizeof(MemBlock *));
+
+ _totAlloc = 0;
+ _numBlocks = 0;
+
+ for (int i = 0; i < MAX_MEMORY_BLOCKS; i++) {
+ _idStack[i] = MAX_MEMORY_BLOCKS - i - 1;
+ _memBlocks[i].ptr = NULL;
+ _memBlockIndex[i] = NULL;
+ }
+
+ _idStackPtr = MAX_MEMORY_BLOCKS;
+}
+
+MemoryManager::~MemoryManager() {
+ for (int i = 0; i < MAX_MEMORY_BLOCKS; i++)
+ free(_memBlocks[i].ptr);
+ free(_memBlocks);
+ free(_memBlockIndex);
+ free(_idStack);
+}
+
+int32 MemoryManager::encodePtr(byte *ptr) {
+ if (ptr == NULL)
+ return 0;
+
+ int idx = findPointerInIndex(ptr);
+
+ assert(idx != -1);
+
+ uint32 id = _memBlockIndex[idx]->id;
+ uint32 offset = ptr - _memBlocks[id].ptr;
+
+ assert(id < 0x03ff);
+ assert(offset <= 0x003fffff);
+ assert(offset < _memBlocks[id].size);
+
+ return ((id + 1) << 22) | (ptr - _memBlocks[id].ptr);
+}
+
+byte *MemoryManager::decodePtr(int32 n) {
+ if (n == 0)
+ return NULL;
+
+ uint32 id = ((n & 0xffc00000) >> 22) - 1;
+ uint32 offset = n & 0x003fffff;
+
+ assert(_memBlocks[id].ptr);
+ assert(offset < _memBlocks[id].size);
+
+ return _memBlocks[id].ptr + offset;
+}
+
+int16 MemoryManager::findExactPointerInIndex(byte *ptr) {
+ int left = 0;
+ int right = _numBlocks - 1;
+
+ while (right >= left) {
+ int n = (left + right) / 2;
+
+ if (_memBlockIndex[n]->ptr == ptr)
+ return n;
+
+ if (_memBlockIndex[n]->ptr > ptr)
+ right = n - 1;
+ else
+ left = n + 1;
+ }
+
+ return -1;
+}
+
+int16 MemoryManager::findPointerInIndex(byte *ptr) {
+ int left = 0;
+ int right = _numBlocks - 1;
+
+ while (right >= left) {
+ int n = (left + right) / 2;
+
+ if (_memBlockIndex[n]->ptr <= ptr && _memBlockIndex[n]->ptr + _memBlockIndex[n]->size > ptr)
+ return n;
+
+ if (_memBlockIndex[n]->ptr > ptr)
+ right = n - 1;
+ else
+ left = n + 1;
+ }
+
+ return -1;
+}
+
+int16 MemoryManager::findInsertionPointInIndex(byte *ptr) {
+ if (_numBlocks == 0)
+ return 0;
+
+ int left = 0;
+ int right = _numBlocks - 1;
+ int n = 0;
+
+ while (right >= left) {
+ n = (left + right) / 2;
+
+ if (_memBlockIndex[n]->ptr == ptr)
+ return -1;
+
+ if (_memBlockIndex[n]->ptr > ptr)
+ right = n - 1;
+ else
+ left = n + 1;
+ }
+
+ if (_memBlockIndex[n]->ptr < ptr)
+ n++;
+
+ return n;
+}
+
+byte *MemoryManager::memAlloc(uint32 size, int16 uid) {
+ assert(_idStackPtr > 0);
+
+ // Get the new block's id from the stack.
+ int16 id = _idStack[--_idStackPtr];
+
+ // Allocate the new memory block
+ byte *ptr = (byte *)malloc(size);
+
+ assert(ptr);
+
+ _memBlocks[id].id = id;
+ _memBlocks[id].uid = uid;
+ _memBlocks[id].ptr = ptr;
+ _memBlocks[id].size = size;
+
+ // Update the memory block index.
+ int16 idx = findInsertionPointInIndex(ptr);
+
+ assert(idx != -1);
+
+ for (int i = _numBlocks; i > idx; i--)
+ _memBlockIndex[i] = _memBlockIndex[i - 1];
+
+ _memBlockIndex[idx] = &_memBlocks[id];
+ _numBlocks++;
+ _totAlloc += size;
+
+ return _memBlocks[id].ptr;
+}
+
+void MemoryManager::memFree(byte *ptr) {
+ int16 idx = findExactPointerInIndex(ptr);
+
+ if (idx == -1) {
+ warning("Freeing non-allocated pointer %p", ptr);
+ return;
+ }
+
+ // Put back the id on the stack
+ _idStack[_idStackPtr++] = _memBlockIndex[idx]->id;
+
+ // Release the memory block
+ free(_memBlockIndex[idx]->ptr);
+ _memBlockIndex[idx]->ptr = NULL;
+
+ _totAlloc -= _memBlockIndex[idx]->size;
+
+ // Remove the memory block from the index
+ _numBlocks--;
+
+ for (int i = idx; i < _numBlocks; i++)
+ _memBlockIndex[i] = _memBlockIndex[i + 1];
+}
+
+} // End of namespace Sword2
diff --git a/engines/sword2/memory.h b/engines/sword2/memory.h
new file mode 100644
index 0000000000..3154842cd9
--- /dev/null
+++ b/engines/sword2/memory.h
@@ -0,0 +1,70 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#ifndef MEMORY_H
+#define MEMORY_H
+
+#define MAX_MEMORY_BLOCKS 999
+
+namespace Sword2 {
+
+struct MemBlock {
+ int16 id;
+ int16 uid;
+ byte *ptr;
+ uint32 size;
+};
+
+class MemoryManager {
+private:
+ Sword2Engine *_vm;
+
+ MemBlock *_memBlocks;
+ MemBlock **_memBlockIndex;
+ int16 _numBlocks;
+
+ uint32 _totAlloc;
+
+ int16 *_idStack;
+ int16 _idStackPtr;
+
+ int16 findExactPointerInIndex(byte *ptr);
+ int16 findPointerInIndex(byte *ptr);
+ int16 findInsertionPointInIndex(byte *ptr);
+
+public:
+ MemoryManager(Sword2Engine *vm);
+ ~MemoryManager();
+
+ int16 getNumBlocks() { return _numBlocks; }
+ uint32 getTotAlloc() { return _totAlloc; }
+ MemBlock *getMemBlocks() { return _memBlocks; }
+
+ int32 encodePtr(byte *ptr);
+ byte *decodePtr(int32 n);
+
+ byte *memAlloc(uint32 size, int16 uid);
+ void memFree(byte *ptr);
+};
+
+} // End of namespace Sword2
+
+#endif
diff --git a/engines/sword2/menu.cpp b/engines/sword2/menu.cpp
new file mode 100644
index 0000000000..07e00accb6
--- /dev/null
+++ b/engines/sword2/menu.cpp
@@ -0,0 +1,291 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "common/stdafx.h"
+
+#include "sword2/sword2.h"
+#include "sword2/defs.h"
+#include "sword2/mouse.h"
+
+namespace Sword2 {
+
+#define MENUDEEP 40
+#define MAXMENUANIMS 8
+
+void Mouse::clearIconArea(int menu, int pocket, Common::Rect *r) {
+ byte *buf = _vm->_screen->getScreen();
+ int16 screenWide = _vm->_screen->getScreenWide();
+
+ r->top = menu * (RENDERDEEP + MENUDEEP) + (MENUDEEP - RDMENU_ICONDEEP) / 2;
+ r->bottom = r->top + RDMENU_ICONDEEP;
+ r->left = RDMENU_ICONSTART + pocket * (RDMENU_ICONWIDE + RDMENU_ICONSPACING);
+ r->right = r->left + RDMENU_ICONWIDE;
+
+ byte *dst = buf + r->top * screenWide + r->left;
+
+ for (int i = 0; i < RDMENU_ICONDEEP; i++) {
+ memset(dst, 0, RDMENU_ICONWIDE);
+ dst += screenWide;
+ }
+}
+
+/**
+ * This function should be called regularly to process the menubar system. The
+ * rate at which this function is called will dictate how smooth the menu
+ * system is.
+ */
+
+void Mouse::processMenu() {
+ uint8 menu;
+ uint8 i, j;
+ uint8 frameCount;
+ Common::Rect r1, r2;
+ static int32 lastTime = 0;
+
+ byte *buf = _vm->_screen->getScreen();
+ int16 screenWide = _vm->_screen->getScreenWide();
+
+ if (lastTime == 0) {
+ lastTime = _vm->getMillis();
+ frameCount = 1;
+ } else {
+ int32 delta = _vm->getMillis() - lastTime;
+
+ if (delta > 250) {
+ lastTime += delta;
+ delta = 250;
+ frameCount = 1;
+ } else {
+ frameCount = (uint8) ((_iconCount + 8) * delta / 750);
+ lastTime += frameCount * 750 / (_iconCount + 8);
+ }
+ }
+
+ // Note: The "almost hidden" menu state exists only so that the menu
+ // will be redrawn one last time before it's completely hidden. We do
+ // not need a corresponding "almost shown" state because the menu will
+ // always be redrawn while it's shown anyway. (We may want to change
+ // this later.)
+
+ while (frameCount-- > 0) {
+ for (menu = RDMENU_TOP; menu <= RDMENU_BOTTOM; menu++) {
+ if (_menuStatus[menu] == RDMENU_HIDDEN || _menuStatus[menu] == RDMENU_ALMOST_HIDDEN || _menuStatus[menu] == RDMENU_SHOWN)
+ continue;
+
+ int target, direction, nextState;
+
+ if (_menuStatus[menu] == RDMENU_OPENING) {
+ target = MAXMENUANIMS;
+ direction = 1;
+ nextState = RDMENU_SHOWN;
+ } else {
+ target = 0;
+ direction = -1;
+ nextState = RDMENU_ALMOST_HIDDEN;
+ }
+
+ bool complete = true;
+
+ // Propagate animation from the first icon...
+ for (i = RDMENU_MAXPOCKETS - 1; i > 0; i--) {
+ _pocketStatus[menu][i] = _pocketStatus[menu][i - 1];
+
+ if (_pocketStatus[menu][i] != target)
+ complete = false;
+ }
+
+ if (_pocketStatus[menu][i] != target)
+ complete = false;
+
+ // ...and animate the first icon
+ if (_pocketStatus[menu][0] != target)
+ _pocketStatus[menu][0] += direction;
+
+ if (complete)
+ _menuStatus[menu] = nextState;
+ }
+ }
+
+ for (menu = RDMENU_TOP; menu <= RDMENU_BOTTOM; menu++) {
+ if (_menuStatus[menu] == RDMENU_HIDDEN)
+ continue;
+
+ if (_menuStatus[menu] == RDMENU_ALMOST_HIDDEN)
+ _menuStatus[menu] = RDMENU_HIDDEN;
+
+ // Draw the menu here.
+ int32 curx = RDMENU_ICONSTART + RDMENU_ICONWIDE / 2;
+ int32 cury = (MENUDEEP / 2) + (RENDERDEEP + MENUDEEP) * menu;
+
+ for (i = 0; i < RDMENU_MAXPOCKETS; i++) {
+ if (_icons[menu][i]) {
+ int32 xoff, yoff;
+
+ // Since we no longer clear the screen after
+ // each frame we need to clear the icon area.
+
+ clearIconArea(menu, i, &r1);
+
+ if (_pocketStatus[menu][i] == MAXMENUANIMS) {
+ xoff = (RDMENU_ICONWIDE / 2);
+ r2.left = curx - xoff;
+ r2.right = r2.left + RDMENU_ICONWIDE;
+ yoff = (RDMENU_ICONDEEP / 2);
+ r2.top = cury - yoff;
+ r2.bottom = r2.top + RDMENU_ICONDEEP;
+ } else {
+ xoff = (RDMENU_ICONWIDE / 2) * _pocketStatus[menu][i] / MAXMENUANIMS;
+ r2.left = curx - xoff;
+ r2.right = curx + xoff;
+ yoff = (RDMENU_ICONDEEP / 2) * _pocketStatus[menu][i] / MAXMENUANIMS;
+ r2.top = cury - yoff;
+ r2.bottom = cury + yoff;
+ }
+
+ if (xoff != 0 && yoff != 0) {
+ byte *dst = buf + r2.top * screenWide + r2.left;
+ byte *src = _icons[menu][i];
+
+ if (_pocketStatus[menu][i] != MAXMENUANIMS) {
+ _vm->_screen->scaleImageFast(
+ dst, screenWide, r2.right - r2.left, r2.bottom - r2.top,
+ src, RDMENU_ICONWIDE, RDMENU_ICONWIDE, RDMENU_ICONDEEP);
+ } else {
+ for (j = 0; j < RDMENU_ICONDEEP; j++) {
+ memcpy(dst, src, RDMENU_ICONWIDE);
+ src += RDMENU_ICONWIDE;
+ dst += screenWide;
+ }
+ }
+ }
+ _vm->_screen->updateRect(&r1);
+ }
+ curx += (RDMENU_ICONSPACING + RDMENU_ICONWIDE);
+ }
+ }
+}
+
+/**
+ * This function brings a specified menu into view.
+ * @param menu RDMENU_TOP or RDMENU_BOTTOM, depending on which menu to show
+ * @return RD_OK, or an error code
+ */
+
+int32 Mouse::showMenu(uint8 menu) {
+ // Check for invalid menu parameter
+ if (menu > RDMENU_BOTTOM)
+ return RDERR_INVALIDMENU;
+
+ // Check that the menu is not currently shown, or in the process of
+ // being shown.
+ if (_menuStatus[menu] == RDMENU_SHOWN || _menuStatus[menu] == RDMENU_OPENING)
+ return RDERR_INVALIDCOMMAND;
+
+ _menuStatus[menu] = RDMENU_OPENING;
+ return RD_OK;
+}
+
+/**
+ * This function hides a specified menu.
+ * @param menu RDMENU_TOP or RDMENU_BOTTOM depending on which menu to hide
+ * @return RD_OK, or an error code
+ */
+
+int32 Mouse::hideMenu(uint8 menu) {
+ // Check for invalid menu parameter
+ if (menu > RDMENU_BOTTOM)
+ return RDERR_INVALIDMENU;
+
+ // Check that the menu is not currently hidden, or in the process of
+ // being hidden.
+ if (_menuStatus[menu] == RDMENU_HIDDEN || _menuStatus[menu] == RDMENU_CLOSING)
+ return RDERR_INVALIDCOMMAND;
+
+ _menuStatus[menu] = RDMENU_CLOSING;
+ return RD_OK;
+}
+
+/**
+ * This function hides both menus immediately.
+ */
+
+void Mouse::closeMenuImmediately() {
+ Common::Rect r;
+ int i;
+
+ _menuStatus[RDMENU_TOP] = RDMENU_HIDDEN;
+ _menuStatus[RDMENU_BOTTOM] = RDMENU_HIDDEN;
+
+ for (i = 0; i < RDMENU_MAXPOCKETS; i++) {
+ if (_icons[RDMENU_TOP][i]) {
+ clearIconArea(RDMENU_TOP, i, &r);
+ _vm->_screen->updateRect(&r);
+ }
+ if (_icons[RDMENU_BOTTOM][i]) {
+ clearIconArea(RDMENU_BOTTOM, i, &r);
+ _vm->_screen->updateRect(&r);
+ }
+ }
+
+ memset(_pocketStatus, 0, sizeof(uint8) * 2 * RDMENU_MAXPOCKETS);
+}
+
+/**
+ * This function sets a menubar icon.
+ * @param menu RDMENU_TOP or RDMENU_BOTTOM, depending on which menu to change
+ * @param pocket the menu pocket to change
+ * @param icon icon data, or NULL to clear the icon
+ * @return RD_OK, or an error code
+ */
+
+int32 Mouse::setMenuIcon(uint8 menu, uint8 pocket, byte *icon) {
+ Common::Rect r;
+
+ // Check for invalid menu parameter.
+ if (menu > RDMENU_BOTTOM)
+ return RDERR_INVALIDMENU;
+
+ // Check for invalid pocket parameter
+ if (pocket >= RDMENU_MAXPOCKETS)
+ return RDERR_INVALIDPOCKET;
+
+ // If there is an icon in the requested menu/pocket, clear it out.
+ if (_icons[menu][pocket]) {
+ _iconCount--;
+ free(_icons[menu][pocket]);
+ _icons[menu][pocket] = NULL;
+ clearIconArea(menu, pocket, &r);
+ _vm->_screen->updateRect(&r);
+ }
+
+ // Only put the icon in the pocket if it is not NULL
+ if (icon != NULL) {
+ _iconCount++;
+ _icons[menu][pocket] = (byte *)malloc(RDMENU_ICONWIDE * RDMENU_ICONDEEP);
+ if (_icons[menu][pocket] == NULL)
+ return RDERR_OUTOFMEMORY;
+ memcpy(_icons[menu][pocket], icon, RDMENU_ICONWIDE * RDMENU_ICONDEEP);
+ }
+
+ return RD_OK;
+}
+
+} // End of namespace Sword2
diff --git a/engines/sword2/module.mk b/engines/sword2/module.mk
new file mode 100644
index 0000000000..2ee3f39ebb
--- /dev/null
+++ b/engines/sword2/module.mk
@@ -0,0 +1,48 @@
+MODULE := engines/sword2
+
+MODULE_OBJS := \
+ engines/sword2/_mouse.o \
+ engines/sword2/animation.o \
+ engines/sword2/anims.o \
+ engines/sword2/build_display.o \
+ engines/sword2/console.o \
+ engines/sword2/controls.o \
+ engines/sword2/d_draw.o \
+ engines/sword2/debug.o \
+ engines/sword2/events.o \
+ engines/sword2/function.o \
+ engines/sword2/icons.o \
+ engines/sword2/interpreter.o \
+ engines/sword2/layers.o \
+ engines/sword2/logic.o \
+ engines/sword2/maketext.o \
+ engines/sword2/memory.o \
+ engines/sword2/menu.o \
+ engines/sword2/mouse.o \
+ engines/sword2/music.o \
+ engines/sword2/palette.o \
+ engines/sword2/protocol.o \
+ engines/sword2/rdwin.o \
+ engines/sword2/render.o \
+ engines/sword2/resman.o \
+ engines/sword2/router.o \
+ engines/sword2/save_rest.o \
+ engines/sword2/scroll.o \
+ engines/sword2/sound.o \
+ engines/sword2/speech.o \
+ engines/sword2/sprite.o \
+ engines/sword2/startup.o \
+ engines/sword2/sword2.o \
+ engines/sword2/sync.o \
+ engines/sword2/walker.o
+
+MODULE_DIRS += \
+ engines/sword2
+
+# This module can be built as a plugin
+ifdef BUILD_PLUGINS
+PLUGIN := 1
+endif
+
+# Include common rules
+include $(srcdir)/common.rules
diff --git a/engines/sword2/mouse.cpp b/engines/sword2/mouse.cpp
new file mode 100644
index 0000000000..f8c315a47f
--- /dev/null
+++ b/engines/sword2/mouse.cpp
@@ -0,0 +1,1437 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "common/stdafx.h"
+#include "sword2/sword2.h"
+#include "sword2/console.h"
+#include "sword2/controls.h"
+#include "sword2/defs.h"
+#include "sword2/logic.h"
+#include "sword2/maketext.h"
+#include "sword2/mouse.h"
+#include "sword2/resman.h"
+#include "sword2/sound.h"
+
+namespace Sword2 {
+
+// Pointer resource id's
+
+enum {
+ CROSHAIR = 18,
+ EXIT0 = 788,
+ EXIT1 = 789,
+ EXIT2 = 790,
+ EXIT3 = 791,
+ EXIT4 = 792,
+ EXIT5 = 793,
+ EXIT6 = 794,
+ EXIT7 = 795,
+ EXITDOWN = 796,
+ EXITUP = 797,
+ MOUTH = 787,
+ NORMAL = 17,
+ PICKUP = 3099,
+ SCROLL_L = 1440,
+ SCROLL_R = 1441,
+ USE = 3100
+};
+
+Mouse::Mouse(Sword2Engine *vm) {
+ _vm = vm;
+
+ setPos(0, 0);
+ resetMouseList();
+
+ _mouseTouching = 0;
+ _oldMouseTouching = 0;
+ _menuSelectedPos = 0;
+ _examiningMenuIcon = false;
+ _mousePointerRes = 0;
+ _mouseMode = 0;
+ _mouseStatus = false;
+ _mouseModeLocked = false;
+ _currentLuggageResource = 0;
+ _oldButton = 0;
+ _buttonClick = 0;
+ _pointerTextBlocNo = 0;
+ _playerActivityDelay = 0;
+ _realLuggageItem = 0;
+
+ _mouseAnim.data = NULL;
+ _luggageAnim.data = NULL;
+
+ // For the menus
+ _totalTemp = 0;
+ memset(_tempList, 0, sizeof(_tempList));
+
+ _totalMasters = 0;
+ memset(_masterMenuList, 0, sizeof(_masterMenuList));
+ memset(_mouseList, 0, sizeof(_mouseList));
+ memset(_subjectList, 0, sizeof(_subjectList));
+
+ _defaultResponseId = 0;
+ _choosing = false;
+
+ _iconCount = 0;
+
+ for (int i = 0; i < 2; i++) {
+ for (int j = 0; j < RDMENU_MAXPOCKETS; j++) {
+ _icons[i][j] = NULL;
+ _pocketStatus[i][j] = 0;
+ }
+
+ _menuStatus[i] = RDMENU_HIDDEN;
+ }
+}
+
+Mouse::~Mouse() {
+ free(_mouseAnim.data);
+ free(_luggageAnim.data);
+ for (int i = 0; i < 2; i++)
+ for (int j = 0; j < RDMENU_MAXPOCKETS; j++)
+ free(_icons[i][j]);
+}
+
+void Mouse::getPos(int &x, int &y) {
+ x = _pos.x;
+ y = _pos.y;
+}
+
+void Mouse::setPos(int x, int y) {
+ _pos.x = x;
+ _pos.y = y;
+}
+
+/**
+ * Call at beginning of game loop
+ */
+
+void Mouse::resetMouseList() {
+ _curMouse = 0;
+}
+
+void Mouse::registerMouse(byte *ob_mouse, BuildUnit *build_unit) {
+ assert(_curMouse < TOTAL_mouse_list);
+
+ ObjectMouse mouse;
+
+ mouse.read(ob_mouse);
+
+ if (!mouse.pointer)
+ return;
+
+ if (build_unit) {
+ _mouseList[_curMouse].rect.left = build_unit->x;
+ _mouseList[_curMouse].rect.top = build_unit->y;
+ _mouseList[_curMouse].rect.right = 1 + build_unit->x + build_unit->scaled_width;
+ _mouseList[_curMouse].rect.bottom = 1 + build_unit->y + build_unit->scaled_height;
+ } else {
+ _mouseList[_curMouse].rect.left = mouse.x1;
+ _mouseList[_curMouse].rect.top = mouse.y1;
+ _mouseList[_curMouse].rect.right = 1 + mouse.x2;
+ _mouseList[_curMouse].rect.bottom = 1 + mouse.y2;
+ }
+
+ _mouseList[_curMouse].priority = mouse.priority;
+ _mouseList[_curMouse].pointer = mouse.pointer;
+
+ // Change all COGS pointers to CROSHAIR. I'm guessing that this was a
+ // design decision made in mid-development and they didn't want to go
+ // back and re-generate the resource files.
+
+ if (_mouseList[_curMouse].pointer == USE)
+ _mouseList[_curMouse].pointer = CROSHAIR;
+
+ // Check if pointer text field is set due to previous object using this
+ // slot (ie. not correct for this one)
+
+ // If 'pointer_text' field is set, but the 'id' field isn't same is
+ // current id then we don't want this "left over" pointer text
+
+ if (_mouseList[_curMouse].pointer_text && _mouseList[_curMouse].id != (int32)_vm->_logic->readVar(ID))
+ _mouseList[_curMouse].pointer_text = 0;
+
+ // Get id from system variable 'id' which is correct for current object
+ _mouseList[_curMouse].id = _vm->_logic->readVar(ID);
+
+ _curMouse++;
+}
+
+void Mouse::registerPointerText(int32 text_id) {
+ assert(_curMouse < TOTAL_mouse_list);
+
+ // current object id - used for checking pointer_text when mouse area
+ // registered (in fnRegisterMouse and fnRegisterFrame)
+
+ _mouseList[_curMouse].id = _vm->_logic->readVar(ID);
+ _mouseList[_curMouse].pointer_text = text_id;
+}
+
+/**
+ * This function is called every game cycle.
+ */
+
+void Mouse::mouseEngine() {
+ monitorPlayerActivity();
+ clearPointerText();
+
+ // If George is dead, the system menu is visible all the time, and is
+ // the only thing that can be used.
+
+ if (_vm->_logic->readVar(DEAD)) {
+ if (_mouseMode != MOUSE_system_menu) {
+ _mouseMode = MOUSE_system_menu;
+
+ if (_mouseTouching) {
+ _oldMouseTouching = 0;
+ _mouseTouching = 0;
+ }
+
+ setMouse(NORMAL_MOUSE_ID);
+ buildSystemMenu();
+ }
+ systemMenuMouse();
+ return;
+ }
+
+ // If the mouse is not visible, do nothing
+
+ if (_mouseStatus)
+ return;
+
+ switch (_mouseMode) {
+ case MOUSE_normal:
+ normalMouse();
+ break;
+ case MOUSE_menu:
+ menuMouse();
+ break;
+ case MOUSE_drag:
+ dragMouse();
+ break;
+ case MOUSE_system_menu:
+ systemMenuMouse();
+ break;
+ case MOUSE_holding:
+ if (_pos.y < 400) {
+ _mouseMode = MOUSE_normal;
+ debug(5, " releasing");
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+#if RIGHT_CLICK_CLEARS_LUGGAGE
+bool Mouse::heldIsInInventory() {
+ int32 object_held = (int32)_vm->_logic->readVar(OBJECT_HELD);
+
+ for (uint i = 0; i < _totalMasters; i++) {
+ if (_masterMenuList[i].icon_resource == object_held)
+ return true;
+ }
+ return false;
+}
+#endif
+
+int Mouse::menuClick(int menu_items) {
+ if (_pos.x < RDMENU_ICONSTART)
+ return -1;
+
+ if (_pos.x > RDMENU_ICONSTART + menu_items * (RDMENU_ICONWIDE + RDMENU_ICONSPACING) - RDMENU_ICONSPACING)
+ return -1;
+
+ return (_pos.x - RDMENU_ICONSTART) / (RDMENU_ICONWIDE + RDMENU_ICONSPACING);
+}
+
+void Mouse::systemMenuMouse() {
+ uint32 safe_looping_music_id;
+ MouseEvent *me;
+ int hit;
+ byte *icon;
+ int32 pars[2];
+ uint32 icon_list[5] = {
+ OPTIONS_ICON,
+ QUIT_ICON,
+ SAVE_ICON,
+ RESTORE_ICON,
+ RESTART_ICON
+ };
+
+ // If the mouse is moved off the menu, close it. Unless the player is
+ // dead, in which case the menu should always be visible.
+
+ if (_pos.y > 0 && !_vm->_logic->readVar(DEAD)) {
+ _mouseMode = MOUSE_normal;
+ hideMenu(RDMENU_TOP);
+ return;
+ }
+
+ // Check if the user left-clicks anywhere in the menu area.
+
+ me = _vm->mouseEvent();
+
+ if (!me || !(me->buttons & RD_LEFTBUTTONDOWN))
+ return;
+
+ if (_pos.y > 0)
+ return;
+
+ hit = menuClick(ARRAYSIZE(icon_list));
+
+ if (hit < 0)
+ return;
+
+ // No save when dead
+
+ if (icon_list[hit] == SAVE_ICON && _vm->_logic->readVar(DEAD))
+ return;
+
+ // Gray out all he icons, except the one that was clicked
+
+ for (int i = 0; i < ARRAYSIZE(icon_list); i++) {
+ if (i != hit) {
+ icon = _vm->_resman->openResource(icon_list[i]) + ResHeader::size();
+ setMenuIcon(RDMENU_TOP, i, icon);
+ _vm->_resman->closeResource(icon_list[i]);
+ }
+ }
+
+ _vm->_sound->pauseFx();
+
+ // NB. Need to keep a safe copy of '_loopingMusicId' for savegame & for
+ // playing when returning from control panels because control panel
+ // music will overwrite it!
+
+ safe_looping_music_id = _vm->_sound->getLoopingMusicId();
+
+ pars[0] = 221;
+ pars[1] = FX_LOOP;
+ _vm->_logic->fnPlayMusic(pars);
+
+ // HACK: Restore proper looping_music_id
+ _vm->_sound->setLoopingMusicId(safe_looping_music_id);
+
+ processMenu();
+
+ // call the relevant screen
+
+ switch (hit) {
+ case 0:
+ {
+ OptionsDialog dialog(_vm);
+ dialog.runModal();
+ }
+ break;
+ case 1:
+ {
+ QuitDialog dialog(_vm);
+ dialog.runModal();
+ }
+ break;
+ case 2:
+ {
+ SaveDialog dialog(_vm);
+ dialog.runModal();
+ }
+ break;
+ case 3:
+ {
+ RestoreDialog dialog(_vm);
+ dialog.runModal();
+ }
+ break;
+ case 4:
+ {
+ RestartDialog dialog(_vm);
+ dialog.runModal();
+ }
+ break;
+ }
+
+ // Menu stays open on death screen. Otherwise it's closed.
+
+ if (!_vm->_logic->readVar(DEAD)) {
+ _mouseMode = MOUSE_normal;
+ hideMenu(RDMENU_TOP);
+ } else {
+ setMouse(NORMAL_MOUSE_ID);
+ buildSystemMenu();
+ }
+
+ // Back to the game again
+
+ processMenu();
+
+ // Reset game palette, but not after a successful restore or restart!
+ // See RestoreFromBuffer() in save_rest.cpp
+
+ ScreenInfo *screenInfo = _vm->_screen->getScreenInfo();
+
+ if (screenInfo->new_palette != 99) {
+ // 0 means put back game screen palette; see build_display.cpp
+ _vm->_screen->setFullPalette(0);
+
+ // Stop the engine fading in the restored screens palette
+ screenInfo->new_palette = 0;
+ } else
+ screenInfo->new_palette = 1;
+
+ _vm->_sound->unpauseFx();
+
+ // If there was looping music before coming into the control panels
+ // then restart it! NB. If a game has been restored the music will be
+ // restarted twice, but this shouldn't cause any harm.
+
+ if (_vm->_sound->getLoopingMusicId()) {
+ pars[0] = _vm->_sound->getLoopingMusicId();
+ pars[1] = FX_LOOP;
+ _vm->_logic->fnPlayMusic(pars);
+ } else
+ _vm->_logic->fnStopMusic(NULL);
+}
+
+void Mouse::dragMouse() {
+ byte buf1[NAME_LEN], buf2[NAME_LEN];
+ MouseEvent *me;
+ int hit;
+
+ // We can use dragged object both on other inventory objects, or on
+ // objects in the scene, so if the mouse moves off the inventory menu,
+ // then close it.
+
+ if (_pos.y < 400) {
+ _mouseMode = MOUSE_normal;
+ hideMenu(RDMENU_BOTTOM);
+ return;
+ }
+
+ // Handles cursors and the luggage on/off according to type
+
+ mouseOnOff();
+
+ // Now do the normal click stuff
+
+ me = _vm->mouseEvent();
+
+ if (!me)
+ return;
+
+#if RIGHT_CLICK_CLEARS_LUGGAGE
+ if ((me->buttons & RD_RIGHTBUTTONDOWN) && heldIsInInventory()) {
+ _vm->_logic->writeVar(OBJECT_HELD, 0);
+ _menuSelectedPos = 0;
+ _mouseMode = MOUSE_menu;
+ setLuggage(0);
+ buildMenu();
+ return;
+ }
+#endif
+
+ if (!(me->buttons & RD_LEFTBUTTONDOWN))
+ return;
+
+ // there's a mouse event to be processed
+
+ // could be clicking on an on screen object or on the menu
+ // which is currently displayed
+
+ if (_mouseTouching) {
+ // mouse is over an on screen object - and we have luggage
+
+ // Depending on type we'll maybe kill the object_held - like
+ // for exits
+
+ // Set global script variable 'button'. We know that it was the
+ // left button, not the right one.
+
+ _vm->_logic->writeVar(LEFT_BUTTON, 1);
+ _vm->_logic->writeVar(RIGHT_BUTTON, 0);
+
+ // These might be required by the action script about to be run
+ ScreenInfo *screenInfo = _vm->_screen->getScreenInfo();
+
+ _vm->_logic->writeVar(MOUSE_X, _pos.x + screenInfo->scroll_offset_x);
+ _vm->_logic->writeVar(MOUSE_Y, _pos.y + screenInfo->scroll_offset_y);
+
+ // For scripts to know what's been clicked. First used for
+ // 'room_13_turning_script' in object 'biscuits_13'
+
+ _vm->_logic->writeVar(CLICKED_ID, _mouseTouching);
+
+ _vm->_logic->setPlayerActionEvent(CUR_PLAYER_ID, _mouseTouching);
+
+ debug(2, "Used \"%s\" on \"%s\"",
+ _vm->_resman->fetchName(_vm->_logic->readVar(OBJECT_HELD), buf1),
+ _vm->_resman->fetchName(_vm->_logic->readVar(CLICKED_ID), buf2));
+
+ // Hide menu - back to normal menu mode
+
+ hideMenu(RDMENU_BOTTOM);
+ _mouseMode = MOUSE_normal;
+
+ return;
+ }
+
+ // Better check for combine/cancel. Cancel puts us back in MOUSE_menu
+ // mode
+
+ hit = menuClick(TOTAL_engine_pockets);
+
+ if (hit < 0 || !_masterMenuList[hit].icon_resource)
+ return;
+
+ // Always back into menu mode. Remove the luggage as well.
+
+ _mouseMode = MOUSE_menu;
+ setLuggage(0);
+
+ if ((uint)hit == _menuSelectedPos) {
+ // If we clicked on the same icon again, reset the first icon
+
+ _vm->_logic->writeVar(OBJECT_HELD, 0);
+ _menuSelectedPos = 0;
+ } else {
+ // Otherwise, combine the two icons
+
+ _vm->_logic->writeVar(COMBINE_BASE, _masterMenuList[hit].icon_resource);
+ _vm->_logic->setPlayerActionEvent(CUR_PLAYER_ID, MENU_MASTER_OBJECT);
+
+ // Turn off mouse now, to prevent player trying to click
+ // elsewhere BUT leave the bottom menu open
+
+ hideMouse();
+
+ debug(2, "Used \"%s\" on \"%s\"",
+ _vm->_resman->fetchName(_vm->_logic->readVar(OBJECT_HELD), buf1),
+ _vm->_resman->fetchName(_vm->_logic->readVar(COMBINE_BASE), buf2));
+ }
+
+ // Refresh the menu
+
+ buildMenu();
+}
+
+void Mouse::menuMouse() {
+ byte buf[NAME_LEN];
+ MouseEvent *me;
+ int hit;
+
+ // If the mouse is moved off the menu, close it.
+
+ if (_pos.y < 400) {
+ _mouseMode = MOUSE_normal;
+ hideMenu(RDMENU_BOTTOM);
+ return;
+ }
+
+ me = _vm->mouseEvent();
+
+ if (!me)
+ return;
+
+ hit = menuClick(TOTAL_engine_pockets);
+
+ // Check if we clicked on an actual icon.
+
+ if (hit < 0 || !_masterMenuList[hit].icon_resource)
+ return;
+
+ if (me->buttons & RD_RIGHTBUTTONDOWN) {
+ // Right button - examine an object, identified by its icon
+ // resource id.
+
+ _examiningMenuIcon = true;
+ _vm->_logic->writeVar(OBJECT_HELD, _masterMenuList[hit].icon_resource);
+
+ // Must clear this so next click on exit becomes 1st click
+ // again
+
+ _vm->_logic->writeVar(EXIT_CLICK_ID, 0);
+
+ _vm->_logic->setPlayerActionEvent(CUR_PLAYER_ID, MENU_MASTER_OBJECT);
+
+ // Refresh the menu
+
+ buildMenu();
+
+ // Turn off mouse now, to prevent player trying to click
+ // elsewhere BUT leave the bottom menu open
+
+ hideMouse();
+
+ debug(2, "Right-click on \"%s\" icon",
+ _vm->_resman->fetchName(_vm->_logic->readVar(OBJECT_HELD), buf));
+
+ return;
+ }
+
+ if (me->buttons & RD_LEFTBUTTONDOWN) {
+ // Left button - bung us into drag luggage mode. The object is
+ // identified by its icon resource id. We need the luggage
+ // resource id for mouseOnOff
+
+ _mouseMode = MOUSE_drag;
+
+ _menuSelectedPos = hit;
+ _vm->_logic->writeVar(OBJECT_HELD, _masterMenuList[hit].icon_resource);
+ _currentLuggageResource = _masterMenuList[hit].luggage_resource;
+
+ // Must clear this so next click on exit becomes 1st click
+ // again
+
+ _vm->_logic->writeVar(EXIT_CLICK_ID, 0);
+
+ // Refresh the menu
+
+ buildMenu();
+
+ setLuggage(_masterMenuList[hit].luggage_resource);
+
+ debug(2, "Left-clicked on \"%s\" icon - switch to drag mode",
+ _vm->_resman->fetchName(_vm->_logic->readVar(OBJECT_HELD), buf));
+ }
+}
+
+void Mouse::normalMouse() {
+ // The gane is playing and none of the menus are activated - but, we
+ // need to check if a menu is to start. Note, won't have luggage
+
+ MouseEvent *me;
+
+ // Check if the cursor has moved onto the system menu area. No save in
+ // big-object menu lock situation, of if the player is dragging an
+ // object.
+
+ if (_pos.y < 0 && !_mouseModeLocked && !_vm->_logic->readVar(OBJECT_HELD)) {
+ _mouseMode = MOUSE_system_menu;
+
+ if (_mouseTouching) {
+ // We were on something, but not anymore
+ _oldMouseTouching = 0;
+ _mouseTouching = 0;
+ }
+
+ // Reset mouse cursor - in case we're between mice
+
+ setMouse(NORMAL_MOUSE_ID);
+ buildSystemMenu();
+ return;
+ }
+
+ // Check if the cursor has moved onto the inventory menu area. No
+ // inventory in big-object menu lock situation,
+
+ if (_pos.y > 399 && !_mouseModeLocked) {
+ // If an object is being held, i.e. if the mouse cursor has a
+ // luggage, go to drag mode instead of menu mode, but the menu
+ // is still opened.
+ //
+ // That way, we can still use an object on another inventory
+ // object, even if the inventory menu was closed after the
+ // first object was selected.
+
+ if (!_vm->_logic->readVar(OBJECT_HELD))
+ _mouseMode = MOUSE_menu;
+ else
+ _mouseMode = MOUSE_drag;
+
+ // If mouse is moving off an object and onto the menu then do a
+ // standard get-off
+
+ if (_mouseTouching) {
+ _oldMouseTouching = 0;
+ _mouseTouching = 0;
+ }
+
+ // Reset mouse cursor
+
+ setMouse(NORMAL_MOUSE_ID);
+ buildMenu();
+ return;
+ }
+
+ // Check for moving the mouse on or off things
+
+ mouseOnOff();
+
+ me = _vm->mouseEvent();
+
+ if (!me)
+ return;
+
+ bool button_down = (me->buttons & (RD_LEFTBUTTONDOWN | RD_RIGHTBUTTONDOWN)) != 0;
+
+ // For debugging. We can draw a rectangle on the screen and see its
+ // coordinates. This was probably used to help defining hit areas.
+
+ if (_vm->_debugger->_definingRectangles) {
+ ScreenInfo *screenInfo = _vm->_screen->getScreenInfo();
+
+ if (_vm->_debugger->_draggingRectangle == 0) {
+ // Not yet dragging a rectangle, so need click to start
+
+ if (button_down) {
+ // set both (x1,y1) and (x2,y2) to this point
+ _vm->_debugger->_rectX1 = _vm->_debugger->_rectX2 = (uint32)_pos.x + screenInfo->scroll_offset_x;
+ _vm->_debugger->_rectY1 = _vm->_debugger->_rectY2 = (uint32)_pos.y + screenInfo->scroll_offset_y;
+ _vm->_debugger->_draggingRectangle = 1;
+ }
+ } else if (_vm->_debugger->_draggingRectangle == 1) {
+ // currently dragging a rectangle - click means reset
+
+ if (button_down) {
+ // lock rectangle, so you can let go of mouse
+ // to type in the coords
+ _vm->_debugger->_draggingRectangle = 2;
+ } else {
+ // drag rectangle
+ _vm->_debugger->_rectX2 = (uint32)_pos.x + screenInfo->scroll_offset_x;
+ _vm->_debugger->_rectY2 = (uint32)_pos.y + screenInfo->scroll_offset_y;
+ }
+ } else {
+ // currently locked to avoid knocking out of place
+ // while reading off the coords
+
+ if (button_down) {
+ // click means reset - back to start again
+ _vm->_debugger->_draggingRectangle = 0;
+ }
+ }
+
+ return;
+ }
+
+#if RIGHT_CLICK_CLEARS_LUGGAGE
+ if (_vm->_logic->readVar(OBJECT_HELD) && (me->buttons & RD_RIGHTBUTTONDOWN) && heldIsInInventory()) {
+ _vm->_logic->writeVar(OBJECT_HELD, 0);
+ _menuSelectedPos = 0;
+ setLuggage(0);
+ return;
+ }
+#endif
+
+ // Now do the normal click stuff
+
+ // We only care about down clicks when the mouse is over an object.
+
+ if (!_mouseTouching || !button_down)
+ return;
+
+ // There's a mouse event to be processed and the mouse is on something.
+ // Notice that the floor itself is considered an object.
+
+ // There are no menus about so its nice and simple. This is as close to
+ // the old advisor_188 script as we get, I'm sorry to say.
+
+ // If player is walking or relaxing then those need to terminate
+ // correctly. Otherwise set player run the targets action script or, do
+ // a special walk if clicking on the scroll-more icon
+
+ // PLAYER_ACTION script variable - whatever catches this must reset to
+ // 0 again
+ // _vm->_logic->writeVar(PLAYER_ACTION, _mouseTouching);
+
+ // Idle or router-anim will catch it
+
+ // Set global script variable 'button'
+
+ if (me->buttons & RD_LEFTBUTTONDOWN) {
+ _vm->_logic->writeVar(LEFT_BUTTON, 1);
+ _vm->_logic->writeVar(RIGHT_BUTTON, 0);
+ _buttonClick = 0; // for re-click
+ } else {
+ _vm->_logic->writeVar(LEFT_BUTTON, 0);
+ _vm->_logic->writeVar(RIGHT_BUTTON, 1);
+ _buttonClick = 1; // for re-click
+ }
+
+ // These might be required by the action script about to be run
+ ScreenInfo *screenInfo = _vm->_screen->getScreenInfo();
+
+ _vm->_logic->writeVar(MOUSE_X, _pos.x + screenInfo->scroll_offset_x);
+ _vm->_logic->writeVar(MOUSE_Y, _pos.y + screenInfo->scroll_offset_y);
+
+ if (_mouseTouching == _vm->_logic->readVar(EXIT_CLICK_ID) && (me->buttons & RD_LEFTBUTTONDOWN)) {
+ // It's the exit double click situation. Let the existing
+ // interaction continue and start fading down. Switch the human
+ // off too
+
+ noHuman();
+ _vm->_logic->fnFadeDown(NULL);
+
+ // Tell the walker
+
+ _vm->_logic->writeVar(EXIT_FADING, 1);
+ } else if (_oldButton == _buttonClick && _mouseTouching == _vm->_logic->readVar(CLICKED_ID) && _mousePointerRes != NORMAL_MOUSE_ID) {
+ // Re-click. Do nothing, except on floors
+ } else {
+ // For re-click
+
+ _oldButton = _buttonClick;
+
+ // For scripts to know what's been clicked. First used for
+ // 'room_13_turning_script' in object 'biscuits_13'
+
+ _vm->_logic->writeVar(CLICKED_ID, _mouseTouching);
+
+ // Must clear these two double-click control flags - do it here
+ // so reclicks after exit clicks are cleared up
+
+ _vm->_logic->writeVar(EXIT_CLICK_ID, 0);
+ _vm->_logic->writeVar(EXIT_FADING, 0);
+
+ _vm->_logic->setPlayerActionEvent(CUR_PLAYER_ID, _mouseTouching);
+
+ byte buf1[NAME_LEN], buf2[NAME_LEN];
+
+ if (_vm->_logic->readVar(OBJECT_HELD))
+ debug(2, "Used \"%s\" on \"%s\"",
+ _vm->_resman->fetchName(_vm->_logic->readVar(OBJECT_HELD), buf1),
+ _vm->_resman->fetchName(_vm->_logic->readVar(CLICKED_ID), buf2));
+ else if (_vm->_logic->readVar(LEFT_BUTTON))
+ debug(2, "Left-clicked on \"%s\"",
+ _vm->_resman->fetchName(_vm->_logic->readVar(CLICKED_ID), buf1));
+ else // RIGHT BUTTON
+ debug(2, "Right-clicked on \"%s\"",
+ _vm->_resman->fetchName(_vm->_logic->readVar(CLICKED_ID), buf1));
+ }
+}
+
+uint32 Mouse::chooseMouse() {
+ // Unlike the other mouse "engines", this one is called directly by the
+ // fnChoose() opcode.
+
+ uint i;
+
+ _vm->_logic->writeVar(AUTO_SELECTED, 0);
+
+ uint32 in_subject = _vm->_logic->readVar(IN_SUBJECT);
+ uint32 object_held = _vm->_logic->readVar(OBJECT_HELD);
+
+ if (object_held) {
+ // The player used an object on a person. In this case it
+ // triggered a conversation menu. Act as if the user tried to
+ // talk to the person about that object. If the person doesn't
+ // know anything about it, use the default response.
+
+ uint32 response = _defaultResponseId;
+
+ for (i = 0; i < in_subject; i++) {
+ if (_subjectList[i].res == object_held) {
+ response = _subjectList[i].ref;
+ break;
+ }
+ }
+
+ // The user won't be holding the object any more, and the
+ // conversation menu will be closed.
+
+ _vm->_logic->writeVar(OBJECT_HELD, 0);
+ _vm->_logic->writeVar(IN_SUBJECT, 0);
+ return response;
+ }
+
+ if (_vm->_logic->readVar(CHOOSER_COUNT_FLAG) == 0 && in_subject == 1 && _subjectList[0].res == EXIT_ICON) {
+ // This is the first time the chooser is coming up in this
+ // conversation, there is only one subject and that's the
+ // EXIT icon.
+ //
+ // In other words, the player doesn't have anything to talk
+ // about. Skip it.
+
+ // The conversation menu will be closed. We set AUTO_SELECTED
+ // because the speech script depends on it.
+
+ _vm->_logic->writeVar(AUTO_SELECTED, 1);
+ _vm->_logic->writeVar(IN_SUBJECT, 0);
+ return _subjectList[0].ref;
+ }
+
+ byte *icon;
+
+ if (!_choosing) {
+ // This is a new conversation menu.
+
+ if (!in_subject)
+ error("fnChoose with no subjects");
+
+ for (i = 0; i < in_subject; i++) {
+ icon = _vm->_resman->openResource(_subjectList[i].res) + ResHeader::size() + RDMENU_ICONWIDE * RDMENU_ICONDEEP;
+ setMenuIcon(RDMENU_BOTTOM, i, icon);
+ _vm->_resman->closeResource(_subjectList[i].res);
+ }
+
+ for (; i < 15; i++)
+ setMenuIcon(RDMENU_BOTTOM, (uint8) i, NULL);
+
+ showMenu(RDMENU_BOTTOM);
+ setMouse(NORMAL_MOUSE_ID);
+ _choosing = true;
+ return (uint32)-1;
+ }
+
+ // The menu is there - we're just waiting for a click. We only care
+ // about left clicks.
+
+ MouseEvent *me = _vm->mouseEvent();
+ int mouseX, mouseY;
+
+ getPos(mouseX, mouseY);
+
+ if (!me || !(me->buttons & RD_LEFTBUTTONDOWN) || mouseY < 400)
+ return (uint32)-1;
+
+ // Check for click on a menu.
+
+ int hit = _vm->_mouse->menuClick(in_subject);
+ if (hit < 0)
+ return (uint32)-1;
+
+ // Hilight the clicked icon by greying the others. This can look a bit
+ // odd when you click on the exit icon, but there are also cases when
+ // it looks strange if you don't do it.
+
+ for (i = 0; i < in_subject; i++) {
+ if ((int)i != hit) {
+ icon = _vm->_resman->openResource(_subjectList[i].res) + ResHeader::size();
+ _vm->_mouse->setMenuIcon(RDMENU_BOTTOM, i, icon);
+ _vm->_resman->closeResource(_subjectList[i].res);
+ }
+ }
+
+ // For non-speech scripts that manually call the chooser
+ _vm->_logic->writeVar(RESULT, _subjectList[hit].res);
+
+ // The conversation menu will be closed
+
+ _choosing = false;
+ _vm->_logic->writeVar(IN_SUBJECT, 0);
+ setMouse(0);
+
+ return _subjectList[hit].ref;
+}
+
+void Mouse::mouseOnOff() {
+ // this handles the cursor graphic when moving on and off mouse areas
+ // it also handles the luggage thingy
+
+ uint32 pointer_type;
+ static uint8 mouse_flicked_off = 0;
+
+ _oldMouseTouching = _mouseTouching;
+
+ // don't detect objects that are hidden behind the menu bars (ie. in
+ // the scrolled-off areas of the screen)
+
+ if (_pos.y < 0 || _pos.y > 399) {
+ pointer_type = 0;
+ _mouseTouching = 0;
+ } else {
+ // set '_mouseTouching' & return pointer_type
+ pointer_type = checkMouseList();
+ }
+
+ // same as previous cycle?
+ if (!mouse_flicked_off && _oldMouseTouching == _mouseTouching) {
+ // yes, so nothing to do
+ // BUT CARRY ON IF MOUSE WAS FLICKED OFF!
+ return;
+ }
+
+ // can reset this now
+ mouse_flicked_off = 0;
+
+ //the cursor has moved onto something
+ if (!_oldMouseTouching && _mouseTouching) {
+ // make a copy of the object we've moved onto because one day
+ // we'll move back off again! (but the list positioning could
+ // theoretically have changed)
+
+ // we can only move onto something from being on nothing - we
+ // stop the system going from one to another when objects
+ // overlap
+
+ _oldMouseTouching = _mouseTouching;
+
+ // run get on
+
+ if (pointer_type) {
+ // 'pointer_type' holds the resource id of the
+ // pointer anim
+
+ setMouse(pointer_type);
+
+ // setup luggage icon
+ if (_vm->_logic->readVar(OBJECT_HELD)) {
+ setLuggage(_currentLuggageResource);
+ }
+ } else {
+ byte buf[NAME_LEN];
+
+ error("ERROR: mouse.pointer==0 for object %d (%s) - update logic script!", _mouseTouching, _vm->_resman->fetchName(_mouseTouching, buf));
+ }
+ } else if (_oldMouseTouching && !_mouseTouching) {
+ // the cursor has moved off something - reset cursor to
+ // normal pointer
+
+ _oldMouseTouching = 0;
+ setMouse(NORMAL_MOUSE_ID);
+
+ // reset luggage only when necessary
+ } else if (_oldMouseTouching && _mouseTouching) {
+ // The cursor has moved off something and onto something
+ // else. Flip to a blank cursor for a cycle.
+
+ // ignore the new id this cycle - should hit next cycle
+ _mouseTouching = 0;
+ _oldMouseTouching = 0;
+ setMouse(0);
+
+ // so we know to set the mouse pointer back to normal if 2nd
+ // hot-spot doesn't register because mouse pulled away
+ // quickly (onto nothing)
+
+ mouse_flicked_off = 1;
+
+ // reset luggage only when necessary
+ } else {
+ // Mouse was flicked off for one cycle, but then moved onto
+ // nothing before 2nd hot-spot registered
+
+ // both '_oldMouseTouching' & '_mouseTouching' will be zero
+ // reset cursor to normal pointer
+
+ setMouse(NORMAL_MOUSE_ID);
+ }
+
+ // possible check for edge of screen more-to-scroll here on large
+ // screens
+}
+
+void Mouse::setMouse(uint32 res) {
+ // high level - whats the mouse - for the engine
+ _mousePointerRes = res;
+
+ if (res) {
+ byte *icon = _vm->_resman->openResource(res) + ResHeader::size();
+ uint32 len = _vm->_resman->fetchLen(res) - ResHeader::size();
+
+ // don't pulse the normal pointer - just do the regular anim
+ // loop
+
+ if (res == NORMAL_MOUSE_ID)
+ setMouseAnim(icon, len, RDMOUSE_NOFLASH);
+ else
+ setMouseAnim(icon, len, RDMOUSE_FLASH);
+
+ _vm->_resman->closeResource(res);
+ } else {
+ // blank cursor
+ setMouseAnim(NULL, 0, 0);
+ }
+}
+
+void Mouse::setLuggage(uint32 res) {
+ _realLuggageItem = res;
+
+ if (res) {
+ byte *icon = _vm->_resman->openResource(res) + ResHeader::size();
+ uint32 len = _vm->_resman->fetchLen(res) - ResHeader::size();
+
+ setLuggageAnim(icon, len);
+ _vm->_resman->closeResource(res);
+ } else
+ setLuggageAnim(NULL, 0);
+}
+
+void Mouse::setObjectHeld(uint32 res) {
+ setLuggage(res);
+
+ _vm->_logic->writeVar(OBJECT_HELD, res);
+ _currentLuggageResource = res;
+
+ // mode locked - no menu available
+ _mouseModeLocked = true;
+}
+
+uint32 Mouse::checkMouseList() {
+ ScreenInfo *screenInfo = _vm->_screen->getScreenInfo();
+
+ Common::Point mousePos(_pos.x + screenInfo->scroll_offset_x, _pos.y + screenInfo->scroll_offset_y);
+
+ // Number of priorities subject to implementation needs
+ for (int priority = 0; priority < 10; priority++) {
+ for (uint i = 0; i < _curMouse; i++) {
+ // If the mouse pointer is over this
+ // mouse-detection-box
+
+ if (_mouseList[i].priority == priority && _mouseList[i].rect.contains(mousePos)) {
+ // Record id
+ _mouseTouching = _mouseList[i].id;
+
+ createPointerText(_mouseList[i].pointer_text, _mouseList[i].pointer);
+
+ // Return pointer type
+ return _mouseList[i].pointer;
+ }
+ }
+ }
+
+ // Touching nothing; no pointer to return
+ _mouseTouching = 0;
+ return 0;
+}
+
+#define POINTER_TEXT_WIDTH 640 // just in case!
+#define POINTER_TEXT_PEN 184 // white
+
+void Mouse::createPointerText(uint32 text_id, uint32 pointer_res) {
+ uint32 local_text;
+ uint32 text_res;
+ byte *text;
+ // offsets for pointer text sprite from pointer position
+ int16 xOffset, yOffset;
+ uint8 justification;
+
+ if (!_objectLabels || !text_id)
+ return;
+
+ // Check what the pointer is, to set offsets correctly for text
+ // position
+
+ switch (pointer_res) {
+ case CROSHAIR:
+ yOffset = -7;
+ xOffset = +10;
+ break;
+ case EXIT0:
+ yOffset = +15;
+ xOffset = +20;
+ break;
+ case EXIT1:
+ yOffset = +16;
+ xOffset = -10;
+ break;
+ case EXIT2:
+ yOffset = +10;
+ xOffset = -22;
+ break;
+ case EXIT3:
+ yOffset = -16;
+ xOffset = -10;
+ break;
+ case EXIT4:
+ yOffset = -15;
+ xOffset = +15;
+ break;
+ case EXIT5:
+ yOffset = -12;
+ xOffset = +10;
+ break;
+ case EXIT6:
+ yOffset = +10;
+ xOffset = +25;
+ break;
+ case EXIT7:
+ yOffset = +16;
+ xOffset = +20;
+ break;
+ case EXITDOWN:
+ yOffset = -20;
+ xOffset = -10;
+ break;
+ case EXITUP:
+ yOffset = +20;
+ xOffset = +20;
+ break;
+ case MOUTH:
+ yOffset = -10;
+ xOffset = +15;
+ break;
+ case NORMAL:
+ yOffset = -10;
+ xOffset = +15;
+ break;
+ case PICKUP:
+ yOffset = -40;
+ xOffset = +10;
+ break;
+ case SCROLL_L:
+ yOffset = -20;
+ xOffset = +20;
+ break;
+ case SCROLL_R:
+ yOffset = -20;
+ xOffset = -20;
+ break;
+ case USE:
+ yOffset = -8;
+ xOffset = +20;
+ break;
+ default:
+ // Shouldn't happen if we cover all the different mouse
+ // pointers above
+ yOffset = -10;
+ xOffset = +10;
+ break;
+ }
+
+ // Set up justification for text sprite, based on its offsets from the
+ // pointer position
+
+ if (yOffset < 0) {
+ // Above pointer
+ if (xOffset < 0) {
+ // Above left
+ justification = POSITION_AT_RIGHT_OF_BASE;
+ } else if (xOffset > 0) {
+ // Above right
+ justification = POSITION_AT_LEFT_OF_BASE;
+ } else {
+ // Above centre
+ justification = POSITION_AT_CENTRE_OF_BASE;
+ }
+ } else if (yOffset > 0) {
+ // Below pointer
+ if (xOffset < 0) {
+ // Below left
+ justification = POSITION_AT_RIGHT_OF_TOP;
+ } else if (xOffset > 0) {
+ // Below right
+ justification = POSITION_AT_LEFT_OF_TOP;
+ } else {
+ // Below centre
+ justification = POSITION_AT_CENTRE_OF_TOP;
+ }
+ } else {
+ // Same y-coord as pointer
+ if (xOffset < 0) {
+ // Centre left
+ justification = POSITION_AT_RIGHT_OF_CENTRE;
+ } else if (xOffset > 0) {
+ // Centre right
+ justification = POSITION_AT_LEFT_OF_CENTRE;
+ } else {
+ // Centre centre - shouldn't happen anyway!
+ justification = POSITION_AT_LEFT_OF_CENTRE;
+ }
+ }
+
+ // Text resource number, and line number within the resource
+
+ text_res = text_id / SIZE;
+ local_text = text_id & 0xffff;
+
+ // open text file & get the line
+ text = _vm->fetchTextLine(_vm->_resman->openResource(text_res), local_text);
+
+ // 'text+2' to skip the first 2 bytes which form the
+ // line reference number
+
+ _pointerTextBlocNo = _vm->_fontRenderer->buildNewBloc(
+ text + 2, _pos.x + xOffset,
+ _pos.y + yOffset,
+ POINTER_TEXT_WIDTH, POINTER_TEXT_PEN,
+ RDSPR_TRANS | RDSPR_DISPLAYALIGN,
+ _vm->_speechFontId, justification);
+
+ // now ok to close the text file
+ _vm->_resman->closeResource(text_res);
+}
+
+void Mouse::clearPointerText() {
+ if (_pointerTextBlocNo) {
+ _vm->_fontRenderer->killTextBloc(_pointerTextBlocNo);
+ _pointerTextBlocNo = 0;
+ }
+}
+
+void Mouse::hideMouse() {
+ // leaves the menus open
+ // used by the system when clicking right on a menu item to examine
+ // it and when combining objects
+
+ // for logic scripts
+ _vm->_logic->writeVar(MOUSE_AVAILABLE, 0);
+
+ // human/mouse off
+ _mouseStatus = true;
+
+ setMouse(0);
+ setLuggage(0);
+}
+
+void Mouse::noHuman() {
+ hideMouse();
+ clearPointerText();
+
+ // Must be normal mouse situation or a largely neutral situation -
+ // special menus use hideMouse()
+
+ // Don't hide menu in conversations
+ if (_vm->_logic->readVar(TALK_FLAG) == 0)
+ hideMenu(RDMENU_BOTTOM);
+
+ if (_mouseMode == MOUSE_system_menu) {
+ // Close menu
+ _mouseMode = MOUSE_normal;
+ hideMenu(RDMENU_TOP);
+ }
+}
+
+void Mouse::addHuman() {
+ // For logic scripts
+ _vm->_logic->writeVar(MOUSE_AVAILABLE, 1);
+
+ if (_mouseStatus) {
+ // Force engine to choose a cursor
+ _mouseStatus = false;
+ _mouseTouching = 1;
+ }
+
+ // Clear this to reset no-second-click system
+ _vm->_logic->writeVar(CLICKED_ID, 0);
+
+ // This is now done outside the OBJECT_HELD check in case it's set to
+ // zero before now!
+
+ // Unlock the mouse from possible large object lock situtations - see
+ // syphon in rm 3
+
+ _mouseModeLocked = false;
+
+ if (_vm->_logic->readVar(OBJECT_HELD)) {
+ // Was dragging something around - need to clear this again
+ _vm->_logic->writeVar(OBJECT_HELD, 0);
+
+ // And these may also need clearing, just in case
+ _examiningMenuIcon = false;
+ _vm->_logic->writeVar(COMBINE_BASE, 0);
+
+ setLuggage(0);
+ }
+
+ // If mouse is over menu area
+ if (_pos.y > 399) {
+ if (_mouseMode != MOUSE_holding) {
+ // VITAL - reset things & rebuild the menu
+ _mouseMode = MOUSE_normal;
+ }
+ setMouse(NORMAL_MOUSE_ID);
+ }
+
+ // Enabled/disabled from console; status printed with on-screen debug
+ // info
+
+ if (_vm->_debugger->_testingSnR) {
+ uint8 black[4] = { 0, 0, 0, 0 };
+ uint8 white[4] = { 255, 255, 255, 0 };
+
+ // Testing logic scripts by simulating instant Save & Restore
+
+ _vm->_screen->setPalette(0, 1, white, RDPAL_INSTANT);
+
+ // Stops all fx & clears the queue - eg. when leaving a room
+ _vm->_sound->clearFxQueue();
+
+ // Trash all object resources so they load in fresh & restart
+ // their logic scripts
+
+ _vm->_resman->killAllObjects(false);
+
+ _vm->_screen->setPalette(0, 1, black, RDPAL_INSTANT);
+ }
+}
+
+void Mouse::refreshInventory() {
+ // Can reset this now
+ _vm->_logic->writeVar(COMBINE_BASE, 0);
+
+ // Cause 'object_held' icon to be greyed. The rest are coloured.
+ _examiningMenuIcon = true;
+ buildMenu();
+ _examiningMenuIcon = false;
+}
+
+void Mouse::startConversation() {
+ if (_vm->_logic->readVar(TALK_FLAG) == 0) {
+ // See fnChooser & speech scripts
+ _vm->_logic->writeVar(CHOOSER_COUNT_FLAG, 0);
+ }
+
+ noHuman();
+}
+
+void Mouse::endConversation() {
+ hideMenu(RDMENU_BOTTOM);
+
+ if (_pos.y > 399) {
+ // Will wait for cursor to move off the bottom menu
+ _mouseMode = MOUSE_holding;
+ }
+
+ // In case DC forgets
+ _vm->_logic->writeVar(TALK_FLAG, 0);
+}
+
+void Mouse::monitorPlayerActivity() {
+ // if there is at least one mouse event outstanding
+ if (_vm->checkForMouseEvents()) {
+ // reset activity delay counter
+ _playerActivityDelay = 0;
+ } else {
+ // no. of game cycles since mouse event queue last empty
+ _playerActivityDelay++;
+ }
+}
+
+void Mouse::checkPlayerActivity(uint32 seconds) {
+ // Convert seconds to game cycles
+ uint32 threshold = seconds * 12;
+
+ // If the actual delay is at or above the given threshold, reset the
+ // activity delay counter now that we've got a positive check.
+
+ if (_playerActivityDelay >= threshold) {
+ _playerActivityDelay = 0;
+ _vm->_logic->writeVar(RESULT, 1);
+ } else
+ _vm->_logic->writeVar(RESULT, 0);
+}
+
+void Mouse::pauseGame() {
+ // Make the mouse cursor normal. This is the only place where we are
+ // allowed to clear the luggage this way.
+
+ clearPointerText();
+ setLuggageAnim(NULL, 0);
+ setMouse(0);
+ setMouseTouching(1);
+}
+
+void Mouse::unpauseGame() {
+ if (_vm->_logic->readVar(OBJECT_HELD) && _realLuggageItem)
+ setLuggage(_realLuggageItem);
+}
+
+} // End of namespace Sword2
diff --git a/engines/sword2/mouse.h b/engines/sword2/mouse.h
new file mode 100644
index 0000000000..bed5e032ba
--- /dev/null
+++ b/engines/sword2/mouse.h
@@ -0,0 +1,257 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#ifndef MOUSE_H
+#define MOUSE_H
+
+#define MAX_SUBJECT_LIST 30 // is that enough?
+
+#define TOTAL_mouse_list 50
+
+namespace Sword2 {
+
+struct BuildUnit;
+
+// Menubar defines.
+
+#define RDMENU_TOP 0
+#define RDMENU_BOTTOM 1
+
+enum {
+ MOUSE_normal = 0, // normal in game
+ MOUSE_menu = 1, // menu chooser
+ MOUSE_drag = 2, // dragging luggage
+ MOUSE_system_menu = 3, // system menu chooser
+ MOUSE_holding = 4 // special
+};
+
+enum {
+ RDMOUSE_NOFLASH,
+ RDMOUSE_FLASH
+};
+
+enum {
+ RDMENU_HIDDEN,
+ RDMENU_SHOWN,
+ RDMENU_OPENING,
+ RDMENU_CLOSING,
+ RDMENU_ALMOST_HIDDEN
+};
+
+#define RDMENU_ICONWIDE 35
+#define RDMENU_ICONDEEP 30
+#define RDMENU_ICONSTART 24
+#define RDMENU_ICONSPACING 5
+#define RDMENU_MAXPOCKETS 15
+
+#define MOUSE_ANIM_HEADER_SIZE 6
+
+struct MouseAnim {
+ uint8 runTimeComp; // type of runtime compression used for the
+ // frame data
+ uint8 noAnimFrames; // number of frames in the anim
+ int8 xHotSpot;
+ int8 yHotSpot;
+ uint8 mousew;
+ uint8 mouseh;
+
+ byte *data;
+};
+
+// The MOUSE_holding mode is entered when the conversation menu is closed, and
+// exited when the mouse cursor moves off that menu area. I don't know why yet.
+
+// mouse unit - like ObjectMouse, but with anim resource & pc (needed if
+// sprite is to act as mouse detection mask)
+
+struct MouseUnit {
+ // Basically the same information as in ObjectMouse, except the
+ // coordinates are adjusted to conform to standard ScummVM usage.
+
+ Common::Rect rect;
+ int32 priority;
+ int32 pointer;
+
+ // In addition, we need an id when checking the mouse list, and a
+ // text id for mouse-overs.
+
+ int32 id;
+ int32 pointer_text;
+};
+
+// Array of these for subject menu build up
+
+ struct SubjectUnit {
+ uint32 res;
+ uint32 ref;
+};
+
+class Mouse {
+private:
+ Sword2Engine *_vm;
+
+ Common::Point _pos;
+
+ MouseUnit _mouseList[TOTAL_mouse_list];
+ uint32 _curMouse;
+
+ MenuObject _tempList[TOTAL_engine_pockets];
+ uint32 _totalTemp;
+
+ MenuObject _masterMenuList[TOTAL_engine_pockets];
+ uint32 _totalMasters;
+
+ SubjectUnit _subjectList[MAX_SUBJECT_LIST];
+
+ // ref number for default response when luggage icon is used on a
+ // person & it doesn't match any of the icons which would have been in
+ // the chooser
+
+ uint32 _defaultResponseId;
+
+ // could alternately use logic->looping of course
+ bool _choosing;
+
+ uint8 _menuStatus[2];
+ byte *_icons[2][RDMENU_MAXPOCKETS];
+ uint8 _pocketStatus[2][RDMENU_MAXPOCKETS];
+
+ uint8 _iconCount;
+
+ // If it's NORMAL_MOUSE_ID (ie. normal pointer) then it's over a floor
+ // area (or hidden hot-zone)
+
+ uint32 _mousePointerRes;
+
+ MouseAnim _mouseAnim;
+ MouseAnim _luggageAnim;
+
+ uint8 _mouseFrame;
+
+ uint32 _mouseMode;
+
+ bool _mouseStatus; // Human 0 on/1 off
+ bool _mouseModeLocked; // 0 not !0 mode cannot be changed from
+ // normal mouse to top menu (i.e. when
+ // carrying big objects)
+ uint32 _realLuggageItem; // Last minute for pause mode
+ uint32 _currentLuggageResource;
+ uint32 _oldButton; // For the re-click stuff - must be
+ // the same button you see
+ uint32 _buttonClick;
+ uint32 _pointerTextBlocNo;
+ uint32 _playerActivityDelay; // Player activity delay counter
+
+ bool _examiningMenuIcon;
+
+ // Set by checkMouseList()
+ uint32 _mouseTouching;
+ uint32 _oldMouseTouching;
+
+ bool _objectLabels;
+
+ uint32 _menuSelectedPos;
+
+ void decompressMouse(byte *decomp, byte *comp, uint8 frame, int width, int height, int pitch, int xOff = 0, int yOff = 0);
+
+ int32 setMouseAnim(byte *ma, int32 size, int32 mouseFlash);
+ int32 setLuggageAnim(byte *la, int32 size);
+
+ void clearIconArea(int menu, int pocket, Common::Rect *r);
+
+public:
+ Mouse(Sword2Engine *vm);
+ ~Mouse();
+
+ void getPos(int &x, int &y);
+ void setPos(int x, int y);
+
+ bool getObjectLabels() { return _objectLabels; }
+ void setObjectLabels(bool b) { _objectLabels = b; }
+
+ bool getMouseStatus() { return _mouseStatus; }
+ uint32 getMouseTouching() { return _mouseTouching; }
+ void setMouseTouching(uint32 touching) { _mouseTouching = touching; }
+
+ void pauseGame();
+ void unpauseGame();
+
+ void setMouse(uint32 res);
+ void setLuggage(uint32 res);
+
+ void setObjectHeld(uint32 res);
+
+ void resetMouseList();
+
+ void registerMouse(byte *ob_mouse, BuildUnit *build_unit);
+ void registerPointerText(int32 text_id);
+
+ void createPointerText(uint32 text_id, uint32 pointer_res);
+ void clearPointerText();
+
+ void drawMouse();
+ int32 animateMouse();
+
+ void processMenu();
+
+ void addMenuObject(byte *ptr);
+ void addSubject(int32 id, int32 ref);
+
+ void buildMenu();
+ void buildSystemMenu();
+
+ int32 showMenu(uint8 menu);
+ int32 hideMenu(uint8 menu);
+ int32 setMenuIcon(uint8 menu, uint8 pocket, byte *icon);
+
+ void closeMenuImmediately();
+
+ void refreshInventory();
+
+ void startConversation();
+ void endConversation();
+
+ void hideMouse();
+ void noHuman();
+ void addHuman();
+
+ void resetPlayerActivityDelay() { _playerActivityDelay = 0; }
+ void monitorPlayerActivity();
+ void checkPlayerActivity(uint32 seconds);
+
+ void mouseOnOff();
+ uint32 checkMouseList();
+ void mouseEngine();
+
+ void normalMouse();
+ void menuMouse();
+ void dragMouse();
+ void systemMenuMouse();
+
+ bool isChoosing() { return _choosing; }
+ uint32 chooseMouse();
+
+ int menuClick(int menu_items);
+};
+
+} // End of namespace Sword2
+
+#endif
diff --git a/engines/sword2/music.cpp b/engines/sword2/music.cpp
new file mode 100644
index 0000000000..847f4808cf
--- /dev/null
+++ b/engines/sword2/music.cpp
@@ -0,0 +1,856 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+// One feature still missing is the original's DipMusic() function which, as
+// far as I can understand, softened the music volume when someone was
+// speaking, but only (?) if the music was playing loudly at the time.
+//
+// All things considered, I think this is more bother than it's worth.
+
+#include "common/stdafx.h"
+#include "common/file.h"
+#include "common/system.h"
+#include "sound/mp3.h"
+#include "sound/vorbis.h"
+#include "sound/flac.h"
+#include "sound/rate.h"
+#include "sound/wave.h"
+
+#include "sword2/sword2.h"
+#include "sword2/defs.h"
+#include "sword2/resman.h"
+#include "sword2/sound.h"
+
+namespace Sword2 {
+
+static AudioStream *makeCLUStream(Common::File *fp, int size);
+
+static AudioStream *getAudioStream(SoundFileHandle *fh, const char *base, int cd, uint32 id, uint32 *numSamples) {
+ debug(3, "Playing %s from CD %d", base, cd);
+
+ if (!fh->file.isOpen()) {
+ struct {
+ const char *ext;
+ int mode;
+ } file_types[] = {
+ #ifdef USE_MAD
+ { "cl3", kMP3Mode },
+ #endif
+ #ifdef USE_VORBIS
+ { "clg", kVorbisMode },
+ #endif
+ #ifdef USE_FLAC
+ { "clf", kFlacMode },
+ #endif
+ { "clu", kCLUMode }
+ };
+
+ int soundMode = 0;
+ char filename[20];
+
+ for (int i = 0; i < ARRAYSIZE(file_types); i++) {
+ Common::File f;
+
+ sprintf(filename, "%s%d.%s", base, cd, file_types[i].ext);
+ if (f.open(filename)) {
+ soundMode = file_types[i].mode;
+ break;
+ }
+
+ sprintf(filename, "%s.%s", base, file_types[i].ext);
+ if (f.open(filename)) {
+ soundMode = file_types[i].mode;
+ break;
+ }
+ }
+
+ if (soundMode == 0)
+ return NULL;
+
+ fh->file.open(filename);
+ fh->fileType = soundMode;
+ if (!fh->file.isOpen()) {
+ warning("Very strange fopen error");
+ return NULL;
+ }
+ if (fh->fileSize != fh->file.size()) {
+ if (fh->idxTab) {
+ free(fh->idxTab);
+ fh->idxTab = NULL;
+ }
+ }
+ }
+
+ uint32 entrySize = (fh->fileType == kCLUMode) ? 2 : 3;
+
+ if (!fh->idxTab) {
+ fh->file.seek(0);
+ fh->idxLen = fh->file.readUint32LE();
+ fh->file.seek(entrySize * 4);
+
+ fh->idxTab = (uint32*)malloc(fh->idxLen * 3 * sizeof(uint32));
+ for (uint32 cnt = 0; cnt < fh->idxLen; cnt++) {
+ fh->idxTab[cnt * 3 + 0] = fh->file.readUint32LE();
+ fh->idxTab[cnt * 3 + 1] = fh->file.readUint32LE();
+ if (fh->fileType == kCLUMode) {
+ fh->idxTab[cnt * 3 + 2] = fh->idxTab[cnt * 3 + 1];
+ fh->idxTab[cnt * 3 + 1]--;
+ } else
+ fh->idxTab[cnt * 3 + 2] = fh->file.readUint32LE();
+ }
+ }
+
+ uint32 pos = fh->idxTab[id * 3 + 0];
+ uint32 len = fh->idxTab[id * 3 + 1];
+ uint32 enc_len = fh->idxTab[id * 3 + 2];
+
+ if (numSamples)
+ *numSamples = len;
+
+ if (!pos || !len) {
+ fh->file.close();
+ return NULL;
+ }
+
+ fh->file.seek(pos, SEEK_SET);
+
+ switch (fh->fileType) {
+ case kCLUMode:
+ return makeCLUStream(&fh->file, enc_len);
+#ifdef USE_MAD
+ case kMP3Mode:
+ return makeMP3Stream(&fh->file, enc_len);
+#endif
+#ifdef USE_VORBIS
+ case kVorbisMode:
+ return makeVorbisStream(&fh->file, enc_len);
+#endif
+#ifdef USE_FLAC
+ case kFlacMode:
+ return makeFlacStream(&fh->file, enc_len);
+#endif
+ default:
+ return NULL;
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Custom AudioStream class to handle Broken Sword 2's audio compression.
+// ----------------------------------------------------------------------------
+
+#define GetCompressedShift(n) ((n) >> 4)
+#define GetCompressedSign(n) (((n) >> 3) & 1)
+#define GetCompressedAmplitude(n) ((n) & 7)
+
+CLUInputStream::CLUInputStream(Common::File *file, int size)
+ : _file(file), _firstTime(true), _bufferEnd(_outbuf + BUFFER_SIZE) {
+
+ _file->incRef();
+
+ // Determine the end position.
+ _file_pos = _file->pos();
+ _end_pos = _file_pos + size;
+
+ // Read in initial data
+ refill();
+}
+
+CLUInputStream::~CLUInputStream() {
+ _file->decRef();
+}
+
+int CLUInputStream::readBuffer(int16 *buffer, const int numSamples) {
+ int samples = 0;
+ while (samples < numSamples && !eosIntern()) {
+ const int len = MIN(numSamples - samples, (int)(_bufferEnd - _pos));
+ memcpy(buffer, _pos, len * 2);
+ buffer += len;
+ _pos += len;
+ samples += len;
+ if (_pos >= _bufferEnd) {
+ refill();
+ }
+ }
+ return samples;
+}
+
+void CLUInputStream::refill() {
+ byte *in = _inbuf;
+ int16 *out = _outbuf;
+
+ _file->seek(_file_pos, SEEK_SET);
+
+ uint len_left = _file->read(in, MIN((uint32)BUFFER_SIZE, _end_pos - _file->pos()));
+
+ _file_pos = _file->pos();
+
+ while (len_left > 0) {
+ uint16 sample;
+
+ if (_firstTime) {
+ _firstTime = false;
+ _prev = READ_LE_UINT16(in);
+ sample = _prev;
+ len_left -= 2;
+ in += 2;
+ } else {
+ uint16 delta = GetCompressedAmplitude(*in) << GetCompressedShift(*in);
+ if (GetCompressedSign(*in))
+ sample = _prev - delta;
+ else
+ sample = _prev + delta;
+
+ _prev = sample;
+ len_left--;
+ in++;
+ }
+
+ *out++ = sample;
+ }
+
+ _pos = _outbuf;
+ _bufferEnd = out;
+}
+
+AudioStream *makeCLUStream(Common::File *file, int size) {
+ return new CLUInputStream(file, size);
+}
+
+// ----------------------------------------------------------------------------
+// Another custom AudioStream class, to wrap around the various AudioStream
+// classes used for music decompression, and to add looping, fading, etc.
+// ----------------------------------------------------------------------------
+
+// The length of a fade-in/out, in milliseconds.
+#define FADE_LENGTH 3000
+
+MusicInputStream::MusicInputStream(int cd, SoundFileHandle *fh, uint32 musicId, bool looping) {
+ _cd = cd;
+ _fh = fh;
+ _musicId = musicId;
+ _looping = looping;
+
+ _bufferEnd = _buffer + BUFFER_SIZE;
+ _remove = false;
+ _fading = 0;
+
+ _decoder = getAudioStream(_fh, "music", _cd, _musicId, &_numSamples);
+ if (_decoder) {
+ _samplesLeft = _numSamples;
+ _fadeSamples = (getRate() * FADE_LENGTH) / 1000;
+ fadeUp();
+
+ // Read in initial data
+ refill();
+ }
+}
+
+MusicInputStream::~MusicInputStream() {
+ delete _decoder;
+}
+
+int MusicInputStream::readBuffer(int16 *buffer, const int numSamples) {
+ if (!_decoder)
+ return 0;
+
+ int samples = 0;
+ while (samples < numSamples && !eosIntern()) {
+ const int len = MIN(numSamples - samples, (int)(_bufferEnd - _pos));
+ memcpy(buffer, _pos, len * 2);
+ buffer += len;
+ _pos += len;
+ samples += len;
+ if (_pos >= _bufferEnd) {
+ refill();
+ }
+ }
+ return samples;
+}
+
+void MusicInputStream::refill() {
+ int16 *buf = _buffer;
+ uint32 numSamples = 0;
+ uint32 len_left;
+ bool endFade = false;
+
+ len_left = BUFFER_SIZE;
+
+ if (_fading > 0 && (uint32)_fading < len_left)
+ len_left = _fading;
+
+ if (_samplesLeft < len_left)
+ len_left = _samplesLeft;
+
+ if (!_looping) {
+ // Non-looping music is faded out at the end. If this fade
+ // out would have started somewhere within the len_left samples
+ // to read, we only read up to that point. This way, we can
+ // treat this fade as any other.
+
+ if (!_fading) {
+ uint32 currentlyAt = _numSamples - _samplesLeft;
+ uint32 fadeOutAt = _numSamples - _fadeSamples;
+ uint32 readTo = currentlyAt + len_left;
+
+ if (fadeOutAt == currentlyAt)
+ fadeDown();
+ else if (fadeOutAt > currentlyAt && fadeOutAt <= readTo) {
+ len_left = fadeOutAt - currentlyAt;
+ endFade = true;
+ }
+ }
+ }
+
+ int desired = len_left - numSamples;
+ int len = _decoder->readBuffer(buf, desired);
+
+ // Shouldn't happen, but if it does it could cause an infinite loop.
+ // Of course there were bugs that caused it to happen several times
+ // during development. :-)
+
+ if (len < desired) {
+ warning("Expected %d samples, but got %d", desired, len);
+ _samplesLeft = len;
+ }
+
+ buf += len;
+ numSamples += len;
+ len_left -= len;
+ _samplesLeft -= len;
+
+ int16 *ptr;
+
+ if (_fading > 0) {
+ // Fade down
+ for (ptr = _buffer; ptr < buf; ptr++) {
+ if (_fading > 0) {
+ _fading--;
+ *ptr = (*ptr * _fading) / _fadeSamples;
+ }
+ if (_fading == 0) {
+ _looping = false;
+ _remove = true;
+ *ptr = 0;
+ }
+ }
+ } else if (_fading < 0) {
+ // Fade up
+ for (ptr = _buffer; ptr < buf; ptr++) {
+ _fading--;
+ *ptr = -(*ptr * _fading) / _fadeSamples;
+ if (_fading <= -_fadeSamples) {
+ _fading = 0;
+ break;
+ }
+ }
+ }
+
+ if (endFade)
+ fadeDown();
+
+ if (!_samplesLeft) {
+ if (_looping) {
+ delete _decoder;
+ _decoder = getAudioStream(_fh, "music", _cd, _musicId, &_numSamples);
+ _samplesLeft = _numSamples;
+ } else
+ _remove = true;
+ }
+
+ _pos = _buffer;
+ _bufferEnd = buf;
+}
+
+void MusicInputStream::fadeUp() {
+ if (_fading > 0)
+ _fading = -_fading;
+ else if (_fading == 0)
+ _fading = -1;
+}
+
+void MusicInputStream::fadeDown() {
+ if (_fading < 0)
+ _fading = -_fading;
+ else if (_fading == 0)
+ _fading = _fadeSamples;
+}
+
+bool MusicInputStream::readyToRemove() {
+ return _remove;
+}
+
+int32 MusicInputStream::getTimeRemaining() {
+ // This is far from exact, but it doesn't have to be.
+ return (_samplesLeft + BUFFER_SIZE) / getRate();
+}
+
+// ----------------------------------------------------------------------------
+// Main sound class
+// ----------------------------------------------------------------------------
+
+// AudioStream API
+
+int Sound::readBuffer(int16 *buffer, const int numSamples) {
+ Common::StackLock lock(_mutex);
+ int i;
+
+ if (_musicPaused)
+ return 0;
+
+ for (i = 0; i < MAXMUS; i++) {
+ if (_music[i] && _music[i]->readyToRemove()) {
+ delete _music[i];
+ _music[i] = NULL;
+ }
+ }
+
+ memset(buffer, 0, 2 * numSamples);
+
+ if (!_mixBuffer || numSamples > _mixBufferLen) {
+ if (_mixBuffer)
+ _mixBuffer = (int16 *)realloc(_mixBuffer, 2 * numSamples);
+ else
+ _mixBuffer = (int16 *)malloc(2 * numSamples);
+
+ _mixBufferLen = numSamples;
+ }
+
+ if (!_mixBuffer)
+ return 0;
+
+ for (i = 0; i < MAXMUS; i++) {
+ if (!_music[i])
+ continue;
+
+ int len = _music[i]->readBuffer(_mixBuffer, numSamples);
+
+ if (!_musicMuted) {
+ for (int j = 0; j < len; j++) {
+ Audio::clampedAdd(buffer[j], _mixBuffer[j]);
+ }
+ }
+ }
+
+ bool inUse[MAXMUS];
+
+ for (i = 0; i < MAXMUS; i++)
+ inUse[i] = false;
+
+ for (i = 0; i < MAXMUS; i++) {
+ if (_music[i]) {
+ if (_music[i]->getCD() == 1)
+ inUse[0] = true;
+ else
+ inUse[1] = true;
+ }
+ }
+
+ for (i = 0; i < MAXMUS; i++) {
+ if (!inUse[i] && !_musicFile[i].inUse && _musicFile[i].file.isOpen())
+ _musicFile[i].file.close();
+ }
+
+ return numSamples;
+}
+
+bool Sound::endOfData() const {
+ for (int i = 0; i < MAXMUS; i++) {
+ if (_musicFile[i].file.isOpen())
+ return false;
+ }
+
+ return true;
+}
+
+// ----------------------------------------------------------------------------
+// MUSIC
+// ----------------------------------------------------------------------------
+
+/**
+ * Stops the music dead in its tracks. Any music that is currently being
+ * streamed is paused.
+ */
+
+void Sound::pauseMusic() {
+ Common::StackLock lock(_mutex);
+
+ _musicPaused = true;
+}
+
+/**
+ * Restarts the music from where it was stopped.
+ */
+
+void Sound::unpauseMusic() {
+ Common::StackLock lock(_mutex);
+
+ _musicPaused = false;
+}
+
+/**
+ * Fades out and stops the music.
+ */
+
+void Sound::stopMusic(bool immediately) {
+ Common::StackLock lock(_mutex);
+
+ _loopingMusicId = 0;
+
+ for (int i = 0; i < MAXMUS; i++) {
+ if (_music[i]) {
+ if (immediately) {
+ delete _music[i];
+ _music[i] = NULL;
+ } else
+ _music[i]->fadeDown();
+ }
+ }
+}
+
+/**
+ * Streams music from a cluster file.
+ * @param musicId the id of the music to stream
+ * @param loop true if the music is to loop back to the start
+ * @return RD_OK or an error code
+ */
+int32 Sound::streamCompMusic(uint32 musicId, bool loop) {
+ //Common::StackLock lock(_mutex);
+
+ _mutex.lock();
+ int cd = _vm->_resman->getCD();
+
+ if (loop)
+ _loopingMusicId = musicId;
+ else
+ _loopingMusicId = 0;
+
+ int primary = -1;
+ int secondary = -1;
+
+ // If both music streams are active, one of them will have to go.
+
+ if (_music[0] && _music[1]) {
+ int32 fade0 = _music[0]->isFading();
+ int32 fade1 = _music[1]->isFading();
+
+ if (!fade0 && !fade1) {
+ // Neither is fading. This shouldn't happen, so just
+ // pick one and be done with it.
+ primary = 0;
+ } else if (fade0 && !fade1) {
+ // Stream 0 is fading, so pick that one.
+ primary = 0;
+ } else if (!fade0 && fade1) {
+ // Stream 1 is fading, so pick that one.
+ primary = 1;
+ } else {
+ // Both streams are fading. Pick the one that is
+ // closest to silent.
+ if (ABS(fade0) < ABS(fade1))
+ primary = 0;
+ else
+ primary = 1;
+ }
+
+ delete _music[primary];
+ _music[primary] = NULL;
+ }
+
+ // Pick the available music stream. If no music is playing it doesn't
+ // matter which we use.
+
+ if (_music[0] || _music[1]) {
+ if (_music[0]) {
+ primary = 1;
+ secondary = 0;
+ } else {
+ primary = 0;
+ secondary = 1;
+ }
+ } else
+ primary = 0;
+
+ // Don't start streaming if the volume is off.
+ if (isMusicMute()) {
+ _mutex.unlock();
+ return RD_OK;
+ }
+
+ if (secondary != -1)
+ _music[secondary]->fadeDown();
+ SoundFileHandle *fh = (cd == 1) ? &_musicFile[0] : &_musicFile[1];
+ fh->inUse = true;
+ _mutex.unlock();
+
+ MusicInputStream *tmp = new MusicInputStream(cd, fh, musicId, loop);
+
+ if (tmp->isReady()) {
+ _mutex.lock();
+ _music[primary] = tmp;
+ fh->inUse = false;
+ _mutex.unlock();
+ return RD_OK;
+ } else {
+ _mutex.lock();
+ fh->inUse = false;
+ _mutex.unlock();
+ delete tmp;
+ return RDERR_INVALIDFILENAME;
+ }
+}
+
+/**
+ * @return the time left for the current music, in seconds.
+ */
+
+int32 Sound::musicTimeRemaining() {
+ Common::StackLock lock(_mutex);
+
+ for (int i = 0; i < MAXMUS; i++) {
+ if (_music[i] && _music[i]->isFading() <= 0)
+ return _music[i]->getTimeRemaining();
+ }
+
+ return 0;
+}
+
+// ----------------------------------------------------------------------------
+// SPEECH
+// ----------------------------------------------------------------------------
+
+/**
+ * Mutes/Unmutes the speech.
+ * @param mute If mute is false, restore the volume to the last set master
+ * level. Otherwise the speech is muted (volume 0).
+ */
+
+void Sound::muteSpeech(bool mute) {
+ _speechMuted = mute;
+
+ if (_vm->_mixer->isSoundHandleActive(_soundHandleSpeech)) {
+ uint volume = mute ? 0 : Audio::Mixer::kMaxChannelVolume;
+
+ _vm->_mixer->setChannelVolume(_soundHandleSpeech, volume);
+ }
+}
+
+/**
+ * Stops the speech dead in its tracks.
+ */
+
+void Sound::pauseSpeech() {
+ _speechPaused = true;
+ _vm->_mixer->pauseHandle(_soundHandleSpeech, true);
+}
+
+/**
+ * Restarts the speech from where it was stopped.
+ */
+
+void Sound::unpauseSpeech() {
+ _speechPaused = false;
+ _vm->_mixer->pauseHandle(_soundHandleSpeech, false);
+}
+
+/**
+ * Stops the speech from playing.
+ */
+
+int32 Sound::stopSpeech() {
+ if (_vm->_mixer->isSoundHandleActive(_soundHandleSpeech)) {
+ _vm->_mixer->stopHandle(_soundHandleSpeech);
+ return RD_OK;
+ }
+
+ return RDERR_SPEECHNOTPLAYING;
+}
+
+/**
+ * @return Either RDSE_SAMPLEPLAYING or RDSE_SAMPLEFINISHED
+ */
+
+int32 Sound::getSpeechStatus() {
+ return _vm->_mixer->isSoundHandleActive(_soundHandleSpeech) ? RDSE_SAMPLEPLAYING : RDSE_SAMPLEFINISHED;
+}
+
+/**
+ * Returns either RDSE_QUIET or RDSE_SPEAKING
+ */
+
+int32 Sound::amISpeaking() {
+ if (!_speechMuted && !_speechPaused && _vm->_mixer->isSoundHandleActive(_soundHandleSpeech))
+ return RDSE_SPEAKING;
+
+ return RDSE_QUIET;
+}
+
+/**
+ * This function loads and decompresses a list of speech from a cluster, but
+ * does not play it. This is used for cutscene voice-overs, presumably to
+ * avoid having to read from more than one file on the CD during playback.
+ * @param speechId the text line id used to reference the speech
+ * @param buf a pointer to the buffer that will be allocated for the sound
+ */
+
+uint32 Sound::preFetchCompSpeech(uint32 speechId, uint16 **buf) {
+ int cd = _vm->_resman->getCD();
+ uint32 numSamples;
+
+ SoundFileHandle *fh = (cd == 1) ? &_speechFile[0] : &_speechFile[1];
+
+ AudioStream *input = getAudioStream(fh, "speech", cd, speechId, &numSamples);
+
+ if (!input)
+ return 0;
+
+ *buf = NULL;
+
+ // Decompress data into speech buffer.
+
+ uint32 bufferSize = 2 * numSamples;
+
+ *buf = (uint16 *)malloc(bufferSize);
+ if (!*buf) {
+ delete input;
+ fh->file.close();
+ return 0;
+ }
+
+ uint32 readSamples = input->readBuffer((int16 *)*buf, numSamples);
+
+ fh->file.close();
+ delete input;
+
+ return 2 * readSamples;
+}
+
+/**
+ * This function loads, decompresses and plays a line of speech. An error
+ * occurs if speech is already playing.
+ * @param speechId the text line id used to reference the speech
+ * @param vol volume, 0 (minimum) to 16 (maximum)
+ * @param pan panning, -16 (full left) to 16 (full right)
+ */
+
+int32 Sound::playCompSpeech(uint32 speechId, uint8 vol, int8 pan) {
+ if (_speechMuted)
+ return RD_OK;
+
+ if (getSpeechStatus() == RDERR_SPEECHPLAYING)
+ return RDERR_SPEECHPLAYING;
+
+ int cd = _vm->_resman->getCD();
+ SoundFileHandle *fh = (cd == 1) ? &_speechFile[0] : &_speechFile[1];
+
+ AudioStream *input = getAudioStream(fh, "speech", cd, speechId, NULL);
+
+ if (!input)
+ return RDERR_INVALIDID;
+
+ // Modify the volume according to the master volume
+
+ byte volume = _speechMuted ? 0 : vol * Audio::Mixer::kMaxChannelVolume / 16;
+ int8 p = (pan * 127) / 16;
+
+ if (isReverseStereo())
+ p = -p;
+
+ // Start the speech playing
+ _vm->_mixer->playInputStream(Audio::Mixer::kSpeechSoundType, &_soundHandleSpeech, input, -1, volume, p);
+ return RD_OK;
+}
+
+// ----------------------------------------------------------------------------
+// SOUND EFFECTS
+// ----------------------------------------------------------------------------
+
+/**
+ * Mutes/Unmutes the sound effects.
+ * @param mute If mute is false, restore the volume to the last set master
+ * level. Otherwise the sound effects are muted (volume 0).
+ */
+
+void Sound::muteFx(bool mute) {
+ _fxMuted = mute;
+
+ // Now update the volume of any fxs playing
+ for (int i = 0; i < FXQ_LENGTH; i++) {
+ if (_fxQueue[i].resource) {
+ _vm->_mixer->setChannelVolume(_fxQueue[i].handle, mute ? 0 : _fxQueue[i].volume);
+ }
+ }
+}
+
+/**
+ * Sets the volume and pan of the sample which is currently playing
+ * @param id the id of the sample
+ * @param vol volume
+ * @param pan panning
+ */
+
+int32 Sound::setFxIdVolumePan(int32 id, int vol, int pan) {
+ if (!_fxQueue[id].resource)
+ return RDERR_FXNOTOPEN;
+
+ if (vol > 16)
+ vol = 16;
+
+ _fxQueue[id].volume = (vol * Audio::Mixer::kMaxChannelVolume) / 16;
+
+ if (pan != 255) {
+ if (isReverseStereo())
+ pan = -pan;
+ _fxQueue[id].pan = (pan * 127) / 16;
+ }
+
+ if (!_fxMuted && _vm->_mixer->isSoundHandleActive(_fxQueue[id].handle)) {
+ _vm->_mixer->setChannelVolume(_fxQueue[id].handle, _fxQueue[id].volume);
+ if (pan != -1)
+ _vm->_mixer->setChannelBalance(_fxQueue[id].handle, _fxQueue[id].pan);
+ }
+
+ return RD_OK;
+}
+
+void Sound::pauseFx() {
+ if (_fxPaused)
+ return;
+
+ for (int i = 0; i < FXQ_LENGTH; i++) {
+ if (_fxQueue[i].resource)
+ _vm->_mixer->pauseHandle(_fxQueue[i].handle, true);
+ }
+
+ _fxPaused = true;
+}
+
+void Sound::unpauseFx() {
+ if (!_fxPaused)
+ return;
+
+ for (int i = 0; i < FXQ_LENGTH; i++)
+ if (_fxQueue[i].resource)
+ _vm->_mixer->pauseHandle(_fxQueue[i].handle, false);
+
+ _fxPaused = false;
+}
+
+} // End of namespace Sword2
diff --git a/engines/sword2/object.h b/engines/sword2/object.h
new file mode 100644
index 0000000000..1fd434f8a0
--- /dev/null
+++ b/engines/sword2/object.h
@@ -0,0 +1,342 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#ifndef _SCRIPT_STRUCTURES
+#define _SCRIPT_STRUCTURES
+
+#include "common/stream.h"
+
+namespace Sword2 {
+
+// these structures represent the broken up compact components
+// these here declared to the system must be the same as those declared to
+// LINC (or it wont work)
+
+// mouse structure - defines mouse detection area, detection priority &
+// 'type' flag
+
+struct ObjectMouse {
+ int32 x1; // Top-left and bottom-right of mouse
+ int32 y1; // area. (These coords are inclusive.)
+ int32 x2;
+ int32 y2;
+ int32 priority;
+ int32 pointer; // type (or resource id?) of pointer used over this area
+
+ static const int size() {
+ return 24;
+ }
+
+ void read(byte *addr) {
+ Common::MemoryReadStream readS(addr, size());
+
+ x1 = readS.readSint32LE();
+ y1 = readS.readSint32LE();
+ x2 = readS.readSint32LE();
+ y2 = readS.readSint32LE();
+ priority = readS.readSint32LE();
+ pointer = readS.readSint32LE();
+ }
+
+ void write(byte *addr) {
+ Common::MemoryWriteStream writeS(addr, size());
+
+ writeS.writeSint32LE(x1);
+ writeS.writeSint32LE(y1);
+ writeS.writeSint32LE(x2);
+ writeS.writeSint32LE(y2);
+ writeS.writeSint32LE(priority);
+ writeS.writeSint32LE(pointer);
+ }
+};
+
+// logic structure - contains fields used in logic script processing
+
+class ObjectLogic {
+ // int32 looping; // 0 when first calling fn<function>;
+ // 1 when calling subsequent times in
+ // same loop
+ // int32 pause; // pause count, used by fnPause()
+
+private:
+ byte *_addr;
+
+public:
+ ObjectLogic(byte *addr) {
+ _addr = addr;
+ }
+
+ static const int size() {
+ return 8;
+ }
+
+ byte *data() {
+ return _addr;
+ }
+
+ int32 getLooping() { return READ_LE_UINT32(_addr); }
+ int32 getPause() { return READ_LE_UINT32(_addr + 4); }
+
+ void setLooping(int32 x) { WRITE_LE_UINT32(_addr, x); }
+ void setPause(int32 x) { WRITE_LE_UINT32(_addr + 4, x); }
+};
+
+// status bits for 'type' field of ObjectGraphic)
+
+// in low word:
+
+#define NO_SPRITE 0x00000000 // don't print
+#define BGP0_SPRITE 0x00000001 // fixed to background parallax[0]
+#define BGP1_SPRITE 0x00000002 // fixed to background parallax[1]
+#define BACK_SPRITE 0x00000004 // 'background' sprite, fixed to main background
+#define SORT_SPRITE 0x00000008 // 'sorted' sprite, fixed to main background
+#define FORE_SPRITE 0x00000010 // 'foreground' sprite, fixed to main background
+#define FGP0_SPRITE 0x00000020 // fixed to foreground parallax[0]
+#define FGP1_SPRITE 0x00000040 // fixed to foreground parallax[0]
+
+// in high word:
+
+#define UNSHADED_SPRITE 0x00000000 // not to be shaded
+#define SHADED_SPRITE 0x00010000 // to be shaded, based on shading mask
+
+// graphic structure - contains fields appropriate to sprite output
+
+class ObjectGraphic {
+ // int32 type; // see above
+ // int32 anim_resource; // resource id of animation file
+ // int32 anim_pc; // current frame number of animation
+
+private:
+ byte *_addr;
+
+public:
+ ObjectGraphic(byte *addr) {
+ _addr = addr;
+ }
+
+ static const int size() {
+ return 12;
+ }
+
+ byte *data() {
+ return _addr;
+ }
+
+ int32 getType() { return READ_LE_UINT32(_addr); }
+ int32 getAnimResource() { return READ_LE_UINT32(_addr + 4); }
+ int32 getAnimPc() { return READ_LE_UINT32(_addr + 8); }
+
+ void setType(int32 x) { WRITE_LE_UINT32(_addr, x); }
+ void setAnimResource(int32 x) { WRITE_LE_UINT32(_addr + 4, x); }
+ void setAnimPc(int32 x) { WRITE_LE_UINT32(_addr + 8, x); }
+};
+
+// speech structure - contains fields used by speech scripts & text output
+
+class ObjectSpeech {
+ // int32 pen; // colour to use for body of characters
+ // int32 width; // max width of text sprite
+ // int32 command; // speech script command id
+ // int32 ins1; // speech script instruction parameters
+ // int32 ins2;
+ // int32 ins3;
+ // int32 ins4;
+ // int32 ins5;
+ // int32 wait_state; // 0 not waiting,
+ // 1 waiting for next speech command
+
+private:
+ byte *_addr;
+
+public:
+ ObjectSpeech(byte *addr) {
+ _addr = addr;
+ }
+
+ static const int size() {
+ return 36;
+ }
+
+ byte *data() {
+ return _addr;
+ }
+
+ int32 getPen() { return READ_LE_UINT32(_addr); }
+ int32 getWidth() { return READ_LE_UINT32(_addr + 4); }
+ int32 getCommand() { return READ_LE_UINT32(_addr + 8); }
+ int32 getIns1() { return READ_LE_UINT32(_addr + 12); }
+ int32 getIns2() { return READ_LE_UINT32(_addr + 16); }
+ int32 getIns3() { return READ_LE_UINT32(_addr + 20); }
+ int32 getIns4() { return READ_LE_UINT32(_addr + 24); }
+ int32 getIns5() { return READ_LE_UINT32(_addr + 28); }
+ int32 getWaitState() { return READ_LE_UINT32(_addr + 32); }
+
+ void setPen(int32 x) { WRITE_LE_UINT32(_addr, x); }
+ void setWidth(int32 x) { WRITE_LE_UINT32(_addr + 4, x); }
+ void setCommand(int32 x) { WRITE_LE_UINT32(_addr + 8, x); }
+ void setIns1(int32 x) { WRITE_LE_UINT32(_addr + 12, x); }
+ void setIns2(int32 x) { WRITE_LE_UINT32(_addr + 16, x); }
+ void setIns3(int32 x) { WRITE_LE_UINT32(_addr + 20, x); }
+ void setIns4(int32 x) { WRITE_LE_UINT32(_addr + 24, x); }
+ void setIns5(int32 x) { WRITE_LE_UINT32(_addr + 28, x); }
+ void setWaitState(int32 x) { WRITE_LE_UINT32(_addr + 32, x); }
+};
+
+// mega structure - contains fields used for mega-character & mega-set
+// processing
+
+class ObjectMega {
+ // int32 NOT_USED_1; // only free roaming megas need to
+ // check this before registering their
+ // graphics for drawing
+ // int32 NOT_USED_2; // id of floor on which we are standing
+ // int32 NOT_USED_3; // id of object which we are getting to
+ // int32 NOT_USED_4; // pixel distance to stand from player
+ // character when in conversation
+ // int32 currently_walking; // number given us by the auto router
+ // int32 walk_pc; // current frame number of walk-anim
+ // int32 scale_a; // current scale factors, taken from
+ // int32 scale_b; // floor data
+ // int32 feet_x; // mega feet coords - frame-offsets are
+ // int32 feet_y; // added to these position mega frames
+ // int32 current_dir; // current dirction faced by mega; used
+ // by autorouter to determine turns
+ // required
+ // int32 NOT_USED_5; // means were currently avoiding a
+ // collision (see fnWalk)
+ // int32 megaset_res; // resource id of mega-set file
+ // int32 NOT_USED_6; // NOT USED
+
+private:
+ byte *_addr;
+
+public:
+ ObjectMega(byte *addr) {
+ _addr = addr;
+ }
+
+ static const int size() {
+ return 56;
+ }
+
+ byte *data() {
+ return _addr;
+ }
+
+ int32 getIsWalking() { return READ_LE_UINT32(_addr + 16); }
+ int32 getWalkPc() { return READ_LE_UINT32(_addr + 20); }
+ int32 getScaleA() { return READ_LE_UINT32(_addr + 24); }
+ int32 getScaleB() { return READ_LE_UINT32(_addr + 28); }
+ int32 getFeetX() { return READ_LE_UINT32(_addr + 32); }
+ int32 getFeetY() { return READ_LE_UINT32(_addr + 36); }
+ int32 getCurDir() { return READ_LE_UINT32(_addr + 40); }
+ int32 getMegasetRes() { return READ_LE_UINT32(_addr + 48); }
+
+ void setIsWalking(int32 x) { WRITE_LE_UINT32(_addr + 16, x); }
+ void setWalkPc(int32 x) { WRITE_LE_UINT32(_addr + 20, x); }
+ void setScaleA(int32 x) { WRITE_LE_UINT32(_addr + 24, x); }
+ void setScaleB(int32 x) { WRITE_LE_UINT32(_addr + 28, x); }
+ void setFeetX(int32 x) { WRITE_LE_UINT32(_addr + 32, x); }
+ void setFeetY(int32 x) { WRITE_LE_UINT32(_addr + 36, x); }
+ void setCurDir(int32 x) { WRITE_LE_UINT32(_addr + 40, x); }
+ void setMegasetRes(int32 x) { WRITE_LE_UINT32(_addr + 48, x); }
+
+ int32 calcScale() {
+ // Calc scale at which to print the sprite, based on feet
+ // y-coord & scaling constants (NB. 'scale' is actually
+ // 256 * true_scale, to maintain accuracy)
+
+ // Ay+B gives 256 * scale ie. 256 * 256 * true_scale for even
+ // better accuracy, ie. scale = (Ay + B) / 256
+ return (getScaleA() * getFeetY() + getScaleB()) / 256;
+ }
+};
+
+// walk-data structure - contains details of layout of frames in the
+// mega-set, and how they are to be used
+
+struct ObjectWalkdata {
+ int32 nWalkFrames; // no. of frames per walk-cycle
+ int32 usingStandingTurnFrames; // 0 = no 1 = yes
+ int32 usingWalkingTurnFrames; // 0 = no 1 = yes
+ int32 usingSlowInFrames; // 0 = no 1 = yes
+ int32 usingSlowOutFrames; // 0 = no !0 = number of slow-out frames in each direction
+ int32 nSlowInFrames[8]; // no. of slow-in frames in each direction
+ int32 leadingLeg[8]; // leading leg for walk in each direction (0 = left 1 = right)
+ int32 dx[8 * (12 + 1)]; // walk step distances in x direction
+ int32 dy[8 * (12 + 1)]; // walk step distances in y direction
+
+ static const int size() {
+ return 916;
+ }
+
+ void read(byte *addr) {
+ Common::MemoryReadStream readS(addr, size());
+
+ nWalkFrames = readS.readUint32LE();
+ usingStandingTurnFrames = readS.readUint32LE();
+ usingWalkingTurnFrames = readS.readUint32LE();
+ usingSlowInFrames = readS.readUint32LE();
+ usingSlowOutFrames = readS.readUint32LE();
+
+ int i;
+
+ for (i = 0; i < ARRAYSIZE(nSlowInFrames); i++)
+ nSlowInFrames[i] = readS.readUint32LE();
+
+ for (i = 0; i < ARRAYSIZE(leadingLeg); i++)
+ leadingLeg[i] = readS.readUint32LE();
+
+ for (i = 0; i < ARRAYSIZE(dx); i++)
+ dx[i] = readS.readUint32LE();
+
+ for (i = 0; i < ARRAYSIZE(dy); i++)
+ dy[i] = readS.readUint32LE();
+ }
+
+ void write(byte *addr) {
+ Common::MemoryWriteStream writeS(addr, size());
+
+ writeS.writeUint32LE(nWalkFrames);
+ writeS.writeUint32LE(usingStandingTurnFrames);
+ writeS.writeUint32LE(usingWalkingTurnFrames);
+ writeS.writeUint32LE(usingSlowInFrames);
+ writeS.writeUint32LE(usingSlowOutFrames);
+
+ int i;
+
+ for (i = 0; i < ARRAYSIZE(nSlowInFrames); i++)
+ writeS.writeUint32LE(nSlowInFrames[i]);
+
+ for (i = 0; i < ARRAYSIZE(leadingLeg); i++)
+ writeS.writeUint32LE(leadingLeg[i]);
+
+ for (i = 0; i < ARRAYSIZE(dx); i++)
+ writeS.writeUint32LE(dx[i]);
+
+ for (i = 0; i < ARRAYSIZE(dy); i++)
+ writeS.writeUint32LE(dy[i]);
+ }
+};
+
+} // End of namespace Sword2
+
+#endif
diff --git a/engines/sword2/palette.cpp b/engines/sword2/palette.cpp
new file mode 100644
index 0000000000..0b6b7a8ca2
--- /dev/null
+++ b/engines/sword2/palette.cpp
@@ -0,0 +1,267 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "common/stdafx.h"
+#include "common/system.h"
+#include "sword2/sword2.h"
+#include "sword2/defs.h"
+#include "sword2/logic.h"
+#include "sword2/resman.h"
+
+namespace Sword2 {
+
+/**
+ * Start layer palette fading up
+ */
+
+void Screen::startNewPalette() {
+ // If the screen is still fading down then wait for black - could
+ // happen when everythings cached into a large memory model
+ waitForFade();
+
+ byte *screenFile = _vm->_resman->openResource(_thisScreen.background_layer_id);
+
+ memcpy(_paletteMatch, _vm->fetchPaletteMatchTable(screenFile), PALTABLESIZE);
+ setPalette(0, 256, _vm->fetchPalette(screenFile), RDPAL_FADE);
+
+ // Indicating that it's a screen palette
+ _lastPaletteRes = 0;
+
+ _vm->_resman->closeResource(_thisScreen.background_layer_id);
+ fadeUp();
+ _thisScreen.new_palette = 0;
+}
+
+void Screen::setFullPalette(int32 palRes) {
+ // fudge for hut interior
+ // - unpausing should restore last palette as normal (could be screen
+ // palette or 'dark_palette_13')
+ // - but restoring the screen palette after 'dark_palette_13' should
+ // now work properly too!
+
+ // "Hut interior" refers to the watchman's hut in Marseille, and this
+ // is apparently needed for the palette to be restored properly when
+ // you turn the light off. (I didn't even notice the light switch!)
+
+ if (_vm->_logic->readVar(LOCATION) == 13) {
+ // unpausing
+ if (palRes == -1) {
+ // restore whatever palette was last set (screen
+ // palette or 'dark_palette_13')
+ palRes = _lastPaletteRes;
+ }
+ } else {
+ // check if we're just restoring the current screen palette
+ // because we might actually need to use a separate palette
+ // file anyway eg. for pausing & unpausing during the eclipse
+
+ // unpausing (fudged for location 13)
+ if (palRes == -1) {
+ // we really meant '0'
+ palRes = 0;
+ }
+
+ if (palRes == 0 && _lastPaletteRes)
+ palRes = _lastPaletteRes;
+ }
+
+ // If non-zero, set palette to this separate palette file. Otherwise,
+ // set palette to current screen palette.
+
+ if (palRes) {
+ byte *pal = _vm->_resman->openResource(palRes);
+
+ assert(_vm->_resman->fetchType(pal) == PALETTE_FILE);
+
+ pal += ResHeader::size();
+
+ // always set colour 0 to black because most background screen
+ // palettes have a bright colour 0 although it should come out
+ // as black in the game!
+
+ pal[0] = 0;
+ pal[1] = 0;
+ pal[2] = 0;
+ pal[3] = 0;
+
+ setPalette(0, 256, pal, RDPAL_INSTANT);
+ _vm->_resman->closeResource(palRes);
+ } else {
+ if (_thisScreen.background_layer_id) {
+ byte *data = _vm->_resman->openResource(_thisScreen.background_layer_id);
+ memcpy(_paletteMatch, _vm->fetchPaletteMatchTable(data), PALTABLESIZE);
+ setPalette(0, 256, _vm->fetchPalette(data), RDPAL_INSTANT);
+ _vm->_resman->closeResource(_thisScreen.background_layer_id);
+ } else
+ error("setFullPalette(0) called, but no current screen available!");
+ }
+
+ if (palRes != CONTROL_PANEL_PALETTE)
+ _lastPaletteRes = palRes;
+}
+
+/**
+ * Matches a colour triplet to a palette index.
+ * @param r red colour component
+ * @param g green colour component
+ * @param b blue colour component
+ * @return the palette index of the closest matching colour in the palette
+ */
+
+// FIXME: This used to be inlined - probably a good idea - but the
+// linker complained when I tried to use it in sprite.cpp.
+
+uint8 Screen::quickMatch(uint8 r, uint8 g, uint8 b) {
+ return _paletteMatch[((int32)(r >> 2) << 12) + ((int32)(g >> 2) << 6) + (b >> 2)];
+}
+
+/**
+ * Sets the palette.
+ * @param startEntry the first colour entry to set
+ * @param noEntries the number of colour entries to set
+ * @param colourTable the new colour entries
+ * @param fadeNow whether to perform the change immediately or delayed
+ */
+
+void Screen::setPalette(int16 startEntry, int16 noEntries, byte *colourTable, uint8 fadeNow) {
+ assert(noEntries > 0);
+
+ memcpy(&_palette[4 * startEntry], colourTable, noEntries * 4);
+
+ if (fadeNow == RDPAL_INSTANT) {
+ _vm->_system->setPalette(_palette, startEntry, noEntries);
+ setNeedFullRedraw();
+ }
+}
+
+void Screen::dimPalette() {
+ byte *p = _palette;
+
+ for (int i = 0; i < 256; i++) {
+ p[i * 4 + 0] /= 2;
+ p[i * 4 + 1] /= 2;
+ p[i * 4 + 2] /= 2;
+ }
+
+ _vm->_system->setPalette(p, 0, 256);
+ setNeedFullRedraw();
+}
+
+/**
+ * Fades the palette up from black to the current palette.
+ * @param time the time it will take the palette to fade up
+ */
+
+int32 Screen::fadeUp(float time) {
+ if (getFadeStatus() != RDFADE_BLACK && getFadeStatus() != RDFADE_NONE)
+ return RDERR_FADEINCOMPLETE;
+
+ _fadeTotalTime = (int32)(time * 1000);
+ _fadeStatus = RDFADE_UP;
+ _fadeStartTime = _vm->_system->getMillis();
+
+ return RD_OK;
+}
+
+/**
+ * Fades the palette down to black from the current palette.
+ * @param time the time it will take the palette to fade down
+ */
+
+int32 Screen::fadeDown(float time) {
+ if (getFadeStatus() != RDFADE_BLACK && getFadeStatus() != RDFADE_NONE)
+ return RDERR_FADEINCOMPLETE;
+
+ _fadeTotalTime = (int32)(time * 1000);
+ _fadeStatus = RDFADE_DOWN;
+ _fadeStartTime = _vm->_system->getMillis();
+
+ return RD_OK;
+}
+
+/**
+ * Get the current fade status
+ * @return RDFADE_UP (fading up), RDFADE_DOWN (fading down), RDFADE_NONE
+ * (not faded), or RDFADE_BLACK (completely faded down)
+ */
+
+uint8 Screen::getFadeStatus() {
+ return _fadeStatus;
+}
+
+void Screen::waitForFade() {
+ while (getFadeStatus() != RDFADE_NONE && getFadeStatus() != RDFADE_BLACK && !_vm->_quit) {
+ updateDisplay();
+ _vm->_system->delayMillis(20);
+ }
+}
+
+void Screen::fadeServer() {
+ static int32 previousTime = 0;
+ byte fadePalette[256 * 4];
+ byte *newPalette = fadePalette;
+ int32 currentTime;
+ int16 fadeMultiplier;
+ int16 i;
+
+ // If we're not in the process of fading, do nothing.
+ if (getFadeStatus() != RDFADE_UP && getFadeStatus() != RDFADE_DOWN)
+ return;
+
+ // I don't know if this is necessary, but let's limit how often the
+ // palette is updated, just to be safe.
+ currentTime = _vm->_system->getMillis();
+ if (currentTime - previousTime <= 25)
+ return;
+
+ previousTime = currentTime;
+
+ if (getFadeStatus() == RDFADE_UP) {
+ if (currentTime >= _fadeStartTime + _fadeTotalTime) {
+ _fadeStatus = RDFADE_NONE;
+ newPalette = _palette;
+ } else {
+ fadeMultiplier = (int16)(((int32)(currentTime - _fadeStartTime) * 256) / _fadeTotalTime);
+ for (i = 0; i < 256; i++) {
+ newPalette[i * 4 + 0] = (_palette[i * 4 + 0] * fadeMultiplier) >> 8;
+ newPalette[i * 4 + 1] = (_palette[i * 4 + 1] * fadeMultiplier) >> 8;
+ newPalette[i * 4 + 2] = (_palette[i * 4 + 2] * fadeMultiplier) >> 8;
+ }
+ }
+ } else {
+ if (currentTime >= _fadeStartTime + _fadeTotalTime) {
+ _fadeStatus = RDFADE_BLACK;
+ memset(newPalette, 0, sizeof(fadePalette));
+ } else {
+ fadeMultiplier = (int16)(((int32)(_fadeTotalTime - (currentTime - _fadeStartTime)) * 256) / _fadeTotalTime);
+ for (i = 0; i < 256; i++) {
+ newPalette[i * 4 + 0] = (_palette[i * 4 + 0] * fadeMultiplier) >> 8;
+ newPalette[i * 4 + 1] = (_palette[i * 4 + 1] * fadeMultiplier) >> 8;
+ newPalette[i * 4 + 2] = (_palette[i * 4 + 2] * fadeMultiplier) >> 8;
+ }
+ }
+ }
+
+ _vm->_system->setPalette(newPalette, 0, 256);
+ setNeedFullRedraw();
+}
+
+} // End of namespace Sword2
diff --git a/engines/sword2/protocol.cpp b/engines/sword2/protocol.cpp
new file mode 100644
index 0000000000..23010e27a9
--- /dev/null
+++ b/engines/sword2/protocol.cpp
@@ -0,0 +1,216 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "common/stdafx.h"
+#include "sword2/sword2.h"
+#include "sword2/resman.h"
+
+namespace Sword2 {
+
+/**
+ * Returns a pointer to the first palette entry, given the pointer to the start
+ * of the screen file.
+ */
+
+byte *Sword2Engine::fetchPalette(byte *screenFile) {
+ MultiScreenHeader mscreenHeader;
+
+ mscreenHeader.read(screenFile + ResHeader::size());
+
+ byte *palette = screenFile + ResHeader::size() + mscreenHeader.palette;
+
+ // Always set colour 0 to black, because while most background screen
+ // palettes have a bright colour 0 it should come out as black in the
+ // game.
+
+ palette[0] = 0;
+ palette[1] = 0;
+ palette[2] = 0;
+ palette[3] = 0;
+
+ return palette;
+}
+
+/**
+ * Returns a pointer to the start of the palette match table, given the pointer
+ * to the start of the screen file.
+ */
+
+byte *Sword2Engine::fetchPaletteMatchTable(byte *screenFile) {
+ MultiScreenHeader mscreenHeader;
+
+ mscreenHeader.read(screenFile + ResHeader::size());
+
+ return screenFile + ResHeader::size() + mscreenHeader.paletteTable;
+}
+
+/**
+ * Returns a pointer to the screen header, given the pointer to the start of
+ * the screen file.
+ */
+
+byte *Sword2Engine::fetchScreenHeader(byte *screenFile) {
+ MultiScreenHeader mscreenHeader;
+
+ mscreenHeader.read(screenFile + ResHeader::size());
+
+ return screenFile + ResHeader::size() + mscreenHeader.screen;
+}
+
+/**
+ * Returns a pointer to the requested layer header, given the pointer to the
+ * start of the screen file. Drops out if the requested layer number exceeds
+ * the number of layers on this screen.
+ */
+
+byte *Sword2Engine::fetchLayerHeader(byte *screenFile, uint16 layerNo) {
+#ifdef SWORD2_DEBUG
+ ScreenHeader screenHead;
+
+ screenHead.read(fetchScreenHeader(screenFile));
+ assert(layerNo < screenHead.noLayers);
+#endif
+
+ MultiScreenHeader mscreenHeader;
+
+ mscreenHeader.read(screenFile + ResHeader::size());
+
+ return screenFile + ResHeader::size() + mscreenHeader.layers + layerNo * LayerHeader::size();
+}
+
+/**
+ * Returns a pointer to the start of the shading mask, given the pointer to the
+ * start of the screen file.
+ */
+
+byte *Sword2Engine::fetchShadingMask(byte *screenFile) {
+ MultiScreenHeader mscreenHeader;
+
+ mscreenHeader.read(screenFile + ResHeader::size());
+
+ return screenFile + ResHeader::size() + mscreenHeader.maskOffset;
+}
+
+/**
+ * Returns a pointer to the anim header, given the pointer to the start of the
+ * anim file.
+ */
+
+byte *Sword2Engine::fetchAnimHeader(byte *animFile) {
+ return animFile + ResHeader::size();
+}
+
+/**
+ * Returns a pointer to the requested frame number's cdtEntry, given the
+ * pointer to the start of the anim file. Drops out if the requested frame
+ * number exceeds the number of frames in this anim.
+ */
+
+byte *Sword2Engine::fetchCdtEntry(byte *animFile, uint16 frameNo) {
+#ifdef SWORD2_DEBUG
+ AnimHeader animHead;
+
+ animHead.read(fetchAnimHeader(animFile));
+
+ if (frameNo > animHead->noAnimFrames - 1)
+ error("fetchCdtEntry(animFile,%d) - anim only %d frames", frameNo, animHead.noAnimFrames);
+#endif
+
+ return fetchAnimHeader(animFile) + AnimHeader::size() + frameNo * CdtEntry::size();
+}
+
+/**
+ * Returns a pointer to the requested frame number's header, given the pointer
+ * to the start of the anim file. Drops out if the requested frame number
+ * exceeds the number of frames in this anim
+ */
+
+byte *Sword2Engine::fetchFrameHeader(byte *animFile, uint16 frameNo) {
+ // required address = (address of the start of the anim header) + frameOffset
+ CdtEntry cdt;
+
+ cdt.read(fetchCdtEntry(animFile, frameNo));
+
+ return animFile + ResHeader::size() + cdt.frameOffset;
+}
+
+/**
+ * Returns a pointer to the requested parallax layer data.
+ */
+
+byte *Sword2Engine::fetchBackgroundParallaxLayer(byte *screenFile, int layer) {
+ MultiScreenHeader mscreenHeader;
+
+ mscreenHeader.read(screenFile + ResHeader::size());
+ assert(mscreenHeader.bg_parallax[layer]);
+
+ return screenFile + ResHeader::size() + mscreenHeader.bg_parallax[layer];
+}
+
+byte *Sword2Engine::fetchBackgroundLayer(byte *screenFile) {
+ MultiScreenHeader mscreenHeader;
+
+ mscreenHeader.read(screenFile + ResHeader::size());
+ assert(mscreenHeader.screen);
+
+ return screenFile + ResHeader::size() + mscreenHeader.screen + ScreenHeader::size();
+}
+
+byte *Sword2Engine::fetchForegroundParallaxLayer(byte *screenFile, int layer) {
+ MultiScreenHeader mscreenHeader;
+
+ mscreenHeader.read(screenFile + ResHeader::size());
+ assert(mscreenHeader.fg_parallax[layer]);
+
+ return screenFile + ResHeader::size() + mscreenHeader.fg_parallax[layer];
+}
+
+byte *Sword2Engine::fetchTextLine(byte *file, uint32 text_line) {
+ TextHeader text_header;
+ static byte errorLine[128];
+
+ text_header.read(file + ResHeader::size());
+
+ if (text_line >= text_header.noOfLines) {
+ sprintf((char *)errorLine, "xxMissing line %d of %s (only 0..%d)", text_line, _resman->fetchName(file), text_header.noOfLines - 1);
+
+ // first 2 chars are NULL so that actor-number comes out as '0'
+ errorLine[0] = 0;
+ errorLine[1] = 0;
+ return errorLine;
+ }
+
+ // The "number of lines" field is followed by a lookup table
+
+ return file + READ_LE_UINT32(file + ResHeader::size() + 4 + 4 * text_line);
+}
+
+// Used for testing text & speech (see fnISpeak in speech.cpp)
+
+bool Sword2Engine::checkTextLine(byte *file, uint32 text_line) {
+ TextHeader text_header;
+
+ text_header.read(file + ResHeader::size());
+
+ return text_line < text_header.noOfLines;
+}
+
+} // End of namespace Sword2
diff --git a/engines/sword2/rdwin.cpp b/engines/sword2/rdwin.cpp
new file mode 100644
index 0000000000..310b66abf7
--- /dev/null
+++ b/engines/sword2/rdwin.cpp
@@ -0,0 +1,118 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "common/stdafx.h"
+#include "common/system.h"
+#include "sword2/sword2.h"
+
+namespace Sword2 {
+
+/**
+ * Tell updateDisplay() that the scene needs to be completely updated.
+ */
+
+void Screen::setNeedFullRedraw() {
+ _needFullRedraw = true;
+}
+
+/**
+ * Mark an area of the screen as dirty, first generation.
+ */
+
+void Screen::markAsDirty(int16 x0, int16 y0, int16 x1, int16 y1) {
+ int16 gridX0 = x0 / CELLWIDE;
+ int16 gridY0 = y0 / CELLDEEP;
+ int16 gridX1 = x1 / CELLWIDE;
+ int16 gridY1 = y1 / CELLDEEP;
+
+ for (int16 i = gridY0; i <= gridY1; i++)
+ for (int16 j = gridX0; j <= gridX1; j++)
+ _dirtyGrid[i * _gridWide + j] = 2;
+}
+
+/**
+ * This function has two purposes: It redraws the scene, and it handles input
+ * events, palette fading, etc. It should be called at a high rate (> 20 per
+ * second), but the scene is usually only redrawn about 12 times per second,
+ * except when then screen is scrolling.
+ *
+ * @param redrawScene If true, redraw the scene.
+ */
+
+void Screen::updateDisplay(bool redrawScene) {
+ _vm->parseInputEvents();
+ fadeServer();
+
+ if (redrawScene) {
+ int i;
+
+ // Note that the entire scene is always rendered, which is less
+ // than optimal, but at least we can try to be intelligent
+ // about updating the screen afterwards.
+
+ if (_needFullRedraw) {
+ // Update the entire screen. This is necessary when
+ // scrolling, fading, etc.
+
+ _vm->_system->copyRectToScreen(_buffer + MENUDEEP * _screenWide, _screenWide, 0, MENUDEEP, _screenWide, _screenDeep - 2 * MENUDEEP);
+ _needFullRedraw = false;
+ } else {
+ // Update only the dirty areas of the screen
+
+ int j, x, y;
+ int stripWide;
+
+ for (i = 0; i < _gridDeep; i++) {
+ stripWide = 0;
+
+ for (j = 0; j < _gridWide; j++) {
+ if (_dirtyGrid[i * _gridWide + j]) {
+ stripWide++;
+ } else if (stripWide) {
+ x = CELLWIDE * (j - stripWide);
+ y = CELLDEEP * i;
+ _vm->_system->copyRectToScreen(_buffer + y * _screenWide + x, _screenWide, x, y, stripWide * CELLWIDE, CELLDEEP);
+ stripWide = 0;
+ }
+ }
+
+ if (stripWide) {
+ x = CELLWIDE * (j - stripWide);
+ y = CELLDEEP * i;
+ _vm->_system->copyRectToScreen(_buffer + y * _screenWide + x, _screenWide, x, y, stripWide * CELLWIDE, CELLDEEP);
+ stripWide = 0;
+ }
+ }
+ }
+
+ // Age the dirty cells one generation. This way we keep track
+ // of both the cells that were updated this time, and the ones
+ // that were updated the last time.
+
+ for (i = 0; i < _gridWide * _gridDeep; i++)
+ _dirtyGrid[i] >>= 1;
+ }
+
+ // We always need to update because of fades, menu animations, etc.
+ _vm->_system->updateScreen();
+}
+
+} // End of namespace Sword2
diff --git a/engines/sword2/render.cpp b/engines/sword2/render.cpp
new file mode 100644
index 0000000000..da60ecd6d9
--- /dev/null
+++ b/engines/sword2/render.cpp
@@ -0,0 +1,584 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "common/stdafx.h"
+#include "common/system.h"
+
+#include "graphics/primitives.h"
+
+#include "sword2/sword2.h"
+#include "sword2/defs.h"
+#include "sword2/build_display.h"
+
+#ifdef BACKEND_8BIT
+#include "sword2/animation.h"
+#endif
+
+namespace Sword2 {
+
+#define MILLISECSPERCYCLE 83
+#define RENDERAVERAGETOTAL 4
+
+void Screen::updateRect(Common::Rect *r) {
+ _vm->_system->copyRectToScreen(_buffer + r->top * _screenWide + r->left,
+ _screenWide, r->left, r->top, r->right - r->left,
+ r->bottom - r->top);
+}
+
+void Screen::blitBlockSurface(BlockSurface *s, Common::Rect *r, Common::Rect *clipRect) {
+ if (!r->intersects(*clipRect))
+ return;
+
+ byte *src = s->data;
+
+ if (r->top < clipRect->top) {
+ src -= BLOCKWIDTH * (r->top - clipRect->top);
+ r->top = clipRect->top;
+ }
+ if (r->left < clipRect->left) {
+ src -= (r->left - clipRect->left);
+ r->left = clipRect->left;
+ }
+ if (r->bottom > clipRect->bottom)
+ r->bottom = clipRect->bottom;
+ if (r->right > clipRect->right)
+ r->right = clipRect->right;
+
+ byte *dst = _buffer + r->top * _screenWide + r->left;
+ int i;
+
+ if (s->transparent) {
+ for (i = 0; i < r->bottom - r->top; i++) {
+ for (int j = 0; j < r->right - r->left; j++) {
+ if (src[j])
+ dst[j] = src[j];
+ }
+ src += BLOCKWIDTH;
+ dst += _screenWide;
+ }
+ } else {
+ for (i = 0; i < r->bottom - r->top; i++) {
+ memcpy(dst, src, r->right - r->left);
+ src += BLOCKWIDTH;
+ dst += _screenWide;
+ }
+ }
+}
+
+// There are two different separate functions for scaling the image - one fast
+// and one good. Or at least that's the theory. I'm sure there are better ways
+// to scale an image than this. The latter is used at the highest graphics
+// quality setting. Note that the "good" scaler takes an extra parameter, a
+// pointer to the area of the screen where the sprite will be drawn.
+//
+// This code isn't quite like the original DrawSprite(), but should be close
+// enough.
+
+void Screen::scaleImageFast(byte *dst, uint16 dstPitch, uint16 dstWidth, uint16 dstHeight, byte *src, uint16 srcPitch, uint16 srcWidth, uint16 srcHeight) {
+ int x, y;
+
+ for (x = 0; x < dstWidth; x++)
+ _xScale[x] = (x * srcWidth) / dstWidth;
+
+ for (y = 0; y < dstHeight; y++)
+ _yScale[y] = (y * srcHeight) / dstHeight;
+
+ for (y = 0; y < dstHeight; y++) {
+ for (x = 0; x < dstWidth; x++) {
+ dst[x] = src[_yScale[y] * srcPitch + _xScale[x]];
+ }
+ dst += dstPitch;
+ }
+}
+
+void Screen::scaleImageGood(byte *dst, uint16 dstPitch, uint16 dstWidth, uint16 dstHeight, byte *src, uint16 srcPitch, uint16 srcWidth, uint16 srcHeight, byte *backbuf) {
+ for (int y = 0; y < dstHeight; y++) {
+ for (int x = 0; x < dstWidth; x++) {
+ uint8 c1, c2, c3, c4;
+
+ uint32 xPos = (x * srcWidth) / dstWidth;
+ uint32 yPos = (y * srcHeight) / dstHeight;
+ uint32 xFrac = dstWidth - (x * srcWidth) % dstWidth;
+ uint32 yFrac = dstHeight - (y * srcHeight) % dstHeight;
+
+ byte *srcPtr = src + yPos * srcPitch + xPos;
+ byte *backPtr = backbuf + y * _screenWide + x;
+
+ bool transparent = true;
+
+ if (*srcPtr) {
+ c1 = *srcPtr;
+ transparent = false;
+ } else
+ c1 = *backPtr;
+
+ if (x < dstWidth - 1) {
+ if (*(srcPtr + 1)) {
+ c2 = *(srcPtr + 1);
+ transparent = false;
+ } else
+ c2 = *(backPtr + 1);
+ } else
+ c2 = c1;
+
+ if (y < dstHeight - 1) {
+ if (*(srcPtr + srcPitch)) {
+ c3 = *(srcPtr + srcPitch);
+ transparent = false;
+ } else
+ c3 = *(backPtr + _screenWide);
+ } else
+ c3 = c1;
+
+ if (x < dstWidth - 1 && y < dstHeight - 1) {
+ if (*(srcPtr + srcPitch + 1)) {
+ c4 = *(srcPtr + srcPitch + 1);
+ transparent = false;
+ } else
+ c4 = *(backPtr + _screenWide + 1);
+ } else
+ c4 = c3;
+
+ if (!transparent) {
+ uint32 r1 = _palette[c1 * 4 + 0];
+ uint32 g1 = _palette[c1 * 4 + 1];
+ uint32 b1 = _palette[c1 * 4 + 2];
+
+ uint32 r2 = _palette[c2 * 4 + 0];
+ uint32 g2 = _palette[c2 * 4 + 1];
+ uint32 b2 = _palette[c2 * 4 + 2];
+
+ uint32 r3 = _palette[c3 * 4 + 0];
+ uint32 g3 = _palette[c3 * 4 + 1];
+ uint32 b3 = _palette[c3 * 4 + 2];
+
+ uint32 r4 = _palette[c4 * 4 + 0];
+ uint32 g4 = _palette[c4 * 4 + 1];
+ uint32 b4 = _palette[c4 * 4 + 2];
+
+ uint32 r5 = (r1 * xFrac + r2 * (dstWidth - xFrac)) / dstWidth;
+ uint32 g5 = (g1 * xFrac + g2 * (dstWidth - xFrac)) / dstWidth;
+ uint32 b5 = (b1 * xFrac + b2 * (dstWidth - xFrac)) / dstWidth;
+
+ uint32 r6 = (r3 * xFrac + r4 * (dstWidth - xFrac)) / dstWidth;
+ uint32 g6 = (g3 * xFrac + g4 * (dstWidth - xFrac)) / dstWidth;
+ uint32 b6 = (b3 * xFrac + b4 * (dstWidth - xFrac)) / dstWidth;
+
+ uint32 r = (r5 * yFrac + r6 * (dstHeight - yFrac)) / dstHeight;
+ uint32 g = (g5 * yFrac + g6 * (dstHeight - yFrac)) / dstHeight;
+ uint32 b = (b5 * yFrac + b6 * (dstHeight - yFrac)) / dstHeight;
+
+ dst[y * dstWidth + x] = quickMatch(r, g, b);
+ } else
+ dst[y * dstWidth + x] = 0;
+ }
+ }
+}
+
+/**
+ * Plots a point relative to the top left corner of the screen. This is only
+ * used for debugging.
+ * @param x x-coordinate of the point
+ * @param y y-coordinate of the point
+ * @param colour colour of the point
+ */
+
+void Screen::plotPoint(int x, int y, uint8 colour) {
+ byte *buf = _buffer + MENUDEEP * RENDERWIDE;
+
+ x -= _scrollX;
+ y -= _scrollY;
+
+ if (x >= 0 && x < RENDERWIDE && y >= 0 && y < RENDERDEEP) {
+ buf[y * RENDERWIDE + x] = colour;
+ markAsDirty(x, y + MENUDEEP, x, y + MENUDEEP);
+ }
+}
+
+static void plot(int x, int y, int colour, void *data) {
+ Screen *screen = (Screen *)data;
+ screen->plotPoint(x, y, (uint8) colour);
+}
+
+/**
+ * Draws a line from one point to another. This is only used for debugging.
+ * @param x0 x-coordinate of the start point
+ * @param y0 y-coordinate of the start point
+ * @param x1 x-coordinate of the end point
+ * @param y1 y-coordinate of the end point
+ * @param colour colour of the line
+ */
+
+void Screen::drawLine(int x0, int y0, int x1, int y1, uint8 colour) {
+ Graphics::drawLine(x0, y0, x1, y1, colour, &plot, this);
+}
+
+/**
+ * This function tells the driver the size of the background screen for the
+ * current location.
+ * @param w width of the current location
+ * @param h height of the current location
+ */
+
+void Screen::setLocationMetrics(uint16 w, uint16 h) {
+ _locationWide = w;
+ _locationDeep = h;
+ setNeedFullRedraw();
+}
+
+/**
+ * Draws a parallax layer at the current position determined by the scroll. A
+ * parallax can be either foreground, background or the main screen.
+ */
+
+void Screen::renderParallax(byte *ptr, int16 l) {
+ Parallax p;
+ int16 x, y;
+ Common::Rect r;
+
+ p.read(ptr);
+
+ if (_locationWide == _screenWide)
+ x = 0;
+ else
+ x = ((int32)((p.w - _screenWide) * _scrollX) / (int32)(_locationWide - _screenWide));
+
+ if (_locationDeep == _screenDeep - MENUDEEP * 2)
+ y = 0;
+ else
+ y = ((int32)((p.h - (_screenDeep - MENUDEEP * 2)) * _scrollY) / (int32)(_locationDeep - (_screenDeep - MENUDEEP * 2)));
+
+ Common::Rect clipRect;
+
+ // Leave enough space for the top and bottom menues
+
+ clipRect.left = 0;
+ clipRect.right = _screenWide;
+ clipRect.top = MENUDEEP;
+ clipRect.bottom = _screenDeep - MENUDEEP;
+
+ for (int j = 0; j < _yBlocks[l]; j++) {
+ for (int i = 0; i < _xBlocks[l]; i++) {
+ if (_blockSurfaces[l][i + j * _xBlocks[l]]) {
+ r.left = i * BLOCKWIDTH - x;
+ r.right = r.left + BLOCKWIDTH;
+ r.top = j * BLOCKHEIGHT - y + MENUDEEP;
+ r.bottom = r.top + BLOCKHEIGHT;
+ blitBlockSurface(_blockSurfaces[l][i + j * _xBlocks[l]], &r, &clipRect);
+ }
+ }
+ }
+
+ _parallaxScrollX = _scrollX - x;
+ _parallaxScrollY = _scrollY - y;
+}
+
+// Uncomment this when benchmarking the drawing routines.
+#define LIMIT_FRAME_RATE
+
+/**
+ * Initialises the timers before the render loop is entered.
+ */
+
+void Screen::initialiseRenderCycle() {
+ _initialTime = _vm->_system->getMillis();
+ _totalTime = _initialTime + MILLISECSPERCYCLE;
+}
+
+/**
+ * This function should be called when the game engine is ready to start the
+ * render cycle.
+ */
+
+void Screen::startRenderCycle() {
+ _scrollXOld = _scrollX;
+ _scrollYOld = _scrollY;
+
+ _startTime = _vm->_system->getMillis();
+
+ if (_startTime + _renderAverageTime >= _totalTime) {
+ _scrollX = _scrollXTarget;
+ _scrollY = _scrollYTarget;
+ _renderTooSlow = true;
+ } else {
+ _scrollX = (int16)(_scrollXOld + ((_scrollXTarget - _scrollXOld) * (_startTime - _initialTime + _renderAverageTime)) / (_totalTime - _initialTime));
+ _scrollY = (int16)(_scrollYOld + ((_scrollYTarget - _scrollYOld) * (_startTime - _initialTime + _renderAverageTime)) / (_totalTime - _initialTime));
+ _renderTooSlow = false;
+ }
+
+ if (_scrollXOld != _scrollX || _scrollYOld != _scrollY)
+ setNeedFullRedraw();
+
+ _framesPerGameCycle = 0;
+}
+
+/**
+ * This function should be called at the end of the render cycle.
+ * @return true if the render cycle is to be terminated,
+ * or false if it should continue
+ */
+
+bool Screen::endRenderCycle() {
+ static int32 renderTimeLog[4] = { 60, 60, 60, 60 };
+ static int32 renderCountIndex = 0;
+ int32 time;
+
+ time = _vm->_system->getMillis();
+ renderTimeLog[renderCountIndex] = time - _startTime;
+ _startTime = time;
+ _renderAverageTime = (renderTimeLog[0] + renderTimeLog[1] + renderTimeLog[2] + renderTimeLog[3]) >> 2;
+
+ _framesPerGameCycle++;
+
+ if (++renderCountIndex == RENDERAVERAGETOTAL)
+ renderCountIndex = 0;
+
+ if (_renderTooSlow) {
+ initialiseRenderCycle();
+ return true;
+ }
+
+ if (_startTime + _renderAverageTime >= _totalTime) {
+ _totalTime += MILLISECSPERCYCLE;
+ _initialTime = time;
+ return true;
+ }
+
+#ifdef LIMIT_FRAME_RATE
+ if (_scrollXTarget == _scrollX && _scrollYTarget == _scrollY) {
+ // If we have already reached the scroll target sleep for the
+ // rest of the render cycle.
+ _vm->sleepUntil(_totalTime);
+ _initialTime = _vm->_system->getMillis();
+ _totalTime += MILLISECSPERCYCLE;
+ return true;
+ }
+#endif
+
+ // This is an attempt to ensure that we always reach the scroll target.
+ // Otherwise the game frequently tries to pump out new interpolation
+ // frames without ever getting anywhere.
+
+ if (ABS(_scrollX - _scrollXTarget) <= 1 && ABS(_scrollY - _scrollYTarget) <= 1) {
+ _scrollX = _scrollXTarget;
+ _scrollY = _scrollYTarget;
+ } else {
+ _scrollX = (int16)(_scrollXOld + ((_scrollXTarget - _scrollXOld) * (_startTime - _initialTime + _renderAverageTime)) / (_totalTime - _initialTime));
+ _scrollY = (int16)(_scrollYOld + ((_scrollYTarget - _scrollYOld) * (_startTime - _initialTime + _renderAverageTime)) / (_totalTime - _initialTime));
+ }
+
+ if (_scrollX != _scrollXOld || _scrollY != _scrollYOld)
+ setNeedFullRedraw();
+
+#ifdef LIMIT_FRAME_RATE
+ // Give the other threads some breathing space. This apparently helps
+ // against bug #875683, though I was never able to reproduce it for
+ // myself.
+ _vm->_system->delayMillis(10);
+#endif
+
+ return false;
+}
+
+/**
+ * Reset scrolling stuff. This function is called from initBackground()
+ */
+
+void Screen::resetRenderEngine() {
+ _parallaxScrollX = 0;
+ _parallaxScrollY = 0;
+ _scrollX = 0;
+ _scrollY = 0;
+}
+
+/**
+ * This function should be called five times with either the parallax layer
+ * or a NULL pointer in order of background parallax to foreground parallax.
+ */
+
+int32 Screen::initialiseBackgroundLayer(byte *parallax) {
+ Parallax p;
+ uint16 i, j, k;
+ byte *data;
+ byte *dst;
+
+ debug(2, "initialiseBackgroundLayer");
+
+ assert(_layer < MAXLAYERS);
+
+ if (!parallax) {
+ _layer++;
+ return RD_OK;
+ }
+
+ p.read(parallax);
+
+ _xBlocks[_layer] = (p.w + BLOCKWIDTH - 1) / BLOCKWIDTH;
+ _yBlocks[_layer] = (p.h + BLOCKHEIGHT - 1) / BLOCKHEIGHT;
+
+ _blockSurfaces[_layer] = (BlockSurface **)calloc(_xBlocks[_layer] * _yBlocks[_layer], sizeof(BlockSurface *));
+ if (!_blockSurfaces[_layer])
+ return RDERR_OUTOFMEMORY;
+
+ // Decode the parallax layer into a large chunk of memory
+
+ byte *memchunk = (byte *)calloc(_xBlocks[_layer] * _yBlocks[_layer], BLOCKWIDTH * BLOCKHEIGHT);
+ if (!memchunk)
+ return RDERR_OUTOFMEMORY;
+
+ for (i = 0; i < p.h; i++) {
+ uint32 p_offset = READ_LE_UINT32(parallax + Parallax::size() + 4 * i);
+
+ if (!p_offset)
+ continue;
+
+ byte *pLine = parallax + p_offset;
+ uint16 packets = READ_LE_UINT16(pLine);
+ uint16 offset = READ_LE_UINT16(pLine + 2);
+
+ data = pLine + 4;
+ dst = memchunk + i * p.w + offset;
+
+ if (!packets) {
+ memcpy(dst, data, p.w);
+ continue;
+ }
+
+ bool zeros = false;
+
+ for (j = 0; j < packets; j++) {
+ if (zeros) {
+ dst += *data;
+ offset += *data;
+ data++;
+ zeros = false;
+ } else if (!*data) {
+ data++;
+ zeros = true;
+ } else {
+ uint16 count = *data++;
+ memcpy(dst, data, count);
+ data += count;
+ dst += count;
+ offset += count;
+ zeros = true;
+ }
+ }
+ }
+
+ // The large memory chunk is now divided into a number of smaller
+ // surfaces. For most parallax layers, we'll end up using less memory
+ // this way, and it will be faster to draw since completely transparent
+ // surfaces are discarded.
+
+ for (i = 0; i < _xBlocks[_layer] * _yBlocks[_layer]; i++) {
+ bool block_has_data = false;
+ bool block_is_transparent = false;
+
+ int x = BLOCKWIDTH * (i % _xBlocks[_layer]);
+ int y = BLOCKHEIGHT * (i / _xBlocks[_layer]);
+
+ data = memchunk + p.w * y + x;
+
+ for (j = 0; j < BLOCKHEIGHT; j++) {
+ for (k = 0; k < BLOCKWIDTH; k++) {
+ if (x + k < p.w && y + j < p.h) {
+ if (data[j * p.w + k])
+ block_has_data = true;
+ else
+ block_is_transparent = true;
+ }
+ }
+ }
+
+ // Only assign a surface to the block if it contains data.
+
+ if (block_has_data) {
+ _blockSurfaces[_layer][i] = (BlockSurface *)malloc(sizeof(BlockSurface));
+
+ // Copy the data into the surfaces.
+ dst = _blockSurfaces[_layer][i]->data;
+ for (j = 0; j < BLOCKHEIGHT; j++) {
+ memcpy(dst, data, BLOCKWIDTH);
+ data += p.w;
+ dst += BLOCKWIDTH;
+ }
+
+ _blockSurfaces[_layer][i]->transparent = block_is_transparent;
+
+ } else
+ _blockSurfaces[_layer][i] = NULL;
+ }
+
+ free(memchunk);
+ _layer++;
+
+ return RD_OK;
+}
+
+/**
+ * Should be called once after leaving the room to free up memory.
+ */
+
+void Screen::closeBackgroundLayer() {
+ debug(2, "CloseBackgroundLayer");
+
+ for (int i = 0; i < MAXLAYERS; i++) {
+ if (_blockSurfaces[i]) {
+ for (int j = 0; j < _xBlocks[i] * _yBlocks[i]; j++)
+ if (_blockSurfaces[i][j])
+ free(_blockSurfaces[i][j]);
+ free(_blockSurfaces[i]);
+ _blockSurfaces[i] = NULL;
+ }
+ }
+
+ _layer = 0;
+}
+
+#ifdef BACKEND_8BIT
+void Screen::plotYUV(byte *lut, int width, int height, byte *const *dat) {
+ byte *buf = _buffer + ((480 - height) / 2) * RENDERWIDE + (640 - width) / 2;
+
+ int x, y;
+
+ int ypos = 0;
+ int cpos = 0;
+ int linepos = 0;
+
+ for (y = 0; y < height; y += 2) {
+ for (x = 0; x < width; x += 2) {
+ int i = ((((dat[2][cpos] + ROUNDADD) >> SHIFT) * (BITDEPTH + 1)) + ((dat[1][cpos] + ROUNDADD) >> SHIFT)) * (BITDEPTH + 1);
+ cpos++;
+
+ buf[linepos ] = lut[i + ((dat[0][ ypos ] + ROUNDADD) >> SHIFT)];
+ buf[RENDERWIDE + linepos++] = lut[i + ((dat[0][width + ypos++] + ROUNDADD) >> SHIFT)];
+ buf[linepos ] = lut[i + ((dat[0][ ypos ] + ROUNDADD) >> SHIFT)];
+ buf[RENDERWIDE + linepos++] = lut[i + ((dat[0][width + ypos++] + ROUNDADD) >> SHIFT)];
+ }
+ linepos += (2 * RENDERWIDE - width);
+ ypos += width;
+ }
+}
+#endif
+
+
+} // End of namespace Sword2
diff --git a/engines/sword2/resman.cpp b/engines/sword2/resman.cpp
new file mode 100644
index 0000000000..7387dc8f50
--- /dev/null
+++ b/engines/sword2/resman.cpp
@@ -0,0 +1,633 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "common/stdafx.h"
+#include "common/file.h"
+#include "common/system.h"
+#include "sword2/sword2.h"
+#include "sword2/defs.h"
+#include "sword2/console.h"
+#include "sword2/logic.h"
+#include "sword2/memory.h"
+#include "sword2/resman.h"
+#include "sword2/router.h"
+#include "sword2/sound.h"
+
+#define Debug_Printf _vm->_debugger->DebugPrintf
+
+namespace Sword2 {
+
+// Welcome to the easy resource manager - written in simple code for easy
+// maintenance
+//
+// The resource compiler will create two files
+//
+// resource.inf which is a list of ascii cluster file names
+// resource.tab which is a table which tells us which cluster a resource
+// is located in and the number within the cluster
+
+enum {
+ BOTH = 0x0, // Cluster is on both CDs
+ CD1 = 0x1, // Cluster is on CD1 only
+ CD2 = 0x2, // Cluster is on CD2 only
+ LOCAL_CACHE = 0x4, // Cluster is cached on HDD
+ LOCAL_PERM = 0x8 // Cluster is on HDD.
+};
+
+struct CdInf {
+ uint8 clusterName[20]; // Null terminated cluster name.
+ uint8 cd; // Cd cluster is on and whether it is on the local drive or not.
+};
+
+ResourceManager::ResourceManager(Sword2Engine *vm) {
+ _vm = vm;
+
+ // Until proven differently, assume we're on CD 1. This is so the start
+ // dialog will be able to play any music at all.
+ setCD(1);
+
+ // We read in the resource info which tells us the names of the
+ // resource cluster files ultimately, although there might be groups
+ // within the clusters at this point it makes no difference. We only
+ // wish to know what resource files there are and what is in each
+
+ Common::File file;
+ uint32 size;
+ byte *temp;
+
+ _totalClusters = 0;
+ _resConvTable = NULL;
+
+ if (!file.open("resource.inf"))
+ error("Cannot open resource.inf");
+
+ size = file.size();
+
+ // Get some space for the incoming resource file - soon to be trashed
+ temp = (byte *)malloc(size);
+
+ if (file.read(temp, size) != size) {
+ file.close();
+ error("init cannot *READ* resource.inf");
+ }
+
+ file.close();
+
+ // Ok, we've loaded in the resource.inf file which contains a list of
+ // all the files now extract the filenames.
+
+ // Using this method the Gode generated resource.inf must have #0d0a on
+ // the last entry
+
+ uint32 i = 0;
+ uint32 j = 0;
+
+ do {
+ // item must have an #0d0a
+ while (temp[i] != 13) {
+ _resFiles[_totalClusters].fileName[j] = temp[i];
+ i++;
+ j++;
+ }
+
+ // NULL terminate our extracted string
+ _resFiles[_totalClusters].fileName[j] = '\0';
+ _resFiles[_totalClusters].numEntries = -1;
+ _resFiles[_totalClusters].entryTab = NULL;
+
+ // Reset position in current slot between entries, skip the
+ // 0x0a in the source and increase the number of clusters.
+
+ j = 0;
+ i += 2;
+ _totalClusters++;
+
+ // TODO: put overload check here
+ } while (i != size);
+
+ free(temp);
+
+ // Now load in the binary id to res conversion table
+ if (!file.open("resource.tab"))
+ error("Cannot open resource.tab");
+
+ // Find how many resources
+ size = file.size();
+
+ _totalResFiles = size / 4;
+
+ // Table seems ok so malloc some space
+ _resConvTable = (uint16 *)malloc(size);
+
+ for (i = 0; i < size / 2; i++)
+ _resConvTable[i] = file.readUint16LE();
+
+ if (file.ioFailed()) {
+ file.close();
+ error("Cannot read resource.tab");
+ }
+
+ file.close();
+
+ if (!file.open("cd.inf"))
+ error("Cannot open cd.inf");
+
+ CdInf *cdInf = new CdInf[_totalClusters];
+
+ for (i = 0; i < _totalClusters; i++) {
+ file.read(cdInf[i].clusterName, sizeof(cdInf[i].clusterName));
+
+ cdInf[i].cd = file.readByte();
+
+ if (file.ioFailed())
+ error("Cannot read cd.inf");
+
+ // It has been reported that there are two different versions
+ // of the cd.inf file: One where all clusters on CD also have
+ // the LOCAL_CACHE bit set. This bit is no longer used. To
+ // avoid future problems, let's normalize the flag once and for
+ // all here.
+
+ if (cdInf[i].cd & LOCAL_PERM)
+ cdInf[i].cd = 0;
+ else if (cdInf[i].cd & CD1)
+ cdInf[i].cd = 1;
+ else if (cdInf[i].cd & CD2)
+ cdInf[i].cd = 2;
+ else
+ cdInf[i].cd = 0;
+ }
+
+ file.close();
+
+ for (i = 0; i < _totalClusters; i++) {
+ for (j = 0; j < _totalClusters; j++) {
+ if (scumm_stricmp((char *)cdInf[j].clusterName, _resFiles[i].fileName) == 0)
+ break;
+ }
+
+ if (j == _totalClusters)
+ error("%s is not in cd.inf", _resFiles[i].fileName);
+
+ _resFiles[i].cd = cdInf[j].cd;
+ }
+
+ delete [] cdInf;
+
+ debug(1, "%d resources in %d cluster files", _totalResFiles, _totalClusters);
+ for (i = 0; i < _totalClusters; i++)
+ debug(2, "filename of cluster %d: -%s (%d)", i, _resFiles[i].fileName, _resFiles[i].cd);
+
+ _resList = (Resource *)malloc(_totalResFiles * sizeof(Resource));
+
+ for (i = 0; i < _totalResFiles; i++) {
+ _resList[i].ptr = NULL;
+ _resList[i].size = 0;
+ _resList[i].refCount = 0;
+ _resList[i].prev = _resList[i].next = NULL;
+ }
+ _cacheStart = _cacheEnd = NULL;
+ _usedMem = 0;
+}
+
+ResourceManager::~ResourceManager() {
+ Resource *res = _cacheStart;
+ while (res) {
+ _vm->_memory->memFree(res->ptr);
+ res = res->next;
+ }
+ for (uint i = 0; i < _totalClusters; i++)
+ free(_resFiles[i].entryTab);
+ free(_resList);
+ free(_resConvTable);
+}
+
+/**
+ * Returns the address of a resource. Loads if not in memory. Retains a count.
+ */
+
+byte *ResourceManager::openResource(uint32 res, bool dump) {
+ assert(res < _totalResFiles);
+
+ // Is the resource in memory already? If not, load it.
+
+ if (!_resList[res].ptr) {
+ // Fetch the correct file and read in the correct portion.
+ uint16 cluFileNum = _resConvTable[res * 2]; // points to the number of the ascii filename
+ assert(cluFileNum != 0xffff);
+
+ // Relative resource within the file
+ // First we have to find the file via the _resConvTable
+ uint16 actual_res = _resConvTable[(res * 2) + 1];
+
+ debug(5, "openResource %s res %d", _resFiles[cluFileNum].fileName, res);
+
+ // If we're loading a cluster that's only available from one
+ // of the CDs, remember which one so that we can play the
+ // correct speech and music.
+
+ setCD(_resFiles[cluFileNum].cd);
+
+ // Actually, as long as the file can be found we don't really
+ // care which CD it's on. But if we can't find it, keep asking
+ // for the CD until we do.
+
+ Common::File *file = openCluFile(cluFileNum);
+
+ if (_resFiles[cluFileNum].entryTab == NULL) {
+ // we didn't read from this file before, get its index table
+ readCluIndex(cluFileNum, file);
+ }
+
+ uint32 pos = _resFiles[cluFileNum].entryTab[actual_res * 2 + 0];
+ uint32 len = _resFiles[cluFileNum].entryTab[actual_res * 2 + 1];
+
+ file->seek(pos, SEEK_SET);
+
+ debug(6, "res len %d", len);
+
+ // Ok, we know the length so try and allocate the memory.
+ _resList[res].ptr = _vm->_memory->memAlloc(len, res);
+ _resList[res].size = len;
+ _resList[res].refCount = 0;
+
+ file->read(_resList[res].ptr, len);
+
+ debug(3, "Loaded resource '%s' from '%s' on CD %d (%d)", fetchName(_resList[res].ptr), _resFiles[cluFileNum].fileName, getCD(), _resFiles[cluFileNum].cd);
+
+ if (dump) {
+ char buf[256];
+ const char *tag;
+ Common::File out;
+
+ switch (fetchType(_resList[res].ptr)) {
+ case ANIMATION_FILE:
+ tag = "anim";
+ break;
+ case SCREEN_FILE:
+ tag = "layer";
+ break;
+ case GAME_OBJECT:
+ tag = "object";
+ break;
+ case WALK_GRID_FILE:
+ tag = "walkgrid";
+ break;
+ case GLOBAL_VAR_FILE:
+ tag = "globals";
+ break;
+ case PARALLAX_FILE_null:
+ tag = "parallax"; // Not used!
+ break;
+ case RUN_LIST:
+ tag = "runlist";
+ break;
+ case TEXT_FILE:
+ tag = "text";
+ break;
+ case SCREEN_MANAGER:
+ tag = "screen";
+ break;
+ case MOUSE_FILE:
+ tag = "mouse";
+ break;
+ case WAV_FILE:
+ tag = "wav";
+ break;
+ case ICON_FILE:
+ tag = "icon";
+ break;
+ case PALETTE_FILE:
+ tag = "palette";
+ break;
+ default:
+ tag = "unknown";
+ break;
+ }
+
+#if defined(MACOS_CARBON)
+ sprintf(buf, ":dumps:%s-%d.dmp", tag, res);
+#else
+ sprintf(buf, "dumps/%s-%d.dmp", tag, res);
+#endif
+
+ if (!out.exists(buf, "")) {
+ if (out.open(buf, Common::File::kFileWriteMode, ""))
+ out.write(_resList[res].ptr, len);
+ }
+ }
+
+ // close the cluster
+ file->close();
+ delete file;
+
+ _usedMem += len;
+ checkMemUsage();
+ } else if (_resList[res].refCount == 0)
+ removeFromCacheList(_resList + res);
+
+ _resList[res].refCount++;
+
+ return _resList[res].ptr;
+}
+
+void ResourceManager::closeResource(uint32 res) {
+ assert(res < _totalResFiles);
+
+ // Don't try to close the resource if it has already been forcibly
+ // closed, e.g. by fnResetGlobals().
+
+ if (_resList[res].ptr == NULL)
+ return;
+
+ assert(_resList[res].refCount > 0);
+
+ _resList[res].refCount--;
+ if (_resList[res].refCount == 0)
+ addToCacheList(_resList + res);
+
+ // It's tempting to free the resource immediately when refCount
+ // reaches zero, but that'd be a mistake. Closing a resource does not
+ // mean "I'm not going to use this resource any more". It means that
+ // "the next time I use this resource I'm going to ask for a new
+ // pointer to it".
+ //
+ // Since the original memory manager had to deal with memory
+ // fragmentation, keeping a resource open - and thus locked down to a
+ // specific memory address - was considered a bad thing.
+}
+
+void ResourceManager::removeFromCacheList(Resource *res) {
+ if (_cacheStart == res)
+ _cacheStart = res->next;
+
+ if (_cacheEnd == res)
+ _cacheEnd = res->prev;
+
+ if (res->prev)
+ res->prev->next = res->next;
+ if (res->next)
+ res->next->prev = res->prev;
+ res->prev = res->next = NULL;
+}
+
+void ResourceManager::addToCacheList(Resource *res) {
+ res->prev = NULL;
+ res->next = _cacheStart;
+ if (_cacheStart)
+ _cacheStart->prev = res;
+ _cacheStart = res;
+ if (!_cacheEnd)
+ _cacheEnd = res;
+}
+
+Common::File *ResourceManager::openCluFile(uint16 fileNum) {
+ Common::File *file = new Common::File;
+ while (!file->open(_resFiles[fileNum].fileName)) {
+ // HACK: We have to check for this, or it'll be impossible to
+ // quit while the game is asking for the user to insert a CD.
+ // But recovering from this situation gracefully is just too
+ // much trouble, so quit now.
+ if (_vm->_quit)
+ g_system->quit();
+
+ // If the file is supposed to be on hard disk, or we're
+ // playing a demo, then we're in trouble if the file
+ // can't be found!
+
+ if ((_vm->_features & GF_DEMO) || _resFiles[fileNum].cd == 0)
+ error("Could not find '%s'", _resFiles[fileNum].fileName);
+
+ askForCD(_resFiles[fileNum].cd);
+ }
+ return file;
+}
+
+void ResourceManager::readCluIndex(uint16 fileNum, Common::File *file) {
+ if (_resFiles[fileNum].entryTab == NULL) {
+ // we didn't read from this file before, get its index table
+ if (file == NULL)
+ file = openCluFile(fileNum);
+ else
+ file->incRef();
+
+ // 1st DWORD of a cluster is an offset to the look-up table
+ uint32 table_offset = file->readUint32LE();
+ debug(6, "table offset = %d", table_offset);
+ uint32 tableSize = file->size() - table_offset; // the table is stored at the end of the file
+ file->seek(table_offset);
+
+ assert((tableSize % 8) == 0);
+ _resFiles[fileNum].entryTab = (uint32*)malloc(tableSize);
+ _resFiles[fileNum].numEntries = tableSize / 8;
+ file->read(_resFiles[fileNum].entryTab, tableSize);
+ if (file->ioFailed())
+ error("unable to read index table from file %s\n", _resFiles[fileNum].fileName);
+#ifdef SCUMM_BIG_ENDIAN
+ for (int tabCnt = 0; tabCnt < _resFiles[fileNum].numEntries * 2; tabCnt++)
+ _resFiles[fileNum].entryTab[tabCnt] = FROM_LE_32(_resFiles[fileNum].entryTab[tabCnt]);
+#endif
+ file->decRef();
+ }
+}
+
+/**
+ * Returns true if resource is valid, otherwise false.
+ */
+
+bool ResourceManager::checkValid(uint32 res) {
+ // Resource number out of range
+ if (res >= _totalResFiles)
+ return false;
+
+ // Points to the number of the ascii filename
+ uint16 parent_res_file = _resConvTable[res * 2];
+
+ // Null & void resource
+ if (parent_res_file == 0xffff)
+ return false;
+
+ return true;
+}
+
+/**
+ * Returns the total file length of a resource - i.e. all headers are included
+ * too.
+ */
+
+uint32 ResourceManager::fetchLen(uint32 res) {
+ if (_resList[res].ptr)
+ return _resList[res].size;
+
+ // Does this ever happen?
+ warning("fetchLen: Resource %u is not loaded; reading length from file", res);
+
+ // Points to the number of the ascii filename
+ uint16 parent_res_file = _resConvTable[res * 2];
+
+ // relative resource within the file
+ uint16 actual_res = _resConvTable[(res * 2) + 1];
+
+ // first we have to find the file via the _resConvTable
+ // open the cluster file
+
+ if (_resFiles[parent_res_file].entryTab == NULL) {
+ readCluIndex(parent_res_file);
+ }
+ return _resFiles[parent_res_file].entryTab[actual_res * 2 + 1];
+}
+
+void ResourceManager::checkMemUsage() {
+ while (_usedMem > MAX_MEM_CACHE) {
+ // we're using up more memory than we wanted to. free some old stuff.
+ // Newly loaded objects are added to the start of the list,
+ // we start freeing from the end, to free the oldest items first
+ if (_cacheEnd) {
+ Resource *tmp = _cacheEnd;
+ assert((tmp->refCount == 0) && (tmp->ptr) && (tmp->next == NULL));
+ removeFromCacheList(tmp);
+
+ _vm->_memory->memFree(tmp->ptr);
+ tmp->ptr = NULL;
+ _usedMem -= tmp->size;
+ } else {
+ warning("%d bytes of memory used, but cache list is empty!\n");
+ return;
+ }
+ }
+}
+
+void ResourceManager::remove(int res) {
+ if (_resList[res].ptr) {
+ removeFromCacheList(_resList + res);
+
+ _vm->_memory->memFree(_resList[res].ptr);
+ _resList[res].ptr = NULL;
+ _resList[res].refCount = 0;
+ _usedMem -= _resList[res].size;
+ }
+}
+
+/**
+ * Remove all res files from memory - ready for a total restart. This includes
+ * the player object and global variables resource.
+ */
+
+void ResourceManager::removeAll() {
+ // We need to clear the FX queue, because otherwise the sound system
+ // will still believe that the sound resources are in memory, and that
+ // it's ok to close them.
+
+ _vm->_sound->clearFxQueue();
+
+ for (uint i = 0; i < _totalResFiles; i++)
+ remove(i);
+}
+
+/**
+ * Remove all resources from memory.
+ */
+
+void ResourceManager::killAll(bool wantInfo) {
+ int nuked = 0;
+
+ // We need to clear the FX queue, because otherwise the sound system
+ // will still believe that the sound resources are in memory, and that
+ // it's ok to close them.
+
+ _vm->_sound->clearFxQueue();
+
+ for (uint i = 0; i < _totalResFiles; i++) {
+ // Don't nuke the global variables or the player object!
+ if (i == 1 || i == CUR_PLAYER_ID)
+ continue;
+
+ if (_resList[i].ptr) {
+ if (wantInfo)
+ Debug_Printf("Nuked %5d: %s\n", i, fetchName(_resList[i].ptr));
+
+ remove(i);
+ nuked++;
+ }
+ }
+
+ if (wantInfo)
+ Debug_Printf("Expelled %d resources\n", nuked);
+}
+
+/**
+ * Like killAll but only kills objects (except George & the variable table of
+ * course) - ie. forcing them to reload & restart their scripts, which
+ * simulates the effect of a save & restore, thus checking that each object's
+ * re-entrant logic works correctly, and doesn't cause a statuette to
+ * disappear forever, or some plaster-filled holes in sand to crash the game &
+ * get James in trouble again.
+ */
+
+void ResourceManager::killAllObjects(bool wantInfo) {
+ int nuked = 0;
+
+ for (uint i = 0; i < _totalResFiles; i++) {
+ // Don't nuke the global variables or the player object!
+ if (i == 1 || i == CUR_PLAYER_ID)
+ continue;
+
+ if (_resList[i].ptr) {
+ if (fetchType(_resList[i].ptr) == GAME_OBJECT) {
+ if (wantInfo)
+ Debug_Printf("Nuked %5d: %s\n", i, fetchName(_resList[i].ptr));
+
+ remove(i);
+ nuked++;
+ }
+ }
+ }
+
+ if (wantInfo)
+ Debug_Printf("Expelled %d resources\n", nuked);
+}
+
+void ResourceManager::askForCD(int cd) {
+ byte *textRes;
+
+ // Stop any music from playing - so the system no longer needs the
+ // current CD - otherwise when we take out the CD, Windows will
+ // complain!
+
+ _vm->_sound->stopMusic(true);
+
+ textRes = openResource(2283);
+ _vm->_screen->displayMsg(_vm->fetchTextLine(textRes, 5 + cd) + 2, 0);
+ closeResource(2283);
+
+ // The original code probably determined automagically when the correct
+ // CD had been inserted, but our backend doesn't support that, and
+ // anyway I don't know if all systems allow that sort of thing. So we
+ // wait for the user to press any key instead, or click the mouse.
+ //
+ // But just in case we ever try to identify the CDs by their labels,
+ // they should be:
+ //
+ // CD1: "RBSII1" (or "PCF76" for the PCF76 version, whatever that is)
+ // CD2: "RBSII2"
+}
+
+} // End of namespace Sword2
diff --git a/engines/sword2/resman.h b/engines/sword2/resman.h
new file mode 100644
index 0000000000..18b5cb8765
--- /dev/null
+++ b/engines/sword2/resman.h
@@ -0,0 +1,134 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#ifndef RESMAN_H
+#define RESMAN_H
+
+namespace Common {
+ class File;
+}
+
+#define MAX_MEM_CACHE (8 * 1024 * 1024) // we keep up to 8 megs of resource data files in memory
+#define MAX_res_files 20
+
+namespace Sword2 {
+
+class Sword2Engine;
+
+struct Resource {
+ byte *ptr;
+ uint32 size;
+ uint32 refCount;
+ Resource *next, *prev;
+};
+
+struct ResourceFile {
+ char fileName[20];
+ int32 numEntries;
+ uint32 *entryTab;
+ uint8 cd;
+};
+
+class ResourceManager {
+private:
+ Common::File *openCluFile(uint16 fileNum);
+ void readCluIndex(uint16 fileNum, Common::File *file = NULL);
+ void removeFromCacheList(Resource *res);
+ void addToCacheList(Resource *res);
+ void checkMemUsage();
+
+ Sword2Engine *_vm;
+
+ int _curCD;
+ uint32 _totalResFiles;
+ uint32 _totalClusters;
+
+ // Gode generated res-id to res number/rel number conversion table
+
+ uint16 *_resConvTable;
+ ResourceFile _resFiles[MAX_res_files];
+ Resource *_resList;
+
+ Resource *_cacheStart, *_cacheEnd;
+ uint32 _usedMem; // amount of used memory in bytes
+
+public:
+ ResourceManager(Sword2Engine *vm); // read in the config file
+ ~ResourceManager();
+
+ uint32 getNumResFiles() { return _totalResFiles; }
+ uint32 getNumClusters() { return _totalClusters; }
+ ResourceFile *getResFiles() { return _resFiles; }
+ Resource *getResList() { return _resList; }
+
+ byte *openResource(uint32 res, bool dump = false);
+ void closeResource(uint32 res);
+
+ bool checkValid(uint32 res);
+ uint32 fetchLen(uint32 res);
+ uint8 fetchType(uint32 res) {
+ byte *ptr = openResource(res);
+ uint8 type = ptr[0];
+ closeResource(res);
+
+ return type;
+ }
+
+ uint8 fetchType(byte *ptr) {
+ return ptr[0];
+ }
+
+ byte *fetchName(uint32 res, byte *buf) {
+ byte *ptr = openResource(res);
+ memcpy(buf, ptr + 10, NAME_LEN);
+ closeResource(res);
+
+ return buf;
+ }
+
+ byte *fetchName(byte *ptr) {
+ return ptr + 10;
+ }
+
+ // Prompts the user for the specified CD.
+ void askForCD(int cd);
+
+ void setCD(int cd) {
+ if (cd)
+ _curCD = cd;
+ }
+
+ int getCD() {
+ return _curCD;
+ }
+
+ void remove(int res);
+ void removeAll();
+
+ // ----console commands
+
+ void killAll(bool wantInfo);
+ void killAllObjects(bool wantInfo);
+};
+
+} // End of namespace Sword2
+
+#endif
diff --git a/engines/sword2/router.cpp b/engines/sword2/router.cpp
new file mode 100644
index 0000000000..bbca609e3c
--- /dev/null
+++ b/engines/sword2/router.cpp
@@ -0,0 +1,2506 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+// ---------------------------------------------------------------------------
+// ROUTER.CPP by James
+//
+// A rehash of Jeremy's original jrouter.c, containing low-level system
+// routines for calculating routes between points inside a walk-grid, and
+// constructing walk animations from mega-sets.
+//
+// jrouter.c underwent 2 major reworks from the original:
+// (1) Restructured to allow more flexibility in the mega-sets, ie. more info
+// taken from the walk-data
+// - the new George & Nico mega-sets & walk-data were then tested &
+// tweaked in the Sword1 system
+// (2) Updated for the new Sword2 system, ie. new object structures
+// - now compatible with Sword2, the essential code already having been
+// tested
+//
+// ---------------------------------------------------------------------------
+
+/****************************************************************************
+ * JROUTER.C polygon router with modular walks
+ * using a tree of modules
+ * 21 july 94
+ * 3 november 94
+ * System currently works by scanning grid data and coming up with a ROUTE
+ * as a series of way points(nodes), the smoothest eight directional PATH
+ * through these nodes is then found, and a WALK created to fit the PATH.
+ *
+ * Two funtions are called by the user, RouteFinder creates a route as a
+ * module list, HardWalk creates an animation list from the module list.
+ * The split is only provided to allow the possibility of turning the
+ * autorouter over two game cycles.
+ ****************************************************************************
+ *
+ * Routine timings on osborne 486
+ *
+ * Read floor resource (file already loaded) 112 pixels
+ *
+ * Read mega resource (file already loaded) 112 pixels
+ *
+ *
+ *
+ ****************************************************************************
+ *
+ * Modified 12 Oct 95
+ *
+ * Target Points within 1 pixel of a line are ignored ???
+ *
+ * Modules split into Points within 1 pixel of a line are ignored ???
+ *
+ ****************************************************************************
+ *
+ * TOTALLY REHASHED BY JAMES FOR NEW MEGAS USING OLD SYSTEM
+ * THEN REINCARNATED BY JAMES FOR NEW MEGAS USING NEW SYSTEM
+ *
+ ****************************************************************************/
+
+#include "common/stdafx.h"
+#include "common/stream.h"
+#include "sword2/sword2.h"
+#include "sword2/defs.h"
+#include "sword2/logic.h"
+#include "sword2/resman.h"
+#include "sword2/router.h"
+
+namespace Sword2 {
+
+//----------------------------------------------------------
+// (4) WALK-GRID FILES
+//----------------------------------------------------------
+// a walk-grid file consists of:
+//
+// standard file header
+// walk-grid file header
+// walk-grid data
+
+// Walk-Grid Header - taken directly from old "header.h" in STD_INC
+
+struct WalkGridHeader {
+ int32 numBars; // number of bars on the floor
+ int32 numNodes; // number of nodes
+};
+
+uint8 Router::returnSlotNo(uint32 megaId) {
+ if (_vm->_logic->readVar(ID) == CUR_PLAYER_ID) {
+ // George (8)
+ return 0;
+ } else {
+ // One of Nico's mega id's
+ return 1;
+ }
+}
+
+void Router::allocateRouteMem() {
+ uint8 slotNo;
+
+ // Player character always always slot 0, while the other mega
+ // (normally Nico) always uses slot 1
+ // Better this way, so that if mega object removed from memory while
+ // in middle of route, the old route will be safely cleared from
+ // memory just before they create a new one
+
+ slotNo = returnSlotNo(_vm->_logic->readVar(ID));
+
+ // if this slot is already used, then it can't be needed any more
+ // because this id is creating a new route!
+
+ if (_routeSlots[slotNo])
+ freeRouteMem();
+
+ _routeSlots[slotNo] = (WalkData *)malloc(sizeof(WalkData) * O_WALKANIM_SIZE);
+
+ // 12000 bytes were used for this in Sword1 mega compacts, based on
+ // 20 bytes per 'WalkData' frame
+ // ie. allowing for 600 frames including end-marker
+ // Now 'WalkData' is 8 bytes, so 8*600 = 4800 bytes.
+ // Note that a 600 frame walk lasts about 48 seconds!
+ // (600fps / 12.5s = 48s)
+
+ // mega keeps note of which slot contains the pointer to it's walk
+ // animation mem block
+ // +1 so that '0' can mean "not walking"
+ // megaObject->route_slot_id = slotNo + 1;
+}
+
+WalkData *Router::getRouteMem() {
+ uint8 slotNo = returnSlotNo(_vm->_logic->readVar(ID));
+
+ return (WalkData *)_routeSlots[slotNo];
+}
+
+void Router::freeRouteMem() {
+ uint8 slotNo = returnSlotNo(_vm->_logic->readVar(ID));
+
+ free(_routeSlots[slotNo]);
+ _routeSlots[slotNo] = NULL;
+}
+
+void Router::freeAllRouteMem() {
+ for (int i = 0; i < TOTAL_ROUTE_SLOTS; i++) {
+ free(_routeSlots[i]);
+ _routeSlots[i] = NULL;
+ }
+}
+
+int32 Router::routeFinder(byte *ob_mega, byte *ob_walkdata, int32 x, int32 y, int32 dir) {
+ /*********************************************************************
+ * RouteFinder.C polygon router with modular walks
+ * 21 august 94
+ * 3 november 94
+ * routeFinder creates a list of modules that enables HardWalk to
+ * create an animation list.
+ *
+ * routeFinder currently works by scanning grid data and coming up
+ * with a ROUTE as a series of way points(nodes), the smoothest eight
+ * directional PATH through these nodes is then found, this
+ * information is made available to HardWalk for a WALK to be created
+ * to fit the PATH.
+ *
+ * 30 november 94 return values modified
+ *
+ * return 0 = failed to find a route
+ *
+ * 1 = found a route
+ *
+ * 2 = mega already at target
+ *
+ *********************************************************************/
+
+ int32 routeFlag = 0;
+ int32 solidFlag = 0;
+ WalkData *walkAnim;
+
+ // megaId = id;
+
+ setUpWalkGrid(ob_mega, x, y, dir);
+ loadWalkData(ob_walkdata);
+
+ walkAnim = getRouteMem();
+
+ // All route data now loaded start finding a route
+
+ // Check if we can get a route through the floor. changed 12 Oct95 JPS
+
+ routeFlag = getRoute();
+
+ switch (routeFlag) {
+ case 2:
+ // special case for zero length route
+
+ // if target direction specified as any
+ if (_targetDir > 7)
+ _targetDir = _startDir;
+
+ // just a turn on the spot is required set an end module for
+ // the route let the animator deal with it
+ // modularPath is normally set by extractRoute
+
+ _modularPath[0].dir = _startDir;
+ _modularPath[0].num = 0;
+ _modularPath[0].x = _startX;
+ _modularPath[0].y = _startY;
+ _modularPath[1].dir = _targetDir;
+ _modularPath[1].num = 0;
+ _modularPath[1].x = _startX;
+ _modularPath[1].y = _startY;
+ _modularPath[2].dir = 9;
+ _modularPath[2].num = ROUTE_END_FLAG;
+
+ slidyWalkAnimator(walkAnim);
+ routeFlag = 2;
+ break;
+ case 1:
+ // A normal route. Convert the route to an exact path
+ smoothestPath();
+
+ // The Route had waypoints and direction options
+
+ // The Path is an exact set of lines in 8 directions that
+ // reach the target.
+
+ // The path is in module format, but steps taken in each
+ // direction are not accurate
+
+ // if target dir = 8 then the walk isn't linked to an anim so
+ // we can create a route without sliding and miss the exact
+ // target
+
+#ifndef FORCE_SLIDY
+ if (_targetDir == 8) {
+ // can end facing ANY direction (ie. exact end
+ // position not vital) - so use SOLID walk to
+ // avoid sliding to exact position
+
+ solidPath();
+ solidFlag = solidWalkAnimator(walkAnim);
+ }
+#endif
+
+ if (!solidFlag) {
+ // if we failed to create a SOLID route, do a SLIDY
+ // one instead
+
+ slidyPath();
+ slidyWalkAnimator(walkAnim);
+ }
+
+ break;
+ default:
+ // Route didn't reach target so assume point was off the floor
+ // routeFlag = 0;
+ break;
+ }
+
+ return routeFlag; // send back null route
+}
+
+int32 Router::getRoute() {
+ /*********************************************************************
+ * GetRoute.C extract a path from walk grid
+ * 12 october 94
+ *
+ * GetRoute currently works by scanning grid data and coming up with
+ * a ROUTE as a series of way points(nodes).
+ *
+ * static routeData _route[O_ROUTE_SIZE];
+ *
+ * return 0 = failed to find a route
+ *
+ * 1 = found a route
+ *
+ * 2 = mega already at target
+ *
+ * 3 = failed to find a route because target was on a line
+ *
+ *********************************************************************/
+
+ int32 routeGot = 0;
+
+ if (_startX == _targetX && _startY == _targetY)
+ routeGot = 2;
+ else {
+ // 'else' added by JEL (23jan96) otherwise 'routeGot' affected
+ // even when already set to '2' above - causing some 'turns'
+ // to walk downwards on the spot
+
+ // returns 3 if target on a line ( +- 1 pixel )
+ routeGot = checkTarget(_targetX, _targetY);
+ }
+
+ if (routeGot == 0) {
+ // still looking for a route check if target is within a pixel
+ // of a line
+
+ // scan through the nodes linking each node to its nearest
+ // neighbour until no more nodes change
+
+ // This is the routine that finds a route using scan()
+
+ int32 level = 1;
+
+ while (scan(level))
+ level++;
+
+ // Check to see if the route reached the target
+
+ if (_node[_nNodes].dist < 9999) {
+ // it did so extract the route as nodes and the
+ // directions to go between each node
+
+ routeGot = 1;
+ extractRoute();
+
+ // route.X,route.Y and route.Dir now hold all the
+ // route infomation with the target dir or route
+ // continuation
+ }
+ }
+
+ return routeGot;
+}
+
+// THE SLIDY PATH ROUTINES
+
+int32 Router::smoothestPath() {
+ // This is the second big part of the route finder and the the only
+ // bit that tries to be clever (the other bits are clever).
+ //
+ // This part of the autorouter creates a list of modules from a set of
+ // lines running across the screen. The task is complicated by two
+ // things:
+ //
+ // Firstly in choosing a route through the maze of nodes the routine
+ // tries to minimise the amount of each individual turn avoiding 90
+ // degree and greater turns (where possible) and reduces the total
+ // number of turns (subject to two 45 degree turns being better than
+ // one 90 degree turn).
+ //
+ // Secondly when walking in a given direction the number of steps
+ // required to reach the end of that run is not calculated accurately.
+ // This is because I was unable to derive a function to relate number
+ // of steps taken between two points to the shrunken step size
+
+ int i;
+ int32 steps = 0;
+ int32 lastDir;
+ int32 tempturns[4];
+ int32 turns[4];
+ int32 turntable[NO_DIRECTIONS] = { 0, 1, 3, 5, 7, 5, 3, 1 };
+
+ // route.X route.Y and route.Dir start at far end
+
+ _smoothPath[0].x = _startX;
+ _smoothPath[0].y = _startY;
+ _smoothPath[0].dir = _startDir;
+ _smoothPath[0].num = 0;
+
+ lastDir = _startDir;
+
+ // for each section of the route
+
+ for (int p = 0; p < _routeLength; p++) {
+ int32 dirS = _route[p].dirS;
+ int32 dirD = _route[p].dirD;
+ int32 nextDirS = _route[p + 1].dirS;
+ int32 nextDirD = _route[p + 1].dirD;
+
+ // Check directions into and out of a pair of nodes going in
+ int32 dS = dirS - lastDir;
+ if (dS < 0)
+ dS = dS + NO_DIRECTIONS;
+
+ int32 dD = dirD - lastDir;
+ if (dD < 0)
+ dD = dD + NO_DIRECTIONS;
+
+ // coming out
+ int32 dSS = dirS - nextDirS;
+ if (dSS < 0)
+ dSS = dSS + NO_DIRECTIONS;
+
+ int32 dDD = dirD - nextDirD;
+ if (dDD < 0)
+ dDD = dDD + NO_DIRECTIONS;
+
+ int32 dSD = dirS - nextDirD;
+ if (dSD < 0)
+ dSD = dSD + NO_DIRECTIONS;
+
+ int32 dDS = dirD - nextDirS;
+ if (dDS < 0)
+ dDS = dDS + NO_DIRECTIONS;
+
+ // Determine the amount of turning involved in each possible
+ // path
+
+ dS = turntable[dS];
+ dD = turntable[dD];
+ dSS = turntable[dSS];
+ dDD = turntable[dDD];
+ dSD = turntable[dSD];
+ dDS = turntable[dDS];
+
+ // get the best path out ie assume next section uses best
+ // direction
+
+ if (dSD < dSS)
+ dSS = dSD;
+
+ if (dDS < dDD)
+ dDD = dDS;
+
+ // Rate each option. Split routes look crap so weight against
+ // them
+
+ int32 SS = dS + dSS + 3;
+ int32 SD = dS + dDD;
+ int32 DS = dD + dSS;
+ int32 DD = dD + dDD + 3;
+
+ // set up turns as a sorted array of the turn values
+
+ tempturns[0] = SS;
+ turns[0] = 0;
+ tempturns[1] = SD;
+ turns[1] = 1;
+ tempturns[2] = DS;
+ turns[2] = 2;
+ tempturns[3] = DD;
+ turns[3] = 3;
+
+ for (i = 0; i < 3; i++) {
+ for (int j = 0; j < 3; j++) {
+ if (tempturns[j] > tempturns[j + 1]) {
+ SWAP(turns[j], turns[j + 1]);
+ SWAP(tempturns[j], tempturns[j + 1]);
+ }
+ }
+ }
+
+ // best option matched in order of the priority we would like
+ // to see on the screen but each option must be checked to see
+ // if it can be walked
+
+ int32 options = newCheck(1, _route[p].x, _route[p].y, _route[p + 1].x, _route[p + 1].y);
+
+ assert(options);
+
+ i = 0;
+ steps = 0;
+
+ do {
+ int32 opt = 1 << turns[i];
+ if (options & opt)
+ steps = smoothCheck(turns[i], p, dirS, dirD);
+ i++;
+ } while (steps == 0 && i < 4);
+
+ assert(steps);
+
+ // route.X route.Y route.dir and bestTurns start at far end
+ }
+
+ // best turns will end heading as near as possible to target dir rest
+ // is down to anim for now
+
+ _smoothPath[steps].dir = 9;
+ _smoothPath[steps].num = ROUTE_END_FLAG;
+ return 1;
+}
+
+int32 Router::smoothCheck(int32 best, int32 p, int32 dirS, int32 dirD) {
+ /*********************************************************************
+ * Slip sliding away
+ * This path checker checks to see if a walk that exactly follows the
+ * path would be valid. This should be inherently true for atleast one
+ * of the turn options.
+ * No longer checks the data it only creates the smoothPath array JPS
+ *********************************************************************/
+
+ static int32 k;
+ int32 dsx, dsy;
+ int32 ddx, ddy;
+ int32 ss0, ss1, ss2;
+ int32 sd0, sd1, sd2;
+
+ if (p == 0)
+ k = 1;
+
+ int32 x = _route[p].x;
+ int32 y = _route[p].y;
+ int32 x2 = _route[p + 1].x;
+ int32 y2 = _route[p + 1].y;
+ int32 ldx = x2 - x;
+ int32 ldy = y2 - y;
+ int32 dirX = 1;
+ int32 dirY = 1;
+
+ if (ldx < 0) {
+ ldx = -ldx;
+ dirX = -1;
+ }
+
+ if (ldy < 0) {
+ ldy = -ldy;
+ dirY = -1;
+ }
+
+ // set up sd0-ss2 to reflect possible movement in each direction
+
+ if (dirS == 0 || dirS == 4) { // vert and diag
+ ddx = ldx;
+ ddy = (ldx * _diagonaly) / _diagonalx;
+ dsy = ldy - ddy;
+ ddx = ddx * dirX;
+ ddy = ddy * dirY;
+ dsy = dsy * dirY;
+ dsx = 0;
+
+ sd0 = (ddx + _modX[dirD] / 2) / _modX[dirD];
+ ss0 = (dsy + _modY[dirS] / 2) / _modY[dirS];
+ sd1 = sd0 / 2;
+ ss1 = ss0 / 2;
+ sd2 = sd0 - sd1;
+ ss2 = ss0 - ss1;
+ } else {
+ ddy = ldy;
+ ddx = (ldy * _diagonalx) / _diagonaly;
+ dsx = ldx - ddx;
+ ddy = ddy * dirY;
+ ddx = ddx * dirX;
+ dsx = dsx * dirX;
+ dsy = 0;
+
+ sd0 = (ddy + _modY[dirD] / 2) / _modY[dirD];
+ ss0 = (dsx + _modX[dirS] / 2) / _modX[dirS];
+ sd1 = sd0 / 2;
+ ss1 = ss0 / 2;
+ sd2 = sd0 - sd1;
+ ss2 = ss0 - ss1;
+ }
+
+ switch (best) {
+ case 0: // halfsquare, diagonal, halfsquare
+ _smoothPath[k].x = x + dsx / 2;
+ _smoothPath[k].y = y + dsy / 2;
+ _smoothPath[k].dir = dirS;
+ _smoothPath[k].num = ss1;
+ k++;
+
+ _smoothPath[k].x = x + dsx / 2 + ddx;
+ _smoothPath[k].y = y + dsy / 2 + ddy;
+ _smoothPath[k].dir = dirD;
+ _smoothPath[k].num = sd0;
+ k++;
+
+ _smoothPath[k].x = x + dsx + ddx;
+ _smoothPath[k].y = y + dsy + ddy;
+ _smoothPath[k].dir = dirS;
+ _smoothPath[k].num = ss2;
+ k++;
+
+ break;
+ case 1: // square, diagonal
+ _smoothPath[k].x = x + dsx;
+ _smoothPath[k].y = y + dsy;
+ _smoothPath[k].dir = dirS;
+ _smoothPath[k].num = ss0;
+ k++;
+
+ _smoothPath[k].x = x2;
+ _smoothPath[k].y = y2;
+ _smoothPath[k].dir = dirD;
+ _smoothPath[k].num = sd0;
+ k++;
+
+ break;
+ case 2: // diagonal square
+ _smoothPath[k].x = x + ddx;
+ _smoothPath[k].y = y + ddy;
+ _smoothPath[k].dir = dirD;
+ _smoothPath[k].num = sd0;
+ k++;
+
+ _smoothPath[k].x = x2;
+ _smoothPath[k].y = y2;
+ _smoothPath[k].dir = dirS;
+ _smoothPath[k].num = ss0;
+ k++;
+
+ break;
+ default: // halfdiagonal, square, halfdiagonal
+ _smoothPath[k].x = x + ddx / 2;
+ _smoothPath[k].y = y + ddy / 2;
+ _smoothPath[k].dir = dirD;
+ _smoothPath[k].num = sd1;
+ k++;
+
+ _smoothPath[k].x = x + dsx + ddx / 2;
+ _smoothPath[k].y = y + dsy + ddy / 2;
+ _smoothPath[k].dir = dirS;
+ _smoothPath[k].num = ss0;
+ k++;
+
+ _smoothPath[k].x = x2;
+ _smoothPath[k].y = y2;
+ _smoothPath[k].dir = dirD;
+ _smoothPath[k].num = sd2;
+ k++;
+
+ break;
+ }
+
+ return k;
+}
+
+void Router::slidyPath() {
+ /*********************************************************************
+ * slidyPath creates a path based on part steps with no sliding to get
+ * as near as possible to the target without any sliding this routine
+ * is intended for use when just clicking about.
+ *
+ * produce a module list from the line data
+ *********************************************************************/
+
+ int32 smooth = 1;
+ int32 slidy = 1;
+
+ // strip out the short sections
+
+ _modularPath[0].x = _smoothPath[0].x;
+ _modularPath[0].y = _smoothPath[0].y;
+ _modularPath[0].dir = _smoothPath[0].dir;
+ _modularPath[0].num = 0;
+
+ while (_smoothPath[smooth].num < ROUTE_END_FLAG) {
+ int32 scale = _scaleA * _smoothPath[smooth].y + _scaleB;
+ int32 deltaX = _smoothPath[smooth].x - _modularPath[slidy - 1].x;
+ int32 deltaY = _smoothPath[smooth].y - _modularPath[slidy - 1].y;
+ // quarter a step minimum
+ int32 stepX = (scale * _modX[_smoothPath[smooth].dir]) >> 19;
+ int32 stepY = (scale * _modY[_smoothPath[smooth].dir]) >> 19;
+
+ if (ABS(deltaX) >= ABS(stepX) && ABS(deltaY) >= ABS(stepY)) {
+ _modularPath[slidy].x = _smoothPath[smooth].x;
+ _modularPath[slidy].y = _smoothPath[smooth].y;
+ _modularPath[slidy].dir = _smoothPath[smooth].dir;
+ _modularPath[slidy].num = 1;
+ slidy++;
+ }
+ smooth++;
+ }
+
+ // in case the last bit had no steps
+
+ if (slidy > 1) {
+ _modularPath[slidy - 1].x = _smoothPath[smooth - 1].x;
+ _modularPath[slidy - 1].y = _smoothPath[smooth - 1].y;
+ }
+
+ // set up the end of the walk
+
+ _modularPath[slidy].x = _smoothPath[smooth - 1].x;
+ _modularPath[slidy].y = _smoothPath[smooth - 1].y;
+ _modularPath[slidy].dir = _targetDir;
+ _modularPath[slidy].num = 0;
+ slidy++;
+
+ _modularPath[slidy].x = _smoothPath[smooth - 1].x;
+ _modularPath[slidy].y = _smoothPath[smooth - 1].y;
+ _modularPath[slidy].dir = 9;
+ _modularPath[slidy].num = ROUTE_END_FLAG;
+ slidy++;
+}
+
+// SLOW IN
+
+bool Router::addSlowInFrames(WalkData *walkAnim) {
+ if (_walkData.usingSlowInFrames && _modularPath[1].num > 0) {
+ for (int slowInFrameNo = 0; slowInFrameNo < _walkData.nSlowInFrames[_currentDir]; slowInFrameNo++) {
+ walkAnim[_stepCount].frame = _firstSlowInFrame[_currentDir] + slowInFrameNo;
+ walkAnim[_stepCount].step = 0;
+ walkAnim[_stepCount].dir = _currentDir;
+ walkAnim[_stepCount].x = _moduleX;
+ walkAnim[_stepCount].y = _moduleY;
+ _stepCount++;
+ }
+ return true;
+ }
+
+ return false;
+}
+
+void Router::earlySlowOut(byte *ob_mega, byte *ob_walkdata) {
+ int32 slowOutFrameNo;
+ int32 walk_pc;
+ WalkData *walkAnim;
+
+ ObjectMega obMega(ob_mega);
+
+ debug(5, "EARLY SLOW-OUT");
+
+ loadWalkData(ob_walkdata);
+
+ debug(5, "********************************");
+ debug(5, "_framesPerStep = %d", _framesPerStep);
+ debug(5, "_numberOfSlowOutFrames = %d", _numberOfSlowOutFrames);
+ debug(5, "_firstWalkingTurnLeftFrame = %d", _firstWalkingTurnLeftFrame);
+ debug(5, "_firstWalkingTurnRightFrame = %d", _firstWalkingTurnRightFrame);
+ debug(5, "_firstSlowOutFrame = %d", _firstSlowOutFrame);
+ debug(5, "********************************");
+
+ walk_pc = obMega.getWalkPc();
+
+ walkAnim = getRouteMem();
+
+ // if this mega does actually have slow-out frames
+ if (_walkData.usingSlowOutFrames) {
+ // overwrite the next step (half a cycle) of the walk
+ // (ie .step - 0..5)
+
+ do {
+ debug(5, "STEP NUMBER: walkAnim[%d].step = %d", walk_pc, walkAnim[walk_pc].step);
+ debug(5, "ORIGINAL FRAME: walkAnim[%d].frame = %d", walk_pc, walkAnim[walk_pc].frame);
+
+ // map from existing walk frame across to correct
+ // frame number of slow-out - remember, there may be
+ // more slow-out frames than walk-frames!
+
+ if (walkAnim[walk_pc].frame >= _firstWalkingTurnRightFrame) {
+ // if it's a walking turn-right, rather than a
+ // normal step, then map it to a normal step
+ // frame first
+
+ walkAnim[walk_pc].frame -= _firstWalkingTurnRightFrame;
+ debug(5, "MAPPED TO WALK: walkAnim[%d].frame = %d (walking turn-right frame --> walk frame)", walk_pc, walkAnim[walk_pc].frame);
+ } else if (walkAnim[walk_pc].frame >= _firstWalkingTurnLeftFrame) {
+ // if it's a walking turn-left, rather than a
+ // normal step, then map it to a normal step
+ // frame first
+
+ walkAnim[walk_pc].frame -= _firstWalkingTurnLeftFrame;
+ debug(5, "MAPPED TO WALK: walkAnim[%d].frame = %d (walking turn-left frame --> walk frame)", walk_pc, walkAnim[walk_pc].frame);
+ }
+
+ walkAnim[walk_pc].frame += _firstSlowOutFrame + ((walkAnim[walk_pc].frame / _framesPerStep) * (_numberOfSlowOutFrames - _framesPerStep));
+ walkAnim[walk_pc].step = 0;
+ debug(5, "SLOW-OUT FRAME: walkAnim[%d].frame = %d",walk_pc, walkAnim[walk_pc].frame);
+ walk_pc++;
+ } while (walkAnim[walk_pc].step > 0);
+
+ // add stationary frame(s) (OPTIONAL)
+
+ for (slowOutFrameNo = _framesPerStep; slowOutFrameNo < _numberOfSlowOutFrames; slowOutFrameNo++) {
+ walkAnim[walk_pc].frame = walkAnim[walk_pc - 1].frame + 1;
+ debug(5, "EXTRA FRAME: walkAnim[%d].frame = %d", walk_pc, walkAnim[walk_pc].frame);
+ walkAnim[walk_pc].step = 0;
+ walkAnim[walk_pc].dir = walkAnim[walk_pc - 1].dir;
+ walkAnim[walk_pc].x = walkAnim[walk_pc - 1].x;
+ walkAnim[walk_pc].y = walkAnim[walk_pc - 1].y;
+ walk_pc++;
+ }
+ } else {
+ // this mega doesn't have slow-out frames
+ // stand in current direction
+
+ walkAnim[walk_pc].frame = _firstStandFrame + walkAnim[walk_pc - 1].dir;
+ walkAnim[walk_pc].step = 0;
+ walkAnim[walk_pc].dir = walkAnim[walk_pc - 1].dir;
+ walkAnim[walk_pc].x = walkAnim[walk_pc - 1].x;
+ walkAnim[walk_pc].y = walkAnim[walk_pc - 1].y;
+ walk_pc++;
+ }
+
+ // end of sequence
+ walkAnim[walk_pc].frame = 512;
+
+ // so that this doesn't happen again while 'george_walking' is still
+ // '2'
+ walkAnim[walk_pc].step = 99;
+}
+
+// SLOW OUT
+
+void Router::addSlowOutFrames(WalkData *walkAnim) {
+ int32 slowOutFrameNo;
+
+ // if the mega did actually walk, we overwrite the last step (half a
+ // cycle) with slow-out frames + add any necessary stationary frames
+
+ if (_walkData.usingSlowOutFrames && _lastCount >= _framesPerStep) {
+ // place stop frames here
+ // slowdown at the end of the last walk
+
+ slowOutFrameNo = _lastCount - _framesPerStep;
+
+ debug(5, "SLOW OUT: slowOutFrameNo(%d) = _lastCount(%d) - _framesPerStep(%d)", slowOutFrameNo, _lastCount, _framesPerStep);
+
+ // overwrite the last step (half a cycle) of the walk
+
+ do {
+ // map from existing walk frame across to correct
+ // frame number of slow-out - remember, there may be
+ // more slow-out frames than walk-frames!
+
+ walkAnim[slowOutFrameNo].frame += _firstSlowOutFrame + ((walkAnim[slowOutFrameNo].frame / _framesPerStep) * (_numberOfSlowOutFrames - _framesPerStep));
+
+ // because no longer a normal walk-step
+ walkAnim[slowOutFrameNo].step = 0;
+
+ debug(5, "walkAnim[%d].frame = %d",slowOutFrameNo,walkAnim[slowOutFrameNo].frame);
+ slowOutFrameNo++;
+ } while (slowOutFrameNo < _lastCount);
+
+ // add stationary frame(s) (OPTIONAL)
+
+ for (slowOutFrameNo = _framesPerStep; slowOutFrameNo < _numberOfSlowOutFrames; slowOutFrameNo++) {
+ walkAnim[_stepCount].frame = walkAnim[_stepCount - 1].frame + 1;
+
+ debug(5, "EXTRA FRAMES: walkAnim[%d].frame = %d", _stepCount, walkAnim[_stepCount].frame);
+
+ walkAnim[_stepCount].step = 0;
+ walkAnim[_stepCount].dir = walkAnim[_stepCount - 1].dir;
+ walkAnim[_stepCount].x = walkAnim[_stepCount - 1].x;
+ walkAnim[_stepCount].y = walkAnim[_stepCount - 1].y;
+ _stepCount++;
+ }
+ }
+}
+
+void Router::slidyWalkAnimator(WalkData *walkAnim) {
+ /*********************************************************************
+ * Skidding every where HardWalk creates an animation that exactly
+ * fits the smoothPath and uses foot slipping to fit whole steps into
+ * the route
+ *
+ * Parameters: georgeg, mouseg
+ * Returns: rout
+ *
+ * produce a module list from the line data
+ *********************************************************************/
+
+ static int32 left = 0;
+ int32 p;
+ int32 lastDir;
+ int32 lastRealDir;
+ int32 turnDir;
+ int32 scale;
+ int32 step;
+ int32 module;
+ int32 moduleEnd;
+ int32 module16X;
+ int32 module16Y;
+ int32 stepX;
+ int32 stepY;
+ int32 errorX;
+ int32 errorY;
+ int32 lastErrorX;
+ int32 lastErrorY;
+ int32 frameCount;
+ int32 frames;
+
+ p = 0;
+ lastDir = _modularPath[0].dir;
+ _currentDir = _modularPath[1].dir;
+
+ if (_currentDir == NO_DIRECTIONS)
+ _currentDir = lastDir;
+
+ _moduleX = _startX;
+ _moduleY = _startY;
+ module16X = _moduleX << 16;
+ module16Y = _moduleY << 16;
+ _stepCount = 0;
+
+ // START THE WALK WITH THE FIRST STANDFRAME THIS MAY CAUSE A DELAY
+ // BUT IT STOPS THE PLAYER MOVING FOR COLLISIONS ARE DETECTED
+
+ debug(5, "SLIDY: STARTING THE WALK");
+
+ module = _framesPerChar + lastDir;
+ walkAnim[_stepCount].frame = module;
+ walkAnim[_stepCount].step = 0;
+ walkAnim[_stepCount].dir = lastDir;
+ walkAnim[_stepCount].x = _moduleX;
+ walkAnim[_stepCount].y = _moduleY;
+ _stepCount++;
+
+ // TURN TO START THE WALK
+
+ debug(5, "SLIDY: TURNING TO START THE WALK");
+ // rotate if we need to
+
+ if (lastDir != _currentDir) {
+ // get the direction to turn
+ turnDir = _currentDir - lastDir;
+ if (turnDir < 0)
+ turnDir += NO_DIRECTIONS;
+
+ if (turnDir > 4)
+ turnDir = -1;
+ else if (turnDir > 0)
+ turnDir = 1;
+
+ // rotate to new walk direction
+ // for george and nico put in a head turn at the start
+
+ if (_walkData.usingStandingTurnFrames) {
+ // new frames for turn frames 29oct95jps
+ if (turnDir < 0)
+ module = _firstStandingTurnLeftFrame + lastDir;
+ else
+ module = _firstStandingTurnRightFrame + lastDir;
+
+ walkAnim[_stepCount].frame = module;
+ walkAnim[_stepCount].step = 0;
+ walkAnim[_stepCount].dir = lastDir;
+ walkAnim[_stepCount].x = _moduleX;
+ walkAnim[_stepCount].y = _moduleY;
+ _stepCount++;
+ }
+
+ // rotate till were facing new dir then go back 45 degrees
+ while (lastDir != _currentDir) {
+ lastDir += turnDir;
+
+ // new frames for turn frames 29oct95jps
+ if (turnDir < 0) {
+ if ( lastDir < 0)
+ lastDir += NO_DIRECTIONS;
+ module = _firstStandingTurnLeftFrame + lastDir;
+ } else {
+ if ( lastDir > 7)
+ lastDir -= NO_DIRECTIONS;
+ module = _firstStandingTurnRightFrame + lastDir;
+ }
+
+ walkAnim[_stepCount].frame = module;
+ walkAnim[_stepCount].step = 0;
+ walkAnim[_stepCount].dir = lastDir;
+ walkAnim[_stepCount].x = _moduleX;
+ walkAnim[_stepCount].y = _moduleY;
+ _stepCount++;
+ }
+
+ // the back 45 degrees bit
+ // step back one because new head turn for george takes us
+ // past the new dir
+ _stepCount--;
+ }
+
+ // his head is in the right direction
+ lastRealDir = _currentDir;
+
+ // SLIDY: THE SLOW IN
+
+ addSlowInFrames(walkAnim);
+
+ // THE WALK
+
+ debug(5, "SLIDY: THE WALK");
+
+ // start the walk on the left or right leg, depending on how the
+ // slow-in frames were drawn
+
+ // (0 = left; 1 = right)
+
+ if (_walkData.leadingLeg[_currentDir] == 0) {
+ // start the walk on the left leg (ie. at beginning of the
+ // first step of the walk cycle)
+ left = 0;
+ } else {
+ // start the walk on the right leg (ie. at beginning of the
+ // second step of the walk cycle)
+ left = _framesPerStep;
+ }
+
+ _lastCount = _stepCount;
+
+ // this ensures that we don't put in turn frames for the start
+ lastDir = 99;
+
+ // this ensures that we don't put in turn frames for the start
+ _currentDir = 99;
+
+ do {
+ assert(_stepCount < O_WALKANIM_SIZE);
+ while (_modularPath[p].num == 0) {
+ p++;
+ if (_currentDir != 99)
+ lastRealDir = _currentDir;
+ lastDir = _currentDir;
+ _lastCount = _stepCount;
+ }
+
+ // calculate average amount to lose in each step on the way
+ // to the next node
+
+ _currentDir = _modularPath[p].dir;
+
+ if (_currentDir < NO_DIRECTIONS) {
+ module = _currentDir * _framesPerStep * 2 + left;
+
+ if (left == 0)
+ left = _framesPerStep;
+ else
+ left = 0;
+
+ moduleEnd = module + _framesPerStep;
+ step = 0;
+ scale = (_scaleA * _moduleY + _scaleB);
+
+ do {
+ module16X += _walkData.dx[module] * scale;
+ module16Y += _walkData.dy[module] * scale;
+ _moduleX = module16X >> 16;
+ _moduleY = module16Y >> 16;
+ walkAnim[_stepCount].frame = module;
+ walkAnim[_stepCount].step = step; // normally 0,1,2,3,4,5,0,1,2,etc
+ walkAnim[_stepCount].dir = _currentDir;
+ walkAnim[_stepCount].x = _moduleX;
+ walkAnim[_stepCount].y = _moduleY;
+ _stepCount++;
+ step++;
+ module++;
+ } while (module < moduleEnd);
+
+ stepX = _modX[_modularPath[p].dir];
+ stepY = _modY[_modularPath[p].dir];
+ errorX = _modularPath[p].x - _moduleX;
+ errorX = errorX * stepX;
+ errorY = _modularPath[p].y - _moduleY;
+ errorY = errorY * stepY;
+
+ if (errorX < 0 || errorY < 0) {
+ _modularPath[p].num = 0; // the end of the path
+
+ // okay those last steps took us past our
+ // target but do we want to scoot or moonwalk
+
+ frames = _stepCount - _lastCount;
+ errorX = _modularPath[p].x - walkAnim[_stepCount - 1].x;
+ errorY = _modularPath[p].y - walkAnim[_stepCount - 1].y;
+
+ if (frames > _framesPerStep) {
+ lastErrorX = _modularPath[p].x - walkAnim[_stepCount - 7].x;
+ lastErrorY = _modularPath[p].y - walkAnim[_stepCount - 7].y;
+
+ if (stepX == 0) {
+ if (3 * ABS(lastErrorY) < ABS(errorY)) {
+ // the last stop was
+ // closest
+ _stepCount -= _framesPerStep;
+ if (left == 0)
+ left = _framesPerStep;
+ else
+ left = 0;
+ }
+ } else {
+ if (3 * ABS(lastErrorX) < ABS(errorX)) {
+ //the last stop was
+ // closest
+ _stepCount -= _framesPerStep;
+ if (left == 0)
+ left = _framesPerStep;
+ else
+ left = 0;
+ }
+ }
+ }
+
+ errorX = _modularPath[p].x - walkAnim[_stepCount-1].x;
+ errorY = _modularPath[p].y - walkAnim[_stepCount-1].y;
+
+ // okay we've reached the end but we still
+ // have an error
+
+ if (errorX != 0) {
+ frameCount = 0;
+ frames = _stepCount - _lastCount;
+
+ do {
+ frameCount++;
+ walkAnim[_lastCount + frameCount - 1].x += errorX * frameCount / frames;
+ } while (frameCount < frames);
+ }
+
+ if (errorY != 0) {
+ frameCount = 0;
+ frames = _stepCount - _lastCount;
+ do {
+ frameCount++;
+ walkAnim[_lastCount + frameCount - 1].y += errorY * frameCount / frames;
+ } while (frameCount < frames);
+ }
+
+ // Now is the time to put in the turn frames
+ // for the last turn
+
+ if (frames < _framesPerStep) {
+ // this ensures that we don't put in
+ // turn frames for this walk or the
+ // next
+ _currentDir = 99;
+ }
+
+ if (_currentDir != 99)
+ lastRealDir = _currentDir;
+
+ // check each turn condition in turn
+
+ // only for george
+ if (lastDir != 99 && _currentDir != 99 && _walkData.usingWalkingTurnFrames) {
+ // 1 and -7 going right -1 and 7 going
+ // left
+ lastDir = _currentDir - lastDir;
+
+ if (lastDir == -1 || lastDir == 7 || lastDir == -2 || lastDir == 6) {
+ // turn at the end of the last
+ // walk
+
+ _frame = _lastCount - _framesPerStep;
+ do {
+ // turning left
+ walkAnim[_frame].frame += _firstWalkingTurnLeftFrame;
+ _frame++;
+ } while (_frame < _lastCount);
+ } else if (lastDir == 1 || lastDir == -7 || lastDir == 2 || lastDir == -6) {
+ // turn at the end of the
+ // current walk
+
+ _frame = _lastCount - _framesPerStep;
+ do {
+ // turning right
+ walkAnim[_frame].frame += _firstWalkingTurnRightFrame;
+ _frame++;
+ } while (_frame < _lastCount);
+ }
+ lastDir = _currentDir;
+ }
+
+ // all turns checked
+
+ _lastCount = _stepCount;
+ _moduleX = walkAnim[_stepCount - 1].x;
+ _moduleY = walkAnim[_stepCount - 1].y;
+ module16X = _moduleX << 16;
+ module16Y = _moduleY << 16;
+ }
+ }
+ } while (_modularPath[p].dir < NO_DIRECTIONS);
+
+#ifdef SWORD2_DEBUG
+ if (lastRealDir == 99)
+ error("slidyWalkAnimatorlast direction error");
+#endif
+
+ // THE SLOW OUT
+ addSlowOutFrames(walkAnim);
+
+ // TURNS TO END THE WALK ?
+
+ // We've done the walk now put in any turns at the end
+
+ if (_targetDir == 8) {
+ // ANY direction -> stand in the last direction
+
+ module = _firstStandFrame + lastRealDir;
+ _targetDir = lastRealDir;
+ walkAnim[_stepCount].frame = module;
+ walkAnim[_stepCount].step = 0;
+ walkAnim[_stepCount].dir = lastRealDir;
+ walkAnim[_stepCount].x = _moduleX;
+ walkAnim[_stepCount].y = _moduleY;
+ _stepCount++;
+ }
+
+ if (_targetDir == 9) {
+ // 'stance' was non-zero
+ if (_stepCount == 0) {
+ module = _framesPerChar + lastRealDir;
+ walkAnim[_stepCount].frame = module;
+ walkAnim[_stepCount].step = 0;
+ walkAnim[_stepCount].dir = lastRealDir;
+ walkAnim[_stepCount].x = _moduleX;
+ walkAnim[_stepCount].y = _moduleY;
+ _stepCount++;
+ }
+ } else if (_targetDir != lastRealDir) {
+ // rotate to target direction
+ turnDir = _targetDir - lastRealDir;
+ if ( turnDir < 0)
+ turnDir += NO_DIRECTIONS;
+
+ if (turnDir > 4)
+ turnDir = -1;
+ else if (turnDir > 0)
+ turnDir = 1;
+
+ // rotate to target direction
+ // for george and nico put in a head turn at the start
+
+ if (_walkData.usingStandingTurnFrames) {
+ // new frames for turn frames 29oct95jps
+ if (turnDir < 0)
+ module = _firstStandingTurnLeftFrame + lastDir;
+ else
+ module = _firstStandingTurnRightFrame + lastDir;
+
+ walkAnim[_stepCount].frame = module;
+ walkAnim[_stepCount].step = 0;
+ walkAnim[_stepCount].dir = lastRealDir;
+ walkAnim[_stepCount].x = _moduleX;
+ walkAnim[_stepCount].y = _moduleY;
+ _stepCount++;
+ }
+
+ // rotate if we need to
+
+ while (lastRealDir != _targetDir) {
+ lastRealDir += turnDir;
+
+ // new frames for turn frames 29oct95jps
+ if (turnDir < 0) {
+ if (lastRealDir < 0)
+ lastRealDir += NO_DIRECTIONS;
+ module = _firstStandingTurnLeftFrame + lastRealDir;
+ } else {
+ if (lastRealDir > 7)
+ lastRealDir -= NO_DIRECTIONS;
+ module = _firstStandingTurnRightFrame + lastRealDir;
+ }
+
+ walkAnim[_stepCount].frame = module;
+ walkAnim[_stepCount].step = 0;
+ walkAnim[_stepCount].dir = lastRealDir;
+ walkAnim[_stepCount].x = _moduleX;
+ walkAnim[_stepCount].y = _moduleY;
+ _stepCount++;
+ }
+
+ module = _firstStandFrame + lastRealDir;
+ walkAnim[_stepCount - 1].frame = module;
+ } else {
+ // just stand at the end
+ module = _firstStandFrame + lastRealDir;
+ walkAnim[_stepCount].frame = module;
+ walkAnim[_stepCount].step = 0;
+ walkAnim[_stepCount].dir = lastRealDir;
+ walkAnim[_stepCount].x = _moduleX;
+ walkAnim[_stepCount].y = _moduleY;
+ _stepCount++;
+ }
+
+ walkAnim[_stepCount].frame = 512;
+ walkAnim[_stepCount].step = 99;
+ _stepCount++;
+
+ walkAnim[_stepCount].frame = 512;
+ walkAnim[_stepCount].step = 99;
+ _stepCount++;
+
+ walkAnim[_stepCount].frame = 512;
+ walkAnim[_stepCount].step = 99;
+
+ // write all the frames to "debug.txt"
+ debug(5, "THE WALKDATA:");
+
+ for (_frame = 0; _frame <= _stepCount; _frame++)
+ debug(5, "walkAnim[%d].frame=%d", _frame, walkAnim[_frame].frame);
+
+ debug(5, "routeFinder RouteSize is %d", _stepCount);
+ return;
+}
+
+#ifndef FORCE_SLIDY
+
+// THE SOLID PATH ROUTINES
+
+int32 Router::solidPath() {
+ /*********************************************************************
+ * SolidPath creates a path based on whole steps with no sliding to
+ * get as near as possible to the target without any sliding this
+ * routine is currently unused, but is intended for use when just
+ * clicking about.
+ *
+ * produce a module list from the line data
+ *********************************************************************/
+
+ int32 smooth;
+ int32 solid;
+ int32 scale;
+ int32 stepX;
+ int32 stepY;
+ int32 deltaX;
+ int32 deltaY;
+
+ // strip out the short sections
+
+ solid = 1;
+ smooth = 1;
+ _modularPath[0].x = _smoothPath[0].x;
+ _modularPath[0].y = _smoothPath[0].y;
+ _modularPath[0].dir = _smoothPath[0].dir;
+ _modularPath[0].num = 0;
+
+ do {
+ scale = _scaleA * _smoothPath[smooth].y + _scaleB;
+ deltaX = _smoothPath[smooth].x - _modularPath[solid - 1].x;
+ deltaY = _smoothPath[smooth].y - _modularPath[solid - 1].y;
+ stepX = _modX[_smoothPath[smooth].dir];
+ stepY = _modY[_smoothPath[smooth].dir];
+ stepX = stepX * scale;
+ stepY = stepY * scale;
+ stepX = stepX >> 16;
+ stepY = stepY >> 16;
+
+ if (ABS(deltaX) >= ABS(stepX) && ABS(deltaY) >= ABS(stepY)) {
+ _modularPath[solid].x = _smoothPath[smooth].x;
+ _modularPath[solid].y = _smoothPath[smooth].y;
+ _modularPath[solid].dir = _smoothPath[smooth].dir;
+ _modularPath[solid].num = 1;
+ solid++;
+ }
+
+ smooth++;
+ } while (_smoothPath[smooth].num < ROUTE_END_FLAG);
+
+ // in case the last bit had no steps
+
+ if (solid == 1) {
+ // there were no paths so put in a dummy end
+ solid = 2;
+ _modularPath[1].dir = _smoothPath[0].dir;
+ _modularPath[1].num = 0;
+ }
+
+ _modularPath[solid - 1].x = _smoothPath[smooth - 1].x;
+ _modularPath[solid - 1].y = _smoothPath[smooth - 1].y;
+
+ // set up the end of the walk
+ _modularPath[solid].x = _smoothPath[smooth - 1].x;
+ _modularPath[solid].y = _smoothPath[smooth - 1].y;
+ _modularPath[solid].dir = 9;
+ _modularPath[solid].num = ROUTE_END_FLAG;
+
+ return 1;
+}
+
+int32 Router::solidWalkAnimator(WalkData *walkAnim) {
+ /*********************************************************************
+ * SolidWalk creates an animation based on whole steps with no sliding
+ * to get as near as possible to the target without any sliding. This
+ * routine is is intended for use when just clicking about.
+ *
+ * produce a module list from the line data
+ *
+ * returns 0 if solid route not found
+ *********************************************************************/
+
+ int32 left;
+ int32 turnDir;
+ int32 scale;
+ int32 step;
+ int32 errorX;
+ int32 errorY;
+ int32 moduleEnd;
+ bool slowStart = false;
+
+ // start at the beginning for a change
+
+ int32 lastDir = _modularPath[0].dir;
+ int32 module = _framesPerChar + lastDir;
+
+ _currentDir = _modularPath[1].dir;
+ _moduleX = _startX;
+ _moduleY = _startY;
+ _stepCount = 0;
+
+ int32 module16X = _moduleX << 16;
+ int32 module16Y = _moduleY << 16;
+
+ // START THE WALK WITH THE FIRST STANDFRAME THIS MAY CAUSE A DELAY
+ // BUT IT STOPS THE PLAYER MOVING FOR COLLISIONS ARE DETECTED
+
+ debug(5, "SOLID: STARTING THE WALK");
+ walkAnim[_stepCount].frame = module;
+ walkAnim[_stepCount].step = 0;
+ walkAnim[_stepCount].dir = lastDir;
+ walkAnim[_stepCount].x = _moduleX;
+ walkAnim[_stepCount].y = _moduleY;
+ _stepCount++;
+
+ // TURN TO START THE WALK
+
+ debug(5, "SOLID: TURNING TO START THE WALK");
+
+ // rotate if we need to
+
+ if (lastDir != _currentDir) {
+ // get the direction to turn
+ turnDir = _currentDir - lastDir;
+ if (turnDir < 0)
+ turnDir += NO_DIRECTIONS;
+
+ if (turnDir > 4)
+ turnDir = -1;
+ else if (turnDir > 0)
+ turnDir = 1;
+
+ // rotate to new walk direction
+ // for george and nico put in a head turn at the start
+
+ if (_walkData.usingStandingTurnFrames) {
+ // new frames for turn frames 29oct95jps
+ if (turnDir < 0)
+ module = _firstStandingTurnLeftFrame + lastDir;
+ else
+ module = _firstStandingTurnRightFrame + lastDir;
+
+ walkAnim[_stepCount].frame = module;
+ walkAnim[_stepCount].step = 0;
+ walkAnim[_stepCount].dir = lastDir;
+ walkAnim[_stepCount].x = _moduleX;
+ walkAnim[_stepCount].y = _moduleY;
+ _stepCount++;
+ }
+
+ // rotate till were facing new dir then go back 45 degrees
+
+ while (lastDir != _currentDir) {
+ lastDir += turnDir;
+
+ // new frames for turn frames
+ if (turnDir < 0) {
+ if (lastDir < 0)
+ lastDir += NO_DIRECTIONS;
+ module = _firstStandingTurnLeftFrame + lastDir;
+ } else {
+ if (lastDir > 7)
+ lastDir -= NO_DIRECTIONS;
+ module = _firstStandingTurnRightFrame + lastDir;
+ }
+
+ walkAnim[_stepCount].frame = module;
+ walkAnim[_stepCount].step = 0;
+ walkAnim[_stepCount].dir = lastDir;
+ walkAnim[_stepCount].x = _moduleX;
+ walkAnim[_stepCount].y = _moduleY;
+ _stepCount++;
+ }
+
+ // the back 45 degrees bit
+ // step back one because new head turn for george takes us
+ // past the new dir
+
+ _stepCount--;
+ }
+
+ // THE SLOW IN
+
+ slowStart = addSlowInFrames(walkAnim);
+
+ // THE WALK
+
+ debug(5, "SOLID: THE WALK");
+
+ // start the walk on the left or right leg, depending on how the
+ // slow-in frames were drawn
+
+ // (0 = left; 1 = right)
+ if (_walkData.leadingLeg[_currentDir] == 0) {
+ // start the walk on the left leg (ie. at beginning of the
+ // first step of the walk cycle)
+ left = 0;
+ } else {
+ // start the walk on the right leg (ie. at beginning of the
+ // second step of the walk cycle)
+ left = _framesPerStep;
+ }
+
+ _lastCount = _stepCount;
+
+ // this ensures that we don't put in turn frames for the start
+ lastDir = 99;
+
+ // this ensures that we don't put in turn frames for the start
+ _currentDir = 99;
+
+ int32 p = 1;
+
+ do {
+ while (_modularPath[p].num > 0) {
+ _currentDir = _modularPath[p].dir;
+ if (_currentDir < NO_DIRECTIONS) {
+ module = _currentDir * _framesPerStep * 2 + left;
+
+ if (left == 0)
+ left = _framesPerStep;
+ else
+ left = 0;
+
+ moduleEnd = module + _framesPerStep;
+ step = 0;
+ scale = (_scaleA * _moduleY + _scaleB);
+
+ do {
+ module16X += _walkData.dx[module] * scale;
+ module16Y += _walkData.dy[module] * scale;
+ _moduleX = module16X >> 16;
+ _moduleY = module16Y >> 16;
+ walkAnim[_stepCount].frame = module;
+ walkAnim[_stepCount].step = step; // normally 0,1,2,3,4,5,0,1,2,etc
+ walkAnim[_stepCount].dir = _currentDir;
+ walkAnim[_stepCount].x = _moduleX;
+ walkAnim[_stepCount].y = _moduleY;
+ _stepCount++;
+ module++;
+ step++;
+ } while (module < moduleEnd);
+
+ errorX = _modularPath[p].x - _moduleX;
+ errorX = errorX * _modX[_modularPath[p].dir];
+ errorY = _modularPath[p].y - _moduleY;
+ errorY = errorY * _modY[_modularPath[p].dir];
+
+ if (errorX < 0 || errorY < 0) {
+ _modularPath[p].num = 0;
+ _stepCount -= _framesPerStep;
+
+ if (left == 0)
+ left = _framesPerStep;
+ else
+ left = 0;
+
+ // Okay this is the end of a section
+
+ _moduleX = walkAnim[_stepCount - 1].x;
+ _moduleY = walkAnim[_stepCount - 1].y;
+ module16X = _moduleX << 16;
+ module16Y = _moduleY << 16;
+ _modularPath[p].x = _moduleX;
+ _modularPath[p].y = _moduleY;
+
+ // Now is the time to put in the turn
+ // frames for the last turn
+
+ if (_stepCount - _lastCount < _framesPerStep) {
+ // no step taken
+
+ // clean up if a slow in but no
+ // walk
+
+ if (slowStart) {
+ _stepCount -= _walkData.nSlowInFrames[_currentDir];
+ _lastCount -= _walkData.nSlowInFrames[_currentDir];
+ slowStart = false;
+ }
+
+ // this ensures that we don't
+ // put in turn frames for this
+ // walk or the next
+
+ _currentDir = 99;
+ }
+
+ // check each turn condition in turn
+ if (lastDir != 99 && _currentDir != 99 && _walkData.usingWalkingTurnFrames) {
+ // only for george
+ // 1 and -7 going right -1 and
+ // 7 going left
+
+ lastDir = _currentDir - lastDir;
+
+ if (lastDir == -1 || lastDir == 7 || lastDir == -2 || lastDir == 6) {
+ // turn at the end of
+ // the last walk
+
+ _frame = _lastCount - _framesPerStep;
+
+ do {
+ // turning left
+ walkAnim[_frame].frame += _firstWalkingTurnLeftFrame;
+ _frame++;
+ } while (_frame < _lastCount);
+ } else if (lastDir == 1 || lastDir == -7 || lastDir == 2 || lastDir == -6) {
+ // turn at the end of
+ // the current walk
+
+ _frame = _lastCount - _framesPerStep;
+ do {
+ // turning right
+ walkAnim[_frame].frame += _firstWalkingTurnRightFrame;
+ _frame++;
+ } while (_frame < _lastCount);
+ }
+ }
+
+ // all turns checked
+ _lastCount = _stepCount;
+ }
+ }
+ }
+ p++;
+ lastDir = _currentDir;
+
+ // can only be valid first time round
+ slowStart = false;
+ } while (_modularPath[p].dir < NO_DIRECTIONS);
+
+ // THE SLOW OUT
+
+ addSlowOutFrames(walkAnim);
+
+ module = _framesPerChar + _modularPath[p - 1].dir;
+ walkAnim[_stepCount].frame = module;
+ walkAnim[_stepCount].step = 0;
+ walkAnim[_stepCount].dir = _modularPath[p - 1].dir;
+ walkAnim[_stepCount].x = _moduleX;
+ walkAnim[_stepCount].y = _moduleY;
+ _stepCount++;
+
+ walkAnim[_stepCount].frame = 512;
+ walkAnim[_stepCount].step = 99;
+ _stepCount++;
+
+ walkAnim[_stepCount].frame = 512;
+ walkAnim[_stepCount].step = 99;
+ _stepCount++;
+
+ walkAnim[_stepCount].frame = 512;
+ walkAnim[_stepCount].step = 99;
+
+ debug(5, "THE WALKDATA:");
+
+ for (_frame = 0; _frame <= _stepCount; _frame++)
+ debug(5, "walkAnim[%d].frame=%d", _frame, walkAnim[_frame].frame);
+
+ // NO END TURNS
+
+ debug(5, "routeFinder RouteSize is %d", _stepCount);
+ // now check the route
+
+ int i = 0;
+
+ do {
+ if (!check(_modularPath[i].x, _modularPath[i].y, _modularPath[i + 1].x, _modularPath[i + 1].y))
+ p = 0;
+ i++;
+ } while (i < p - 1);
+
+ if (p != 0) {
+ _targetDir = _modularPath[p - 1].dir;
+ if (checkTarget(_moduleX, _moduleY) == 3) {
+ // new target on a line
+ p = 0;
+ debug(5, "Solid walk target was on a line %d %d", _moduleX, _moduleY);
+ }
+ }
+
+ return p;
+}
+#endif
+
+// THE SCAN ROUTINES
+
+bool Router::scan(int32 level) {
+ /*********************************************************************
+ * Called successively from routeFinder until no more changes take
+ * place in the grid array, ie he best path has been found
+ *
+ * Scans through every point in the node array and checks if there is
+ * a route between each point and if this route gives a new route.
+ *
+ * This routine could probably halve its processing time if it doubled
+ * up on the checks after each route check
+ *
+ *********************************************************************/
+
+ int32 x1, y1, x2, y2;
+ int32 distance;
+ bool changed = false;
+
+ // For all the nodes that have new values and a distance less than
+ // enddist, ie dont check for new routes from a point we checked
+ // before or from a point that is already further away than the best
+ // route so far.
+
+ for (int i = 0; i < _nNodes; i++) {
+ if (_node[i].dist < _node[_nNodes].dist && _node[i].level == level) {
+ x1 = _node[i].x;
+ y1 = _node[i].y;
+
+ for (int j = _nNodes; j > 0; j--) {
+ if (_node[j].dist > _node[i].dist) {
+ x2 = _node[j].x;
+ y2 = _node[j].y;
+
+ if (ABS(x2 - x1) > 4.5 * ABS(y2 - y1))
+ distance = (8 * ABS(x2 - x1) + 18 * ABS(y2 - y1)) / (54 * 8) + 1;
+ else
+ distance = (6 * ABS(x2 - x1) + 36 * ABS(y2 - y1)) / (36 * 14) + 1;
+
+ if (distance + _node[i].dist < _node[_nNodes].dist && distance + _node[i].dist < _node[j].dist) {
+ if (newCheck(0, x1, y1, x2, y2)) {
+ _node[j].level = level + 1;
+ _node[j].dist = distance + _node[i].dist;
+ _node[j].prev = i;
+ changed = true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return changed;
+}
+
+int32 Router::newCheck(int32 status, int32 x1, int32 y1, int32 x2, int32 y2) {
+ /*********************************************************************
+ * newCheck routine checks if the route between two points can be
+ * achieved without crossing any of the bars in the Bars array.
+ *
+ * newCheck differs from check in that that 4 route options are
+ * considered corresponding to actual walked routes.
+ *
+ * Note distance doesnt take account of shrinking ???
+ *
+ * Note Bars array must be properly calculated ie min max dx dy co
+ *********************************************************************/
+
+ int32 ldx;
+ int32 ldy;
+ int32 dlx;
+ int32 dly;
+ int32 dirX;
+ int32 dirY;
+ int32 step1;
+ int32 step2;
+ int32 step3;
+ int32 steps;
+ int32 options;
+
+ steps = 0;
+ options = 0;
+ ldx = x2 - x1;
+ ldy = y2 - y1;
+ dirX = 1;
+ dirY = 1;
+
+ if (ldx < 0) {
+ ldx = -ldx;
+ dirX = -1;
+ }
+
+ if (ldy < 0) {
+ ldy = -ldy;
+ dirY = -1;
+ }
+
+ // make the route options
+
+ if (_diagonaly * ldx > _diagonalx * ldy) {
+ // dir = 1,2 or 2,3 or 5,6 or 6,7
+
+ dly = ldy;
+ dlx = (ldy * _diagonalx) / _diagonaly;
+ ldx = ldx - dlx;
+ dlx = dlx * dirX;
+ dly = dly * dirY;
+ ldx = ldx * dirX;
+ ldy = 0;
+
+ // options are square, diagonal a code 1 route
+
+ step1 = check(x1, y1, x1 + ldx, y1);
+ if (step1 != 0) {
+ step2 = check(x1 + ldx, y1, x2, y2);
+ if (step2 != 0) {
+ steps = step1 + step2;
+ options |= 2;
+ }
+ }
+
+ // diagonal, square a code 2 route
+
+ if (steps == 0 || status == 1) {
+ step1 = check(x1, y1, x1 + dlx, y1 + dly);
+ if (step1 != 0) {
+ step2 = check(x1 + dlx, y2, x2, y2);
+ if (step2 != 0) {
+ steps = step1 + step2;
+ options |= 4;
+ }
+ }
+ }
+
+ // halfsquare, diagonal, halfsquare a code 0 route
+
+ if (steps == 0 || status == 1) {
+ step1 = check(x1, y1, x1 + ldx / 2, y1);
+ if (step1 != 0) {
+ step2 = check(x1 + ldx / 2, y1, x1 + ldx / 2 + dlx, y2);
+ if (step2 != 0) {
+ step3 = check(x1 + ldx / 2 + dlx, y2, x2, y2);
+ if (step3 != 0) {
+ steps = step1 + step2 + step3;
+ options |= 1;
+ }
+ }
+ }
+ }
+
+ //halfdiagonal, square, halfdiagonal a code 3 route
+
+ if (steps == 0 || status == 1) {
+ step1 = check(x1, y1, x1 + dlx / 2, y1 + dly / 2);
+ if (step1 != 0) {
+ step2 = check(x1 + dlx / 2, y1 + dly / 2, x1 + ldx + dlx / 2, y1 + dly / 2);
+ if (step2 != 0) {
+ step3 = check(x1 + ldx + dlx / 2, y1 + dly / 2, x2, y2);
+ if (step3 != 0) {
+ steps = step1 + step2 + step3;
+ options |= 8;
+ }
+ }
+ }
+ }
+ } else {
+ // dir = 7,0 or 0,1 or 3,4 or 4,5
+
+ dlx = ldx;
+ dly = (ldx * _diagonaly) / _diagonalx;
+ ldy = ldy - dly;
+ dlx = dlx * dirX;
+ dly = dly * dirY;
+ ldy = ldy * dirY;
+ ldx = 0;
+
+ // options are square, diagonal a code 1 route
+
+ step1 = check(x1 ,y1, x1, y1 + ldy);
+ if (step1 != 0) {
+ step2 = check(x1, y1 + ldy, x2, y2);
+ if (step2 != 0) {
+ steps = step1 + step2;
+ options |= 2;
+ }
+ }
+
+ // diagonal, square a code 2 route
+
+ if (steps == 0 || status == 1) {
+ step1 = check(x1, y1, x2, y1 + dly);
+ if (step1 != 0) {
+ step2 = check(x2, y1 + dly, x2, y2);
+ if (step2 != 0) {
+ steps = step1 + step2;
+ options |= 4;
+ }
+ }
+ }
+
+ // halfsquare, diagonal, halfsquare a code 0 route
+
+ if (steps == 0 || status == 1) {
+ step1 = check(x1, y1, x1, y1 + ldy / 2);
+ if (step1 != 0) {
+ step2 = check(x1, y1 + ldy / 2, x2, y1 + ldy / 2 + dly);
+ if (step2 != 0) {
+ step3 = check(x2, y1 + ldy / 2 + dly, x2, y2);
+ if (step3 != 0) {
+ steps = step1 + step2 + step3;
+ options |= 1;
+ }
+ }
+ }
+ }
+
+ // halfdiagonal, square, halfdiagonal a code 3 route
+
+ if (steps == 0 || status == 1) {
+ step1 = check(x1, y1, x1 + dlx / 2, y1 + dly / 2);
+ if (step1 != 0) {
+ step2 = check(x1 + dlx / 2, y1 + dly / 2, x1 + dlx / 2, y1 + ldy + dly / 2);
+ if (step2 != 0) {
+ step3 = check(x1 + dlx / 2, y1 + ldy + dly / 2, x2, y2);
+ if (step3 != 0) {
+ steps = step1 + step2 + step3;
+ options |= 8;
+ }
+ }
+ }
+ }
+ }
+
+ if (status == 0)
+ status = steps;
+ else
+ status = options;
+
+ return status;
+}
+
+// CHECK ROUTINES
+
+bool Router::check(int32 x1, int32 y1, int32 x2, int32 y2) {
+ // call the fastest line check for the given line
+ // returns true if line didn't cross any bars
+
+ if (x1 == x2 && y1 == y2)
+ return true;
+
+ if (x1 == x2)
+ return vertCheck(x1, y1, y2);
+
+ if (y1 == y2)
+ return horizCheck(x1, y1, x2);
+
+ return lineCheck(x1, y1, x2, y2);
+}
+
+bool Router::lineCheck(int32 x1, int32 y1, int32 x2, int32 y2) {
+ bool linesCrossed = true;
+
+ int32 xmin = MIN(x1, x2);
+ int32 xmax = MAX(x1, x2);
+ int32 ymin = MIN(y1, y2);
+ int32 ymax = MAX(y1, y2);
+
+ // Line set to go one step in chosen direction so ignore if it hits
+ // anything
+
+ int32 dirx = x2 - x1;
+ int32 diry = y2 - y1;
+
+ int32 co = (y1 * dirx) - (x1 * diry); // new line equation
+
+ for (int i = 0; i < _nBars && linesCrossed; i++) {
+ // skip if not on module
+ if (xmax >= _bars[i].xmin && xmin <= _bars[i].xmax && ymax >= _bars[i].ymin && ymin <= _bars[i].ymax) {
+ // Okay, it's a valid line. Calculate an intercept. Wow
+ // but all this arithmetic we must have loads of time
+
+ // slope it he slope between the two lines
+ int32 slope = (_bars[i].dx * diry) - (_bars[i].dy *dirx);
+ // assuming parallel lines don't cross
+ if (slope != 0) {
+ // calculate x intercept and check its on both
+ // lines
+ int32 xc = ((_bars[i].co * dirx) - (co * _bars[i].dx)) / slope;
+
+ // skip if not on module
+ if (xc >= xmin - 1 && xc <= xmax + 1) {
+ // skip if not on line
+ if (xc >= _bars[i].xmin - 1 && xc <= _bars[i].xmax + 1) {
+ int32 yc = ((_bars[i].co * diry) - (co * _bars[i].dy)) / slope;
+
+ // skip if not on module
+ if (yc >= ymin - 1 && yc <= ymax + 1) {
+ // skip if not on line
+ if (yc >= _bars[i].ymin - 1 && yc <= _bars[i].ymax + 1) {
+ linesCrossed = false;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return linesCrossed;
+}
+
+bool Router::horizCheck(int32 x1, int32 y, int32 x2) {
+ bool linesCrossed = true;
+
+ int32 xmin = MIN(x1, x2);
+ int32 xmax = MAX(x1, x2);
+
+ // line set to go one step in chosen direction so ignore if it hits
+ // anything
+
+ for (int i = 0; i < _nBars && linesCrossed; i++) {
+ // skip if not on module
+ if (xmax >= _bars[i].xmin && xmin <= _bars[i].xmax && y >= _bars[i].ymin && y <= _bars[i].ymax) {
+ // Okay, it's a valid line calculate an intercept. Wow
+ // but all this arithmetic we must have loads of time
+
+ if (_bars[i].dy == 0)
+ linesCrossed = false;
+ else {
+ int32 ldy = y - _bars[i].y1;
+ int32 xc = _bars[i].x1 + (_bars[i].dx * ldy) / _bars[i].dy;
+ // skip if not on module
+ if (xc >= xmin - 1 && xc <= xmax + 1)
+ linesCrossed = false;
+ }
+ }
+ }
+
+ return linesCrossed;
+}
+
+bool Router::vertCheck(int32 x, int32 y1, int32 y2) {
+ bool linesCrossed = true;
+
+ int32 ymin = MIN(y1, y2);
+ int32 ymax = MAX(y1, y2);
+
+ // Line set to go one step in chosen direction so ignore if it hits
+ // anything
+
+ for (int i = 0; i < _nBars && linesCrossed; i++) {
+ // skip if not on module
+ if (x >= _bars[i].xmin && x <= _bars[i].xmax && ymax >= _bars[i].ymin && ymin <= _bars[i].ymax) {
+ // Okay, it's a valid line calculate an intercept. Wow
+ // but all this arithmetic we must have loads of time
+
+ // both lines vertical and overlap in x and y so they
+ // cross
+
+ if (_bars[i].dx == 0)
+ linesCrossed = false;
+ else {
+ int32 ldx = x - _bars[i].x1;
+ int32 yc = _bars[i].y1 + (_bars[i].dy * ldx) / _bars[i].dx;
+ // the intercept overlaps
+ if (yc >= ymin - 1 && yc <= ymax + 1)
+ linesCrossed = false;
+ }
+ }
+ }
+
+ return linesCrossed;
+}
+
+int32 Router::checkTarget(int32 x, int32 y) {
+ int32 onLine = 0;
+
+ int32 xmin = x - 1;
+ int32 xmax = x + 1;
+ int32 ymin = y - 1;
+ int32 ymax = y + 1;
+
+ // check if point +- 1 is on the line
+ // so ignore if it hits anything
+
+ for (int i = 0; i < _nBars && onLine == 0; i++) {
+ // overlapping line
+ if (xmax >= _bars[i].xmin && xmin <= _bars[i].xmax && ymax >= _bars[i].ymin && ymin <= _bars[i].ymax) {
+ int32 xc, yc;
+
+ // okay this line overlaps the target calculate
+ // an y intercept for x
+
+ // vertical line so we know it overlaps y
+ if (_bars[i].dx == 0)
+ yc = 0;
+ else {
+ int ldx = x - _bars[i].x1;
+ yc = _bars[i].y1 + (_bars[i].dy * ldx) / _bars[i].dx;
+ }
+
+ // overlapping point for y
+ if (yc >= ymin && yc <= ymax) {
+ // target on a line so drop out
+ onLine = 3;
+ debug(5, "RouteFail due to target on a line %d %d", x, y);
+ } else {
+ // vertical line so we know it overlaps y
+ if (_bars[i].dy == 0)
+ xc = 0;
+ else {
+ int32 ldy = y - _bars[i].y1;
+ xc = _bars[i].x1 + (_bars[i].dx * ldy) / _bars[i].dy;
+ }
+
+ // skip if not on module
+ if (xc >= xmin && xc <= xmax) {
+ // target on a line so drop out
+ onLine = 3;
+ debug(5, "RouteFail due to target on a line %d %d", x, y);
+ }
+ }
+ }
+ }
+
+ return onLine;
+}
+
+// THE SETUP ROUTINES
+
+void Router::loadWalkData(byte *ob_walkdata) {
+ uint16 firstFrameOfDirection;
+ uint16 walkFrameNo;
+ uint32 frameCounter = 0; // starts at frame 0 of mega set
+ int i;
+
+ _walkData.read(ob_walkdata);
+
+ // 0 = not using slow out frames; non-zero = using that many frames
+ // for each leading leg for each direction
+
+ _numberOfSlowOutFrames = _walkData.usingSlowOutFrames;
+
+ for (i = 0; i < NO_DIRECTIONS; i++) {
+ firstFrameOfDirection = i * _walkData.nWalkFrames;
+
+ _modX[i] = 0;
+ _modY[i] = 0;
+
+ for (walkFrameNo = firstFrameOfDirection; walkFrameNo < firstFrameOfDirection + _walkData.nWalkFrames / 2; walkFrameNo++) {
+ // eg. _modX[0] is the sum of the x-step sizes for the
+ // first half of the walk cycle for direction 0
+ _modX[i] += _walkData.dx[walkFrameNo];
+ _modY[i] += _walkData.dy[walkFrameNo];
+ }
+ }
+
+ _diagonalx = _modX[3];
+ _diagonaly = _modY[3];
+
+ // interpret the walk data
+
+ _framesPerStep = _walkData.nWalkFrames / 2;
+ _framesPerChar = _walkData.nWalkFrames * NO_DIRECTIONS;
+
+ // offset pointers added Oct 30 95 JPS
+ // mega id references removed 16sep96 by JEL
+
+ // WALK FRAMES
+ // start on frame 0
+
+ frameCounter += _framesPerChar;
+
+ // STAND FRAMES
+ // stand frames come after the walk frames
+ // one stand frame for each direction
+
+ _firstStandFrame = frameCounter;
+ frameCounter += NO_DIRECTIONS;
+
+ // STANDING TURN FRAMES - OPTIONAL!
+ // standing turn-left frames come after the slow-out frames
+ // one for each direction
+ // standing turn-left frames come after the standing turn-right frames
+ // one for each direction
+
+ if (_walkData.usingStandingTurnFrames) {
+ _firstStandingTurnLeftFrame = frameCounter;
+ frameCounter += NO_DIRECTIONS;
+
+ _firstStandingTurnRightFrame = frameCounter;
+ frameCounter += NO_DIRECTIONS;
+ } else {
+ // refer instead to the normal stand frames
+ _firstStandingTurnLeftFrame = _firstStandFrame;
+ _firstStandingTurnRightFrame = _firstStandFrame;
+ }
+
+ // WALKING TURN FRAMES - OPTIONAL!
+ // walking left-turn frames come after the stand frames
+ // walking right-turn frames come after the walking left-turn frames
+
+ if (_walkData.usingWalkingTurnFrames) {
+ _firstWalkingTurnLeftFrame = frameCounter;
+ frameCounter += _framesPerChar;
+
+ _firstWalkingTurnRightFrame = frameCounter;
+ frameCounter += _framesPerChar;
+ } else {
+ _firstWalkingTurnLeftFrame = 0;
+ _firstWalkingTurnRightFrame = 0;
+ }
+
+ // SLOW-IN FRAMES - OPTIONAL!
+ // slow-in frames come after the walking right-turn frames
+
+ if (_walkData.usingSlowInFrames) {
+ // Make note of frame number of first slow-in frame for each
+ // direction. There may be a different number of slow-in
+ // frames in each direction
+
+ for (i = 0; i < NO_DIRECTIONS; i++) {
+ _firstSlowInFrame[i] = frameCounter;
+ frameCounter += _walkData.nSlowInFrames[i];
+ }
+ }
+
+ // SLOW-OUT FRAMES - OPTIONAL!
+ // slow-out frames come after the slow-in frames
+
+ if (_walkData.usingSlowOutFrames)
+ _firstSlowOutFrame = frameCounter;
+}
+
+// THE ROUTE EXTRACTOR
+
+void Router::extractRoute() {
+ /*********************************************************************
+ * extractRoute gets route from the node data after a full scan, route
+ * is written with just the basic way points and direction options for
+ * heading to the next point.
+ *********************************************************************/
+
+ int32 prev;
+ int32 prevx;
+ int32 prevy;
+ int32 last;
+ int32 point;
+ int32 p;
+ int32 dirx;
+ int32 diry;
+ int32 dir;
+ int32 ldx;
+ int32 ldy;
+
+ // extract the route from the node data
+
+ prev = _nNodes;
+ last = prev;
+ point = O_ROUTE_SIZE - 1;
+ _route[point].x = _node[last].x;
+ _route[point].y = _node[last].y;
+
+ do {
+ point--;
+ prev = _node[last].prev;
+ prevx = _node[prev].x;
+ prevy = _node[prev].y;
+ _route[point].x = prevx;
+ _route[point].y = prevy;
+ last = prev;
+ } while (prev > 0);
+
+ // now shuffle route down in the buffer
+
+ _routeLength = 0;
+
+ do {
+ _route[_routeLength].x = _route[point].x;
+ _route[_routeLength].y = _route[point].y;
+ point++;
+ _routeLength++;
+ } while (point < O_ROUTE_SIZE);
+
+ _routeLength--;
+
+ // okay the route exists as a series point now put in some directions
+
+ p = 0;
+
+ do {
+ ldx = _route[p + 1].x - _route[p].x;
+ ldy = _route[p + 1].y - _route[p].y;
+ dirx = 1;
+ diry = 1;
+
+ if (ldx < 0) {
+ ldx = -ldx;
+ dirx = -1;
+ }
+
+ if (ldy < 0) {
+ ldy = -ldy;
+ diry = -1;
+ }
+
+ if (_diagonaly * ldx > _diagonalx * ldy) {
+ // dir = 1,2 or 2,3 or 5,6 or 6,7
+
+ // 2 or 6
+ dir = 4 - 2 * dirx;
+ _route[p].dirS = dir;
+
+ // 1, 3, 5 or 7
+ dir = dir + diry * dirx;
+ _route[p].dirD = dir;
+ } else {
+ // dir = 7,0 or 0,1 or 3,4 or 4,5
+
+ // 0 or 4
+ dir = 2 + 2 * diry;
+ _route[p].dirS = dir;
+
+ // 2 or 6
+ dir = 4 - 2 * dirx;
+
+ // 1, 3, 5 or 7
+ dir = dir + diry * dirx;
+ _route[p].dirD = dir;
+ }
+ p++;
+ } while (p < _routeLength);
+
+ // set the last dir to continue previous route unless specified
+
+ if (_targetDir == 8) {
+ // ANY direction
+ _route[p].dirS = _route[p - 1].dirS;
+ _route[p].dirD = _route[p - 1].dirD;
+ } else {
+ _route[p].dirS = _targetDir;
+ _route[p].dirD = _targetDir;
+ }
+
+ return;
+}
+
+void Router::setUpWalkGrid(byte *ob_mega, int32 x, int32 y, int32 dir) {
+ ObjectMega obMega(ob_mega);
+
+ // get walk grid file + extra grid into 'bars' & 'node' arrays
+ loadWalkGrid();
+
+ // copy the mega structure into the local variables for use in all
+ // subroutines
+
+ _startX = obMega.getFeetX();
+ _startY = obMega.getFeetY();
+ _startDir = obMega.getCurDir();
+ _targetX = x;
+ _targetY = y;
+ _targetDir = dir;
+
+ _scaleA = obMega.getScaleA();
+ _scaleB = obMega.getScaleB();
+
+ // mega's current position goes into first node
+
+ _node[0].x = _startX;
+ _node[0].y = _startY;
+ _node[0].level = 1;
+ _node[0].prev = 0;
+ _node[0].dist = 0;
+
+ // reset other nodes
+
+ for (int i = 1; i < _nNodes; i++) {
+ _node[i].level = 0;
+ _node[i].prev = 0;
+ _node[i].dist = 9999;
+ }
+
+ // target position goes into final node
+ _node[_nNodes].x = _targetX;
+ _node[_nNodes].y = _targetY;
+ _node[_nNodes].level = 0;
+ _node[_nNodes].prev = 0;
+ _node[_nNodes].dist = 9999;
+}
+
+void Router::plotWalkGrid() {
+ int32 i;
+
+ // get walk grid file + extra grid into 'bars' & 'node' arrays
+ loadWalkGrid();
+
+ // lines
+
+ for (i = 0; i < _nBars; i++)
+ _vm->_screen->drawLine(_bars[i].x1, _bars[i].y1, _bars[i].x2, _bars[i].y2, 254);
+
+ // nodes
+
+ // leave node 0 for start node
+ for (i = 1; i < _nNodes; i++)
+ plotCross(_node[i].x, _node[i].y, 184);
+}
+
+void Router::plotCross(int16 x, int16 y, uint8 colour) {
+ _vm->_screen->drawLine(x - 1, y - 1, x + 1, y + 1, colour);
+ _vm->_screen->drawLine(x + 1, y - 1, x - 1, y + 1, colour);
+}
+
+void Router::loadWalkGrid() {
+ WalkGridHeader floorHeader;
+ byte *fPolygrid;
+ uint16 fPolygridLen;
+
+ _nBars = 0; // reset counts
+ _nNodes = 1; // leave node 0 for start-node
+
+ // STATIC GRIDS (added/removed by object logics)
+
+ // go through walkgrid list
+ for (int i = 0; i < MAX_WALKGRIDS; i++) {
+ if (_walkGridList[i]) {
+ int j;
+
+ // open walk grid file
+ fPolygrid = _vm->_resman->openResource(_walkGridList[i]);
+ fPolygridLen = _vm->_resman->fetchLen(_walkGridList[i]);
+
+ Common::MemoryReadStream readS(fPolygrid, fPolygridLen);
+
+ readS.seek(ResHeader::size());
+
+ floorHeader.numBars = readS.readSint32LE();
+ floorHeader.numNodes = readS.readSint32LE();
+
+ // check that we're not going to exceed the max
+ // allowed in the complete walkgrid arrays
+
+ assert(_nBars + floorHeader.numBars < O_GRID_SIZE);
+ assert(_nNodes + floorHeader.numNodes < O_GRID_SIZE);
+
+ // lines
+
+ for (j = 0; j < floorHeader.numBars; j++) {
+ _bars[_nBars + j].x1 = readS.readSint16LE();
+ _bars[_nBars + j].y1 = readS.readSint16LE();
+ _bars[_nBars + j].x2 = readS.readSint16LE();
+ _bars[_nBars + j].y2 = readS.readSint16LE();
+ _bars[_nBars + j].xmin = readS.readSint16LE();
+ _bars[_nBars + j].ymin = readS.readSint16LE();
+ _bars[_nBars + j].xmax = readS.readSint16LE();
+ _bars[_nBars + j].ymax = readS.readSint16LE();
+ _bars[_nBars + j].dx = readS.readSint16LE();
+ _bars[_nBars + j].dy = readS.readSint16LE();
+ _bars[_nBars + j].co = readS.readSint32LE();
+ }
+
+ // nodes
+
+ // leave node 0 for start node
+ for (j = 0; j < floorHeader.numNodes; j++) {
+ _node[_nNodes + j].x = readS.readSint16LE();
+ _node[_nNodes + j].y = readS.readSint16LE();
+ }
+
+ // close walk grid file
+ _vm->_resman->closeResource(_walkGridList[i]);
+
+ // increment counts of total bars & nodes in whole
+ // walkgrid
+
+ _nBars += floorHeader.numBars;
+ _nNodes += floorHeader.numNodes;
+ }
+ }
+}
+
+void Router::clearWalkGridList() {
+ memset(_walkGridList, 0, sizeof(_walkGridList));
+}
+
+// called from fnAddWalkGrid
+
+void Router::addWalkGrid(int32 gridResource) {
+ int i;
+ // First, scan the list to see if this grid is already included
+
+ for (i = 0; i < MAX_WALKGRIDS; i++) {
+ if (_walkGridList[i] == gridResource)
+ return;
+ }
+
+ // Scan the list for a free slot
+
+ for (i = 0; i < MAX_WALKGRIDS; i++) {
+ if (_walkGridList[i] == 0) {
+ _walkGridList[i] = gridResource;
+ return;
+ }
+ }
+
+ error("_walkGridList[] full");
+}
+
+// called from fnRemoveWalkGrid
+
+void Router::removeWalkGrid(int32 gridResource) {
+ for (int i = 0; i < MAX_WALKGRIDS; i++) {
+ if (_walkGridList[i] == gridResource) {
+ // If we've found it in the list, reset entry to zero.
+ // Otherwise just ignore the request.
+ _walkGridList[i] = 0;
+ break;
+ }
+ }
+}
+
+} // End of namespace Sword2
diff --git a/engines/sword2/router.h b/engines/sword2/router.h
new file mode 100644
index 0000000000..45efd046bd
--- /dev/null
+++ b/engines/sword2/router.h
@@ -0,0 +1,255 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#ifndef _ROUTER_H
+#define _ROUTER_H
+
+// This used to be a variable, but it was never set. Actually, it wasn't even
+// initialised!
+//
+// Define this to force the use of slidy router (so solid path not used when
+// ending walk in ANY direction)
+//
+// #define FORCE_SLIDY
+
+#include "sword2/object.h"
+
+namespace Sword2 {
+
+struct WalkData {
+ uint16 frame;
+ int16 x;
+ int16 y;
+ uint8 step;
+ uint8 dir;
+};
+
+struct BarData {
+ int16 x1;
+ int16 y1;
+ int16 x2;
+ int16 y2;
+ int16 xmin;
+ int16 ymin;
+ int16 xmax;
+ int16 ymax;
+ int16 dx; // x2 - x1
+ int16 dy; // y2 - y1
+ int32 co; // co = (y1 * dx) - (x1 * dy) from an equation for a
+ // line y * dx = x * dy + co
+};
+
+struct NodeData {
+ int16 x;
+ int16 y;
+ int16 level;
+ int16 prev;
+ int16 dist;
+};
+
+// because we only have 2 megas in the game!
+#define TOTAL_ROUTE_SLOTS 2
+
+#define MAX_FRAMES_PER_CYCLE 16
+#define NO_DIRECTIONS 8
+#define MAX_FRAMES_PER_CHAR (MAX_FRAMES_PER_CYCLE * NO_DIRECTIONS)
+#define ROUTE_END_FLAG 255
+
+#define MAX_WALKGRIDS 10
+
+#define O_WALKANIM_SIZE 600 // max number of nodes in router output
+#define O_GRID_SIZE 200 // max 200 lines & 200 points
+#define O_ROUTE_SIZE 50 // max number of modules in a route
+
+struct RouteData {
+ int32 x;
+ int32 y;
+ int32 dirS;
+ int32 dirD;
+};
+
+struct PathData {
+ int32 x;
+ int32 y;
+ int32 dir;
+ int32 num;
+};
+
+class Router {
+private:
+ Sword2Engine *_vm;
+
+ int16 _standbyX; // see fnSetStandbyCoords()
+ int16 _standbyY;
+ int16 _standbyDir;
+
+ // stores pointers to mem blocks containing routes created & used by
+ // megas (NULL if slot not in use)
+ WalkData *_routeSlots[TOTAL_ROUTE_SLOTS];
+
+ BarData _bars[O_GRID_SIZE];
+ NodeData _node[O_GRID_SIZE];
+
+ int32 _walkGridList[MAX_WALKGRIDS];
+
+ int32 _nBars;
+ int32 _nNodes;
+
+ int32 _startX;
+ int32 _startY;
+ int32 _startDir;
+ int32 _targetX;
+ int32 _targetY;
+ int32 _targetDir;
+ int32 _scaleA;
+ int32 _scaleB;
+
+ RouteData _route[O_ROUTE_SIZE];
+ PathData _smoothPath[O_ROUTE_SIZE];
+ PathData _modularPath[O_ROUTE_SIZE];
+ int32 _routeLength;
+
+ int32 _framesPerStep;
+ int32 _framesPerChar;
+
+ ObjectWalkdata _walkData;
+
+ int8 _modX[NO_DIRECTIONS];
+ int8 _modY[NO_DIRECTIONS];
+ int32 _diagonalx;
+ int32 _diagonaly;
+
+ int32 _firstStandFrame;
+
+ int32 _firstStandingTurnLeftFrame;
+ int32 _firstStandingTurnRightFrame;
+
+ int32 _firstWalkingTurnLeftFrame; // left walking turn
+ int32 _firstWalkingTurnRightFrame; // right walking turn
+
+ uint32 _firstSlowInFrame[NO_DIRECTIONS];
+
+ int32 _firstSlowOutFrame;
+
+ // number of slow-out frames on for each leading-leg in each direction
+ // ie. total number of slow-out frames = (numberOfSlowOutFrames * 2 *
+ // NO_DIRECTIONS)
+
+ int32 _numberOfSlowOutFrames;
+
+ int32 _stepCount;
+
+ int32 _moduleX;
+ int32 _moduleY;
+ int32 _currentDir;
+ int32 _lastCount;
+ int32 _frame;
+
+ uint8 returnSlotNo(uint32 megaId);
+
+ int32 getRoute();
+ void extractRoute();
+ void loadWalkGrid();
+ void setUpWalkGrid(byte *ob_mega, int32 x, int32 y, int32 dir);
+ void loadWalkData(byte *ob_walkdata);
+ bool scan(int32 level);
+
+ int32 newCheck(int32 status, int32 x1, int32 y1, int32 x2, int32 y2);
+ bool lineCheck(int32 x1, int32 x2, int32 y1, int32 y2);
+ bool vertCheck(int32 x, int32 y1, int32 y2);
+ bool horizCheck(int32 x1, int32 y, int32 x2);
+ bool check(int32 x1, int32 y1, int32 x2, int32 y2);
+ int32 checkTarget(int32 x, int32 y);
+
+ int32 smoothestPath();
+ void slidyPath();
+
+ int32 smoothCheck(int32 best, int32 p, int32 dirS, int32 dirD);
+
+ bool addSlowInFrames(WalkData *walkAnim);
+ void addSlowOutFrames(WalkData *walkAnim);
+ void slidyWalkAnimator(WalkData *walkAnim);
+
+#ifndef FORCE_SLIDY
+ int32 solidPath();
+ int32 solidWalkAnimator(WalkData *walkAnim);
+#endif
+
+ void plotCross(int16 x, int16 y, uint8 colour);
+
+public:
+ Router(Sword2Engine *vm) : _vm(vm), _diagonalx(0), _diagonaly(0) {
+ memset(_routeSlots, 0, sizeof(_routeSlots));
+ memset(_bars, 0, sizeof(_bars));
+ memset(_node, 0, sizeof(_node));
+ memset(_walkGridList, 0, sizeof(_walkGridList));
+ memset(_route, 0, sizeof(_route));
+ memset(_smoothPath, 0, sizeof(_smoothPath));
+ memset(_modularPath, 0, sizeof(_modularPath));
+ memset(_modX, 0, sizeof(_modX));
+ memset(_modY, 0, sizeof(_modY));
+ memset(_firstSlowInFrame, 0, sizeof(_firstSlowInFrame));
+ }
+
+ void setStandbyCoords(int16 x, int16 y, uint8 dir);
+ int whatTarget(int startX, int startY, int destX, int destY);
+
+ // Sprites
+ void setSpriteStatus(byte *ob_graph, uint32 type);
+ void setSpriteShading(byte *ob_graph, uint32 type);
+
+ // Animation
+ int doAnimate(byte *ob_logic, byte *ob_graph, int32 animRes, bool reverse);
+ int megaTableAnimate(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *animTable, bool reverse);
+
+ // Walking
+ int doWalk(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, int16 target_x, int16 target_y, uint8 target_dir);
+ int walkToAnim(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, uint32 animRes);
+ int walkToTalkToMega(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, uint32 megaId, uint32 separation);
+
+ // Turning
+ int doFace(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, uint8 target_dir);
+ int faceXY(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, int16 target_x, int16 target_y);
+ int faceMega(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, uint32 megaId);
+
+ // Standing
+ void standAt(byte *ob_graph, byte *ob_mega, int32 x, int32 y, int32 dir);
+ void standAfterAnim(byte *ob_graph, byte *ob_mega, uint32 animRes);
+ void standAtAnim(byte *ob_graph, byte *ob_mega, uint32 animRes);
+
+ int32 routeFinder(byte *ob_mega, byte *ob_walkdata, int32 x, int32 y, int32 dir);
+
+ void earlySlowOut(byte *ob_mega, byte *ob_walkdata);
+
+ void allocateRouteMem();
+ WalkData *getRouteMem();
+ void freeRouteMem();
+ void freeAllRouteMem();
+ void addWalkGrid(int32 gridResource);
+ void removeWalkGrid(int32 gridResource);
+ void clearWalkGridList();
+
+ void plotWalkGrid();
+};
+
+} // End of namespace Sword2
+
+#endif
diff --git a/engines/sword2/save_rest.cpp b/engines/sword2/save_rest.cpp
new file mode 100644
index 0000000000..df03c21be3
--- /dev/null
+++ b/engines/sword2/save_rest.cpp
@@ -0,0 +1,414 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+// ---------------------------------------------------------------------------
+// SAVE_REST.CPP save, restore & restart functions
+//
+// James 05feb97
+//
+// "Jesus Saves", but could he Restore or Restart? He can now...
+//
+// ---------------------------------------------------------------------------
+
+#include "common/stdafx.h"
+#include "common/savefile.h"
+#include "sword2/sword2.h"
+#include "sword2/defs.h"
+#include "sword2/logic.h"
+#include "sword2/mouse.h"
+#include "sword2/resman.h"
+#include "sword2/sound.h"
+
+namespace Sword2 {
+
+// A savegame consists of a header and the global variables
+
+// Max length of a savegame filename
+#define MAX_FILENAME_LEN 128
+
+/**
+ * Calculate size of required savegame buffer
+ */
+
+uint32 Sword2Engine::findBufferSize() {
+ // Size of savegame header + size of global variables
+ return 212 + _resman->fetchLen(1);
+}
+
+/**
+ * Save the game.
+ */
+
+uint32 Sword2Engine::saveGame(uint16 slotNo, byte *desc) {
+ char description[SAVE_DESCRIPTION_LEN];
+ uint32 bufferSize = findBufferSize();
+ byte *saveBuffer = (byte *)malloc(bufferSize);
+ ScreenInfo *screenInfo = _screen->getScreenInfo();
+
+ memset(description, 0, sizeof(description));
+ strncpy(description, (char *)desc, SAVE_DESCRIPTION_LEN - 1);
+
+ Common::MemoryWriteStream writeS(saveBuffer, bufferSize);
+
+ byte *globalVars = _resman->openResource(1);
+ byte *objectHub = _resman->openResource(CUR_PLAYER_ID) + ResHeader::size();
+
+ // Script no. 7 - 'george_savedata_request' calls fnPassPlayerSaveData
+ _logic->runResScript(CUR_PLAYER_ID, 7);
+
+ writeS.writeUint32LE(0); // Checksum
+ writeS.write(description, SAVE_DESCRIPTION_LEN);
+ writeS.writeUint32LE(_resman->fetchLen(1));
+ writeS.writeUint32LE(screenInfo->background_layer_id);
+ writeS.writeUint32LE(_logic->getRunList());
+ writeS.writeUint32LE(screenInfo->feet_x);
+ writeS.writeUint32LE(screenInfo->feet_y);
+ writeS.writeUint32LE(_sound->getLoopingMusicId());
+ writeS.write(objectHub, ObjectHub::size());
+ writeS.write(_logic->_saveLogic, ObjectLogic::size());
+ writeS.write(_logic->_saveGraphic, ObjectGraphic::size());
+ writeS.write(_logic->_saveMega, ObjectMega::size());
+ writeS.write(globalVars, _resman->fetchLen(1));
+
+ WRITE_LE_UINT32(saveBuffer, calcChecksum(saveBuffer + 4, bufferSize - 4));
+
+ _resman->closeResource(CUR_PLAYER_ID);
+ _resman->closeResource(1);
+
+ uint32 errorCode = saveData(slotNo, saveBuffer, bufferSize);
+
+ free(saveBuffer);
+
+ if (errorCode != SR_OK) {
+ uint32 textId;
+
+ switch (errorCode) {
+ case SR_ERR_FILEOPEN:
+ textId = TEXT_SAVE_CANT_OPEN;
+ break;
+ default:
+ textId = TEXT_SAVE_FAILED;
+ break;
+ }
+
+ _screen->displayMsg(fetchTextLine(_resman->openResource(textId / SIZE), textId & 0xffff) + 2, 0);
+ }
+
+ return errorCode;
+}
+
+uint32 Sword2Engine::saveData(uint16 slotNo, byte *buffer, uint32 bufferSize) {
+ char saveFileName[MAX_FILENAME_LEN];
+
+ sprintf(saveFileName, "%s.%.3d", _targetName.c_str(), slotNo);
+
+ Common::OutSaveFile *out;
+
+ if (!(out = _saveFileMan->openForSaving(saveFileName))) {
+ return SR_ERR_FILEOPEN;
+ }
+
+ out->write(buffer, bufferSize);
+ out->flush();
+
+ if (!out->ioFailed()) {
+ delete out;
+ return SR_OK;
+ }
+
+ delete out;
+ return SR_ERR_WRITEFAIL;
+}
+
+/**
+ * Restore the game.
+ */
+
+uint32 Sword2Engine::restoreGame(uint16 slotNo) {
+ uint32 bufferSize = findBufferSize();
+ byte *saveBufferMem = (byte *)malloc(bufferSize);
+
+ uint32 errorCode = restoreData(slotNo, saveBufferMem, bufferSize);
+
+ // If it was read in successfully, then restore the game from the
+ // buffer & free the buffer. Note that restoreFromBuffer() frees the
+ // buffer in order to clear it from memory before loading in the new
+ // screen and runlist, so we only need to free it in case of failure.
+
+ if (errorCode == SR_OK)
+ errorCode = restoreFromBuffer(saveBufferMem, bufferSize);
+ else
+ free(saveBufferMem);
+
+ if (errorCode != SR_OK) {
+ uint32 textId;
+
+ switch (errorCode) {
+ case SR_ERR_FILEOPEN:
+ textId = TEXT_RESTORE_CANT_OPEN;
+ break;
+ case SR_ERR_INCOMPATIBLE:
+ textId = TEXT_RESTORE_INCOMPATIBLE;
+ break;
+ default:
+ textId = TEXT_RESTORE_FAILED;
+ break;
+ }
+
+ _screen->displayMsg(fetchTextLine(_resman->openResource(textId / SIZE), textId & 0xffff) + 2, 0);
+ } else {
+ // Prime system with a game cycle
+
+ // Reset the graphic 'BuildUnit' list before a new logic list
+ // (see fnRegisterFrame)
+ _screen->resetRenderLists();
+
+ // Reset the mouse hot-spot list. See fnRegisterMouse()
+ // and fnRegisterFrame()
+ _mouse->resetMouseList();
+
+ if (_logic->processSession())
+ error("restore 1st cycle failed??");
+ }
+
+ // Force the game engine to pick a cursor. This appears to be needed
+ // when using the -x command-line option to restore a game.
+ _mouse->setMouseTouching(1);
+ return errorCode;
+}
+
+uint32 Sword2Engine::restoreData(uint16 slotNo, byte *buffer, uint32 bufferSize) {
+ char saveFileName[MAX_FILENAME_LEN];
+
+ sprintf(saveFileName, "%s.%.3d", _targetName.c_str(), slotNo);
+
+ Common::InSaveFile *in;
+
+ if (!(in = _saveFileMan->openForLoading(saveFileName))) {
+ // error: couldn't open file
+ return SR_ERR_FILEOPEN;
+ }
+
+ // Read savegame into the buffer
+ uint32 itemsRead = in->read(buffer, bufferSize);
+
+ delete in;
+
+ if (itemsRead != bufferSize) {
+ // We didn't get all of it. At the moment we have no way of
+ // knowing why, so assume that it's an incompatible savegame.
+
+ return SR_ERR_INCOMPATIBLE;
+ }
+
+ return SR_OK;
+}
+
+uint32 Sword2Engine::restoreFromBuffer(byte *buffer, uint32 size) {
+ Common::MemoryReadStream readS(buffer, size);
+
+ // Calc checksum & check that aginst the value stored in the header
+
+ if (readS.readUint32LE() != calcChecksum(buffer + 4, size - 4)) {
+ free(buffer);
+ return SR_ERR_INCOMPATIBLE;
+ }
+
+ readS.seek(SAVE_DESCRIPTION_LEN, SEEK_CUR);
+
+ // Check savegame against length of current global variables resource
+ // This would most probably be trapped by the checksum test anyway,
+ // but it doesn't do any harm to check this as well.
+
+ // Historical note: During development, earlier savegames would often
+ // be shorter than the current expected length.
+
+ if (readS.readUint32LE() != _resman->fetchLen(1)) {
+ free(buffer);
+ return SR_ERR_INCOMPATIBLE;
+ }
+
+ byte *globalVars = _resman->openResource(1);
+ byte *objectHub = _resman->openResource(CUR_PLAYER_ID) + ResHeader::size();
+
+ uint32 screenId = readS.readUint32LE();
+ uint32 runListId = readS.readUint32LE();
+ uint32 feetX = readS.readUint32LE();
+ uint32 feetY = readS.readUint32LE();
+ uint32 musicId = readS.readUint32LE();
+
+ // Trash all resources from memory except player object & global vars
+ _resman->killAll(false);
+ _logic->resetKillList();
+
+ readS.read(objectHub, ObjectHub::size());
+ readS.read(_logic->_saveLogic, ObjectLogic::size());
+ readS.read(_logic->_saveGraphic, ObjectGraphic::size());
+ readS.read(_logic->_saveMega, ObjectMega::size());
+
+ // Fill out the player object structures from the savegame structures.
+ // Also run the appropriate scripts to set up George's anim tables and
+ // walkdata, and Nico's anim tables.
+
+ // Script no. 8 - 'george_savedata_return' calls fnGetPlayerSaveData
+ _logic->runResScript(CUR_PLAYER_ID, 8);
+
+ // Script no. 14 - 'set_up_nico_anim_tables'
+ _logic->runResScript(CUR_PLAYER_ID, 14);
+
+ // Which megaset was the player at the time of saving?
+ ObjectMega obMega(_logic->_saveMega);
+
+ uint32 scriptNo = 0;
+
+ switch (obMega.getMegasetRes()) {
+ case 36: // GeoMega:
+ scriptNo = 9; // script no.9 - 'player_is_george'
+ break;
+ case 2003: // GeoMegaB:
+ scriptNo = 13; // script no.13 - 'player_is_georgeB'
+ break;
+ case 1366: // NicMegaA:
+ scriptNo = 11; // script no.11 - 'player_is_nicoA'
+ break;
+ case 1437: // NicMegaB:
+ scriptNo = 12; // script no.12 - 'player_is_nicoB'
+ break;
+ case 1575: // NicMegaC:
+ scriptNo = 10; // script no.10 - 'player_is_nicoC'
+ break;
+ }
+
+ _logic->runResScript(CUR_PLAYER_ID, scriptNo);
+
+ // Copy variables from savegame buffer to memory
+ readS.read(globalVars, _resman->fetchLen(1));
+
+ _resman->closeResource(CUR_PLAYER_ID);
+ _resman->closeResource(1);
+
+ free(buffer);
+
+ int32 pars[2];
+
+ pars[0] = screenId;
+ pars[1] = 1;
+ _logic->fnInitBackground(pars);
+
+ ScreenInfo *screenInfo = _screen->getScreenInfo();
+
+ // So palette not restored immediately after control panel - we want to
+ // fade up instead!
+ screenInfo->new_palette = 99;
+
+ // These need setting after the defaults get set in fnInitBackground.
+ // Remember that these can change through the game, so need saving &
+ // restoring too.
+
+ screenInfo->feet_x = feetX;
+ screenInfo->feet_y = feetY;
+
+ // Start the new run list
+ _logic->expressChangeSession(runListId);
+
+ // Force in the new scroll position, so unsightly scroll-catch-up does
+ // not occur when screen first draws after returning from restore panel
+
+ // Set the screen record of player position - ready for setScrolling()
+
+ screenInfo->player_feet_x = obMega.getFeetX();
+ screenInfo->player_feet_y = obMega.getFeetY();
+
+ // if this screen is wide, recompute the scroll offsets now
+ if (screenInfo->scroll_flag)
+ _screen->setScrolling();
+
+ // Any music required will be started after we've returned from
+ // restoreControl() - see systemMenuMouse() in mouse.cpp!
+
+ // Restart any looping music. Originally this was - and still is - done
+ // in systemMenuMouse(), but with ScummVM we have other ways of
+ // restoring savegames so it's easier to put it here as well.
+
+ if (musicId) {
+ pars[0] = musicId;
+ pars[1] = FX_LOOP;
+ _logic->fnPlayMusic(pars);
+ } else
+ _logic->fnStopMusic(NULL);
+
+ return SR_OK;
+}
+
+/**
+ * Get the description of a savegame
+ */
+
+uint32 Sword2Engine::getSaveDescription(uint16 slotNo, byte *description) {
+ char saveFileName[MAX_FILENAME_LEN];
+
+ sprintf(saveFileName, "%s.%.3d", _targetName.c_str(), slotNo);
+
+ Common::InSaveFile *in;
+
+ if (!(in = _saveFileMan->openForLoading(saveFileName))) {
+ return SR_ERR_FILEOPEN;
+ }
+
+ in->readUint32LE();
+ in->read(description, SAVE_DESCRIPTION_LEN);
+
+ delete in;
+ return SR_OK;
+}
+
+bool Sword2Engine::saveExists() {
+ for (int i = 0; i <= 99; i++)
+ if (saveExists(i))
+ return true;
+ return false;
+}
+
+bool Sword2Engine::saveExists(uint16 slotNo) {
+ char saveFileName[MAX_FILENAME_LEN];
+
+ sprintf(saveFileName, "%s.%.3d", _targetName.c_str(), slotNo);
+
+ Common::InSaveFile *in;
+
+ if (!(in = _saveFileMan->openForLoading(saveFileName))) {
+ return false;
+ }
+
+ delete in;
+ return true;
+}
+
+uint32 Sword2Engine::calcChecksum(byte *buffer, uint32 size) {
+ uint32 total = 0;
+
+ for (uint32 pos = 0; pos < size; pos++)
+ total += buffer[pos];
+
+ return total;
+}
+
+} // End of namespace Sword2
diff --git a/engines/sword2/save_rest.h b/engines/sword2/save_rest.h
new file mode 100644
index 0000000000..9463f3fd4a
--- /dev/null
+++ b/engines/sword2/save_rest.h
@@ -0,0 +1,47 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#ifndef SAVE_REST_H
+#define SAVE_REST_H
+
+namespace Sword2 {
+
+#define SAVE_DESCRIPTION_LEN 64
+
+// Save & Restore error codes
+
+enum {
+ SR_OK, // No worries
+ SR_ERR_FILEOPEN, // Can't open file - Couldn't create file for
+ // saving, or couldn't find file for loading.
+ SR_ERR_INCOMPATIBLE, // (Restore) Incompatible savegame data.
+ // Savegame file is obsolete. (Won't happen
+ // after development stops)
+ SR_ERR_READFAIL, // (Restore) Failed on reading savegame file -
+ // Something screwed up during the read
+ SR_ERR_WRITEFAIL // (Save) Failed on writing savegame file -
+ // Something screwed up during the write -
+ // could be hard-drive full..?
+};
+
+} // End of namespace Sword2
+
+#endif
diff --git a/engines/sword2/scroll.cpp b/engines/sword2/scroll.cpp
new file mode 100644
index 0000000000..3d0a263bc6
--- /dev/null
+++ b/engines/sword2/scroll.cpp
@@ -0,0 +1,151 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "common/stdafx.h"
+#include "sword2/sword2.h"
+#include "sword2/defs.h"
+#include "sword2/logic.h"
+
+namespace Sword2 {
+
+// Max no of pixel allowed to scroll per cycle
+#define MAX_SCROLL_DISTANCE 8
+
+/**
+ * Sets the scroll target position for the end of the game cycle. The driver
+ * will then automatically scroll as many times as it can to reach this
+ * position in the allotted time.
+ */
+
+void Screen::setScrollTarget(int16 sx, int16 sy) {
+ _scrollXTarget = sx;
+ _scrollYTarget = sy;
+}
+
+/**
+ * If the room is larger than the physical screen, this function is called
+ * every game cycle to update the scroll offsets.
+ */
+
+void Screen::setScrolling() {
+ // Normally we aim to get George's feet at (320,250) from top left
+ // of screen window
+ // feet_x = 128 + 320
+ // feet_y = 128 + 250
+
+ // Set scroll offsets according to the player's coords
+
+ // If the scroll offsets are being forced in script, ensure that they
+ // are neither too far to the right nor too far down.
+ uint32 scrollX = _vm->_logic->readVar(SCROLL_X);
+ uint32 scrollY = _vm->_logic->readVar(SCROLL_Y);
+
+ if (scrollX || scrollY) {
+ _thisScreen.scroll_offset_x = MIN((uint16)scrollX, _thisScreen.max_scroll_offset_x);
+ _thisScreen.scroll_offset_y = MIN((uint16)scrollY, _thisScreen.max_scroll_offset_y);
+ return;
+ }
+
+ // George's offset from the centre - the desired position for him
+
+ int16 offset_x = _thisScreen.player_feet_x - _thisScreen.feet_x;
+ int16 offset_y = _thisScreen.player_feet_y - _thisScreen.feet_y;
+
+ // Prevent scrolling too far left/right/up/down
+
+ if (offset_x < 0)
+ offset_x = 0;
+ else if (offset_x > _thisScreen.max_scroll_offset_x)
+ offset_x = _thisScreen.max_scroll_offset_x;
+
+ if (offset_y < 0)
+ offset_y = 0;
+ else if (offset_y > _thisScreen.max_scroll_offset_y)
+ offset_y = _thisScreen.max_scroll_offset_y;
+
+ // First time on this screen - need absolute scroll immediately!
+
+ if (_thisScreen.scroll_flag == 2) {
+ debug(5, "init scroll");
+ _thisScreen.scroll_offset_x = offset_x;
+ _thisScreen.scroll_offset_y = offset_y;
+ _thisScreen.scroll_flag = 1;
+ return;
+ }
+
+ // Catch up with required scroll offsets - speed depending on distance
+ // to catch up (dx and dy) and _scrollFraction used, but limit to
+ // certain number of pixels per cycle (MAX_SCROLL_DISTANCE)
+
+ int16 dx = _thisScreen.scroll_offset_x - offset_x;
+ int16 dy = _thisScreen.scroll_offset_y - offset_y;
+
+ uint16 scroll_distance_x; // how much we want to scroll
+ uint16 scroll_distance_y;
+
+ if (dx < 0) {
+ // Current scroll_offset_x is less than the required value
+
+ // NB. I'm adding 1 to the result of dx / SCROLL_FRACTION,
+ // because it would otherwise not scroll at all when
+ // dx < SCROLL_FRACTION
+
+ // => inc by (fraction of the differnce) NB. dx is -ve, so we
+ // subtract dx / SCROLL_FRACTION
+
+ scroll_distance_x = 1 - dx / _scrollFraction;
+
+ if (scroll_distance_x > MAX_SCROLL_DISTANCE)
+ scroll_distance_x = MAX_SCROLL_DISTANCE;
+
+ _thisScreen.scroll_offset_x += scroll_distance_x;
+ } else if (dx > 0) {
+ // Current scroll_offset_x is greater than
+ // the required value
+
+ // => dec by (fraction of the differnce)
+
+ scroll_distance_x = 1 + dx / _scrollFraction;
+
+ if (scroll_distance_x > MAX_SCROLL_DISTANCE)
+ scroll_distance_x = MAX_SCROLL_DISTANCE;
+
+ _thisScreen.scroll_offset_x -= scroll_distance_x;
+ }
+
+ if (dy < 0) {
+ scroll_distance_y = 1 - dy / _scrollFraction;
+
+ if (scroll_distance_y > MAX_SCROLL_DISTANCE)
+ scroll_distance_y = MAX_SCROLL_DISTANCE;
+
+ _thisScreen.scroll_offset_y += scroll_distance_y;
+ } else if (dy > 0) {
+ scroll_distance_y = 1 + dy / _scrollFraction;
+
+ if (scroll_distance_y > MAX_SCROLL_DISTANCE)
+ scroll_distance_y = MAX_SCROLL_DISTANCE;
+
+ _thisScreen.scroll_offset_y -= scroll_distance_y;
+ }
+}
+
+} // End of namespace Sword2
diff --git a/engines/sword2/sound.cpp b/engines/sword2/sound.cpp
new file mode 100644
index 0000000000..d855e88afb
--- /dev/null
+++ b/engines/sword2/sound.cpp
@@ -0,0 +1,319 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+// ---------------------------------------------------------------------------
+// BROKEN SWORD 2
+//
+// SOUND.CPP Contains the sound engine, fx & music functions
+// Some very 'sound' code in here ;)
+//
+// (16Dec96 JEL)
+//
+// ---------------------------------------------------------------------------
+
+#include "common/stdafx.h"
+#include "common/file.h"
+#include "common/system.h"
+
+#include "sword2/sword2.h"
+#include "sword2/defs.h"
+#include "sword2/logic.h"
+#include "sword2/resman.h"
+#include "sword2/sound.h"
+
+#include "sound/wave.h"
+
+namespace Sword2 {
+
+Sound::Sound(Sword2Engine *vm) {
+ int i;
+
+ _vm = vm;
+
+ for (i = 0; i < FXQ_LENGTH; i++)
+ _fxQueue[i].resource = 0;
+
+ for (i = 0; i < MAXMUS; i++) {
+ _music[i] = NULL;
+
+ _musicFile[i].idxTab = NULL;
+ _musicFile[i].idxLen = 0;
+ _musicFile[i].fileSize = 0;
+ _musicFile[i].fileType = 0;
+ _musicFile[i].inUse = false;
+
+ _speechFile[i].idxTab = NULL;
+ _speechFile[i].idxLen = 0;
+ _speechFile[i].fileSize = 0;
+ _speechFile[i].fileType = 0;
+ _speechFile[i].inUse = false;
+ }
+
+ _speechPaused = false;
+ _musicPaused = false;
+ _fxPaused = false;
+
+ _speechMuted = false;
+ _musicMuted = false;
+ _fxMuted = false;
+
+ _reverseStereo = false;
+
+ _loopingMusicId = 0;
+
+ _mixBuffer = NULL;
+ _mixBufferLen = 0;
+
+ _vm->_mixer->setupPremix(this, Audio::Mixer::kMusicSoundType);
+}
+
+Sound::~Sound() {
+ _vm->_mixer->setupPremix(0);
+
+ clearFxQueue();
+ stopMusic(true);
+ stopSpeech();
+
+ free(_mixBuffer);
+
+ for (int i = 0; i < MAXMUS; i++) {
+ if (_musicFile[i].file.isOpen())
+ _musicFile[i].file.close();
+ if (_speechFile[i].file.isOpen())
+ _speechFile[i].file.close();
+
+ free(_musicFile[i].idxTab);
+ free(_speechFile[i].idxTab);
+ }
+}
+
+void Sound::setReverseStereo(bool reverse) {
+ if (reverse != _reverseStereo) {
+ _reverseStereo = reverse;
+
+ for (int i = 0; i < FXQ_LENGTH; i++) {
+ if (!_fxQueue[i].resource)
+ continue;
+
+ _fxQueue[i].pan = -_fxQueue[i].pan;
+ _vm->_mixer->setChannelBalance(_fxQueue[i].handle, _fxQueue[i].pan);
+ }
+ }
+}
+
+/**
+ * Stop all sounds, close their resources and clear the FX queue.
+ */
+
+void Sound::clearFxQueue() {
+ for (int i = 0; i < FXQ_LENGTH; i++) {
+ if (_fxQueue[i].resource) {
+ stopFx(i);
+ }
+ }
+}
+
+/**
+ * Process the FX queue. This function is called once every game cycle.
+ */
+
+void Sound::processFxQueue() {
+ for (int i = 0; i < FXQ_LENGTH; i++) {
+ if (!_fxQueue[i].resource)
+ continue;
+
+ switch (_fxQueue[i].type) {
+ case FX_RANDOM:
+ // 1 in 'delay' chance of this fx occurring
+ if (_vm->_rnd.getRandomNumber(_fxQueue[i].delay) == 0)
+ playFx(&_fxQueue[i]);
+ break;
+ case FX_SPOT:
+ if (_fxQueue[i].delay)
+ _fxQueue[i].delay--;
+ else {
+ playFx(&_fxQueue[i]);
+ _fxQueue[i].type = FX_SPOT2;
+ }
+ break;
+ case FX_LOOP:
+ playFx(&_fxQueue[i]);
+ _fxQueue[i].type = FX_LOOPING;
+ break;
+ case FX_SPOT2:
+ // Once the FX has finished remove it from the queue.
+ if (!_vm->_mixer->isSoundHandleActive(_fxQueue[i].handle)) {
+ _vm->_resman->closeResource(_fxQueue[i].resource);
+ _fxQueue[i].resource = 0;
+ }
+ break;
+ case FX_LOOPING:
+ // Once the looped FX has started we can ignore it,
+ // but we can't close it since the WAV data is in use.
+ break;
+ }
+ }
+}
+
+/**
+ * Queue a sound effect for playing later.
+ * @param res the sound resource number
+ * @param type the type of sound effect
+ * @param delay when to play the sound effect
+ * @param volume the sound effect volume (0 through 16)
+ * @param pan the sound effect panning (-16 through 16)
+ */
+
+void Sound::queueFx(int32 res, int32 type, int32 delay, int32 volume, int32 pan) {
+ if (_vm->_wantSfxDebug) {
+ const char *typeStr;
+
+ switch (type) {
+ case FX_SPOT:
+ typeStr = "SPOT";
+ break;
+ case FX_LOOP:
+ typeStr = "LOOPED";
+ break;
+ case FX_RANDOM:
+ typeStr = "RANDOM";
+ break;
+ default:
+ typeStr = "INVALID";
+ break;
+ }
+
+ byte buf[NAME_LEN];
+
+ debug(0, "SFX (sample=\"%s\", vol=%d, pan=%d, delay=%d, type=%s)", _vm->_resman->fetchName(res, buf), volume, pan, delay, typeStr);
+ }
+
+ for (int i = 0; i < FXQ_LENGTH; i++) {
+ if (!_fxQueue[i].resource) {
+ byte *data = _vm->_resman->openResource(res);
+
+ assert(_vm->_resman->fetchType(data) == WAV_FILE);
+
+ uint32 len = _vm->_resman->fetchLen(res) - ResHeader::size();
+
+ if (type == FX_RANDOM) {
+ // For spot effects and loops the delay is the
+ // number of frames to wait. For random
+ // effects, however, it's the average number of
+ // seconds between playing the sound, so we
+ // have to multiply by the frame rate.
+ delay *= 12;
+ }
+
+ volume = (volume * Audio::Mixer::kMaxChannelVolume) / 16;
+ pan = (pan * 127) / 16;
+
+ if (isReverseStereo())
+ pan = -pan;
+
+ _fxQueue[i].resource = res;
+ _fxQueue[i].data = data + ResHeader::size();
+ _fxQueue[i].len = len;
+ _fxQueue[i].delay = delay;
+ _fxQueue[i].volume = volume;
+ _fxQueue[i].pan = pan;
+ _fxQueue[i].type = type;
+
+ // Keep track of the index in the loop so that
+ // fnStopFx() can be used later to kill this sound.
+ // Mainly for FX_LOOP and FX_RANDOM.
+
+ _vm->_logic->writeVar(RESULT, i);
+ return;
+ }
+ }
+
+ warning("No free slot in FX queue");
+}
+
+int32 Sound::playFx(FxQueueEntry *fx) {
+ return playFx(&fx->handle, fx->data, fx->len, fx->volume, fx->pan, (fx->type == FX_LOOP), Audio::Mixer::kSFXSoundType);
+}
+
+int32 Sound::playFx(Audio::SoundHandle *handle, byte *data, uint32 len, uint8 vol, int8 pan, bool loop, Audio::Mixer::SoundType soundType) {
+ if (_fxMuted)
+ return RD_OK;
+
+ if (_vm->_mixer->isSoundHandleActive(*handle))
+ return RDERR_FXALREADYOPEN;
+
+ Common::MemoryReadStream stream(data, len);
+ int rate, size;
+ byte flags;
+
+ if (!loadWAVFromStream(stream, size, rate, flags)) {
+ warning("playFX: Not a valid WAV file");
+ return RDERR_INVALIDWAV;
+ }
+
+ // The resource manager must have complete control over when resources
+ // are freed, or reference counting will break horribly. Besides, the
+ // data pointer is not valid for passing to free(). Why the hell is the
+ // AUTOFREE flag set by default anyway?
+
+ flags &= ~Audio::Mixer::FLAG_AUTOFREE;
+
+ if (isReverseStereo())
+ flags |= Audio::Mixer::FLAG_REVERSE_STEREO;
+
+ if (loop)
+ flags |= Audio::Mixer::FLAG_LOOP;
+
+ _vm->_mixer->playRaw(handle, data + stream.pos(), size, rate, flags, -1, vol, pan, 0, 0, soundType);
+ return RD_OK;
+}
+
+/**
+ * This function closes a sound effect which has been previously opened for
+ * playing. Sound effects must be closed when they are finished with, otherwise
+ * you will run out of sound effect buffers.
+ * @param i the index of the sound to close
+ */
+
+int32 Sound::stopFx(int32 i) {
+ if (!_fxQueue[i].resource)
+ return RDERR_FXNOTOPEN;
+
+ _vm->_mixer->stopHandle(_fxQueue[i].handle);
+
+ _vm->_resman->closeResource(_fxQueue[i].resource);
+ _fxQueue[i].resource = 0;
+ return RD_OK;
+}
+
+void Sound::pauseAllSound() {
+ pauseMusic();
+ pauseSpeech();
+ pauseFx();
+}
+
+void Sound::unpauseAllSound() {
+ unpauseMusic();
+ unpauseSpeech();
+ unpauseFx();
+}
+
+} // End of namespace Sword2
diff --git a/engines/sword2/sound.h b/engines/sword2/sound.h
new file mode 100644
index 0000000000..7390a60bbc
--- /dev/null
+++ b/engines/sword2/sound.h
@@ -0,0 +1,272 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+/*****************************************************************************
+ * SOUND.H Sound engine
+ *
+ * SOUND.CPP Contains the sound engine, fx & music functions
+ * Some very 'sound' code in here ;)
+ *
+ * (16Dec96 JEL)
+ *
+ ****************************************************************************/
+
+#ifndef SOUND_H
+#define SOUND_H
+
+#include "common/file.h"
+#include "sound/audiostream.h"
+#include "sound/mixer.h"
+
+// Max number of sound fx
+#define MAXMUS 2
+
+// Max number of fx in queue at once
+#define FXQ_LENGTH 32
+
+#define BUFFER_SIZE 4096
+
+namespace Sword2 {
+
+enum {
+ kCLUMode = 1,
+ kMP3Mode,
+ kVorbisMode,
+ kFlacMode
+};
+
+enum {
+ // These three types correspond to types set by the scripts
+ FX_SPOT = 0,
+ FX_LOOP = 1,
+ FX_RANDOM = 2,
+
+ // These are used for FX queue bookkeeping
+ FX_SPOT2 = 3,
+ FX_LOOPING = 4
+};
+
+// Sound defines
+
+enum {
+ RDSE_SAMPLEFINISHED = 0,
+ RDSE_SAMPLEPLAYING = 1,
+ RDSE_FXTOCLEAR = 0, // Unused
+ RDSE_FXCACHED = 1, // Unused
+ RDSE_FXSPOT = 0,
+ RDSE_FXLOOP = 1,
+ RDSE_FXLEADIN = 2,
+ RDSE_FXLEADOUT = 3,
+ RDSE_QUIET = 1,
+ RDSE_SPEAKING = 0
+};
+
+class CLUInputStream : public AudioStream {
+private:
+ Common::File *_file;
+ bool _firstTime;
+ uint32 _file_pos;
+ uint32 _end_pos;
+ int16 _outbuf[BUFFER_SIZE];
+ byte _inbuf[BUFFER_SIZE];
+ const int16 *_bufferEnd;
+ const int16 *_pos;
+
+ uint16 _prev;
+
+ void refill();
+
+ inline bool eosIntern() const {
+ return _pos >= _bufferEnd;
+ }
+
+public:
+ CLUInputStream(Common::File *file, int size);
+ ~CLUInputStream();
+
+ int readBuffer(int16 *buffer, const int numSamples);
+
+ bool endOfData() const { return eosIntern(); }
+ bool isStereo() const { return false; }
+ int getRate() const { return 22050; }
+};
+
+struct SoundFileHandle {
+ Common::File file;
+ uint32 *idxTab;
+ uint32 idxLen;
+ uint32 fileSize;
+ uint32 fileType;
+ volatile bool inUse;
+};
+
+class MusicInputStream : public AudioStream {
+private:
+ int _cd;
+ SoundFileHandle *_fh;
+ uint32 _musicId;
+ AudioStream *_decoder;
+ int16 _buffer[BUFFER_SIZE];
+ const int16 *_bufferEnd;
+ const int16 *_pos;
+ bool _remove;
+ uint32 _numSamples;
+ uint32 _samplesLeft;
+ bool _looping;
+ int32 _fading;
+ int32 _fadeSamples;
+ bool _paused;
+
+ void refill();
+
+ inline bool eosIntern() const {
+ if (_looping)
+ return false;
+ return _remove || _pos >= _bufferEnd;
+ }
+
+public:
+ MusicInputStream(int cd, SoundFileHandle *fh, uint32 musicId, bool looping);
+ ~MusicInputStream();
+
+ int readBuffer(int16 *buffer, const int numSamples);
+
+ bool endOfData() const { return eosIntern(); }
+ bool isStereo() const { return _decoder->isStereo(); }
+ int getRate() const { return _decoder->getRate(); }
+
+ int getCD() { return _cd; }
+
+ void fadeUp();
+ void fadeDown();
+
+ bool isReady() { return _decoder != NULL; }
+ int32 isFading() { return _fading; }
+
+ bool readyToRemove();
+ int32 getTimeRemaining();
+};
+
+class Sound : public AudioStream {
+private:
+ Sword2Engine *_vm;
+
+ Common::Mutex _mutex;
+
+ struct FxQueueEntry {
+ Audio::SoundHandle handle; // sound handle
+ uint32 resource; // resource id of sample
+ byte *data; // pointer to WAV data
+ uint32 len; // WAV data length
+ uint16 delay; // cycles to wait before playing (or 'random chance' if FX_RANDOM)
+ uint8 volume; // sound volume
+ int8 pan; // sound panning
+ uint8 type; // FX_SPOT, FX_RANDOM, FX_LOOP
+ };
+
+ FxQueueEntry _fxQueue[FXQ_LENGTH];
+
+ void triggerFx(uint8 i);
+
+ bool _reverseStereo;
+
+ bool _speechMuted;
+ bool _fxMuted;
+ bool _musicMuted;
+
+ bool _speechPaused;
+ bool _fxPaused;
+ bool _musicPaused;
+
+ int32 _loopingMusicId;
+
+ Audio::SoundHandle _soundHandleSpeech;
+
+ MusicInputStream *_music[MAXMUS];
+ SoundFileHandle _musicFile[MAXMUS];
+ SoundFileHandle _speechFile[MAXMUS];
+
+ int16 *_mixBuffer;
+ int _mixBufferLen;
+
+public:
+ Sound(Sword2Engine *vm);
+ ~Sound();
+
+ // AudioStream API
+
+ int readBuffer(int16 *buffer, const int numSamples);
+ bool isStereo() const { return false; }
+ bool endOfData() const;
+ int getRate() const { return 22050; }
+
+ // End of AudioStream API
+
+ void clearFxQueue();
+ void processFxQueue();
+
+ void setReverseStereo(bool reverse);
+ bool isReverseStereo() const { return _reverseStereo; }
+
+ void muteSpeech(bool mute);
+ bool isSpeechMute() const { return _speechMuted; }
+
+ void muteFx(bool mute);
+ bool isFxMute() const { return _fxMuted; }
+
+ void muteMusic(bool mute) { _musicMuted = mute; }
+ bool isMusicMute() const { return _musicMuted; }
+
+ void setLoopingMusicId(int32 id) { _loopingMusicId = id; }
+ int32 getLoopingMusicId() const { return _loopingMusicId; }
+
+ void pauseSpeech();
+ void unpauseSpeech();
+
+ void pauseFx();
+ void unpauseFx();
+
+ void pauseMusic();
+ void unpauseMusic();
+
+ void pauseAllSound();
+ void unpauseAllSound();
+
+ void queueFx(int32 res, int32 type, int32 delay, int32 volume, int32 pan);
+ int32 playFx(FxQueueEntry *fx);
+ int32 playFx(Audio::SoundHandle *handle, byte *data, uint32 len, uint8 vol, int8 pan, bool loop, Audio::Mixer::SoundType soundType);
+ int32 stopFx(int32 i);
+ int32 setFxIdVolumePan(int32 id, int vol, int pan = 255);
+
+ int32 getSpeechStatus();
+ int32 amISpeaking();
+ int32 playCompSpeech(uint32 speechId, uint8 vol, int8 pan);
+ uint32 preFetchCompSpeech(uint32 speechId, uint16 **buf);
+ int32 stopSpeech();
+
+ int32 streamCompMusic(uint32 musicId, bool loop);
+ void stopMusic(bool immediately);
+ int32 musicTimeRemaining();
+};
+
+} // End of namespace Sword2
+
+#endif
diff --git a/engines/sword2/speech.cpp b/engines/sword2/speech.cpp
new file mode 100644
index 0000000000..da6bd258ea
--- /dev/null
+++ b/engines/sword2/speech.cpp
@@ -0,0 +1,229 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "common/stdafx.h"
+#include "common/file.h"
+
+#include "sword2/sword2.h"
+#include "sword2/console.h"
+#include "sword2/defs.h"
+#include "sword2/logic.h"
+#include "sword2/maketext.h"
+#include "sword2/resman.h"
+
+namespace Sword2 {
+
+// To request the status of a target, we run its 4th script, get-speech-state.
+// This will cause RESULT to be set to either 1 (target is waiting) or 0
+// (target is busy).
+
+// Distance kept above talking sprite
+#define GAP_ABOVE_HEAD 20
+
+enum {
+ S_OB_GRAPHIC = 0,
+ S_OB_SPEECH = 1,
+ S_OB_LOGIC = 2,
+ S_OB_MEGA = 3,
+
+ S_TEXT = 4,
+ S_WAV = 5,
+ S_ANIM = 6,
+ S_DIR_TABLE = 7,
+ S_ANIM_MODE = 8
+};
+
+/**
+ * Sets _textX and _textY for position of text sprite. Note that _textX is
+ * also used to calculate speech pan.
+ */
+
+void Logic::locateTalker(int32 *params) {
+ // params: 0 pointer to ob_graphic
+ // 1 pointer to ob_speech
+ // 2 pointer to ob_logic
+ // 3 pointer to ob_mega
+ // 4 encoded text number
+ // 5 wav res id
+ // 6 anim res id
+ // 7 pointer to anim table
+ // 8 animation mode 0 lip synced,
+ // 1 just straight animation
+
+ if (!_animId) {
+ // There is no animation. Assume it's voice-over text, and put
+ // it at the bottom of the screen.
+
+ _textX = 320;
+ _textY = 400;
+ return;
+ }
+
+ byte *file = _vm->_resman->openResource(_animId);
+
+ // '0' means 1st frame
+
+ CdtEntry cdt_entry;
+ FrameHeader frame_head;
+
+ cdt_entry.read(_vm->fetchCdtEntry(file, 0));
+ frame_head.read(_vm->fetchFrameHeader(file, 0));
+
+ // Note: This part of the code is quite similar to registerFrame().
+
+ if (cdt_entry.frameType & FRAME_OFFSET) {
+ // The frame has offsets, i.e. it's a scalable mega frame
+ ObjectMega obMega(decodePtr(params[S_OB_MEGA]));
+
+ uint16 scale = obMega.calcScale();
+
+ // Calc suitable centre point above the head, based on scaled
+ // height
+
+ // just use 'feet_x' as centre
+ _textX = obMega.getFeetX();
+
+ // Add scaled y-offset to feet_y coord to get top of sprite
+ _textY = obMega.getFeetY() + (cdt_entry.y * scale) / 256;
+ } else {
+ // It's a non-scaling anim - calc suitable centre point above
+ // the head, based on scaled width
+
+ // x-coord + half of width
+ _textX = cdt_entry.x + frame_head.width / 2;
+ _textY = cdt_entry.y;
+ }
+
+ _vm->_resman->closeResource(_animId);
+
+ // Leave space above their head
+ _textY -= GAP_ABOVE_HEAD;
+
+ // Adjust the text coords for RDSPR_DISPLAYALIGN
+
+ ScreenInfo *screenInfo = _vm->_screen->getScreenInfo();
+
+ _textX -= screenInfo->scroll_offset_x;
+ _textY -= screenInfo->scroll_offset_y;
+}
+
+/**
+ * This function is called the first time to build the text, if we need one. If
+ * If necessary it also brings in the wav and sets up the animation.
+ *
+ * If there is an animation it can be repeating lip-sync or run-once.
+ *
+ * If there is no wav, then the text comes up instead. There can be any
+ * combination of text/wav playing.
+ */
+
+void Logic::formText(int32 *params) {
+ // params 0 pointer to ob_graphic
+ // 1 pointer to ob_speech
+ // 2 pointer to ob_logic
+ // 3 pointer to ob_mega
+ // 4 encoded text number
+ // 5 wav res id
+ // 6 anim res id
+ // 7 pointer to anim table
+ // 8 animation mode 0 lip synced,
+ // 1 just straight animation
+
+ // There should always be a text line, as all text is derived from it.
+ // If there is none, that's bad...
+
+ if (!params[S_TEXT]) {
+ warning("No text line for speech wav %d", params[S_WAV]);
+ return;
+ }
+
+ ObjectSpeech obSpeech(decodePtr(params[S_OB_SPEECH]));
+
+ // Establish the max width allowed for this text sprite.
+ uint32 textWidth = obSpeech.getWidth();
+
+ if (!textWidth)
+ textWidth = 400;
+
+ // Pull out the text line, and make the sprite and text block
+
+ uint32 text_res = params[S_TEXT] / SIZE;
+ uint32 local_text = params[S_TEXT] & 0xffff;
+ byte *text = _vm->fetchTextLine(_vm->_resman->openResource(text_res), local_text);
+
+ // 'text + 2' to skip the first 2 bytes which form the line reference
+ // number
+
+ _speechTextBlocNo = _vm->_fontRenderer->buildNewBloc(
+ text + 2, _textX, _textY,
+ textWidth, obSpeech.getPen(),
+ RDSPR_TRANS | RDSPR_DISPLAYALIGN,
+ _vm->_speechFontId, POSITION_AT_CENTRE_OF_BASE);
+
+ _vm->_resman->closeResource(text_res);
+
+ // Set speech duration, in case not using a wav.
+ _speechTime = strlen((char *)text) + 30;
+}
+
+/**
+ * There are some hard-coded cases where speech is used to illustrate a sound
+ * effect. In this case there is no sound associated with the speech itself.
+ */
+
+bool Logic::wantSpeechForLine(uint32 wavId) {
+ switch (wavId) {
+ case 1328: // AttendantSpeech
+ // SFX(Phone71);
+ // FX <Telephone rings>
+ case 2059: // PabloSpeech
+ // SFX (2059);
+ // FX <Sound of sporadic gunfire from below>
+ case 4082: // DuaneSpeech
+ // SFX (4082);
+ // FX <Pffffffffffft! Frp. (Unimpressive, flatulent noise.)>
+ case 4214: // cat_52
+ // SFX (4214);
+ // 4214FXMeow!
+ case 4568: // trapdoor_13
+ // SFX (4568);
+ // 4568fx<door slamming>
+ case 4913: // LobineauSpeech
+ // SFX (tone2);
+ // FX <Lobineau hangs up>
+ case 5120: // bush_66
+ // SFX (5120);
+ // 5120FX<loud buzzing>
+ case 528: // PresidentaSpeech
+ // SFX (528);
+ // FX <Nearby Crash of Collapsing Masonry>
+ case 920: // Zombie Island forest maze (bird)
+ case 923: // Zombie Island forest maze (monkey)
+ case 926: // Zombie Island forest maze (zombie)
+ // Don't want speech for these lines!
+ return false;
+ default:
+ // Ok for all other lines
+ return true;
+ }
+}
+
+} // End of namespace Sword2
diff --git a/engines/sword2/sprite.cpp b/engines/sword2/sprite.cpp
new file mode 100644
index 0000000000..a0ba7f7189
--- /dev/null
+++ b/engines/sword2/sprite.cpp
@@ -0,0 +1,655 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "common/stdafx.h"
+
+#include "sword2/sword2.h"
+#include "sword2/defs.h"
+#include "sword2/build_display.h"
+
+namespace Sword2 {
+
+/**
+ * This function takes a sprite and creates a mirror image of it.
+ * @param dst destination buffer
+ * @param src source buffer
+ * @param w width of the sprite
+ * @param h height of the sprite
+ */
+
+void Screen::mirrorSprite(byte *dst, byte *src, int16 w, int16 h) {
+ for (int y = 0; y < h; y++) {
+ for (int x = 0; x < w; x++) {
+ *dst++ = *(src + w - x - 1);
+ }
+ src += w;
+ }
+}
+
+/**
+ * This function takes a compressed frame of a sprite with up to 256 colours
+ * and decompresses it.
+ * @param dst destination buffer
+ * @param src source buffer
+ * @param decompSize the expected size of the decompressed sprite
+ */
+
+int32 Screen::decompressRLE256(byte *dst, byte *src, int32 decompSize) {
+ // PARAMETERS:
+ // source points to the start of the sprite data for input
+ // decompSize gives size of decompressed data in bytes
+ // dest points to start of destination buffer for decompressed
+ // data
+
+ byte headerByte; // block header byte
+ byte *endDest = dst + decompSize; // pointer to byte after end of decomp buffer
+ int32 rv;
+
+ while (1) {
+ // FLAT block
+ // read FLAT block header & increment 'scan' to first pixel
+ // of block
+ headerByte = *src++;
+
+ // if this isn't a zero-length block
+ if (headerByte) {
+ if (dst + headerByte > endDest) {
+ rv = 1;
+ break;
+ }
+
+ // set the next 'headerByte' pixels to the next colour
+ // at 'source'
+ memset(dst, *src, headerByte);
+
+ // increment destination pointer to just after this
+ // block
+ dst += headerByte;
+
+ // increment source pointer to just after this colour
+ src++;
+
+ // if we've decompressed all of the data
+ if (dst == endDest) {
+ rv = 0; // return "OK"
+ break;
+ }
+ }
+
+ // RAW block
+ // read RAW block header & increment 'scan' to first pixel of
+ // block
+ headerByte = *src++;
+
+ // if this isn't a zero-length block
+ if (headerByte) {
+ if (dst + headerByte > endDest) {
+ rv = 1;
+ break;
+ }
+
+ // copy the next 'headerByte' pixels from source to
+ // destination
+ memcpy(dst, src, headerByte);
+
+ // increment destination pointer to just after this
+ // block
+ dst += headerByte;
+
+ // increment source pointer to just after this block
+ src += headerByte;
+
+ // if we've decompressed all of the data
+ if (dst == endDest) {
+ rv = 0; // return "OK"
+ break;
+ }
+ }
+ }
+
+ return rv;
+}
+
+/**
+ * Unwinds a run of 16-colour data into 256-colour palette data.
+ */
+
+void Screen::unwindRaw16(byte *dst, byte *src, uint8 blockSize, byte *colTable) {
+ // for each pair of pixels
+ while (blockSize > 1) {
+ // 1st colour = number in table at position given by upper
+ // nibble of source byte
+ *dst++ = colTable[(*src) >> 4];
+
+ // 2nd colour = number in table at position given by lower
+ // nibble of source byte
+ *dst++ = colTable[(*src) & 0x0f];
+
+ // point to next source byte
+ src++;
+
+ // decrement count of how many pixels left to read
+ blockSize -= 2;
+ }
+
+ // if there's a final odd pixel
+ if (blockSize) {
+ // colour = number in table at position given by upper nibble
+ // of source byte
+ *dst++ = colTable[(*src) >> 4];
+ }
+}
+
+/**
+ * This function takes a compressed frame of a sprite (with up to 16 colours)
+ * and decompresses it.
+ * @param dst destination buffer
+ * @param src source buffer
+ * @param decompSize the expected size of the uncompressed sprite
+ * @param colTable mapping from the 16 encoded colours to the current palette
+ */
+
+int32 Screen::decompressRLE16(byte *dst, byte *src, int32 decompSize, byte *colTable) {
+ byte headerByte; // block header byte
+ byte *endDest = dst + decompSize; // pointer to byte after end of decomp buffer
+ int32 rv;
+
+ while (1) {
+ // FLAT block
+ // read FLAT block header & increment 'scan' to first pixel
+ // of block
+ headerByte = *src++;
+
+ // if this isn't a zero-length block
+ if (headerByte) {
+ if (dst + headerByte > endDest) {
+ rv = 1;
+ break;
+ }
+
+ // set the next 'headerByte' pixels to the next
+ // colour at 'source'
+ memset(dst, *src, headerByte);
+
+ // increment destination pointer to just after this
+ // block
+ dst += headerByte;
+
+ // increment source pointer to just after this colour
+ src++;
+
+ // if we've decompressed all of the data
+ if (dst == endDest) {
+ rv = 0; // return "OK"
+ break;
+ }
+ }
+
+ // RAW block
+ // read RAW block header & increment 'scan' to first pixel of
+ // block
+ headerByte = *src++;
+
+ // if this isn't a zero-length block
+ if (headerByte) {
+ if (dst + headerByte > endDest) {
+ rv = 1;
+ break;
+ }
+
+ // copy the next 'headerByte' pixels from source to
+ // destination (NB. 2 pixels per byte)
+ unwindRaw16(dst, src, headerByte, colTable);
+
+ // increment destination pointer to just after this
+ // block
+ dst += headerByte;
+
+ // increment source pointer to just after this block
+ // (NB. headerByte gives pixels, so /2 for bytes)
+ src += (headerByte + 1) / 2;
+
+ // if we've decompressed all of the data
+ if (dst >= endDest) {
+ rv = 0; // return "OK"
+ break;
+ }
+ }
+ }
+
+ return rv;
+}
+
+/**
+ * Creates a sprite surface. Sprite surfaces are used by the in-game dialogs
+ * and for displaying cutscene subtitles, which makes them much easier to draw
+ * than standard sprites.
+ * @param s information about how to decode the sprite
+ * @param sprite the buffer that will be created to store the surface
+ * @return RD_OK, or an error code
+ */
+
+int32 Screen::createSurface(SpriteInfo *s, byte **sprite) {
+ *sprite = (byte *)malloc(s->w * s->h);
+ if (!*sprite)
+ return RDERR_OUTOFMEMORY;
+
+ // Surfaces are either uncompressed or RLE256-compressed. No need to
+ // test for anything else.
+
+ if (s->type & RDSPR_NOCOMPRESSION) {
+ memcpy(*sprite, s->data, s->w * s->h);
+ } else if (decompressRLE256(*sprite, s->data, s->w * s->h)) {
+ free(*sprite);
+ return RDERR_DECOMPRESSION;
+ }
+
+ return RD_OK;
+}
+
+/**
+ * Draws the sprite surface created earlier.
+ * @param s information about how to place the sprite
+ * @param surface pointer to the surface created earlier
+ * @param clipRect the clipping rectangle
+ */
+
+void Screen::drawSurface(SpriteInfo *s, byte *surface, Common::Rect *clipRect) {
+ Common::Rect rd, rs;
+ uint16 x, y;
+ byte *src, *dst;
+
+ rs.left = 0;
+ rs.right = s->w;
+ rs.top = 0;
+ rs.bottom = s->h;
+
+ rd.left = s->x;
+ rd.right = rd.left + rs.right;
+ rd.top = s->y;
+ rd.bottom = rd.top + rs.bottom;
+
+ Common::Rect defClipRect(0, 0, _screenWide, _screenDeep);
+
+ if (!clipRect) {
+ clipRect = &defClipRect;
+ }
+
+ if (clipRect->left > rd.left) {
+ rs.left += (clipRect->left - rd.left);
+ rd.left = clipRect->left;
+ }
+
+ if (clipRect->top > rd.top) {
+ rs.top += (clipRect->top - rd.top);
+ rd.top = clipRect->top;
+ }
+
+ if (clipRect->right < rd.right) {
+ rd.right = clipRect->right;
+ }
+
+ if (clipRect->bottom < rd.bottom) {
+ rd.bottom = clipRect->bottom;
+ }
+
+ if (rd.width() <= 0 || rd.height() <= 0)
+ return;
+
+ src = surface + rs.top * s->w + rs.left;
+ dst = _buffer + _screenWide * rd.top + rd.left;
+
+ // Surfaces are always transparent.
+
+ for (y = 0; y < rd.height(); y++) {
+ for (x = 0; x < rd.width(); x++) {
+ if (src[x])
+ dst[x] = src[x];
+ }
+ src += s->w;
+ dst += _screenWide;
+ }
+
+ updateRect(&rd);
+}
+
+/**
+ * Destroys a surface.
+ */
+
+void Screen::deleteSurface(byte *surface) {
+ free(surface);
+}
+
+/**
+ * Draws a sprite onto the screen. The type of the sprite can be a combination
+ * of the following flags, some of which are mutually exclusive:
+ * RDSPR_DISPLAYALIGN The sprite is drawn relative to the top left corner
+ * of the screen
+ * RDSPR_FLIP The sprite is mirrored
+ * RDSPR_TRANS The sprite has a transparent colour zero
+ * RDSPR_BLEND The sprite is translucent
+ * RDSPR_SHADOW The sprite is affected by the light mask. (Scaled
+ * sprites always are.)
+ * RDSPR_NOCOMPRESSION The sprite data is not compressed
+ * RDSPR_RLE16 The sprite data is a 16-colour compressed sprite
+ * RDSPR_RLE256 The sprite data is a 256-colour compressed sprite
+ * @param s all the information needed to draw the sprite
+ * @warning Sprites will only be drawn onto the background, not over menubar
+ * areas.
+ */
+
+// FIXME: I'm sure this could be optimized. There's plenty of data copying and
+// mallocing here.
+
+int32 Screen::drawSprite(SpriteInfo *s) {
+ byte *src, *dst;
+ byte *sprite, *newSprite;
+ uint16 scale;
+ int16 i, j;
+ uint16 srcPitch;
+ bool freeSprite = false;
+ Common::Rect rd, rs;
+
+ // -----------------------------------------------------------------
+ // Decompression and mirroring
+ // -----------------------------------------------------------------
+
+ if (s->type & RDSPR_NOCOMPRESSION)
+ sprite = s->data;
+ else {
+ sprite = (byte *)malloc(s->w * s->h);
+ freeSprite = true;
+ if (!sprite)
+ return RDERR_OUTOFMEMORY;
+ if ((s->type & 0xff00) == RDSPR_RLE16) {
+ if (decompressRLE16(sprite, s->data, s->w * s->h, s->colourTable)) {
+ free(sprite);
+ return RDERR_DECOMPRESSION;
+ }
+ } else {
+ if (decompressRLE256(sprite, s->data, s->w * s->h)) {
+ free(sprite);
+ return RDERR_DECOMPRESSION;
+ }
+ }
+ }
+
+ if (s->type & RDSPR_FLIP) {
+ newSprite = (byte *)malloc(s->w * s->h);
+ if (newSprite == NULL) {
+ if (freeSprite)
+ free(sprite);
+ return RDERR_OUTOFMEMORY;
+ }
+ mirrorSprite(newSprite, sprite, s->w, s->h);
+ if (freeSprite)
+ free(sprite);
+ sprite = newSprite;
+ freeSprite = true;
+ }
+
+ // -----------------------------------------------------------------
+ // Positioning and clipping.
+ // -----------------------------------------------------------------
+
+ int16 spriteX = s->x;
+ int16 spriteY = s->y;
+
+ if (!(s->type & RDSPR_DISPLAYALIGN)) {
+ spriteX += _parallaxScrollX;
+ spriteY += _parallaxScrollY;
+ }
+
+ spriteY += MENUDEEP;
+
+ // A scale factor 0 or 256 means don't scale. Why do they use two
+ // different values to mean the same thing? Normalize it here for
+ // convenience.
+
+ scale = (s->scale == 0) ? 256 : s->scale;
+
+ rs.top = 0;
+ rs.left = 0;
+
+ if (scale != 256) {
+ rs.right = s->scaledWidth;
+ rs.bottom = s->scaledHeight;
+ srcPitch = s->scaledWidth;
+ } else {
+ rs.right = s->w;
+ rs.bottom = s->h;
+ srcPitch = s->w;
+ }
+
+ rd.top = spriteY;
+ rd.left = spriteX;
+
+ if (!(s->type & RDSPR_DISPLAYALIGN)) {
+ rd.top -= _scrollY;
+ rd.left -= _scrollX;
+ }
+
+ rd.right = rd.left + rs.right;
+ rd.bottom = rd.top + rs.bottom;
+
+ // Check if the sprite would end up completely outside the screen.
+
+ if (rd.left > RENDERWIDE || rd.top > RENDERDEEP + MENUDEEP || rd.right < 0 || rd.bottom < MENUDEEP) {
+ if (freeSprite)
+ free(sprite);
+ return RD_OK;
+ }
+
+ if (rd.top < MENUDEEP) {
+ rs.top = MENUDEEP - rd.top;
+ rd.top = MENUDEEP;
+ }
+ if (rd.bottom > RENDERDEEP + MENUDEEP) {
+ rd.bottom = RENDERDEEP + MENUDEEP;
+ rs.bottom = rs.top + (rd.bottom - rd.top);
+ }
+ if (rd.left < 0) {
+ rs.left = -rd.left;
+ rd.left = 0;
+ }
+ if (rd.right > RENDERWIDE) {
+ rd.right = RENDERWIDE;
+ rs.right = rs.left + (rd.right - rd.left);
+ }
+
+ // -----------------------------------------------------------------
+ // Scaling
+ // -----------------------------------------------------------------
+
+ if (scale != 256) {
+ if (s->scaledWidth > SCALE_MAXWIDTH || s->scaledHeight > SCALE_MAXHEIGHT) {
+ if (freeSprite)
+ free(sprite);
+ return RDERR_NOTIMPLEMENTED;
+ }
+
+ newSprite = (byte *)malloc(s->scaledWidth * s->scaledHeight);
+ if (newSprite == NULL) {
+ if (freeSprite)
+ free(sprite);
+ return RDERR_OUTOFMEMORY;
+ }
+
+ if (_renderCaps & RDBLTFX_EDGEBLEND)
+ scaleImageGood(newSprite, s->scaledWidth, s->scaledWidth, s->scaledHeight, sprite, s->w, s->w, s->h, _buffer + _screenWide * rd.top + rd.left);
+ else
+ scaleImageFast(newSprite, s->scaledWidth, s->scaledWidth, s->scaledHeight, sprite, s->w, s->w, s->h);
+
+ if (freeSprite)
+ free(sprite);
+ sprite = newSprite;
+ freeSprite = true;
+ }
+
+ // -----------------------------------------------------------------
+ // Light masking
+ // -----------------------------------------------------------------
+
+ // The light mask is an optional layer that covers the entire room
+ // and which is used to simulate light and shadows. Scaled sprites
+ // (actors, presumably) are always affected.
+
+ if ((_renderCaps & RDBLTFX_SHADOWBLEND) && _lightMask && (scale != 256 || (s->type & RDSPR_SHADOW))) {
+ byte *lightMap;
+
+ // Make sure that we never apply the shadow to the original
+ // resource data. This could only ever happen in the
+ // RDSPR_NOCOMPRESSION case.
+
+ if (!freeSprite) {
+ newSprite = (byte *)malloc(s->w * s->h);
+ memcpy(newSprite, sprite, s->w * s->h);
+ sprite = newSprite;
+ freeSprite = true;
+ }
+
+ src = sprite + rs.top * srcPitch + rs.left;
+ lightMap = _lightMask + (rd.top + _scrollY - MENUDEEP) * _locationWide + rd.left + _scrollX;
+
+ for (i = 0; i < rs.height(); i++) {
+ for (j = 0; j < rs.width(); j++) {
+ if (src[j] && lightMap[j]) {
+ uint8 r = ((32 - lightMap[j]) * _palette[src[j] * 4 + 0]) >> 5;
+ uint8 g = ((32 - lightMap[j]) * _palette[src[j] * 4 + 1]) >> 5;
+ uint8 b = ((32 - lightMap[j]) * _palette[src[j] * 4 + 2]) >> 5;
+ src[j] = quickMatch(r, g, b);
+ }
+ }
+ src += srcPitch;
+ lightMap += _locationWide;
+ }
+ }
+
+ // -----------------------------------------------------------------
+ // Drawing
+ // -----------------------------------------------------------------
+
+ src = sprite + rs.top * srcPitch + rs.left;
+ dst = _buffer + _screenWide * rd.top + rd.left;
+
+ if (s->type & RDSPR_BLEND) {
+ // The original code had two different blending cases. One for
+ // s->blend & 0x01 and one for s->blend & 0x02. However, the
+ // only values that actually appear in the cluster files are
+ // 0, 513 and 1025 so the s->blend & 0x02 case was never used.
+ // Which is just as well since that code made no sense to me.
+
+ if (!(_renderCaps & RDBLTFX_SPRITEBLEND)) {
+ for (i = 0; i < rs.height(); i++) {
+ for (j = 0; j < rs.width(); j++) {
+ if (src[j] && ((i & 1) == (j & 1)))
+ dst[j] = src[j];
+ }
+ src += srcPitch;
+ dst += _screenWide;
+ }
+ } else {
+ uint8 n = s->blend >> 8;
+
+ for (i = 0; i < rs.height(); i++) {
+ for (j = 0; j < rs.width(); j++) {
+ if (src[j]) {
+ uint8 r1 = _palette[src[j] * 4 + 0];
+ uint8 g1 = _palette[src[j] * 4 + 1];
+ uint8 b1 = _palette[src[j] * 4 + 2];
+ uint8 r2 = _palette[dst[j] * 4 + 0];
+ uint8 g2 = _palette[dst[j] * 4 + 1];
+ uint8 b2 = _palette[dst[j] * 4 + 2];
+
+ uint8 r = (r1 * n + r2 * (8 - n)) >> 3;
+ uint8 g = (g1 * n + g2 * (8 - n)) >> 3;
+ uint8 b = (b1 * n + b2 * (8 - n)) >> 3;
+ dst[j] = quickMatch(r, g, b);
+ }
+ }
+ src += srcPitch;
+ dst += _screenWide;
+ }
+ }
+ } else {
+ if (s->type & RDSPR_TRANS) {
+ for (i = 0; i < rs.height(); i++) {
+ for (j = 0; j < rs.width(); j++) {
+ if (src[j])
+ dst[j] = src[j];
+ }
+ src += srcPitch;
+ dst += _screenWide;
+ }
+ } else {
+ for (i = 0; i < rs.height(); i++) {
+ memcpy(dst, src, rs.width());
+ src += srcPitch;
+ dst += _screenWide;
+ }
+ }
+ }
+
+ if (freeSprite)
+ free(sprite);
+
+ markAsDirty(rd.left, rd.top, rd.right - 1, rd.bottom - 1);
+ return RD_OK;
+}
+
+/**
+ * Opens the light masking sprite for a room.
+ */
+
+int32 Screen::openLightMask(SpriteInfo *s) {
+ // FIXME: The light mask is only needed on higher graphics detail
+ // settings, so to save memory we could simply ignore it on lower
+ // settings. But then we need to figure out how to ensure that it
+ // is properly loaded if the user changes the settings in mid-game.
+
+ if (_lightMask)
+ return RDERR_NOTCLOSED;
+
+ _lightMask = (byte *)malloc(s->w * s->h);
+ if (!_lightMask)
+ return RDERR_OUTOFMEMORY;
+
+ if (decompressRLE256(_lightMask, s->data, s->w * s->h))
+ return RDERR_DECOMPRESSION;
+
+ return RD_OK;
+}
+
+/**
+ * Closes the light masking sprite for a room.
+ */
+
+int32 Screen::closeLightMask() {
+ if (!_lightMask)
+ return RDERR_NOTOPEN;
+
+ free(_lightMask);
+ _lightMask = NULL;
+ return RD_OK;
+}
+
+} // End of namespace Sword2
diff --git a/engines/sword2/startup.cpp b/engines/sword2/startup.cpp
new file mode 100644
index 0000000000..2e1dd7b0ae
--- /dev/null
+++ b/engines/sword2/startup.cpp
@@ -0,0 +1,173 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "common/stdafx.h"
+#include "common/file.h"
+
+#include "sword2/sword2.h"
+#include "sword2/defs.h"
+#include "sword2/logic.h"
+#include "sword2/maketext.h"
+#include "sword2/memory.h"
+#include "sword2/resman.h"
+#include "sword2/router.h"
+#include "sword2/sound.h"
+
+namespace Sword2 {
+
+bool Sword2Engine::initStartMenu() {
+ // Print out a list of all the start points available.
+ // There should be a linc produced file called startup.txt.
+ // This file should contain ascii numbers of all the resource game
+ // objects that are screen managers.
+ // We query each in turn and setup an array of start structures.
+ // If the file doesn't exist then we say so and return a 0.
+
+ Common::File fp;
+
+ // ok, load in the master screen manager file
+
+ _totalStartups = 0;
+ _totalScreenManagers = 0;
+
+ if (!fp.open("startup.inf")) {
+ warning("Cannot open startup.inf - the debugger won't have a start menu");
+ return false;
+ }
+
+ // The startup.inf file which contains a list of all the files. Now
+ // extract the filenames
+
+ int start_ids[MAX_starts];
+
+ while (1) {
+ bool done = false;
+
+ start_ids[_totalScreenManagers] = 0;
+
+ // Scan the string until the LF in CRLF
+
+ int b;
+
+ do {
+ b = fp.readByte();
+
+ if (fp.ioFailed()) {
+ done = true;
+ break;
+ }
+
+ if (isdigit(b)) {
+ start_ids[_totalScreenManagers] *= 10;
+ start_ids[_totalScreenManagers] += (b - '0');
+ }
+ } while (b != 10);
+
+ if (done)
+ break;
+
+ _totalScreenManagers++;
+
+ if (_totalScreenManagers == MAX_starts) {
+ warning("MAX_starts exceeded");
+ break;
+ }
+ }
+
+ fp.close();
+
+ // Using this method the Gode generated resource.inf must have #0d0a
+ // on the last entry
+
+ debug(1, "%d screen manager objects", _totalScreenManagers);
+
+ // Open each object and make a query call. The object must fill in a
+ // startup structure. It may fill in several if it wishes - for
+ // instance a startup could be set for later in the game where
+ // specific vars are set
+
+ for (uint i = 0; i < _totalScreenManagers; i++) {
+ _startRes = start_ids[i];
+
+ debug(2, "Querying screen manager %d", _startRes);
+
+ // Open each one and run through the interpreter. Script 0 is
+ // the query request script
+
+ // if the resource number is within range & it's not a null
+ // resource
+ // - need to check in case un-built sections included in
+ // start list
+
+ if (_resman->checkValid(_startRes)) {
+ _logic->runResScript(_startRes, 0);
+ } else
+ warning("Start menu resource %d invalid", _startRes);
+ }
+
+ return 1;
+}
+
+void Sword2Engine::registerStartPoint(int32 key, char *name) {
+ assert(_totalStartups < MAX_starts);
+
+ _startList[_totalStartups].start_res_id = _startRes;
+ _startList[_totalStartups].key = key;
+
+ strncpy(_startList[_totalStartups].description, name, MAX_description);
+ _startList[_totalStartups].description[MAX_description - 1] = 0;
+
+ _totalStartups++;
+}
+
+void Sword2Engine::runStart(int start) {
+ // Restarting - stop sfx, music & speech!
+
+ _sound->clearFxQueue();
+ _logic->fnStopMusic(NULL);
+ _sound->unpauseSpeech();
+ _sound->stopSpeech();
+
+ // Remove all resources from memory, including player object and global
+ // variables
+
+ _resman->removeAll();
+
+ // Reopen global variables resource and player object
+ setupPersistentResources();
+
+ // Free all the route memory blocks from previous game
+ _logic->_router->freeAllRouteMem();
+
+ // If there was speech text, kill the text block
+ if (_logic->_speechTextBlocNo) {
+ _fontRenderer->killTextBloc(_logic->_speechTextBlocNo);
+ _logic->_speechTextBlocNo = 0;
+ }
+
+ _logic->runResObjScript(_startList[start].start_res_id, CUR_PLAYER_ID, _startList[start].key & 0xffff);
+
+ // Make sure there's a mouse, in case restarting while mouse not
+ // available
+ _logic->fnAddHuman(NULL);
+}
+
+} // End of namespace Sword2
diff --git a/engines/sword2/sword2.cpp b/engines/sword2/sword2.cpp
new file mode 100644
index 0000000000..6056d636b7
--- /dev/null
+++ b/engines/sword2/sword2.cpp
@@ -0,0 +1,689 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "common/stdafx.h"
+
+#include "backends/fs/fs.h"
+
+#include "base/gameDetector.h"
+#include "base/plugins.h"
+
+#include "common/config-manager.h"
+#include "common/file.h"
+#include "common/system.h"
+
+#include "sword2/sword2.h"
+#include "sword2/console.h"
+#include "sword2/controls.h"
+#include "sword2/defs.h"
+#include "sword2/logic.h"
+#include "sword2/maketext.h"
+#include "sword2/memory.h"
+#include "sword2/mouse.h"
+#include "sword2/resman.h"
+#include "sword2/router.h"
+#include "sword2/sound.h"
+
+#ifdef _WIN32_WCE
+extern bool isSmartphone();
+#endif
+
+struct Sword2GameSettings {
+ const char *gameid;
+ const char *description;
+ uint32 features;
+ const char *detectname;
+ GameSettings toGameSettings() const {
+ GameSettings dummy = { gameid, description, features };
+ return dummy;
+ }
+};
+
+static const Sword2GameSettings sword2_settings[] = {
+ /* Broken Sword 2 */
+ {"sword2", "Broken Sword 2: The Smoking Mirror", GF_DEFAULT_TO_1X_SCALER, "players.clu" },
+ {"sword2alt", "Broken Sword 2: The Smoking Mirror (alt)", GF_DEFAULT_TO_1X_SCALER, "r2ctlns.ocx" },
+ {"sword2demo", "Broken Sword 2: The Smoking Mirror (Demo)", GF_DEFAULT_TO_1X_SCALER | Sword2::GF_DEMO, "players.clu" },
+ {NULL, NULL, 0, NULL}
+};
+
+GameList Engine_SWORD2_gameList() {
+ const Sword2GameSettings *g = sword2_settings;
+ GameList games;
+ while (g->gameid) {
+ games.push_back(g->toGameSettings());
+ g++;
+ }
+ return games;
+}
+
+DetectedGameList Engine_SWORD2_detectGames(const FSList &fslist) {
+ DetectedGameList detectedGames;
+ const Sword2GameSettings *g;
+
+ // TODO: It would be nice if we had code here which distinguishes
+ // between the 'sword2' and 'sword2demo' targets. The current code
+ // can't do that since they use the same detectname.
+
+ for (g = sword2_settings; g->gameid; ++g) {
+ // Iterate over all files in the given directory
+ for (FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
+ if (!file->isDirectory()) {
+ const char *gameName = file->displayName().c_str();
+
+ if (0 == scumm_stricmp(g->detectname, gameName)) {
+ // Match found, add to list of candidates, then abort inner loop.
+ detectedGames.push_back(g->toGameSettings());
+ break;
+ }
+ }
+ }
+ }
+ return detectedGames;
+}
+
+Engine *Engine_SWORD2_create(GameDetector *detector, OSystem *syst) {
+ return new Sword2::Sword2Engine(detector, syst);
+}
+
+REGISTER_PLUGIN(SWORD2, "Broken Sword 2")
+
+namespace Sword2 {
+
+Sword2Engine::Sword2Engine(GameDetector *detector, OSystem *syst) : Engine(syst) {
+ // Add default file directories
+ Common::File::addDefaultDirectory(_gameDataPath + "CLUSTERS/");
+ Common::File::addDefaultDirectory(_gameDataPath + "SWORD2/");
+ Common::File::addDefaultDirectory(_gameDataPath + "VIDEO/");
+ Common::File::addDefaultDirectory(_gameDataPath + "clusters/");
+ Common::File::addDefaultDirectory(_gameDataPath + "sword2/");
+ Common::File::addDefaultDirectory(_gameDataPath + "video/");
+
+ _features = detector->_game.features;
+ _targetName = detector->_targetName;
+
+ _bootParam = ConfMan.getInt("boot_param");
+ _saveSlot = ConfMan.getInt("save_slot");
+
+ _memory = NULL;
+ _resman = NULL;
+ _sound = NULL;
+ _screen = NULL;
+ _mouse = NULL;
+ _logic = NULL;
+ _fontRenderer = NULL;
+ _debugger = NULL;
+
+ _keyboardEvent.pending = false;
+ _keyboardEvent.repeat = 0;
+ _mouseEvent.pending = false;
+
+ _wantSfxDebug = false;
+
+#ifdef SWORD2_DEBUG
+ _stepOneCycle = false;
+ _renderSkip = false;
+#endif
+
+ _gamePaused = false;
+ _graphicsLevelFudged = false;
+
+ _gameCycle = 0;
+
+ _quit = false;
+}
+
+Sword2Engine::~Sword2Engine() {
+ delete _debugger;
+ delete _sound;
+ delete _fontRenderer;
+ delete _screen;
+ delete _mouse;
+ delete _logic;
+ delete _resman;
+ delete _memory;
+}
+
+void Sword2Engine::errorString(const char *buf1, char *buf2) {
+ strcpy(buf2, buf1);
+
+#ifdef _WIN32_WCE
+ if (isSmartphone())
+ return;
+#endif
+
+ // Unless an error -originated- within the debugger, spawn the
+ // debugger. Otherwise exit out normally.
+ if (_debugger && !_debugger->isAttached()) {
+ // (Print it again in case debugger segfaults)
+ printf("%s\n", buf2);
+ _debugger->attach(buf2);
+ _debugger->onFrame();
+ }
+}
+
+void Sword2Engine::registerDefaultSettings() {
+ ConfMan.registerDefault("music_mute", false);
+ ConfMan.registerDefault("speech_mute", false);
+ ConfMan.registerDefault("sfx_mute", false);
+ ConfMan.registerDefault("gfx_details", 2);
+ ConfMan.registerDefault("subtitles", false);
+ ConfMan.registerDefault("reverse_stereo", false);
+}
+
+void Sword2Engine::readSettings() {
+ _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, ConfMan.getInt("music_volume"));
+ _mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, ConfMan.getInt("speech_volume"));
+ _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, ConfMan.getInt("sfx_volume"));
+ setSubtitles(ConfMan.getBool("subtitles"));
+ _sound->muteMusic(ConfMan.getBool("music_mute"));
+ _sound->muteSpeech(ConfMan.getBool("speech_mute"));
+ _sound->muteFx(ConfMan.getBool("sfx_mute"));
+ _sound->setReverseStereo(ConfMan.getBool("reverse_stereo"));
+ _mouse->setObjectLabels(ConfMan.getBool("object_labels"));
+ _screen->setRenderLevel(ConfMan.getInt("gfx_details"));
+}
+
+void Sword2Engine::writeSettings() {
+ ConfMan.set("music_volume", _mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType));
+ ConfMan.set("speech_volume", _mixer->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType));
+ ConfMan.set("sfx_volume", _mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType));
+ ConfMan.set("music_mute", _sound->isMusicMute());
+ ConfMan.set("speech_mute", _sound->isSpeechMute());
+ ConfMan.set("sfx_mute", _sound->isFxMute());
+ ConfMan.set("gfx_details", _screen->getRenderLevel());
+ ConfMan.set("subtitles", getSubtitles());
+ ConfMan.set("object_labels", _mouse->getObjectLabels());
+ ConfMan.set("reverse_stereo", _sound->isReverseStereo());
+
+ ConfMan.flushToDisk();
+}
+
+/**
+ * The global script variables and player object should be kept open throughout
+ * the game, so that they are never expelled by the resource manager.
+ */
+
+void Sword2Engine::setupPersistentResources() {
+ _logic->_scriptVars = _resman->openResource(1) + ResHeader::size();
+ _resman->openResource(CUR_PLAYER_ID);
+}
+
+int Sword2Engine::init(GameDetector &detector) {
+ // Get some falling RAM and put it in your pocket, never let it slip
+ // away
+
+ _system->beginGFXTransaction();
+ initCommonGFX(detector);
+ _screen = new Screen(this, 640, 480);
+ _system->endGFXTransaction();
+
+ // Create the debugger as early as possible (but not before the
+ // screen object!) so that errors can be displayed in it. In
+ // particular, we want errors about missing files to be clearly
+ // visible to the user.
+
+ _debugger = new Debugger(this);
+
+ _memory = new MemoryManager(this);
+ _resman = new ResourceManager(this);
+ _logic = new Logic(this);
+ _fontRenderer = new FontRenderer(this);
+ _sound = new Sound(this);
+ _mouse = new Mouse(this);
+
+ // Setup mixer
+ if (!_mixer->isReady())
+ warning("Sound initialization failed");
+
+ registerDefaultSettings();
+ readSettings();
+
+ initStartMenu();
+
+ // During normal gameplay, we care neither about mouse button releases
+ // nor the scroll wheel.
+ setInputEventFilter(RD_LEFTBUTTONUP | RD_RIGHTBUTTONUP | RD_WHEELUP | RD_WHEELDOWN);
+
+ setupPersistentResources();
+ initialiseFontResourceFlags();
+
+ if (_features & GF_DEMO)
+ _logic->writeVar(DEMO, 1);
+ else
+ _logic->writeVar(DEMO, 0);
+
+ if (_saveSlot != -1) {
+ if (saveExists(_saveSlot))
+ restoreGame(_saveSlot);
+ else {
+ RestoreDialog dialog(this);
+ if (!dialog.runModal())
+ startGame();
+ }
+ } else if (!_bootParam && saveExists()) {
+ int32 pars[2] = { 221, FX_LOOP };
+ bool result;
+
+ _mouse->setMouse(NORMAL_MOUSE_ID);
+ _logic->fnPlayMusic(pars);
+
+ StartDialog dialog(this);
+
+ result = (dialog.runModal() != 0);
+
+ // If the game is started from the beginning, the cutscene
+ // player will kill the music for us. Otherwise, the restore
+ // will either have killed the music, or done a crossfade.
+
+ if (_quit)
+ return 0;
+
+ if (result)
+ startGame();
+ } else
+ startGame();
+
+ _screen->initialiseRenderCycle();
+
+ return 0;
+}
+
+int Sword2Engine::go() {
+ while (1) {
+ if (_debugger->isAttached())
+ _debugger->onFrame();
+
+#ifdef SWORD2_DEBUG
+ if (_stepOneCycle) {
+ pauseGame();
+ _stepOneCycle = false;
+ }
+#endif
+
+ KeyboardEvent *ke = keyboardEvent();
+
+ if (ke) {
+ if ((ke->modifiers == OSystem::KBD_CTRL && ke->keycode == 'd') || ke->ascii == '#' || ke->ascii == '~') {
+ _debugger->attach();
+ } else if (ke->modifiers == 0 || ke->modifiers == OSystem::KBD_SHIFT) {
+ switch (ke->keycode) {
+ case 'p':
+ if (_gamePaused)
+ unpauseGame();
+ else
+ pauseGame();
+ break;
+ case 'c':
+ if (!_logic->readVar(DEMO) && !_mouse->isChoosing()) {
+ ScreenInfo *screenInfo = _screen->getScreenInfo();
+ _logic->fnPlayCredits(NULL);
+ screenInfo->new_palette = 99;
+ }
+ break;
+#ifdef SWORD2_DEBUG
+ case ' ':
+ if (_gamePaused) {
+ _stepOneCycle = true;
+ unpauseGame();
+ }
+ break;
+ case 's':
+ _renderSkip = !_renderSkip;
+ break;
+#endif
+ default:
+ break;
+ }
+ }
+ }
+
+ // skip GameCycle if we're paused
+ if (!_gamePaused) {
+ _gameCycle++;
+ gameCycle();
+ }
+
+ // We can't use this as termination condition for the loop,
+ // because we want the break to happen before updating the
+ // screen again.
+
+ if (_quit)
+ break;
+
+ // creates the debug text blocks
+ _debugger->buildDebugText();
+
+#ifdef SWORD2_DEBUG
+ // if not in console & '_renderSkip' is set, only render
+ // display once every 4 game-cycles
+
+ if (!_renderSkip || (_gameCycle % 4) == 0)
+ _screen->buildDisplay();
+#else
+ _screen->buildDisplay();
+#endif
+ }
+
+ return 0;
+}
+
+void Sword2Engine::closeGame() {
+ _quit = true;
+}
+
+void Sword2Engine::restartGame() {
+ ScreenInfo *screenInfo = _screen->getScreenInfo();
+ uint32 temp_demo_flag;
+
+ _mouse->closeMenuImmediately();
+
+ // Restart the game. To do this, we must...
+
+ // Stop music instantly!
+ _sound->stopMusic(true);
+
+ // In case we were dead - well we're not anymore!
+ _logic->writeVar(DEAD, 0);
+
+ // Restart the game. Clear all memory and reset the globals
+ temp_demo_flag = _logic->readVar(DEMO);
+
+ // Remove all resources from memory, including player object and
+ // global variables
+ _resman->removeAll();
+
+ // Reopen global variables resource and player object
+ setupPersistentResources();
+
+ _logic->writeVar(DEMO, temp_demo_flag);
+
+ // Free all the route memory blocks from previous game
+ _logic->_router->freeAllRouteMem();
+
+ // Call the same function that first started us up
+ startGame();
+
+ // Prime system with a game cycle
+
+ // Reset the graphic 'BuildUnit' list before a new logic list
+ // (see fnRegisterFrame)
+ _screen->resetRenderLists();
+
+ // Reset the mouse hot-spot list (see fnRegisterMouse and
+ // fnRegisterFrame)
+ _mouse->resetMouseList();
+
+ _mouse->closeMenuImmediately();
+
+ // FOR THE DEMO - FORCE THE SCROLLING TO BE RESET!
+ // - this is taken from fnInitBackground
+ // switch on scrolling (2 means first time on screen)
+ screenInfo->scroll_flag = 2;
+
+ if (_logic->processSession())
+ error("restart 1st cycle failed??");
+
+ // So palette not restored immediately after control panel - we want
+ // to fade up instead!
+ screenInfo->new_palette = 99;
+}
+
+bool Sword2Engine::checkForMouseEvents() {
+ return _mouseEvent.pending;
+}
+
+MouseEvent *Sword2Engine::mouseEvent() {
+ if (!_mouseEvent.pending)
+ return NULL;
+
+ _mouseEvent.pending = false;
+ return &_mouseEvent;
+}
+
+KeyboardEvent *Sword2Engine::keyboardEvent() {
+ if (!_keyboardEvent.pending)
+ return NULL;
+
+ _keyboardEvent.pending = false;
+ return &_keyboardEvent;
+}
+
+uint32 Sword2Engine::setInputEventFilter(uint32 filter) {
+ uint32 oldFilter = _inputEventFilter;
+
+ _inputEventFilter = filter;
+ return oldFilter;
+}
+
+/**
+ * Clear the input events. This is so that we won't get any keyboard repeat
+ * right after using the debugging console.
+ */
+
+void Sword2Engine::clearInputEvents() {
+ _keyboardEvent.pending = false;
+ _keyboardEvent.repeat = 0;
+ _mouseEvent.pending = false;
+}
+
+/**
+ * OSystem Event Handler. Full of cross platform goodness and 99% fat free!
+ */
+
+void Sword2Engine::parseInputEvents() {
+ OSystem::Event event;
+
+ uint32 now = _system->getMillis();
+
+ while (_system->pollEvent(event)) {
+ switch (event.type) {
+ case OSystem::EVENT_KEYDOWN:
+ if (!(_inputEventFilter & RD_KEYDOWN)) {
+ _keyboardEvent.pending = true;
+ _keyboardEvent.repeat = now + 400;
+ _keyboardEvent.ascii = event.kbd.ascii;
+ _keyboardEvent.keycode = event.kbd.keycode;
+ _keyboardEvent.modifiers = event.kbd.flags;
+ }
+ break;
+ case OSystem::EVENT_KEYUP:
+ _keyboardEvent.repeat = 0;
+ break;
+ case OSystem::EVENT_MOUSEMOVE:
+ if (!(_inputEventFilter & RD_KEYDOWN)) {
+ _mouse->setPos(event.mouse.x, event.mouse.y - MENUDEEP);
+ }
+ break;
+ case OSystem::EVENT_LBUTTONDOWN:
+ if (!(_inputEventFilter & RD_LEFTBUTTONDOWN)) {
+ _mouseEvent.pending = true;
+ _mouseEvent.buttons = RD_LEFTBUTTONDOWN;
+ }
+ break;
+ case OSystem::EVENT_RBUTTONDOWN:
+ if (!(_inputEventFilter & RD_RIGHTBUTTONDOWN)) {
+ _mouseEvent.pending = true;
+ _mouseEvent.buttons = RD_RIGHTBUTTONDOWN;
+ }
+ break;
+ case OSystem::EVENT_LBUTTONUP:
+ if (!(_inputEventFilter & RD_LEFTBUTTONUP)) {
+ _mouseEvent.pending = true;
+ _mouseEvent.buttons = RD_LEFTBUTTONUP;
+ }
+ break;
+ case OSystem::EVENT_RBUTTONUP:
+ if (!(_inputEventFilter & RD_RIGHTBUTTONUP)) {
+ _mouseEvent.pending = true;
+ _mouseEvent.buttons = RD_RIGHTBUTTONUP;
+ }
+ break;
+ case OSystem::EVENT_WHEELUP:
+ if (!(_inputEventFilter & RD_WHEELUP)) {
+ _mouseEvent.pending = true;
+ _mouseEvent.buttons = RD_WHEELUP;
+ }
+ break;
+ case OSystem::EVENT_WHEELDOWN:
+ if (!(_inputEventFilter & RD_WHEELDOWN)) {
+ _mouseEvent.pending = true;
+ _mouseEvent.buttons = RD_WHEELDOWN;
+ }
+ break;
+ case OSystem::EVENT_QUIT:
+ closeGame();
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Handle keyboard auto-repeat
+ if (!_keyboardEvent.pending && _keyboardEvent.repeat && now >= _keyboardEvent.repeat) {
+ _keyboardEvent.pending = true;
+ _keyboardEvent.repeat = now + 100;
+ }
+}
+
+void Sword2Engine::gameCycle() {
+ // Do one game cycle, that is run the logic session until a full loop
+ // has been performed.
+
+ if (_logic->getRunList()) {
+ do {
+ // Reset the 'BuildUnit' and mouse hot-spot lists
+ // before each new logic list. The service scripts
+ // will fill thrm through fnRegisterFrame() and
+ // fnRegisterMouse().
+
+ _screen->resetRenderLists();
+ _mouse->resetMouseList();
+
+ // Keep going as long as new lists keep getting put in
+ // - i.e. screen changes.
+ } while (_logic->processSession());
+ } else {
+ // Start the console and print the start options perhaps?
+ _debugger->attach("AWAITING START COMMAND: (Enter 's 1' then 'q' to start from beginning)");
+ }
+
+ // If this screen is wide, recompute the scroll offsets every cycle
+ ScreenInfo *screenInfo = _screen->getScreenInfo();
+
+ if (screenInfo->scroll_flag)
+ _screen->setScrolling();
+
+ _mouse->mouseEngine();
+ _sound->processFxQueue();
+}
+
+void Sword2Engine::startGame() {
+ // Boot the game straight into a start script. It's always George's
+ // script #1, but with different ScreenManager objects depending on
+ // if it's the demo or the full game, or if we're using a boot param.
+
+ int screen_manager_id = 0;
+
+ debug(5, "startGame() STARTING:");
+
+ if (!_bootParam) {
+ if (_logic->readVar(DEMO))
+ screen_manager_id = 19; // DOCKS SECTION START
+ else
+ screen_manager_id = 949; // INTRO & PARIS START
+ } else {
+ // FIXME this could be validated against startup.inf for valid
+ // numbers to stop people shooting themselves in the foot
+
+ if (_bootParam != 0)
+ screen_manager_id = _bootParam;
+ }
+
+ _logic->runResObjScript(screen_manager_id, CUR_PLAYER_ID, 1);
+}
+
+// FIXME: Move this to some better place?
+
+void Sword2Engine::sleepUntil(uint32 time) {
+ while (getMillis() < time) {
+ // Make sure menu animations and fades don't suffer, but don't
+ // redraw the entire scene.
+ _mouse->processMenu();
+ _screen->updateDisplay(false);
+ _system->delayMillis(10);
+ }
+}
+
+void Sword2Engine::pauseGame() {
+ // Don't allow Pause while screen fading or while black
+ if (_screen->getFadeStatus() != RDFADE_NONE)
+ return;
+
+ _sound->pauseAllSound();
+ _mouse->pauseGame();
+
+ // If render level is at max, turn it down because palette-matching
+ // won't work when the palette is dimmed.
+
+ if (_screen->getRenderLevel() == 3) {
+ _screen->setRenderLevel(2);
+ _graphicsLevelFudged = true;
+ }
+
+#ifdef SWORD2_DEBUG
+ // Don't dim it if we're single-stepping through frames
+ // dim the palette during the pause
+
+ if (!_stepOneCycle)
+ _screen->dimPalette();
+#else
+ _screen->dimPalette();
+#endif
+
+ _gamePaused = true;
+}
+
+void Sword2Engine::unpauseGame() {
+ _mouse->unpauseGame();
+ _sound->unpauseAllSound();
+
+ // Put back game screen palette; see build_display.cpp
+ _screen->setFullPalette(-1);
+
+ // If graphics level at max, turn up again
+ if (_graphicsLevelFudged) {
+ _screen->setRenderLevel(3);
+ _graphicsLevelFudged = false;
+ }
+
+ _gamePaused = false;
+
+ // If mouse is about or we're in a chooser menu
+ if (!_mouse->getMouseStatus() || _mouse->isChoosing())
+ _mouse->setMouse(NORMAL_MOUSE_ID);
+}
+
+uint32 Sword2Engine::getMillis() {
+ return _system->getMillis();
+}
+
+} // End of namespace Sword2
diff --git a/engines/sword2/sword2.h b/engines/sword2/sword2.h
new file mode 100644
index 0000000000..bcb80ec957
--- /dev/null
+++ b/engines/sword2/sword2.h
@@ -0,0 +1,240 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#ifndef _SWORD2
+#define _SWORD2
+
+// Enable this to make it possible to clear the mouse cursor luggage by
+// right-clicking. The original didn't do this, but it feels natural to me.
+// However, I'm afraid that it'll interfer badly with parts of the game, so
+// for now I'll keep it disabled.
+
+#define RIGHT_CLICK_CLEARS_LUGGAGE 0
+
+#include "base/engine.h"
+
+#include "common/util.h"
+
+#include "sword2/build_display.h"
+#include "sword2/header.h"
+#include "sword2/icons.h"
+#include "sword2/object.h"
+#include "sword2/save_rest.h"
+
+#define MAX_starts 100
+#define MAX_description 100
+
+class GameDetector;
+class OSystem;
+
+namespace Sword2 {
+
+enum {
+ GF_DEMO = 1 << 0
+};
+
+class MemoryManager;
+class ResourceManager;
+class Sound;
+class Screen;
+class Mouse;
+class Logic;
+class FontRenderer;
+class Gui;
+class Debugger;
+
+enum {
+ RD_LEFTBUTTONDOWN = 0x01,
+ RD_LEFTBUTTONUP = 0x02,
+ RD_RIGHTBUTTONDOWN = 0x04,
+ RD_RIGHTBUTTONUP = 0x08,
+ RD_WHEELUP = 0x10,
+ RD_WHEELDOWN = 0x20,
+ RD_KEYDOWN = 0x40,
+ RD_MOUSEMOVE = 0x80
+};
+
+struct MouseEvent {
+ bool pending;
+ uint16 buttons;
+};
+
+struct KeyboardEvent {
+ bool pending;
+ uint32 repeat;
+ uint16 ascii;
+ int keycode;
+ int modifiers;
+};
+
+struct StartUp {
+ char description[MAX_description];
+
+ // id of screen manager object
+ uint32 start_res_id;
+
+ // Tell the manager which startup you want (if there are more than 1)
+ // (i.e more than 1 entrance to a screen and/or separate game boots)
+ uint32 key;
+};
+
+class Sword2Engine : public Engine {
+private:
+ uint32 _inputEventFilter;
+
+ // The event "buffers"
+ MouseEvent _mouseEvent;
+ KeyboardEvent _keyboardEvent;
+
+ uint32 _bootParam;
+ int32 _saveSlot;
+
+ void getPlayerStructures();
+ void putPlayerStructures();
+
+ uint32 saveData(uint16 slotNo, byte *buffer, uint32 bufferSize);
+ uint32 restoreData(uint16 slotNo, byte *buffer, uint32 bufferSize);
+
+ uint32 calcChecksum(byte *buffer, uint32 size);
+
+ void pauseGame();
+ void unpauseGame();
+
+ uint32 _totalStartups;
+ uint32 _totalScreenManagers;
+ uint32 _startRes;
+
+ bool _useSubtitles;
+
+ StartUp _startList[MAX_starts];
+
+public:
+ Sword2Engine(GameDetector *detector, OSystem *syst);
+ ~Sword2Engine();
+ int go();
+ int init(GameDetector &detector);
+
+ void registerDefaultSettings();
+ void readSettings();
+ void writeSettings();
+
+ void setupPersistentResources();
+
+ bool getSubtitles() { return _useSubtitles; }
+ void setSubtitles(bool b) { _useSubtitles = b; }
+
+ bool _quit;
+
+ uint32 _features;
+ Common::String _targetName; // target name for saves
+
+ MemoryManager *_memory;
+ ResourceManager *_resman;
+ Sound *_sound;
+ Screen *_screen;
+ Mouse *_mouse;
+ Logic *_logic;
+ FontRenderer *_fontRenderer;
+
+ Debugger *_debugger;
+
+ Common::RandomSource _rnd;
+
+ uint32 _speechFontId;
+ uint32 _controlsFontId;
+ uint32 _redFontId;
+
+ uint32 setInputEventFilter(uint32 filter);
+
+ void clearInputEvents();
+ void parseInputEvents();
+
+ bool checkForMouseEvents();
+ MouseEvent *mouseEvent();
+ KeyboardEvent *keyboardEvent();
+
+ bool _wantSfxDebug;
+
+ int32 _gameCycle;
+
+#ifdef SWORD2_DEBUG
+ bool _renderSkip;
+ bool _stepOneCycle;
+#endif
+
+#if RIGHT_CLICK_CLEARS_LUGGAGE
+ bool heldIsInInventory();
+#endif
+
+ byte *fetchPalette(byte *screenFile);
+ byte *fetchScreenHeader(byte *screenFile);
+ byte *fetchLayerHeader(byte *screenFile, uint16 layerNo);
+ byte *fetchShadingMask(byte *screenFile);
+
+ byte *fetchAnimHeader(byte *animFile);
+ byte *fetchCdtEntry(byte *animFile, uint16 frameNo);
+ byte *fetchFrameHeader(byte *animFile, uint16 frameNo);
+ byte *fetchBackgroundParallaxLayer(byte *screenFile, int layer);
+ byte *fetchBackgroundLayer(byte *screenFile);
+ byte *fetchForegroundParallaxLayer(byte *screenFile, int layer);
+ byte *fetchTextLine(byte *file, uint32 text_line);
+ bool checkTextLine(byte *file, uint32 text_line);
+ byte *fetchPaletteMatchTable(byte *screenFile);
+
+ uint32 saveGame(uint16 slotNo, byte *description);
+ uint32 restoreGame(uint16 slotNo);
+ uint32 getSaveDescription(uint16 slotNo, byte *description);
+ bool saveExists();
+ bool saveExists(uint16 slotNo);
+ uint32 restoreFromBuffer(byte *buffer, uint32 size);
+ uint32 findBufferSize();
+
+ bool _gamePaused;
+ bool _graphicsLevelFudged;
+
+ void startGame();
+ void gameCycle();
+ void closeGame();
+ void restartGame();
+
+ void sleepUntil(uint32 time);
+
+ void errorString(const char *buf_input, char *buf_output);
+ void initialiseFontResourceFlags();
+ void initialiseFontResourceFlags(uint8 language);
+
+ bool initStartMenu();
+ void registerStartPoint(int32 key, char *name);
+
+ uint32 getNumStarts() { return _totalStartups; }
+ uint32 getNumScreenManagers() { return _totalScreenManagers; }
+ StartUp *getStartList() { return _startList; }
+
+ void runStart(int start);
+
+ // Convenience alias for OSystem::getMillis().
+ // This is a bit hackish, of course :-).
+ uint32 getMillis();
+};
+
+} // End of namespace Sword2
+
+#endif
diff --git a/engines/sword2/sync.cpp b/engines/sword2/sync.cpp
new file mode 100644
index 0000000000..205e273873
--- /dev/null
+++ b/engines/sword2/sync.cpp
@@ -0,0 +1,76 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "common/stdafx.h"
+#include "sword2/sword2.h"
+#include "sword2/defs.h"
+#include "sword2/logic.h"
+
+namespace Sword2 {
+
+/**
+ * Clear any syncs registered for this id. Call this just after the id has been
+ * processed. Theoretically there could be more than one sync waiting for us,
+ * so clear the lot.
+ */
+
+void Logic::clearSyncs(uint32 id) {
+ for (int i = 0; i < ARRAYSIZE(_syncList); i++) {
+ if (_syncList[i].id == id) {
+ debug(5, "removing sync %d for %d", i, id);
+ _syncList[i].id = 0;
+ }
+ }
+}
+
+void Logic::sendSync(uint32 id, uint32 sync) {
+ for (int i = 0; i < ARRAYSIZE(_syncList); i++) {
+ if (_syncList[i].id == 0) {
+ debug(5, "%d sends sync %d to %d", readVar(ID), sync, id);
+ _syncList[i].id = id;
+ _syncList[i].sync = sync;
+ return;
+ }
+ }
+
+ // The original code didn't even check for this condition, so maybe
+ // it should be a fatal error?
+
+ warning("No free sync slot");
+}
+
+/**
+ * Check for a sync waiting for this character. Called from fnAnim() to see if
+ * animation is to be finished. Returns an index into _syncList[], or -1.
+ */
+
+int Logic::getSync() {
+ uint32 id = readVar(ID);
+
+ for (int i = 0; i < ARRAYSIZE(_syncList); i++) {
+ if (_syncList[i].id == id)
+ return i;
+ }
+
+ return -1;
+}
+
+} // End of namespace Sword2
diff --git a/engines/sword2/walker.cpp b/engines/sword2/walker.cpp
new file mode 100644
index 0000000000..82ef80f65f
--- /dev/null
+++ b/engines/sword2/walker.cpp
@@ -0,0 +1,454 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+// WALKER.CPP by James (14nov96)
+
+// Functions for moving megas about the place & also for keeping tabs on them
+
+#include "common/stdafx.h"
+#include "sword2/sword2.h"
+#include "sword2/defs.h"
+#include "sword2/interpreter.h"
+#include "sword2/logic.h"
+#include "sword2/resman.h"
+#include "sword2/router.h"
+
+namespace Sword2 {
+
+void Router::setStandbyCoords(int16 x, int16 y, uint8 dir) {
+ assert(dir <= 7);
+
+ _standbyX = x;
+ _standbyY = y;
+ _standbyDir = dir;
+}
+
+/**
+ * Work out direction from start to dest.
+ */
+
+// Used in whatTarget(); not valid for all megas
+#define diagonalx 36
+#define diagonaly 8
+
+int Router::whatTarget(int startX, int startY, int destX, int destY) {
+ int deltaX = destX - startX;
+ int deltaY = destY - startY;
+
+ // 7 0 1
+ // 6 2
+ // 5 4 3
+
+ // Flat route
+
+ if (ABS(deltaY) * diagonalx < ABS(deltaX) * diagonaly / 2)
+ return (deltaX > 0) ? 2 : 6;
+
+ // Vertical route
+
+ if (ABS(deltaY) * diagonalx / 2 > ABS(deltaX) * diagonaly)
+ return (deltaY > 0) ? 4 : 0;
+
+ // Diagonal route
+
+ if (deltaX > 0)
+ return (deltaY > 0) ? 3 : 1;
+
+ return (deltaY > 0) ? 5 : 7;
+}
+
+/**
+ * Walk meta to (x,y,dir). Set RESULT to 0 if it succeeded. Otherwise, set
+ * RESULT to 1. Return true if the mega has finished walking.
+ */
+
+int Router::doWalk(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, int16 target_x, int16 target_y, uint8 target_dir) {
+ ObjectLogic obLogic(ob_logic);
+ ObjectGraphic obGraph(ob_graph);
+ ObjectMega obMega(ob_mega);
+
+ // If this is the start of the walk, calculate the route.
+
+ if (obLogic.getLooping() == 0) {
+ // If we're already there, don't even bother allocating
+ // memory and calling the router, just quit back & continue
+ // the script! This avoids an embarassing mega stand frame
+ // appearing for one cycle when we're already in position for
+ // an anim eg. repeatedly clicking on same object to repeat
+ // an anim - no mega frame will appear in between runs of the
+ // anim.
+
+ if (obMega.getFeetX() == target_x && obMega.getFeetY() == target_y && obMega.getCurDir() == target_dir) {
+ _vm->_logic->writeVar(RESULT, 0);
+ return IR_CONT;
+ }
+
+ assert(target_dir <= 8);
+
+ obMega.setWalkPc(0);
+
+ // Set up mem for _walkData in route_slots[] & set mega's
+ // 'route_slot_id' accordingly
+ allocateRouteMem();
+
+ int32 route = routeFinder(ob_mega, ob_walkdata, target_x, target_y, target_dir);
+
+ // 0 = can't make route to target
+ // 1 = created route
+ // 2 = zero route but may need to turn
+
+ if (route != 1 && route != 2) {
+ freeRouteMem();
+ _vm->_logic->writeVar(RESULT, 1);
+ return IR_CONT;
+ }
+
+ // Walk is about to start
+
+ obMega.setIsWalking(1);
+ obLogic.setLooping(1);
+ obGraph.setAnimResource(obMega.getMegasetRes());
+ } else if (_vm->_logic->readVar(EXIT_FADING) && _vm->_screen->getFadeStatus() == RDFADE_BLACK) {
+ // Double clicked an exit, and the screen has faded down to
+ // black. Ok, that's it. Back to script and change screen.
+
+ // We have to clear te EXIT_CLICK_ID variable in case there's a
+ // walk instruction on the new screen, or it'd be cut short.
+
+ freeRouteMem();
+
+ obLogic.setLooping(0);
+ obMega.setIsWalking(0);
+ _vm->_logic->writeVar(EXIT_CLICK_ID, 0);
+ _vm->_logic->writeVar(RESULT, 0);
+
+ return IR_CONT;
+ }
+
+ // Get pointer to walkanim & current frame position
+
+ WalkData *walkAnim = getRouteMem();
+ int32 walk_pc = obMega.getWalkPc();
+
+ // If stopping the walk early, overwrite the next step with a
+ // slow-out, then finish
+
+ if (_vm->_logic->checkEventWaiting() && walkAnim[walk_pc].step == 0 && walkAnim[walk_pc + 1].step == 1) {
+ // At the beginning of a step
+ earlySlowOut(ob_mega, ob_walkdata);
+ }
+
+ // Get new frame of walk
+
+ obGraph.setAnimPc(walkAnim[walk_pc].frame);
+ obMega.setCurDir(walkAnim[walk_pc].dir);
+ obMega.setFeetX(walkAnim[walk_pc].x);
+ obMega.setFeetY(walkAnim[walk_pc].y);
+
+ // Is the NEXT frame is the end-marker (512) of the walk sequence?
+
+ if (walkAnim[walk_pc + 1].frame != 512) {
+ // No, it wasn't. Increment the walk-anim frame number and
+ // come back next cycle.
+ obMega.setWalkPc(obMega.getWalkPc() + 1);
+ return IR_REPEAT;
+ }
+
+ // We have reached the end-marker, which means we can return to the
+ // script just as the final (stand) frame of the walk is set.
+
+ freeRouteMem();
+ obLogic.setLooping(0);
+ obMega.setIsWalking(0);
+
+ // If George's walk has been interrupted to run a new action script for
+ // instance or Nico's walk has been interrupted by player clicking on
+ // her to talk
+
+ // There used to be code here for checking if two megas were colliding,
+ // but it had been commented out, and it was only run if a function
+ // that always returned zero returned non-zero.
+
+ if (_vm->_logic->checkEventWaiting()) {
+ _vm->_logic->startEvent();
+ _vm->_logic->writeVar(RESULT, 1);
+ return IR_TERMINATE;
+ }
+
+ _vm->_logic->writeVar(RESULT, 0);
+
+ // CONTINUE the script so that RESULT can be checked! Also, if an anim
+ // command follows the fnWalk command, the 1st frame of the anim (which
+ // is always a stand frame itself) can replace the final stand frame of
+ // the walk, to hide the slight difference between the shrinking on the
+ // mega frames and the pre-shrunk anim start-frame.
+
+ return IR_CONT;
+}
+
+/**
+ * Walk mega to start position of anim
+ */
+
+int Router::walkToAnim(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, uint32 animRes) {
+ int16 target_x = 0;
+ int16 target_y = 0;
+ uint8 target_dir = 0;
+
+ // Walkdata is needed for earlySlowOut if player clicks elsewhere
+ // during the walk.
+
+ // If this is the start of the walk, read anim file to get start coords
+
+ ObjectLogic obLogic(ob_logic);
+
+ if (obLogic.getLooping() == 0) {
+ byte *anim_file = _vm->_resman->openResource(animRes);
+ AnimHeader anim_head;
+
+ anim_head.read(_vm->fetchAnimHeader(anim_file));
+
+ target_x = anim_head.feetStartX;
+ target_y = anim_head.feetStartY;
+ target_dir = anim_head.feetStartDir;
+
+ _vm->_resman->closeResource(animRes);
+
+ // If start coords not yet set in anim header, use the standby
+ // coords (which should be set beforehand in the script).
+
+ if (target_x == 0 && target_y == 0) {
+ target_x = _standbyX;
+ target_y = _standbyY;
+ target_dir = _standbyDir;
+ }
+
+ assert(target_dir <= 7);
+ }
+
+ return doWalk(ob_logic, ob_graph, ob_mega, ob_walkdata, target_x, target_y, target_dir);
+}
+
+/**
+ * Route to the left or right hand side of target id, if possible.
+ */
+
+int Router::walkToTalkToMega(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, uint32 megaId, uint32 separation) {
+ ObjectMega obMega(ob_mega);
+
+ int16 target_x = 0;
+ int16 target_y = 0;
+ uint8 target_dir = 0;
+
+ // If this is the start of the walk, calculate the route.
+
+ ObjectLogic obLogic(ob_logic);
+
+ if (obLogic.getLooping() == 0) {
+ assert(_vm->_resman->fetchType(megaId) == GAME_OBJECT);
+
+ // Call the base script. This is the graphic/mouse service
+ // call, and will set _engineMega to the ObjectMega of mega we
+ // want to route to.
+
+ _vm->_logic->runResScript(megaId, 3);
+
+ ObjectMega targetMega(_vm->_logic->getEngineMega());
+
+ // Stand exactly beside the mega, ie. at same y-coord
+ target_y = targetMega.getFeetY();
+
+ int scale = obMega.calcScale();
+ int mega_separation = (separation * scale) / 256;
+
+ debug(4, "Target is at (%d, %d), separation %d", targetMega.getFeetX(), targetMega.getFeetY(), mega_separation);
+
+ if (targetMega.getFeetX() < obMega.getFeetX()) {
+ // Target is left of us, so aim to stand to their
+ // right. Face down_left
+
+ target_x = targetMega.getFeetX() + mega_separation;
+ target_dir = 5;
+ } else {
+ // Ok, must be right of us so aim to stand to their
+ // left. Face down_right.
+
+ target_x = targetMega.getFeetX() - mega_separation;
+ target_dir = 3;
+ }
+ }
+
+ return doWalk(ob_logic, ob_graph, ob_mega, ob_walkdata, target_x, target_y, target_dir);
+}
+/**
+ * Turn mega to the specified direction. Just needs to call doWalk() with
+ * current feet coords, so router can produce anim of turn frames.
+ */
+
+int Router::doFace(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, uint8 target_dir) {
+ int16 target_x = 0;
+ int16 target_y = 0;
+
+ // If this is the start of the turn, get the mega's current feet
+ // coords + the required direction
+
+ ObjectLogic obLogic(ob_logic);
+
+ if (obLogic.getLooping() == 0) {
+ assert(target_dir <= 7);
+
+ ObjectMega obMega(ob_mega);
+
+ target_x = obMega.getFeetX();
+ target_y = obMega.getFeetY();
+ }
+
+ return doWalk(ob_logic, ob_graph, ob_mega, ob_walkdata, target_x, target_y, target_dir);
+}
+
+/**
+ * Turn mega to face point (x,y) on the floor
+ */
+
+int Router::faceXY(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, int16 target_x, int16 target_y) {
+ uint8 target_dir = 0;
+
+ // If this is the start of the turn, get the mega's current feet
+ // coords + the required direction
+
+ ObjectLogic obLogic(ob_logic);
+
+ if (obLogic.getLooping() == 0) {
+ ObjectMega obMega(ob_mega);
+
+ target_dir = whatTarget(obMega.getFeetX(), obMega.getFeetY(), target_x, target_y);
+ }
+
+ return doFace(ob_logic, ob_graph, ob_mega, ob_walkdata, target_dir);
+}
+
+/**
+ * Turn mega to face another mega.
+ */
+
+int Router::faceMega(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, uint32 megaId) {
+ uint8 target_dir = 0;
+
+ // If this is the start of the walk, decide where to walk to.
+
+ ObjectLogic obLogic(ob_logic);
+
+ if (obLogic.getLooping() == 0) {
+ assert(_vm->_resman->fetchType(megaId) == GAME_OBJECT);
+
+ // Call the base script. This is the graphic/mouse service
+ // call, and will set _engineMega to the ObjectMega of mega we
+ // want to turn to face.
+
+ _vm->_logic->runResScript(megaId, 3);
+
+ ObjectMega obMega(ob_mega);
+ ObjectMega targetMega(_vm->_logic->getEngineMega());
+
+ target_dir = whatTarget(obMega.getFeetX(), obMega.getFeetY(), targetMega.getFeetX(), targetMega.getFeetY());
+ }
+
+ return doFace(ob_logic, ob_graph, ob_mega, ob_walkdata, target_dir);
+}
+
+/**
+ * Stand mega at (x,y,dir)
+ * Sets up the graphic object, but also needs to set the new 'current_dir' in
+ * the mega object, so the router knows in future
+ */
+
+void Router::standAt(byte *ob_graph, byte *ob_mega, int32 x, int32 y, int32 dir) {
+ assert(dir >= 0 && dir <= 7);
+
+ ObjectGraphic obGraph(ob_graph);
+ ObjectMega obMega(ob_mega);
+
+ // Set up the stand frame & set the mega's new direction
+
+ obMega.setFeetX(x);
+ obMega.setFeetY(y);
+ obMega.setCurDir(dir);
+
+ // Mega-set animation file
+ obGraph.setAnimResource(obMega.getMegasetRes());
+
+ // Dir + first stand frame (always frame 96)
+ obGraph.setAnimPc(dir + 96);
+}
+
+/**
+ * Stand mega at end position of anim
+ */
+
+void Router::standAfterAnim(byte *ob_graph, byte *ob_mega, uint32 animRes) {
+ byte *anim_file = _vm->_resman->openResource(animRes);
+ AnimHeader anim_head;
+
+ anim_head.read(_vm->fetchAnimHeader(anim_file));
+
+ int32 x = anim_head.feetEndX;
+ int32 y = anim_head.feetEndY;
+ int32 dir = anim_head.feetEndDir;
+
+ _vm->_resman->closeResource(animRes);
+
+ // If start coords not available either use the standby coords (which
+ // should be set beforehand in the script)
+
+ if (x == 0 && y == 0) {
+ x = _standbyX;
+ y = _standbyY;
+ dir = _standbyDir;
+ }
+
+ standAt(ob_graph, ob_mega, x, y, dir);
+}
+
+void Router::standAtAnim(byte *ob_graph, byte *ob_mega, uint32 animRes) {
+ byte *anim_file = _vm->_resman->openResource(animRes);
+ AnimHeader anim_head;
+
+ anim_head.read(_vm->fetchAnimHeader(anim_file));
+
+ int32 x = anim_head.feetStartX;
+ int32 y = anim_head.feetStartY;
+ int32 dir = anim_head.feetStartDir;
+
+ _vm->_resman->closeResource(animRes);
+
+ // If start coords not available use the standby coords (which should
+ // be set beforehand in the script)
+
+ if (x == 0 && y == 0) {
+ x = _standbyX;
+ y = _standbyY;
+ dir = _standbyDir;
+ }
+
+ standAt(ob_graph, ob_mega, x, y, dir);
+}
+
+} // End of namespace Sword2