/*
===========================================================================
Return to Castle Wolfenstein multiplayer GPL Source Code
Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (RTCW MP Source Code).
RTCW MP Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
RTCW MP Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with RTCW MP Source Code. If not, see .
In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
// $Header$
#import "Q3Controller.h"
#import
#import
#include "client.h"
#include "macosx_local.h"
#ifdef OMNI_TIMER
#import "macosx_timers.h"
#endif
#define MAX_ARGC 1024
static qboolean Sys_IsProcessingTerminationRequest = qfalse;
static void Sys_CreatePathToFile( NSString *path, NSDictionary *attributes );
@interface Q3Controller ( Private )
- (void)quakeMain;
@end
@implementation Q3Controller
#ifndef DEDICATED
- (void)applicationDidFinishLaunching : (NSNotification *)notification;
{
NS_DURING {
[self quakeMain];
} NS_HANDLER {
Sys_Error( "%@", [localException reason] );
} NS_ENDHANDLER;
Sys_Quit();
}
-(void)applicationDidUnhide : (NSNotification *)notification;
{
// Don't reactivate the game if we are asking whether to quit
if ( Sys_IsProcessingTerminationRequest ) {
return;
}
if ( !Sys_Unhide() ) {
// Didn't work -- hide again so we should get another chance to unhide later
[NSApp hide : nil];
}
}
-(NSApplicationTerminateReply)applicationShouldTerminate : (NSApplication *)sender;
{
int choice;
if ( !Sys_IsHidden ) {
// We're terminating via -terminate:
return NSTerminateNow;
}
// Avoid reactivating GL when we unhide due to this panel
Sys_IsProcessingTerminationRequest = qtrue;
choice = NSRunAlertPanel( nil, @ "Quit without saving?", @ "Don't Quit", @ "Quit", nil );
Sys_IsProcessingTerminationRequest = qfalse;
if ( choice == NSAlertAlternateReturn ) {
return NSTerminateNow;
}
// Make sure we get re-hidden
[NSApp hide : nil];
return NSTerminateCancel;
}
// Actions
-(IBAction)paste : (id)sender;
{
int shiftWasDown, insertWasDown;
unsigned int currentTime;
currentTime = Sys_Milliseconds();
// Save the original keyboard state
shiftWasDown = keys[K_SHIFT].down;
insertWasDown = keys[K_INS].down;
// Fake a Shift-Insert keyboard event
keys[K_SHIFT].down = qtrue;
Sys_QueEvent( currentTime, SE_KEY, K_INS, qtrue, 0, NULL );
Sys_QueEvent( currentTime, SE_KEY, K_INS, qfalse, 0, NULL );
// Restore the original keyboard state
keys[K_SHIFT].down = shiftWasDown;
keys[K_INS].down = insertWasDown;
}
extern void CL_Quit_f( void );
-(IBAction)requestTerminate : (id)sender;
{
Com_Quit_f();
// UI_QuitMenu();
}
-(void)showBanner;
{
static BOOL hasShownBanner = NO;
if ( !hasShownBanner ) {
cvar_t *showBanner;
hasShownBanner = YES;
showBanner = Cvar_Get( "cl_showBanner", "1", 0 );
if ( showBanner->integer != 0 ) {
NSPanel *splashPanel;
NSImage *bannerImage;
NSRect bannerRect;
NSImageView *bannerImageView;
bannerImage = [[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForImageResource:@ "banner.jpg"]];
bannerRect = NSMakeRect( 0.0, 0.0, [bannerImage size].width, [bannerImage size].height );
splashPanel = [[NSPanel alloc] initWithContentRect:bannerRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
bannerImageView = [[NSImageView alloc] initWithFrame:bannerRect];
[bannerImageView setImage : bannerImage];
[splashPanel setContentView : bannerImageView];
[bannerImageView release];
[splashPanel center];
[splashPanel setHasShadow : YES];
[splashPanel orderFront : nil];
[NSThread sleepUntilDate :[NSDate dateWithTimeIntervalSinceNow : 2.5]];
[splashPanel close];
[bannerImage release];
}
}
}
// Services
-(void)connectToServer : (NSPasteboard *)pasteboard userData : (NSString *)data error : (NSString **)error;
{
NSArray *pasteboardTypes;
pasteboardTypes = [pasteboard types];
if ([pasteboardTypes containsObject : NSStringPboardType] ) {
NSString *requestedServer;
requestedServer = [pasteboard stringForType:NSStringPboardType];
if ( requestedServer ) {
Cbuf_AddText( va( "connect %s\n", [requestedServer cString] ) );
return;
}
}
*error = @ "Unable to connect to server: could not find string on pasteboard";
}
-(void)performCommand : (NSPasteboard *)pasteboard userData : (NSString *)data error : (NSString **)error;
{
NSArray *pasteboardTypes;
pasteboardTypes = [pasteboard types];
if ([pasteboardTypes containsObject : NSStringPboardType] ) {
NSString *requestedCommand;
requestedCommand = [pasteboard stringForType:NSStringPboardType];
if ( requestedCommand ) {
Cbuf_AddText( va( "%s\n", [requestedCommand cString] ) );
return;
}
}
*error = @ "Unable to perform command: could not find string on pasteboard";
}
#endif
- (void)quakeMain;
{
NSAutoreleasePool *pool;
int argc = 0;
const char *argv[MAX_ARGC];
NSProcessInfo *processInfo;
NSArray *arguments;
unsigned int argumentIndex, argumentCount;
NSFileManager *defaultManager;
unsigned int commandLineLength;
NSString *installationPathKey, *installationPath;
char *cmdline;
BOOL foundDirectory;
NSString *appName, *demoAppName, *selectButton;
pool = [[NSAutoreleasePool alloc] init];
[NSApp setServicesProvider : self];
processInfo = [NSProcessInfo processInfo];
arguments = [processInfo arguments];
argumentCount = [arguments count];
for ( argumentIndex = 0; argumentIndex < argumentCount; argumentIndex++ ) {
NSString *arg;
arg = [arguments objectAtIndex:argumentIndex];
// Don't pass the Process Serial Number command line arg that the Window Server/Finder invokes us with
if ([arg hasPrefix : @ "-psn_"] ) {
continue;
}
argv[argc++] = strdup([arg cString] );
}
// Figure out where the level data is stored.
installationPathKey = @ "RetailInstallationPath";
installationPath = [[NSUserDefaults standardUserDefaults] objectForKey:installationPathKey];
if ( !installationPath ) {
// Default to the directory containing the executable (which is where most users will want to put it
installationPath = [[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent];
installationPath = [installationPath stringByAppendingPathComponent: @ "wolfmp"];
}
appName = [[[NSBundle mainBundle] infoDictionary] objectForKey: @ "CFBundleName"];
#ifdef DEDICATED
#warning TJW: We are hard coding the app name here since the dedicated server is a tool, not a app bundle and does not have access to the Info.plist that the client app does. Suck.
appName = @ "WolfensteinMP";
#endif
demoAppName = appName;
while ( YES ) {
NSString *dataPath;
NSOpenPanel *openPanel;
int result;
foundDirectory = NO;
defaultManager = [NSFileManager defaultManager];
NSLog( @ "Candidate installation path = %@", installationPath );
#ifdef PRE_RELEASE_DEMO
dataPath = [installationPath stringByAppendingPathComponent: @ "demomain"];
#else
dataPath = [installationPath stringByAppendingPathComponent: @ "main"];
#endif
if ([defaultManager fileExistsAtPath : dataPath] ) {
// Check that the data directory contains at least one .pk3 file. We don't know what it will be named, so don't hard code a name (for example it might be named 'french.pk3' for a French release
NSArray *files;
unsigned int fileIndex;
files = [defaultManager directoryContentsAtPath: dataPath];
fileIndex = [files count];
while ( fileIndex-- ) {
if ([[files objectAtIndex : fileIndex] hasSuffix : @ "pk3"] ) {
//NSLog(@"Found %@.", [files objectAtIndex: fileIndex]);
foundDirectory = YES;
break;
}
}
}
if ( foundDirectory ) {
break;
}
#ifdef DEDICATED
#warning TJW: We are hard coding the app name and default domain here since the dedicated server is a tool, not a app bundle and does not have access to the Info.plist that the client app does. Suck.
NSLog( @ "Unable to determine installation directory. Please move the executable into the '%@' installation directory or add a '%@' key in the 'WolfDedicated' defaults domain.", appName, installationPathKey, [[NSBundle mainBundle] bundleIdentifier] );
break;
// Sys_Quit();
// exit(1);
#else
selectButton = @ "Select Retail Installation...";
result = NSRunAlertPanel( demoAppName, @ "You need to select the installation directory for %@ (not any directory inside of it -- the installation directory itself).", selectButton, @ "Quit", nil, appName );
switch ( result ) {
case NSAlertDefaultReturn:
break;
default:
Sys_Quit();
break;
}
openPanel = [NSOpenPanel openPanel];
[openPanel setAllowsMultipleSelection : NO];
[openPanel setCanChooseDirectories : YES];
[openPanel setCanChooseFiles : NO];
result = [openPanel runModalForDirectory:nil file:nil];
if ( result == NSOKButton ) {
NSArray *filenames;
filenames = [openPanel filenames];
if ([filenames count] == 1 ) {
installationPath = [filenames objectAtIndex:0];
[[NSUserDefaults standardUserDefaults] setObject : installationPath forKey : installationPathKey];
[[NSUserDefaults standardUserDefaults] synchronize];
}
}
#endif
}
// Create the application support directory if it doesn't exist already
do {
NSArray *results;
NSString *libraryPath, *homePath, *filePath;
NSDictionary *attributes;
results = NSSearchPathForDirectoriesInDomains( NSLibraryDirectory, NSUserDomainMask, YES );
if ( ![results count] ) {
break;
}
libraryPath = [results objectAtIndex: 0];
homePath = [libraryPath stringByAppendingPathComponent: @ "Application Support"];
homePath = [homePath stringByAppendingPathComponent: appName];
filePath = [homePath stringByAppendingPathComponent: @ "foo"];
attributes = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithUnsignedInt: 0750], NSFilePosixPermissions, nil];
NS_DURING {
Sys_CreatePathToFile( filePath, attributes );
Sys_SetDefaultHomePath([homePath fileSystemRepresentation] );
} NS_HANDLER {
NSLog( @ "Exception: %@", localException );
#ifndef DEDICATED
NSRunAlertPanel( nil, @ "Unable to create '%@'. Please make sure that you have permission to write to this folder and re-run the game.", @ "OK", nil, nil, homePath );
#endif
Sys_Quit();
} NS_ENDHANDLER;
} while ( 0 );
// Provoke the CD scanning code into looking up the CD.
Sys_CheckCD();
// Let the filesystem know where our local install is
Sys_SetDefaultInstallPath([installationPath cString] );
// merge the command line, this is kinda silly
for ( commandLineLength = 1, argumentIndex = 1; argumentIndex < argc; argumentIndex++ )
commandLineLength += strlen( argv[argumentIndex] ) + 1;
cmdline = malloc( commandLineLength );
*cmdline = '\0';
for ( argumentIndex = 1; argumentIndex < argc; argumentIndex++ ) {
if ( argumentIndex > 1 ) {
strcat( cmdline, " " );
}
strcat( cmdline, argv[argumentIndex] );
}
Com_Init( cmdline );
#ifndef DEDICATED
[NSApp activateIgnoringOtherApps : YES];
#endif
while ( 1 ) {
Com_Frame();
// We should think about doing this less frequently than every frame
[pool release];
pool = [[NSAutoreleasePool alloc] init];
}
[pool release];
}
@end
// Creates any directories needed to be able to create a file at the specified path. Raises an exception on failure.
static void Sys_CreatePathToFile( NSString *path, NSDictionary *attributes ) {
NSArray *pathComponents;
unsigned int dirIndex, dirCount;
unsigned int startingIndex;
NSFileManager *manager;
manager = [NSFileManager defaultManager];
pathComponents = [path pathComponents];
dirCount = [pathComponents count] - 1;
startingIndex = 0;
for ( dirIndex = startingIndex; dirIndex < dirCount; dirIndex++ ) {
NSString *partialPath;
BOOL fileExists;
partialPath = [NSString pathWithComponents:[pathComponents subarrayWithRange:NSMakeRange( 0, dirIndex + 1 )]];
// Don't use the 'fileExistsAtPath:isDirectory:' version since it doesn't traverse symlinks
fileExists = [manager fileExistsAtPath:partialPath];
if ( !fileExists ) {
if ( ![manager createDirectoryAtPath : partialPath attributes : attributes] ) {
[NSException raise : NSGenericException format : @ "Unable to create a directory at path: %@", partialPath];
}
} else {
NSDictionary *attributes;
attributes = [manager fileAttributesAtPath:partialPath traverseLink:YES];
if ( ![[attributes objectForKey : NSFileType] isEqualToString : NSFileTypeDirectory] ) {
[NSException raise : NSGenericException format : @ "Unable to write to path \"%@\" because \"%@\" is not a directory",
path, partialPath];
}
}
}
}