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