Cube  Check-in [75e920ae30]

Overview
Comment:Switch from clang-format to manual formatting

clang-format does too many weird things.

Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 75e920ae307d96d6ce0141652617959f609c21b542e930cb537789298856c480
User & Date: js on 2025-03-29 14:25:43
Other Links: manifest | tags
Context
2025-03-29
17:13
More style fixes check-in: c634a689e7 user: js tags: trunk
14:25
Switch from clang-format to manual formatting check-in: 75e920ae30 user: js tags: trunk
13:01
Make more use of OFColor check-in: 932a90c261 user: js tags: trunk
Changes

Deleted src/.clang-format version [0d630f9489].

Modified src/Alias.h from [9b91901d8c] to [0fb6418ce5].

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







16
17
18
19
1
2
3
4
5
6
7
8







9
10
11
12
13
14
15
16
17
18
19








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




#import "Identifier.h"

OF_ASSUME_NONNULL_BEGIN

@interface Alias: Identifier
@property (direct, copy, nonatomic) OFString *action;
@property (readonly, nonatomic) bool persisted;

+ (instancetype)aliasWithName:(OFString *)name
                       action:(OFString *)action
                    persisted:(bool)persisted OF_DIRECT;
- (instancetype)initWithName:(OFString *)name OF_UNAVAILABLE;
- (instancetype)initWithName:(OFString *)name
                      action:(OFString *)action
                   persisted:(bool)persisted OF_DESIGNATED_INITIALIZER
+ (instancetype)aliasWithName: (OFString *)name
                       action: (OFString *)action
                    persisted: (bool)persisted OF_DIRECT;
- (instancetype)initWithName: (OFString *)name OF_UNAVAILABLE;
- (instancetype)initWithName: (OFString *)name
                      action: (OFString *)action
                   persisted: (bool)persisted OF_DESIGNATED_INITIALIZER
    OF_DIRECT;
@end

OF_ASSUME_NONNULL_END

Modified src/Alias.m from [dce3b067a2] to [fe2a009013].

1
2
3
4
5
6



7
8
9
10



11
12
13
14
15



16
17

18
19
20
21
22
23
24
1
2
3



4
5
6
7



8
9
10
11
12



13
14
15
16

17
18
19
20
21
22
23
24



-
-
-
+
+
+

-
-
-
+
+
+


-
-
-
+
+
+

-
+







#import "Alias.h"

@implementation Alias
+ (instancetype)aliasWithName:(OFString *)name
                       action:(OFString *)action
                    persisted:(bool)persisted;
+ (instancetype)aliasWithName: (OFString *)name
                       action: (OFString *)action
                    persisted: (bool)persisted;
{
	return [[self alloc] initWithName:name
	                           action:action
	                        persisted:persisted];
	return [[self alloc] initWithName: name
	                           action: action
	                        persisted: persisted];
}

- (instancetype)initWithName:(OFString *)name
                      action:(OFString *)action
                   persisted:(bool)persisted
- (instancetype)initWithName: (OFString *)name
                      action: (OFString *)action
                   persisted: (bool)persisted
{
	self = [super initWithName:name];
	self = [super initWithName: name];

	_action = [action copy];
	_persisted = persisted;

	return self;
}
@end

Modified src/Command.h from [710ad3a2bf] to [f6eb8c644d].

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









14
15
16
17
18
19
20
21
22
23
24
25
26
27








28
29
30
1
2
3
4









5
6
7
8
9
10
11
12
13
14
15
16
17
18
19








20
21
22
23
24
25
26
27
28
29
30




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






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



#import "Identifier.h"

OF_ASSUME_NONNULL_BEGIN

#define COMMAND(name, nargs, block_)                          \
	OF_CONSTRUCTOR()                                      \
	{                                                     \
		enqueueInit(^{                                \
			Identifier.identifiers[@ #name] =     \
			    [Command commandWithName:@ #name  \
			              argumentsTypes:nargs    \
			                       block:block_]; \
		});                                           \
#define COMMAND(name, nargs, block_)				\
	OF_CONSTRUCTOR()					\
	{							\
		enqueueInit(^ {					\
			Identifier.identifiers[@#name] =	\
			    [Command commandWithName: @#name	\
			              argumentsTypes: nargs	\
			                       block: block_];	\
		});						\
	}

OF_DIRECT_MEMBERS
@interface Command: Identifier
@property (readonly, nonatomic) int argumentsTypes;

+ (instancetype)commandWithName:(OFString *)name
                 argumentsTypes:(int)argumentsTypes
                          block:(id)block;
- (instancetype)initWithName:(OFString *)name OF_UNAVAILABLE;
- (instancetype)initWithName:(OFString *)name
              argumentsTypes:(int)argumentsTypes
                       block:(id)block OF_DESIGNATED_INITIALIZER;
- (int)callWithArguments:(OFArray<OFString *> *)arguments isDown:(bool)isDown;
+ (instancetype)commandWithName: (OFString *)name
                 argumentsTypes: (int)argumentsTypes
                          block: (id)block;
- (instancetype)initWithName: (OFString *)name OF_UNAVAILABLE;
- (instancetype)initWithName: (OFString *)name
              argumentsTypes: (int)argumentsTypes
                       block: (id)block OF_DESIGNATED_INITIALIZER;
- (int)callWithArguments: (OFArray<OFString *> *)arguments isDown: (bool)isDown;
@end

OF_ASSUME_NONNULL_END

Modified src/Command.m from [1956763843] to [254356cb16].

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

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







-
+










-
-
-
+
+
+

-
-
-
+
+
+


-
-
-
+
+
+

-
+







-
+






-
+






-
-
+
+






-
-
-
+
+
+






-
-
-
-
+
+
+
+







	OFMutableArray<OFString *> *copy;

	if (arguments.count >= count)
		return arguments;

	copy = [arguments mutableCopy];
	while (copy.count < count)
		[copy addObject:@""];
		[copy addObject: @""];

	[copy makeImmutable];
	return copy;
}

@implementation Command
{
	id _block;
}

+ (instancetype)commandWithName:(OFString *)name
                 argumentsTypes:(int)argumentsTypes
                          block:(id)block
+ (instancetype)commandWithName: (OFString *)name
                 argumentsTypes: (int)argumentsTypes
                          block: (id)block
{
	return [[self alloc] initWithName:name
	                   argumentsTypes:argumentsTypes
	                            block:block];
	return [[self alloc] initWithName: name
	                   argumentsTypes: argumentsTypes
	                            block: block];
}

- (instancetype)initWithName:(OFString *)name
              argumentsTypes:(int)argumentsTypes
                       block:(id)block
- (instancetype)initWithName: (OFString *)name
              argumentsTypes: (int)argumentsTypes
                       block: (id)block
{
	self = [super initWithName:name];
	self = [super initWithName: name];

	_argumentsTypes = argumentsTypes;
	_block = block;

	return self;
}

- (int)callWithArguments:(OFArray<OFString *> *)arguments isDown:(bool)isDown
- (int)callWithArguments: (OFArray<OFString *> *)arguments isDown: (bool)isDown
{
	switch (_argumentsTypes) {
	case ARG_1INT:
		if (isDown) {
			arguments = padArguments(arguments, 2);
			((void (^)(int))_block)(
			    [arguments[1] cube_intValueWithBase:0]);
			    [arguments[1] cube_intValueWithBase: 0]);
		}
		break;
	case ARG_2INT:
		if (isDown) {
			arguments = padArguments(arguments, 3);
			((void (^)(int, int))_block)(
			    [arguments[1] cube_intValueWithBase:0],
			    [arguments[2] cube_intValueWithBase:0]);
			    [arguments[1] cube_intValueWithBase: 0],
			    [arguments[2] cube_intValueWithBase: 0]);
		}
		break;
	case ARG_3INT:
		if (isDown) {
			arguments = padArguments(arguments, 4);
			((void (^)(int, int, int))_block)(
			    [arguments[1] cube_intValueWithBase:0],
			    [arguments[2] cube_intValueWithBase:0],
			    [arguments[3] cube_intValueWithBase:0]);
			    [arguments[1] cube_intValueWithBase: 0],
			    [arguments[2] cube_intValueWithBase: 0],
			    [arguments[3] cube_intValueWithBase: 0]);
		}
		break;
	case ARG_4INT:
		if (isDown) {
			arguments = padArguments(arguments, 5);
			((void (^)(int, int, int, int))_block)(
			    [arguments[1] cube_intValueWithBase:0],
			    [arguments[2] cube_intValueWithBase:0],
			    [arguments[3] cube_intValueWithBase:0],
			    [arguments[4] cube_intValueWithBase:0]);
			    [arguments[1] cube_intValueWithBase: 0],
			    [arguments[2] cube_intValueWithBase: 0],
			    [arguments[3] cube_intValueWithBase: 0],
			    [arguments[4] cube_intValueWithBase: 0]);
		}
		break;
	case ARG_NONE:
		if (isDown)
			((void (^)())_block)();
		break;
	case ARG_1STR:
150
151
152
153
154
155
156
157
158


159
160
161
162
163
164
150
151
152
153
154
155
156


157
158
159
160
161
162
163
164







-
-
+
+






			    arguments[1], arguments[2]);
		}
		break;
	case ARG_VARI:
		if (isDown)
			// limit, remove
			((void (^)(OFString *))_block)([[arguments
			    objectsInRange:OFMakeRange(1, arguments.count - 1)]
			    componentsJoinedByString:@" "]);
			    objectsInRange: OFMakeRange(1, arguments.count - 1)]
			    componentsJoinedByString: @" "]);
		break;
	}

	return 0;
}
@end

Modified src/ConsoleLine.h from [3048f3e388] to [7afa80390f].

1
2
3
4
5
6
7
8
9


10
1
2
3
4
5
6
7


8
9
10







-
-
+
+

#import <ObjFW/ObjFW.h>

OF_DIRECT_MEMBERS
@interface ConsoleLine: OFObject
@property (readonly, copy) OFString *text;
@property (readonly) int outtime;

+ (instancetype)lineWithText:(OFString *)text outtime:(int)outtime;
- (instancetype)initWithText:(OFString *)text outtime:(int)outtime;
+ (instancetype)lineWithText: (OFString *)text outtime: (int)outtime;
- (instancetype)initWithText: (OFString *)text outtime: (int)outtime;
@end

Modified src/ConsoleLine.m from [8c83ebcd46] to [558d4dd328].

1
2
3
4

5
6

7
8
9

10
11
12
13
14
15
16
1
2
3

4
5

6
7
8

9
10
11
12
13
14
15
16



-
+

-
+


-
+







#import "ConsoleLine.h"

@implementation ConsoleLine
+ (instancetype)lineWithText:(OFString *)text outtime:(int)outtime
+ (instancetype)lineWithText: (OFString *)text outtime: (int)outtime
{
	return [[self alloc] initWithText:text outtime:outtime];
	return [[self alloc] initWithText: text outtime: outtime];
}

- (instancetype)initWithText:(OFString *)text outtime:(int)outtime
- (instancetype)initWithText: (OFString *)text outtime: (int)outtime
{
	self = [super init];

	_text = [text copy];
	_outtime = outtime;

	return self;

Modified src/Cube.m from [37d676edf6] to [67dd855e73].

17
18
19
20
21
22
23
24

25
26
27
28
29
30
31
17
18
19
20
21
22
23

24
25
26
27
28
29
30
31







-
+







}

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

- (void)applicationDidFinishLaunching:(OFNotification *)notification
- (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;

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







-
+



















-
+
















-
-
+
+

-
-
+
+







			{ '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];
		    [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];
				[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];
		    [_userDataIRI IRIByAppendingPathComponent: @"demos"]
						createParents: true];
		[OFFileManager.defaultManager createDirectoryAtIRI:
		        [_userDataIRI IRIByAppendingPathComponent:@"savegames"]
		                                     createParents:true];
		    [_userDataIRI IRIByAppendingPathComponent: @"savegames"]
						createParents: true];

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

		initEntities();
		initPlayers();

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







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

-
+













-
+

-
+














-
+







		SDL_ShowCursor(0);

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

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

			Player *player1 = Player.player1;

			int millis = SDL_GetTicks() * gamespeed / 100;
			if (millis - lastmillis > 200)
				lastmillis = millis - 200;
			else if (millis - lastmillis < 1)
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
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







-
+











-
+





-
+







					break;
				}
			}
		}
	}
}

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

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

- (void)screenshot
{
	SDL_Surface *image;
	SDL_Surface *temp;
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
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







-
-
+
+
-

-
-
+
+










-
+









-
-
+
+


-
+

-
-
+
+



-
+



-
+


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

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

		SDL_FreeSurface(image);
	}
}

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

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

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

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

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

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

Modified src/DynamicEntity.h from [0ee26a40fb] to [0008508591].

29
30
31
32
33
34
35
36

37
38
39
40
29
30
31
32
33
34
35

36
37
38
39
40







-
+




@property (direct, readonly, nonatomic) int *ammo;
@property (direct, nonatomic) bool attacking;
// used by physics to signal ai
@property (direct, nonatomic) bool blocked, moving;
@property (direct, copy, nonatomic) OFString *name;

- (OFData *)dataBySerializing;
- (void)setFromSerializedData:(OFData *)data;
- (void)setFromSerializedData: (OFData *)data;
- (void)resetMovement;
// reset player state not persistent accross spawns
- (void)resetToSpawnState;
@end

Modified src/DynamicEntity.m from [7a1598ea36] to [ba58eb8bdf].

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







-
+







-
+









-
+


-
+







		.moving = _moving };

	for (int i = 0; i < NUMGUNS; i++)
		data.ammo[i] = _ammo[i];

	memcpy(data.name, _name.UTF8String, min(_name.UTF8StringLength, 259));

	if ([self isKindOfClass:Player.class]) {
	if ([self isKindOfClass: Player.class]) {
		Player *player = (Player *)self;
		data.lifeSequence = player.lifeSequence,
		data.frags = player.frags;
		memcpy(data.team, player.team.UTF8String,
		    min(player.team.UTF8StringLength, 259));
	}

	if ([self isKindOfClass:Monster.class]) {
	if ([self isKindOfClass: Monster.class]) {
		Monster *monster = (Monster *)self;
		data.monsterState = monster.monsterState;
		data.monsterType = monster.monsterType;
		data.targetYaw = monster.targetYaw;
		data.trigger = monster.trigger;
		data.attackTarget = monster.attackTarget;
		data.anger = monster.anger;
	}

	return [OFData dataWithItems:&data count:sizeof(data)];
	return [OFData dataWithItems: &data count: sizeof(data)];
}

- (void)setFromSerializedData:(OFData *)data
- (void)setFromSerializedData: (OFData *)data
{
	struct dynent d;

	if (data.count != sizeof(struct dynent))
		@throw [OFOutOfRangeException exception];

	memcpy(&d, data.items, data.count);
235
236
237
238
239
240
241
242

243
244

245
246
247
248
249
250
251

252
253
254
255
256
257
258
235
236
237
238
239
240
241

242
243

244
245
246
247
248
249
250

251
252
253
254
255
256
257
258







-
+

-
+






-
+








	for (int i = 0; i < NUMGUNS; i++)
		_ammo[i] = d.ammo[i];

	_blocked = d.blocked;
	_moving = d.moving;

	_name = [[OFString alloc] initWithUTF8String:d.name];
	_name = [[OFString alloc] initWithUTF8String: d.name];

	if ([self isKindOfClass:Player.class]) {
	if ([self isKindOfClass: Player.class]) {
		Player *player = (Player *)self;
		player.lifeSequence = d.lifeSequence;
		player.frags = d.frags;
		player.team = @(d.team);
	}

	if ([self isKindOfClass:Monster.class]) {
	if ([self isKindOfClass: Monster.class]) {
		Monster *monster = (Monster *)self;
		monster.monsterState = d.monsterState;
		monster.monsterType = d.monsterType;
		monster.targetYaw = d.targetYaw;
		monster.trigger = d.trigger;
		monster.attackTarget = d.attackTarget;
		monster.anger = d.anger;

Modified src/Identifier.h from [79fe1b6b9a] to [91de4ded4b].

1
2
3
4
5
6
7
8
9
10
11

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

11
12
13
14










-
+



#import <ObjFW/ObjFW.h>

OF_ASSUME_NONNULL_BEGIN

@interface Identifier: OFObject
@property (direct, readonly, copy, nonatomic) OFString *name;
@property (class, direct, readonly, nonatomic)
    OFMutableDictionary<OFString *, __kindof Identifier *> *identifiers;

- (instancetype)init OF_UNAVAILABLE;
- (instancetype)initWithName:(OFString *)name;
- (instancetype)initWithName: (OFString *)name;
@end

OF_ASSUME_NONNULL_END

Modified src/Identifier.m from [9cd9c4b220] to [eff0175544].

11
12
13
14
15
16
17
18

19
20
21
22
23
24
25
11
12
13
14
15
16
17

18
19
20
21
22
23
24
25







-
+







}

+ (OFMutableDictionary<OFString *, __kindof Identifier *> *)identifiers
{
	return identifiers;
}

- (instancetype)initWithName:(OFString *)name
- (instancetype)initWithName: (OFString *)name
{
	self = [super init];

	_name = [name copy];

	return self;
}

Modified src/KeyMapping.h from [3b8ccce865] to [7e5483fe4c].

1
2
3
4
5
6
7
8
9
10
11

12
13

14
15
16
1
2
3
4
5
6
7
8
9
10

11
12

13
14
15
16










-
+

-
+



#import <ObjFW/ObjFW.h>

OF_ASSUME_NONNULL_BEGIN

OF_DIRECT_MEMBERS
@interface KeyMapping: OFObject
@property (readonly) int code;
@property (readonly, nonatomic) OFString *name;
@property (copy, nonatomic) OFString *action;

+ (instancetype)mappingWithCode:(int)code name:(OFString *)name;
+ (instancetype)mappingWithCode: (int)code name: (OFString *)name;
- (instancetype)init OF_UNAVAILABLE;
- (instancetype)initWithCode:(int)code name:(OFString *)name;
- (instancetype)initWithCode: (int)code name: (OFString *)name;
@end

OF_ASSUME_NONNULL_END

Modified src/KeyMapping.m from [49eed66821] to [962ce788ca].

1
2
3
4

5
6

7
8
9

10
11
12
13
14
15
16
1
2
3

4
5

6
7
8

9
10
11
12
13
14
15
16



-
+

-
+


-
+







#import "KeyMapping.h"

@implementation KeyMapping
+ (instancetype)mappingWithCode:(int)code name:(OFString *)name
+ (instancetype)mappingWithCode: (int)code name: (OFString *)name
{
	return [[self alloc] initWithCode:code name:name];
	return [[self alloc] initWithCode: code name: name];
}

- (instancetype)initWithCode:(int)code name:(OFString *)name
- (instancetype)initWithCode: (int)code name: (OFString *)name
{
	self = [super init];

	_code = code;
	_name = [name copy];

	return self;

Modified src/MD2.h from [738081230a] to [25323c6c56].

8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26












27
28
29
8
9
10
11
12
13
14












15
16
17
18
19
20
21
22
23
24
25
26
27
28
29







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



@interface MD2: OFObject
@property (nonatomic) MapModelInfo *mmi;
@property (copy, nonatomic) OFString *loadname;
@property (nonatomic) int mdlnum;
@property (nonatomic) bool loaded;

+ (instancetype)md2;
- (bool)loadWithIRI:(OFIRI *)IRI;
- (void)renderWithLight:(OFColor *)light
                  frame:(int)frame
                  range:(int)range
               position:(OFVector3D)position
                    yaw:(float)yaw
                  pitch:(float)pitch
                  scale:(float)scale
                  speed:(float)speed
                   snap:(int)snap
               basetime:(int)basetime;
- (void)scaleWithFrame:(int)frame scale:(float)scale snap:(int)snap;
- (bool)loadWithIRI: (OFIRI *)IRI;
- (void)renderWithLight: (OFColor *)light
                  frame: (int)frame
                  range: (int)range
               position: (OFVector3D)position
                    yaw: (float)yaw
                  pitch: (float)pitch
                  scale: (float)scale
                  speed: (float)speed
                   snap: (int)snap
               basetime: (int)basetime;
- (void)scaleWithFrame: (int)frame scale: (float)scale snap: (int)snap;
@end

OF_ASSUME_NONNULL_END

Modified src/MD2.m from [eb988e3999] to [64d68c27a0].

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







-
+



-
-
-
+
+
+




-
+



-
+











-
-
-
+
+
+










-
-
-
+
+
+















-
+

















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



-
+







	if (_mverts != NULL)
		for (size_t i = 0; i < _numFrames; i++)
			OFFreeMemory(_mverts[i]);

	OFFreeMemory(_mverts);
}

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

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

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

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

	@try {
		_frames = OFAllocMemory(header.numFrames, header.frameSize);
	} @catch (OFOutOfMemoryException *e) {
		return false;
	}

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

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

	@try {
		_glCommands = OFAllocMemory(header.numGlCommands, sizeof(int));
	} @catch (OFOutOfMemoryException *e) {
		return false;
	}

	[stream seekToOffset:header.offsetGlCommands whence:OFSeekSet];
	[stream readIntoBuffer:_glCommands
	           exactLength:header.numGlCommands * sizeof(int)];
	[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 = OFAllocZeroedMemory(_numFrames, sizeof(OFVector3D *));

	return true;
}

- (void)scaleWithFrame:(int)frame scale:(float)scale snap:(int)sn
- (void)scaleWithFrame: (int)frame scale: (float)scale snap: (int)sn
{
	OFAssert(_mverts[frame] == NULL);

	_mverts[frame] = OFAllocMemory(_numVerts, sizeof(OFVector3D));
	struct md2_frame *cf =
	    (struct md2_frame *)((char *)_frames + _frameSize * frame);
	float sc = 16.0f / scale;
	for (int vi = 0; vi < _numVerts; vi++) {
		unsigned char *cv = (unsigned char *)&cf->vertices[vi].vertex;
		OFVector3D *v = &(_mverts[frame])[vi];
		v->x = (snap(sn, cv[0] * cf->scale[0]) + cf->translate[0]) / sc;
		v->y =
		    -(snap(sn, cv[1] * cf->scale[1]) + cf->translate[1]) / sc;
		v->z = (snap(sn, cv[2] * cf->scale[2]) + cf->translate[2]) / sc;
	}
}

- (void)renderWithLight:(OFColor *)light
                  frame:(int)frame
                  range:(int)range
               position:(OFVector3D)position
                    yaw:(float)yaw
                  pitch:(float)pitch
                  scale:(float)sc
                  speed:(float)speed
                   snap:(int)sn
               basetime:(int)basetime
- (void)renderWithLight: (OFColor *)light
                  frame: (int)frame
                  range: (int)range
               position: (OFVector3D)position
                    yaw: (float)yaw
                  pitch: (float)pitch
                  scale: (float)sc
                  speed: (float)speed
                   snap: (int)sn
               basetime: (int)basetime
{
	for (int i = 0; i < range; i++)
		if (!_mverts[frame + i])
			[self scaleWithFrame:frame + i scale:sc snap:sn];
			[self scaleWithFrame: frame + i scale: sc snap: sn];

	glPushMatrix();
	glTranslatef(position.x, position.y, position.z);
	glRotatef(yaw + 180, 0, -1, 0);
	glRotatef(pitch, 0, 0, 1);

	[light cube_setAsGLColor];

Modified src/MapModelInfo.h from [3aab1dec04] to [9966a1ab68].

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





15
16
17
18
19
20





21
22
23
1
2
3
4
5
6
7
8
9





10
11
12
13
14
15





16
17
18
19
20
21
22
23









-
-
-
-
-
+
+
+
+
+

-
-
-
-
-
+
+
+
+
+



#import <ObjFW/ObjFW.h>

OF_ASSUME_NONNULL_BEGIN

OF_DIRECT_MEMBERS
@interface MapModelInfo: OFObject
@property (nonatomic) int rad, h, zoff, snap;
@property (copy, nonatomic) OFString *name;

+ (instancetype)infoWithRad:(int)rad
                          h:(int)h
                       zoff:(int)zoff
                       snap:(int)snap
                       name:(OFString *)name;
+ (instancetype)infoWithRad: (int)rad
                          h: (int)h
                       zoff: (int)zoff
                       snap: (int)snap
                       name: (OFString *)name;
- (instancetype)init OF_UNAVAILABLE;
- (instancetype)initWithRad:(int)rad
                          h:(int)h
                       zoff:(int)zoff
                       snap:(int)snap
                       name:(OFString *)name;
- (instancetype)initWithRad: (int)rad
                          h: (int)h
                       zoff: (int)zoff
                       snap: (int)snap
                       name: (OFString *)name;
@end

OF_ASSUME_NONNULL_END

Modified src/MapModelInfo.m from [d506409929] to [19135ee4c2].

1
2
3
4
5
6
7
8





9
10





11
12
13
14
15
16
17





18
19
20
21
22
23
24
1
2
3





4
5
6
7
8
9

10
11
12
13
14
15
16





17
18
19
20
21
22
23
24
25
26
27
28



-
-
-
-
-
+
+
+
+
+

-
+
+
+
+
+


-
-
-
-
-
+
+
+
+
+







#import "MapModelInfo.h"

@implementation MapModelInfo
+ (instancetype)infoWithRad:(int)rad
                          h:(int)h
                       zoff:(int)zoff
                       snap:(int)snap
                       name:(OFString *)name
+ (instancetype)infoWithRad: (int)rad
                          h: (int)h
                       zoff: (int)zoff
                       snap: (int)snap
                       name: (OFString *)name
{
	return [[self alloc] initWithRad:rad h:h zoff:zoff snap:snap name:name];
	return [[self alloc] initWithRad: rad
				       h: h
				    zoff: zoff
				    snap: snap
				    name: name];
}

- (instancetype)initWithRad:(int)rad
                          h:(int)h
                       zoff:(int)zoff
                       snap:(int)snap
                       name:(OFString *)name
- (instancetype)initWithRad: (int)rad
                          h: (int)h
                       zoff: (int)zoff
                       snap: (int)snap
                       name: (OFString *)name
{
	self = [super init];

	_rad = rad;
	_h = h;
	_zoff = zoff;
	_snap = snap;

Modified src/Menu.h from [213d149aa4] to [fddccab06a].

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

15
16

17
18
19
1
2
3
4
5
6
7
8
9
10
11
12
13

14
15

16
17
18
19













-
+

-
+



#import <ObjFW/ObjFW.h>

OF_ASSUME_NONNULL_BEGIN

@class MenuItem;

OF_DIRECT_MEMBERS
@interface Menu: OFObject
@property (readonly, nonatomic) OFString *name;
@property (readonly) OFMutableArray<MenuItem *> *items;
@property (nonatomic) int mwidth;
@property (nonatomic) int menusel;

+ (instancetype)menuWithName:(OFString *)name;
+ (instancetype)menuWithName: (OFString *)name;
- (instancetype)init OF_UNAVAILABLE;
- (instancetype)initWithName:(OFString *)name;
- (instancetype)initWithName: (OFString *)name;
@end

OF_ASSUME_NONNULL_END

Modified src/Menu.m from [e5fbce3936] to [106bd88ce2].

1
2
3
4

5
6

7
8
9

10
11
12
13
14
15
16
1
2
3

4
5

6
7
8

9
10
11
12
13
14
15
16



-
+

-
+


-
+







#import "Menu.h"

@implementation Menu
+ (instancetype)menuWithName:(OFString *)name
+ (instancetype)menuWithName: (OFString *)name
{
	return [[self alloc] initWithName:name];
	return [[self alloc] initWithName: name];
}

- (instancetype)initWithName:(OFString *)name
- (instancetype)initWithName: (OFString *)name
{
	self = [super init];

	_name = [name copy];
	_items = [[OFMutableArray alloc] init];

	return self;

Modified src/MenuItem.h from [969de9221d] to [d2df5485ed].

1
2
3
4
5
6
7

8
9

10
1
2
3
4
5
6

7
8

9
10






-
+

-
+

#import <ObjFW/ObjFW.h>

OF_DIRECT_MEMBERS
@interface MenuItem: OFObject
@property (readonly, nonatomic) OFString *text, *action;

+ (instancetype)itemWithText:(OFString *)text action:(OFString *)action;
+ (instancetype)itemWithText: (OFString *)text action: (OFString *)action;
- (instancetype)init OF_UNAVAILABLE;
- (instancetype)initWithText:(OFString *)text action:(OFString *)action;
- (instancetype)initWithText: (OFString *)text action: (OFString *)action;
@end

Modified src/MenuItem.m from [5ee318d452] to [c51eda1a22].

1
2
3
4

5
6

7
8
9

10
11
12
13
14
15
16
17
18
19

20
21
22
23

24
25
26
27
28
29
30
1
2
3

4
5

6
7
8

9
10
11
12
13
14
15
16
17
18

19
20
21
22

23
24
25
26
27
28
29
30



-
+

-
+


-
+









-
+



-
+







#import "MenuItem.h"

@implementation MenuItem
+ (instancetype)itemWithText:(OFString *)text action:(OFString *)action
+ (instancetype)itemWithText: (OFString *)text action: (OFString *)action
{
	return [[self alloc] initWithText:text action:action];
	return [[self alloc] initWithText: text action: action];
}

- (instancetype)initWithText:(OFString *)text action:(OFString *)action
- (instancetype)initWithText: (OFString *)text action: (OFString *)action
{
	self = [super init];

	_text = [text copy];
	_action = [action copy];

	return self;
}

- (OFComparisonResult)compare:(id)otherObject
- (OFComparisonResult)compare: (id)otherObject
{
	MenuItem *otherItem;

	if (![otherObject isKindOfClass:MenuItem.class])
	if (![otherObject isKindOfClass: MenuItem.class])
		@throw [OFInvalidArgumentException exception];

	int x, y;
	@try {
		x = _text.intValue;
	} @catch (OFInvalidFormatException *e) {
		x = 0;

Modified src/Monster.h from [f02a70099a] to [ecb02516a6].

21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39












40
21
22
23
24
25
26
27












28
29
30
31
32
33
34
35
36
37
38
39
40







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

// called after map start of when toggling edit mode to reset/spawn all
// monsters to initial state
+ (void)restoreAll;
+ (void)resetAll;
+ (void)thinkAll;
+ (void)renderAll;
// TODO: Move this somewhere else
+ (void)endSinglePlayerWithAllKilled:(bool)allKilled;
+ (instancetype)monsterWithType:(int)type
                            yaw:(int)yaw
                          state:(int)state
                        trigger:(int)trigger
                           move:(int)move;
- (instancetype)initWithType:(int)type
                         yaw:(int)yaw
                       state:(int)state
                     trigger:(int)trigger
                        move:(int)move;
- (void)incurDamage:(int)damage fromEntity:(__kindof DynamicEntity *)d;
+ (void)endSinglePlayerWithAllKilled: (bool)allKilled;
+ (instancetype)monsterWithType: (int)type
                            yaw: (int)yaw
                          state: (int)state
                        trigger: (int)trigger
                           move: (int)move;
- (instancetype)initWithType: (int)type
                         yaw: (int)yaw
                       state: (int)state
                     trigger: (int)trigger
                        move: (int)move;
- (void)incurDamage: (int)damage fromEntity: (__kindof DynamicEntity *)d;
@end

Modified src/Monster.m from [96bb3308b2] to [911d4fb855].

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







-
-
-
-
-
+
+
+
+
+

-
-
-
-
-
+
+
+
+
+







}

+ (OFMutableArray<Monster *> *)monsters
{
	return monsters;
}

+ (instancetype)monsterWithType:(int)type
                            yaw:(int)yaw
                          state:(int)state
                        trigger:(int)trigger
                           move:(int)move
+ (instancetype)monsterWithType: (int)type
                            yaw: (int)yaw
                          state: (int)state
                        trigger: (int)trigger
                           move: (int)move
{
	return [[self alloc] initWithType:type
	                              yaw:yaw
	                            state:state
	                          trigger:trigger
	                             move:move];
	return [[self alloc] initWithType: type
	                              yaw: yaw
	                            state: state
	                          trigger: trigger
	                             move: move];
}

VARF(skill, 1, 3, 10, conoutf(@"skill is now %d", skill));

// for savegames
+ (void)restoreAll
{
71
72
73
74
75
76
77
78
79
80
81
82





83
84
85
86
87
88
89
71
72
73
74
75
76
77





78
79
80
81
82
83
84
85
86
87
88
89







-
-
-
-
-
+
+
+
+
+







	    @"a hellpig", @"monster/hellpig" },
	{ GUN_ICEBALL, 12, 250, 1, 0, 10, 400, 6, 18, 18, S_PAINH, S_DEATHH,
	    @"a knight", @"monster/knight" },
	{ GUN_SLIMEBALL, 15, 100, 1, 0, 200, 400, 2, 13, 10, S_PAIND, S_DEATHD,
	    @"a goblin", @"monster/goblin" },
};

- (instancetype)initWithType:(int)type
                         yaw:(int)yaw
                       state:(int)state
                     trigger:(int)trigger
                        move:(int)move
- (instancetype)initWithType: (int)type
                         yaw: (int)yaw
                       state: (int)state
                     trigger: (int)trigger
                        move: (int)move
{
	self = [super init];

	if (type >= NUMMONSTERTYPES) {
		conoutf(@"warning: unknown monster in spawn: %d", type);
		type = 0;
	}
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
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







-
-
-
-
-
+
+
+
+
+




















-
-
-
-
-
+
+
+
+
+

-
+







	for (int i = 0;; i++) {
		if ((n -= monstertypes[i].freq) < 0) {
			type = i;
			break;
		}
	}

	[monsters addObject:[Monster monsterWithType:type
	                                         yaw:rnd(360)
	                                       state:M_SEARCH
	                                     trigger:1000
	                                        move:1]];
	[monsters addObject: [Monster monsterWithType: type
						  yaw: rnd(360)
						state: M_SEARCH
					      trigger: 1000
						 move: 1]];
}

+ (void)resetAll
{
	[monsters removeAllObjects];

	numkilled = 0;
	monstertotal = 0;
	spawnremain = 0;

	if (m_dmsp) {
		nextmonster = mtimestart = lastmillis + 10000;
		monstertotal = spawnremain = gamemode < 0 ? skill * 10 : 0;
	} else if (m_classicsp) {
		mtimestart = lastmillis;

		for (Entity *e in ents) {
			if (e.type != MONSTER)
				continue;

			Monster *m = [Monster monsterWithType:e.attr2
			                                  yaw:e.attr1
			                                state:M_SLEEP
			                              trigger:100
			                                 move:0];
			Monster *m = [Monster monsterWithType: e.attr2
			                                  yaw: e.attr1
			                                state: M_SLEEP
			                              trigger: 100
			                                 move: 0];
			m.origin = OFMakeVector3D(e.x, e.y, e.z);
			[monsters addObject:m];
			[monsters addObject: m];
			entinmap(m);
			monstertotal++;
		}
	}
}

// height-correct line of sight for monster shooting/seeing
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
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







-
+







-
+














-
+







// where they execute a particular behaviour until the trigger time is hit, and
// then they reevaluate their situation based on the current state, the
// environment etc., and transition to the next state. Transition timeframes are
// parametrized by difficulty level (skill), faster transitions means quicker
// decision making means tougher AI.

// n = at skill 0, n/2 = at skill 10, r = added random factor
- (void)transitionWithState:(int)state moving:(int)moving n:(int)n r:(int)r
- (void)transitionWithState: (int)state moving: (int)moving n: (int)n r: (int)r
{
	self.monsterState = state;
	self.move = moving;
	n = n * 130 / 100;
	self.trigger = lastmillis + n - skill * (n / 16) + rnd(r + 1);
}

- (void)normalizeWithAngle:(float)angle
- (void)normalizeWithAngle: (float)angle
{
	while (self.yaw < angle - 180.0f)
		self.yaw += 360.0f;
	while (self.yaw > angle + 180.0f)
		self.yaw -= 360.0f;
}

// main AI thinking routine, called every frame for every monster
- (void)performAction
{
	if (self.enemy.state == CS_DEAD) {
		self.enemy = Player.player1;
		self.anger = 0;
	}
	[self normalizeWithAngle:self.targetYaw];
	[self normalizeWithAngle: self.targetYaw];
	// slowly turn monster towards his target
	if (self.targetYaw > self.yaw) {
		self.yaw += curtime * 0.5f;
		if (self.targetYaw < self.yaw)
			self.yaw = self.targetYaw;
	} else {
		self.yaw -= curtime * 0.5f;
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
396
397
398
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
431
432
433

434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453

454
455
456

457
458
459
460
461
462
463
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
396

397
398
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
431
432
433
434
435
436
437
438

439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458

459
460
461

462
463
464
465
466
467
468
469







-
-
-
-
+
+
+
+













-
+
+
+
+








-
+






-
+
+
+
+













-
-
-
-
+
+
+
+












-
-
-
-
+
+
+
+









-
-
-
-
+
+
+
+




-
-
-
-
+
+
+
+









-
+


-
+




















-
-
-
-
+
+
+
+

















-
+



















-
+


-
+







		if (!rnd(20000 / monstertypes[self.monsterType].speed))
			self.jumpNext = true;
		// search for a way around (common)
		else if (self.trigger < lastmillis &&
		    (self.monsterState != M_HOME || !rnd(5))) {
			// patented "random walk" AI pathfinding (tm) ;)
			self.targetYaw += 180 + rnd(180);
			[self transitionWithState:M_SEARCH
			                   moving:1
			                        n:400
			                        r:1000];
			[self transitionWithState: M_SEARCH
			                   moving: 1
			                        n: 400
			                        r: 1000];
		}
	}

	float enemyYaw = -(float)atan2(self.enemy.origin.x - self.origin.x,
	                     self.enemy.origin.y - self.origin.y) /
	        PI * 180 +
	    180;

	switch (self.monsterState) {
	case M_PAIN:
	case M_ATTACKING:
	case M_SEARCH:
		if (self.trigger < lastmillis)
			[self transitionWithState:M_HOME moving:1 n:100 r:200];
			[self transitionWithState: M_HOME
					   moving: 1
						n: 100
						r: 200];
		break;

	case M_SLEEP: // state classic sp monster start in, wait for visual
	              // contact
	{
		OFVector3D target;
		if (editmode || !enemylos(self, &target))
			return; // skip running physics
		[self normalizeWithAngle:enemyYaw];
		[self normalizeWithAngle: enemyYaw];
		float angle = (float)fabs(enemyYaw - self.yaw);
		if (disttoenemy < 8 // the better the angle to the player, the
		                    // further the monster can see/hear
		    || (disttoenemy < 16 && angle < 135) ||
		    (disttoenemy < 32 && angle < 90) ||
		    (disttoenemy < 64 && angle < 45) || angle < 10) {
			[self transitionWithState:M_HOME moving:1 n:500 r:200];
			[self transitionWithState: M_HOME
					   moving: 1
						n: 500
						r: 200];
			OFVector3D loc = self.origin;
			playsound(S_GRUNT1 + rnd(2), &loc);
		}
		break;
	}

	case M_AIMING:
		// this state is the delay between wanting to shoot and actually
		// firing
		if (self.trigger < lastmillis) {
			self.lastAction = 0;
			self.attacking = true;
			shoot(self, self.attackTarget);
			[self transitionWithState:M_ATTACKING
			                   moving:0
			                        n:600
			                        r:0];
			[self transitionWithState: M_ATTACKING
			                   moving: 0
			                        n: 600
			                        r: 0];
		}
		break;

	case M_HOME:
		// monster has visual contact, heads straight for player and
		// may want to shoot at any time
		self.targetYaw = enemyYaw;
		if (self.trigger < lastmillis) {
			OFVector3D target;
			if (!enemylos(self, &target)) {
				// no visual contact anymore, let monster get
				// as close as possible then search for player
				[self transitionWithState:M_HOME
				                   moving:1
				                        n:800
				                        r:500];
				[self transitionWithState: M_HOME
				                   moving: 1
				                        n: 800
				                        r: 500];
			} else {
				// the closer the monster is the more likely he
				// wants to shoot
				if (!rnd((int)disttoenemy / 3 + 1) &&
				    self.enemy.state == CS_ALIVE) {
					// get ready to fire
					self.attackTarget = target;
					int n =
					    monstertypes[self.monsterType].lag;
					[self transitionWithState:M_AIMING
					                   moving:0
					                        n:n
					                        r:10];
					[self transitionWithState: M_AIMING
					                   moving: 0
					                        n: n
					                        r: 10];
				} else {
					// track player some more
					int n =
					    monstertypes[self.monsterType].rate;
					[self transitionWithState:M_HOME
					                   moving:1
					                        n:n
					                        r:0];
					[self transitionWithState: M_HOME
					                   moving: 1
					                        n: n
					                        r: 0];
				}
			}
		}
		break;
	}

	moveplayer(self, 1, false); // use physics to move monster
}

- (void)incurDamage:(int)damage fromEntity:(__kindof DynamicEntity *)d
- (void)incurDamage: (int)damage fromEntity: (__kindof DynamicEntity *)d
{
	// a monster hit us
	if ([d isKindOfClass:Monster.class]) {
	if ([d isKindOfClass: Monster.class]) {
		Monster *m = (Monster *)d;

		// guard for RL guys shooting themselves :)
		if (self != m) {
			// don't attack straight away, first get angry
			self.anger++;
			int anger =
			    (self.monsterType == m.monsterType ? self.anger / 2
			                                       : self.anger);
			if (anger >= monstertypes[self.monsterType].loyalty)
				// monster infight if very angry
				self.enemy = m;
		}
	} else {
		// player hit us
		self.anger = 0;
		self.enemy = d;
	}

	// in this state monster won't attack
	[self transitionWithState:M_PAIN
	                   moving:0
	                        n:monstertypes[self.monsterType].pain
	                        r:200];
	[self transitionWithState: M_PAIN
	                   moving: 0
	                        n: monstertypes[self.monsterType].pain
	                        r: 200];

	if ((self.health -= damage) <= 0) {
		self.state = CS_DEAD;
		self.lastAction = lastmillis;
		numkilled++;
		Player.player1.frags = numkilled;
		OFVector3D loc = self.origin;
		playsound(monstertypes[self.monsterType].diesound, &loc);
		int remain = monstertotal - numkilled;
		if (remain > 0 && remain <= 5)
			conoutf(@"only %d monster(s) remaining", remain);
	} else {
		OFVector3D loc = self.origin;
		playsound(monstertypes[self.monsterType].painsound, &loc);
	}
}

+ (void)endSinglePlayerWithAllKilled:(bool)allKilled
+ (void)endSinglePlayerWithAllKilled: (bool)allKilled
{
	conoutf(allKilled ? @"you have cleared the map!"
	                  : @"you reached the exit!");
	conoutf(@"score: %d kills in %d seconds", numkilled,
	    (lastmillis - mtimestart) / 1000);
	monstertotal = 0;
	startintermission();
}

+ (void)thinkAll
{
	if (m_dmsp && spawnremain && lastmillis > nextmonster) {
		if (spawnremain-- == monstertotal)
			conoutf(@"The invasion has begun!");
		nextmonster = lastmillis + 1000;
		spawnmonster();
	}

	if (monstertotal && !spawnremain && numkilled == monstertotal)
		[self endSinglePlayerWithAllKilled:true];
		[self endSinglePlayerWithAllKilled: true];

	// equivalent of player entity touch, but only teleports are used
	[ents enumerateObjectsUsingBlock:^(Entity *e, size_t i, bool *stop) {
	[ents enumerateObjectsUsingBlock: ^ (Entity *e, size_t i, bool *stop) {
		if (e.type != TELEPORT)
			return;

		if (OUTBORD(e.x, e.y))
			return;

		OFVector3D v =

Modified src/OFColor+Cube.m from [830e6212b6] to [a418a3dce6].

1
2
3
4
5
6
7
8
9

10
11
12
1
2
3
4
5
6
7
8

9
10
11
12








-
+



#include "cube.h"

#import "OFColor+Cube.h"

@implementation OFColor (Cube)
- (void)cube_setAsGLColor
{
	float red, green, blue, alpha;
	[self getRed:&red green:&green blue:&blue alpha:&alpha];
	[self getRed: &red green: &green blue: &blue alpha: &alpha];
	glColor4f(red, green, blue, alpha);
}
@end

Modified src/OFString+Cube.h from [521e5eedaf] to [4053523497].

1
2
3
4
5
6

7
1
2
3
4
5

6
7





-
+

#import <ObjFW/ObjFW.h>

@interface OFString (Cube)
@property (readonly, nonatomic) int cube_intValue;

- (int)cube_intValueWithBase:(unsigned char)base;
- (int)cube_intValueWithBase: (unsigned char)base;
@end

Modified src/OFString+Cube.m from [fe77741148] to [fa8b89b835].

12
13
14
15
16
17
18
19

20
21
22

23
24
25
26
27
28
29
12
13
14
15
16
17
18

19
20
21

22
23
24
25
26
27
28
29







-
+


-
+







		return 0;
	} @catch (OFOutOfRangeException *e) {
		conoutf(@"invalid value: %@", self);
		return 0;
	}
}

- (int)cube_intValueWithBase:(unsigned char)base
- (int)cube_intValueWithBase: (unsigned char)base
{
	@try {
		return [self intValueWithBase:base];
		return [self intValueWithBase: base];
	} @catch (OFInvalidFormatException *e) {
		conoutf(@"invalid value: %@", self);
		return 0;
	} @catch (OFOutOfRangeException *e) {
		conoutf(@"invalid value: %@", self);
		return 0;
	}

Modified src/Player.m from [0c9f736137] to [312fff2095].

10
11
12
13
14
15
16
17

18
19
20
21
22
23
24
10
11
12
13
14
15
16

17
18
19
20
21
22
23
24







-
+







}

+ (instancetype)player
{
	return [[self alloc] init];
}

+ (void)setPlayer1:(Player *)player1_
+ (void)setPlayer1: (Player *)player1_
{
	player1 = player1_;
}

+ (Player *)player1
{
	return player1;

Modified src/ResolverResult.h from [48e906dbd2] to [97bc46e6ab].

1
2
3
4
5
6
7
8
9
10


11
12


13
1
2
3
4
5
6
7
8
9

10
11
12

13
14
15









-
+
+

-
+
+

#import <ObjFW/ObjFW.h>

#import "cube.h"

OF_DIRECT_MEMBERS
@interface ResolverResult: OFObject
@property (readonly, nonatomic) OFString *query;
@property (readonly, nonatomic) ENetAddress address;

+ (instancetype)resultWithQuery:(OFString *)query address:(ENetAddress)address;
+ (instancetype)resultWithQuery: (OFString *)query
			address: (ENetAddress)address;
- (instancetype)init OF_UNAVAILABLE;
- (instancetype)initWithQuery:(OFString *)query address:(ENetAddress)address;
- (instancetype)initWithQuery: (OFString *)query
		      address: (ENetAddress)address OF_DESIGNATED_INITIALIZER;
@end

Modified src/ResolverResult.m from [36dcd5aa96] to [2be4a1c685].

1
2
3
4

5
6

7
8
9

10
11
12
13
14
15
16
1
2
3

4
5

6
7
8

9
10
11
12
13
14
15
16



-
+

-
+


-
+







#import "ResolverResult.h"

@implementation ResolverResult
+ (instancetype)resultWithQuery:(OFString *)query address:(ENetAddress)address
+ (instancetype)resultWithQuery: (OFString *)query address: (ENetAddress)address
{
	return [[self alloc] initWithQuery:query address:address];
	return [[self alloc] initWithQuery: query address: address];
}

- (instancetype)initWithQuery:(OFString *)query address:(ENetAddress)address
- (instancetype)initWithQuery: (OFString *)query address: (ENetAddress)address
{
	self = [super init];

	_query = query;
	_address = address;

	return self;

Modified src/ResolverThread.m from [5a480794ce] to [c6ab30f422].

21
22
23
24
25
26
27
28
29
30



31
32
33
34
35
36
37
21
22
23
24
25
26
27



28
29
30
31
32
33
34
35
36
37







-
-
-
+
+
+







			_starttime = lastmillis;
		}

		ENetAddress address = { ENET_HOST_ANY, CUBE_SERVINFO_PORT };
		enet_address_set_host(&address, _query.UTF8String);

		@synchronized(ResolverThread.class) {
			[resolverresults
			    addObject:[ResolverResult resultWithQuery:_query
			                                      address:address]];
			[resolverresults addObject:
			    [ResolverResult resultWithQuery: _query
						    address: address]];

			_query = NULL;
			_starttime = 0;
		}
	}

	return nil;

Modified src/ServerInfo.h from [8949d4cc02] to [688d346abd].

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

15
16

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

14
15

16
17













-
+

-
+

#import <ObjFW/ObjFW.h>

#include <enet/enet.h>

OF_DIRECT_MEMBERS
@interface ServerInfo: OFObject <OFComparing>
@property (readonly, nonatomic) OFString *name;
@property (copy, nonatomic) OFString *full;
@property (copy, nonatomic) OFString *map;
@property (copy, nonatomic) OFString *sdesc;
@property (nonatomic) int mode, numplayers, ping, protocol, minremain;
@property (nonatomic) ENetAddress address;

+ (instancetype)infoWithName:(OFString *)name;
+ (instancetype)infoWithName: (OFString *)name;
- (instancetype)init OF_UNAVAILABLE;
- (instancetype)initWithName:(OFString *)name;
- (instancetype)initWithName: (OFString *)name;
@end

Modified src/ServerInfo.m from [77aa772923] to [774d015eb0].

1
2
3
4
5
6

7
8

9
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
1
2
3
4
5

6
7

8
9
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





-
+

-
+


-
+


















-
+

-
+







-
+


#import "ServerInfo.h"

#include "cube.h"

@implementation ServerInfo
+ (instancetype)infoWithName:(OFString *)name;
+ (instancetype)infoWithName: (OFString *)name;
{
	return [[self alloc] initWithName:name];
	return [[self alloc] initWithName: name];
}

- (instancetype)initWithName:(OFString *)name
- (instancetype)initWithName: (OFString *)name
{
	self = [super init];

	_name = [name copy];
	_full = @"";
	_mode = 0;
	_numplayers = 0;
	_ping = 9999;
	_protocol = 0;
	_minremain = 0;
	_map = @"";
	_sdesc = @"";
	_address.host = ENET_HOST_ANY;
	_address.port = CUBE_SERVINFO_PORT;

	return self;
}

- (OFComparisonResult)compare:(ServerInfo *)otherObject
- (OFComparisonResult)compare: (ServerInfo *)otherObject
{
	if (![otherObject isKindOfClass:ServerInfo.class])
	if (![otherObject isKindOfClass: ServerInfo.class])
		@throw [OFInvalidArgumentException exception];

	if (_ping > otherObject.ping)
		return OFOrderedDescending;
	if (_ping < otherObject.ping)
		return OFOrderedAscending;

	return [_name compare:otherObject.name];
	return [_name compare: otherObject.name];
}
@end

Modified src/Variable.h from [b06c507597] to [780b5706d6].

1
2
3
4
5
6
7
8
9
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
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
1
2
3
4















5
6
7
8
9
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
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




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

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

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







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


-
+



#import "Identifier.h"

OF_ASSUME_NONNULL_BEGIN

#define VARP(name, min_, cur, max_)                                 \
	int name = cur;                                             \
                                                                    \
	OF_CONSTRUCTOR()                                            \
	{                                                           \
		enqueueInit(^{                                      \
			Variable *variable =                        \
			    [Variable variableWithName:@ #name      \
			                           min:min_         \
			                           max:max_         \
			                       storage:&name        \
			                      function:NULL         \
			                     persisted:true];       \
			Identifier.identifiers[@ #name] = variable; \
		});                                                 \
#define VARP(name, min_, cur, max_)					\
	int name = cur;							\
									\
	OF_CONSTRUCTOR()						\
	{								\
		enqueueInit(^ {						\
			Variable *variable =				\
			    [Variable variableWithName: @#name		\
			                           min: min_		\
			                           max: max_		\
			                       storage: &name		\
			                      function: NULL		\
			                     persisted: true];		\
			Identifier.identifiers[@#name] = variable;	\
		});							\
	}
#define VAR(name, min_, cur, max_)                                  \
	int name = cur;                                             \
                                                                    \
	OF_CONSTRUCTOR()                                            \
	{                                                           \
		enqueueInit(^{                                      \
			Variable *variable =                        \
			    [Variable variableWithName:@ #name      \
			                           min:min_         \
			                           max:max_         \
			                       storage:&name        \
			                      function:NULL         \
			                     persisted:false];      \
			Identifier.identifiers[@ #name] = variable; \
		});                                                 \
#define VAR(name, min_, cur, max_)					\
	int name = cur;							\
									\
	OF_CONSTRUCTOR()						\
	{								\
		enqueueInit(^ {						\
			Variable *variable =				\
			    [Variable variableWithName: @#name		\
			                           min: min_		\
			                           max: max_		\
			                       storage: &name		\
			                      function: NULL		\
			                     persisted: false];		\
			Identifier.identifiers[@#name] = variable;	\
		});							\
	}
#define VARF(name, min_, cur, max_, body)                           \
	static void var_##name();                                   \
	static int name = cur;                                      \
                                                                    \
	OF_CONSTRUCTOR()                                            \
	{                                                           \
		enqueueInit(^{                                      \
			Variable *variable =                        \
			    [Variable variableWithName:@ #name      \
			                           min:min_         \
			                           max:max_         \
			                       storage:&name        \
			                      function:var_##name   \
			                     persisted:false];      \
			Identifier.identifiers[@ #name] = variable; \
		});                                                 \
	}                                                           \
                                                                    \
	static void var_##name() { body; }
#define VARFP(name, min_, cur, max_, body)                          \
	static void var_##name();                                   \
	static int name = cur;                                      \
                                                                    \
	OF_CONSTRUCTOR()                                            \
#define VARF(name, min_, cur, max_, body)				\
	static void var_##name(void);					\
	static int name = cur;						\
									\
	OF_CONSTRUCTOR()						\
	{								\
		enqueueInit(^ {						\
			Variable *variable =				\
			    [Variable variableWithName: @#name		\
			                           min: min_		\
			                           max: max_		\
			                       storage: &name		\
			                      function: var_##name	\
			                     persisted: false];		\
			Identifier.identifiers[@#name] = variable;	\
		});							\
	}								\
									\
	static void							\
	var_##name(void)						\
	{								\
		body;							\
	}
#define VARFP(name, min_, cur, max_, body)				\
	static void var_##name(void);					\
	static int name = cur;						\
									\
	OF_CONSTRUCTOR()						\
	{                                                           \
		enqueueInit(^{                                      \
			Variable *variable =                        \
			    [Variable variableWithName:@ #name      \
			                           min:min_         \
			                           max:max_         \
			                       storage:&name        \
			                      function:var_##name   \
			                     persisted:true];       \
			Identifier.identifiers[@ #name] = variable; \
	{								\
		enqueueInit(^ {						\
			Variable *variable =				\
			    [Variable variableWithName: @#name		\
			                           min: min_		\
			                           max: max_		\
			                       storage: &name		\
			                      function: var_##name	\
			                     persisted: true];		\
			Identifier.identifiers[@#name] = variable;	\
		});                                                 \
	}                                                           \
                                                                    \
	static void var_##name() { body; }
		});							\
	}								\
									\
	static void							\
	var_##name(void)						\
	{								\
		body;							\
	}

@interface Variable: Identifier
@property (direct, readonly, nonatomic) int min, max;
@property (direct, readonly, nonatomic) int *storage;
@property (direct, readonly, nullable, nonatomic) void (*function)();
@property (readonly, nonatomic) bool persisted;

+ (instancetype)variableWithName:(OFString *)name
                             min:(int)min
                             max:(int)max
                         storage:(int *)storage
                        function:(void (*_Nullable)())function
                       persisted:(bool)persisted OF_DIRECT;
- (instancetype)initWithName:(OFString *)name OF_UNAVAILABLE;
- (instancetype)initWithName:(OFString *)name
                         min:(int)min
                         max:(int)max
                     storage:(int *)storage
                    function:(void (*_Nullable)())function
                   persisted:(bool)persisted OF_DESIGNATED_INITIALIZER
+ (instancetype)variableWithName: (OFString *)name
                             min: (int)min
                             max: (int)max
                         storage: (int *)storage
                        function: (void (*_Nullable)())function
                       persisted: (bool)persisted OF_DIRECT;
- (instancetype)initWithName: (OFString *)name OF_UNAVAILABLE;
- (instancetype)initWithName: (OFString *)name
                         min: (int)min
                         max: (int)max
                     storage: (int *)storage
                    function: (void (*_Nullable)())function
                   persisted: (bool)persisted OF_DESIGNATED_INITIALIZER
    OF_DIRECT;
- (void)printValue OF_DIRECT;
- (void)setValue:(int)value OF_DIRECT;
- (void)setValue: (int)value OF_DIRECT;
@end

OF_ASSUME_NONNULL_END

Modified src/Variable.m from [eb5893de54] to [60a145c003].

1
2
3
4
5
6
7
8
9
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
48
49
50
51
1
2
3
4
5






6
7
8
9
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
48
49
50
51





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

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


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

-
+















-
+







#import "Variable.h"

#include "cube.h"

@implementation Variable
+ (instancetype)variableWithName:(OFString *)name
                             min:(int)min
                             max:(int)max
                         storage:(int *)storage
                        function:(void (*__cdecl)())function
                       persisted:(bool)persisted
+ (instancetype)variableWithName: (OFString *)name
                             min: (int)min
                             max: (int)max
                         storage: (int *)storage
                        function: (void (*__cdecl)())function
                       persisted: (bool)persisted
{
	return [[self alloc] initWithName:name
	                              min:min
	                              max:max
	                          storage:storage
	                         function:function
	                        persisted:persisted];
	return [[self alloc] initWithName: name
	                              min: min
	                              max: max
	                          storage: storage
	                         function: function
	                        persisted: persisted];
}

- (instancetype)initWithName:(OFString *)name
                         min:(int)min
                         max:(int)max
                     storage:(int *)storage
                    function:(void (*__cdecl)())function
                   persisted:(bool)persisted
- (instancetype)initWithName: (OFString *)name
                         min: (int)min
                         max: (int)max
                     storage: (int *)storage
                    function: (void (*__cdecl)())function
                   persisted: (bool)persisted
{
	self = [super initWithName:name];
	self = [super initWithName: name];

	_min = min;
	_max = max;
	_storage = storage;
	_function = function;
	_persisted = persisted;

	return self;
}

- (void)printValue
{
	conoutf(@"%@ = %d", self.name, *_storage);
}

- (void)setValue:(int)value
- (void)setValue: (int)value
{
	bool outOfRange = false;

	if (_min > _max) {
		conoutf(@"variable is read-only");
		return;
	}

Modified src/clientextras.m from [89ccbf227a] to [d459541c88].

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







-
+

-
+

-
-
+
+

-
-
+
+

-
+

-
+

-
+




















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


















-
-
-
+
+
+






-
+













-
+







			}
		}
		if (mz < -1000)
			return;
		// mdl = (((int)d>>6)&1)+1;
		// mz = d.o.z-d.eyeHeight+0.2f;
		// scale = 1.2f;
	} else if (d.state == CS_EDITING) {
	} else if (d.state == CS_EDITING)
		n = 16;
	} else if (d.state == CS_LAGGED) {
	else if (d.state == CS_LAGGED)
		n = 17;
	} else if ([d isKindOfClass:Monster.class] &&
	    ((Monster *)d).monsterState == M_ATTACKING) {
	else if ([d isKindOfClass: Monster.class] &&
	    ((Monster *)d).monsterState == M_ATTACKING)
		n = 8;
	} else if ([d isKindOfClass:Monster.class] &&
	    ((Monster *)d).monsterState == M_PAIN) {
	else if ([d isKindOfClass: Monster.class] &&
	    ((Monster *)d).monsterState == M_PAIN)
		n = 10;
	} else if ((!d.move && !d.strafe) || !d.moving) {
	else if ((!d.move && !d.strafe) || !d.moving)
		n = 12;
	} else if (!d.onFloor && d.timeInAir > 100) {
	else if (!d.onFloor && d.timeInAir > 100)
		n = 18;
	} else {
	else {
		n = 14;
		speed = 1200 / d.maxSpeed * scale;
		if (hellpig)
			speed = 300 / d.maxSpeed;
	}
	if (hellpig) {
		n++;
		scale *= 32;
		mz -= 1.9f;
	}
	rendermodel(mdlname, frame[n], range[n], 0, 1.5f,
	    OFMakeVector3D(d.origin.x, mz, d.origin.y), d.yaw + 90, d.pitch / 2,
	    team, scale, speed, 0, basetime);
}

extern int democlientnum;

void
renderclients()
{
	[players
	    enumerateObjectsUsingBlock:^(Player *player, size_t i, bool *stop) {
		    if ([player isKindOfClass:Player.class] &&
		        (!demoplayback || i != democlientnum))
			    renderclient(player,
	[players enumerateObjectsUsingBlock: ^ (Player *player, size_t i,
	    bool *stop) {
		if ([player isKindOfClass: Player.class] &&
		    (!demoplayback || i != democlientnum))
			renderclient(player, isteam(Player.player1.team,
			        isteam(Player.player1.team, [player team]),
			        @"monster/ogro", false, 1.0f);
	    }];
			    [player team]), @"monster/ogro", false, 1.0f);
	}];
}

// creation of scoreboard pseudo-menu

bool scoreson = false;

void
showscores(bool on)
{
	scoreson = on;
	menuset(((int)on) - 1);
}

static OFMutableArray<OFString *> *scoreLines;

static void
renderscore(Player *d)
{
	OFString *lag = [OFString stringWithFormat:@"%d", d.lag];
	OFString *name = [OFString stringWithFormat:@"(%@)", d.name];
	OFString *line = [OFString stringWithFormat:@"%d\t%@\t%d\t%@\t%@",
	OFString *lag = [OFString stringWithFormat: @"%d", d.lag];
	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];
	[scoreLines addObject: line];

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

#define maxTeams 4
static OFString *teamName[maxTeams];
static int teamScore[maxTeams];
static size_t teamsUsed;

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

	if (teamsUsed == maxTeams)
		return;
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
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







-
+





-
+





-
-
+
+







-
+







{
	if (!scoreson)
		return;
	[scoreLines removeAllObjects];
	if (!demoplayback)
		renderscore(Player.player1);
	for (Player *player in players)
		if ([player isKindOfClass:Player.class])
		if ([player isKindOfClass: Player.class])
			renderscore(player);
	sortmenu();
	if (m_teammode) {
		teamsUsed = 0;
		for (Player *player in players)
			if ([player isKindOfClass:Player.class])
			if ([player isKindOfClass: Player.class])
				addteamscore(player);
		if (!demoplayback)
			addteamscore(Player.player1);
		OFMutableString *teamScores = [OFMutableString string];
		for (size_t j = 0; j < teamsUsed; j++)
			[teamScores appendFormat:@"[ %@: %d ]", teamName[j],
			    teamScore[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

COMMAND(sendmap, ARG_1STR, (^(OFString *mapname) {
COMMAND(sendmap, ARG_1STR, (^ (OFString *mapname) {
	if (mapname.length > 0)
		save_world(mapname);
	changemap(mapname);
	mapname = getclientmap();
	OFData *mapdata = readmap(mapname);
	if (mapdata == nil)
		return;
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
199
200
201
202
203
204
205

206


207

208
209
210

211
212
213
214
215
216
217
218
219
220
221







-
+
-
-
+
-



-
+










	}
	memcpy(p, mapdata.items, mapdata.count);
	p += mapdata.count;
	*(unsigned short *)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 *msg = [OFString stringWithFormat:
	    [OFString stringWithFormat:@"[map %@ uploaded to server, "
	                               @"\"getmap\" to receive it]",
	    @"[map %@ uploaded to server, \"getmap\" to receive it]", mapname];
	        mapname];
	toserver(msg);
}))

COMMAND(getmap, ARG_NONE, ^{
COMMAND(getmap, ARG_NONE, ^ {
	ENetPacket *packet =
	    enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
	unsigned char *start = packet->data;
	unsigned char *p = start + 2;
	putint(&p, SV_RECVMAP);
	*(unsigned short *)start = ENET_HOST_TO_NET_16(p - start);
	enet_packet_resize(packet, p - start);
	sendpackettoserv(packet);
	conoutf(@"requesting map from server...");
})

Modified src/clientgame.m from [591f2f2799] to [0b8b0d202d].

9
10
11
12
13
14
15
16

17
18
19
20
21
22
23
9
10
11
12
13
14
15

16
17
18
19
20
21
22
23







-
+







#import "OFString+Cube.h"
#import "Player.h"
#import "Variable.h"

int nextmode = 0; // nextmode becomes gamemode after next map load
VAR(gamemode, 1, 0, 0);

COMMAND(mode, ARG_1INT, ^(int n) {
COMMAND(mode, ARG_1INT, ^ (int n) {
	addmsg(1, 2, SV_GAMEMODE, nextmode = n);
})

bool intermission = false;

OFMutableArray *players; // other clients

49
50
51
52
53
54
55
56

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

56
57
58
59
60
61
62
63







-
+







}

static void
arenacount(Player *d, int *alive, int *dead, OFString **lastteam, bool *oneteam)
{
	if (d.state != CS_DEAD) {
		(*alive)++;
		if (![*lastteam isEqual:d.team])
		if (![*lastteam isEqual: d.team])
			*oneteam = false;
		*lastteam = d.team;
	} else
		(*dead)++;
}

int arenarespawnwait = 0;
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
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







-
-
-
-
+
+
+
+

-
-
-
-
-
+
+
+
+
+

-
-
-
-
-
+
+
+
+
+







}

extern int democlientnum;

void
otherplayers()
{
	[players
	    enumerateObjectsUsingBlock:^(Player *player, size_t i, bool *stop) {
		    if ([player isKindOfClass:Player.class])
			    return;
	[players enumerateObjectsUsingBlock: ^ (Player *player, size_t i,
	    bool *stop) {
		if ([player isKindOfClass: Player.class])
			return;

		    const int lagtime = lastmillis - player.lastUpdate;
		    if (lagtime > 1000 && player.state == CS_ALIVE) {
			    player.state = CS_LAGGED;
			    return;
		    }
		const int lagtime = lastmillis - player.lastUpdate;
		if (lagtime > 1000 && player.state == CS_ALIVE) {
			player.state = CS_LAGGED;
			return;
		}

		    if (lagtime && player.state != CS_DEAD &&
		        (!demoplayback || i != democlientnum))
			    // use physics to extrapolate player position
			    moveplayer(player, 2, false);
	    }];
		if (lagtime && player.state != CS_DEAD &&
		    (!demoplayback || i != democlientnum))
			// use physics to extrapolate player position
			moveplayer(player, 2, false);
	}];
}

void
respawn()
{
	if (Player.player1.state == CS_DEAD) {
		Player.player1.attacking = false;
136
137
138
139
140
141
142
143

144
145
146
147
148
149
150
136
137
138
139
140
141
142

143
144
145
146
147
148
149
150







-
+







		} // if we die in SP we try the same map again
		respawnself();
	}
}

int sleepwait = 0;
static OFString *sleepcmd = nil;
COMMAND(sleep, ARG_2STR, ^(OFString *msec, OFString *cmd) {
COMMAND(sleep, ARG_2STR, ^ (OFString *msec, OFString *cmd) {
	sleepwait = msec.cube_intValue + lastmillis;
	sleepcmd = cmd;
})

void
updateworld(int millis) // main game update loop
{
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
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







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







-
+








-
+




-
+







	entinmap(d);
	[d resetToSpawnState];
	d.state = CS_ALIVE;
}

// movement input code

#define dir(name, v, d, s, os)                                    \
	COMMAND(name, ARG_DOWN, ^(bool isDown) {                  \
		Player *player1 = Player.player1;                 \
		player1.s = isDown;                               \
		player1.v = isDown ? d : (player1.os ? -(d) : 0); \
		player1.lastMove = lastmillis;                    \
#define dir(name, v, d, s, os)						\
	COMMAND(name, ARG_DOWN, ^ (bool isDown) {			\
		Player *player1 = Player.player1;			\
		player1.s = isDown;					\
		player1.v = isDown ? d : (player1.os ? -(d) : 0);	\
		player1.lastMove = lastmillis;				\
	})

dir(backward, move, -1, k_down, k_up);
dir(forward, move, 1, k_up, k_down);
dir(left, strafe, 1, k_left, k_right);
dir(right, strafe, -1, k_right, k_left);

COMMAND(attack, ARG_DOWN, ^(bool on) {
COMMAND(attack, ARG_DOWN, ^ (bool on) {
	if (intermission)
		return;
	if (editmode)
		editdrag(on);
	else if ((Player.player1.attacking = on))
		respawn();
})

COMMAND(jump, ARG_DOWN, ^(bool on) {
COMMAND(jump, ARG_DOWN, ^ (bool on) {
	if (!intermission && (Player.player1.jumpNext = on))
		respawn();
})

COMMAND(showscores, ARG_DOWN, ^(bool isDown) {
COMMAND(showscores, ARG_DOWN, ^ (bool isDown) {
	showscores(isDown);
})

void
fixplayer1range()
{
	const float MAXPITCH = 90.0f;
372
373
374
375
376
377
378
379

380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396

397
398
399
400
401
402
403
372
373
374
375
376
377
378

379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395

396
397
398
399
400
401
402
403







-
+
















-
+







{
	if (cn < 0 || cn >= MAXCLIENTS) {
		neterr(@"clientnum");
		return nil;
	}

	while (cn >= players.count)
		[players addObject:[OFNull null]];
		[players addObject: [OFNull null]];

	id player = players[cn];
	if (player == [OFNull null]) {
		player = [Player player];
		players[cn] = player;
	}

	return player;
}

void
setclient(int cn, id client)
{
	if (cn < 0 || cn >= MAXCLIENTS)
		neterr(@"clientnum");
	while (cn >= players.count)
		[players addObject:[OFNull null]];
		[players addObject: [OFNull null]];
	players[cn] = client;
}

void
initclient()
{
	clientmap = @"";
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
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







-
+














-
+


	sleepwait = 0;
	[Monster resetAll];
	projreset();
	spawncycle = -1;
	spawnplayer(Player.player1);
	Player.player1.frags = 0;
	for (Player *player in players)
		if ([player isKindOfClass:Player.class])
		if ([player isKindOfClass: Player.class])
			player.frags = 0;
	resetspawns();
	clientmap = name;
	if (editmode)
		toggleedit();
	setvar(@"gamespeed", 100);
	setvar(@"fog", 180);
	setvar(@"fogcolour", 0x8099B3);
	showscores(false);
	intermission = false;
	Cube.sharedInstance.framesInMap = 0;
	conoutf(@"game mode is %@", modestr(gamemode));
}

COMMAND(map, ARG_1STR, ^(OFString *name) {
COMMAND(map, ARG_1STR, ^ (OFString *name) {
	changemap(name);
})

Modified src/clients.m from [0204d78717] to [0b6e5b2153].

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







-
+




-
+









-
+




-
+






-
-
+
+








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

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

	Player.player1.name = name;
}

COMMAND(name, ARG_1STR, ^(OFString *name) {
COMMAND(name, ARG_1STR, ^ (OFString *name) {
	newname(name);
})

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

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

	Player.player1.team = name;
}

COMMAND(team, ARG_1STR, ^(OFString *name) {
COMMAND(team, ARG_1STR, ^ (OFString *name) {
	newteam(name);
})

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

void
connects(OFString *servername)
{
	disconnect(true, false); // reset state
	addserver(servername);
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
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







-
+


-
+


-
+


-
+




















-
-
-
-
-
+
+
+
+
+





-
+







-
+















-
+







void
toserver(OFString *text)
{
	conoutf(@"%@:\f %@", Player.player1.name, text);
	ctext = text;
}

COMMAND(echo, ARG_VARI, ^(OFString *text) {
COMMAND(echo, ARG_VARI, ^ (OFString *text) {
	conoutf(@"%@", text);
})
COMMAND(say, ARG_VARI, ^(OFString *text) {
COMMAND(say, ARG_VARI, ^ (OFString *text) {
	toserver(text);
})
COMMAND(connect, ARG_1STR, ^(OFString *servername) {
COMMAND(connect, ARG_1STR, ^ (OFString *servername) {
	connects(servername);
})
COMMAND(disconnect, ARG_NONE, ^{
COMMAND(disconnect, ARG_NONE, ^ {
	trydisconnect();
})

// collect c2s messages conveniently

static OFMutableArray<OFData *> *messages;

void
addmsg(int rel, int num, int type, ...)
{
	if (demoplayback)
		return;
	if (num != msgsizelookup(type))
		fatal(@"inconsistant msg size for %d (%d != %d)", type, num,
		    msgsizelookup(type));
	if (messages.count == 100) {
		conoutf(@"command flood protection (type %d)", type);
		return;
	}

	OFMutableData *msg = [OFMutableData dataWithItemSize:sizeof(int)
	                                            capacity:num + 2];
	[msg addItem:&num];
	[msg addItem:&rel];
	[msg addItem:&type];
	OFMutableData *msg = [OFMutableData dataWithItemSize: sizeof(int)
	                                            capacity: num + 2];
	[msg addItem: &num];
	[msg addItem: &rel];
	[msg addItem: &type];

	va_list marker;
	va_start(marker, type);
	for (int i = 0; i < num - 1; i++) {
		int tmp = va_arg(marker, int);
		[msg addItem:&tmp];
		[msg addItem: &tmp];
	}
	va_end(marker);
	[msg makeImmutable];

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

	[messages addObject:msg];
	[messages addObject: msg];
}

void
server_err()
{
	conoutf(@"server network error, disconnecting...");
	disconnect(false, false);
}

int lastupdate = 0, lastping = 0;
OFString *toservermap;
bool senditemstoserver =
    false; // after a map change, since server doesn't have map data

OFString *clientpassword;
COMMAND(password, ARG_1STR, ^(OFString *p) {
COMMAND(password, ARG_1STR, ^ (OFString *p) {
	clientpassword = p;
})

bool
netmapstart()
{
	senditemstoserver = true;
313
314
315
316
317
318
319
320
321
322
323



324
325
326
327
328
329
330
313
314
315
316
317
318
319




320
321
322
323
324
325
326
327
328
329







-
-
-
-
+
+
+







		putint(&p, (int)(d.pitch * DAF));
		putint(&p, (int)(d.roll * DAF));
		// quantize to 1/100, almost always 1 byte
		putint(&p, (int)(d.velocity.x * DVF));
		putint(&p, (int)(d.velocity.y * DVF));
		putint(&p, (int)(d.velocity.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));
		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);
345
346
347
348
349
350
351
352

353
354
355


356
357
358
359
360
361
362
344
345
346
347
348
349
350

351
352


353
354
355
356
357
358
359
360
361







-
+

-
-
+
+







			putint(&p, SV_INITC2S);
			sendstring(Player.player1.name, &p);
			sendstring(Player.player1.team, &p);
			putint(&p, Player.player1.lifeSequence);
		}
		for (OFData *msg in messages) {
			// send messages collected during the previous frames
			if (*(int *)[msg itemAtIndex:1])
			if (*(int *)[msg itemAtIndex: 1])
				packet->flags = ENET_PACKET_FLAG_RELIABLE;
			for (int i = 0; i < *(int *)[msg itemAtIndex:0]; i++)
				putint(&p, *(int *)[msg itemAtIndex:i + 2]);
			for (int i = 0; i < *(int *)[msg itemAtIndex: 0]; i++)
				putint(&p, *(int *)[msg itemAtIndex: i + 2]);
		}
		[messages removeAllObjects];
		if (lastmillis - lastping > 250) {
			putint(&p, SV_PING);
			putint(&p, lastmillis);
			lastping = lastmillis;
		}

Modified src/clients2c.m from [476557dd41] to [11f87c82e6].

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







-
-
-
+
+
+
+
-










-
+







				if (mapchanged)
					setspawn(n, true);
			break;
		}
		// server requests next map
		case SV_MAPRELOAD: {
			getint(&p);
			OFString *nextmapalias = [OFString
			    stringWithFormat:@"nextmap_%@", getclientmap()];
			OFString *map =
			OFString *nextmapalias = [OFString stringWithFormat:
			    @"nextmap_%@", getclientmap()];
			// look up map in the cycle
			OFString *map = getalias(nextmapalias);
			    getalias(nextmapalias); // look up map in the cycle
			changemap(map != nil ? map : getclientmap());
			break;
		}

		// another client either connected or changed name/team
		case SV_INITC2S: {
			Player *d_ = (Player *)d;
			sgetstr();
			if (d_.name.length > 0) {
				// already connected
				if (![d_.name isEqual:@(text)])
				if (![d_.name isEqual: @(text)])
					conoutf(@"%@ is now known as %s",
					    d_.name, text);
			} else {
				// new client

				// send new players my info again
				c2sinit = false;
275
276
277
278
279
280
281
282

283
284
285
286
287
288
289
275
276
277
278
279
280
281

282
283
284
285
286
287
288
289







-
+







			OFVector3D loc = d_.origin;
			playsound(S_DIE1 + rnd(2), &loc);
			d_.lifeSequence++;
			break;
		}

		case SV_FRAGS:
			OFAssert([players[cn] isKindOfClass:Player.class]);
			OFAssert([players[cn] isKindOfClass: Player.class]);
			((Player *)players[cn]).frags = getint(&p);
			break;

		case SV_ITEMPICKUP:
			setspawn(getint(&p), false);
			getint(&p);
			break;
338
339
340
341
342
343
344
345

346
347
348
349
350
351
352
338
339
340
341
342
343
344

345
346
347
348
349
350
351
352







-
+







		case SV_EDITENT: // coop edit of ent
		{
			unsigned int i = getint(&p);

			while (ents.count <= i) {
				Entity *e = [Entity entity];
				e.type = NOTUSED;
				[ents addObject:e];
				[ents addObject: e];
			}

			int to = ents[i].type;
			ents[i].type = getint(&p);
			ents[i].x = getint(&p);
			ents[i].y = getint(&p);
			ents[i].z = getint(&p);
368
369
370
371
372
373
374
375

376
377
378
379
380
381
382
368
369
370
371
372
373
374

375
376
377
378
379
380
381
382







-
+







			addmsg(0, 2, SV_CLIENTPING,
			    Player.player1.ping = (Player.player1.ping * 5 +
			                              lastmillis - getint(&p)) /
			        6);
			break;

		case SV_CLIENTPING:
			OFAssert([players[cn] isKindOfClass:Player.class]);
			OFAssert([players[cn] isKindOfClass: Player.class]);
			((Player *)players[cn]).ping = getint(&p);
			break;

		case SV_GAMEMODE:
			nextmode = getint(&p);
			break;

Modified src/commands.m from [45f7f8cf15] to [e072f98aae].

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







-
-
-
+
+
+

-
+







-
+








-
+








-
+
















-
+








void
alias(OFString *name, OFString *action)
{
	Alias *alias = Identifier.identifiers[name];

	if (alias == nil)
		Identifier.identifiers[name] = [Alias aliasWithName:name
		                                             action:action
		                                          persisted:true];
		Identifier.identifiers[name] = [Alias aliasWithName: name
		                                             action: action
		                                          persisted: true];
	else {
		if ([alias isKindOfClass:Alias.class])
		if ([alias isKindOfClass: Alias.class])
			alias.action = action;
		else
			conoutf(
			    @"cannot redefine builtin %@ with an alias", name);
	}
}

COMMAND(alias, ARG_2STR, ^(OFString *name, OFString *action) {
COMMAND(alias, ARG_2STR, ^ (OFString *name, OFString *action) {
	alias(name, action);
})

void
setvar(OFString *name, int i)
{
	Variable *variable = Identifier.identifiers[name];

	if ([variable isKindOfClass:Variable.class])
	if ([variable isKindOfClass: Variable.class])
		*variable.storage = i;
}

int
getvar(OFString *name)
{
	Variable *variable = Identifier.identifiers[name];

	if ([variable isKindOfClass:Variable.class])
	if ([variable isKindOfClass: Variable.class])
		return *variable.storage;

	return 0;
}

bool
identexists(OFString *name)
{
	return (Identifier.identifiers[name] != nil);
}

OFString *
getalias(OFString *name)
{
	Alias *alias = Identifier.identifiers[name];

	if ([alias isKindOfClass:Alias.class])
	if ([alias isKindOfClass: Alias.class])
		return alias.action;

	return nil;
}

// parse any nested set of () or []
static char *
94
95
96
97
98
99
100
101
102


103
104
105
106
107
108
109
94
95
96
97
98
99
100


101
102
103
104
105
106
107
108
109







-
-
+
+







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







-
+

-
-
-
+
+
+
+


-
+









-
+









-
+


-
+

-
+







-
-
+
+


-
+




-
+







}

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

	if ([identifier isKindOfClass:Variable.class]) {
		return [OFString stringWithFormat:@"%d", *[identifier storage]];
	} else if ([identifier isKindOfClass:Alias.class])
	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]);
	conoutf(@"unknown alias lookup: %@", [n substringFromIndex: 1]);
	return n;
}

int
executeIdentifier(__kindof Identifier *identifier,
    OFArray<OFString *> *arguments, bool isDown)
{
	if (identifier == nil) {
		@try {
			return [arguments[0] intValueWithBase:0];
			return [arguments[0] intValueWithBase: 0];
		} @catch (OFInvalidFormatException *e) {
			conoutf(@"unknown command: %@", arguments[0]);
			return 0;
		} @catch (OFOutOfRangeException *e) {
			conoutf(@"invalid value: %@", arguments[0]);
			return 0;
		}
	}

	if ([identifier isKindOfClass:Command.class])
	if ([identifier isKindOfClass: Command.class])
		// game defined commands use very ad-hoc function signature,
		// and just call it
		return [identifier callWithArguments:arguments isDown:isDown];
		return [identifier callWithArguments: arguments isDown: isDown];

	if ([identifier isKindOfClass:Variable.class]) {
	if ([identifier isKindOfClass: Variable.class]) {
		if (!isDown)
			return 0;

		// game defined variables
		if (arguments.count < 2 || arguments[1].length == 0)
			[identifier printValue];
		else
			[identifier
			    setValue:[arguments[1] cube_intValueWithBase:0]];
			[identifier setValue:
			    [arguments[1] cube_intValueWithBase: 0]];
	}

	if ([identifier isKindOfClass:Alias.class]) {
	if ([identifier isKindOfClass: Alias.class]) {
		// alias, also used as functions and (global) variables
		for (int i = 1; i < arguments.count; i++) {
			// set any arguments as (global) arg values so
			// functions can access them
			OFString *t = [OFString stringWithFormat:@"arg%d", i];
			OFString *t = [OFString stringWithFormat: @"arg%d", i];
			alias(t, arguments[i]);
		}

		return execute([identifier action], isDown);
	}

	return 0;
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
396
397
398
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
431
432
433
434
435
436
437
438
439
440
441

442
443
444
445
446
447
448
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
485
486
487


488
489
490

491
492
493
494

495
496
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
396

397
398
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
431
432
433
434
435
436
437
438
439
440
441
442
443

444
445
446
447
448
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

485
486
487
488


489
490
491
492

493
494
495
496

497
498
499







-
-
+
+







-
+


















-
-
+
+










-
-
+
+

-
+
-
-
-
+
+
+













-
+














-
+

-
+



-
+









-
-
-
+
+
+
+




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

-
+

-
-
-
+
+
+



-
-
+
+

-
+


-
+

-
-
-
-
+
+
+
+


-
-
+
+





-
+










-
+


-
-
+
+


-
+








-
+




-
+










-
+



-
-
+
+


-
+













-
+












-
+



-
+



-
+



-
+



-
+



-
+



-
+



-
+



-
-
+
+


-
+



-
+


		}

		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];
		if ([c hasPrefix: @"/"]) {
			c = [c substringFromIndex: 1];
			w[0] = c;
		}
		// empty statement
		if (c.length == 0)
			continue;

		val = executeIdentifier(Identifier.identifiers[c],
		    [OFArray arrayWithObjects:w count:numargs], isDown);
		    [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 hasPrefix: @"/"])
		[s insertString: @"/" atIndex: 0];

	if (s.length == 1)
		return;

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

	__block int idx = 0;
	[Identifier.identifiers enumerateKeysAndObjectsUsingBlock:^(
	    OFString *name, __kindof Identifier *identifier, bool *stop) {
	[Identifier.identifiers enumerateKeysAndObjectsUsingBlock:
	    ^ (OFString *name, __kindof Identifier *identifier, bool *stop) {
		if (strncmp(identifier.name.UTF8String, s.UTF8String + 1,
		        completesize) == 0 &&
		    completesize) == 0 && idx++ == completeidx)
		    idx++ == completeidx)
			[s replaceCharactersInRange:OFMakeRange(1, s.length - 1)
			                 withString:identifier.name];
			[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];
		command = [OFString stringWithContentsOfIRI: cfgfile];
	} @catch (OFOpenItemFailedException *e) {
		return false;
	} @catch (OFReadFailedException *e) {
		return false;
	}

	execute(command, true);
	return true;
}

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

COMMAND(exec, ARG_1STR, ^(OFString *cfgfile) {
COMMAND(exec, ARG_1STR, ^ (OFString *cfgfile) {
	exec(cfgfile);
})

void
writecfg()
{
	OFStream *stream;
	@try {
		OFIRI *IRI = [Cube.sharedInstance.userDataIRI
		    IRIByAppendingPathComponent:@"config.cfg"];
		stream = [[OFIRIHandler handlerForIRI:IRI] openItemAtIRI:IRI
		                                                    mode:@"w"];
		    IRIByAppendingPathComponent: @"config.cfg"];
		stream = [[OFIRIHandler handlerForIRI: IRI]
		    openItemAtIRI: IRI
			     mode: @"w"];
	} @catch (id e) {
		return;
	}

	[stream writeString:
	[stream writeString:@"// automatically written on exit, do not modify\n"
	                    @"// delete this file to have defaults.cfg "
	                    @"overwrite these settings\n"
	                    @"// modify settings in game, or put settings in "
	                    @"autoexec.cfg to override anything\n"
	                    @"\n"];
	    @"// automatically written on exit, do not modify\n"
	    @"// delete this file to have defaults.cfg overwrite these "
	    @"settings\n"
	    @"// modify settings in game, or put settings in autoexec.cfg to "
	    @"override anything\n"
	    @"\n"];
	writeclientinfo(stream);
	[stream writeString:@"\n"];
	[stream writeString: @"\n"];

	[Identifier.identifiers enumerateKeysAndObjectsUsingBlock:^(
	    OFString *name, __kindof Identifier *identifier, bool *stop) {
		if (![identifier isKindOfClass:Variable.class] ||
	[Identifier.identifiers enumerateKeysAndObjectsUsingBlock:
	    ^ (OFString *name, __kindof Identifier *identifier, bool *stop) {
		if (![identifier isKindOfClass: Variable.class] ||
		    ![identifier persisted])
			return;

		[stream writeFormat:@"%@ %d\n", identifier.name,
		    *[identifier storage]];
		[stream writeFormat:
		    @"%@ %d\n", identifier.name, *[identifier storage]];
	}];
	[stream writeString:@"\n"];
	[stream writeString: @"\n"];

	writebinds(stream);
	[stream writeString:@"\n"];
	[stream writeString: @"\n"];

	[Identifier.identifiers enumerateKeysAndObjectsUsingBlock:^(
	    OFString *name, __kindof Identifier *identifier, bool *stop) {
		if (![identifier isKindOfClass:Alias.class] ||
		    [identifier.name hasPrefix:@"nextmap_"])
	[Identifier.identifiers enumerateKeysAndObjectsUsingBlock:
	    ^ (OFString *name, Alias *alias, bool *stop) {
		if (![alias isKindOfClass: Alias.class] ||
		    [alias.name hasPrefix: @"nextmap_"])
			return;

		[stream writeFormat:@"alias \"%@\" [%@]\n", identifier.name,
		    [identifier action]];
		[stream writeFormat: @"alias \"%@\" [%@]\n",
				     alias.name, alias.action];
	}];

	[stream close];
}

COMMAND(writecfg, ARG_NONE, ^{
COMMAND(writecfg, ARG_NONE, ^ {
	writecfg();
})

// 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]);
	alias(name, [OFString stringWithFormat: @"%d", v]);
}

COMMAND(if, ARG_3STR, ^(OFString *cond, OFString *thenp, OFString *elsep) {
	execute((![cond hasPrefix:@"0"] ? thenp : elsep), true);
COMMAND(if, ARG_3STR, ^ (OFString *cond, OFString *thenp, OFString *elsep) {
	execute((![cond hasPrefix: @"0"] ? thenp : elsep), true);
})

COMMAND(loop, ARG_2STR, ^(OFString *times, OFString *body) {
COMMAND(loop, ARG_2STR, ^ (OFString *times, OFString *body) {
	int t = times.cube_intValue;

	for (int i = 0; i < t; i++) {
		intset(@"i", i);
		execute(body, true);
	}
})

COMMAND(while, ARG_2STR, ^(OFString *cond, OFString *body) {
COMMAND(while, ARG_2STR, ^ (OFString *cond, OFString *body) {
	while (execute(cond, true))
		execute(body, true);
})

COMMAND(onrelease, ARG_DWN1, ^(bool on, OFString *body) {
COMMAND(onrelease, ARG_DWN1, ^ (bool on, OFString *body) {
	if (!on)
		execute(body, true);
})

void
concat(OFString *s)
{
	alias(@"s", s);
}

COMMAND(concat, ARG_VARI, ^(OFString *s) {
COMMAND(concat, ARG_VARI, ^ (OFString *s) {
	concat(s);
})

COMMAND(concatword, ARG_VARI, ^(OFString *s) {
	concat([s stringByReplacingOccurrencesOfString:@" " withString:@""]);
COMMAND(concatword, ARG_VARI, ^ (OFString *s) {
	concat([s stringByReplacingOccurrencesOfString: @" " withString: @""]);
})

COMMAND(listlen, ARG_1EST, ^(OFString *a_) {
COMMAND(listlen, ARG_1EST, ^ (OFString *a_) {
	const char *a = a_.UTF8String;

	if (!*a)
		return 0;

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

	return n + 1;
})

COMMAND(at, ARG_2STR, ^(OFString *s_, OFString *pos) {
COMMAND(at, ARG_2STR, ^ (OFString *s_, OFString *pos) {
	int n = pos.cube_intValue;
	char *copy __attribute__((__cleanup__(cleanup))) =
	    strdup(s_.UTF8String);
	char *s = copy;
	for (int i = 0; i < n; i++) {
		s += strcspn(s, " \0");
		s += strspn(s, " ");
	}
	s[strcspn(s, " \0")] = 0;
	concat(@(s));
})

COMMAND(+, ARG_2EXP, ^(int a, int b) {
COMMAND(+, ARG_2EXP, ^ (int a, int b) {
	return a + b;
})

COMMAND(*, ARG_2EXP, ^(int a, int b) {
COMMAND(*, ARG_2EXP, ^ (int a, int b) {
	return a * b;
})

COMMAND(-, ARG_2EXP, ^(int a, int b) {
COMMAND(-, ARG_2EXP, ^ (int a, int b) {
	return a - b;
})

COMMAND(div, ARG_2EXP, ^(int a, int b) {
COMMAND(div, ARG_2EXP, ^ (int a, int b) {
	return b ? a / b : 0;
})

COMMAND(mod, ARG_2EXP, ^(int a, int b) {
COMMAND(mod, ARG_2EXP, ^ (int a, int b) {
	return b ? a % b : 0;
})

COMMAND(=, ARG_2EXP, ^(int a, int b) {
COMMAND(=, ARG_2EXP, ^ (int a, int b) {
	return (int)(a == b);
})

COMMAND(<, ARG_2EXP, ^(int a, int b) {
COMMAND(<, ARG_2EXP, ^ (int a, int b) {
	return (int)(a < b);
})

COMMAND(>, ARG_2EXP, ^(int a, int b) {
COMMAND(>, ARG_2EXP, ^ (int a, int b) {
	return (int)(a > b);
})

COMMAND(strcmp, ARG_2EST, ^(OFString *a, OFString *b) {
	return [a isEqual:b];
COMMAND(strcmp, ARG_2EST, ^ (OFString *a, OFString *b) {
	return [a isEqual: b];
})

COMMAND(rnd, ARG_1EXP, ^(int a) {
COMMAND(rnd, ARG_1EXP, ^ (int a) {
	return (a > 0 ? rnd(a) : 0);
})

COMMAND(millis, ARG_1EXP, ^(int unused) {
COMMAND(millis, ARG_1EXP, ^ (int unused) {
	return lastmillis;
})

Modified src/console.m from [a83084fb6c] to [1d463d8f9f].

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
72


73
74
75
76
77
78
79
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
72
73
74
75
76
77
78
79







-
+



















-
+

-
+




-
-
-
+
+
+













-
-
+
+





-
-
+
+







const int ndraw = 5;
const int WORDWRAP = 80;
int conskip = 0;

bool saycommandon = false;
static OFMutableString *commandbuf;

COMMAND(conskip, ARG_1INT, ^(int n) {
COMMAND(conskip, ARG_1INT, ^ (int n) {
	conskip += n;
	if (conskip < 0)
		conskip = 0;
})

static void
conline(OFString *sf, bool highlight) // add a line to the console buffer
{
	OFMutableString *text;

	// constrain the buffer size
	if (conlines.count > 100) {
		text = [conlines.lastObject.text mutableCopy];
		[conlines removeLastObject];
	} else
		text = [OFMutableString string];

	if (highlight)
		// show line in a different colour, for chat etc.
		[text appendString:@"\f"];
		[text appendString: @"\f"];

	[text appendString:sf];
	[text appendString: sf];

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

	[conlines insertObject:[ConsoleLine lineWithText:text
	                                         outtime:lastmillis]
	               atIndex:0];
	[conlines insertObject: [ConsoleLine lineWithText: text
						  outtime: lastmillis]
		       atIndex: 0];

	puts(text.UTF8String);
#ifndef OF_WINDOWS
	fflush(stdout);
#endif
}

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

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

	va_end(arguments);

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

// render buffer taking into account time & scrolling
void
renderconsole()
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
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







-
+



-
-
+
+

-
+


-
+

-
+







		    (FONTH / 4 * 5) * (nd - j - 1) + FONTH / 3, 2);
}

// keymap is defined externally in keymap.cfg

static OFMutableArray<KeyMapping *> *keyMappings = nil;

COMMAND(keymap, ARG_3STR, ^(OFString *code, OFString *key, OFString *action) {
COMMAND(keymap, ARG_3STR, ^ (OFString *code, OFString *key, OFString *action) {
	if (keyMappings == nil)
		keyMappings = [[OFMutableArray alloc] init];

	KeyMapping *mapping = [KeyMapping mappingWithCode:code.cube_intValue
	                                             name:key];
	KeyMapping *mapping = [KeyMapping mappingWithCode: code.cube_intValue
	                                             name: key];
	mapping.action = action;
	[keyMappings addObject:mapping];
	[keyMappings addObject: mapping];
})

COMMAND(bind, ARG_2STR, ^(OFString *key, OFString *action) {
COMMAND(bind, ARG_2STR, ^ (OFString *key, OFString *action) {
	for (KeyMapping *mapping in keyMappings) {
		if ([mapping.name caseInsensitiveCompare:key] ==
		if ([mapping.name caseInsensitiveCompare: key] ==
		    OFOrderedSame) {
			mapping.action = action;
			return;
		}
	}

	conoutf(@"unknown key \"%@\"", key);
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
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







-
+



-
+







-
+





-
+








	if (init == nil)
		init = @"";

	commandbuf = [init mutableCopy];
}

COMMAND(saycommand, ARG_VARI, ^(OFString *init) {
COMMAND(saycommand, ARG_VARI, ^ (OFString *init) {
	saycommand(init);
})

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

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

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

COMMAND(history, ARG_1INT, ^(int n) {
COMMAND(history, ARG_1INT, ^ (int n) {
	static bool rec = false;

	if (!rec && n >= 0 && n < vhistory.count) {
		rec = true;
		execute(vhistory[vhistory.count - n - 1], true);
		rec = false;
	}
222
223
224
225
226
227
228
229
230


231
232
233


234
235
236

237
238
239
240
241
242
243
222
223
224
225
226
227
228


229
230
231


232
233
234
235

236
237
238
239
240
241
242
243







-
-
+
+

-
-
+
+


-
+







				if (commandbuf.length > 0) {
					if (vhistory == nil)
						vhistory =
						    [[OFMutableArray alloc]
						        init];

					if (vhistory.count == 0 ||
					    ![vhistory.lastObject
					        isEqual:commandbuf]) {
					    ![vhistory.lastObject isEqual:
					    commandbuf]) {
						// cap this?
						[vhistory addObject:[commandbuf
						                        copy]];
						[vhistory addObject:
						    [commandbuf copy]];
					}
					histpos = vhistory.count;
					if ([commandbuf hasPrefix:@"/"])
					if ([commandbuf hasPrefix: @"/"])
						execute(commandbuf, true);
					else
						toserver(commandbuf);
				}
				saycommand(NULL);
			} else if (code == SDLK_ESCAPE) {
				saycommand(NULL);
257
258
259
260
261
262
263
264

265
266
267
268
269
270
271
272
273
274
275
276
277
278
279


280
257
258
259
260
261
262
263

264
265
266
267
268
269
270
271
272
273
274
275
276
277


278
279
280







-
+













-
-
+
+

	}
}

void
input(OFString *text)
{
	if (saycommandon)
		[commandbuf appendString:text];
		[commandbuf appendString: text];
}

OFString *
getcurcommand()
{
	return saycommandon ? commandbuf : NULL;
}

void
writebinds(OFStream *stream)
{
	for (KeyMapping *mapping in keyMappings)
		if (mapping.action.length > 0)
			[stream writeFormat:@"bind \"%@\" [%@]\n", mapping.name,
			    mapping.action];
			[stream writeFormat: @"bind \"%@\" [%@]\n",
					     mapping.name, mapping.action];
}

Modified src/cube.h from [8e48390c7e] to [e6d07ee71e].

306
307
308
309
310
311
312
313

314
315
316
317
318
319
320
306
307
308
309
310
311
312

313
314
315
316
317
318
319
320







-
+







#define m_noitemsrail (gamemode <= 5)
#define m_arena (gamemode >= 8)
#define m_tarena (gamemode >= 10)
#define m_teammode (gamemode & 1 && gamemode > 2)
#define m_sp (gamemode < 0)
#define m_dmsp (gamemode == -1)
#define m_classicsp (gamemode == -2)
#define isteam(a, b) (m_teammode && [a isEqual:b])
#define isteam(a, b) (m_teammode && [a isEqual: b])

// function signatures for script functions, see command.mm
enum {
	ARG_1INT,
	ARG_2INT,
	ARG_3INT,
	ARG_4INT,

Modified src/editing.m from [bc0b6a3e18] to [1510ae8332].

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







-
+







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







// invariant: all code assumes that these are kept inside MINBORD distance of
// the edge of the map

struct block sel = { 0, 0, 0, 0 };

OF_CONSTRUCTOR()
{
	enqueueInit(^{
	enqueueInit(^ {
		static const struct {
			OFString *name;
			int *storage;
		} vars[4] = { { @"selx", &sel.x }, { @"sely", &sel.y },
			{ @"selxs", &sel.xs }, { @"selys", &sel.ys } };

		for (size_t i = 0; i < 4; i++) {
			Variable *variable =
			    [Variable variableWithName:vars[i].name
			                           min:0
			                           max:4096
			                       storage:vars[i].storage
			                      function:NULL
			                     persisted:false];
			Variable *variable = [Variable
			    variableWithName: vars[i].name
					 min: 0
					 max: 4096
				     storage: vars[i].storage
				    function: NULL
				   persisted: false];
			Identifier.identifiers[vars[i].name] = variable;
		}
	});
}

int selh = 0;
bool selset = false;
89
90
91
92
93
94
95
96

97
98
99
100
101
102
103
89
90
91
92
93
94
95

96
97
98
99
100
101
102
103







-
+







		projreset();
	}
	Cube.sharedInstance.repeatsKeys = editmode;
	selset = false;
	editing = editmode;
}

COMMAND(edittoggle, ARG_NONE, ^{
COMMAND(edittoggle, ARG_NONE, ^ {
	toggleedit();
})

void
correctsel() // ensures above invariant
{
	selset = !OUTBORD(sel.x, sel.y);
133
134
135
136
137
138
139
140

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

140
141
142
143
144
145
146
147







-
+







#define EDITSELMP                                            \
	if (noteditmode() || noselection() || multiplayer()) \
		return;
#define EDITMP                              \
	if (noteditmode() || multiplayer()) \
		return;

COMMAND(select, ARG_4INT, (^(int x, int y, int xs, int ys) {
COMMAND(select, ARG_4INT, (^ (int x, int y, int xs, int ys) {
	struct block s = { x, y, xs, ys };
	sel = s;
	selh = 0;
	correctsel();
}))

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







-
-
-
-
+
+
+
+

-
-
-
-
+
+
+
+




-
-
-
-
+
+
+
+







			if (SOLID(s))
				continue;
			float h1 = sheight(s, s, z);
			float h2 = sheight(s, SWS(s, 1, 0, ssize), z);
			float h3 = sheight(s, SWS(s, 1, 1, ssize), z);
			float h4 = sheight(s, SWS(s, 0, 1, ssize), z);
			if (s->tag)
				linestyle(GRIDW, [OFColor colorWithRed:1.0f
								 green:0.25f
								  blue:0.25f
								 alpha:1.0f]);
				linestyle(GRIDW, [OFColor colorWithRed: 1.0f
								 green: 0.25f
								  blue: 0.25f
								 alpha: 1.0f]);
			else if (s->type == FHF || s->type == CHF)
				linestyle(GRIDW, [OFColor colorWithRed:0.5f
								 green:1.0f
								  blue:0.5f
								 alpha:1.0f]);
				linestyle(GRIDW, [OFColor colorWithRed: 0.5f
								 green: 1.0f
								  blue: 0.5f
								 alpha: 1.0f]);
			else
				linestyle(GRIDW, OFColor.gray);
			struct block b = { ix, iy, 1, 1 };
			box(&b, h1, h2, h3, h4);
			linestyle(GRID8, [OFColor colorWithRed:0.25f
							 green:0.25f
							  blue:1.0f
							 alpha:1.0f]);
			linestyle(GRID8, [OFColor colorWithRed: 0.25f
							 green: 0.25f
							  blue: 1.0f
							 alpha: 1.0f]);
			if (!(ix & GRIDM))
				line(ix, iy, h1, ix, iy + 1, h4);
			if (!(ix + 1 & GRIDM))
				line(ix + 1, iy, h2, ix + 1, iy + 1, h3);
			if (!(iy & GRIDM))
				line(ix, iy, h1, ix + 1, iy, h2);
			if (!(iy + 1 & GRIDM))
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
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







-
-
-
-
+
+
+
+












-
+




-
+









-
+


-
+



-
+













-
+








-
+







		    sheight(s, SWS(s, 0, 1, ssize), z));
		linestyle(GRIDS, OFColor.red);
		dot(cx, cy, ih);
		ch = (int)ih;
	}

	if (selset) {
		linestyle(GRIDS, [OFColor colorWithRed:1.0f
						 green:0.25f
						  blue:0.25f
						 alpha:1.0f]);
		linestyle(GRIDS, [OFColor colorWithRed: 1.0f
						 green: 0.25f
						  blue: 0.25f
						 alpha: 1.0f]);
		box(&sel, (float)selh, (float)selh, (float)selh, (float)selh);
	}
}

static OFMutableData *undos; // unlimited undo
VARP(undomegs, 0, 1, 10);    // bounded by n megs

void
pruneundos(int maxremain) // bound memory
{
	int t = 0;
	for (ssize_t i = (ssize_t)undos.count - 1; i >= 0; i--) {
		struct block *undo = [undos mutableItemAtIndex:i];
		struct block *undo = [undos mutableItemAtIndex: i];

		t += undo->xs * undo->ys * sizeof(struct sqr);
		if (t > maxremain) {
			OFFreeMemory(undo);
			[undos removeItemAtIndex:i];
			[undos removeItemAtIndex: i];
		}
	}
}

void
makeundo()
{
	if (undos == nil)
		undos = [[OFMutableData alloc]
		    initWithItemSize:sizeof(struct block *)];
		    initWithItemSize: sizeof(struct block *)];

	struct block *copy = blockcopy(&sel);
	[undos addItem:&copy];
	[undos addItem: &copy];
	pruneundos(undomegs << 20);
}

COMMAND(undo, ARG_NONE, ^{
COMMAND(undo, ARG_NONE, ^ {
	EDITMP;
	if (undos.count == 0) {
		conoutf(@"nothing more to undo");
		return;
	}
	struct block *p = undos.mutableLastItem;
	[undos removeLastItem];
	blockpaste(p);
	OFFreeMemory(p);
})

static struct block *copybuf = NULL;

COMMAND(copy, ARG_NONE, ^{
COMMAND(copy, ARG_NONE, ^ {
	EDITSELMP;

	if (copybuf)
		OFFreeMemory(copybuf);

	copybuf = blockcopy(&sel);
})

COMMAND(paste, ARG_NONE, ^{
COMMAND(paste, ARG_NONE, ^ {
	EDITMP;
	if (!copybuf) {
		conoutf(@"nothing to paste");
		return;
	}
	sel.xs = copybuf->xs;
	sel.ys = copybuf->ys;
397
398
399
400
401
402
403
404

405
406
407
408
409
410
411
397
398
399
400
401
402
403

404
405
406
407
408
409
410
411







-
+







	EDITSEL;

	bool isfloor = flr == 0;
	editheightxy(isfloor, amount, &sel);
	addmsg(1, 7, SV_EDITH, sel.x, sel.y, sel.xs, sel.ys, isfloor, amount);
}

COMMAND(editheight, ARG_2INT, ^(int flr, int amount) {
COMMAND(editheight, ARG_2INT, ^ (int flr, int amount) {
	editheight(flr, amount);
})

void
edittexxy(int type, int t, const struct block *sel)
{
	loopselxy(switch (type) {
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
450
451
452
453
454
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
450
451
452
453
454







-
+



















-
+







		        break;
	        case 3:
		        s->utex = t;
		        break;
	});
}

COMMAND(edittex, ARG_2INT, ^(int type, int dir) {
COMMAND(edittex, ARG_2INT, ^ (int type, int dir) {
	EDITSEL;

	if (type < 0 || type > 3)
		return;

	if (type != lasttype) {
		tofronttex();
		lasttype = type;
	}

	int atype = type == 3 ? 1 : type;
	int i = curedittex[atype];
	i = i < 0 ? 0 : i + dir;
	curedittex[atype] = i = min(max(i, 0), 255);
	int t = lasttex = hdr.texlists[atype][i];
	edittexxy(type, t, &sel);
	addmsg(1, 7, SV_EDITT, sel.x, sel.y, sel.xs, sel.ys, type, t);
})

COMMAND(replace, ARG_NONE, (^{
COMMAND(replace, ARG_NONE, (^ {
	EDITSELMP;

	for (int x = 0; x < ssize; x++) {
		for (int y = 0; y < ssize; y++) {
			struct sqr *s = S(x, y);
			switch (lasttype) {
			case 0:
493
494
495
496
497
498
499
500

501
502
503
504

505
506
507
508

509
510
511
512
513
514
515
493
494
495
496
497
498
499

500
501
502
503

504
505
506
507

508
509
510
511
512
513
514
515







-
+



-
+



-
+







		return;
	}

	edittypexy(type, &sel);
	addmsg(1, 6, SV_EDITS, sel.x, sel.y, sel.xs, sel.ys, type);
}

COMMAND(heightfield, ARG_1INT, ^(int t) {
COMMAND(heightfield, ARG_1INT, ^ (int t) {
	edittype(t == 0 ? FHF : CHF);
})

COMMAND(solid, ARG_1INT, ^(int t) {
COMMAND(solid, ARG_1INT, ^ (int t) {
	edittype(t == 0 ? SPACE : SOLID);
})

COMMAND(corner, ARG_NONE, ^{
COMMAND(corner, ARG_NONE, ^ {
	edittype(CORNER);
})

void
editequalisexy(bool isfloor, const struct block *sel)
{
	int low = 127, hi = -128;
525
526
527
528
529
530
531
532

533
534
535
536
537
538
539
540
541
542
543
544
545
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
525
526
527
528
529
530
531

532
533
534
535
536
537
538
539
540
541
542
543
544
545
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







-
+















-
+










-
+











-
+







		else
			s->ceil = hi;
		if (s->floor >= s->ceil)
			s->floor = s->ceil - 1;
	});
}

COMMAND(equalize, ARG_1INT, ^(int flr) {
COMMAND(equalize, ARG_1INT, ^ (int flr) {
	bool isfloor = (flr == 0);

	EDITSEL;

	editequalisexy(isfloor, &sel);
	addmsg(1, 6, SV_EDITE, sel.x, sel.y, sel.xs, sel.ys, isfloor);
})

void
setvdeltaxy(int delta, const struct block *sel)
{
	loopselxy(s->vdelta = max(s->vdelta + delta, 0));
	remipmore(sel, 0);
}

COMMAND(vdelta, ARG_1INT, ^(int delta) {
COMMAND(vdelta, ARG_1INT, ^ (int delta) {
	EDITSEL;

	setvdeltaxy(delta, &sel);
	addmsg(1, 6, SV_EDITD, sel.x, sel.y, sel.xs, sel.ys, delta);
})

#define MAXARCHVERT 50
int archverts[MAXARCHVERT][MAXARCHVERT];
bool archvinit = false;

COMMAND(archvertex, ARG_3INT, ^(int span, int vert, int delta) {
COMMAND(archvertex, ARG_3INT, ^ (int span, int vert, int delta) {
	if (!archvinit) {
		archvinit = true;
		for (int s = 0; s < MAXARCHVERT; s++)
			for (int v = 0; v < MAXARCHVERT; v++)
				archverts[s][v] = 0;
	}
	if (span >= MAXARCHVERT || vert >= MAXARCHVERT || span < 0 || vert < 0)
		return;
	archverts[span][vert] = delta;
})

COMMAND(arch, ARG_2INT, ^(int sidedelta, int _a) {
COMMAND(arch, ARG_2INT, ^ (int sidedelta, int _a) {
	EDITSELMP;

	sel.xs++;
	sel.ys++;

	if (sel.xs > MAXARCHVERT)
		sel.xs = MAXARCHVERT;
586
587
588
589
590
591
592
593

594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612

613
614
615
616
617
618
619
586
587
588
589
590
591
592

593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611

612
613
614
615
616
617
618
619







-
+


















-
+







	        ? (archverts[sel->xs - 1][x] +
	              (y == 0 || y == sel->ys - 1 ? sidedelta : 0))
	        : (archverts[sel->ys - 1][y] +
	              (x == 0 || x == sel->xs - 1 ? sidedelta : 0)));
	remipmore(sel, 0);
})

COMMAND(slope, ARG_2INT, ^(int xd, int yd) {
COMMAND(slope, ARG_2INT, ^ (int xd, int yd) {
	EDITSELMP;

	int off = 0;
	if (xd < 0)
		off -= xd * sel.xs;
	if (yd < 0)
		off -= yd * sel.ys;

	sel.xs++;
	sel.ys++;

	struct block *sel_ = &sel;
	// Ugly hack to make the macro work.
	struct block *sel = sel_;
	loopselxy(s->vdelta = xd * x + yd * y + off);
	remipmore(sel, 0);
})

COMMAND(perlin, ARG_3INT, ^(int scale, int seed, int psize) {
COMMAND(perlin, ARG_3INT, ^ (int scale, int seed, int psize) {
	EDITSELMP;

	sel.xs++;
	sel.ys++;

	makeundo();

635
636
637
638
639
640
641
642

643
644
645
646
647
648
649
650
651
652
653


654
655
656
657
658




635
636
637
638
639
640
641

642
643
644
645
646
647
648
649
650
651


652
653
654




655
656
657
658







-
+









-
-
+
+

-
-
-
-
+
+
+
+
    fullbright, 0, 0, 1, if (fullbright) {
	    if (noteditmode())
		    return;
	    for (int i = 0; i < mipsize; i++)
		    world[i].r = world[i].g = world[i].b = 176;
    });

COMMAND(edittag, ARG_1INT, ^(int tag) {
COMMAND(edittag, ARG_1INT, ^ (int tag) {
	EDITSELMP;

	struct block *sel_ = &sel;
	// Ugly hack to make the macro work.
	struct block *sel = sel_;
	loopselxy(s->tag = tag);
})

COMMAND(newent, ARG_5STR,
    ^(OFString *what, OFString *a1, OFString *a2, OFString *a3, OFString *a4) {
	    EDITSEL;
    ^ (OFString *what, OFString *a1, OFString *a2, OFString *a3, OFString *a4) {
	EDITSEL;

	    newentity(sel.x, sel.y, (int)Player.player1.origin.z, what,
	        [a1 cube_intValueWithBase:0], [a2 cube_intValueWithBase:0],
	        [a3 cube_intValueWithBase:0], [a4 cube_intValueWithBase:0]);
    })
	newentity(sel.x, sel.y, (int)Player.player1.origin.z, what,
	    [a1 cube_intValueWithBase: 0], [a2 cube_intValueWithBase: 0],
	    [a3 cube_intValueWithBase: 0], [a4 cube_intValueWithBase: 0]);
})

Modified src/entities.m from [e376d54115] to [945da2b6da].

309
310
311
312
313
314
315
316

317
318
319
320
321
322
323
309
310
311
312
313
314
315

316
317
318
319
320
321
322
323







-
+







checkitems()
{
	Player *player1 = Player.player1;

	if (editmode)
		return;

	[ents enumerateObjectsUsingBlock:^(Entity *e, size_t i, bool *stop) {
	[ents enumerateObjectsUsingBlock: ^ (Entity *e, size_t i, bool *stop) {
		if (e.type == NOTUSED)
			return;

		if (!e.spawned && e.type != TELEPORT && e.type != JUMPPAD)
			return;

		if (OUTBORD(e.x, e.y))
344
345
346
347
348
349
350
351

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

351
352
353
354
355
356
357
358







-
+







	}
}

// puts items in network stream and also spawns them locally
void
putitems(unsigned char **p)
{
	[ents enumerateObjectsUsingBlock:^(Entity *e, size_t i, bool *stop) {
	[ents enumerateObjectsUsingBlock: ^ (Entity *e, size_t i, bool *stop) {
		if ((e.type >= I_SHELLS && e.type <= I_QUAD) ||
		    e.type == CARROT) {
			putint(p, i);
			e.spawned = true;
		}
	}];
}

Modified src/menus.m from [c38d6c5a56] to [abea91099d].

17
18
19
20
21
22
23
24

25
26
27

28
29
30
31
32
33
34
17
18
19
20
21
22
23

24
25
26

27
28
29
30
31
32
33
34







-
+


-
+







{
	if ((vmenu = menu) >= 1)
		[Player.player1 resetMovement];
	if (vmenu == 1)
		menus[1].menusel = 0;
}

COMMAND(showmenu, ARG_1STR, ^(OFString *name) {
COMMAND(showmenu, ARG_1STR, ^ (OFString *name) {
	int i = 0;
	for (Menu *menu in menus) {
		if (i > 1 && [menu.name isEqual:name]) {
		if (i > 1 && [menu.name isEqual: name]) {
			menuset(i);
			return;
		}
		i++;
	}
})

50
51
52
53
54
55
56
57

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

57
58
59
60
61
62
63
64







-
+








	if (vmenu == 1)
		refreshservers();

	Menu *m = menus[vmenu];
	OFString *title;
	if (vmenu > 1)
		title = [OFString stringWithFormat:@"[ %@ menu ]", m.name];
		title = [OFString stringWithFormat: @"[ %@ menu ]", m.name];
	else
		title = m.name;
	int mdisp = m.items.count;
	int w = 0;
	for (int i = 0; i < mdisp; i++) {
		int x = text_width(m.items[i].text);
		if (x > w)
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
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







-
+


-
+









-
-
+
+


-
+


-
-
-
-
+
+
+
+








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

	[menus addObject:[Menu menuWithName:name]];
	[menus addObject: [Menu menuWithName: name]];
}

COMMAND(newmenu, ARG_1STR, ^(OFString *name) {
COMMAND(newmenu, ARG_1STR, ^ (OFString *name) {
	newmenu(name);
})

void
menumanual(int m, int n, OFString *text)
{
	if (n == 0)
		[menus[m].items removeAllObjects];

	MenuItem *item = [MenuItem itemWithText:text action:@""];
	[menus[m].items addObject:item];
	MenuItem *item = [MenuItem itemWithText: text action: @""];
	[menus[m].items addObject: item];
}

COMMAND(menuitem, ARG_2STR, ^(OFString *text, OFString *action) {
COMMAND(menuitem, ARG_2STR, ^ (OFString *text, OFString *action) {
	Menu *menu = menus.lastObject;

	MenuItem *item =
	    [MenuItem itemWithText:text
	                    action:(action.length > 0 ? action : text)];
	[menu.items addObject:item];
	MenuItem *item = [MenuItem
	    itemWithText: text
		  action: (action.length > 0 ? action : text)];
	[menu.items addObject: item];
})

bool
menukey(int code, bool isdown)
{
	if (vmenu <= 0)
		return false;
151
152
153
154
155
156
157
158

159
160
161
162
163
164
165
166
151
152
153
154
155
156
157

158
159
160
161
162
163
164
165
166







-
+








			OFString *action = menus[vmenu].items[menusel].action;
			if (vmenu == 1)
				connects(getservername(menusel));

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

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

			execute(action, true);
		}
	}

	return true;
}

Modified src/physics.m from [8500b9ce45] to [95d4c13f64].

28
29
30
31
32
33
34
35

36
37
38
39
40
41
42
28
29
30
31
32
33
34

35
36
37
38
39
40
41
42







-
+







			if (o.origin.z - o.eyeHeight < *hi)
				*hi = o.origin.z - o.eyeHeight - 1;
		} else if (o.origin.z + o.aboveEye > *lo)
			*lo = o.origin.z + o.aboveEye + 1;

		if (fabs(o.origin.z - d.origin.z) < o.aboveEye + d.eyeHeight)
			return false;
		if ([d isKindOfClass:Monster.class])
		if ([d isKindOfClass: Monster.class])
			return false; // hack

		*headspace = d.origin.z - o.origin.z - o.aboveEye - d.eyeHeight;
		if (*headspace < 0)
			*headspace = 10;
	}

104
105
106
107
108
109
110
111

112
113

114
115
116
117
118
119
120
121
104
105
106
107
108
109
110

111


112

113
114
115
116
117
118
119







-
+
-
-
+
-







	const float fy2 = d.origin.y + d.radius;
	const int x1 = fx1;
	const int y1 = fy1;
	const int x2 = fx2;
	const int y2 = fy2;
	float hi = 127, lo = -128;
	// big monsters are afraid of heights, unless angry :)
	float minfloor =
	float minfloor = ([d isKindOfClass: Monster.class] && !spawn &&
	    ([d isKindOfClass:Monster.class] && !spawn && d.health > 100
	            ? d.origin.z - d.eyeHeight - 4.5f
	    d.health > 100 ? d.origin.z - d.eyeHeight - 4.5f : -1000.0f);
	            : -1000.0f);

	for (int x = x1; x <= x2; x++) {
		for (int y = y1; y <= y2; y++) {
			// collide with map
			if (OUTBORD(x, y))
				return false;
			struct sqr *s = S(x, y);
324
325
326
327
328
329
330
331

332
333
334
335
336
337
338
339
340

341
342
343
344
345
346
347
322
323
324
325
326
327
328

329
330
331
332
333
334
335
336
337

338
339
340
341
342
343
344
345







-
+








-
+







				// correct water feel
				if (water)
					pl.velocity = OFMakeVector3D(
					    pl.velocity.x / 8,
					    pl.velocity.y / 8, pl.velocity.z);
				if (local)
					playsoundc(S_JUMP);
				else if ([pl isKindOfClass:Monster.class]) {
				else if ([pl isKindOfClass: Monster.class]) {
					OFVector3D loc = pl.origin;
					playsound(S_JUMP, &loc);
				}
			} else if (pl.timeInAir > 800) {
				// if we land after long time must have been a
				// high jump, make thud sound
				if (local)
					playsoundc(S_LAND);
				else if ([pl isKindOfClass:Monster.class]) {
				else if ([pl isKindOfClass: Monster.class]) {
					OFVector3D loc = pl.origin;
					playsound(S_LAND, &loc);
				}
			}

			pl.timeInAir = 0;
		} else

Modified src/rendercubes.m from [4e2c5682e1] to [d20e5d1984].

60
61
62
63
64
65
66
67

68
69
70
71
72
73
74
60
61
62
63
64
65
66

67
68
69
70
71
72
73
74







-
+







const float TEXTURESCALE = 32.0f;
bool floorstrip = false, deltastrip = false;
int oh, oy, ox, ogltex; // the o* vars are used by the stripification
int ol3r, ol3g, ol3b, ol4r, ol4g, ol4b;
int firstindex;
bool showm = false;

COMMAND(showmip, ARG_NONE, ^{
COMMAND(showmip, ARG_NONE, ^ {
	showm = !showm;
})

void
mipstats(int a, int b, int c)
{
	if (showm)

Modified src/renderextras.m from [efc1c2688d] to [f1478ac26e].

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







-
-
-
-
+
+
+
+










-
-
-
-
+
+
+
+







blendbox(int x1, int y1, int x2, int y2, bool border)
{
	glDepthMask(GL_FALSE);
	glDisable(GL_TEXTURE_2D);
	glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
	glBegin(GL_QUADS);
	if (border)
		[[OFColor colorWithRed:0.5f
				 green:0.3f
				  blue:0.4f
				 alpha:1.0f] cube_setAsGLColor];
		[[OFColor colorWithRed: 0.5f
				 green: 0.3f
				  blue: 0.4f
				 alpha: 1.0f] cube_setAsGLColor];
	else
		[OFColor.white cube_setAsGLColor];
	glVertex2i(x1, y1);
	glVertex2i(x2, y1);
	glVertex2i(x2, y2);
	glVertex2i(x1, y2);
	glEnd();
	glDisable(GL_BLEND);
	glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	glBegin(GL_POLYGON);
	[[OFColor colorWithRed:0.2f
			 green:0.7f
			  blue:0.4f
			 alpha:1.0f] cube_setAsGLColor];
	[[OFColor colorWithRed: 0.2f
			 green: 0.7f
			  blue: 0.4f
			 alpha: 1.0f] cube_setAsGLColor];
	glVertex2i(x1, y1);
	glVertex2i(x2, y1);
	glVertex2i(x2, y2);
	glVertex2i(x1, y2);
	glEnd();
	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	xtraverts += 8;
129
130
131
132
133
134
135
136
137
138
139




140
141
142
143
144
145
146
129
130
131
132
133
134
135




136
137
138
139
140
141
142
143
144
145
146







-
-
-
-
+
+
+
+







	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE);
	glBindTexture(GL_TEXTURE_2D, 4);

	for (struct sphere *p, **pp = &slist; (p = *pp) != NULL;) {
		glPushMatrix();
		float size = p->size / p->max;
		[[OFColor colorWithRed:1.0f
				 green:1.0f
				  blue:1.0f
				 alpha:1.0f - size] cube_setAsGLColor];
		[[OFColor colorWithRed: 1.0f
				 green: 1.0f
				  blue: 1.0f
				 alpha: 1.0f - size] cube_setAsGLColor];
		glTranslatef(p->o.x, p->o.z, p->o.y);
		glRotatef(lastmillis / 5.0f, 1, 1, 1);
		glScalef(p->size, p->size, p->size);
		glCallList(1);
		glScalef(0.8f, 0.8f, 0.8f);
		glCallList(1);
		glPopMatrix();
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
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







-
-
+
+
+





-
+


-
-
+
+

-
+






-
-
+
+


-
+
-
-
+
-







		particle_splash(2, 2, 40, OFMakeVector3D(e.x, e.y, e.z));
	}

	int e = closestent();
	if (e >= 0) {
		Entity *c = ents[e];
		closeent =
		    [OFString stringWithFormat:@"closest entity = %@ (%d, %d, "
		                               @"%d, %d), selection = (%d, %d)",
		    [OFString stringWithFormat:
			@"closest entity = %@ (%d, %d, %d, %d), "
			@"selection = (%d, %d)",
		        entnames[c.type], c.attr1, c.attr2, c.attr3, c.attr4,
		        getvar(@"selxs"), getvar(@"selys")];
	}
}

COMMAND(loadsky, ARG_1STR, (^(OFString *basename) {
COMMAND(loadsky, ARG_1STR, (^ (OFString *basename) {
	static OFString *lastsky = @"";

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

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

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

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

	lastsky = basename;
}))

float cursordepth = 0.9f;
357
358
359
360
361
362
363
364
365
366
367




368
369
370
371
372




373
374
375
376
377
378
379
356
357
358
359
360
361
362




363
364
365
366
367




368
369
370
371
372
373
374
375
376
377
378







-
-
-
-
+
+
+
+

-
-
-
-
+
+
+
+








	glDepthMask(GL_FALSE);

	if (dblend || underwater) {
		glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
		glBegin(GL_QUADS);
		if (dblend)
			[[OFColor colorWithRed:0.0f
					 green:0.9f
					  blue:0.9f
					 alpha:1.0f] cube_setAsGLColor];
			[[OFColor colorWithRed: 0.0f
					 green: 0.9f
					  blue: 0.9f
					 alpha: 1.0f] cube_setAsGLColor];
		else
			[[OFColor colorWithRed:0.9f
					 green:0.5f
					  blue:0.0f
					 alpha:1.0f] cube_setAsGLColor];
			[[OFColor colorWithRed: 0.9f
					 green: 0.5f
					  blue: 0.0f
					 alpha: 1.0f] cube_setAsGLColor];
		glVertex2i(0, 0);
		glVertex2i(VIRTW, 0);
		glVertex2i(VIRTW, VIRTH);
		glVertex2i(0, VIRTH);
		glEnd();
		dblend -= curtime / 3;
		if (dblend < 0)
400
401
402
403
404
405
406
407
408
409
410





411
412
413
414
415
416
417
399
400
401
402
403
404
405




406
407
408
409
410
411
412
413
414
415
416
417







-
-
-
-
+
+
+
+
+







		[OFColor.white cube_setAsGLColor];
		if (crosshairfx) {
			if (player1.gunWait)
				[OFColor.gray cube_setAsGLColor];
			else if (player1.health <= 25)
				[OFColor.red cube_setAsGLColor];
			else if (player1.health <= 50)
				[[OFColor colorWithRed:1.0f
						 green:0.5f
						  blue:0.0f
						 alpha:1.0f] cube_setAsGLColor];
				[[OFColor colorWithRed: 1.0f
						 green: 0.5f
						  blue: 0.0f
						 alpha: 1.0f]
				    cube_setAsGLColor];
		}
		float chsize = (float)crosshairsize;
		glTexCoord2d(0.0, 0.0);
		glVertex2f(VIRTW / 2 - chsize, VIRTH / 2 - chsize);
		glTexCoord2d(1.0, 0.0);
		glVertex2f(VIRTW / 2 + chsize, VIRTH / 2 - chsize);
		glTexCoord2d(1.0, 1.0);

Modified src/rendergl.m from [a52bf36780] to [8875dffa42].

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







-
+



-
+






-
-
+
+




















-
+













-
-
+
+

-
+
-
-
+
-







	for (int i = 0; i < 256; i++)
		for (int j = 0; j < MAXFRAMES; j++)
			mapping[i][j] = 0;
}

int curtexnum = 0;

COMMAND(texturereset, ARG_NONE, ^{
COMMAND(texturereset, ARG_NONE, ^ {
	curtexnum = 0;
})

COMMAND(texture, ARG_2STR, (^(OFString *aframe, OFString *name) {
COMMAND(texture, ARG_2STR, (^ (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:@"/"];
	mapname[num][frame] = [name stringByReplacingOccurrencesOfString: @"\\"
	                                                      withString: @"/"];
}))

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

	if (tid >= FIRSTTEX) {
		*xs = texx[tid - FIRSTTEX];
		*ys = texy[tid - FIRSTTEX];
		return tid;
	}

	*xs = *ys = 16;
	if (tid == 0)
		return 1; // crosshair :)

	// lazily happens once per "texture" command, basically
	for (int i = 0; i < curtex; i++) {
		if ([mapname[tex][frame] isEqual:texname[i]]) {
		if ([mapname[tex][frame] isEqual: texname[i]]) {
			mapping[tex][frame] = tid = i + FIRSTTEX;
			*xs = texx[i];
			*ys = texy[i];
			return tid;
		}
	}

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

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

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

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

326
327
328

329
330
331
332
333
334
335
316
317
318
319
320
321
322

323
324
325

326
327
328
329
330
331
332
333







-
+


-
+







}

void
addstrip(int tex, int start, int n)
{
	if (strips == nil)
		strips = [[OFMutableData alloc]
		    initWithItemSize:sizeof(struct strip)];
		    initWithItemSize: sizeof(struct strip)];

	struct strip s = { .tex = tex, .start = start, .num = n };
	[strips addItem:&s];
	[strips addItem: &s];
}

#undef gamma

VARFP(gamma, 30, 100, 300, {
	float f = gamma / 100.0f;
	Uint16 ramp[256];

Modified src/rendermd2.m from [c6c4c06ab4] to [923fa1d88f].

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


72
73
74


75
76

77
78
79
80
81
82
83
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
72


73
74
75

76
77
78
79
80
81
82
83







-
-
+
+

-
+

-
-
+
+
+


-
+
+

















-
+










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

-
-
+
+

-
-
+
+

-
+








static const int FIRSTMDL = 20;

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

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

		OFIRI *IRI2 = [baseIRI IRIByAppendingPathComponent:@"skin.jpg"];
		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 md2];
	m.mdlnum = modelnum++;
	m.mmi = [MapModelInfo infoWithRad:2 h:2 zoff:0 snap:0 name:@""];
	m.mmi = [MapModelInfo infoWithRad: 2 h: 2 zoff: 0 snap: 0 name: @""];
	m.loadname = name;

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

	mdllookup[name] = m;

	return m;
}

COMMAND(mapmodel, ARG_5STR,
COMMAND(mapmodel, ARG_5STR, ^ (OFString *rad, OFString *h, OFString *zoff,
    ^(OFString *rad, OFString *h, OFString *zoff, OFString *snap,
        OFString *name) {
    OFString *snap, OFString *name) {
	    MD2 *m =
	        loadmodel([name stringByReplacingOccurrencesOfString:@"\\"
	                                                  withString:@"/"]);
	    m.mmi = [MapModelInfo infoWithRad:rad.cube_intValue
	                                    h:h.cube_intValue
	                                 zoff:zoff.cube_intValue
	                                 snap:snap.cube_intValue
	                                 name:m.loadname];
	MD2 *m = loadmodel([name stringByReplacingOccurrencesOfString: @"\\"
							   withString: @"/"]);
	m.mmi = [MapModelInfo infoWithRad: rad.cube_intValue
					h: h.cube_intValue
				     zoff: zoff.cube_intValue
				     snap: snap.cube_intValue
				     name: m.loadname];

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

	    [mapmodels addObject:m];
    })
	[mapmodels addObject: m];
})

COMMAND(mapmodelreset, ARG_NONE, ^{
COMMAND(mapmodelreset, ARG_NONE, ^ {
	[mapmodels removeAllObjects];
})

MapModelInfo *
getmminfo(int i)
{
	return i < mapmodels.count ? mapmodels[i].mmi : nil;
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
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







-
+





-
-
-
-
+
+
+
+




-
-
-
-
-
+
+
+
+
+


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


	int xs, ys;
	glBindTexture(GL_TEXTURE_2D,
	    tex ? lookuptexture(tex, &xs, &ys) : FIRSTMDL + m.mdlnum);

	int ix = (int)position.x;
	int iy = (int)position.z;
	OFColor *light = [OFColor colorWithRed:1 green:1 blue:1 alpha:1];
	OFColor *light = OFColor.white;

	if (!OUTBORD(ix, iy)) {
		struct sqr *s = S(ix, iy);
		float ll = 256.0f; // 0.96f;
		float of = 0.0f;   // 0.1f;
		light = [OFColor colorWithRed:s->r / ll + of
		                        green:s->g / ll + of
		                         blue:s->b / ll + of
		                        alpha:1];
		light = [OFColor colorWithRed: s->r / ll + of
		                        green: s->g / ll + of
		                         blue: s->b / ll + of
		                        alpha: 1];
	}

	if (teammate) {
		float red, green, blue;
		[light getRed:&red green:&green blue:&blue alpha:NULL];
		light = [OFColor colorWithRed:red * 0.6f
		                        green:green * 0.7f
		                         blue:blue * 1.2f
		                        alpha:1];
		[light getRed: &red green: &green blue: &blue alpha: NULL];
		light = [OFColor colorWithRed: red * 0.6f
		                        green: green * 0.7f
		                         blue: blue * 1.2f
		                        alpha: 1];
	}

	[m renderWithLight:light
	             frame:frame
	             range:range
	          position:position
	               yaw:yaw
	             pitch:pitch
	             scale:scale
	             speed:speed
	              snap:snap
	          basetime:basetime];
	[m renderWithLight: light
	             frame: frame
	             range: range
	          position: position
	               yaw: yaw
	             pitch: pitch
	             scale: scale
	             speed: speed
	              snap: snap
	          basetime: basetime];
}

Modified src/rendertext.m from [da461b7a90] to [656fe9791c].

135
136
137
138
139
140
141
142
143


144
145
146
147
148
149
150
135
136
137
138
139
140
141


142
143
144
145
146
147
148
149
150







-
-
+
+







}

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];
	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)
{
166
167
168
169
170
171
172
173
174
175
176




177
178
179
180
181
182
183
166
167
168
169
170
171
172




173
174
175
176
177
178
179
180
181
182
183







-
-
-
-
+
+
+
+








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

		if (c == '\f') {
			[[OFColor colorWithRed:0.25f
					 green:1.0f
					  blue:0.5f
					 alpha:1.0f] cube_setAsGLColor];
			[[OFColor colorWithRed: 0.25f
					 green: 1.0f
					  blue: 0.5f
					 alpha: 1.0f] cube_setAsGLColor];
			continue;
		}

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

Modified src/savegamedemo.m from [fe0624b79b] to [94bda1495e].

93
94
95
96
97
98
99
100

101
102
103
104
105
106
107
108
93
94
95
96
97
98
99

100

101
102
103
104
105
106
107







-
+
-







}

void
savestate(OFIRI *IRI)
{
	stop();
	f = gzopen([IRI.fileSystemRepresentation
	               cStringWithEncoding:OFLocale.encoding],
	    cStringWithEncoding: OFLocale.encoding], "wb9");
	    "wb9");
	if (!f) {
		conoutf(@"could not write %@", IRI.string);
		return;
	}
	gzwrite(f, (void *)"CUBESAVE", 8);
	gzputc(f, islittleendian);
	gzputi(SAVEGAMEVERSION);
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
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







-
+





-
-
-
+
+
+
+












-
+
-







	for (id player in players) {
		gzput(player == [OFNull null]);
		data = [player dataBySerializing];
		gzwrite(f, data.items, data.count);
	}
}

COMMAND(savegame, ARG_1STR, (^(OFString *name) {
COMMAND(savegame, ARG_1STR, (^ (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];
	OFString *path = [OFString stringWithFormat:
	    @"savegames/%@.csgz", name];
	OFIRI *IRI = [Cube.sharedInstance.userDataIRI
	    IRIByAppendingPathComponent: path];
	savestate(IRI);
	stop();
	conoutf(@"wrote %@", IRI.string);
}))

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

	char mapname[_MAXDEFSTR] = { 0 };
	char buf[8];
179
180
181
182
183
184
185
186
187
188
189





190
191
192
193
194
195
196
178
179
180
181
182
183
184




185
186
187
188
189
190
191
192
193
194
195
196







-
-
-
-
+
+
+
+
+







	return;
out:
	conoutf(@"aborting: savegame/demo from a different version of "
	        @"cube or cpu architecture");
	stop();
}

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

void
loadgameout()
{
	stop();
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
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







-
-
+
+

-
+









-
+















-
+

















-
+









-
-
-
+
+
+







		if (e.type == CARROT && !e.spawned)
			trigger(e.attr1, e.attr2, true);
	}

	restoreserverstate(ents);

	OFMutableData *data =
	    [OFMutableData dataWithCapacity:DynamicEntity.serializedSize];
	[data increaseCountBy:DynamicEntity.serializedSize];
	    [OFMutableData dataWithCapacity: DynamicEntity.serializedSize];
	[data increaseCountBy: DynamicEntity.serializedSize];
	gzread(f, data.mutableItems, data.count);
	[Player.player1 setFromSerializedData:data];
	[Player.player1 setFromSerializedData: data];
	Player.player1.lastAction = lastmillis;

	int nmonsters = gzgeti();
	OFArray<Monster *> *monsters = Monster.monsters;
	if (nmonsters != monsters.count)
		return loadgameout();

	for (Monster *monster in monsters) {
		gzread(f, data.mutableItems, data.count);
		[monster setFromSerializedData:data];
		[monster setFromSerializedData: data];
		// lazy, could save id of enemy instead
		monster.enemy = Player.player1;
		// also lazy, but no real noticable effect on game
		monster.lastAction = monster.trigger = lastmillis + 500;
		if (monster.state == CS_DEAD)
			monster.lastAction = 0;
	}
	[Monster restoreAll];

	int nplayers = gzgeti();
	for (int i = 0; i < nplayers; i++) {
		if (!gzget()) {
			Player *d = getclient(i);
			assert(d);
			gzread(f, data.mutableItems, data.count);
			[d setFromSerializedData:data];
			[d setFromSerializedData: data];
		}
	}

	conoutf(@"savegame restored");
	if (demoloading)
		startdemo();
	else
		stop();
}

// demo functions

int starttime = 0;
int playbacktime = 0;
int ddamage, bdamage;
OFVector3D dorig;

COMMAND(record, ARG_1STR, (^(OFString *name) {
COMMAND(record, ARG_1STR, (^ (OFString *name) {
	if (m_sp) {
		conoutf(@"cannot record singleplayer games");
		return;
	}

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

	OFString *path = [OFString stringWithFormat:@"demos/%@.cdgz", name];
	OFIRI *IRI =
	    [Cube.sharedInstance.userDataIRI IRIByAppendingPathComponent:path];
	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;
}))
329
330
331
332
333
334
335
336
337
338
339




340
341
342
343
344
345
346
329
330
331
332
333
334
335




336
337
338
339
340
341
342
343
344
345
346







-
-
-
-
+
+
+
+







			ddamage = 0;
		}
		// FIXME: add all other client state which is not send through
		// the network
	}
}

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

void
stopreset()
{
458
459
460
461
462
463
464
465

466
467
468

469
470
471
472
473
474
475
458
459
460
461
462
463
464

465
466
467

468
469
470
471
472
473
474
475







-
+


-
+







		        playerhistory.lastObject.lastUpdate != playbacktime)) {
			Player *d = [target copy];
			d.lastUpdate = playbacktime;

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

			[playerhistory addObject:d];
			[playerhistory addObject: d];

			if (playerhistory.count > 20)
				[playerhistory removeObjectAtIndex:0];
				[playerhistory removeObjectAtIndex: 0];
		}

		readdemotime();
	}

	if (!demoplayback)
		return;
538
539
540
541
542
543
544
545

546
547
548
549
550
551
538
539
540
541
542
543
544

545
546
547
548
549
550
551







-
+






			}
			break;
		}
	}
	// if(player1->state!=CS_DEAD) showscores(false);
}

COMMAND(stop, ARG_NONE, ^{
COMMAND(stop, ARG_NONE, ^ {
	if (demoplayback)
		stopreset();
	else
		stop();
	conoutf(@"demo stopped");
})

Modified src/server.m from [853a6dbe34] to [95faadaf28].

20
21
22
23
24
25
26
27
28


29
30
31
32
33
34
35
20
21
22
23
24
25
26


27
28
29
30
31
32
33
34
35







-
-
+
+







bool notgotitems = true;
int mode = 0;

// hack: called from savegame code, only works in SP
void
restoreserverstate(OFArray<Entity *> *ents)
{
	[sents enumerateObjectsUsingBlock:^(
	    ServerEntity *e, size_t i, bool *stop) {
	[sents enumerateObjectsUsingBlock:
	    ^ (ServerEntity *e, size_t i, bool *stop) {
		e.spawned = ents[i].spawned;
		e.spawnsecs = 0;
	}];
}

int interm = 0, minremain = 0, mapend = 0;
bool mapreload = false;
97
98
99
100
101
102
103
104
105


106
107
108
109
110
111
112
97
98
99
100
101
102
103


104
105
106
107
108
109
110
111
112







-
-
+
+







	if (packet->referenceCount == 0)
		enet_packet_destroy(packet);
}

void
disconnect_client(int n, OFString *reason)
{
	[OFStdOut writeFormat:@"disconnecting client (%@) [%@]\n",
	    clients[n].hostname, reason];
	[OFStdOut writeFormat: @"disconnecting client (%@) [%@]\n",
			       clients[n].hostname, reason];
	enet_peer_disconnect(clients[n].peer);
	clients[n].type = ST_EMPTY;
	send2(true, -1, SV_CDIS, n);
}

void
resetitems()
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
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







-
+












-
-
+
+







	clients[sender].mapvote = map;
	clients[sender].modevote = reqmode;

	int yes = 0, no = 0;
	for (Client *client in clients) {
		if (client.type != ST_EMPTY) {
			if (client.mapvote.length > 0) {
				if ([client.mapvote isEqual:map] &&
				if ([client.mapvote isEqual: map] &&
				    client.modevote == reqmode)
					yes++;
				else
					no++;
			} else
				no++;
		}
	}

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

	OFString *msg = [OFString
	    stringWithFormat:@"%@ suggests %@ on map %@ (set map to vote)",
	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");
223
224
225
226
227
228
229
230
231


232
233
234
235
236
237
238
223
224
225
226
227
228
229


230
231
232
233
234
235
236
237
238







-
-
+
+







		}

		case SV_ITEMLIST: {
			int n;
			while ((n = getint(&p)) != -1)
				if (notgotitems) {
					while (sents.count <= n)
						[sents addObject:[ServerEntity
						                     entity]];
						[sents addObject:
						    [ServerEntity entity]];
					sents[n].spawned = true;
				}
			notgotitems = false;
			break;
		}

		case SV_ITEMPICKUP: {
310
311
312
313
314
315
316
317
318


319
320
321
322
323
324
325
310
311
312
313
314
315
316


317
318
319
320
321
322
323
324
325







-
-
+
+







	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);
		[sents enumerateObjectsUsingBlock:^(
		    ServerEntity *e, size_t i, bool *stop) {
		[sents enumerateObjectsUsingBlock:
		    ^ (ServerEntity *e, size_t i, bool *stop) {
			if (e.spawned)
				putint(&p, i);
		}];
		putint(&p, -1);
	}
	*(unsigned short *)start = ENET_HOST_TO_NET_16(p - start);
	enet_packet_resize(packet, p - start);
352
353
354
355
356
357
358
359

360
361
362
363
364
365
366
352
353
354
355
356
357
358

359
360
361
362
363
364
365
366







-
+







			return client;

	Client *client = [Client client];

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

	[clients addObject:client];
	[clients addObject: client];

	return client;
}

void
checkintermission()
{
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
431
432
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
431
432







-
-
+
+














-
-
+
+








void
serverslice(int seconds,
    unsigned int timeout) // main server update, called from cube main loop in
                          // sp, or dedicated server loop
{
	// spawn entities when timer reached
	[sents enumerateObjectsUsingBlock:^(
	    ServerEntity *e, size_t i, bool *stop) {
	[sents enumerateObjectsUsingBlock:
	    ^ (ServerEntity *e, size_t i, bool *stop) {
		if (e.spawnsecs && (e.spawnsecs -= seconds - lastsec) <= 0) {
			e.spawnsecs = 0;
			e.spawned = true;
			send2(true, -1, SV_ITEMSPAWN, i);
		}
	}];

	lastsec = seconds;

	if ((mode > 1 || (mode == 0 && nonlocalclients)) &&
	    seconds > mapend - minremain * 60)
		checkintermission();
	if (interm && seconds > interm) {
		interm = 0;
		[clients enumerateObjectsUsingBlock:^(
		    Client *client, size_t i, bool *stop) {
		[clients enumerateObjectsUsingBlock:
		    ^ (Client *client, size_t i, bool *stop) {
			if (client.type != ST_EMPTY) {
				// ask a client to trigger map reload
				send2(true, i, SV_MAPRELOAD, 0);
				mapreload = true;
				*stop = true;
				return;
			}
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
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







-
-
+
+












-
+
+







			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];
			[OFStdOut writeFormat: @"client connected (%@)\n",
					       c.hostname];
			send_welcome(lastconnect = clients.count - 1);
			break;
		}
		case ENET_EVENT_TYPE_RECEIVE:
			brec += event.packet->dataLength;
			process(event.packet, (intptr_t)event.peer->data);
			if (event.packet->referenceCount == 0)
				enet_packet_destroy(event.packet);
			break;
		case ENET_EVENT_TYPE_DISCONNECT:
			if ((intptr_t)event.peer->data < 0)
				break;
			[OFStdOut writeFormat:@"disconnected client (%@)\n",
			[OFStdOut writeFormat:
			    @"disconnected client (%@)\n",
			    clients[(size_t)event.peer->data].hostname];
			clients[(size_t)event.peer->data].type = ST_EMPTY;
			send2(true, -1, SV_CDIS, (intptr_t)event.peer->data);
			event.peer->data = (void *)-1;
			break;
		case ENET_EVENT_TYPE_NONE:
			break;

Modified src/serverbrowser.m from [738828d34a] to [8678daf863].

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







-
+




















-
+







	resolverresults = [[OFMutableArray alloc] init];
	resolverlimit = limit;
	resolversem = SDL_CreateSemaphore(0);

	while (threads > 0) {
		ResolverThread *rt = [ResolverThread thread];
		rt.name = @"resolverthread";
		[resolverthreads addObject:rt];
		[resolverthreads addObject: rt];
		[rt start];
		--threads;
	}
}

void
resolverstop(size_t i, bool restart)
{
	@synchronized(ResolverThread.class) {
		ResolverThread *rt = resolverthreads[i];
		[rt stop];

		if (restart) {
			rt = [ResolverThread thread];
			rt.name = @"resolverthread";

			resolverthreads[i] = rt;

			[rt start];
		} else
			[resolverthreads removeObjectAtIndex:i];
			[resolverthreads removeObjectAtIndex: i];
	}
}

void
resolverclear()
{
	@synchronized(ResolverThread.class) {
67
68
69
70
71
72
73
74

75
76
77
78
79
80
81
67
68
69
70
71
72
73

74
75
76
77
78
79
80
81







-
+







	}
}

void
resolverquery(OFString *name)
{
	@synchronized(ResolverThread.class) {
		[resolverqueries addObject:name];
		[resolverqueries addObject: name];
		SDL_SemPost(resolversem);
	}
}

bool
resolvercheck(OFString **name, ENetAddress *address)
{
114
115
116
117
118
119
120
121

122
123
124
125
126
127

128
129
130

131
132
133
134
135
136
137
114
115
116
117
118
119
120

121
122
123
124
125
126

127
128
129

130
131
132
133
134
135
136
137







-
+





-
+


-
+







	return servers[n].name;
}

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

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

	[servers addObject:[ServerInfo infoWithName:servername]];
	[servers addObject: [ServerInfo infoWithName: servername]];
}

COMMAND(addserver, ARG_1STR, ^(OFString *servername) {
COMMAND(addserver, ARG_1STR, ^ (OFString *servername) {
	addserver(servername);
})

void
pingservers()
{
	ENetBuffer buf;
159
160
161
162
163
164
165
166

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

166
167
168
169
170
171
172
173







-
+







	OFString *name = nil;
	ENetAddress addr = { ENET_HOST_ANY, CUBE_SERVINFO_PORT };
	while (resolvercheck(&name, &addr)) {
		if (addr.host == ENET_HOST_ANY)
			continue;

		for (ServerInfo *si in servers) {
			if ([name isEqual:si.name]) {
			if ([name isEqual: si.name]) {
				si.address = addr;
				addr.host = ENET_HOST_ANY;
				break;
			}
		}
	}
}
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
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







-
-
+
+



-
+
-

-
-
+
+





-
-
-
+
+
+




-
+







	checkresolver();
	checkpings();
	if (lastmillis - lastinfo >= 5000)
		pingservers();
	[servers sort];

	__block int maxmenu = 16;
	[servers enumerateObjectsUsingBlock:^(
	    ServerInfo *si, size_t i, bool *stop) {
	[servers enumerateObjectsUsingBlock:
	    ^ (ServerInfo *si, size_t i, bool *stop) {
		if (si.address.host != ENET_HOST_ANY && si.ping != 9999) {
			if (si.protocol != PROTOCOL_VERSION)
				si.full = [OFString stringWithFormat:
				        @"%@ [different cube protocol]",
				    @"%@ [different cube protocol]", si.name];
				    si.name];
			else
				si.full = [OFString
				    stringWithFormat:@"%d\t%d\t%@, %@: %@ %@",
				si.full = [OFString stringWithFormat:
				    @"%d\t%d\t%@, %@: %@ %@",
				    si.ping, si.numplayers,
				    si.map.length > 0 ? si.map : @"[unknown]",
				    modestr(si.mode), si.name, si.sdesc];
		} else
			si.full = [OFString stringWithFormat:
			        (si.address.host != ENET_HOST_ANY
			                ? @"%@ [waiting for server response]"
			                : @"%@ [unknown host]\t"),
			    (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];
			si.full = [si.full substringToIndex: 50];

		menumanual(1, i, si.full);

		if (!--maxmenu)
			return;
	}];
}
261
262
263
264
265
266
267
268

269
270
271
272

273
274
275
276
277
278
279
260
261
262
263
264
265
266

267
268
269
270

271
272
273
274
275
276
277
278







-
+



-
+







	for (ServerInfo *si in servers)
		resolverquery(si.name);

	refreshservers();
	menuset(1);
}

COMMAND(servermenu, ARG_NONE, ^{
COMMAND(servermenu, ARG_NONE, ^ {
	servermenu();
})

COMMAND(updatefrommaster, ARG_NONE, ^{
COMMAND(updatefrommaster, ARG_NONE, ^ {
	const int MAXUPD = 32000;
	unsigned char buf[MAXUPD];
	unsigned char *reply = retrieveservers(buf, MAXUPD);
	if (!*reply || strstr((char *)reply, "<html>") ||
	    strstr((char *)reply, "<HTML>"))
		conoutf(@"master server not replying");
	else {

Modified src/serverms.m from [3e3cb4cc55] to [86b7b53d7e].

1
2
3
4
5
6
7
8
9
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
1
2
3
4
5
6
7
8
9
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











-
+
















-
-
-
-
+
+
+
+
+



-
+







// 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];
		[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) {
		printf("could not open socket\n");
		return;
	}
	if (enet_socket_connect(mssock, ad) < 0) {
		printf("could not connect\n");
		return;
	}
	ENetBuffer buf;
	OFString *httpget = [OFString stringWithFormat:@"GET %@ HTTP/1.0\n"
	                                               @"Host: %@\n"
	                                               @"Referer: %@\n"
	                                               @"User-Agent: %@\n\n",
	OFString *httpget = [OFString stringWithFormat:
	    @"GET %@ HTTP/1.0\n"
	    @"Host: %@\n"
	    @"Referer: %@\n"
	    @"User-Agent: %@\n\n",
	    req, hostname, ref, agent];
	buf.data = (void *)httpget.UTF8String;
	buf.dataLength = httpget.UTF8StringLength;
	[OFStdOut writeFormat:@"sending request to %@...\n", hostname];
	[OFStdOut writeFormat: @"sending request to %@...\n", hostname];
	enet_socket_send(mssock, NULL, &buf, 1);
}

static void
httpgetrecieve(ENetBuffer *buf)
{
	if (mssock == ENET_SOCKET_NULL)
73
74
75
76
77
78
79
80
81


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


81
82
83
84
85
86
87
88
89







-
-
+
+







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];
		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;
	}
96
97
98
99
100
101
102
103
104


105
106
107
108
109
110
111
97
98
99
100
101
102
103


104
105
106
107
108
109
110
111
112







-
-
+
+







	if (busy && mssock == ENET_SOCKET_NULL)
		printf("masterserver reply: %s\n", stripheader(masterrep));
}

unsigned char *
retrieveservers(unsigned char *buf, int buflen)
{
	OFString *path =
	    [OFString stringWithFormat:@"%@retrieve.do?item=list", masterpath];
	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)
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
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







-
-
+
+















-
+
+










		if (len < 0)
			return;
		p = &pong[len];
		putint(&p, PROTOCOL_VERSION);
		putint(&p, mode);
		putint(&p, numplayers);
		putint(&p, minremain);
		OFString *mname = [OFString stringWithFormat:@"%@%@",
		    (isfull ? @"[FULL] " : @""), smapname];
		OFString *mname = [OFString stringWithFormat:
		    @"%@%@", (isfull ? @"[FULL] " : @""), smapname];
		sendstring(mname, &p);
		sendstring(serverdesc, &p);
		buf.dataLength = p - pong;
		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 stringWithUTF8String:master length:mid - master];
	masterbase = [OFString stringWithUTF8String: 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.m from [25b79fa395] to [da87a5e2ce].

142
143
144
145
146
147
148
149

150
151
152

153
154
155
156
157
158
159
142
143
144
145
146
147
148

149
150
151

152
153
154
155
156
157
158
159







-
+


-
+







void
fatal(OFConstantString *s, ...)
{
	cleanupserver();

	va_list args;
	va_start(args, s);
	OFString *msg = [[OFString alloc] initWithFormat:s arguments:args];
	OFString *msg = [[OFString alloc] initWithFormat: s arguments: args];
	va_end(args);

	[OFStdOut writeFormat:@"servererror: %@\n", msg];
	[OFStdOut writeFormat: @"servererror: %@\n", msg];

	exit(1);
}

void *
alloc(int s)
{

Modified src/sound.m from [0732f14714] to [78a898d2ad].

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







-
+






-
-
-
-
+
+
+
+

-
+















-
+









-
+

-
-
+
+

-
+







		conoutf(@"sound init failed (SDL_mixer): %s",
		    (size_t)Mix_GetError());
		nosound = true;
	}
	Mix_AllocateChannels(MAXCHAN);
}

COMMAND(music, ARG_1STR, (^(OFString *name) {
COMMAND(music, ARG_1STR, (^ (OFString *name) {
	if (nosound)
		return;

	stopsound();

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

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

static OFMutableData *samples;
static OFMutableArray<OFString *> *snames;

COMMAND(registersound, ARG_1EST, ^int(OFString *name) {
	int i = 0;
	for (OFString *iter in snames) {
		if ([iter isEqual:name])
		if ([iter isEqual: name])
			return i;

		i++;
	}

	if (snames == nil)
		snames = [[OFMutableArray alloc] init];
	if (samples == nil)
		samples = [[OFMutableData alloc]
		    initWithItemSize:sizeof(Mix_Chunk *)];
		    initWithItemSize: sizeof(Mix_Chunk *)];

	[snames addObject:[name stringByReplacingOccurrencesOfString:@"\\"
	                                                  withString:@"/"]];
	[snames addObject: [name stringByReplacingOccurrencesOfString: @"\\"
							   withString: @"/"]];
	Mix_Chunk *sample = NULL;
	[samples addItem:&sample];
	[samples addItem: &sample];

	return samples.count - 1;
})

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







-
+

-
-
+
+

-
+



















-
+


		return;

	if (n < 0 || n >= samples.count) {
		conoutf(@"unregistered sound: %d", n);
		return;
	}

	Mix_Chunk **sample = (Mix_Chunk **)[samples mutableItemAtIndex:n];
	Mix_Chunk **sample = (Mix_Chunk **)[samples mutableItemAtIndex: n];
	if (*sample == NULL) {
		OFString *path = [OFString
		    stringWithFormat:@"packages/sounds/%@.wav", snames[n]];
		OFString *path = [OFString stringWithFormat:
		    @"packages/sounds/%@.wav", snames[n]];
		OFIRI *IRI = [Cube.sharedInstance.gameDataIRI
		    IRIByAppendingPathComponent:path];
		    IRIByAppendingPathComponent: path];

		*sample = Mix_LoadWAV(IRI.fileSystemRepresentation.UTF8String);

		if (*sample == NULL) {
			conoutf(@"failed to load sample: %@", IRI.string);
			return;
		}
	}

	int chan = Mix_PlayChannel(-1, *sample, 0);
	if (chan < 0)
		return;

	if (loc)
		newsoundloc(chan, loc);

	updatechanvol(chan, loc);
}

COMMAND(sound, ARG_1INT, ^(int n) {
COMMAND(sound, ARG_1INT, ^ (int n) {
	playsound(n, NULL);
})

Modified src/weapon.m from [48ff54c543] to [6c8b962c81].

62
63
64
65
66
67
68
69

70
71
72
73
74
75
76
62
63
64
65
66
67
68

69
70
71
72
73
74
75
76







-
+








int
reloadtime(int gun)
{
	return guns[gun].attackdelay;
}

COMMAND(weapon, ARG_3STR, ^(OFString *a1, OFString *a2, OFString *a3) {
COMMAND(weapon, ARG_3STR, ^ (OFString *a1, OFString *a2, OFString *a3) {
	selectgun((a1.length > 0 ? a1.cube_intValue : -1),
	    (a2.length > 0 ? a2.cube_intValue : -1),
	    (a3.length > 0 ? a3.cube_intValue : -1));
})

// create random spread of rays for the shotgun
void
113
114
115
116
117
118
119
120

121
122
123
124
125
126
127
113
114
115
116
117
118
119

120
121
122
123
124
125
126
127







-
+







playerincrosshair()
{
	if (demoplayback)
		return NULL;

	OFVector3D o = Player.player1.origin;
	for (Player *player in players) {
		if (![Player isKindOfClass:Player.class])
		if (![Player isKindOfClass: Player.class])
			continue;

		if (intersect(player, o, worldpos))
			return [player name];
	}

	return nil;
163
164
165
166
167
168
169
170
171
172



173
174
175
176
177
178
179
163
164
165
166
167
168
169



170
171
172
173
174
175
176
177
178
179







-
-
-
+
+
+








static void
hit(int target, int damage, __kindof DynamicEntity *d, DynamicEntity *at)
{
	OFVector3D o = d.origin;
	if (d == Player.player1)
		selfdamage(damage, (at == Player.player1) ? -1 : -2, at);
	else if ([d isKindOfClass:Monster.class])
		[d incurDamage:damage fromEntity:at];
	else if ([d isKindOfClass:Player.class]) {
	else if ([d isKindOfClass: Monster.class])
		[d incurDamage: damage fromEntity: at];
	else if ([d isKindOfClass: Player.class]) {
		addmsg(1, 4, SV_DAMAGE, target, damage,
		    ((Player *)d).lifeSequence);
		playsound(S_PAIN1 + rnd(5), &o);
	}
	particle_splash(3, damage, 1000, o);
	demodamage(damage, o);
}
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
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







-
-
+
+









-
-
+
+







		dodynlight(vold, v, 0, 0, p.owner);

		if (!p.local)
			return;

		radialeffect(Player.player1, v, -1, qdam, p.owner);

		[players enumerateObjectsUsingBlock:^(
		    id player, size_t i, bool *stop) {
		[players enumerateObjectsUsingBlock:
		    ^ (id player, size_t i, bool *stop) {
			if (i == notthisplayer)
				return;

			if (player == [OFNull null])
				return;

			radialeffect(player, v, i, qdam, p.owner);
		}];

		[Monster.monsters enumerateObjectsUsingBlock:^(
		    Monster *monster, size_t i, bool *stop) {
		[Monster.monsters enumerateObjectsUsingBlock:
		    ^ (Monster *monster, size_t i, bool *stop) {
			if (i != notthismonster)
				radialeffect(monster, v, i, qdam, p.owner);
		}];
	}
}

static inline void
264
265
266
267
268
269
270
271

272
273
274
275
276
277
278
264
265
266
267
268
269
270

271
272
273
274
275
276
277
278







-
+







	for (size_t i = 0; i < MAXPROJ; i++) {
		Projectile *p = projs[i];

		if (!p.inuse)
			continue;

		int qdam = guns[p.gun].damage * (p.owner.quadMillis ? 4 : 1);
		if ([p.owner isKindOfClass:Monster.class])
		if ([p.owner isKindOfClass: Monster.class])
			qdam /= MONSTERDAMAGEFACTOR;
		OFVector3D po = p.o, pto = p.to;
		float dist = OFDistanceOfVectors3D(pto, po);
		OFVector3D v = OFSubtractVectors3D(pto, po);
		float dtime = dist * 1000 / p.speed;
		if (time > dtime)
			dtime = time;
332
333
334
335
336
337
338
339

340
341
342
343
344
345
346
332
333
334
335
336
337
338

339
340
341
342
343
344
345
346







-
+







		break;

	case GUN_RL:
	case GUN_FIREBALL:
	case GUN_ICEBALL:
	case GUN_SLIMEBALL:
		pspeed = guns[gun].projspeed;
		if ([d isKindOfClass:Monster.class])
		if ([d isKindOfClass: Monster.class])
			pspeed /= 2;
		newprojectile(from, to, (float)pspeed, local, d, gun);
		break;

	case GUN_RIFLE:
		particle_splash(0, 50, 200, to);
		particle_trail(1, 500, from, to);
364
365
366
367
368
369
370
371

372
373
374
375
376
377
378
364
365
366
367
368
369
370

371
372
373
374
375
376
377
378







-
+







    DynamicEntity *o, OFVector3D from, OFVector3D to, DynamicEntity *d, int i)
{
	if (o.state != CS_ALIVE)
		return;
	int qdam = guns[d.gunSelect].damage;
	if (d.quadMillis)
		qdam *= 4;
	if ([d isKindOfClass:Monster.class])
	if ([d isKindOfClass: Monster.class])
		qdam /= MONSTERDAMAGEFACTOR;
	if (d.gunSelect == GUN_SG) {
		int damage = 0;
		for (int r = 0; r < SGRAYS; r++)
			if (intersect(o, from, sg[r]))
				damage += qdam;
		if (damage)
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
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







-
+








-
+
+








-
+


	}
	if (d.gunSelect == GUN_SG)
		createrays(from, to);

	if (d.quadMillis && attacktime > 200)
		playsoundc(S_ITEMPUP);
	shootv(d.gunSelect, from, to, d, true);
	if (![d isKindOfClass:Monster.class])
	if (![d isKindOfClass: Monster.class])
		addmsg(1, 8, SV_SHOT, d.gunSelect, (int)(from.x * DMF),
		    (int)(from.y * DMF), (int)(from.z * DMF), (int)(to.x * DMF),
		    (int)(to.y * DMF), (int)(to.z * DMF));
	d.gunWait = guns[d.gunSelect].attackdelay;

	if (guns[d.gunSelect].projspeed)
		return;

	[players enumerateObjectsUsingBlock:^(id player, size_t i, bool *stop) {
	[players enumerateObjectsUsingBlock:
	    ^ (id player, size_t i, bool *stop) {
		if (player != [OFNull null])
			raydamage(player, from, to, d, i);
	}];

	for (Monster *monster in Monster.monsters)
		if (monster != d)
			raydamage(monster, from, to, d, -2);

	if ([d isKindOfClass:Monster.class])
	if ([d isKindOfClass: Monster.class])
		raydamage(Player.player1, from, to, d, -1);
}

Modified src/world.m from [1c22ea0614] to [a8feb3f63c].

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







-
+
















-
-
+
+





-
+


-
+








// set for playing
void
settagareas()
{
	settag(0, 1);

	[ents enumerateObjectsUsingBlock:^(Entity *e, size_t i, bool *stop) {
	[ents enumerateObjectsUsingBlock: ^ (Entity *e, size_t i, bool *stop) {
		if (ents[i].type == CARROT)
			setspawn(i, true);
	}];
}

void
trigger(int tag, int type, bool savegame)
{
	if (!tag)
		return;

	settag(tag, type);

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

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

	if (identexists(aliasname))
		execute(aliasname, true);

	if (type == 2)
		[Monster endSinglePlayerWithAllKilled:false];
		[Monster endSinglePlayerWithAllKilled: false];
}

COMMAND(trigger, ARG_2INT, ^(int tag, int type, bool savegame) {
COMMAND(trigger, ARG_2INT, ^ (int tag, int type, bool savegame) {
	trigger(tag, type, savegame);
})

// main geometric mipmapping routine, recursively rebuild mipmaps within block
// b. tries to produce cube out of 4 lower level mips as well as possible, sets
// defer to 0 if mipped cube is a perfect mip, i.e. can be rendered at this mip
// level indistinguishable from its constituent cubes (saves considerable
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
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







-
+














-
+



















-
+

















-
+







closestent() // used for delent and edit mode ent display
{
	if (noteditmode())
		return -1;

	__block int best;
	__block float bdist = 99999;
	[ents enumerateObjectsUsingBlock:^(Entity *e, size_t i, bool *stop) {
	[ents enumerateObjectsUsingBlock: ^ (Entity *e, size_t i, bool *stop) {
		if (e.type == NOTUSED)
			return;

		OFVector3D v = OFMakeVector3D(e.x, e.y, e.z);
		float dist = OFDistanceOfVectors3D(v, Player.player1.origin);
		if (dist < bdist) {
			best = i;
			bdist = dist;
		}
	}];

	return (bdist == 99999 ? -1 : best);
}

COMMAND(entproperty, ARG_2INT, ^(int prop, int amount) {
COMMAND(entproperty, ARG_2INT, ^ (int prop, int amount) {
	int e = closestent();
	if (e < 0)
		return;
	switch (prop) {
	case 0:
		ents[e].attr1 += amount;
		break;
	case 1:
		ents[e].attr2 += amount;
		break;
	case 2:
		ents[e].attr3 += amount;
		break;
	case 3:
		ents[e].attr4 += amount;
		break;
	}
})

COMMAND(delent, ARG_NONE, ^{
COMMAND(delent, ARG_NONE, ^ {
	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)
{
	for (int i = 0; i < MAXENTTYPES; i++)
		if ([what isEqual:entnames[i]])
		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)
385
386
387
388
389
390
391
392

393
394
395
396
397
398
399
400

401
402
403
404
405
406
407
385
386
387
388
389
390
391

392
393
394
395
396
397
398
399

400
401
402
403
404
405
406
407







-
+







-
+







	case PLAYERSTART:
		e.attr1 = (int)Player.player1.yaw;
		break;
	}
	addmsg(1, 10, SV_EDITENT, ents.count, type, e.x, e.y, e.z, e.attr1,
	    e.attr2, e.attr3, e.attr4);

	[ents addObject:e];
	[ents addObject: e];

	if (type == LIGHT)
		calclight();

	return e;
}

COMMAND(clearents, ARG_1STR, ^(OFString *name) {
COMMAND(clearents, ARG_1STR, ^ (OFString *name) {
	int type = findtype(name);

	if (noteditmode() || multiplayer())
		return;

	for (Entity *e in ents)
		if (e.type == type)
416
417
418
419
420
421
422
423

424
425
426
427
428
429
430
416
417
418
419
420
421
422

423
424
425
426
427
428
429
430







-
+







{
	int n = c * intens / 100;
	if (n > 255)
		n = 255;
	return n;
}

COMMAND(scalelights, ARG_2INT, ^(int f, int intens) {
COMMAND(scalelights, ARG_2INT, ^ (int f, int intens) {
	for (Entity *e in ents) {
		if (e.type != LIGHT)
			continue;

		e.attr1 = e.attr1 * f / 100;
		if (e.attr1 < 2)
			e.attr1 = 2;
541
542
543
544
545
546
547
548

549
550
551
552

553
554
555
556

557
558
541
542
543
544
545
546
547

548
549
550
551

552
553
554
555

556
557
558







-
+



-
+



-
+


	if (oldworld) {
		OFFreeMemory(oldworld);
		toggleedit();
		execute(@"fullbright 1", true);
	}
}

COMMAND(mapenlarge, ARG_NONE, ^{
COMMAND(mapenlarge, ARG_NONE, ^ {
	empty_world(-1, false);
})

COMMAND(newmap, ARG_1INT, ^(int i) {
COMMAND(newmap, ARG_1INT, ^ (int i) {
	empty_world(i, false);
})

COMMAND(recalc, ARG_NONE, ^{
COMMAND(recalc, ARG_NONE, ^ {
	calclight();
})

Modified src/worldio.m from [d379fda5be] to [f35a650122].

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








48
49
50
51
52
53
54
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
48
49
50
51
52
53
54







-
-
+
+








-
-
+
+



-
-
+
+





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







	unsigned char type; // type is one of the above
	unsigned char attr2, attr3, attr4;
};

void
backup(OFString *name, OFString *backupname)
{
	[OFFileManager.defaultManager removeItemAtPath:backupname];
	[OFFileManager.defaultManager moveItemAtPath:name toPath:backupname];
	[OFFileManager.defaultManager removeItemAtPath: backupname];
	[OFFileManager.defaultManager moveItemAtPath: name toPath: backupname];
}

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

static void
setnames(OFString *name)
{
	OFCharacterSet *cs =
	    [OFCharacterSet characterSetWithCharactersInString:@"/\\"];
	OFRange range = [name rangeOfCharacterFromSet: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];
		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];
	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).
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
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







-
+













-
+


















-
-
+
+








void
writemap(OFString *mname, int msize, unsigned char *mdata)
{
	setnames(mname);
	backup(cgzname, bakname);

	FILE *f = fopen([cgzname cStringWithEncoding:OFLocale.encoding], "wb");
	FILE *f = fopen([cgzname cStringWithEncoding: OFLocale.encoding], "wb");
	if (!f) {
		conoutf(@"could not write map to %@", cgzname);
		return;
	}
	fwrite(mdata, 1, msize, f);
	fclose(f);
	conoutf(@"wrote map %@ as file %@", mname, cgzname);
}

OFData *
readmap(OFString *mname)
{
	setnames(mname);
	return [OFData dataWithContentsOfFile:mname];
	return [OFData dataWithContentsOfFile: mname];
}

// save map as .cgz file. uses 2 layers of compression: first does simple
// 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");
	gzFile f = gzopen([cgzname cStringWithEncoding: OFLocale.encoding],
	    "wb9");
	if (!f) {
		conoutf(@"could not write map to %@", cgzname);
		return;
	}
	hdr.version = MAPVERSION;
	hdr.numents = 0;
	for (Entity *e in ents)
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
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







-
+











-
-
+
+







	}
	spurge;
	gzclose(f);
	conoutf(@"wrote map file %@", cgzname);
	settagareas();
}

COMMAND(savemap, ARG_1STR, ^(OFString *mname) {
COMMAND(savemap, ARG_1STR, ^ (OFString *mname) {
	save_world(mname);
})

void
load_world(OFString *mname) // still supports all map formats that have existed
                            // since the earliest cube betas!
{
	stopifrecording();
	cleardlights();
	pruneundos(0);
	setnames(mname);
	gzFile f =
	    gzopen([cgzname cStringWithEncoding:OFLocale.encoding], "rb9");
	gzFile f = gzopen([cgzname cStringWithEncoding: OFLocale.encoding],
	    "rb9");
	if (!f) {
		conoutf(@"could not read map %@", cgzname);
		return;
	}
	gzread(f, &hdr, sizeof(struct header) - sizeof(int) * 16);
	endianswap(&hdr.version, sizeof(int), 4);
	if (strncmp(hdr.head, "CUBE", 4) != 0)
301
302
303
304
305
306
307
308

309
310
311
312
313
314
315
301
302
303
304
305
306
307

308
309
310
311
312
313
314
315







-
+







		e.y = tmp.y;
		e.z = tmp.z;
		e.attr1 = tmp.attr1;
		e.type = tmp.type;
		e.attr2 = tmp.attr2;
		e.attr3 = tmp.attr3;
		e.attr4 = tmp.attr4;
		[ents addObject:e];
		[ents addObject: e];

		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
		}
392
393
394
395
396
397
398
399
400


401
402
403
404
405
406
407
408



409
392
393
394
395
396
397
398


399
400
401
402
403
404
405



406
407
408
409







-
-
+
+





-
-
-
+
+
+

			lookuptexture(i, &xs, &ys);
	conoutf(@"read map %@ (%d milliseconds)", cgzname,
	    SDL_GetTicks() - lastmillis);
	conoutf(@"%s", hdr.maptitle);
	startmap(mname);
	for (int l = 0; l < 256; l++) {
		// can this be done smarter?
		OFString *aliasname =
		    [OFString stringWithFormat:@"level_trigger_%d", l];
		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]);
	    IRIByAppendingPathComponent: @"data/default_map_settings.cfg"]);
	execfile([gameDataIRI IRIByAppendingPathComponent: pcfname]);
	execfile([gameDataIRI IRIByAppendingPathComponent: mcfname]);
}

Modified src/worldlight.m from [279400f849] to [38a360a277].

201
202
203
204
205
206
207
208

209
210
211
212
213
214
215
201
202
203
204
205
206
207

208
209
210
211
212
213
214
215







-
+








void
dodynlight(OFVector3D vold, OFVector3D v, int reach, int strength,
    DynamicEntity *owner)
{
	if (!reach)
		reach = dynlight;
	if ([owner isKindOfClass:Monster.class])
	if ([owner isKindOfClass: Monster.class])
		reach = reach / 2;
	if (!reach)
		return;
	if (v.x < 0 || v.y < 0 || v.x > ssize || v.y > ssize)
		return;

	int creach = reach + 16; // dependant on lightray random offsets!
223
224
225
226
227
228
229
230

231
232
233
234

235
236
237
238
239
240
241
223
224
225
226
227
228
229

230
231
232
233

234
235
236
237
238
239
240
241







-
+



-
+







	if (b.xs + b.x > ssize - 2)
		b.xs = ssize - 2 - b.x;
	if (b.ys + b.y > ssize - 2)
		b.ys = ssize - 2 - b.y;

	if (dlights == nil)
		dlights = [[OFMutableData alloc]
		    initWithItemSize:sizeof(struct block *)];
		    initWithItemSize: sizeof(struct block *)];

	// backup area before rendering in dynlight
	struct block *copy = blockcopy(&b);
	[dlights addItem:&copy];
	[dlights addItem: &copy];

	Entity *l = [Entity entity];
	l.x = v.x;
	l.y = v.y;
	l.z = v.z;
	l.attr1 = reach;
	l.type = LIGHT;

Modified src/worldocull.m from [201c3dd9c9] to [a7de71ad12].

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

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

14
15
16
17
18
19
20
21













-
+







// worldocull.cpp: occlusion map and occlusion test

#include "cube.h"

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

#define NUMRAYS 512

float rdist[NUMRAYS];
bool ocull = true;
float odist = 256;

COMMAND(toggleocull, ARG_NONE, ^{
COMMAND(toggleocull, ARG_NONE, ^ {
	ocull = !ocull;
})

// constructs occlusion map: cast rays in all directions on the 2d plane and
// record distance. done exactly once per frame.

void