1 #include "game.h"
2 namespace physics
3 {
4     FVAR(IDF_WORLD, stairheight, 0, 4.1f, 1000);
5     FVAR(IDF_WORLD, floorz, 0, 0.867f, 1);
6     FVAR(IDF_WORLD, slopez, 0, 0.5f, 1);
7     FVAR(IDF_WORLD, wallz, 0, 0.2f, 1);
8     FVAR(IDF_WORLD, stepspeed, 1e-4f, 1.f, 1000);
9 
10     FVAR(IDF_PERSIST, floatspeed, FVAR_NONZERO, 200, FVAR_MAX);
11     FVAR(IDF_PERSIST, floatcoast, 0, 3.f, FVAR_MAX);
12 
13     VAR(IDF_PERSIST, physframetime, 5, 5, 20);
14     VAR(IDF_PERSIST, physinterp, 0, 1, 1);
15 
16     FVAR(IDF_PERSIST, impulsekick, 0, 150, 180); // determines the minimum yaw angle to switch between wall kick and run
17     VAR(IDF_PERSIST, impulsemethod, 0, 3, 3); // determines which impulse method to use, 0 = none, 1 = power jump, 2 = power slide, 3 = both
18     VAR(IDF_PERSIST, impulseaction, 0, 3, 3); // determines how impulse action works, 0 = off, 1 = impulse jump, 2 = impulse dash, 3 = both
19     FVAR(IDF_PERSIST, impulseroll, 0, 15, 89);
20 
21     VAR(IDF_PERSIST, jumpstyle, 0, 1, 1); // 0 = unpressed on action, 1 = remains pressed
22     VAR(IDF_PERSIST, dashstyle, 0, 1, 1); // 0 = only with impulse, 1 = double tap
23     VAR(IDF_PERSIST, crouchstyle, 0, 0, 2); // 0 = press and hold, 1 = double-tap toggle, 2 = toggle
24     VAR(IDF_PERSIST, walkstyle, 0, 0, 2); // 0 = press and hold, 1 = double-tap toggle, 2 = toggle
25     VAR(IDF_PERSIST, grabstyle, 0, 2, 2); // 0 = up=up down=down, 1 = up=down down=up, 2 = up=up, down=up
26     VAR(IDF_PERSIST, grabplayerstyle, 0, 3, 3); // 0 = up=up down=down, 1 = up=down down=up, 2 = up=up, down=up, 3 = directly toward player
27     VAR(IDF_PERSIST, kickoffstyle, 0, 1, 1); // 0 = old method 1 = new method
28     FVAR(IDF_PERSIST, kickoffangle, 0, 60, 89);
29     VAR(IDF_PERSIST, kickupstyle, 0, 1, 1); // 0 = old method 1 = new method
30     FVAR(IDF_PERSIST, kickupangle, 0, 89, 89);
31 
32     int physsteps = 0, lastphysframe = 0, lastmove = 0, lastdirmove = 0, laststrafe = 0, lastdirstrafe = 0, lastcrouch = 0, lastwalk = 0;
33 
allowimpulse(physent * d,int type)34     bool allowimpulse(physent *d, int type)
35     {
36         if(d && gameent::is(d))
37             return (!type || AA(((gameent *)d)->actortype, abilities)&(1<<type)) && (impulsestyle || PHYS(gravity) == 0);
38         return false;
39     }
40 
canimpulse(physent * d,int type,bool kick)41     bool canimpulse(physent *d, int type, bool kick)
42     {
43         if(gameent::is(d) && allowimpulse(d, type))
44         {
45             gameent *e = (gameent *)d;
46             if(!kick && impulsestyle == 1 && e->impulse[IM_TYPE] > IM_T_NONE && e->impulse[IM_TYPE] < IM_T_WALL) return false;
47             int time = e->impulse[IM_TIME], delay = e->impulse[IM_TYPE] == IM_T_KICK || e->impulse[IM_TYPE] == IM_T_VAULT || e->impulse[IM_TYPE] == IM_T_GRAB ? impulsekickdelay : impulseboostdelay;
48             if(!time)
49             {
50                 time = e->impulse[IM_JUMP];
51                 delay = impulsejumpdelay;
52             }
53             if(time && delay && lastmillis-time <= delay) return false;
54             if(!m_freestyle(game::gamemode, game::mutators) && impulsestyle <= 2 && e->impulse[IM_COUNT] >= impulsecount) return false;
55             return true;
56         }
57         return false;
58     }
59 
60     #define imov(name,v,u,d,s,os) \
61         void do##name(bool down) \
62         { \
63             game::player1->s = down; \
64             int dir = game::player1->s ? d : (game::player1->os ? -(d) : 0); \
65             game::player1->v = dir; \
66             if(down) \
67             { \
68                 if(allowimpulse(game::player1, A_A_DASH) && dashstyle && impulseaction&2 && last##v && lastdir##v && dir == lastdir##v && lastmillis-last##v < PHYSMILLIS) \
69                 { \
70                     game::player1->action[AC_DASH] = true; \
71                     game::player1->actiontime[AC_DASH] = lastmillis; \
72                 } \
73                 last##v = lastmillis; lastdir##v = dir; \
74                 last##u = lastdir##u = 0; \
75             } \
76         } \
77         ICOMMAND(0, name, "D", (int *down), { do##name(*down!=0); });
78 
79     imov(backward, move,   strafe,  -1, k_down,  k_up);
80     imov(forward,  move,   strafe,   1, k_up,    k_down);
81     imov(left,     strafe, move,     1, k_left,  k_right);
82     imov(right,    strafe, move,    -1, k_right, k_left);
83 
84     // inputs
doaction(int type,bool down)85     void doaction(int type, bool down)
86     {
87         if(type < AC_TOTAL && type > -1)
88         {
89             if(game::allowmove(game::player1))
90             {
91                 int style = 0, *last = NULL;
92                 switch(type)
93                 {
94                     case AC_CROUCH: style = crouchstyle; last = &lastcrouch; break;
95                     case AC_WALK: style = walkstyle; last = &lastwalk; break;
96                     default: break;
97                 }
98                 switch(style)
99                 {
100                     case 1:
101                     {
102                         if(!down && game::player1->action[type])
103                         {
104                             if(*last && lastmillis-*last < PHYSMILLIS) return;
105                             *last = lastmillis;
106                         }
107                         break;
108                     }
109                     case 2:
110                     {
111                         if(!down)
112                         {
113                             if(*last)
114                             {
115                                 *last = 0;
116                                 return;
117                             }
118                             *last = lastmillis;
119                         }
120                         break;
121                     }
122                     default: break;
123                 }
124                 if(down) game::player1->actiontime[type] = lastmillis;
125                 else if(type == AC_CROUCH) game::player1->actiontime[type] = -lastmillis;
126                 game::player1->action[type] = down;
127             }
128             else
129             {
130                 game::player1->action[type] = false;
131                 if((type == AC_PRIMARY || type == AC_JUMP) && down) game::respawn(game::player1);
132             }
133         }
134     }
135 
136     ICOMMAND(0, primary, "D", (int *n), doaction(AC_PRIMARY, *n!=0));
137     ICOMMAND(0, secondary, "D", (int *n), doaction(AC_SECONDARY, *n!=0));
138     ICOMMAND(0, reload, "D", (int *n), doaction(AC_RELOAD, *n!=0));
139     ICOMMAND(0, use, "D", (int *n), doaction(AC_USE, *n!=0));
140     ICOMMAND(0, jump, "D", (int *n), doaction(AC_JUMP, *n!=0));
141     ICOMMAND(0, walk, "D", (int *n), doaction(AC_WALK, *n!=0));
142     ICOMMAND(0, crouch, "D", (int *n), doaction(AC_CROUCH, *n!=0));
143     ICOMMAND(0, special, "D", (int *n), doaction(AC_SPECIAL, *n!=0));
144     ICOMMAND(0, drop, "D", (int *n), doaction(AC_DROP, *n!=0));
145     ICOMMAND(0, affinity, "D", (int *n), doaction(AC_AFFINITY, *n!=0));
146 
carryaffinity(gameent * d)147     bool carryaffinity(gameent *d)
148     {
149         if(m_capture(game::gamemode)) return capture::carryaffinity(d);
150         if(m_bomber(game::gamemode)) return bomber::carryaffinity(d);
151         return false;
152     }
153 
dropaffinity(gameent * d)154     bool dropaffinity(gameent *d)
155     {
156         if(m_capture(game::gamemode)) return capture::dropaffinity(d);
157         if(m_bomber(game::gamemode)) return bomber::dropaffinity(d);
158         return false;
159     }
160 
secondaryweap(gameent * d)161     bool secondaryweap(gameent *d)
162     {
163         if(!isweap(d->weapselect)) return false;
164         if(d->action[AC_SECONDARY] && (!d->action[AC_PRIMARY] || d->actiontime[AC_SECONDARY] > d->actiontime[AC_PRIMARY])) return true;
165         else if(d->actiontime[AC_SECONDARY] > d->actiontime[AC_PRIMARY] && d->weapstate[d->weapselect] == W_S_POWER) return true;
166         return false;
167     }
168 
isghost(gameent * d,gameent * e,bool proj)169     bool isghost(gameent *d, gameent *e, bool proj)
170     { // d is target, e is from
171         if(!e || (d == e && !proj)) return false;
172         if(d->actortype < A_ENEMY && e->actortype < A_ENEMY && m_ghost(game::gamemode, game::mutators)) return true;
173         switch(d->actortype)
174         {
175             case A_PLAYER: if(!(AA(e->actortype, collide)&(1<<A_C_PLAYERS))) return true; break;
176             case A_BOT: if(!(AA(e->actortype, collide)&(1<<A_C_BOTS))) return true; break;
177             default: if(!(AA(e->actortype, collide)&(1<<A_C_ENEMIES))) return true; break;
178         }
179         if(m_team(game::gamemode, game::mutators) && d->team == e->team && (proj || AA(e->actortype, teamdamage)&(1<<A_T_GHOST))) switch(d->actortype)
180         {
181             case A_PLAYER: if(!(AA(e->actortype, teamdamage)&(1<<A_T_PLAYERS))) return true; break;
182             case A_BOT: if(!(AA(e->actortype, teamdamage)&(1<<A_T_BOTS))) return true; break;
183             default: if(!(AA(e->actortype, teamdamage)&(1<<A_T_ENEMIES))) return true; break;
184         }
185         return false;
186     }
187 
issolid(physent * d,physent * e,bool esc,bool impact,bool reverse)188     bool issolid(physent *d, physent *e, bool esc, bool impact, bool reverse)
189     { // d is target, e is from
190         if(!e || d == e) return false; // don't collide with themself
191         if(projent::is(e))
192         {
193             projent *p = (projent *)e;
194             if(gameent::is(d))
195             {
196                 gameent *g = (gameent *)d;
197                 if(g->state != CS_ALIVE) return false;
198                 if(g->protect(lastmillis, m_protect(game::gamemode, game::mutators))) return false;
199                 if(p->stick == d || isghost(g, p->owner, true)) return false;
200                 if(impact && (p->hit == g || !(p->projcollide&COLLIDE_PLAYER))) return false;
201                 if(p->owner == d && (!(p->projcollide&COLLIDE_OWNER) || (esc && !p->escaped))) return false;
202             }
203             else if(projent::is(d))
204             {
205                 projent *q = (projent *)d;
206                 if(p->projtype == PRJ_SHOT && q->projtype == PRJ_SHOT)
207                 {
208                     if((p->projcollide&IMPACT_SHOTS || p->projcollide&BOUNCE_SHOTS) && q->projcollide&COLLIDE_PROJ) return true;
209                 }
210                 return false;
211             }
212             else return false;
213         }
214         if(gameent::is(d))
215         {
216             gameent *g = (gameent *)d;
217             if(g->state != CS_ALIVE) return false;
218             if(gameent::is(e) && isghost(g, (gameent *)e)) return false;
219             return true;
220         }
221         else if(projent::is(d) && !reverse) return issolid(e, d, esc, impact, true);
222         return false;
223     }
224 
liquidcheck(physent * d)225     bool liquidcheck(physent *d) { return d->inliquid && !d->onladder && d->submerged >= PHYS(liquidsubmerge); }
226 
liquidmerge(physent * d,float from,float to)227     float liquidmerge(physent *d, float from, float to)
228     {
229         if(d->inliquid)
230         {
231             if(d->physstate >= PHYS_SLIDE && d->submerged < 1.f)
232                 return from-((from-to)*d->submerged);
233             else return to;
234         }
235         return from;
236     }
237 
jumpvel(physent * d,bool liquid)238     float jumpvel(physent *d, bool liquid)
239     {
240         float vel = jumpspeed*(liquid ? liquidmerge(d, 1.f, PHYS(liquidspeed)) : 1.f)*d->speedscale;
241         if(gameent::is(d))
242         {
243             gameent *e = (gameent *)d;
244             vel *= 1.f-clamp(e->stunned(lastmillis), 0.f, 1.f);
245         }
246         return vel;
247     }
248 
gravityvel(physent * d)249     float gravityvel(physent *d)
250     {
251         float vel = PHYS(gravity)*(d->weight/100.f);
252         if(gameent::is(d))
253         {
254             gameent *e = (gameent *)d;
255             vel *= 1.f-clamp(e->stunned(lastmillis, true), 0.f, 1.f);
256         }
257         return vel;
258     }
259 
stepvel(physent * d,bool up)260     float stepvel(physent *d, bool up)
261     {
262         if(d->physstate > PHYS_FALL) return stepspeed;
263         return 1.f;
264     }
265 
sticktofloor(physent * d)266     bool sticktofloor(physent *d)
267     {
268         if(!d->onladder)
269         {
270             if(liquidcheck(d)) return false;
271             if(gameent::is(d))
272             {
273                 gameent *e = (gameent *)d;
274                 if(e->sliding()) return false;
275             }
276         }
277         return true;
278     }
279 
sticktospecial(physent * d)280     bool sticktospecial(physent *d)
281     {
282         if(gameent::is(d)) { if(((gameent *)d)->turnside) return true; }
283         return false;
284     }
285 
isfloating(physent * d)286     bool isfloating(physent *d)
287     {
288         return d->type == ENT_CAMERA || (d->type == ENT_PLAYER && d->state == CS_EDITING);
289     }
290 
movevelocity(physent * d,bool floating)291     float movevelocity(physent *d, bool floating)
292     {
293         physent *pl = d->type == ENT_CAMERA ? game::player1 : d;
294         float vel = pl->speed;
295         if(floating) vel *= floatspeed/100.0f;
296         else
297         {
298             vel *= pl->speedscale;
299             if(gameent::is(pl))
300             {
301                 gameent *e = (gameent *)pl;
302                 vel *= movespeed/100.f*(1.f-clamp(e->stunned(lastmillis), 0.f, 1.f));
303                 if((d->physstate >= PHYS_SLOPE || d->onladder) && !e->sliding(true) && e->crouching()) vel *= movecrawl;
304                 else if(isweap(e->weapselect) && e->weapstate[e->weapselect] == W_S_ZOOM) vel *= movecrawl;
305                 if(e->move >= 0) vel *= e->strafe ? movestrafe : movestraight;
306                 if(e->running()) vel *= moverun;
307                 switch(e->physstate)
308                 {
309                     case PHYS_FALL: if(PHYS(gravity) > 0) vel *= moveinair; break;
310                     case PHYS_STEP_DOWN: vel *= movestepdown; break;
311                     case PHYS_STEP_UP: vel *= movestepup; break;
312                     default: break;
313                 }
314                 if(carryaffinity(e))
315                 {
316                     if(m_capture(game::gamemode)) vel *= capturecarryspeed;
317                     else if(m_bomber(game::gamemode)) vel *= bombercarryspeed;
318                 }
319             }
320         }
321         return vel;
322     }
323 
impulsevelocity(physent * d,float amt,int & cost,int type,float redir,vec & keep)324     float impulsevelocity(physent *d, float amt, int &cost, int type, float redir, vec &keep)
325     {
326         float scale = d->speedscale;
327         if(gameent::is(d))
328         {
329             gameent *e = (gameent *)d;
330             scale *= 1.f-clamp(e->stunned(lastmillis), 0.f, 1.f);
331             if(carryaffinity(e))
332             {
333                 if(m_capture(game::gamemode)) scale *= capturecarryspeed;
334                 else if(m_bomber(game::gamemode)) scale *= bombercarryspeed;
335             }
336             if(cost && m_impulsemeter(game::gamemode, game::mutators))
337             {
338                 if(impulsecostscale) cost = int(cost*scale);
339                 int diff = impulsemeter-e->impulse[IM_METER];
340                 if(cost > diff)
341                 {
342                     if(type >= A_A_IMFIRST && impulsecostrelax&(1<<(type-A_A_IMFIRST)))
343                     {
344                         scale *= float(diff)/float(cost);
345                         cost = diff;
346                     }
347                     else return 0.f;
348                 }
349             }
350             else cost = 0;
351         }
352         float speed = (impulsespeed*amt*scale)+(keep.magnitude()*redir);
353         keep.mul(1-min(redir, 1.f));
354         if(impulselimit > 0) return min(speed, impulselimit*scale);
355         return speed;
356     }
357 
movepitch(physent * d)358     bool movepitch(physent *d)
359     {
360         if(d->type == ENT_CAMERA || d->state == CS_EDITING || d->state == CS_SPECTATOR) return true;
361         if(d->onladder || (d->inliquid && (liquidcheck(d) || d->pitch < 0.f)) || PHYS(gravity) == 0) return true;
362         return false;
363     }
364 
recalcdir(physent * d,const vec & oldvel,vec & dir)365     void recalcdir(physent *d, const vec &oldvel, vec &dir)
366     {
367         float speed = oldvel.magnitude();
368         if(speed > 1e-6f)
369         {
370             float step = dir.magnitude();
371             dir = d->vel;
372             dir.add(d->falling);
373             dir.mul(step/speed);
374         }
375     }
376 
slideagainst(physent * d,vec & dir,const vec & obstacle,bool foundfloor,bool slidecollide)377     void slideagainst(physent *d, vec &dir, const vec &obstacle, bool foundfloor, bool slidecollide)
378     {
379         vec wall(obstacle);
380         if(foundfloor ? wall.z > 0 : slidecollide)
381         {
382             wall.z = 0;
383             if(!wall.iszero()) wall.normalize();
384         }
385         vec oldvel(d->vel);
386         oldvel.add(d->falling);
387         d->vel.project(wall);
388         d->falling.project(wall);
389         recalcdir(d, oldvel, dir);
390     }
391 
switchfloor(physent * d,vec & dir,const vec & floor)392     void switchfloor(physent *d, vec &dir, const vec &floor)
393     {
394         if(floor.z >= floorz) d->falling = vec(0, 0, 0);
395 
396         vec oldvel(d->vel);
397         oldvel.add(d->falling);
398         if(dir.dot(floor) >= 0)
399         {
400             if(d->physstate < PHYS_SLIDE || fabs(dir.dot(d->floor)) > 0.01f*dir.magnitude()) return;
401             d->vel.projectxy(floor, 0.0f);
402         }
403         else d->vel.projectxy(floor);
404         d->falling.project(floor);
405         recalcdir(d, oldvel, dir);
406     }
407 
trystepup(physent * d,vec & dir,const vec & obstacle,float maxstep,const vec & floor)408     bool trystepup(physent *d, vec &dir, const vec &obstacle, float maxstep, const vec &floor)
409     {
410         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);
411         float force = stepvel(d, true);
412         bool cansmooth = true;
413         d->o = old;
414         /* check if there is space atop the stair to move to */
415         if(d->physstate != PHYS_STEP_UP)
416         {
417             vec checkdir = stairdir;
418             checkdir.mul(0.1f);
419             checkdir.z += maxstep + 0.1f;
420             d->o.add(checkdir);
421             if(collide(d))
422             {
423                 d->o = old;
424                 if(!collide(d, vec(0, 0, -1), slopez)) return false;
425                 cansmooth = false;
426             }
427         }
428 
429         if(cansmooth)
430         {
431             vec checkdir = stairdir;
432             checkdir.z += 1;
433             checkdir.mul(maxstep);
434             d->o = old;
435             d->o.add(checkdir);
436             int scale = 2;
437             if(collide(d, checkdir))
438             {
439                 if(!collide(d, vec(0, 0, -1), slopez))
440                 {
441                     d->o = old;
442                     return false;
443                 }
444                 d->o.add(checkdir);
445                 if(collide(d, vec(0, 0, -1), slopez)) scale = 1;
446             }
447             if(scale != 1)
448             {
449                 d->o = old;
450                 d->o.sub(checkdir.mul(vec(2, 2, 1)));
451                 if(!collide(d, vec(0, 0, -1), slopez)) scale = 1;
452             }
453 
454             d->o = old;
455             vec smoothdir = vec(dir.x, dir.y, 0);
456             float magxy = smoothdir.magnitude();
457             if(magxy > 1e-9f)
458             {
459                 if(magxy > scale*dir.z)
460                 {
461                     smoothdir.mul(1/magxy);
462                     smoothdir.z = 1.0f/scale;
463                     smoothdir.mul(dir.magnitude()/smoothdir.magnitude());
464                 }
465                 else smoothdir.z = dir.z;
466                 d->o.add(smoothdir.mul(force));
467                 float margin = (maxstep + 0.1f)*ceil(force);
468                 d->o.z += margin;
469                 if(!collide(d, smoothdir))
470                 {
471                     d->o.z -= margin;
472                     if(d->physstate == PHYS_FALL || d->floor != floor)
473                     {
474                         d->airmillis = 0;
475                         d->floor = floor;
476                         switchfloor(d, dir, d->floor);
477                     }
478                     d->physstate = PHYS_STEP_UP;
479                     return true;
480                 }
481             }
482         }
483 
484         /* try stepping up */
485         d->o = old;
486         d->o.z += dir.magnitude()*force;
487         if(!collide(d, vec(0, 0, 1)))
488         {
489             if(d->physstate == PHYS_FALL || d->floor != floor)
490             {
491                 d->airmillis = 0;
492                 d->floor = floor;
493                 switchfloor(d, dir, d->floor);
494             }
495             if(cansmooth) d->physstate = PHYS_STEP_UP;
496             return true;
497         }
498         d->o = old;
499         return false;
500     }
501 
trystepdown(physent * d,vec & dir,float step,float xy,float z,bool init=false)502     bool trystepdown(physent *d, vec &dir, float step, float xy, float z, bool init = false)
503     {
504         vec stepdir(dir.x, dir.y, 0);
505         stepdir.z = -stepdir.magnitude2()*z/xy;
506         if(!stepdir.z) return false;
507         stepdir.normalize();
508 
509         vec old(d->o);
510         d->o.add(vec(stepdir).mul(stairheight/fabs(stepdir.z))).z -= stairheight;
511         d->zmargin = -stairheight;
512         if(collide(d, vec(0, 0, -1), slopez))
513         {
514             d->o = old;
515             d->o.add(vec(stepdir).mul(step));
516             d->zmargin = 0;
517             if(!collide(d, vec(0, 0, -1)))
518             {
519                 vec stepfloor(stepdir);
520                 stepfloor.mul(-stepfloor.z).z += 1;
521                 stepfloor.normalize();
522                 if(d->physstate >= PHYS_SLOPE && d->floor != stepfloor)
523                 {
524                     // prevent alternating step-down/step-up states if player would keep bumping into the same floor
525                     vec stepped(d->o);
526                     d->o.z -= 0.5f;
527                     d->zmargin = -0.5f;
528                     if(collide(d, stepdir) && collidewall == d->floor)
529                     {
530                         d->o = old;
531                         if(!init) { d->o.x += dir.x; d->o.y += dir.y; if(dir.z <= 0 || collide(d, dir)) d->o.z += dir.z; }
532                         d->zmargin = 0;
533                         d->physstate = PHYS_STEP_DOWN;
534                         return true;
535                     }
536                     d->o = init ? old : stepped;
537                     d->zmargin = 0;
538                 }
539                 else if(init) d->o = old;
540                 switchfloor(d, dir, stepfloor);
541                 d->floor = stepfloor;
542                 if(init)
543                 {
544                     d->airmillis = 0;
545                     d->physstate = PHYS_STEP_DOWN;
546                 }
547                 return true;
548             }
549         }
550         d->o = old;
551         d->zmargin = 0;
552         return false;
553     }
554 
trystepdown(physent * d,vec & dir,bool init=false)555     bool trystepdown(physent *d, vec &dir, bool init = false)
556     {
557         if(!sticktofloor(d)) return false;
558         vec old(d->o);
559         d->o.z -= stairheight;
560         d->zmargin = -stairheight;
561         if(!collide(d, vec(0, 0, -1), slopez))
562         {
563             d->o = old;
564             d->zmargin = 0;
565             return false;
566         }
567         d->o = old;
568         d->zmargin = 0;
569         float step = dir.magnitude();
570         if(trystepdown(d, dir, step, 2, 1, init)) return true;
571         if(trystepdown(d, dir, step, 1, 1, init)) return true;
572         if(trystepdown(d, dir, step, 1, 2, init)) return true;
573         return false;
574     }
575 
falling(physent * d,vec & dir,const vec & floor)576     void falling(physent *d, vec &dir, const vec &floor)
577     {
578         if(d->physstate >= PHYS_SLOPE && dir.dot(d->floor) <= 0.01f*dir.magnitude() && trystepdown(d, dir, true)) return;
579         if(floor.z > 0.0f && floor.z < slopez)
580         {
581             if(floor.z >= wallz) switchfloor(d, dir, floor);
582             d->airmillis = 0;
583             d->physstate = PHYS_SLIDE;
584             d->floor = floor;
585         }
586         else if(sticktospecial(d))
587         {
588             d->airmillis = 0;
589             d->physstate = PHYS_FLOOR;
590             d->floor = vec(0, 0, 1);
591         }
592         else d->physstate = PHYS_FALL;
593     }
594 
landing(physent * d,vec & dir,const vec & floor,bool collided)595     void landing(physent *d, vec &dir, const vec &floor, bool collided)
596     {
597         switchfloor(d, dir, floor);
598         d->airmillis = 0;
599         if((d->physstate!=PHYS_STEP_UP && d->physstate!=PHYS_STEP_DOWN) || !collided)
600             d->physstate = floor.z >= floorz ? PHYS_FLOOR : PHYS_SLOPE;
601         d->floor = floor;
602     }
603 
findfloor(physent * d,bool collided,const vec & obstacle,bool & slide,vec & floor)604     bool findfloor(physent *d, bool collided, const vec &obstacle, bool &slide, vec &floor)
605     {
606         bool found = false;
607         vec moved(d->o);
608         d->o.z -= 0.1f;
609         if(sticktospecial(d))
610         {
611             floor = vec(0, 0, 1);
612             found = true;
613         }
614         else if(d->physstate != PHYS_FALL && collide(d, vec(0, 0, -1), d->physstate == PHYS_SLOPE || d->physstate == PHYS_STEP_DOWN ? slopez : floorz))
615         {
616             floor = collidewall;
617             found = true;
618         }
619         else if(collided && obstacle.z >= slopez)
620         {
621             floor = obstacle;
622             found = true;
623             slide = false;
624         }
625         else if(d->physstate == PHYS_STEP_UP || d->physstate == PHYS_SLIDE)
626         {
627             if(collide(d, vec(0, 0, -1)) && collidewall.z > 0.0f)
628             {
629                 floor = collidewall;
630                 if(floor.z >= slopez) found = true;
631             }
632         }
633         else if(d->physstate >= PHYS_SLOPE && d->floor.z < 1.0f)
634         {
635             if(collide(d, vec(d->floor).neg(), 0.95f) || !collide(d, vec(0, 0, -1)))
636             {
637                 floor = collidewall;
638                 if(floor.z >= slopez && floor.z < 1.0f) found = true;
639             }
640         }
641         if(collided && (!found || obstacle.z > floor.z))
642         {
643             floor = obstacle;
644             slide = !found && (floor.z < wallz || floor.z >= slopez);
645         }
646         d->o = moved;
647         return found;
648     }
649 
move(physent * d,vec & dir)650     bool move(physent *d, vec &dir)
651     {
652         vec old(d->o), obstacle; d->o.add(dir);
653         bool collided = false, slidecollide = false;
654         if(collide(d, dir))
655         {
656             obstacle = collidewall;
657             /* check to see if there is an obstacle that would prevent this one from being used as a floor */
658             if((gameent::is(d)) && ((collidewall.z>=slopez && dir.z<0) || (collidewall.z<=-slopez && dir.z>0)) && (dir.x || dir.y) && collide(d, vec(dir.x, dir.y, 0)))
659             {
660                 if(collidewall.dot(dir) >= 0) slidecollide = true;
661                 obstacle = collidewall;
662             }
663 
664             d->o = old;
665             d->o.z -= stairheight;
666             d->zmargin = -stairheight;
667             if(d->physstate == PHYS_SLOPE || d->physstate == PHYS_FLOOR  || (collide(d, vec(0, 0, -1), slopez) && (d->physstate == PHYS_STEP_UP || d->physstate == PHYS_STEP_DOWN || collidewall.z >= floorz)))
668             {
669                 d->o = old;
670                 d->zmargin = 0;
671                 if(trystepup(d, dir, obstacle, stairheight, d->physstate == PHYS_SLOPE || d->physstate == PHYS_FLOOR ? d->floor : vec(collidewall)))
672                     return true;
673             }
674             else
675             {
676                 d->o = old;
677                 d->zmargin = 0;
678             }
679             collided = true; // can't step over the obstacle, so just slide against it
680         }
681         else if(d->physstate == PHYS_STEP_UP)
682         {
683             if(collide(d, vec(0, 0, -1), slopez))
684             {
685                 d->o = old;
686                 if(trystepup(d, dir, vec(0, 0, 1), stairheight, vec(collidewall))) return true;
687                 d->o.add(dir);
688             }
689         }
690         else if((gameent::is(d)) && d->physstate == PHYS_STEP_DOWN && dir.dot(d->floor) <= 1e-6f)
691         {
692             vec moved(d->o);
693             d->o = old;
694             if(trystepdown(d, dir)) return true;
695             d->o = moved;
696         }
697         vec floor(0, 0, 0);
698         bool slide = collided, found = findfloor(d, collided, obstacle, slide, floor);
699         if(slide || (!collided && floor.z > 0 && floor.z < wallz))
700         {
701             slideagainst(d, dir, slide ? obstacle : floor, found, slidecollide);
702             d->blocked = true;
703         }
704         if(found) landing(d, dir, floor, collided);
705         else falling(d, dir, floor);
706         return !collided;
707     }
708 
impulseplayer(gameent * d,bool & onfloor,bool melee=false)709     bool impulseplayer(gameent *d, bool &onfloor, bool melee = false)
710     {
711         bool power = !melee && onfloor && impulsemethod&1 && d->sliding(true) && d->action[AC_JUMP];
712         if(power || d->actortype >= A_BOT || impulseaction || melee)
713         {
714             bool dash = false, pulse = false;
715             if(melee) { dash = onfloor; pulse = !onfloor; }
716             else if(!power)
717             {
718                 if(onfloor) dash = impulseaction&2 && d->action[AC_DASH] && (!d->impulse[IM_TIME] || lastmillis-d->impulse[IM_TIME] > impulsedashdelay);
719                 else pulse = ((d->actortype >= A_BOT || impulseaction&1) && d->action[AC_JUMP]) || ((d->actortype >= A_BOT || impulseaction&2) && d->action[AC_DASH]);
720             }
721             if(!canimpulse(d, dash ? A_A_DASH : A_A_BOOST, false)) return false;
722             if(power || dash || pulse)
723             {
724                 bool mchk = !melee || onfloor, action = mchk && (d->actortype >= A_BOT || melee || impulseaction&2);
725                 int move = action ? d->move : 0, strafe = action ? d->strafe : 0;
726                 bool moving = mchk && (move || strafe);
727                 float skew = power ? impulsepower : (moving ? impulseboost : impulsejump);
728                 if(onfloor)
729                 {
730                     if(!power) skew = impulsedash;
731                     if(!dash && !melee) d->impulse[IM_JUMP] = lastmillis;
732                 }
733                 int cost = int(impulsecost*(melee ? impulsecostmelee : (dash ? impulsecostdash : impulsecostboost)));
734                 vec keepvel = vec(d->vel).add(d->falling);
735                 float force = impulsevelocity(d, skew, cost, melee ? A_A_PARKOUR : (dash ? A_A_DASH : A_A_BOOST), melee ? impulsemeleeredir : (dash ? impulsedashredir : (moving ? impulseboostredir : impulsejumpredir)), keepvel);
736                 if(force > 0)
737                 {
738                     vec dir(0, 0, 1);
739                     if(power || dash || moving || onfloor)
740                     {
741                         float yaw = d->yaw, pitch = moving && (power || pulse) ? d->pitch : 0;
742                         vecfromyawpitch(yaw, pitch, moving ? move : 1, strafe, dir);
743                         if(!power && dash && !d->floor.iszero() && !dir.iszero())
744                         {
745                             dir.project(d->floor).normalize();
746                             if(dir.z < 0) force += -dir.z*force;
747                         }
748                     }
749                     d->vel = vec(dir).mul(force).add(keepvel);
750                     if(power) d->vel.z += jumpvel(d, true);
751                     d->doimpulse(cost, melee ? IM_T_MELEE : (dash ? IM_T_DASH : IM_T_BOOST), lastmillis);
752                     d->action[AC_JUMP] = false;
753                     if(power || pulse) onfloor = false;
754                     client::addmsg(N_SPHY, "ri2", d->clientnum, melee ? SPHY_MELEE : (dash ? SPHY_DASH : SPHY_BOOST));
755                     game::impulseeffect(d);
756                     return true;
757                 }
758             }
759         }
760         return false;
761     }
762 
modifyinput(gameent * d,vec & m,bool wantsmove,int millis)763     void modifyinput(gameent *d, vec &m, bool wantsmove, int millis)
764     {
765         bool onfloor = d->physstate >= PHYS_SLOPE || d->onladder || liquidcheck(d);
766         if(d->turnside && (!allowimpulse(d, A_A_PARKOUR) || d->impulse[IM_TYPE] != IM_T_SKATE || (impulseskate && lastmillis-d->impulse[IM_TIME] > impulseskate) || d->vel.magnitude() <= 1))
767         {
768             d->turnside = 0;
769             d->resetphys(true);
770             onfloor = false;
771         }
772         if(d->turnside)
773         {
774             if(d->action[AC_JUMP] && canimpulse(d, A_A_PARKOUR, true))
775             {
776                 int cost = int(impulsecost*impulsecostkick);
777                 vec keepvel = vec(d->vel).add(d->falling);
778                 float mag = impulsevelocity(d, impulseparkourkick, cost, A_A_PARKOUR, impulseparkourkickredir, keepvel);
779                 if(mag > 0)
780                 {
781                     vec rft;
782                     vecfromyawpitch(d->yaw, d->actortype >= A_BOT || !kickoffstyle ? kickoffangle : d->pitch, 1, 0, rft);
783                     d->vel = vec(rft).mul(mag).add(keepvel);
784                     d->doimpulse(cost, IM_T_KICK, lastmillis);
785                     d->turnmillis = PHYSMILLIS;
786                     d->turnside = 0; d->turnyaw = d->turnroll = 0;
787                     d->action[AC_JUMP] = onfloor = false;
788                     client::addmsg(N_SPHY, "ri2", d->clientnum, SPHY_KICK);
789                     game::impulseeffect(d);
790                     game::footstep(d);
791                 }
792             }
793         }
794         else
795         {
796             impulseplayer(d, onfloor);
797             if(onfloor && d->action[AC_JUMP] && AA(d->actortype, abilities)&(1<<A_A_JUMP))
798             {
799                 float force = jumpvel(d, true);
800                 if(force > 0)
801                 {
802                     d->vel.z += force;
803                     if(d->inliquid)
804                     {
805                         float scale = liquidmerge(d, 1.f, PHYS(liquidspeed));
806                         d->vel.x *= scale;
807                         d->vel.y *= scale;
808                     }
809                     d->resetphys();
810                     d->impulse[IM_JUMP] = lastmillis;
811                     d->action[AC_JUMP] = onfloor = false;
812                     client::addmsg(N_SPHY, "ri2", d->clientnum, SPHY_JUMP);
813                     playsound(S_JUMP, d->o, d);
814                     createshape(PART_SMOKE, int(d->radius), 0x222222, 21, 20, 250, d->feetpos(), 1, 1, -10, 0, 10.f);
815                 }
816             }
817             if(d->hasmelee(lastmillis, true, d->sliding(true), onfloor))
818             {
819                 vec oldpos = d->o, dir(d->yaw*RAD, 0.f);
820                 d->o.add(dir);
821                 bool collided = collide(d, dir);
822                 d->o = oldpos;
823                 if(collided && hitplayer && gameent::is(hitplayer))
824                 {
825                     //d->action[AC_SPECIAL] = false;
826                     d->resetjump();
827                     impulseplayer(d, onfloor, true);
828                     if(d->turnside)
829                     {
830                         d->turnmillis = PHYSMILLIS;
831                         d->turnside = 0;
832                         d->turnyaw = d->turnroll = 0;
833                     }
834                     loopv(projs::projs)
835                     {
836                         projent *p = projs::projs[i];
837                         if(p->owner != d || p->projtype != PRJ_SHOT || p->weap != W_MELEE) continue;
838                         p->target = (gameent *)hitplayer;
839                     }
840                 }
841             }
842         }
843         bool found = false;
844         if(d->turnside || d->action[AC_SPECIAL])
845         {
846             vec oldpos = d->o, dir;
847             const int movements[6][2] = { { 2, 2 }, { 1, 2 }, { 1, -1 }, { 1, 1 }, { 0, 2 }, { -1, 2 } };
848             loopi(d->turnside ? 6 : 4) // we do these insane checks so that running along walls works at all times
849             {
850                 int move = movements[i][0], strafe = movements[i][1];
851                 if(move == 2) move = d->move > 0 ? d->move : 0;
852                 if(strafe == 2) strafe = d->turnside ? d->turnside : d->strafe;
853                 if(!move && !strafe) continue;
854                 vecfromyawpitch(d->yaw, 0, move, strafe, dir);
855                 d->o.add(dir);
856                 bool collided = collide(d);
857                 d->o = oldpos;
858                 if(!collided || hitplayer || collidewall.iszero()) continue;
859                 vec face = vec(collidewall).normalize();
860                 if(fabs(face.z) <= impulseparkournorm)
861                 {
862                     bool cankick = d->action[AC_SPECIAL] && canimpulse(d, A_A_PARKOUR, true), parkour = cankick && !onfloor && !d->onladder;
863                     float yaw = 0, pitch = 0;
864                     vectoyawpitch(face, yaw, pitch);
865                     float off = yaw-d->yaw;
866                     if(off > 180) off -= 360;
867                     else if(off < -180) off += 360;
868                     bool iskick = impulsekick > 0 && fabs(off) >= impulsekick, vault = false;
869                     if(cankick && iskick)
870                     {
871                         float space = d->height+d->aboveeye, m = min(impulsevaultmin, impulsevaultmax), n = max(impulsevaultmin, impulsevaultmax);
872                         d->o.add(dir);
873                         if(onfloor)
874                         {
875                             d->o.z += space*m;
876                             if(collide(d))
877                             {
878                                 d->o.z += space*n-space*m;
879                                 if(!collide(d) || hitplayer) vault = true;
880                             }
881                         }
882                         else
883                         {
884                             d->o.z += space*n;
885                             if(!collide(d) || hitplayer) vault = true;
886                         }
887                         d->o = oldpos;
888                     }
889                     if(!d->turnside && (parkour || vault) && iskick)
890                     {
891                         int cost = int(impulsecost*(vault ? impulsecostvault : impulsecostclimb));
892                         vec keepvel = vec(d->vel).add(d->falling);
893                         float mag = impulsevelocity(d, vault ? impulseparkourvault : impulseparkourclimb, cost, A_A_PARKOUR, vault ? impulseparkourvaultredir : impulseparkourclimbredir, keepvel);
894                         if(mag > 0)
895                         {
896                             vec rft;
897                             vecfromyawpitch(d->yaw, vault || d->actortype >= A_BOT || !kickupstyle ? kickupangle : fabs(d->pitch), 1, 0, rft);
898                             rft.reflect(face);
899                             d->vel = vec(rft).mul(mag).add(keepvel);
900                             d->doimpulse(cost, vault ? IM_T_VAULT : IM_T_KICK, lastmillis);
901                             d->turnmillis = PHYSMILLIS;
902                             d->turnside = 0;
903                             d->turnyaw = d->turnroll = 0;
904                             client::addmsg(N_SPHY, "ri2", d->clientnum, vault ? SPHY_VAULT : SPHY_KICK);
905                             game::impulseeffect(d);
906                             game::footstep(d);
907                         }
908                         break;
909                     }
910                     else if(d->turnside || parkour)
911                     {
912                         int side = off < 0 ? -1 : 1;
913                         if(off < 0) yaw += 90;
914                         else yaw -= 90;
915                         while(yaw >= 360) yaw -= 360;
916                         while(yaw < 0) yaw += 360;
917                         vec rft;
918                         vecfromyawpitch(yaw, 0.f, 1, 0, rft);
919                         if(!d->turnside)
920                         {
921                             int cost = int(impulsecost*impulsecostparkour);
922                             vec keepvel = vec(d->vel).add(d->falling);
923                             float mag = impulsevelocity(d, impulseparkour, cost, A_A_PARKOUR, impulseparkourredir, keepvel);
924                             if(mag > 0)
925                             {
926                                 d->vel = vec(rft).mul(mag).add(keepvel);
927                                 off = yaw-d->yaw;
928                                 if(off > 180) off -= 360;
929                                 else if(off < -180) off += 360;
930                                 d->doimpulse(cost, IM_T_SKATE, lastmillis);
931                                 d->turnmillis = PHYSMILLIS;
932                                 d->turnside = side;
933                                 d->turnyaw = off;
934                                 d->turnroll = (impulseroll*d->turnside)-d->roll;
935                                 client::addmsg(N_SPHY, "ri2", d->clientnum, SPHY_SKATE);
936                                 game::impulseeffect(d);
937                                 game::footstep(d);
938                                 found = true;
939                             }
940                             break;
941                         }
942                         else if(side == d->turnside)
943                         {
944                             (m = rft).normalize(); // re-project and override
945                             found = true;
946                             break;
947                         }
948                     }
949                 }
950             }
951         }
952         if(d->canmelee(m_weapon(d->actortype, game::gamemode, game::mutators), lastmillis, true, d->sliding(true), onfloor))
953             weapons::doshot(d, d->o, W_MELEE, true, d->sliding(true));
954         if(!found && d->turnside) d->turnside = 0;
955         d->action[AC_DASH] = false;
956     }
957 
modifymovement(gameent * d,vec & m,bool local,bool wantsmove,int millis)958     void modifymovement(gameent *d, vec &m, bool local, bool wantsmove, int millis)
959     {
960         if(wantsmove && d->physstate >= PHYS_SLOPE)
961         { // move up or down slopes in air but only move up slopes in liquid
962             float dz = -(m.x*d->floor.x + m.y*d->floor.y)/d->floor.z;
963             m.z = liquidcheck(d) ? max(m.z, dz) : dz;
964             if(!m.iszero()) m.normalize();
965         }
966         if(!d->turnside && (d->physstate >= PHYS_SLOPE || d->onladder || liquidcheck(d))) d->resetjump();
967         if(local && game::allowmove(d)) modifyinput(d, m, wantsmove, millis);
968         if(d->physstate == PHYS_FALL && !d->onladder && !d->turnside)
969         {
970             if(!d->airmillis) d->airmillis = lastmillis ? lastmillis : 1;
971             d->floormillis = 0;
972         }
973         else
974         {
975             d->airmillis = 0;
976             if(!d->floormillis) d->floormillis = lastmillis ? lastmillis : 1;
977         }
978         if(!d->turnside && d->onladder && !m.iszero()) m.add(vec(0, 0, m.z >= 0 ? 1 : -1)).normalize();
979     }
980 
coastscale(const vec & o)981     float coastscale(const vec &o)
982     {
983         return lookupvslot(lookupcube(ivec(o)).texture[O_TOP], false).coastscale;
984     }
985 
modifyvelocity(physent * pl,bool local,bool floating,int millis)986     void modifyvelocity(physent *pl, bool local, bool floating, int millis)
987     {
988         vec m(0, 0, 0);
989         bool wantsmove = game::allowmove(pl) && (pl->move || pl->strafe);
990         if(wantsmove) vecfromyawpitch(pl->yaw, movepitch(pl) ? pl->pitch : 0, pl->move, pl->strafe, m);
991         if(!floating && gameent::is(pl)) modifymovement((gameent *)pl, m, local, wantsmove, millis);
992         else if(pl->physstate == PHYS_FALL && !pl->onladder)
993         {
994             if(!pl->airmillis) pl->airmillis = lastmillis ? lastmillis : 1;
995             pl->floormillis = 0;
996         }
997         else
998         {
999             pl->airmillis = 0;
1000             if(!pl->floormillis) pl->floormillis = lastmillis ? lastmillis : 1;
1001         }
1002 
1003         m.mul(movevelocity(pl, floating));
1004         float coast = PHYS(floorcoast);
1005         if(floating || pl->type == ENT_CAMERA) coast = floatcoast;
1006         else
1007         {
1008             bool slide = gameent::is(pl) && ((gameent *)pl)->sliding();
1009             float c = pl->physstate >= PHYS_SLOPE || pl->onladder ? (slide ? PHYS(slidecoast) : PHYS(floorcoast))*coastscale(pl->feetpos(-1)) : PHYS(aircoast);
1010             coast = pl->inliquid ? liquidmerge(pl, c, PHYS(liquidcoast)) : c;
1011         }
1012         pl->vel.lerp(m, pl->vel, pow(max(1.0f - 1.0f/coast, 0.0f), millis/20.0f));
1013     }
1014 
modifygravity(physent * pl,int curtime)1015     void modifygravity(physent *pl, int curtime)
1016     {
1017         if(PHYS(gravity) > 0)
1018         {
1019             vec g(0, 0, 0);
1020             float secs = curtime/1000.0f;
1021             if(pl->physstate == PHYS_FALL) g.z -= gravityvel(pl)*secs;
1022             else if(pl->floor.z > 0 && pl->floor.z < floorz)
1023             {
1024                 g.z = -1;
1025                 g.project(pl->floor);
1026                 g.normalize();
1027                 g.mul(gravityvel(pl)*secs);
1028             }
1029             bool liquid = liquidcheck(pl);
1030             if(!liquid || (!pl->move && !pl->strafe) || (gameent::is(pl) && ((gameent *)pl)->crouching()))
1031                 pl->falling.add(g);
1032             if(liquid || pl->physstate >= PHYS_SLOPE)
1033             {
1034                 float coast = liquid ? liquidmerge(pl, PHYS(aircoast), PHYS(liquidcoast)) : PHYS(floorcoast)*coastscale(pl->feetpos(-1)),
1035                       c = liquid ? 1.0f : clamp((pl->floor.z - slopez)/(floorz-slopez), 0.0f, 1.0f);
1036                 pl->falling.mul(pow(max(1.0f - c/coast, 0.0f), curtime/20.0f));
1037             }
1038         }
1039         else pl->falling = vec(0, 0, 0);
1040     }
1041 
updatematerial(physent * pl,const vec & center,const vec & bottom,bool local)1042     void updatematerial(physent *pl, const vec &center, const vec &bottom, bool local)
1043     {
1044         int matid = lookupmaterial(bottom), curmat = matid&MATF_VOLUME, flagmat = matid&MATF_FLAGS,
1045             oldmatid = pl->inmaterial, oldmat = oldmatid&MATF_VOLUME;
1046         float radius = center.z-bottom.z, submerged = pl->submerged;
1047         if(curmat != oldmat)
1048         {
1049             #define mattrig(mo,mcol,ms,mt,mz,mq,mp,mw) \
1050             { \
1051                 int col = (int(mcol[2]*mq) + (int(mcol[1]*mq) << 8) + (int(mcol[0]*mq) << 16)); \
1052                 regularshape(mp, mt, col, 21, 20, mz, mo, ms, 1, 10, 0, 20); \
1053                 if(mw >= 0) playsound(mw, mo, pl); \
1054             }
1055             if(curmat == MAT_WATER || oldmat == MAT_WATER)
1056             {
1057                 const bvec &watercol = getwatercol((curmat == MAT_WATER ? matid : pl->inmaterial) & MATF_INDEX);
1058                 mattrig(bottom, watercol, 0.5f, int(radius), PHYSMILLIS, 0.25f, PART_SPARK, curmat != MAT_WATER ? S_SPLASH2 : S_SPLASH1);
1059             }
1060             if(curmat == MAT_LAVA)
1061             {
1062                 const bvec &lavacol = getlavacol(matid & MATF_INDEX);
1063                 mattrig(center, lavacol, 2.f, int(radius), PHYSMILLIS*2, 1.f, PART_FIREBALL, S_BURNLAVA);
1064             }
1065         }
1066         pl->inmaterial = matid;
1067         if((pl->inliquid = isliquid(curmat)) != false)
1068         {
1069             float frac = radius/10.f, sub = pl->submerged;
1070             vec tmp = bottom;
1071             int found = 0;
1072             loopi(20)
1073             {
1074                 tmp.z += frac;
1075                 if(!isliquid(lookupmaterial(tmp)&MATF_VOLUME))
1076                 {
1077                     found = i+1;
1078                     break;
1079                 }
1080             }
1081             pl->submerged = found ? found/20.f : 1.f;
1082             if(local && pl->physstate < PHYS_SLIDE && sub >= 0.5f && pl->submerged < 0.5f && pl->vel.z > 1e-3f)
1083                 pl->vel.z = max(pl->vel.z, max(jumpvel(pl, false), max(gravityvel(pl), 50.f)));
1084         }
1085         else pl->submerged = 0;
1086         pl->onladder = flagmat&MAT_LADDER;
1087         if(pl->onladder && pl->physstate < PHYS_SLIDE) pl->floor = vec(0, 0, 1);
1088         if(local && gameent::is(pl) && (pl->inmaterial != oldmatid || pl->submerged != submerged))
1089             client::addmsg(N_SPHY, "ri3f", ((gameent *)pl)->clientnum, SPHY_MATERIAL, pl->inmaterial, pl->submerged);
1090     }
1091 
1092     // main physics routine, moves a player/monster for a time step
1093     // moveres indicated the physics precision (which is lower for monsters and multiplayer prediction)
1094     // local is false for multiplayer prediction
1095 
moveplayer(physent * pl,int moveres,bool local,int millis)1096     bool moveplayer(physent *pl, int moveres, bool local, int millis)
1097     {
1098         bool floating = isfloating(pl), player = !floating && gameent::is(pl);
1099         float secs = millis/1000.f;
1100 
1101         pl->blocked = false;
1102         if(player)
1103         {
1104             updatematerial(pl, pl->center(), pl->feetpos(), local);
1105             modifyvelocity(pl, local, false, millis);
1106             if(!sticktospecial(pl) && !pl->onladder) modifygravity(pl, millis); // apply gravity
1107             else pl->resetphys(false);
1108         }
1109         else
1110         {
1111             pl->inliquid = pl->onladder = false;
1112             pl->submerged = 0;
1113             modifyvelocity(pl, local, floating, millis);
1114         }
1115 
1116         vec vel(pl->vel);
1117         if(player && pl->inliquid) vel.mul(liquidmerge(pl, 1.f, PHYS(liquidspeed)));
1118         vel.add(pl->falling);
1119         vel.mul(secs);
1120 
1121         if(floating) // just apply velocity
1122         {
1123             if(pl->physstate != PHYS_FLOAT)
1124             {
1125                 pl->physstate = PHYS_FLOAT;
1126                 pl->airmillis = pl->floormillis = 0;
1127                 pl->falling = vec(0, 0, 0);
1128             }
1129             pl->o.add(vel);
1130         }
1131         else // apply velocity with collision
1132         {
1133             float mag = vec(pl->vel).add(pl->falling).magnitude();
1134             int collisions = 0, timeinair = pl->airtime(lastmillis);
1135             vel.mul(1.0f/moveres);
1136             loopi(moveres) if(!move(pl, vel)) { if(++collisions<5) i--; } // discrete steps collision detection & sliding
1137             if(player)
1138             {
1139                 gameent *d = (gameent *)pl;
1140                 if(!d->airmillis)
1141                 {
1142                     if(local && impulsemethod&2 && timeinair >= impulseboostdelay && d->move == 1 && allowimpulse(d, A_A_DASH) && d->action[AC_CROUCH])
1143                     {
1144                         d->action[AC_DASH] = true;
1145                         d->actiontime[AC_DASH] = lastmillis;
1146                     }
1147                     if(timeinair >= PHYSMILLIS)
1148                     {
1149                         if(mag >= 20)
1150                         {
1151                             int vol = min(int(mag*1.25f), 255); if(d->inliquid) vol *= 0.5f;
1152                             playsound(S_LAND, d->o, d, 0, vol);
1153                         }
1154                         else game::footstep(d);
1155                     }
1156                 }
1157             }
1158         }
1159 
1160         if(gameent::is(pl))
1161         {
1162             if(pl->state == CS_ALIVE) updatedynentcache(pl);
1163             if(local)
1164             {
1165                 gameent *d = (gameent *)pl;
1166                 if(d->state == CS_ALIVE && !floating)
1167                 {
1168                     if(d->o.z < 0)
1169                     {
1170                         game::suicide(d, HIT_LOST);
1171                         return false;
1172                     }
1173                     if(d->turnmillis > 0)
1174                     {
1175                         float amt = float(millis)/float(PHYSMILLIS), yaw = d->turnyaw*amt, roll = d->turnroll*amt;
1176                         if(yaw != 0) d->yaw += yaw;
1177                         if(roll != 0) d->roll += roll;
1178                         d->turnmillis -= millis;
1179                     }
1180                     else
1181                     {
1182                         d->turnmillis = 0;
1183                         if(d->roll != 0 && !d->turnside) adjustscaled(d->roll, PHYSMILLIS);
1184                     }
1185                 }
1186                 else
1187                 {
1188                     d->turnmillis = d->turnside = 0;
1189                     d->roll = 0;
1190                 }
1191             }
1192         }
1193 
1194         return true;
1195     }
1196 
interppos(physent * d)1197     void interppos(physent *d)
1198     {
1199         d->o = d->newpos;
1200         d->o.z += d->height;
1201 
1202         int diff = lastphysframe - lastmillis;
1203         if(diff <= 0 || !physinterp) return;
1204 
1205         vec deltapos(d->deltapos);
1206         deltapos.mul(min(diff, physframetime)/float(physframetime));
1207         d->o.add(deltapos);
1208     }
1209 
movecamera(physent * pl,const vec & dir,float dist,float stepdist)1210     bool movecamera(physent *pl, const vec &dir, float dist, float stepdist)
1211     {
1212         int steps = (int)ceil(dist/stepdist);
1213         if(steps <= 0) return true;
1214 
1215         vec d(dir);
1216         d.mul(dist/steps);
1217         loopi(steps)
1218         {
1219             vec oldpos(pl->o);
1220             pl->o.add(d);
1221             if(collide(pl, vec(0, 0, 0), 0, false))
1222             {
1223                 pl->o = oldpos;
1224                 return false;
1225             }
1226         }
1227         return true;
1228     }
1229 
move(physent * d,int moveres,bool local)1230     void move(physent *d, int moveres, bool local)
1231     {
1232         if(physsteps <= 0)
1233         {
1234             if(local) interppos(d);
1235             return;
1236         }
1237 
1238         if(local)
1239         {
1240             d->o = d->newpos;
1241             d->o.z += d->height;
1242         }
1243         loopi(physsteps-1) moveplayer(d, moveres, local, physframetime);
1244         if(local) d->deltapos = d->o;
1245         moveplayer(d, moveres, local, physframetime);
1246         if(local)
1247         {
1248             d->newpos = d->o;
1249             d->deltapos.sub(d->newpos);
1250             d->newpos.z -= d->height;
1251             interppos(d);
1252         }
1253     }
1254 
avoidcollision(physent * d,const vec & dir,physent * obstacle,float space)1255     void avoidcollision(physent *d, const vec &dir, physent *obstacle, float space)
1256     {
1257         float rad = obstacle->radius+d->radius;
1258         vec bbmin(obstacle->o);
1259         bbmin.x -= rad;
1260         bbmin.y -= rad;
1261         bbmin.z -= obstacle->height+d->aboveeye;
1262         bbmin.sub(space);
1263         vec bbmax(obstacle->o);
1264         bbmax.x += rad;
1265         bbmax.y += rad;
1266         bbmax.z += obstacle->aboveeye+d->height;
1267         bbmax.add(space);
1268 
1269         loopi(3) if(d->o[i] <= bbmin[i] || d->o[i] >= bbmax[i]) return;
1270 
1271         float mindist = 1e16f;
1272         loopi(3) if(dir[i] != 0)
1273         {
1274             float dist = ((dir[i] > 0 ? bbmax[i] : bbmin[i]) - d->o[i]) / dir[i];
1275             mindist = min(mindist, dist);
1276         }
1277         if(mindist >= 0.0f && mindist < 1e15f) d->o.add(vec(dir).mul(mindist));
1278     }
1279 
updatephysstate(physent * d)1280     void updatephysstate(physent *d)
1281     {
1282         if(d->physstate == PHYS_FALL && !d->onladder) return;
1283         vec old(d->o);
1284         /* Attempt to reconstruct the floor state.
1285          * May be inaccurate since movement collisions are not considered.
1286          * If good floor is not found, just keep the old floor and hope it's correct enough.
1287          */
1288         bool foundfloor = false;
1289         switch(d->physstate)
1290         {
1291             case PHYS_SLOPE:
1292             case PHYS_FLOOR:
1293             case PHYS_STEP_DOWN:
1294                 d->o.z -= 0.15f;
1295                 if(collide(d, vec(0, 0, -1), d->physstate == PHYS_SLOPE || d->physstate == PHYS_STEP_DOWN ? slopez : floorz))
1296                 {
1297                     d->floor = collidewall;
1298                     foundfloor = true;
1299                 }
1300                 break;
1301 
1302             case PHYS_STEP_UP:
1303                 d->o.z -= stairheight+0.15f;
1304                 if(collide(d, vec(0, 0, -1), slopez))
1305                 {
1306                     d->floor = collidewall;
1307                     foundfloor = true;
1308                 }
1309                 break;
1310 
1311             case PHYS_SLIDE:
1312                 d->o.z -= 0.15f;
1313                 if(collide(d, vec(0, 0, -1)) && collidewall.z < slopez)
1314                 {
1315                     d->floor = collidewall;
1316                     foundfloor = true;
1317                 }
1318                 break;
1319             default: break;
1320         }
1321         if((d->physstate > PHYS_FALL && d->floor.z <= 0) || (d->onladder && !foundfloor)) d->floor = vec(0, 0, 1);
1322         d->o = old;
1323     }
1324 
xcollide(physent * d,const vec & dir,physent * o)1325     bool xcollide(physent *d, const vec &dir, physent *o)
1326     {
1327         hitflags = HITFLAG_NONE;
1328         if(d->type == ENT_PROJ && gameent::is(o))
1329         {
1330             gameent *e = (gameent *)o;
1331             if(e->wantshitbox())
1332             {
1333                 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))
1334                     hitflags |= HITFLAG_LEGS;
1335                 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))
1336                     hitflags |= HITFLAG_TORSO;
1337                 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))
1338                     hitflags |= HITFLAG_HEAD;
1339                 return hitflags != HITFLAG_NONE;
1340             }
1341         }
1342         if(plcollide(d, dir, o))
1343         {
1344             hitflags |= HITFLAG_TORSO;
1345             return true;
1346         }
1347         return false;
1348     }
1349 
xtracecollide(physent * d,const vec & from,const vec & to,float x1,float x2,float y1,float y2,float maxdist,float & dist,physent * o)1350     bool xtracecollide(physent *d, const vec &from, const vec &to, float x1, float x2, float y1, float y2, float maxdist, float &dist, physent *o)
1351     {
1352         hitflags = HITFLAG_NONE;
1353         if(d && d->type == ENT_PROJ && gameent::is(o))
1354         {
1355             gameent *e = (gameent *)o;
1356             if(e->wantshitbox())
1357             {
1358                 float bestdist = 1e16f;
1359                 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)
1360                 {
1361                     vec bottom(e->legs), top(e->legs); bottom.z -= e->lrad.z; top.z += e->lrad.z; float t = 1e16f;
1362                     if(linecylinderintersect(from, to, bottom, top, max(e->lrad.x, e->lrad.y), t)) { hitflags |= HITFLAG_LEGS; bestdist = min(bestdist, t); }
1363                 }
1364                 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)
1365                 {
1366                     vec bottom(e->torso), top(e->torso); bottom.z -= e->trad.z; top.z += e->trad.z; float t = 1e16f;
1367                     if(linecylinderintersect(from, to, bottom, top, max(e->trad.x, e->trad.y), t)) { hitflags |= HITFLAG_TORSO; bestdist = min(bestdist, t); }
1368                 }
1369                 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)
1370                 {
1371                     vec bottom(e->head), top(e->head); bottom.z -= e->hrad.z; top.z += e->hrad.z; float t = 1e16f;
1372                     if(linecylinderintersect(from, to, bottom, top, max(e->hrad.x, e->hrad.y), t)) { hitflags |= HITFLAG_HEAD; bestdist = min(bestdist, t); }
1373                 }
1374                 if(hitflags == HITFLAG_NONE) return false;
1375                 dist = bestdist*from.dist(to);
1376                 return true;
1377             }
1378         }
1379         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))
1380         {
1381             hitflags |= HITFLAG_TORSO;
1382             return true;
1383         }
1384         return false;
1385     }
1386 
complexboundbox(physent * d)1387     void complexboundbox(physent *d)
1388     {
1389         render3dbox(d->o, d->height, d->aboveeye, d->xradius, d->yradius);
1390         renderellipse(d->o, d->xradius, d->yradius, d->yaw);
1391         if(gameent::is(d))
1392         {
1393             gameent *e = (gameent *)d;
1394             if(e->wantshitbox())
1395             {
1396                 render3dbox(e->head, e->hrad.z, e->hrad.z, e->hrad.x, e->hrad.y);
1397                 renderellipse(e->head, e->hrad.x, e->hrad.y, e->yaw);
1398                 render3dbox(e->torso, e->trad.z, e->trad.z, e->trad.x, e->trad.y);
1399                 renderellipse(e->torso, e->trad.x, e->trad.y, e->yaw);
1400                 render3dbox(e->legs, e->lrad.z, e->lrad.z, e->lrad.x, e->lrad.y);
1401                 renderellipse(e->legs, e->lrad.x, e->lrad.y, e->yaw);
1402             }
1403         }
1404     }
1405 
entinmap(physent * d,bool avoidplayers)1406     bool entinmap(physent *d, bool avoidplayers)
1407     {
1408         if(d->state != CS_ALIVE)
1409         {
1410             d->resetinterp();
1411             return insideworld(d->o);
1412         }
1413         vec orig = d->o;
1414         float maxrad = max(d->radius, max(d->xradius, d->yradius))+1;
1415         #define doposchk \
1416             if(insideworld(d->o) && !collide(d, vec(0, 0, 0), 0, avoidplayers)) \
1417             { \
1418                 d->resetinterp(); \
1419                 return true; \
1420             } \
1421             else d->o = orig;
1422         #define inmapchk(x,y) \
1423             loopi(x) \
1424             { \
1425                 int n = i+1; \
1426                 y; \
1427                 doposchk; \
1428             }
1429         doposchk;
1430         if(gameent::is(d)) loopk(18)
1431         {
1432             vec dir = vec(d->yaw*RAD, d->pitch*RAD).rotate_around_z(k*20.f*RAD);
1433             if(!dir.iszero()) inmapchk(100, d->o.add(vec(dir).mul(n/10.f+maxrad)));
1434         }
1435         if(!d->vel.iszero()) loopk(18)
1436         {
1437             vec dir = vec(d->vel).normalize().rotate_around_z(k*20.f*RAD);
1438             inmapchk(100, d->o.add(vec(dir).mul(n/10.f+maxrad)));
1439         }
1440         inmapchk(100, d->o.add(vec((rnd(21)-10)/10.f, (rnd(21)-10)/10.f, (rnd(21)-10)/10.f).normalize().mul(vec(n/10.f+maxrad, n/10.f+maxrad, n/25.f+maxrad))));
1441         inmapchk(20, d->o.z += (d->height+d->aboveeye)*n/10.f);
1442         d->o = orig;
1443         d->resetinterp();
1444         return false;
1445     }
1446 
1447     VAR(IDF_PERSIST, smoothmove, 0, 100, 200);
1448     VAR(IDF_PERSIST, smoothdist, 0, 64, 1024);
1449 
predictplayer(gameent * d,bool domove,int res=0,bool local=false)1450     void predictplayer(gameent *d, bool domove, int res = 0, bool local = false)
1451     {
1452         d->o = d->newpos;
1453         d->o.z += d->height;
1454 
1455         d->yaw = d->newyaw;
1456         d->pitch = d->newpitch;
1457 
1458         if(domove)
1459         {
1460             move(d, res, local);
1461             d->newpos = d->o;
1462             d->newpos.z -= d->height;
1463         }
1464 
1465         float k = 1.0f - float(lastmillis - d->smoothmillis)/float(smoothmove);
1466         if(k>0)
1467         {
1468             d->o.add(vec(d->deltapos).mul(k));
1469 
1470             d->yaw += d->deltayaw*k;
1471             if(d->yaw<0) d->yaw += 360;
1472             else if(d->yaw>=360) d->yaw -= 360;
1473             d->pitch += d->deltapitch*k;
1474         }
1475     }
1476 
smoothplayer(gameent * d,int res,bool local)1477     void smoothplayer(gameent *d, int res, bool local)
1478     {
1479         if(d->state == CS_ALIVE || d->state == CS_EDITING)
1480         {
1481             if(smoothmove && d->smoothmillis>0) predictplayer(d, true, res, local);
1482             else move(d, res, local);
1483         }
1484         else if(d->state==CS_DEAD || d->state == CS_WAITING)
1485         {
1486             if(d->ragdoll) moveragdoll(d, true);
1487             else if(lastmillis-d->lastpain<2000) move(d, res, local);
1488         }
1489     }
1490 
droptofloor(vec & o,int type,float radius,float height)1491     bool droptofloor(vec &o, int type, float radius, float height)
1492     {
1493         static struct dropent : physent
1494         {
1495             dropent()
1496             {
1497                 physent::reset();
1498                 radius = xradius = yradius = height = aboveeye = 1;
1499                 type = ENT_DUMMY;
1500                 vel = vec(0, 0, -1);
1501             }
1502         } d;
1503         d.o = o;
1504         d.type = type;
1505         if(!insideworld(d.o))
1506         {
1507             if(d.o.z < hdr.worldsize) return false;
1508             d.o.z = hdr.worldsize - 1e-3f;
1509             if(!insideworld(d.o)) return false;
1510         }
1511         vec v(0.0001f, 0.0001f, -1);
1512         v.normalize();
1513         if(raycube(d.o, v, hdr.worldsize) >= hdr.worldsize) return false;
1514         d.radius = d.xradius = d.yradius = radius;
1515         d.height = height;
1516         d.aboveeye = radius;
1517         if(!movecamera(&d, vec(0, 0, -1), hdr.worldsize, 1))
1518         {
1519             o = d.o;
1520             return true;
1521         }
1522         return false;
1523     }
1524 
reset()1525     void reset()
1526     {
1527         lastphysframe = 0;
1528     }
1529 
update()1530     void update()
1531     {
1532         if(!lastphysframe) lastphysframe = lastmillis;
1533         int diff = lastmillis - lastphysframe;
1534         if(diff <= 0) physsteps = 0;
1535         else
1536         {
1537             physsteps = (diff + physframetime - 1)/physframetime;
1538             lastphysframe += physsteps * physframetime;
1539         }
1540         cleardynentcache();
1541     }
1542 }
1543