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
#import "Identifier.h"

OF_ASSUME_NONNULL_BEGIN












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

+ (instancetype)commandWithName:(OFString *)name
                       function:(void (*)())function
                 argumentsTypes:(int)argumentsTypes;

- (instancetype)initWithName:(OFString *)name OF_UNAVAILABLE;
- (instancetype)initWithName:(OFString *)name
                    function:(void (*)())function
              argumentsTypes:(int)argumentsTypes;

- (int)callWithArguments:(OFArray<OFString *> *)arguments isDown:(bool)isDown;
@end

OF_ASSUME_NONNULL_END




>
>
>
>
>
>
>
>
>
>
>

<



<
|
>


<
|
>




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) 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;
- (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
		[copy addObject:@""];

	[copy makeImmutable];
	return copy;
}

@implementation Command




+ (instancetype)commandWithName:(OFString *)name
                       function:(void (*)())function
                 argumentsTypes:(int)argumentsTypes

{
	return [[self alloc] initWithName:name
	                         function:function
	                   argumentsTypes:argumentsTypes];

}

- (instancetype)initWithName:(OFString *)name
                    function:(void (*)())function
              argumentsTypes:(int)argumentsTypes

{
	self = [super initWithName:name];

	_function = function;
	_argumentsTypes = argumentsTypes;


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

	return 0;
}







>
>
>
>

<

>


<
|
>



<

>



<

>










|






|







|








|








|




|





|






<
|






|
|





|



<
|




|






|







<
|





|
|





|







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

                 argumentsTypes:(int)argumentsTypes
                          block:(id)block
{
	return [[self alloc] initWithName:name

	                   argumentsTypes:argumentsTypes
	                            block:block];
}

- (instancetype)initWithName:(OFString *)name

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


	_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 (^)(int))_block)(
			    [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]);
		}
		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]);
		}
		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]);
		}
		break;
	case ARG_NONE:
		if (isDown)
			((void (^)())_block)();
		break;
	case ARG_1STR:
		if (isDown) {
			arguments = padArguments(arguments, 2);
			((void (^)(OFString *))_block)(arguments[1]);
		}
		break;
	case ARG_2STR:
		if (isDown) {
			arguments = padArguments(arguments, 3);
			((void (^)(OFString *, OFString *))_block)(
			    arguments[1], arguments[2]);
		}
		break;
	case ARG_3STR:
		if (isDown) {
			arguments = padArguments(arguments, 4);

			((void (^)(OFString *, OFString *, OFString *))_block)(
			    arguments[1], arguments[2], arguments[3]);
		}
		break;
	case ARG_5STR:
		if (isDown) {
			arguments = padArguments(arguments, 6);
			((void (^)(OFString *, OFString *, OFString *,
			    OFString *, OFString *))_block)(arguments[1],
			    arguments[2], arguments[3], arguments[4],
			    arguments[5]);
		}
		break;
	case ARG_DOWN:
		((void (^)(bool))_block)(isDown);
		break;
	case ARG_DWN1:
		arguments = padArguments(arguments, 2);

		((void (^)(bool, OFString *))_block)(isDown, arguments[1]);
		break;
	case ARG_1EXP:
		if (isDown) {
			arguments = padArguments(arguments, 2);
			return ((int (^)(int))_block)(
			    execute(arguments[1], isDown));
		}
		break;
	case ARG_2EXP:
		if (isDown) {
			arguments = padArguments(arguments, 3);
			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 (^)(OFString *))_block)(arguments[1]);
		}
		break;
	case ARG_2EST:
		if (isDown) {
			arguments = padArguments(arguments, 3);
			return ((int (^)(OFString *, OFString *))_block)(
			    arguments[1], arguments[2]);
		}
		break;
	case ARG_VARI:
		if (isDown)
			// limit, remove
			((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
// main.cpp: initialisation & main loop

#include "cube.h"


#import "DynamicEntity.h"

OF_APPLICATION_DELEGATE(Cube)

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





>







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

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

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

void
quit() // normal exit
{

	[Cube.sharedInstance quit];
}
COMMAND(quit, ARG_NONE)

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







<
|
<
>

|
<

<
|
<

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


// normal exit

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



COMMAND(screenshot, ARG_NONE, ^{

	[Cube.sharedInstance screenshot];
})

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

1
2
3
4
5
6
7



8
9
10
11
12
#import <ObjFW/ObjFW.h>

OF_ASSUME_NONNULL_BEGIN

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




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

OF_ASSUME_NONNULL_END







>
>
>





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
#import "Identifier.h"




@implementation Identifier
























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

	_name = [name copy];

	return self;


>
>
>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







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
// clientextras.cpp: stuff that didn't fit in client.cpp or clientgame.cpp :)

#include "cube.h"


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

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





>







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
		menumanual(0, scoreLines.count, @"");
		menumanual(0, scoreLines.count + 1, teamScores);
	}
}

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

void
sendmap(OFString *mapname)
{
	if (mapname.length > 0)
		save_world(mapname);
	changemap(mapname);
	mapname = getclientmap();
	OFData *mapdata = readmap(mapname);
	if (mapdata == nil)
		return;







<
|
<







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


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

void
getmap()
{
	ENetPacket *packet =
	    enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
	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)







|

<
|
<









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


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 [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
// clientgame.cpp: core game related stuff

#include "cube.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)
{
	addmsg(1, 2, SV_GAMEMODE, nextmode = n);
}
COMMAND(mode, ARG_1INT)

bool intermission = false;

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

void




>








<
|
<

|
<







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


COMMAND(mode, ARG_1INT, ^(int n) {

	addmsg(1, 2, SV_GAMEMODE, nextmode = n);
})


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







<
|
<


|
<







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;

COMMAND(sleep, ARG_2STR, ^(OFString *msec, OFString *cmd) {

	sleepwait = msec.cube_intValue + lastmillis;
	sleepcmd = cmd;
})


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
	[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); \
		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)
{
	if (intermission)
		return;
	if (editmode)
		editdrag(on);
	else if ((player1.attacking = on))
		respawn();
}

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



void
fixplayer1range()
{
	const float MAXPITCH = 90.0f;
	if (player1.pitch > MAXPITCH)
		player1.pitch = MAXPITCH;







<
|
|
|

|






<
|
<






|

<
|
<


|

<
<
<
<
<
<
|
>
>







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

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


COMMAND(attack, ARG_DOWN, ^(bool on) {

	if (intermission)
		return;
	if (editmode)
		editdrag(on);
	else if ((player1.attacking = on))
		respawn();
})


COMMAND(jump, ARG_DOWN, ^(bool on) {

	if (!intermission && (player1.jumpNext = on))
		respawn();
})







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

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


COMMANDN(map, changemap, ARG_1STR)








>
|
>
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) {
	changemap(name);
})

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

1
2
3
4

5
6
7
8
9
10
11
// client.cpp, mostly network related client game code

#include "cube.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




>







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
	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
newname(OFString *name)
{
	c2sinit = false;

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

	player1.name = name;
}

COMMANDN(name, newname, ARG_1STR)



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

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

	player1.team = name;
}

COMMANDN(team, newteam, ARG_1STR)



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







|









>
|
>
>

|









>
|
>
>







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

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

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

	player1.name = name;
}

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

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

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

	player1.team = name;
}

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
void
toserver(OFString *text)
{
	conoutf(@"%@:\f %@", player1.name, text);
	ctext = text;
}

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

COMMAND(echo, ARG_VARI)
COMMANDN(say, toserver, ARG_VARI)


COMMANDN(connect, connects, ARG_1STR)

COMMANDN(disconnect, trydisconnect, ARG_NONE)



// collect c2s messages conveniently

static OFMutableArray<OFData *> *messages;

void
addmsg(int rel, int num, int type, ...)







<
|
<

|
|
<
|
>
>
|
>
|
>
>







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


COMMAND(echo, ARG_VARI, ^(OFString *text) {

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

	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

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)
{
	clientpassword = p;
}
COMMAND(password, ARG_1STR)

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







<
|
<

|
<







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;

COMMAND(password, ARG_1STR, ^(OFString *p) {

	clientpassword = p;
})


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

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

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

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

COMMAND(alias, ARG_2STR)



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

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

	identifiers[name] = variable;

	return cur;
}

void
setvar(OFString *name, int i)
{

	*[identifiers[name] storage] = i;


}

int
getvar(OFString *name)
{





	return *[identifiers[name] storage];
}

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

OFString *
getalias(OFString *name)
{
	Alias *alias = identifiers[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;) {











<
<
<









|

|
|
|
<
<
|
<
|







>
|
>
>





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






>
|
>
>





>
>
>
>
>
|





|





|







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







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"




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

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

	if (alias == nil)
		[Identifier addIdentifier:[Alias aliasWithName:name
		                                        action:action


		                                     persisted:true]];

	else {
		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) {
	alias(name, action);
})

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






	return cur;
}

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

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

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

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

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

	return nil;
}
















// 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
	return strndup(word, *p - word);
}

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


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

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







|
>







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

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

	return val;
}

// tab-completion of all identifiers







|







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

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

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








|
|







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;
	[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
{
	if (!execfile([Cube.sharedInstance.userDataIRI
	        IRIByAppendingPathComponent:cfgfile]) &&
	    !execfile([Cube.sharedInstance.gameDataIRI
	        IRIByAppendingPathComponent:cfgfile]))
		conoutf(@"could not read \"%@\"", cfgfile);
}





void
writecfg()
{
	OFStream *stream;
	@try {
		OFIRI *IRI = [Cube.sharedInstance.userDataIRI







>
>
>
>







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

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

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

	[stream close];
}

COMMAND(writecfg, ARG_NONE)



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

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

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

void
loopa(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)
{
	while (execute(cond, true))
		execute(body, true);
}

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

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

void
concatword(OFString *s)

{

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

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

	if (!*a)
		return 0;

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

	return n + 1;
}

void
at(OFString *s_, OFString *pos)
{
	int n = pos.cube_intValue;
	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)
{
	return a + b;
}
COMMANDN(+, add, ARG_2EXP)

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

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

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

int
mod(int a, int b)
{
	return b ? a % b : 0;

}
COMMAND(mod, ARG_2EXP)

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

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

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

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

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

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







|
|
|
|
|

|
|
|





|
|
|
|
|

|
|
|




|
>
>











<
|
<

|

<
|
<






|

<
|
<


|

<
|
<


|







|
|
>
|
>

|

<
|
<











|

<
|
<










|

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

|
<

<
|
<

|
<

<
|
<

|
<

<
|
<

|
<

<
|
<

>
|
<
<
<
|
<

|
<

<
|
<

|
<

<
|
<

|
<

<
|
<

|
<

<
|
<
|
|
<

<
|
<

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

	[Identifier
	    enumerateIdentifiersUsingBlock:^(__kindof Identifier *identifier) {
		    if (![identifier isKindOfClass:Variable.class] ||
		        ![identifier persisted])
			    return;

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

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

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

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

	[stream close];
}

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


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

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

	while (execute(cond, true))
		execute(body, true);
})


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) {
	concat(s);
})

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


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

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

	return a + b;
})



COMMAND(*, ARG_2EXP, ^(int a, int b) {

	return a * b;
})



COMMAND(-, ARG_2EXP, ^(int a, int b) {

	return a - b;
})



COMMAND(div, ARG_2EXP, ^(int a, int b) {

	return b ? a / b : 0;
})



COMMAND(mod, ARG_2EXP, ^(int a, int b) {

	return b ? a % b : 0;
})




COMMAND(=, ARG_2EXP, ^(int a, int b) {

	return (int)(a == b);
})



COMMAND(<, ARG_2EXP, ^(int a, int b) {

	return (int)(a < 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(rnd, ARG_1EXP, ^(int a) {

	return (a > 0 ? rnd(a) : 0);
})



COMMAND(millis, ARG_1EXP, ^(int unused) {

	return lastmillis;
})

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
// console.cpp: the console buffer, its display, and command line control

#include "cube.h"

#include <ctype.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)
{
	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






>













<
|
<



|
<







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;


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
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
		    (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)
{
	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)
{
	for (KeyMapping *mapping in keyMappings) {
		if ([mapping.name caseInsensitiveCompare:key] ==
		    OFOrderedSame) {
			mapping.action = action;
			return;
		}
	}

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


void
saycommand(OFString *init) // turns input to the command line on or off
{
	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)


{

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







<
|
<







|
<

<
|
<









|
<

>
|
|















<

<
|
>
>
|
>


|
<










<
|
<







|
<







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;


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


// 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, ^(OFString *init) {
	saycommand(init);
})

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

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


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


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







<
<
<
<
<
<
<
<







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 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
// editing.cpp: most map editing commands go here, entity editing commands are
// in world.cpp

#include "cube.h"


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

bool editmode = false;

// the current selection, used by almost all editing commands





>







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
			[Monster resetAll];
		projreset();
	}
	Cube.sharedInstance.repeatsKeys = editmode;
	selset = false;
	editing = editmode;
}


COMMANDN(edittoggle, toggleedit, ARG_NONE)


void
correctsel() // ensures above invariant
{
	selset = !OUTBORD(sel.x, sel.y);
	int bsize = ssize - MINBORD;
	if (sel.xs + sel.x > bsize)







>
>
|
>







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, ^{
	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
#define EDITSELMP                                            \
	if (noteditmode() || noselection() || multiplayer()) \
		return;
#define EDITMP                              \
	if (noteditmode() || multiplayer()) \
		return;

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







<
|
<




|







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;


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
		    initWithItemSize:sizeof(struct block *)];

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

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

	if (copybuf)
		OFFreeMemory(copybuf);

	copybuf = blockcopy(&sel);
}

void
paste()
{
	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];







<
|
<









|



<
|
<






|

<
|
<
















|







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


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, ^{

	EDITSELMP;

	if (copybuf)
		OFFreeMemory(copybuf);

	copybuf = blockcopy(&sel);
})


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



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







>
|
>
>




















<
|
<

















|

<
|
<







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


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, (^{

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

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

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

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)
{
	edittype(t == 0 ? FHF : CHF);
}
COMMAND(heightfield, ARG_1INT)

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

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

void
editequalisexy(bool isfloor, const struct block *sel)
{
	int low = 127, hi = -128;
	loopselxy({
		if (s->floor < low)







|







|















<
|
<

|
<

<
|
<

|
<

<
|
<

|
<







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

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


COMMAND(heightfield, ARG_1INT, ^(int t) {

	edittype(t == 0 ? FHF : CHF);
})



COMMAND(solid, ARG_1INT, ^(int t) {

	edittype(t == 0 ? SPACE : SOLID);
})



COMMAND(corner, ARG_NONE, ^{

	edittype(CORNER);
})


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
		else
			s->ceil = hi;
		if (s->floor >= s->ceil)
			s->floor = s->ceil - 1;
	});
}

void
equalize(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)
{
	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)
{
	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)
{
	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)
{
	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)
{
	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)
{
	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)
{
	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]);
}

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)







<
|
<






|
<








<
|
<




|





<
|
<









|

<
|
<



















|

<
|
<
















|

<
|
<



















|









<
|
<






|

<
>
|
<
|

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


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

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

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

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


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

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


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;

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














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
// menus.cpp: ingame menu system (also used for scores and serverlist)

#include "cube.h"

#import "Menu.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)
{
	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];
}







>
















<
|
<








|
<







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


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


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
newmenu(OFString *name)
{
	if (menus == nil)
		menus = [[OFMutableArray alloc] init];

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

COMMAND(newmenu, ARG_1STR)



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








>
|
>
>











<
|
<






|
<







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, ^(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];
}


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


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












<







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 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
// rendercubes.cpp: sits in between worldrender.cpp and rendergl.cpp and fills
// the vertex array for different cube surfaces.

#include "cube.h"



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

void
setarraypointers()




>
>







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







<
|
<

|
<







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;


COMMAND(showmip, ARG_NONE, ^{

	showm = !showm;
})


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
// renderextras.cpp: misc gl render code and the HUD

#include "cube.h"


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

void
line(int x1, int y1, float z1, int x2, int y2, float z2)
{
	glBegin(GL_POLYGON);




>







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
		    [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)
{
	static OFString *lastsky = @"";

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

	if ([lastsky isEqual:basename])
		return;







<
|
<







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


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







|
<







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


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
// rendergl.cpp: core opengl rendering stuff

#include "cube.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




>







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
	for (int i = 0; i < 256; i++)
		for (int j = 0; j < MAXFRAMES; j++)
			mapping[i][j] = 0;
}

int curtexnum = 0;

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

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

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

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

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








<
|
<

|
<

<
|
<








|
<







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;


COMMAND(texturereset, ARG_NONE, ^{

	curtexnum = 0;
})



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:@"/"];
}))


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
// rendermd2.cpp: loader code adapted from a nehe tutorial

#include "cube.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;




>







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
		mdllookup = [[OFMutableDictionary alloc] init];

	mdllookup[name] = m;

	return m;
}

void
mapmodel(
    OFString *rad, OFString *h, OFString *zoff, OFString *snap, OFString *name)
{


	MD2 *m = loadmodel([name stringByReplacingOccurrencesOfString:@"\\"
	                                                   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];

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

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

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








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

|
|

|
|
<

<
|
<

|
<







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


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

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

	    [mapmodels addObject:m];
    })



COMMAND(mapmodelreset, ARG_NONE, ^{

	[mapmodels removeAllObjects];
})


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
// 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 "DynamicEntity.h"
#import "Entity.h"
#import "Monster.h"

#ifdef OF_BIG_ENDIAN
static const int islittleendian = 0;
#else





>







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
	for (id player in players) {
		gzput(player == [OFNull null]);
		data = [player dataBySerializing];
		gzwrite(f, data.items, data.count);
	}
}

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

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

void
loadstate(OFIRI *IRI)
{
	stop();
	if (multiplayer())
		return;







<
|
<











|
<







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


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


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
	return;
out:
	conoutf(@"aborting: savegame/demo from a different version of "
	        @"cube or cpu architecture");
	stop();
}

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

void
loadgameout()
{
	stop();
	conoutf(@"loadgame incomplete: savegame from a different version of "
	        @"this map");







<
|
<




|
<







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


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

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

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







<
|
<


















|
<







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;


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


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
			ddamage = 0;
		}
		// FIXME: add all other client state which is not send through
		// the network
	}
}

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

void
stopreset()
{
	conoutf(@"demo stopped (%d msec elapsed)", lastmillis - starttime);
	stop();
	[players removeAllObjects];







<
|
<





|
<







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


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()
{
	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
			}
			break;
		}
	}
	// if(player1->state!=CS_DEAD) showscores(false);
}

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







<
|
<





|
<
534
535
536
537
538
539
540

541

542
543
544
545
546
547

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


COMMAND(stop, ARG_NONE, ^{

	if (demoplayback)
		stopreset();
	else
		stop();
	conoutf(@"demo stopped");
})

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

1
2
3
4
5
6

7
8
9
10
11
12
13
// serverbrowser.cpp: eihrul's concurrent resolver, and server browser window
// management

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


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

static OFMutableArray<ResolverThread *> *resolverthreads;
OFMutableArray<OFString *> *resolverqueries;
OFMutableArray<ResolverResult *> *resolverresults;






>







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

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

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





void
pingservers()
{
	ENetBuffer buf;
	unsigned char ping[MAXTRANS];
	unsigned char *p;







>
>
>
>







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
		menumanual(1, i, si.full);

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

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

{

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







|
















|
|
>
|
>











|
<
<
<
<












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

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

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





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
#include "cube.h"


#import "DynamicEntity.h"

#include <SDL_mixer.h>

VARP(soundvol, 0, 255, 255);
VARP(musicvol, 0, 128, 255);
bool nosound = false;


>







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
		conoutf(@"sound init failed (SDL_mixer): %s",
		    (size_t)Mix_GetError());
		nosound = true;
	}
	Mix_AllocateChannels(MAXCHAN);
}

void
music(OFString *name)
{
	if (nosound)
		return;

	stopsound();

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

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







<
|
<



















|
<




<
|
<




















|
<







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


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


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


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

	if (loc)
		newsoundloc(chan, loc);

	updatechanvol(chan, loc);
}

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







<
|
<

|
<
207
208
209
210
211
212
213

214

215
216


	if (loc)
		newsoundloc(chan, loc);

	updatechanvol(chan, loc);
}


COMMAND(sound, ARG_1INT, ^(int n) {

	playsound(n, NULL);
})

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

1
2
3
4

5
6
7
8
9
10
11
// weapon.cpp: all shooting and effects code

#include "cube.h"


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

static const int MONSTERDAMAGEFACTOR = 4;
#define SGRAYS 20




>







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

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

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







<
|
<



|
<







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


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
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
// world.cpp: core map management stuff

#include "cube.h"


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

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

struct sqr *world = NULL;




>







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

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

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

COMMAND(trigger, ARG_2INT)



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








>
|
>
>







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, ^(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
			bdist = dist;
		}
	}];

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

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







<
|
<

















|

<
|
<











|







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


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, ^{

	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

	if (type == LIGHT)
		calclight();

	return e;
}

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







<
|
<











|
<










<
|
<


















|
<







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


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


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


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


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

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

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

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

COMMAND(mapenlarge, ARG_NONE)
COMMAND(newmap, ARG_1INT)
COMMANDN(recalc, calclight, ARG_NONE)
COMMAND(delent, ARG_NONE)
COMMAND(entproperty, ARG_2INT)








<
|
<

|

<
|
<

|

|
<
|
<
<
>
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, ^{

	empty_world(-1, false);
})


COMMAND(newmap, ARG_1INT, ^(int i) {

	empty_world(i, false);
})

COMMAND(recalc, ARG_NONE, ^{

	calclight();


})

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

1
2
3
4

5
6
7
8
9
10
11
// worldio.cpp: loading & saving of maps and savegames

#include "cube.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;




>







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
		t = s;
	}
	spurge;
	gzclose(f);
	conoutf(@"wrote map file %@", cgzname);
	settagareas();
}


COMMANDN(savemap, save_world, ARG_1STR)


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







>
>
|
>







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) {
	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
// worldocull.cpp: occlusion map and occlusion test

#include "cube.h"


#import "DynamicEntity.h"

#define NUMRAYS 512

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

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




>








<
|
<

|
<







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;


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
computeraytable(float vx, float vy)
{