1 #define GAMEPHYSICS 1
2 #include "game.h"
3 namespace physics
4 {
5 	FVARW(gravity,			0, 50.f, 10000);		// gravity
6 	FVARW(jumpspeed,		0, 50.f, 10000);		// extra velocity to add when jumping
7 	FVARW(movespeed,		0, 50.f, 10000);		// speed
8 	FVARW(movecrawl,		0, 0.5f, 10000);		// crawl modifier
9 	FVARW(impulsespeed,		0, 50.f, 10000);		// extra velocity to add when impulsing
10 
11 	VARW(impulsestyle,		0, 1, 3);				// impulse style; 0 = off, 1 = touch and count, 2 = count only, 3 = freestyle
12 	VARW(impulsemeter,		0, 30000, INT_MAX-1);	// impulse dash length; 0 = unlimited, anything else = timer
13 	VARW(impulsecost,		0, 1000, INT_MAX-1);	// cost of impulse jump
14 	VARW(impulsecount,		0, 5, INT_MAX-1);		// number of impulse actions per air transit
15 	VARW(impulseskate,		0, 1000, INT_MAX-1);	// length of time a run along a wall can last
16 	FVARW(impulseregen,		0, 5, 10000);			// impulse regen multiplier
17 
18 	FVARW(liquidspeed,		0, 0.85f, 1);
19 	FVARW(liquidcurb,		0, 10.f, 10000);
20 	FVARW(floorcurb,		0, 5.f, 10000);
21 	FVARW(aircurb,			0, 25.f, 10000);
22 
23 	FVARW(stairheight,		0, 4.1f, 10000);
24 	FVARW(floorz,			0, 0.867f, 1);
25 	FVARW(slopez,			0, 0.5f, 1);
26 	FVARW(wallz,			0, 0.2f, 1);
27 	FVARW(stepspeed,		1e-3f, 1.f, 10000);
28 	FVARW(ladderspeed,		1e-3f, 1.f, 10000);
29 
30 	FVARP(floatspeed,		1e-3f, 75, 10000);
31 	FVARP(floatcurb,        0, 1.f, 10000);
32 
33 	FVARP(impulseroll,      0, 10, 90);
34 	FVARP(impulsereflect,   0, 155, 360);
35 
36 	VARP(physframetime,		5, 5, 20);
37 	VARP(physinterp,		0, 1, 1);
38 
39 	VARP(impulsedash,		0, 1, 3);			// determines how impulsedash works, 0 = off, 1 = double jump, 2 = double tap, 3 = double jump only
40 
41 	int physsteps = 0, lastphysframe = 0, lastmove = 0, lastdirmove = 0, laststrafe = 0, lastdirstrafe = 0;
42 
43 	#define imov(name,v,u,d,s,os) \
44 		void do##name(bool down) \
45 		{ \
46 			game::player1->s = down; \
47 			int dir = game::player1->s ? d : (game::player1->os ? -(d) : 0); \
48 			game::player1->v = dir; \
49 			if(down) \
50 			{ \
51 				if(FWV(impulsestyle) && impulsedash == 2 && last##v && lastdir##v && dir == lastdir##v && lastmillis-last##v < PHYSMILLIS) \
52 				{ \
53 					game::player1->action[AC_DASH] = true; \
54 					game::player1->actiontime[AC_DASH] = lastmillis; \
55 				} \
56 				last##v = lastmillis; lastdir##v = dir; \
57 				last##u = lastdir##u = 0; \
58 			} \
59 		} \
60 		ICOMMAND(name, "D", (int *down), { do##name(*down!=0); });
61 
62 	imov(backward, move,   strafe,	-1, k_down,  k_up);
63 	imov(forward,  move,   strafe,	 1, k_up,    k_down);
64 	imov(left,     strafe, move,	 1, k_left,  k_right);
65 	imov(right,    strafe, move,	-1, k_right, k_left);
66 
67 	// inputs
doaction(int type,bool down)68 	void doaction(int type, bool down)
69 	{
70 		if(type < AC_TOTAL && type > -1)
71 		{
72 			if(game::allowmove(game::player1))
73 			{
74 				if(type == AC_CROUCH)
75 				{
76 					if(game::player1->action[type] != down)
77 					{
78 						if(game::player1->actiontime[type] >= 0) game::player1->actiontime[type] = lastmillis-max(PHYSMILLIS-(lastmillis-game::player1->actiontime[type]), 0);
79 						else if(down) game::player1->actiontime[type] = -game::player1->actiontime[type];
80 					}
81 				}
82 				else if(down) game::player1->actiontime[type] = lastmillis;
83 				game::player1->action[type] = down;
84 			}
85 			else
86 			{
87 				game::player1->action[type] = false;
88 				if(type == AC_ATTACK && down) game::respawn(game::player1);
89 			}
90 		}
91 	}
92 	ICOMMAND(action, "Di", (int *n, int *i), { doaction(*i, *n!=0); });
93 
issolid(physent * d,physent * e)94 	bool issolid(physent *d, physent *e)
95 	{
96 		if(e && e->type == ENT_PROJ)
97 		{
98 			projent *p = (projent *)e;
99 			if(p->hit == d || !(p->projcollide&COLLIDE_PLAYER)) return false;
100 			if(p->owner == d && (!(p->projcollide&COLLIDE_OWNER) || !p->escaped)) return false;
101 		}
102 		if(d->state == CS_ALIVE)
103 		{
104 			if(d->type == ENT_PLAYER && ((gameent *)d)->protect(lastmillis, m_protect(game::gamemode, game::mutators)))
105 				return false;
106 			return true;
107 		}
108         return d->state == CS_DEAD || d->state == CS_WAITING;
109 	}
110 
iscrouching(physent * d)111 	bool iscrouching(physent *d)
112 	{
113 		if(d->type == ENT_PLAYER || d->type == ENT_AI)
114 		{
115 			gameent *e = (gameent *)d;
116 			return e->action[AC_CROUCH] || e->actiontime[AC_CROUCH] < 0 || lastmillis-e->actiontime[AC_CROUCH] <= PHYSMILLIS;
117 		}
118 		return false;
119 	}
liquidcheck(physent * d)120 	bool liquidcheck(physent *d) { return d->inliquid && d->submerged > 0.8f; }
121 
liquidmerge(physent * d,float from,float to)122 	float liquidmerge(physent *d, float from, float to)
123 	{
124 		if(d->inliquid)
125 		{
126 			if(d->physstate >= PHYS_SLIDE && d->submerged < 1.f)
127 				return from-((from-to)*d->submerged);
128 			else return to;
129 		}
130 		return from;
131 	}
132 
jumpforce(physent * d,bool liquid)133 	float jumpforce(physent *d, bool liquid) { return FWV(jumpspeed)*(d->weight/100.f)*(liquid ? liquidmerge(d, 1.f, FWV(liquidspeed)) : 1.f); }
impulseforce(physent * d)134 	float impulseforce(physent *d) { return FWV(impulsespeed)*(d->weight/100.f); }
gravityforce(physent * d)135 	float gravityforce(physent *d) { return FWV(gravity)*(d->weight/100.f); }
136 
stepforce(physent * d,bool up)137 	float stepforce(physent *d, bool up)
138 	{
139 		if(up && d->onladder) return ladderspeed;
140 		if(d->physstate > PHYS_FALL) return stepspeed;
141 		return 1.f;
142 	}
143 
sticktofloor(physent * d)144 	bool sticktofloor(physent *d)
145 	{
146 		if(!d->onladder && !liquidcheck(d) && (d->type == ENT_PLAYER || d->type == ENT_AI) && FWV(gravity) > 0)
147 		{
148 			gameent *e = (gameent *)d;
149 			if(e->turnside || (e->lastpush && lastmillis-e->lastpush <= PHYSMILLIS) || (e->actiontime[AC_JUMP] && lastmillis-e->actiontime[AC_JUMP] <= PHYSMILLIS))
150 				return false;
151 			return true;
152 		}
153 		return false;
154 	}
155 
sticktospecial(physent * d)156 	bool sticktospecial(physent *d)
157 	{
158 		if(d->onladder) return true;
159 		if(d->type == ENT_PLAYER || d->type == ENT_AI) { if(((gameent *)d)->turnside) return true; }
160 		return false;
161 	}
162 
canimpulse(physent * d,int cost)163 	bool canimpulse(physent *d, int cost)
164 	{
165 		if((d->type == ENT_PLAYER || d->type == ENT_AI) && FWV(impulsestyle))
166 		{
167 			gameent *e = (gameent *)d;
168 			if(FWV(impulsemeter) && e->impulse[IM_METER]+(cost > 0 ? cost : FWV(impulsecost)) > FWV(impulsemeter)) return false;
169 			if(cost <= 0)
170 			{
171 				if(e->impulse[IM_TIME] && lastmillis-e->impulse[IM_TIME] <= PHYSMILLIS) return false;
172 				if(FWV(gravity) > 0)
173 				{
174 					if(FWV(impulsestyle) <= 2 && e->impulse[IM_COUNT] >= FWV(impulsecount)) return false;
175 					if(cost == 0 && FWV(impulsestyle) == 1 && e->impulse[IM_TYPE] > IM_T_NONE && e->impulse[IM_TYPE] < IM_T_WALL) return false;
176 				}
177 			}
178 			return true;
179 		}
180 		return false;
181 	}
182 
movevelocity(physent * d)183 	float movevelocity(physent *d)
184 	{
185 		if(d->type == ENT_CAMERA) return game::player1->maxspeed*(game::player1->weight/100.f)*(floatspeed/100.0f);
186 		else if(d->type == ENT_PLAYER || d->type == ENT_AI)
187 		{
188 			if(d->state == CS_EDITING || d->state == CS_SPECTATOR) return d->maxspeed*(d->weight/100.f)*(floatspeed/100.0f);
189 			else
190 			{
191 				float speed = FWV(movespeed);
192 				if(iscrouching(d) || (d == game::player1 && game::inzoom())) speed *= FWV(movecrawl);
193 				if(FWV(impulsestyle) && ((gameent *)d)->action[AC_IMPULSE] && (d->move || d->strafe) && (!FWV(impulsemeter) || ((gameent *)d)->impulse[IM_METER] < FWV(impulsemeter)))
194 					speed += FWV(impulsespeed)*(d->move < 0 ? 0.5f : 1);
195 				return max(d->maxspeed,1.f)*(d->weight/100.f)*(speed/100.f);
196 			}
197 		}
198 		return max(d->maxspeed,1.f);
199 	}
200 
movepitch(physent * d)201 	bool movepitch(physent *d)
202 	{
203 		if(d->type == ENT_CAMERA || d->state == CS_EDITING || d->state == CS_SPECTATOR) return true;
204 		if(d->state == CS_ALIVE && FWV(gravity) <= 0 && d->physstate == PHYS_FALL) return true;
205 		return false;
206 	}
207 
recalcdir(physent * d,const vec & oldvel,vec & dir)208     void recalcdir(physent *d, const vec &oldvel, vec &dir)
209     {
210         float speed = oldvel.magnitude();
211         if(speed > 1e-6f)
212         {
213             float step = dir.magnitude();
214             dir = d->vel;
215             dir.add(d->falling);
216             dir.mul(step/speed);
217         }
218     }
219 
slideagainst(physent * d,vec & dir,const vec & obstacle,bool foundfloor,bool slidecollide)220     void slideagainst(physent *d, vec &dir, const vec &obstacle, bool foundfloor, bool slidecollide)
221     {
222         vec wall(obstacle);
223         if(foundfloor ? wall.z > 0 : slidecollide)
224         {
225             wall.z = 0;
226             if(!wall.iszero()) wall.normalize();
227         }
228         vec oldvel(d->vel);
229         oldvel.add(d->falling);
230         d->vel.project(wall);
231         d->falling.project(wall);
232         recalcdir(d, oldvel, dir);
233     }
234 
switchfloor(physent * d,vec & dir,const vec & floor)235     void switchfloor(physent *d, vec &dir, const vec &floor)
236     {
237         if(floor.z >= floorz) d->falling = vec(0, 0, 0);
238 
239         vec oldvel(d->vel);
240         oldvel.add(d->falling);
241         if(dir.dot(floor) >= 0)
242         {
243             if(d->physstate < PHYS_SLIDE || fabs(dir.dot(d->floor)) > 0.01f*dir.magnitude()) return;
244             d->vel.projectxy(floor, 0.0f);
245         }
246         else d->vel.projectxy(floor);
247         d->falling.project(floor);
248         recalcdir(d, oldvel, dir);
249     }
250 
trystepup(physent * d,vec & dir,const vec & obstacle,float maxstep,const vec & floor)251     bool trystepup(physent *d, vec &dir, const vec &obstacle, float maxstep, const vec &floor)
252     {
253         vec old(d->o), stairdir = (obstacle.z >= 0 && obstacle.z < slopez ? vec(-obstacle.x, -obstacle.y, 0) : vec(dir.x, dir.y, 0)).rescale(1);
254         float force = stepforce(d, true);
255 		if(d->onladder)
256 		{
257 			vec laddir = vec(stairdir).add(vec(0, 0, maxstep)).mul(0.1f*force);
258             loopi(2)
259             {
260 				d->o.add(laddir);
261             	if(collide(d))
262 				{
263 					if(d->physstate == PHYS_FALL || d->floor != floor)
264 					{
265 						d->timeinair = 0;
266 						d->floor = vec(0, 0, 1);
267 						switchfloor(d, dir, d->floor);
268 					}
269 					d->physstate = PHYS_STEP_UP;
270 					return true;
271 				}
272 				d->o = old; // try again, but only up
273 				laddir.x = laddir.y = 0;
274             }
275             return false;
276 		}
277         bool cansmooth = true;
278         d->o = old;
279         /* check if there is space atop the stair to move to */
280         if(d->physstate != PHYS_STEP_UP)
281         {
282             vec checkdir = stairdir;
283             checkdir.mul(0.1f);
284             checkdir.z += maxstep + 0.1f;
285             checkdir.mul(force);
286             d->o.add(checkdir);
287             if(!collide(d))
288             {
289                 d->o = old;
290                 if(collide(d, vec(0, 0, -1), slopez)) return false;
291                 cansmooth = false;
292             }
293         }
294 
295         if(cansmooth)
296         {
297             d->o = old;
298             vec checkdir = stairdir;
299             checkdir.z += 1;
300             checkdir.mul(maxstep*force);
301             d->o.add(checkdir);
302             if(!collide(d, checkdir))
303             {
304                 if(collide(d, vec(0, 0, -1), slopez))
305                 {
306                     d->o = old;
307                     return false;
308                 }
309             }
310 			/* try stepping up half as much as forward */
311 			d->o = old;
312 			vec smoothdir = vec(dir.x, dir.y, 0).mul(force);
313 			float magxy = smoothdir.magnitude();
314 			if(magxy > 1e-9f)
315 			{
316 				if(magxy > 2*dir.z)
317 				{
318 					smoothdir.mul(1/magxy);
319 					smoothdir.z = 0.5f;
320 					smoothdir.mul(dir.magnitude()*force/smoothdir.magnitude());
321 				}
322 				else smoothdir.z = dir.z;
323 				d->o.add(smoothdir);
324 				d->o.z += maxstep*force + 0.1f*force;
325 				if(collide(d, smoothdir))
326 				{
327 					d->o.z -= maxstep*force + 0.1f*force;
328 					if(d->physstate == PHYS_FALL || d->floor != floor)
329 					{
330 						d->timeinair = 0;
331 						d->floor = floor;
332 						switchfloor(d, dir, d->floor);
333 					}
334 					d->physstate = PHYS_STEP_UP;
335 					return true;
336 				}
337 			}
338         }
339 
340         /* try stepping up */
341         d->o = old;
342         d->o.z += dir.magnitude()*force;
343         if(collide(d, vec(0, 0, 1)))
344         {
345             if(d->physstate == PHYS_FALL || d->floor != floor)
346             {
347                 d->timeinair = 0;
348                 d->floor = floor;
349                 switchfloor(d, dir, d->floor);
350             }
351             if(cansmooth) d->physstate = PHYS_STEP_UP;
352             return true;
353         }
354         d->o = old;
355         return false;
356     }
357 
trystepdown(physent * d,vec & dir,float step,float xy,float z)358 	bool trystepdown(physent *d, vec &dir, float step, float xy, float z)
359 	{
360 		vec stepdir(dir.x, dir.y, 0);
361         stepdir.z = -stepdir.magnitude2()*z/xy;
362         if(!stepdir.z) return false;
363         stepdir.normalize();
364 
365         vec old(d->o);
366 		d->o.add(vec(stepdir).mul(stairheight/fabs(stepdir.z))).z -= stairheight;
367 		if(!collide(d, vec(0, 0, -1), slopez))
368 		{
369 			d->o = old;
370 			d->o.add(vec(stepdir).mul(step));
371 			if(collide(d, vec(0, 0, -1)))
372             {
373                 stepdir.mul(-stepdir.z).z += 1;
374                 stepdir.normalize();
375                 switchfloor(d, dir, stepdir);
376                 d->floor = stepdir;
377                 return true;
378             }
379 		}
380 		d->o = old;
381 		return false;
382 	}
383 
384 
falling(physent * d,vec & dir,const vec & floor)385     void falling(physent *d, vec &dir, const vec &floor)
386 	{
387 		if(d->physstate >= PHYS_FLOOR && (d->physstate != PHYS_STEP_DOWN || dir.z < -0.25f*dir.magnitude2()) && sticktofloor(d))
388 		{
389 			vec moved(d->o);
390 			d->o.z -= stairheight+0.1f;
391 			if(!collide(d, vec(0, 0, -1), slopez))
392 			{
393 				d->o = moved;
394 				d->timeinair = 0;
395 				d->physstate = PHYS_STEP_DOWN;
396 				return;
397 			}
398 			else d->o = moved;
399 		}
400 
401         if(floor.z > 0.0f && floor.z < slopez)
402         {
403             if(floor.z >= wallz) switchfloor(d, dir, floor);
404             d->timeinair = 0;
405             d->physstate = PHYS_SLIDE;
406             d->floor = floor;
407         }
408         else if(sticktospecial(d))
409         {
410             d->timeinair = 0;
411             d->physstate = PHYS_FLOOR;
412             d->floor = vec(0, 0, 1);
413         }
414 		else d->physstate = PHYS_FALL;
415 	}
416 
landing(physent * d,vec & dir,const vec & floor,bool collided)417     void landing(physent *d, vec &dir, const vec &floor, bool collided)
418 	{
419         switchfloor(d, dir, floor);
420         d->timeinair = 0;
421         if((d->physstate!=PHYS_STEP_UP && d->physstate!=PHYS_STEP_DOWN) || !collided)
422             d->physstate = floor.z >= floorz ? PHYS_FLOOR : PHYS_SLOPE;
423 		d->floor = floor;
424 	}
425 
findfloor(physent * d,bool collided,const vec & obstacle,bool & slide,vec & floor)426 	bool findfloor(physent *d, bool collided, const vec &obstacle, bool &slide, vec &floor)
427 	{
428 		bool found = false;
429 		vec moved(d->o);
430 		d->o.z -= 0.1f;
431 		if(sticktospecial(d))
432 		{
433 			floor = vec(0, 0, 1);
434 			found = true;
435 		}
436 		else if(d->physstate != PHYS_FALL && !collide(d, vec(0, 0, -1), d->physstate == PHYS_SLOPE || d->physstate == PHYS_STEP_DOWN ? slopez : floorz))
437 		{
438 			floor = wall;
439 			found = true;
440 		}
441 		else if(collided && obstacle.z >= slopez)
442 		{
443 			floor = obstacle;
444 			found = true;
445 			slide = false;
446 		}
447         else if(d->physstate == PHYS_STEP_UP || d->physstate == PHYS_SLIDE)
448         {
449             if(!collide(d, vec(0, 0, -1)) && wall.z > 0.0f)
450             {
451                 floor = wall;
452                 if(floor.z >= slopez) found = true;
453             }
454         }
455         else if(d->physstate >= PHYS_SLOPE && d->floor.z < 1.0f)
456         {
457             if(!collide(d, vec(d->floor).neg(), 0.95f) || !collide(d, vec(0, 0, -1)))
458             {
459                 floor = wall;
460                 if(floor.z >= slopez && floor.z < 1.0f) found = true;
461             }
462         }
463         if(collided && (!found || obstacle.z > floor.z))
464         {
465             floor = obstacle;
466             slide = !found && (floor.z < wallz || floor.z >= slopez);
467         }
468 		d->o = moved;
469 		return found;
470 	}
471 
move(physent * d,vec & dir)472 	bool move(physent *d, vec &dir)
473 	{
474 		vec old(d->o), obstacle; d->o.add(dir);
475 		bool collided = false, slidecollide = false;
476 		if(!collide(d, dir))
477 		{
478             obstacle = wall;
479             /* check to see if there is an obstacle that would prevent this one from being used as a floor */
480             if((d->type==ENT_PLAYER || d->type==ENT_AI) && ((wall.z>=slopez && dir.z<0) || (wall.z<=-slopez && dir.z>0)) && (dir.x || dir.y) && !collide(d, vec(dir.x, dir.y, 0)))
481             {
482                 if(wall.dot(dir) >= 0) slidecollide = true;
483                 obstacle = wall;
484             }
485 
486             d->o = old;
487             d->o.z -= stairheight;
488             d->zmargin = -stairheight;
489             if(d->physstate == PHYS_SLOPE || d->physstate == PHYS_FLOOR  || d->physstate == PHYS_STEP_DOWN || (!collide(d, vec(0, 0, -1), slopez) && (d->physstate == PHYS_STEP_UP || wall.z >= floorz || d->onladder)))
490             {
491                 d->o = old;
492                 d->zmargin = 0;
493                 if(trystepup(d, dir, obstacle, stairheight, d->physstate == PHYS_SLOPE || d->physstate == PHYS_FLOOR  || d->physstate == PHYS_STEP_DOWN || d->onladder ? d->floor : vec(wall)))
494 					return true;
495             }
496             else
497             {
498                 d->o = old;
499                 d->zmargin = 0;
500             }
501 			collided = true; // can't step over the obstacle, so just slide against it
502 		}
503         else if(d->physstate == PHYS_STEP_UP || d->onladder)
504         {
505             if(!collide(d, vec(0, 0, -1), slopez))
506             {
507                 d->o = old;
508                 if(trystepup(d, dir, vec(0, 0, 1), stairheight, d->onladder ? d->floor : vec(wall))) return true;
509                 d->o.add(dir);
510             }
511         }
512 		else if((d->type == ENT_PLAYER || d->type == ENT_AI) && d->physstate == PHYS_STEP_DOWN && sticktofloor(d))
513 		{
514 			d->o = old;
515 			float step = dir.magnitude();
516 			if(trystepdown(d, dir, step, 2, 1)) return true;
517 			else if(trystepdown(d, dir, step, 1, 1)) return true;
518 			else if(trystepdown(d, dir, step, 1, 2)) return true;
519 			d->o = old; d->o.add(dir);
520 		}
521 		vec floor(0, 0, 0);
522 		bool slide = collided, found = findfloor(d, collided, obstacle, slide, floor);
523         if(slide || (!collided && floor.z > 0 && floor.z < wallz))
524         {
525             slideagainst(d, dir, slide ? obstacle : floor, found, slidecollide);
526             d->blocked = true;
527 		}
528 		if(found) landing(d, dir, floor, collided);
529 		else falling(d, dir, floor);
530 		return !collided;
531 	}
532 
modifyinput(gameent * d,vec & m,bool wantsmove,bool floating,int millis)533 	void modifyinput(gameent *d, vec &m, bool wantsmove, bool floating, int millis)
534 	{
535 		if(floating)
536 		{
537 			if(game::allowmove(d) && d->action[AC_JUMP]) d->vel.z += jumpforce(d, false);
538 		}
539 		else if(game::allowmove(d))
540 		{
541 			bool onfloor = d->physstate >= PHYS_SLOPE || d->onladder || liquidcheck(d);
542 			if(millis && FWV(impulsestyle))
543 			{
544 				if(d->action[AC_IMPULSE] && (d->move || d->strafe))
545 				{
546 					if(canimpulse(d, millis)) d->impulse[IM_METER] += millis;
547 					else d->action[AC_IMPULSE] = false;
548 				}
549 				else if(d->impulse[IM_METER] > 0 && FWV(impulseregen) > 0)
550 				{
551 					int timeslice = max(int(millis*FWV(impulseregen)), 1);
552 					if(iscrouching(d)) timeslice += timeslice;
553 					if(d->move || d->strafe) timeslice -= timeslice/2;
554 					if(d->physstate == PHYS_FALL && !d->onladder) timeslice -= timeslice/2;
555 					if((d->impulse[IM_METER] -= timeslice) < 0) d->impulse[IM_METER] = 0;
556 				}
557 			}
558 
559 			if(d->turnside && (!FWV(impulsestyle) || d->impulse[IM_TYPE] != IM_T_SKATE || lastmillis-d->impulse[IM_TIME] > FWV(impulseskate)))
560 			{
561 				d->turnside = 0;
562 				d->resetphys();
563 			}
564 
565 			if(FWV(impulsestyle) && (d->ai || (impulsedash > 0 && impulsedash < 3)) && canimpulse(d) && (d->move || d->strafe) && (!d->ai && impulsedash == 2 ? d->action[AC_DASH] : d->action[AC_JUMP] && !onfloor))
566 			{
567 				float mag = impulseforce(d)+max(d->vel.magnitude(), 1.f);
568 				vecfromyawpitch(d->aimyaw, !d->ai && impulsedash == 2 ? max(d->aimpitch, 10.f) : d->aimpitch, d->move, d->strafe, d->vel);
569 				d->vel.normalize().mul(mag); d->vel.z += mag/4;
570 				d->doimpulse(FWV(impulsecost), IM_T_DASH, lastmillis);
571 				playsound(S_IMPULSE, d->o, d); game::impulseeffect(d, true);
572 				client::addmsg(SV_PHYS, "ri2", d->clientnum, SPHY_IMPULSE);
573 			}
574 			if(!d->turnside && onfloor)
575 			{
576 				if(d->action[AC_JUMP])
577 				{
578 					d->vel.z += jumpforce(d, true);
579 					if(d->inliquid)
580 					{
581 						float scale = liquidmerge(d, 1.f, FWV(liquidspeed));
582 						d->vel.x *= scale;
583 						d->vel.y *= scale;
584 					}
585 					d->resetphys();
586 					playsound(S_JUMP, d->o, d);
587 					regularshape(PART_SMOKE, int(d->radius), 0x111111, 21, 20, 100, d->feetpos(), 1, 1, -10, 0, 10.f);
588 					client::addmsg(SV_PHYS, "ri2", d->clientnum, SPHY_JUMP);
589 				}
590 			}
591 			else
592 			{
593 				if(FWV(impulsestyle) && !d->turnside && !onfloor && canimpulse(d) && d->action[AC_JUMP])
594 				{
595 					d->vel.z += impulseforce(d)*1.5f;
596 					d->doimpulse(FWV(impulsecost), IM_T_BOOST, lastmillis);
597 					playsound(S_IMPULSE, d->o, d);
598 					game::impulseeffect(d, true);
599 					client::addmsg(SV_PHYS, "ri2", d->clientnum, SPHY_IMPULSE);
600 				}
601 				if(FWV(impulsestyle) && (d->turnside || (canimpulse(d, -1) && d->action[AC_SPECIAL])) && !d->inliquid && !d->onladder)
602 				{
603 					loopi(d->turnside ? 3 : 1)
604 					{
605 						vec oldpos = d->o, dir;
606 						int move = i ? (i%2 ? 1 : -1) : d->move, strafe = i >= 2 ? d->turnside : d->strafe;
607 						if(!move && !strafe) continue;
608 						vecfromyawpitch(d->aimyaw, 0, move, strafe, dir);
609 						d->o.add(dir);
610 						if(collide(d, dir) || wall.iszero())
611 						{
612 							d->o = oldpos;
613 							if(i >= (d->turnside ? 2 : 0)) { if(d->turnside) { d->turnside = 0; d->resetphys(); } break; }
614 							continue;
615 						}
616 						d->o = oldpos;
617 						wall.normalize();
618 						float yaw = 0, pitch = 0;
619 						vectoyawpitch(wall, yaw, pitch);
620 						float off = yaw-d->aimyaw;
621 						if(off > 180) off -= 360;
622 						else if(off < -180) off += 360;
623 						int key = (d->turnside && d->action[AC_JUMP]) ? AC_JUMP : ((!d->turnside && d->action[AC_SPECIAL] && fabs(off) >= impulsereflect) ? AC_SPECIAL : -1);
624 						if(key >= 0 && canimpulse(d, -1))
625 						{
626 							float mag = (impulseforce(d)+max(d->vel.magnitude(), 1.f))/2;
627 							d->vel = vec(d->turnside ? wall : vec(dir).reflect(wall)).add(vec(d->vel).reflect(wall).rescale(1)).mul(mag/2);
628 							vectoyawpitch(d->vel, yaw, pitch);
629 							d->vel.z += d->turnside ? mag : mag/2;
630 							off = yaw-d->aimyaw;
631 							if(off > 180) off -= 360;
632 							else if(off < -180) off += 360;
633 							d->doimpulse(FWV(impulsecost), IM_T_KICK, lastmillis);
634 							d->action[key] = false;
635 							d->turnmillis = PHYSMILLIS;
636 							d->turnside = (off < 0 ? -1 : 1)*(move ? move : 1);
637 							d->turnyaw = off;
638 							d->turnroll = 0;
639 							playsound(S_IMPULSE, d->o, d);
640 							game::impulseeffect(d, true);
641 							client::addmsg(SV_PHYS, "ri2", d->clientnum, SPHY_IMPULSE);
642 						}
643 						else if(d->turnside || (d->action[AC_SPECIAL] && canimpulse(d, -1)))
644 						{
645 							if(off < 0) yaw += 90;
646 							else yaw -= 90;
647 							while(yaw >= 360) yaw -= 360;
648 							while(yaw < 0) yaw += 360;
649 							vec rft; vecfromyawpitch(yaw, 0, 1, 0, rft);
650 							if(!d->turnside)
651 							{
652 								float mag = max(d->vel.magnitude(), 3.f);
653 								d->vel = vec(rft).mul(mag);
654 								off = yaw-d->aimyaw;
655 								if(off > 180) off -= 360;
656 								else if(off < -180) off += 360;
657 								d->doimpulse(FWV(impulsecost), IM_T_SKATE, lastmillis);
658 								d->action[AC_SPECIAL] = false;
659 								d->turnmillis = PHYSMILLIS;
660 								d->turnside = (off < 0 ? -1 : 1)*(move ? move : 1);
661 								d->turnyaw = off;
662 								d->turnroll = (impulseroll*d->turnside)-d->roll;
663 							}
664 							else if(d->vel.magnitude() >= 3) m = rft; // re-project and override
665 							else { d->turnside = 0; d->resetphys(); break; }
666 						}
667 						break;
668 					}
669 				}
670 				else if(d->turnside) { d->turnside = 0; d->resetphys(); }
671 			}
672 		}
673 		d->action[AC_JUMP] = d->action[AC_DASH] = false;
674 		if((d->physstate == PHYS_FALL && !d->onladder) || d->turnside) d->timeinair += millis;
675 		else d->dojumpreset();
676 	}
677 
modifyvelocity(physent * pl,bool local,bool floating,int millis)678 	void modifyvelocity(physent *pl, bool local, bool floating, int millis)
679 	{
680 		vec m(0, 0, 0);
681         bool wantsmove = game::allowmove(pl) && (pl->move || pl->strafe);
682 		if(wantsmove)
683 		{
684 			vecfromyawpitch(pl->aimyaw, floating || (pl->inliquid && (liquidcheck(pl) || pl->aimpitch < 0.f)) || movepitch(pl) ? pl->aimpitch : 0, pl->move, pl->strafe, m);
685             if((pl->type == ENT_PLAYER || pl->type == ENT_AI) && !floating && pl->physstate >= PHYS_SLOPE)
686 			{ // move up or down slopes in air but only move up slopes in liquid
687 				float dz = -(m.x*pl->floor.x + m.y*pl->floor.y)/pl->floor.z;
688                 m.z = liquidcheck(pl) ? max(m.z, dz) : dz;
689 			}
690 			m.normalize();
691 		}
692 		if(local && (pl->type == ENT_PLAYER || pl->type == ENT_AI)) modifyinput((gameent *)pl, m, wantsmove, floating, millis);
693 		else if(pl->physstate == PHYS_FALL && !pl->onladder) pl->timeinair += millis;
694 		else pl->timeinair = 0;
695 		vec d = vec(m).mul(movevelocity(pl));
696         if((pl->type == ENT_PLAYER || pl->type == ENT_AI) && !floating && !pl->inliquid)
697 			d.mul(pl->move || pl->strafe || pl->physstate == PHYS_FALL || pl->physstate == PHYS_STEP_DOWN ? (pl->strafe || pl->move <= 0 ? 1.25f : 1.5f) : 1.0f);
698 		if(floating || pl->type==ENT_CAMERA) pl->vel.lerp(d, pl->vel, pow(max(1.0f - 1.0f/floatcurb, 0.0f), millis/20.0f));
699 		else
700 		{
701 			bool floor = pl->physstate >= PHYS_SLOPE;
702 			if(floor && (pl->type == ENT_PLAYER || pl->type == ENT_AI) && ((FWV(impulsemeter) && ((gameent *)pl)->action[AC_IMPULSE] && ((gameent *)pl)->impulse[IM_METER] < FWV(impulsemeter))))
703 				floor = false;
704 			float curb = floor ? FWV(floorcurb) : FWV(aircurb), fric = pl->inliquid ? liquidmerge(pl, curb, FWV(liquidcurb)) : curb;
705 			pl->vel.lerp(d, pl->vel, pow(max(1.0f - 1.0f/fric, 0.0f), millis/20.0f));
706 		}
707 	}
708 
modifygravity(physent * pl,int curtime)709     void modifygravity(physent *pl, int curtime)
710     {
711         float secs = curtime/1000.0f;
712         vec g(0, 0, 0);
713         if(FWV(gravity) > 0)
714         {
715 			if(pl->physstate == PHYS_FALL) g.z -= gravityforce(pl)*secs;
716 			else if(pl->floor.z > 0 && pl->floor.z < floorz)
717 			{
718 				g.z = -1;
719 				g.project(pl->floor);
720 				g.normalize();
721 				g.mul(gravityforce(pl)*secs);
722 			}
723 			if(!liquidcheck(pl) || (!pl->move && !pl->strafe)) pl->falling.add(g);
724         }
725         else pl->falling = g;
726         if(liquidcheck(pl) || pl->physstate >= PHYS_SLOPE)
727         {
728             float fric = liquidcheck(pl) ? liquidmerge(pl, FWV(aircurb), FWV(liquidcurb)) : FWV(floorcurb),
729                   c = liquidcheck(pl) ? 1.0f : clamp((pl->floor.z - slopez)/(floorz-slopez), 0.0f, 1.0f);
730             pl->falling.mul(pow(max(1.0f - c/fric, 0.0f), curtime/20.0f));
731         }
732     }
733 
updatematerial(physent * pl,const vec & center,float radius,const vec & bottom,bool local,bool floating)734     void updatematerial(physent *pl, const vec &center, float radius, const vec &bottom, bool local, bool floating)
735     {
736 		int matid = lookupmaterial(bottom), curmat = matid&MATF_VOLUME, flagmat = matid&MATF_FLAGS,
737 			oldmat = pl->inmaterial&MATF_VOLUME;
738 
739 		if(!floating && curmat != oldmat)
740 		{
741 			#define mattrig(mo,mcol,ms,mt,mz,mq,mp,mw) \
742 			{ \
743 				int col = (int(mcol[2]*mq) + (int(mcol[1]*mq) << 8) + (int(mcol[0]*mq) << 16)); \
744 				regularshape(mp, mt, col, 21, 20, mz, mo, ms, 1, 10, 0, 20); \
745 				if(mw >= 0) playsound(mw, mo, pl); \
746 			}
747 			if(curmat == MAT_WATER || oldmat == MAT_WATER)
748 				mattrig(bottom, watercol, 0.5f, int(radius), PHYSMILLIS, 0.25f, PART_SPARK, curmat != MAT_WATER ? S_SPLASH2 : S_SPLASH1);
749 			if(curmat == MAT_LAVA) mattrig(vec(bottom).add(vec(0, 0, radius)), lavacol, 2.f, int(radius), PHYSMILLIS*2, 1.f, PART_FIREBALL, S_BURNING);
750 		}
751 		if(local && (pl->type == ENT_PLAYER || pl->type == ENT_AI) && pl->state == CS_ALIVE && flagmat == MAT_DEATH)
752 			game::suicide((gameent *)pl, (curmat == MAT_LAVA ? HIT_MELT : (curmat == MAT_WATER ? HIT_WATER : HIT_DEATH))|HIT_FULL);
753 		pl->inmaterial = matid;
754 		if((pl->inliquid = !floating && isliquid(curmat)) != false)
755 		{
756 			float frac = float(center.z-bottom.z)/10.f, sub = pl->submerged;
757 			vec tmp = bottom;
758 			int found = 0;
759 			loopi(10)
760 			{
761 				tmp.z += frac;
762 				if(!isliquid(lookupmaterial(tmp)&MATF_VOLUME))
763 				{
764 					found = i+1;
765 					break;
766 				}
767 			}
768 			pl->submerged = found ? found/10.f : 1.f;
769 			if(local)
770 			{
771 				if(curmat == MAT_WATER && (pl->type == ENT_PLAYER || pl->type == ENT_AI) && pl->submerged >= 0.5f && ((gameent *)pl)->lastfire)
772 				{
773 					gameent *d = (gameent *)pl;
774 					if(issound(d->fschan)) removesound(d->fschan);
775 					d->fschan = -1; d->lastfire = 0;
776 					playsound(S_EXTINGUISH, d->o, d, 0, d != game::player1 ? 128 : 224, -1, -1);
777 					client::addmsg(SV_PHYS, "ri2", d->clientnum, SPHY_EXTINGUISH);
778 				}
779 				if(pl->physstate < PHYS_SLIDE && sub >= 0.5f && pl->submerged < 0.5f && pl->vel.z > 1e-16f)
780 					pl->vel.z = max(pl->vel.z, max(jumpforce(pl, false), max(gravityforce(pl), 50.f)));
781 			}
782 		}
783 		else pl->submerged = 0;
784 		pl->onladder = !floating && flagmat == MAT_LADDER;
785         if(pl->onladder && pl->physstate < PHYS_SLIDE) pl->floor = vec(0, 0, 1);
786     }
787 
updatematerial(physent * pl,bool local,bool floating)788     void updatematerial(physent *pl, bool local, bool floating)
789     {
790         updatematerial(pl, pl->o, pl->height/2.f, (pl->type == ENT_PLAYER || pl->type == ENT_AI) ? pl->feetpos(1) : pl->o, local, floating);
791     }
792 
updateragdoll(dynent * d,const vec & center,float radius)793     void updateragdoll(dynent *d, const vec &center, float radius)
794     {
795         vec bottom(center);
796         bottom.z -= radius/2.f;
797         updatematerial(d, center, radius, bottom, false, false);
798     }
799 
800 	// main physics routine, moves a player/monster for a time step
801 	// moveres indicated the physics precision (which is lower for monsters and multiplayer prediction)
802 	// local is false for multiplayer prediction
803 
moveplayer(physent * pl,int moveres,bool local,int millis)804 	bool moveplayer(physent *pl, int moveres, bool local, int millis)
805 	{
806 		bool floating = pl->type == ENT_CAMERA || (pl->type == ENT_PLAYER && pl->state == CS_EDITING);
807 		float secs = millis/1000.f;
808 
809 		pl->blocked = false;
810 		if(pl->type==ENT_PLAYER || pl->type==ENT_AI)
811         {
812             updatematerial(pl, local, floating);
813             if(!floating && !sticktospecial(pl)) modifygravity(pl, millis); // apply gravity
814         }
815 		modifyvelocity(pl, local, floating, millis); // apply any player generated changes in velocity
816 
817 		vec d(pl->vel);
818         if((pl->type==ENT_PLAYER || pl->type==ENT_AI) && !floating && pl->inliquid) d.mul(liquidmerge(pl, 1.f, FWV(liquidspeed)));
819         d.add(pl->falling);
820 		d.mul(secs);
821 
822 		if(floating)				// just apply velocity
823 		{
824 			if(pl->physstate != PHYS_FLOAT)
825 			{
826 				pl->physstate = PHYS_FLOAT;
827 				pl->timeinair = 0;
828                 pl->falling = vec(0, 0, 0);
829 			}
830 			pl->o.add(d);
831 		}
832 		else						// apply velocity with collision
833 		{
834 			const float f = 1.0f/moveres;
835 			int collisions = 0, timeinair = pl->timeinair;
836 			vec vel(pl->vel);
837 
838 			d.mul(f);
839 			loopi(moveres) if(!move(pl, d)) { if(++collisions<5) i--; } // discrete steps collision detection & sliding
840 			if(pl->type == ENT_PLAYER && !pl->timeinair && timeinair > PHYSMILLIS*4) // if we land after long time must have been a high jump, make thud sound
841 				playsound(S_LAND, pl->o, pl);
842 		}
843 
844         if(pl->type == ENT_PLAYER || pl->type == ENT_AI)
845         {
846 			if(pl->state == CS_ALIVE) updatedynentcache(pl);
847 			if(local)
848 			{
849 				gameent *d = (gameent *)pl;
850 				if(d->state == CS_ALIVE)
851 				{
852 					if(d->o.z < 0)
853 					{
854 						game::suicide(d, HIT_DEATH|HIT_FULL);
855 						return false;
856 					}
857 					if(d->turnmillis > 0)
858 					{
859 						float amt = float(millis)/float(PHYSMILLIS), yaw = d->turnyaw*amt, roll = d->turnroll*amt;
860 						if(yaw != 0) { d->aimyaw += yaw; d->yaw += yaw; }
861 						if(roll != 0) d->roll += roll;
862 						d->turnmillis -= millis;
863 					}
864 					else
865 					{
866 						d->turnmillis = 0;
867 						if(d->roll != 0 && !d->turnside) adjustscaled(float, d->roll, PHYSMILLIS);
868 					}
869 				}
870 				else
871 				{
872 					d->turnmillis = d->turnside = 0;
873 					d->roll = 0;
874 				}
875 			}
876         }
877 
878 		return true;
879 	}
880 
movecamera(physent * pl,const vec & dir,float dist,float stepdist)881     bool movecamera(physent *pl, const vec &dir, float dist, float stepdist)
882     {
883         int steps = (int)ceil(dist/stepdist);
884         if(steps <= 0) return true;
885 
886         vec d(dir);
887         d.mul(dist/steps);
888         loopi(steps)
889         {
890             vec oldpos(pl->o);
891             pl->o.add(d);
892             if(!collide(pl, vec(0, 0, 0), 0, false))
893             {
894                 pl->o = oldpos;
895                 return false;
896             }
897         }
898         return true;
899     }
900 
interppos(physent * d)901     void interppos(physent *d)
902     {
903         d->o = d->newpos;
904         d->o.z += d->height;
905 
906         int diff = lastphysframe - lastmillis;
907         if(diff <= 0 || !physinterp) return;
908 
909         vec deltapos(d->deltapos);
910         deltapos.mul(min(diff, physframetime)/float(physframetime));
911         d->o.add(deltapos);
912     }
913 
move(physent * d,int moveres,bool local)914 	void move(physent *d, int moveres, bool local)
915 	{
916         if(physsteps <= 0)
917         {
918             if(local) interppos(d);
919             return;
920         }
921 
922         if(local)
923         {
924             d->o = d->newpos;
925             d->o.z += d->height;
926         }
927         loopi(physsteps-1) moveplayer(d, moveres, local, physframetime);
928         if(local) d->deltapos = d->o;
929         moveplayer(d, moveres, local, physframetime);
930         if(local)
931         {
932             d->newpos = d->o;
933             d->deltapos.sub(d->newpos);
934             d->newpos.z -= d->height;
935             interppos(d);
936         }
937 	}
938 
avoidcollision(physent * d,const vec & dir,physent * obstacle,float space)939 	void avoidcollision(physent *d, const vec &dir, physent *obstacle, float space)
940 	{
941 		float rad = obstacle->radius+d->radius;
942 		vec bbmin(obstacle->o);
943 		bbmin.x -= rad;
944 		bbmin.y -= rad;
945 		bbmin.z -= obstacle->height+d->aboveeye;
946 		bbmin.sub(space);
947 		vec bbmax(obstacle->o);
948 		bbmax.x += rad;
949 		bbmax.y += rad;
950 		bbmax.z += obstacle->aboveeye+d->height;
951 		bbmax.add(space);
952 
953 		loopi(3) if(d->o[i] <= bbmin[i] || d->o[i] >= bbmax[i]) return;
954 
955 		float mindist = 1e16f;
956 		loopi(3) if(dir[i] != 0)
957 		{
958 			float dist = ((dir[i] > 0 ? bbmax[i] : bbmin[i]) - d->o[i]) / dir[i];
959 			mindist = min(mindist, dist);
960 		}
961 		if(mindist >= 0.0f && mindist < 1e15f) d->o.add(vec(dir).mul(mindist));
962 	}
963 
updatephysstate(physent * d)964 	void updatephysstate(physent *d)
965 	{
966 		if(d->physstate == PHYS_FALL && !d->onladder) return;
967 		d->timeinair = 0;
968 		vec old(d->o);
969 		/* Attempt to reconstruct the floor state.
970 		 * May be inaccurate since movement collisions are not considered.
971 		 * If good floor is not found, just keep the old floor and hope it's correct enough.
972 		 */
973         bool foundfloor = false;
974 		switch(d->physstate)
975 		{
976 			case PHYS_SLOPE:
977 			case PHYS_FLOOR:
978 			case PHYS_STEP_DOWN:
979 				d->o.z -= 0.15f;
980 				if(!collide(d, vec(0, 0, -1), d->physstate == PHYS_SLOPE || d->physstate == PHYS_STEP_DOWN ? slopez : floorz))
981                 {
982 					d->floor = wall;
983                     foundfloor = true;
984                 }
985 				break;
986 
987 			case PHYS_STEP_UP:
988 				d->o.z -= stairheight+0.15f;
989 				if(!collide(d, vec(0, 0, -1), slopez))
990                 {
991 					d->floor = wall;
992                     foundfloor = true;
993                 }
994 				break;
995 
996 			case PHYS_SLIDE:
997 				d->o.z -= 0.15f;
998 				if(!collide(d, vec(0, 0, -1)) && wall.z < slopez)
999                 {
1000 					d->floor = wall;
1001                     foundfloor = true;
1002                 }
1003 				break;
1004 			default: break;
1005 		}
1006 		if((d->physstate > PHYS_FALL && d->floor.z <= 0) || (d->onladder && !foundfloor)) d->floor = vec(0, 0, 1);
1007 		d->o = old;
1008 	}
1009 
xcollide(physent * d,const vec & dir,physent * o)1010 	bool xcollide(physent *d, const vec &dir, physent *o)
1011 	{
1012 		hitflags = HITFLAG_NONE;
1013 		if(d->type == ENT_PROJ && (o->type == ENT_PLAYER || (o->type == ENT_AI && (!isaitype(((gameent *)o)->aitype) || aistyle[((gameent *)o)->aitype].maxspeed))))
1014 		{
1015 			gameent *e = (gameent *)o;
1016 			if(!d->o.reject(e->legs, d->radius+max(e->lrad.x, e->lrad.y)) && !ellipsecollide(d, dir, e->legs, vec(0, 0, 0), e->yaw, e->lrad.x, e->lrad.y, e->lrad.z, e->lrad.z))
1017 				hitflags |= HITFLAG_LEGS;
1018 			if(!d->o.reject(e->torso, d->radius+max(e->trad.x, e->trad.y)) && !ellipsecollide(d, dir, e->torso, vec(0, 0, 0), e->yaw, e->trad.x, e->trad.y, e->trad.z, e->trad.z))
1019 				hitflags |= HITFLAG_TORSO;
1020 			if(!d->o.reject(e->head, d->radius+max(e->hrad.x, e->hrad.y)) && !ellipsecollide(d, dir, e->head, vec(0, 0, 0), e->yaw, e->hrad.x, e->hrad.y, e->hrad.z, e->hrad.z))
1021 				hitflags |= HITFLAG_HEAD;
1022 			return hitflags == HITFLAG_NONE;
1023 		}
1024         if(!plcollide(d, dir, o))
1025         {
1026 			hitflags |= HITFLAG_TORSO;
1027 			return false;
1028 		}
1029 		return true;
1030 	}
1031 
xtracecollide(physent * d,const vec & from,const vec & to,float x1,float x2,float y1,float y2,float maxdist,float & dist,physent * o)1032 	bool xtracecollide(physent *d, const vec &from, const vec &to, float x1, float x2, float y1, float y2, float maxdist, float &dist, physent *o)
1033 	{
1034 		hitflags = HITFLAG_NONE;
1035 		if(d && d->type == ENT_PROJ && (o->type == ENT_PLAYER || (o->type == ENT_AI && (!isaitype(((gameent *)o)->aitype) || aistyle[((gameent *)o)->aitype].maxspeed))))
1036 		{
1037 			gameent *e = (gameent *)o;
1038 			float bestdist = 1e16f;
1039 			if(e->legs.x+e->lrad.x >= x1 && e->legs.y+e->lrad.y >= y1 && e->legs.x-e->lrad.x <= x2 && e->legs.y-e->lrad.y <= y2)
1040 			{
1041 				vec bottom(e->legs), top(e->legs); bottom.z -= e->lrad.z; top.z += e->lrad.z; float d = 1e16f;
1042 				if(linecylinderintersect(from, to, bottom, top, max(e->lrad.x, e->lrad.y), d)) { hitflags |= HITFLAG_LEGS; bestdist = min(bestdist, d); }
1043 			}
1044 			if(e->torso.x+e->trad.x >= x1 && e->torso.y+e->trad.y >= y1 && e->torso.x-e->trad.x <= x2 && e->torso.y-e->trad.y <= y2)
1045 			{
1046 				vec bottom(e->torso), top(e->torso); bottom.z -= e->trad.z; top.z += e->trad.z; float d = 1e16f;
1047 				if(linecylinderintersect(from, to, bottom, top, max(e->trad.x, e->trad.y), d)) { hitflags |= HITFLAG_TORSO; bestdist = min(bestdist, d); }
1048 			}
1049 			if(e->head.x+e->hrad.x >= x1 && e->head.y+e->hrad.y >= y1 && e->head.x-e->hrad.x <= x2 && e->head.y-e->hrad.y <= y2)
1050 			{
1051 				vec bottom(e->head), top(e->head); bottom.z -= e->hrad.z; top.z += e->hrad.z; float d = 1e16f;
1052 				if(linecylinderintersect(from, to, bottom, top, max(e->hrad.x, e->hrad.y), d)) { hitflags |= HITFLAG_HEAD; bestdist = min(bestdist, d); }
1053 			}
1054 			if(hitflags == HITFLAG_NONE) return true;
1055 			dist = bestdist*from.dist(to);
1056 			return false;
1057 		}
1058 		if(o->o.x+o->radius >= x1 && o->o.y+o->radius >= y1 && o->o.x-o->radius <= x2 && o->o.y-o->radius <= y2 && intersect(o, from, to, dist))
1059 		{
1060 			hitflags |= HITFLAG_TORSO;
1061 			return false;
1062 		}
1063 		return true;
1064 	}
1065 
complexboundbox(physent * d)1066 	void complexboundbox(physent *d)
1067 	{
1068 		render3dbox(d->o, d->height, d->aboveeye, d->radius);
1069 		renderellipse(d->o, d->xradius, d->yradius, d->yaw);
1070 		if(d->type == ENT_PLAYER || (d->type == ENT_AI && (!isaitype(((gameent *)d)->aitype) || aistyle[((gameent *)d)->aitype].maxspeed)))
1071 		{
1072 			gameent *e = (gameent *)d;
1073 			render3dbox(e->head, e->hrad.z, e->hrad.z, max(e->hrad.x, e->hrad.y));
1074 			renderellipse(e->head, e->hrad.x, e->hrad.y, e->yaw);
1075 			render3dbox(e->torso, e->trad.z, e->trad.z, max(e->trad.x, e->trad.y));
1076 			renderellipse(e->torso, e->trad.x, e->trad.y, e->yaw);
1077 			render3dbox(e->legs, e->lrad.z, e->lrad.z, max(e->lrad.x, e->lrad.y));
1078 			renderellipse(e->legs, e->lrad.x, e->lrad.y, e->yaw);
1079 			render3dbox(e->waist, 0.25f, 0.25f, 0.25f);
1080 			render3dbox(e->lfoot, 1, 1, 1); render3dbox(e->rfoot, 1, 1, 1);
1081 		}
1082 	}
1083 
entinmap(physent * d,bool avoidplayers)1084 	bool entinmap(physent *d, bool avoidplayers)
1085 	{
1086 		if(d->state != CS_ALIVE) { d->resetinterp(); return insideworld(d->o); }
1087 		vec orig = d->o;
1088 		#define inmapchk(x,y) \
1089 		{ \
1090 			loopi(x) \
1091 			{ \
1092 				if(i) { y; } \
1093 				if(collide(d) && !inside) \
1094 				{ \
1095 					if(avoidplayers && hitplayer && issolid(hitplayer, d)) continue; \
1096                     d->resetinterp(); \
1097 					return true; \
1098 				} \
1099 				d->o = orig; \
1100 			} \
1101 		}
1102 		if(d->type == ENT_PLAYER || (d->type == ENT_AI && (!isaitype(((gameent *)d)->aitype) || aistyle[((gameent *)d)->aitype].maxspeed)))
1103 		{
1104 			vec dir; vecfromyawpitch(d->yaw, d->pitch, 1, 0, dir);
1105 			inmapchk(100, d->o.add(vec(dir).mul(i/10.f)));
1106 		}
1107 		inmapchk(100, d->o.add(vec((rnd(21)-10)*i/10.f, (rnd(21)-10)*i/10.f, (rnd(21)-10)*i/10.f)));
1108 		d->o = orig;
1109         d->resetinterp();
1110 		return false;
1111 	}
1112 
1113     VARP(smoothmove, 0, 90, 1000);
1114     VARP(smoothdist, 0, 64, 1024);
1115 
predictplayer(gameent * d,bool domove,int res=0,bool local=false)1116     void predictplayer(gameent *d, bool domove, int res = 0, bool local = false)
1117     {
1118         d->o = d->newpos;
1119         d->o.z += d->height;
1120 
1121         d->yaw = d->newyaw;
1122         d->pitch = d->newpitch;
1123 
1124         d->aimyaw = d->newaimyaw;
1125         d->aimpitch = d->newaimpitch;
1126 
1127         if(domove)
1128         {
1129             move(d, res, local);
1130             d->newpos = d->o;
1131             d->newpos.z -= d->height;
1132         }
1133 
1134         float k = 1.0f - float(lastmillis - d->smoothmillis)/float(smoothmove);
1135         if(k>0)
1136         {
1137             d->o.add(vec(d->deltapos).mul(k));
1138 
1139             d->yaw += d->deltayaw*k;
1140             if(d->yaw<0) d->yaw += 360;
1141             else if(d->yaw>=360) d->yaw -= 360;
1142             d->pitch += d->deltapitch*k;
1143 
1144             d->aimyaw += d->deltaaimyaw*k;
1145             if(d->aimyaw<0) d->aimyaw += 360;
1146             else if(d->aimyaw>=360) d->aimyaw -= 360;
1147             d->aimpitch += d->deltaaimpitch*k;
1148         }
1149     }
1150 
smoothplayer(gameent * d,int res,bool local)1151 	void smoothplayer(gameent *d, int res, bool local)
1152 	{
1153 		if(d->state == CS_ALIVE || d->state == CS_EDITING)
1154 		{
1155 			if(smoothmove && d->smoothmillis>0) predictplayer(d, true, res, local);
1156 			else move(d, res, local);
1157 		}
1158 		else if(d->state==CS_DEAD || d->state == CS_WAITING)
1159         {
1160             if(d->ragdoll) moveragdoll(d, true);
1161             else if(lastmillis-d->lastpain<2000) move(d, res, local);
1162         }
1163 	}
1164 
droptofloor(vec & o,float radius,float height)1165 	bool droptofloor(vec &o, float radius, float height)
1166 	{
1167 		if(!insideworld(o)) return false;
1168 		vec v(0.0001f, 0.0001f, -1);
1169 		v.normalize();
1170 		if(raycube(o, v, hdr.worldsize) >= hdr.worldsize) return false;
1171 		physent d;
1172 		d.type = ENT_DUMMY;
1173         d.collidetype = COLLIDE_AABB;
1174 		d.o = o;
1175 		d.radius = radius;
1176 		d.height = height;
1177 		d.aboveeye = radius;
1178         if(!movecamera(&d, vec(0, 0, -1), hdr.worldsize, 1))
1179         {
1180             o = d.o;
1181             return true;
1182         }
1183         return false;
1184 	}
1185 
update()1186 	void update()
1187 	{
1188         int diff = lastmillis - lastphysframe;
1189         if(diff <= 0) physsteps = 0;
1190         else
1191         {
1192             physsteps = (diff + physframetime - 1)/physframetime;
1193             lastphysframe += physsteps * physframetime;
1194         }
1195 		cleardynentcache();
1196 	}
1197 }
1198 #undef GAMEPHYSICS
1199