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