~ubuntu-branches/debian/stretch/assaultcube-data/stretch

« back to all changes in this revision

Viewing changes to source/xcode/Launcher.m

  • Committer: Bazaar Package Importer
  • Author(s): Gonéri Le Bouder, Ansgar Burchardt, Gonéri Le Bouder
  • Date: 2010-04-02 23:37:55 UTC
  • mfrom: (1.1.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20100402233755-kf74fxwlu634o6vg
Tags: 1.0.4+repack1-1
[ Ansgar Burchardt ]
* debian/control: fix typo in short description

[ Gonéri Le Bouder ]
* Upgrade to 1.0.4
* bump standards-version to 3.8.4
* Add Depends: ${misc:Depends} just to avoid a lintian warning
* Add a debian/source/format file for the same reason

Show diffs side-by-side

added added

removed removed

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