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