Cube  Check-in [b787ad5a04]

Overview
Comment:Clean up DynamicEntity
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: b787ad5a04fc238a69b9e48b3bdfccde416f636a021eef0d955445b5ab92252b
User & Date: js on 2025-03-22 23:10:13
Other Links: manifest | tags
Context
2025-03-23
02:03
Convert monster into a class check-in: e8f80b0482 user: js tags: trunk
2025-03-22
23:10
Clean up DynamicEntity check-in: b787ad5a04 user: js tags: trunk
21:29
Use OFColor instead of abusing OFVector3D check-in: e258bfb559 user: js tags: trunk
Changes

Modified src/Cube.m from [6edead03c0] to [5126c37a7e].

228
229
230
231
232
233
234
235

236
237
238
239
240
241
242
228
229
230
231
232
233
234

235
236
237
238
239
240
241
242







-
+








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

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

			computeraytable(player1.o.x, player1.o.y);
			computeraytable(player1.origin.x, player1.origin.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.

Modified src/DynamicEntity.h from [0a25294c06] to [52cd3f4f7c].

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






-
-
+



-
+

-
-
-
+
+
+




-
+

-
-
+
+

-
+



-
-
-
+
+
+



-
-
-
+
+
+



-
+





-
+




+


+
+
+

#import <ObjFW/ObjFW.h>

// players & monsters
@interface DynamicEntity: OFObject <OFCopying>
@property (class, readonly, nonatomic) size_t serializedSize;

// origin, velocity
@property (nonatomic) OFVector3D o, vel;
@property (nonatomic) OFVector3D origin, velocity;
// used as OFVector3D in one place
@property (nonatomic) float yaw, pitch, roll;
// cubes per second, 24 for player
@property (nonatomic) float maxspeed;
@property (nonatomic) float maxSpeed;
// from his eyes
@property (nonatomic) bool outsidemap;
@property (nonatomic) bool inwater;
@property (nonatomic) bool onfloor, jumpnext;
@property (nonatomic) bool outsideMap;
@property (nonatomic) bool inWater;
@property (nonatomic) bool onFloor, jumpNext;
@property (nonatomic) int move, strafe;
// see input code
@property (nonatomic) bool k_left, k_right, k_up, k_down;
// used for fake gravity
@property (nonatomic) int timeinair;
@property (nonatomic) int timeInAir;
// bounding box size
@property (nonatomic) float radius, eyeheight, aboveeye;
@property (nonatomic) int lastupdate, plag, ping;
@property (nonatomic) float radius, eyeHeight, aboveEye;
@property (nonatomic) int lastUpdate, lag, ping;
// sequence id for each respawn, used in damage test
@property (nonatomic) int lifesequence;
@property (nonatomic) int lifeSequence;
// one of CS_* below
@property (nonatomic) int state;
@property (nonatomic) int frags;
@property (nonatomic) int health, armour, armourtype, quadmillis;
@property (nonatomic) int gunselect, gunwait;
@property (nonatomic) int lastaction, lastattackgun, lastmove;
@property (nonatomic) int health, armour, armourType, quadMillis;
@property (nonatomic) int gunSelect, gunWait;
@property (nonatomic) int lastAction, lastAttackGun, lastMove;
@property (nonatomic) bool attacking;
@property (readonly, nonatomic) int *ammo;
// one of M_* below, M_NONE means human
@property (nonatomic) int monsterstate;
// see monster.cpp
@property (nonatomic) int mtype;
@property (nonatomic) int monsterState;
// see monster.m
@property (nonatomic) int monsterType;
// monster wants to kill this entity
@property (nonatomic) DynamicEntity *enemy;
// monster wants to look in this direction
@property (nonatomic) float targetyaw;
@property (nonatomic) float targetYaw;
// used by physics to signal ai
@property (nonatomic) bool blocked, moving;
// millis at which transition to another monsterstate takes place
@property (nonatomic) int trigger;
// delayed attacks
@property (nonatomic) OFVector3D attacktarget;
@property (nonatomic) OFVector3D attackTarget;
// how many times already hit by fellow monster
@property (nonatomic) int anger;
@property (copy, nonatomic) OFString *name, *team;

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

Modified src/DynamicEntity.m from [ace0df653e] to [1744394702].

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

230
231
232
233
234










































































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





-
+

-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
-
-
+
+
+


-
-
+
+

-
+


-
+





+
+
+
+
+











+
+
+
+
+
+
+
+
+
+
+












-
-
+
+



-
-
-
-
-
+
+
+
+
+






-
+

-
-
-
-
+
+
+
+

-
+




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





-
-
+
+

-
+



-
+












-
-
+
+



-
-
-
-
-
+
+
+
+
+






-
+

-
-
-
-
+
+
+
+

-
+




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

-
-
-
+
+
+



-
+




















-
-
+
+



-
-
-
-
-
+
+
+
+
+






-
+

-
-
-
-
+
+
+
+

-
+




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





-
-
-
+
+
+



-
+





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

#import "DynamicEntity.h"

#include "cube.h"

struct dynent {
	OFVector3D o, vel;
	OFVector3D origin, velocity;
	float yaw, pitch, roll;
	float maxspeed;
	bool outsidemap;
	bool inwater;
	bool onfloor, jumpnext;
	float maxSpeed;
	bool outsideMap;
	bool inWater;
	bool onFloor, jumpNext;
	int move, strafe;
	bool k_left, k_right, k_up, k_down;
	int timeinair;
	float radius, eyeheight, aboveeye;
	int lastupdate, plag, ping;
	int lifesequence;
	int timeInAir;
	float radius, eyeHeight, aboveEye;
	int lastUpdate, lag, ping;
	int lifeSequence;
	int state;
	int frags;
	int health, armour, armourtype, quadmillis;
	int gunselect, gunwait;
	int lastaction, lastattackgun, lastmove;
	int health, armour, armourType, quadMillis;
	int gunSelect, gunWait;
	int lastAction, lastAttackGun, lastMove;
	bool attacking;
	int ammo[NUMGUNS];
	int monsterstate;
	int mtype;
	int monsterState;
	int monsterType;
	void *enemy;
	float targetyaw;
	float targetYaw;
	bool blocked, moving;
	int trigger;
	OFVector3D attacktarget;
	OFVector3D attackTarget;
	int anger;
	char name[260], team[260];
};

@implementation DynamicEntity
+ (instancetype)entity
{
	return [[self alloc] init];
}

+ (size_t)serializedSize
{
	return sizeof(struct dynent);
}

- (instancetype)init
{
	self = [super init];

	_ammo = (int *)OFAllocZeroedMemory(NUMGUNS, sizeof(int));

	_yaw = 270;
	_maxSpeed = 22;
	_radius = 1.1f;
	_eyeHeight = 3.2f;
	_aboveEye = 0.7f;
	_lastUpdate = lastmillis;
	_name = _team = @"";
	_state = CS_ALIVE;

	[self resetToSpawnState];

	return self;
}

- (void)dealloc
{
	OFFreeMemory(_ammo);
}

- (id)copy
{
	DynamicEntity *copy = [[self.class alloc] init];

	copy->_o = _o;
	copy->_vel = _vel;
	copy->_origin = _origin;
	copy->_velocity = _velocity;
	copy->_yaw = _yaw;
	copy->_pitch = _pitch;
	copy->_roll = _roll;
	copy->_maxspeed = _maxspeed;
	copy->_outsidemap = _outsidemap;
	copy->_inwater = _inwater;
	copy->_onfloor = _onfloor;
	copy->_jumpnext = _jumpnext;
	copy->_maxSpeed = _maxSpeed;
	copy->_outsideMap = _outsideMap;
	copy->_inWater = _inWater;
	copy->_onFloor = _onFloor;
	copy->_jumpNext = _jumpNext;
	copy->_move = _move;
	copy->_strafe = _strafe;
	copy->_k_left = _k_left;
	copy->_k_right = _k_right;
	copy->_k_up = _k_up;
	copy->_k_down = _k_down;
	copy->_timeinair = _timeinair;
	copy->_timeInAir = _timeInAir;
	copy->_radius = _radius;
	copy->_eyeheight = _eyeheight;
	copy->_aboveeye = _aboveeye;
	copy->_lastupdate = _lastupdate;
	copy->_plag = _plag;
	copy->_eyeHeight = _eyeHeight;
	copy->_aboveEye = _aboveEye;
	copy->_lastUpdate = _lastUpdate;
	copy->_lag = _lag;
	copy->_ping = _ping;
	copy->_lifesequence = _lifesequence;
	copy->_lifeSequence = _lifeSequence;
	copy->_state = _state;
	copy->_frags = _frags;
	copy->_health = _health;
	copy->_armour = _armour;
	copy->_armourtype = _armourtype;
	copy->_quadmillis = _quadmillis;
	copy->_gunselect = _gunselect;
	copy->_gunwait = _gunwait;
	copy->_lastaction = _lastaction;
	copy->_lastattackgun = _lastattackgun;
	copy->_lastmove = _lastmove;
	copy->_armourType = _armourType;
	copy->_quadMillis = _quadMillis;
	copy->_gunSelect = _gunSelect;
	copy->_gunWait = _gunWait;
	copy->_lastAction = _lastAction;
	copy->_lastAttackGun = _lastAttackGun;
	copy->_lastMove = _lastMove;
	copy->_attacking = _attacking;

	for (size_t i = 0; i < NUMGUNS; i++)
		copy->_ammo[i] = _ammo[i];

	copy->_monsterstate = _monsterstate;
	copy->_mtype = _mtype;
	copy->_monsterState = _monsterState;
	copy->_monsterType = _monsterType;
	copy->_enemy = _enemy;
	copy->_targetyaw = _targetyaw;
	copy->_targetYaw = _targetYaw;
	copy->_blocked = _blocked;
	copy->_moving = _moving;
	copy->_trigger = _trigger;
	copy->_attacktarget = _attacktarget;
	copy->_attackTarget = _attackTarget;
	copy->_anger = _anger;

	copy->_name = [_name copy];
	copy->_team = [_team copy];

	return copy;
}

- (OFData *)dataBySerializing
{
	// This is frighteningly *TERRIBLE*, but the format used by existing
	// savegames.
	struct dynent data = { .o = _o,
		.vel = _vel,
	struct dynent data = { .origin = _origin,
		.velocity = _velocity,
		.yaw = _yaw,
		.pitch = _pitch,
		.roll = _roll,
		.maxspeed = _maxspeed,
		.outsidemap = _outsidemap,
		.inwater = _inwater,
		.onfloor = _onfloor,
		.jumpnext = _jumpnext,
		.maxSpeed = _maxSpeed,
		.outsideMap = _outsideMap,
		.inWater = _inWater,
		.onFloor = _onFloor,
		.jumpNext = _jumpNext,
		.move = _move,
		.strafe = _strafe,
		.k_left = _k_left,
		.k_right = _k_right,
		.k_up = _k_up,
		.k_down = _k_down,
		.timeinair = _timeinair,
		.timeInAir = _timeInAir,
		.radius = _radius,
		.eyeheight = _eyeheight,
		.aboveeye = _aboveeye,
		.lastupdate = _lastupdate,
		.plag = _plag,
		.eyeHeight = _eyeHeight,
		.aboveEye = _aboveEye,
		.lastUpdate = _lastUpdate,
		.lag = _lag,
		.ping = _ping,
		.lifesequence = _lifesequence,
		.lifeSequence = _lifeSequence,
		.state = _state,
		.frags = _frags,
		.health = _health,
		.armour = _armour,
		.armourtype = _armourtype,
		.quadmillis = _quadmillis,
		.gunselect = _gunselect,
		.gunwait = _gunwait,
		.lastaction = _lastaction,
		.lastattackgun = _lastattackgun,
		.lastmove = _lastmove,
		.armourType = _armourType,
		.quadMillis = _quadMillis,
		.gunSelect = _gunSelect,
		.gunWait = _gunWait,
		.lastAction = _lastAction,
		.lastAttackGun = _lastAttackGun,
		.lastMove = _lastMove,
		.attacking = _attacking,
		.monsterstate = _monsterstate,
		.mtype = _mtype,
		.targetyaw = _targetyaw,
		.monsterState = _monsterState,
		.monsterType = _monsterType,
		.targetYaw = _targetYaw,
		.blocked = _blocked,
		.moving = _moving,
		.trigger = _trigger,
		.attacktarget = _attacktarget,
		.attackTarget = _attackTarget,
		.anger = _anger };

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

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

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

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

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

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

	_o = d.o;
	_vel = d.vel;
	_origin = d.origin;
	_velocity = d.velocity;
	_yaw = d.yaw;
	_pitch = d.pitch;
	_roll = d.roll;
	_maxspeed = d.maxspeed;
	_outsidemap = d.outsidemap;
	_inwater = d.inwater;
	_onfloor = d.onfloor;
	_jumpnext = d.jumpnext;
	_maxSpeed = d.maxSpeed;
	_outsideMap = d.outsideMap;
	_inWater = d.inWater;
	_onFloor = d.onFloor;
	_jumpNext = d.jumpNext;
	_move = d.move;
	_strafe = d.strafe;
	_k_left = d.k_left;
	_k_right = d.k_right;
	_k_up = d.k_up;
	_k_down = d.k_down;
	_timeinair = d.timeinair;
	_timeInAir = d.timeInAir;
	_radius = d.radius;
	_eyeheight = d.eyeheight;
	_aboveeye = d.aboveeye;
	_lastupdate = d.lastupdate;
	_plag = d.plag;
	_eyeHeight = d.eyeHeight;
	_aboveEye = d.aboveEye;
	_lastUpdate = d.lastUpdate;
	_lag = d.lag;
	_ping = d.ping;
	_lifesequence = d.lifesequence;
	_lifeSequence = d.lifeSequence;
	_state = d.state;
	_frags = d.frags;
	_health = d.health;
	_armour = d.armour;
	_armourtype = d.armourtype;
	_quadmillis = d.quadmillis;
	_gunselect = d.gunselect;
	_gunwait = d.gunwait;
	_lastaction = d.lastaction;
	_lastattackgun = d.lastattackgun;
	_lastmove = d.lastmove;
	_armourType = d.armourType;
	_quadMillis = d.quadMillis;
	_gunSelect = d.gunSelect;
	_gunWait = d.gunWait;
	_lastAction = d.lastAction;
	_lastAttackGun = d.lastAttackGun;
	_lastMove = d.lastMove;
	_attacking = d.attacking;

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

	_monsterstate = d.monsterstate;
	_mtype = d.mtype;
	_targetyaw = d.targetyaw;
	_monsterState = d.monsterState;
	_monsterType = d.monsterType;
	_targetYaw = d.targetYaw;
	_blocked = d.blocked;
	_moving = d.moving;
	_trigger = d.trigger;
	_attacktarget = d.attacktarget;
	_attackTarget = d.attackTarget;
	_anger = d.anger;

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

- (void)resetMovement
{
	_k_left = false;
	_k_right = false;
	_k_up = false;
	_k_down = false;
	_jumpNext = false;
	_strafe = 0;
	_move = 0;
}

- (void)resetToSpawnState
{
	[self resetMovement];

	_velocity = OFMakeVector3D(0, 0, 0);
	_onFloor = false;
	_timeInAir = 0;
	_health = 100;
	_armour = 50;
	_armourType = A_BLUE;
	_quadMillis = 0;
	_lastAttackGun = _gunSelect = GUN_SG;
	_gunWait = 0;
	_attacking = false;
	_lastAction = 0;

	for (size_t i = 0; i < NUMGUNS; i++)
		_ammo[i] = 0;
	_ammo[GUN_FIST] = 1;

	if (m_noitems) {
		_gunSelect = GUN_RIFLE;
		_armour = 0;

		if (m_noitemsrail) {
			_health = 1;
			_ammo[GUN_RIFLE] = 100;
		} else {
			if (gamemode == 12) {
				// eihrul's secret "instafist" mode
				_gunSelect = GUN_FIST;
				return;
			}

			_health = 256;

			if (m_tarena) {
				_gunSelect = rnd(4) + 1;
				baseammo(_gunSelect);

				int gun2;
				do {
					gun2 = rnd(4) + 1;
				} while (gun2 != _gunSelect);

				baseammo(gun2);
			} else if (m_arena) {
				// insta arena
				_ammo[GUN_RIFLE] = 100;
			} else {
				// efficiency
				for (size_t i = 0; i < 4; i++)
					baseammo(i + 1);

				_gunSelect = GUN_CG;
			}

			_ammo[GUN_CG] /= 2;
		}
	} else
		_ammo[GUN_SG] = 5;
}
@end

Modified src/clientextras.m from [ff738c9cd3] to [ab4fb7df43].

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







-
+











-
-
+
+












-
+





-
+

-
+



-
+



-
+

-
+







-
-
+
+








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;
	float mz = d.origin.z - d.eyeHeight + 1.55f * scale;
	intptr_t tmp = (intptr_t)d;
	int basetime = -(tmp & 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;
		basetime = d.lastAction;
		int t = lastmillis - d.lastAction;
		if (t < 0 || t > 20000)
			return;
		if (t > (r - 1) * 100) {
			n += 4;
			if (t > (r + 10) * 100) {
				t -= (r + 10) * 100;
				mz -= t * t / 10000000000.0f * t;
			}
		}
		if (mz < -1000)
			return;
		// mdl = (((int)d>>6)&1)+1;
		// mz = d.o.z-d.eyeheight+0.2f;
		// mz = d.o.z-d.eyeHeight+0.2f;
		// scale = 1.2f;
	} else if (d.state == CS_EDITING) {
		n = 16;
	} else if (d.state == CS_LAGGED) {
		n = 17;
	} else if (d.monsterstate == M_ATTACKING) {
	} else if (d.monsterState == M_ATTACKING) {
		n = 8;
	} else if (d.monsterstate == M_PAIN) {
	} else if (d.monsterState == M_PAIN) {
		n = 10;
	} else if ((!d.move && !d.strafe) || !d.moving) {
		n = 12;
	} else if (!d.onfloor && d.timeinair > 100) {
	} else if (!d.onFloor && d.timeInAir > 100) {
		n = 18;
	} else {
		n = 14;
		speed = 1200 / d.maxspeed * scale;
		speed = 1200 / d.maxSpeed * scale;
		if (hellpig)
			speed = 300 / d.maxspeed;
			speed = 300 / d.maxSpeed;
	}
	if (hellpig) {
		n++;
		scale *= 32;
		mz -= 1.9f;
	}
	rendermodel(mdlname, frame[n], range[n], 0, 1.5f,
	    OFMakeVector3D(d.o.x, mz, d.o.y), d.yaw + 90, d.pitch / 2, team,
	    scale, speed, 0, basetime);
	    OFMakeVector3D(d.origin.x, mz, d.origin.y), d.yaw + 90, d.pitch / 2,
	    team, scale, speed, 0, basetime);
}

extern int democlientnum;

void
renderclients()
{
103
104
105
106
107
108
109
110

111
112
113
114
115
116
117
103
104
105
106
107
108
109

110
111
112
113
114
115
116
117







-
+







}

static OFMutableArray<OFString *> *scoreLines;

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

	if (scoreLines == nil)

Modified src/clientgame.m from [ca9306999a] to [f7cff8c331].

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







-
+

















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








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

void
initPlayers()
{
	player1 = newdynent();
	player1 = [[DynamicEntity alloc] init];
	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);
}

199
200
201
202
203
204
205
206

207
208
209
210
211
212
213
104
105
106
107
108
109
110

111
112
113
114
115
116
117
118







-
+







void
otherplayers()
{
	[players enumerateObjectsUsingBlock:^(id player, size_t i, bool *stop) {
		if (player == [OFNull null])
			return;

		const int lagtime = lastmillis - [player lastupdate];
		const int lagtime = lastmillis - [player lastUpdate];
		if (lagtime > 1000 && [player state] == CS_ALIVE) {
			[player setState:CS_LAGGED];
			return;
		}

		if (lagtime && [player state] != CS_DEAD &&
		    (!demoplayback || i != democlientnum))
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
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







-
+



-
+




















-
-
+
+


-
+

-
-
+
+













-
+





+
-
+

-
+










-
+







			// when our player moves
			gets2c();
		}
		otherplayers();
		if (!demoplayback) {
			monsterthink();
			if (player1.state == CS_DEAD) {
				if (lastmillis - player1.lastaction < 2000) {
				if (lastmillis - player1.lastAction < 2000) {
					player1.move = player1.strafe = 0;
					moveplayer(player1, 10, false);
				} else if (!m_arena && !m_sp &&
				    lastmillis - player1.lastaction > 10000)
				    lastmillis - player1.lastAction > 10000)
					respawn();
			} else if (!intermission) {
				moveplayer(player1, 20, true);
				checkitems();
			}
			// do this last, to reduce the effective frame lag
			c2sinfo(player1);
		}
	}
	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);
		OFVector3D old = d.origin;
		d.origin = OFAddVector3D(d.origin, OFMakeVector3D(dx, dy, 0));
		if (collide(d, true, 0, 0))
			return;
		d.o = old;
		d.origin = old;
	}
	conoutf(
	    @"can't find entity spawn spot! (%d, %d)", (int)d.o.x, (int)d.o.y);
	conoutf(@"can't find entity spawn spot! (%d, %d)", (int)d.origin.x,
	    (int)d.origin.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(
		d.origin = OFMakeVector3D(
		    ents[spawncycle].x, ents[spawncycle].y, ents[spawncycle].z);
		d.yaw = ents[spawncycle].attr1;
		d.pitch = 0;
		d.roll = 0;
	} else
		d.origin =
		d.o = OFMakeVector3D((float)ssize / 2, (float)ssize / 2, 4);
		    OFMakeVector3D((float)ssize / 2, (float)ssize / 2, 4);
	entinmap(d);
	spawnstate(d);
	[d resetToSpawnState];
	d.state = CS_ALIVE;
}

// movement input code

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

354
355
356
357
358
359
360
361

362
363
364
365
366
367
368
260
261
262
263
264
265
266

267
268
269
270
271
272
273
274







-
+







	else if ((player1.attacking = on))
		respawn();
}

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

COMMAND(backward, ARG_DOWN)
COMMAND(forward, ARG_DOWN)
COMMAND(left, ARG_DOWN)
COMMAND(right, ARG_DOWN)
402
403
404
405
406
407
408
409

410
411
412
413
414
415
416
308
309
310
311
312
313
314

315
316
317
318
319
320
321
322







-
+







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;
	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
436
437
438
439
440
441
442
443

444
445
446
447
448
449
450


451
452
453
454
455
456
457
342
343
344
345
346
347
348

349
350
351
352
353
354


355
356
357
358
359
360
361
362
363







-
+





-
-
+
+







				else
					conoutf(
					    @"you got fragged by %@", a.name);
			}
		}
		showscores(true);
		addmsg(1, 2, SV_DIED, actor);
		player1.lifesequence++;
		player1.lifeSequence++;
		player1.attacking = false;
		player1.state = CS_DEAD;
		player1.pitch = 0;
		player1.roll = 60;
		playsound(S_DIE1 + rnd(2), NULL);
		spawnstate(player1);
		player1.lastaction = lastmillis;
		[player1 resetToSpawnState];
		player1.lastAction = lastmillis;
	} else
		playsound(S_PAIN6, NULL);
}

void
timeupdate(int timeremain)
{
469
470
471
472
473
474
475

476
477
478
479








480
481
482
483
484
485
486
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







+


-
-
+
+
+
+
+
+
+
+







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

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

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

	return player;
}

void
setclient(int cn, id client)
{
	if (cn < 0 || cn >= MAXCLIENTS)
		neterr(@"clientnum");

Modified src/clients.m from [edb3579f73] to [90d8557e37].

138
139
140
141
142
143
144
145

146
147
148
149
150
151
152
138
139
140
141
142
143
144

145
146
147
148
149
150
151
152







-
+







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

	localdisconnect();

	if (!onlyclean) {
		stop();
		localconnect();
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
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







-
-
-
+
+
+




-
-
-
-
+
+
+
+


-
+







		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.origin.x * DMF));
		putint(&p, (int)(d.origin.y * DMF));
		putint(&p, (int)(d.origin.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, (int)(d.velocity.x * DVF));
		putint(&p, (int)(d.velocity.y * DVF));
		putint(&p, (int)(d.velocity.z * DVF));
		// pack rest in 1 byte: strafe:2, move:2, onFloor:1, state:3
		putint(&p,
		    (d.strafe & 3) | ((d.move & 3) << 2) |
		        (((int)d.onfloor) << 4) |
		        (((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);
335
336
337
338
339
340
341
342

343
344
345
346
347
348
349
335
336
337
338
339
340
341

342
343
344
345
346
347
348
349







-
+







		// 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);
			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;
			loopi(*(int *)[msg itemAtIndex:0])
			    putint(&p, *(int *)[msg itemAtIndex:i + 2]);

Modified src/clients2c.m from [e43832fc31] to [8bc911d5e6].

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







-
-
-
-
+
+
+
+





+
-
-
+
+

+
-
-
+
+
-

-
+

-
-
+
+







// 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 dx = player1.origin.x - d.origin.x;
	const float dy = player1.origin.y - d.origin.y;
	const float dz = player1.origin.z - d.origin.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.origin = OFAddVector3D(d.origin,
			d.o = OFMakeVector3D(d.o.x,
			    d.o.y + (dy < 0 ? r - fy : -(r - fy)), d.o.z);
			    OFMakeVector3D(
			        0, (dy < 0 ? r - fy : -(r - fy)), 0));
		else
			d.origin = OFAddVector3D(d.origin,
			d.o = OFMakeVector3D(
			    d.o.x + (dx < 0 ? r - fx : -(r - fx)), d.o.y,
			    OFMakeVector3D(
			        (dx < 0 ? r - fx : -(r - fx)), 0, 0));
			    d.o.z);
	}
	int lagtime = lastmillis - d.lastupdate;
	int lagtime = lastmillis - d.lastUpdate;
	if (lagtime) {
		d.plag = (d.plag * 5 + lagtime) / 6;
		d.lastupdate = lastmillis;
		d.lag = (d.lag * 5 + lagtime) / 6;
		d.lastUpdate = lastmillis;
	}
}

// processes any updates from the server
void
localservertoclient(uchar *buf, int len)
{
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
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







-
+






-
+




-
+


-
+







-
+







			d = getclient(cn);
			if (d == nil)
				return;
			OFVector3D tmp;
			tmp.x = getint(&p) / DMF;
			tmp.y = getint(&p) / DMF;
			tmp.z = getint(&p) / DMF;
			d.o = tmp;
			d.origin = tmp;
			d.yaw = getint(&p) / DAF;
			d.pitch = getint(&p) / DAF;
			d.roll = getint(&p) / DAF;
			tmp.x = getint(&p) / DVF;
			tmp.y = getint(&p) / DVF;
			tmp.z = getint(&p) / DVF;
			d.vel = tmp;
			d.velocity = tmp;
			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;
			d.onFloor = (f >> 2) & 1;
			int state = f >> 3;
			if (state == CS_DEAD && d.state != CS_DEAD)
				d.lastaction = lastmillis;
				d.lastAction = lastmillis;
			d.state = state;
			if (!demoplayback)
				updatepos(d);
			break;
		}

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

		case SV_TEXT:
			sgetstr();
			conoutf(@"%@:\f %s", d.name, text);
193
194
195
196
197
198
199
200

201
202
203
204
205
206
207
194
195
196
197
198
199
200

201
202
203
204
205
206
207
208







-
+







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

		case SV_CDIS:
			cn = getint(&p);
			if ((d = getclient(cn)) == nil)
				break;
226
227
228
229
230
231
232
233

234
235
236

237
238
239
240
241
242
243
227
228
229
230
231
232
233

234
235
236

237
238
239
240
241
242
243
244







-
+


-
+







		}

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

		case SV_DIED: {
			int actor = getint(&p);
263
264
265
266
267
268
269
270

271
272

273
274
275
276
277
278
279
264
265
266
267
268
269
270

271
272

273
274
275
276
277
278
279
280







-
+

-
+







						        @"teammate (%@)",
						    a.name, d.name);
					else
						conoutf(@"%@ fragged %@",
						    a.name, d.name);
				}
			}
			OFVector3D loc = d.o;
			OFVector3D loc = d.origin;
			playsound(S_DIE1 + rnd(2), &loc);
			d.lifesequence++;
			d.lifeSequence++;
			break;
		}

		case SV_FRAGS:
			[players[cn] setFrags:getint(&p)];
			break;

Modified src/cube.h from [3a60f1d5c8] to [d202a9fdbd].

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
277
278
279
280
281
282
283

284







285







286






287





288
289
290
291
292
293
294







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







#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)                                                   \
#define vmul(u, f) u = OFMultiplyVector3D(u, OFMakeVector3D(f, f, f))
	{                                                            \
		OFVector3D tmp_ = u;                                 \
		float tmp2_ = f;                                     \
		u = OFMakeVector3D(                                  \
		    tmp_.x * tmp2_, tmp_.y * tmp2_, tmp_.z * tmp2_); \
	}
#define vdiv(u, f)                                                   \
#define vdiv(u, f) u = OFDivideVector3D(u, OFMakeVector3D(f, f, f))
	{                                                            \
		OFVector3D tmp_ = u;                                 \
		float tmp2_ = f;                                     \
		u = OFMakeVector3D(                                  \
		    tmp_.x / tmp2_, tmp_.y / tmp2_, tmp_.z / tmp2_); \
	}
#define vadd(u, v)                                                   \
#define vadd(u, v) u = OFAddVector3D(u, v)
	{                                                            \
		OFVector3D tmp_ = u;                                 \
		u = OFMakeVector3D(                                  \
		    tmp_.x + (v).x, tmp_.y + (v).y, tmp_.z + (v).z); \
	}
#define vsub(u, v)                                                   \
#define vsub(u, v) u = OFSubtractVector3D(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))

Modified src/editing.m from [72ec7780d9] to [d8eb643406].

165
166
167
168
169
170
171
172
173



174
175
176
177
178
179
180
165
166
167
168
169
170
171


172
173
174
175
176
177
178
179
180
181







-
-
+
+
+








	if (OUTBORD(cx, cy))
		return;
	struct 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;
		// find right wall cube
		x += (x > player1.origin.x ? 0.5f : -0.5f);
		y += (y > player1.origin.y ? 0.5f : -0.5f);

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

		if (OUTBORD(cx, cy))
			return;
	}
618
619
620
621
622
623
624
625

626
627
628
629
630
631
632
619
620
621
622
623
624
625

626
627
628
629
630
631
632
633







-
+







	loopselxy(s->tag = tag);
}

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

COMMANDN(select, selectpos, ARG_4INT)
COMMAND(edittag, ARG_1INT)
COMMAND(replace, ARG_NONE)

Modified src/entities.m from [415631efe6] to [a819c302d8].

167
168
169
170
171
172
173
174

175
176
177
178
179

180
181
182
183

184
185
186
187
188
189
190
167
168
169
170
171
172
173

174
175
176
177
178

179
180
181
182

183
184
185
186
187
188
189
190







-
+




-
+



-
+







		break;
	case I_BOOST:
		d.health = radditem(n, d.health);
		break;

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

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

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

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

210
211
212
213
214
215
216

217

218
219
220

221
222
223
224
225
226
227
210
211
212
213
214
215
216
217

218
219
220

221
222
223
224
225
226
227
228







+
-
+


-
+







		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.origin =
			d.o = OFMakeVector3D(ents[e].x, ents[e].y, ents[e].z);
			    OFMakeVector3D(ents[e].x, ents[e].y, ents[e].z);
			d.yaw = ents[e].attr1;
			d.pitch = 0;
			d.vel = OFMakeVector3D(0, 0, 0);
			d.velocity = OFMakeVector3D(0, 0, 0);
			entinmap(d);
			playsoundc(S_TELEPORT);
			break;
		}
	}
}

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







-
+









-
+







		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)
		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);
		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
289
290
291
292
293
294
295

296
297


298
299
300
301
302
303
304
290
291
292
293
294
295
296
297


298
299
300
301
302
303
304
305
306







+
-
-
+
+







	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.velocity =
		player1.vel = OFMakeVector3D(player1.vel.x, player1.vel.y, 0);
		vadd(player1.vel, v);
		    OFMakeVector3D(player1.velocity.x, player1.velocity.y, 0);
		vadd(player1.velocity, v);
		playsoundc(S_JUMPPAD);
		break;
	}
	}
}

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







-
-
+
+









-
-
+
+







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

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

		OFVector3D v = OFMakeVector3D(
		    e.x, e.y, (float)S(e.x, e.y)->floor + player1.eyeheight);
		vdist(dist, t, player1.o, v);
		    e.x, e.y, (float)S(e.x, e.y)->floor + player1.eyeHeight);
		vdist(dist, t, player1.origin, v);

		if (dist < (e.type == TELEPORT ? 4 : 2.5))
			pickup(i, player1);
	}];
}

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

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

Modified src/menus.m from [bd623c6a87] to [9e1b20764c].

1
2
3
4
5


6
7
8
9
10
11
12
13
14
15
16

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

18
19
20
21
22
23
24
25





+
+










-
+







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

#include "cube.h"

#import "Menu.h"

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

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

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

void
showmenu(OFString *name)
{

Modified src/monster.m from [c3b07dd60c] to [8b5a6bb4d3].

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







-
-
-
-
+
+
+
+

-
-
-
+
+
+



-
+


-
-
+
+







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();
	struct monstertype *t = &monstertypes[(m.mtype = type)];
	m.eyeheight = 2.0f;
	m.aboveeye = 1.9f;
	DynamicEntity *m = [DynamicEntity entity];
	struct monstertype *t = &monstertypes[(m.monsterType = 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;
	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.targetYaw = m.yaw = (float)yaw;
	m.move = move;
	m.enemy = player1;
	m.gunselect = t->gun;
	m.maxspeed = (float)t->speed;
	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;
125
126
127
128
129
130
131
132

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

132
133
134
135
136
137
138
139







-
+








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

			DynamicEntity *m =
			    basicmonster(e.attr2, e.attr1, M_SLEEP, 100, 0);
			m.o = OFMakeVector3D(e.x, e.y, e.z);
			m.origin = OFMakeVector3D(e.x, e.y, e.z);
			entinmap(m);
			monstertotal++;
		}
	}
}

// height-correct line of sight for monster shooting/seeing
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
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







-
-
-
+
+
+













-
+







	}
	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);
	*v = m.origin;
	return los(m.origin.x, m.origin.y, m.origin.z, m.enemy.origin.x,
	    m.enemy.origin.y, m.enemy.origin.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.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)
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
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







-
+

-
+

-
-
+
+


-
-
+
+


-
-
+
+





-
-
+
+


-
+

-
+




-
-
+
+
+


-
+













-
-
+
+






-
+









-
+

-
-
+
+







-
+












-
+

-
+
+



-
+
+












-
+




+
-
-
+
+









-
+


-
+


-
-
+
+




-
-
+
+







void
monsteraction(DynamicEntity *m)
{
	if (m.enemy.state == CS_DEAD) {
		m.enemy = player1;
		m.anger = 0;
	}
	normalise(m, m.targetyaw);
	normalise(m, m.targetYaw);
	// slowly turn monster towards his target
	if (m.targetyaw > m.yaw) {
	if (m.targetYaw > m.yaw) {
		m.yaw += curtime * 0.5f;
		if (m.targetyaw < m.yaw)
			m.yaw = m.targetyaw;
		if (m.targetYaw < m.yaw)
			m.yaw = m.targetYaw;
	} else {
		m.yaw -= curtime * 0.5f;
		if (m.targetyaw > m.yaw)
			m.yaw = m.targetyaw;
		if (m.targetYaw > m.yaw)
			m.yaw = m.targetYaw;
	}

	vdist(disttoenemy, vectoenemy, m.o, m.enemy.o);
	m.pitch = atan2(m.enemy.o.z - m.o.z, disttoenemy) * 180 / PI;
	vdist(disttoenemy, vectoenemy, m.origin, m.enemy.origin);
	m.pitch = atan2(m.enemy.origin.z - m.origin.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;
		if (!rnd(20000 / monstertypes[m.monsterType].speed))
			m.jumpNext = true;
		// search for a way around (common)
		else if (m.trigger < lastmillis &&
		    (m.monsterstate != M_HOME || !rnd(5))) {
		    (m.monsterState != M_HOME || !rnd(5))) {
			// patented "random walk" AI pathfinding (tm) ;)
			m.targetyaw += 180 + rnd(180);
			m.targetYaw += 180 + rnd(180);
			transition(m, M_SEARCH, 1, 400, 1000);
		}
	}

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

	switch (m.monsterstate) {
	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);
		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;
			OFVector3D loc = m.origin;
			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.lastAction = 0;
			m.attacking = true;
			OFVector3D attacktarget = m.attacktarget;
			shoot(m, &attacktarget);
			OFVector3D attackTarget = m.attackTarget;
			shoot(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;
		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;
					m.attackTarget = target;
					transition(m, M_AIMING, 0,
					    monstertypes[m.mtype].lag, 10);
					    monstertypes[m.monsterType].lag,
					    10);
				} else
					// track player some more
					transition(m, M_HOME, 1,
					    monstertypes[m.mtype].rate, 0);
					    monstertypes[m.monsterType].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) {
	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.monsterType == d.monsterType ? m.anger / 2
			int anger = m.mtype == d.mtype ? m.anger / 2 : m.anger;
			if (anger >= monstertypes[m.mtype].loyalty)
			                                           : m.anger;
			if (anger >= monstertypes[m.monsterType].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);
	transition(m, M_PAIN, 0, monstertypes[m.monsterType].pain, 200);
	if ((m.health -= damage) <= 0) {
		m.state = CS_DEAD;
		m.lastaction = lastmillis;
		m.lastAction = lastmillis;
		numkilled++;
		player1.frags = numkilled;
		OFVector3D loc = m.o;
		playsound(monstertypes[m.mtype].diesound, &loc);
		OFVector3D loc = m.origin;
		playsound(monstertypes[m.monsterType].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);
		OFVector3D loc = m.origin;
		playsound(monstertypes[m.monsterType].painsound, &loc);
	}
}

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







-
+




-
-
-
+
+
+

















-
-
+
+
+

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

		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) {
				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;
				v.z += monster.eyeHeight;
				vdist(dist, t, monster.origin, v);
				v.z -= monster.eyeHeight;

				if (dist < 4)
					teleport(i, 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);
		    monstertypes[monster.monsterType].mdlname,
		    monster.monsterType == 5,
		    monstertypes[monster.monsterType].mscale / 10.0f);
}

Modified src/physics.m from [25a7c9cd46] to [bb6edf173a].

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







+

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

-
+

-
+

+
-
+



+







// collide with player or monster
static 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.origin.x - d.origin.x) < r &&
	    fabs(o.origin.y - d.origin.y) < r) {
		if (d.origin.z - d.eyeHeight < o.origin.z - o.eyeHeight) {
			if (o.origin.z - o.eyeHeight < *hi)
				*hi = o.origin.z - o.eyeHeight - 1;
		} else if (o.origin.z + o.aboveEye > *lo)
			*lo = o.origin.z + o.aboveEye + 1;

		if (fabs(o.o.z - d.o.z) < o.aboveeye + d.eyeheight)
		if (fabs(o.origin.z - d.origin.z) < o.aboveEye + d.eyeHeight)
			return false;
		if (d.monsterstate)
		if (d.monsterState)
			return false; // hack
			              //
		*headspace = d.o.z - o.o.z - o.aboveeye - d.eyeheight;
		*headspace = d.origin.z - o.origin.z - o.aboveEye - d.eyeHeight;
		if (*headspace < 0)
			*headspace = 10;
	}

	return true;
}

// recursively collide with a mipmapped corner cube
static bool
cornertest(int mip, int x, int y, int dx, int dy, int *bx, int *by, int *bs)
{
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
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







-
+



-
+

















-
-
-
-
+
+
+
+






-
-
+
+


-
+







			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) {
		if (fabs(e.x - d.origin.x) < r && fabs(e.y - d.origin.y) < r) {
			float mmz =
			    (float)(S(e.x, e.y)->floor + mmi.zoff + e.attr3);

			if (d.o.z - d.eyeheight < mmz) {
			if (d.origin.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 float fx1 = d.origin.x - d.radius;
	const float fy1 = d.origin.y - d.radius;
	const float fx2 = d.origin.x + d.radius;
	const float fy2 = d.origin.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
	float minfloor = (d.monsterState && !spawn && d.health > 100)
	    ? d.origin.z - d.eyeHeight - 4.5f
	    : -1000.0f;

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







-
-
+
+
+









+



+



-
+


+






+
-
-
+
+

-
+



-
-
+
+


-
-
+
+




+
-
-
+
+

-
+




-
+
+

+
-
+


-
+

+







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

	if (hi - lo < d.eyeheight + d.aboveeye)
	}

	if (hi - lo < d.eyeHeight + d.aboveEye)
		return false;

	float headspace = 10;
	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 &&
		if (!vreject(d.origin, monster.origin, 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.origin =
		d.o = OFMakeVector3D(d.o.x, d.o.y, lo + d.eyeheight);
		d.onfloor = true;
		    OFMakeVector3D(d.origin.x, d.origin.y, lo + d.eyeHeight);
		d.onFloor = true;
	} else {
		const float space = d.o.z - d.eyeheight - lo;
		const float space = d.origin.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);
				d.origin = OFMakeVector3D(
				    d.origin.x, d.origin.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);
				d.origin = OFAddVector3D(
				    d.origin, OFMakeVector3D(0, 0, rise));
			else
				return false;
		} else
			// gravity
			d.origin = OFSubtractVector3D(d.origin,
			d.o = OFMakeVector3D(d.o.x, d.o.y,
			    d.o.z - min(min(drop, space), headspace));
			    OFMakeVector3D(
			        0, 0, min(min(drop, space), headspace)));

		const float space2 = hi - (d.o.z + d.aboveeye);
		const float space2 = hi - (d.origin.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);
			d.origin = OFMakeVector3D(
			    d.origin.x, d.origin.y, hi - d.aboveEye);
			// cancel out jumping velocity
			d.velocity =
			d.vel = OFMakeVector3D(d.vel.x, d.vel.y, 0);
			    OFMakeVector3D(d.velocity.x, d.velocity.y, 0);
		}

		d.onfloor = d.o.z - d.eyeheight - lo < 0.001f;
		d.onFloor = (d.origin.z - d.eyeHeight - lo < 0.001f);
	}

	return true;
}

float
rad(float x)
{
	return x * 3.14159f / 180;
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
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







-
+

















-
+

-
+





-
-
-
-
+
+
+
+







-
-
-
-
+
+
+
+
+



-
-
-
+
+
+

-
-
+
+



-
-
+
+
+


-
-
+
+


-
+




-
-
+
+



+
-
+

-
+




-
+



-
+









-
-
+
+


+


+
-
+




+

-
-
+
+




+


+
-
+




-
+
+
+







-
-
+
+
+

-
-
-
-
+
+
+
+
+
+
















-
-
+
+

-
-
-
+
+
+


-
+









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

static void
moveplayer4(DynamicEntity *pl, int moveres, bool local, int curtime)
{
	const bool water = hdr.waterlevel > pl.o.z - 0.5f;
	const bool water = (hdr.waterlevel > pl.origin.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 speed = curtime / (water ? 2000.0f : 1000.0f) * pl.maxSpeed;
	const float friction =
	    water ? 20.0f : (pl.onfloor || floating ? 6.0f : 30.0f);
	    water ? 20.0f : (pl.onFloor || floating ? 6.0f : 30.0f);

	const float fpsfric = friction / curtime * 20.0f;

	// 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(pl.velocity, fpsfric - 1);
	vadd(pl.velocity, d);
	vdiv(pl.velocity, fpsfric);
	d = pl.velocity;
	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);
		vadd(pl.origin, d);
		if (pl.jumpNext) {
			pl.jumpNext = false;
			pl.velocity =
			    OFMakeVector3D(pl.velocity.x, pl.velocity.y, 2);
		}
	} else {
		// apply velocity with collision
		if (pl.onfloor || water) {
			if (pl.jumpnext) {
				pl.jumpnext = false;
		if (pl.onFloor || water) {
			if (pl.jumpNext) {
				pl.jumpNext = false;
				// physics impulse upwards
				pl.vel =
				    OFMakeVector3D(pl.vel.x, pl.vel.y, 1.7);
				pl.velocity = OFMakeVector3D(
				    pl.velocity.x, pl.velocity.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);
					pl.velocity = OFMakeVector3D(
					    pl.velocity.x / 8,
					    pl.velocity.y / 8, pl.velocity.z);
				if (local)
					playsoundc(S_JUMP);
				else if (pl.monsterstate) {
					OFVector3D loc = pl.o;
				else if (pl.monsterState) {
					OFVector3D loc = pl.origin;
					playsound(S_JUMP, &loc);
				}
			} else if (pl.timeinair > 800) {
			} 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;
				else if (pl.monsterState) {
					OFVector3D loc = pl.origin;
					playsound(S_LAND, &loc);
				}
			}

			pl.timeinair = 0;
			pl.timeInAir = 0;
		} else
			pl.timeinair += curtime;
			pl.timeInAir += curtime;

		const float gravity = 20;
		const float f = 1.0f / moveres;
		// incorrect, but works fine
		float dropf = ((gravity - 1) + pl.timeinair / 15.0f);
		float dropf = ((gravity - 1) + pl.timeInAir / 15.0f);
		// float slowly down in water
		if (water) {
			dropf = 5;
			pl.timeinair = 0;
			pl.timeInAir = 0;
		}
		// 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);
			pl.origin = OFAddVector3D(pl.origin,
			    OFMakeVector3D(f * d.x, f * d.y, f * d.z));
			if (collide(pl, false, drop, rise))
				continue;

			// player stuck, try slide along y axis
			pl.blocked = true;
			pl.origin = OFSubtractVector3D(
			pl.o = OFMakeVector3D(pl.o.x - f * d.x, pl.o.y, pl.o.z);
			    pl.origin, OFMakeVector3D(f * d.x, 0, 0));
			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);
			pl.origin = OFAddVector3D(
			    pl.origin, OFMakeVector3D(f * d.x, -f * d.y, 0));
			if (collide(pl, false, drop, rise)) {
				d.y = 0;
				continue;
			}

			// try just dropping down
			pl.moving = false;
			pl.origin = OFSubtractVector3D(
			pl.o = OFMakeVector3D(pl.o.x - f * d.x, pl.o.y, pl.o.z);
			    pl.origin, OFMakeVector3D(f * d.x, 0, 0));
			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);

			pl.origin = OFSubtractVector3D(
			    pl.origin, OFMakeVector3D(0, 0, 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;
	if (pl.origin.x < 0 || pl.origin.x >= ssize || pl.origin.y < 0 ||
	    pl.origin.y > ssize)
		pl.outsideMap = true;
	else {
		struct 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);
		struct sqr *s = S((int)pl.origin.x, (int)pl.origin.y);
		pl.outsideMap = SOLID(s) ||
		    pl.origin.z <
		        s->floor - (s->type == FHF ? s->vdelta / 4 : 0) ||
		    pl.origin.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;
	if (!pl.inWater && water) {
		OFVector3D loc = pl.origin;
		playsound(S_SPLASH2, &loc);
		pl.vel = OFMakeVector3D(pl.vel.x, pl.vel.y, 0);
	} else if (pl.inwater && !water) {
		OFVector3D loc = pl.o;
		pl.velocity = OFMakeVector3D(pl.velocity.x, pl.velocity.y, 0);
	} else if (pl.inWater && !water) {
		OFVector3D loc = pl.origin;
		playsound(S_SPLASH1, &loc);
	}
	pl.inwater = water;
	pl.inWater = water;
}

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

Modified src/protos.h from [b7d19818d1] to [1cd5f32081].

96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
96
97
98
99
100
101
102

103
104
105
106
107

108
109
110
111
112
113
114







-





-







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

Modified src/renderextras.m from [5de4da4840] to [81b3ccc9f0].

331
332
333
334
335
336
337
338

339
340
341
342
343
344
345
331
332
333
334
335
336
337

338
339
340
341
342
343
344
345







-
+








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

	glDisable(GL_DEPTH_TEST);
	invertperspective();
381
382
383
384
385
386
387
388

389
390
391
392
393
394
395
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)
			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;
424
425
426
427
428
429
430
431

432
433
434
435
436
437
438
439
440


441
442
443
444
445
446
447
424
425
426
427
428
429
430

431
432
433
434
435
436
437
438


439
440
441
442
443
444
445
446
447







-
+







-
-
+
+








	if (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]);
		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;
			    (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.m from [2d15d16f3c] to [09aa28380f].

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







-
-
-
-
+
+
+
+

















-
-
+
+
+






-
+










-
-
-
-
+
+
+
+

















-
+
















-
+




















-
-
+
+







{
	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);
	glTranslated(-player1.origin.x,
	    (player1.state == CS_DEAD ? player1.eyeHeight - 0.2f : 0) -
	        player1.origin.z,
	    -player1.origin.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,
	    OFMakeVector3D(player1.o.x, player1.o.z, player1.o.y),
	rendermodel(hudgunnames[player1.gunSelect], start, end, 0, 1.0f,
	    OFMakeVector3D(
	        player1.origin.x, player1.origin.z, player1.origin.y),
	    player1.yaw + 90, player1.pitch, false, 1.0f, speed, 0, base);
}

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

	glEnable(GL_CULL_FACE);

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

	// glClear(GL_DEPTH_BUFFER_BIT);
	int rtime = reloadtime(player1.gunselect);
	if (player1.lastaction && player1.lastattackgun == player1.gunselect &&
	    lastmillis - player1.lastaction < rtime) {
		drawhudmodel(7, 18, rtime / 18.0f, player1.lastaction);
	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;
	bool underwater = (player1.origin.z < hf);

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

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

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

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

	transplayer();

	glEnable(GL_TEXTURE_2D);

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

	resetcubes();

	curvert = 0;
	[strips removeAllItems];

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

	gl_setupworld();

	renderstripssky();

	glLoadIdentity();

Modified src/rendermd2.m from [820ea372aa] to [3f3cf16cef].

89
90
91
92
93
94
95
96

97
98
99
100
101
102
103
89
90
91
92
93
94
95

96
97
98
99
100
101
102
103







-
+







void
rendermodel(OFString *mdl, int frame, int range, int tex, float rad,
    OFVector3D position, 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, position.x - rad,
	if (isoccluded(player1.origin.x, player1.origin.y, position.x - rad,
	        position.z - rad, rad * 2))
		return;

	delayedload(m);

	int xs, ys;
	glBindTexture(GL_TEXTURE_2D,

Modified src/renderparticles.m from [f527e01774] to [63da7f6939].

53
54
55
56
57
58
59
60

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

60
61
62
63
64
65
66
67







-
+







	up = *u;
}

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

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

Modified src/savegamedemo.m from [e77568d32b] to [3f6725d365].

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







-
+












-
+

-
+







	restoreserverstate(ents);

	OFMutableData *data =
	    [OFMutableData dataWithCapacity:DynamicEntity.serializedSize];
	[data increaseCountBy:DynamicEntity.serializedSize];
	gzread(f, data.mutableItems, data.count);
	[player1 setFromSerializedData:data];
	player1.lastaction = lastmillis;
	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;
		monster.lastAction = monster.trigger = lastmillis + 500;
		if (monster.state == CS_DEAD)
			monster.lastaction = 0;
			monster.lastAction = 0;
	}
	restoremonsterstate();

	int nplayers = gzgeti();
	loopi(nplayers) if (!gzget())
	{
		DynamicEntity *d = getclient(i);
309
310
311
312
313
314
315
316
317
318
319




320
321
322

323
324
325
326
327
328
329
309
310
311
312
313
314
315




316
317
318
319
320
321

322
323
324
325
326
327
328
329







-
-
-
-
+
+
+
+


-
+







	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);
		gzput(player1.gunSelect);
		gzput(player1.lastAttackGun);
		gzputi(player1.lastAction - starttime);
		gzputi(player1.gunWait);
		gzputi(player1.health);
		gzputi(player1.armour);
		gzput(player1.armourtype);
		gzput(player1.armourType);
		loopi(NUMGUNS) gzput(player1.ammo[i]);
		gzput(player1.state);
		gzputi(bdamage);
		bdamage = 0;
		gzputi(ddamage);
		if (ddamage) {
			gzputv(&dorig);
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
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







-
-
-
-
+
+
+
+


-
+


-
+












-
+

-
+




















-
+

















-
-
+
+



-
+


-
-
+
+







		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.gunSelect = gzget();
			target.lastAttackGun = gzget();
			target.lastAction = scaletime(gzgeti());
			target.gunWait = gzgeti();
			target.health = gzgeti();
			target.armour = gzgeti();
			target.armourtype = gzget();
			target.armourType = gzget();
			loopi(NUMGUNS) target.ammo[i] = gzget();
			target.state = gzget();
			target.lastmove = playbacktime;
			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)) {
		        playerhistory.lastObject.lastUpdate != playbacktime)) {
			DynamicEntity *d = [target copy];
			d.lastupdate = playbacktime;
			d.lastUpdate = playbacktime;

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

			[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) {
		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);
				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);
				vdist(dist, v, z.origin, c.origin);
				// if teleport or spawn, don't interpolate
				if (dist < 16) {
					catmulrom(
					    z.o, a.o, b.o, c.o, bf, player1.o);
					catmulrom(z.origin, a.origin, b.origin,
					    c.origin, bf, player1.origin);
					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(

Modified src/sound.m from [fab11a9b01] to [8879a01acd].

113
114
115
116
117
118
119
120

121
122
123
124
125
126
127
113
114
115
116
117
118
119

120
121
122
123
124
125
126
127







-
+







VAR(stereo, 0, 1, 1);

static void
updatechanvol(int chan, const OFVector3D *loc)
{
	int vol = soundvol, pan = 255 / 2;
	if (loc) {
		vdist(dist, v, *loc, player1.o);
		vdist(dist, v, *loc, player1.origin);
		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)

Modified src/weapon.m from [81ddf6dcb5] to [af10bcd44b].

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







-
+
















-
+

-
+








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;
	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)
	if (s != player1.gunSelect)
		playsoundc(S_WEAPLOAD);
	player1.gunselect = s;
	player1.gunSelect = s;
	// conoutf(@"%@ selected", (int)guns[s].name);
}

int
reloadtime(int gun)
{
	return guns[gun].attackdelay;
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
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







-
+



















-
-
-
+
+
+
+












-
+







	}
}

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

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

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

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

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

		OFVector3D o = player1.o;
		OFVector3D o = player1.origin;
		if (intersect(player, &o, &worldpos))
			return [player name];
	}

	return nil;
}

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







-
+


-
+


-
+















-
+







-
+







		return;
	}
}

void
hit(int target, int damage, DynamicEntity *d, DynamicEntity *at)
{
	OFVector3D o = d.o;
	OFVector3D o = d.origin;
	if (d == player1)
		selfdamage(damage, at == player1 ? -1 : -2, at);
	else if (d.monsterstate)
	else if (d.monsterState)
		monsterpain(d, damage, at);
	else {
		addmsg(1, 4, SV_DAMAGE, target, damage, d.lifesequence);
		addmsg(1, 4, SV_DAMAGE, target, damage, d.lifeSequence);
		playsound(S_PAIN1 + rnd(5), &o);
	}
	particle_splash(3, damage, 1000, &o);
	demodamage(damage, &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);
	vdist(dist, temp, *v, o.origin);
	dist -= 2; // account for eye distance imprecision
	if (dist < RL_DAMRAD) {
		if (dist < 0)
			dist = 0;
		int damage = (int)(qdam * (1 - (dist / RL_DAMRAD)));
		hit(cn, damage, o, at);
		vmul(temp, (RL_DAMRAD - dist) * damage / 800);
		vadd(o.vel, temp);
		vadd(o.velocity, temp);
	}
}

void
splash(Projectile *p, const OFVector3D *v, const OFVector3D *vold,
    int notthisplayer, int notthismonster, int qdam)
{
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
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







-
-
+
+
















-
+







{
	for (size_t i = 0; i < MAXPROJ; i++) {
		Projectile *p = projs[i];

		if (!p.inuse)
			continue;

		int qdam = guns[p.gun].damage * (p.owner.quadmillis ? 4 : 1);
		if (p.owner.monsterstate)
		int qdam = guns[p.gun].damage * (p.owner.quadMillis ? 4 : 1);
		if (p.owner.monsterState)
			qdam /= MONSTERDAMAGEFACTOR;
		vdist(dist, v, p.o, p.to);
		float dtime = dist * 1000 / p.speed;
		if (time > dtime)
			dtime = time;
		vmul(v, time / dtime);
		vadd(v, p.o);
		if (p.local) {
			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) &&
				if (!vreject(monster.origin, v, 10.0f) &&
				    monster != p.owner)
					projdamage(monster, p, &v, -1, i, qdam);
		}
		if (p.inuse) {
			OFVector3D po = p.o;

			if (time == dtime)
308
309
310
311
312
313
314
315

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

316
317
318
319
320
321
322
323







-
+







}

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

	case GUN_SG: {
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
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







-
+


















-
+








-
-
+
+

-
+

-
+











-
-
+
+

-
+


-
-
-
+
+
+

-
-
+
+


-
-
-
+
+
+






-
-
+
+

-
+

-
+




-
+


-
+

-
-
-
+
+
+


-
+

-
+











-
+


		break;

	case GUN_RL:
	case GUN_FIREBALL:
	case GUN_ICEBALL:
	case GUN_SLIMEBALL:
		pspeed = guns[gun].projspeed;
		if (d.monsterstate)
		if (d.monsterState)
			pspeed /= 2;
		newprojectile(from, to, (float)pspeed, local, d, gun);
		break;

	case GUN_RIFLE:
		particle_splash(0, 50, 200, to);
		particle_trail(1, 500, from, to);
		break;
	}
}

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

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

void
shoot(DynamicEntity *d, const OFVector3D *targ)
{
	int attacktime = lastmillis - d.lastaction;
	if (attacktime < d.gunwait)
	int attacktime = lastmillis - d.lastAction;
	if (attacktime < d.gunWait)
		return;
	d.gunwait = 0;
	d.gunWait = 0;
	if (!d.attacking)
		return;
	d.lastaction = lastmillis;
	d.lastattackgun = d.gunselect;
	if (!d.ammo[d.gunselect]) {
	d.lastAction = lastmillis;
	d.lastAttackGun = d.gunSelect;
	if (!d.ammo[d.gunSelect]) {
		playsoundc(S_NOAMMO);
		d.gunwait = 250;
		d.lastattackgun = -1;
		d.gunWait = 250;
		d.lastAttackGun = -1;
		return;
	}
	if (d.gunselect)
		d.ammo[d.gunselect]--;
	OFVector3D from = d.o;
	if (d.gunSelect)
		d.ammo[d.gunSelect]--;
	OFVector3D from = d.origin;
	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);
	vmul(kickback, guns[d.gunSelect].kickamount * -0.01f);
	vadd(d.velocity, kickback);
	if (d.pitch < 80.0f)
		d.pitch += guns[d.gunselect].kickamount * 0.05f;
		d.pitch += guns[d.gunSelect].kickamount * 0.05f;

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

	if (d.quadmillis && attacktime > 200)
	if (d.quadMillis && attacktime > 200)
		playsoundc(S_ITEMPUP);
	shootv(d.gunselect, &from, &to, d, true);
	if (!d.monsterstate)
		addmsg(1, 8, SV_SHOT, d.gunselect, (int)(from.x * DMF),
	shootv(d.gunSelect, &from, &to, d, true);
	if (!d.monsterState)
		addmsg(1, 8, SV_SHOT, d.gunSelect, (int)(from.x * DMF),
		    (int)(from.y * DMF), (int)(from.z * DMF), (int)(to.x * DMF),
		    (int)(to.y * DMF), (int)(to.z * DMF));
	d.gunwait = guns[d.gunselect].attackdelay;
	d.gunWait = guns[d.gunSelect].attackdelay;

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

	[players enumerateObjectsUsingBlock:^(id player, size_t i, bool *stop) {
		if (player != [OFNull null])
			raydamage(player, &from, &to, d, i);
	}];

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

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

Modified src/world.m from [16c30b946f] to [023ca9b6df].

266
267
268
269
270
271
272
273

274
275
276
277
278
279
280
266
267
268
269
270
271
272

273
274
275
276
277
278
279
280







-
+







	__block int best;
	__block float bdist = 99999;
	[ents enumerateObjectsUsingBlock:^(Entity *e, size_t i, bool *stop) {
		if (e.type == NOTUSED)
			return;

		OFVector3D v = OFMakeVector3D(e.x, e.y, e.z);
		vdist(dist, t, player1.o, v);
		vdist(dist, t, player1.origin, v);
		if (dist < bdist) {
			best = i;
			bdist = dist;
		}
	}];

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

Modified src/worldlight.m from [feb6b04e94] to [d596ebb5c3].

201
202
203
204
205
206
207
208

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

208
209
210
211
212
213
214
215







-
+








void
dodynlight(const OFVector3D *vold, const OFVector3D *v, int reach, int strength,
    DynamicEntity *owner)
{
	if (!reach)
		reach = dynlight;
	if (owner.monsterstate)
	if (owner.monsterState)
		reach = reach / 2;
	if (!reach)
		return;
	if (v->x < 0 || v->y < 0 || v->x > ssize || v->y > ssize)
		return;

	int creach = reach + 16; // dependant on lightray random offsets!

Modified src/worldrender.m from [6775552eda] to [37acc1d08b].

133
134
135
136
137
138
139
140

141
142
143
144
145
146
147
133
134
135
136
137
138
139

140
141
142
143
144
145
146
147







-
+







	int ry = vyy + lodbot;

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