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

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

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

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

			// cheap hack to get rid of initial sparklies, even when
			// triple buffering etc.







|







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.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
#import <ObjFW/ObjFW.h>

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

// origin, velocity
@property (nonatomic) OFVector3D o, vel;
// used as OFVector3D in one place
@property (nonatomic) float yaw, pitch, roll;
// cubes per second, 24 for player
@property (nonatomic) float maxspeed;
// from his eyes
@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;
// bounding box size
@property (nonatomic) float radius, eyeheight, aboveeye;
@property (nonatomic) int lastupdate, plag, ping;
// sequence id for each respawn, used in damage test
@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) 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;
// monster wants to kill this entity
@property (nonatomic) DynamicEntity *enemy;
// monster wants to look in this direction
@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;
// how many times already hit by fellow monster
@property (nonatomic) int anger;
@property (copy, nonatomic) OFString *name, *team;


- (OFData *)dataBySerializing;
- (void)setFromSerializedData:(OFData *)data;



@end






<
|



|

|
|
|




|

|
|

|



|
|
|



|
|
|



|





|




>


>
>
>

1
2
3
4
5
6

7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#import <ObjFW/ObjFW.h>

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


@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;
// from his eyes
@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;
// bounding box size
@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;
// 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) bool attacking;
@property (readonly, nonatomic) int *ammo;
// one of M_* below, M_NONE means human
@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;
// 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;
// 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
#import "DynamicEntity.h"

#include "cube.h"

struct dynent {
	OFVector3D o, vel;
	float yaw, pitch, roll;
	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 state;
	int frags;
	int health, armour, armourtype, quadmillis;
	int gunselect, gunwait;
	int lastaction, lastattackgun, lastmove;
	bool attacking;
	int ammo[NUMGUNS];
	int monsterstate;
	int mtype;
	void *enemy;
	float targetyaw;
	bool blocked, moving;
	int trigger;
	OFVector3D attacktarget;
	int anger;
	char name[260], team[260];
};

@implementation DynamicEntity





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

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

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












	return self;
}

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

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

	copy->_o = _o;
	copy->_vel = _vel;
	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->_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->_radius = _radius;
	copy->_eyeheight = _eyeheight;
	copy->_aboveeye = _aboveeye;
	copy->_lastupdate = _lastupdate;
	copy->_plag = _plag;
	copy->_ping = _ping;
	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->_attacking = _attacking;

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

	copy->_monsterstate = _monsterstate;
	copy->_mtype = _mtype;
	copy->_enemy = _enemy;
	copy->_targetyaw = _targetyaw;
	copy->_blocked = _blocked;
	copy->_moving = _moving;
	copy->_trigger = _trigger;
	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,
		.yaw = _yaw,
		.pitch = _pitch,
		.roll = _roll,
		.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,
		.radius = _radius,
		.eyeheight = _eyeheight,
		.aboveeye = _aboveeye,
		.lastupdate = _lastupdate,
		.plag = _plag,
		.ping = _ping,
		.lifesequence = _lifesequence,
		.state = _state,
		.frags = _frags,
		.health = _health,
		.armour = _armour,
		.armourtype = _armourtype,
		.quadmillis = _quadmillis,
		.gunselect = _gunselect,
		.gunwait = _gunwait,
		.lastaction = _lastaction,
		.lastattackgun = _lastattackgun,
		.lastmove = _lastmove,
		.attacking = _attacking,
		.monsterstate = _monsterstate,
		.mtype = _mtype,
		.targetyaw = _targetyaw,
		.blocked = _blocked,
		.moving = _moving,
		.trigger = _trigger,
		.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;
	_yaw = d.yaw;
	_pitch = d.pitch;
	_roll = d.roll;
	_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;
	_radius = d.radius;
	_eyeheight = d.eyeheight;
	_aboveeye = d.aboveeye;
	_lastupdate = d.lastupdate;
	_plag = d.plag;
	_ping = d.ping;
	_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;
	_attacking = d.attacking;

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

	_monsterstate = d.monsterstate;
	_mtype = d.mtype;
	_targetyaw = d.targetyaw;
	_blocked = d.blocked;
	_moving = d.moving;
	_trigger = d.trigger;
	_attacktarget = d.attacktarget;
	_anger = d.anger;

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










































































@end





|

|
|
|
|


|
|
|
|


|
|
|


|
|

|


|





>
>
>
>
>











>
>
>
>
>
>
>
>
>
>
>












|
|



|
|
|
|
|






|

|
|
|
|

|




|
|
|
|
|
|
|





|
|

|



|












|
|



|
|
|
|
|






|

|
|
|
|

|




|
|
|
|
|
|
|

|
|
|



|




















|
|



|
|
|
|
|






|

|
|
|
|

|




|
|
|
|
|
|
|





|
|
|



|





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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
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 origin, velocity;
	float yaw, pitch, roll;
	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, lag, ping;
	int lifeSequence;
	int state;
	int frags;
	int health, armour, armourType, quadMillis;
	int gunSelect, gunWait;
	int lastAction, lastAttackGun, lastMove;
	bool attacking;
	int ammo[NUMGUNS];
	int monsterState;
	int monsterType;
	void *enemy;
	float targetYaw;
	bool blocked, moving;
	int trigger;
	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->_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->_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->_radius = _radius;
	copy->_eyeHeight = _eyeHeight;
	copy->_aboveEye = _aboveEye;
	copy->_lastUpdate = _lastUpdate;
	copy->_lag = _lag;
	copy->_ping = _ping;
	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->_attacking = _attacking;

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

	copy->_monsterState = _monsterState;
	copy->_monsterType = _monsterType;
	copy->_enemy = _enemy;
	copy->_targetYaw = _targetYaw;
	copy->_blocked = _blocked;
	copy->_moving = _moving;
	copy->_trigger = _trigger;
	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 = { .origin = _origin,
		.velocity = _velocity,
		.yaw = _yaw,
		.pitch = _pitch,
		.roll = _roll,
		.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,
		.radius = _radius,
		.eyeHeight = _eyeHeight,
		.aboveEye = _aboveEye,
		.lastUpdate = _lastUpdate,
		.lag = _lag,
		.ping = _ping,
		.lifeSequence = _lifeSequence,
		.state = _state,
		.frags = _frags,
		.health = _health,
		.armour = _armour,
		.armourType = _armourType,
		.quadMillis = _quadMillis,
		.gunSelect = _gunSelect,
		.gunWait = _gunWait,
		.lastAction = _lastAction,
		.lastAttackGun = _lastAttackGun,
		.lastMove = _lastMove,
		.attacking = _attacking,
		.monsterState = _monsterState,
		.monsterType = _monsterType,
		.targetYaw = _targetYaw,
		.blocked = _blocked,
		.moving = _moving,
		.trigger = _trigger,
		.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);

	_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;
	_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;
	_radius = d.radius;
	_eyeHeight = d.eyeHeight;
	_aboveEye = d.aboveEye;
	_lastUpdate = d.lastUpdate;
	_lag = d.lag;
	_ping = d.ping;
	_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;
	_attacking = d.attacking;

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

	_monsterState = d.monsterState;
	_monsterType = d.monsterType;
	_targetYaw = d.targetYaw;
	_blocked = d.blocked;
	_moving = d.moving;
	_trigger = d.trigger;
	_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

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

extern int democlientnum;

void
renderclients()
{







|











|
|












|





|

|



|



|

|







|
|







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.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;
		if (t < 0 || t > 20000)
			return;
		if (t > (r - 1) * 100) {
			n += 4;
			if (t > (r + 10) * 100) {
				t -= (r + 10) * 100;
				mz -= t * t / 10000000000.0f * t;
			}
		}
		if (mz < -1000)
			return;
		// mdl = (((int)d>>6)&1)+1;
		// mz = d.o.z-d.eyeHeight+0.2f;
		// scale = 1.2f;
	} else if (d.state == CS_EDITING) {
		n = 16;
	} else if (d.state == CS_LAGGED) {
		n = 17;
	} else if (d.monsterState == M_ATTACKING) {
		n = 8;
	} else if (d.monsterState == M_PAIN) {
		n = 10;
	} else if ((!d.move && !d.strafe) || !d.moving) {
		n = 12;
	} else if (!d.onFloor && d.timeInAir > 100) {
		n = 18;
	} else {
		n = 14;
		speed = 1200 / d.maxSpeed * scale;
		if (hellpig)
			speed = 300 / d.maxSpeed;
	}
	if (hellpig) {
		n++;
		scale *= 32;
		mz -= 1.9f;
	}
	rendermodel(mdlname, frame[n], range[n], 0, 1.5f,
	    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
}

static OFMutableArray<OFString *> *scoreLines;

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

	if (scoreLines == nil)







|







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

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

void
initPlayers()
{
	player1 = newdynent();
	players = [[OFMutableArray alloc] init];
}

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

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

OFString *
getclientmap()
{
	return clientmap;
}

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

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

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

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








|

















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







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 = [[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
respawnself()
{
	spawnplayer(player1);
	showscores(false);
}

199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
void
otherplayers()
{
	[players enumerateObjectsUsingBlock:^(id player, size_t i, bool *stop) {
		if (player == [OFNull null])
			return;

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







|







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];
		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
			// when our player moves
			gets2c();
		}
		otherplayers();
		if (!demoplayback) {
			monsterthink();
			if (player1.state == CS_DEAD) {
				if (lastmillis - player1.lastaction < 2000) {
					player1.move = player1.strafe = 0;
					moveplayer(player1, 10, false);
				} else if (!m_arena && !m_sp &&
				    lastmillis - player1.lastaction > 10000)
					respawn();
			} else if (!intermission) {
				moveplayer(player1, 20, true);
				checkitems();
			}
			// do this last, to reduce the effective frame lag
			c2sinfo(player1);
		}
	}
	lastmillis = millis;
}

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

	// leave ent at original pos, possibly stuck
}

int spawncycle = -1;
int fixspawn = 2;

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

		d.o = OFMakeVector3D((float)ssize / 2, (float)ssize / 2, 4);
	entinmap(d);
	spawnstate(d);
	d.state = CS_ALIVE;
}

// movement input code

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

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








|



|




















|
|


|

<
|
>













|





>
|

|










|







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

// brute force but effective way to find a free spawn spot in the map
void
entinmap(DynamicEntity *d)
{
	loopi(100) // try max 100 times
	{
		float dx = (rnd(21) - 10) / 10.0f * i; // increasing distance
		float dy = (rnd(21) - 10) / 10.0f * i;
		OFVector3D old = d.origin;
		d.origin = OFAddVector3D(d.origin, OFMakeVector3D(dx, dy, 0));
		if (collide(d, true, 0, 0))
			return;
		d.origin = old;
	}

	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.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 =
		    OFMakeVector3D((float)ssize / 2, (float)ssize / 2, 4);
	entinmap(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;                    \
	}

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

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

COMMAND(backward, ARG_DOWN)
COMMAND(forward, ARG_DOWN)
COMMAND(left, ARG_DOWN)
COMMAND(right, ARG_DOWN)







|







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))
		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
selfdamage(int damage, int actor, DynamicEntity *act)
{
	if (player1.state != CS_ALIVE || editmode || intermission)
		return;
	damageblend(damage);
	demoblend(damage);
	// let armour absorb when possible
	int ad = damage * (player1.armourtype + 1) * 20 / 100;
	if (ad > player1.armour)
		ad = player1.armour;
	player1.armour -= ad;
	damage -= ad;
	float droll = damage / 0.5f;
	player1.roll += player1.roll > 0
	    ? droll







|







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;
	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
				else
					conoutf(
					    @"you got fragged by %@", a.name);
			}
		}
		showscores(true);
		addmsg(1, 2, SV_DIED, actor);
		player1.lifesequence++;
		player1.attacking = false;
		player1.state = CS_DEAD;
		player1.pitch = 0;
		player1.roll = 60;
		playsound(S_DIE1 + rnd(2), NULL);
		spawnstate(player1);
		player1.lastaction = lastmillis;
	} else
		playsound(S_PAIN6, NULL);
}

void
timeupdate(int timeremain)
{







|





|
|







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.attacking = false;
		player1.state = CS_DEAD;
		player1.pitch = 0;
		player1.roll = 60;
		playsound(S_DIE1 + rnd(2), NULL);
		[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
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()));



}

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







>


|
>
>
>
|
>
>
>







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

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

	localdisconnect();

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







|







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

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







|
|
|




|
|
|
|


|







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.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.velocity.x * DVF));
		putint(&p, (int)(d.velocity.y * DVF));
		putint(&p, (int)(d.velocity.z * DVF));
		// pack rest in 1 byte: strafe:2, move:2, onFloor:1, state:3
		putint(&p,
		    (d.strafe & 3) | ((d.move & 3) << 2) |
		        (((int)d.onFloor) << 4) |
		        ((editmode ? CS_EDITING : d.state) << 5));

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







|







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);
		}
		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
// don't care if he's in the scenery or other players,
// just don't overlap with our client

void
updatepos(DynamicEntity *d)
{
	const float r = player1.radius + d.radius;
	const float dx = player1.o.x - d.o.x;
	const float dy = player1.o.y - d.o.y;
	const float dz = player1.o.z - d.o.z;
	const float rz = player1.aboveeye + d.eyeheight;
	const float fx = (float)fabs(dx), fy = (float)fabs(dy),
	            fz = (float)fabs(dz);
	if (fx < r && fy < r && fz < rz && d.state != CS_DEAD) {
		if (fx < fy)
			// push aside

			d.o = OFMakeVector3D(d.o.x,
			    d.o.y + (dy < 0 ? r - fy : -(r - fy)), d.o.z);
		else

			d.o = OFMakeVector3D(
			    d.o.x + (dx < 0 ? r - fx : -(r - fx)), d.o.y,
			    d.o.z);
	}
	int lagtime = lastmillis - d.lastupdate;
	if (lagtime) {
		d.plag = (d.plag * 5 + lagtime) / 6;
		d.lastupdate = lastmillis;
	}
}

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







|
|
|
|





>
|
|

>
|
|
<

|

|
|







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.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,
			    OFMakeVector3D(
			        0, (dy < 0 ? r - fy : -(r - fy)), 0));
		else
			d.origin = OFAddVector3D(d.origin,
			    OFMakeVector3D(
			        (dx < 0 ? r - fx : -(r - fx)), 0, 0));

	}
	int lagtime = lastmillis - d.lastUpdate;
	if (lagtime) {
		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
			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.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;
			int f = getint(&p);
			d.strafe = (f & 3) == 3 ? -1 : f & 3;
			f >>= 2;
			d.move = (f & 3) == 3 ? -1 : f & 3;
			d.onfloor = (f >> 2) & 1;
			int state = f >> 3;
			if (state == CS_DEAD && d.state != CS_DEAD)
				d.lastaction = lastmillis;
			d.state = state;
			if (!demoplayback)
				updatepos(d);
			break;
		}

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

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







|






|




|


|







|







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.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.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;
			int state = f >> 3;
			if (state == CS_DEAD && d.state != CS_DEAD)
				d.lastAction = lastmillis;
			d.state = state;
			if (!demoplayback)
				updatepos(d);
			break;
		}

		case SV_SOUND: {
			OFVector3D loc = d.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
				// send new players my info again
				c2sinit = false;
				conoutf(@"connected: %s", text);
			}
			d.name = @(text);
			sgetstr();
			d.team = @(text);
			d.lifesequence = getint(&p);
			break;
		}

		case SV_CDIS:
			cn = getint(&p);
			if ((d = getclient(cn)) == nil)
				break;







|







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

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

		case SV_DIED: {
			int actor = getint(&p);







|


|







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)
					selfdamage(damage, cn, d);
			} else {
				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
						        @"teammate (%@)",
						    a.name, d.name);
					else
						conoutf(@"%@ fragged %@",
						    a.name, d.name);
				}
			}
			OFVector3D loc = d.o;
			playsound(S_DIE1 + rnd(2), &loc);
			d.lifesequence++;
			break;
		}

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








|

|







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.origin;
			playsound(S_DIE1 + rnd(2), &loc);
			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
#define PIXELTAB (VIRTW / 12)

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

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







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







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) u = OFMultiplyVector3D(u, OFMakeVector3D(f, f, f))






#define vdiv(u, f) u = OFDivideVector3D(u, OFMakeVector3D(f, f, f))






#define vadd(u, v) u = OFAddVector3D(u, v)





#define vsub(u, v) u = OFSubtractVector3D(u, v)





#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

	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;


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

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







|
|
>







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) {
		// 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
	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,
	    [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)







|







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.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
		break;
	case I_BOOST:
		d.health = radditem(n, d.health);
		break;

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

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

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

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








|




|



|







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

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

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

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

210
211
212
213
214
215
216

217
218
219
220
221
222
223
224
225
226
227
		if (e == beenhere || e < 0) {
			conoutf(@"no teleport destination for tag %d", tag);
			return;
		}
		if (beenhere < 0)
			beenhere = e;
		if (ents[e].attr2 == tag) {

			d.o = OFMakeVector3D(ents[e].x, ents[e].y, ents[e].z);
			d.yaw = ents[e].attr1;
			d.pitch = 0;
			d.vel = OFMakeVector3D(0, 0, 0);
			entinmap(d);
			playsoundc(S_TELEPORT);
			break;
		}
	}
}








>
|


|







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 =
			    OFMakeVector3D(ents[e].x, ents[e].y, ents[e].z);
			d.yaw = ents[e].attr1;
			d.pitch = 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
		break;
	case I_BOOST:
		additem(n, d.health, 60);
		break;

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

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

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

	case CARROT:
		ents[n].spawned = false;
		triggertime = lastmillis;
		trigger(ents[n].attr1, ents[n].attr2,
		    false); // needs to go over server for multiplayer







|









|







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)
			break;
		additem(n, d.armour, 20);
		break;

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

	case I_QUAD:
		additem(n, d.quadMillis, 60);
		break;

	case CARROT:
		ents[n].spawned = false;
		triggertime = lastmillis;
		trigger(ents[n].attr1, ents[n].attr2,
		    false); // needs to go over server for multiplayer
289
290
291
292
293
294
295

296
297
298
299
300
301
302
303
304
	case JUMPPAD: {
		static int lastjumppad = 0;
		if (lastmillis - lastjumppad < 300)
			break;
		lastjumppad = lastmillis;
		OFVector3D v = OFMakeVector3D((int)(char)ents[n].attr3 / 10.0f,
		    (int)(char)ents[n].attr2 / 10.0f, ents[n].attr1 / 10.0f);

		player1.vel = OFMakeVector3D(player1.vel.x, player1.vel.y, 0);
		vadd(player1.vel, v);
		playsoundc(S_JUMPPAD);
		break;
	}
	}
}

void







>
|
|







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

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

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

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







|
|









|
|







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

#include "cube.h"

#import "Menu.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);
	if (vmenu == 1)
		menus[1].menusel = 0;
}

void
showmenu(OFString *name)
{





>
>










|







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)
		[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
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;
	m.radius *= t->bscale / 10.0f;
	m.eyeheight *= t->bscale / 10.0f;
	m.aboveeye *= t->bscale / 10.0f;
	m.monsterstate = state;
	if (state != M_SLEEP)
		spawnplayer(m);
	m.trigger = lastmillis + trigger;
	m.targetyaw = m.yaw = (float)yaw;
	m.move = move;
	m.enemy = player1;
	m.gunselect = t->gun;
	m.maxspeed = (float)t->speed;
	m.health = t->health;
	m.armour = 0;
	loopi(NUMGUNS) m.ammo[i] = 10000;
	m.pitch = 0;
	m.roll = 0;
	m.state = CS_ALIVE;
	m.anger = 0;







|
|
|
|

|
|
|



|


|
|







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 = [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;
	if (state != M_SLEEP)
		spawnplayer(m);
	m.trigger = lastmillis + trigger;
	m.targetYaw = m.yaw = (float)yaw;
	m.move = move;
	m.enemy = player1;
	m.gunSelect = t->gun;
	m.maxSpeed = (float)t->speed;
	m.health = t->health;
	m.armour = 0;
	loopi(NUMGUNS) m.ammo[i] = 10000;
	m.pitch = 0;
	m.roll = 0;
	m.state = CS_ALIVE;
	m.anger = 0;
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);
			entinmap(m);
			monstertotal++;
		}
	}
}

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







|







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.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
	}
	return i >= steps;
}

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

// monster AI is sequenced using transitions: they are in a particular state
// where they execute a particular behaviour until the trigger time is hit, and
// then they reevaluate their situation based on the current state, the
// environment etc., and transition to the next state. Transition timeframes are
// parametrized by difficulty level (skill), faster transitions means quicker
// decision making means tougher AI.

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

void
normalise(DynamicEntity *m, float angle)







|
|
|













|







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

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

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

	float enemyyaw =

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

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

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

	case M_AIMING:
		// this state is the delay between wanting to shoot and actually
		// firing
		if (m.trigger < lastmillis) {
			m.lastaction = 0;
			m.attacking = true;
			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;
		if (m.trigger < lastmillis) {
			OFVector3D target;
			if (!enemylos(m, &target)) {
				// no visual contact anymore, let monster get
				// as close as possible then search for player
				transition(m, M_HOME, 1, 800, 500);
			} else {
				// the closer the monster is the more likely he
				// wants to shoot
				if (!rnd((int)disttoenemy / 3 + 1) &&
				    m.enemy.state == CS_ALIVE) {
					// get ready to fire
					m.attacktarget = target;
					transition(m, M_AIMING, 0,
					    monstertypes[m.mtype].lag, 10);

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

			}
		}
		break;
	}

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

void
monsterpain(DynamicEntity *m, int damage, DynamicEntity *d)
{
	// a monster hit us
	if (d.monsterstate) {
		// guard for RL guys shooting themselves :)
		if (m != d) {
			// don't attack straight away, first get angry
			m.anger++;

			int anger = m.mtype == d.mtype ? m.anger / 2 : m.anger;
			if (anger >= monstertypes[m.mtype].loyalty)
				// monster infight if very angry
				m.enemy = d;
		}
	} else {
		// player hit us
		m.anger = 0;
		m.enemy = d;
	}
	// in this state monster won't attack
	transition(m, M_PAIN, 0, monstertypes[m.mtype].pain, 200);
	if ((m.health -= damage) <= 0) {
		m.state = CS_DEAD;
		m.lastaction = lastmillis;
		numkilled++;
		player1.frags = numkilled;
		OFVector3D loc = m.o;
		playsound(monstertypes[m.mtype].diesound, &loc);
		int remain = monstertotal - numkilled;
		if (remain > 0 && remain <= 5)
			conoutf(@"only %d monster(s) remaining", remain);
	} else {
		OFVector3D loc = m.o;
		playsound(monstertypes[m.mtype].painsound, &loc);
	}
}

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







|

|

|
|


|
|


|
|





|
|


|

|




|
>
|


|













|
|






|









|

|
|







|












|

|
>



|
>












|




>
|
|









|


|


|
|




|
|







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);
	// slowly turn monster towards his target
	if (m.targetYaw > m.yaw) {
		m.yaw += curtime * 0.5f;
		if (m.targetYaw < m.yaw)
			m.yaw = m.targetYaw;
	} else {
		m.yaw -= curtime * 0.5f;
		if (m.targetYaw > m.yaw)
			m.yaw = m.targetYaw;
	}

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

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

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

	case M_SLEEP: // state classic sp monster start in, wait for visual
	              // contact
	{
		OFVector3D target;
		if (editmode || !enemylos(m, &target))
			return; // skip running physics
		normalise(m, enemyYaw);
		float angle = (float)fabs(enemyYaw - m.yaw);
		if (disttoenemy < 8 // the better the angle to the player, the
		                    // further the monster can see/hear
		    || (disttoenemy < 16 && angle < 135) ||
		    (disttoenemy < 32 && angle < 90) ||
		    (disttoenemy < 64 && angle < 45) || angle < 10) {
			transition(m, M_HOME, 1, 500, 200);
			OFVector3D loc = m.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.attacking = true;
			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;
		if (m.trigger < lastmillis) {
			OFVector3D target;
			if (!enemylos(m, &target)) {
				// no visual contact anymore, let monster get
				// as close as possible then search for player
				transition(m, M_HOME, 1, 800, 500);
			} else {
				// the closer the monster is the more likely he
				// wants to shoot
				if (!rnd((int)disttoenemy / 3 + 1) &&
				    m.enemy.state == CS_ALIVE) {
					// get ready to fire
					m.attackTarget = target;
					transition(m, M_AIMING, 0,
					    monstertypes[m.monsterType].lag,
					    10);
				} else
					// track player some more
					transition(m, M_HOME, 1,
					    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) {
		// 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
			                                           : 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.monsterType].pain, 200);
	if ((m.health -= damage) <= 0) {
		m.state = CS_DEAD;
		m.lastAction = lastmillis;
		numkilled++;
		player1.frags = numkilled;
		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.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
		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) {
					monster.move = 0;
					moveplayer(monster, 1, false);
				}
			} else {
				v.z += monster.eyeheight;
				vdist(dist, t, monster.o, v);
				v.z -= monster.eyeheight;

				if (dist < 4)
					teleport(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);
}







|




|
|
|

















|
>
|

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) {
					monster.move = 0;
					moveplayer(monster, 1, false);
				}
			} else {
				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.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
// 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.o.z - d.o.z) < o.aboveeye + d.eyeheight)
			return false;
		if (d.monsterstate)
			return false; // hack

		*headspace = d.o.z - o.o.z - o.aboveeye - d.eyeheight;
		if (*headspace < 0)
			*headspace = 10;
	}

	return true;
}

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







>

|
>
|
|
|
|
|

|

|

>
|



>







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.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.origin.z - d.origin.z) < o.aboveEye + d.eyeHeight)
			return false;
		if (d.monsterState)
			return false; // hack
			              //
		*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
			continue;

		MapModelInfo *mmi = getmminfo(e.attr2);
		if (mmi == nil || !mmi.h)
			continue;

		const float r = mmi.rad + d.radius;
		if (fabs(e.x - d.o.x) < r && fabs(e.y - d.o.y) < r) {
			float mmz =
			    (float)(S(e.x, e.y)->floor + mmi.zoff + e.attr3);

			if (d.o.z - d.eyeheight < mmz) {
				if (mmz < *hi)
					*hi = mmz;
			} else if (mmz + mmi.h > *lo)
				*lo = mmz + mmi.h;
		}
	}
}

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

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

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







|



|

















|
|
|
|






|
|


|







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.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.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.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.origin.z - d.eyeHeight - 4.5f
	    : -1000.0f;

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


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

	float headspace = 10;
	for (id player in players) {
		if (player == [OFNull null] || player == d)
			continue;
		if (!plcollide(d, player, &headspace, &hi, &lo))
			return false;
	}

	if (d != player1)
		if (!plcollide(d, player1, &headspace, &hi, &lo))
			return false;

	// this loop can be a performance bottleneck with many monster on a slow
	// cpu, should replace with a blockmap but seems mostly fast enough
	for (DynamicEntity *monster in getmonsters())
		if (!vreject(d.o, monster.o, 7.0f) && d != monster &&
		    !plcollide(d, monster, &headspace, &hi, &lo))
			return false;

	headspace -= 0.01f;

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

	if (spawn) {
		// just drop to floor (sideeffect)

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

			d.o = OFMakeVector3D(d.o.x, d.o.y,
			    d.o.z - min(min(drop, space), headspace));

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

			// cancel out jumping velocity

			d.vel = OFMakeVector3D(d.vel.x, d.vel.y, 0);
		}

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

	return true;
}

float
rad(float x)
{
	return x * 3.14159f / 180;







|
>
|









>



>



|


>






>
|
|

|



|
|


|
|




>
|
|

|




|
>

>
|


|

>







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)
		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.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 =
		    OFMakeVector3D(d.origin.x, d.origin.y, lo + d.eyeHeight);
		d.onFloor = true;
	} else {
		const float space = d.origin.z - d.eyeHeight - lo;
		if (space < 0) {
			if (space > -0.01)
				// stick on step
				d.origin = OFMakeVector3D(
				    d.origin.x, d.origin.y, lo + d.eyeHeight);
			else if (space > -1.26f)
				// rise thru stair
				d.origin = OFAddVector3D(
				    d.origin, OFMakeVector3D(0, 0, rise));
			else
				return false;
		} else
			// gravity
			d.origin = OFSubtractVector3D(d.origin,
			    OFMakeVector3D(
			        0, 0, min(min(drop, space), headspace)));

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

		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
// 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 floating = (editmode && local) || pl.state == CS_EDITING;

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

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

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

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

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

	const float fpsfric = friction / curtime * 20.0f;

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

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

	if (floating) {
		// just apply velocity
		vadd(pl.o, d);
		if (pl.jumpnext) {
			pl.jumpnext = false;

			pl.vel = OFMakeVector3D(pl.vel.x, pl.vel.y, 2);
		}
	} else {
		// apply velocity with collision
		if (pl.onfloor || water) {
			if (pl.jumpnext) {
				pl.jumpnext = false;
				// physics impulse upwards
				pl.vel =
				    OFMakeVector3D(pl.vel.x, pl.vel.y, 1.7);
				// dampen velocity change even harder, gives
				// correct water feel
				if (water)
					pl.vel = OFMakeVector3D(pl.vel.x / 8,

					    pl.vel.y / 8, pl.vel.z);
				if (local)
					playsoundc(S_JUMP);
				else if (pl.monsterstate) {
					OFVector3D loc = pl.o;
					playsound(S_JUMP, &loc);
				}
			} else if (pl.timeinair > 800) {
				// if we land after long time must have been a
				// high jump, make thud sound
				if (local)
					playsoundc(S_LAND);
				else if (pl.monsterstate) {
					OFVector3D loc = pl.o;
					playsound(S_LAND, &loc);
				}
			}

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

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

		loopi(moveres) // discrete steps collision detection & sliding
		{
			// try move forward
			pl.o = OFMakeVector3D(pl.o.x + f * d.x,
			    pl.o.y + f * d.y, pl.o.z + f * d.z);
			if (collide(pl, false, drop, rise))
				continue;

			// player stuck, try slide along y axis
			pl.blocked = true;

			pl.o = OFMakeVector3D(pl.o.x - f * d.x, pl.o.y, pl.o.z);
			if (collide(pl, false, drop, rise)) {
				d.x = 0;
				continue;
			}

			// still stuck, try x axis
			pl.o = OFMakeVector3D(
			    pl.o.x + f * d.x, pl.o.y - f * d.y, pl.o.z);
			if (collide(pl, false, drop, rise)) {
				d.y = 0;
				continue;
			}

			// try just dropping down
			pl.moving = false;

			pl.o = OFMakeVector3D(pl.o.x - f * d.x, pl.o.y, pl.o.z);
			if (collide(pl, false, drop, rise)) {
				d.y = d.x = 0;
				continue;
			}
			pl.o = OFMakeVector3D(pl.o.x, pl.o.y, pl.o.z - f * d.z);


			break;
		}
	}

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

	if (pl.o.x < 0 || pl.o.x >= ssize || pl.o.y < 0 || pl.o.y > ssize)

		pl.outsidemap = true;
	else {
		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);
	}

	// automatically apply smooth roll when strafing

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

	// play sounds on water transitions

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

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







|

















|

|





|
|
|
|







|
|
|
>
|



|
|
|

|
|



|
>
|


|
|


|




|
|



>
|

|




|



|









|
|


>


>
|




>

|
|




>


>
|




|
>
>







|
>
|

|
|
>
|
>
|
















|
|

|
|
|


|









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

	const float fpsfric = friction / curtime * 20.0f;

	// slowly apply friction and direction to
	// velocity, gives a smooth movement
	vmul(pl.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.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;
				// physics impulse upwards
				pl.velocity = OFMakeVector3D(
				    pl.velocity.x, pl.velocity.y, 1.7);
				// dampen velocity change even harder, gives
				// correct water feel
				if (water)
					pl.velocity = OFMakeVector3D(
					    pl.velocity.x / 8,
					    pl.velocity.y / 8, pl.velocity.z);
				if (local)
					playsoundc(S_JUMP);
				else if (pl.monsterState) {
					OFVector3D loc = pl.origin;
					playsound(S_JUMP, &loc);
				}
			} else if (pl.timeInAir > 800) {
				// if we land after long time must have been a
				// high jump, make thud sound
				if (local)
					playsoundc(S_LAND);
				else if (pl.monsterState) {
					OFVector3D loc = pl.origin;
					playsound(S_LAND, &loc);
				}
			}

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

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

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

			// still stuck, try x axis
			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.origin, OFMakeVector3D(f * d.x, 0, 0));
			if (collide(pl, false, drop, rise)) {
				d.y = d.x = 0;
				continue;
			}

			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.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.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.origin;
		playsound(S_SPLASH2, &loc);
		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;
}

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







<





<







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

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

	glDisable(GL_DEPTH_TEST);
	invertperspective();







|







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







|







381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
	renderscores();
	if (!rendermenu()) {
		glBlendFunc(GL_SRC_ALPHA, GL_SRC_ALPHA);
		glBindTexture(GL_TEXTURE_2D, 1);
		glBegin(GL_QUADS);
		glColor3ub(255, 255, 255);
		if (crosshairfx) {
			if (player1.gunWait)
				glColor3ub(128, 128, 128);
			else if (player1.health <= 25)
				glColor3ub(255, 0, 0);
			else if (player1.health <= 50)
				glColor3ub(255, 128, 0);
		}
		float chsize = (float)crosshairsize;
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]);
		glPopMatrix();
		glPushMatrix();
		glOrtho(0, VIRTW, VIRTH, 0, -1, 1);
		glDisable(GL_BLEND);
		drawicon(128, 128, 20, 1650);
		if (player1.armour)
			drawicon(
			    (float)(player1.armourtype * 64), 0, 620, 1650);
		int g = player1.gunselect;
		int r = 64;
		if (g > 2) {
			g -= 3;
			r = 128;
		}
		drawicon((float)(g * 64), (float)r, 1220, 1650);
		glPopMatrix();







|







|
|







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]);
		glPopMatrix();
		glPushMatrix();
		glOrtho(0, VIRTW, VIRTH, 0, -1, 1);
		glDisable(GL_BLEND);
		drawicon(128, 128, 20, 1650);
		if (player1.armour)
			drawicon(
			    (float)(player1.armourType * 64), 0, 620, 1650);
		int g = player1.gunSelect;
		int r = 64;
		if (g > 2) {
			g -= 3;
			r = 128;
		}
		drawicon((float)(g * 64), (float)r, 1220, 1650);
		glPopMatrix();

Modified src/rendergl.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
{
	glLoadIdentity();

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

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

VARP(fov, 10, 105, 120);

int xtraverts;

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

VARP(hudgun, 0, 1, 1);

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

void
drawhudmodel(int start, int end, float speed, int base)
{
	rendermodel(hudgunnames[player1.gunselect], start, end, 0, 1.0f,
	    OFMakeVector3D(player1.o.x, player1.o.z, player1.o.y),

	    player1.yaw + 90, player1.pitch, false, 1.0f, speed, 0, base);
}

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

	glEnable(GL_CULL_FACE);

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

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

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

	glDisable(GL_CULL_FACE);
}

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

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

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

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

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

	transplayer();

	glEnable(GL_TEXTURE_2D);

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

	resetcubes();

	curvert = 0;
	[strips removeAllItems];

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

	gl_setupworld();

	renderstripssky();

	glLoadIdentity();







|
|
|
|

















|
|
>






|










|
|
|
|

















|
















|




















|
|







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.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.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*/)
		return;

	glEnable(GL_CULL_FACE);

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

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

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

	glDisable(GL_CULL_FACE);
}

void
gl_drawframe(int w, int h, float curfps)
{
	float hf = hdr.waterlevel - 0.3f;
	float fovy = (float)fov * h / w;
	float aspect = w / (float)h;
	bool underwater = (player1.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) |
	    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.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
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,
	        position.z - rad, rad * 2))
		return;

	delayedload(m);

	int xs, ys;
	glBindTexture(GL_TEXTURE_2D,







|







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.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
	up = *u;
}

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

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







|







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.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
	restoreserverstate(ents);

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

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

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

	int nplayers = gzgeti();
	loopi(nplayers) if (!gzget())
	{
		DynamicEntity *d = getclient(i);







|












|

|







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;

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

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

	int nplayers = gzgeti();
	loopi(nplayers) if (!gzget())
	{
		DynamicEntity *d = getclient(i);
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);
		gzputi(player1.health);
		gzputi(player1.armour);
		gzput(player1.armourtype);
		loopi(NUMGUNS) gzput(player1.ammo[i]);
		gzput(player1.state);
		gzputi(bdamage);
		bdamage = 0;
		gzputi(ddamage);
		if (ddamage) {
			gzputv(&dorig);







|
|
|
|


|







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);
		gzputi(player1.health);
		gzputi(player1.armour);
		gzput(player1.armourType);
		loopi(NUMGUNS) gzput(player1.ammo[i]);
		gzput(player1.state);
		gzputi(bdamage);
		bdamage = 0;
		gzputi(ddamage);
		if (ddamage) {
			gzputv(&dorig);
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.health = gzgeti();
			target.armour = gzgeti();
			target.armourtype = gzget();
			loopi(NUMGUNS) target.ammo[i] = gzget();
			target.state = gzget();
			target.lastmove = playbacktime;
			if ((bdamage = gzgeti()))
				damageblend(bdamage);
			if ((ddamage = gzgeti())) {
				gzgetv(&dorig);
				particle_splash(3, ddamage, 1000, &dorig);
			}
			// FIXME: set more client state here
		}

		// insert latest copy of player into history
		if (extras &&
		    (playerhistory.count == 0 ||
		        playerhistory.lastObject.lastupdate != playbacktime)) {
			DynamicEntity *d = [target copy];
			d.lastupdate = playbacktime;

			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) {
			DynamicEntity *a = playerhistory[i];
			DynamicEntity *b = a;

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

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







|
|
|
|


|


|












|

|




















|

















|
|



|


|
|







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.health = gzgeti();
			target.armour = gzgeti();
			target.armourType = gzget();
			loopi(NUMGUNS) target.ammo[i] = gzget();
			target.state = gzget();
			target.lastMove = playbacktime;
			if ((bdamage = gzgeti()))
				damageblend(bdamage);
			if ((ddamage = gzgeti())) {
				gzgetv(&dorig);
				particle_splash(3, ddamage, 1000, &dorig);
			}
			// FIXME: set more client state here
		}

		// insert latest copy of player into history
		if (extras &&
		    (playerhistory.count == 0 ||
		        playerhistory.lastObject.lastUpdate != playbacktime)) {
			DynamicEntity *d = [target copy];
			d.lastUpdate = playbacktime;

			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) {
			DynamicEntity *a = playerhistory[i];
			DynamicEntity *b = a;

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

			player1 = b;
			// interpolate pos & angles
			if (a != b) {
				DynamicEntity *c = b;
				if (i + 2 < playerhistory.count)
					c = playerhistory[i + 2];
				DynamicEntity *z = a;
				if (i - 1 >= 0)
					z = playerhistory[i - 1];
				// if(a==z || b==c)
				//	printf("* %d\n", lastmillis);
				float bf = (itime - a.lastUpdate) /
				    (float)(b.lastUpdate - a.lastUpdate);
				fixwrap(a, player1);
				fixwrap(c, player1);
				fixwrap(z, player1);
				vdist(dist, v, z.origin, c.origin);
				// if teleport or spawn, don't interpolate
				if (dist < 16) {
					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
VAR(stereo, 0, 1, 1);

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







|







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

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

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







|
















|

|







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

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

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

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

	return (p->x <= d.o.x + d.radius && p->x >= d.o.x - d.radius &&
	    p->y <= d.o.y + d.radius && p->y >= d.o.y - d.radius &&

	    p->z <= d.o.z + d.aboveeye && p->z >= d.o.z - d.eyeheight);
}

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

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

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

	return nil;
}








|



















|
|
>
|












|







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

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

void
splash(Projectile *p, const OFVector3D *v, const OFVector3D *vold,
    int notthisplayer, int notthismonster, int qdam)
{







|


|


|















|







|







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.origin;
	if (d == player1)
		selfdamage(damage, at == player1 ? -1 : -2, at);
	else if (d.monsterState)
		monsterpain(d, damage, at);
	else {
		addmsg(1, 4, SV_DAMAGE, target, damage, d.lifeSequence);
		playsound(S_PAIN1 + rnd(5), &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.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.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
{
	for (size_t i = 0; i < MAXPROJ; i++) {
		Projectile *p = projs[i];

		if (!p.inuse)
			continue;

		int qdam = guns[p.gun].damage * (p.owner.quadmillis ? 4 : 1);
		if (p.owner.monsterstate)
			qdam /= MONSTERDAMAGEFACTOR;
		vdist(dist, v, p.o, p.to);
		float dtime = dist * 1000 / p.speed;
		if (time > dtime)
			dtime = time;
		vmul(v, time / dtime);
		vadd(v, p.o);
		if (p.local) {
			for (id player in players)
				if (player != [OFNull null])
					projdamage(player, p, &v, i, -1, qdam);

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

			for (DynamicEntity *monster in getmonsters())
				if (!vreject(monster.o, v, 10.0f) &&
				    monster != p.owner)
					projdamage(monster, p, &v, -1, i, qdam);
		}
		if (p.inuse) {
			OFVector3D po = p.o;

			if (time == dtime)







|
|
















|







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

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

	case GUN_SG: {







|







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

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

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

void
hitpush(int target, int damage, DynamicEntity *d, DynamicEntity *at,
    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);
}

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)
		qdam *= 4;
	if (d.monsterstate)
		qdam /= MONSTERDAMAGEFACTOR;
	if (d.gunselect == GUN_SG) {
		int damage = 0;
		loop(r, SGRAYS) if (intersect(o, from, &sg[r])) damage += qdam;
		if (damage)
			hitpush(i, damage, o, d, from, to);
	} else if (intersect(o, from, to))
		hitpush(i, qdam, o, d, from, to);
}

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

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

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

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

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

	[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)
		raydamage(player1, &from, &to, d, -1);
}







|


















|








|
|

|

|











|
|

|


|
|
|

|
|


|
|
|






|
|

|

|




|


|

|
|
|


|

|











|


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)
			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.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)
		qdam *= 4;
	if (d.monsterState)
		qdam /= MONSTERDAMAGEFACTOR;
	if (d.gunSelect == GUN_SG) {
		int damage = 0;
		loop(r, SGRAYS) if (intersect(o, from, &sg[r])) damage += qdam;
		if (damage)
			hitpush(i, damage, o, d, from, to);
	} else if (intersect(o, from, to))
		hitpush(i, qdam, o, d, from, to);
}

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

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

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

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

	[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)
		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
	__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);
		if (dist < bdist) {
			best = i;
			bdist = dist;
		}
	}];

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







|







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

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

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







|







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)
		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
	int ry = vyy + lodbot;

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

	int pvx = (int)vx >> mip;
	int pvy = (int)vy >> mip;
	if (pvx >= 0 && pvy >= 0 && pvx < sz && pvy < sz) {







|







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