/* 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 "scumm/scumm.h" #include "scumm/actor.h" #include "scumm/costume.h" #include "scumm/sound.h" #include "scumm/util.h" namespace Scumm { const byte smallCostumeScaleTable[256] = { 0xFF, 0xFD, 0x7D, 0xBD, 0x3D, 0xDD, 0x5D, 0x9D, 0x1D, 0xED, 0x6D, 0xAD, 0x2D, 0xCD, 0x4D, 0x8D, 0x0D, 0xF5, 0x75, 0xB5, 0x35, 0xD5, 0x55, 0x95, 0x15, 0xE5, 0x65, 0xA5, 0x25, 0xC5, 0x45, 0x85, 0x05, 0xF9, 0x79, 0xB9, 0x39, 0xD9, 0x59, 0x99, 0x19, 0xE9, 0x69, 0xA9, 0x29, 0xC9, 0x49, 0x89, 0x09, 0xF1, 0x71, 0xB1, 0x31, 0xD1, 0x51, 0x91, 0x11, 0xE1, 0x61, 0xA1, 0x21, 0xC1, 0x41, 0x81, 0x01, 0xFB, 0x7B, 0xBB, 0x3B, 0xDB, 0x5B, 0x9B, 0x1B, 0xEB, 0x6B, 0xAB, 0x2B, 0xCB, 0x4B, 0x8B, 0x0B, 0xF3, 0x73, 0xB3, 0x33, 0xD3, 0x53, 0x93, 0x13, 0xE3, 0x63, 0xA3, 0x23, 0xC3, 0x43, 0x83, 0x03, 0xF7, 0x77, 0xB7, 0x37, 0xD7, 0x57, 0x97, 0x17, 0xE7, 0x67, 0xA7, 0x27, 0xC7, 0x47, 0x87, 0x07, 0xEF, 0x6F, 0xAF, 0x2F, 0xCF, 0x4F, 0x8F, 0x0F, 0xDF, 0x5F, 0x9F, 0x1F, 0xBF, 0x3F, 0x7F, 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0, 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8, 0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4, 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC, 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2, 0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA, 0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6, 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE }; static const int v1MMNESLookup[25] = { 0x00, 0x03, 0x01, 0x06, 0x08, 0x02, 0x00, 0x07, 0x0C, 0x04, 0x09, 0x0A, 0x12, 0x0B, 0x14, 0x0D, 0x11, 0x0F, 0x0E, 0x10, 0x17, 0x00, 0x01, 0x05, 0x16 }; static const byte v0ActorTalkArray[0x19] = { 0x00, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x00, 0x46, 0x06, 0x06, 0x06, 0x06, 0xFF, 0xFF, 0x06, 0xC0, 0x06, 0x06, 0x00, 0xC0, 0xC0, 0x00, 0x06, 0x06 }; byte ClassicCostumeRenderer::mainRoutine(int xmoveCur, int ymoveCur) { int i, skip = 0; byte drawFlag = 1; bool use_scaling; byte startScaleIndexX; int ex1, ex2; Common::Rect rect; int step; Codec1 v1; const int scaletableSize = 128; const bool newAmiCost = (_vm->_game.version == 5) && (_vm->_game.platform == Common::kPlatformAmiga); const bool pcEngCost = (_vm->_game.id == GID_LOOM && _vm->_game.platform == Common::kPlatformPCEngine); v1.scaletable = smallCostumeScaleTable; if (_loaded._numColors == 32) { v1.mask = 7; v1.shr = 3; } else { v1.mask = 15; v1.shr = 4; } switch (_loaded._format) { case 0x60: case 0x61: // This format is used e.g. in the Sam&Max intro ex1 = _srcptr[0]; ex2 = _srcptr[1]; _srcptr += 2; if (ex1 != 0xFF || ex2 != 0xFF) { ex1 = READ_LE_UINT16(_loaded._frameOffsets + ex1 * 2); _srcptr = _loaded._baseptr + READ_LE_UINT16(_loaded._baseptr + ex1 + ex2 * 2) + 14; } } use_scaling = (_scaleX != 0xFF) || (_scaleY != 0xFF); v1.x = _actorX; v1.y = _actorY; if (use_scaling) { /* Scale direction */ v1.scaleXstep = -1; if (xmoveCur < 0) { xmoveCur = -xmoveCur; v1.scaleXstep = 1; } // It's possible that the scale indexes will overflow and wrap // around to zero, so it's important that we use the same // method of accessing it both when calculating the size of the // scaled costume, and when drawing it. See bug #1519667. if (_mirror) { /* Adjust X position */ startScaleIndexX = _scaleIndexX = scaletableSize - xmoveCur; for (i = 0; i < xmoveCur; i++) { if (v1.scaletable[_scaleIndexX++] < _scaleX) v1.x -= v1.scaleXstep; } rect.left = rect.right = v1.x; _scaleIndexX = startScaleIndexX; for (i = 0; i < _width; i++) { if (rect.right < 0) { skip++; startScaleIndexX = _scaleIndexX; } if (v1.scaletable[_scaleIndexX++] < _scaleX) rect.right++; } } else { /* No mirror */ /* Adjust X position */ startScaleIndexX = _scaleIndexX = xmoveCur + scaletableSize; for (i = 0; i < xmoveCur; i++) { if (v1.scaletable[_scaleIndexX--] < _scaleX) v1.x += v1.scaleXstep; } rect.left = rect.right = v1.x; _scaleIndexX = startScaleIndexX; for (i = 0; i < _width; i++) { if (rect.left >= _out.w) { startScaleIndexX = _scaleIndexX; skip++; } if (v1.scaletable[_scaleIndexX--] < _scaleX) rect.left--; } } _scaleIndexX = startScaleIndexX; if (skip) skip--; step = -1; if (ymoveCur < 0) { ymoveCur = -ymoveCur; step = 1; } _scaleIndexY = scaletableSize - ymoveCur; for (i = 0; i < ymoveCur; i++) { if (v1.scaletable[_scaleIndexY++] < _scaleY) v1.y -= step; } rect.top = rect.bottom = v1.y; _scaleIndexY = scaletableSize - ymoveCur; for (i = 0; i < _height; i++) { if (v1.scaletable[_scaleIndexY++] < _scaleY) rect.bottom++; } _scaleIndexY = scaletableSize - ymoveCur; } else { if (!_mirror) xmoveCur = -xmoveCur; v1.x += xmoveCur; v1.y += ymoveCur; if (_mirror) { rect.left = v1.x; rect.right = v1.x + _width; } else { rect.left = v1.x - _width; rect.right = v1.x; } rect.top = v1.y; rect.bottom = rect.top + _height; } v1.skip_width = _width; v1.scaleXstep = _mirror ? 1 : -1; if (_vm->_game.version == 1) // V1 games uses 8 x 8 pixels for actors _vm->markRectAsDirty(kMainVirtScreen, rect.left, rect.right + 8, rect.top, rect.bottom, _actorID); else _vm->markRectAsDirty(kMainVirtScreen, rect.left, rect.right + 1, rect.top, rect.bottom, _actorID); if (rect.top >= _out.h || rect.bottom <= 0) return 0; if (rect.left >= _out.w || rect.right <= 0) return 0; v1.replen = 0; if (_mirror) { if (!use_scaling) skip = -v1.x; if (skip > 0) { if (!newAmiCost && !pcEngCost && _loaded._format != 0x57) { v1.skip_width -= skip; codec1_ignorePakCols(v1, skip); v1.x = 0; } } else { skip = rect.right - _out.w; if (skip <= 0) { drawFlag = 2; } else { v1.skip_width -= skip; } } } else { if (!use_scaling) skip = rect.right - _out.w; if (skip > 0) { if (!newAmiCost && !pcEngCost && _loaded._format != 0x57) { v1.skip_width -= skip; codec1_ignorePakCols(v1, skip); v1.x = _out.w - 1; } } else { // V1 games uses 8 x 8 pixels for actors if (_loaded._format == 0x57) skip = -8 - rect.left; else skip = -1 - rect.left; if (skip <= 0) drawFlag = 2; else v1.skip_width -= skip; } } if (v1.skip_width <= 0) return 0; if (rect.left < 0) rect.left = 0; if (rect.top < 0) rect.top = 0; if (rect.top > _out.h) rect.top = _out.h; if (rect.bottom > _out.h) rect.bottom = _out.h; if (_draw_top > rect.top) _draw_top = rect.top; if (_draw_bottom < rect.bottom) _draw_bottom = rect.bottom; if (_height + rect.top >= 256) { return 2; } v1.destptr = (byte *)_out.pixels + v1.y * _out.pitch + v1.x * _vm->_bytesPerPixel; v1.mask_ptr = _vm->getMaskBuffer(0, v1.y, _zbuf); if (_loaded._format == 0x57) { // The v1 costume renderer needs the actor number, which is // the same thing as the costume renderer's _actorID. procC64(v1, _actorID); } else if (newAmiCost) proc3_ami(v1); else if (pcEngCost) procPCEngine(v1); else proc3(v1); return drawFlag; } static const int v1MMActorPalatte1[25] = { 8, 8, 8, 8, 4, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 }; static const int v1MMActorPalatte2[25] = { 0, 7, 2, 6, 9, 1, 3, 7, 7, 1, 1, 9, 1, 4, 5, 5, 4, 1, 0, 5, 4, 2, 2, 7, 7 }; #define MASK_AT(xoff) \ (mask && (mask[((v1.x + xoff) / 8)] & revBitMask((v1.x + xoff) & 7))) #define LINE(c,p) \ pcolor = (color >> c) & 3; \ if (pcolor) { \ if (!MASK_AT(p)) \ dst[p] = palette[pcolor]; \ if (!MASK_AT(p + 1)) \ dst[p + 1] = palette[pcolor]; \ } void ClassicCostumeRenderer::procC64(Codec1 &v1, int actor) { const byte *mask, *src; byte *dst; byte len; int y; uint height; byte color, pcolor; bool rep; y = v1.y; src = _srcptr; dst = v1.destptr; len = v1.replen; color = v1.repcolor; height = _height; v1.skip_width /= 8; // Set up the palette data byte palette[4] = { 0, 0, 0, 0 }; if (_vm->getCurrentLights() & LIGHTMODE_actor_use_colors) { if (_vm->_game.id == GID_MANIAC) { palette[1] = v1MMActorPalatte1[actor]; palette[2] = v1MMActorPalatte2[actor]; } else { // Adjust for C64 version of Zak McKracken palette[1] = (_vm->_game.platform == Common::kPlatformC64) ? 10 : 8; palette[2] = _palette[actor]; } } else { palette[2] = 11; palette[3] = 11; } mask = v1.mask_ptr; if (len) goto StartPos; do { len = *src++; if (len & 0x80) color = *src++; StartPos:; rep = (len & 0x80) != 0; len &= 0x7f; while (len--) { if (!rep) color = *src++; if (0 <= y && y < _out.h && 0 <= v1.x && v1.x < _out.w) { if (!_mirror) { LINE(0, 0); LINE(2, 2); LINE(4, 4); LINE(6, 6); } else { LINE(6, 0); LINE(4, 2); LINE(2, 4); LINE(0, 6); } } dst += _out.pitch; y++; mask += _numStrips; if (!--height) { if (!--v1.skip_width) return; height = _height; y = v1.y; v1.x += 8 * v1.scaleXstep; if (v1.x < 0 || v1.x >= _out.w) return; mask = v1.mask_ptr; v1.destptr += 8 * v1.scaleXstep; dst = v1.destptr; } } } while (1); } #undef LINE #undef MASK_AT #ifdef USE_ARM_COSTUME_ASM #ifndef IPHONE #define ClassicProc3RendererShadowARM _ClassicProc3RendererShadowARM #endif extern "C" int ClassicProc3RendererShadowARM(int _scaleY, ClassicCostumeRenderer::Codec1 *v1, Graphics::Surface *_out, const byte *src, int height, int _scaleX, int _scaleIndexX, byte *_shadow_table, uint16 _palette[32], int32 _numStrips, int _scaleIndexY); #endif void ClassicCostumeRenderer::proc3(Codec1 &v1) { const byte *mask, *src; byte *dst; byte len, maskbit; int y; uint color, height, pcolor; byte scaleIndexY; bool masked; #ifdef USE_ARM_COSTUME_ASM if (((_shadow_mode & 0x20) == 0) && (v1.mask_ptr != NULL) && (_shadow_table != NULL)) { _scaleIndexX = ClassicProc3RendererShadowARM(_scaleY, &v1, &_out, _srcptr, _height, _scaleX, _scaleIndexX, _shadow_table, _palette, _numStrips, _scaleIndexY); return; } #endif /* USE_ARM_COSTUME_ASM */ y = v1.y; src = _srcptr; dst = v1.destptr; len = v1.replen; color = v1.repcolor; height = _height; scaleIndexY = _scaleIndexY; maskbit = revBitMask(v1.x & 7); mask = v1.mask_ptr + v1.x / 8; if (len) goto StartPos; do { len = *src++; color = len >> v1.shr; len &= v1.mask; if (!len) len = *src++; do { if (_scaleY == 255 || v1.scaletable[scaleIndexY++] < _scaleY) { masked = (y < 0 || y >= _out.h) || (v1.x < 0 || v1.x >= _out.w) || (v1.mask_ptr && (mask[0] & maskbit)); if (color && !masked) { if (_shadow_mode & 0x20) { pcolor = _shadow_table[*dst]; } else { pcolor = _palette[color]; if (pcolor == 13 && _shadow_table) pcolor = _shadow_table[*dst]; } *dst = pcolor; } dst += _out.pitch; mask += _numStrips; y++; } if (!--height) { if (!--v1.skip_width) return; height = _height; y = v1.y; scaleIndexY = _scaleIndexY; if (_scaleX == 255 || v1.scaletable[_scaleIndexX] < _scaleX) { v1.x += v1.scaleXstep; if (v1.x < 0 || v1.x >= _out.w) return; maskbit = revBitMask(v1.x & 7); v1.destptr += v1.scaleXstep; } _scaleIndexX += v1.scaleXstep; dst = v1.destptr; mask = v1.mask_ptr + v1.x / 8; } StartPos:; } while (--len); } while (1); } void ClassicCostumeRenderer::proc3_ami(Codec1 &v1) { const byte *mask, *src; byte *dst; byte maskbit, len, height, width; int color; int y; bool masked; int oldXpos, oldScaleIndexX; mask = v1.mask_ptr + v1.x / 8; dst = v1.destptr; height = _height; width = _width; src = _srcptr; maskbit = revBitMask(v1.x & 7); y = v1.y; oldXpos = v1.x; oldScaleIndexX = _scaleIndexX; // Indy4 Amiga always uses the room map to match colors to the currently // setup palette in the actor code in the original, thus we need to do this // mapping over here too. byte *amigaMap = 0; if (_vm->_game.platform == Common::kPlatformAmiga && _vm->_game.id == GID_INDY4) amigaMap = _vm->_roomPalette; do { len = *src++; color = len >> v1.shr; len &= v1.mask; if (!len) len = *src++; do { if (_scaleY == 255 || v1.scaletable[_scaleIndexY] < _scaleY) { masked = (y < 0 || y >= _out.h) || (v1.x < 0 || v1.x >= _out.w) || (v1.mask_ptr && (mask[0] & maskbit)); if (color && !masked) { if (amigaMap) *dst = amigaMap[_palette[color]]; else *dst = _palette[color]; } if (_scaleX == 255 || v1.scaletable[_scaleIndexX] < _scaleX) { v1.x += v1.scaleXstep; dst += v1.scaleXstep; maskbit = revBitMask(v1.x & 7); } _scaleIndexX += v1.scaleXstep; mask = v1.mask_ptr + v1.x / 8; } if (!--width) { if (!--height) return; if (y >= _out.h) return; if (v1.x != oldXpos) { dst += _out.pitch - (v1.x - oldXpos); v1.mask_ptr += _numStrips; mask = v1.mask_ptr + oldXpos / 8; maskbit = revBitMask(oldXpos & 7); y++; } width = _width; v1.x = oldXpos; _scaleIndexX = oldScaleIndexX; _scaleIndexY++; } } while (--len); } while (1); } void PCESetCostumeData(byte block[16][16], int index, byte value) { int row = (index % 16); int plane = (index / 16) % 4; int colOffset = (index < 64) ? 8 : 0; for (int i = 0; i < 8; ++i) { int bit = (value >> (7-i)) & 0x1; block[row][i + colOffset] |= bit << plane; } } void ClassicCostumeRenderer::procPCEngine(Codec1 &v1) { const byte *mask, *src; byte *dst; byte maskbit; int xPos, yPos; uint pcolor, width, height; bool masked; int vertShift; int xStep; byte block[16][16]; src = _srcptr; width = _width / 16; height = _height / 16; if (_numBlocks == 0) return; xStep = _mirror ? +1 : -1; for (uint x = 0; x < width; ++x) { yPos = 0; for (uint y = 0; y < height; ++y) { vertShift = *src++; if (vertShift == 0xFF) { yPos += 16; continue; } else { yPos += vertShift; } memset(block, 0, sizeof(block)); int index = 0; while (index < 128) { byte cmd = *src++; int cnt = (cmd & 0x3F) + 1; if (!(cmd & 0xC0)) { for (int i = 0; i < cnt; ++i) PCESetCostumeData(block, index++, 0); } else if (cmd & 0x80) { int value = *src++; for (int i = 0; i < cnt; ++i) PCESetCostumeData(block, index++, value); } else { for (int i = 0; i < cnt; ++i) PCESetCostumeData(block, index++, *src++); } } if (index != 128) { warning("ClassicCostumeRenderer::procPCEngine: index %d != 128\n", index); } for (int row = 0; row < 16; ++row) { xPos = xStep * x * 16; for (int col = 0; col < 16; ++col) { dst = v1.destptr + yPos * _out.pitch + xPos * _vm->_bytesPerPixel; mask = v1.mask_ptr + yPos * _numStrips + (v1.x + xPos) / 8; maskbit = revBitMask((v1.x + xPos) % 8); pcolor = block[row][col]; masked = (v1.y + yPos < 0 || v1.y + yPos >= _out.h) || (v1.x + xPos < 0 || v1.x + xPos >= _out.w) || (v1.mask_ptr && (mask[0] & maskbit)); if (pcolor && !masked) { WRITE_UINT16(dst, ((uint16*)_palette)[pcolor]); } xPos += xStep; } yPos++; } } } } void ClassicCostumeLoader::loadCostume(int id) { _id = id; byte *ptr = _vm->getResourceAddress(rtCostume, id); if (_vm->_game.version >= 6) ptr += 8; else if (_vm->_game.features & GF_OLD_BUNDLE) ptr += -2; else if (_vm->_game.features & GF_SMALL_HEADER) ptr += 0; else ptr += 2; _baseptr = ptr; _numAnim = ptr[6]; _format = ptr[7] & 0x7F; _mirror = (ptr[7] & 0x80) != 0; _palette = ptr + 8; if (_vm->_game.id == GID_LOOM && _vm->_game.platform == Common::kPlatformPCEngine) { _numColors = 16; ptr += 8 + 17; _animCmds = READ_LE_UINT16(ptr) + ptr + 2; _frameOffsets = ptr + 2; _dataOffsets = ptr + 34; return; } switch (_format) { case 0x57: // Only used in V1 games _numColors = 0; break; case 0x58: _numColors = 16; break; case 0x59: _numColors = 32; break; case 0x60: // New since version 6 _numColors = 16; break; case 0x61: // New since version 6 _numColors = 32; break; default: error("Costume %d with format 0x%X is invalid", id, _format); } // In GF_OLD_BUNDLE games, there is no actual palette, just a single color byte. // Don't forget, these games were designed around a fixed 16 color HW palette :-) // In addition, all offsets are shifted by 2; we accomodate that via a separate // _baseptr value (instead of adding tons of if's throughout the code). if (_vm->_game.features & GF_OLD_BUNDLE) { _numColors = (_format == 0x57) ? 0 : 1; _baseptr += 2; } ptr += 8 + _numColors; _frameOffsets = ptr + 2; if (_format == 0x57) { _dataOffsets = ptr + 18; _baseptr += 4; } else { _dataOffsets = ptr + 34; } _animCmds = _baseptr + READ_LE_UINT16(ptr); } byte NESCostumeRenderer::drawLimb(const Actor *a, int limb) { const byte darkpalette[16] = {0x00,0x00,0x2D,0x3D,0x00,0x00,0x2D,0x3D,0x00,0x00,0x2D,0x3D,0x00,0x00,0x2D,0x3D}; const CostumeData &cost = a->_cost; const byte *palette, *src, *sprdata; int anim, frameNum, frame, offset, numSprites; // If the specified limb is stopped or not existing, do nothing. if (cost.curpos[limb] == 0xFFFF) return 0; if (_vm->getCurrentLights() & LIGHTMODE_actor_use_base_palette) palette = _vm->_NESPalette[1]; else palette = darkpalette; src = _loaded._dataOffsets; anim = 4 * cost.frame[limb] + newDirToOldDir(a->getFacing()); frameNum = cost.curpos[limb]; frame = src[src[2 * anim] + frameNum]; offset = READ_LE_UINT16(_vm->_NEScostdesc + v1MMNESLookup[_loaded._id] * 2); numSprites = _vm->_NEScostlens[offset + frame] + 1; sprdata = _vm->_NEScostdata + READ_LE_UINT16(_vm->_NEScostoffs + 2 * (offset + frame)) + numSprites * 3; bool flipped = (newDirToOldDir(a->getFacing()) == 1); int left = 239, right = 0, top = 239, bottom = 0; byte *maskBuf = _vm->getMaskBuffer(0, 0, 1); for (int spr = 0; spr < numSprites; spr++) { byte mask, tile, sprpal; int8 y, x; sprdata -= 3; mask = (sprdata[0] & 0x80) ? 0x01 : 0x80; y = sprdata[0] << 1; y >>= 1; tile = sprdata[1]; sprpal = (sprdata[2] & 0x03) << 2; x = sprdata[2]; x >>= 2; if (flipped) { mask = (mask == 0x80) ? 0x01 : 0x80; x = -x; } left = MIN(left, _actorX + x); right = MAX(right, _actorX + x + 8); top = MIN(top, _actorY + y); bottom = MAX(bottom, _actorY + y + 8); if ((_actorX + x < 0) || (_actorX + x + 8 >= _out.w)) continue; if ((_actorY + y < 0) || (_actorY + y + 8 >= _out.h)) continue; for (int ty = 0; ty < 8; ty++) { byte c1 = _vm->_NESPatTable[0][tile * 16 + ty]; byte c2 = _vm->_NESPatTable[0][tile * 16 + ty + 8]; for (int tx = 0; tx < 8; tx++) { unsigned char c = ((c1 & mask) ? 1 : 0) | ((c2 & mask) ? 2 : 0) | sprpal; if (mask == 0x01) { c1 >>= 1; c2 >>= 1; } else { c1 <<= 1; c2 <<= 1; } if (!(c & 3)) continue; int my = _actorY + y + ty; int mx = _actorX + x + tx; if (!(_zbuf && (maskBuf[my * _numStrips + mx / 8] & revBitMask(mx & 7)))) *((byte *)_out.pixels + my * _out.pitch + mx) = palette[c]; } } } _draw_top = top; _draw_bottom = bottom; _vm->markRectAsDirty(kMainVirtScreen, left, right, top, bottom, _actorID); return 0; } #define PCE_SIGNED(a) (((a) & 0x80) ? -((a) & 0x7F) : (a)) byte ClassicCostumeRenderer::drawLimb(const Actor *a, int limb) { int i; int code; const byte *baseptr, *frameptr; const CostumeData &cost = a->_cost; // If the specified limb is stopped or not existing, do nothing. if (cost.curpos[limb] == 0xFFFF || cost.stopped & (1 << limb)) return 0; // Determine the position the limb is at i = cost.curpos[limb] & 0x7FFF; baseptr = _loaded._baseptr; // Get the frame pointer for that limb if (_vm->_game.id == GID_LOOM && _vm->_game.platform == Common::kPlatformPCEngine) baseptr = _loaded._frameOffsets + limb * 2 + 2; frameptr = baseptr + READ_LE_UINT16(_loaded._frameOffsets + limb * 2); // Determine the offset to the costume data for the limb at position i code = _loaded._animCmds[i] & 0x7F; // Code 0x7B indicates a limb for which there is nothing to draw if (code != 0x7B) { if (_vm->_game.id == GID_LOOM && _vm->_game.platform == Common::kPlatformPCEngine) baseptr = frameptr + code * 2 + 2; _srcptr = baseptr + READ_LE_UINT16(frameptr + code * 2); if (!(_vm->_game.features & GF_OLD256) || code < 0x79) { const CostumeInfo *costumeInfo; int xmoveCur, ymoveCur; if (_vm->_game.id == GID_LOOM && _vm->_game.platform == Common::kPlatformPCEngine) { _numBlocks = _srcptr[0]; _width = _srcptr[1] * 16; _height = _srcptr[2] * 16; xmoveCur = _xmove + PCE_SIGNED(_srcptr[3]); ymoveCur = _ymove + PCE_SIGNED(_srcptr[4]); _xmove += PCE_SIGNED(_srcptr[5]); _ymove -= PCE_SIGNED(_srcptr[6]); _srcptr += 7; } else if (_loaded._format == 0x57) { _width = _srcptr[0] * 8; _height = _srcptr[1]; xmoveCur = _xmove + (int8)_srcptr[2] * 8; ymoveCur = _ymove - (int8)_srcptr[3]; _xmove += (int8)_srcptr[4] * 8; _ymove -= (int8)_srcptr[5]; _srcptr += 6; } else { costumeInfo = (const CostumeInfo *)_srcptr; _width = READ_LE_UINT16(&costumeInfo->width); _height = READ_LE_UINT16(&costumeInfo->height); xmoveCur = _xmove + (int16)READ_LE_UINT16(&costumeInfo->rel_x); ymoveCur = _ymove + (int16)READ_LE_UINT16(&costumeInfo->rel_y); _xmove += (int16)READ_LE_UINT16(&costumeInfo->move_x); _ymove -= (int16)READ_LE_UINT16(&costumeInfo->move_y); _srcptr += 12; } return mainRoutine(xmoveCur, ymoveCur); } } return 0; } void NESCostumeRenderer::setPalette(uint16 *palette) { // TODO } void NESCostumeRenderer::setFacing(const Actor *a) { // TODO //_mirror = newDirToOldDir(a->getFacing()) != 0 || _loaded._mirror; } void NESCostumeRenderer::setCostume(int costume, int shadow) { _loaded.loadCostume(costume); } #ifdef USE_RGB_COLOR void PCEngineCostumeRenderer::setPalette(uint16 *palette) { const byte* ptr = _loaded._palette; byte rgb[45]; byte *rgbPtr = rgb; _vm->readPCEPalette(&ptr, &rgbPtr, 15); _palette[0] = 0; for (int i = 0; i < 15; ++i) _palette[i + 1] = _vm->get16BitColor(rgb[i * 3 + 0], rgb[i * 3 + 1], rgb[i * 3 + 2]); } #endif void ClassicCostumeLoader::costumeDecodeData(Actor *a, int frame, uint usemask) { const byte *r; const byte *baseptr; uint mask, j; int i; byte extra, cmd; int anim; loadCostume(a->_costume); anim = newDirToOldDir(a->getFacing()) + frame * 4; if (anim > _numAnim) { return; } if (_vm->_game.id == GID_LOOM && _vm->_game.platform == Common::kPlatformPCEngine) baseptr = _dataOffsets + anim * 2 + 2; else baseptr = _baseptr; r = baseptr + READ_LE_UINT16(_dataOffsets + anim * 2); if (r == baseptr) { return; } if (_vm->_game.version == 1) { mask = *r++ << 8; } else { mask = READ_LE_UINT16(r); r += 2; } i = 0; do { if (mask & 0x8000) { if ((_vm->_game.version <= 3) && !(_vm->_game.id == GID_LOOM && _vm->_game.platform == Common::kPlatformPCEngine)) { j = *r++; if (j == 0xFF) j = 0xFFFF; } else { j = READ_LE_UINT16(r); r += 2; } if (usemask & 0x8000) { if (j == 0xFFFF) { a->_cost.curpos[i] = 0xFFFF; a->_cost.start[i] = 0; a->_cost.frame[i] = frame; } else { extra = *r++; cmd = _animCmds[j]; if (cmd == 0x7A) { a->_cost.stopped &= ~(1 << i); } else if (cmd == 0x79) { a->_cost.stopped |= (1 << i); } else { a->_cost.curpos[i] = a->_cost.start[i] = j; a->_cost.end[i] = j + (extra & 0x7F); if (extra & 0x80) a->_cost.curpos[i] |= 0x8000; a->_cost.frame[i] = frame; } } } else { if (j != 0xFFFF) r++; } } i++; usemask <<= 1; mask <<= 1; } while (mask&0xFFFF); } void ClassicCostumeRenderer::setPalette(uint16 *palette) { int i; byte color; if (_loaded._format == 0x57) { for (i = 0; i < 13; i++) _palette[i] = palette[i]; } else if (_vm->_game.features & GF_OLD_BUNDLE) { if (_vm->getCurrentLights() & LIGHTMODE_actor_use_colors) { for (i = 0; i < 16; i++) _palette[i] = palette[i]; } else { for (i = 0; i < 16; i++) _palette[i] = 8; _palette[12] = 0; } _palette[_loaded._palette[0]] = _palette[0]; } else { if (_vm->getCurrentLights() & LIGHTMODE_actor_use_colors) { for (i = 0; i < _loaded._numColors; i++) { color = palette[i]; if (color == 255) color = _loaded._palette[i]; _palette[i] = color; } } else { memset(_palette, 8, _loaded._numColors); _palette[12] = 0; } } } void ClassicCostumeRenderer::setFacing(const Actor *a) { _mirror = newDirToOldDir(a->getFacing()) != 0 || _loaded._mirror; } void ClassicCostumeRenderer::setCostume(int costume, int shadow) { _loaded.loadCostume(costume); } byte ClassicCostumeLoader::increaseAnims(Actor *a) { int i; byte r = 0; for (i = 0; i != 16; i++) { if (a->_cost.curpos[i] != 0xFFFF) r += increaseAnim(a, i); } return r; } byte ClassicCostumeLoader::increaseAnim(Actor *a, int slot) { int highflag; int i, end; byte code, nc; if (a->_cost.curpos[slot] == 0xFFFF) return 0; highflag = a->_cost.curpos[slot] & 0x8000; i = a->_cost.curpos[slot] & 0x7FFF; end = a->_cost.end[slot]; code = _animCmds[i] & 0x7F; if (_vm->_game.version <= 3) { if (_animCmds[i] & 0x80) a->_cost.soundCounter++; } do { if (!highflag) { if (i++ >= end) i = a->_cost.start[slot]; } else { if (i != end) i++; } nc = _animCmds[i]; if (nc == 0x7C) { a->_cost.animCounter++; if (a->_cost.start[slot] != end) continue; } else { if (_vm->_game.version >= 6) { if (nc >= 0x71 && nc <= 0x78) { uint sound = (_vm->_game.heversion == 60) ? 0x78 - nc : nc - 0x71; _vm->_sound->addSoundToQueue2(a->_sound[sound]); if (a->_cost.start[slot] != end) continue; } } else { if (nc == 0x78) { a->_cost.soundCounter++; if (a->_cost.start[slot] != end) continue; } } } a->_cost.curpos[slot] = i | highflag; return (_animCmds[i] & 0x7F) != code; } while (1); } /** * costume ID -> v1MMNESLookup[] -> desc -> lens & offs -> data -> Gfx & pal */ void NESCostumeLoader::loadCostume(int id) { _id = id; _baseptr = _vm->getResourceAddress(rtCostume, id); _dataOffsets = _baseptr + 2; _numAnim = 0x17; } void NESCostumeLoader::costumeDecodeData(Actor *a, int frame, uint usemask) { int anim; loadCostume(a->_costume); anim = 4 * frame + newDirToOldDir(a->getFacing()); if (anim > _numAnim) { return; } a->_cost.curpos[0] = 0; a->_cost.start[0] = 0; a->_cost.end[0] = _dataOffsets[2 * anim + 1]; a->_cost.frame[0] = frame; } byte NESCostumeLoader::increaseAnims(Actor *a) { int i; byte r = 0; for (i = 0; i != 16; i++) { if (a->_cost.curpos[i] != 0xFFFF) r += increaseAnim(a, i); } return r; } byte NESCostumeLoader::increaseAnim(Actor *a, int slot) { int oldframe = a->_cost.curpos[slot]++; if (a->_cost.curpos[slot] >= a->_cost.end[slot]) a->_cost.curpos[slot] = a->_cost.start[slot]; return (a->_cost.curpos[slot] != oldframe); } static const byte actorColorsMMC64[25] = { 0, 7, 2, 6, 9, 1, 3, 7, 7, 1, 1, 9, 1, 4, 5, 5, 4, 1, 0, 5, 4, 2, 2, 7, 7 }; #define MASK_AT(xoff) \ (mask && (mask[((destX + xoff) / 8)] & revBitMask((destX + xoff) & 7))) #define LINE(c,p) \ pcolor = (color >> c) & 3; \ if (pcolor) { \ if (!MASK_AT(p)) \ dst[p] = palette[pcolor]; \ if (!MASK_AT(p + 1)) \ dst[p + 1] = palette[pcolor]; \ } byte C64CostumeRenderer::drawLimb(const Actor *a, int limb) { if (limb >= 8) return 0; if (a->_cost.start[limb] == 0xFFFF) return 0; if (limb == 0) { _draw_top = 200; _draw_bottom = 0; } bool flipped = (a->_cost.start[limb] & 0x80) != 0; byte frameStart = _loaded._frameOffsets[a->_cost.frame[limb]]; byte frame = _loaded._frameOffsets[frameStart + a->_cost.curpos[limb]]; if (frame == 0xFF) return 0; byte ptrLow = _loaded._baseptr[frame]; byte ptrHigh = ptrLow + _loaded._dataOffsets[4]; int frameOffset = (_loaded._baseptr[ptrHigh] << 8) + _loaded._baseptr[ptrLow + 2]; // 0x23EF / 0x2400 const byte *data = _loaded._baseptr + frameOffset; // Set up the palette data byte palette[4] = { 0, 0, 0, 0 }; if (_vm->getCurrentLights() & LIGHTMODE_actor_use_colors) { palette[1] = 10; palette[2] = actorColorsMMC64[_actorID]; } else { palette[2] = 11; palette[3] = 11; } int width = data[0]; int height = data[1]; int offsetX = _xmove + data[2]; int offsetY = _ymove + data[3]; _xmove += (int8)data[4]; _ymove += (int8)data[5]; data += 6; if (!width || !height) return 0; int xpos = _actorX + (flipped ? -1 : +1) * (offsetX * 8 - a->_width / 2); // +1 as we appear to be 1 pixel away from the original interpreter int ypos = _actorY - offsetY + 1; // This code is very similar to procC64() for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { byte color = data[y * width + x]; byte pcolor; int destX = xpos + (flipped ? -(x + 1) : x) * 8; int destY = ypos + y; if (destY >= 0 && destY < _out.h && destX >= 0 && destX < _out.w) { byte *dst = (byte *)_out.pixels + destY * _out.pitch + destX; byte *mask = _vm->getMaskBuffer(0, destY, _zbuf); if (flipped) { LINE(0, 0); LINE(2, 2); LINE(4, 4); LINE(6, 6); } else { LINE(6, 0); LINE(4, 2); LINE(2, 4); LINE(0, 6); } } } } _draw_top = MIN(_draw_top, ypos); _draw_bottom = MAX(_draw_bottom, ypos + height); if (flipped) _vm->markRectAsDirty(kMainVirtScreen, xpos - (width * 8), xpos, ypos, ypos + height, _actorID); else _vm->markRectAsDirty(kMainVirtScreen, xpos, xpos + (width * 8), ypos, ypos + height, _actorID); return 0; } #undef LINE #undef MASK_AT void C64CostumeRenderer::setCostume(int costume, int shadow) { _loaded.loadCostume(costume); } void C64CostumeLoader::loadCostume(int id) { const byte *ptr = _vm->getResourceAddress(rtCostume, id); _id = id; _baseptr = ptr + 9; _format = 0x57; _numColors = 0; _numAnim = 0; _mirror = 0; _palette = &actorColorsMMC64[id]; _frameOffsets = _baseptr + READ_LE_UINT16(ptr + 5); _dataOffsets = ptr; _animCmds = _baseptr + READ_LE_UINT16(ptr + 7); _maxHeight = 0; } void C64CostumeLoader::frameUpdate(ActorC64 *a, int cmd ) { byte limbFrames = 0; // Each costume-command has 8 limbs (0x2622) cmd <<= 3; for (int limb = 0, pos = 0; limb < 8; ++limb, pos = 0) { // get the frame number for the beginning of the costume command limbFrames = ((_animCmds + cmd)[limb]); // Dont change if frame is invalid if (limbFrames == 0xFF) continue; a->_byte_FCE2[limb] = a->_byte_FD0A; // Has limb frames ptr changed since last update? if (a->_cost.start[limb] == limbFrames) continue; // Set new limb command addresses a->_cost.start[limb] = limbFrames; a->_cost.frame[limb] = _frameOffsets[limb] + (limbFrames & 0x7f); // limb animation-frames ptr // Get first entry of a limbs' frames byte frameStart = _frameOffsets[ a->_cost.frame[limb]]; // Loop each frame in this limb until we reach the end marker while (pos != 0xFF) { // This is just so we dont overflow byte frame = _frameOffsets[frameStart + pos]; // Each animation-frame until we find end if (frame == 0xFF) break; byte ptrLow = _baseptr[frame]; byte ptrHigh = ptrLow + _dataOffsets[4]; int frameOffset = (_baseptr[ptrHigh] << 8) + _baseptr[ptrLow + 2]; // 0x23EF / 0x2400 const byte *data = _baseptr + frameOffset; if (data[3] > _maxHeight) _maxHeight = data[3] + 1; ++pos; } // Set ending position of limb frames a->_cost.end[limb] = pos; a->_cost.curpos[limb] = 0; } } // based on 0x2BCA, doesn't match disassembly because 'oldDir' variable // is not the same value as stored in the original interpreter int C64CostumeLoader::dirToDirStop(int oldDir) { int res = 0; switch (oldDir) { case 0: res = 4; // Left break; case 1: res = 5; // Right break; case 2: res = 6; // Face Camera break; default: res = 7; // Face Away break; } return res; } void C64CostumeLoader::actorSpeak(ActorC64 *a, int &cmd) { if (v0ActorTalkArray[a->_number] & 0x80) return; if ((a->_speaking & 0x80)) cmd += 0x0C; else cmd += 0x10; } void C64CostumeLoader::costumeDecodeData(Actor *a, int frame, uint usemask) { ActorC64 *A = (ActorC64 *)a; int dir = newDirToOldDir(a->getFacing()); int command = dir; loadCostume(a->_costume); // Enable/Disable speaking flag if (frame == a->_talkStartFrame) { if (v0ActorTalkArray[a->_number] & 0x40) return; A->_speaking = 1; return; } if (frame == a->_talkStopFrame) { A->_speaking = 0; return; } // Different command for stand frame if (frame == a->_standFrame) { command = dirToDirStop(dir); A->_byte_FDE8 = 0xFF; } // Update the limb frames frameUpdate(A, command); // Keep current command/frame mode A->_costCommand = dir; A->_costFrame = frame; // Update 'speaking' frames? if (A->_speaking) { command = dir; // Incase standing frame was set as cmd actorSpeak(A, command); // Update the limb speak frames frameUpdate(A, command); } } byte C64CostumeLoader::increaseAnims(Actor *a) { ActorC64 *A = (ActorC64 *)a; // check if the actor speak flag has changed since last frame increase if (A->_speaking != A->_speakingPrev) { int cmd = A->_costCommand; A->_speakingPrev = A->_speaking; actorSpeak(A, cmd); // Update the limb frames frameUpdate(A, cmd); } if (A->_moving && _vm->_currentRoom != 1 && _vm->_currentRoom != 44) { if (a->_cost.soundPos == 0) a->_cost.soundCounter++; // Is this the correct location? // 0x073C if (v0ActorTalkArray[a->_number] & 0x3F) a->_cost.soundPos = (a->_cost.soundPos + 1) % 3; } // increase each frame pos // 0x2543 for (int limb = 0; limb < 8; ++limb) { if (++a->_cost.curpos[limb] >= a->_cost.end[limb]) { // 0x2541 if( A->_byte_FCE2[limb] == 0 ) { // 0x2556 --a->_cost.curpos[limb]; A->_costCommandNew = 0xFF; //A->_costCommand = 0xFF; // 0x2568 //A->_limbCommandNew[limb] = 0xFF; //A->_limbCommand[limb] = 0xFF; } else { if( A->_byte_FCE2[limb] != 0xFF ) --A->_byte_FCE2[limb]; a->_cost.curpos[limb] = 0; } } } return 1; } } // End of namespace Scumm