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
+ (Cube *)sharedInstance
{
	return (Cube *)OFApplication.sharedApplication.delegate;
}

- (void)applicationDidFinishLaunching:(OFNotification *)notification
{

	bool dedicated, windowed;
	int par = 0, uprate = 0, maxcl = 4;
	OFString *__autoreleasing sdesc, *__autoreleasing ip;
	OFString *__autoreleasing master, *__autoreleasing passwd;

	processInitQueue();

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

	const OFOptionsParserOption options[] = { { 'd', @"dedicated", 0,
		                                      &dedicated, NULL },
		{ 't', @"window", 0, &windowed, NULL },
		{ 'w', @"width", 1, NULL, NULL },
		{ 'h', @"height", 1, NULL, NULL },
		{ 'u', @"upload-rate", 1, NULL, NULL },
		{ 'n', @"server-desc", 1, NULL, &sdesc },
		{ 'i', @"ip", 1, NULL, &ip },
		{ 'm', @"master", 1, NULL, &master },
		{ 'p', @"password", 1, NULL, &passwd },
		{ 'c', @"max-clients", 1, NULL, NULL },
		{ '\0', nil, 0, NULL, NULL } };

	OFOptionsParser *optionsParser =
	    [OFOptionsParser parserWithOptions:options];
	OFUnichar option;
	while ((option = [optionsParser nextOption]) != '\0') {
		switch (option) {
		case 'w':
			_width = optionsParser.argument.intValue;
			break;
		case 'h':
			_height = optionsParser.argument.intValue;
			break;
		case 'u':
			uprate = optionsParser.argument.intValue;
			break;
		case 'c':
			maxcl = optionsParser.argument.intValue;
			break;
		case ':':
		case '=':
		case '?':
			conoutf(@"unknown commandline option");
			[OFApplication terminateWithStatus:1];
		}
	}

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


	_gameDataIRI = [OFFileManager.defaultManager currentDirectoryIRI];

	_userDataIRI = [OFFileManager.defaultManager currentDirectoryIRI];

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

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

	initPlayers();

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

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

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

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

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

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

	log(@"video: mode");
	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
	if ((_window = SDL_CreateWindow("cube engine", SDL_WINDOWPOS_UNDEFINED,
	         SDL_WINDOWPOS_UNDEFINED, _width, _height,

	         SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL |
	             (!windowed ? SDL_WINDOW_FULLSCREEN : 0))) == NULL ||

	    SDL_GL_CreateContext(_window) == NULL)
		fatal(@"Unable to create OpenGL screen");

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

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

	log(@"basetex");
	int xs, ys;
	if (!installtex(2,

	        [_gameDataIRI IRIByAppendingPathComponent:@"data/newchars.png"],
	        &xs, &ys, false) ||
	    !installtex(3,
	        [_gameDataIRI
	            IRIByAppendingPathComponent:@"data/martin/base.png"],
	        &xs, &ys, false) ||
	    !installtex(6,
	        [_gameDataIRI
	            IRIByAppendingPathComponent:@"data/martin/ball1.png"],
	        &xs, &ys, false) ||
	    !installtex(7,
	        [_gameDataIRI
	            IRIByAppendingPathComponent:@"data/martin/smoke.png"],
	        &xs, &ys, false) ||
	    !installtex(8,
	        [_gameDataIRI
	            IRIByAppendingPathComponent:@"data/martin/ball2.png"],
	        &xs, &ys, false) ||
	    !installtex(9,
	        [_gameDataIRI
	            IRIByAppendingPathComponent:@"data/martin/ball3.png"],
	        &xs, &ys, false) ||
	    !installtex(4,
	        [_gameDataIRI
	            IRIByAppendingPathComponent:@"data/explosion.jpg"],
	        &xs, &ys, false) ||
	    !installtex(5,

	        [_gameDataIRI IRIByAppendingPathComponent:@"data/items.png"],
	        &xs, &ys, false) ||
	    !installtex(1,
	        [_gameDataIRI
	            IRIByAppendingPathComponent:@"data/crosshair.png"],
	        &xs, &ys, false))
		fatal(@"could not find core textures (hint: run cube from the "
		      @"parent of the bin directory)");

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

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

		execfile([_gameDataIRI
		    IRIByAppendingPathComponent:@"data/defaults.cfg"]);
	exec(@"autoexec.cfg");

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

	log(@"mainloop");

	int ignore = 5;
	OFDate *past = [OFDate date];

	for (;;) {

		[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));

		cleardlights();
		updateworld(millis);

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

		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();

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

		gl_drawframe(_width, _height, fps);

		SDL_Event event;
		int lasttype = 0, lastbut = 0;
		while (SDL_PollEvent(&event)) {
			switch (event.type) {
			case SDL_QUIT:
				[self quit];
				break;
			case SDL_KEYDOWN:
			case SDL_KEYUP:
				if (_repeatsKeys || event.key.repeat == 0)

					keypress(event.key.keysym.sym,
					    event.key.state == SDL_PRESSED);

				break;
			case SDL_TEXTINPUT:
				@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;

				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();







>
|
|
|
|

|


|

|
|
|
|
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|

>
|
>
|

|
|
|
|
|
|
|
|

|
|

|

|
|
|

|
|
|

|
|

|
|
|

|
|

|
|
|
|
|
|
|
|

|
|
|
|
>
|
|
>
|
|

|
|
|
|

|
|

|
|
|
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|

|
|

|
|
|
|
|
|
|
|
|
>
|
|
|

|
|
|
|

|
>
|

>

>
|

|
|
|
|
|
|
|

|
|

|
|

|
|

|
|
|
|
|

|
|
|
|
|
|
|

|

|
|
|
|
|
|
|
|
|
|
>
|
|
>
|
|
<

<
|
|
|
|
|
|
|
>
|
|
|
|
|
|
>
|

|
|
|
|
|
|
|
|
|
<







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;

		processInitQueue();

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

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

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

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

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

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

		initPlayers();

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

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

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

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

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

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

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

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

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

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

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

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

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

		log(@"mainloop");
	}

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

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

			cleardlights();
			updateworld(millis);

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

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

			computeraytable(player1.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;
			}

			gl_drawframe(_width, _height, fps);

			SDL_Event event;
			int lasttype = 0, lastbut = 0;
			while (SDL_PollEvent(&event)) {
				switch (event.type) {
				case SDL_QUIT:
					[self quit];
					break;
				case SDL_KEYDOWN:
				case SDL_KEYUP:
					if (_repeatsKeys ||
					    event.key.repeat == 0)
						keypress(event.key.keysym.sym,
						    event.key.state ==
						        SDL_PRESSED);
					break;
				case SDL_TEXTINPUT:

					input(@(event.text.text));

					break;
				case SDL_MOUSEMOTION:
					if (ignore) {
						ignore--;
						break;
					}
					mousemove(event.motion.xrel,
					    event.motion.yrel);
					break;
				case SDL_MOUSEBUTTONDOWN:
				case SDL_MOUSEBUTTONUP:
					if (lasttype == event.type &&
					    lastbut == event.button.button)
						// why?? get event twice without
						// it
						break;

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

}

- (void)applicationWillTerminate:(OFNotification *)notification
{
	stop();
	disconnect(true);
	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
				memcpy(dest,
				    (char *)image->pixels +
				        3 * _width * (_height - 1 - idx),
				    3 * _width);
				endianswap(dest, 3, _width);
			}

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

		SDL_FreeSurface(image);
	}
}








<
|
<
|
|
|
<
|
|
<







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);
			}


			OFString *path = [OFString

			    stringWithFormat:@"screenshots/screenshot_%d.bmp",
			    lastmillis];
			SDL_SaveBMP(temp,

			    [_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
		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;
		}

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

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

		if (header.magic != 844121161 || header.version != 8)
			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];

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

		_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);

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

		[stream close];

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

		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;







<
|
|
|
|
>
|
|
|

|
|

|
|
<
|

|
|

|
|
|

|
|
|

|
<
|

|
|
|

|
|
|
|

|
|
|
|
|

|

|
|

|
<







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
{

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

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

	md2_header header;
	[stream readIntoBuffer:&header exactLength:sizeof(md2_header)];

	endianswap(&header, sizeof(int), sizeof(md2_header) / sizeof(int));

	if (header.magic != 844121161 || header.version != 8)
		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];

	for (int i = 0; i < header.numFrames; ++i)

		endianswap(_frames + i * header.frameSize, sizeof(float), 6);

	_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);

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

	[stream close];

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

	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
	enet_peer_throttle_configure(clienthost->peers,
	    throttle_interval * 1000, throttle_accel, throttle_decel);
}

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

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

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

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

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

		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) <
		    0) {
			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);







<
|

|
|

|
<






<
|

|
|

|
<


















<
|
<
|
|
<







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)
{

	c2sinit = false;

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

	player1.name = name;

}
COMMANDN(name, newname, ARG_1STR)

void
newteam(OFString *name)
{

	c2sinit = false;

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

	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 };

	if (enet_address_set_host(&address, servername.UTF8String) < 0) {

		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
		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
			        // 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,
			// state:3
			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
				// frames
				if (*(int *)[msg itemAtIndex:1])
					packet->flags =
					    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
	}
}

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







<
|
|
<
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<
|
|
|
|

|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<
|
|
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<







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)
{

	if (clientnum < 0)
		return; // we haven't had a welcome message from the 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;
	// 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

		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 frames

			if (*(int *)[msg itemAtIndex:1])
				packet->flags = 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

}

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
}

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)];

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

		[scoreLines addObject: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;
			}
		}

		if (teamsUsed == maxTeams)
			return;

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

void
renderscores()
{
	if (!scoreson)
		return;







<
|
|
|
|
|
|

|
|

|

|
<










<
|
|
|
|
|
|

|
|

|
|
<







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)
{

	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];

	[scoreLines addObject: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)
{

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

	if (teamsUsed == maxTeams)
		return;

	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
		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);
		}
	}
}

// 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);
	}
}

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







<
|
<








<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<







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, @"");

		menumanual(0, scoreLines.count + 1, teamScores);

	}
}

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

void
sendmap(OFString *mapname)
{

	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
			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 "
					        @"password "
					        @"to join this server!");
					disconnect();
					return;
				}
			}
			if (getint(p) == 1)
				conoutf(@"server is FULL, disconnecting..");
			break;
		}

		case SV_POS: {







<
|
|
|
<
|
|
|
<







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();

			if (text[0] &&
			    strcmp(text, clientpassword.UTF8String)) {
				conoutf(@"you need to set the correct password "

				        @"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
		case SV_TEXT:
			sgetstr();
			conoutf(@"%@:\f %s", d.name, text);
			break;

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

		case SV_ITEMLIST: {
			int n;
			if (mapchanged) {
				senditemstoserver = false;







<
|
<







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();

			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
			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);
			}
			break;
		}

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







<
|
|
|
|
<







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);

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

// parse single argument, including expressions
char *
parseword(char *&p)







<
|
|
<
|
|
|
|
|
<







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 == '(') {

		OFString *t;
		@try {

			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
	char *word = p;
	p += strcspn(p, "; \t\r\n\0");
	if (p - word == 0)
		return NULL;
	return strndup(word, p - word);
}


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

		if ([identifier isKindOfClass:Variable.class]) {
			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,







>

|

<
|
<

|
<
|
|
|
<







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)
{

	__kindof Identifier *identifier = identifiers[[n substringFromIndex:1]];


	if ([identifier isKindOfClass:Variable.class]) {

		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
	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);
				}
			}

			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);
		}

		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.length == 1)
			return;

		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(
				                                1, s.length - 1)
				                 withString:identifier.name];
		}];

		completeidx++;

		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;
		}

		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);
	}
}

void
writecfg()
{
	OFStream *stream;
	@try {







<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
|
|

|
|
|

|
<















<
|
|

|
|

|
|
|
|

|
|
|
|
|
|
|
<
|
|

|

|
|
<





<
|
|
|
|
|
|
|
|

|
|
<





<
|
|
|
|
|
<







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)
{

	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;

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

	return val;

}

// tab-completion of all identifiers

int completesize = 0, completeidx = 0;

void
resetcomplete()
{
	completesize = 0;
}

void
complete(OFMutableString *s)
{

	if (![s hasPrefix:@"/"])
		[s insertString:@"/" atIndex:0];

	if (s.length == 1)
		return;

	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(1, s.length - 1)

			                 withString:identifier.name];
	}];

	completeidx++;

	if (completeidx >= idx)
		completeidx = 0;

}

bool
execfile(OFIRI *cfgfile)
{

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

	execute(command);
	return true;

}

void
exec(OFString *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
// 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]);
	}
}

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;

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

void
whilea(OFString *cond, OFString *body)
{
	while (execute(cond))







<
|
<











<
|

|
|
|
|
<







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)
{

	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)
{

	int t = times.cube_intValue;

	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
{
	concat([s stringByReplacingOccurrencesOfString:@" " withString:@""]);
}

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

		if (!*a)
			return 0;

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

		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));
	}
}

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







<
|

|
|

|
|
|
|

|
<





<
|
|
|
|
|
|
<







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_)
{

	const char *a = a_.UTF8String;

	if (!*a)
		return 0;

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

	return n + 1;

}

void
at(OFString *s_, OFString *pos)
{

	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
	fflush(stdout);
#endif
}

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

		OFString *string = [[OFString alloc] initWithFormat:format
		                                          arguments: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);
	}
}

// render buffer taking into account time & scrolling
void
renderconsole()
{
	int nd = 0;







<
|
|

|
|

|

|
|
|
|
|
|
<







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, ...)
{

	va_list arguments;
	va_start(arguments, format);

	OFString *string = [[OFString alloc] initWithFormat:format
	                                          arguments: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);

}

// 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
	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);
	}
}
COMMAND(mapmsg, ARG_1STR)

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

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

void
history(int n)







<
|
|
<






<
|
<







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)
{

	memset(hdr.maptitle, '\0', sizeof(hdr.maptitle));
	strncpy(hdr.maptitle, s.UTF8String, 127);

}
COMMAND(mapmsg, ARG_1STR)

void
pasteconsole()
{

	[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

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

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







<
|
|
|
|

|
|
|
|
|
<
<
|
<







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) {

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

					if (vhistory.count == 0 ||
					    ![vhistory.lastObject
					        isEqual:commandbuf]) {
						// cap this?
						[vhistory addObject:[commandbuf


						                        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
	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]);
	}
}

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







<
|
|
|
<







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;

	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
	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,
				    (float)mmi.rad, e.x,
				    (float)S(e.x, e.y)->floor + mmi.zoff +
				        e.attr3,
				    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)







<
|
<
|
<
<
|
|
<







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;

			rendermodel(mmi.name, 0, 1, e.attr4, (float)mmi.rad,

			    e.x, (float)S(e.x, e.y)->floor + mmi.zoff + e.attr3,


			    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
}

void refreshservers();

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

		if (vmenu == 1)
			refreshservers();

		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;
	}
}

void
newmenu(OFString *name)
{
	if (menus == nil)
		menus = [[OFMutableArray alloc] init];







<
|
|
|
|

|
|

|
|
|
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<







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()
{

	if (vmenu < 0) {
		[menuStack removeAllObjects];
		return false;
	}

	if (vmenu == 1)
		refreshservers();

	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;

}

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
			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) {
				@autoreleasepool {
					connects(getservername(menusel));
				}
			}

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

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

			execute(action, true);
		}
	}

	return true;
}







|
<
|
<
<













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)

				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
		    getvar(@"selxs"), getvar(@"selys")];
	}
}

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

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

		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",
			              basename, side[i]];

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

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

float cursordepth = 0.9f;
GLint viewport[4];
GLdouble mm[16], pm[16];
OFVector3D worldpos;







<
|

|
|

|
|

|
|
|
|
|
|
|
<

|
|
|
|
|
|
|

|
<







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)
{

	static OFString *lastsky = @"";

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

	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", basename, side[i]];


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

	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
		dblend -= curtime / 3;
		if (dblend < 0)
			dblend = 0;
	}

	glEnable(GL_TEXTURE_2D);

	@autoreleasepool {
		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);
	}

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







<
|
|

|
|
|
|
|
|
<







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);


	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);


	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
	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;
		}

		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;
			}

			@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);
			}
		}

#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);
		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;
		}

		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 (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);

		SDL_FreeSurface(s);

		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;







<
<
|
|
|
|
|

|
|
|
|
<
|
|
|
|

|
|
|
|
|
|
|
|
|

|
|
|
|
|
|








|
|
|
|
|
|
<
|
|
|
|

|
|
|
|
|
|

|

|
|
|
|
|
|
|

|
|
|

|
|

|

|
<







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)
{


	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) {

			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);
		}
	}

#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);

	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;
	}

	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 (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);

	SDL_FreeSurface(s);

	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
	curtexnum = 0;
}
COMMAND(texturereset, ARG_NONE)

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

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

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

int
lookuptexture(int tex, int *xs, int *ys)
{
	int frame = 0; // other frames?







<
|

|
|

|
<
|
|
<







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)
{

	int num = curtexnum++, frame = aframe.cube_intValue;

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

	mapping[num][frame] = 1;

	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

	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]];

		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);







<
|
|

|
|
|
|
|
|
|
|
|
|
|
<







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];


	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

	}
}

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

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];

			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;
		}
	}
}

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

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

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

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

		mdllookup[name] = m;

		return m;
	}
}

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







<
|
|
|
|

<
|
|
|

<
|
|
|
|
<






<
|

|
|
|

|
|
|
<
<
<
<
|

|
|

|

|
<







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) {

		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 *IRI2 = [baseIRI IRIByAppendingPathComponent:@"skin.jpg"];
		int xs, ys;
		installtex(FIRSTMDL + m.mdlnum, IRI2, &xs, &ys, false);
		m.loaded = true;

	}
}

MD2 *
loadmodel(OFString *name)
{

	static int modelnum = 0;

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

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




	m.loadname = name;

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

	mdllookup[name] = 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
	{ 270, 448, 310, 512 }, //}
	{ 310, 448, 363, 512 }, //~
};

int
text_width(OFString *string)
{
	@autoreleasepool {
		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;
			}

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

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

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

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

		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);
	}
}

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);

		int x = left;
		int y = top;

		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];

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

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

			if (c == ' ') {
				x += FONTH / 2;
				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_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();

			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,







<
|
|

|
|
|
|
|
|
|

|
|

|
|
|
|

|
|
|

|
|
|

|
<





<
|
|
|
|
|
|
<





<
|
|
|

|
|

|
|
|

|
|
|
|

|
|
<
<
|
|

|
|
|
|

|
|
|
|

|
|
|

|
|
|
|

|
|

|
|
|
|
|
|
|
|
|
|

|
|
<







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)
{

	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;
		}

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

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

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

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

	return x;

}

void
draw_textf(OFConstantString *format, int left, int top, int 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)
{

	glBlendFunc(GL_ONE, GL_ONE);
	glBindTexture(GL_TEXTURE_2D, gl_num);
	glColor3ub(255, 255, 255);

	int x = left;
	int y = top;

	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];

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


			continue;
		}

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

		if (c == ' ') {
			x += FONTH / 2;
			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_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();

		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
	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);
		}
	}
}

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);
	}
}
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;
		}

		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));
		}
		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);
	}
}
COMMAND(loadgame, ARG_1STR)

void
loadgameout()
{
	stop();







<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<











<
<
|
|
|
|
|
|
<






<
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
|
|
|
|
<
|
|
|
<
|
|
|
|
|
<





<
<
|
|
|
|
<







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)
{

	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;
	}



	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)
{

	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();

	// 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();

}

void
loadgame(OFString *name)
{


	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
		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;
	}
}
COMMAND(record, ARG_1STR)

void
demodamage(int damage, const OFVector3D &o)
{
	ddamage = damage;







<
<
|
|
|
|
|
|
|
|
|
<







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;



	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
		// 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;
	}
}
COMMAND(demo, ARG_1STR)

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







<
<
|
|
|
|
|
<







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)
{


	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
				no++;
		}
	}

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

	@autoreleasepool {
		OFString *msg =
		    [OFString 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;







<
|
<
|
|
|
<







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


	OFString *msg = [OFString

	    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
		switch ((type = getint(p))) {
		case SV_TEXT:
			sgetstr();
			break;

		case SV_INITC2S:
			sgetstr();
			@autoreleasepool {
				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;
			}
			mapreload = false;
			mode = reqmode;
			minremain = mode & 1 ? 15 : 10;
			mapend = lastsec + minremain * 60;
			interm = 0;
			@autoreleasepool {
				smapname = @(text);
			}
			resetitems();
			sender = -1;
			break;
		}

		case SV_ITEMLIST: {
			int n;







<
|
<









<
|
|
|
<





<
|
<







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();

			clients[cn].name = @(text);

			sgetstr();
			getint(p);
			break;

		case SV_MAPCHANGE: {
			sgetstr();
			int reqmode = getint(p);
			if (reqmode < 0)
				reqmode = 0;

			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;

			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
			loopi(size - 2) getint(p);
			break;
		}

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

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








<
|
<







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);

			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
	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);
	}
	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);







<
|
<







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);

	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
		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");
			}
			[OFStdOut
			    writeFormat:@"client connected (%@)\n", c.hostname];
			send_welcome(lastconnect = clients.count - 1);
			break;
		}
		case ENET_EVENT_TYPE_RECEIVE:
			brec += event.packet->dataLength;







<
<
|
|
|
|
<







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];


			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

	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");
		}
		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 (;;)


			serverslice(/*enet_time_get_sec()*/ time(NULL), 5);
	}
}








<
|
|
|
<


















>
>
|
|
|
>
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 };

		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(
				    /*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
{
	return servers[n].name;
}

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

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

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

void
pingservers()
{
	ENetBuffer buf;
	uchar ping[MAXTRANS];







<
|
|
|

|
|

<
|
<







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)
{

	for (ServerInfo *si in servers)
		if ([si.name isEqual:servername])
			return;

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


	[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
				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);
				}
				sgetstr();
				@autoreleasepool {
					si.sdesc = @(text);
				}
				break;
			}
		}
	}
}

void







<
|
<

<
|
<







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();

				si.map = @(text);

				sgetstr();

				si.sdesc = @(text);

				break;
			}
		}
	}
}

void
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
			    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];
		}

		menumanual(1, i, si.full);

		if (!--maxmenu)
			return;

		i++;







<
|
|
<







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

		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
	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));
		}
	}
	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);
	}
	fclose(f);
}







<
|
<















<
|
|
<


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];

		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");

	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
// 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);
		}
		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) {












<
|
<







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];

		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
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",
			    masterpath];
			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");
	}
	ENetBuffer eb;
	buf[0] = 0;
	eb.data = buf;
	eb.dataLength = buflen - 1;
	while (mssock != ENET_SOCKET_NULL)
		httpgetrecieve(eb);
	return stripheader(buf);







<
|
|
<
|
|
<



















<
|
|
|
|
<







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) {

		OFString *path = [OFString
		    stringWithFormat:@"%@register.do?action=add", masterpath];

		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)
{

	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
		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;

		if (listen) {
			ENetAddress address = { ENET_HOST_ANY,
				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");
		}
	}
}







<
|
|
|
|
|
|
|
|

|
|
<
|
|
|
|
|
|
<
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)
{

	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, 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");
	}
}

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
	} else
		return c;
}

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

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

		putint(p, 0);
	}
}

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







<
|

|
|

|
<







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)
{

	const char *t = t_.UTF8String;

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

	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
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];

#ifdef USE_MIXER
			if ((mod = Mix_LoadMUS(
			         IRI.fileSystemRepresentation.UTF8String)) !=
			    NULL) {
				Mix_PlayMusic(mod, -1);
				Mix_VolumeMusic((musicvol * MAXVOL) / 255);
			}
#else
			if ((mod = FMUSIC_LoadSong(
			         IRI.fileSystemRepresentation.UTF8String)) !=
			    NULL) {
				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 {
				conoutf(
				    @"could not play music: %@", IRI.string);
			}
#endif
		}
	}
}
COMMAND(music, ARG_1STR)

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







<
|
|
|
|
|
|


|
|
<
|
|
|

|
|
<
|
|
|
|
|
<
|
|
|
|
|
|
|
<
|
|

<







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) {

		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)) != NULL) {

			Mix_PlayMusic(mod, -1);
			Mix_VolumeMusic((musicvol * MAXVOL) / 255);
		}
#else
		if ((mod = FMUSIC_LoadSong(
		         IRI.fileSystemRepresentation.UTF8String)) != NULL) {

			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 {

			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
		return;

	settag(tag, type);

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

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

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

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

// main geometric mipmapping routine, recursively rebuild mipmaps within block







<
|
|

|
|
<







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);


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

	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
{
	int e = closestent();
	if (e < 0) {
		conoutf(@"no more entities");
		return;
	}
	int t = ents[e].type;
	@autoreleasepool {
		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;
	}
}

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,







<
|
<









<
|
|
|
<







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;

	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)
{

	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
}

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

static void
setnames(OFString *name)
{
	@autoreleasepool {
		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;
		}

		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];
	}
}

// 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).







<
|
|
|
|

|
|
|
|
|
|
|

|
|
<
|
|
|
|
|
|
<







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)
{

	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;
	}

	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];

}

// 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
// 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;
#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];
#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)

			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)
					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];
					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] =
				    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]);
	}
}







<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|











|
|
|

|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<







<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<
|
|
|
|
|
|
|
|
|
|
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<

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)
{

	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];
#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)

		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!
{

	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, 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);

			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;

	}
	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]);

}