1 // physics.cpp: no physics books were hurt nor consulted in the construction of this code.
2 // All physics computations and constants were invented on the fly and simply tweaked until
3 // they "felt right", and have no basis in reality. Collision detection is simplistic but
4 // very robust (uses discrete steps at fixed fps).
5 
6 #include "cube.h"
7 
raycube(const vec & o,const vec & ray,vec & surface)8 float raycube(const vec &o, const vec &ray, vec &surface)
9 {
10     surface = vec(0, 0, 0);
11 
12     if(ray.iszero()) return -1;
13 
14     vec v = o;
15     float dist = 0, dx = 0, dy = 0, dz = 0;
16 
17     int nr;
18     for(nr=0;nr<512;nr++) // sam's suggestion :: I found no map which got nr > 350
19     {
20         int x = int(v.x), y = int(v.y);
21         if(x < 0 || y < 0 || x >= ssize || y >= ssize) return -1;
22         sqr *s = S(x, y);
23         float floor = s->floor, ceil = s->ceil;
24         if(s->type==FHF) floor -= s->vdelta/4.0f;
25         if(s->type==CHF) ceil += s->vdelta/4.0f;
26         if(SOLID(s) || v.z < floor || v.z > ceil)
27         {
28             if((!dx && !dy) || s->wtex==DEFAULT_SKY || (!SOLID(s) && v.z > ceil && s->ctex==DEFAULT_SKY)) return -1;
29             if(s->type!=CORNER)// && s->type!=FHF && s->type!=CHF)
30             {
31                 if(dx<dy) surface.x = ray.x>0 ? -1 : 1;
32                 else surface.y = ray.y>0 ? -1 : 1;
33                 sqr *n = S(x+(int)surface.x, y+(int)surface.y);
34                 if(SOLID(n) || (v.z < floor && v.z < n->floor) || (v.z > ceil && v.z > n->ceil))
35                 {
36                     surface = dx<dy ? vec(0, ray.y>0 ? -1 : 1, 0) : vec(ray.x>0 ? -1 : 1, 0, 0);
37                     n = S(x+(int)surface.x, y+(int)surface.y);
38                     if(SOLID(n) || (v.z < floor && v.z < n->floor) || (v.z > ceil && v.z > n->ceil))
39                         surface = vec(0, 0, ray.z>0 ? -1 : 1);
40                 }
41             }
42             dist = max(dist-0.1f, 0.0f);
43             break;
44         }
45         dx = ray.x ? (x + (ray.x > 0 ? 1 : 0) - v.x)/ray.x : 1e16f;
46         dy = ray.y ? (y + (ray.y > 0 ? 1 : 0) - v.y)/ray.y : 1e16f;
47         dz = ray.z ? ((ray.z > 0 ? ceil : floor) - v.z)/ray.z : 1e16f;
48         if(dz < dx && dz < dy)
49         {
50             if(ray.z>0 && s->ctex==DEFAULT_SKY) return -1;
51             if(s->type!=FHF && s->type!=CHF) surface.z = ray.z>0 ? -1 : 1;
52             dist += dz;
53             break;
54         }
55         float disttonext = 0.1f + min(dx, dy);
56         v.add(vec(ray).mul(disttonext));
57         dist += disttonext;
58     }
59     if (nr == 512) return -1;
60     return dist;
61 }
62 
raycubelos(const vec & from,const vec & to,float margin)63 bool raycubelos(const vec &from, const vec &to, float margin)
64 {
65     vec dir(to);
66     dir.sub(from);
67     float limit = dir.magnitude();
68     dir.mul(1.0f/limit);
69     vec surface;
70     float dist = raycube(from, dir, surface);
71     return dist > max(limit - margin, 0.0f);
72 }
73 
74 physent *hitplayer = NULL;
75 
plcollide(physent * d,physent * o,float & headspace,float & hi,float & lo)76 bool plcollide(physent *d, physent *o, float &headspace, float &hi, float &lo)          // collide with physent
77 {
78     if(o->state!=CS_ALIVE || !o->cancollide) return false;
79     const float r = o->radius+d->radius;
80     const vec dr = vec(o->o.x-d->o.x,o->o.y-d->o.y,0);
81     const float deyeheight = d->eyeheight, oeyeheight = o->eyeheight;
82     if((d->type==ENT_PLAYER && o->type==ENT_PLAYER ? dr.sqrxy() < r*r : fabs(dr.x)<r && fabs(dr.y)<r) && dr.dotxy(d->vel) >= 0.0f)
83     {
84         if(d->o.z-deyeheight<o->o.z-oeyeheight) { if(o->o.z-oeyeheight<hi) hi = o->o.z-oeyeheight-1; }
85         else if(o->o.z+o->aboveeye>lo) lo = o->o.z+o->aboveeye+1;
86 
87         if(fabs(o->o.z-d->o.z)<o->aboveeye+deyeheight) { hitplayer = o; return true; }
88         headspace = d->o.z-o->o.z-o->aboveeye-deyeheight;
89         if(headspace<0) headspace = 10;
90     }
91     return false;
92 }
93 
cornertest(int mip,int x,int y,int dx,int dy,int & bx,int & by,int & bs)94 bool cornertest(int mip, int x, int y, int dx, int dy, int &bx, int &by, int &bs)    // recursively collide with a mipmapped corner cube
95 {
96     sqr *w = wmip[mip];
97     int mfactor = sfactor - mip;
98     bool stest = SOLID(SWS(w, x+dx, y, mfactor)) && SOLID(SWS(w, x, y+dy, mfactor));
99     mip++;
100     x /= 2;
101     y /= 2;
102     if(SWS(wmip[mip], x, y, mfactor-1)->type==CORNER)
103     {
104         bx = x<<mip;
105         by = y<<mip;
106         bs = 1<<mip;
107         return cornertest(mip, x, y, dx, dy, bx, by, bs);
108     }
109     return stest;
110 }
111 
mmcollide(physent * d,float & hi,float & lo)112 bool mmcollide(physent *d, float &hi, float &lo)           // collide with a mapmodel
113 {
114     const float eyeheight = d->eyeheight;
115     const float playerheight = eyeheight + d->aboveeye;
116     loopv(ents)
117     {
118         entity &e = ents[i];
119         // if(e.type==CLIP || (e.type == PLCLIP && d->type == ENT_PLAYER))
120         if (e.type==CLIP || (e.type == PLCLIP && (d->type == ENT_BOT || d->type == ENT_PLAYER || (d->type == ENT_BOUNCE && ((bounceent *)d)->plclipped)))) // don't allow bots to hack themselves into plclips - Bukz 2011/04/14
121         {
122             if(fabs(e.x-d->o.x) < e.attr2 + d->radius && fabs(e.y-d->o.y) < e.attr3 + d->radius)
123             {
124                 const float cz = float(S(e.x, e.y)->floor+e.attr1), ch = float(e.attr4);
125                 const float dz = d->o.z-d->eyeheight;
126                 if(dz < cz - 0.001) { if(cz<hi) hi = cz; }
127                 else if(cz+ch>lo) lo = cz+ch;
128                 if(hi-lo < playerheight) return true;
129             }
130         }
131         else if(e.type==MAPMODEL)
132         {
133             mapmodelinfo &mmi = getmminfo(e.attr2);
134             if(!&mmi || !mmi.h) continue;
135             const float r = mmi.rad+d->radius;
136             if(fabs(e.x-d->o.x)<r && fabs(e.y-d->o.y)<r)
137             {
138                 const float mmz = float(S(e.x, e.y)->floor+mmi.zoff+e.attr3);
139                 const float dz = d->o.z-eyeheight;
140                 if(dz<mmz) { if(mmz<hi) hi = mmz; }
141                 else if(mmz+mmi.h>lo) lo = mmz+mmi.h;
142                 if(hi-lo < playerheight) return true;
143             }
144         }
145     }
146     return false;
147 }
148 
objcollide(physent * d,const vec & objpos,float objrad,float objheight)149 bool objcollide(physent *d, const vec &objpos, float objrad, float objheight) // collide with custom/typeless objects
150 {
151     const float r = d->radius+objrad;
152     if(fabs(objpos.x-d->o.x)<r && fabs(objpos.y-d->o.y)<r)
153     {
154         const float maxdist = (d->eyeheight+d->aboveeye+objheight)/2.0f;
155         const float dz = d->o.z+(-d->eyeheight+d->aboveeye)/2.0f;
156         const float objz = objpos.z+objheight/2.0f;
157         return dz-objz <= maxdist && dz-objz >= -maxdist;
158     }
159     return false;
160 }
161 
162 // all collision happens here
163 // spawn is a dirty side effect used in spawning
164 // drop & rise are supplied by the physics below to indicate gravity/push for current mini-timestep
165 static int cornersurface = 0;
166 
collide(physent * d,bool spawn,float drop,float rise,int level)167 bool collide(physent *d, bool spawn, float drop, float rise, int level) // levels 1 = map, 2 = players, 4 = models, default: 1+2+4
168 {
169     cornersurface = 0;
170     const float fx1 = d->o.x-d->radius;     // figure out integer cube rectangle this entity covers in map
171     const float fy1 = d->o.y-d->radius;
172     const float fx2 = d->o.x+d->radius;
173     const float fy2 = d->o.y+d->radius;
174     const int x1 = int(fx1);
175     const int y1 = int(fy1);
176     const int x2 = int(fx2);
177     const int y2 = int(fy2);
178     float hi = 127, lo = -128;
179     const float eyeheight = d->eyeheight;
180     const float playerheight = eyeheight + d->aboveeye;
181 
182     if(level&1) for(int y = y1; y<=y2; y++) for(int x = x1; x<=x2; x++)     // collide with map
183     {
184         if(OUTBORD(x,y)) return true;
185         sqr *s = S(x,y);
186         float ceil = s->ceil;
187         float floor = s->floor;
188         switch(s->type)
189         {
190             case SOLID:
191                 return true;
192 
193             case CORNER:
194             {
195                 int bx = x, by = y, bs = 1;
196                 cornersurface = 1;
197                 if((x==x1 && y==y2 && cornertest(0, x, y, -1,  1, bx, by, bs) && fx1-bx<=fy2-by)
198                 || (x==x2 && y==y1 && cornertest(0, x, y,  1, -1, bx, by, bs) && fx2-bx>=fy1-by) || !(++cornersurface)
199                 || (x==x1 && y==y1 && cornertest(0, x, y, -1, -1, bx, by, bs) && fx1-bx+fy1-by<=bs)
200                 || (x==x2 && y==y2 && cornertest(0, x, y,  1,  1, bx, by, bs) && fx2-bx+fy2-by>=bs))
201                     return true;
202                 cornersurface = 0;
203                 break;
204             }
205 
206             case FHF:       // FIXME: too simplistic collision with slopes, makes it feels like tiny stairs
207                 floor -= (s->vdelta+S(x+1,y)->vdelta+S(x,y+1)->vdelta+S(x+1,y+1)->vdelta)/16.0f;
208                 break;
209 
210             case CHF:
211                 ceil += (s->vdelta+S(x+1,y)->vdelta+S(x,y+1)->vdelta+S(x+1,y+1)->vdelta)/16.0f;
212 
213         }
214         if(ceil<hi) hi = ceil;
215         if(floor>lo) lo = floor;
216     }
217 
218     if(level&1 && hi-lo < playerheight) return true;
219 
220     float headspace = 10.0f;
221 
222     if( level&2 && d->type!=ENT_CAMERA)
223     {
224         loopv(players)       // collide with other players
225         {
226             playerent *o = players[i];
227             if(!o || o==d || (o==player1 && d->type==ENT_CAMERA)) continue;
228             if(plcollide(d, o, headspace, hi, lo)) return true;
229         }
230         if(d!=player1) if(plcollide(d, player1, headspace, hi, lo)) return true;
231     }
232 
233     headspace -= 0.01f;
234     if( level&4 && mmcollide(d, hi, lo)) return true;    // collide with map models
235 
236     if(spawn)
237     {
238         d->o.z = lo+eyeheight;       // just drop to floor (sideeffect)
239         d->onfloor = true;
240     }
241     else
242     {
243         const float spacelo = d->o.z-eyeheight-lo;
244         if(spacelo<0)
245         {
246             if(spacelo>-0.01)
247             {
248                 d->o.z = lo+eyeheight;   // stick on step
249             }
250             else if(spacelo>-1.26f && d->type!=ENT_BOUNCE) d->o.z += rise;       // rise thru stair
251             else return true;
252         }
253         else
254         {
255             d->o.z -= min(min(drop, spacelo), headspace);       // gravity
256         }
257 
258         const float spacehi = hi-(d->o.z+d->aboveeye);
259         if(spacehi<0)
260         {
261             if(spacehi<-0.1) return true;     // hack alert!
262             if(spacelo>0.1f) d->o.z = hi-d->aboveeye; // glue to ceiling if in midair
263             d->vel.z = 0;                     // cancel out jumping velocity
264         }
265 
266         const float floorclamp = d->crouching ? 0.1f : 0.01f;
267         d->onfloor = d->o.z-eyeheight-lo < floorclamp;
268     }
269     return false;
270 }
271 
272 VARP(maxroll, 0, 0, 20); // note: when changing max value, fix network transmission
273 //VAR(recoilbackfade, 0, 100, 1000);
274 
resizephysent(physent * pl,int moveres,int curtime,float min,float max)275 void resizephysent(physent *pl, int moveres, int curtime, float min, float max)
276 {
277     if(pl->eyeheightvel==0.0f) return;
278 
279     const bool water = hdr.waterlevel>pl->o.z;
280     const float speed = curtime*pl->maxspeed/(water ? 2000.0f : 1000.0f);
281     float h = pl->eyeheightvel * speed / moveres;
282 
283     loopi(moveres)
284     {
285         pl->eyeheight += h;
286         pl->o.z += h;
287         if(collide(pl))
288         {
289             pl->eyeheight -= h; // collided, revert mini-step
290             pl->o.z -= h;
291             break;
292         }
293         if(pl->eyeheight<min) // clamp to min
294         {
295             pl->o.z += min - pl->eyeheight;
296             pl->eyeheight = min;
297             pl->eyeheightvel = 0.0f;
298             break;
299         }
300         if(pl->eyeheight>max)
301         {
302             pl->o.z -= pl->eyeheight - max;
303             pl->eyeheight = max;
304             pl->eyeheightvel = 0.0f;
305             break;
306         }
307     }
308 }
309 
310 // main physics routine, moves a player/monster for a curtime step
311 // moveres indicated the physics precision (which is lower for monsters and multiplayer prediction)
312 // local is false for multiplayer prediction
313 
clamproll(physent * pl)314 void clamproll(physent *pl)
315 {
316     extern int maxrollremote;
317     int mroll = pl == player1 ? maxroll : maxrollremote;
318     if(pl->roll > mroll) pl->roll = mroll;
319     else if(pl->roll < -mroll) pl->roll = -mroll;
320 }
321 
322 float var_f = 0;
323 int var_i = 0;
324 bool var_b = true;
325 
326 FVARP(flyspeed, 1.0, 2.0, 5.0);
327 
moveplayer(physent * pl,int moveres,bool local,int curtime)328 void moveplayer(physent *pl, int moveres, bool local, int curtime)
329 {
330     bool water = false;
331     const bool editfly = pl->state==CS_EDITING;
332     const bool specfly = pl->type==ENT_PLAYER && ((playerent *)pl)->spectatemode==SM_FLY;
333     const bool isfly = editfly || specfly;
334 
335     vec d;      // vector of direction we ideally want to move in
336 
337     float drop = 0, rise = 0;
338 
339     if(pl->type==ENT_BOUNCE)
340     {
341         bounceent* bounce = (bounceent *) pl;
342         water = hdr.waterlevel>pl->o.z;
343 
344         const float speed = curtime*pl->maxspeed/(water ? 2000.0f : 1000.0f);
345         const float friction = water ? 20.0f : (pl->onfloor || isfly ? 6.0f : 30.0f);
346         const float fpsfric = max(friction*20.0f/curtime, 1.0f);
347 
348         if(pl->onfloor) // apply friction
349         {
350             pl->vel.mul(fpsfric-1);
351         pl->vel.div(fpsfric);
352         }
353         else // apply gravity
354         {
355             const float CUBES_PER_METER = 4; // assumes 4 cubes make up 1 meter
356             const float BOUNCE_MASS = 0.5f; // sane default mass of 0.5 kg
357             const float GRAVITY = BOUNCE_MASS*9.81f/CUBES_PER_METER/1000.0f;
358             bounce->vel.z -= GRAVITY*curtime;
359         }
360 
361         d = bounce->vel;
362         d.mul(speed);
363         if(water) d.div(6.0f); // incorrect
364 
365         // rotate
366         float rotspeed = bounce->rotspeed*d.magnitude();
367         pl->pitch = fmod(pl->pitch+rotspeed, 360.0f);
368         pl->yaw = fmod(pl->yaw+rotspeed, 360.0f);
369     }
370     else // fake physics for player ents to create _the_ cube movement (tm)
371     {
372         const int timeinair = pl->timeinair;
373         int move = pl->onladder && !pl->onfloor && pl->move == -1 ? 0 : pl->move; // movement on ladder
374         water = hdr.waterlevel>pl->o.z-0.5f;
375 
376         float chspeed = 0.4f;
377         if(!(pl->onfloor || pl->onladder)) chspeed = 1.0f;
378 
379         const bool crouching = pl->crouching || pl->eyeheight < pl->maxeyeheight;
380         const float speed = curtime/(water ? 2000.0f : 1000.0f)*pl->maxspeed*(crouching && pl->state != CS_EDITING ? chspeed : 1.0f)*(pl==player1 && isfly ? flyspeed : 1.0f);
381         const float friction = water ? 20.0f : (pl->onfloor || isfly ? 6.0f : (pl->onladder ? 1.5f : 30.0f));
382         const float fpsfric = max(friction/curtime*20.0f, 1.0f);
383 
384         d.x = (float)(move*cosf(RAD*(pl->yaw-90)));
385         d.y = (float)(move*sinf(RAD*(pl->yaw-90)));
386         d.z = 0.0f;
387 
388         if(isfly || water)
389         {
390             d.x *= (float)cosf(RAD*(pl->pitch));
391             d.y *= (float)cosf(RAD*(pl->pitch));
392             d.z = (float)(move*sinf(RAD*(pl->pitch)));
393         }
394 
395         d.x += (float)(pl->strafe*cosf(RAD*(pl->yaw-180)));
396         d.y += (float)(pl->strafe*sinf(RAD*(pl->yaw-180)));
397 
398         pl->vel.mul(fpsfric-1.0f);   // slowly apply friction and direction to velocity, gives a smooth movement
399         pl->vel.add(d);
400         pl->vel.div(fpsfric);
401         d = pl->vel;
402         d.mul(speed);
403 
404         if(editfly)                // just apply velocity
405         {
406             pl->o.add(d);
407             if(pl->jumpnext && !pl->trycrouch)
408             {
409                 pl->jumpnext = true; // fly directly upwards while holding jump keybinds
410                 pl->vel.z = 0.5f;
411             }
412             else if (pl->trycrouch && !pl->jumpnext)
413             {
414                 pl->vel.z = -0.5f; // fly directly down while holding crouch keybinds
415             }
416         }
417         else if(specfly)
418         {
419             rise = speed/moveres/1.2f;
420             if(pl->jumpnext)
421             {
422                 pl->jumpnext = false;
423                 pl->vel.z = 2;
424             }
425         }
426         else                        // apply velocity with collisions
427         {
428             if(pl->type!=ENT_CAMERA)
429             {
430                 if(pl->onladder)
431                 {
432                     const float climbspeed = 1.0f;
433 
434                     if(pl->type==ENT_BOT && pl->state == CS_ALIVE) pl->vel.z = climbspeed; // bots climb upwards only
435                     else if(pl->type==ENT_PLAYER)
436                     {
437                         if(((playerent *)pl)->k_up) pl->vel.z = climbspeed;
438                         else if(((playerent *)pl)->k_down) pl->vel.z = -climbspeed;
439                     }
440                     pl->timeinair = 0;
441                 }
442                 else
443                 {
444                     if(pl->onfloor || water)
445                     {
446                         if(pl->jumpnext)
447                         {
448                             pl->jumpnext = false;
449                             bool doublejump = pl->lastjump && lastmillis-pl->lastjump < 250 && pl->strafe != 0 && pl->lastjumpheight != 0 && pl->lastjumpheight != pl->o.z;
450                             pl->lastjumpheight = pl->o.z;
451                             pl->vel.z = 2.0f; // physics impulse upwards
452                             if(doublejump) // more velocity on double jump
453                             {
454                                 pl->vel.mul(1.25f);
455                             }
456                             if(water) // dampen velocity change even harder, gives correct water feel
457                             {
458                                 pl->vel.x /= 8.0f;
459                                 pl->vel.y /= 8.0f;
460                             }
461                             else if(pl==player1 || pl->type!=ENT_PLAYER) audiomgr.playsoundc(S_JUMP, pl);
462                             pl->lastjump = lastmillis;
463                         }
464                         pl->timeinair = 0;
465                         pl->crouchedinair = false;
466                     }
467                     else
468                     {
469                         pl->timeinair += curtime;
470                         if (pl->trycrouch && !pl->crouching && !pl->crouchedinair && pl->state!=CS_EDITING) {
471                             pl->vel.z += 0.3f;
472                             pl->crouchedinair = true;
473                         }
474                     }
475                 }
476 
477                 if(timeinair > 200 && !pl->timeinair)
478                 {
479                     int sound = timeinair > 800 ? S_HARDLAND : S_SOFTLAND;
480                     if(pl->state!=CS_DEAD)
481                     {
482                         if(pl==player1 || pl->type!=ENT_PLAYER) audiomgr.playsoundc(sound, pl);
483                     }
484                 }
485             }
486 
487             const float gravity = 20.0f;
488             float dropf = (gravity-1)+pl->timeinair/15.0f;         // incorrect, but works fine
489             if(water) { dropf = 5; pl->timeinair = 0; }            // float slowly down in water
490             if(pl->onladder) { dropf = 0; pl->timeinair = 0; }
491 
492             drop = dropf*curtime/gravity/100/moveres;              // at high fps, gravity kicks in too fast
493             rise = speed/moveres/1.2f;                             // extra smoothness when lifting up stairs
494             if(pl->maxspeed-16.0f>0.5f) pl += 0xF0F0;
495         }
496     }
497 
498     bool collided = false;
499     vec oldorigin = pl->o;
500 
501     if(!editfly) loopi(moveres)                                // discrete steps collision detection & sliding
502     {
503         const float f = 1.0f/moveres;
504 
505         // try move forward
506         pl->o.x += f*d.x;
507         pl->o.y += f*d.y;
508         pl->o.z += f*d.z;
509         hitplayer = NULL;
510         if(!collide(pl, false, drop, rise)) continue;
511         else collided = true;
512         if(pl->type==ENT_BOUNCE && cornersurface)
513         { // try corner bounce
514             float ct2f = cornersurface == 2 ? -1.0 : 1.0;
515             vec oo = pl->o, xd = d;
516             xd.x = d.y * ct2f;
517             xd.y = d.x * ct2f;
518             pl->o.x += f * (-d.x + xd.x);
519             pl->o.y += f * (-d.y + xd.y);
520             if(!collide(pl, false, drop, rise))
521             {
522                 d = xd;
523                 float sw = pl->vel.x * ct2f;
524                 pl->vel.x = pl->vel.y * ct2f;
525                 pl->vel.y = sw;
526                 pl->vel.mul(0.7f);
527                 continue;
528             }
529             pl->o = oo;
530         }
531         if(pl->type==ENT_CAMERA || (pl->type==ENT_PLAYER && pl->state==CS_DEAD && ((playerent *)pl)->spectatemode != SM_FLY))
532         {
533             pl->o.x -= f*d.x;
534             pl->o.y -= f*d.y;
535             pl->o.z -= f*d.z;
536             break;
537         }
538         if(pl->type!=ENT_BOUNCE && hitplayer)
539         {
540             vec dr(hitplayer->o.x-pl->o.x,hitplayer->o.y-pl->o.y,0);
541             float invdist = ufInvSqrt(dr.sqrxy()),
542                   push = (invdist < 10.0f ? dr.dotxy(d)*1.1f*invdist : dr.dotxy(d) * 11.0f);
543 
544             pl->o.x -= f*d.x*push;
545             pl->o.y -= f*d.y*push;
546             if(i==0 && pl->type==ENT_BOT) pl->yaw += (dr.cxy(d)>0 ? 2:-2); // force the bots to change direction
547             if( !collide(pl, false, drop, rise) ) continue;
548             pl->o.x += f*d.x*push;
549             pl->o.y += f*d.y*push;
550         }
551         if (cornersurface)
552         {
553             float ct2f = (cornersurface == 2 ? -1.0 : 1.0);
554             float diag = f*d.magnitudexy()*2;
555             vec vd = vec((d.y*ct2f+d.x >= 0.0f ? diag : -diag), (d.x*ct2f+d.y >= 0.0f ? diag : -diag), 0);
556             pl->o.x -= f*d.x;
557             pl->o.y -= f*d.y;
558 
559             pl->o.x += vd.x;
560             pl->o.y += vd.y;
561             if(!collide(pl, false, drop, rise))
562             {
563                 d.x = vd.x; d.y = vd.y;
564                 continue;
565             }
566             pl->o.x -= vd.x;
567             pl->o.y -= vd.y;
568         }
569         else
570         {
571 #define WALKALONGAXIS(x,y) \
572             pl->o.x -= f*d.x; \
573             if(!collide(pl, false, drop, rise)) \
574             { \
575                 d.x = 0; \
576                 if(pl->type==ENT_BOUNCE) { pl->vel.x = -pl->vel.x; pl->vel.mul(0.7f); } \
577                 continue; \
578             } \
579             pl->o.x += f*d.x;
580             // player stuck, try slide along y axis
581             WALKALONGAXIS(x,y);
582             // still stuck, try x axis
583             WALKALONGAXIS(y,x);
584         }
585 //         try just dropping down
586         pl->o.x -= f*d.x;
587         pl->o.y -= f*d.y;
588         if(!collide(pl, false, drop, rise))
589         {
590             d.y = d.x = 0;
591             continue;
592         }
593         pl->o.z -= f*d.z;
594         if(pl->type==ENT_BOUNCE) { pl->vel.z = -pl->vel.z; pl->vel.mul(0.5f); }
595         break;
596     }
597 
598     pl->stuck = (oldorigin==pl->o);
599     if(collided) pl->oncollision();
600     else pl->onmoved(oldorigin.sub(pl->o));
601 
602     if(pl->type==ENT_CAMERA) return;
603 
604     if(pl->type!=ENT_BOUNCE && pl==player1)
605     {
606         // automatically apply smooth roll when strafing
607         if(pl->strafe==0)
608         {
609             pl->roll = pl->roll/(1+(float)sqrt((float)curtime)/25);
610         }
611         else
612         {
613             pl->roll += pl->strafe*curtime/-30.0f;
614             clamproll(pl);
615         }
616 
617         // smooth pitch
618         const float fric = 6.0f/curtime*20.0f;
619         pl->pitch += pl->pitchvel*(curtime/1000.0f)*pl->maxspeed*(pl->crouching ? 0.75f : 1.0f);
620         pl->pitchvel *= fric-3;
621         pl->pitchvel /= fric;
622         /*extern int recoiltest;
623         if(recoiltest)
624         {
625             if(pl->pitchvel < 0.05f && pl->pitchvel > 0.001f) pl->pitchvel -= recoilbackfade/100.0f; // slide back
626         }
627         else*/ if(pl->pitchvel < 0.05f && pl->pitchvel > 0.001f) pl->pitchvel -= ((playerent *)pl)->weaponsel->info.recoilbackfade/100.0f; // slide back
628         if(pl->pitchvel) fixcamerarange(pl); // fix pitch if necessary
629     }
630 
631     // play sounds on water transitions
632     if(pl->type!=ENT_CAMERA)
633     {
634         if(!pl->inwater && water)
635         {
636             if(!pl->lastsplash || lastmillis-pl->lastsplash>500)
637             {
638                 audiomgr.playsound(S_SPLASH2, pl);
639                 pl->lastsplash = lastmillis;
640             }
641             if(pl==player1) pl->vel.z = 0;
642         }
643         else if(pl->inwater && !water) audiomgr.playsound(S_SPLASH1, &pl->o);
644         pl->inwater = water;
645     }
646 
647     // store previous locations of all players/bots
648     if(pl->type==ENT_PLAYER || pl->type==ENT_BOT)
649     {
650         ((playerent *)pl)->history.update(pl->o, lastmillis);
651     }
652 
653     // apply volume-resize when crouching
654     if(pl->type==ENT_PLAYER || pl->type==ENT_BOT)
655     {
656 //         if(pl==player1 && !(intermission || player1->onladder || (pl->trycrouch && !player1->onfloor && player1->timeinair > 50))) updatecrouch(player1, player1->trycrouch);
657         if(!intermission && (pl == player1 || pl->type == ENT_BOT)) updatecrouch((playerent *)pl, pl->trycrouch);
658         const float croucheyeheight = pl->maxeyeheight*3.0f/4.0f;
659         resizephysent(pl, moveres, curtime, croucheyeheight, pl->maxeyeheight);
660     }
661 }
662 
663 const int PHYSFPS = 200;
664 const int PHYSFRAMETIME = 1000 / PHYSFPS;
665 int physsteps = 0, physframetime = PHYSFRAMETIME, lastphysframe = 0;
666 
physicsframe()667 void physicsframe()          // optimally schedule physics frames inside the graphics frames
668 {
669     int diff = lastmillis - lastphysframe;
670     if(diff <= 0) physsteps = 0;
671     else
672     {
673         extern int gamespeed;
674         physframetime = clamp((PHYSFRAMETIME*gamespeed)/100, 1, PHYSFRAMETIME);
675         physsteps = (diff + physframetime - 1)/physframetime;
676         lastphysframe += physsteps * physframetime;
677     }
678 }
679 
680 VAR(physinterp, 0, 1, 1);
681 
interppos(physent * pl)682 void interppos(physent *pl)
683 {
684     pl->o = pl->newpos;
685     pl->o.z += pl->eyeheight;
686 
687     int diff = lastphysframe - lastmillis;
688     if(diff <= 0 || !physinterp) return;
689 
690     vec deltapos(pl->deltapos);
691     deltapos.mul(min(diff, physframetime)/float(physframetime));
692     pl->o.add(deltapos);
693 }
694 
moveplayer(physent * pl,int moveres,bool local)695 void moveplayer(physent *pl, int moveres, bool local)
696 {
697     if(physsteps <= 0)
698     {
699         if(local) interppos(pl);
700         return;
701     }
702 
703     if(local)
704     {
705         pl->o = pl->newpos;
706         pl->o.z += pl->eyeheight;
707     }
708     loopi(physsteps-1) moveplayer(pl, moveres, local, physframetime);
709     if(local) pl->deltapos = pl->o;
710     moveplayer(pl, moveres, local, physframetime);
711     if(local)
712     {
713         pl->newpos = pl->o;
714         pl->deltapos.sub(pl->newpos);
715         pl->newpos.z -= pl->eyeheight;
716         interppos(pl);
717     }
718 }
719 
movebounceent(bounceent * p,int moveres,bool local)720 void movebounceent(bounceent *p, int moveres, bool local)
721 {
722     moveplayer(p, moveres, local);
723 }
724 
725 // movement input code
726 
727 #define dir(name,v,d,s,os) void name(bool isdown) { player1->s = isdown; player1->v = isdown ? d : (player1->os ? -(d) : 0); player1->lastmove = lastmillis; }
728 
729 dir(backward, move,   -1, k_down,  k_up)
730 dir(forward,  move,    1, k_up,    k_down)
731 dir(left,     strafe,  1, k_left,  k_right)
732 dir(right,    strafe, -1, k_right, k_left)
733 
attack(bool on)734 void attack(bool on)
735 {
736     if(intermission) return;
737     if(editmode) editdrag(on);
738     else if(player1->state==CS_DEAD || player1->state==CS_SPECTATE)
739     {
740         if(!on) tryrespawn();
741     }
742     else player1->attacking = on;
743 }
744 
jumpn(bool on)745 void jumpn(bool on)
746 {
747     if(intermission) return;
748     if(player1->isspectating())
749     {
750         if(lastmillis - player1->respawnoffset > 1000 && on) togglespect();
751     }
752     else if(player1->crouching) return;
753     else player1->jumpnext = on;
754 }
755 
updatecrouch(playerent * p,bool on)756 void updatecrouch(playerent *p, bool on)
757 {
758     if(p->crouching == on) return;
759     if(p->state == CS_EDITING) return; // don't apply regular crouch physics in editfly
760     const float crouchspeed = 0.6f;
761     p->crouching = on;
762     p->eyeheightvel = on ? -crouchspeed : crouchspeed;
763     if(p==player1) audiomgr.playsoundc(on ? S_CROUCH : S_UNCROUCH);
764 }
765 
crouch(bool on)766 void crouch(bool on)
767 {
768     if(player1->isspectating()) return;
769     player1->trycrouch = on;
770 }
771 
inWater(int * type)772 int inWater(int *type)
773 {
774     if(hdr.waterlevel > (*type ? player1->o.z : (player1->o.z - player1->eyeheight))) return 1;
775     else return 0;
776 }
777 
778 COMMAND(backward, "d");
779 COMMAND(forward, "d");
780 COMMAND(left, "d");
781 COMMAND(right, "d");
782 COMMANDN(jump, jumpn, "d");
783 COMMAND(attack, "d");
784 COMMAND(crouch, "d");
785 COMMAND(inWater, "i");
786 
fixcamerarange(physent * cam)787 void fixcamerarange(physent *cam)
788 {
789     const float MAXPITCH = 90.0f;
790     if(cam->pitch>MAXPITCH) cam->pitch = MAXPITCH;
791     if(cam->pitch<-MAXPITCH) cam->pitch = -MAXPITCH;
792     while(cam->yaw<0.0f) cam->yaw += 360.0f;
793     while(cam->yaw>=360.0f) cam->yaw -= 360.0f;
794 }
795 
796 FVARP(sensitivity, 1e-3f, 3.0f, 1000.0f);
797 FVARP(scopesensscale, 1e-3f, 0.5f, 1000.0f);
798 FVARP(sensitivityscale, 1e-3f, 1, 1000);
799 FVARP(scopesens, 0, 0, 1000);
800 VARP(scopesensfeel, 0, 0, 1);
801 VARP(invmouse, 0, 0, 1);
802 FVARP(mouseaccel, 0, 0, 1000);
803 FVARP(mfilter, 0.0f, 0.0f, 6.0f);
804 VARP(autoscopesens, 0, 0, 1);
805 
806 float testsens=0;
807 bool senst=0;
tsens(int x)808 int tsens(int x)
809 {
810     static bool highlock=0,lowlock=0;
811     static bool hightry=0,lowtry=0;
812     static float sensn=0,sensl=0,sensh=0;
813     static int nstep=1;
814     if (x==-2000) {  // RENDERING PART!!!
815         if(senst) {
816         draw_textf(
817         "\fJSensitivity Training (hotkeys):\n\fE1. try High Sens. %s\n2. try Low Sens. %s\n\fJ%s :"
818         "\fE\n3. choose: High Sens.\n4. choose: Low Sens.\n\fIrepeat the steps above until the training stops.\n\f35. Stop Training.",
819         VIRTW/4  , VIRTH/3,
820         hightry?"(TRIED)":"" , lowtry?"(TRIED)":"",
821         hightry&&lowtry?"after trying both choose the one you liked most":"now you can choose the sensitivity you preferred");
822         glPushMatrix(); glScalef(2,2,2);
823         draw_textf("step: \f0%d",VIRTW/2  , VIRTH/3*2,nstep);
824         glPopMatrix();
825         }
826         return 0;
827     }
828     if (x == SDLK_3 || x == SDLK_4)
829     {
830         if(!hightry || !lowtry) {
831             if(!hightry && !lowtry) {
832                 conoutf("--- \f3ERROR:\f0before choosing a sensitivity, first try both higher and lower sens.");
833             } else {
834                 conoutf("--- \f3ERROR:\f0try the %s%ser sensitivity too.",hightry?"":"high",lowtry?"":"low");
835             }
836         } else {
837             if (x == SDLK_3) //high sens
838             {
839                 lowlock=1;
840                 if (highlock)
841                 {
842                     sensl=sensn;
843                     sensn=(sensh+sensl)/2.0f;
844                 }
845                 else
846                 {
847                     sensl=sensn;
848                     sensn=sensh;
849                     sensh=sensn*2.0f;
850                 }
851             }
852             if (x == SDLK_4) //low sens
853             {
854                 highlock=1;
855                 if (lowlock)
856                 {
857                     sensh=sensn;
858                     sensn=(sensh+sensl)/2.0f;
859                 }
860                 else
861                 {
862                     sensh=sensn;
863                     sensn=sensl;
864                     sensl=sensn/2.0f;
865                 }
866             }
867             if(sensh/sensn > 1.04f) {
868             conoutf("--- \f0you chose the %ser sensitivity.",x==SDLK_3?"higher":"lower");
869             conoutf("--- \f0repeat previous steps by trying both higher and lower sens and then by choosing the one you like most.");
870             hudoutf("next step!");
871             nstep++;
872             }
873             testsens=sensn;
874             hightry=lowtry=0;
875         }
876 
877     }
878     if (x == SDLK_2)
879     {
880         testsens=sensl;
881         conoutf("--- \f0You are now %strying the lower sensitivity.",lowtry?"re-":""); lowtry=1;
882     }
883     if (x == SDLK_1)
884     {
885         testsens=sensh;
886         conoutf("--- \f0You are now %strying the higher sensitivity.",hightry?"re-":""); hightry=1;
887     }
888     if (x==-1000)
889     {
890         float factor=rnd(800)+600;
891         factor/=1000;
892         sensh=sensitivity*factor*2.0f;
893         sensl=sensitivity*factor/2.0f;
894         sensn=sensitivity*factor;
895     }
896     if (sensh/sensn <= 1.04f || x == SDLK_5)
897     {
898         senst=0;
899         if(sensh/sensn <= 1.04f) {
900             conoutf("--- \f0Sensitivity Training Ended. happy fragging.");
901             sensitivity=sensn;
902         } else {
903             conoutf("--- \f0Sensitivity Training Stopped.");
904         }
905         hightry=lowtry=highlock=lowlock=0;
906         sensn=sensl=sensh=0;
907         testsens=0;
908         nstep=1;
909     }
910     return 0;
911 }
912 
findsens()913 void findsens()
914 {
915     if(!watchingdemo) {
916         senst=1;
917         tsens(-1000);
918         testsens=sensitivity;
919         conoutf("--- \f0Sensitivity Training Started.");
920         return;
921     }
922 }
923 COMMAND(findsens, "");
924 
zooming(playerent * plx)925 inline bool zooming(playerent *plx) { return (plx->weaponsel->type == GUN_SNIPER && ((sniperrifle *)plx->weaponsel)->scoped); }
926 
mousemove(int odx,int ody)927 void mousemove(int odx, int ody)
928 {
929     static float fdx = 0, fdy = 0;
930     if(intermission || (player1->isspectating() && player1->spectatemode==SM_FOLLOW1ST)) return;
931     float dx = odx, dy = ody;
932     if(mfilter > 0.0f)
933     {
934         float k = mfilter * 0.1f;
935         dx = fdx = dx * ( 1.0f - k ) + fdx * k;
936         dy = fdy = dy * ( 1.0f - k ) + fdy * k;
937     }
938     extern float scopesensfunc;
939     float cursens = sensitivity;
940     if(senst) {cursens=testsens;}
941     if(mouseaccel && curtime && (dx || dy)) cursens += 0.02f * mouseaccel * sqrtf(dx*dx + dy*dy)/curtime;
942     if(scopesens==0 || !zooming(player1))
943     {
944         if(scopesensfeel)
945         {
946             // AC 1.1
947             cursens /= 33.0f*sensitivityscale;
948             if( zooming(player1) ) { cursens *= autoscopesens ? scopesensfunc : scopesensscale; }
949             camera1->yaw += dx*cursens;
950             camera1->pitch -= dy*cursens*(invmouse ? -1 : 1);
951         }
952         else
953         {
954             // AC 1.0
955             if( zooming(player1) ) { cursens *= autoscopesens ? scopesensfunc : scopesensscale; }
956             float sensfactor = 33.0f*sensitivityscale;
957             camera1->yaw += dx*cursens/sensfactor;
958             camera1->pitch -= dy*cursens*(invmouse ? -1 : 1)/sensfactor;
959         }
960     }
961     else
962     {
963         // user provided value
964         float sensfactor = 33.0f*sensitivityscale;
965         camera1->yaw += dx*scopesens/sensfactor;
966         camera1->pitch -= dy*scopesens*(invmouse ? -1 : 1)/sensfactor;
967     }
968 
969     fixcamerarange();
970     if(camera1!=player1 && player1->spectatemode!=SM_DEATHCAM)
971     {
972         player1->yaw = camera1->yaw;
973         player1->pitch = camera1->pitch;
974     }
975 }
976 
entinmap(physent * d)977 void entinmap(physent *d)    // brute force but effective way to find a free spawn spot in the map
978 {
979     vec orig(d->o);
980     loopi(100)              // try max 100 times
981     {
982         float dx = (rnd(21)-10)/10.0f*i;  // increasing distance
983         float dy = (rnd(21)-10)/10.0f*i;
984         d->o.x += dx;
985         d->o.y += dy;
986         if(!collide(d, true))
987         {
988             d->resetinterp();
989             return;
990         }
991         d->o = orig;
992     }
993     // leave ent at original pos, possibly stuck
994     d->resetinterp();
995     conoutf(_("can't find entity spawn spot! (%d, %d)"), d->o.x, d->o.y);
996 }
997 
vecfromyawpitch(float yaw,float pitch,int move,int strafe,vec & m)998 void vecfromyawpitch(float yaw, float pitch, int move, int strafe, vec &m)
999 {
1000     if(move)
1001     {
1002         m.x = move*-sinf(RAD*yaw);
1003         m.y = move*cosf(RAD*yaw);
1004     }
1005     else m.x = m.y = 0;
1006 
1007     if(pitch)
1008     {
1009         m.x *= cosf(RAD*pitch);
1010         m.y *= cosf(RAD*pitch);
1011         m.z = move*sinf(RAD*pitch);
1012     }
1013     else m.z = 0;
1014 
1015     if(strafe)
1016     {
1017         m.x += strafe*cosf(RAD*yaw);
1018         m.y += strafe*sinf(RAD*yaw);
1019     }
1020 }
1021 
vectoyawpitch(const vec & v,float & yaw,float & pitch)1022 void vectoyawpitch(const vec &v, float &yaw, float &pitch)
1023 {
1024     yaw = -atan2(v.x, v.y)/RAD;
1025     pitch = asin(v.z/v.magnitude())/RAD;
1026 }
1027