Cube  Cube.m at [c39abceb9b]

File src/Cube.m artifact 1868ef5fa1 part of check-in c39abceb9b


// main.cpp: initialisation & main loop

#include "cube.h"

#import "Command.h"
#import "Player.h"
#import "Variable.h"

OF_APPLICATION_DELEGATE(Cube)

static int gamespeed = 100;
VARB(gamespeed, 10, 1000, ^ { return gamespeed; }, ^ (int value) {
	if (multiplayer())
		gamespeed = 100;
	else
		gamespeed = value;
})

VARP(minmillis, 0, 5, 1000)

@implementation Cube
{
	int _width, _height;
}

+ (Cube *)sharedInstance
{
	return (Cube *)OFApplication.sharedApplication.delegate;
}

- (void)applicationDidFinishLaunching: (OFNotification *)notification
{
	@autoreleasepool {
		bool dedicated, windowed;
		int par = 0, uprate = 0, maxcl = 4;
		OFString *__autoreleasing sdesc, *__autoreleasing ip;
		OFString *__autoreleasing master, *__autoreleasing passwd;

		processInitQueue();

#define log(s) conoutf(@"init: %@", s)
		log(@"sdl");

		const OFOptionsParserOption options[] = {
			{ 'd', @"dedicated", 0, &dedicated, NULL },
			{ 't', @"window", 0, &windowed, NULL },
			{ 'w', @"width", 1, NULL, NULL },
			{ 'h', @"height", 1, NULL, NULL },
			{ 'u', @"upload-rate", 1, NULL, NULL },
			{ 'n', @"server-desc", 1, NULL, &sdesc },
			{ 'i', @"ip", 1, NULL, &ip },
			{ 'm', @"master", 1, NULL, &master },
			{ 'p', @"password", 1, NULL, &passwd },
			{ 'c', @"max-clients", 1, NULL, NULL },
			{ '\0', nil, 0, NULL, NULL }
		};
		OFOptionsParser *optionsParser =
		    [OFOptionsParser parserWithOptions: options];
		OFUnichar option;
		while ((option = [optionsParser nextOption]) != '\0') {
			switch (option) {
			case 'w':
				_width = optionsParser.argument.intValue;
				break;
			case 'h':
				_height = optionsParser.argument.intValue;
				break;
			case 'u':
				uprate = optionsParser.argument.intValue;
				break;
			case 'c':
				maxcl = optionsParser.argument.intValue;
				break;
			case ':':
			case '=':
			case '?':
				conoutf(@"unknown commandline option");
				[OFApplication terminateWithStatus: 1];
			}
		}

		if (sdesc == nil)
			sdesc = @"";
		if (ip == nil)
			ip = @"";
		if (passwd == nil)
			passwd = @"";

		_gameDataIRI =
		    [OFFileManager.defaultManager currentDirectoryIRI];
		_userDataIRI =
		    [OFFileManager.defaultManager currentDirectoryIRI];

		[OFFileManager.defaultManager createDirectoryAtIRI:
		    [_userDataIRI IRIByAppendingPathComponent: @"demos"]
						createParents: true];
		[OFFileManager.defaultManager createDirectoryAtIRI:
		    [_userDataIRI IRIByAppendingPathComponent: @"savegames"]
						createParents: true];

		if (SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO | par) < 0)
			fatal(@"Unable to initialize SDL");

		initEntities();
		initPlayers();

		log(@"net");
		if (enet_initialize() < 0)
			fatal(@"Unable to initialise network module");

		initclient();
		// never returns if dedicated
		initserver(dedicated, uprate, sdesc, ip, master, passwd, maxcl);

		log(@"world");
		empty_world(7, true);

		log(@"video: sdl");
		if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
			fatal(@"Unable to initialize SDL Video");

		if (_width == 0 || _height == 0) {
			SDL_DisplayMode mode;

			if (SDL_GetDesktopDisplayMode(0, &mode) == 0) {
				_width = mode.w;
				_height = mode.h;
			} else {
				_width = 1920;
				_height = 1080;
			}
		}

		log(@"video: mode");
		SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
		if ((_window = SDL_CreateWindow("cube engine",
		    SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
		    _width, _height, SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL |
		    (!windowed ? SDL_WINDOW_FULLSCREEN : 0))) == NULL ||
		    SDL_GL_CreateContext(_window) == NULL)
			fatal(@"Unable to create OpenGL screen");

		log(@"video: misc");
		SDL_SetWindowGrab(_window, SDL_TRUE);
		SDL_SetRelativeMouseMode(SDL_TRUE);
		SDL_ShowCursor(0);

		log(@"gl");
		gl_init(_width, _height);

		log(@"basetex");
		int xs, ys;
		if (!installtex(2, [_gameDataIRI IRIByAppendingPathComponent:
		    @"data/newchars.png"], &xs, &ys, false) ||
		    !installtex(3, [_gameDataIRI IRIByAppendingPathComponent:
		    @"data/martin/base.png"], &xs, &ys, false) ||
		    !installtex(6, [_gameDataIRI IRIByAppendingPathComponent:
		    @"data/martin/ball1.png"], &xs, &ys, false) ||
		    !installtex(7, [_gameDataIRI IRIByAppendingPathComponent:
		    @"data/martin/smoke.png"], &xs, &ys, false) ||
		    !installtex(8, [_gameDataIRI IRIByAppendingPathComponent:
		    @"data/martin/ball2.png"], &xs, &ys, false) ||
		    !installtex(9, [_gameDataIRI IRIByAppendingPathComponent:
		    @"data/martin/ball3.png"], &xs, &ys, false) ||
		    !installtex(4, [_gameDataIRI IRIByAppendingPathComponent:
		    @"data/explosion.jpg"], &xs, &ys, false) ||
		    !installtex(5, [_gameDataIRI IRIByAppendingPathComponent:
		    @"data/items.png"], &xs, &ys, false) ||
		    !installtex(1, [_gameDataIRI IRIByAppendingPathComponent:
		    @"data/crosshair.png"], &xs, &ys, false))
			fatal(@"could not find core textures (hint: run cube "
			    @"from the parent of the bin directory)");

		log(@"sound");
		initsound();

		log(@"cfg");
		newmenu(@"frags\tpj\tping\tteam\tname");
		newmenu(@"ping\tplr\tserver");
		exec(@"data/keymap.cfg");
		exec(@"data/menus.cfg");
		exec(@"data/prefabs.cfg");
		exec(@"data/sounds.cfg");
		exec(@"servers.cfg");
		if (!execfile([_userDataIRI
		    IRIByAppendingPathComponent: @"config.cfg"]))
			execfile([_gameDataIRI
			    IRIByAppendingPathComponent: @"data/defaults.cfg"]);
		exec(@"autoexec.cfg");

		log(@"localconnect");
		localconnect();
		// if this map is changed, also change depthcorrect()
		changemap(@"metl3");

		log(@"mainloop");
	}

	OFDate *past = [OFDate date];
	int ignore = 5;
	for (;;) {
		@autoreleasepool {
			[OFRunLoop.mainRunLoop runUntilDate: past];

			Player *player1 = Player.player1;

			int millis = SDL_GetTicks() * gamespeed / 100;
			if (millis - lastmillis > 200)
				lastmillis = millis - 200;
			else if (millis - lastmillis < 1)
				lastmillis = millis - 1;
			if (millis - lastmillis < minmillis)
				SDL_Delay(minmillis - (millis - lastmillis));

			cleardlights();
			updateworld(millis);

			if (!demoplayback)
				serverslice((int)time(NULL), 0);

			static float fps = 30.0f;
			fps = (1000.0f / curtime + fps * 50) / 51;

			computeraytable(player1.origin.x, player1.origin.y);
			readdepth(_width, _height);
			SDL_GL_SwapWindow(_window);
			extern void updatevol();
			updatevol();

			// cheap hack to get rid of initial sparklies, even when
			// triple buffering etc.
			if (_framesInMap++ < 5) {
				player1.yaw += 5;
				gl_drawframe(_width, _height, fps);
				player1.yaw -= 5;
			}

			gl_drawframe(_width, _height, fps);

			SDL_Event event;
			int lasttype = 0, lastbut = 0;
			while (SDL_PollEvent(&event)) {
				switch (event.type) {
				case SDL_QUIT:
					[self quit];
					break;
				case SDL_KEYDOWN:
				case SDL_KEYUP:
					if (_repeatsKeys ||
					    event.key.repeat == 0)
						keypress(event.key.keysym.sym,
						    (event.key.state ==
						    SDL_PRESSED));
					break;
				case SDL_TEXTINPUT:
					input(@(event.text.text));
					break;
				case SDL_MOUSEMOTION:
					if (ignore) {
						ignore--;
						break;
					}
					mousemove(event.motion.xrel,
					    event.motion.yrel);
					break;
				case SDL_MOUSEBUTTONDOWN:
				case SDL_MOUSEBUTTONUP:
					if (lasttype == event.type &&
					    lastbut == event.button.button)
						// why?? get event twice without
						// it
						break;

					keypress(-event.button.button,
					    event.button.state != 0);
					lasttype = event.type;
					lastbut = event.button.button;
					break;
				}
			}
		}
	}
}

- (void)applicationWillTerminate: (OFNotification *)notification
{
	stop();
	disconnect(true, false);
	writecfg();
	cleangl();
	cleansound();
	cleanupserver();
	SDL_ShowCursor(1);
	SDL_Quit();
}

- (void)showMessage: (OFString *)msg
{
#ifdef _WIN32
	MessageBoxW(
	    NULL, msg.UTF16String, L"cube fatal error", MB_OK | MB_SYSTEMMODAL);
#else
	[OFStdOut writeString: msg];
#endif
}

- (void)screenshot
{
	SDL_Surface *image;
	SDL_Surface *temp;

	if ((image = SDL_CreateRGBSurface(SDL_SWSURFACE, _width, _height, 24,
	    0x0000FF, 0x00FF00, 0xFF0000, 0)) != NULL) {
		if ((temp = SDL_CreateRGBSurface(SDL_SWSURFACE, _width, _height,
		    24, 0x0000FF, 0x00FF00, 0xFF0000, 0)) != NULL) {
			glReadPixels(0, 0, _width, _height, GL_RGB,
			    GL_UNSIGNED_BYTE, image->pixels);

			for (int idx = 0; idx < _height; idx++) {
				char *dest =
				    (char *)temp->pixels + 3 * _width * idx;
				memcpy(dest, (char *)image->pixels + 3 *
				    _width * (_height - 1 - idx), 3 * _width);
				endianswap(dest, 3, _width);
			}

			OFString *path = [OFString stringWithFormat:
			    @"screenshots/screenshot_%d.bmp", lastmillis];
			SDL_SaveBMP(temp,
			    [_userDataIRI IRIByAppendingPathComponent: path]
			    .fileSystemRepresentation.UTF8String);
			SDL_FreeSurface(temp);
		}

		SDL_FreeSurface(image);
	}
}

- (void)quit
{
	writeservercfg();
	[OFApplication terminateWithStatus: 0];
}
@end

// failure exit
void
fatal(OFConstantString *s, ...)
{
	va_list args;
	va_start(args, s);
	OFMutableString *msg = [[OFMutableString alloc] initWithFormat: s
							     arguments: args];
	va_end(args);

	[msg appendFormat: @" (%s)\n", SDL_GetError()];

	[Cube.sharedInstance showMessage: msg];
	[OFApplication terminateWithStatus: 1];
}

// normal exit
COMMAND(quit, ARG_NONE, ^ {
	[Cube.sharedInstance quit];
})

COMMAND(screenshot, ARG_NONE, ^ {
	[Cube.sharedInstance screenshot];
})