Cube  Check-in [d2b3ff790f]

Overview
Comment:Convert dynent to a class
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: d2b3ff790fee10ef4150cdb17f1c979e7001a420b2629b153b4dbf4c0b489704
User & Date: js on 2025-03-09 18:57:42
Other Links: manifest | tags
Context
2025-03-09
21:33
Clean up serverbrowser.mm check-in: 4d3e209260 user: js tags: trunk
18:57
Convert dynent to a class check-in: d2b3ff790f user: js tags: trunk
11:24
Migrate projectile to a class check-in: d3b4b2d476 user: js tags: trunk
Changes

Modified src/Cube.mm from [909022edb6] to [874b037f55].

1
2
3


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



+
+







// main.cpp: initialisation & main loop

#include "cube.h"

#import "DynamicEntity.h"

OF_APPLICATION_DELEGATE(Cube)

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

@implementation Cube
83
84
85
86
87
88
89


90
91
92
93
94
95
96
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100







+
+







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

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

	initPlayers();

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

	initclient();
	// never returns if dedicated
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
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







-
+








-
+

-
+








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

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

		computeraytable(player1->o.x, player1->o.y);
		computeraytable(player1.o.x, player1.o.y);
		readdepth(_width, _height);
		SDL_GL_SwapWindow(_window);
		extern void updatevol();
		updatevol();

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

		gl_drawframe(_width, _height, fps);

		SDL_Event event;
		int lasttype = 0, lastbut = 0;
		while (SDL_PollEvent(&event)) {

Added src/DynamicEntity.h version [0a25294c06].

Added src/DynamicEntity.mm version [412962c92b].

Modified src/Projectile.h from [28cf35ad48] to [f3375dc858].

1
2
3

4
5
6
7
8

9
10
11
1
2

3
4
5
6
7

8
9
10
11


-
+




-
+



#import <ObjFW/ObjFW.h>

typedef struct dynent dynent;
@class DynamicEntity;

@interface Projectile: OFObject
@property (nonatomic) OFVector3D o, to;
@property (nonatomic) float speed;
@property (nonatomic) dynent *owner;
@property (nonatomic) DynamicEntity *owner;
@property (nonatomic) int gun;
@property (nonatomic) bool inuse, local;
@end

Modified src/client.mm from [cdd53f348e] to [90c446262c].

1
2
3
4


5
6
7
8
9
10








11
12
13
14
15
16
17
1
2
3
4
5
6






7
8
9
10
11
12
13
14
15
16
17
18
19
20
21




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







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

#include "cube.h"

#import "DynamicEntity.h"

ENetHost *clienthost = NULL;
int connecting = 0;
int connattempts = 0;
int disconnecting = 0;
int clientnum = -1;   // our client id in the game
bool c2sinit = false; // whether we need to tell the other clients our stats
static ENetHost *clienthost = NULL;
static int connecting = 0;
static int connattempts = 0;
static int disconnecting = 0;
// our client id in the game
int clientnum = -1;
// whether we need to tell the other clients our stats
bool c2sinit = false;

int
getclientnum()
{
	return clientnum;
}

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







+
-
-
-
+
+
+
+
+
+







+
-
-
-
+
+
+
+
+
+







-
-
+
+







	enet_peer_throttle_configure(clienthost->peers,
	    throttle_interval * 1000, throttle_accel, throttle_decel);
}

void
newname(OFString *name)
{
	@autoreleasepool {
	c2sinit = false;
	@autoreleasepool {
		strn0cpy(player1->name, name.UTF8String, 16);
		c2sinit = false;

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

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

void
newteam(OFString *name)
{
	@autoreleasepool {
	c2sinit = false;
	@autoreleasepool {
		strn0cpy(player1->team, name.UTF8String, 5);
		c2sinit = false;

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

		player1.team = name;
	}
}
COMMANDN(team, newteam, ARG_1STR)

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

void
connects(OFString *servername)
{
	disconnect(1); // reset state
	addserver(servername);
133
134
135
136
137
138
139
140
141


142
143
144
145
146
147
148
145
146
147
148
149
150
151


152
153
154
155
156
157
158
159
160







-
-
+
+







		conoutf(@"disconnected");
	clienthost = NULL;
	connecting = 0;
	connattempts = 0;
	disconnecting = 0;
	clientnum = -1;
	c2sinit = false;
	player1->lifesequence = 0;
	loopv(players) zapdynent(players[i]);
	player1.lifesequence = 0;
	[players removeAllObjects];

	localdisconnect();

	if (!onlyclean) {
		stop();
		localconnect();
	}
164
165
166
167
168
169
170
171

172
173
174
175
176
177
178
176
177
178
179
180
181
182

183
184
185
186
187
188
189
190







-
+







	disconnect(0, !disconnecting);
}

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

void
echo(OFString *text)
{
	conoutf(@"%@", text);
267
268
269
270
271
272
273

274
275

276
277
278
279
280
281
282
279
280
281
282
283
284
285
286
287

288
289
290
291
292
293
294
295







+

-
+







	if (clienthost) {
		enet_host_broadcast(clienthost, 0, (ENetPacket *)packet);
		enet_host_flush(clienthost);
	} else
		localclienttoserver((ENetPacket *)packet);
}

// send update to the server
void
c2sinfo(dynent *d) // send update to the server
c2sinfo(DynamicEntity *d)
{
	@autoreleasepool {
		if (clientnum < 0)
			return; // we haven't had a welcome message from the
			        // server yet
		if (lastmillis - lastupdate < 40)
			return; // don't update faster than 25fps
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
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







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



-
-
-
+
+
+

















-
+
-
+



-
-
-
+
+
+







			putint(p, SV_MAPCHANGE);
			sendstring(toservermap, p);
			toservermap = @"";
			putint(p, nextmode);
		} else {
			putint(p, SV_POS);
			putint(p, clientnum);
			putint(p,
			    (int)(d->o.x *
			        DMF)); // quantize coordinates to 1/16th
			               // of a cube, between 1 and 3 bytes
			putint(p, (int)(d->o.y * DMF));
			putint(p, (int)(d->o.z * DMF));
			putint(p, (int)(d->yaw * DAF));
			putint(p, (int)(d->pitch * DAF));
			putint(p, (int)(d->roll * DAF));
			putint(
			// quantize coordinates to 1/16th of a cube, between 1
			// and 3 bytes
			putint(p, (int)(d.o.x * DMF));
			putint(p, (int)(d.o.y * DMF));
			putint(p, (int)(d.o.z * DMF));
			putint(p, (int)(d.yaw * DAF));
			putint(p, (int)(d.pitch * DAF));
			putint(p, (int)(d.roll * DAF));
			    p, (int)(d->vel.x * DVF)); // quantize to 1/100,
			                               // almost always 1 byte
			putint(p, (int)(d->vel.y * DVF));
			putint(p, (int)(d->vel.z * DVF));
			// quantize to 1/100, almost always 1 byte
			putint(p, (int)(d.vel.x * DVF));
			putint(p, (int)(d.vel.y * DVF));
			putint(p, (int)(d.vel.z * DVF));
			// pack rest in 1 byte: strafe:2, move:2, onfloor:1,
			// state:3
			putint(p,
			    (d->strafe & 3) | ((d->move & 3) << 2) |
			        (((int)d->onfloor) << 4) |
			        ((editmode ? CS_EDITING : d->state) << 5));
			    (d.strafe & 3) | ((d.move & 3) << 2) |
			        (((int)d.onfloor) << 4) |
			        ((editmode ? CS_EDITING : d.state) << 5));

			if (senditemstoserver) {
				packet->flags = ENET_PACKET_FLAG_RELIABLE;
				putint(p, SV_ITEMLIST);
				if (!m_noitems)
					putitems(p);
				putint(p, -1);
				senditemstoserver = false;
				serveriteminitdone = true;
			}
			// player chat, not flood protected for now
			if (ctext.length > 0) {
				packet->flags = ENET_PACKET_FLAG_RELIABLE;
				putint(p, SV_TEXT);
				sendstring(ctext, p);
				ctext = @"";
			}
			if (!c2sinit) // tell other clients who I am
			// tell other clients who I am
			{
			if (!c2sinit) {
				packet->flags = ENET_PACKET_FLAG_RELIABLE;
				c2sinit = true;
				putint(p, SV_INITC2S);
				sendstring(@(player1->name), p);
				sendstring(@(player1->team), p);
				putint(p, player1->lifesequence);
				sendstring(player1.name, p);
				sendstring(player1.team, p);
				putint(p, player1.lifesequence);
			}
			for (OFData *msg in messages) {
				// send messages collected during the previous
				// frames
				if (*(int *)[msg itemAtIndex:1])
					packet->flags =
					    ENET_PACKET_FLAG_RELIABLE;

Modified src/clientextras.mm from [ccc9d61fe8] to [5e3b6ff5ad].

1
2
3


4
5
6
7
8
9
10
11
12
13
14
15
16

17

18
19
20
21

22
23

24
25
26
27
28
29
30
31
32
33


34
35
36
37
38
39
40
41
42
43
44
45
46

47
48

49
50

51
52

53
54

55
56

57
58

59
60
61
62

63
64

65
66
67
68
69
70
71
72


73
74
75
76
77
78
79
80
81
82
83









84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100

101
102
103
104
105
106
107
108






109
110
111
112
113
114
115
116
117
118
119
120
121


122
123
124

125
126
127
128
129
130
131

132
133
134
135


136
137
138
139
140
141
142
143
144


145
146
147
148
149
150
151
152
153
154
155


156

157
158
159


160

161
162
163
164

165
166
167
168
169
170
171
172
173
174
175
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

20
21
22
23

24
25

26
27
28
29
30
31
32
33
34


35
36
37
38
39
40
41
42
43
44
45
46
47
48

49
50

51
52

53
54

55
56

57
58

59
60

61
62
63
64

65
66

67
68
69
70
71
72
73


74
75
76
77
78
79
80
81
82




83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107

108
109
110






111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128

129
130
131
132

133
134



135


136




137
138
139
140
141
142
143
144
145


146
147
148
149
150
151
152
153
154
155
156
157
158
159
160

161
162
163
164
165
166

167
168
169
170

171

172
173

174
175
176
177
178
179
180



+
+













+
-
+



-
+

-
+








-
-
+
+












-
+

-
+

-
+

-
+

-
+

-
+

-
+



-
+

-
+






-
-
+
+







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
















-
+


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












-
+
+


-
+

-
-
-

-
-
+
-
-
-
-
+
+







-
-
+
+











+
+
-
+



+
+
-
+



-
+
-


-







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

#include "cube.h"

#import "DynamicEntity.h"

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

//              D    D    D    D'   D    D    D    D'   A   A'  P   P'  I   I'
//              R,  R'  E    L    J   J'
int frame[] = { 178, 184, 190, 137, 183, 189, 197, 164, 46, 51, 54, 32, 0, 0,
	40, 1, 162, 162, 67, 168 };
int range[] = { 6, 6, 8, 28, 1, 1, 1, 1, 8, 19, 4, 18, 40, 1, 6, 15, 1, 1, 1,
	1 };

void
renderclient(
renderclient(dynent *d, bool team, OFString *mdlname, bool hellpig, float scale)
    DynamicEntity *d, bool team, OFString *mdlname, bool hellpig, float scale)
{
	int n = 3;
	float speed = 100.0f;
	float mz = d->o.z - d->eyeheight + 1.55f * scale;
	float mz = d.o.z - d.eyeheight + 1.55f * scale;
	int basetime = -((intptr_t)d & 0xFFF);
	if (d->state == CS_DEAD) {
	if (d.state == CS_DEAD) {
		int r;
		if (hellpig) {
			n = 2;
			r = range[3];
		} else {
			n = (intptr_t)d % 3;
			r = range[n];
		}
		basetime = d->lastaction;
		int t = lastmillis - d->lastaction;
		basetime = d.lastaction;
		int t = lastmillis - d.lastaction;
		if (t < 0 || t > 20000)
			return;
		if (t > (r - 1) * 100) {
			n += 4;
			if (t > (r + 10) * 100) {
				t -= (r + 10) * 100;
				mz -= t * t / 10000000000.0f * t;
			}
		}
		if (mz < -1000)
			return;
		// mdl = (((int)d>>6)&1)+1;
		// mz = d->o.z-d->eyeheight+0.2f;
		// mz = d.o.z-d.eyeheight+0.2f;
		// scale = 1.2f;
	} else if (d->state == CS_EDITING) {
	} else if (d.state == CS_EDITING) {
		n = 16;
	} else if (d->state == CS_LAGGED) {
	} else if (d.state == CS_LAGGED) {
		n = 17;
	} else if (d->monsterstate == M_ATTACKING) {
	} else if (d.monsterstate == M_ATTACKING) {
		n = 8;
	} else if (d->monsterstate == M_PAIN) {
	} else if (d.monsterstate == M_PAIN) {
		n = 10;
	} else if ((!d->move && !d->strafe) || !d->moving) {
	} else if ((!d.move && !d.strafe) || !d.moving) {
		n = 12;
	} else if (!d->onfloor && d->timeinair > 100) {
	} else if (!d.onfloor && d.timeinair > 100) {
		n = 18;
	} else {
		n = 14;
		speed = 1200 / d->maxspeed * scale;
		speed = 1200 / d.maxspeed * scale;
		if (hellpig)
			speed = 300 / d->maxspeed;
			speed = 300 / d.maxspeed;
	}
	if (hellpig) {
		n++;
		scale *= 32;
		mz -= 1.9f;
	}
	rendermodel(mdlname, frame[n], range[n], 0, 1.5f, d->o.x, mz, d->o.y,
	    d->yaw + 90, d->pitch / 2, team, scale, speed, 0, basetime);
	rendermodel(mdlname, frame[n], range[n], 0, 1.5f, d.o.x, mz, d.o.y,
	    d.yaw + 90, d.pitch / 2, team, scale, speed, 0, basetime);
}

extern int democlientnum;

void
renderclients()
{
	dynent *d;
	loopv(players) if ((d = players[i]) &&
	    (!demoplayback || i != democlientnum)) renderclient(d,
	    isteam(player1->team, d->team), @"monster/ogro", false, 1.0f);
	size_t i = 0;
	for (id player in players) {
		if (player != [OFNull null] &&
		    (!demoplayback || i != democlientnum))
			renderclient(player,
			    isteam(player1.team, [player team]),
			    @"monster/ogro", false, 1.0f);
		i++;
	}
}

// creation of scoreboard pseudo-menu

bool scoreson = false;

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

static OFMutableArray<OFString *> *scoreLines;

void
renderscore(dynent *d)
renderscore(DynamicEntity *d)
{
	@autoreleasepool {
		OFString *lag = [OFString stringWithFormat:@"%d", d->plag];
		OFString *name = [OFString stringWithFormat:@"(%s)", d->name];
		OFString *line = [OFString
		    stringWithFormat:@"%d\t%@\t%d\t%s\t%@", d->frags,
		    (d->state == CS_LAGGED ? @"LAG" : lag), d->ping, d->team,
		    (d->state == CS_DEAD ? name : @(d->name))];
		OFString *lag = [OFString stringWithFormat:@"%d", d.plag];
		OFString *name = [OFString stringWithFormat:@"(%@)", d.name];
		OFString *line =
		    [OFString stringWithFormat:@"%d\t%@\t%d\t%@\t%@", d.frags,
		              (d.state == CS_LAGGED ? @"LAG" : lag), d.ping,
		              d.team, (d.state == CS_DEAD ? name : d.name)];

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

		[scoreLines addObject:line];

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

static const int maxTeams = 4;
static OFString *teamName[maxTeams];
static int teamScore[maxTeams], teamsUsed;
static int teamScore[maxTeams];
static size_t teamsUsed;

void
addteamscore(dynent *d)
addteamscore(DynamicEntity *d)
{
	if (d == NULL)
		return;

	@autoreleasepool {
		OFString *team = @(d->team);

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

		if (teamsUsed == maxTeams)
			return;

		teamName[teamsUsed] = @(d->team);
		teamScore[teamsUsed++] = d->frags;
		teamName[teamsUsed] = d.team;
		teamScore[teamsUsed++] = d.frags;
	}
}

void
renderscores()
{
	if (!scoreson)
		return;
	[scoreLines removeAllObjects];
	if (!demoplayback)
		renderscore(player1);
	for (id player in players)
		if (player != [OFNull null])
	loopv(players) if (players[i]) renderscore(players[i]);
			renderscore(player);
	sortmenu();
	if (m_teammode) {
		teamsUsed = 0;
		for (id player in players)
			if (player != [OFNull null])
		loopv(players) addteamscore(players[i]);
				addteamscore(player);
		if (!demoplayback)
			addteamscore(player1);
		OFMutableString *teamScores = [[OFMutableString alloc] init];
		loopj(teamsUsed)
		for (size_t j = 0; j < teamsUsed; j++)
		{
			[teamScores appendFormat:@"[ %@: %d ]", teamName[j],
			            teamScore[j]];
		}
		menumanual(0, scoreLines.count, @"");
		@autoreleasepool {
			menumanual(0, scoreLines.count + 1, teamScores);
		}
	}
}

Modified src/clientgame.mm from [1b8431e6e6] to [5f10611541].

1
2
3


4
5
6
7
8
9
10
11
12
13
14
15
16






17
18



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

36
37
38
39
40
41
42
43







44
45

46
47

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













63
64
65


66
67
68


69
70

71

72
73
74


75
76
77

78
79
80
81
82
83
84
85

86
87
88




89
90
91

92
93

94
95
96
97
98
99





100
101
102
103
104


105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125



















126
127
128
129
130
131
132
133
134
135
136
137
138


139
140

141
142

143
144
145


146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165

166

167
168
169




170
171
172
173
174
175
176
177
178
179
180

181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196


197

198
199
200
201
202
203
204
205








206
207
208




209
210
211
212
213
214
215
216


217
218
219
220
221
222
223
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24


25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

44
45







46
47
48
49
50
51
52
53
54
55
56

57
58
59













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


74
75
76


77
78
79
80
81

82
83


84
85
86
87

88
89
90
91
92
93
94
95

96



97
98
99
100

101

102
103

104
105





106
107
108
109
110

111
112


113
114





















115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145

146
147
148

149
150

151
152


153
154
155

156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172

173
174
175



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

190
191
192
193
194







195
196
197
198
199
200
201

202








203
204
205
206
207
208
209
210



211
212
213
214
215
216
217
218
219
220


221
222
223
224
225
226
227
228
229



+
+













+
+
+
+
+
+
-
-
+
+
+
















-
+

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


+

-
+


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

-
-
+
+

-
-
+
+


+
-
+

-
-
+
+


-
+







-
+
-
-
-
+
+
+
+
-

-
+

-
+

-
-
-
-
-
+
+
+
+
+
-


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












-
+
+

-
+

-
+

-
-
+
+

-

















-
+

+
-
-
-
+
+
+
+










-
+




-
-
-
-
-
-
-





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






-
-
+
+







// clientgame.cpp: core game related stuff

#include "cube.h"

#import "DynamicEntity.h"

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

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

bool intermission = false;

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

void
initPlayers()
{
dynent *player1 = newdynent(); // our client
dvector players;               // other clients
	player1 = newdynent();
	players = [[OFMutableArray alloc] init];
}

VARP(sensitivity, 0, 10, 10000);
VARP(sensitivityscale, 1, 1, 10000);
VARP(invmouse, 0, 0, 1);

int lastmillis = 0;
int curtime = 10;
OFString *clientmap;

OFString *
getclientmap()
{
	return clientmap;
}

void
resetmovement(dynent *d)
resetmovement(DynamicEntity *d)
{
	d->k_left = false;
	d->k_right = false;
	d->k_up = false;
	d->k_down = false;
	d->jumpnext = false;
	d->strafe = 0;
	d->move = 0;
	d.k_left = false;
	d.k_right = false;
	d.k_up = false;
	d.k_down = false;
	d.jumpnext = false;
	d.strafe = 0;
	d.move = 0;
}

// reset player state not persistent accross spawns
void
spawnstate(dynent *d) // reset player state not persistent accross spawns
spawnstate(DynamicEntity *d)
{
	resetmovement(d);
	d->vel.x = d->vel.y = d->vel.z = 0;
	d->onfloor = false;
	d->timeinair = 0;
	d->health = 100;
	d->armour = 50;
	d->armourtype = A_BLUE;
	d->quadmillis = 0;
	d->lastattackgun = d->gunselect = GUN_SG;
	d->gunwait = 0;
	d->attacking = false;
	d->lastaction = 0;
	loopi(NUMGUNS) d->ammo[i] = 0;
	d->ammo[GUN_FIST] = 1;
	d.vel = OFMakeVector3D(0, 0, 0);
	d.onfloor = false;
	d.timeinair = 0;
	d.health = 100;
	d.armour = 50;
	d.armourtype = A_BLUE;
	d.quadmillis = 0;
	d.lastattackgun = d.gunselect = GUN_SG;
	d.gunwait = 0;
	d.attacking = false;
	d.lastaction = 0;
	loopi(NUMGUNS) d.ammo[i] = 0;
	d.ammo[GUN_FIST] = 1;
	if (m_noitems) {
		d->gunselect = GUN_RIFLE;
		d->armour = 0;
		d.gunselect = GUN_RIFLE;
		d.armour = 0;
		if (m_noitemsrail) {
			d->health = 1;
			d->ammo[GUN_RIFLE] = 100;
			d.health = 1;
			d.ammo[GUN_RIFLE] = 100;
		} else {
			if (gamemode == 12) {
				// eihrul's secret "instafist" mode
				d->gunselect = GUN_FIST;
				d.gunselect = GUN_FIST;
				return;
			} // eihrul's secret "instafist" mode
			d->health = 256;
			}
			d.health = 256;
			if (m_tarena) {
				int gun1 = rnd(4) + 1;
				baseammo(d->gunselect = gun1);
				baseammo(d.gunselect = gun1);
				for (;;) {
					int gun2 = rnd(4) + 1;
					if (gun1 != gun2) {
						baseammo(gun2);
						break;
					}
				}
			} else if (m_arena) // insta arena
			} else if (m_arena) {
			{
				d->ammo[GUN_RIFLE] = 100;
			} else // efficiency
				// insta arena
				d.ammo[GUN_RIFLE] = 100;
			} else {
				// efficiency
			{
				loopi(4) baseammo(i + 1);
				d->gunselect = GUN_CG;
				d.gunselect = GUN_CG;
			}
			d->ammo[GUN_CG] /= 2;
			d.ammo[GUN_CG] /= 2;
		}
	} else {
		d->ammo[GUN_SG] = 5;
	}
}

	} else
		d.ammo[GUN_SG] = 5;
}

DynamicEntity *
dynent *
newdynent() // create a new blank player or monster
{
	dynent *d = (dynent *)OFAllocMemory(1, sizeof(dynent));
	d->o.x = 0;
	DynamicEntity *d = [[DynamicEntity alloc] init];
	d.o = OFMakeVector3D(0, 0, 0);
	d->o.y = 0;
	d->o.z = 0;
	d->yaw = 270;
	d->pitch = 0;
	d->roll = 0;
	d->maxspeed = 22;
	d->outsidemap = false;
	d->inwater = false;
	d->radius = 1.1f;
	d->eyeheight = 3.2f;
	d->aboveeye = 0.7f;
	d->frags = 0;
	d->plag = 0;
	d->ping = 0;
	d->lastupdate = lastmillis;
	d->enemy = NULL;
	d->monsterstate = 0;
	d->name[0] = d->team[0] = 0;
	d->blocked = false;
	d->lifesequence = 0;
	d->state = CS_ALIVE;
	d.yaw = 270;
	d.pitch = 0;
	d.roll = 0;
	d.maxspeed = 22;
	d.outsidemap = false;
	d.inwater = false;
	d.radius = 1.1f;
	d.eyeheight = 3.2f;
	d.aboveeye = 0.7f;
	d.frags = 0;
	d.plag = 0;
	d.ping = 0;
	d.lastupdate = lastmillis;
	d.enemy = NULL;
	d.monsterstate = 0;
	d.name = d.team = @"";
	d.blocked = false;
	d.lifesequence = 0;
	d.state = CS_ALIVE;
	spawnstate(d);
	return d;
}

void
respawnself()
{
	spawnplayer(player1);
	showscores(false);
}

void
arenacount(dynent *d, int &alive, int &dead, char *&lastteam, bool &oneteam)
arenacount(
    DynamicEntity *d, int &alive, int &dead, OFString **lastteam, bool &oneteam)
{
	if (d->state != CS_DEAD) {
	if (d.state != CS_DEAD) {
		alive++;
		if (lastteam && strcmp(lastteam, d->team))
		if (![*lastteam isEqual:d.team])
			oneteam = false;
		lastteam = d->team;
	} else {
		*lastteam = d.team;
	} else
		dead++;
	}
}

int arenarespawnwait = 0;
int arenadetectwait = 0;

void
arenarespawn()
{
	if (arenarespawnwait) {
		if (arenarespawnwait < lastmillis) {
			arenarespawnwait = 0;
			conoutf(@"new round starting... fight!");
			respawnself();
		}
	} else if (arenadetectwait == 0 || arenadetectwait < lastmillis) {
		arenadetectwait = 0;
		int alive = 0, dead = 0;
		char *lastteam = NULL;
		OFString *lastteam = nil;
		bool oneteam = true;
		for (id player in players)
		loopv(players) if (players[i])
		    arenacount(players[i], alive, dead, lastteam, oneteam);
		arenacount(player1, alive, dead, lastteam, oneteam);
			if (player != [OFNull null])
				arenacount(
				    player, alive, dead, &lastteam, oneteam);
		arenacount(player1, alive, dead, &lastteam, oneteam);
		if (dead > 0 && (alive <= 1 || (m_teammode && oneteam))) {
			conoutf(
			    @"arena round is over! next round in 5 seconds...");
			if (alive)
				conoutf(
				    @"team %s is last man standing", lastteam);
			else
				conoutf(@"everyone died!");
			arenarespawnwait = lastmillis + 5000;
			arenadetectwait = lastmillis + 10000;
			player1->roll = 0;
			player1.roll = 0;
		}
	}
}

void
zapdynent(dynent *&d)
{
	OFFreeMemory(d);
	d = NULL;
}

extern int democlientnum;

void
otherplayers()
{
	size_t i = 0;
	for (id player in players) {
	loopv(players) if (players[i])
		if (player != [OFNull null]) {
	{
		const int lagtime = lastmillis - players[i]->lastupdate;
		if (lagtime > 1000 && players[i]->state == CS_ALIVE) {
			players[i]->state = CS_LAGGED;
			continue;
		}
		if (lagtime && players[i]->state != CS_DEAD &&
		    (!demoplayback || i != democlientnum))
			const int lagtime = lastmillis - [player lastupdate];
			if (lagtime > 1000 && [player state] == CS_ALIVE) {
				[player setState:CS_LAGGED];
				i++;
				continue;
			}
			if (lagtime && [player state] != CS_DEAD &&
			    (!demoplayback || i != democlientnum))
			moveplayer(
			    players[i], 2, false); // use physics to extrapolate
			                           // player position
				// use physics to extrapolate player position
				moveplayer(player, 2, false);
		}
		i++;
	}
}

void
respawn()
{
	if (player1->state == CS_DEAD) {
		player1->attacking = false;
	if (player1.state == CS_DEAD) {
		player1.attacking = false;
		if (m_arena) {
			conoutf(@"waiting for new round to start...");
			return;
		}
		if (m_sp) {
			nextmode = gamemode;
			changemap(clientmap);
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
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







-
-
-
+
+
+


-
+





+
-
+
-







-
+





-
-
+
+


-
+
-

+
-
+
-






+

-
+




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


-
+




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














-
+






-
+















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





-
+


-
-
+
+







-
+

-
+



-
-
-
-
-
+
+
+
+
+


-
+

-
+




-
+

-
+



-
+

-
-
-
+
+
+

-
-
-
+
+
+

-
+
-




-
-
-
-
-
+
+
+
+
+


-
-
+
+

-







-
+








-
+




-
+

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







				                          // connected to server
			gets2c(); // do this first, so we have most accurate
			          // information when our player moves
		}
		otherplayers();
		if (!demoplayback) {
			monsterthink();
			if (player1->state == CS_DEAD) {
				if (lastmillis - player1->lastaction < 2000) {
					player1->move = player1->strafe = 0;
			if (player1.state == CS_DEAD) {
				if (lastmillis - player1.lastaction < 2000) {
					player1.move = player1.strafe = 0;
					moveplayer(player1, 10, false);
				} else if (!m_arena && !m_sp &&
				    lastmillis - player1->lastaction > 10000)
				    lastmillis - player1.lastaction > 10000)
					respawn();
			} else if (!intermission) {
				moveplayer(player1, 20, true);
				checkitems();
			}
			// do this last, to reduce the effective frame lag
			c2sinfo(player1); // do this last, to reduce the
			c2sinfo(player1);
			                  // effective frame lag
		}
	}
	lastmillis = millis;
}

// brute force but effective way to find a free spawn spot in the map
void
entinmap(dynent *d)
entinmap(DynamicEntity *d)
{
	loopi(100) // try max 100 times
	{
		float dx = (rnd(21) - 10) / 10.0f * i; // increasing distance
		float dy = (rnd(21) - 10) / 10.0f * i;
		d->o.x += dx;
		d->o.y += dy;
		OFVector3D old = d.o;
		d.o = OFMakeVector3D(d.o.x + dx, d.o.y + dy, d.o.z);
		if (collide(d, true, 0, 0))
			return;
		d->o.x -= dx;
		d.o = old;
		d->o.y -= dy;
	}
	conoutf(
	conoutf(@"can't find entity spawn spot! (%d, %d)", (int)d->o.x,
	    @"can't find entity spawn spot! (%d, %d)", (int)d.o.x, (int)d.o.y);
	    (int)d->o.y);
	// leave ent at original pos, possibly stuck
}

int spawncycle = -1;
int fixspawn = 2;

// place at random spawn. also used by monsters!
void
spawnplayer(dynent *d) // place at random spawn. also used by monsters!
spawnplayer(DynamicEntity *d)
{
	int r = fixspawn-- > 0 ? 4 : rnd(10) + 1;
	loopi(r) spawncycle = findentity(PLAYERSTART, spawncycle + 1);
	if (spawncycle != -1) {
		d->o.x = ents[spawncycle].x;
		d->o.y = ents[spawncycle].y;
		d.o = OFMakeVector3D(
		    ents[spawncycle].x, ents[spawncycle].y, ents[spawncycle].z);
		d->o.z = ents[spawncycle].z;
		d->yaw = ents[spawncycle].attr1;
		d->pitch = 0;
		d->roll = 0;
	} else {
		d->o.x = d->o.y = (float)ssize / 2;
		d.yaw = ents[spawncycle].attr1;
		d.pitch = 0;
		d.roll = 0;
	} else
		d.o = OFMakeVector3D((float)ssize / 2, (float)ssize / 2, 4);
		d->o.z = 4;
	}
	entinmap(d);
	spawnstate(d);
	d->state = CS_ALIVE;
	d.state = CS_ALIVE;
}

// movement input code

#define dir(name, v, d, s, os)                                      \
	void name(bool isdown)                                      \
	{                                                           \
		player1->s = isdown;                                \
		player1->v = isdown ? d : (player1->os ? -(d) : 0); \
		player1->lastmove = lastmillis;                     \
#define dir(name, v, d, s, os)                                    \
	void name(bool isdown)                                    \
	{                                                         \
		player1.s = isdown;                               \
		player1.v = isdown ? d : (player1.os ? -(d) : 0); \
		player1.lastmove = lastmillis;                    \
	}

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

void
attack(bool on)
{
	if (intermission)
		return;
	if (editmode)
		editdrag(on);
	else if (player1->attacking = on)
	else if ((player1.attacking = on))
		respawn();
}

void
jumpn(bool on)
{
	if (!intermission && (player1->jumpnext = on))
	if (!intermission && (player1.jumpnext = on))
		respawn();
}

COMMAND(backward, ARG_DOWN)
COMMAND(forward, ARG_DOWN)
COMMAND(left, ARG_DOWN)
COMMAND(right, ARG_DOWN)
COMMANDN(jump, jumpn, ARG_DOWN)
COMMAND(attack, ARG_DOWN)
COMMAND(showscores, ARG_DOWN)

void
fixplayer1range()
{
	const float MAXPITCH = 90.0f;
	if (player1->pitch > MAXPITCH)
		player1->pitch = MAXPITCH;
	if (player1->pitch < -MAXPITCH)
		player1->pitch = -MAXPITCH;
	while (player1->yaw < 0.0f)
		player1->yaw += 360.0f;
	while (player1->yaw >= 360.0f)
		player1->yaw -= 360.0f;
	if (player1.pitch > MAXPITCH)
		player1.pitch = MAXPITCH;
	if (player1.pitch < -MAXPITCH)
		player1.pitch = -MAXPITCH;
	while (player1.yaw < 0.0f)
		player1.yaw += 360.0f;
	while (player1.yaw >= 360.0f)
		player1.yaw -= 360.0f;
}

void
mousemove(int dx, int dy)
{
	if (player1->state == CS_DEAD || intermission)
	if (player1.state == CS_DEAD || intermission)
		return;
	const float SENSF = 33.0f; // try match quake sens
	player1->yaw += (dx / SENSF) * (sensitivity / (float)sensitivityscale);
	player1->pitch -= (dy / SENSF) *
	player1.yaw += (dx / SENSF) * (sensitivity / (float)sensitivityscale);
	player1.pitch -= (dy / SENSF) *
	    (sensitivity / (float)sensitivityscale) * (invmouse ? -1 : 1);
	fixplayer1range();
}

// damage arriving from the network, monsters, yourself, all ends up here.

void
selfdamage(int damage, int actor, dynent *act)
selfdamage(int damage, int actor, DynamicEntity *act)
{
	if (player1->state != CS_ALIVE || editmode || intermission)
	if (player1.state != CS_ALIVE || editmode || intermission)
		return;
	damageblend(damage);
	demoblend(damage);
	int ad = damage * (player1->armourtype + 1) * 20 /
	    100; // let armour absorb when possible
	if (ad > player1->armour)
		ad = player1->armour;
	player1->armour -= ad;
	// let armour absorb when possible
	int ad = damage * (player1.armourtype + 1) * 20 / 100;
	if (ad > player1.armour)
		ad = player1.armour;
	player1.armour -= ad;
	damage -= ad;
	float droll = damage / 0.5f;
	player1->roll += player1->roll > 0
	player1.roll += player1.roll > 0
	    ? droll
	    : (player1->roll < 0
	    : (player1.roll < 0
	              ? -droll
	              : (rnd(2) ? droll
	                        : -droll)); // give player a kick depending
	                                    // on amount of damage
	if ((player1->health -= damage) <= 0) {
	if ((player1.health -= damage) <= 0) {
		if (actor == -2) {
			conoutf(@"you got killed by %s!", act->name);
			conoutf(@"you got killed by %@!", act.name);
		} else if (actor == -1) {
			actor = getclientnum();
			conoutf(@"you suicided!");
			addmsg(1, 2, SV_FRAGS, --player1->frags);
			addmsg(1, 2, SV_FRAGS, --player1.frags);
		} else {
			dynent *a = getclient(actor);
			if (a) {
				if (isteam(a->team, player1->team)) {
			DynamicEntity *a = getclient(actor);
			if (a != nil) {
				if (isteam(a.team, player1.team))
					conoutf(@"you got fragged by a "
					        @"teammate (%s)",
					    a->name);
				} else {
					        @"teammate (%@)",
					    a.name);
				else
					conoutf(
					    @"you got fragged by %s", a->name);
					    @"you got fragged by %@", a.name);
				}
			}
		}
		showscores(true);
		addmsg(1, 2, SV_DIED, actor);
		player1->lifesequence++;
		player1->attacking = false;
		player1->state = CS_DEAD;
		player1->pitch = 0;
		player1->roll = 60;
		player1.lifesequence++;
		player1.attacking = false;
		player1.state = CS_DEAD;
		player1.pitch = 0;
		player1.roll = 60;
		playsound(S_DIE1 + rnd(2));
		spawnstate(player1);
		player1->lastaction = lastmillis;
	} else {
		player1.lastaction = lastmillis;
	} else
		playsound(S_PAIN6);
	}
}

void
timeupdate(int timeremain)
{
	if (!timeremain) {
		intermission = true;
		player1->attacking = false;
		player1.attacking = false;
		conoutf(@"intermission:");
		conoutf(@"game has ended!");
		showscores(true);
	} else {
		conoutf(@"time remaining: %d minutes", timeremain);
	}
}

dynent *
DynamicEntity *
getclient(int cn) // ensure valid entity
{
	if (cn < 0 || cn >= MAXCLIENTS) {
		neterr(@"clientnum");
		return NULL;
		return nil;
	}
	if (players == nil)
		players = [[OFMutableArray alloc] init];
	while (cn >= players.length())
		players.add(NULL);
	return players[cn] ? players[cn] : (players[cn] = newdynent());
	while (cn >= players.count)
		[players addObject:[OFNull null]];
	return (players[cn] != [OFNull null] ? players[cn]
	                                     : (players[cn] = newdynent()));
}

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

void
initclient()
{
	clientmap = @"";
	initclientnet();
490
491
492
493
494
495
496
497
498




499
500
501
502
503
504
505
506
507
508
509
510
511
512


513
514
515
516
517
518
519
520
521
522
523







-
-
+
+
+
+







		conoutf(@"coop sp not supported yet");
	}
	sleepwait = 0;
	monsterclear();
	projreset();
	spawncycle = -1;
	spawnplayer(player1);
	player1->frags = 0;
	loopv(players) if (players[i]) players[i]->frags = 0;
	player1.frags = 0;
	for (id player in players)
		if (player != [OFNull null])
			[player setFrags:0];
	resetspawns();
	clientmap = name;
	if (editmode)
		toggleedit();
	setvar(@"gamespeed", 100);
	setvar(@"fog", 180);
	setvar(@"fogcolour", 0x8099B3);

Modified src/clients2c.mm from [e5e63c92dd] to [ef18048f8b].

1
2
3
4


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




+
+







// client processing of the incoming network stream

#include "cube.h"

#import "DynamicEntity.h"

extern int clientnum;
extern bool c2sinit, senditemstoserver;
extern OFString *toservermap;
extern OFString *clientpassword;

void
neterr(OFString *s)
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
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







-
+

-
-
-
-
-
+
+
+
+
+


-
+

+
+
-
+

+
-
+
+

-
+

-
-
+
+



+

-
+
-









-
+







}

// update the position of other clients in the game in our world
// don't care if he's in the scenery or other players,
// just don't overlap with our client

void
updatepos(dynent *d)
updatepos(DynamicEntity *d)
{
	const float r = player1->radius + d->radius;
	const float dx = player1->o.x - d->o.x;
	const float dy = player1->o.y - d->o.y;
	const float dz = player1->o.z - d->o.z;
	const float rz = player1->aboveeye + d->eyeheight;
	const float r = player1.radius + d.radius;
	const float dx = player1.o.x - d.o.x;
	const float dy = player1.o.y - d.o.y;
	const float dz = player1.o.z - d.o.z;
	const float rz = player1.aboveeye + d.eyeheight;
	const float fx = (float)fabs(dx), fy = (float)fabs(dy),
	            fz = (float)fabs(dz);
	if (fx < r && fy < r && fz < rz && d->state != CS_DEAD) {
	if (fx < r && fy < r && fz < rz && d.state != CS_DEAD) {
		if (fx < fy)
			// push aside
			d.o = OFMakeVector3D(d.o.x,
			d->o.y += dy < 0 ? r - fy : -(r - fy); // push aside
			    d.o.y + (dy < 0 ? r - fy : -(r - fy)), d.o.z);
		else
			d.o = OFMakeVector3D(
			d->o.x += dx < 0 ? r - fx : -(r - fx);
			    d.o.x + (dx < 0 ? r - fx : -(r - fx)), d.o.y,
			    d.o.z);
	}
	int lagtime = lastmillis - d->lastupdate;
	int lagtime = lastmillis - d.lastupdate;
	if (lagtime) {
		d->plag = (d->plag * 5 + lagtime) / 6;
		d->lastupdate = lastmillis;
		d.plag = (d.plag * 5 + lagtime) / 6;
		d.lastupdate = lastmillis;
	}
}

// processes any updates from the server
void
localservertoclient(
localservertoclient(uchar *buf, int len)
    uchar *buf, int len) // processes any updates from the server
{
	if (ENET_NET_TO_HOST_16(*(ushort *)buf) != len)
		neterr(@"packet length");
	incomingdemodata(buf, len);

	uchar *end = buf + len;
	uchar *p = buf + 2;
	char text[MAXTRANS];
	int cn = -1, type;
	dynent *d = NULL;
	DynamicEntity *d = nil;
	bool mapchanged = false;

	while (p < end)
		switch (type = getint(p)) {
		case SV_INITS2C: // welcome messsage from the server
		{
			cn = getint(p);
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
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







+
-
+
-


-
+

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

-
+

-
-
+
+

-
-
-
+
+
+





-
-
+
+
+

+



-
+







				}
			}
			if (getint(p) == 1)
				conoutf(@"server is FULL, disconnecting..");
			break;
		}

		case SV_POS: {
		case SV_POS: // position of another client
			// position of another client
		{
			cn = getint(p);
			d = getclient(cn);
			if (!d)
			if (d == nil)
				return;
			d->o.x = getint(p) / DMF;
			d->o.y = getint(p) / DMF;
			d.o = OFMakeVector3D(
			    getint(p) / DMF, getint(p) / DMF, getint(p) / DMF);
			d->o.z = getint(p) / DMF;
			d->yaw = getint(p) / DAF;
			d->pitch = getint(p) / DAF;
			d->roll = getint(p) / DAF;
			d->vel.x = getint(p) / DVF;
			d->vel.y = getint(p) / DVF;
			d.yaw = getint(p) / DAF;
			d.pitch = getint(p) / DAF;
			d.roll = getint(p) / DAF;
			d.vel = OFMakeVector3D(
			    getint(p) / DVF, getint(p) / DVF, getint(p) / DVF);
			d->vel.z = getint(p) / DVF;
			int f = getint(p);
			d->strafe = (f & 3) == 3 ? -1 : f & 3;
			d.strafe = (f & 3) == 3 ? -1 : f & 3;
			f >>= 2;
			d->move = (f & 3) == 3 ? -1 : f & 3;
			d->onfloor = (f >> 2) & 1;
			d.move = (f & 3) == 3 ? -1 : f & 3;
			d.onfloor = (f >> 2) & 1;
			int state = f >> 3;
			if (state == CS_DEAD && d->state != CS_DEAD)
				d->lastaction = lastmillis;
			d->state = state;
			if (state == CS_DEAD && d.state != CS_DEAD)
				d.lastaction = lastmillis;
			d.state = state;
			if (!demoplayback)
				updatepos(d);
			break;
		}

		case SV_SOUND:
			playsound(getint(p), &d->o);
		case SV_SOUND: {
			OFVector3D loc = d.o;
			playsound(getint(p), &loc);
			break;
		}

		case SV_TEXT:
			sgetstr();
			conoutf(@"%s:\f %s", d->name, text);
			conoutf(@"%@:\f %s", d.name, text);
			break;

		case SV_MAPCHANGE:
			sgetstr();
			@autoreleasepool {
				changemapserv(@(text), getint(p));
			}
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
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







-
-
+
+
-

-
+

-
-
-
+
+
+


-
-
+
+
+


-
+

-
-
+
+





-
+

-
-
-
+
+
+







			    stringWithFormat:@"nextmap_%@", getclientmap()];
			OFString *map =
			    getalias(nextmapalias); // look up map in the cycle
			changemap(map != nil ? map : getclientmap());
			break;
		}

		case SV_INITC2S: // another client either connected or changed
		                 // name/team
		// another client either connected or changed name/team
		case SV_INITC2S: {
		{
			sgetstr();
			if (d->name[0]) {
			if (d.name.length > 0) {
				// already connected
				if (strcmp(d->name, text))
					conoutf(@"%s is now known as %s",
					    d->name, text);
				if (![d.name isEqual:@(text)])
					conoutf(@"%@ is now known as %s",
					    d.name, text);
			} else {
				// new client
				c2sinit =
				    false; // send new players my info again

				// send new players my info again
				c2sinit = false;
				conoutf(@"connected: %s", text);
			}
			strcpy_s(d->name, text);
			d.name = @(text);
			sgetstr();
			strcpy_s(d->team, text);
			d->lifesequence = getint(p);
			d.team = @(text);
			d.lifesequence = getint(p);
			break;
		}

		case SV_CDIS:
			cn = getint(p);
			if (!(d = getclient(cn)))
			if ((d = getclient(cn)) == nil)
				break;
			conoutf(@"player %s disconnected",
			    d->name[0] ? d->name : "[incompatible client]");
			zapdynent(players[cn]);
			conoutf(@"player %@ disconnected",
			    d.name.length ? d.name : @"[incompatible client]");
			players[cn] = [OFNull null];
			break;

		case SV_SHOT: {
			int gun = getint(p);
			OFVector3D s, e;
			s.x = getint(p) / DMF;
			s.y = getint(p) / DMF;
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
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







-
+

-
-
+
+
+
-
+






-
+


-
+

-
-
+
+


-
+

-
+
+

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




-
+







		}

		case SV_DAMAGE: {
			int target = getint(p);
			int damage = getint(p);
			int ls = getint(p);
			if (target == clientnum) {
				if (ls == player1->lifesequence)
				if (ls == player1.lifesequence)
					selfdamage(damage, cn, d);
			} else
				playsound(
			} else {
				OFVector3D loc = getclient(target).o;
				playsound(S_PAIN1 + rnd(5), &loc);
				    S_PAIN1 + rnd(5), &getclient(target)->o);
			}
			break;
		}

		case SV_DIED: {
			int actor = getint(p);
			if (actor == cn) {
				conoutf(@"%s suicided", d->name);
				conoutf(@"%@ suicided", d.name);
			} else if (actor == clientnum) {
				int frags;
				if (isteam(player1->team, d->team)) {
				if (isteam(player1.team, d.team)) {
					frags = -1;
					conoutf(@"you fragged a teammate (%s)",
					    d->name);
					conoutf(@"you fragged a teammate (%@)",
					    d.name);
				} else {
					frags = 1;
					conoutf(@"you fragged %s", d->name);
					conoutf(@"you fragged %@", d.name);
				}
				addmsg(1, 2, SV_FRAGS, player1->frags += frags);
				addmsg(
				    1, 2, SV_FRAGS, (player1.frags += frags));
			} else {
				dynent *a = getclient(actor);
				if (a) {
					if (isteam(a->team, d->name)) {
						conoutf(@"%s fragged his "
						        @"teammate (%s)",
						    a->name, d->name);
					} else {
						conoutf(@"%s fragged %s",
						    a->name, d->name);
					}
				}
				DynamicEntity *a = getclient(actor);
				if (a != nil) {
					if (isteam(a.team, d.name))
						conoutf(@"%@ fragged his "
						        @"teammate (%@)",
						    a.name, d.name);
					else
						conoutf(@"%@ fragged %@",
						    a.name, d.name);
				}
			}
			}
			playsound(S_DIE1 + rnd(2), &d->o);
			d->lifesequence++;
			OFVector3D loc = d.o;
			playsound(S_DIE1 + rnd(2), &loc);
			d.lifesequence++;
			break;
		}

		case SV_FRAGS:
			players[cn]->frags = getint(p);
			[players[cn] setFrags:getint(p)];
			break;

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

344
345
346
347
348
349
350
351
352


353
354
355
356
357

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


359
360
361
362
363
364

365
366
367
368
369
370
371
372







-
-
+
+




-
+








		case SV_PING:
			getint(p);
			break;

		case SV_PONG:
			addmsg(0, 2, SV_CLIENTPING,
			    player1->ping =
			        (player1->ping * 5 + lastmillis - getint(p)) /
			    player1.ping =
			        (player1.ping * 5 + lastmillis - getint(p)) /
			        6);
			break;

		case SV_CLIENTPING:
			players[cn]->ping = getint(p);
			[players[cn] setPing:getint(p)];
			break;

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

		case SV_TIMEUP:

Modified src/cube.h from [4e3991eb16] to [3f59a3114f].

1
2
3
4
5
6
7
8
9


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









+
+







// one big bad include file for the whole engine... nasty!

#import <ObjFW/ObjFW.h>

#define gamma gamma__
#include <SDL2/SDL.h>
#undef gamma

#include "tools.h"

@class DynamicEntity;

@interface Cube: OFObject <OFApplicationDelegate>
@property (class, readonly, nonatomic) Cube *sharedInstance;
@property (readonly, nonatomic) SDL_Window *window;
@property (readonly, nonatomic) OFIRI *gameDataIRI, *userDataIRI;
@property (nonatomic) bool repeatsKeys;
@property (nonatomic) int framesInMap;
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164


165
166
167
168
169
170
171
172
126
127
128
129
130
131
132


































133
134

135
136
137
138
139
140
141







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







	GUN_FIREBALL,
	GUN_ICEBALL,
	GUN_SLIMEBALL,
	GUN_BITE,
	NUMGUNS
};

struct dynent // players & monsters
{
	OFVector3D o, vel;      // origin, velocity
	float yaw, pitch, roll; // used as OFVector3D in one place
	float maxspeed;         // cubes per second, 24 for player
	bool outsidemap;        // from his eyes
	bool inwater;
	bool onfloor, jumpnext;
	int move, strafe;
	bool k_left, k_right, k_up, k_down; // see input code
	int timeinair;                      // used for fake gravity
	float radius, eyeheight, aboveeye;  // bounding box size
	int lastupdate, plag, ping;
	int lifesequence; // sequence id for each respawn, used in damage test
	int state;        // one of CS_* below
	int frags;
	int health, armour, armourtype, quadmillis;
	int gunselect, gunwait;
	int lastaction, lastattackgun, lastmove;
	bool attacking;
	int ammo[NUMGUNS];
	int monsterstate;     // one of M_* below, M_NONE means human
	int mtype;            // see monster.cpp
	dynent *enemy;        // monster wants to kill this entity
	float targetyaw;      // monster wants to look in this direction
	bool blocked, moving; // used by physics to signal ai
	int trigger; // millis at which transition to another monsterstate takes
	             // place
	OFVector3D attacktarget; // delayed attacks
	int anger;               // how many times already hit by fellow monster
	string name, team;
};

#define SAVEGAMEVERSION \
// bump if dynent/netprotocol changes or any other savegame/demo data
#define SAVEGAMEVERSION 4
	4 // bump if dynent/netprotocol changes or any other savegame/demo data

enum { A_BLUE, A_GREEN, A_YELLOW }; // armour types... take 20/40/60 % off
enum {
	M_NONE = 0,
	M_SEARCH,
	M_HOME,
	M_ATTACKING,
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
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







-
-







-
-
-
+
+
+
+







// vertex array format

struct vertex {
	float u, v, x, y, z;
	uchar r, g, b, a;
};

typedef vector<dynent *> dvector;

// globals ooh naughty

extern sqr *world,
    *wmip[];       // map data, the mips are sequential 2D arrays in memory
extern header hdr; // current map header
extern int sfactor, ssize;     // ssize = 2^sfactor
extern int cubicsize, mipsize; // cubicsize = ssize^2
extern dynent
    *player1; // special client ent that receives input and acts as camera
extern dvector players; // all the other clients (in multiplayer)
// special client ent that receives input and acts as camera
extern DynamicEntity *player1;
// all the other clients (in multiplayer)
extern OFMutableArray *players;
extern bool editmode;
extern vector<entity> ents; // map entities
extern OFVector3D worldpos; // current target of the crosshair in the world
extern int lastmillis;      // last time
extern int curtime;         // current frame time
extern int gamemode, nextmode;
extern int xtraverts;
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
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







-
-
-
-
-
+
+
+
+
+
+

-
-
+
+
-
-
-
+
+
+
+

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







#define PIXELTAB (VIRTW / 12)

#define PI (3.1415927f)
#define PI2 (2 * PI)

// simplistic vector ops
#define dotprod(u, v) ((u).x * (v).x + (u).y * (v).y + (u).z * (v).z)
#define vmul(u, f)            \
	{                     \
		(u).x *= (f); \
		(u).y *= (f); \
		(u).z *= (f); \
#define vmul(u, f)                                                   \
	{                                                            \
		OFVector3D tmp_ = u;                                 \
		float tmp2_ = f;                                     \
		u = OFMakeVector3D(                                  \
		    tmp_.x * tmp2_, tmp_.y * tmp2_, tmp_.z * tmp2_); \
	}
#define vdiv(u, f)            \
	{                     \
#define vdiv(u, f)                                                   \
	{                                                            \
		(u).x /= (f); \
		(u).y /= (f); \
		(u).z /= (f); \
		OFVector3D tmp_ = u;                                 \
		float tmp2_ = f;                                     \
		u = OFMakeVector3D(                                  \
		    tmp_.x / tmp2_, tmp_.y / tmp2_, tmp_.z / tmp2_); \
	}
#define vadd(u, v)              \
	{                       \
		(u).x += (v).x; \
#define vadd(u, v)                                                   \
	{                                                            \
		OFVector3D tmp_ = u;                                 \
		u = OFMakeVector3D(                                  \
		    tmp_.x + (v).x, tmp_.y + (v).y, tmp_.z + (v).z); \
		(u).y += (v).y; \
		(u).z += (v).z; \
	};
#define vsub(u, v)              \
	{                       \
		(u).x -= (v).x; \
	}
#define vsub(u, v)                                                   \
	{                                                            \
		OFVector3D tmp_ = u;                                 \
		u = OFMakeVector3D(                                  \
		    tmp_.x - (v).x, tmp_.y - (v).y, tmp_.z - (v).z); \
		(u).y -= (v).y; \
		(u).z -= (v).z; \
	};
	}
#define vdist(d, v, e, s) \
	OFVector3D v = s; \
	vsub(v, e);       \
	float d = (float)sqrt(dotprod(v, v));
#define vreject(v, u, max)                                 \
	((v).x > (u).x + (max) || (v).x < (u).x - (max) || \
	    (v).y > (u).y + (max) || (v).y < (u).y - (max))
366
367
368
369
370
371
372
373

374
375
376
377
378
379
380
336
337
338
339
340
341
342

343
344
345
346
347
348
349
350







-
+







#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 && strcmp(a, b) == 0)
#define isteam(a, b) (m_teammode && [a isEqual:b])

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

Modified src/editing.mm from [0154fefe79] to [ecc3874fd1].

1
2
3
4
5


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





+
+







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

#include "cube.h"

#import "DynamicEntity.h"

bool editmode = false;

// the current selection, used by almost all editing commands
// invariant: all code assumes that these are kept inside MINBORD distance of
// the edge of the map

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







-
+










-
+







sqr rtex;

VAR(editing, 0, 0, 1);

void
toggleedit()
{
	if (player1->state == CS_DEAD)
	if (player1.state == CS_DEAD)
		return; // do not allow dead players to edit to avoid state
		        // confusion
	if (!editmode && !allowedittoggle())
		return; // not in most multiplayer modes
	if (!(editmode = !editmode)) {
		settagareas();     // reset triggers to allow quick playtesting
		entinmap(player1); // find spawn closest to current floating pos
	} else {
		resettagareas(); // clear trigger areas to allow them to be
		                 // edited
		player1->health = 100;
		player1.health = 100;
		if (m_classicsp)
			monsterclear(); // all monsters back at their spawns for
			                // editing
		projreset();
	}
	Cube.sharedInstance.repeatsKeys = editmode;
	selset = false;
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
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







-
+















-
-
+
+







	    ? (s->type == FHF ? s->floor - t->vdelta / 4.0f : (float)s->floor)
	    : (s->type == CHF ? s->ceil + t->vdelta / 4.0f : (float)s->ceil);
}

void
cursorupdate() // called every frame from hud
{
	flrceil = ((int)(player1->pitch >= 0)) * 2;
	flrceil = ((int)(player1.pitch >= 0)) * 2;

	volatile float x =
	    worldpos.x; // volatile needed to prevent msvc7 optimizer bug?
	volatile float y = worldpos.y;
	volatile float z = worldpos.z;

	cx = (int)x;
	cy = (int)y;

	if (OUTBORD(cx, cy))
		return;
	sqr *s = S(cx, cy);

	// selected wall
	if (fabs(sheight(s, s, z) - z) > 1) {
		x += x > player1->o.x ? 0.5f : -0.5f; // find right wall cube
		y += y > player1->o.y ? 0.5f : -0.5f;
		x += x > player1.o.x ? 0.5f : -0.5f; // find right wall cube
		y += y > player1.o.y ? 0.5f : -0.5f;

		cx = (int)x;
		cy = (int)y;

		if (OUTBORD(cx, cy))
			return;
	}
318
319
320
321
322
323
324
325

326
327

328
329
330
331
332
333
334
320
321
322
323
324
325
326

327
328

329
330
331
332
333
334
335
336







-
+

-
+







			p[0] = t;
			curedittex[i] = -1;
		}
	}
}

void
editdrag(bool isdown)
editdrag(bool isDown)
{
	if (dragging = isdown) {
	if ((dragging = isDown)) {
		lastx = cx;
		lasty = cy;
		lasth = ch;
		selset = false;
		tofronttex();
	}
	makesel();
598
599
600
601
602
603
604
605

606
607
608
609
610
611
612
600
601
602
603
604
605
606

607
608
609
610
611
612
613
614







-
+







}

void
newent(OFString *what, OFString *a1, OFString *a2, OFString *a3, OFString *a4)
{
	EDITSEL;
	@autoreleasepool {
		newentity(sel.x, sel.y, (int)player1->o.z, what,
		newentity(sel.x, sel.y, (int)player1.o.z, what,
		    (int)[a1 longLongValueWithBase:0],
		    (int)[a2 longLongValueWithBase:0],
		    (int)[a3 longLongValueWithBase:0],
		    (int)[a4 longLongValueWithBase:0]);
	}
}

Modified src/entities.mm from [f745b5a428] to [0816ef168b].

1
2
3
4

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




+







// entities.cpp: map entity related functions (pickup etc.)

#include "cube.h"

#import "DynamicEntity.h"
#import "MapModelInfo.h"

vector<entity> ents;

static OFString *entmdlnames[] = {
	@"shells",
	@"bullets",
118
119
120
121
122
123
124
125

126
127
128
129
130
131
132


133
134
135
136
137
138
139

140
141
142
143

144
145
146
147

148
149
150

151
152
153

154
155
156

157
158
159

160
161
162

163
164
165
166
167


168
169
170
171
172


173
174
175
176

177
178
179
180
181
182
183
184
185

186

187
188



189
190

191
192
193

194
195
196

197
198

199
200
201
202
203
204
205
206
207
208
209
210

211
212
213
214


215

216
217
218
219
220
221
222
223
224

225
226

227
228




229
230
231
232
233

234
235
236

237
238
239

240
241
242

243
244
245

246
247
248

249
250
251
252
253

254
255

256
257
258
259

260
261
262
263

264
265
266
267
268
269
270
119
120
121
122
123
124
125

126
127
128
129
130
131


132
133
134
135
136
137
138
139
140
141
142
143
144

145
146
147
148

149
150
151

152
153
154

155
156
157

158
159
160

161
162
163

164
165
166
167


168
169
170
171
172


173
174
175
176
177

178
179
180
181
182
183
184
185
186

187
188
189


190
191
192


193



194
195
196
197
198
199

200
201
202
203
204
205
206
207
208
209
210
211

212




213
214

215
216
217
218
219
220
221
222
223

224
225
226
227


228
229
230
231

232
233
234

235
236
237

238
239
240

241
242
243

244
245
246

247
248
249

250
251
252
253
254

255
256

257
258
259
260

261
262
263
264

265
266
267
268
269
270
271
272







-
+





-
-
+
+







+



-
+



-
+


-
+


-
+


-
+


-
+


-
+



-
-
+
+



-
-
+
+



-
+








-
+

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



+

-
+











-
+
-
-
-
-
+
+
-
+








-
+


+
-
-
+
+
+
+
-



-
+


-
+


-
+


-
+


-
+


-
+




-
+

-
+



-
+



-
+







	{ 150, 150, S_ITEMARMOUR },
	{ 20000, 30000, S_ITEMPUP },
};

void
baseammo(int gun)
{
	player1->ammo[gun] = itemstats[gun - 1].add * 2;
	player1.ammo[gun] = itemstats[gun - 1].add * 2;
}

// these two functions are called when the server acknowledges that you really
// picked up the item (in multiplayer someone may grab it before you).

void
radditem(int i, int &v)
static int
radditem(int i, int v)
{
	itemstat &is = itemstats[ents[i].type - I_SHELLS];
	ents[i].spawned = false;
	v += is.add;
	if (v > is.max)
		v = is.max;
	playsoundc(is.sound);
	return v;
}

void
realpickup(int n, dynent *d)
realpickup(int n, DynamicEntity *d)
{
	switch (ents[n].type) {
	case I_SHELLS:
		radditem(n, d->ammo[1]);
		d.ammo[1] = radditem(n, d.ammo[1]);
		break;
	case I_BULLETS:
		radditem(n, d->ammo[2]);
		d.ammo[2] = radditem(n, d.ammo[2]);
		break;
	case I_ROCKETS:
		radditem(n, d->ammo[3]);
		d.ammo[3] = radditem(n, d.ammo[3]);
		break;
	case I_ROUNDS:
		radditem(n, d->ammo[4]);
		d.ammo[4] = radditem(n, d.ammo[4]);
		break;
	case I_HEALTH:
		radditem(n, d->health);
		d.health = radditem(n, d.health);
		break;
	case I_BOOST:
		radditem(n, d->health);
		d.health = radditem(n, d.health);
		break;

	case I_GREENARMOUR:
		radditem(n, d->armour);
		d->armourtype = A_GREEN;
		d.armour = radditem(n, d.armour);
		d.armourtype = A_GREEN;
		break;

	case I_YELLOWARMOUR:
		radditem(n, d->armour);
		d->armourtype = A_YELLOW;
		d.armour = radditem(n, d.armour);
		d.armourtype = A_YELLOW;
		break;

	case I_QUAD:
		radditem(n, d->quadmillis);
		d.quadmillis = radditem(n, d.quadmillis);
		conoutf(@"you got the quad!");
		break;
	}
}

// these functions are called when the client touches the item

void
additem(int i, int &v, int spawnsec)
additem(int i, int v, int spawnsec)
{
	// don't pick up if not needed
	if (v < itemstats[ents[i].type - I_SHELLS]
	            .max) // don't pick up if not needed
	if (v < itemstats[ents[i].type - I_SHELLS].max) {
		// first ask the server for an ack even if someone else gets it
		// first
	{
		addmsg(1, 3, SV_ITEMPICKUP, i,
		addmsg(1, 3, SV_ITEMPICKUP, i, m_classicsp ? 100000 : spawnsec);
		    m_classicsp ? 100000
		                : spawnsec); // first ask the server for an ack
		ents[i].spawned = false; // even if someone else gets it first
		ents[i].spawned = false;
	}
}

// also used by monsters
void
teleport(int n, dynent *d) // also used by monsters
teleport(int n, DynamicEntity *d)
{
	int e = -1, tag = ents[n].attr1, beenhere = -1;
	for (;;) {
		e = findentity(TELEDEST, e + 1);
		if (e == beenhere || e < 0) {
			conoutf(@"no teleport destination for tag %d", tag);
			return;
		}
		if (beenhere < 0)
			beenhere = e;
		if (ents[e].attr2 == tag) {
			d->o.x = ents[e].x;
			d.o = OFMakeVector3D(ents[e].x, ents[e].y, ents[e].z);
			d->o.y = ents[e].y;
			d->o.z = ents[e].z;
			d->yaw = ents[e].attr1;
			d->pitch = 0;
			d.yaw = ents[e].attr1;
			d.pitch = 0;
			d->vel.x = d->vel.y = d->vel.z = 0;
			d.vel = OFMakeVector3D(0, 0, 0);
			entinmap(d);
			playsoundc(S_TELEPORT);
			break;
		}
	}
}

void
pickup(int n, dynent *d)
pickup(int n, DynamicEntity *d)
{
	int np = 1;
	for (id player in players)
	loopv(players) if (players[i]) np++;
	np = np < 3 ? 4 : (np > 4 ? 2 : 3); // spawn times are dependent on
		if (player != [OFNull null])
			np++;
	// spawn times are dependent on number of players
	np = np < 3 ? 4 : (np > 4 ? 2 : 3);
	                                    // number of players
	int ammo = np * 2;
	switch (ents[n].type) {
	case I_SHELLS:
		additem(n, d->ammo[1], ammo);
		additem(n, d.ammo[1], ammo);
		break;
	case I_BULLETS:
		additem(n, d->ammo[2], ammo);
		additem(n, d.ammo[2], ammo);
		break;
	case I_ROCKETS:
		additem(n, d->ammo[3], ammo);
		additem(n, d.ammo[3], ammo);
		break;
	case I_ROUNDS:
		additem(n, d->ammo[4], ammo);
		additem(n, d.ammo[4], ammo);
		break;
	case I_HEALTH:
		additem(n, d->health, np * 5);
		additem(n, d.health, np * 5);
		break;
	case I_BOOST:
		additem(n, d->health, 60);
		additem(n, d.health, 60);
		break;

	case I_GREENARMOUR:
		// (100h/100g only absorbs 166 damage)
		if (d->armourtype == A_YELLOW && d->armour > 66)
		if (d.armourtype == A_YELLOW && d.armour > 66)
			break;
		additem(n, d->armour, 20);
		additem(n, d.armour, 20);
		break;

	case I_YELLOWARMOUR:
		additem(n, d->armour, 20);
		additem(n, d.armour, 20);
		break;

	case I_QUAD:
		additem(n, d->quadmillis, 60);
		additem(n, d.quadmillis, 60);
		break;

	case CARROT:
		ents[n].spawned = false;
		triggertime = lastmillis;
		trigger(ents[n].attr1, ents[n].attr2,
		    false); // needs to go over server for multiplayer
282
283
284
285
286
287
288
289
290


291
292
293
294
295
296
297
284
285
286
287
288
289
290


291
292
293
294
295
296
297
298
299







-
-
+
+







	case JUMPPAD: {
		static int lastjumppad = 0;
		if (lastmillis - lastjumppad < 300)
			break;
		lastjumppad = lastmillis;
		OFVector3D v = OFMakeVector3D((int)(char)ents[n].attr3 / 10.0f,
		    (int)(char)ents[n].attr2 / 10.0f, ents[n].attr1 / 10.0f);
		player1->vel.z = 0;
		vadd(player1->vel, v);
		player1.vel = OFMakeVector3D(player1.vel.x, player1.vel.y, 0);
		vadd(player1.vel, v);
		playsoundc(S_JUMPPAD);
		break;
	}
	}
}

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







-
-
+
+








-
-
+
+







		if (e.type == NOTUSED)
			continue;
		if (!ents[i].spawned && e.type != TELEPORT && e.type != JUMPPAD)
			continue;
		if (OUTBORD(e.x, e.y))
			continue;
		OFVector3D v = OFMakeVector3D(
		    e.x, e.y, (float)S(e.x, e.y)->floor + player1->eyeheight);
		vdist(dist, t, player1->o, v);
		    e.x, e.y, (float)S(e.x, e.y)->floor + player1.eyeheight);
		vdist(dist, t, player1.o, v);
		if (dist < (e.type == TELEPORT ? 4 : 2.5))
			pickup(i, player1);
	}
}

void
checkquad(int time)
{
	if (player1->quadmillis && (player1->quadmillis -= time) < 0) {
		player1->quadmillis = 0;
	if (player1.quadmillis && (player1.quadmillis -= time) < 0) {
		player1.quadmillis = 0;
		playsoundc(S_PUPOUT);
		conoutf(@"quad damage is over");
	}
}

void
putitems(uchar *&p) // puts items in network stream and also spawns them locally

Modified src/meson.build from [2625064d57] to [d42ec0e39c].

1
2
3
4
5

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





+







executable('client',
  [
    'Alias.m',
    'Command.mm',
    'Cube.mm',
    'DynamicEntity.mm',
    'Identifier.m',
    'KeyMapping.m',
    'MD2.mm',
    'MapModelInfo.m',
    'Menu.m',
    'MenuItem.m',
    'Projectile.m',

Modified src/monster.mm from [3ac13a2932] to [d6e394fd76].

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




+
-
-
+
+
+



-
+





+



+
-
+
-
+
+







// monster.cpp: implements AI for single player monsters, currently client only

#include "cube.h"

#import "DynamicEntity.h"
dvector monsters;
int nextmonster, spawnremain, numkilled, monstertotal, mtimestart;

static OFMutableArray<DynamicEntity *> *monsters;
static int nextmonster, spawnremain, numkilled, monstertotal, mtimestart;

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

dvector &
OFArray<DynamicEntity *> *
getmonsters()
{
	return monsters;
}

// for savegames
void
restoremonsterstate()
{
	for (DynamicEntity *monster in monsters)
	loopv(monsters) if (monsters[i]->state == CS_DEAD) numkilled++;
		if (monster.state == CS_DEAD)
} // for savegames
			numkilled++;
}

#define TOTMFREQ 13
#define NUMMONSTERTYPES 8

struct monstertype // see docs for how these values modify behaviour
{
	short gun, speed, health, freq, lag, rate, pain, loyalty, mscale,
45
46
47
48
49
50
51
52

53
54
55
56
57
58
59
60
61
62
63
64
65
66








67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85




















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


102
103

104
105
106
107

108
109
110
111
112
113
114
115
116
117
118

119
120

121
122
123
124
125
126
127
128

129
130

131
132
133
134
135
136
137
138
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







-
+






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


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
















+
+

-
+
-

-
-
+










-
+

-
+
-
-






+

-
+
-







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

dynent *
DynamicEntity *
basicmonster(int type, int yaw, int state, int trigger, int move)
{
	if (type >= NUMMONSTERTYPES) {
		conoutf(@"warning: unknown monster in spawn: %d", type);
		type = 0;
	}
	dynent *m = newdynent();
	monstertype *t = &monstertypes[m->mtype = type];
	m->eyeheight = 2.0f;
	m->aboveeye = 1.9f;
	m->radius *= t->bscale / 10.0f;
	m->eyeheight *= t->bscale / 10.0f;
	m->aboveeye *= t->bscale / 10.0f;
	m->monsterstate = state;
	DynamicEntity *m = newdynent();
	monstertype *t = &monstertypes[(m.mtype = type)];
	m.eyeheight = 2.0f;
	m.aboveeye = 1.9f;
	m.radius *= t->bscale / 10.0f;
	m.eyeheight *= t->bscale / 10.0f;
	m.aboveeye *= t->bscale / 10.0f;
	m.monsterstate = state;
	if (state != M_SLEEP)
		spawnplayer(m);
	m->trigger = lastmillis + trigger;
	m->targetyaw = m->yaw = (float)yaw;
	m->move = move;
	m->enemy = player1;
	m->gunselect = t->gun;
	m->maxspeed = (float)t->speed;
	m->health = t->health;
	m->armour = 0;
	loopi(NUMGUNS) m->ammo[i] = 10000;
	m->pitch = 0;
	m->roll = 0;
	m->state = CS_ALIVE;
	m->anger = 0;
	@autoreleasepool {
		strcpy_s(m->name, t->name.UTF8String);
	}
	monsters.add(m);
	m.trigger = lastmillis + trigger;
	m.targetyaw = m.yaw = (float)yaw;
	m.move = move;
	m.enemy = player1;
	m.gunselect = t->gun;
	m.maxspeed = (float)t->speed;
	m.health = t->health;
	m.armour = 0;
	loopi(NUMGUNS) m.ammo[i] = 10000;
	m.pitch = 0;
	m.roll = 0;
	m.state = CS_ALIVE;
	m.anger = 0;
	m.name = t->name;

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

	[monsters addObject:m];

	return m;
}

void
spawnmonster() // spawn a random monster according to freq distribution in DMSP
{
	int n = rnd(TOTMFREQ), type;
	for (int i = 0;; i++) {
		if ((n -= monstertypes[i].freq) < 0) {
			type = i;
			break;
		}
	}
	basicmonster(type, rnd(360), M_SEARCH, 1000, 1);
}

// called after map start of when toggling edit mode to reset/spawn all
// monsters to initial state
void
monsterclear() // called after map start of when toggling edit mode to
monsterclear()
               // reset/spawn all monsters to initial state
{
	loopv(monsters) free(monsters[i]);
	monsters.setsize(0);
	[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;
		loopv(ents) if (ents[i].type == MONSTER)
		{
			dynent *m = basicmonster(
			DynamicEntity *m = basicmonster(
			    ents[i].attr2, ents[i].attr1, M_SLEEP, 100, 0);
			m->o.x = ents[i].x;
			m.o = OFMakeVector3D(ents[i].x, ents[i].y, ents[i].z);
			m->o.y = ents[i].y;
			m->o.z = ents[i].z;
			entinmap(m);
			monstertotal++;
		}
	}
}

// height-correct line of sight for monster shooting/seeing
bool
los(float lx, float ly, float lz, float bx, float by, float bz,
los(float lx, float ly, float lz, float bx, float by, float bz, OFVector3D &v)
    OFVector3D &v) // height-correct line of sight for monster shooting/seeing
{
	if (OUTBORD((int)lx, (int)ly) || OUTBORD((int)bx, (int)by))
		return false;
	float dx = bx - lx;
	float dy = by - ly;
	int steps = (int)(sqrt(dx * dx + dy * dy) / 0.9);
	if (!steps)
160
161
162
163
164
165
166
167

168
169
170
171



172
173
174
175
176
177
178
179
180

181
182

183
184
185
186


187
188

189
190
191
192

193
194
195
196
197




198
199

200
201

202
203
204
205
206



207
208
209


210
211
212
213




214
215
216
217



218
219
220
221


222
223
224
225


226
227
228


229
230
231


232
233

234
235
236
237
238
239

240
241
242
243

244
245
246
247

248
249
250
251
252
253
254
255
256
257
258

259
260
261
262
263
264

265

266
267
268
269

270
271
272
273
274
275






276
277
278
279

280
281
282
283




284
285
286


287
288

289
290

291
292


293
294
295
296


297
298

299
300
301



302
303
304

305
306
307
308
309
310
311
312
313
314
315

316
317
318

319
320
321
322
323


324
325

326
327

328
329
330
331
332


333
334
335
336
337
338




339
340
341



342
343
344
345
346




347
348
349
350
351
352
353
166
167
168
169
170
171
172

173
174



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

189

190


191
192
193

194
195
196
197

198
199




200
201
202
203
204
205
206
207

208

209



210
211
212
213


214
215




216
217
218
219
220



221
222
223
224
225


226
227
228
229


230
231
232


233
234
235


236
237
238

239
240
241
242
243
244

245

246
247

248
249
250
251

252
253
254
255
256
257
258
259
260
261
262

263
264
265
266
267
268
269
270

271
272
273
274
275
276






277
278
279
280
281
282
283
284
285
286
287




288
289
290
291
292


293
294


295

296
297


298
299

300


301
302


303
304


305
306
307

308

309

310
311
312
313
314
315
316
317
318

319
320
321

322
323
324
325


326
327


328
329

330
331
332
333


334
335
336
337




338
339
340
341
342


343
344
345
346
347
348


349
350
351
352
353
354
355
356
357
358
359







-
+

-
-
-
+
+
+









+

-
+
-

-
-
+
+

-
+



-
+

-
-
-
-
+
+
+
+


+

-
+
-

-
-
-
+
+
+

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

-
-
-
+
+
+


-
-
+
+


-
-
+
+

-
-
+
+

-
-
+
+

-
+





-
+
-


-
+



-
+










-
+






+
-
+




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




+
-
-
-
-
+
+
+
+

-
-
+
+
-
-
+
-

+
-
-
+
+
-

-
-
+
+
-
-
+

-
-
+
+
+
-

-
+
-









-
+


-
+



-
-
+
+
-
-
+

-
+



-
-
+
+


-
-
-
-
+
+
+
+

-
-
+
+
+



-
-
+
+
+
+







		y += dy / (float)steps;
		i++;
	}
	return i >= steps;
}

bool
enemylos(dynent *m, OFVector3D &v)
enemylos(DynamicEntity *m, OFVector3D &v)
{
	v = m->o;
	return los(m->o.x, m->o.y, m->o.z, m->enemy->o.x, m->enemy->o.y,
	    m->enemy->o.z, v);
	v = m.o;
	return los(
	    m.o.x, m.o.y, m.o.z, m.enemy.o.x, m.enemy.o.y, m.enemy.o.z, v);
}

// monster AI is sequenced using transitions: they are in a particular state
// 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
transition(dynent *m, int state, int moving, int n,
transition(DynamicEntity *m, int state, int moving, int n, int r)
    int r) // n = at skill 0, n/2 = at skill 10, r = added random factor
{
	m->monsterstate = state;
	m->move = moving;
	m.monsterstate = state;
	m.move = moving;
	n = n * 130 / 100;
	m->trigger = lastmillis + n - skill * (n / 16) + rnd(r + 1);
	m.trigger = lastmillis + n - skill * (n / 16) + rnd(r + 1);
}

void
normalise(dynent *m, float angle)
normalise(DynamicEntity *m, float angle)
{
	while (m->yaw < angle - 180.0f)
		m->yaw += 360.0f;
	while (m->yaw > angle + 180.0f)
		m->yaw -= 360.0f;
	while (m.yaw < angle - 180.0f)
		m.yaw += 360.0f;
	while (m.yaw > angle + 180.0f)
		m.yaw -= 360.0f;
}

// main AI thinking routine, called every frame for every monster
void
monsteraction(
monsteraction(DynamicEntity *m)
    dynent *m) // main AI thinking routine, called every frame for every monster
{
	if (m->enemy->state == CS_DEAD) {
		m->enemy = player1;
		m->anger = 0;
	if (m.enemy.state == CS_DEAD) {
		m.enemy = player1;
		m.anger = 0;
	}
	normalise(m, m->targetyaw);
	if (m->targetyaw > m->yaw) // slowly turn monster towards his target
	normalise(m, m.targetyaw);
	// slowly turn monster towards his target
	{
		m->yaw += curtime * 0.5f;
		if (m->targetyaw < m->yaw)
			m->yaw = m->targetyaw;
	if (m.targetyaw > m.yaw) {
		m.yaw += curtime * 0.5f;
		if (m.targetyaw < m.yaw)
			m.yaw = m.targetyaw;
	} else {
		m->yaw -= curtime * 0.5f;
		if (m->targetyaw > m->yaw)
			m->yaw = m->targetyaw;
		m.yaw -= curtime * 0.5f;
		if (m.targetyaw > m.yaw)
			m.yaw = m.targetyaw;
	}

	vdist(disttoenemy, vectoenemy, m->o, m->enemy->o);
	m->pitch = atan2(m->enemy->o.z - m->o.z, disttoenemy) * 180 / PI;
	vdist(disttoenemy, vectoenemy, m.o, m.enemy.o);
	m.pitch = atan2(m.enemy.o.z - m.o.z, disttoenemy) * 180 / PI;

	// special case: if we run into scenery
	if (m->blocked) {
		m->blocked = false;
	if (m.blocked) {
		m.blocked = false;
		// try to jump over obstackle (rare)
		if (!rnd(20000 / monstertypes[m->mtype].speed))
			m->jumpnext = true;
		if (!rnd(20000 / monstertypes[m.mtype].speed))
			m.jumpnext = true;
		// search for a way around (common)
		else if (m->trigger < lastmillis &&
		    (m->monsterstate != M_HOME || !rnd(5))) {
		else if (m.trigger < lastmillis &&
		    (m.monsterstate != M_HOME || !rnd(5))) {
			// patented "random walk" AI pathfinding (tm) ;)
			m->targetyaw += 180 + rnd(180);
			m.targetyaw += 180 + rnd(180);
			transition(m, M_SEARCH, 1, 400, 1000);
		}
	}

	float enemyyaw =
	    -(float)atan2(m->enemy->o.x - m->o.x, m->enemy->o.y - m->o.y) / PI *
	    -(float)atan2(m.enemy.o.x - m.o.x, m.enemy.o.y - m.o.y) / PI * 180 +
	        180 +
	    180;

	switch (m->monsterstate) {
	switch (m.monsterstate) {
	case M_PAIN:
	case M_ATTACKING:
	case M_SEARCH:
		if (m->trigger < lastmillis)
		if (m.trigger < lastmillis)
			transition(m, M_HOME, 1, 100, 200);
		break;

	case M_SLEEP: // state classic sp monster start in, wait for visual
	              // contact
	{
		OFVector3D target;
		if (editmode || !enemylos(m, target))
			return; // skip running physics
		normalise(m, enemyyaw);
		float angle = (float)fabs(enemyyaw - m->yaw);
		float angle = (float)fabs(enemyyaw - m.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) {
			transition(m, M_HOME, 1, 500, 200);
			OFVector3D loc = m.o;
			playsound(S_GRUNT1 + rnd(2), &m->o);
			playsound(S_GRUNT1 + rnd(2), &loc);
		}
		break;
	}

	case M_AIMING:
	case M_AIMING: // this state is the delay between wanting to shoot and
	               // actually firing
		if (m->trigger < lastmillis) {
			m->lastaction = 0;
			m->attacking = true;
			shoot(m, m->attacktarget);
		// this state is the delay between wanting to shoot and actually
		// firing
		if (m.trigger < lastmillis) {
			m.lastaction = 0;
			m.attacking = true;
			shoot(m, m.attacktarget);
			transition(m, M_ATTACKING, 0, 600, 0);
		}
		break;

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

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

void
monsterpain(dynent *m, int damage, dynent *d)
monsterpain(DynamicEntity *m, int damage, DynamicEntity *d)
{
	// a monster hit us
	if (d->monsterstate) {
	if (d.monsterstate) {
		// guard for RL guys shooting themselves :)
		if (m != d) {
			// don't attack straight away, first get angry
			m->anger++;
			int anger =
			m.anger++;
			int anger = m.mtype == d.mtype ? m.anger / 2 : m.anger;
			    m->mtype == d->mtype ? m->anger / 2 : m->anger;
			if (anger >= monstertypes[m->mtype].loyalty)
			if (anger >= monstertypes[m.mtype].loyalty)
				// monster infight if very angry
				m->enemy = d;
				m.enemy = d;
		}
	} else {
		// player hit us
		m->anger = 0;
		m->enemy = d;
		m.anger = 0;
		m.enemy = d;
	}
	// in this state monster won't attack
	transition(m, M_PAIN, 0, monstertypes[m->mtype].pain, 200);
	if ((m->health -= damage) <= 0) {
		m->state = CS_DEAD;
		m->lastaction = lastmillis;
	transition(m, M_PAIN, 0, monstertypes[m.mtype].pain, 200);
	if ((m.health -= damage) <= 0) {
		m.state = CS_DEAD;
		m.lastaction = lastmillis;
		numkilled++;
		player1->frags = numkilled;
		playsound(monstertypes[m->mtype].diesound, &m->o);
		player1.frags = numkilled;
		OFVector3D loc = m.o;
		playsound(monstertypes[m.mtype].diesound, &loc);
		int remain = monstertotal - numkilled;
		if (remain > 0 && remain <= 5)
			conoutf(@"only %d monster(s) remaining", remain);
	} else
		playsound(monstertypes[m->mtype].painsound, &m->o);
	} else {
		OFVector3D loc = m.o;
		playsound(monstertypes[m.mtype].painsound, &loc);
	}
}

void
endsp(bool allkilled)
{
	conoutf(allkilled ? @"you have cleared the map!"
	                  : @"you reached the exit!");
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
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







-
-
+
+








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


-
-
-
+
+
+

-
-
+




+
-
-
+
+





+
-
-
-
+
+
+

		nextmonster = lastmillis + 1000;
		spawnmonster();
	}

	if (monstertotal && !spawnremain && numkilled == monstertotal)
		endsp(true);

	loopv(ents) // equivalent of player entity touch, but only teleports are
	            // used
	// equivalent of player entity touch, but only teleports are used
	loopv(ents)
	{
		entity &e = ents[i];
		if (e.type != TELEPORT)
			continue;
		if (OUTBORD(e.x, e.y))
			continue;
		OFVector3D v =
		    OFMakeVector3D(e.x, e.y, (float)S(e.x, e.y)->floor);
		loopv(monsters)
		for (DynamicEntity *monster in monsters) {
		{
			if (monsters[i]->state == CS_DEAD) {
				if (lastmillis - monsters[i]->lastaction <
			if (monster.state == CS_DEAD) {
				if (lastmillis - monster.lastaction < 2000) {
				    2000) {
					monsters[i]->move = 0;
					moveplayer(monsters[i], 1, false);
					monster.move = 0;
					moveplayer(monster, 1, false);
				}
			} else {
				v.z += monsters[i]->eyeheight;
				vdist(dist, t, monsters[i]->o, v);
				v.z -= monsters[i]->eyeheight;
				v.z += monster.eyeheight;
				vdist(dist, t, monster.o, v);
				v.z -= monster.eyeheight;
				if (dist < 4)
					teleport(
					    (int)(&e - &ents[0]), monsters[i]);
					teleport((int)(&e - &ents[0]), monster);
			}
		}
	}

	for (DynamicEntity *monster in monsters)
	loopv(monsters) if (monsters[i]->state == CS_ALIVE)
	    monsteraction(monsters[i]);
		if (monster.state == CS_ALIVE)
			monsteraction(monster);
}

void
monsterrender()
{
	for (DynamicEntity *monster in monsters)
	loopv(monsters) renderclient(monsters[i], false,
	    monstertypes[monsters[i]->mtype].mdlname, monsters[i]->mtype == 5,
	    monstertypes[monsters[i]->mtype].mscale / 10.0f);
		renderclient(monster, false,
		    monstertypes[monster.mtype].mdlname, monster.mtype == 5,
		    monstertypes[monster.mtype].mscale / 10.0f);
}

Modified src/physics.mm from [cbd2b108e1] to [850bd490e8].

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








+


+

-
-
+
+

-
+

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

-
+

-
+

-
+







// physics.cpp: no physics books were hurt nor consulted in the construction of
// this code. All physics computations and constants were invented on the fly
// and simply tweaked until they "felt right", and have no basis in reality.
// Collision detection is simplistic but very robust (uses discrete steps at
// fixed fps).

#include "cube.h"

#import "DynamicEntity.h"
#import "MapModelInfo.h"

// collide with player or monster
bool
plcollide(dynent *d, dynent *o, float &headspace, float &hi,
    float &lo) // collide with player or monster
plcollide(
    DynamicEntity *d, DynamicEntity *o, float &headspace, float &hi, float &lo)
{
	if (o->state != CS_ALIVE)
	if (o.state != CS_ALIVE)
		return true;
	const float r = o->radius + d->radius;
	if (fabs(o->o.x - d->o.x) < r && fabs(o->o.y - d->o.y) < r) {
		if (d->o.z - d->eyeheight < o->o.z - o->eyeheight) {
			if (o->o.z - o->eyeheight < hi)
				hi = o->o.z - o->eyeheight - 1;
		} else if (o->o.z + o->aboveeye > lo)
			lo = o->o.z + o->aboveeye + 1;
	const float r = o.radius + d.radius;
	if (fabs(o.o.x - d.o.x) < r && fabs(o.o.y - d.o.y) < r) {
		if (d.o.z - d.eyeheight < o.o.z - o.eyeheight) {
			if (o.o.z - o.eyeheight < hi)
				hi = o.o.z - o.eyeheight - 1;
		} else if (o.o.z + o.aboveeye > lo)
			lo = o.o.z + o.aboveeye + 1;

		if (fabs(o->o.z - d->o.z) < o->aboveeye + d->eyeheight)
		if (fabs(o.o.z - d.o.z) < o.aboveeye + d.eyeheight)
			return false;
		if (d->monsterstate)
		if (d.monsterstate)
			return false; // hack
		headspace = d->o.z - o->o.z - o->aboveeye - d->eyeheight;
		headspace = d.o.z - o.o.z - o.aboveeye - d.eyeheight;
		if (headspace < 0)
			headspace = 10;
	}
	return true;
}

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







-
+









-
-
+
+


-
+














-
+

+
-
+
-
-
-
-
-
+
+
+





+
-
-
-
+
+
+
-


-
+
-
+







		bs = 1 << mip;
		return cornertest(mip, x, y, dx, dy, bx, by, bs);
	}
	return stest;
}

void
mmcollide(dynent *d, float &hi, float &lo) // collide with a mapmodel
mmcollide(DynamicEntity *d, float &hi, float &lo) // collide with a mapmodel
{
	loopv(ents)
	{
		entity &e = ents[i];
		if (e.type != MAPMODEL)
			continue;
		MapModelInfo *mmi = getmminfo(e.attr2);
		if (mmi == nil || !mmi.h)
			continue;
		const float r = mmi.rad + d->radius;
		if (fabs(e.x - d->o.x) < r && fabs(e.y - d->o.y) < r) {
		const float r = mmi.rad + d.radius;
		if (fabs(e.x - d.o.x) < r && fabs(e.y - d.o.y) < r) {
			float mmz =
			    (float)(S(e.x, e.y)->floor + mmi.zoff + e.attr3);
			if (d->o.z - d->eyeheight < mmz) {
			if (d.o.z - d.eyeheight < mmz) {
				if (mmz < hi)
					hi = mmz;
			} else if (mmz + mmi.h > lo)
				lo = mmz + mmi.h;
		}
	}
}

// all collision happens here
// spawn is a dirty side effect used in spawning
// drop & rise are supplied by the physics below to indicate gravity/push for
// current mini-timestep

bool
collide(dynent *d, bool spawn, float drop, float rise)
collide(DynamicEntity *d, bool spawn, float drop, float rise)
{
	// figure out integer cube rectangle this entity covers in map
	const float fx1 =
	const float fx1 = d.o.x - d.radius;
	    d->o.x - d->radius; // figure out integer cube rectangle this entity
	                        // covers in map
	const float fy1 = d->o.y - d->radius;
	const float fx2 = d->o.x + d->radius;
	const float fy2 = d->o.y + d->radius;
	const float fy1 = d.o.y - d.radius;
	const float fx2 = d.o.x + d.radius;
	const float fy2 = d.o.y + d.radius;
	const int x1 = fast_f2nat(fx1);
	const int y1 = fast_f2nat(fy1);
	const int x2 = fast_f2nat(fx2);
	const int y2 = fast_f2nat(fy2);
	float hi = 127, lo = -128;
	// big monsters are afraid of heights, unless angry :)
	float minfloor = (d->monsterstate && !spawn && d->health > 100)
	    ? d->o.z - d->eyeheight - 4.5f
	    : -1000.0f; // big monsters are afraid of heights,
	float minfloor = (d.monsterstate && !spawn && d.health > 100)
	    ? d.o.z - d.eyeheight - 4.5f
	    : -1000.0f;
	                // unless angry :)

	for (int x = x1; x <= x2; x++)
		for (int y = y1; y <= y2; y++) // collide with map
		for (int y = y1; y <= y2; y++) {
		{
			// collide with map
			if (OUTBORD(x, y))
				return false;
			sqr *s = S(x, y);
			float ceil = s->ceil;
			float floor = s->floor;
			switch (s->type) {
			case SOLID:
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
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







-
+



-
+
-
-
+
-

-
+





-


+
-
-
+
+
+





-
-
+
+
+

-
+


-
+
+
+

-
+
+
+


-
-
-
+
+
+
+
+
-
-
+


-
-
-
+
+
+
+
+


-
+







				hi = ceil;
			if (floor > lo)
				lo = floor;
			if (floor < minfloor)
				return false;
		}

	if (hi - lo < d->eyeheight + d->aboveeye)
	if (hi - lo < d.eyeheight + d.aboveeye)
		return false;

	float headspace = 10;
	loopv(players) // collide with other players
	for (id player in players) {
	{
		dynent *o = players[i];
		if (player == [OFNull null] || player == d)
		if (!o || o == d)
			continue;
		if (!plcollide(d, o, headspace, hi, lo))
		if (!plcollide(d, player, headspace, hi, lo))
			return false;
	}
	if (d != player1)
		if (!plcollide(d, player1, headspace, hi, lo))
			return false;
	dvector &v = getmonsters();
	// this loop can be a performance bottleneck with many monster on a slow
	// cpu, should replace with a blockmap but seems mostly fast enough
	for (DynamicEntity *monster in getmonsters())
	loopv(v) if (!vreject(d->o, v[i]->o, 7.0f) && d != v[i] &&
	    !plcollide(d, v[i], headspace, hi, lo)) return false;
		if (!vreject(d.o, monster.o, 7.0f) && d != monster &&
		    !plcollide(d, monster, headspace, hi, lo))
			return false;
	headspace -= 0.01f;

	mmcollide(d, hi, lo); // collide with map models

	if (spawn) {
		d->o.z = lo + d->eyeheight; // just drop to floor (sideeffect)
		d->onfloor = true;
		// just drop to floor (sideeffect)
		d.o = OFMakeVector3D(d.o.x, d.o.y, lo + d.eyeheight);
		d.onfloor = true;
	} else {
		const float space = d->o.z - d->eyeheight - lo;
		const float space = d.o.z - d.eyeheight - lo;
		if (space < 0) {
			if (space > -0.01)
				d->o.z = lo + d->eyeheight; // stick on step
				// stick on step
				d.o = OFMakeVector3D(
				    d.o.x, d.o.y, lo + d.eyeheight);
			else if (space > -1.26f)
				d->o.z += rise; // rise thru stair
				// rise thru stair
				d.o =
				    OFMakeVector3D(d.o.x, d.o.y, d.o.z + rise);
			else
				return false;
		} else {
			d->o.z -= min(min(drop, space), headspace); // gravity
		}
		} else
			// gravity
			d.o = OFMakeVector3D(d.o.x, d.o.y,
			    d.o.z - min(min(drop, space), headspace));


		const float space2 = hi - (d->o.z + d->aboveeye);
		const float space2 = hi - (d.o.z + d.aboveeye);
		if (space2 < 0) {
			if (space2 < -0.1)
				return false;      // hack alert!
			d->o.z = hi - d->aboveeye; // glue to ceiling
			d->vel.z = 0; // cancel out jumping velocity
				return false; // hack alert!
			// glue to ceiling
			d.o = OFMakeVector3D(d.o.x, d.o.y, hi - d.aboveeye);
			// cancel out jumping velocity
			d.vel = OFMakeVector3D(d.vel.x, d.vel.y, 0);
		}

		d->onfloor = d->o.z - d->eyeheight - lo < 0.001f;
		d.onfloor = d.o.z - d.eyeheight - lo < 0.001f;
	}
	return true;
}

float
rad(float x)
{
233
234
235
236
237
238
239
240

241
242
243


244
245
246
247
248


249
250
251
252
253
254



255
256
257
258


259
260
261

262
263

264
265
266
267
268
269
270
271






272
273
274
275


276
277

278
279
280
281
282





283

284

285
286
287
288
289






290
291
292
293
294



295
296
297
298
299
300





301
302
303
304
305
306
307
308
309
310








311
312
313
314
315
316

317
318
319
320

321
322
323
324
325




326
327
328
329
330
331


332
333
334
335
336
337


338
339
340
341
342
343

344

345
346
347
348
349
350
351
352


353
354
355
356
357
358

359
360
361
362
363
364
365
366
367
368
369
370
371
372







373
374
375
376
377
378
379
380
381
382
383
384








385
386
387
388
389
390
391
392
393
394









395
396
397
398

399
400
401
402
403
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







-
+

-
-
+
+



-
-
+
+



-
-
-
+
+
+


-
-
+
+

-
-
+

-
+



-
-
-
-
-
+
+
+
+
+
+


-
-
+
+

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

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


-
-
-
+
+
+
-


-
-
-
+
+
+
+
+




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




-
+



-
+

-
-
-
-
+
+
+
+




-
-
+
+
-



-
-
+
+




-

+
-
+




-

-
-
+
+
-




-
+







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




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




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



-
+





}

// main physics routine, moves a player/monster for a curtime step
// moveres indicated the physics precision (which is lower for monsters and
// multiplayer prediction) local is false for multiplayer prediction

void
moveplayer(dynent *pl, int moveres, bool local, int curtime)
moveplayer(DynamicEntity *pl, int moveres, bool local, int curtime)
{
	const bool water = hdr.waterlevel > pl->o.z - 0.5f;
	const bool floating = (editmode && local) || pl->state == CS_EDITING;
	const bool water = hdr.waterlevel > pl.o.z - 0.5f;
	const bool floating = (editmode && local) || pl.state == CS_EDITING;

	OFVector3D d; // vector of direction we ideally want to move in

	d.x = (float)(pl->move * cos(rad(pl->yaw - 90)));
	d.y = (float)(pl->move * sin(rad(pl->yaw - 90)));
	d.x = (float)(pl.move * cos(rad(pl.yaw - 90)));
	d.y = (float)(pl.move * sin(rad(pl.yaw - 90)));
	d.z = 0;

	if (floating || water) {
		d.x *= (float)cos(rad(pl->pitch));
		d.y *= (float)cos(rad(pl->pitch));
		d.z = (float)(pl->move * sin(rad(pl->pitch)));
		d.x *= (float)cos(rad(pl.pitch));
		d.y *= (float)cos(rad(pl.pitch));
		d.z = (float)(pl.move * sin(rad(pl.pitch)));
	}

	d.x += (float)(pl->strafe * cos(rad(pl->yaw - 180)));
	d.y += (float)(pl->strafe * sin(rad(pl->yaw - 180)));
	d.x += (float)(pl.strafe * cos(rad(pl.yaw - 180)));
	d.y += (float)(pl.strafe * sin(rad(pl.yaw - 180)));

	const float speed =
	    curtime / (water ? 2000.0f : 1000.0f) * pl->maxspeed;
	const float speed = curtime / (water ? 2000.0f : 1000.0f) * pl.maxspeed;
	const float friction =
	    water ? 20.0f : (pl->onfloor || floating ? 6.0f : 30.0f);
	    water ? 20.0f : (pl.onfloor || floating ? 6.0f : 30.0f);

	const float fpsfric = friction / curtime * 20.0f;

	vmul(pl->vel, fpsfric - 1); // slowly apply friction and direction to
	                            // velocity, gives a smooth movement
	vadd(pl->vel, d);
	vdiv(pl->vel, fpsfric);
	d = pl->vel;
	// slowly apply friction and direction to
	// velocity, gives a smooth movement
	vmul(pl.vel, fpsfric - 1);
	vadd(pl.vel, d);
	vdiv(pl.vel, fpsfric);
	d = pl.vel;
	vmul(d, speed); // d is now frametime based velocity vector

	pl->blocked = false;
	pl->moving = true;
	pl.blocked = false;
	pl.moving = true;

	if (floating) // just apply velocity
	if (floating) {
	{
		vadd(pl->o, d);
		if (pl->jumpnext) {
			pl->jumpnext = false;
			pl->vel.z = 2;
		// just apply velocity
		vadd(pl.o, d);
		if (pl.jumpnext) {
			pl.jumpnext = false;
			pl.vel = OFMakeVector3D(pl.vel.x, pl.vel.y, 2);
		}
	} else {
	} else // apply velocity with collision
		// apply velocity with collision
	{
		if (pl->onfloor || water) {
			if (pl->jumpnext) {
				pl->jumpnext = false;
				pl->vel.z = 1.7f; // physics impulse upwards
		if (pl.onfloor || water) {
			if (pl.jumpnext) {
				pl.jumpnext = false;
				// physics impulse upwards
				pl.vel =
				    OFMakeVector3D(pl.vel.x, pl.vel.y, 1.7);
				// dampen velocity change even harder, gives
				// correct water feel
				if (water) {
					pl->vel.x /= 8;
					pl->vel.y /= 8;
				if (water)
					pl.vel = OFMakeVector3D(pl.vel.x / 8,
					    pl.vel.y / 8, pl.vel.z);
				}
				if (local)
					playsoundc(S_JUMP);
				else if (pl->monsterstate)
					playsound(S_JUMP, &pl->o);
			} else if (pl->timeinair > 800) {
				else if (pl.monsterstate) {
					OFVector3D loc = pl.o;
					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->monsterstate)
					playsound(S_LAND, &pl->o);
			}
			pl->timeinair = 0;
		} else {
			pl->timeinair += curtime;
				else if (pl.monsterstate) {
					OFVector3D loc = pl.o;
					playsound(S_LAND, &loc);
				}
			}
			pl.timeinair = 0;
		} else
			pl.timeinair += curtime;
		}

		const float gravity = 20;
		const float f = 1.0f / moveres;
		// incorrect, but works fine
		float dropf = ((gravity - 1) + pl->timeinair / 15.0f);
		float dropf = ((gravity - 1) + pl.timeinair / 15.0f);
		// float slowly down in water
		if (water) {
			dropf = 5;
			pl->timeinair = 0;
			pl.timeinair = 0;
		}
		const float drop = dropf * curtime / gravity / 100 /
		    moveres; // at high fps, gravity kicks in too fast
		const float rise = speed / moveres /
		    1.2f; // extra smoothness when lifting up stairs
		// at high fps, gravity kicks in too fast
		const float drop = dropf * curtime / gravity / 100 / moveres;
		// extra smoothness when lifting up stairs
		const float rise = speed / moveres / 1.2f;

		loopi(moveres) // discrete steps collision detection & sliding
		{
			// try move forward
			pl->o.x += f * d.x;
			pl->o.y += f * d.y;
			pl.o = OFMakeVector3D(pl.o.x + f * d.x,
			    pl.o.y + f * d.y, pl.o.z + f * d.z);
			pl->o.z += f * d.z;
			if (collide(pl, false, drop, rise))
				continue;
			// player stuck, try slide along y axis
			pl->blocked = true;
			pl->o.x -= f * d.x;
			pl.blocked = true;
			pl.o = OFMakeVector3D(pl.o.x - f * d.x, pl.o.y, pl.o.z);
			if (collide(pl, false, drop, rise)) {
				d.x = 0;
				continue;
			}
			pl->o.x += f * d.x;
			// still stuck, try x axis
			pl.o = OFMakeVector3D(
			pl->o.y -= f * d.y;
			    pl.o.x + f * d.x, pl.o.y - f * d.y, pl.o.z);
			if (collide(pl, false, drop, rise)) {
				d.y = 0;
				continue;
			}
			pl->o.y += f * d.y;
			// try just dropping down
			pl->moving = false;
			pl->o.x -= f * d.x;
			pl.moving = false;
			pl.o = OFMakeVector3D(pl.o.x - f * d.x, pl.o.y, pl.o.z);
			pl->o.y -= f * d.y;
			if (collide(pl, false, drop, rise)) {
				d.y = d.x = 0;
				continue;
			}
			pl->o.z -= f * d.z;
			pl.o = OFMakeVector3D(pl.o.x, pl.o.y, pl.o.z - f * d.z);
			break;
		}
	}

	// detect wether player is outside map, used for skipping zbuffer clear
	// mostly

	if (pl->o.x < 0 || pl->o.x >= ssize || pl->o.y < 0 || pl->o.y > ssize) {
		pl->outsidemap = true;
	} else {
		sqr *s = S((int)pl->o.x, (int)pl->o.y);
		pl->outsidemap = SOLID(s) ||
		    pl->o.z < s->floor - (s->type == FHF ? s->vdelta / 4 : 0) ||
		    pl->o.z > s->ceil + (s->type == CHF ? s->vdelta / 4 : 0);
	if (pl.o.x < 0 || pl.o.x >= ssize || pl.o.y < 0 || pl.o.y > ssize)
		pl.outsidemap = true;
	else {
		sqr *s = S((int)pl.o.x, (int)pl.o.y);
		pl.outsidemap = SOLID(s) ||
		    pl.o.z < s->floor - (s->type == FHF ? s->vdelta / 4 : 0) ||
		    pl.o.z > s->ceil + (s->type == CHF ? s->vdelta / 4 : 0);
	}

	// automatically apply smooth roll when strafing

	if (pl->strafe == 0) {
		pl->roll = pl->roll / (1 + (float)sqrt((float)curtime) / 25);
	} else {
		pl->roll += pl->strafe * curtime / -30.0f;
		if (pl->roll > maxroll)
			pl->roll = (float)maxroll;
		if (pl->roll < -maxroll)
			pl->roll = (float)-maxroll;
	if (pl.strafe == 0)
		pl.roll = pl.roll / (1 + (float)sqrt((float)curtime) / 25);
	else {
		pl.roll += pl.strafe * curtime / -30.0f;
		if (pl.roll > maxroll)
			pl.roll = (float)maxroll;
		if (pl.roll < -maxroll)
			pl.roll = (float)-maxroll;
	}

	// play sounds on water transitions

	if (!pl->inwater && water) {
		playsound(S_SPLASH2, &pl->o);
		pl->vel.z = 0;
	} else if (pl->inwater && !water)
		playsound(S_SPLASH1, &pl->o);
	pl->inwater = water;
	if (!pl.inwater && water) {
		OFVector3D loc = pl.o;
		playsound(S_SPLASH2, &loc);
		pl.vel = OFMakeVector3D(pl.vel.x, pl.vel.y, 0);
	} else if (pl.inwater && !water) {
		OFVector3D loc = pl.o;
		playsound(S_SPLASH1, &loc);
	}
	pl.inwater = water;
}

void
moveplayer(dynent *pl, int moveres, bool local)
moveplayer(DynamicEntity *pl, int moveres, bool local)
{
	loopi(physicsrepeat) moveplayer(pl, moveres, local,
	    i ? curtime / physicsrepeat
	      : curtime - curtime / physicsrepeat * (physicsrepeat - 1));
}

Modified src/protos.h from [455fc77430] to [442aaeb8db].

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







-
+








+





-
-
-
+
+
+


-
-
+
+

-
+





-
+



















-
+







extern void disconnect(int onlyclean = 0, int async = 0);
extern void toserver(OFString *text);
extern void addmsg(int rel, int num, int type, ...);
extern bool multiplayer();
extern bool allowedittoggle();
extern void sendpackettoserv(void *packet);
extern void gets2c();
extern void c2sinfo(dynent *d);
extern void c2sinfo(DynamicEntity *d);
extern void neterr(OFString *s);
extern void initclientnet();
extern bool netmapstart();
extern int getclientnum();
extern void changemapserv(OFString *name, int mode);
extern void writeclientinfo(OFStream *stream);

// clientgame
extern void initPlayers();
extern void mousemove(int dx, int dy);
extern void updateworld(int millis);
extern void startmap(OFString *name);
extern void changemap(OFString *name);
extern void initclient();
extern void spawnplayer(dynent *d);
extern void selfdamage(int damage, int actor, dynent *act);
extern dynent *newdynent();
extern void spawnplayer(DynamicEntity *d);
extern void selfdamage(int damage, int actor, DynamicEntity *act);
extern DynamicEntity *newdynent();
extern OFString *getclientmap();
extern OFString *modestr(int n);
extern void zapdynent(dynent *&d);
extern dynent *getclient(int cn);
extern DynamicEntity *getclient(int cn);
extern void setclient(int cn, id client);
extern void timeupdate(int timeremain);
extern void resetmovement(dynent *d);
extern void resetmovement(DynamicEntity *d);
extern void fixplayer1range();

// clientextras
extern void renderclients();
extern void renderclient(
    dynent *d, bool team, OFString *mdlname, bool hellpig, float scale);
    DynamicEntity *d, bool team, OFString *mdlname, bool hellpig, float scale);
void showscores(bool on);
extern void renderscores();

// world
extern void setupworld(int factor);
extern void empty_world(int factor, bool force);
extern void remip(block &b, int level = 0);
extern void remipmore(block &b, int level = 0);
extern int closestent();
extern int findentity(int type, int index = 0);
extern void trigger(int tag, int type, bool savegame);
extern void resettagareas();
extern void settagareas();
extern entity *newentity(
    int x, int y, int z, OFString *what, int v1, int v2, int v3, int v4);

// worldlight
extern void calclight();
extern void dodynlight(const OFVector3D &vold, const OFVector3D &v, int reach,
    int strength, dynent *owner);
    int strength, DynamicEntity *owner);
extern void cleardlights();
extern block *blockcopy(block &b);
extern void blockpaste(block &b);

// worldrender
extern void render_world(float vx, float vy, float vh, int yaw, int pitch,
    float widef, int w, int h);
187
188
189
190
191
192
193
194

195
196
197
198
199
200



201
202
203
204
205
206
207
188
189
190
191
192
193
194

195
196
197
198



199
200
201
202
203
204
205
206
207
208







-
+



-
-
-
+
+
+







extern void writemap(OFString *mname, int msize, uchar *mdata);
extern OFData *readmap(OFString *mname);
extern void loadgamerest();
extern void incomingdemodata(uchar *buf, int len, bool extras = false);
extern void demoplaybackstep();
extern void stop();
extern void stopifrecording();
extern void demodamage(int damage, OFVector3D &o);
extern void demodamage(int damage, const OFVector3D &o);
extern void demoblend(int damage);

// physics
extern void moveplayer(dynent *pl, int moveres, bool local);
extern bool collide(dynent *d, bool spawn, float drop, float rise);
extern void entinmap(dynent *d);
extern void moveplayer(DynamicEntity *pl, int moveres, bool local);
extern bool collide(DynamicEntity *d, bool spawn, float drop, float rise);
extern void entinmap(DynamicEntity *d);
extern void setentphysics(int mml, int mmr);
extern void physicsframe();

// sound
extern void playsound(int n, const OFVector3D *loc = NULL);
extern void playsoundc(int n);
extern void initsound();
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
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







-
-
-
+
+
+











-
-
+
+







-
+



-
+




    OFString *smapname, int seconds, bool isfull);
extern void servermsinit(OFString *master, OFString *sdesc, bool listen);
extern void sendmaps(int n, OFString *mapname, int mapsize, uchar *mapdata);
extern ENetPacket *recvmap(int n);

// weapon
extern void selectgun(int a = -1, int b = -1, int c = -1);
extern void shoot(dynent *d, OFVector3D &to);
extern void shootv(int gun, OFVector3D &from, OFVector3D &to, dynent *d = 0,
    bool local = false);
extern void shoot(DynamicEntity *d, const OFVector3D &to);
extern void shootv(int gun, OFVector3D &from, OFVector3D &to,
    DynamicEntity *d = 0, bool local = false);
extern void createrays(OFVector3D &from, OFVector3D &to);
extern void moveprojectiles(float time);
extern void projreset();
extern OFString *playerincrosshair();
extern int reloadtime(int gun);

// monster
extern void monsterclear();
extern void restoremonsterstate();
extern void monsterthink();
extern void monsterrender();
extern dvector &getmonsters();
extern void monsterpain(dynent *m, int damage, dynent *d);
extern OFArray<DynamicEntity *> *getmonsters();
extern void monsterpain(DynamicEntity *m, int damage, DynamicEntity *d);
extern void endsp(bool allkilled);

// entities
extern void renderents();
extern void putitems(uchar *&p);
extern void checkquad(int time);
extern void checkitems();
extern void realpickup(int n, dynent *d);
extern void realpickup(int n, DynamicEntity *d);
extern void renderentities();
extern void resetspawns();
extern void setspawn(uint i, bool on);
extern void teleport(int n, dynent *d);
extern void teleport(int n, DynamicEntity *d);
extern void baseammo(int gun);

// rndmap
extern void perlinarea(block &b, int scale, int seed, int psize);

Modified src/renderextras.mm from [4ee74611a3] to [3c937d7f40].

1
2
3
4


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




+
+







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

#include "cube.h"

#import "DynamicEntity.h"

void
line(int x1, int y1, float z1, int x2, int y2, float z2)
{
	glBegin(GL_POLYGON);
	glVertex3f((float)x1, z1, (float)y1);
	glVertex3f((float)x1, z1, y1 + 0.01f);
	glVertex3f((float)x2, z2, y2 + 0.01f);
325
326
327
328
329
330
331
332

333
334
335
336
337
338
339
327
328
329
330
331
332
333

334
335
336
337
338
339
340
341







-
+








void
gl_drawhud(int w, int h, int curfps, int nquads, int curvert, bool underwater)
{
	readmatrices();
	if (editmode) {
		if (cursordepth == 1.0f)
			worldpos = player1->o;
			worldpos = player1.o;
		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
		cursorupdate();
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	}

	glDisable(GL_DEPTH_TEST);
	invertperspective();
377
378
379
380
381
382
383
384

385
386

387
388

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

386
387

388
389

390
391
392
393
394
395
396
397







-
+

-
+

-
+







	renderscores();
	if (!rendermenu()) {
		glBlendFunc(GL_SRC_ALPHA, GL_SRC_ALPHA);
		glBindTexture(GL_TEXTURE_2D, 1);
		glBegin(GL_QUADS);
		glColor3ub(255, 255, 255);
		if (crosshairfx) {
			if (player1->gunwait)
			if (player1.gunwait)
				glColor3ub(128, 128, 128);
			else if (player1->health <= 25)
			else if (player1.health <= 25)
				glColor3ub(255, 0, 0);
			else if (player1->health <= 50)
			else if (player1.health <= 50)
				glColor3ub(255, 128, 0);
		}
		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);
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
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







-
+


-
-
-
+
+
+
-
-
+





-
+

-
-
+
+







		draw_textf(@"wqd %d", 3200, 2460, 2, nquads);
		draw_textf(@"wvt %d", 3200, 2530, 2, curvert);
		draw_textf(@"evt %d", 3200, 2600, 2, xtraverts);
	}

	glPopMatrix();

	if (player1->state == CS_ALIVE) {
	if (player1.state == CS_ALIVE) {
		glPushMatrix();
		glOrtho(0, VIRTW / 2, VIRTH / 2, 0, -1, 1);
		draw_textf(@"%d", 90, 827, 2, player1->health);
		if (player1->armour)
			draw_textf(@"%d", 390, 827, 2, player1->armour);
		draw_textf(@"%d", 90, 827, 2, player1.health);
		if (player1.armour)
			draw_textf(@"%d", 390, 827, 2, player1.armour);
		draw_textf(
		    @"%d", 690, 827, 2, player1->ammo[player1->gunselect]);
		draw_textf(@"%d", 690, 827, 2, player1.ammo[player1.gunselect]);
		glPopMatrix();
		glPushMatrix();
		glOrtho(0, VIRTW, VIRTH, 0, -1, 1);
		glDisable(GL_BLEND);
		drawicon(128, 128, 20, 1650);
		if (player1->armour)
		if (player1.armour)
			drawicon(
			    (float)(player1->armourtype * 64), 0, 620, 1650);
		int g = player1->gunselect;
			    (float)(player1.armourtype * 64), 0, 620, 1650);
		int g = player1.gunselect;
		int r = 64;
		if (g > 2) {
			g -= 3;
			r = 128;
		}
		drawicon((float)(g * 64), (float)r, 1220, 1650);
		glPopMatrix();

Modified src/rendergl.mm from [f024dcac09] to [6011a74a0d].

1
2
3
4


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




+
+







// rendergl.cpp: core opengl rendering stuff

#include "cube.h"

#import "DynamicEntity.h"

#ifdef DARWIN
# define GL_COMBINE_EXT GL_COMBINE_ARB
# define GL_COMBINE_RGB_EXT GL_COMBINE_RGB_ARB
# define GL_SOURCE0_RBG_EXT GL_SOURCE0_RGB_ARB
# define GL_SOURCE1_RBG_EXT GL_SOURCE1_RGB_ARB
# define GL_RGB_SCALE_EXT GL_RGB_SCALE_ARB
#endif
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
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







-
-
-
+
+
+

-
-
-
-
+
+
+
+

















-
-
-
+
+
+





-
+










-
+
-
-
-
-
-
+
+
+
+

-















-
+
















-
+




















-
-
+
+







-
-
+
+







})

void
transplayer()
{
	glLoadIdentity();

	glRotated(player1->roll, 0.0, 0.0, 1.0);
	glRotated(player1->pitch, -1.0, 0.0, 0.0);
	glRotated(player1->yaw, 0.0, 1.0, 0.0);
	glRotated(player1.roll, 0.0, 0.0, 1.0);
	glRotated(player1.pitch, -1.0, 0.0, 0.0);
	glRotated(player1.yaw, 0.0, 1.0, 0.0);

	glTranslated(-player1->o.x,
	    (player1->state == CS_DEAD ? player1->eyeheight - 0.2f : 0) -
	        player1->o.z,
	    -player1->o.y);
	glTranslated(-player1.o.x,
	    (player1.state == CS_DEAD ? player1.eyeheight - 0.2f : 0) -
	        player1.o.z,
	    -player1.o.y);
}

VARP(fov, 10, 105, 120);

int xtraverts;

VAR(fog, 64, 180, 1024);
VAR(fogcolour, 0, 0x8099B3, 0xFFFFFF);

VARP(hudgun, 0, 1, 1);

OFString *hudgunnames[] = { @"hudguns/fist", @"hudguns/shotg",
	@"hudguns/chaing", @"hudguns/rocket", @"hudguns/rifle" };

void
drawhudmodel(int start, int end, float speed, int base)
{
	rendermodel(hudgunnames[player1->gunselect], start, end, 0, 1.0f,
	    player1->o.x, player1->o.z, player1->o.y, player1->yaw + 90,
	    player1->pitch, false, 1.0f, speed, 0, base);
	rendermodel(hudgunnames[player1.gunselect], start, end, 0, 1.0f,
	    player1.o.x, player1.o.z, player1.o.y, player1.yaw + 90,
	    player1.pitch, false, 1.0f, speed, 0, base);
}

void
drawhudgun(float fovy, float aspect, int farplane)
{
	if (!hudgun /*|| !player1->gunselect*/)
	if (!hudgun /*|| !player1.gunselect*/)
		return;

	glEnable(GL_CULL_FACE);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(fovy, aspect, 0.3f, farplane);
	glMatrixMode(GL_MODELVIEW);

	// glClear(GL_DEPTH_BUFFER_BIT);
	int rtime = reloadtime(player1->gunselect);
	int rtime = reloadtime(player1.gunselect);
	if (player1->lastaction &&
	    player1->lastattackgun == player1->gunselect &&
	    lastmillis - player1->lastaction < rtime) {
		drawhudmodel(7, 18, rtime / 18.0f, player1->lastaction);
	} else {
	if (player1.lastaction && player1.lastattackgun == player1.gunselect &&
	    lastmillis - player1.lastaction < rtime) {
		drawhudmodel(7, 18, rtime / 18.0f, player1.lastaction);
	} else
		drawhudmodel(6, 1, 100, 0);
	}

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(fovy, aspect, 0.15f, farplane);
	glMatrixMode(GL_MODELVIEW);

	glDisable(GL_CULL_FACE);
}

void
gl_drawframe(int w, int h, float curfps)
{
	float hf = hdr.waterlevel - 0.3f;
	float fovy = (float)fov * h / w;
	float aspect = w / (float)h;
	bool underwater = player1->o.z < hf;
	bool underwater = player1.o.z < hf;

	glFogi(GL_FOG_START, (fog + 64) / 8);
	glFogi(GL_FOG_END, fog);
	float fogc[4] = { (fogcolour >> 16) / 256.0f,
		((fogcolour >> 8) & 255) / 256.0f, (fogcolour & 255) / 256.0f,
		1.0f };
	glFogfv(GL_FOG_COLOR, fogc);
	glClearColor(fogc[0], fogc[1], fogc[2], 1.0f);

	if (underwater) {
		fovy += (float)sin(lastmillis / 1000.0) * 2.0f;
		aspect += (float)sin(lastmillis / 1000.0 + PI) * 0.1f;
		glFogi(GL_FOG_START, 0);
		glFogi(GL_FOG_END, (fog + 96) / 8);
	}

	glClear((player1->outsidemap ? GL_COLOR_BUFFER_BIT : 0) |
	glClear((player1.outsidemap ? GL_COLOR_BUFFER_BIT : 0) |
	    GL_DEPTH_BUFFER_BIT);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	int farplane = fog * 5 / 2;
	gluPerspective(fovy, aspect, 0.15f, farplane);
	glMatrixMode(GL_MODELVIEW);

	transplayer();

	glEnable(GL_TEXTURE_2D);

	int xs, ys;
	skyoglid = lookuptexture(DEFAULT_SKY, &xs, &ys);

	resetcubes();

	curvert = 0;
	strips.setsize(0);

	render_world(player1->o.x, player1->o.y, player1->o.z,
	    (int)player1->yaw, (int)player1->pitch, (float)fov, w, h);
	render_world(player1.o.x, player1.o.y, player1.o.z, (int)player1.yaw,
	    (int)player1.pitch, (float)fov, w, h);
	finishstrips();

	setupworld();

	renderstripssky();

	glLoadIdentity();
	glRotated(player1->pitch, -1.0, 0.0, 0.0);
	glRotated(player1->yaw, 0.0, 1.0, 0.0);
	glRotated(player1.pitch, -1.0, 0.0, 0.0);
	glRotated(player1.yaw, 0.0, 1.0, 0.0);
	glRotated(90.0, 1.0, 0.0, 0.0);
	glColor3f(1.0f, 1.0f, 1.0f);
	glDisable(GL_FOG);
	glDepthFunc(GL_GREATER);
	draw_envbox(14, fog * 4 / 3);
	glDepthFunc(GL_LESS);
	glEnable(GL_FOG);

Modified src/rendermd2.mm from [26b0b7264d] to [21d4efdffc].

1
2
3
4

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




+







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

#include "cube.h"

#import "DynamicEntity.h"
#import "MD2.h"
#import "MapModelInfo.h"

static OFMutableDictionary<OFString *, MD2 *> *mdllookup = nil;
static OFMutableArray<MD2 *> *mapmodels = nil;

static const int FIRSTMDL = 20;
96
97
98
99
100
101
102
103

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

104
105
106
107
108
109
110
111







-
+







void
rendermodel(OFString *mdl, int frame, int range, int tex, float rad, float x,
    float y, float z, float yaw, float pitch, bool teammate, float scale,
    float speed, int snap, int basetime)
{
	MD2 *m = loadmodel(mdl);

	if (isoccluded(player1->o.x, player1->o.y, x - rad, z - rad, rad * 2))
	if (isoccluded(player1.o.x, player1.o.y, x - rad, z - rad, rad * 2))
		return;

	delayedload(m);

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

Modified src/renderparticles.mm from [f46610835d] to [1d20594b8c].

1
2
3


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



+
+







// renderparticles.cpp

#include "cube.h"

#import "DynamicEntity.h"

const int MAXPARTICLES = 10500;
const int NUMPARTCUTOFF = 20;
struct particle {
	OFVector3D o, d;
	int fade, type;
	int millis;
52
53
54
55
56
57
58
59

60
61
62
63
64
65
66
54
55
56
57
58
59
60

61
62
63
64
65
66
67
68







-
+







}

void
render_particles(int time)
{
	if (demoplayback && demotracking) {
		OFVector3D nom = OFMakeVector3D(0, 0, 0);
		newparticle(player1->o, nom, 100000000, 8);
		newparticle(player1.o, nom, 100000000, 8);
	}

	glDepthMask(GL_FALSE);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_SRC_ALPHA);
	glDisable(GL_FOG);

Modified src/savegamedemo.mm from [518f13ae58] to [76a6b74abd].

1
2
3
4
5


6
7
8
9
10
11
12

13
14
15
16

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

14
15
16
17

18
19
20
21
22
23
24
25





+
+






-
+



-
+







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

#include "cube.h"

#import "DynamicEntity.h"

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

gzFile f = NULL;
static gzFile f = NULL;
bool demorecording = false;
bool demoplayback = false;
bool demoloading = false;
dvector playerhistory;
static OFMutableArray<DynamicEntity *> *playerhistory;
int democlientnum = 0;

void startdemo();

void
gzput(int i)
{
72
73
74
75
76
77
78
79
80

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


81
82
83
84
85
86
87
88







-
-
+







			gzputi(-1);
		gzclose(f);
	}
	f = NULL;
	demorecording = false;
	demoplayback = false;
	demoloading = false;
	loopv(playerhistory) zapdynent(playerhistory[i]);
	playerhistory.setsize(0);
	[playerhistory removeAllObjects];
}

void
stopifrecording()
{
	if (demorecording)
		stop();
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
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







+
-
-
+
+
+
+
+



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







		if (!f) {
			conoutf(@"could not write %@", IRI.string);
			return;
		}
		gzwrite(f, (void *)"CUBESAVE", 8);
		gzputc(f, islittleendian);
		gzputi(SAVEGAMEVERSION);
		OFData *data = [player1 dataBySerializing];
		gzputi(sizeof(dynent));
		gzwrite(f, getclientmap().UTF8String, _MAXDEFSTR);
		gzputi(data.count);
		char map[260] = { 0 };
		memcpy(map, getclientmap().UTF8String,
		    min(getclientmap().UTF8StringLength, 259));
		gzwrite(f, map, _MAXDEFSTR);
		gzputi(gamemode);
		gzputi(ents.length());
		loopv(ents) gzputc(f, ents[i].spawned);
		gzwrite(f, player1, sizeof(dynent));
		dvector &monsters = getmonsters();
		gzputi(monsters.length());
		loopv(monsters) gzwrite(f, monsters[i], sizeof(dynent));
		gzputi(players.length());
		loopv(players)
		gzwrite(f, data.items, data.count);
		OFArray<DynamicEntity *> *monsters = getmonsters();
		gzputi(monsters.count);
		for (DynamicEntity *monster in monsters) {
			data = [monster dataBySerializing];
			gzwrite(f, data.items, data.count);
		}
		gzputi(players.count);
		for (id player in players) {
		{
			gzput(players[i] == NULL);
			gzwrite(f, players[i], sizeof(dynent));
			gzput(player == [OFNull null]);
			data = [player dataBySerializing];
			gzwrite(f, data.items, data.count);
		}
	}
}

void
savegame(OFString *name)
{
159
160
161
162
163
164
165
166


167
168
169
170
171
172
173
167
168
169
170
171
172
173

174
175
176
177
178
179
180
181
182







-
+
+







		gzread(f, buf, 8);
		if (strncmp(buf, "CUBESAVE", 8))
			goto out;
		if (gzgetc(f) != islittleendian)
			goto out; // not supporting save->load accross
			          // incompatible architectures simpifies things
			          // a LOT
		if (gzgeti() != SAVEGAMEVERSION || gzgeti() != sizeof(dynent))
		if (gzgeti() != SAVEGAMEVERSION ||
		    gzgeti() != DynamicEntity.serializedSize)
			goto out;
		string mapname;
		gzread(f, mapname, _MAXDEFSTR);
		nextmode = gzgeti();
		@autoreleasepool {
			// continue below once map has been loaded and client &
			// server have updated
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
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







+
+
+
-
-
+
+
+


-
-
+
+

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






-
+

-
+
+







	{
		ents[i].spawned = gzgetc(f) != 0;
		if (ents[i].type == CARROT && !ents[i].spawned)
			trigger(ents[i].attr1, ents[i].attr2, true);
	}
	restoreserverstate(ents);

	OFMutableData *data =
	    [OFMutableData dataWithCapacity:DynamicEntity.serializedSize];
	[data increaseCountBy:DynamicEntity.serializedSize];
	gzread(f, player1, sizeof(dynent));
	player1->lastaction = lastmillis;
	gzread(f, data.mutableItems, data.count);
	[player1 setFromSerializedData:data];
	player1.lastaction = lastmillis;

	int nmonsters = gzgeti();
	dvector &monsters = getmonsters();
	if (nmonsters != monsters.length())
	OFArray<DynamicEntity *> *monsters = getmonsters();
	if (nmonsters != monsters.count)
		return loadgameout();
	loopv(monsters)
	{
		gzread(f, monsters[i], sizeof(dynent));
		monsters[i]->enemy =
		    player1; // lazy, could save id of enemy instead
		monsters[i]->lastaction = monsters[i]->trigger = lastmillis +
		    500; // also lazy, but no real noticable effect on game
		if (monsters[i]->state == CS_DEAD)
			monsters[i]->lastaction = 0;

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

	int nplayers = gzgeti();
	loopi(nplayers) if (!gzget())
	{
		dynent *d = getclient(i);
		DynamicEntity *d = getclient(i);
		assert(d);
		gzread(f, d, sizeof(dynent));
		gzread(f, data.mutableItems, data.count);
		[d setFromSerializedData:data];
	}

	conoutf(@"savegame restored");
	if (demoloading)
		startdemo();
	else
		stop();
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
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







-
+




+
















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







		starttime = lastmillis;
		ddamage = bdamage = 0;
	}
}
COMMAND(record, ARG_1STR)

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

void
demoblend(int damage)
{
	bdamage = damage;
}

void
incomingdemodata(uchar *buf, int len, bool extras)
{
	if (!demorecording)
		return;
	gzputi(lastmillis - starttime);
	gzputi(len);
	gzwrite(f, buf, len);
	gzput(extras);
	if (extras) {
		gzput(player1->gunselect);
		gzput(player1->lastattackgun);
		gzputi(player1->lastaction - starttime);
		gzputi(player1->gunwait);
		gzputi(player1->health);
		gzputi(player1->armour);
		gzput(player1->armourtype);
		loopi(NUMGUNS) gzput(player1->ammo[i]);
		gzput(player1->state);
		gzput(player1.gunselect);
		gzput(player1.lastattackgun);
		gzputi(player1.lastaction - starttime);
		gzputi(player1.gunwait);
		gzputi(player1.health);
		gzputi(player1.armour);
		gzput(player1.armourtype);
		loopi(NUMGUNS) gzput(player1.ammo[i]);
		gzput(player1.state);
		gzputi(bdamage);
		bdamage = 0;
		gzputi(ddamage);
		if (ddamage) {
			gzputv(dorig);
			ddamage = 0;
		}
344
345
346
347
348
349
350
351

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

367
368
369
370
371
372
373
374







-
+







COMMAND(demo, ARG_1STR)

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

VAR(demoplaybackspeed, 10, 100, 1000);
int
scaletime(int t)
{
372
373
374
375
376
377
378
379

380
381
382
383
384
385
386
387
388
389

390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411






















412
413
414

415
416
417
418
419




420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437

438
439
440
441
442


443
444
445
446
447
448
449
450
451
452
453











454
455
456
457
458
459
460
461
462
463
464
465
466
467



468
469
470
471



472
473

474
475
476
477
478
479
480
481




482
483
484


485
486
487
488
489






490

491
492


493
494
495



496
497

498
499
500
501
502
503




504
505
506
507
508



509
510
511
512
513
514
515
516
517





























518
519
520
521
522
523
524

525
526
527
528
529
530
531
532
533
534
535
536
388
389
390
391
392
393
394

395


396
397
398
399
400
401


402






















403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426

427
428




429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449

450
451
452
453


454
455











456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477



478
479
480




481
482
483


484

485
486
487
488
489


490
491
492
493



494
495





496
497
498
499
500
501
502
503


504
505



506
507
508
509

510
511
512




513
514
515
516
517
518
519


520
521
522









523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556

557
558
559
560
561
562
563
564
565
566
567
568
569
570







-
+
-
-






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


-
+

-
-
-
-
+
+
+
+

















-
+



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











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





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

+
-
-
+
+
-
-
-
+
+
+

-
+


-
-
-
-
+
+
+
+



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





-

+












void
startdemo()
{
	democlientnum = gzgeti();
	demoplayback = true;
	starttime = lastmillis;
	conoutf(@"now playing demo");
	dynent *d = getclient(democlientnum);
	setclient(democlientnum, [player1 copy]);
	assert(d);
	*d = *player1;
	readdemotime();
}

VAR(demodelaymsec, 0, 120, 500);

// spline interpolation
void
catmulrom(OFVector3D &z, OFVector3D &a, OFVector3D &b, OFVector3D &c, float s,
#define catmulrom(z, a, b, c, s, dest)           \
    OFVector3D &dest)
{
	OFVector3D t1 = b, t2 = c;

	vsub(t1, z);
	vmul(t1, 0.5f) vsub(t2, a);
	vmul(t2, 0.5f);

	float s2 = s * s;
	float s3 = s * s2;

	dest = a;
	OFVector3D t = b;

	vmul(dest, 2 * s3 - 3 * s2 + 1);
	vmul(t, -2 * s3 + 3 * s2);
	vadd(dest, t);
	vmul(t1, s3 - 2 * s2 + s);
	vadd(dest, t1);
	vmul(t2, s3 - s2);
	vadd(dest, t2);
}
	{                                        \
		OFVector3D t1 = b, t2 = c;       \
                                                 \
		vsub(t1, z);                     \
		vmul(t1, 0.5f);                  \
		vsub(t2, a);                     \
		vmul(t2, 0.5f);                  \
                                                 \
		float s2 = s * s;                \
		float s3 = s * s2;               \
                                                 \
		dest = a;                        \
		OFVector3D t = b;                \
                                                 \
		vmul(dest, 2 * s3 - 3 * s2 + 1); \
		vmul(t, -2 * s3 + 3 * s2);       \
		vadd(dest, t);                   \
		vmul(t1, s3 - 2 * s2 + s);       \
		vadd(dest, t1);                  \
		vmul(t2, s3 - s2);               \
		vadd(dest, t2);                  \
	}

void
fixwrap(dynent *a, dynent *b)
fixwrap(DynamicEntity *a, DynamicEntity *b)
{
	while (b->yaw - a->yaw > 180)
		a->yaw += 360;
	while (b->yaw - a->yaw < -180)
		a->yaw -= 360;
	while (b.yaw - a.yaw > 180)
		a.yaw += 360;
	while (b.yaw - a.yaw < -180)
		a.yaw -= 360;
}

void
demoplaybackstep()
{
	while (demoplayback && lastmillis >= playbacktime) {
		int len = gzgeti();
		if (len < 1 || len > MAXTRANS) {
			conoutf(
			    @"error: huge packet during demo play (%d)", len);
			stopreset();
			return;
		}
		uchar buf[MAXTRANS];
		gzread(f, buf, len);
		localservertoclient(buf, len); // update game state

		dynent *target = players[democlientnum];
		DynamicEntity *target = players[democlientnum];
		assert(target);

		int extras;
		if (extras = gzget()) // read additional client side state not
		                      // present in normal network stream
		// read additional client side state not present in normal
		// network stream
		{
			target->gunselect = gzget();
			target->lastattackgun = gzget();
			target->lastaction = scaletime(gzgeti());
			target->gunwait = gzgeti();
			target->health = gzgeti();
			target->armour = gzgeti();
			target->armourtype = gzget();
			loopi(NUMGUNS) target->ammo[i] = gzget();
			target->state = gzget();
			target->lastmove = playbacktime;
		if (extras = gzget()) {
			target.gunselect = gzget();
			target.lastattackgun = gzget();
			target.lastaction = scaletime(gzgeti());
			target.gunwait = gzgeti();
			target.health = gzgeti();
			target.armour = gzgeti();
			target.armourtype = gzget();
			loopi(NUMGUNS) target.ammo[i] = gzget();
			target.state = gzget();
			target.lastmove = playbacktime;
			if (bdamage = gzgeti())
				damageblend(bdamage);
			if (ddamage = gzgeti()) {
				gzgetv(dorig);
				particle_splash(3, ddamage, 1000, dorig);
			}
			// FIXME: set more client state here
		}

		// insert latest copy of player into history
		if (extras &&
		    (playerhistory.empty() ||
		        playerhistory.last()->lastupdate != playbacktime)) {
			dynent *d = newdynent();
		    (playerhistory.count == 0 ||
		        playerhistory.lastObject.lastupdate != playbacktime)) {
			DynamicEntity *d = [target copy];
			*d = *target;
			d->lastupdate = playbacktime;
			playerhistory.add(d);
			if (playerhistory.length() > 20) {
			d.lastupdate = playbacktime;
			[playerhistory addObject:d];
			if (playerhistory.count > 20)
				zapdynent(playerhistory[0]);
				playerhistory.remove(0);
				[playerhistory removeObjectAtIndex:0];
			}
		}

		readdemotime();
	}

	if (demoplayback) {
		int itime = lastmillis - demodelaymsec;
	if (!demoplayback)
		return;

	int itime = lastmillis - demodelaymsec;
		loopvrev(playerhistory) if (playerhistory[i]->lastupdate <
		    itime) // find 2 positions in
		           // history that surround
	// find 2 positions in history that surround interpolation time point
	size_t count = playerhistory.count;
		           // interpolation time point
		{
			dynent *a = playerhistory[i];
			dynent *b = a;
			if (i + 1 < playerhistory.length())
	for (ssize_t i = count - 1; i >= 0; i--) {
		if (playerhistory[i].lastupdate < itime) {
			DynamicEntity *a = playerhistory[i];
			DynamicEntity *b = a;

			if (i + 1 < playerhistory.count)
				b = playerhistory[i + 1];

			*player1 = *b;
			if (a != b) // interpolate pos & angles
			player1 = b;
			// interpolate pos & angles
			{
				dynent *c = b;
				if (i + 2 < playerhistory.length())
			if (a != b) {
				DynamicEntity *c = b;
				if (i + 2 < playerhistory.count)
					c = playerhistory[i + 2];
				dynent *z = a;
				DynamicEntity *z = a;
				if (i - 1 >= 0)
					z = playerhistory[i - 1];
				// if(a==z || b==c) printf("* %d\n",
				// lastmillis);
				float bf = (itime - a->lastupdate) /
				    (float)(b->lastupdate - a->lastupdate);
				// if(a==z || b==c)
				//	printf("* %d\n", lastmillis);
				float bf = (itime - a.lastupdate) /
				    (float)(b.lastupdate - a.lastupdate);
				fixwrap(a, player1);
				fixwrap(c, player1);
				fixwrap(z, player1);
				vdist(dist, v, z->o, c->o);
				if (dist < 16) // if teleport or spawn, dont't
				vdist(dist, v, z.o, c.o);
				// if teleport or spawn, don't interpolate
				if (dist < 16) {
				               // interpolate
				{
					catmulrom(z->o, a->o, b->o, c->o, bf,
					    player1->o);
					catmulrom(*(OFVector3D *)&z->yaw,
					    *(OFVector3D *)&a->yaw,
					    *(OFVector3D *)&b->yaw,
					    *(OFVector3D *)&c->yaw, bf,
					    *(OFVector3D *)&player1->yaw);
					catmulrom(
					    z.o, a.o, b.o, c.o, bf, player1.o);
					OFVector3D vz = OFMakeVector3D(
					    z.yaw, z.pitch, z.roll);
					OFVector3D va = OFMakeVector3D(
					    a.yaw, a.pitch, a.roll);
					OFVector3D vb = OFMakeVector3D(
					    b.yaw, b.pitch, b.roll);
					OFVector3D vc = OFMakeVector3D(
					    c.yaw, c.pitch, c.roll);
					OFVector3D vp1 =
					    OFMakeVector3D(player1.yaw,
					        player1.pitch, player1.roll);
					catmulrom(vz, va, vb, vc, bf, vp1);
					z.yaw = vz.x;
					z.pitch = vz.y;
					z.roll = vz.z;
					a.yaw = va.x;
					a.pitch = va.y;
					a.roll = va.z;
					b.yaw = vb.x;
					b.pitch = vb.y;
					b.roll = vb.z;
					c.yaw = vc.x;
					c.pitch = vc.y;
					c.roll = vc.z;
					player1.yaw = vp1.x;
					player1.pitch = vp1.y;
					player1.roll = vp1.z;
				}
				fixplayer1range();
			}
			break;
		}
		// if(player1->state!=CS_DEAD) showscores(false);
	}
	// if(player1->state!=CS_DEAD) showscores(false);
}

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

Modified src/sound.mm from [4f8b2c4c47] to [f6d08d4aed].

1
2
3
4


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




+
+







// sound.cpp: uses fmod on windows and sdl_mixer on unix (both had problems on
// the other platform)

#include "cube.h"

#import "DynamicEntity.h"

// #ifndef _WIN32    // NOTE: fmod not being supported for the moment as it does
// not allow stereo pan/vol updating during playback
#define USE_MIXER
// #endif

VARP(soundvol, 0, 255, 255);
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
171
172
173
174
175
176
177

178
179
180
181
182

183




184
185
186


187
188
189
190
191
192
193







-
+



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







VAR(stereo, 0, 1, 1);

static void
updatechanvol(int chan, const OFVector3D *loc)
{
	int vol = soundvol, pan = 255 / 2;
	if (loc) {
		vdist(dist, v, *loc, player1->o);
		vdist(dist, v, *loc, player1.o);
		vol -= (int)(dist * 3 * soundvol /
		    255); // simple mono distance attenuation
		if (stereo && (v.x != 0 || v.y != 0)) {
			// relative angle of sound along X-Y axis
			float yaw = -atan2(v.x, v.y) -
			float yaw =
			    player1->yaw *
			        (PI / 180.0f); // relative angle of
			                       // sound along X-Y axis
			pan = int(255.9f *
			    -atan2(v.x, v.y) - player1.yaw * (PI / 180.0f);
			// range is from 0 (left) to 255 (right)
			pan = int(255.9f * (0.5 * sin(yaw) + 0.5f));
			    (0.5 * sin(yaw) + 0.5f)); // range is from 0 (left)
			                              // to 255 (right)
		}
	}
	vol = (vol * MAXVOL) / 255;
#ifdef USE_MIXER
	Mix_Volume(chan, vol);
	Mix_SetPanning(chan, 255 - pan, pan);
#else

Modified src/weapon.mm from [652afaa3f5] to [c57ce4ac4b].

1
2
3
4

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




+







// weapon.cpp: all shooting and effects code

#include "cube.h"

#import "DynamicEntity.h"
#import "Projectile.h"

static const int MONSTERDAMAGEFACTOR = 4;
static const int SGRAYS = 20;
static const float SGSPREAD = 2;
static OFVector3D sg[SGRAYS];

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







-
-
+
+

-
+

-
+

-
+

-
+

-
+

-
+



-
+

-
+








void
selectgun(int a, int b, int c)
{
	if (a < -1 || b < -1 || c < -1 || a >= NUMGUNS || b >= NUMGUNS ||
	    c >= NUMGUNS)
		return;
	int s = player1->gunselect;
	if (a >= 0 && s != a && player1->ammo[a])
	int s = player1.gunselect;
	if (a >= 0 && s != a && player1.ammo[a])
		s = a;
	else if (b >= 0 && s != b && player1->ammo[b])
	else if (b >= 0 && s != b && player1.ammo[b])
		s = b;
	else if (c >= 0 && s != c && player1->ammo[c])
	else if (c >= 0 && s != c && player1.ammo[c])
		s = c;
	else if (s != GUN_RL && player1->ammo[GUN_RL])
	else if (s != GUN_RL && player1.ammo[GUN_RL])
		s = GUN_RL;
	else if (s != GUN_CG && player1->ammo[GUN_CG])
	else if (s != GUN_CG && player1.ammo[GUN_CG])
		s = GUN_CG;
	else if (s != GUN_SG && player1->ammo[GUN_SG])
	else if (s != GUN_SG && player1.ammo[GUN_SG])
		s = GUN_SG;
	else if (s != GUN_RIFLE && player1->ammo[GUN_RIFLE])
	else if (s != GUN_RIFLE && player1.ammo[GUN_RIFLE])
		s = GUN_RIFLE;
	else
		s = GUN_FIST;
	if (s != player1->gunselect)
	if (s != player1.gunselect)
		playsoundc(S_WEAPLOAD);
	player1->gunselect = s;
	player1.gunselect = s;
	// conoutf(@"%@ selected", (int)guns[s].name);
}

int
reloadtime(int gun)
{
	return guns[gun].attackdelay;
81
82
83
84
85
86
87
88

89
90

91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112



113
114
115
116
117
118
119
120
121
122
123



124

125
126


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

145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164

165
166
167
168

169
170
171
172



173
174
175


176
177
178
179
180
181

182

183
184

185
186

187
188
189
190
191
192
193
194

195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216





217

218
219


220
221
222
223
224
225









226
227
228
229

230

231
232

233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250


251
252
253
254
255
256
257
258
259

260
261

262
263
264
265


266
267

268
269
270
271




272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290

291
292

293
294

295

296
297
298
299
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
82
83
84
85
86
87
88

89
90

91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110



111
112
113
114
115
116
117
118
119
120




121
122
123
124
125


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

145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164

165
166
167
168

169
170
171


172
173
174
175


176
177
178
179
180
181
182
183
184

185
186

187
188

189
190
191
192
193
194
195
196

197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216



217
218
219
220
221
222
223


224
225






226
227
228
229
230
231
232
233
234
235
236
237
238
239

240
241

242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258


259
260
261
262
263
264
265
266
267
268

269


270




271
272
273
274
275




276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
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







-
+

-
+



















-
-
-
+
+
+







-
-
-
-
+
+
+

+
-
-
+
+

















-
+



















-
+



-
+


-
-
+
+
+

-
-
+
+






+
-
+

-
+

-
+







-
+



















-
-
-
+
+
+
+
+

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




+
-
+

-
+
















-
-
+
+








-
+
-
-
+
-
-
-
-
+
+


+
-
-
-
-
+
+
+
+



















+

-
+
-

+
-
+




















-
+












-
-
+
+




-
+



+
-
+

-
+

-
-
+
+

-
+

-
+









-
+

-
-
+
+

-
-
+
+

-
-
-
+
+
+

-
-
+
+


-
-
-
+
+
+






-
-
-
-
+
+
+
+

-
+




-
+


-
+

-
-
-
+
+
+


-
+

-
+


+
-
+
-
-
+
-
-
-
+
+


-
-
+
+
+

-
+


		sg[i] = to;
		vadd(sg[i], r);
	}
}

// if lineseg hits entity bounding box
bool
intersect(dynent *d, const OFVector3D &from, const OFVector3D &to)
intersect(DynamicEntity *d, const OFVector3D &from, const OFVector3D &to)
{
	OFVector3D v = to, w = d->o;
	OFVector3D v = to, w = d.o;
	const OFVector3D *p;
	vsub(v, from);
	vsub(w, from);
	float c1 = dotprod(w, v);

	if (c1 <= 0)
		p = &from;
	else {
		float c2 = dotprod(v, v);
		if (c2 <= c1)
			p = &to;
		else {
			float f = c1 / c2;
			vmul(v, f);
			vadd(v, from);
			p = &v;
		}
	}

	return (p->x <= d->o.x + d->radius && p->x >= d->o.x - d->radius &&
	    p->y <= d->o.y + d->radius && p->y >= d->o.y - d->radius &&
	    p->z <= d->o.z + d->aboveeye && p->z >= d->o.z - d->eyeheight);
	return (p->x <= d.o.x + d.radius && p->x >= d.o.x - d.radius &&
	    p->y <= d.o.y + d.radius && p->y >= d.o.y - d.radius &&
	    p->z <= d.o.z + d.aboveeye && p->z >= d.o.z - d.eyeheight);
}

OFString *
playerincrosshair()
{
	if (demoplayback)
		return NULL;
	loopv(players)
	{
		dynent *o = players[i];
		if (!o)

	for (id player in players) {
		if (player == [OFNull null])
			continue;

		if (intersect(o, player1->o, worldpos))
			return @(o->name);
		if (intersect(player, player1.o, worldpos))
			return [player name];
	}

	return nil;
}

static const size_t MAXPROJ = 100;
static Projectile *projs[MAXPROJ];

void
projreset()
{
	for (size_t i = 0; i < MAXPROJ; i++)
		projs[i].inuse = false;
}

void
newprojectile(OFVector3D &from, OFVector3D &to, float speed, bool local,
    dynent *owner, int gun)
    DynamicEntity *owner, int gun)
{
	for (size_t i = 0; i < MAXPROJ; i++) {
		Projectile *p = projs[i];

		if (p.inuse)
			continue;

		p.inuse = true;
		p.o = from;
		p.to = to;
		p.speed = speed;
		p.local = local;
		p.owner = owner;
		p.gun = gun;
		return;
	}
}

void
hit(int target, int damage, dynent *d, dynent *at)
hit(int target, int damage, DynamicEntity *d, DynamicEntity *at)
{
	if (d == player1)
		selfdamage(damage, at == player1 ? -1 : -2, at);
	else if (d->monsterstate)
	else if (d.monsterstate)
		monsterpain(d, damage, at);
	else {
		addmsg(1, 4, SV_DAMAGE, target, damage, d->lifesequence);
		playsound(S_PAIN1 + rnd(5), &d->o);
		addmsg(1, 4, SV_DAMAGE, target, damage, d.lifesequence);
		OFVector3D loc = d.o;
		playsound(S_PAIN1 + rnd(5), &loc);
	}
	particle_splash(3, damage, 1000, d->o);
	demodamage(damage, d->o);
	particle_splash(3, damage, 1000, d.o);
	demodamage(damage, d.o);
}

const float RL_RADIUS = 5;
const float RL_DAMRAD = 7; // hack

static void
radialeffect(
radialeffect(dynent *o, const OFVector3D &v, int cn, int qdam, dynent *at)
    DynamicEntity *o, const OFVector3D &v, int cn, int qdam, DynamicEntity *at)
{
	if (o->state != CS_ALIVE)
	if (o.state != CS_ALIVE)
		return;
	vdist(dist, temp, v, o->o);
	vdist(dist, temp, v, o.o);
	dist -= 2; // account for eye distance imprecision
	if (dist < RL_DAMRAD) {
		if (dist < 0)
			dist = 0;
		int damage = (int)(qdam * (1 - (dist / RL_DAMRAD)));
		hit(cn, damage, o, at);
		vmul(temp, (RL_DAMRAD - dist) * damage / 800);
		vadd(o->vel, temp);
		vadd(o.vel, temp);
	}
}

void
splash(Projectile *p, const OFVector3D &v, const OFVector3D &vold,
    int notthisplayer, int notthismonster, int qdam)
{
	particle_splash(0, 50, 300, v);
	p.inuse = false;
	if (p.gun != GUN_RL) {
		playsound(S_FEXPLODE, &v);
		// no push?
	} else {
		playsound(S_RLHIT, &v);
		newsphere(v, RL_RADIUS, 0);
		dodynlight(vold, v, 0, 0, p.owner);
		if (!p.local)
			return;
		radialeffect(player1, v, -1, qdam, p.owner);
		loopv(players)
		{
			if (i == notthisplayer)

		size_t i = 0;
		for (id player in players) {
			if (i == notthisplayer) {
				i++;
				continue;
			}
			dynent *o = players[i];
			if (!o)

			if (player != [OFNull null])
				continue;
			radialeffect(o, v, i, qdam, p.owner);
		}
		dvector &mv = getmonsters();
		loopv(mv) if (i != notthismonster)
		    radialeffect(mv[i], v, i, qdam, p.owner);
				radialeffect(player, v, i, qdam, p.owner);

			i++;
		}

		i = 0;
		for (DynamicEntity *monster in getmonsters())
			if (i != notthismonster)
				radialeffect(monster, v, i, qdam, p.owner);
	}
}

inline void
projdamage(
projdamage(dynent *o, Projectile *p, OFVector3D &v, int i, int im, int qdam)
    DynamicEntity *o, Projectile *p, OFVector3D &v, int i, int im, int qdam)
{
	if (o->state != CS_ALIVE)
	if (o.state != CS_ALIVE)
		return;
	if (intersect(o, p.o, v)) {
		splash(p, v, p.o, i, im, qdam);
		hit(i, qdam, o, p.owner);
	}
}

void
moveprojectiles(float time)
{
	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->monsterstate)
		int qdam = guns[p.gun].damage * (p.owner.quadmillis ? 4 : 1);
		if (p.owner.monsterstate)
			qdam /= MONSTERDAMAGEFACTOR;
		vdist(dist, v, p.o, p.to);
		float dtime = dist * 1000 / p.speed;
		if (time > dtime)
			dtime = time;
		vmul(v, time / dtime);
		vadd(v, p.o) if (p.local)
		{
			loopv(players)
			for (id player in players)
			{
				dynent *o = players[i];
				if (player != [OFNull null])
				if (!o)
					continue;
				projdamage(o, p, v, i, -1, qdam);
			}
					projdamage(player, p, v, i, -1, qdam);

			if (p.owner != player1)
				projdamage(player1, p, v, -1, -1, qdam);

			dvector &mv = getmonsters();
			loopv(mv) if (!vreject(mv[i]->o, v, 10.0f) &&
			    mv[i] != p.owner)
			    projdamage(mv[i], p, v, -1, i, qdam);
			for (DynamicEntity *monster in getmonsters())
				if (!vreject(monster.o, v, 10.0f) &&
				    monster != p.owner)
					projdamage(monster, p, v, -1, i, qdam);
		}
		if (p.inuse) {
			if (time == dtime)
				splash(p, v, p.o, -1, -1, qdam);
			else {
				if (p.gun == GUN_RL) {
					dodynlight(p.o, v, 0, 255, p.owner);
					particle_splash(5, 2, 200, v);
				} else {
					particle_splash(1, 1, 200, v);
					particle_splash(
					    guns[p.gun].part, 1, 1, v);
				}
			}
		}
		p.o = v;
	}
}

// create visual effect from a shot
void
shootv(int gun, OFVector3D &from, OFVector3D &to, dynent *d,
shootv(int gun, OFVector3D &from, OFVector3D &to, DynamicEntity *d, bool local)
    bool local) // create visual effect from a shot
{
	OFVector3D loc = d.o;
	playsound(guns[gun].sound, d == player1 ? NULL : &d->o);
	playsound(guns[gun].sound, d == player1 ? NULL : &loc);
	int pspeed = 25;
	switch (gun) {
	case GUN_FIST:
		break;

	case GUN_SG: {
		loopi(SGRAYS) particle_splash(0, 5, 200, sg[i]);
		break;
	}

	case GUN_CG:
		particle_splash(0, 100, 250, to);
		// particle_trail(1, 10, from, to);
		break;

	case GUN_RL:
	case GUN_FIREBALL:
	case GUN_ICEBALL:
	case GUN_SLIMEBALL:
		pspeed = guns[gun].projspeed;
		if (d->monsterstate)
		if (d.monsterstate)
			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);
		break;
	}
}

void
hitpush(int target, int damage, dynent *d, dynent *at, OFVector3D &from,
    OFVector3D &to)
hitpush(int target, int damage, DynamicEntity *d, DynamicEntity *at,
    OFVector3D &from, OFVector3D &to)
{
	hit(target, damage, d, at);
	vdist(dist, v, from, to);
	vmul(v, damage / dist / 50);
	vadd(d->vel, v);
	vadd(d.vel, v);
}

void
raydamage(
raydamage(dynent *o, OFVector3D &from, OFVector3D &to, dynent *d, int i)
    DynamicEntity *o, OFVector3D &from, OFVector3D &to, DynamicEntity *d, int i)
{
	if (o->state != CS_ALIVE)
	if (o.state != CS_ALIVE)
		return;
	int qdam = guns[d->gunselect].damage;
	if (d->quadmillis)
	int qdam = guns[d.gunselect].damage;
	if (d.quadmillis)
		qdam *= 4;
	if (d->monsterstate)
	if (d.monsterstate)
		qdam /= MONSTERDAMAGEFACTOR;
	if (d->gunselect == GUN_SG) {
	if (d.gunselect == GUN_SG) {
		int damage = 0;
		loop(r, SGRAYS) if (intersect(o, from, sg[r])) damage += qdam;
		if (damage)
			hitpush(i, damage, o, d, from, to);
	} else if (intersect(o, from, to))
		hitpush(i, qdam, o, d, from, to);
}

void
shoot(dynent *d, OFVector3D &targ)
shoot(DynamicEntity *d, const OFVector3D &targ)
{
	int attacktime = lastmillis - d->lastaction;
	if (attacktime < d->gunwait)
	int attacktime = lastmillis - d.lastaction;
	if (attacktime < d.gunwait)
		return;
	d->gunwait = 0;
	if (!d->attacking)
	d.gunwait = 0;
	if (!d.attacking)
		return;
	d->lastaction = lastmillis;
	d->lastattackgun = d->gunselect;
	if (!d->ammo[d->gunselect]) {
	d.lastaction = lastmillis;
	d.lastattackgun = d.gunselect;
	if (!d.ammo[d.gunselect]) {
		playsoundc(S_NOAMMO);
		d->gunwait = 250;
		d->lastattackgun = -1;
		d.gunwait = 250;
		d.lastattackgun = -1;
		return;
	}
	if (d->gunselect)
		d->ammo[d->gunselect]--;
	OFVector3D from = d->o;
	if (d.gunselect)
		d.ammo[d.gunselect]--;
	OFVector3D from = d.o;
	OFVector3D to = targ;
	from.z -= 0.2f; // below eye

	vdist(dist, unitv, from, to);
	vdiv(unitv, dist);
	OFVector3D kickback = unitv;
	vmul(kickback, guns[d->gunselect].kickamount * -0.01f);
	vadd(d->vel, kickback);
	if (d->pitch < 80.0f)
		d->pitch += guns[d->gunselect].kickamount * 0.05f;
	vmul(kickback, guns[d.gunselect].kickamount * -0.01f);
	vadd(d.vel, kickback);
	if (d.pitch < 80.0f)
		d.pitch += guns[d.gunselect].kickamount * 0.05f;

	if (d->gunselect == GUN_FIST || d->gunselect == GUN_BITE) {
	if (d.gunselect == GUN_FIST || d.gunselect == GUN_BITE) {
		vmul(unitv, 3); // punch range
		to = from;
		vadd(to, unitv);
	}
	if (d->gunselect == GUN_SG)
	if (d.gunselect == GUN_SG)
		createrays(from, to);

	if (d->quadmillis && attacktime > 200)
	if (d.quadmillis && attacktime > 200)
		playsoundc(S_ITEMPUP);
	shootv(d->gunselect, from, to, d, true);
	if (!d->monsterstate)
		addmsg(1, 8, SV_SHOT, d->gunselect, (int)(from.x * DMF),
	shootv(d.gunselect, from, to, d, true);
	if (!d.monsterstate)
		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;
	d.gunwait = guns[d.gunselect].attackdelay;

	if (guns[d->gunselect].projspeed)
	if (guns[d.gunselect].projspeed)
		return;

	size_t i = 0;
	loopv(players)
	for (id player in players) {
	{
		dynent *o = players[i];
		if (player != [OFNull null])
		if (!o)
			continue;
		raydamage(o, from, to, d, i);
			raydamage(player, from, to, d, i);
		i++;
	}

	dvector &v = getmonsters();
	loopv(v) if (v[i] != d) raydamage(v[i], from, to, d, -2);
	for (DynamicEntity *monster in getmonsters())
		if (monster != d)
			raydamage(monster, from, to, d, -2);

	if (d->monsterstate)
	if (d.monsterstate)
		raydamage(player1, from, to, d, -1);
}

Modified src/world.mm from [2ffb0794b1] to [1a4fc65de4].

1
2
3


4
5
6
7
8
9
10
11


12
13

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

17

18
19
20
21
22
23
24



+
+








+
+

-
+
-







// world.cpp: core map management stuff

#include "cube.h"

#import "DynamicEntity.h"

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

sqr *world = NULL;
int sfactor, ssize, cubicsize, mipsize;

header hdr;

// set all cubes with "tag" to space, if tag is 0 then reset ALL tagged cubes
// according to type
void
settag(int tag, int type) // set all cubes with "tag" to space, if tag is 0 then
settag(int tag, int type)
                          // reset ALL tagged cubes according to type
{
	int maxx = 0, maxy = 0, minx = ssize, miny = ssize;
	loop(x, ssize) loop(y, ssize)
	{
		sqr *s = S(x, y);
		if (s->tag) {
			if (tag) {
259
260
261
262
263
264
265
266

267
268
269
270
271
272
273
262
263
264
265
266
267
268

269
270
271
272
273
274
275
276







-
+







	float bdist = 99999;
	loopv(ents)
	{
		entity &e = ents[i];
		if (e.type == NOTUSED)
			continue;
		OFVector3D v = OFMakeVector3D(e.x, e.y, e.z);
		vdist(dist, t, player1->o, v);
		vdist(dist, t, player1.o, v);
		if (dist < bdist) {
			best = i;
			bdist = dist;
		}
	}
	return bdist == 99999 ? -1 : best;
}
341
342
343
344
345
346
347
348

349
350
351
352
353
354
355
344
345
346
347
348
349
350

351
352
353
354
355
356
357
358







-
+







	case MAPMODEL:
		e.attr4 = e.attr3;
		e.attr3 = e.attr2;
	case MONSTER:
	case TELEDEST:
		e.attr2 = (uchar)e.attr1;
	case PLAYERSTART:
		e.attr1 = (int)player1->yaw;
		e.attr1 = (int)player1.yaw;
		break;
	}
	addmsg(1, 10, SV_EDITENT, ents.length(), type, e.x, e.y, e.z, e.attr1,
	    e.attr2, e.attr3, e.attr4);
	ents.add(*((entity *)&e)); // unsafe!
	if (type == LIGHT)
		calclight();

Modified src/worldlight.mm from [ccc85287d7] to [9b1a521652].

1
2
3


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



+
+







// worldlight.cpp

#include "cube.h"

#import "DynamicEntity.h"

extern bool hasoverbright;

VAR(lightscale, 1, 4, 100);

void
lightray(float bx, float by,
197
198
199
200
201
202
203
204

205
206
207
208

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

206
207
208
209

210
211
212
213
214
215
216
217







-
+



-
+







		blockpaste(*backup);
		free(backup);
	}
}

void
dodynlight(const OFVector3D &vold, const OFVector3D &v, int reach, int strength,
    dynent *owner)
    DynamicEntity *owner)
{
	if (!reach)
		reach = dynlight;
	if (owner->monsterstate)
	if (owner.monsterstate)
		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!

Modified src/worldocull.mm from [fabee6f557] to [8d9314d707].

1
2
3
4


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




+
+







// worldocull.cpp: occlusion map and occlusion test

#include "cube.h"

#import "DynamicEntity.h"

#define NUMRAYS 512

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

void
22
23
24
25
26
27
28
29

30
31
32


33
34
35
36
37
38
39
24
25
26
27
28
29
30

31
32


33
34
35
36
37
38
39
40
41







-
+

-
-
+
+







computeraytable(float vx, float vy)
{
	if (!ocull)
		return;

	odist = getvar(@"fog") * 1.5f;

	float apitch = (float)fabs(player1->pitch);
	float apitch = (float)fabs(player1.pitch);
	float af = getvar(@"fov") / 2 + apitch / 1.5f + 3;
	float byaw = (player1->yaw - 90 + af) / 360 * PI2;
	float syaw = (player1->yaw - 90 - af) / 360 * PI2;
	float byaw = (player1.yaw - 90 + af) / 360 * PI2;
	float syaw = (player1.yaw - 90 - af) / 360 * PI2;

	loopi(NUMRAYS)
	{
		float angle = i * PI2 / NUMRAYS;
		if ((apitch > 45 // must be bigger if fov>120
		        || (angle < byaw && angle > syaw) ||
		        (angle < byaw - PI2 && angle > syaw - PI2) ||

Modified src/worldrender.mm from [b1342f3226] to [f6cb50b391].

1
2
3
4
5
6


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






+
+







// worldrender.cpp: goes through all cubes in top down quad tree fashion,
// determines what has to be rendered and how (depending on neighbouring cubes),
// then calls functions in rendercubes.cpp

#include "cube.h"

#import "DynamicEntity.h"

void
render_wall(sqr *o, sqr *s, int x1, int y1, int x2, int y2, int mip, sqr *d1,
    sqr *d2, bool topleft)
{
	if (SOLID(o) || o->type == SEMISOLID) {
		float c1 = s->floor;
		float c2 = s->floor;
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
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







-
+








+
-
+
-







	int ry = vyy + lodbot;

	float fsize = (float)(1 << mip);
	for (int ox = x; ox < xs; ox++) {
		// first collect occlusion information for this block
		for (int oy = y; oy < ys; oy++) {
			SWS(w, ox, oy, sz)->occluded =
			    isoccluded(player1->o.x, player1->o.y,
			    isoccluded(player1.o.x, player1.o.y,
			        (float)(ox << mip), (float)(oy << mip), fsize);
		}
	}

	int pvx = (int)vx >> mip;
	int pvy = (int)vy >> mip;
	if (pvx >= 0 && pvy >= 0 && pvx < sz && pvy < sz) {
		// SWS(w,vxx,vyy,sz)->occluded = 0;
		// player cell never occluded
		SWS(w, pvx, pvy, sz)->occluded =
		SWS(w, pvx, pvy, sz)->occluded = 0;
		    0; // player cell never occluded
	}

#define df(x) s->floor - (x->vdelta / 4.0f)
#define dc(x) s->ceil + (x->vdelta / 4.0f)

	// loop through the rect 3 times (for floor/ceil/walls seperately, to
	// facilitate dynamic stripify) for each we skip occluded cubes