Cube  Check-in [489124a92f]

Overview
Comment:Use one autorelease pool per frame

This way, nowhere else autorelease pools need to be managed.

Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 489124a92fd2a7e6d543b58ce50e454f2cb1647c81b4ba637d6c252404012ddd
User & Date: js on 2025-03-16 10:11:39
Other Links: manifest | tags
Context
2025-03-20
10:52
Fix projectiles check-in: be136b699f user: js tags: trunk
2025-03-16
10:11
Use one autorelease pool per frame check-in: 489124a92f user: js tags: trunk
00:05
Migrate vectors check-in: 853e760619 user: js tags: trunk
Changes

Modified src/Cube.mm from [e347edc067] to [fe88d055bb].

17
18
19
20
21
22
23

24
25
26
27




28
29

30
31
32

33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69





































70
71
72
73
74
75
76






77

78
79



80
81
82
83
84
85
86
87
88








89
90
91


92
93

94
95
96
97



98
99
100
101



102
103
104


105
106
107
108



109
110
111


112
113
114
115
116
117
118
119
120








121
122
123
124
125
126
127
128
129










130
131
132
133
134




135
136
137


138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176








































177
178
179


180
181
182
183
184
185
186
187
188
189
190
191
192













193
194
195
196
197




198
199
200



201

202

203

204
205
206
207
208
209
210
211







212
213
214


215
216
217


218
219
220


221
222
223
224
225
226





227
228
229
230
231
232
233
234







235
236

237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
















252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
















269
270
271
272
273
274
275
276
277
278









279
280
281
282
283
284
285
286
17
18
19
20
21
22
23
24




25
26
27
28
29

30
31
32

33
34




































35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72






73
74
75
76
77
78
79
80


81
82
83
84








85
86
87
88
89
90
91
92
93


94
95
96

97
98



99
100
101
102



103
104
105
106


107
108
109



110
111
112
113


114
115
116








117
118
119
120
121
122
123
124
125








126
127
128
129
130
131
132
133
134
135
136




137
138
139
140
141


142
143
144






































145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185


186
187
188












189
190
191
192
193
194
195
196
197
198
199
200
201
202




203
204
205
206
207


208
209
210
211
212
213
214

215
216







217
218
219
220
221
222
223
224


225
226
227


228
229
230


231
232
233





234
235
236
237
238
239







240
241
242
243
244
245
246
247

248
249














250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265

266















267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283









284
285
286
287
288
289
290
291
292

293
294
295
296
297
298
299







+
-
-
-
-
+
+
+
+

-
+


-
+

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
-
-
-
-
-
+
+
+
+
+
+

+
-
-
+
+
+

-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+

-
-
+
+

-
+

-
-
-
+
+
+

-
-
-
+
+
+

-
-
+
+

-
-
-
+
+
+

-
-
+
+

-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+

-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+

-
-
-
-
+
+
+
+

-
-
+
+

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
-
+
+

-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+

-
-
-
-
+
+
+
+

-
-
+
+
+

+

+
-
+

-
-
-
-
-
-
-
+
+
+
+
+
+
+

-
-
+
+

-
-
+
+

-
-
+
+

-
-
-
-
-
+
+
+
+
+

-
-
-
-
-
-
-
+
+
+
+
+
+
+

-
+

-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-







+ (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;
		bool dedicated, windowed;
		int par = 0, uprate = 0, maxcl = 4;
		OFString *__autoreleasing sdesc, *__autoreleasing ip;
		OFString *__autoreleasing master, *__autoreleasing passwd;

	processInitQueue();
		processInitQueue();

#define log(s) conoutf(@"init: %@", s)
	log(@"sdl");
		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];
		}
	}
		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 = @"";
		if (sdesc == nil)
			sdesc = @"";
		if (ip == nil)
			ip = @"";
		if (passwd == nil)
			passwd = @"";

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

	[OFFileManager.defaultManager
	    createDirectoryAtIRI:[_userDataIRI
	                             IRIByAppendingPathComponent:@"demos"]
	           createParents:true];
	[OFFileManager.defaultManager
	    createDirectoryAtIRI:[_userDataIRI
	                             IRIByAppendingPathComponent:@"savegames"]
	           createParents:true];
		[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");
		if (SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO | par) < 0)
			fatal(@"Unable to initialize SDL");

	initPlayers();
		initPlayers();

	log(@"net");
	if (enet_initialize() < 0)
		fatal(@"Unable to initialise network module");
		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);
		initclient();
		// never returns if dedicated
		initserver(dedicated, uprate, sdesc, ip, master, passwd, maxcl);

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

	log(@"video: sdl");
	if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
		fatal(@"Unable to initialize SDL Video");
		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 (_width == 0 || _height == 0) {
			SDL_DisplayMode mode;

		if (SDL_GetDesktopDisplayMode(0, &mode) == 0) {
			_width = mode.w;
			_height = mode.h;
		} else {
			_width = 1920;
			_height = 1080;
		}
	}
			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: 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(@"video: misc");
		SDL_SetWindowGrab(_window, SDL_TRUE);
		SDL_SetRelativeMouseMode(SDL_TRUE);
		SDL_ShowCursor(0);

	log(@"gl");
	gl_init(_width, _height);
		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(@"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(@"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(@"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(@"localconnect");
		localconnect();
		// if this map is changed, also change depthcorrect()
		changemap(@"metl3");

	log(@"mainloop");
	int ignore = 5;
		log(@"mainloop");
	}

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

		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));
			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);
			cleardlights();
			updateworld(millis);

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

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

		computeraytable(player1.o.x, player1.o.y);
		readdepth(_width, _height);
		SDL_GL_SwapWindow(_window);
		extern void updatevol();
		updatevol();
			computeraytable(player1.o.x, player1.o.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;
		}
			// 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);
			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:
			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:
				@autoreleasepool {
					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;
					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;
			}
		}
	}

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

- (void)applicationWillTerminate:(OFNotification *)notification
{
	stop();
	disconnect(true);
	writecfg();
319
320
321
322
323
324
325
326
327

328
329
330
331



332
333
334


335
336
337
338
339
340
341
342
332
333
334
335
336
337
338


339




340
341
342



343
344

345
346
347
348
349
350
351







-
-
+
-
-
-
-
+
+
+
-
-
-
+
+
-







				memcpy(dest,
				    (char *)image->pixels +
				        3 * _width * (_height - 1 - idx),
				    3 * _width);
				endianswap(dest, 3, _width);
			}

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

		SDL_FreeSurface(image);
	}
}

Modified src/MD2.mm from [18fb7f56bd] to [4b55922953].

50
51
52
53
54
55
56
57
58
59
60
61
62
63
64








65
66
67


68
69
70


71
72

73
74
75


76
77
78
79



80
81
82
83



84
85

86
87

88
89
90
91



92
93
94
95
96




97
98
99
100
101
102





103
104

105
106
107


108
109

110
111
112
113
114
115
116
117
50
51
52
53
54
55
56








57
58
59
60
61
62
63
64
65


66
67
68


69
70


71
72


73
74
75



76
77
78
79



80
81
82
83

84


85
86



87
88
89
90




91
92
93
94
95





96
97
98
99
100
101

102
103


104
105
106

107

108
109
110
111
112
113
114







-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+

-
-
+
+

-
-
+
+
-
-
+

-
-
+
+

-
-
-
+
+
+

-
-
-
+
+
+

-
+
-
-
+

-
-
-
+
+
+

-
-
-
-
+
+
+
+

-
-
-
-
-
+
+
+
+
+

-
+

-
-
+
+

-
+
-







		delete[] _glCommands;
	if (_frames)
		delete[] _frames;
}

- (bool)loadWithIRI:(OFIRI *)IRI
{
	@autoreleasepool {
		OFSeekableStream *stream;
		@try {
			stream = (OFSeekableStream *)[[OFIRIHandler
			    handlerForIRI:IRI] openItemAtIRI:IRI mode:@"r"];
		} @catch (id e) {
			return false;
		}
	OFSeekableStream *stream;
	@try {
		stream = (OFSeekableStream *)[[OFIRIHandler handlerForIRI:IRI]
		    openItemAtIRI:IRI
		             mode:@"r"];
	} @catch (id e) {
		return false;
	}

		if (![stream isKindOfClass:OFSeekableStream.class])
			return false;
	if (![stream isKindOfClass:OFSeekableStream.class])
		return false;

		md2_header header;
		[stream readIntoBuffer:&header exactLength:sizeof(md2_header)];
	md2_header header;
	[stream readIntoBuffer:&header exactLength:sizeof(md2_header)];
		endianswap(
		    &header, sizeof(int), sizeof(md2_header) / sizeof(int));
	endianswap(&header, sizeof(int), sizeof(md2_header) / sizeof(int));

		if (header.magic != 844121161 || header.version != 8)
			return false;
	if (header.magic != 844121161 || header.version != 8)
		return false;

		_frames = new char[header.frameSize * header.numFrames];
		if (_frames == NULL)
			return false;
	_frames = new char[header.frameSize * header.numFrames];
	if (_frames == NULL)
		return false;

		[stream seekToOffset:header.offsetFrames whence:OFSeekSet];
		[stream readIntoBuffer:_frames
		           exactLength:header.frameSize * header.numFrames];
	[stream seekToOffset:header.offsetFrames whence:OFSeekSet];
	[stream readIntoBuffer:_frames
	           exactLength:header.frameSize * header.numFrames];

		for (int i = 0; i < header.numFrames; ++i)
	for (int i = 0; i < header.numFrames; ++i)
			endianswap(
			    _frames + i * header.frameSize, sizeof(float), 6);
		endianswap(_frames + i * header.frameSize, sizeof(float), 6);

		_glCommands = new int[header.numGlCommands];
		if (_glCommands == NULL)
			return false;
	_glCommands = new int[header.numGlCommands];
	if (_glCommands == NULL)
		return false;

		[stream seekToOffset:header.offsetGlCommands whence:OFSeekSet];
		[stream readIntoBuffer:_glCommands
		           exactLength:header.numGlCommands * sizeof(int)];
		endianswap(_glCommands, sizeof(int), header.numGlCommands);
	[stream seekToOffset:header.offsetGlCommands whence:OFSeekSet];
	[stream readIntoBuffer:_glCommands
	           exactLength:header.numGlCommands * sizeof(int)];
	endianswap(_glCommands, sizeof(int), header.numGlCommands);

		_numFrames = header.numFrames;
		_numGlCommands = header.numGlCommands;
		_frameSize = header.frameSize;
		_numTriangles = header.numTriangles;
		_numVerts = header.numVertices;
	_numFrames = header.numFrames;
	_numGlCommands = header.numGlCommands;
	_frameSize = header.frameSize;
	_numTriangles = header.numTriangles;
	_numVerts = header.numVertices;

		[stream close];
	[stream close];

		_mverts = new OFVector3D *[_numFrames];
		loopj(_numFrames) _mverts[j] = NULL;
	_mverts = new OFVector3D *[_numFrames];
	loopj(_numFrames) _mverts[j] = NULL;

		return true;
	return true;
	}
}

- (void)scaleWithFrame:(int)frame scale:(float)scale snap:(int)sn
{
	_mverts[frame] = new OFVector3D[_numVerts];
	md2_frame *cf = (md2_frame *)((char *)_frames + _frameSize * frame);
	float sc = 16.0f / scale;

Modified src/client.mm from [90c446262c] to [c63e414b28].

59
60
61
62
63
64
65
66
67

68
69
70


71
72

73
74
75
76
77
78
79
80
81

82
83
84


85
86

87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107

108
109
110


111
112
113
114
115
116
117
118
59
60
61
62
63
64
65


66
67


68
69
70

71

72
73
74
75
76
77


78
79


80
81
82

83

84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101


102



103
104

105
106
107
108
109
110
111







-
-
+

-
-
+
+

-
+
-






-
-
+

-
-
+
+

-
+
-


















-
-
+
-
-
-
+
+
-







	enet_peer_throttle_configure(clienthost->peers,
	    throttle_interval * 1000, throttle_accel, throttle_decel);
}

void
newname(OFString *name)
{
	@autoreleasepool {
		c2sinit = false;
	c2sinit = false;

		if (name.length > 16)
			name = [name substringToIndex:16];
	if (name.length > 16)
		name = [name substringToIndex:16];

		player1.name = name;
	player1.name = name;
	}
}
COMMANDN(name, newname, ARG_1STR)

void
newteam(OFString *name)
{
	@autoreleasepool {
		c2sinit = false;
	c2sinit = false;

		if (name.length > 5)
			name = [name substringToIndex:5];
	if (name.length > 5)
		name = [name substringToIndex:5];

		player1.team = name;
	player1.team = name;
	}
}
COMMANDN(team, newteam, ARG_1STR)

void
writeclientinfo(OFStream *stream)
{
	[stream writeFormat:@"name \"%@\"\nteam \"%@\"\n", player1.name,
	        player1.team];
}

void
connects(OFString *servername)
{
	disconnect(1); // reset state
	addserver(servername);

	conoutf(@"attempting to connect to %@", servername);
	ENetAddress address = { ENET_HOST_ANY, CUBE_SERVER_PORT };
	@autoreleasepool {
		if (enet_address_set_host(&address, servername.UTF8String) <
	if (enet_address_set_host(&address, servername.UTF8String) < 0) {
		    0) {
			conoutf(@"could not resolve server %@", servername);
			return;
		conoutf(@"could not resolve server %@", servername);
		return;
		}
	}

	clienthost = enet_host_create(NULL, 1, rate, rate);

	if (clienthost) {
		enet_host_connect(clienthost, &address, 1);
		enet_host_flush(clienthost);
283
284
285
286
287
288
289
290
291
292


293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323































324
325
326
327
328




329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356



























357
358
359


360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381





















382
383
384
385
386
387
388
389
276
277
278
279
280
281
282



283
284































285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315





316
317
318
319
320



























321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347



348
349






















350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370

371
372
373
374
375
376
377







-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-







		localclienttoserver((ENetPacket *)packet);
}

// send update to the server
void
c2sinfo(DynamicEntity *d)
{
	@autoreleasepool {
		if (clientnum < 0)
			return; // we haven't had a welcome message from the
	if (clientnum < 0)
		return; // we haven't had a welcome message from the server yet
			        // server yet
		if (lastmillis - lastupdate < 40)
			return; // don't update faster than 25fps
		ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, 0);
		uchar *start = packet->data;
		uchar *p = start + 2;
		bool serveriteminitdone = false;
		if (toservermap.length > 0) // suggest server to change map
		{ // do this exclusively as map change may invalidate rest of
		  // update
			packet->flags = ENET_PACKET_FLAG_RELIABLE;
			putint(p, SV_MAPCHANGE);
			sendstring(toservermap, p);
			toservermap = @"";
			putint(p, nextmode);
		} else {
			putint(p, SV_POS);
			putint(p, clientnum);
			// quantize coordinates to 1/16th of a cube, between 1
			// and 3 bytes
			putint(p, (int)(d.o.x * DMF));
			putint(p, (int)(d.o.y * DMF));
			putint(p, (int)(d.o.z * DMF));
			putint(p, (int)(d.yaw * DAF));
			putint(p, (int)(d.pitch * DAF));
			putint(p, (int)(d.roll * DAF));
			// quantize to 1/100, almost always 1 byte
			putint(p, (int)(d.vel.x * DVF));
			putint(p, (int)(d.vel.y * DVF));
			putint(p, (int)(d.vel.z * DVF));
			// pack rest in 1 byte: strafe:2, move:2, onfloor:1,
	if (lastmillis - lastupdate < 40)
		return; // don't update faster than 25fps
	ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, 0);
	uchar *start = packet->data;
	uchar *p = start + 2;
	bool serveriteminitdone = false;
	// suggest server to change map
	if (toservermap.length > 0) {
		// do this exclusively as map change may invalidate rest of
		// update
		packet->flags = ENET_PACKET_FLAG_RELIABLE;
		putint(p, SV_MAPCHANGE);
		sendstring(toservermap, p);
		toservermap = @"";
		putint(p, nextmode);
	} else {
		putint(p, SV_POS);
		putint(p, clientnum);
		// quantize coordinates to 1/16th of a cube, between 1 and 3
		// bytes
		putint(p, (int)(d.o.x * DMF));
		putint(p, (int)(d.o.y * DMF));
		putint(p, (int)(d.o.z * DMF));
		putint(p, (int)(d.yaw * DAF));
		putint(p, (int)(d.pitch * DAF));
		putint(p, (int)(d.roll * DAF));
		// quantize to 1/100, almost always 1 byte
		putint(p, (int)(d.vel.x * DVF));
		putint(p, (int)(d.vel.y * DVF));
		putint(p, (int)(d.vel.z * DVF));
		// pack rest in 1 byte: strafe:2, move:2, onfloor:1, state:3
			// state:3
			putint(p,
			    (d.strafe & 3) | ((d.move & 3) << 2) |
			        (((int)d.onfloor) << 4) |
			        ((editmode ? CS_EDITING : d.state) << 5));
		putint(p,
		    (d.strafe & 3) | ((d.move & 3) << 2) |
		        (((int)d.onfloor) << 4) |
		        ((editmode ? CS_EDITING : d.state) << 5));

			if (senditemstoserver) {
				packet->flags = ENET_PACKET_FLAG_RELIABLE;
				putint(p, SV_ITEMLIST);
				if (!m_noitems)
					putitems(p);
				putint(p, -1);
				senditemstoserver = false;
				serveriteminitdone = true;
			}
			// player chat, not flood protected for now
			if (ctext.length > 0) {
				packet->flags = ENET_PACKET_FLAG_RELIABLE;
				putint(p, SV_TEXT);
				sendstring(ctext, p);
				ctext = @"";
			}
			// tell other clients who I am
			if (!c2sinit) {
				packet->flags = ENET_PACKET_FLAG_RELIABLE;
				c2sinit = true;
				putint(p, SV_INITC2S);
				sendstring(player1.name, p);
				sendstring(player1.team, p);
				putint(p, player1.lifesequence);
			}
			for (OFData *msg in messages) {
				// send messages collected during the previous
		if (senditemstoserver) {
			packet->flags = ENET_PACKET_FLAG_RELIABLE;
			putint(p, SV_ITEMLIST);
			if (!m_noitems)
				putitems(p);
			putint(p, -1);
			senditemstoserver = false;
			serveriteminitdone = true;
		}
		// player chat, not flood protected for now
		if (ctext.length > 0) {
			packet->flags = ENET_PACKET_FLAG_RELIABLE;
			putint(p, SV_TEXT);
			sendstring(ctext, p);
			ctext = @"";
		}
		// tell other clients who I am
		if (!c2sinit) {
			packet->flags = ENET_PACKET_FLAG_RELIABLE;
			c2sinit = true;
			putint(p, SV_INITC2S);
			sendstring(player1.name, p);
			sendstring(player1.team, p);
			putint(p, player1.lifesequence);
		}
		for (OFData *msg in messages) {
			// send messages collected during the previous frames
				// frames
				if (*(int *)[msg itemAtIndex:1])
					packet->flags =
			if (*(int *)[msg itemAtIndex:1])
				packet->flags = ENET_PACKET_FLAG_RELIABLE;
					    ENET_PACKET_FLAG_RELIABLE;
				loopi(*(int *)[msg itemAtIndex:0])
				    putint(p, *(int *)[msg itemAtIndex:i + 2]);
			}
			[messages removeAllObjects];
			if (lastmillis - lastping > 250) {
				putint(p, SV_PING);
				putint(p, lastmillis);
				lastping = lastmillis;
			}
		}
		*(ushort *)start = ENET_HOST_TO_NET_16(p - start);
		enet_packet_resize(packet, p - start);
		incomingdemodata(start, p - start, true);
		if (clienthost) {
			enet_host_broadcast(clienthost, 0, packet);
			enet_host_flush(clienthost);
		} else
			localclienttoserver(packet);
		lastupdate = lastmillis;
		if (serveriteminitdone)
			loadgamerest(); // hack
			loopi(*(int *)[msg itemAtIndex:0])
			    putint(p, *(int *)[msg itemAtIndex:i + 2]);
		}
		[messages removeAllObjects];
		if (lastmillis - lastping > 250) {
			putint(p, SV_PING);
			putint(p, lastmillis);
			lastping = lastmillis;
		}
	}
	*(ushort *)start = ENET_HOST_TO_NET_16(p - start);
	enet_packet_resize(packet, p - start);
	incomingdemodata(start, p - start, true);
	if (clienthost) {
		enet_host_broadcast(clienthost, 0, packet);
		enet_host_flush(clienthost);
	} else
		localclienttoserver(packet);
	lastupdate = lastmillis;
	if (serveriteminitdone)
		loadgamerest(); // hack
	}
}

void
gets2c() // get updates from the server
{
	ENetEvent event;
	if (!clienthost)

Modified src/clientextras.mm from [5e3b6ff5ad] to [364ff9cb88].

103
104
105
106
107
108
109
110
111
112
113
114
115
116






117
118
119


120
121

122
123

124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141






142
143
144


145
146
147


148
149
150
151
152
153
154
155
103
104
105
106
107
108
109







110
111
112
113
114
115
116


117
118
119

120
121

122

123
124
125
126
127
128
129
130
131
132







133
134
135
136
137
138
139


140
141
142


143
144

145
146
147
148
149
150
151







-
-
-
-
-
-
-
+
+
+
+
+
+

-
-
+
+

-
+

-
+
-










-
-
-
-
-
-
-
+
+
+
+
+
+

-
-
+
+

-
-
+
+
-







}

static OFMutableArray<OFString *> *scoreLines;

void
renderscore(DynamicEntity *d)
{
	@autoreleasepool {
		OFString *lag = [OFString stringWithFormat:@"%d", d.plag];
		OFString *name = [OFString stringWithFormat:@"(%@)", d.name];
		OFString *line =
		    [OFString stringWithFormat:@"%d\t%@\t%d\t%@\t%@", d.frags,
		              (d.state == CS_LAGGED ? @"LAG" : lag), d.ping,
		              d.team, (d.state == CS_DEAD ? name : d.name)];
	OFString *lag = [OFString stringWithFormat:@"%d", d.plag];
	OFString *name = [OFString stringWithFormat:@"(%@)", d.name];
	OFString *line =
	    [OFString stringWithFormat:@"%d\t%@\t%d\t%@\t%@", d.frags,
	              (d.state == CS_LAGGED ? @"LAG" : lag), d.ping, d.team,
	              (d.state == CS_DEAD ? name : d.name)];

		if (scoreLines == nil)
			scoreLines = [[OFMutableArray alloc] init];
	if (scoreLines == nil)
		scoreLines = [[OFMutableArray alloc] init];

		[scoreLines addObject:line];
	[scoreLines addObject:line];

		menumanual(0, scoreLines.count - 1, line);
	menumanual(0, scoreLines.count - 1, line);
	}
}

static const int maxTeams = 4;
static OFString *teamName[maxTeams];
static int teamScore[maxTeams];
static size_t teamsUsed;

void
addteamscore(DynamicEntity *d)
{
	@autoreleasepool {
		for (size_t i = 0; i < teamsUsed; i++) {
			if ([teamName[i] isEqual:d.team]) {
				teamScore[i] += d.frags;
				return;
			}
		}
	for (size_t i = 0; i < teamsUsed; i++) {
		if ([teamName[i] isEqual:d.team]) {
			teamScore[i] += d.frags;
			return;
		}
	}

		if (teamsUsed == maxTeams)
			return;
	if (teamsUsed == maxTeams)
		return;

		teamName[teamsUsed] = d.team;
		teamScore[teamsUsed++] = d.frags;
	teamName[teamsUsed] = d.team;
	teamScore[teamsUsed++] = d.frags;
	}
}

void
renderscores()
{
	if (!scoreson)
		return;
168
169
170
171
172
173
174
175
176

177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216






























217
218
219
220
221
222
223
224
164
165
166
167
168
169
170


171

172
173
174
175
176
177
178
179































180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209

210
211
212
213
214
215
216







-
-
+
-








-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-







		if (!demoplayback)
			addteamscore(player1);
		OFMutableString *teamScores = [[OFMutableString alloc] init];
		for (size_t j = 0; j < teamsUsed; j++)
			[teamScores appendFormat:@"[ %@: %d ]", teamName[j],
			            teamScore[j]];
		menumanual(0, scoreLines.count, @"");
		@autoreleasepool {
			menumanual(0, scoreLines.count + 1, teamScores);
		menumanual(0, scoreLines.count + 1, teamScores);
		}
	}
}

// sendmap/getmap commands, should be replaced by more intuitive map downloading

void
sendmap(OFString *mapname)
{
	@autoreleasepool {
		if (mapname.length > 0)
			save_world(mapname);
		changemap(mapname);
		mapname = getclientmap();
		OFData *mapdata = readmap(mapname);
		if (mapdata == nil)
			return;
		ENetPacket *packet = enet_packet_create(
		    NULL, MAXTRANS + mapdata.count, ENET_PACKET_FLAG_RELIABLE);
		uchar *start = packet->data;
		uchar *p = start + 2;
		putint(p, SV_SENDMAP);
		sendstring(mapname, p);
		putint(p, mapdata.count);
		if (65535 - (p - start) < mapdata.count) {
			conoutf(@"map %@ is too large to send", mapname);
			enet_packet_destroy(packet);
			return;
		}
		memcpy(p, mapdata.items, mapdata.count);
		p += mapdata.count;
		*(ushort *)start = ENET_HOST_TO_NET_16(p - start);
		enet_packet_resize(packet, p - start);
		sendpackettoserv(packet);
		conoutf(@"sending map %@ to server...", mapname);
		OFString *msg =
		    [OFString stringWithFormat:@"[map %@ uploaded to server, "
		                               @"\"getmap\" to receive it]",
		              mapname];
		toserver(msg);
	if (mapname.length > 0)
		save_world(mapname);
	changemap(mapname);
	mapname = getclientmap();
	OFData *mapdata = readmap(mapname);
	if (mapdata == nil)
		return;
	ENetPacket *packet = enet_packet_create(
	    NULL, MAXTRANS + mapdata.count, ENET_PACKET_FLAG_RELIABLE);
	uchar *start = packet->data;
	uchar *p = start + 2;
	putint(p, SV_SENDMAP);
	sendstring(mapname, p);
	putint(p, mapdata.count);
	if (65535 - (p - start) < mapdata.count) {
		conoutf(@"map %@ is too large to send", mapname);
		enet_packet_destroy(packet);
		return;
	}
	memcpy(p, mapdata.items, mapdata.count);
	p += mapdata.count;
	*(ushort *)start = ENET_HOST_TO_NET_16(p - start);
	enet_packet_resize(packet, p - start);
	sendpackettoserv(packet);
	conoutf(@"sending map %@ to server...", mapname);
	OFString *msg =
	    [OFString stringWithFormat:@"[map %@ uploaded to server, "
	                               @"\"getmap\" to receive it]",
	              mapname];
	toserver(msg);
	}
}

void
getmap()
{
	ENetPacket *packet =
	    enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE);

Modified src/clients2c.mm from [ef18048f8b] to [1561f54e4a].

91
92
93
94
95
96
97
98
99
100
101



102
103
104
105



106
107
108
109
110
111
112
113
91
92
93
94
95
96
97




98
99
100




101
102
103

104
105
106
107
108
109
110







-
-
-
-
+
+
+
-
-
-
-
+
+
+
-







			toservermap = @"";
			clientnum = cn; // we are now fully connected
			if (!getint(p))
				// we are the first client on this server, set
				// map
				toservermap = getclientmap();
			sgetstr();
			@autoreleasepool {
				if (text[0] &&
				    strcmp(text, clientpassword.UTF8String)) {
					conoutf(@"you need to set the correct "
			if (text[0] &&
			    strcmp(text, clientpassword.UTF8String)) {
				conoutf(@"you need to set the correct password "
					        @"password "
					        @"to join this server!");
					disconnect();
					return;
				        @"to join this server!");
				disconnect();
				return;
				}
			}
			if (getint(p) == 1)
				conoutf(@"server is FULL, disconnecting..");
			break;
		}

		case SV_POS: {
146
147
148
149
150
151
152
153
154

155
156
157
158
159
160
161
162
143
144
145
146
147
148
149


150

151
152
153
154
155
156
157







-
-
+
-







		case SV_TEXT:
			sgetstr();
			conoutf(@"%@:\f %s", d.name, text);
			break;

		case SV_MAPCHANGE:
			sgetstr();
			@autoreleasepool {
				changemapserv(@(text), getint(p));
			changemapserv(@(text), getint(p));
			}
			mapchanged = true;
			break;

		case SV_ITEMLIST: {
			int n;
			if (mapchanged) {
				senditemstoserver = false;
374
375
376
377
378
379
380
381
382
383
384
385




386
387
388
389
390
391
392
393
369
370
371
372
373
374
375





376
377
378
379

380
381
382
383
384
385
386







-
-
-
-
-
+
+
+
+
-







			break;

		case SV_RECVMAP: {
			sgetstr();
			conoutf(@"received map \"%s\" from server, reloading..",
			    text);
			int mapsize = getint(p);
			@autoreleasepool {
				OFString *string = @(text);
				writemap(string, mapsize, p);
				p += mapsize;
				changemapserv(string, gamemode);
			OFString *string = @(text);
			writemap(string, mapsize, p);
			p += mapsize;
			changemapserv(string, gamemode);
			}
			break;
		}

		case SV_SERVMSG:
			sgetstr();
			conoutf(@"%s", text);
			break;

Modified src/commands.mm from [d5247fc383] to [8783bcd63b].

119
120
121
122
123
124
125
126
127
128


129
130
131
132
133
134





135
136
137
138
139
140
141
142
119
120
121
122
123
124
125



126
127






128
129
130
131
132

133
134
135
136
137
138
139







-
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
-







			p--;
			conoutf(@"missing \"%c\"", right);
			return NULL;
		}
	}
	char *s = strndup(word, p - word - 1);
	if (left == '(') {
		@autoreleasepool {
			OFString *t;
			@try {
		OFString *t;
		@try {
				t = [OFString
				    stringWithFormat:@"%d", execute(@(s))];
			} @finally {
				free(s);
			}
			s = strdup(t.UTF8String);
			t = [OFString stringWithFormat:@"%d", execute(@(s))];
		} @finally {
			free(s);
		}
		s = strdup(t.UTF8String);
		}
	}
	return s;
}

// parse single argument, including expressions
char *
parseword(char *&p)
160
161
162
163
164
165
166

167
168

169
170
171

172
173
174

175
176
177
178



179
180
181
182
183
184
185
186
157
158
159
160
161
162
163
164
165

166
167


168

169

170




171
172
173

174
175
176
177
178
179
180







+

-
+

-
-
+
-

-
+
-
-
-
-
+
+
+
-







	char *word = p;
	p += strcspn(p, "; \t\r\n\0");
	if (p - word == 0)
		return NULL;
	return strndup(word, p - word);
}

// find value of ident referenced with $ in exp
OFString *
lookup(OFString *n) // find value of ident referenced with $ in exp
lookup(OFString *n)
{
	@autoreleasepool {
		__kindof Identifier *identifier =
	__kindof Identifier *identifier = identifiers[[n substringFromIndex:1]];
		    identifiers[[n substringFromIndex:1]];

		if ([identifier isKindOfClass:Variable.class]) {
	if ([identifier isKindOfClass:Variable.class]) {
			return [OFString
			    stringWithFormat:@"%d", *[identifier storage]];
		} else if ([identifier isKindOfClass:Alias.class])
			return [identifier action];
		return [OFString stringWithFormat:@"%d", *[identifier storage]];
	} else if ([identifier isKindOfClass:Alias.class])
		return [identifier action];
	}

	conoutf(@"unknown alias lookup: %@", [n substringFromIndex:1]);
	return n;
}

int
executeIdentifier(__kindof Identifier *identifier,
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267






























268
269
270
271
272
273
274
275
276
277
278
279
280












281
282
283
284



285
286

287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305


306
307
308


309
310
311
312
313




314
315
316
317
318
319
320
321







322
323
324


325
326

327
328
329


330
331
332
333
334
335
336
337
338
339
340
341
342
343
344








345
346
347


348
349
350
351
352
353
354
355
356
357
358
359





360
361
362
363
364
365
366
367
224
225
226
227
228
229
230































231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261












262
263
264
265
266
267
268
269
270
271
272
273
274



275
276
277
278

279

280
281
282
283
284
285
286
287
288
289
290
291
292
293
294



295
296
297


298
299
300




301
302
303
304
305







306
307
308
309
310
311
312



313
314
315

316
317


318
319

320
321
322
323
324









325
326
327
328
329
330
331
332
333


334
335

336
337
338
339
340






341
342
343
344
345

346
347
348
349
350
351
352







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+

-
-
-
+
+
+

-
+
-















-
-
-
+
+

-
-
+
+

-
-
-
-
+
+
+
+

-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
+
+

-
+

-
-
+
+
-





-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+

-
-
+
+
-





-
-
-
-
-
-
+
+
+
+
+
-







	return 0;
}

// all evaluation happens here, recursively
int
execute(OFString *string, bool isDown)
{
	@autoreleasepool {
		std::unique_ptr<char> copy(strdup(string.UTF8String));
		char *p = copy.get();
		const int MAXWORDS = 25; // limit, remove
		OFString *w[MAXWORDS];
		int val = 0;
		for (bool cont = true; cont;) {
			// for each ; seperated statement
			int numargs = MAXWORDS;
			loopi(MAXWORDS)
			{
				// collect all argument values
				w[i] = @"";
				if (i > numargs)
					continue;
				// parse and evaluate exps
				char *s = parseword(p);
				if (!s) {
					numargs = i;
					s = strdup("");
				}
				@try {
					if (*s == '$')
						// substitute variables
						w[i] = lookup(@(s));
					else
						w[i] = @(s);
				} @finally {
					free(s);
				}
			}
	std::unique_ptr<char> copy(strdup(string.UTF8String));
	char *p = copy.get();
	const int MAXWORDS = 25; // limit, remove
	OFString *w[MAXWORDS];
	int val = 0;
	for (bool cont = true; cont;) {
		// for each ; seperated statement
		int numargs = MAXWORDS;
		loopi(MAXWORDS)
		{
			// collect all argument values
			w[i] = @"";
			if (i > numargs)
				continue;
			// parse and evaluate exps
			char *s = parseword(p);
			if (!s) {
				numargs = i;
				s = strdup("");
			}
			@try {
				if (*s == '$')
					// substitute variables
					w[i] = lookup(@(s));
				else
					w[i] = @(s);
			} @finally {
				free(s);
			}
		}

			p += strcspn(p, ";\n\0");
			// more statements if this isn't the end of the string
			cont = *p++ != 0;
			OFString *c = w[0];
			// strip irc-style command prefix
			if ([c hasPrefix:@"/"]) {
				c = [c substringFromIndex:1];
				w[0] = c;
			}
			// empty statement
			if (c.length == 0)
				continue;
		p += strcspn(p, ";\n\0");
		// more statements if this isn't the end of the string
		cont = *p++ != 0;
		OFString *c = w[0];
		// strip irc-style command prefix
		if ([c hasPrefix:@"/"]) {
			c = [c substringFromIndex:1];
			w[0] = c;
		}
		// empty statement
		if (c.length == 0)
			continue;

			val = executeIdentifier(identifiers[c],
			    [OFArray arrayWithObjects:w count:numargs], isDown);
		}
		val = executeIdentifier(identifiers[c],
		    [OFArray arrayWithObjects:w count:numargs], isDown);
	}

		return val;
	return val;
	}
}

// tab-completion of all identifiers

int completesize = 0, completeidx = 0;

void
resetcomplete()
{
	completesize = 0;
}

void
complete(OFMutableString *s)
{
	@autoreleasepool {
		if (![s hasPrefix:@"/"])
			[s insertString:@"/" atIndex:0];
	if (![s hasPrefix:@"/"])
		[s insertString:@"/" atIndex:0];

		if (s.length == 1)
			return;
	if (s.length == 1)
		return;

		if (!completesize) {
			completesize = s.length - 1;
			completeidx = 0;
		}
	if (!completesize) {
		completesize = s.length - 1;
		completeidx = 0;
	}

		__block int idx = 0;
		[identifiers enumerateKeysAndObjectsUsingBlock:^(
		    OFString *name, Identifier *identifier, bool *stop) {
			if (strncmp(identifier.name.UTF8String,
			        s.UTF8String + 1, completesize) == 0 &&
			    idx++ == completeidx)
				[s replaceCharactersInRange:OFMakeRange(
	__block int idx = 0;
	[identifiers enumerateKeysAndObjectsUsingBlock:^(
	    OFString *name, Identifier *identifier, bool *stop) {
		if (strncmp(identifier.name.UTF8String, s.UTF8String + 1,
		        completesize) == 0 &&
		    idx++ == completeidx)
			[s replaceCharactersInRange:OFMakeRange(1, s.length - 1)
				                                1, s.length - 1)
				                 withString:identifier.name];
		}];
			                 withString:identifier.name];
	}];

		completeidx++;
	completeidx++;

		if (completeidx >= idx)
			completeidx = 0;
	if (completeidx >= idx)
		completeidx = 0;
	}
}

bool
execfile(OFIRI *cfgfile)
{
	@autoreleasepool {
		OFString *command;
		@try {
			command = [OFString stringWithContentsOfIRI:cfgfile];
		} @catch (OFOpenItemFailedException *e) {
			return false;
		} @catch (OFReadFailedException *e) {
			return false;
		}
	OFString *command;
	@try {
		command = [OFString stringWithContentsOfIRI:cfgfile];
	} @catch (OFOpenItemFailedException *e) {
		return false;
	} @catch (OFReadFailedException *e) {
		return false;
	}

		execute(command);
		return true;
	execute(command);
	return true;
	}
}

void
exec(OFString *cfgfile)
{
	@autoreleasepool {
		if (!execfile([Cube.sharedInstance.userDataIRI
		        IRIByAppendingPathComponent:cfgfile]) &&
		    !execfile([Cube.sharedInstance.gameDataIRI
		        IRIByAppendingPathComponent:cfgfile]))
			conoutf(@"could not read \"%@\"", cfgfile);
	if (!execfile([Cube.sharedInstance.userDataIRI
	        IRIByAppendingPathComponent:cfgfile]) &&
	    !execfile([Cube.sharedInstance.gameDataIRI
	        IRIByAppendingPathComponent:cfgfile]))
		conoutf(@"could not read \"%@\"", cfgfile);
	}
}

void
writecfg()
{
	OFStream *stream;
	@try {
414
415
416
417
418
419
420
421
422

423
424
425
426
427
428
429
430
431
432
433
434
435
436

437
438
439
440
441




442
443
444
445
446
447
448
449
399
400
401
402
403
404
405


406

407
408
409
410
411
412
413
414
415
416
417


418
419




420
421
422
423

424
425
426
427
428
429
430







-
-
+
-











-
-
+

-
-
-
-
+
+
+
+
-







// below the commands that implement a small imperative language. thanks to the
// semantics of () and [] expressions, any control construct can be defined
// trivially.

void
intset(OFString *name, int v)
{
	@autoreleasepool {
		alias(name, [OFString stringWithFormat:@"%d", v]);
	alias(name, [OFString stringWithFormat:@"%d", v]);
	}
}

void
ifthen(OFString *cond, OFString *thenp, OFString *elsep)
{
	execute((![cond hasPrefix:@"0"] ? thenp : elsep));
}

void
loopa(OFString *times, OFString *body)
{
	@autoreleasepool {
		int t = times.cube_intValue;
	int t = times.cube_intValue;

		loopi(t)
		{
			intset(@"i", i);
			execute(body);
	loopi(t)
	{
		intset(@"i", i);
		execute(body);
		}
	}
}

void
whilea(OFString *cond, OFString *body)
{
	while (execute(cond))
468
469
470
471
472
473
474
475
476

477
478
479


480
481
482
483
484




485
486

487
488
489
490
491
492
493
494
495
496
497
498
499






500
501
502
503
504
505
506
507
449
450
451
452
453
454
455


456
457


458
459
460




461
462
463
464
465

466

467
468
469
470
471







472
473
474
475
476
477

478
479
480
481
482
483
484







-
-
+

-
-
+
+

-
-
-
-
+
+
+
+

-
+
-





-
-
-
-
-
-
-
+
+
+
+
+
+
-







{
	concat([s stringByReplacingOccurrencesOfString:@" " withString:@""]);
}

int
listlen(OFString *a_)
{
	@autoreleasepool {
		const char *a = a_.UTF8String;
	const char *a = a_.UTF8String;

		if (!*a)
			return 0;
	if (!*a)
		return 0;

		int n = 0;
		while (*a)
			if (*a++ == ' ')
				n++;
	int n = 0;
	while (*a)
		if (*a++ == ' ')
			n++;

		return n + 1;
	return n + 1;
	}
}

void
at(OFString *s_, OFString *pos)
{
	@autoreleasepool {
		int n = pos.cube_intValue;
		std::unique_ptr<char> copy(strdup(s_.UTF8String));
		char *s = copy.get();
		loopi(n) s += strspn(s += strcspn(s, " \0"), " ");
		s[strcspn(s, " \0")] = 0;
		concat(@(s));
	int n = pos.cube_intValue;
	std::unique_ptr<char> copy(strdup(s_.UTF8String));
	char *s = copy.get();
	loopi(n) s += strspn(s += strcspn(s, " \0"), " ");
	s[strcspn(s, " \0")] = 0;
	concat(@(s));
	}
}

COMMANDN(loop, loopa, ARG_2STR)
COMMANDN(while, whilea, ARG_2STR)
COMMANDN(if, ifthen, ARG_3STR)
COMMAND(onrelease, ARG_DWN1)
COMMAND(exec, ARG_1STR)

Modified src/console.mm from [6b830d51dc] to [c4ec2131b8].

79
80
81
82
83
84
85
86
87
88


89
90
91


92
93

94
95
96
97
98
99
100






101
102
103
104
105
106
107
108
79
80
81
82
83
84
85



86
87
88


89
90
91

92
93






94
95
96
97
98
99

100
101
102
103
104
105
106







-
-
-
+
+

-
-
+
+

-
+

-
-
-
-
-
-
+
+
+
+
+
+
-







	fflush(stdout);
#endif
}

void
conoutf(OFConstantString *format, ...)
{
	@autoreleasepool {
		va_list arguments;
		va_start(arguments, format);
	va_list arguments;
	va_start(arguments, format);

		OFString *string = [[OFString alloc] initWithFormat:format
		                                          arguments:arguments];
	OFString *string = [[OFString alloc] initWithFormat:format
	                                          arguments:arguments];

		va_end(arguments);
	va_end(arguments);

		int n = 0;
		while (string.length > WORDWRAP) {
			conline([string substringToIndex:WORDWRAP], n++ != 0);
			string = [string substringFromIndex:WORDWRAP];
		}
		conline(string, n != 0);
	int n = 0;
	while (string.length > WORDWRAP) {
		conline([string substringToIndex:WORDWRAP], n++ != 0);
		string = [string substringFromIndex:WORDWRAP];
	}
	conline(string, n != 0);
	}
}

// render buffer taking into account time & scrolling
void
renderconsole()
{
	int nd = 0;
177
178
179
180
181
182
183
184
185
186


187
188
189
190
191
192
193
194
195

196
197
198
199
200
201
202
203
175
176
177
178
179
180
181



182
183

184
185
186
187
188
189


190

191
192
193
194
195
196
197







-
-
-
+
+
-






-
-
+
-







	commandbuf = [init mutableCopy];
}
COMMAND(saycommand, ARG_VARI)

void
mapmsg(OFString *s)
{
	@autoreleasepool {
		memset(hdr.maptitle, '\0', sizeof(hdr.maptitle));
		strncpy(hdr.maptitle, s.UTF8String, 127);
	memset(hdr.maptitle, '\0', sizeof(hdr.maptitle));
	strncpy(hdr.maptitle, s.UTF8String, 127);
	}
}
COMMAND(mapmsg, ARG_1STR)

void
pasteconsole()
{
	@autoreleasepool {
		[commandbuf appendString:@(SDL_GetClipboardText())];
	[commandbuf appendString:@(SDL_GetClipboardText())];
	}
}

static OFMutableArray<OFString *> *vhistory;
static int histpos = 0;

void
history(int n)
258
259
260
261
262
263
264
265
266
267
268
269




270
271
272
273
274
275





276
277
278

279
280
281
282
283
284
285
286
252
253
254
255
256
257
258





259
260
261
262
263





264
265
266
267
268



269

270
271
272
273
274
275
276







-
-
-
-
-
+
+
+
+

-
-
-
-
-
+
+
+
+
+
-
-
-
+
-








			default:
				resetcomplete();
			}
		} else {
			if (code == SDLK_RETURN) {
				if (commandbuf.length > 0) {
					@autoreleasepool {
						if (vhistory == nil)
							vhistory =
							    [[OFMutableArray
							        alloc] init];
					if (vhistory == nil)
						vhistory =
						    [[OFMutableArray alloc]
						        init];

						if (vhistory.count == 0 ||
						    ![vhistory.lastObject
						        isEqual:commandbuf]) {
							// cap this?
							[vhistory
					if (vhistory.count == 0 ||
					    ![vhistory.lastObject
					        isEqual:commandbuf]) {
						// cap this?
						[vhistory addObject:[commandbuf
							    addObject:
							        [commandbuf
							            copy]];
						                        copy]];
						}
					}
					histpos = vhistory.count;
					if ([commandbuf hasPrefix:@"/"])
						execute(commandbuf, true);
					else
						toserver(commandbuf);
				}

Modified src/editing.mm from [76cc8ec718] to [2700645c79].

600
601
602
603
604
605
606
607
608
609
610



611
612
613
614
615
616
617
618
600
601
602
603
604
605
606




607
608
609

610
611
612
613
614
615
616







-
-
-
-
+
+
+
-







	loopselxy(s->tag = tag);
}

void
newent(OFString *what, OFString *a1, OFString *a2, OFString *a3, OFString *a4)
{
	EDITSEL;
	@autoreleasepool {
		newentity(sel.x, sel.y, (int)player1.o.z, what,
		    [a1 cube_intValueWithBase:0], [a2 cube_intValueWithBase:0],
		    [a3 cube_intValueWithBase:0], [a4 cube_intValueWithBase:0]);
	newentity(sel.x, sel.y, (int)player1.o.z, what,
	    [a1 cube_intValueWithBase:0], [a2 cube_intValueWithBase:0],
	    [a3 cube_intValueWithBase:0], [a4 cube_intValueWithBase:0]);
	}
}

COMMANDN(select, selectpos, ARG_4INT)
COMMAND(edittag, ARG_1INT)
COMMAND(replace, ARG_NONE)
COMMAND(archvertex, ARG_3INT)
COMMAND(arch, ARG_2INT)

Modified src/entities.mm from [0816ef168b] to [58180dec4d].

38
39
40
41
42
43
44
45
46

47
48

49
50
51
52


53
54
55
56
57
58
59
60
38
39
40
41
42
43
44


45


46




47
48

49
50
51
52
53
54
55







-
-
+
-
-
+
-
-
-
-
+
+
-







	loopv(ents)
	{
		entity &e = ents[i];
		if (e.type == MAPMODEL) {
			MapModelInfo *mmi = getmminfo(e.attr2);
			if (mmi == nil)
				continue;
			@autoreleasepool {
				rendermodel(mmi.name, 0, 1, e.attr4,
			rendermodel(mmi.name, 0, 1, e.attr4, (float)mmi.rad,
				    (float)mmi.rad, e.x,
				    (float)S(e.x, e.y)->floor + mmi.zoff +
			    e.x, (float)S(e.x, e.y)->floor + mmi.zoff + e.attr3,
				        e.attr3,
				    e.y,
				    (float)((e.attr1 + 7) - (e.attr1 + 7) % 15),
				    0, false, 1.0f, 10.0f, mmi.snap);
			    e.y, (float)((e.attr1 + 7) - (e.attr1 + 7) % 15), 0,
			    false, 1.0f, 10.0f, mmi.snap);
			}
		} else {
			if (OUTBORD(e.x, e.y))
				continue;
			if (e.type != CARROT) {
				if (!e.spawned && e.type != TELEPORT)
					continue;
				if (e.type < I_SHELLS || e.type > TELEPORT)

Modified src/menus.mm from [8be4b9144c] to [e754271834].

39
40
41
42
43
44
45
46
47
48
49
50




51
52
53


54
55
56
57



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91

































92
93
94
95
96
97
98
99
39
40
41
42
43
44
45





46
47
48
49
50


51
52
53



54
55
56


































57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89

90
91
92
93
94
95
96







-
-
-
-
-
+
+
+
+

-
-
+
+

-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-







}

void refreshservers();

bool
rendermenu()
{
	@autoreleasepool {
		if (vmenu < 0) {
			[menuStack removeAllObjects];
			return false;
		}
	if (vmenu < 0) {
		[menuStack removeAllObjects];
		return false;
	}

		if (vmenu == 1)
			refreshservers();
	if (vmenu == 1)
		refreshservers();

		Menu *m = menus[vmenu];
		OFString *title;
		if (vmenu > 1)
	Menu *m = menus[vmenu];
	OFString *title;
	if (vmenu > 1)
			title =
			    [OFString stringWithFormat:@"[ %@ menu ]", m.name];
		else
			title = m.name;
		int mdisp = m.items.count;
		int w = 0;
		loopi(mdisp)
		{
			int x = text_width(m.items[i].text);
			if (x > w)
				w = x;
		}
		int tw = text_width(title);
		if (tw > w)
			w = tw;
		int step = FONTH / 4 * 5;
		int h = (mdisp + 2) * step;
		int y = (VIRTH - h) / 2;
		int x = (VIRTW - w) / 2;
		blendbox(x - FONTH / 2 * 3, y - FONTH, x + w + FONTH / 2 * 3,
		    y + h + FONTH, true);
		draw_text(title, x, y, 2);
		y += FONTH * 2;
		if (vmenu) {
			int bh = y + m.menusel * step;
			blendbox(x - FONTH, bh - 10, x + w + FONTH,
			    bh + FONTH + 10, false);
		}
		loopj(mdisp)
		{
			draw_text(m.items[j].text, x, y, 2);
			y += step;
		}
		return true;
		title = [OFString stringWithFormat:@"[ %@ menu ]", m.name];
	else
		title = m.name;
	int mdisp = m.items.count;
	int w = 0;
	loopi(mdisp)
	{
		int x = text_width(m.items[i].text);
		if (x > w)
			w = x;
	}
	int tw = text_width(title);
	if (tw > w)
		w = tw;
	int step = FONTH / 4 * 5;
	int h = (mdisp + 2) * step;
	int y = (VIRTH - h) / 2;
	int x = (VIRTW - w) / 2;
	blendbox(x - FONTH / 2 * 3, y - FONTH, x + w + FONTH / 2 * 3,
	    y + h + FONTH, true);
	draw_text(title, x, y, 2);
	y += FONTH * 2;
	if (vmenu) {
		int bh = y + m.menusel * step;
		blendbox(
		    x - FONTH, bh - 10, x + w + FONTH, bh + FONTH + 10, false);
	}
	loopj(mdisp)
	{
		draw_text(m.items[j].text, x, y, 2);
		y += step;
	}
	return true;
	}
}

void
newmenu(OFString *name)
{
	if (menus == nil)
		menus = [[OFMutableArray alloc] init];
150
151
152
153
154
155
156
157

158
159

160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
147
148
149
150
151
152
153

154


155


156
157
158
159
160
161
162
163
164
165
166
167
168







-
+
-
-
+
-
-













			menusel = n - 1;
		else if (menusel >= n)
			menusel = 0;
		menus[vmenu].menusel = menusel;
	} else {
		if (code == SDLK_RETURN || code == -2) {
			OFString *action = menus[vmenu].items[menusel].action;
			if (vmenu == 1) {
			if (vmenu == 1)
				@autoreleasepool {
					connects(getservername(menusel));
				connects(getservername(menusel));
				}
			}

			if (menuStack == nil)
				menuStack = [[OFMutableArray alloc] init];

			[menuStack addObject:@(vmenu)];
			menuset(-1);

			execute(action, true);
		}
	}

	return true;
}

Modified src/renderextras.mm from [5d964620ef] to [87f5ac882b].

199
200
201
202
203
204
205
206
207

208
209
210


211
212
213


214
215
216
217
218
219
220
221







222
223
224
225
226
227
228
229
230







231
232

233
234
235
236
237
238
239
240
199
200
201
202
203
204
205


206
207


208
209
210


211
212
213







214
215
216
217
218
219
220

221







222
223
224
225
226
227
228
229

230

231
232
233
234
235
236
237







-
-
+

-
-
+
+

-
-
+
+

-
-
-
-
-
-
-
+
+
+
+
+
+
+
-

-
-
-
-
-
-
-
+
+
+
+
+
+
+

-
+
-







		    getvar(@"selxs"), getvar(@"selys")];
	}
}

void
loadsky(OFString *basename)
{
	@autoreleasepool {
		static OFString *lastsky = @"";
	static OFString *lastsky = @"";

		basename = [basename stringByReplacingOccurrencesOfString:@"\\"
		                                               withString:@"/"];
	basename = [basename stringByReplacingOccurrencesOfString:@"\\"
	                                               withString:@"/"];

		if ([lastsky isEqual:basename])
			return;
	if ([lastsky isEqual:basename])
		return;

		static const OFString *side[] = { @"ft", @"bk", @"lf", @"rt",
			@"dn", @"up" };
		int texnum = 14;
		loopi(6)
		{
			OFString *path =
			    [OFString stringWithFormat:@"packages/%@_%@.jpg",
	static const OFString *side[] = { @"ft", @"bk", @"lf", @"rt", @"dn",
		@"up" };
	int texnum = 14;
	loopi(6)
	{
		OFString *path = [OFString
		    stringWithFormat:@"packages/%@_%@.jpg", basename, side[i]];
			              basename, side[i]];

			int xs, ys;
			if (!installtex(texnum + i,
			        [Cube.sharedInstance.gameDataIRI
			            IRIByAppendingPathComponent:path],
			        &xs, &ys, true))
				conoutf(@"could not load sky textures");
		}
		int xs, ys;
		if (!installtex(texnum + i,
		        [Cube.sharedInstance.gameDataIRI
		            IRIByAppendingPathComponent:path],
		        &xs, &ys, true))
			conoutf(@"could not load sky textures");
	}

		lastsky = basename;
	lastsky = basename;
	}
}
COMMAND(loadsky, ARG_1STR)

float cursordepth = 0.9f;
GLint viewport[4];
GLdouble mm[16], pm[16];
OFVector3D worldpos;
363
364
365
366
367
368
369
370
371
372


373
374
375
376
377
378
379






380
381
382
383
384
385
386
387
360
361
362
363
364
365
366



367
368
369






370
371
372
373
374
375

376
377
378
379
380
381
382







-
-
-
+
+

-
-
-
-
-
-
+
+
+
+
+
+
-







		dblend -= curtime / 3;
		if (dblend < 0)
			dblend = 0;
	}

	glEnable(GL_TEXTURE_2D);

	@autoreleasepool {
		OFString *command = getcurcommand();
		OFString *player = playerincrosshair();
	OFString *command = getcurcommand();
	OFString *player = playerincrosshair();

		if (command)
			draw_textf(@"> %@_", 20, 1570, 2, command);
		else if (closeent.length > 0)
			draw_text(closeent, 20, 1570, 2);
		else if (player != nil)
			draw_text(player, 20, 1570, 2);
	if (command)
		draw_textf(@"> %@_", 20, 1570, 2, command);
	else if (closeent.length > 0)
		draw_text(closeent, 20, 1570, 2);
	else if (player != nil)
		draw_text(player, 20, 1570, 2);
	}

	renderscores();
	if (!rendermenu()) {
		glBlendFunc(GL_SRC_ALPHA, GL_SRC_ALPHA);
		glBindTexture(GL_TEXTURE_2D, 1);
		glBegin(GL_QUADS);
		glColor3ub(255, 255, 255);

Modified src/rendergl.mm from [e315f901d3] to [6d1422bea1].

74
75
76
77
78
79
80
81
82
83
84
85
86
87





88
89
90
91
92




93
94
95
96
97




98
99
100
101
102
103
104
105
106
107









108
109
110
111
112
113
114






115
116
117
118
119
120
121
122
123
124
125
126
127
128






129
130
131
132
133




134
135
136
137
138
139
140






141
142

143
144
145
146
147
148
149
150







151
152
153
154



155
156
157


158
159

160
161

162
163
164
165
166
167
168
169
74
75
76
77
78
79
80







81
82
83
84
85
86




87
88
89
90





91
92
93
94
95









96
97
98
99
100
101
102
103
104
105






106
107
108
109
110
111
112
113
114
115
116
117
118
119






120
121
122
123
124
125





126
127
128
129
130






131
132
133
134
135
136
137

138
139







140
141
142
143
144
145
146
147



148
149
150
151


152
153
154

155
156

157

158
159
160
161
162
163
164







-
-
-
-
-
-
-
+
+
+
+
+

-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+

-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+

-
-
-
-
-
-
+
+
+
+
+
+








-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+

-
-
-
-
-
-
+
+
+
+
+
+

-
+

-
-
-
-
-
-
-
+
+
+
+
+
+
+

-
-
-
+
+
+

-
-
+
+

-
+

-
+
-







	if (qsphere)
		gluDeleteQuadric(qsphere);
}

bool
installtex(int tnum, OFIRI *IRI, int *xs, int *ys, bool clamp)
{
	@autoreleasepool {
		SDL_Surface *s =
		    IMG_Load(IRI.fileSystemRepresentation.UTF8String);
		if (s == NULL) {
			conoutf(@"couldn't load texture %@", IRI.string);
			return false;
		}
	SDL_Surface *s = IMG_Load(IRI.fileSystemRepresentation.UTF8String);
	if (s == NULL) {
		conoutf(@"couldn't load texture %@", IRI.string);
		return false;
	}

		if (s->format->BitsPerPixel != 24) {
			SDL_PixelFormat *format =
			    SDL_AllocFormat(SDL_PIXELFORMAT_RGB24);
			if (format == NULL) {
	if (s->format->BitsPerPixel != 24) {
		SDL_PixelFormat *format =
		    SDL_AllocFormat(SDL_PIXELFORMAT_RGB24);
		if (format == NULL) {
				conoutf(
				    @"texture cannot be converted to 24bpp: %@",
				    IRI.string);
				return false;
			}
			conoutf(@"texture cannot be converted to 24bpp: %@",
			    IRI.string);
			return false;
		}

			@try {
				SDL_Surface *converted =
				    SDL_ConvertSurface(s, format, 0);
				if (converted == NULL) {
					conoutf(@"texture cannot be converted "
					        @"to 24bpp: %@",
					    IRI.string);
					return false;
				}
		@try {
			SDL_Surface *converted =
			    SDL_ConvertSurface(s, format, 0);
			if (converted == NULL) {
				conoutf(@"texture cannot be converted "
				        @"to 24bpp: %@",
				    IRI.string);
				return false;
			}

				SDL_FreeSurface(s);
				s = converted;
			} @finally {
				SDL_FreeFormat(format);
			}
		}
			SDL_FreeSurface(s);
			s = converted;
		} @finally {
			SDL_FreeFormat(format);
		}
	}

#if 0
		loopi(s->w * s->h * 3)
		{
			uchar *p = (uchar *)s->pixels + i;
			*p = 255 - *p;
		}
#endif
		glBindTexture(GL_TEXTURE_2D, tnum);
		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
		    clamp ? GL_CLAMP_TO_EDGE : GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
		    clamp ? GL_CLAMP_TO_EDGE : GL_REPEAT);
	glBindTexture(GL_TEXTURE_2D, tnum);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
	    clamp ? GL_CLAMP_TO_EDGE : GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
	    clamp ? GL_CLAMP_TO_EDGE : GL_REPEAT);
		glTexParameteri(
		    GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
		    GL_LINEAR_MIPMAP_LINEAR); // NEAREST);
		glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
	    GL_LINEAR_MIPMAP_LINEAR); // NEAREST);
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

		*xs = s->w;
		*ys = s->h;
		while (*xs > glmaxtexsize || *ys > glmaxtexsize) {
			*xs /= 2;
			*ys /= 2;
		}
	*xs = s->w;
	*ys = s->h;
	while (*xs > glmaxtexsize || *ys > glmaxtexsize) {
		*xs /= 2;
		*ys /= 2;
	}

		void *scaledimg = s->pixels;
	void *scaledimg = s->pixels;

		if (*xs != s->w) {
			conoutf(@"warning: quality loss: scaling %@",
			    IRI.string); // for voodoo cards under linux
			scaledimg = OFAllocMemory(1, *xs * *ys * 3);
			gluScaleImage(GL_RGB, s->w, s->h, GL_UNSIGNED_BYTE,
			    s->pixels, *xs, *ys, GL_UNSIGNED_BYTE, scaledimg);
		}
	if (*xs != s->w) {
		conoutf(@"warning: quality loss: scaling %@",
		    IRI.string); // for voodoo cards under linux
		scaledimg = OFAllocMemory(1, *xs * *ys * 3);
		gluScaleImage(GL_RGB, s->w, s->h, GL_UNSIGNED_BYTE, s->pixels,
		    *xs, *ys, GL_UNSIGNED_BYTE, scaledimg);
	}

		if (gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, *xs, *ys, GL_RGB,
		        GL_UNSIGNED_BYTE, scaledimg))
			fatal(@"could not build mipmaps");
	if (gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, *xs, *ys, GL_RGB,
	        GL_UNSIGNED_BYTE, scaledimg))
		fatal(@"could not build mipmaps");

		if (*xs != s->w)
			free(scaledimg);
	if (*xs != s->w)
		free(scaledimg);

		SDL_FreeSurface(s);
	SDL_FreeSurface(s);

		return true;
	return true;
	}
}

// management of texture slots
// each texture slot can have multople texture frames, of which currently only
// the first is used additional frames can be used for various shaders

static const int MAXTEX = 1000;
193
194
195
196
197
198
199
200
201

202
203
204


205
206

207
208
209


210
211
212
213
214
215
216
217
188
189
190
191
192
193
194


195
196


197
198
199

200



201
202

203
204
205
206
207
208
209







-
-
+

-
-
+
+

-
+
-
-
-
+
+
-







	curtexnum = 0;
}
COMMAND(texturereset, ARG_NONE)

void
texture(OFString *aframe, OFString *name)
{
	@autoreleasepool {
		int num = curtexnum++, frame = aframe.cube_intValue;
	int num = curtexnum++, frame = aframe.cube_intValue;

		if (num < 0 || num >= 256 || frame < 0 || frame >= MAXFRAMES)
			return;
	if (num < 0 || num >= 256 || frame < 0 || frame >= MAXFRAMES)
		return;

		mapping[num][frame] = 1;
	mapping[num][frame] = 1;
		mapname[num][frame] =
		    [name stringByReplacingOccurrencesOfString:@"\\"
		                                    withString:@"/"];
	mapname[num][frame] = [name stringByReplacingOccurrencesOfString:@"\\"
	                                                      withString:@"/"];
	}
}
COMMAND(texture, ARG_2STR)

int
lookuptexture(int tex, int *xs, int *ys)
{
	int frame = 0; // other frames?
239
240
241
242
243
244
245
246
247
248


249
250
251
252
253
254
255
256
257
258
259
260











261
262
263
264
265
266
267
268
231
232
233
234
235
236
237



238
239
240











241
242
243
244
245
246
247
248
249
250
251

252
253
254
255
256
257
258







-
-
-
+
+

-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-








	if (curtex == MAXTEX)
		fatal(@"loaded too many textures");

	int tnum = curtex + FIRSTTEX;
	texname[curtex] = mapname[tex][frame];

	@autoreleasepool {
		OFString *path =
		    [OFString stringWithFormat:@"packages/%@", texname[curtex]];
	OFString *path =
	    [OFString stringWithFormat:@"packages/%@", texname[curtex]];

		if (installtex(tnum,
		        [Cube.sharedInstance.gameDataIRI
		            IRIByAppendingPathComponent:path],
		        xs, ys, false)) {
			mapping[tex][frame] = tnum;
			texx[curtex] = *xs;
			texy[curtex] = *ys;
			curtex++;
			return tnum;
		} else {
			return mapping[tex][frame] = FIRSTTEX; // temp fix
	if (installtex(tnum,
	        [Cube.sharedInstance.gameDataIRI
	            IRIByAppendingPathComponent:path],
	        xs, ys, false)) {
		mapping[tex][frame] = tnum;
		texx[curtex] = *xs;
		texy[curtex] = *ys;
		curtex++;
		return tnum;
	} else {
		return mapping[tex][frame] = FIRSTTEX; // temp fix
		}
	}
}

void
setupworld()
{
	glEnableClientState(GL_VERTEX_ARRAY);

Modified src/rendermd2.mm from [08d6b30ba2] to [9b0441a720].

12
13
14
15
16
17
18
19
20
21
22
23




24
25
26
27
28



29
30
31
32
33
34




35
36
37
38
39
40
41
42
43

44
45
46
47



48
49
50
51



52
53
54
55
56

57
58
59


60
61

62
63

64
65
66
67
68
69
70
71
12
13
14
15
16
17
18





19
20
21
22
23




24
25
26
27





28
29
30
31

32
33
34
35
36
37


38
39



40
41
42
43



44
45
46





47
48


49
50
51

52
53

54

55
56
57
58
59
60
61







-
-
-
-
-
+
+
+
+

-
-
-
-
+
+
+

-
-
-
-
-
+
+
+
+
-






-
-
+

-
-
-
+
+
+

-
-
-
+
+
+
-
-
-
-
-
+

-
-
+
+

-
+

-
+
-








static const int FIRSTMDL = 20;

void
delayedload(MD2 *m)
{
	if (!m.loaded) {
		@autoreleasepool {
			OFString *path = [OFString
			    stringWithFormat:@"packages/models/%@", m.loadname];
			OFIRI *baseIRI = [Cube.sharedInstance.gameDataIRI
			    IRIByAppendingPathComponent:path];
		OFString *path = [OFString
		    stringWithFormat:@"packages/models/%@", m.loadname];
		OFIRI *baseIRI = [Cube.sharedInstance.gameDataIRI
		    IRIByAppendingPathComponent:path];

			OFIRI *IRI1 =
			    [baseIRI IRIByAppendingPathComponent:@"tris.md2"];
			if (![m loadWithIRI:IRI1])
				fatal(@"loadmodel: ", IRI1.string);
		OFIRI *IRI1 = [baseIRI IRIByAppendingPathComponent:@"tris.md2"];
		if (![m loadWithIRI:IRI1])
			fatal(@"loadmodel: ", IRI1.string);

			OFIRI *IRI2 =
			    [baseIRI IRIByAppendingPathComponent:@"skin.jpg"];
			int xs, ys;
			installtex(FIRSTMDL + m.mdlnum, IRI2, &xs, &ys, false);
			m.loaded = true;
		OFIRI *IRI2 = [baseIRI IRIByAppendingPathComponent:@"skin.jpg"];
		int xs, ys;
		installtex(FIRSTMDL + m.mdlnum, IRI2, &xs, &ys, false);
		m.loaded = true;
		}
	}
}

MD2 *
loadmodel(OFString *name)
{
	@autoreleasepool {
		static int modelnum = 0;
	static int modelnum = 0;

		MD2 *m = mdllookup[name];
		if (m != nil)
			return m;
	MD2 *m = mdllookup[name];
	if (m != nil)
		return m;

		m = [[MD2 alloc] init];
		m.mdlnum = modelnum++;
		m.mmi = [[MapModelInfo alloc] initWithRad:2
	m = [[MD2 alloc] init];
	m.mdlnum = modelnum++;
	m.mmi = [[MapModelInfo alloc] initWithRad:2 h:2 zoff:0 snap:0 name:@""];
		                                        h:2
		                                     zoff:0
		                                     snap:0
		                                     name:@""];
		m.loadname = name;
	m.loadname = name;

		if (mdllookup == nil)
			mdllookup = [[OFMutableDictionary alloc] init];
	if (mdllookup == nil)
		mdllookup = [[OFMutableDictionary alloc] init];

		mdllookup[name] = m;
	mdllookup[name] = m;

		return m;
	return m;
	}
}

void
mapmodel(
    OFString *rad, OFString *h, OFString *zoff, OFString *snap, OFString *name)
{
	MD2 *m = loadmodel([name stringByReplacingOccurrencesOfString:@"\\"

Modified src/rendertext.mm from [d117eaf6b5] to [4a2a0ee6d3].

98
99
100
101
102
103
104
105
106
107


108
109
110
111
112
113
114
115







116
117
118


119
120
121
122
123




124
125
126
127



128
129
130
131



132
133

134
135
136
137
138
139
140
141
142
143
144
145
146






147
148
149
150
151
152
153
154
155
156



157
158
159


160
161
162
163



164
165
166
167
168




169
170
171


172
173
174
175


176
177
178
179
180




181
182
183
184
185




186
187
188
189



190
191
192
193
194




195
196
197


198
199
200
201
202
203
204
205
206
207
208










209
210
211


212
213
214
215
216
217
218
219
98
99
100
101
102
103
104



105
106
107







108
109
110
111
112
113
114
115


116
117
118




119
120
121
122
123



124
125
126
127



128
129
130
131

132

133
134
135
136
137







138
139
140
141
142
143

144
145
146
147
148




149
150
151
152


153
154
155



156
157
158
159




160
161
162
163
164


165
166




167
168
169




170
171
172
173
174




175
176
177
178
179



180
181
182
183




184
185
186
187
188


189
190
191










192
193
194
195
196
197
198
199
200
201
202


203
204

205
206
207
208
209
210
211







-
-
-
+
+

-
-
-
-
-
-
-
+
+
+
+
+
+
+

-
-
+
+

-
-
-
-
+
+
+
+

-
-
-
+
+
+

-
-
-
+
+
+

-
+
-





-
-
-
-
-
-
-
+
+
+
+
+
+
-





-
-
-
-
+
+
+

-
-
+
+

-
-
-
+
+
+

-
-
-
-
+
+
+
+

-
-
+
+
-
-
-
-
+
+

-
-
-
-
+
+
+
+

-
-
-
-
+
+
+
+

-
-
-
+
+
+

-
-
-
-
+
+
+
+

-
-
+
+

-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+

-
-
+
+
-







	{ 270, 448, 310, 512 }, //}
	{ 310, 448, 363, 512 }, //~
};

int
text_width(OFString *string)
{
	@autoreleasepool {
		const char *str = string.UTF8String;
		size_t len = string.UTF8StringLength;
	const char *str = string.UTF8String;
	size_t len = string.UTF8StringLength;

		int x = 0;
		for (int i = 0; i < len; i++) {
			int c = str[i];
			if (c == '\t') {
				x = (x + PIXELTAB) / PIXELTAB * PIXELTAB;
				continue;
			}
	int x = 0;
	for (int i = 0; i < len; i++) {
		int c = str[i];
		if (c == '\t') {
			x = (x + PIXELTAB) / PIXELTAB * PIXELTAB;
			continue;
		}

			if (c == '\f')
				continue;
		if (c == '\f')
			continue;

			if (c == ' ') {
				x += FONTH / 2;
				continue;
			}
		if (c == ' ') {
			x += FONTH / 2;
			continue;
		}

			c -= 33;
			if (c < 0 || c >= 95)
				continue;
		c -= 33;
		if (c < 0 || c >= 95)
			continue;

			int in_width = char_coords[c][2] - char_coords[c][0];
			x += in_width + 1;
		}
		int in_width = char_coords[c][2] - char_coords[c][0];
		x += in_width + 1;
	}

		return x;
	return x;
	}
}

void
draw_textf(OFConstantString *format, int left, int top, int gl_num, ...)
{
	@autoreleasepool {
		va_list arguments;
		va_start(arguments, gl_num);
		OFString *str = [[OFString alloc] initWithFormat:format
		                                       arguments:arguments];
		va_end(arguments);
		draw_text(str, left, top, gl_num);
	va_list arguments;
	va_start(arguments, gl_num);
	OFString *str = [[OFString alloc] initWithFormat:format
	                                       arguments:arguments];
	va_end(arguments);
	draw_text(str, left, top, gl_num);
	}
}

void
draw_text(OFString *string, int left, int top, int gl_num)
{
	@autoreleasepool {
		glBlendFunc(GL_ONE, GL_ONE);
		glBindTexture(GL_TEXTURE_2D, gl_num);
		glColor3ub(255, 255, 255);
	glBlendFunc(GL_ONE, GL_ONE);
	glBindTexture(GL_TEXTURE_2D, gl_num);
	glColor3ub(255, 255, 255);

		int x = left;
		int y = top;
	int x = left;
	int y = top;

		int i;
		float in_left, in_top, in_right, in_bottom;
		int in_width, in_height;
	int i;
	float in_left, in_top, in_right, in_bottom;
	int in_width, in_height;

		const char *str = string.UTF8String;
		size_t len = string.UTF8StringLength;
		for (i = 0; i < len; i++) {
			int c = str[i];
	const char *str = string.UTF8String;
	size_t len = string.UTF8StringLength;
	for (i = 0; i < len; i++) {
		int c = str[i];

			if (c == '\t') {
				x = (x - left + PIXELTAB) / PIXELTAB *
		if (c == '\t') {
			x = (x - left + PIXELTAB) / PIXELTAB * PIXELTAB + left;
				        PIXELTAB +
				    left;
				continue;
			}
			continue;
		}

			if (c == '\f') {
				glColor3ub(64, 255, 128);
				continue;
			}
		if (c == '\f') {
			glColor3ub(64, 255, 128);
			continue;
		}

			if (c == ' ') {
				x += FONTH / 2;
				continue;
			}
		if (c == ' ') {
			x += FONTH / 2;
			continue;
		}

			c -= 33;
			if (c < 0 || c >= 95)
				continue;
		c -= 33;
		if (c < 0 || c >= 95)
			continue;

			in_left = ((float)char_coords[c][0]) / 512.0f;
			in_top = ((float)char_coords[c][1] + 2) / 512.0f;
			in_right = ((float)char_coords[c][2]) / 512.0f;
			in_bottom = ((float)char_coords[c][3] - 2) / 512.0f;
		in_left = ((float)char_coords[c][0]) / 512.0f;
		in_top = ((float)char_coords[c][1] + 2) / 512.0f;
		in_right = ((float)char_coords[c][2]) / 512.0f;
		in_bottom = ((float)char_coords[c][3] - 2) / 512.0f;

			in_width = char_coords[c][2] - char_coords[c][0];
			in_height = char_coords[c][3] - char_coords[c][1];
		in_width = char_coords[c][2] - char_coords[c][0];
		in_height = char_coords[c][3] - char_coords[c][1];

			glBegin(GL_QUADS);
			glTexCoord2f(in_left, in_top);
			glVertex2i(x, y);
			glTexCoord2f(in_right, in_top);
			glVertex2i(x + in_width, y);
			glTexCoord2f(in_right, in_bottom);
			glVertex2i(x + in_width, y + in_height);
			glTexCoord2f(in_left, in_bottom);
			glVertex2i(x, y + in_height);
			glEnd();
		glBegin(GL_QUADS);
		glTexCoord2f(in_left, in_top);
		glVertex2i(x, y);
		glTexCoord2f(in_right, in_top);
		glVertex2i(x + in_width, y);
		glTexCoord2f(in_right, in_bottom);
		glVertex2i(x + in_width, y + in_height);
		glTexCoord2f(in_left, in_bottom);
		glVertex2i(x, y + in_height);
		glEnd();

			xtraverts += 4;
			x += in_width + 1;
		xtraverts += 4;
		x += in_width + 1;
		}
	}
}

// also Don's code, so goes in here too :)

void
draw_envbox_aux(float s0, float t0, int x0, int y0, int z0, float s1, float t1,

Modified src/savegamedemo.mm from [7c09343b90] to [b5a43a0cf0].

87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
































127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146






147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164










165
166
167
168
169
170
171
172
173
174
175
176
177
178
179














180
181
182
183



184
185
186
187
188
189





190
191
192
193
194
195
196
197
198
199
200
201




202
203
204
205
206
207
208
209
87
88
89
90
91
92
93

































94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125

126
127
128
129
130
131
132
133
134
135
136








137
138
139
140
141
142

143
144
145
146
147
148











149
150
151
152
153
154
155
156
157
158
159














160
161
162
163
164
165
166
167
168
169
170
171
172
173




174
175
176






177
178
179
180
181

182
183
184
185
186






187
188
189
190

191
192
193
194
195
196
197







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-











-
-
-
-
-
-
-
-
+
+
+
+
+
+
-






-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+

-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
-
-
-
-
-
-
+
+
+
+
+
-





-
-
-
-
-
-
+
+
+
+
-







	if (demorecording)
		stop();
}

void
savestate(OFIRI *IRI)
{
	@autoreleasepool {
		stop();
		f = gzopen([IRI.fileSystemRepresentation
		               cStringWithEncoding:OFLocale.encoding],
		    "wb9");
		if (!f) {
			conoutf(@"could not write %@", IRI.string);
			return;
		}
		gzwrite(f, (void *)"CUBESAVE", 8);
		gzputc(f, islittleendian);
		gzputi(SAVEGAMEVERSION);
		OFData *data = [player1 dataBySerializing];
		gzputi(data.count);
		char map[_MAXDEFSTR] = { 0 };
		memcpy(map, getclientmap().UTF8String,
		    min(getclientmap().UTF8StringLength, _MAXDEFSTR - 1));
		gzwrite(f, map, _MAXDEFSTR);
		gzputi(gamemode);
		gzputi(ents.length());
		loopv(ents) gzputc(f, ents[i].spawned);
		gzwrite(f, data.items, data.count);
		OFArray<DynamicEntity *> *monsters = getmonsters();
		gzputi(monsters.count);
		for (DynamicEntity *monster in monsters) {
			data = [monster dataBySerializing];
			gzwrite(f, data.items, data.count);
		}
		gzputi(players.count);
		for (id player in players) {
			gzput(player == [OFNull null]);
			data = [player dataBySerializing];
			gzwrite(f, data.items, data.count);
	stop();
	f = gzopen([IRI.fileSystemRepresentation
	               cStringWithEncoding:OFLocale.encoding],
	    "wb9");
	if (!f) {
		conoutf(@"could not write %@", IRI.string);
		return;
	}
	gzwrite(f, (void *)"CUBESAVE", 8);
	gzputc(f, islittleendian);
	gzputi(SAVEGAMEVERSION);
	OFData *data = [player1 dataBySerializing];
	gzputi(data.count);
	char map[_MAXDEFSTR] = { 0 };
	memcpy(map, getclientmap().UTF8String,
	    min(getclientmap().UTF8StringLength, _MAXDEFSTR - 1));
	gzwrite(f, map, _MAXDEFSTR);
	gzputi(gamemode);
	gzputi(ents.length());
	loopv(ents) gzputc(f, ents[i].spawned);
	gzwrite(f, data.items, data.count);
	OFArray<DynamicEntity *> *monsters = getmonsters();
	gzputi(monsters.count);
	for (DynamicEntity *monster in monsters) {
		data = [monster dataBySerializing];
		gzwrite(f, data.items, data.count);
	}
	gzputi(players.count);
	for (id player in players) {
		gzput(player == [OFNull null]);
		data = [player dataBySerializing];
		gzwrite(f, data.items, data.count);
		}
	}
}

void
savegame(OFString *name)
{
	if (!m_classicsp) {
		conoutf(@"can only save classic sp games");
		return;
	}

	@autoreleasepool {
		OFString *path =
		    [OFString stringWithFormat:@"savegames/%@.csgz", name];
		OFIRI *IRI = [Cube.sharedInstance.userDataIRI
		    IRIByAppendingPathComponent:path];
		savestate(IRI);
		stop();
		conoutf(@"wrote %@", IRI.string);
	OFString *path = [OFString stringWithFormat:@"savegames/%@.csgz", name];
	OFIRI *IRI =
	    [Cube.sharedInstance.userDataIRI IRIByAppendingPathComponent:path];
	savestate(IRI);
	stop();
	conoutf(@"wrote %@", IRI.string);
	}
}
COMMAND(savegame, ARG_1STR)

void
loadstate(OFIRI *IRI)
{
	@autoreleasepool {
		stop();
		if (multiplayer())
			return;
		f = gzopen([IRI.fileSystemRepresentation
		               cStringWithEncoding:OFLocale.encoding],
		    "rb9");
		if (!f) {
			conoutf(@"could not open %@", IRI.string);
			return;
		}
	stop();
	if (multiplayer())
		return;
	f = gzopen([IRI.fileSystemRepresentation
	               cStringWithEncoding:OFLocale.encoding],
	    "rb9");
	if (!f) {
		conoutf(@"could not open %@", IRI.string);
		return;
	}

		char mapname[_MAXDEFSTR] = { 0 };
		char buf[8];
		gzread(f, buf, 8);
		if (strncmp(buf, "CUBESAVE", 8))
			goto out;
		if (gzgetc(f) != islittleendian)
			goto out; // not supporting save->load accross
			          // incompatible architectures simpifies things
			          // a LOT
		if (gzgeti() != SAVEGAMEVERSION ||
		    gzgeti() != DynamicEntity.serializedSize)
			goto out;
		gzread(f, mapname, _MAXDEFSTR);
		nextmode = gzgeti();
	char mapname[_MAXDEFSTR] = { 0 };
	char buf[8];
	gzread(f, buf, 8);
	if (strncmp(buf, "CUBESAVE", 8))
		goto out;
	if (gzgetc(f) != islittleendian)
		goto out; // not supporting save->load accross
		          // incompatible architectures simpifies things
		          // a LOT
	if (gzgeti() != SAVEGAMEVERSION ||
	    gzgeti() != DynamicEntity.serializedSize)
		goto out;
	gzread(f, mapname, _MAXDEFSTR);
	nextmode = gzgeti();
		@autoreleasepool {
			// continue below once map has been loaded and client &
			// server have updated
			changemap(@(mapname));
	// continue below once map has been loaded and client & server
	// have updated
	changemap(@(mapname));
		}
		return;
	out:
		conoutf(@"aborting: savegame/demo from a different version of "
		        @"cube or cpu architecture");
		stop();
	return;
out:
	conoutf(@"aborting: savegame/demo from a different version of "
	        @"cube or cpu architecture");
	stop();
	}
}

void
loadgame(OFString *name)
{
	@autoreleasepool {
		OFString *path =
		    [OFString stringWithFormat:@"savegames/%@.csgz", name];
		OFIRI *IRI = [Cube.sharedInstance.userDataIRI
		    IRIByAppendingPathComponent:path];
		loadstate(IRI);
	OFString *path = [OFString stringWithFormat:@"savegames/%@.csgz", name];
	OFIRI *IRI =
	    [Cube.sharedInstance.userDataIRI IRIByAppendingPathComponent:path];
	loadstate(IRI);
	}
}
COMMAND(loadgame, ARG_1STR)

void
loadgameout()
{
	stop();
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299









300
301
302
303
304
305
306
307
270
271
272
273
274
275
276











277
278
279
280
281
282
283
284
285

286
287
288
289
290
291
292







-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-







		return;
	}

	int cn = getclientnum();
	if (cn < 0)
		return;

	@autoreleasepool {
		OFString *path =
		    [OFString stringWithFormat:@"demos/%@.cdgz", name];
		OFIRI *IRI = [Cube.sharedInstance.userDataIRI
		    IRIByAppendingPathComponent:path];
		savestate(IRI);
		gzputi(cn);
		conoutf(@"started recording demo to %@", IRI.string);
		demorecording = true;
		starttime = lastmillis;
		ddamage = bdamage = 0;
	OFString *path = [OFString stringWithFormat:@"demos/%@.cdgz", name];
	OFIRI *IRI =
	    [Cube.sharedInstance.userDataIRI IRIByAppendingPathComponent:path];
	savestate(IRI);
	gzputi(cn);
	conoutf(@"started recording demo to %@", IRI.string);
	demorecording = true;
	starttime = lastmillis;
	ddamage = bdamage = 0;
	}
}
COMMAND(record, ARG_1STR)

void
demodamage(int damage, const OFVector3D &o)
{
	ddamage = damage;
344
345
346
347
348
349
350
351
352
353
354
355
356
357





358
359
360
361
362
363
364
365
329
330
331
332
333
334
335







336
337
338
339
340

341
342
343
344
345
346
347







-
-
-
-
-
-
-
+
+
+
+
+
-







		// the network
	}
}

void
demo(OFString *name)
{
	@autoreleasepool {
		OFString *path =
		    [OFString stringWithFormat:@"demos/%@.cdgz", name];
		OFIRI *IRI = [Cube.sharedInstance.userDataIRI
		    IRIByAppendingPathComponent:path];
		loadstate(IRI);
		demoloading = true;
	OFString *path = [OFString stringWithFormat:@"demos/%@.cdgz", name];
	OFIRI *IRI =
	    [Cube.sharedInstance.userDataIRI IRIByAppendingPathComponent:path];
	loadstate(IRI);
	demoloading = true;
	}
}
COMMAND(demo, ARG_1STR)

void
stopreset()
{
	conoutf(@"demo stopped (%d msec elapsed)", lastmillis - starttime);

Modified src/server.mm from [7081fc19b8] to [bc477ab2a8].

169
170
171
172
173
174
175
176
177

178
179
180
181



182
183
184
185
186
187
188
189
169
170
171
172
173
174
175


176




177
178
179

180
181
182
183
184
185
186







-
-
+
-
-
-
-
+
+
+
-







				no++;
		}
	}

	if (yes == 1 && no == 0)
		return true; // single player

	@autoreleasepool {
		OFString *msg =
	OFString *msg = [OFString
		    [OFString stringWithFormat:
		                  @"%@ suggests %@ on map %@ (set map to vote)",
		              clients[sender].name, modestr(reqmode), map];
		sendservmsg(msg);
	    stringWithFormat:@"%@ suggests %@ on map %@ (set map to vote)",
	    clients[sender].name, modestr(reqmode), map];
	sendservmsg(msg);
	}

	if (yes / (float)(yes + no) <= 0.5f)
		return false;

	sendservmsg(@"vote passed");
	resetvotes();
	return true;
211
212
213
214
215
216
217
218
219

220
221
222
223
224
225
226
227
228
229
230
231
232
233



234
235
236
237
238
239
240
241

242
243
244
245
246
247
248
249
208
209
210
211
212
213
214


215

216
217
218
219
220
221
222
223
224




225
226
227

228
229
230
231
232


233

234
235
236
237
238
239
240







-
-
+
-









-
-
-
-
+
+
+
-





-
-
+
-







		switch ((type = getint(p))) {
		case SV_TEXT:
			sgetstr();
			break;

		case SV_INITC2S:
			sgetstr();
			@autoreleasepool {
				clients[cn].name = @(text);
			clients[cn].name = @(text);
			}
			sgetstr();
			getint(p);
			break;

		case SV_MAPCHANGE: {
			sgetstr();
			int reqmode = getint(p);
			if (reqmode < 0)
				reqmode = 0;
			@autoreleasepool {
				if (smapname.length > 0 && !mapreload &&
				    !vote(@(text), reqmode, sender))
					return;
			if (smapname.length > 0 && !mapreload &&
			    !vote(@(text), reqmode, sender))
				return;
			}
			mapreload = false;
			mode = reqmode;
			minremain = mode & 1 ? 15 : 10;
			mapend = lastsec + minremain * 60;
			interm = 0;
			@autoreleasepool {
				smapname = @(text);
			smapname = @(text);
			}
			resetitems();
			sender = -1;
			break;
		}

		case SV_ITEMLIST: {
			int n;
280
281
282
283
284
285
286
287
288

289
290
291
292
293
294
295
296
271
272
273
274
275
276
277


278

279
280
281
282
283
284
285







-
-
+
-







			loopi(size - 2) getint(p);
			break;
		}

		case SV_SENDMAP: {
			sgetstr();
			int mapsize = getint(p);
			@autoreleasepool {
				sendmaps(sender, @(text), mapsize, p);
			sendmaps(sender, @(text), mapsize, p);
			}
			return;
		}

		case SV_RECVMAP:
			send(sender, recvmap(sender));
			return;

325
326
327
328
329
330
331
332
333

334
335
336
337
338
339
340
341
314
315
316
317
318
319
320


321

322
323
324
325
326
327
328







-
-
+
-







	ENetPacket *packet =
	    enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
	uchar *start = packet->data;
	uchar *p = start + 2;
	putint(p, SV_INITS2C);
	putint(p, n);
	putint(p, PROTOCOL_VERSION);
	@autoreleasepool {
		putint(p, *smapname.UTF8String);
	putint(p, *smapname.UTF8String);
	}
	sendstring(serverpassword, p);
	putint(p, clients.count > maxclients);
	if (smapname.length > 0) {
		putint(p, SV_MAPCHANGE);
		sendstring(smapname, p);
		putint(p, mode);
		putint(p, SV_ITEMLIST);
489
490
491
492
493
494
495
496
497
498
499
500
501




502
503
504
505
506
507
508
509
476
477
478
479
480
481
482






483
484
485
486

487
488
489
490
491
492
493







-
-
-
-
-
-
+
+
+
+
-







		switch (event.type) {
		case ENET_EVENT_TYPE_CONNECT: {
			Client *c = addclient();
			c.type = ST_TCPIP;
			c.peer = event.peer;
			c.peer->data = (void *)(clients.count - 1);
			char hn[1024];
			@autoreleasepool {
				c.hostname =
				    (enet_address_get_host(
				         &c.peer->address, hn, sizeof(hn)) == 0
				            ? @(hn)
				            : @"localhost");
			c.hostname = (enet_address_get_host(
			                  &c.peer->address, hn, sizeof(hn)) == 0
			        ? @(hn)
			        : @"localhost");
			}
			[OFStdOut
			    writeFormat:@"client connected (%@)\n", c.hostname];
			send_welcome(lastconnect = clients.count - 1);
			break;
		}
		case ENET_EVENT_TYPE_RECEIVE:
			brec += event.packet->dataLength;
562
563
564
565
566
567
568
569
570
571
572



573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591


592
593
594




546
547
548
549
550
551
552




553
554
555

556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575



576
577
578
579







-
-
-
-
+
+
+
-


















+
+
-
-
-
+
+
+
+
	serverpassword = passwd;
	maxclients = maxcl;
	servermsinit(master ? master : @"wouter.fov120.com/cube/masterserver/",
	    sdesc, dedicated);

	if ((isdedicated = dedicated)) {
		ENetAddress address = { ENET_HOST_ANY, CUBE_SERVER_PORT };
		@autoreleasepool {
			if (ip.length > 0 &&
			    enet_address_set_host(&address, ip.UTF8String) < 0)
				printf("WARNING: server ip not resolved");
		if (ip.length > 0 &&
		    enet_address_set_host(&address, ip.UTF8String) < 0)
			printf("WARNING: server ip not resolved");
		}
		serverhost = enet_host_create(&address, MAXCLIENTS, 0, uprate);
		if (!serverhost)
			fatal(@"could not create server host\n");
		loopi(MAXCLIENTS) serverhost->peers[i].data = (void *)-1;
	}

	resetserverifempty();

	// do not return, this becomes main loop
	if (isdedicated) {
#ifdef _WIN32
		SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
#endif
		printf("dedicated server started, waiting for "
		       "clients...\nCtrl-C to exit\n\n");
		atexit(cleanupserver);
		atexit(enet_deinitialize);
		for (;;)
			@autoreleasepool {
				serverslice(
			serverslice(/*enet_time_get_sec()*/ time(NULL), 5);
	}
}
				    /*enet_time_get_sec()*/ time(NULL), 5);
			}
	}
}

Modified src/serverbrowser.mm from [fbd128e000] to [bb466005e7].

176
177
178
179
180
181
182
183
184
185
186



187
188
189


190
191
192

193
194
195
196
197
198
199
200
176
177
178
179
180
181
182




183
184
185
186


187
188
189


190

191
192
193
194
195
196
197







-
-
-
-
+
+
+

-
-
+
+

-
-
+
-







{
	return servers[n].name;
}

void
addserver(OFString *servername)
{
	@autoreleasepool {
		for (ServerInfo *si in servers)
			if ([si.name isEqual:servername])
				return;
	for (ServerInfo *si in servers)
		if ([si.name isEqual:servername])
			return;

		if (servers == nil)
			servers = [[OFMutableArray alloc] init];
	if (servers == nil)
		servers = [[OFMutableArray alloc] init];

		[servers
		    addObject:[[ServerInfo alloc] initWithName:servername]];
	[servers addObject:[[ServerInfo alloc] initWithName:servername]];
	}
}

void
pingservers()
{
	ENetBuffer buf;
	uchar ping[MAXTRANS];
256
257
258
259
260
261
262
263
264

265
266
267
268

269
270
271
272
273
274
275
276
253
254
255
256
257
258
259


260

261


262

263
264
265
266
267
268
269







-
-
+
-

-
-
+
-







				si.protocol = getint(p);
				if (si.protocol != PROTOCOL_VERSION)
					si.ping = 9998;
				si.mode = getint(p);
				si.numplayers = getint(p);
				si.minremain = getint(p);
				sgetstr();
				@autoreleasepool {
					si.map = @(text);
				si.map = @(text);
				}
				sgetstr();
				@autoreleasepool {
					si.sdesc = @(text);
				si.sdesc = @(text);
				}
				break;
			}
		}
	}
}

void
302
303
304
305
306
307
308
309
310
311


312
313
314
315
316
317
318
319
295
296
297
298
299
300
301



302
303

304
305
306
307
308
309
310







-
-
-
+
+
-







			    initWithFormat:
			        (si.address.host != ENET_HOST_ANY
			                ? @"%@ [waiting for server response]"
			                : @"%@ [unknown host]\t"),
			    si.name];

		// cut off too long server descriptions
		@autoreleasepool {
			if (si.full.length > 50)
				si.full = [si.full substringToIndex:50];
		if (si.full.length > 50)
			si.full = [si.full substringToIndex:50];
		}

		menumanual(1, i, si.full);

		if (!--maxmenu)
			return;

		i++;
344
345
346
347
348
349
350
351
352

353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371


372
373
374
335
336
337
338
339
340
341


342

343
344
345
346
347
348
349
350
351
352
353
354
355
356
357



358
359

360
361







-
-
+
-















-
-
-
+
+
-


	uchar buf[MAXUPD];
	uchar *reply = retrieveservers(buf, MAXUPD);
	if (!*reply || strstr((char *)reply, "<html>") ||
	    strstr((char *)reply, "<HTML>"))
		conoutf(@"master server not replying");
	else {
		[servers removeAllObjects];
		@autoreleasepool {
			execute(@((char *)reply));
		execute(@((char *)reply));
		}
	}
	servermenu();
}

COMMAND(addserver, ARG_1STR)
COMMAND(servermenu, ARG_NONE)
COMMAND(updatefrommaster, ARG_NONE)

void
writeservercfg()
{
	FILE *f = fopen("servers.cfg", "w");
	if (!f)
		return;
	fprintf(f, "// servers connected to are added here automatically\n\n");
	@autoreleasepool {
		for (ServerInfo *si in servers.reversedArray)
			fprintf(f, "addserver %s\n", si.name.UTF8String);
	for (ServerInfo *si in servers.reversedArray)
		fprintf(f, "addserver %s\n", si.name.UTF8String);
	}
	fclose(f);
}

Modified src/serverms.mm from [fcffeffeb7] to [4bb1ac1ee8].

1
2
3
4
5
6
7
8
9
10
11
12
13
14

15
16
17
18
19
20
21
22
1
2
3
4
5
6
7
8
9
10
11
12


13

14
15
16
17
18
19
20












-
-
+
-







// all server side masterserver and pinging functionality

#include "cube.h"

static ENetSocket mssock = ENET_SOCKET_NULL;

static void
httpgetsend(ENetAddress &ad, OFString *hostname, OFString *req, OFString *ref,
    OFString *agent)
{
	if (ad.host == ENET_HOST_ANY) {
		[OFStdOut writeFormat:@"looking up %@...\n", hostname];
		@autoreleasepool {
			enet_address_set_host(&ad, hostname.UTF8String);
		enet_address_set_host(&ad, hostname.UTF8String);
		}
		if (ad.host == ENET_HOST_ANY)
			return;
	}
	if (mssock != ENET_SOCKET_NULL)
		enet_socket_destroy(mssock);
	mssock = enet_socket_create(ENET_SOCKET_TYPE_STREAM, NULL);
	if (mssock == ENET_SOCKET_NULL) {
75
76
77
78
79
80
81
82
83
84


85
86
87


88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112




113
114
115
116
117
118
119
120
73
74
75
76
77
78
79



80
81



82
83

84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102





103
104
105
106

107
108
109
110
111
112
113







-
-
-
+
+
-
-
-
+
+
-



















-
-
-
-
-
+
+
+
+
-







static ENetBuffer masterb;

static void
updatemasterserver(int seconds)
{
	// send alive signal to masterserver every hour of uptime
	if (seconds > updmaster) {
		@autoreleasepool {
			OFString *path = [OFString
			    stringWithFormat:@"%@register.do?action=add",
		OFString *path = [OFString
		    stringWithFormat:@"%@register.do?action=add", masterpath];
			    masterpath];
			httpgetsend(masterserver, masterbase, path,
			    @"cubeserver", @"Cube Server");
		httpgetsend(masterserver, masterbase, path, @"cubeserver",
		    @"Cube Server");
		}
		masterrep[0] = 0;
		masterb.data = masterrep;
		masterb.dataLength = MAXTRANS - 1;
		updmaster = seconds + 60 * 60;
	}
}

static void
checkmasterreply()
{
	bool busy = mssock != ENET_SOCKET_NULL;
	httpgetrecieve(masterb);
	if (busy && mssock == ENET_SOCKET_NULL)
		printf("masterserver reply: %s\n", stripheader(masterrep));
}

uchar *
retrieveservers(uchar *buf, int buflen)
{
	@autoreleasepool {
		OFString *path = [OFString
		    stringWithFormat:@"%@retrieve.do?item=list", masterpath];
		httpgetsend(masterserver, masterbase, path, @"cubeserver",
		    @"Cube Server");
	OFString *path =
	    [OFString stringWithFormat:@"%@retrieve.do?item=list", masterpath];
	httpgetsend(
	    masterserver, masterbase, path, @"cubeserver", @"Cube Server");
	}
	ENetBuffer eb;
	buf[0] = 0;
	eb.data = buf;
	eb.dataLength = buflen - 1;
	while (mssock != ENET_SOCKET_NULL)
		httpgetrecieve(eb);
	return stripheader(buf);
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171








172
173
174


175
176
177
178
179
180
181






182
149
150
151
152
153
154
155









156
157
158
159
160
161
162
163
164


165
166







167
168
169
170
171
172








-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+

-
-
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
-
		enet_socket_send(pongsock, &addr, &buf, 1);
	}
}

void
servermsinit(OFString *master_, OFString *sdesc, bool listen)
{
	@autoreleasepool {
		const char *master = master_.UTF8String;
		const char *mid = strstr(master, "/");
		if (!mid)
			mid = master;
		masterpath = @(mid);
		masterbase = [[OFString alloc] initWithUTF8String:master
		                                           length:mid - master];
		serverdesc = sdesc;
	const char *master = master_.UTF8String;
	const char *mid = strstr(master, "/");
	if (!mid)
		mid = master;
	masterpath = @(mid);
	masterbase = [[OFString alloc] initWithUTF8String:master
	                                           length:mid - master];
	serverdesc = sdesc;

		if (listen) {
			ENetAddress address = { ENET_HOST_ANY,
	if (listen) {
		ENetAddress address = { ENET_HOST_ANY, CUBE_SERVINFO_PORT };
				CUBE_SERVINFO_PORT };
			pongsock = enet_socket_create(
			    ENET_SOCKET_TYPE_DATAGRAM, &address);
			if (pongsock == ENET_SOCKET_NULL)
				fatal(@"could not create server info socket\n");
		}
	}
		pongsock =
		    enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM, &address);
		if (pongsock == ENET_SOCKET_NULL)
			fatal(@"could not create server info socket\n");
	}
}
}

Modified src/serverutil.mm from [26cbbdcb4f] to [0ddeef8a46].

41
42
43
44
45
46
47
48
49

50
51
52


53
54

55
56
57
58
59
60
61
62
41
42
43
44
45
46
47


48
49


50
51
52

53

54
55
56
57
58
59
60







-
-
+

-
-
+
+

-
+
-







	} else
		return c;
}

void
sendstring(OFString *t_, uchar *&p)
{
	@autoreleasepool {
		const char *t = t_.UTF8String;
	const char *t = t_.UTF8String;

		for (size_t i = 0; i < _MAXDEFSTR && *t != '\0'; i++)
			putint(p, *t++);
	for (size_t i = 0; i < _MAXDEFSTR && *t != '\0'; i++)
		putint(p, *t++);

		putint(p, 0);
	putint(p, 0);
	}
}

static const OFString *modenames[] = {
	@"SP",
	@"DMSP",
	@"ffa/default",
	@"coopedit",

Modified src/sound.mm from [7cef1841c0] to [285830fef5].

83
84
85
86
87
88
89
90
91
92
93
94
95
96






97
98
99
100


101
102
103
104



105
106
107


108
109
110
111
112
113





114
115
116
117
118
119
120
121







122
123
124


125
126
127
128
129
130
131
132
133
83
84
85
86
87
88
89







90
91
92
93
94
95
96
97


98
99




100
101
102
103


104
105






106
107
108
109
110








111
112
113
114
115
116
117



118
119
120

121
122
123
124
125
126
127







-
-
-
-
-
-
-
+
+
+
+
+
+


-
-
+
+
-
-
-
-
+
+
+

-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
+
+

-







void
music(OFString *name)
{
	if (nosound)
		return;
	stopsound();
	if (soundvol && musicvol) {
		@autoreleasepool {
			name = [name stringByReplacingOccurrencesOfString:@"\\"
			                                       withString:@"/"];
			OFString *path =
			    [OFString stringWithFormat:@"packages/%@", name];
			OFIRI *IRI = [Cube.sharedInstance.gameDataIRI
			    IRIByAppendingPathComponent:path];
		name = [name stringByReplacingOccurrencesOfString:@"\\"
		                                       withString:@"/"];
		OFString *path =
		    [OFString stringWithFormat:@"packages/%@", name];
		OFIRI *IRI = [Cube.sharedInstance.gameDataIRI
		    IRIByAppendingPathComponent:path];

#ifdef USE_MIXER
			if ((mod = Mix_LoadMUS(
			         IRI.fileSystemRepresentation.UTF8String)) !=
		if ((mod = Mix_LoadMUS(
		         IRI.fileSystemRepresentation.UTF8String)) != NULL) {
			    NULL) {
				Mix_PlayMusic(mod, -1);
				Mix_VolumeMusic((musicvol * MAXVOL) / 255);
			}
			Mix_PlayMusic(mod, -1);
			Mix_VolumeMusic((musicvol * MAXVOL) / 255);
		}
#else
			if ((mod = FMUSIC_LoadSong(
			         IRI.fileSystemRepresentation.UTF8String)) !=
		if ((mod = FMUSIC_LoadSong(
		         IRI.fileSystemRepresentation.UTF8String)) != NULL) {
			    NULL) {
				FMUSIC_PlaySong(mod);
				FMUSIC_SetMasterVolume(mod, musicvol);
			} else if (stream = FSOUND_Stream_Open(
			               IRI.fileSystemRepresentation.UTF8String,
			               FSOUND_LOOP_NORMAL, 0, 0)) {
			FMUSIC_PlaySong(mod);
			FMUSIC_SetMasterVolume(mod, musicvol);
		} else if (stream = FSOUND_Stream_Open(
		               IRI.fileSystemRepresentation.UTF8String,
		               FSOUND_LOOP_NORMAL, 0, 0)) {
				int chan =
				    FSOUND_Stream_Play(FSOUND_FREE, stream);
				if (chan >= 0) {
					FSOUND_SetVolume(
					    chan, (musicvol * MAXVOL) / 255);
					FSOUND_SetPaused(chan, false);
				}
			} else {
			int chan = FSOUND_Stream_Play(FSOUND_FREE, stream);
			if (chan >= 0) {
				FSOUND_SetVolume(
				    chan, (musicvol * MAXVOL) / 255);
				FSOUND_SetPaused(chan, false);
			}
		} else {
				conoutf(
				    @"could not play music: %@", IRI.string);
			}
			conoutf(@"could not play music: %@", IRI.string);
		}
#endif
		}
	}
}
COMMAND(music, ARG_1STR)

#ifdef USE_MIXER
vector<Mix_Chunk *> samples;
#else

Modified src/world.mm from [f38617a638] to [2ac8b85a8f].

63
64
65
66
67
68
69
70
71
72


73
74
75


76
77
78
79
80
81
82
83
63
64
65
66
67
68
69



70
71
72


73
74

75
76
77
78
79
80
81







-
-
-
+
+

-
-
+
+
-







		return;

	settag(tag, type);

	if (!savegame && type != 3)
		playsound(S_RUMBLE);

	@autoreleasepool {
		OFString *aliasname =
		    [OFString stringWithFormat:@"level_trigger_%d", tag];
	OFString *aliasname =
	    [OFString stringWithFormat:@"level_trigger_%d", tag];

		if (identexists(aliasname))
			execute(aliasname);
	if (identexists(aliasname))
		execute(aliasname);
	}

	if (type == 2)
		endsp(false);
}
COMMAND(trigger, ARG_2INT)

// main geometric mipmapping routine, recursively rebuild mipmaps within block
302
303
304
305
306
307
308
309
310

311
312
313
314
315
316
317
318
319
320
321
322
323
324



325
326
327
328
329
330
331
332
300
301
302
303
304
305
306


307

308
309
310
311
312
313
314
315
316




317
318
319

320
321
322
323
324
325
326







-
-
+
-









-
-
-
-
+
+
+
-







{
	int e = closestent();
	if (e < 0) {
		conoutf(@"no more entities");
		return;
	}
	int t = ents[e].type;
	@autoreleasepool {
		conoutf(@"%@ entity deleted", entnames[t]);
	conoutf(@"%@ entity deleted", entnames[t]);
	}
	ents[e].type = NOTUSED;
	addmsg(1, 10, SV_EDITENT, e, NOTUSED, 0, 0, 0, 0, 0, 0, 0);
	if (t == LIGHT)
		calclight();
}

int
findtype(OFString *what)
{
	@autoreleasepool {
		loopi(MAXENTTYPES) if ([what isEqual:entnames[i]]) return i;
		conoutf(@"unknown entity type \"%@\"", what);
		return NOTUSED;
	loopi(MAXENTTYPES) if ([what isEqual:entnames[i]]) return i;
	conoutf(@"unknown entity type \"%@\"", what);
	return NOTUSED;
	}
}

entity *
newentity(int x, int y, int z, OFString *what, int v1, int v2, int v3, int v4)
{
	int type = findtype(what);
	persistent_entity e = { (short)x, (short)y, (short)z, (short)v1,

Modified src/worldio.mm from [f3a87aa011] to [1dbc40dfe7].

10
11
12
13
14
15
16
17
18
19
20
21




22
23
24
25
26
27
28
29







30
31
32


33
34
35
36
37
38
39






40
41
42
43
44
45
46
47
10
11
12
13
14
15
16





17
18
19
20
21







22
23
24
25
26
27
28
29


30
31







32
33
34
35
36
37

38
39
40
41
42
43
44







-
-
-
-
-
+
+
+
+

-
-
-
-
-
-
-
+
+
+
+
+
+
+

-
-
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
-







}

static OFString *cgzname, *bakname, *pcfname, *mcfname;

static void
setnames(OFString *name)
{
	@autoreleasepool {
		OFCharacterSet *cs =
		    [OFCharacterSet characterSetWithCharactersInString:@"/\\"];
		OFRange range = [name rangeOfCharacterFromSet:cs];
		OFString *pakname, *mapname;
	OFCharacterSet *cs =
	    [OFCharacterSet characterSetWithCharactersInString:@"/\\"];
	OFRange range = [name rangeOfCharacterFromSet:cs];
	OFString *pakname, *mapname;

		if (range.location != OFNotFound) {
			pakname = [name substringToIndex:range.location];
			mapname = [name substringFromIndex:range.location + 1];
		} else {
			pakname = @"base";
			mapname = name;
		}
	if (range.location != OFNotFound) {
		pakname = [name substringToIndex:range.location];
		mapname = [name substringFromIndex:range.location + 1];
	} else {
		pakname = @"base";
		mapname = name;
	}

		cgzname = [[OFString alloc]
		    initWithFormat:@"packages/%@/%@.cgz", pakname, mapname];
	cgzname = [[OFString alloc]
	    initWithFormat:@"packages/%@/%@.cgz", pakname, mapname];
		bakname =
		    [[OFString alloc] initWithFormat:@"packages/%@/%@_%d.BAK",
		                      pakname, mapname, lastmillis];
		pcfname = [[OFString alloc]
		    initWithFormat:@"packages/%@/package.cfg", pakname];
		mcfname = [[OFString alloc]
		    initWithFormat:@"packages/%@/%@.cfg", pakname, mapname];
	bakname = [[OFString alloc] initWithFormat:@"packages/%@/%@_%d.BAK",
	                            pakname, mapname, lastmillis];
	pcfname = [[OFString alloc]
	    initWithFormat:@"packages/%@/package.cfg", pakname];
	mcfname = [[OFString alloc]
	    initWithFormat:@"packages/%@/%@.cfg", pakname, mapname];
	}
}

// the optimize routines below are here to reduce the detrimental effects of
// messy mapping by setting certain properties (vdeltas and textures) to
// neighbouring values wherever there is no visible difference. This allows the
// mipmapper to generate more efficient mips. the reason it is done on save is
// to reduce the amount spend in the mipmapper (as that is done in realtime).
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194































195
196
197
198
199
200
201
202
203
204
205
206
207
208



209
210
211
212
213
214





215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248

































249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290

































291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340

















































341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358

















359
360
361
362
363
364
365
366
367
368
369










370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393























394
395
153
154
155
156
157
158
159
































160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201



202
203
204
205





206
207
208
209
210
211

































212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244

245
246
247
248
249
250
251


































252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284


















































285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333


















334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350











351
352
353
354
355
356
357
358
359
360
























361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383

384







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+











-
-
-
+
+
+

-
-
-
-
-
+
+
+
+
+

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-

// run-length encoding and leaves out data for certain kinds of cubes, then zlib
// removes the last bits of redundancy. Both passes contribute greatly to the
// miniscule map sizes.

void
save_world(OFString *mname)
{
	@autoreleasepool {
		resettagareas(); // wouldn't be able to reproduce tagged areas
		                 // otherwise
		voptimize();
		toptimize();
		if (mname.length == 0)
			mname = getclientmap();
		setnames(mname);
		backup(cgzname, bakname);
		gzFile f = gzopen(
		    [cgzname cStringWithEncoding:OFLocale.encoding], "wb9");
		if (!f) {
			conoutf(@"could not write map to %@", cgzname);
			return;
		}
		hdr.version = MAPVERSION;
		hdr.numents = 0;
		loopv(ents) if (ents[i].type != NOTUSED) hdr.numents++;
		header tmp = hdr;
		endianswap(&tmp.version, sizeof(int), 4);
		endianswap(&tmp.waterlevel, sizeof(int), 16);
		gzwrite(f, &tmp, sizeof(header));
		loopv(ents)
		{
			if (ents[i].type != NOTUSED) {
				entity tmp = ents[i];
				endianswap(&tmp, sizeof(short), 4);
				gzwrite(f, &tmp, sizeof(persistent_entity));
			}
		}
		sqr *t = NULL;
		int sc = 0;
	resettagareas(); // wouldn't be able to reproduce tagged areas
	                 // otherwise
	voptimize();
	toptimize();
	if (mname.length == 0)
		mname = getclientmap();
	setnames(mname);
	backup(cgzname, bakname);
	gzFile f =
	    gzopen([cgzname cStringWithEncoding:OFLocale.encoding], "wb9");
	if (!f) {
		conoutf(@"could not write map to %@", cgzname);
		return;
	}
	hdr.version = MAPVERSION;
	hdr.numents = 0;
	loopv(ents) if (ents[i].type != NOTUSED) hdr.numents++;
	header tmp = hdr;
	endianswap(&tmp.version, sizeof(int), 4);
	endianswap(&tmp.waterlevel, sizeof(int), 16);
	gzwrite(f, &tmp, sizeof(header));
	loopv(ents)
	{
		if (ents[i].type != NOTUSED) {
			entity tmp = ents[i];
			endianswap(&tmp, sizeof(short), 4);
			gzwrite(f, &tmp, sizeof(persistent_entity));
		}
	}
	sqr *t = NULL;
	int sc = 0;
#define spurge                          \
	while (sc) {                    \
		gzputc(f, 255);         \
		if (sc > 255) {         \
			gzputc(f, 255); \
			sc -= 255;      \
		} else {                \
			gzputc(f, sc);  \
			sc = 0;         \
		}                       \
	}
		loopk(cubicsize)
		{
			sqr *s = &world[k];
	loopk(cubicsize)
	{
		sqr *s = &world[k];
#define c(f) (s->f == t->f)
			// 4 types of blocks, to compress a bit:
			// 255 (2): same as previous block + count
			// 254 (3): same as previous, except light // deprecated
			// SOLID (5)
			// anything else (9)
		// 4 types of blocks, to compress a bit:
		// 255 (2): same as previous block + count
		// 254 (3): same as previous, except light // deprecated
		// SOLID (5)
		// anything else (9)

			if (SOLID(s)) {
				if (t && c(type) && c(wtex) && c(vdelta)) {
					sc++;
				} else {
					spurge;
					gzputc(f, s->type);
					gzputc(f, s->wtex);
					gzputc(f, s->vdelta);
				}
			} else {
				if (t && c(type) && c(floor) && c(ceil) &&
				    c(ctex) && c(ftex) && c(utex) && c(wtex) &&
				    c(vdelta) && c(tag)) {
					sc++;
				} else {
					spurge;
					gzputc(f, s->type);
					gzputc(f, s->floor);
					gzputc(f, s->ceil);
					gzputc(f, s->wtex);
					gzputc(f, s->ftex);
					gzputc(f, s->ctex);
					gzputc(f, s->vdelta);
					gzputc(f, s->utex);
					gzputc(f, s->tag);
				}
			}
			t = s;
		}
		spurge;
		gzclose(f);
		conoutf(@"wrote map file %@", cgzname);
		settagareas();
		if (SOLID(s)) {
			if (t && c(type) && c(wtex) && c(vdelta)) {
				sc++;
			} else {
				spurge;
				gzputc(f, s->type);
				gzputc(f, s->wtex);
				gzputc(f, s->vdelta);
			}
		} else {
			if (t && c(type) && c(floor) && c(ceil) && c(ctex) &&
			    c(ftex) && c(utex) && c(wtex) && c(vdelta) &&
			    c(tag)) {
				sc++;
			} else {
				spurge;
				gzputc(f, s->type);
				gzputc(f, s->floor);
				gzputc(f, s->ceil);
				gzputc(f, s->wtex);
				gzputc(f, s->ftex);
				gzputc(f, s->ctex);
				gzputc(f, s->vdelta);
				gzputc(f, s->utex);
				gzputc(f, s->tag);
			}
		}
		t = s;
	}
	spurge;
	gzclose(f);
	conoutf(@"wrote map file %@", cgzname);
	settagareas();
	}
}
COMMANDN(savemap, save_world, ARG_1STR)

void
load_world(OFString *mname) // still supports all map formats that have existed
                            // since the earliest cube betas!
{
	@autoreleasepool {
		stopifrecording();
		cleardlights();
		pruneundos();
		setnames(mname);
		gzFile f = gzopen(
		    [cgzname cStringWithEncoding:OFLocale.encoding], "rb9");
		if (!f) {
			conoutf(@"could not read map %@", cgzname);
			return;
		}
		gzread(f, &hdr, sizeof(header) - sizeof(int) * 16);
		endianswap(&hdr.version, sizeof(int), 4);
		if (strncmp(hdr.head, "CUBE", 4) != 0)
			fatal(@"while reading map: header malformatted");
		if (hdr.version > MAPVERSION)
			fatal(@"this map requires a newer version of cube");
		if (sfactor < SMALLEST_FACTOR || sfactor > LARGEST_FACTOR)
			fatal(@"illegal map size");
		if (hdr.version >= 4) {
			gzread(f, &hdr.waterlevel, sizeof(int) * 16);
			endianswap(&hdr.waterlevel, sizeof(int), 16);
		} else {
			hdr.waterlevel = -100000;
		}
		ents.setsize(0);
		loopi(hdr.numents)
		{
			entity &e = ents.add();
			gzread(f, &e, sizeof(persistent_entity));
			endianswap(&e, sizeof(short), 4);
			e.spawned = false;
			if (e.type == LIGHT) {
				if (!e.attr2)
	stopifrecording();
	cleardlights();
	pruneundos();
	setnames(mname);
	gzFile f =
	    gzopen([cgzname cStringWithEncoding:OFLocale.encoding], "rb9");
	if (!f) {
		conoutf(@"could not read map %@", cgzname);
		return;
	}
	gzread(f, &hdr, sizeof(header) - sizeof(int) * 16);
	endianswap(&hdr.version, sizeof(int), 4);
	if (strncmp(hdr.head, "CUBE", 4) != 0)
		fatal(@"while reading map: header malformatted");
	if (hdr.version > MAPVERSION)
		fatal(@"this map requires a newer version of cube");
	if (sfactor < SMALLEST_FACTOR || sfactor > LARGEST_FACTOR)
		fatal(@"illegal map size");
	if (hdr.version >= 4) {
		gzread(f, &hdr.waterlevel, sizeof(int) * 16);
		endianswap(&hdr.waterlevel, sizeof(int), 16);
	} else {
		hdr.waterlevel = -100000;
	}
	ents.setsize(0);
	loopi(hdr.numents)
	{
		entity &e = ents.add();
		gzread(f, &e, sizeof(persistent_entity));
		endianswap(&e, sizeof(short), 4);
		e.spawned = false;
		if (e.type == LIGHT) {
			if (!e.attr2)
					e.attr2 =
					    255; // needed for MAPVERSION<=2
				if (e.attr1 > 32)
					e.attr1 = 32; // 12_03 and below
			}
		}
		free(world);
		setupworld(hdr.sfactor);
		char texuse[256];
		loopi(256) texuse[i] = 0;
		sqr *t = NULL;
		loopk(cubicsize)
		{
			sqr *s = &world[k];
			int type = gzgetc(f);
			switch (type) {
			case 255: {
				int n = gzgetc(f);
				for (int i = 0; i < n; i++, k++)
					memcpy(&world[k], t, sizeof(sqr));
				k--;
				break;
			}
			case 254: // only in MAPVERSION<=2
			{
				memcpy(s, t, sizeof(sqr));
				s->r = s->g = s->b = gzgetc(f);
				gzgetc(f);
				break;
			}
			case SOLID: {
				s->type = SOLID;
				s->wtex = gzgetc(f);
				s->vdelta = gzgetc(f);
				if (hdr.version <= 2) {
					gzgetc(f);
					gzgetc(f);
				}
				s->ftex = DEFAULT_FLOOR;
				s->ctex = DEFAULT_CEIL;
				s->utex = s->wtex;
				s->tag = 0;
				s->floor = 0;
				s->ceil = 16;
				break;
			}
			default: {
				if (type < 0 || type >= MAXTYPE) {
					OFString *t = [OFString
					    stringWithFormat:@"%d @ %d", type,
				e.attr2 = 255; // needed for MAPVERSION<=2
			if (e.attr1 > 32)
				e.attr1 = 32; // 12_03 and below
		}
	}
	free(world);
	setupworld(hdr.sfactor);
	char texuse[256];
	loopi(256) texuse[i] = 0;
	sqr *t = NULL;
	loopk(cubicsize)
	{
		sqr *s = &world[k];
		int type = gzgetc(f);
		switch (type) {
		case 255: {
			int n = gzgetc(f);
			for (int i = 0; i < n; i++, k++)
				memcpy(&world[k], t, sizeof(sqr));
			k--;
			break;
		}
		case 254: // only in MAPVERSION<=2
		{
			memcpy(s, t, sizeof(sqr));
			s->r = s->g = s->b = gzgetc(f);
			gzgetc(f);
			break;
		}
		case SOLID: {
			s->type = SOLID;
			s->wtex = gzgetc(f);
			s->vdelta = gzgetc(f);
			if (hdr.version <= 2) {
				gzgetc(f);
				gzgetc(f);
			}
			s->ftex = DEFAULT_FLOOR;
			s->ctex = DEFAULT_CEIL;
			s->utex = s->wtex;
			s->tag = 0;
			s->floor = 0;
			s->ceil = 16;
			break;
		}
		default: {
			if (type < 0 || type >= MAXTYPE) {
				OFString *t = [OFString
				    stringWithFormat:@"%d @ %d", type, k];
					    k];
					fatal(@"while reading map: type out of "
					      @"range: ",
					    t);
				}
				s->type = type;
				s->floor = gzgetc(f);
				s->ceil = gzgetc(f);
				if (s->floor >= s->ceil)
					s->floor = s->ceil - 1; // for pre 12_13
				s->wtex = gzgetc(f);
				s->ftex = gzgetc(f);
				s->ctex = gzgetc(f);
				if (hdr.version <= 2) {
					gzgetc(f);
					gzgetc(f);
				}
				s->vdelta = gzgetc(f);
				fatal(@"while reading map: type out of "
				      @"range: ",
				    t);
			}
			s->type = type;
			s->floor = gzgetc(f);
			s->ceil = gzgetc(f);
			if (s->floor >= s->ceil)
				s->floor = s->ceil - 1; // for pre 12_13
			s->wtex = gzgetc(f);
			s->ftex = gzgetc(f);
			s->ctex = gzgetc(f);
			if (hdr.version <= 2) {
				gzgetc(f);
				gzgetc(f);
			}
			s->vdelta = gzgetc(f);
				s->utex =
				    (hdr.version >= 2) ? gzgetc(f) : s->wtex;
				s->tag = (hdr.version >= 5) ? gzgetc(f) : 0;
				s->type = type;
			}
			}
			s->defer = 0;
			t = s;
			texuse[s->wtex] = 1;
			if (!SOLID(s))
				texuse[s->utex] = texuse[s->ftex] =
			s->utex = (hdr.version >= 2) ? gzgetc(f) : s->wtex;
			s->tag = (hdr.version >= 5) ? gzgetc(f) : 0;
			s->type = type;
		}
		}
		s->defer = 0;
		t = s;
		texuse[s->wtex] = 1;
		if (!SOLID(s))
			texuse[s->utex] = texuse[s->ftex] = texuse[s->ctex] = 1;
				    texuse[s->ctex] = 1;
		}
		gzclose(f);
		calclight();
		settagareas();
		int xs, ys;
		loopi(256) if (texuse) lookuptexture(i, &xs, &ys);
		conoutf(@"read map %@ (%d milliseconds)", cgzname,
		    SDL_GetTicks() - lastmillis);
		conoutf(@"%s", hdr.maptitle);
		startmap(mname);
		loopl(256)
		{
			// can this be done smarter?
			OFString *aliasname =
			    [OFString stringWithFormat:@"level_trigger_%d", l];
			if (identexists(aliasname))
				alias(aliasname, @"");
		}
		OFIRI *gameDataIRI = Cube.sharedInstance.gameDataIRI;
		execfile([gameDataIRI IRIByAppendingPathComponent:
		                          @"data/default_map_settings.cfg"]);
		execfile([gameDataIRI IRIByAppendingPathComponent:pcfname]);
		execfile([gameDataIRI IRIByAppendingPathComponent:mcfname]);
	}
	gzclose(f);
	calclight();
	settagareas();
	int xs, ys;
	loopi(256) if (texuse) lookuptexture(i, &xs, &ys);
	conoutf(@"read map %@ (%d milliseconds)", cgzname,
	    SDL_GetTicks() - lastmillis);
	conoutf(@"%s", hdr.maptitle);
	startmap(mname);
	loopl(256)
	{
		// can this be done smarter?
		OFString *aliasname =
		    [OFString stringWithFormat:@"level_trigger_%d", l];
		if (identexists(aliasname))
			alias(aliasname, @"");
	}
	OFIRI *gameDataIRI = Cube.sharedInstance.gameDataIRI;
	execfile([gameDataIRI
	    IRIByAppendingPathComponent:@"data/default_map_settings.cfg"]);
	execfile([gameDataIRI IRIByAppendingPathComponent:pcfname]);
	execfile([gameDataIRI IRIByAppendingPathComponent:mcfname]);
	}
}