diff options
Diffstat (limited to 'engines/kyra/graphics')
24 files changed, 12679 insertions, 0 deletions
diff --git a/engines/kyra/graphics/animator_hof.cpp b/engines/kyra/graphics/animator_hof.cpp new file mode 100644 index 0000000000..0b8db62a9b --- /dev/null +++ b/engines/kyra/graphics/animator_hof.cpp @@ -0,0 +1,313 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/engine/kyra_hof.h" +#include "kyra/graphics/wsamovie.h" + +#include "common/system.h" + +namespace Kyra { + +void KyraEngine_HoF::restorePage3() { + screen()->copyBlockToPage(2, 0, 0, 320, 144, _gamePlayBuffer); +} + +void KyraEngine_HoF::clearAnimObjects() { + _animObjects[0].index = 0; + _animObjects[0].type = 0; + _animObjects[0].enabled = 1; + _animObjects[0].flags = 0x800; + _animObjects[0].width = 32; + _animObjects[0].height = 49; + _animObjects[0].width2 = 4; + _animObjects[0].height2 = 10; + + for (int i = 1; i < 11; ++i) { + _animObjects[i].index = i; + _animObjects[i].type = 2; + } + + for (int i = 11; i <= 40; ++i) { + _animObjects[i].index = i; + _animObjects[i].type = 1; + _animObjects[i].flags = 0x800; + _animObjects[i].width = 16; + _animObjects[i].height = 16; + } +} + +void KyraEngine_HoF::drawAnimObjects() { + for (AnimObj *curObject = _animList; curObject; curObject = curObject->nextObject) { + if (!curObject->enabled) + continue; + + int x = curObject->xPos2 - (_screen->getScreenDim(2)->sx << 3); + int y = curObject->yPos2 - _screen->getScreenDim(2)->sy; + int layer = 7; + + if (curObject->flags & 0x800) { + if (curObject->animFlags) + layer = 0; + else + layer = getDrawLayer(curObject->xPos1, curObject->yPos1); + } + curObject->flags |= 0x800; + + if (curObject->index) + drawSceneAnimObject(curObject, x, y, layer); + else + drawCharacterAnimObject(curObject, x, y, layer); + } +} + +void KyraEngine_HoF::refreshAnimObjects(int force) { + for (AnimObj *curObject = _animList; curObject; curObject = curObject->nextObject) { + if (!curObject->enabled) + continue; + if (!curObject->needRefresh && !force) + continue; + + int x = curObject->xPos2 - curObject->width2; + if (x < 0) + x = 0; + if (x >= 320) + x = 319; + int y = curObject->yPos2 - curObject->height2; + if (y < 0) + y = 0; + if (y >= 143) + y = 142; + + int width = curObject->width + curObject->width2 + 8; + int height = curObject->height + curObject->height2*2; + if (width + x > 320) + width -= width + x - 322; + if (height + y > 143) + height -= height + y - 144; + + _screen->copyRegion(x, y, x, y, width, height, 2, 0, Screen::CR_NO_P_CHECK); + + curObject->needRefresh = false; + } +} + +void KyraEngine_HoF::updateItemAnimations() { + bool nextFrame = false; + + if (_itemAnimDefinition[0].itemIndex == -1 || _inventorySaved) + return; + + const ItemAnimDefinition *s = &_itemAnimDefinition[_nextAnimItem]; + ActiveItemAnim *a = &_activeItemAnim[_nextAnimItem]; + _nextAnimItem = (_nextAnimItem + 1) % _itemAnimDefinitionSize; + + if (_system->getMillis() < a->nextFrameTime) + return; + + uint16 shpIdx = s->frames[a->currentFrame].index + 64; + if (s->itemIndex == _mouseState && s->itemIndex == _itemInHand && _screen->isMouseVisible()) { + nextFrame = true; + _screen->setMouseCursor(8, 15, getShapePtr(shpIdx)); + } + + for (int i = 0; i < 10; i++) { + if (s->itemIndex == _mainCharacter.inventory[i]) { + nextFrame = true; + _screen->drawShape(2, getShapePtr(240 + i), 304, 184, 0, 0); + _screen->drawShape(2, getShapePtr(shpIdx), 304, 184, 0, 0); + _screen->copyRegion(304, 184, _inventoryX[i], _inventoryY[i], 16, 16, 2, 0); + } + } + + _screen->updateScreen(); + + for (int i = 11; i < 40; i++) { + AnimObj *animObject = &_animObjects[i]; + if (animObject->shapeIndex2 == s->itemIndex + 64) { + if (s->itemIndex == 121) { + int f = findItem(_mainCharacter.sceneId, 121); + int nx = _itemList[f].x - 4; + if (nx > 12) { + if (lineIsPassable(nx, _itemList[f].y)) { + animObject->xPos2 -= 4; + _itemList[f].x -= 4; + } + } + } + animObject->shapePtr = getShapePtr(shpIdx); + animObject->shapeIndex1 = shpIdx; + animObject->needRefresh = 1; + nextFrame = true; + } + } + + if (nextFrame) { + a->nextFrameTime = _system->getMillis() + (s->frames[a->currentFrame].delay * _tickLength); + a->currentFrame = (a->currentFrame + 1) % s->numFrames; + } +} + +void KyraEngine_HoF::updateCharFacing() { + if (_mainCharacter.x1 > _mouseX) + _mainCharacter.facing = 5; + else + _mainCharacter.facing = 3; + + _mainCharacter.animFrame = _characterFrameTable[_mainCharacter.facing]; + updateCharacterAnim(0); + refreshAnimObjectsIfNeed(); +} + +void KyraEngine_HoF::updateCharacterAnim(int) { + Character *c = &_mainCharacter; + AnimObj *animState = _animObjects; + + animState->needRefresh = 1; + animState->specialRefresh = 1; + + if (c->facing >= 1 && c->facing <= 3) + animState->flags |= 1; + else if (c->facing >= 5 && c->facing <= 7) + animState->flags &= ~1; + + animState->xPos2 = animState->xPos1 = c->x1; + animState->yPos2 = animState->yPos1 = c->y1; + animState->shapePtr = getShapePtr(c->animFrame); + animState->shapeIndex1 = animState->shapeIndex2 = c->animFrame; + + int xAdd = _shapeDescTable[c->animFrame-9].xAdd; + int yAdd = _shapeDescTable[c->animFrame-9].yAdd; + + _charScale = getScale(c->x1, c->y1); + + animState->xPos2 += (xAdd * _charScale) >> 8; + animState->yPos2 += (yAdd * _charScale) >> 8; + animState->width2 = 8; + animState->height2 = 10; + + _animList = deleteAnimListEntry(_animList, animState); + if (_animList) + _animList = addToAnimListSorted(_animList, animState); + else + _animList = initAnimList(_animList, animState); + + updateCharPal(1); +} + +void KyraEngine_HoF::updateSceneAnim(int anim, int newFrame) { + AnimObj *animObject = &_animObjects[1+anim]; + if (!animObject->enabled) + return; + + animObject->needRefresh = 1; + animObject->specialRefresh = 1; + animObject->flags = 0; + + if (_sceneAnims[anim].flags & 2) + animObject->flags |= 0x800; + else + animObject->flags &= ~0x800; + + if (_sceneAnims[anim].flags & 4) + animObject->flags |= 1; + else + animObject->flags &= ~1; + + if (_sceneAnims[anim].flags & 0x20) { + animObject->shapePtr = _sceneShapeTable[newFrame]; + animObject->shapeIndex2 = 0xFFFF; + animObject->shapeIndex3 = 0xFFFF; + animObject->animNum = 0xFFFF; + } else { + animObject->shapePtr = 0; + animObject->shapeIndex3 = newFrame; + animObject->animNum = anim; + } + + animObject->xPos1 = _sceneAnims[anim].x; + animObject->yPos1 = _sceneAnims[anim].y; + animObject->xPos2 = _sceneAnims[anim].x2; + animObject->yPos2 = _sceneAnims[anim].y2; + + if (_sceneAnims[anim].flags & 2) { + _animList = deleteAnimListEntry(_animList, animObject); + if (!_animList) + _animList = initAnimList(_animList, animObject); + else + _animList = addToAnimListSorted(_animList, animObject); + } +} + +void KyraEngine_HoF::drawSceneAnimObject(AnimObj *obj, int x, int y, int layer) { + if (obj->type == 1) { + if (obj->shapeIndex1 == 0xFFFF) + return; + int scale = getScale(obj->xPos1, obj->yPos1); + _screen->drawShape(2, getShapePtr(obj->shapeIndex1), x, y, 2, obj->flags | 4, layer, scale, scale); + return; + } + + if (obj->shapePtr) { + _screen->drawShape(2, obj->shapePtr, x, y, 2, obj->flags, layer); + } else { + if (obj->shapeIndex3 == 0xFFFF || obj->animNum == 0xFFFF) + return; + + int flags = 0x4000; + if (obj->flags & 0x800) + flags |= 0x8000; + + if (_sceneAnims[obj->animNum].wsaFlag) { + x = y = 0; + } else { + x = obj->xPos2; + y = obj->yPos2; + } + + _sceneAnimMovie[obj->animNum]->displayFrame(obj->shapeIndex3, 2, x, y, int(flags | layer), 0, 0); + } +} + +void KyraEngine_HoF::drawCharacterAnimObject(AnimObj *obj, int x, int y, int layer) { + if (_drawNoShapeFlag || obj->shapeIndex1 == 0xFFFF) + return; + _screen->drawShape(2, getShapePtr(obj->shapeIndex1), x, y, 2, obj->flags | 4, layer, _charScale, _charScale); +} + +void KyraEngine_HoF::setCharacterAnimDim(int w, int h) { + restorePage3(); + + _animObj0Width = _animObjects[0].width; + _animObj0Height = _animObjects[0].height; + + _animObjects[0].width = w; + _animObjects[0].height = h; +} + +void KyraEngine_HoF::resetCharacterAnimDim() { + restorePage3(); + + _animObjects[0].width = _animObj0Width; + _animObjects[0].height = _animObj0Height; +} + +} // End of namespace Kyra diff --git a/engines/kyra/graphics/animator_lok.cpp b/engines/kyra/graphics/animator_lok.cpp new file mode 100644 index 0000000000..9f4236b32b --- /dev/null +++ b/engines/kyra/graphics/animator_lok.cpp @@ -0,0 +1,651 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/engine/kyra_lok.h" +#include "kyra/graphics/screen.h" +#include "kyra/graphics/animator_lok.h" +#include "kyra/engine/sprites.h" + +namespace Kyra { + +Animator_LoK::Animator_LoK(KyraEngine_LoK *vm, OSystem *system) { + _vm = vm; + _screen = vm->screen(); + _initOk = false; + _system = system; + _screenObjects = _actors = _items = _sprites = _objectQueue = 0; + _noDrawShapesFlag = 0; + + _actorBkgBackUp[0] = new uint8[_screen->getRectSize(8, 69)]; + memset(_actorBkgBackUp[0], 0, _screen->getRectSize(8, 69)); + _actorBkgBackUp[1] = new uint8[_screen->getRectSize(8, 69)]; + memset(_actorBkgBackUp[1], 0, _screen->getRectSize(8, 69)); +} + +Animator_LoK::~Animator_LoK() { + close(); + delete[] _actorBkgBackUp[0]; + delete[] _actorBkgBackUp[1]; +} + +void Animator_LoK::init(int actors_, int items_, int 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 Animator_LoK::close() { + if (_initOk) { + _initOk = false; + delete[] _screenObjects; + _screenObjects = _actors = _items = _sprites = _objectQueue = 0; + } +} + +void Animator_LoK::initAnimStateList() { + AnimObject *animStates = _screenObjects; + animStates[0].index = 0; + animStates[0].active = 1; + animStates[0].flags = 0x800; + animStates[0].background = _actorBkgBackUp[0]; + 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 = _actorBkgBackUp[1]; + 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[345 + 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 Animator_LoK::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 Animator_LoK::flagAllObjectsForBkgdChange() { + AnimObject *curObject = _objectQueue; + while (curObject) { + curObject->bkgdChangeFlag = 1; + curObject = curObject->nextAnimObject; + } +} + +void Animator_LoK::flagAllObjectsForRefresh() { + AnimObject *curObject = _objectQueue; + while (curObject) { + curObject->refreshFlag = 1; + curObject = curObject->nextAnimObject; + } +} + +void Animator_LoK::restoreAllObjectBackgrounds() { + 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 Animator_LoK::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 Animator_LoK::preserveOrRestoreBackground(AnimObject *obj, bool restore) { + int x = 0, y = 0, width = obj->width, height = obj->height; + + if (restore) { + x = obj->x2 >> 3; + y = obj->y2; + } else { + x = obj->x1 >> 3; + y = obj->y1; + } + + if (x < 0) + x = 0; + if (y < 0) + y = 0; + + int temp; + + temp = x + width; + if (temp >= 39) + x = 39 - width; + temp = y + height; + if (temp >= 136) + y = 136 - height; + + if (restore) + _screen->copyBlockToPage(_screen->_curPage, x << 3, y, width << 3, height, obj->background); + else + _screen->copyRegionToBuffer(_screen->_curPage, x << 3, y, width << 3, height, obj->background); +} + +void Animator_LoK::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 && (_vm->_currentCharacter->currentAnimFrame != 88 || curObject->index != 0)) { + 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[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[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[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 (flagUnk2 & 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 (flagUnk2 & 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 Animator_LoK::copyChangedObjectsForward(int refreshFlag) { + for (AnimObject *curObject = _objectQueue; curObject; curObject = curObject->nextAnimObject) { + if (curObject->active) { + if (curObject->refreshFlag || refreshFlag) { + int xpos = 0, ypos = 0, width = 0, height = 0; + xpos = (curObject->x1 >> 3) - (curObject->width2 >> 3) - 1; + ypos = curObject->y1 - curObject->height2; + width = curObject->width + (curObject->width2 >> 3) + 2; + height = curObject->height + curObject->height2 * 2; + + if (xpos < 1) + xpos = 1; + else if (xpos > 39) + continue; + + if (xpos + width > 39) + width = 39 - xpos; + + if (ypos < 8) + ypos = 8; + else if (ypos > 136) + continue; + + if (ypos + height > 136) + height = 136 - ypos; + + _screen->copyRegion(xpos << 3, ypos, xpos << 3, ypos, width << 3, height, 2, 0); + curObject->refreshFlag = 0; + } + } + } + + _screen->updateScreen(); +} + +void Animator_LoK::updateAllObjectShapes() { + restoreAllObjectBackgrounds(); + preserveAnyChangedBackgrounds(); + prepDrawAllObjects(); + copyChangedObjectsForward(0); +} + +void Animator_LoK::animRemoveGameItem(int 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 Animator_LoK::animAddGameItem(int index, uint16 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[216 + 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 Animator_LoK::animAddNPC(int 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[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; +} + +Animator_LoK::AnimObject *Animator_LoK::objectRemoveQueue(AnimObject *queue, AnimObject *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; +} + +Animator_LoK::AnimObject *Animator_LoK::objectAddHead(AnimObject *queue, AnimObject *head) { + head->nextAnimObject = queue; + return head; +} + +Animator_LoK::AnimObject *Animator_LoK::objectQueue(AnimObject *queue, AnimObject *add) { + if (!queue || add->drawY <= queue->drawY) { + 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 Animator_LoK::addObjectToQueue(AnimObject *object) { + if (!_objectQueue) + _objectQueue = objectAddHead(0, object); + else + _objectQueue = objectQueue(_objectQueue, object); +} + +void Animator_LoK::refreshObject(AnimObject *object) { + _objectQueue = objectRemoveQueue(_objectQueue, object); + if (_objectQueue) + _objectQueue = objectQueue(_objectQueue, object); + else + _objectQueue = objectAddHead(0, object); +} + +void Animator_LoK::makeBrandonFaceMouse() { + Common::Point mouse = _vm->getMousePos(); + if (mouse.x >= _vm->_currentCharacter->x1) + _vm->_currentCharacter->facing = 3; + else + _vm->_currentCharacter->facing = 5; + animRefreshNPC(0); + updateAllObjectShapes(); +} + +int16 Animator_LoK::fetchAnimWidth(const uint8 *shape, int16 mult) { + if (_vm->gameFlags().useAltShapeHeader) + shape += 2; + return (((int16)READ_LE_UINT16((shape + 3))) * mult) >> 8; +} + +int16 Animator_LoK::fetchAnimHeight(const uint8 *shape, int16 mult) { + if (_vm->gameFlags().useAltShapeHeader) + shape += 2; + return (int16)(((int8)*(shape + 2)) * mult) >> 8; +} + +void Animator_LoK::setBrandonAnimSeqSize(int width, int height) { + restoreAllObjectBackgrounds(); + _brandonAnimSeqSizeWidth = _actors[0].width; + _brandonAnimSeqSizeHeight = _actors[0].height; + _actors[0].width = width + 1; + _actors[0].height = height; + preserveAllBackgrounds(); +} + +void Animator_LoK::resetBrandonAnimSeqSize() { + restoreAllObjectBackgrounds(); + _actors[0].width = _brandonAnimSeqSizeWidth; + _actors[0].height = _brandonAnimSeqSizeHeight; + preserveAllBackgrounds(); +} + +void Animator_LoK::animRefreshNPC(int 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()[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()[_brandonDrawFrame]; + if (_vm->_brandonStatusBit0x02Flag) { + ++_brandonDrawFrame; + // TODO: check this + 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; + + int newScale = _vm->_scaleTable[ch->y1]; + _brandonScaleX = newScale; + _brandonScaleY = newScale; + + 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 Animator_LoK::setCharacterDefaultFrame(int character) { + static const 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 Animator_LoK::setCharactersHeight() { + static const 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/graphics/animator_lok.h b/engines/kyra/graphics/animator_lok.h new file mode 100644 index 0000000000..55c4d571fd --- /dev/null +++ b/engines/kyra/graphics/animator_lok.h @@ -0,0 +1,127 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef KYRA_ANIMATOR_LOK_H +#define KYRA_ANIMATOR_LOK_H + +namespace Kyra { +class KyraEngine_LoK; +class Screen; + +class Animator_LoK { +public: + 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; + }; + + Animator_LoK(KyraEngine_LoK *vm, OSystem *system); + virtual ~Animator_LoK(); + + 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; + uint16 _brandonDrawFrame; + int _brandonScaleX; + int _brandonScaleY; + +protected: + KyraEngine_LoK *_vm; + Screen *_screen; + OSystem *_system; + bool _initOk; + + AnimObject *_screenObjects; + + AnimObject *_actors; + AnimObject *_items; + AnimObject *_sprites; + + uint8 *_actorBkgBackUp[2]; + + 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/graphics/animator_mr.cpp b/engines/kyra/graphics/animator_mr.cpp new file mode 100644 index 0000000000..11b1bcb45a --- /dev/null +++ b/engines/kyra/graphics/animator_mr.cpp @@ -0,0 +1,456 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/engine/kyra_mr.h" +#include "kyra/resource/resource.h" +#include "kyra/graphics/wsamovie.h" + +#include "common/system.h" + +namespace Kyra { + +void KyraEngine_MR::restorePage3() { + screen()->copyBlockToPage(2, 0, 0, 320, 200, _gamePlayBuffer); +} + +void KyraEngine_MR::clearAnimObjects() { + for (int i = 0; i < 67; ++i) + _animObjects[i].enabled = false; + + _animObjects[0].index = 0; + _animObjects[0].type = 0; + _animObjects[0].enabled = true; + _animObjects[0].specialRefresh = 1; + _animObjects[0].flags = 0x800; + _animObjects[0].width = 57; + _animObjects[0].height = 91; + _animObjects[0].width2 = 4; + _animObjects[0].height2 = 10; + + for (int i = 1; i < 17; ++i) { + _animObjects[i].index = i; + _animObjects[i].type = 2; + _animObjects[i].flags = 0; + _animObjects[i].enabled = false; + _animObjects[i].needRefresh = 0; + _animObjects[i].specialRefresh = 1; + } + + for (int i = 17; i <= 66; ++i) { + _animObjects[i].index = i; + _animObjects[i].type = 1; + _animObjects[i].specialRefresh = 1; + _animObjects[i].flags = 0x800; + _animObjects[i].width = 24; + _animObjects[i].height = 20; + _animObjects[i].width2 = 0; + _animObjects[i].height2 = 0; + } +} + +void KyraEngine_MR::animSetupPaletteEntry(AnimObj *anim) { + int layer = _screen->getLayer(anim->xPos1, anim->yPos1) - 1; + int16 count = 0; + for (int i = 0; i < 3; ++i) + count += _sceneDatPalette[layer*3+i]; + count /= 3; + count *= -1; + count = MAX<int16>(0, MIN<int16>(count, 10)); + anim->palette = count / 3; +} + +void KyraEngine_MR::drawAnimObjects() { + for (AnimObj *curObject = _animList; curObject; curObject = curObject->nextObject) { + if (!curObject->enabled) + continue; + + int x = curObject->xPos2 - (_screen->getScreenDim(2)->sx << 3); + int y = curObject->yPos2 - _screen->getScreenDim(2)->sy; + int layer = 7; + + if (curObject->flags & 0x800) { + if (!curObject->specialRefresh) + layer = 0; + else + layer = getDrawLayer(curObject->xPos1, curObject->yPos1); + } + + if (curObject->index) + drawSceneAnimObject(curObject, x, y, layer); + else + drawCharacterAnimObject(curObject, x, y, layer); + } +} + +void KyraEngine_MR::drawSceneAnimObject(AnimObj *obj, int x, int y, int layer) { + if (obj->type == 1) { + if (obj->shapeIndex1 == 0xFFFF) + return; + int scale = getScale(obj->xPos1, obj->yPos1); + _screen->drawShape(2, getShapePtr(obj->shapeIndex1), x, y, 2, obj->flags | 0x104, _paletteOverlay, obj->palette, layer, scale, scale); + } else { + if (obj->shapePtr) { + _screen->drawShape(2, obj->shapePtr, x, y, 2, obj->flags, 7); + } else { + if (obj->shapeIndex3 == 0xFFFF || obj->animNum == 0xFFFF) + return; + uint16 flags = 0x4000; + if (obj->flags & 0x800) + flags |= 0x8000; + x = obj->xPos2 - _sceneAnimMovie[obj->animNum]->xAdd(); + y = obj->yPos2 - _sceneAnimMovie[obj->animNum]->yAdd(); + _sceneAnimMovie[obj->animNum]->displayFrame(obj->shapeIndex3, 2, x, y, flags | layer, 0, 0); + } + } +} + +void KyraEngine_MR::drawCharacterAnimObject(AnimObj *obj, int x, int y, int layer) { + if (_drawNoShapeFlag) + return; + + if (_mainCharacter.animFrame < 9) + _mainCharacter.animFrame = 87; + + if (obj->shapeIndex1 == 0xFFFF || _mainCharacter.animFrame == 87) + return; + + _screen->drawShape(2, getShapePtr(421), _mainCharacter.x3, _mainCharacter.y3, 2, obj->flags | 0x304, _paletteOverlay, 3, layer, _charScale, _charScale); + uint8 *shape = getShapePtr(_mainCharacter.animFrame); + if (shape) + _screen->drawShape(2, shape, x, y, 2, obj->flags | 4, layer, _charScale, _charScale); +} + +void KyraEngine_MR::refreshAnimObjects(int force) { + for (AnimObj *curObject = _animList; curObject; curObject = curObject->nextObject) { + if (!curObject->enabled) + continue; + if (!curObject->needRefresh && !force) + continue; + + const int scale = (curObject->index == 0) ? _charScale : 0; + + int x = curObject->xPos2 - curObject->width2; + if (scale) + x -= (0x100 - scale) >> 4; + + if (x < 0) + x = 0; + if (x >= 320) + x = 319; + + int y = curObject->yPos2 - curObject->height2; + if (scale) + y -= (0x100 - scale) >> 3; + if (y < 0) + y = 0; + if (y >= 187) + y = 186; + + int width = curObject->width + curObject->width2 + 8; + int height = curObject->height + curObject->height2*2; + if (width + x > 320) + width -= width + x - 322; + + const int maxY = _inventoryState ? 143 : 187; + if (height + y > maxY) + height -= height + y - (maxY + 1); + + if (height > 0) { + _screen->copyRegion(x, y, x, y, width, height, 2, 0, Screen::CR_NO_P_CHECK); + } + + curObject->needRefresh = false; + } +} + +void KyraEngine_MR::updateItemAnimations() { + bool nextFrame = false; + + if (_itemAnimDefinition[0].itemIndex == -1) + return; + + const ItemAnimDefinition *s = &_itemAnimDefinition[_nextAnimItem]; + ActiveItemAnim *a = &_activeItemAnim[_nextAnimItem]; + _nextAnimItem = (_nextAnimItem + 1) % 10; + + if (_system->getMillis() < a->nextFrameTime) + return; + + uint16 shpIdx = s->frames[a->currentFrame].index + 248; + if (s->itemIndex == _mouseState && s->itemIndex == _itemInHand && _screen->isMouseVisible()) { + nextFrame = true; + _screen->setMouseCursor(12, 19, getShapePtr(shpIdx)); + } + + if (_inventoryState) { + for (int i = 0; i < 10; i++) { + if (s->itemIndex == _mainCharacter.inventory[i]) { + nextFrame = true; + _screen->drawShape(2, getShapePtr(422 + i), 9, 0, 0, 0); + _screen->drawShape(2, getShapePtr(shpIdx), 9, 0, 0, 0); + _screen->copyRegion(9, 0, _inventoryX[i], _inventoryY[i], 24, 20, 2, 0, Screen::CR_NO_P_CHECK); + } + } + } + + _screen->updateScreen(); + + for (int i = 17; i < 66; i++) { + AnimObj *animObject = &_animObjects[i]; + if (animObject->shapeIndex2 == s->itemIndex + 248) { + animObject->shapePtr = getShapePtr(shpIdx); + animObject->shapeIndex1 = shpIdx; + animObject->needRefresh = true; + nextFrame = true; + } + } + + if (nextFrame) { + a->nextFrameTime = _system->getMillis() + (s->frames[a->currentFrame].delay * _tickLength); + a->currentFrame = (a->currentFrame + 1) % s->numFrames; + } +} + +void KyraEngine_MR::updateCharacterAnim(int charId) { + AnimObj *obj = &_animObjects[0]; + obj->needRefresh = true; + obj->flags &= ~1; + obj->xPos1 = _mainCharacter.x1; + obj->yPos1 = _mainCharacter.y1; + obj->shapePtr = getShapePtr(_mainCharacter.animFrame); + obj->shapeIndex1 = obj->shapeIndex2 = _mainCharacter.animFrame; + + int shapeOffsetX = 0, shapeOffsetY = 0; + if (_mainCharacter.animFrame >= 50 && _mainCharacter.animFrame <= 87) { + shapeOffsetX = _malcolmShapeXOffset; + shapeOffsetY = _malcolmShapeYOffset; + } else { + shapeOffsetX = _animShapeXAdd; + shapeOffsetY = _animShapeYAdd; + } + + obj->xPos2 = _mainCharacter.x1; + obj->yPos2 = _mainCharacter.y1; + _charScale = getScale(_mainCharacter.x1, _mainCharacter.y1); + obj->xPos2 += (shapeOffsetX * _charScale) >> 8; + obj->yPos2 += (shapeOffsetY * _charScale) >> 8; + _mainCharacter.x3 = _mainCharacter.x1 - (_charScale >> 4) - 1; + _mainCharacter.y3 = _mainCharacter.y1 - (_charScale >> 6) - 1; + if (_charBackUpWidth2 == -1) { + obj->width2 = 4; + obj->height2 = 10; + } + + for (int i = 1; i <= 16; ++i) { + if (_animObjects[i].enabled && _animObjects[i].specialRefresh) + _animObjects[i].needRefresh = true; + } + + _animList = deleteAnimListEntry(_animList, obj); + if (_animList) + _animList = addToAnimListSorted(_animList, obj); + else + _animList = initAnimList(_animList, obj); + + if (!_loadingState) + updateCharPal(1); +} + +void KyraEngine_MR::updateSceneAnim(int anim, int newFrame) { + AnimObj *animObject = &_animObjects[1+anim]; + if (!animObject->enabled) + return; + + animObject->needRefresh = true; + + if (_sceneAnims[anim].flags & 2) + animObject->flags |= 1; + else + animObject->flags &= ~1; + + if (_sceneAnims[anim].flags & 4) { + animObject->shapePtr = _sceneShapes[newFrame]; + animObject->shapeIndex2 = 0xFFFF; + animObject->shapeIndex3 = 0xFFFF; + animObject->animNum = 0xFFFF; + } else { + animObject->shapePtr = 0; + animObject->shapeIndex3 = newFrame; + animObject->animNum = anim; + } + + animObject->xPos1 = _sceneAnims[anim].x; + animObject->yPos1 = _sceneAnims[anim].y; + animObject->xPos2 = _sceneAnims[anim].x2; + animObject->yPos2 = _sceneAnims[anim].y2; + + if (_sceneAnims[anim].flags & 0x20) { + _animList = deleteAnimListEntry(_animList, animObject); + if (!_animList) + _animList = initAnimList(_animList, animObject); + else + _animList = addToAnimListSorted(_animList, animObject); + } +} + +void KyraEngine_MR::setupSceneAnimObject(int animId, uint16 flags, int x, int y, int x2, int y2, int w, + int h, int unk10, int specialSize, int unk14, int shape, const char *filename) { + restorePage3(); + SceneAnim &anim = _sceneAnims[animId]; + anim.flags = flags; + anim.x = x; + anim.y = y; + anim.x2 = x2; + anim.y2 = y2; + anim.width = w; + anim.height = h; + anim.specialSize = specialSize; + anim.shapeIndex = shape; + if (filename) + strcpy(anim.filename, filename); + + if (flags & 8) { + _sceneAnimMovie[animId]->open(filename, 1, 0); + if (_sceneAnimMovie[animId]->opened()) { + anim.wsaFlag = 1; + if (x2 == -1) + x2 = _sceneAnimMovie[animId]->xAdd(); + if (y2 == -1) + y2 = _sceneAnimMovie[animId]->yAdd(); + if (w == -1) + w = _sceneAnimMovie[animId]->width(); + if (h == -1) + h = _sceneAnimMovie[animId]->height(); + if (x == -1) + x = (w >> 1) + x2; + if (y == -1) + y = y2 + h - 1; + + anim.x = x; + anim.y = y; + anim.x2 = x2; + anim.y2 = y2; + anim.width = w; + anim.height = h; + } + } + + AnimObj *obj = &_animObjects[1+animId]; + obj->enabled = true; + obj->needRefresh = true; + + obj->specialRefresh = (anim.flags & 0x20) ? 1 : 0; + obj->flags = (anim.flags & 0x10) ? 0x800 : 0; + if (anim.flags & 2) + obj->flags |= 1; + + obj->xPos1 = anim.x; + obj->yPos1 = anim.y; + + if ((anim.flags & 4) && anim.shapeIndex != -1) + obj->shapePtr = _sceneShapes[anim.shapeIndex]; + else + obj->shapePtr = 0; + + if (anim.flags & 8) { + obj->shapeIndex3 = anim.shapeIndex; + obj->animNum = animId; + } else { + obj->shapeIndex3 = 0xFFFF; + obj->animNum = 0xFFFF; + } + + obj->xPos3 = obj->xPos2 = anim.x2; + obj->yPos3 = obj->yPos2 = anim.y2; + obj->width = anim.width; + obj->height = anim.height; + obj->width2 = obj->height2 = anim.specialSize; + + if (_animList) + _animList = addToAnimListSorted(_animList, obj); + else + _animList = initAnimList(_animList, obj); +} + +void KyraEngine_MR::removeSceneAnimObject(int anim, int refresh) { + AnimObj *obj = &_animObjects[anim+1]; + restorePage3(); + obj->shapeIndex3 = 0xFFFF; + obj->animNum = 0xFFFF; + obj->needRefresh = true; + + if (refresh) + refreshAnimObjectsIfNeed(); + + obj->enabled = false; + _animList = deleteAnimListEntry(_animList, obj); + _sceneAnimMovie[anim]->close(); +} + +void KyraEngine_MR::setCharacterAnimDim(int w, int h) { + restorePage3(); + _charBackUpWidth = _animObjects[0].width; + _charBackUpWidth2 = _animObjects[0].width2; + _charBackUpHeight = _animObjects[0].height; + _charBackUpHeight2 = _animObjects[0].height2; + + _animObjects[0].width2 = (w - _charBackUpWidth) / 2; + _animObjects[0].height2 = h - _charBackUpHeight; + _animObjects[0].width = w; + _animObjects[0].height = h; +} + +void KyraEngine_MR::resetCharacterAnimDim() { + restorePage3(); + _animObjects[0].width2 = _charBackUpWidth2; + _animObjects[0].height2 = _charBackUpHeight2; + _animObjects[0].width = _charBackUpWidth; + _animObjects[0].height = _charBackUpHeight; + _charBackUpWidth2 = _charBackUpHeight2 = -1; + _charBackUpWidth = _charBackUpHeight = -1; +} + +void KyraEngine_MR::showIdleAnim() { + if (_mainCharacter.sceneId == 20 || _mainCharacter.sceneId == 21 + || _mainCharacter.sceneId == 12 || _mainCharacter.sceneId == 11) + return; + + if (_mainCharacter.animFrame == 87) + return; + + if (!_nextIdleType && !talkObjectsInCurScene()) { + randomSceneChat(); + } else { + static const char *const facingTable[] = { + "A", "R", "R", "FR", "FX", "FL", "L", "L" + }; + + Common::String filename = Common::String::format( "MI0%s%.02d.EMC", facingTable[_mainCharacter.facing], _characterShapeFile); + + if (_res->exists(filename.c_str())) + runAnimationScript(filename.c_str(), 1, 1, 1, 1); + } + + _nextIdleType = !_nextIdleType; +} + +} // End of namespace Kyra diff --git a/engines/kyra/graphics/animator_tim.cpp b/engines/kyra/graphics/animator_tim.cpp new file mode 100644 index 0000000000..160524e6ca --- /dev/null +++ b/engines/kyra/graphics/animator_tim.cpp @@ -0,0 +1,235 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/script/script_tim.h" +#include "kyra/graphics/wsamovie.h" +#include "kyra/graphics/screen_lol.h" + +#ifdef ENABLE_LOL +#include "kyra/engine/lol.h" +#else +#include "kyra/graphics/screen_v2.h" +#endif + +#include "common/system.h" + +namespace Kyra { + +#ifdef ENABLE_LOL +TimAnimator::TimAnimator(LoLEngine *engine, Screen_v2 *screen_v2, OSystem *system, bool useParts) : _vm(engine), _screen(screen_v2), _system(system), _useParts(useParts) { +#else +TimAnimator::TimAnimator(KyraEngine_v1 *engine, Screen_v2 *screen_v2, OSystem *system, bool useParts) : _vm(engine), _screen(screen_v2), _system(system), _useParts(useParts) { +#endif + _animations = new Animation[TIM::kWSASlots]; + memset(_animations, 0, TIM::kWSASlots * sizeof(Animation)); + + if (_useParts) { + for (int i = 0; i < TIM::kWSASlots; i++) { + _animations[i].parts = new AnimPart[TIM::kAnimParts]; + memset(_animations[i].parts, 0, TIM::kAnimParts * sizeof(AnimPart)); + } + } +} + +TimAnimator::~TimAnimator() { + for (int i = 0; i < TIM::kWSASlots; i++) { + delete _animations[i].wsa; + if (_useParts) + delete[] _animations[i].parts; + } + + delete[] _animations; +} + +void TimAnimator::init(int animIndex, Movie *wsa, int x, int y, int wsaCopyParams, int frameDelay) { + Animation *anim = &_animations[animIndex]; + anim->wsa = wsa; + anim->x = x; + anim->y = y; + anim->wsaCopyParams = wsaCopyParams; + anim->frameDelay = frameDelay; + anim->enable = 0; + anim->lastPart = -1; +} + +void TimAnimator::reset(int animIndex, bool clearStruct) { + Animation *anim = &_animations[animIndex]; + if (!anim) + return; + anim->field_D = 0; + anim->enable = 0; + delete anim->wsa; + anim->wsa = 0; + + if (clearStruct) { + if (_useParts) + delete[] anim->parts; + + memset(anim, 0, sizeof(Animation)); + + if (_useParts) { + anim->parts = new AnimPart[TIM::kAnimParts]; + memset(anim->parts, 0, TIM::kAnimParts * sizeof(AnimPart)); + } + } +} + +void TimAnimator::displayFrame(int animIndex, int page, int frame, int flags) { + Animation *anim = &_animations[animIndex]; + if ((anim->wsaCopyParams & 0x4000) != 0) + page = 2; + // WORKAROUND for some bugged scripts that will try to display frames of non-existent animations + if (anim->wsa) + anim->wsa->displayFrame(frame, page, anim->x, anim->y, (flags == -1) ? (anim->wsaCopyParams & 0xF0FF) : flags, 0, 0); + if (!page) + _screen->updateScreen(); +} + +#ifdef ENABLE_LOL +void TimAnimator::setupPart(int animIndex, int part, int firstFrame, int lastFrame, int cycles, int nextPart, int partDelay, int f, int sfxIndex, int sfxFrame) { + AnimPart *a = &_animations[animIndex].parts[part]; + a->firstFrame = firstFrame; + a->lastFrame = lastFrame; + a->cycles = cycles; + a->nextPart = nextPart; + a->partDelay = partDelay; + a->field_A = f; + a->sfxIndex = sfxIndex; + a->sfxFrame = sfxFrame; +} + +void TimAnimator::start(int animIndex, int part) { + if (!_vm || !_system || !_screen) + return; + + Animation *anim = &_animations[animIndex]; + anim->curPart = part; + AnimPart *p = &anim->parts[part]; + anim->enable = 1; + anim->nextFrame = _system->getMillis() + anim->frameDelay * _vm->_tickLength; + anim->curFrame = p->firstFrame; + anim->cyclesCompleted = 0; + + // WORKAROUND for some bugged scripts that will try to display frames of non-existent animations + if (anim->wsa) + anim->wsa->displayFrame(anim->curFrame - 1, 0, anim->x, anim->y, 0, 0, 0); +} + +void TimAnimator::stop(int animIndex) { + Animation *anim = &_animations[animIndex]; + anim->enable = 0; + anim->field_D = 0; + if (animIndex == 5) { + delete anim->wsa; + anim->wsa = 0; + } +} + +void TimAnimator::update(int animIndex) { + if (!_vm || !_system || !_screen) + return; + + Animation *anim = &_animations[animIndex]; + if (!anim->enable || anim->nextFrame >= _system->getMillis()) + return; + + AnimPart *p = &anim->parts[anim->curPart]; + anim->nextFrame = 0; + + int step = 0; + if (p->lastFrame >= p->firstFrame) { + step = 1; + anim->curFrame++; + } else { + step = -1; + anim->curFrame--; + } + + if (anim->curFrame == (p->lastFrame + step)) { + anim->cyclesCompleted++; + + if ((anim->cyclesCompleted > p->cycles) || anim->field_D) { + anim->lastPart = anim->curPart; + + if ((p->nextPart == -1) || (anim->field_D && p->field_A)) { + anim->enable = 0; + anim->field_D = 0; + return; + } + + anim->nextFrame += (p->partDelay * _vm->_tickLength); + anim->curPart = p->nextPart; + + p = &anim->parts[anim->curPart]; + anim->curFrame = p->firstFrame; + anim->cyclesCompleted = 0; + + } else { + anim->curFrame = p->firstFrame; + } + } + + if (p->sfxIndex != -1 && p->sfxFrame == anim->curFrame) + _vm->snd_playSoundEffect(p->sfxIndex, -1); + + anim->nextFrame += (anim->frameDelay * _vm->_tickLength); + + anim->wsa->displayFrame(anim->curFrame - 1, 0, anim->x, anim->y, 0, 0, 0); + anim->nextFrame += _system->getMillis(); +} + +void TimAnimator::playPart(int animIndex, int firstFrame, int lastFrame, int delay) { + if (!_vm || !_system || !_screen) + return; + + Animation *anim = &_animations[animIndex]; + // WORKAROUND for some bugged scripts that will try to play invalid animations + if (!anim->wsa) + return; + + int step = (lastFrame >= firstFrame) ? 1 : -1; + for (int i = firstFrame; i != (lastFrame + step); i += step) { + uint32 next = _system->getMillis() + delay * _vm->_tickLength; + if (anim->wsaCopyParams & 0x4000) { + _screen->copyRegion(112, 0, 112, 0, 176, 120, 6, 2); + anim->wsa->displayFrame(i - 1, 2, anim->x, anim->y, anim->wsaCopyParams & 0x1000 ? 0x5000 : 0x4000, _vm->_transparencyTable1, _vm->_transparencyTable2); + _screen->copyRegion(112, 0, 112, 0, 176, 120, 2, 0); + _screen->updateScreen(); + } else { + anim->wsa->displayFrame(i - 1, 0, anim->x, anim->y, 0, 0, 0); + _screen->updateScreen(); + } + int32 del = (int32)(next - _system->getMillis()); + if (del > 0) + _vm->delay(del, true); + } +} + +int TimAnimator::resetLastPart(int animIndex) { + Animation *anim = &_animations[animIndex]; + int8 res = -1; + SWAP(res, anim->lastPart); + return res; +} +#endif + +} // End of namespace Kyra diff --git a/engines/kyra/graphics/animator_v2.cpp b/engines/kyra/graphics/animator_v2.cpp new file mode 100644 index 0000000000..a7a8d70962 --- /dev/null +++ b/engines/kyra/graphics/animator_v2.cpp @@ -0,0 +1,187 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/engine/kyra_v2.h" +#include "kyra/graphics/screen_v2.h" + +namespace Kyra { + +void KyraEngine_v2::allocAnimObjects(int actors, int anims, int items) { + _animObjects = new AnimObj[actors + anims + items]; + assert(_animObjects); + + memset(_animObjects, 0, sizeof(AnimObj) * (actors + anims + items)); + + _animActor = _animObjects; + _animAnims = _animObjects + actors; + _animItems = _animObjects + actors + anims; +} + +KyraEngine_v2::AnimObj *KyraEngine_v2::initAnimList(AnimObj *list, AnimObj *entry) { + entry->nextObject = list; + return entry; +} + +KyraEngine_v2::AnimObj *KyraEngine_v2::addToAnimListSorted(AnimObj *list, AnimObj *add) { + add->nextObject = 0; + + if (!list) + return add; + + if (add->yPos1 <= list->yPos1) { + add->nextObject = list; + return add; + } + + AnimObj *cur = list; + AnimObj *prev = list; + while (add->yPos1 > cur->yPos1) { + AnimObj *temp = cur->nextObject; + if (!temp) + break; + prev = cur; + cur = temp; + } + + if (add->yPos1 <= cur->yPos1) { + prev->nextObject = add; + add->nextObject = cur; + } else { + cur->nextObject = add; + add->nextObject = 0; + } + return list; +} + +KyraEngine_v2::AnimObj *KyraEngine_v2::deleteAnimListEntry(AnimObj *list, AnimObj *entry) { + if (!list) + return 0; + + AnimObj *old = 0; + AnimObj *cur = list; + + while (true) { + if (cur == entry) + break; + if (!cur->nextObject) + break; + old = cur; + cur = cur->nextObject; + } + + if (cur != entry) + return list; + + if (cur == list) { + if (!cur->nextObject) + return 0; + cur = cur->nextObject; + return cur; + } + + if (!cur->nextObject) { + if (!old) + return 0; + old->nextObject = 0; + return list; + } + + if (cur != entry) + return list; + + old->nextObject = entry->nextObject; + return list; +} + +void KyraEngine_v2::refreshAnimObjectsIfNeed() { + for (AnimObj *curEntry = _animList; curEntry; curEntry = curEntry->nextObject) { + if (curEntry->enabled && curEntry->needRefresh) { + restorePage3(); + drawAnimObjects(); + refreshAnimObjects(0); + screen()->updateScreen(); + return; + } + } +} + +void KyraEngine_v2::flagAnimObjsForRefresh() { + for (AnimObj *curEntry = _animList; curEntry; curEntry = curEntry->nextObject) + curEntry->needRefresh = 1; +} + +void KyraEngine_v2::flagAnimObjsSpecialRefresh() { + for (AnimObj *curEntry = _animList; curEntry; curEntry = curEntry->nextObject) + curEntry->specialRefresh = 1; +} + +void KyraEngine_v2::addItemToAnimList(int item) { + assert(item >= 0 && item < _itemListSize); + + restorePage3(); + + AnimObj *animObj = _animItems + item; + + animObj->enabled = 1; + animObj->needRefresh = 1; + + int itemId = _itemList[item].id; + + animObj->xPos2 = animObj->xPos1 = _itemList[item].x; + animObj->yPos2 = animObj->yPos1 = _itemList[item].y; + + animObj->shapePtr = getShapePtr(itemId + _desc.itemShapeStart); + animSetupPaletteEntry(animObj); + animObj->shapeIndex2 = animObj->shapeIndex1 = itemId + _desc.itemShapeStart; + + int scaleY, scaleX; + scaleY = scaleX = getScale(animObj->xPos1, animObj->yPos1); + + uint8 *shapePtr = getShapePtr(itemId + _desc.itemShapeStart); + animObj->xPos3 = (animObj->xPos2 -= (screen_v2()->getShapeScaledWidth(shapePtr, scaleX) >> 1)); + animObj->yPos3 = (animObj->yPos2 -= screen_v2()->getShapeScaledHeight(shapePtr, scaleY)); + + animObj->width2 = animObj->height2 = 0; + + _animList = addToAnimListSorted(_animList, animObj); + animObj->needRefresh = 1; +} + +void KyraEngine_v2::deleteItemAnimEntry(int item) { + assert(item < _itemListSize); + + AnimObj *animObj = _animItems + item; + + restorePage3(); + + animObj->shapePtr = 0; + animObj->shapeIndex1 = 0xFFFF; + animObj->shapeIndex2 = 0xFFFF; + animObj->needRefresh = 1; + + refreshAnimObjectsIfNeed(); + + animObj->enabled = 0; + _animList = deleteAnimListEntry(_animList, animObj); +} + +} // End of namespace Kyra diff --git a/engines/kyra/graphics/screen.cpp b/engines/kyra/graphics/screen.cpp new file mode 100644 index 0000000000..a07e437d5f --- /dev/null +++ b/engines/kyra/graphics/screen.cpp @@ -0,0 +1,3971 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/graphics/screen.h" +#include "kyra/kyra_v1.h" +#include "kyra/resource/resource.h" + +#include "common/endian.h" +#include "common/memstream.h" +#include "common/system.h" +#include "common/config-manager.h" + +#include "engines/util.h" + +#include "graphics/cursorman.h" +#include "graphics/palette.h" +#include "graphics/sjis.h" + +namespace Kyra { + +Screen::Screen(KyraEngine_v1 *vm, OSystem *system, const ScreenDim *dimTable, const int dimTableSize) + : _system(system), _vm(vm), _sjisInvisibleColor(0), _dimTable(dimTable), _dimTableCount(dimTableSize), + _cursorColorKey((vm->game() == GI_KYRA1 || vm->game() == GI_EOB1 || vm->game() == GI_EOB2) ? 0xFF : 0) { + _debugEnabled = false; + _maskMinY = _maskMaxY = -1; + + _drawShapeVar1 = 0; + _drawShapeVar3 = 1; + _drawShapeVar4 = 0; + _drawShapeVar5 = 0; + + memset(_fonts, 0, sizeof(_fonts)); + + memset(_pagePtrs, 0, sizeof(_pagePtrs)); + // In VGA mode the odd and even page pointers point to the same buffers. + for (int i = 0; i < SCREEN_PAGE_NUM; i++) + _pageMapping[i] = i & ~1; + + _renderMode = Common::kRenderDefault; + _sjisMixedFontMode = false; + + _useHiColorScreen = _vm->gameFlags().useHiColorMode; + _screenPageSize = SCREEN_PAGE_SIZE; + _16bitPalette = 0; + _16bitConversionPalette = 0; + _16bitShadingLevel = 0; + _bytesPerPixel = 1; + + _currentFont = FID_8_FNT; + _paletteChanged = true; + _curDim = 0; +} + +Screen::~Screen() { + for (int i = 0; i < SCREEN_OVLS_NUM; ++i) + delete[] _sjisOverlayPtrs[i]; + + delete[] _pagePtrs[0]; + + for (int f = 0; f < ARRAYSIZE(_fonts); ++f) + delete _fonts[f]; + + delete _screenPalette; + delete _internFadePalette; + delete[] _decodeShapeBuffer; + delete[] _animBlockPtr; + delete[] _16bitPalette; + delete[] _16bitConversionPalette; + + for (uint i = 0; i < _palettes.size(); ++i) + delete _palettes[i]; + + for (int i = 0; i < _dimTableCount; ++i) + delete _customDimTable[i]; + delete[] _customDimTable; +} + +bool Screen::init() { + _debugEnabled = false; + + memset(_sjisOverlayPtrs, 0, sizeof(_sjisOverlayPtrs)); + _useOverlays = false; + _useSJIS = false; + _use16ColorMode = _vm->gameFlags().use16ColorMode; + _isAmiga = (_vm->gameFlags().platform == Common::kPlatformAmiga); + + // We only check the "render_mode" setting for both Eye of the Beholder + // games here, since all the other games do not support the render_mode + // setting or handle it differently, like Kyra 1 PC-98. This avoids + // graphics glitches and crashes in other games, when the user sets his + // global render_mode setting to EGA for example. + // TODO/FIXME: It would be nice not to hardcode this. But there is no + // trivial/non annoying way to do mode checks in an easy fashion right + // now. + // In a more general sense, we might want to think about a way to only + // pass valid config values, as in values which the engine can work with, + // to the engines. We already limit the selection via our GUIO flags in + // the game specific settings, but this is not enough due to global + // settings allowing everything. + if (_vm->game() == GI_EOB1 || _vm->game() == GI_EOB2) { + if (ConfMan.hasKey("render_mode")) + _renderMode = Common::parseRenderMode(ConfMan.get("render_mode")); + } + + // CGA and EGA modes use additional pages to do the CGA/EGA specific graphics conversions. + if (_vm->game() == GI_EOB1 && (_renderMode == Common::kRenderCGA || _renderMode == Common::kRenderEGA)) { + for (int i = 0; i < 8; i++) + _pageMapping[i] = i; + } + + memset(_fonts, 0, sizeof(_fonts)); + + _useOverlays = (_vm->gameFlags().useHiRes && _renderMode != Common::kRenderEGA); + + if (_useOverlays) { + _useSJIS = (_vm->gameFlags().lang == Common::JA_JPN); + _sjisInvisibleColor = (_vm->game() == GI_KYRA1) ? 0x80 : 0xF6; + _sjisMixedFontMode = !_use16ColorMode; + + if (!_sjisOverlayPtrs[0]) { + // We alway assume 2 bytes per pixel here when the backend is in hicolor mode, since this is the surface that is passed to the backend. + // We do this regardsless of the paramater sent to enableHiColorMode() so as not to have to change the backend color mode. + // Conversions from 8bit to 16bit have to take place when copying data to this surface here. + int bpp = _useHiColorScreen ? 2 : 1; + _sjisOverlayPtrs[0] = new uint8[SCREEN_OVL_SJIS_SIZE * bpp]; + assert(_sjisOverlayPtrs[0]); + memset(_sjisOverlayPtrs[0], _sjisInvisibleColor, SCREEN_OVL_SJIS_SIZE * bpp); + } + + for (int i = 1; i < SCREEN_OVLS_NUM; ++i) { + if (!_sjisOverlayPtrs[i]) { + _sjisOverlayPtrs[i] = new uint8[SCREEN_OVL_SJIS_SIZE]; + assert(_sjisOverlayPtrs[i]); + memset(_sjisOverlayPtrs[i], _sjisInvisibleColor, SCREEN_OVL_SJIS_SIZE); + } + } + + if (_useSJIS) { + Graphics::FontSJIS *font = Graphics::FontSJIS::createFont(_vm->gameFlags().platform); + + if (!font) + error("Could not load any SJIS font, neither the original nor ScummVM's 'SJIS.FNT'"); + + _fonts[FID_SJIS_FNT] = new SJISFont(font, _sjisInvisibleColor, _use16ColorMode, !_use16ColorMode && _vm->game() != GI_LOL && _vm->game() != GI_EOB2, _vm->game() == GI_EOB2 && _vm->gameFlags().platform == Common::kPlatformFMTowns, !_use16ColorMode && _vm->game() == GI_LOL ? 1 : 0); + } + } + + _curPage = 0; + + enableHiColorMode(false); + + memset(_shapePages, 0, sizeof(_shapePages)); + + const int paletteCount = _isAmiga ? 13 : 4; + // We allow 256 color palettes in EGA mode, since original EOB II code does the same and requires it + const int numColors = _use16ColorMode ? 16 : (_isAmiga ? 32 : (_renderMode == Common::kRenderCGA ? 4 : 256)); + + _interfacePaletteEnabled = false; + + _screenPalette = new Palette(numColors); + assert(_screenPalette); + + _palettes.resize(paletteCount); + for (int i = 0; i < paletteCount; ++i) { + _palettes[i] = new Palette(numColors); + assert(_palettes[i]); + } + + // Setup CGA colors (if CGA mode is selected) + if (_renderMode == Common::kRenderCGA) { + Palette pal(5); + pal.setCGAPalette(1, Palette::kIntensityHigh); + // create additional black color 4 for use with the mouse cursor manager + pal.fill(4, 1, 0); + Screen::setScreenPalette(pal); + } + + _internFadePalette = new Palette(numColors); + assert(_internFadePalette); + + setScreenPalette(getPalette(0)); + + // We setup the PC98 text mode palette at [16, 24], since that will be used + // for KANJI characters in Lands of Lore. + if (_use16ColorMode && _vm->gameFlags().platform == Common::kPlatformPC98) { + uint8 palette[8 * 3]; + + for (int i = 0; i < 8; ++i) { + palette[i * 3 + 0] = ((i >> 1) & 1) * 0xFF; + palette[i * 3 + 1] = ((i >> 2) & 1) * 0xFF; + palette[i * 3 + 2] = ((i >> 0) & 1) * 0xFF; + } + + _system->getPaletteManager()->setPalette(palette, 16, 8); + } + + _customDimTable = new ScreenDim *[_dimTableCount]; + memset(_customDimTable, 0, sizeof(ScreenDim *) * _dimTableCount); + + _curDimIndex = -1; + _curDim = 0; + _charWidth = 0; + _charOffset = 0; + for (int i = 0; i < ARRAYSIZE(_textColorsMap); ++i) + _textColorsMap[i] = i; + _textColorsMap16bit[0] = _textColorsMap16bit[1] = 0; + _decodeShapeBuffer = NULL; + _decodeShapeBufferSize = 0; + _animBlockPtr = NULL; + _animBlockSize = 0; + _mouseLockCount = 1; + CursorMan.showMouse(false); + + _forceFullUpdate = false; + + return true; +} + +bool Screen::enableScreenDebug(bool enable) { + bool temp = _debugEnabled; + + if (_debugEnabled != enable) { + _debugEnabled = enable; + setResolution(); + _forceFullUpdate = true; + updateScreen(); + } + + return temp; +} + +void Screen::setResolution() { + byte palette[3 * 256]; + if (!_useHiColorScreen) + _system->getPaletteManager()->grabPalette(palette, 0, 256); + + int width = 320, height = 200; + + if (_vm->gameFlags().useHiRes) { + height = 400; + + if (_debugEnabled) + width = 960; + else + width = 640; + } else { + if (_debugEnabled) + width = 640; + else + width = 320; + } + + if (_useHiColorScreen) { + Graphics::PixelFormat px(2, 5, 5, 5, 0, 10, 5, 0, 0); + Common::List<Graphics::PixelFormat> tryModes = _system->getSupportedFormats(); + for (Common::List<Graphics::PixelFormat>::iterator g = tryModes.begin(); g != tryModes.end(); ++g) { + if (g->bytesPerPixel != 2 || g->aBits()) { + g = tryModes.reverse_erase(g); + } else if (*g == px) { + tryModes.clear(); + tryModes.push_back(px); + break; + } + } + initGraphics(width, height, tryModes); + if (_system->getScreenFormat().bytesPerPixel != 2) + error("Required graphics mode not supported by platform."); + + } else { + initGraphics(width, height); + _system->getPaletteManager()->setPalette(palette, 0, 256); + } +} + +void Screen::enableHiColorMode(bool enabled) { + if (_useHiColorScreen && enabled) { + if (!_16bitPalette) + _16bitPalette = new uint16[1024]; + memset(_16bitPalette, 0, 1024 * sizeof(uint16)); + delete[] _16bitConversionPalette; + _16bitConversionPalette = 0; + _bytesPerPixel = 2; + } else { + if (_useHiColorScreen) { + if (!_16bitConversionPalette) + _16bitConversionPalette = new uint16[256]; + memset(_16bitConversionPalette, 0, 256 * sizeof(uint16)); + } + + delete[] _16bitPalette; + _16bitPalette = 0; + _bytesPerPixel = 1; + } + + resetPagePtrsAndBuffers(SCREEN_PAGE_SIZE * _bytesPerPixel); +} + +void Screen::updateScreen() { + bool needRealUpdate = _forceFullUpdate || !_dirtyRects.empty() || _paletteChanged; + _paletteChanged = false; + + if (_useOverlays) + updateDirtyRectsOvl(); + else if (_isAmiga && _interfacePaletteEnabled) + updateDirtyRectsAmiga(); + else + updateDirtyRects(); + + if (_debugEnabled) { + needRealUpdate = true; + + if (!_useOverlays) + _system->copyRectToScreen(getPagePtr(2), SCREEN_W, 320, 0, SCREEN_W, SCREEN_H); + else + _system->copyRectToScreen(getPagePtr(2), SCREEN_W, 640, 0, SCREEN_W, SCREEN_H); + } + + if (needRealUpdate) + _system->updateScreen(); +} + +void Screen::updateDirtyRects() { + if (_forceFullUpdate) { + _system->copyRectToScreen(getCPagePtr(0), SCREEN_W, 0, 0, SCREEN_W, SCREEN_H); + } else { + const byte *page0 = getCPagePtr(0); + Common::List<Common::Rect>::iterator it; + for (it = _dirtyRects.begin(); it != _dirtyRects.end(); ++it) { + _system->copyRectToScreen(page0 + it->top * SCREEN_W + it->left, SCREEN_W, it->left, it->top, it->width(), it->height()); + } + } + _forceFullUpdate = false; + _dirtyRects.clear(); +} + +void Screen::updateDirtyRectsAmiga() { + if (_forceFullUpdate) { + _system->copyRectToScreen(getCPagePtr(0), SCREEN_W, 0, 0, SCREEN_W, 136); + + // Page 8 is not used by Kyra 1 AMIGA, thus we can use it to adjust the colors + copyRegion(0, 136, 0, 0, 320, 64, 0, 8, CR_NO_P_CHECK); + + uint8 *dst = getPagePtr(8); + for (int y = 0; y < 64; ++y) + for (int x = 0; x < 320; ++x) + *dst++ += 32; + + _system->copyRectToScreen(getCPagePtr(8), SCREEN_W, 0, 136, SCREEN_W, 64); + } else { + const byte *page0 = getCPagePtr(0); + Common::List<Common::Rect>::iterator it; + + for (it = _dirtyRects.begin(); it != _dirtyRects.end(); ++it) { + if (it->bottom <= 136) { + _system->copyRectToScreen(page0 + it->top * SCREEN_W + it->left, SCREEN_W, it->left, it->top, it->width(), it->height()); + } else { + // Check whether the rectangle is part of both the screen and the interface + if (it->top < 136) { + // The rectangle covers both screen part and interface part + + const int screenHeight = 136 - it->top; + const int interfaceHeight = it->bottom - 136; + + const int width = it->width(); + const int lineAdd = SCREEN_W - width; + + // Copy the screen part verbatim + _system->copyRectToScreen(page0 + it->top * SCREEN_W + it->left, SCREEN_W, it->left, it->top, width, screenHeight); + + // Adjust the interface part + copyRegion(it->left, 136, 0, 0, width, interfaceHeight, 0, 8, Screen::CR_NO_P_CHECK); + + uint8 *dst = getPagePtr(8); + for (int y = 0; y < interfaceHeight; ++y) { + for (int x = 0; x < width; ++x) + *dst++ += 32; + dst += lineAdd; + } + + _system->copyRectToScreen(getCPagePtr(8), SCREEN_W, it->left, 136, width, interfaceHeight); + } else { + // The rectangle only covers the interface part + + const int width = it->width(); + const int height = it->height(); + const int lineAdd = SCREEN_W - width; + + copyRegion(it->left, it->top, 0, 0, width, height, 0, 8, Screen::CR_NO_P_CHECK); + + uint8 *dst = getPagePtr(8); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) + *dst++ += 32; + dst += lineAdd; + } + + _system->copyRectToScreen(getCPagePtr(8), SCREEN_W, it->left, it->top, width, height); + } + } + } + } + + _forceFullUpdate = false; + _dirtyRects.clear(); +} + +void Screen::updateDirtyRectsOvl() { + if (_forceFullUpdate) { + const byte *src = getCPagePtr(0); + byte *dst = _sjisOverlayPtrs[0]; + scale2x(dst, 640, src, SCREEN_W, SCREEN_W, SCREEN_H); + mergeOverlay(0, 0, 640, 400); + _system->copyRectToScreen(dst, _useHiColorScreen ? 1280 : 640, 0, 0, 640, 400); + } else { + const byte *page0 = getCPagePtr(0); + byte *ovl0 = _sjisOverlayPtrs[0]; + int dstBpp = _useHiColorScreen ? 2 : 1; + + Common::List<Common::Rect>::iterator it; + for (it = _dirtyRects.begin(); it != _dirtyRects.end(); ++it) { + byte *dst = ovl0 + it->top * 1280 * dstBpp + (it->left << dstBpp); + const byte *src = page0 + it->top * SCREEN_W * _bytesPerPixel + it->left * _bytesPerPixel; + + scale2x(dst, 640, src, SCREEN_W, it->width(), it->height()); + mergeOverlay(it->left<<1, it->top<<1, it->width()<<1, it->height()<<1); + _system->copyRectToScreen(dst, _useHiColorScreen ? 1280 : 640, it->left << 1, it->top << 1, it->width() << 1, it->height() << 1); + } + } + + _forceFullUpdate = false; + _dirtyRects.clear(); +} + +void Screen::scale2x(byte *dst, int dstPitch, const byte *src, int srcPitch, int w, int h) { + int srcBpp = _bytesPerPixel; + int dstBpp = _useHiColorScreen ? 2 : 1; + + byte *dstL1 = dst; + byte *dstL2 = dst + dstPitch * dstBpp; + + int dstAdd = (dstPitch * 2 - w * 2) * dstBpp; + int srcAdd = (srcPitch - w) * srcBpp; + int dstInc = 2 * dstBpp; + + while (h--) { + for (int x = 0; x < w; x++, src += srcBpp, dstL1 += dstInc, dstL2 += dstInc) { + if (dstBpp == 1) { + uint16 col = *src; + col |= col << 8; + *(uint16 *)(dstL1) = *(uint16 *)(dstL2) = col; + } else if (dstBpp == srcBpp) { + uint32 col = *(const uint16 *)src; + col |= col << 16; + *(uint32 *)(dstL1) = *(uint32 *)(dstL2) = col; + } else if (dstBpp == 2) { + uint32 col = _16bitConversionPalette[*src]; + col |= col << 16; + *(uint32 *)(dstL1) = *(uint32 *)(dstL2) = col; + } + } + dstL1 += dstAdd; dstL2 += dstAdd; + src += srcAdd; + } +} + +void Screen::mergeOverlay(int x, int y, int w, int h) { + int bpp = _useHiColorScreen ? 2 : 1; + byte *dst = _sjisOverlayPtrs[0] + y * 640 * bpp + x * bpp; + const byte *src = _sjisOverlayPtrs[1] + y * 640 + x; + uint16 *p16 = _16bitPalette ? _16bitPalette : (_16bitConversionPalette ? _16bitConversionPalette : 0); + + int add = 640 - w; + + while (h--) { + for (x = 0; x < w; ++x, dst += bpp) { + byte col = *src++; + if (col != _sjisInvisibleColor) { + if (bpp == 2) + *(uint16*)dst = p16[col]; + else + *dst = col; + } + } + dst += add * bpp; + src += add; + } +} + +const ScreenDim *Screen::getScreenDim(int dim) const { + assert(dim < _dimTableCount); + return _customDimTable[dim] ? _customDimTable[dim] : &_dimTable[dim]; +} + +void Screen::modifyScreenDim(int dim, int x, int y, int w, int h) { + if (!_customDimTable[dim]) + _customDimTable[dim] = new ScreenDim; + + memcpy(_customDimTable[dim], &_dimTable[dim], sizeof(ScreenDim)); + _customDimTable[dim]->sx = x; + _customDimTable[dim]->sy = y; + _customDimTable[dim]->w = w; + _customDimTable[dim]->h = h; + if (dim == _curDimIndex || _vm->game() == GI_LOL) + setScreenDim(dim); +} + +void Screen::setScreenDim(int dim) { + _curDim = getScreenDim(dim); + _curDimIndex = dim; +} + +void Screen::resetPagePtrsAndBuffers(int pageSize) { + _screenPageSize = pageSize; + + delete[] _pagePtrs[0]; + memset(_pagePtrs, 0, sizeof(_pagePtrs)); + + Common::Array<uint8> realPages; + for (int i = 0; i < SCREEN_PAGE_NUM; i++) { + if (Common::find(realPages.begin(), realPages.end(), _pageMapping[i]) == realPages.end()) + realPages.push_back(_pageMapping[i]); + } + + int numPages = realPages.size(); + uint32 bufferSize = numPages * _screenPageSize; + + uint8 *pagePtr = new uint8[bufferSize]; + memset(pagePtr, 0, bufferSize); + + memset(_pagePtrs, 0, sizeof(_pagePtrs)); + for (int i = 0; i < SCREEN_PAGE_NUM; i++) { + if (_pagePtrs[_pageMapping[i]]) { + _pagePtrs[i] = _pagePtrs[_pageMapping[i]]; + } else { + _pagePtrs[i] = pagePtr; + pagePtr += _screenPageSize; + } + } +} + +uint8 *Screen::getPagePtr(int pageNum) { + assert(pageNum < SCREEN_PAGE_NUM); + return _pagePtrs[pageNum]; +} + +const uint8 *Screen::getCPagePtr(int pageNum) const { + assert(pageNum < SCREEN_PAGE_NUM); + return _pagePtrs[pageNum]; +} + +uint8 *Screen::getPageRect(int pageNum, int x, int y, int w, int h) { + assert(pageNum < SCREEN_PAGE_NUM); + if (pageNum == 0 || pageNum == 1) + addDirtyRect(x, y, w, h); + return _pagePtrs[pageNum] + y * SCREEN_W + x; +} + +void Screen::clearPage(int pageNum) { + assert(pageNum < SCREEN_PAGE_NUM); + if (pageNum == 0 || pageNum == 1) + _forceFullUpdate = true; + memset(getPagePtr(pageNum), 0, _screenPageSize); + clearOverlayPage(pageNum); +} + +int Screen::setCurPage(int pageNum) { + assert(pageNum < SCREEN_PAGE_NUM); + int previousPage = _curPage; + _curPage = pageNum; + return previousPage; +} + +void Screen::clearCurPage() { + if (_curPage == 0 || _curPage == 1) + _forceFullUpdate = true; + memset(getPagePtr(_curPage), 0, _screenPageSize); + clearOverlayPage(_curPage); +} + +void Screen::copyWsaRect(int x, int y, int w, int h, int dimState, int plotFunc, const uint8 *src, + int unk1, const uint8 *unkPtr1, const uint8 *unkPtr2) { + uint8 *dstPtr = getPagePtr(_curPage); + uint8 *origDst = dstPtr; + + const ScreenDim *dim = getScreenDim(dimState); + int dimX1 = dim->sx << 3; + int dimX2 = dim->w << 3; + dimX2 += dimX1; + + int dimY1 = dim->sy; + int dimY2 = dim->h; + dimY2 += dimY1; + + int temp = y - dimY1; + if (temp < 0) { + if ((temp += h) <= 0) + return; + else { + SWAP(temp, h); + y += temp - h; + src += (temp - h) * w; + } + } + + temp = dimY2 - y; + if (temp <= 0) + return; + + if (temp < h) + h = temp; + + int srcOffset = 0; + temp = x - dimX1; + if (temp < 0) { + temp = -temp; + srcOffset = temp; + x += temp; + w -= temp; + } + + int srcAdd = 0; + + temp = dimX2 - x; + if (temp <= 0) + return; + + if (temp < w) { + SWAP(w, temp); + temp -= w; + srcAdd = temp; + } + + dstPtr += y * SCREEN_W + x; + uint8 *dst = dstPtr; + + if (_curPage == 0 || _curPage == 1) + addDirtyRect(x, y, w, h); + + if (!_use16ColorMode) + clearOverlayRect(_curPage, x, y, w, h); + + temp = h; + int curY = y; + while (h--) { + src += srcOffset; + ++curY; + int cW = w; + + switch (plotFunc) { + case 0: + memcpy(dst, src, cW); + dst += cW; src += cW; + break; + + case 1: + while (cW--) { + uint8 d = *src++; + uint8 t = unkPtr1[d]; + if (t != 0xFF) + d = unkPtr2[*dst + (t << 8)]; + *dst++ = d; + } + break; + + case 4: + while (cW--) { + uint8 d = *src++; + if (d) + *dst = d; + ++dst; + } + break; + + case 5: + while (cW--) { + uint8 d = *src++; + if (d) { + uint8 t = unkPtr1[d]; + if (t != 0xFF) + d = unkPtr2[*dst + (t << 8)]; + *dst = d; + } + ++dst; + } + break; + + case 8: + case 9: + while (cW--) { + uint8 d = *src++; + uint8 t = _shapePages[0][dst - origDst] & 7; + if (unk1 < t && (curY > _maskMinY && curY < _maskMaxY)) + d = _shapePages[1][dst - origDst]; + *dst++ = d; + } + break; + + case 12: + case 13: + while (cW--) { + uint8 d = *src++; + if (d) { + uint8 t = _shapePages[0][dst - origDst] & 7; + if (unk1 < t && (curY > _maskMinY && curY < _maskMaxY)) + d = _shapePages[1][dst - origDst]; + *dst++ = d; + } else { + d = _shapePages[1][dst - origDst]; + *dst++ = d; + } + } + break; + + default: + break; + } + + dst = (dstPtr += SCREEN_W); + src += srcAdd; + } +} + +int Screen::getPagePixel(int pageNum, int x, int y) { + assert(pageNum < SCREEN_PAGE_NUM); + assert(x >= 0 && x < SCREEN_W && y >= 0 && y < SCREEN_H); + if (_bytesPerPixel == 1) + return _pagePtrs[pageNum][y * SCREEN_W + x]; + else + return ((uint16*)_pagePtrs[pageNum])[y * SCREEN_W + x]; +} + +void Screen::setPagePixel(int pageNum, int x, int y, uint8 color) { + assert(pageNum < SCREEN_PAGE_NUM); + assert(x >= 0 && x < SCREEN_W && y >= 0 && y < SCREEN_H); + + if (pageNum == 0 || pageNum == 1) + addDirtyRect(x, y, 1, 1); + + if (_use16ColorMode) { + color &= 0x0F; + color |= (color << 4); + } else if (_renderMode == Common::kRenderCGA) { + color &= 0x03; + } else if (_renderMode == Common::kRenderEGA && !_useHiResEGADithering) { + color &= 0x0F; + } + + if (_bytesPerPixel == 2) { + ((uint16*)_pagePtrs[pageNum])[y * SCREEN_W + x] = _16bitPalette[color]; + } else { + _pagePtrs[pageNum][y * SCREEN_W + x] = color; + } +} + +void Screen::fadeFromBlack(int delay, const UpdateFunctor *upFunc) { + fadePalette(getPalette(0), delay, upFunc); +} + +void Screen::fadeToBlack(int delay, const UpdateFunctor *upFunc) { + if (_renderMode == Common::kRenderEGA) + return; + + Palette pal(getPalette(0).getNumColors()); + fadePalette(pal, delay, upFunc); +} + +void Screen::fadePalette(const Palette &pal, int delay, const UpdateFunctor *upFunc) { + if (_renderMode == Common::kRenderEGA || _bytesPerPixel == 2) + setScreenPalette(pal); + + updateScreen(); + + if (_renderMode == Common::kRenderCGA || _renderMode == Common::kRenderEGA || _bytesPerPixel == 2) + return; + + int diff = 0, delayInc = 0; + getFadeParams(pal, delay, delayInc, diff); + + int delayAcc = 0; + while (!_vm->shouldQuit()) { + delayAcc += delayInc; + + int refreshed = fadePalStep(pal, diff); + + if (upFunc && upFunc->isValid()) + (*upFunc)(); + else if (_useHiColorScreen) + updateScreen(); + else + _system->updateScreen(); + + if (!refreshed) + break; + + _vm->delay((delayAcc >> 8) * 1000 / 60); + delayAcc &= 0xFF; + } + + // In case we should quit we setup the final palette here. This avoids + // ugly palette glitches when quitting while fading. This can for example + // be noticed when quitting while viewing the family album in Kyra3. + if (_vm->shouldQuit()) { + setScreenPalette(pal); + } +} + +void Screen::getFadeParams(const Palette &pal, int delay, int &delayInc, int &diff) { + uint8 maxDiff = 0; + + for (int i = 0; i < pal.getNumColors() * 3; ++i) { + diff = ABS(pal[i] - (*_screenPalette)[i]); + maxDiff = MAX<uint8>(maxDiff, diff); + } + + delayInc = (delay << 8) & 0x7FFF; + if (maxDiff != 0) + delayInc /= maxDiff; + + delay = delayInc; + for (diff = 1; diff <= maxDiff; ++diff) { + if (delayInc >= 512) + break; + delayInc += delay; + } +} + +int Screen::fadePalStep(const Palette &pal, int diff) { + _internFadePalette->copy(*_screenPalette); + + bool needRefresh = false; + + for (int i = 0; i < pal.getNumColors() * 3; ++i) { + int c1 = pal[i]; + int c2 = (*_internFadePalette)[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; + } + + (*_internFadePalette)[i] = (uint8)c2; + } + } + + if (needRefresh) + setScreenPalette(*_internFadePalette); + + return needRefresh ? 1 : 0; +} + +void Screen::setPaletteIndex(uint8 index, uint8 red, uint8 green, uint8 blue) { + Palette &pal = getPalette(0); + + const int offset = index * 3; + + if (pal[offset + 0] == red && pal[offset + 1] == green && pal[offset + 2] == blue) + return; + + pal[offset + 0] = red; + pal[offset + 1] = green; + pal[offset + 2] = blue; + + setScreenPalette(pal); +} + +void Screen::getRealPalette(int num, uint8 *dst) { + const int colors = _use16ColorMode ? 16 : (_isAmiga ? 32 : 256); + const uint8 *palData = getPalette(num).getData(); + + if (!palData) { + memset(dst, 0, colors * 3); + return; + } + + for (int i = 0; i < colors; ++i) { + dst[0] = (palData[0] * 0xFF) / 0x3F; + dst[1] = (palData[1] * 0xFF) / 0x3F; + dst[2] = (palData[2] * 0xFF) / 0x3F; + dst += 3; + palData += 3; + } +} + +void Screen::setScreenPalette(const Palette &pal) { + uint8 screenPal[256 * 3]; + _screenPalette->copy(pal); + + for (int i = 0; i < pal.getNumColors(); ++i) { + screenPal[3 * i + 0] = (pal[i * 3 + 0] * 0xFF) / 0x3F; + screenPal[3 * i + 1] = (pal[i * 3 + 1] * 0xFF) / 0x3F; + screenPal[3 * i + 2] = (pal[i * 3 + 2] * 0xFF) / 0x3F; + } + + _paletteChanged = true; + + if (_useHiColorScreen) { + if (_16bitPalette) + memcpy(_16bitPalette, pal.getData(), 512); + + // Generate 16bit palette for the 8bit/16 bit conversion in scale2x() + if (_16bitConversionPalette) { + Graphics::PixelFormat pixelFormat = _system->getScreenFormat(); + for (int i = 0; i < 256; ++i) + _16bitConversionPalette[i] = pixelFormat.RGBToColor(screenPal[i * 3], screenPal[i * 3 + 1], screenPal[i * 3 + 2]); + // The whole Surface has to be converted again after each palette chance + _forceFullUpdate = true; + } + return; + } + + _system->getPaletteManager()->setPalette(screenPal, 0, pal.getNumColors()); +} + +void Screen::enableInterfacePalette(bool e) { + _interfacePaletteEnabled = e; + + _forceFullUpdate = true; + _dirtyRects.clear(); + + // TODO: We might need to reset the mouse cursor + + updateScreen(); +} + +void Screen::setInterfacePalette(const Palette &pal, uint8 r, uint8 g, uint8 b) { + if (!_isAmiga) + return; + + uint8 screenPal[32 * 3]; + + assert(32 <= pal.getNumColors()); + + for (int i = 0; i < pal.getNumColors(); ++i) { + if (i != 0x10) { + screenPal[3 * i + 0] = (pal[i * 3 + 0] * 0xFF) / 0x3F; + screenPal[3 * i + 1] = (pal[i * 3 + 1] * 0xFF) / 0x3F; + screenPal[3 * i + 2] = (pal[i * 3 + 2] * 0xFF) / 0x3F; + } else { + screenPal[3 * i + 0] = (r * 0xFF) / 0x3F; + screenPal[3 * i + 1] = (g * 0xFF) / 0x3F; + screenPal[3 * i + 2] = (b * 0xFF) / 0x3F; + } + } + + _paletteChanged = true; + _system->getPaletteManager()->setPalette(screenPal, 32, pal.getNumColors()); +} + +void Screen::copyToPage0(int y, int h, uint8 page, uint8 *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; + } + addDirtyRect(0, y, SCREEN_W, h); + // This would remove the text in the end sequence of + // the (Kyrandia 1) FM-TOWNS version. + // Since this method is just used for the Seqplayer + // this shouldn't be a problem anywhere else, so it's + // safe to disable the call here. + //clearOverlayRect(0, 0, y, SCREEN_W, h); +} + +void Screen::copyRegion(int x1, int y1, int x2, int y2, int w, int h, int srcPage, int dstPage, int flags) { + 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; + } + + const uint8 *src = getPagePtr(srcPage) + y1 * SCREEN_W * _bytesPerPixel + x1 * _bytesPerPixel; + uint8 *dst = getPagePtr(dstPage) + y2 * SCREEN_W * _bytesPerPixel + x2 * _bytesPerPixel; + + if (src == dst) + return; + + if (dstPage == 0 || dstPage == 1) + addDirtyRect(x2, y2, w, h); + + copyOverlayRegion(x1, y1, x2, y2, w, h, srcPage, dstPage); + + if (flags & CR_NO_P_CHECK) { + while (h--) { + memmove(dst, src, w * _bytesPerPixel); + src += SCREEN_W * _bytesPerPixel; + dst += SCREEN_W * _bytesPerPixel; + } + } else { + while (h--) { + for (int i = 0; i < w; ++i) { + if (_bytesPerPixel == 2) { + uint px = *(const uint16*)&src[i << 1]; + if (px) + *(uint16*)&dst[i << 1] = px; + } else { + if (src[i]) + dst[i] = src[i]; + } + } + src += SCREEN_W * _bytesPerPixel; + dst += SCREEN_W * _bytesPerPixel; + } + } +} + +void Screen::copyRegionToBuffer(int pageNum, int x, int y, int w, int h, uint8 *dest) { + if (y < 0) { + dest += (-y) * w * _bytesPerPixel; + h += y; + y = 0; + } else if (y + h > SCREEN_H) { + h = SCREEN_H - y; + } + + if (x < 0) { + dest += -x * _bytesPerPixel; + w += x; + x = 0; + } else if (x + w > SCREEN_W) { + w = SCREEN_W - x; + } + + if (w < 0 || h < 0) + return; + + uint8 *pagePtr = getPagePtr(pageNum); + + for (int i = y; i < y + h; ++i) + memcpy(dest + (i - y) * w * _bytesPerPixel, pagePtr + i * SCREEN_W * _bytesPerPixel + x * _bytesPerPixel, w * _bytesPerPixel); +} + +void Screen::copyPage(uint8 srcPage, uint8 dstPage) { + uint8 *src = getPagePtr(srcPage); + uint8 *dst = getPagePtr(dstPage); + if (src != dst) + memcpy(dst, src, SCREEN_W * SCREEN_H * _bytesPerPixel); + copyOverlayRegion(0, 0, 0, 0, SCREEN_W, SCREEN_H, srcPage, dstPage); + + if (dstPage == 0 || dstPage == 1) + _forceFullUpdate = true; +} + +void Screen::copyBlockToPage(int pageNum, int x, int y, int w, int h, const uint8 *src) { + if (y < 0) { + src += (-y) * w * _bytesPerPixel; + h += y; + y = 0; + } else if (y + h > SCREEN_H) { + h = SCREEN_H - y; + } + + if (x < 0) { + src += -x * _bytesPerPixel; + w += x; + x = 0; + } else if (x + w > SCREEN_W) { + w = SCREEN_W - x; + } + + if (w < 0 || h < 0) + return; + + uint8 *dst = getPagePtr(pageNum) + y * SCREEN_W * _bytesPerPixel + x * _bytesPerPixel; + + if (pageNum == 0 || pageNum == 1) + addDirtyRect(x, y, w, h); + + clearOverlayRect(pageNum, x, y, w, h); + + while (h--) { + memcpy(dst, src, w * _bytesPerPixel); + dst += SCREEN_W * _bytesPerPixel; + src += w * _bytesPerPixel; + } +} + +void Screen::shuffleScreen(int sx, int sy, int w, int h, int srcPage, int dstPage, int ticks, bool 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 && !_vm->shouldQuit(); ++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); + } + // forcing full update for now + _forceFullUpdate = true; + updateScreen(); + now = (int32)_system->getMillis(); + wait = ticks * _vm->tickLength() - (now - start); + if (wait > 0) + _vm->delay(wait); + } + + copyOverlayRegion(sx, sy, sx, sy, w, h, srcPage, dstPage); + + if (_vm->shouldQuit()) { + copyRegion(sx, sy, sx, sy, w, h, srcPage, dstPage); + _system->updateScreen(); + } +} + +void Screen::fillRect(int x1, int y1, int x2, int y2, uint8 color, int pageNum, bool xored) { + assert(x2 < SCREEN_W && y2 < SCREEN_H); + uint16 color16 = 0; + if (pageNum == -1) + pageNum = _curPage; + + uint8 *dst = getPagePtr(pageNum) + y1 * SCREEN_W * _bytesPerPixel + x1 * _bytesPerPixel; + + if (pageNum == 0 || pageNum == 1) + addDirtyRect(x1, y1, x2-x1+1, y2-y1+1); + + clearOverlayRect(pageNum, x1, y1, x2-x1+1, y2-y1+1); + + if (_use16ColorMode) { + color &= 0x0F; + color |= (color << 4); + } else if (_renderMode == Common::kRenderCGA) { + color &= 0x03; + } else if (_renderMode == Common::kRenderEGA && !_useHiResEGADithering) { + color &= 0x0F; + } else if (_bytesPerPixel == 2) + color16 = shade16bitColor(_16bitPalette[color]); + + if (xored) { + // no 16 bit support for this (unneeded) + for (; y1 <= y2; ++y1) { + for (int x = x1; x <= x2; ++x) + dst[x] ^= color; + dst += SCREEN_W; + } + } else { + for (; y1 <= y2; ++y1) { + if (_bytesPerPixel == 2) { + uint16 *ptr = (uint16*)dst; + for (int i = 0; i < x2 - x1 + 1; i++) + *ptr++ = color16; + } else { + memset(dst, color, x2 - x1 + 1); + } + dst += SCREEN_W * _bytesPerPixel; + } + } +} + +void Screen::drawBox(int x1, int y1, int x2, int y2, int 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) { + assert(x1 >= 0 && y1 >= 0); + 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 - 1, x2 - 1, y2 - 1, color2); + drawClippedLine(x1, y2, x2, y2, color2); +} + +void Screen::drawClippedLine(int x1, int y1, int x2, int y2, int 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 vertical, int x, int y, int length, int color) { + uint8 *ptr = getPagePtr(_curPage) + y * SCREEN_W * _bytesPerPixel + x * _bytesPerPixel; + + if (_use16ColorMode) { + color &= 0x0F; + color |= (color << 4); + } else if (_renderMode == Common::kRenderCGA) { + color &= 0x03; + } else if (_renderMode == Common::kRenderEGA && !_useHiResEGADithering) { + color &= 0x0F; + } else if (_bytesPerPixel == 2) + color = shade16bitColor(_16bitPalette[color]); + + if (vertical) { + assert((y + length) <= SCREEN_H); + int currLine = 0; + while (currLine < length) { + if (_bytesPerPixel == 2) + *(uint16*)ptr = color; + else + *ptr = color; + ptr += SCREEN_W * _bytesPerPixel; + currLine++; + } + } else { + assert((x + length) <= SCREEN_W); + if (_bytesPerPixel == 2) { + for (int i = 0; i < length; i++) { + *(uint16*)ptr = color; + ptr += 2; + } + } else { + memset(ptr, color, length); + } + } + + if (_curPage == 0 || _curPage == 1) + addDirtyRect(x, y, (vertical) ? 1 : length, (vertical) ? length : 1); + + clearOverlayRect(_curPage, x, y, (vertical) ? 1 : length, (vertical) ? length : 1); +} + +void Screen::setAnimBlockPtr(int size) { + delete[] _animBlockPtr; + _animBlockPtr = new uint8[size]; + assert(_animBlockPtr); + memset(_animBlockPtr, 0, size); + _animBlockSize = size; +} + +void Screen::setTextColor(const uint8 *cmap8, int a, int b) { + memcpy(&_textColorsMap[a], cmap8, (b - a + 1)); + // We need to update the color tables of all fonts, we + // setup so far here. + for (int i = 0; i < FID_NUM; ++i) { + if (_fonts[i]) + _fonts[i]->setColorMap(_textColorsMap); + } +} + +void Screen::setTextColor16bit(const uint16 *cmap16) { + assert(cmap16); + _textColorsMap16bit[0] = cmap16[0]; + _textColorsMap16bit[1] = cmap16[1]; + // We need to update the color tables of all fonts, we + // setup so far here. + for (int i = 0; i < FID_NUM; ++i) { + if (_fonts[i]) + _fonts[i]->set16bitColorMap(_textColorsMap16bit); + } +} + +bool Screen::loadFont(FontId fontId, const char *filename) { + if (fontId == FID_SJIS_FNT) { + warning("Trying to replace system SJIS font"); + return true; + } + + Font *&fnt = _fonts[fontId]; + + if (!fnt) { + if (_isAmiga) + fnt = new AMIGAFont(); +#ifdef ENABLE_EOB + else if (_vm->game() == GI_EOB1 || _vm->game() == GI_EOB2) + // We use normal VGA rendering in EOB II, since we do the complete EGA dithering in updateScreen(). + fnt = new OldDOSFont(_useHiResEGADithering ? Common::kRenderVGA : _renderMode); +#endif // ENABLE_EOB + else + fnt = new DOSFont(); + + assert(fnt); + } + + Common::SeekableReadStream *file = _vm->resource()->createReadStream(filename); + if (!file) + error("Font file '%s' is missing", filename); + + bool ret = fnt->load(*file); + fnt->setColorMap(_textColorsMap); + delete file; + return ret; +} + +Screen::FontId Screen::setFont(FontId fontId) { + FontId prev = _currentFont; + _currentFont = fontId; + + assert(_fonts[_currentFont]); + return prev; +} + +int Screen::getFontHeight() const { + return _fonts[_currentFont]->getHeight(); +} + +int Screen::getFontWidth() const { + return _fonts[_currentFont]->getWidth(); +} + +int Screen::getCharWidth(uint16 c) const { + const int width = _fonts[_currentFont]->getCharWidth(c); + return width + ((_currentFont != FID_SJIS_FNT && _currentFont != FID_SJIS_LARGE_FNT && _currentFont != FID_SJIS_SMALL_FNT) ? _charWidth : 0); +} + +int Screen::getTextWidth(const char *str) { + int curLineLen = 0; + int maxLineLen = 0; + + FontId curFont = _currentFont; + + while (1) { + if (_sjisMixedFontMode && curFont != FID_SJIS_FNT && curFont != FID_SJIS_LARGE_FNT && curFont != FID_SJIS_SMALL_FNT) + setFont((*str & 0x80) ? ((_vm->game() == GI_EOB2 && curFont == FID_6_FNT) ? FID_SJIS_SMALL_FNT : FID_SJIS_FNT) : curFont); + + uint c = fetchChar(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) { + uint16 cmap16[2]; + if (_16bitPalette) { + cmap16[0] = color2 ? shade16bitColor(_16bitPalette[color2]) : 0xFFFF; + cmap16[1] = _16bitPalette[color1]; + setTextColor16bit(cmap16); + } + + uint8 cmap8[2]; + cmap8[0] = color2; + cmap8[1] = color1; + setTextColor(cmap8, 0, 1); + + FontId curFont = _currentFont; + + 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) { + if (_sjisMixedFontMode && curFont != FID_SJIS_FNT && curFont != FID_SJIS_LARGE_FNT && curFont != FID_SJIS_SMALL_FNT) + setFont((*str & 0x80) ? ((_vm->game() == GI_EOB2 && curFont == FID_6_FNT) ? FID_SJIS_SMALL_FNT : FID_SJIS_FNT) : curFont); + + uint8 charHeightFnt = getFontHeight(); + + uint c = fetchChar(str); + + if (c == 0) { + break; + } else if (c == '\r') { + x = x_start; + y += (charHeightFnt + _charOffset); + } else { + int charWidth = getCharWidth(c); + if (x + charWidth > SCREEN_W) { + x = x_start; + y += (charHeightFnt + _charOffset); + if (y >= SCREEN_H) + break; + } + + drawChar(c, x, y); + x += charWidth; + } + } +} + +uint16 Screen::fetchChar(const char *&s) const { + if (_currentFont != FID_SJIS_FNT && _currentFont != FID_SJIS_LARGE_FNT && _currentFont != FID_SJIS_SMALL_FNT) + return (uint8)*s++; + + uint16 ch = (uint8)*s++; + + if (ch <= 0x7F || (ch >= 0xA1 && ch <= 0xDF)) + return ch; + + ch |= (uint8)(*s++) << 8; + return ch; +} + +void Screen::drawChar(uint16 c, int x, int y) { + Font *fnt = _fonts[_currentFont]; + assert(fnt); + + const bool useOverlay = fnt->usesOverlay(); + const int charWidth = fnt->getCharWidth(c); + const int charHeight = fnt->getHeight(); + + if (x < 0 || y < 0) + return; + if (x + charWidth > SCREEN_W || y + charHeight > SCREEN_H) + return; + + if (useOverlay) { + uint8 *destPage = getOverlayPtr(_curPage); + if (!destPage) { + warning("trying to draw SJIS char on unsupported page %d", _curPage); + return; + } + + int bpp = (_currentFont == Screen::FID_SJIS_FNT || _currentFont == Screen::FID_SJIS_SMALL_FNT) ? 1 : 2; + destPage += (y * 2) * 640 * bpp + (x * 2 * bpp); + + fnt->drawChar(c, destPage, 640, bpp); + } else { + fnt->drawChar(c, getPagePtr(_curPage) + y * SCREEN_W * _bytesPerPixel + x * _bytesPerPixel, SCREEN_W, _bytesPerPixel); + } + + if (_curPage == 0 || _curPage == 1) + addDirtyRect(x, y, charWidth, charHeight); +} + +void Screen::drawShape(uint8 pageNum, const uint8 *shapeData, int x, int y, int sd, int flags, ...) { + if (!shapeData) + return; + + if (_vm->gameFlags().useAltShapeHeader) + shapeData += 2; + + if (*shapeData & 1) + flags |= 0x400; + + va_list args; + va_start(args, flags); + + static const int drawShapeVar2[] = { + 1, 3, 2, 5, 4, 3, 2, 1 + }; + + _dsShapeFadingTable = 0; + _dsShapeFadingLevel = 0; + _dsColorTable = 0; + _dsTransparencyTable1 = 0; + _dsTransparencyTable2 = 0; + _dsBackgroundFadingTable = 0; + _dsDrawLayer = 0; + + if (flags & DSF_CUSTOM_PALETTE) { + _dsColorTable = va_arg(args, uint8 *); + } + + if (flags & DSF_SHAPE_FADING) { + _dsShapeFadingTable = va_arg(args, uint8 *); + _dsShapeFadingLevel = va_arg(args, int); + if (!_dsShapeFadingLevel) + flags &= ~DSF_SHAPE_FADING; + } + + if (flags & DSF_TRANSPARENCY) { + _dsTransparencyTable1 = va_arg(args, uint8 *); + _dsTransparencyTable2 = va_arg(args, uint8 *); + } + + if (flags & 0x200) { + _drawShapeVar1 = (_drawShapeVar1 + 1) & 0x7; + _drawShapeVar3 = drawShapeVar2[_drawShapeVar1]; + _drawShapeVar4 = 0; + _drawShapeVar5 = 256; + } + + if (flags & 0x4000) + _drawShapeVar5 = va_arg(args, int); + + if (flags & 0x800) + _dsDrawLayer = va_arg(args, int); + + if (flags & DSF_SCALE) { + _dsScaleW = va_arg(args, int); + _dsScaleH = va_arg(args, int); + } else { + _dsScaleW = 0x100; + _dsScaleH = 0x100; + } + + if ((flags & DSF_BACKGROUND_FADING) && _vm->game() != GI_KYRA1) + _dsBackgroundFadingTable = va_arg(args, uint8 *); + + va_end(args); + + static const DsMarginSkipFunc dsMarginFunc[] = { + &Screen::drawShapeMarginNoScaleUpwind, + &Screen::drawShapeMarginNoScaleDownwind, + &Screen::drawShapeMarginNoScaleUpwind, + &Screen::drawShapeMarginNoScaleDownwind, + &Screen::drawShapeMarginScaleUpwind, + &Screen::drawShapeMarginScaleDownwind, + &Screen::drawShapeMarginScaleUpwind, + &Screen::drawShapeMarginScaleDownwind + }; + + static const DsMarginSkipFunc dsSkipFunc[] = { + &Screen::drawShapeMarginNoScaleUpwind, + &Screen::drawShapeMarginNoScaleDownwind, + &Screen::drawShapeMarginNoScaleUpwind, + &Screen::drawShapeMarginNoScaleDownwind, + &Screen::drawShapeSkipScaleUpwind, + &Screen::drawShapeSkipScaleDownwind, + &Screen::drawShapeSkipScaleUpwind, + &Screen::drawShapeSkipScaleDownwind + }; + + static const DsLineFunc dsLineFunc[] = { + &Screen::drawShapeProcessLineNoScaleUpwind, + &Screen::drawShapeProcessLineNoScaleDownwind, + &Screen::drawShapeProcessLineNoScaleUpwind, + &Screen::drawShapeProcessLineNoScaleDownwind, + &Screen::drawShapeProcessLineScaleUpwind, + &Screen::drawShapeProcessLineScaleDownwind, + &Screen::drawShapeProcessLineScaleUpwind, + &Screen::drawShapeProcessLineScaleDownwind + }; + + static const DsPlotFunc dsPlotFunc[] = { + &Screen::drawShapePlotType0, // used by Kyra 1 + 2 + &Screen::drawShapePlotType1, // used by Kyra 3 + 0, + &Screen::drawShapePlotType3_7, // used by Kyra 3 (shadow) + &Screen::drawShapePlotType4, // used by Kyra 1, 2 + 3 + &Screen::drawShapePlotType5, // used by Kyra 1 + &Screen::drawShapePlotType6, // used by Kyra 1 (invisibility) + &Screen::drawShapePlotType3_7, // used by Kyra 1 (invisibility) + &Screen::drawShapePlotType8, // used by Kyra 2 + &Screen::drawShapePlotType9, // used by Kyra 1 + 3 + 0, + &Screen::drawShapePlotType11_15, // used by Kyra 1 (invisibility) + Kyra 3 (shadow) + &Screen::drawShapePlotType12, // used by Kyra 2 + &Screen::drawShapePlotType13, // used by Kyra 1 + &Screen::drawShapePlotType14, // used by Kyra 1 (invisibility) + &Screen::drawShapePlotType11_15, // used by Kyra 1 (invisibility) + &Screen::drawShapePlotType16, // used by LoL PC-98/16 Colors (teleporters), + 0, 0, 0, + &Screen::drawShapePlotType20, // used by LoL (heal spell effect) + &Screen::drawShapePlotType21, // used by LoL (white tower spirits) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, + &Screen::drawShapePlotType33, // used by LoL (blood spots on the floor) + 0, 0, 0, + &Screen::drawShapePlotType37, // used by LoL (monsters) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + &Screen::drawShapePlotType48, // used by LoL (slime spots on the floor) + 0, 0, 0, + &Screen::drawShapePlotType52, // used by LoL (projectiles) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0 + }; + + int scaleCounterV = 0; + + const int drawFunc = flags & 0x0F; + _dsProcessMargin = dsMarginFunc[drawFunc]; + _dsScaleSkip = dsSkipFunc[drawFunc]; + _dsProcessLine = dsLineFunc[drawFunc]; + + const int ppc = (flags >> 8) & 0x3F; + _dsPlot = dsPlotFunc[ppc]; + DsPlotFunc dsPlot2 = dsPlotFunc[ppc], dsPlot3 = dsPlotFunc[ppc]; + if (flags & 0x800) + dsPlot3 = dsPlotFunc[((flags >> 8) & 0xF7) & 0x3F]; + + if (!_dsPlot || !dsPlot2 || !dsPlot3) { + if (!dsPlot2) + warning("Missing drawShape plotting method type %d", ppc); + if (dsPlot3 != dsPlot2 && !dsPlot3) + warning("Missing drawShape plotting method type %d", (((flags >> 8) & 0xF7) & 0x3F)); + return; + } + + int curY = y; + const uint8 *src = shapeData; + uint8 *dst = _dsDstPage = getPagePtr(pageNum); + + const ScreenDim *dsDim = getScreenDim(sd); + dst += (dsDim->sx << 3); + + if (!(flags & 0x10)) + x -= (dsDim->sx << 3); + + int x2 = (dsDim->w << 3); + int y1 = dsDim->sy; + if (flags & 0x10) + y += y1; + + int y2 = y1 + dsDim->h; + + uint16 shapeFlags = READ_LE_UINT16(src); src += 2; + + int shapeHeight = *src++; + uint16 shapeWidth = READ_LE_UINT16(src); src += 2; + + int shpWidthScaled1 = shapeWidth; + int shpWidthScaled2 = shapeWidth; + + if (flags & DSF_SCALE) { + shapeHeight = (shapeHeight * _dsScaleH) >> 8; + shpWidthScaled1 = shpWidthScaled2 = (shapeWidth * _dsScaleW) >> 8; + + if (!shapeHeight || !shpWidthScaled1) + return; + } + + if (flags & DSF_CENTER) { + x -= (shpWidthScaled1 >> 1); + y -= (shapeHeight >> 1); + } + + src += 3; + + uint16 frameSize = READ_LE_UINT16(src); src += 2; + + int colorTableColors = ((_vm->game() != GI_KYRA1) && (shapeFlags & 4)) ? *src++ : 16; + + if (!(flags & 0x8000) && (shapeFlags & 1)) + _dsColorTable = src; + + if (flags & 0x400) + src += colorTableColors; + + if (!(shapeFlags & 2)) { + decodeFrame4(src, _animBlockPtr, frameSize); + src = _animBlockPtr; + } + + int t = (flags & 2) ? y2 - y - shapeHeight : y - y1; + + if (t < 0) { + shapeHeight += t; + if (shapeHeight <= 0) { + return; + } + + t *= -1; + const uint8 *srcBackUp = 0; + + do { + _dsOffscreenScaleVal1 = 0; + srcBackUp = src; + _dsTmpWidth = shapeWidth; + + int cnt = shapeWidth; + (this->*_dsScaleSkip)(dst, src, cnt); + + scaleCounterV += _dsScaleH; + + if (scaleCounterV & 0xFF00) { + uint8 r = scaleCounterV >> 8; + scaleCounterV &= 0xFF; + t -= r; + } + } while (!(scaleCounterV & 0xFF00) && (t > 0)); + + if (t < 0) { + src = srcBackUp; + scaleCounterV += (-t << 8); + } + + if (!(flags & 2)) + y = y1; + } + + t = (flags & 2) ? y + shapeHeight - y1 : y2 - y; + if (t <= 0) + return; + + if (t < shapeHeight) { + shapeHeight = t; + if (flags & 2) + y = y1; + } + + _dsOffscreenLeft = 0; + if (x < 0) { + shpWidthScaled1 += x; + _dsOffscreenLeft = -x; + if (_dsOffscreenLeft >= shpWidthScaled2) + return; + x = 0; + } + + _dsOffscreenRight = 0; + t = x2 - x; + + if (t <= 0) + return; + + if (t < shpWidthScaled1) { + shpWidthScaled1 = t; + _dsOffscreenRight = shpWidthScaled2 - _dsOffscreenLeft - shpWidthScaled1; + } + + int dsPitch = 320; + int ty = y; + + if (flags & 2) { + dsPitch *= -1; + ty = ty - 1 + shapeHeight; + } + + if (flags & DSF_X_FLIPPED) { + SWAP(_dsOffscreenLeft, _dsOffscreenRight); + dst += (shpWidthScaled1 - 1); + } + + dst += (320 * ty + x); + + if (flags & DSF_SCALE) { + _dsOffscreenRight = 0; + _dsOffscreenScaleVal2 = _dsOffscreenLeft; + _dsOffscreenLeft <<= 8; + _dsOffscreenScaleVal1 = (_dsOffscreenLeft % _dsScaleW) * -1; + _dsOffscreenLeft /= _dsScaleW; + } + + if (shapeHeight <= 0 || shpWidthScaled1 <= 0) + return; + + if (pageNum == 0 || pageNum == 1) + addDirtyRect(x, y, shpWidthScaled1, shapeHeight); + clearOverlayRect(pageNum, x, y, shpWidthScaled1, shapeHeight); + + uint8 *d = dst; + + bool normalPlot = true; + while (true) { + while (!(scaleCounterV & 0xFF00)) { + scaleCounterV += _dsScaleH; + if (!(scaleCounterV & 0xFF00)) { + _dsTmpWidth = shapeWidth; + int cnt = shapeWidth; + (this->*_dsScaleSkip)(d, src, cnt); + } + } + + const uint8 *b_src = src; + + do { + src = b_src; + _dsTmpWidth = shapeWidth; + int cnt = _dsOffscreenLeft; + int scaleState = (this->*_dsProcessMargin)(d, src, cnt); + + if (_dsTmpWidth) { + cnt += shpWidthScaled1; + if (cnt > 0) { + if (flags & 0x800) + normalPlot = (curY > _maskMinY && curY < _maskMaxY); + _dsPlot = normalPlot ? dsPlot2 : dsPlot3; + (this->*_dsProcessLine)(d, src, cnt, scaleState); + } + cnt += _dsOffscreenRight; + if (cnt) + (this->*_dsScaleSkip)(d, src, cnt); + } + dst += dsPitch; + d = dst; + ++curY; + + if (!--shapeHeight) + return; + + scaleCounterV -= 0x100; + } while (scaleCounterV & 0xFF00); + } +} + +int Screen::drawShapeMarginNoScaleUpwind(uint8 *&dst, const uint8 *&src, int &cnt) { + while (cnt-- > 0) { + if (*src++) + continue; + cnt = cnt + 1 - (*src++); + } + + cnt++; + dst -= cnt; + return 0; +} + +int Screen::drawShapeMarginNoScaleDownwind(uint8 *&dst, const uint8 *&src, int &cnt) { + while (cnt-- > 0) { + if (*src++) + continue; + cnt = cnt + 1 - (*src++); + } + + cnt++; + dst += cnt; + return 0; +} + +int Screen::drawShapeMarginScaleUpwind(uint8 *&dst, const uint8 *&src, int &cnt) { + _dsTmpWidth -= cnt; + + while (cnt > 0) { + --cnt; + if (*src++) + continue; + + cnt = cnt + 1 - (*src++); + } + + if (!cnt) + return _dsOffscreenScaleVal1; + + _dsTmpWidth += cnt; + + int i = (_dsOffscreenLeft - cnt) * _dsScaleW; + int res = i & 0xFF; + i >>= 8; + i -= _dsOffscreenScaleVal2; + dst += i; + cnt = -i; + + return res; +} + +int Screen::drawShapeMarginScaleDownwind(uint8 *&dst, const uint8 *&src, int &cnt) { + _dsTmpWidth -= cnt; + + while (cnt > 0) { + --cnt; + if (*src++) + continue; + + cnt = cnt + 1 - (*src++); + } + + if (!cnt) + return _dsOffscreenScaleVal1; + + _dsTmpWidth += cnt; + + int i = (_dsOffscreenLeft - cnt) * _dsScaleW; + int res = i & 0xFF; + i >>= 8; + i -= _dsOffscreenScaleVal2; + dst -= i; + cnt = -i; + + return res; +} + +int Screen::drawShapeSkipScaleUpwind(uint8 *&dst, const uint8 *&src, int &cnt) { + cnt = _dsTmpWidth; + + if (cnt <= 0) + return 0; + + do { + --cnt; + if (*src++) + continue; + cnt = cnt + 1 - (*src++); + } while (cnt > 0); + + return 0; +} + +int Screen::drawShapeSkipScaleDownwind(uint8 *&dst, const uint8 *&src, int &cnt) { + cnt = _dsTmpWidth; + bool found = false; + + if (cnt == 0) + return 0; + + do { + --cnt; + if (*src++) + continue; + found = true; + cnt = cnt + 1 - (*src++); + } while (cnt > 0); + + return found ? 0 : _dsOffscreenScaleVal1; +} + +void Screen::drawShapeProcessLineNoScaleUpwind(uint8 *&dst, const uint8 *&src, int &cnt, int16) { + do { + uint8 c = *src++; + if (c) { + uint8 *d = dst++; + (this->*_dsPlot)(d, c); + cnt--; + } else { + c = *src++; + dst += c; + cnt -= c; + } + } while (cnt > 0); +} + +void Screen::drawShapeProcessLineNoScaleDownwind(uint8 *&dst, const uint8 *&src, int &cnt, int16) { + do { + uint8 c = *src++; + if (c) { + uint8 *d = dst--; + (this->*_dsPlot)(d, c); + cnt--; + } else { + c = *src++; + dst -= c; + cnt -= c; + } + } while (cnt > 0); +} + +void Screen::drawShapeProcessLineScaleUpwind(uint8 *&dst, const uint8 *&src, int &cnt, int16 scaleState) { + int c = 0; + + do { + if ((scaleState & 0x8000) || !(scaleState & 0xFF00)) { + c = *src++; + _dsTmpWidth--; + if (c) { + scaleState += _dsScaleW; + } else { + _dsTmpWidth++; + c = *src++; + _dsTmpWidth -= c; + int r = c * _dsScaleW + scaleState; + dst += (r >> 8); + cnt -= (r >> 8); + scaleState = r & 0xFF; + } + } else if (scaleState) { + (this->*_dsPlot)(dst++, c); + scaleState -= 0x100; + cnt--; + } + } while (cnt > 0); + + cnt = -1; +} + +void Screen::drawShapeProcessLineScaleDownwind(uint8 *&dst, const uint8 *&src, int &cnt, int16 scaleState) { + int c = 0; + + do { + if ((scaleState & 0x8000) || !(scaleState & 0xFF00)) { + c = *src++; + _dsTmpWidth--; + if (c) { + scaleState += _dsScaleW; + } else { + _dsTmpWidth++; + c = *src++; + _dsTmpWidth -= c; + int r = c * _dsScaleW + scaleState; + dst -= (r >> 8); + cnt -= (r >> 8); + scaleState = r & 0xFF; + } + } else { + (this->*_dsPlot)(dst--, c); + scaleState -= 0x100; + cnt--; + } + } while (cnt > 0); + + cnt = -1; +} + +void Screen::drawShapePlotType0(uint8 *dst, uint8 cmd) { + *dst = cmd; +} + +void Screen::drawShapePlotType1(uint8 *dst, uint8 cmd) { + for (int i = 0; i < _dsShapeFadingLevel; ++i) + cmd = _dsShapeFadingTable[cmd]; + + if (cmd) + *dst = cmd; +} + +void Screen::drawShapePlotType3_7(uint8 *dst, uint8 cmd) { + cmd = *dst; + for (int i = 0; i < _dsShapeFadingLevel; ++i) + cmd = _dsShapeFadingTable[cmd]; + + if (cmd) + *dst = cmd; +} + +void Screen::drawShapePlotType4(uint8 *dst, uint8 cmd) { + *dst = _dsColorTable[cmd]; +} + +void Screen::drawShapePlotType5(uint8 *dst, uint8 cmd) { + cmd = _dsColorTable[cmd]; + for (int i = 0; i < _dsShapeFadingLevel; ++i) + cmd = _dsShapeFadingTable[cmd]; + + if (cmd) + *dst = cmd; +} + +void Screen::drawShapePlotType6(uint8 *dst, uint8 cmd) { + int t = _drawShapeVar4 + _drawShapeVar5; + if (t & 0xFF00) { + cmd = dst[_drawShapeVar3]; + t &= 0xFF; + } else { + cmd = _dsColorTable[cmd]; + } + + _drawShapeVar4 = t; + *dst = cmd; +} + +void Screen::drawShapePlotType8(uint8 *dst, uint8 cmd) { + uint32 relOffs = dst - _dsDstPage; + int t = (_shapePages[0][relOffs] & 0x7F) & 0x87; + if (_dsDrawLayer < t) + cmd = _shapePages[1][relOffs]; + + *dst = cmd; +} + +void Screen::drawShapePlotType9(uint8 *dst, uint8 cmd) { + uint32 relOffs = dst - _dsDstPage; + int t = (_shapePages[0][relOffs] & 0x7F) & 0x87; + if (_dsDrawLayer < t) { + cmd = _shapePages[1][relOffs]; + } else { + for (int i = 0; i < _dsShapeFadingLevel; ++i) + cmd = _dsShapeFadingTable[cmd]; + } + + if (cmd) + *dst = cmd; +} + +void Screen::drawShapePlotType11_15(uint8 *dst, uint8 cmd) { + uint32 relOffs = dst - _dsDstPage; + int t = (_shapePages[0][relOffs] & 0x7F) & 0x87; + + if (_dsDrawLayer < t) { + cmd = _shapePages[1][relOffs]; + } else { + cmd = *dst; + for (int i = 0; i < _dsShapeFadingLevel; ++i) + cmd = _dsShapeFadingTable[cmd]; + } + + if (cmd) + *dst = cmd; +} + +void Screen::drawShapePlotType12(uint8 *dst, uint8 cmd) { + uint32 relOffs = dst - _dsDstPage; + int t = (_shapePages[0][relOffs] & 0x7F) & 0x87; + if (_dsDrawLayer < t) { + cmd = _shapePages[1][relOffs]; + } else { + cmd = _dsColorTable[cmd]; + } + + *dst = cmd; +} + +void Screen::drawShapePlotType13(uint8 *dst, uint8 cmd) { + uint32 relOffs = dst - _dsDstPage; + int t = (_shapePages[0][relOffs] & 0x7F) & 0x87; + if (_dsDrawLayer < t) { + cmd = _shapePages[1][relOffs]; + } else { + cmd = _dsColorTable[cmd]; + for (int i = 0; i < _dsShapeFadingLevel; ++i) + cmd = _dsShapeFadingTable[cmd]; + } + + if (cmd) + *dst = cmd; +} + +void Screen::drawShapePlotType14(uint8 *dst, uint8 cmd) { + uint32 relOffs = dst - _dsDstPage; + int t = (_shapePages[0][relOffs] & 0x7F) & 0x87; + if (_dsDrawLayer < t) { + cmd = _shapePages[1][relOffs]; + } else { + t = _drawShapeVar4 + _drawShapeVar5; + if (t & 0xFF00) { + cmd = dst[_drawShapeVar3]; + t &= 0xFF; + } else { + cmd = _dsColorTable[cmd]; + } + } + + _drawShapeVar4 = t; + *dst = cmd; +} + +void Screen::drawShapePlotType16(uint8 *dst, uint8 cmd) { + uint8 tOffs = _dsTransparencyTable1[cmd]; + if (!(tOffs & 0x80)) + cmd = _dsTransparencyTable2[tOffs << 8 | *dst]; + *dst = cmd; +} + +void Screen::drawShapePlotType20(uint8 *dst, uint8 cmd) { + cmd = _dsColorTable[cmd]; + uint8 tOffs = _dsTransparencyTable1[cmd]; + if (!(tOffs & 0x80)) + cmd = _dsTransparencyTable2[tOffs << 8 | *dst]; + + *dst = cmd; +} + +void Screen::drawShapePlotType21(uint8 *dst, uint8 cmd) { + cmd = _dsColorTable[cmd]; + uint8 tOffs = _dsTransparencyTable1[cmd]; + if (!(tOffs & 0x80)) + cmd = _dsTransparencyTable2[tOffs << 8 | *dst]; + + for (int i = 0; i < _dsShapeFadingLevel; ++i) + cmd = _dsShapeFadingTable[cmd]; + + if (cmd) + *dst = cmd; +} + +void Screen::drawShapePlotType33(uint8 *dst, uint8 cmd) { + if (cmd == 255) { + *dst = _dsBackgroundFadingTable[*dst]; + } else { + for (int i = 0; i < _dsShapeFadingLevel; ++i) + cmd = _dsShapeFadingTable[cmd]; + if (cmd) + *dst = cmd; + } +} + +void Screen::drawShapePlotType37(uint8 *dst, uint8 cmd) { + cmd = _dsColorTable[cmd]; + + if (cmd == 255) { + cmd = _dsBackgroundFadingTable[*dst]; + } else { + for (int i = 0; i < _dsShapeFadingLevel; ++i) + cmd = _dsShapeFadingTable[cmd]; + } + + if (cmd) + *dst = cmd; +} + +void Screen::drawShapePlotType48(uint8 *dst, uint8 cmd) { + uint8 offs = _dsTransparencyTable1[cmd]; + if (!(offs & 0x80)) + cmd = _dsTransparencyTable2[(offs << 8) | *dst]; + *dst = cmd; +} + +void Screen::drawShapePlotType52(uint8 *dst, uint8 cmd) { + cmd = _dsColorTable[cmd]; + uint8 offs = _dsTransparencyTable1[cmd]; + + if (!(offs & 0x80)) + cmd = _dsTransparencyTable2[(offs << 8) | *dst]; + + *dst = cmd; +} + +void Screen::decodeFrame1(const uint8 *src, uint8 *dst, uint32 size) { + const uint8 *dstEnd = dst + size; + + struct Pattern { + const uint8 *pos; + uint16 len; + }; + + Pattern *patterns = new Pattern[3840]; + uint16 numPatterns = 0; + uint8 nib = 0; + + uint16 code = decodeEGAGetCode(src, nib); + uint8 last = code & 0xFF; + + uint8 *dstPrev = dst; + uint16 count = 1; + uint16 countPrev = 1; + + *dst++ = last; + + while (dst < dstEnd) { + code = decodeEGAGetCode(src, nib); + uint8 cmd = code >> 8; + + if (cmd--) { + code = (cmd << 8) | (code & 0xFF); + uint8 *tmpDst = dst; + + if (code < numPatterns) { + const uint8 *tmpSrc = patterns[code].pos; + countPrev = patterns[code].len; + last = *tmpSrc; + for (int i = 0; i < countPrev; i++) + *dst++ = *tmpSrc++; + + } else { + const uint8 *tmpSrc = dstPrev; + count = countPrev; + for (int i = 0; i < countPrev; i++) + *dst++ = *tmpSrc++; + *dst++ = last; + countPrev++; + } + + if (numPatterns < 3840) { + patterns[numPatterns].pos = dstPrev; + patterns[numPatterns++].len = ++count; + } + + dstPrev = tmpDst; + count = countPrev; + + } else { + *dst++ = last = (code & 0xFF); + + if (numPatterns < 3840) { + patterns[numPatterns].pos = dstPrev; + patterns[numPatterns++].len = ++count; + } + + dstPrev = dst - 1; + count = 1; + countPrev = 1; + } + } + delete[] patterns; +} + +uint16 Screen::decodeEGAGetCode(const uint8 *&pos, uint8 &nib) { + uint16 res = READ_BE_UINT16(pos++); + if ((++nib) & 1) { + res >>= 4; + } else { + pos++; + res &= 0xFFF; + } + return res; +} + +void Screen::decodeFrame3(const uint8 *src, uint8 *dst, uint32 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; + } + } +} + +uint Screen::decodeFrame4(const uint8 *src, uint8 *dst, uint32 dstSize) { + uint8 *dstOrig = dst; + uint8 *dstEnd = dst + dstSize; + while (1) { + int count = dstEnd - dst; + if (count == 0) + break; + + uint8 code = *src++; + if (!(code & 0x80)) { // 8th bit isn't set + int len = MIN(count, (code >> 4) + 3); //upper half of code is the length + int offs = ((code & 0xF) << 8) | *src++; //lower half of code as byte 2 of offset. + const uint8 *dstOffs = dst - offs; + while (len--) + *dst++ = *dstOffs++; + } else if (code & 0x40) { // 7th bit is set + 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) { // not just the 8th bit set. + //Copy some bytes from source to dest. + int len = MIN(count, code & 0x3F); + while (len--) + *dst++ = *src++; + } else { + break; + } + } + return dst - dstOrig; +} + +void Screen::decodeFrameDelta(uint8 *dst, const uint8 *src, bool noXor) { + if (noXor) + wrapped_decodeFrameDelta<true>(dst, src); + else + wrapped_decodeFrameDelta<false>(dst, src); +} + +template<bool noXor> +void Screen::wrapped_decodeFrameDelta(uint8 *dst, const uint8 *src) { + while (1) { + uint8 code = *src++; + if (code == 0) { + uint8 len = *src++; + code = *src++; + while (len--) { + if (noXor) + *dst++ = code; + else + *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--) { + if (noXor) + *dst++ = code; + else + *dst++ ^= code; + } + } else { + while (subcode--) { + if (noXor) + *dst++ = *src++; + else + *dst++ ^= *src++; + } + } + } else { + dst += subcode; + } + } + } else { + while (code--) { + if (noXor) + *dst++ = *src++; + else + *dst++ ^= *src++; + } + } + } +} + +void Screen::decodeFrameDeltaPage(uint8 *dst, const uint8 *src, int pitch, bool noXor) { + if (noXor) + wrapped_decodeFrameDeltaPage<true>(dst, src, pitch); + else + wrapped_decodeFrameDeltaPage<false>(dst, src, pitch); +} + +void Screen::convertAmigaGfx(uint8 *data, int w, int h, int depth, bool wsa, int bytesPerPlane) { + const int planeWidth = (bytesPerPlane == -1) ? (w + 7) / 8 : bytesPerPlane; + const int planeSize = planeWidth * h; + const uint imageSize = planeSize * depth; + + // Our static buffer which holds the plane data. We need this + // because the "data" pointer is both source and destination pointer. + // The buffer has enough space to fit the AMIGA MSC files, which are + // the biggest graphics files found in the AMIGA version. + static uint8 temp[40320]; + assert(imageSize <= sizeof(temp)); + + // WSA files store their graphics data in a little different format, than + // the usual AMIGA graphics format used in BitMaps. Thus we need to do + // some special handling for them here. Means we convert them into + // the usual format. + // + // TODO: We might think of moving this conversion into the WSAMovieAmiga + // class. + if (wsa) { + const byte *src = data; + for (int y = 0; y < h; ++y) { + for (int x = 0; x < planeWidth; ++x) + for (int i = 0; i < depth; ++i) + temp[y * planeWidth + x + planeSize * i] = *src++; + } + } else { + memcpy(temp, data, imageSize); + } + + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + const int bytePos = x / 8 + y * planeWidth; + const int bitPos = 7 - (x & 7); // x & 7 == x % 8 + + byte col = 0; + + for (int i = 0; i < depth; ++i) + col |= ((temp[bytePos + planeSize * i] >> bitPos) & 1) << i; + + *data++ = col; + } + } +} + +void Screen::convertAmigaMsc(uint8 *data) { + // MSC files are always 320x144, thus we can safely assume + // this to be correct. Also they contain 7 planes instead + // of the normal 5 planes, which is used in 32 color mode. + // The need for 7 planes can be explained, because the MSC + // files have 6 bits for the layer number (bits 1 to 6) + // and one bit for the "blocked" flag (bit 0), and every + // plane contains one bit per pixel. + convertAmigaGfx(data, 320, 144, 7); + + // We need to do some post conversion, since + // the AMIGA MSC format is different from the DOS + // one we use internally for our code.That is even + // after converting it from the AMIGA plane based + // approach to one byte per pixel approach. + for (int i = 0; i < 320 * 144; ++i) { + // The lowest bit indicates, whether the position + // is walkable or not. If the bit is set, the + // position is walkable, elsewise it is blocked. + if (data[i] & 1) + data[i] &= 0xFE; + else + data[i] |= 0x80; + + // The graphics layer for the pixel is saved + // in the following format: + // The highest bit set indicates the number of + // the graphics layer. We count the first + // bit as 0 here, thus we need to add one, + // to get the correct number. + // + // Funnily since the first bit (bit 0) is + // resevered for testing whether the position + // is walkable or not, there is no possibility + // for layer 1 to be present. + int layer = 0; + for (int k = 0; k < 7; ++k) + if (data[i] & (1 << k)) + layer = k + 1; + + data[i] &= 0x80; + data[i] |= layer; + } +} + +template<bool noXor> +void Screen::wrapped_decodeFrameDeltaPage(uint8 *dst, const uint8 *src, int pitch) { + 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) { + 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->gameFlags().useAltShapeHeader) + shapeSize += 12; + else + shapeSize += 10; + + if (flags & 1) + shapeSize += 16; + + uint8 table[274]; + int tableIndex = 0; + + uint8 *newShape = 0; + newShape = new uint8[shapeSize+16]; + assert(newShape); + + byte *dst = newShape; + + if (_vm->gameFlags().useAltShapeHeader) + 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(table)); + 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->gameFlags().useAltShapeHeader) + dst += 2; + + flags = READ_LE_UINT16(dst); + flags |= 2; + WRITE_LE_UINT16(dst, flags); + } else { + src = newShape; + if (_vm->gameFlags().useAltShapeHeader) + 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; + uint8 *newShape2 = new uint8[shapeSize]; + assert(newShape2); + memcpy(newShape2, newShape, shapeSize); + delete[] newShape; + newShape = newShape2; + } else { + dst = shapePtrBackUp; + src = _animBlockPtr; + memcpy(dst, src, shapeSize2); + dst = newShape; + if (_vm->gameFlags().useAltShapeHeader) + dst += 2; + flags = READ_LE_UINT16(dst); + flags |= 2; + WRITE_LE_UINT16(dst, flags); + } + } + } + + dst = newShape; + if (_vm->gameFlags().useAltShapeHeader) + dst += 2; + WRITE_LE_UINT16((dst + 6), shapeSize); + + if (flags & 1) { + dst = newShape + 10; + if (_vm->gameFlags().useAltShapeHeader) + dst += 2; + src = &table[0x100]; + memcpy(dst, src, sizeof(uint8)*16); + } + + return newShape; +} + +int16 Screen::encodeShapeAndCalculateSize(uint8 *from, uint8 *to, int 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++ = curPixel; + 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-1) == curPixel) { + if (*(from+size-1) != *(to+size-2)) + continue; + + byte *fromBackUp = from; + byte *toBackUp = to; + --to; + const int checkSize = fromPtrEnd - from; + for (int i = 0; i < checkSize; ++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 (diffSize <= 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); +} + +uint16 Screen::shade16bitColor(uint16 col) { + uint8 r = (col & 0x1f); + uint8 g = (col & 0x3E0) >> 5; + uint8 b = (col & 0x7C00) >> 10; + + r = (r > _16bitShadingLevel) ? r - _16bitShadingLevel : 0; + g = (g > _16bitShadingLevel) ? g - _16bitShadingLevel : 0; + b = (b > _16bitShadingLevel) ? b - _16bitShadingLevel : 0; + + return (b << 10) | (g << 5) | r; +} + +void Screen::hideMouse() { + ++_mouseLockCount; + CursorMan.showMouse(false); +} + +void Screen::showMouse() { + if (_mouseLockCount == 1) { + CursorMan.showMouse(true); + + // We need to call OSystem::updateScreen here, else the mouse cursor + // will only be visible on mouse movment. + _system->updateScreen(); + } + + if (_mouseLockCount > 0) + _mouseLockCount--; +} + + +bool Screen::isMouseVisible() const { + return _mouseLockCount == 0; +} + +void Screen::setShapePages(int page1, int page2, int minY, int maxY) { + _shapePages[0] = _pagePtrs[page1]; + _shapePages[1] = _pagePtrs[page2]; + _maskMinY = minY; + _maskMaxY = maxY; +} + +void Screen::setMouseCursor(int x, int y, const byte *shape) { + if (!shape) + return; + + if (_vm->gameFlags().useAltShapeHeader) + shape += 2; + + int mouseHeight = *(shape + 2); + int mouseWidth = (READ_LE_UINT16(shape + 3)) + 2; + + if (_vm->gameFlags().useAltShapeHeader) + shape -= 2; + + if (_vm->gameFlags().useHiRes) { + x <<= 1; + y <<= 1; + mouseWidth <<= 1; + mouseHeight <<= 1; + } + + uint8 *cursor = new uint8[mouseHeight * mouseWidth]; + fillRect(0, 0, mouseWidth, mouseHeight, _cursorColorKey, 8); + drawShape(8, shape, 0, 0, 0, 0); + + int xOffset = 0; + + if (_vm->gameFlags().useHiRes) { + xOffset = mouseWidth; + scale2x(getPagePtr(8) + mouseWidth, SCREEN_W, getPagePtr(8), SCREEN_W, mouseWidth, mouseHeight); + postProcessCursor(getPagePtr(8) + mouseWidth, mouseWidth, mouseHeight, SCREEN_W); + } else { + postProcessCursor(getPagePtr(8), mouseWidth, mouseHeight, SCREEN_W); + } + + CursorMan.showMouse(false); + copyRegionToBuffer(8, xOffset, 0, mouseWidth, mouseHeight, cursor); + CursorMan.replaceCursor(cursor, mouseWidth, mouseHeight, x, y, _cursorColorKey); + if (isMouseVisible()) + CursorMan.showMouse(true); + delete[] cursor; + + // makes sure that the cursor is drawn + // we do not use Screen::updateScreen here + // so we can be sure that changes to page 0 + // are NOT updated on the real screen here + _system->updateScreen(); +} + +Palette &Screen::getPalette(int num) { + assert(num >= 0 && (uint)num < _palettes.size()); + return *_palettes[num]; +} + +void Screen::copyPalette(const int dst, const int src) { + getPalette(dst).copy(getPalette(src)); +} + +byte Screen::getShapeFlag1(int x, int 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) { + uint8 color = _shapePages[0][y * SCREEN_W + x]; + color &= 0x7F; + color &= 0x87; + return color; +} + +int Screen::getDrawLayer(int x, int 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) { + 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; +} + + +int Screen::setNewShapeHeight(uint8 *shape, int height) { + if (_vm->gameFlags().useAltShapeHeader) + shape += 2; + + int oldHeight = shape[2]; + shape[2] = height; + return oldHeight; +} + +int Screen::resetShapeHeight(uint8 *shape) { + if (_vm->gameFlags().useAltShapeHeader) + shape += 2; + + int oldHeight = shape[2]; + shape[2] = shape[5]; + return oldHeight; +} + +void Screen::blockInRegion(int x, int y, int width, int 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) { + 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::shakeScreen(int times) { + while (times--) { + // seems to be 1 line (320 pixels) offset in the original + // 4 looks more like dosbox though, maybe check this again + _system->setShakePos(4); + _system->updateScreen(); + _system->setShakePos(0); + _system->updateScreen(); + } +} + +void Screen::loadBitmap(const char *filename, int tempPage, int dstPage, Palette *pal, bool skip) { + uint32 fileSize; + uint8 *srcData = _vm->resource()->fileData(filename, &fileSize); + + if (!srcData) { + warning("couldn't load bitmap: '%s'", filename); + return; + } + + if (skip) + srcData += 4; + + const char *ext = filename + strlen(filename) - 3; + uint8 compType = srcData[2]; + uint32 imgSize = (_vm->game() == GI_KYRA2 && !scumm_stricmp(ext, "CMP")) ? READ_LE_UINT16(srcData) : READ_LE_UINT32(srcData + 4); + uint16 palSize = READ_LE_UINT16(srcData + 8); + + if (pal && palSize) + loadPalette(srcData + 10, *pal, palSize); + + uint8 *srcPtr = srcData + 10 + palSize; + uint8 *dstData = getPagePtr(dstPage); + memset(dstData, 0, _screenPageSize); + if (dstPage == 0 || tempPage == 0) + _forceFullUpdate = true; + + switch (compType) { + case 0: + memcpy(dstData, srcPtr, imgSize); + break; + case 1: + Screen::decodeFrame1(srcPtr, dstData, 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); + } + + if (_isAmiga) { + if (!scumm_stricmp(ext, "MSC")) + Screen::convertAmigaMsc(dstData); + else + Screen::convertAmigaGfx(dstData, 320, 200); + } + + if (skip) + srcData -= 4; + + delete[] srcData; +} + +bool Screen::loadPalette(const char *filename, Palette &pal) { + if (_renderMode == Common::kRenderCGA) + return true; + + Common::SeekableReadStream *stream = _vm->resource()->createReadStream(filename); + + if (!stream) + return false; + + debugC(3, kDebugLevelScreen, "Screen::loadPalette('%s', %p)", filename, (const void *)&pal); + + const int maxCols = _16bitPalette ? 256 : pal.getNumColors(); + int numCols = 0; + + if (_isAmiga) { + numCols = stream->size() / Palette::kAmigaBytesPerColor; + pal.loadAmigaPalette(*stream, 0, MIN(maxCols, numCols)); + } else if (_vm->gameFlags().platform == Common::kPlatformPC98 && _use16ColorMode) { + numCols = stream->size() / Palette::kPC98BytesPerColor; + pal.loadPC98Palette(*stream, 0, MIN(maxCols, numCols)); + } else if (_renderMode == Common::kRenderEGA) { + numCols = stream->size(); + // There aren't any 16 color EGA palette files. So this shouldn't ever get triggered. + assert (numCols != 16); + numCols /= Palette::kVGABytesPerColor; + pal.loadVGAPalette(*stream, 0, numCols); + } else { + if (_bytesPerPixel == 2) { + numCols = stream->size() / 2; + pal.loadHiColorPalette(*stream, 0, numCols); + } else if (!_16bitPalette) { + numCols = stream->size() / Palette::kVGABytesPerColor; + pal.loadVGAPalette(*stream, 0, MIN(maxCols, numCols)); + } else { + error("Screen::loadPalette(): Failed to load file '%s' with invalid size %d in HiColor mode", filename, stream->size()); + } + } + + if (numCols > maxCols) + warning("Palette file '%s' includes %d colors, but the target palette only support %d colors", filename, numCols, maxCols); + + delete stream; + return true; +} + +bool Screen::loadPaletteTable(const char *filename, int firstPalette) { + Common::SeekableReadStream *stream = _vm->resource()->createReadStream(filename); + + if (!stream) + return false; + + debugC(3, kDebugLevelScreen, "Screen::loadPaletteTable('%s', %d)", filename, firstPalette); + + if (_isAmiga) { + const int numColors = getPalette(firstPalette).getNumColors(); + const int palSize = getPalette(firstPalette).getNumColors() * Palette::kAmigaBytesPerColor; + const int numPals = stream->size() / palSize; + + for (int i = 0; i < numPals; ++i) + getPalette(i + firstPalette).loadAmigaPalette(*stream, 0, numColors); + } else { + const int numColors = getPalette(firstPalette).getNumColors(); + const int palSize = getPalette(firstPalette).getNumColors() * Palette::kVGABytesPerColor; + const int numPals = stream->size() / palSize; + + for (int i = 0; i < numPals; ++i) + getPalette(i + firstPalette).loadVGAPalette(*stream, 0, numColors); + } + + delete stream; + return true; +} + +void Screen::loadPalette(const byte *data, Palette &pal, int bytes) { + Common::MemoryReadStream stream(data, bytes, DisposeAfterUse::NO); + + if (_isAmiga) + pal.loadAmigaPalette(stream, 0, stream.size() / Palette::kAmigaBytesPerColor); + else if (_vm->gameFlags().platform == Common::kPlatformPC98 && _use16ColorMode) + pal.loadPC98Palette(stream, 0, stream.size() / Palette::kPC98BytesPerColor); + else if (_renderMode == Common::kRenderEGA) { + // EOB II checks the number of palette bytes to distinguish between real EGA palettes + // and normal palettes (which are used to generate a color map). + if (stream.size() == 16) + pal.loadEGAPalette(stream, 0, stream.size()); + else + pal.loadVGAPalette(stream, 0, stream.size() / Palette::kVGABytesPerColor); + } else + pal.loadVGAPalette(stream, 0, stream.size() / Palette::kVGABytesPerColor); +} + +// dirty rect handling + +void Screen::addDirtyRect(int x, int y, int w, int h) { + if (_dirtyRects.size() >= kMaxDirtyRects || _forceFullUpdate) { + _forceFullUpdate = true; + return; + } + + Common::Rect r(x, y, x + w, y + h); + + // Clip rectangle + r.clip(SCREEN_W, SCREEN_H); + + // If it is empty after clipping, we are done + if (r.isEmpty()) + return; + + // Check if the new rectangle is contained within another in the list + Common::List<Common::Rect>::iterator it; + for (it = _dirtyRects.begin(); it != _dirtyRects.end(); ) { + // If we find a rectangle which fully contains the new one, + // we can abort the search. + if (it->contains(r)) + return; + + // Conversely, if we find rectangles which are contained in + // the new one, we can remove them + if (r.contains(*it)) + it = _dirtyRects.erase(it); + else + ++it; + } + + // If we got here, we can safely add r to the list of dirty rects. + _dirtyRects.push_back(r); +} + +// overlay functions + +byte *Screen::getOverlayPtr(int page) { + if (page == 0 || page == 1) + return _sjisOverlayPtrs[1]; + else if (page == 2 || page == 3) + return _sjisOverlayPtrs[2]; + + if (_vm->game() == GI_KYRA2) { + if (page == 12 || page == 13) + return _sjisOverlayPtrs[3]; + } else if (_vm->game() == GI_LOL) { + if (page == 4 || page == 5) + return _sjisOverlayPtrs[3]; + if (page == 6 || page == 7) + return _sjisOverlayPtrs[4]; + if (page == 12 || page == 13) + return _sjisOverlayPtrs[5]; + } + + return 0; +} + +void Screen::clearOverlayPage(int page) { + byte *dst = getOverlayPtr(page); + if (!dst) + return; + memset(dst, _sjisInvisibleColor, SCREEN_OVL_SJIS_SIZE); +} + +void Screen::clearOverlayRect(int page, int x, int y, int w, int h) { + byte *dst = getOverlayPtr(page); + + if (!dst || w < 0 || h < 0) + return; + + x <<= 1; y <<= 1; + w <<= 1; h <<= 1; + + dst += y * 640 + x; + + if (w == 640 && h == 400) { + memset(dst, _sjisInvisibleColor, SCREEN_OVL_SJIS_SIZE); + } else { + while (h--) { + memset(dst, _sjisInvisibleColor, w); + dst += 640; + } + } +} + +void Screen::copyOverlayRegion(int x, int y, int x2, int y2, int w, int h, int srcPage, int dstPage) { + byte *dst = getOverlayPtr(dstPage); + const byte *src = getOverlayPtr(srcPage); + + if (!dst || !src) + return; + + x <<= 1; x2 <<= 1; + y <<= 1; y2 <<= 1; + w <<= 1; h <<= 1; + + if (w == 640 && h == 400) { + memcpy(dst, src, SCREEN_OVL_SJIS_SIZE); + } else { + dst += y2 * 640 + x2; + src += y * 640 + x; + + while (h--) { + for (x = 0; x < w; ++x) + memmove(dst, src, w); + dst += 640; + src += 640; + } + } +} + +void Screen::crossFadeRegion(int x1, int y1, int x2, int y2, int w, int h, int srcPage, int dstPage) { + if (srcPage > 13 || dstPage > 13) + error("Screen::crossFadeRegion(): attempting to use temp page as source or dest page."); + + hideMouse(); + + uint16 *wB = (uint16 *)_pagePtrs[14]; + uint8 *hB = _pagePtrs[14] + 640 * _bytesPerPixel; + + for (int i = 0; i < w; i++) + wB[i] = i; + + for (int i = 0; i < h; i++) + hB[i] = i; + + for (int i = 0; i < w; i++) + SWAP(wB[_vm->_rnd.getRandomNumberRng(0, w - 1)], wB[i]); + + for (int i = 0; i < h; i++) + SWAP(hB[_vm->_rnd.getRandomNumberRng(0, h - 1)], hB[i]); + + uint8 *s = _pagePtrs[srcPage]; + uint8 *d = _pagePtrs[dstPage]; + + for (int i = 0; i < h; i++) { + int iH = i; + uint32 end = _system->getMillis() + 3; + for (int ii = 0; ii < w; ii++) { + int sX = (x1 + wB[ii]); + int sY = (y1 + hB[iH]); + int dX = (x2 + wB[ii]); + int dY = (y2 + hB[iH]); + + if (++iH >= h) + iH = 0; + + if (_bytesPerPixel == 2) + ((uint16*)d)[dY * 320 + dX] = ((uint16*)s)[sY * 320 + sX]; + else + d[dY * 320 + dX] = s[sY * 320 + sX]; + addDirtyRect(dX, dY, 1, 1); + } + + // This tries to speed things up, to get similiar speeds as in DOSBox etc. + // We can't write single pixels directly into the video memory like the original did. + // We also (unlike the original) want to aim at similiar speeds for all platforms. + if (!(i % 10)) + updateScreen(); + + uint32 cur = _system->getMillis(); + if (end > cur) + _system->delayMillis(end - cur); + } + + updateScreen(); + showMouse(); +} + +#pragma mark - + +DOSFont::DOSFont() { + _data = _widthTable = _heightTable = 0; + _colorMap = 0; + _width = _height = _numGlyphs = 0; + _bitmapOffsets = 0; +} + +bool DOSFont::load(Common::SeekableReadStream &file) { + unload(); + + _data = new uint8[file.size()]; + assert(_data); + + file.read(_data, file.size()); + if (file.err()) + return false; + + const uint16 fontSig = READ_LE_UINT16(_data + 2); + + if (fontSig != 0x0500) { + warning("DOSFont: invalid font: %.04X)", fontSig); + return false; + } + + const uint16 descOffset = READ_LE_UINT16(_data + 4); + + _width = _data[descOffset + 5]; + _height = _data[descOffset + 4]; + _numGlyphs = _data[descOffset + 3] + 1; + + _bitmapOffsets = (uint16 *)(_data + READ_LE_UINT16(_data + 6)); + _widthTable = _data + READ_LE_UINT16(_data + 8); + _heightTable = _data + READ_LE_UINT16(_data + 12); + + for (int i = 0; i < _numGlyphs; ++i) + _bitmapOffsets[i] = READ_LE_UINT16(&_bitmapOffsets[i]); + + return true; +} + +int DOSFont::getCharWidth(uint16 c) const { + if (c >= _numGlyphs) + return 0; + return _widthTable[c]; +} + +void DOSFont::drawChar(uint16 c, byte *dst, int pitch, int) const { + if (c >= _numGlyphs) + return; + + if (!_bitmapOffsets[c]) + return; + + const uint8 *src = _data + _bitmapOffsets[c]; + const uint8 charWidth = _widthTable[c]; + + if (!charWidth) + return; + + pitch -= charWidth; + + uint8 charH1 = _heightTable[c * 2 + 0]; + uint8 charH2 = _heightTable[c * 2 + 1]; + uint8 charH0 = _height - (charH1 + charH2); + + while (charH1--) { + uint8 col = _colorMap[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 = _colorMap[b >> 4]; + } else { + b = *src++; + col = _colorMap[b & 0xF]; + } + if (col != 0) { + *dst = col; + } + ++dst; + } + dst += pitch; + } + + while (charH0--) { + uint8 col = _colorMap[0]; + for (int i = 0; i < charWidth; ++i) { + if (col != 0) + *dst = col; + ++dst; + } + dst += pitch; + } +} + +void DOSFont::unload() { + delete[] _data; + _data = _widthTable = _heightTable = 0; + _colorMap = 0; + _width = _height = _numGlyphs = 0; + _bitmapOffsets = 0; +} + + +AMIGAFont::AMIGAFont() { + _width = _height = 0; + memset(_chars, 0, sizeof(_chars)); +} + +bool AMIGAFont::load(Common::SeekableReadStream &file) { + const uint16 dataSize = file.readUint16BE(); + if (dataSize + 2 != file.size()) + return false; + + _width = file.readByte(); + _height = file.readByte(); + + // Read the character definition offset table + uint16 offsets[ARRAYSIZE(_chars)]; + for (int i = 0; i < ARRAYSIZE(_chars); ++i) + offsets[i] = file.readUint16BE() + 4; + + if (file.err()) + return false; + + for (int i = 0; i < ARRAYSIZE(_chars); ++i) { + file.seek(offsets[i], SEEK_SET); + + _chars[i].yOffset = file.readByte(); + _chars[i].xOffset = file.readByte(); + _chars[i].width = file.readByte(); + file.readByte(); // unused + + // If the y offset is 255, then the character + // does not have any bitmap representation + if (_chars[i].yOffset != 255) { + Character::Graphics &g = _chars[i].graphics; + + g.width = file.readUint16BE(); + g.height = file.readUint16BE(); + + int depth = file.readByte(); + int specialWidth = file.readByte(); + int flags = file.readByte(); + int bytesPerPlane = file.readByte(); + + assert(depth != 0 && specialWidth == 0 && flags == 0 && bytesPerPlane != 0); + + // Allocate a temporary buffer to store the plane data + const int planesSize = bytesPerPlane * g.height * depth; + uint8 *tempData = new uint8[MAX(g.width * g.height, planesSize)]; + assert(tempData); + + file.read(tempData, planesSize); + + // Convert the plane based graphics to our graphic format + Screen::convertAmigaGfx(tempData, g.width, g.height, depth, false, bytesPerPlane); + + // Create a buffer perfectly fitting the character + g.bitmap = new uint8[g.width * g.height]; + assert(g.bitmap); + + memcpy(g.bitmap, tempData, g.width * g.height); + delete[] tempData; + } + + if (file.err()) + return false; + } + + return !file.err(); +} + +int AMIGAFont::getCharWidth(uint16 c) const { + if (c >= 255) + return 0; + return _chars[c].width; +} + +void AMIGAFont::drawChar(uint16 c, byte *dst, int pitch, int) const { + if (c >= 255) + return; + + if (_chars[c].yOffset == 255) + return; + + dst += _chars[c].yOffset * pitch; + dst += _chars[c].xOffset; + + pitch -= _chars[c].graphics.width; + + const uint8 *src = _chars[c].graphics.bitmap; + assert(src); + + for (int y = 0; y < _chars[c].graphics.height; ++y) { + for (int x = 0; x < _chars[c].graphics.width; ++x) { + if (*src) + *dst = *src; + ++src; + ++dst; + } + + dst += pitch; + } +} + +void AMIGAFont::unload() { + _width = _height = 0; + for (int i = 0; i < ARRAYSIZE(_chars); ++i) + delete[] _chars[i].graphics.bitmap; + memset(_chars, 0, sizeof(_chars)); +} + +SJISFont::SJISFont(Graphics::FontSJIS *font, const uint8 invisColor, bool is16Color, bool drawOutline, bool fatPrint, int extraSpacing) + : _colorMap(0), _font(font), _invisColor(invisColor), _is16Color(is16Color), _drawOutline(drawOutline), _sjisWidthOffset(extraSpacing) { + assert(_font); + _font->setDrawingMode(_drawOutline ? Graphics::FontSJIS::kOutlineMode : Graphics::FontSJIS::kDefaultMode); + _font->toggleFatPrint(fatPrint); + _sjisWidth = _font->getMaxFontWidth() >> 1; + _fontHeight = _font->getFontHeight() >> 1; + _asciiWidth = _font->getCharWidth('a') >> 1; +} + +void SJISFont::unload() { + delete _font; + _font = 0; +} + +int SJISFont::getHeight() const { + return _fontHeight; +} + +int SJISFont::getWidth() const { + return _sjisWidth + _sjisWidthOffset; +} + +int SJISFont::getCharWidth(uint16 c) const { + if (c <= 0x7F || (c >= 0xA1 && c <= 0xDF)) + return _asciiWidth; + else + return _sjisWidth + _sjisWidthOffset; +} + +void SJISFont::setColorMap(const uint8 *src) { + _colorMap = src; + + if (!_is16Color) { + if (_colorMap[0] == _invisColor) + _font->setDrawingMode(Graphics::FontSJIS::kDefaultMode); + else + _font->setDrawingMode(_drawOutline ? Graphics::FontSJIS::kOutlineMode : Graphics::FontSJIS::kDefaultMode); + } +} + +void SJISFont::drawChar(uint16 c, byte *dst, int pitch, int) const { + uint8 color1, color2; + + if (_is16Color) { + // PC98 16 color games specify a color value which is for the + // PC98 text mode palette, thus we need to remap it. + color1 = ((_colorMap[1] >> 5) & 0x7) + 16; + color2 = ((_colorMap[0] >> 5) & 0x7) + 16; + } else { + color1 = _colorMap[1]; + color2 = _colorMap[0]; + } + + _font->drawChar(dst, c, 640, 1, color1, color2, 640, 400); +} + +#pragma mark - + +Palette::Palette(const int numColors) : _palData(0), _numColors(numColors) { + _palData = new uint8[numColors * 3]; + assert(_palData); + + memset(_palData, 0, numColors * 3); +} + +Palette::~Palette() { + delete[] _palData; + _palData = 0; +} + +void Palette::loadVGAPalette(Common::ReadStream &stream, int startIndex, int colors) { + assert(startIndex + colors <= _numColors); + + uint8 *pos = _palData + startIndex * 3; + for (int i = 0 ; i < colors * 3; i++) + *pos++ = stream.readByte() & 0x3F; +} + +void Palette::loadHiColorPalette(Common::ReadStream &stream, int startIndex, int colors) { + uint16 *pos = (uint16*)(_palData + startIndex * 2); + + Graphics::PixelFormat currentFormat = g_system->getScreenFormat(); + Graphics::PixelFormat originalFormat(2, 5, 5, 5, 0, 5, 10, 0, 0); + + for (int i = 0; i < colors; i++) { + uint8 r, g, b; + originalFormat.colorToRGB(stream.readUint16LE(), r, g, b); + *pos++ = currentFormat.RGBToColor(r, g, b); + } +} + +void Palette::loadEGAPalette(Common::ReadStream &stream, int startIndex, int colors) { + assert(startIndex + colors <= 16); + + uint8 *dst = _palData + startIndex * 3; + for (int i = 0; i < colors; i++) { + uint8 index = stream.readByte(); + assert(index < _egaNumColors); + memcpy(dst, &_egaColors[index * 3], 3); + dst += 3; + } +} + +void Palette::setCGAPalette(int palIndex, CGAIntensity intensity) { + assert(_numColors >= _cgaNumColors); + assert(!(palIndex & ~1)); + memcpy(_palData, _cgaColors[palIndex * 2 + intensity], _numColors * 3); +} + +void Palette::loadAmigaPalette(Common::ReadStream &stream, int startIndex, int colors) { + assert(startIndex + colors <= _numColors); + + for (int i = 0; i < colors; ++i) { + uint16 col = stream.readUint16BE(); + _palData[(i + startIndex) * 3 + 2] = ((col & 0xF) * 0x3F) / 0xF; col >>= 4; + _palData[(i + startIndex) * 3 + 1] = ((col & 0xF) * 0x3F) / 0xF; col >>= 4; + _palData[(i + startIndex) * 3 + 0] = ((col & 0xF) * 0x3F) / 0xF; col >>= 4; + } +} + +void Palette::loadPC98Palette(Common::ReadStream &stream, int startIndex, int colors) { + assert(startIndex + colors <= _numColors); + + for (int i = 0; i < colors; ++i) { + const byte g = stream.readByte(), r = stream.readByte(), b = stream.readByte(); + + _palData[(i + startIndex) * 3 + 0] = ((r & 0xF) * 0x3F) / 0xF; + _palData[(i + startIndex) * 3 + 1] = ((g & 0xF) * 0x3F) / 0xF; + _palData[(i + startIndex) * 3 + 2] = ((b & 0xF) * 0x3F) / 0xF; + } +} + +void Palette::clear() { + memset(_palData, 0, _numColors * 3); +} + +void Palette::fill(int firstCol, int numCols, uint8 value) { + assert(firstCol >= 0 && firstCol + numCols <= _numColors); + + memset(_palData + firstCol * 3, CLIP<int>(value, 0, 63), numCols * 3); +} + +void Palette::copy(const Palette &source, int firstCol, int numCols, int dstStart) { + if (numCols == -1) + numCols = MIN(source.getNumColors(), _numColors) - firstCol; + if (dstStart == -1) + dstStart = firstCol; + + assert(numCols >= 0 && numCols <= _numColors); + assert(firstCol >= 0 && firstCol <= source.getNumColors()); + assert(dstStart >= 0 && dstStart + numCols <= _numColors); + + memmove(_palData + dstStart * 3, source._palData + firstCol * 3, numCols * 3); +} + +void Palette::copy(const uint8 *source, int firstCol, int numCols, int dstStart) { + if (dstStart == -1) + dstStart = firstCol; + + assert(numCols >= 0 && numCols <= _numColors); + assert(firstCol >= 0); + assert(dstStart >= 0 && dstStart + numCols <= _numColors); + + memmove(_palData + dstStart * 3, source + firstCol * 3, numCols * 3); +} + +uint8 *Palette::fetchRealPalette() const { + uint8 *buffer = new uint8[_numColors * 3]; + assert(buffer); + + uint8 *dst = buffer; + const uint8 *palData = _palData; + + for (int i = 0; i < _numColors; ++i) { + dst[0] = (palData[0] << 2) | (palData[0] & 3); + dst[1] = (palData[1] << 2) | (palData[1] & 3); + dst[2] = (palData[2] << 2) | (palData[2] & 3); + + dst += 3; + palData += 3; + } + + return buffer; +} + +const uint8 Palette::_egaColors[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0x00, 0xAA, 0x00, 0x00, 0xAA, 0xAA, + 0xAA, 0x00, 0x00, 0xAA, 0x00, 0xAA, 0xAA, 0x55, 0x00, 0xAA, 0xAA, 0xAA, + 0x55, 0x55, 0x55, 0x55, 0x55, 0xFF, 0x55, 0xFF, 0x55, 0x55, 0xFF, 0xFF, + 0xFF, 0x55, 0x55, 0xFF, 0x55, 0xFF, 0xFF, 0xFF, 0x55, 0xFF, 0xFF, 0xFF +}; + +const int Palette::_egaNumColors = ARRAYSIZE(_egaColors) / 3; + +const uint8 Palette::_cgaColors[4][12] = { + { 0x00, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x2A, 0x00, 0x00, 0x2A, 0x15, 0x00 }, + { 0x00, 0x00, 0x00, 0x15, 0x3F, 0x15, 0x3F, 0x15, 0x15, 0x3F, 0x3F, 0x15 }, + { 0x00, 0x00, 0x00, 0x00, 0x2A, 0x2A, 0x2A, 0x00, 0x2A, 0x2A, 0x2A, 0x2A }, + { 0x00, 0x00, 0x00, 0x15, 0x3F, 0x3F, 0x3F, 0x15, 0x3F, 0x3F, 0x3F, 0x3F } +}; + +const int Palette::_cgaNumColors = ARRAYSIZE(_cgaColors[0]) / 3; + +} // End of namespace Kyra diff --git a/engines/kyra/graphics/screen.h b/engines/kyra/graphics/screen.h new file mode 100644 index 0000000000..44113e4372 --- /dev/null +++ b/engines/kyra/graphics/screen.h @@ -0,0 +1,736 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef KYRA_SCREEN_H +#define KYRA_SCREEN_H + +#include "common/util.h" +#include "common/func.h" +#include "common/list.h" +#include "common/array.h" +#include "common/rect.h" +#include "common/rendermode.h" +#include "common/stream.h" + +class OSystem; + +namespace Graphics { +class FontSJIS; +} // End of namespace Graphics + +namespace Kyra { + +typedef Common::Functor0<void> UpdateFunctor; + +class KyraEngine_v1; +class Screen; + +struct ScreenDim { + uint16 sx; + uint16 sy; + uint16 w; + uint16 h; + uint16 unk8; + uint16 unkA; + uint16 unkC; + uint16 unkE; +}; + +/** + * A class that handles KYRA fonts. + */ +class Font { +public: + virtual ~Font() {} + + /** + * Tries to load a file from the given stream + */ + virtual bool load(Common::SeekableReadStream &file) = 0; + + /** + * Whether the font draws on the overlay. + */ + virtual bool usesOverlay() const { return false; } + + /** + * The font height. + */ + virtual int getHeight() const = 0; + + /** + * The font width, this is the maximal character + * width. + */ + virtual int getWidth() const = 0; + + /** + * Gets the width of a specific character. + */ + virtual int getCharWidth(uint16 c) const = 0; + + /** + * Sets a text palette map. The map contains 16 entries. + */ + virtual void setColorMap(const uint8 *src) = 0; + + /** + * Sets a text 16bit palette map. Only used in in EOB II FM-Towns. The map contains 2 entries. + */ + virtual void set16bitColorMap(const uint16 *src) {} + + /** + * Draws a specific character. + * + * TODO/FIXME: Replace this with a nicer API. Currently + * the user has to assure that the character fits. + * We use this API, since it's hard to assure dirty rect + * handling from outside Screen. + */ + virtual void drawChar(uint16 c, byte *dst, int pitch, int bpp) const = 0; +}; + +/** + * Implementation of the Font interface for DOS fonts. + * + * TODO: Clean up the implementation. For example we might be able + * to not to keep the whole font in memory. + */ +class DOSFont : public Font { +public: + DOSFont(); + ~DOSFont() { unload(); } + + bool load(Common::SeekableReadStream &file); + int getHeight() const { return _height; } + int getWidth() const { return _width; } + int getCharWidth(uint16 c) const; + void setColorMap(const uint8 *src) { _colorMap = src; } + void drawChar(uint16 c, byte *dst, int pitch, int) const; + +private: + void unload(); + + const uint8 *_colorMap; + + uint8 *_data; + + int _width, _height; + + int _numGlyphs; + + uint8 *_widthTable; + uint8 *_heightTable; + uint16 *_bitmapOffsets; +}; + +#ifdef ENABLE_EOB +/** +* Implementation of the Font interface for old DOS fonts used +* in EOB and EOB II. +* +*/ +class OldDOSFont : public Font { +public: + OldDOSFont(Common::RenderMode mode); + ~OldDOSFont(); + + bool load(Common::SeekableReadStream &file); + int getHeight() const { return _height; } + int getWidth() const { return _width; } + int getCharWidth(uint16 c) const; + void setColorMap(const uint8 *src) { _colorMap8bit = src; } + void set16bitColorMap(const uint16 *src) { _colorMap16bit = src; } + void drawChar(uint16 c, byte *dst, int pitch, int bpp) const; + +private: + void unload(); + + uint8 *_data; + uint16 *_bitmapOffsets; + + int _width, _height; + const uint8 *_colorMap8bit; + const uint16 *_colorMap16bit; + + int _numGlyphs; + + Common::RenderMode _renderMode; + + static uint16 *_cgaDitheringTable; + static int _numRef; +}; +#endif // ENABLE_EOB + +/** + * Implementation of the Font interface for AMIGA fonts. + */ +class AMIGAFont : public Font { +public: + AMIGAFont(); + ~AMIGAFont() { unload(); } + + bool load(Common::SeekableReadStream &file); + int getHeight() const { return _height; } + int getWidth() const { return _width; } + int getCharWidth(uint16 c) const; + void setColorMap(const uint8 *src) {} + void drawChar(uint16 c, byte *dst, int pitch, int) const; + +private: + void unload(); + + int _width, _height; + + struct Character { + uint8 yOffset, xOffset, width; + + struct Graphics { + uint16 width, height; + uint8 *bitmap; + } graphics; + }; + + Character _chars[255]; +}; + +/** + * Implementation of the Font interface for FM-Towns/PC98 fonts + */ +class SJISFont : public Font { +public: + SJISFont(Graphics::FontSJIS *font, const uint8 invisColor, bool is16Color, bool drawOutline, bool fatPrint, int extraSpacing); + virtual ~SJISFont() { unload(); } + + virtual bool usesOverlay() const { return true; } + + bool load(Common::SeekableReadStream &) { return true; } + int getHeight() const; + int getWidth() const; + int getCharWidth(uint16 c) const; + void setColorMap(const uint8 *src); + virtual void drawChar(uint16 c, byte *dst, int pitch, int) const; + +protected: + void unload(); + + const uint8 *_colorMap; + Graphics::FontSJIS *_font; + int _sjisWidth, _asciiWidth; + int _fontHeight; + const bool _drawOutline; + +private: + const uint8 _invisColor; + const bool _is16Color; + // We use this for cases where the font width returned by getWidth() or getCharWidth() does not match the original. + // The original Japanese game versions use hard coded sjis font widths of 8 or 9. However, this does not necessarily + // depend on whether an outline is used or not (neither LOL/PC-9801 nor LOL/FM-TOWNS use an outline, but the first + // version uses a font width of 8 where the latter uses a font width of 9). + const int _sjisWidthOffset; +}; + +/** + * A class that manages KYRA palettes. + * + * This class stores the palette data as VGA RGB internally. + */ +class Palette { +public: + Palette(const int numColors); + ~Palette(); + + enum { + kVGABytesPerColor = 3, + kPC98BytesPerColor = 3, + kAmigaBytesPerColor = 2 + }; + + /** + * Load a VGA palette from the given stream. + */ + void loadVGAPalette(Common::ReadStream &stream, int startIndex, int colors); + + /** + * Load a HiColor palette from the given stream. + */ + void loadHiColorPalette(Common::ReadStream &stream, int startIndex, int colors); + + /** + * Load a EGA palette from the given stream. + */ + void loadEGAPalette(Common::ReadStream &stream, int startIndex, int colors); + + /** + * Set default CGA palette. We only need the cyan/magenta/grey mode. + */ + enum CGAIntensity { + kIntensityLow = 0, + kIntensityHigh = 1 + }; + + void setCGAPalette(int palIndex, CGAIntensity intensity); + + /** + * Load a AMIGA palette from the given stream. + */ + void loadAmigaPalette(Common::ReadStream &stream, int startIndex, int colors); + + /** + * Load a PC98 16 color palette from the given stream. + */ + void loadPC98Palette(Common::ReadStream &stream, int startIndex, int colors); + + /** + * Return the number of colors this palette manages. + */ + int getNumColors() const { return _numColors; } + + /** + * Set all palette colors to black. + */ + void clear(); + + /** + * Fill the given indexes with the given component value. + * + * @param firstCol the first color, which should be overwritten. + * @param numCols number of colors, which schould be overwritten. + * @param value color component value, which should be stored. + */ + void fill(int firstCol, int numCols, uint8 value); + + /** + * Copy data from another palette. + * + * @param source palette to copy data from. + * @param firstCol the first color of the source which should be copied. + * @param numCols number of colors, which should be copied. -1 all remaining colors. + * @param dstStart the first color, which should be ovewritten. If -1 firstCol will be used as start. + */ + void copy(const Palette &source, int firstCol = 0, int numCols = -1, int dstStart = -1); + + /** + * Copy data from a raw VGA palette. + * + * @param source source buffer + * @param firstCol the first color of the source which should be copied. + * @param numCols number of colors, which should be copied. + * @param dstStart the first color, which should be ovewritten. If -1 firstCol will be used as start. + */ + void copy(const uint8 *source, int firstCol, int numCols, int dstStart = -1); + + /** + * Fetch a RGB palette. + * + * @return a pointer to the RGB palette data, the client must delete[] it. + */ + uint8 *fetchRealPalette() const; + + //XXX + uint8 &operator[](const int index) { + assert(index >= 0 && index <= _numColors * 3); + return _palData[index]; + } + + const uint8 &operator[](const int index) const { + assert(index >= 0 && index <= _numColors * 3); + return _palData[index]; + } + + /** + * Gets raw access to the palette. + * + * TODO: Get rid of this. + */ + uint8 *getData() { return _palData; } + const uint8 *getData() const { return _palData; } + +private: + uint8 *_palData; + const int _numColors; + + static const uint8 _egaColors[]; + static const int _egaNumColors; + static const uint8 _cgaColors[4][12]; + static const int _cgaNumColors; +}; + +class Screen { +public: + enum { + SCREEN_W = 320, + SCREEN_H = 200, + SCREEN_PAGE_SIZE = 320 * 200 + 1024, + SCREEN_OVL_SJIS_SIZE = 640 * 400, + SCREEN_PAGE_NUM = 16, + SCREEN_OVLS_NUM = 6 + }; + + enum CopyRegionFlags { + CR_NO_P_CHECK = 0x01 + }; + + enum DrawShapeFlags { + DSF_X_FLIPPED = 0x01, + DSF_Y_FLIPPED = 0x02, + DSF_SCALE = 0x04, + DSF_WND_COORDS = 0x10, + DSF_CENTER = 0x20, + + DSF_SHAPE_FADING = 0x100, + DSF_TRANSPARENCY = 0x1000, + DSF_BACKGROUND_FADING = 0x2000, + DSF_CUSTOM_PALETTE = 0x8000 + }; + + enum FontId { + FID_6_FNT = 0, + FID_8_FNT, + FID_9_FNT, + FID_CRED6_FNT, + FID_CRED8_FNT, + FID_BOOKFONT_FNT, + FID_GOLDFONT_FNT, + FID_INTRO_FNT, + FID_SJIS_FNT, + FID_SJIS_LARGE_FNT, + FID_SJIS_SMALL_FNT, + FID_NUM + }; + + Screen(KyraEngine_v1 *vm, OSystem *system, const ScreenDim *dimTable, const int dimTableSize); + virtual ~Screen(); + + // init + virtual bool init(); + virtual void setResolution(); + virtual void enableHiColorMode(bool enabled); + + void updateScreen(); + + // debug functions + bool queryScreenDebug() const { return _debugEnabled; } + bool enableScreenDebug(bool enable); + + // page cur. functions + int setCurPage(int pageNum); + void clearCurPage(); + + void copyWsaRect(int x, int y, int w, int h, int dimState, int plotFunc, const uint8 *src, + int unk1, const uint8 *unkPtr1, const uint8 *unkPtr2); + + // page 0 functions + void copyToPage0(int y, int h, uint8 page, uint8 *seqBuf); + void shakeScreen(int times); + + // page functions + void copyRegion(int x1, int y1, int x2, int y2, int w, int h, int srcPage, int dstPage, int flags=0); + void copyPage(uint8 srcPage, uint8 dstPage); + + void copyRegionToBuffer(int pageNum, int x, int y, int w, int h, uint8 *dest); + void copyBlockToPage(int pageNum, int x, int y, int w, int h, const uint8 *src); + + 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, bool xored = false); + + void clearPage(int pageNum); + + int getPagePixel(int pageNum, int x, int y); + void setPagePixel(int pageNum, int x, int y, uint8 color); + + const uint8 *getCPagePtr(int pageNum) const; + uint8 *getPageRect(int pageNum, int x, int y, int w, int h); + + // palette handling + void fadeFromBlack(int delay=0x54, const UpdateFunctor *upFunc = 0); + void fadeToBlack(int delay=0x54, const UpdateFunctor *upFunc = 0); + + virtual void fadePalette(const Palette &pal, int delay, const UpdateFunctor *upFunc = 0); + virtual void getFadeParams(const Palette &pal, int delay, int &delayInc, int &diff); + virtual int fadePalStep(const Palette &pal, int diff); + + void setPaletteIndex(uint8 index, uint8 red, uint8 green, uint8 blue); + virtual void setScreenPalette(const Palette &pal); + + // AMIGA version only + bool isInterfacePaletteEnabled() const { return _interfacePaletteEnabled; } + void enableInterfacePalette(bool e); + void setInterfacePalette(const Palette &pal, uint8 r, uint8 g, uint8 b); + + virtual void getRealPalette(int num, uint8 *dst); + Palette &getPalette(int num); + void copyPalette(const int dst, const int src); + + // gui specific (processing on _curPage) + void drawLine(bool vertical, int x, int y, int length, int color); + void drawClippedLine(int x1, int y1, int x2, int y2, int color); + virtual 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); + + // font/text handling + virtual bool loadFont(FontId fontId, const char *filename); + FontId setFont(FontId fontId); + + int getFontHeight() const; + int getFontWidth() const; + + int getCharWidth(uint16 c) const; + int getTextWidth(const char *str); + + void printText(const char *str, int x, int y, uint8 color1, uint8 color2); + + virtual void setTextColorMap(const uint8 *cmap) = 0; + void setTextColor(const uint8 *cmap, int a, int b); + void setTextColor16bit(const uint16 *cmap16); + + const ScreenDim *getScreenDim(int dim) const; + void modifyScreenDim(int dim, int x, int y, int w, int h); + int screenDimTableCount() const { return _dimTableCount; } + + void setScreenDim(int dim); + int curDimIndex() const { return _curDimIndex; } + + const ScreenDim *_curDim; + + // shape handling + uint8 *encodeShape(int x, int y, int w, int h, int flags); + + int setNewShapeHeight(uint8 *shape, int height); + int resetShapeHeight(uint8 *shape); + + virtual void drawShape(uint8 pageNum, const uint8 *shapeData, int x, int y, int sd, int flags, ...); + + // mouse handling + void hideMouse(); + void showMouse(); + bool isMouseVisible() const; + virtual void setMouseCursor(int x, int y, const byte *shape); + + // rect handling + virtual int getRectSize(int w, int h) = 0; + + void rectClip(int &x, int &y, int w, int h); + + // misc + void loadBitmap(const char *filename, int tempPage, int dstPage, Palette *pal, bool skip=false); + + virtual bool loadPalette(const char *filename, Palette &pal); + bool loadPaletteTable(const char *filename, int firstPalette); + virtual void loadPalette(const byte *data, Palette &pal, int bytes); + + void setAnimBlockPtr(int size); + + void setShapePages(int page1, int page2, int minY = -1, int maxY = 201); + + virtual byte getShapeFlag1(int x, int y); + virtual byte getShapeFlag2(int x, int y); + + virtual int getDrawLayer(int x, int y); + virtual int getDrawLayer2(int x, int y, int height); + + void blockInRegion(int x, int y, int width, int height); + void blockOutRegion(int x, int y, int width, int height); + + int _charWidth; + int _charOffset; + int _curPage; + uint8 *_shapePages[2]; + int _maskMinY, _maskMaxY; + FontId _currentFont; + + // decoding functions + static void decodeFrame1(const uint8 *src, uint8 *dst, uint32 size); + static uint16 decodeEGAGetCode(const uint8 *&pos, uint8 &nib); + + static void decodeFrame3(const uint8 *src, uint8 *dst, uint32 size); + static uint decodeFrame4(const uint8 *src, uint8 *dst, uint32 dstSize); + static void decodeFrameDelta(uint8 *dst, const uint8 *src, bool noXor = false); + static void decodeFrameDeltaPage(uint8 *dst, const uint8 *src, const int pitch, bool noXor); + + static void convertAmigaGfx(uint8 *data, int w, int h, int depth = 5, bool wsa = false, int bytesPerPlane = -1); + static void convertAmigaMsc(uint8 *data); + + // RPG specific, this does not belong here + void crossFadeRegion(int x1, int y1, int x2, int y2, int w, int h, int srcPage, int dstPage); + + uint16 *get16bitPalette() { return _16bitPalette; } + void set16bitShadingLevel(int lvl) { _16bitShadingLevel = lvl; } + +protected: + void resetPagePtrsAndBuffers(int pageSize); + uint8 *getPagePtr(int pageNum); + virtual void updateDirtyRects(); + void updateDirtyRectsAmiga(); + void updateDirtyRectsOvl(); + + void scale2x(byte *dst, int dstPitch, const byte *src, int srcPitch, int w, int h); + virtual void mergeOverlay(int x, int y, int w, int h); + + // overlay specific + byte *getOverlayPtr(int pageNum); + void clearOverlayPage(int pageNum); + void clearOverlayRect(int pageNum, int x, int y, int w, int h); + void copyOverlayRegion(int x, int y, int x2, int y2, int w, int h, int srcPage, int dstPage); + + // font/text specific + uint16 fetchChar(const char *&s) const; + void drawChar(uint16 c, int x, int y); + + int16 encodeShapeAndCalculateSize(uint8 *from, uint8 *to, int size); + + template<bool noXor> static void wrapped_decodeFrameDelta(uint8 *dst, const uint8 *src); + template<bool noXor> static void wrapped_decodeFrameDeltaPage(uint8 *dst, const uint8 *src, const int pitch); + + uint8 *_pagePtrs[16]; + uint8 *_sjisOverlayPtrs[SCREEN_OVLS_NUM]; + uint8 _pageMapping[SCREEN_PAGE_NUM]; + + bool _useOverlays; + bool _useSJIS; + bool _use16ColorMode; + bool _useHiResEGADithering; + bool _useHiColorScreen; + bool _isAmiga; + Common::RenderMode _renderMode; + int _bytesPerPixel; + int _screenPageSize; + + uint8 _sjisInvisibleColor; + bool _sjisMixedFontMode; + + Palette *_screenPalette; + Common::Array<Palette *> _palettes; + Palette *_internFadePalette; + + uint16 shade16bitColor(uint16 col); + + uint16 *_16bitPalette; + uint16 *_16bitConversionPalette; + uint8 _16bitShadingLevel; + + Font *_fonts[FID_NUM]; + uint8 _textColorsMap[16]; + uint16 _textColorsMap16bit[2]; + + uint8 *_decodeShapeBuffer; + int _decodeShapeBufferSize; + + uint8 *_animBlockPtr; + int _animBlockSize; + + // dimension handling + const ScreenDim *const _dimTable; + ScreenDim **_customDimTable; + const int _dimTableCount; + int _curDimIndex; + + // mouse handling + int _mouseLockCount; + const uint8 _cursorColorKey; + + virtual void postProcessCursor(uint8 *data, int w, int h, int pitch) {} + + enum { + kMaxDirtyRects = 50 + }; + + bool _forceFullUpdate; + bool _paletteChanged; + Common::List<Common::Rect> _dirtyRects; + + void addDirtyRect(int x, int y, int w, int h); + + OSystem *_system; + KyraEngine_v1 *_vm; + + // shape + int drawShapeMarginNoScaleUpwind(uint8 *&dst, const uint8 *&src, int &cnt); + int drawShapeMarginNoScaleDownwind(uint8 *&dst, const uint8 *&src, int &cnt); + int drawShapeMarginScaleUpwind(uint8 *&dst, const uint8 *&src, int &cnt); + int drawShapeMarginScaleDownwind(uint8 *&dst, const uint8 *&src, int &cnt); + int drawShapeSkipScaleUpwind(uint8 *&dst, const uint8 *&src, int &cnt); + int drawShapeSkipScaleDownwind(uint8 *&dst, const uint8 *&src, int &cnt); + void drawShapeProcessLineNoScaleUpwind(uint8 *&dst, const uint8 *&src, int &cnt, int16 scaleState); + void drawShapeProcessLineNoScaleDownwind(uint8 *&dst, const uint8 *&src, int &cnt, int16 scaleState); + void drawShapeProcessLineScaleUpwind(uint8 *&dst, const uint8 *&src, int &cnt, int16 scaleState); + void drawShapeProcessLineScaleDownwind(uint8 *&dst, const uint8 *&src, int &cnt, int16 scaleState); + + void drawShapePlotType0(uint8 *dst, uint8 cmd); + void drawShapePlotType1(uint8 *dst, uint8 cmd); + void drawShapePlotType3_7(uint8 *dst, uint8 cmd); + void drawShapePlotType4(uint8 *dst, uint8 cmd); + void drawShapePlotType5(uint8 *dst, uint8 cmd); + void drawShapePlotType6(uint8 *dst, uint8 cmd); + void drawShapePlotType8(uint8 *dst, uint8 cmd); + void drawShapePlotType9(uint8 *dst, uint8 cmd); + void drawShapePlotType11_15(uint8 *dst, uint8 cmd); + void drawShapePlotType12(uint8 *dst, uint8 cmd); + void drawShapePlotType13(uint8 *dst, uint8 cmd); + void drawShapePlotType14(uint8 *dst, uint8 cmd); + void drawShapePlotType16(uint8 *dst, uint8 cmd); + void drawShapePlotType20(uint8 *dst, uint8 cmd); + void drawShapePlotType21(uint8 *dst, uint8 cmd); + void drawShapePlotType33(uint8 *dst, uint8 cmd); + void drawShapePlotType37(uint8 *dst, uint8 cmd); + void drawShapePlotType48(uint8 *dst, uint8 cmd); + void drawShapePlotType52(uint8 *dst, uint8 cmd); + + typedef int (Screen::*DsMarginSkipFunc)(uint8 *&dst, const uint8 *&src, int &cnt); + typedef void (Screen::*DsLineFunc)(uint8 *&dst, const uint8 *&src, int &cnt, int16 scaleState); + typedef void (Screen::*DsPlotFunc)(uint8 *dst, uint8 cmd); + + DsMarginSkipFunc _dsProcessMargin; + DsMarginSkipFunc _dsScaleSkip; + DsLineFunc _dsProcessLine; + DsPlotFunc _dsPlot; + + const uint8 *_dsShapeFadingTable; + int _dsShapeFadingLevel; + const uint8 *_dsColorTable; + const uint8 *_dsTransparencyTable1; + const uint8 *_dsTransparencyTable2; + const uint8 *_dsBackgroundFadingTable; + int _dsDrawLayer; + uint8 *_dsDstPage; + int _dsTmpWidth; + int _dsOffscreenLeft; + int _dsOffscreenRight; + int _dsScaleW; + int _dsScaleH; + int _dsOffscreenScaleVal1; + int _dsOffscreenScaleVal2; + int _drawShapeVar1; + int _drawShapeVar3; + int _drawShapeVar4; + int _drawShapeVar5; + + // AMIGA version + bool _interfacePaletteEnabled; + + // debug + bool _debugEnabled; +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/graphics/screen_eob.cpp b/engines/kyra/graphics/screen_eob.cpp new file mode 100644 index 0000000000..3945c034fc --- /dev/null +++ b/engines/kyra/graphics/screen_eob.cpp @@ -0,0 +1,1974 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + + +#if !defined(ENABLE_EOB) +#include "kyra/graphics/screen.h" +#endif + +#ifdef ENABLE_EOB + +#include "kyra/engine/eobcommon.h" +#include "kyra/resource/resource.h" + +#include "common/system.h" + +#include "graphics/cursorman.h" +#include "graphics/palette.h" +#include "graphics/sjis.h" + +namespace Kyra { + +Screen_EoB::Screen_EoB(EoBCoreEngine *vm, OSystem *system) : Screen(vm, system, _screenDimTable, _screenDimTableCount), _cursorColorKey16Bit(0x8000) { + _dsBackgroundFading = false; + _dsShapeFadingLevel = 0; + _dsBackgroundFadingXOffs = 0; + _dsShapeFadingTable = 0; + _dsX1 = _dsX2 = _dsY1 = _dsY2 = 0; + _gfxX = _gfxY = 0; + _gfxCol = 0; + _dsTempPage = 0; + _shpBuffer = _convertHiColorBuffer = 0; + _dsDiv = 0; + _dsRem = 0; + _dsScaleTrans = 0; + _cgaScaleTable = 0; + _gfxMaxY = 0; + _egaDitheringTable = 0; + _egaDitheringTempPage = 0; + _cgaMappingDefault = 0; + _cgaDitheringTables[0] = _cgaDitheringTables[1] = 0; + _useHiResEGADithering = false; +} + +Screen_EoB::~Screen_EoB() { + delete[] _dsTempPage; + delete[] _shpBuffer; + delete[] _convertHiColorBuffer; + delete[] _cgaScaleTable; + delete[] _egaDitheringTable; + delete[] _egaDitheringTempPage; + delete[] _cgaDitheringTables[0]; + delete[] _cgaDitheringTables[1]; +} + +bool Screen_EoB::init() { + if (Screen::init()) { + int temp; + _gfxMaxY = _vm->staticres()->loadRawData(kEoBBaseExpObjectY, temp); + _dsTempPage = new uint8[12000]; + + if (_vm->gameFlags().platform == Common::kPlatformFMTowns) { + _shpBuffer = new uint8[SCREEN_H * SCREEN_W]; + _convertHiColorBuffer = new uint8[SCREEN_H * SCREEN_W]; + enableHiColorMode(true); + + Graphics::FontSJIS *font = Graphics::FontSJIS::createFont(Common::kPlatformFMTowns); + if (!font) + error("Could not load any SJIS font, neither the original nor ScummVM's 'SJIS.FNT'"); + _fonts[FID_SJIS_LARGE_FNT] = new SJISFontLarge(font); + + loadFont(FID_SJIS_SMALL_FNT, "FONT.DMP"); + } + + if (_vm->gameFlags().useHiRes && _renderMode == Common::kRenderEGA) { + _useHiResEGADithering = true; + _egaDitheringTable = new uint8[256]; + _egaDitheringTempPage = new uint8[SCREEN_W * 2 * SCREEN_H * 2]; + for (int i = 0; i < 256; i++) + _egaDitheringTable[i] = i & 0x0F; + } else if (_renderMode == Common::kRenderCGA) { + _cgaMappingDefault = _vm->staticres()->loadRawData(kEoB1CgaMappingDefault, temp); + _cgaDitheringTables[0] = new uint16[256]; + memset(_cgaDitheringTables[0], 0, 256 * sizeof(uint16)); + _cgaDitheringTables[1] = new uint16[256]; + memset(_cgaDitheringTables[1], 0, 256 * sizeof(uint16)); + + _cgaScaleTable = new uint8[256]; + memset(_cgaScaleTable, 0, 256 * sizeof(uint8)); + for (int i = 0; i < 256; i++) + _cgaScaleTable[i] = ((i & 0xF0) >> 2) | (i & 0x03); + } + + return true; + } + return false; +} + +void Screen_EoB::setClearScreenDim(int dim) { + setScreenDim(dim); + clearCurDim(); +} + +void Screen_EoB::clearCurDim() { + fillRect(_curDim->sx << 3, _curDim->sy, ((_curDim->sx + _curDim->w) << 3) - 1, (_curDim->sy + _curDim->h) - 1, _curDim->unkA); +} + +void Screen_EoB::setMouseCursor(int x, int y, const byte *shape) { + setMouseCursor(x, y, shape, 0); +} + +void Screen_EoB::setMouseCursor(int x, int y, const byte *shape, const uint8 *ovl) { + if (!shape) + return; + + int mouseW = (shape[2] << 3); + int mouseH = (shape[3]); + int colorKey = (_renderMode == Common::kRenderCGA) ? 0 : (_bytesPerPixel == 2 ? _cursorColorKey16Bit : _cursorColorKey); + + int scaleFactor = _vm->gameFlags().useHiRes ? 2 : 1; + int bpp = _useHiColorScreen ? 2 : 1; + + uint8 *cursor = new uint8[mouseW * scaleFactor * bpp * mouseH * scaleFactor]; + + if (_bytesPerPixel == 2) { + for (int s = mouseW * scaleFactor * bpp * mouseH * scaleFactor; s; s -= 2) + *(uint16*)(cursor + s - 2) = colorKey; + } else { + // We don't use fillRect here to make sure that the color key 0xFF doesn't get converted into EGA color + memset(cursor, colorKey, mouseW * scaleFactor * bpp * mouseH * scaleFactor); + } + + copyBlockToPage(6, 0, 0, mouseW * scaleFactor, mouseH * scaleFactor, cursor); + drawShape(6, shape, 0, 0, 0, 2, ovl); + CursorMan.showMouse(false); + + if (_useHiResEGADithering) + ditherRect(getCPagePtr(6), cursor, mouseW * scaleFactor, mouseW, mouseH, colorKey); + else if (_vm->gameFlags().useHiRes) + scale2x(cursor, mouseW * scaleFactor, getCPagePtr(6), SCREEN_W, mouseW, mouseH); + else + copyRegionToBuffer(6, 0, 0, mouseW, mouseH, cursor); + + // Mouse cursor post processing for CGA mode. Unlike the original (which uses drawShape for the mouse cursor) + // the cursor manager cannot know whether a pixel value of 0 is supposed to be black or transparent. Thus, we + // go over the transparency mask again and turn the black pixels to color 4. + if (_renderMode == Common::kRenderCGA) { + const uint8 *maskTbl = shape + 4 + ((mouseW * mouseH) >> 2); + uint8 *dst = cursor; + uint8 trans = 0; + uint8 shift = 6; + + uint16 mH = mouseH; + while (mH--) { + uint16 mW = mouseW; + while (mW--) { + if (shift == 6) + trans = *maskTbl++; + if (!*dst && !((trans >> shift) & 3)) + *dst = 4; + dst++; + shift = (shift - 2) & 7; + } + } + } + + // Convert color key to 16 bit after drawing the mouse cursor. + // The cursor has been converted to 16 bit in scale2x(). + colorKey = _16bitConversionPalette ? _16bitConversionPalette[colorKey] : colorKey; + Graphics::PixelFormat pixelFormat = _system->getScreenFormat(); + + CursorMan.replaceCursor(cursor, mouseW * scaleFactor, mouseH * scaleFactor, x * scaleFactor, y * scaleFactor, colorKey, false, &pixelFormat); + if (isMouseVisible()) + CursorMan.showMouse(true); + delete[] cursor; + + // makes sure that the cursor is drawn + // we do not use Screen::updateScreen here + // so we can be sure that changes to page 0 + // are NOT updated on the real screen here + _system->updateScreen(); +} + +void Screen_EoB::loadFileDataToPage(Common::SeekableReadStream *s, int pageNum, uint32 size) { + s->read(_pagePtrs[pageNum], size); +} + +void Screen_EoB::printShadedText(const char *string, int x, int y, int col1, int col2) { + if (_vm->gameFlags().platform != Common::kPlatformFMTowns) { + printText(string, x - 1, y, 12, col2); + printText(string, x, y + 1, 12, 0); + printText(string, x - 1, y + 1, 12, 0); + } else if (col2) { + fillRect(x, y, x + getTextWidth(string) - 1, y + getFontHeight() - 1, col2); + } + printText(string, x, y, col1, 0); +} + +void Screen_EoB::loadShapeSetBitmap(const char *file, int tempPage, int destPage) { + loadEoBBitmap(file, _cgaMappingDefault, tempPage, destPage, -1); + _curPage = 2; +} + +void Screen_EoB::loadEoBBitmap(const char *file, const uint8 *cgaMapping, int tempPage, int destPage, int convertToPage) { + const char *filePattern = _vm->gameFlags().platform == Common::kPlatformFMTowns ? "%s.SHP" : ((_vm->game() == GI_EOB1 && (_renderMode == Common::kRenderEGA || _renderMode == Common::kRenderCGA)) ? "%s.EGA" : "%s.CPS"); + Common::String tmp = Common::String::format(filePattern, file); + Common::SeekableReadStream *s = _vm->resource()->createReadStream(tmp); + bool loadAlternative = false; + + if (_vm->gameFlags().platform == Common::kPlatformFMTowns) { + if (!s) + error("Screen_EoB::loadEoBBitmap(): Failed to load file '%s'", file); + s->read(_shpBuffer, s->size()); + decodeSHP(_shpBuffer, destPage); + } else if (s) { + // This additional check is necessary since some localized versions of EOB II seem to contain invalid (size zero) cps files + if (s->size()) + loadBitmap(tmp.c_str(), tempPage, destPage, 0); + else + loadAlternative = true; + } else { + loadAlternative = true; + } + + delete s; + + if (loadAlternative) { + if (_vm->game() == GI_EOB1) { + tmp.insertChar('1', tmp.size() - 4); + loadBitmap(tmp.c_str(), tempPage, destPage, 0); + } else { + tmp.setChar('X', 0); + s = _vm->resource()->createReadStream(tmp); + if (!s) + error("Screen_EoB::loadEoBBitmap(): Failed to load file '%s'", file); + s->seek(768); + loadFileDataToPage(s, destPage, 64000); + delete s; + } + } + + if (convertToPage == -1) + return; + + if (_16bitPalette) + convertToHiColor(destPage); + + if (convertToPage == 2 && _renderMode == Common::kRenderCGA) { + convertPage(destPage, 4, cgaMapping); + copyRegion(0, 0, 0, 0, 320, 200, 4, 2, Screen::CR_NO_P_CHECK); + } else if (convertToPage == 0) { + convertPage(destPage, 2, cgaMapping); + copyRegion(0, 0, 0, 0, 320, 200, 2, 0, Screen::CR_NO_P_CHECK); + } else { + convertPage(destPage, convertToPage, cgaMapping); + } +} + +void Screen_EoB::convertPage(int srcPage, int dstPage, const uint8 *cgaMapping) { + uint8 *src = getPagePtr(srcPage); + uint8 *dst = getPagePtr(dstPage); + if (src == dst) + return; + + if (_renderMode == Common::kRenderCGA) { + if (cgaMapping) + generateCGADitheringTables(cgaMapping); + + uint16 *d = (uint16 *)dst; + uint8 tblSwitch = 0; + for (int height = SCREEN_H; height; height--) { + const uint16 *table = _cgaDitheringTables[(tblSwitch++) & 1]; + for (int width = SCREEN_W / 2; width; width--) { + WRITE_LE_UINT16(d++, table[((src[1] & 0x0F) << 4) | (src[0] & 0x0F)]); + src += 2; + } + } + } else if (_renderMode == Common::kRenderEGA && !_useHiResEGADithering) { + uint32 len = SCREEN_W * SCREEN_H; + while (len--) + *dst++ = *src++ & 0x0F; + } else { + copyPage(srcPage, dstPage); + } + + if (dstPage == 0 || dstPage == 1) + _forceFullUpdate = true; +} + +void Screen_EoB::setScreenPalette(const Palette &pal) { + if (_bytesPerPixel == 2) { + for (int i = 0; i < 4; i++) + createFadeTable16bit((const uint16*)(pal.getData()), &_16bitPalette[i * 256], 0, i * 85); + }else if (_useHiResEGADithering && pal.getNumColors() != 16) { + generateEGADitheringTable(pal); + } else if (_renderMode == Common::kRenderEGA && pal.getNumColors() == 16) { + _screenPalette->copy(pal); + _system->getPaletteManager()->setPalette(_screenPalette->getData(), 0, _screenPalette->getNumColors()); + } else if (_renderMode != Common::kRenderCGA && _renderMode != Common::kRenderEGA) { + Screen::setScreenPalette(pal); + } +} + +void Screen_EoB::getRealPalette(int num, uint8 *dst) { + if (_renderMode == Common::kRenderCGA || _renderMode == Common::kRenderEGA) { + const uint8 *pal = _screenPalette->getData(); + for (int i = 0; i < 16; ++i) { + dst[0] = (pal[0] << 2) | (pal[0] & 3); + dst[1] = (pal[1] << 2) | (pal[1] & 3); + dst[2] = (pal[2] << 2) | (pal[2] & 3); + dst += 3; + pal += 3; + } + } else { + Screen::getRealPalette(num, dst); + } +} + +uint8 *Screen_EoB::encodeShape(uint16 x, uint16 y, uint16 w, uint16 h, bool encode8bit, const uint8 *cgaMapping) { + uint8 *shp = 0; + uint16 shapesize = 0; + + uint8 *srcLineStart = getPagePtr(_curPage | 1) + y * 320 + (x << 3); + uint8 *src = srcLineStart; + + if (_renderMode == Common::kRenderEGA && !_useHiResEGADithering) + encode8bit = false; + + if (_bytesPerPixel == 2 && encode8bit) { + shapesize = h * (w << 3) + 4; + shp = new uint8[shapesize]; + memset(shp, 0, shapesize); + uint8 *dst = shp; + + *dst++ = 0; + *dst++ = (h & 0xFF); + *dst++ = (w & 0xFF); + *dst++ = (h & 0xFF); + + w <<= 3; + + for (int i = 0; i < h; ++i) { + memcpy(dst, src, w); + srcLineStart += SCREEN_W; + src = srcLineStart; + dst += w; + } + } else if (_renderMode == Common::kRenderCGA) { + if (cgaMapping) + generateCGADitheringTables(cgaMapping); + shapesize = h * (w << 2) + 4; + shp = new uint8[shapesize]; + memset(shp, 0, shapesize); + uint8 *dst = shp; + + *dst++ = 4; + *dst++ = (h & 0xFF); + *dst++ = (w & 0xFF); + *dst++ = (h & 0xFF); + + uint8 *dst2 = dst + (h * (w << 1)); + + uint8 tblSwitch = 0; + uint16 h1 = h; + while (h1--) { + uint16 w1 = w << 1; + const uint16 *table = _cgaDitheringTables[(tblSwitch++) & 1]; + + while (w1--) { + uint16 p0 = table[((src[1] & 0x0F) << 4) | (src[0] & 0x0F)]; + uint16 p1 = table[((src[3] & 0x0F) << 4) | (src[2] & 0x0F)]; + + *dst++ = ((p0 & 0x0003) << 6) | ((p0 & 0x0300) >> 4) | ((p1 & 0x0003) << 2) | ((p1 & 0x0300) >> 8); + + uint8 msk = 0; + for (int i = 0; i < 4; i++) { + if (!src[3 - i]) + msk |= (3 << (i << 1)); + } + *dst2++ = msk; + src += 4; + } + srcLineStart += SCREEN_W; + src = srcLineStart; + } + + } else if (encode8bit) { + uint16 h1 = h; + while (h1--) { + uint8 *lineEnd = src + (w << 3); + do { + if (!*src++) { + shapesize++; + uint8 *startZeroPos = src; + while (src != lineEnd && *src == 0) + src++; + + uint16 numZero = src - startZeroPos + 1; + if (numZero >> 8) + shapesize += 2; + } + shapesize++; + } while (src != lineEnd); + + srcLineStart += SCREEN_W; + src = srcLineStart; + } + + shapesize += 4; + + shp = new uint8[shapesize]; + memset(shp, 0, shapesize); + uint8 *dst = shp; + + *dst++ = 1; + *dst++ = (h & 0xFF); + *dst++ = (w & 0xFF); + *dst++ = (h & 0xFF); + + srcLineStart = getPagePtr(_curPage | 1) + y * 320 + (x << 3); + src = srcLineStart; + + h1 = h; + while (h1--) { + uint8 *lineEnd = src + (w << 3); + do { + uint8 val = *src++; + if (!val) { + *dst++ = 0; + uint8 *startZeroPos = src; + + while (src != lineEnd && *src == 0) + src++; + + uint16 numZero = src - startZeroPos + 1; + if (numZero >> 8) { + *dst++ = 255; + *dst++ = 0; + numZero -= 255; + } + val = numZero & 0xFF; + } + *dst++ = val; + } while (src != lineEnd); + + srcLineStart += SCREEN_W; + src = srcLineStart; + } + + } else { + uint8 nib = 0, col = 0; + uint8 *colorMap = 0; + + if (_renderMode != Common::kRenderEGA || _useHiResEGADithering) { + colorMap = new uint8[0x100]; + memset(colorMap, 0xFF, 0x100); + } + + shapesize = h * (w << 2) + 20; + shp = new uint8[shapesize]; + memset(shp, 0, shapesize); + uint8 *dst = shp; + + *dst++ = 2; + *dst++ = (h & 0xFF); + *dst++ = (w & 0xFF); + *dst++ = (h & 0xFF); + + if (_renderMode != Common::kRenderEGA || _useHiResEGADithering) { + memset(dst, 0xFF, 0x10); + } else { + for (int i = 0; i < 16; i++) + dst[i] = i; + } + + uint8 *pal = dst; + dst += 16; + nib = col = 0; + + uint16 h1 = h; + while (h1--) { + uint16 w1 = w << 3; + while (w1--) { + uint8 s = *src++; + uint8 c = s & 0x0F; + if (colorMap) { + c = colorMap[s]; + if (c == 0xFF) { + if (col < 0x10) { + *pal++ = s; + c = colorMap[s] = col++; + } else { + c = 0; + } + } + } + + if (++nib & 1) + *dst = c << 4; + else + *dst++ |= c; + } + srcLineStart += SCREEN_W; + src = srcLineStart; + } + delete[] colorMap; + } + + return shp; +} + +void Screen_EoB::drawShape(uint8 pageNum, const uint8 *shapeData, int x, int y, int sd, int flags, ...) { + uint8 *dst = getPagePtr(pageNum); + const uint8 *src = shapeData; + + if (!src) + return; + + if (sd != -1) { + const ScreenDim *dm = getScreenDim(sd); + setShapeFrame(dm->sx, dm->sy, dm->sx + dm->w, dm->sy + dm->h); + x += (_dsX1 << 3); + y += _dsY1; + } + + uint8 *ovl = 0; + + va_list args; + va_start(args, flags); + if (flags & 2) { + ovl = va_arg(args, uint8 *); + _dsBackgroundFadingXOffs = x; + } + va_end(args); + + dst += (_dsX1 << (2 + _bytesPerPixel)); + int16 dX = x - (_dsX1 << 3); + int16 dY = y; + int16 dW = _dsX2 - _dsX1; + + uint8 pixelsPerByte = *src++; + uint16 dH = *src++; + uint16 width = (*src++) << 3; + uint16 transOffset = (pixelsPerByte == 4) ? (dH * width) >> 2 : 0; + src++; + + int rX = x; + int rY = y; + int rW = width + 8; + int rH = dH; + + uint16 w2 = width; + int d = dY - _dsY1; + + int pixelStep = (flags & 1) ? -1 : 1; + + if (pixelsPerByte < 2) { + uint16 marginLeft = 0; + uint16 marginRight = 0; + + if (d < 0) { + dH += d; + if (dH <= 0) + return; + d = -d; + + for (int i = 0; i < d; i++) { + marginLeft = width; + for (int ii = 0; ii < marginLeft; ii++) { + if (!*src++) + marginLeft = marginLeft + 1 - *src++; + } + } + dY = _dsY1; + } + + d = _dsY2 - dY; + + if (d < 1) + return; + + if (d < dH) + dH = d; + + marginLeft = 0; + + if (dX < 0) { + width += dX; + marginLeft = -dX; + + if (marginLeft >= w2) + return; + + dX = 0; + } + + marginRight = 0; + d = (dW << 3) - dX; + + if (d < 1) + return; + + if (d < width) { + width = d; + marginRight = w2 - marginLeft - width; + } + + dst += (dY * SCREEN_W * _bytesPerPixel + dX * _bytesPerPixel); + uint8 *dstL = dst; + + if (pageNum == 0 || pageNum == 1) + addDirtyRect(rX, rY, rW, rH); + + while (dH--) { + int16 xpos = (int16) marginLeft; + + if (flags & 1) { + if (pixelsPerByte == 1) { + for (int i = 0; i < w2; i++) { + if (*src++ == 0) { + i += (*src - 1); + src += (*src - 1); + } + } + } else { + src += w2; + } + src--; + } + const uint8 *src2 = src; + + if (xpos) { + if (pixelsPerByte == 1) { + do { + uint8 val = (flags & 1) ? *(src - 1) : *src; + while (val && xpos) { + src += pixelStep; + xpos--; + val = (flags & 1) ? *(src - 1) : *src; + } + + val = (flags & 1) ? *(src - 1) : *src; + if (!val) { + src += pixelStep; + uint8 bt = (flags & 1) ? src[1] : src[0]; + src += pixelStep; + xpos = xpos - bt; + } + } while (xpos > 0); + } else { + src += (xpos * pixelStep); + xpos = 0; + } + } + + dst -= xpos * _bytesPerPixel; + xpos += width; + + while (xpos > 0) { + uint8 c = *src; + uint8 m = (flags & 1) ? *(src - 1) : c; + src += pixelStep; + + if (m) { + drawShapeSetPixel(dst, c); + dst += _bytesPerPixel; + xpos--; + } else if (pixelsPerByte) { + uint8 len = (flags & 1) ? src[1] : src[0]; + dst += len * _bytesPerPixel; + xpos -= len; + src += pixelStep; + } else { + dst += _bytesPerPixel; + xpos--; + } + } + xpos += marginRight; + + if (xpos) { + do { + if (pixelsPerByte == 1) { + uint8 val = (flags & 1) ? *(src - 1) : *src; + while (val && xpos) { + src += pixelStep; + xpos--; + val = (flags & 1) ? *(src - 1) : *src; + } + + val = (flags & 1) ? *(src - 1) : *src; + if (!val) { + src += pixelStep; + uint8 bt = (flags & 1) ? src[1] : src[0]; + src += pixelStep; + xpos = xpos - bt; + } + } else { + src += (xpos * pixelStep); + xpos = 0; + } + } while (xpos > 0); + } + + dstL += SCREEN_W * _bytesPerPixel; + dst = dstL; + if (flags & 1) + src = src2 + 1; + } + } else { + const uint8 *pal = 0; + uint8 cgaPal[4]; + memset(cgaPal, 0, 4); + + if (pixelsPerByte == 2) { + pal = ovl ? ovl : src; + src += 16; + } else { + static const uint8 cgaDefOvl[] = { 0x00, 0x55, 0xAA, 0xFF }; + pal = ovl ? ovl : cgaDefOvl; + for (int i = 0; i < 4; i++) + cgaPal[i] = pal[i] & 3; + pal = cgaPal; + } + + if (d < 0) { + d = -d; + if (d >= dH) + return; + src += (d * (width / pixelsPerByte)); + d = dY + dH - _dsY1; + if (d >= 0) { + dH = d; + dY = _dsY1; + d = _dsY2 - dY; + } + } else { + d = _dsY2 - dY; + } + + if (d < 1) + return; + + if (d < dH) + dH = d; + + bool trimL = false; + uint8 dXbitAlign = dX & (pixelsPerByte - 1); + + if (dX < 0) { + width += dX; + d = -dX; + if (flags & 1) + src -= (d / pixelsPerByte); + else + src += (d / pixelsPerByte); + + if (d >= w2) + return; + + dX = 0; + trimL = true; + } + + d = (dW << 3) - dX; + + if (d < 1) + return; + + if (d < width) + width = d; + + dst += (dY * SCREEN_W * _bytesPerPixel + dX * _bytesPerPixel); + + if (pageNum == 0 || pageNum == 1) + addDirtyRect(rX, rY, rW, rH); + + int pitch = SCREEN_W - width; + int16 lineSrcStep = (w2 - width) / pixelsPerByte; + uint8 lineSrcStepRemainder = (w2 - width) % pixelsPerByte; + + w2 /= pixelsPerByte; + if (flags & 1) + src += (w2 - 1); + + uint8 pixelPacking = 8 / pixelsPerByte; + uint8 pixelPackingMask = 0; + + for (int i = 0; i < pixelPacking; i++) + pixelPackingMask |= (1 << i); + + if (trimL && (dXbitAlign > lineSrcStepRemainder)) + lineSrcStep--; + + uint8 bitShDef = 8 - pixelPacking; + if (flags & 1) { + lineSrcStep = (w2 << 1) - lineSrcStep; + bitShDef = 0; + } + + uint8 bitShLineStart = bitShDef; + if (trimL) + bitShLineStart -= (dXbitAlign * pixelStep * pixelPacking); + + while (dH--) { + int16 wd = width; + uint8 in = 0; + uint8 trans = 0; + uint8 shift = bitShLineStart; + uint8 shSwtch = bitShLineStart; + + while (wd--) { + if (shift == shSwtch) { + in = *src; + trans = src[transOffset]; + src += pixelStep; + shSwtch = bitShDef; + } + uint8 col = (pixelsPerByte == 2) ? pal[(in >> shift) & pixelPackingMask] : (*dst & ((trans >> shift) & (pixelPackingMask))) | pal[(in >> shift) & pixelPackingMask]; + if (col || pixelsPerByte == 4) + drawShapeSetPixel(dst, col); + dst += _bytesPerPixel; + shift = ((shift - (pixelStep * pixelPacking)) & 7); + } + src += lineSrcStep; + dst += (pitch * _bytesPerPixel); + } + } +} + +const uint8 *Screen_EoB::scaleShape(const uint8 *shapeData, int steps) { + setShapeFadingLevel(steps); + + while (shapeData && steps--) + shapeData = scaleShapeStep(shapeData); + + return shapeData; +} + +const uint8 *Screen_EoB::scaleShapeStep(const uint8 *shp) { + uint8 *dst = (shp != _dsTempPage) ? _dsTempPage : _dsTempPage + 6000; + uint8 *d = dst; + uint8 pixelsPerByte = *d++ = *shp++; + assert(pixelsPerByte > 1); + + uint16 h = shp[0] + 1; + d[0] = d[2] = (h << 1) / 3; + + uint16 w = shp[1]; + uint16 w2 = (w << 3) / pixelsPerByte; + uint16 t = ((w << 1) % 3) ? 1 : 0; + d[1] = ((w << 1) / 3) + t; + + uint32 transOffsetSrc = (pixelsPerByte == 4) ? (shp[0] * shp[1]) << 1 : 0; + uint32 transOffsetDst = (pixelsPerByte == 4) ? (d[0] * d[1]) << 1 : 0; + shp += 3; + d += 3; + + if (pixelsPerByte == 2) { + int i = 0; + while (i < 16) { + if (!shp[i]) { + i = -i; + break; + } + i++; + } + + if (i >= 0) + i = 0; + else + i = -i; + + _dsScaleTrans = (i << 4) | (i & 0x0F); + for (int ii = 0; ii < 16; ii++) + *d++ = *shp++; + } + + _dsDiv = w2 / 3; + _dsRem = w2 % 3; + + while (--h) { + if (pixelsPerByte == 2) + scaleShapeProcessLine4Bit(d, shp); + else + scaleShapeProcessLine2Bit(d, shp, transOffsetDst, transOffsetSrc); + if (!--h) + break; + if (pixelsPerByte == 2) + scaleShapeProcessLine4Bit(d, shp); + else + scaleShapeProcessLine2Bit(d, shp, transOffsetDst, transOffsetSrc); + if (!--h) + break; + shp += w2; + } + + return (const uint8 *)dst; +} + +const uint8 *Screen_EoB::generateShapeOverlay(const uint8 *shp, const uint8 *fadingTable) { + if (*shp != 2) + return 0; + + if (_bytesPerPixel == 2) { + setFadeTable(fadingTable); + setShapeFadingLevel(1); + return 0; + } + + shp += 4; + for (int i = 0; i < 16; i++) + _shapeOverlay[i] = fadingTable[shp[i]]; + return _shapeOverlay; +} + +void Screen_EoB::setShapeFrame(int x1, int y1, int x2, int y2) { + _dsX1 = x1; + _dsY1 = y1; + _dsX2 = x2; + _dsY2 = y2; +} + +void Screen_EoB::enableShapeBackgroundFading(bool enable) { + _dsBackgroundFading = enable; +} + +void Screen_EoB::setShapeFadingLevel(int level) { + _dsShapeFadingLevel = level; +} + +void Screen_EoB::setGfxParameters(int x, int y, int col) { + _gfxX = x; + _gfxY = y; + _gfxCol = col; +} + +void Screen_EoB::drawExplosion(int scale, int radius, int numElements, int stepSize, int aspectRatio, const uint8 *colorTable, int colorTableSize) { + int ymin = 0; + int ymax = _gfxMaxY[scale]; + int xmin = -100; + int xmax = 276; + + if (scale) + --scale; + + hideMouse(); + + const ScreenDim *dm = getScreenDim(5); + int rX1 = dm->sx << 3; + int rY1 = dm->sy; + int rX2 = rX1 + (dm->w << 3); + int rY2 = rY1 + dm->h - 1; + + int16 gx2 = _gfxX; + int16 gy2 = _gfxY; + + int16 *ptr2 = (int16 *)_dsTempPage; + int16 *ptr3 = (int16 *)&_dsTempPage[300]; + int16 *ptr4 = (int16 *)&_dsTempPage[600]; + int16 *ptr5 = (int16 *)&_dsTempPage[900]; + int16 *ptr6 = (int16 *)&_dsTempPage[1200]; + int16 *ptr7 = (int16 *)&_dsTempPage[1500]; + int16 *ptr8 = (int16 *)&_dsTempPage[1800]; + + if (numElements > 150) + numElements = 150; + + for (int i = 0; i < numElements; i++) { + ptr2[i] = ptr3[i] = 0; + ptr4[i] = _vm->_rnd.getRandomNumberRng(0, radius) - (radius >> 1); + ptr5[i] = _vm->_rnd.getRandomNumberRng(0, radius) - (radius >> 1) - (radius >> (8 - aspectRatio)); + ptr7[i] = _vm->_rnd.getRandomNumberRng(1024 / stepSize, 2048 / stepSize); + ptr8[i] = scale << 8; + } + + for (int l = 2; l;) { + if (l != 2) { + for (int i = numElements - 1; i >= 0; i--) { + int16 px = ((ptr2[i] >> 6) >> scale) + gx2; + int16 py = ((ptr3[i] >> 6) >> scale) + gy2; + if (py > ymax) + py = ymax; + if (posWithinRect(px, py, rX1, rY1, rX2, rY2)) { + if (_bytesPerPixel == 2) + setPagePixel16bit(0, px, py, ptr6[i]); + else + setPagePixel(0, px, py, ptr6[i]); + } + } + } + + l = 0; + + for (int i = 0; i < numElements; i++) { + uint32 end = _system->getMillis() + 1; + if (ptr4[i] <= 0) + ptr4[i]++; + else + ptr4[i]--; + ptr2[i] += ptr4[i]; + ptr5[i] += 5; + ptr3[i] += ptr5[i]; + ptr8[i] += ptr7[i]; + + int16 px = ((ptr2[i] >> 6) >> scale) + gx2; + int16 py = ((ptr3[i] >> 6) >> scale) + gy2; + if (py >= ymax || py < ymin) + ptr5[i] = -(ptr5[i] >> 1); + if (px >= xmax || px < xmin) + ptr4[i] = -(ptr4[i] >> 1); + + if (py > ymax) + py = ymax; + + int pxVal1 = 0; + if (posWithinRect(px, py, 0, 0, 319, 199)) { + pxVal1 = getPagePixel(2, px, py); + ptr6[i] = getPagePixel(0, px, py); + } + + assert((ptr8[i] >> 8) < colorTableSize); + int pxVal2 = colorTable[ptr8[i] >> 8]; + if (pxVal2) { + l = 1; + if (pxVal1 == _gfxCol && posWithinRect(px, py, rX1, rY1, rX2, rY2)) { + setPagePixel(0, px, py, pxVal2); + if (i % 5 == 0) { + updateScreen(); + uint32 cur = _system->getMillis(); + if (end > cur) + _system->delayMillis(end - cur); + } + } + } else { + ptr7[i] = 0; + } + } + } + + showMouse(); +} + +void Screen_EoB::drawVortex(int numElements, int radius, int stepSize, int, int disorder, const uint8 *colorTable, int colorTableSize) { + int16 *xCoords = (int16 *)_dsTempPage; + int16 *yCoords = (int16 *)&_dsTempPage[300]; + int16 *xMod = (int16 *)&_dsTempPage[600]; + int16 *yMod = (int16 *)&_dsTempPage[900]; + int16 *pixBackup = (int16 *)&_dsTempPage[1200]; + int16 *colTableStep = (int16 *)&_dsTempPage[1500]; + int16 *colTableIndex = (int16 *)&_dsTempPage[1800]; + int16 *pixDelay = (int16 *)&_dsTempPage[2100]; + + hideMouse(); + int cp = _curPage; + + if (numElements > 150) + numElements = 150; + + int cx = 88; + int cy = 48; + radius <<= 6; + + for (int i = 0; i < numElements; i++) { + int16 v38 = _vm->_rnd.getRandomNumberRng(radius >> 2, radius); + int16 stepSum = 0; + int16 sqsum = 0; + while (sqsum < v38) { + stepSum += stepSize; + sqsum += stepSum; + } + + switch (_vm->_rnd.getRandomNumber(255) & 3) { + case 0: + xCoords[i] = 32; + yCoords[i] = sqsum; + xMod[i] = stepSum; + yMod[i] = 0; + break; + + case 1: + xCoords[i] = sqsum; + yCoords[i] = 32; + xMod[i] = 0; + yMod[i] = stepSum; + break; + + case 2: + xCoords[i] = 32; + yCoords[i] = -sqsum; + xMod[i] = stepSum; + yMod[i] = 0; + break; + + default: + xCoords[i] = -sqsum; + yCoords[i] = 32; + xMod[i] = 0; + yMod[i] = stepSum; + break; + } + + if (_vm->_rnd.getRandomBit()) { + xMod[i] *= -1; + yMod[i] *= -1; + } + + colTableStep[i] = _vm->_rnd.getRandomNumberRng(1024 / disorder, 2048 / disorder); + colTableIndex[i] = 0; + pixDelay[i] = _vm->_rnd.getRandomNumberRng(0, disorder >> 2); + } + + int d = 0; + for (int i = 2; i;) { + if (i != 2) { + for (int ii = numElements - 1; ii >= 0; ii--) { + int16 px = CLIP((xCoords[ii] >> 6) + cx, 0, SCREEN_W - 1); + int16 py = CLIP((yCoords[ii] >> 6) + cy, 0, SCREEN_H - 1); + if (_bytesPerPixel == 2) + setPagePixel16bit(0, px, py, pixBackup[ii]); + else + setPagePixel(0, px, py, pixBackup[ii]); + } + } + + i = 0; + int r = (stepSize >> 1) + (stepSize >> 2) + (stepSize >> 3); + uint32 nextDelay = _system->getMillis() + 1; + + for (int ii = 0; ii < numElements; ii++) { + if (pixDelay[ii] == 0) { + if (xCoords[ii] > 0) { + xMod[ii] -= ((xMod[ii] > 0) ? stepSize : r); + } else { + xMod[ii] += ((xMod[ii] < 0) ? stepSize : r); + } + + if (yCoords[ii] > 0) { + yMod[ii] -= ((yMod[ii] > 0) ? stepSize : r); + } else { + yMod[ii] += ((yMod[ii] < 0) ? stepSize : r); + } + + xCoords[ii] += xMod[ii]; + yCoords[ii] += yMod[ii]; + colTableIndex[ii] += colTableStep[ii]; + + } else { + pixDelay[ii]--; + } + + int16 px = CLIP((xCoords[ii] >> 6) + cx, 0, SCREEN_W - 1); + int16 py = CLIP((yCoords[ii] >> 6) + cy, 0, SCREEN_H - 1); + + uint8 tc1 = ((disorder >> 2) <= d) ? getPagePixel(2, px, py) : 0; + pixBackup[ii] = getPagePixel(0, px, py); + uint8 tblIndex = CLIP(colTableIndex[ii] >> 8, 0, colorTableSize - 1); + uint8 tc2 = colorTable[tblIndex]; + + if (tc2) { + i = 1; + if (tc1 == _gfxCol && !pixDelay[ii]) { + setPagePixel(0, px, py, tc2); + if (ii % 15 == 0) { + updateScreen(); + uint32 cur = _system->getMillis(); + if (nextDelay > cur) + _system->delayMillis(nextDelay - cur); + nextDelay += 1; + } + } + } else { + colTableStep[ii] = 0; + } + } + d++; + } + + _curPage = cp; + showMouse(); +} + +void Screen_EoB::fadeTextColor(Palette *pal, int color1, int rate) { + uint8 *col = pal->getData(); + + for (bool loop = true; loop;) { + loop = true; + uint32 end = _system->getMillis() + _vm->tickLength(); + + loop = false; + for (int ii = 0; ii < 3; ii++) { + uint8 c = col[color1 * 3 + ii]; + if (c > rate) { + col[color1 * 3 + ii] -= rate; + loop = true; + } else if (c) { + col[color1 * 3 + ii] = 0; + loop = true; + } + } + + if (loop) { + setScreenPalette(*pal); + updateScreen(); + uint32 cur = _system->getMillis(); + if (end > cur) + _system->delayMillis(end - cur); + } + } +} + +bool Screen_EoB::delayedFadePalStep(Palette *fadePal, Palette *destPal, int rate) { + bool res = false; + + uint8 *s = fadePal->getData(); + uint8 *d = destPal->getData(); + + for (int i = 0; i < 765; i++) { + int fadeVal = *s++; + int dstCur = *d; + int diff = ABS(fadeVal - dstCur); + + if (diff == 0) { + d++; + continue; + } + + res = true; + diff = MIN(diff, rate); + + if (dstCur < fadeVal) + *d += diff; + else + *d -= diff; + d++; + } + + return res; +} + +int Screen_EoB::getRectSize(int w, int h) { + return w * h; +} + +void Screen_EoB::setFadeTable(const uint8 *table) { + _dsShapeFadingTable = table; + if (_bytesPerPixel == 2) + memcpy(&_16bitPalette[0x100], table, 512); +} + +void Screen_EoB::createFadeTable(const uint8 *palData, uint8 *dst, uint8 rootColor, uint8 weight) { + if (!palData) + return; + + const uint8 *src = palData + 3 * rootColor; + uint8 r = *src++; + uint8 g = *src++; + uint8 b = *src; + uint8 tr, tg, tb; + src = palData + 3; + + *dst++ = 0; + weight >>= 1; + + for (uint8 i = 1; i; i++) { + uint16 tmp = (uint16)((*src - r) * weight) << 1; + tr = *src++ - ((tmp >> 8) & 0xFF); + tmp = (uint16)((*src - g) * weight) << 1; + tg = *src++ - ((tmp >> 8) & 0xFF); + tmp = (uint16)((*src - b) * weight) << 1; + tb = *src++ - ((tmp >> 8) & 0xFF); + + const uint8 *d = palData + 3; + uint16 v = 0xFFFF; + uint8 col = rootColor; + + for (uint8 ii = 1; ii; ii++) { + int a = *d++ - tr; + int t = a * a; + a = *d++ - tg; + t += (a * a); + a = *d++ - tb; + t += (a * a); + + if (t <= v && (ii == rootColor || ii != i)) { + v = t; + col = ii; + } + } + *dst++ = col; + } +} + +void Screen_EoB::createFadeTable16bit(const uint16 *palData, uint16 *dst, uint16 rootColor, uint8 weight) { + rootColor = palData[rootColor]; + uint8 r8 = (rootColor & 0x1f); + uint8 g8 = (rootColor & 0x3E0) >> 5; + uint8 b8 = (rootColor & 0x7C00) >> 10; + + int root_r = r8 << 4; + int root_g = g8 << 4; + int root_b = b8 << 4; + + *dst++ = palData[0]; + + for (uint8 i = 1; i; i++) { + r8 = (palData[i] & 0x1f); + g8 = (palData[i] & 0x3E0) >> 5; + b8 = (palData[i] & 0x7C00) >> 10; + + int red = r8 << 4; + int green = g8 << 4; + int blue = b8 << 4; + + if (red > root_r) { + red -= weight; + if (root_r > red) + red = root_r; + } else { + red += weight; + if (root_r < red) + red = root_r; + } + + if (green > root_g) { + green -= weight; + if (root_g > green) + green = root_g; + } else { + green += weight; + if (root_g < green) + green = root_g; + } + + if (blue > root_b) { + blue -= weight; + if (root_b > blue) + blue = root_b; + } else { + blue += weight; + if (root_b < blue) + blue = root_b; + } + + r8 = red >> 4; + g8 = green >> 4; + b8 = blue >> 4; + + *dst++ = (b8 << 10) | (g8 << 5) | r8; + } +} + +const uint16 *Screen_EoB::getCGADitheringTable(int index) { + return !(index & ~1) ? _cgaDitheringTables[index] : 0; +} + +const uint8 *Screen_EoB::getEGADitheringTable() { + return _egaDitheringTable; +} + +bool Screen_EoB::loadFont(FontId fontId, const char *filename) { + if (scumm_stricmp(filename, "FONT.DMP")) + return Screen::loadFont(fontId, filename); + + Font *&fnt = _fonts[fontId]; + int temp; + + const uint16 *tbl = _vm->staticres()->loadRawDataBe16(kEoB2FontDmpSearchTbl, temp); + assert(tbl); + + if (!fnt) { + fnt = new SJISFont12x12(tbl); + assert(fnt); + } + + Common::SeekableReadStream *file = _vm->resource()->createReadStream(filename); + if (!file) + error("Font file '%s' is missing", filename); + + bool ret = fnt->load(*file); + fnt->setColorMap(_textColorsMap); + delete file; + return ret; +} + +void Screen_EoB::decodeSHP(const uint8 *data, int dstPage) { + int32 bytesLeft = READ_LE_UINT32(data); + const uint8 *src = data + 4; + uint8 *dst = getPagePtr(dstPage); + + if (bytesLeft < 0) { + memcpy(dst, data, 64000); + return; + } + + while (bytesLeft > 0) { + uint8 code = *src++; + bytesLeft--; + + for (int i = 8; i; i--) { + if (code & 0x80) { + uint16 copyOffs = (src[0] << 4) | (src[1] >> 4); + uint8 count = (src[1] & 0x0F) + 3; + src += 2; + bytesLeft -= 2; + const uint8 *copySrc = dst - 1 - copyOffs; + while (count--) + *dst++ = *copySrc++; + } else if (bytesLeft) { + *dst++ = *src++; + bytesLeft--; + } else { + break; + } + code <<= 1; + } + } +} + +void Screen_EoB::convertToHiColor(int page) { + if (!_16bitPalette) + return; + uint16 *dst = (uint16 *)getPagePtr(page); + memcpy(_convertHiColorBuffer, dst, SCREEN_H * SCREEN_W); + uint8 *src = _convertHiColorBuffer; + for (int s = SCREEN_H * SCREEN_W; s; --s) + *dst++ = _16bitPalette[*src++]; +} + +void Screen_EoB::shadeRect(int x1, int y1, int x2, int y2, int shadingLevel) { + if (!_16bitPalette) + return; + + int l = _16bitShadingLevel; + _16bitShadingLevel = shadingLevel; + + if (_curPage == 0 || _curPage == 1) + addDirtyRect(x1, y1, x2 - x1 + 1, y2 - y1 + 1); + + uint16 *dst = (uint16*)(getPagePtr(_curPage) + y1 * SCREEN_W * _bytesPerPixel + x1 * _bytesPerPixel); + + for (; y1 < y2; ++y1) { + uint16 *ptr = dst; + for (int i = 0; i < x2 - x1; i++) { + *ptr = shade16bitColor(*ptr); + ptr++; + } + dst += SCREEN_W; + } + + _16bitShadingLevel = l; +} + +void Screen_EoB::updateDirtyRects() { + if (!_useHiResEGADithering) { + Screen::updateDirtyRects(); + return; + } + + if (_forceFullUpdate) { + ditherRect(getCPagePtr(0), _egaDitheringTempPage, SCREEN_W * 2, SCREEN_W, SCREEN_H); + _system->copyRectToScreen(_egaDitheringTempPage, SCREEN_W * 2, 0, 0, SCREEN_W * 2, SCREEN_H * 2); + } else { + const byte *page0 = getCPagePtr(0); + Common::List<Common::Rect>::iterator it; + for (it = _dirtyRects.begin(); it != _dirtyRects.end(); ++it) { + ditherRect(page0 + it->top * SCREEN_W + it->left, _egaDitheringTempPage, SCREEN_W * 2, it->width(), it->height()); + _system->copyRectToScreen(_egaDitheringTempPage, SCREEN_W * 2, it->left * 2, it->top * 2, it->width() * 2, it->height() * 2); + } + } + _forceFullUpdate = false; + _dirtyRects.clear(); +} + +void Screen_EoB::ditherRect(const uint8 *src, uint8 *dst, int dstPitch, int srcW, int srcH, int colorKey) { + while (srcH--) { + uint8 *dst2 = dst + dstPitch; + for (int i = 0; i < srcW; i++) { + int in = *src++; + if (in != colorKey) { + in = _egaDitheringTable[in]; + *dst++ = *dst2++ = in >> 4; + *dst++ = *dst2++ = in & 0x0F; + } else { + dst[0] = dst[1] = dst2[0] = dst2[1] = colorKey; + dst += 2; + dst2 += 2; + } + } + src += (SCREEN_W - srcW); + dst += ((dstPitch - srcW) * 2); + } +} + +void Screen_EoB::drawShapeSetPixel(uint8 *dst, uint8 col) { + if (_bytesPerPixel == 2) { + *(uint16*)dst = _16bitPalette[(_dsShapeFadingLevel << 8) + col]; + return; + } else if ((_renderMode != Common::kRenderCGA && _renderMode != Common::kRenderEGA) || _useHiResEGADithering) { + if (_dsBackgroundFading) { + if (_dsShapeFadingLevel) { + col = *dst; + } else { + _dsBackgroundFadingXOffs &= 7; + col = *(dst + _dsBackgroundFadingXOffs++); + } + } + + if (_dsShapeFadingLevel) { + uint8 cnt = _dsShapeFadingLevel; + while (cnt--) + col = _dsShapeFadingTable[col]; + } + } + + *dst = col; +} + +void Screen_EoB::scaleShapeProcessLine2Bit(uint8 *&shpDst, const uint8 *&shpSrc, uint32 transOffsetDst, uint32 transOffsetSrc) { + for (int i = 0; i < _dsDiv; i++) { + shpDst[0] = (_cgaScaleTable[shpSrc[0]] << 2) | (shpSrc[1] >> 6); + shpDst[1] = ((shpSrc[1] & 0x0F) << 4) | ((shpSrc[2] >> 2) & 0x0F); + shpDst[transOffsetDst] = (_cgaScaleTable[shpSrc[transOffsetSrc]] << 2) | (shpSrc[transOffsetSrc + 1] >> 6); + shpDst[transOffsetDst + 1] = ((shpSrc[transOffsetSrc + 1] & 0x0F) << 4) | ((shpSrc[transOffsetSrc + 2] >> 2) & 0x0F); + shpSrc += 3; + shpDst += 2; + } + + if (_dsRem == 1) { + shpDst[0] = _cgaScaleTable[shpSrc[0]] << 2; + shpDst[1] = 0; + shpDst[transOffsetDst] = (_cgaScaleTable[shpSrc[transOffsetSrc]] << 2) | 3; + shpDst[transOffsetDst + 1] = 0xFF; + shpSrc++; + shpDst += 2; + + } else if (_dsRem == 2) { + shpDst[0] = (_cgaScaleTable[shpSrc[0]] << 2) | (shpSrc[1] >> 6); + shpDst[1] = (shpSrc[1] & 0x3F) << 2; + shpDst[transOffsetDst] = (_cgaScaleTable[shpSrc[transOffsetSrc]] << 2) | (shpSrc[transOffsetSrc + 1] >> 6); + shpDst[transOffsetDst + 1] = ((shpSrc[transOffsetSrc + 1] & 0x3F) << 2) | 3; + shpSrc += 2; + shpDst += 2; + } +} + +void Screen_EoB::scaleShapeProcessLine4Bit(uint8 *&dst, const uint8 *&src) { + for (int i = 0; i < _dsDiv; i++) { + *dst++ = *src++; + *dst++ = (READ_BE_UINT16(src) >> 4) & 0xFF; + src += 2; + } + + if (_dsRem == 1) { + *dst++ = *src++; + *dst++ = _dsScaleTrans; + } else if (_dsRem == 2) { + *dst++ = (src[0] & 0xF0) | (src[1] >> 4); + src += 2; + *dst++ = _dsScaleTrans; + *dst++ = _dsScaleTrans; + *dst++ = _dsScaleTrans; + } +} + +bool Screen_EoB::posWithinRect(int posX, int posY, int x1, int y1, int x2, int y2) { + if (posX < x1 || posX > x2 || posY < y1 || posY > y2) + return false; + return true; +} + +void Screen_EoB::setPagePixel16bit(int pageNum, int x, int y, uint16 color) { + assert(pageNum < SCREEN_PAGE_NUM); + assert(x >= 0 && x < SCREEN_W && y >= 0 && y < SCREEN_H); + assert(_bytesPerPixel == 2); + + if (pageNum == 0 || pageNum == 1) + addDirtyRect(x, y, 1, 1); + + ((uint16*)_pagePtrs[pageNum])[y * SCREEN_W + x] = color; +} + +void Screen_EoB::generateEGADitheringTable(const Palette &pal) { + assert(_egaDitheringTable); + const uint8 *src = pal.getData(); + uint8 *dst = _egaDitheringTable; + + for (int i = 256; i; i--) { + int r = *src++; + int g = *src++; + int b = *src++; + + uint8 col = 0; + uint16 min = 0x2E83; + + for (int ii = 256; ii; ii--) { + const uint8 *palEntry = _egaMatchTable + (ii - 1) * 3; + if (*palEntry == 0xFF) + continue; + + int e_r = palEntry[0] - r; + int e_g = palEntry[1] - g; + int e_b = palEntry[2] - b; + + uint16 s = (e_r * e_r) + (e_g * e_g) + (e_b * e_b); + + if (s <= min) { + min = s; + col = ii - 1; + } + } + *dst++ = col; + } +} + +void Screen_EoB::generateCGADitheringTables(const uint8 *mappingData) { + for (int i = 0; i < 256; i++) { + _cgaDitheringTables[0][i] = (mappingData[(i >> 4) + 16] << 8) | mappingData[i & 0x0F]; + _cgaDitheringTables[1][i] = (mappingData[i >> 4] << 8) | mappingData[(i & 0x0F) + 16]; + } +} + +const uint8 Screen_EoB::_egaMatchTable[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x1E, 0x00, 0x00, 0x1E, 0x1E, 0x1E, 0x00, 0x00, 0x1E, + 0x00, 0x1E, 0x1E, 0x0F, 0x00, 0x1E, 0x1E, 0x1E, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x2D, 0x0F, 0x2D, + 0x0F, 0x0F, 0x2D, 0x2D, 0x2D, 0x0F, 0x0F, 0x2D, 0x0F, 0x2D, 0x2D, 0x2D, 0x0F, 0x2D, 0x2D, 0x2D, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x2A, 0x00, 0x1E, 0x1E, 0x00, 0x1E, 0x2A, 0x1E, 0x00, 0x1E, 0x1E, + 0x00, 0x2A, 0x1E, 0x0F, 0x1E, 0x1E, 0x1E, 0x2A, 0x0F, 0x0F, 0x21, 0x0F, 0x0F, 0x36, 0x0F, 0x2D, + 0x21, 0x0F, 0x2D, 0x36, 0x2D, 0x0F, 0x21, 0x2D, 0x0F, 0x36, 0x2D, 0x2D, 0x21, 0x2D, 0x2D, 0x36, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x2A, 0x00, 0x00, 0x2A, 0x1E, 0x1E, 0x1E, 0x00, 0x1E, + 0x1E, 0x1E, 0x1E, 0x21, 0x00, 0x1E, 0x2A, 0x1E, 0x0F, 0x21, 0x0F, 0x0F, 0x21, 0x2D, 0x0F, 0x36, + 0x0F, 0x0F, 0x36, 0x2D, 0x2D, 0x21, 0x0F, 0x2D, 0x21, 0x2D, 0x2D, 0x36, 0x0F, 0x2D, 0x36, 0x2D, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x2A, 0x2A, 0x1E, 0x1E, 0x1E, 0x1E, + 0x1E, 0x2A, 0x1E, 0x21, 0x1E, 0x1E, 0x2A, 0x2A, 0x0F, 0x21, 0x21, 0x0F, 0x21, 0x36, 0x0F, 0x36, + 0x21, 0x0F, 0x36, 0x36, 0x2D, 0x21, 0x21, 0x2D, 0x21, 0x36, 0x2D, 0x36, 0x21, 0x2D, 0x36, 0x36, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x2A, 0x00, 0x00, 0x2A, + 0x00, 0x1E, 0x2A, 0x0F, 0x00, 0x2A, 0x1E, 0x1E, 0x21, 0x0F, 0x0F, 0x21, 0x0F, 0x2D, 0x21, 0x2D, + 0x0F, 0x21, 0x2D, 0x2D, 0x36, 0x0F, 0x0F, 0x36, 0x0F, 0x2D, 0x36, 0x2D, 0x0F, 0x36, 0x2D, 0x2D, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x2A, + 0x00, 0x2A, 0x2A, 0x0F, 0x1E, 0x2A, 0x1E, 0x2A, 0x21, 0x0F, 0x21, 0x21, 0x0F, 0x36, 0x21, 0x2D, + 0x21, 0x21, 0x2D, 0x36, 0x36, 0x0F, 0x21, 0x36, 0x0F, 0x36, 0x36, 0x2D, 0x21, 0x36, 0x2D, 0x36, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2A, 0x15, 0x00, 0x2A, 0x21, 0x1E, 0x21, 0x15, 0x0F, 0x21, 0x15, 0x2D, 0x21, 0x2F, + 0x0F, 0x21, 0x2F, 0x2D, 0x36, 0x15, 0x0F, 0x36, 0x15, 0x2D, 0x36, 0x2F, 0x0F, 0x36, 0x2F, 0x2D, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x2A, 0x2A, 0x2A, 0x21, 0x21, 0x21, 0x21, 0x21, 0x36, 0x21, 0x36, + 0x21, 0x21, 0x36, 0x36, 0x36, 0x21, 0x21, 0x36, 0x21, 0x36, 0x36, 0x36, 0x21, 0x36, 0x36, 0x36, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x15, 0x15, 0x15, 0x15, 0x15, 0x2F, 0x15, 0x2F, + 0x15, 0x15, 0x2F, 0x2F, 0x2F, 0x15, 0x15, 0x2F, 0x15, 0x2F, 0x2F, 0x2F, 0x15, 0x2F, 0x2F, 0x2F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x15, 0x15, 0x3F, 0x15, 0x2F, + 0x2F, 0x15, 0x2F, 0x3F, 0x2F, 0x15, 0x2F, 0x2F, 0x15, 0x3F, 0x2F, 0x2F, 0x2F, 0x2F, 0x2F, 0x3F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x15, 0x3F, + 0x15, 0x15, 0x3F, 0x2F, 0x2F, 0x2F, 0x15, 0x2F, 0x2F, 0x2F, 0x2F, 0x3F, 0x15, 0x2F, 0x3F, 0x2F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x15, 0x3F, 0x3F, 0x2F, 0x2F, 0x2F, 0x2F, 0x2F, 0x3F, 0x2F, 0x3F, 0x2F, 0x2F, 0x3F, 0x3F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x15, 0x15, 0x3F, 0x15, 0x2F, 0x3F, 0x2F, 0x15, 0x3F, 0x2F, 0x2F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x15, 0x3F, 0x3F, 0x2F, 0x2F, 0x3F, 0x2F, 0x3F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x3F, 0x15, 0x3F, 0x3F, 0x2F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x3F, 0x3F +}; + +uint16 *OldDOSFont::_cgaDitheringTable = 0; +int OldDOSFont::_numRef = 0; + +OldDOSFont::OldDOSFont(Common::RenderMode mode) : _renderMode(mode) { + _data = 0; + _width = _height = _numGlyphs = 0; + _bitmapOffsets = 0; + + _numRef++; + if (!_cgaDitheringTable && _numRef == 1) { + _cgaDitheringTable = new uint16[256]; + memset(_cgaDitheringTable, 0, 256 * sizeof(uint16)); + static const uint bits[] = { 0, 3, 12, 15 }; + for (int i = 0; i < 256; i++) + _cgaDitheringTable[i] = (bits[i & 3] << 8) | (bits[(i >> 2) & 3] << 12) | (bits[(i >> 4) & 3] << 0) | (bits[(i >> 6) & 3] << 4); + } +} + +OldDOSFont::~OldDOSFont() { + unload(); + + if (_numRef) + --_numRef; + + if (_cgaDitheringTable && !_numRef) { + delete[] _cgaDitheringTable; + _cgaDitheringTable = 0; + } +} + +bool OldDOSFont::load(Common::SeekableReadStream &file) { + unload(); + + _data = new uint8[file.size()]; + assert(_data); + + file.read(_data, file.size()); + if (file.err()) + return false; + + if (file.size() - 2 != READ_LE_UINT16(_data)) + return false; + + _width = _data[0x103]; + _height = _data[0x102]; + _numGlyphs = 255; + + _bitmapOffsets = (uint16 *)(_data + 2); + + for (int i = 0; i < _numGlyphs; ++i) + _bitmapOffsets[i] = READ_LE_UINT16(&_bitmapOffsets[i]); + + return true; +} + +int OldDOSFont::getCharWidth(uint16 c) const { + if (c >= _numGlyphs) + return 0; + return _width; +} + +void OldDOSFont::drawChar(uint16 c, byte *dst, int pitch, int bpp) const { + static const uint8 renderMaskTable6[] = { 0xFC, 0x00, 0x7E, 0x00, 0x3F, 0x00, 0x1F, 0x80, 0x0F, 0xC0, 0x07, 0xE0, 0x03, 0xF0, 0x01, 0xF8 }; + static const uint8 renderMaskTable8[] = { 0xFF, 0x00, 0x7F, 0x80, 0x3F, 0xC0, 0x1F, 0xE0, 0x0F, 0xF0, 0x07, 0xF8, 0x03, 0xFC, 0x01, 0xFE }; + + if (_width != 8 && _width != 6) + error("EOB font rendering not implemented for other font widths than 6 and 8."); + + if (_width == 6) { + switch (c) { + case 0x81: + case 0x9A: + c = 0x5D; + break; + case 0x84: + case 0x8E: + c = 0x5B; + break; + case 0x94: + case 0x99: + c = 0x40; + case 0xE1: + // TODO: recheck this: no conversion for 'ß' ? + break; + } + } else if (_width == 8) { + switch (c) { + case 0x81: + case 0x9A: + case 0x5D: + c = 0x1D; + break; + case 0x84: + case 0x5B: + c = 0x1E; + break; + case 0x94: + case 0x40: + c = 0x1F; + break; + case 0x8E: + c = 0x1B; + break; + case 0x99: + c = 0x1C; + break; + case 0xE1: + c = 0x19; + break; + } + } + + pitch *= bpp; + const uint8 *src = &_data[_bitmapOffsets[c]]; + uint8 *dst2 = dst + pitch; + + int w = (_width - 1) >> 3; + pitch -= _width * bpp; + + uint16 color1 = _colorMap8bit[1]; + uint16 color2 = _colorMap8bit[0]; + + if (bpp == 2) { + color1 = _colorMap16bit[1]; + color2 = _colorMap16bit[0]; + } else if (_renderMode == Common::kRenderCGA || _renderMode == Common::kRenderEGA) { + color1 &= 0x0F; + color2 &= 0x0F; + } + + static const uint16 cgaColorMask[] = { 0, 0x5555, 0xAAAA, 0xFFFF }; + uint16 cgaMask1 = cgaColorMask[color1 & 3]; + uint16 cgaMask2 = cgaColorMask[color2 & 3]; + + int cH = _height; + while (cH--) { + int cW = w; + uint8 last = 0; + const uint8 *mtbl = _width == 8 ? renderMaskTable8 : renderMaskTable6; + + if (_renderMode == Common::kRenderCGA) { + uint8 s = *src++; + uint8 m = *mtbl++; + + uint8 in = s | last; + uint16 cmp1 = 0; + uint16 cmp2 = 0; + + if (color1) { + in &= m; + cmp1 = _cgaDitheringTable[in]; + } + + if (color2) { + in = ~in & m; + cmp2 = _cgaDitheringTable[in]; + } + + uint16 cDst = 0; + uint8 sh = 6; + for (int i = 0; i < _width; i++) { + cDst |= ((dst[i] & 3) << sh); + sh = (sh - 2) & 0x0F; + } + + uint16 out = (~(cmp1 | cmp2) & cDst) | (cmp1 & cgaMask1) | (cmp2 & cgaMask2); + + sh = 6; + for (int i = 0; i < _width; i++) { + *dst++ = (out >> sh) & 3; + sh = (sh - 2) & 0x0F; + } + + last = s; + + } else { + for (bool runWidthLoop = true; runWidthLoop;) { + uint8 s = *src++; + uint8 m = *mtbl++; + + for (uint8 i = 0x80; i; i >>= 1) { + if (!(m & i)) { + runWidthLoop = false; + break; + } + + if (s & i) { + if (bpp == 2) + *(uint16*)dst = color1; + else if (color1) + *dst = color1; + } else { + if (bpp == 2) { + if (color2 != 0xFFFF) + *(uint16*)dst = color2; + } else if (color2) { + *dst = color2; + } + } + dst += bpp; + } + + if (cW) + cW--; + else + runWidthLoop = false; + } + } + + dst += pitch; + dst2 += pitch; + } +} + +void OldDOSFont::unload() { + delete[] _data; + _data = 0; + _width = _height = _numGlyphs = 0; + _bitmapOffsets = 0; +} + +SJISFontLarge::SJISFontLarge(Graphics::FontSJIS *font) : SJISFont(font, 0, false, false, false, 0) { + _sjisWidth = _font->getMaxFontWidth(); + _fontHeight = _font->getFontHeight(); + _asciiWidth = _font->getCharWidth('a'); +} + +void SJISFontLarge::drawChar(uint16 c, byte *dst, int pitch, int) const { + _font->drawChar(dst, c, 320, 1, _colorMap[1], _colorMap[0], 320, 200); +} + +SJISFont12x12::SJISFont12x12(const uint16 *searchTable) : _height(6), _width(6), _data(0) { + assert(searchTable); + for (int i = 0; i < 148; i++) + _searchTable[searchTable[i]] = i + 1; +} + +bool SJISFont12x12::load(Common::SeekableReadStream &file) { + delete[] _data; + int size = 148 * 24; + if (file.size() < size) + return false; + + _data = new uint8[size]; + file.read(_data, size); + + return true; +} + +void SJISFont12x12::unload() { + delete[] _data; + _data = 0; + _searchTable.clear(); +} + +void SJISFont12x12::drawChar(uint16 c, byte *dst, int pitch, int) const { + int offs = _searchTable[c]; + if (!offs) + return; + + const uint8 *src = _data + (offs - 1) * 24; + uint8 color1 = _colorMap[1]; + + int bt = 0; + uint16 chr = 0; + + for (int i = 0; i < 192; ++i) { + if (!bt) { + chr = *src++; + bt = 8; + } + if (chr & 0x80) + *dst = color1; + dst++; + if (--bt) + chr <<= 1; + else if (i & 8) + dst += (pitch - 16); + } +} + +} // End of namespace Kyra + +#endif // ENABLE_EOB diff --git a/engines/kyra/graphics/screen_eob.h b/engines/kyra/graphics/screen_eob.h new file mode 100644 index 0000000000..401a229045 --- /dev/null +++ b/engines/kyra/graphics/screen_eob.h @@ -0,0 +1,177 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef KYRA_SCREEN_EOB_H +#define KYRA_SCREEN_EOB_H + +#ifdef ENABLE_EOB + +#include "kyra/graphics/screen.h" + +namespace Kyra { + +class EoBCoreEngine; +class Screen_EoB : public Screen { +public: + Screen_EoB(EoBCoreEngine *vm, OSystem *system); + virtual ~Screen_EoB(); + + bool init(); + + void setClearScreenDim(int dim); + void clearCurDim(); + + void setMouseCursor(int x, int y, const byte *shape); + void setMouseCursor(int x, int y, const byte *shape, const uint8 *ovl); + + void loadFileDataToPage(Common::SeekableReadStream *s, int pageNum, uint32 size); + + void printShadedText(const char *string, int x, int y, int col1, int col2); + + void loadEoBBitmap(const char *file, const uint8 *cgaMapping, int tempPage, int destPage, int convertToPage); + void loadShapeSetBitmap(const char *file, int tempPage, int destPage); + + void convertPage(int srcPage, int dstPage, const uint8 *cgaMapping); + + void setScreenPalette(const Palette &pal); + void getRealPalette(int num, uint8 *dst); + + uint8 *encodeShape(uint16 x, uint16 y, uint16 w, uint16 h, bool encode8bit = false, const uint8 *cgaMapping = 0); + void drawShape(uint8 pageNum, const uint8 *shapeData, int x, int y, int sd = -1, int flags = 0, ...); + const uint8 *scaleShape(const uint8 *shapeData, int blockDistance); + const uint8 *scaleShapeStep(const uint8 *shp); + const uint8 *generateShapeOverlay(const uint8 *shp, const uint8 *fadingTable); + + void setShapeFrame(int x1, int y1, int x2, int y2); + void enableShapeBackgroundFading(bool enable); + void setShapeFadingLevel(int val); + + void setGfxParameters(int x, int y, int col); + void drawExplosion(int scale, int radius, int numElements, int stepSize, int aspectRatio, const uint8 *colorTable, int colorTableSize); + void drawVortex(int numElements, int radius, int stepSize, int, int disorder, const uint8 *colorTable, int colorTableSize); + + void fadeTextColor(Palette *pal, int color1, int fadeTextColor); + bool delayedFadePalStep(Palette *fadePal, Palette *destPal, int rate); + + void setTextColorMap(const uint8 *cmap) {} + int getRectSize(int w, int h); + + void setFadeTable(const uint8 *table); + void createFadeTable(const uint8 *palData, uint8 *dst, uint8 rootColor, uint8 weight); + void createFadeTable16bit(const uint16 *palData, uint16 *dst, uint16 rootColor, uint8 weight); + + const uint16 *getCGADitheringTable(int index); + const uint8 *getEGADitheringTable(); + + bool loadFont(FontId fontId, const char *filename); + + // FM-Towns specific + void decodeSHP(const uint8 *data, int dstPage); + void convertToHiColor(int page); + void shadeRect(int x1, int y1, int x2, int y2, int shadingLevel); + +private: + void updateDirtyRects(); + void ditherRect(const uint8 *src, uint8 *dst, int dstPitch, int srcW, int srcH, int colorKey = -1); + + void drawShapeSetPixel(uint8 *dst, uint8 col); + void scaleShapeProcessLine2Bit(uint8 *&shpDst, const uint8 *&shpSrc, uint32 transOffsetDst, uint32 transOffsetSrc); + void scaleShapeProcessLine4Bit(uint8 *&dst, const uint8 *&src); + bool posWithinRect(int posX, int posY, int x1, int y1, int x2, int y2); + + void setPagePixel16bit(int pageNum, int x, int y, uint16 color); + + void generateEGADitheringTable(const Palette &pal); + void generateCGADitheringTables(const uint8 *mappingData); + + int _dsDiv, _dsRem, _dsScaleTrans; + uint8 *_cgaScaleTable; + int16 _gfxX, _gfxY; + uint16 _gfxCol; + const uint8 *_gfxMaxY; + + int16 _dsX1, _dsX2, _dsY1, _dsY2; + + bool _dsBackgroundFading; + int16 _dsBackgroundFadingXOffs; + uint8 _shapeOverlay[16]; + + uint8 *_dsTempPage; + uint8 *_shpBuffer; + uint8 *_convertHiColorBuffer; + + uint16 *_cgaDitheringTables[2]; + const uint8 *_cgaMappingDefault; + + uint8 *_egaDitheringTable; + uint8 *_egaDitheringTempPage; + + const uint16 _cursorColorKey16Bit; + + static const uint8 _egaMatchTable[]; + static const ScreenDim _screenDimTable[]; + static const int _screenDimTableCount; +}; + +/** +* SJIS Font variant used in the intro and outro of EOB II FM-Towns. It appears twice as large, since it is not rendered on the hires overlay pages +*/ +class SJISFontLarge : public SJISFont { +public: + SJISFontLarge(Graphics::FontSJIS *font); + virtual ~SJISFontLarge() { unload(); } + + virtual bool usesOverlay() const { return false; } + virtual void drawChar(uint16 c, byte *dst, int pitch, int) const; +}; + +/** +* 12 x 12 SJIS font for EOB II FM-Towns. The data for this font comes from a file, not from the font rom. +*/ +class SJISFont12x12 : public Font { +public: + SJISFont12x12(const uint16 *searchTable); + virtual ~SJISFont12x12() { unload(); } + + virtual bool load(Common::SeekableReadStream &file); + virtual bool usesOverlay() const { return true; } + virtual int getHeight() const { return _height; } + virtual int getWidth() const { return _width; } + virtual int getCharWidth(uint16 c) const { return _width; } + virtual void setColorMap(const uint8 *src) { _colorMap = src; } + virtual void drawChar(uint16 c, byte *dst, int pitch, int) const; + +private: + void unload(); + + uint8 *_data; + Common::HashMap<uint16, uint8> _searchTable; + + const uint8 *_colorMap; + const int _height, _width; +}; + +} // End of namespace Kyra + +#endif // ENABLE_EOB + +#endif diff --git a/engines/kyra/graphics/screen_hof.cpp b/engines/kyra/graphics/screen_hof.cpp new file mode 100644 index 0000000000..a3ab811cdb --- /dev/null +++ b/engines/kyra/graphics/screen_hof.cpp @@ -0,0 +1,96 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/graphics/screen_hof.h" +#include "kyra/engine/kyra_hof.h" + +namespace Kyra { + +Screen_HoF::Screen_HoF(KyraEngine_HoF *vm, OSystem *system) + : Screen_v2(vm, system, _screenDimTable, _screenDimTableCount), _vm(vm) { +} + +void Screen_HoF::generateGrayOverlay(const Palette &srcPal, uint8 *grayOverlay, int factor, int addR, int addG, int addB, int lastColor, bool flag) { + Palette tmpPal(lastColor); + + for (int i = 0; i != lastColor; i++) { + if (flag) { + int v = ((((srcPal[3 * i] & 0x3F) + (srcPal[3 * i + 1] & 0x3F) + + (srcPal[3 * i + 2] & 0x3F)) / 3) * factor) / 0x40; + tmpPal[3 * i] = tmpPal[3 * i + 1] = tmpPal[3 * i + 2] = v & 0xFF; + } else { + int v = (((srcPal[3 * i] & 0x3F) * factor) / 0x40) + addR; + tmpPal[3 * i] = (v > 0x3F) ? 0x3F : v & 0xFF; + v = (((srcPal[3 * i + 1] & 0x3F) * factor) / 0x40) + addG; + tmpPal[3 * i + 1] = (v > 0x3F) ? 0x3F : v & 0xFF; + v = (((srcPal[3 * i + 2] & 0x3F) * factor) / 0x40) + addB; + tmpPal[3 * i + 2] = (v > 0x3F) ? 0x3F : v & 0xFF; + } + } + + for (int i = 0; i < lastColor; i++) + grayOverlay[i] = findLeastDifferentColor(tmpPal.getData() + 3 * i, srcPal, 0, lastColor); +} + +void Screen_HoF::cmpFadeFrameStep(int srcPage, int srcW, int srcH, int srcX, int srcY, int dstPage, int dstW, + int dstH, int dstX, int dstY, int cmpW, int cmpH, int cmpPage) { + + if (!cmpW || !cmpH) + return; + + int r1, r2, r3, r4, r5, r6; + + int X1 = srcX; + int Y1 = srcY; + int W1 = cmpW; + int H1 = cmpH; + + if (!calcBounds(srcW, srcH, X1, Y1, W1, H1, r1, r2, r3)) + return; + + int X2 = dstX; + int Y2 = dstY; + int W2 = W1; + int H2 = H1; + + if (!calcBounds(dstW, dstH, X2, Y2, W2, H2, r4, r5, r6)) + return; + + const uint8 *src = getPagePtr(srcPage) + srcW * (Y1 + r5); + uint8 *dst = getPagePtr(dstPage) + dstW * (Y2 + r2); + const uint8 *cmp = getPagePtr(cmpPage); + + while (H2--) { + const uint8 *s = src + r4 + X1; + uint8 *d = dst + r1 + X2; + + for (int i = 0; i < W2; i++) { + int ix = (*s++ << 8) + *d; + *d++ = cmp[ix]; + } + + src += W1; + dst += W2; + } +} + +} // End of namespace Kyra diff --git a/engines/kyra/graphics/screen_hof.h b/engines/kyra/graphics/screen_hof.h new file mode 100644 index 0000000000..5cb189e0ef --- /dev/null +++ b/engines/kyra/graphics/screen_hof.h @@ -0,0 +1,50 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef KYRA_SCREEN_HOF_H +#define KYRA_SCREEN_HOF_H + +#include "kyra/graphics/screen_v2.h" + +namespace Kyra { + +class KyraEngine_HoF; + +class Screen_HoF : public Screen_v2 { +friend class Debugger_v2; +public: + Screen_HoF(KyraEngine_HoF *vm, OSystem *system); + + // sequence player + void generateGrayOverlay(const Palette &pal, uint8 *grayOverlay, int factor, int addR, int addG, int addB, int lastColor, bool flag); + void cmpFadeFrameStep(int srcPage, int srcW, int srcH, int srcX, int srcY, int dstPage, int dstW, int dstH, int dstX, int dstY, int cmpW, int cmpH, int cmpPage); + +private: + KyraEngine_HoF *_vm; + + static const ScreenDim _screenDimTable[]; + static const int _screenDimTableCount; +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/graphics/screen_lok.cpp b/engines/kyra/graphics/screen_lok.cpp new file mode 100644 index 0000000000..114382b487 --- /dev/null +++ b/engines/kyra/graphics/screen_lok.cpp @@ -0,0 +1,447 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/graphics/screen_lok.h" +#include "kyra/engine/kyra_lok.h" + +#include "common/system.h" + +#include "graphics/palette.h" + +namespace Kyra { + +Screen_LoK::Screen_LoK(KyraEngine_LoK *vm, OSystem *system) + : Screen(vm, system, _screenDimTable, _screenDimTableCount) { + _vm = vm; + _unkPtr1 = _unkPtr2 = 0; + _bitBlitNum = 0; +} + +Screen_LoK::~Screen_LoK() { + for (int i = 0; i < ARRAYSIZE(_saveLoadPage); ++i) { + delete[] _saveLoadPage[i]; + _saveLoadPage[i] = 0; + } + + for (int i = 0; i < ARRAYSIZE(_saveLoadPageOvl); ++i) { + delete[] _saveLoadPageOvl[i]; + _saveLoadPageOvl[i] = 0; + } + + delete[] _unkPtr1; + delete[] _unkPtr2; +} + +bool Screen_LoK::init() { + if (!Screen::init()) + return false; + + memset(_bitBlitRects, 0, sizeof(_bitBlitRects)); + _bitBlitNum = 0; + memset(_saveLoadPage, 0, sizeof(_saveLoadPage)); + memset(_saveLoadPageOvl, 0, sizeof(_saveLoadPageOvl)); + + _unkPtr1 = new uint8[getRectSize(1, 144)]; + assert(_unkPtr1); + memset(_unkPtr1, 0, getRectSize(1, 144)); + _unkPtr2 = new uint8[getRectSize(1, 144)]; + assert(_unkPtr2); + memset(_unkPtr2, 0, getRectSize(1, 144)); + + return true; +} + +void Screen_LoK::fadeSpecialPalette(int palIndex, int startIndex, int size, int fadeTime) { + if (_vm->gameFlags().platform == Common::kPlatformAmiga) + return; + + assert(_vm->palTable1()[palIndex]); + + Palette tempPal(getPalette(0).getNumColors()); + tempPal.copy(getPalette(0)); + tempPal.copy(_vm->palTable1()[palIndex], 0, size, startIndex); + + fadePalette(tempPal, fadeTime * 18); + + getPalette(0).copy(tempPal, startIndex, size); + setScreenPalette(getPalette(0)); + _system->updateScreen(); +} + +void Screen_LoK::addBitBlitRect(int x, int y, int w, int h) { + if (_bitBlitNum >= kNumBitBlitRects) + error("too many bit blit rects"); + + _bitBlitRects[_bitBlitNum].left = x; + _bitBlitRects[_bitBlitNum].top = y; + _bitBlitRects[_bitBlitNum].right = x + w; + _bitBlitRects[_bitBlitNum].bottom = y + h; + ++_bitBlitNum; +} + +void Screen_LoK::bitBlitRects() { + Common::Rect *cur = _bitBlitRects; + while (_bitBlitNum) { + _bitBlitNum--; + copyRegion(cur->left, cur->top, cur->left, cur->top, cur->width(), cur->height(), 2, 0); + ++cur; + } +} + +void Screen_LoK::savePageToDisk(const char *file, int 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); + + if (_useOverlays) { + if (!_saveLoadPageOvl[page / 2]) { + _saveLoadPageOvl[page / 2] = new uint8[SCREEN_OVL_SJIS_SIZE]; + assert(_saveLoadPageOvl[page / 2]); + } + + uint8 *srcPage = getOverlayPtr(page); + if (!srcPage) { + warning("trying to save unsupported overlay page %d", page); + return; + } + + memcpy(_saveLoadPageOvl[page / 2], srcPage, SCREEN_OVL_SJIS_SIZE); + } +} + +void Screen_LoK::loadPageFromDisk(const char *file, int page) { + if (!_saveLoadPage[page / 2]) { + warning("trying to restore page %d, but no backup found", page); + return; + } + + copyBlockToPage(page, 0, 0, SCREEN_W, SCREEN_H, _saveLoadPage[page / 2]); + delete[] _saveLoadPage[page / 2]; + _saveLoadPage[page / 2] = 0; + + if (_saveLoadPageOvl[page / 2]) { + uint8 *dstPage = getOverlayPtr(page); + if (!dstPage) { + warning("trying to restore unsupported overlay page %d", page); + return; + } + + memcpy(dstPage, _saveLoadPageOvl[page / 2], SCREEN_OVL_SJIS_SIZE); + delete[] _saveLoadPageOvl[page / 2]; + _saveLoadPageOvl[page / 2] = 0; + } +} + +void Screen_LoK::queryPageFromDisk(const char *file, int page, uint8 *buffer) { + if (!_saveLoadPage[page / 2]) { + warning("trying to query page %d, but no backup found", page); + return; + } + + memcpy(buffer, _saveLoadPage[page / 2], SCREEN_W * SCREEN_H); +} + +void Screen_LoK::deletePageFromDisk(int page) { + delete[] _saveLoadPage[page / 2]; + _saveLoadPage[page / 2] = 0; + + if (_saveLoadPageOvl[page / 2]) { + delete[] _saveLoadPageOvl[page / 2]; + _saveLoadPageOvl[page / 2] = 0; + } +} + +void Screen_LoK::copyBackgroundBlock(int x, int page, int 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; + 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; + } + } + _curPage = oldVideoPage; +} + +void Screen_LoK::copyBackgroundBlock2(int x) { + copyBackgroundBlock(x, 4, 1); +} + +void Screen_LoK::setTextColorMap(const uint8 *cmap) { + setTextColor(cmap, 0, 11); +} + +int Screen_LoK::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_LoK::postProcessCursor(uint8 *data, int width, int height, int pitch) { + if (_vm->gameFlags().platform == Common::kPlatformAmiga && _interfacePaletteEnabled) { + pitch -= width; + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (*data != _cursorColorKey) + *data += 32; + ++data; + } + + data += pitch; + } + } +} + +#pragma mark - + +Screen_LoK_16::Screen_LoK_16(KyraEngine_LoK *vm, OSystem *system) : Screen_LoK(vm, system) { + memset(_paletteDither, 0, sizeof(_paletteDither)); +} + +void Screen_LoK_16::setScreenPalette(const Palette &pal) { + _screenPalette->copy(pal); + + for (int i = 0; i < 256; ++i) + paletteMap(i, pal[i * 3 + 0] << 2, pal[i * 3 + 1] << 2, pal[i * 3 + 2] << 2); + + set16ColorPalette(_palette16); + _forceFullUpdate = true; +} + +void Screen_LoK_16::fadePalette(const Palette &pal, int delay, const UpdateFunctor *upFunc) { + uint8 notBlackFlag = 0; + for (int i = 0; i < 768; ++i) { + if ((*_screenPalette)[i]) + notBlackFlag |= 1; + if (pal[i]) + notBlackFlag |= 2; + } + + if (notBlackFlag == 1 || notBlackFlag == 2) { + bool upFade = false; + + for (int i = 0; i < 768; ++i) { + if ((*_screenPalette)[i] < pal[i]) { + upFade = true; + break; + } + } + + if (upFade) { + for (int i = 0; i < 256; ++i) + paletteMap(i, pal[i * 3 + 0] << 2, pal[i * 3 + 1] << 2, pal[i * 3 + 2] << 2); + _forceFullUpdate = true; + } + + uint8 color16Palette[16 * 3]; + + if (upFade) + memset(color16Palette, 0, sizeof(color16Palette)); + else + memcpy(color16Palette, _palette16, sizeof(color16Palette)); + + set16ColorPalette(color16Palette); + updateScreen(); + + for (int i = 0; i < 16; ++i) { + set16ColorPalette(color16Palette); + + for (int k = 0; k < 48; ++k) { + if (upFade) { + if (color16Palette[k] < _palette16[k]) + ++color16Palette[k]; + } else { + if (color16Palette[k] > 0) + --color16Palette[k]; + } + } + + if (upFunc && upFunc->isValid()) + (*upFunc)(); + else + _system->updateScreen(); + + _vm->delay((delay >> 5) * _vm->tickLength()); + } + } + + setScreenPalette(pal); +} + +void Screen_LoK_16::getFadeParams(const Palette &pal, int delay, int &delayInc, int &diff) { + error("Screen_LoK_16::getFadeParams called"); +} + +int Screen_LoK_16::fadePalStep(const Palette &pal, int diff) { + error("Screen_LoK_16::fadePalStep called"); + return 0; // for compilers that don't support NORETURN +} + +void Screen_LoK_16::paletteMap(uint8 idx, int r, int g, int b) { + const int red = r; + const int green = g; + const int blue = b; + + uint16 rgbDiff = 1000; + int rDiff = 0, gDiff = 0, bDiff = 0; + + int index1 = -1; + + for (int i = 0; i < 16; ++i) { + const int realR = _palette16[i * 3 + 0] << 4; + const int realG = _palette16[i * 3 + 1] << 4; + const int realB = _palette16[i * 3 + 2] << 4; + + uint16 diff = ABS(r - realR) + ABS(g - realG) + ABS(b - realB); + + if (diff < rgbDiff) { + rgbDiff = diff; + index1 = i; + + rDiff = r - realR; + gDiff = g - realG; + bDiff = b - realB; + } + } + + r = rDiff / 4 + red; + g = gDiff / 4 + green; + b = bDiff / 4 + blue; + + rgbDiff = 1000; + int index2 = -1; + + for (int i = 0; i < 16; ++i) { + const int realR = _palette16[i * 3 + 0] << 4; + const int realG = _palette16[i * 3 + 1] << 4; + const int realB = _palette16[i * 3 + 2] << 4; + + uint16 diff = ABS(r - realR) + ABS(g - realG) + ABS(b - realB); + + if (diff < rgbDiff) { + rgbDiff = diff; + index2 = i; + } + } + + _paletteDither[idx].bestMatch = index1; + _paletteDither[idx].invertMatch = index2; +} + +void Screen_LoK_16::convertTo16Colors(uint8 *page, int w, int h, int pitch, int keyColor) { + const int rowAdd = pitch * 2 - w; + + uint8 *row1 = page; + uint8 *row2 = page + pitch; + + for (int i = 0; i < h; i += 2) { + for (int k = 0; k < w; k += 2) { + if (keyColor == -1 || keyColor != *row1) { + const PaletteDither &dither = _paletteDither[*row1]; + + *row1++ = dither.bestMatch; + *row1++ = dither.invertMatch; + *row2++ = dither.invertMatch; + *row2++ = dither.bestMatch; + } else { + row1 += 2; + row2 += 2; + } + } + + row1 += rowAdd; + row2 += rowAdd; + } +} + +void Screen_LoK_16::mergeOverlay(int x, int y, int w, int h) { + byte *dst = _sjisOverlayPtrs[0] + y * 640 + x; + + // We do a game screen rect to 16 color dithering here. It is + // important that we do not dither the overlay, since else the + // japanese fonts will look wrong. + convertTo16Colors(dst, w, h, 640); + + const byte *src = _sjisOverlayPtrs[1] + y * 640 + x; + + int add = 640 - w; + + while (h--) { + for (x = 0; x < w; ++x, ++dst) { + byte col = *src++; + if (col != _sjisInvisibleColor) + *dst = _paletteDither[col].bestMatch; + } + dst += add; + src += add; + } +} + +void Screen_LoK_16::set16ColorPalette(const uint8 *pal) { + uint8 palette[16 * 3]; + for (int i = 0; i < 16; ++i) { + palette[i * 3 + 0] = (pal[i * 3 + 0] * 0xFF) / 0x0F; + palette[i * 3 + 1] = (pal[i * 3 + 1] * 0xFF) / 0x0F; + palette[i * 3 + 2] = (pal[i * 3 + 2] * 0xFF) / 0x0F; + } + + _system->getPaletteManager()->setPalette(palette, 0, 16); +} + +} // End of namespace Kyra diff --git a/engines/kyra/graphics/screen_lok.h b/engines/kyra/graphics/screen_lok.h new file mode 100644 index 0000000000..3a4ebfd6a4 --- /dev/null +++ b/engines/kyra/graphics/screen_lok.h @@ -0,0 +1,111 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef KYRA_SCREEN_LOK_H +#define KYRA_SCREEN_LOK_H + +#include "kyra/graphics/screen.h" + +namespace Kyra { + +class KyraEngine_LoK; + +class Screen_LoK : public Screen { +public: + Screen_LoK(KyraEngine_LoK *vm, OSystem *system); + virtual ~Screen_LoK(); + + bool init(); + + int getRectSize(int w, int h); + + void setTextColorMap(const uint8 *cmap); + + void fadeSpecialPalette(int palIndex, int startIndex, int size, int fadeTime); + + void savePageToDisk(const char *file, int page); + void loadPageFromDisk(const char *file, int page); + void queryPageFromDisk(const char *file, int page, uint8 *buffer); + void deletePageFromDisk(int page); + + void copyBackgroundBlock(int x, int page, int flag); + void copyBackgroundBlock2(int x); + + void addBitBlitRect(int x, int y, int w, int h); + void bitBlitRects(); + + // AMIGA specific + virtual void postProcessCursor(uint8 *data, int width, int height, int pitch); + +protected: + enum { + kNumBitBlitRects = 10 + }; + + KyraEngine_LoK *_vm; + + static const ScreenDim _screenDimTable[]; + static const int _screenDimTableCount; + + Common::Rect _bitBlitRects[kNumBitBlitRects]; + int _bitBlitNum; + uint8 *_unkPtr1, *_unkPtr2; + + uint8 *_saveLoadPage[8]; + uint8 *_saveLoadPageOvl[8]; +}; + +class Screen_LoK_16 : public Screen_LoK { +public: + Screen_LoK_16(KyraEngine_LoK *vm, OSystem *system); + + void setScreenPalette(const Palette &pal); + + void fadePalette(const Palette &pal, int delay, const UpdateFunctor *upFunc = 0); + void getFadeParams(const Palette &pal, int delay, int &delayInc, int &diff); + int fadePalStep(const Palette &pal, int diff); +private: + void updateDirtyRectsOvl(); + + void convertTo16Colors(uint8 *page, int w, int h, int pitch, int keyColor = -1); + void postProcessCursor(uint8 *data, int width, int height, int pitch) { + convertTo16Colors(data, width, height, pitch, _cursorColorKey); + } + void mergeOverlay(int x, int y, int w, int h); + + void set16ColorPalette(const uint8 *pal); + + void paletteMap(uint8 idx, int r, int g, int b); + + struct PaletteDither { + uint8 bestMatch; + uint8 invertMatch; + }; + + PaletteDither _paletteDither[256]; + + static const uint8 _palette16[48]; +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/graphics/screen_lol.cpp b/engines/kyra/graphics/screen_lol.cpp new file mode 100644 index 0000000000..b42565fc9d --- /dev/null +++ b/engines/kyra/graphics/screen_lol.cpp @@ -0,0 +1,893 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifdef ENABLE_LOL + +#include "kyra/graphics/screen_lol.h" +#include "kyra/engine/lol.h" + +#include "common/system.h" + +#include "graphics/palette.h" + +namespace Kyra { + +Screen_LoL::Screen_LoL(LoLEngine *vm, OSystem *system) : Screen_v2(vm, system, vm->gameFlags().use16ColorMode ? _screenDimTable16C : _screenDimTable256C, _screenDimTableCount) { + _paletteOverlay1 = new uint8[0x100]; + _paletteOverlay2 = new uint8[0x100]; + _grayOverlay = new uint8[0x100]; + memset(_paletteOverlay1, 0, 0x100); + memset(_paletteOverlay2, 0, 0x100); + memset(_grayOverlay, 0, 0x100); + + for (int i = 0; i < 8; i++) + _levelOverlays[i] = new uint8[256]; + + _fadeFlag = 2; +} + +Screen_LoL::~Screen_LoL() { + for (int i = 0; i < 8; i++) + delete[] _levelOverlays[i]; + + delete[] _paletteOverlay1; + delete[] _paletteOverlay2; + delete[] _grayOverlay; +} + +void Screen_LoL::fprintString(const char *format, int x, int y, uint8 col1, uint8 col2, uint flags, ...) { + if (!format) + return; + + char string[240]; + va_list vaList; + va_start(vaList, flags); + vsnprintf(string, sizeof(string), format, vaList); + va_end(vaList); + + if (flags & 1) + x -= (getTextWidth(string) >> 1); + + if (flags & 2) + x -= getTextWidth(string); + + if (_use16ColorMode) { + if (flags & 12) { + printText(string, x - 1, y, 0x44, col2); + printText(string, x, y + 1, 0x44, col2); + } + } else { + if (flags & 4) { + printText(string, x - 1, y, 1, col2); + printText(string, x, y + 1, 1, col2); + } + + if (flags & 8) { + printText(string, x - 1, y, 227, col2); + printText(string, x, y + 1, 227, col2); + } + } + + printText(string, x, y, col1, col2); +} + +void Screen_LoL::fprintStringIntro(const char *format, int x, int y, uint8 c1, uint8 c2, uint8 c3, uint flags, ...) { + char buffer[400]; + + va_list args; + va_start(args, flags); + vsnprintf(buffer, sizeof(buffer), format, args); + va_end(args); + + if ((flags & 0x0F00) == 0x100) + x -= getTextWidth(buffer) >> 1; + if ((flags & 0x0F00) == 0x200) + x -= getTextWidth(buffer); + + if ((flags & 0x00F0) == 0x20) { + printText(buffer, x - 1, y, c3, c2); + printText(buffer, x, y + 1, c3, c2); + } + + printText(buffer, x, y, c1, c2); +} + +void Screen_LoL::drawShadedBox(int x1, int y1, int x2, int y2, int color1, int color2) { + assert(x1 >= 0 && y1 >= 0); + hideMouse(); + + fillRect(x1, y1, x2, y1 + 1, color1); + fillRect(x1, y1, x1 + 1, y2, color1); + + drawClippedLine(x2, y1, x2, y2, color2); + drawClippedLine(x2 - 1, y1 + 1, x2 - 1, y2 - 1, color2); + drawClippedLine(x1 + 1, y2 - 1, x2, y2 - 1, color2); + drawClippedLine(x1, y2, x2, y2, color2); + + if (_use16ColorMode && color1 > color2) + drawBox(x1, y1, x2, y2, 0x44); + + showMouse(); +} + +void Screen_LoL::generateGrayOverlay(const Palette &srcPal, uint8 *grayOverlay, int factor, int addR, int addG, int addB, int lastColor, bool skipSpecialColors) { + Palette tmpPal(lastColor); + + for (int i = 0; i != lastColor; i++) { + int v = (((srcPal[3 * i] & 0x3F) * factor) / 0x40) + addR; + tmpPal[3 * i] = (v > 0x3F) ? 0x3F : v & 0xFF; + v = (((srcPal[3 * i + 1] & 0x3F) * factor) / 0x40) + addG; + tmpPal[3 * i + 1] = (v > 0x3F) ? 0x3F : v & 0xFF; + v = (((srcPal[3 * i + 2] & 0x3F) * factor) / 0x40) + addB; + tmpPal[3 * i + 2] = (v > 0x3F) ? 0x3F : v & 0xFF; + } + + for (int i = 0; i < lastColor; i++) + grayOverlay[i] = findLeastDifferentColor(tmpPal.getData() + 3 * i, srcPal, 0, lastColor, skipSpecialColors); +} + +void Screen_LoL::createTransparencyTablesIntern(const uint8 *ovl, int a, const uint8 *fxPal1, const uint8 *fxPal2, uint8 *outTable1, uint8 *outTable2, int b) { + Palette screenPal(256); + screenPal.copy(fxPal2, 0, 256); + + memset(outTable1, 0xFF, 256); + + for (int i = 0; i < a; i++) + outTable1[ovl[i]] = i; + + for (int i = 0; i < a; i++) { + if (ovl[i]) { + uint8 tcol[3]; + uint16 fcol[3]; + uint16 scol[3]; + + uint16 t1 = (b << 6) / 100; + uint16 t2 = 64 - t1; + + uint8 c = ovl[i]; + fcol[0] = fxPal1[3 * c]; + fcol[1] = fxPal1[3 * c + 1]; + fcol[2] = fxPal1[3 * c + 2]; + + uint8 *o = &outTable2[i << 8]; + + for (int ii = 0; ii < 256; ii++) { + scol[0] = screenPal[3 * ii]; + scol[1] = screenPal[3 * ii + 1]; + scol[2] = screenPal[3 * ii + 2]; + + tcol[0] = CLIP(((fcol[0] * t2) >> 6) + ((scol[0] * t1) >> 6), 0, 63); + tcol[1] = CLIP(((fcol[1] * t2) >> 6) + ((scol[1] * t1) >> 6), 0, 63); + tcol[2] = CLIP(((fcol[2] * t2) >> 6) + ((scol[2] * t1) >> 6), 0, 63); + + o[ii] = findLeastDifferentColor(tcol, screenPal, 0, 255); + } + + } else { + memset(&outTable2[i << 8], 0, 256); + } + } +} + +void Screen_LoL::drawGridBox(int x, int y, int w, int h, int col) { + if (w <= 0 || x >= 320 || h <= 0 || y >= 200) + return; + + if (x < 0) { + x += w; + if (x <= 0) + return; + w = x; + x = 0; + } + + int tmp = x + w; + if (tmp >= 320) { + w = 320 - x; + } + + int pitch = 320 - w; + + if (y < 0) { + y += h; + if (y <= 0) + return; + h = y; + y = 0; + } + + tmp = y + h; + if (tmp >= 200) { + h = 200 - y; + } + + tmp = (y + x) & 1; + uint8 *p = getPagePtr(_curPage) + y * 320 + x; + uint8 s = (tmp >> 8) & 1; + + w >>= 1; + int w2 = w; + + while (h--) { + if (w) { + while (w--) { + *(p + tmp) = col; + p += 2; + } + } + + if (s == 1) { + if (tmp == 0) + *p = col; + p++; + } + tmp ^= 1; + p += pitch; + w = w2; + } +} + +void Screen_LoL::fadeClearSceneWindow(int delay) { + if (_fadeFlag == 1) + return; + + if (_use16ColorMode) { + fadeToBlack(delay); + fillRect(112, 0, 288, 120, 0x44); + } else { + Palette tpal(getPalette(0).getNumColors()); + tpal.copy(getPalette(0), 128); + + loadSpecialColors(tpal); + fadePalette(tpal, delay); + + fillRect(112, 0, 288, 120, 0); + } + + _fadeFlag = 1; +} + +void Screen_LoL::backupSceneWindow(int srcPageNum, int dstPageNum) { + uint8 *src = getPagePtr(srcPageNum) + 112; + uint8 *dst = getPagePtr(dstPageNum) + 0xA500; + + for (int h = 0; h < 120; h++) { + for (int w = 0; w < 176; w++) + *dst++ = *src++; + src += 144; + } +} + +void Screen_LoL::restoreSceneWindow(int srcPageNum, int dstPageNum) { + uint8 *src = getPagePtr(srcPageNum) + 0xA500; + uint8 *dst = getPagePtr(dstPageNum) + 112; + + for (int h = 0; h < 120; h++) { + memcpy(dst, src, 176); + src += 176; + dst += 320; + } + + if (!dstPageNum) + addDirtyRect(112, 0, 176, 120); +} + +void Screen_LoL::clearGuiShapeMemory(int pageNum) { + uint8 *dst = getPagePtr(pageNum) + 0x79B0; + for (int i = 0; i < 23; i++) { + memset(dst, 0, 176); + dst += 320; + } +} + +void Screen_LoL::copyGuiShapeFromSceneBackupBuffer(int srcPageNum, int dstPageNum) { + uint8 *src = getPagePtr(srcPageNum) + 0x79C3; + uint8 *dst = getPagePtr(dstPageNum); + + for (int i = 0; i < 23; i++) { + uint8 len = 0; + uint8 v = 0; + + do { + v = *src++; + len++; + } while (!v); + + *dst++ = len; + + len = 69 - len; + memcpy(dst, src, len); + src += (len + 251); + dst += len; + } +} + +void Screen_LoL::copyGuiShapeToSurface(int srcPageNum, int dstPageNum) { + uint8 *src = getPagePtr(srcPageNum); + uint8 *dst = getPagePtr(dstPageNum) + 0xE7C3; + + for (int i = 0; i < 23; i++) { + uint8 v = *src++; + uint8 len = 69 - v; + dst += v; + memcpy(dst, src, len); + src += (len - 1); + dst += len; + + for (int ii = 0; ii < len; ii++) + *dst++ = *src--; + + src += (len + 1); + dst += (v + 38); + } +} + +void Screen_LoL::smoothScrollZoomStepTop(int srcPageNum, int dstPageNum, int x, int y) { + uint8 *src = getPagePtr(srcPageNum) + 0xA500 + y * 176 + x; + uint8 *dst = getPagePtr(dstPageNum) + 0xA500; + + x <<= 1; + uint16 width = 176 - x; + uint16 scaleX = (((x + 1) << 8) / width + 0x100); + uint16 cntW = scaleX >> 8; + scaleX <<= 8; + width--; + uint16 widthCnt = width; + + uint16 height = 46 - y; + uint16 scaleY = (((y + 1) << 8) / height + 0x100); + scaleY <<= 8; + + uint32 scaleYc = 0; + while (height) { + uint32 scaleXc = 0; + do { + scaleXc += scaleX; + int numbytes = cntW + (scaleXc >> 16); + scaleXc &= 0xFFFF; + memset(dst, *src++, numbytes); + dst += numbytes; + } while (--widthCnt); + + *dst++ = *src++; + widthCnt = width; + + src += x; + scaleYc += scaleY; + + if (scaleYc >> 16) { + scaleYc = 0; + src -= 176; + continue; + } + + height--; + } +} + +void Screen_LoL::smoothScrollZoomStepBottom(int srcPageNum, int dstPageNum, int x, int y) { + uint8 *src = getPagePtr(srcPageNum) + 0xC4A0 + x; + uint8 *dst = getPagePtr(dstPageNum) + 0xC4A0; + + x <<= 1; + uint16 width = 176 - x; + uint16 scaleX = (((x + 1) << 8) / width + 0x100); + uint16 cntW = scaleX >> 8; + scaleX <<= 8; + width--; + uint16 widthCnt = width; + + uint16 height = 74 - y; + uint16 scaleY = (((y + 1) << 8) / height + 0x100); + scaleY <<= 8; + + uint32 scaleYc = 0; + while (height) { + uint32 scaleXc = 0; + do { + scaleXc += scaleX; + int numbytes = cntW + (scaleXc >> 16); + scaleXc &= 0xFFFF; + memset(dst, *src++, numbytes); + dst += numbytes; + } while (--widthCnt); + + *dst++ = *src++; + widthCnt = width; + + src += x; + scaleYc += scaleY; + + if (scaleYc >> 16) { + scaleYc = 0; + src -= 176; + continue; + } + + height--; + } +} + +void Screen_LoL::smoothScrollHorizontalStep(int pageNum, int srcX, int dstX, int w) { + uint8 *d = getPagePtr(pageNum); + uint8 *s = d + 112 + srcX; + + int w2 = srcX + w - dstX; + int pitchS = 320 + w2 - (w << 1); + + int pitchD = 320 - w; + int h = 120; + + while (h--) { + for (int i = 0; i < w; i++) + *d++ = *s++; + d -= w; + s -= w2; + + for (int i = 0; i < w; i++) + *s++ = *d++; + + s += pitchS; + d += pitchD; + } +} + +void Screen_LoL::smoothScrollTurnStep1(int srcPage1Num, int srcPage2Num, int dstPageNum) { + uint8 *s = getPagePtr(srcPage1Num) + 273; + uint8 *d = getPagePtr(dstPageNum) + 0xA500; + + for (int i = 0; i < 120; i++) { + uint8 a = *s++; + *d++ = a; + *d++ = a; + + for (int ii = 0; ii < 14; ii++) { + a = *s++; + *d++ = a; + *d++ = a; + *d++ = a; + } + + s += 305; + d += 132; + } + + s = getPagePtr(srcPage2Num) + 112; + d = getPagePtr(dstPageNum) + 0xA52C; + + for (int i = 0; i < 120; i++) { + for (int ii = 0; ii < 33; ii++) { + *d++ = *s++; + *d++ = *s++; + uint8 a = *s++; + *d++ = a; + *d++ = a; + } + + s += 221; + d += 44; + } +} + +void Screen_LoL::smoothScrollTurnStep2(int srcPage1Num, int srcPage2Num, int dstPageNum) { + uint8 *s = getPagePtr(srcPage1Num) + 244; + uint8 *d = getPagePtr(dstPageNum) + 0xA500; + + for (int k = 0; k < 2; k++) { + for (int i = 0; i < 120; i++) { + for (int ii = 0; ii < 44; ii++) { + uint8 a = *s++; + *d++ = a; + *d++ = a; + } + + s += 276; + d += 88; + } + + s = getPagePtr(srcPage2Num) + 112; + d = getPagePtr(dstPageNum) + 0xA558; + } +} + +void Screen_LoL::smoothScrollTurnStep3(int srcPage1Num, int srcPage2Num, int dstPageNum) { + uint8 *s = getPagePtr(srcPage1Num) + 189; + uint8 *d = getPagePtr(dstPageNum) + 0xA500; + + for (int i = 0; i < 120; i++) { + for (int ii = 0; ii < 33; ii++) { + *d++ = *s++; + *d++ = *s++; + uint8 a = *s++; + *d++ = a; + *d++ = a; + } + + s += 221; + d += 44; + } + + s = getPagePtr(srcPage2Num) + 112; + d = getPagePtr(dstPageNum) + 0xA584; + + for (int i = 0; i < 120; i++) { + for (int ii = 0; ii < 14; ii++) { + uint8 a = *s++; + *d++ = a; + *d++ = a; + *d++ = a; + } + + uint8 a = *s++; + *d++ = a; + *d++ = a; + + s += 305; + d += 132; + } +} + +void Screen_LoL::copyRegionSpecial(int page1, int w1, int h1, int x1, int y1, int page2, int w2, int h2, int x2, int y2, int w3, int h3, int mode, ...) { + if (!w3 || !h3) + return; + + uint8 *table1 = 0; + uint8 *table2 = 0; + + if (mode == 2) { + va_list args; + va_start(args, mode); + table1 = va_arg(args, uint8 *); + table2 = va_arg(args, uint8 *); + va_end(args); + } + + int na = 0, nb = 0, nc = w3; + if (!calcBounds(w1, h1, x1, y1, w3, h3, na, nb, nc)) + return; + + int iu5_1 = na; + int iu6_1 = nb; + int ibw_1 = w3; + int dx_1 = x1; + int dy_1 = y1; + + if (!calcBounds(w2, h2, x2, y2, w3, h3, na, nb, nc)) + return; + + int iu5_2 = na; + int iu6_2 = nb; + int ibw_2 = w3; + int ibh_2 = h3; + int dx_2 = x2; + int dy_2 = y2; + + uint8 *src = getPagePtr(page1) + (dy_1 + iu6_2) * w1; + uint8 *dst = getPagePtr(page2) + (dy_2 + iu6_1) * w2; + + for (int i = 0; i < ibh_2; i++) { + uint8 *s = src + iu5_2 + dx_1; + uint8 *d = dst + iu5_1 + dx_2; + + if (mode == 0) { + memcpy(d, s, ibw_2); + + } else if (mode == 1) { + if (!(i & 1)) { + s++; + d++; + } + + for (int ii = (i & 1) ^ 1; ii < ibw_2; ii += 2) { + *d = *s; + d += 2; + s += 2; + } + + } else if (mode == 2) { + for (int ii = 0; ii < ibw_2; ii++) { + uint8 cmd = *s++; + uint8 offs = table1[cmd]; + if (!(offs & 0x80)) + cmd = table2[(offs << 8) | *d]; + *d++ = cmd; + } + + } else if (mode == 3) { + s = s - iu5_2 + ibw_1; + s = s - iu5_2 - 1; + for (int ii = 0; ii < ibw_2; ii++) + *d++ = *s--; + } + + dst += w2; + src += w1; + } + + if (!page2) + addDirtyRect(x2, y2, w2, h2); +} + +void Screen_LoL::copyBlockAndApplyOverlay(int page1, int x1, int y1, int page2, int x2, int y2, int w, int h, int dim, uint8 *ovl) { + if (!w || !h || !ovl) + return; + + const ScreenDim *cdim = getScreenDim(dim); + int ix = cdim->sx << 3; + int iy = cdim->sy; + int iw = cdim->w << 3; + int ih = cdim->h; + + int na = 0, nb = 0, nc = w; + if (!calcBounds(iw, ih, x2, y2, w, h, na, nb, nc)) + return; + + uint8 *src = getPagePtr(page1) + y1 * 320 + x1; + uint8 *dst = getPagePtr(page2) + (y2 + iy) * 320; + + for (int i = 0; i < h; i++) { + uint8 *s = src + na; + uint8 *d = dst + (x2 + ix); + + for (int ii = 0; ii < w; ii++) { + uint8 p = ovl[*s++]; + if (p) + *d = p; + d++; + } + + dst += 320; + src += 320; + } + + if (!page2) + addDirtyRect(x2 + ix, y2 + iy, w, h); +} + +void Screen_LoL::applyOverlaySpecial(int page1, int x1, int y1, int page2, int x2, int y2, int w, int h, int dim, int flag, uint8 *ovl) { + if (!w || !h || !ovl) + return; + + const ScreenDim *cdim = getScreenDim(dim); + int ix = cdim->sx << 3; + int iy = cdim->sy; + int iw = cdim->w << 3; + int ih = cdim->h; + + int na = 0, nb = 0, nc = w; + if (!calcBounds(iw, ih, x2, y2, w, h, na, nb, nc)) + return; + + uint8 *src = getPagePtr(page1) + y1 * 320 + x1; + uint8 *dst = getPagePtr(page2) + (y2 + iy) * 320; + + for (int i = 0; i < h; i++) { + uint8 *s = src + na; + uint8 *d = dst + (x2 + ix); + + if (flag) + d += (i >> 1); + + for (int ii = 0; ii < w; ii++) { + if (*s++) + *d = ovl[*d]; + d++; + } + + dst += 320; + src += 320; + } + + if (!page2) + addDirtyRect(x2 + ix, y2 + iy, w, h); +} + +void Screen_LoL::copyBlockAndApplyOverlayOutro(int srcPage, int dstPage, const uint8 *ovl) { + if (!ovl) + return; + + const byte *src = getCPagePtr(srcPage); + byte *dst = getPagePtr(dstPage); + + for (int y = 0; y < 200; ++y) { + for (int x = 0; x < 80; ++x) { + uint32 srcData = READ_LE_UINT32(src); src += 4; + uint32 dstData = READ_LE_UINT32(dst); + uint16 offset = 0; + + offset = ((srcData & 0xFF) << 8) + (dstData & 0xFF); + *dst++ = ovl[offset]; + + offset = (srcData & 0xFF00) + ((dstData & 0xFF00) >> 8); + *dst++ = ovl[offset]; + + srcData >>= 16; + dstData >>= 16; + + offset = ((srcData & 0xFF) << 8) + (dstData & 0xFF); + *dst++ = ovl[offset]; + + offset = (srcData & 0xFF00) + ((dstData & 0xFF00) >> 8); + *dst++ = ovl[offset]; + } + } +} + +void Screen_LoL::fadeToBlack(int delay, const UpdateFunctor *upFunc) { + Screen::fadeToBlack(delay, upFunc); + _fadeFlag = 2; +} + +void Screen_LoL::fadeToPalette1(int delay) { + loadSpecialColors(getPalette(1)); + fadePalette(getPalette(1), delay); + _fadeFlag = 0; +} + +void Screen_LoL::loadSpecialColors(Palette &dst) { + if (_use16ColorMode) + return; + + dst.copy(*_screenPalette, 192, 4); +} + +void Screen_LoL::copyColor(int dstColorIndex, int srcColorIndex) { + uint8 *s = _screenPalette->getData() + srcColorIndex * 3; + uint8 *d = _screenPalette->getData() + dstColorIndex * 3; + memcpy(d, s, 3); + + uint8 ci[3]; + ci[0] = (d[0] << 2) | (d[0] & 3); + ci[1] = (d[1] << 2) | (d[1] & 3); + ci[2] = (d[2] << 2) | (d[2] & 3); + + _system->getPaletteManager()->setPalette(ci, dstColorIndex, 1); +} + +bool Screen_LoL::fadeColor(int dstColorIndex, int srcColorIndex, uint32 elapsedTicks, uint32 totalTicks) { + if (_use16ColorMode) + return false; + + const uint8 *dst = _screenPalette->getData() + 3 * dstColorIndex; + const uint8 *src = _screenPalette->getData() + 3 * srcColorIndex; + uint8 *p = getPalette(1).getData() + 3 * dstColorIndex; + + bool res = false; + + int16 srcV = 0; + int16 dstV = 0; + int32 outV = 0; + + uint8 tmpPalEntry[3]; + + for (int i = 0; i < 3; i++) { + if (elapsedTicks < totalTicks) { + srcV = *src & 0x3F; + dstV = *dst & 0x3F; + + outV = srcV - dstV; + if (outV) + res = true; + + outV = dstV + ((((outV << 8) / (int32)totalTicks) * (int32)elapsedTicks) >> 8); + } else { + *p = outV = *src; + res = false; + } + + tmpPalEntry[i] = outV & 0xFF; + src++; + dst++; + p++; + } + + _internFadePalette->copy(*_screenPalette); + _internFadePalette->copy(tmpPalEntry, 0, 1, dstColorIndex); + setScreenPalette(*_internFadePalette); + updateScreen(); + + return res; +} + +Palette **Screen_LoL::generateFadeTable(Palette **dst, Palette *src1, Palette *src2, int numTabs) { + int len = _use16ColorMode ? 48 : 768; + if (!src1) + src1 = _screenPalette; + + uint8 *p1 = (*dst++)->getData(); + uint8 *p2 = src1->getData(); + uint8 *p3 = src2->getData(); + uint8 *p4 = p1; + uint8 *p5 = p2; + + for (int i = 0; i < len; i++) { + int8 val = (int8)*p3++ - (int8)*p2++; + *p4++ = (uint8)val; + } + + int16 t = 0; + int16 d = 256 / numTabs; + + for (int i = 1; i < numTabs - 1; i++) { + p2 = p5; + p3 = p1; + t += d; + p4 = (*dst++)->getData(); + + for (int ii = 0; ii < len; ii++) { + int16 val = (((int8)*p3++ * t) >> 8) + (int8)*p2++; + *p4++ = (uint8)val; + } + } + + memcpy(p1, p5, len); + (*dst)->copy(*src2); + + return ++dst; +} + +uint8 Screen_LoL::getShapePaletteSize(const uint8 *shp) { + return shp[10]; +} + +void Screen_LoL::mergeOverlay(int x, int y, int w, int h) { + // For now we convert to 16 colors on overlay merging. If that gives + // any problems, like Screen functionallity not prepared for the + // format PC98 16 color uses, we'll need to think of a better way. + // + // We must do this before merging the overlay, else the font colors + // will be wrong. + if (_use16ColorMode) + convertPC98Gfx(_sjisOverlayPtrs[0] + y * 640 + x, w, h, 640); + + Screen_v2::mergeOverlay(x, y, w, h); +} + +void Screen_LoL::convertPC98Gfx(uint8 *data, int w, int h, int pitch) { + while (h--) { + for (int i = 0; i < w; ++i) { + *data = (*data >> 4) & (*data & 0x0F); + ++data; + } + + data += pitch - w; + } +} + +void Screen_LoL::postProcessCursor(uint8 *data, int w, int h, int pitch) { + if (!_use16ColorMode) + return; + + while (h--) { + for (int i = 0; i < w; ++i) { + if (*data != _cursorColorKey) + *data = (*data >> 4) & (*data & 0x0F); + ++data; + } + + data += pitch - w; + } +} + +} // End of namespace Kyra + +#endif // ENABLE_LOL diff --git a/engines/kyra/graphics/screen_lol.h b/engines/kyra/graphics/screen_lol.h new file mode 100644 index 0000000000..ef14c52463 --- /dev/null +++ b/engines/kyra/graphics/screen_lol.h @@ -0,0 +1,105 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifdef ENABLE_LOL + +#ifndef KYRA_SCREEN_LOL_H +#define KYRA_SCREEN_LOL_H + +#include "kyra/graphics/screen_v2.h" + +namespace Kyra { + +class LoLEngine; + +class Screen_LoL : public Screen_v2 { +public: + Screen_LoL(LoLEngine *vm, OSystem *system); + ~Screen_LoL(); + + void fprintString(const char *format, int x, int y, uint8 col1, uint8 col2, uint flags, ...) GCC_PRINTF(2, 8); + void fprintStringIntro(const char *format, int x, int y, uint8 c1, uint8 c2, uint8 c3, uint flags, ...) GCC_PRINTF(2, 9); + + void drawShadedBox(int x1, int y1, int x2, int y2, int color1, int color2); + + void drawGridBox(int x, int y, int w, int h, int col); + void fadeClearSceneWindow(int delay); + + // smooth scrolling + void backupSceneWindow(int srcPageNum, int dstPageNum); + void restoreSceneWindow(int srcPageNum, int dstPageNum); + void clearGuiShapeMemory(int pageNum); + void copyGuiShapeFromSceneBackupBuffer(int srcPageNum, int dstPageNum); + void copyGuiShapeToSurface(int srcPageNum, int dstPageNum); + void smoothScrollZoomStepTop(int srcPageNum, int dstPageNum, int x, int y); + void smoothScrollZoomStepBottom(int srcPageNum, int dstPageNum, int x, int y); + void smoothScrollHorizontalStep(int pageNum, int x, int u2, int w); + void smoothScrollTurnStep1(int srcPage1Num, int srcPage2Num, int dstPageNum); + void smoothScrollTurnStep2(int srcPage1Num, int srcPage2Num, int dstPageNum); + void smoothScrollTurnStep3(int srcPage1Num, int srcPage2Num, int dstPageNum); + + void copyRegionSpecial(int page1, int w1, int h1, int x1, int y1, int page2, int w2, int h2, int x2, int y2, int w3, int h3, int mode, ...); + + // palette stuff + void fadeToBlack(int delay=0x54, const UpdateFunctor *upFunc = 0); + void fadeToPalette1(int delay); + void loadSpecialColors(Palette &dst); + void copyColor(int dstColorIndex, int srcColorIndex); + bool fadeColor(int dstColorIndex, int srcColorIndex, uint32 elapsedTicks, uint32 totalTicks); + Palette **generateFadeTable(Palette **dst, Palette *src1, Palette *src2, int numTabs); + + void generateGrayOverlay(const Palette &Pal, uint8 *grayOverlay, int factor, int addR, int addG, int addB, int lastColor, bool skipSpecialColors); + uint8 *getLevelOverlay(int index) { return _levelOverlays[index]; } + + void createTransparencyTablesIntern(const uint8 *ovl, int a, const uint8 *fxPal1, const uint8 *fxPal2, uint8 *outTable1, uint8 *outTable2, int b); + + void copyBlockAndApplyOverlay(int page1, int x1, int y1, int page2, int x2, int y2, int w, int h, int dim, uint8 *ovl); + void applyOverlaySpecial(int page1, int x1, int y1, int page2, int x2, int y2, int w, int h, int dim, int flag, uint8 *ovl); + + void copyBlockAndApplyOverlayOutro(int srcPage, int dstPage, const uint8 *ovl); + + uint8 getShapePaletteSize(const uint8 *shp); + + uint8 *_paletteOverlay1; + uint8 *_paletteOverlay2; + uint8 *_grayOverlay; + int _fadeFlag; + + // PC98 specific + static void convertPC98Gfx(uint8 *data, int w, int h, int pitch); + +private: + static const ScreenDim _screenDimTable256C[]; + static const ScreenDim _screenDimTable16C[]; + static const int _screenDimTableCount; + + uint8 *_levelOverlays[8]; + + void mergeOverlay(int x, int y, int w, int h); + void postProcessCursor(uint8 *data, int width, int height, int pitch); +}; + +} // End of namespace Kyra + +#endif + +#endif // ENABLE_LOL diff --git a/engines/kyra/graphics/screen_mr.cpp b/engines/kyra/graphics/screen_mr.cpp new file mode 100644 index 0000000000..acb99fe0b0 --- /dev/null +++ b/engines/kyra/graphics/screen_mr.cpp @@ -0,0 +1,130 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/graphics/screen_mr.h" +#include "kyra/engine/kyra_mr.h" + +namespace Kyra { + +Screen_MR::Screen_MR(KyraEngine_MR *vm, OSystem *system) + : Screen_v2(vm, system, _screenDimTable, _screenDimTableCount) { +} + +Screen_MR::~Screen_MR() { +} + +int Screen_MR::getLayer(int x, int y) { + if (x < 0) + x = 0; + else if (x >= 320) + x = 319; + if (y < 0) + y = 0; + else if (y >= 188) + y = 187; + + if (y < _maskMinY || y > _maskMaxY) + return 15; + + uint8 pixel = *(getCPagePtr(5) + y * 320 + x); + pixel &= 0x7F; + pixel >>= 3; + + if (pixel < 1) + pixel = 1; + else if (pixel > 15) + pixel = 15; + return pixel; +} + +byte Screen_MR::getShapeFlag1(int x, int y) { + if (y < _maskMinY || y > _maskMaxY) + return 0; + + uint8 color = _shapePages[0][y * SCREEN_W + x]; + color &= 0x80; + color ^= 0x80; + + if (color & 0x80) + return 1; + return 0; +} + +byte Screen_MR::getShapeFlag2(int x, int y) { + if (y < _maskMinY || y > _maskMaxY) + return 0; + + uint8 color = _shapePages[0][y * SCREEN_W + x]; + color &= 0x7F; + color &= 0x87; + return color; +} + +int Screen_MR::getDrawLayer(int x, int y) { + int xpos = x - 8; + int ypos = y; + int layer = 1; + + for (int curX = xpos; curX < xpos + 24; ++curX) { + int tempLayer = getShapeFlag2(curX, ypos); + + if (layer < tempLayer) + layer = tempLayer; + + if (layer >= 7) + return 7; + } + return layer; +} + +int Screen_MR::getDrawLayer2(int x, int y, int height) { + int xpos = x - 8; + int ypos = y; + int layer = 1; + + for (int useX = xpos; useX < xpos + 24; ++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_MR::drawFilledBox(int x1, int y1, int x2, int y2, uint8 c1, uint8 c2, uint8 c3) { + fillRect(x1, y1, x2, y2, c1); + + fillRect(x1, y1, x2, y1+1, c2); + fillRect(x2-1, y1, x2, y2, c2); + + drawClippedLine(x1, y1, x1, y2, c3); + drawClippedLine(x1+1, y1+1, x1+1, y2-2, c3); + drawClippedLine(x1, y2, x2, y2, c3); + drawClippedLine(x1, y2-1, x2-1, y2-1, c3); +} + +} // End of namespace Kyra diff --git a/engines/kyra/graphics/screen_mr.h b/engines/kyra/graphics/screen_mr.h new file mode 100644 index 0000000000..fcbef380c7 --- /dev/null +++ b/engines/kyra/graphics/screen_mr.h @@ -0,0 +1,53 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef KYRA_SCREEN_MR_H +#define KYRA_SCREEN_MR_H + +#include "kyra/graphics/screen_v2.h" + +namespace Kyra { + +class KyraEngine_MR; + +class Screen_MR : public Screen_v2 { +public: + Screen_MR(KyraEngine_MR *vm, OSystem *system); + ~Screen_MR(); + + int getLayer(int x, int y); + + byte getShapeFlag1(int x, int y); + byte getShapeFlag2(int x, int y); + + int getDrawLayer(int x, int y); + int getDrawLayer2(int x, int y, int height); + + void drawFilledBox(int x1, int y1, int x2, int y2, uint8 c1, uint8 c2, uint8 c3); +private: + static const ScreenDim _screenDimTable[]; + static const int _screenDimTableCount; +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/graphics/screen_v2.cpp b/engines/kyra/graphics/screen_v2.cpp new file mode 100644 index 0000000000..cb9fbca1b7 --- /dev/null +++ b/engines/kyra/graphics/screen_v2.cpp @@ -0,0 +1,461 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/graphics/screen_v2.h" + +#include "common/endian.h" + +namespace Kyra { + +Screen_v2::Screen_v2(KyraEngine_v1 *vm, OSystem *system, const ScreenDim *dimTable, const int dimTableSize) + : Screen(vm, system, dimTable, dimTableSize), _wsaFrameAnimBuffer(0) { + _wsaFrameAnimBuffer = new uint8[1024]; + assert(_wsaFrameAnimBuffer); +} + +Screen_v2::~Screen_v2() { + delete[] _wsaFrameAnimBuffer; +} + +uint8 *Screen_v2::generateOverlay(const Palette &pal, uint8 *buffer, int opColor, uint weight, int maxColor) { + if (!buffer) + return buffer; + + weight = MIN<uint>(weight, 255) >> 1; + + const byte opR = pal[opColor * 3 + 0]; + const byte opG = pal[opColor * 3 + 1]; + const byte opB = pal[opColor * 3 + 2]; + + uint8 *dst = buffer; + *dst++ = 0; + + int maxIndex = maxColor; + if (maxIndex == -1) { + if (_vm->game() == GI_LOL) { + if (_use16ColorMode) + maxIndex = 255; + else + maxIndex = 127; + } else { + maxIndex = 255; + } + } + + for (int i = 1; i != 256; ++i) { + const byte curR = pal[i * 3 + 0] - (((pal[i * 3 + 0] - opR) * weight) >> 7); + const byte curG = pal[i * 3 + 1] - (((pal[i * 3 + 1] - opG) * weight) >> 7); + const byte curB = pal[i * 3 + 2] - (((pal[i * 3 + 2] - opB) * weight) >> 7); + + uint16 idxSum = _use16ColorMode ? 0xFFFF : 0x7FFF; + byte index = opColor; + + for (int curIdx = 1; curIdx <= maxIndex; ++curIdx) { + if (!_use16ColorMode && i == curIdx) + continue; + + int diff = 0; + uint16 sum = 0; + + diff = pal[curIdx * 3 + 0] - curR; + sum += diff * diff; + diff = pal[curIdx * 3 + 1] - curG; + sum += diff * diff; + diff = pal[curIdx * 3 + 2] - curB; + sum += diff * diff; + + if (!sum) { + index = curIdx; + break; + } + + if (sum <= idxSum) { + if (!_use16ColorMode || (curIdx == opColor || curIdx != i)) { + idxSum = sum; + index = curIdx; + } + } + } + + *dst++ = index; + } + + return buffer; +} + +void Screen_v2::applyOverlay(int x, int y, int w, int h, int pageNum, const uint8 *overlay) { + if (pageNum == 0 || pageNum == 1) + addDirtyRect(x, y, w, h); + + uint8 *dst = getPagePtr(pageNum) + y * 320 + x; + while (h--) { + for (int wi = 0; wi < w; ++wi) { + uint8 index = *dst; + *dst++ = overlay[index]; + } + dst += 320 - w; + } +} + +int Screen_v2::findLeastDifferentColor(const uint8 *paletteEntry, const Palette &pal, uint8 firstColor, uint16 numColors, bool skipSpecialColors) { + int m = 0x7FFF; + int r = 0x101; + + for (int i = 0; i < numColors; i++) { + if (skipSpecialColors && i >= 0xC0 && i <= 0xC3) + continue; + + int v = paletteEntry[0] - pal[(i + firstColor) * 3 + 0]; + int c = v * v; + v = paletteEntry[1] - pal[(i + firstColor) * 3 + 1]; + c += (v * v); + v = paletteEntry[2] - pal[(i + firstColor) * 3 + 2]; + c += (v * v); + + if (c <= m) { + m = c; + r = i; + } + } + + return r; +} + +void Screen_v2::getFadeParams(const Palette &pal, int delay, int &delayInc, int &diff) { + int maxDiff = 0; + diff = 0; + for (int i = 0; i < pal.getNumColors() * 3; ++i) { + diff = ABS(pal[i] - (*_screenPalette)[i]); + maxDiff = MAX(maxDiff, diff); + } + + delayInc = delay << 8; + if (maxDiff != 0) { + delayInc /= maxDiff; + delayInc = MIN(delayInc, 0x7FFF); + } + + delay = delayInc; + for (diff = 1; diff <= maxDiff; ++diff) { + if (delayInc >= 256) + break; + delayInc += delay; + } +} + +bool Screen_v2::timedPaletteFadeStep(uint8 *pal1, uint8 *pal2, uint32 elapsedTime, uint32 totalTime) { + Palette &p1 = getPalette(1); + + bool res = false; + for (int i = 0; i < p1.getNumColors() * 3; i++) { + uint8 out = 0; + + if (elapsedTime < totalTime) { + int32 d = ((pal2[i] & 0x3F) - (pal1[i] & 0x3F)); + if (d) + res = true; + + int32 val = ((((d << 8) / (int32)totalTime) * (int32)elapsedTime) >> 8); + out = ((pal1[i] & 0x3F) + (int8)val); + } else { + out = p1[i] = (pal2[i] & 0x3F); + res = false; + } + + (*_internFadePalette)[i] = out; + } + + setScreenPalette(*_internFadePalette); + updateScreen(); + + return res; +} + +const uint8 *Screen_v2::getPtrToShape(const uint8 *shpFile, int shape) { + uint16 shapes = READ_LE_UINT16(shpFile); + + if (shapes <= shape) + return 0; + + uint32 offset = READ_LE_UINT32(shpFile + (shape << 2) + 2); + + return shpFile + offset + 2; +} + +uint8 *Screen_v2::getPtrToShape(uint8 *shpFile, int shape) { + uint16 shapes = READ_LE_UINT16(shpFile); + + if (shapes <= shape) + return 0; + + uint32 offset = READ_LE_UINT32(shpFile + (shape << 2) + 2); + + return shpFile + offset + 2; +} + +int Screen_v2::getShapeScaledWidth(const uint8 *shpFile, int scale) { + if (!shpFile) + return 0; + int width = READ_LE_UINT16(shpFile + 3); + return (width * scale) >> 8; +} + +int Screen_v2::getShapeScaledHeight(const uint8 *shpFile, int scale) { + if (!shpFile) + return 0; + int height = shpFile[2]; + return (height * scale) >> 8; +} + +uint16 Screen_v2::getShapeSize(const uint8 *shp) { + if (!shp) + return 0; + return READ_LE_UINT16(shp + 6); +} + +uint8 *Screen_v2::makeShapeCopy(const uint8 *src, int index) { + const uint8 *shape = getPtrToShape(src, index); + if (!shape) + return 0; + + int size = getShapeSize(shape); + + uint8 *copy = new uint8[size]; + assert(copy); + memcpy(copy, shape, size); + + return copy; +} + +int Screen_v2::getLayer(int x, int y) { + if (x < 0) + x = 0; + else if (x >= 320) + x = 319; + if (y < 0) + y = 0; + else if (y >= 144) + y = 143; + + uint8 pixel = *(getCPagePtr(5) + y * 320 + x); + pixel &= 0x7F; + pixel >>= 3; + + if (pixel < 1) + pixel = 1; + else if (pixel > 15) + pixel = 15; + return pixel; +} + +int Screen_v2::getRectSize(int w, int h) { + if (w > 320 || h > 200) + return 0; + return w * h; +} + +void Screen_v2::setTextColorMap(const uint8 *cmap) { + setTextColor(cmap, 0, 15); +} + +void Screen_v2::wsaFrameAnimationStep(int x1, int y1, int x2, int y2, + int w1, int h1, int w2, int h2, int srcPage, int dstPage, int dim) { + + if (!w1 || !h1 || !w2 || !h2) + return; + + ScreenDim cdm = *getScreenDim(dim); + cdm.sx <<= 3; + cdm.w <<= 3; + + int na = 0, nb = 0, nc = w2; + + if (!calcBounds(cdm.w, cdm.h, x2, y2, w2, h2, na, nb, nc)) + return; + + const uint8 *src = getPagePtr(srcPage) + y1 * 320; + uint8 *dst = getPagePtr(dstPage) + (y2 + cdm.sy) * 320; + + int u = -1; + + do { + int t = (nb * h1) / h2; + if (t != u) { + u = t; + const uint8 *s = src + x1 + t * 320; + uint8 *dt = (uint8 *)_wsaFrameAnimBuffer; + + t = w2 - w1; + if (!t) { + memcpy(dt, s, w2); + } else if (t > 0) { + if (w1 == 1) { + memset(dt, *s, w2); + } else { + t = ((((((w2 - w1 + 1) & 0xFFFF) << 8) / w1) + 0x100) & 0xFFFF) << 8; + int bp = 0; + for (int i = 0; i < w1; i++) { + int cnt = (t >> 16); + bp += (t & 0xFFFF); + if (bp > 0xFFFF) { + bp -= 0xFFFF; + cnt++; + } + memset(dt, *s++, cnt); + dt += cnt; + } + } + } else { + if (w2 == 1) { + *dt = *s; + } else { + t = (((((w1 - w2) & 0xFFFF) << 8) / w2) & 0xFFFF) << 8; + int bp = 0; + for (int i = 0; i < w2; i++) { + *dt++ = *s++; + bp += (t & 0xFFFF); + if (bp > 0xFFFF) { + bp -= 0xFFFF; + s++; + } + s += (t >> 16); + } + } + } + } + memcpy(dst + x2 + cdm.sx, _wsaFrameAnimBuffer + na, w2); + dst += 320; + } while (++nb < h2); + + if (!dstPage) + addDirtyRect(x2, y2, w2, h2); +} + +void Screen_v2::copyPageMemory(int srcPage, int srcPos, int dstPage, int dstPos, int numBytes) { + const uint8 *src = getPagePtr(srcPage) + srcPos; + uint8 *dst = getPagePtr(dstPage) + dstPos; + memcpy(dst, src, numBytes); +} + +void Screen_v2::copyRegionEx(int srcPage, int srcW, int srcH, int dstPage, int dstX, int dstY, int dstW, int dstH, const ScreenDim *dim, bool flag) { + int x0 = dim->sx << 3; + int y0 = dim->sy; + int w0 = dim->w << 3; + int h0 = dim->h; + + int x1 = dstX; + int y1 = dstY; + int w1 = dstW; + int h1 = dstH; + + int x2, y2, w2; + + calcBounds(w0, h0, x1, y1, w1, h1, x2, y2, w2); + + const uint8 *src = getPagePtr(srcPage) + (320 * srcH) + srcW; + uint8 *dst = getPagePtr(dstPage) + 320 * (y0 + y1); + + for (int y = 0; y < h1; y++) { + const uint8 *s = src + x2; + uint8 *d = dst + x0 + x1; + + if (flag) + d += (h1 >> 1); + + for (int x = 0; x < w1; x++) { + if (*s) + *d = *s; + s++; + d++; + } + dst += 320; + src += 320; + } +} + +bool Screen_v2::calcBounds(int w0, int h0, int &x1, int &y1, int &w1, int &h1, int &x2, int &y2, int &w2) { + x2 = 0; + y2 = 0; + w2 = w1; + + int t = x1 + w1; + if (t < 1) { + w1 = h1 = -1; + } else { + if (t <= x1) { + x2 = w1 - t; + w1 = t; + x1 = 0; + } + t = w0 - x1; + if (t < 1) { + w1 = h1 = -1; + } else { + if (t <= w1) { + w1 = t; + } + w2 -= w1; + t = h1 + y1; + if (t < 1) { + w1 = h1 = -1; + } else { + if (t <= y1) { + y2 = h1 - t; + h1 = t; + y1 = 0; + } + t = h0 - y1; + if (t < 1) { + w1 = h1 = -1; + } else { + if (t <= h1) { + h1 = t; + } + } + } + } + } + + return w1 != -1; +} + +void Screen_v2::checkedPageUpdate(int srcPage, int dstPage) { + const uint32 *src = (const uint32 *)getPagePtr(srcPage); + uint32 *dst = (uint32 *)getPagePtr(dstPage); + uint32 *page0 = (uint32 *)getPagePtr(0); + + bool updated = false; + + for (int y = 0; y < 200; ++y) { + for (int x = 0; x < 80; ++x, ++src, ++dst, ++page0) { + if (*src != *dst) { + updated = true; + *dst = *page0 = *src; + } + } + } + + if (updated) + addDirtyRect(0, 0, 320, 200); +} + +} // End of namespace Kyra diff --git a/engines/kyra/graphics/screen_v2.h b/engines/kyra/graphics/screen_v2.h new file mode 100644 index 0000000000..9c8aa12563 --- /dev/null +++ b/engines/kyra/graphics/screen_v2.h @@ -0,0 +1,81 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef KYRA_SCREEN_V2_H +#define KYRA_SCREEN_V2_H + +#include "kyra/graphics/screen.h" +#include "kyra/engine/kyra_v2.h" + +namespace Kyra { + +class Screen_v2 : public Screen { +public: + Screen_v2(KyraEngine_v1 *vm, OSystem *system, const ScreenDim *dimTable, const int dimTableSize); + ~Screen_v2(); + + // screen page handling + void checkedPageUpdate(int srcPage, int dstPage); + + // palette handling + uint8 *generateOverlay(const Palette &pal, uint8 *buffer, int color, uint weight, int maxColor = -1); + void applyOverlay(int x, int y, int w, int h, int pageNum, const uint8 *overlay); + int findLeastDifferentColor(const uint8 *paletteEntry, const Palette &pal, uint8 firstColor, uint16 numColors, bool skipSpecialColors = false); + + virtual void getFadeParams(const Palette &pal, int delay, int &delayInc, int &diff); + + bool timedPaletteFadeStep(uint8 *pal1, uint8 *pal2, uint32 elapsedTime, uint32 totalTime); + + // shape handling + uint8 *getPtrToShape(uint8 *shpFile, int shape); + const uint8 *getPtrToShape(const uint8 *shpFile, int shape); + + int getShapeScaledWidth(const uint8 *shpFile, int scale); + int getShapeScaledHeight(const uint8 *shpFile, int scale); + + uint16 getShapeSize(const uint8 *shp); + + uint8 *makeShapeCopy(const uint8 *src, int index); + + // rect handling + int getRectSize(int w, int h); + bool calcBounds(int w0, int h0, int &x1, int &y1, int &w1, int &h1, int &x2, int &y2, int &w2); + + // text display + void setTextColorMap(const uint8 *cmap); + + // layer handling + virtual int getLayer(int x, int y); + + // special WSA handling + void wsaFrameAnimationStep(int x1, int y1, int x2, int y2, int w1, int h1, int w2, int h2, int srcPage, int dstPage, int dim); + + // used in non-interactive HoF/LoL demos + void copyPageMemory(int srcPage, int srcPos, int dstPage, int dstPos, int numBytes); + void copyRegionEx(int srcPage, int srcW, int srcH, int dstPage, int dstX,int dstY, int dstW, int dstH, const ScreenDim *d, bool flag = false); +protected: + uint8 *_wsaFrameAnimBuffer; +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/graphics/vqa.cpp b/engines/kyra/graphics/vqa.cpp new file mode 100644 index 0000000000..5a4e250b42 --- /dev/null +++ b/engines/kyra/graphics/vqa.cpp @@ -0,0 +1,667 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +// Player for Kyrandia 3 VQA movies, based on the information found at +// http://multimedia.cx/VQA_INFO.TXT +// +// The benchl.vqa movie (or whatever it is) is not supported. It does not have +// a FINF chunk. +// +// The jung2.vqa movie does work, but only thanks to a grotesque hack. + +#include "kyra/kyra_v1.h" +#include "kyra/graphics/vqa.h" +#include "kyra/graphics/screen.h" + +#include "audio/audiostream.h" +#include "audio/decoders/raw.h" + +#include "common/system.h" +#include "common/events.h" + +#include "graphics/palette.h" +#include "graphics/surface.h" + +namespace Kyra { + +static uint32 readTag(Common::SeekableReadStream *stream) { + // Some tags have to be on an even offset, so they are padded with a + // zero byte. Skip that. + + uint32 tag = stream->readUint32BE(); + + if (stream->eos()) + return 0; + + if (!(tag & 0xFF000000)) { + tag = (tag << 8) | stream->readByte(); + } + + return tag; +} + +VQADecoder::VQADecoder() { + memset(&_header, 0, sizeof(_header)); +} + +VQADecoder::~VQADecoder() { + close(); + delete[] _frameInfo; +} + +bool VQADecoder::loadStream(Common::SeekableReadStream *stream) { + close(); + _fileStream = stream; + + if (_fileStream->readUint32BE() != MKTAG('F','O','R','M')) { + warning("VQADecoder::loadStream(): Cannot find `FORM' tag"); + return false; + } + + // Ignore the size of the FORM chunk. We're only interested in its + // children. + _fileStream->readUint32BE(); + + if (_fileStream->readUint32BE() != MKTAG('W','V','Q','A')) { + warning("VQADecoder::loadStream(): Cannot find `WVQA' tag"); + return false; + } + + // We want to find both a VQHD chunk containing the header, and a FINF + // chunk containing the frame offsets. + + bool foundVQHD = false; + bool foundFINF = false; + + VQAAudioTrack *audioTrack = NULL; + + // The information we need is stored in two chunks: VQHD and FINF. We + // need both of them before we can begin decoding the movie. + + while (!foundVQHD || !foundFINF) { + uint32 tag = readTag(stream); + uint32 size = _fileStream->readUint32BE(); + + switch (tag) { + case MKTAG('V','Q','H','D'): + handleVQHD(_fileStream); + if (_header.flags & 1) { + audioTrack = new VQAAudioTrack(&_header, getSoundType()); + addTrack(audioTrack); + } + foundVQHD = true; + break; + case MKTAG('F','I','N','F'): + if (!foundVQHD) { + warning("VQADecoder::loadStream(): Found `FINF' before `VQHD'"); + return false; + } + if (size != 4 * getFrameCount()) { + warning("VQADecoder::loadStream(): Expected size %d for `FINF' chunk, but got %u", 4 * getFrameCount(), size); + return false; + } + handleFINF(_fileStream); + foundFINF = true; + break; + default: + warning("VQADecoder::loadStream(): Unknown tag `%s'", tag2str(tag)); + _fileStream->seek(size, SEEK_CUR); + break; + } + } + + return true; +} + +void VQADecoder::handleVQHD(Common::SeekableReadStream *stream) { + _header.version = stream->readUint16LE(); + _header.flags = stream->readUint16LE(); + _header.numFrames = stream->readUint16LE(); + _header.width = stream->readUint16LE(); + _header.height = stream->readUint16LE(); + _header.blockW = stream->readByte(); + _header.blockH = stream->readByte(); + _header.frameRate = stream->readByte(); + _header.cbParts = stream->readByte(); + _header.colors = stream->readUint16LE(); + _header.maxBlocks = stream->readUint16LE(); + _header.unk1 = stream->readUint32LE(); + _header.unk2 = stream->readUint16LE(); + _header.freq = stream->readUint16LE(); + _header.channels = stream->readByte(); + _header.bits = stream->readByte(); + _header.unk3 = stream->readUint32LE(); + _header.unk4 = stream->readUint16LE(); + _header.maxCBFZSize = stream->readUint32LE(); + _header.unk5 = stream->readUint32LE(); + + _frameInfo = new uint32[_header.numFrames + 1]; + + VQAVideoTrack *videoTrack = new VQAVideoTrack(&_header); + addTrack(videoTrack); + + // Kyrandia 3 uses version 1 VQA files, and is the only known game to + // do so. This version of the format has some implicit default values. + + if (_header.version == 1) { + if (_header.freq == 0) + _header.freq = 22050; + if (_header.channels == 0) + _header.channels = 1; + if (_header.bits == 0) + _header.bits = 8; + } + + if (_header.flags & 1) { + // Kyrandia 3 uses 8-bit sound, and so far testing indicates + // that it's all mono. + // + // This is good, because it means we won't have to worry about + // the confusing parts of the VQA spec, where 8- and 16-bit + // data have different signedness and stereo sample layout + // varies between different games. + + assert(_header.bits == 8); + assert(_header.channels == 1); + } +} + +void VQADecoder::handleFINF(Common::SeekableReadStream *stream) { + for (int i = 0; i < _header.numFrames; i++) { + _frameInfo[i] = 2 * stream->readUint32LE(); + } + + // HACK: This flag is set in jung2.vqa, and its purpose - if it has + // one - is currently unknown. It can't be a general purpose flag, + // because in large movies the frame offset can be large enough to + // set this flag, though of course never for the first frame. + // + // At least in my copy of Kyrandia 3, _frameInfo[0] is 0x81000098, and + // the desired index is 0x4716. So the value should be 0x80004716, but + // I don't want to hard-code it. Instead, scan the file for the offset + // to the first VQFR chunk. + + if (_frameInfo[0] & 0x01000000) { + uint32 oldPos = stream->pos(); + + while (1) { + uint32 scanTag = readTag(stream); + uint32 scanSize = stream->readUint32BE(); + + if (stream->eos()) + break; + + if (scanTag == MKTAG('V','Q','F','R')) { + _frameInfo[0] = (stream->pos() - 8) | 0x80000000; + break; + } + + stream->seek(scanSize, SEEK_CUR); + } + + stream->seek(oldPos); + } + + _frameInfo[_header.numFrames] = 0x7FFFFFFF; +} + +void VQADecoder::readNextPacket() { + VQAVideoTrack *videoTrack = (VQAVideoTrack *)getTrack(0); + VQAAudioTrack *audioTrack = (VQAAudioTrack *)getTrack(1); + + assert(videoTrack); + + int curFrame = videoTrack->getCurFrame(); + + // Stop if reading the tag is enough to put us ahead of the next frame + int32 end = (_frameInfo[curFrame + 1] & 0x7FFFFFFF) - 7; + + // At this point, we probably only need to adjust for the offset in the + // stream to be even. But we may as well do this to really make sure + // we have the correct offset. + if (curFrame >= 0) { + _fileStream->seek(_frameInfo[curFrame] & 0x7FFFFFFF); + if (_frameInfo[curFrame] & 0x80000000) { + videoTrack->setHasDirtyPalette(); + } + } + + while (!_fileStream->eos() && _fileStream->pos() < end) { + uint32 tag = readTag(_fileStream); + uint32 size; + + switch (tag) { + case MKTAG('S','N','D','0'): // Uncompressed sound + assert(audioTrack); + audioTrack->handleSND0(_fileStream); + break; + case MKTAG('S','N','D','1'): // Compressed sound, almost like AUD + assert(audioTrack); + audioTrack->handleSND1(_fileStream); + break; + case MKTAG('S','N','D','2'): // Compressed sound + assert(audioTrack); + audioTrack->handleSND2(_fileStream); + break; + case MKTAG('V','Q','F','R'): + videoTrack->handleVQFR(_fileStream); + break; + case MKTAG('C','M','D','S'): + // The purpose of this is unknown, but it's known to + // exist so don't warn about it. + size = _fileStream->readUint32BE(); + _fileStream->seek(size, SEEK_CUR); + break; + default: + warning("VQADecoder::readNextPacket(): Unknown tag `%s'", tag2str(tag)); + size = _fileStream->readUint32BE(); + _fileStream->seek(size, SEEK_CUR); + break; + } + } +} + +// ----------------------------------------------------------------------- + +VQADecoder::VQAAudioTrack::VQAAudioTrack(const VQAHeader *header, Audio::Mixer::SoundType soundType) : + AudioTrack(soundType) { + _audioStream = Audio::makeQueuingAudioStream(header->freq, false); +} + +VQADecoder::VQAAudioTrack::~VQAAudioTrack() { + delete _audioStream; +} + +Audio::AudioStream *VQADecoder::VQAAudioTrack::getAudioStream() const { + return _audioStream; +} + +void VQADecoder::VQAAudioTrack::handleSND0(Common::SeekableReadStream *stream) { + uint32 size = stream->readUint32BE(); + byte *buf = (byte *)malloc(size); + stream->read(buf, size); + _audioStream->queueBuffer(buf, size, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED); +} + +void VQADecoder::VQAAudioTrack::handleSND1(Common::SeekableReadStream *stream) { + stream->readUint32BE(); + uint16 outsize = stream->readUint16LE(); + uint16 insize = stream->readUint16LE(); + byte *inbuf = (byte *)malloc(insize); + + stream->read(inbuf, insize); + + if (insize == outsize) { + _audioStream->queueBuffer(inbuf, insize, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED); + } else { + const int8 WSTable2Bit[] = { -2, -1, 0, 1 }; + const int8 WSTable4Bit[] = { + -9, -8, -6, -5, -4, -3, -2, -1, + 0, 1, 2, 3, 4, 5, 6, 8 + }; + + byte *outbuf = (byte *)malloc(outsize); + byte *in = inbuf; + byte *out = outbuf; + int16 curSample = 0x80; + uint16 bytesLeft = outsize; + + while (bytesLeft > 0) { + uint16 input = *in++ << 2; + byte code = (input >> 8) & 0xFF; + int8 count = (input & 0xFF) >> 2; + int i; + + switch (code) { + case 2: + if (count & 0x20) { + /* NOTE: count is signed! */ + count <<= 3; + curSample += (count >> 3); + *out++ = curSample; + bytesLeft--; + } else { + for (; count >= 0; count--) { + *out++ = *in++; + bytesLeft--; + } + curSample = *(out - 1); + } + break; + case 1: + for (; count >= 0; count--) { + code = *in++; + + for (i = 0; i < 2; i++) { + curSample += WSTable4Bit[code & 0x0F]; + curSample = CLIP<int16>(curSample, 0, 255); + code >>= 4; + *out++ = curSample; + } + + bytesLeft -= 2; + } + break; + case 0: + for (; count >= 0; count--) { + code = *in++; + + for (i = 0; i < 4; i++) { + curSample += WSTable2Bit[code & 0x03]; + curSample = CLIP<int16>(curSample, 0, 255); + code >>= 2; + *out++ = curSample; + } + + bytesLeft -= 4; + } + break; + default: + for (; count >= 0; count--) { + *out++ = curSample; + bytesLeft--; + } + break; + } + } + _audioStream->queueBuffer(outbuf, outsize, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED); + free(inbuf); + } +} + +void VQADecoder::VQAAudioTrack::handleSND2(Common::SeekableReadStream *stream) { + uint32 size = stream->readUint32BE(); + warning("VQADecoder::VQAAudioTrack::handleSND2(): `SND2' is not implemented"); + stream->seek(size, SEEK_CUR); +} + +// ----------------------------------------------------------------------- + +VQADecoder::VQAVideoTrack::VQAVideoTrack(const VQAHeader *header) { + memset(_palette, 0, sizeof(_palette)); + _dirtyPalette = false; + + _width = header->width; + _height = header->height; + _blockW = header->blockW; + _blockH = header->blockH; + _cbParts = header->cbParts; + + _newFrame = false; + + _curFrame = -1; + _frameCount = header->numFrames; + _frameRate = header->frameRate; + + _codeBookSize = 0xF00 * header->blockW * header->blockH; + _compressedCodeBook = false; + _codeBook = new byte[_codeBookSize]; + _partialCodeBookSize = 0; + _numPartialCodeBooks = 0; + _partialCodeBook = new byte[_codeBookSize]; + _numVectorPointers = (header->width / header->blockW) * (header->height * header->blockH); + _vectorPointers = new uint16[_numVectorPointers]; + + memset(_codeBook, 0, _codeBookSize); + memset(_partialCodeBook, 0, _codeBookSize); + memset(_vectorPointers, 0, _numVectorPointers); + + _surface = new Graphics::Surface(); + _surface->create(header->width, header->height, Graphics::PixelFormat::createFormatCLUT8()); +} + +VQADecoder::VQAVideoTrack::~VQAVideoTrack() { + _surface->free(); + delete _surface; + delete[] _codeBook; + delete[] _partialCodeBook; + delete[] _vectorPointers; +} + +uint16 VQADecoder::VQAVideoTrack::getWidth() const { + return _width; +} + +uint16 VQADecoder::VQAVideoTrack::getHeight() const { + return _height; +} + +Graphics::PixelFormat VQADecoder::VQAVideoTrack::getPixelFormat() const { + return _surface->format; +} + +int VQADecoder::VQAVideoTrack::getCurFrame() const { + return _curFrame; +} + +int VQADecoder::VQAVideoTrack::getFrameCount() const { + return _frameCount; +} + +Common::Rational VQADecoder::VQAVideoTrack::getFrameRate() const { + return _frameRate; +} + +void VQADecoder::VQAVideoTrack::setHasDirtyPalette() { + _dirtyPalette = true; +} + +bool VQADecoder::VQAVideoTrack::hasDirtyPalette() const { + return _dirtyPalette; +} + +const byte *VQADecoder::VQAVideoTrack::getPalette() const { + _dirtyPalette = false; + return _palette; +} + +const Graphics::Surface *VQADecoder::VQAVideoTrack::decodeNextFrame() { + if (_newFrame) { + _newFrame = false; + + int blockPitch = _width / _blockW; + + for (int by = 0; by < _height / _blockH; by++) { + for (int bx = 0; bx < blockPitch; bx++) { + byte *dst = (byte *)_surface->getBasePtr(bx * _blockW, by * _blockH); + int val = _vectorPointers[by * blockPitch + bx]; + int i; + + if ((val & 0xFF00) == 0xFF00) { + // Solid color + for (i = 0; i < _blockH; i++) { + memset(dst, 255 - (val & 0xFF), _blockW); + dst += _width; + } + } else { + // Copy data from _vectorPointers. I'm not sure + // why we don't use the three least significant + // bits of 'val'. + byte *src = &_codeBook[(val >> 3) * _blockW * _blockH]; + + for (i = 0; i < _blockH; i++) { + memcpy(dst, src, _blockW); + src += _blockW; + dst += _width; + } + } + } + } + + if (_numPartialCodeBooks == _cbParts) { + if (_compressedCodeBook) { + Screen::decodeFrame4(_partialCodeBook, _codeBook, _codeBookSize); + } else { + memcpy(_codeBook, _partialCodeBook, _partialCodeBookSize); + } + _numPartialCodeBooks = 0; + _partialCodeBookSize = 0; + } + } + + _curFrame++; + return _surface; +} + +void VQADecoder::VQAVideoTrack::handleVQFR(Common::SeekableReadStream *stream) { + uint32 size = stream->readUint32BE(); + int32 end = stream->pos() + size - 8; + byte *inbuf; + + _newFrame = true; + + while (stream->pos() < end) { + uint32 tag = readTag(stream); + uint32 i; + size = stream->readUint32BE(); + + switch (tag) { + case MKTAG('C','B','F','0'): // Full codebook + stream->read(_codeBook, size); + break; + case MKTAG('C','B','F','Z'): // Full codebook + inbuf = (byte *)malloc(size); + stream->read(inbuf, size); + Screen::decodeFrame4(inbuf, _codeBook, _codeBookSize); + free(inbuf); + break; + case MKTAG('C','B','P','0'): // Partial codebook + _compressedCodeBook = false; + stream->read(_partialCodeBook + _partialCodeBookSize, size); + _partialCodeBookSize += size; + _numPartialCodeBooks++; + break; + case MKTAG('C','B','P','Z'): // Partial codebook + _compressedCodeBook = true; + stream->read(_partialCodeBook + _partialCodeBookSize, size); + _partialCodeBookSize += size; + _numPartialCodeBooks++; + break; + case MKTAG('C','P','L','0'): // Palette + assert(size <= 3 * 256); + stream->read(_palette, size); + break; + case MKTAG('C','P','L','Z'): // Palette + inbuf = (byte *)malloc(size); + stream->read(inbuf, size); + Screen::decodeFrame4(inbuf, _palette, 3 * 256); + free(inbuf); + break; + case MKTAG('V','P','T','0'): // Frame data + assert(size / 2 <= _numVectorPointers); + for (i = 0; i < size / 2; i++) + _vectorPointers[i] = stream->readUint16LE(); + break; + case MKTAG('V','P','T','Z'): // Frame data + inbuf = (byte *)malloc(size); + stream->read(inbuf, size); + size = Screen::decodeFrame4(inbuf, (uint8 *)_vectorPointers, 2 * _numVectorPointers); + for (i = 0; i < size / 2; i++) + _vectorPointers[i] = TO_LE_16(_vectorPointers[i]); + free(inbuf); + break; + default: + warning("VQADecoder::VQAVideoTrack::handleVQFR(): Unknown `VQFR' sub-tag `%s'", tag2str(tag)); + stream->seek(size, SEEK_CUR); + break; + } + } +} + +// ----------------------------------------------------------------------- + +VQAMovie::VQAMovie(KyraEngine_v1 *vm, OSystem *system) { + _system = system; + _vm = vm; + _screen = _vm->screen(); + _decoder = new VQADecoder(); +} + +VQAMovie::~VQAMovie() { + close(); + delete _decoder; +} + +bool VQAMovie::open(const char *filename) { + if (_file.open(filename)) { + return true; + } + return false; +} + +void VQAMovie::close() { + if (_file.isOpen()) { + _file.close(); + } +} + +void VQAMovie::play() { + if (_decoder->loadStream(&_file)) { + Common::EventManager *eventMan = _vm->getEventManager(); + int width = _decoder->getWidth(); + int height = _decoder->getHeight(); + int x = (Screen::SCREEN_W - width) / 2; + int y = (Screen::SCREEN_H - height) / 2; + + _decoder->start(); + + // Note that decoding starts at frame -1. That's because there + // is usually sound data before the first frame, probably to + // avoid sound underflow. + + while (!_decoder->endOfVideo()) { + Common::Event event; + while (eventMan->pollEvent(event)) { + switch (event.type) { + case Common::EVENT_KEYDOWN: + if (event.kbd.keycode == Common::KEYCODE_ESCAPE) + return; + break; + case Common::EVENT_RTL: + case Common::EVENT_QUIT: + return; + default: + break; + } + } + + if (_decoder->needsUpdate()) { + const Graphics::Surface *surface = _decoder->decodeNextFrame(); + if (_decoder->hasDirtyPalette()) { + const byte *decoderPalette = _decoder->getPalette(); + byte systemPalette[256 * 3]; + for (int i = 0; i < ARRAYSIZE(systemPalette); i++) { + systemPalette[i] = (decoderPalette[i] * 0xFF) / 0x3F; + } + _system->getPaletteManager()->setPalette(systemPalette, 0, 256); + } + + _system->copyRectToScreen((const byte *)surface->getBasePtr(0, 0), surface->pitch, x, y, width, height); + } + + _system->updateScreen(); + _system->delayMillis(10); + } + } +} + +} // End of namespace Kyra diff --git a/engines/kyra/graphics/vqa.h b/engines/kyra/graphics/vqa.h new file mode 100644 index 0000000000..df51a81988 --- /dev/null +++ b/engines/kyra/graphics/vqa.h @@ -0,0 +1,161 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef KYRA_VQA_H +#define KYRA_VQA_H + +#include "video/video_decoder.h" +#include "common/file.h" +#include "common/rational.h" + +class OSystem; + +namespace Audio { +class QueuingAudioStream; +} // End of namespace Audio + +namespace Kyra { + +class KyraEngine_v1; +class Screen; + +class VQADecoder : public Video::VideoDecoder { +public: + VQADecoder(); + virtual ~VQADecoder(); + + bool loadStream(Common::SeekableReadStream *stream); + void readNextPacket(); + +private: + Common::SeekableReadStream *_fileStream; + + void handleVQHD(Common::SeekableReadStream *stream); + void handleFINF(Common::SeekableReadStream *stream); + + struct VQAHeader { + uint16 version; + uint16 flags; + uint16 numFrames; + uint16 width; + uint16 height; + uint8 blockW; + uint8 blockH; + uint8 frameRate; + uint8 cbParts; + uint16 colors; + uint16 maxBlocks; + uint32 unk1; + uint16 unk2; + uint16 freq; + uint8 channels; + uint8 bits; + uint32 unk3; + uint16 unk4; + uint32 maxCBFZSize; + uint32 unk5; + }; + + VQAHeader _header; + uint32 *_frameInfo; + + class VQAAudioTrack : public AudioTrack { + public: + VQAAudioTrack(const VQAHeader *header, Audio::Mixer::SoundType soundType); + ~VQAAudioTrack(); + + void handleSND0(Common::SeekableReadStream *stream); + void handleSND1(Common::SeekableReadStream *stream); + void handleSND2(Common::SeekableReadStream *stream); + + protected: + Audio::AudioStream *getAudioStream() const; + + private: + Audio::QueuingAudioStream *_audioStream; + }; + + class VQAVideoTrack : public FixedRateVideoTrack { + public: + VQAVideoTrack(const VQAHeader *header); + ~VQAVideoTrack(); + + uint16 getWidth() const; + uint16 getHeight() const; + Graphics::PixelFormat getPixelFormat() const; + int getCurFrame() const; + int getFrameCount() const; + const Graphics::Surface *decodeNextFrame(); + + void setHasDirtyPalette(); + bool hasDirtyPalette() const; + const byte *getPalette() const; + + void handleVQFR(Common::SeekableReadStream *stream); + + protected: + Common::Rational getFrameRate() const; + + private: + Graphics::Surface *_surface; + byte _palette[3 * 256]; + mutable bool _dirtyPalette; + + bool _newFrame; + + uint16 _width, _height; + uint8 _blockW, _blockH; + uint8 _cbParts; + int _frameCount; + int _curFrame; + byte _frameRate; + + uint32 _codeBookSize; + bool _compressedCodeBook; + byte *_codeBook; + int _partialCodeBookSize; + int _numPartialCodeBooks; + byte *_partialCodeBook; + uint32 _numVectorPointers; + uint16 *_vectorPointers; + }; +}; + +class VQAMovie { +public: + VQAMovie(KyraEngine_v1 *vm, OSystem *system); + ~VQAMovie(); + + bool open(const char *filename); + void close(); + void play(); +private: + OSystem *_system; + KyraEngine_v1 *_vm; + Screen *_screen; + VQADecoder *_decoder; + Common::File _file; +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/graphics/wsamovie.cpp b/engines/kyra/graphics/wsamovie.cpp new file mode 100644 index 0000000000..6cbda7b6c9 --- /dev/null +++ b/engines/kyra/graphics/wsamovie.cpp @@ -0,0 +1,463 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/graphics/wsamovie.h" +#include "kyra/resource/resource.h" + +#include "common/endian.h" + +namespace Kyra { + +WSAMovie_v1::WSAMovie_v1(KyraEngine_v1 *vm) + : Movie(vm), _frameData(0), _frameOffsTable(0), _offscreenBuffer(0), _deltaBuffer(0) { +} + +WSAMovie_v1::~WSAMovie_v1() { + close(); +} + +int WSAMovie_v1::open(const char *filename, int offscreenDecode, Palette *palBuf) { + close(); + + uint32 flags = 0; + uint32 fileSize; + uint8 *p = _vm->resource()->fileData(filename, &fileSize); + if (!p) + return 0; + + 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->gameFlags().useAltShapeHeader) { + flags = READ_LE_UINT16(wsaData); + wsaData += 2; + } + + uint32 offsPal = 0; + if (flags & 1) { + offsPal = 0x300; + _flags |= WF_HAS_PALETTE; + if (palBuf) + _screen->loadPalette(wsaData + 8 + ((_numFrames << 2) & 0xFFFF), *palBuf, 0x300); + } + + if (offscreenDecode) { + _flags |= WF_OFFSCREEN_DECODE; + const int offscreenBufferSize = _width * _height; + _offscreenBuffer = new uint8[offscreenBufferSize]; + memset(_offscreenBuffer, 0, offscreenBufferSize); + } + + if (_numFrames & 0x8000) { + // This is used in the Amiga version. + if (_vm->gameFlags().platform != Common::kPlatformAmiga) + warning("Unhandled wsa flags 0x8000"); + _flags |= WF_FLIPPED; + _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); + if (_frameOffsTable[i]) + _frameOffsTable[i] -= frameDataOffs; + wsaData += 4; + } + + if (!_frameOffsTable[_numFrames + 1]) + _flags |= WF_NO_LAST_FRAME; + + // 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; + + return _numFrames; +} + +void WSAMovie_v1::close() { + if (_opened) { + delete[] _deltaBuffer; + delete[] _offscreenBuffer; + delete[] _frameOffsTable; + delete[] _frameData; + _opened = false; + } +} + +void WSAMovie_v1::displayFrame(int frameNum, int pageNum, int x, int y, uint16 flags, const uint8 *table1, const uint8 *table2) { + if (frameNum >= _numFrames || frameNum < 0 || !_opened) + return; + + _x = x; + _y = y; + _drawPage = pageNum; + + uint8 *dst = 0; + if (_flags & WF_OFFSCREEN_DECODE) + dst = _offscreenBuffer; + else + dst = _screen->getPageRect(_drawPage, _x, _y, _width, _height); + + if (_currentFrame == _numFrames) { + if (!(_flags & WF_NO_FIRST_FRAME)) { + if (_flags & WF_OFFSCREEN_DECODE) + Screen::decodeFrameDelta(dst, _deltaBuffer); + else + Screen::decodeFrameDeltaPage(dst, _deltaBuffer, _width, (_flags & WF_XOR) == 0); + } + _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 && !(_flags & WF_NO_LAST_FRAME)) + frameStep = -1; + else + frameCount = diffCount; + } else { + frameCount = _numFrames - _currentFrame + frameNum; + if (frameCount >= diffCount || (_flags & WF_NO_LAST_FRAME)) { + 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) { + int pageBackUp = _screen->setCurPage(_drawPage); + + int plotFunc = (flags & 0xFF00) >> 12; + int unk1 = flags & 0xFF; + + _screen->copyWsaRect(_x, _y, _width, _height, 0, plotFunc, _offscreenBuffer, unk1, table1, table2); + + _screen->_curPage = pageBackUp; + } +} + +void WSAMovie_v1::processFrame(int frameNum, uint8 *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, false); +} + +#pragma mark - + +WSAMovieAmiga::WSAMovieAmiga(KyraEngine_v1 *vm) : WSAMovie_v1(vm), _buffer(0) {} + +int WSAMovieAmiga::open(const char *filename, int offscreenDecode, Palette *palBuf) { + int res = WSAMovie_v1::open(filename, offscreenDecode, palBuf); + + if (!res) + return 0; + + _buffer = new uint8[_width * _height]; + assert(_buffer); + return res; +} + +void WSAMovieAmiga::close() { + if (_opened) { + delete[] _buffer; + _buffer = 0; + } + WSAMovie_v1::close(); +} + +void WSAMovieAmiga::displayFrame(int frameNum, int pageNum, int x, int y, uint16 flags, const uint8 *table1, const uint8 *table2) { + if (frameNum >= _numFrames || frameNum < 0 || !_opened) + return; + + _x = x; + _y = y; + _drawPage = pageNum; + + uint8 *dst; + dst = _buffer; + memset(_buffer, 0, _width * _height); + + if (_currentFrame == _numFrames) { + if (!(_flags & WF_NO_FIRST_FRAME)) { + Screen::decodeFrameDelta(dst, _deltaBuffer, true); + Screen::convertAmigaGfx(dst, _width, _height, 5, (_flags & WF_FLIPPED) != 0); + + if (_flags & WF_OFFSCREEN_DECODE) { + dst = _offscreenBuffer; + const uint8 *src = _buffer; + int size = _width * _height; + + for (int i = 0; i < size; ++i) + *dst++ ^= *src++; + + dst = _buffer; + } else { + _screen->copyBlockToPage(_drawPage, _x, _y, _width, _height, _buffer); + } + } + _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 && !(_flags & WF_NO_LAST_FRAME)) + frameStep = -1; + else + frameCount = diffCount; + } else { + frameCount = _numFrames - _currentFrame + frameNum; + if (frameCount >= diffCount || (_flags & WF_NO_LAST_FRAME)) { + 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) { + int pageBackUp = _screen->setCurPage(_drawPage); + + int plotFunc = (flags & 0xFF00) >> 12; + int unk1 = flags & 0xFF; + + _screen->copyWsaRect(_x, _y, _width, _height, 0, plotFunc, _offscreenBuffer, unk1, table1, table2); + + _screen->_curPage = pageBackUp; + } +} + +void WSAMovieAmiga::processFrame(int frameNum, uint8 *dst) { + if (!_opened) + return; + assert(frameNum <= _numFrames); + + memset(dst, 0, _width * _height); + + const uint8 *src = _frameData + _frameOffsTable[frameNum]; + Screen::decodeFrame4(src, _deltaBuffer, _deltaBufferSize); + Screen::decodeFrameDelta(dst, _deltaBuffer, true); + Screen::convertAmigaGfx(dst, _width, _height, 5, (_flags & WF_FLIPPED) != 0); + + src = dst; + dst = 0; + int dstPitch = 0; + if (_flags & WF_OFFSCREEN_DECODE) { + dst = _offscreenBuffer; + dstPitch = _width; + } else { + dst = _screen->getPageRect(_drawPage, _x, _y, _width, _height); + dstPitch = Screen::SCREEN_W; + } + + for (int y = 0; y < _height; ++y) { + for (int x = 0; x < _width; ++x) + *dst++ ^= *src++; + dst += dstPitch - _width; + } +} + +#pragma mark - + +WSAMovie_v2::WSAMovie_v2(KyraEngine_v1 *vm) : WSAMovie_v1(vm), _xAdd(0), _yAdd(0) {} + +int WSAMovie_v2::open(const char *filename, int unk1, Palette *palBuf) { + close(); + + uint32 flags = 0; + uint32 fileSize; + uint8 *p = _vm->resource()->fileData(filename, &fileSize); + if (!p) { + warning("couldn't load wsa file: '%s'", filename); + return 0; + } + + const uint8 *wsaData = p; + _numFrames = READ_LE_UINT16(wsaData); wsaData += 2; + _xAdd = (int16)(READ_LE_UINT16(wsaData)); wsaData += 2; + _yAdd = (int16)(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; + flags = READ_LE_UINT16(wsaData); wsaData += 2; + + uint32 offsPal = 0; + if (flags & 1) { + offsPal = 0x300; + _flags |= WF_HAS_PALETTE; + if (palBuf) + _screen->loadPalette(wsaData + 8 + ((_numFrames << 2) & 0xFFFF), *palBuf, 0x300); + } + + if (flags & 2) { + if (_vm->gameFlags().use16ColorMode) { + offsPal = 0x30; + _flags |= WF_HAS_PALETTE; + if (palBuf) + _screen->loadPalette(wsaData + 8 + ((_numFrames << 2) & 0xFFFF), *palBuf, 0x30); + } + + _flags |= WF_XOR; + } + + + if (!(unk1 & 2)) { + _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); + if (_frameOffsTable[i]) + _frameOffsTable[i] -= frameDataOffs; + wsaData += 4; + } + + if (!_frameOffsTable[_numFrames + 1]) + _flags |= WF_NO_LAST_FRAME; + + // 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; + + return _numFrames; +} + +} // End of namespace Kyra diff --git a/engines/kyra/graphics/wsamovie.h b/engines/kyra/graphics/wsamovie.h new file mode 100644 index 0000000000..d00aa89af1 --- /dev/null +++ b/engines/kyra/graphics/wsamovie.h @@ -0,0 +1,134 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef KYRA_WSAMOVIE_H +#define KYRA_WSAMOVIE_H + +#include "kyra/kyra_v1.h" + +namespace Kyra { + +class Palette; + +class Movie { +public: + Movie(KyraEngine_v1 *vm) : _vm(vm), _screen(vm->screen()), _opened(false), _x(-1), _y(-1), _drawPage(-1) {} + virtual ~Movie() {} + + virtual bool opened() { return _opened; } + + virtual int xAdd() const { return 0; } + virtual int yAdd() const { return 0; } + + virtual int width() const = 0; + virtual int height() const = 0; + + virtual int open(const char *filename, int offscreen, Palette *palette) = 0; + virtual void close() = 0; + + virtual int frames() = 0; + + virtual void displayFrame(int frameNum, int pageNum, int x, int y, uint16 flags, const uint8 *table1, const uint8 *table2) = 0; + +protected: + KyraEngine_v1 *_vm; + Screen *_screen; + bool _opened; + + int _x, _y; + int _drawPage; +}; + +class WSAMovie_v1 : public Movie { +public: + WSAMovie_v1(KyraEngine_v1 *vm); + virtual ~WSAMovie_v1(); + + int width() const { return _width; } + int height() const { return _height; } + + virtual int open(const char *filename, int offscreen, Palette *palette); + virtual void close(); + + virtual int frames() { return _opened ? _numFrames : -1; } + + virtual void displayFrame(int frameNum, int pageNum, int x, int y, uint16 flags, const uint8 *table1, const uint8 *table2); + + enum WSAFlags { + WF_OFFSCREEN_DECODE = 0x10, + WF_NO_LAST_FRAME = 0x20, + WF_NO_FIRST_FRAME = 0x40, + WF_FLIPPED = 0x80, + WF_HAS_PALETTE = 0x100, + WF_XOR = 0x200 + }; + +protected: + virtual void processFrame(int frameNum, uint8 *dst); + + uint16 _currentFrame; + uint16 _numFrames; + uint16 _width; + uint16 _height; + uint16 _flags; + uint8 *_deltaBuffer; + uint32 _deltaBufferSize; + uint8 *_offscreenBuffer; + uint32 *_frameOffsTable; + uint8 *_frameData; +}; + +class WSAMovieAmiga : public WSAMovie_v1 { +public: + WSAMovieAmiga(KyraEngine_v1 *vm); + int open(const char *filename, int offscreen, Palette *palette); + void close(); + + void displayFrame(int frameNum, int pageNum, int x, int y, uint16 flags, const uint8 *table1, const uint8 *table2); +private: + void processFrame(int frameNum, uint8 *dst); + + uint8 *_buffer; +}; + +class WSAMovie_v2 : public WSAMovie_v1 { +public: + WSAMovie_v2(KyraEngine_v1 *vm); + + int open(const char *filename, int unk1, Palette *palette); + virtual void displayFrame(int frameNum, int pageNum, int x, int y, uint16 flags, const uint8 *table1, const uint8 *table2) { + WSAMovie_v1::displayFrame(frameNum, pageNum, x + _xAdd, y + _yAdd, flags, table1, table2); + } + + int xAdd() const { return _xAdd; } + int yAdd() const { return _yAdd; } + + void setWidth(int w) { _width = w; } + void setHeight(int h) { _height = h; } +protected: + int16 _xAdd; + int16 _yAdd; +}; + +} // End of namespace Kyra + +#endif |