28#import "MyOpenGLView.h"
49#import <Sparkle/Sparkle.h>
54#elif (OOLITE_GNUSTEP && !defined(NDEBUG))
62@interface GameController (OOPrivate)
64- (void)reportUnhandledStartupException:(NSException *)exception;
88 [NSException raise:NSInternalInconsistencyException format:@"%s: expected only one GameController to exist at a time.", __PRETTY_FUNCTION__];
91 if ((
self = [super init]))
93 _finishedLaunching = NO;
94 last_timeInterval = [NSDate timeIntervalSinceReferenceDate];
96 _animationTimerInterval = [[NSUserDefaults standardUserDefaults] oo_doubleForKey:@"animation_timer_interval" defaultValue:MINIMUM_ANIMATION_TICK];
100 ranrot_srand((uint32_t)[[NSDate date] timeIntervalSince1970]);
102 _splashStart = [[NSDate alloc] init];
112 [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:UNIVERSE];
119 [playerFileToLoad release];
120 [playerFileDirectory release];
121 [expansionPathsToInclude release];
133- (void) setGamePaused:(BOOL)value
135 if (value && !gameIsPaused)
137 _resumeMode = [
self mouseInteractionMode];
138 [
self setMouseInteractionModeForUIWithMouseInteraction:NO];
139 [
self setEcoQoS:YES];
141 [PLAYER doScriptEvent:OOJSID("gamePaused")];
143 else if (!value && gameIsPaused)
145 [
self setMouseInteractionMode:_resumeMode];
148 [PLAYER doScriptEvent:OOJSID("gameResumed")];
153- (void) setEcoQoS: (BOOL)efficiencyModeRequested
156 if ([[NSUserDefaults standardUserDefaults] oo_boolForKey:
@"ecoqos" defaultValue:YES])
158 BOOL setEfficiencyMode = !!efficiencyModeRequested;
159 HANDLE currentProcess = GetCurrentProcess();
161 if (
EXPECT_NOT(!SetPriorityClass(currentProcess, setEfficiencyMode ? IDLE_PRIORITY_CLASS : NORMAL_PRIORITY_CLASS)))
163 OOLog(
@"gameController.setEcoQos",
@"SetPriorityClass failed with error %lu", GetLastError());
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))))
173 OOLog(
@"gameController.setEcoQos",
@"SetProcessInformation failed with error %lu", GetLastError());
189 if (
mode == oldMode)
return;
194#if OO_USE_FULLSCREEN_CONTROLLER
195 if ([
self inFullScreenMode])
197 [_fullScreenController noteMouseInteractionModeChangedFrom:oldMode to:mode];
202 [[
self gameView] noteMouseInteractionModeChangedFrom:oldMode to:mode];
207- (void) setMouseInteractionModeForFlight
209 [
self setMouseInteractionMode:[PLAYER isMouseControlOn] ? MOUSE_MODE_FLIGHT_WITH_MOUSE_CONTROL : MOUSE_MODE_FLIGHT_NO_MOUSE_CONTROL];
213- (void) setMouseInteractionModeForUIWithMouseInteraction:(BOOL)interaction
215 [
self setMouseInteractionMode:interaction ? MOUSE_MODE_UI_SCREEN_WITH_INTERACTION : MOUSE_MODE_UI_SCREEN_NO_INTERACTION];
228 gameView = [view retain];
229 [gameView setGameController:self];
230 [UNIVERSE setGameView:gameView];
234- (void) applicationDidFinishLaunching:(NSNotification *)notification
236 NSAutoreleasePool *pool =
nil;
239 pool = [[NSAutoreleasePool alloc] init];
246#if OO_OXP_VERIFIER_ENABLED
250 [
self exitAppWithContext:@"OXP verifier run"];
254 [
self beginSplashScreen];
258 [
self beginSplashScreen];
266 [
self setUpDisplayModes];
269 if (expansionPathsToInclude)
271 for (i = 0; i < [expansionPathsToInclude count]; i++)
282 [[
Universe alloc] initWithGameView:gameView];
284 [
self loadPlayerIfRequired];
286 [
self logProgress:@""];
289 [
self startAnimationTimer];
291 [
self endSplashScreen];
293 @catch (NSException *exception)
295 [
self reportUnhandledStartupException:exception];
299 OOLog(
@"startup.complete",
@"========== Loading complete in %.2f seconds. ==========", -[_splashStart timeIntervalSinceNow]);
301#if OO_USE_FULLSCREEN_CONTROLLER
302 [
self setFullScreenMode:[[NSUserDefaults standardUserDefaults] boolForKey:@"fullscreen"]];
305 _finishedLaunching = YES;
311 [[NSRunLoop currentRunLoop] run];
316- (BOOL) finishedLaunching
318 return _finishedLaunching;
322- (void) loadPlayerIfRequired
324 if (playerFileToLoad !=
nil)
326 [
self logProgress:DESC(@"loading-player")];
329 [UNIVERSE useGUILightSource:YES];
330 [UNIVERSE useGUILightSource:NO];
331 [PLAYER loadPlayerFromFile:playerFileToLoad asNew:NO];
336- (void) beginSplashScreen
343 [gameView setGameController:self];
344 [gameView initSplashScreen];
347 [gameView updateScreen];
354- (void) performGameTick:(
id)sender
356 [gameView pollControls];
357 [
self doPerformGameTick];
362- (void) performGameTick:(
id)sender
364 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
366 [gameView pollControls];
367 [
self doPerformGameTick];
383 delta_t = [NSDate timeIntervalSinceReferenceDate] - last_timeInterval;
384 last_timeInterval += delta_t;
389 [UNIVERSE update:delta_t];
392 [UNIVERSE reinitAndShowDemo:YES];
400 @catch (
id exception)
402 OOLog(
@"exception.backtrace",
@"%@",[exception callStackSymbols]);
409 @catch (
id exception) {}
413- (void) startAnimationTimer
417 NSTimeInterval ti = _animationTimerInterval;
419 timer = [[NSTimer timerWithTimeInterval:ti target:self selector:@selector(performGameTick:) userInfo:nil repeats:YES] retain];
421 [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
423 [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSEventTrackingRunLoopMode];
430- (void) stopAnimationTimer
443- (void) recenterVirtualJoystick
446 my_mouse_x = my_mouse_y = 0;
447 [gameView setVirtualJoystick:0.0 :0.0];
451- (IBAction) showLogAction:sender
453 [[NSWorkspace sharedWorkspace] openFile:[OOLogHandlerGetLogBasePath() stringByAppendingPathComponent:@"Previous.log"]];
457- (IBAction) showLogFolderAction:sender
459 [[NSWorkspace sharedWorkspace] openFile:OOLogHandlerGetLogBasePath()];
466 id result = [[NSUserDefaults standardUserDefaults] objectForKey:key];
467 if (expectedClass != Nil && ![result isKindOfClass:expectedClass]) result =
nil;
475 [[NSUserDefaults standardUserDefaults] setObject:value forKey:key];
479static void RemovePreference(NSString *key)
481 [[NSUserDefaults standardUserDefaults] removeObjectForKey:key];
485#define kSnapshotsDirRefKey @"snapshots-directory-reference"
486#define kSnapshotsDirNameKey @"snapshots-directory-name"
488- (NSURL *) snapshotsURLCreatingIfNeeded:(BOOL)create
493 NSString *name =
DESC(
@"snapshots-directory-name-mac");
495 if (snapshotDirDict !=
nil)
500 NSString *existingName = [[url path] lastPathComponent];
501 if ([existingName compare:name options:NSCaseInsensitiveSearch] != 0)
505 if (originalOldName ==
nil || [existingName compare:originalOldName options:NSCaseInsensitiveSearch] != 0)
512 Boolean inTrash =
false;
513 const UInt8 *utfPath = (
const UInt8 *)[[url path] UTF8String];
515 OSStatus err = DetermineIfPathIsEnclosedByFolder(kOnAppropriateDisk, kTrashFolderType, utfPath,
false, &inTrash);
517 if (err == noErr && inTrash ==
true) url =
nil;
523 NSString *path =
nil;
524 NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSUserDomainMask, YES);
525 if ([searchPaths
count] > 0)
527 path = [[searchPaths objectAtIndex:0] stringByAppendingPathComponent:name];
529 url = [NSURL fileURLWithPath:path];
536 NSFileManager *fmgr = [NSFileManager defaultManager];
537 if (![fmgr fileExistsAtPath:path])
539 [fmgr oo_createDirectoryAtPath:path attributes:nil];
548 if (snapshotDirDict !=
nil)
563- (IBAction) showSnapshotsAction:sender
565 [[NSWorkspace sharedWorkspace] openURL:[
self snapshotsURLCreatingIfNeeded:YES]];
569- (IBAction) showAddOnsAction:sender
574 for (NSString *path in paths) {
575 if ([
self addOnsExistAtPath:path]) {
576 [
self openPath:path];
582 for (NSString *path in paths) {
583 if ([
self isDirectoryAtPath:path]) {
584 [
self openPath:path];
590 [NSFileManager.defaultManager createDirectoryAtPath:[paths objectAtIndex:0]
591 withIntermediateDirectories:YES
594 [
self openPath:[paths objectAtIndex:0]];
598- (BOOL) isDirectoryAtPath:(NSString *)path
601 return [NSFileManager.defaultManager fileExistsAtPath:path isDirectory:&isDirectory] && isDirectory;
605- (BOOL) addOnsExistAtPath:(NSString *)path
607 if (![
self isDirectoryAtPath:path]) return NO;
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;
620- (void) openPath:(NSString *)path
622 [NSWorkspace.sharedWorkspace openURL:[NSURL fileURLWithPath:path]];
626- (BOOL) validateMenuItem:(NSMenuItem *)menuItem
628 SEL action = menuItem.action;
630 if (action ==
@selector(showLogAction:))
633 return ([[NSFileManager defaultManager] fileExistsAtPath:[
OOLogHandlerGetLogBasePath() stringByAppendingPathComponent:
@"Previous.log"]]);
636 if (action ==
@selector(showAddOnsAction:))
642 if (action ==
@selector(showSnapshotsAction:))
644 BOOL pathIsDirectory;
645 if(![[NSFileManager defaultManager] fileExistsAtPath:[
self snapshotsURLCreatingIfNeeded:NO].path isDirectory:&pathIsDirectory])
649 return pathIsDirectory;
652 if (action ==
@selector(toggleFullScreenAction:))
654 if (_fullScreenController.fullScreenMode)
657 menuItem.title = NSLocalizedString(
@"Exit Full Screen", NULL);
661 menuItem.title = NSLocalizedString(
@"Enter Full Screen", NULL);
670- (NSMenu *)applicationDockMenu:(NSApplication *)sender
677- (NSURL *) snapshotsURLCreatingIfNeeded:(BOOL)create
679 NSURL *url = [NSURL fileURLWithPath:[NSHomeDirectory() stringByAppendingPathComponent:DESC(@"snapshots-directory-name")]];
683 NSString *path = [url path];
684 NSFileManager *fmgr = [NSFileManager defaultManager];
685 if (![fmgr fileExistsAtPath:path])
687 [fmgr oo_createDirectoryAtPath:path attributes:nil];
694 #error Unknown environment!
697- (void) logProgress:(NSString *)message
699 if (![
UNIVERSE doingStartUp])
return;
702 [splashProgressTextField setStringValue:message];
703 [splashProgressTextField display];
705 if([message length] > 0)
707 OOLog(
@"startup.progress",
@"===== [%.2f s] %@", -[_splashStart timeIntervalSinceNow], message);
714- (BOOL) debugMessageTrackingIsOn
716 return splashProgressTextField !=
nil;
720- (NSString *) debugMessageCurrentString
722 return [splashProgressTextField stringValue];
725- (BOOL) debugMessageTrackingIsOn
731- (NSString *) debugMessageCurrentString
737- (void) debugLogProgress:(NSString *)format, ...
740 va_start(args, format);
741 [
self debugLogProgress:format arguments:args];
746- (void) debugLogProgress:(NSString *)format arguments:(va_list)arguments
748 NSString *message = [[[NSString alloc] initWithFormat:format arguments:arguments] autorelease];
749 [
self logProgress:message];
753static NSMutableArray *sMessageStack;
755- (void) debugPushProgressMessage:(NSString *)format, ...
757 if ([
self debugMessageTrackingIsOn])
759 if (sMessageStack ==
nil) sMessageStack = [[NSMutableArray alloc] init];
760 [sMessageStack addObject:[
self debugMessageCurrentString]];
763 va_start(args, format);
764 [
self debugLogProgress:format arguments:args];
772- (void) debugPopProgressMessage
776 if ([sMessageStack
count] > 0)
778 NSString *message = [sMessageStack lastObject];
779 if ([message length] > 0) [
self logProgress:message];
780 [sMessageStack removeLastObject];
787- (void) endSplashScreen
793 splashProgressTextField =
nil;
796 [gameWindow setAcceptsMouseMovedEvents:YES];
797 [gameWindow setContentView:gameView];
798 [gameWindow makeFirstResponder:gameView];
800 [gameView endSplashScreen];
810 NSString *path =
nil;
813 path = [[NSBundle mainBundle] pathForResource:@"OoliteReadMe" ofType:@"pdf"];
816 PDFDocument *document = [[PDFDocument alloc] initWithURL:[NSURL fileURLWithPath:path]];
817 [helpView setDocument:document];
820 [helpView setBackgroundColor:[NSColor whiteColor]];
825- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
827 if ([[
filename pathExtension] isEqual:
@"oolite-save"])
829 [
self setPlayerFileToLoad:filename];
830 [
self setPlayerFileDirectory:filename];
833 if ([[
filename pathExtension] isEqualToString:
@"oxp"])
836 [[NSFileManager defaultManager] fileExistsAtPath:filename isDirectory:&dir_test];
839 if (expansionPathsToInclude ==
nil)
841 expansionPathsToInclude = [[NSMutableArray alloc] init];
843 [expansionPathsToInclude addObject:filename];
851- (void) exitAppWithContext:(NSString *)context
853 [gameView.window orderOut:nil];
855 [NSApp terminate:self];
859- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
863 return NSTerminateNow;
868- (void) exitAppWithContext:(NSString *)context
870 OOLog(
@"exit.context",
@"Exiting: %@.", context);
871#if (OOLITE_GNUSTEP && !defined(NDEBUG))
877 if (![gameView atDesktopResolution])
879 OOLog(
@"gameController.exitApp",
@"%@",
@"Restoring desktop resolution.");
880 ChangeDisplaySettingsEx(NULL, NULL, NULL, 0, NULL);
883 [[NSUserDefaults standardUserDefaults] synchronize];
884 OOLog(
@"gameController.exitApp",
@"%@",
@".GNUstepDefaults synchronized.");
892 #error Unknown environment!
896- (void) exitAppCommandQ
898 [
self exitAppWithContext:@"Command-Q"];
902- (void)windowDidResize:(NSNotification *)aNotification
904 [gameView updateScreen];
908- (NSString *) playerFileToLoad
910 return playerFileToLoad;
914- (void) setPlayerFileToLoad:(NSString *)filename
916 if (playerFileToLoad)
917 [playerFileToLoad autorelease];
918 playerFileToLoad =
nil;
919 if ([[[
filename pathExtension] lowercaseString] isEqual:
@"oolite-save"])
924- (NSString *) playerFileDirectory
926 if (playerFileDirectory ==
nil)
928 playerFileDirectory = [[NSUserDefaults standardUserDefaults] stringForKey:@"save-directory"];
929 if (playerFileDirectory !=
nil && ![[NSFileManager defaultManager] fileExistsAtPath:playerFileDirectory])
931 playerFileDirectory =
nil;
933 if (playerFileDirectory ==
nil) playerFileDirectory = [[NSFileManager defaultManager] defaultCommanderPath];
935 [playerFileDirectory retain];
938 return playerFileDirectory;
942- (void) setPlayerFileDirectory:(NSString *)filename
944 if (playerFileDirectory !=
nil)
946 [playerFileDirectory autorelease];
947 playerFileDirectory =
nil;
950 if ([[[
filename pathExtension] lowercaseString] isEqual:
@"oolite-save"])
952 filename = [filename stringByDeletingLastPathComponent];
955 playerFileDirectory = [filename retain];
956 [[NSUserDefaults standardUserDefaults] setObject:filename forKey:@"save-directory"];
960- (void)reportUnhandledStartupException:(NSException *)exception
962 OOLog(
@"startup.exception",
@"***** Unhandled exception during startup: %@ (%@).", [exception name], [exception reason]);
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]);
972- (void)setUpBasicOpenGLStateWithSize:(NSSize)viewSize
977 float aspect = viewSize.height/viewSize.width;
979 OOGL(glClearColor(0.0, 0.0, 0.0, 0.0));
980 OOGL(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
982 OOGL(glClearDepth(1.0));
983 OOGL(glViewport(0, 0, viewSize.width, viewSize.height));
988 OOGL(glDepthFunc(GL_LESS));
992 [UNIVERSE setLighting];
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};
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));
1008 if ([extMgr usePointSmoothing])
OOGL(glEnable(GL_POINT_SMOOTH));
1009 if ([extMgr useLineSmoothing])
OOGL(glEnable(GL_LINE_SMOOTH));
1012#if GL_APPLE_transform_hint
1013 if ([extMgr haveExtension:
@"GL_APPLE_transform_hint"])
1015 OOGL(glHint(GL_TRANSFORM_HINT_APPLE, GL_FASTEST));
1019 OOGL(glDisable(GL_NORMALIZE));
1020 OOGL(glDisable(GL_RESCALE_NORMAL));
1024 if ([extMgr versionIsAtLeastMajor:1 minor:2])
1026 OOGL(glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR));
1036- (BOOL) suppressClangStuff
1038 return pauseSelector &&
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"
1054#define TEST_RELEASE_FEED_URL (@ FEED_URL_BASE TEST_RELEASE_FEED_NAME)
1055#define DEPLOYMENT_FEED_URL (@ FEED_URL_BASE DEPLOYMENT_FEED_NAME)
1059#define DEFAULT_TEST_RELEASE 0
1061#define DEFAULT_TEST_RELEASE 1
1064 BOOL useTestReleases = [[NSUserDefaults standardUserDefaults] oo_boolForKey:@"use-test-release-updates"
1065 defaultValue:DEFAULT_TEST_RELEASE];
1067 SUUpdater *updater = [SUUpdater sharedUpdater];
1068 [updater setFeedURL:[NSURL URLWithString:useTestReleases ? TEST_RELEASE_FEED_URL : DEPLOYMENT_FEED_URL]];
#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)
void OOJSFrameCallbacksInvoke(OOTimeDelta delta)
NSString * OOLogHandlerGetLogBasePath(void)
BOOL OOLogWillDisplayMessagesInClass(NSString *inMessageClass)
#define OOLogOutdentIf(class)
#define OOLog(class, format,...)
#define OOLogIndentIf(class)
void OOLoggingTerminate(void)
void OOLogSetDisplayMessagesInClass(NSString *inClass, BOOL inFlag)
NSString * OOStringFromMouseInteractionMode(OOMouseInteractionMode mode)
void OOGLFrustum(double left, double right, double bottom, double top, double near, double far)
void OOGLResetProjection(void)
static id GetPreference(NSString *key, Class expectedClass)
static void SetPreference(NSString *key, id value)
void finishOngoingFlush()
OOCacheManager * sharedCache()
OODebugMonitor * sharedDebugMonitor()
BOOL setStickHandlerClass:(Class aClass)
OOOXZManager * sharedManager()
OOOpenALController * sharedController()
OOOpenGLExtensionManager * sharedManager()
void addExternalPath:(NSString *fileName)
NSArray * userRootPaths()
void ranrot_srand(uint32_t seed)