Cube  Check-in [d7661be1b1]

Overview
Comment:Clean up identifiers, use blocks for commands
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: d7661be1b1dc8fda8e4de50f9a9d75907f498e6e07530241fb04be015ca3d9ae
User & Date: js on 2025-03-23 19:40:00
Other Links: manifest | tags
Context
2025-03-23
21:31
Remove vdist check-in: 6259424802 user: js tags: trunk
19:40
Clean up identifiers, use blocks for commands check-in: d7661be1b1 user: js tags: trunk
17:45
Remove fast_f2nat check-in: 51fb59fc93 user: js tags: trunk
Changes

Modified src/Command.h from [e984d89122] to [a73716cedf].

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


20
21
22
23


24
25
26
27
28
29




+
+
+
+
+
+
+
+
+
+
+

-



-
-
+
+


-
-
+
+




#import "Identifier.h"

OF_ASSUME_NONNULL_BEGIN

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

@interface Command: Identifier
@property (readonly, nonatomic) void (*function)();
@property (readonly, nonatomic) int argumentsTypes;

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

OF_ASSUME_NONNULL_END

Modified src/Command.m from [6dc988a0ae] to [1956763843].

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
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
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
109
110
111


112
113
114
115
116
117
118

119
120
121
122


123
124
125
126
127

128
129
130
131
132
133
134

135
136
137
138
139
140
141
142


143
144
145
146
147
148


149
150
151
152
153
154
155

156
157
158
159
160
161
162
163







+
+
+
+

-

+


-
-
+
+



-

+



-

+










-
+






-
+







-
+








-
+








-
+




-
+





-
+






-
-
+






-
-
+
+





-
+



-
-
+




-
+






-
+







-
-
+





-
-
+
+





-
+







		[copy addObject:@""];

	[copy makeImmutable];
	return copy;
}

@implementation Command
{
	id _block;
}

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

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

	_function = function;
	_argumentsTypes = argumentsTypes;
	_block = block;

	return self;
}

- (int)callWithArguments:(OFArray<OFString *> *)arguments isDown:(bool)isDown
{
	switch (_argumentsTypes) {
	case ARG_1INT:
		if (isDown) {
			arguments = padArguments(arguments, 2);
			((void(__cdecl *)(int))_function)(
			((void (^)(int))_block)(
			    [arguments[1] cube_intValueWithBase:0]);
		}
		break;
	case ARG_2INT:
		if (isDown) {
			arguments = padArguments(arguments, 3);
			((void(__cdecl *)(int, int))_function)(
			((void (^)(int, int))_block)(
			    [arguments[1] cube_intValueWithBase:0],
			    [arguments[2] cube_intValueWithBase:0]);
		}
		break;
	case ARG_3INT:
		if (isDown) {
			arguments = padArguments(arguments, 4);
			((void(__cdecl *)(int, int, int))_function)(
			((void (^)(int, int, int))_block)(
			    [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(__cdecl *)(int, int, int, int))_function)(
			((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]);
		}
		break;
	case ARG_NONE:
		if (isDown)
			((void(__cdecl *)())_function)();
			((void (^)())_block)();
		break;
	case ARG_1STR:
		if (isDown) {
			arguments = padArguments(arguments, 2);
			((void(__cdecl *)(OFString *))_function)(arguments[1]);
			((void (^)(OFString *))_block)(arguments[1]);
		}
		break;
	case ARG_2STR:
		if (isDown) {
			arguments = padArguments(arguments, 3);
			((void(__cdecl *)(OFString *, OFString *))_function)(
			((void (^)(OFString *, OFString *))_block)(
			    arguments[1], arguments[2]);
		}
		break;
	case ARG_3STR:
		if (isDown) {
			arguments = padArguments(arguments, 4);
			((void(__cdecl *)(
			    OFString *, OFString *, OFString *))_function)(
			((void (^)(OFString *, OFString *, OFString *))_block)(
			    arguments[1], arguments[2], arguments[3]);
		}
		break;
	case ARG_5STR:
		if (isDown) {
			arguments = padArguments(arguments, 6);
			((void(__cdecl *)(OFString *, OFString *, OFString *,
			    OFString *, OFString *))_function)(arguments[1],
			((void (^)(OFString *, OFString *, OFString *,
			    OFString *, OFString *))_block)(arguments[1],
			    arguments[2], arguments[3], arguments[4],
			    arguments[5]);
		}
		break;
	case ARG_DOWN:
		((void(__cdecl *)(bool))_function)(isDown);
		((void (^)(bool))_block)(isDown);
		break;
	case ARG_DWN1:
		arguments = padArguments(arguments, 2);
		((void(__cdecl *)(bool, OFString *))_function)(
		    isDown, arguments[1]);
		((void (^)(bool, OFString *))_block)(isDown, arguments[1]);
		break;
	case ARG_1EXP:
		if (isDown) {
			arguments = padArguments(arguments, 2);
			return ((int(__cdecl *)(int))_function)(
			return ((int (^)(int))_block)(
			    execute(arguments[1], isDown));
		}
		break;
	case ARG_2EXP:
		if (isDown) {
			arguments = padArguments(arguments, 3);
			return ((int(__cdecl *)(int, int))_function)(
			return ((int (^)(int, int))_block)(
			    execute(arguments[1], isDown),
			    execute(arguments[2], isDown));
		}
		break;
	case ARG_1EST:
		if (isDown) {
			arguments = padArguments(arguments, 2);
			return ((int(__cdecl *)(OFString *))_function)(
			    arguments[1]);
			return ((int (^)(OFString *))_block)(arguments[1]);
		}
		break;
	case ARG_2EST:
		if (isDown) {
			arguments = padArguments(arguments, 3);
			return ((int(__cdecl *)(OFString *,
			    OFString *))_function)(arguments[1], arguments[2]);
			return ((int (^)(OFString *, OFString *))_block)(
			    arguments[1], arguments[2]);
		}
		break;
	case ARG_VARI:
		if (isDown)
			// limit, remove
			((void(__cdecl *)(OFString *))_function)([[arguments
			((void (^)(OFString *))_block)([[arguments
			    objectsInRange:OFMakeRange(1, arguments.count - 1)]
			    componentsJoinedByString:@" "]);
		break;
	}

	return 0;
}

Modified src/Cube.m from [5126c37a7e] to [3b4164e066].

1
2
3
4

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




+







// main.cpp: initialisation & main loop

#include "cube.h"

#import "Command.h"
#import "DynamicEntity.h"

OF_APPLICATION_DELEGATE(Cube)

VARF(gamespeed, 10, 100, 1000, if (multiplayer()) gamespeed = 100);
VARP(minmillis, 0, 5, 1000);

369
370
371
372
373
374
375
376
377

378

379
380

381
382
383
384

385
386
387

388
370
371
372
373
374
375
376


377

378
379

380

381


382

383

384








-
-
+
-
+

-
+
-

-
-
+
-

-
+
-

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

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

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

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

Modified src/Identifier.h from [f570e25a18] to [8e21f78264].

1
2
3
4
5
6
7



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







+
+
+





#import <ObjFW/ObjFW.h>

OF_ASSUME_NONNULL_BEGIN

@interface Identifier: OFObject
@property (readonly, copy, nonatomic) OFString *name;

+ (void)addIdentifier:(__kindof Identifier *)identifier;
+ (__kindof Identifier *)identifierForName:(OFString *)name;
+ (void)enumerateIdentifiersUsingBlock:(void (^)(__kindof Identifier *))block;
- (instancetype)init OF_UNAVAILABLE;
- (instancetype)initWithName:(OFString *)name;
@end

OF_ASSUME_NONNULL_END

Modified src/Identifier.m from [dfc38df5ef] to [a76ca7afae].

1
2



3
























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


+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







#import "Identifier.h"

// contains ALL vars/commands/aliases
static OFMutableDictionary<OFString *, __kindof Identifier *> *identifiers;

@implementation Identifier
+ (void)initialize
{
	if (self == Identifier.class)
		identifiers = [[OFMutableDictionary alloc] init];
}

+ (void)addIdentifier:(__kindof Identifier *)identifier
{
	identifiers[identifier.name] = identifier;
}

+ (__kindof Identifier *)identifierForName:(OFString *)name
{
	return identifiers[name];
}

+ (void)enumerateIdentifiersUsingBlock:(void (^)(__kindof Identifier *))block
{
	[identifiers enumerateKeysAndObjectsUsingBlock:^(
	    OFString *name, __kindof Identifier *identifier, bool *stop) {
		block(identifier);
	}];
}

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

	_name = [name copy];

	return self;

Modified src/clientextras.m from [1114a27a28] to [b510debdf2].

1
2
3
4

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




+







// clientextras.cpp: stuff that didn't fit in client.cpp or clientgame.cpp :)

#include "cube.h"

#import "Command.h"
#import "DynamicEntity.h"
#import "Monster.h"

// render players & monsters
// very messy ad-hoc handling of animation frames, should be made more
// configurable

173
174
175
176
177
178
179
180
181

182
183
184
185
186
187
188
189
174
175
176
177
178
179
180


181

182
183
184
185
186
187
188







-
-
+
-







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

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

void
sendmap(OFString *mapname)
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;
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
205
206
207
208
209
210
211

212
213


214

215
216
217
218
219
220
221
222
223

224










-
+

-
-
+
-









-
+
-
-
-
	sendpackettoserv(packet);
	conoutf(@"sending map %@ to server...", mapname);
	OFString *msg =
	    [OFString stringWithFormat:@"[map %@ uploaded to server, "
	                               @"\"getmap\" to receive it]",
	              mapname];
	toserver(msg);
}
}))

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

COMMAND(sendmap, ARG_1STR)
COMMAND(getmap, ARG_NONE)

Modified src/clientgame.m from [8ff39fb916] to [3ae039157a].

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


14

15

16

17
18
19
20
21
22
23




+








-
-
+
-

-
+
-







// clientgame.cpp: core game related stuff

#include "cube.h"

#import "Command.h"
#import "DynamicEntity.h"
#import "Entity.h"
#import "Monster.h"
#import "OFString+Cube.h"

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

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

bool intermission = false;

DynamicEntity *player1;  // our client
OFMutableArray *players; // other clients

void
138
139
140
141
142
143
144
145
146

147
148
149
150

151
152
153
154
155
156
157
158
136
137
138
139
140
141
142


143

144
145

146

147
148
149
150
151
152
153







-
-
+
-


-
+
-







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

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

void
updateworld(int millis) // main game update loop
{
	if (lastmillis) {
		curtime = millis - lastmillis;
		if (sleepwait && lastmillis > sleepwait) {
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
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







-
-
-
-
+
+
+

-
+






-
-
+
-






-
+

-
-
+
-


-
+

-
-
-
-
-
-
-
+
+
+







	[d resetToSpawnState];
	d.state = CS_ALIVE;
}

// movement input code

#define dir(name, v, d, s, os)                                    \
	static void name(bool isdown)                             \
	{                                                         \
		player1.s = isdown;                               \
		player1.v = isdown ? d : (player1.os ? -(d) : 0); \
	COMMAND(name, ARG_DOWN, ^(bool isDown) {                  \
		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);

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

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

COMMAND(backward, ARG_DOWN)
COMMAND(forward, ARG_DOWN)
COMMAND(left, ARG_DOWN)
COMMAND(right, ARG_DOWN)
COMMANDN(jump, jumpn, ARG_DOWN)
COMMAND(attack, ARG_DOWN)
COMMAND(showscores, ARG_DOWN)
COMMAND(showscores, ARG_DOWN, ^(bool isDown) {
	showscores(isDown);
})

void
fixplayer1range()
{
	const float MAXPITCH = 90.0f;
	if (player1.pitch > MAXPITCH)
		player1.pitch = MAXPITCH;
436
437
438
439
440
441
442

443


422
423
424
425
426
427
428
429

430
431







+
-
+
+
	setvar(@"fogcolour", 0x8099B3);
	showscores(false);
	intermission = false;
	Cube.sharedInstance.framesInMap = 0;
	conoutf(@"game mode is %@", modestr(gamemode));
}

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

Modified src/clients.m from [6542299bf2] to [d14310e680].

1
2
3
4

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




+







// client.cpp, mostly network related client game code

#include "cube.h"

#import "Command.h"
#import "DynamicEntity.h"

static ENetHost *clienthost = NULL;
static int connecting = 0;
static int connattempts = 0;
static int disconnecting = 0;
// our client id in the game
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
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







-
+









+
-
+
+
+

-
+









+
-
+
+
+







	if (!clienthost || connecting)
		return;
	assert(ENET_PEER_PACKET_THROTTLE_SCALE == 32);
	enet_peer_throttle_configure(clienthost->peers,
	    throttle_interval * 1000, throttle_accel, throttle_decel);
}

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

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

	player1.name = name;
}

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

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

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

	player1.team = name;
}

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

void
writeclientinfo(OFStream *stream)
{
	[stream writeFormat:@"name \"%@\"\nteam \"%@\"\n", player1.name,
	        player1.team];
}
173
174
175
176
177
178
179
180
181

182
183
184
185


186
187
188
189








190
191
192
193
194
195
196
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







-
-
+
-

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







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

void
echo(OFString *text)
COMMAND(echo, ARG_VARI, ^(OFString *text) {
{
	conoutf(@"%@", text);
}

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

// collect c2s messages conveniently

static OFMutableArray<OFData *> *messages;

void
addmsg(int rel, int num, int type, ...)
235
236
237
238
239
240
241
242
243

244
245
246

247
248
249
250
251
252
253
254
244
245
246
247
248
249
250


251

252

253

254
255
256
257
258
259
260







-
-
+
-

-
+
-








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

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

bool
netmapstart()
{
	senditemstoserver = true;
	return clienthost != NULL;
}

Modified src/commands.m from [4117ae0f6a] to [116e44fb5b].

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
109
110
111
112
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











-
-
-









-
+

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







+
-
+
+
+





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






+
-
+
+
+





+
+
+
+
+
-
+





-
+





-
+







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-







// command.cpp: implements the parsing and execution of a tiny script language
// which is largely backwards compatible with the quake console language.

#include "cube.h"

#import "Alias.h"
#import "Command.h"
#import "Identifier.h"
#import "OFString+Cube.h"
#import "Variable.h"

// contains ALL vars/commands/aliases
static OFMutableDictionary<OFString *, __kindof Identifier *> *identifiers;

static void
cleanup(char **string)
{
	free(*string);
}

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

	if (alias == nil) {
		alias = [Alias aliasWithName:name action:action persisted:true];

	if (alias == nil)
		[Identifier addIdentifier:[Alias aliasWithName:name
		                                        action:action
		if (identifiers == nil)
			identifiers = [[OFMutableDictionary alloc] init];

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

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

int
variable(OFString *name, int min, int cur, int max, int *storage,
    void (*function)(), bool persisted)
{
	Variable *variable = [Variable variableWithName:name
	                                            min:min
	                                            max:max
	                                        storage:storage
	                                       function:function
	                                      persisted:persisted];
	[Identifier addIdentifier:[Variable variableWithName:name
	                                                 min:min
	                                                 max:max
	                                             storage:storage
	                                            function:function
	                                           persisted:persisted]];

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

	identifiers[name] = variable;

	return cur;
}

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

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

int
getvar(OFString *name)
{
	Variable *variable = [Identifier identifierForName:name];

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

	return *[identifiers[name] storage];
	return 0;
}

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

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

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

	return nil;
}

bool
addcommand(OFString *name, void (*function)(), int argumentsTypes)
{
	Command *command = [Command commandWithName:name
	                                   function:function
	                             argumentsTypes:argumentsTypes];

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

	identifiers[name] = command;

	return false;
}

// parse any nested set of () or []
static char *
parseexp(char **p, int right)
{
	int left = *(*p)++;
	char *word = *p;
	for (int brak = 1; brak;) {
164
165
166
167
168
169
170
171


172
173
174
175
176
177
178
148
149
150
151
152
153
154

155
156
157
158
159
160
161
162
163







-
+
+







	return strndup(word, *p - word);
}

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

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

	conoutf(@"unknown alias lookup: %@", [n substringFromIndex:1]);
271
272
273
274
275
276
277
278

279
280
281
282
283
284
285
256
257
258
259
260
261
262

263
264
265
266
267
268
269
270







-
+







			c = [c substringFromIndex:1];
			w[0] = c;
		}
		// empty statement
		if (c.length == 0)
			continue;

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

	return val;
}

// tab-completion of all identifiers
303
304
305
306
307
308
309
310
311


312
313
314
315
316
317
318
288
289
290
291
292
293
294


295
296
297
298
299
300
301
302
303







-
-
+
+








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

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

343
344
345
346
347
348
349




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







+
+
+
+







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

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

void
writecfg()
{
	OFStream *stream;
	@try {
		OFIRI *IRI = [Cube.sharedInstance.userDataIRI
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
500

501
502
503
504

505
506
507

508
509
510
511

512
513
514

515
516
517
518

519
520
521

522
523
524
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
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

500

501

502


503



504
505

506


507

508

509








-
-
-
-
-
+
+
+
+
+

-
-
-
+
+
+





-
-
-
-
-
+
+
+
+
+

-
-
-
+
+
+




-
+
+
+











-
-
+
-

-
+

-
-
+
-






-
+

-
-
+
-


-
+

-
-
+
-


-
+







-
-
-
+
+
+
+
+

-
+

-
-
+
-











-
+

-
-
+
-










-
+

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

-
+
-

-
-
+
-

-
+
-

-
-
+
-

-
+
-

-
-
+
-

-
+
-

-
-
+
-

+
-
+
-
-
-
-
+
-

-
+
-

-
-
+
-

-
+
-

-
-
+
-

-
+
-

-
-
+
-

-
+
-

-
-
+
-
-
-
+
+
-

-
-
+
-

-
+
-
	                    @"overwrite these settings\n"
	                    @"// modify settings in game, or put settings in "
	                    @"autoexec.cfg to override anything\n"
	                    @"\n"];
	writeclientinfo(stream);
	[stream writeString:@"\n"];

	[identifiers enumerateKeysAndObjectsUsingBlock:^(
	    OFString *name, __kindof Identifier *identifier, bool *stop) {
		if (![identifier isKindOfClass:Variable.class] ||
		    ![identifier persisted])
			return;
	[Identifier
	    enumerateIdentifiersUsingBlock:^(__kindof Identifier *identifier) {
		    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"];

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

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

		[stream writeFormat:@"alias \"%@\" [%@]\n", identifier.name,
		        [identifier action]];
	}];
		    [stream writeFormat:@"alias \"%@\" [%@]\n", identifier.name,
		            [identifier 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]);
}

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

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

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

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

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

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

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

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

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

COMMANDN(loop, loopa, ARG_2STR)
COMMANDN(while, whilea, ARG_2STR)
COMMANDN(if, ifthen, ARG_3STR)
COMMAND(onrelease, ARG_DWN1)
COMMAND(exec, ARG_1STR)
COMMAND(concat, ARG_VARI)
COMMAND(concatword, ARG_VARI)
COMMAND(at, ARG_2STR)
COMMAND(listlen, ARG_1EST)

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

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

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

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

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

COMMAND(mod, ARG_2EXP)

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

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

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

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

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

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

Modified src/console.m from [1ac50365c9] to [d7f7b48227].

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






+













-
-
+
-



-
+
-







// console.cpp: the console buffer, its display, and command line control

#include "cube.h"

#include <ctype.h>

#import "Command.h"
#import "ConsoleLine.h"
#import "KeyMapping.h"
#import "OFString+Cube.h"

static OFMutableArray<ConsoleLine *> *conlines;

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

bool saycommandon = false;
static OFMutableString *commandbuf;

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

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

	// constrain the buffer size
100
101
102
103
104
105
106
107
108

109
110
111
112
113
114
115
116
117

118
119
120
121

122
123
124
125
126
127
128
129
130
131
132

133
134

135
136


137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156





157
158
159

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

173
174
175
176
177
178
179
180
181

182
183
184
185
186
187
188
189
98
99
100
101
102
103
104


105

106
107
108
109
110
111
112

113

114


115

116
117
118
119
120
121
122
123
124

125

126
127


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

145



146
147
148
149
150
151
152

153

154
155
156
157
158
159
160
161
162
163


164

165
166
167
168
169
170
171

172

173
174
175
176
177
178
179







-
-
+
-







-
+
-

-
-
+
-









-
+
-

+
-
-
+
+















-

-
-
-
+
+
+
+
+


-
+
-










-
-
+
-







-
+
-







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

// keymap is defined externally in keymap.cfg

static OFMutableArray<KeyMapping *> *keyMappings = nil;

void
keymap(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];
	mapping.action = action;
	[keyMappings addObject:mapping];
}
})
COMMAND(keymap, ARG_3STR)

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

	conoutf(@"unknown key \"%@\"", key);
}
})
COMMANDN(bind, bindkey, ARG_2STR)

// turns input to the command line on or off
void
saycommand(OFString *init) // turns input to the command line on or off
static void
saycommand(OFString *init)
{
	saycommandon = (init != nil);
	if (saycommandon)
		SDL_StartTextInput();
	else
		SDL_StopTextInput();

	if (!editmode)
		Cube.sharedInstance.repeatsKeys = saycommandon;

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

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

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

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

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

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

void
history(int n)
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;
	}
}
})
COMMAND(history, ARG_1INT)

void
keypress(int code, bool isDown)
{
	// keystrokes go to commandline
	if (saycommandon) {
		if (isDown) {

Modified src/cube.h from [96eff6d77d] to [6875cee25c].

336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
336
337
338
339
340
341
342








343
344
345
346
347
348
349







-
-
-
-
-
-
-
-







	ARG_1EST,
	ARG_2EST,
	ARG_VARI
};

// nasty macros for registering script functions, abuses globals to avoid
// excessive infrastructure
#define COMMANDN(name, fun, nargs)                                   \
	OF_CONSTRUCTOR()                                             \
	{                                                            \
		enqueueInit(^{                                       \
			addcommand(@ #name, (void (*)())fun, nargs); \
		});                                                  \
	}
#define COMMAND(name, nargs) COMMANDN(name, name, nargs)
#define VARP(name, min, cur, max)                                       \
	int name;                                                       \
	OF_CONSTRUCTOR()                                                \
	{                                                               \
		enqueueInit(^{                                          \
			name = variable(                                \
			    @ #name, min, cur, max, &name, NULL, true); \

Modified src/editing.m from [6ba0a980d9] to [696569b661].

1
2
3
4
5

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





+







// editing.cpp: most map editing commands go here, entity editing commands are
// in world.cpp

#include "cube.h"

#import "Command.h"
#import "DynamicEntity.h"
#import "Monster.h"
#import "OFString+Cube.h"

bool editmode = false;

// the current selection, used by almost all editing commands
74
75
76
77
78
79
80


81


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

84
85
86
87
88
89
90
91
92







+
+
-
+
+







			[Monster resetAll];
		projreset();
	}
	Cube.sharedInstance.repeatsKeys = editmode;
	selset = false;
	editing = editmode;
}

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

void
correctsel() // ensures above invariant
{
	selset = !OUTBORD(sel.x, sel.y);
	int bsize = ssize - MINBORD;
	if (sel.xs + sel.x > bsize)
116
117
118
119
120
121
122
123
124

125
126
127
128
129
130

131
132
133
134
135
136
137
120
121
122
123
124
125
126


127

128
129
130
131

132
133
134
135
136
137
138
139







-
-
+
-




-
+







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

void
selectpos(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
makesel()
{
	struct block s = { min(lastx, cx), min(lasty, cy), abs(lastx - cx) + 1,
		abs(lasty - cy) + 1 };
	sel = s;
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
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







-
-
+
-









-
+



-
-
+
-






-
+

-
-
+
-
















-
+







		    initWithItemSize:sizeof(struct block *)];

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

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

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

	if (copybuf)
		OFFreeMemory(copybuf);

	copybuf = blockcopy(&sel);
}
})

void
paste()
COMMAND(paste, ARG_NONE, ^{
{
	EDITMP;
	if (!copybuf) {
		conoutf(@"nothing to paste");
		return;
	}
	sel.xs = copybuf->xs;
	sel.ys = copybuf->ys;
	correctsel();
	if (!selset || sel.xs != copybuf->xs || sel.ys != copybuf->ys) {
		conoutf(@"incorrect selection");
		return;
	}
	makeundo();
	copybuf->x = sel.x;
	copybuf->y = sel.y;
	blockpaste(copybuf);
}
})

void
tofronttex() // maintain most recently used of the texture lists when applying
             // texture
{
	for (int i = 0; i < 3; i++) {
		int c = curedittex[i];
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
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







+
-
+
+
+




















-
-
+
-

















-
+

-
-
+
-







{
	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)
COMMAND(editheight, ARG_2INT, ^(int flr, int amount) {
	editheight(flr, amount);
})

void
edittexxy(int type, int t, const struct block *sel)
{
	loopselxy(switch (type) {
	        case 0:
		        s->ftex = t;
		        break;
	        case 1:
		        s->wtex = t;
		        break;
	        case 2:
		        s->ctex = t;
		        break;
	        case 3:
		        s->utex = t;
		        break;
	});
}

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

void
replace()
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:
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
500
501
502
503
504
505
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







-
+







-
+















-
-
+
-

-
+
-

-
-
+
-

-
+
-

-
-
+
-

-
+
-







				break;
			}
		}
	}

	struct block b = { 0, 0, ssize, ssize };
	remip(&b, 0);
}
}))

void
edittypexy(int type, const struct block *sel)
{
	loopselxy(s->type = type);
}

void
static void
edittype(int type)
{
	EDITSEL;

	if (type == CORNER &&
	    (sel.xs != sel.ys || sel.xs == 3 || (sel.xs > 4 && sel.xs != 8) ||
	        sel.x & ~-sel.xs || sel.y & ~-sel.ys)) {
		conoutf(@"corner selection must be power of 2 aligned");
		return;
	}

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

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

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

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

void
editequalisexy(bool isfloor, const struct block *sel)
{
	int low = 127, hi = -128;
	loopselxy({
		if (s->floor < low)
513
514
515
516
517
518
519
520
521

522
523
524
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
579
580
581
582
583
584
585
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
620
621
622
623
624
625
626
627
628
629
630
631
632
633

634
635
636
637
638
639
640
641
642
643
644

645
646
647
648
649
650
651
652

653
654
655


656
657

658
659
660
661
662




663
664
665
666
667
668
669
670
671
672
673
674
675
676
499
500
501
502
503
504
505


506

507
508
509
510
511
512

513

514
515
516
517
518
519
520
521


522

523
524
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
579
580
581
582
583

584
585


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
620
621
622

623
624


625
626


627
628




629
630
631
632





















-
-
+
-






-
+
-








-
-
+
-




-
+





-
-
+
-









-
+

-
-
+
-



















-
+

-
-
+
-
















-
+

-
-
+
-



















-
+









-
-
+
-






-
+

-
-
+
+
-
-
+

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

void
equalize(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);
}
})
COMMAND(equalize, ARG_1INT)

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

void
setvdelta(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;

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

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

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

	if (sel.xs > MAXARCHVERT)
		sel.xs = MAXARCHVERT;
	if (sel.ys > MAXARCHVERT)
		sel.ys = MAXARCHVERT;

	struct block *sel_ = &sel;
	// Ugly hack to make the macro work.
	struct block *sel = sel_;
	loopselxy(s->vdelta = sel->xs > sel->ys
	        ? (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);
}
})

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

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

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

	makeundo();

	sel.xs--;
	sel.ys--;

	perlinarea(&sel, scale, seed, psize);

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

	remipmore(&sel, 0);

	sel.xs--;
	sel.ys--;
}
})

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

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

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

	newentity(sel.x, sel.y, (int)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)player1.origin.z, what,
	        [a1 cube_intValueWithBase:0], [a2 cube_intValueWithBase:0],
	        [a3 cube_intValueWithBase:0], [a4 cube_intValueWithBase:0]);
    })

COMMANDN(select, selectpos, ARG_4INT)
COMMAND(edittag, ARG_1INT)
COMMAND(replace, ARG_NONE)
COMMAND(archvertex, ARG_3INT)
COMMAND(arch, ARG_2INT)
COMMAND(slope, ARG_2INT)
COMMANDN(vdelta, setvdelta, ARG_1INT)
COMMANDN(undo, editundo, ARG_NONE)
COMMAND(copy, ARG_NONE)
COMMAND(paste, ARG_NONE)
COMMAND(edittex, ARG_2INT)
COMMAND(newent, ARG_5STR)
COMMAND(perlin, ARG_3INT)

Modified src/menus.m from [9134766c32] to [d14babe2b3].

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






+
















-
-
+
-








-
+
-







// menus.cpp: ingame menu system (also used for scores and serverlist)

#include "cube.h"

#import "Menu.h"

#import "Command.h"
#import "DynamicEntity.h"
#import "MenuItem.h"

static OFMutableArray<OFNumber *> *menuStack;
static OFMutableArray<Menu *> *menus;
static int vmenu = -1;

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

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

void
sortmenu()
{
	[menus[0].items sort];
}

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







+
-
+
+
+











-
-
+
-






-
+
-







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

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

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

void
menuitem(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];
}
})
COMMAND(menuitem, ARG_2STR)

bool
menukey(int code, bool isdown)
{
	if (vmenu <= 0)
		return false;

Modified src/protos.h from [5294dddff8] to [6ea87debb4].

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

13
14
15
16
17
18
19












-







// protos for ALL external functions in cube...

#ifdef __cplusplus
extern "C" {
#endif

// command
extern int variable(OFString *name, int min, int cur, int max, int *storage,
    void (*fun)(), bool persist);
extern void setvar(OFString *name, int i);
extern int getvar(OFString *name);
extern bool identexists(OFString *name);
extern bool addcommand(OFString *name, void (*fun)(), int narg);
extern int execute(OFString *p, bool down);
extern void exec(OFString *cfgfile);
extern bool execfile(OFIRI *cfgfile);
extern void resetcomplete();
extern void complete(OFMutableString *s);
extern void alias(OFString *name, OFString *action);
extern OFString *getalias(OFString *name);

Modified src/rendercubes.m from [3c13e91f26] to [03979e8529].

1
2
3
4


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




+
+







// rendercubes.cpp: sits in between worldrender.cpp and rendergl.cpp and fills
// the vertex array for different cube surfaces.

#include "cube.h"

#import "Command.h"

static struct vertex *verts = NULL;
int curvert;
static int curmaxverts = 10000;

void
setarraypointers()
57
58
59
60
61
62
63
64
65

66
67
68

69
70
71
72
73
74
75
76
59
60
61
62
63
64
65


66

67

68

69
70
71
72
73
74
75







-
-
+
-

-
+
-







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;

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

void
mipstats(int a, int b, int c)
{
	if (showm)
		conoutf(@"1x1/2x2/4x4: %d / %d / %d", a, b, c);
}

Modified src/renderextras.m from [2902b001e4] to [e6c4962238].

1
2
3
4

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




+







// renderextras.cpp: misc gl render code and the HUD

#include "cube.h"

#import "Command.h"
#import "DynamicEntity.h"
#import "Entity.h"

void
line(int x1, int y1, float z1, int x2, int y2, float z2)
{
	glBegin(GL_POLYGON);
199
200
201
202
203
204
205
206
207

208
209
210
211
212
213
214
215
200
201
202
203
204
205
206


207

208
209
210
211
212
213
214







-
-
+
-







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

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

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

	if ([lastsky isEqual:basename])
		return;
226
227
228
229
230
231
232
233

234
235
236
237
238
239
240
241
225
226
227
228
229
230
231

232

233
234
235
236
237
238
239







-
+
-







		        [Cube.sharedInstance.gameDataIRI
		            IRIByAppendingPathComponent:path],
		        &xs, &ys, true))
			conoutf(@"could not load sky textures");
	}

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

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

void

Modified src/rendergl.m from [d9ebc3b45e] to [e1c74cee31].

1
2
3
4

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




+







// rendergl.cpp: core opengl rendering stuff

#include "cube.h"

#import "Command.h"
#import "DynamicEntity.h"
#import "Monster.h"
#import "OFString+Cube.h"

#ifdef DARWIN
# define GL_COMBINE_EXT GL_COMBINE_ARB
# define GL_COMBINE_RGB_EXT GL_COMBINE_RGB_ARB
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
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







-
-
+
-

-
+
-

-
-
+
-








-
+
-







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

int curtexnum = 0;

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

void
texture(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:@"/"];
}
}))
COMMAND(texture, ARG_2STR)

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

Modified src/rendermd2.m from [3f3cf16cef] to [7b42d00ad3].

1
2
3
4

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




+







// rendermd2.cpp: loader code adapted from a nehe tutorial

#include "cube.h"

#import "Command.h"
#import "DynamicEntity.h"
#import "MD2.h"
#import "MapModelInfo.h"
#import "OFString+Cube.h"

static OFMutableDictionary<OFString *, MD2 *> *mdllookup = nil;
static OFMutableArray<MD2 *> *mapmodels = nil;
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
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







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

-
-
+
+

-
-
+
+
-

-
-
+
-

-
+
-







		mdllookup = [[OFMutableDictionary alloc] init];

	mdllookup[name] = m;

	return m;
}

void
mapmodel(
    OFString *rad, OFString *h, OFString *zoff, OFString *snap, OFString *name)
COMMAND(mapmodel, ARG_5STR,
    ^(OFString *rad, OFString *h, OFString *zoff, OFString *snap,
{
	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];
        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];

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

	[mapmodels addObject:m];
}
	    [mapmodels addObject:m];
    })
COMMAND(mapmodel, ARG_5STR)

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

MapModelInfo *
getmminfo(int i)
{
	return i < mapmodels.count ? mapmodels[i].mmi : nil;
}

Modified src/savegamedemo.m from [3891d96448] to [1beb03a02a].

1
2
3
4
5

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





+







// loading and saving of savegames & demos, dumps the spawn state of all
// mapents, the full state of all dynents (monsters + player)

#include "cube.h"

#import "Command.h"
#import "DynamicEntity.h"
#import "Entity.h"
#import "Monster.h"

#ifdef OF_BIG_ENDIAN
static const int islittleendian = 0;
#else
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
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







-
-
+
-











-
+
-







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

void
savegame(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];
	savestate(IRI);
	stop();
	conoutf(@"wrote %@", IRI.string);
}
}))
COMMAND(savegame, ARG_1STR)

void
loadstate(OFIRI *IRI)
{
	stop();
	if (multiplayer())
		return;
180
181
182
183
184
185
186
187
188

189
190
191
192
193
194

195
196
197
198
199
200
201
202
178
179
180
181
182
183
184


185

186
187
188
189

190

191
192
193
194
195
196
197







-
-
+
-




-
+
-







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

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

void
loadgameout()
{
	stop();
	conoutf(@"loadgame incomplete: savegame from a different version of "
	        @"this map");
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
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







-
-
+
-


















-
+
-







// demo functions

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

void
record(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];
	savestate(IRI);
	gzputi(cn);
	conoutf(@"started recording demo to %@", IRI.string);
	demorecording = true;
	starttime = lastmillis;
	ddamage = bdamage = 0;
}
}))
COMMAND(record, ARG_1STR)

void
demodamage(int damage, const OFVector3D *o)
{
	ddamage = damage;
	dorig = *o;
}
333
334
335
336
337
338
339
340
341

342
343
344
345
346
347
348

349
350
351
352
353
354
355
356
325
326
327
328
329
330
331


332

333
334
335
336
337

338

339
340
341
342
343
344
345







-
-
+
-





-
+
-







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

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

void
stopreset()
{
	conoutf(@"demo stopped (%d msec elapsed)", lastmillis - starttime);
	stop();
	[players removeAllObjects];
545
546
547
548
549
550
551
552
553

554
555
556
557
558
559
560

561
534
535
536
537
538
539
540


541

542
543
544
545
546

547








-
-
+
-





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

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

Modified src/serverbrowser.m from [5762ec3ef4] to [6d0f47176b].

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






+







// serverbrowser.cpp: eihrul's concurrent resolver, and server browser window
// management

#include "SDL_thread.h"
#include "cube.h"

#import "Command.h"
#import "ResolverResult.h"
#import "ResolverThread.h"
#import "ServerInfo.h"

static OFMutableArray<ResolverThread *> *resolverthreads;
OFMutableArray<OFString *> *resolverqueries;
OFMutableArray<ResolverResult *> *resolverresults;
121
122
123
124
125
126
127




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







+
+
+
+







			return;

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

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

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

void
pingservers()
{
	ENetBuffer buf;
	unsigned char ping[MAXTRANS];
	unsigned char *p;
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
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







-
+
















-
-
-
+
+
+
+
+











-
+
-
-
-
-












		menumanual(1, i, si.full);

		if (!--maxmenu)
			return;
	}];
}

void
static void
servermenu()
{
	if (pingsock == ENET_SOCKET_NULL) {
		pingsock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM, NULL);
		resolverinit(1, 1000);
	}

	resolverclear();

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

	refreshservers();
	menuset(1);
}

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

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 {
		[servers removeAllObjects];
		execute(@((char *)reply), true);
	}
	servermenu();
}
})

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

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

Modified src/sound.m from [4ad5c2248e] to [24cd809ba3].

1
2

3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
10


+







#include "cube.h"

#import "Command.h"
#import "DynamicEntity.h"

#include <SDL_mixer.h>

VARP(soundvol, 0, 255, 255);
VARP(musicvol, 0, 128, 255);
bool nosound = false;
43
44
45
46
47
48
49
50
51

52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72

73
74
75
76
77
78
79

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

102
103
104
105
106
107
108
109
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







-
-
+
-



















-
+
-




-
-
+
-




















-
+
-







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

void
music(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];
		OFIRI *IRI = [Cube.sharedInstance.gameDataIRI
		    IRIByAppendingPathComponent:path];

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

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

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

		i++;
	}

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

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

	return samples.count - 1;
}
})
COMMAND(registersound, ARG_1EST)

void
cleansound()
{
	if (nosound)
		return;
	stopsound();
212
213
214
215
216
217
218
219
220

221
222
223

224
207
208
209
210
211
212
213


214

215

216








-
-
+
-

-
+
-

	if (loc)
		newsoundloc(chan, loc);

	updatechanvol(chan, loc);
}

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

Modified src/weapon.m from [d7a9ce79c2] to [a4ab4f9d09].

1
2
3
4

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




+







// weapon.cpp: all shooting and effects code

#include "cube.h"

#import "Command.h"
#import "DynamicEntity.h"
#import "Monster.h"
#import "OFString+Cube.h"
#import "Projectile.h"

static const int MONSTERDAMAGEFACTOR = 4;
#define SGRAYS 20
58
59
60
61
62
63
64
65
66

67
68
69
70
71

72
73
74
75
76
77
78
79
59
60
61
62
63
64
65


66

67
68
69

70

71
72
73
74
75
76
77







-
-
+
-



-
+
-








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

void
weapon(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));
}
})
COMMAND(weapon, ARG_3STR)

// create random spread of rays for the shotgun
void
createrays(const OFVector3D *from, const OFVector3D *to)
{
	vdist(dist, dvec, *from, *to);
	float f = dist * SGSPREAD / 1000;

Modified src/world.m from [b3c4009ae5] to [e48df32f18].

1
2
3
4

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




+







// world.cpp: core map management stuff

#include "cube.h"

#import "Command.h"
#import "DynamicEntity.h"
#import "Entity.h"
#import "Monster.h"

extern OFString *entnames[]; // lookup from map entities above to strings

struct sqr *world = NULL;
87
88
89
90
91
92
93

94



95
96
97
98
99
100
101
88
89
90
91
92
93
94
95

96
97
98
99
100
101
102
103
104
105







+
-
+
+
+








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

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

COMMAND(trigger, ARG_2INT)
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
// rendering time if this is possible).

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







-
-
+
-

















-
+

-
-
+
-











-
+







			bdist = dist;
		}
	}];

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

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

void
delent()
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]])
			return i;
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
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







-
-
+
-











-
+
-










-
-
+
-


















-
+
-








	if (type == LIGHT)
		calclight();

	return e;
}

void
clearents(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)
			e.type = NOTUSED;

	if (type == LIGHT)
		calclight();
}
})
COMMAND(clearents, ARG_1STR)

static unsigned char
scalecomp(unsigned char c, int intens)
{
	int n = c * intens / 100;
	if (n > 255)
		n = 255;
	return n;
}

void
scalelights(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;
		if (e.attr1 > 32)
			e.attr1 = 32;

		if (intens) {
			e.attr2 = scalecomp(e.attr2, intens);
			e.attr3 = scalecomp(e.attr3, intens);
			e.attr4 = scalecomp(e.attr4, intens);
		}
	}

	calclight();
}
})
COMMAND(scalelights, ARG_2INT)

int
findentity(int type, int index)
{
	for (int i = index; i < ents.count; i++)
		if (ents[i].type == type)
			return i;
547
548
549
550
551
552
553
554
555

556
557
558

559
560
561

562
563
564

565
566

567
568

569
570

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

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

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

COMMAND(mapenlarge, ARG_NONE)
COMMAND(recalc, ARG_NONE, ^{
COMMAND(newmap, ARG_1INT)
COMMANDN(recalc, calclight, ARG_NONE)
	calclight();
COMMAND(delent, ARG_NONE)
COMMAND(entproperty, ARG_2INT)
})

Modified src/worldio.m from [7be3522f11] to [f78faa674c].

1
2
3
4

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




+







// worldio.cpp: loading & saving of maps and savegames

#include "cube.h"

#import "Command.h"
#import "Entity.h"

struct persistent_entity {
	short x, y, z; // cube aligned position
	short attr1;
	unsigned char type; // type is one of the above
	unsigned char attr2, attr3, attr4;
252
253
254
255
256
257
258


259


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

262
263
264
265
266
267
268
269
270







+
+
-
+
+







		t = s;
	}
	spurge;
	gzclose(f);
	conoutf(@"wrote map file %@", cgzname);
	settagareas();
}

COMMAND(savemap, ARG_1STR, ^(OFString *mname) {
COMMANDN(savemap, save_world, ARG_1STR)
	save_world(mname);
})

void
load_world(OFString *mname) // still supports all map formats that have existed
                            // since the earliest cube betas!
{
	stopifrecording();
	cleardlights();

Modified src/worldocull.m from [1dcc5bb2a7] to [80938b4118].

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


14

15

16

17
18
19
20
21
22
23




+








-
-
+
-

-
+
-







// worldocull.cpp: occlusion map and occlusion test

#include "cube.h"

#import "Command.h"
#import "DynamicEntity.h"

#define NUMRAYS 512

float rdist[NUMRAYS];
bool ocull = true;
float odist = 256;

void
toggleocull()
COMMAND(toggleocull, ARG_NONE, ^{
{
	ocull = !ocull;
}
})
COMMAND(toggleocull, ARG_NONE)

// constructs occlusion map: cast rays in all directions on the 2d plane and
// record distance. done exactly once per frame.

void
computeraytable(float vx, float vy)
{