Line data Source code
1 0 : /*
2 :
3 : GameController.m
4 :
5 : Oolite
6 : Copyright (C) 2004-2013 Giles C Williams and contributors
7 :
8 : This program is free software; you can redistribute it and/or
9 : modify it under the terms of the GNU General Public License
10 : as published by the Free Software Foundation; either version 2
11 : of the License, or (at your option) any later version.
12 :
13 : This program is distributed in the hope that it will be useful,
14 : but WITHOUT ANY WARRANTY; without even the implied warranty of
15 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 : GNU General Public License for more details.
17 :
18 : You should have received a copy of the GNU General Public License
19 : along with this program; if not, write to the Free Software
20 : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21 : MA 02110-1301, USA.
22 :
23 : */
24 :
25 : #import "GameController.h"
26 : #import "Universe.h"
27 : #import "ResourceManager.h"
28 : #import "MyOpenGLView.h"
29 : #import "OOSound.h"
30 : #import "OOOpenGL.h"
31 : #import "PlayerEntityLoadSave.h"
32 : #include <stdlib.h>
33 : #import "OOCollectionExtractors.h"
34 : #import "OOOXPVerifier.h"
35 : #import "OOLoggingExtended.h"
36 : #import "NSFileManagerOOExtensions.h"
37 : #import "OOLogOutputHandler.h"
38 : #import "OODebugFlags.h"
39 : #import "OOJSFrameCallbacks.h"
40 : #import "OOOpenGLExtensionManager.h"
41 : #import "OOOpenALController.h"
42 : #import "OODebugSupport.h"
43 : #import "legacy_random.h"
44 : #import "OOOXZManager.h"
45 : #import "OOOpenGLMatrixManager.h"
46 :
47 : #if OOLITE_MAC_OS_X
48 : #import "JAPersistentFileReference.h"
49 : #import <Sparkle/Sparkle.h>
50 : #import "OoliteApp.h"
51 : #import "OOMacJoystickManager.h"
52 :
53 : static void SetUpSparkle(void);
54 : #elif (OOLITE_GNUSTEP && !defined(NDEBUG))
55 : #import "OODebugMonitor.h"
56 : #endif
57 :
58 :
59 0 : static GameController *sSharedController = nil;
60 :
61 :
62 : @interface GameController (OOPrivate)
63 :
64 0 : - (void)reportUnhandledStartupException:(NSException *)exception;
65 :
66 0 : - (void)doPerformGameTick;
67 :
68 : @end
69 :
70 :
71 : @implementation GameController
72 :
73 : + (GameController *) sharedController
74 : {
75 : if (sSharedController == nil)
76 : {
77 : sSharedController = [[self alloc] init];
78 : }
79 : return sSharedController;
80 : }
81 :
82 :
83 0 : - (id) init
84 : {
85 : if (sSharedController != nil)
86 : {
87 : [self release];
88 : [NSException raise:NSInternalInconsistencyException format:@"%s: expected only one GameController to exist at a time.", __PRETTY_FUNCTION__];
89 : }
90 :
91 : if ((self = [super init]))
92 : {
93 : _finishedLaunching = NO;
94 : last_timeInterval = [NSDate timeIntervalSinceReferenceDate];
95 : delta_t = 0.01; // one hundredth of a second
96 : _animationTimerInterval = [[NSUserDefaults standardUserDefaults] oo_doubleForKey:@"animation_timer_interval" defaultValue:MINIMUM_ANIMATION_TICK];
97 :
98 : // rather than seeding this with the date repeatedly, seed it
99 : // once here at startup
100 : ranrot_srand((uint32_t)[[NSDate date] timeIntervalSince1970]); // reset randomiser with current time
101 :
102 : _splashStart = [[NSDate alloc] init];
103 : }
104 :
105 : return self;
106 : }
107 :
108 :
109 0 : - (void) dealloc
110 : {
111 : #if OOLITE_MAC_OS_X
112 : [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:UNIVERSE];
113 : #endif
114 :
115 : [timer release];
116 : [gameView release];
117 : [UNIVERSE release];
118 :
119 : [playerFileToLoad release];
120 : [playerFileDirectory release];
121 : [expansionPathsToInclude release];
122 :
123 : [super dealloc];
124 : }
125 :
126 :
127 : - (BOOL) isGamePaused
128 : {
129 : return gameIsPaused;
130 : }
131 :
132 :
133 : - (void) setGamePaused:(BOOL)value
134 : {
135 : if (value && !gameIsPaused)
136 : {
137 : _resumeMode = [self mouseInteractionMode];
138 : [self setMouseInteractionModeForUIWithMouseInteraction:NO];
139 : gameIsPaused = YES;
140 : [PLAYER doScriptEvent:OOJSID("gamePaused")];
141 : }
142 : else if (!value && gameIsPaused)
143 : {
144 : [self setMouseInteractionMode:_resumeMode];
145 : gameIsPaused = NO;
146 : [PLAYER doScriptEvent:OOJSID("gameResumed")];
147 : }
148 : }
149 :
150 :
151 : - (OOMouseInteractionMode) mouseInteractionMode
152 : {
153 : return _mouseMode;
154 : }
155 :
156 :
157 : - (void) setMouseInteractionMode:(OOMouseInteractionMode)mode
158 : {
159 : OOMouseInteractionMode oldMode = _mouseMode;
160 : if (mode == oldMode) return;
161 :
162 : _mouseMode = mode;
163 : OOLog(@"input.mouseMode.changed", @"Mouse interaction mode changed from %@ to %@", OOStringFromMouseInteractionMode(oldMode), OOStringFromMouseInteractionMode(mode));
164 :
165 : #if OO_USE_FULLSCREEN_CONTROLLER
166 : if ([self inFullScreenMode])
167 : {
168 : [_fullScreenController noteMouseInteractionModeChangedFrom:oldMode to:mode];
169 : }
170 : else
171 : #endif
172 : {
173 : [[self gameView] noteMouseInteractionModeChangedFrom:oldMode to:mode];
174 : }
175 : }
176 :
177 :
178 : - (void) setMouseInteractionModeForFlight
179 : {
180 : [self setMouseInteractionMode:[PLAYER isMouseControlOn] ? MOUSE_MODE_FLIGHT_WITH_MOUSE_CONTROL : MOUSE_MODE_FLIGHT_NO_MOUSE_CONTROL];
181 : }
182 :
183 :
184 : - (void) setMouseInteractionModeForUIWithMouseInteraction:(BOOL)interaction
185 : {
186 : [self setMouseInteractionMode:interaction ? MOUSE_MODE_UI_SCREEN_WITH_INTERACTION : MOUSE_MODE_UI_SCREEN_NO_INTERACTION];
187 : }
188 :
189 :
190 : - (MyOpenGLView *) gameView
191 : {
192 : return gameView;
193 : }
194 :
195 :
196 : - (void) setGameView:(MyOpenGLView *)view
197 : {
198 : [gameView release];
199 : gameView = [view retain];
200 : [gameView setGameController:self];
201 : [UNIVERSE setGameView:gameView];
202 : }
203 :
204 :
205 : - (void) applicationDidFinishLaunching:(NSNotification *)notification
206 : {
207 : NSAutoreleasePool *pool = nil;
208 : unsigned i;
209 :
210 : pool = [[NSAutoreleasePool alloc] init];
211 :
212 : @try
213 : {
214 : // if not verifying oxps, ensure that gameView is drawn to using beginSplashScreen
215 : // OpenGL is initialised and that allows textures to initialise too.
216 :
217 : #if OO_OXP_VERIFIER_ENABLED
218 :
219 : if ([OOOXPVerifier runVerificationIfRequested])
220 : {
221 : [self exitAppWithContext:@"OXP verifier run"];
222 : }
223 : else
224 : {
225 : [self beginSplashScreen];
226 : }
227 :
228 : #else
229 : [self beginSplashScreen];
230 : #endif
231 :
232 : #if OOLITE_MAC_OS_X
233 : [OOJoystickManager setStickHandlerClass:[OOMacJoystickManager class]];
234 : SetUpSparkle();
235 : #endif
236 :
237 : [self setUpDisplayModes];
238 :
239 : // moved to before the Universe is created
240 : if (expansionPathsToInclude)
241 : {
242 : for (i = 0; i < [expansionPathsToInclude count]; i++)
243 : {
244 : [ResourceManager addExternalPath: (NSString*)[expansionPathsToInclude objectAtIndex: i]];
245 : }
246 : }
247 :
248 : // initialise OXZ manager
249 : [OOOXZManager sharedManager];
250 :
251 : // moved here to try to avoid initialising this before having an Open GL context
252 : //[self logProgress:DESC(@"Initialising universe")]; // DESC expansions only possible after Universe init
253 : [[Universe alloc] initWithGameView:gameView];
254 :
255 : [self loadPlayerIfRequired];
256 :
257 : [self logProgress:@""];
258 :
259 : // get the run loop and add the call to performGameTick:
260 : [self startAnimationTimer];
261 :
262 : [self endSplashScreen];
263 : }
264 : @catch (NSException *exception)
265 : {
266 : [self reportUnhandledStartupException:exception];
267 : exit(EXIT_FAILURE);
268 : }
269 :
270 : OOLog(@"startup.complete", @"========== Loading complete in %.2f seconds. ==========", -[_splashStart timeIntervalSinceNow]);
271 :
272 : #if OO_USE_FULLSCREEN_CONTROLLER
273 : [self setFullScreenMode:[[NSUserDefaults standardUserDefaults] boolForKey:@"fullscreen"]];
274 : #endif
275 :
276 : _finishedLaunching = YES;
277 :
278 : // Release anything allocated above that is not required.
279 : [pool release];
280 :
281 : #if !OOLITE_MAC_OS_X
282 : [[NSRunLoop currentRunLoop] run];
283 : #endif
284 : }
285 :
286 :
287 : - (BOOL) finishedLaunching
288 : {
289 : return _finishedLaunching;
290 : }
291 :
292 :
293 : - (void) loadPlayerIfRequired
294 : {
295 : if (playerFileToLoad != nil)
296 : {
297 : [self logProgress:DESC(@"loading-player")];
298 : // fix problem with non-shader lighting when starting skips
299 : // the splash screen
300 : [UNIVERSE useGUILightSource:YES];
301 : [UNIVERSE useGUILightSource:NO];
302 : [PLAYER loadPlayerFromFile:playerFileToLoad asNew:NO];
303 : }
304 : }
305 :
306 :
307 : - (void) beginSplashScreen
308 : {
309 : #if !OOLITE_MAC_OS_X
310 : if(!gameView)
311 : {
312 : gameView = [MyOpenGLView alloc];
313 : [gameView init];
314 : [gameView setGameController:self];
315 : [gameView initSplashScreen];
316 : }
317 : #else
318 : [gameView updateScreen];
319 : #endif
320 : }
321 :
322 :
323 : #if OOLITE_MAC_OS_X
324 :
325 : - (void) performGameTick:(id)sender
326 : {
327 : [gameView pollControls];
328 : [self doPerformGameTick];
329 : }
330 :
331 : #else
332 :
333 : - (void) performGameTick:(id)sender
334 : {
335 : NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
336 :
337 : [gameView pollControls];
338 : [self doPerformGameTick];
339 :
340 : [pool release];
341 : }
342 :
343 : #endif
344 :
345 :
346 0 : - (void) doPerformGameTick
347 : {
348 : @try
349 : {
350 : if (gameIsPaused)
351 : delta_t = 0.0; // no movement!
352 : else
353 : {
354 : delta_t = [NSDate timeIntervalSinceReferenceDate] - last_timeInterval;
355 : last_timeInterval += delta_t;
356 : if (delta_t > MINIMUM_GAME_TICK)
357 : delta_t = MINIMUM_GAME_TICK; // peg the maximum pause (at 0.5->1.0 seconds) to protect against when the machine sleeps
358 : }
359 :
360 : [UNIVERSE update:delta_t];
361 : if (EXPECT_NOT([PLAYER status] == STATUS_RESTART_GAME))
362 : {
363 : [UNIVERSE reinitAndShowDemo:YES];
364 : }
365 : [OOSound update];
366 : if (!gameIsPaused)
367 : {
368 : OOJSFrameCallbacksInvoke(delta_t);
369 : }
370 : }
371 : @catch (id exception)
372 : {
373 : OOLog(@"exception.backtrace",@"%@",[exception callStackSymbols]);
374 : }
375 :
376 : @try
377 : {
378 : [gameView display];
379 : }
380 : @catch (id exception) {}
381 : }
382 :
383 :
384 : - (void) startAnimationTimer
385 : {
386 : if (timer == nil)
387 : {
388 : NSTimeInterval ti = _animationTimerInterval; // default one two-hundredth of a second (should be a fair bit faster than expected frame rate ~60Hz to avoid problems with phase differences)
389 :
390 : timer = [[NSTimer timerWithTimeInterval:ti target:self selector:@selector(performGameTick:) userInfo:nil repeats:YES] retain];
391 :
392 : [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
393 : #if OOLITE_MAC_OS_X
394 : [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSEventTrackingRunLoopMode];
395 : #endif
396 :
397 : }
398 : }
399 :
400 :
401 : - (void) stopAnimationTimer
402 : {
403 : if (timer != nil)
404 : {
405 : [timer invalidate];
406 : [timer release];
407 : timer = nil;
408 : }
409 : }
410 :
411 :
412 : #if OOLITE_MAC_OS_X
413 :
414 : - (void) recenterVirtualJoystick
415 : {
416 : // FIXME: does this really need to be spread across GameController and MyOpenGLView? -- Ahruman 2011-01-22
417 : my_mouse_x = my_mouse_y = 0; // center mouse
418 : [gameView setVirtualJoystick:0.0 :0.0];
419 : }
420 :
421 :
422 : - (IBAction) showLogAction:sender
423 : {
424 : [[NSWorkspace sharedWorkspace] openFile:[OOLogHandlerGetLogBasePath() stringByAppendingPathComponent:@"Previous.log"]];
425 : }
426 :
427 :
428 : - (IBAction) showLogFolderAction:sender
429 : {
430 : [[NSWorkspace sharedWorkspace] openFile:OOLogHandlerGetLogBasePath()];
431 : }
432 :
433 :
434 : // Helpers to allow -snapshotsURLCreatingIfNeeded: code to be identical here and in dock tile plug-in.
435 0 : static id GetPreference(NSString *key, Class expectedClass)
436 : {
437 : id result = [[NSUserDefaults standardUserDefaults] objectForKey:key];
438 : if (expectedClass != Nil && ![result isKindOfClass:expectedClass]) result = nil;
439 :
440 : return result;
441 : }
442 :
443 :
444 0 : static void SetPreference(NSString *key, id value)
445 : {
446 : [[NSUserDefaults standardUserDefaults] setObject:value forKey:key];
447 : }
448 :
449 :
450 0 : static void RemovePreference(NSString *key)
451 : {
452 : [[NSUserDefaults standardUserDefaults] removeObjectForKey:key];
453 : }
454 :
455 :
456 0 : #define kSnapshotsDirRefKey @"snapshots-directory-reference"
457 0 : #define kSnapshotsDirNameKey @"snapshots-directory-name"
458 :
459 : - (NSURL *) snapshotsURLCreatingIfNeeded:(BOOL)create
460 : {
461 : BOOL stale = NO;
462 : NSDictionary *snapshotDirDict = GetPreference(kSnapshotsDirRefKey, [NSDictionary class]);
463 : NSURL *url = nil;
464 : NSString *name = DESC(@"snapshots-directory-name-mac");
465 :
466 : if (snapshotDirDict != nil)
467 : {
468 : url = JAURLFromPersistentFileReference(snapshotDirDict, kJAPersistentFileReferenceWithoutUI | kJAPersistentFileReferenceWithoutMounting, &stale);
469 : if (url != nil)
470 : {
471 : NSString *existingName = [[url path] lastPathComponent];
472 : if ([existingName compare:name options:NSCaseInsensitiveSearch] != 0)
473 : {
474 : // Check name from previous access, because we might have changed localizations.
475 : NSString *originalOldName = GetPreference(kSnapshotsDirNameKey, [NSString class]);
476 : if (originalOldName == nil || [existingName compare:originalOldName options:NSCaseInsensitiveSearch] != 0)
477 : {
478 : url = nil;
479 : }
480 : }
481 :
482 : // did we put the old directory in the trash?
483 : Boolean inTrash = false;
484 : const UInt8 *utfPath = (const UInt8 *)[[url path] UTF8String];
485 :
486 : OSStatus err = DetermineIfPathIsEnclosedByFolder(kOnAppropriateDisk, kTrashFolderType, utfPath, false, &inTrash);
487 : // if so, create a new directory.
488 : if (err == noErr && inTrash == true) url = nil;
489 : }
490 : }
491 :
492 : if (url == nil)
493 : {
494 : NSString *path = nil;
495 : NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSUserDomainMask, YES);
496 : if ([searchPaths count] > 0)
497 : {
498 : path = [[searchPaths objectAtIndex:0] stringByAppendingPathComponent:name];
499 : }
500 : url = [NSURL fileURLWithPath:path];
501 :
502 : if (url != nil)
503 : {
504 : stale = YES;
505 : if (create)
506 : {
507 : NSFileManager *fmgr = [NSFileManager defaultManager];
508 : if (![fmgr fileExistsAtPath:path])
509 : {
510 : [fmgr oo_createDirectoryAtPath:path attributes:nil];
511 : }
512 : }
513 : }
514 : }
515 :
516 : if (stale)
517 : {
518 : snapshotDirDict = JAPersistentFileReferenceFromURL(url);
519 : if (snapshotDirDict != nil)
520 : {
521 : SetPreference(kSnapshotsDirRefKey, snapshotDirDict);
522 : SetPreference(kSnapshotsDirNameKey, [[url path] lastPathComponent]);
523 : }
524 : else
525 : {
526 : RemovePreference(kSnapshotsDirRefKey);
527 : }
528 : }
529 :
530 : return url;
531 : }
532 :
533 :
534 : - (IBAction) showSnapshotsAction:sender
535 : {
536 : [[NSWorkspace sharedWorkspace] openURL:[self snapshotsURLCreatingIfNeeded:YES]];
537 : }
538 :
539 :
540 : - (IBAction) showAddOnsAction:sender
541 : {
542 : NSArray *paths = ResourceManager.userRootPaths;
543 :
544 : // Look for an AddOns directory that actually contains some AddOns.
545 : for (NSString *path in paths) {
546 : if ([self addOnsExistAtPath:path]) {
547 : [self openPath:path];
548 : return;
549 : }
550 : }
551 :
552 : // If that failed, look for an AddOns directory that actually exists.
553 : for (NSString *path in paths) {
554 : if ([self isDirectoryAtPath:path]) {
555 : [self openPath:path];
556 : return;
557 : }
558 : }
559 :
560 : // None found, create the default path.
561 : [NSFileManager.defaultManager createDirectoryAtPath:[paths objectAtIndex:0]
562 : withIntermediateDirectories:YES
563 : attributes:nil
564 : error:NULL];
565 : [self openPath:[paths objectAtIndex:0]];
566 : }
567 :
568 :
569 0 : - (BOOL) isDirectoryAtPath:(NSString *)path
570 : {
571 : BOOL isDirectory;
572 : return [NSFileManager.defaultManager fileExistsAtPath:path isDirectory:&isDirectory] && isDirectory;
573 : }
574 :
575 :
576 0 : - (BOOL) addOnsExistAtPath:(NSString *)path
577 : {
578 : if (![self isDirectoryAtPath:path]) return NO;
579 :
580 : NSWorkspace *workspace = NSWorkspace.sharedWorkspace;
581 : for (NSString *subPath in [NSFileManager.defaultManager enumeratorAtPath:path]) {
582 : subPath = [path stringByAppendingPathComponent:subPath];
583 : NSString *type = [workspace typeOfFile:subPath error:NULL];
584 : if ([workspace type:type conformsToType:@"org.aegidian.oolite.expansion"]) return YES;
585 : }
586 :
587 : return NO;
588 : }
589 :
590 :
591 0 : - (void) openPath:(NSString *)path
592 : {
593 : [NSWorkspace.sharedWorkspace openURL:[NSURL fileURLWithPath:path]];
594 : }
595 :
596 :
597 0 : - (BOOL) validateMenuItem:(NSMenuItem *)menuItem
598 : {
599 : SEL action = menuItem.action;
600 :
601 : if (action == @selector(showLogAction:))
602 : {
603 : // the first path is always Resources
604 : return ([[NSFileManager defaultManager] fileExistsAtPath:[OOLogHandlerGetLogBasePath() stringByAppendingPathComponent:@"Previous.log"]]);
605 : }
606 :
607 : if (action == @selector(showAddOnsAction:))
608 : {
609 : // Always enabled in unrestricted mode, to allow users to add OXPs more easily.
610 : return [ResourceManager useAddOns] != nil;
611 : }
612 :
613 : if (action == @selector(showSnapshotsAction:))
614 : {
615 : BOOL pathIsDirectory;
616 : if(![[NSFileManager defaultManager] fileExistsAtPath:[self snapshotsURLCreatingIfNeeded:NO].path isDirectory:&pathIsDirectory])
617 : {
618 : return NO;
619 : }
620 : return pathIsDirectory;
621 : }
622 :
623 : if (action == @selector(toggleFullScreenAction:))
624 : {
625 : if (_fullScreenController.fullScreenMode)
626 : {
627 : // NOTE: not DESC, because menu titles are not generally localizable.
628 : menuItem.title = NSLocalizedString(@"Exit Full Screen", NULL);
629 : }
630 : else
631 : {
632 : menuItem.title = NSLocalizedString(@"Enter Full Screen", NULL);
633 : }
634 : }
635 :
636 : // default
637 : return YES;
638 : }
639 :
640 :
641 0 : - (NSMenu *)applicationDockMenu:(NSApplication *)sender
642 : {
643 : return dockMenu;
644 : }
645 :
646 : #elif OOLITE_SDL
647 :
648 : - (NSURL *) snapshotsURLCreatingIfNeeded:(BOOL)create
649 : {
650 : NSURL *url = [NSURL fileURLWithPath:[NSHomeDirectory() stringByAppendingPathComponent:DESC(@"snapshots-directory-name")]];
651 :
652 : if (create)
653 : {
654 : NSString *path = [url path];
655 : NSFileManager *fmgr = [NSFileManager defaultManager];
656 : if (![fmgr fileExistsAtPath:path])
657 : {
658 : [fmgr oo_createDirectoryAtPath:path attributes:nil];
659 : }
660 : }
661 : return url;
662 : }
663 :
664 : #else
665 : #error Unknown environment!
666 : #endif
667 :
668 : - (void) logProgress:(NSString *)message
669 : {
670 : if (![UNIVERSE doingStartUp]) return;
671 :
672 : #if OOLITE_MAC_OS_X
673 : [splashProgressTextField setStringValue:message];
674 : [splashProgressTextField display];
675 : #endif
676 : if([message length] > 0)
677 : {
678 : OOLog(@"startup.progress", @"===== [%.2f s] %@", -[_splashStart timeIntervalSinceNow], message);
679 : }
680 : }
681 :
682 :
683 : #if OO_DEBUG
684 : #if OOLITE_MAC_OS_X
685 : - (BOOL) debugMessageTrackingIsOn
686 : {
687 : return splashProgressTextField != nil;
688 : }
689 :
690 :
691 : - (NSString *) debugMessageCurrentString
692 : {
693 : return [splashProgressTextField stringValue];
694 : }
695 : #else
696 : - (BOOL) debugMessageTrackingIsOn
697 : {
698 : return OOLogWillDisplayMessagesInClass(@"startup.progress");
699 : }
700 :
701 :
702 : - (NSString *) debugMessageCurrentString
703 : {
704 : return @"";
705 : }
706 : #endif
707 :
708 : - (void) debugLogProgress:(NSString *)format, ...
709 : {
710 : va_list args;
711 : va_start(args, format);
712 : [self debugLogProgress:format arguments:args];
713 : va_end(args);
714 : }
715 :
716 :
717 : - (void) debugLogProgress:(NSString *)format arguments:(va_list)arguments
718 : {
719 : NSString *message = [[[NSString alloc] initWithFormat:format arguments:arguments] autorelease];
720 : [self logProgress:message];
721 : }
722 :
723 :
724 : static NSMutableArray *sMessageStack;
725 :
726 : - (void) debugPushProgressMessage:(NSString *)format, ...
727 : {
728 : if ([self debugMessageTrackingIsOn])
729 : {
730 : if (sMessageStack == nil) sMessageStack = [[NSMutableArray alloc] init];
731 : [sMessageStack addObject:[self debugMessageCurrentString]];
732 :
733 : va_list args;
734 : va_start(args, format);
735 : [self debugLogProgress:format arguments:args];
736 : va_end(args);
737 : }
738 :
739 : OOLogIndentIf(@"startup.progress");
740 : }
741 :
742 :
743 : - (void) debugPopProgressMessage
744 : {
745 : OOLogOutdentIf(@"startup.progress");
746 :
747 : if ([sMessageStack count] > 0)
748 : {
749 : NSString *message = [sMessageStack lastObject];
750 : if ([message length] > 0) [self logProgress:message];
751 : [sMessageStack removeLastObject];
752 : }
753 : }
754 :
755 : #endif
756 :
757 :
758 : - (void) endSplashScreen
759 : {
760 : OOLogSetDisplayMessagesInClass(@"startup.progress", NO);
761 :
762 : #if OOLITE_MAC_OS_X
763 : // These views will be released when we replace the content view.
764 : splashProgressTextField = nil;
765 : splashView = nil;
766 :
767 : [gameWindow setAcceptsMouseMovedEvents:YES];
768 : [gameWindow setContentView:gameView];
769 : [gameWindow makeFirstResponder:gameView];
770 : #elif OOLITE_SDL
771 : [gameView endSplashScreen];
772 : #endif
773 : }
774 :
775 :
776 : #if OOLITE_MAC_OS_X
777 :
778 : // NIB methods
779 0 : - (void)awakeFromNib
780 : {
781 : NSString *path = nil;
782 :
783 : // Set contents of Help window
784 : path = [[NSBundle mainBundle] pathForResource:@"OoliteReadMe" ofType:@"pdf"];
785 : if (path != nil)
786 : {
787 : PDFDocument *document = [[PDFDocument alloc] initWithURL:[NSURL fileURLWithPath:path]];
788 : [helpView setDocument:document];
789 : [document release];
790 : }
791 : [helpView setBackgroundColor:[NSColor whiteColor]];
792 : }
793 :
794 :
795 : // delegate methods
796 0 : - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
797 : {
798 : if ([[filename pathExtension] isEqual:@"oolite-save"])
799 : {
800 : [self setPlayerFileToLoad:filename];
801 : [self setPlayerFileDirectory:filename];
802 : return YES;
803 : }
804 : if ([[filename pathExtension] isEqualToString:@"oxp"])
805 : {
806 : BOOL dir_test;
807 : [[NSFileManager defaultManager] fileExistsAtPath:filename isDirectory:&dir_test];
808 : if (dir_test)
809 : {
810 : if (expansionPathsToInclude == nil)
811 : {
812 : expansionPathsToInclude = [[NSMutableArray alloc] init];
813 : }
814 : [expansionPathsToInclude addObject:filename];
815 : return YES;
816 : }
817 : }
818 : return NO;
819 : }
820 :
821 :
822 : - (void) exitAppWithContext:(NSString *)context
823 : {
824 : [gameView.window orderOut:nil];
825 : [(OoliteApp *)NSApp setExitContext:context];
826 : [NSApp terminate:self];
827 : }
828 :
829 :
830 0 : - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
831 : {
832 : [[OOCacheManager sharedCache] finishOngoingFlush];
833 : OOLoggingTerminate();
834 : return NSTerminateNow;
835 : }
836 :
837 : #elif OOLITE_SDL
838 :
839 : - (void) exitAppWithContext:(NSString *)context
840 : {
841 : OOLog(@"exit.context", @"Exiting: %@.", context);
842 : #if (OOLITE_GNUSTEP && !defined(NDEBUG))
843 : [[OODebugMonitor sharedDebugMonitor] applicationWillTerminate];
844 : #endif
845 : #if OOLITE_WINDOWS
846 : // This should not be required normally but we have to ensure that
847 : // desktop resolution is restored also on some Intel cards on Win10
848 : if (![gameView atDesktopResolution])
849 : {
850 : OOLog(@"gameController.exitApp", @"%@", @"Restoring desktop resolution.");
851 : ChangeDisplaySettingsEx(NULL, NULL, NULL, 0, NULL);
852 : }
853 : #endif
854 : [[NSUserDefaults standardUserDefaults] synchronize];
855 : OOLog(@"gameController.exitApp", @"%@", @".GNUstepDefaults synchronized.");
856 : OOLoggingTerminate();
857 : SDL_Quit();
858 : [[OOOpenALController sharedController] shutdown];
859 : exit(0);
860 : }
861 :
862 : #else
863 : #error Unknown environment!
864 : #endif
865 :
866 :
867 : - (void) exitAppCommandQ
868 : {
869 : [self exitAppWithContext:@"Command-Q"];
870 : }
871 :
872 :
873 : - (void)windowDidResize:(NSNotification *)aNotification
874 : {
875 : [gameView updateScreen];
876 : }
877 :
878 :
879 : - (NSString *) playerFileToLoad
880 : {
881 : return playerFileToLoad;
882 : }
883 :
884 :
885 : - (void) setPlayerFileToLoad:(NSString *)filename
886 : {
887 : if (playerFileToLoad)
888 : [playerFileToLoad autorelease];
889 : playerFileToLoad = nil;
890 : if ([[[filename pathExtension] lowercaseString] isEqual:@"oolite-save"])
891 : playerFileToLoad = [filename copy];
892 : }
893 :
894 :
895 : - (NSString *) playerFileDirectory
896 : {
897 : if (playerFileDirectory == nil)
898 : {
899 : playerFileDirectory = [[NSUserDefaults standardUserDefaults] stringForKey:@"save-directory"];
900 : if (playerFileDirectory != nil && ![[NSFileManager defaultManager] fileExistsAtPath:playerFileDirectory])
901 : {
902 : playerFileDirectory = nil;
903 : }
904 : if (playerFileDirectory == nil) playerFileDirectory = [[NSFileManager defaultManager] defaultCommanderPath];
905 :
906 : [playerFileDirectory retain];
907 : }
908 :
909 : return playerFileDirectory;
910 : }
911 :
912 :
913 : - (void) setPlayerFileDirectory:(NSString *)filename
914 : {
915 : if (playerFileDirectory != nil)
916 : {
917 : [playerFileDirectory autorelease];
918 : playerFileDirectory = nil;
919 : }
920 :
921 : if ([[[filename pathExtension] lowercaseString] isEqual:@"oolite-save"])
922 : {
923 : filename = [filename stringByDeletingLastPathComponent];
924 : }
925 :
926 : playerFileDirectory = [filename retain];
927 : [[NSUserDefaults standardUserDefaults] setObject:filename forKey:@"save-directory"];
928 : }
929 :
930 :
931 0 : - (void)reportUnhandledStartupException:(NSException *)exception
932 : {
933 : OOLog(@"startup.exception", @"***** Unhandled exception during startup: %@ (%@).", [exception name], [exception reason]);
934 :
935 : #if OOLITE_MAC_OS_X
936 : // Display an error alert.
937 : // TODO: provide better information on reporting bugs in the manual, and refer to it here.
938 : NSRunCriticalAlertPanel(@"Oolite failed to start up, because an unhandled exception occurred.", @"An exception of type %@ occurred. If this problem persists, please file a bug report.", @"OK", NULL, NULL, [exception name]);
939 : #endif
940 : }
941 :
942 :
943 : - (void)setUpBasicOpenGLStateWithSize:(NSSize)viewSize
944 : {
945 : OOOpenGLExtensionManager *extMgr = [OOOpenGLExtensionManager sharedManager];
946 :
947 : float ratio = 0.5;
948 : float aspect = viewSize.height/viewSize.width;
949 :
950 : OOGL(glClearColor(0.0, 0.0, 0.0, 0.0));
951 : OOGL(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
952 :
953 : OOGL(glClearDepth(1.0));
954 : OOGL(glViewport(0, 0, viewSize.width, viewSize.height));
955 :
956 : OOGLResetProjection(); // reset matrix
957 : OOGLFrustum(-ratio, ratio, -aspect*ratio, aspect*ratio, 1.0, MAX_CLEAR_DEPTH); // set projection matrix
958 :
959 : OOGL(glDepthFunc(GL_LESS)); // depth buffer
960 :
961 : if (UNIVERSE)
962 : {
963 : [UNIVERSE setLighting];
964 : }
965 : else
966 : {
967 : GLfloat black[4] = {0.0, 0.0, 0.0, 1.0};
968 : GLfloat white[] = {1.0, 1.0, 1.0, 1.0};
969 : GLfloat stars_ambient[] = {0.25, 0.2, 0.25, 1.0};
970 :
971 : OOGL(glLightfv(GL_LIGHT1, GL_AMBIENT, black));
972 : OOGL(glLightfv(GL_LIGHT1, GL_SPECULAR, white));
973 : OOGL(glLightfv(GL_LIGHT1, GL_DIFFUSE, white));
974 : OOGL(glLightfv(GL_LIGHT1, GL_POSITION, black));
975 : OOGL(glLightModelfv(GL_LIGHT_MODEL_AMBIENT, stars_ambient));
976 :
977 : }
978 :
979 : if ([extMgr usePointSmoothing]) OOGL(glEnable(GL_POINT_SMOOTH));
980 : if ([extMgr useLineSmoothing]) OOGL(glEnable(GL_LINE_SMOOTH));
981 :
982 : // world's simplest OpenGL optimisations...
983 : #if GL_APPLE_transform_hint
984 : if ([extMgr haveExtension:@"GL_APPLE_transform_hint"])
985 : {
986 : OOGL(glHint(GL_TRANSFORM_HINT_APPLE, GL_FASTEST));
987 : }
988 : #endif
989 :
990 : OOGL(glDisable(GL_NORMALIZE));
991 : OOGL(glDisable(GL_RESCALE_NORMAL));
992 :
993 : #if GL_VERSION_1_2
994 : // For OpenGL 1.2 or later, we want GL_SEPARATE_SPECULAR_COLOR all the time.
995 : if ([extMgr versionIsAtLeastMajor:1 minor:2])
996 : {
997 : OOGL(glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR));
998 : }
999 : #endif
1000 : }
1001 :
1002 :
1003 : #ifndef NDEBUG
1004 : /* This method exists purely to suppress Clang static analyzer warnings that
1005 : these ivars are unused (but may be used by categories, which they are).
1006 : */
1007 0 : - (BOOL) suppressClangStuff
1008 : {
1009 : return pauseSelector &&
1010 : pauseTarget;
1011 : }
1012 : #endif
1013 :
1014 : @end
1015 :
1016 :
1017 : #if OOLITE_MAC_OS_X
1018 :
1019 0 : static void SetUpSparkle(void)
1020 : {
1021 0 : #define FEED_URL_BASE "http://www.oolite.org/updates/"
1022 0 : #define TEST_RELEASE_FEED_NAME "oolite-mac-test-release-appcast.xml"
1023 0 : #define DEPLOYMENT_FEED_NAME "oolite-mac-appcast.xml"
1024 :
1025 0 : #define TEST_RELEASE_FEED_URL (@ FEED_URL_BASE TEST_RELEASE_FEED_NAME)
1026 0 : #define DEPLOYMENT_FEED_URL (@ FEED_URL_BASE DEPLOYMENT_FEED_NAME)
1027 :
1028 : // Default to test releases in test release or debug builds, and stable releases for deployment builds.
1029 : #ifdef NDEBUG
1030 : #define DEFAULT_TEST_RELEASE 0
1031 : #else
1032 0 : #define DEFAULT_TEST_RELEASE 1
1033 : #endif
1034 :
1035 : BOOL useTestReleases = [[NSUserDefaults standardUserDefaults] oo_boolForKey:@"use-test-release-updates"
1036 : defaultValue:DEFAULT_TEST_RELEASE];
1037 :
1038 : SUUpdater *updater = [SUUpdater sharedUpdater];
1039 : [updater setFeedURL:[NSURL URLWithString:useTestReleases ? TEST_RELEASE_FEED_URL : DEPLOYMENT_FEED_URL]];
1040 : }
1041 :
1042 : #endif
|