1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11 
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16 
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "common/stream.h"
24 #include "common/memstream.h"
25 
26 #include "startrek/iwfile.h"
27 #include "startrek/resource.h"
28 #include "startrek/room.h"
29 #include "startrek/startrek.h"
30 
31 namespace StarTrek {
32 
initActors()33 void StarTrekEngine::initActors() {
34 	for (int i = 0; i < NUM_ACTORS; i++)
35 		_actorList[i] = Actor();
36 
37 	for (int i = 0; i < MAX_BAN_FILES; i++) {
38 		delete _banFiles[i];
39 		_banFiles[i] = nullptr;
40 	}
41 
42 	_kirkActor->animationString = "kstnd";
43 	_spockActor->animationString = "sstnd";
44 	_mccoyActor->animationString = "mstnd";
45 	_redshirtActor->animationString = "rstnd";
46 }
47 
loadActorAnim(int actorIndex,const Common::String & animName,int16 x,int16 y,Fixed8 scale)48 int StarTrekEngine::loadActorAnim(int actorIndex, const Common::String &animName, int16 x, int16 y, Fixed8 scale) {
49 	debugC(6, kDebugGraphics, "Load animation '%s' on actor %d", animName.c_str(), actorIndex);
50 
51 	if (actorIndex == -1) {
52 		bool foundSlot = false;
53 
54 		for (int i = 8; i < NUM_ACTORS; i++) {
55 			if (_actorList[i].spriteDrawn == 0) {
56 				actorIndex = i;
57 				foundSlot = true;
58 				break;
59 			}
60 		}
61 
62 		if (!foundSlot) {
63 			error("All animations are in use");
64 		}
65 	}
66 
67 	Actor *actor = &_actorList[actorIndex];
68 
69 	if (actor->spriteDrawn) {
70 		releaseAnim(actor);
71 		drawActorToScreen(actor, animName, x, y, scale, false);
72 	} else {
73 		drawActorToScreen(actor, animName, x, y, scale, true);
74 	}
75 
76 	actor->triggerActionWhenAnimFinished = false;
77 	actor->finishedAnimActionParam = 0;
78 
79 	return actorIndex;
80 }
81 
loadBanFile(const Common::String & name)82 void StarTrekEngine::loadBanFile(const Common::String &name) {
83 	debugC(kDebugGeneral, 7, "Load BAN file: %s.ban", name.c_str());
84 	for (int i = 0; i < MAX_BAN_FILES; i++) {
85 		if (!_banFiles[i]) {
86 			_banFiles[i] = _resource->loadFile(name + ".ban");
87 			_banFileOffsets[i] = 0;
88 			return;
89 		}
90 	}
91 
92 	warning("Couldn't load .BAN file \"%s.ban\"", name.c_str());
93 }
94 
actorWalkToPosition(int actorIndex,const Common::String & animFile,int16 srcX,int16 srcY,int16 destX,int16 destY)95 bool StarTrekEngine::actorWalkToPosition(int actorIndex, const Common::String &animFile, int16 srcX, int16 srcY, int16 destX, int16 destY) {
96 	debugC(6, "Obj %d: walk from (%d,%d) to (%d,%d)", actorIndex, srcX, srcY, destX, destY);
97 
98 	Actor *actor = &_actorList[actorIndex];
99 
100 	actor->triggerActionWhenAnimFinished = false;
101 	if (isPositionSolid(destX, destY))
102 		return false;
103 
104 	if (actor->spriteDrawn)
105 		releaseAnim(actor);
106 	else
107 		_gfx->addSprite(&actor->sprite);
108 
109 	actor->spriteDrawn = true;
110 	actor->animType = 1;
111 	actor->frameToStartNextAnim = _frameIndex + 1;
112 	actor->animationString2 = animFile;
113 
114 	actor->dest.x = destX;
115 	actor->dest.y = destY;
116 	actor->field92 = 0;
117 	actor->triggerActionWhenAnimFinished = false;
118 
119 	actor->iwDestPosition = -1;
120 	actor->iwSrcPosition = -1;
121 
122 	if (directPathExists(srcX, srcY, destX, destY)) {
123 		chooseActorDirectionForWalking(actor, srcX, srcY, destX, destY);
124 		updateActorPositionWhileWalking(actor, (actor->granularPosX + 0.5).toInt(), (actor->granularPosY + 0.5).toInt());
125 		return true;
126 	} else {
127 		actor->iwSrcPosition = _iwFile->getClosestKeyPosition(srcX, srcY);
128 		actor->iwDestPosition = _iwFile->getClosestKeyPosition(destX, destY);
129 
130 		if (actor->iwSrcPosition == -1 || actor->iwDestPosition == -1) {
131 			// No path exists; face south by default.
132 			actor->animationString2 += "S";
133 			actor->direction = 'S';
134 
135 			updateActorPositionWhileWalking(actor, srcX, srcY);
136 			initStandAnim(actorIndex);
137 
138 			return false;
139 		} else {
140 			Common::Point iwSrc = _iwFile->_keyPositions[actor->iwSrcPosition];
141 			chooseActorDirectionForWalking(actor, srcX, srcY, iwSrc.x, iwSrc.y);
142 			updateActorPositionWhileWalking(actor, (actor->granularPosX + 0.5).toInt(), (actor->granularPosY + 0.5).toInt());
143 			return true;
144 		}
145 	}
146 }
147 
updateActorAnimations()148 void StarTrekEngine::updateActorAnimations() {
149 	for (int i = 0; i < NUM_ACTORS; i++) {
150 		Actor *actor = &_actorList[i];
151 		if (!actor->spriteDrawn)
152 			continue;
153 
154 		switch (actor->animType) {
155 		case 0: // Not walking?
156 		case 2:
157 			if (_frameIndex >= actor->frameToStartNextAnim) {
158 				int nextAnimIndex = getRandomWord() & 3;
159 				actor->animFile->seek(18 + nextAnimIndex + actor->animFrame * 22, SEEK_SET);
160 				byte nextAnimFrame = actor->animFile->readByte();
161 
162 				if (actor->animFrame != nextAnimFrame) {
163 					if (nextAnimFrame == actor->numAnimFrames - 1) {
164 						actor->field62++;
165 						if (actor->triggerActionWhenAnimFinished) {
166 							addAction(ACTION_FINISHED_ANIMATION, actor->finishedAnimActionParam, 0, 0);
167 						}
168 					}
169 				}
170 
171 				actor->animFrame = nextAnimFrame;
172 				if (actor->animFrame >= actor->numAnimFrames) {
173 					if (actor->animationString.empty())
174 						removeActorFromScreen(i);
175 					else
176 						initStandAnim(i);
177 				} else {
178 					Sprite *sprite = &actor->sprite;
179 
180 					actor->animFile->seek(actor->animFrame * 22, SEEK_SET);
181 					char animFrameFilename[16];
182 					actor->animFile->read(animFrameFilename, 16);
183 					actor->bitmapFilename = animFrameFilename;
184 					actor->bitmapFilename.trim();
185 					if (actor->bitmapFilename.contains(' '))
186 						actor->bitmapFilename = actor->bitmapFilename.substr(0, actor->bitmapFilename.find(' '));
187 
188 					sprite->setBitmap(loadAnimationFrame(actor->bitmapFilename, actor->scale));
189 
190 					actor->animFile->seek(10 + actor->animFrame * 22, SEEK_SET);
191 					uint16 xOffset = actor->animFile->readUint16();
192 					uint16 yOffset = actor->animFile->readUint16();
193 					uint16 basePriority = actor->animFile->readUint16();
194 					uint16 frames = actor->animFile->readUint16();
195 
196 					sprite->pos.x = xOffset + actor->pos.x;
197 					sprite->pos.y = yOffset + actor->pos.y;
198 					sprite->drawPriority = _gfx->getPriValue(0, yOffset + actor->pos.y) + basePriority;
199 					sprite->bitmapChanged = true;
200 
201 					actor->frameToStartNextAnim = frames + _frameIndex;
202 				}
203 			}
204 			break;
205 		case 1: // Walking
206 			if (_frameIndex < actor->frameToStartNextAnim)
207 				break;
208 			if (i == 0) // Kirk only
209 				checkTouchedLoadingZone(actor->pos.x, actor->pos.y);
210 			if (actor->field90 != 0) {
211 				Sprite *sprite = &actor->sprite;
212 				int loops;
213 				if (getActorScaleAtPosition((actor->granularPosY + 0.5).toInt()) < 0.625)
214 					loops = 1;
215 				else
216 					loops = 2;
217 				for (int k = 0; k < loops; k++) {
218 					if (actor->field90 == 0)
219 						break;
220 					actor->field90--;
221 					Fixed16 newX = actor->granularPosX + actor->speedX;
222 					Fixed16 newY = actor->granularPosY + actor->speedY;
223 					if ((actor->field90 & 3) == 0) {
224 						delete sprite->bitmap;
225 						sprite->bitmap = nullptr;
226 						updateActorPositionWhileWalking(actor, (newX + 0.5).toInt(), (newY + 0.5).toInt());
227 						actor->field92++;
228 					}
229 
230 					actor->granularPosX = newX;
231 					actor->granularPosY = newY;
232 					actor->frameToStartNextAnim = _frameIndex;
233 				}
234 			} else { // actor->field90 == 0
235 				if (actor->iwSrcPosition == -1) {
236 					if (actor->triggerActionWhenAnimFinished) {
237 						actor->triggerActionWhenAnimFinished = false;
238 						addAction(ACTION_FINISHED_WALKING, actor->finishedAnimActionParam & 0xff, 0, 0);
239 					}
240 
241 					delete actor->sprite.bitmap;
242 					actor->sprite.bitmap = nullptr;
243 					updateActorPositionWhileWalking(actor, (actor->granularPosX + 0.5).toInt(), (actor->granularPosY + 0.5).toInt());
244 					initStandAnim(i);
245 				} else { // actor->iwSrcPosition != -1
246 					if (actor->iwSrcPosition == actor->iwDestPosition) {
247 						actor->animationString2.deleteLastChar();
248 						actor->iwDestPosition = -1;
249 						actor->iwSrcPosition = -1;
250 						chooseActorDirectionForWalking(actor, actor->pos.x, actor->pos.y, actor->dest.x, actor->dest.y);
251 					} else {
252 						int index = _iwFile->_iwEntries[actor->iwSrcPosition][actor->iwDestPosition];
253 						actor->iwSrcPosition = index;
254 						Common::Point dest = _iwFile->_keyPositions[actor->iwSrcPosition];
255 						actor->animationString2.deleteLastChar();
256 						chooseActorDirectionForWalking(actor, actor->pos.x, actor->pos.y, dest.x, dest.y);
257 					}
258 				}
259 			}
260 			break;
261 		default:
262 			error("Invalid anim type.");
263 			break;
264 		}
265 	}
266 }
267 
renderBanBelowSprites()268 void StarTrekEngine::renderBanBelowSprites() {
269 	if ((_frameIndex & 3) != 0)
270 		return;
271 
272 	byte *screenPixels = _gfx->lockScreenPixels();
273 	byte *bgPixels = _gfx->getBackgroundPixels();
274 
275 	for (int i = 0; i < MAX_BAN_FILES; i++) {
276 		if (!_banFiles[i])
277 			continue;
278 
279 		// TODO: video modes other than VGA
280 
281 		_banFiles[i]->seek(_banFileOffsets[i], SEEK_SET);
282 		uint16 offset = _banFiles[i]->readUint16();
283 
284 		if (offset == 0xffff) {
285 			_banFileOffsets[i] = 0;
286 			_banFiles[i]->seek(0, SEEK_SET);
287 			offset = _banFiles[i]->readSint16();
288 		}
289 
290 		int16 size = _banFiles[i]->readSint16();
291 		if (size != 0)
292 			renderBan(screenPixels, bgPixels, i);
293 	}
294 
295 	_gfx->unlockScreenPixels();
296 }
297 
renderBan(byte * screenPixels,byte * bgPixels,int banFileIndex)298 void StarTrekEngine::renderBan(byte *screenPixels, byte *bgPixels, int banFileIndex) {
299 	Common::MemoryReadStreamEndian *banFile = _banFiles[banFileIndex];
300 	banFile->seek(_banFileOffsets[banFileIndex], SEEK_SET);
301 
302 	uint16 offset = banFile->readUint16();
303 	int32 size = banFile->readUint16();
304 
305 	byte *dest1 = screenPixels + offset;
306 	byte *dest2 = bgPixels + offset;
307 
308 	// Skip 8 bytes (rectangle encompassing the area being drawn to)
309 	banFile->skip(8);
310 
311 	while (--size >= 0) {
312 		assert(dest1 >= screenPixels && dest1 < screenPixels + SCREEN_WIDTH * SCREEN_HEIGHT);
313 		assert(dest2 >= bgPixels && dest2 < bgPixels + SCREEN_WIDTH * SCREEN_HEIGHT);
314 
315 		int8 b = banFile->readByte();
316 
317 		if (b == -128) {	// Add value to destination (usually jumping to next row)
318 			uint16 skip = banFile->readUint16();
319 			dest1 += skip;
320 			dest2 += skip;
321 		} else if (b < 0) { // Repeated byte
322 			byte c = banFile->readByte();
323 			if (c == 0) {
324 				dest1 += (-b) + 1;
325 				dest2 += (-b) + 1;
326 			} else {
327 				for (int j = 0; j < (-b) + 1; j++) {
328 					(*dest1++) = c;
329 					(*dest2++) = c;
330 				}
331 			}
332 		} else { // List of bytes
333 			b++;
334 			while (b-- != 0) {
335 				byte c = banFile->readByte();
336 				if (c == 0) {
337 					dest1++;
338 					dest2++;
339 				} else {
340 					*(dest1++) = c;
341 					*(dest2++) = c;
342 				}
343 			}
344 		}
345 	}
346 }
347 
renderBanAboveSprites()348 void StarTrekEngine::renderBanAboveSprites() {
349 	if ((_frameIndex & 3) != 0)
350 		return;
351 
352 	for (int i = 0; i < MAX_BAN_FILES; i++) {
353 		if (!_banFiles[i])
354 			continue;
355 
356 		_banFiles[i]->seek(_banFileOffsets[i], SEEK_SET);
357 		uint16 offset = _banFiles[i]->readUint16();
358 
359 		if (offset == 0xffff) {
360 			_banFileOffsets[i] = 0;
361 			_banFiles[i]->seek(0, SEEK_SET);
362 			offset = _banFiles[i]->readSint16();
363 		}
364 
365 		int16 size = _banFiles[i]->readSint16();
366 		if (size != 0) {
367 			Common::Rect rect;
368 			rect.left   = _banFiles[i]->readSint16();
369 			rect.top    = _banFiles[i]->readSint16();
370 			rect.right  = _banFiles[i]->readSint16() + 1;
371 			rect.bottom = _banFiles[i]->readSint16() + 1;
372 
373 			// Draw all sprites in this rectangle to a custom surface, and only update the
374 			// specific pixels that were updated by the BAN file this frame.
375 			// Rationale behind this is that, since the background may not have been
376 			// redrawn, the transparent sprites (ie. textboxes) would further darken any
377 			// pixels behind them that haven't been updated this frame. So, we can't just
378 			// update everything in this rectangle.
379 			// FIXME: This copies the entire screen surface for temporary drawing, which
380 			// is somewhat wasteful. Original game had one more graphics layer it drew to
381 			// before the screen was updated...
382 			::Graphics::Surface surface;
383 			_gfx->drawAllSpritesInRectToSurface(rect, &surface);
384 
385 			byte *destPixels = _gfx->lockScreenPixels();
386 			byte *src = (byte *)surface.getPixels() + offset;
387 			byte *dest = destPixels + offset;
388 
389 			// This is similar to renderBan(), except it copies pixels from the surface
390 			// above instead of drawing directly to it. (Important since sprites may be
391 			// drawn on top.)
392 			while (--size >= 0) {
393 				assert(dest >= destPixels && dest < destPixels + SCREEN_WIDTH * SCREEN_HEIGHT);
394 				int8 b = _banFiles[i]->readByte();
395 				if (b == -128) {
396 					uint16 skip = _banFiles[i]->readUint16();
397 					dest += skip;
398 					src  += skip;
399 				} else if (b < 0) {
400 					byte c = _banFiles[i]->readByte();
401 					if (c == 0) {
402 						dest += (-b) + 1;
403 						src  += (-b) + 1;
404 					}
405 					else {
406 						for (int j = 0; j < (-b) + 1; j++)
407 							*(dest++) = *(src++);
408 					}
409 				} else {
410 					b++;
411 					while (b-- != 0) {
412 						byte c = _banFiles[i]->readByte();
413 						if (c == 0) {
414 							dest++;
415 							src++;
416 						} else
417 							*(dest++) = *(src++);
418 					}
419 				}
420 			}
421 
422 			_gfx->unlockScreenPixels();
423 			surface.free();
424 
425 			_banFileOffsets[i] = _banFiles[i]->pos();
426 		}
427 	}
428 }
429 
removeActorFromScreen(int actorIndex)430 void StarTrekEngine::removeActorFromScreen(int actorIndex) {
431 	Actor *actor = &_actorList[actorIndex];
432 
433 	if (actor->spriteDrawn != 1)
434 		return;
435 
436 	debugC(6, kDebugGraphics, "Stop drawing actor %d", actorIndex);
437 
438 	Sprite *sprite = &actor->sprite;
439 	sprite->field16 = true;
440 	sprite->bitmapChanged = true;
441 	_gfx->drawAllSprites();
442 	_gfx->delSprite(sprite);
443 	releaseAnim(actor);
444 }
445 
removeDrawnActorsFromScreen()446 void StarTrekEngine::removeDrawnActorsFromScreen() {
447 	for (int i = 0; i < NUM_ACTORS; i++) {
448 		if (_actorList[i].spriteDrawn == 1) {
449 			removeActorFromScreen(i);
450 		}
451 	}
452 
453 	for (int i = 0; i < MAX_BAN_FILES; i++) {
454 		delete _banFiles[i];
455 		_banFiles[i] = nullptr;
456 	}
457 }
458 
drawActorToScreen(Actor * actor,const Common::String & _animName,int16 x,int16 y,Fixed8 scale,bool addSprite)459 void StarTrekEngine::drawActorToScreen(Actor *actor, const Common::String &_animName, int16 x, int16 y, Fixed8 scale, bool addSprite) {
460 	Common::String animFilename = _animName;
461 	if (_animName.hasPrefixIgnoreCase("stnd") /* && word_45d20 == -1 */) // TODO
462 		animFilename += 'j';
463 
464 	actor->animFilename = _animName;
465 	actor->animType = 2;
466 	actor->animFile = SharedPtr<Common::MemoryReadStreamEndian>(_resource->loadFile(animFilename + ".anm"));
467 	actor->numAnimFrames = actor->animFile->size() / 22;
468 	actor->animFrame = 0;
469 	actor->pos.x = x;
470 	actor->pos.y = y;
471 	actor->field62 = 0;
472 	actor->scale = scale;
473 
474 	actor->animFile->seek(16, SEEK_SET);
475 	actor->frameToStartNextAnim = actor->animFile->readUint16() + _frameIndex;
476 
477 	char firstFrameFilename[11];
478 	actor->animFile->seek(0, SEEK_SET);
479 	actor->animFile->read(firstFrameFilename, 10);
480 	firstFrameFilename[10] = '\0';
481 
482 	Sprite *sprite = &actor->sprite;
483 	if (addSprite)
484 		_gfx->addSprite(sprite);
485 
486 	actor->bitmapFilename = firstFrameFilename;
487 	actor->bitmapFilename.trim();
488 	sprite->setBitmap(loadAnimationFrame(actor->bitmapFilename, scale));
489 	actor->scale = scale;
490 	actor->animFile->seek(10, SEEK_SET);
491 
492 	uint16 xOffset = actor->animFile->readUint16();
493 	uint16 yOffset = actor->animFile->readUint16();
494 	uint16 basePriority = actor->animFile->readUint16();
495 
496 	sprite->pos.x = xOffset + actor->pos.x;
497 	sprite->pos.y = yOffset + actor->pos.y;
498 	sprite->drawPriority = _gfx->getPriValue(0, yOffset + actor->pos.y) + basePriority;
499 	sprite->bitmapChanged = true;
500 
501 	actor->spriteDrawn = 1;
502 }
503 
releaseAnim(Actor * actor)504 void StarTrekEngine::releaseAnim(Actor *actor) {
505 	switch (actor->animType) {
506 	case 0:
507 	case 2:
508 		actor->animFile.reset();
509 		// Fall through
510 	case 1:
511 		delete actor->sprite.bitmap;
512 		actor->sprite.bitmap = nullptr;
513 		break;
514 	default:
515 		error("Invalid anim type");
516 		break;
517 	}
518 
519 	actor->spriteDrawn = 0;
520 }
521 
initStandAnim(int actorIndex)522 void StarTrekEngine::initStandAnim(int actorIndex) {
523 	Actor *actor = &_actorList[actorIndex];
524 
525 	if (!actor->spriteDrawn)
526 		error("initStandAnim: dead anim");
527 
528 	////////////////////
529 	// sub_239d2
530 	const char *directions = "nsew";
531 
532 	if (actorIndex >= 0 && actorIndex <= 3) {
533 		int8 dir = _awayMission.crewDirectionsAfterWalk[actorIndex];
534 		if (dir != -1) {
535 			actor->direction = directions[dir];
536 			_awayMission.crewDirectionsAfterWalk[actorIndex] = -1;
537 		}
538 	}
539 	// end of sub_239d2
540 	////////////////////
541 
542 	Common::String animName;
543 	if (actor->direction != 0)
544 		animName = actor->animationString + (char)actor->direction;
545 	else // Default to facing south
546 		animName = actor->animationString + 's';
547 
548 	Fixed8 scale = getActorScaleAtPosition(actor->pos.y);
549 	loadActorAnim(actorIndex, animName, actor->pos.x, actor->pos.y, scale);
550 	actor->animType = 0;
551 }
552 
updateActorPositionWhileWalking(Actor * actor,int16 x,int16 y)553 void StarTrekEngine::updateActorPositionWhileWalking(Actor *actor, int16 x, int16 y) {
554 	actor->scale = getActorScaleAtPosition(y);
555 	Common::String animName = Common::String::format("%s%02d", actor->animationString2.c_str(), actor->field92 & 7);
556 	actor->sprite.setBitmap(loadAnimationFrame(animName, actor->scale));
557 	actor->bitmapFilename = animName;
558 
559 	Sprite *sprite = &actor->sprite;
560 	sprite->drawPriority = _gfx->getPriValue(0, y);
561 	sprite->pos.x = x;
562 	sprite->pos.y = y;
563 	sprite->bitmapChanged = true;
564 
565 	actor->frameToStartNextAnim = _frameIndex;
566 	actor->pos.x = x;
567 	actor->pos.y = y;
568 }
569 
chooseActorDirectionForWalking(Actor * actor,int16 srcX,int16 srcY,int16 destX,int16 destY)570 void StarTrekEngine::chooseActorDirectionForWalking(Actor *actor, int16 srcX, int16 srcY, int16 destX, int16 destY) {
571 	actor->granularPosX = srcX;
572 	actor->granularPosY = srcY;
573 
574 	int16 distX = destX - srcX;
575 	int16 distY = destY - srcY;
576 	int16 absDistX = abs(distX);
577 	int16 absDistY = abs(distY);
578 
579 	if (absDistX > absDistY) {
580 		char d;
581 		if (distX > 0)
582 			d = 'E';
583 		else
584 			d = 'W';
585 
586 		// Append direction to animation string
587 		actor->animationString2 += d;
588 
589 		actor->direction = d;
590 		actor->field90 = absDistX;
591 
592 		if (distX != 0) {
593 			if (distX > 0)
594 				actor->speedX = 1.0;
595 			else
596 				actor->speedX = -1.0;
597 
598 			actor->speedY = Fixed16(distY) / absDistX;
599 		}
600 	} else {
601 		char d;
602 		if (distY > 0)
603 			d = 'S';
604 		else
605 			d = 'N';
606 
607 		// Append direction to animation string
608 		actor->animationString2 += d;
609 
610 		actor->direction = d;
611 		actor->field90 = absDistY;
612 
613 		if (distY != 0) {
614 			if (distY > 0)
615 				actor->speedY = 1.0;
616 			else
617 				actor->speedY = -1.0;
618 
619 			actor->speedX = Fixed16(distX) / absDistY;
620 		}
621 	}
622 }
623 
directPathExists(int16 srcX,int16 srcY,int16 destX,int16 destY)624 bool StarTrekEngine::directPathExists(int16 srcX, int16 srcY, int16 destX, int16 destY) {
625 	int32 distX = destX - srcX;
626 	int32 distY = destY - srcY;
627 
628 	int32 absDistX = abs(distX);
629 	int32 absDistY = abs(distY);
630 
631 	int32 distCounter;
632 	Fixed16 speedX, speedY;
633 
634 	if (absDistX > absDistY) {
635 		distCounter = absDistX;
636 
637 		if (distCounter == 0)
638 			return true;
639 
640 		speedY = Fixed16(distY) / absDistX;
641 
642 		if (distX > 0)
643 			speedX = 1.0;
644 		else
645 			speedX = -1.0;
646 	} else { // absDistX <= absDistY
647 		distCounter = absDistY;
648 
649 		if (distCounter == 0)
650 			return true;
651 
652 		speedX = Fixed16(distX) / absDistY;
653 
654 		if (distY > 0)
655 			speedY = 1.0;
656 		else
657 			speedY = -1.0;
658 	}
659 
660 	Fixed16 fixedX = srcX;
661 	Fixed16 fixedY = srcY;
662 
663 	if (isPositionSolid((fixedX + 0.5).toInt(), (fixedY + 0.5).toInt()))
664 		return false;
665 
666 	while (distCounter-- > 0) {
667 		fixedX += speedX;
668 		fixedY += speedY;
669 
670 		if (isPositionSolid((fixedX + 0.5).toInt(), (fixedY + 0.5).toInt()))
671 			return false;
672 	}
673 
674 	return true;
675 }
676 
findObjectAt(int x,int y)677 int StarTrekEngine::findObjectAt(int x, int y) {
678 	Sprite *sprite = _gfx->getSpriteAt(x, y);
679 
680 	if (sprite != nullptr) {
681 		if (sprite == &_inventoryIconSprite)
682 			return OBJECT_INVENTORY_ICON;
683 		else if (sprite == &_itemIconSprite)
684 			return _awayMission.activeObject;
685 
686 		for (int i = 0; i < NUM_ACTORS; i++) {
687 			Actor *actor = &_actorList[i];
688 			if (sprite == &actor->sprite)
689 				return i;
690 		}
691 
692 		error("findObject: Clicked on an unknown sprite");
693 	}
694 
695 	_objectHasWalkPosition = false;
696 	int actionBit = 1 << (_awayMission.activeAction - 1);
697 	int offset = _room->getFirstHotspot();
698 
699 	while (offset != _room->getHotspotEnd()) {
700 		uint16 word = _room->readRdfWord(offset);
701 		if (word & 0x8000) {
702 			if ((word & actionBit) && _room->isPointInPolygon(offset + 6, x, y)) {
703 				int actorIndex = _room->readRdfWord(offset + 6);
704 				_objectHasWalkPosition = true;
705 				_objectWalkPosition.x = _room->readRdfWord(offset + 2);
706 				_objectWalkPosition.y = _room->readRdfWord(offset + 4);
707 				return actorIndex;
708 			}
709 
710 			int numVertices = _room->readRdfWord(offset + 8);
711 			offset = offset + 10 + numVertices * 4;
712 		} else {
713 			if (_room->isPointInPolygon(offset, x, y)) {
714 				int actorIndex = _room->readRdfWord(offset);
715 				return actorIndex;
716 			}
717 
718 			int numVertices = _room->readRdfWord(offset + 2);
719 			offset = offset + 4 + numVertices * 4;
720 		}
721 	}
722 
723 	return -1;
724 }
725 
loadAnimationFrame(const Common::String & filename,Fixed8 scale)726 Bitmap *StarTrekEngine::loadAnimationFrame(const Common::String &filename, Fixed8 scale) {
727 	Bitmap *bitmapToReturn = nullptr;
728 	bool isDemo = getFeatures() & GF_DEMO;
729 
730 	char basename[5];
731 	strncpy(basename, filename.c_str() + 1, 4);
732 	basename[4] = '\0';
733 
734 	char mcCoyChar = !isDemo ? 'm' : 'b';
735 
736 	char c = filename[0];
737 	if ((strcmp(basename, "stnd") == 0 || strcmp(basename, "tele") == 0)
738 	        && (c == mcCoyChar || c == 's' || c == 'k' || c == 'r')) {
739 		if (c == mcCoyChar) {
740 			// Mccoy has the "base" animations for all crewmen
741 			bitmapToReturn = new Bitmap(_resource->loadBitmapFile(filename));
742 		} else {
743 			// All crewman other than mccoy copy the animation frames from mccoy, change
744 			// the colors of the uniforms, and load an "xor" file to redraw the face.
745 
746 			// TODO: The ".$bm" extension is a "virtual file"? Caches the changes to the
747 			// file made here?
748 			// bitmapToReturn = new Bitmap(loadBitmapFile(filename + ".$bm"));
749 
750 			if (bitmapToReturn == nullptr) {
751 				Common::String mccoyFilename = filename;
752 				mccoyFilename.setChar(mcCoyChar, 0);
753 				if (isDemo && mccoyFilename.hasPrefix("bstnds"))
754 					mccoyFilename.setChar('m', 0);
755 				Bitmap *bitmap = new Bitmap(_resource->loadBitmapFile(mccoyFilename));
756 
757 				uint16 width = bitmap->width;
758 				uint16 height = bitmap->height;
759 
760 				bitmapToReturn = new Bitmap(width, height);
761 				bitmapToReturn->xoffset = bitmap->xoffset;
762 				bitmapToReturn->yoffset = bitmap->yoffset;
763 
764 				// Change uniform color
765 				int16 colorShift;
766 				switch (c) {
767 				case 'k': // Kirk
768 					colorShift = 8;
769 					break;
770 				case 'r': // Redshirt
771 					colorShift = -8;
772 					break;
773 				case 's': // Spock
774 					colorShift = 0;
775 					break;
776 				case 'm': // McCoy
777 				case 'b': // McCoy (demo)
778 					colorShift = 0;
779 					break;
780 				default:
781 					colorShift = 0;
782 					break;
783 				}
784 
785 				if (colorShift == 0) {
786 					memcpy(bitmapToReturn->pixels, bitmap->pixels, width * height);
787 				} else {
788 					byte *src = bitmap->pixels;
789 					byte *dest = bitmapToReturn->pixels;
790 					byte baseUniformColor = 0xa8;
791 
792 					for (int i = 0; i < width * height; i++) {
793 						byte b = *src++;
794 						if (b >= baseUniformColor && b < baseUniformColor + 8)
795 							*dest++ = b + colorShift;
796 						else
797 							*dest++ = b;
798 					}
799 				}
800 
801 				// Redraw face with XOR file
802 				if (!isDemo) {
803 					Common::MemoryReadStreamEndian *xorFile = _resource->loadFile(filename + ".xor");
804 					xorFile->seek(0, SEEK_SET);
805 					uint16 xoffset = bitmap->xoffset - xorFile->readUint16();
806 					uint16 yoffset = bitmap->yoffset - xorFile->readUint16();
807 					uint16 xorWidth = xorFile->readUint16();
808 					uint16 xorHeight = xorFile->readUint16();
809 
810 					byte *dest = bitmapToReturn->pixels + yoffset * bitmap->width + xoffset;
811 
812 					for (int i = 0; i < xorHeight; i++) {
813 						for (int j = 0; j < xorWidth; j++)
814 							*dest++ ^= xorFile->readByte();
815 						dest += (bitmap->width - xorWidth);
816 					}
817 
818 					delete xorFile;
819 				}
820 
821 				delete bitmap;
822 			}
823 		}
824 	} else {
825 		// TODO: when loading a bitmap, it passes a different argument than is standard to
826 		// the "file loading with cache" function...
827 		bitmapToReturn = new Bitmap(_resource->loadBitmapFile(filename));
828 	}
829 
830 	if (scale != 1.0) {
831 		bitmapToReturn = scaleBitmap(bitmapToReturn, scale);
832 	}
833 
834 	return bitmapToReturn;
835 }
836 
837 
selectObjectForUseAction()838 int StarTrekEngine::selectObjectForUseAction() {
839 	while (true) {
840 		if (!(_awayMission.crewDownBitset & (1 << OBJECT_KIRK)))
841 			showInventoryIcons(false);
842 
843 		TrekEvent event;
844 
845 		while (true) {
846 			if (!getNextEvent(&event))
847 				continue;
848 
849 			if (event.type == TREKEVENT_TICK) {
850 				updateMouseBitmap();
851 				_gfx->drawAllSprites();
852 				_sound->checkLoopMusic();
853 			} else if (event.type == TREKEVENT_LBUTTONDOWN) {
854 				removeNextEvent();
855 				break;
856 			} else if (event.type == TREKEVENT_MOUSEMOVE) {
857 			} else if (event.type == TREKEVENT_RBUTTONDOWN) {
858 				// Allow this to be processed by main away mission loop
859 				break;
860 			} else if (event.type == TREKEVENT_KEYDOWN) {
861 				if (event.kbd.keycode == Common::KEYCODE_ESCAPE
862 				        || event.kbd.keycode == Common::KEYCODE_w
863 				        || event.kbd.keycode == Common::KEYCODE_t
864 				        || event.kbd.keycode == Common::KEYCODE_u
865 				        || event.kbd.keycode == Common::KEYCODE_g
866 				        || event.kbd.keycode == Common::KEYCODE_l
867 				        || event.kbd.keycode == Common::KEYCODE_SPACE
868 				        || event.kbd.keycode == Common::KEYCODE_F2) {
869 					// Allow these buttons to be processed by main away mission loop
870 					break;
871 				} else if (event.kbd.keycode == Common::KEYCODE_i) {
872 					removeNextEvent();
873 					break;
874 				} else if (event.kbd.keycode == Common::KEYCODE_RETURN || event.kbd.keycode == Common::KEYCODE_KP_ENTER || event.kbd.keycode == Common::KEYCODE_F1) {
875 					// Simulate left-click
876 					removeNextEvent();
877 					event.type = TREKEVENT_LBUTTONDOWN;
878 					break;
879 				}
880 			}
881 
882 			removeNextEvent();
883 		}
884 
885 		if (event.type == TREKEVENT_KEYDOWN && event.kbd.keycode == Common::KEYCODE_i) {
886 			hideInventoryIcons();
887 			int clickedObject = showInventoryMenu(50, 50, true);
888 			if (clickedObject == -1)
889 				continue;
890 			return clickedObject;
891 		} else if (event.type == TREKEVENT_LBUTTONDOWN) {
892 			int clickedObject = findObjectAt(_gfx->getMousePos());
893 			hideInventoryIcons();
894 
895 			if (clickedObject == -1)
896 				continue;
897 			else if (isObjectUnusable(clickedObject, ACTION_USE))
898 				continue;
899 			else if (clickedObject == OBJECT_INVENTORY_ICON) {
900 				clickedObject = showInventoryMenu(50, 50, false);
901 				if (clickedObject == -1)
902 					continue;
903 				else
904 					return clickedObject;
905 			} else if (clickedObject <= OBJECT_REDSHIRT)
906 				return clickedObject;
907 			else if (isObjectUnusable(OBJECT_KIRK, ACTION_USE))
908 				continue;
909 			else if (_room->actionHasCode(ACTION_USE, OBJECT_KIRK, clickedObject, 0)
910 			         || _room->actionHasCode(ACTION_GET, clickedObject, 0, 0)
911 			         || _room->actionHasCode(ACTION_WALK, clickedObject, 0, 0)) {
912 				_awayMission.activeObject = OBJECT_KIRK;
913 				_awayMission.passiveObject = clickedObject;
914 				_awayMission.activeAction = ACTION_USE;
915 				clickedObject = OBJECT_KIRK;
916 				if (!walkActiveObjectToHotspot())
917 					addAction(_awayMission.activeAction, _awayMission.activeObject, _awayMission.passiveObject, 0);
918 				return clickedObject;
919 			} else
920 				continue;
921 		} else {
922 			hideInventoryIcons();
923 			return -1;
924 		}
925 	}
926 }
927 
getCrewmanAnimFilename(int actorIndex,const Common::String & basename)928 Common::String StarTrekEngine::getCrewmanAnimFilename(int actorIndex, const Common::String &basename) {
929 	bool isDemo = getFeatures() & GF_DEMO;
930 	const char *crewmanChars = !isDemo ? "ksmr" : "ksbr";	// Kirk, Spock, McCoy (Bones), RedShirt
931 	assert(actorIndex >= 0 && actorIndex < 4);
932 	return crewmanChars[actorIndex] + basename;
933 }
934 
updateMouseBitmap()935 void StarTrekEngine::updateMouseBitmap() {
936 	const bool worksOnCrewmen[] = { // True if the action reacts with crewmen
937 		false, // ACTION_WALK
938 		true,  // ACTION_USE
939 		false, // ACTION_GET
940 		true,  // ACTION_LOOK
941 		true   // ACTION_TALK
942 	};
943 	const bool worksOnActors[] = { // True if the action reacts with other objects
944 		false, // ACTION_WALK
945 		true,  // ACTION_USE
946 		true,  // ACTION_GET
947 		true,  // ACTION_LOOK
948 		true   // ACTION_TALK
949 	};
950 	const bool worksOnHotspots[] = { // True if the action reacts with hotspots
951 		false, // ACTION_WALK
952 		true,  // ACTION_USE
953 		true,  // ACTION_GET
954 		true,  // ACTION_LOOK
955 		false  // ACTION_TALK
956 	};
957 
958 	Common::Point mousePos = _gfx->getMousePos();
959 	int selected = findObjectAt(mousePos.x, mousePos.y);
960 	int action = _awayMission.activeAction;
961 	assert(action >= 1 && action <= 5);
962 
963 	bool withRedOutline;
964 
965 	if (selected >= 0 && selected <= 3 && worksOnCrewmen[action - 1])
966 		withRedOutline = true;
967 	else if (selected > 3 && selected < NUM_ACTORS && worksOnActors[action - 1])
968 		withRedOutline = true;
969 	else if (selected >= NUM_ACTORS && selected < HOTSPOTS_END && worksOnHotspots[action - 1])
970 		withRedOutline = true;
971 	else
972 		withRedOutline = false;
973 
974 	chooseMouseBitmapForAction(action, withRedOutline);
975 }
976 
walkActiveObjectToHotspot()977 bool StarTrekEngine::walkActiveObjectToHotspot() {
978 	if (!_objectHasWalkPosition)
979 		return false;
980 
981 	int objectIndex;
982 	if (_awayMission.activeAction != ACTION_USE)
983 		objectIndex = OBJECT_KIRK;
984 	else if (_awayMission.activeObject <= OBJECT_REDSHIRT)
985 		objectIndex = _awayMission.activeObject;
986 	else if (_awayMission.activeObject >= ITEMS_START && _awayMission.activeObject <= ITEMS_END) { // FIXME: "<= ITEMS_END" doesn't make sense?
987 		if (_awayMission.activeObject == OBJECT_ISTRICOR)
988 			objectIndex = OBJECT_SPOCK;
989 		else if (_awayMission.activeObject == OBJECT_IMTRICOR)
990 			objectIndex = OBJECT_MCCOY;
991 		else
992 			objectIndex = OBJECT_KIRK;
993 	} else // This is the original error message...
994 		error("Jay didn't think about pmcheck");
995 
996 	byte finishedAnimActionParam = false;
997 	bool walk = false;
998 
999 	if (_awayMission.activeAction == ACTION_WALK)
1000 		walk = true;
1001 	else {
1002 		// If this action has code defined for it in this room, buffer the action to be
1003 		// done after the object finished walking there.
1004 		Action action = {static_cast<int8>(_awayMission.activeAction), _awayMission.activeObject, 0, 0};
1005 		if (_awayMission.activeAction == ACTION_USE)
1006 			action.b2 = _awayMission.passiveObject;
1007 
1008 		if (_room->actionHasCode(action)) {
1009 			for (int i = 0; i < MAX_BUFFERED_WALK_ACTIONS; i++) {
1010 				if (!_actionOnWalkCompletionInUse[i]) {
1011 					finishedAnimActionParam = i + 0xe0;
1012 					_actionOnWalkCompletionInUse[i] = true;
1013 					_actionOnWalkCompletion[i] = action;
1014 					walk = true;
1015 					break;
1016 				}
1017 			}
1018 		}
1019 	}
1020 
1021 	if (walk) {
1022 		Actor *actor = &_actorList[objectIndex];
1023 		Common::String anim = getCrewmanAnimFilename(objectIndex, "walk");
1024 		actorWalkToPosition(objectIndex, anim, actor->pos.x, actor->pos.y, _objectWalkPosition.x, _objectWalkPosition.y);
1025 		if (finishedAnimActionParam != 0) {
1026 			actor->triggerActionWhenAnimFinished = true;
1027 			actor->finishedAnimActionParam = finishedAnimActionParam;
1028 		}
1029 		_objectHasWalkPosition = false;
1030 		return true;
1031 	} else {
1032 		_objectHasWalkPosition = false;
1033 		return false;
1034 	}
1035 }
1036 
showInventoryIcons(bool showItem)1037 void StarTrekEngine::showInventoryIcons(bool showItem) {
1038 	const char *crewmanFilenames[] = {
1039 		"ikirk",
1040 		"ispock",
1041 		"imccoy",
1042 		"iredshir"
1043 	};
1044 
1045 	Common::String itemFilename;
1046 
1047 	if (showItem) {
1048 		int i = _awayMission.activeObject;
1049 		if (i >= OBJECT_KIRK && i <= OBJECT_REDSHIRT)
1050 			itemFilename = crewmanFilenames[i];
1051 		else {
1052 			assert(i >= ITEMS_START && i < ITEMS_END);
1053 			Item *item = &_itemList[i - ITEMS_START];
1054 			itemFilename = item->name;
1055 		}
1056 	}
1057 
1058 	if (itemFilename.empty())
1059 		_inventoryIconSprite.pos.x = 10;
1060 	else {
1061 		_gfx->addSprite(&_itemIconSprite);
1062 		_itemIconSprite.drawMode = 2;
1063 		_itemIconSprite.pos.x = 10;
1064 		_itemIconSprite.pos.y = 10;
1065 		_itemIconSprite.drawPriority = 15;
1066 		_itemIconSprite.drawPriority2 = 8;
1067 		_itemIconSprite.setBitmap(_resource->loadBitmapFile(itemFilename));
1068 
1069 		_inventoryIconSprite.pos.x = 46;
1070 	}
1071 
1072 	_gfx->addSprite(&_inventoryIconSprite);
1073 
1074 	_inventoryIconSprite.pos.y = 10;
1075 	_inventoryIconSprite.drawMode = 2;
1076 	_inventoryIconSprite.drawPriority = 15;
1077 	_inventoryIconSprite.drawPriority2 = 8;
1078 	_inventoryIconSprite.setBitmap(_resource->loadBitmapFile("inv00"));
1079 }
1080 
isObjectUnusable(int object,int action)1081 bool StarTrekEngine::isObjectUnusable(int object, int action) {
1082 	if (action == ACTION_LOOK)
1083 		return false;
1084 	if (object == OBJECT_REDSHIRT && _awayMission.redshirtDead)
1085 		return true;
1086 	if (object >= OBJECT_KIRK && object <= OBJECT_REDSHIRT && (_awayMission.crewDownBitset & (1 << object)))
1087 		return true;
1088 	if (object == OBJECT_IMTRICOR && (_awayMission.crewDownBitset & (1 << OBJECT_MCCOY)))
1089 		return true;
1090 	if (object == OBJECT_ISTRICOR && (_awayMission.crewDownBitset & (1 << OBJECT_SPOCK)))
1091 		return true;
1092 	return false;
1093 }
1094 
hideInventoryIcons()1095 void StarTrekEngine::hideInventoryIcons() {
1096 	// Clear these sprites from the screen
1097 	if (_itemIconSprite.drawMode == 2)
1098 		_itemIconSprite.dontDrawNextFrame();
1099 	if (_inventoryIconSprite.drawMode == 2)
1100 		_inventoryIconSprite.dontDrawNextFrame();
1101 
1102 	_gfx->drawAllSprites();
1103 
1104 	if (_itemIconSprite.drawMode == 2) {
1105 		_gfx->delSprite(&_itemIconSprite);
1106 		_itemIconSprite.drawMode = 0;
1107 		delete _itemIconSprite.bitmap;
1108 		_itemIconSprite.bitmap = nullptr;
1109 	}
1110 
1111 	if (_inventoryIconSprite.drawMode == 2) {
1112 		_gfx->delSprite(&_inventoryIconSprite);
1113 		_inventoryIconSprite.drawMode = 0;
1114 		delete _inventoryIconSprite.bitmap;
1115 		_inventoryIconSprite.bitmap = nullptr;
1116 	}
1117 }
1118 
updateCrewmanGetupTimers()1119 void StarTrekEngine::updateCrewmanGetupTimers() {
1120 	if (_awayMission.crewDownBitset == 0)
1121 		return;
1122 	for (int i = OBJECT_KIRK; i <= OBJECT_REDSHIRT; i++) {
1123 		Actor *actor = &_actorList[i];
1124 
1125 		if (!(_awayMission.crewDownBitset & (1 << i)))
1126 			continue;
1127 
1128 		_awayMission.crewGetupTimers[i]--;
1129 		if (_awayMission.crewGetupTimers[i] <= 0) {
1130 			Common::String anim = getCrewmanAnimFilename(i, "getu");
1131 			int8 dir = _awayMission.crewDirectionsAfterWalk[i];
1132 			char d;
1133 			if (dir == -1) {
1134 				d = actor->direction;
1135 			} else {
1136 				const char *dirs = "nsew";
1137 				Fixed8 scale = getActorScaleAtPosition(actor->sprite.pos.y);
1138 				d = dirs[dir];
1139 
1140 				int16 xOffset = 0, yOffset = 0;
1141 				if (d == 'n') {
1142 					xOffset = -24;
1143 					yOffset = -8;
1144 				} else if (d == 'w') {
1145 					xOffset = -35;
1146 					yOffset = -12;
1147 				}
1148 				actor->sprite.pos.x += scale.multToInt(xOffset);
1149 				actor->sprite.pos.y += scale.multToInt(yOffset);
1150 			}
1151 
1152 			anim += (char)d;
1153 			loadActorAnimWithRoomScaling(i, anim, actor->sprite.pos.x, actor->sprite.pos.y);
1154 			_awayMission.crewDownBitset &= ~(1 << i);
1155 		}
1156 	}
1157 }
1158 
showInventoryMenu(int x,int y,bool restoreMouse)1159 int StarTrekEngine::showInventoryMenu(int x, int y, bool restoreMouse) {
1160 	const int ITEMS_PER_ROW = 5;
1161 
1162 	Common::Point oldMousePos = _gfx->getMousePos();
1163 	bool keyboardControlledMouse = _keyboardControlsMouse;
1164 	_keyboardControlsMouse = false;
1165 
1166 	int itemIndex = 0;
1167 	int numItems = 0;
1168 
1169 	char itemNames[NUM_OBJECTS][10];
1170 	Common::Point itemPositions[NUM_OBJECTS];
1171 	int16 itemIndices[NUM_OBJECTS];
1172 
1173 	while (itemIndex < NUM_OBJECTS) {
1174 		if (_itemList[itemIndex].have) {
1175 			strcpy(itemNames[numItems], _itemList[itemIndex].name);
1176 
1177 			int16 itemX = (numItems % ITEMS_PER_ROW) * 32 + x;
1178 			int16 itemY = (numItems / ITEMS_PER_ROW) * 32 + y;
1179 			itemPositions[numItems] = Common::Point(itemX, itemY);
1180 			itemIndices[numItems] = _itemList[itemIndex].field2;
1181 
1182 			numItems++;
1183 		}
1184 		itemIndex++;
1185 	}
1186 
1187 	Sprite itemSprites[NUM_OBJECTS];
1188 
1189 	for (int i = 0; i < numItems; i++) {
1190 		_gfx->addSprite(&itemSprites[i]);
1191 
1192 		itemSprites[i].drawMode = 2;
1193 		itemSprites[i].pos.x = itemPositions[i].x;
1194 		itemSprites[i].pos.y = itemPositions[i].y;
1195 		itemSprites[i].drawPriority = 15;
1196 		itemSprites[i].drawPriority2 = 8;
1197 		itemSprites[i].setBitmap(_resource->loadBitmapFile(itemNames[i]));
1198 	}
1199 
1200 	chooseMousePositionFromSprites(itemSprites, numItems, -1, 4);
1201 	bool displayMenu = true;
1202 	int lastItemIndex = -1;
1203 
1204 	while (displayMenu) {
1205 		_sound->checkLoopMusic();
1206 
1207 		TrekEvent event;
1208 		if (!getNextEvent(&event))
1209 			continue;
1210 
1211 		switch (event.type) {
1212 		case TREKEVENT_TICK: {
1213 			Common::Point mousePos = _gfx->getMousePos();
1214 			itemIndex = getMenuButtonAt(itemSprites, numItems, mousePos.x, mousePos.y);
1215 			if (itemIndex != lastItemIndex) {
1216 				if (lastItemIndex != -1) {
1217 					drawMenuButtonOutline(itemSprites[lastItemIndex].bitmap, 0);
1218 					itemSprites[lastItemIndex].bitmapChanged = true;
1219 				}
1220 				if (itemIndex != -1) {
1221 					drawMenuButtonOutline(itemSprites[itemIndex].bitmap, 15);
1222 					itemSprites[itemIndex].bitmapChanged = true;
1223 				}
1224 				lastItemIndex = itemIndex;
1225 			}
1226 			_gfx->drawAllSprites();
1227 			break;
1228 		}
1229 
1230 		case TREKEVENT_LBUTTONDOWN:
1231 			displayMenu = false;
1232 			break;
1233 
1234 		case TREKEVENT_RBUTTONDOWN:
1235 			displayMenu = false;
1236 			lastItemIndex = -1;
1237 			break;
1238 
1239 		case TREKEVENT_KEYDOWN:
1240 			switch (event.kbd.keycode) {
1241 			case Common::KEYCODE_ESCAPE:
1242 			case Common::KEYCODE_F2:
1243 				displayMenu = false;
1244 				lastItemIndex = -1;
1245 				break;
1246 
1247 			case Common::KEYCODE_RETURN:
1248 			case Common::KEYCODE_KP_ENTER:
1249 			case Common::KEYCODE_F1:
1250 				displayMenu = false;
1251 				break;
1252 
1253 			case Common::KEYCODE_HOME:
1254 			case Common::KEYCODE_KP7:
1255 				chooseMousePositionFromSprites(itemSprites, numItems, lastItemIndex, 4);
1256 				break;
1257 
1258 			case Common::KEYCODE_UP:
1259 			case Common::KEYCODE_KP8:
1260 			case Common::KEYCODE_PAGEUP:
1261 			case Common::KEYCODE_KP9:
1262 				chooseMousePositionFromSprites(itemSprites, numItems, lastItemIndex, 2);
1263 				break;
1264 
1265 			case Common::KEYCODE_LEFT:
1266 			case Common::KEYCODE_KP4:
1267 				chooseMousePositionFromSprites(itemSprites, numItems, lastItemIndex, 1);
1268 				break;
1269 
1270 			case Common::KEYCODE_RIGHT:
1271 			case Common::KEYCODE_KP6:
1272 				chooseMousePositionFromSprites(itemSprites, numItems, lastItemIndex, 0);
1273 				break;
1274 
1275 			case Common::KEYCODE_END:
1276 			case Common::KEYCODE_KP1:
1277 				chooseMousePositionFromSprites(itemSprites, numItems, lastItemIndex, 5);
1278 				break;
1279 
1280 			case Common::KEYCODE_DOWN:
1281 			case Common::KEYCODE_KP2:
1282 			case Common::KEYCODE_PAGEDOWN:
1283 			case Common::KEYCODE_KP3:
1284 				chooseMousePositionFromSprites(itemSprites, numItems, lastItemIndex, 3);
1285 				break;
1286 
1287 			default:
1288 				break;
1289 			}
1290 			break;
1291 
1292 		default:
1293 			break;
1294 		}
1295 
1296 		removeNextEvent();
1297 	}
1298 
1299 	_sound->playSoundEffectIndex(0x10);
1300 	if (lastItemIndex >= 0)
1301 		drawMenuButtonOutline(itemSprites[lastItemIndex].bitmap, 0);
1302 
1303 	for (int i = 0; i < numItems; i++)
1304 		itemSprites[i].dontDrawNextFrame();
1305 
1306 	_gfx->drawAllSprites();
1307 
1308 	for (int i = 0; i < numItems; i++) {
1309 		delete itemSprites[i].bitmap;
1310 		itemSprites[i].bitmap = nullptr;
1311 		_gfx->delSprite(&itemSprites[i]);
1312 	}
1313 
1314 	if (lastItemIndex >= 0) {
1315 		lastItemIndex = itemIndices[lastItemIndex];
1316 	}
1317 
1318 	if (restoreMouse)
1319 		_gfx->warpMouse(oldMousePos.x, oldMousePos.y);
1320 
1321 	_keyboardControlsMouse = keyboardControlledMouse;
1322 	return lastItemIndex;
1323 }
1324 
initStarfieldSprite(Sprite * sprite,Bitmap * bitmap,const Common::Rect & rect)1325 void StarTrekEngine::initStarfieldSprite(Sprite *sprite, Bitmap *bitmap, const Common::Rect &rect) {
1326 	sprite->setXYAndPriority(rect.left, rect.top, 0);
1327 	sprite->setBitmap(bitmap);
1328 	bitmap->xoffset = 0;
1329 	bitmap->yoffset = 0;
1330 	bitmap->width = rect.width();
1331 	bitmap->height = rect.height();
1332 	_gfx->addSprite(sprite);
1333 	sprite->drawMode = 1;
1334 }
1335 
scaleBitmap(Bitmap * bitmap,Fixed8 scale)1336 Bitmap *StarTrekEngine::scaleBitmap(Bitmap *bitmap, Fixed8 scale) {
1337 	int scaledWidth  = scale.multToInt(bitmap->width);
1338 	int scaledHeight = scale.multToInt(bitmap->height);
1339 	int origWidth  = bitmap->width;
1340 	int origHeight = bitmap->height;
1341 
1342 	if (scaledWidth < 1)
1343 		scaledWidth = 1;
1344 	if (scaledHeight < 1)
1345 		scaledHeight = 1;
1346 
1347 	Bitmap *scaledBitmap = new Bitmap(scaledWidth, scaledHeight);
1348 	scaledBitmap->xoffset = scale.multToInt(bitmap->xoffset);
1349 	scaledBitmap->yoffset = scale.multToInt(bitmap->yoffset);
1350 
1351 	// sub_344a5(scaledWidth, origWidth);
1352 
1353 	origHeight--;
1354 	scaledHeight--;
1355 
1356 	byte *src = bitmap->pixels;
1357 	byte *dest = scaledBitmap->pixels;
1358 
1359 	if (scale <= 1.0) {
1360 		int16 var2e = 0;
1361 		uint16 var30 = scaledHeight << 1;
1362 		uint16 var32 = (scaledHeight - origHeight) << 1;
1363 		uint16 origRow = 0;
1364 
1365 		while (origRow <= origHeight) {
1366 			if (var2e < 0) {
1367 				var2e += var30;
1368 			} else {
1369 				var2e += var32;
1370 				scaleBitmapRow(src, dest, origWidth, scaledWidth);
1371 				dest += scaledWidth;
1372 			}
1373 
1374 			src += bitmap->width;
1375 			origRow++;
1376 		}
1377 	} else {
1378 		int16 var2e = (origHeight << 1) - scaledHeight;
1379 		uint16 var30 = origHeight << 1;
1380 		uint16 var32 = (origHeight - scaledHeight) << 1;
1381 		uint16 srcRowChanged = true;
1382 		origWidth = bitmap->width;
1383 		uint16 scaledRow = 0;
1384 		byte *rowData = new byte[scaledWidth];
1385 
1386 		while (scaledRow++ <= scaledHeight) {
1387 			if (srcRowChanged) {
1388 				scaleBitmapRow(src, rowData, origWidth, scaledWidth);
1389 				srcRowChanged = false;
1390 			}
1391 
1392 			memcpy(dest, rowData, scaledWidth);
1393 			dest += scaledWidth;
1394 
1395 			if (var2e < 0) {
1396 				var2e += var30;
1397 			} else {
1398 				var2e += var32;
1399 				src += origWidth;
1400 				srcRowChanged = true;
1401 			}
1402 		}
1403 
1404 		delete[] rowData;
1405 	}
1406 
1407 	delete bitmap;
1408 
1409 	return scaledBitmap;
1410 }
1411 
scaleBitmapRow(byte * src,byte * dest,uint16 origWidth,uint16 scaledWidth)1412 void StarTrekEngine::scaleBitmapRow(byte *src, byte *dest, uint16 origWidth, uint16 scaledWidth) {
1413 	if (origWidth >= scaledWidth) {
1414 		int16 var2 = (scaledWidth << 1) - origWidth;
1415 		uint16 var4 = scaledWidth << 1;
1416 		uint16 var6 = (scaledWidth - origWidth) << 1;
1417 		uint16 varE = 0;
1418 		uint16 varA = 0;
1419 		uint16 var8 = origWidth;
1420 		uint16 di = 0;
1421 
1422 		while (var8-- != 0) {
1423 			if (var2 < 0) {
1424 				var2 += var4;
1425 			} else {
1426 				var2 += var6;
1427 				if (di != 0) {
1428 					if (varE != 0) {
1429 						*(dest - 1) = *src++;
1430 						varE = 0;
1431 						di--;
1432 					}
1433 					src += di;
1434 					di = 0;
1435 				}
1436 				*dest++ = *src;
1437 				varE = 1;
1438 			}
1439 
1440 			di++;
1441 			varA++;
1442 		}
1443 	} else {
1444 		int16 var2 = ((origWidth - 1) << 1) - (scaledWidth - 1);
1445 		uint16 var4 = (origWidth - 1) << 1;
1446 		uint16 var6 = ((origWidth - 1) - (scaledWidth - 1)) << 1;
1447 		uint16 varA = 0;
1448 		uint16 var8 = scaledWidth;
1449 		uint16 di = 0;
1450 
1451 		while (var8-- != 0) {
1452 			if (di != 0) {
1453 				src += di;
1454 				di = 0;
1455 			}
1456 			*dest++ = *src;
1457 
1458 			if (var2 < 0)
1459 				var2 += var4;
1460 			else {
1461 				var2 += var6;
1462 				di++;
1463 			}
1464 
1465 			varA++;
1466 		}
1467 	}
1468 }
1469 
1470 } // End of namespace StarTrek
1471