Cube  Check-in [75e920ae30]

Overview
Comment:Switch from clang-format to manual formatting

clang-format does too many weird things.

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#import "Identifier.h"

OF_ASSUME_NONNULL_BEGIN

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

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

OF_ASSUME_NONNULL_END








|
|
|
|
|
|
|




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#import "Identifier.h"

OF_ASSUME_NONNULL_BEGIN

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

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

OF_ASSUME_NONNULL_END

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#import "Alias.h"

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

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

	_action = [action copy];
	_persisted = persisted;

	return self;
}
@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
#import "Alias.h"

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

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

	_action = [action copy];
	_persisted = persisted;

	return self;
}
@end

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#import "Identifier.h"

OF_ASSUME_NONNULL_BEGIN

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

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

+ (instancetype)commandWithName:(OFString *)name
                 argumentsTypes:(int)argumentsTypes
                          block:(id)block;
- (instancetype)initWithName:(OFString *)name OF_UNAVAILABLE;
- (instancetype)initWithName:(OFString *)name
              argumentsTypes:(int)argumentsTypes
                       block:(id)block OF_DESIGNATED_INITIALIZER;
- (int)callWithArguments:(OFArray<OFString *> *)arguments isDown:(bool)isDown;
@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
30
#import "Identifier.h"

OF_ASSUME_NONNULL_BEGIN

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

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

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

OF_ASSUME_NONNULL_END

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

9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
	OFMutableArray<OFString *> *copy;

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

	copy = [arguments mutableCopy];
	while (copy.count < count)
		[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:







|










|
|
|

|
|
|


|
|
|

|







|






|






|
|






|
|
|






|
|
|
|







9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
	OFMutableArray<OFString *> *copy;

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

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

	[copy 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:
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
			    arguments[1], arguments[2]);
		}
		break;
	case ARG_VARI:
		if (isDown)
			// limit, remove
			((void (^)(OFString *))_block)([[arguments
			    objectsInRange:OFMakeRange(1, arguments.count - 1)]
			    componentsJoinedByString:@" "]);
		break;
	}

	return 0;
}
@end







|
|






150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
			    arguments[1], arguments[2]);
		}
		break;
	case ARG_VARI:
		if (isDown)
			// limit, remove
			((void (^)(OFString *))_block)([[arguments
			    objectsInRange: OFMakeRange(1, arguments.count - 1)]
			    componentsJoinedByString: @" "]);
		break;
	}

	return 0;
}
@end

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

1
2
3
4
5
6
7
8
9
10
#import <ObjFW/ObjFW.h>

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

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







|
|

1
2
3
4
5
6
7
8
9
10
#import <ObjFW/ObjFW.h>

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import "ConsoleLine.h"

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

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

	_text = [text copy];
	_outtime = outtime;

	return self;



|

|


|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import "ConsoleLine.h"

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

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

	_text = [text copy];
	_outtime = outtime;

	return self;

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

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

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

- (void)applicationDidFinishLaunching:(OFNotification *)notification
{
	@autoreleasepool {
		bool dedicated, windowed;
		int par = 0, uprate = 0, maxcl = 4;
		OFString *__autoreleasing sdesc, *__autoreleasing ip;
		OFString *__autoreleasing master, *__autoreleasing passwd;








|







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

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

- (void)applicationDidFinishLaunching: (OFNotification *)notification
{
	@autoreleasepool {
		bool dedicated, windowed;
		int par = 0, uprate = 0, maxcl = 4;
		OFString *__autoreleasing sdesc, *__autoreleasing ip;
		OFString *__autoreleasing master, *__autoreleasing passwd;

44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
			{ 'i', @"ip", 1, NULL, &ip },
			{ 'm', @"master", 1, NULL, &master },
			{ 'p', @"password", 1, NULL, &passwd },
			{ 'c', @"max-clients", 1, NULL, NULL },
			{ '\0', nil, 0, NULL, NULL }
		};
		OFOptionsParser *optionsParser =
		    [OFOptionsParser parserWithOptions:options];
		OFUnichar option;
		while ((option = [optionsParser nextOption]) != '\0') {
			switch (option) {
			case 'w':
				_width = optionsParser.argument.intValue;
				break;
			case 'h':
				_height = optionsParser.argument.intValue;
				break;
			case 'u':
				uprate = optionsParser.argument.intValue;
				break;
			case 'c':
				maxcl = optionsParser.argument.intValue;
				break;
			case ':':
			case '=':
			case '?':
				conoutf(@"unknown commandline option");
				[OFApplication terminateWithStatus:1];
			}
		}

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

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

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

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

		initEntities();
		initPlayers();








|



















|
















|
|

|
|







44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
			{ 'i', @"ip", 1, NULL, &ip },
			{ 'm', @"master", 1, NULL, &master },
			{ 'p', @"password", 1, NULL, &passwd },
			{ 'c', @"max-clients", 1, NULL, NULL },
			{ '\0', nil, 0, NULL, NULL }
		};
		OFOptionsParser *optionsParser =
		    [OFOptionsParser parserWithOptions: options];
		OFUnichar option;
		while ((option = [optionsParser nextOption]) != '\0') {
			switch (option) {
			case 'w':
				_width = optionsParser.argument.intValue;
				break;
			case 'h':
				_height = optionsParser.argument.intValue;
				break;
			case 'u':
				uprate = optionsParser.argument.intValue;
				break;
			case 'c':
				maxcl = optionsParser.argument.intValue;
				break;
			case ':':
			case '=':
			case '?':
				conoutf(@"unknown commandline option");
				[OFApplication terminateWithStatus: 1];
			}
		}

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

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

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

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

		initEntities();
		initPlayers();

141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
		SDL_ShowCursor(0);

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

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

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

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

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

		log(@"mainloop");
	}

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

			Player *player1 = Player.player1;

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







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

|













|

|














|







141
142
143
144
145
146
147
148


149

150
151


152
153


154
155


156
157


158
159

160


161
162


163
164


165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
		SDL_ShowCursor(0);

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

		log(@"basetex");
		int xs, ys;
		if (!installtex(2, [_gameDataIRI IRIByAppendingPathComponent:


		    @"data/newchars.png"], &xs, &ys, false) ||

		    !installtex(3, [_gameDataIRI IRIByAppendingPathComponent:
		    @"data/martin/base.png"], &xs, &ys, false) ||


		    !installtex(6, [_gameDataIRI IRIByAppendingPathComponent:
		    @"data/martin/ball1.png"], &xs, &ys, false) ||


		    !installtex(7, [_gameDataIRI IRIByAppendingPathComponent:
		    @"data/martin/smoke.png"], &xs, &ys, false) ||


		    !installtex(8, [_gameDataIRI IRIByAppendingPathComponent:
		    @"data/martin/ball2.png"], &xs, &ys, false) ||


		    !installtex(9, [_gameDataIRI IRIByAppendingPathComponent:
		    @"data/martin/ball3.png"], &xs, &ys, false) ||

		    !installtex(4, [_gameDataIRI IRIByAppendingPathComponent:


		    @"data/explosion.jpg"], &xs, &ys, false) ||
		    !installtex(5, [_gameDataIRI IRIByAppendingPathComponent:


		    @"data/items.png"], &xs, &ys, false) ||
		    !installtex(1, [_gameDataIRI IRIByAppendingPathComponent:


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

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

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

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

		log(@"mainloop");
	}

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

			Player *player1 = Player.player1;

			int millis = SDL_GetTicks() * gamespeed / 100;
			if (millis - lastmillis > 200)
				lastmillis = millis - 200;
			else if (millis - lastmillis < 1)
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
					break;
				}
			}
		}
	}
}

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

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

- (void)screenshot
{
	SDL_Surface *image;
	SDL_Surface *temp;







|











|





|







273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
					break;
				}
			}
		}
	}
}

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

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

- (void)screenshot
{
	SDL_Surface *image;
	SDL_Surface *temp;
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
				memcpy(dest,
				    (char *)image->pixels +
				        3 * _width * (_height - 1 - idx),
				    3 * _width);
				endianswap(dest, 3, _width);
			}

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

		SDL_FreeSurface(image);
	}
}

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

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

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







|
|
<

|
|










|









|
|


|

|
|



|



|


317
318
319
320
321
322
323
324
325

326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
				memcpy(dest,
				    (char *)image->pixels +
				        3 * _width * (_height - 1 - idx),
				    3 * _width);
				endianswap(dest, 3, _width);
			}

			OFString *path = [OFString stringWithFormat:
			    @"screenshots/screenshot_%d.bmp", lastmillis];

			SDL_SaveBMP(temp,
			    [_userDataIRI IRIByAppendingPathComponent: path]
			    .fileSystemRepresentation.UTF8String);
			SDL_FreeSurface(temp);
		}

		SDL_FreeSurface(image);
	}
}

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

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

	[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/DynamicEntity.h from [0ee26a40fb] to [0008508591].

29
30
31
32
33
34
35
36
37
38
39
40
@property (direct, readonly, nonatomic) int *ammo;
@property (direct, nonatomic) bool attacking;
// used by physics to signal ai
@property (direct, nonatomic) bool blocked, moving;
@property (direct, copy, nonatomic) OFString *name;

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







|




29
30
31
32
33
34
35
36
37
38
39
40
@property (direct, readonly, nonatomic) int *ammo;
@property (direct, nonatomic) bool attacking;
// used by physics to signal ai
@property (direct, nonatomic) bool blocked, moving;
@property (direct, copy, nonatomic) OFString *name;

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

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

163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
		.moving = _moving };

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

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

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

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

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

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

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

	memcpy(&d, data.items, data.count);







|







|









|


|







163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
		.moving = _moving };

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

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

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

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

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

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

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

	memcpy(&d, data.items, data.count);
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258

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

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

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

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

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







|

|






|







235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258

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

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#import <ObjFW/ObjFW.h>

OF_ASSUME_NONNULL_BEGIN

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

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

OF_ASSUME_NONNULL_END










|



1
2
3
4
5
6
7
8
9
10
11
12
13
14
#import <ObjFW/ObjFW.h>

OF_ASSUME_NONNULL_BEGIN

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

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

OF_ASSUME_NONNULL_END

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

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

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

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

	_name = [name copy];

	return self;
}







|







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

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

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

	_name = [name copy];

	return self;
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import <ObjFW/ObjFW.h>

OF_ASSUME_NONNULL_BEGIN

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

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

OF_ASSUME_NONNULL_END










|

|



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import <ObjFW/ObjFW.h>

OF_ASSUME_NONNULL_BEGIN

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

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

OF_ASSUME_NONNULL_END

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import "KeyMapping.h"

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

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

	_code = code;
	_name = [name copy];

	return self;



|

|


|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import "KeyMapping.h"

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

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

	_code = code;
	_name = [name copy];

	return self;

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

8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@interface MD2: OFObject
@property (nonatomic) MapModelInfo *mmi;
@property (copy, nonatomic) OFString *loadname;
@property (nonatomic) int mdlnum;
@property (nonatomic) bool loaded;

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

OF_ASSUME_NONNULL_END







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



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@interface MD2: OFObject
@property (nonatomic) MapModelInfo *mmi;
@property (copy, nonatomic) OFString *loadname;
@property (nonatomic) int mdlnum;
@property (nonatomic) bool loaded;

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

OF_ASSUME_NONNULL_END

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

59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
	if (_mverts != NULL)
		for (size_t i = 0; i < _numFrames; i++)
			OFFreeMemory(_mverts[i]);

	OFFreeMemory(_mverts);
}

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

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

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

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

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

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

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

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

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

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

	[stream close];

	_mverts = OFAllocZeroedMemory(_numFrames, sizeof(OFVector3D *));

	return true;
}

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

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

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

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

	[light cube_setAsGLColor];







|



|
|
|




|



|











|
|
|










|
|
|















|

















|
|
|
|
|
|
|
|
|
|



|







59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
	if (_mverts != NULL)
		for (size_t i = 0; i < _numFrames; i++)
			OFFreeMemory(_mverts[i]);

	OFFreeMemory(_mverts);
}

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

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

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

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

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

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

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

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

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

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

	[stream close];

	_mverts = OFAllocZeroedMemory(_numFrames, sizeof(OFVector3D *));

	return true;
}

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

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

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

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

	[light cube_setAsGLColor];

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#import <ObjFW/ObjFW.h>

OF_ASSUME_NONNULL_BEGIN

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

+ (instancetype)infoWithRad:(int)rad
                          h:(int)h
                       zoff:(int)zoff
                       snap:(int)snap
                       name:(OFString *)name;
- (instancetype)init OF_UNAVAILABLE;
- (instancetype)initWithRad:(int)rad
                          h:(int)h
                       zoff:(int)zoff
                       snap:(int)snap
                       name:(OFString *)name;
@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
#import <ObjFW/ObjFW.h>

OF_ASSUME_NONNULL_BEGIN

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

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

OF_ASSUME_NONNULL_END

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

1
2
3
4
5
6
7
8
9
10




11
12
13
14
15
16
17
18
19
20
21
22
23
24
#import "MapModelInfo.h"

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




}

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

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



|
|
|
|
|

|
>
>
>
>


|
|
|
|
|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#import "MapModelInfo.h"

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#import <ObjFW/ObjFW.h>

OF_ASSUME_NONNULL_BEGIN

@class MenuItem;

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

+ (instancetype)menuWithName:(OFString *)name;
- (instancetype)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
16
17
18
19
#import <ObjFW/ObjFW.h>

OF_ASSUME_NONNULL_BEGIN

@class MenuItem;

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

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

OF_ASSUME_NONNULL_END

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import "Menu.h"

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

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

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

	return self;



|

|


|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import "Menu.h"

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

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

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

	return self;

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

1
2
3
4
5
6
7
8
9
10
#import <ObjFW/ObjFW.h>

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

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






|

|

1
2
3
4
5
6
7
8
9
10
#import <ObjFW/ObjFW.h>

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#import "MenuItem.h"

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

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

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

	return self;
}

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

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

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



|

|


|









|



|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#import "MenuItem.h"

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

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

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

	return self;
}

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

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

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

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

21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// called after map start of when toggling edit mode to reset/spawn all
// monsters to initial state
+ (void)restoreAll;
+ (void)resetAll;
+ (void)thinkAll;
+ (void)renderAll;
// TODO: Move this somewhere else
+ (void)endSinglePlayerWithAllKilled:(bool)allKilled;
+ (instancetype)monsterWithType:(int)type
                            yaw:(int)yaw
                          state:(int)state
                        trigger:(int)trigger
                           move:(int)move;
- (instancetype)initWithType:(int)type
                         yaw:(int)yaw
                       state:(int)state
                     trigger:(int)trigger
                        move:(int)move;
- (void)incurDamage:(int)damage fromEntity:(__kindof DynamicEntity *)d;
@end







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

21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// called after map start of when toggling edit mode to reset/spawn all
// monsters to initial state
+ (void)restoreAll;
+ (void)resetAll;
+ (void)thinkAll;
+ (void)renderAll;
// TODO: Move this somewhere else
+ (void)endSinglePlayerWithAllKilled: (bool)allKilled;
+ (instancetype)monsterWithType: (int)type
                            yaw: (int)yaw
                          state: (int)state
                        trigger: (int)trigger
                           move: (int)move;
- (instancetype)initWithType: (int)type
                         yaw: (int)yaw
                       state: (int)state
                     trigger: (int)trigger
                        move: (int)move;
- (void)incurDamage: (int)damage fromEntity: (__kindof DynamicEntity *)d;
@end

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

18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
}

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

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

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

// for savegames
+ (void)restoreAll
{







|
|
|
|
|

|
|
|
|
|







18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
}

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

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

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

// for savegames
+ (void)restoreAll
{
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
	    @"a hellpig", @"monster/hellpig" },
	{ GUN_ICEBALL, 12, 250, 1, 0, 10, 400, 6, 18, 18, S_PAINH, S_DEATHH,
	    @"a knight", @"monster/knight" },
	{ GUN_SLIMEBALL, 15, 100, 1, 0, 200, 400, 2, 13, 10, S_PAIND, S_DEATHD,
	    @"a goblin", @"monster/goblin" },
};

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

	if (type >= NUMMONSTERTYPES) {
		conoutf(@"warning: unknown monster in spawn: %d", type);
		type = 0;
	}







|
|
|
|
|







71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
	    @"a hellpig", @"monster/hellpig" },
	{ GUN_ICEBALL, 12, 250, 1, 0, 10, 400, 6, 18, 18, S_PAINH, S_DEATHH,
	    @"a knight", @"monster/knight" },
	{ GUN_SLIMEBALL, 15, 100, 1, 0, 200, 400, 2, 13, 10, S_PAIND, S_DEATHD,
	    @"a goblin", @"monster/goblin" },
};

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

	if (type >= NUMMONSTERTYPES) {
		conoutf(@"warning: unknown monster in spawn: %d", type);
		type = 0;
	}
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
	for (int i = 0;; i++) {
		if ((n -= monstertypes[i].freq) < 0) {
			type = i;
			break;
		}
	}

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

+ (void)resetAll
{
	[monsters removeAllObjects];

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

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

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

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

// height-correct line of sight for monster shooting/seeing







|
|
|
|
|




















|
|
|
|
|

|







142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
	for (int i = 0;; i++) {
		if ((n -= monstertypes[i].freq) < 0) {
			type = i;
			break;
		}
	}

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

+ (void)resetAll
{
	[monsters removeAllObjects];

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

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

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

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

// height-correct line of sight for monster shooting/seeing
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
// where they execute a particular behaviour until the trigger time is hit, and
// then they reevaluate their situation based on the current state, the
// environment etc., and transition to the next state. Transition timeframes are
// parametrized by difficulty level (skill), faster transitions means quicker
// decision making means tougher AI.

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

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

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







|







|














|







233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
// where they execute a particular behaviour until the trigger time is hit, and
// then they reevaluate their situation based on the current state, the
// environment etc., and transition to the next state. Transition timeframes are
// parametrized by difficulty level (skill), faster transitions means quicker
// decision making means tougher AI.

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

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

// main AI thinking routine, called every frame for every monster
- (void)performAction
{
	if (self.enemy.state == CS_DEAD) {
		self.enemy = Player.player1;
		self.anger = 0;
	}
	[self normalizeWithAngle: self.targetYaw];
	// slowly turn monster towards his target
	if (self.targetYaw > self.yaw) {
		self.yaw += curtime * 0.5f;
		if (self.targetYaw < self.yaw)
			self.yaw = self.targetYaw;
	} else {
		self.yaw -= curtime * 0.5f;
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308



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



325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
		if (!rnd(20000 / monstertypes[self.monsterType].speed))
			self.jumpNext = true;
		// search for a way around (common)
		else if (self.trigger < lastmillis &&
		    (self.monsterState != M_HOME || !rnd(5))) {
			// patented "random walk" AI pathfinding (tm) ;)
			self.targetYaw += 180 + rnd(180);
			[self transitionWithState:M_SEARCH
			                   moving:1
			                        n:400
			                        r:1000];
		}
	}

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

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



		break;

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



			OFVector3D loc = self.origin;
			playsound(S_GRUNT1 + rnd(2), &loc);
		}
		break;
	}

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

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

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

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

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

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

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

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

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

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

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

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

		OFVector3D v =







|
|
|
|













|
>
>
>








|






|
>
>
>













|
|
|
|












|
|
|
|









|
|
|
|




|
|
|
|









|


|




















|
|
|
|

















|



















|


|







284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
		if (!rnd(20000 / monstertypes[self.monsterType].speed))
			self.jumpNext = true;
		// search for a way around (common)
		else if (self.trigger < lastmillis &&
		    (self.monsterState != M_HOME || !rnd(5))) {
			// patented "random walk" AI pathfinding (tm) ;)
			self.targetYaw += 180 + rnd(180);
			[self transitionWithState: M_SEARCH
			                   moving: 1
			                        n: 400
			                        r: 1000];
		}
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

		OFVector3D v =

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

1
2
3
4
5
6
7
8
9
10
11
12
#include "cube.h"

#import "OFColor+Cube.h"

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








|



1
2
3
4
5
6
7
8
9
10
11
12
#include "cube.h"

#import "OFColor+Cube.h"

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

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

1
2
3
4
5
6
7
#import <ObjFW/ObjFW.h>

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

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





|

1
2
3
4
5
6
7
#import <ObjFW/ObjFW.h>

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

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

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

12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
		return 0;
	} @catch (OFOutOfRangeException *e) {
		conoutf(@"invalid value: %@", self);
		return 0;
	}
}

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







|


|







12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
		return 0;
	} @catch (OFOutOfRangeException *e) {
		conoutf(@"invalid value: %@", self);
		return 0;
	}
}

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

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

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

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

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

+ (Player *)player1
{
	return player1;







|







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

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

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

+ (Player *)player1
{
	return player1;

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

1
2
3
4
5
6
7
8
9
10

11
12

13
#import <ObjFW/ObjFW.h>

#import "cube.h"

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

+ (instancetype)resultWithQuery:(OFString *)query address:(ENetAddress)address;

- (instancetype)init OF_UNAVAILABLE;
- (instancetype)initWithQuery:(OFString *)query address:(ENetAddress)address;

@end









|
>

|
>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#import <ObjFW/ObjFW.h>

#import "cube.h"

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import "ResolverResult.h"

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

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

	_query = query;
	_address = address;

	return self;



|

|


|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import "ResolverResult.h"

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

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

	_query = query;
	_address = address;

	return self;

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

21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
			_starttime = lastmillis;
		}

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

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

			_query = NULL;
			_starttime = 0;
		}
	}

	return nil;







|
|
|







21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
			_starttime = lastmillis;
		}

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

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

			_query = NULL;
			_starttime = 0;
		}
	}

	return nil;

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#import <ObjFW/ObjFW.h>

#include <enet/enet.h>

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

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













|

|

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#import <ObjFW/ObjFW.h>

#include <enet/enet.h>

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#import "ServerInfo.h"

#include "cube.h"

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

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

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

	return self;
}

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

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

	return [_name compare:otherObject.name];
}
@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
30
31
32
33
34
35
36
37
38
39
40
41
42
#import "ServerInfo.h"

#include "cube.h"

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

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

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

	return self;
}

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55




56
57
58
59
60
61

62
63
64
65
66
67
68
69
70
71
72


73
74




75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#import "Identifier.h"

OF_ASSUME_NONNULL_BEGIN

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




#define VARFP(name, min_, cur, max_, body)                          \
	static void var_##name();                                   \
	static int name = cur;                                      \
                                                                    \
	OF_CONSTRUCTOR()                                            \
	{                                                           \

		enqueueInit(^{                                      \
			Variable *variable =                        \
			    [Variable variableWithName:@ #name      \
			                           min:min_         \
			                           max:max_         \
			                       storage:&name        \
			                      function:var_##name   \
			                     persisted:true];       \
			Identifier.identifiers[@ #name] = variable; \
		});                                                 \
	}                                                           \


                                                                    \
	static void var_##name() { body; }





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

+ (instancetype)variableWithName:(OFString *)name
                             min:(int)min
                             max:(int)max
                         storage:(int *)storage
                        function:(void (*_Nullable)())function
                       persisted:(bool)persisted OF_DIRECT;
- (instancetype)initWithName:(OFString *)name OF_UNAVAILABLE;
- (instancetype)initWithName:(OFString *)name
                         min:(int)min
                         max:(int)max
                     storage:(int *)storage
                    function:(void (*_Nullable)())function
                   persisted:(bool)persisted OF_DESIGNATED_INITIALIZER
    OF_DIRECT;
- (void)printValue OF_DIRECT;
- (void)setValue:(int)value OF_DIRECT;
@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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

65
66
67
68
69
70
71
72
73
74


75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#import "Identifier.h"

OF_ASSUME_NONNULL_BEGIN

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

	{								\
		enqueueInit(^ {						\
			Variable *variable =				\
			    [Variable variableWithName: @#name		\
			                           min: min_		\
			                           max: max_		\
			                       storage: &name		\
			                      function: var_##name	\
			                     persisted: true];		\
			Identifier.identifiers[@#name] = variable;	\


		});							\
	}								\
									\
	static void							\
	var_##name(void)						\
	{								\
		body;							\
	}

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

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

OF_ASSUME_NONNULL_END

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#import "Variable.h"

#include "cube.h"

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

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

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

	return self;
}

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

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

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





|
|
|
|
|
|

|
|
|
|
|
|


|
|
|
|
|
|

|















|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#import "Variable.h"

#include "cube.h"

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

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

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

	return self;
}

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

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

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

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

47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
			}
		}
		if (mz < -1000)
			return;
		// mdl = (((int)d>>6)&1)+1;
		// mz = d.o.z-d.eyeHeight+0.2f;
		// scale = 1.2f;
	} else if (d.state == CS_EDITING) {
		n = 16;
	} else if (d.state == CS_LAGGED) {
		n = 17;
	} else if ([d isKindOfClass:Monster.class] &&
	    ((Monster *)d).monsterState == M_ATTACKING) {
		n = 8;
	} else if ([d isKindOfClass:Monster.class] &&
	    ((Monster *)d).monsterState == M_PAIN) {
		n = 10;
	} else if ((!d.move && !d.strafe) || !d.moving) {
		n = 12;
	} else if (!d.onFloor && d.timeInAir > 100) {
		n = 18;
	} else {
		n = 14;
		speed = 1200 / d.maxSpeed * scale;
		if (hellpig)
			speed = 300 / d.maxSpeed;
	}
	if (hellpig) {
		n++;
		scale *= 32;
		mz -= 1.9f;
	}
	rendermodel(mdlname, frame[n], range[n], 0, 1.5f,
	    OFMakeVector3D(d.origin.x, mz, d.origin.y), d.yaw + 90, d.pitch / 2,
	    team, scale, speed, 0, basetime);
}

extern int democlientnum;

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

// creation of scoreboard pseudo-menu

bool scoreson = false;

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

static OFMutableArray<OFString *> *scoreLines;

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

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

	[scoreLines addObject:line];

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

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

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

	if (teamsUsed == maxTeams)
		return;







|

|

|
|

|
|

|

|

|




















|
|
|
|
|
<
|
|


















|
|
|






|













|







47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93

94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
			}
		}
		if (mz < -1000)
			return;
		// mdl = (((int)d>>6)&1)+1;
		// mz = d.o.z-d.eyeHeight+0.2f;
		// scale = 1.2f;
	} else if (d.state == CS_EDITING)
		n = 16;
	else if (d.state == CS_LAGGED)
		n = 17;
	else if ([d isKindOfClass: Monster.class] &&
	    ((Monster *)d).monsterState == M_ATTACKING)
		n = 8;
	else if ([d isKindOfClass: Monster.class] &&
	    ((Monster *)d).monsterState == M_PAIN)
		n = 10;
	else if ((!d.move && !d.strafe) || !d.moving)
		n = 12;
	else if (!d.onFloor && d.timeInAir > 100)
		n = 18;
	else {
		n = 14;
		speed = 1200 / d.maxSpeed * scale;
		if (hellpig)
			speed = 300 / d.maxSpeed;
	}
	if (hellpig) {
		n++;
		scale *= 32;
		mz -= 1.9f;
	}
	rendermodel(mdlname, frame[n], range[n], 0, 1.5f,
	    OFMakeVector3D(d.origin.x, mz, d.origin.y), d.yaw + 90, d.pitch / 2,
	    team, scale, speed, 0, basetime);
}

extern int democlientnum;

void
renderclients()
{
	[players enumerateObjectsUsingBlock: ^ (Player *player, size_t i,
	    bool *stop) {
		if ([player isKindOfClass: Player.class] &&
		    (!demoplayback || i != democlientnum))
			renderclient(player, isteam(Player.player1.team,

			    [player team]), @"monster/ogro", false, 1.0f);
	}];
}

// creation of scoreboard pseudo-menu

bool scoreson = false;

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

static OFMutableArray<OFString *> *scoreLines;

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

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

	[scoreLines addObject: line];

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

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

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

	if (teamsUsed == maxTeams)
		return;
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
{
	if (!scoreson)
		return;
	[scoreLines removeAllObjects];
	if (!demoplayback)
		renderscore(Player.player1);
	for (Player *player in players)
		if ([player isKindOfClass:Player.class])
			renderscore(player);
	sortmenu();
	if (m_teammode) {
		teamsUsed = 0;
		for (Player *player in players)
			if ([player isKindOfClass:Player.class])
				addteamscore(player);
		if (!demoplayback)
			addteamscore(Player.player1);
		OFMutableString *teamScores = [OFMutableString string];
		for (size_t j = 0; j < teamsUsed; j++)
			[teamScores appendFormat:@"[ %@: %d ]", teamName[j],
			    teamScore[j]];
		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;







|





|





|
|







|







152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
{
	if (!scoreson)
		return;
	[scoreLines removeAllObjects];
	if (!demoplayback)
		renderscore(Player.player1);
	for (Player *player in players)
		if ([player isKindOfClass: Player.class])
			renderscore(player);
	sortmenu();
	if (m_teammode) {
		teamsUsed = 0;
		for (Player *player in players)
			if ([player isKindOfClass: Player.class])
				addteamscore(player);
		if (!demoplayback)
			addteamscore(Player.player1);
		OFMutableString *teamScores = [OFMutableString string];
		for (size_t j = 0; j < teamsUsed; j++)
			[teamScores appendFormat:
			    @"[ %@: %d ]", teamName[j], teamScore[j]];
		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;
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
	}
	memcpy(p, mapdata.items, mapdata.count);
	p += mapdata.count;
	*(unsigned short *)start = ENET_HOST_TO_NET_16(p - start);
	enet_packet_resize(packet, p - start);
	sendpackettoserv(packet);
	conoutf(@"sending map %@ to server...", mapname);
	OFString *msg =
	    [OFString 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...");
})







|
<
|
<



|










199
200
201
202
203
204
205
206

207

208
209
210
211
212
213
214
215
216
217
218
219
220
221
	}
	memcpy(p, mapdata.items, mapdata.count);
	p += mapdata.count;
	*(unsigned short *)start = ENET_HOST_TO_NET_16(p - start);
	enet_packet_resize(packet, p - start);
	sendpackettoserv(packet);
	conoutf(@"sending map %@ to server...", mapname);
	OFString *msg = [OFString 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 [591f2f2799] to [0b8b0d202d].

9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#import "OFString+Cube.h"
#import "Player.h"
#import "Variable.h"

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

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

bool intermission = false;

OFMutableArray *players; // other clients








|







9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#import "OFString+Cube.h"
#import "Player.h"
#import "Variable.h"

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

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

bool intermission = false;

OFMutableArray *players; // other clients

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

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

int arenarespawnwait = 0;







|







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

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

int arenarespawnwait = 0;
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
}

extern int democlientnum;

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

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

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

void
respawn()
{
	if (Player.player1.state == CS_DEAD) {
		Player.player1.attacking = false;







|
|
|
|

|
|
|
|
|

|
|
|
|
|







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

extern int democlientnum;

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

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

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

void
respawn()
{
	if (Player.player1.state == CS_DEAD) {
		Player.player1.attacking = false;
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
		} // 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
{







|







136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
		} // if we die in SP we try the same map again
		respawnself();
	}
}

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

void
updateworld(int millis) // main game update loop
{
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
	entinmap(d);
	[d resetToSpawnState];
	d.state = CS_ALIVE;
}

// movement input code

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

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 ((Player.player1.attacking = on))
		respawn();
})

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

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

void
fixplayer1range()
{
	const float MAXPITCH = 90.0f;







|
|
|
|
|
|







|








|




|







233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
	entinmap(d);
	[d resetToSpawnState];
	d.state = CS_ALIVE;
}

// movement input code

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

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 ((Player.player1.attacking = on))
		respawn();
})

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

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

void
fixplayer1range()
{
	const float MAXPITCH = 90.0f;
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
{
	if (cn < 0 || cn >= MAXCLIENTS) {
		neterr(@"clientnum");
		return nil;
	}

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

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

	return player;
}

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

void
initclient()
{
	clientmap = @"";







|
















|







372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
{
	if (cn < 0 || cn >= MAXCLIENTS) {
		neterr(@"clientnum");
		return nil;
	}

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

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

	return player;
}

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

void
initclient()
{
	clientmap = @"";
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
	sleepwait = 0;
	[Monster resetAll];
	projreset();
	spawncycle = -1;
	spawnplayer(Player.player1);
	Player.player1.frags = 0;
	for (Player *player in players)
		if ([player isKindOfClass:Player.class])
			player.frags = 0;
	resetspawns();
	clientmap = name;
	if (editmode)
		toggleedit();
	setvar(@"gamespeed", 100);
	setvar(@"fog", 180);
	setvar(@"fogcolour", 0x8099B3);
	showscores(false);
	intermission = false;
	Cube.sharedInstance.framesInMap = 0;
	conoutf(@"game mode is %@", modestr(gamemode));
}

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







|














|


414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
	sleepwait = 0;
	[Monster resetAll];
	projreset();
	spawncycle = -1;
	spawnplayer(Player.player1);
	Player.player1.frags = 0;
	for (Player *player in players)
		if ([player isKindOfClass: Player.class])
			player.frags = 0;
	resetspawns();
	clientmap = name;
	if (editmode)
		toggleedit();
	setvar(@"gamespeed", 100);
	setvar(@"fog", 180);
	setvar(@"fogcolour", 0x8099B3);
	showscores(false);
	intermission = false;
	Cube.sharedInstance.framesInMap = 0;
	conoutf(@"game mode is %@", modestr(gamemode));
}

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

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

64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106

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

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

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

	Player.player1.team = name;
}

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

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

void
connects(OFString *servername)
{
	disconnect(true, false); // reset state
	addserver(servername);







|




|









|




|






|
|







64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106

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

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

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

	Player.player1.team = name;
}

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

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

void
connects(OFString *servername)
{
	disconnect(true, false); // reset state
	addserver(servername);
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
void
toserver(OFString *text)
{
	conoutf(@"%@:\f %@", Player.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, ...)
{
	if (demoplayback)
		return;
	if (num != msgsizelookup(type))
		fatal(@"inconsistant msg size for %d (%d != %d)", type, num,
		    msgsizelookup(type));
	if (messages.count == 100) {
		conoutf(@"command flood protection (type %d)", type);
		return;
	}

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

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

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

	[messages addObject:msg];
}

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

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

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

bool
netmapstart()
{
	senditemstoserver = true;







|


|


|


|




















|
|
|
|
|





|







|















|







181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
void
toserver(OFString *text)
{
	conoutf(@"%@:\f %@", Player.player1.name, text);
	ctext = text;
}

COMMAND(echo, ARG_VARI, ^ (OFString *text) {
	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, ...)
{
	if (demoplayback)
		return;
	if (num != msgsizelookup(type))
		fatal(@"inconsistant msg size for %d (%d != %d)", type, num,
		    msgsizelookup(type));
	if (messages.count == 100) {
		conoutf(@"command flood protection (type %d)", type);
		return;
	}

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

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

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

	[messages addObject: msg];
}

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

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

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

bool
netmapstart()
{
	senditemstoserver = true;
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
		putint(&p, (int)(d.pitch * DAF));
		putint(&p, (int)(d.roll * DAF));
		// quantize to 1/100, almost always 1 byte
		putint(&p, (int)(d.velocity.x * DVF));
		putint(&p, (int)(d.velocity.y * DVF));
		putint(&p, (int)(d.velocity.z * DVF));
		// pack rest in 1 byte: strafe:2, move:2, onFloor:1, state:3
		putint(&p,
		    (d.strafe & 3) | ((d.move & 3) << 2) |
		        (((int)d.onFloor) << 4) |
		        ((editmode ? CS_EDITING : d.state) << 5));

		if (senditemstoserver) {
			packet->flags = ENET_PACKET_FLAG_RELIABLE;
			putint(&p, SV_ITEMLIST);
			if (!m_noitems)
				putitems(&p);
			putint(&p, -1);







<
|
|
|







313
314
315
316
317
318
319

320
321
322
323
324
325
326
327
328
329
		putint(&p, (int)(d.pitch * DAF));
		putint(&p, (int)(d.roll * DAF));
		// quantize to 1/100, almost always 1 byte
		putint(&p, (int)(d.velocity.x * DVF));
		putint(&p, (int)(d.velocity.y * DVF));
		putint(&p, (int)(d.velocity.z * DVF));
		// pack rest in 1 byte: strafe:2, move:2, onFloor:1, state:3

		putint(&p, (d.strafe & 3) | ((d.move & 3) << 2) |
		    (((int)d.onFloor) << 4) |
		    ((editmode ? CS_EDITING : d.state) << 5));

		if (senditemstoserver) {
			packet->flags = ENET_PACKET_FLAG_RELIABLE;
			putint(&p, SV_ITEMLIST);
			if (!m_noitems)
				putitems(&p);
			putint(&p, -1);
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
			putint(&p, SV_INITC2S);
			sendstring(Player.player1.name, &p);
			sendstring(Player.player1.team, &p);
			putint(&p, Player.player1.lifeSequence);
		}
		for (OFData *msg in messages) {
			// send messages collected during the previous frames
			if (*(int *)[msg itemAtIndex:1])
				packet->flags = ENET_PACKET_FLAG_RELIABLE;
			for (int i = 0; i < *(int *)[msg itemAtIndex:0]; i++)
				putint(&p, *(int *)[msg itemAtIndex:i + 2]);
		}
		[messages removeAllObjects];
		if (lastmillis - lastping > 250) {
			putint(&p, SV_PING);
			putint(&p, lastmillis);
			lastping = lastmillis;
		}







|

|
|







344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
			putint(&p, SV_INITC2S);
			sendstring(Player.player1.name, &p);
			sendstring(Player.player1.team, &p);
			putint(&p, Player.player1.lifeSequence);
		}
		for (OFData *msg in messages) {
			// send messages collected during the previous frames
			if (*(int *)[msg itemAtIndex: 1])
				packet->flags = ENET_PACKET_FLAG_RELIABLE;
			for (int i = 0; i < *(int *)[msg itemAtIndex: 0]; i++)
				putint(&p, *(int *)[msg itemAtIndex: i + 2]);
		}
		[messages removeAllObjects];
		if (lastmillis - lastping > 250) {
			putint(&p, SV_PING);
			putint(&p, lastmillis);
			lastping = lastmillis;
		}

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

170
171
172
173
174
175
176
177
178

179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
				if (mapchanged)
					setspawn(n, true);
			break;
		}
		// server requests next map
		case SV_MAPRELOAD: {
			getint(&p);
			OFString *nextmapalias = [OFString
			    stringWithFormat:@"nextmap_%@", getclientmap()];

			OFString *map =
			    getalias(nextmapalias); // look up map in the cycle
			changemap(map != nil ? map : getclientmap());
			break;
		}

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

				// send new players my info again
				c2sinit = false;







|
|
>
|
<










|







170
171
172
173
174
175
176
177
178
179
180

181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
				if (mapchanged)
					setspawn(n, true);
			break;
		}
		// server requests next map
		case SV_MAPRELOAD: {
			getint(&p);
			OFString *nextmapalias = [OFString stringWithFormat:
			    @"nextmap_%@", getclientmap()];
			// look up map in the cycle
			OFString *map = getalias(nextmapalias);

			changemap(map != nil ? map : getclientmap());
			break;
		}

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

				// send new players my info again
				c2sinit = false;
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
			OFVector3D loc = d_.origin;
			playsound(S_DIE1 + rnd(2), &loc);
			d_.lifeSequence++;
			break;
		}

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

		case SV_ITEMPICKUP:
			setspawn(getint(&p), false);
			getint(&p);
			break;







|







275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
			OFVector3D loc = d_.origin;
			playsound(S_DIE1 + rnd(2), &loc);
			d_.lifeSequence++;
			break;
		}

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

		case SV_ITEMPICKUP:
			setspawn(getint(&p), false);
			getint(&p);
			break;
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
		case SV_EDITENT: // coop edit of ent
		{
			unsigned int i = getint(&p);

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

			int to = ents[i].type;
			ents[i].type = getint(&p);
			ents[i].x = getint(&p);
			ents[i].y = getint(&p);
			ents[i].z = getint(&p);







|







338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
		case SV_EDITENT: // coop edit of ent
		{
			unsigned int i = getint(&p);

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

			int to = ents[i].type;
			ents[i].type = getint(&p);
			ents[i].x = getint(&p);
			ents[i].y = getint(&p);
			ents[i].z = getint(&p);
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
			addmsg(0, 2, SV_CLIENTPING,
			    Player.player1.ping = (Player.player1.ping * 5 +
			                              lastmillis - getint(&p)) /
			        6);
			break;

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

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








|







368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
			addmsg(0, 2, SV_CLIENTPING,
			    Player.player1.ping = (Player.player1.ping * 5 +
			                              lastmillis - getint(&p)) /
			        6);
			break;

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

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

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

17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

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

	if (alias == nil)
		Identifier.identifiers[name] = [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);
})

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

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

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

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

	return 0;
}

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

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

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

	return nil;
}

// parse any nested set of () or []
static char *







|
|
|

|







|








|








|
















|







17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

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

	if (alias == nil)
		Identifier.identifiers[name] = [Alias aliasWithName: name
		                                             action: action
		                                          persisted: true];
	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);
})

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

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

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

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

	return 0;
}

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

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

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

	return nil;
}

// parse any nested set of () or []
static char *
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
			return NULL;
		}
	}
	char *s = strndup(word, *p - word - 1);
	if (left == '(') {
		OFString *t;
		@try {
			t = [OFString
			    stringWithFormat:@"%d", execute(@(s), true)];
		} @finally {
			free(s);
		}
		s = strdup(t.UTF8String);
	}
	return s;
}







|
|







94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
			return NULL;
		}
	}
	char *s = strndup(word, *p - word - 1);
	if (left == '(') {
		OFString *t;
		@try {
			t = [OFString stringWithFormat:
			    @"%d", execute(@(s), true)];
		} @finally {
			free(s);
		}
		s = strdup(t.UTF8String);
	}
	return s;
}
136
137
138
139
140
141
142
143
144
145
146

147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
}

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

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

	} else if ([identifier isKindOfClass:Alias.class])
		return [identifier action];

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

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

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

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

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

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

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

	return 0;







|

|
|
>
|


|









|









|


|

|







|
|


|




|







136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
}

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

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

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

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

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

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

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

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

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

	return 0;
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287

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

335
336
337
338
339

340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
		}

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

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

	return val;
}

// tab-completion of all identifiers

int completesize = 0, completeidx = 0;

void
resetcomplete()
{
	completesize = 0;
}

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

	if (s.length == 1)
		return;

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

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

			                 withString:identifier.name];
	}];

	completeidx++;

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

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

	execute(command, true);
	return true;
}

void
exec(OFString *cfgfile)
{
	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
		    IRIByAppendingPathComponent:@"config.cfg"];
		stream = [[OFIRIHandler handlerForIRI:IRI] openItemAtIRI:IRI

		                                                    mode:@"w"];
	} @catch (id e) {
		return;
	}


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

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

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

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

	[Identifier.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, ^{
	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;
})







|
|







|


















|
|










|
|

|
<
|
>
|













|














|

|



|









|
|
>
|




>
|
|
|
|
|
|

|

|
|
|



|
|

|


|

|
|
|
|


|
|





|










|


|
|


|








|




|










|



|
|


|













|












|



|



|



|



|



|



|



|



|
|


|



|


236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286

287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
		}

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

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

	return val;
}

// tab-completion of all identifiers

int completesize = 0, completeidx = 0;

void
resetcomplete()
{
	completesize = 0;
}

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

	if (s.length == 1)
		return;

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

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

			[s replaceCharactersInRange: OFMakeRange(
							 1, s.length - 1)
					 withString: identifier.name];
	}];

	completeidx++;

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

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

	execute(command, true);
	return true;
}

void
exec(OFString *cfgfile)
{
	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
		    IRIByAppendingPathComponent: @"config.cfg"];
		stream = [[OFIRIHandler handlerForIRI: IRI]
		    openItemAtIRI: IRI
			     mode: @"w"];
	} @catch (id e) {
		return;
	}

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

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

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

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

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

		[stream writeFormat: @"alias \"%@\" [%@]\n",
				     alias.name, alias.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 [a83084fb6c] to [1d463d8f9f].

14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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
	if (conlines.count > 100) {
		text = [conlines.lastObject.text mutableCopy];
		[conlines removeLastObject];
	} else
		text = [OFMutableString string];

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

	[text appendString:sf];

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

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

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

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

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

	va_end(arguments);

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

// render buffer taking into account time & scrolling
void
renderconsole()







|



















|

|




|
|
|













|
|





|
|







14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
const int ndraw = 5;
const int WORDWRAP = 80;
int conskip = 0;

bool saycommandon = false;
static OFMutableString *commandbuf;

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

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

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

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

	[text appendString: sf];

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

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

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

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

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

	va_end(arguments);

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

// render buffer taking into account time & scrolling
void
renderconsole()
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
		    (FONTH / 4 * 5) * (nd - j - 1) + FONTH / 3, 2);
}

// keymap is defined externally in keymap.cfg

static OFMutableArray<KeyMapping *> *keyMappings = nil;

COMMAND(keymap, ARG_3STR, ^(OFString *code, OFString *key, OFString *action) {
	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);







|



|
|

|


|

|







98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
		    (FONTH / 4 * 5) * (nd - j - 1) + FONTH / 3, 2);
}

// keymap is defined externally in keymap.cfg

static OFMutableArray<KeyMapping *> *keyMappings = nil;

COMMAND(keymap, ARG_3STR, ^ (OFString *code, OFString *key, OFString *action) {
	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);
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171

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

	commandbuf = [init mutableCopy];
}

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







|



|







|





|







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

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

	commandbuf = [init mutableCopy];
}

COMMAND(saycommand, ARG_VARI, ^ (OFString *init) {
	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;
	}
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
				if (commandbuf.length > 0) {
					if (vhistory == nil)
						vhistory =
						    [[OFMutableArray alloc]
						        init];

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







|
|

|
|


|







222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
				if (commandbuf.length > 0) {
					if (vhistory == nil)
						vhistory =
						    [[OFMutableArray alloc]
						        init];

					if (vhistory.count == 0 ||
					    ![vhistory.lastObject isEqual:
					    commandbuf]) {
						// cap this?
						[vhistory addObject:
						    [commandbuf copy]];
					}
					histpos = vhistory.count;
					if ([commandbuf hasPrefix: @"/"])
						execute(commandbuf, true);
					else
						toserver(commandbuf);
				}
				saycommand(NULL);
			} else if (code == SDLK_ESCAPE) {
				saycommand(NULL);
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
	}
}

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

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

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







|













|
|

257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
	}
}

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

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

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

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

306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
#define m_noitemsrail (gamemode <= 5)
#define m_arena (gamemode >= 8)
#define m_tarena (gamemode >= 10)
#define m_teammode (gamemode & 1 && gamemode > 2)
#define m_sp (gamemode < 0)
#define m_dmsp (gamemode == -1)
#define m_classicsp (gamemode == -2)
#define isteam(a, b) (m_teammode && [a isEqual:b])

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







|







306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
#define m_noitemsrail (gamemode <= 5)
#define m_arena (gamemode >= 8)
#define m_tarena (gamemode >= 10)
#define m_teammode (gamemode & 1 && gamemode > 2)
#define m_sp (gamemode < 0)
#define m_dmsp (gamemode == -1)
#define m_classicsp (gamemode == -2)
#define isteam(a, b) (m_teammode && [a isEqual: b])

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

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

16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// invariant: all code assumes that these are kept inside MINBORD distance of
// the edge of the map

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

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

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

int selh = 0;
bool selset = false;







|







|
|
|
|
|
|
|







16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// invariant: all code assumes that these are kept inside MINBORD distance of
// the edge of the map

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

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

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

int selh = 0;
bool selset = false;
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
		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);







|







89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
		projreset();
	}
	Cube.sharedInstance.repeatsKeys = editmode;
	selset = false;
	editing = editmode;
}

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

void
correctsel() // ensures above invariant
{
	selset = !OUTBORD(sel.x, sel.y);
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#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







|







133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#define EDITSELMP                                            \
	if (noteditmode() || noselection() || multiplayer()) \
		return;
#define EDITMP                              \
	if (noteditmode() || multiplayer()) \
		return;

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

void
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
			if (SOLID(s))
				continue;
			float h1 = sheight(s, s, z);
			float h2 = sheight(s, SWS(s, 1, 0, ssize), z);
			float h3 = sheight(s, SWS(s, 1, 1, ssize), z);
			float h4 = sheight(s, SWS(s, 0, 1, ssize), z);
			if (s->tag)
				linestyle(GRIDW, [OFColor colorWithRed:1.0f
								 green:0.25f
								  blue:0.25f
								 alpha:1.0f]);
			else if (s->type == FHF || s->type == CHF)
				linestyle(GRIDW, [OFColor colorWithRed:0.5f
								 green:1.0f
								  blue:0.5f
								 alpha:1.0f]);
			else
				linestyle(GRIDW, OFColor.gray);
			struct block b = { ix, iy, 1, 1 };
			box(&b, h1, h2, h3, h4);
			linestyle(GRID8, [OFColor colorWithRed:0.25f
							 green:0.25f
							  blue:1.0f
							 alpha:1.0f]);
			if (!(ix & GRIDM))
				line(ix, iy, h1, ix, iy + 1, h4);
			if (!(ix + 1 & GRIDM))
				line(ix + 1, iy, h2, ix + 1, iy + 1, h3);
			if (!(iy & GRIDM))
				line(ix, iy, h1, ix + 1, iy, h2);
			if (!(iy + 1 & GRIDM))







|
|
|
|

|
|
|
|




|
|
|
|







217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
			if (SOLID(s))
				continue;
			float h1 = sheight(s, s, z);
			float h2 = sheight(s, SWS(s, 1, 0, ssize), z);
			float h3 = sheight(s, SWS(s, 1, 1, ssize), z);
			float h4 = sheight(s, SWS(s, 0, 1, ssize), z);
			if (s->tag)
				linestyle(GRIDW, [OFColor colorWithRed: 1.0f
								 green: 0.25f
								  blue: 0.25f
								 alpha: 1.0f]);
			else if (s->type == FHF || s->type == CHF)
				linestyle(GRIDW, [OFColor colorWithRed: 0.5f
								 green: 1.0f
								  blue: 0.5f
								 alpha: 1.0f]);
			else
				linestyle(GRIDW, OFColor.gray);
			struct block b = { ix, iy, 1, 1 };
			box(&b, h1, h2, h3, h4);
			linestyle(GRID8, [OFColor colorWithRed: 0.25f
							 green: 0.25f
							  blue: 1.0f
							 alpha: 1.0f]);
			if (!(ix & GRIDM))
				line(ix, iy, h1, ix, iy + 1, h4);
			if (!(ix + 1 & GRIDM))
				line(ix + 1, iy, h2, ix + 1, iy + 1, h3);
			if (!(iy & GRIDM))
				line(ix, iy, h1, ix + 1, iy, h2);
			if (!(iy + 1 & GRIDM))
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
		    sheight(s, SWS(s, 0, 1, ssize), z));
		linestyle(GRIDS, OFColor.red);
		dot(cx, cy, ih);
		ch = (int)ih;
	}

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

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

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

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

void
makeundo()
{
	if (undos == nil)
		undos = [[OFMutableData alloc]
		    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;







|
|
|
|












|




|









|


|



|













|








|







258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
		    sheight(s, SWS(s, 0, 1, ssize), z));
		linestyle(GRIDS, OFColor.red);
		dot(cx, cy, ih);
		ch = (int)ih;
	}

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

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

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

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

void
makeundo()
{
	if (undos == nil)
		undos = [[OFMutableData alloc]
		    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;
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
	EDITSEL;

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

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

void
edittexxy(int type, int t, const struct block *sel)
{
	loopselxy(switch (type) {







|







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

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

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

void
edittexxy(int type, int t, const struct block *sel)
{
	loopselxy(switch (type) {
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
		        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:







|



















|







420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
		        break;
	        case 3:
		        s->utex = t;
		        break;
	});
}

COMMAND(edittex, ARG_2INT, ^ (int type, int dir) {
	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:
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
		return;
	}

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

COMMAND(heightfield, ARG_1INT, ^(int t) {
	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;







|



|



|







493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
		return;
	}

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

COMMAND(heightfield, ARG_1INT, ^ (int t) {
	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;
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
		else
			s->ceil = hi;
		if (s->floor >= s->ceil)
			s->floor = s->ceil - 1;
	});
}

COMMAND(equalize, ARG_1INT, ^(int flr) {
	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;







|















|










|











|







525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
		else
			s->ceil = hi;
		if (s->floor >= s->ceil)
			s->floor = s->ceil - 1;
	});
}

COMMAND(equalize, ARG_1INT, ^ (int flr) {
	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;
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
	        ? (archverts[sel->xs - 1][x] +
	              (y == 0 || y == sel->ys - 1 ? sidedelta : 0))
	        : (archverts[sel->ys - 1][y] +
	              (x == 0 || x == sel->xs - 1 ? sidedelta : 0)));
	remipmore(sel, 0);
})

COMMAND(slope, ARG_2INT, ^(int xd, int yd) {
	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();








|


















|







586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
	        ? (archverts[sel->xs - 1][x] +
	              (y == 0 || y == sel->ys - 1 ? sidedelta : 0))
	        : (archverts[sel->ys - 1][y] +
	              (x == 0 || x == sel->xs - 1 ? sidedelta : 0)));
	remipmore(sel, 0);
})

COMMAND(slope, ARG_2INT, ^ (int xd, int yd) {
	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();

635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
    fullbright, 0, 0, 1, if (fullbright) {
	    if (noteditmode())
		    return;
	    for (int i = 0; i < mipsize; i++)
		    world[i].r = world[i].g = world[i].b = 176;
    });

COMMAND(edittag, ARG_1INT, ^(int tag) {
	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)Player.player1.origin.z, what,
	        [a1 cube_intValueWithBase:0], [a2 cube_intValueWithBase:0],
	        [a3 cube_intValueWithBase:0], [a4 cube_intValueWithBase:0]);
    })







|









|
|

|
|
|
|
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
    fullbright, 0, 0, 1, if (fullbright) {
	    if (noteditmode())
		    return;
	    for (int i = 0; i < mipsize; i++)
		    world[i].r = world[i].g = world[i].b = 176;
    });

COMMAND(edittag, ARG_1INT, ^ (int tag) {
	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)Player.player1.origin.z, what,
	    [a1 cube_intValueWithBase: 0], [a2 cube_intValueWithBase: 0],
	    [a3 cube_intValueWithBase: 0], [a4 cube_intValueWithBase: 0]);
})

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

309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
checkitems()
{
	Player *player1 = Player.player1;

	if (editmode)
		return;

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

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

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







|







309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
checkitems()
{
	Player *player1 = Player.player1;

	if (editmode)
		return;

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

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

		if (OUTBORD(e.x, e.y))
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
	}
}

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







|







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

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

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

17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
	if ((vmenu = menu) >= 1)
		[Player.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++;
	}
})








|


|







17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
	if ((vmenu = menu) >= 1)
		[Player.player1 resetMovement];
	if (vmenu == 1)
		menus[1].menusel = 0;
}

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

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

	if (vmenu == 1)
		refreshservers();

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







|







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

	if (vmenu == 1)
		refreshservers();

	Menu *m = menus[vmenu];
	OFString *title;
	if (vmenu > 1)
		title = [OFString stringWithFormat: @"[ %@ menu ]", m.name];
	else
		title = m.name;
	int mdisp = m.items.count;
	int w = 0;
	for (int i = 0; i < mdisp; i++) {
		int x = text_width(m.items[i].text);
		if (x > w)
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126

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







|


|









|
|


|


|
|
|
|







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

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

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

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;
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
			OFString *action = menus[vmenu].items[menusel].action;
			if (vmenu == 1)
				connects(getservername(menusel));

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

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

			execute(action, true);
		}
	}

	return true;
}







|








151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
			OFString *action = menus[vmenu].items[menusel].action;
			if (vmenu == 1)
				connects(getservername(menusel));

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

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

			execute(action, true);
		}
	}

	return true;
}

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

28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
			if (o.origin.z - o.eyeHeight < *hi)
				*hi = o.origin.z - o.eyeHeight - 1;
		} else if (o.origin.z + o.aboveEye > *lo)
			*lo = o.origin.z + o.aboveEye + 1;

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

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








|







28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
			if (o.origin.z - o.eyeHeight < *hi)
				*hi = o.origin.z - o.eyeHeight - 1;
		} else if (o.origin.z + o.aboveEye > *lo)
			*lo = o.origin.z + o.aboveEye + 1;

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

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

104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
	const float fy2 = d.origin.y + d.radius;
	const int x1 = fx1;
	const int y1 = fy1;
	const int x2 = fx2;
	const int y2 = fy2;
	float hi = 127, lo = -128;
	// big monsters are afraid of heights, unless angry :)
	float minfloor =
	    ([d isKindOfClass:Monster.class] && !spawn && d.health > 100
	            ? d.origin.z - d.eyeHeight - 4.5f
	            : -1000.0f);

	for (int x = x1; x <= x2; x++) {
		for (int y = y1; y <= y2; y++) {
			// collide with map
			if (OUTBORD(x, y))
				return false;
			struct sqr *s = S(x, y);







|
<
|
<







104
105
106
107
108
109
110
111

112

113
114
115
116
117
118
119
	const float fy2 = d.origin.y + d.radius;
	const int x1 = fx1;
	const int y1 = fy1;
	const int x2 = fx2;
	const int y2 = fy2;
	float hi = 127, lo = -128;
	// big monsters are afraid of heights, unless angry :)
	float minfloor = ([d isKindOfClass: Monster.class] && !spawn &&

	    d.health > 100 ? d.origin.z - d.eyeHeight - 4.5f : -1000.0f);


	for (int x = x1; x <= x2; x++) {
		for (int y = y1; y <= y2; y++) {
			// collide with map
			if (OUTBORD(x, y))
				return false;
			struct sqr *s = S(x, y);
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
				// correct water feel
				if (water)
					pl.velocity = OFMakeVector3D(
					    pl.velocity.x / 8,
					    pl.velocity.y / 8, pl.velocity.z);
				if (local)
					playsoundc(S_JUMP);
				else if ([pl isKindOfClass:Monster.class]) {
					OFVector3D loc = pl.origin;
					playsound(S_JUMP, &loc);
				}
			} else if (pl.timeInAir > 800) {
				// if we land after long time must have been a
				// high jump, make thud sound
				if (local)
					playsoundc(S_LAND);
				else if ([pl isKindOfClass:Monster.class]) {
					OFVector3D loc = pl.origin;
					playsound(S_LAND, &loc);
				}
			}

			pl.timeInAir = 0;
		} else







|








|







322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
				// correct water feel
				if (water)
					pl.velocity = OFMakeVector3D(
					    pl.velocity.x / 8,
					    pl.velocity.y / 8, pl.velocity.z);
				if (local)
					playsoundc(S_JUMP);
				else if ([pl isKindOfClass: Monster.class]) {
					OFVector3D loc = pl.origin;
					playsound(S_JUMP, &loc);
				}
			} else if (pl.timeInAir > 800) {
				// if we land after long time must have been a
				// high jump, make thud sound
				if (local)
					playsoundc(S_LAND);
				else if ([pl isKindOfClass: Monster.class]) {
					OFVector3D loc = pl.origin;
					playsound(S_LAND, &loc);
				}
			}

			pl.timeInAir = 0;
		} else

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

60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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)







|







60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
const float TEXTURESCALE = 32.0f;
bool floorstrip = false, deltastrip = false;
int oh, oy, ox, ogltex; // the o* vars are used by the stripification
int ol3r, ol3g, ol3b, ol4r, ol4g, ol4b;
int firstindex;
bool showm = false;

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

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

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

56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
blendbox(int x1, int y1, int x2, int y2, bool border)
{
	glDepthMask(GL_FALSE);
	glDisable(GL_TEXTURE_2D);
	glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
	glBegin(GL_QUADS);
	if (border)
		[[OFColor colorWithRed:0.5f
				 green:0.3f
				  blue:0.4f
				 alpha:1.0f] cube_setAsGLColor];
	else
		[OFColor.white cube_setAsGLColor];
	glVertex2i(x1, y1);
	glVertex2i(x2, y1);
	glVertex2i(x2, y2);
	glVertex2i(x1, y2);
	glEnd();
	glDisable(GL_BLEND);
	glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	glBegin(GL_POLYGON);
	[[OFColor colorWithRed:0.2f
			 green:0.7f
			  blue:0.4f
			 alpha:1.0f] cube_setAsGLColor];
	glVertex2i(x1, y1);
	glVertex2i(x2, y1);
	glVertex2i(x2, y2);
	glVertex2i(x1, y2);
	glEnd();
	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	xtraverts += 8;







|
|
|
|










|
|
|
|







56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
blendbox(int x1, int y1, int x2, int y2, bool border)
{
	glDepthMask(GL_FALSE);
	glDisable(GL_TEXTURE_2D);
	glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
	glBegin(GL_QUADS);
	if (border)
		[[OFColor colorWithRed: 0.5f
				 green: 0.3f
				  blue: 0.4f
				 alpha: 1.0f] cube_setAsGLColor];
	else
		[OFColor.white cube_setAsGLColor];
	glVertex2i(x1, y1);
	glVertex2i(x2, y1);
	glVertex2i(x2, y2);
	glVertex2i(x1, y2);
	glEnd();
	glDisable(GL_BLEND);
	glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	glBegin(GL_POLYGON);
	[[OFColor colorWithRed: 0.2f
			 green: 0.7f
			  blue: 0.4f
			 alpha: 1.0f] cube_setAsGLColor];
	glVertex2i(x1, y1);
	glVertex2i(x2, y1);
	glVertex2i(x2, y2);
	glVertex2i(x1, y2);
	glEnd();
	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	xtraverts += 8;
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE);
	glBindTexture(GL_TEXTURE_2D, 4);

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







|
|
|
|







129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE);
	glBindTexture(GL_TEXTURE_2D, 4);

	for (struct sphere *p, **pp = &slist; (p = *pp) != NULL;) {
		glPushMatrix();
		float size = p->size / p->max;
		[[OFColor colorWithRed: 1.0f
				 green: 1.0f
				  blue: 1.0f
				 alpha: 1.0f - size] cube_setAsGLColor];
		glTranslatef(p->o.x, p->o.z, p->o.y);
		glRotatef(lastmillis / 5.0f, 1, 1, 1);
		glScalef(p->size, p->size, p->size);
		glCallList(1);
		glScalef(0.8f, 0.8f, 0.8f);
		glCallList(1);
		glPopMatrix();
203
204
205
206
207
208
209
210

211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
		particle_splash(2, 2, 40, OFMakeVector3D(e.x, e.y, e.z));
	}

	int e = closestent();
	if (e >= 0) {
		Entity *c = ents[e];
		closeent =
		    [OFString stringWithFormat:@"closest entity = %@ (%d, %d, "

		                               @"%d, %d), selection = (%d, %d)",
		        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;

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

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

	lastsky = basename;
}))

float cursordepth = 0.9f;







|
>
|





|


|
|

|






|
|


|
<
|
<







203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235

236

237
238
239
240
241
242
243
		particle_splash(2, 2, 40, OFMakeVector3D(e.x, e.y, e.z));
	}

	int e = closestent();
	if (e >= 0) {
		Entity *c = ents[e];
		closeent =
		    [OFString stringWithFormat:
			@"closest entity = %@ (%d, %d, %d, %d), "
			@"selection = (%d, %d)",
		        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;

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

		int xs, ys;
		if (!installtex(texnum + i, [Cube.sharedInstance.gameDataIRI

		    IRIByAppendingPathComponent: path], &xs, &ys, true))

			conoutf(@"could not load sky textures");
	}

	lastsky = basename;
}))

float cursordepth = 0.9f;
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379

	glDepthMask(GL_FALSE);

	if (dblend || underwater) {
		glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
		glBegin(GL_QUADS);
		if (dblend)
			[[OFColor colorWithRed:0.0f
					 green:0.9f
					  blue:0.9f
					 alpha:1.0f] cube_setAsGLColor];
		else
			[[OFColor colorWithRed:0.9f
					 green:0.5f
					  blue:0.0f
					 alpha:1.0f] cube_setAsGLColor];
		glVertex2i(0, 0);
		glVertex2i(VIRTW, 0);
		glVertex2i(VIRTW, VIRTH);
		glVertex2i(0, VIRTH);
		glEnd();
		dblend -= curtime / 3;
		if (dblend < 0)







|
|
|
|

|
|
|
|







356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378

	glDepthMask(GL_FALSE);

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

410
411
412
413
414
415
416
417
		[OFColor.white cube_setAsGLColor];
		if (crosshairfx) {
			if (player1.gunWait)
				[OFColor.gray cube_setAsGLColor];
			else if (player1.health <= 25)
				[OFColor.red cube_setAsGLColor];
			else if (player1.health <= 50)
				[[OFColor colorWithRed:1.0f
						 green:0.5f
						  blue:0.0f

						 alpha:1.0f] cube_setAsGLColor];
		}
		float chsize = (float)crosshairsize;
		glTexCoord2d(0.0, 0.0);
		glVertex2f(VIRTW / 2 - chsize, VIRTH / 2 - chsize);
		glTexCoord2d(1.0, 0.0);
		glVertex2f(VIRTW / 2 + chsize, VIRTH / 2 - chsize);
		glTexCoord2d(1.0, 1.0);







|
|
|
>
|







399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
		[OFColor.white cube_setAsGLColor];
		if (crosshairfx) {
			if (player1.gunWait)
				[OFColor.gray cube_setAsGLColor];
			else if (player1.health <= 25)
				[OFColor.red cube_setAsGLColor];
			else if (player1.health <= 50)
				[[OFColor colorWithRed: 1.0f
						 green: 0.5f
						  blue: 0.0f
						 alpha: 1.0f]
				    cube_setAsGLColor];
		}
		float chsize = (float)crosshairsize;
		glTexCoord2d(0.0, 0.0);
		glVertex2f(VIRTW / 2 - chsize, VIRTH / 2 - chsize);
		glTexCoord2d(1.0, 0.0);
		glVertex2f(VIRTW / 2 + chsize, VIRTH / 2 - chsize);
		glTexCoord2d(1.0, 1.0);

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

186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
	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];

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

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

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

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

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

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

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







|



|






|
|




















|













|
|

|
<
|
<







186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243

244

245
246
247
248
249
250
251
	for (int i = 0; i < 256; i++)
		for (int j = 0; j < MAXFRAMES; j++)
			mapping[i][j] = 0;
}

int curtexnum = 0;

COMMAND(texturereset, ARG_NONE, ^ {
	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];

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

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

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

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

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

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

	if (installtex(tnum, [Cube.sharedInstance.gameDataIRI

	    IRIByAppendingPathComponent: path], xs, ys, false)) {

		mapping[tex][frame] = tnum;
		texx[curtex] = *xs;
		texy[curtex] = *ys;
		curtex++;
		return tnum;
	} else {
		return mapping[tex][frame] = FIRSTTEX; // temp fix
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
}

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

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

#undef gamma

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







|


|







316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
}

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

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

#undef gamma

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

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

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

26
27
28
29

30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

static const int FIRSTMDL = 20;

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

		OFIRI *IRI1 = [baseIRI IRIByAppendingPathComponent:@"tris.md2"];

		if (![m loadWithIRI:IRI1])
			fatal(@"loadmodel: %@", IRI1.string);

		OFIRI *IRI2 = [baseIRI IRIByAppendingPathComponent:@"skin.jpg"];

		int xs, ys;
		installtex(FIRSTMDL + m.mdlnum, IRI2, &xs, &ys, false);
		m.loaded = true;
	}
}

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

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

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

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

	mdllookup[name] = m;

	return m;
}

COMMAND(mapmodel, ARG_5STR,
    ^(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;







|
|

|

|
>
|


|
>

















|










|
<
|
<
|
|
|
|
|
|
|

|
|

|
|

|







13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

61

62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

static const int FIRSTMDL = 20;

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

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

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

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

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

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

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

	mdllookup[name] = m;

	return m;
}

COMMAND(mapmodel, ARG_5STR, ^ (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;
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136

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

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

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

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

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







|





|
|
|
|




|
|
|
|
|


|
|
|
|
|
|
|
|
|
|

98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136

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

	int ix = (int)position.x;
	int iy = (int)position.z;
	OFColor *light = OFColor.white;

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

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

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

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

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

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

void
draw_text(OFString *string, int left, int top, int gl_num)
{







|
|







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

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

void
draw_text(OFString *string, int left, int top, int gl_num)
{
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183

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

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

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







|
|
|
|







166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183

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

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

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

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

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

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







|
<







93
94
95
96
97
98
99
100

101
102
103
104
105
106
107
}

void
savestate(OFIRI *IRI)
{
	stop();
	f = gzopen([IRI.fileSystemRepresentation
	    cStringWithEncoding: OFLocale.encoding], "wb9");

	if (!f) {
		conoutf(@"could not write %@", IRI.string);
		return;
	}
	gzwrite(f, (void *)"CUBESAVE", 8);
	gzputc(f, islittleendian);
	gzputi(SAVEGAMEVERSION);
127
128
129
130
131
132
133
134
135
136
137
138
139
140

141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
	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;
	f = gzopen([IRI.fileSystemRepresentation
	               cStringWithEncoding:OFLocale.encoding],
	    "rb9");
	if (!f) {
		conoutf(@"could not open %@", IRI.string);
		return;
	}

	char mapname[_MAXDEFSTR] = { 0 };
	char buf[8];







|





|
>
|
|












|
<







126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155

156
157
158
159
160
161
162
	for (id player in players) {
		gzput(player == [OFNull null]);
		data = [player dataBySerializing];
		gzwrite(f, data.items, data.count);
	}
}

COMMAND(savegame, ARG_1STR, (^ (OFString *name) {
	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;
	f = gzopen([IRI.fileSystemRepresentation
	    cStringWithEncoding: OFLocale.encoding], "rb9");

	if (!f) {
		conoutf(@"could not open %@", IRI.string);
		return;
	}

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

188
189
190
191
192
193
194
195
196
	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();







|
|
>
|
|







178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
	return;
out:
	conoutf(@"aborting: savegame/demo from a different version of "
	        @"cube or cpu architecture");
	stop();
}

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

void
loadgameout()
{
	stop();
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
		if (e.type == CARROT && !e.spawned)
			trigger(e.attr1, e.attr2, true);
	}

	restoreserverstate(ents);

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

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

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

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

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

// demo functions

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

COMMAND(record, ARG_1STR, (^(OFString *name) {
	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;
}))







|
|

|









|















|

















|









|
|
|







213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
		if (e.type == CARROT && !e.spawned)
			trigger(e.attr1, e.attr2, true);
	}

	restoreserverstate(ents);

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

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

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

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

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

// demo functions

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

COMMAND(record, ARG_1STR, (^ (OFString *name) {
	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;
}))
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
			ddamage = 0;
		}
		// FIXME: add all other client state which is not send through
		// the network
	}
}

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

void
stopreset()
{







|
|
|
|







329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
			ddamage = 0;
		}
		// FIXME: add all other client state which is not send through
		// the network
	}
}

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

void
stopreset()
{
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
		        playerhistory.lastObject.lastUpdate != playbacktime)) {
			Player *d = [target copy];
			d.lastUpdate = playbacktime;

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

			[playerhistory addObject:d];

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

		readdemotime();
	}

	if (!demoplayback)
		return;







|


|







458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
		        playerhistory.lastObject.lastUpdate != playbacktime)) {
			Player *d = [target copy];
			d.lastUpdate = playbacktime;

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

			[playerhistory addObject: d];

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

		readdemotime();
	}

	if (!demoplayback)
		return;
538
539
540
541
542
543
544
545
546
547
548
549
550
551
			}
			break;
		}
	}
	// if(player1->state!=CS_DEAD) showscores(false);
}

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







|






538
539
540
541
542
543
544
545
546
547
548
549
550
551
			}
			break;
		}
	}
	// if(player1->state!=CS_DEAD) showscores(false);
}

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

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

20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
bool notgotitems = true;
int mode = 0;

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

int interm = 0, minremain = 0, mapend = 0;
bool mapreload = false;







|
|







20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
bool notgotitems = true;
int mode = 0;

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

int interm = 0, minremain = 0, mapend = 0;
bool mapreload = false;
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
	if (packet->referenceCount == 0)
		enet_packet_destroy(packet);
}

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

void
resetitems()







|
|







97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
	if (packet->referenceCount == 0)
		enet_packet_destroy(packet);
}

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

void
resetitems()
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
	clients[sender].mapvote = map;
	clients[sender].modevote = reqmode;

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

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

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

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

	sendservmsg(@"vote passed");







|












|
|







142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
	clients[sender].mapvote = map;
	clients[sender].modevote = reqmode;

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

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

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

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

	sendservmsg(@"vote passed");
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
		}

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

		case SV_ITEMPICKUP: {







|
|







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

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

		case SV_ITEMPICKUP: {
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
	sendstring(serverpassword, &p);
	putint(&p, clients.count > maxclients);
	if (smapname.length > 0) {
		putint(&p, SV_MAPCHANGE);
		sendstring(smapname, &p);
		putint(&p, mode);
		putint(&p, SV_ITEMLIST);
		[sents enumerateObjectsUsingBlock:^(
		    ServerEntity *e, size_t i, bool *stop) {
			if (e.spawned)
				putint(&p, i);
		}];
		putint(&p, -1);
	}
	*(unsigned short *)start = ENET_HOST_TO_NET_16(p - start);
	enet_packet_resize(packet, p - start);







|
|







310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
	sendstring(serverpassword, &p);
	putint(&p, clients.count > maxclients);
	if (smapname.length > 0) {
		putint(&p, SV_MAPCHANGE);
		sendstring(smapname, &p);
		putint(&p, mode);
		putint(&p, SV_ITEMLIST);
		[sents enumerateObjectsUsingBlock:
		    ^ (ServerEntity *e, size_t i, bool *stop) {
			if (e.spawned)
				putint(&p, i);
		}];
		putint(&p, -1);
	}
	*(unsigned short *)start = ENET_HOST_TO_NET_16(p - start);
	enet_packet_resize(packet, p - start);
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
			return client;

	Client *client = [Client client];

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

	[clients addObject:client];

	return client;
}

void
checkintermission()
{







|







352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
			return client;

	Client *client = [Client client];

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

	[clients addObject: client];

	return client;
}

void
checkintermission()
{
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432

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

	lastsec = seconds;

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







|
|














|
|







401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432

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

	lastsec = seconds;

	if ((mode > 1 || (mode == 0 && nonlocalclients)) &&
	    seconds > mapend - minremain * 60)
		checkintermission();
	if (interm && seconds > interm) {
		interm = 0;
		[clients enumerateObjectsUsingBlock:
		    ^ (Client *client, size_t i, bool *stop) {
			if (client.type != ST_EMPTY) {
				// ask a client to trigger map reload
				send2(true, i, SV_MAPRELOAD, 0);
				mapreload = true;
				*stop = true;
				return;
			}
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492

493
494
495
496
497
498
499
			c.peer = event.peer;
			c.peer->data = (void *)(clients.count - 1);
			char hn[1024];
			c.hostname = (enet_address_get_host(
			                  &c.peer->address, hn, sizeof(hn)) == 0
			        ? @(hn)
			        : @"localhost");
			[OFStdOut
			    writeFormat:@"client connected (%@)\n", c.hostname];
			send_welcome(lastconnect = clients.count - 1);
			break;
		}
		case ENET_EVENT_TYPE_RECEIVE:
			brec += event.packet->dataLength;
			process(event.packet, (intptr_t)event.peer->data);
			if (event.packet->referenceCount == 0)
				enet_packet_destroy(event.packet);
			break;
		case ENET_EVENT_TYPE_DISCONNECT:
			if ((intptr_t)event.peer->data < 0)
				break;
			[OFStdOut writeFormat:@"disconnected client (%@)\n",

			    clients[(size_t)event.peer->data].hostname];
			clients[(size_t)event.peer->data].type = ST_EMPTY;
			send2(true, -1, SV_CDIS, (intptr_t)event.peer->data);
			event.peer->data = (void *)-1;
			break;
		case ENET_EVENT_TYPE_NONE:
			break;







|
|












|
>







471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
			c.peer = event.peer;
			c.peer->data = (void *)(clients.count - 1);
			char hn[1024];
			c.hostname = (enet_address_get_host(
			                  &c.peer->address, hn, sizeof(hn)) == 0
			        ? @(hn)
			        : @"localhost");
			[OFStdOut writeFormat: @"client connected (%@)\n",
					       c.hostname];
			send_welcome(lastconnect = clients.count - 1);
			break;
		}
		case ENET_EVENT_TYPE_RECEIVE:
			brec += event.packet->dataLength;
			process(event.packet, (intptr_t)event.peer->data);
			if (event.packet->referenceCount == 0)
				enet_packet_destroy(event.packet);
			break;
		case ENET_EVENT_TYPE_DISCONNECT:
			if ((intptr_t)event.peer->data < 0)
				break;
			[OFStdOut writeFormat:
			    @"disconnected client (%@)\n",
			    clients[(size_t)event.peer->data].hostname];
			clients[(size_t)event.peer->data].type = ST_EMPTY;
			send2(true, -1, SV_CDIS, (intptr_t)event.peer->data);
			event.peer->data = (void *)-1;
			break;
		case ENET_EVENT_TYPE_NONE:
			break;

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

23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
	resolverresults = [[OFMutableArray alloc] init];
	resolverlimit = limit;
	resolversem = SDL_CreateSemaphore(0);

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

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

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

			resolverthreads[i] = rt;

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

void
resolverclear()
{
	@synchronized(ResolverThread.class) {







|




















|







23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
	resolverresults = [[OFMutableArray alloc] init];
	resolverlimit = limit;
	resolversem = SDL_CreateSemaphore(0);

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

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

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

			resolverthreads[i] = rt;

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

void
resolverclear()
{
	@synchronized(ResolverThread.class) {
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
	}
}

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

bool
resolvercheck(OFString **name, ENetAddress *address)
{







|







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

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

bool
resolvercheck(OFString **name, ENetAddress *address)
{
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
	return servers[n].name;
}

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

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

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

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

void
pingservers()
{
	ENetBuffer buf;







|





|


|







114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
	return servers[n].name;
}

void
addserver(OFString *servername)
{
	for (ServerInfo *si in servers)
		if ([si.name isEqual: servername])
			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;
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
	OFString *name = nil;
	ENetAddress addr = { ENET_HOST_ANY, CUBE_SERVINFO_PORT };
	while (resolvercheck(&name, &addr)) {
		if (addr.host == ENET_HOST_ANY)
			continue;

		for (ServerInfo *si in servers) {
			if ([name isEqual:si.name]) {
				si.address = addr;
				addr.host = ENET_HOST_ANY;
				break;
			}
		}
	}
}







|







159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
	OFString *name = nil;
	ENetAddress addr = { ENET_HOST_ANY, CUBE_SERVINFO_PORT };
	while (resolvercheck(&name, &addr)) {
		if (addr.host == ENET_HOST_ANY)
			continue;

		for (ServerInfo *si in servers) {
			if ([name isEqual: si.name]) {
				si.address = addr;
				addr.host = ENET_HOST_ANY;
				break;
			}
		}
	}
}
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
	checkresolver();
	checkpings();
	if (lastmillis - lastinfo >= 5000)
		pingservers();
	[servers sort];

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

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

		menumanual(1, i, si.full);

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







|
|



|
<

|
|





|
|
|




|







213
214
215
216
217
218
219
220
221
222
223
224
225

226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
	checkresolver();
	checkpings();
	if (lastmillis - lastinfo >= 5000)
		pingservers();
	[servers sort];

	__block int maxmenu = 16;
	[servers enumerateObjectsUsingBlock:
	    ^ (ServerInfo *si, size_t i, bool *stop) {
		if (si.address.host != ENET_HOST_ANY && si.ping != 9999) {
			if (si.protocol != PROTOCOL_VERSION)
				si.full = [OFString stringWithFormat:
				    @"%@ [different cube protocol]", si.name];

			else
				si.full = [OFString stringWithFormat:
				    @"%d\t%d\t%@, %@: %@ %@",
				    si.ping, si.numplayers,
				    si.map.length > 0 ? si.map : @"[unknown]",
				    modestr(si.mode), si.name, si.sdesc];
		} else
			si.full = [OFString stringWithFormat:
			    (si.address.host != ENET_HOST_ANY
			    ? @"%@ [waiting for server response]"
			    : @"%@ [unknown host]\t"),
			    si.name];

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

		menumanual(1, i, si.full);

		if (!--maxmenu)
			return;
	}];
}
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
	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 {







|



|







260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
	for (ServerInfo *si in servers)
		resolverquery(si.name);

	refreshservers();
	menuset(1);
}

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

30
31
32
33
34
35
36
37
38
39
40
41
42
43
// all server side masterserver and pinging functionality

#include "cube.h"

static ENetSocket mssock = ENET_SOCKET_NULL;

static void
httpgetsend(ENetAddress *ad, OFString *hostname, OFString *req, OFString *ref,
    OFString *agent)
{
	if (ad->host == ENET_HOST_ANY) {
		[OFStdOut writeFormat:@"looking up %@...\n", hostname];
		enet_address_set_host(ad, hostname.UTF8String);
		if (ad->host == ENET_HOST_ANY)
			return;
	}
	if (mssock != ENET_SOCKET_NULL)
		enet_socket_destroy(mssock);
	mssock = enet_socket_create(ENET_SOCKET_TYPE_STREAM, NULL);
	if (mssock == ENET_SOCKET_NULL) {
		printf("could not open socket\n");
		return;
	}
	if (enet_socket_connect(mssock, ad) < 0) {
		printf("could not connect\n");
		return;
	}
	ENetBuffer buf;
	OFString *httpget = [OFString stringWithFormat:@"GET %@ HTTP/1.0\n"

	                                               @"Host: %@\n"
	                                               @"Referer: %@\n"
	                                               @"User-Agent: %@\n\n",
	    req, hostname, ref, agent];
	buf.data = (void *)httpget.UTF8String;
	buf.dataLength = httpget.UTF8StringLength;
	[OFStdOut writeFormat:@"sending request to %@...\n", hostname];
	enet_socket_send(mssock, NULL, &buf, 1);
}

static void
httpgetrecieve(ENetBuffer *buf)
{
	if (mssock == ENET_SOCKET_NULL)











|
















|
>
|
|
|



|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// all server side masterserver and pinging functionality

#include "cube.h"

static ENetSocket mssock = ENET_SOCKET_NULL;

static void
httpgetsend(ENetAddress *ad, OFString *hostname, OFString *req, OFString *ref,
    OFString *agent)
{
	if (ad->host == ENET_HOST_ANY) {
		[OFStdOut writeFormat: @"looking up %@...\n", hostname];
		enet_address_set_host(ad, hostname.UTF8String);
		if (ad->host == ENET_HOST_ANY)
			return;
	}
	if (mssock != ENET_SOCKET_NULL)
		enet_socket_destroy(mssock);
	mssock = enet_socket_create(ENET_SOCKET_TYPE_STREAM, NULL);
	if (mssock == ENET_SOCKET_NULL) {
		printf("could not open socket\n");
		return;
	}
	if (enet_socket_connect(mssock, ad) < 0) {
		printf("could not connect\n");
		return;
	}
	ENetBuffer buf;
	OFString *httpget = [OFString stringWithFormat:
	    @"GET %@ HTTP/1.0\n"
	    @"Host: %@\n"
	    @"Referer: %@\n"
	    @"User-Agent: %@\n\n",
	    req, hostname, ref, agent];
	buf.data = (void *)httpget.UTF8String;
	buf.dataLength = httpget.UTF8StringLength;
	[OFStdOut writeFormat: @"sending request to %@...\n", hostname];
	enet_socket_send(mssock, NULL, &buf, 1);
}

static void
httpgetrecieve(ENetBuffer *buf)
{
	if (mssock == ENET_SOCKET_NULL)
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
static ENetBuffer masterb;

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







|
|







74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
static ENetBuffer masterb;

static void
updatemasterserver(int seconds)
{
	// send alive signal to masterserver every hour of uptime
	if (seconds > updmaster) {
		OFString *path = [OFString stringWithFormat:
		    @"%@register.do?action=add", masterpath];
		httpgetsend(&masterserver, masterbase, path, @"cubeserver",
		    @"Cube Server");
		masterrep[0] = 0;
		masterb.data = masterrep;
		masterb.dataLength = MAXTRANS - 1;
		updmaster = seconds + 60 * 60;
	}
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
	if (busy && mssock == ENET_SOCKET_NULL)
		printf("masterserver reply: %s\n", stripheader(masterrep));
}

unsigned char *
retrieveservers(unsigned char *buf, int buflen)
{
	OFString *path =
	    [OFString stringWithFormat:@"%@retrieve.do?item=list", masterpath];
	httpgetsend(
	    &masterserver, masterbase, path, @"cubeserver", @"Cube Server");
	ENetBuffer eb;
	buf[0] = 0;
	eb.data = buf;
	eb.dataLength = buflen - 1;
	while (mssock != ENET_SOCKET_NULL)







|
|







97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
	if (busy && mssock == ENET_SOCKET_NULL)
		printf("masterserver reply: %s\n", stripheader(masterrep));
}

unsigned char *
retrieveservers(unsigned char *buf, int buflen)
{
	OFString *path = [OFString stringWithFormat:
	    @"%@retrieve.do?item=list", masterpath];
	httpgetsend(
	    &masterserver, masterbase, path, @"cubeserver", @"Cube Server");
	ENetBuffer eb;
	buf[0] = 0;
	eb.data = buf;
	eb.dataLength = buflen - 1;
	while (mssock != ENET_SOCKET_NULL)
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160

161
162
163
164
165
166
167
168
169
170
		if (len < 0)
			return;
		p = &pong[len];
		putint(&p, PROTOCOL_VERSION);
		putint(&p, mode);
		putint(&p, numplayers);
		putint(&p, minremain);
		OFString *mname = [OFString stringWithFormat:@"%@%@",
		    (isfull ? @"[FULL] " : @""), smapname];
		sendstring(mname, &p);
		sendstring(serverdesc, &p);
		buf.dataLength = p - pong;
		enet_socket_send(pongsock, &addr, &buf, 1);
	}
}

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

	serverdesc = sdesc;

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







|
|















|
>










137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
		if (len < 0)
			return;
		p = &pong[len];
		putint(&p, PROTOCOL_VERSION);
		putint(&p, mode);
		putint(&p, numplayers);
		putint(&p, minremain);
		OFString *mname = [OFString stringWithFormat:
		    @"%@%@", (isfull ? @"[FULL] " : @""), smapname];
		sendstring(mname, &p);
		sendstring(serverdesc, &p);
		buf.dataLength = p - pong;
		enet_socket_send(pongsock, &addr, &buf, 1);
	}
}

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

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

Modified src/serverutil.m from [25b79fa395] to [da87a5e2ce].

142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
void
fatal(OFConstantString *s, ...)
{
	cleanupserver();

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

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

	exit(1);
}

void *
alloc(int s)
{







|


|







142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
void
fatal(OFConstantString *s, ...)
{
	cleanupserver();

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

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

	exit(1);
}

void *
alloc(int s)
{

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

45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
		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()
{







|






|
|
|
|

|















|









|

|
|

|







45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
		conoutf(@"sound init failed (SDL_mixer): %s",
		    (size_t)Mix_GetError());
		nosound = true;
	}
	Mix_AllocateChannels(MAXCHAN);
}

COMMAND(music, ARG_1STR, (^ (OFString *name) {
	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()
{
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
		return;

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

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

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

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

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

	if (loc)
		newsoundloc(chan, loc);

	updatechanvol(chan, loc);
}

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







|

|
|

|



















|


189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
		return;

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

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

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

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

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

	if (loc)
		newsoundloc(chan, loc);

	updatechanvol(chan, loc);
}

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

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

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

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







|







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

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

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

// create random spread of rays for the shotgun
void
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
playerincrosshair()
{
	if (demoplayback)
		return NULL;

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

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

	return nil;







|







113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
playerincrosshair()
{
	if (demoplayback)
		return NULL;

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

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

	return nil;
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179

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







|
|
|







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

static void
hit(int target, int damage, __kindof DynamicEntity *d, DynamicEntity *at)
{
	OFVector3D o = d.origin;
	if (d == Player.player1)
		selfdamage(damage, (at == Player.player1) ? -1 : -2, at);
	else if ([d isKindOfClass: Monster.class])
		[d incurDamage: damage fromEntity: at];
	else if ([d isKindOfClass: Player.class]) {
		addmsg(1, 4, SV_DAMAGE, target, damage,
		    ((Player *)d).lifeSequence);
		playsound(S_PAIN1 + rnd(5), &o);
	}
	particle_splash(3, damage, 1000, o);
	demodamage(damage, o);
}
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
		dodynlight(vold, v, 0, 0, p.owner);

		if (!p.local)
			return;

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

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

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

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

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

static inline void







|
|









|
|







221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
		dodynlight(vold, v, 0, 0, p.owner);

		if (!p.local)
			return;

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

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

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

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

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

static inline void
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
	for (size_t i = 0; i < MAXPROJ; i++) {
		Projectile *p = projs[i];

		if (!p.inuse)
			continue;

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







|







264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
	for (size_t i = 0; i < MAXPROJ; i++) {
		Projectile *p = projs[i];

		if (!p.inuse)
			continue;

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

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

	case GUN_RIFLE:
		particle_splash(0, 50, 200, to);
		particle_trail(1, 500, from, to);







|







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

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

	case GUN_RIFLE:
		particle_splash(0, 50, 200, to);
		particle_trail(1, 500, from, to);
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
    DynamicEntity *o, OFVector3D from, OFVector3D to, DynamicEntity *d, int i)
{
	if (o.state != CS_ALIVE)
		return;
	int qdam = guns[d.gunSelect].damage;
	if (d.quadMillis)
		qdam *= 4;
	if ([d isKindOfClass:Monster.class])
		qdam /= MONSTERDAMAGEFACTOR;
	if (d.gunSelect == GUN_SG) {
		int damage = 0;
		for (int r = 0; r < SGRAYS; r++)
			if (intersect(o, from, sg[r]))
				damage += qdam;
		if (damage)







|







364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
    DynamicEntity *o, OFVector3D from, OFVector3D to, DynamicEntity *d, int i)
{
	if (o.state != CS_ALIVE)
		return;
	int qdam = guns[d.gunSelect].damage;
	if (d.quadMillis)
		qdam *= 4;
	if ([d isKindOfClass: Monster.class])
		qdam /= MONSTERDAMAGEFACTOR;
	if (d.gunSelect == GUN_SG) {
		int damage = 0;
		for (int r = 0; r < SGRAYS; r++)
			if (intersect(o, from, sg[r]))
				damage += qdam;
		if (damage)
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435

436
437
438
439
440
441
442
443
444
445
446
	}
	if (d.gunSelect == GUN_SG)
		createrays(from, to);

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

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

	[players enumerateObjectsUsingBlock:^(id player, size_t i, bool *stop) {

		if (player != [OFNull null])
			raydamage(player, from, to, d, i);
	}];

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

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







|








|
>








|


419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
	}
	if (d.gunSelect == GUN_SG)
		createrays(from, to);

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

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

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

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

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

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

62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103

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

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

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

	settag(tag, type);

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

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

	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







|
















|
|





|


|







62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103

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

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

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

	settag(tag, type);

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

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

	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
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
closestent() // used for delent and edit mode ent display
{
	if (noteditmode())
		return -1;

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

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

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

COMMAND(entproperty, ARG_2INT, ^(int prop, int amount) {
	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;
	conoutf(@"unknown entity type \"%@\"", what);
	return NOTUSED;
}

Entity *
newentity(int x, int y, int z, OFString *what, int v1, int v2, int v3, int v4)







|














|



















|

















|







289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
closestent() // used for delent and edit mode ent display
{
	if (noteditmode())
		return -1;

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

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

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

COMMAND(entproperty, ARG_2INT, ^ (int prop, int amount) {
	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;
	conoutf(@"unknown entity type \"%@\"", what);
	return NOTUSED;
}

Entity *
newentity(int x, int y, int z, OFString *what, int v1, int v2, int v3, int v4)
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
	case PLAYERSTART:
		e.attr1 = (int)Player.player1.yaw;
		break;
	}
	addmsg(1, 10, SV_EDITENT, ents.count, type, e.x, e.y, e.z, e.attr1,
	    e.attr2, e.attr3, e.attr4);

	[ents addObject:e];

	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)







|







|







385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
	case PLAYERSTART:
		e.attr1 = (int)Player.player1.yaw;
		break;
	}
	addmsg(1, 10, SV_EDITENT, ents.count, type, e.x, e.y, e.z, e.attr1,
	    e.attr2, e.attr3, e.attr4);

	[ents addObject: e];

	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)
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
{
	int n = c * intens / 100;
	if (n > 255)
		n = 255;
	return n;
}

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

		e.attr1 = e.attr1 * f / 100;
		if (e.attr1 < 2)
			e.attr1 = 2;







|







416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
{
	int n = c * intens / 100;
	if (n > 255)
		n = 255;
	return n;
}

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

		e.attr1 = e.attr1 * f / 100;
		if (e.attr1 < 2)
			e.attr1 = 2;
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
	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();
})







|



|



|


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 [d379fda5be] to [f35a650122].

11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
	unsigned char type; // type is one of the above
	unsigned char attr2, attr3, attr4;
};

void
backup(OFString *name, OFString *backupname)
{
	[OFFileManager.defaultManager removeItemAtPath:backupname];
	[OFFileManager.defaultManager moveItemAtPath:name toPath:backupname];
}

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

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

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

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

// the optimize routines below are here to reduce the detrimental effects of
// messy mapping by setting certain properties (vdeltas and textures) to
// neighbouring values wherever there is no visible difference. This allows the
// mipmapper to generate more efficient mips. the reason it is done on save is
// to reduce the amount spend in the mipmapper (as that is done in realtime).







|
|








|
|



|
|





|
|
|
|
|
|
|
|







11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
	unsigned char type; // type is one of the above
	unsigned char attr2, attr3, attr4;
};

void
backup(OFString *name, OFString *backupname)
{
	[OFFileManager.defaultManager removeItemAtPath: backupname];
	[OFFileManager.defaultManager moveItemAtPath: name toPath: backupname];
}

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

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

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

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

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

void
writemap(OFString *mname, int msize, unsigned char *mdata)
{
	setnames(mname);
	backup(cgzname, bakname);

	FILE *f = fopen([cgzname cStringWithEncoding:OFLocale.encoding], "wb");
	if (!f) {
		conoutf(@"could not write map to %@", cgzname);
		return;
	}
	fwrite(mdata, 1, msize, f);
	fclose(f);
	conoutf(@"wrote map %@ as file %@", mname, cgzname);
}

OFData *
readmap(OFString *mname)
{
	setnames(mname);
	return [OFData dataWithContentsOfFile:mname];
}

// save map as .cgz file. uses 2 layers of compression: first does simple
// run-length encoding and leaves out data for certain kinds of cubes, then zlib
// removes the last bits of redundancy. Both passes contribute greatly to the
// miniscule map sizes.

void
save_world(OFString *mname)
{
	resettagareas(); // wouldn't be able to reproduce tagged areas
	                 // otherwise
	voptimize();
	toptimize();
	if (mname.length == 0)
		mname = getclientmap();
	setnames(mname);
	backup(cgzname, bakname);
	gzFile f =
	    gzopen([cgzname cStringWithEncoding:OFLocale.encoding], "wb9");
	if (!f) {
		conoutf(@"could not write map to %@", cgzname);
		return;
	}
	hdr.version = MAPVERSION;
	hdr.numents = 0;
	for (Entity *e in ents)







|













|


















|
|







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

void
writemap(OFString *mname, int msize, unsigned char *mdata)
{
	setnames(mname);
	backup(cgzname, bakname);

	FILE *f = fopen([cgzname cStringWithEncoding: OFLocale.encoding], "wb");
	if (!f) {
		conoutf(@"could not write map to %@", cgzname);
		return;
	}
	fwrite(mdata, 1, msize, f);
	fclose(f);
	conoutf(@"wrote map %@ as file %@", mname, cgzname);
}

OFData *
readmap(OFString *mname)
{
	setnames(mname);
	return [OFData dataWithContentsOfFile: mname];
}

// save map as .cgz file. uses 2 layers of compression: first does simple
// run-length encoding and leaves out data for certain kinds of cubes, then zlib
// removes the last bits of redundancy. Both passes contribute greatly to the
// miniscule map sizes.

void
save_world(OFString *mname)
{
	resettagareas(); // wouldn't be able to reproduce tagged areas
	                 // otherwise
	voptimize();
	toptimize();
	if (mname.length == 0)
		mname = getclientmap();
	setnames(mname);
	backup(cgzname, bakname);
	gzFile f = gzopen([cgzname cStringWithEncoding: OFLocale.encoding],
	    "wb9");
	if (!f) {
		conoutf(@"could not write map to %@", cgzname);
		return;
	}
	hdr.version = MAPVERSION;
	hdr.numents = 0;
	for (Entity *e in ents)
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
	}
	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();
	pruneundos(0);
	setnames(mname);
	gzFile f =
	    gzopen([cgzname cStringWithEncoding:OFLocale.encoding], "rb9");
	if (!f) {
		conoutf(@"could not read map %@", cgzname);
		return;
	}
	gzread(f, &hdr, sizeof(struct header) - sizeof(int) * 16);
	endianswap(&hdr.version, sizeof(int), 4);
	if (strncmp(hdr.head, "CUBE", 4) != 0)







|











|
|







254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
	}
	spurge;
	gzclose(f);
	conoutf(@"wrote map file %@", cgzname);
	settagareas();
}

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

void
load_world(OFString *mname) // still supports all map formats that have existed
                            // since the earliest cube betas!
{
	stopifrecording();
	cleardlights();
	pruneundos(0);
	setnames(mname);
	gzFile f = gzopen([cgzname cStringWithEncoding: OFLocale.encoding],
	    "rb9");
	if (!f) {
		conoutf(@"could not read map %@", cgzname);
		return;
	}
	gzread(f, &hdr, sizeof(struct header) - sizeof(int) * 16);
	endianswap(&hdr.version, sizeof(int), 4);
	if (strncmp(hdr.head, "CUBE", 4) != 0)
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
		e.y = tmp.y;
		e.z = tmp.z;
		e.attr1 = tmp.attr1;
		e.type = tmp.type;
		e.attr2 = tmp.attr2;
		e.attr3 = tmp.attr3;
		e.attr4 = tmp.attr4;
		[ents addObject:e];

		if (e.type == LIGHT) {
			if (!e.attr2)
				e.attr2 = 255; // needed for MAPVERSION<=2
			if (e.attr1 > 32)
				e.attr1 = 32; // 12_03 and below
		}







|







301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
		e.y = tmp.y;
		e.z = tmp.z;
		e.attr1 = tmp.attr1;
		e.type = tmp.type;
		e.attr2 = tmp.attr2;
		e.attr3 = tmp.attr3;
		e.attr4 = tmp.attr4;
		[ents addObject: e];

		if (e.type == LIGHT) {
			if (!e.attr2)
				e.attr2 = 255; // needed for MAPVERSION<=2
			if (e.attr1 > 32)
				e.attr1 = 32; // 12_03 and below
		}
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
			lookuptexture(i, &xs, &ys);
	conoutf(@"read map %@ (%d milliseconds)", cgzname,
	    SDL_GetTicks() - lastmillis);
	conoutf(@"%s", hdr.maptitle);
	startmap(mname);
	for (int l = 0; l < 256; l++) {
		// can this be done smarter?
		OFString *aliasname =
		    [OFString stringWithFormat:@"level_trigger_%d", l];
		if (identexists(aliasname))
			alias(aliasname, @"");
	}
	OFIRI *gameDataIRI = Cube.sharedInstance.gameDataIRI;
	execfile([gameDataIRI
	    IRIByAppendingPathComponent:@"data/default_map_settings.cfg"]);
	execfile([gameDataIRI IRIByAppendingPathComponent:pcfname]);
	execfile([gameDataIRI IRIByAppendingPathComponent:mcfname]);
}







|
|





|
|
|

392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
			lookuptexture(i, &xs, &ys);
	conoutf(@"read map %@ (%d milliseconds)", cgzname,
	    SDL_GetTicks() - lastmillis);
	conoutf(@"%s", hdr.maptitle);
	startmap(mname);
	for (int l = 0; l < 256; l++) {
		// can this be done smarter?
		OFString *aliasname = [OFString stringWithFormat:
		    @"level_trigger_%d", l];
		if (identexists(aliasname))
			alias(aliasname, @"");
	}
	OFIRI *gameDataIRI = Cube.sharedInstance.gameDataIRI;
	execfile([gameDataIRI
	    IRIByAppendingPathComponent: @"data/default_map_settings.cfg"]);
	execfile([gameDataIRI IRIByAppendingPathComponent: pcfname]);
	execfile([gameDataIRI IRIByAppendingPathComponent: mcfname]);
}

Modified src/worldlight.m from [279400f849] to [38a360a277].

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

void
dodynlight(OFVector3D vold, OFVector3D v, int reach, int strength,
    DynamicEntity *owner)
{
	if (!reach)
		reach = dynlight;
	if ([owner isKindOfClass:Monster.class])
		reach = reach / 2;
	if (!reach)
		return;
	if (v.x < 0 || v.y < 0 || v.x > ssize || v.y > ssize)
		return;

	int creach = reach + 16; // dependant on lightray random offsets!







|







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

void
dodynlight(OFVector3D vold, OFVector3D v, int reach, int strength,
    DynamicEntity *owner)
{
	if (!reach)
		reach = dynlight;
	if ([owner isKindOfClass: Monster.class])
		reach = reach / 2;
	if (!reach)
		return;
	if (v.x < 0 || v.y < 0 || v.x > ssize || v.y > ssize)
		return;

	int creach = reach + 16; // dependant on lightray random offsets!
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
	if (b.xs + b.x > ssize - 2)
		b.xs = ssize - 2 - b.x;
	if (b.ys + b.y > ssize - 2)
		b.ys = ssize - 2 - b.y;

	if (dlights == nil)
		dlights = [[OFMutableData alloc]
		    initWithItemSize:sizeof(struct block *)];

	// backup area before rendering in dynlight
	struct block *copy = blockcopy(&b);
	[dlights addItem:&copy];

	Entity *l = [Entity entity];
	l.x = v.x;
	l.y = v.y;
	l.z = v.z;
	l.attr1 = reach;
	l.type = LIGHT;







|



|







223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
	if (b.xs + b.x > ssize - 2)
		b.xs = ssize - 2 - b.x;
	if (b.ys + b.y > ssize - 2)
		b.ys = ssize - 2 - b.y;

	if (dlights == nil)
		dlights = [[OFMutableData alloc]
		    initWithItemSize: sizeof(struct block *)];

	// backup area before rendering in dynlight
	struct block *copy = blockcopy(&b);
	[dlights addItem: &copy];

	Entity *l = [Entity entity];
	l.x = v.x;
	l.y = v.y;
	l.z = v.z;
	l.attr1 = reach;
	l.type = LIGHT;

Modified src/worldocull.m from [201c3dd9c9] to [a7de71ad12].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// worldocull.cpp: occlusion map and occlusion test

#include "cube.h"

#import "Command.h"
#import "Player.h"

#define NUMRAYS 512

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

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

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

void













|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// worldocull.cpp: occlusion map and occlusion test

#include "cube.h"

#import "Command.h"
#import "Player.h"

#define NUMRAYS 512

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

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

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

void