diff options
Diffstat (limited to 'engines/startrek/actors.cpp')
-rw-r--r-- | engines/startrek/actors.cpp | 1421 |
1 files changed, 1421 insertions, 0 deletions
diff --git a/engines/startrek/actors.cpp b/engines/startrek/actors.cpp new file mode 100644 index 0000000000..04c2a175c4 --- /dev/null +++ b/engines/startrek/actors.cpp @@ -0,0 +1,1421 @@ +/* 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 "startrek/iwfile.h" +#include "startrek/room.h" +#include "startrek/startrek.h" + +namespace StarTrek { + +void StarTrekEngine::initActors() { + for (int i = 0; i < NUM_ACTORS; i++) + _actorList[i] = Actor(); + for (int i = 0; i < MAX_BAN_FILES; i++) + _banFiles[i].reset(); + + strcpy(_kirkActor->animationString, "kstnd"); + strcpy(_spockActor->animationString, "sstnd"); + strcpy(_mccoyActor->animationString, "mstnd"); + strcpy(_redshirtActor->animationString, "rstnd"); +} + +int StarTrekEngine::loadActorAnim(int actorIndex, const Common::String &animName, int16 x, int16 y, Fixed8 scale) { + debugC(6, kDebugGraphics, "Load animation '%s' on actor %d", animName.c_str(), actorIndex); + + Actor *actor; + + if (actorIndex == -1) { + // TODO + warning("loadActorAnim: actor == -1"); + } else + actor = &_actorList[actorIndex]; + + if (actor->spriteDrawn) { + releaseAnim(actor); + drawActorToScreen(actor, animName, x, y, scale, false); + } else { + drawActorToScreen(actor, animName, x, y, scale, true); + } + + actor->triggerActionWhenAnimFinished = false; + actor->finishedAnimActionParam = 0; + + return actorIndex; +} + +void StarTrekEngine::loadBanFile(const Common::String &name) { + debugC(kDebugGeneral, 7, "Load BAN file: %s.ban", name.c_str()); + for (int i = 0; i < MAX_BAN_FILES; i++) { + if (!_banFiles[i]) { + _banFiles[i] = loadFile(name + ".ban"); + _banFileOffsets[i] = 0; + return; + } + } + + warning("Couldn't load .BAN file \"%s.ban\"", name.c_str()); +} + +bool StarTrekEngine::actorWalkToPosition(int actorIndex, const Common::String &animFile, int16 srcX, int16 srcY, int16 destX, int16 destY) { + debugC(6, "Obj %d: walk from (%d,%d) to (%d,%d)", actorIndex, srcX, srcY, destX, destY); + + Actor *actor = &_actorList[actorIndex]; + + actor->triggerActionWhenAnimFinished = false; + if (isPositionSolid(destX, destY)) + return false; + + if (actor->spriteDrawn) + releaseAnim(actor); + else + _gfx->addSprite(&actor->sprite); + + actor->spriteDrawn = true; + actor->animType = 1; + actor->frameToStartNextAnim = _frameIndex + 1; + strcpy(actor->animationString2, animFile.c_str()); + + actor->dest.x = destX; + actor->dest.y = destY; + actor->field92 = 0; + actor->triggerActionWhenAnimFinished = false; + + actor->iwDestPosition = -1; + actor->iwSrcPosition = -1; + + if (directPathExists(srcX, srcY, destX, destY)) { + chooseActorDirectionForWalking(actor, srcX, srcY, destX, destY); + updateActorPositionWhileWalking(actor, (actor->granularPosX + 0.5).toInt(), (actor->granularPosY + 0.5).toInt()); + return true; + } else { + actor->iwSrcPosition = _iwFile->getClosestKeyPosition(srcX, srcY); + actor->iwDestPosition = _iwFile->getClosestKeyPosition(destX, destY); + + if (actor->iwSrcPosition == -1 || actor->iwDestPosition == -1) { + // No path exists; face south by default. + strcat(actor->animationString2, "S"); + actor->direction = 'S'; + + updateActorPositionWhileWalking(actor, srcX, srcY); + initStandAnim(actorIndex); + + return false; + } else { + Common::Point iwSrc = _iwFile->_keyPositions[actor->iwSrcPosition]; + chooseActorDirectionForWalking(actor, srcX, srcY, iwSrc.x, iwSrc.y); + updateActorPositionWhileWalking(actor, (actor->granularPosX + 0.5).toInt(), (actor->granularPosY + 0.5).toInt()); + return true; + } + } +} + +void StarTrekEngine::updateActorAnimations() { + for (int i = 0; i < NUM_ACTORS; i++) { + Actor *actor = &_actorList[i]; + if (!actor->spriteDrawn) + continue; + + switch (actor->animType) { + case 0: // Not walking? + case 2: + if (_frameIndex >= actor->frameToStartNextAnim) { + int nextAnimIndex = getRandomWord() & 3; + actor->animFile->seek(18 + nextAnimIndex + actor->animFrame * 22, SEEK_SET); + byte nextAnimFrame = actor->animFile->readByte(); + + if (actor->animFrame != nextAnimFrame) { + if (nextAnimFrame == actor->numAnimFrames - 1) { + actor->field62++; + if (actor->triggerActionWhenAnimFinished) { + addAction(ACTION_FINISHED_ANIMATION, actor->finishedAnimActionParam, 0, 0); + } + } + } + + actor->animFrame = nextAnimFrame; + if (actor->animFrame >= actor->numAnimFrames) { + if (actor->animationString[0] == '\0') + removeActorFromScreen(i); + else + initStandAnim(i); + } else { + Sprite *sprite = &actor->sprite; + + actor->animFile->seek(actor->animFrame * 22, SEEK_SET); + char animFrameFilename[16]; + actor->animFile->read(animFrameFilename, 16); + sprite->setBitmap(loadAnimationFrame(animFrameFilename, actor->scale)); + + memset(actor->bitmapFilename, 0, 10); + strncpy(actor->bitmapFilename, animFrameFilename, 9); + + actor->animFile->seek(10 + actor->animFrame * 22, SEEK_SET); + uint16 xOffset = actor->animFile->readUint16(); + uint16 yOffset = actor->animFile->readUint16(); + uint16 basePriority = actor->animFile->readUint16(); + uint16 frames = actor->animFile->readUint16(); + + sprite->pos.x = xOffset + actor->pos.x; + sprite->pos.y = yOffset + actor->pos.y; + sprite->drawPriority = _gfx->getPriValue(0, yOffset + actor->pos.y) + basePriority; + sprite->bitmapChanged = true; + + actor->frameToStartNextAnim = frames + _frameIndex; + } + } + break; + case 1: // Walking + if (_frameIndex < actor->frameToStartNextAnim) + break; + if (i == 0) // Kirk only + checkTouchedLoadingZone(actor->pos.x, actor->pos.y); + if (actor->field90 != 0) { + Sprite *sprite = &actor->sprite; + int loops; + if (getActorScaleAtPosition((actor->granularPosY + 0.5).toInt()) < 0.625) + loops = 1; + else + loops = 2; + for (int k = 0; k < loops; k++) { + if (actor->field90 == 0) + break; + actor->field90--; + Fixed16 newX = actor->granularPosX + actor->speedX; + Fixed16 newY = actor->granularPosY + actor->speedY; + if ((actor->field90 & 3) == 0) { + sprite->bitmap.reset(); + updateActorPositionWhileWalking(actor, (newX + 0.5).toInt(), (newY + 0.5).toInt()); + actor->field92++; + } + + actor->granularPosX = newX; + actor->granularPosY = newY; + actor->frameToStartNextAnim = _frameIndex; + } + } else { // actor->field90 == 0 + if (actor->iwSrcPosition == -1) { + if (actor->triggerActionWhenAnimFinished) { + actor->triggerActionWhenAnimFinished = false; + addAction(ACTION_FINISHED_WALKING, actor->finishedAnimActionParam & 0xff, 0, 0); + } + + actor->sprite.bitmap.reset(); + updateActorPositionWhileWalking(actor, (actor->granularPosX + 0.5).toInt(), (actor->granularPosY + 0.5).toInt()); + initStandAnim(i); + } else { // actor->iwSrcPosition != -1 + if (actor->iwSrcPosition == actor->iwDestPosition) { + actor->animationString2[strlen(actor->animationString2) - 1] = '\0'; + actor->iwDestPosition = -1; + actor->iwSrcPosition = -1; + chooseActorDirectionForWalking(actor, actor->pos.x, actor->pos.y, actor->dest.x, actor->dest.y); + } else { + int index = _iwFile->_iwEntries[actor->iwSrcPosition][actor->iwDestPosition]; + actor->iwSrcPosition = index; + Common::Point dest = _iwFile->_keyPositions[actor->iwSrcPosition]; + actor->animationString2[strlen(actor->animationString2) - 1] = '\0'; + chooseActorDirectionForWalking(actor, actor->pos.x, actor->pos.y, dest.x, dest.y); + } + } + } + break; + default: + error("Invalid anim type."); + break; + } + } +} + +void StarTrekEngine::renderBanBelowSprites() { + if ((_frameIndex & 3) != 0) + return; + + byte *screenPixels = _gfx->lockScreenPixels(); + byte *bgPixels = _gfx->getBackgroundPixels(); + + for (int i = 0; i < MAX_BAN_FILES; i++) { + if (!_banFiles[i]) + continue; + + // TODO: video modes other than VGA + + _banFiles[i]->seek(_banFileOffsets[i], SEEK_SET); + uint16 offset = _banFiles[i]->readUint16(); + + if (offset == 0xffff) { + _banFileOffsets[i] = 0; + _banFiles[i]->seek(0, SEEK_SET); + offset = _banFiles[i]->readSint16(); + } + + int16 size = _banFiles[i]->readSint16(); + if (size != 0) { + _banFiles[i]->seek(_banFileOffsets[i], SEEK_SET); + renderBan(screenPixels, _banFiles[i]); + + _banFiles[i]->seek(_banFileOffsets[i], SEEK_SET); + renderBan(bgPixels, _banFiles[i]); + } + + } + + _gfx->unlockScreenPixels(); +} + +void StarTrekEngine::renderBan(byte *destPixels, SharedPtr<FileStream> banFile) { + uint16 offset = banFile->readUint16(); + int32 size = banFile->readUint16(); + + byte *dest = destPixels + offset; + + // Skip 8 bytes (rectangle encompassing the area being drawn to) + banFile->readSint32(); + banFile->readSint32(); + + while (--size >= 0) { + assert(dest >= destPixels && dest < destPixels + SCREEN_WIDTH * SCREEN_HEIGHT); + int8 b = banFile->readByte(); + + if (b == -128) // Add value to destination (usually jumping to next row) + dest += banFile->readUint16(); + else if (b < 0) { // Repeated byte + byte c = banFile->readByte(); + if (c == 0) + dest += (-b) + 1; + else { + for (int j = 0; j < (-b) + 1; j++) + (*dest++) = c; + } + } else { // List of bytes + b++; + while (b-- != 0) { + byte c = banFile->readByte(); + if (c == 0) + dest++; + else + *(dest++) = c; + } + } + } +} + +void StarTrekEngine::renderBanAboveSprites() { + if ((_frameIndex & 3) != 0) + return; + + for (int i = 0; i < MAX_BAN_FILES; i++) { + if (!_banFiles[i]) + continue; + + _banFiles[i]->seek(_banFileOffsets[i], SEEK_SET); + uint16 offset = _banFiles[i]->readUint16(); + + if (offset == 0xffff) { + _banFileOffsets[i] = 0; + _banFiles[i]->seek(0, SEEK_SET); + offset = _banFiles[i]->readSint16(); + } + + int16 size = _banFiles[i]->readSint16(); + if (size != 0) { + Common::Rect rect; + rect.left = _banFiles[i]->readSint16(); + rect.top = _banFiles[i]->readSint16(); + rect.right = _banFiles[i]->readSint16() + 1; + rect.bottom = _banFiles[i]->readSint16() + 1; + + // Draw all sprites in this rectangle to a custom surface, and only update the + // specific pixels that were updated by the BAN file this frame. + // Rationale behind this is that, since the background may not have been + // redrawn, the transparent sprites (ie. textboxes) would further darken any + // pixels behind them that haven't been updated this frame. So, we can't just + // update everything in this rectangle. + // FIXME: This copies the entire screen surface for temporary drawing, which + // is somewhat wasteful. Original game had one more graphics layer it drew to + // before the screen was updated... + ::Graphics::Surface surface; + _gfx->drawAllSpritesInRectToSurface(rect, &surface); + + byte *destPixels = _gfx->lockScreenPixels(); + byte *src = (byte *)surface.getPixels() + offset; + byte *dest = destPixels + offset; + + // This is similar to renderBan(), except it copies pixels from the surface + // above instead of drawing directly to it. (Important since sprites may be + // drawn on top.) + while (--size >= 0) { + assert(dest >= destPixels && dest < destPixels + SCREEN_WIDTH * SCREEN_HEIGHT); + int8 b = _banFiles[i]->readByte(); + if (b == -128) { + uint16 skip = _banFiles[i]->readUint16(); + dest += skip; + src += skip; + } else if (b < 0) { + byte c = _banFiles[i]->readByte(); + if (c == 0) { + dest += (-b) + 1; + src += (-b) + 1; + } + else { + for (int j = 0; j < (-b) + 1; j++) + *(dest++) = *(src++); + } + } else { + b++; + while (b-- != 0) { + byte c = _banFiles[i]->readByte(); + if (c == 0) { + dest++; + src++; + } else + *(dest++) = *(src++); + } + } + } + + _gfx->unlockScreenPixels(); + surface.free(); + + _banFileOffsets[i] = _banFiles[i]->pos(); + } + } +} + +void StarTrekEngine::removeActorFromScreen(int actorIndex) { + Actor *actor = &_actorList[actorIndex]; + + if (actor->spriteDrawn != 1) + return; + + debugC(6, kDebugGraphics, "Stop drawing actor %d", actorIndex); + + Sprite *sprite = &actor->sprite; + sprite->field16 = true; + sprite->bitmapChanged = true; + _gfx->drawAllSprites(); + _gfx->delSprite(sprite); + releaseAnim(actor); +} + +void StarTrekEngine::actorFunc1() { + for (int i = 0; i < NUM_ACTORS; i++) { + if (_actorList[i].spriteDrawn == 1) { + removeActorFromScreen(i); + } + } + + for (int i = 0; i < MAX_BAN_FILES; i++) { + _banFiles[i].reset(); + } +} + +void StarTrekEngine::drawActorToScreen(Actor *actor, const Common::String &_animName, int16 x, int16 y, Fixed8 scale, bool addSprite) { + Common::String animFilename = _animName; + if (_animName.hasPrefixIgnoreCase("stnd") /* && word_45d20 == -1 */) // TODO + animFilename += 'j'; + memcpy(actor->animFilename, _animName.c_str(), sizeof(actor->animFilename)); + + actor->animType = 2; + actor->animFile = loadFile(animFilename + ".anm"); + actor->numAnimFrames = actor->animFile->size() / 22; + actor->animFrame = 0; + actor->pos.x = x; + actor->pos.y = y; + actor->field62 = 0; + actor->scale = scale; + + actor->animFile->seek(16, SEEK_SET); + actor->frameToStartNextAnim = actor->animFile->readUint16() + _frameIndex; + + char firstFrameFilename[11]; + actor->animFile->seek(0, SEEK_SET); + actor->animFile->read(firstFrameFilename, 10); + firstFrameFilename[10] = '\0'; + + Sprite *sprite = &actor->sprite; + if (addSprite) + _gfx->addSprite(sprite); + + sprite->setBitmap(loadAnimationFrame(firstFrameFilename, scale)); + memset(actor->bitmapFilename, 0, sizeof(char) * 10); + strncpy(actor->bitmapFilename, firstFrameFilename, sizeof(char) * 9); + + actor->scale = scale; + + actor->animFile->seek(10, SEEK_SET); + uint16 xOffset = actor->animFile->readUint16(); + uint16 yOffset = actor->animFile->readUint16(); + uint16 basePriority = actor->animFile->readUint16(); + + sprite->pos.x = xOffset + actor->pos.x; + sprite->pos.y = yOffset + actor->pos.y; + sprite->drawPriority = _gfx->getPriValue(0, yOffset + actor->pos.y) + basePriority; + sprite->bitmapChanged = true; + + actor->spriteDrawn = 1; +} + +void StarTrekEngine::releaseAnim(Actor *actor) { + switch (actor->animType) { + case 0: + case 2: + actor->sprite.bitmap.reset(); + actor->animFile.reset(); + break; + case 1: + actor->sprite.bitmap.reset(); + break; + default: + error("Invalid anim type"); + break; + } + + actor->spriteDrawn = 0; +} + +void StarTrekEngine::initStandAnim(int actorIndex) { + Actor *actor = &_actorList[actorIndex]; + + if (!actor->spriteDrawn) + error("initStandAnim: dead anim"); + + //////////////////// + // sub_239d2 + const char *directions = "nsew"; + + if (actorIndex >= 0 && actorIndex <= 3) { + int8 dir = _awayMission.crewDirectionsAfterWalk[actorIndex]; + if (dir != -1) { + actor->direction = directions[dir]; + _awayMission.crewDirectionsAfterWalk[actorIndex] = -1; + } + } + // end of sub_239d2 + //////////////////// + + Common::String animName; + if (actor->direction != 0) + animName = Common::String(actor->animationString) + (char)actor->direction; + else // Default to facing south + animName = Common::String(actor->animationString) + 's'; + + Fixed8 scale = getActorScaleAtPosition(actor->pos.y); + loadActorAnim(actorIndex, animName, actor->pos.x, actor->pos.y, scale); + actor->animType = 0; +} + +void StarTrekEngine::updateActorPositionWhileWalking(Actor *actor, int16 x, int16 y) { + actor->scale = getActorScaleAtPosition(y); + Common::String animName = Common::String::format("%s%02d", actor->animationString2, actor->field92 & 7); + actor->sprite.setBitmap(loadAnimationFrame(animName, actor->scale)); + + memset(actor->bitmapFilename, 0, 10); + strncpy(actor->bitmapFilename, animName.c_str(), 9); + + Sprite *sprite = &actor->sprite; + sprite->drawPriority = _gfx->getPriValue(0, y); + sprite->pos.x = x; + sprite->pos.y = y; + sprite->bitmapChanged = true; + + actor->frameToStartNextAnim = _frameIndex; + actor->pos.x = x; + actor->pos.y = y; +} + +void StarTrekEngine::chooseActorDirectionForWalking(Actor *actor, int16 srcX, int16 srcY, int16 destX, int16 destY) { + actor->granularPosX = srcX; + actor->granularPosY = srcY; + + int16 distX = destX - srcX; + int16 distY = destY - srcY; + int16 absDistX = abs(distX); + int16 absDistY = abs(distY); + + if (absDistX > absDistY) { + char d; + if (distX > 0) + d = 'E'; + else + d = 'W'; + + // Append direction to animation string + actor->animationString2[strlen(actor->animationString2) + 1] = '\0'; + actor->animationString2[strlen(actor->animationString2)] = d; + + actor->direction = d; + actor->field90 = absDistX; + + if (distX != 0) { + if (distX > 0) + actor->speedX = 1.0; + else + actor->speedX = -1.0; + + actor->speedY = Fixed16(distY) / absDistX; + } + } else { + char d; + if (distY > 0) + d = 'S'; + else + d = 'N'; + + // Append direction to animation string + actor->animationString2[strlen(actor->animationString2) + 1] = '\0'; + actor->animationString2[strlen(actor->animationString2)] = d; + + actor->direction = d; + actor->field90 = absDistY; + + if (distY != 0) { + if (distY > 0) + actor->speedY = 1.0; + else + actor->speedY = -1.0; + + actor->speedX = Fixed16(distX) / absDistY; + } + } +} + +bool StarTrekEngine::directPathExists(int16 srcX, int16 srcY, int16 destX, int16 destY) { + int32 distX = destX - srcX; + int32 distY = destY - srcY; + + int32 absDistX = abs(distX); + int32 absDistY = abs(distY); + + int32 distCounter; + Fixed16 speedX, speedY; + + if (absDistX > absDistY) { + distCounter = absDistX; + + if (distCounter == 0) + return true; + + speedY = Fixed16(distY) / absDistX; + + if (distX > 0) + speedX = 1.0; + else + speedX = -1.0; + } else { // absDistX <= absDistY + distCounter = absDistY; + + if (distCounter == 0) + return true; + + speedX = Fixed16(distX) / absDistY; + + if (distY > 0) + speedY = 1.0; + else + speedY = -1.0; + } + + Fixed16 fixedX = srcX; + Fixed16 fixedY = srcY; + + if (isPositionSolid((fixedX + 0.5).toInt(), (fixedY + 0.5).toInt())) + return false; + + while (distCounter-- > 0) { + fixedX += speedX; + fixedY += speedY; + + if (isPositionSolid((fixedX + 0.5).toInt(), (fixedY + 0.5).toInt())) + return false; + } + + return true; +} + +int StarTrekEngine::findObjectAt(int x, int y) { + Sprite *sprite = _gfx->getSpriteAt(x, y); + + if (sprite != nullptr) { + if (sprite == &_inventoryIconSprite) + return OBJECT_INVENTORY_ICON; + else if (sprite == &_itemIconSprite) + return _awayMission.activeObject; + + for (int i = 0; i < NUM_ACTORS; i++) { + Actor *actor = &_actorList[i]; + if (sprite == &actor->sprite) + return i; + } + + error("findObject: Clicked on an unknown sprite"); + } + + _objectHasWalkPosition = false; + int actionBit = 1 << (_awayMission.activeAction - 1); + int offset = _room->getFirstHotspot(); + + while (offset != _room->getHotspotEnd()) { + uint16 word = _room->readRdfWord(offset); + if (word & 0x8000) { + if ((word & actionBit) && isPointInPolygon((int16 *)(_room->_rdfData + offset + 6), x, y)) { + int actorIndex = _room->readRdfWord(offset + 6); + _objectHasWalkPosition = true; + _objectWalkPosition.x = _room->readRdfWord(offset + 2); + _objectWalkPosition.y = _room->readRdfWord(offset + 4); + return actorIndex; + } + + int numVertices = _room->readRdfWord(offset + 8); + offset = offset + 10 + numVertices * 4; + } else { + if (isPointInPolygon((int16 *)(_room->_rdfData + offset), x, y)) { + int actorIndex = _room->readRdfWord(offset); + return actorIndex; + } + + int numVertices = _room->readRdfWord(offset + 2); + offset = offset + 4 + numVertices * 4; + } + } + + return -1; +} + +SharedPtr<Bitmap> StarTrekEngine::loadAnimationFrame(const Common::String &filename, Fixed8 scale) { + SharedPtr<Bitmap> bitmapToReturn; + + char basename[5]; + strncpy(basename, filename.c_str() + 1, 4); + basename[4] = '\0'; + + char c = filename[0]; + if ((strcmp(basename, "stnd") == 0 || strcmp(basename, "tele") == 0) + && (c == 'm' || c == 's' || c == 'k' || c == 'r')) { + if (c == 'm') { + // Mccoy has the "base" animations for all crewmen + bitmapToReturn = _gfx->loadBitmap(filename); + } else { + // All crewman other than mccoy copy the animation frames from mccoy, change + // the colors of the uniforms, and load an "xor" file to redraw the face. + + // TODO: The ".$bm" extension is a "virtual file"? Caches the changes to the + // file made here? + // bitmapToReturn = _gfx->loadBitmap(filename + ".$bm"); + + if (bitmapToReturn == nullptr) { + Common::String mccoyFilename = filename; + mccoyFilename.setChar('m', 0); + SharedPtr<Bitmap> bitmap = _gfx->loadBitmap(mccoyFilename); + + uint16 width = bitmap->width; + uint16 height = bitmap->height; + + bitmapToReturn = SharedPtr<Bitmap>(new Bitmap(width, height)); + bitmapToReturn->xoffset = bitmap->xoffset; + bitmapToReturn->yoffset = bitmap->yoffset; + + // Change uniform color + int16 colorShift; + switch (c) { + case 'k': // Kirk + colorShift = 8; + break; + case 'r': // Redshirt + colorShift = -8; + break; + case 's': // Spock + colorShift = 0; + break; + } + + if (colorShift == 0) { + memcpy(bitmapToReturn->pixels, bitmap->pixels, width * height); + } else { + byte *src = bitmap->pixels; + byte *dest = bitmapToReturn->pixels; + byte baseUniformColor = 0xa8; + + for (int i = 0; i < width * height; i++) { + byte b = *src++; + if (b >= baseUniformColor && b < baseUniformColor + 8) + *dest++ = b + colorShift; + else + *dest++ = b; + } + } + + // Redraw face with xor file + SharedPtr<FileStream> xorFile = loadFile(filename + ".xor"); + xorFile->seek(0, SEEK_SET); + uint16 xoffset = bitmap->xoffset - xorFile->readUint16(); + uint16 yoffset = bitmap->yoffset - xorFile->readUint16(); + uint16 xorWidth = xorFile->readUint16(); + uint16 xorHeight = xorFile->readUint16(); + + byte *dest = bitmapToReturn->pixels + yoffset * bitmap->width + xoffset; + + for (int i = 0; i < xorHeight; i++) { + for (int j = 0; j < xorWidth; j++) + *dest++ ^= xorFile->readByte(); + dest += (bitmap->width - xorWidth); + } + } + } + } else { + // TODO: when loading a bitmap, it passes a different argument than is standard to + // the "file loading with cache" function... + bitmapToReturn = _gfx->loadBitmap(filename); + } + + if (scale != 1.0) { + bitmapToReturn = scaleBitmap(bitmapToReturn, scale); + } + + return bitmapToReturn; +} + + +int StarTrekEngine::selectObjectForUseAction() { + while (true) { + if (!(_awayMission.crewDownBitset & (1 << OBJECT_KIRK))) + showInventoryIcons(false); + + TrekEvent event; + + while (true) { + if (!getNextEvent(&event)) + continue; + + if (event.type == TREKEVENT_TICK) { + updateMouseBitmap(); + _gfx->drawAllSprites(); + _sound->checkLoopMusic(); + } else if (event.type == TREKEVENT_LBUTTONDOWN) { + removeNextEvent(); + break; + } else if (event.type == TREKEVENT_MOUSEMOVE) { + } else if (event.type == TREKEVENT_RBUTTONDOWN) { + // Allow this to be processed by main away mission loop + break; + } else if (event.type == TREKEVENT_KEYDOWN) { + if (event.kbd.keycode == Common::KEYCODE_ESCAPE + || event.kbd.keycode == Common::KEYCODE_w + || event.kbd.keycode == Common::KEYCODE_t + || event.kbd.keycode == Common::KEYCODE_u + || event.kbd.keycode == Common::KEYCODE_g + || event.kbd.keycode == Common::KEYCODE_l + || event.kbd.keycode == Common::KEYCODE_SPACE + || event.kbd.keycode == Common::KEYCODE_F2) { + // Allow these buttons to be processed by main away mission loop + break; + } else if (event.kbd.keycode == Common::KEYCODE_i) { + removeNextEvent(); + break; + } else if (event.kbd.keycode == Common::KEYCODE_RETURN || event.kbd.keycode == Common::KEYCODE_KP_ENTER || event.kbd.keycode == Common::KEYCODE_F1) { + // Simulate left-click + removeNextEvent(); + event.type = TREKEVENT_LBUTTONDOWN; + break; + } + } + + removeNextEvent(); + } + + if (event.type == TREKEVENT_KEYDOWN && event.kbd.keycode == Common::KEYCODE_i) { + hideInventoryIcons(); + int clickedObject = showInventoryMenu(50, 50, true); + if (clickedObject == -1) + continue; + return clickedObject; + } else if (event.type == TREKEVENT_LBUTTONDOWN) { + int clickedObject = findObjectAt(_gfx->getMousePos()); + hideInventoryIcons(); + + if (clickedObject == -1) + continue; + else if (isObjectUnusable(clickedObject, ACTION_USE)) + continue; + else if (clickedObject == OBJECT_INVENTORY_ICON) { + clickedObject = showInventoryMenu(50, 50, false); + if (clickedObject == -1) + continue; + else + return clickedObject; + } else if (clickedObject <= OBJECT_REDSHIRT) + return clickedObject; + else if (isObjectUnusable(OBJECT_KIRK, ACTION_USE)) + continue; + else if (_room->actionHasCode(ACTION_USE, OBJECT_KIRK, clickedObject, 0) + || _room->actionHasCode(ACTION_GET, clickedObject, 0, 0) + || _room->actionHasCode(ACTION_WALK, clickedObject, 0, 0)) { + _awayMission.activeObject = OBJECT_KIRK; + _awayMission.passiveObject = clickedObject; + _awayMission.activeAction = ACTION_USE; + clickedObject = OBJECT_KIRK; + if (!walkActiveObjectToHotspot()) + addAction(_awayMission.activeAction, _awayMission.activeObject, _awayMission.passiveObject, 0); + return clickedObject; + } else + continue; + } else { + hideInventoryIcons(); + return -1; + } + } +} + +Common::String StarTrekEngine::getCrewmanAnimFilename(int actorIndex, const Common::String &basename) { + const char *crewmanChars = "ksmr"; + assert(actorIndex >= 0 && actorIndex < 4); + return crewmanChars[actorIndex] + basename; +} + +void StarTrekEngine::updateMouseBitmap() { + const bool worksOnCrewmen[] = { // True if the action reacts with crewmen + false, // ACTION_WALK + true, // ACTION_USE + false, // ACTION_GET + true, // ACTION_LOOK + true // ACTION_TALK + }; + const bool worksOnActors[] = { // True if the action reacts with other objects + false, // ACTION_WALK + true, // ACTION_USE + true, // ACTION_GET + true, // ACTION_LOOK + true // ACTION_TALK + }; + const bool worksOnHotspots[] = { // True if the action reacts with hotspots + false, // ACTION_WALK + true, // ACTION_USE + true, // ACTION_GET + true, // ACTION_LOOK + false // ACTION_TALK + }; + + Common::Point mousePos = _gfx->getMousePos(); + int selected = findObjectAt(mousePos.x, mousePos.y); + int action = _awayMission.activeAction; + assert(action >= 1 && action <= 5); + + bool withRedOutline; + + if (selected >= 0 && selected <= 3 && worksOnCrewmen[action - 1]) + withRedOutline = true; + else if (selected > 3 && selected < NUM_ACTORS && worksOnActors[action - 1]) + withRedOutline = true; + else if (selected >= NUM_ACTORS && selected < HOTSPOTS_END && worksOnHotspots[action - 1]) + withRedOutline = true; + else + withRedOutline = false; + + chooseMouseBitmapForAction(action, withRedOutline); +} + +bool StarTrekEngine::walkActiveObjectToHotspot() { + if (!_objectHasWalkPosition) + return false; + + int objectIndex; + if (_awayMission.activeAction != ACTION_USE) + objectIndex = OBJECT_KIRK; + else if (_awayMission.activeObject <= OBJECT_REDSHIRT) + objectIndex = _awayMission.activeObject; + else if (_awayMission.activeObject >= ITEMS_START && _awayMission.activeObject <= ITEMS_END) { // FIXME: "<= ITEMS_END" doesn't make sense? + if (_awayMission.activeObject == OBJECT_ISTRICOR) + objectIndex = OBJECT_SPOCK; + else if (_awayMission.activeObject == OBJECT_IMTRICOR) + objectIndex = OBJECT_MCCOY; + else + objectIndex = OBJECT_KIRK; + } else // This is the original error message... + error("Jay didn't think about pmcheck"); + + byte finishedAnimActionParam = false; + bool walk = false; + + if (_awayMission.activeAction == ACTION_WALK) + walk = true; + else { + // If this action has code defined for it in this room, buffer the action to be + // done after the object finished walking there. + Action action = {_awayMission.activeAction, _awayMission.activeObject, 0, 0}; + if (_awayMission.activeAction == ACTION_USE) + action.b2 = _awayMission.passiveObject; + + if (_room->actionHasCode(action)) { + for (int i = 0; i < MAX_BUFFERED_WALK_ACTIONS; i++) { + if (!_actionOnWalkCompletionInUse[i]) { + finishedAnimActionParam = i + 0xe0; + _actionOnWalkCompletionInUse[i] = true; + _actionOnWalkCompletion[i] = action; + walk = true; + break; + } + } + } + } + + if (walk) { + Actor *actor = &_actorList[objectIndex]; + Common::String anim = getCrewmanAnimFilename(objectIndex, "walk"); + actorWalkToPosition(objectIndex, anim, actor->pos.x, actor->pos.y, _objectWalkPosition.x, _objectWalkPosition.y); + if (finishedAnimActionParam != 0) { + actor->triggerActionWhenAnimFinished = true; + actor->finishedAnimActionParam = finishedAnimActionParam; + } + _objectHasWalkPosition = false; + return true; + } else { + _objectHasWalkPosition = false; + return false; + } +} + +void StarTrekEngine::showInventoryIcons(bool showItem) { + const char *crewmanFilenames[] = { + "ikirk", + "ispock", + "imccoy", + "iredshir" + }; + + Common::String itemFilename; + + if (showItem) { + int i = _awayMission.activeObject; + if (i >= OBJECT_KIRK && i <= OBJECT_REDSHIRT) + itemFilename = crewmanFilenames[i]; + else { + assert(i >= ITEMS_START && i < ITEMS_END); + Item *item = &_itemList[i - ITEMS_START]; + itemFilename = item->name; + } + } + + if (itemFilename.empty()) + _inventoryIconSprite.pos.x = 10; + else { + _gfx->addSprite(&_itemIconSprite); + _itemIconSprite.drawMode = 2; + _itemIconSprite.pos.x = 10; + _itemIconSprite.pos.y = 10; + _itemIconSprite.drawPriority = 15; + _itemIconSprite.drawPriority2 = 8; + _itemIconSprite.setBitmap(_gfx->loadBitmap(itemFilename)); + + _inventoryIconSprite.pos.x = 46; + } + + _gfx->addSprite(&_inventoryIconSprite); + + _inventoryIconSprite.pos.y = 10; + _inventoryIconSprite.drawMode = 2; + _inventoryIconSprite.drawPriority = 15; + _inventoryIconSprite.drawPriority2 = 8; + _inventoryIconSprite.setBitmap(_gfx->loadBitmap("inv00")); +} + +bool StarTrekEngine::isObjectUnusable(int object, int action) { + if (action == ACTION_LOOK) + return false; + if (object == OBJECT_REDSHIRT && _awayMission.redshirtDead) + return true; + if (object >= OBJECT_KIRK && object <= OBJECT_REDSHIRT && (_awayMission.crewDownBitset & (1 << object))) + return true; + if (object == OBJECT_IMTRICOR && (_awayMission.crewDownBitset & (1 << OBJECT_MCCOY))) + return true; + if (object == OBJECT_ISTRICOR && (_awayMission.crewDownBitset & (1 << OBJECT_SPOCK))) + return true; + return false; +} + +void StarTrekEngine::hideInventoryIcons() { + // Clear these sprites from the screen + if (_itemIconSprite.drawMode == 2) + _itemIconSprite.dontDrawNextFrame(); + if (_inventoryIconSprite.drawMode == 2) + _inventoryIconSprite.dontDrawNextFrame(); + + _gfx->drawAllSprites(); + + if (_itemIconSprite.drawMode == 2) { + _gfx->delSprite(&_itemIconSprite); + _itemIconSprite.drawMode = 0; + _itemIconSprite.bitmap.reset(); + } + + if (_inventoryIconSprite.drawMode == 2) { + _gfx->delSprite(&_inventoryIconSprite); + _inventoryIconSprite.drawMode = 0; + _inventoryIconSprite.bitmap.reset(); + } +} + +void StarTrekEngine::updateCrewmanGetupTimers() { + if (_awayMission.crewDownBitset == 0) + return; + for (int i = OBJECT_KIRK; i <= OBJECT_REDSHIRT; i++) { + Actor *actor = &_actorList[i]; + + if (!(_awayMission.crewDownBitset & (1 << i))) + continue; + + _awayMission.crewGetupTimers[i]--; + if (_awayMission.crewGetupTimers[i] <= 0) { + Common::String anim = getCrewmanAnimFilename(i, "getu"); + int8 dir = _awayMission.crewDirectionsAfterWalk[i]; + char d; + if (dir == -1) { + d = actor->direction; + } else { + const char *dirs = "nsew"; + Fixed8 scale = getActorScaleAtPosition(actor->sprite.pos.y); + d = dirs[dir]; + + int16 xOffset = 0, yOffset = 0; + if (d == 'n') { + xOffset = -24; + yOffset = -8; + } else if (d == 'w') { + xOffset = -35; + yOffset = -12; + } + actor->sprite.pos.x += scale.multToInt(xOffset); + actor->sprite.pos.y += scale.multToInt(yOffset); + } + + anim += (char)d; + loadActorAnimWithRoomScaling(i, anim, actor->sprite.pos.x, actor->sprite.pos.y); + _awayMission.crewDownBitset &= ~(1 << i); + } + } +} + +int StarTrekEngine::showInventoryMenu(int x, int y, bool restoreMouse) { + const int ITEMS_PER_ROW = 5; + + Common::Point oldMousePos = _gfx->getMousePos(); + bool keyboardControlledMouse = _keyboardControlsMouse; + _keyboardControlsMouse = false; + + int itemIndex = 0; + int numItems = 0; + + char itemNames[NUM_OBJECTS][10]; + Common::Point itemPositions[NUM_OBJECTS]; + int16 itemIndices[NUM_OBJECTS]; + + while (itemIndex < NUM_OBJECTS) { + if (_itemList[itemIndex].have) { + strcpy(itemNames[numItems], _itemList[itemIndex].name); + + int16 itemX = (numItems % ITEMS_PER_ROW) * 32 + x; + int16 itemY = (numItems / ITEMS_PER_ROW) * 32 + y; + itemPositions[numItems] = Common::Point(itemX, itemY); + itemIndices[numItems] = _itemList[itemIndex].field2; + + numItems++; + } + itemIndex++; + } + + Sprite itemSprites[NUM_OBJECTS]; + + for (int i = 0; i < numItems; i++) { + _gfx->addSprite(&itemSprites[i]); + + itemSprites[i].drawMode = 2; + itemSprites[i].pos.x = itemPositions[i].x; + itemSprites[i].pos.y = itemPositions[i].y; + itemSprites[i].drawPriority = 15; + itemSprites[i].drawPriority2 = 8; + itemSprites[i].setBitmap(_gfx->loadBitmap(itemNames[i])); + } + + chooseMousePositionFromSprites(itemSprites, numItems, -1, 4); + bool displayMenu = true; + int lastItemIndex = -1; + + while (displayMenu) { + _sound->checkLoopMusic(); + + TrekEvent event; + if (!getNextEvent(&event)) + continue; + + switch (event.type) { + case TREKEVENT_TICK: { + Common::Point mousePos = _gfx->getMousePos(); + itemIndex = getMenuButtonAt(itemSprites, numItems, mousePos.x, mousePos.y); + if (itemIndex != lastItemIndex) { + if (lastItemIndex != -1) { + drawMenuButtonOutline(itemSprites[lastItemIndex].bitmap, 0); + itemSprites[lastItemIndex].bitmapChanged = true; + } + if (itemIndex != -1) { + drawMenuButtonOutline(itemSprites[itemIndex].bitmap, 15); + itemSprites[itemIndex].bitmapChanged = true; + } + lastItemIndex = itemIndex; + } + _gfx->drawAllSprites(); + break; + } + + case TREKEVENT_LBUTTONDOWN: +exitWithSelection: + displayMenu = false; + break; + + case TREKEVENT_RBUTTONDOWN: +exitWithoutSelection: + displayMenu = false; + lastItemIndex = -1; + break; + + case TREKEVENT_KEYDOWN: + switch (event.kbd.keycode) { + case Common::KEYCODE_ESCAPE: + case Common::KEYCODE_F2: + goto exitWithoutSelection; + + case Common::KEYCODE_RETURN: + case Common::KEYCODE_KP_ENTER: + case Common::KEYCODE_F1: + goto exitWithSelection; + + case Common::KEYCODE_HOME: + case Common::KEYCODE_KP7: + chooseMousePositionFromSprites(itemSprites, numItems, lastItemIndex, 4); + break; + + case Common::KEYCODE_UP: + case Common::KEYCODE_KP8: + case Common::KEYCODE_PAGEUP: + case Common::KEYCODE_KP9: + chooseMousePositionFromSprites(itemSprites, numItems, lastItemIndex, 2); + break; + + case Common::KEYCODE_LEFT: + case Common::KEYCODE_KP4: + chooseMousePositionFromSprites(itemSprites, numItems, lastItemIndex, 1); + break; + + case Common::KEYCODE_RIGHT: + case Common::KEYCODE_KP6: + chooseMousePositionFromSprites(itemSprites, numItems, lastItemIndex, 0); + break; + + case Common::KEYCODE_END: + case Common::KEYCODE_KP1: + chooseMousePositionFromSprites(itemSprites, numItems, lastItemIndex, 5); + break; + + case Common::KEYCODE_DOWN: + case Common::KEYCODE_KP2: + case Common::KEYCODE_PAGEDOWN: + case Common::KEYCODE_KP3: + chooseMousePositionFromSprites(itemSprites, numItems, lastItemIndex, 3); + break; + + default: + break; + } + break; + + default: + break; + } + + removeNextEvent(); + } + + playSoundEffectIndex(0x10); + if (lastItemIndex >= 0) + drawMenuButtonOutline(itemSprites[lastItemIndex].bitmap, 0); + + for (int i = 0; i < numItems; i++) + itemSprites[i].dontDrawNextFrame(); + + _gfx->drawAllSprites(); + + for (int i = 0; i < numItems; i++) { + itemSprites[i].bitmap.reset(); + _gfx->delSprite(&itemSprites[i]); + } + + if (lastItemIndex >= 0) { + lastItemIndex = itemIndices[lastItemIndex]; + } + + if (restoreMouse) + _gfx->warpMouse(oldMousePos.x, oldMousePos.y); + + _keyboardControlsMouse = keyboardControlledMouse; + return lastItemIndex; +} + +void StarTrekEngine::initStarfieldSprite(Sprite *sprite, SharedPtr<Bitmap> bitmap, const Common::Rect &rect) { + sprite->setXYAndPriority(rect.left, rect.top, 0); + sprite->setBitmap(bitmap); + bitmap->xoffset = 0; + bitmap->yoffset = 0; + bitmap->width = rect.width(); + bitmap->height = rect.height(); + _gfx->addSprite(sprite); + sprite->drawMode = 1; +} + +SharedPtr<Bitmap> StarTrekEngine::scaleBitmap(SharedPtr<Bitmap> bitmap, Fixed8 scale) { + int scaledWidth = scale.multToInt(bitmap->width); + int scaledHeight = scale.multToInt(bitmap->height); + int origWidth = bitmap->width; + int origHeight = bitmap->height; + + if (scaledWidth < 1) + scaledWidth = 1; + if (scaledHeight < 1) + scaledHeight = 1; + + SharedPtr<Bitmap> scaledBitmap(new Bitmap(scaledWidth, scaledHeight)); + scaledBitmap->xoffset = scale.multToInt(bitmap->xoffset); + scaledBitmap->yoffset = scale.multToInt(bitmap->yoffset); + + // sub_344a5(scaledWidth, origWidth); + + origHeight--; + scaledHeight--; + + byte *src = bitmap->pixels; + byte *dest = scaledBitmap->pixels; + + if (scale <= 1.0) { + int16 var2e = 0; + uint16 var30 = scaledHeight << 1; + uint16 var32 = (scaledHeight - origHeight) << 1; + uint16 origRow = 0; + + while (origRow <= origHeight) { + if (var2e < 0) { + var2e += var30; + } else { + var2e += var32; + scaleBitmapRow(src, dest, origWidth, scaledWidth); + dest += scaledWidth; + } + + src += bitmap->width; + origRow++; + } + } else { + int16 var2e = (origHeight << 1) - scaledHeight; + uint16 var30 = origHeight << 1; + uint16 var32 = (origHeight - scaledHeight) << 1; + uint16 srcRowChanged = true; + origWidth = bitmap->width; + uint16 scaledRow = 0; + byte *rowData = new byte[scaledWidth]; + + while (scaledRow++ <= scaledHeight) { + if (srcRowChanged) { + scaleBitmapRow(src, rowData, origWidth, scaledWidth); + srcRowChanged = false; + } + + memcpy(dest, rowData, scaledWidth); + dest += scaledWidth; + + if (var2e < 0) { + var2e += var30; + } else { + var2e += var32; + src += origWidth; + srcRowChanged = true; + } + } + + delete[] rowData; + } + + return scaledBitmap; +} + +void StarTrekEngine::scaleBitmapRow(byte *src, byte *dest, uint16 origWidth, uint16 scaledWidth) { + if (origWidth >= scaledWidth) { + int16 var2 = (scaledWidth << 1) - origWidth; + uint16 var4 = scaledWidth << 1; + uint16 var6 = (scaledWidth - origWidth) << 1; + uint16 varE = 0; + uint16 varA = 0; + uint16 var8 = origWidth; + uint16 di = 0; + + while (var8-- != 0) { + if (var2 < 0) { + var2 += var4; + } else { + var2 += var6; + if (di != 0) { + if (varE != 0) { + *(dest - 1) = *src++; + varE = 0; + di--; + } + src += di; + di = 0; + } + *dest++ = *src; + varE = 1; + } + + di++; + varA++; + } + } else { + int16 var2 = ((origWidth - 1) << 1) - (scaledWidth - 1); + uint16 var4 = (origWidth - 1) << 1; + uint16 var6 = ((origWidth - 1) - (scaledWidth - 1)) << 1; + uint16 varA = 0; + uint16 var8 = scaledWidth; + uint16 di = 0; + + while (var8-- != 0) { + if (di != 0) { + src += di; + di = 0; + } + *dest++ = *src; + + if (var2 < 0) + var2 += var4; + else { + var2 += var6; + di++; + } + + varA++; + } + } +} + +} // End of namespace StarTrek |