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 "startrek/iwfile.h"
24 #include "startrek/resource.h"
25 #include "startrek/room.h"
26 #include "startrek/startrek.h"
27 
28 namespace StarTrek {
29 
initAwayMission()30 void StarTrekEngine::initAwayMission() {
31 	_awayMission = AwayMission(); // Initialize members to 0
32 
33 	// memset(bitmapBuffer->pixels, 0, 0xfa00);
34 
35 	_resource->setTxtFileName("ground");
36 
37 	// sub_23a60(); // TODO
38 	_sound->loadMusicFile("ground");
39 
40 	loadRoom(_missionToLoad, _roomIndexToLoad);
41 	_roomIndexToLoad = -1;
42 
43 	// Load crew positions for beaming in
44 	initAwayCrewPositions(4);
45 }
46 
runAwayMission()47 void StarTrekEngine::runAwayMission() {
48 	while (_gameMode == GAMEMODE_AWAYMISSION && !_resetGameMode) {
49 		// Original game manipulates the stack when the room changes to return execution
50 		// to this point. Instead of doing that, just check if a variable is set.
51 		if (_roomIndexToLoad != -1 && _spawnIndexToLoad != -1) {
52 			loadRoomIndex(_roomIndexToLoad, _spawnIndexToLoad);
53 			_roomIndexToLoad = -1;
54 			_spawnIndexToLoad = -1;
55 		}
56 
57 		handleAwayMissionEvents();
58 
59 		Common::Point mousePos = _gfx->getMousePos();
60 		_awayMission.mouseX = mousePos.x;
61 		_awayMission.mouseY = mousePos.y;
62 
63 		assert(_actionQueue.size() <= 16);
64 		while (!_actionQueue.empty()) {
65 			// sub_200e7(); // TODO
66 			// sub_20118();
67 			handleAwayMissionAction();
68 		}
69 	}
70 }
71 
cleanupAwayMission()72 void StarTrekEngine::cleanupAwayMission() {
73 	// TODO
74 }
75 
loadRoom(const Common::String & missionName,int roomIndex)76 void StarTrekEngine::loadRoom(const Common::String &missionName, int roomIndex) {
77 	_keyboardControlsMouse = true;
78 
79 	_missionName = _missionToLoad;
80 	_roomIndex = roomIndex;
81 
82 	_roomFrameCounter = 0;
83 	_awayMission.disableInput = false;
84 
85 	_gfx->fadeoutScreen();
86 	_sound->stopAllVocSounds();
87 
88 	_gfx->setBackgroundImage(getScreenName());
89 	_gfx->loadPri(getScreenName());
90 	_gfx->loadPalette("palette");
91 	_gfx->copyBackgroundScreen();
92 
93 	_room = new Room(this, getScreenName());
94 
95 	// Original sets up bytes 0-3 of rdf file as "remote function caller"
96 
97 	bool isDemo = getFeatures() & GF_DEMO;
98 	if (!isDemo)
99 		_room->loadMapFile(getScreenName());
100 
101 	_awayMission.activeAction = ACTION_WALK;
102 
103 	removeDrawnActorsFromScreen();
104 	initActors();
105 
106 	Fixed8 num = _room->getMaxScale() - _room->getMinScale();
107 	int16 den = _room->getMaxY() - _room->getMinY() + 1;
108 	_playerActorScale = Fixed16(num) / den;
109 
110 	_actionQueue.clear();
111 
112 	if (!isDemo) {
113 		int16 addr = _room->getBanDataStart();
114 		while (addr != _room->getBanDataEnd()) {
115 			Common::String name((char *)&_room->_rdfData[addr]);
116 			loadBanFile(name);
117 			addr += strlen((char *)&_room->_rdfData[addr]) + 1;
118 		}
119 	}
120 }
121 
initAwayCrewPositions(int warpEntryIndex)122 void StarTrekEngine::initAwayCrewPositions(int warpEntryIndex) {
123 	_sound->stopAllVocSounds();
124 
125 	memset(_awayMission.crewDirectionsAfterWalk, 0xff, 4);
126 
127 	switch (warpEntryIndex) {
128 	case 0: // 0-3: Crew spawns in a spot and walks to a spot.
129 	case 1:
130 	case 2:
131 	case 3:
132 		for (int i = 0; i < (_awayMission.redshirtDead ? 3 : 4); i++) {
133 			Common::String anim = getCrewmanAnimFilename(i, "walk");
134 
135 			int16 rdfOffset = RDF_ROOM_ENTRY_POSITIONS + warpEntryIndex * 32 + i * 8;
136 
137 			int16 srcX = _room->readRdfWord(rdfOffset + 0);  // Position to spawn at
138 			int16 srcY = _room->readRdfWord(rdfOffset + 2);
139 			int16 destX = _room->readRdfWord(rdfOffset + 4); // Position to walk to
140 			int16 destY = _room->readRdfWord(rdfOffset + 6);
141 
142 			actorWalkToPosition(i, anim, srcX, srcY, destX, destY);
143 		}
144 
145 		_kirkActor->triggerActionWhenAnimFinished = true;
146 		_kirkActor->finishedAnimActionParam = 0xff;
147 		_awayMission.disableInput = true;
148 		_warpHotspotsActive = false;
149 		break;
150 	case 4: // Crew is beaming in.
151 		warpEntryIndex -= 4;
152 		for (int i = 0; i < (_awayMission.redshirtDead ? 3 : 4); i++) {
153 			Common::String animFilename = getCrewmanAnimFilename(i, "tele");
154 			Common::Point warpPos = _room->getBeamInPosition(i);
155 			loadActorAnimWithRoomScaling(i, animFilename, warpPos.x, warpPos.y);
156 		}
157 		_kirkActor->triggerActionWhenAnimFinished = true;
158 		_kirkActor->finishedAnimActionParam = 0xff;
159 		_awayMission.disableInput = true;
160 		_sound->playSoundEffectIndex(0x09);
161 		_warpHotspotsActive = false;
162 		break;
163 	case 5: // Crew spawns in directly at a position.
164 		for (int i = 0; i < (_awayMission.redshirtDead ? 3 : 4); i++) {
165 			Common::String animFilename = getCrewmanAnimFilename(i, "stnds");
166 			Common::Point warpPos = _room->getSpawnPosition(i);
167 			loadActorAnimWithRoomScaling(i, animFilename, warpPos.x, warpPos.y);
168 		}
169 		_warpHotspotsActive = true;
170 		break;
171 	case 6:
172 		loadBridgeActors();
173 		break;
174 	default:
175 		warning("Invalid parameter (%d) to initAwayCrewPositions", warpEntryIndex);
176 		break;
177 	}
178 }
179 
handleAwayMissionEvents()180 void StarTrekEngine::handleAwayMissionEvents() {
181 	TrekEvent event;
182 
183 	if (popNextEvent(&event)) {
184 		switch (event.type) {
185 		case TREKEVENT_TICK:
186 			updateActorAnimations();
187 			updateCrewmanGetupTimers();
188 
189 			updateMouseBitmap();
190 			renderBanBelowSprites();
191 			_gfx->drawAllSprites(false);
192 			renderBanAboveSprites();
193 			_gfx->updateScreen();
194 
195 			_sound->checkLoopMusic();
196 			updateAwayMissionTimers();
197 			_frameIndex++;
198 			_roomFrameCounter++;
199 			addAction(ACTION_TICK, _roomFrameCounter & 0xff, (_roomFrameCounter >> 8) & 0xff, 0);
200 			if (_roomFrameCounter >= 2)
201 				_gfx->incPaletteFadeLevel();
202 			break;
203 
204 		case TREKEVENT_LBUTTONDOWN:
205 			awayMissionLeftClick();
206 			break; // End of TREKEVENT_LBUTTONDOWN
207 
208 		case TREKEVENT_MOUSEMOVE:
209 			break;
210 
211 		case TREKEVENT_RBUTTONDOWN:
212 			awayMissionSelectAction(true);
213 			break;
214 
215 		case TREKEVENT_KEYDOWN:
216 			if (_awayMission.disableInput)
217 				break;
218 
219 			switch (event.kbd.keycode) {
220 			case Common::KEYCODE_ESCAPE:
221 			case Common::KEYCODE_SPACE:
222 			case Common::KEYCODE_F2:
223 				awayMissionSelectAction(true);
224 				break;
225 
226 			case Common::KEYCODE_t:
227 				hideInventoryIcons();
228 				_awayMission.activeAction = ACTION_TALK;
229 				awayMissionSelectAction(false);
230 				break;
231 
232 			case Common::KEYCODE_l:
233 				hideInventoryIcons();
234 				_awayMission.activeAction = ACTION_LOOK;
235 				awayMissionSelectAction(false);
236 				break;
237 
238 			case Common::KEYCODE_g:
239 				hideInventoryIcons();
240 				_awayMission.activeAction = ACTION_GET;
241 				awayMissionSelectAction(false);
242 				break;
243 
244 			case Common::KEYCODE_u:
245 				hideInventoryIcons();
246 				_awayMission.activeAction = ACTION_USE;
247 				awayMissionSelectAction(false);
248 				break;
249 
250 			case Common::KEYCODE_w:
251 				hideInventoryIcons();
252 				_awayMission.activeAction = ACTION_WALK;
253 				break;
254 
255 			case Common::KEYCODE_i:
256 				if (_awayMission.activeAction == ACTION_USE) {
257 					hideInventoryIcons();
258 					int clickedObject = showInventoryMenu(50, 50, true);
259 					if (clickedObject == -1)
260 						clickedObject = -2;
261 					awayMissionUseObject(clickedObject);
262 				} else if (_awayMission.activeAction == ACTION_LOOK) {
263 					hideInventoryIcons();
264 					int clickedObject = showInventoryMenu(50, 50, true);
265 					if (clickedObject == -1)
266 						clickedObject = -2;
267 					awayMissionGetLookOrTalk(clickedObject);
268 				}
269 				break;
270 
271 			case Common::KEYCODE_RETURN:
272 			case Common::KEYCODE_KP_ENTER:
273 			case Common::KEYCODE_F1:
274 				awayMissionLeftClick();
275 				break;
276 
277 			case Common::KEYCODE_c:
278 				// Bridge computer, where the player can ask about various topics.
279 				// ENHANCEMENT: Normally, this is only available when in the bridge.
280 				// We also show it in missions.
281 				if (!(getFeatures() & GF_DEMO))
282 					handleBridgeComputer();
283 				break;
284 
285 			case Common::KEYCODE_p:
286 				// Pause game
287 				// TODO
288 				break;
289 
290 			case Common::KEYCODE_e:
291 				if (event.kbd.flags & Common::KBD_CTRL) {
292 					_sound->toggleSfx();
293 				}
294 				break;
295 
296 			case Common::KEYCODE_m:
297 				if (event.kbd.flags & Common::KBD_CTRL) {
298 					_sound->toggleMusic();
299 				}
300 				break;
301 
302 			case Common::KEYCODE_q:
303 				if (event.kbd.flags & Common::KBD_CTRL) {
304 					showQuitGamePrompt(20, 20);
305 				}
306 				break;
307 
308 			default:
309 				break;
310 			}
311 			break;
312 
313 		default:
314 			break;
315 		}
316 	}
317 }
318 
awayMissionLeftClick()319 void StarTrekEngine::awayMissionLeftClick() {
320 	if (_awayMission.disableInput)
321 		return;
322 
323 	switch (_awayMission.activeAction) {
324 	case ACTION_WALK: {
325 		if (_awayMission.disableWalking)
326 			break;
327 		_kirkActor->sprite.drawMode = 1; // Hide these objects for function call below?
328 		_spockActor->sprite.drawMode = 1;
329 		_mccoyActor->sprite.drawMode = 1;
330 		_redshirtActor->sprite.drawMode = 1;
331 
332 		int16 clickedObject = findObjectAt(_gfx->getMousePos());
333 
334 		_kirkActor->sprite.drawMode = 0;
335 		_spockActor->sprite.drawMode = 0;
336 		_mccoyActor->sprite.drawMode = 0;
337 		_redshirtActor->sprite.drawMode = 0;
338 
339 		if (walkActiveObjectToHotspot())
340 			break;
341 
342 		if (clickedObject > OBJECT_KIRK && clickedObject < ITEMS_START)
343 			addAction(ACTION_WALK, clickedObject, 0, 0);
344 		else {
345 			Common::String animFilename = getCrewmanAnimFilename(OBJECT_KIRK, "walk");
346 			Common::Point mousePos = _gfx->getMousePos();
347 			actorWalkToPosition(OBJECT_KIRK, animFilename, _kirkActor->pos.x, _kirkActor->pos.y, mousePos.x, mousePos.y);
348 		}
349 		break;
350 	}
351 
352 	case ACTION_USE: {
353 		if (_awayMission.activeObject == OBJECT_REDSHIRT && (_awayMission.redshirtDead || (_awayMission.crewDownBitset & (1 << OBJECT_REDSHIRT)))) {
354 			hideInventoryIcons();
355 			_awayMission.activeAction = ACTION_WALK;
356 			break;
357 		}
358 
359 		int16 clickedObject = findObjectAt(_gfx->getMousePos());
360 		hideInventoryIcons();
361 
362 		if (clickedObject == OBJECT_INVENTORY_ICON) {
363 			clickedObject = showInventoryMenu(50, 50, false);
364 
365 			// -1 means "clicked on something unknown"; -2 means "clicked on
366 			// nothing". In the case of the inventory, either one clicks on an
367 			// inventory item, or no action is performed.
368 			if (clickedObject == -1)
369 				clickedObject = -2;
370 		}
371 
372 		awayMissionUseObject(clickedObject);
373 		break;
374 	}
375 
376 	case ACTION_GET:
377 	case ACTION_LOOK:
378 	case ACTION_TALK: {
379 		int16 clickedObject = findObjectAt(_gfx->getMousePos());
380 		if (!isObjectUnusable(clickedObject, _awayMission.activeAction)) {
381 			hideInventoryIcons();
382 
383 			if (clickedObject == OBJECT_INVENTORY_ICON) {
384 				clickedObject = showInventoryMenu(50, 50, false);
385 				if (clickedObject == -1)
386 					clickedObject = -2;
387 			}
388 
389 			awayMissionGetLookOrTalk(clickedObject);
390 		}
391 		break;
392 	}
393 
394 	default:
395 		break;
396 	}
397 }
398 
awayMissionSelectAction(bool openActionMenu)399 void StarTrekEngine::awayMissionSelectAction(bool openActionMenu) {
400 	if (openActionMenu) {
401 		if (_awayMission.disableInput)
402 			return;
403 		hideInventoryIcons();
404 		_sound->playSoundEffectIndex(kSfxButton);
405 		_awayMission.activeAction = showActionMenu();
406 	}
407 
408 	if (_awayMission.activeAction == ACTION_USE) {
409 		int16 clickedObject = selectObjectForUseAction();
410 		if (clickedObject == -1)
411 			return;
412 		else
413 			_awayMission.activeObject = clickedObject;
414 	}
415 	if (_awayMission.activeAction == ACTION_USE
416 			&& _awayMission.activeObject == OBJECT_ICOMM && (_awayMission.crewDownBitset & (1 << OBJECT_KIRK)) == 0) {
417 		if (!walkActiveObjectToHotspot()) {
418 			addAction(_awayMission.activeAction, _awayMission.activeObject, 0, 0);
419 			_sound->playVoc("communic");
420 			_awayMission.activeAction = ACTION_WALK;
421 		}
422 	} else if (_awayMission.activeAction == ACTION_LOOK)
423 		showInventoryIcons(false);
424 	else if (_awayMission.activeAction == ACTION_USE && (_awayMission.crewDownBitset & (1 << OBJECT_KIRK)) == 0)
425 		showInventoryIcons(true);
426 }
427 
awayMissionUseObject(int16 clickedObject)428 void StarTrekEngine::awayMissionUseObject(int16 clickedObject) {
429 	_awayMission.passiveObject = clickedObject;
430 
431 	bool activeIsCrewman = _awayMission.activeObject <= OBJECT_REDSHIRT;
432 	bool activeIsItem = _awayMission.activeObject >= ITEMS_START && _awayMission.activeObject < ITEMS_END;
433 	bool passiveIsCrewman = _awayMission.passiveObject <= OBJECT_REDSHIRT;
434 	bool passiveIsItem = _awayMission.passiveObject >= ITEMS_START && _awayMission.passiveObject <= ITEMS_END; // FIXME: "<= ITEMS_END" doesn't make sense?
435 
436 	bool tryWalkToHotspot = false;
437 	bool showInventory = false;
438 
439 	if (clickedObject == -2)
440 		tryWalkToHotspot = true;
441 	else if (_room->actionHasCode(ACTION_USE, _awayMission.activeObject, _awayMission.passiveObject, 0))
442 		tryWalkToHotspot = true;
443 	else if (_awayMission.activeObject == OBJECT_MCCOY && _room->actionHasCode(ACTION_USE, OBJECT_IMEDKIT, _awayMission.passiveObject, 0))
444 		tryWalkToHotspot = true;
445 	// CHECKME: Identical to the previous check, thus never used
446 	//else if (_awayMission.activeObject == OBJECT_MCCOY && _room->actionHasCode(ACTION_USE, OBJECT_IMEDKIT, _awayMission.passiveObject, 0))
447 	//	tryWalkToHotspot = true;
448 	else if (_awayMission.activeObject == OBJECT_SPOCK && _room->actionHasCode(ACTION_USE, OBJECT_ISTRICOR, _awayMission.passiveObject, 0))
449 		tryWalkToHotspot = true;
450 
451 	if (!tryWalkToHotspot) {
452 		if ((activeIsCrewman && passiveIsCrewman)
453 				|| (activeIsCrewman && passiveIsItem)
454 				|| (activeIsItem && passiveIsItem)) {
455 			if (_awayMission.passiveObject == OBJECT_ICOMM) {
456 				if (walkActiveObjectToHotspot())
457 					return;
458 				addAction(ACTION_USE, OBJECT_ICOMM, 0, 0);
459 				_sound->playVoc("commun30");
460 				if (_awayMission.activeObject <= OBJECT_REDSHIRT) {
461 					showInventory = true;
462 				} else {
463 					_awayMission.activeAction = ACTION_WALK;
464 					return;
465 				}
466 			}
467 
468 			_awayMission.activeObject = _awayMission.passiveObject;
469 			showInventory = true;
470 		} else
471 			tryWalkToHotspot = true;
472 	}
473 
474 	if (tryWalkToHotspot) {
475 		if (!walkActiveObjectToHotspot()) {
476 			if (clickedObject != -2)
477 				addAction(_awayMission.activeAction, _awayMission.activeObject, _awayMission.passiveObject, 0);
478 			showInventory = true;
479 		}
480 	}
481 
482 	if (showInventory && !(_awayMission.crewDownBitset & (1 << OBJECT_KIRK)))
483 		showInventoryIcons(true);
484 }
485 
awayMissionGetLookOrTalk(int16 clickedObject)486 void StarTrekEngine::awayMissionGetLookOrTalk(int16 clickedObject) {
487 	_awayMission.activeObject = clickedObject;
488 
489 	if (walkActiveObjectToHotspot())
490 		return;
491 
492 	if (clickedObject != -2)
493 		addAction(_awayMission.activeAction, _awayMission.activeObject, 0, 0);
494 
495 	if (_awayMission.activeAction == ACTION_LOOK && !(_awayMission.crewDownBitset & (1 << OBJECT_KIRK)))
496 		showInventoryIcons(false);
497 }
498 
unloadRoom()499 void StarTrekEngine::unloadRoom() {
500 	_gfx->fadeoutScreen();
501 	// sub_2394b(); // TODO
502 	removeDrawnActorsFromScreen();
503 	delete _room;
504 	_room = nullptr;
505 	delete _mapFile;
506 	_mapFile = nullptr;
507 	delete _iwFile;
508 	_iwFile = nullptr;
509 }
510 
loadActorAnimWithRoomScaling(int actorIndex,const Common::String & animName,int16 x,int16 y)511 int StarTrekEngine::loadActorAnimWithRoomScaling(int actorIndex, const Common::String &animName, int16 x, int16 y) {
512 	Fixed8 scale = getActorScaleAtPosition(y);
513 	return loadActorAnim(actorIndex, animName, x, y, scale);
514 }
515 
getActorScaleAtPosition(int16 y)516 Fixed8 StarTrekEngine::getActorScaleAtPosition(int16 y) {
517 	int16 maxY = _room->getMaxY();
518 	int16 minY = _room->getMinY();
519 	Fixed8 minScale = _room->getMinScale();
520 
521 	if (y > maxY)
522 		y = maxY;
523 	if (y < minY)
524 		y = minY;
525 
526 	return Fixed8(_playerActorScale * (y - minY)) + minScale;
527 }
528 
getRoom()529 Room *StarTrekEngine::getRoom() {
530 	return _room;
531 }
532 
addAction(const Action & action)533 void StarTrekEngine::addAction(const Action &action) {
534 	if (action.type != ACTION_TICK)
535 		debugC(kDebugGeneral, 4, "Action %d: %x, %x, %x", action.type, action.b1, action.b2, action.b3);
536 	_actionQueue.push(action);
537 }
538 
addAction(int8 type,byte b1,byte b2,byte b3)539 void StarTrekEngine::addAction(int8 type, byte b1, byte b2, byte b3) {
540 	const Action a = {type, b1, b2, b3};
541 	addAction(a);
542 }
543 
handleAwayMissionAction()544 void StarTrekEngine::handleAwayMissionAction() {
545 	Action action = _actionQueue.pop();
546 
547 	if ((action.type == ACTION_FINISHED_ANIMATION || action.type == ACTION_FINISHED_WALKING) && action.b1 == 0xff) {
548 		// Just finished walking or beaming into a room
549 		if (_awayMission.disableInput == 1)
550 			_awayMission.disableInput = false;
551 		_warpHotspotsActive = true;
552 		return;
553 	} else if (action.type == ACTION_FINISHED_WALKING && action.b1 >= 0xe0) {
554 		// Finished walking to a position; perform the action that was input back when
555 		// they started walking over there.
556 		int index = action.b1 - 0xe0;
557 		addAction(_actionOnWalkCompletion[index]);
558 		_actionOnWalkCompletionInUse[index] = false;
559 	}
560 
561 	if (_room->handleAction(action))
562 		return;
563 
564 	// Action not defined for the room, check for default behaviour
565 
566 	switch (action.type) {
567 
568 	case ACTION_WALK:
569 		if (!_room->handleActionWithBitmask(action)) {
570 			Common::String animFilename = getCrewmanAnimFilename(OBJECT_KIRK, "walk");
571 			Common::Point mousePos = _gfx->getMousePos();
572 			actorWalkToPosition(OBJECT_KIRK, animFilename, _kirkActor->pos.x, _kirkActor->pos.y, mousePos.x, mousePos.y);
573 		}
574 		break;
575 
576 	case ACTION_USE:
577 		if (action.activeObject() != action.passiveObject()) {
578 			switch (action.activeObject()) {
579 			case OBJECT_KIRK:
580 				// BUGFIX: Don't allow the "use" action to bypass the "disableWalking" variable
581 				if (!(!_awayMission.disableWalking && _room->handleAction(ACTION_WALK, action.passiveObject(), 0, 0))
582 						&& !_room->handleAction(ACTION_GET, action.passiveObject(), 0, 0)) {
583 					showTextbox("Capt. Kirk", _resource->getLoadedText(GROUNDTX_KIRK_USE), 20, 20, TEXTCOLOR_YELLOW, 0);
584 				}
585 				break;
586 
587 			case OBJECT_SPOCK:
588 				if (!_room->handleAction(ACTION_USE, OBJECT_ISTRICOR, action.passiveObject(), 0)) {
589 					// BUGFIX: Original game has just "Spock" instead of "Mr. Spock" as the
590 					// speaker. That's inconsistent.
591 					// Same applies to other parts of this function.
592 					showTextbox("Mr. Spock", _resource->getLoadedText(GROUNDTX_SPOCK_USE), 20, 20, TEXTCOLOR_BLUE, 0);
593 				}
594 				break;
595 
596 			case OBJECT_MCCOY:
597 				if (!_room->handleAction(ACTION_USE, OBJECT_IMEDKIT, action.passiveObject(), 0)
598 						&& !_room->handleAction(ACTION_USE, OBJECT_IMTRICOR, action.passiveObject(), 0)) {
599 					// BUGFIX: Original game has just "McCoy" instead of "Dr. McCoy".
600 					showTextbox("Dr. McCoy", _resource->getLoadedText(GROUNDTX_MCCOY_USE), 20, 20, TEXTCOLOR_BLUE, 0);
601 				}
602 				break;
603 
604 			case OBJECT_REDSHIRT:
605 				showTextbox(NULL, _resource->getLoadedText(GROUNDTX_REDSHIRT_USE), 20, 20, TEXTCOLOR_YELLOW, 0);
606 				break;
607 
608 			case OBJECT_IPHASERS:
609 			case OBJECT_IPHASERK:
610 				if (action.passiveObject() == OBJECT_SPOCK) {
611 					int text = GROUNDTX_PHASER_ON_SPOCK + getRandomWord() % 8;
612 					showTextbox("Mr. Spock", _resource->getLoadedText(text), 20, 20, TEXTCOLOR_BLUE, 0);
613 				} else if (action.passiveObject() == OBJECT_MCCOY) {
614 					int text = GROUNDTX_PHASER_ON_MCCOY + getRandomWord() % 8;
615 					showTextbox("Dr. McCoy", _resource->getLoadedText(text), 20, 20, TEXTCOLOR_BLUE, 0);
616 				} else if (action.passiveObject() == OBJECT_REDSHIRT) {
617 					Common::String text = _resource->getLoadedText(GROUNDTX_PHASER_ON_REDSHIRT + getRandomWord() % 8);
618 					// Replace audio filename with start of mission name (to load the
619 					// audio for the crewman specific to the mission))
620 					text.setChar(_missionName[0], 6);
621 					text.setChar(_missionName[1], 7);
622 					text.setChar(_missionName[2], 8);
623 					showTextbox("Security Officer", text, 20, 20, TEXTCOLOR_RED, 0);
624 					// TODO: replace "Security Officer" string with their actual name as
625 					// an enhancement?
626 				} else if (!_room->handleActionWithBitmask(action)) {
627 					int index = getRandomWord() % 7;
628 					if (index & 1)
629 						showTextbox("Dr. McCoy", _resource->getLoadedText(GROUNDTX_PHASER_ANYWHERE + index), 20, 20, TEXTCOLOR_BLUE, 0);
630 					else
631 						showTextbox("Mr. Spock", _resource->getLoadedText(GROUNDTX_PHASER_ANYWHERE + index), 20, 20, TEXTCOLOR_BLUE, 0);
632 				}
633 				break;
634 
635 			case OBJECT_ISTRICOR:
636 				showTextbox("Mr. Spock", _resource->getLoadedText(GROUNDTX_SPOCK_SCAN), 20, 20, TEXTCOLOR_BLUE, 0);
637 				break;
638 
639 			case OBJECT_IMTRICOR:
640 				showTextbox("Dr. McCoy", _resource->getLoadedText(GROUNDTX_MCCOY_SCAN), 20, 20, TEXTCOLOR_BLUE, 0);
641 				break;
642 
643 			case OBJECT_ICOMM:
644 				if (!_room->handleAction(ACTION_USE, OBJECT_ICOMM, 0xff, 0))
645 					showTextbox("Lt. Uhura", _resource->getLoadedText(GROUNDTX_USE_COMMUNICATOR), 20, 20, TEXTCOLOR_RED, 0);
646 				break;
647 
648 			case OBJECT_IMEDKIT:
649 				showTextbox("Dr. McCoy", _resource->getLoadedText(GROUNDTX_USE_MEDKIT), 20, 20, TEXTCOLOR_BLUE, 0);
650 				break;
651 
652 			default:
653 				if (!_room->handleActionWithBitmask(action.type, action.b1, action.b2, action.b3))
654 					showTextbox("", _resource->getLoadedText(GROUNDTX_NOTHING_HAPPENS), 20, 20, TEXTCOLOR_YELLOW, 0);
655 			}
656 		}
657 		break;
658 
659 	case ACTION_GET:
660 		if (!_room->handleActionWithBitmask(action.type, action.b1, action.b2, action.b3))
661 			showTextbox("", _resource->getLoadedText(GROUNDTX_FAIL_TO_OBTAIN_ANYTHING), 20, 20, TEXTCOLOR_YELLOW, 0);
662 		break;
663 
664 	case ACTION_LOOK:
665 		if (action.activeObject() >= ITEMS_START && action.activeObject() < ITEMS_END) {
666 			int i = action.activeObject() - ITEMS_START;
667 			Common::String text = _resource->getLoadedText(_itemList[i].textIndex);
668 			showTextbox("", text, 20, 20, TEXTCOLOR_YELLOW, 0);
669 		} else if (action.activeObject() == OBJECT_KIRK)
670 			showTextbox("", _resource->getLoadedText(GROUNDTX_LOOK_KIRK), 20, 20, TEXTCOLOR_YELLOW, 0);
671 		else if (action.activeObject() == OBJECT_SPOCK)
672 			showTextbox("", _resource->getLoadedText(GROUNDTX_LOOK_SPOCK), 20, 20, TEXTCOLOR_YELLOW, 0);
673 		else if (action.activeObject() == OBJECT_MCCOY)
674 			showTextbox("", _resource->getLoadedText(GROUNDTX_LOOK_MCCOY), 20, 20, TEXTCOLOR_YELLOW, 0);
675 		else if (action.activeObject() == OBJECT_REDSHIRT)
676 			showTextbox("", _resource->getLoadedText(GROUNDTX_LOOK_REDSHIRT), 20, 20, TEXTCOLOR_YELLOW, 0);
677 		else
678 			// Show generic "nothing of note" text.
679 			// BUGFIX: originally this was shown after the redshirt's text as well.
680 			// Though, the original game may not have used this default implementation
681 			// anywhere...
682 			showTextbox("", _resource->getLoadedText(GROUNDTX_LOOK_ANYWHERE), 20, 20, TEXTCOLOR_YELLOW, 0);
683 		break;
684 
685 	case ACTION_TALK:
686 		switch (action.activeObject()) {
687 		case OBJECT_KIRK:
688 		case OBJECT_SPOCK:
689 		case OBJECT_MCCOY:
690 		case OBJECT_REDSHIRT:
691 			showTextbox("", _resource->getLoadedText(GROUNDTX_TALK_TO_CREWMAN), 20, 20, TEXTCOLOR_YELLOW, 0);
692 			break;
693 
694 		default:
695 			showTextbox("", _resource->getLoadedText(GROUNDTX_NO_RESPONSE), 20, 20, TEXTCOLOR_YELLOW, 0);
696 			break;
697 		}
698 		break;
699 
700 	case ACTION_TOUCHED_WARP:
701 		if (!_room->handleActionWithBitmask(action)) {
702 			byte warpIndex = action.b1;
703 			int16 roomIndex = _room->readRdfWord(RDF_WARP_ROOM_INDICES + warpIndex * 2);
704 			unloadRoom();
705 			_sound->loadMusicFile("ground");
706 			loadRoom(_missionName, roomIndex);
707 			initAwayCrewPositions(warpIndex ^ 1);
708 		}
709 		break;
710 
711 	default:
712 		_room->handleActionWithBitmask(action);
713 		break;
714 	}
715 }
716 
checkTouchedLoadingZone(int16 x,int16 y)717 void StarTrekEngine::checkTouchedLoadingZone(int16 x, int16 y) {
718 	int16 offset = _room->getFirstDoorPolygonOffset();
719 
720 	while (offset != _room->getDoorPolygonEndOffset()) {
721 		if (_room->isPointInPolygon(offset, x, y)) {
722 			uint16 var = _room->readRdfWord(offset);
723 			if (_activeDoorWarpHotspot != var) {
724 				_activeDoorWarpHotspot = var;
725 				addAction(ACTION_TOUCHED_HOTSPOT, var & 0xff, 0, 0);
726 			}
727 			return;
728 		}
729 
730 		int16 numVertices = _room->readRdfWord(offset + 2);
731 		offset += numVertices * 4 + 4;
732 	}
733 	_activeDoorWarpHotspot = -1;
734 
735 	if (_awayMission.crewDownBitset == 0 && _warpHotspotsActive) {
736 		offset = _room->getFirstWarpPolygonOffset();
737 
738 		while (offset != _room->getWarpPolygonEndOffset()) {
739 			if (_room->isPointInPolygon(offset, x, y)) {
740 				uint16 var = _room->readRdfWord(offset);
741 				if (_activeWarpHotspot != var) {
742 					_activeWarpHotspot = var;
743 					addAction(ACTION_TOUCHED_WARP, var & 0xff, 0, 0);
744 				}
745 				return;
746 			}
747 
748 			int16 numVertices = _room->readRdfWord(offset + 2);
749 			offset += numVertices * 4 + 4;
750 		}
751 	}
752 	_activeWarpHotspot = -1;
753 }
754 
updateAwayMissionTimers()755 void StarTrekEngine::updateAwayMissionTimers() {
756 	for (int i = 0; i < 8; i++) {
757 		if (_awayMission.timers[i] == 0)
758 			continue;
759 		_awayMission.timers[i]--;
760 		if (_awayMission.timers[i] == 0)
761 			addAction(ACTION_TIMER_EXPIRED, i, 0, 0);
762 	}
763 }
764 
isPositionSolid(int16 x,int16 y)765 bool StarTrekEngine::isPositionSolid(int16 x, int16 y) {
766 	assert(x >= 0 && x < SCREEN_WIDTH && y >= 0 && y < SCREEN_HEIGHT);
767 
768 	_mapFile->seek((y * SCREEN_WIDTH + x) / 8, SEEK_SET);
769 	return _mapFile->readByte() & (0x80 >> (x % 8));
770 }
771 
loadRoomIndex(int roomIndex,int spawnIndex)772 void StarTrekEngine::loadRoomIndex(int roomIndex, int spawnIndex) {
773 	unloadRoom();
774 	_sound->loadMusicFile("ground");
775 
776 	loadRoom(_missionName, roomIndex);
777 	initAwayCrewPositions(spawnIndex % 6);
778 
779 	// WORKAROUND: original game calls "retrieveStackVars" to return execution directly to
780 	// the top of "runAwayMission". That can't really be done here. But does it matter?
781 }
782 
783 } // End of namespace StarTrek
784