/* 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 "common/stream.h" #include "common/memstream.h" #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++) { delete _banFiles[i]; _banFiles[i] = nullptr; } _kirkActor->animationString = "kstnd"; _spockActor->animationString = "sstnd"; _mccoyActor->animationString = "mstnd"; _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); if (actorIndex == -1) { // TODO warning("loadActorAnim: actor == -1"); } else { Actor *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; actor->animationString2 = animFile; 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. 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.empty()) 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)); actor->bitmapFilename = animFrameFilename; 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.deleteLastChar(); 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.deleteLastChar(); 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) renderBan(screenPixels, bgPixels, i); } _gfx->unlockScreenPixels(); } void StarTrekEngine::renderBan(byte *screenPixels, byte *bgPixels, int banFileIndex) { Common::MemoryReadStreamEndian *banFile = _banFiles[banFileIndex]; banFile->seek(_banFileOffsets[banFileIndex], SEEK_SET); uint16 offset = banFile->readUint16(); int32 size = banFile->readUint16(); byte *dest1 = screenPixels + offset; byte *dest2 = bgPixels + offset; // Skip 8 bytes (rectangle encompassing the area being drawn to) banFile->skip(8); while (--size >= 0) { assert(dest1 >= screenPixels && dest1 < screenPixels + SCREEN_WIDTH * SCREEN_HEIGHT); assert(dest2 >= bgPixels && dest2 < bgPixels + SCREEN_WIDTH * SCREEN_HEIGHT); int8 b = banFile->readByte(); if (b == -128) { // Add value to destination (usually jumping to next row) uint16 skip = banFile->readUint16(); dest1 += skip; dest2 += skip; } else if (b < 0) { // Repeated byte byte c = banFile->readByte(); if (c == 0) { dest1 += (-b) + 1; dest2 += (-b) + 1; } else { for (int j = 0; j < (-b) + 1; j++) { (*dest1++) = c; (*dest2++) = c; } } } else { // List of bytes b++; while (b-- != 0) { byte c = banFile->readByte(); if (c == 0) { dest1++; dest2++; } else { *(dest1++) = c; *(dest2++) = 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++) { delete _banFiles[i]; _banFiles[i] = nullptr; } } 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'; actor->animFilename = _animName; actor->animType = 2; actor->animFile = SharedPtr(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)); actor->bitmapFilename = firstFrameFilename; 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 = actor->animationString + (char)actor->direction; else // Default to facing south animName = 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.c_str(), actor->field92 & 7); actor->sprite.setBitmap(loadAnimationFrame(animName, actor->scale)); actor->bitmapFilename = animName; 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 += 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 += 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) && _room->isPointInPolygon(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 (_room->isPointInPolygon(offset, x, y)) { int actorIndex = _room->readRdfWord(offset); return actorIndex; } int numVertices = _room->readRdfWord(offset + 2); offset = offset + 4 + numVertices * 4; } } return -1; } Bitmap *StarTrekEngine::loadAnimationFrame(const Common::String &filename, Fixed8 scale) { Bitmap *bitmapToReturn = nullptr; 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 = new Bitmap(loadBitmapFile(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 = new Bitmap(loadBitmapFile(filename + ".$bm")); if (bitmapToReturn == nullptr) { Common::String mccoyFilename = filename; mccoyFilename.setChar('m', 0); Bitmap *bitmap = new Bitmap(loadBitmapFile(mccoyFilename)); uint16 width = bitmap->width; uint16 height = bitmap->height; bitmapToReturn = 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; case 'm': // McCoy colorShift = 0; break; default: 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 Common::MemoryReadStreamEndian *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); } delete xorFile; } } } else { // TODO: when loading a bitmap, it passes a different argument than is standard to // the "file loading with cache" function... bitmapToReturn = new Bitmap(loadBitmapFile(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(loadBitmapFile(itemFilename)); _inventoryIconSprite.pos.x = 46; } _gfx->addSprite(&_inventoryIconSprite); _inventoryIconSprite.pos.y = 10; _inventoryIconSprite.drawMode = 2; _inventoryIconSprite.drawPriority = 15; _inventoryIconSprite.drawPriority2 = 8; _inventoryIconSprite.setBitmap(loadBitmapFile("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(loadBitmapFile(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.get(), 0); itemSprites[lastItemIndex].bitmapChanged = true; } if (itemIndex != -1) { drawMenuButtonOutline(itemSprites[itemIndex].bitmap.get(), 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(); } _sound->playSoundEffectIndex(0x10); if (lastItemIndex >= 0) drawMenuButtonOutline(itemSprites[lastItemIndex].bitmap.get(), 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, 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; } Bitmap *StarTrekEngine::scaleBitmap(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; 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; } delete bitmap; 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