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
// main.cpp: initialisation & main loop

#include "cube.h"



OF_APPLICATION_DELEGATE(Cube)

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

@implementation Cube



>
>







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

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



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

	initclient();
	// never returns if dedicated







>
>







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

		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);
		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;
			gl_drawframe(_width, _height, fps);
			player1->yaw -= 5;
		}

		gl_drawframe(_width, _height, fps);

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







|








|

|







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);
		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;
			gl_drawframe(_width, _height, fps);
			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
#import <ObjFW/ObjFW.h>

typedef struct dynent dynent;

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


|




|



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

@class DynamicEntity;

@interface Projectile: OFObject
@property (nonatomic) OFVector3D o, to;
@property (nonatomic) float speed;
@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
// client.cpp, mostly network related client game code

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


int
getclientnum()
{
	return clientnum;
}





>
>
|
|
|
|
>
|
|
>







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"

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
	enet_peer_throttle_configure(clienthost->peers,
	    throttle_interval * 1000, throttle_accel, throttle_decel);
}

void
newname(OFString *name)
{

	c2sinit = false;
	@autoreleasepool {


		strn0cpy(player1->name, name.UTF8String, 16);

	}
}
COMMANDN(name, newname, ARG_1STR)

void
newteam(OFString *name)
{

	c2sinit = false;
	@autoreleasepool {


		strn0cpy(player1->team, name.UTF8String, 5);

	}
}
COMMANDN(team, newteam, ARG_1STR)

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

void
connects(OFString *servername)
{
	disconnect(1); // reset state
	addserver(servername);







>
|
|
>
>
|
>







>
|
|
>
>
|
>







|
|







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;

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

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

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

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

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

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

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
		conoutf(@"disconnected");
	clienthost = NULL;
	connecting = 0;
	connattempts = 0;
	disconnecting = 0;
	clientnum = -1;
	c2sinit = false;
	player1->lifesequence = 0;
	loopv(players) zapdynent(players[i]);

	localdisconnect();

	if (!onlyclean) {
		stop();
		localconnect();
	}







|
|







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;
	[players removeAllObjects];

	localdisconnect();

	if (!onlyclean) {
		stop();
		localconnect();
	}
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
	disconnect(0, !disconnecting);
}

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

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







|







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(@"%@:\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
	if (clienthost) {
		enet_host_broadcast(clienthost, 0, (ENetPacket *)packet);
		enet_host_flush(clienthost);
	} else
		localclienttoserver((ENetPacket *)packet);
}


void
c2sinfo(dynent *d) // send update to the server
{
	@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







>

|







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

			// pack rest in 1 byte: strafe:2, move:2, onfloor:1,
			// state:3
			putint(p,
			    (d->strafe & 3) | ((d->move & 3) << 2) |
			        (((int)d->onfloor) << 4) |
			        ((editmode ? CS_EDITING : d->state) << 5));

			if (senditemstoserver) {
				packet->flags = ENET_PACKET_FLAG_RELIABLE;
				putint(p, SV_ITEMLIST);
				if (!m_noitems)
					putitems(p);
				putint(p, -1);
				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
			{

				packet->flags = ENET_PACKET_FLAG_RELIABLE;
				c2sinit = true;
				putint(p, SV_INITC2S);
				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;







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



|
|
|

















|
<
>



|
|
|







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


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

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

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

#include "cube.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(dynent *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;
	int basetime = -((intptr_t)d & 0xFFF);
	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;
		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;
		// scale = 1.2f;
	} else if (d->state == CS_EDITING) {
		n = 16;
	} else if (d->state == CS_LAGGED) {
		n = 17;
	} else if (d->monsterstate == M_ATTACKING) {
		n = 8;
	} else if (d->monsterstate == M_PAIN) {
		n = 10;
	} else if ((!d->move && !d->strafe) || !d->moving) {
		n = 12;
	} else if (!d->onfloor && d->timeinair > 100) {
		n = 18;
	} else {
		n = 14;
		speed = 1200 / d->maxspeed * scale;
		if (hellpig)
			speed = 300 / d->maxspeed;
	}
	if (hellpig) {
		n++;
		scale *= 32;
		mz -= 1.9f;
	}
	rendermodel(mdlname, frame[n], range[n], 0, 1.5f, 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);


}

// 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)
{
	@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))];

		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;


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

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

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

		if (teamsUsed == maxTeams)
			return;

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

void
renderscores()
{
	if (!scoreson)
		return;
	[scoreLines removeAllObjects];
	if (!demoplayback)
		renderscore(player1);


	loopv(players) if (players[i]) renderscore(players[i]);
	sortmenu();
	if (m_teammode) {
		teamsUsed = 0;


		loopv(players) addteamscore(players[i]);
		if (!demoplayback)
			addteamscore(player1);
		OFMutableString *teamScores = [[OFMutableString alloc] init];
		loopj(teamsUsed)
		{
			[teamScores appendFormat:@"[ %@: %d ]", teamName[j],
			            teamScore[j]];
		}
		menumanual(0, scoreLines.count, @"");
		@autoreleasepool {
			menumanual(0, scoreLines.count + 1, teamScores);
		}
	}
}




>
>













>
|



|

|








|
|












|

|

|

|

|

|

|



|

|






|
|







|
|
>
|
>
>
|
>
>
















|


|
|
|
|
|
|












|
>


|

<
<
<

<
|
<
<
|
|







|
|











>
>
|



>
>
|



|
<


<







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(
    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;
	int basetime = -((intptr_t)d & 0xFFF);
	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;
		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;
		// scale = 1.2f;
	} else if (d.state == CS_EDITING) {
		n = 16;
	} else if (d.state == CS_LAGGED) {
		n = 17;
	} else if (d.monsterstate == M_ATTACKING) {
		n = 8;
	} else if (d.monsterstate == M_PAIN) {
		n = 10;
	} else if ((!d.move && !d.strafe) || !d.moving) {
		n = 12;
	} else if (!d.onfloor && d.timeinair > 100) {
		n = 18;
	} else {
		n = 14;
		speed = 1200 / d.maxspeed * scale;
		if (hellpig)
			speed = 300 / d.maxspeed;
	}
	if (hellpig) {
		n++;
		scale *= 32;
		mz -= 1.9f;
	}
	rendermodel(mdlname, frame[n], range[n], 0, 1.5f, d.o.x, mz, d.o.y,
	    d.yaw + 90, d.pitch / 2, team, scale, speed, 0, basetime);
}

extern int democlientnum;

void
renderclients()
{
	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(DynamicEntity *d)
{
	@autoreleasepool {
		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];
static size_t teamsUsed;

void
addteamscore(DynamicEntity *d)
{



	@autoreleasepool {

		for (size_t i = 0; i < teamsUsed; i++) {


			if ([teamName[i] isEqual:d.team]) {
				teamScore[i] += d.frags;
				return;
			}
		}

		if (teamsUsed == maxTeams)
			return;

		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])
			renderscore(player);
	sortmenu();
	if (m_teammode) {
		teamsUsed = 0;
		for (id player in players)
			if (player != [OFNull null])
				addteamscore(player);
		if (!demoplayback)
			addteamscore(player1);
		OFMutableString *teamScores = [[OFMutableString alloc] init];
		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
// clientgame.cpp: core game related stuff

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







dynent *player1 = newdynent(); // our client
dvector players;               // other clients


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


void
spawnstate(dynent *d) // reset player state not persistent accross spawns
{
	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;
	if (m_noitems) {
		d->gunselect = GUN_RIFLE;
		d->armour = 0;
		if (m_noitemsrail) {
			d->health = 1;
			d->ammo[GUN_RIFLE] = 100;
		} else {
			if (gamemode == 12) {

				d->gunselect = GUN_FIST;
				return;
			} // eihrul's secret "instafist" mode

			d->health = 256;
			if (m_tarena) {
				int gun1 = rnd(4) + 1;
				baseammo(d->gunselect = gun1);
				for (;;) {
					int gun2 = rnd(4) + 1;
					if (gun1 != gun2) {
						baseammo(gun2);
						break;
					}
				}
			} else if (m_arena) // insta arena
			{

				d->ammo[GUN_RIFLE] = 100;

			} else // efficiency
			{
				loopi(4) baseammo(i + 1);
				d->gunselect = GUN_CG;
			}
			d->ammo[GUN_CG] /= 2;
		}
	} else {
		d->ammo[GUN_SG] = 5;
	}
}

dynent *
newdynent() // create a new blank player or monster
{
	dynent *d = (dynent *)OFAllocMemory(1, sizeof(dynent));
	d->o.x = 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;
	spawnstate(d);
	return d;
}

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

void
arenacount(dynent *d, int &alive, int &dead, char *&lastteam, bool &oneteam)

{
	if (d->state != CS_DEAD) {
		alive++;
		if (lastteam && strcmp(lastteam, d->team))
			oneteam = false;
		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;
		bool oneteam = true;

		loopv(players) if (players[i])
		    arenacount(players[i], 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;
		}
	}
}

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

extern int democlientnum;

void
otherplayers()
{


	loopv(players) if (players[i])
	{
		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))
			moveplayer(
			    players[i], 2, false); // use physics to extrapolate
			                           // player position


	}
}

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



>
>













>
>
>
>
>
>
|
|
>
















|

|
|
|
|
|
|
|


>

|


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

|
|

|
|


>
|

<
>
|


|







|
<
>
|
>
|
<

|

|

|
|
|
|
|
<


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












|
>

|

|

|
|

<

















|

>
|
|
>
|










|




<
<
<
<
<
<
<





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






|
|







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

// reset player state not persistent accross spawns
void
spawnstate(DynamicEntity *d)
{
	resetmovement(d);

	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;
		if (m_noitemsrail) {
			d.health = 1;
			d.ammo[GUN_RIFLE] = 100;
		} else {
			if (gamemode == 12) {
				// eihrul's secret "instafist" mode
				d.gunselect = GUN_FIST;
				return;

			}
			d.health = 256;
			if (m_tarena) {
				int gun1 = rnd(4) + 1;
				baseammo(d.gunselect = gun1);
				for (;;) {
					int gun2 = rnd(4) + 1;
					if (gun1 != gun2) {
						baseammo(gun2);
						break;
					}
				}
			} else if (m_arena) {

				// insta arena
				d.ammo[GUN_RIFLE] = 100;
			} else {
				// efficiency

				loopi(4) baseammo(i + 1);
				d.gunselect = GUN_CG;
			}
			d.ammo[GUN_CG] /= 2;
		}
	} else
		d.ammo[GUN_SG] = 5;
}

DynamicEntity *

newdynent() // create a new blank player or monster
{
	DynamicEntity *d = [[DynamicEntity alloc] init];
	d.o = OFMakeVector3D(0, 0, 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 = d.team = @"";
	d.blocked = false;
	d.lifesequence = 0;
	d.state = CS_ALIVE;
	spawnstate(d);
	return d;
}

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

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

}

int arenarespawnwait = 0;
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;
		OFString *lastteam = nil;
		bool oneteam = true;
		for (id player in players)
			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;
		}
	}
}








extern int democlientnum;

void
otherplayers()
{
	size_t i = 0;
	for (id player in players) {
		if (player != [OFNull null]) {

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

				// use physics to extrapolate player position
				moveplayer(player, 2, false);
		}
		i++;
	}
}

void
respawn()
{
	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
				                          // 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;
					moveplayer(player1, 10, false);
				} else if (!m_arena && !m_sp &&
				    lastmillis - player1->lastaction > 10000)
					respawn();
			} else if (!intermission) {
				moveplayer(player1, 20, true);
				checkitems();
			}

			c2sinfo(player1); // do this last, to reduce the
			                  // effective frame lag
		}
	}
	lastmillis = millis;
}

// brute force but effective way to find a free spawn spot in the map
void
entinmap(dynent *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;
		if (collide(d, true, 0, 0))
			return;
		d->o.x -= dx;
		d->o.y -= dy;
	}

	conoutf(@"can't find entity spawn spot! (%d, %d)", (int)d->o.x,
	    (int)d->o.y);
	// leave ent at original pos, possibly stuck
}

int spawncycle = -1;
int fixspawn = 2;


void
spawnplayer(dynent *d) // place at random spawn. also used by monsters!
{
	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.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->o.z = 4;
	}
	entinmap(d);
	spawnstate(d);
	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;                     \
	}

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

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

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

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

void
fixplayer1range()
{
	const float MAXPITCH = 90.0f;
	if (player1->pitch > MAXPITCH)
		player1->pitch = MAXPITCH;
	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)
		return;
	const float SENSF = 33.0f; // try match quake sens
	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)
{
	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;
	damage -= ad;
	float droll = damage / 0.5f;
	player1->roll += player1->roll > 0
	    ? droll
	    : (player1->roll < 0
	              ? -droll
	              : (rnd(2) ? droll
	                        : -droll)); // give player a kick depending
	                                    // on amount of damage
	if ((player1->health -= damage) <= 0) {
		if (actor == -2) {
			conoutf(@"you got killed by %s!", act->name);
		} else if (actor == -1) {
			actor = getclientnum();
			conoutf(@"you suicided!");
			addmsg(1, 2, SV_FRAGS, --player1->frags);
		} else {
			dynent *a = getclient(actor);
			if (a) {
				if (isteam(a->team, player1->team)) {
					conoutf(@"you got fragged by a "
					        @"teammate (%s)",
					    a->name);
				} else {
					conoutf(
					    @"you got fragged by %s", 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;
		playsound(S_DIE1 + rnd(2));
		spawnstate(player1);
		player1->lastaction = lastmillis;
	} else {
		playsound(S_PAIN6);
	}
}

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

dynent *
getclient(int cn) // ensure valid entity
{
	if (cn < 0 || cn >= MAXCLIENTS) {
		neterr(@"clientnum");
		return NULL;
	}


	while (cn >= players.length())
		players.add(NULL);

	return players[cn] ? players[cn] : (players[cn] = newdynent());












}

void
initclient()
{
	clientmap = @"";
	initclientnet();







|
|
|


|





>
|
<







|





|
|


|
<

>
|
<






>

|




|
|
<
|
|
|
|
|
<
<


|




|
|
|
|
|
|














|






|















|
|
|
|
|
|
|
|





|


|
|







|

|



<
|
>
|
|
|


|

|




|

|



|

|
|
|

|
|
|

|
<




|
|
|
|
|


|
|

<







|








|




|

>
>
|
|
>
|
>
>
>
>
>
>
>
>
>
>
>
>







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;
					moveplayer(player1, 10, false);
				} else if (!m_arena && !m_sp &&
				    lastmillis - player1.lastaction > 10000)
					respawn();
			} else if (!intermission) {
				moveplayer(player1, 20, true);
				checkitems();
			}
			// do this last, to reduce the effective frame lag
			c2sinfo(player1);

		}
	}
	lastmillis = millis;
}

// brute force but effective way to find a free spawn spot in the map
void
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;
		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 = old;

	}
	conoutf(
	    @"can't find entity spawn spot! (%d, %d)", (int)d.o.x, (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(DynamicEntity *d)
{
	int r = fixspawn-- > 0 ? 4 : rnd(10) + 1;
	loopi(r) spawncycle = findentity(PLAYERSTART, spawncycle + 1);
	if (spawncycle != -1) {
		d.o = OFMakeVector3D(
		    ents[spawncycle].x, ents[spawncycle].y, ents[spawncycle].z);

		d.yaw = ents[spawncycle].attr1;
		d.pitch = 0;
		d.roll = 0;
	} else
		d.o = OFMakeVector3D((float)ssize / 2, (float)ssize / 2, 4);


	entinmap(d);
	spawnstate(d);
	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;                    \
	}

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

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

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

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

void
fixplayer1range()
{
	const float MAXPITCH = 90.0f;
	if (player1.pitch > MAXPITCH)
		player1.pitch = MAXPITCH;
	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)
		return;
	const float SENSF = 33.0f; // try match quake sens
	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, DynamicEntity *act)
{
	if (player1.state != CS_ALIVE || editmode || intermission)
		return;
	damageblend(damage);
	demoblend(damage);

	// 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
	    ? droll
	    : (player1.roll < 0
	              ? -droll
	              : (rnd(2) ? droll
	                        : -droll)); // give player a kick depending
	                                    // on amount of damage
	if ((player1.health -= damage) <= 0) {
		if (actor == -2) {
			conoutf(@"you got killed by %@!", act.name);
		} else if (actor == -1) {
			actor = getclientnum();
			conoutf(@"you suicided!");
			addmsg(1, 2, SV_FRAGS, --player1.frags);
		} else {
			DynamicEntity *a = getclient(actor);
			if (a != nil) {
				if (isteam(a.team, player1.team))
					conoutf(@"you got fragged by a "
					        @"teammate (%@)",
					    a.name);
				else
					conoutf(
					    @"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;
		playsound(S_DIE1 + rnd(2));
		spawnstate(player1);
		player1.lastaction = lastmillis;
	} else
		playsound(S_PAIN6);

}

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

DynamicEntity *
getclient(int cn) // ensure valid entity
{
	if (cn < 0 || cn >= MAXCLIENTS) {
		neterr(@"clientnum");
		return nil;
	}
	if (players == nil)
		players = [[OFMutableArray alloc] init];
	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
		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;

	resetspawns();
	clientmap = name;
	if (editmode)
		toggleedit();
	setvar(@"gamespeed", 100);
	setvar(@"fog", 180);
	setvar(@"fogcolour", 0x8099B3);







|
>
|
>







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;
	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
// client processing of the incoming network stream

#include "cube.h"



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

void
neterr(OFString *s)




>
>







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
}

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


			d->o.y += dy < 0 ? r - fy : -(r - fy); // push aside
		else

			d->o.x += dx < 0 ? r - fx : -(r - fx);

	}
	int lagtime = lastmillis - d->lastupdate;
	if (lagtime) {
		d->plag = (d->plag * 5 + lagtime) / 6;
		d->lastupdate = lastmillis;
	}
}


void
localservertoclient(
    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;
	bool mapchanged = false;

	while (p < end)
		switch (type = getint(p)) {
		case SV_INITS2C: // welcome messsage from the server
		{
			cn = getint(p);







|

|
|
|
|
|


|

>
>
|

>
|
>

|

|
|



>

|
<









|







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(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 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 < fy)
			// push aside
			d.o = OFMakeVector3D(d.o.x,
			    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.y,
			    d.o.z);
	}
	int lagtime = lastmillis - d.lastupdate;
	if (lagtime) {
		d.plag = (d.plag * 5 + lagtime) / 6;
		d.lastupdate = lastmillis;
	}
}

// processes any updates from the server
void
localservertoclient(uchar *buf, int len)

{
	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;
	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
				}
			}
			if (getint(p) == 1)
				conoutf(@"server is FULL, disconnecting..");
			break;
		}


		case SV_POS: // position of another client
		{
			cn = getint(p);
			d = getclient(cn);
			if (!d)
				return;
			d->o.x = getint(p) / DMF;
			d->o.y = 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->vel.z = getint(p) / DVF;
			int f = getint(p);
			d->strafe = (f & 3) == 3 ? -1 : f & 3;
			f >>= 2;
			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 (!demoplayback)
				updatepos(d);
			break;
		}

		case SV_SOUND:

			playsound(getint(p), &d->o);
			break;


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

		case SV_MAPCHANGE:
			sgetstr();
			@autoreleasepool {
				changemapserv(@(text), getint(p));
			}







>
|
<


|

|
|
<
|
|
|
|
|
<

|

|
|

|
|
|





|
>
|

>



|







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: {
			// position of another client

			cn = getint(p);
			d = getclient(cn);
			if (d == nil)
				return;
			d.o = OFMakeVector3D(
			    getint(p) / DMF, getint(p) / DMF, getint(p) / DMF);

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

			int f = getint(p);
			d.strafe = (f & 3) == 3 ? -1 : f & 3;
			f >>= 2;
			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 (!demoplayback)
				updatepos(d);
			break;
		}

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

		case SV_TEXT:
			sgetstr();
			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
			    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
		{
			sgetstr();
			if (d->name[0]) {
				// already connected
				if (strcmp(d->name, text))
					conoutf(@"%s is now known as %s",
					    d->name, text);
			} else {
				// new client
				c2sinit =
				    false; // send new players my info again

				conoutf(@"connected: %s", text);
			}
			strcpy_s(d->name, text);
			sgetstr();
			strcpy_s(d->team, text);
			d->lifesequence = getint(p);
			break;
		}

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

		case SV_SHOT: {
			int gun = getint(p);
			OFVector3D s, e;
			s.x = getint(p) / DMF;
			s.y = getint(p) / DMF;







|
|
<

|

|
|
|


|
|
>


|

|
|





|

|
|
|







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

		// another client either connected or changed name/team
		case SV_INITC2S: {

			sgetstr();
			if (d.name.length > 0) {
				// already connected
				if (![d.name isEqual:@(text)])
					conoutf(@"%@ is now known as %s",
					    d.name, text);
			} else {
				// new client

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

		case SV_CDIS:
			cn = getint(p);
			if ((d = getclient(cn)) == nil)
				break;
			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
		}

		case SV_DAMAGE: {
			int target = getint(p);
			int damage = getint(p);
			int ls = getint(p);
			if (target == clientnum) {
				if (ls == player1->lifesequence)
					selfdamage(damage, cn, d);
			} else

				playsound(
				    S_PAIN1 + rnd(5), &getclient(target)->o);

			break;
		}

		case SV_DIED: {
			int actor = getint(p);
			if (actor == cn) {
				conoutf(@"%s suicided", d->name);
			} else if (actor == clientnum) {
				int frags;
				if (isteam(player1->team, d->team)) {
					frags = -1;
					conoutf(@"you fragged a teammate (%s)",
					    d->name);
				} else {
					frags = 1;
					conoutf(@"you fragged %s", d->name);
				}
				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);
					}
				}
			}

			playsound(S_DIE1 + rnd(2), &d->o);
			d->lifesequence++;
			break;
		}

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

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








|

|
>
|
<
>






|


|

|
|


|

|
>

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




|







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)
					selfdamage(damage, cn, d);
			} else {
				OFVector3D loc = getclient(target).o;
				playsound(S_PAIN1 + rnd(5), &loc);

			}
			break;
		}

		case SV_DIED: {
			int actor = getint(p);
			if (actor == cn) {
				conoutf(@"%@ suicided", d.name);
			} else if (actor == clientnum) {
				int frags;
				if (isteam(player1.team, d.team)) {
					frags = -1;
					conoutf(@"you fragged a teammate (%@)",
					    d.name);
				} else {
					frags = 1;
					conoutf(@"you fragged %@", d.name);
				}
				addmsg(
				    1, 2, SV_FRAGS, (player1.frags += frags));
			} else {
				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);
				}
			}

			OFVector3D loc = d.o;
			playsound(S_DIE1 + rnd(2), &loc);
			d.lifesequence++;
			break;
		}

		case SV_FRAGS:
			[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

		case SV_PING:
			getint(p);
			break;

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

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

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

		case SV_TIMEUP:







|
|




|







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

		case SV_CLIENTPING:
			[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
// 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"



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









>
>







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







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







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

































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


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

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;







<
<







<
|
>
|
>







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



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

// 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
#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 vdiv(u, f)            \
	{                     \
		(u).x /= (f); \
		(u).y /= (f); \


		(u).z /= (f); \
	}
#define vadd(u, v)              \
	{                       \


		(u).x += (v).x; \
		(u).y += (v).y; \
		(u).z += (v).z; \
	};

#define vsub(u, v)              \
	{                       \


		(u).x -= (v).x; \
		(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))







|
|
|
|
>
|

|
|
<
|
>
>
|

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







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)                                                   \
	{                                                            \
		OFVector3D tmp_ = u;                                 \
		float tmp2_ = f;                                     \
		u = OFMakeVector3D(                                  \
		    tmp_.x * tmp2_, tmp_.y * tmp2_, tmp_.z * tmp2_); \
	}
#define vdiv(u, f)                                                   \
	{                                                            \

		OFVector3D tmp_ = u;                                 \
		float tmp2_ = f;                                     \
		u = OFMakeVector3D(                                  \
		    tmp_.x / tmp2_, tmp_.y / tmp2_, tmp_.z / tmp2_); \
	}
#define vadd(u, v)                                                   \
	{                                                            \
		OFVector3D tmp_ = u;                                 \
		u = OFMakeVector3D(                                  \
		    tmp_.x + (v).x, tmp_.y + (v).y, tmp_.z + (v).z); \



	}
#define vsub(u, v)                                                   \
	{                                                            \
		OFVector3D tmp_ = u;                                 \
		u = OFMakeVector3D(                                  \
		    tmp_.x - (v).x, tmp_.y - (v).y, tmp_.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
#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)

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







|







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

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





>
>







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
sqr rtex;

VAR(editing, 0, 0, 1);

void
toggleedit()
{
	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;
		if (m_classicsp)
			monsterclear(); // all monsters back at their spawns for
			                // editing
		projreset();
	}
	Cube.sharedInstance.repeatsKeys = editmode;
	selset = false;







|










|







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

	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;

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

		if (OUTBORD(cx, cy))
			return;
	}







|















|
|







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;

	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;

		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
			p[0] = t;
			curedittex[i] = -1;
		}
	}
}

void
editdrag(bool isdown)
{
	if (dragging = isdown) {
		lastx = cx;
		lasty = cy;
		lasth = ch;
		selset = false;
		tofronttex();
	}
	makesel();







|

|







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

void
newent(OFString *what, OFString *a1, OFString *a2, OFString *a3, OFString *a4)
{
	EDITSEL;
	@autoreleasepool {
		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]);
	}
}








|







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,
		    (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
// entities.cpp: map entity related functions (pickup etc.)

#include "cube.h"


#import "MapModelInfo.h"

vector<entity> ents;

static OFString *entmdlnames[] = {
	@"shells",
	@"bullets",




>







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
	{ 150, 150, S_ITEMARMOUR },
	{ 20000, 30000, S_ITEMPUP },
};

void
baseammo(int gun)
{
	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)
{
	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);

}

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

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

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

	case I_QUAD:
		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)
{

	if (v < itemstats[ents[i].type - I_SHELLS]

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


void
teleport(int n, dynent *d) // also used by monsters
{
	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.y = ents[e].y;
			d->o.z = ents[e].z;
			d->yaw = ents[e].attr1;
			d->pitch = 0;
			d->vel.x = d->vel.y = d->vel.z = 0;

			entinmap(d);
			playsoundc(S_TELEPORT);
			break;
		}
	}
}

void
pickup(int n, dynent *d)
{
	int np = 1;

	loopv(players) if (players[i]) np++;


	np = np < 3 ? 4 : (np > 4 ? 2 : 3); // spawn times are dependent on
	                                    // number of players
	int ammo = np * 2;
	switch (ents[n].type) {
	case I_SHELLS:
		additem(n, d->ammo[1], ammo);
		break;
	case I_BULLETS:
		additem(n, d->ammo[2], ammo);
		break;
	case I_ROCKETS:
		additem(n, d->ammo[3], ammo);
		break;
	case I_ROUNDS:
		additem(n, d->ammo[4], ammo);
		break;
	case I_HEALTH:
		additem(n, d->health, np * 5);
		break;
	case I_BOOST:
		additem(n, d->health, 60);
		break;

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

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

	case I_QUAD:
		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







|





|
|







>



|



|


|


|


|


|


|



|
|



|
|



|








|

>
|
>
|
<
|
<
<
|



>

|











|
<
<
|
|
<
>








|


>
|
>
>
|
<



|


|


|


|


|


|




|

|



|



|







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

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

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, DynamicEntity *d)
{
	switch (ents[n].type) {
	case I_SHELLS:
		d.ammo[1] = radditem(n, d.ammo[1]);
		break;
	case I_BULLETS:
		d.ammo[2] = radditem(n, d.ammo[2]);
		break;
	case I_ROCKETS:
		d.ammo[3] = radditem(n, d.ammo[3]);
		break;
	case I_ROUNDS:
		d.ammo[4] = radditem(n, d.ammo[4]);
		break;
	case I_HEALTH:
		d.health = radditem(n, d.health);
		break;
	case I_BOOST:
		d.health = radditem(n, d.health);
		break;

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

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

	case I_QUAD:
		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)
{
	// 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, m_classicsp ? 100000 : spawnsec);


		ents[i].spawned = false;
	}
}

// also used by monsters
void
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 = OFMakeVector3D(ents[e].x, ents[e].y, ents[e].z);


			d.yaw = ents[e].attr1;
			d.pitch = 0;

			d.vel = OFMakeVector3D(0, 0, 0);
			entinmap(d);
			playsoundc(S_TELEPORT);
			break;
		}
	}
}

void
pickup(int n, DynamicEntity *d)
{
	int np = 1;
	for (id player in players)
		if (player != [OFNull null])
			np++;
	// spawn times are dependent on number of players
	np = np < 3 ? 4 : (np > 4 ? 2 : 3);

	int ammo = np * 2;
	switch (ents[n].type) {
	case I_SHELLS:
		additem(n, d.ammo[1], ammo);
		break;
	case I_BULLETS:
		additem(n, d.ammo[2], ammo);
		break;
	case I_ROCKETS:
		additem(n, d.ammo[3], ammo);
		break;
	case I_ROUNDS:
		additem(n, d.ammo[4], ammo);
		break;
	case I_HEALTH:
		additem(n, d.health, np * 5);
		break;
	case I_BOOST:
		additem(n, d.health, 60);
		break;

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

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

	case I_QUAD:
		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
	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);
		playsoundc(S_JUMPPAD);
		break;
	}
	}
}

void







|
|







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 = 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
		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);
		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;
		playsoundc(S_PUPOUT);
		conoutf(@"quad damage is over");
	}
}

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







|
|








|
|







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);
		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;
		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
executable('client',
  [
    'Alias.m',
    'Command.mm',
    'Cube.mm',

    'Identifier.m',
    'KeyMapping.m',
    'MD2.mm',
    'MapModelInfo.m',
    'Menu.m',
    'MenuItem.m',
    'Projectile.m',





>







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
// monster.cpp: implements AI for single player monsters, currently client only

#include "cube.h"


dvector monsters;

int nextmonster, spawnremain, numkilled, monstertotal, mtimestart;

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

dvector &
getmonsters()
{
	return monsters;
}


void
restoremonsterstate()
{

	loopv(monsters) if (monsters[i]->state == CS_DEAD) numkilled++;
} // for savegames



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




>
|
>
|



|





>



>
|
<
>
>







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"

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

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

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

// for savegames
void
restoremonsterstate()
{
	for (DynamicEntity *monster in monsters)
		if (monster.state == CS_DEAD)

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

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



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


bool
los(float lx, float ly, float lz, float bx, float by, float bz,
    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)







|






|
|
|
|
|
|
|
|


|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>
>
|
|
>
















>
>

|
<

<
|










|

|
<
<






>

|
<







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

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

{

	[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)
		{
			DynamicEntity *m = basicmonster(
			    ents[i].attr2, ents[i].attr1, M_SLEEP, 100, 0);
			m.o = OFMakeVector3D(ents[i].x, ents[i].y, 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, OFVector3D &v)

{
	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
		y += dy / (float)steps;
		i++;
	}
	return i >= steps;
}

bool
enemylos(dynent *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);
}

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


void
transition(dynent *m, int state, int moving, int n,
    int r) // n = at skill 0, n/2 = at skill 10, r = added random factor
{
	m->monsterstate = state;
	m->move = moving;
	n = n * 130 / 100;
	m->trigger = lastmillis + n - skill * (n / 16) + rnd(r + 1);
}

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


void
monsteraction(
    dynent *m) // main AI thinking routine, called every frame for every monster
{
	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
	{

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

	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;
		// try to jump over obstackle (rare)
		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))) {
			// patented "random walk" AI pathfinding (tm) ;)
			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 *
	        180 +
	    180;

	switch (m->monsterstate) {
	case M_PAIN:
	case M_ATTACKING:
	case M_SEARCH:
		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);
		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);

			playsound(S_GRUNT1 + rnd(2), &m->o);
		}
		break;
	}


	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);
			transition(m, M_ATTACKING, 0, 600, 0);
		}
		break;


	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) {
			OFVector3D target;
			if (!enemylos(
			        m, target)) // no visual contact anymore, let
			                    // monster get as close as possible
			                    // then search for player
			{
				transition(m, M_HOME, 1, 800, 500);

			} else // 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->attacktarget = target;
					transition(m, M_AIMING, 0,
					    monstertypes[m->mtype].lag, 10);

				} else // track player some more
				{
					transition(m, M_HOME, 1,
					    monstertypes[m->mtype].rate, 0);
				}
			}
		}
		break;
	}

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

void
monsterpain(dynent *m, int damage, dynent *d)
{
	// a monster hit us
	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->mtype == d->mtype ? m->anger / 2 : m->anger;
			if (anger >= monstertypes[m->mtype].loyalty)
				// monster infight if very angry
				m->enemy = d;
		}
	} else {
		// player hit us
		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;
		numkilled++;
		player1->frags = numkilled;

		playsound(monstertypes[m->mtype].diesound, &m->o);
		int remain = monstertotal - numkilled;
		if (remain > 0 && remain <= 5)
			conoutf(@"only %d monster(s) remaining", remain);
	} else

		playsound(monstertypes[m->mtype].painsound, &m->o);

}

void
endsp(bool allkilled)
{
	conoutf(allkilled ? @"you have cleared the map!"
	                  : @"you reached the exit!");







|

|
|
|









>

|
<

|
|

|



|

|
|
|
|


>

|
<

|
|
|

|
|
<
>
|
|
|

|
|
|


|
|


|
|

|
|

|
|

|





|
<


|



|










|






>
|




>
|
|
|
|
|
|




>
|
|
|
|

|
|
<
|
<

>
|
|
<

|
|
<
|

|
>
|
<

|
<









|


|



|
|
<
|

|



|
|


|
|
|
|

|
>
|



|
>
|
>







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

// 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(DynamicEntity *m, int state, int moving, int n, int r)

{
	m.monsterstate = state;
	m.move = moving;
	n = n * 130 / 100;
	m.trigger = lastmillis + n - skill * (n / 16) + rnd(r + 1);
}

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

// main AI thinking routine, called every frame for every monster
void
monsteraction(DynamicEntity *m)

{
	if (m.enemy.state == CS_DEAD) {
		m.enemy = player1;
		m.anger = 0;
	}
	normalise(m, m.targetyaw);
	// slowly turn monster towards his target

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

	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;
		// try to jump over obstackle (rare)
		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))) {
			// patented "random walk" AI pathfinding (tm) ;)
			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 * 180 +

	    180;

	switch (m.monsterstate) {
	case M_PAIN:
	case M_ATTACKING:
	case M_SEARCH:
		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);
		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), &loc);
		}
		break;
	}

	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);
			transition(m, M_ATTACKING, 0, 600, 0);
		}
		break;

	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) {
			OFVector3D target;
			if (!enemylos(m, target)) {
				// no visual contact anymore, let monster get

				// as close as possible then search for player

				transition(m, M_HOME, 1, 800, 500);
			} else {
				// 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.attacktarget = target;
					transition(m, M_AIMING, 0,
					    monstertypes[m.mtype].lag, 10);
				} else
					// track player some more

					transition(m, M_HOME, 1,
					    monstertypes[m.mtype].rate, 0);

			}
		}
		break;
	}

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

void
monsterpain(DynamicEntity *m, int damage, DynamicEntity *d)
{
	// a monster hit us
	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.mtype == d.mtype ? m.anger / 2 : m.anger;

			if (anger >= monstertypes[m.mtype].loyalty)
				// monster infight if very angry
				m.enemy = d;
		}
	} else {
		// player hit us
		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;
		numkilled++;
		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 {
		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
		nextmonster = lastmillis + 1000;
		spawnmonster();
	}

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

	loopv(ents) // equivalent of player entity touch, but only teleports are
	            // used
	{
		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)
		{
			if (monsters[i]->state == CS_DEAD) {
				if (lastmillis - monsters[i]->lastaction <
				    2000) {
					monsters[i]->move = 0;
					moveplayer(monsters[i], 1, false);
				}
			} else {
				v.z += monsters[i]->eyeheight;
				vdist(dist, t, monsters[i]->o, v);
				v.z -= monsters[i]->eyeheight;
				if (dist < 4)
					teleport(
					    (int)(&e - &ents[0]), monsters[i]);
			}
		}
	}


	loopv(monsters) if (monsters[i]->state == CS_ALIVE)
	    monsteraction(monsters[i]);
}

void
monsterrender()
{

	loopv(monsters) renderclient(monsters[i], false,
	    monstertypes[monsters[i]->mtype].mdlname, monsters[i]->mtype == 5,
	    monstertypes[monsters[i]->mtype].mscale / 10.0f);
}







|
|








|
<
|
|
<
|
|


|
|
|

<
|




>
|
|





>
|
|
|

372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389

390
391

392
393
394
395
396
397
398
399

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

	// 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);
		for (DynamicEntity *monster in monsters) {

			if (monster.state == CS_DEAD) {
				if (lastmillis - monster.lastaction < 2000) {

					monster.move = 0;
					moveplayer(monster, 1, false);
				}
			} else {
				v.z += monster.eyeheight;
				vdist(dist, t, monster.o, v);
				v.z -= monster.eyeheight;
				if (dist < 4)

					teleport((int)(&e - &ents[0]), monster);
			}
		}
	}

	for (DynamicEntity *monster in monsters)
		if (monster.state == CS_ALIVE)
			monsteraction(monster);
}

void
monsterrender()
{
	for (DynamicEntity *monster in monsters)
		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
// 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 "MapModelInfo.h"


bool
plcollide(dynent *d, dynent *o, float &headspace, float &hi,
    float &lo) // collide with player or monster
{
	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;

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

bool








>


>

|
|

|

|
|
|
|
|
|
|

|

|

|







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(
    DynamicEntity *d, DynamicEntity *o, float &headspace, float &hi, float &lo)
{
	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;

		if (fabs(o.o.z - d.o.z) < o.aboveeye + d.eyeheight)
			return false;
		if (d.monsterstate)
			return false; // hack
		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
		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
{
	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) {
			float mmz =
			    (float)(S(e.x, e.y)->floor + mmi.zoff + e.attr3);
			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)
{

	const float fx1 =
	    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 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;

	float minfloor = (d->monsterstate && !spawn && d->health > 100)
	    ? d->o.z - d->eyeheight - 4.5f
	    : -1000.0f; // big monsters are afraid of heights,
	                // unless angry :)

	for (int x = x1; x <= x2; x++)
		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:







|









|
|


|














|

>
|
<
<
|
|
|





>
|
|
|
<


|
<
>







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(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) {
			float mmz =
			    (float)(S(e.x, e.y)->floor + mmi.zoff + e.attr3);
			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(DynamicEntity *d, bool spawn, float drop, float rise)
{
	// figure out integer cube rectangle this entity covers in map
	const float fx1 = d.o.x - 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;


	for (int x = x1; x <= x2; x++)
		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
				hi = ceil;
			if (floor > lo)
				lo = floor;
			if (floor < minfloor)
				return false;
		}

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

	float headspace = 10;
	loopv(players) // collide with other players
	{
		dynent *o = players[i];
		if (!o || o == d)
			continue;
		if (!plcollide(d, o, 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

	loopv(v) if (!vreject(d->o, v[i]->o, 7.0f) && d != v[i] &&
	    !plcollide(d, v[i], 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;
	} else {
		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


			else if (space > -1.26f)
				d->o.z += rise; // rise thru stair


			else
				return false;
		} else {


			d->o.z -= min(min(drop, space), headspace); // gravity
		}

		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

		}

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

float
rad(float x)
{







|



|
<
|
<

|





<


>
|
|
>





|
>
|

|


|
>
>

|
>
>


|
>
>
|
|
<
|


|
|
>
|
>


|







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

	float headspace = 10;
	for (id player in players) {

		if (player == [OFNull null] || player == d)

			continue;
		if (!plcollide(d, player, headspace, hi, lo))
			return false;
	}
	if (d != player1)
		if (!plcollide(d, player1, headspace, hi, lo))
			return false;

	// 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())
		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) {
		// 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;
		if (space < 0) {
			if (space > -0.01)
				// stick on step
				d.o = OFMakeVector3D(
				    d.o.x, d.o.y, lo + d.eyeheight);
			else if (space > -1.26f)
				// rise thru stair
				d.o =
				    OFMakeVector3D(d.o.x, d.o.y, d.o.z + rise);
			else
				return false;
		} 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);
		if (space2 < 0) {
			if (space2 < -0.1)
				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;
	}
	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
}

// 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)
{
	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.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)(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 friction =
	    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;
	vmul(d, speed); // d is now frametime based velocity vector

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

	if (floating) // just apply velocity
	{

		vadd(pl->o, d);
		if (pl->jumpnext) {
			pl->jumpnext = false;
			pl->vel.z = 2;
		}

	} else // apply velocity with collision
	{
		if (pl->onfloor || water) {
			if (pl->jumpnext) {
				pl->jumpnext = false;
				pl->vel.z = 1.7f; // physics impulse upwards


				// dampen velocity change even harder, gives
				// correct water feel
				if (water) {
					pl->vel.x /= 8;
					pl->vel.y /= 8;
				}
				if (local)
					playsoundc(S_JUMP);
				else if (pl->monsterstate)

					playsound(S_JUMP, &pl->o);

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

		const float gravity = 20;
		const float f = 1.0f / moveres;
		// incorrect, but works fine
		float dropf = ((gravity - 1) + pl->timeinair / 15.0f);
		// float slowly down in water
		if (water) {
			dropf = 5;
			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


		loopi(moveres) // discrete steps collision detection & sliding
		{
			// try move forward
			pl->o.x += f * d.x;
			pl->o.y += f * d.y;
			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;
			if (collide(pl, false, drop, rise)) {
				d.x = 0;
				continue;
			}
			pl->o.x += f * d.x;
			// still stuck, try x axis

			pl->o.y -= f * d.y;
			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->o.y -= f * d.y;
			if (collide(pl, false, drop, rise)) {
				d.y = d.x = 0;
				continue;
			}
			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);
	}

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

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

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







|

|
|



|
|



|
|
|


|
|

<
|

|



|
|
>
|
|
|


|
|

|
<
>
|
|
|
|

>
|
<
|
|
|
|
>
>


|
|
|
<


|
>
|
>
|




|
>
|
|
>
|
|
|
<




|



|

<
|
|
|
>




|
|
<



|
|




<

>
|




<

|
|
<




|







|
|
|
|
|
|
|




|
|
|
|
|
|
|
|




|
>
|
|
|
>
|
>
|



|





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

	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.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)(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 friction =
	    water ? 20.0f : (pl.onfloor || floating ? 6.0f : 30.0f);

	const float fpsfric = friction / curtime * 20.0f;

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

	if (floating) {

		// just apply velocity
		vadd(pl.o, d);
		if (pl.jumpnext) {
			pl.jumpnext = false;
			pl.vel = OFMakeVector3D(pl.vel.x, pl.vel.y, 2);
		}
	} else {
		// apply velocity with collision

		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 = OFMakeVector3D(pl.vel.x / 8,
					    pl.vel.y / 8, pl.vel.z);

				if (local)
					playsoundc(S_JUMP);
				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) {
					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 slowly down in water
		if (water) {
			dropf = 5;
			pl.timeinair = 0;
		}

		// 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 = OFMakeVector3D(pl.o.x + f * d.x,
			    pl.o.y + f * d.y, 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 = OFMakeVector3D(pl.o.x - f * d.x, pl.o.y, pl.o.z);
			if (collide(pl, false, drop, rise)) {
				d.x = 0;
				continue;
			}

			// still stuck, try x axis
			pl.o = OFMakeVector3D(
			    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;
			}

			// try just dropping down
			pl.moving = false;
			pl.o = OFMakeVector3D(pl.o.x - f * d.x, pl.o.y, pl.o.z);

			if (collide(pl, false, drop, rise)) {
				d.y = d.x = 0;
				continue;
			}
			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);
	}

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

	// play sounds on water transitions

	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(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
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 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 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 OFString *getclientmap();
extern OFString *modestr(int n);
extern void zapdynent(dynent *&d);
extern dynent *getclient(int cn);
extern void timeupdate(int timeremain);
extern void resetmovement(dynent *d);
extern void fixplayer1range();

// clientextras
extern void renderclients();
extern void renderclient(
    dynent *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);
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);







|








>





|
|
|


|
|

|





|



















|







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(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(DynamicEntity *d);
extern void selfdamage(int damage, int actor, DynamicEntity *act);
extern DynamicEntity *newdynent();
extern OFString *getclientmap();
extern OFString *modestr(int n);
extern DynamicEntity *getclient(int cn);
extern void setclient(int cn, id client);
extern void timeupdate(int timeremain);
extern void resetmovement(DynamicEntity *d);
extern void fixplayer1range();

// clientextras
extern void renderclients();
extern void renderclient(
    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, 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
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 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 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();







|



|
|
|







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, const OFVector3D &o);
extern void demoblend(int damage);

// physics
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
    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 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 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 renderentities();
extern void resetspawns();
extern void setspawn(uint i, bool on);
extern void teleport(int n, dynent *d);
extern void baseammo(int gun);

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







|
|
|











|
|







|



|




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(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 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, DynamicEntity *d);
extern void renderentities();
extern void resetspawns();
extern void setspawn(uint i, bool on);
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
// renderextras.cpp: misc gl render code and the HUD

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




>
>







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

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;
		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
		cursorupdate();
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	}

	glDisable(GL_DEPTH_TEST);
	invertperspective();







|







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;
		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
	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)
				glColor3ub(128, 128, 128);
			else if (player1->health <= 25)
				glColor3ub(255, 0, 0);
			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);







|

|

|







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)
				glColor3ub(128, 128, 128);
			else if (player1.health <= 25)
				glColor3ub(255, 0, 0);
			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
		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) {
		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", 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)
			drawicon(
			    (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();







|


|
|
|
<
|





|

|
|







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) {
		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", 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)
			drawicon(
			    (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
// rendergl.cpp: core opengl rendering stuff

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




>
>







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

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

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

void
drawhudgun(float fovy, float aspect, int farplane)
{
	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);
	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;

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

	setupworld();

	renderstripssky();

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







|
|
|

|
|
|
|

















|
|
|





|










|
<
|
|
|
|

<















|
















|




















|
|







|
|







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

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

void
drawhudgun(float fovy, float aspect, int farplane)
{
	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);

	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;

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

	setupworld();

	renderstripssky();

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

#include "cube.h"


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

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

static const int FIRSTMDL = 20;




>







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

	delayedload(m);

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







|







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

#include "cube.h"



const int MAXPARTICLES = 10500;
const int NUMPARTCUTOFF = 20;
struct particle {
	OFVector3D o, d;
	int fade, type;
	int millis;



>
>







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
}

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

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








|







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

	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
// loading and saving of savegames & demos, dumps the spawn state of all
// mapents, the full state of all dynents (monsters + player)

#include "cube.h"



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

gzFile f = NULL;
bool demorecording = false;
bool demoplayback = false;
bool demoloading = false;
dvector playerhistory;
int democlientnum = 0;

void startdemo();

void
gzput(int i)
{





>
>






|



|







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

static gzFile f = NULL;
bool demorecording = false;
bool demoplayback = false;
bool demoloading = false;
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
			gzputi(-1);
		gzclose(f);
	}
	f = NULL;
	demorecording = false;
	demoplayback = false;
	demoloading = false;
	loopv(playerhistory) zapdynent(playerhistory[i]);
	playerhistory.setsize(0);
}

void
stopifrecording()
{
	if (demorecording)
		stop();







<
|







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;

	[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
		if (!f) {
			conoutf(@"could not write %@", IRI.string);
			return;
		}
		gzwrite(f, (void *)"CUBESAVE", 8);
		gzputc(f, islittleendian);
		gzputi(SAVEGAMEVERSION);

		gzputi(sizeof(dynent));

		gzwrite(f, getclientmap().UTF8String, _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)
		{
			gzput(players[i] == NULL);

			gzwrite(f, players[i], sizeof(dynent));
		}
	}
}

void
savegame(OFString *name)
{







>
|
>
|
>
>



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







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

			goto out;
		string mapname;
		gzread(f, mapname, _MAXDEFSTR);
		nextmode = gzgeti();
		@autoreleasepool {
			// continue below once map has been loaded and client &
			// server have updated







|
>







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() != 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
	{
		ents[i].spawned = gzgetc(f) != 0;
		if (ents[i].type == CARROT && !ents[i].spawned)
			trigger(ents[i].attr1, ents[i].attr2, true);
	}
	restoreserverstate(ents);




	gzread(f, player1, sizeof(dynent));

	player1->lastaction = lastmillis;

	int nmonsters = gzgeti();
	dvector &monsters = getmonsters();
	if (nmonsters != monsters.length())
		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;
	}
	restoremonsterstate();

	int nplayers = gzgeti();
	loopi(nplayers) if (!gzget())
	{
		dynent *d = getclient(i);
		assert(d);
		gzread(f, d, sizeof(dynent));

	}

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







>
>
>
|
>
|


|
|

<
|
>
|
|
|
|
|
>
|
|






|

|
>







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, data.mutableItems, data.count);
	[player1 setFromSerializedData:data];
	player1.lastaction = lastmillis;

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


	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())
	{
		DynamicEntity *d = getclient(i);
		assert(d);
		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
		starttime = lastmillis;
		ddamage = bdamage = 0;
	}
}
COMMAND(record, ARG_1STR)

void
demodamage(int damage, 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);
		gzputi(bdamage);
		bdamage = 0;
		gzputi(ddamage);
		if (ddamage) {
			gzputv(dorig);
			ddamage = 0;
		}







|




>
















|
|
|
|
|
|
|
|
|







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, 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);
		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
COMMAND(demo, ARG_1STR)

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

VAR(demoplaybackspeed, 10, 100, 1000);
int
scaletime(int t)
{







|







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();
	[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
void
startdemo()
{
	democlientnum = gzgeti();
	demoplayback = true;
	starttime = lastmillis;
	conoutf(@"now playing demo");
	dynent *d = getclient(democlientnum);
	assert(d);
	*d = *player1;
	readdemotime();
}

VAR(demodelaymsec, 0, 120, 500);

// spline interpolation
void
catmulrom(OFVector3D &z, OFVector3D &a, OFVector3D &b, OFVector3D &c, float s,
    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);
}

void
fixwrap(dynent *a, dynent *b)
{
	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];
		assert(target);

		int extras;
		if (extras = gzget()) // 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 (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();
			*d = *target;
			d->lastupdate = playbacktime;
			playerhistory.add(d);
			if (playerhistory.length() > 20) {
				zapdynent(playerhistory[0]);
				playerhistory.remove(0);
			}
		}

		readdemotime();
	}

	if (demoplayback) {


		int itime = lastmillis - demodelaymsec;
		loopvrev(playerhistory) if (playerhistory[i]->lastupdate <
		    itime) // find 2 positions in
		           // history that surround
		           // interpolation time point
		{


			dynent *a = playerhistory[i];
			dynent *b = a;

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

			*player1 = *b;
			if (a != b) // interpolate pos & angles
			{

				dynent *c = b;
				if (i + 2 < playerhistory.length())
					c = playerhistory[i + 2];
				dynent *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);
				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
				               // 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);


















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

}

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







|
<
<






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


|

|
|
|
|

















|



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











|
|
|
<
|
|
|
<
|
<





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

>
|
|
<
>
|
|

|


|
|
|
|



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





<

>












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");
	setclient(democlientnum, [player1 copy]);


	readdemotime();
}

VAR(demodelaymsec, 0, 120, 500);

// spline interpolation

#define catmulrom(z, a, b, c, s, 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);                  \
	}

void
fixwrap(DynamicEntity *a, DynamicEntity *b)
{
	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

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

		int extras;
		// read additional client side state not present in normal
		// network stream

		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.count == 0 ||
		        playerhistory.lastObject.lastupdate != playbacktime)) {
			DynamicEntity *d = [target copy];

			d.lastupdate = playbacktime;
			[playerhistory addObject:d];
			if (playerhistory.count > 20)

				[playerhistory removeObjectAtIndex:0];

		}

		readdemotime();
	}

	if (!demoplayback)
		return;

	int itime = lastmillis - demodelaymsec;

	// find 2 positions in history that surround interpolation time point
	size_t count = playerhistory.count;


	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;
			// interpolate pos & angles

			if (a != b) {
				DynamicEntity *c = b;
				if (i + 2 < playerhistory.count)
					c = playerhistory[i + 2];
				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);
				fixwrap(a, player1);
				fixwrap(c, player1);
				fixwrap(z, player1);
				vdist(dist, v, z.o, c.o);
				// if teleport or spawn, don't interpolate
				if (dist < 16) {


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

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
// sound.cpp: uses fmod on windows and sdl_mixer on unix (both had problems on
// the other platform)

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




>
>







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
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);
		vol -= (int)(dist * 3 * soundvol /
		    255); // simple mono distance attenuation
		if (stereo && (v.x != 0 || v.y != 0)) {

			float yaw = -atan2(v.x, v.y) -
			    player1->yaw *
			        (PI / 180.0f); // relative angle of
			                       // sound along X-Y axis
			pan = int(255.9f *
			    (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







|



>
|
<
|
|
|
<
<







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);
		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) - player1.yaw * (PI / 180.0f);
			// range is from 0 (left) to 255 (right)
			pan = int(255.9f * (0.5 * sin(yaw) + 0.5f));


		}
	}
	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
// weapon.cpp: all shooting and effects code

#include "cube.h"


#import "Projectile.h"

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





>







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

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

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







|
|

|

|

|

|

|

|



|

|







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])
		s = a;
	else if (b >= 0 && s != b && player1.ammo[b])
		s = b;
	else if (c >= 0 && s != c && player1.ammo[c])
		s = c;
	else if (s != GUN_RL && player1.ammo[GUN_RL])
		s = GUN_RL;
	else if (s != GUN_CG && player1.ammo[GUN_CG])
		s = GUN_CG;
	else if (s != GUN_SG && player1.ammo[GUN_SG])
		s = GUN_SG;
	else if (s != GUN_RIFLE && player1.ammo[GUN_RIFLE])
		s = GUN_RIFLE;
	else
		s = GUN_FIST;
	if (s != player1.gunselect)
		playsoundc(S_WEAPLOAD);
	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
		sg[i] = to;
		vadd(sg[i], r);
	}
}

// if lineseg hits entity bounding box
bool
intersect(dynent *d, const OFVector3D &from, const OFVector3D &to)
{
	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);
}

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

		if (intersect(o, player1->o, worldpos))
			return @(o->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)
{
	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)
{
	if (d == player1)
		selfdamage(damage, at == player1 ? -1 : -2, at);
	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);
	}
	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(dynent *o, const OFVector3D &v, int cn, int qdam, dynent *at)
{
	if (o->state != CS_ALIVE)
		return;
	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);
	}
}

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)

				continue;

			dynent *o = players[i];
			if (!o)
				continue;
			radialeffect(o, v, i, qdam, p.owner);
		}




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

inline void

projdamage(dynent *o, Projectile *p, OFVector3D &v, int i, int im, int qdam)
{
	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)
			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)
			{
				dynent *o = players[i];
				if (!o)
					continue;
				projdamage(o, 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);
		}
		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;
	}
}


void
shootv(int gun, OFVector3D &from, OFVector3D &to, dynent *d,
    bool local) // create visual effect from a shot
{

	playsound(guns[gun].sound, d == player1 ? NULL : &d->o);
	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)
			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)
{
	hit(target, damage, d, at);
	vdist(dist, v, from, to);
	vmul(v, damage / dist / 50);
	vadd(d->vel, v);
}

void

raydamage(dynent *o, OFVector3D &from, OFVector3D &to, dynent *d, int i)
{
	if (o->state != CS_ALIVE)
		return;
	int qdam = guns[d->gunselect].damage;
	if (d->quadmillis)
		qdam *= 4;
	if (d->monsterstate)
		qdam /= MONSTERDAMAGEFACTOR;
	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)
{
	int attacktime = lastmillis - d->lastaction;
	if (attacktime < d->gunwait)
		return;
	d->gunwait = 0;
	if (!d->attacking)
		return;
	d->lastaction = lastmillis;
	d->lastattackgun = d->gunselect;
	if (!d->ammo[d->gunselect]) {
		playsoundc(S_NOAMMO);
		d->gunwait = 250;
		d->lastattackgun = -1;
		return;
	}
	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;

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

	if (d->quadmillis && attacktime > 200)
		playsoundc(S_ITEMPUP);
	shootv(d->gunselect, from, to, d, true);
	if (!d->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;

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


	loopv(players)
	{
		dynent *o = players[i];
		if (!o)
			continue;
		raydamage(o, from, to, d, i);

	}

	dvector &v = getmonsters();

	loopv(v) if (v[i] != d) raydamage(v[i], from, to, d, -2);

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







|

|



















|
|
|







<
|
|
|

>
|
|

















|



















|



|


|
>
|

|
|






>
|

|

|







|



















<
|
>
>
|
>

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




>
|

|
















|
|








|
<
|
<
<
|
|


>
|
|
|
|



















>

|
<

>
|




















|












|
|




|



>
|

|

|
|

|

|









|

|
|

|
|

|
|
|

|
|


|
|
|






|
|
|
|

|




|


|

|
|
|


|

|


>
|
<
|
<
<
|
>


|
>
|

|


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(DynamicEntity *d, const OFVector3D &from, const OFVector3D &to)
{
	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);
}

OFString *
playerincrosshair()
{
	if (demoplayback)
		return NULL;


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

		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,
    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, DynamicEntity *d, DynamicEntity *at)
{
	if (d == player1)
		selfdamage(damage, at == player1 ? -1 : -2, at);
	else if (d.monsterstate)
		monsterpain(d, damage, at);
	else {
		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);
}

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

static void
radialeffect(
    DynamicEntity *o, const OFVector3D &v, int cn, int qdam, DynamicEntity *at)
{
	if (o.state != CS_ALIVE)
		return;
	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);
	}
}

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


		size_t i = 0;
		for (id player in players) {
			if (i == notthisplayer) {
				i++;
				continue;
			}

			if (player != [OFNull null])

				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(
    DynamicEntity *o, Projectile *p, OFVector3D &v, int i, int im, int qdam)
{
	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)
			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)
		{
			for (id player in players)

				if (player != [OFNull null])


					projdamage(player, p, v, i, -1, qdam);

			if (p.owner != player1)
				projdamage(player1, p, v, -1, -1, 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, DynamicEntity *d, bool local)

{
	OFVector3D loc = 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)
			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, 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);
}

void
raydamage(
    DynamicEntity *o, OFVector3D &from, OFVector3D &to, DynamicEntity *d, int i)
{
	if (o.state != CS_ALIVE)
		return;
	int qdam = guns[d.gunselect].damage;
	if (d.quadmillis)
		qdam *= 4;
	if (d.monsterstate)
		qdam /= MONSTERDAMAGEFACTOR;
	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(DynamicEntity *d, const OFVector3D &targ)
{
	int attacktime = lastmillis - d.lastaction;
	if (attacktime < d.gunwait)
		return;
	d.gunwait = 0;
	if (!d.attacking)
		return;
	d.lastaction = lastmillis;
	d.lastattackgun = d.gunselect;
	if (!d.ammo[d.gunselect]) {
		playsoundc(S_NOAMMO);
		d.gunwait = 250;
		d.lastattackgun = -1;
		return;
	}
	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;

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

	if (d.quadmillis && attacktime > 200)
		playsoundc(S_ITEMPUP);
	shootv(d.gunselect, from, to, d, true);
	if (!d.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;

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

	size_t i = 0;
	for (id player in players) {

		if (player != [OFNull null])


			raydamage(player, from, to, d, i);
		i++;
	}

	for (DynamicEntity *monster in getmonsters())
		if (monster != d)
			raydamage(monster, from, to, d, -2);

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

#include "cube.h"



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

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

header hdr;



void
settag(int tag, int type) // set all cubes with "tag" to space, if tag is 0 then
                          // 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) {



>
>








>
>

|
<







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)

{
	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
	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);
		if (dist < bdist) {
			best = i;
			bdist = dist;
		}
	}
	return bdist == 99999 ? -1 : best;
}







|







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







|







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;
		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
// worldlight.cpp

#include "cube.h"



extern bool hasoverbright;

VAR(lightscale, 1, 4, 100);

void
lightray(float bx, float by,



>
>







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
		blockpaste(*backup);
		free(backup);
	}
}

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







|



|







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,
    DynamicEntity *owner)
{
	if (!reach)
		reach = dynlight;
	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
// worldocull.cpp: occlusion map and occlusion test

#include "cube.h"



#define NUMRAYS 512

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

void




>
>







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
computeraytable(float vx, float vy)
{
	if (!ocull)
		return;

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

	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;

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







|

|
|







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 af = getvar(@"fov") / 2 + apitch / 1.5f + 3;
	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
// 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"



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;






>
>







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
	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,
			        (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;

		SWS(w, pvx, pvy, sz)->occluded =
		    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







|








>
|
<







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,
			        (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 = 0;

	}

#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