diff options
Diffstat (limited to 'engines/kyra')
31 files changed, 18207 insertions, 0 deletions
diff --git a/engines/kyra/animator.cpp b/engines/kyra/animator.cpp new file mode 100644 index 0000000000..e5c5c431c8 --- /dev/null +++ b/engines/kyra/animator.cpp @@ -0,0 +1,691 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2005-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 "kyra/kyra.h" +#include "kyra/screen.h" +#include "kyra/animator.h" +#include "kyra/sprites.h" + +#include "common/system.h" + +namespace Kyra { +ScreenAnimator::ScreenAnimator(KyraEngine *vm, OSystem *system) { + _vm = vm; + _screen = vm->screen(); + _initOk = false; + _updateScreen = false; + _system = system; + _screenObjects = _actors = _items = _sprites = _objectQueue = 0; + _noDrawShapesFlag = 0; +} + +ScreenAnimator::~ScreenAnimator() { + close(); +} + +void ScreenAnimator::init(int actors_, int items_, int sprites_) { + debug(9, "ScreenAnimator::init(%d, %d, %d)", actors_, items_, sprites_); + _screenObjects = new AnimObject[actors_ + items_ + sprites_]; + assert(_screenObjects); + memset(_screenObjects, 0, sizeof(AnimObject) * (actors_ + items_ + sprites_)); + _actors = _screenObjects; + _sprites = &_screenObjects[actors_]; + _items = &_screenObjects[actors_ + items_]; + _brandonDrawFrame = 113; + + _initOk = true; +} + +void ScreenAnimator::close() { + debug(9, "ScreenAnimator::close()"); + if (_initOk) { + _initOk = false; + delete [] _screenObjects; + _screenObjects = _actors = _items = _sprites = _objectQueue = 0; + } +} + +void ScreenAnimator::initAnimStateList() { + AnimObject *animStates = _screenObjects; + animStates[0].index = 0; + animStates[0].active = 1; + animStates[0].flags = 0x800; + animStates[0].background = _vm->_shapes[2]; + animStates[0].rectSize = _screen->getRectSize(4, 48); + animStates[0].width = 4; + animStates[0].height = 48; + animStates[0].width2 = 4; + animStates[0].height2 = 3; + + for (int i = 1; i <= 4; ++i) { + animStates[i].index = i; + animStates[i].active = 0; + animStates[i].flags = 0x800; + animStates[i].background = _vm->_shapes[3]; + animStates[i].rectSize = _screen->getRectSize(4, 64); + animStates[i].width = 4; + animStates[i].height = 48; + animStates[i].width2 = 4; + animStates[i].height2 = 3; + } + + for (int i = 5; i < 16; ++i) { + animStates[i].index = i; + animStates[i].active = 0; + animStates[i].flags = 0; + } + + for (int i = 16; i < 28; ++i) { + animStates[i].index = i; + animStates[i].flags = 0; + animStates[i].background = _vm->_shapes[349+i]; + animStates[i].rectSize = _screen->getRectSize(3, 24); + animStates[i].width = 3; + animStates[i].height = 16; + animStates[i].width2 = 0; + animStates[i].height2 = 0; + } +} + +void ScreenAnimator::preserveAllBackgrounds() { + debug(9, "ScreenAnimator::preserveAllBackgrounds()"); + uint8 curPage = _screen->_curPage; + _screen->_curPage = 2; + + AnimObject *curObject = _objectQueue; + while (curObject) { + if (curObject->active && !curObject->disable) { + preserveOrRestoreBackground(curObject, false); + curObject->bkgdChangeFlag = 0; + } + curObject = curObject->nextAnimObject; + } + _screen->_curPage = curPage; +} + +void ScreenAnimator::flagAllObjectsForBkgdChange() { + debug(9, "ScreenAnimator::flagAllObjectsForBkgdChange()"); + AnimObject *curObject = _objectQueue; + while (curObject) { + curObject->bkgdChangeFlag = 1; + curObject = curObject->nextAnimObject; + } +} + +void ScreenAnimator::flagAllObjectsForRefresh() { + debug(9, "ScreenAnimator::flagAllObjectsForRefresh()"); + AnimObject *curObject = _objectQueue; + while (curObject) { + curObject->refreshFlag = 1; + curObject = curObject->nextAnimObject; + } +} + +void ScreenAnimator::restoreAllObjectBackgrounds() { + debug(9, "ScreenAnimator::restoreAllObjectBackground()"); + AnimObject *curObject = _objectQueue; + _screen->_curPage = 2; + + while (curObject) { + if (curObject->active && !curObject->disable) { + preserveOrRestoreBackground(curObject, true); + curObject->x2 = curObject->x1; + curObject->y2 = curObject->y1; + } + curObject = curObject->nextAnimObject; + } + + _screen->_curPage = 0; +} + +void ScreenAnimator::preserveAnyChangedBackgrounds() { + debug(9, "ScreenAnimator::preserveAnyChangedBackgrounds()"); + AnimObject *curObject = _objectQueue; + _screen->_curPage = 2; + + while (curObject) { + if (curObject->active && !curObject->disable && curObject->bkgdChangeFlag) { + preserveOrRestoreBackground(curObject, false); + curObject->bkgdChangeFlag = 0; + } + curObject = curObject->nextAnimObject; + } + + _screen->_curPage = 0; +} + +void ScreenAnimator::preserveOrRestoreBackground(AnimObject *obj, bool restore) { + debug(9, "ScreenAnimator::preserveOrRestoreBackground(0x%X, restore)", obj, restore); + int x = 0, y = 0, width = obj->width << 3, height = obj->height; + + if (restore) { + x = obj->x2; + y = obj->y2; + } else { + x = obj->x1; + y = obj->y1; + } + + if (x < 0) + x = 0; + if (y < 0) + y = 0; + + int temp; + + temp = x + width; + if (temp >= 319) { + x = 319 - width; + } + temp = y + height; + if (temp >= 136) { + y = 136 - height; + } + + if (restore) { + _screen->copyBlockToPage(_screen->_curPage, x, y, width, height, obj->background); + } else { + _screen->copyRegionToBuffer(_screen->_curPage, x, y, width, height, obj->background); + } +} + +void ScreenAnimator::prepDrawAllObjects() { + debug(9, "ScreenAnimator::prepDrawAllObjects()"); + AnimObject *curObject = _objectQueue; + int drawPage = 2; + int flagUnk1 = 0, flagUnk2 = 0, flagUnk3 = 0; + if (_noDrawShapesFlag) + return; + if (_vm->_brandonStatusBit & 0x20) + flagUnk1 = 0x200; + if (_vm->_brandonStatusBit & 0x40) + flagUnk2 = 0x4000; + + while (curObject) { + if (curObject->active) { + int xpos = curObject->x1; + int ypos = curObject->y1; + + int drawLayer = 0; + if (!(curObject->flags & 0x800)) { + drawLayer = 7; + } else if (curObject->disable) { + drawLayer = 0; + } else { + drawLayer = _vm->_sprites->getDrawLayer(curObject->drawY); + } + + // talking head functionallity + if (_vm->_talkingCharNum != -1) { + const int16 baseAnimFrameTable1[] = { 0x11, 0x35, 0x59, 0x00, 0x00, 0x00 }; + const int16 baseAnimFrameTable2[] = { 0x15, 0x39, 0x5D, 0x00, 0x00, 0x00 }; + const int8 xOffsetTable1[] = { 2, 4, 0, 5, 2, 0, 0, 0 }; + const int8 xOffsetTable2[] = { 6, 4, 8, 3, 6, 0, 0, 0 }; + const int8 yOffsetTable1[] = { 0, 8, 1, 1, 0, 0, 0, 0 }; + const int8 yOffsetTable2[] = { 0, 8, 1, 1, 0, 0, 0, 0 }; + if (curObject->index == 0 || curObject->index <= 4) { + int shapesIndex = 0; + if (curObject->index == _vm->_charSayUnk3) { + shapesIndex = _vm->_currHeadShape + baseAnimFrameTable1[curObject->index]; + } else { + shapesIndex = baseAnimFrameTable2[curObject->index]; + int temp2 = 0; + if (curObject->index == 2) { + if (_vm->_characterList[2].sceneId == 77 || _vm->_characterList[2].sceneId == 86) { + temp2 = 1; + } else { + temp2 = 0; + } + } else { + temp2 = 1; + } + + if (!temp2) { + shapesIndex = -1; + } + } + + xpos = curObject->x1; + ypos = curObject->y1; + + int tempX = 0, tempY = 0; + if (curObject->flags & 0x1) { + tempX = (xOffsetTable1[curObject->index] * _brandonScaleX) >> 8; + tempY = yOffsetTable1[curObject->index]; + } else { + tempX = (xOffsetTable2[curObject->index] * _brandonScaleX) >> 8; + tempY = yOffsetTable2[curObject->index]; + } + tempY = (tempY * _brandonScaleY) >> 8; + xpos += tempX; + ypos += tempY; + + if (_vm->_scaleMode && _brandonScaleX != 256) { + ++xpos; + } + + if (curObject->index == 0 && shapesIndex != -1) { + if (!(_vm->_brandonStatusBit & 2)) { + flagUnk3 = 0x100; + if ((flagUnk1 & 0x200) || (flagUnk2 & 0x4000)) { + flagUnk3 = 0; + } + + int tempFlags = 0; + if (flagUnk3 & 0x100) { + tempFlags = curObject->flags & 1; + tempFlags |= 0x800 | flagUnk1 | 0x100; + } + + if (!(flagUnk3 & 0x100) && (flagUnk2 & 0x4000)) { + tempFlags = curObject->flags & 1; + tempFlags |= 0x900 | flagUnk1 | 0x4000; + _screen->drawShape(drawPage, _vm->_shapes[4+shapesIndex], xpos, ypos, 2, tempFlags | 4, _vm->_brandonPoisonFlagsGFX, int(1), int(_vm->_brandonInvFlag), drawLayer, _brandonScaleX, _brandonScaleY); + } else { + if (!(flagUnk2 & 0x4000)) { + tempFlags = curObject->flags & 1; + tempFlags |= 0x900 | flagUnk1; + } + + _screen->drawShape(drawPage, _vm->_shapes[4+shapesIndex], xpos, ypos, 2, tempFlags | 4, _vm->_brandonPoisonFlagsGFX, int(1), drawLayer, _brandonScaleX, _brandonScaleY); + } + } + } else { + if (shapesIndex != -1) { + int tempFlags = 0; + if (curObject->flags & 1) { + tempFlags = 1; + } + _screen->drawShape(drawPage, _vm->_shapes[4+shapesIndex], xpos, ypos, 2, tempFlags | 0x800, drawLayer); + } + } + } + } + + xpos = curObject->x1; + ypos = curObject->y1; + + curObject->flags |= 0x800; + if (curObject->index == 0) { + flagUnk3 = 0x100; + + if (flagUnk1 & 0x200 || flagUnk2 & 0x4000) { + flagUnk3 = 0; + } + + if (_vm->_brandonStatusBit & 2) { + curObject->flags &= 0xFFFFFFFE; + } + + if (!_vm->_scaleMode) { + if (flagUnk3 & 0x100) { + _screen->drawShape(drawPage, curObject->sceneAnimPtr, xpos, ypos, 2, curObject->flags | flagUnk1 | 0x100, (uint8*)_vm->_brandonPoisonFlagsGFX, int(1), drawLayer); + } else if (flagUnk3 & 0x4000) { + _screen->drawShape(drawPage, curObject->sceneAnimPtr, xpos, ypos, 2, curObject->flags | flagUnk1 | 0x4000, int(_vm->_brandonInvFlag), drawLayer); + } else { + _screen->drawShape(drawPage, curObject->sceneAnimPtr, xpos, ypos, 2, curObject->flags | flagUnk1, drawLayer); + } + } else { + if (flagUnk3 & 0x100) { + _screen->drawShape(drawPage, curObject->sceneAnimPtr, xpos, ypos, 2, curObject->flags | flagUnk1 | 0x104, (uint8*)_vm->_brandonPoisonFlagsGFX, int(1), drawLayer, _brandonScaleX, _brandonScaleY); + } else if (flagUnk3 & 0x4000) { + _screen->drawShape(drawPage, curObject->sceneAnimPtr, xpos, ypos, 2, curObject->flags | flagUnk1 | 0x4004, int(_vm->_brandonInvFlag), drawLayer, _brandonScaleX, _brandonScaleY); + } else { + _screen->drawShape(drawPage, curObject->sceneAnimPtr, xpos, ypos, 2, curObject->flags | flagUnk1 | 0x4, drawLayer, _brandonScaleX, _brandonScaleY); + } + } + } else { + if (curObject->index >= 16 && curObject->index <= 27) { + _screen->drawShape(drawPage, curObject->sceneAnimPtr, xpos, ypos, 2, curObject->flags | 4, drawLayer, (int)_vm->_scaleTable[curObject->drawY], (int)_vm->_scaleTable[curObject->drawY]); + } else { + _screen->drawShape(drawPage, curObject->sceneAnimPtr, xpos, ypos, 2, curObject->flags, drawLayer); + } + } + } + curObject = curObject->nextAnimObject; + } +} + +void ScreenAnimator::copyChangedObjectsForward(int refreshFlag) { + debug(9, "ScreenAnimator::copyChangedObjectsForward(%d)", refreshFlag); + AnimObject *curObject = _objectQueue; + + while (curObject) { + if (curObject->active) { + if (curObject->refreshFlag || refreshFlag) { + int xpos = 0, ypos = 0, width = 0, height = 0; + xpos = curObject->x1 - (curObject->width2+1); + ypos = curObject->y1 - curObject->height2; + width = (curObject->width + ((curObject->width2)>>3)+1)<<3; + height = curObject->height + curObject->height2*2; + + if (xpos < 8) { + xpos = 8; + } else if (xpos + width > 312) { + width = 312 - xpos; + } + + if (ypos < 8) { + ypos = 8; + } else if (ypos + height > 136) { + height = 136 - ypos; + } + + _screen->copyRegion(xpos, ypos, xpos, ypos, width, height, 2, 0, Screen::CR_CLIPPED); + curObject->refreshFlag = 0; + _updateScreen = true; + } + } + curObject = curObject->nextAnimObject; + } + if (_updateScreen) { + _screen->updateScreen(); + _updateScreen = false; + } +} + +void ScreenAnimator::updateAllObjectShapes() { + debug(9, "ScreenAnimator::updateAllObjectShapes()"); + restoreAllObjectBackgrounds(); + preserveAnyChangedBackgrounds(); + prepDrawAllObjects(); + copyChangedObjectsForward(0); +} + +void ScreenAnimator::animRemoveGameItem(int index) { + debug(9, "ScreenAnimator::animRemoveGameItem(%d)", index); + restoreAllObjectBackgrounds(); + + AnimObject *animObj = &_items[index]; + animObj->sceneAnimPtr = 0; + animObj->animFrameNumber = -1; + animObj->refreshFlag = 1; + animObj->bkgdChangeFlag = 1; + updateAllObjectShapes(); + animObj->active = 0; + + objectRemoveQueue(_objectQueue, animObj); +} + +void ScreenAnimator::animAddGameItem(int index, uint16 sceneId) { + debug(9, "ScreenAnimator::animRemoveGameItem(%d, %d)", index, sceneId); + restoreAllObjectBackgrounds(); + assert(sceneId < _vm->_roomTableSize); + Room *currentRoom = &_vm->_roomTable[sceneId]; + AnimObject *animObj = &_items[index]; + animObj->active = 1; + animObj->refreshFlag = 1; + animObj->bkgdChangeFlag = 1; + animObj->drawY = currentRoom->itemsYPos[index]; + animObj->sceneAnimPtr = _vm->_shapes[220+currentRoom->itemsTable[index]]; + animObj->animFrameNumber = -1; + animObj->x1 = currentRoom->itemsXPos[index]; + animObj->y1 = currentRoom->itemsYPos[index]; + animObj->x1 -= fetchAnimWidth(animObj->sceneAnimPtr, _vm->_scaleTable[animObj->drawY]) >> 1; + animObj->y1 -= fetchAnimHeight(animObj->sceneAnimPtr, _vm->_scaleTable[animObj->drawY]); + animObj->x2 = animObj->x1; + animObj->y2 = animObj->y1; + animObj->width2 = 0; + animObj->height2 = 0; + _objectQueue = objectQueue(_objectQueue, animObj); + preserveAnyChangedBackgrounds(); + animObj->refreshFlag = 1; + animObj->bkgdChangeFlag = 1; +} + +void ScreenAnimator::animAddNPC(int character) { + debug(9, "ScreenAnimator::animAddNPC(%d)", character); + restoreAllObjectBackgrounds(); + AnimObject *animObj = &_actors[character]; + const Character *ch = &_vm->_characterList[character]; + + animObj->active = 1; + animObj->refreshFlag = 1; + animObj->bkgdChangeFlag = 1; + animObj->drawY = ch->y1; + animObj->sceneAnimPtr = _vm->_shapes[4+ch->currentAnimFrame]; + animObj->x1 = animObj->x2 = ch->x1 + _vm->_defaultShapeTable[ch->currentAnimFrame-7].xOffset; + animObj->y1 = animObj->y2 = ch->y1 + _vm->_defaultShapeTable[ch->currentAnimFrame-7].yOffset; + if (ch->facing >= 1 && ch->facing <= 3) { + animObj->flags |= 1; + } else if (ch->facing >= 5 && ch->facing <= 7) { + animObj->flags &= 0xFFFFFFFE; + } + _objectQueue = objectQueue(_objectQueue, animObj); + preserveAnyChangedBackgrounds(); + animObj->refreshFlag = 1; + animObj->bkgdChangeFlag = 1; +} + +AnimObject *ScreenAnimator::objectRemoveQueue(AnimObject *queue, AnimObject *rem) { + debug(9, "ScreenAnimator::objectRemoveQueue(0x%X, 0x%X)", queue, rem); + AnimObject *cur = queue; + AnimObject *prev = queue; + + while (cur != rem && cur) { + AnimObject *temp = cur->nextAnimObject; + if (!temp) + break; + prev = cur; + cur = temp; + } + + if (cur == queue) { + if (!cur) + return 0; + return cur->nextAnimObject; + } + + if (!cur->nextAnimObject) { + if (cur == rem) { + if (!prev) { + return 0; + } else { + prev->nextAnimObject = 0; + } + } + } else { + if (cur == rem) { + prev->nextAnimObject = rem->nextAnimObject; + } + } + + return queue; +} + +AnimObject *ScreenAnimator::objectAddHead(AnimObject *queue, AnimObject *head) { + debug(9, "ScreenAnimator::objectAddHead(0x%X, 0x%X)", queue, head); + head->nextAnimObject = queue; + return head; +} + +AnimObject *ScreenAnimator::objectQueue(AnimObject *queue, AnimObject *add) { + debug(9, "ScreenAnimator::objectQueue(0x%X, 0x%X)", queue, add); + if (add->drawY <= queue->drawY || !queue) { + add->nextAnimObject = queue; + return add; + } + AnimObject *cur = queue; + AnimObject *prev = queue; + while (add->drawY > cur->drawY) { + AnimObject *temp = cur->nextAnimObject; + if (!temp) + break; + prev = cur; + cur = temp; + } + + if (add->drawY <= cur->drawY) { + prev->nextAnimObject = add; + add->nextAnimObject = cur; + } else { + cur->nextAnimObject = add; + add->nextAnimObject = 0; + } + return queue; +} + +void ScreenAnimator::addObjectToQueue(AnimObject *object) { + debug(9, "ScreenAnimator::addObjectToQueue(0x%X)", object); + if (!_objectQueue) { + _objectQueue = objectAddHead(0, object); + } else { + _objectQueue = objectQueue(_objectQueue, object); + } +} + +void ScreenAnimator::refreshObject(AnimObject *object) { + debug(9, "ScreenAnimator::refreshObject(0x%X)", object); + _objectQueue = objectRemoveQueue(_objectQueue, object); + if (_objectQueue) { + _objectQueue = objectQueue(_objectQueue, object); + } else { + _objectQueue = objectAddHead(0, object); + } +} + +void ScreenAnimator::makeBrandonFaceMouse() { + debug(9, "ScreenAnimator::makeBrandonFaceMouse()"); + if (_vm->mouseX() >= _vm->_currentCharacter->x1) { + _vm->_currentCharacter->facing = 3; + } else { + _vm->_currentCharacter->facing = 5; + } + animRefreshNPC(0); + updateAllObjectShapes(); +} + +int16 ScreenAnimator::fetchAnimWidth(const uint8 *shape, int16 mult) { + debug(9, "ScreenAnimator::fetchAnimWidth(0x%X, %d)", shape, mult); + if (_vm->features() & GF_TALKIE) + shape += 2; + return (((int16)READ_LE_UINT16((shape+3))) * mult) >> 8; +} + +int16 ScreenAnimator::fetchAnimHeight(const uint8 *shape, int16 mult) { + debug(9, "ScreenAnimator::fetchAnimHeight(0x%X, %d)", shape, mult); + if (_vm->features() & GF_TALKIE) + shape += 2; + return (int16)(((int8)*(shape+2)) * mult) >> 8; +} + +void ScreenAnimator::setBrandonAnimSeqSize(int width, int height) { + debug(9, "ScreenAnimator::setBrandonAnimSeqSize(%d, %d)", width, height); + restoreAllObjectBackgrounds(); + _brandonAnimSeqSizeWidth = _actors[0].width; + _brandonAnimSeqSizeHeight = _actors[0].height; + _actors[0].width = width + 1; + _actors[0].height = height; + preserveAllBackgrounds(); +} + +void ScreenAnimator::resetBrandonAnimSeqSize() { + debug(9, "ScreenAnimator::resetBrandonAnimSeqSize()"); + restoreAllObjectBackgrounds(); + _actors[0].width = _brandonAnimSeqSizeWidth; + _actors[0].height = _brandonAnimSeqSizeHeight; + preserveAllBackgrounds(); +} + +void ScreenAnimator::animRefreshNPC(int character) { + debug(9, "ScreenAnimator::animRefreshNPC(%d)", character); + AnimObject *animObj = &_actors[character]; + Character *ch = &_vm->characterList()[character]; + + animObj->refreshFlag = 1; + animObj->bkgdChangeFlag = 1; + int facing = ch->facing; + if (facing >= 1 && facing <= 3) { + animObj->flags |= 1; + } else if (facing >= 5 && facing <= 7) { + animObj->flags &= 0xFFFFFFFE; + } + + animObj->drawY = ch->y1; + animObj->sceneAnimPtr = _vm->shapes()[4+ch->currentAnimFrame]; + animObj->animFrameNumber = ch->currentAnimFrame; + if (character == 0) { + if (_vm->brandonStatus() & 10) { + animObj->animFrameNumber = 88; + ch->currentAnimFrame = 88; + } + if (_vm->brandonStatus() & 2) { + animObj->animFrameNumber = _brandonDrawFrame; + ch->currentAnimFrame = _brandonDrawFrame; + animObj->sceneAnimPtr = _vm->shapes()[4+_brandonDrawFrame]; + if (_vm->_brandonStatusBit0x02Flag) { + ++_brandonDrawFrame; + if (_brandonDrawFrame >= 122) + _brandonDrawFrame = 113; + _vm->_brandonStatusBit0x02Flag = 0; + } + } + } + + int xOffset = _vm->_defaultShapeTable[ch->currentAnimFrame-7].xOffset; + int yOffset = _vm->_defaultShapeTable[ch->currentAnimFrame-7].yOffset; + + if (_vm->_scaleMode) { + animObj->x1 = ch->x1; + animObj->y1 = ch->y1; + + _brandonScaleX = _vm->_scaleTable[ch->y1]; + _brandonScaleY = _vm->_scaleTable[ch->y1]; + + animObj->x1 += (_brandonScaleX * xOffset) >> 8; + animObj->y1 += (_brandonScaleY * yOffset) >> 8; + } else { + animObj->x1 = ch->x1 + xOffset; + animObj->y1 = ch->y1 + yOffset; + } + animObj->width2 = 4; + animObj->height2 = 3; + + refreshObject(animObj); +} + +void ScreenAnimator::setCharacterDefaultFrame(int character) { + debug(9, "ScreenAnimator::setCharacterDefaultFrame()"); + static uint16 initFrameTable[] = { + 7, 41, 77, 0, 0 + }; + assert(character < ARRAYSIZE(initFrameTable)); + Character *edit = &_vm->characterList()[character]; + edit->sceneId = 0xFFFF; + edit->facing = 0; + edit->currentAnimFrame = initFrameTable[character]; + // edit->unk6 = 1; +} + +void ScreenAnimator::setCharactersHeight() { + debug(9, "ScreenAnimator::setCharactersHeight()"); + static int8 initHeightTable[] = { + 48, 40, 48, 47, 56, + 44, 42, 47, 38, 35, + 40 + }; + for (int i = 0; i < 11; ++i) { + _vm->characterList()[i].height = initHeightTable[i]; + } +} + +} // end of namespace Kyra diff --git a/engines/kyra/animator.h b/engines/kyra/animator.h new file mode 100644 index 0000000000..ecc80d1819 --- /dev/null +++ b/engines/kyra/animator.h @@ -0,0 +1,126 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2005-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 KYRAANIMATOR_H +#define KYRAANIMATOR_H + +namespace Kyra { +class KyraEngine; +class Screen; + +struct AnimObject { + uint8 index; + uint32 active; + uint32 refreshFlag; + uint32 bkgdChangeFlag; + bool disable; + uint32 flags; + int16 drawY; + uint8 *sceneAnimPtr; + int16 animFrameNumber; + uint8 *background; + uint16 rectSize; + int16 x1, y1; + int16 x2, y2; + uint16 width; + uint16 height; + uint16 width2; + uint16 height2; + AnimObject *nextAnimObject; +}; + +class ScreenAnimator { +public: + ScreenAnimator(KyraEngine *vm, OSystem *system); + virtual ~ScreenAnimator(); + + operator bool() const { return _initOk; } + + void init(int actors, int items, int sprites); + void close(); + + AnimObject *objects() { return _screenObjects; } + AnimObject *actors() { return _actors; } + AnimObject *items() { return _items; } + AnimObject *sprites() { return _sprites; } + + void initAnimStateList(); + void preserveAllBackgrounds(); + void flagAllObjectsForBkgdChange(); + void flagAllObjectsForRefresh(); + void restoreAllObjectBackgrounds(); + void preserveAnyChangedBackgrounds(); + virtual void prepDrawAllObjects(); + void copyChangedObjectsForward(int refreshFlag); + + void updateAllObjectShapes(); + void animRemoveGameItem(int index); + void animAddGameItem(int index, uint16 sceneId); + void animAddNPC(int character); + void animRefreshNPC(int character); + + void clearQueue() { _objectQueue = 0; } + void addObjectToQueue(AnimObject *object); + void refreshObject(AnimObject *object); + + void makeBrandonFaceMouse(); + void setBrandonAnimSeqSize(int width, int height); + void resetBrandonAnimSeqSize(); + void setCharacterDefaultFrame(int character); + void setCharactersHeight(); + + int16 fetchAnimWidth(const uint8 *shape, int16 mult); + int16 fetchAnimHeight(const uint8 *shape, int16 mult); + + int _noDrawShapesFlag; + bool _updateScreen; + uint16 _brandonDrawFrame; + int _brandonScaleX; + int _brandonScaleY; + +protected: + KyraEngine *_vm; + Screen *_screen; + OSystem *_system; + bool _initOk; + + AnimObject *_screenObjects; + + AnimObject *_actors; + AnimObject *_items; + AnimObject *_sprites; + + AnimObject *objectRemoveQueue(AnimObject *queue, AnimObject *rem); + AnimObject *objectAddHead(AnimObject *queue, AnimObject *head); + AnimObject *objectQueue(AnimObject *queue, AnimObject *add); + + void preserveOrRestoreBackground(AnimObject *obj, bool restore); + + AnimObject *_objectQueue; + + int _brandonAnimSeqSizeWidth; + int _brandonAnimSeqSizeHeight; + +}; +} // end of namespace Kyra + +#endif diff --git a/engines/kyra/debugger.cpp b/engines/kyra/debugger.cpp new file mode 100644 index 0000000000..b78056e3ad --- /dev/null +++ b/engines/kyra/debugger.cpp @@ -0,0 +1,206 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2005-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/config-manager.h" +#include "common/debugger.cpp" +#include "kyra/debugger.h" +#include "kyra/kyra.h" +#include "kyra/screen.h" + +namespace Kyra { + +Debugger::Debugger(KyraEngine *vm) + : Common::Debugger<Debugger>() { + _vm = vm; + + DCmd_Register("continue", &Debugger::cmd_exit); + DCmd_Register("exit", &Debugger::cmd_exit); + DCmd_Register("help", &Debugger::cmd_help); + DCmd_Register("quit", &Debugger::cmd_exit); + DCmd_Register("enter", &Debugger::cmd_enterRoom); + DCmd_Register("rooms", &Debugger::cmd_listRooms); + DCmd_Register("flags", &Debugger::cmd_listFlags); + DCmd_Register("toggleflag", &Debugger::cmd_toggleFlag); + DCmd_Register("queryflag", &Debugger::cmd_queryFlag); + DCmd_Register("timers", &Debugger::cmd_listTimers); + DCmd_Register("settimercountdown", &Debugger::cmd_setTimerCountdown); + DCmd_Register("give", &Debugger::cmd_giveItem); +} + +void Debugger::preEnter() { + //_vm->midi.pause(1); +} + +void Debugger::postEnter() { + //_vm->midi.pause(0); +} + +bool Debugger::cmd_enterRoom(int argc, const char **argv) { + uint direction = 0; + if (argc > 1) { + int room = atoi(argv[1]); + + // game will crash if entering a non-existent room + if (room >= _vm->_roomTableSize) { + DebugPrintf("room number must be any value between (including) 0 and %d\n", _vm->_roomTableSize-1); + return true; + } + + if (argc > 2) { + direction = atoi(argv[2]); + } else { + if (_vm->_roomTable[room].northExit != 0xff) + direction = 3; + else if (_vm->_roomTable[room].eastExit != 0xff) + direction = 4; + else if (_vm->_roomTable[room].southExit != 0xff) + direction = 1; + else if (_vm->_roomTable[room].westExit != 0xff) + direction = 2; + } + + // Dirty way of hiding the debug console while the room entry scripts are running, + // otherwise the graphics didn't update. + _vm->_system->hideOverlay(); + _vm->_currentCharacter->facing = direction; + + _vm->enterNewScene(room, _vm->_currentCharacter->facing, 0, 0, 1); + _vm->_system->showOverlay(); + _vm->_screen->_mouseLockCount = 0; + + _detach_now = true; + return false; + } + + DebugPrintf("Syntax: room <roomnum> <direction>\n"); + return true; +} + +bool Debugger::cmd_exit(int argc, const char **argv) { + _detach_now = true; + return false; +} + +bool Debugger::cmd_help(int argc, const char **argv) { + // console normally has 39 line width + // wrap around nicely + int width = 0, size, i; + + DebugPrintf("Commands are:\n"); + for (i = 0 ; i < _dcmd_count ; i++) { + size = strlen(_dcmds[i].name) + 1; + + if ((width + size) >= 39) { + DebugPrintf("\n"); + width = size; + } else + width += size; + + DebugPrintf("%s ", _dcmds[i].name); + } + DebugPrintf("\n"); + return true; +} + +bool Debugger::cmd_listRooms(int argc, const char **argv) { + for (int i = 0; i < _vm->_roomTableSize; i++) { + DebugPrintf("%-3i: %-10s", i, _vm->_roomFilenameTable[_vm->_roomTable[i].nameIndex]); + if (!(i % 8)) + DebugPrintf("\n"); + } + DebugPrintf("\n"); + DebugPrintf("Current room: %i\n", _vm->_currentRoom); + return true; +} + +bool Debugger::cmd_listFlags(int argc, const char **argv) { + for (int i = 0; i < (int)sizeof(_vm->_flagsTable)*8; i++) { + DebugPrintf("(%-3i): %-5i", i, _vm->queryGameFlag(i)); + if (!(i % 10)) + DebugPrintf("\n"); + } + DebugPrintf("\n"); + return true; +} + +bool Debugger::cmd_toggleFlag(int argc, const char **argv) { + if (argc > 1) { + uint flag = atoi(argv[1]); + if (_vm->queryGameFlag(flag)) + _vm->resetGameFlag(flag); + else + _vm->setGameFlag(flag); + DebugPrintf("Flag %i is now %i\n", flag, _vm->queryGameFlag(flag)); + } else + DebugPrintf("Syntax: toggleflag <flag>\n"); + + return true; +} + +bool Debugger::cmd_queryFlag(int argc, const char **argv) { + if (argc > 1) { + uint flag = atoi(argv[1]); + DebugPrintf("Flag %i is %i\n", flag, _vm->queryGameFlag(flag)); + } else + DebugPrintf("Syntax: queryflag <flag>\n"); + + return true; +} + +bool Debugger::cmd_listTimers(int argc, const char **argv) { + for (int i = 0; i < ARRAYSIZE(_vm->_timers); i++) + DebugPrintf("Timer %-2i: Active: %-3s Countdown: %-6i\n", i, _vm->_timers[i].active ? "Yes" : "No", _vm->_timers[i].countdown); + + return true; +} + +bool Debugger::cmd_setTimerCountdown(int argc, const char **argv) { + if (argc > 2) { + uint timer = atoi(argv[1]); + uint countdown = atoi(argv[2]); + _vm->setTimerCountdown(timer, countdown); + DebugPrintf("Timer %i now has countdown %i\n", timer, _vm->_timers[timer].countdown); + } else + DebugPrintf("Syntax: settimercountdown <timer> <countdown>\n"); + + return true; +} + +bool Debugger::cmd_giveItem(int argc, const char **argv) { + if (argc == 2) { + int item = atoi(argv[1]); + + // Kyrandia 1 has only 108 items (-1 to 106), otherwise it will crash + if (item < -1 || item > 106) { + DebugPrintf("itemid must be any value between (including) -1 and 106\n"); + return true; + } + + _vm->setMouseItem(item); + _vm->_itemInHand = item; + } else + DebugPrintf("Syntax: give <itemid>\n"); + + return true; +} +} // End of namespace Kyra diff --git a/engines/kyra/debugger.h b/engines/kyra/debugger.h new file mode 100644 index 0000000000..69766f3fc0 --- /dev/null +++ b/engines/kyra/debugger.h @@ -0,0 +1,57 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2005-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 KYRA_DEBUGGER_H +#define KYRA_DEBUGGER_H + +#include "common/debugger.h" + +namespace Kyra { + +class KyraEngine; + +class Debugger : public Common::Debugger<Debugger> { +public: + Debugger(KyraEngine *vm); + virtual ~Debugger() {} // we need this for __SYMBIAN32__ archaic gcc/UIQ + +protected: + KyraEngine *_vm; + + virtual void preEnter(); + virtual void postEnter(); + + bool cmd_exit(int argc, const char **argv); + bool cmd_help(int argc, const char **argv); + bool cmd_enterRoom(int argc, const char **argv); + bool cmd_listRooms(int argc, const char **argv); + bool cmd_listFlags(int argc, const char **argv); + bool cmd_toggleFlag(int argc, const char **argv); + bool cmd_queryFlag(int argc, const char **argv); + bool cmd_listTimers(int argc, const char **argv); + bool cmd_setTimerCountdown(int argc, const char **argv); + bool cmd_giveItem(int argc, const char **argv); +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/gui.cpp b/engines/kyra/gui.cpp new file mode 100644 index 0000000000..a1deccdfcd --- /dev/null +++ b/engines/kyra/gui.cpp @@ -0,0 +1,1020 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2005-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 "kyra/kyra.h" +#include "kyra/screen.h" +#include "kyra/script.h" +#include "kyra/text.h" +#include "kyra/animator.h" + +#include "common/savefile.h" +#include "common/system.h" + +namespace Kyra { + +void KyraEngine::initMainButtonList() { + _buttonList = &_buttonData[0]; + for (int i = 0; _buttonDataListPtr[i]; ++i) { + _buttonList = initButton(_buttonList, _buttonDataListPtr[i]); + } +} + +Button *KyraEngine::initButton(Button *list, Button *newButton) { + if (!newButton) + return list; + if (!list) + return newButton; + Button *cur = list; + while (true) { + if (!cur->nextButton) { + break; + } + cur = cur->nextButton; + } + cur->nextButton = newButton; + return list; +} + +int KyraEngine::buttonInventoryCallback(Button *caller) { + int itemOffset = caller->specialValue - 2; + uint8 inventoryItem = _currentCharacter->inventoryItems[itemOffset]; + if (_itemInHand == -1) { + if (inventoryItem == 0xFF) { + snd_playSoundEffect(0x36); + return 0; + } else { + _screen->hideMouse(); + _screen->fillRect(_itemPosX[itemOffset], _itemPosY[itemOffset], _itemPosX[itemOffset] + 15, _itemPosY[itemOffset] + 15, 12); + snd_playSoundEffect(0x35); + setMouseItem(inventoryItem); + updateSentenceCommand(_itemList[inventoryItem], _takenList[0], 179); + _itemInHand = inventoryItem; + _screen->showMouse(); + _currentCharacter->inventoryItems[itemOffset] = 0xFF; + } + } else { + if (inventoryItem != 0xFF) { + snd_playSoundEffect(0x35); + _screen->hideMouse(); + _screen->fillRect(_itemPosX[itemOffset], _itemPosY[itemOffset], _itemPosX[itemOffset] + 15, _itemPosY[itemOffset] + 15, 12); + _screen->drawShape(0, _shapes[220+_itemInHand], _itemPosX[itemOffset], _itemPosY[itemOffset], 0, 0); + setMouseItem(inventoryItem); + updateSentenceCommand(_itemList[inventoryItem], _takenList[1], 179); + _screen->showMouse(); + _currentCharacter->inventoryItems[itemOffset] = _itemInHand; + _itemInHand = inventoryItem; + } else { + snd_playSoundEffect(0x32); + _screen->hideMouse(); + _screen->drawShape(0, _shapes[220+_itemInHand], _itemPosX[itemOffset], _itemPosY[itemOffset], 0, 0); + _screen->setMouseCursor(1, 1, _shapes[4]); + updateSentenceCommand(_itemList[_itemInHand], _placedList[0], 179); + _screen->showMouse(); + _currentCharacter->inventoryItems[itemOffset] = _itemInHand; + _itemInHand = -1; + } + } + _screen->updateScreen(); + // XXX clearKyrandiaButtonIO + return 0; +} + +int KyraEngine::buttonAmuletCallback(Button *caller) { + if (!(_deathHandler & 8)) + return 1; + int jewel = caller->specialValue - 0x14; + if (_currentCharacter->sceneId == 210) { + if (_beadStateVar == 4 || _beadStateVar == 6) + return 1; + } + if (!queryGameFlag(0x2D)) + return 1; + if (_itemInHand != -1) { + assert(_putDownFirst); + if (_features & GF_TALKIE) { + snd_voiceWaitForFinish(); + snd_playVoiceFile(2000); + } + characterSays(_putDownFirst[0], 0, -2); + return 1; + } + if (queryGameFlag(0xF1)) { + assert(_waitForAmulet); + if (_features & GF_TALKIE) { + snd_voiceWaitForFinish(); + snd_playVoiceFile(2001); + } + characterSays(_waitForAmulet[0], 0, -2); + return 1; + } + if (!queryGameFlag(0x55+jewel)) { + assert(_blackJewel); + _animator->makeBrandonFaceMouse(); + drawJewelPress(jewel, 1); + if (_features & GF_TALKIE) { + snd_voiceWaitForFinish(); + snd_playVoiceFile(2002); + } + characterSays(_blackJewel[0], 0, -2); + return 1; + } + drawJewelPress(jewel, 0); + drawJewelsFadeOutStart(); + drawJewelsFadeOutEnd(jewel); + + _scriptInterpreter->initScript(_scriptClick, _scriptClickData); + _scriptClick->variables[3] = 0; + _scriptClick->variables[6] = jewel; + _scriptInterpreter->startScript(_scriptClick, 4); + + while (_scriptInterpreter->validScript(_scriptClick)) { + _scriptInterpreter->runScript(_scriptClick); + } + + if (_scriptClick->variables[3]) + return 1; + + _unkAmuletVar = 1; + switch (jewel-1) { + case 0: + if (_brandonStatusBit & 1) { + seq_brandonHealing2(); + } else if (_brandonStatusBit == 0) { + seq_brandonHealing(); + assert(_healingTip); + if (_features & GF_TALKIE) { + snd_voiceWaitForFinish(); + snd_playVoiceFile(2003); + } + characterSays(_healingTip[0], 0, -2); + } + break; + + case 1: + seq_makeBrandonInv(); + break; + + case 2: + if (_brandonStatusBit & 1) { + assert(_wispJewelStrings); + if (_features & GF_TALKIE) { + snd_voiceWaitForFinish(); + snd_playVoiceFile(2004); + } + characterSays(_wispJewelStrings[0], 0, -2); + } else { + if (_brandonStatusBit & 2) { + // XXX + seq_makeBrandonNormal2(); + // XXX + } else { + // do not check for item in hand again as in the original since some strings are missing + // in the cd version + if (_currentCharacter->sceneId >= 109 && _currentCharacter->sceneId <= 198) { + snd_playWanderScoreViaMap(1, 0); + seq_makeBrandonWisp(); + snd_playWanderScoreViaMap(17, 0); + } else { + seq_makeBrandonWisp(); + } + setGameFlag(0x9E); + } + } + break; + + case 3: + seq_dispelMagicAnimation(); + assert(_magicJewelString); + if (_features & GF_TALKIE) { + snd_voiceWaitForFinish(); + snd_playVoiceFile(2007); + } + characterSays(_magicJewelString[0], 0, -2); + break; + + default: + break; + } + _unkAmuletVar = 0; + // XXX clearKyrandiaButtonIO (!used before every return in this function!) + return 1; +} + +void KyraEngine::processButtonList(Button *list) { + while (list) { + if (list->flags & 8) { + list = list->nextButton; + continue; + } + + int x = list->x; + int y = list->y; + assert(list->dimTableIndex < _screen->_screenDimTableCount); + if (x < 0) { + x += _screen->_screenDimTable[list->dimTableIndex].w << 3; + } + x += _screen->_screenDimTable[list->dimTableIndex].sx << 3; + + if (y < 0) { + y += _screen->_screenDimTable[list->dimTableIndex].h; + } + y += _screen->_screenDimTable[list->dimTableIndex].sy; + + if (_mouseX >= x && _mouseY >= y && x + list->width >= _mouseX && y + list->height >= _mouseY) { + int processMouseClick = 0; + if (list->flags & 0x400) { + if (_mousePressFlag) { + if (!(list->flags2 & 1)) { + list->flags2 |= 1; + processButton(list); + } + } else { + if (list->flags2 & 1) { + list->flags2 &= 0xFFFE; + processButton(list); + processMouseClick = 1; + } + } + } else if (_mousePressFlag) { + processMouseClick = 1; + } + + if (processMouseClick) { + if (list->buttonCallback) { + if ((this->*(list->buttonCallback))(list)) { + break; + } + } + } + } else { + if (list->flags2 & 1) { + list->flags2 &= 0xFFFE; + processButton(list); + } + list = list->nextButton; + continue; + } + + list = list->nextButton; + } +} + +void KyraEngine::processButton(Button *button) { + if (!button) + return; + + int processType = 0; + uint8 *shape = 0; + Button::ButtonCallback callback = 0; + + int flags = (button->flags2 & 5); + if (flags == 1) { + processType = button->process2; + if (processType == 1) { + shape = button->process2PtrShape; + } else if (processType == 4) { + callback = button->process2PtrCallback; + } + } else if (flags == 4 || flags == 5) { + processType = button->process1; + if (processType == 1) { + shape = button->process1PtrShape; + } else if (processType == 4) { + callback = button->process1PtrCallback; + } + } else { + processType = button->process0; + if (processType == 1) { + shape = button->process0PtrShape; + } else if (processType == 4) { + callback = button->process0PtrCallback; + } + } + + int x = button->x; + int y = button->y; + assert(button->dimTableIndex < _screen->_screenDimTableCount); + if (x < 0) { + x += _screen->_screenDimTable[button->dimTableIndex].w << 3; + } + + if (y < 0) { + y += _screen->_screenDimTable[button->dimTableIndex].h; + } + + if (processType == 1 && shape) { + _screen->drawShape(_screen->_curPage, shape, x, y, button->dimTableIndex, 0x10); + } else if (processType == 4 && callback) { + (this->*callback)(button); + } +} + +void KyraEngine::processAllMenuButtons() { + if (!_menuButtonList) + return; + + Button *cur = _menuButtonList; + while (true) { + if (!cur->nextButton) { + break; + } + processMenuButton(cur); + cur = cur->nextButton; + } + return; +} + +void KyraEngine::processMenuButton(Button *button) { + if (!_displayMenu) + return; + + //_screen->hideMouse(); + + if ( !button || (button->flags & 8)) + return; + + if (button->flags2 & 1) + button->flags2 &= 0xf7; + else + button->flags2 |= 8; + + button->flags2 &= 0xfc; + + if (button->flags2 & 4) + button->flags2 |= 0x10; + else + button->flags2 &= 0xef; + + button->flags2 &= 0xfb; + + processButton(button); + + //_screen->showMouse(); +} + +int KyraEngine::drawBoxCallback(Button *button) { + if (!_displayMenu) + return 0; + + _screen->hideMouse(); + _screen->drawBox(button->x + 1, button->y + 1, button->x + button->width - 1, button->y + button->height - 1, 0xf8); + _screen->showMouse(); + + return 0; +} + +int KyraEngine::drawShadedBoxCallback(Button *button) { + + if (!_displayMenu) + return 0; + + _screen->hideMouse(); + _screen->drawShadedBox(button->x, button->y, button->x + button->width, button->y + button->height, 0xf9, 0xfa); + _screen->showMouse(); + + return 0; +} + +int KyraEngine::buttonMenuCallback(Button *caller) { + _displayMenu = true; + + // XXX setLabels + if (_currentCharacter->sceneId == 210) { + snd_playSoundEffect(0x36); + return 0; + } + // XXX + + for (int i = 0; i < 6; i++) { + _menuButtonData[i].process0 = _menuButtonData[i].process1 = _menuButtonData[i].process2 = 4; + _menuButtonData[i].process0PtrCallback = &KyraEngine::drawShadedBoxCallback; + _menuButtonData[i].process1PtrCallback = &KyraEngine::drawBoxCallback; + _menuButtonData[i].process2PtrCallback = &KyraEngine::drawShadedBoxCallback; + } + + _screen->savePageToDisk("SEENPAGE.TMP", 0); + gui_fadePalette(); + + calcCoords(_menu[0]); + calcCoords(_menu[1]); + calcCoords(_menu[2]); + calcCoords(_menu[3]); + + _menuRestoreScreen = true; + + if (_menuDirectlyToLoad) + gui_loadGameMenu(0); + else { + initMenu(_menu[0]); + processAllMenuButtons(); + } + + while (_displayMenu) { + gui_processHighlights(_menu[0]); + processButtonList(_menuButtonList); + gui_getInput(); + } + + if (_menuRestoreScreen) { + gui_restorePalette(); + _screen->loadPageFromDisk("SEENPAGE.TMP", 0); + _animator->_updateScreen = true; + } + else + _screen->deletePageFromDisk(0); + + return 0; +} + +void KyraEngine::initMenu(Menu menu) { + int menu_x2 = menu.width + menu.x - 1; + int menu_y2 = menu.height + menu.y - 1; + + _menuButtonList = 0; + + _screen->hideMouse(); + _screen->fillRect(menu.x + 2, menu.y + 2, menu_x2 - 2, menu_y2 - 2, menu.bgcolor); + _screen->drawShadedBox(menu.x, menu.y, menu_x2, menu_y2, menu.color1, menu.color2); + + int textX; + int textY; + + if (menu.field_10 != -1) + textX = menu.x; + else + textX = _text->getCenterStringX(menu.menuName, menu.x, menu_x2); + + textY = menu.y + menu.field_12; + + _text->printText(menu.menuName, textX - 1, textY + 1, 12, 248, 0); + _text->printText(menu.menuName, textX, textY, menu.textColor, 0, 0); + + int x1, y1, x2, y2; + for (int i = 0; i < menu.nrOfItems; i++) { + if (!menu.item[i].enabled) + continue; + + x1 = menu.x + menu.item[i].x; + y1 = menu.y + menu.item[i].y; + + x2 = x1 + menu.item[i].width - 1; + y2 = y1 + menu.item[i].height - 1; + + if (i < 6) { + _menuButtonData[i].nextButton = 0; + _menuButtonData[i].x = x1; + _menuButtonData[i].y = y1; + _menuButtonData[i].width = menu.item[i].width - 1; + _menuButtonData[i].height = menu.item[i].height - 1; + _menuButtonData[i].buttonCallback = menu.item[i].callback; + _menuButtonData[i].specialValue = menu.item[i].field_1b; + //_menuButtonData[i].field_6 = menu.item[i].field_25; + //_menuButtonData[i].field_8 = 0; + + if (!_menuButtonList) + _menuButtonList = &_menuButtonData[i]; + else + _menuButtonList = initButton(_menuButtonList, &_menuButtonData[i]); + } + _screen->fillRect(x1, y1, x2, y2, menu.item[i].bgcolor); + _screen->drawShadedBox(x1, y1, x2, y2, menu.item[i].color1, menu.item[i].color2); + + if (menu.item[i].field_12 != -1) + textX = x1 + menu.item[i].field_12 + 3; + else + textX = _text->getCenterStringX(menu.item[i].itemString, x1, x2); + + textY = y1 + 2; + _text->printText(menu.item[i].itemString, textX - 1, textY + 1, 12, 0, 0); + + if (i == menu.highlightedItem) + _text->printText(menu.item[i].itemString, textX, textY, menu.item[i].highlightColor, 0, 0); + else + _text->printText(menu.item[i].itemString, textX, textY, menu.item[i].textColor, 0, 0); + + if (menu.item[i].labelString) { + _text->printText(menu.item[i].labelString, menu.x + menu.item[i].field_21 - 1, menu.y + menu.item[i].field_23 + 1, 12, 0, 0); + _text->printText(menu.item[i].labelString, menu.x + menu.item[i].field_21, menu.y + menu.item[i].field_23, 253, 0, 0); + } + } + + if (menu.scrollUpBtnX != -1) { + _scrollUpButton.x = menu.scrollUpBtnX + menu.x; + _scrollUpButton.y = menu.scrollUpBtnY + menu.y; + _scrollUpButton.buttonCallback = &KyraEngine::gui_scrollUp; + _scrollUpButton.nextButton = 0; + _menuButtonList = initButton(_menuButtonList, &_scrollUpButton); + processMenuButton(&_scrollUpButton); + + _scrollDownButton.x = menu.scrollDownBtnX + menu.x; + _scrollDownButton.y = menu.scrollDownBtnY + menu.y; + _scrollDownButton.buttonCallback = &KyraEngine::gui_scrollDown; + _scrollDownButton.nextButton = 0; + _menuButtonList = initButton(_menuButtonList, &_scrollDownButton); + processMenuButton(&_scrollDownButton); + } + + _screen->showMouse(); + _screen->updateScreen(); +} + +void KyraEngine::calcCoords(Menu &menu) { + if (menu.x == -1) + menu.x = (320 - menu.width)/2; + + if (menu.y == -1) + menu.y = (200 - menu.height)/2; + + assert(menu.nrOfItems < 7); + for (int i = 0; i < menu.nrOfItems; i++) + if (menu.item[i].x == -1) + menu.item[i].x = (menu.width - menu.item[i].width)/2; +} + +void KyraEngine::gui_getInput() { + OSystem::Event event; + uint32 now = _system->getMillis(); + + _mousePressFlag = false; + while (_system->pollEvent(event)) { + switch (event.type) { + case OSystem::EVENT_QUIT: + quitGame(); + break; + case OSystem::EVENT_LBUTTONUP: + _mousePressFlag = true; + break; + case OSystem::EVENT_MOUSEMOVE: + _mouseX = event.mouse.x; + _mouseY = event.mouse.y; + _system->updateScreen(); + break; + case OSystem::EVENT_KEYDOWN: + _keyboardEvent.pending = true; + _keyboardEvent.repeat = now + 400; + _keyboardEvent.ascii = event.kbd.ascii; + break; + case OSystem::EVENT_KEYUP: + _keyboardEvent.repeat = 0; + break; + default: + break; + } + } + + if (!_keyboardEvent.pending && _keyboardEvent.repeat && now >= _keyboardEvent.repeat) { + _keyboardEvent.pending = true; + _keyboardEvent.repeat = now + 100; + } + _system->delayMillis(3); +} + +int KyraEngine::gui_resumeGame(Button *button) { + debug(9, "KyraEngine::gui_resumeGame()"); + processMenuButton(button); + _displayMenu = false; + + return 0; +} + +const char *KyraEngine::getSavegameFilename(int num) { + static char saveLoadSlot[12]; + + sprintf(saveLoadSlot, "%s.%.3d", _targetName.c_str(), num); + return saveLoadSlot; +} + +int KyraEngine::getNextSavegameSlot() { + Common::InSaveFile *in; + + for (int i = 1; i < 1000; i++) { + if ((in = _saveFileMan->openForLoading(getSavegameFilename(i)))) { + delete in; + } else { + return i; + } + } + warning("Didn't save: Ran out of savegame filenames!"); + return 0; +} + +void KyraEngine::setupSavegames(Menu &menu, int num) { + Common::InSaveFile *in; + static char savenames[5][31]; + uint8 startSlot; + assert(num <= 5); + + if (_savegameOffset == 0) { + menu.item[0].itemString = _specialSavegameString; + menu.item[0].enabled = 1; + menu.item[0].field_1b = 0; + startSlot = 1; + } else + startSlot = 0; + + for (int i = startSlot; i < num; i++) { + if ((in = _saveFileMan->openForLoading(getSavegameFilename(i + _savegameOffset)))) { + in->skip(8); + in->read(savenames[i], 31); + menu.item[i].itemString = savenames[i]; + menu.item[i].enabled = 1; + menu.item[i].field_1b = i + _savegameOffset; + delete in; + } else { + menu.item[i].enabled = 0; + //menu.item[i].itemString = ""; + //menu.item[i].field_1b = -1; + } + } +} + +int KyraEngine::gui_saveGameMenu(Button *button) { + debug(9, "KyraEngine::gui_saveGameMenu()"); + processMenuButton(button); + _menu[2].item[5].enabled = true; + + _screen->loadPageFromDisk("SEENPAGE.TMP", 0); + _screen->savePageToDisk("SEENPAGE.TMP", 0); + + _menu[2].menuName = "Select a position to save to:"; + _specialSavegameString = "[ EMPTY SLOT ]"; + for (int i = 0; i < 5; i++) + _menu[2].item[i].callback = &KyraEngine::gui_saveGame; + + _savegameOffset = 0; + setupSavegames(_menu[2], 5); + + initMenu(_menu[2]); + processAllMenuButtons(); + + _displaySubMenu = true; + _cancelSubMenu = false; + + while (_displaySubMenu) { + gui_getInput(); + gui_processHighlights(_menu[2]); + processButtonList(_menuButtonList); + } + + _screen->loadPageFromDisk("SEENPAGE.TMP", 0); + _screen->savePageToDisk("SEENPAGE.TMP", 0); + + if (_cancelSubMenu) { + initMenu(_menu[0]); + processAllMenuButtons(); + } else { + _displayMenu = false; + } + return 0; +} + +int KyraEngine::gui_loadGameMenu(Button *button) { + debug(9, "KyraEngine::gui_loadGameMenu()"); + if (_menuDirectlyToLoad) + _menu[2].item[5].enabled = false; + else { + processMenuButton(button); + _menu[2].item[5].enabled = true; + } + + _screen->loadPageFromDisk("SEENPAGE.TMP", 0); + _screen->savePageToDisk("SEENPAGE.TMP", 0); + + _specialSavegameString = "[ START A NEW GAME ]"; + _menu[2].menuName = "Which game would you like to reload?"; + for (int i = 0; i < 5; i++) + _menu[2].item[i].callback = &KyraEngine::gui_loadGame; + + _savegameOffset = 0; + setupSavegames(_menu[2], 5); + + initMenu(_menu[2]); + processAllMenuButtons(); + + _displaySubMenu = true; + _cancelSubMenu = false; + + while (_displaySubMenu) { + gui_getInput(); + gui_processHighlights(_menu[2]); + processButtonList(_menuButtonList); + } + + _screen->loadPageFromDisk("SEENPAGE.TMP", 0); + _screen->savePageToDisk("SEENPAGE.TMP", 0); + + if (_cancelSubMenu) { + initMenu(_menu[0]); + processAllMenuButtons(); + } else { + gui_restorePalette(); + loadGame(getSavegameFilename(_gameToLoad)); + _displayMenu = false; + _menuRestoreScreen = false; + } + return 0; +} + +void KyraEngine::gui_redrawTextfield() { + _screen->fillRect(38, 91, 287, 102, 250); + _text->printText(_savegameName, 38, 92, 253, 0, 0); + + _screen->_charWidth = -2; + int width = _screen->getTextWidth(_savegameName); + _screen->fillRect(39 + width, 93, 45 + width, 100, 254); + _screen->_charWidth = 0; + + _screen->updateScreen(); +} + +void KyraEngine::gui_updateSavegameString() { + int length; + + if (_keyboardEvent.pending && _keyboardEvent.ascii) { + length = strlen(_savegameName); + + if (_keyboardEvent.ascii > 31 && _keyboardEvent.ascii < 127) { + if (length < 31) { + _savegameName[length] = _keyboardEvent.ascii; + _savegameName[length+1] = 0; + gui_redrawTextfield(); + } + } else if (_keyboardEvent.ascii == 8 ||_keyboardEvent.ascii == 127) { + if (length > 0) { + _savegameName[length-1] = 0; + gui_redrawTextfield(); + } + } else if (_keyboardEvent.ascii == 13) { + _displaySubMenu = false; + } + } + + _keyboardEvent.pending = false; +} + +int KyraEngine::gui_saveGame(Button *button) { + debug(9, "KyraEngine::gui_saveGame()"); + processMenuButton(button); + _gameToLoad = button->specialValue; + + _screen->loadPageFromDisk("SEENPAGE.TMP", 0); + _screen->savePageToDisk("SEENPAGE.TMP", 0); + + initMenu(_menu[3]); + processAllMenuButtons(); + + _displaySubMenu = true; + _cancelSubMenu = false; + + if (_savegameOffset == 0 && _gameToLoad == 0) { + _savegameName[0] = 0; + } else { + for (int i = 0; i < 5; i++) { + if (_menu[2].item[i].field_1b == _gameToLoad) { + strncpy(_savegameName, _menu[2].item[i].itemString, 31); + break; + } + } + } + gui_redrawTextfield(); + + _keyboardEvent.pending = 0; + _keyboardEvent.repeat = 0; + while (_displaySubMenu) { + gui_getInput(); + gui_updateSavegameString(); + gui_processHighlights(_menu[3]); + processButtonList(_menuButtonList); + } + + if (_cancelSubMenu) { + _displaySubMenu = true; + _cancelSubMenu = false; + initMenu(_menu[2]); + processAllMenuButtons(); + } else { + if (_savegameOffset == 0 && _gameToLoad == 0) + _gameToLoad = getNextSavegameSlot(); + if (_gameToLoad > 0) + saveGame(getSavegameFilename(_gameToLoad), _savegameName); + } + + return 0; +} + +int KyraEngine::gui_savegameConfirm(Button *button) { + debug(9, "KyraEngine::gui_savegameConfirm()"); + processMenuButton(button); + _displaySubMenu = false; + + return 0; +} + +int KyraEngine::gui_loadGame(Button *button) { + debug(9, "KyraEngine::gui_loadGame()"); + processMenuButton(button); + _displaySubMenu = false; + _gameToLoad = button->specialValue; + + return 0; +} + +int KyraEngine::gui_cancelSubMenu(Button *button) { + debug(9, "KyraEngine::gui_cancelLoadGameMenu()"); + processMenuButton(button); + _displaySubMenu = false; + _cancelSubMenu = true; + + return 0; +} + +int KyraEngine::gui_quitPlaying(Button *button) { + debug(9, "KyraEngine::gui_quitPlaying()"); + processMenuButton(button); + + if (gui_quitConfirm("Are you sure you want to quit playing?")) + quitGame(); + else { + initMenu(_menu[0]); + processAllMenuButtons(); + } + + return 0; +} + +bool KyraEngine::gui_quitConfirm(const char *str) { + debug(9, "KyraEngine::gui_quitConfirm()"); + + _screen->loadPageFromDisk("SEENPAGE.TMP", 0); + _screen->savePageToDisk("SEENPAGE.TMP", 0); + + _menu[1].menuName = str; + initMenu(_menu[1]); + + _displaySubMenu = true; + _cancelSubMenu = true; + + while (_displaySubMenu) { + gui_getInput(); + gui_processHighlights(_menu[1]); + processButtonList(_menuButtonList); + } + + _screen->loadPageFromDisk("SEENPAGE.TMP", 0); + _screen->savePageToDisk("SEENPAGE.TMP", 0); + + return !_cancelSubMenu; +} + +int KyraEngine::gui_quitConfirmYes(Button *button) { + debug(9, "KyraEngine::gui_quitConfirmYes()"); + processMenuButton(button); + _displaySubMenu = false; + _cancelSubMenu = false; + + return 0; +} + +int KyraEngine::gui_quitConfirmNo(Button *button) { + debug(9, "KyraEngine::gui_quitConfirmNo()"); + processMenuButton(button); + _displaySubMenu = false; + _cancelSubMenu = true; + + return 0; +} + +int KyraEngine::gui_scrollUp(Button *button) { + debug(9, "KyraEngine::gui_scrollUp()"); + processMenuButton(button); + + if (_savegameOffset > 0) { + _savegameOffset--; + setupSavegames(_menu[2], 5); + initMenu(_menu[2]); + } + return 0; +} + +int KyraEngine::gui_scrollDown(Button *button) { + debug(9, "KyraEngine::gui_scrollDown()"); + processMenuButton(button); + + _savegameOffset++; + setupSavegames(_menu[2], 5); + initMenu(_menu[2]); + + return 0; +} + +void KyraEngine::gui_processHighlights(Menu &menu) { + int x1, y1, x2, y2; + + for (int i = 0; i < menu.nrOfItems; i++) { + if (!menu.item[i].enabled) + continue; + + x1 = menu.x + menu.item[i].x; + y1 = menu.y + menu.item[i].y; + + x2 = x1 + menu.item[i].width; + y2 = y1 + menu.item[i].height; + + if (_mouseX > x1 && _mouseX < x2 && + _mouseY > y1 && _mouseY < y2) { + + if (menu.highlightedItem != i) { + if (menu.item[menu.highlightedItem].enabled ) + gui_redrawText(menu); + + menu.highlightedItem = i; + gui_redrawHighlight(menu); + _screen->updateScreen(); + } + } + } +} + +void KyraEngine::gui_redrawText(Menu menu) { + int textX; + int i = menu.highlightedItem; + + int x1 = menu.x + menu.item[i].x; + int y1 = menu.y + menu.item[i].y; + + int x2 = x1 + menu.item[i].width - 1; + + if (menu.item[i].field_12 != -1) + textX = x1 + menu.item[i].field_12 + 3; + else + textX = _text->getCenterStringX(menu.item[i].itemString, x1, x2); + + int textY = y1 + 2; + _text->printText(menu.item[i].itemString, textX - 1, textY + 1, 12, 0, 0); + _text->printText(menu.item[i].itemString, textX, textY, menu.item[i].textColor, 0, 0); +} + +void KyraEngine::gui_redrawHighlight(Menu menu) { + int textX; + int i = menu.highlightedItem; + + int x1 = menu.x + menu.item[i].x; + int y1 = menu.y + menu.item[i].y; + + int x2 = x1 + menu.item[i].width - 1; + + if (menu.item[i].field_12 != -1) + textX = x1 + menu.item[i].field_12 + 3; + else + textX = _text->getCenterStringX(menu.item[i].itemString, x1, x2); + + int textY = y1 + 2; + _text->printText(menu.item[i].itemString, textX - 1, textY + 1, 12, 0, 0); + _text->printText(menu.item[i].itemString, textX, textY, menu.item[i].highlightColor, 0, 0); +} + +void KyraEngine::gui_fadePalette() { + static int16 menuPalIndexes[] = {248, 249, 250, 251, 252, 253, 254, -1}; + int index = 0; + + memcpy(_screen->getPalette(2), _screen->_currentPalette, 768); + + for (int i = 0; i < 768; i++) { + _screen->_currentPalette[i] /= 2; + } + + while( menuPalIndexes[index] != -1) { + memcpy(&_screen->_currentPalette[menuPalIndexes[index]*3], &_screen->getPalette(2)[menuPalIndexes[index]*3], 3); + index++; + } + + _screen->fadePalette(_screen->_currentPalette, 2); +} + +void KyraEngine::gui_restorePalette() { + memcpy(_screen->_currentPalette, _screen->getPalette(2), 768); + _screen->fadePalette(_screen->_currentPalette, 2); +} + + +} // end of namespace Kyra + diff --git a/engines/kyra/items.cpp b/engines/kyra/items.cpp new file mode 100644 index 0000000000..c98e1ee607 --- /dev/null +++ b/engines/kyra/items.cpp @@ -0,0 +1,982 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2005-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 "kyra/kyra.h" +#include "kyra/seqplayer.h" +#include "kyra/screen.h" +#include "kyra/resource.h" +#include "kyra/sound.h" +#include "kyra/sprites.h" +#include "kyra/wsamovie.h" +#include "kyra/animator.h" +#include "kyra/text.h" + +#include "common/system.h" +#include "common/savefile.h" + +namespace Kyra { + +int KyraEngine::findDuplicateItemShape(int shape) { + static uint8 dupTable[] = { + 0x48, 0x46, 0x49, 0x47, 0x4a, 0x46, 0x4b, 0x47, + 0x4c, 0x46, 0x4d, 0x47, 0x5b, 0x5a, 0x5c, 0x5a, + 0x5d, 0x5a, 0x5e, 0x5a, 0xFF, 0xFF + }; + + int i = 0; + + while (dupTable[i] != 0xFF) { + if (dupTable[i] == shape) + return dupTable[i+1]; + i += 2; + } + return -1; +} + +void KyraEngine::addToNoDropRects(int x, int y, int w, int h) { + debug(9, "KyraEngine::addToNoDropRects(%d, %d, %d, %d)", x, y, w, h); + for (int rect = 0; rect < 11; ++rect) { + if (_noDropRects[rect].x == -1) { + _noDropRects[rect].x = x; + _noDropRects[rect].y = y; + _noDropRects[rect].x2 = x + w - 1; + _noDropRects[rect].y2 = y + h - 1; + break; + } + } +} + +void KyraEngine::clearNoDropRects() { + debug(9, "KyraEngine::clearNoDropRects()"); + memset(_noDropRects, -1, sizeof(_noDropRects)); +} + +byte KyraEngine::findFreeItemInScene(int scene) { + debug(9, "KyraEngine::findFreeItemInScene(%d)", scene); + assert(scene < _roomTableSize); + Room *room = &_roomTable[scene]; + for (int i = 0; i < 12; ++i) { + if (room->itemsTable[i] == 0xFF) + return i; + } + return 0xFF; +} + +byte KyraEngine::findItemAtPos(int x, int y) { + debug(9, "KyraEngine::findItemAtPos(%d, %d)", x, y); + assert(_currentCharacter->sceneId < _roomTableSize); + const uint8 *itemsTable = _roomTable[_currentCharacter->sceneId].itemsTable; + const uint16 *xposOffset = _roomTable[_currentCharacter->sceneId].itemsXPos; + const uint8 *yposOffset = _roomTable[_currentCharacter->sceneId].itemsYPos; + + int highestYPos = -1; + byte returnValue = 0xFF; + + for (int i = 0; i < 12; ++i) { + if (*itemsTable != 0xFF) { + int xpos = *xposOffset - 11; + int xpos2 = *xposOffset + 10; + if (x > xpos && x < xpos2) { + assert(*itemsTable < ARRAYSIZE(_itemTable)); + int itemHeight = _itemTable[*itemsTable].height; + int ypos = *yposOffset + 3; + int ypos2 = ypos - itemHeight - 3; + + if (y > ypos2 && ypos > y) { + if (highestYPos <= ypos) { + returnValue = i; + highestYPos = ypos; + } + } + } + } + ++xposOffset; + ++yposOffset; + ++itemsTable; + } + + return returnValue; +} + +void KyraEngine::placeItemInGenericMapScene(int item, int index) { + debug(9, "KyraEngine::placeItemInGenericMapScene(%d, %d)", item, index); + static const uint16 itemMapSceneMinTable[] = { + 0x0000, 0x0011, 0x006D, 0x0025, 0x00C7, 0x0000 + }; + static const uint16 itemMapSceneMaxTable[] = { + 0x0010, 0x0024, 0x00C6, 0x006C, 0x00F5, 0x0000 + }; + + int minValue = itemMapSceneMinTable[index]; + int maxValue = itemMapSceneMaxTable[index]; + + while (true) { + int room = _rnd.getRandomNumberRng(minValue, maxValue); + assert(room < _roomTableSize); + int nameIndex = _roomTable[room].nameIndex; + bool placeItem = false; + + switch (nameIndex) { + case 0: case 1: case 2: case 3: + case 4: case 5: case 6: case 11: + case 12: case 16: case 17: case 20: + case 22: case 23: case 25: case 26: + case 27: case 31: case 33: case 34: + case 36: case 37: case 58: case 59: + case 60: case 61: case 83: case 84: + case 85: case 104: case 105: case 106: + placeItem = true; + break; + + case 51: + if (room != 46) { + placeItem = true; + break; + } + default: + placeItem = false; + break; + } + + if (placeItem) { + Room *roomPtr = &_roomTable[room]; + if (roomPtr->northExit == 0xFFFF && roomPtr->eastExit == 0xFFFF && roomPtr->southExit == 0xFFFF && roomPtr->westExit == 0xFFFF) { + placeItem = false; + } else if (_currentCharacter->sceneId == room) { + placeItem = false; + } + } + + if (placeItem) { + if (!processItemDrop(room, item, -1, -1, 2, 0)) + continue; + break; + } + } +} + +void KyraEngine::createMouseItem(int item) { + debug(9, "KyraEngine::createMouseItem(%d)", item); + _screen->hideMouse(); + setMouseItem(item); + _itemInHand = item; + _screen->showMouse(); +} + +void KyraEngine::destroyMouseItem() { + debug(9, "KyraEngine::destroyMouseItem()"); + _screen->hideMouse(); + _screen->setMouseCursor(1, 1, _shapes[4]); + _itemInHand = -1; + _screen->showMouse(); +} + +void KyraEngine::setMouseItem(int item) { + debug(9, "KyraEngine::setMouseItem(%d)", item); + if (item == -1) { + _screen->setMouseCursor(1, 1, _shapes[10]); + } else { + _screen->setMouseCursor(8, 15, _shapes[220+item]); + } +} + +void KyraEngine::wipeDownMouseItem(int xpos, int ypos) { + debug(9, "KyraEngine::wipeDownMouseItem(%d, %d)", xpos, ypos); + if (_itemInHand == -1) + return; + xpos -= 8; + ypos -= 15; + _screen->hideMouse(); + _screen->backUpRect1(xpos, ypos); + int y = ypos; + int height = 16; + + while (height >= 0) { + _screen->restoreRect1(xpos, ypos); + _screen->setNewShapeHeight(_shapes[220+_itemInHand], height); + uint32 nextTime = _system->getMillis() + 1 * _tickLength; + _screen->drawShape(0, _shapes[220+_itemInHand], xpos, y, 0, 0); + _screen->updateScreen(); + y += 2; + height -= 2; + while (_system->getMillis() < nextTime) {} + } + _screen->restoreRect1(xpos, ypos); + _screen->resetShapeHeight(_shapes[220+_itemInHand]); + destroyMouseItem(); + _screen->showMouse(); +} + +void KyraEngine::setupSceneItems() { + debug(9, "KyraEngine::setupSceneItems()"); + uint16 sceneId = _currentCharacter->sceneId; + assert(sceneId < _roomTableSize); + Room *currentRoom = &_roomTable[sceneId]; + for (int i = 0; i < 12; ++i) { + uint8 item = currentRoom->itemsTable[i]; + if (item == 0xFF || !currentRoom->needInit[i]) { + continue; + } + + int xpos = 0; + int ypos = 0; + + if (currentRoom->itemsXPos[i] == 0xFFFF) { + xpos = currentRoom->itemsXPos[i] = _rnd.getRandomNumberRng(24, 296); + ypos = currentRoom->itemsYPos[i] = _rnd.getRandomNumberRng(_northExitHeight & 0xFF, 130); + } else { + xpos = currentRoom->itemsXPos[i]; + ypos = currentRoom->itemsYPos[i]; + } + + _lastProcessedItem = i; + + int stop = 0; + while (!stop) { + stop = processItemDrop(sceneId, item, xpos, ypos, 3, 0); + if (!stop) { + xpos = currentRoom->itemsXPos[i] = _rnd.getRandomNumberRng(24, 296); + ypos = currentRoom->itemsYPos[i] = _rnd.getRandomNumberRng(_northExitHeight & 0xFF, 130); + if (countItemsInScene(sceneId) >= 12) { + break; + } + } else { + currentRoom->needInit[i] = 0; + } + } + } +} + +int KyraEngine::countItemsInScene(uint16 sceneId) { + debug(9, "KyraEngine::countItemsInScene(%d)", sceneId); + assert(sceneId < _roomTableSize); + Room *currentRoom = &_roomTable[sceneId]; + + int items = 0; + + for (int i = 0; i < 12; ++i) { + if (currentRoom->itemsTable[i] != 0xFF) { + ++items; + } + } + + return items; +} + +int KyraEngine::processItemDrop(uint16 sceneId, uint8 item, int x, int y, int unk1, int unk2) { + debug(9, "KyraEngine::processItemDrop(%d, %d, %d, %d, %d, %d)", sceneId, item, x, y, unk1, unk2); + int freeItem = -1; + uint8 itemIndex = findItemAtPos(x, y); + if (unk1) { + itemIndex = 0xFF; + } + + if (itemIndex != 0xFF) { + exchangeItemWithMouseItem(sceneId, itemIndex); + return 0; + } + + assert(sceneId < _roomTableSize); + Room *currentRoom = &_roomTable[sceneId]; + + if (unk1 != 3) { + for (int i = 0; i < 12; ++i) { + if (currentRoom->itemsTable[i] == 0xFF) { + freeItem = i; + break; + } + } + } else { + freeItem = _lastProcessedItem; + } + + if (freeItem == -1) { + return 0; + } + + if (sceneId != _currentCharacter->sceneId) { + addItemToRoom(sceneId, item, freeItem, x, y); + return 1; + } + + int itemHeight = _itemTable[item].height; + _lastProcessedItemHeight = itemHeight; + + if (x == -1 && x == -1) { + x = _rnd.getRandomNumberRng(16, 304); + y = _rnd.getRandomNumberRng(_northExitHeight & 0xFF, 135); + } + + int xpos = x; + int ypos = y; + int destY = -1; + int destX = -1; + int running = 1; + + while (running) { + if ((_northExitHeight & 0xFF) <= ypos) { + bool running2 = true; + + if (_screen->getDrawLayer(xpos, ypos) > 1) { + if (((_northExitHeight >> 8) & 0xFF) != ypos) { + running2 = false; + } + } + + if (_screen->getDrawLayer2(xpos, ypos, itemHeight) > 1) { + if (((_northExitHeight >> 8) & 0xFF) != ypos) { + running2 = false; + } + } + + if (!isDropable(xpos, ypos)) { + if (((_northExitHeight >> 8) & 0xFF) != ypos) { + running2 = false; + } + } + + int xpos2 = xpos; + int xpos3 = xpos; + + while (running2) { + if (isDropable(xpos2, ypos)) { + if (_screen->getDrawLayer2(xpos2, ypos, itemHeight) < 7) { + if (findItemAtPos(xpos2, ypos) == 0xFF) { + destX = xpos2; + destY = ypos; + running = 0; + running2 = false; + } + } + } + + if (isDropable(xpos3, ypos)) { + if (_screen->getDrawLayer2(xpos3, ypos, itemHeight) < 7) { + if (findItemAtPos(xpos3, ypos) == 0xFF) { + destX = xpos3; + destY = ypos; + running = 0; + running2 = false; + } + } + } + + if (!running2) + continue; + + xpos2 -= 2; + if (xpos2 < 16) { + xpos2 = 16; + } + + xpos3 += 2; + if (xpos3 > 304) { + xpos3 = 304; + } + + if (xpos2 > 16) + continue; + if (xpos3 < 304) + continue; + running2 = false; + } + } + + if (((_northExitHeight >> 8) & 0xFF) == ypos) { + running = 0; + destY -= _rnd.getRandomNumberRng(0, 3); + + if ((_northExitHeight & 0xFF) < destY) { + continue; + } + + destY = (_northExitHeight & 0xFF) + 1; + continue; + } + ypos += 2; + if (((_northExitHeight >> 8) & 0xFF) >= ypos) { + continue; + } + ypos = (_northExitHeight >> 8) & 0xFF; + } + + if (destX == -1 || destY == -1) { + return 0; + } + + if (unk1 == 3) { + currentRoom->itemsXPos[freeItem] = destX; + currentRoom->itemsYPos[freeItem] = destY; + return 1; + } + + if (unk1 == 2) { + itemSpecialFX(x, y, item); + } + + if (unk1 == 0) { + destroyMouseItem(); + } + + itemDropDown(x, y, destX, destY, freeItem, item); + + if (unk1 == 0 && unk2 != 0) { + assert(_itemList && _droppedList); + updateSentenceCommand(_itemList[item], _droppedList[0], 179); + } + + return 1; +} + +void KyraEngine::exchangeItemWithMouseItem(uint16 sceneId, int itemIndex) { + debug(9, "KyraEngine::exchangeItemWithMouseItem(%d, %d)", sceneId, itemIndex); + _screen->hideMouse(); + _animator->animRemoveGameItem(itemIndex); + assert(sceneId < _roomTableSize); + Room *currentRoom = &_roomTable[sceneId]; + + int item = currentRoom->itemsTable[itemIndex]; + currentRoom->itemsTable[itemIndex] = _itemInHand; + _itemInHand = item; + _animator->animAddGameItem(itemIndex, sceneId); + snd_playSoundEffect(53); + + setMouseItem(_itemInHand); + assert(_itemList && _takenList); + updateSentenceCommand(_itemList[_itemInHand], _takenList[1], 179); + _screen->showMouse(); + clickEventHandler2(); +} + +void KyraEngine::addItemToRoom(uint16 sceneId, uint8 item, int itemIndex, int x, int y) { + debug(9, "KyraEngine::addItemToRoom(%d, %d, %d, %d, %d)", sceneId, item, itemIndex, x, y); + assert(sceneId < _roomTableSize); + Room *currentRoom = &_roomTable[sceneId]; + currentRoom->itemsTable[itemIndex] = item; + currentRoom->itemsXPos[itemIndex] = x; + currentRoom->itemsYPos[itemIndex] = y; + currentRoom->needInit[itemIndex] = 1; +} + +int KyraEngine::checkNoDropRects(int x, int y) { + debug(9, "KyraEngine::checkNoDropRects(%d, %d)", x, y); + if (_lastProcessedItemHeight < 1 || _lastProcessedItemHeight > 16) { + _lastProcessedItemHeight = 16; + } + if (_noDropRects[0].x == -1) { + return 0; + } + + for (int i = 0; i < 11; ++i) { + if (_noDropRects[i].x == -1) { + break; + } + + int xpos = _noDropRects[i].x; + int ypos = _noDropRects[i].y; + int xpos2 = _noDropRects[i].x2; + int ypos2 = _noDropRects[i].y2; + + if (xpos > x + 16) + continue; + if (xpos2 < x) + continue; + if (y < ypos) + continue; + if (ypos2 < y - _lastProcessedItemHeight) + continue; + return 1; + } + + return 0; +} + +int KyraEngine::isDropable(int x, int y) { + debug(9, "KyraEngine::isDropable(%d, %d)", x, y); + x -= 8; + y -= 1; + + if (checkNoDropRects(x, y)) { + return 0; + } + + for (int xpos = x; xpos < x + 16; ++xpos) { + if (_screen->getShapeFlag1(xpos, y) == 0) { + return 0; + } + } + return 1; +} + +void KyraEngine::itemDropDown(int x, int y, int destX, int destY, byte freeItem, int item) { + debug(9, "KyraEngine::itemDropDown(%d, %d, %d, %d, %d, %d)", x, y, destX, destY, freeItem, item); + assert(_currentCharacter->sceneId < _roomTableSize); + Room *currentRoom = &_roomTable[_currentCharacter->sceneId]; + if (x == destX && y == destY) { + currentRoom->itemsXPos[freeItem] = destX; + currentRoom->itemsYPos[freeItem] = destY; + currentRoom->itemsTable[freeItem] = item; + snd_playSoundEffect(0x32); + _animator->animAddGameItem(freeItem, _currentCharacter->sceneId); + return; + } + _screen->hideMouse(); + if (y <= destY) { + int tempY = y; + int addY = 2; + int drawX = x - 8; + int drawY = 0; + + _screen->backUpRect0(drawX, y - 16); + + while (tempY < destY) { + _screen->restoreRect0(drawX, tempY - 16); + tempY += addY; + if (tempY > destY) { + tempY = destY; + } + ++addY; + drawY = tempY - 16; + _screen->backUpRect0(drawX, drawY); + uint32 nextTime = _system->getMillis() + 1 * _tickLength; + _screen->drawShape(0, _shapes[220+item], drawX, drawY, 0, 0); + _screen->updateScreen(); + while (_system->getMillis() < nextTime) { + if ((nextTime - _system->getMillis()) >= 10) + delay(10); + } + } + + bool skip = false; + if (x == destX) { + if (destY - y <= 16) { + skip = true; + } + } + + if (!skip) { + snd_playSoundEffect(0x47); + if (addY < 6) + addY = 6; + + int xDiff = (destX - x) << 4; + xDiff /= addY; + int startAddY = addY; + addY >>= 1; + if (destY - y <= 8) { + addY >>= 1; + } + addY = -addY; + int unkX = x << 4; + while (--startAddY) { + drawX = (unkX >> 4) - 8; + drawY = tempY - 16; + _screen->restoreRect0(drawX, drawY); + tempY += addY; + unkX += xDiff; + if (tempY > destY) { + tempY = destY; + } + ++addY; + drawX = (unkX >> 4) - 8; + drawY = tempY - 16; + _screen->backUpRect0(drawX, drawY); + uint32 nextTime = _system->getMillis() + 1 * _tickLength; + _screen->drawShape(0, _shapes[220+item], drawX, drawY, 0, 0); + _screen->updateScreen(); + while (_system->getMillis() < nextTime) { + if ((nextTime - _system->getMillis()) >= 10) + delay(10); + } + } + _screen->restoreRect0(drawX, drawY); + } else { + _screen->restoreRect0(drawX, tempY - 16); + } + } + currentRoom->itemsXPos[freeItem] = destX; + currentRoom->itemsYPos[freeItem] = destY; + currentRoom->itemsTable[freeItem] = item; + snd_playSoundEffect(0x32); + _animator->animAddGameItem(freeItem, _currentCharacter->sceneId); + _screen->showMouse(); +} + +void KyraEngine::dropItem(int unk1, int item, int x, int y, int unk2) { + debug(9, "KyraEngine::dropItem(%d, %d, %d, %d, %d)", unk1, item, x, y, unk2); + if (processItemDrop(_currentCharacter->sceneId, item, x, y, unk1, unk2)) + return; + snd_playSoundEffect(54); + if (12 == countItemsInScene(_currentCharacter->sceneId)) { + assert(_noDropList); + drawSentenceCommand(_noDropList[0], 6); + } else { + assert(_noDropList); + drawSentenceCommand(_noDropList[1], 6); + } +} + +void KyraEngine::itemSpecialFX(int x, int y, int item) { + debug(9, "KyraEngine::itemSpecialFX(%d, %d, %d)", x, y, item); + if (item == 41) { + itemSpecialFX1(x, y, item); + } else { + itemSpecialFX2(x, y, item); + } +} + +void KyraEngine::itemSpecialFX1(int x, int y, int item) { + debug(9, "KyraEngine::itemSpecialFX1(%d, %d, %d)", x, y, item); + uint8 *shape = _shapes[220+item]; + x -= 8; + int startY = y; + y -= 15; + _screen->hideMouse(); + _screen->backUpRect0(x, y); + for (int i = 1; i <= 16; ++i) { + _screen->setNewShapeHeight(shape, i); + --startY; + _screen->restoreRect0(x, y); + uint32 nextTime = _system->getMillis() + 1 * _tickLength; + _screen->drawShape(0, shape, x, startY, 0, 0); + _screen->updateScreen(); + while (_system->getMillis() < nextTime) { + if ((nextTime - _system->getMillis()) >= 10) + delay(10); + } + } + _screen->restoreRect0(x, y); + _screen->showMouse(); +} + +void KyraEngine::itemSpecialFX2(int x, int y, int item) { + debug(9, "KyraEngine::itemSpecialFX2(%d, %d, %d)", x, y, item); + x -= 8; + y -= 15; + int yAdd = (int8)(((16 - _itemTable[item].height) >> 1) & 0xFF); + _screen->backUpRect0(x, y); + if (item >= 80 && item <= 89) { + snd_playSoundEffect(55); + } + + for (int i = 201; i <= 205; ++i) { + _screen->restoreRect0(x, y); + uint32 nextTime = _system->getMillis() + 3 * _tickLength; + _screen->drawShape(0, _shapes[4+i], x, y + yAdd, 0, 0); + _screen->updateScreen(); + while (_system->getMillis() < nextTime) { + if ((nextTime - _system->getMillis()) >= 10) + delay(10); + } + } + + for (int i = 204; i >= 201; --i) { + _screen->restoreRect0(x, y); + uint32 nextTime = _system->getMillis() + 3 * _tickLength; + _screen->drawShape(0, _shapes[220+item], x, y, 0, 0); + _screen->drawShape(0, _shapes[4+i], x, y + yAdd, 0, 0); + _screen->updateScreen(); + while (_system->getMillis() < nextTime) { + if ((nextTime - _system->getMillis()) >= 10) + delay(10); + } + } + _screen->restoreRect0(x, y); +} + +void KyraEngine::magicOutMouseItem(int animIndex, int itemPos) { + debug(9, "KyraEngine::magicOutMouseItem(%d, %d)", animIndex, itemPos); + int videoPageBackUp = _screen->_curPage; + _screen->_curPage = 0; + int x = 0, y = 0; + if (itemPos == -1) { + x = _mouseX - 12; + y = _mouseY - 18; + } else { + x = _itemPosX[itemPos] - 4; + y = _itemPosY[itemPos] - 3; + } + + if (_itemInHand == -1 && itemPos == -1) { + return; + } + + int tableIndex = 0, loopStart = 0, maxLoops = 0; + if (animIndex == 0) { + tableIndex = _rnd.getRandomNumberRng(0, 5); + loopStart = 35; + maxLoops = 9; + } else if (animIndex == 1) { + tableIndex = _rnd.getRandomNumberRng(0, 11); + loopStart = 115; + maxLoops = 8; + } else if (animIndex == 2) { + tableIndex = 0; + loopStart = 124; + maxLoops = 4; + } else { + tableIndex = -1; + } + + if (animIndex == 2) { + snd_playSoundEffect(0x5E); + } else { + snd_playSoundEffect(0x37); + } + _screen->hideMouse(); + _screen->backUpRect1(x, y); + + for (int shape = _magicMouseItemStartFrame[animIndex]; shape <= _magicMouseItemEndFrame[animIndex]; ++shape) { + _screen->restoreRect1(x, y); + uint32 nextTime = _system->getMillis() + 4 * _tickLength; + _screen->drawShape(0, _shapes[220+_itemInHand], x + 4, y + 3, 0, 0); + if (tableIndex == -1) { + _screen->drawShape(0, _shapes[4+shape], x, y, 0, 0); + } else { + specialMouseItemFX(shape, x, y, animIndex, tableIndex, loopStart, maxLoops); + } + _screen->updateScreen(); + while (_system->getMillis() < nextTime) { + if (nextTime - _system->getMillis() >= 10) + delay(10); + } + } + + if (itemPos != -1) { + _screen->restoreRect1(x, y); + _screen->fillRect(_itemPosX[itemPos], _itemPosY[itemPos], _itemPosX[itemPos] + 15, _itemPosY[itemPos] + 15, 12, 0); + _screen->backUpRect1(x, y); + } + + for (int shape = _magicMouseItemStartFrame2[animIndex]; shape <= _magicMouseItemEndFrame2[animIndex]; ++shape) { + _screen->restoreRect1(x, y); + uint32 nextTime = _system->getMillis() + 4 * _tickLength; + _screen->drawShape(0, _shapes[220+_itemInHand], x + 4, y + 3, 0, 0); + if (tableIndex == -1) { + _screen->drawShape(0, _shapes[4+shape], x, y, 0, 0); + } else { + specialMouseItemFX(shape, x, y, animIndex, tableIndex, loopStart, maxLoops); + } + _screen->updateScreen(); + while (_system->getMillis() < nextTime) { + if (nextTime - _system->getMillis() >= 10) + delay(10); + } + } + _screen->restoreRect1(x, y); + if (itemPos == -1) { + _screen->setMouseCursor(1, 1, _shapes[4]); + _itemInHand = -1; + } else { + _characterList[0].inventoryItems[itemPos] = 0xFF; + _screen->hideMouse(); + _screen->fillRect(_itemPosX[itemPos], _itemPosY[itemPos], _itemPosX[itemPos] + 15, _itemPosY[itemPos] + 15, 12, 0); + _screen->showMouse(); + } + _screen->showMouse(); + _screen->_curPage = videoPageBackUp; +} + +void KyraEngine::magicInMouseItem(int animIndex, int item, int itemPos) { + debug(9, "KyraEngine::magicInMouseItem(%d, %d, %d)", animIndex, item, itemPos); + int videoPageBackUp = _screen->_curPage; + _screen->_curPage = 0; + int x = 0, y = 0; + if (itemPos == -1) { + x = _mouseX - 12; + y = _mouseY - 18; + } else { + x = _itemPosX[itemPos] - 4; + y = _itemPosX[itemPos] - 3; + } + if (item < 0) + return; + + int tableIndex = -1, loopStart = 0, maxLoops = 0; + if (animIndex == 0) { + tableIndex = _rnd.getRandomNumberRng(0, 5); + loopStart = 35; + maxLoops = 9; + } else if (animIndex == 1) { + tableIndex = _rnd.getRandomNumberRng(0, 11); + loopStart = 115; + maxLoops = 8; + } else if (animIndex == 2) { + tableIndex = 0; + loopStart = 124; + maxLoops = 4; + } + + _screen->hideMouse(); + _screen->backUpRect1(x, y); + if (animIndex == 2) { + snd_playSoundEffect(0x5E); + } else { + snd_playSoundEffect(0x37); + } + + for (int shape = _magicMouseItemStartFrame[animIndex]; shape <= _magicMouseItemEndFrame[animIndex]; ++shape) { + _screen->restoreRect1(x, y); + uint32 nextTime = _system->getMillis() + 4 * _tickLength; + if (tableIndex == -1) { + _screen->drawShape(0, _shapes[4+shape], x, y, 0, 0); + } else { + specialMouseItemFX(shape, x, y, animIndex, tableIndex, loopStart, maxLoops); + } + _screen->updateScreen(); + while (_system->getMillis() < nextTime) { + if (nextTime - _system->getMillis() >= 10) + delay(10); + } + } + + for (int shape = _magicMouseItemStartFrame2[animIndex]; shape <= _magicMouseItemEndFrame2[animIndex]; ++shape) { + _screen->restoreRect1(x, y); + uint32 nextTime = _system->getMillis() + 4 * _tickLength; + if (tableIndex == -1) { + _screen->drawShape(0, _shapes[4+shape], x, y, 0, 0); + } else { + specialMouseItemFX(shape, x, y, animIndex, tableIndex, loopStart, maxLoops); + } + _screen->updateScreen(); + while (_system->getMillis() < nextTime) { + if (nextTime - _system->getMillis() >= 10) + delay(10); + } + } + _screen->restoreRect1(x, y); + if (itemPos == -1) { + _screen->setMouseCursor(8, 15, _shapes[220+item]); + _itemInHand = item; + } else { + _characterList[0].inventoryItems[itemPos] = item; + _screen->hideMouse(); + _screen->drawShape(0, _shapes[220+item], _itemPosX[itemPos], _itemPosY[itemPos], 0, 0); + _screen->showMouse(); + } + _screen->showMouse(); + _screen->_curPage = videoPageBackUp; +} + +void KyraEngine::specialMouseItemFX(int shape, int x, int y, int animIndex, int tableIndex, int loopStart, int maxLoops) { + debug(9, "KyraEngine::specialMouseItemFX(%d, %d, %d, %d, %d, %d, %d)", shape, x, y, animIndex, tableIndex, loopStart, maxLoops); + static const uint8 table1[] = { + 0x23, 0x45, 0x55, 0x72, 0x84, 0xCF, 0x00, 0x00 + }; + static const uint8 table2[] = { + 0x73, 0xB5, 0x80, 0x21, 0x13, 0x39, 0x45, 0x55, 0x62, 0xB4, 0xCF, 0xD8 + }; + static const uint8 table3[] = { + 0x7C, 0xD0, 0x74, 0x84, 0x87, 0x00, 0x00, 0x00 + }; + int tableValue = 0; + if (animIndex == 0) { + tableValue = table1[tableIndex]; + } else if (animIndex == 1) { + tableValue = table2[tableIndex]; + } else if (animIndex == 2) { + tableValue = table3[tableIndex]; + } else { + return; + } + processSpecialMouseItemFX(shape, x, y, tableValue, loopStart, maxLoops); +} + +void KyraEngine::processSpecialMouseItemFX(int shape, int x, int y, int tableValue, int loopStart, int maxLoops) { + debug(9, "KyraEngine::processSpecialMouseItemFX(%d, %d, %d, %d, %d, %d)", shape, x, y, tableValue, loopStart, maxLoops); + uint8 shapeColorTable[16]; + uint8 *shapePtr = _shapes[4+shape] + 10; + if (_features & GF_TALKIE) + shapePtr += 2; + for (int i = 0; i < 16; ++i) { + shapeColorTable[i] = shapePtr[i]; + } + for (int i = loopStart; i < loopStart + maxLoops; ++i) { + for (int i2 = 0; i2 < 16; ++i2) { + if (shapePtr[i2] == i) { + shapeColorTable[i2] = (i + tableValue) - loopStart; + } + } + } + _screen->drawShape(0, _shapes[4+shape], x, y, 0, 0x8000, shapeColorTable); +} + +void KyraEngine::updatePlayerItemsForScene() { + debug(9, "KyraEngine::updatePlayerItemsForScene()"); + if (_itemInHand >= 29 && _itemInHand < 33) { + ++_itemInHand; + if (_itemInHand > 33) + _itemInHand = 33; + _screen->hideMouse(); + _screen->setMouseCursor(8, 15, _shapes[220+_itemInHand]); + _screen->showMouse(); + } + + bool redraw = false; + for (int i = 0; i < 10; ++i) { + uint8 item = _currentCharacter->inventoryItems[i]; + if (item >= 29 && item < 33) { + ++item; + if (item > 33) + item = 33; + _currentCharacter->inventoryItems[i] = item; + redraw = true; + } + } + + if (redraw) { + _screen->hideMouse(); + redrawInventory(0); + _screen->showMouse(); + } + + if (_itemInHand == 33) { + magicOutMouseItem(2, -1); + } + + _screen->hideMouse(); + for (int i = 0; i < 10; ++i) { + uint8 item = _currentCharacter->inventoryItems[i]; + if (item == 33) { + magicOutMouseItem(2, i); + } + } + _screen->showMouse(); +} + +void KyraEngine::redrawInventory(int page) { + int videoPageBackUp = _screen->_curPage; + _screen->_curPage = page; + _screen->hideMouse(); + for (int i = 0; i < 10; ++i) { + _screen->fillRect(_itemPosX[i], _itemPosY[i], _itemPosX[i] + 15, _itemPosY[i] + 15, 12, page); + if (_currentCharacter->inventoryItems[i] != 0xFF) { + uint8 item = _currentCharacter->inventoryItems[i]; + _screen->drawShape(page, _shapes[220+item], _itemPosX[i], _itemPosY[i], 0, 0); + } + } + _screen->showMouse(); + _screen->_curPage = videoPageBackUp; + _screen->updateScreen(); +} + +} // end of namespace Kyra diff --git a/engines/kyra/kyra.cpp b/engines/kyra/kyra.cpp new file mode 100644 index 0000000000..c85ce9b323 --- /dev/null +++ b/engines/kyra/kyra.cpp @@ -0,0 +1,1215 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-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 "common/md5.h" +#include "common/savefile.h" + +#include "sound/mixer.h" +#include "sound/mididrv.h" + +#include "gui/message.h" + +#include "kyra/kyra.h" +#include "kyra/resource.h" +#include "kyra/screen.h" +#include "kyra/script.h" +#include "kyra/seqplayer.h" +#include "kyra/sound.h" +#include "kyra/sprites.h" +#include "kyra/wsamovie.h" +#include "kyra/animator.h" +#include "kyra/text.h" +#include "kyra/debugger.h" + +using namespace Kyra; + +enum { + // We only compute MD5 of the first megabyte of our data files. + kMD5FileSizeLimit = 1024 * 1024 +}; + +// Kyra MD5 detection brutally ripped from the Gobliins engine. +struct KyraGameSettings { + const char *gameid; + const char *description; + byte id; + uint32 features; + const char *md5sum; + const char *checkFile; + GameSettings toGameSettings() const { + GameSettings dummy = { gameid, description, features }; + return dummy; + } +}; + +// We could get rid of md5 detection at least for kyra 1 since we can locate all +// needed files for detecting the right language and version (Floppy, Talkie) +static const KyraGameSettings kyra_games[] = { + { "kyra1", "The Legend of Kyrandia", GI_KYRA1, GF_ENGLISH | GF_FLOPPY, // english floppy 1.0 from Malice + "3c244298395520bb62b5edfe41688879", "GEMCUT.EMC" }, + { "kyra1", "The Legend of Kyrandia", GI_KYRA1, GF_ENGLISH | GF_FLOPPY, + "796e44863dd22fa635b042df1bf16673", "GEMCUT.EMC" }, + { "kyra1", "The Legend of Kyrandia", GI_KYRA1, GF_FRENCH | GF_FLOPPY, + "abf8eb360e79a6c2a837751fbd4d3d24", "GEMCUT.EMC" }, + { "kyra1", "The Legend of Kyrandia", GI_KYRA1, GF_GERMAN | GF_FLOPPY, + "6018e1dfeaca7fe83f8d0b00eb0dd049", "GEMCUT.EMC"}, + { "kyra1", "The Legend of Kyrandia", GI_KYRA1, GF_GERMAN | GF_FLOPPY, // from Arne.F + "f0b276781f47c130f423ec9679fe9ed9", "GEMCUT.EMC"}, + { "kyra1", "The Legend of Kyrandia", GI_KYRA1, GF_SPANISH | GF_FLOPPY, // from VooD + "8909b41596913b3f5deaf3c9f1017b01", "GEMCUT.EMC"}, + { "kyra1", "The Legend of Kyrandia", GI_KYRA1, GF_SPANISH | GF_FLOPPY, // floppy 1.8 from clemmy + "747861d2a9c643c59fdab570df5b9093", "GEMCUT.EMC"}, + { "kyra1", "The Legend of Kyrandia", GI_KYRA1, GF_ENGLISH | GF_TALKIE, + "fac399fe62f98671e56a005c5e94e39f", "GEMCUT.PAK" }, + { "kyra1", "The Legend of Kyrandia", GI_KYRA1, GF_GERMAN | GF_TALKIE, + "230f54e6afc007ab4117159181a1c722", "GEMCUT.PAK" }, + { "kyra1", "The Legend of Kyrandia", GI_KYRA1, GF_FRENCH | GF_TALKIE, + "b037c41768b652a040360ffa3556fd2a", "GEMCUT.PAK" }, + { "kyra1", "The Legend of Kyrandia Demo", GI_KYRA1, GF_DEMO | GF_ENGLISH, + "fb722947d94897512b13b50cc84fd648", "DEMO1.WSA" }, + { 0, 0, 0, 0, 0, 0 } +}; + +// Keep list of different supported games +struct KyraGameList { + const char *gameid; + const char *description; + uint32 features; + GameSettings toGameSettings() const { + GameSettings dummy = { gameid, description, features }; + return dummy; + } +}; + +static const KyraGameList kyra_list[] = { + { "kyra1", "The Legend of Kyrandia", 0 }, + { 0, 0, 0 } +}; + +struct KyraLanguageTable { + const char *file; + uint32 language; + Common::Language detLanguage; +}; + +static const KyraLanguageTable kyra_languages[] = { + { "MAIN15.CPS", GF_ENGLISH, Common::EN_USA }, + { "MAIN_ENG.CPS", GF_ENGLISH, Common::EN_USA }, + { "MAIN_FRE.CPS", GF_FRENCH, Common::FR_FRA }, + { "MAIN_GER.CPS", GF_GERMAN, Common::DE_DEU }, + { "MAIN_SPA.CPS", GF_SPANISH, Common::ES_ESP }, + { 0, 0, Common::UNK_LANG } +}; + +static Common::Language convertKyraLang(uint32 features) { + if (features & GF_ENGLISH) { + return Common::EN_USA; + } else if (features & GF_FRENCH) { + return Common::FR_FRA; + } else if (features & GF_GERMAN) { + return Common::DE_DEU; + } else if (features & GF_SPANISH) { + return Common::ES_ESP; + } + return Common::UNK_LANG; +} + +GameList Engine_KYRA_gameList() { + GameList games; + const KyraGameList *g = kyra_list; + + while (g->gameid) { + games.push_back(g->toGameSettings()); + g++; + } + return games; +} + +DetectedGameList Engine_KYRA_detectGames(const FSList &fslist) { + DetectedGameList detectedGames; + const KyraGameSettings *g; + FSList::const_iterator file; + + // Iterate over all files in the given directory + bool isFound = false; + for (file = fslist.begin(); file != fslist.end(); file++) { + if (file->isDirectory()) + continue; + + for (g = kyra_games; g->gameid; g++) { + if (scumm_stricmp(file->displayName().c_str(), g->checkFile) == 0) + isFound = true; + } + if (isFound) + break; + } + + if (file == fslist.end()) + return detectedGames; + + uint8 md5sum[16]; + char md5str[32 + 1]; + + if (Common::md5_file(file->path().c_str(), md5sum, NULL, kMD5FileSizeLimit)) { + for (int i = 0; i < 16; i++) { + sprintf(md5str + i * 2, "%02x", (int)md5sum[i]); + } + for (g = kyra_games; g->gameid; g++) { + if (strcmp(g->md5sum, (char *)md5str) == 0) { + detectedGames.push_back(DetectedGame(g->toGameSettings(), convertKyraLang(g->features), Common::kPlatformUnknown)); + } + } + if (detectedGames.isEmpty()) { + debug("Unknown MD5 (%s)! Please report the details (language, platform, etc.) of this game to the ScummVM team\n", md5str); + + const KyraGameList *g1 = kyra_list; + while (g1->gameid) { + detectedGames.push_back(g1->toGameSettings()); + g1++; + } + } + } + return detectedGames; +} + +Engine *Engine_KYRA_create(GameDetector *detector, OSystem *system) { + return new KyraEngine(detector, system); +} + +REGISTER_PLUGIN(KYRA, "Legend of Kyrandia Engine") + +namespace Kyra { + +KyraEngine::KyraEngine(GameDetector *detector, OSystem *system) + : Engine(system) { + _seq_Forest = _seq_KallakWriting = _seq_KyrandiaLogo = _seq_KallakMalcolm = + _seq_MalcolmTree = _seq_WestwoodLogo = _seq_Demo1 = _seq_Demo2 = _seq_Demo3 = + _seq_Demo4 = 0; + + _seq_WSATable = _seq_CPSTable = _seq_COLTable = _seq_textsTable = 0; + _seq_WSATable_Size = _seq_CPSTable_Size = _seq_COLTable_Size = _seq_textsTable_Size = 0; + + _roomFilenameTable = _characterImageTable = 0; + _roomFilenameTableSize = _characterImageTableSize = 0; + _itemList = _takenList = _placedList = _droppedList = _noDropList = 0; + _itemList_Size = _takenList_Size = _placedList_Size = _droppedList_Size = _noDropList_Size = 0; + _putDownFirst = _waitForAmulet = _blackJewel = _poisonGone = _healingTip = 0; + _putDownFirst_Size = _waitForAmulet_Size = _blackJewel_Size = _poisonGone_Size = _healingTip_Size = 0; + _thePoison = _fluteString = _wispJewelStrings = _magicJewelString = _flaskFull = _fullFlask = 0; + _thePoison_Size = _fluteString_Size = _wispJewelStrings_Size = 0; + _magicJewelString_Size = _flaskFull_Size = _fullFlask_Size = 0; + + _defaultShapeTable = _healingShapeTable = _healingShape2Table = 0; + _defaultShapeTableSize = _healingShapeTableSize = _healingShape2TableSize = 0; + _posionDeathShapeTable = _fluteAnimShapeTable = 0; + _posionDeathShapeTableSize = _fluteAnimShapeTableSize = 0; + _winterScrollTable = _winterScroll1Table = _winterScroll2Table = 0; + _winterScrollTableSize = _winterScroll1TableSize = _winterScroll2TableSize = 0; + _drinkAnimationTable = _brandonToWispTable = _magicAnimationTable = _brandonStoneTable = 0; + _drinkAnimationTableSize = _brandonToWispTableSize = _magicAnimationTableSize = _brandonStoneTableSize = 0; + + // Setup mixer + if (!_mixer->isReady()) { + warning("Sound initialization failed."); + } + + _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, ConfMan.getInt("sfx_volume")); + _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, ConfMan.getInt("music_volume")); + _mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, ConfMan.getInt("speech_volume")); + + // Detect game features based on MD5. Again brutally ripped from Gobliins. + uint8 md5sum[16]; + char md5str[32 + 1]; + + const KyraGameSettings *g; + bool found = false; + + // TODO + // Fallback. Maybe we will be able to determine game type from game + // data contents + _features = 0; + + for (g = kyra_games; g->gameid; g++) { + if (!Common::File::exists(g->checkFile)) + continue; + + if (Common::md5_file(g->checkFile, md5sum, ConfMan.get("path").c_str(), kMD5FileSizeLimit)) { + for (int j = 0; j < 16; j++) { + sprintf(md5str + j*2, "%02x", (int)md5sum[j]); + } + } else + continue; + + if (strcmp(g->md5sum, (char *)md5str) == 0) { + _features = g->features; + _game = g->id; + + if (g->description) + g_system->setWindowCaption(g->description); + + found = true; + break; + } + } + + if (!found) { + debug("Unknown MD5 (%s)! Please report the details (language, platform, etc.) of this game to the ScummVM team", md5str); + _features = 0; + _game = GI_KYRA1; + Common::File test; + if (test.open("INTRO.VRM")) { + _features |= GF_TALKIE; + } else { + _features |= GF_FLOPPY; + } + + // tries to detect the language + const KyraLanguageTable *lang = kyra_languages; + for (; lang->file; ++lang) { + if (test.open(lang->file)) { + _features |= lang->language; + found = true; + break; + } + } + + if (!found) { + _features |= GF_LNGUNK; + } + } +} + +int KyraEngine::init(GameDetector &detector) { + _system->beginGFXTransaction(); + initCommonGFX(detector); + //for debug reasons (see Screen::updateScreen) + //_system->initSize(640, 200); + _system->initSize(320, 200); + _system->endGFXTransaction(); + + // for now we prefer MIDI-to-Adlib conversion over native midi + int midiDriver = MidiDriver::detectMusicDriver(MDT_MIDI | MDT_ADLIB/* | MDT_PREFER_MIDI*/); + bool native_mt32 = ((midiDriver == MD_MT32) || ConfMan.getBool("native_mt32")); + + MidiDriver *driver = MidiDriver::createMidi(midiDriver); + if (midiDriver == MD_ADLIB) { + // In this case we should play the Adlib tracks, but for now + // the automagic MIDI-to-Adlib conversion will do. + } else if (native_mt32) { + driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE); + } + + _sound = new SoundPC(driver, _mixer, this); + assert(_sound); + static_cast<SoundPC*>(_sound)->hasNativeMT32(native_mt32); + _sound->setVolume(255); + + _saveFileMan = _system->getSavefileManager(); + assert(_saveFileMan); + _res = new Resource(this); + assert(_res); + _screen = new Screen(this, _system); + assert(_screen); + _sprites = new Sprites(this, _system); + assert(_sprites); + _seq = new SeqPlayer(this, _system); + assert(_seq); + _animator = new ScreenAnimator(this, _system); + assert(_animator); + _animator->init(5, 11, 12); + assert(*_animator); + _text = new TextDisplayer(_screen); + assert(_text); + + _paletteChanged = 1; + _currentCharacter = 0; + _characterList = new Character[11]; + assert(_characterList); + for (int i = 0; i < 11; ++i) { + memset(&_characterList[i], 0, sizeof(Character)); + memset(_characterList[i].inventoryItems, 0xFF, sizeof(_characterList[i].inventoryItems)); + } + _characterList[0].sceneId = 5; + _characterList[0].height = 48; + _characterList[0].facing = 3; + _characterList[0].currentAnimFrame = 7; + + _scriptInterpreter = new ScriptHelper(this); + assert(_scriptInterpreter); + + _npcScriptData = new ScriptData; + memset(_npcScriptData, 0, sizeof(ScriptData)); + assert(_npcScriptData); + _npcScript = new ScriptState; + assert(_npcScript); + memset(_npcScript, 0, sizeof(ScriptState)); + + _scriptMain = new ScriptState; + assert(_scriptMain); + memset(_scriptMain, 0, sizeof(ScriptState)); + + _scriptClickData = new ScriptData; + assert(_scriptClickData); + memset(_scriptClickData, 0, sizeof(ScriptData)); + _scriptClick = new ScriptState; + assert(_scriptClick); + memset(_scriptClick, 0, sizeof(ScriptState)); + + _debugger = new Debugger(this); + assert(_debugger); + memset(_shapes, 0, sizeof(_shapes)); + + for (int i = 0; i < ARRAYSIZE(_movieObjects); ++i) { + _movieObjects[i] = createWSAMovie(); + } + + memset(_flagsTable, 0, sizeof(_flagsTable)); + + _abortWalkFlag = false; + _abortWalkFlag2 = false; + _talkingCharNum = -1; + _charSayUnk3 = -1; + _mouseX = _mouseY = -1; + memset(_currSentenceColor, 0, 3); + _startSentencePalIndex = -1; + _fadeText = false; + + _cauldronState = 0; + _crystalState[0] = _crystalState[1] = -1; + + _brandonStatusBit = 0; + _brandonStatusBit0x02Flag = _brandonStatusBit0x20Flag = 10; + _brandonPosX = _brandonPosY = -1; + _deathHandler = 0xFF; + _poisonDeathCounter = 0; + + memset(_itemTable, 0, sizeof(_itemTable)); + memset(_exitList, 0xFFFF, sizeof(_exitList)); + _exitListPtr = 0; + _pathfinderFlag = _pathfinderFlag2 = 0; + _lastFindWayRet = 0; + _sceneChangeState = _loopFlag2 = 0; + _timerNextRun = 0; + + _movFacingTable = new int[150]; + assert(_movFacingTable); + _movFacingTable[0] = 8; + + _configTalkspeed = 1; + + _marbleVaseItem = -1; + memset(_foyerItemTable, -1, sizeof(_foyerItemTable)); + _mouseState = _itemInHand = -1; + _handleInput = false; + + _currentRoom = 0xFFFF; + _scenePhasingFlag = 0; + _lastProcessedItem = 0; + _lastProcessedItemHeight = 16; + + _unkScreenVar1 = 1; + _unkScreenVar2 = 0; + _unkScreenVar3 = 0; + _unkAmuletVar = 0; + + _endSequenceNeedLoading = 1; + _malcolmFlag = 0; + _beadStateVar = 0; + _endSequenceSkipFlag = 0; + _unkEndSeqVar2 = 0; + _endSequenceBackUpRect = 0; + _unkEndSeqVar4 = 0; + _unkEndSeqVar5 = 0; + _lastDisplayedPanPage = 0; + memset(_panPagesTable, 0, sizeof(_panPagesTable)); + _finalA = _finalB = _finalC = 0; + memset(&_kyragemFadingState, 0, sizeof(_kyragemFadingState)); + _kyragemFadingState.gOffset = 0x13; + _kyragemFadingState.bOffset = 0x13; + + memset(_specialPalettes, 0, sizeof(_specialPalettes)); + _mousePressFlag = false; + + _targetName = detector._targetName; + _menuDirectlyToLoad = false; + + _lastMusicCommand = 0; + + _gameSpeed = 60; + _tickLength = (uint8)(1000.0 / _gameSpeed); + + return 0; +} + +KyraEngine::~KyraEngine() { + closeFinalWsa(); + _scriptInterpreter->unloadScript(_npcScriptData); + _scriptInterpreter->unloadScript(_scriptClickData); + + delete _debugger; + delete _sprites; + delete _animator; + delete _screen; + delete _res; + delete _sound; + delete _saveFileMan; + delete _seq; + delete _scriptInterpreter; + delete _text; + + delete _npcScriptData; + delete _scriptMain; + + delete _scriptClickData; + delete _scriptClick; + + delete [] _characterList; + + delete [] _movFacingTable; + + free(_scrollUpButton.process0PtrShape); + free(_scrollUpButton.process1PtrShape); + free(_scrollUpButton.process2PtrShape); + free(_scrollDownButton.process0PtrShape); + free(_scrollDownButton.process1PtrShape); + free(_scrollDownButton.process2PtrShape); + + for (int i = 0; i < ARRAYSIZE(_shapes); ++i) { + if (_shapes[i] != 0) { + free(_shapes[i]); + _shapes[i] = 0; + for (int i2 = 0; i2 < ARRAYSIZE(_shapes); i2++) { + if (_shapes[i2] == _shapes[i] && i2 != i) { + _shapes[i2] = 0; + } + } + } + } + for (int i = 0; i < ARRAYSIZE(_sceneAnimTable); ++i) { + free(_sceneAnimTable[i]); + } +} + +void KyraEngine::errorString(const char *buf1, char *buf2) { + strcpy(buf2, buf1); +} + +int KyraEngine::go() { + _quitFlag = false; + uint32 sz; + + res_loadResources(); + if (_features & GF_FLOPPY) { + _screen->loadFont(Screen::FID_6_FNT, _res->fileData("6.FNT", &sz)); + } + _screen->loadFont(Screen::FID_8_FNT, _res->fileData("8FAT.FNT", &sz)); + _screen->setScreenDim(0); + + _abortIntroFlag = false; + + if (_features & GF_DEMO) { + seq_demo(); + } else { + setGameFlag(0xF3); + setGameFlag(0xFD); + setGameFlag(0xEF); + seq_intro(); + if (_skipIntroFlag &&_abortIntroFlag) + resetGameFlag(0xEF); + startup(); + resetGameFlag(0xEF); + mainLoop(); + } + quitGame(); + return 0; +} + +void KyraEngine::startup() { + debug(9, "KyraEngine::startup()"); + static const uint8 colorMap[] = { 0, 0, 0, 0, 12, 12, 12, 0, 0, 0, 0, 0 }; + _screen->setTextColorMap(colorMap); +// _screen->setFont(Screen::FID_6_FNT); + _screen->setAnimBlockPtr(3750); + memset(_sceneAnimTable, 0, sizeof(_sceneAnimTable)); + loadMouseShapes(); + _currentCharacter = &_characterList[0]; + for (int i = 1; i < 5; ++i) + _animator->setCharacterDefaultFrame(i); + for (int i = 5; i <= 10; ++i) + setCharactersPositions(i); + _animator->setCharactersHeight(); + resetBrandonPoisonFlags(); + _maskBuffer = _screen->getPagePtr(5); + _screen->_curPage = 0; + // XXX + for (int i = 0; i < 0x0C; ++i) { + int size = _screen->getRectSize(3, 24); + _shapes[365+i] = (byte*)malloc(size); + } + _shapes[0] = (uint8*)malloc(_screen->getRectSize(3, 24)); + memset(_shapes[0], 0, _screen->getRectSize(3, 24)); + _shapes[1] = (uint8*)malloc(_screen->getRectSize(4, 32)); + memset(_shapes[1], 0, _screen->getRectSize(4, 32)); + _shapes[2] = (uint8*)malloc(_screen->getRectSize(8, 69)); + memset(_shapes[2], 0, _screen->getRectSize(8, 69)); + _shapes[3] = (uint8*)malloc(_screen->getRectSize(8, 69)); + memset(_shapes[3], 0, _screen->getRectSize(8, 69)); + for (int i = 0; i < _roomTableSize; ++i) { + for (int item = 0; item < 12; ++item) { + _roomTable[i].itemsTable[item] = 0xFF; + _roomTable[i].itemsXPos[item] = 0xFFFF; + _roomTable[i].itemsYPos[item] = 0xFF; + _roomTable[i].needInit[item] = 0; + } + } + loadCharacterShapes(); + loadSpecialEffectShapes(); + loadItems(); + loadButtonShapes(); + initMainButtonList(); + loadMainScreen(); + setupTimers(); + loadPalette("PALETTE.COL", _screen->_currentPalette); + + // XXX + _animator->initAnimStateList(); + setCharactersInDefaultScene(); + + if (!_scriptInterpreter->loadScript("_STARTUP.EMC", _npcScriptData, _opcodeTable, _opcodeTableSize, 0)) { + error("Could not load \"_STARTUP.EMC\" script"); + } + _scriptInterpreter->initScript(_scriptMain, _npcScriptData); + if (!_scriptInterpreter->startScript(_scriptMain, 0)) { + error("Could not start script function 0 of script \"_STARTUP.EMC\""); + } + while (_scriptInterpreter->validScript(_scriptMain)) { + _scriptInterpreter->runScript(_scriptMain); + } + + _scriptInterpreter->unloadScript(_npcScriptData); + if (!_scriptInterpreter->loadScript("_NPC.EMC", _npcScriptData, _opcodeTable, _opcodeTableSize, 0)) { + error("Could not load \"_NPC.EMC\" script"); + } + + snd_playTheme(1); + snd_setSoundEffectFile(1); + enterNewScene(_currentCharacter->sceneId, _currentCharacter->facing, 0, 0, 1); + + if (_abortIntroFlag && _skipFlag) { + _menuDirectlyToLoad = true; + _screen->setMouseCursor(1, 1, _shapes[4]); + buttonMenuCallback(0); + _menuDirectlyToLoad = false; + } else + saveGame(getSavegameFilename(0), "New game"); +} + +void KyraEngine::mainLoop() { + debug(9, "KyraEngine::mainLoop()"); + + while (!_quitFlag) { + int32 frameTime = (int32)_system->getMillis(); + _skipFlag = false; + + if (_currentCharacter->sceneId == 210) { + updateKyragemFading(); + if (seq_playEnd()) { + if (_deathHandler != 8) + break; + } + } + + if (_deathHandler != 0xFF) { + // this is only used until the original gui is implemented + GUI::MessageDialog dialog("Brandon is dead! Game over!", "Quit"); + dialog.runModal(); + break; + } + + if (_brandonStatusBit & 2) { + if (_brandonStatusBit0x02Flag) + _animator->animRefreshNPC(0); + } + if (_brandonStatusBit & 0x20) { + if (_brandonStatusBit0x20Flag) { + _animator->animRefreshNPC(0); + _brandonStatusBit0x20Flag = 0; + } + } + + _screen->showMouse(); + + processButtonList(_buttonList); + updateMousePointer(); + updateGameTimers(); + updateTextFade(); + + _handleInput = true; + delay((frameTime + _gameSpeed) - _system->getMillis(), true, true); + _handleInput = false; + } +} + +void KyraEngine::quitGame() { + res_unloadResources(RES_ALL); + + for (int i = 0; i < ARRAYSIZE(_movieObjects); ++i) { + _movieObjects[i]->close(); + delete _movieObjects[i]; + _movieObjects[i] = 0; + } + + _system->quit(); +} + +void KyraEngine::delay(uint32 amount, bool update, bool isMainLoop) { + OSystem::Event event; + char saveLoadSlot[20]; + char savegameName[14]; + + _mousePressFlag = false; + uint32 start = _system->getMillis(); + do { + while (_system->pollEvent(event)) { + switch (event.type) { + case OSystem::EVENT_KEYDOWN: + if (event.kbd.keycode >= '1' && event.kbd.keycode <= '9' && + (event.kbd.flags == OSystem::KBD_CTRL || event.kbd.flags == OSystem::KBD_ALT) && isMainLoop) { + sprintf(saveLoadSlot, "%s.00%d", _targetName.c_str(), event.kbd.keycode - '0'); + if (event.kbd.flags == OSystem::KBD_CTRL) + loadGame(saveLoadSlot); + else { + sprintf(savegameName, "Quicksave %d", event.kbd.keycode - '0'); + saveGame(saveLoadSlot, savegameName); + } + } else if (event.kbd.flags == OSystem::KBD_CTRL) { + if (event.kbd.keycode == 'd') + _debugger->attach(); + else if (event.kbd.keycode == 'q') + _quitFlag = true; + } else if (event.kbd.keycode == '.') + _skipFlag = true; + else if (event.kbd.keycode == 13 || event.kbd.keycode == 32 || event.kbd.keycode == 27) { + _abortIntroFlag = true; + _skipFlag = true; + } + + break; + case OSystem::EVENT_MOUSEMOVE: + _mouseX = event.mouse.x; + _mouseY = event.mouse.y; + _system->updateScreen(); + break; + case OSystem::EVENT_QUIT: + quitGame(); + break; + case OSystem::EVENT_LBUTTONDOWN: + _mousePressFlag = true; + if (_abortWalkFlag2) { + _abortWalkFlag = true; + _mouseX = event.mouse.x; + _mouseY = event.mouse.y; + } + if (_handleInput) { + _mouseX = event.mouse.x; + _mouseY = event.mouse.y; + _handleInput = false; + processInput(_mouseX, _mouseY); + _handleInput = true; + } else + _skipFlag = true; + break; + default: + break; + } + } + if (_debugger->isAttached()) + _debugger->onFrame(); + + if (update) + _sprites->updateSceneAnims(); + _animator->updateAllObjectShapes(); + + if (_currentCharacter && _currentCharacter->sceneId == 210) { + updateKyragemFading(); + } + + if (amount > 0 && !_skipFlag) { + _system->delayMillis((amount > 10) ? 10 : amount); + } + } while (!_skipFlag && _system->getMillis() < start + amount); + +} + +void KyraEngine::waitForEvent() { + bool finished = false; + OSystem::Event event; + while (!finished) { + while (_system->pollEvent(event)) { + switch (event.type) { + case OSystem::EVENT_KEYDOWN: + finished = true; + break; + case OSystem::EVENT_MOUSEMOVE: + _mouseX = event.mouse.x; + _mouseY = event.mouse.y; + break; + case OSystem::EVENT_QUIT: + quitGame(); + break; + case OSystem::EVENT_LBUTTONDOWN: + finished = true; + _skipFlag = true; + break; + default: + break; + } + } + + if (_debugger->isAttached()) + _debugger->onFrame(); + + _system->delayMillis(10); + } +} + +void KyraEngine::delayWithTicks(int ticks) { + uint32 nextTime = _system->getMillis() + ticks * _tickLength; + while (_system->getMillis() < nextTime && !_skipFlag) { + _sprites->updateSceneAnims(); + _animator->updateAllObjectShapes(); + if (_currentCharacter->sceneId == 210) { + updateKyragemFading(); + seq_playEnd(); + } + } +} + +#pragma mark - +#pragma mark - Animation/shape specific code +#pragma mark - + +void KyraEngine::setupShapes123(const Shape *shapeTable, int endShape, int flags) { + debug(9, "KyraEngine::setupShapes123(0x%X, startShape, flags)", shapeTable, endShape, flags); + for (int i = 123; i <= 172; ++i) { + _shapes[4+i] = NULL; + } + uint8 curImage = 0xFF; + int curPageBackUp = _screen->_curPage; + _screen->_curPage = 8; // we are using page 8 here in the original page 2 was backuped and then used for this stuff + int shapeFlags = 2; + if (flags) + shapeFlags = 3; + for (int i = 123; i < 123+endShape; ++i) { + uint8 newImage = shapeTable[i-123].imageIndex; + if (newImage != curImage && newImage != 0xFF) { + assert(_characterImageTable); + loadBitmap(_characterImageTable[newImage], 8, 8, 0); + curImage = newImage; + } + _shapes[4+i] = _screen->encodeShape(shapeTable[i-123].x<<3, shapeTable[i-123].y, shapeTable[i-123].w<<3, shapeTable[i-123].h, shapeFlags); + assert(i-7 < _defaultShapeTableSize); + _defaultShapeTable[i-7].xOffset = shapeTable[i-123].xOffset; + _defaultShapeTable[i-7].yOffset = shapeTable[i-123].yOffset; + _defaultShapeTable[i-7].w = shapeTable[i-123].w; + _defaultShapeTable[i-7].h = shapeTable[i-123].h; + } + _screen->_curPage = curPageBackUp; +} + +void KyraEngine::freeShapes123() { + debug(9, "KyraEngine::freeShapes123()"); + for (int i = 123; i <= 172; ++i) { + free(_shapes[4+i]); + _shapes[4+i] = NULL; + } +} + +#pragma mark - +#pragma mark - Misc stuff +#pragma mark - + +Movie *KyraEngine::createWSAMovie() { + // for kyra2 here could be added then WSAMovieV2 + return new WSAMovieV1(this); +} + +int KyraEngine::setGameFlag(int flag) { + _flagsTable[flag >> 3] |= (1 << (flag & 7)); + return 1; +} + +int KyraEngine::queryGameFlag(int flag) { + return ((_flagsTable[flag >> 3] >> (flag & 7)) & 1); +} + +int KyraEngine::resetGameFlag(int flag) { + _flagsTable[flag >> 3] &= ~(1 << (flag & 7)); + return 0; +} + +void KyraEngine::setBrandonPoisonFlags(int reset) { + debug(9, "KyraEngine::setBrandonPoisonFlags(%d)", reset); + _brandonStatusBit |= 1; + if (reset) + _poisonDeathCounter = 0; + for (int i = 0; i < 0x100; ++i) { + _brandonPoisonFlagsGFX[i] = i; + } + _brandonPoisonFlagsGFX[0x99] = 0x34; + _brandonPoisonFlagsGFX[0x9A] = 0x35; + _brandonPoisonFlagsGFX[0x9B] = 0x37; + _brandonPoisonFlagsGFX[0x9C] = 0x38; + _brandonPoisonFlagsGFX[0x9D] = 0x2B; +} + +void KyraEngine::resetBrandonPoisonFlags() { + debug(9, "KyraEngine::resetBrandonPoisonFlags()"); + _brandonStatusBit = 0; + for (int i = 0; i < 0x100; ++i) { + _brandonPoisonFlagsGFX[i] = i; + } +} + +#pragma mark - +#pragma mark - Input +#pragma mark - + +void KyraEngine::processInput(int xpos, int ypos) { + debug(9, "KyraEngine::processInput(%d, %d)", xpos, ypos); + _abortWalkFlag2 = false; + + if (processInputHelper(xpos, ypos)) { + return; + } + uint8 item = findItemAtPos(xpos, ypos); + if (item == 0xFF) { + _changedScene = false; + int handled = clickEventHandler(xpos, ypos); + if (_changedScene || handled) + return; + } + + // XXX _deathHandler specific + if (ypos <= 158) { + uint16 exit = 0xFFFF; + if (xpos < 12) { + exit = _walkBlockWest; + } else if (xpos >= 308) { + exit = _walkBlockEast; + } else if (ypos >= 136) { + exit = _walkBlockSouth; + } else if (ypos < 12) { + exit = _walkBlockNorth; + } + + if (exit != 0xFFFF) { + _abortWalkFlag2 = true; + handleSceneChange(xpos, ypos, 1, 1); + _abortWalkFlag2 = false; + return; + } else { + int script = checkForNPCScriptRun(xpos, ypos); + if (script >= 0) { + runNpcScript(script); + return; + } + if (_itemInHand != -1) { + if (ypos < 155) { + if (hasClickedOnExit(xpos, ypos)) { + _abortWalkFlag2 = true; + handleSceneChange(xpos, ypos, 1, 1); + _abortWalkFlag2 = false; + return; + } + dropItem(0, _itemInHand, xpos, ypos, 1); + } + } else { + if (ypos <= 155) { + _abortWalkFlag2 = true; + handleSceneChange(xpos, ypos, 1, 1); + _abortWalkFlag2 = false; + } + } + } + } +} + +int KyraEngine::processInputHelper(int xpos, int ypos) { + debug(9, "KyraEngine::processInputHelper(%d, %d)", xpos, ypos); + uint8 item = findItemAtPos(xpos, ypos); + if (item != 0xFF) { + if (_itemInHand == -1) { + _screen->hideMouse(); + _animator->animRemoveGameItem(item); + snd_playSoundEffect(53); + assert(_currentCharacter->sceneId < _roomTableSize); + Room *currentRoom = &_roomTable[_currentCharacter->sceneId]; + int item2 = currentRoom->itemsTable[item]; + currentRoom->itemsTable[item] = 0xFF; + setMouseItem(item2); + assert(_itemList && _takenList); + updateSentenceCommand(_itemList[item2], _takenList[0], 179); + _itemInHand = item2; + _screen->showMouse(); + clickEventHandler2(); + return 1; + } else { + exchangeItemWithMouseItem(_currentCharacter->sceneId, item); + return 1; + } + } + return 0; +} + +int KyraEngine::clickEventHandler(int xpos, int ypos) { + debug(9, "KyraEngine::clickEventHandler(%d, %d)", xpos, ypos); + _scriptInterpreter->initScript(_scriptClick, _scriptClickData); + _scriptClick->variables[1] = xpos; + _scriptClick->variables[2] = ypos; + _scriptClick->variables[3] = 0; + _scriptClick->variables[4] = _itemInHand; + _scriptInterpreter->startScript(_scriptClick, 1); + + while (_scriptInterpreter->validScript(_scriptClick)) { + _scriptInterpreter->runScript(_scriptClick); + } + + return _scriptClick->variables[3]; +} + +void KyraEngine::updateMousePointer(bool forceUpdate) { + int shape = 0; + + int newMouseState = 0; + int newX = 0; + int newY = 0; + if (_mouseY <= 158) { + if (_mouseX >= 12) { + if (_mouseX >= 308) { + if (_walkBlockEast == 0xFFFF) { + newMouseState = -2; + } else { + newMouseState = -5; + shape = 3; + newX = 7; + newY = 5; + } + } else if (_mouseY >= 136) { + if (_walkBlockSouth == 0xFFFF) { + newMouseState = -2; + } else { + newMouseState = -4; + shape = 4; + newX = 5; + newY = 7; + } + } else if (_mouseY < 12) { + if (_walkBlockNorth == 0xFFFF) { + newMouseState = -2; + } else { + newMouseState = -6; + shape = 2; + newX = 5; + newY = 1; + } + } + } else { + if (_walkBlockWest == 0xFFFF) { + newMouseState = -2; + } else { + newMouseState = -3; + newX = 1; + newY = shape = 5; + } + } + } + + if (_mouseX >= _entranceMouseCursorTracks[0] && _mouseY >= _entranceMouseCursorTracks[1] + && _mouseX <= _entranceMouseCursorTracks[2] && _mouseY <= _entranceMouseCursorTracks[3]) { + switch (_entranceMouseCursorTracks[4]) { + case 0: + newMouseState = -6; + shape = 2; + newX = 5; + newY = 1; + break; + + case 2: + newMouseState = -5; + shape = 3; + newX = 7; + newY = 5; + break; + + case 4: + newMouseState = -4; + shape = 4; + newX = 5; + newY = 7; + break; + + case 6: + newMouseState = -3; + shape = 5; + newX = 1; + newY = 5; + break; + + default: + break; + } + } + + if (newMouseState == -2) { + shape = 6; + newX = 4; + newY = 4; + } + + if ((newMouseState && _mouseState != newMouseState) || (newMouseState && forceUpdate)) { + _mouseState = newMouseState; + _screen->hideMouse(); + _screen->setMouseCursor(newX, newY, _shapes[4+shape]); + _screen->showMouse(); + } + + if (!newMouseState) { + if (_mouseState != _itemInHand || forceUpdate) { + if (_mouseY > 158 || (_mouseX >= 12 && _mouseX < 308 && _mouseY < 136 && _mouseY >= 12) || forceUpdate) { + _mouseState = _itemInHand; + _screen->hideMouse(); + if (_itemInHand == -1) { + _screen->setMouseCursor(1, 1, _shapes[4]); + } else { + _screen->setMouseCursor(8, 15, _shapes[220+_itemInHand]); + } + _screen->showMouse(); + } + } + } +} + +bool KyraEngine::hasClickedOnExit(int xpos, int ypos) { + debug(9, "KyraEngine::hasClickedOnExit(%d, %d)", xpos, ypos); + if (xpos < 16 || xpos >= 304) { + return true; + } + if (ypos < 8) + return true; + if (ypos < 136 || ypos > 155) { + return false; + } + return true; +} + +void KyraEngine::clickEventHandler2() { + debug(9, "KyraEngine::clickEventHandler2()"); + _scriptInterpreter->initScript(_scriptClick, _scriptClickData); + _scriptClick->variables[0] = _currentCharacter->sceneId; + _scriptClick->variables[1] = _mouseX; + _scriptClick->variables[2] = _mouseY; + _scriptClick->variables[4] = _itemInHand; + _scriptInterpreter->startScript(_scriptClick, 6); + + while (_scriptInterpreter->validScript(_scriptClick)) { + _scriptInterpreter->runScript(_scriptClick); + } +} + +int KyraEngine::checkForNPCScriptRun(int xpos, int ypos) { + debug(9, "KyraEngine::checkForNPCScriptRun(%d, %d)", xpos, ypos); + int returnValue = -1; + const Character *currentChar = _currentCharacter; + int charLeft = 0, charRight = 0, charTop = 0, charBottom = 0; + + int scaleFactor = _scaleTable[currentChar->y1]; + int addX = (((scaleFactor*8)*3)>>8)>>1; + int addY = ((scaleFactor*3)<<4)>>8; + + charLeft = currentChar->x1 - addX; + charRight = currentChar->x1 + addX; + charTop = currentChar->y1 - addY; + charBottom = currentChar->y1; + + if (xpos >= charLeft && charRight >= xpos && charTop <= ypos && charBottom >= ypos) { + return 0; + } + + if (xpos > 304 || xpos < 16) { + return -1; + } + + for (int i = 1; i < 5; ++i) { + currentChar = &_characterList[i]; + + if (currentChar->sceneId != _currentCharacter->sceneId) + continue; + + charLeft = currentChar->x1 - 12; + charRight = currentChar->x1 + 11; + charTop = currentChar->y1 - 48; + // if (!i) { + // charBottom = currentChar->y2 - 16; + // } else { + charBottom = currentChar->y1; + // } + + if (xpos < charLeft || xpos > charRight || ypos < charTop || charBottom < ypos) { + continue; + } + + if (returnValue != -1) { + if (currentChar->y1 >= _characterList[returnValue].y1) { + returnValue = i; + } + } else { + returnValue = i; + } + } + + return returnValue; +} + +void KyraEngine::runNpcScript(int func) { + debug(9, "KyraEngine::runNpcScript(%d)", func); + _scriptInterpreter->initScript(_npcScript, _npcScriptData); + _scriptInterpreter->startScript(_npcScript, func); + _npcScript->variables[0] = _currentCharacter->sceneId; + _npcScript->variables[4] = _itemInHand; + _npcScript->variables[5] = func; + + while (_scriptInterpreter->validScript(_npcScript)) { + _scriptInterpreter->runScript(_npcScript); + } +} +} // End of namespace Kyra diff --git a/engines/kyra/kyra.h b/engines/kyra/kyra.h new file mode 100644 index 0000000000..f1112fa799 --- /dev/null +++ b/engines/kyra/kyra.h @@ -0,0 +1,961 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-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 KYRA_H +#define KYRA_H + +#include "base/engine.h" +#include "common/rect.h" +#include "sound/mixer.h" +#include "common/file.h" + +class AudioStream; + +namespace Kyra { + +class Movie; +class Sound; +class SeqPlayer; +class Resource; +class PAKFile; +class Screen; +class Sprites; +class ScriptHelper; +class Debugger; +class ScreenAnimator; +class TextDisplayer; +class KyraEngine; + +struct ScriptState; +struct ScriptData; + +enum { + GF_FLOPPY = 1 << 0, + GF_TALKIE = 1 << 1, + GF_AUDIOCD = 1 << 2, // FM-Towns versions seems to use audio CD + GF_DEMO = 1 << 3, + GF_ENGLISH = 1 << 4, + GF_FRENCH = 1 << 5, + GF_GERMAN = 1 << 6, + GF_SPANISH = 1 << 7, + // other languages here + GF_LNGUNK = 1 << 16 +}; + +enum { + GI_KYRA1 = 0 +}; + +struct Character { + uint16 sceneId; + uint8 height; + uint8 facing; + uint16 currentAnimFrame; + uint8 inventoryItems[10]; + int16 x1, y1, x2, y2; +}; + +struct Shape { + uint8 imageIndex; + int8 xOffset, yOffset; + uint8 x, y, w, h; +}; + +struct Room { + uint8 nameIndex; + uint16 northExit; + uint16 eastExit; + uint16 southExit; + uint16 westExit; + uint8 itemsTable[12]; + uint16 itemsXPos[12]; + uint8 itemsYPos[12]; + uint8 needInit[12]; +}; + +struct Rect { + int x, y; + int x2, y2; +}; + +struct Item { + uint8 unk1; + uint8 height; + uint8 unk2; + uint8 unk3; +}; + +struct SeqLoop { + const uint8 *ptr; + uint16 count; +}; + +struct SceneExits { + uint16 northXPos; + uint8 northYPos; + uint16 eastXPos; + uint8 eastYPos; + uint16 southXPos; + uint8 southYPos; + uint16 westXPos; + uint8 westYPos; +}; + +struct BeadState { + int16 x; + int16 y; + int16 width; + int16 height; + int16 dstX; + int16 dstY; + int16 width2; + int16 unk8; + int16 unk9; + int16 tableIndex; +}; + +struct Timer { + uint8 active; + int32 countdown; + uint32 nextRun; + void (KyraEngine::*func)(int timerNum); +}; + +struct Button { + Button *nextButton; + uint16 specialValue; + // uint8 unk[4]; + uint8 process0; + uint8 process1; + uint8 process2; + // uint8 unk + uint16 flags; + typedef int (KyraEngine::*ButtonCallback)(Button*); + // using 6 pointers instead of 3 as in the orignal here (safer for use with classes) + uint8 *process0PtrShape; + uint8 *process1PtrShape; + uint8 *process2PtrShape; + ButtonCallback process0PtrCallback; + ButtonCallback process1PtrCallback; + ButtonCallback process2PtrCallback; + uint16 dimTableIndex; + uint16 x; + uint16 y; + uint16 width; + uint16 height; + // uint8 unk[8]; + uint32 flags2; + ButtonCallback buttonCallback; + // uint8 unk[8]; +}; + +struct MenuItem { + bool enabled; + uint16 field_1; + uint8 field_3; + const char *itemString; + int16 x; + int16 field_9; + uint16 y; + uint16 width; + uint16 height; + uint8 textColor; + uint8 highlightColor; + int16 field_12; + uint8 field_13; + uint8 bgcolor; + uint8 color1; + uint8 color2; + int (KyraEngine::*callback)(Button*); + int16 field_1b; + const char *labelString; + uint16 field_21; + uint8 field_23; + uint8 field_24; + uint32 field_25; +}; + +struct Menu { + int16 x; + int16 y; + uint16 width; + uint16 height; + uint8 bgcolor; + uint8 color1; + uint8 color2; + const char *menuName; + uint8 textColor; + int16 field_10; + uint16 field_12; + uint16 highlightedItem; + uint8 nrOfItems; + int16 scrollUpBtnX; + int16 scrollUpBtnY; + int16 scrollDownBtnX; + int16 scrollDownBtnY; + MenuItem item[6]; +}; + +struct KeyboardEvent { + bool pending; + uint32 repeat; + uint8 ascii; +}; + +class KyraEngine : public Engine { + friend class MusicPlayer; + friend class Debugger; + friend class ScreenAnimator; +public: + + enum { + MUSIC_INTRO = 0 + }; + + KyraEngine(GameDetector *detector, OSystem *system); + ~KyraEngine(); + + void errorString(const char *buf_input, char *buf_output); + + Resource *resource() { return _res; } + Screen *screen() { return _screen; } + ScreenAnimator *animator() { return _animator; } + TextDisplayer *text() { return _text; } + Sound *sound() { return _sound; } + uint32 tickLength() const { return _tickLength; } + Movie *createWSAMovie(); + + uint8 game() const { return _game; } + uint32 features() const { return _features; } + + uint8 **shapes() { return _shapes; } + Character *currentCharacter() { return _currentCharacter; } + Character *characterList() { return _characterList; } + uint16 brandonStatus() { return _brandonStatusBit; } + + int _paletteChanged; + Common::RandomSource _rnd; + int16 _northExitHeight; + + typedef void (KyraEngine::*IntroProc)(); + typedef int (KyraEngine::*OpcodeProc)(ScriptState *script); + + const char **seqWSATable() { return const_cast<const char **>(_seq_WSATable); } + const char **seqCPSTable() { return const_cast<const char **>(_seq_CPSTable); } + const char **seqCOLTable() { return const_cast<const char **>(_seq_COLTable); } + const char **seqTextsTable() { return const_cast<const char **>(_seq_textsTable); } + + const uint8 **palTable1() { return const_cast<const uint8 **>(&_specialPalettes[0]); } + const uint8 **palTable2() { return const_cast<const uint8 **>(&_specialPalettes[29]); } + + bool seq_skipSequence() const; + void delay(uint32 millis, bool update = false, bool mainLoop = false); + void quitGame(); + void loadBitmap(const char *filename, int tempPage, int dstPage, uint8 *palData); + + void snd_playTheme(int file, int track = 0); + void snd_playVoiceFile(int id); + void snd_voiceWaitForFinish(bool ingame = true); + void snd_playSoundEffect(int track); + void snd_playWanderScoreViaMap(int command, int restart); + + void drawSentenceCommand(char *sentence, int unk1); + void updateSentenceCommand(char *str1, char *str2, int unk1); + void updateTextFade(); + + void updateGameTimers(); + void clearNextEventTickCount(); + void setTimerCountdown(uint8 timer, int32 countdown); + void setTimerDelay(uint8 timer, int32 countdown); + int16 getTimerDelay(uint8 timer); + void enableTimer(uint8 timer); + void disableTimer(uint8 timer); + + void waitTicks(int ticks); + void delayWithTicks(int ticks); + + void saveGame(const char *fileName, const char *saveName); + void loadGame(const char *fileName); + + int mouseX() { return _mouseX; } + int mouseY() { return _mouseY; } + + // all opcode procs (maybe that is somehow useless atm) + int cmd_magicInMouseItem(ScriptState *script); + int cmd_characterSays(ScriptState *script); + int cmd_pauseTicks(ScriptState *script); + int cmd_drawSceneAnimShape(ScriptState *script); + int cmd_queryGameFlag(ScriptState *script); + int cmd_setGameFlag(ScriptState *script); + int cmd_resetGameFlag(ScriptState *script); + int cmd_runNPCScript(ScriptState *script); + int cmd_setSpecialExitList(ScriptState *script); + int cmd_blockInWalkableRegion(ScriptState *script); + int cmd_blockOutWalkableRegion(ScriptState *script); + int cmd_walkPlayerToPoint(ScriptState *script); + int cmd_dropItemInScene(ScriptState *script); + int cmd_drawAnimShapeIntoScene(ScriptState *script); + int cmd_createMouseItem(ScriptState *script); + int cmd_savePageToDisk(ScriptState *script); + int cmd_sceneAnimOn(ScriptState *script); + int cmd_sceneAnimOff(ScriptState *script); + int cmd_getElapsedSeconds(ScriptState *script); + int cmd_mouseIsPointer(ScriptState *script); + int cmd_destroyMouseItem(ScriptState *script); + int cmd_runSceneAnimUntilDone(ScriptState *script); + int cmd_fadeSpecialPalette(ScriptState *script); + int cmd_playAdlibSound(ScriptState *script); + int cmd_playAdlibScore(ScriptState *script); + int cmd_phaseInSameScene(ScriptState *script); + int cmd_setScenePhasingFlag(ScriptState *script); + int cmd_resetScenePhasingFlag(ScriptState *script); + int cmd_queryScenePhasingFlag(ScriptState *script); + int cmd_sceneToDirection(ScriptState *script); + int cmd_setBirthstoneGem(ScriptState *script); + int cmd_placeItemInGenericMapScene(ScriptState *script); + int cmd_setBrandonStatusBit(ScriptState *script); + int cmd_pauseSeconds(ScriptState *script); + int cmd_getCharactersLocation(ScriptState *script); + int cmd_runNPCSubscript(ScriptState *script); + int cmd_magicOutMouseItem(ScriptState *script); + int cmd_internalAnimOn(ScriptState *script); + int cmd_forceBrandonToNormal(ScriptState *script); + int cmd_poisonDeathNow(ScriptState *script); + int cmd_setScaleMode(ScriptState *script); + int cmd_openWSAFile(ScriptState *script); + int cmd_closeWSAFile(ScriptState *script); + int cmd_runWSAFromBeginningToEnd(ScriptState *script); + int cmd_displayWSAFrame(ScriptState *script); + int cmd_enterNewScene(ScriptState *script); + int cmd_setSpecialEnterXAndY(ScriptState *script); + int cmd_runWSAFrames(ScriptState *script); + int cmd_popBrandonIntoScene(ScriptState *script); + int cmd_restoreAllObjectBackgrounds(ScriptState *script); + int cmd_setCustomPaletteRange(ScriptState *script); + int cmd_loadPageFromDisk(ScriptState *script); + int cmd_customPrintTalkString(ScriptState *script); + int cmd_restoreCustomPrintBackground(ScriptState *script); + int cmd_hideMouse(ScriptState *script); + int cmd_showMouse(ScriptState *script); + int cmd_getCharacterX(ScriptState *script); + int cmd_getCharacterY(ScriptState *script); + int cmd_changeCharactersFacing(ScriptState *script); + int cmd_copyWSARegion(ScriptState *script); + int cmd_printText(ScriptState *script); + int cmd_random(ScriptState *script); + int cmd_loadSoundFile(ScriptState *script); + int cmd_displayWSAFrameOnHidPage(ScriptState *script); + int cmd_displayWSASequentialFrames(ScriptState *script); + int cmd_drawCharacterStanding(ScriptState *script); + int cmd_internalAnimOff(ScriptState *script); + int cmd_changeCharactersXAndY(ScriptState *script); + int cmd_clearSceneAnimatorBeacon(ScriptState *script); + int cmd_querySceneAnimatorBeacon(ScriptState *script); + int cmd_refreshSceneAnimator(ScriptState *script); + int cmd_placeItemInOffScene(ScriptState *script); + int cmd_wipeDownMouseItem(ScriptState *script); + int cmd_placeCharacterInOtherScene(ScriptState *script); + int cmd_getKey(ScriptState *script); + int cmd_specificItemInInventory(ScriptState *script); + int cmd_popMobileNPCIntoScene(ScriptState *script); + int cmd_mobileCharacterInScene(ScriptState *script); + int cmd_hideMobileCharacter(ScriptState *script); + int cmd_unhideMobileCharacter(ScriptState *script); + int cmd_setCharactersLocation(ScriptState *script); + int cmd_walkCharacterToPoint(ScriptState *script); + int cmd_specialEventDisplayBrynnsNote(ScriptState *script); + int cmd_specialEventRemoveBrynnsNote(ScriptState *script); + int cmd_setLogicPage(ScriptState *script); + int cmd_fatPrint(ScriptState *script); + int cmd_preserveAllObjectBackgrounds(ScriptState *script); + int cmd_updateSceneAnimations(ScriptState *script); + int cmd_sceneAnimationActive(ScriptState *script); + int cmd_setCharactersMovementDelay(ScriptState *script); + int cmd_getCharactersFacing(ScriptState *script); + int cmd_bkgdScrollSceneAndMasksRight(ScriptState *script); + int cmd_dispelMagicAnimation(ScriptState *script); + int cmd_findBrightestFireberry(ScriptState *script); + int cmd_setFireberryGlowPalette(ScriptState *script); + int cmd_setDeathHandlerFlag(ScriptState *script); + int cmd_drinkPotionAnimation(ScriptState *script); + int cmd_makeAmuletAppear(ScriptState *script); + int cmd_drawItemShapeIntoScene(ScriptState *script); + int cmd_setCharactersCurrentFrame(ScriptState *script); + int cmd_waitForConfirmationMouseClick(ScriptState *script); + int cmd_pageFlip(ScriptState *script); + int cmd_setSceneFile(ScriptState *script); + int cmd_getItemInMarbleVase(ScriptState *script); + int cmd_setItemInMarbleVase(ScriptState *script); + int cmd_addItemToInventory(ScriptState *script); + int cmd_intPrint(ScriptState *script); + int cmd_shakeScreen(ScriptState *script); + int cmd_createAmuletJewel(ScriptState *script); + int cmd_setSceneAnimCurrXY(ScriptState *script); + int cmd_poisonBrandonAndRemaps(ScriptState *script); + int cmd_fillFlaskWithWater(ScriptState *script); + int cmd_getCharactersMovementDelay(ScriptState *script); + int cmd_getBirthstoneGem(ScriptState *script); + int cmd_queryBrandonStatusBit(ScriptState *script); + int cmd_playFluteAnimation(ScriptState *script); + int cmd_playWinterScrollSequence(ScriptState *script); + int cmd_getIdolGem(ScriptState *script); + int cmd_setIdolGem(ScriptState *script); + int cmd_totalItemsInScene(ScriptState *script); + int cmd_restoreBrandonsMovementDelay(ScriptState *script); + int cmd_setMousePos(ScriptState *script); + int cmd_getMouseState(ScriptState *script); + int cmd_setEntranceMouseCursorTrack(ScriptState *script); + int cmd_itemAppearsOnGround(ScriptState *script); + int cmd_setNoDrawShapesFlag(ScriptState *script); + int cmd_fadeEntirePalette(ScriptState *script); + int cmd_itemOnGroundHere(ScriptState *script); + int cmd_queryCauldronState(ScriptState *script); + int cmd_setCauldronState(ScriptState *script); + int cmd_queryCrystalState(ScriptState *script); + int cmd_setCrystalState(ScriptState *script); + int cmd_setPaletteRange(ScriptState *script); + int cmd_shrinkBrandonDown(ScriptState *script); + int cmd_growBrandonUp(ScriptState *script); + int cmd_setBrandonScaleXAndY(ScriptState *script); + int cmd_resetScaleMode(ScriptState *script); + int cmd_getScaleDepthTableValue(ScriptState *script); + int cmd_setScaleDepthTableValue(ScriptState *script); + int cmd_message(ScriptState *script); + int cmd_checkClickOnNPC(ScriptState *script); + int cmd_getFoyerItem(ScriptState *script); + int cmd_setFoyerItem(ScriptState *script); + int cmd_setNoItemDropRegion(ScriptState *script); + int cmd_walkMalcolmOn(ScriptState *script); + int cmd_passiveProtection(ScriptState *script); + int cmd_setPlayingLoop(ScriptState *script); + int cmd_brandonToStoneSequence(ScriptState *script); + int cmd_brandonHealingSequence(ScriptState *script); + int cmd_protectCommandLine(ScriptState *script); + int cmd_pauseMusicSeconds(ScriptState *script); + int cmd_resetMaskRegion(ScriptState *script); + int cmd_setPaletteChangeFlag(ScriptState *script); + int cmd_fillRect(ScriptState *script); + int cmd_dummy(ScriptState *script); + int cmd_vocUnload(ScriptState *script); + int cmd_vocLoad(ScriptState *script); + +protected: + + int go(); + int init(GameDetector &detector); + + void startup(); + void mainLoop(); + int initCharacterChat(int8 charNum); + int8 getChatPartnerNum(); + void backupChatPartnerAnimFrame(int8 charNum); + void restoreChatPartnerAnimFrame(int8 charNum); + void endCharacterChat(int8 charNum, int16 arg_4); + void waitForChatToFinish(int16 chatDuration, char *str, uint8 charNum); + void characterSays(char *chatStr, int8 charNum, int8 chatDuration); + + void setCharactersPositions(int character); + int setGameFlag(int flag); + int queryGameFlag(int flag); + int resetGameFlag(int flag); + + void enterNewScene(int sceneId, int facing, int unk1, int unk2, int brandonAlive); + void transcendScenes(int roomIndex, int roomName); + void setSceneFile(int roomIndex, int roomName); + void moveCharacterToPos(int character, int facing, int xpos, int ypos); + void setCharacterPositionWithUpdate(int character); + int setCharacterPosition(int character, int *facingTable); + void setCharacterPositionHelper(int character, int *facingTable); + int getOppositeFacingDirection(int dir); + void loadSceneMSC(); + void startSceneScript(int brandonAlive); + void setupSceneItems(); + void initSceneData(int facing, int unk1, int brandonAlive); + void clearNoDropRects(); + void addToNoDropRects(int x, int y, int w, int h); + byte findFreeItemInScene(int scene); + byte findItemAtPos(int x, int y); + void placeItemInGenericMapScene(int item, int index); + void initSceneObjectList(int brandonAlive); + void initSceneScreen(int brandonAlive); + int findDuplicateItemShape(int shape); + int findWay(int x, int y, int toX, int toY, int *moveTable, int moveTableSize); + int findSubPath(int x, int y, int toX, int toY, int *moveTable, int start, int end); + int getFacingFromPointToPoint(int x, int y, int toX, int toY); + void changePosTowardsFacing(int &x, int &y, int facing); + bool lineIsPassable(int x, int y); + int getMoveTableSize(int *moveTable); + int handleSceneChange(int xpos, int ypos, int unk1, int frameReset); + int processSceneChange(int *table, int unk1, int frameReset); + int changeScene(int facing); + void createMouseItem(int item); + void destroyMouseItem(); + void setMouseItem(int item); + void wipeDownMouseItem(int xpos, int ypos); + void setBrandonPoisonFlags(int reset); + void resetBrandonPoisonFlags(); + + void processInput(int xpos, int ypos); + int processInputHelper(int xpos, int ypos); + int clickEventHandler(int xpos, int ypos); + void clickEventHandler2(); + void updateMousePointer(bool forceUpdate = false); + bool hasClickedOnExit(int xpos, int ypos); + int checkForNPCScriptRun(int xpos, int ypos); + void runNpcScript(int func); + + int countItemsInScene(uint16 sceneId); + int processItemDrop(uint16 sceneId, uint8 item, int x, int y, int unk1, int unk2); + void exchangeItemWithMouseItem(uint16 sceneId, int itemIndex); + void addItemToRoom(uint16 sceneId, uint8 item, int itemIndex, int x, int y); + int checkNoDropRects(int x, int y); + int isDropable(int x, int y); + void itemDropDown(int x, int y, int destX, int destY, byte freeItem, int item); + void dropItem(int unk1, int item, int x, int y, int unk2); + void itemSpecialFX(int x, int y, int item); + void itemSpecialFX1(int x, int y, int item); + void itemSpecialFX2(int x, int y, int item); + void magicOutMouseItem(int animIndex, int itemPos); + void magicInMouseItem(int animIndex, int item, int itemPos); + void specialMouseItemFX(int shape, int x, int y, int animIndex, int tableIndex, int loopStart, int maxLoops); + void processSpecialMouseItemFX(int shape, int x, int y, int tableValue, int loopStart, int maxLoops); + void updatePlayerItemsForScene(); + void redrawInventory(int page); + + void drawJewelPress(int jewel, int drawSpecial); + void drawJewelsFadeOutStart(); + void drawJewelsFadeOutEnd(int jewel); + void setupShapes123(const Shape *shapeTable, int endShape, int flags); + void freeShapes123(); + + void seq_demo(); + void seq_intro(); + void seq_introLogos(); + void seq_introStory(); + void seq_introMalcolmTree(); + void seq_introKallakWriting(); + void seq_introKallakMalcolm(); + void seq_createAmuletJewel(int jewel, int page, int noSound, int drawOnly); + void seq_brandonHealing(); + void seq_brandonHealing2(); + void seq_poisonDeathNow(int now); + void seq_poisonDeathNowAnim(); + void seq_playFluteAnimation(); + void seq_winterScroll1(); + void seq_winterScroll2(); + void seq_makeBrandonInv(); + void seq_makeBrandonNormal(); + void seq_makeBrandonNormal2(); + void seq_makeBrandonWisp(); + void seq_dispelMagicAnimation(); + void seq_fillFlaskWithWater(int item, int type); + void seq_playDrinkPotionAnim(int unk1, int unk2, int flags); + int seq_playEnd(); + void seq_brandonToStone(); + void seq_playEnding(); + void seq_playCredits(); + void updateKyragemFading(); + + void snd_setSoundEffectFile(int file); + + static OpcodeProc _opcodeTable[]; + static const int _opcodeTableSize; + + enum { + RES_ALL = 0, + RES_INTRO = (1 << 0), + RES_INGAME = (1 << 1), + RES_OUTRO = (1 << 2) + }; + + void res_loadResources(int type = RES_ALL); + void res_unloadResources(int type = RES_ALL); + void res_loadLangTable(const char *filename, PAKFile *res, byte ***loadTo, int *size, bool nativ); + void res_loadTable(const byte *src, byte ***loadTo, int *size); + void res_loadRoomTable(const byte *src, Room **loadTo, int *size); + void res_loadShapeTable(const byte *src, Shape **loadTo, int *size); + void res_freeLangTable(char ***sting, int *size); + + void waitForEvent(); + void loadPalette(const char *filename, uint8 *palData); + void loadMouseShapes(); + void loadCharacterShapes(); + void loadSpecialEffectShapes(); + void loadItems(); + void loadButtonShapes(); + void initMainButtonList(); + void loadMainScreen(int page = 3); + void setCharactersInDefaultScene(); + void setupPanPages(); + void freePanPages(); + void closeFinalWsa(); + int handleMalcolmFlag(); + int handleBeadState(); + void initBeadState(int x, int y, int x2, int y2, int unk1, BeadState *ptr); + int processBead(int x, int y, int &x2, int &y2, BeadState *ptr); + + void setTimer19(); + void setupTimers(); + void timerUpdateHeadAnims(int timerNum); + void timerSetFlags1(int timerNum); + void timerSetFlags2(int timerNum); + void timerSetFlags3(int timerNum); + void timerCheckAnimFlag1(int timerNum); + void timerCheckAnimFlag2(int timerNum); + void checkAmuletAnimFlags(); + void timerRedrawAmulet(int timerNum); + void timerFadeText(int timerNum); + void updateAnimFlag1(int timerNum); + void updateAnimFlag2(int timerNum); + void drawAmulet(); + void setTextFadeTimerCountdown(int16 countdown); + + int buttonInventoryCallback(Button *caller); + int buttonAmuletCallback(Button *caller); + int buttonMenuCallback(Button *caller); + int drawBoxCallback(Button *button); + int drawShadedBoxCallback(Button *button); + void calcCoords(Menu &menu); + void initMenu(Menu menu); + + Button *initButton(Button *list, Button *newButton); + void processButtonList(Button *list); + void processButton(Button *button); + void processMenuButton(Button *button); + void processAllMenuButtons(); + + const char *getSavegameFilename(int num); + void setupSavegames(Menu &menu, int num); + int getNextSavegameSlot(); + + int gui_resumeGame(Button *button); + int gui_loadGameMenu(Button *button); + int gui_saveGameMenu(Button *button); + int gui_quitPlaying(Button *button); + int gui_quitConfirmYes(Button *button); + int gui_quitConfirmNo(Button *button); + int gui_loadGame(Button *button); + int gui_saveGame(Button *button); + int gui_savegameConfirm(Button *button); + int gui_cancelSubMenu(Button *button); + int gui_scrollUp(Button *button); + int gui_scrollDown(Button *button); + + bool gui_quitConfirm(const char *str); + void gui_getInput(); + void gui_redrawText(Menu menu); + void gui_redrawHighlight(Menu menu); + void gui_processHighlights(Menu &menu); + void gui_updateSavegameString(); + void gui_redrawTextfield(); + void gui_fadePalette(); + void gui_restorePalette(); + + uint8 _game; + bool _quitFlag; + bool _skipFlag; + bool _skipIntroFlag; + bool _abortIntroFlag; + bool _menuDirectlyToLoad; + bool _abortWalkFlag; + bool _abortWalkFlag2; + bool _mousePressFlag; + uint8 _flagsTable[53]; + uint8 *_shapes[377]; + uint16 _gameSpeed; + uint16 _tickLength; + uint32 _features; + int _mouseX, _mouseY; + int8 _itemInHand; + int _mouseState; + bool _handleInput; + bool _changedScene; + int _unkScreenVar1, _unkScreenVar2, _unkScreenVar3; + int _beadStateVar; + int _unkAmuletVar; + + int _malcolmFlag; + int _endSequenceSkipFlag; + int _endSequenceNeedLoading; + int _unkEndSeqVar2; + uint8 *_endSequenceBackUpRect; + int _unkEndSeqVar4; + int _unkEndSeqVar5; + int _lastDisplayedPanPage; + uint8 *_panPagesTable[20]; + Movie *_finalA, *_finalB, *_finalC; + + Movie *_movieObjects[10]; + + uint16 _entranceMouseCursorTracks[8]; + uint16 _walkBlockNorth; + uint16 _walkBlockEast; + uint16 _walkBlockSouth; + uint16 _walkBlockWest; + + int32 _scaleMode; + int16 _scaleTable[145]; + + Rect _noDropRects[11]; + + int8 _birthstoneGemTable[4]; + int8 _idolGemsTable[3]; + + int8 _marbleVaseItem; + int8 _foyerItemTable[3]; + + int8 _cauldronState; + int8 _crystalState[2]; + + uint16 _brandonStatusBit; + uint8 _brandonStatusBit0x02Flag; + uint8 _brandonStatusBit0x20Flag; + uint8 _brandonPoisonFlagsGFX[256]; + uint8 _deathHandler; + int16 _brandonInvFlag; + uint8 _poisonDeathCounter; + int _brandonPosX; + int _brandonPosY; + + uint16 _currentChatPartnerBackupFrame; + uint16 _currentCharAnimFrame; + + int8 *_sceneAnimTable[50]; + + Item _itemTable[145]; + int _lastProcessedItem; + int _lastProcessedItemHeight; + + int16 *_exitListPtr; + int16 _exitList[11]; + SceneExits _sceneExits; + uint16 _currentRoom; + int _scenePhasingFlag; + uint8 *_maskBuffer; + + int _sceneChangeState; + int _loopFlag2; + + int _pathfinderFlag; + int _pathfinderFlag2; + int _lastFindWayRet; + int *_movFacingTable; + + int8 _talkingCharNum; + int8 _charSayUnk2; + int8 _charSayUnk3; + int8 _currHeadShape; + uint8 _currSentenceColor[3]; + int8 _startSentencePalIndex; + bool _fadeText; + + uint8 _configTalkspeed; + + Common::String _targetName; + + int _curMusicTheme; + int _newMusicTheme; + int16 _lastMusicCommand; + + Resource *_res; + Screen *_screen; + ScreenAnimator *_animator; + Sound *_sound; + SeqPlayer *_seq; + Sprites *_sprites; + TextDisplayer *_text; + ScriptHelper *_scriptInterpreter; + Debugger *_debugger; + Common::SaveFileManager *_saveFileMan; + + ScriptState *_scriptMain; + + ScriptState *_npcScript; + ScriptData *_npcScriptData; + + ScriptState *_scriptClick; + ScriptData *_scriptClickData; + + Character *_characterList; + Character *_currentCharacter; + + Button *_buttonList; + Button *_menuButtonList; + bool _displayMenu; + bool _menuRestoreScreen; + bool _displaySubMenu; + bool _cancelSubMenu; + int _savegameOffset; + int _gameToLoad; + char _savegameName[31]; + const char *_specialSavegameString; + KeyboardEvent _keyboardEvent; + + struct KyragemState { + uint16 nextOperation; + uint16 rOffset; + uint16 gOffset; + uint16 bOffset; + uint32 timerCount; + } _kyragemFadingState; + + uint8 *_seq_Forest; + uint8 *_seq_KallakWriting; + uint8 *_seq_KyrandiaLogo; + uint8 *_seq_KallakMalcolm; + uint8 *_seq_MalcolmTree; + uint8 *_seq_WestwoodLogo; + uint8 *_seq_Demo1; + uint8 *_seq_Demo2; + uint8 *_seq_Demo3; + uint8 *_seq_Demo4; + uint8 *_seq_Reunion; + + char **_seq_WSATable; + char **_seq_CPSTable; + char **_seq_COLTable; + char **_seq_textsTable; + + int _seq_WSATable_Size; + int _seq_CPSTable_Size; + int _seq_COLTable_Size; + int _seq_textsTable_Size; + + char **_itemList; + char **_takenList; + char **_placedList; + char **_droppedList; + char **_noDropList; + char **_putDownFirst; + char **_waitForAmulet; + char **_blackJewel; + char **_poisonGone; + char **_healingTip; + char **_thePoison; + char **_fluteString; + char **_wispJewelStrings; + char **_magicJewelString; + char **_flaskFull; + char **_fullFlask; + char **_veryClever; + char **_homeString; + + int _itemList_Size; + int _takenList_Size; + int _placedList_Size; + int _droppedList_Size; + int _noDropList_Size; + int _putDownFirst_Size; + int _waitForAmulet_Size; + int _blackJewel_Size; + int _poisonGone_Size; + int _healingTip_Size; + int _thePoison_Size; + int _fluteString_Size; + int _wispJewelStrings_Size; + int _magicJewelString_Size; + int _flaskFull_Size; + int _fullFlask_Size; + int _veryClever_Size; + int _homeString_Size; + + char **_characterImageTable; + int _characterImageTableSize; + + Shape *_defaultShapeTable; + int _defaultShapeTableSize; + + Shape *_healingShapeTable; + int _healingShapeTableSize; + Shape *_healingShape2Table; + int _healingShape2TableSize; + + Shape *_posionDeathShapeTable; + int _posionDeathShapeTableSize; + + Shape *_fluteAnimShapeTable; + int _fluteAnimShapeTableSize; + + Shape *_winterScrollTable; + int _winterScrollTableSize; + Shape *_winterScroll1Table; + int _winterScroll1TableSize; + Shape *_winterScroll2Table; + int _winterScroll2TableSize; + + Shape *_drinkAnimationTable; + int _drinkAnimationTableSize; + + Shape *_brandonToWispTable; + int _brandonToWispTableSize; + + Shape *_magicAnimationTable; + int _magicAnimationTableSize; + + Shape *_brandonStoneTable; + int _brandonStoneTableSize; + + Room *_roomTable; + int _roomTableSize; + char **_roomFilenameTable; + int _roomFilenameTableSize; + + uint8 *_amuleteAnim; + + uint8 *_specialPalettes[33]; + + Timer _timers[34]; + uint32 _timerNextRun; + static const char *_xmidiFiles[]; + static const int _xmidiFilesCount; + + static const int8 _charXPosTable[]; + static const int8 _addXPosTable[]; + static const int8 _charYPosTable[]; + static const int8 _addYPosTable[]; + + // positions of the inventory + static const uint16 _itemPosX[]; + static const uint8 _itemPosY[]; + + static Button _buttonData[]; + static Button *_buttonDataListPtr[]; + static Button _menuButtonData[]; + static Button _scrollUpButton; + static Button _scrollDownButton; + + static Menu _menu[]; + + static const uint8 _magicMouseItemStartFrame[]; + static const uint8 _magicMouseItemEndFrame[]; + static const uint8 _magicMouseItemStartFrame2[]; + static const uint8 _magicMouseItemEndFrame2[]; + + static const uint16 _amuletX[]; + static const uint16 _amuletY[]; + static const uint16 _amuletX2[]; + static const uint16 _amuletY2[]; +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/module.mk b/engines/kyra/module.mk new file mode 100644 index 0000000000..943c628831 --- /dev/null +++ b/engines/kyra/module.mk @@ -0,0 +1,33 @@ +MODULE := engines/kyra + +MODULE_OBJS := \ + engines/kyra/kyra.o \ + engines/kyra/resource.o \ + engines/kyra/screen.o \ + engines/kyra/script_v1.o \ + engines/kyra/script.o \ + engines/kyra/seqplayer.o \ + engines/kyra/sound.o \ + engines/kyra/staticres.o \ + engines/kyra/sprites.o \ + engines/kyra/wsamovie.o \ + engines/kyra/debugger.o \ + engines/kyra/animator.o \ + engines/kyra/gui.o \ + engines/kyra/sequences_v1.o \ + engines/kyra/items.o \ + engines/kyra/scene.o \ + engines/kyra/text.o \ + engines/kyra/timer.o \ + engines/kyra/saveload.o + +MODULE_DIRS += \ + engines/kyra + +# 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/kyra/resource.cpp b/engines/kyra/resource.cpp new file mode 100644 index 0000000000..0dd76ff5de --- /dev/null +++ b/engines/kyra/resource.cpp @@ -0,0 +1,366 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-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 "kyra/resource.h" +#include "kyra/script.h" +#include "kyra/wsamovie.h" +#include "kyra/screen.h" + +namespace Kyra { +Resource::Resource(KyraEngine *engine) { + _engine = engine; + + // No PAK files in the demo version + if (_engine->features() & GF_DEMO) + return; + + // prefetches all PAK Files + + // ugly a hardcoded list + // TODO: use the FS Backend to get all .PAK Files and load them + // or any other thing to get all files + static const char *kyra1Filelist[] = { + "A_E.PAK", "DAT.PAK", "F_L.PAK", "MAP_5.PAK", "MSC.PAK", "M_S.PAK", + "S_Z.PAK", "WSA1.PAK", "WSA2.PAK", "WSA3.PAK", "WSA4.PAK", "WSA5.PAK", + "WSA6.PAK", 0 + }; + + static const char *kyra1CDFilelist[] = { + "ALTAR.APK", "BELROOM.APK", "BONKBG.APK", "BROKEN.APK", "CASTLE.APK", "CAVE.APK", "CGATE.APK", + "DEAD.APK", "DNSTAIR.APK", "DRAGON1.APK", "DRAGON2.APK", "EXTPOT.APK", "FORESTA.APK", "FORESTB.APK", + "FOUNTN.APK", "FOYER.APK", "GATECV.APK", "GEM.APK", "GEMCUT.APK", "GENHALL.APK", "GLADE.APK", + "GRAVE.APK", "HEALER.APK", "LAGOON.APK", "LANDING.APK", "LAVA.APK", "LEPHOLE.APK", "LIBRARY.APK", + "MIX.APK", "MOONCAV.APK", "POTION.APK", "SONG.APK", "SORROW.APK", "SPELL.APK", "STUMP.APK", + "TEMPLE.APK", "TRUNK.APK", "WILLOW.APK", "XEDGE.APK", + + "ADL.PAK", "BRINS.PAK", "CLIFF.PAK", "ENTER.PAK", "FORESTA.PAK", "GEM.PAK", "INTRO1.PAK", + "LEPHOLE.PAK", "OAKS.PAK", "SPELL.PAK", "WILLOW.PAK", "ALCHEMY.PAK", "BROKEN.PAK", "COL.PAK", + "EXTHEAL.PAK", "FORESTB.PAK", "GEMCUT.PAK", "INTRO2.PAK", "LIBRARY.PAK", "PLATEAU.PAK", "SPRING.PAK", + "WISE.PAK", "ALGAE.PAK", "BURN.PAK", "DARMS.PAK", "EXTPOT.PAK", "FORESTC.PAK", "GENCAVB.PAK", + "INTRO3.PAK", "MISC.PAK", "PLTCAVE.PAK", "SQUARE.PAK", "XEDGE.PAK", "ALTAR.PAK", "CASTLE.PAK", + "DEAD.PAK", "EXTSPEL.PAK", "FOUNTN.PAK", "GENHALL.PAK", "INTRO4.PAK", "MIX.PAK", "POTION.PAK", + "STARTUP.PAK", "XEDGEB.PAK", "ARCH.PAK", "CATACOM.PAK", "DNSTAIR.PAK", "FALLS.PAK", "FOYER.PAK", + "GEN_CAV.PAK", "KITCHEN.PAK", "MOONCAV.PAK", "RUBY.PAK", "STUMP.PAK", "XEDGEC.PAK", "BALCONY.PAK", + "CAVE.PAK", "DRAGON.PAK", "FESTSTH.PAK", "FSOUTH.PAK", "GLADE.PAK", "KYRAGEM.PAK", "NCLIFF.PAK", + "SICKWIL.PAK", "TEMPLE.PAK", "XMI.PAK", "BELROOM.PAK", "CAVEB.PAK", "EDGE.PAK", "FGOWEST.PAK", + "FSOUTHB.PAK", "GRAVE.PAK", "LAGOON.PAK", "NCLIFFB.PAK", "SND.PAK", "TRUNK.PAK", "ZROCK.PAK", + "BONKBG.PAK", "CGATE.PAK", "EDGEB.PAK", "FINALE.PAK", "FWSTSTH.PAK", "GRTHALL.PAK", "LANDING.PAK", + "NWCLIFB.PAK", "SONG.PAK", "UPSTAIR.PAK", "BRIDGE.PAK", "CHASM.PAK", "EMCAV.PAK", "FNORTH.PAK", + "GATECV.PAK", "HEALER.PAK", "LAVA.PAK", "NWCLIFF.PAK", "SORROW.PAK", "WELL.PAK", + + "CHAPTER1.VRM", 0 + }; + + const char **usedFilelist = 0; + + if (_engine->features() & GF_FLOPPY) + usedFilelist = kyra1Filelist; + else if (_engine->features() & GF_TALKIE) + usedFilelist = kyra1CDFilelist; + else + error("no filelist found for this game"); + + for (uint32 tmp = 0; usedFilelist[tmp]; ++tmp) { + // prefetch file + PAKFile *file = new PAKFile(usedFilelist[tmp]); + assert(file); + + PakFileEntry newPak; + newPak._file = file; + strncpy(newPak._filename, usedFilelist[tmp], 32); + if (file->isOpen() && file->isValid()) + _pakfiles.push_back(newPak); + else { + delete file; + debug(3, "couldn't load file '%s' correctly", usedFilelist[tmp]); + } + } +} + +Resource::~Resource() { + Common::List<PakFileEntry>::iterator start = _pakfiles.begin(); + + for (;start != _pakfiles.end(); ++start) { + delete start->_file; + start->_file = 0; + } +} + +bool Resource::loadPakFile(const char *filename) { + if (isInPakList(filename)) + return true; + PAKFile *file = new PAKFile(filename); + if (!file) { + error("couldn't load file: '%s'", filename); + } + PakFileEntry newPak; + newPak._file = file; + strncpy(newPak._filename, filename, 32); + _pakfiles.push_back(newPak); + return true; +} + +void Resource::unloadPakFile(const char *filename) { + Common::List<PakFileEntry>::iterator start = _pakfiles.begin(); + for (;start != _pakfiles.end(); ++start) { + if (scumm_stricmp(start->_filename, filename) == 0) { + delete start->_file; + _pakfiles.erase(start); + break; + } + } + return; +} + +bool Resource::isInPakList(const char *filename) { + Common::List<PakFileEntry>::iterator start = _pakfiles.begin(); + for (;start != _pakfiles.end(); ++start) { + if (scumm_stricmp(start->_filename, filename) == 0) + return true; + } + return false; +} + +uint8 *Resource::fileData(const char *file, uint32 *size) { + uint8 *buffer = 0; + Common::File file_; + + // test to open it in the main dir + if (file_.open(file)) { + + *size = file_.size(); + buffer = new uint8[*size]; + assert(buffer); + + file_.read(buffer, *size); + + file_.close(); + } else { + // opens the file in a PAK File + Common::List<PakFileEntry>::iterator start = _pakfiles.begin(); + + for (;start != _pakfiles.end(); ++start) { + *size = start->_file->getFileSize(file); + + if (!(*size)) + continue; + + buffer = start->_file->getFile(file); + break; + } + } + + if (!buffer || !(*size)) { + return 0; + } + + return buffer; +} + +bool Resource::fileHandle(const char *file, uint32 *size, Common::File &filehandle) { + filehandle.close(); + + if (filehandle.open(file)) + return true; + + Common::List<PakFileEntry>::iterator start = _pakfiles.begin(); + + for (;start != _pakfiles.end(); ++start) { + *size = start->_file->getFileSize(file); + + if (!(*size)) + continue; + + if (start->_file->getFileHandle(file, filehandle)) { + return true; + } + } + + return false; +} + +/////////////////////////////////////////// +// Pak file manager +#define PAKFile_Iterate Common::List<PakChunk*>::iterator start=_files.begin();start != _files.end(); ++start +PAKFile::PAKFile(const Common::String& file) { + _filename = 0; + + Common::File pakfile; + uint8 *buffer = 0; + _open = false; + + if (!pakfile.open(file.c_str())) { + debug(3, "couldn't open pakfile '%s'\n", file.c_str()); + return; + } + + uint32 filesize = pakfile.size(); + buffer = new uint8[filesize]; + assert(buffer); + + pakfile.read(buffer, filesize); + pakfile.close(); + + // works with the file + uint32 pos = 0, startoffset = 0, endoffset = 0; + + startoffset = READ_LE_UINT32(buffer + pos); + pos += 4; + + while (pos < filesize) { + PakChunk* chunk = new PakChunk; + assert(chunk); + + // saves the name + chunk->_name = new char[strlen((const char*)buffer + pos) + 1]; + assert(chunk->_name); + strcpy(chunk->_name, (const char*)buffer + pos); + pos += strlen(chunk->_name) + 1; + if (!(*chunk->_name)) + break; + + endoffset = READ_LE_UINT32(buffer + pos); + pos += 4; + + if (endoffset == 0) { + endoffset = filesize; + } + + chunk->_start = startoffset; + chunk->_size = endoffset - startoffset; + + _files.push_back(chunk); + + if (endoffset == filesize) + break; + + startoffset = endoffset; + } + _open = true; + delete [] buffer; + + _filename = new char[file.size()+1]; + assert(_filename); + strcpy(_filename, file.c_str()); +} + +PAKFile::~PAKFile() { + delete [] _filename; + _filename = 0; + _open = false; + + for (PAKFile_Iterate) { + delete [] (*start)->_name; + (*start)->_name = 0; + delete *start; + *start = 0; + } +} + +uint8 *PAKFile::getFile(const char *file) { + for (PAKFile_Iterate) { + if (!scumm_stricmp((*start)->_name, file)) { + Common::File pakfile; + if (!pakfile.open(_filename)) { + debug(3, "couldn't open pakfile '%s'\n", _filename); + return 0; + } + pakfile.seek((*start)->_start); + uint8 *buffer = new uint8[(*start)->_size]; + assert(buffer); + pakfile.read(buffer, (*start)->_size); + return buffer; + } + } + return 0; +} + +bool PAKFile::getFileHandle(const char *file, Common::File &filehandle) { + filehandle.close(); + + for (PAKFile_Iterate) { + if (!scumm_stricmp((*start)->_name, file)) { + if (!filehandle.open(_filename)) { + debug(3, "couldn't open pakfile '%s'\n", _filename); + return 0; + } + filehandle.seek((*start)->_start); + return true; + } + } + return false; +} + +uint32 PAKFile::getFileSize(const char* file) { + for (PAKFile_Iterate) { + if (!scumm_stricmp((*start)->_name, file)) + return (*start)->_size; + } + return 0; +} + +void KyraEngine::loadPalette(const char *filename, uint8 *palData) { + debug(9, "KyraEngine::loadPalette('%s' 0x%X)", filename, palData); + uint32 fileSize = 0; + uint8 *srcData = _res->fileData(filename, &fileSize); + + if (palData && fileSize) { + debug(9, "Loading a palette of size %i from '%s'", fileSize, filename); + memcpy(palData, srcData, fileSize); + } + delete [] srcData; +} + +void KyraEngine::loadBitmap(const char *filename, int tempPage, int dstPage, uint8 *palData) { + debug(9, "KyraEngine::copyBitmap('%s', %d, %d, 0x%X)", filename, tempPage, dstPage, palData); + uint32 fileSize; + uint8 *srcData = _res->fileData(filename, &fileSize); + uint8 compType = srcData[2]; + uint32 imgSize = READ_LE_UINT32(srcData + 4); + uint16 palSize = READ_LE_UINT16(srcData + 8); + if (palData && palSize) { + debug(9, "Loading a palette of size %i from %s", palSize, filename); + memcpy(palData, srcData + 10, palSize); + } + uint8 *srcPtr = srcData + 10 + palSize; + uint8 *dstData = _screen->getPagePtr(dstPage); + switch (compType) { + case 0: + memcpy(dstData, srcPtr, imgSize); + break; + case 3: + Screen::decodeFrame3(srcPtr, dstData, imgSize); + break; + case 4: + Screen::decodeFrame4(srcPtr, dstData, imgSize); + break; + default: + error("Unhandled bitmap compression %d", compType); + break; + } + delete[] srcData; +} + +} // end of namespace Kyra + diff --git a/engines/kyra/resource.h b/engines/kyra/resource.h new file mode 100644 index 0000000000..1a479c0b73 --- /dev/null +++ b/engines/kyra/resource.h @@ -0,0 +1,95 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-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 RESOURCE_H +#define RESOURCE_H + +#include "common/stdafx.h" +#include "common/scummsys.h" +#include "common/str.h" +#include "common/list.h" +#include "common/map.h" + +#include "kyra/kyra.h" + +namespace Kyra { + +// standard Package format for Kyrandia games +class PAKFile { + struct PakChunk { + char* _name; + uint32 _start; + uint32 _size; + }; + +public: + + PAKFile(const Common::String &file); + ~PAKFile(); + + uint8* getFile(const char *file); + bool getFileHandle(const char *file, Common::File &filehandle); + uint32 getFileSize(const char *file); + + bool isValid(void) const { return (_filename != 0); } + bool isOpen(void) const { return _open; } + +private: + + bool _open; + char *_filename; + Common::List<PakChunk*> _files; // the entries +}; + +// some resource types +class Movie; +class VMContext; + +class Resource { +public: + + Resource(KyraEngine *engine); + ~Resource(); + + bool loadPakFile(const char *filename); + void unloadPakFile(const char *filename); + bool isInPakList(const char *filename); + + uint8* fileData(const char *file, uint32 *size); + // it gives back a file handle (used for the speech player) + // it could be that the needed file is embedded in the returned + // handle + bool fileHandle(const char *file, uint32 *size, Common::File &filehandle); + +protected: + struct PakFileEntry { + PAKFile *_file; + char _filename[32]; + }; + + KyraEngine* _engine; + Common::List<PakFileEntry> _pakfiles; +}; + +} // end of namespace Kyra + +#endif diff --git a/engines/kyra/saveload.cpp b/engines/kyra/saveload.cpp new file mode 100644 index 0000000000..fcc6dbfe66 --- /dev/null +++ b/engines/kyra/saveload.cpp @@ -0,0 +1,319 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2005-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 "kyra/kyra.h" +#include "kyra/animator.h" +#include "kyra/screen.h" +#include "kyra/resource.h" + +#include "common/savefile.h" +#include "common/system.h" + +#define CURRENT_VERSION 3 + +namespace Kyra { +void KyraEngine::loadGame(const char *fileName) { + debug(9, "loadGame('%s')", fileName); + Common::InSaveFile *in; + + if (!(in = _saveFileMan->openForLoading(fileName))) { + warning("Can't open file '%s', game not loaded", fileName); + return; + } + + uint32 type = in->readUint32BE(); + if (type != MKID('KYRA')) { + warning("No Kyrandia 1 savefile header"); + delete in; + return; + } + uint32 version = in->readUint32BE(); + if (version > CURRENT_VERSION) { + warning("Savegame is not the right version (%d)", version); + delete in; + return; + } + + char saveName[31]; + in->read(saveName, 31); + + if (version >= 2) { + uint32 gameFlags = in->readUint32BE(); + if ((gameFlags & GF_FLOPPY) && !(_features & GF_FLOPPY)) { + warning("can not load floppy savefile for this (non floppy) gameversion!"); + delete in; + return; + } else if ((gameFlags & GF_TALKIE) && !(_features & GF_TALKIE)) { + warning("can not load cdrom savefile for this (non cdrom) gameversion!"); + delete in; + return; + } + } else { + warning("Make sure your savefile was from this version! (too old savefile version to detect that)"); + } + + snd_playSoundEffect(0x0A); + snd_playWanderScoreViaMap(0, 1); + + // unload the current voice file should fix some problems with voices + if (_currentRoom != 0xFFFF && (_features & GF_TALKIE)) { + char file[32]; + assert(_currentRoom < _roomTableSize); + int tableId = _roomTable[_currentRoom].nameIndex; + assert(tableId < _roomFilenameTableSize); + strcpy(file, _roomFilenameTable[tableId]); + strcat(file, ".VRM"); + _res->unloadPakFile(file); + } + + int brandonX = 0, brandonY = 0; + for (int i = 0; i < 11; i++) { + _characterList[i].sceneId = in->readUint16BE(); + _characterList[i].height = in->readByte(); + _characterList[i].facing = in->readByte(); + _characterList[i].currentAnimFrame = in->readUint16BE(); + //_characterList[i].unk6 = in->readUint32BE(); + in->read(_characterList[i].inventoryItems, 10); + _characterList[i].x1 = in->readSint16BE(); + _characterList[i].y1 = in->readSint16BE(); + _characterList[i].x2 = in->readSint16BE(); + _characterList[i].y2 = in->readSint16BE(); + if (i == 0) { + brandonX = _characterList[i].x1; + brandonY = _characterList[i].y1; + } + //_characterList[i].field_20 = in->readUint16BE(); + //_characterList[i].field_23 = in->readUint16BE(); + } + + _marbleVaseItem = in->readSint16BE(); + _itemInHand = in->readByte(); + + for (int i = 0; i < 4; ++i) { + _birthstoneGemTable[i] = in->readByte(); + } + for (int i = 0; i < 3; ++i) { + _idolGemsTable[i] = in->readByte(); + } + for (int i = 0; i < 3; ++i) { + _foyerItemTable[i] = in->readByte(); + } + _cauldronState = in->readByte(); + for (int i = 0; i < 2; ++i) { + _crystalState[i] = in->readByte(); + } + + _brandonStatusBit = in->readUint16BE(); + _brandonStatusBit0x02Flag = in->readByte(); + _brandonStatusBit0x20Flag = in->readByte(); + in->read(_brandonPoisonFlagsGFX, 256); + _brandonInvFlag = in->readSint16BE(); + _poisonDeathCounter = in->readByte(); + _animator->_brandonDrawFrame = in->readUint16BE(); + + for (int i = 0; i < 32; i++) { + _timers[i].active = in->readByte(); + _timers[i].countdown = in->readSint32BE(); + _timers[i].nextRun = in->readUint32BE(); + if (_timers[i].nextRun != 0) + _timers[i].nextRun += _system->getMillis(); + } + _timerNextRun = 0; + + uint32 flagsSize = in->readUint32BE(); + assert(flagsSize == sizeof(_flagsTable)); + in->read(_flagsTable, flagsSize); + + for (int i = 0; i < _roomTableSize; ++i) { + for (int item = 0; item < 12; ++item) { + _roomTable[i].itemsTable[item] = 0xFF; + _roomTable[i].itemsXPos[item] = 0xFFFF; + _roomTable[i].itemsYPos[item] = 0xFF; + _roomTable[i].needInit[item] = 0; + } + } + + uint16 sceneId = 0; + + while (true) { + sceneId = in->readUint16BE(); + if (sceneId == 0xFFFF) + break; + assert(sceneId < _roomTableSize); + _roomTable[sceneId].nameIndex = in->readByte(); + + for (int i = 0; i < 12; i++) { + _roomTable[sceneId].itemsTable[i] = in->readByte(); + _roomTable[sceneId].itemsXPos[i] = in->readUint16BE(); + _roomTable[sceneId].itemsYPos[i] = in->readUint16BE(); + _roomTable[sceneId].needInit[i] = in->readByte(); + } + } + if (version >= 3) { + _lastMusicCommand = in->readSint16BE(); + if (_lastMusicCommand != -1) + snd_playWanderScoreViaMap(_lastMusicCommand, 1); + } + + if (queryGameFlag(0x2D)) { + loadMainScreen(8); + loadBitmap("AMULET3.CPS", 10, 10, 0); + if (!queryGameFlag(0xF1)) { + for (int i = 0x55; i <= 0x5A; ++i) { + if (queryGameFlag(i)) { + seq_createAmuletJewel(i-0x55, 10, 1, 1); + } + } + } + _screen->copyRegion(0, 0, 0, 0, 320, 200, 10, 8); + uint8 *_pageSrc = _screen->getPagePtr(8); + uint8 *_pageDst = _screen->getPagePtr(0); + memcpy(_pageDst, _pageSrc, 320*200); + } else { + loadMainScreen(8); + } + + createMouseItem(_itemInHand); + _animator->setBrandonAnimSeqSize(5, 48); + _animator->_noDrawShapesFlag = 1; + enterNewScene(_currentCharacter->sceneId, _currentCharacter->facing, 0, 0, 1); + _animator->_noDrawShapesFlag = 0; + + _currentCharacter->x1 = brandonX; + _currentCharacter->y1 = brandonY; + _animator->animRefreshNPC(0); + _animator->restoreAllObjectBackgrounds(); + _animator->preserveAnyChangedBackgrounds(); + _animator->prepDrawAllObjects(); + _animator->copyChangedObjectsForward(0); + _screen->copyRegion(8, 8, 8, 8, 304, 128, 2, 0); + redrawInventory(0); + + _abortWalkFlag = true; + _abortWalkFlag2 = false; + _mousePressFlag = false; + _mouseX = brandonX; + _mouseY = brandonY; + _system->warpMouse(brandonX, brandonY); + + if (in->ioFailed()) + error("Load failed."); + else + debug(1, "Loaded savegame '%s.'", saveName); + + delete in; +} + +void KyraEngine::saveGame(const char *fileName, const char *saveName) { + debug(9, "saveGame('%s', '%s')", fileName, saveName); + Common::OutSaveFile *out; + + if (!(out = _saveFileMan->openForSaving(fileName))) { + warning("Can't create file '%s', game not saved", fileName); + return; + } + + // Savegame version + out->writeUint32BE(MKID('KYRA')); + out->writeUint32BE(CURRENT_VERSION); + out->write(saveName, 31); + out->writeUint32BE(_features); + + for (int i = 0; i < 11; i++) { + out->writeUint16BE(_characterList[i].sceneId); + out->writeByte(_characterList[i].height); + out->writeByte(_characterList[i].facing); + out->writeUint16BE(_characterList[i].currentAnimFrame); + //out->writeUint32BE(_characterList[i].unk6); + out->write(_characterList[i].inventoryItems, 10); + out->writeSint16BE(_characterList[i].x1); + out->writeSint16BE(_characterList[i].y1); + out->writeSint16BE(_characterList[i].x2); + out->writeSint16BE(_characterList[i].y2); + //out->writeUint16BE(_characterList[i].field_20); + //out->writeUint16BE(_characterList[i].field_23); + } + + out->writeSint16BE(_marbleVaseItem); + out->writeByte(_itemInHand); + + for (int i = 0; i < 4; ++i) { + out->writeByte(_birthstoneGemTable[i]); + } + for (int i = 0; i < 3; ++i) { + out->writeByte(_idolGemsTable[i]); + } + for (int i = 0; i < 3; ++i) { + out->writeByte(_foyerItemTable[i]); + } + out->writeByte(_cauldronState); + for (int i = 0; i < 2; ++i) { + out->writeByte(_crystalState[i]); + } + + out->writeUint16BE(_brandonStatusBit); + out->writeByte(_brandonStatusBit0x02Flag); + out->writeByte(_brandonStatusBit0x20Flag); + out->write(_brandonPoisonFlagsGFX, 256); + out->writeSint16BE(_brandonInvFlag); + out->writeByte(_poisonDeathCounter); + out->writeUint16BE(_animator->_brandonDrawFrame); + + for (int i = 0; i < 32; i++) { + out->writeByte(_timers[i].active); + out->writeSint32BE(_timers[i].countdown); + if (_system->getMillis() >= _timers[i].nextRun) { + out->writeUint32BE(0); + } else { + out->writeUint32BE(_timers[i].nextRun - _system->getMillis()); + } + } + + out->writeUint32BE(sizeof(_flagsTable)); + out->write(_flagsTable, sizeof(_flagsTable)); + + for (uint16 i = 0; i < _roomTableSize; i++) { + out->writeUint16BE(i); + out->writeByte(_roomTable[i].nameIndex); + for (int a = 0; a < 12; a++) { + out->writeByte(_roomTable[i].itemsTable[a]); + out->writeUint16BE(_roomTable[i].itemsXPos[a]); + out->writeUint16BE(_roomTable[i].itemsYPos[a]); + out->writeByte(_roomTable[i].needInit[a]); + } + } + // room table terminator + out->writeUint16BE(0xFFFF); + + out->writeSint16BE(_lastMusicCommand); + + out->flush(); + + // check for errors + if (out->ioFailed()) + warning("Can't write file '%s'. (Disk full?)", fileName); + else + debug(1, "Saved game '%s.'", saveName); + + delete out; +} +} // end of namespace Kyra diff --git a/engines/kyra/scene.cpp b/engines/kyra/scene.cpp new file mode 100644 index 0000000000..a94050a296 --- /dev/null +++ b/engines/kyra/scene.cpp @@ -0,0 +1,1581 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2005-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 "kyra/kyra.h" +#include "kyra/seqplayer.h" +#include "kyra/screen.h" +#include "kyra/resource.h" +#include "kyra/sound.h" +#include "kyra/sprites.h" +#include "kyra/wsamovie.h" +#include "kyra/animator.h" +#include "kyra/text.h" +#include "kyra/script.h" + +#include "common/system.h" +#include "common/savefile.h" + +namespace Kyra { + +void KyraEngine::enterNewScene(int sceneId, int facing, int unk1, int unk2, int brandonAlive) { + debug(9, "KyraEngine::enterNewScene(%d, %d, %d, %d, %d)", sceneId, facing, unk1, unk2, brandonAlive); + int unkVar1 = 1; + _screen->hideMouse(); + _handleInput = false; + _abortWalkFlag = false; + _abortWalkFlag2 = false; + if (_currentCharacter->sceneId == 7 && sceneId == 24) { + _newMusicTheme = 3; + } else if (_currentCharacter->sceneId == 25 && sceneId == 109) { + _newMusicTheme = 4; + } else if (_currentCharacter->sceneId == 120 && sceneId == 37) { + _newMusicTheme = 5; + } else if (_currentCharacter->sceneId == 52 && sceneId == 199) { + _newMusicTheme = 6; + } else if (_currentCharacter->sceneId == 37 && sceneId == 120) { + _newMusicTheme = 4; + } else if (_currentCharacter->sceneId == 109 && sceneId == 25) { + _newMusicTheme = 3; + } else if (_currentCharacter->sceneId == 24 && sceneId == 7) { + _newMusicTheme = 2; + } + if (_newMusicTheme != _curMusicTheme) { + snd_playTheme(_newMusicTheme); + } + + switch (_currentCharacter->sceneId) { + case 1: + if (sceneId == 0) { + moveCharacterToPos(0, 0, _currentCharacter->x1, 84); + unkVar1 = 0; + } + break; + + case 3: + if (sceneId == 2) { + moveCharacterToPos(0, 6, 155, _currentCharacter->y1); + unkVar1 = 0; + } + break; + + case 26: + if (sceneId == 27) { + moveCharacterToPos(0, 6, 155, _currentCharacter->y1); + unkVar1 = 0; + } + break; + + case 44: + if (sceneId == 45) { + moveCharacterToPos(0, 2, 192, _currentCharacter->y1); + unkVar1 = 0; + } + break; + + default: + break; + } + + if (unkVar1 && unk1) { + int xpos = _currentCharacter->x1; + int ypos = _currentCharacter->y1; + switch (facing) { + case 0: + ypos = _currentCharacter->y1 - 6; + break; + + case 2: + xpos = 336; + break; + + case 4: + ypos = 143; + break; + + case 6: + xpos = -16; + break; + + default: + break; + } + + moveCharacterToPos(0, facing, xpos, ypos); + } + + for (int i = 0; i < ARRAYSIZE(_movieObjects); ++i) { + _movieObjects[i]->close(); + } + + if (!brandonAlive) { + _scriptInterpreter->initScript(_scriptClick, _scriptClickData); + _scriptInterpreter->startScript(_scriptClick, 5); + while (_scriptInterpreter->validScript(_scriptClick)) { + _scriptInterpreter->runScript(_scriptClick); + } + } + + memset(_entranceMouseCursorTracks, 0xFFFF, sizeof(uint16)*4); + _currentCharacter->sceneId = sceneId; + + assert(sceneId < _roomTableSize); + assert(_roomTable[sceneId].nameIndex < _roomFilenameTableSize); + + Room *currentRoom = &_roomTable[sceneId]; + + if (_currentRoom != 0xFFFF && (_features & GF_TALKIE)) { + char file[32]; + assert(_currentRoom < _roomTableSize); + int tableId = _roomTable[_currentRoom].nameIndex; + assert(tableId < _roomFilenameTableSize); + strcpy(file, _roomFilenameTable[tableId]); + strcat(file, ".VRM"); + _res->unloadPakFile(file); + } + + _currentRoom = sceneId; + + int tableId = _roomTable[_currentCharacter->sceneId].nameIndex; + char fileNameBuffer[32]; + strcpy(fileNameBuffer, _roomFilenameTable[tableId]); + strcat(fileNameBuffer, ".DAT"); + _sprites->loadDAT(fileNameBuffer, _sceneExits); + _sprites->setupSceneAnims(); + _scriptInterpreter->unloadScript(_scriptClickData); + loadSceneMSC(); + + if ((_features & GF_TALKIE)) { + strcpy(fileNameBuffer, _roomFilenameTable[tableId]); + strcat(fileNameBuffer, ".VRM"); + _res->loadPakFile(fileNameBuffer); + } + + _walkBlockNorth = currentRoom->northExit; + _walkBlockEast = currentRoom->eastExit; + _walkBlockSouth = currentRoom->southExit; + _walkBlockWest = currentRoom->westExit; + + if (_walkBlockNorth == 0xFFFF) { + _screen->blockOutRegion(0, 0, 320, (_northExitHeight & 0xFF)+3); + } + if (_walkBlockEast == 0xFFFF) { + _screen->blockOutRegion(312, 0, 8, 139); + } + if (_walkBlockSouth == 0xFFFF) { + _screen->blockOutRegion(0, 135, 320, 8); + } + if (_walkBlockWest == 0xFFFF) { + _screen->blockOutRegion(0, 0, 8, 139); + } + + if (!brandonAlive) { + updatePlayerItemsForScene(); + } + + startSceneScript(brandonAlive); + setupSceneItems(); + + initSceneData(facing, unk2, brandonAlive); + + _loopFlag2 = 0; + _screen->showMouse(); + if (!brandonAlive) { + seq_poisonDeathNow(0); + } + updateMousePointer(true); + _changedScene = true; +} + +void KyraEngine::transcendScenes(int roomIndex, int roomName) { + debug(9, "KyraEngine::transcendScenes(%d, %d)", roomIndex, roomName); + assert(roomIndex < _roomTableSize); + if (_features & GF_TALKIE) { + char file[32]; + assert(roomIndex < _roomTableSize); + int tableId = _roomTable[roomIndex].nameIndex; + assert(tableId < _roomFilenameTableSize); + strcpy(file, _roomFilenameTable[tableId]); + strcat(file, ".VRM"); + _res->unloadPakFile(file); + } + _roomTable[roomIndex].nameIndex = roomName; + _unkScreenVar2 = 1; + _unkScreenVar3 = 1; + _unkScreenVar1 = 0; + _brandonPosX = _currentCharacter->x1; + _brandonPosY = _currentCharacter->y1; + enterNewScene(roomIndex, _currentCharacter->facing, 0, 0, 0); + _unkScreenVar1 = 1; + _unkScreenVar2 = 0; + _unkScreenVar3 = 0; +} + +void KyraEngine::setSceneFile(int roomIndex, int roomName) { + debug(9, "KyraEngine::setSceneFile(%d, %d)", roomIndex, roomName); + assert(roomIndex < _roomTableSize); + _roomTable[roomIndex].nameIndex = roomName; +} + +void KyraEngine::moveCharacterToPos(int character, int facing, int xpos, int ypos) { + debug(9, "KyraEngine::moveCharacterToPos(%d, %d, %d, %d)", character, facing, xpos, ypos); + Character *ch = &_characterList[character]; + ch->facing = facing; + _screen->hideMouse(); + xpos = (int16)(xpos & 0xFFFC); + ypos = (int16)(ypos & 0xFFFE); + disableTimer(19); + disableTimer(14); + disableTimer(18); + uint32 nextFrame = 0; + switch (facing) { + case 0: + while (ypos < ch->y1) { + nextFrame = getTimerDelay(5 + character) * _tickLength + _system->getMillis(); + setCharacterPositionWithUpdate(character); + while (_system->getMillis() < nextFrame) { updateGameTimers(); } + } + break; + + case 2: + while (ch->x1 < xpos) { + nextFrame = getTimerDelay(5 + character) * _tickLength + _system->getMillis(); + setCharacterPositionWithUpdate(character); + while (_system->getMillis() < nextFrame) { updateGameTimers(); } + } + break; + + case 4: + while (ypos > ch->y1) { + nextFrame = getTimerDelay(5 + character) * _tickLength + _system->getMillis(); + setCharacterPositionWithUpdate(character); + while (_system->getMillis() < nextFrame) { updateGameTimers(); } + } + break; + + case 6: + while (ch->x1 > xpos) { + nextFrame = getTimerDelay(5 + character) * _tickLength + _system->getMillis(); + setCharacterPositionWithUpdate(character); + while (_system->getMillis() < nextFrame) { updateGameTimers(); } + } + break; + + default: + break; + } + enableTimer(19); + enableTimer(14); + enableTimer(18); + _screen->showMouse(); +} + +void KyraEngine::setCharacterPositionWithUpdate(int character) { + debug(9, "KyraEngine::setCharacterPositionWithUpdate(%d)", character); + setCharacterPosition(character, 0); + _sprites->updateSceneAnims(); + updateGameTimers(); + _animator->updateAllObjectShapes(); + updateTextFade(); + + if (_currentCharacter->sceneId == 210) { + updateKyragemFading(); + } +} + +int KyraEngine::setCharacterPosition(int character, int *facingTable) { + debug(9, "KyraEngine::setCharacterPosition(%d, 0x%X)", character, facingTable); + if (character == 0) { + _currentCharacter->x1 += _charXPosTable[_currentCharacter->facing]; + _currentCharacter->y1 += _charYPosTable[_currentCharacter->facing]; + setCharacterPositionHelper(0, facingTable); + return 1; + } else { + _characterList[character].x1 += _charXPosTable[_characterList[character].facing]; + _characterList[character].y1 += _charYPosTable[_characterList[character].facing]; + if (_characterList[character].sceneId == _currentCharacter->sceneId) { + setCharacterPositionHelper(character, 0); + } + } + return 0; +} + +void KyraEngine::setCharacterPositionHelper(int character, int *facingTable) { + debug(9, "KyraEngine::setCharacterPositionHelper(%d, 0x%X)", character, facingTable); + Character *ch = &_characterList[character]; + ++ch->currentAnimFrame; + int facing = ch->facing; + if (facingTable) { + if (*facingTable != *(facingTable - 1)) { + if (*(facingTable - 1) == *(facingTable + 1)) { + facing = getOppositeFacingDirection(*(facingTable - 1)); + *facingTable = *(facingTable - 1); + } + } + } + + static uint8 facingIsZero[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + static uint8 facingIsFour[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + + if (facing == 0) { + ++facingIsZero[character]; + } else { + bool resetTables = false; + if (facing != 7) { + if (facing - 1 != 0) { + if (facing != 4) { + if (facing == 3 || facing == 5) { + if (facingIsFour[character] > 2) { + facing = 4; + } + resetTables = true; + } + } else { + ++facingIsFour[character]; + } + } else { + if (facingIsZero[character] > 2) { + facing = 0; + } + resetTables = true; + } + } else { + if (facingIsZero[character] > 2) { + facing = 0; + } + resetTables = true; + } + + if (resetTables) { + facingIsZero[character] = 0; + facingIsFour[character] = 0; + } + } + + static const uint16 maxAnimationFrame[] = { + 0x000F, 0x0031, 0x0055, 0x0000, 0x0000, 0x0000, + 0x0008, 0x002A, 0x004E, 0x0000, 0x0000, 0x0000, + 0x0022, 0x0046, 0x006A, 0x0000, 0x0000, 0x0000, + 0x001D, 0x0041, 0x0065, 0x0000, 0x0000, 0x0000, + 0x001F, 0x0043, 0x0067, 0x0000, 0x0000, 0x0000, + 0x0028, 0x004C, 0x0070, 0x0000, 0x0000, 0x0000, + 0x0023, 0x0047, 0x006B, 0x0000, 0x0000, 0x0000 + }; + + if (facing == 0) { + if (maxAnimationFrame[36+character] > ch->currentAnimFrame) { + ch->currentAnimFrame = maxAnimationFrame[36+character]; + } + if (maxAnimationFrame[30+character] < ch->currentAnimFrame) { + ch->currentAnimFrame = maxAnimationFrame[36+character]; + } + } else if (facing == 4) { + if (maxAnimationFrame[18+character] > ch->currentAnimFrame) { + ch->currentAnimFrame = maxAnimationFrame[18+character]; + } + if (maxAnimationFrame[12+character] < ch->currentAnimFrame) { + ch->currentAnimFrame = maxAnimationFrame[18+character]; + } + } else { + if (maxAnimationFrame[18+character] < ch->currentAnimFrame) { + ch->currentAnimFrame = maxAnimationFrame[30+character]; + } + if (maxAnimationFrame[character] == ch->currentAnimFrame) { + ch->currentAnimFrame = maxAnimationFrame[6+character]; + } + if (maxAnimationFrame[character] < ch->currentAnimFrame) { + ch->currentAnimFrame = maxAnimationFrame[6+character]+2; + } + } + + if (character == 0) { + if (_brandonStatusBit & 0x10) + ch->currentAnimFrame = 88; + } + + _animator->animRefreshNPC(character); +} + +int KyraEngine::getOppositeFacingDirection(int dir) { + debug(9, "KyraEngine::getOppositeFacingDirection(%d)", dir); + switch (dir) { + case 0: + return 2; + break; + + case 1: + return 1; + break; + + case 3: + return 7; + break; + + case 4: + return 6; + break; + + case 5: + return 5; + break; + + case 6: + return 4; + break; + + case 7: + return 3; + break; + + default: + break; + } + return 0; +} + +void KyraEngine::loadSceneMSC() { + assert(_currentCharacter->sceneId < _roomTableSize); + int tableId = _roomTable[_currentCharacter->sceneId].nameIndex; + assert(tableId < _roomFilenameTableSize); + char fileNameBuffer[32]; + strcpy(fileNameBuffer, _roomFilenameTable[tableId]); + strcat(fileNameBuffer, ".MSC"); + _screen->fillRect(0, 0, 319, 199, 0, 5); + loadBitmap(fileNameBuffer, 3, 5, 0); +} + +void KyraEngine::startSceneScript(int brandonAlive) { + debug(9, "KyraEngine::startSceneScript(%d)", brandonAlive); + assert(_currentCharacter->sceneId < _roomTableSize); + int tableId = _roomTable[_currentCharacter->sceneId].nameIndex; + assert(tableId < _roomFilenameTableSize); + char fileNameBuffer[32]; + strcpy(fileNameBuffer, _roomFilenameTable[tableId]); + strcat(fileNameBuffer, ".CPS"); + loadBitmap(fileNameBuffer, 3, 3, 0); + _sprites->loadSceneShapes(); + _exitListPtr = 0; + + _screen->setScreenPalette(_screen->_currentPalette); + + _scaleMode = 1; + for (int i = 0; i < 145; ++i) { + _scaleTable[i] = 256; + } + + clearNoDropRects(); + _scriptInterpreter->initScript(_scriptClick, _scriptClickData); + strcpy(fileNameBuffer, _roomFilenameTable[tableId]); + strcat(fileNameBuffer, ".EMC"); + _scriptInterpreter->unloadScript(_scriptClickData); + _scriptInterpreter->loadScript(fileNameBuffer, _scriptClickData, _opcodeTable, _opcodeTableSize, 0); + _scriptInterpreter->startScript(_scriptClick, 0); + _scriptClick->variables[0] = _currentCharacter->sceneId; + _scriptClick->variables[7] = brandonAlive; + + while (_scriptInterpreter->validScript(_scriptClick)) { + _scriptInterpreter->runScript(_scriptClick); + } +} + +void KyraEngine::initSceneData(int facing, int unk1, int brandonAlive) { + debug(9, "KyraEngine::initSceneData(%d, %d, %d)", facing, unk1, brandonAlive); + + int16 xpos2 = 0; + int setFacing = 1; + + int16 xpos = 0, ypos = 0; + + if (_brandonPosX == -1 && _brandonPosY == -1) { + switch (facing+1) { + case 0: + xpos = ypos = -1; + break; + + case 1: case 2: case 8: + xpos = _sceneExits.southXPos; + ypos = _sceneExits.southYPos; + break; + + case 3: + xpos = _sceneExits.westXPos; + ypos = _sceneExits.westYPos; + break; + + case 4: case 5: case 6: + xpos = _sceneExits.northXPos; + ypos = _sceneExits.northYPos; + break; + + case 7: + xpos = _sceneExits.eastXPos; + ypos = _sceneExits.eastYPos; + break; + + default: + break; + } + + if ((uint8)(_northExitHeight & 0xFF) + 2 >= ypos) { + ypos = (_northExitHeight & 0xFF) + 4; + } + if (xpos >= 308) { + xpos = 304; + } + if ((uint8)(_northExitHeight >> 8) - 2 <= ypos) { + ypos = (_northExitHeight >> 8) - 4; + } + if (xpos <= 12) { + xpos = 16; + } + } + + if (_brandonPosX > -1) { + xpos = _brandonPosX; + } + if (_brandonPosY > -1) { + ypos = _brandonPosY; + } + + int16 ypos2 = 0; + if (_brandonPosX > -1 && _brandonPosY > -1) { + switch (_currentCharacter->sceneId) { + case 1: + _currentCharacter->x1 = xpos; + _currentCharacter->x2 = xpos; + _currentCharacter->y1 = ypos; + _currentCharacter->y2 = ypos; + facing = 4; + xpos2 = 192; + ypos2 = 104; + setFacing = 0; + unk1 = 1; + break; + + case 3: + _currentCharacter->x1 = xpos; + _currentCharacter->x2 = xpos; + _currentCharacter->y1 = ypos; + _currentCharacter->y2 = ypos; + facing = 2; + xpos2 = 204; + ypos2 = 94; + setFacing = 0; + unk1 = 1; + break; + + case 26: + _currentCharacter->x1 = xpos; + _currentCharacter->x2 = xpos; + _currentCharacter->y1 = ypos; + _currentCharacter->y2 = ypos; + facing = 2; + xpos2 = 192; + ypos2 = 128; + setFacing = 0; + unk1 = 1; + break; + + case 44: + _currentCharacter->x1 = xpos; + _currentCharacter->x2 = xpos; + _currentCharacter->y1 = ypos; + _currentCharacter->y2 = ypos; + facing = 6; + xpos2 = 156; + ypos2 = 96; + setFacing = 0; + unk1 = 1; + break; + + case 37: + _currentCharacter->x1 = xpos; + _currentCharacter->x2 = xpos; + _currentCharacter->y1 = ypos; + _currentCharacter->y2 = ypos; + facing = 2; + xpos2 = 148; + ypos2 = 114; + setFacing = 0; + unk1 = 1; + break; + + default: + break; + } + } + + _brandonPosX = _brandonPosY = -1; + + if (unk1 && setFacing) { + ypos2 = ypos; + xpos2 = xpos; + switch (facing) { + case 0: + ypos = 142; + break; + + case 2: + xpos = -16; + break; + + case 4: + ypos = (uint8)(_northExitHeight & 0xFF) - 4; + break; + + case 6: + xpos = 336; + break; + + default: + break; + } + } + + xpos2 = (int16)(xpos2 & 0xFFFC); + ypos2 = (int16)(ypos2 & 0xFFFE); + xpos = (int16)(xpos & 0xFFFC); + ypos = (int16)(ypos & 0xFFFE); + _currentCharacter->facing = facing; + _currentCharacter->x1 = xpos; + _currentCharacter->x2 = xpos; + _currentCharacter->y1 = ypos; + _currentCharacter->y2 = ypos; + + initSceneObjectList(brandonAlive); + + if (unk1 && brandonAlive == 0) { + moveCharacterToPos(0, facing, xpos2, ypos2); + } + + _scriptClick->variables[4] = _itemInHand; + _scriptClick->variables[7] = brandonAlive; + _scriptInterpreter->startScript(_scriptClick, 3); + while (_scriptInterpreter->validScript(_scriptClick)) { + _scriptInterpreter->runScript(_scriptClick); + } +} + +void KyraEngine::initSceneObjectList(int brandonAlive) { + debug(9, "KyraEngine::initSceneObjectList(%d)", brandonAlive); + for (int i = 0; i < 28; ++i) { + _animator->actors()[i].active = 0; + } + + int startAnimFrame = 0; + + AnimObject *curAnimState = _animator->actors(); + curAnimState->active = 1; + curAnimState->drawY = _currentCharacter->y1; + curAnimState->sceneAnimPtr = _shapes[4+_currentCharacter->currentAnimFrame]; + curAnimState->animFrameNumber = _currentCharacter->currentAnimFrame; + startAnimFrame = _currentCharacter->currentAnimFrame-7; + int xOffset = _defaultShapeTable[startAnimFrame].xOffset; + int yOffset = _defaultShapeTable[startAnimFrame].yOffset; + if (_scaleMode) { + curAnimState->x1 = _currentCharacter->x1; + curAnimState->y1 = _currentCharacter->y1; + + _animator->_brandonScaleX = _scaleTable[_currentCharacter->y1]; + _animator->_brandonScaleY = _scaleTable[_currentCharacter->y1]; + + curAnimState->x1 += (_animator->_brandonScaleX * xOffset) >> 8; + curAnimState->y1 += (_animator->_brandonScaleY * yOffset) >> 8; + } else { + curAnimState->x1 = _currentCharacter->x1 + xOffset; + curAnimState->y1 = _currentCharacter->y1 + yOffset; + } + curAnimState->x2 = curAnimState->x1; + curAnimState->y2 = curAnimState->y1; + curAnimState->refreshFlag = 1; + curAnimState->bkgdChangeFlag = 1; + _animator->clearQueue(); + _animator->addObjectToQueue(curAnimState); + + int listAdded = 0; + int addedObjects = 1; + + for (int i = 1; i < 5; ++i) { + Character *ch = &_characterList[i]; + curAnimState = &_animator->actors()[addedObjects]; + if (ch->sceneId != _currentCharacter->sceneId) { + curAnimState->active = 0; + curAnimState->refreshFlag = 0; + curAnimState->bkgdChangeFlag = 0; + ++addedObjects; + continue; + } + + curAnimState->drawY = ch->y1; + curAnimState->sceneAnimPtr = _shapes[4+ch->currentAnimFrame]; + curAnimState->animFrameNumber = ch->currentAnimFrame; + startAnimFrame = ch->currentAnimFrame-7; + xOffset = _defaultShapeTable[startAnimFrame].xOffset; + yOffset = _defaultShapeTable[startAnimFrame].yOffset; + if (_scaleMode) { + curAnimState->x1 = ch->x1; + curAnimState->y1 = ch->y1; + + _animator->_brandonScaleX = _scaleTable[ch->y1]; + _animator->_brandonScaleY = _scaleTable[ch->y1]; + + curAnimState->x1 += (_animator->_brandonScaleX * xOffset) >> 8; + curAnimState->y1 += (_animator->_brandonScaleY * yOffset) >> 8; + } else { + curAnimState->x1 = ch->x1 + xOffset; + curAnimState->y1 = ch->y1 + yOffset; + } + curAnimState->x2 = curAnimState->x1; + curAnimState->y2 = curAnimState->y1; + curAnimState->active = 1; + curAnimState->refreshFlag = 1; + curAnimState->bkgdChangeFlag = 1; + + if (ch->facing >= 1 && ch->facing <= 3) { + curAnimState->flags |= 1; + } else if (ch->facing >= 5 && ch->facing <= 7) { + curAnimState->flags &= 0xFFFFFFFE; + } + + _animator->addObjectToQueue(curAnimState); + + ++addedObjects; + ++listAdded; + if (listAdded < 2) + i = 5; + } + + for (int i = 0; i < 11; ++i) { + curAnimState = &_animator->sprites()[i]; + + if (_sprites->_anims[i].play) { + curAnimState->active = 1; + curAnimState->refreshFlag = 1; + curAnimState->bkgdChangeFlag = 1; + } else { + curAnimState->active = 0; + curAnimState->refreshFlag = 0; + curAnimState->bkgdChangeFlag = 0; + } + curAnimState->height = _sprites->_anims[i].height; + curAnimState->height2 = _sprites->_anims[i].height2; + curAnimState->width = _sprites->_anims[i].width + 1; + curAnimState->width2 = _sprites->_anims[i].width2; + curAnimState->drawY = _sprites->_anims[i].drawY; + curAnimState->x1 = curAnimState->x2 = _sprites->_anims[i].x; + curAnimState->y1 = curAnimState->y2 = _sprites->_anims[i].y; + curAnimState->background = _sprites->_anims[i].background; + curAnimState->sceneAnimPtr = _sprites->_sceneShapes[_sprites->_anims[i].sprite]; + + curAnimState->disable = _sprites->_anims[i].disable; + + if (_sprites->_anims[i].unk2) + curAnimState->flags = 0x800; + else + curAnimState->flags = 0; + + if (_sprites->_anims[i].flipX) + curAnimState->flags |= 0x1; + + _animator->addObjectToQueue(curAnimState); + } + + for (int i = 0; i < 12; ++i) { + curAnimState = &_animator->items()[i]; + Room *curRoom = &_roomTable[_currentCharacter->sceneId]; + byte curItem = curRoom->itemsTable[i]; + if (curItem != 0xFF) { + curAnimState->drawY = curRoom->itemsYPos[i]; + curAnimState->sceneAnimPtr = _shapes[220+curItem]; + curAnimState->animFrameNumber = (int16)0xFFFF; + curAnimState->y1 = curRoom->itemsYPos[i]; + curAnimState->x1 = curRoom->itemsXPos[i]; + + curAnimState->x1 -= (_animator->fetchAnimWidth(curAnimState->sceneAnimPtr, _scaleTable[curAnimState->drawY])) >> 1; + curAnimState->y1 -= _animator->fetchAnimHeight(curAnimState->sceneAnimPtr, _scaleTable[curAnimState->drawY]); + + curAnimState->x2 = curAnimState->x1; + curAnimState->y2 = curAnimState->y1; + + curAnimState->active = 1; + curAnimState->refreshFlag = 1; + curAnimState->bkgdChangeFlag = 1; + + _animator->addObjectToQueue(curAnimState); + } else { + curAnimState->active = 0; + curAnimState->refreshFlag = 0; + curAnimState->bkgdChangeFlag = 0; + } + } + + _animator->preserveAnyChangedBackgrounds(); + curAnimState = _animator->actors(); + curAnimState->bkgdChangeFlag = 1; + curAnimState->refreshFlag = 1; + for (int i = 1; i < 28; ++i) { + curAnimState = &_animator->objects()[i]; + if (curAnimState->active) { + curAnimState->bkgdChangeFlag = 1; + curAnimState->refreshFlag = 1; + } + } + _animator->restoreAllObjectBackgrounds(); + _animator->preserveAnyChangedBackgrounds(); + _animator->prepDrawAllObjects(); + initSceneScreen(brandonAlive); + _animator->copyChangedObjectsForward(0); +} + +void KyraEngine::initSceneScreen(int brandonAlive) { + // XXX (Pointless?) Palette stuff + if (_unkScreenVar2 == 1) { + _screen->shuffleScreen(8, 8, 304, 128, 2, 0, _unkScreenVar3, false); + } else { + _screen->copyRegion(8, 8, 8, 8, 304, 128, 2, 0); + } + _screen->updateScreen(); + // XXX More (pointless?) palette stuff + + if (!_scriptInterpreter->startScript(_scriptClick, 2)) + error("Could not start script function 2 of scene script"); + + _scriptClick->variables[7] = brandonAlive; + + while (_scriptInterpreter->validScript(_scriptClick)) + _scriptInterpreter->runScript(_scriptClick); + + setTextFadeTimerCountdown(-1); + if (_currentCharacter->sceneId == 210) { + if (_itemInHand != -1) + magicOutMouseItem(2, -1); + + _screen->hideMouse(); + for (int i = 0; i < 10; ++i) { + if (_currentCharacter->inventoryItems[i] != 0xFF) + magicOutMouseItem(2, i); + } + _screen->showMouse(); + } +} + +int KyraEngine::handleSceneChange(int xpos, int ypos, int unk1, int frameReset) { + debug(9, "KyraEngine::handleSceneChange(%d, %d, %d, %d)", xpos, ypos, unk1, frameReset); + if (queryGameFlag(0xEF)) { + unk1 = 0; + } + int sceneId = _currentCharacter->sceneId; + _pathfinderFlag = 0; + if (xpos < 12) { + if (_roomTable[sceneId].westExit != 0xFFFF) { + xpos = 12; + ypos = _sceneExits.westYPos; + _pathfinderFlag = 7; + } + } else if(xpos >= 308) { + if (_roomTable[sceneId].eastExit != 0xFFFF) { + xpos = 307; + ypos = _sceneExits.eastYPos; + _pathfinderFlag = 13; + } + } + + if (ypos <= (_northExitHeight&0xFF)+2) { + if (_roomTable[sceneId].northExit != 0xFFFF) { + xpos = _sceneExits.northXPos; + ypos = _northExitHeight & 0xFF; + _pathfinderFlag = 14; + } + } else if (ypos >= 136) { + if (_roomTable[sceneId].southExit != 0xFFFF) { + xpos = _sceneExits.southXPos; + ypos = 136; + _pathfinderFlag = 11; + } + } + + int temp = xpos - _currentCharacter->x1; + if (ABS(temp) < 4) { + temp = ypos - _currentCharacter->y1; + if (ABS(temp) < 2) { + return 0; + } + } + + int x = (int16)(_currentCharacter->x1 & 0xFFFC); + int y = (int16)(_currentCharacter->y1 & 0xFFFE); + xpos = (int16)(xpos & 0xFFFC); + ypos = (int16)(ypos & 0xFFFE); + int ret = findWay(x, y, xpos, ypos, _movFacingTable, 150); + _pathfinderFlag = 0; + if (ret >= _lastFindWayRet) { + _lastFindWayRet = ret; + } + if (ret == 0x7D00 || ret == 0) { + return 0; + } + return processSceneChange(_movFacingTable, unk1, frameReset); +} + +int KyraEngine::processSceneChange(int *table, int unk1, int frameReset) { + debug(9, "KyraEngine::processSceneChange(0x%X, %d, %d)", table, unk1, frameReset); + if (queryGameFlag(0xEF)) { + unk1 = 0; + } + int *tableStart = table; + _sceneChangeState = 0; + _loopFlag2 = 0; + bool running = true; + int returnValue = 0; + uint32 nextFrame = 0; + _abortWalkFlag = false; + _mousePressFlag = false; + + while (running) { + if (_abortWalkFlag) { + *table = 8; + _currentCharacter->currentAnimFrame = 7; + _animator->animRefreshNPC(0); + _animator->updateAllObjectShapes(); + processInput(_mouseX, _mouseY); + return 0; + } + bool forceContinue = false; + switch (*table) { + case 0: case 1: case 2: + case 3: case 4: case 5: + case 6: case 7: + _currentCharacter->facing = getOppositeFacingDirection(*table); + break; + + case 8: + forceContinue = true; + running = false; + break; + + default: + ++table; + forceContinue = true; + break; + } + + returnValue = changeScene(_currentCharacter->facing); + if (returnValue) { + running = false; + _abortWalkFlag = false; + } + + if (unk1) { + if (_mousePressFlag) { + running = false; + _sceneChangeState = 1; + } + } + + if (forceContinue || !running) { + continue; + } + + int temp = 0; + if (table == tableStart || table[1] == 8) { + temp = setCharacterPosition(0, 0); + } else { + temp = setCharacterPosition(0, table); + } + if (temp) { + ++table; + } + + nextFrame = getTimerDelay(5) * _tickLength + _system->getMillis(); + while (_system->getMillis() < nextFrame) { + _sprites->updateSceneAnims(); + updateMousePointer(); + updateGameTimers(); + _animator->updateAllObjectShapes(); + updateTextFade(); + if (_currentCharacter->sceneId == 210) { + updateKyragemFading(); + if (seq_playEnd() || _beadStateVar == 4 || _beadStateVar == 5) { + *table = 8; + running = false; + break; + } + } + if ((nextFrame - _system->getMillis()) >= 10) + delay(10); + } + } + + if (frameReset && !(_brandonStatusBit & 2)) { + _currentCharacter->currentAnimFrame = 7; + } + _animator->animRefreshNPC(0); + _animator->updateAllObjectShapes(); + return returnValue; +} + +int KyraEngine::changeScene(int facing) { + debug(9, "KyraEngine::changeScene(%d)", facing); + if (queryGameFlag(0xEF)) { + if (_currentCharacter->sceneId == 5) { + return 0; + } + } + + int xpos = _charXPosTable[facing] + _currentCharacter->x1; + int ypos = _charYPosTable[facing] + _currentCharacter->y1; + + if (xpos >= 12 && xpos <= 308) { + if (!lineIsPassable(xpos, ypos)) + return false; + } + + if (_exitListPtr) { + int16 *ptr = _exitListPtr; + // this loop should be only entered on time, seems to be some hack in the original + while (true) { + if (*ptr == -1) + break; + + if (*ptr > _currentCharacter->x1 || _currentCharacter->y1 < ptr[1] || _currentCharacter->x1 > ptr[2] || _currentCharacter->y1 > ptr[3]) { + ptr += 10; + break; + } + _brandonPosX = ptr[6]; + _brandonPosY = ptr[7]; + uint16 sceneId = ptr[5]; + facing = ptr[4]; + int unk1 = ptr[8]; + int unk2 = ptr[9]; + if (sceneId == 0xFFFF) { + switch (facing) { + case 0: + sceneId = _roomTable[_currentCharacter->sceneId].northExit; + break; + + case 2: + sceneId = _roomTable[_currentCharacter->sceneId].eastExit; + break; + + case 4: + sceneId = _roomTable[_currentCharacter->sceneId].southExit; + break; + + case 6: + sceneId = _roomTable[_currentCharacter->sceneId].westExit; + break; + + default: + break; + } + } + + _currentCharacter->facing = facing; + _animator->animRefreshNPC(0); + _animator->updateAllObjectShapes(); + enterNewScene(sceneId, facing, unk1, unk2, 0); + resetGameFlag(0xEE); + return 1; + } + } + + int returnValue = 0; + facing = 0; + + if ((_northExitHeight & 0xFF) + 2 >= ypos || (_northExitHeight & 0xFF) + 2 >= _currentCharacter->y1) { + facing = 0; + returnValue = 1; + } + + if (xpos >= 308 || (_currentCharacter->x1 + 4) >= 308) { + facing = 2; + returnValue = 1; + } + + if (((_northExitHeight >> 8) & 0xFF) - 2 < ypos || ((_northExitHeight >> 8) & 0xFF) - 2 < _currentCharacter->y1) { + facing = 4; + returnValue = 1; + } + + if (xpos <= 12 || _currentCharacter->y1 <= 12) { + facing = 6; + returnValue = 1; + } + + if (!returnValue) + return 0; + + uint16 sceneId = 0xFFFF; + switch (facing) { + case 0: + sceneId = _roomTable[_currentCharacter->sceneId].northExit; + break; + + case 2: + sceneId = _roomTable[_currentCharacter->sceneId].eastExit; + break; + + case 4: + sceneId = _roomTable[_currentCharacter->sceneId].southExit; + break; + + default: + sceneId = _roomTable[_currentCharacter->sceneId].westExit; + break; + } + + if (sceneId == 0xFFFF) + return 0; + + enterNewScene(sceneId, facing, 1, 1, 0); + return returnValue; +} + +void KyraEngine::setCharactersInDefaultScene() { + static const uint32 defaultSceneTable[][4] = { + { 0xFFFF, 0x0004, 0x0003, 0xFFFF }, + { 0xFFFF, 0x0022, 0xFFFF, 0x0000 }, + { 0xFFFF, 0x001D, 0x0021, 0xFFFF }, + { 0xFFFF, 0x0000, 0x0000, 0xFFFF } + }; + + for (int i = 1; i < 5; ++i) { + Character *cur = &_characterList[i]; + //cur->field_20 = 0; + const uint32 *curTable = defaultSceneTable[i-1]; + cur->sceneId = curTable[0]; + if (cur->sceneId == _currentCharacter->sceneId) { + //++cur->field_20; + cur->sceneId = curTable[1/*cur->field_20*/]; + } + //cur->field_23 = curTable[cur->field_20+1]; + } +} + +void KyraEngine::setCharactersPositions(int character) { + static uint16 initXPosTable[] = { + 0x3200, 0x0024, 0x2230, 0x2F00, 0x0020, 0x002B, + 0x00CA, 0x00F0, 0x0082, 0x00A2, 0x0042 + }; + static uint8 initYPosTable[] = { + 0x00, 0xA2, 0x00, 0x42, 0x00, + 0x67, 0x67, 0x60, 0x5A, 0x71, + 0x76 + }; + assert(character < ARRAYSIZE(initXPosTable)); + Character *edit = &_characterList[character]; + edit->x1 = edit->x2 = initXPosTable[character]; + edit->y1 = edit->y2 = initYPosTable[character]; +} + +#pragma mark - +#pragma mark - Pathfinder +#pragma mark - + +int KyraEngine::findWay(int x, int y, int toX, int toY, int *moveTable, int moveTableSize) { + debug(9, "KyraEngine::findWay(%d, %d, %d, %d, 0x%X, %d)", x, y, toX, toY, moveTable, moveTableSize); + x &= 0xFFFC; toX &= 0xFFFC; + y &= 0xFFFE; toY &= 0xFFFE; + x = (int16)x; y = (int16)y; toX = (int16)toX; toY = (int16)toY; + + if (x == toY && y == toY) { + moveTable[0] = 8; + return 0; + } + + int curX = x; + int curY = y; + int lastUsedEntry = 0; + int tempValue = 0; + int *pathTable1 = new int[0x7D0]; + int *pathTable2 = new int[0x7D0]; + assert(pathTable1 && pathTable2); + + while (true) { + int newFacing = getFacingFromPointToPoint(x, y, toX, toY); + changePosTowardsFacing(curX, curY, newFacing); + + if (curX == toX && curY == toY) { + if (!lineIsPassable(curX, curY)) + break; + moveTable[lastUsedEntry++] = newFacing; + break; + } + + if (lineIsPassable(curX, curY)) { + if (lastUsedEntry == moveTableSize) { + delete [] pathTable1; + delete [] pathTable2; + return 0x7D00; + } + // debug drawing + //if (curX >= 0 && curY >= 0 && curX < 320 && curY < 200) { + // _screen->setPagePixel(0, curX, curY, 11); + // _screen->updateScreen(); + // waitTicks(5); + //} + moveTable[lastUsedEntry++] = newFacing; + x = curX; + y = curY; + continue; + } + + int temp = 0; + while (true) { + newFacing = getFacingFromPointToPoint(curX, curY, toX, toY); + changePosTowardsFacing(curX, curY, newFacing); + // debug drawing + //if (curX >= 0 && curY >= 0 && curX < 320 && curY < 200) { + // _screen->setPagePixel(0, curX, curY, 8); + // _screen->updateScreen(); + // waitTicks(5); + //} + + if (!lineIsPassable(curX, curY)) { + if (curX != toX || curY != toY) + continue; + } + + if (curX == toX && curY == toY) { + if (!lineIsPassable(curX, curY)) { + tempValue = 0; + temp = 0; + break; + } + } + + temp = findSubPath(x, y, curX, curY, pathTable1, 1, 0x7D0); + tempValue = findSubPath(x, y, curX, curY, pathTable2, 0, 0x7D0); + if (curX == toX && curY == toY) { + if (temp == 0x7D00 && tempValue == 0x7D00) { + delete [] pathTable1; + delete [] pathTable2; + return 0x7D00; + } + } + + if (temp != 0x7D00 || tempValue != 0x7D00) { + break; + } + } + + if (temp < tempValue) { + if (lastUsedEntry + temp > moveTableSize) { + delete [] pathTable1; + delete [] pathTable2; + return 0x7D00; + } + memcpy(&moveTable[lastUsedEntry], pathTable1, temp*sizeof(int)); + lastUsedEntry += temp; + } else { + if (lastUsedEntry + tempValue > moveTableSize) { + delete [] pathTable1; + delete [] pathTable2; + return 0x7D00; + } + memcpy(&moveTable[lastUsedEntry], pathTable2, tempValue*sizeof(int)); + lastUsedEntry += tempValue; + } + x = curX; + y = curY; + if (curX == toX && curY == toY) { + break; + } + } + delete [] pathTable1; + delete [] pathTable2; + moveTable[lastUsedEntry] = 8; + return getMoveTableSize(moveTable); +} + +int KyraEngine::findSubPath(int x, int y, int toX, int toY, int *moveTable, int start, int end) { + debug(9, "KyraEngine::findSubPath(%d, %d, %d, %d, 0x%X, %d, %d)", x, y, toX, toY, moveTable, start, end); + // only used for debug specific code + //static uint16 unkTable[] = { 8, 5 }; + static const int8 facingTable1[] = { 7, 0, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 0 }; + static const int8 facingTable2[] = { -1, 0, -1, 2, -1, 4, -1, 6, -1, 2, -1, 4, -1, 6, -1, 0 }; + static const int8 facingTable3[] = { 2, 4, 4, 6, 6, 0, 0, 2, 6, 6, 0, 0, 2, 2, 4, 4 }; + static const int8 addPosTableX[] = { -1, 0, -1, 4, -1, 0, -1, -4, -1, -4, -1, 0, -1, 4, -1, 0 }; + static const int8 addPosTableY[] = { -1, 2, -1, 0, -1, -2, -1, 0, -1, 0, -1, 2, -1, 0, -1, -2 }; + + // debug specific + //++unkTable[start]; + //while (_screen->getPalette(0)[unkTable[start]] != 0x0F) { + // ++unkTable[start]; + //} + + int xpos1 = x, xpos2 = x; + int ypos1 = y, ypos2 = y; + int newFacing = getFacingFromPointToPoint(x, y, toX, toY); + int position = 0; + + while (position != end) { + int newFacing2 = newFacing; + while (true) { + changePosTowardsFacing(xpos1, ypos1, facingTable1[start*8 + newFacing2]); + if (!lineIsPassable(xpos1, ypos1)) { + if (facingTable1[start*8 + newFacing2] == newFacing) { + return 0x7D00; + } + newFacing2 = facingTable1[start*8 + newFacing2]; + xpos1 = x; + ypos1 = y; + continue; + } + newFacing = facingTable1[start*8 + newFacing2]; + break; + } + // debug drawing + //if (xpos1 >= 0 && ypos1 >= 0 && xpos1 < 320 && ypos1 < 200) { + // _screen->setPagePixel(0, xpos1, ypos1, unkTable[start]); + // _screen->updateScreen(); + // waitTicks(5); + //} + if (newFacing & 1) { + int temp = xpos1 + addPosTableX[newFacing + start * 8]; + if (toX == temp) { + temp = ypos1 + addPosTableY[newFacing + start * 8]; + if (toY == temp) { + moveTable[position++] = facingTable2[newFacing + start * 8]; + return position; + } + } + } + moveTable[position++] = newFacing; + x = xpos1; + y = ypos1; + if (x == toX && y == toY) { + return position; + } + + if (xpos1 == xpos2 && ypos1 == ypos2) { + break; + } + + newFacing = facingTable3[start*8 + newFacing]; + } + return 0x7D00; +} + +int KyraEngine::getFacingFromPointToPoint(int x, int y, int toX, int toY) { + debug(9, "KyraEngine::getFacingFromPointToPoint(%d, %d, %d, %d)", x, y, toX, toY); + static const int facingTable[] = { + 1, 0, 1, 2, 3, 4, 3, 2, 7, 0, 7, 6, 5, 4, 5, 6 + }; + + int facingEntry = 0; + int ydiff = y - toY; + if (ydiff < 0) { + ++facingEntry; + ydiff = -ydiff; + } + facingEntry <<= 1; + + int xdiff = toX - x; + if (xdiff < 0) { + ++facingEntry; + xdiff = -xdiff; + } + + if (xdiff >= ydiff) { + int temp = ydiff; + ydiff = xdiff; + xdiff = temp; + + facingEntry <<= 1; + } else { + facingEntry <<= 1; + facingEntry += 1; + } + int temp = (ydiff + 1) >> 1; + + if (xdiff < temp) { + facingEntry <<= 1; + facingEntry += 1; + } else { + facingEntry <<= 1; + } + assert(facingEntry < ARRAYSIZE(facingTable)); + return facingTable[facingEntry]; +} + +void KyraEngine::changePosTowardsFacing(int &x, int &y, int facing) { + debug(9, "KyraEngine::changePosTowardsFacing(%d, %d, %d)", x, y, facing); + x += _addXPosTable[facing]; + y += _addYPosTable[facing]; +} + +bool KyraEngine::lineIsPassable(int x, int y) { + debug(9, "KyraEngine::lineIsPassable(%d, %d)", x, y); + if (queryGameFlag(0xEF)) { + if (_currentCharacter->sceneId == 5) + return true; + } + + if (_pathfinderFlag & 2) { + if (x >= 312) + return false; + } + + if (_pathfinderFlag & 4) { + if (y >= 136) + return false; + } + + if (_pathfinderFlag & 8) { + if (x < 8) + return false; + } + + if (_pathfinderFlag2) { + if (x <= 8 || x >= 312) + return true; + if (y < (_northExitHeight & 0xFF) || y > 135) + return true; + } + + if (y > 137) { + return false; + } + + int ypos = 8; + if (_scaleMode) { + ypos = (_scaleTable[y] >> 5) + 1; + if (8 < ypos) + ypos = 8; + } + + x -= (ypos >> 1); + if (y < 0) + y = 0; + + int xpos = x; + int xtemp = xpos + ypos - 1; + if (x < 0) + xpos = 0; + + if (xtemp > 319) + xtemp = 319; + + for (; xpos < xtemp; ++xpos) { + if (!_screen->getShapeFlag1(xpos, y)) + return false; + } + return true; +} + +int KyraEngine::getMoveTableSize(int *moveTable) { + debug(9, "KyraEngine::getMoveTableSize(0x%X)", moveTable); + int retValue = 0; + if (moveTable[0] == 8) + return 0; + + static const int facingTable[] = { + 4, 5, 6, 7, 0, 1, 2, 3 + }; + static const int unkTable[] = { + -1, -1, 1, 2, -1, 6, 7, -1, + -1, -1, -1, -1, 2, -1, 0, -1, + 1, -1, -1, -1, 3, 4, -1, 0, + 2, -1, -1, -1, -1, -1, 4, -1, + -1, 2, 3, -1, -1, -1, 5, 6, + 6, -1, 4, -1, -1, -1, -1, -1, + 7, 0, -1, 4, 5, -1, -1, -1, + -1, -1, 0, -1, 6, -1, -1, -1 + }; + + int *oldPosition = moveTable; + int *tempPosition = moveTable; + int *curPosition = moveTable + 1; + retValue = 1; + + while (*curPosition != 8) { + if (*oldPosition == facingTable[*curPosition]) { + retValue -= 2; + *oldPosition = 9; + *curPosition = 9; + + while (tempPosition != moveTable) { + --tempPosition; + if (*tempPosition != 9) + break; + } + + if (tempPosition == moveTable && *tempPosition == 9) { + while (*tempPosition != 8 && *tempPosition == 9) { + ++tempPosition; + } + if (*tempPosition == 8) { + return 0; + } + } + + oldPosition = tempPosition; + curPosition = oldPosition+1; + while (*curPosition != 8 && *curPosition == 9) { + ++curPosition; + } + continue; + } + + if (unkTable[*curPosition+((*oldPosition)*8)] != -1) { + --retValue; + *oldPosition = unkTable[*curPosition+((*oldPosition)*8)]; + *curPosition = 9; + + if (tempPosition != oldPosition) { + curPosition = oldPosition; + oldPosition = tempPosition; + while (true) { + if (tempPosition == moveTable) { + break; + } + --tempPosition; + if (*tempPosition != 9) { + break; + } + } + } else { + while (true) { + ++curPosition; + if (*curPosition != 9) { + break; + } + } + } + continue; + } + + tempPosition = oldPosition; + oldPosition = curPosition; + ++retValue; + while (true) { + ++curPosition; + if (*curPosition != 9) { + break; + } + } + } + + return retValue; +} + +} // end of namespace Kyra diff --git a/engines/kyra/screen.cpp b/engines/kyra/screen.cpp new file mode 100644 index 0000000000..4bf2d8da75 --- /dev/null +++ b/engines/kyra/screen.cpp @@ -0,0 +1,2084 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-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 "kyra/screen.h" +#include "kyra/kyra.h" + +namespace Kyra { + +#define BITBLIT_RECTS 10 + +Screen::Screen(KyraEngine *vm, OSystem *system) + : _system(system), _vm(vm) { + _curPage = 0; + for (int pageNum = 0; pageNum < SCREEN_PAGE_NUM; pageNum += 2) { + uint8 *pagePtr = (uint8 *)malloc(SCREEN_PAGE_SIZE); + if (pagePtr) { + memset(pagePtr, 0, SCREEN_PAGE_SIZE); + _pagePtrs[pageNum] = _pagePtrs[pageNum + 1] = pagePtr; + } + } + memset(_shapePages, 0, sizeof(_shapePages)); + _currentPalette = (uint8 *)malloc(768); + if (_currentPalette) { + memset(_currentPalette, 0, 768); + } + _screenPalette = (uint8 *)malloc(768); + if (_screenPalette) { + memset(_screenPalette, 0, 768); + } + for (int i = 0; i < 3; ++i) { + _palettes[i] = (uint8 *)malloc(768); + if (_palettes[i]) { + memset(_palettes[i], 0, 768); + } + } + _curDim = &_screenDimTable[0]; + _charWidth = 0; + _charOffset = 0; + memset(_fonts, 0, sizeof(_fonts)); + for (int i = 0; i < ARRAYSIZE(_textColorsMap); ++i) { + _textColorsMap[i] = i; + } + _decodeShapeBuffer = NULL; + _decodeShapeBufferSize = 0; + _animBlockPtr = NULL; + _animBlockSize = 0; + _mouseLockCount = 0; + + _bitBlitRects = new Rect[BITBLIT_RECTS]; + assert(_bitBlitRects); + memset(_bitBlitRects, 0, sizeof(Rect)*BITBLIT_RECTS); + _bitBlitNum = 0; + memset(_saveLoadPage, 0, sizeof(_saveLoadPage)); + + _unkPtr1 = (uint8*)malloc(getRectSize(1, 144)); + memset(_unkPtr1, 0, getRectSize(1, 144)); + _unkPtr2 = (uint8*)malloc(getRectSize(1, 144)); + memset(_unkPtr2, 0, getRectSize(1, 144)); +} + +Screen::~Screen() { + for (int pageNum = 0; pageNum < SCREEN_PAGE_NUM; pageNum += 2) { + free(_pagePtrs[pageNum]); + _pagePtrs[pageNum] = _pagePtrs[pageNum + 1] = 0; + } + for (int f = 0; f < ARRAYSIZE(_fonts); ++f) { + delete[] _fonts[f].fontData; + _fonts[f].fontData = NULL; + } + free(_currentPalette); + free(_screenPalette); + free(_decodeShapeBuffer); + free(_animBlockPtr); + for (int i = 0; i < 3; ++i) { + free(_palettes[i]); + } + delete [] _bitBlitRects; + for (int i = 0; i < ARRAYSIZE(_saveLoadPage); ++i) { + delete [] _saveLoadPage[i]; + _saveLoadPage[i] = 0; + } + + free(_unkPtr1); + free(_unkPtr2); +} + +void Screen::updateScreen() { + debug(9, "Screen::updateScreen()"); + _system->copyRectToScreen(getPagePtr(0), SCREEN_W, 0, 0, SCREEN_W, SCREEN_H); + //for debug reasons (needs 640x200 screen) + //_system->copyRectToScreen(getPagePtr(2), SCREEN_W, 320, 0, SCREEN_W, SCREEN_H); + _system->updateScreen(); +} + +uint8 *Screen::getPagePtr(int pageNum) { + debug(9, "Screen::getPagePtr(%d)", pageNum); + assert(pageNum < SCREEN_PAGE_NUM); + return _pagePtrs[pageNum]; +} + +void Screen::clearPage(int pageNum) { + debug(9, "Screen::clearPage(%d)", pageNum); + assert(pageNum < SCREEN_PAGE_NUM); + memset(getPagePtr(pageNum), 0, SCREEN_PAGE_SIZE); +} + +int Screen::setCurPage(int pageNum) { + debug(9, "Screen::setCurPage(%d)", pageNum); + assert(pageNum < SCREEN_PAGE_NUM); + int previousPage = _curPage; + _curPage = pageNum; + return previousPage; +} + +void Screen::clearCurPage() { + debug(9, "Screen::clearCurPage()"); + memset(getPagePtr(_curPage), 0, SCREEN_PAGE_SIZE); +} + +uint8 Screen::getPagePixel(int pageNum, int x, int y) { + debug(9, "Screen::getPagePixel(%d, %d, %d)", pageNum, x, y); + assert(pageNum < SCREEN_PAGE_NUM); + assert(x >= 0 && x < SCREEN_W && y >= 0 && y < SCREEN_H); + return _pagePtrs[pageNum][y * SCREEN_W + x]; +} + +void Screen::setPagePixel(int pageNum, int x, int y, uint8 color) { + debug(9, "Screen::setPagePixel(%d, %d, %d, %d)", pageNum, x, y, color); + assert(pageNum < SCREEN_PAGE_NUM); + assert(x >= 0 && x < SCREEN_W && y >= 0 && y < SCREEN_H); + _pagePtrs[pageNum][y * SCREEN_W + x] = color; +} + +void Screen::fadeFromBlack() { + debug(9, "Screen::fadeFromBlack()"); + fadePalette(_currentPalette, 0x54); +} + +void Screen::fadeToBlack() { + debug(9, "Screen::fadeToBlack()"); + uint8 blackPal[768]; + memset(blackPal, 0, 768); + fadePalette(blackPal, 0x54); +} + +void Screen::fadeSpecialPalette(int palIndex, int startIndex, int size, int fadeTime) { + debug(9, "fadeSpecialPalette(%d, %d, %d, %d)", palIndex, startIndex, size, fadeTime); + assert(_vm->palTable1()[palIndex]); + assert(_currentPalette); + uint8 tempPal[768]; + memcpy(tempPal, _currentPalette, 768); + memcpy(&tempPal[startIndex*3], _vm->palTable1()[palIndex], size*3); + fadePalette(tempPal, fadeTime*18); + memcpy(&_currentPalette[startIndex*3], &tempPal[startIndex*3], size*3); + setScreenPalette(_currentPalette); + _system->updateScreen(); +} + +void Screen::fadePalette(const uint8 *palData, int delay) { + debug(9, "Screen::fadePalette(0x%X, %d)", palData, delay); + uint8 fadePal[768]; + memcpy(fadePal, _screenPalette, 768); + uint8 diff, maxDiff = 0; + for (int i = 0; i < 768; ++i) { + diff = ABS(palData[i] - fadePal[i]); + if (diff > maxDiff) { + maxDiff = diff; + } + } + int16 delayInc = delay << 8; + if (maxDiff != 0) { + delayInc /= maxDiff; + } + delay = delayInc; + for (diff = 1; diff <= maxDiff; ++diff) { + if (delayInc >= 512) { + break; + } + delayInc += delay; + } + int delayAcc = 0; + while (1) { + delayAcc += delayInc; + bool needRefresh = false; + for (int i = 0; i < 768; ++i) { + int c1 = palData[i]; + int c2 = fadePal[i]; + if (c1 != c2) { + needRefresh = true; + if (c1 > c2) { + c2 += diff; + if (c1 < c2) { + c2 = c1; + } + } + if (c1 < c2) { + c2 -= diff; + if (c1 > c2) { + c2 = c1; + } + } + fadePal[i] = (uint8)c2; + } + } + if (!needRefresh) { + break; + } + setScreenPalette(fadePal); + _system->updateScreen(); + //_system->delayMillis((delayAcc >> 8) * 1000 / 60); + _vm->delay((delayAcc >> 8) * 1000 / 60); + delayAcc &= 0xFF; + } +} + +void Screen::setScreenPalette(const uint8 *palData) { + debug(9, "Screen::setScreenPalette(0x%X)", palData); + memcpy(_screenPalette, palData, 768); + uint8 screenPal[256 * 4]; + for (int i = 0; i < 256; ++i) { + screenPal[4 * i + 0] = (palData[0] << 2) | (palData[0] & 3); + screenPal[4 * i + 1] = (palData[1] << 2) | (palData[1] & 3); + screenPal[4 * i + 2] = (palData[2] << 2) | (palData[2] & 3); + screenPal[4 * i + 3] = 0; + palData += 3; + } + _system->setPalette(screenPal, 0, 256); +} + +void Screen::copyToPage0(int y, int h, uint8 page, uint8 *seqBuf) { + debug(9, "Screen::copyToPage0(%d, %d, %d, 0x%X)", y, h, page, seqBuf); + assert(y + h <= SCREEN_H); + const uint8 *src = getPagePtr(page) + y * SCREEN_W; + uint8 *dstPage = getPagePtr(0) + y * SCREEN_W; + for (int i = 0; i < h; ++i) { + for (int x = 0; x < SCREEN_W; ++x) { + if (seqBuf[x] != src[x]) { + seqBuf[x] = src[x]; + dstPage[x] = src[x]; + } + } + src += SCREEN_W; + seqBuf += SCREEN_W; + dstPage += SCREEN_W; + } +} + +void Screen::copyRegion(int x1, int y1, int x2, int y2, int w, int h, int srcPage, int dstPage, int flags) { + debug(9, "Screen::copyRegion(%d, %d, %d, %d, %d, %d, %d, %d, %d)", x1, y1, x2, y2, w, h, srcPage, dstPage, flags); + + if (flags & CR_CLIPPED) { + if (x2 < 0) { + if (x2 <= -w) + return; + w += x2; + x1 -= x2; + x2 = 0; + } else if (x2 + w >= SCREEN_W) { + if (x2 > SCREEN_W) + return; + w = SCREEN_W - x2; + } + + if (y2 < 0) { + if (y2 <= -h ) + return; + h += y2; + y1 -= y2; + y2 = 0; + } else if (y2 + h >= SCREEN_H) { + if (y2 > SCREEN_H) + return; + h = SCREEN_H - y2; + } + } + + assert(x1 + w <= SCREEN_W && y1 + h <= SCREEN_H); + const uint8 *src = getPagePtr(srcPage) + y1 * SCREEN_W + x1; + assert(x2 + w <= SCREEN_W && y2 + h <= SCREEN_H); + uint8 *dst = getPagePtr(dstPage) + y2 * SCREEN_W + x2; + + if (flags & CR_X_FLIPPED) { + while (h--) { + for (int i = 0; i < w; ++i) { + if (src[i]) { + dst[w-i] = src[i]; + } + } + src += SCREEN_W; + dst += SCREEN_W; + } + } else { + while (h--) { + for (int i = 0; i < w; ++i) { + if (src[i]) { + dst[i] = src[i]; + } + } + src += SCREEN_W; + dst += SCREEN_W; + } + } +} + +void Screen::copyRegionToBuffer(int pageNum, int x, int y, int w, int h, uint8 *dest) { + debug(9, "Screen::copyRegionToBuffer(%d, %d, %d, %d, %d)", pageNum, x, y, w, h); + assert(x >= 0 && x < Screen::SCREEN_W && y >= 0 && y < Screen::SCREEN_H && dest); + uint8 *pagePtr = getPagePtr(pageNum); + for (int i = y; i < y + h; i++) { + memcpy(dest + (i - y) * w, pagePtr + i * SCREEN_W + x, w); + } +} + +void Screen::copyBlockToPage(int pageNum, int x, int y, int w, int h, const uint8 *src) { + debug(9, "Screen::copyBlockToPage(%d, %d, %d, %d, %d, 0x%X)", pageNum, x, y, w, h, src); + assert(x >= 0 && x < Screen::SCREEN_W && y >= 0 && y < Screen::SCREEN_H); + uint8 *dst = getPagePtr(pageNum) + y * SCREEN_W + x; + while (h--) { + memcpy(dst, src, w); + dst += SCREEN_W; + src += w; + } +} + +void Screen::copyFromCurPageBlock(int x, int y, int w, int h, const uint8 *src) { + debug(9, "Screen::copyFromCurPageBlock(%d, %d, %d, %d, 0x%X)", x, y, w, h, src); + if (x < 0) { + x = 0; + } else if (x >= 40) { + return; + } + if (x + w > 40) { + w = 40 - x; + } + if (y < 0) { + y = 0; + } else if (y >= 200) { + return; + } + if (y + h > 200) { + h = 200 - y; + } + uint8 *dst = getPagePtr(_curPage) + y * SCREEN_W + x * 8; + while (h--) { + memcpy(dst, src, w*8); + dst += SCREEN_W; + src += w*8; + } +} + +void Screen::copyCurPageBlock(int x, int y, int w, int h, uint8 *dst) { + debug(9, "Screen::copyCurPageBlock(%d, %d, %d, %d, 0x%X)", x, y, w, h, dst); + assert(dst); + if (x < 0) { + x = 0; + } else if (x >= 40) { + return; + } + if (x + w > 40) { + w = 40 - x; + } + if (y < 0) { + y = 0; + } else if (y >= 200) { + return; + } + if (y + h > 200) { + h = 200 - y; + } + const uint8 *src = getPagePtr(_curPage) + y * SCREEN_W + x * 8; + while (h--) { + memcpy(dst, src, w*8); + dst += w*8; + src += SCREEN_W; + } +} + +void Screen::shuffleScreen(int sx, int sy, int w, int h, int srcPage, int dstPage, int ticks, bool transparent) { + debug(9, "Screen::shuffleScreen(%d, %d, %d, %d, %d, %d, %d, %d)", sx, sy, w, h, srcPage, dstPage, ticks, transparent); + assert(sx >= 0 && w <= SCREEN_W); + int x; + uint16 x_offs[SCREEN_W]; + for (x = 0; x < SCREEN_W; ++x) { + x_offs[x] = x; + } + for (x = 0; x < w; ++x) { + int i = _vm->_rnd.getRandomNumber(w - 1); + SWAP(x_offs[x], x_offs[i]); + } + + assert(sy >= 0 && h <= SCREEN_H); + int y; + uint8 y_offs[SCREEN_H]; + for (y = 0; y < SCREEN_H; ++y) { + y_offs[y] = y; + } + for (y = 0; y < h; ++y) { + int i = _vm->_rnd.getRandomNumber(h - 1); + SWAP(y_offs[y], y_offs[i]); + } + + int32 start, now; + int wait; + for (y = 0; y < h; ++y) { + start = (int32)_system->getMillis(); + int y_cur = y; + for (x = 0; x < w; ++x) { + int i = sx + x_offs[x]; + int j = sy + y_offs[y_cur]; + ++y_cur; + if (y_cur >= h) { + y_cur = 0; + } + uint8 color = getPagePixel(srcPage, i, j); + if (!transparent || color != 0) { + setPagePixel(dstPage, i, j, color); + } + } + updateScreen(); + now = (int32)_system->getMillis(); + wait = ticks * _vm->tickLength() - (now - start); + if (wait > 0) { + _vm->delay(wait); + } + } +} + +void Screen::fillRect(int x1, int y1, int x2, int y2, uint8 color, int pageNum) { + debug(9, "Screen::fillRect(%d, %d, %d, %d, %d, %d)", x1, y1, x2, y2, color, pageNum); + assert(x2 < SCREEN_W && y2 < SCREEN_H); + if (pageNum == -1) { + pageNum = _curPage; + } + uint8 *dst = getPagePtr(pageNum) + y1 * SCREEN_W + x1; + for (; y1 <= y2; ++y1) { + memset(dst, color, x2 - x1 + 1); + dst += SCREEN_W; + } +} + +void Screen::drawBox(int x1, int y1, int x2, int y2, int color) { + debug(9, "Screen::drawBox(%i, %i, %i, %i, %i)", x1, y1, x2, y2, color); + + drawClippedLine(x1, y1, x2, y1, color); + drawClippedLine(x1, y1, x1, y2, color); + drawClippedLine(x2, y1, x2, y2, color); + drawClippedLine(x1, y2, x2, y2, color); +} + +void Screen::drawShadedBox(int x1, int y1, int x2, int y2, int color1, int color2) { + debug(9, "Screen::drawShadedBox(%i, %i, %i, %i, %i, %i)", x1, y1, x2, y2, color1, color2); + assert(x1 > 0 && y1 > 0); + hideMouse(); + + fillRect(x1, y1, x2, y1 + 1, color1); + fillRect(x2 - 1, y1, x2, y2, color1); + + drawClippedLine(x1, y1, x1, y2, color2); + drawClippedLine(x1 + 1, y1 + 1, x1 + 1, y2 - 1, color2); + drawClippedLine(x1, y2, x2, y2, color2); + drawClippedLine(x1, y2 - 1, x2 - 1, y2 - 1, color2); + + showMouse(); +} + +void Screen::drawClippedLine(int x1, int y1, int x2, int y2, int color) { + debug(9, "Screen::drawClippedLine(%i, %i, %i, %i, %i)", x1, y1, x2, y2, color); + + if (x1 < 0) + x1 = 0; + else if (x1 > 319) + x1 = 319; + + if (x2 < 0) + x2 = 0; + else if (x2 > 319) + x2 = 319; + + if (y1 < 0) + y1 = 0; + else if (y1 > 199) + y1 = 199; + + if (y2 < 0) + y2 = 0; + else if (y2 > 199) + y2 = 199; + + if (x1 == x2) + if (y1 > y2) + drawLine(true, x1, y2, y1 - y2 + 1, color); + else + drawLine(true, x1, y1, y2 - y1 + 1, color); + else + if (x1 > x2) + drawLine(false, x2, y1, x1 - x2 + 1, color); + else + drawLine(false, x1, y1, x2 - x1 + 1, color); +} + +void Screen::drawLine(bool horizontal, int x, int y, int length, int color) { + debug(9, "Screen::drawLine(%i, %i, %i, %i, %i)", horizontal, x, y, length, color); + + uint8 *ptr = getPagePtr(_curPage) + y * SCREEN_W + x; + + if (horizontal) { + assert((y + length) <= SCREEN_H); + int currLine = 0; + while (currLine < length) { + *ptr = color; + ptr += SCREEN_W; + currLine++; + } + } else { + assert((x + length) <= SCREEN_W); + memset(ptr, color, length); + } +} + +void Screen::setAnimBlockPtr(int size) { + debug(9, "Screen::setAnimBlockPtr(%d)", size); + free(_animBlockPtr); + _animBlockPtr = (uint8 *)malloc(size); + assert(_animBlockPtr); + memset(_animBlockPtr, 0, size); + _animBlockSize = size; +} + +void Screen::setTextColorMap(const uint8 *cmap) { + debug(9, "Screen::setTextColorMap(0x%X)", cmap); + setTextColor(cmap, 0, 11); +} + +void Screen::setTextColor(const uint8 *cmap, int a, int b) { + debug(9, "Screen::setTextColor(0x%X, %d, %d)", cmap, a, b); + for (int i = a; i <= b; ++i) { + _textColorsMap[i] = *cmap++; + } +} + +void Screen::loadFont(FontId fontId, uint8 *fontData) { + debug(9, "Screen::loadFont(%d, 0x%X)", fontId, fontData); + Font *fnt = &_fonts[fontId]; + assert(fontData && !fnt->fontData); + fnt->fontData = fontData; + uint16 fontSig = READ_LE_UINT16(fontData + 2); + if (fontSig != 0x500) { + error("Invalid font data"); + } + fnt->charWidthTable = fontData + READ_LE_UINT16(fontData + 8); + fnt->charBoxHeight = READ_LE_UINT16(fontData + 4); + fnt->charBitmapOffset = READ_LE_UINT16(fontData + 6); + fnt->charWidthTableOffset = READ_LE_UINT16(fontData + 8); + fnt->charHeightTableOffset = READ_LE_UINT16(fontData + 0xC); +} + +Screen::FontId Screen::setFont(FontId fontId) { + debug(9, "Screen::setFont(%d)", fontId); + FontId prev = _currentFont; + _currentFont = fontId; + return prev; +} + +int Screen::getCharWidth(uint8 c) const { + debug(9, "Screen::getCharWidth('%c')", c); + return (int)_fonts[_currentFont].charWidthTable[c] + _charWidth; +} + +int Screen::getTextWidth(const char *str) const { + debug(9, "Screen::getTextWidth('%s')", str); + int curLineLen = 0; + int maxLineLen = 0; + while (1) { + char c = *str++; + if (c == 0) { + break; + } else if (c == '\r') { + if (curLineLen > maxLineLen) { + maxLineLen = curLineLen; + } else { + curLineLen = 0; + } + } else { + curLineLen += getCharWidth(c); + } + } + return MAX(curLineLen, maxLineLen); +} + +void Screen::printText(const char *str, int x, int y, uint8 color1, uint8 color2) { + debug(9, "Screen::printText('%s', %d, %d, 0x%X, 0x%X)", str, x, y, color1, color2); + uint8 cmap[2]; + cmap[0] = color2; + cmap[1] = color1; + setTextColor(cmap, 0, 1); + + Font *fnt = &_fonts[_currentFont]; + uint8 charHeight = *(fnt->fontData + fnt->charBoxHeight + 4); + + if (x < 0) { + x = 0; + } else if (x >= SCREEN_W) { + return; + } + int x_start = x; + if (y < 0) { + y = 0; + } else if (y >= SCREEN_H) { + return; + } + + while (1) { + char c = *str++; + if (c == 0) { + break; + } else if (c == '\r') { + x = x_start; + y += charHeight + _charOffset; + } else { + int charWidth = getCharWidth(c); + if (x + charWidth > SCREEN_W) { + x = x_start; + y += charHeight + _charOffset; + if (y >= SCREEN_H) { + break; + } + } + drawChar(c, x, y); + x += charWidth; + } + } +} + +void Screen::drawChar(uint8 c, int x, int y) { + debug(9, "Screen::drawChar('%c', %d, %d)", c, x, y); + Font *fnt = &_fonts[_currentFont]; + uint8 *dst = getPagePtr(_curPage) + y * SCREEN_W + x; + uint16 bitmapOffset = READ_LE_UINT16(fnt->fontData + fnt->charBitmapOffset + c * 2); + if (bitmapOffset == 0) { + return; + } + uint8 charWidth = *(fnt->fontData + fnt->charWidthTableOffset + c); + if (charWidth + x > SCREEN_W) { + return; + } + uint8 charH0 = *(fnt->fontData + fnt->charBoxHeight + 4); + if (charH0 + y > SCREEN_H) { + return; + } + uint8 charH1 = *(fnt->fontData + fnt->charHeightTableOffset + c * 2); + uint8 charH2 = *(fnt->fontData + fnt->charHeightTableOffset + c * 2 + 1); + charH0 -= charH1 + charH2; + + const uint8 *src = fnt->fontData + bitmapOffset; + const int pitch = SCREEN_W - charWidth; + + while (charH1--) { + uint8 col = _textColorsMap[0]; + for (int i = 0; i < charWidth; ++i) { + if (col != 0) { + *dst = col; + } + ++dst; + } + dst += pitch; + } + + while (charH2--) { + uint8 b = 0; + for (int i = 0; i < charWidth; ++i) { + uint8 col; + if (i & 1) { + col = _textColorsMap[b >> 4]; + } else { + b = *src++; + col = _textColorsMap[b & 0xF]; + } + if (col != 0) { + *dst = col; + } + ++dst; + } + dst += pitch; + } + + while (charH0--) { + uint8 col = _textColorsMap[0]; + for (int i = 0; i < charWidth; ++i) { + if (col != 0) { + *dst = col; + } + ++dst; + } + dst += pitch; + } +} + +void Screen::setScreenDim(int dim) { + debug(9, "setScreenDim(%d)", dim); + assert(dim < _screenDimTableCount); + _curDim = &_screenDimTable[dim]; + // XXX +} + +void Screen::drawShape(uint8 pageNum, const uint8 *shapeData, int x, int y, int sd, int flags, ...) { + debug(9, "Screen::drawShape(%d, 0x%X, %d, %d, %d, 0x%.04X, ...)", pageNum, shapeData, x, y, sd, flags); + if (!shapeData) + return; + va_list args; + va_start(args, flags); + + static int drawShapeVar1 = 0; + static int drawShapeVar2[] = { + 1, 3, 2, 5, 4, 3, 2, 1 + }; + static int drawShapeVar3 = 1; + static int drawShapeVar4 = 0; + static int drawShapeVar5 = 0; + + uint8 *table = 0; + int tableLoopCount = 0; + int drawLayer = 0; + uint8 *table2 = 0; + uint8 *table3 = 0; + uint8 *table4 = 0; + + if (flags & 0x8000) { + table2 = va_arg(args, uint8*); + } + if (flags & 0x100) { + table = va_arg(args, uint8*); + tableLoopCount = va_arg(args, int); + if (!tableLoopCount) + flags &= 0xFFFFFEFF; + } + if (flags & 0x1000) { + table3 = va_arg(args, uint8*); + table4 = va_arg(args, uint8*); + } + if (flags & 0x200) { + drawShapeVar1 += 1; + drawShapeVar1 &= 7; + drawShapeVar3 = drawShapeVar2[drawShapeVar1]; + drawShapeVar4 = 0; + drawShapeVar5 = 256; + } + if (flags & 0x4000) { + drawShapeVar5 = va_arg(args, int); + } + if (flags & 0x800) { + drawLayer = va_arg(args, int); + } + int scale_w, scale_h; + if (flags & DSF_SCALE) { + scale_w = va_arg(args, int); + scale_h = va_arg(args, int); + } else { + scale_w = 0x100; + scale_h = 0x100; + } + + int ppc = (flags >> 8) & 0x3F; + + const uint8 *src = shapeData; + if (_vm->features() & GF_TALKIE) { + src += 2; + } + uint16 shapeFlags = READ_LE_UINT16(src); src += 2; + + int shapeHeight = *src++; + int scaledShapeHeight = (shapeHeight * scale_h) >> 8; + if (scaledShapeHeight == 0) { + va_end(args); + return; + } + + int shapeWidth = READ_LE_UINT16(src); src += 2; + int scaledShapeWidth = (shapeWidth * scale_w) >> 8; + if (scaledShapeWidth == 0) { + va_end(args); + return; + } + + if (flags & DSF_CENTER) { + x -= scaledShapeWidth >> 1; + y -= scaledShapeHeight >> 1; + } + + src += 3; + + uint16 frameSize = READ_LE_UINT16(src); src += 2; + if ((shapeFlags & 1) || (flags & 0x400)) { + src += 0x10; + } + if (!(shapeFlags & 2)) { + decodeFrame4(src, _animBlockPtr, frameSize); + src = _animBlockPtr; + } + + int shapeSize = shapeWidth * shapeHeight; + if (_decodeShapeBufferSize < shapeSize) { + free(_decodeShapeBuffer); + _decodeShapeBuffer = (uint8 *)malloc(shapeSize); + _decodeShapeBufferSize = shapeSize; + } + if (!_decodeShapeBuffer) { + _decodeShapeBufferSize = 0; + va_end(args); + return; + } + memset(_decodeShapeBuffer, 0, _decodeShapeBufferSize); + uint8 *decodedShapeFrame = _decodeShapeBuffer; + + // only used if shapeFlag & 1 is NOT zero + const uint8 *colorTable = shapeData + 10; + if (_vm->features() & GF_TALKIE) { + colorTable += 2; + } + + for (int j = 0; j < shapeHeight; ++j) { + uint8 *dsbNextLine = decodedShapeFrame + shapeWidth; + int count = shapeWidth; + while (count > 0) { + uint8 code = *src++; + if (code != 0) { + // this is guessed + if (shapeFlags & 1) { + if (code < 16) { + *decodedShapeFrame++ = colorTable[code]; + } + } else { + *decodedShapeFrame++ = code; + } + --count; + } else { + code = *src++; + decodedShapeFrame += code; + count -= code; + } + } + decodedShapeFrame = dsbNextLine; + } + + uint16 sx1 = _screenDimTable[sd].sx * 8; + uint16 sy1 = _screenDimTable[sd].sy; + uint16 sx2 = sx1 + _screenDimTable[sd].w * 8; + uint16 sy2 = sy1 + _screenDimTable[sd].h; + if (flags & DSF_WND_COORDS) { + x += sx1; + y += sy1; + } + + int x1, x2; + if (x >= 0) { + x1 = 0; + if (x + scaledShapeWidth < sx2) { + x2 = scaledShapeWidth; + } else { + x2 = sx2 - x; + } + } else { + x2 = scaledShapeWidth; + x1 = -x; + x = 0; + if (x2 > sx2) { + x2 = sx2; + } + } + + int y1, y2; + if (y >= 0) { + y1 = 0; + if (y + scaledShapeHeight < sy2) { + y2 = scaledShapeHeight; + } else { + y2 = sy2 - y; + } + } else { + y2 = scaledShapeHeight; + y1 = -y; + y = 0; + if (y2 > sy2) { + y2 = sy2; + } + } + + uint8 *dst = getPagePtr(pageNum) + y * SCREEN_W + x; + uint8 *dstStart = getPagePtr(pageNum); + + int scaleYTable[SCREEN_H]; + assert(y1 >= 0 && y2 < SCREEN_H); + for (y = y1; y < y2; ++y) { + scaleYTable[y] = (y << 8) / scale_h; + } + int scaleXTable[SCREEN_W]; + assert(x1 >= 0 && x2 < SCREEN_W); + for (x = x1; x < x2; ++x) { + scaleXTable[x] = (x << 8) / scale_w; + } + + const uint8 *shapeBuffer = _decodeShapeBuffer; + if (flags & DSF_Y_FLIPPED) { + shapeBuffer += shapeWidth * (shapeHeight - 1); + } + if (flags & DSF_X_FLIPPED) { + shapeBuffer += shapeWidth - 1; + } + + for (y = y1; y < y2; ++y) { + uint8 *dstNextLine = dst + SCREEN_W; + int j = scaleYTable[y]; + if (flags & DSF_Y_FLIPPED) { + j = -j; + } + for (x = x1; x < x2; ++x) { + int xpos = scaleXTable[x]; + if (flags & DSF_X_FLIPPED) { + xpos = -xpos; + } + uint8 color = shapeBuffer[j * shapeWidth + xpos]; + if (color != 0) { + switch (ppc) { + case 0: + *dst = color; + break; + + case 1: + for (int i = 0; i < tableLoopCount; ++i) { + color = table[color]; + } + break; + + case 2: { + int temp = drawShapeVar4 + drawShapeVar5; + if (temp & 0xFF00) { + drawShapeVar4 = temp & 0xFF; + dst += drawShapeVar3; + color = *dst; + dst -= drawShapeVar3; + } else { + drawShapeVar4 = temp; + } + } break; + + case 7: + case 3: + color = *dst; + for (int i = 0; i < tableLoopCount; ++i) { + color = table[color]; + } + break; + + case 4: + color = table2[color]; + break; + + case 5: + color = table2[color]; + for (int i = 0; i < tableLoopCount; ++i) { + color = table[color]; + } + break; + + case 6: { + int temp = drawShapeVar4 + drawShapeVar5; + if (temp & 0xFF00) { + drawShapeVar4 = temp & 0xFF; + dst += drawShapeVar3; + color = *dst; + dst -= drawShapeVar3; + } else { + drawShapeVar4 = temp; + color = table2[color]; + } + } break; + + case 8: { + int offset = dst - dstStart; + uint8 pixel = *(_shapePages[0] + offset); + pixel &= 0x7F; + pixel &= 0x87; + if (drawLayer < pixel) { + color = *(_shapePages[1] + offset); + } + } break; + + case 9: { + int offset = dst - dstStart; + uint8 pixel = *(_shapePages[0] + offset); + pixel &= 0x7F; + pixel &= 0x87; + if (drawLayer < pixel) { + color = *(_shapePages[1] + offset); + } else { + for (int i = 0; i < tableLoopCount; ++i) { + color = table[color]; + } + } + } break; + + case 10: { + int offset = dst - dstStart; + uint8 pixel = *(_shapePages[0] + offset); + pixel &= 0x7F; + pixel &= 0x87; + if (drawLayer < pixel) { + color = *(_shapePages[1] + offset); + drawShapeVar4 = pixel; + } else { + int temp = drawShapeVar4 + drawShapeVar5; + if (temp & 0xFF00) { + dst += drawShapeVar3; + color = *dst; + dst -= drawShapeVar3; + } + drawShapeVar4 = temp & 0xFF; + } + } break; + + case 15: + case 11: { + int offset = dst - dstStart; + uint8 pixel = *(_shapePages[0] + offset); + pixel &= 0x7F; + pixel &= 0x87; + if (drawLayer < pixel) { + color = *(_shapePages[1] + offset); + } else { + color = *dst; + for (int i = 0; i < tableLoopCount; ++i) { + color = table[color]; + } + } + } break; + + case 12: { + int offset = dst - dstStart; + uint8 pixel = *(_shapePages[0] + offset); + pixel &= 0x7F; + pixel &= 0x87; + if (drawLayer < pixel) { + color = *(_shapePages[1] + offset); + } else { + color = table2[color]; + } + } break; + + case 13: { + int offset = dst - dstStart; + uint8 pixel = *(_shapePages[0] + offset); + pixel &= 0x7F; + pixel &= 0x87; + if (drawLayer < pixel) { + color = *(_shapePages[1] + offset); + } else { + color = table2[color]; + for (int i = 0; i < tableLoopCount; ++i) { + color = table[color]; + } + } + } break; + + case 14: { + int offset = dst - dstStart; + uint8 pixel = *(_shapePages[0] + offset); + pixel &= 0x7F; + pixel &= 0x87; + if (drawLayer < pixel) { + color = *(_shapePages[1] + offset); + drawShapeVar4 = pixel; + } else { + int temp = drawShapeVar4 + drawShapeVar5; + if (temp & 0xFF00) { + dst += drawShapeVar3; + color = *dst; + dst -= drawShapeVar3; + drawShapeVar4 = temp % 0xFF; + } else { + drawShapeVar4 = temp; + color = table2[color]; + } + } + } break; + + case 16: { + uint8 newColor = table3[color]; + if (!(newColor & 0x80)) { + color = *dst; + color = table4[color + (newColor << 8)]; + } + } break; + + case 17: { + for (int i = 0; i < tableLoopCount; ++i) { + color = table[color]; + } + uint8 newColor = table3[color]; + if (!(newColor & 0x80)) { + color = *dst; + color = table4[color + (newColor << 8)]; + } + } break; + + case 18: { + int temp = drawShapeVar4 + drawShapeVar5; + if (temp & 0xFF00) { + drawShapeVar4 = temp & 0xFF; + dst += drawShapeVar3; + color = *dst; + dst -= drawShapeVar3; + uint8 newColor = table3[color]; + if (!(newColor & 0x80)) { + color = *dst; + color = table4[color + (newColor << 8)]; + } + } else { + drawShapeVar4 = temp; + } + } break; + + case 23: + case 19: { + color = *dst; + for (int i = 0; i < tableLoopCount; ++i) { + color = table[color]; + } + uint8 newColor = table3[color]; + if (!(newColor & 0x80)) { + color = *dst; + color = table4[color + (newColor << 8)]; + } + } break; + + case 20: { + color = table2[color]; + uint8 newColor = table3[color]; + if (!(newColor & 0x80)) { + color = *dst; + color = table4[color + (newColor << 8)]; + } + } break; + + case 21: { + color = table2[color]; + for (int i = 0; i < tableLoopCount; ++i) { + color = table[color]; + } + uint8 newColor = table3[color]; + if (!(newColor & 0x80)) { + color = *dst; + color = table4[color + (newColor << 8)]; + } + } break; + + case 22: { + int temp = drawShapeVar4 + drawShapeVar5; + if (temp & 0xFF00) { + drawShapeVar4 = temp & 0xFF; + dst += drawShapeVar3; + color = *dst; + dst -= drawShapeVar3; + uint8 newColor = table3[color]; + if (!(newColor & 0x80)) { + color = *dst; + color = table4[color + (newColor << 8)]; + } + } else { + drawShapeVar4 = temp; + color = table2[color]; + uint8 newColor = table3[color]; + if (!(newColor & 0x80)) { + color = *dst; + color = table4[color + (newColor << 8)]; + } + } + } break; + + case 24: { + int offset = dst - dstStart; + uint8 pixel = *(_shapePages[0] + offset); + pixel &= 0x7F; + pixel &= 0x87; + if (drawLayer < pixel) { + color = *(_shapePages[1] + offset); + } + uint8 newColor = table3[color]; + if (!(newColor & 0x80)) { + color = *dst; + color = table4[color + (newColor << 8)]; + } + } break; + + default: + warning("unhandled ppc: %d", ppc); + break; + } + *dst = color; + } + ++dst; + } + dst = dstNextLine; + } + va_end(args); +} + +void Screen::decodeFrame3(const uint8 *src, uint8 *dst, uint32 size) { + debug(9, "Screen::decodeFrame3(0x%X, 0x%X, %d)", src, dst, size); + const uint8 *dstEnd = dst + size; + while (dst < dstEnd) { + int8 code = *src++; + if (code == 0) { + uint16 sz = READ_BE_UINT16(src); + src += 2; + memset(dst, *src++, sz); + dst += sz; + } else if (code < 0) { + memset(dst, *src++, -code); + dst -= code; + } else { + memcpy(dst, src, code); + dst += code; + src += code; + } + } +} + +void Screen::decodeFrame4(const uint8 *src, uint8 *dst, uint32 dstSize) { + debug(9, "Screen::decodeFrame4(0x%X, 0x%X, %d)", src, dst, dstSize); + uint8 *dstOrig = dst; + uint8 *dstEnd = dst + dstSize; + while (1) { + int count = dstEnd - dst; + if (count == 0) { + break; + } + uint8 code = *src++; + if (!(code & 0x80)) { + int len = MIN(count, (code >> 4) + 3); + int offs = ((code & 0xF) << 8) | *src++; + const uint8 *dstOffs = dst - offs; + while (len--) { + *dst++ = *dstOffs++; + } + } else if (code & 0x40) { + int len = (code & 0x3F) + 3; + if (code == 0xFE) { + len = READ_LE_UINT16(src); src += 2; + if (len > count) { + len = count; + } + memset(dst, *src++, len); dst += len; + } else { + if (code == 0xFF) { + len = READ_LE_UINT16(src); src += 2; + } + int offs = READ_LE_UINT16(src); src += 2; + if (len > count) { + len = count; + } + const uint8 *dstOffs = dstOrig + offs; + while (len--) { + *dst++ = *dstOffs++; + } + } + } else if (code != 0x80) { + int len = MIN(count, code & 0x3F); + while (len--) { + *dst++ = *src++; + } + } else { + break; + } + } +} + +void Screen::decodeFrameDelta(uint8 *dst, const uint8 *src) { + debug(9, "Screen::decodeFrameDelta(0x%X, 0x%X)", dst, src); + while (1) { + uint8 code = *src++; + if (code == 0) { + uint8 len = *src++; + code = *src++; + while (len--) { + *dst++ ^= code; + } + } else if (code & 0x80) { + code -= 0x80; + if (code != 0) { + dst += code; + } else { + uint16 subcode = READ_LE_UINT16(src); src += 2; + if (subcode == 0) { + break; + } else if (subcode & 0x8000) { + subcode -= 0x8000; + if (subcode & 0x4000) { + uint16 len = subcode - 0x4000; + code = *src++; + while (len--) { + *dst++ ^= code; + } + } else { + while (subcode--) { + *dst++ ^= *src++; + } + } + } else { + dst += subcode; + } + } + } else { + while (code--) { + *dst++ ^= *src++; + } + } + } +} + +void Screen::decodeFrameDeltaPage(uint8 *dst, const uint8 *src, int pitch, int noXor) { + debug(9, "Screen::decodeFrameDeltaPage(0x%X, 0x%X, %d, %d)", dst, src, pitch, noXor); + int count = 0; + uint8 *dstNext = dst; + while (1) { + uint8 code = *src++; + if (code == 0) { + uint8 len = *src++; + code = *src++; + while (len--) { + if (noXor) { + *dst++ = code; + } else { + *dst++ ^= code; + } + if (++count == pitch) { + count = 0; + dstNext += SCREEN_W; + dst = dstNext; + } + } + } else if (code & 0x80) { + code -= 0x80; + if (code != 0) { + dst += code; + + count += code; + while (count >= pitch) { + count -= pitch; + dstNext += SCREEN_W; + dst = dstNext + count; + } + } else { + uint16 subcode = READ_LE_UINT16(src); src += 2; + if (subcode == 0) { + break; + } else if (subcode & 0x8000) { + subcode -= 0x8000; + if (subcode & 0x4000) { + uint16 len = subcode - 0x4000; + code = *src++; + while (len--) { + if (noXor) { + *dst++ = code; + } else { + *dst++ ^= code; + } + if (++count == pitch) { + count = 0; + dstNext += SCREEN_W; + dst = dstNext; + } + } + } else { + while (subcode--) { + if (noXor) { + *dst++ = *src++; + } else { + *dst++ ^= *src++; + } + if (++count == pitch) { + count = 0; + dstNext += SCREEN_W; + dst = dstNext; + } + } + } + } else { + dst += subcode; + + count += subcode; + while (count >= pitch) { + count -= pitch; + dstNext += SCREEN_W; + dst = dstNext + count; + } + + } + } + } else { + while (code--) { + if (noXor) { + *dst++ = *src++; + } else { + *dst++ ^= *src++; + } + if (++count == pitch) { + count = 0; + dstNext += SCREEN_W; + dst = dstNext; + } + } + } + } +} + +uint8 *Screen::encodeShape(int x, int y, int w, int h, int flags) { + debug(9, "Screen::encodeShape(%d, %d, %d, %d, %d)", x, y, w, h, flags); + uint8 *srcPtr = &_pagePtrs[_curPage][y * SCREEN_W + x]; + int16 shapeSize = 0; + uint8 *tmp = srcPtr; + int xpos = w; + + for (int i = h; i > 0; --i) { + uint8 *start = tmp; + shapeSize += w; + xpos = w; + while (xpos) { + uint8 value = *tmp++; + --xpos; + + if (!value) { + shapeSize += 2; + int16 curX = xpos; + bool skip = false; + + while (xpos) { + value = *tmp++; + --xpos; + + if (value) { + skip = true; + break; + } + } + + if (!skip) + ++curX; + + curX -= xpos; + shapeSize -= curX; + + while (curX > 0xFF) { + curX -= 0xFF; + shapeSize += 2; + } + } + } + + tmp = start + SCREEN_W; + } + + int16 shapeSize2 = shapeSize; + if (_vm->features() & GF_TALKIE) { + shapeSize += 12; + } else { + shapeSize += 10; + } + if (flags & 1) + shapeSize += 16; + + static uint8 table[274]; + int tableIndex = 0; + + uint8 *newShape = NULL; + newShape = (uint8*)malloc(shapeSize+16); + assert(newShape); + + byte *dst = newShape; + if (_vm->features() & GF_TALKIE) + dst += 2; + WRITE_LE_UINT16(dst, (flags & 3)); dst += 2; + *dst = h; dst += 1; + WRITE_LE_UINT16(dst, w); dst += 2; + *dst = h; dst += 1; + WRITE_LE_UINT16(dst, shapeSize); dst += 2; + WRITE_LE_UINT16(dst, shapeSize2); dst += 2; + + byte *src = srcPtr; + if (flags & 1) { + dst += 16; + memset(table, 0, sizeof(uint8)*274); + tableIndex = 1; + } + + for (int ypos = h; ypos > 0; --ypos) { + uint8 *srcBackUp = src; + xpos = w; + while (xpos) { + uint8 value = *src++; + if (value) { + if (flags & 1) { + if (!table[value]) { + if (tableIndex == 16) { + value = 1; + } else { + table[0x100+tableIndex] = value; + table[value] = tableIndex; + ++tableIndex; + value = table[value]; + } + } else { + value = table[value]; + } + } + --xpos; + *dst++ = value; + } else { + int16 temp = 1; + --xpos; + + while (xpos) { + if (*src) + break; + ++src; + ++temp; + --xpos; + } + + while (temp > 0xFF) { + *dst++ = 0; + *dst++ = 0xFF; + temp -= 0xFF; + } + + if (temp & 0xFF) { + *dst++ = 0; + *dst++ = temp & 0xFF; + } + } + } + src = srcBackUp + SCREEN_W; + } + + if (!(flags & 2)) { + if (shapeSize > _animBlockSize) { + dst = newShape; + if (_vm->features() & GF_TALKIE) { + dst += 2; + } + flags = READ_LE_UINT16(dst); + flags |= 2; + WRITE_LE_UINT16(dst, flags); + } else { + src = newShape; + if (_vm->features() & GF_TALKIE) { + src += 2; + } + if (flags & 1) { + src += 16; + } + src += 10; + uint8 *shapePtrBackUp = src; + dst = _animBlockPtr; + memcpy(dst, src, shapeSize2); + + int16 size = encodeShapeAndCalculateSize(_animBlockPtr, shapePtrBackUp, shapeSize2); + if (size > shapeSize2) { + shapeSize -= shapeSize2 - size; + newShape = (uint8*)realloc(newShape, shapeSize); + assert(newShape); + } else { + dst = shapePtrBackUp; + src = _animBlockPtr; + memcpy(dst, src, shapeSize2); + dst = newShape; + flags = READ_LE_UINT16(dst); + flags |= 2; + WRITE_LE_UINT16(dst, flags); + } + } + } + + dst = newShape; + if (_vm->features() & GF_TALKIE) { + dst += 2; + } + WRITE_LE_UINT16((dst + 6), shapeSize); + + if (flags & 1) { + dst = newShape + 10; + if (_vm->features() & GF_TALKIE) { + dst += 2; + } + src = &table[0x100]; + memcpy(dst, src, sizeof(uint8)*16); + } + + return newShape; +} + +int16 Screen::encodeShapeAndCalculateSize(uint8 *from, uint8 *to, int size_to) { + debug(9, "Screen::encodeShapeAndCalculateSize(0x%X, 0x%X, %d)", from, to, size_to); + byte *fromPtrEnd = from + size_to; + bool skipPixel = true; + byte *tempPtr = 0; + byte *toPtr = to; + byte *fromPtr = from; + byte *toPtr2 = to; + + *to++ = 0x81; + *to++ = *from++; + + while (from < fromPtrEnd) { + byte *curToPtr = to; + to = fromPtr; + int size = 1; + + while (true) { + byte curPixel = *from; + if (curPixel == *(from+0x40)) { + byte *toBackUp = to; + to = from; + + for (int i = 0; i < (fromPtrEnd - from); ++i) { + if (*to++ != curPixel) + break; + } + --to; + uint16 diffSize = (to - from); + if (diffSize >= 0x41) { + skipPixel = false; + from = to; + to = curToPtr; + *to++ = 0xFE; + WRITE_LE_UINT16(to, diffSize); to += 2; + *to++ = (size >> 8) & 0xFF; + curToPtr = to; + to = toBackUp; + continue; + } else { + to = toBackUp; + } + } + + bool breakLoop = false; + while (true) { + if ((from - to) == 0) { + breakLoop = true; + break; + } + for (int i = 0; i < (from - to); ++i) { + if (*to++ == curPixel) + break; + } + if (*to == curPixel) { + if (*(from+size-1) == *(to+size-2)) + break; + + byte *fromBackUp = from; + byte *toBackUp = to; + --to; + for (int i = 0; i < (fromPtrEnd - from); ++i) { + if (*from++ != *to++) + break; + } + if (*(from - 1) == *(to - 1)) + ++to; + from = fromBackUp; + int temp = to - toBackUp; + to = toBackUp; + if (temp >= size) { + size = temp; + tempPtr = toBackUp - 1; + } + break; + } else { + breakLoop = true; + break; + } + } + + if (breakLoop) + break; + } + + to = curToPtr; + if (size > 2) { + uint16 word = 0; + if (size <= 0x0A) { + uint16 diffSize = from - tempPtr; + if (size <= 0x0FFF) { + byte highByte = ((diffSize & 0xFF00) >> 8) + (((size & 0xFF) - 3) << 4); + word = ((diffSize & 0xFF) << 8) | highByte; + WRITE_LE_UINT16(to, word); to += 2; + from += size; + skipPixel = false; + continue; + } + } + + if (size > 0x40) { + *to++ = 0xFF; + WRITE_LE_UINT16(to, size); to += 2; + } else { + *to++ = ((size & 0xFF) - 3) | 0xC0; + } + + word = tempPtr - fromPtr; + WRITE_LE_UINT16(to, word); to += 2; + from += size; + skipPixel = false; + } else { + if (!skipPixel) { + toPtr2 = to; + *to++ = 0x80; + } + + if (*toPtr2 == 0xBF) { + toPtr2 = to; + *to++ = 0x80; + } + + ++(*toPtr2); + *to++ = *from++; + skipPixel = true; + } + } + *to++ = 0x80; + + return (to - toPtr); +} + +int Screen::getRectSize(int x, int y) { + if (x < 1) { + x = 1; + } else if (x > 40) { + x = 40; + } + + if (y < 1) { + y = 1; + } else if (y > 200) { + y = 200; + } + + return ((x*y) << 3); +} + +void Screen::hideMouse() { + debug(9, "Screen::hideMouse()"); + ++_mouseLockCount; + _system->showMouse(false); +} + +void Screen::showMouse() { + debug(9, "Screen::showMouse()"); + + if (_mouseLockCount == 1) + _system->showMouse(true); + + if (_mouseLockCount > 0) + _mouseLockCount--; + +} + +void Screen::setShapePages(int page1, int page2) { + debug(9, "Screen::setShapePages(%d, %d)", page1, page2); + _shapePages[0] = _pagePtrs[page1]; + _shapePages[1] = _pagePtrs[page2]; +} + +void Screen::setMouseCursor(int x, int y, byte *shape) { + debug(9, "Screen::setMouseCursor(%d, %d, 0x%X)", x, y, shape); + if (!shape) + return; + // if mouseDisabled + // return _mouseShape + + if (_vm->features() & GF_TALKIE) + shape += 2; + + int mouseHeight = *(shape+2); + int mouseWidth = (READ_LE_UINT16(shape + 3)) + 2; + + if (_vm->features() & GF_TALKIE) + shape -= 2; + + uint8 *cursor = (uint8 *)malloc(mouseHeight * mouseWidth); + fillRect(0, 0, mouseWidth, mouseHeight, 0, 8); + drawShape(8, shape, 0, 0, 0, 0); + + _system->showMouse(false); + copyRegionToBuffer(8, 0, 0, mouseWidth, mouseHeight, cursor); + _system->setMouseCursor(cursor, mouseWidth, mouseHeight, x, y, 0); + _system->showMouse(true); + free(cursor); + + return; + +} + +void Screen::copyScreenFromRect(int x, int y, int w, int h, uint8 *ptr) { + debug(9, "Screen::copyScreenFromRect(%d, %d, %d, %d, 0x%X)", x, y, w, h, ptr); + x <<= 3; w <<= 3; + uint8 *src = ptr; + uint8 *dst = &_pagePtrs[0][y * SCREEN_W + x]; + for (int i = 0; i < h; ++i) { + memcpy(dst, src, w); + src += w; + dst += SCREEN_W; + } +} + +void Screen::copyScreenToRect(int x, int y, int w, int h, uint8 *ptr) { + debug(9, "Screen::copyScreenToRect(%d, %d, %d, %d, 0x%X)", x, y, w, h, ptr); + x <<= 3; w <<= 3; + uint8 *src = &_pagePtrs[0][y * SCREEN_W + x]; + uint8 *dst = ptr; + for (int i = 0; i < h; ++i) { + memcpy(dst, src, w); + dst += w; + src += SCREEN_W; + } +} + +uint8 *Screen::getPalette(int num) { + debug(9, "Screen::getPalette(%d)", num); + assert(num >= 0 && num < 4); + if (num == 0) { + return _screenPalette; + } + + return _palettes[num-1]; +} + +byte Screen::getShapeFlag1(int x, int y) { + debug(9, "Screen::getShapeFlag1(%d, %d)", x, y); + uint8 color = _shapePages[0][y * SCREEN_W + x]; + color &= 0x80; + color ^= 0x80; + + if (color & 0x80) { + return 1; + } + return 0; +} + +byte Screen::getShapeFlag2(int x, int y) { + debug(9, "Screen::getShapeFlag2(%d, %d)", x, y); + uint8 color = _shapePages[0][y * SCREEN_W + x]; + color &= 0x7F; + color &= 0x87; + return color; +} + +int Screen::setNewShapeHeight(uint8 *shape, int height) { + debug(9, "Screen::setNewShapeHeight(0x%X, %d)", shape, height); + if (_vm->features() & GF_TALKIE) + shape += 2; + int oldHeight = shape[2]; + shape[2] = height; + return oldHeight; +} + +int Screen::resetShapeHeight(uint8 *shape) { + debug(9, "Screen::setNewShapeHeight(0x%X)", shape); + if (_vm->features() & GF_TALKIE) + shape += 2; + int oldHeight = shape[2]; + shape[2] = shape[5]; + return oldHeight; +} + +void Screen::addBitBlitRect(int x, int y, int w, int h) { + debug(9, "Screen::addBitBlitRects(%d, %d, %d, %d)", x, y, w, h); + if (_bitBlitNum >= BITBLIT_RECTS) { + error("too many bit blit rects"); + } + _bitBlitRects[_bitBlitNum].x = x; + _bitBlitRects[_bitBlitNum].y = y; + _bitBlitRects[_bitBlitNum].x2 = w; + _bitBlitRects[_bitBlitNum].y2 = h; + ++_bitBlitNum; +} + +void Screen::bitBlitRects() { + debug(9, "Screen::bitBlitRects()"); + Rect *cur = _bitBlitRects; + while (_bitBlitNum) { + _bitBlitNum--; + copyRegion(cur->x, cur->y, cur->x, cur->y, cur->x2, cur->y2, 2, 0); + ++cur; + } +} + +void Screen::savePageToDisk(const char *file, int page) { + debug(9, "Screen::savePageToDisk('%s', %d)", file, page); + if (!_saveLoadPage[page/2]) { + _saveLoadPage[page/2] = new uint8[SCREEN_W * SCREEN_H]; + assert(_saveLoadPage[page/2]); + } + memcpy(_saveLoadPage[page/2], getPagePtr(page), SCREEN_W * SCREEN_H); +} + +void Screen::loadPageFromDisk(const char *file, int page) { + debug(9, "Screen::loadPageFromDisk('%s', %d)", file, page); + copyBlockToPage(page, 0, 0, SCREEN_W, SCREEN_H, _saveLoadPage[page/2]); + delete [] _saveLoadPage[page/2]; + _saveLoadPage[page/2] = 0; +} + +void Screen::deletePageFromDisk(int page) { + debug(9, "Screen::deletePageFromDisk(%d)", page); + delete [] _saveLoadPage[page/2]; + _saveLoadPage[page/2] = 0; +} + +void Screen::blockInRegion(int x, int y, int width, int height) { + debug(9, "Screen::blockInRegion(%d, %d, %d, %d)", x, y, width, height); + assert(_shapePages[0]); + byte *toPtr = _shapePages[0] + (y * 320 + x); + for (int i = 0; i < height; ++i) { + byte *backUpTo = toPtr; + for (int i2 = 0; i2 < width; ++i2) { + *toPtr++ &= 0x7F; + } + toPtr = (backUpTo + 320); + } +} + +void Screen::blockOutRegion(int x, int y, int width, int height) { + debug(9, "Screen::blockOutRegion(%d, %d, %d, %d)", x, y, width, height); + assert(_shapePages[0]); + byte *toPtr = _shapePages[0] + (y * 320 + x); + for (int i = 0; i < height; ++i) { + byte *backUpTo = toPtr; + for (int i2 = 0; i2 < width; ++i2) { + *toPtr++ |= 0x80; + } + toPtr = (backUpTo + 320); + } +} + +void Screen::rectClip(int &x, int &y, int w, int h) { + if (x < 0) { + x = 0; + } else if (x + w >= 320) { + x = 320 - w; + } + if (y < 0) { + y = 0; + } else if (y + h >= 200) { + y = 200 - h; + } +} + +void Screen::backUpRect0(int xpos, int ypos) { + debug(9, "Screen::backUpRect0(%d, %d)", xpos, ypos); + rectClip(xpos, ypos, 3<<3, 24); + copyRegionToBuffer(_curPage, xpos, ypos, 3<<3, 24, _vm->shapes()[0]); +} + +void Screen::restoreRect0(int xpos, int ypos) { + debug(9, "Screen::restoreRect0(%d, %d)", xpos, ypos); + rectClip(xpos, ypos, 3<<3, 24); + copyBlockToPage(_curPage, xpos, ypos, 3<<3, 24, _vm->shapes()[0]); +} + +void Screen::backUpRect1(int xpos, int ypos) { + debug(9, "Screen::backUpRect1(%d, %d)", xpos, ypos); + rectClip(xpos, ypos, 4<<3, 32); + copyRegionToBuffer(_curPage, xpos, ypos, 4<<3, 32, _vm->shapes()[1]); +} + +void Screen::restoreRect1(int xpos, int ypos) { + debug(9, "Screen::restoreRect1(%d, %d)", xpos, ypos); + rectClip(xpos, ypos, 4<<3, 32); + copyBlockToPage(_curPage, xpos, ypos, 4<<3, 32, _vm->shapes()[1]); +} + +int Screen::getDrawLayer(int x, int y) { + debug(9, "Screen::getDrawLayer(%d, %d)", x, y); + int xpos = x - 8; + int ypos = y - 1; + int layer = 1; + for (int curX = xpos; curX < xpos + 16; ++curX) { + int tempLayer = getShapeFlag2(curX, ypos); + if (layer < tempLayer) { + layer = tempLayer; + } + if (layer >= 7) { + return 7; + } + } + return layer; +} + +int Screen::getDrawLayer2(int x, int y, int height) { + debug(9, "Screen::getDrawLayer2(%d, %d, %d)", x, y, height); + int xpos = x - 8; + int ypos = y - 1; + int layer = 1; + + for (int useX = xpos; useX < xpos + 16; ++useX) { + for (int useY = ypos - height; useY < ypos; ++useY) { + int tempLayer = getShapeFlag2(useX, useY); + if (tempLayer > layer) { + layer = tempLayer; + } + + if (tempLayer >= 7) { + return 7; + } + } + } + return layer; +} + +void Screen::copyBackgroundBlock(int x, int page, int flag) { + debug(9, "Screen::copyBackgroundBlock(%d, %d, %d)", x, page, flag); + + if (x < 1) + return; + + int height = 128; + if (flag) + height += 8; + if (!(x & 1)) + ++x; + if (x == 19) + x = 17; + uint8 *ptr1 = _unkPtr1; + uint8 *ptr2 = _unkPtr2; + int oldVideoPage = _curPage; + _curPage = page; + + int curX = x; + hideMouse(); + copyRegionToBuffer(_curPage, 8, 8, 8, height, ptr2); + for (int i = 0; i < 19; ++i) { + int tempX = curX + 1; + copyRegionToBuffer(_curPage, tempX<<3, 8, 8, height, ptr1); + copyBlockToPage(_curPage, tempX<<3, 8, 8, height, ptr2); + int newXPos = curX + x; + if (newXPos > 37) { + newXPos = newXPos % 38; + } + tempX = newXPos + 1; + copyRegionToBuffer(_curPage, tempX<<3, 8, 8, height, ptr2); + copyBlockToPage(_curPage, tempX<<3, 8, 8, height, ptr1); + curX += x*2; + if (curX > 37) { + curX = curX % 38; + } + } + showMouse(); + _curPage = oldVideoPage; +} + +void Screen::copyBackgroundBlock2(int x) { + copyBackgroundBlock(x, 4, 1); +} + +} // End of namespace Kyra diff --git a/engines/kyra/screen.h b/engines/kyra/screen.h new file mode 100644 index 0000000000..0f2c59cbea --- /dev/null +++ b/engines/kyra/screen.h @@ -0,0 +1,206 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-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 KYRASCREEN_H +#define KYRASCREEN_H + +#include "common/util.h" +#include <stdarg.h> + +class OSystem; + +namespace Kyra { + +class KyraEngine; +class Debugger; +struct Rect; + +struct ScreenDim { + uint16 sx; + uint16 sy; + uint16 w; + uint16 h; + uint16 unk8; + uint16 unkA; + uint16 unkC; + uint16 unkE; +}; + +struct Font { + uint8 *fontData; + uint8 *charWidthTable; + uint16 charBoxHeight; + uint16 charBitmapOffset; + uint16 charWidthTableOffset; + uint16 charHeightTableOffset; +}; + +class Screen { + friend class Debugger; +public: + + enum { + SCREEN_W = 320, + SCREEN_H = 200, + SCREEN_PAGE_SIZE = 320 * 200 + 1024, + SCREEN_PAGE_NUM = 16 + }; + + enum CopyRegionFlags { + CR_X_FLIPPED = 0x01, + CR_CLIPPED = 0x02 + }; + + enum DrawShapeFlags { + DSF_X_FLIPPED = 0x01, + DSF_Y_FLIPPED = 0x02, + DSF_SCALE = 0x04, + DSF_WND_COORDS = 0x10, + DSF_CENTER = 0x20 + }; + + enum FontId { + FID_6_FNT = 0, + FID_8_FNT, + FID_CRED6_FNT, + FID_CRED8_FNT, + FID_NUM + }; + + Screen(KyraEngine *vm, OSystem *system); + ~Screen(); + + void updateScreen(); + uint8 *getPagePtr(int pageNum); + void clearPage(int pageNum); + int setCurPage(int pageNum); + void clearCurPage(); + uint8 getPagePixel(int pageNum, int x, int y); + void setPagePixel(int pageNum, int x, int y, uint8 color); + void fadeFromBlack(); + void fadeToBlack(); + void fadeSpecialPalette(int palIndex, int startIndex, int size, int fadeTime); + void fadePalette(const uint8 *palData, int delay); + void setScreenPalette(const uint8 *palData); + void copyToPage0(int y, int h, uint8 page, uint8 *seqBuf); + void copyRegion(int x1, int y1, int x2, int y2, int w, int h, int srcPage, int dstPage, int flags=0); + void copyBlockToPage(int pageNum, int x, int y, int w, int h, const uint8 *src); + void copyFromCurPageBlock(int x, int y, int w, int h, const uint8 *src); + void copyCurPageBlock(int x, int y, int w, int h, uint8 *dst); + void shuffleScreen(int sx, int sy, int w, int h, int srcPage, int dstPage, int ticks, bool transparent); + void fillRect(int x1, int y1, int x2, int y2, uint8 color, int pageNum = -1); + void drawLine(bool horizontal, int x, int y, int length, int color); + void drawClippedLine(int x1, int y1, int x2, int y2, int color); + void drawShadedBox(int x1, int y1, int x2, int y2, int color1, int color2); + void drawBox(int x1, int y1, int x2, int y2, int color); + void setAnimBlockPtr(int size); + void setTextColorMap(const uint8 *cmap); + void setTextColor(const uint8 *cmap, int a, int b); + void loadFont(FontId fontId, uint8 *fontData); + FontId setFont(FontId fontId); + int getCharWidth(uint8 c) const; + int getTextWidth(const char *str) const; + void printText(const char *str, int x, int y, uint8 color1, uint8 color2); + void drawChar(uint8 c, int x, int y); + void setScreenDim(int dim); + void drawShapePlotPixelCallback1(uint8 *dst, uint8 color); + void drawShape(uint8 pageNum, const uint8 *shapeData, int x, int y, int sd, int flags, ...); + static void decodeFrame3(const uint8 *src, uint8 *dst, uint32 size); + static void decodeFrame4(const uint8 *src, uint8 *dst, uint32 dstSize); + static void decodeFrameDelta(uint8 *dst, const uint8 *src); + static void decodeFrameDeltaPage(uint8 *dst, const uint8 *src, int pitch, int noXor); + uint8 *encodeShape(int x, int y, int w, int h, int flags); + void copyRegionToBuffer(int pageNum, int x, int y, int w, int h, uint8 *dest); + + int getRectSize(int x, int y); + void hideMouse(); + void showMouse(); + void setShapePages(int page1, int page2); + void setMouseCursor(int x, int y, byte *shape); + uint8 *getPalette(int num); + + byte getShapeFlag1(int x, int y); + byte getShapeFlag2(int x, int y); + int setNewShapeHeight(uint8 *shape, int height); + int resetShapeHeight(uint8 *shape); + + void addBitBlitRect(int x, int y, int w, int h); + void bitBlitRects(); + + void savePageToDisk(const char *file, int page); + void loadPageFromDisk(const char *file, int page); + void deletePageFromDisk(int page); + + void blockInRegion(int x, int y, int width, int height); + void blockOutRegion(int x, int y, int width, int height); + + void backUpRect0(int xpos, int ypos); + void restoreRect0(int xpos, int ypos); + void backUpRect1(int xpos, int ypos); + void restoreRect1(int xpos, int ypos); + void copyBackgroundBlock(int x, int page, int flag); + void copyBackgroundBlock2(int x); + void rectClip(int &x, int &y, int w, int h); + int getDrawLayer(int x, int y); + int getDrawLayer2(int x, int y, int height); + + int _charWidth; + int _charOffset; + int _curPage; + uint8 *_currentPalette; + uint8 *_shapePages[2]; + + const ScreenDim *_curDim; + + static const ScreenDim _screenDimTable[]; + static const int _screenDimTableCount; +private: + int16 encodeShapeAndCalculateSize(uint8 *from, uint8 *to, int size); + void restoreMouseRect(); + void copyMouseToScreen(); + void copyScreenFromRect(int x, int y, int w, int h, uint8 *ptr); + void copyScreenToRect(int x, int y, int w, int h, uint8 *ptr); + + uint8 *_pagePtrs[16]; + uint8 *_saveLoadPage[8]; + uint8 *_screenPalette; + uint8 *_palettes[3]; + FontId _currentFont; + Font _fonts[FID_NUM]; + uint8 _textColorsMap[16]; + uint8 *_decodeShapeBuffer; + int _decodeShapeBufferSize; + uint8 *_animBlockPtr; + int _animBlockSize; + int _mouseLockCount; + + Rect *_bitBlitRects; + int _bitBlitNum; + uint8 *_unkPtr1, *_unkPtr2; + + OSystem *_system; + KyraEngine *_vm; +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/script.cpp b/engines/kyra/script.cpp new file mode 100644 index 0000000000..b43766dba7 --- /dev/null +++ b/engines/kyra/script.cpp @@ -0,0 +1,565 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-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 "common/util.h" +#include "common/system.h" +#include "kyra/kyra.h" +#include "kyra/resource.h" +#include "kyra/script.h" + +#define FORM_CHUNK 0x4D524F46 +#define TEXT_CHUNK 0x54584554 +#define DATA_CHUNK 0x41544144 +#define ORDR_CHUNK 0x5244524F + +namespace Kyra { +ScriptHelper::ScriptHelper(KyraEngine *vm) : _vm(vm) { +#define COMMAND(x) { &ScriptHelper::x, #x } + // now we create a list of all Command/Opcode procs and so + static CommandEntry commandProcs[] = { + // 0x00 + COMMAND(c1_jmpTo), + COMMAND(c1_setRetValue), + COMMAND(c1_pushRetOrPos), + COMMAND(c1_push), + // 0x04 + COMMAND(c1_push), + COMMAND(c1_pushVar), + COMMAND(c1_pushBPNeg), + COMMAND(c1_pushBPAdd), + // 0x08 + COMMAND(c1_popRetOrPos), + COMMAND(c1_popVar), + COMMAND(c1_popBPNeg), + COMMAND(c1_popBPAdd), + // 0x0C + COMMAND(c1_addSP), + COMMAND(c1_subSP), + COMMAND(c1_execOpcode), + COMMAND(c1_ifNotJmp), + // 0x10 + COMMAND(c1_negate), + COMMAND(c1_eval), + COMMAND(c1_setRetAndJmp) + }; + _commands = commandProcs; +#undef COMMAND +} + +ScriptHelper::~ScriptHelper() { +} + +bool ScriptHelper::loadScript(const char *filename, ScriptData *scriptData, KyraEngine::OpcodeProc *opcodes, int opcodeSize, byte *specialPtr) { + uint32 size = 0; + uint8 *data = _vm->resource()->fileData(filename, &size); + byte *curData = data; + + uint32 formBlockSize = getFORMBlockSize(curData); + if (formBlockSize == (uint32)-1) { + delete [] data; + error("No FORM chunk found in file: '%s'", filename); + return false; + } + + uint32 chunkSize = getIFFBlockSize(data, curData, size, TEXT_CHUNK); + if (chunkSize != (uint32)-1) { + if (specialPtr) { + scriptData->mustBeFreed = 0; + scriptData->text = specialPtr; + specialPtr += chunkSize; + } else { + scriptData->mustBeFreed = 1; + scriptData->text = new byte[chunkSize]; + } + if (!loadIFFBlock(data, curData, size, TEXT_CHUNK, scriptData->text, chunkSize)) { + delete [] data; + unloadScript(scriptData); + error("Couldn't load TEXT chunk from file: '%s'", filename); + return false; + } + } + + chunkSize = getIFFBlockSize(data, curData, size, ORDR_CHUNK); + if (chunkSize == (uint32)-1) { + delete [] data; + unloadScript(scriptData); + error("No ORDR chunk found in file: '%s'", filename); + return false; + } + if (specialPtr) { + scriptData->mustBeFreed = 0; + scriptData->ordr = specialPtr; + specialPtr += chunkSize; + } else { + scriptData->mustBeFreed = 1; + scriptData->ordr = new byte[chunkSize]; + } + if (!loadIFFBlock(data, curData, size, ORDR_CHUNK, scriptData->ordr, chunkSize)) { + delete [] data; + unloadScript(scriptData); + error("Couldn't load ORDR chunk from file: '%s'", filename); + return false; + } + chunkSize = chunkSize / 2; + while (chunkSize--) { + ((uint16*)scriptData->ordr)[chunkSize] = READ_BE_UINT16(&((uint16*)scriptData->ordr)[chunkSize]); + } + + chunkSize = getIFFBlockSize(data, curData, size, DATA_CHUNK); + if (chunkSize == (uint32)-1) { + delete [] data; + unloadScript(scriptData); + error("No DATA chunk found in file: '%s'", filename); + return false; + } + if (specialPtr) { + scriptData->mustBeFreed = 0; + scriptData->data = specialPtr; + specialPtr += chunkSize; + } else { + scriptData->mustBeFreed = 1; + scriptData->data = new byte[chunkSize]; + } + if (!loadIFFBlock(data, curData, size, DATA_CHUNK, scriptData->data, chunkSize)) { + delete [] data; + unloadScript(scriptData); + error("Couldn't load DATA chunk from file: '%s'", filename); + return false; + } + scriptData->dataSize = chunkSize / 2; + scriptData->opcodes = opcodes; + scriptData->opcodeSize = opcodeSize; + + delete [] data; + return true; +} + +void ScriptHelper::unloadScript(ScriptData *data) { + if (data->mustBeFreed) { + delete [] data->text; + delete [] data->ordr; + delete [] data->data; + } + + data->mustBeFreed = 0; + data->text = data->ordr = data->data = 0; +} + +void ScriptHelper::initScript(ScriptState *scriptStat, ScriptData *data) { + scriptStat->dataPtr = data; + scriptStat->ip = 0; + scriptStat->stack[60] = 0; + scriptStat->bp = 62; + scriptStat->sp = 60; +} + +bool ScriptHelper::startScript(ScriptState *script, int function) { + if (!script->dataPtr) { + return false; + } + uint16 functionOffset = ((uint16*)script->dataPtr->ordr)[function]; + if (functionOffset == (uint16)-1) { + return false; + } + script->ip = &script->dataPtr->data[functionOffset*2]; + return true; +} + +bool ScriptHelper::validScript(ScriptState *script) { + if (!script->ip || !script->dataPtr) + return false; + return true; +} + +bool ScriptHelper::runScript(ScriptState *script) { + _parameter = 0; + _continue = true; + + if (!script->ip) { + return false; + } + + int16 code = READ_BE_UINT16(script->ip); script->ip += 2; + int16 opcode = (code >> 8) & 0x1F; + + if (code & 0x8000) { + opcode = 0; + _parameter = code & 0x7FFF; + } else if (code & 0x4000) { + _parameter = (int8)(code); + } else if (code & 0x2000) { + _parameter = READ_BE_UINT16(script->ip); script->ip += 2; + } else { + _parameter = 0; + } + + if (opcode > 18) { + error("Script unknown command: %d", opcode); + } else { + debug(5, "%s(%d)", _commands[opcode].desc, _parameter); + (this->*(_commands[opcode].proc))(script); + } + + return _continue; +} + +uint32 ScriptHelper::getFORMBlockSize(byte *&data) const { + static const uint32 chunkName = FORM_CHUNK; + if (READ_LE_UINT32(data) != chunkName) { + return (uint32)-1; + } + data += 4; + uint32 retValue = READ_BE_UINT32(data); data += 4; + return retValue; +} + +uint32 ScriptHelper::getIFFBlockSize(byte *start, byte *&data, uint32 maxSize, const uint32 chunkName) const { + uint32 size = (uint32)-1; + bool special = false; + + if (data == (start + maxSize)) { + data = start + 0x0C; + } + while (data < (start + maxSize)) { + uint32 chunk = READ_LE_UINT32(data); data += 4; + uint32 size_temp = READ_BE_UINT32(data); data += 4; + if (chunk != chunkName) { + if (special) { + data += (size_temp + 1) & 0xFFFFFFFE; + } else { + data = start + 0x0C; + special = true; + } + } else { + // kill our data + data = start; + size = size_temp; + break; + } + } + return size; +} + +bool ScriptHelper::loadIFFBlock(byte *start, byte *&data, uint32 maxSize, const uint32 chunkName, byte *loadTo, uint32 ptrSize) const { + bool special = false; + + if (data == (start + maxSize)) { + data = start + 0x0C; + } + while (data < (start + maxSize)) { + uint32 chunk = READ_LE_UINT32(data); data += 4; + uint32 chunkSize = READ_BE_UINT32(data); data += 4; + if (chunk != chunkName) { + if (special) { + data += (chunkSize + 1) & 0xFFFFFFFE; + } else { + data = start + 0x0C; + special = true; + } + } else { + uint32 loadSize = 0; + if (chunkSize < ptrSize) + loadSize = chunkSize; + else + loadSize = ptrSize; + memcpy(loadTo, data, loadSize); + chunkSize = (chunkSize + 1) & 0xFFFFFFFE; + if (chunkSize > loadSize) { + data += (chunkSize - loadSize); + } + return true; + } + } + return false; +} + +#pragma mark - +#pragma mark - Command implementations +#pragma mark - + +void ScriptHelper::c1_jmpTo(ScriptState* script) { + script->ip = script->dataPtr->data + (_parameter << 1); +} + +void ScriptHelper::c1_setRetValue(ScriptState* script) { + script->retValue = _parameter; +} + +void ScriptHelper::c1_pushRetOrPos(ScriptState* script) { + switch (_parameter) { + case 0: + script->stack[--script->sp] = script->retValue; + break; + + case 1: + script->stack[--script->sp] = (script->ip - script->dataPtr->data) / 2 + 1; + script->stack[--script->sp] = script->bp; + script->bp = script->sp + 2; + break; + + default: + _continue = false; + script->ip = 0; + break; + } +} + +void ScriptHelper::c1_push(ScriptState* script) { + script->stack[--script->sp] = _parameter; +} + +void ScriptHelper::c1_pushVar(ScriptState* script) { + script->stack[--script->sp] = script->variables[_parameter]; +} + +void ScriptHelper::c1_pushBPNeg(ScriptState* script) { + script->stack[--script->sp] = script->stack[(-(int32)(_parameter + 2)) + script->bp]; +} + +void ScriptHelper::c1_pushBPAdd(ScriptState* script) { + script->stack[--script->sp] = script->stack[(_parameter - 1) + script->bp]; +} + +void ScriptHelper::c1_popRetOrPos(ScriptState* script) { + switch (_parameter) { + case 0: + script->retValue = script->stack[script->sp++]; + break; + + case 1: + if (script->sp >= 60) { + _continue = false; + script->ip = 0; + } else { + script->bp = script->stack[script->sp++]; + script->ip = script->dataPtr->data + (script->stack[script->sp++] << 1); + } + break; + + default: + _continue = false; + script->ip = 0; + break; + } +} + +void ScriptHelper::c1_popVar(ScriptState* script) { + script->variables[_parameter] = script->stack[script->sp++]; +} + +void ScriptHelper::c1_popBPNeg(ScriptState* script) { + script->stack[(-(int32)(_parameter + 2)) + script->bp] = script->stack[script->sp++]; +} + +void ScriptHelper::c1_popBPAdd(ScriptState* script) { + script->stack[(_parameter - 1) + script->bp] = script->stack[script->sp++]; +} + +void ScriptHelper::c1_addSP(ScriptState* script) { + script->sp += _parameter; +} + +void ScriptHelper::c1_subSP(ScriptState* script) { + script->sp -= _parameter; +} + +void ScriptHelper::c1_execOpcode(ScriptState* script) { + assert((uint8)_parameter < script->dataPtr->opcodeSize); + if (script->dataPtr->opcodes[(uint8)_parameter] == &KyraEngine::cmd_dummy) + debug("calling unimplemented opcode(0x%.02X)", (uint8)_parameter); + int val = (_vm->*script->dataPtr->opcodes[(uint8)_parameter])(script); + assert(script); + script->retValue = val; +} + +void ScriptHelper::c1_ifNotJmp(ScriptState* script) { + if (!script->stack[script->sp++]) { + _parameter &= 0x7FFF; + script->ip = script->dataPtr->data + (_parameter << 1); + } +} + +void ScriptHelper::c1_negate(ScriptState* script) { + int16 value = script->stack[script->sp]; + switch (_parameter) { + case 0: + if (!value) { + script->stack[script->sp] = 1; + } else { + script->stack[script->sp] = 0; + } + break; + + case 1: + script->stack[script->sp] = -value; + break; + + case 2: + script->stack[script->sp] = ~value; + break; + + default: + _continue = false; + break; + } +} + +void ScriptHelper::c1_eval(ScriptState* script) { + int16 ret = 0; + bool error = false; + + int16 val1 = script->stack[script->sp++]; + int16 val2 = script->stack[script->sp++]; + + switch (_parameter) { + case 0: + if (!val2 || !val1) { + ret = 0; + } else { + ret = 1; + } + break; + + case 1: + if (val2 || val1) { + ret = 1; + } else { + ret = 0; + } + break; + + case 2: + if (val1 == val2) { + ret = 1; + } else { + ret = 0; + } + break; + + case 3: + if (val1 != val2) { + ret = 1; + } else { + ret = 0; + } + break; + + case 4: + if (val1 > val2) { + ret = 1; + } else { + ret = 0; + } + break; + + case 5: + if (val1 >= val2) { + ret = 1; + } else { + ret = 0; + } + break; + + case 6: + if (val1 < val2) { + ret = 1; + } else { + ret = 0; + } + break; + + case 7: + if (val1 <= val2) { + ret = 1; + } else { + ret = 0; + } + break; + + case 8: + ret = val1 + val2; + break; + + case 9: + ret = val2 - val1; + break; + + case 10: + ret = val1 * val2; + break; + + case 11: + ret = val2 / val1; + break; + + case 12: + ret = val2 >> val1; + break; + + case 13: + ret = val2 << val1; + break; + + case 14: + ret = val1 & val2; + break; + + case 15: + ret = val1 | val2; + break; + + case 16: + ret = val2 % val1; + break; + + case 17: + ret = val1 ^ val2; + break; + + default: + warning("Unknown evaluate func: %d", _parameter); + error = true; + break; + } + + if (error) { + script->ip = 0; + _continue = false; + } else { + script->stack[--script->sp] = ret; + } +} + +void ScriptHelper::c1_setRetAndJmp(ScriptState* script) { + if (script->sp >= 60) { + _continue = false; + script->ip = 0; + } else { + script->retValue = script->stack[script->sp++]; + uint16 temp = script->stack[script->sp++]; + script->stack[60] = 0; + script->ip = &script->dataPtr->data[temp*2]; + } +} +} // end of namespace Kyra diff --git a/engines/kyra/script.h b/engines/kyra/script.h new file mode 100644 index 0000000000..28f5170422 --- /dev/null +++ b/engines/kyra/script.h @@ -0,0 +1,106 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-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 KYRASCRIPT_H +#define KYRASCRIPT_H + +#include "kyra/kyra.h" + +namespace Kyra { +struct ScriptData { + byte *text; + byte *data; + byte *ordr; + uint16 dataSize; + KyraEngine::OpcodeProc *opcodes; + int opcodeSize; + uint16 mustBeFreed; +}; + +struct ScriptState { + byte *ip; + ScriptData *dataPtr; + int16 retValue; + uint16 bp; + uint16 sp; + int16 variables[30]; + int16 stack[61]; +}; + +enum { + SCRIPT_INIT = 0 +}; + +class ScriptHelper { +public: + ScriptHelper(KyraEngine *vm); + virtual ~ScriptHelper(); + + bool loadScript(const char *filename, ScriptData *data, KyraEngine::OpcodeProc *opcodes, int opcodeSize, byte *specialPtr = 0); + void unloadScript(ScriptData *data); + + void initScript(ScriptState *scriptState, ScriptData *data); + bool startScript(ScriptState *script, int function); + + bool validScript(ScriptState *script); + + bool runScript(ScriptState *script); +protected: + uint32 getFORMBlockSize(byte *&data) const; + uint32 getIFFBlockSize(byte *start, byte *&data, uint32 maxSize, const uint32 chunk) const; + bool loadIFFBlock(byte *start, byte *&data, uint32 maxSize, const uint32 chunk, byte *loadTo, uint32 ptrSize) const; + + KyraEngine *_vm; + int16 _parameter; + bool _continue; + + typedef void (ScriptHelper::*CommandProc)(ScriptState*); + struct CommandEntry { + CommandProc proc; + const char* desc; + }; + + const CommandEntry *_commands; +private: + void c1_jmpTo(ScriptState*); + void c1_setRetValue(ScriptState*); + void c1_pushRetOrPos(ScriptState*); + void c1_push(ScriptState*); + //void c1_push(); same as 03 + void c1_pushVar(ScriptState*); + void c1_pushBPNeg(ScriptState*); + void c1_pushBPAdd(ScriptState*); + void c1_popRetOrPos(ScriptState*); + void c1_popVar(ScriptState*); + void c1_popBPNeg(ScriptState*); + void c1_popBPAdd(ScriptState*); + void c1_addSP(ScriptState*); + void c1_subSP(ScriptState*); + void c1_execOpcode(ScriptState*); + void c1_ifNotJmp(ScriptState*); + void c1_negate(ScriptState*); + void c1_eval(ScriptState*); + void c1_setRetAndJmp(ScriptState*); +}; +} // end of namespace Kyra + +#endif diff --git a/engines/kyra/script_v1.cpp b/engines/kyra/script_v1.cpp new file mode 100644 index 0000000000..08ff4a6d9f --- /dev/null +++ b/engines/kyra/script_v1.cpp @@ -0,0 +1,1713 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-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 "kyra/kyra.h" +#include "kyra/script.h" +#include "kyra/screen.h" +#include "kyra/sprites.h" +#include "kyra/wsamovie.h" +#include "kyra/animator.h" +#include "kyra/text.h" +#include "common/system.h" + +namespace Kyra { +#define stackPos(x) script->stack[script->sp+x] +#define stackPosString(x) (char*)&script->dataPtr->text[READ_BE_UINT16(&((uint16 *)script->dataPtr->text)[stackPos(x)])] + +int KyraEngine::cmd_magicInMouseItem(ScriptState *script) { + debug(3, "cmd_magicInMouseItem(0x%X) (%d, %d)", script, stackPos(0), stackPos(1)); + magicInMouseItem(stackPos(0), stackPos(1), -1); + return 0; +} + +int KyraEngine::cmd_characterSays(ScriptState *script) { + _skipFlag = false; + if (_features & GF_TALKIE) { + debug(3, "cmd_characterSays(0x%X) (%d, '%s', %d, %d)", script, stackPos(0), stackPosString(1), stackPos(2), stackPos(3)); + snd_voiceWaitForFinish(); + snd_playVoiceFile(stackPos(0)); + characterSays(stackPosString(1), stackPos(2), stackPos(3)); + } else { + debug(3, "cmd_characterSays(0x%X) ('%s', %d, %d)", script, stackPosString(0), stackPos(1), stackPos(2)); + characterSays(stackPosString(0), stackPos(1), stackPos(2)); + } + + return 0; +} + +int KyraEngine::cmd_pauseTicks(ScriptState *script) { + debug(3, "cmd_pauseTicks(0x%X) (%d, %d)", script, stackPos(0), stackPos(1)); + if (stackPos(1)) { + warning("STUB: special cmd_pauseTicks"); + // delete this after correct implementing + delayWithTicks(stackPos(0)); + } else { + delayWithTicks(stackPos(0)); + } + return 0; +} + +int KyraEngine::cmd_drawSceneAnimShape(ScriptState *script) { + debug(3, "cmd_drawSceneAnimShape(0x%X) (%d, %d, %d, %d, %d)", script, stackPos(0), stackPos(1), stackPos(2), stackPos(3), stackPos(4)); + _screen->drawShape(stackPos(4), _sprites->_sceneShapes[stackPos(0)], stackPos(1), stackPos(2), 0, (stackPos(3) != 0) ? 1 : 0); + return 0; +} + +int KyraEngine::cmd_queryGameFlag(ScriptState *script) { + debug(3, "cmd_queryGameFlag(0x%X) (0x%X)", script, stackPos(0)); + return queryGameFlag(stackPos(0)); +} + +int KyraEngine::cmd_setGameFlag(ScriptState *script) { + debug(3, "cmd_setGameFlag(0x%X) (0x%X)", script, stackPos(0)); + return setGameFlag(stackPos(0)); +} + +int KyraEngine::cmd_resetGameFlag(ScriptState *script) { + debug(3, "cmd_resetGameFlag(0x%X) (0x%X)", script, stackPos(0)); + return resetGameFlag(stackPos(0)); +} + +int KyraEngine::cmd_runNPCScript(ScriptState *script) { + warning("STUB: cmd_runNPCScript"); + return 0; +} + +int KyraEngine::cmd_setSpecialExitList(ScriptState *script) { + debug(3, "cmd_setSpecialExitList(0x%X) (%d, %d, %d, %d, %d, %d, %d, %d, %d, %d)", script, stackPos(0), stackPos(1), stackPos(2), stackPos(3), stackPos(4), stackPos(5), stackPos(6), stackPos(7), stackPos(8), stackPos(9)); + + for (int i = 0; i < 10; ++i) { + _exitList[i] = stackPos(i); + } + _exitListPtr = _exitList; + + return 0; +} + +int KyraEngine::cmd_blockInWalkableRegion(ScriptState *script) { + debug(3, "cmd_blockInWalkableRegion(0x%X) (%d, %d, %d, %d)", script, stackPos(0), stackPos(1), stackPos(2), stackPos(3)); + _screen->blockInRegion(stackPos(0), stackPos(1), stackPos(2)-stackPos(0)+1, stackPos(3)-stackPos(1)+1); + return 0; +} + +int KyraEngine::cmd_blockOutWalkableRegion(ScriptState *script) { + debug(3, "cmd_blockOutWalkableRegion(0x%X) (%d, %d, %d, %d)", script, stackPos(0), stackPos(1), stackPos(2), stackPos(3)); + _screen->blockOutRegion(stackPos(0), stackPos(1), stackPos(2)-stackPos(0)+1, stackPos(3)-stackPos(1)+1); + return 0; +} + +int KyraEngine::cmd_walkPlayerToPoint(ScriptState *script) { + debug(3, "cmd_walkPlayerToPoint(0x%X) (%d, %d, %d, %d)", script, stackPos(0), stackPos(1), stackPos(2), stackPos(3)); + + int normalTimers = stackPos(2); + if (!normalTimers) { + disableTimer(19); + disableTimer(14); + disableTimer(18); + } + + int reinitScript = handleSceneChange(stackPos(0), stackPos(1), stackPos(2), stackPos(3)); + + if (!normalTimers) { + enableTimer(19); + enableTimer(14); + enableTimer(18); + } + + if (reinitScript) { + _scriptInterpreter->initScript(script, script->dataPtr); + } + + if (_sceneChangeState) { + _sceneChangeState = 0; + return 1; + } + return 0; +} + +int KyraEngine::cmd_dropItemInScene(ScriptState *script) { + debug(3, "cmd_dropItemInScene(0x%X) (%d, %d, %d)", script, stackPos(0), stackPos(1), stackPos(2)); + int item = stackPos(0); + int xpos = stackPos(1); + int ypos = stackPos(2); + + byte freeItem = findFreeItemInScene(_currentCharacter->sceneId); + if (freeItem != 0xFF) { + int sceneId = _currentCharacter->sceneId; + Room *room = &_roomTable[sceneId]; + room->itemsXPos[freeItem] = xpos; + room->itemsYPos[freeItem] = ypos; + room->itemsTable[freeItem] = item; + + _animator->animAddGameItem(freeItem, sceneId); + _animator->updateAllObjectShapes(); + } else { + if (item == 43) { + placeItemInGenericMapScene(item, 0); + } else { + placeItemInGenericMapScene(item, 1); + } + } + return 0; +} + +int KyraEngine::cmd_drawAnimShapeIntoScene(ScriptState *script) { + debug(3, "cmd_drawAnimShapeIntoScene(0x%X) (%d, %d, %d, %d)", stackPos(0), stackPos(1), stackPos(2), stackPos(3)); + _screen->hideMouse(); + _animator->restoreAllObjectBackgrounds(); + int shape = stackPos(0); + int xpos = stackPos(1); + int ypos = stackPos(2); + int flags = (stackPos(3) != 0) ? 1 : 0; + _screen->drawShape(2, _sprites->_sceneShapes[shape], xpos, ypos, 0, flags); + _screen->drawShape(0, _sprites->_sceneShapes[shape], xpos, ypos, 0, flags); + _animator->flagAllObjectsForBkgdChange(); + _animator->preserveAnyChangedBackgrounds(); + _animator->flagAllObjectsForRefresh(); + _animator->updateAllObjectShapes(); + _screen->showMouse(); + return 0; +} + +int KyraEngine::cmd_createMouseItem(ScriptState *script) { + debug(3, "cmd_createMouseItem(0x%X) (%d)", script, stackPos(0)); + createMouseItem(stackPos(0)); + return 0; +} + +int KyraEngine::cmd_savePageToDisk(ScriptState *script) { + debug(3, "cmd_savePageToDisk(0x%X) ('%s', %d)", script, stackPosString(0), stackPos(1)); + _screen->savePageToDisk(stackPosString(0), stackPos(1)); + return 0; +} + +int KyraEngine::cmd_sceneAnimOn(ScriptState *script) { + debug(3, "cmd_sceneAnimOn(0x%X) (%d)", script, stackPos(0)); + _sprites->_anims[stackPos(0)].play = true; + return 0; +} + +int KyraEngine::cmd_sceneAnimOff(ScriptState *script) { + debug(3, "cmd_sceneAnimOff(0x%X) (%d)", script, stackPos(0)); + _sprites->_anims[stackPos(0)].play = false; + return 0; +} + +int KyraEngine::cmd_getElapsedSeconds(ScriptState *script) { + debug(3, "cmd_getElapsedSeconds(0x%X) ()", script); + return _system->getMillis() / 1000; +} + +int KyraEngine::cmd_mouseIsPointer(ScriptState *script) { + debug(3, "cmd_mouseIsPointer(0x%X) ()", script); + if (_itemInHand == -1) { + return 1; + } + return 0; +} + +int KyraEngine::cmd_destroyMouseItem(ScriptState *script) { + debug(3, "cmd_destroyMouseItem(0x%X) ()", script); + destroyMouseItem(); + return 0; +} + +int KyraEngine::cmd_runSceneAnimUntilDone(ScriptState *script) { + debug(3, "cmd_runSceneAnimUntilDone(0x%X) (%d)", script, stackPos(0)); + _screen->hideMouse(); + _animator->restoreAllObjectBackgrounds(); + _sprites->_anims[stackPos(0)].play = true; + _animator->sprites()[stackPos(0)].active = 1; + _animator->flagAllObjectsForBkgdChange(); + _animator->preserveAnyChangedBackgrounds(); + while (_sprites->_anims[stackPos(0)].play) { + _sprites->updateSceneAnims(); + _animator->updateAllObjectShapes(); + delay(10); + } + _animator->restoreAllObjectBackgrounds(); + _screen->showMouse(); + return 0; +} + +int KyraEngine::cmd_fadeSpecialPalette(ScriptState *script) { + debug(3, "cmd_fadeSpecialPalette(0x%X) (%d, %d, %d, %d)", script, stackPos(0), stackPos(1), stackPos(2), stackPos(3)); + _screen->fadeSpecialPalette(stackPos(0), stackPos(1), stackPos(2), stackPos(3)); + return 0; +} + +int KyraEngine::cmd_playAdlibSound(ScriptState *script) { + debug(3, "cmd_playAdlibSound(0x%X) (%d)", script, stackPos(0)); + snd_playSoundEffect(stackPos(0)); + return 0; +} + +int KyraEngine::cmd_playAdlibScore(ScriptState *script) { + debug(3, "cmd_playAdlibScore(0x%X) (%d, %d)", script, stackPos(0), stackPos(1)); + snd_playWanderScoreViaMap(stackPos(0), stackPos(1)); + return 0; +} + +int KyraEngine::cmd_phaseInSameScene(ScriptState *script) { + debug(3, "cmd_phaseInSameScene(0x%X) (%d, %d)", script, stackPos(0), stackPos(1)); + transcendScenes(stackPos(0), stackPos(1)); + return 0; +} + +int KyraEngine::cmd_setScenePhasingFlag(ScriptState *script) { + debug(3, "cmd_setScenePhasingFlag(0x%X) ()", script); + _scenePhasingFlag = 1; + return 1; +} + +int KyraEngine::cmd_resetScenePhasingFlag(ScriptState *script) { + debug(3, "cmd_resetScenePhasingFlag(0x%X) ()", script); + _scenePhasingFlag = 0; + return 0; +} + +int KyraEngine::cmd_queryScenePhasingFlag(ScriptState *script) { + debug(3, "cmd_queryScenePhasingFlag(0x%X) ()", script); + return _scenePhasingFlag; +} + +int KyraEngine::cmd_sceneToDirection(ScriptState *script) { + debug(3, "cmd_sceneToDirection(0x%X) (%d, %d)", script, stackPos(0), stackPos(1)); + assert(stackPos(0) < _roomTableSize); + Room *curRoom = &_roomTable[stackPos(0)]; + uint16 returnValue = 0xFFFF; + switch (stackPos(1)) { + case 0: + returnValue = curRoom->northExit; + break; + + case 2: + returnValue = curRoom->eastExit; + break; + + case 4: + returnValue = curRoom->southExit; + break; + + case 6: + returnValue = curRoom->westExit; + break; + + default: + break; + } + if (returnValue == 0xFFFF) + return -1; + return returnValue; +} + +int KyraEngine::cmd_setBirthstoneGem(ScriptState *script) { + debug(3, "cmd_setBirthstoneGem(0x%X) (%d, %d)", script, stackPos(0), stackPos(1)); + int index = stackPos(0); + if (index < 4 && index >= 0) { + _birthstoneGemTable[index] = stackPos(1); + return 1; + } + return 0; +} + +int KyraEngine::cmd_placeItemInGenericMapScene(ScriptState *script) { + debug(3, "cmd_placeItemInGenericMapScene(0x%X) (%d, %d)", script, stackPos(0), stackPos(1)); + placeItemInGenericMapScene(stackPos(0), stackPos(1)); + return 0; +} + +int KyraEngine::cmd_setBrandonStatusBit(ScriptState *script) { + debug(3, "cmd_setBrandonStatusBit(0x%X) (%d)", script, stackPos(0)); + _brandonStatusBit |= stackPos(0); + return 0; +} + +int KyraEngine::cmd_pauseSeconds(ScriptState *script) { + debug(3, "cmd_pauseSeconds(0x%X) (%d)", script, stackPos(0)); + if (stackPos(0) > 0 && !_skipFlag) + delay(stackPos(0)*1000, true); + _skipFlag = false; + return 0; +} + +int KyraEngine::cmd_getCharactersLocation(ScriptState *script) { + debug(3, "cmd_getCharactersLocation(0x%X) (%d)", script, stackPos(0)); + return _characterList[stackPos(0)].sceneId; +} + +int KyraEngine::cmd_runNPCSubscript(ScriptState *script) { + warning("STUB: cmd_runNPCSubscript"); + return 0; +} + +int KyraEngine::cmd_magicOutMouseItem(ScriptState *script) { + debug(3, "cmd_magicOutMouseItem(0x%X) (%d)", script, stackPos(0)); + magicOutMouseItem(stackPos(0), -1); + return 0; +} + +int KyraEngine::cmd_internalAnimOn(ScriptState *script) { + debug(3, "cmd_internalAnimOn(0x%X) (%d)", script, stackPos(0)); + _animator->sprites()[stackPos(0)].active = 1; + return 0; +} + +int KyraEngine::cmd_forceBrandonToNormal(ScriptState *script) { + debug(3, "cmd_forceBrandonToNormal(0x%X) ()", script); + checkAmuletAnimFlags(); + return 0; +} + +int KyraEngine::cmd_poisonDeathNow(ScriptState *script) { + debug(3, "cmd_poisonDeathNow(0x%X) ()", script); + seq_poisonDeathNow(1); + return 0; +} + +int KyraEngine::cmd_setScaleMode(ScriptState *script) { + debug(3, "cmd_setScaleMode(0x%X) (%d, %d, %d, %d)", script, stackPos(0), stackPos(1), stackPos(2), stackPos(3)); + int len = stackPos(0); + int setValue1 = stackPos(1); + int start2 = stackPos(2); + int setValue2 = stackPos(3); + for (int i = 0; i < len; ++i) { + _scaleTable[i] = setValue1; + } + int temp = setValue2 - setValue1; + int temp2 = start2 - len; + for (int i = len, offset = 0; i < start2; ++i, ++offset) { + _scaleTable[i] = (offset * temp) / temp2 + setValue1; + } + for (int i = start2; i < 145; ++i) { + _scaleTable[i] = setValue2; + } + _scaleMode = 1; + return _scaleMode; +} + +int KyraEngine::cmd_openWSAFile(ScriptState *script) { + debug(3, "cmd_openWSAFile(0x%X) ('%s', %d, %d)", script, stackPosString(0), stackPos(1), stackPos(2)); + + char *filename = stackPosString(0); + int wsaIndex = stackPos(1); + + _movieObjects[wsaIndex]->open(filename, (stackPos(2) != 0) ? 1 : 0, 0); + assert(_movieObjects[wsaIndex]->opened()); + + return 0; +} + +int KyraEngine::cmd_closeWSAFile(ScriptState *script) { + debug(3, "cmd_closeWSAFile(0x%X) (%d)", script, stackPos(0)); + + int wsaIndex = stackPos(0); + if (_movieObjects[wsaIndex]) { + _movieObjects[wsaIndex]->close(); + } + + return 0; +} + +int KyraEngine::cmd_runWSAFromBeginningToEnd(ScriptState *script) { + debug(3, "cmd_runWSAFromBeginningToEnd(0x%X) (%d, %d, %d, %d, %d)", script, stackPos(0), stackPos(1), stackPos(2), stackPos(3), stackPos(4)); + + _screen->hideMouse(); + + bool running = true; + + int xpos = stackPos(0); + int ypos = stackPos(1); + int waitTime = stackPos(2); + int wsaIndex = stackPos(3); + int worldUpdate = stackPos(4); + int wsaFrame = 0; + + _movieObjects[wsaIndex]->_x = xpos; + _movieObjects[wsaIndex]->_y = ypos; + _movieObjects[wsaIndex]->_drawPage = 0; + while (running) { + _movieObjects[wsaIndex]->displayFrame(wsaFrame++); + _animator->_updateScreen = true; + if (wsaFrame >= _movieObjects[wsaIndex]->frames()) + running = false; + + uint32 continueTime = waitTime * _tickLength + _system->getMillis(); + while (_system->getMillis() < continueTime) { + if (worldUpdate) { + _sprites->updateSceneAnims(); + _animator->updateAllObjectShapes(); + } else { + _screen->updateScreen(); + } + if (continueTime - _system->getMillis() >= 10) + delay(10); + } + } + + _screen->showMouse(); + + return 0; +} + +int KyraEngine::cmd_displayWSAFrame(ScriptState *script) { + debug(3, "cmd_displayWSAFrame(0x%X) (%d, %d, %d, %d, %d)", script, stackPos(0), stackPos(1), stackPos(2), stackPos(3), stackPos(4)); + int frame = stackPos(0); + int xpos = stackPos(1); + int ypos = stackPos(2); + int waitTime = stackPos(3); + int wsaIndex = stackPos(4); + _screen->hideMouse(); + _movieObjects[wsaIndex]->_x = xpos; + _movieObjects[wsaIndex]->_y = ypos; + _movieObjects[wsaIndex]->_drawPage = 0; + _movieObjects[wsaIndex]->displayFrame(frame); + _animator->_updateScreen = true; + uint32 continueTime = waitTime * _tickLength + _system->getMillis(); + while (_system->getMillis() < continueTime && !_skipFlag) { + _sprites->updateSceneAnims(); + _animator->updateAllObjectShapes(); + if (continueTime - _system->getMillis() >= 10) + delay(10); + } + _screen->showMouse(); + return 0; +} + +int KyraEngine::cmd_enterNewScene(ScriptState *script) { + debug(3, "cmd_enterNewScene(0x%X) (%d, %d, %d, %d, %d)", script, stackPos(0), stackPos(1), stackPos(2), stackPos(3), stackPos(4)); + enterNewScene(stackPos(0), stackPos(1), stackPos(2), stackPos(3), stackPos(4)); + return 0; +} + +int KyraEngine::cmd_setSpecialEnterXAndY(ScriptState *script) { + debug(3, "cmd_setSpecialEnterXAndY(0x%X) (%d, %d)", script, stackPos(0), stackPos(1)); + _brandonPosX = stackPos(0); + _brandonPosY = stackPos(1); + if (_brandonPosX + 1 == 0 && _brandonPosY + 1 == 0) + _currentCharacter->currentAnimFrame = 88; + return 0; +} + +int KyraEngine::cmd_runWSAFrames(ScriptState *script) { + debug(3, "cmd_runWSAFrames(0x%X) (%d, %d, %d, %d, %d, %d)", script, stackPos(0), stackPos(1), stackPos(2), stackPos(3), stackPos(4), stackPos(5)); + int xpos = stackPos(0); + int ypos = stackPos(1); + int delayTime = stackPos(2); + int startFrame = stackPos(3); + int endFrame = stackPos(4); + int wsaIndex = stackPos(5); + _screen->hideMouse(); + _movieObjects[wsaIndex]->_x = xpos; + _movieObjects[wsaIndex]->_y = ypos; + _movieObjects[wsaIndex]->_drawPage = 0; + for (; startFrame <= endFrame; ++startFrame) { + uint32 nextRun = _system->getMillis() + delayTime * _tickLength; + _movieObjects[wsaIndex]->displayFrame(startFrame); + _animator->_updateScreen = true; + while (_system->getMillis() < nextRun) { + _sprites->updateSceneAnims(); + _animator->updateAllObjectShapes(); + if (nextRun - _system->getMillis() >= 10) + delay(10); + } + } + _screen->showMouse(); + return 0; +} + +int KyraEngine::cmd_popBrandonIntoScene(ScriptState *script) { + debug(3, "cmd_popBrandonIntoScene(0x%X) (%d, %d, %d, %d)", script, stackPos(0), stackPos(1), stackPos(2), stackPos(3)); + int changeScaleMode = stackPos(3); + int xpos = (int16)(stackPos(0) & 0xFFFC); + int ypos = (int16)(stackPos(1) & 0xFFFE); + int facing = stackPos(2); + _currentCharacter->x1 = _currentCharacter->x2 = xpos; + _currentCharacter->y1 = _currentCharacter->y2 = ypos; + _currentCharacter->facing = facing; + _currentCharacter->currentAnimFrame = 7; + int xOffset = _defaultShapeTable[0].xOffset; + int yOffset = _defaultShapeTable[0].yOffset; + int width = _defaultShapeTable[0].w << 3; + int height = _defaultShapeTable[0].h; + AnimObject *curAnim = _animator->actors(); + + if (changeScaleMode) { + curAnim->x1 = _currentCharacter->x1; + curAnim->y1 = _currentCharacter->y1; + _animator->_brandonScaleY = _scaleTable[_currentCharacter->y1]; + _animator->_brandonScaleX = _animator->_brandonScaleY; + + int animWidth = _animator->fetchAnimWidth(curAnim->sceneAnimPtr, _animator->_brandonScaleX) >> 1; + int animHeight = _animator->fetchAnimHeight(curAnim->sceneAnimPtr, _animator->_brandonScaleY); + + animWidth = (xOffset * animWidth) / width; + animHeight = (yOffset * animHeight) / height; + + curAnim->x2 = curAnim->x1 += animWidth; + curAnim->y2 = curAnim->y1 += animHeight; + } else { + curAnim->x2 = curAnim->x1 = _currentCharacter->x1 + xOffset; + curAnim->y2 = curAnim->y1 = _currentCharacter->y1 + yOffset; + } + + int scaleModeBackup = _scaleMode; + if (changeScaleMode) { + _scaleMode = 1; + } + + _animator->animRefreshNPC(0); + _animator->preserveAllBackgrounds(); + _animator->prepDrawAllObjects(); + _animator->copyChangedObjectsForward(0); + + _scaleMode = scaleModeBackup; + + return 0; +} + +int KyraEngine::cmd_restoreAllObjectBackgrounds(ScriptState *script) { + debug(3, "cmd_restoreAllObjectBackgrounds(0x%X) ()", script); + _animator->restoreAllObjectBackgrounds(); + return 0; +} + +int KyraEngine::cmd_setCustomPaletteRange(ScriptState *script) { + debug(3, "cmd_setCustomPaletteRange(0x%X) (%d, %d, %d)", script, stackPos(0), stackPos(1), stackPos(2)); + uint8 *screenPal = _screen->_currentPalette; + memcpy(&screenPal[stackPos(1)*3], _specialPalettes[stackPos(0)], stackPos(2)*3); + _screen->setScreenPalette(screenPal); + return 0; +} + +int KyraEngine::cmd_loadPageFromDisk(ScriptState *script) { + debug(3, "cmd_loadPageFromDisk(0x%X) ('%s', %d)", script, stackPosString(0), stackPos(1)); + _screen->loadPageFromDisk(stackPosString(0), stackPos(1)); + _animator->_updateScreen = true; + return 0; +} + +int KyraEngine::cmd_customPrintTalkString(ScriptState *script) { + if (_features & GF_TALKIE) { + debug(3, "cmd_customPrintTalkString(0x%X) (%d, '%s', %d, %d, %d)", script, stackPos(0), stackPosString(1), stackPos(2), stackPos(3), stackPos(4) & 0xFF); + snd_voiceWaitForFinish(); + snd_playVoiceFile(stackPos(0)); + _skipFlag = false; + _text->printTalkTextMessage(stackPosString(1), stackPos(2), stackPos(3), stackPos(4) & 0xFF, 0, 2); + } else { + debug(3, "cmd_customPrintTalkString(0x%X) ('%s', %d, %d, %d)", script, stackPosString(0), stackPos(1), stackPos(2), stackPos(3) & 0xFF); + _skipFlag = false; + _text->printTalkTextMessage(stackPosString(0), stackPos(1), stackPos(2), stackPos(3) & 0xFF, 0, 2); + } + _screen->updateScreen(); + return 0; +} + +int KyraEngine::cmd_restoreCustomPrintBackground(ScriptState *script) { + debug(3, "cmd_restoreCustomPrintBackground(0x%X) ()", script); + _text->restoreTalkTextMessageBkgd(2, 0); + return 0; +} + +int KyraEngine::cmd_hideMouse(ScriptState *script) { + debug(3, "cmd_hideMouse(0x%X) ()", script); + _screen->hideMouse(); + return 0; +} + +int KyraEngine::cmd_showMouse(ScriptState *script) { + debug(3, "cmd_showMouse(0x%X) ()", script); + _screen->showMouse(); + return 0; +} + +int KyraEngine::cmd_getCharacterX(ScriptState *script) { + debug(3, "cmd_getCharacterX(0x%X) (%d)", script, stackPos(0)); + return _characterList[stackPos(0)].x1; +} + +int KyraEngine::cmd_getCharacterY(ScriptState *script) { + debug(3, "cmd_getCharacterY(0x%X) (%d)", script, stackPos(0)); + return _characterList[stackPos(0)].y1; +} + +int KyraEngine::cmd_changeCharactersFacing(ScriptState *script) { + debug(3, "cmd_changeCharactersFacing(0x%X) (%d, %d, %d)", script, stackPos(0), stackPos(1), stackPos(2)); + int character = stackPos(0); + int facing = stackPos(1); + int newAnimFrame = stackPos(2); + + _animator->restoreAllObjectBackgrounds(); + if (newAnimFrame != -1) { + _characterList[character].currentAnimFrame = newAnimFrame; + } + _characterList[character].facing = facing; + _animator->animRefreshNPC(character); + _animator->preserveAllBackgrounds(); + _animator->prepDrawAllObjects(); + _animator->copyChangedObjectsForward(0); + + return 0; +} + +int KyraEngine::cmd_copyWSARegion(ScriptState *script) { + debug(3, "cmd_copyWSARegion(0x%X) (%d, %d, %d, %d, %d, %d)", script, stackPos(0), stackPos(1), stackPos(2), stackPos(3), stackPos(4), stackPos(5)); + int xpos = stackPos(0); + int ypos = stackPos(1); + int width = stackPos(2); + int height = stackPos(3); + int srcPage = stackPos(4); + int dstPage = stackPos(5); + _screen->copyRegion(xpos, ypos, xpos, ypos, width, height, srcPage, dstPage); + _animator->_updateScreen = true; + return 0; +} + +int KyraEngine::cmd_printText(ScriptState *script) { + debug(3, "cmd_printText(0x%X) ('%s', %d, %d, 0x%X, 0x%X)", script, stackPosString(0), stackPos(1), stackPos(2), stackPos(3), stackPos(4)); + _screen->printText(stackPosString(0), stackPos(1), stackPos(2), stackPos(3), stackPos(4)); + _screen->updateScreen(); + return 0; +} + +int KyraEngine::cmd_random(ScriptState *script) { + debug(3, "cmd_random(0x%X) (%d, %d)", script, stackPos(0), stackPos(1)); + assert(stackPos(0) < stackPos(1)); + return _rnd.getRandomNumberRng(stackPos(0), stackPos(1)); +} + +int KyraEngine::cmd_loadSoundFile(ScriptState *script) { + warning("STUB: cmd_loadSoundFile"); + return 0; +} + +int KyraEngine::cmd_displayWSAFrameOnHidPage(ScriptState *script) { + debug(3, "cmd_displayWSAFrameOnHidPage(0x%X) (%d, %d, %d, %d, %d)", script, stackPos(0), stackPos(1), stackPos(2), stackPos(3), stackPos(4)); + int frame = stackPos(0); + int xpos = stackPos(1); + int ypos = stackPos(2); + int waitTime = stackPos(3); + int wsaIndex = stackPos(4); + + _screen->hideMouse(); + uint32 continueTime = waitTime * _tickLength + _system->getMillis(); + _movieObjects[wsaIndex]->_x = xpos; + _movieObjects[wsaIndex]->_y = ypos; + _movieObjects[wsaIndex]->_drawPage = 2; + _movieObjects[wsaIndex]->displayFrame(frame); + _animator->_updateScreen = true; + while (_system->getMillis() < continueTime) { + _sprites->updateSceneAnims(); + _animator->updateAllObjectShapes(); + if (continueTime - _system->getMillis() >= 10) + delay(10); + } + _screen->showMouse(); + + return 0; +} + +int KyraEngine::cmd_displayWSASequentialFrames(ScriptState *script) { + debug(3, "cmd_displayWSASequentialFrames(0x%X) (%d, %d, %d, %d, %d, %d, %d)", script, stackPos(0), stackPos(1), stackPos(2), stackPos(3), stackPos(4), stackPos(5), stackPos(6)); + int startFrame = stackPos(0); + int endFrame = stackPos(1); + int xpos = stackPos(2); + int ypos = stackPos(3); + int waitTime = stackPos(4); + int wsaIndex = stackPos(5); + int maxTime = stackPos(6); + if (maxTime - 1 <= 0) + maxTime = 1; + + _movieObjects[wsaIndex]->_x = xpos; + _movieObjects[wsaIndex]->_y = ypos; + _movieObjects[wsaIndex]->_drawPage = 0; + + int curTime = 0; + _screen->hideMouse(); + while (curTime < maxTime) { + if (endFrame >= startFrame) { + int frame = startFrame; + while (endFrame >= frame) { + _movieObjects[wsaIndex]->displayFrame(frame); + _animator->_updateScreen = true; + uint32 continueTime = waitTime * _tickLength + _system->getMillis(); + while (_system->getMillis() < continueTime && !_skipFlag) { + _sprites->updateSceneAnims(); + _animator->updateAllObjectShapes(); + if (continueTime - _system->getMillis() >= 10) + delay(10); + } + ++frame; + } + } else { + int frame = startFrame; + while (endFrame <= frame) { + _movieObjects[wsaIndex]->displayFrame(frame); + _animator->_updateScreen = true; + uint32 continueTime = waitTime * _tickLength + _system->getMillis(); + while (_system->getMillis() < continueTime && !_skipFlag) { + _sprites->updateSceneAnims(); + _animator->updateAllObjectShapes(); + if (continueTime - _system->getMillis() >= 10) + delay(10); + } + --frame; + } + } + if (!_skipFlag) + break; + else + ++curTime; + } + _screen->showMouse(); + + return 0; +} + +int KyraEngine::cmd_drawCharacterStanding(ScriptState *script) { + debug(3, "cmd_drawCharacterStanding(0x%X) (%d, %d, %d, %d)", script, stackPos(0), stackPos(1), stackPos(2), stackPos(3)); + int character = stackPos(0); + int animFrame = stackPos(1); + int newFacing = stackPos(2); + int updateShapes = stackPos(3); + _characterList[character].currentAnimFrame = animFrame; + if (newFacing != -1) { + _characterList[character].facing = newFacing; + } + _animator->animRefreshNPC(character); + if (updateShapes) { + _animator->updateAllObjectShapes(); + } + return 0; +} + +int KyraEngine::cmd_internalAnimOff(ScriptState *script) { + debug(3, "cmd_internalAnimOff(0x%X) (%d)", script, stackPos(0)); + _animator->sprites()[stackPos(0)].active = 0; + return 0; +} + +int KyraEngine::cmd_changeCharactersXAndY(ScriptState *script) { + debug(3, "cmd_changeCharactersXAndY(0x%X) (%d, %d, %d)", script, stackPos(0), stackPos(1), stackPos(2)); + Character *ch = &_characterList[stackPos(0)]; + int16 x = stackPos(1); + int16 y = stackPos(2); + if (x != -1 && y != -1) { + x &= 0xFFFC; + y &= 0xFFFE; + } + _animator->restoreAllObjectBackgrounds(); + ch->x1 = ch->x2 = x; + ch->y1 = ch->y2 = y; + _animator->preserveAllBackgrounds(); + return 0; +} + +int KyraEngine::cmd_clearSceneAnimatorBeacon(ScriptState *script) { + debug(3, "cmd_clearSceneAnimatorBeacon(0x%X) ()", script); + _sprites->_sceneAnimatorBeaconFlag = 0; + return 0; +} + +int KyraEngine::cmd_querySceneAnimatorBeacon(ScriptState *script) { + debug(3, "cmd_querySceneAnimatorBeacon(0x%X) ()", script); + return _sprites->_sceneAnimatorBeaconFlag; +} + +int KyraEngine::cmd_refreshSceneAnimator(ScriptState *script) { + debug(3, "cmd_refreshSceneAnimator(0x%X) ()", script); + _sprites->updateSceneAnims(); + _animator->updateAllObjectShapes(); + return 0; +} + +int KyraEngine::cmd_placeItemInOffScene(ScriptState *script) { + debug(3, "cmd_placeItemInOffScene(0x%X) (%d, %d, %d, %d)", script, stackPos(0), stackPos(1), stackPos(2), stackPos(3)); + int item = stackPos(0); + int xpos = stackPos(1); + int ypos = stackPos(2); + int sceneId = stackPos(3); + + byte freeItem = findFreeItemInScene(sceneId); + if (freeItem != 0xFF) { + assert(sceneId < _roomTableSize); + Room *room = &_roomTable[sceneId]; + + room->itemsTable[freeItem] = item; + room->itemsXPos[freeItem] = xpos; + room->itemsYPos[freeItem] = ypos; + } + return 0; +} + +int KyraEngine::cmd_wipeDownMouseItem(ScriptState *script) { + debug(3, "cmd_wipeDownMouseItem(0x%X) (%d, %d, %d)", script, stackPos(0), stackPos(1), stackPos(2)); + _screen->hideMouse(); + wipeDownMouseItem(stackPos(1), stackPos(2)); + destroyMouseItem(); + _screen->showMouse(); + return 0; +} + +int KyraEngine::cmd_placeCharacterInOtherScene(ScriptState *script) { + debug(3, "cmd_placeCharacterInOtherScene(0x%X) (%d, %d, %d, %d, %d, %d)", script, stackPos(0), stackPos(1), stackPos(2), stackPos(3), stackPos(4), stackPos(5)); + int id = stackPos(0); + int sceneId = stackPos(1); + int xpos = (int16)(stackPos(2) & 0xFFFC); + int ypos = (int16)(stackPos(3) & 0xFFFE); + int facing = stackPos(4); + int animFrame = stackPos(5); + + _characterList[id].sceneId = sceneId; + _characterList[id].x1 = _characterList[id].x2 = xpos; + _characterList[id].y1 = _characterList[id].y2 = ypos; + _characterList[id].facing = facing; + _characterList[id].currentAnimFrame = animFrame; + return 0; +} + +int KyraEngine::cmd_getKey(ScriptState *script) { + debug(3, "cmd_getKey(0x%X) ()", script); + waitForEvent(); + return 0; +} + +int KyraEngine::cmd_specificItemInInventory(ScriptState *script) { + warning("STUB: cmd_specificItemInInventory"); + return 0; +} + +int KyraEngine::cmd_popMobileNPCIntoScene(ScriptState *script) { + debug(3, "cmd_popMobileNPCIntoScene(0x%X) (%d, %d, %d, %d, %d, %d)", script, stackPos(0), stackPos(1), stackPos(2), stackPos(3), (int16)(stackPos(4) & 0xFFFC), (int8)(stackPos(5) & 0xFE)); + int character = stackPos(0); + int sceneId = stackPos(1); + int animFrame = stackPos(2); + int facing = stackPos(3); + int16 xpos = (int16)(stackPos(4) & 0xFFFC); + int8 ypos = (int16)(stackPos(5) & 0xFFFE); + Character *curChar = &_characterList[character]; + + curChar->sceneId = sceneId; + curChar->currentAnimFrame = animFrame; + curChar->facing = facing; + curChar->x1 = curChar->x2 = xpos; + curChar->y1 = curChar->y2 = ypos; + + _animator->animAddNPC(character); + _animator->updateAllObjectShapes(); + return 0; +} + +int KyraEngine::cmd_mobileCharacterInScene(ScriptState *script) { + warning("STUB: cmd_mobileCharacterInScene"); + return 0; +} + +int KyraEngine::cmd_hideMobileCharacter(ScriptState *script) { + warning("STUB: cmd_hideMobileCharacter"); + return 0; +} + +int KyraEngine::cmd_unhideMobileCharacter(ScriptState *script) { + warning("STUB: cmd_unhideMobileCharacter"); + return 0; +} + +int KyraEngine::cmd_setCharactersLocation(ScriptState *script) { + debug(3, "cmd_setCharactersLocation(0x%X) (%d, %d)", script, stackPos(0), stackPos(1)); + Character *ch = &_characterList[stackPos(0)]; + AnimObject *animObj = &_animator->actors()[stackPos(0)]; + int newScene = stackPos(1); + if (_currentCharacter->sceneId == ch->sceneId) { + if (_currentCharacter->sceneId != newScene) + animObj->active = 0; + } else if (_currentCharacter->sceneId == newScene) { + if (_currentCharacter->sceneId != ch->sceneId) + animObj->active = 1; + } + + ch->sceneId = stackPos(1); + return 0; +} + +int KyraEngine::cmd_walkCharacterToPoint(ScriptState *script) { + debug(3, "cmd_walkCharacterToPoint(0x%X) (%d, %d, %d)", script, stackPos(0), stackPos(1), stackPos(2)); + int character = stackPos(0); + int toX = stackPos(1); + int toY = stackPos(2); + _pathfinderFlag2 = 1; + uint32 nextFrame; + int findWayReturn = findWay(_characterList[character].x1, _characterList[character].y1, toX, toY, _movFacingTable, 150); + _pathfinderFlag2 = 0; + if (_lastFindWayRet < findWayReturn) { + _lastFindWayRet = findWayReturn; + } + if (findWayReturn == 0x7D00 || findWayReturn == 0) { + return 0; + } + int *curPos = _movFacingTable; + bool running = true; + while (running) { + bool forceContinue = false; + switch (*curPos) { + case 0: + _characterList[character].facing = 2; + break; + + case 1: + _characterList[character].facing = 1; + break; + + case 2: + _characterList[character].facing = 0; + break; + + case 3: + _characterList[character].facing = 7; + break; + + case 4: + _characterList[character].facing = 6; + break; + + case 5: + _characterList[character].facing = 5; + break; + + case 6: + _characterList[character].facing = 4; + break; + + case 7: + _characterList[character].facing = 3; + break; + + case 8: + running = 0; + break; + + default: + ++curPos; + forceContinue = true; + break; + } + + if (forceContinue || !running) { + continue; + } + + setCharacterPosition(character, 0); + ++curPos; + + nextFrame = getTimerDelay(5 + character) * _tickLength + _system->getMillis(); + while (_system->getMillis() < nextFrame) { + _sprites->updateSceneAnims(); + updateMousePointer(); + updateGameTimers(); + _animator->updateAllObjectShapes(); + updateTextFade(); + if ((nextFrame - _system->getMillis()) >= 10) + delay(10); + } + } + return 0; +} + +int KyraEngine::cmd_specialEventDisplayBrynnsNote(ScriptState *script) { + debug(3, "cmd_specialEventDisplayBrynnsNote(0x%X) ()", script); + _screen->hideMouse(); + _screen->savePageToDisk("HIDPAGE.TMP", 2); + _screen->savePageToDisk("SEENPAGE.TMP", 0); + if (_features & GF_TALKIE) { + if (_features & GF_ENGLISH) { + loadBitmap("NOTEENG.CPS", 3, 3, 0); + } else if (_features & GF_FRENCH) { + loadBitmap("NOTEFRE.CPS", 3, 3, 0); + } else if (_features & GF_GERMAN) { + loadBitmap("NOTEGER.CPS", 3, 3, 0); + } + } else { + loadBitmap("NOTE.CPS", 3, 3, 0); + } + _screen->copyRegion(63, 8, 63, 8, 194, 128, 2, 0); + _screen->updateScreen(); + _screen->showMouse(); + _screen->setFont(Screen::FID_6_FNT); + return 0; +} + +int KyraEngine::cmd_specialEventRemoveBrynnsNote(ScriptState *script) { + debug(3, "cmd_specialEventRemoveBrynnsNote(0x%X) ()", script); + _screen->hideMouse(); + _screen->loadPageFromDisk("SEENPAGE.TMP", 0); + _screen->loadPageFromDisk("HIDPAGE.TMP", 2); + _screen->updateScreen(); + _screen->showMouse(); + _screen->setFont(Screen::FID_8_FNT); + return 0; +} + +int KyraEngine::cmd_setLogicPage(ScriptState *script) { + debug(3, "cmd_setLogicPage(0x%X) (%d)", script, stackPos(0)); + _screen->_curPage = stackPos(0); + return stackPos(0); +} + +int KyraEngine::cmd_fatPrint(ScriptState *script) { + debug(3, "cmd_fatPrint(0x%X) ('%s', %d, %d, %d, %d, %d)", script, stackPosString(0), stackPos(1), stackPos(2), stackPos(3), stackPos(4), stackPos(5)); + _text->printText(stackPosString(0), stackPos(1), stackPos(2), stackPos(3), stackPos(4), stackPos(5)); + return 0; +} + +int KyraEngine::cmd_preserveAllObjectBackgrounds(ScriptState *script) { + debug(3, "cmd_preserveAllObjectBackgrounds(0x%X) ()", script); + _animator->preserveAllBackgrounds(); + return 0; +} + +int KyraEngine::cmd_updateSceneAnimations(ScriptState *script) { + debug(3, "cmd_updateSceneAnimations(0x%X) (%d)", script, stackPos(0)); + if (stackPos(0)) { + _sprites->updateSceneAnims(); + _animator->updateAllObjectShapes(); + } + return 0; +} + +int KyraEngine::cmd_sceneAnimationActive(ScriptState *script) { + debug(3, "cmd_sceneAnimationActive(0x%X) (%d)", script, stackPos(0)); + return _sprites->_anims[stackPos(0)].play; +} + +int KyraEngine::cmd_setCharactersMovementDelay(ScriptState *script) { + debug(3, "cmd_setCharactersMovementDelay(0x%X) (%d, %d)", script, stackPos(0), stackPos(1)); + setTimerDelay(stackPos(0)+5, stackPos(1)); + return 0; +} + +int KyraEngine::cmd_getCharactersFacing(ScriptState *script) { + debug(3, "cmd_getCharactersFacing(0x%X) (%d)", script, stackPos(0)); + return _characterList[stackPos(0)].facing; +} + +int KyraEngine::cmd_bkgdScrollSceneAndMasksRight(ScriptState *script) { + debug(3, "cmd_bkgdScrollSceneAndMasksRight(0x%X) (%d)", script, stackPos(0)); + _screen->copyBackgroundBlock(stackPos(0), 2, 0); + _screen->copyBackgroundBlock2(stackPos(0)); + // update the whole screen + _screen->copyRegion(7, 7, 7, 7, 305, 129, 3, 0); + _screen->updateScreen(); + return 0; +} + +int KyraEngine::cmd_dispelMagicAnimation(ScriptState *script) { + debug(3, "cmd_dispelMagicAnimation(0x%X) ()", script); + seq_dispelMagicAnimation(); + return 0; +} + +int KyraEngine::cmd_findBrightestFireberry(ScriptState *script) { + debug(3, "cmd_findBrightestFireberry(0x%X) ()", script); + if (_currentCharacter->sceneId >= 187 && _currentCharacter->sceneId <= 198) { + return 29; + } + if (_currentCharacter->sceneId == 133 || _currentCharacter->sceneId == 137 || + _currentCharacter->sceneId == 165 || _currentCharacter->sceneId == 173) { + return 29; + } + if (_itemInHand == 28) + return 28; + int brightestFireberry = 107; + if (_itemInHand >= 29 && _itemInHand <= 33) + brightestFireberry = _itemInHand; + for (int i = 0; i < 10; ++i) { + uint8 item = _currentCharacter->inventoryItems[i]; + if (item == 0xFF) + continue; + if (item == 28) + return 28; + if (item >= 29 && item <= 33) { + if (item < brightestFireberry) + brightestFireberry = item; + } + } + assert(_currentCharacter->sceneId < _roomTableSize); + Room *curRoom = &_roomTable[_currentCharacter->sceneId]; + for (int i = 0; i < 12; ++i) { + uint8 item = curRoom->itemsTable[i]; + if (item == 0xFF) + continue; + if (item == 28) + return 28; + if (item >= 29 && item <= 33) { + if (item < brightestFireberry) + brightestFireberry = item; + } + } + if (brightestFireberry == 107) + return -1; + return brightestFireberry; +} + +int KyraEngine::cmd_setFireberryGlowPalette(ScriptState *script) { + debug(3, "cmd_setFireberryGlowPalette(0x%X) (%d)", script, stackPos(0)); + int palIndex = 0; + switch (stackPos(0)) { + case 0x1E: + palIndex = 9; + break; + + case 0x1F: + palIndex = 10; + break; + + case 0x20: + palIndex = 11; + break; + + case 0x21: + case -1: + palIndex = 12; + break; + + default: + palIndex = 8; + break; + } + if (_brandonStatusBit & 2) { + if (_currentCharacter->sceneId != 133 && _currentCharacter->sceneId != 137 && + _currentCharacter->sceneId != 165 && _currentCharacter->sceneId != 173 && + (_currentCharacter->sceneId < 187 || _currentCharacter->sceneId > 198)) { + palIndex = 14; + } + } + uint8 *palette = _specialPalettes[palIndex]; + memcpy(&_screen->_currentPalette[684], palette, 44); + _screen->setScreenPalette(_screen->_currentPalette); + return 0; +} + +int KyraEngine::cmd_setDeathHandlerFlag(ScriptState *script) { + debug(3, "cmd_setDeathHandlerFlag(0x%X) (%d)", script, stackPos(0)); + _deathHandler = stackPos(0); + return 0; +} + +int KyraEngine::cmd_drinkPotionAnimation(ScriptState *script) { + debug(3, "cmd_drinkPotionAnimation(0x%X) (%d, %d, %d)", script, stackPos(0), stackPos(1), stackPos(2)); + seq_playDrinkPotionAnim(stackPos(0), stackPos(1), stackPos(2)); + return 0; +} + +int KyraEngine::cmd_makeAmuletAppear(ScriptState *script) { + debug(3, "cmd_makeAmuletAppear(0x%X) ()", script); + WSAMovieV1 amulet(this); + amulet.open("AMULET.WSA", 1, 0); + amulet._drawPage = 0; + amulet._x = 224; + amulet._y = 152; + if (amulet.opened()) { + assert(_amuleteAnim); + _screen->hideMouse(); + snd_playSoundEffect(0x70); + uint32 nextTime = 0; + for (int i = 0; _amuleteAnim[i] != 0xFF; ++i) { + nextTime = _system->getMillis() + 5 * _tickLength; + + uint8 code = _amuleteAnim[i]; + if (code == 3 || code == 7) { + snd_playSoundEffect(0x71); + } + + if (code == 5) { + snd_playSoundEffect(0x72); + } + + if (code == 14) { + snd_playSoundEffect(0x73); + } + + + amulet.displayFrame(code); + _animator->_updateScreen = true; + + while (_system->getMillis() < nextTime) { + _sprites->updateSceneAnims(); + _animator->updateAllObjectShapes(); + if (nextTime - _system->getMillis() >= 10) + delay(10); + } + } + _screen->showMouse(); + } + setGameFlag(0x2D); + return 0; +} + +int KyraEngine::cmd_drawItemShapeIntoScene(ScriptState *script) { + debug(3, "cmd_drawItemShapeIntoScene(0x%X) (%d, %d, %d, %d, %d)", script, stackPos(0), stackPos(1), stackPos(2), stackPos(3), stackPos(4)); + int item = stackPos(0); + int x = stackPos(1); + int y = stackPos(2); + int flags = stackPos(3); + int onlyHidPage = stackPos(4); + if (flags) + flags = 1; + if (onlyHidPage) { + _screen->drawShape(2, _shapes[220+item], x, y, 0, flags); + } else { + _screen->hideMouse(); + _animator->restoreAllObjectBackgrounds(); + _screen->drawShape(2, _shapes[220+item], x, y, 0, flags); + _screen->drawShape(0, _shapes[220+item], x, y, 0, flags); + _animator->flagAllObjectsForBkgdChange(); + _animator->flagAllObjectsForRefresh(); + _animator->updateAllObjectShapes(); + _screen->showMouse(); + } + return 0; +} + +int KyraEngine::cmd_setCharactersCurrentFrame(ScriptState *script) { + debug(3, "cmd_setCharactersCurrentFrame(0x%X) (%d, %d)", script, stackPos(0), stackPos(1)); + _characterList[stackPos(0)].currentAnimFrame = stackPos(1); + return 0; +} + +int KyraEngine::cmd_waitForConfirmationMouseClick(ScriptState *script) { + debug(3, "cmd_waitForConfirmationMouseClick(0x%X) ()", script); + // if (mouseEnabled) { + while (!_mousePressFlag) { + updateMousePointer(); + _sprites->updateSceneAnims(); + _animator->updateAllObjectShapes(); + delay(10); + } + + while (_mousePressFlag) { + updateMousePointer(); + _sprites->updateSceneAnims(); + _animator->updateAllObjectShapes(); + delay(10); + } + // } + // XXX processButtonList calls + script->variables[1] = _mouseX; + script->variables[2] = _mouseY; + return 0; +} + +int KyraEngine::cmd_pageFlip(ScriptState *script) { + warning("STUB: cmd_pageFlip"); + return 0; +} + +int KyraEngine::cmd_setSceneFile(ScriptState *script) { + debug(3, "cmd_setSceneFile(0x%X) (%d, %d)", script, stackPos(0), stackPos(1)); + setSceneFile(stackPos(0), stackPos(1)); + return 0; +} + +int KyraEngine::cmd_getItemInMarbleVase(ScriptState *script) { + debug(3, "cmd_getItemInMarbleVase(0x%X) ()", script); + return _marbleVaseItem; +} + +int KyraEngine::cmd_setItemInMarbleVase(ScriptState *script) { + debug(3, "cmd_setItemInMarbleVase(0x%X) (%d)", script, stackPos(0)); + _marbleVaseItem = stackPos(0); + return 0; +} + +int KyraEngine::cmd_addItemToInventory(ScriptState *script) { + warning("STUB: cmd_addItemToInventory"); + return 0; +} + +int KyraEngine::cmd_intPrint(ScriptState *script) { + warning("STUB: cmd_intPrint"); + return 0; +} + +int KyraEngine::cmd_shakeScreen(ScriptState *script) { + warning("STUB: cmd_shakeScreen"); + return 0; +} + +int KyraEngine::cmd_createAmuletJewel(ScriptState *script) { + debug(3, "cmd_createAmuletJewel(0x%X) (%d)", script, stackPos(0)); + seq_createAmuletJewel(stackPos(0), 0, 0, 0); + return 0; +} + +int KyraEngine::cmd_setSceneAnimCurrXY(ScriptState *script) { + debug(3, "cmd_setSceneAnimCurrXY(0x%X) (%d, %d, %d)", script, stackPos(0), stackPos(1), stackPos(2)); + _sprites->_anims[stackPos(0)].x = stackPos(1); + _sprites->_anims[stackPos(0)].y = stackPos(2); + return 0; +} + +int KyraEngine::cmd_poisonBrandonAndRemaps(ScriptState *script) { + debug(3, "cmd_poisonBrandonAndRemaps(0x%X) ()", script); + setBrandonPoisonFlags(1); + return 0; +} + +int KyraEngine::cmd_fillFlaskWithWater(ScriptState *script) { + debug(3, "cmd_fillFlaskWithWater(0x%X) (%d, %d)", script, stackPos(0), stackPos(1)); + seq_fillFlaskWithWater(stackPos(0), stackPos(1)); + return 0; +} + +int KyraEngine::cmd_getCharactersMovementDelay(ScriptState *script) { + debug(3, "cmd_getCharactersMovementDelay(0x%X) (%d)", script, stackPos(0)); + return getTimerDelay(stackPos(0)+5); +} + +int KyraEngine::cmd_getBirthstoneGem(ScriptState *script) { + debug(3, "cmd_getBirthstoneGem(0x%X) (%d)", script, stackPos(0)); + if (stackPos(0) < 4) { + return _birthstoneGemTable[stackPos(0)]; + } + return 0; +} + +int KyraEngine::cmd_queryBrandonStatusBit(ScriptState *script) { + debug(3, "cmd_queryBrandonStatusBit(0x%X) (%d)", script, stackPos(0)); + if (_brandonStatusBit & stackPos(0)) { + return 1; + } + return 0; +} + +int KyraEngine::cmd_playFluteAnimation(ScriptState *script) { + debug(3, "cmd_playFluteAnimation(0x%X) ()", script); + seq_playFluteAnimation(); + return 0; +} + +int KyraEngine::cmd_playWinterScrollSequence(ScriptState *script) { + debug(3, "cmd_playWinterScrollSequence(0x%X) (%d)", script, stackPos(0)); + if (!stackPos(0)) { + seq_winterScroll2(); + } else { + seq_winterScroll1(); + } + return 0; +} + +int KyraEngine::cmd_getIdolGem(ScriptState *script) { + debug(3, "cmd_getIdolGem(0x%X) (%d)", script, stackPos(0)); + return _idolGemsTable[stackPos(0)];; +} + +int KyraEngine::cmd_setIdolGem(ScriptState *script) { + debug(3, "cmd_setIdolGem(0x%X) (%d, %d)", script, stackPos(0), stackPos(1)); + _idolGemsTable[stackPos(0)] = stackPos(1); + return 0; +} + +int KyraEngine::cmd_totalItemsInScene(ScriptState *script) { + debug(3, "cmd_totalItemsInScene(0x%X) (%d)", script, stackPos(0)); + return countItemsInScene(stackPos(0)); +} + +int KyraEngine::cmd_restoreBrandonsMovementDelay(ScriptState *script) { + debug(3, "cmd_restoreBrandonsMovementDelay(0x%X) ()", script); + //TODO: Use movement set by menu, instead of 5. + setTimerDelay(5, 5); + return 0; +} + +int KyraEngine::cmd_setMousePos(ScriptState *script) { + debug(3, "cmd_setMousePos(0x%X) (%d, %d)", script, stackPos(0), stackPos(1)); + _system->warpMouse(stackPos(0), stackPos(1)); + _mouseX = stackPos(0); + _mouseY = stackPos(1); + return 0; +} + +int KyraEngine::cmd_getMouseState(ScriptState *script) { + debug(3, "cmd_getMouseState(0x%X) ()", script); + return _mouseState; +} + +int KyraEngine::cmd_setEntranceMouseCursorTrack(ScriptState *script) { + debug(3, "cmd_setEntranceMouseCursorTrack(0x%X) (%d, %d, %d, %d, %d)", script, stackPos(0), stackPos(1), stackPos(2), stackPos(3), stackPos(4)); + _entranceMouseCursorTracks[0] = stackPos(0); + _entranceMouseCursorTracks[1] = stackPos(1); + _entranceMouseCursorTracks[2] = stackPos(0) + stackPos(2) - 1; + _entranceMouseCursorTracks[3] = stackPos(1) + stackPos(3) - 1; + _entranceMouseCursorTracks[4] = stackPos(4); + return 0; +} + +int KyraEngine::cmd_itemAppearsOnGround(ScriptState *script) { + debug(3, "cmd_itemAppearsOnGround(0x%X) (%d, %d, %d)", script, stackPos(0), stackPos(1), stackPos(2)); + processItemDrop(_currentCharacter->sceneId, stackPos(0), stackPos(1), stackPos(2), 2, 0); + return 0; +} + +int KyraEngine::cmd_setNoDrawShapesFlag(ScriptState *script) { + debug(3, "cmd_setNoDrawShapesFlag(0x%X) (%d)", script, stackPos(0)); + _animator->_noDrawShapesFlag = stackPos(0); + return 0; +} + +int KyraEngine::cmd_fadeEntirePalette(ScriptState *script) { + warning("STUB: cmd_fadeEntirePalette"); + return 0; +} + +int KyraEngine::cmd_itemOnGroundHere(ScriptState *script) { + debug(3, "cmd_itemOnGroundHere(0x%X) (%d, %d)", script, stackPos(0), stackPos(1)); + assert(stackPos(0) < _roomTableSize); + Room *curRoom = &_roomTable[stackPos(0)]; + for (int i = 0; i < 12; ++i) { + if (curRoom->itemsTable[i] == stackPos(1)) + return 1; + } + return 0; +} + +int KyraEngine::cmd_queryCauldronState(ScriptState *script) { + debug(3, "cmd_queryCauldronState(0x%X) ()", script); + return _cauldronState; +} + +int KyraEngine::cmd_setCauldronState(ScriptState *script) { + debug(3, "cmd_setCauldronState(0x%X) (%d)", script, stackPos(0)); + _cauldronState = stackPos(0); + return _cauldronState; +} + +int KyraEngine::cmd_queryCrystalState(ScriptState *script) { + debug(3, "cmd_queryCrystalState(0x%X) (%d)", script, stackPos(0)); + if (!stackPos(0)) { + return _crystalState[0]; + } else if (stackPos(0) == 1) { + return _crystalState[1]; + } + return -1; +} + +int KyraEngine::cmd_setCrystalState(ScriptState *script) { + debug(3, "cmd_setCrystalState(0x%X) (%d)", script, stackPos(0), stackPos(1)); + if (!stackPos(0)) { + _crystalState[0] = stackPos(1); + } else if (stackPos(0) == 1) { + _crystalState[1] = stackPos(1); + } + return stackPos(1); +} + +int KyraEngine::cmd_setPaletteRange(ScriptState *script) { + warning("STUB: cmd_setPaletteRange"); + return 0; +} + +int KyraEngine::cmd_shrinkBrandonDown(ScriptState *script) { + debug(3, "cmd_shrinkBrandonDown(0x%X) (%d)", script, stackPos(0)); + int delayTime = stackPos(0); + checkAmuletAnimFlags(); + int scaleValue = _scaleTable[_currentCharacter->y1]; + int scale = 0; + if (_scaleMode) { + scale = scaleValue; + } else { + scale = 256; + } + int scaleModeBackUp = _scaleMode; + _scaleMode = 1; + int scaleEnd = scale >> 1; + for (; scaleEnd <= scale; --scale) { + _scaleTable[_currentCharacter->y1] = scale; + _animator->animRefreshNPC(0); + delayWithTicks(1); + } + delayWithTicks(delayTime); // XXX + _scaleTable[_currentCharacter->y1] = scaleValue; + _scaleMode = scaleModeBackUp; + return 0; +} + +int KyraEngine::cmd_growBrandonUp(ScriptState *script) { + debug(3, "cmd_growBrandonUp(0x%X) ()", script); + int scaleValue = _scaleTable[_currentCharacter->y1]; + int scale = 0; + if (_scaleMode) { + scale = scaleValue; + } else { + scale = 256; + } + int scaleModeBackUp = _scaleMode; + _scaleMode = 1; + for (int curScale = scale >> 1; curScale <= scale; ++curScale) { + _scaleTable[_currentCharacter->y1] = curScale; + _animator->animRefreshNPC(0); + delayWithTicks(1); + } + _scaleTable[_currentCharacter->y1] = scaleValue; + _scaleMode = scaleModeBackUp; + return 0; +} + +int KyraEngine::cmd_setBrandonScaleXAndY(ScriptState *script) { + debug(3, "cmd_setBrandonScaleXAndY(0x%X) (%d, %d)", script, stackPos(0), stackPos(1)); + _animator->_brandonScaleX = stackPos(0); + _animator->_brandonScaleY = stackPos(1); + return 0; +} + +int KyraEngine::cmd_resetScaleMode(ScriptState *script) { + debug(3, "cmd_resetScaleMode(0x%X) ()", script); + _scaleMode = 0; + return 0; +} + +int KyraEngine::cmd_getScaleDepthTableValue(ScriptState *script) { + debug(3, "cmd_getScaleDepthTableValue(0x%X) (%d)", script, stackPos(0)); + assert(stackPos(0) < ARRAYSIZE(_scaleTable)); + return _scaleTable[stackPos(0)]; +} + +int KyraEngine::cmd_setScaleDepthTableValue(ScriptState *script) { + debug(3, "cmd_setScaleDepthTableValue(0x%X) (%d, %d)", script, stackPos(0), stackPos(1)); + assert(stackPos(0) < ARRAYSIZE(_scaleTable)); + _scaleTable[stackPos(0)] = stackPos(1); + return stackPos(1); +} + +int KyraEngine::cmd_message(ScriptState *script) { + if (_features & GF_TALKIE) { + debug(3, "cmd_message(0x%X) (%d, '%s', %d)", script, stackPos(0), stackPosString(1), stackPos(2)); + drawSentenceCommand(stackPosString(1), stackPos(2)); + } else { + debug(3, "cmd_message(0x%X) ('%s', %d)", script, stackPosString(0), stackPos(1)); + drawSentenceCommand(stackPosString(0), stackPos(1)); + } + + return 0; +} + +int KyraEngine::cmd_checkClickOnNPC(ScriptState *script) { + debug(3, "cmd_checkClickOnNPC(0x%X) (%d, %d)", script, stackPos(0), stackPos(1)); + return checkForNPCScriptRun(stackPos(0), stackPos(1)); +} + +int KyraEngine::cmd_getFoyerItem(ScriptState *script) { + debug(3, "cmd_getFoyerItem(0x%X) (%d)", stackPos(0)); + assert(stackPos(0) < ARRAYSIZE(_foyerItemTable)); + return _foyerItemTable[stackPos(0)]; +} + +int KyraEngine::cmd_setFoyerItem(ScriptState *script) { + debug(3, "cmd_setFoyerItem(0x%X) (%d, %d)", stackPos(0), stackPos(1)); + assert(stackPos(0) < ARRAYSIZE(_foyerItemTable)); + _foyerItemTable[stackPos(0)] = stackPos(1); + return stackPos(1); +} + +int KyraEngine::cmd_setNoItemDropRegion(ScriptState *script) { + debug(3, "cmd_setNoItemDropRegion(0x%X) (%d, %d, %d, %d)", script, stackPos(0), stackPos(1), stackPos(2), stackPos(3)); + addToNoDropRects(stackPos(0), stackPos(1), stackPos(2), stackPos(3)); + return 0; +} + +int KyraEngine::cmd_walkMalcolmOn(ScriptState *script) { + debug(3, "cmd_walkMalcolmOn(0x%X) ()", script); + if (!_malcolmFlag) + _malcolmFlag = 1; + return 0; +} + +int KyraEngine::cmd_passiveProtection(ScriptState *script) { + debug(3, "cmd_passiveProtection(0x%X) ()", script); + return 1; +} + +int KyraEngine::cmd_setPlayingLoop(ScriptState *script) { + warning("STUB: cmd_setPlayingLoop"); + return 0; +} + +int KyraEngine::cmd_brandonToStoneSequence(ScriptState *script) { + warning("STUB: cmd_brandonToStoneSequence"); + return 0; +} + +int KyraEngine::cmd_brandonHealingSequence(ScriptState *script) { + debug(3, "cmd_brandonHealingSequence(0x%X) ()", script); + seq_brandonHealing(); + return 0; +} + +int KyraEngine::cmd_protectCommandLine(ScriptState *script) { + debug(3, "cmd_protectCommandLine(0x%X) (%d)", script, stackPos(0)); + return stackPos(0); +} + +int KyraEngine::cmd_pauseMusicSeconds(ScriptState *script) { + warning("STUB: cmd_pauseMusicSeconds"); + return 0; +} + +int KyraEngine::cmd_resetMaskRegion(ScriptState *script) { + warning("STUB: cmd_resetMaskRegion"); + return 0; +} + +int KyraEngine::cmd_setPaletteChangeFlag(ScriptState *script) { + debug(3, "cmd_setPaletteChangeFlag(0x%X) (%d)", script, stackPos(0)); + _paletteChanged = stackPos(0); + return _paletteChanged; +} + +int KyraEngine::cmd_fillRect(ScriptState *script) { + debug(3, "cmd_fillRect(0x%X) (%d, %d, %d, %d, %d, 0x%X)", script, stackPos(0), stackPos(1), stackPos(2), stackPos(3), stackPos(4), stackPos(5)); + int videoPageBackup = _screen->_curPage; + _screen->_curPage = stackPos(0); + _screen->fillRect(stackPos(1), stackPos(2), stackPos(3), stackPos(4), stackPos(5)); + _screen->_curPage = videoPageBackup; + return 0; +} + +int KyraEngine::cmd_vocUnload(ScriptState *script) { + debug(3, "cmd_vocUnload(0x%X) ()", script); + // this should unload all voc files (not needed) + return 0; +} + +int KyraEngine::cmd_vocLoad(ScriptState *script) { + debug(3, "cmd_vocLoad(0x%X) (%d)", script, stackPos(0)); + // this should load the specified voc file (not needed) + return 0; +} + +int KyraEngine::cmd_dummy(ScriptState *script) { + debug(3, "cmd_dummy(0x%X) ()", script); + return 0; +} + +} // end of namespace Kyra diff --git a/engines/kyra/seqplayer.cpp b/engines/kyra/seqplayer.cpp new file mode 100644 index 0000000000..fab2228c8a --- /dev/null +++ b/engines/kyra/seqplayer.cpp @@ -0,0 +1,640 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-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 "base/engine.h" + +#include "kyra/kyra.h" +#include "kyra/resource.h" +#include "kyra/screen.h" +#include "kyra/sound.h" +#include "kyra/wsamovie.h" +#include "kyra/text.h" + +#include "kyra/seqplayer.h" + +#define SEQOP(n, x) { n, &SeqPlayer::x, #x } + +namespace Kyra { + +SeqPlayer::SeqPlayer(KyraEngine* vm, OSystem* system) { + _vm = vm; + _system = system; + + _screen = vm->screen(); + _sound = vm->sound(); + _res = vm->resource(); + + _copyViewOffs = false; + _specialBuffer = 0; + + for (int i = 0; i < ARRAYSIZE(_handShapes); ++i) + _handShapes[i] = 0; +} + +SeqPlayer::~SeqPlayer() { + freeHandShapes(); + + for (int i = 0; i < ARRAYSIZE(_seqMovies); ++i) { + _seqMovies[i].movie->close(); + delete _seqMovies[i].movie; + _seqMovies[i].movie = 0; + } +} + +uint8 *SeqPlayer::setPanPages(int pageNum, int shape) { + debug(9, "SeqPlayer::setPanPages(%d, %d)", pageNum, shape); + uint8 *panPage = 0; + const uint8 *data = _screen->getPagePtr(pageNum); + uint16 numShapes = READ_LE_UINT16(data); + if (shape < numShapes) { + uint32 offs = 0; + if (_vm->features() & GF_TALKIE) { + offs = READ_LE_UINT32(data + 2 + shape * 4); + } else { + offs = READ_LE_UINT16(data + 2 + shape * 2); + } + if (offs != 0) { + data += offs; + uint16 sz = READ_LE_UINT16(data + 6); + panPage = (uint8 *)malloc(sz); + if (panPage) { + memcpy(panPage, data, sz); + } + } + } + return panPage; +} + +void SeqPlayer::makeHandShapes() { + debug(9, "SeqPlayer::makeHandShapes()"); + _vm->loadBitmap("WRITING.CPS", 3, 3, 0); + for (int i = 0; i < ARRAYSIZE(_handShapes); ++i) { + if (_handShapes[i]) + free(_handShapes[i]); + _handShapes[i] = setPanPages(3, i); + } +} + +void SeqPlayer::freeHandShapes() { + debug(9, "SeqPlayer::freeHandShapes()"); + for (int i = 0; i < ARRAYSIZE(_handShapes); ++i) { + free(_handShapes[i]); + _handShapes[i] = 0; + } +} + +void SeqPlayer::s1_wsaOpen() { + uint8 wsaObj = *_seqData++; + assert(wsaObj < ARRAYSIZE(_seqMovies)); + uint8 offscreenDecode = *_seqData++; + _seqWsaCurDecodePage = _seqMovies[wsaObj].page = (offscreenDecode == 0) ? 0 : 3; + if (!_seqMovies[wsaObj].movie) + _seqMovies[wsaObj].movie = _vm->createWSAMovie(); + _seqMovies[wsaObj].movie->_drawPage = _seqMovies[wsaObj].page; + _seqMovies[wsaObj].movie->open(_vm->seqWSATable()[wsaObj], offscreenDecode, 0); + _seqMovies[wsaObj].frame = 0; + _seqMovies[wsaObj].numFrames = _seqMovies[wsaObj].movie->frames() - 1; +} + +void SeqPlayer::s1_wsaClose() { + uint8 wsaObj = *_seqData++; + assert(wsaObj < ARRAYSIZE(_seqMovies)); + if (_seqMovies[wsaObj].movie) { + _seqMovies[wsaObj].movie->close(); + } +} + +void SeqPlayer::s1_wsaPlayFrame() { + uint8 wsaObj = *_seqData++; + assert(wsaObj < ARRAYSIZE(_seqMovies)); + int16 frame = (int8)*_seqData++; + _seqMovies[wsaObj].pos.x = READ_LE_UINT16(_seqData); _seqData += 2; + _seqMovies[wsaObj].pos.y = *_seqData++; + assert(_seqMovies[wsaObj].movie); + _seqMovies[wsaObj].movie->_x = _seqMovies[wsaObj].pos.x; + _seqMovies[wsaObj].movie->_y = _seqMovies[wsaObj].pos.y; + _seqMovies[wsaObj].movie->displayFrame(frame); + _seqMovies[wsaObj].frame = frame; +} + +void SeqPlayer::s1_wsaPlayNextFrame() { + uint8 wsaObj = *_seqData++; + assert(wsaObj < ARRAYSIZE(_seqMovies)); + int16 frame = ++_seqMovies[wsaObj].frame; + if (frame > _seqMovies[wsaObj].numFrames) { + frame = 0; + _seqMovies[wsaObj].frame = 0; + } + _seqMovies[wsaObj].movie->displayFrame(frame); +} + +void SeqPlayer::s1_wsaPlayPrevFrame() { + uint8 wsaObj = *_seqData++; + assert(wsaObj < ARRAYSIZE(_seqMovies)); + int16 frame = --_seqMovies[wsaObj].frame; + if (frame < 0) { + frame = _seqMovies[wsaObj].numFrames; + _seqMovies[wsaObj].frame = frame; + } else { + _seqMovies[wsaObj].movie->displayFrame(frame); + } +} + +void SeqPlayer::s1_drawShape() { + uint8 shapeNum = *_seqData++; + int x = READ_LE_UINT16(_seqData); _seqData += 2; + int y = *_seqData++; + _screen->drawShape(2, _handShapes[shapeNum], x, y, 0, 0, 0); +} + +void SeqPlayer::s1_waitTicks() { + uint16 ticks = READ_LE_UINT16(_seqData); _seqData += 2; + _vm->delay(ticks * _vm->tickLength()); +} + +void SeqPlayer::s1_copyWaitTicks() { + s1_copyView(); + s1_waitTicks(); +} + +void SeqPlayer::s1_shuffleScreen() { + _screen->shuffleScreen(0, 16, 320, 128, 2, 0, 0, false); + _screen->_curPage = 2; + if (_specialBuffer) + _screen->copyCurPageBlock(0, 16, 40, 128, _specialBuffer); + _screen->_curPage = 0; +} + +void SeqPlayer::s1_copyView() { + int y = 128; + if (!_copyViewOffs) { + y -= 8; + } + if (_specialBuffer && !_copyViewOffs) { + _screen->copyToPage0(16, y, 3, _specialBuffer); + } else { + _screen->copyRegion(0, 16, 0, 16, 320, y, 2, 0); + } +} + +void SeqPlayer::s1_loopInit() { + uint8 seqLoop = *_seqData++; + if (seqLoop < ARRAYSIZE(_seqLoopTable)) { + _seqLoopTable[seqLoop].ptr = _seqData; + } else { + _seqQuitFlag = true; + } +} + +void SeqPlayer::s1_loopInc() { + uint8 seqLoop = *_seqData++; + uint16 seqLoopCount = READ_LE_UINT16(_seqData); _seqData += 2; + if (_seqLoopTable[seqLoop].count == 0xFFFF) { + _seqLoopTable[seqLoop].count = seqLoopCount - 1; + _seqData = _seqLoopTable[seqLoop].ptr; + } else if (_seqLoopTable[seqLoop].count == 0) { + _seqLoopTable[seqLoop].count = 0xFFFF; + _seqLoopTable[seqLoop].ptr = 0; + } else { + --_seqLoopTable[seqLoop].count; + _seqData = _seqLoopTable[seqLoop].ptr; + } +} + +void SeqPlayer::s1_skip() { + uint8 a = *_seqData++; + warning("STUB: s1_skip(%d)\n", a); +} + +void SeqPlayer::s1_loadPalette() { + uint8 colNum = *_seqData++; + uint32 fileSize; + uint8 *srcData; + srcData = _res->fileData(_vm->seqCOLTable()[colNum], &fileSize); + memcpy(_screen->_currentPalette, srcData, fileSize); + delete[] srcData; +} + +void SeqPlayer::s1_loadBitmap() { + uint8 cpsNum = *_seqData++; + _vm->loadBitmap(_vm->seqCPSTable()[cpsNum], 3, 3, 0); +} + +void SeqPlayer::s1_fadeToBlack() { + _screen->fadeToBlack(); +} + +void SeqPlayer::s1_printText() { + static const uint8 colorMap[] = { 0, 0, 0, 0, 12, 12, 12, 0, 0, 0, 0, 0 }; + uint8 txt = *_seqData++; + _screen->fillRect(0, 180, 319, 195, 12); + _screen->setTextColorMap(colorMap); + if (!_seqDisplayTextFlag) { + const char *str = _vm->seqTextsTable()[txt]; + int x = (Screen::SCREEN_W - _screen->getTextWidth(str)) / 2; + _screen->printText(str, x, 180, 0xF, 0xC); + } else { + _seqDisplayedTextTimer = _system->getMillis() + 1000 / ((_vm->features() & GF_FRENCH) ? 120 : 60); + _seqDisplayedText = txt; + _seqDisplayedChar = 0; + const char *str = _vm->seqTextsTable()[_seqDisplayedText]; + _seqDisplayedTextX = (Screen::SCREEN_W - _screen->getTextWidth(str)) / 2; + } +} + +void SeqPlayer::s1_printTalkText() { + uint8 txt = *_seqData++; + int x = READ_LE_UINT16(_seqData); _seqData += 2; + int y = *_seqData++; + uint8 fillColor = *_seqData++; + int b; + if (_seqTalkTextPrinted && !_seqTalkTextRestored) { + if (_seqWsaCurDecodePage != 0 && !_specialBuffer) { + b = 2; + } else { + b = 0; + } + _vm->text()->restoreTalkTextMessageBkgd(2, b); + } + _seqTalkTextPrinted = true; + _seqTalkTextRestored = false; + if (_seqWsaCurDecodePage != 0 && !_specialBuffer) { + b = 2; + } else { + b = 0; + } + _vm->text()->printTalkTextMessage(_vm->seqTextsTable()[txt], x, y, fillColor, b, 2); +} + +void SeqPlayer::s1_restoreTalkText() { + if (_seqTalkTextPrinted && !_seqTalkTextRestored) { + int b; + if (_seqWsaCurDecodePage != 0 && !_specialBuffer) { + b = 2; + } else { + b = 0; + } + _vm->text()->restoreTalkTextMessageBkgd(2, b); + _seqTalkTextRestored = true; + } +} + +void SeqPlayer::s1_clearCurrentScreen() { + _screen->fillRect(10, 180, 319, 196, 0xC); +} + +void SeqPlayer::s1_break() { + // Do nothing +} + +void SeqPlayer::s1_fadeFromBlack() { + _screen->fadeFromBlack(); +} + +void SeqPlayer::s1_copyRegion() { + uint8 srcPage = *_seqData++; + uint8 dstPage = *_seqData++; + _screen->copyRegion(0, 0, 0, 0, 320, 200, srcPage, dstPage); +} + +void SeqPlayer::s1_copyRegionSpecial() { + static const uint8 colorMap[] = { 0, 0, 0, 0, 0, 12, 12, 0, 0, 0, 0, 0 }; + const char *copyStr = 0; + if (_vm->features() & GF_FLOPPY || _vm->features() & GF_DEMO) { + copyStr = "Copyright (c) 1992 Westwood Studios"; + } else if (_vm->features() & GF_TALKIE) { + copyStr = "Copyright (c) 1992,1993 Westwood Studios"; + } + + uint8 so = *_seqData++; + switch (so) { + case 0: + _screen->copyRegion(0, 0, 0, 47, 320, 77, 2, 0); + break; + case 1: + _screen->copyRegion(0, 0, 0, 47, 320, 56, 2, 0); + break; + case 2: + _screen->copyRegion(107, 72, 107, 72, 43, 87, 2, 0); + _screen->copyRegion(130, 159, 130, 159, 35, 17, 2, 0); + _screen->copyRegion(165, 105, 165, 105, 32, 9, 2, 0); + _screen->copyRegion(206, 83, 206, 83, 94, 93, 2, 0); + break; + case 3: + _screen->copyRegion(152, 56, 152, 56, 48, 48, 2, 0); + break; + case 4: { + _screen->_charWidth = -2; + const int x = (Screen::SCREEN_W - _screen->getTextWidth(copyStr)) / 2; + const int y = 179; + _screen->setTextColorMap(colorMap); + _screen->printText(copyStr, x + 1, y + 1, 0xB, 0xC); + _screen->printText(copyStr, x, y, 0xF, 0xC); + } break; + case 5: + _screen->_curPage = 2; + break; + default: + error("Invalid subopcode %d for s1_copyRegionSpecial", so); + break; + } +} + +void SeqPlayer::s1_fillRect() { + int x1 = READ_LE_UINT16(_seqData); _seqData += 2; + int y1 = *_seqData++; + int x2 = READ_LE_UINT16(_seqData); _seqData += 2; + int y2 = *_seqData++; + uint8 color = *_seqData++; + uint8 page = *_seqData++; + _screen->fillRect(x1, y1, x2, y2, color, page); +} + +void SeqPlayer::s1_playEffect() { + uint8 track = *_seqData++; + _vm->delay(3 * _vm->tickLength()); + _sound->playSoundEffect(track); +} + +void SeqPlayer::s1_playTrack() { + uint8 msg = *_seqData++; +/* + // we do not have audio cd support for now + if (_vm->features() & GF_AUDIOCD) { + switch (msg) { + case 0: + // nothing to do here... + break; + case 1: + _sound->beginFadeOut(); + break; + case 56: + _vm->snd_playTheme(KyraEngine::MUSIC_INTRO, 3); + break; + case 57: + _vm->snd_playTheme(KyraEngine::MUSIC_INTRO, 4); + break; + case 58: + _vm->snd_playTheme(KyraEngine::MUSIC_INTRO, 5); + break; + default: + warning("Unknown seq. message: %.02d", msg); + break; + } + } else {*/ + if (msg == 0) { + // nothing to do here... + } else if (msg == 1) { + _sound->beginFadeOut(); + } else { + _sound->playTrack(msg); + } +// } +} + +void SeqPlayer::s1_allocTempBuffer() { + if (_vm->features() & GF_DEMO) { + _seqQuitFlag = true; + } else { + if (!_specialBuffer && !_copyViewOffs) { + _specialBuffer = new uint8[40960]; + assert(_specialBuffer); + int page = _screen->_curPage; + _screen->_curPage = 0; + _screen->copyCurPageBlock(0, 0, 320, 128, _specialBuffer); + _screen->_curPage = page; + } + } +} + +void SeqPlayer::s1_textDisplayEnable() { + _seqDisplayTextFlag = true; +} + +void SeqPlayer::s1_textDisplayDisable() { + _seqDisplayTextFlag = false; +} + +void SeqPlayer::s1_endOfScript() { + _seqQuitFlag = true; +} + +void SeqPlayer::s1_loadIntroVRM() { + _res->loadPakFile("INTRO.VRM"); +} + +void SeqPlayer::s1_playVocFile() { + _vm->snd_voiceWaitForFinish(false); + uint8 a = *_seqData++; + _vm->snd_playVoiceFile(a); +} + +void SeqPlayer::s1_miscUnk3() { + warning("STUB: s1_miscUnk3"); +} + +void SeqPlayer::s1_prefetchVocFile() { + *_seqData++; + // we do not have to prefetch the vocfiles on modern systems +} + +bool SeqPlayer::playSequence(const uint8 *seqData, bool skipSeq) { + debug(9, "SeqPlayer::seq_playSequence(0x%X, %d)", seqData, skipSeq); + assert(seqData); + + static SeqEntry floppySeqProcs[] = { + // 0x00 + SEQOP(3, s1_wsaOpen), + SEQOP(2, s1_wsaClose), + SEQOP(6, s1_wsaPlayFrame), + SEQOP(2, s1_wsaPlayNextFrame), + // 0x04 + SEQOP(2, s1_wsaPlayPrevFrame), + SEQOP(5, s1_drawShape), + SEQOP(3, s1_waitTicks), + SEQOP(3, s1_copyWaitTicks), + // 0x08 + SEQOP(1, s1_shuffleScreen), + SEQOP(1, s1_copyView), + SEQOP(2, s1_loopInit), + SEQOP(4, s1_loopInc), + // 0x0C + SEQOP(2, s1_loadPalette), + SEQOP(2, s1_loadBitmap), + SEQOP(1, s1_fadeToBlack), + SEQOP(2, s1_printText), + // 0x10 + SEQOP(6, s1_printTalkText), + SEQOP(1, s1_restoreTalkText), + SEQOP(1, s1_clearCurrentScreen), + SEQOP(1, s1_break), + // 0x14 + SEQOP(1, s1_fadeFromBlack), + SEQOP(3, s1_copyRegion), + SEQOP(2, s1_copyRegionSpecial), + SEQOP(9, s1_fillRect), + // 0x18 + SEQOP(2, s1_playEffect), + SEQOP(2, s1_playTrack), + SEQOP(1, s1_allocTempBuffer), + SEQOP(1, s1_textDisplayEnable), + // 0x1C + SEQOP(1, s1_textDisplayDisable), + SEQOP(1, s1_endOfScript) + }; + + static SeqEntry cdromSeqProcs[] = { + // 0x00 + SEQOP(3, s1_wsaOpen), + SEQOP(2, s1_wsaClose), + SEQOP(6, s1_wsaPlayFrame), + SEQOP(2, s1_wsaPlayNextFrame), + // 0x04 + SEQOP(2, s1_wsaPlayPrevFrame), + SEQOP(5, s1_drawShape), + SEQOP(3, s1_waitTicks), + SEQOP(3, s1_waitTicks), + // 0x08 + SEQOP(3, s1_copyWaitTicks), + SEQOP(1, s1_shuffleScreen), + SEQOP(1, s1_copyView), + SEQOP(2, s1_loopInit), + // 0x0C + SEQOP(4, s1_loopInc), + SEQOP(4, s1_loopInc), + SEQOP(2, s1_skip), + SEQOP(2, s1_loadPalette), + // 0x10 + SEQOP(2, s1_loadBitmap), + SEQOP(1, s1_fadeToBlack), + SEQOP(2, s1_printText), + SEQOP(6, s1_printTalkText), + // 0x14 + SEQOP(1, s1_restoreTalkText), + SEQOP(1, s1_clearCurrentScreen), + SEQOP(1, s1_break), + SEQOP(1, s1_fadeFromBlack), + // 0x18 + SEQOP(3, s1_copyRegion), + SEQOP(2, s1_copyRegionSpecial), + SEQOP(9, s1_fillRect), + SEQOP(2, s1_playEffect), + // 0x1C + SEQOP(2, s1_playTrack), + SEQOP(1, s1_allocTempBuffer), + SEQOP(1, s1_textDisplayEnable), + SEQOP(1, s1_textDisplayDisable), + // 0x20 + SEQOP(1, s1_endOfScript), + SEQOP(1, s1_loadIntroVRM), + SEQOP(2, s1_playVocFile), + SEQOP(1, s1_miscUnk3), + // 0x24 + SEQOP(2, s1_prefetchVocFile) + }; + + const SeqEntry* commands; + int numCommands; + + if (_vm->features() & GF_FLOPPY || _vm->features() & GF_DEMO) { + commands = floppySeqProcs; + numCommands = ARRAYSIZE(floppySeqProcs); + } else if (_vm->features() & GF_TALKIE) { + commands = cdromSeqProcs; + numCommands = ARRAYSIZE(cdromSeqProcs); + } else { + error("No commandlist found"); + } + + bool seqSkippedFlag = false; + + _seqData = seqData; + + _seqDisplayedTextTimer = 0xFFFFFFFF; + _seqDisplayTextFlag = false; + _seqDisplayedTextX = 0; + _seqDisplayedText = 0; + _seqDisplayedChar = 0; + _seqTalkTextRestored = false; + _seqTalkTextPrinted = false; + + _seqQuitFlag = false; + _seqWsaCurDecodePage = 0; + + for (int i = 0; i < 20; ++i) { + _seqLoopTable[i].ptr = 0; + _seqLoopTable[i].count = 0xFFFF; + } + + memset(_seqMovies, 0, sizeof(_seqMovies)); + + _screen->_curPage = 0; + while (!_seqQuitFlag) { + if (skipSeq && _vm->seq_skipSequence()) { + while (1) { + uint8 code = *_seqData; + if (commands[code].proc == &SeqPlayer::s1_endOfScript || commands[code].proc == &SeqPlayer::s1_break) { + break; + } + _seqData += commands[code].len; + } + skipSeq = false; + seqSkippedFlag = true; + } + // used in Kallak writing intro + if (_seqDisplayTextFlag && _seqDisplayedTextTimer != 0xFFFFFFFF) { + if (_seqDisplayedTextTimer < _system->getMillis()) { + char charStr[2]; + charStr[0] = _vm->seqTextsTable()[_seqDisplayedText][_seqDisplayedChar]; + charStr[1] = '\0'; + _screen->printText(charStr, _seqDisplayedTextX, 180, 0xF, 0xC); + _seqDisplayedTextX += _screen->getCharWidth(charStr[0]); + ++_seqDisplayedChar; + if (_vm->seqTextsTable()[_seqDisplayedText][_seqDisplayedChar] == '\0') { + _seqDisplayedTextTimer = 0xFFFFFFFF; + } else { + _seqDisplayedTextTimer = _system->getMillis() + 1000 / ((_vm->features() & GF_FRENCH) ? 120 : 60); + } + } + } + + uint8 seqCode = *_seqData++; + if (seqCode < numCommands) { + SeqProc currentProc = commands[seqCode].proc; + debug(5, "seqCode = %d (%s)", seqCode, commands[seqCode].desc); + (this->*currentProc)(); + } else { + error("Invalid sequence opcode %d", seqCode); + } + + _screen->updateScreen(); + } + delete [] _specialBuffer; + _specialBuffer = 0; + return seqSkippedFlag; +} + + +} // End of namespace Kyra diff --git a/engines/kyra/seqplayer.h b/engines/kyra/seqplayer.h new file mode 100644 index 0000000000..ad747238c5 --- /dev/null +++ b/engines/kyra/seqplayer.h @@ -0,0 +1,123 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-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 KYRASEQPLAYER_H +#define KYRASEQPLAYER_H + +namespace Kyra { + +class SeqPlayer { +public: + SeqPlayer(KyraEngine* vm, OSystem* system); + ~SeqPlayer(); + + void setCopyViewOffs(bool offs) { + _copyViewOffs = offs; + } + + void makeHandShapes(); + void freeHandShapes(); + + bool playSequence(const uint8 *seqData, bool skipSeq); + + uint8 *setPanPages(int pageNum, int shape); + +protected: + KyraEngine *_vm; + OSystem *_system; + Screen *_screen; + Sound *_sound; + Resource *_res; + + uint8 *_handShapes[3]; + bool _copyViewOffs; + + typedef void (SeqPlayer::*SeqProc)(); + struct SeqEntry { + uint8 len; + SeqProc proc; + const char* desc; + }; + + // the sequence procs + void s1_wsaOpen(); + void s1_wsaClose(); + void s1_wsaPlayFrame(); + void s1_wsaPlayNextFrame(); + void s1_wsaPlayPrevFrame(); + void s1_drawShape(); + void s1_waitTicks(); + void s1_copyWaitTicks(); + void s1_shuffleScreen(); + void s1_copyView(); + void s1_loopInit(); + void s1_loopInc(); + void s1_skip(); + void s1_loadPalette(); + void s1_loadBitmap(); + void s1_fadeToBlack(); + void s1_printText(); + void s1_printTalkText(); + void s1_restoreTalkText(); + void s1_clearCurrentScreen(); + void s1_break(); + void s1_fadeFromBlack(); + void s1_copyRegion(); + void s1_copyRegionSpecial(); + void s1_fillRect(); + void s1_playEffect(); + void s1_playTrack(); + void s1_allocTempBuffer(); + void s1_textDisplayEnable(); + void s1_textDisplayDisable(); + void s1_endOfScript(); + void s1_loadIntroVRM(); + void s1_playVocFile(); + void s1_miscUnk3(); + void s1_prefetchVocFile(); + + struct SeqMovie { + Movie *movie; + int32 page; + int16 frame; + int16 numFrames; + Common::Point pos; + }; + + const uint8 *_seqData; + uint8 *_specialBuffer; + SeqMovie _seqMovies[12]; + SeqLoop _seqLoopTable[20]; + uint16 _seqWsaCurDecodePage; + uint32 _seqDisplayedTextTimer; + bool _seqDisplayTextFlag; + uint8 _seqDisplayedText; + uint8 _seqDisplayedChar; + uint16 _seqDisplayedTextX; + bool _seqTalkTextPrinted; + bool _seqTalkTextRestored; + bool _seqQuitFlag; +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/sequences_v1.cpp b/engines/kyra/sequences_v1.cpp new file mode 100644 index 0000000000..8b60196763 --- /dev/null +++ b/engines/kyra/sequences_v1.cpp @@ -0,0 +1,1630 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2005-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 "kyra/kyra.h" +#include "kyra/seqplayer.h" +#include "kyra/screen.h" +#include "kyra/resource.h" +#include "kyra/sound.h" +#include "kyra/sprites.h" +#include "kyra/wsamovie.h" +#include "kyra/animator.h" +#include "kyra/text.h" + +#include "common/system.h" +#include "common/savefile.h" + +namespace Kyra { + +void KyraEngine::seq_demo() { + debug(9, "KyraEngine::seq_demo()"); + + snd_playTheme(MUSIC_INTRO, 2); + + loadBitmap("START.CPS", 7, 7, _screen->_currentPalette); + _screen->copyRegion(0, 0, 0, 0, 320, 200, 6, 0); + _system->copyRectToScreen(_screen->getPagePtr(0), 320, 0, 0, 320, 200); + _screen->fadeFromBlack(); + delay(60 * _tickLength); + _screen->fadeToBlack(); + + _screen->clearPage(0); + loadBitmap("TOP.CPS", 7, 7, NULL); + loadBitmap("BOTTOM.CPS", 5, 5, _screen->_currentPalette); + _screen->copyRegion(0, 91, 0, 8, 320, 103, 6, 0); + _screen->copyRegion(0, 0, 0, 111, 320, 64, 6, 0); + _system->copyRectToScreen(_screen->getPagePtr(0), 320, 0, 0, 320, 200); + _screen->fadeFromBlack(); + + _seq->playSequence(_seq_WestwoodLogo, true); + delay(60 * _tickLength); + _seq->playSequence(_seq_KyrandiaLogo, true); + + _screen->fadeToBlack(); + _screen->clearPage(2); + _screen->clearPage(0); + + _seq->playSequence(_seq_Demo1, true); + + _screen->clearPage(0); + _seq->playSequence(_seq_Demo2, true); + + _screen->clearPage(0); + _seq->playSequence(_seq_Demo3, true); + + _screen->clearPage(0); + _seq->playSequence(_seq_Demo4, true); + + _screen->clearPage(0); + loadBitmap("FINAL.CPS", 7, 7, _screen->_currentPalette); + _screen->_curPage = 0; + _screen->copyRegion(0, 0, 0, 0, 320, 200, 6, 0); + _system->copyRectToScreen(_screen->getPagePtr(0), 320, 0, 0, 320, 200); + _screen->fadeFromBlack(); + delay(60 * _tickLength); + _screen->fadeToBlack(); + _sound->stopMusic(); +} + +void KyraEngine::seq_intro() { + debug(9, "KyraEngine::seq_intro()"); + + if (_features & GF_TALKIE) { + _res->loadPakFile("INTRO.VRM"); + } + + static const IntroProc introProcTable[] = { + &KyraEngine::seq_introLogos, + &KyraEngine::seq_introStory, + &KyraEngine::seq_introMalcolmTree, + &KyraEngine::seq_introKallakWriting, + &KyraEngine::seq_introKallakMalcolm + }; + + Common::InSaveFile *in; + if ((in = _saveFileMan->openForLoading(getSavegameFilename(0)))) { + delete in; + _skipIntroFlag = true; + } else + _skipIntroFlag = false; + + _seq->setCopyViewOffs(true); + _screen->setFont(Screen::FID_8_FNT); + snd_playTheme(MUSIC_INTRO, 2); + snd_setSoundEffectFile(MUSIC_INTRO); + _text->setTalkCoords(144); + for (int i = 0; i < ARRAYSIZE(introProcTable) && !seq_skipSequence(); ++i) { + (this->*introProcTable[i])(); + } + _text->setTalkCoords(136); + delay(30 * _tickLength); + _seq->setCopyViewOffs(false); + _sound->stopMusic(); + if (_features & GF_TALKIE) { + _res->unloadPakFile("INTRO.VRM"); + } + res_unloadResources(RES_INTRO | RES_OUTRO); +} + +void KyraEngine::seq_introLogos() { + debug(9, "KyraEngine::seq_introLogos()"); + _screen->clearPage(0); + loadBitmap("TOP.CPS", 7, 7, NULL); + loadBitmap("BOTTOM.CPS", 5, 5, _screen->_currentPalette); + _screen->_curPage = 0; + _screen->copyRegion(0, 91, 0, 8, 320, 103, 6, 0); + _screen->copyRegion(0, 0, 0, 111, 320, 64, 6, 0); + _system->copyRectToScreen(_screen->getPagePtr(0), 320, 0, 0, 320, 200); + _screen->fadeFromBlack(); + + if (_seq->playSequence(_seq_WestwoodLogo, _skipFlag)) { + _screen->fadeToBlack(); + _screen->clearPage(0); + return; + } + delay(60 * _tickLength); + if (_seq->playSequence(_seq_KyrandiaLogo, _skipFlag) && !seq_skipSequence()) { + _screen->fadeToBlack(); + _screen->clearPage(0); + return; + } + _screen->fillRect(0, 179, 319, 199, 0); + + int y1 = 8; + int h1 = 175; + int y2 = 176; + int h2 = 0; + int32 start, now; + int wait; + _screen->copyRegion(0, 91, 0, 8, 320, 103, 6, 2); + _screen->copyRegion(0, 0, 0, 111, 320, 64, 6, 2); + do { + start = (int32)_system->getMillis(); + if (h1 > 0) { + _screen->copyRegion(0, y1, 0, 8, 320, h1, 2, 0); + } + ++y1; + --h1; + if (h2 > 0) { + _screen->copyRegion(0, 64, 0, y2, 320, h2, 4, 0); + } + --y2; + ++h2; + _screen->updateScreen(); + now = (int32)_system->getMillis(); + wait = _tickLength - (now - start); + if (wait > 0) { + delay(wait); + } + } while (y2 >= 64); + + _seq->playSequence(_seq_Forest, true); +} + +void KyraEngine::seq_introStory() { + debug(9, "KyraEngine::seq_introStory()"); + _screen->clearPage(3); + _screen->clearPage(0); + if (_features & GF_TALKIE) { + return; + } else if (_features & GF_ENGLISH) { + loadBitmap("TEXT.CPS", 3, 3, 0); + } else if (_features & GF_GERMAN) { + loadBitmap("TEXT_GER.CPS", 3, 3, 0); + } else if (_features & GF_FRENCH) { + loadBitmap("TEXT_FRE.CPS", 3, 3, 0); + } else if (_features & GF_SPANISH) { + loadBitmap("TEXT_SPA.CPS", 3, 3, 0); + } else { + warning("no story graphics file found"); + } + _screen->copyRegion(0, 0, 0, 0, 320, 200, 3, 0); + _screen->updateScreen(); + debug("skipFlag %i, %i", _skipFlag, _tickLength); + delay(360 * _tickLength); +} + +void KyraEngine::seq_introMalcolmTree() { + debug(9, "KyraEngine::seq_introMalcolmTree()"); + _screen->_curPage = 0; + _screen->clearPage(3); + _seq->playSequence(_seq_MalcolmTree, true); +} + +void KyraEngine::seq_introKallakWriting() { + debug(9, "KyraEngine::seq_introKallakWriting()"); + _seq->makeHandShapes(); + _screen->setAnimBlockPtr(5060); + _screen->_charWidth = -2; + _screen->clearPage(3); + _seq->playSequence(_seq_KallakWriting, true); +} + +void KyraEngine::seq_introKallakMalcolm() { + debug(9, "KyraEngine::seq_introKallakMalcolm()"); + _screen->clearPage(3); + _seq->playSequence(_seq_KallakMalcolm, true); +} + +void KyraEngine::seq_createAmuletJewel(int jewel, int page, int noSound, int drawOnly) { + debug(9, "seq_createAmuletJewel(%d, %d, %d, %d)", jewel, page, noSound, drawOnly); + static const uint16 specialJewelTable[] = { + 0x167, 0x162, 0x15D, 0x158, 0x153, 0xFFFF + }; + static const uint16 specialJewelTable1[] = { + 0x14F, 0x154, 0x159, 0x15E, 0x163, 0xFFFF + }; + static const uint16 specialJewelTable2[] = { + 0x150, 0x155, 0x15A, 0x15F, 0x164, 0xFFFF + }; + static const uint16 specialJewelTable3[] = { + 0x151, 0x156, 0x15B, 0x160, 0x165, 0xFFFF + }; + static const uint16 specialJewelTable4[] = { + 0x152, 0x157, 0x15C, 0x161, 0x166, 0xFFFF + }; + if (!noSound) + snd_playSoundEffect(0x5F); + _screen->hideMouse(); + if (!drawOnly) { + for (int i = 0; specialJewelTable[i] != 0xFFFF; ++i) { + _screen->drawShape(page, _shapes[4+specialJewelTable[i]], _amuletX2[jewel], _amuletY2[jewel], 0, 0); + _screen->updateScreen(); + delayWithTicks(3); + } + + const uint16 *opcodes = 0; + switch (jewel - 1) { + case 0: + opcodes = specialJewelTable1; + break; + + case 1: + opcodes = specialJewelTable2; + break; + + case 2: + opcodes = specialJewelTable3; + break; + + case 3: + opcodes = specialJewelTable4; + break; + } + + if (opcodes) { + for (int i = 0; opcodes[i] != 0xFFFF; ++i) { + _screen->drawShape(page, _shapes[4+opcodes[i]], _amuletX2[jewel], _amuletY2[jewel], 0, 0); + _screen->updateScreen(); + delayWithTicks(3); + } + } + } + _screen->drawShape(page, _shapes[327+jewel], _amuletX2[jewel], _amuletY2[jewel], 0, 0); + _screen->updateScreen(); + _screen->showMouse(); + setGameFlag(0x55+jewel); +} + +void KyraEngine::seq_brandonHealing() { + debug(9, "seq_brandonHealing()"); + if (!(_deathHandler & 8)) + return; + if (_currentCharacter->sceneId == 210) { + if (_beadStateVar == 4 || _beadStateVar == 6) + return; + } + _screen->hideMouse(); + checkAmuletAnimFlags(); + assert(_healingShapeTable); + setupShapes123(_healingShapeTable, 22, 0); + _animator->setBrandonAnimSeqSize(3, 48); + snd_playSoundEffect(0x53); + for (int i = 123; i <= 144; ++i) { + _currentCharacter->currentAnimFrame = i; + _animator->animRefreshNPC(0); + delayWithTicks(8); + } + for (int i = 125; i >= 123; --i) { + _currentCharacter->currentAnimFrame = i; + _animator->animRefreshNPC(0); + delayWithTicks(8); + } + _animator->resetBrandonAnimSeqSize(); + _currentCharacter->currentAnimFrame = 7; + _animator->animRefreshNPC(0); + freeShapes123(); + _screen->showMouse(); +} + +void KyraEngine::seq_brandonHealing2() { + debug(9, "seq_brandonHealing2()"); + _screen->hideMouse(); + checkAmuletAnimFlags(); + assert(_healingShape2Table); + setupShapes123(_healingShape2Table, 30, 0); + resetBrandonPoisonFlags(); + _animator->setBrandonAnimSeqSize(3, 48); + snd_playSoundEffect(0x50); + for (int i = 123; i <= 152; ++i) { + _currentCharacter->currentAnimFrame = i; + _animator->animRefreshNPC(0); + delayWithTicks(8); + } + _animator->resetBrandonAnimSeqSize(); + _currentCharacter->currentAnimFrame = 7; + _animator->animRefreshNPC(0); + freeShapes123(); + _screen->showMouse(); + assert(_poisonGone); + if (_features & GF_TALKIE) { + snd_voiceWaitForFinish(); + snd_playVoiceFile(2010); + } + characterSays(_poisonGone[0], 0, -2); + if (_features & GF_TALKIE) { + snd_voiceWaitForFinish(); + snd_playVoiceFile(2011); + } + characterSays(_poisonGone[1], 0, -2); +} + +void KyraEngine::seq_poisonDeathNow(int now) { + debug(9, "seq_poisonDeathNow(%d)", now); + if (!(_brandonStatusBit & 1)) + return; + ++_poisonDeathCounter; + if (now) + _poisonDeathCounter = 2; + if (_poisonDeathCounter >= 2) { + snd_playWanderScoreViaMap(1, 1); + assert(_thePoison); + if (_features & GF_TALKIE) { + snd_voiceWaitForFinish(); + snd_playVoiceFile(7000); + } + characterSays(_thePoison[0], 0, -2); + if (_features & GF_TALKIE) { + snd_voiceWaitForFinish(); + snd_playVoiceFile(7001); + } + characterSays(_thePoison[1], 0, -2); + seq_poisonDeathNowAnim(); + _deathHandler = 3; + } else { + assert(_thePoison); + if (_features & GF_TALKIE) { + snd_voiceWaitForFinish(); + snd_playVoiceFile(7002); + } + characterSays(_thePoison[2], 0, -2); + if (_features & GF_TALKIE) { + snd_voiceWaitForFinish(); + snd_playVoiceFile(7004); + } + characterSays(_thePoison[3], 0, -2); + } +} + +void KyraEngine::seq_poisonDeathNowAnim() { + debug(9, "seq_poisonDeathNowAnim()"); + _screen->hideMouse(); + checkAmuletAnimFlags(); + assert(_posionDeathShapeTable); + setupShapes123(_posionDeathShapeTable, 20, 0); + _animator->setBrandonAnimSeqSize(8, 48); + + _currentCharacter->currentAnimFrame = 124; + _animator->animRefreshNPC(0); + delayWithTicks(30); + + _currentCharacter->currentAnimFrame = 123; + _animator->animRefreshNPC(0); + delayWithTicks(30); + + for (int i = 125; i <= 139; ++i) { + _currentCharacter->currentAnimFrame = i; + _animator->animRefreshNPC(0); + delayWithTicks(8); + } + + delayWithTicks(60); + + for (int i = 140; i <= 142; ++i) { + _currentCharacter->currentAnimFrame = i; + _animator->animRefreshNPC(0); + delayWithTicks(8); + } + + delayWithTicks(60); + + _animator->resetBrandonAnimSeqSize(); + freeShapes123(); + _animator->restoreAllObjectBackgrounds(); + _currentCharacter->x1 = _currentCharacter->x2 = -1; + _currentCharacter->y1 = _currentCharacter->y2 = -1; + _animator->preserveAllBackgrounds(); + _screen->showMouse(); +} + +void KyraEngine::seq_playFluteAnimation() { + debug(9, "seq_playFluteAnimation()"); + _screen->hideMouse(); + checkAmuletAnimFlags(); + setupShapes123(_fluteAnimShapeTable, 36, 0); + _animator->setBrandonAnimSeqSize(3, 75); + for (int i = 123; i <= 130; ++i) { + _currentCharacter->currentAnimFrame = i; + _animator->animRefreshNPC(0); + delayWithTicks(2); + } + + int delayTime = 0, soundType = 0; + if (queryGameFlag(0x85)) { + snd_playSoundEffect(0x63); + delayTime = 9; + soundType = 3; + } else if (!queryGameFlag(0x86)) { + snd_playSoundEffect(0x61); + delayTime = 2; + soundType = 1; + setGameFlag(0x86); + } else { + snd_playSoundEffect(0x62); + delayTime = 2; + soundType = 2; + } + + for (int i = 131; i <= 158; ++i) { + _currentCharacter->currentAnimFrame = i; + _animator->animRefreshNPC(0); + delayWithTicks(delayTime); + } + + for (int i = 126; i >= 123; --i) { + _currentCharacter->currentAnimFrame = i; + _animator->animRefreshNPC(0); + delayWithTicks(delayTime); + } + _animator->resetBrandonAnimSeqSize(); + _currentCharacter->currentAnimFrame = 7; + _animator->animRefreshNPC(0); + freeShapes123(); + _screen->showMouse(); + + if (soundType == 1) { + assert(_fluteString); + if (_features & GF_TALKIE) { + snd_voiceWaitForFinish(); + snd_playVoiceFile(1000); + } + characterSays(_fluteString[0], 0, -2); + } else if (soundType == 2) { + assert(_fluteString); + if (_features & GF_TALKIE) { + snd_voiceWaitForFinish(); + snd_playVoiceFile(1001); + } + characterSays(_fluteString[1], 0, -2); + } +} + +void KyraEngine::seq_winterScroll1() { + debug(9, "seq_winterScroll1()"); + _screen->hideMouse(); + checkAmuletAnimFlags(); + assert(_winterScrollTable); + assert(_winterScroll1Table); + assert(_winterScroll2Table); + setupShapes123(_winterScrollTable, 7, 0); + _animator->setBrandonAnimSeqSize(5, 66); + + for (int i = 123; i <= 129; ++i) { + _currentCharacter->currentAnimFrame = i; + _animator->animRefreshNPC(0); + delayWithTicks(8); + } + + freeShapes123(); + snd_playSoundEffect(0x20); + + uint8 numFrames, midpoint; + if (_features & GF_TALKIE) { + numFrames = 18; + midpoint = 136; + } else { + numFrames = 35; + midpoint = 147; + } + setupShapes123(_winterScroll1Table, numFrames, 0); + for (int i = 123; i < midpoint; ++i) { + _currentCharacter->currentAnimFrame = i; + _animator->animRefreshNPC(0); + delayWithTicks(8); + } + + if (_currentCharacter->sceneId == 41 && !queryGameFlag(0xA2)) { + snd_playSoundEffect(0x20); + _sprites->_anims[0].play = false; + _animator->sprites()[0].active = 0; + _sprites->_anims[1].play = true; + _animator->sprites()[1].active = 1; + setGameFlag(0xA2); + } + + for (int i = midpoint; i < 123 + numFrames; ++i) { + _currentCharacter->currentAnimFrame = i; + _animator->animRefreshNPC(0); + delayWithTicks(8); + } + + if (_currentCharacter->sceneId == 117 && !queryGameFlag(0xB3)) { + for (int i = 0; i <= 7; ++i) { + _sprites->_anims[i].play = false; + _animator->sprites()[i].active = 0; + } + uint8 tmpPal[768]; + memcpy(tmpPal, _screen->_currentPalette, 768); + memcpy(&tmpPal[684], palTable2()[0], 60); + _screen->fadePalette(tmpPal, 72); + memcpy(&_screen->_currentPalette[684], palTable2()[0], 60); + _screen->setScreenPalette(_screen->_currentPalette); + setGameFlag(0xB3); + } else { + delayWithTicks(120); + } + + freeShapes123(); + setupShapes123(_winterScroll2Table, 4, 0); + + for (int i = 123; i <= 126; ++i) { + _currentCharacter->currentAnimFrame = i; + _animator->animRefreshNPC(0); + delayWithTicks(8); + } + + _animator->resetBrandonAnimSeqSize(); + _currentCharacter->currentAnimFrame = 7; + _animator->animRefreshNPC(0); + freeShapes123(); + _screen->showMouse(); +} + +void KyraEngine::seq_winterScroll2() { + debug(9, "seq_winterScroll2()"); + _screen->hideMouse(); + checkAmuletAnimFlags(); + assert(_winterScrollTable); + setupShapes123(_winterScrollTable, 7, 0); + _animator->setBrandonAnimSeqSize(5, 66); + + for (int i = 123; i <= 128; ++i) { + _currentCharacter->currentAnimFrame = i; + _animator->animRefreshNPC(0); + delayWithTicks(8); + } + + delayWithTicks(120); + + for (int i = 127; i >= 123; --i) { + _currentCharacter->currentAnimFrame = i; + _animator->animRefreshNPC(0); + delayWithTicks(8); + } + + _animator->resetBrandonAnimSeqSize(); + _currentCharacter->currentAnimFrame = 7; + _animator->animRefreshNPC(0); + freeShapes123(); + _screen->showMouse(); +} + +void KyraEngine::seq_makeBrandonInv() { + debug(9, "seq_makeBrandonInv()"); + if (_deathHandler == 8) + return; + + if (_currentCharacter->sceneId == 210) { + if (_beadStateVar == 4 || _beadStateVar == 6) + return; + } + + _screen->hideMouse(); + checkAmuletAnimFlags(); + _brandonStatusBit |= 0x20; + setTimerCountdown(18, 2700); + _brandonStatusBit |= 0x40; + snd_playSoundEffect(0x77); + _brandonInvFlag = 0; + while (_brandonInvFlag <= 0x100) { + _animator->animRefreshNPC(0); + delayWithTicks(10); + _brandonInvFlag += 0x10; + } + _brandonStatusBit &= 0xFFBF; + _screen->showMouse(); +} + +void KyraEngine::seq_makeBrandonNormal() { + debug(9, "seq_makeBrandonNormal()"); + _screen->hideMouse(); + _brandonStatusBit |= 0x40; + snd_playSoundEffect(0x77); + _brandonInvFlag = 0x100; + while (_brandonInvFlag >= 0) { + _animator->animRefreshNPC(0); + delayWithTicks(10); + _brandonInvFlag -= 0x10; + } + _brandonInvFlag = 0; + _brandonStatusBit &= 0xFF9F; + _screen->showMouse(); +} + +void KyraEngine::seq_makeBrandonNormal2() { + debug(9, "seq_makeBrandonNormal2()"); + _screen->hideMouse(); + assert(_brandonToWispTable); + setupShapes123(_brandonToWispTable, 26, 0); + _animator->setBrandonAnimSeqSize(5, 48); + _brandonStatusBit &= 0xFFFD; + snd_playSoundEffect(0x6C); + for (int i = 138; i >= 123; --i) { + _currentCharacter->currentAnimFrame = i; + _animator->animRefreshNPC(0); + delayWithTicks(8); + } + _animator->setBrandonAnimSeqSize(4, 48); + _currentCharacter->currentAnimFrame = 7; + _animator->animRefreshNPC(0); + if (_currentCharacter->sceneId >= 229 && _currentCharacter->sceneId <= 245) { + _screen->fadeSpecialPalette(31, 234, 13, 4); + } else if (_currentCharacter->sceneId >= 118 && _currentCharacter->sceneId <= 186) { + _screen->fadeSpecialPalette(14, 228, 15, 4); + } + freeShapes123(); + _screen->showMouse(); +} + +void KyraEngine::seq_makeBrandonWisp() { + debug(9, "seq_makeBrandonWisp()"); + if (_deathHandler == 8) + return; + + if (_currentCharacter->sceneId == 210) { + if (_beadStateVar == 4 || _beadStateVar == 6) + return; + } + _screen->hideMouse(); + checkAmuletAnimFlags(); + assert(_brandonToWispTable); + setupShapes123(_brandonToWispTable, 26, 0); + _animator->setBrandonAnimSeqSize(5, 48); + snd_playSoundEffect(0x6C); + for (int i = 123; i <= 138; ++i) { + _currentCharacter->currentAnimFrame = i; + _animator->animRefreshNPC(0); + delayWithTicks(8); + } + _brandonStatusBit |= 2; + if (_currentCharacter->sceneId >= 109 && _currentCharacter->sceneId <= 198) { + setTimerCountdown(14, 18000); + } else { + setTimerCountdown(14, 7200); + } + _animator->_brandonDrawFrame = 113; + _brandonStatusBit0x02Flag = 1; + _currentCharacter->currentAnimFrame = 113; + _animator->animRefreshNPC(0); + _animator->updateAllObjectShapes(); + if (_currentCharacter->sceneId >= 229 && _currentCharacter->sceneId <= 245) { + _screen->fadeSpecialPalette(30, 234, 13, 4); + } else if (_currentCharacter->sceneId >= 118 && _currentCharacter->sceneId <= 186) { + _screen->fadeSpecialPalette(14, 228, 15, 4); + } + freeShapes123(); + _screen->showMouse(); +} + +void KyraEngine::seq_dispelMagicAnimation() { + debug(9, "seq_dispelMagicAnimation()"); + if (_deathHandler == 8) + return; + if (_currentCharacter->sceneId == 210) { + if (_beadStateVar == 4 || _beadStateVar == 6) + return; + } + _screen->hideMouse(); + if (_currentCharacter->sceneId == 210 && _currentCharacter->sceneId < 160) + _currentCharacter->facing = 3; + if (_malcolmFlag == 7 && _beadStateVar == 3) { + _beadStateVar = 6; + _unkEndSeqVar5 = 2; + _malcolmFlag = 10; + } + checkAmuletAnimFlags(); + setGameFlag(0xEE); + assert(_magicAnimationTable); + setupShapes123(_magicAnimationTable, 5, 0); + _animator->setBrandonAnimSeqSize(8, 49); + snd_playSoundEffect(0x15); + for (int i = 123; i <= 127; ++i) { + _currentCharacter->currentAnimFrame = i; + _animator->animRefreshNPC(0); + delayWithTicks(8); + } + + delayWithTicks(120); + + for (int i = 127; i >= 123; --i) { + _currentCharacter->currentAnimFrame = i; + _animator->animRefreshNPC(0); + delayWithTicks(10); + } + _animator->resetBrandonAnimSeqSize(); + _currentCharacter->currentAnimFrame = 7; + _animator->animRefreshNPC(0); + freeShapes123(); + _screen->showMouse(); +} + +void KyraEngine::seq_fillFlaskWithWater(int item, int type) { + debug(9, "seq_fillFlaskWithWater(%d, %d)", item, type); + int newItem = -1; + static const uint8 flaskTable1[] = { 0x46, 0x48, 0x4A, 0x4C }; + static const uint8 flaskTable2[] = { 0x47, 0x49, 0x4B, 0x4D }; + + if (item >= 60 && item <= 77) { + assert(_flaskFull); + if (_features & GF_TALKIE) { + snd_voiceWaitForFinish(); + snd_playVoiceFile(8006); + } + characterSays(_flaskFull[0], 0, -2); + } else if (item == 78) { + assert(type >= 0 && type < ARRAYSIZE(flaskTable1)); + newItem = flaskTable1[type]; + } else if (item == 79) { + assert(type >= 0 && type < ARRAYSIZE(flaskTable2)); + newItem = flaskTable2[type]; + } + + if (newItem == -1) + return; + + _screen->hideMouse(); + setMouseItem(newItem); + _screen->showMouse(); + _itemInHand = newItem; + assert(_fullFlask); + assert(type < _fullFlask_Size && type >= 0); + if (_features & GF_TALKIE) { + snd_voiceWaitForFinish(); + static const uint16 voiceEntries[] = { + 0x1F40, 0x1F41, 0x1F42, 0x1F45 + }; + assert(type < ARRAYSIZE(voiceEntries)); + snd_playVoiceFile(voiceEntries[type]); + } + characterSays(_fullFlask[type], 0, -2); +} + +void KyraEngine::seq_playDrinkPotionAnim(int unk1, int unk2, int flags) { + debug(9, "KyraEngine::seq_playDrinkPotionAnim(%d, %d, %d)", unk1, unk2, flags); + // XXX + _screen->hideMouse(); + checkAmuletAnimFlags(); + _currentCharacter->facing = 5; + _animator->animRefreshNPC(0); + assert(_drinkAnimationTable); + setupShapes123(_drinkAnimationTable, 9, flags); + _animator->setBrandonAnimSeqSize(5, 54); + + for (int i = 123; i <= 131; ++i) { + _currentCharacter->currentAnimFrame = i; + _animator->animRefreshNPC(0); + delayWithTicks(5); + } + snd_playSoundEffect(0x34); + for (int i = 0; i < 2; ++i) { + _currentCharacter->currentAnimFrame = 130; + _animator->animRefreshNPC(0); + delayWithTicks(7); + _currentCharacter->currentAnimFrame = 131; + _animator->animRefreshNPC(0); + delayWithTicks(7); + } + + if (unk2) { + // XXX + } + + for (int i = 131; i >= 123; --i) { + _currentCharacter->currentAnimFrame = i; + _animator->animRefreshNPC(0); + delayWithTicks(5); + } + + _animator->resetBrandonAnimSeqSize(); + _currentCharacter->currentAnimFrame = 7; + _animator->animRefreshNPC(0); + freeShapes123(); + _screen->showMouse(); +} + +int KyraEngine::seq_playEnd() { + debug(9, "KyraEngine::seq_playEnd()"); + if (_endSequenceSkipFlag) { + return 0; + } + if (_deathHandler == 8) { + return 0; + } + _screen->_curPage = 2; + if (_endSequenceNeedLoading) { + snd_playWanderScoreViaMap(50, 1); + setupPanPages(); + _finalA = new WSAMovieV1(this); + assert(_finalA); + _finalA->open("finala.wsa", 1, 0); + _finalB = new WSAMovieV1(this); + assert(_finalB); + _finalB->open("finalb.wsa", 1, 0); + _finalC = new WSAMovieV1(this); + assert(_finalC); + _endSequenceNeedLoading = 0; + _finalC->open("finalc.wsa", 1, 0); + _screen->_curPage = 0; + _beadStateVar = 0; + _malcolmFlag = 0; + // wired stuff with _unkEndSeqVar2 which needs timer handling + _screen->copyRegion(312, 0, 312, 0, 8, 136, 0, 2); + } + if (handleMalcolmFlag()) { + _beadStateVar = 0; + _malcolmFlag = 12; + handleMalcolmFlag(); + handleBeadState(); + closeFinalWsa(); + if (_deathHandler == 8) { + _screen->_curPage = 0; + checkAmuletAnimFlags(); + seq_brandonToStone(); + delay(60 * _tickLength); + return 1; + } else { + _endSequenceSkipFlag = 1; + if (_text->printed()) { + _text->restoreTalkTextMessageBkgd(2, 0); + } + _screen->_curPage = 0; + _screen->hideMouse(); + _screen->fadeSpecialPalette(32, 228, 20, 60); + delay(60 * _tickLength); + loadBitmap("GEMHEAL.CPS", 3, 3, _screen->_currentPalette); + _screen->setScreenPalette(_screen->_currentPalette); + _screen->shuffleScreen(8, 8, 304, 128, 2, 0, 1, 0); + uint32 nextTime = _system->getMillis() + 120 * _tickLength; + _finalA = new WSAMovieV1(this); + assert(_finalA); + _finalA->open("finald.wsa", 1, 0); + _finalA->_x = _finalA->_y = 8; + _finalA->_drawPage = 0; + while (_system->getMillis() < nextTime) {} + snd_playSoundEffect(0x40); + for (int i = 0; i < 22; ++i) { + while (_system->getMillis() < nextTime) {} + if (i == 4) { + snd_playSoundEffect(0x3E); + } else if (i == 20) { + snd_playSoundEffect(0x0E); + } + nextTime = _system->getMillis() + 8 * _tickLength; + _finalA->displayFrame(i); + _screen->updateScreen(); + } + delete _finalA; + _finalA = 0; + seq_playEnding(); + return 1; + } + } else { + handleBeadState(); + _screen->bitBlitRects(); + _screen->updateScreen(); + _screen->_curPage = 0; + } + return 0; +} + +void KyraEngine::seq_brandonToStone() { + debug(9, "KyraEngine::seq_brandonToStone()"); + _screen->hideMouse(); + assert(_brandonStoneTable); + setupShapes123(_brandonStoneTable, 14, 0); + _animator->setBrandonAnimSeqSize(5, 51); + for (int i = 123; i <= 136; ++i) { + _currentCharacter->currentAnimFrame = i; + _animator->animRefreshNPC(0); + delayWithTicks(8); + } + _animator->resetBrandonAnimSeqSize(); + freeShapes123(); + _screen->showMouse(); +} + +void KyraEngine::seq_playEnding() { + debug(9, "KyraEngine::seq_playEnding()"); + _screen->hideMouse(); + res_unloadResources(RES_INGAME); + res_loadResources(RES_OUTRO); + loadBitmap("REUNION.CPS", 3, 3, _screen->_currentPalette); + _screen->copyRegion(8, 8, 8, 8, 304, 128, 2, 0); + _screen->_curPage = 0; + // XXX + assert(_homeString); + drawSentenceCommand(_homeString[0], 179); + _screen->_curPage = 0; + _screen->fadeToBlack(); + _seq->playSequence(_seq_Reunion, false); + _screen->fadeToBlack(); + _screen->showMouse(); + seq_playCredits(); +} + +void KyraEngine::seq_playCredits() { + debug(9, "KyraEngine::seq_playCredits()"); + static const uint8 colorMap[] = { 0, 0, 0xC, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + _screen->hideMouse(); + uint32 sz = 0; + if (_features & GF_FLOPPY) { + _screen->loadFont(Screen::FID_CRED6_FNT, _res->fileData("CREDIT6.FNT", &sz)); + _screen->loadFont(Screen::FID_CRED8_FNT, _res->fileData("CREDIT8.FNT", &sz)); + } + loadBitmap("CHALET.CPS", 2, 2, _screen->_currentPalette); + _screen->setScreenPalette(_screen->_currentPalette); + _screen->setCurPage(0); + _screen->clearCurPage(); + _screen->copyRegion(8, 8, 8, 8, 304, 128, 2, 0); + _screen->setTextColorMap(colorMap); + _screen->_charWidth = -1; + snd_playWanderScoreViaMap(53, 1); + // delete + _screen->updateScreen(); + // XXX + delay(120 * _tickLength); // wait until user presses escape normally + _screen->fadeToBlack(); + _screen->clearCurPage(); + _screen->showMouse(); +} + +bool KyraEngine::seq_skipSequence() const { + debug(9, "KyraEngine::seq_skipSequence()"); + return _quitFlag || _abortIntroFlag; +} + +int KyraEngine::handleMalcolmFlag() { + debug(9, "KyraEngine::handleMalcolmFlag()"); + static uint16 frame = 0; + static uint32 timer1 = 0; + static uint32 timer2 = 0; + + switch (_malcolmFlag) { + case 1: + frame = 0; + _malcolmFlag = 2; + timer2 = 0; + case 2: + if (_system->getMillis() >= timer2) { + _finalA->_x = 8; + _finalA->_y = 46; + _finalA->_drawPage = 0; + _finalA->displayFrame(frame); + _screen->updateScreen(); + timer2 = _system->getMillis() + 8 * _tickLength; + ++frame; + if (frame > 13) { + _malcolmFlag = 3; + timer1 = _system->getMillis() + 180 * _tickLength; + } + } + break; + + case 3: + if (_system->getMillis() < timer1) { + if (_system->getMillis() >= timer2) { + frame = _rnd.getRandomNumberRng(14, 17); + _finalA->_x = 8; + _finalA->_y = 46; + _finalA->_drawPage = 0; + _finalA->displayFrame(frame); + _screen->updateScreen(); + timer2 = _system->getMillis() + 8 * _tickLength; + } + } else { + _malcolmFlag = 4; + frame = 18; + } + break; + + case 4: + if (_system->getMillis() >= timer2) { + _finalA->_x = 8; + _finalA->_y = 46; + _finalA->_drawPage = 0; + _finalA->displayFrame(frame); + _screen->updateScreen(); + timer2 = _system->getMillis() + 8 * _tickLength; + ++frame; + if (frame > 25) { + frame = 26; + _malcolmFlag = 5; + _beadStateVar = 1; + } + } + break; + + case 5: + if (_system->getMillis() >= timer2) { + _finalA->_x = 8; + _finalA->_y = 46; + _finalA->_drawPage = 0; + _finalA->displayFrame(frame); + _screen->updateScreen(); + timer2 = _system->getMillis() + 8 * _tickLength; + ++frame; + if (frame > 31) { + frame = 32; + _malcolmFlag = 6; + } + } + break; + + case 6: + if (_unkEndSeqVar4) { + if (frame <= 33 && _system->getMillis() >= timer2) { + _finalA->_x = 8; + _finalA->_y = 46; + _finalA->_drawPage = 0; + _finalA->displayFrame(frame); + _screen->updateScreen(); + timer2 = _system->getMillis() + 8 * _tickLength; + ++frame; + if (frame > 33) { + _malcolmFlag = 7; + frame = 32; + _unkEndSeqVar5 = 0; + } + } + } + break; + + case 7: + if (_unkEndSeqVar5 == 1) { + _malcolmFlag = 8; + frame = 34; + } else if (_unkEndSeqVar5 == 2) { + _malcolmFlag = 3; + timer1 = _system->getMillis() + 180 * _tickLength; + } + break; + + case 8: + if (_system->getMillis() >= timer2) { + _finalA->_x = 8; + _finalA->_y = 46; + _finalA->_drawPage = 0; + _finalA->displayFrame(frame); + _screen->updateScreen(); + timer2 = _system->getMillis() + 8 * _tickLength; + ++frame; + if (frame > 37) { + _malcolmFlag = 0; + _deathHandler = 8; + return 1; + } + } + break; + + case 9: + snd_playSoundEffect(12); + snd_playSoundEffect(12); + _finalC->_x = 16; + _finalC->_y = 50; + _finalC->_drawPage = 0; + for (int i = 0; i < 18; ++i) { + timer2 = _system->getMillis() + 4 * _tickLength; + _finalC->displayFrame(i); + _screen->updateScreen(); + while (_system->getMillis() < timer2) {} + } + snd_playWanderScoreViaMap(51, 1); + delay(60*_tickLength); + _malcolmFlag = 0; + return 1; + break; + + case 10: + if (!_beadStateVar) { + handleBeadState(); + _screen->bitBlitRects(); + assert(_veryClever); + _text->printTalkTextMessage(_veryClever[0], 60, 31, 5, 0, 2); + timer2 = _system->getMillis() + 180 * _tickLength; + _malcolmFlag = 11; + } + break; + + case 11: + if (_system->getMillis() >= timer2) { + _text->restoreTalkTextMessageBkgd(2, 0); + _malcolmFlag = 3; + timer1 = _system->getMillis() + 180 * _tickLength; + } + break; + + default: + break; + } + + return 0; +} + +int KyraEngine::handleBeadState() { + debug(9, "KyraEngine::handleBeadState()"); + static uint32 timer1 = 0; + static uint32 timer2 = 0; + static BeadState beadState1 = { -1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + static BeadState beadState2 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + + static const int table1[] = { + -1, -2, -4, -5, -6, -7, -6, -5, + -4, -2, -1, 0, 1, 2, 4, 5, + 6, 7, 6, 5, 4, 2, 1, 0, 0 + }; + static const int table2[] = { + 0, 0, 1, 1, 2, 2, 3, 3, + 4, 4, 5, 5, 5, 5, 4, 4, + 3, 3, 2, 2, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + switch (_beadStateVar) { + case 0: + if (beadState1.x != -1 && _endSequenceBackUpRect) { + _screen->copyFromCurPageBlock(beadState1.x >> 3, beadState1.y, beadState1.width, beadState1.height, _endSequenceBackUpRect); + _screen->addBitBlitRect(beadState1.x, beadState1.y, beadState1.width2, beadState1.height); + } else { + beadState1.x = -1; + beadState1.tableIndex = 0; + timer1 = 0; + timer2 = 0; + _lastDisplayedPanPage = 0; + return 1; + } + + case 1: + if (beadState1.x != -1) { + if (_endSequenceBackUpRect) { + _screen->copyFromCurPageBlock(beadState1.x >> 3, beadState1.y, beadState1.width, beadState1.height, _endSequenceBackUpRect); + _screen->addBitBlitRect(beadState1.x, beadState1.y, beadState1.width2, beadState1.height); + } + beadState1.x = -1; + beadState1.tableIndex = 0; + } + _beadStateVar = 2; + break; + + case 2: + if (_system->getMillis() >= timer1) { + int x = 0, y = 0; + timer1 = _system->getMillis() + 4 * _tickLength; + if (beadState1.x == -1) { + assert(_panPagesTable); + beadState1.width2 = _animator->fetchAnimWidth(_panPagesTable[19], 256); + beadState1.width = ((beadState1.width2 + 7) >> 3) + 1; + beadState1.height = _animator->fetchAnimHeight(_panPagesTable[19], 256); + if (!_endSequenceBackUpRect) { + _endSequenceBackUpRect = new uint8[(beadState1.width * beadState1.height) << 3]; + assert(_endSequenceBackUpRect); + memset(_endSequenceBackUpRect, 0, ((beadState1.width * beadState1.height) << 3) * sizeof(uint8)); + } + x = beadState1.x = 60; + y = beadState1.y = 40; + initBeadState(x, y, x, 25, 8, &beadState2); + } else { + if (processBead(beadState1.x, beadState1.y, x, y, &beadState2)) { + _beadStateVar = 3; + timer2 = _system->getMillis() + 240 * _tickLength; + _unkEndSeqVar4 = 0; + beadState1.dstX = beadState1.x; + beadState1.dstY = beadState1.y; + return 0; + } else { + _screen->copyFromCurPageBlock(beadState1.x >> 3, beadState1.y, beadState1.width, beadState1.height, _endSequenceBackUpRect); + _screen->addBitBlitRect(beadState1.x, beadState1.y, beadState1.width2, beadState1.height); + beadState1.x = x; + beadState1.y = y; + } + } + _screen->copyCurPageBlock(x >> 3, y, beadState1.width, beadState1.height, _endSequenceBackUpRect); + _screen->drawShape(2, _panPagesTable[_lastDisplayedPanPage++], x, y, 0, 0); + if (_lastDisplayedPanPage > 17) + _lastDisplayedPanPage = 0; + _screen->addBitBlitRect(x, y, beadState1.width2, beadState1.height); + } + break; + + case 3: + if (_system->getMillis() >= timer1) { + timer1 = _system->getMillis() + 4 * _tickLength; + _screen->copyFromCurPageBlock(beadState1.x >> 3, beadState1.y, beadState1.width, beadState1.height, _endSequenceBackUpRect); + _screen->addBitBlitRect(beadState1.x, beadState1.y, beadState1.width2, beadState1.height); + beadState1.x = beadState1.dstX + table1[beadState1.tableIndex]; + beadState1.y = beadState1.dstY + table2[beadState1.tableIndex]; + _screen->copyCurPageBlock(beadState1.x >> 3, beadState1.y, beadState1.width, beadState1.height, _endSequenceBackUpRect); + _screen->drawShape(2, _panPagesTable[_lastDisplayedPanPage++], beadState1.x, beadState1.y, 0, 0); + if (_lastDisplayedPanPage >= 17) { + _lastDisplayedPanPage = 0; + } + _screen->addBitBlitRect(beadState1.x, beadState1.y, beadState1.width2, beadState1.height); + ++beadState1.tableIndex; + if (beadState1.tableIndex > 24) { + beadState1.tableIndex = 0; + _unkEndSeqVar4 = 1; + } + if (_system->getMillis() > timer2 && _malcolmFlag == 7 && !_unkAmuletVar && !_text->printed()) { + snd_playSoundEffect(0x0B); + if (_currentCharacter->x1 > 233 && _currentCharacter->x1 < 305 && _currentCharacter->y1 > 85 && _currentCharacter->y1 < 105 && + (_brandonStatusBit & 0x20)) { + beadState1.unk8 = 290; + beadState1.unk9 = 40; + _beadStateVar = 5; + } else { + _beadStateVar = 4; + beadState1.unk8 = _currentCharacter->x1 - 4; + beadState1.unk9 = _currentCharacter->y1 - 30; + } + + if (_text->printed()) { + _text->restoreTalkTextMessageBkgd(2, 0); + } + initBeadState(beadState1.x, beadState1.y, beadState1.unk8, beadState1.unk9, 6, &beadState2); + _lastDisplayedPanPage = 18; + } + } + break; + + case 4: + if (_system->getMillis() >= timer1) { + int x = 0, y = 0; + timer1 = _system->getMillis(); + if (processBead(beadState1.x, beadState1.y, x, y, &beadState2)) { + if (_brandonStatusBit & 20) { + _unkEndSeqVar5 = 2; + _beadStateVar = 6; + } else { + snd_playWanderScoreViaMap(52, 1); + snd_playSoundEffect(0x0C); + _unkEndSeqVar5 = 1; + _beadStateVar = 0; + } + } else { + _screen->copyFromCurPageBlock(beadState1.x >> 3, beadState1.y, beadState1.width, beadState1.height, _endSequenceBackUpRect); + _screen->addBitBlitRect(beadState1.x, beadState1.y, beadState1.width2, beadState1.height); + beadState1.x = x; + beadState1.y = y; + _screen->copyCurPageBlock(beadState1.x >> 3, beadState1.y, beadState1.width, beadState1.height, _endSequenceBackUpRect); + _screen->drawShape(2, _panPagesTable[_lastDisplayedPanPage++], x, y, 0, 0); + if (_lastDisplayedPanPage > 17) { + _lastDisplayedPanPage = 0; + } + _screen->addBitBlitRect(beadState1.x, beadState1.y, beadState1.width2, beadState1.height); + } + } + break; + + case 5: + if (_system->getMillis() >= timer1) { + timer1 = _system->getMillis(); + int x = 0, y = 0; + if (processBead(beadState1.x, beadState1.y, x, y, &beadState2)) { + if (beadState1.dstX == 290) { + _screen->copyFromCurPageBlock(beadState1.x >> 3, beadState1.y, beadState1.width, beadState1.height, _endSequenceBackUpRect); + uint32 nextRun = 0; + _finalB->_x = 224; + _finalB->_y = 8; + _finalB->_drawPage = 0; + for (int i = 0; i < 8; ++i) { + nextRun = _system->getMillis() + _tickLength; + _finalB->displayFrame(i); + _screen->updateScreen(); + while (_system->getMillis() < nextRun) {} + } + snd_playSoundEffect(0x0D); + for (int i = 7; i >= 0; --i) { + nextRun = _system->getMillis() + _tickLength; + _finalB->displayFrame(i); + _screen->updateScreen(); + while (_system->getMillis() < nextRun) {} + } + initBeadState(beadState1.x, beadState1.y, 63, 60, 6, &beadState2); + } else { + _screen->copyFromCurPageBlock(beadState1.x >> 3, beadState1.y, beadState1.width, beadState1.height, _endSequenceBackUpRect); + _screen->addBitBlitRect(beadState1.x, beadState1.y, beadState1.width2, beadState1.height); + beadState1.x = -1; + beadState1.tableIndex = 0; + _beadStateVar = 0; + _malcolmFlag = 9; + } + } else { + _screen->copyFromCurPageBlock(beadState1.x >> 3, beadState1.y, beadState1.width, beadState1.height, _endSequenceBackUpRect); + _screen->addBitBlitRect(beadState1.x, beadState1.y, beadState1.width2, beadState1.height); + beadState1.x = x; + beadState1.y = y; + _screen->copyCurPageBlock(beadState1.x >> 3, beadState1.y, beadState1.width, beadState1.height, _endSequenceBackUpRect); + _screen->drawShape(2, _panPagesTable[_lastDisplayedPanPage++], x, y, 0, 0); + if (_lastDisplayedPanPage > 17) { + _lastDisplayedPanPage = 0; + } + _screen->addBitBlitRect(beadState1.x, beadState1.y, beadState1.width2, beadState1.height); + } + } + break; + + case 6: + _screen->drawShape(2, _panPagesTable[19], beadState1.x, beadState1.y, 0, 0); + _screen->addBitBlitRect(beadState1.x, beadState1.y, beadState1.width2, beadState1.height); + _beadStateVar = 0; + break; + + default: + break; + } + return 0; +} + +void KyraEngine::initBeadState(int x, int y, int x2, int y2, int unk, BeadState *ptr) { + debug(9, "KyraEngine::initBeadState(%d, %d, %d, %d, %d, 0x%X)", x, y, x2, y2, unk, ptr); + ptr->unk9 = unk; + int xDiff = x2 - x; + int yDiff = y2 - y; + int unk1 = 0, unk2 = 0; + if (xDiff > 0) { + unk1 = 1; + } else if (xDiff == 0) { + unk1 = 0; + } else { + unk1 = -1; + } + + if (yDiff > 0) { + unk2 = 1; + } else if (yDiff == 0) { + unk2 = 0; + } else { + unk2 = -1; + } + + xDiff = abs(xDiff); + yDiff = abs(yDiff); + + ptr->y = 0; + ptr->x = 0; + ptr->width = xDiff; + ptr->height = yDiff; + ptr->dstX = x2; + ptr->dstY = y2; + ptr->width2 = unk1; + ptr->unk8 = unk2; +} + +int KyraEngine::processBead(int x, int y, int &x2, int &y2, BeadState *ptr) { + debug(9, "KyraEngine::processBead(%d, %d, 0x%X, 0x%X, 0x%X)", x, y, &x2, &y2, ptr); + if (x == ptr->dstX && y == ptr->dstY) { + return 1; + } + + int xPos = x, yPos = y; + if (ptr->width >= ptr->height) { + for (int i = 0; i < ptr->unk9; ++i) { + ptr->y += ptr->height; + if (ptr->y >= ptr->width) { + ptr->y -= ptr->width; + yPos += ptr->unk8; + } + xPos += ptr->width2; + } + } else { + for (int i = 0; i < ptr->unk9; ++i) { + ptr->x += ptr->width; + if (ptr->x >= ptr->height) { + ptr->x -= ptr->height; + xPos += ptr->width2; + } + yPos += ptr->unk8; + } + } + + int temp = abs(x - ptr->dstX); + if (ptr->unk9 > temp) { + xPos = ptr->dstX; + } + temp = abs(y - ptr->dstY); + if (ptr->unk9 > temp) { + yPos = ptr->dstY; + } + x2 = xPos; + y2 = yPos; + return 0; +} + +void KyraEngine::setupPanPages() { + debug(9, "KyraEngine::setupPanPages()"); + loadBitmap("bead.cps", 3, 3, 0); + for (int i = 0; i <= 19; ++i) { + _panPagesTable[i] = _seq->setPanPages(3, i); + } +} + +void KyraEngine::freePanPages() { + debug(9, "KyraEngine::freePanPages()"); + delete _endSequenceBackUpRect; + _endSequenceBackUpRect = 0; + for (int i = 0; i <= 19; ++i) { + free(_panPagesTable[i]); + _panPagesTable[i] = NULL; + } +} + +void KyraEngine::closeFinalWsa() { + debug(9, "KyraEngine::closeFinalWsa()"); + delete _finalA; + _finalA = 0; + delete _finalB; + _finalB = 0; + delete _finalC; + _finalC = 0; + freePanPages(); + _endSequenceNeedLoading = 1; +} + +void KyraEngine::updateKyragemFading() { + static const uint8 kyraGemPalette[0x28] = { + 0x3F, 0x3B, 0x38, 0x34, 0x32, 0x2F, 0x2C, 0x29, 0x25, 0x22, + 0x1F, 0x1C, 0x19, 0x16, 0x12, 0x0F, 0x0C, 0x0A, 0x06, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + if (_system->getMillis() < _kyragemFadingState.timerCount) + return; + + _kyragemFadingState.timerCount = _system->getMillis() + 4 * _tickLength; + int palPos = 684; + for (int i = 0; i < 20; ++i) { + _screen->_currentPalette[palPos++] = kyraGemPalette[i + _kyragemFadingState.rOffset]; + _screen->_currentPalette[palPos++] = kyraGemPalette[i + _kyragemFadingState.gOffset]; + _screen->_currentPalette[palPos++] = kyraGemPalette[i + _kyragemFadingState.bOffset]; + } + _screen->setScreenPalette(_screen->_currentPalette); + _animator->_updateScreen = true; + switch (_kyragemFadingState.nextOperation) { + case 0: + --_kyragemFadingState.bOffset; + if (_kyragemFadingState.bOffset >= 1) + return; + _kyragemFadingState.nextOperation = 1; + break; + + case 1: + ++_kyragemFadingState.rOffset; + if (_kyragemFadingState.rOffset < 19) + return; + _kyragemFadingState.nextOperation = 2; + break; + + case 2: + --_kyragemFadingState.gOffset; + if (_kyragemFadingState.gOffset >= 1) + return; + _kyragemFadingState.nextOperation = 3; + break; + + case 3: + ++_kyragemFadingState.bOffset; + if (_kyragemFadingState.bOffset < 19) + return; + _kyragemFadingState.nextOperation = 4; + break; + + case 4: + --_kyragemFadingState.rOffset; + if (_kyragemFadingState.rOffset >= 1) + return; + _kyragemFadingState.nextOperation = 5; + break; + + case 5: + ++_kyragemFadingState.gOffset; + if (_kyragemFadingState.gOffset < 19) + return; + _kyragemFadingState.nextOperation = 0; + break; + + default: + break; + } + + _kyragemFadingState.timerCount = _system->getMillis() + 120 * _tickLength; +} + +void KyraEngine::drawJewelPress(int jewel, int drawSpecial) { + debug(9, "KyraEngine::drawJewelPress(%d, %d)", jewel, drawSpecial); + _screen->hideMouse(); + int shape = 0; + if (drawSpecial) { + shape = 0x14E; + } else { + shape = jewel + 0x149; + } + snd_playSoundEffect(0x45); + _screen->drawShape(0, _shapes[4+shape], _amuletX2[jewel], _amuletY2[jewel], 0, 0); + _screen->updateScreen(); + delayWithTicks(2); + if (drawSpecial) { + shape = 0x148; + } else { + shape = jewel + 0x143; + } + _screen->drawShape(0, _shapes[4+shape], _amuletX2[jewel], _amuletY2[jewel], 0, 0); + _screen->updateScreen(); + _screen->showMouse(); +} + +void KyraEngine::drawJewelsFadeOutStart() { + debug(9, "KyraEngine::drawJewelsFadeOutStart()"); + static const uint16 jewelTable1[] = { 0x164, 0x15F, 0x15A, 0x155, 0x150, 0xFFFF }; + static const uint16 jewelTable2[] = { 0x163, 0x15E, 0x159, 0x154, 0x14F, 0xFFFF }; + static const uint16 jewelTable3[] = { 0x166, 0x160, 0x15C, 0x157, 0x152, 0xFFFF }; + static const uint16 jewelTable4[] = { 0x165, 0x161, 0x15B, 0x156, 0x151, 0xFFFF }; + for (int i = 0; jewelTable1[i] != 0xFFFF; ++i) { + if (queryGameFlag(0x57)) { + _screen->drawShape(0, _shapes[4+jewelTable1[i]], _amuletX2[2], _amuletY2[2], 0, 0); + } + if (queryGameFlag(0x59)) { + _screen->drawShape(0, _shapes[4+jewelTable3[i]], _amuletX2[4], _amuletY2[4], 0, 0); + } + if (queryGameFlag(0x56)) { + _screen->drawShape(0, _shapes[4+jewelTable2[i]], _amuletX2[1], _amuletY2[1], 0, 0); + } + if (queryGameFlag(0x58)) { + _screen->drawShape(0, _shapes[4+jewelTable4[i]], _amuletX2[3], _amuletY2[3], 0, 0); + } + _screen->updateScreen(); + delayWithTicks(3); + } +} + +void KyraEngine::drawJewelsFadeOutEnd(int jewel) { + debug(9, "KyraEngine::drawJewelsFadeOutEnd(%d)", jewel); + static const uint16 jewelTable[] = { 0x153, 0x158, 0x15D, 0x162, 0x148, 0xFFFF }; + int newDelay = 0; + switch (jewel-1) { + case 2: + if (_currentCharacter->sceneId >= 109 && _currentCharacter->sceneId <= 198) { + newDelay = 18900; + } else { + newDelay = 8100; + } + break; + + default: + newDelay = 3600; + break; + } + setGameFlag(0xF1); + setTimerCountdown(19, newDelay); + _screen->hideMouse(); + for (int i = 0; jewelTable[i] != 0xFFFF; ++i) { + uint16 shape = jewelTable[i]; + if (queryGameFlag(0x57)) { + _screen->drawShape(0, _shapes[4+shape], _amuletX2[2], _amuletY2[2], 0, 0); + } + if (queryGameFlag(0x59)) { + _screen->drawShape(0, _shapes[4+shape], _amuletX2[4], _amuletY2[4], 0, 0); + } + if (queryGameFlag(0x56)) { + _screen->drawShape(0, _shapes[4+shape], _amuletX2[1], _amuletY2[1], 0, 0); + } + if (queryGameFlag(0x58)) { + _screen->drawShape(0, _shapes[4+shape], _amuletX2[3], _amuletY2[3], 0, 0); + } + _screen->updateScreen(); + delayWithTicks(3); + } + _screen->showMouse(); +} + +} // end of namespace Kyra diff --git a/engines/kyra/sound.cpp b/engines/kyra/sound.cpp new file mode 100644 index 0000000000..48e59a5d15 --- /dev/null +++ b/engines/kyra/sound.cpp @@ -0,0 +1,477 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-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 "kyra/resource.h" +#include "kyra/sound.h" + +#include "sound/mixer.h" +#include "sound/voc.h" +#include "sound/audiostream.h" + +#include "sound/mp3.h" +#include "sound/vorbis.h" +#include "sound/flac.h" + +namespace Kyra { + +SoundPC::SoundPC(MidiDriver *driver, Audio::Mixer *mixer, KyraEngine *engine) : Sound() { + _engine = engine; + _driver = driver; + _passThrough = false; + _eventFromMusic = false; + _fadeMusicOut = _sfxIsPlaying = false; + _fadeStartTime = 0; + _isPlaying = _isLooping = _nativeMT32 = false; + _soundEffect = _parser = 0; + _soundEffectSource = _parserSource = 0; + + memset(_channel, 0, sizeof(MidiChannel*) * 32); + memset(_channelVolume, 50, sizeof(uint8) * 16); + _channelVolume[10] = 100; + for (int i = 0; i < 16; ++i) { + _virChannel[i] = i; + } + _volume = 0; + + int ret = open(); + if (ret != MERR_ALREADY_OPEN && ret != 0) { + error("couldn't open midi driver"); + } + + _currentVocFile = 0; + _mixer = mixer; +} + +SoundPC::~SoundPC() { + _driver->setTimerCallback(NULL, NULL); + close(); +} + +void SoundPC::setVolume(int volume) { + if (volume < 0) + volume = 0; + else if (volume > 255) + volume = 255; + + if (_volume == volume) + return; + + _volume = volume; + for (int i = 0; i < 32; ++i) { + if (_channel[i]) { + if (i >= 16) { + _channel[i]->volume(_channelVolume[i - 16] * _volume / 255); + } else { + _channel[i]->volume(_channelVolume[i] * _volume / 255); + } + } + } +} + +int SoundPC::open() { + // Don't ever call open without first setting the output driver! + if (!_driver) + return 255; + + int ret = _driver->open(); + if (ret) + return ret; + + _driver->setTimerCallback(this, &onTimer); + return 0; +} + +void SoundPC::close() { + if (_driver) + _driver->close(); + _driver = 0; +} + +void SoundPC::send(uint32 b) { + if (_passThrough) { + if ((b & 0xFFF0) == 0x007BB0) + return; + _driver->send(b); + return; + } + + uint8 channel = (byte)(b & 0x0F); + if (((b & 0xFFF0) == 0x6FB0 || (b & 0xFFF0) == 0x6EB0) && channel != 9) { + if (_virChannel[channel] == channel) { + _virChannel[channel] = channel + 16; + if (!_channel[_virChannel[channel]]) + _channel[_virChannel[channel]] = _driver->allocateChannel(); + if (_channel[_virChannel[channel]]) + _channel[_virChannel[channel]]->volume(_channelVolume[channel] * _volume / 255); + } + return; + } + + if ((b & 0xFFF0) == 0x07B0) { + // Adjust volume changes by master volume + uint8 volume = (uint8)((b >> 16) & 0x7F); + _channelVolume[channel] = volume; + volume = volume * _volume / 255; + b = (b & 0xFF00FFFF) | (volume << 16); + } else if ((b & 0xF0) == 0xC0 && !_nativeMT32) { + b = (b & 0xFFFF00FF) | MidiDriver::_mt32ToGm[(b >> 8) & 0xFF] << 8; + } else if ((b & 0xFFF0) == 0x007BB0) { + //Only respond to All Notes Off if this channel + //has currently been allocated + if (!_channel[channel]) + return; + } + + if (!_channel[_virChannel[channel]]) { + _channel[_virChannel[channel]] = (channel == 9) ? _driver->getPercussionChannel() : _driver->allocateChannel(); + if (_channel[_virChannel[channel]]) + _channel[_virChannel[channel]]->volume(_channelVolume[channel] * _volume / 255); + } + if (_channel[_virChannel[channel]]) + _channel[_virChannel[channel]]->send(b); +} + +void SoundPC::metaEvent(byte type, byte *data, uint16 length) { + switch (type) { + case 0x2F: // End of Track + if (_eventFromMusic) { + if (!_isLooping) { + _isPlaying = false; + } + // remap all channels + for (int i = 0; i < 16; ++i) { + _virChannel[i] = i; + } + } else { + _sfxIsPlaying = false; + } + break; + default: + _driver->metaEvent(type, data, length); + break; + } +} + +void SoundPC::playMusic(const char *file) { + uint32 size; + uint8 *data = (_engine->resource())->fileData(file, &size); + + if (!data) { + warning("couldn't load '%s'", file); + return; + } + + playMusic(data, size); +} + +void SoundPC::playMusic(uint8 *data, uint32 size) { + stopMusic(); + + _parserSource = data; + _parser = MidiParser::createParser_XMIDI(); + assert(_parser); + + if (!_parser->loadMusic(data, size)) { + warning("Error reading track!"); + delete _parser; + _parser = 0; + return; + } + + _parser->setTrack(0); + _parser->setMidiDriver(this); + _parser->setTimerRate(getBaseTempo()); + _parser->property(MidiParser::mpAutoLoop, false); +} + +void SoundPC::loadSoundEffectFile(const char *file) { + uint32 size; + uint8 *data = (_engine->resource())->fileData(file, &size); + + if (!data) { + warning("couldn't load '%s'", file); + return; + } + + loadSoundEffectFile(data, size); +} + +void SoundPC::loadSoundEffectFile(uint8 *data, uint32 size) { + stopSoundEffect(); + + _soundEffectSource = data; + _soundEffect = MidiParser::createParser_XMIDI(); + assert(_soundEffect); + + if (!_soundEffect->loadMusic(data, size)) { + warning("Error reading track!"); + delete _parser; + _parser = 0; + return; + } + + _soundEffect->setTrack(0); + _soundEffect->setMidiDriver(this); + _soundEffect->setTimerRate(getBaseTempo()); + _soundEffect->property(MidiParser::mpAutoLoop, false); +} + +void SoundPC::stopMusic() { + _isLooping = false; + _isPlaying = false; + if (_parser) { + _parser->unloadMusic(); + delete _parser; + _parser = 0; + delete [] _parserSource; + _parserSource = 0; + + _fadeStartTime = 0; + _fadeMusicOut = false; + setVolume(255); + } +} + +void SoundPC::stopSoundEffect() { + _sfxIsPlaying = false; + if (_soundEffect) { + _soundEffect->unloadMusic(); + delete _soundEffect; + _soundEffect = 0; + delete [] _soundEffectSource; + _soundEffectSource = 0; + } +} + +void SoundPC::onTimer(void *refCon) { + SoundPC *music = (SoundPC *)refCon; + + // this should be set to the fadeToBlack value + static const uint32 musicFadeTime = 2 * 1000; + + if (music->_fadeMusicOut && music->_fadeStartTime + musicFadeTime > music->_engine->_system->getMillis()) { + byte volume = (byte)((musicFadeTime - (music->_engine->_system->getMillis() - music->_fadeStartTime)) * 255 / musicFadeTime); + music->setVolume(volume); + } else if(music->_fadeStartTime) { + music->setVolume(255); + music->_fadeStartTime = 0; + music->_fadeMusicOut = false; + music->_isLooping = false; + music->_isPlaying = false; + + music->_eventFromMusic = true; + // from sound/midiparser.cpp + for (int i = 0; i < 128; ++i) { + for (int j = 0; j < 16; ++j) { + music->send(0x80 | j | i << 8); + } + } + for (int i = 0; i < 16; ++i) { + music->send(0x007BB0 | i); + } + } + + if (music->_isPlaying) { + if (music->_parser) { + music->_eventFromMusic = true; + music->_parser->onTimer(); + } + } + + if (music->_sfxIsPlaying) { + if (music->_soundEffect) { + music->_eventFromMusic = false; + music->_soundEffect->onTimer(); + } + } +} + +void SoundPC::playTrack(uint8 track, bool loop) { + if (_parser) { + _isPlaying = true; + _isLooping = loop; + _parser->setTrack(track); + _parser->jumpToTick(0); + _parser->setTempo(1); + _parser->property(MidiParser::mpAutoLoop, loop); + } +} + +void SoundPC::playSoundEffect(uint8 track) { + if (_soundEffect) { + _sfxIsPlaying = true; + _soundEffect->setTrack(track); + _soundEffect->jumpToTick(0); + _soundEffect->property(MidiParser::mpAutoLoop, false); + } +} + +void SoundPC::beginFadeOut() { + // this should be something like fade out... + _fadeMusicOut = true; + _fadeStartTime = _engine->_system->getMillis(); +} + +void SoundPC::voicePlay(const char *file) { + uint32 fileSize = 0; + byte *fileData = 0; + bool found = false; + char filenamebuffer[25]; + + for (int i = 0; _supportedCodes[i].fileext; ++i) { + strcpy(filenamebuffer, file); + strcat(filenamebuffer, _supportedCodes[i].fileext); + + _engine->resource()->fileHandle(filenamebuffer, &fileSize, _compressHandle); + if (!_compressHandle.isOpen()) + continue; + + _currentVocFile = _supportedCodes[i].streamFunc(&_compressHandle, fileSize); + found = true; + break; + } + + if (!found) { + strcpy(filenamebuffer, file); + strcat(filenamebuffer, ".VOC"); + + fileData = _engine->resource()->fileData(filenamebuffer, &fileSize); + if (!fileData) + return; + + Common::MemoryReadStream vocStream(fileData, fileSize); + _mixer->stopHandle(_vocHandle); + _currentVocFile = makeVOCStream(vocStream); + } + + if (_currentVocFile) + _mixer->playInputStream(Audio::Mixer::kSpeechSoundType, &_vocHandle, _currentVocFile); + delete [] fileData; + fileSize = 0; +} + +bool SoundPC::voiceIsPlaying() { + return _mixer->isSoundHandleActive(_vocHandle); +} + +void KyraEngine::snd_playTheme(int file, int track) { + debug(9, "KyraEngine::snd_playTheme(%d)", file); + assert(file < _xmidiFilesCount); + _curMusicTheme = _newMusicTheme = file; + _sound->playMusic(_xmidiFiles[file]); + _sound->playTrack(track, false); +} + +void KyraEngine::snd_setSoundEffectFile(int file) { + debug(9, "KyraEngine::snd_setSoundEffectFile(%d)", file); + assert(file < _xmidiFilesCount); + _sound->loadSoundEffectFile(_xmidiFiles[file]); +} + +void KyraEngine::snd_playSoundEffect(int track) { + debug(9, "KyraEngine::snd_playSoundEffect(%d)", track); + if (track == 49) { + snd_playWanderScoreViaMap(56, 1); + } else { + _sound->playSoundEffect(track); + } +} + +void KyraEngine::snd_playWanderScoreViaMap(int command, int restart) { + debug(9, "KyraEngine::snd_playWanderScoreViaMap(%d, %d)", command, restart); + static const int8 soundTable[] = { + -1, 0, -1, 1, 0, 3, 0, 2, + 0, 4, 1, 2, 1, 3, 1, 4, + 1, 0x5C, 1, 6, 1, 7, 2, 2, + 2, 3, 2, 4, 2, 5, 2, 6, + 2, 7, 3, 3, 3, 4, 1, 8, + 1, 9, 4, 2, 4, 3, 4, 4, + 4, 5, 4, 6, 4, 7, 4, 8, + 1, 0x0B, 1, 0x0C, 1, 0x0E, 1, 0x0D, + 4, 9, 5, 0x0C, 6, 2, 6, 6, + 6, 7, 6, 8, 6, 9, 6, 3, + 6, 4, 6, 5, 7, 2, 7, 3, + 7, 4, 7, 5, 7, 6, 7, 7, + 7, 8, 7, 9, 8, 2, 8, 3, + 8, 4, 8, 5, 6, 0x0B, 5, 0x0B + }; + //if (!_disableSound) { + // XXX + //} + assert(command*2+1 < ARRAYSIZE(soundTable)); + if (_curMusicTheme != soundTable[command*2]+1) { + if (soundTable[command*2] != -1) { + snd_playTheme(soundTable[command*2]+1); + } + } + + if (restart) + _lastMusicCommand = -1; + + if (command != 1) { + if (_lastMusicCommand != command) { + _lastMusicCommand = command; + _sound->playTrack(soundTable[command*2+1], true); + } + } else { + _lastMusicCommand = 1; + _sound->beginFadeOut(); + } +} + +void KyraEngine::snd_playVoiceFile(int id) { + debug(9, "KyraEngine::snd_playVoiceFile(%d)", id); + char vocFile[9]; + assert(id >= 0 && id < 9999); + sprintf(vocFile, "%03d", id); + _sound->voicePlay(vocFile); +} + +void KyraEngine::snd_voiceWaitForFinish(bool ingame) { + debug(9, "KyraEngine::snd_voiceWaitForFinish(%d)", ingame); + while (_sound->voiceIsPlaying() && !_skipFlag) { + if (ingame) { + delay(10, true); + } else { + _system->delayMillis(10); + } + } +} + +// static res + +const SoundPC::SpeechCodecs SoundPC::_supportedCodes[] = { +#ifdef USE_MAD + { ".VO3", makeMP3Stream }, +#endif // USE_MAD +#ifdef USE_VORBIS + { ".VOG", makeVorbisStream }, +#endif // USE_VORBIS +#ifdef USE_FLAC + { ".VOF", makeFlacStream }, +#endif // USE_FLAC + { 0, 0 } +}; + +} // end of namespace Kyra diff --git a/engines/kyra/sound.h b/engines/kyra/sound.h new file mode 100644 index 0000000000..7af201cd9f --- /dev/null +++ b/engines/kyra/sound.h @@ -0,0 +1,155 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-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 SOUND_H +#define SOUND_H + +#include "common/stdafx.h" +#include "common/scummsys.h" +#include "sound/mididrv.h" +#include "sound/midiparser.h" +#include "kyra/kyra.h" + +class AudioStream; + +namespace Audio { +class Mixer; +class SoundHandle; +} // end of namespace Audio + +namespace Kyra { + +class Sound { +public: + Sound() {} + virtual ~Sound() {} + + virtual void setVolume(int volume) = 0; + virtual int getVolume() = 0; + + virtual void playMusic(const char *file) = 0; + virtual void playMusic(uint8 *data, uint32 size) = 0; + virtual void stopMusic() = 0; + + virtual void playTrack(uint8 track, bool looping = true) = 0; + virtual void haltTrack() = 0; + virtual void startTrack() = 0; + + virtual void loadSoundEffectFile(const char *file) = 0; + virtual void loadSoundEffectFile(uint8 *data, uint32 size) = 0; + virtual void stopSoundEffect() = 0; + + virtual void playSoundEffect(uint8 track) = 0; + + virtual void beginFadeOut() = 0; + virtual bool fadeOut() = 0; + + virtual void voicePlay(const char *file) = 0; + virtual void voiceUnload() = 0; + virtual bool voiceIsPlaying() = 0; +}; + +class SoundPC : public MidiDriver, public Sound { +public: + + SoundPC(MidiDriver *driver, Audio::Mixer *mixer, KyraEngine *engine); + ~SoundPC(); + + void setVolume(int volume); + int getVolume() { return _volume; } + + void hasNativeMT32(bool nativeMT32) { _nativeMT32 = nativeMT32; } + bool isMT32() { return _nativeMT32; } + + void playMusic(const char *file); + void playMusic(uint8 *data, uint32 size); + void stopMusic(); + + void playTrack(uint8 track, bool looping); + void haltTrack() { _isPlaying = false; } + void startTrack() { _isPlaying = true; } + void setPassThrough(bool b) { _passThrough = b; } + + void loadSoundEffectFile(const char *file); + void loadSoundEffectFile(uint8 *data, uint32 size); + void stopSoundEffect(); + + void playSoundEffect(uint8 track); + + void beginFadeOut(); + bool fadeOut() { return _fadeMusicOut; } + + void voicePlay(const char *file); + void voiceUnload() {}; + bool voiceIsPlaying(); + + //MidiDriver interface implementation + int open(); + void close(); + void send(uint32 b); + void metaEvent(byte type, byte *data, uint16 length); + + void setTimerCallback(void *timerParam, void (*timerProc)(void *)) { } + uint32 getBaseTempo(void) { return _driver ? _driver->getBaseTempo() : 0; } + + //Channel allocation functions + MidiChannel *allocateChannel() { return 0; } + MidiChannel *getPercussionChannel() { return 0; } + +protected: + + static void onTimer(void *data); + + MidiChannel *_channel[32]; + int _virChannel[16]; + uint8 _channelVolume[16]; + MidiDriver *_driver; + bool _nativeMT32; + bool _passThrough; + uint8 _volume; + bool _isPlaying; + bool _sfxIsPlaying; + uint32 _fadeStartTime; + bool _fadeMusicOut; + bool _isLooping; + bool _eventFromMusic; + MidiParser *_parser; + byte *_parserSource; + MidiParser *_soundEffect; + byte *_soundEffectSource; + KyraEngine *_engine; + + Audio::Mixer *_mixer; + AudioStream *_currentVocFile; + Audio::SoundHandle _vocHandle; + Common::File _compressHandle; + + struct SpeechCodecs { + const char *fileext; + AudioStream *(*streamFunc)(Common::File*, uint32); + }; + + static const SpeechCodecs _supportedCodes[]; +}; +} // end of namespace Kyra + +#endif diff --git a/engines/kyra/sprites.cpp b/engines/kyra/sprites.cpp new file mode 100644 index 0000000000..d1f9d00fd6 --- /dev/null +++ b/engines/kyra/sprites.cpp @@ -0,0 +1,569 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-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 "common/util.h" +#include "common/system.h" +#include "kyra/screen.h" +#include "kyra/kyra.h" +#include "kyra/sprites.h" +#include "kyra/resource.h" +#include "kyra/animator.h" + +namespace Kyra { + +Sprites::Sprites(KyraEngine *engine, OSystem *system) { + _engine = engine; + _res = engine->resource(); + _screen = engine->screen(); + _system = system; + _dat = 0; + memset(_anims, 0, sizeof(_anims)); + memset(_sceneShapes, 0, sizeof(_sceneShapes)); + _animDelay = 16; + _spriteDefStart = 0; + memset(_drawLayerTable, 0, sizeof(_drawLayerTable)); + _sceneAnimatorBeaconFlag = 0; +} + +Sprites::~Sprites() { + delete[] _dat; + freeSceneShapes(); + for (int i = 0; i < MAX_NUM_ANIMS; i++) { + if (_anims[i].background) + free(_anims[i].background); + } +} + +void Sprites::setupSceneAnims() { + debug(9, "Sprites::setupSceneAnims()"); + uint8 *data; + + for (int i = 0; i < MAX_NUM_ANIMS; i++) { + if (_anims[i].background) { + free(_anims[i].background); + _anims[i].background = 0; + } + + if (_anims[i].script != 0) { + data = _anims[i].script; + + assert( READ_LE_UINT16(data) == 0xFF86 ); + data += 4; + + _anims[i].disable = READ_LE_UINT16(data) != 0; + data += 4; + _anims[i].unk2 = READ_LE_UINT16(data); + data += 4; + + if (_engine->_northExitHeight > READ_LE_UINT16(data)) + _anims[i].drawY = _engine->_northExitHeight; + else + _anims[i].drawY = READ_LE_UINT16(data); + data += 4; + + //sceneUnk2[i] = READ_LE_UINT16(data); + data += 4; + + _anims[i].x = READ_LE_UINT16(data); + data += 4; + _anims[i].y = READ_LE_UINT16(data); + data += 4; + _anims[i].width = *(data) - 1; + data += 4; + _anims[i].height = *(data); + data += 4; + _anims[i].sprite = READ_LE_UINT16(data); + data += 4; + _anims[i].flipX = READ_LE_UINT16(data) != 0; + data += 4; + _anims[i].width2 = *(data); + data += 4; + _anims[i].height2 = *(data); + data += 4; + _anims[i].unk1 = READ_LE_UINT16(data) != 0; + data += 4; + _anims[i].play = READ_LE_UINT16(data) != 0; + data += 2; + + _anims[i].script = data; + + int bkgdWidth = _anims[i].width << 3; + int bkgdHeight = _anims[i].height; + + if (_anims[i].width2) + bkgdWidth += _anims[i].width2 << 3; + + if (_anims[i].height2) + bkgdHeight += _anims[i].height2; + + _anims[i].background = (uint8 *)malloc(_screen->getRectSize(bkgdWidth + 1, bkgdHeight)); + memset(_anims[i].background, 0, _screen->getRectSize(bkgdWidth + 1, bkgdHeight)); + + assert(_anims[i].background); + } + } +} + +void Sprites::updateSceneAnims() { + debug(9, "Sprites::updateSceneAnims()"); + uint32 currTime = _system->getMillis(); + uint8 *data; + bool endLoop; + uint16 rndNr; + uint16 anim; + uint16 sound; + + for (int i = 0; i < MAX_NUM_ANIMS; i++) { + if (_anims[i].script == 0 || !_anims[i].play || _anims[i].nextRun != 0 && _anims[i].nextRun > currTime) + continue; + + if (_anims[i].reentry == 0) { + data = _anims[i].script; + if (READ_LE_UINT16(data) == 0xFF8B) + continue; + } else { + data = _anims[i].reentry; + _anims[i].reentry = 0; + } + + endLoop = false; + while (READ_LE_UINT16(data) != 0xFF87 && !endLoop) { + assert((data - _anims[i].script) < _anims[i].length); + switch (READ_LE_UINT16(data)) { + case 0xFF88: + data += 2; + debug(6, "func: Set sprite image."); + debug(6, "Sprite index %i", READ_LE_UINT16(data)); + _anims[i].sprite = READ_LE_UINT16(data); + data += 2; + //debug(6, "Unused %i", READ_LE_UINT16(data)); + data += 2; + debug(6, "X %i", READ_LE_UINT16(data)); + _anims[i].x = READ_LE_UINT16(data); + data += 2; + debug(6, "Y %i", READ_LE_UINT16(data)); + _anims[i].y = READ_LE_UINT16(data); + data += 2; + _anims[i].flipX = false; + refreshSceneAnimObject(i, _anims[i].sprite, _anims[i].x, _anims[i].y, _anims[i].flipX, _anims[i].unk1 != 0); + break; + case 0xFF8D: + data += 2; + debug(6, "func: Set sprite image, flipped."); + debug(6, "Sprite index %i", READ_LE_UINT16(data)); + _anims[i].sprite = READ_LE_UINT16(data); + data += 2; + //debug(9, "Unused %i", READ_LE_UINT16(data)); + data += 2; + debug(6, "X %i", READ_LE_UINT16(data)); + _anims[i].x = READ_LE_UINT16(data); + data += 2; + debug(6, "Y %i", READ_LE_UINT16(data)); + _anims[i].y = READ_LE_UINT16(data); + data += 2; + _anims[i].flipX = true; + refreshSceneAnimObject(i, _anims[i].sprite, _anims[i].x, _anims[i].y, _anims[i].flipX, _anims[i].unk1 != 0); + break; + case 0xFF8A: + data += 2; + debug(6, "func: Set time to wait"); + debug(6, "Time %i", READ_LE_UINT16(data)); + _anims[i].nextRun = _system->getMillis() + READ_LE_UINT16(data) * _animDelay; + data += 2; + break; + case 0xFFB3: + data += 2; + debug(6, "func: Set time to wait to random value"); + rndNr = READ_LE_UINT16(data) + _rnd.getRandomNumber( READ_LE_UINT16(data) + 2); + debug(6, "Minimum time %i", READ_LE_UINT16(data)); + data += 2; + debug(6, "Maximum time %i", READ_LE_UINT16(data)); + data += 2; + _anims[i].nextRun = _system->getMillis() + rndNr * _animDelay; + break; + case 0xFF8C: + data += 2; + debug(6, "func: Wait until wait time has elapsed"); + _anims[i].reentry = data; + endLoop = true; + //assert( _anims[i].nextRun > _system->getMillis()); + break; + case 0xFF99: + data += 2; + debug(1, "func: Set value of unknown animation property to 1"); + _anims[i].unk1 = 1; + break; + case 0xFF9A: + data += 2; + debug(1, "func: Set value of unknown animation property to 0"); + _anims[i].unk1 = 0; + break; + case 0xFF97: + data += 2; + debug(6, "func: Set default X coordinate of sprite"); + debug(6, "X %i", READ_LE_UINT16(data)); + _anims[i].x = READ_LE_UINT16(data); + data += 2; + break; + case 0xFF98: + data += 2; + debug(6, "func: Set default Y coordinate of sprite"); + debug(6, "Y %i", READ_LE_UINT16(data)); + _anims[i].y = READ_LE_UINT16(data); + data += 2; + break; + case 0xFF8B: + debug(6, "func: Jump to start of script section"); + //data = scriptStart; + _anims[i].nextRun = _system->getMillis(); + endLoop = true; + break; + case 0xFF8E: + data += 2; + debug(6, "func: Begin for () loop"); + debug(6, "Iterations: %i", READ_LE_UINT16(data)); + _anims[i].loopsLeft = READ_LE_UINT16(data); + data += 2; + _anims[i].loopStart = data; + break; + case 0xFF8F: + data += 2; + debug(6, "func: End for () loop"); + if (_anims[i].loopsLeft > 0) { + _anims[i].loopsLeft--; + data = _anims[i].loopStart; + } + break; + case 0xFF90: + data += 2; + debug(6, "func: Set sprite image using default X and Y"); + debug(6, "Sprite index %i", READ_LE_UINT16(data)); + _anims[i].sprite = READ_LE_UINT16(data); + _anims[i].flipX = false; + data += 2; + refreshSceneAnimObject(i, _anims[i].sprite, _anims[i].x, _anims[i].y, _anims[i].flipX, _anims[i].unk1 != 0); + break; + case 0xFF91: + data += 2; + debug(6, "func: Set sprite image using default X and Y, flipped."); + debug(6, "Sprite index %i", READ_LE_UINT16(data)); + _anims[i].sprite = READ_LE_UINT16(data); + _anims[i].flipX = true; + data += 2; + refreshSceneAnimObject(i, _anims[i].sprite, _anims[i].x, _anims[i].y, _anims[i].flipX, _anims[i].unk1 != 0); + break; + case 0xFF92: + data += 2; + debug(6, "func: Increase value of default X-coordinate"); + debug(6, "Increment %i", READ_LE_UINT16(data)); + _anims[i].x += READ_LE_UINT16(data); + data += 2; + break; + case 0xFF93: + data += 2; + debug(6, "func: Increase value of default Y-coordinate"); + debug(6, "Increment %i", READ_LE_UINT16(data)); + _anims[i].y += READ_LE_UINT16(data); + data += 2; + break; + case 0xFF94: + data += 2; + debug(6, "func: Decrease value of default X-coordinate"); + debug(6, "Decrement %i", READ_LE_UINT16(data)); + _anims[i].x -= READ_LE_UINT16(data); + data += 2; + break; + case 0xFF95: + data += 2; + debug(6, "func: Decrease value of default Y-coordinate"); + debug(6, "Decrement %i", READ_LE_UINT16(data)); + _anims[i].y -= READ_LE_UINT16(data); + data += 2; + break; + case 0xFF96: + data += 2; + debug(9, "func: Stop animation"); + debug(9, "Animation index %i", READ_LE_UINT16(data)); + anim = READ_LE_UINT16(data); + data += 2; + _anims[anim].play = false; + _anims[anim].sprite = -1; + break; +/* case 0xFF97: + data += 2; + debug(1, "func: Set value of animation property 34h to 0"); + break;*/ + case 0xFFAD: + data += 2; + debug(6, "func: Set Brandon's X coordinate"); + debug(6, "X %i", READ_LE_UINT16(data)); + _engine->currentCharacter()->x1 = READ_LE_UINT16(data); + data += 2; + break; + case 0xFFAE: + data += 2; + debug(6, "func: Set Brandon's Y coordinate"); + debug(6, "Y %i", READ_LE_UINT16(data)); + _engine->currentCharacter()->y1 = READ_LE_UINT16(data); + data += 2; + break; + case 0xFFAF: + data += 2; + debug(6, "func: Set Brandon's sprite"); + debug(6, "Sprite %i", READ_LE_UINT16(data)); + _engine->currentCharacter()->currentAnimFrame = READ_LE_UINT16(data); + data += 2; + break; + case 0xFFAA: + data += 2; + debug(1, "TODO func: Reset Brandon's sprite"); + break; + case 0xFFAB: + data += 2; + debug(6, "func: Update Brandon's sprite"); + _engine->animator()->animRefreshNPC(0); + _engine->animator()->flagAllObjectsForRefresh(); + _engine->animator()->updateAllObjectShapes(); + break; + case 0xFFB0: + data += 2; + debug(6, "func: Play sound"); + debug(6, "Sound index %i", READ_LE_UINT16(data)); + _engine->snd_playSoundEffect(READ_LE_UINT16(data)); + data += 2; + break; + case 0xFFB1: + data += 2; + _sceneAnimatorBeaconFlag = 1; + break; + case 0xFFB2: + data += 2; + _sceneAnimatorBeaconFlag = 0; + break; + case 0xFFB4: + data += 2; + debug(6, "func: Play (at random) a certain sound at a certain percentage of time"); + debug(6, "Sound index %i", READ_LE_UINT16(data)); + sound = READ_LE_UINT16(data); + data += 2; + debug(6, "Percentage %i", READ_LE_UINT16(data)); + rndNr = _rnd.getRandomNumber(100); + if (rndNr <= READ_LE_UINT16(data)) + _engine->snd_playSoundEffect(sound); + data += 2; + break; + case 0xFFA7: + data += 2; + _anims[READ_LE_UINT16(data)].play = 1; + data += 2; + break; + default: + debug(1, "Unsupported anim command %X in script %i", READ_LE_UINT16(data), i); + //endLoop = true; + data += 1; + break; + } + } + + if (READ_LE_UINT16(data) == 0xFF87) + _anims[i].play = false; + } +} + +void Sprites::loadDAT(const char *filename, SceneExits &exits) { + debug(9, "Sprites::loadDat('%s')", filename); + uint32 fileSize; + + delete[] _dat; + _spriteDefStart = 0; + + _dat = _res->fileData(filename, &fileSize); + + memset(_anims, 0, sizeof(_anims)); + uint8 nextAnim = 0; + + assert(fileSize > 0x6D); + + memcpy(_drawLayerTable, (_dat + 0x0D), 8); + _engine->_northExitHeight = READ_LE_UINT16(_dat + 0x15); + if (_engine->_northExitHeight & 1) + _engine->_northExitHeight += 1; + // XXX + memcpy(_screen->_currentPalette + 744 - 60, _dat + 0x17, 60); + uint8 *data = _dat + 0x6B; + + uint16 length = READ_LE_UINT16(data); + data += 2; + + if (length > 2) { + assert( length < fileSize); + uint8 *animstart; + uint8 *start = data; + + while (1) { + if (((uint16)(data - _dat) >= fileSize) || (data - start) >= length) + break; + + if (READ_LE_UINT16(data) == 0xFF83) { + //debug(1, "Body section end."); + data += 2; + break; + } + + switch (READ_LE_UINT16(data)) { + case 0xFF81: + data += 2; + //debug(1, "Body section start"); + break; + case 0xFF82: + data += 2; + //debug(1, "Unknown 0xFF82 section"); + break; + case 0xFF84: + data += 2; + _spriteDefStart = data; + while (READ_LE_UINT16(data) != 0xFF85) { + data += 2; + } + data += 2; + break; + case 0xFF86: + assert(nextAnim < MAX_NUM_ANIMS); + _anims[nextAnim].script = data; + _anims[nextAnim].sprite = -1; + _anims[nextAnim].play = true; + animstart = data; + data += 2; + while (READ_LE_UINT16(data) != 0xFF87) { + assert((uint16)(data - _dat) < fileSize); + data += 2; + } + _anims[nextAnim].length = data - animstart; + //debug(1, "Found an anim script of length %i!", _anims[nextAnim].length); + nextAnim++; + data += 2; + break; + default: + debug(1, "Unknown code in DAT file: %x", READ_LE_UINT16(data)); + data += 2; + break; + } + } + } else { + data += 2; + } + + assert(fileSize - (data - _dat) == 0xC); + + exits.northXPos = READ_LE_UINT16(data) & 0xFFFC; data += 2; + exits.northYPos = *data++ & 0xFFFE; + exits.eastXPos = READ_LE_UINT16(data) & 0xFFFC; data += 2; + exits.eastYPos = *data++ & 0xFFFE; + exits.southXPos = READ_LE_UINT16(data) & 0xFFFC; data += 2; + exits.southYPos = *data++ & 0xFFFE; + exits.westXPos = READ_LE_UINT16(data) & 0xFFFC; data += 2; + exits.westYPos = *data++ & 0xFFFE; +} + +void Sprites::freeSceneShapes() { + debug(9, "Sprites::freeSceneShapes()"); + for (int i = 0; i < ARRAYSIZE(_sceneShapes); i++ ) { + free(_sceneShapes[i]); + _sceneShapes[i] = 0; + } +} + +void Sprites::loadSceneShapes() { + debug(9, "Sprites::loadSceneShapes()"); + uint8 *data = _spriteDefStart; + int spriteNum, x, y, width, height; + + freeSceneShapes(); + memset( _sceneShapes, 0, sizeof(_sceneShapes)); + + if (_spriteDefStart == 0) + return; + + int bakPage = _screen->_curPage; + _screen->_curPage = 3; + + while (READ_LE_UINT16(data) != 0xFF85) { + spriteNum = READ_LE_UINT16(data); + assert(spriteNum < ARRAYSIZE(_sceneShapes)); + data += 2; + x = READ_LE_UINT16(data) * 8; + data += 2; + y = READ_LE_UINT16(data); + data += 2; + width = READ_LE_UINT16(data) * 8; + data += 2; + height = READ_LE_UINT16(data); + data += 2; + _sceneShapes[spriteNum] = _screen->encodeShape(x, y, width, height, 2); + debug(9, "Sprite %i is at (%i, %i), width %i, height %i", spriteNum, x, y, width, height); + } + _screen->_curPage = bakPage; +} + +void Sprites::refreshSceneAnimObject(uint8 animNum, uint8 shapeNum, uint16 x, uint16 y, bool flipX, bool unkFlag) { + debug(9, "Sprites::refreshSceneAnimObject(%i, %i, %i, %i, %i, %i", animNum, shapeNum, x, y, flipX, unkFlag); + AnimObject &anim = _engine->animator()->sprites()[animNum]; + anim.refreshFlag = 1; + anim.bkgdChangeFlag = 1; + + if (unkFlag) + anim.flags |= 0x0200; + else + anim.flags &= 0xFD00; + + if (flipX) + anim.flags |= 1; + else + anim.flags &= 0xFE; + + anim.sceneAnimPtr = _sceneShapes[shapeNum]; + anim.animFrameNumber = -1; + anim.x1 = x; + anim.y1 = y; +} + +int Sprites::getDrawLayer(int y) { + debug(9, "getDrawLayer(%d)", y); + uint8 returnValue = 0; + for (int i = 0; i < ARRAYSIZE(_drawLayerTable); ++i) { + uint8 temp = _drawLayerTable[i]; + if (temp) { + if (temp <= y) { + returnValue = i; + } + } + } + if (returnValue <= 0) { + returnValue = 1; + } else if (returnValue >= 7) { + returnValue = 6; + } + return returnValue; +} +} // end of namespace Kyra diff --git a/engines/kyra/sprites.h b/engines/kyra/sprites.h new file mode 100644 index 0000000000..b1ce9ae8e7 --- /dev/null +++ b/engines/kyra/sprites.h @@ -0,0 +1,95 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-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 KYRASPRITES_H +#define KYRASPRITES_H + +namespace Kyra { + +#define MAX_NUM_ANIMS 11 + +struct Sprite { + uint16 x; + uint16 y; + uint16 width; + uint16 height; +}; + +struct Anim { + uint8 *script; + uint16 length; + int16 x; + int16 y; + bool flipX; + int8 sprite; + uint8 *loopStart; + uint16 loopsLeft; + uint8 *reentry; + uint32 nextRun; + bool play; + uint16 width; + uint16 height; + uint16 width2; + uint16 height2; + uint16 unk1; + uint16 drawY; + uint16 unk2; + uint8 *background; + bool disable; +}; + +class Sprites { +public: + + Sprites(KyraEngine *engine, OSystem *system); + ~Sprites(); + + void updateSceneAnims(); + void setupSceneAnims(); + void loadDAT(const char *filename, SceneExits &exits); + void loadSceneShapes(); + + Anim _anims[MAX_NUM_ANIMS]; + uint8 *_sceneShapes[50]; + + void refreshSceneAnimObject(uint8 animNum, uint8 shapeNum, uint16 x, uint16 y, bool flipX, bool unkFlag); + + int getDrawLayer(int y); + + int _sceneAnimatorBeaconFlag; +protected: + void freeSceneShapes(); + + KyraEngine *_engine; + Resource *_res; + OSystem *_system; + Screen *_screen; + uint8 *_dat; + Common::RandomSource _rnd; + uint8 _animDelay; + uint8 *_spriteDefStart; + uint8 _drawLayerTable[8]; +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/staticres.cpp b/engines/kyra/staticres.cpp new file mode 100644 index 0000000000..66e10358c3 --- /dev/null +++ b/engines/kyra/staticres.cpp @@ -0,0 +1,978 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-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 "kyra/kyra.h" +#include "kyra/screen.h" +#include "kyra/resource.h" + +namespace Kyra { + +#define RESFILE_VERSION 11 + +#define GAME_FLAGS (GF_FLOPPY | GF_TALKIE | GF_DEMO | GF_AUDIOCD) +#define LANGUAGE_FLAGS (GF_ENGLISH | GF_FRENCH | GF_GERMAN | GF_SPANISH | GF_LNGUNK) + +byte *getFile(PAKFile &res, const char *filename) { + uint32 size = 0; + size = res.getFileSize(filename); + if (!size) + return 0; + return res.getFile(filename); +} + +struct LanguageTypes { + uint32 flags; + const char *ext; +}; + +static LanguageTypes languages[] = { + { GF_ENGLISH, "ENG" }, // this is the default language + { GF_FRENCH, "FRE" }, + { GF_GERMAN, "GER" }, + { GF_SPANISH, "SPA" }, + { 0, 0 } +}; + +void KyraEngine::res_loadResources(int type) { + debug(9, "res_loadResources(%d)", type); + PAKFile resFile("KYRA.DAT"); + if (!resFile.isValid() || !resFile.isOpen()) { + error("couldn't open Kyrandia resource file ('KYRA.DAT') make sure you got one file for your version"); + } + + uint32 version = 0; + uint32 gameID = 0; + uint32 featuresValue = 0; + bool loadNativeLanguage = true; + + byte *temp = 0; + + if (_features & GF_TALKIE) { + temp = getFile(resFile, "INDEX.CD"); + } else if (_features & GF_DEMO) { + temp = getFile(resFile, "INDEX.DEM"); + } else { + temp = getFile(resFile, "INDEX"); + } + if (!temp) { + error("no matching INDEX file found"); + } + + version = READ_BE_UINT32(temp); + gameID = READ_BE_UINT32((temp+4)); + featuresValue = READ_BE_UINT32((temp+8)); + + delete [] temp; + temp = 0; + + if (version < RESFILE_VERSION) { + error("invalid KYRA.DAT file version (%d, required %d)", version, RESFILE_VERSION); + } + if (gameID != _game) { + error("invalid game id (%d)", gameID); + } + if ((featuresValue & GAME_FLAGS) != (_features & GAME_FLAGS)) { + error("your data file has a different game flags (0x%.08X has the data and your version has 0x%.08X)", (featuresValue & GAME_FLAGS), (_features & GAME_FLAGS)); + } + + if (!((featuresValue & LANGUAGE_FLAGS) & (_features & LANGUAGE_FLAGS))) { + char buffer[240]; + sprintf(buffer, "your data file has support for:"); + if (featuresValue & GF_ENGLISH) { + sprintf(buffer + strlen(buffer), " English"); + } + if (featuresValue & GF_FRENCH) { + sprintf(buffer + strlen(buffer), " French"); + } + if (featuresValue & GF_GERMAN) { + sprintf(buffer + strlen(buffer), " German"); + } + if (featuresValue & GF_SPANISH) { + sprintf(buffer + strlen(buffer), " Spanish"); + } + sprintf(buffer + strlen(buffer), " but not your language ("); + if (_features & GF_ENGLISH) { + sprintf(buffer + strlen(buffer), "English"); + } else if (_features & GF_FRENCH) { + sprintf(buffer + strlen(buffer), "French"); + } else if (_features & GF_GERMAN) { + sprintf(buffer + strlen(buffer), "German"); + } else if (_features & GF_SPANISH) { + sprintf(buffer + strlen(buffer), "Spanish"); + } else { + sprintf(buffer + strlen(buffer), "unknown"); + } + sprintf(buffer + strlen(buffer), ")"); + warning(buffer); + loadNativeLanguage = false; + } + +#define getFileEx(x, y) \ + if (_features & GF_TALKIE) { \ + temp = getFile(x, y ".CD"); \ + } else if (_features & GF_DEMO) { \ + temp = getFile(x, y ".DEM"); \ + } else { \ + temp = getFile(x, y); \ + } +#define loadRawFile(x, y, z) \ + getFileEx(x, y) \ + if (temp) { \ + z = temp; \ + temp = 0; \ + } +#define loadTable(x, y, z, a) \ + getFileEx(x, y) \ + if (temp) { \ + res_loadTable(temp, z, a); \ + delete [] temp; \ + temp = 0; \ + } +#define loadRooms(x, y, z, a) \ + getFileEx(x, y) \ + if (temp) { \ + res_loadRoomTable(temp, z, a); \ + delete [] temp; \ + temp = 0; \ + } +#define loadShapes(x, y, z, a) \ + getFileEx(x, y) \ + if (temp) { \ + res_loadShapeTable(temp, z, a); \ + delete [] temp; \ + temp = 0; \ + } + + + if ((type & RES_INTRO) || (type & RES_OUTRO) || type == RES_ALL) { + loadRawFile(resFile, "FOREST.SEQ", _seq_Forest); + loadRawFile(resFile, "KALLAK-WRITING.SEQ", _seq_KallakWriting); + loadRawFile(resFile, "KYRANDIA-LOGO.SEQ", _seq_KyrandiaLogo); + loadRawFile(resFile, "KALLAK-MALCOLM.SEQ", _seq_KallakMalcolm); + loadRawFile(resFile, "MALCOLM-TREE.SEQ", _seq_MalcolmTree); + loadRawFile(resFile, "WESTWOOD-LOGO.SEQ", _seq_WestwoodLogo); + loadRawFile(resFile, "DEMO1.SEQ", _seq_Demo1); + loadRawFile(resFile, "DEMO2.SEQ", _seq_Demo2); + loadRawFile(resFile, "DEMO3.SEQ", _seq_Demo3); + loadRawFile(resFile, "DEMO4.SEQ", _seq_Demo4); + + loadTable(resFile, "INTRO-CPS.TXT", (byte***)&_seq_CPSTable, &_seq_CPSTable_Size); + loadTable(resFile, "INTRO-COL.TXT", (byte***)&_seq_COLTable, &_seq_COLTable_Size); + loadTable(resFile, "INTRO-WSA.TXT", (byte***)&_seq_WSATable, &_seq_WSATable_Size); + + res_loadLangTable("INTRO-STRINGS.", &resFile, (byte***)&_seq_textsTable, &_seq_textsTable_Size, loadNativeLanguage); + + loadRawFile(resFile, "REUNION.SEQ", _seq_Reunion); + + res_loadLangTable("HOME.", &resFile, (byte***)&_homeString, &_homeString_Size, loadNativeLanguage); + } + + if ((type & RES_INGAME) || type == RES_ALL) { + loadTable(resFile, "ROOM-FILENAMES.TXT", (byte***)&_roomFilenameTable, &_roomFilenameTableSize); + loadRooms(resFile, "ROOM-TABLE.ROOM", &_roomTable, &_roomTableSize); + + loadTable(resFile, "CHAR-IMAGE.TXT", (byte***)&_characterImageTable, &_characterImageTableSize); + + loadShapes(resFile, "SHAPES-DEFAULT.SHP", &_defaultShapeTable, &_defaultShapeTableSize); + + res_loadLangTable("ITEMLIST.", &resFile, (byte***)&_itemList, &_itemList_Size, loadNativeLanguage); + res_loadLangTable("TAKEN.", &resFile, (byte***)&_takenList, &_takenList_Size, loadNativeLanguage); + res_loadLangTable("PLACED.", &resFile, (byte***)&_placedList, &_placedList_Size, loadNativeLanguage); + res_loadLangTable("DROPPED.", &resFile, (byte***)&_droppedList, &_droppedList_Size, loadNativeLanguage); + res_loadLangTable("NODROP.", &resFile, (byte***)&_noDropList, &_noDropList_Size, loadNativeLanguage); + + loadRawFile(resFile, "AMULETEANIM.SEQ", _amuleteAnim); + + for (int i = 1; i <= 33; ++i) { + char buffer[32]; + sprintf(buffer, "PALTABLE%d.PAL", i); + if (_features & GF_TALKIE) { + strcat(buffer, ".CD"); + } else if (_features & GF_DEMO) { + strcat(buffer, ".DEM"); + } + temp = getFile(resFile, buffer); + if (temp) { + _specialPalettes[i-1] = temp; + temp = 0; + } + } + + res_loadLangTable("PUTDOWN.", &resFile, (byte***)&_putDownFirst, &_putDownFirst_Size, loadNativeLanguage); + res_loadLangTable("WAITAMUL.", &resFile, (byte***)&_waitForAmulet, &_waitForAmulet_Size, loadNativeLanguage); + res_loadLangTable("BLACKJEWEL.", &resFile, (byte***)&_blackJewel, &_blackJewel_Size, loadNativeLanguage); + res_loadLangTable("POISONGONE.", &resFile, (byte***)&_poisonGone, &_poisonGone_Size, loadNativeLanguage); + res_loadLangTable("HEALINGTIP.", &resFile, (byte***)&_healingTip, &_healingTip_Size, loadNativeLanguage); + + loadShapes(resFile, "HEALING.SHP", &_healingShapeTable, &_healingShapeTableSize); + loadShapes(resFile, "HEALING2.SHP", &_healingShape2Table, &_healingShape2TableSize); + + res_loadLangTable("THEPOISON.", &resFile, (byte***)&_thePoison, &_thePoison_Size, loadNativeLanguage); + res_loadLangTable("FLUTE.", &resFile, (byte***)&_fluteString, &_fluteString_Size, loadNativeLanguage); + + loadShapes(resFile, "POISONDEATH.SHP", &_posionDeathShapeTable, &_posionDeathShapeTableSize); + loadShapes(resFile, "FLUTE.SHP", &_fluteAnimShapeTable, &_fluteAnimShapeTableSize); + + loadShapes(resFile, "WINTER1.SHP", &_winterScrollTable, &_winterScrollTableSize); + loadShapes(resFile, "WINTER2.SHP", &_winterScroll1Table, &_winterScroll1TableSize); + loadShapes(resFile, "WINTER3.SHP", &_winterScroll2Table, &_winterScroll2TableSize); + loadShapes(resFile, "DRINK.SHP", &_drinkAnimationTable, &_drinkAnimationTableSize); + loadShapes(resFile, "WISP.SHP", &_brandonToWispTable, &_brandonToWispTableSize); + loadShapes(resFile, "MAGICANIM.SHP", &_magicAnimationTable, &_magicAnimationTableSize); + loadShapes(resFile, "BRANSTONE.SHP", &_brandonStoneTable, &_brandonStoneTableSize); + + res_loadLangTable("WISPJEWEL.", &resFile, (byte***)&_wispJewelStrings, &_wispJewelStrings_Size, loadNativeLanguage); + res_loadLangTable("MAGICJEWEL.", &resFile, (byte***)&_magicJewelString, &_magicJewelString_Size, loadNativeLanguage); + + res_loadLangTable("FLASKFULL.", &resFile, (byte***)&_flaskFull, &_fullFlask_Size, loadNativeLanguage); + res_loadLangTable("FULLFLASK.", &resFile, (byte***)&_fullFlask, &_fullFlask_Size, loadNativeLanguage); + + res_loadLangTable("VERYCLEVER.", &resFile, (byte***)&_veryClever, &_veryClever_Size, loadNativeLanguage); + } + +#undef loadRooms +#undef loadTable +#undef loadRawFile +#undef getFileEx +} + +void KyraEngine::res_unloadResources(int type) { + debug(9, "res_unloadResources(%d)", type); + if ((type & RES_INTRO) || (type & RES_OUTRO) || type & RES_ALL) { + res_freeLangTable(&_seq_WSATable, &_seq_WSATable_Size); + res_freeLangTable(&_seq_CPSTable, &_seq_CPSTable_Size); + res_freeLangTable(&_seq_COLTable, &_seq_COLTable_Size); + res_freeLangTable(&_seq_textsTable, &_seq_textsTable_Size); + + delete [] _seq_Forest; _seq_Forest = 0; + delete [] _seq_KallakWriting; _seq_KallakWriting = 0; + delete [] _seq_KyrandiaLogo; _seq_KyrandiaLogo = 0; + delete [] _seq_KallakMalcolm; _seq_KallakMalcolm = 0; + delete [] _seq_MalcolmTree; _seq_MalcolmTree = 0; + delete [] _seq_WestwoodLogo; _seq_WestwoodLogo = 0; + delete [] _seq_Demo1; _seq_Demo1 = 0; + delete [] _seq_Demo2; _seq_Demo2 = 0; + delete [] _seq_Demo3; _seq_Demo3 = 0; + delete [] _seq_Demo4; _seq_Demo4 = 0; + + delete [] _seq_Reunion; _seq_Reunion = 0; + res_freeLangTable(&_homeString, &_homeString_Size); + } + + if ((type & RES_INGAME) || type & RES_ALL) { + res_freeLangTable(&_roomFilenameTable, &_roomFilenameTableSize); + + delete [] _roomTable; _roomTable = 0; + _roomTableSize = 0; + + res_freeLangTable(&_characterImageTable, &_characterImageTableSize); + + delete [] _defaultShapeTable; + _defaultShapeTable = 0; + _defaultShapeTableSize = 0; + + res_freeLangTable(&_itemList, &_itemList_Size); + res_freeLangTable(&_takenList, &_takenList_Size); + res_freeLangTable(&_placedList, &_placedList_Size); + res_freeLangTable(&_droppedList, &_droppedList_Size); + res_freeLangTable(&_noDropList, &_noDropList_Size); + + delete [] _amuleteAnim; + _amuleteAnim = 0; + + for (int i = 0; i < 33; ++i) { + delete [] _specialPalettes[i]; + _specialPalettes[i] = 0; + } + + res_freeLangTable(&_putDownFirst, &_putDownFirst_Size); + res_freeLangTable(&_waitForAmulet, &_waitForAmulet_Size); + res_freeLangTable(&_blackJewel, &_blackJewel_Size); + res_freeLangTable(&_poisonGone, &_poisonGone_Size); + res_freeLangTable(&_healingTip, &_healingTip_Size); + + delete [] _healingShapeTable; + _healingShapeTable = 0; + _healingShapeTableSize = 0; + + delete [] _healingShape2Table; + _healingShape2Table = 0; + _healingShape2TableSize = 0; + + res_freeLangTable(&_thePoison, &_thePoison_Size); + res_freeLangTable(&_fluteString, &_fluteString_Size); + + delete [] _posionDeathShapeTable; + _posionDeathShapeTable = 0; + _posionDeathShapeTableSize = 0; + + delete [] _fluteAnimShapeTable; + _fluteAnimShapeTable = 0; + _fluteAnimShapeTableSize = 0; + + delete [] _winterScrollTable; + _winterScrollTable = 0; + _winterScrollTableSize = 0; + + delete [] _winterScroll1Table; + _winterScroll1Table = 0; + _winterScroll1TableSize = 0; + + delete [] _winterScroll2Table; + _winterScroll2Table = 0; + _winterScroll2TableSize = 0; + + delete [] _drinkAnimationTable; + _drinkAnimationTable = 0; + _drinkAnimationTableSize = 0; + + delete [] _brandonToWispTable; + _brandonToWispTable = 0; + _brandonToWispTableSize = 0; + + delete [] _magicAnimationTable; + _magicAnimationTable = 0; + _magicAnimationTableSize = 0; + + delete [] _brandonStoneTable; + _brandonStoneTable = 0; + _brandonStoneTableSize = 0; + + res_freeLangTable(&_flaskFull, &_flaskFull_Size); + res_freeLangTable(&_fullFlask, &_fullFlask_Size); + + res_freeLangTable(&_veryClever, &_veryClever_Size); + } +} + +void KyraEngine::res_loadLangTable(const char *filename, PAKFile *res, byte ***loadTo, int *size, bool nativ) { + char file[36]; + for (int i = 0; languages[i].ext; ++i) { + if (languages[i].flags != (_features & LANGUAGE_FLAGS) && nativ) { + continue; + } + + strcpy(file, filename); + strcat(file, languages[i].ext); + if (_features & GF_TALKIE) { + strcat(file, ".CD"); + } else if (_features & GF_DEMO) { + strcat(file, ".DEM"); + } + byte *temp = getFile(*res, file); + if (temp) { + res_loadTable(temp, loadTo, size); + delete [] temp; + temp = 0; + } else { + if (!nativ) + continue; + } + break; + } +} + +void KyraEngine::res_loadTable(const byte *src, byte ***loadTo, int *size) { + uint32 count = READ_BE_UINT32(src); src += 4; + *size = count; + *loadTo = new byte*[count]; + + const char *curPos = (const char*)src; + for (uint32 i = 0; i < count; ++i) { + int strLen = strlen(curPos); + (*loadTo)[i] = new byte[strLen+1]; + memcpy((*loadTo)[i], curPos, strLen+1); + curPos += strLen+1; + } +} + +void KyraEngine::res_loadRoomTable(const byte *src, Room **loadTo, int *size) { + uint32 count = READ_BE_UINT32(src); src += 4; + *size = count; + *loadTo = new Room[count]; + + for (uint32 i = 0; i < count; ++i) { + (*loadTo)[i].nameIndex = *src++; + (*loadTo)[i].northExit = READ_BE_UINT16(src); src += 2; + (*loadTo)[i].eastExit = READ_BE_UINT16(src); src += 2; + (*loadTo)[i].southExit = READ_BE_UINT16(src); src += 2; + (*loadTo)[i].westExit = READ_BE_UINT16(src); src += 2; + memset(&(*loadTo)[i].itemsTable[0], 0xFF, sizeof(byte)*6); + memset(&(*loadTo)[i].itemsTable[6], 0, sizeof(byte)*6); + memset((*loadTo)[i].itemsXPos, 0, sizeof(uint16)*12); + memset((*loadTo)[i].itemsYPos, 0, sizeof(uint8)*12); + memset((*loadTo)[i].needInit, 0, sizeof((*loadTo)[i].needInit)); + } +} + +void KyraEngine::res_loadShapeTable(const byte *src, Shape **loadTo, int *size) { + uint32 count = READ_BE_UINT32(src); src += 4; + *size = count; + *loadTo = new Shape[count]; + + for (uint32 i = 0; i < count; ++i) { + (*loadTo)[i].imageIndex = *src++; + (*loadTo)[i].x = *src++; + (*loadTo)[i].y = *src++; + (*loadTo)[i].w = *src++; + (*loadTo)[i].h = *src++; + (*loadTo)[i].xOffset = *src++; + (*loadTo)[i].yOffset = *src++; + } +} + +void KyraEngine::res_freeLangTable(char ***string, int *size) { + if (!string || !size) + return; + if (!*size || !*string) + return; + for (int i = 0; i < *size; ++i) { + delete [] (*string)[i]; + } + delete [] *string; + size = 0; + *string = 0; +} + +void KyraEngine::loadMouseShapes() { + loadBitmap("MOUSE.CPS", 3, 3, 0); + _screen->_curPage = 2; + _shapes[4] = _screen->encodeShape(0, 0, 8, 10, 0); + _shapes[5] = _screen->encodeShape(0, 0x17, 0x20, 7, 0); + _shapes[6] = _screen->encodeShape(0x50, 0x12, 0x10, 9, 0); + _shapes[7] = _screen->encodeShape(0x60, 0x12, 0x10, 11, 0); + _shapes[8] = _screen->encodeShape(0x70, 0x12, 0x10, 9, 0); + _shapes[9] = _screen->encodeShape(0x80, 0x12, 0x10, 11, 0); + _shapes[10] = _screen->encodeShape(0x90, 0x12, 0x10, 10, 0); + _shapes[364] = _screen->encodeShape(0x28, 0, 0x10, 13, 0); + _screen->setMouseCursor(1, 1, 0); + _screen->setMouseCursor(1, 1, _shapes[4]); + _screen->setShapePages(5, 3); +} + +void KyraEngine::loadCharacterShapes() { + int curImage = 0xFF; + int videoPage = _screen->_curPage; + _screen->_curPage = 2; + for (int i = 0; i < 115; ++i) { + assert(i < _defaultShapeTableSize); + Shape *shape = &_defaultShapeTable[i]; + if (shape->imageIndex == 0xFF) { + _shapes[i+7+4] = 0; + continue; + } + if (shape->imageIndex != curImage) { + assert(shape->imageIndex < _characterImageTableSize); + loadBitmap(_characterImageTable[shape->imageIndex], 3, 3, 0); + curImage = shape->imageIndex; + } + _shapes[i+7+4] = _screen->encodeShape(shape->x<<3, shape->y, shape->w<<3, shape->h, 1); + } + _screen->_curPage = videoPage; +} + +void KyraEngine::loadSpecialEffectShapes() { + loadBitmap("EFFECTS.CPS", 3, 3, 0); + _screen->_curPage = 2; + + int currShape; + for (currShape = 173; currShape < 183; currShape++) + _shapes[4 + currShape] = _screen->encodeShape((currShape-173) * 24, 0, 24, 24, 1); + + for (currShape = 183; currShape < 190; currShape++) + _shapes[4 + currShape] = _screen->encodeShape((currShape-183) * 24, 24, 24, 24, 1); + + for (currShape = 190; currShape < 201; currShape++) + _shapes[4 + currShape] = _screen->encodeShape((currShape-190) * 24, 48, 24, 24, 1); + + for (currShape = 201; currShape < 206; currShape++) + _shapes[4 + currShape] = _screen->encodeShape((currShape-201) * 16, 106, 16, 16, 1); +} + +void KyraEngine::loadItems() { + int shape; + + loadBitmap("JEWELS3.CPS", 3, 3, 0); + _screen->_curPage = 2; + + _shapes[327] = 0; + + for (shape = 1; shape < 6; shape++ ) + _shapes[327 + shape] = _screen->encodeShape((shape - 1) * 32, 0, 32, 17, 0); + + for (shape = 330; shape <= 334; shape++) + _shapes[4 + shape] = _screen->encodeShape((shape-330) * 32, 102, 32, 17, 0); + + for (shape = 335; shape <= 339; shape++) + _shapes[4 + shape] = _screen->encodeShape((shape-335) * 32, 17, 32, 17, 0); + + for (shape = 340; shape <= 344; shape++) + _shapes[4 + shape] = _screen->encodeShape((shape-340) * 32, 34, 32, 17, 0); + + for (shape = 345; shape <= 349; shape++) + _shapes[4 + shape] = _screen->encodeShape((shape-345) * 32, 51, 32, 17, 0); + + for (shape = 350; shape <= 354; shape++) + _shapes[4 + shape] = _screen->encodeShape((shape-350) * 32, 68, 32, 17, 0); + + for (shape = 355; shape <= 359; shape++) + _shapes[4 + shape] = _screen->encodeShape((shape-355) * 32, 85, 32, 17, 0); + + + loadBitmap("ITEMS.CPS", 3, 3, 0); + _screen->_curPage = 2; + + for (int i = 0; i < 107; i++) { + shape = findDuplicateItemShape(i); + + if (shape != -1) + _shapes[220 + i] = _shapes[220 + shape]; + else + _shapes[220 + i] = _screen->encodeShape( (i % 20) * 16, i/20 * 16, 16, 16, 0); + } + + uint32 size; + uint8 *fileData = _res->fileData("_ITEM_HT.DAT", &size); + assert(fileData); + + for (int i = 0; i < 107; i++) { + _itemTable[i].height = fileData[i]; + _itemTable[i].unk1 = _itemTable[i].unk2 = 0; + } + + delete[] fileData; +} + +void KyraEngine::loadButtonShapes() { + loadBitmap("BUTTONS2.CPS", 3, 3, 0); + _screen->_curPage = 2; + _scrollUpButton.process0PtrShape = _screen->encodeShape(0, 0, 24, 14, 1); + _scrollUpButton.process1PtrShape = _screen->encodeShape(24, 0, 24, 14, 1); + _scrollUpButton.process2PtrShape = _screen->encodeShape(48, 0, 24, 14, 1); + _scrollDownButton.process0PtrShape = _screen->encodeShape(0, 15, 24, 14, 1); + _scrollDownButton.process1PtrShape = _screen->encodeShape(24, 15, 24, 14, 1); + _scrollDownButton.process2PtrShape = _screen->encodeShape(48, 15, 24, 14, 1); + _screen->_curPage = 0; +} + +void KyraEngine::loadMainScreen(int page) { + if ((_features & GF_ENGLISH) && (_features & GF_TALKIE)) + loadBitmap("MAIN_ENG.CPS", page, page, 0); + else if(_features & GF_FRENCH) + loadBitmap("MAIN_FRE.CPS", page, page, 0); + else if(_features & GF_GERMAN) + loadBitmap("MAIN_GER.CPS", page, page, 0); + else if ((_features & GF_ENGLISH) && (_features & GF_FLOPPY)) + loadBitmap("MAIN15.CPS", page, page, 0); + else if (_features & GF_SPANISH) + loadBitmap("MAIN_SPA.CPS", page, page, 0); + else + warning("no main graphics file found"); + + uint8 *_pageSrc = _screen->getPagePtr(page); + uint8 *_pageDst = _screen->getPagePtr(0); + memcpy(_pageDst, _pageSrc, 320*200); +} + +const ScreenDim Screen::_screenDimTable[] = { + { 0x00, 0x00, 0x28, 0xC8, 0x0F, 0x0C, 0x00, 0x00 }, + { 0x08, 0x48, 0x18, 0x38, 0x0F, 0x0C, 0x00, 0x00 }, + { 0x01, 0x08, 0x26, 0x80, 0x0F, 0x0C, 0x00, 0x00 }, + { 0x00, 0xC2, 0x28, 0x06, 0x0F, 0x0C, 0x00, 0x00 }, + { 0x00, 0x90, 0x28, 0x38, 0x04, 0x0C, 0x00, 0x00 }, + { 0x01, 0x94, 0x26, 0x30, 0x04, 0x1B, 0x00, 0x00 }, + { 0x00, 0x90, 0x28, 0x38, 0x0F, 0x0D, 0x00, 0x00 }, + { 0x01, 0x96, 0x26, 0x32, 0x0F, 0x0D, 0x00, 0x00 }, + { 0x00, 0x00, 0x28, 0x88, 0x0F, 0x0C, 0x00, 0x00 }, + { 0x01, 0x20, 0x26, 0x80, 0x0F, 0x0C, 0x00, 0x00 }, + { 0x03, 0x28, 0x22, 0x46, 0x0F, 0x0D, 0x00, 0x00 } +}; + +const int Screen::_screenDimTableCount = ARRAYSIZE(_screenDimTable); + +// CD Version *could* use an different opcodeTable +#define Opcode(x) &KyraEngine::x +KyraEngine::OpcodeProc KyraEngine::_opcodeTable[] = { + // 0x00 + Opcode(cmd_magicInMouseItem), + Opcode(cmd_characterSays), + Opcode(cmd_pauseTicks), + Opcode(cmd_drawSceneAnimShape), + // 0x04 + Opcode(cmd_queryGameFlag), + Opcode(cmd_setGameFlag), + Opcode(cmd_resetGameFlag), + Opcode(cmd_runNPCScript), + // 0x08 + Opcode(cmd_setSpecialExitList), + Opcode(cmd_blockInWalkableRegion), + Opcode(cmd_blockOutWalkableRegion), + Opcode(cmd_walkPlayerToPoint), + // 0x0c + Opcode(cmd_dropItemInScene), + Opcode(cmd_drawAnimShapeIntoScene), + Opcode(cmd_createMouseItem), + Opcode(cmd_savePageToDisk), + // 0x10 + Opcode(cmd_sceneAnimOn), + Opcode(cmd_sceneAnimOff), + Opcode(cmd_getElapsedSeconds), + Opcode(cmd_mouseIsPointer), + // 0x14 + Opcode(cmd_destroyMouseItem), + Opcode(cmd_runSceneAnimUntilDone), + Opcode(cmd_fadeSpecialPalette), + Opcode(cmd_playAdlibSound), + // 0x18 + Opcode(cmd_playAdlibScore), + Opcode(cmd_phaseInSameScene), + Opcode(cmd_setScenePhasingFlag), + Opcode(cmd_resetScenePhasingFlag), + // 0x1c + Opcode(cmd_queryScenePhasingFlag), + Opcode(cmd_sceneToDirection), + Opcode(cmd_setBirthstoneGem), + Opcode(cmd_placeItemInGenericMapScene), + // 0x20 + Opcode(cmd_setBrandonStatusBit), + Opcode(cmd_pauseSeconds), + Opcode(cmd_getCharactersLocation), + Opcode(cmd_runNPCSubscript), + // 0x24 + Opcode(cmd_magicOutMouseItem), + Opcode(cmd_internalAnimOn), + Opcode(cmd_forceBrandonToNormal), + Opcode(cmd_poisonDeathNow), + // 0x28 + Opcode(cmd_setScaleMode), + Opcode(cmd_openWSAFile), + Opcode(cmd_closeWSAFile), + Opcode(cmd_runWSAFromBeginningToEnd), + // 0x2c + Opcode(cmd_displayWSAFrame), + Opcode(cmd_enterNewScene), + Opcode(cmd_setSpecialEnterXAndY), + Opcode(cmd_runWSAFrames), + // 0x30 + Opcode(cmd_popBrandonIntoScene), + Opcode(cmd_restoreAllObjectBackgrounds), + Opcode(cmd_setCustomPaletteRange), + Opcode(cmd_loadPageFromDisk), + // 0x34 + Opcode(cmd_customPrintTalkString), + Opcode(cmd_restoreCustomPrintBackground), + Opcode(cmd_hideMouse), + Opcode(cmd_showMouse), + // 0x38 + Opcode(cmd_getCharacterX), + Opcode(cmd_getCharacterY), + Opcode(cmd_changeCharactersFacing), + Opcode(cmd_copyWSARegion), + // 0x3c + Opcode(cmd_printText), + Opcode(cmd_random), + Opcode(cmd_loadSoundFile), + Opcode(cmd_displayWSAFrameOnHidPage), + // 0x40 + Opcode(cmd_displayWSASequentialFrames), + Opcode(cmd_drawCharacterStanding), + Opcode(cmd_internalAnimOff), + Opcode(cmd_changeCharactersXAndY), + // 0x44 + Opcode(cmd_clearSceneAnimatorBeacon), + Opcode(cmd_querySceneAnimatorBeacon), + Opcode(cmd_refreshSceneAnimator), + Opcode(cmd_placeItemInOffScene), + // 0x48 + Opcode(cmd_wipeDownMouseItem), + Opcode(cmd_placeCharacterInOtherScene), + Opcode(cmd_getKey), + Opcode(cmd_specificItemInInventory), + // 0x4c + Opcode(cmd_popMobileNPCIntoScene), + Opcode(cmd_mobileCharacterInScene), + Opcode(cmd_hideMobileCharacter), + Opcode(cmd_unhideMobileCharacter), + // 0x50 + Opcode(cmd_setCharactersLocation), + Opcode(cmd_walkCharacterToPoint), + Opcode(cmd_specialEventDisplayBrynnsNote), + Opcode(cmd_specialEventRemoveBrynnsNote), + // 0x54 + Opcode(cmd_setLogicPage), + Opcode(cmd_fatPrint), + Opcode(cmd_preserveAllObjectBackgrounds), + Opcode(cmd_updateSceneAnimations), + // 0x58 + Opcode(cmd_sceneAnimationActive), + Opcode(cmd_setCharactersMovementDelay), + Opcode(cmd_getCharactersFacing), + Opcode(cmd_bkgdScrollSceneAndMasksRight), + // 0x5c + Opcode(cmd_dispelMagicAnimation), + Opcode(cmd_findBrightestFireberry), + Opcode(cmd_setFireberryGlowPalette), + Opcode(cmd_setDeathHandlerFlag), + // 0x60 + Opcode(cmd_drinkPotionAnimation), + Opcode(cmd_makeAmuletAppear), + Opcode(cmd_drawItemShapeIntoScene), + Opcode(cmd_setCharactersCurrentFrame), + // 0x64 + Opcode(cmd_waitForConfirmationMouseClick), + Opcode(cmd_pageFlip), + Opcode(cmd_setSceneFile), + Opcode(cmd_getItemInMarbleVase), + // 0x68 + Opcode(cmd_setItemInMarbleVase), + Opcode(cmd_addItemToInventory), + Opcode(cmd_intPrint), + Opcode(cmd_shakeScreen), + // 0x6c + Opcode(cmd_createAmuletJewel), + Opcode(cmd_setSceneAnimCurrXY), + Opcode(cmd_poisonBrandonAndRemaps), + Opcode(cmd_fillFlaskWithWater), + // 0x70 + Opcode(cmd_getCharactersMovementDelay), + Opcode(cmd_getBirthstoneGem), + Opcode(cmd_queryBrandonStatusBit), + Opcode(cmd_playFluteAnimation), + // 0x74 + Opcode(cmd_playWinterScrollSequence), + Opcode(cmd_getIdolGem), + Opcode(cmd_setIdolGem), + Opcode(cmd_totalItemsInScene), + // 0x78 + Opcode(cmd_restoreBrandonsMovementDelay), + Opcode(cmd_setMousePos), + Opcode(cmd_getMouseState), + Opcode(cmd_setEntranceMouseCursorTrack), + // 0x7c + Opcode(cmd_itemAppearsOnGround), + Opcode(cmd_setNoDrawShapesFlag), + Opcode(cmd_fadeEntirePalette), + Opcode(cmd_itemOnGroundHere), + // 0x80 + Opcode(cmd_queryCauldronState), + Opcode(cmd_setCauldronState), + Opcode(cmd_queryCrystalState), + Opcode(cmd_setCrystalState), + // 0x84 + Opcode(cmd_setPaletteRange), + Opcode(cmd_shrinkBrandonDown), + Opcode(cmd_growBrandonUp), + Opcode(cmd_setBrandonScaleXAndY), + // 0x88 + Opcode(cmd_resetScaleMode), + Opcode(cmd_getScaleDepthTableValue), + Opcode(cmd_setScaleDepthTableValue), + Opcode(cmd_message), + // 0x8c + Opcode(cmd_checkClickOnNPC), + Opcode(cmd_getFoyerItem), + Opcode(cmd_setFoyerItem), + Opcode(cmd_setNoItemDropRegion), + // 0x90 + Opcode(cmd_walkMalcolmOn), + Opcode(cmd_passiveProtection), + Opcode(cmd_setPlayingLoop), + Opcode(cmd_brandonToStoneSequence), + // 0x94 + Opcode(cmd_brandonHealingSequence), + Opcode(cmd_protectCommandLine), + Opcode(cmd_pauseMusicSeconds), + Opcode(cmd_resetMaskRegion), + // 0x98 + Opcode(cmd_setPaletteChangeFlag), + Opcode(cmd_fillRect), + Opcode(cmd_vocUnload), + Opcode(cmd_vocLoad), + Opcode(cmd_dummy) +}; +#undef Opcode + +const int KyraEngine::_opcodeTableSize = ARRAYSIZE(_opcodeTable); + +const char *KyraEngine::_xmidiFiles[] = { + "INTRO.XMI", + "KYRA1A.XMI", + "KYRA1B.XMI", + "KYRA2A.XMI", + "KYRA3A.XMI", + "KYRA4A.XMI", + "KYRA4B.XMI", + "KYRA5A.XMI", + "KYRA5B.XMI", + "KYRAMISC.XMI" +}; + +const int KyraEngine::_xmidiFilesCount = ARRAYSIZE(_xmidiFiles); + +const int8 KyraEngine::_charXPosTable[] = { + 0, 4, 4, 4, 0, -4, -4, -4 +}; + +const int8 KyraEngine::_addXPosTable[] = { + 4, 4, 0, -4, -4, -4, 0, 4 +}; + +const int8 KyraEngine::_charYPosTable[] = { + -2, -2, 0, 2, 2, 2, 0, -2 +}; + +const int8 KyraEngine::_addYPosTable[] = { + 0, -2, -2, -2, 0, 2, 2, 2 +}; + +const uint16 KyraEngine::_itemPosX[] = { + 95, 115, 135, 155, 175, 95, 115, 135, 155, 175 +}; + +const uint8 KyraEngine::_itemPosY[] = { + 160, 160, 160, 160, 160, 181, 181, 181, 181, 181 +}; + +Button KyraEngine::_buttonData[] = { + { 0, 0x02, /*XXX,*/0, 0, 0, /*XXX,*/ 0x0400, 0, 0, 0, 0, 0, 0, 0, 0x05D, 0x9E, 0x13, 0x14, /*XXX,*/ 0, &KyraEngine::buttonInventoryCallback/*, XXX*/ }, + { 0, 0x01, /*XXX,*/1, 1, 1, /*XXX,*/ 0x0487, 0, 0, 0, 0, 0, 0, 0, 0x009, 0xA4, 0x36, 0x1E, /*XXX,*/ 0, &KyraEngine::buttonMenuCallback/*, XXX*/ }, + { 0, 0x03, /*XXX,*/0, 0, 0, /*XXX,*/ 0x0400, 0, 0, 0, 0, 0, 0, 0, 0x071, 0x9E, 0x13, 0x14, /*XXX,*/ 0, &KyraEngine::buttonInventoryCallback/*, XXX*/ }, + { 0, 0x04, /*XXX,*/0, 0, 0, /*XXX,*/ 0x0400, 0, 0, 0, 0, 0, 0, 0, 0x085, 0x9E, 0x13, 0x14, /*XXX,*/ 0, &KyraEngine::buttonInventoryCallback/*, XXX*/ }, + { 0, 0x05, /*XXX,*/0, 0, 0, /*XXX,*/ 0x0400, 0, 0, 0, 0, 0, 0, 0, 0x099, 0x9E, 0x13, 0x14, /*XXX,*/ 0, &KyraEngine::buttonInventoryCallback/*, XXX*/ }, + { 0, 0x06, /*XXX,*/0, 0, 0, /*XXX,*/ 0x0400, 0, 0, 0, 0, 0, 0, 0, 0x0AD, 0x9E, 0x13, 0x14, /*XXX,*/ 0, &KyraEngine::buttonInventoryCallback/*, XXX*/ }, + { 0, 0x07, /*XXX,*/0, 0, 0, /*XXX,*/ 0x0400, 0, 0, 0, 0, 0, 0, 0, 0x05D, 0xB3, 0x13, 0x14, /*XXX,*/ 0, &KyraEngine::buttonInventoryCallback/*, XXX*/ }, + { 0, 0x08, /*XXX,*/0, 0, 0, /*XXX,*/ 0x0400, 0, 0, 0, 0, 0, 0, 0, 0x071, 0xB3, 0x13, 0x14, /*XXX,*/ 0, &KyraEngine::buttonInventoryCallback/*, XXX*/ }, + { 0, 0x09, /*XXX,*/0, 0, 0, /*XXX,*/ 0x0400, 0, 0, 0, 0, 0, 0, 0, 0x085, 0xB3, 0x13, 0x14, /*XXX,*/ 0, &KyraEngine::buttonInventoryCallback/*, XXX*/ }, + { 0, 0x0A, /*XXX,*/0, 0, 0, /*XXX,*/ 0x0400, 0, 0, 0, 0, 0, 0, 0, 0x099, 0xB3, 0x13, 0x14, /*XXX,*/ 0, &KyraEngine::buttonInventoryCallback/*, XXX*/ }, + { 0, 0x0B, /*XXX,*/0, 0, 0, /*XXX,*/ 0x0400, 0, 0, 0, 0, 0, 0, 0, 0x0AD, 0xB3, 0x13, 0x14, /*XXX,*/ 0, &KyraEngine::buttonInventoryCallback/*, XXX*/ }, + { 0, 0x15, /*XXX,*/1, 1, 1, /*XXX,*/ 0x0487, 0, 0, 0, 0, 0, 0, 0, 0x0FD, 0x9C, 0x1A, 0x12, /*XXX,*/ 0, &KyraEngine::buttonAmuletCallback/*, XXX*/ }, + { 0, 0x16, /*XXX,*/1, 1, 1, /*XXX,*/ 0x0487, 0, 0, 0, 0, 0, 0, 0, 0x0E7, 0xAA, 0x1A, 0x12, /*XXX,*/ 0, &KyraEngine::buttonAmuletCallback/*, XXX*/ }, + { 0, 0x17, /*XXX,*/1, 1, 1, /*XXX,*/ 0x0487, 0, 0, 0, 0, 0, 0, 0, 0x0FD, 0xB5, 0x1A, 0x12, /*XXX,*/ 0, &KyraEngine::buttonAmuletCallback/*, XXX*/ }, + { 0, 0x18, /*XXX,*/1, 1, 1, /*XXX,*/ 0x0487, 0, 0, 0, 0, 0, 0, 0, 0x113, 0xAA, 0x1A, 0x12, /*XXX,*/ 0, &KyraEngine::buttonAmuletCallback/*, XXX*/ } +}; + +Button *KyraEngine::_buttonDataListPtr[] = { + &_buttonData[1], + &_buttonData[2], + &_buttonData[3], + &_buttonData[4], + &_buttonData[5], + &_buttonData[6], + &_buttonData[7], + &_buttonData[8], + &_buttonData[9], + &_buttonData[10], + &_buttonData[11], + &_buttonData[12], + &_buttonData[13], + &_buttonData[14], + 0 +}; + +Button KyraEngine::_scrollUpButton = {0, 0x12, 1, 1, 1, 0x483, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x18, 0x0f, 0, 0}; +Button KyraEngine::_scrollDownButton = {0, 0x13, 1, 1, 1, 0x483, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x18, 0x0f, 0, 0}; + + + +Button KyraEngine::_menuButtonData[] = { + { 0, 0x0c, /*XXX,*/1, 1, 1, /*XXX,*/ 0x487, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /*XXX,*/ 0, 0 /*, XXX*/ }, + { 0, 0x0d, /*XXX,*/1, 1, 1, /*XXX,*/ 0x487, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /*XXX,*/ 0, 0 /*, XXX*/ }, + { 0, 0x0e, /*XXX,*/1, 1, 1, /*XXX,*/ 0x487, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /*XXX,*/ 0, 0 /*, XXX*/ }, + { 0, 0x0f, /*XXX,*/1, 1, 1, /*XXX,*/ 0x487, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /*XXX,*/ 0, 0 /*, XXX*/ }, + { 0, 0x10, /*XXX,*/1, 1, 1, /*XXX,*/ 0x487, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /*XXX,*/ 0, 0 /*, XXX*/ }, + { 0, 0x11, /*XXX,*/1, 1, 1, /*XXX,*/ 0x487, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /*XXX,*/ 0, 0 /*, XXX*/ } +}; + +Menu KyraEngine::_menu[] = { + { -1, -1, 208, 136, 248, 249, 250, "The Legend of Kyrandia", 251, -1, 8, 0, 5, -1, -1, -1, -1, + { + {1, 0, 0, "Load a Game", -1, -1, 30, 148, 15, 252, 253, 24, 0, + 248, 249, 250, &KyraEngine::gui_loadGameMenu, -1, 0, 0, 0, 0, 0}, + {1, 0, 0, "Save this Game", -1, -1, 47, 148, 15, 252, 253, 24, 0, + 248, 249, 250, &KyraEngine::gui_saveGameMenu, -1, 0, 0, 0, 0, 0}, + {1, 0, 0, "Game Controls", -1, -1, 64, 148, 15, 252, 253, 24, 0, + 248, 249, 250, /*&menu_gameControls*/ 0, -1, 0, 0, 0, 0, 0}, + {1, 0, 0, "Quit playing", -1, -1, 81, 148, 15, 252, 253, 24, 0, + 248, 249, 250, &KyraEngine::gui_quitPlaying, -1, 0, 0, 0, 0, 0}, + {1, 0, 0, "Resume game", 86, 0, 110, 92, 15, 252, 253, -1, 255, + 248, 249, 250, &KyraEngine::gui_resumeGame, -1, 0, 0, 0, 0, 0} + } + }, + { -1, -1, 288, 56, 248, 249, 250, 0, 254,-1, 8, 0, 2, -1, -1, -1, -1, + { + {1, 0, 0, "Yes", 24, 0, 30, 72, 15, 252, 253, -1, 255, + 248, 249, 250, &KyraEngine::gui_quitConfirmYes, -1, 0, 0, 0, 0, 0}, + {1, 0, 0, "No", 192, 0, 30, 72, 15, 252, 253, -1, 255, + 248, 249, 250, &KyraEngine::gui_quitConfirmNo, -1, 0, 0, 0, 0, 0} + } + }, + { -1, -1, 288, 160, 248, 249, 250, 0, 251, -1, 8, 0, 6, 132, 22, 132, 124, + { + {1, 0, 0, 0, -1, 255, 39, 256, 15, 252, 253, 5, 0, + 248, 249, 250, 0, -1, 0, 0, 0, 0, 0}, + {1, 0, 0, 0, -1, 255, 56, 256, 15, 252, 253, 5, 0, + 248, 249, 250, 0, -1, 0, 0, 0, 0, 0}, + {1, 0, 0, 0, -1, 255, 73, 256, 15, 252, 253, 5, 0, + 248, 249, 250, 0, -1, 0, 0, 0, 0, 0}, + {1, 0, 0, 0, -1, 255, 90, 256, 15, 252, 253, 5, 0, + 248, 249, 250, 0, -1, 0, 0, 0, 0, 0}, + {1, 0, 0, 0, -1, 255, 107, 256, 15, 252, 253, 5, 0, + 248, 249, 250, 0, -1, 0, 0, 0, 0, 0}, + {1, 0, 0, "Cancel", 184, 0, 134, 88, 15, 252, 253, -1, 255, + 248, 249, 250, &KyraEngine::gui_cancelSubMenu, -1, 0, 0, 0, 0, 0}, + } + }, + { -1, -1, 288, 67, 248, 249, 250, "Enter a description of your saved game:", 251, -1, 8, 0, 3, -1, -1, -1, -1, + { + {1, 0, 0, "Save", 24, 0, 44, 72, 15, 252, 253, -1, 255, + 248, 249, 250, &KyraEngine::gui_savegameConfirm, -1, 0, 0, 0, 0, 0}, + // {1, 0, 0, "Cancel", 110, 0, 44, 72, 15, 252, 253, -1, 255, + // 248, 249, 250, /*&menu_cancelconfirmsave*/ 0, -1, 0, 0, 0, 0, 0}, + {1, 0, 0, "Cancel", 192, 0, 44, 72, 15, 252, 253, -1, 255, + 248, 249, 250, &KyraEngine::gui_cancelSubMenu, -1, 0, 0, 0, 0, 0} + } + } +}; + +const uint8 KyraEngine::_magicMouseItemStartFrame[] = { + 0xAD, 0xB7, 0xBE, 0x00 +}; + +const uint8 KyraEngine::_magicMouseItemEndFrame[] = { + 0xB1, 0xB9, 0xC2, 0x00 +}; + +const uint8 KyraEngine::_magicMouseItemStartFrame2[] = { + 0xB2, 0xBA, 0xC3, 0x00 +}; + +const uint8 KyraEngine::_magicMouseItemEndFrame2[] = { + 0xB6, 0xBD, 0xC8, 0x00 +}; + +const uint16 KyraEngine::_amuletX[] = { 231, 275, 253, 253 }; +const uint16 KyraEngine::_amuletY[] = { 170, 170, 159, 181 }; + +const uint16 KyraEngine::_amuletX2[] = { 0x000, 0x0FD, 0x0E7, 0x0FD, 0x113, 0x000 }; +const uint16 KyraEngine::_amuletY2[] = { 0x000, 0x09F, 0x0AA, 0x0B5, 0x0AA, 0x000 }; +} // End of namespace Kyra diff --git a/engines/kyra/text.cpp b/engines/kyra/text.cpp new file mode 100644 index 0000000000..2877687682 --- /dev/null +++ b/engines/kyra/text.cpp @@ -0,0 +1,574 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2005-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 "kyra/kyra.h" +#include "kyra/screen.h" +#include "kyra/text.h" +#include "kyra/animator.h" +#include "kyra/sprites.h" + +#include "common/system.h" + +namespace Kyra { + +void KyraEngine::waitForChatToFinish(int16 chatDuration, char *chatStr, uint8 charNum) { + debug(9, "KyraEngine::waitForChatToFinish(%i, %s, %i)", chatDuration, chatStr, charNum); + bool hasUpdatedNPCs = false; + bool runLoop = true; + uint8 currPage; + OSystem::Event event; + int16 delayTime; + + //while( towns_isEscKeyPressed() ) + //towns_getKey(); + + uint32 timeToEnd = strlen(chatStr) * 8 * _tickLength + _system->getMillis(); + + if (chatDuration != -1 ) { + switch (_configTalkspeed) { + case 0: chatDuration *= 2; + break; + case 2: chatDuration /= 4; + break; + case 3: chatDuration = -1; + } + } + + if (chatDuration != -1) + chatDuration *= _tickLength; + + disableTimer(14); + disableTimer(18); + disableTimer(19); + + uint32 timeAtStart = _system->getMillis(); + uint32 loopStart; + while (runLoop) { + loopStart = _system->getMillis(); + if (_currentCharacter->sceneId == 210) + if (seq_playEnd()) + break; + + if (_system->getMillis() > timeToEnd && !hasUpdatedNPCs) { + hasUpdatedNPCs = true; + disableTimer(15); + _currHeadShape = 4; + _animator->animRefreshNPC(0); + _animator->animRefreshNPC(_talkingCharNum); + + if (_charSayUnk2 != -1) { + _animator->sprites()[_charSayUnk2].active = 0; + _sprites->_anims[_charSayUnk2].play = false; + _charSayUnk2 = -1; + } + } + + updateGameTimers(); + _sprites->updateSceneAnims(); + _animator->restoreAllObjectBackgrounds(); + _animator->preserveAnyChangedBackgrounds(); + _animator->prepDrawAllObjects(); + + currPage = _screen->_curPage; + _screen->_curPage = 2; + _text->printCharacterText(chatStr, charNum, _characterList[charNum].x1); + _animator->_updateScreen = true; + _screen->_curPage = currPage; + + _animator->copyChangedObjectsForward(0); + updateTextFade(); + + if ((chatDuration < (int16)(_system->getMillis() - timeAtStart)) && chatDuration != -1) + break; + + while (_system->pollEvent(event)) { + switch (event.type) { + case OSystem::EVENT_KEYDOWN: + if (event.kbd.keycode == '.') + _skipFlag = true; + break; + case OSystem::EVENT_QUIT: + quitGame(); + case OSystem::EVENT_LBUTTONDOWN: + runLoop = false; + break; + default: + break; + } + } + + if (_skipFlag) + runLoop = false; + + delayTime = (loopStart + _gameSpeed) - _system->getMillis(); + if (delayTime > 0) + _system->delayMillis(delayTime); + } + + enableTimer(14); + enableTimer(15); + enableTimer(18); + enableTimer(19); + //clearKyrandiaButtonIO(); +} + +void KyraEngine::endCharacterChat(int8 charNum, int16 convoInitialized) { + _charSayUnk3 = -1; + + if (charNum > 4 && charNum < 11) { + //TODO: weird _game_inventory stuff here + warning("STUB: endCharacterChat() for high charnums"); + } + + if (convoInitialized != 0) { + _talkingCharNum = -1; + _currentCharacter->currentAnimFrame = 7; + _animator->animRefreshNPC(0); + _animator->updateAllObjectShapes(); + } +} + +void KyraEngine::restoreChatPartnerAnimFrame(int8 charNum) { + _talkingCharNum = -1; + + if (charNum > 0 && charNum < 5) { + _characterList[charNum].currentAnimFrame = _currentChatPartnerBackupFrame; + _animator->animRefreshNPC(charNum); + } + + _currentCharacter->currentAnimFrame = 7; + _animator->animRefreshNPC(0); + _animator->updateAllObjectShapes(); +} + +void KyraEngine::backupChatPartnerAnimFrame(int8 charNum) { + _talkingCharNum = 0; + + if (charNum < 5 && charNum > 0) + _currentChatPartnerBackupFrame = _characterList[charNum].currentAnimFrame; + + if (_scaleMode != 0) + _currentCharacter->currentAnimFrame = 7; + else + _currentCharacter->currentAnimFrame = _currentCharAnimFrame; + + _animator->animRefreshNPC(0); + _animator->updateAllObjectShapes(); +} + +int8 KyraEngine::getChatPartnerNum() { + uint8 sceneTable[] = {0x2, 0x5, 0x2D, 0x7, 0x1B, 0x8, 0x22, 0x9, 0x30, 0x0A}; + int pos = 0; + int partner = -1; + + for (int i = 1; i < 6; i++) { + if (_currentCharacter->sceneId == sceneTable[pos]) { + partner = sceneTable[pos+1]; + break; + } + pos += 2; + } + + for (int i = 1; i < 5; i++) { + if (_characterList[i].sceneId == _currentCharacter->sceneId) { + partner = i; + break; + } + } + return partner; +} + +int KyraEngine::initCharacterChat(int8 charNum) { + if (_talkingCharNum == -1) { + _talkingCharNum = 0; + + if (_scaleMode != 0) + _currentCharacter->currentAnimFrame = 7; + else + _currentCharacter->currentAnimFrame = 16; + + _animator->animRefreshNPC(0); + _animator->updateAllObjectShapes(); + } + + _charSayUnk2 = -1; + _animator->flagAllObjectsForBkgdChange(); + _animator->restoreAllObjectBackgrounds(); + + if (charNum > 4 && charNum < 11) { + // TODO: Fill in weird _game_inventory stuff here + warning("STUB: initCharacterChat() for high charnums"); + } + + _animator->flagAllObjectsForRefresh(); + _animator->flagAllObjectsForBkgdChange(); + _animator->preserveAnyChangedBackgrounds(); + _charSayUnk3 = charNum; + + return 1; +} + +void KyraEngine::characterSays(char *chatStr, int8 charNum, int8 chatDuration) { + debug(9, "KyraEngine::characterSays('%s', %i, %d)", chatStr, charNum, chatDuration); + uint8 startAnimFrames[] = { 0x10, 0x32, 0x56, 0x0, 0x0, 0x0 }; + + uint16 chatTicks; + int16 convoInitialized; + int8 chatPartnerNum; + + if (_currentCharacter->sceneId == 210) + return; + + convoInitialized = initCharacterChat(charNum); + chatPartnerNum = getChatPartnerNum(); + + if (chatPartnerNum != -1 && chatPartnerNum < 5) + backupChatPartnerAnimFrame(chatPartnerNum); + + if (charNum < 5) { + _characterList[charNum].currentAnimFrame = startAnimFrames[charNum]; + _charSayUnk3 = charNum; + _talkingCharNum = charNum; + _animator->animRefreshNPC(charNum); + } + + char *processedString = _text->preprocessString(chatStr); + int lineNum = _text->buildMessageSubstrings(processedString); + + int16 yPos = _characterList[charNum].y1; + yPos -= _scaleTable[charNum] * _characterList[charNum].height; + yPos -= 8; + yPos -= lineNum * 10; + + if (yPos < 11) + yPos = 11; + + if (yPos > 100) + yPos = 100; + + _text->_talkMessageY = yPos; + _text->_talkMessageH = lineNum * 10; + _animator->restoreAllObjectBackgrounds(); + + _screen->copyRegion(12, _text->_talkMessageY, 12, 136, 296, _text->_talkMessageH, 2, 2); + _screen->hideMouse(); + + _text->printCharacterText(processedString, charNum, _characterList[charNum].x1); + _screen->showMouse(); + + if (chatDuration == -2) + chatTicks = strlen(processedString) * 9; + else + chatTicks = chatDuration; + + waitForChatToFinish(chatTicks, chatStr, charNum); + + _animator->restoreAllObjectBackgrounds(); + + _screen->copyRegion(12, 136, 12, _text->_talkMessageY, 296, _text->_talkMessageH, 2, 2); + _animator->preserveAllBackgrounds(); + _animator->prepDrawAllObjects(); + _screen->hideMouse(); + + _screen->copyRegion(12, _text->_talkMessageY, 12, _text->_talkMessageY, 296, _text->_talkMessageH, 2, 0); + _screen->showMouse(); + _animator->flagAllObjectsForRefresh(); + _animator->copyChangedObjectsForward(0); + + if (chatPartnerNum != -1 && chatPartnerNum < 5) + restoreChatPartnerAnimFrame(chatPartnerNum); + + endCharacterChat(charNum, convoInitialized); +} + +void KyraEngine::drawSentenceCommand(char *sentence, int color) { + debug(9, "KyraEngine::drawSentenceCommand('%s', %i)", sentence, color); + _screen->hideMouse(); + _screen->fillRect(8, 143, 311, 152, 12); + + if (_startSentencePalIndex != color || _fadeText != false) { + _currSentenceColor[0] = _screen->_currentPalette[765] = _screen->_currentPalette[color*3]; + _currSentenceColor[1] = _screen->_currentPalette[766] = _screen->_currentPalette[color*3+1]; + _currSentenceColor[2] = _screen->_currentPalette[767] = _screen->_currentPalette[color*3+2]; + + _screen->setScreenPalette(_screen->_currentPalette); + _startSentencePalIndex = 0; + } + + _text->printText(sentence, 8, 143, 0xFF, 12, 0); + _screen->showMouse(); + setTextFadeTimerCountdown(15); + _fadeText = false; +} + +void KyraEngine::updateSentenceCommand(char *str1, char *str2, int color) { + debug(9, "KyraEngine::updateSentenceCommand('%s', '%s', %i)", str1, str2, color); + char sentenceCommand[500]; + strncpy(sentenceCommand, str1, 500); + if (str2) + strncat(sentenceCommand, str2, 500 - strlen(sentenceCommand)); + + drawSentenceCommand(sentenceCommand, color); + _screen->updateScreen(); +} + +void KyraEngine::updateTextFade() { + debug(9, "KyraEngine::updateTextFade()"); + if (!_fadeText) + return; + + bool finished = false; + for (int i = 0; i < 3; i++) + if (_currSentenceColor[i] > 4) + _currSentenceColor[i] -= 4; + else + if (_currSentenceColor[i]) { + _currSentenceColor[i] = 0; + finished = true; + } + + _screen->_currentPalette[765] = _currSentenceColor[0]; + _screen->_currentPalette[766] = _currSentenceColor[1]; + _screen->_currentPalette[767] = _currSentenceColor[2]; + _screen->setScreenPalette(_screen->_currentPalette); + + if (finished) { + _fadeText = false; + _startSentencePalIndex = -1; + } +} + +TextDisplayer::TextDisplayer(Screen *screen) { + _screen = screen; + + _talkCoords.y = 0x88; + _talkCoords.x = 0; + _talkCoords.w = 0; + _talkMessageY = 0xC; + _talkMessageH = 0; + _talkMessagePrinted = false; +} + +void TextDisplayer::setTalkCoords(uint16 y) { + debug(9, "TextDisplayer::setTalkCoords(%d)", y); + _talkCoords.y = y; +} + +int TextDisplayer::getCenterStringX(const char *str, int x1, int x2) { + debug(9, "TextDisplayer::getCenterStringX('%s', %d, %d)", str, x1, x2); + _screen->_charWidth = -2; + Screen::FontId curFont = _screen->setFont(Screen::FID_8_FNT); + int strWidth = _screen->getTextWidth(str); + _screen->setFont(curFont); + _screen->_charWidth = 0; + int w = x2 - x1 + 1; + return x1 + (w - strWidth) / 2; +} + +int TextDisplayer::getCharLength(const char *str, int len) { + debug(9, "TextDisplayer::getCharLength('%s', %d)", str, len); + int charsCount = 0; + if (*str) { + _screen->_charWidth = -2; + Screen::FontId curFont = _screen->setFont(Screen::FID_8_FNT); + int i = 0; + while (i <= len && *str) { + i += _screen->getCharWidth(*str++); + ++charsCount; + } + _screen->setFont(curFont); + _screen->_charWidth = 0; + } + return charsCount; +} + +int TextDisplayer::dropCRIntoString(char *str, int offs) { + debug(9, "TextDisplayer::dropCRIntoString('%s', %d)", str, offs); + int pos = 0; + str += offs; + while (*str) { + if (*str == ' ') { + *str = '\r'; + return pos; + } + ++str; + ++pos; + } + return 0; +} + +char *TextDisplayer::preprocessString(const char *str) { + debug(9, "TextDisplayer::preprocessString('%s')", str); + assert(strlen(str) < sizeof(_talkBuffer) - 1); + strcpy(_talkBuffer, str); + char *p = _talkBuffer; + while (*p) { + if (*p == '\r') { + return _talkBuffer; + } + ++p; + } + p = _talkBuffer; + Screen::FontId curFont = _screen->setFont(Screen::FID_8_FNT); + _screen->_charWidth = -2; + int textWidth = _screen->getTextWidth(p); + _screen->_charWidth = 0; + if (textWidth > 176) { + if (textWidth > 352) { + int count = getCharLength(p, textWidth / 3); + int offs = dropCRIntoString(p, count); + p += count + offs; + _screen->_charWidth = -2; + textWidth = _screen->getTextWidth(p); + _screen->_charWidth = 0; + count = getCharLength(p, textWidth / 2); + dropCRIntoString(p, count); + } else { + int count = getCharLength(p, textWidth / 2); + dropCRIntoString(p, count); + } + } + _screen->setFont(curFont); + return _talkBuffer; +} + +int TextDisplayer::buildMessageSubstrings(const char *str) { + debug(9, "TextDisplayer::buildMessageSubstrings('%s')", str); + int currentLine = 0; + int pos = 0; + while (*str) { + if (*str == '\r') { + assert(currentLine < TALK_SUBSTRING_NUM); + _talkSubstrings[currentLine * TALK_SUBSTRING_LEN + pos] = '\0'; + ++currentLine; + pos = 0; + } else { + _talkSubstrings[currentLine * TALK_SUBSTRING_LEN + pos] = *str; + ++pos; + if (pos > TALK_SUBSTRING_LEN - 2) { + pos = TALK_SUBSTRING_LEN - 2; + } + } + ++str; + } + _talkSubstrings[currentLine * TALK_SUBSTRING_LEN + pos] = '\0'; + return currentLine + 1; +} + +int TextDisplayer::getWidestLineWidth(int linesCount) { + debug(9, "TextDisplayer::getWidestLineWidth(%d)", linesCount); + int maxWidth = 0; + Screen::FontId curFont = _screen->setFont(Screen::FID_8_FNT); + _screen->_charWidth = -2; + for (int l = 0; l < linesCount; ++l) { + int w = _screen->getTextWidth(&_talkSubstrings[l * TALK_SUBSTRING_LEN]); + if (maxWidth < w) { + maxWidth = w; + } + } + _screen->setFont(curFont); + _screen->_charWidth = 0; + return maxWidth; +} + +void TextDisplayer::calcWidestLineBounds(int &x1, int &x2, int w, int cx) { + debug(9, "TextDisplayer::calcWidestLineBounds(%d, %d)", w, cx); + x1 = cx - w / 2; + if (x1 + w >= Screen::SCREEN_W - 12) { + x1 = Screen::SCREEN_W - 12 - w - 1; + } else if (x1 < 12) { + x1 = 12; + } + x2 = x1 + w + 1; +} + +void TextDisplayer::restoreTalkTextMessageBkgd(int srcPage, int dstPage) { + debug(9, "TextDisplayer::restoreTalkTextMessageBkgd(%d, %d)", srcPage, dstPage); + if (_talkMessagePrinted) { + _talkMessagePrinted = false; + _screen->copyRegion(_talkCoords.x, _talkCoords.y, _talkCoords.x, _talkMessageY, _talkCoords.w, _talkMessageH, srcPage, dstPage); + } +} + +void TextDisplayer::printTalkTextMessage(const char *text, int x, int y, uint8 color, int srcPage, int dstPage) { + debug(9, "TextDisplayer::printTalkTextMessage('%s', %d, %d, %d, %d, %d)", text, x, y, color, srcPage, dstPage); + char *str = preprocessString(text); + int lineCount = buildMessageSubstrings(str); + int top = y - lineCount * 10; + if (top < 0) { + top = 0; + } + _talkMessageY = top; + _talkMessageH = lineCount * 10; + int w = getWidestLineWidth(lineCount); + int x1, x2; + calcWidestLineBounds(x1, x2, w, x); + _talkCoords.x = x1; + _talkCoords.w = w + 2; + _screen->copyRegion(_talkCoords.x, _talkMessageY, _talkCoords.x, _talkCoords.y, _talkCoords.w, _talkMessageH, srcPage, dstPage); + int curPage = _screen->_curPage; + _screen->_curPage = srcPage; + for (int i = 0; i < lineCount; ++i) { + top = i * 10 + _talkMessageY; + char *msg = &_talkSubstrings[i * TALK_SUBSTRING_LEN]; + int left = getCenterStringX(msg, x1, x2); + printText(msg, left, top, color, 0xC, 0); + } + _screen->_curPage = curPage; + _talkMessagePrinted = true; +} + +void TextDisplayer::printText(const char *str, int x, int y, uint8 c0, uint8 c1, uint8 c2) { + debug(9, "TextDisplayer::printText('%s', %d, %d, %d, %d, %d)", str, x, y, c0, c1, c2); + uint8 colorMap[] = { 0, 15, 12, 12 }; + colorMap[3] = c1; + _screen->setTextColor(colorMap, 0, 3); + Screen::FontId curFont = _screen->setFont(Screen::FID_8_FNT); + _screen->_charWidth = -2; + _screen->printText(str, x, y, c0, c2); + _screen->_charWidth = 0; + _screen->setFont(curFont); +} + +void TextDisplayer::printCharacterText(char *text, int8 charNum, int charX) { + debug(9, "TextDisplayer::printCharacterText('%s', %d, %d)", text, charNum, charX); + uint8 colorTable[] = {0x0F, 0x9, 0x0C9, 0x80, 0x5, 0x81, 0x0E, 0xD8, 0x55, 0x3A, 0x3a}; + int top, left, x1, x2, w, x; + char *msg; + + uint8 color = colorTable[charNum]; + text = preprocessString(text); + int lineCount = buildMessageSubstrings(text); + w = getWidestLineWidth(lineCount); + x = charX; + calcWidestLineBounds(x1, x2, w, x); + + for (int i = 0; i < lineCount; ++i) { + top = i * 10 + _talkMessageY; + msg = &_talkSubstrings[i * TALK_SUBSTRING_LEN]; + left = getCenterStringX(msg, x1, x2); + printText(msg, left, top, color, 0xC, 0); + } +} +} // end of namespace Kyra diff --git a/engines/kyra/text.h b/engines/kyra/text.h new file mode 100644 index 0000000000..f8f7c975a6 --- /dev/null +++ b/engines/kyra/text.h @@ -0,0 +1,69 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2005-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 KYRATEXT_H +#define KYRATEXT_H + +namespace Kyra { +class Screen; + +class TextDisplayer { + struct TalkCoords { + uint16 y, x, w; + }; + + enum { + TALK_SUBSTRING_LEN = 80, + TALK_SUBSTRING_NUM = 3 + }; +public: + TextDisplayer(Screen *screen); + ~TextDisplayer() {} + + void setTalkCoords(uint16 y); + int getCenterStringX(const char *str, int x1, int x2); + int getCharLength(const char *str, int len); + int dropCRIntoString(char *str, int offs); + char *preprocessString(const char *str); + int buildMessageSubstrings(const char *str); + int getWidestLineWidth(int linesCount); + void calcWidestLineBounds(int &x1, int &x2, int w, int cx); + void restoreTalkTextMessageBkgd(int srcPage, int dstPage); + void printTalkTextMessage(const char *text, int x, int y, uint8 color, int srcPage, int dstPage); + void printText(const char *str, int x, int y, uint8 c0, uint8 c1, uint8 c2); + void printCharacterText(char *text, int8 charNum, int charX); + + uint16 _talkMessageY; + uint16 _talkMessageH; + bool printed() const { return _talkMessagePrinted; } +private: + Screen *_screen; + + char _talkBuffer[300]; + char _talkSubstrings[TALK_SUBSTRING_LEN * TALK_SUBSTRING_NUM]; + TalkCoords _talkCoords; + bool _talkMessagePrinted; +}; +} // end of namespace Kyra + +#endif + diff --git a/engines/kyra/timer.cpp b/engines/kyra/timer.cpp new file mode 100644 index 0000000000..7abfd9fcbc --- /dev/null +++ b/engines/kyra/timer.cpp @@ -0,0 +1,280 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2005-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 "kyra/kyra.h" +#include "kyra/screen.h" +#include "kyra/animator.h" + +#include "common/system.h" + +namespace Kyra { +void KyraEngine::setupTimers() { + debug(9, "setupTimers()"); + memset(_timers, 0, sizeof(_timers)); + + for (int i = 0; i < 34; i++) + _timers[i].active = 1; + + _timers[0].func = _timers[1].func = _timers[2].func = _timers[3].func = _timers[4].func = 0; //Unused. + _timers[5].func = _timers[6].func = _timers[7].func = _timers[8].func = _timers[9].func = 0; //_nullsub51; + _timers[10].func = _timers[11].func = _timers[12].func = _timers[13].func = 0; //_nullsub50; + _timers[14].func = &KyraEngine::timerCheckAnimFlag2; //_nullsub52; + _timers[15].func = &KyraEngine::timerUpdateHeadAnims; //_nullsub48; + _timers[16].func = &KyraEngine::timerSetFlags1; //_nullsub47; + _timers[17].func = 0; //sub_15120; + _timers[18].func = &KyraEngine::timerCheckAnimFlag1; //_nullsub53; + _timers[19].func = &KyraEngine::timerRedrawAmulet; //_nullsub54; + _timers[20].func = 0; //offset _timerDummy1 + _timers[21].func = 0; //sub_1517C; + _timers[22].func = 0; //offset _timerDummy2 + _timers[23].func = 0; //offset _timerDummy3, + _timers[24].func = 0; //_nullsub45; + _timers[25].func = 0; //offset _timerDummy4 + _timers[26].func = 0; //_nullsub46; + _timers[27].func = 0; //offset _timerDummy5, + _timers[28].func = 0; //offset _timerDummy6 + _timers[29].func = 0; //offset _timerDummy7, + _timers[30].func = 0; //offset _timerDummy8, + _timers[31].func = &KyraEngine::timerFadeText; //sub_151F8; + _timers[32].func = &KyraEngine::updateAnimFlag1; //_nullsub61; + _timers[33].func = &KyraEngine::updateAnimFlag2; //_nullsub62; + + _timers[0].countdown = _timers[1].countdown = _timers[2].countdown = _timers[3].countdown = _timers[4].countdown = -1; + _timers[5].countdown = 5; + _timers[6].countdown = 7; + _timers[7].countdown = 8; + _timers[8].countdown = 9; + _timers[9].countdown = 7; + _timers[10].countdown = _timers[11].countdown = _timers[12].countdown = _timers[13].countdown = 420; + _timers[14].countdown = 600; + _timers[15].countdown = 11; + _timers[16].countdown = _timers[17].countdown = 7200; + _timers[18].countdown = _timers[19].countdown = 600; + _timers[20].countdown = 7200; + _timers[21].countdown = 18000; + _timers[22].countdown = 7200; + _timers[23].countdown = _timers[24].countdown = _timers[25].countdown = _timers[26].countdown = _timers[27].countdown = 10800; + _timers[28].countdown = 21600; + _timers[29].countdown = 7200; + _timers[30].countdown = 10800; + _timers[31].countdown = -1; + _timers[32].countdown = 9; + _timers[33].countdown = 3; +} + +void KyraEngine::updateGameTimers() { + debug(9, "updateGameTimers()"); + + if (_system->getMillis() < _timerNextRun) + return; + + _timerNextRun += 99999; + + for (int i = 0; i < 34; i++) { + if (_timers[i].active && _timers[i].countdown > -1) { + if (_timers[i].nextRun <=_system->getMillis()) { + if (i > 4 && _timers[i].func) + (*this.*_timers[i].func)(i); + + _timers[i].nextRun = _system->getMillis() + _timers[i].countdown * _tickLength; + } + } + if (_timers[i].nextRun < _timerNextRun) + _timerNextRun = _timers[i].nextRun; + } +} + +void KyraEngine::clearNextEventTickCount() { + debug(9, "clearNextEventTickCount()"); + _timerNextRun = 0; +} + +void KyraEngine::setTimerDelay(uint8 timer, int32 countdown) { + debug(9, "setTimerDelay(%i, %d)", timer, countdown); + _timers[timer].countdown = countdown; +} + +int16 KyraEngine::getTimerDelay(uint8 timer) { + debug(9, "getTimerDelay(%i)", timer); + return _timers[timer].countdown; +} + +void KyraEngine::setTimerCountdown(uint8 timer, int32 countdown) { + debug(9, "setTimerCountdown(%i, %i)", timer, countdown); + _timers[timer].countdown = countdown; + _timers[timer].nextRun = _system->getMillis() + countdown * _tickLength; + + uint32 nextRun = _system->getMillis() + countdown * _tickLength; + if (nextRun < _timerNextRun) + _timerNextRun = nextRun; +} + +void KyraEngine::enableTimer(uint8 timer) { + debug(9, "enableTimer(%i)", timer); + _timers[timer].active = 1; +} + +void KyraEngine::disableTimer(uint8 timer) { + debug(9, "disableTimer(%i)", timer); + _timers[timer].active = 0; +} + +void KyraEngine::timerUpdateHeadAnims(int timerNum) { + debug(9, "timerUpdateHeadAnims(%i)", timerNum); + static int8 currentFrame = 0; + static const int8 frameTable[] = {4, 5, 4, 5, 4, 5, 0, 1, 4, 5, + 4, 4, 6, 4, 8, 1, 9, 4, -1}; + + if (_talkingCharNum < 0) + return; + + _currHeadShape = frameTable[currentFrame]; + currentFrame++; + + if (frameTable[currentFrame] == -1) + currentFrame = 0; + + _animator->animRefreshNPC(0); + _animator->animRefreshNPC(_talkingCharNum); +} + +void KyraEngine::timerSetFlags1(int timerNum) { + debug(9, "timerSetFlags(%i)", timerNum); + if (_currentCharacter->sceneId == 0x1C) + return; + + int rndNr = _rnd.getRandomNumberRng(0, 3); + + for (int i = 0; i < 4; i++) { + if (!queryGameFlag(rndNr + 17)) { + setGameFlag(rndNr + 17); + break; + } else { + rndNr++; + if (rndNr > 3) + rndNr = 0; + } + } +} + +void KyraEngine::timerFadeText(int timerNum) { + debug(9, "timerFadeText(%i)", timerNum); + _fadeText = true; +} + +void KyraEngine::updateAnimFlag1(int timerNum) { + debug(9, "updateAnimFlag1(%d)", timerNum); + if (_brandonStatusBit & 2) { + _brandonStatusBit0x02Flag = 1; + } +} + +void KyraEngine::updateAnimFlag2(int timerNum) { + debug(9, "updateAnimFlag2(%d)", timerNum); + if (_brandonStatusBit & 0x20) { + _brandonStatusBit0x20Flag = 1; + } +} + +void KyraEngine::setTextFadeTimerCountdown(int16 countdown) { + debug(9, "setTextFadeTimerCountdown(%i)", countdown); + //if (countdown == -1) + //countdown = 32000; + + setTimerCountdown(31, countdown*60); +} + +void KyraEngine::timerSetFlags2(int timerNum) { + debug(9, "timerSetFlags2(%i)", timerNum); + if (!((uint32*)(_flagsTable+0x2D))[timerNum]) + ((uint32*)(_flagsTable+0x2D))[timerNum] = 1; +} + +void KyraEngine::timerCheckAnimFlag1(int timerNum) { + debug(9, "timerCheckAnimFlag1(%i)", timerNum); + if (_brandonStatusBit & 0x20) { + checkAmuletAnimFlags(); + setTimerCountdown(18, -1); + } +} + +void KyraEngine::timerCheckAnimFlag2(int timerNum) { + debug(9, "timerCheckAnimFlag1(%i)", timerNum); + if (_brandonStatusBit & 0x2) { + checkAmuletAnimFlags(); + setTimerCountdown(14, -1); + } +} + +void KyraEngine::checkAmuletAnimFlags() { + debug(9, "checkSpecialAnimFlags()"); + if (_brandonStatusBit & 2) { + seq_makeBrandonNormal2(); + setTimerCountdown(19, 300); + } + + if (_brandonStatusBit & 0x20) { + seq_makeBrandonNormal(); + setTimerCountdown(19, 300); + } +} + +void KyraEngine::timerRedrawAmulet(int timerNum) { + debug(9, "timerRedrawAmulet(%i)", timerNum); + if (queryGameFlag(0xF1)) { + drawAmulet(); + setTimerCountdown(19, -1); + } +} + +void KyraEngine::drawAmulet() { + debug(9, "drawAmulet()"); + static const int16 amuletTable1[] = {0x167, 0x162, 0x15D, 0x158, 0x153, 0x150, 0x155, 0x15A, 0x15F, 0x164, 0x145, -1}; + static const int16 amuletTable3[] = {0x167, 0x162, 0x15D, 0x158, 0x153, 0x14F, 0x154, 0x159, 0x15E, 0x163, 0x144, -1}; + static const int16 amuletTable2[] = {0x167, 0x162, 0x15D, 0x158, 0x153, 0x152, 0x157, 0x15C, 0x161, 0x166, 0x147, -1}; + static const int16 amuletTable4[] = {0x167, 0x162, 0x15D, 0x158, 0x153, 0x151, 0x156, 0x15B, 0x160, 0x165, 0x146, -1}; + + resetGameFlag(0xF1); + _screen->hideMouse(); + + int i = 0; + while (amuletTable1[i] != -1) { + if (queryGameFlag(87)) + _screen->drawShape(0, _shapes[4+amuletTable1[i]], _amuletX[0], _amuletY[0], 0, 0); + + if (queryGameFlag(89)) + _screen->drawShape(0, _shapes[4+amuletTable2[i]], _amuletX[1], _amuletY[1], 0, 0); + + if (queryGameFlag(86)) + _screen->drawShape(0, _shapes[4+amuletTable3[i]], _amuletX[2], _amuletY[2], 0, 0); + + if (queryGameFlag(88)) + _screen->drawShape(0, _shapes[4+amuletTable4[i]], _amuletX[3], _amuletY[3], 0, 0); + + _screen->updateScreen(); + delayWithTicks(3); + i++; + } + _screen->showMouse(); +} +} // end of namespace Kyra + diff --git a/engines/kyra/wsamovie.cpp b/engines/kyra/wsamovie.cpp new file mode 100644 index 0000000000..17fe3f8cb2 --- /dev/null +++ b/engines/kyra/wsamovie.cpp @@ -0,0 +1,206 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-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 "kyra/kyra.h" +#include "kyra/screen.h" +#include "kyra/wsamovie.h" + + +namespace Kyra { +WSAMovieV1::WSAMovieV1(KyraEngine *vm) : Movie(vm) {} +WSAMovieV1::~WSAMovieV1() { close(); } + +void WSAMovieV1::open(const char *filename, int offscreenDecode, uint8 *palBuf) { + debug(9, "WSAMovieV1::open('%s', %d, 0x%X)", filename, offscreenDecode, palBuf); + close(); + + uint32 flags = 0; + uint32 fileSize; + uint8 *p = _vm->resource()->fileData(filename, &fileSize); + if (!p) + return; + + const uint8 *wsaData = p; + _numFrames = READ_LE_UINT16(wsaData); wsaData += 2; + _width = READ_LE_UINT16(wsaData); wsaData += 2; + _height = READ_LE_UINT16(wsaData); wsaData += 2; + _deltaBufferSize = READ_LE_UINT16(wsaData); wsaData += 2; + _offscreenBuffer = NULL; + _flags = 0; + if (_vm->features() & GF_TALKIE) { + flags = READ_LE_UINT16(wsaData); wsaData += 2; + } + + uint32 offsPal = 0; + if (flags & 1) { + offsPal = 0x300; + _flags |= WF_HAS_PALETTE; + if (palBuf) { + memcpy(palBuf, wsaData + (_numFrames + 2) * 4, 0x300); + } + } + + if (offscreenDecode) { + _flags |= WF_OFFSCREEN_DECODE; + const int offscreenBufferSize = _width * _height; + _offscreenBuffer = new uint8[offscreenBufferSize]; + memset(_offscreenBuffer, 0, offscreenBufferSize); + } + + if (_numFrames & 0x8000) { + warning("Unhandled wsa flags 0x80"); + _flags |= 0x80; + _numFrames &= 0x7FFF; + } + _currentFrame = _numFrames; + + _deltaBuffer = new uint8[_deltaBufferSize]; + memset(_deltaBuffer, 0, _deltaBufferSize); + + // read frame offsets + _frameOffsTable = new uint32[_numFrames + 2]; + _frameOffsTable[0] = 0; + uint32 frameDataOffs = READ_LE_UINT32(wsaData); wsaData += 4; + bool firstFrame = true; + if (frameDataOffs == 0) { + firstFrame = false; + frameDataOffs = READ_LE_UINT32(wsaData); + _flags |= WF_NO_FIRST_FRAME; + } + for (int i = 1; i < _numFrames + 2; ++i) { + _frameOffsTable[i] = READ_LE_UINT32(wsaData) - frameDataOffs; + wsaData += 4; + } + + // skip palette + wsaData += offsPal; + + // read frame data + const int frameDataSize = p + fileSize - wsaData; + _frameData = new uint8[frameDataSize]; + memcpy(_frameData, wsaData, frameDataSize); + + // decode first frame + if (firstFrame) { + Screen::decodeFrame4(_frameData, _deltaBuffer, _deltaBufferSize); + } + + delete [] p; + _opened = true; +} + +void WSAMovieV1::close() { + debug(9, "WSAMovieV1::close()"); + if (_opened) { + delete [] _deltaBuffer; + delete [] _offscreenBuffer; + delete [] _frameOffsTable; + delete [] _frameData; + _opened = false; + } +} + +void WSAMovieV1::displayFrame(int frameNum) { + debug(9, "WSAMovieV1::displayFrame(%d)", frameNum); + if (frameNum >= _numFrames || !_opened) + return; + + uint8 *dst; + if (_flags & WF_OFFSCREEN_DECODE) { + dst = _offscreenBuffer; + } else { + dst = _vm->screen()->getPagePtr(_drawPage) + _y * Screen::SCREEN_W + _x; + } + + if (_currentFrame == _numFrames) { + if (!(_flags & WF_NO_FIRST_FRAME)) { + if (_flags & WF_OFFSCREEN_DECODE) { + Screen::decodeFrameDelta(dst, _deltaBuffer); + } else { + Screen::decodeFrameDeltaPage(dst, _deltaBuffer, _width, 1); + } + } + _currentFrame = 0; + } + + // try to reduce the number of needed frame operations + int diffCount = ABS(_currentFrame - frameNum); + int frameStep = 1; + int frameCount; + if (_currentFrame < frameNum) { + frameCount = _numFrames - frameNum + _currentFrame; + if (diffCount > frameCount) { + frameStep = -1; + } else { + frameCount = diffCount; + } + } else { + frameCount = _numFrames - _currentFrame + frameNum; + if (frameCount >= diffCount) { + frameStep = -1; + frameCount = diffCount; + } + } + + // process + if (frameStep > 0) { + uint16 cf = _currentFrame; + while (frameCount--) { + cf += frameStep; + processFrame(cf, dst); + if (cf == _numFrames) { + cf = 0; + } + } + } else { + uint16 cf = _currentFrame; + while (frameCount--) { + if (cf == 0) { + cf = _numFrames; + } + processFrame(cf, dst); + cf += frameStep; + } + } + + // display + _currentFrame = frameNum; + if (_flags & WF_OFFSCREEN_DECODE) { + _vm->screen()->copyBlockToPage(_drawPage, _x, _y, _width, _height, _offscreenBuffer); + } +} + +void WSAMovieV1::processFrame(int frameNum, uint8 *dst) { + debug(9, "WSAMovieV1::processFrame(%d, 0x%X)", frameNum, dst); + if (!_opened) + return; + assert(frameNum <= _numFrames); + const uint8 *src = _frameData + _frameOffsTable[frameNum]; + Screen::decodeFrame4(src, _deltaBuffer, _deltaBufferSize); + if (_flags & WF_OFFSCREEN_DECODE) { + Screen::decodeFrameDelta(dst, _deltaBuffer); + } else { + Screen::decodeFrameDeltaPage(dst, _deltaBuffer, _width, 0); + } +} +} // end of namespace Kyra diff --git a/engines/kyra/wsamovie.h b/engines/kyra/wsamovie.h new file mode 100644 index 0000000000..fe3927f175 --- /dev/null +++ b/engines/kyra/wsamovie.h @@ -0,0 +1,85 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-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 WSAMOVIES_H +#define WSAMOVIES_H + +#include "kyra/resource.h" + +namespace Kyra { +class KyraEngine; + +class Movie { +public: + Movie(KyraEngine *vm) : _x(-1), _y(-1), _drawPage(-1), _vm(vm), _opened(false) {} + virtual ~Movie() {} + + virtual bool opened() { return _opened; } + + virtual void open(const char *filename, int offscreen, uint8 *palette) = 0; + virtual void close() = 0; + + virtual int frames() = 0; + + virtual void displayFrame(int frameNum) = 0; + + int _x, _y; + int _drawPage; +protected: + KyraEngine *_vm; + bool _opened; +}; + +class WSAMovieV1 : public Movie { +public: + WSAMovieV1(KyraEngine *vm); + virtual ~WSAMovieV1(); + + virtual void open(const char *filename, int offscreen, uint8 *palette); + virtual void close(); + + virtual int frames() { return _opened ? _numFrames : -1; } + + virtual void displayFrame(int frameNum); +protected: + virtual void processFrame(int frameNum, uint8 *dst); + + enum WSAFlags { + WF_OFFSCREEN_DECODE = 0x10, + WF_NO_FIRST_FRAME = 0x40, + WF_HAS_PALETTE = 0x100 + }; + + uint16 _currentFrame; + uint16 _numFrames; + uint16 _width; + uint16 _height; + uint16 _flags; + uint8 *_deltaBuffer; + uint32 _deltaBufferSize; + uint8 *_offscreenBuffer; + uint32 *_frameOffsTable; + uint8 *_frameData; +}; +} // end of namespace Kyra + +#endif |