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