scummvm/engines/startrek/actors.cpp

1458 lines
40 KiB
C++

/* 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/resource.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] = _resource->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);
actor->bitmapFilename = animFrameFilename;
actor->bitmapFilename.trim();
sprite->setBitmap(loadAnimationFrame(actor->bitmapFilename, actor->scale));
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) {
delete sprite->bitmap;
sprite->bitmap = nullptr;
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);
}
delete actor->sprite.bitmap;
actor->sprite.bitmap = nullptr;
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.contains("stnd") /* && word_45d20 == -1 */) // TODO
animFilename += 'j';
actor->animFilename = _animName;
actor->animType = 2;
actor->animFile = SharedPtr<Common::MemoryReadStreamEndian>(_resource->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);
actor->bitmapFilename = firstFrameFilename;
actor->bitmapFilename.trim();
sprite->setBitmap(loadAnimationFrame(actor->bitmapFilename, scale));
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->animFile.reset();
// Fall through
case 1:
delete actor->sprite.bitmap;
actor->sprite.bitmap = nullptr;
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;
bool isDemo = getFeatures() & GF_DEMO;
char basename[5];
strncpy(basename, filename.c_str() + 1, 4);
basename[4] = '\0';
char mcCoyChar = !isDemo ? 'm' : 'b';
char c = filename[0];
if ((strcmp(basename, "stnd") == 0 || strcmp(basename, "tele") == 0)
&& (c == mcCoyChar || c == 's' || c == 'k' || c == 'r')) {
if (c == mcCoyChar) {
// Mccoy has the "base" animations for all crewmen
bitmapToReturn = new Bitmap(_resource->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(mcCoyChar, 0);
if (isDemo && mccoyFilename.hasPrefix("bstnds"))
mccoyFilename.setChar('m', 0);
Bitmap *bitmap = new Bitmap(_resource->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
case 'b': // McCoy (demo)
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
if (!isDemo) {
Common::MemoryReadStreamEndian *xorFile = _resource->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;
}
delete bitmap;
}
}
} else {
// TODO: when loading a bitmap, it passes a different argument than is standard to
// the "file loading with cache" function...
bitmapToReturn = new Bitmap(_resource->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) {
bool isDemo = getFeatures() & GF_DEMO;
const char *crewmanChars = !isDemo ? "ksmr" : "ksbr"; // Kirk, Spock, McCoy (Bones), RedShirt
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(_resource->loadBitmapFile(itemFilename));
_inventoryIconSprite.pos.x = 46;
}
_gfx->addSprite(&_inventoryIconSprite);
_inventoryIconSprite.pos.y = 10;
_inventoryIconSprite.drawMode = 2;
_inventoryIconSprite.drawPriority = 15;
_inventoryIconSprite.drawPriority2 = 8;
_inventoryIconSprite.setBitmap(_resource->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;
delete _itemIconSprite.bitmap;
_itemIconSprite.bitmap = nullptr;
}
if (_inventoryIconSprite.drawMode == 2) {
_gfx->delSprite(&_inventoryIconSprite);
_inventoryIconSprite.drawMode = 0;
delete _inventoryIconSprite.bitmap;
_inventoryIconSprite.bitmap = nullptr;
}
}
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(_resource->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, 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:
displayMenu = false;
break;
case TREKEVENT_RBUTTONDOWN:
displayMenu = false;
lastItemIndex = -1;
break;
case TREKEVENT_KEYDOWN:
switch (event.kbd.keycode) {
case Common::KEYCODE_ESCAPE:
case Common::KEYCODE_F2:
displayMenu = false;
lastItemIndex = -1;
break;
case Common::KEYCODE_RETURN:
case Common::KEYCODE_KP_ENTER:
case Common::KEYCODE_F1:
displayMenu = false;
break;
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, 0);
for (int i = 0; i < numItems; i++)
itemSprites[i].dontDrawNextFrame();
_gfx->drawAllSprites();
for (int i = 0; i < numItems; i++) {
delete itemSprites[i].bitmap;
itemSprites[i].bitmap = nullptr;
_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