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