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  * Additional copyright for this file:
8  * Copyright (C) 1995-1997 Presto Studios, Inc.
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License
12  * as published by the Free Software Foundation; either version 2
13  * of the License, or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23  *
24  */
25 
26 #include "pegasus/cursor.h"
27 #include "pegasus/pegasus.h"
28 #include "pegasus/items/biochips/arthurchip.h"
29 #include "pegasus/neighborhood/norad/constants.h"
30 #include "pegasus/neighborhood/norad/delta/globegame.h"
31 #include "pegasus/neighborhood/norad/delta/noraddelta.h"
32 
33 namespace Pegasus {
34 
35 static const TimeValue kDurationPerFrame = 600 / 15;
36 static const TimeValue kDurationPerRow = kNumLongSlices * kDurationPerFrame;
37 static const short kVerticalDuration = 16;
38 
GlobeTracker(Movie * globeMovie,Picture * leftHighlight,Picture * rightHighlight,Picture * upHighlight,Picture * downHighlight)39 GlobeTracker::GlobeTracker(Movie *globeMovie, Picture *leftHighlight, Picture *rightHighlight,
40 		Picture *upHighlight, Picture *downHighlight) {
41 	_globeMovie = globeMovie;
42 	_leftHighlight = leftHighlight;
43 	_rightHighlight = rightHighlight;
44 	_upHighlight = upHighlight;
45 	_downHighlight = downHighlight;
46 	_trackSpot = nullptr;
47 	_trackTime = -1;
48 	_trackDirection = kTrackDown;
49 }
50 
setTrackParameters(const Hotspot * trackSpot,GlobeTrackDirection direction)51 void GlobeTracker::setTrackParameters(const Hotspot *trackSpot, GlobeTrackDirection direction) {
52 	_trackSpot = trackSpot;
53 	_trackDirection = direction;
54 
55 	TimeValue time, newTime, start;
56 
57 	switch (_trackDirection) {
58 	case kTrackLeft:
59 		time = _globeMovie->getTime();
60 
61 		if (((time / kDurationPerRow) & 1) == 0) {
62 			start = (time / kDurationPerRow + 1) * kDurationPerRow;
63 			newTime = start + kDurationPerRow - time % kDurationPerRow;
64 		} else {
65 			start = (time / kDurationPerRow) * kDurationPerRow;
66 			newTime = time;
67 		}
68 
69 		_globeMovie->setSegment(start, start + kDurationPerRow);
70 
71 		// Clip new time so we don't go past the end of the segment
72 		if (newTime >= start + kDurationPerRow)
73 			newTime = start + kDurationPerRow - 1;
74 
75 		if (newTime != time) {
76 			_globeMovie->setTime(newTime);
77 			_globeMovie->redrawMovieWorld();
78 		}
79 
80 		_globeMovie->setFlags(kLoopTimeBase);
81 		break;
82 	case kTrackRight:
83 		time = _globeMovie->getTime();
84 
85 		if (((time / kDurationPerRow) & 1) == 0) {
86 			start = (time / kDurationPerRow) * kDurationPerRow;
87 			newTime = time;
88 		} else {
89 			start = (time / kDurationPerRow - 1) * kDurationPerRow;
90 			newTime = start + kDurationPerRow - time % kDurationPerRow;
91 		}
92 
93 		_globeMovie->setSegment(start, start + kDurationPerRow);
94 
95 		// Clip new time so we don't go past the end of the segment
96 		if (newTime >= start + kDurationPerRow)
97 			newTime = start + kDurationPerRow - 1;
98 
99 		if (newTime != time) {
100 			_globeMovie->setTime(newTime);
101 			_globeMovie->redrawMovieWorld();
102 		}
103 
104 		_globeMovie->setFlags(kLoopTimeBase);
105 		break;
106 	case kTrackUp:
107 	case kTrackDown:
108 		_globeMovie->setSegment(0, _globeMovie->getDuration());
109 		_globeMovie->setFlags(0);
110 		break;
111 	default:
112 		break;
113 	}
114 }
115 
activateHotspots()116 void GlobeTracker::activateHotspots() {
117 	Tracker::activateHotspots();
118 
119 	if (_trackSpot)
120 		g_allHotspots.activateOneHotspot(_trackSpot->getObjectID());
121 }
122 
stopTrackingInput(const Input & input)123 bool GlobeTracker::stopTrackingInput(const Input &input) {
124 	return !JMPPPInput::isPressingInput(input);
125 }
126 
continueTracking(const Input & input)127 void GlobeTracker::continueTracking(const Input &input) {
128 	Common::Point where;
129 	input.getInputLocation(where);
130 
131 	if (g_allHotspots.findHotspot(where) == _trackSpot)
132 		trackGlobeMovie();
133 	else
134 		stopGlobeMovie();
135 }
136 
startTracking(const Input & input)137 void GlobeTracker::startTracking(const Input &input) {
138 	Tracker::startTracking(input);
139 	trackGlobeMovie();
140 }
141 
stopTracking(const Input & input)142 void GlobeTracker::stopTracking(const Input &input) {
143 	Tracker::stopTracking(input);
144 	stopGlobeMovie();
145 }
146 
trackGlobeMovie()147 void GlobeTracker::trackGlobeMovie() {
148 	TimeValue time;
149 
150 	switch (_trackDirection) {
151 	case kTrackLeft:
152 		if (!_globeMovie->isRunning())
153 			_globeMovie->start();
154 
155 		_leftHighlight->show();
156 		break;
157 	case kTrackRight:
158 		if (!_globeMovie->isRunning())
159 			_globeMovie->start();
160 
161 		_rightHighlight->show();
162 		break;
163 	case kTrackUp:
164 		time = _globeMovie->getTime();
165 
166 		if (_trackTime == 0) {
167 			_trackTime = tickCount();
168 		} else if ((int)time - (int)kDurationPerRow * 2 >= 0 && (int)tickCount() >= _trackTime + kVerticalDuration) {
169 			_trackTime = tickCount();
170 			_globeMovie->setTime(time - kDurationPerRow * 2);
171 			_globeMovie->redrawMovieWorld();
172 		}
173 
174 		_upHighlight->show();
175 		break;
176 	case kTrackDown:
177 		time = _globeMovie->getTime();
178 
179 		if (_trackTime == 0) {
180 			_trackTime = tickCount();
181 		} else if (time + kDurationPerRow * 2 < _globeMovie->getDuration() && (int)tickCount() >= _trackTime + kVerticalDuration) {
182 			_trackTime = tickCount();
183 			_globeMovie->setTime(time + kDurationPerRow * 2);
184 			_globeMovie->redrawMovieWorld();
185 		}
186 
187 		_downHighlight->show();
188 		break;
189 	default:
190 		break;
191 	}
192 }
193 
stopGlobeMovie()194 void GlobeTracker::stopGlobeMovie() {
195 	switch (_trackDirection) {
196 	case kTrackLeft:
197 		_leftHighlight->hide();
198 		_globeMovie->stop();
199 		break;
200 	case kTrackRight:
201 		_rightHighlight->hide();
202 		_globeMovie->stop();
203 		break;
204 	case kTrackUp:
205 		_upHighlight->hide();
206 		_trackTime = tickCount() - kVerticalDuration;
207 		break;
208 	case kTrackDown:
209 		_downHighlight->hide();
210 		_trackTime = tickCount() - kVerticalDuration;
211 		break;
212 	default:
213 		break;
214 	}
215 }
216 
217 // Globe game PICTs:
218 static const ResIDType kGlobeCircleLeftPICTID = 300;
219 static const ResIDType kGlobeCircleRightPICTID = 301;
220 static const ResIDType kGlobeCircleUpPICTID = 302;
221 static const ResIDType kGlobeCircleDownPICTID = 303;
222 static const ResIDType kTargetUpperLeftPICTID = 304;
223 static const ResIDType kTargetUpperRightPICTID = 305;
224 static const ResIDType kTargetLowerLeftPICTID = 306;
225 static const ResIDType kTargetLowerRightPICTID = 307;
226 static const ResIDType kMotionHiliteLeftPICTID = 308;
227 static const ResIDType kMotionHiliteRightPICTID = 309;
228 static const ResIDType kMotionHiliteUpPICTID = 310;
229 static const ResIDType kMotionHiliteDownPICTID = 311;
230 
231 static const ResIDType kGlobeCountdownDigitsID = 350;
232 
233 static const int kGlobeCountdownWidth = 28;
234 static const int kGlobeCountdownHeight = 12;
235 static const int kGlobeCountdownOffset1 = 12;
236 static const int kGlobeCountdownOffset2 = 20;
237 
GlobeCountdown(const DisplayElementID id)238 GlobeCountdown::GlobeCountdown(const DisplayElementID id) : IdlerAnimation(id) {
239 	_digits.getImageFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kGlobeCountdownDigitsID);
240 
241 	Common::Rect r;
242 	_digits.getSurfaceBounds(r);
243 	_digitOffset = r.width() / 10;
244 	setScale(1);
245 	sizeElement(kGlobeCountdownWidth, kGlobeCountdownHeight);
246 }
247 
setDisplayOrder(const DisplayOrder order)248 void GlobeCountdown::setDisplayOrder(const DisplayOrder order) {
249 	IdlerAnimation::setDisplayOrder(order);
250 }
251 
show()252 void GlobeCountdown::show() {
253 	IdlerAnimation::show();
254 }
255 
hide()256 void GlobeCountdown::hide() {
257 	IdlerAnimation::hide();
258 }
259 
moveElementTo(const CoordType x,const CoordType y)260 void GlobeCountdown::moveElementTo(const CoordType x, const CoordType y) {
261 	IdlerAnimation::moveElementTo(x, y);
262 }
263 
setCountdownTime(const int numSeconds)264 void GlobeCountdown::setCountdownTime(const int numSeconds) {
265 	stop();
266 	setSegment(0, numSeconds);
267 	setTime(numSeconds);
268 }
269 
startCountdown()270 void GlobeCountdown::startCountdown() {
271 	setRate(-1);
272 }
273 
stopCountdown()274 void GlobeCountdown::stopCountdown() {
275 	stop();
276 }
277 
draw(const Common::Rect &)278 void GlobeCountdown::draw(const Common::Rect &) {
279 	Common::Rect r1;
280 	_digits.getSurfaceBounds(r1);
281 	r1.right = r1.left + _digitOffset;
282 	Common::Rect r2 = r1;
283 	TimeValue time = getTime();
284 
285 	Common::Rect bounds;
286 	getBounds(bounds);
287 
288 	if (time > 60 * 9 + 59) {
289 		r2.moveTo(bounds.left, bounds.top);
290 		r1.moveTo(9 * _digitOffset, 0);
291 		_digits.copyToCurrentPort(r1, r2);
292 
293 		r2.moveTo(bounds.left + kGlobeCountdownOffset1, bounds.top);
294 		r1.moveTo(5 * _digitOffset, 0);
295 		_digits.copyToCurrentPort(r1, r2);
296 
297 		r2.moveTo(bounds.left + kGlobeCountdownOffset2, bounds.top);
298 		r1.moveTo(9 * _digitOffset, 0);
299 		_digits.copyToCurrentPort(r1, r2);
300 	} else {
301 		r2.moveTo(bounds.left, bounds.top);
302 		r1.moveTo((time / 60) * _digitOffset, 0);
303 		_digits.copyToCurrentPort(r1, r2);
304 
305 		time %= 60;
306 		r2.moveTo(bounds.left + kGlobeCountdownOffset1, bounds.top);
307 		r1.moveTo((time / 10) * _digitOffset, 0);
308 		_digits.copyToCurrentPort(r1, r2);
309 
310 		r2.moveTo(bounds.left + kGlobeCountdownOffset2, bounds.top);
311 		r1.moveTo((time % 10) * _digitOffset, 0);
312 		_digits.copyToCurrentPort(r1, r2);
313 	}
314 }
315 
316 const int16 GlobeGame::_siloCoords[kNumAllSilos][2] = {
317 	{ 60, -151 }, // Anchorage, Alaska
318 	{ 6, 39 },    // Addis Ababa, Ethiopia
319 	{ -22, 44 },  // Antaro, Madagascar
320 	{ 30, -83 },  // Atlanta, Georgia
321 	{ -41, 173 }, // Auckland, New Zealand
322 	{ 39, -78 },  // Baltimore, Maryland
323 	{ 11, 101 },  // Bangkok, Thailand
324 	{ 2, -75 },   // Bogota, Colombia
325 	{ 46, 4 },    // Bonn, Germany
326 	{ 51, -7 },   // Dublin, Ireland
327 	{ 28, -1 },   // El Menia, Algeria
328 	{ 67, -111 }, // Ellesmere, Canada
329 	{ 43, -107 }, // Glasgow, Montana
330 	{ 61, -48 },  // Godthab, Greenland
331 	{ 19, -157 }, // Honolulu, Hawaii
332 	{ 6, 5 },     // Ibadan, Nigeria
333 	{ -29, 26 },  // Johannesburg, South Africa
334 	{ 46, 92 },   // Kobdo, Mongolia
335 	{ -15, -63 }, // La Paz, Bolivia
336 	{ -35, -61 }, // La Plata, Argentina
337 	{ -9, -76 },  // Lima, Peru
338 	{ 38, -4 },   // Madrid, Spain
339 	{ -8, -51 },  // Manaus, Brazil
340 	{ 13, 120 },  // Manila, Phillipines
341 	{ -35, 143 }, // Melbourne, Australia
342 	{ 60, -161 }, // Nome, Alaska
343 	{ -7, 142 },  // Papua, New Guinea
344 	{ -32, 117 }, // Perth, West Australia
345 	{ 34, -114 }, // Phoenix, Arizona
346 	{ 18, -71 },  // Port-Au-Prince, Haiti
347 	{ 42, -121 }, // Portland, Oregon
348 	{ 61, -20 },  // Reykjavik, Iceland
349 	{ -22, -46 }, // Rio de Janeiro
350 	{ 27, -101 }, // San Antonio, Texas
351 	{ 34, 126 },  // Seoul, Korea
352 	{ 37, -87 },  // Saint Louis, Missouri
353 	{ 60, 30 },   // Saint Petersberg, Russia
354 	{ 56, 12 },   // Stockholm, Sweden
355 	{ 51, 105 },  // Svortalsk, Siberia
356 	{ 36, -96 }   // Tulsa, Oklahoma
357 };
358 
359 const int16 GlobeGame::_targetSilo[kNumTargetSilos] = {
360 	14, 9, 1, 33, 6, 8, 34, 31, 38, 21
361 };
362 
363 const short GlobeGame::_timeLimit[kNumTargetSilos] = {
364 	120, 110, 100, 90, 80, 70, 60, 50, 40, 30
365 };
366 
367 const TimeValue GlobeGame::_siloName[kNumTargetSilos][2] = {
368 	{ kHonoluluIn, kHonoluluOut },
369 	{ kDublinIn, kDublinOut },
370 	{ kAddisAbabaIn, kAddisAbabaOut },
371 	{ kSanAntonioIn, kSanAntonioOut },
372 	{ kBangkokIn, kBangkokOut },
373 	{ kBonnIn, kBonnOut },
374 	{ kSeoulIn, kSeoulOut },
375 	{ kReykjavikIn, kReykjavikOut },
376 	{ kSvortalskIn, kSvortalskOut },
377 	{ kMadridIn, kMadridOut }
378 };
379 
380 // From globe room models
381 
382 static const GlobeGame::Point3D kCameraLocation = { 0.53f, 4.4f, -0.86f };
383 static const GlobeGame::Point3D kGlobeCenter = { -31.5f, 8.0f, 0.0f };
384 static const float kGlobeRadius = 8.25f;
385 static const int16 kDegreesPerLongSlice = 360 / kNumLongSlices;
386 static const int16 kDegreesPerLatSlice = 25;
387 static const int16 kLongOrigin = -95;
388 
389 // Other constants.
390 
391 static const float kTanFieldOfView = 0.7082373180482f;
392 static const float kPicturePlaneDistance = 10.0f; // Completely arbitrary.
393 static const int16 kLatError = 2;
394 static const int16 kLongError = 2;
395 static const TimeValue kGlobeMovieStartTime = 2 * 2 * kNumLongSlices * 600 / 15;
396 
397 static const TimeValue kTimePerGlobeFrame = 40;
398 
399 static const NotificationFlags kGlobeSplash1Finished = 1;
400 static const NotificationFlags kGlobeRobot1Finished = kGlobeSplash1Finished << 1;
401 static const NotificationFlags kGlobeRobot2Finished = kGlobeRobot1Finished << 1;
402 static const NotificationFlags kGlobeRobot3Finished = kGlobeRobot2Finished << 1;
403 static const NotificationFlags kGlobeRobot4Finished = kGlobeRobot3Finished << 1;
404 static const NotificationFlags kGlobeRobot5Finished = kGlobeRobot4Finished << 1;
405 static const NotificationFlags kGlobeRobot6Finished = kGlobeRobot5Finished << 1;
406 static const NotificationFlags kGlobeTimerExpired = kGlobeRobot6Finished << 1;
407 static const NotificationFlags kMaxDeactivatedFinished = kGlobeTimerExpired << 1;
408 
409 static const NotificationFlags kGlobeNotificationFlags = kGlobeSplash1Finished |
410 													kGlobeRobot1Finished |
411 													kGlobeRobot2Finished |
412 													kGlobeRobot3Finished |
413 													kGlobeRobot4Finished |
414 													kGlobeRobot5Finished |
415 													kGlobeRobot6Finished |
416 													kGlobeTimerExpired |
417 													kMaxDeactivatedFinished;
418 
419 enum {
420 	kSplash1End = 4,
421 	kSplash2End = 5,
422 	kSplash3Start = 8,
423 	kSplash3Stop = 9,
424 	kSplash4Start = 9,
425 	kSplash4Stop = 10,
426 	kNewLaunchSiloTime = 10,
427 	kSiloDeactivatedTime = 11,
428 	kMissileLaunchedTime = 12,
429 	kMaxDeactivatedStart = 13,
430 	kMaxDeactivatedStop = 23,
431 
432 	kGamePlaying = 1,
433 	kGameOver = 2
434 };
435 
436 enum {
437 	kGameIntro,
438 	kPlayingRobotIntro,
439 	kPlayingStrikeAuthorized,
440 	kPlayingPrimaryTarget,
441 	kPlayingNewSilo1,
442 	kPlayingNewSilo2,
443 	kPlayingNewSilo3,
444 	kPlayingTime,
445 	kPlayingInstructions,
446 	kWaitingForPlayer,
447 	kSiloDeactivated,
448 	kRobotTaunting,
449 	kDelayingPlayer,
450 	kPlayerWon1,
451 	kPlayerWon2,
452 	kPlayerLost1
453 };
454 
455 // TODO: Use ScummVM equivalent
456 static const float kPI = 3.1415926535f;
457 
degreesToRadians(float angle)458 float degreesToRadians(float angle) {
459 	return (angle * kPI) / 180;
460 }
461 
radiansToDegrees(float angle)462 float radiansToDegrees(float angle) {
463 	return (angle * 180) / kPI;
464 }
465 
GlobeGame(Neighborhood * handler)466 GlobeGame::GlobeGame(Neighborhood *handler) : GameInteraction(kNoradGlobeGameInteractionID, handler),
467 		_robotMovie(kGlobeRobotID), _monitorMovie(kGlobeMonitorID), _globeMovie(kGlobeMovieID),
468 		_upperNamesMovie(kGlobeUpperNamesID), _lowerNamesMovie(kGlobeLowerNamesID),
469 		_globeNotification(kNoradGlobeNotificationID, (PegasusEngine *)g_engine), _globeCircleLeft(kGlobeCircleLeftID),
470 		_globeCircleRight(kGlobeCircleRightID), _globeCircleUp(kGlobeCircleUpID), _globeCircleDown(kGlobeCircleDownID),
471 		_motionHighlightLeft(kMotionHiliteLeftID), _motionHighlightRight(kMotionHiliteRightID),
472 		_motionHighlightUp(kMotionHiliteUpID), _motionHighlightDown(kMotionHiliteDownID),
473 		_targetHighlightUpperLeft(kTargetHiliteUpperLeftID), _targetHighlightUpperRight(kTargetHiliteUpperRightID),
474 		_targetHighlightLowerLeft(kTargetHiliteLowerLeftID), _targetHighlightLowerRight(kTargetHiliteLowerRightID),
475 		_globeTracker(&_globeMovie, &_motionHighlightLeft, &_motionHighlightRight, &_motionHighlightUp,
476 				&_motionHighlightDown), _countdown(kGlobeCountdownID) {
477 	_neighborhoodNotification = handler->getNeighborhoodNotification();
478 }
479 
setSoundFXLevel(const uint16 fxLevel)480 void GlobeGame::setSoundFXLevel(const uint16 fxLevel) {
481 	_monitorMovie.setVolume(fxLevel);
482 }
483 
openInteraction()484 void GlobeGame::openInteraction() {
485 	if (((PegasusEngine *)g_engine)->isDVD()) {
486 		_robotMovie.initFromMovieFile("Images/Norad Delta/N79 Back Monitor1");
487 		_robotMovie.setVolume(((PegasusEngine *)g_engine)->getSoundFXLevel());
488 		_robotMovie.moveElementTo(kNavAreaLeft, kNavAreaTop);
489 		_robotMovie.setDisplayOrder(kGlobeMonitorLayer);
490 		_robotMovie.startDisplaying();
491 		_robotMovie.show();
492 
493 		_robotCallBack.setNotification(&_globeNotification);
494 		_robotCallBack.initCallBack(&_robotMovie, kCallBackAtExtremes);
495 		_robotCallBack.setCallBackFlag(kGlobeRobot1Finished);
496 		_robotCallBack.scheduleCallBack(kTriggerAtStop, 0, 0);
497 	}
498 
499 	_monitorMovie.initFromMovieFile("Images/Norad Delta/N79 Left Monitor");
500 	_monitorMovie.setVolume(((PegasusEngine *)g_engine)->getSoundFXLevel());
501 	_monitorMovie.moveElementTo(kGlobeMonitorLeft, kGlobeMonitorTop);
502 	_monitorMovie.setDisplayOrder(kGlobeMonitorLayer);
503 	_monitorMovie.startDisplaying();
504 	_monitorMovie.setSegment(0, kSplash1End * _monitorMovie.getScale());
505 	_monitorMovie.show();
506 
507 	_monitorCallBack.setNotification(&_globeNotification);
508 	_monitorCallBack.initCallBack(&_monitorMovie, kCallBackAtExtremes);
509 	_monitorCallBack.setCallBackFlag(kGlobeSplash1Finished);
510 	_monitorCallBack.scheduleCallBack(kTriggerAtStop, 0, 0);
511 
512 	_upperNamesMovie.initFromMovieFile("Images/Norad Delta/Upper Names");
513 	_upperNamesMovie.moveElementTo(kGlobeUpperNamesLeft, kGlobeUpperNamesTop);
514 	_upperNamesMovie.setDisplayOrder(kGlobeUpperNamesLayer);
515 	_upperNamesMovie.startDisplaying();
516 
517 	_lowerNamesMovie.initFromMovieFile("Images/Norad Delta/Lower Names");
518 	_lowerNamesMovie.moveElementTo(kGlobeLowerNamesLeft, kGlobeLowerNamesTop);
519 	_lowerNamesMovie.setDisplayOrder(kGlobeLowerNamesLayer);
520 	_lowerNamesMovie.startDisplaying();
521 
522 	_globeMovie.initFromMovieFile("Images/Norad Delta/Spinning Globe");
523 	_globeMovie.moveElementTo(kGlobeLeft, kGlobeTop);
524 	_globeMovie.setDisplayOrder(kGlobeMovieLayer);
525 	_globeMovie.startDisplaying();
526 	_globeMovie.setTime(kGlobeMovieStartTime);
527 	_globeMovie.redrawMovieWorld();
528 
529 	_globeCircleLeft.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kGlobeCircleLeftPICTID, true);
530 	_globeCircleLeft.moveElementTo(kGlobeCircleLeftLeft, kGlobeCircleLeftTop);
531 	_globeCircleLeft.setDisplayOrder(kGlobeCircleLayer);
532 	_globeCircleLeft.startDisplaying();
533 
534 	_globeCircleRight.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kGlobeCircleRightPICTID, true);
535 	_globeCircleRight.moveElementTo(kGlobeCircleRightLeft, kGlobeCircleRightTop);
536 	_globeCircleRight.setDisplayOrder(kGlobeCircleLayer);
537 	_globeCircleRight.startDisplaying();
538 
539 	_globeCircleUp.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kGlobeCircleUpPICTID, true);
540 	_globeCircleUp.moveElementTo(kGlobeCircleUpLeft, kGlobeCircleUpTop);
541 	_globeCircleUp.setDisplayOrder(kGlobeCircleLayer);
542 	_globeCircleUp.startDisplaying();
543 
544 	_globeCircleDown.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kGlobeCircleDownPICTID, true);
545 	_globeCircleDown.moveElementTo(kGlobeCircleDownLeft, kGlobeCircleDownTop);
546 	_globeCircleDown.setDisplayOrder(kGlobeCircleLayer);
547 	_globeCircleDown.startDisplaying();
548 
549 	_motionHighlightLeft.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kMotionHiliteLeftPICTID, true);
550 	_motionHighlightLeft.moveElementTo(kGlobeLeftMotionHiliteLeft, kGlobeLeftMotionHiliteTop);
551 	_motionHighlightLeft.setDisplayOrder(kGlobeHilitesLayer);
552 	_motionHighlightLeft.startDisplaying();
553 
554 	_motionHighlightRight.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kMotionHiliteRightPICTID, true);
555 	_motionHighlightRight.moveElementTo(kGlobeRightMotionHiliteLeft, kGlobeRightMotionHiliteTop);
556 	_motionHighlightRight.setDisplayOrder(kGlobeCircleLayer);
557 	_motionHighlightRight.startDisplaying();
558 
559 	_motionHighlightUp.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kMotionHiliteUpPICTID, true);
560 	_motionHighlightUp.moveElementTo(kGlobeUpMotionHiliteLeft, kGlobeUpMotionHiliteTop);
561 	_motionHighlightUp.setDisplayOrder(kGlobeHilitesLayer);
562 	_motionHighlightUp.startDisplaying();
563 
564 	_motionHighlightDown.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kMotionHiliteDownPICTID, true);
565 	_motionHighlightDown.moveElementTo(kGlobeDownMotionHiliteLeft, kGlobeDownMotionHiliteTop);
566 	_motionHighlightDown.setDisplayOrder(kGlobeHilitesLayer);
567 	_motionHighlightDown.startDisplaying();
568 
569 	_targetHighlightUpperLeft.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kTargetUpperLeftPICTID, true);
570 	_targetHighlightUpperLeft.moveElementTo(kGlobeUpperLeftHiliteLeft, kGlobeUpperLeftHiliteTop);
571 	_targetHighlightUpperLeft.setDisplayOrder(kGlobeHilitesLayer);
572 	_targetHighlightUpperLeft.startDisplaying();
573 
574 	_targetHighlightUpperRight.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kTargetUpperRightPICTID, true);
575 	_targetHighlightUpperRight.moveElementTo(kGlobeUpperRightHiliteLeft, kGlobeUpperRightHiliteTop);
576 	_targetHighlightUpperRight.setDisplayOrder(kGlobeHilitesLayer);
577 	_targetHighlightUpperRight.startDisplaying();
578 
579 	_targetHighlightLowerLeft.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kTargetLowerLeftPICTID, true);
580 	_targetHighlightLowerLeft.moveElementTo(kGlobeLowerLeftHiliteLeft, kGlobeLowerLeftHiliteTop);
581 	_targetHighlightLowerLeft.setDisplayOrder(kGlobeHilitesLayer);
582 	_targetHighlightLowerLeft.startDisplaying();
583 
584 	_targetHighlightLowerRight.initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, kTargetLowerRightPICTID, true);
585 	_targetHighlightLowerRight.moveElementTo(kGlobeLowerRightHiliteLeft, kGlobeLowerRightHiliteTop);
586 	_targetHighlightLowerRight.setDisplayOrder(kGlobeHilitesLayer);
587 	_targetHighlightLowerRight.startDisplaying();
588 
589 	_countdown.setDisplayOrder(kGlobeCountdownLayer);
590 	_countdown.moveElementTo(kGlobeCountdownLeft, kGlobeCountdownTop);
591 	_countdown.startDisplaying();
592 	_countdown.setCountdownTime(_timeLimit[0]);
593 
594 	_countdownCallBack.setNotification(&_globeNotification);
595 	_countdownCallBack.initCallBack(&_countdown, kCallBackAtExtremes);
596 	_countdownCallBack.setCallBackFlag(kGlobeTimerExpired);
597 	_countdownCallBack.scheduleCallBack(kTriggerAtStart, 0, 0);
598 
599 	_globeNotification.notifyMe(this, kGlobeNotificationFlags, kGlobeNotificationFlags);
600 
601 	_gameState = kGameIntro;
602 	_currentSiloIndex = 0;
603 	_playedInstructions = false;
604 
605 	_neighborhoodNotification->notifyMe(this, kDelayCompletedFlag | kSpotSoundCompletedFlag, kDelayCompletedFlag | kSpotSoundCompletedFlag);
606 }
607 
initInteraction()608 void GlobeGame::initInteraction() {
609 	if (((PegasusEngine *)g_engine)->isDVD())
610 		_robotMovie.start();
611 	_monitorMovie.start();
612 	_monitorMovie.redrawMovieWorld();
613 }
614 
closeInteraction()615 void GlobeGame::closeInteraction() {
616 	if (((PegasusEngine *)g_engine)->isDVD()) {
617 		_robotMovie.stop();
618 		_robotMovie.stopDisplaying();
619 		_robotMovie.releaseMovie();
620 		_robotCallBack.releaseCallBack();
621 	}
622 
623 	_monitorMovie.stop();
624 	_monitorMovie.stopDisplaying();
625 	_monitorMovie.releaseMovie();
626 	_monitorCallBack.releaseCallBack();
627 
628 	_globeMovie.stop();
629 	_globeMovie.stopDisplaying();
630 	_globeMovie.releaseMovie();
631 	_globeNotification.cancelNotification(this);
632 
633 	_upperNamesMovie.stop();
634 	_upperNamesMovie.stopDisplaying();
635 	_upperNamesMovie.releaseMovie();
636 
637 	_lowerNamesMovie.stop();
638 	_lowerNamesMovie.stopDisplaying();
639 	_lowerNamesMovie.releaseMovie();
640 
641 	_countdown.hide();
642 	_countdown.stopDisplaying();
643 	_countdownCallBack.releaseCallBack();
644 
645 	_globeCircleLeft.stopDisplaying();
646 	_globeCircleLeft.deallocateSurface();
647 	_globeCircleRight.stopDisplaying();
648 	_globeCircleRight.deallocateSurface();
649 	_globeCircleUp.stopDisplaying();
650 	_globeCircleUp.deallocateSurface();
651 	_globeCircleDown.stopDisplaying();
652 	_globeCircleDown.deallocateSurface();
653 
654 	_motionHighlightLeft.stopDisplaying();
655 	_motionHighlightLeft.deallocateSurface();
656 	_motionHighlightRight.stopDisplaying();
657 	_motionHighlightRight.deallocateSurface();
658 	_motionHighlightUp.stopDisplaying();
659 	_motionHighlightUp.deallocateSurface();
660 	_motionHighlightDown.stopDisplaying();
661 	_motionHighlightDown.deallocateSurface();
662 
663 	_targetHighlightUpperLeft.stopDisplaying();
664 	_targetHighlightUpperLeft.deallocateSurface();
665 	_targetHighlightUpperRight.stopDisplaying();
666 	_targetHighlightUpperRight.deallocateSurface();
667 	_targetHighlightLowerLeft.stopDisplaying();
668 	_targetHighlightLowerLeft.deallocateSurface();
669 	_targetHighlightLowerRight.stopDisplaying();
670 	_targetHighlightLowerRight.deallocateSurface();
671 
672 	_neighborhoodNotification->cancelNotification(this);
673 }
674 
receiveNotification(Notification * notification,const NotificationFlags flags)675 void GlobeGame::receiveNotification(Notification *notification, const NotificationFlags flags) {
676 	TimeScale scale = _monitorMovie.getScale();
677 
678 	if (notification == _neighborhoodNotification) {
679 		switch (_gameState) {
680 		case kPlayingRobotIntro:
681 			if (!((PegasusEngine *)g_engine)->isDVD()) {
682 				_monitorMovie.stop();
683 				_monitorMovie.setSegment(0, _monitorMovie.getDuration());
684 				_monitorMovie.setTime(kSplash2End * scale - 1);
685 				_monitorMovie.redrawMovieWorld();
686 				_monitorMovie.setFlags(0);
687 
688 				_owner->requestDelay(1, 2, kFilterNoInput, 0);
689 				_owner->requestSpotSound(kStrikeAuthorizedIn, kStrikeAuthorizedOut,
690 						kFilterNoInput, kSpotSoundCompletedFlag);
691 				_gameState = kPlayingStrikeAuthorized;
692 			}
693 			break;
694 		case kPlayingStrikeAuthorized:
695 			_monitorMovie.setSegment(kSplash3Start * scale, kSplash3Stop * scale);
696 			_monitorMovie.setTime(kSplash3Start * scale);
697 			_monitorMovie.redrawMovieWorld();
698 
699 			_owner->requestDelay(1, 3, kFilterNoInput, 0);
700 			_owner->requestSpotSound(kPrimaryTargetIn, kPrimaryTargetOut, kFilterNoInput, 0);
701 			_owner->requestDelay(1, 5, kFilterNoInput, kDelayCompletedFlag);
702 			_monitorMovie.start();
703 			_gameState = kPlayingPrimaryTarget;
704 			break;
705 		case kPlayingPrimaryTarget:
706 			_monitorMovie.stop();
707 			_monitorMovie.setSegment(0, _monitorMovie.getDuration());
708 			_monitorMovie.setTime(kNewLaunchSiloTime * scale);
709 			_monitorMovie.redrawMovieWorld();
710 			_owner->requestSpotSound(kNewLaunchSiloIn, kNewLaunchSiloOut, kFilterNoInput,
711 					kSpotSoundCompletedFlag);
712 			_gameState = kPlayingNewSilo1;
713 			break;
714 		case kPlayingNewSilo1:
715 			_monitorMovie.stop();
716 			_monitorMovie.setSegment(0, _monitorMovie.getDuration());
717 			_owner->requestDelay(1, 3, kFilterNoInput, kDelayCompletedFlag);
718 			_gameState = kPlayingNewSilo2;
719 			break;
720 		case kPlayingNewSilo2:
721 			_upperNamesMovie.show();
722 			_upperNamesMovie.setTime(_currentSiloIndex * _upperNamesMovie.getScale());
723 			_upperNamesMovie.redrawMovieWorld();
724 			_monitorMovie.setTime(kSplash4Stop * scale - 1);
725 			_monitorMovie.redrawMovieWorld();
726 			_owner->requestSpotSound(_siloName[_currentSiloIndex][0], _siloName[_currentSiloIndex][1], kFilterNoInput, 0);
727 			_owner->requestDelay(1, 3, kFilterNoInput, 0);
728 			_owner->requestSpotSound(kLaunchToProceedIn, kLaunchToProceedOut, kFilterNoInput, 0);
729 			_owner->requestDelay(1, 5, kFilterNoInput, kDelayCompletedFlag);
730 			_gameState = kPlayingNewSilo3;
731 			break;
732 		case kPlayingNewSilo3:
733 			_countdown.stopCountdown();
734 			_countdown.setCountdownTime(_timeLimit[_currentSiloIndex]);
735 			_countdown.show();
736 			_gameState = kPlayingTime;
737 
738 			if (_timeLimit[_currentSiloIndex] >= 120)
739 				_owner->requestSpotSound(kTwoMinutesIn, kTwoMinutesOut, kFilterNoInput, 0);
740 			else if (_timeLimit[_currentSiloIndex] >= 60)
741 				_owner->requestSpotSound(kOneMinuteIn, kOneMinuteOut, kFilterNoInput, 0);
742 
743 			switch (_timeLimit[_currentSiloIndex] % 60) {
744 			case 0:
745 				_owner->requestDelay(1, 5, kFilterNoInput, kDelayCompletedFlag);
746 				break;
747 			case 10:
748 				_owner->requestDelay(1, 5, kFilterNoInput, 0);
749 				_owner->requestSpotSound(kTenSecondsIn, kTenSecondsOut, kFilterNoInput,
750 						kSpotSoundCompletedFlag);
751 				break;
752 			case 20:
753 				_owner->requestDelay(1, 5, kFilterNoInput, 0);
754 				_owner->requestSpotSound(kTwentySecondsIn, kTwentySecondsOut,
755 						kFilterNoInput, kSpotSoundCompletedFlag);
756 				break;
757 			case 30:
758 				_owner->requestDelay(1, 5, kFilterNoInput, 0);
759 				_owner->requestSpotSound(kThirtySecondsIn, kThirtySecondsOut,
760 						kFilterNoInput, kSpotSoundCompletedFlag);
761 				break;
762 			case 40:
763 				_owner->requestDelay(1, 5, kFilterNoInput, 0);
764 				_owner->requestSpotSound(kFortySecondsIn, kFortySecondsOut,
765 						kFilterNoInput, kSpotSoundCompletedFlag);
766 				break;
767 			case 50:
768 				_owner->requestDelay(1, 5, kFilterNoInput, 0);
769 				_owner->requestSpotSound(kFiftySecondsIn, kFiftySecondsOut,
770 						kFilterNoInput, kSpotSoundCompletedFlag);
771 				break;
772 			default:
773 				break;
774 			}
775 			// fall through
776 			// FIXME: fall through intentional?
777 		case kPlayingTime:
778 			_gameState = kPlayingInstructions;
779 			_globeMovie.show();
780 			_globeCircleLeft.show();
781 			_globeCircleRight.show();
782 			_globeCircleUp.show();
783 			_globeCircleDown.show();
784 
785 			if (_playedInstructions) {
786 				receiveNotification(notification, flags);
787 			} else {
788 				_owner->requestSpotSound(kToDeactivateIn, kToDeactivateOut, kFilterNoInput,
789 						kSpotSoundCompletedFlag);
790 				_playedInstructions = true;
791 			}
792 			break;
793 		case kPlayingInstructions:
794 			_gameState = kWaitingForPlayer;
795 			_countdown.startCountdown();
796 			break;
797 		case kSiloDeactivated:
798 			_gameState = kRobotTaunting;
799 
800 			switch (_currentSiloIndex) {
801 			case 3:
802 				if (!((PegasusEngine *)g_engine)->isDVD()) {
803 					_owner->requestSpotSound(kYouCannotPossiblyIn, kYouCannotPossiblyOut,
804 							kFilterNoInput, kSpotSoundCompletedFlag);
805 				} else {
806 					_robotMovie.hide();
807 					_robotMovie.stopDisplaying();
808 					_robotMovie.releaseMovie();
809 
810 					_robotMovie.initFromMovieFile("Images/Norad Delta/N79 Back Monitor2");
811 					_robotMovie.setVolume(((PegasusEngine *)g_engine)->getSoundFXLevel());
812 					_robotMovie.moveElementTo(kNavAreaLeft, kNavAreaTop);
813 					_robotMovie.startDisplaying();
814 					_robotMovie.show();
815 					_robotMovie.start();
816 
817 					_owner->requestDelay(_robotMovie.getDuration(), 0, kFilterNoInput, kDelayCompletedFlag);
818 				}
819 				break;
820 			case 5:
821 				if (!((PegasusEngine *)g_engine)->isDVD()) {
822 					_owner->requestSpotSound(kYouWillFailIn, kYouWillFailOut, kFilterNoInput,
823 							kSpotSoundCompletedFlag);
824 				} else {
825 					_robotMovie.hide();
826 					_robotMovie.stopDisplaying();
827 					_robotMovie.releaseMovie();
828 
829 					_robotMovie.initFromMovieFile("Images/Norad Delta/N79 Back Monitor3");
830 					_robotMovie.setVolume(((PegasusEngine *)g_engine)->getSoundFXLevel());
831 					_robotMovie.moveElementTo(kNavAreaLeft, kNavAreaTop);
832 					_robotMovie.startDisplaying();
833 					_robotMovie.show();
834 					_robotMovie.start();
835 
836 					_owner->requestDelay(_robotMovie.getDuration(), 0, kFilterNoInput, kDelayCompletedFlag);
837 				}
838 				break;
839 			case 7:
840 				if (!((PegasusEngine *)g_engine)->isDVD()) {
841 					_owner->requestSpotSound(kGiveUpHumanIn, kGiveUpHumanOut, kFilterNoInput,
842 							kSpotSoundCompletedFlag);
843 				} else {
844 					_robotMovie.hide();
845 					_robotMovie.stopDisplaying();
846 					_robotMovie.releaseMovie();
847 
848 					_robotMovie.initFromMovieFile("Images/Norad Delta/N79 Back Monitor4");
849 					_robotMovie.setVolume(((PegasusEngine *)g_engine)->getSoundFXLevel());
850 					_robotMovie.moveElementTo(kNavAreaLeft, kNavAreaTop);
851 					_robotMovie.startDisplaying();
852 					_robotMovie.show();
853 					_robotMovie.start();
854 
855 					_owner->requestDelay(_robotMovie.getDuration(), 0, kFilterNoInput, kDelayCompletedFlag);
856 				}
857 				break;
858 			case 9:
859 				if (!((PegasusEngine *)g_engine)->isDVD()) {
860 					_owner->requestSpotSound(kYouAreRunningIn, kYouAreRunningOut,
861 							kFilterNoInput, kSpotSoundCompletedFlag);
862 				} else {
863 					_robotMovie.hide();
864 					_robotMovie.stopDisplaying();
865 					_robotMovie.releaseMovie();
866 
867 					_robotMovie.initFromMovieFile("Images/Norad Delta/N79 Back Monitor5");
868 					_robotMovie.setVolume(((PegasusEngine *)g_engine)->getSoundFXLevel());
869 					_robotMovie.moveElementTo(kNavAreaLeft, kNavAreaTop);
870 					_robotMovie.startDisplaying();
871 					_robotMovie.show();
872 					_robotMovie.start();
873 
874 					_owner->requestDelay(_robotMovie.getDuration(), 0, kFilterNoInput, kDelayCompletedFlag);
875 				}
876 				break;
877 			default:
878 				_owner->requestSpotSound(kNewLaunchSiloIn, kNewLaunchSiloOut,
879 						kFilterNoInput, kSpotSoundCompletedFlag);
880 				_monitorMovie.setTime(kNewLaunchSiloTime * scale);
881 				_monitorMovie.redrawMovieWorld();
882 				_gameState = kPlayingNewSilo1;
883 				break;
884 			}
885 			break;
886 		case kRobotTaunting:
887 			_owner->requestDelay(1, 2, kFilterNoInput, 0);
888 			_owner->requestSpotSound(kNewLaunchSiloIn, kNewLaunchSiloOut, kFilterNoInput, kSpotSoundCompletedFlag);
889 			_monitorMovie.setTime(kNewLaunchSiloTime * scale);
890 			_monitorMovie.redrawMovieWorld();
891 			_gameState = kPlayingNewSilo1;
892 			break;
893 		case kDelayingPlayer:
894 			_gameState = kWaitingForPlayer;
895 			break;
896 		case kPlayerLost1:
897 			_owner->recallToTSAFailure();
898 			break;
899 		case kPlayerWon2:
900 			((NoradDelta *)_owner)->finishedGlobeGame();
901 			_owner->requestDeleteCurrentInteraction();
902 			break;
903 		default:
904 			break;
905 		}
906 	} else if (notification == &_globeNotification) {
907 		ExtraTable::Entry entry;
908 
909 		switch (flags) {
910 		case kGlobeSplash1Finished:
911 			_monitorMovie.stop();
912 			_monitorMovie.setSegment(kSplash1End * scale, kSplash2End * scale);
913 			_monitorMovie.setFlags(kLoopTimeBase);
914 			_monitorMovie.start();
915 			if (!((PegasusEngine *)g_engine)->isDVD()) {
916 				_owner->getExtraEntry(kN79BrightView, entry);
917 				_owner->showViewFrame(entry.movieStart);
918 				_owner->requestSpotSound(kIJustBrokeIn, kIJustBrokeOut, kFilterNoInput, 0);
919 				_owner->requestDelay(1, 2, kFilterNoInput, kDelayCompletedFlag);
920 				_gameState = kPlayingRobotIntro;
921 			}
922 			break;
923 		case kGlobeRobot1Finished:
924 			if (((PegasusEngine *)g_engine)->isDVD()) {
925 				_owner->getExtraEntry(kN79BrightView, entry);
926 				_monitorMovie.stop();
927 				_monitorMovie.setSegment(0, _monitorMovie.getDuration());
928 				_monitorMovie.setTime(kSplash2End * scale - 1);
929 				_monitorMovie.redrawMovieWorld();
930 				_monitorMovie.setFlags(0);
931 
932 				_owner->showViewFrame(entry.movieStart);
933 				_owner->requestDelay(1, 2, kFilterNoInput, 0);
934 				_owner->requestSpotSound(kStrikeAuthorizedIn, kStrikeAuthorizedOut,
935 						kFilterNoInput, kSpotSoundCompletedFlag);
936 
937 				_gameState = kPlayingStrikeAuthorized;
938 			}
939 			break;
940 		case kGlobeTimerExpired:
941 			// Missile launched, player loses.
942 			_upperNamesMovie.hide();
943 			_lowerNamesMovie.hide();
944 			_countdown.hide();
945 			_monitorMovie.setTime(kMissileLaunchedTime * scale);
946 			_monitorMovie.redrawMovieWorld();
947 			_owner->requestSpotSound(kMissileLaunchedIn, kMissileLaunchedOut, kFilterNoInput, kSpotSoundCompletedFlag);
948 			_gameState = kPlayerLost1;
949 			break;
950 		case kMaxDeactivatedFinished:
951 			_monitorMovie.stop();
952 			_monitorMovie.setSegment(0, _monitorMovie.getDuration());
953 			if (g_arthurChip)
954 				g_arthurChip->playArthurMovieForEvent("Images/AI/Globals/XGLOBA02", kArthurNoradFinishedGlobeGame);
955 			if (!((PegasusEngine *)g_engine)->isDVD()) {
956 				_owner->requestDelay(1, 2, kFilterNoInput, 0);
957 				_owner->requestSpotSound(kTheOnlyGoodHumanIn, kTheOnlyGoodHumanOut, kFilterNoInput, 0);
958 				_owner->requestDelay(1, 2, kFilterNoInput, kDelayCompletedFlag);
959 			} else {
960 				_robotMovie.hide();
961 				_robotMovie.stopDisplaying();
962 				_robotMovie.releaseMovie();
963 
964 				_robotMovie.initFromMovieFile("Images/Norad Delta/N79 Back Monitor6");
965 				_robotMovie.setVolume(((PegasusEngine *)g_engine)->getSoundFXLevel());
966 				_robotMovie.moveElementTo(kNavAreaLeft, kNavAreaTop);
967 				_robotMovie.setDisplayOrder(kGlobeCountdownLayer + 1);
968 				_robotMovie.startDisplaying();
969 				_robotMovie.show();
970 				_robotMovie.start();
971 
972 				_owner->requestDelay(_robotMovie.getDuration(), 0, kFilterNoInput, kDelayCompletedFlag);
973 			}
974 			_gameState = kPlayerWon2;
975 			break;
976 		default:
977 			break;
978 		}
979 	}
980 }
981 
982 // Prevent the player from getting up until the game is over.
983 
handleInput(const Input & input,const Hotspot * cursorSpot)984 void GlobeGame::handleInput(const Input &input, const Hotspot *cursorSpot) {
985 	Common::Point where;
986 	input.getInputLocation(where);
987 	Hotspot *spot = g_allHotspots.findHotspot(where);
988 
989 	if (((PegasusEngine *)g_engine)->_cursor->isVisible() && spot != 0 &&
990 			spot->getObjectID() == kNorad79SiloAreaSpotID && findClickedSilo(input) != -1) {
991 		_targetHighlightUpperLeft.show();
992 		_targetHighlightUpperRight.show();
993 		_targetHighlightLowerLeft.show();
994 		_targetHighlightLowerRight.show();
995 	} else {
996 		_targetHighlightUpperLeft.hide();
997 		_targetHighlightUpperRight.hide();
998 		_targetHighlightLowerLeft.hide();
999 		_targetHighlightLowerRight.hide();
1000 	}
1001 
1002 	// Interrupt certain inputs to prevent player from switching modes.
1003 	InputHandler::handleInput(input, cursorSpot);
1004 }
1005 
findClickedSilo(const Input & input)1006 int16 GlobeGame::findClickedSilo(const Input &input) {
1007 	Common::Point screenPoint;
1008 	input.getInputLocation(screenPoint);
1009 	screenPoint.x -= kNavAreaLeft;
1010 	screenPoint.y -= kNavAreaTop;
1011 
1012 	Line3D ray;
1013 	screenPointTo3DPoint(screenPoint.x, screenPoint.y, ray.pt2);
1014 	ray.pt1 = kCameraLocation;
1015 
1016 	Point3D globePoint;
1017 	if (lineHitsGlobe(ray, globePoint)) {
1018 		int16 latOrigin, longOrigin, latitude, longitude;
1019 		globeMovieFrameToOrigin(_globeMovie.getTime() / kTimePerGlobeFrame, latOrigin, longOrigin);
1020 		globePointToLatLong(globePoint, latOrigin, longOrigin, latitude, longitude);
1021 
1022 		for (int16 i = 0; i < kNumAllSilos; i++)
1023 			if (_siloCoords[i][0] >= latitude - kLatError && _siloCoords[i][0] <= latitude + kLatError &&
1024 					_siloCoords[i][1] >= longitude - kLongError && _siloCoords[i][1] <= longitude + kLongError)
1025 				return i;
1026 	}
1027 
1028 	return -1;
1029 }
1030 
spinGlobe(const Input & input,const Hotspot * spot,GlobeTrackDirection trackDirection)1031 void GlobeGame::spinGlobe(const Input &input, const Hotspot *spot, GlobeTrackDirection trackDirection) {
1032 	_globeTracker.setTrackParameters(spot, trackDirection);
1033 	_globeTracker.startTracking(input);
1034 }
1035 
clickGlobe(const Input & input)1036 void GlobeGame::clickGlobe(const Input &input) {
1037 	Movie movie(kNoDisplayElement);
1038 	Input movieInput;
1039 
1040 	if (((PegasusEngine *)g_engine)->isDVD() && JMPPPInput::isEasterEggModifierInput(input)) {
1041 		((PegasusEngine *)g_engine)->_cursor->hide();
1042 
1043 		movie.initFromMovieFile("Images/Norad Delta/N79 Back Monitor7");
1044 		movie.setVolume(((PegasusEngine *)g_engine)->getSoundFXLevel());
1045 		movie.moveElementTo(kNavAreaLeft, kNavAreaTop);
1046 		movie.setDisplayOrder(kGlobeCountdownLayer + 1);
1047 		movie.startDisplaying();
1048 		movie.show();
1049 		movie.start();
1050 
1051 		while (movie.isRunning() && !((PegasusEngine *)g_engine)->shouldQuit()) {
1052 			InputDevice.getInput(movieInput, kFilterNoInput);
1053 
1054 			((PegasusEngine *)g_engine)->checkCallBacks();
1055 			((PegasusEngine *)g_engine)->refreshDisplay();
1056 			((PegasusEngine *)g_engine)->_system->delayMillis(10);
1057 		}
1058 
1059 		if (((PegasusEngine *)g_engine)->shouldQuit())
1060 			return;
1061 
1062 		movie.hide();
1063 		movie.stopDisplaying();
1064 		movie.releaseMovie();
1065 
1066 		((PegasusEngine *)g_engine)->_cursor->hideUntilMoved();
1067 	} else {
1068 		int16 newSilo = findClickedSilo(input);
1069 		if (newSilo != -1) {
1070 			_targetHighlightUpperLeft.hide();
1071 			_targetHighlightUpperRight.hide();
1072 			_targetHighlightLowerLeft.hide();
1073 			_targetHighlightLowerRight.hide();
1074 			_lowerNamesMovie.show();
1075 			_lowerNamesMovie.setTime(newSilo * _lowerNamesMovie.getScale());
1076 			_lowerNamesMovie.redrawMovieWorld();
1077 			_owner->requestSpotSound(kSiloBeepIn, kSiloBeepOut, kFilterNoInput, 0);
1078 
1079 			if (newSilo == _targetSilo[_currentSiloIndex]) {
1080 				_currentSiloIndex++;
1081 				_countdown.stopCountdown();
1082 				_owner->requestSpotSound(kSiloDeactivatedIn, kSiloDeactivatedOut, kFilterNoInput, 0);
1083 
1084 				if (_currentSiloIndex == kNumTargetSilos) {
1085 					// Player won.
1086 					_owner->requestDelay(1, 2, kFilterNoInput, 0);
1087 					_upperNamesMovie.hide();
1088 					_lowerNamesMovie.hide();
1089 					_countdown.hide();
1090 					_monitorMovie.setSegment(kMaxDeactivatedStart * _monitorMovie.getScale(),
1091 							kMaxDeactivatedStop * _monitorMovie.getScale());
1092 					_monitorMovie.setTime(kMaxDeactivatedStart * _monitorMovie.getScale());
1093 					_monitorCallBack.setCallBackFlag(kMaxDeactivatedFinished);
1094 					_monitorCallBack.scheduleCallBack(kTriggerAtStop, 0, 0);
1095 					_monitorMovie.start();
1096 					_owner->requestSpotSound(kMaximumDeactivationIn, kMaximumDeactivationOut,
1097 							kFilterNoInput, kSpotSoundCompletedFlag);
1098 
1099 					// This sound was left out of the original.
1100 					_owner->requestSpotSound(kAllSilosDeactivatedIn, kAllSilosDeactivatedOut,
1101 							kFilterNoInput, kSpotSoundCompletedFlag);
1102 
1103 					_gameState = kPlayerWon1;
1104 				} else {
1105 					_owner->requestDelay(1, 2, kFilterNoInput, kDelayCompletedFlag);
1106 					_upperNamesMovie.hide();
1107 					_lowerNamesMovie.hide();
1108 					_countdown.hide();
1109 					_monitorMovie.setTime(kSiloDeactivatedTime * _monitorMovie.getScale());
1110 					_monitorMovie.redrawMovieWorld();
1111 					_gameState = kSiloDeactivated;
1112 				}
1113 			} else {
1114 				_owner->requestDelay(5, 1, kFilterNoInput, kDelayCompletedFlag);
1115 				_gameState = kDelayingPlayer;
1116 				// Play "incorrect" sound?
1117 				if (g_arthurChip)
1118 					g_arthurChip->playArthurMovieForEvent("Images/AI/Globals/XGLOBB38", kArthurNoradSelectedIncorrectSilo);
1119 			}
1120 		}
1121 	}
1122 }
1123 
clickInHotspot(const Input & input,const Hotspot * spot)1124 void GlobeGame::clickInHotspot(const Input &input, const Hotspot *spot) {
1125 	switch (spot->getObjectID()) {
1126 	case kNorad79SpinLeftSpotID:
1127 		spinGlobe(input, spot, kTrackLeft);
1128 		break;
1129 	case kNorad79SpinRightSpotID:
1130 		spinGlobe(input, spot, kTrackRight);
1131 		break;
1132 	case kNorad79SpinUpSpotID:
1133 		spinGlobe(input, spot, kTrackUp);
1134 		break;
1135 	case kNorad79SpinDownSpotID:
1136 		spinGlobe(input, spot, kTrackDown);
1137 		break;
1138 	case kNorad79SiloAreaSpotID:
1139 		clickGlobe(input);
1140 		break;
1141 	default:
1142 		GameInteraction::clickInHotspot(input, spot);
1143 		break;
1144 	}
1145 }
1146 
activateHotspots()1147 void GlobeGame::activateHotspots() {
1148 	GameInteraction::activateHotspots();
1149 
1150 	switch (_gameState) {
1151 	case kWaitingForPlayer:
1152 		g_allHotspots.deactivateOneHotspot(kNorad79WestOutSpotID);
1153 		g_allHotspots.activateOneHotspot(kNorad79SpinLeftSpotID);
1154 		g_allHotspots.activateOneHotspot(kNorad79SpinRightSpotID);
1155 		g_allHotspots.activateOneHotspot(kNorad79SpinUpSpotID);
1156 		g_allHotspots.activateOneHotspot(kNorad79SpinDownSpotID);
1157 		g_allHotspots.activateOneHotspot(kNorad79SiloAreaSpotID);
1158 		break;
1159 	default:
1160 		g_allHotspots.deactivateOneHotspot(kNorad79WestOutSpotID);
1161 		break;
1162 	}
1163 }
1164 
globeMovieFrameToOrigin(int16 frameNum,int16 & latOrigin,int16 & longOrigin)1165 void GlobeGame::globeMovieFrameToOrigin(int16 frameNum, int16 &latOrigin, int16 &longOrigin) {
1166 	latOrigin = kDegreesPerLatSlice * 2 - (frameNum / (kNumLongSlices * 2)) * kDegreesPerLatSlice;
1167 	frameNum %= kNumLongSlices * 2;
1168 
1169 	if (frameNum >= kNumLongSlices)
1170 		longOrigin = kLongOrigin + (kNumLongSlices * 2 - 1 - frameNum) * kDegreesPerLongSlice;
1171 	else
1172 		longOrigin = kLongOrigin + frameNum * kDegreesPerLongSlice;
1173 
1174 	if (longOrigin > 180)
1175 		longOrigin -= 360;
1176 }
1177 
globePointToLatLong(const GlobeGame::Point3D & pt,int16 latOrigin,int16 longOrigin,int16 & latitude,int16 & longitude)1178 void GlobeGame::globePointToLatLong(const GlobeGame::Point3D &pt, int16 latOrigin, int16 longOrigin,
1179 		int16 &latitude, int16 &longitude) {
1180 	Point3D scratch = pt;
1181 
1182 	// Translate globe center to origin.
1183 	scratch.x -= kGlobeCenter.x;
1184 	scratch.y -= kGlobeCenter.y;
1185 	scratch.z -= kGlobeCenter.z;
1186 
1187 	// Rotate around z axis latOrigin degrees to bring equator parallel with XZ plane
1188 	float theta = degreesToRadians(latOrigin);
1189 	float s = sin(theta);
1190 	float c = cos(theta);
1191 	float x = scratch.x * c - scratch.y * s;
1192 	float y = scratch.y * c + scratch.x * s;
1193 	scratch.x = x;
1194 	scratch.y = y;
1195 
1196 	// Calculate latitude
1197 	latitude = (int16)radiansToDegrees(asin(scratch.y / kGlobeRadius));
1198 
1199 	// Rotate around y axis longOrigin degrees to bring longitude 0 to positive X axis
1200 	theta = degreesToRadians(longOrigin);
1201 	s = sin(theta);
1202 	c = cos(theta);
1203 	x = scratch.x * c - scratch.z * s;
1204 	float z = scratch.z * c + scratch.x * s;
1205 	scratch.x = x;
1206 	scratch.z = z;
1207 
1208 	// Calculate longitude
1209 	longitude = (int16)radiansToDegrees(acos(scratch.x / sqrt(scratch.x * scratch.x + scratch.z * scratch.z)));
1210 
1211 	if (scratch.z < 0)
1212 		longitude = -longitude;
1213 }
1214 
1215 // h, v in [0, 511][0, 255]
1216 // Looking down negative x axis.
screenPointTo3DPoint(int16 h,int16 v,GlobeGame::Point3D & pt)1217 void GlobeGame::screenPointTo3DPoint(int16 h, int16 v, GlobeGame::Point3D &pt) {
1218 	pt.x = kCameraLocation.x - kPicturePlaneDistance;
1219 	pt.y = kCameraLocation.y + (128 - v) * kPicturePlaneDistance * kTanFieldOfView / 256;
1220 	pt.z = kCameraLocation.z + (h - 256) * kPicturePlaneDistance * kTanFieldOfView / 256;
1221 }
1222 
1223 // Fundamentals of Three-Dimensional Graphics, by Alan Watt
1224 // pp. 163-164
lineHitsGlobe(const GlobeGame::Line3D & line,GlobeGame::Point3D & pt)1225 bool GlobeGame::lineHitsGlobe(const GlobeGame::Line3D &line, GlobeGame::Point3D &pt) {
1226 	float i = line.pt2.x - line.pt1.x;
1227 	float j = line.pt2.y - line.pt1.y;
1228 	float k = line.pt2.z - line.pt1.z;
1229 	float a = i * i + j * j + k * k;
1230 	float b = 2 * i * (line.pt1.x - kGlobeCenter.x) + 2 * j * (line.pt1.y - kGlobeCenter.y) +
1231 			2 * k * (line.pt1.z - kGlobeCenter.z);
1232 	float c = kGlobeCenter.x * kGlobeCenter.x + kGlobeCenter.y * kGlobeCenter.y +
1233 			kGlobeCenter.z * kGlobeCenter.z + line.pt1.x * line.pt1.x + line.pt1.y * line.pt1.y +
1234 			line.pt1.z * line.pt1.z + -2 * (kGlobeCenter.x * line.pt1.x + kGlobeCenter.y * line.pt1.y +
1235 			kGlobeCenter.z * line.pt1.z) - kGlobeRadius * kGlobeRadius;
1236 
1237 	// Solve quadratic equation of a, b, c.
1238 	float t = b * b - 4 * a * c;
1239 
1240 	if (t >= 0.0f) {
1241 		// Return smaller root, which corresponds to closest intersection point.
1242 		t = (-b - sqrt(t)) / (2 * a);
1243 		pt.x = i * t + line.pt1.x;
1244 		pt.y = j * t + line.pt1.y;
1245 		pt.z = k * t + line.pt1.z;
1246 		return true;
1247 	}
1248 
1249 	return false;
1250 }
1251 
canSolve()1252 bool GlobeGame::canSolve() {
1253 	return	_gameState != kPlayerWon1 && _gameState != kPlayerWon2 && _gameState != kPlayerLost1;
1254 }
1255 
doSolve()1256 void GlobeGame::doSolve() {
1257 	_owner->requestDelay(1, 2, kFilterNoInput, 0);
1258 	_upperNamesMovie.hide();
1259 	_lowerNamesMovie.hide();
1260 	_countdown.hide();
1261 	_monitorMovie.setSegment(kMaxDeactivatedStart * _monitorMovie.getScale() + (kSiloDeactivatedOut - kSiloDeactivatedIn), kMaxDeactivatedStop * _monitorMovie.getScale());
1262 	_monitorMovie.setTime(kMaxDeactivatedStart * _monitorMovie.getScale() + (kSiloDeactivatedOut - kSiloDeactivatedIn));
1263 	_monitorCallBack.setCallBackFlag(kMaxDeactivatedFinished);
1264 	_monitorCallBack.scheduleCallBack(kTriggerAtStop, 0, 0);
1265 	_monitorMovie.start();
1266 	_owner->requestSpotSound(kMaximumDeactivationIn, kMaximumDeactivationOut, kFilterNoInput, kSpotSoundCompletedFlag);
1267 	_owner->requestSpotSound(kAllSilosDeactivatedIn, kAllSilosDeactivatedOut, kFilterNoInput, kSpotSoundCompletedFlag);
1268 	_gameState = kPlayerWon1;
1269 }
1270 
1271 } // End of namespace Pegasus
1272