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