Launcher.m 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623
  1. #import "Launcher.h"
  2. #import "ConsoleView.h"
  3. #include <stdlib.h>
  4. #include <unistd.h> /* _exit() */
  5. #include <util.h> /* forkpty() */
  6. // User default keys
  7. #define dkVERSION @"version"
  8. #define dkFULLSCREEN @"fullscreen"
  9. #define dkFSAA @"fsaa"
  10. #define dkRESOLUTION @"resolution"
  11. #define dkADVANCEDOPTS @"advancedOptions"
  12. #define dkSERVEROPTS @"server_options"
  13. #define dkDESCRIPTION @"server_description"
  14. #define dkPASSWORD @"server_password"
  15. #define dkMAXCLIENTS @"server_maxclients"
  16. #define kMaxDisplays 16
  17. //If you make a MOD then please change this, the bundle indentifier, the file extensions (.cgz, .dmo), and the url registration.
  18. #define kASSAULTCUBE @"assaultcube"
  19. #define kUSERCONFGIDIR @"assaultcube/v1.3"
  20. //tab names, i.e. image names (text is localised)
  21. #define tkMAIN @"Main"
  22. #define tkSERVER @"Server"
  23. @interface NSString(Extras)
  24. @end
  25. @implementation NSString(Extras)
  26. - (NSString*)expand {
  27. NSMutableString *str = [NSMutableString string];
  28. [str setString:self];
  29. [str replaceOccurrencesOfString:@":s" withString:kASSAULTCUBE options:0 range:NSMakeRange(0, [str length])];
  30. return str;
  31. }
  32. @end
  33. @interface NSUserDefaults(Extras) // unless you want strings with "(null)" in them :-/
  34. - (NSString*)nonNullStringForKey:(NSString*)key;
  35. @end
  36. @implementation NSUserDefaults(Extras)
  37. - (NSString*)nonNullStringForKey:(NSString*)key {
  38. NSString *result = [self stringForKey:key];
  39. return (result ? result : @"");
  40. }
  41. @end
  42. @interface Map : NSObject {
  43. NSString *path;
  44. BOOL demo, user;
  45. }
  46. @end
  47. @implementation Map
  48. - (id)initWithPath:(NSString*)aPath user:(BOOL)aUser demo:(BOOL)aDemo
  49. {
  50. if((self = [super init]))
  51. {
  52. path = [[aPath stringByDeletingPathExtension] retain];
  53. user = aUser;
  54. demo = aDemo;
  55. }
  56. return self;
  57. }
  58. - (void)dealloc
  59. {
  60. [path release];
  61. [super dealloc];
  62. }
  63. - (NSString*)path { return (demo ? [NSString stringWithFormat:@"--loaddemo=%@", path] : path); } // minor hack
  64. - (NSString*)name { return [path lastPathComponent]; }
  65. - (NSImage*)image
  66. {
  67. NSImage *image = [[NSImage alloc] initWithContentsOfFile:[path stringByAppendingString:@".jpg"]];
  68. if(!image && demo) image = [NSImage imageNamed:tkMAIN];
  69. if(!image) image = [NSImage imageNamed:@"Nomap"];
  70. return image;
  71. }
  72. - (NSString*)text
  73. {
  74. NSString *text = [NSString alloc];
  75. NSError *error;
  76. if([text respondsToSelector:@selector(initWithContentsOfFile:encoding:error:)])
  77. text = [text initWithContentsOfFile:[path stringByAppendingString:@".txt"] encoding:NSASCIIStringEncoding error:&error];
  78. else
  79. text = [text initWithContentsOfFile:[path stringByAppendingString:@".txt"]]; //deprecated in 10.4
  80. if(!text) text = (demo)?@"Recorded demo data":@"";
  81. return text;
  82. }
  83. - (void)setText:(NSString*)text { } // wtf? - damn textfield believes it's editable
  84. - (NSString*)tickIfExists:(NSString*)ext
  85. {
  86. unichar tickCh = 0x2713;
  87. return ([[NSFileManager defaultManager] fileExistsAtPath:[path stringByAppendingString:ext]] ? [NSString stringWithCharacters:&tickCh length:1] : @"");
  88. }
  89. - (NSString*)hasImage { return [self tickIfExists:@".jpg"]; }
  90. - (NSString*)hasText { return [self tickIfExists:@".txt"]; }
  91. - (NSString*)hasCfg { return [self tickIfExists:@".cfg"]; }
  92. - (NSString*)user {
  93. unichar tickCh = 0x2713;
  94. return (user ? [NSString stringWithCharacters:&tickCh length:1] : @"");
  95. }
  96. @end
  97. static int numberForKey(CFDictionaryRef desc, CFStringRef key)
  98. {
  99. CFNumberRef value;
  100. int num = 0;
  101. if ((value = CFDictionaryGetValue(desc, key)) == NULL)
  102. return 0;
  103. CFNumberGetValue(value, kCFNumberIntType, &num);
  104. return num;
  105. }
  106. @interface Launcher(ToolBar)
  107. @end
  108. @implementation Launcher(ToolBar)
  109. - (void)switchViews:(NSToolbarItem *)item
  110. {
  111. NSView *views[] = {view1, view4};
  112. NSView *prefsView = views[[item tag]-1];
  113. //to stop flicker, we make a temp blank view.
  114. NSView *tempView = [[NSView alloc] initWithFrame:[[window contentView] frame]];
  115. [window setContentView:tempView];
  116. [tempView release];
  117. //mojo to get the right frame for the new window.
  118. NSRect newFrame = [window frame];
  119. newFrame.size.height = [prefsView frame].size.height + ([window frame].size.height - [[window contentView] frame].size.height);
  120. newFrame.size.width = [prefsView frame].size.width;
  121. newFrame.origin.y += ([[window contentView] frame].size.height - [prefsView frame].size.height);
  122. //set the frame to newFrame and animate it.
  123. [window setFrame:newFrame display:YES animate:YES];
  124. //set the main content view to the new view we have picked through.
  125. [window setContentView:prefsView];
  126. [window setContentMinSize:[prefsView bounds].size];
  127. }
  128. - (void)initToolBar
  129. {
  130. toolBarItems = [[NSMutableDictionary alloc] init];
  131. NSEnumerator *e = [[self toolbarDefaultItemIdentifiers:nil] objectEnumerator];
  132. NSString *identifier;
  133. while(identifier = [e nextObject])
  134. {
  135. NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:identifier];
  136. int tag = [identifier intValue];
  137. NSString *name = identifier;
  138. SEL action = @selector(displayHelp:);
  139. id target = self;
  140. if(tag) {
  141. NSString *names[] = {tkMAIN, tkSERVER};
  142. name = names[tag-1];
  143. action = @selector(switchViews:);
  144. target = self;
  145. }
  146. [item setTag:tag];
  147. [item setTarget:target];
  148. [item setAction:action];
  149. [item setLabel:NSLocalizedString(name, @"")];
  150. [item setImage:[NSImage imageNamed:name]];
  151. [toolBarItems setObject:item forKey:identifier];
  152. [item release];
  153. }
  154. NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@"assaultcube_toolbar1"];
  155. [toolbar setDelegate:self];
  156. [toolbar setAllowsUserCustomization:NO];
  157. [toolbar setAutosavesConfiguration:NO];
  158. [window setToolbar:toolbar];
  159. [toolbar release];
  160. if([window respondsToSelector:@selector(setShowsToolbarButton:)]) [window setShowsToolbarButton:NO]; //10.4+
  161. //select the first by default
  162. NSToolbarItem *first = [toolBarItems objectForKey:[[self toolbarDefaultItemIdentifiers:nil] objectAtIndex:0]];
  163. [toolbar setSelectedItemIdentifier:[first itemIdentifier]];
  164. [self switchViews:first];
  165. }
  166. #pragma mark toolbar delegate methods
  167. - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag
  168. {
  169. return [toolBarItems objectForKey:itemIdentifier];
  170. }
  171. - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar*)theToolbar
  172. {
  173. return [self toolbarDefaultItemIdentifiers:theToolbar];
  174. }
  175. - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar*)toolbar
  176. {
  177. NSMutableArray *array = (NSMutableArray *)[self toolbarSelectableItemIdentifiers:toolbar];
  178. [array addObject:NSToolbarFlexibleSpaceItemIdentifier];
  179. [array addObject:@"Help"];
  180. return array;
  181. }
  182. - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar
  183. {
  184. NSMutableArray *array = [NSMutableArray array];
  185. NSView *views[] = {view1, view4};
  186. int i;
  187. for(i = 0; i < sizeof(views)/sizeof(NSView*); i++) if(views[i]) [array addObject:[NSString stringWithFormat:@"%d", i+1]];
  188. return array;
  189. }
  190. - (void)displayHelp:(id)sender
  191. {
  192. NSString *path = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:@"/help/README.html"];
  193. if (![[NSWorkspace sharedWorkspace] openFile:path])
  194. NSLog(@"Warning: [[NSWorkspace sharedWorkspace] openFile:path] failed");
  195. }
  196. @end
  197. @implementation Launcher
  198. /* directory where the executable lives */
  199. + (NSString *)cwd
  200. {
  201. return [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"Contents/gamedata"];
  202. }
  203. /* directory where user files are kept - typically /Users/<name>/Application Support/assaultcube_major.minor */
  204. + (NSString*)userdir
  205. {
  206. FSRef folder;
  207. NSString *path = nil;
  208. if(FSFindFolder(kUserDomain, kApplicationSupportFolderType, NO, &folder) == noErr) {
  209. CFURLRef url = CFURLCreateFromFSRef(kCFAllocatorDefault, &folder);
  210. path = [(NSURL *)url path];
  211. CFRelease(url);
  212. path = [path stringByAppendingPathComponent:kUSERCONFGIDIR];
  213. NSFileManager *fm = [NSFileManager defaultManager];
  214. if(![fm fileExistsAtPath:path]) [fm createDirectoryAtPath:path attributes:nil]; //ensure it exists
  215. }
  216. return path;
  217. }
  218. - (void)addResolutionsForDisplay:(CGDirectDisplayID)dspy
  219. {
  220. CFIndex i, cnt;
  221. CFArrayRef modeList = CGDisplayAvailableModes(dspy);
  222. if(modeList == NULL) return;
  223. cnt = CFArrayGetCount(modeList);
  224. for(i = 0; i < cnt; i++) {
  225. CFDictionaryRef mode = CFArrayGetValueAtIndex(modeList, i);
  226. NSString *title = [NSString stringWithFormat:@"%i x %i", numberForKey(mode, kCGDisplayWidth), numberForKey(mode, kCGDisplayHeight)];
  227. if(![resolutions itemWithTitle:title]) [resolutions addItemWithTitle:title];
  228. }
  229. }
  230. - (void)initResolutions
  231. {
  232. CGDirectDisplayID display[kMaxDisplays];
  233. CGDisplayCount numDisplays;
  234. [resolutions removeAllItems];
  235. if(CGGetActiveDisplayList(kMaxDisplays, display, &numDisplays) == CGDisplayNoErr)
  236. {
  237. CGDisplayCount i;
  238. for (i = 0; i < numDisplays; i++)
  239. [self addResolutionsForDisplay:display[i]];
  240. }
  241. [resolutions selectItemAtIndex: [[NSUserDefaults standardUserDefaults] integerForKey:dkRESOLUTION]];
  242. }
  243. - (void)killServer {
  244. if(server > 0) kill(server, SIGKILL); //@WARNING - you do NOT want a 0 or -1 to be accidentally sent a kill!
  245. server = -1;
  246. [multiplayer setTitle:NSLocalizedString(@"Start", @"")];
  247. [console appendText:@"\n \n"];
  248. }
  249. - (void)serverDataAvailable:(NSNotification *)note
  250. {
  251. NSFileHandle *taskOutput = [note object];
  252. NSData *data = [[note userInfo] objectForKey:NSFileHandleNotificationDataItem];
  253. if (data && [data length])
  254. {
  255. NSString *text = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
  256. [console appendText:text];
  257. [text release];
  258. [taskOutput readInBackgroundAndNotify]; //wait for more data
  259. }
  260. else
  261. {
  262. NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  263. [nc removeObserver:self name:NSFileHandleReadCompletionNotification object:taskOutput];
  264. close([taskOutput fileDescriptor]);
  265. [self killServer];
  266. }
  267. }
  268. - (BOOL)launchGame:(NSArray *)args {
  269. NSString *cwd = [Launcher cwd];
  270. NSString *exe = [[NSBundle bundleWithPath:[cwd stringByAppendingPathComponent:[@":s.app" expand]]] executablePath];
  271. BOOL okay = YES;
  272. if([args containsObject:@"-d"])
  273. {
  274. if(server != -1) return NO; // server is already running
  275. const char **argv = (const char**)malloc(sizeof(char*)*([args count] + 2)); //{path, <args>, NULL};
  276. argv[0] = [exe fileSystemRepresentation];
  277. argv[[args count]+1] = NULL;
  278. int i;
  279. for(i = 0; i < [args count]; i++) argv[i+1] = [[args objectAtIndex:i] UTF8String];
  280. int fdm;
  281. NSString *fail = [NSLocalizedString(@"ServerAlertMesg", nil) expand];
  282. switch ( (server = forkpty(&fdm, NULL, NULL, NULL)) ) // forkpty so we can reliably grab SDL console
  283. {
  284. case -1:
  285. [console appendLine:fail];
  286. [self killServer];
  287. okay = NO;
  288. break;
  289. case 0: // child
  290. chdir([cwd fileSystemRepresentation]);
  291. if(execv([exe fileSystemRepresentation], (char*const*)argv) == -1) fprintf(stderr, "%s\n", [fail UTF8String]);
  292. _exit(0);
  293. default: // parent
  294. [multiplayer setTitle:NSLocalizedString(@"Stop", @"")];
  295. NSFileHandle *taskOutput = [[NSFileHandle alloc] initWithFileDescriptor:fdm];
  296. NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  297. [nc addObserver:self selector:@selector(serverDataAvailable:) name:NSFileHandleReadCompletionNotification object:taskOutput];
  298. [taskOutput readInBackgroundAndNotify];
  299. break;
  300. }
  301. free(argv);
  302. }
  303. else
  304. {
  305. NS_DURING
  306. NSTask *task = [[NSTask alloc] init];
  307. [task setCurrentDirectoryPath:cwd];
  308. [task setLaunchPath:exe];
  309. [task setArguments:args]; NSLog(@"%@",[args description]);
  310. [task setEnvironment:[NSDictionary dictionaryWithObjectsAndKeys:
  311. @"1", @"SDL_SINGLEDISPLAY",
  312. @"1", @"SDL_ENABLEAPPEVENTS", nil
  313. ]]; // makes Command-H, Command-M and Command-Q work at least when not in fullscreen
  314. [task launch];
  315. if(server == -1) [NSApp terminate:self]; //if there is a server then don't exit!
  316. NS_HANDLER
  317. //NSLog(@"%@", localException);
  318. NSBeginCriticalAlertSheet(
  319. [NSLocalizedString(@"ClientAlertTitle", @"") expand] , nil, nil, nil,
  320. window, nil, nil, nil, nil,
  321. @"%@", [NSLocalizedString(@"ClientAlertMesg", @"") expand]);
  322. okay = NO;
  323. NS_ENDHANDLER
  324. }
  325. return okay;
  326. }
  327. /*
  328. * nil will just launch the fps game
  329. * "-e.." will launch and run commands
  330. * otherwise we are specifying a map to play
  331. */
  332. - (BOOL)playFile:(id)filename
  333. {
  334. NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
  335. NSArray *res = [[resolutions titleOfSelectedItem] componentsSeparatedByString:@" x "];
  336. NSMutableArray *args = [NSMutableArray array];
  337. [args addObject:[NSString stringWithFormat:@"--home=%@", [Launcher userdir]]];
  338. [args addObject:@"--init"];
  339. [args addObject:[NSString stringWithFormat:@"-w%@", [res objectAtIndex:0]]];
  340. [args addObject:[NSString stringWithFormat:@"-h%@", [res objectAtIndex:1]]];
  341. [args addObject:@"-z32"]; //otherwise seems to have a fondness to use -z16 which looks crap
  342. [args addObject:[NSString stringWithFormat:@"-a%ld", (long)[defs integerForKey:dkFSAA]]];
  343. [args addObject:[NSString stringWithFormat:@"-t%ld", (long)[defs integerForKey:dkFULLSCREEN]]];
  344. if ([stencil state] == NSOnState)
  345. [args addObject:@"-s8"];
  346. NSMutableArray *cmds = [NSMutableArray array];
  347. if(filename)
  348. {
  349. if([filename hasPrefix:@"-e"])
  350. [cmds addObject:[filename substringFromIndex:2]];
  351. if([filename hasPrefix:[NSString stringWithFormat:@"%@://", kASSAULTCUBE]])
  352. [args addObject:filename];
  353. else
  354. {
  355. if([filename hasPrefix:@"--loaddemo"])
  356. [args addObject:filename];
  357. else
  358. [args addObject:[NSString stringWithFormat:@"--loadmap=%@", filename]];
  359. }
  360. }
  361. if([cmds count] > 0)
  362. {
  363. NSString *script = [cmds objectAtIndex:0];
  364. int i;
  365. for(i = 1; i < [cmds count]; i++) script = [NSString stringWithFormat:@"%@;%@", script, [cmds objectAtIndex:i]];
  366. [args addObject:[NSString stringWithFormat:@"-e%@", script]];
  367. }
  368. NSEnumerator *e = [[[defs nonNullStringForKey:dkADVANCEDOPTS] componentsSeparatedByString:@" "] objectEnumerator];
  369. NSString *opt;
  370. while(opt = [e nextObject]) if([opt length] != 0) [args addObject:opt]; //skip empty ones
  371. return [self launchGame:args];
  372. }
  373. - (void)scanMaps:(id)obj //@note threaded!
  374. {
  375. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  376. int i;
  377. for(i = 0; i < 2; i++)
  378. {
  379. NSString *dir = (i==0) ? [Launcher cwd] : [Launcher userdir];
  380. NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtPath:dir];
  381. NSString *file;
  382. while(file = [enumerator nextObject])
  383. {
  384. NSString *role = [fileRoles objectForKey:[file pathExtension]];
  385. if(role)
  386. {
  387. Map *map = [[Map alloc] initWithPath:[dir stringByAppendingPathComponent:file] user:(i==1) demo:[role isEqual:@"Viewer"]];
  388. [maps performSelectorOnMainThread:@selector(addObject:) withObject:map waitUntilDone:NO];
  389. }
  390. }
  391. }
  392. [prog performSelectorOnMainThread:@selector(stopAnimation:) withObject:nil waitUntilDone:NO];
  393. [pool release];
  394. }
  395. - (void)initMaps
  396. {
  397. [prog startAnimation:nil];
  398. [maps removeObjects:[maps arrangedObjects]];
  399. [NSThread detachNewThreadSelector: @selector(scanMaps:) toTarget:self withObject:nil];
  400. }
  401. - (void)awakeFromNib
  402. {
  403. //generate some pretty icons if they are missing
  404. NSRect region = NSMakeRect(0, 0, 64, 64);
  405. NSImage *image = [NSImage imageNamed:tkMAIN];
  406. if(!image) {
  407. image = [[NSImage imageNamed:@"NSApplicationIcon"] copy];
  408. [image setSize:region.size];
  409. [image setName:tkMAIN]; //one less image to include
  410. }
  411. [self initToolBar];
  412. [window setBackgroundColor:[NSColor colorWithDeviceRed:0.90 green:0.90 blue:0.90 alpha:1.0]]; //Apples 'mercury' crayon color
  413. //from the plist we determine that dmo->Viewer, and cgz->Editor
  414. fileRoles = [[NSMutableDictionary dictionary] retain];
  415. NSEnumerator *types = [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDocumentTypes"] objectEnumerator];
  416. NSDictionary *type;
  417. while((type = [types nextObject])) {
  418. NSString *role = [type objectForKey:@"CFBundleTypeRole"];
  419. NSEnumerator *exts = [[type objectForKey:@"CFBundleTypeExtensions"] objectEnumerator];
  420. NSString *ext;
  421. while((ext = [exts nextObject])) [fileRoles setObject:role forKey:ext];
  422. }
  423. NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
  424. NSFileManager *fm = [NSFileManager defaultManager];
  425. NSString *appVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
  426. NSString *version = [defs stringForKey:dkVERSION];
  427. if(!version || ![version isEqual:appVersion])
  428. {
  429. NSLog(@"Upgraded Version...");
  430. //need to flush lurking config files - they're automatically generated, so no big deal...
  431. NSString *dir = [Launcher userdir];
  432. [fm removeFileAtPath:[dir stringByAppendingPathComponent:@"init.cfg"] handler:nil];
  433. [fm removeFileAtPath:[dir stringByAppendingPathComponent:@"saved.cfg"] handler:nil];
  434. }
  435. [defs setObject:appVersion forKey:dkVERSION];
  436. [self initMaps];
  437. [self initResolutions];
  438. server = -1;
  439. [NSApp setDelegate:self]; //so can catch the double-click, dropped files, termination
  440. [[NSAppleEventManager sharedAppleEventManager] setEventHandler:self andSelector:@selector(getUrl:withReplyEvent:) forEventClass:kInternetEventClass andEventID:kAEGetURL];
  441. }
  442. #pragma mark -
  443. #pragma mark application delegate
  444. - (void)applicationDidFinishLaunching:(NSNotification *)note {
  445. NSFileManager *fm = [NSFileManager defaultManager];
  446. NSString *dir = [Launcher cwd];
  447. if(![fm fileExistsAtPath:dir])
  448. NSBeginCriticalAlertSheet(
  449. [NSLocalizedString(@"InitAlertTitle", @"") expand], nil, nil, nil,
  450. window, self, nil, nil, nil,
  451. @"%@", [NSLocalizedString(@"InitAlertMesg", @"") expand]);
  452. }
  453. -(BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication {
  454. return YES;
  455. }
  456. - (void)applicationWillTerminate: (NSNotification *)note {
  457. [self killServer];
  458. }
  459. //we register 'cgz' and 'dmo' as doc types
  460. - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
  461. {
  462. NSString *role = [fileRoles objectForKey:[filename pathExtension]];
  463. if(!role) return NO;
  464. BOOL demo = [role isEqual:@"Viewer"];
  465. filename = [filename stringByDeletingPathExtension]; //chop off extension
  466. int i;
  467. for(i = 0; i < 2; i++) {
  468. NSString *pkg = (i == 0) ? [Launcher cwd] : [Launcher userdir];
  469. if(!demo) pkg = [pkg stringByAppendingPathComponent:@"packages/maps/"];
  470. if([filename hasPrefix:pkg])
  471. {
  472. filename = [filename lastPathComponent]; //chop off extension
  473. return [self playFile:(demo ? [NSString stringWithFormat:@"--loaddemo=%@", filename] : filename)];
  474. }
  475. }
  476. NSBeginCriticalAlertSheet(
  477. [NSLocalizedString(@"FileAlertTitle", @"") expand], NSLocalizedString(@"Ok", @""), NSLocalizedString(@"Cancel", @""), nil,
  478. window, self, @selector(openPackageFolder:returnCode:contextInfo:), nil, nil,
  479. @"%@", [NSLocalizedString(@"FileAlertMesg", @"") expand]);
  480. return NO;
  481. }
  482. - (void)openPackageFolder:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
  483. {
  484. if(returnCode == 0) return;
  485. [self openUserdir:nil];
  486. }
  487. //we register 'assaultcube' as a url scheme
  488. - (void)getUrl:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent
  489. {
  490. NSURL *url = [NSURL URLWithString:[[event paramDescriptorForKeyword:keyDirectObject] stringValue]];
  491. if(!url) return;
  492. [self playFile:[[event paramDescriptorForKeyword:keyDirectObject] stringValue]]; //use the game internal parser
  493. //[self playFile:[NSString stringWithFormat:@"-econnect %@", [url host]]];
  494. }
  495. #pragma mark interface actions
  496. - (IBAction)multiplayerAction:(id)sender
  497. {
  498. [window makeFirstResponder:window]; //ensure fields are exited and committed
  499. if(server != -1)
  500. {
  501. [self killServer];
  502. }
  503. else
  504. {
  505. NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
  506. NSMutableArray *args = [NSMutableArray arrayWithObject:@"-d"];
  507. NSEnumerator *e = [[[defs nonNullStringForKey:dkSERVEROPTS] componentsSeparatedByString:@" "] objectEnumerator];
  508. NSString *opt;
  509. while(opt = [e nextObject]) if([opt length] != 0) [args addObject:opt]; //skip empty ones
  510. NSString *desc = [defs nonNullStringForKey:dkDESCRIPTION];
  511. if (![desc isEqual:@""]) [args addObject:[NSString stringWithFormat:@"-n%@", desc]];
  512. NSString *pass = [defs nonNullStringForKey:dkPASSWORD];
  513. if (![pass isEqual:@""]) [args addObject:[NSString stringWithFormat:@"-p%@", pass]];
  514. if (![[admin_password stringValue] isEqual:@""]) [args addObject:[NSString stringWithFormat:@"-x%@", pass]];
  515. int clients = [defs integerForKey:dkMAXCLIENTS];
  516. if (clients > 0) [args addObject:[NSString stringWithFormat:@"-c%d", clients]];
  517. //server doesn't support --home
  518. //[args addObject:[NSString stringWithFormat:@"--home%@", [Launcher userdir]]];
  519. [self launchGame:args];
  520. }
  521. }
  522. - (IBAction)playAction:(id)sender
  523. {
  524. [window makeFirstResponder:window]; //ensure fields are exited and committed
  525. [self playFile:nil];
  526. }
  527. - (IBAction)playMap:(id)sender
  528. {
  529. NSArray *sel = [maps selectedObjects];
  530. if(sel && [sel count] > 0) [self playFile:[[sel objectAtIndex:0] path]];
  531. }
  532. - (IBAction)openUserdir:(id)sender
  533. {
  534. [[NSWorkspace sharedWorkspace] openFile:[Launcher userdir]];
  535. }
  536. @end