Make sv_gameplayfix_delayprojectiles match the engine's behaviour of only running...
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / physics / movetypes / movetypes.qc
1 #include "movetypes.qh"
2
3 #ifdef SVQC
4 void set_movetype(entity this, int mt)
5 {
6         this.move_movetype = mt;
7         if (mt == MOVETYPE_PHYSICS) {
8                 this.move_qcphysics = false;
9         }
10         if(!IL_CONTAINS(g_moveables, this))
11                 IL_PUSH(g_moveables, this); // add it to the moveable entities list (even if it doesn't move!) logic: if an object never sets its movetype, we assume it never does anything notable
12         this.movetype = (this.move_qcphysics) ? MOVETYPE_QCENTITY : mt;
13 }
14 #elif defined(CSQC)
15 void set_movetype(entity this, int mt)
16 {
17         this.move_movetype = mt;
18 }
19 #endif
20
21 bool _Movetype_NudgeOutOfSolid_PivotIsKnownGood(entity this, vector pivot) // SV_NudgeOutOfSolid_PivotIsKnownGood
22 {
23         vector stuckorigin = this.origin;
24         vector goodmins = pivot, goodmaxs = pivot;
25         for(int bump = 0; bump < 6; bump++)
26         {
27                 int coord = 2 - (bump >> 1);
28                 int dir = (bump & 1);
29
30                 for(int subbump = 0; ; ++subbump)
31                 {
32                         vector testorigin = stuckorigin;
33                         if(dir)
34                         {
35                                 // pushing maxs
36                                 switch(coord)
37                                 {
38                                         case 0: testorigin.x += this.maxs_x - goodmaxs.x; break;
39                                         case 1: testorigin.y += this.maxs_y - goodmaxs.y; break;
40                                         case 2: testorigin.z += this.maxs_z - goodmaxs.z; break;
41                                 }
42                         }
43                         else
44                         {
45                                 // pushing mins
46                                 switch(coord)
47                                 {
48                                         case 0: testorigin.x += this.mins_x - goodmins.x; break;
49                                         case 1: testorigin.y += this.mins_y - goodmins.y; break;
50                                         case 2: testorigin.z += this.mins_z - goodmins.z; break;
51                                 }
52                         }
53
54                         tracebox(stuckorigin, goodmins, goodmaxs, testorigin, MOVE_NOMONSTERS, this);
55                         if(trace_startsolid && trace_ent.solid == SOLID_BSP) // NOTE: this checks for bmodelstartsolid in the engine
56                         {
57                                 // BAD BAD, can't fix that
58                                 return false;
59                         }
60
61                         if(trace_fraction >= 1)
62                                 break; // it WORKS!
63
64                         if(subbump >= 10)
65                         {
66                                 // BAD BAD, can't fix that
67                                 return false;
68                         }
69
70                         // we hit something... let's move out of it
71                         vector move = trace_endpos - testorigin;
72                         float nudge = (trace_plane_normal * move) + 0.03125; // FIXME cvar this constant
73                         stuckorigin = stuckorigin + nudge * trace_plane_normal;
74                 }
75
76                 if(dir)
77                 {
78                         // pushing maxs
79                         switch(coord)
80                         {
81                                 case 0: goodmaxs.x = this.maxs_x; break;
82                                 case 1: goodmaxs.y = this.maxs_y; break;
83                                 case 2: goodmaxs.z = this.maxs_z; break;
84                         }
85                 }
86                 else
87                 {
88                         // pushing mins
89                         switch(coord)
90                         {
91                                 case 0: goodmins.x = this.mins_x; break;
92                                 case 1: goodmins.y = this.mins_y; break;
93                                 case 2: goodmins.z = this.mins_z; break;
94                         }
95                 }
96         }
97
98         // WE WIN
99         this.origin = stuckorigin;
100
101         return true;
102 }
103
104 void _Movetype_WallFriction(entity this, vector stepnormal)  // SV_WallFriction
105 {
106         /*float d, i;
107         vector into, side;
108         makevectors(this.v_angle);
109         d = (stepnormal * v_forward) + 0.5;
110
111         if(d < 0)
112         {
113             i = (stepnormal * this.velocity);
114             into = i * stepnormal;
115             side = this.velocity - into;
116             this.velocity_x = side.x * (1 * d);
117             this.velocity_y = side.y * (1 * d);
118         }*/
119 }
120
121 vector planes[MAX_CLIP_PLANES];
122 int _Movetype_FlyMove(entity this, float dt, bool applygravity, bool applystepnormal, float stepheight) // SV_FlyMove
123 {
124         move_stepnormal = '0 0 0';
125
126         if(dt <= 0)
127                 return 0;
128
129         int blockedflag = 0;
130         int i, j, numplanes = 0;
131         float time_left = dt, grav = 0;
132         vector push;
133         vector primal_velocity, original_velocity;
134         vector restore_velocity = this.velocity;
135
136         for(i = 0; i < MAX_CLIP_PLANES; ++i)
137                 planes[i] = '0 0 0';
138
139         if(applygravity)
140         {
141                 this.move_didgravity = 1;
142                 grav = dt * (this.gravity ? this.gravity : 1) * PHYS_GRAVITY(this);
143
144                 if(!GAMEPLAYFIX_NOGRAVITYONGROUND || !IS_ONGROUND(this))
145                 {
146                         if(GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
147                                 this.velocity_z -= grav * 0.5;
148                         else
149                                 this.velocity_z -= grav;
150                 }
151         }
152
153         original_velocity = primal_velocity = this.velocity;
154
155         for(int bumpcount = 0;bumpcount < MAX_CLIP_PLANES;bumpcount++)
156         {
157                 if(this.velocity == '0 0 0')
158                         break;
159
160                 push = this.velocity * time_left;
161                 if(!_Movetype_PushEntity(this, push, true, false))
162                 {
163                         // we got teleported by a touch function
164                         // let's abort the move
165                         blockedflag |= 8;
166                         break;
167                 }
168
169                 // this code is used by MOVETYPE_WALK and MOVETYPE_STEP and SV_UnstickEntity
170                 // abort move if we're stuck in the world (and didn't make it out)
171                 if(trace_startsolid && trace_allsolid)
172                 {
173                         this.velocity = restore_velocity;
174                         return 3;
175                 }
176
177                 if(trace_fraction == 1)
178                         break;
179
180                 float my_trace_fraction = trace_fraction;
181                 vector my_trace_plane_normal = trace_plane_normal;
182
183                 if(trace_plane_normal.z)
184                 {
185                         if(trace_plane_normal.z > 0.7)
186                         {
187                                 // floor
188                                 blockedflag |= 1;
189
190                                 if(!trace_ent)
191                                 {
192                                         //dprint("_Movetype_FlyMove: !trace_ent\n");
193                                         trace_ent = NULL;
194                                 }
195
196                                 SET_ONGROUND(this);
197                                 this.groundentity = trace_ent;
198                         }
199                 }
200                 else if(stepheight)
201                 {
202                         // step - handle it immediately
203                         vector org = this.origin;
204                         vector steppush = '0 0 1' * stepheight;
205
206                         if(!_Movetype_PushEntity(this, steppush, true, false))
207                         {
208                                 blockedflag |= 8;
209                                 break;
210                         }
211                         if(!_Movetype_PushEntity(this, push, true, false))
212                         {
213                                 blockedflag |= 8;
214                                 break;
215                         }
216                         float trace2_fraction = trace_fraction;
217                         steppush = vec3(0, 0, org.z - this.origin_z);
218                         if(!_Movetype_PushEntity(this, steppush, true, false))
219                         {
220                                 blockedflag |= 8;
221                                 break;
222                         }
223
224                         // accept the new position if it made some progress...
225                         if(fabs(this.origin_x - org.x) >= 0.03125 || fabs(this.origin_y - org.y) >= 0.03125)
226                         {
227                                 trace_endpos = this.origin;
228                                 time_left *= 1 - trace2_fraction;
229                                 numplanes = 0;
230                                 continue;
231                         }
232                         else
233                                 this.origin = org;
234                 }
235                 else
236                 {
237                         // step - return it to caller
238                         blockedflag |= 2;
239                         // save the trace for player extrafriction
240                         if(applystepnormal)
241                                 move_stepnormal = trace_plane_normal;
242                 }
243
244                 if(my_trace_fraction >= 0.001)
245                 {
246                         // actually covered some distance
247                         original_velocity = this.velocity;
248                         numplanes = 0;
249                 }
250
251                 time_left *= 1 - my_trace_fraction;
252
253                 // clipped to another plane
254                 if(numplanes >= MAX_CLIP_PLANES)
255                 {
256                         // this shouldn't really happen
257                         this.velocity = '0 0 0';
258                         blockedflag = 3;
259                         break;
260                 }
261
262                 planes[numplanes] = my_trace_plane_normal;
263                 numplanes++;
264
265                 // modify original_velocity so it parallels all of the clip planes
266                 vector new_velocity = '0 0 0';
267                 for (i = 0;i < numplanes;i++)
268                 {
269                         new_velocity = _Movetype_ClipVelocity(original_velocity, planes[i], 1);
270                         for (j = 0;j < numplanes;j++)
271                         {
272                                 if(j != i)
273                                 {
274                                         // not ok
275                                         if((new_velocity * planes[j]) < 0)
276                                                 break;
277                                 }
278                         }
279                         if(j == numplanes)
280                                 break;
281                 }
282
283                 if(i != numplanes)
284                 {
285                         // go along this plane
286                         this.velocity = new_velocity;
287                 }
288                 else
289                 {
290                         // go along the crease
291                         if(numplanes != 2)
292                         {
293                                 this.velocity = '0 0 0';
294                                 blockedflag = 7;
295                                 break;
296                         }
297                         vector dir = cross(planes[0], planes[1]);
298                         // LordHavoc: thanks to taniwha of QuakeForge for pointing out this fix for slowed falling in corners
299                         float ilength = sqrt((dir * dir));
300                         if(ilength)
301                                 ilength = 1.0 / ilength;
302                         dir.x *= ilength;
303                         dir.y *= ilength;
304                         dir.z *= ilength;
305                         float d = (dir * this.velocity);
306                         this.velocity = dir * d;
307                 }
308
309                 // if current velocity is against the original velocity,
310                 // stop dead to avoid tiny occilations in sloping corners
311                 if((this.velocity * primal_velocity) <= 0)
312                 {
313                         this.velocity = '0 0 0';
314                         break;
315                 }
316         }
317
318         // LordHavoc: this came from QW and allows you to get out of water more easily
319         if(GAMEPLAYFIX_EASIERWATERJUMP(this) && (this.flags & FL_WATERJUMP) && !(blockedflag & 8))
320                 this.velocity = primal_velocity;
321
322         if(PHYS_WALLCLIP(this) && this.pm_time && !(this.flags & FL_WATERJUMP) && !(blockedflag & 8))
323                 this.velocity = primal_velocity;
324
325         if(applygravity)
326         {
327                 if(!GAMEPLAYFIX_NOGRAVITYONGROUND || !IS_ONGROUND(this))
328                 {
329                         if(GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
330                                 this.velocity_z -= grav * 0.5f;
331                 }
332         }
333
334         return blockedflag;
335 }
336
337 void _Movetype_CheckVelocity(entity this)  // SV_CheckVelocity
338 {
339         // if(vlen(this.velocity) < 0.0001)
340         // this.velocity = '0 0 0';
341 }
342
343 bool _Movetype_CheckWater(entity this)  // SV_CheckWater
344 {
345         vector point = this.origin;
346         point.z += this.mins.z + 1;
347
348         int nativecontents = pointcontents(point);
349         if(this.watertype && this.watertype != nativecontents)
350         {
351                 // dprintf("_Movetype_CheckWater(): Original: '%d', New: '%d'\n", this.watertype, nativecontents);
352                 if(this.contentstransition)
353                         this.contentstransition(this.watertype, nativecontents);
354         }
355
356         this.waterlevel = WATERLEVEL_NONE;
357         this.watertype = CONTENT_EMPTY;
358
359         int supercontents = Mod_Q1BSP_SuperContentsFromNativeContents(nativecontents);
360         if(supercontents & DPCONTENTS_LIQUIDSMASK)
361         {
362                 this.watertype = nativecontents;
363                 this.waterlevel = WATERLEVEL_WETFEET;
364                 point.z = this.origin.z + (this.mins.z + this.maxs.z) * 0.5;
365                 if(Mod_Q1BSP_SuperContentsFromNativeContents(pointcontents(point)) & DPCONTENTS_LIQUIDSMASK)
366                 {
367                         this.waterlevel = WATERLEVEL_SWIMMING;
368                         point.z = this.origin.z + this.view_ofs.z;
369                         if(Mod_Q1BSP_SuperContentsFromNativeContents(pointcontents(point)) & DPCONTENTS_LIQUIDSMASK)
370                                 this.waterlevel = WATERLEVEL_SUBMERGED;
371                 }
372         }
373
374         return this.waterlevel > 1;
375 }
376
377 void _Movetype_CheckWaterTransition(entity ent)  // SV_CheckWaterTransition
378 {
379         int contents = pointcontents(ent.origin);
380
381         if(!ent.watertype)
382         {
383                 // just spawned here
384                 if(!GAMEPLAYFIX_WATERTRANSITION(ent))
385                 {
386                         ent.watertype = contents;
387                         ent.waterlevel = 1;
388                         return;
389                 }
390         }
391         else if(ent.watertype != contents)
392         {
393                 // dprintf("_Movetype_CheckWaterTransition(): Origin: %s, Direct: '%d', Original: '%d', New: '%d'\n", vtos(ent.origin), pointcontents(ent.origin), ent.watertype, contents);
394                 if(ent.contentstransition)
395                         ent.contentstransition(ent.watertype, contents);
396         }
397
398         if(contents <= CONTENT_WATER)
399         {
400                 ent.watertype = contents;
401                 ent.waterlevel = 1;
402         }
403         else
404         {
405                 ent.watertype = CONTENT_EMPTY;
406                 ent.waterlevel = (GAMEPLAYFIX_WATERTRANSITION(ent) ? 0 : contents);
407         }
408 }
409
410 void _Movetype_Impact(entity this, entity oth)  // SV_Impact
411 {
412         if(!this && !oth)
413                 return;
414
415         if(this.solid != SOLID_NOT && gettouch(this))
416                 gettouch(this)(this, oth);
417
418         if(oth.solid != SOLID_NOT && gettouch(oth))
419                 gettouch(oth)(oth, this);
420 }
421
422 void _Movetype_LinkEdict_TouchAreaGrid(entity this)  // SV_LinkEdict_TouchAreaGrid
423 {
424         if(this.solid == SOLID_NOT)
425                 return;
426
427     FOREACH_ENTITY_RADIUS(0.5 * (this.absmin + this.absmax), 0.5 * vlen(this.absmax - this.absmin), true, {
428                 if (it.solid == SOLID_TRIGGER && it != this)
429                 if (it.move_nomonsters != MOVE_NOMONSTERS && it.move_nomonsters != MOVE_WORLDONLY)
430                 if (gettouch(it) && boxesoverlap(it.absmin, it.absmax, this.absmin, this.absmax))
431                 {
432                         trace_allsolid = false;
433                         trace_startsolid = false;
434                         trace_fraction = 1;
435                         trace_inwater = false;
436                         trace_inopen = true;
437                         trace_endpos = it.origin;
438                         trace_plane_normal = '0 0 1';
439                         trace_plane_dist = 0;
440                         trace_ent = this;
441                         trace_dpstartcontents = 0;
442                         trace_dphitcontents = 0;
443                         trace_dphitq3surfaceflags = 0;
444                         trace_dphittexturename = string_null;
445
446                         gettouch(it)(it, this);
447                 }
448     });
449 }
450
451 bool autocvar__movetype_debug = false;
452 void _Movetype_LinkEdict(entity this, bool touch_triggers)  // SV_LinkEdict
453 {
454         if(autocvar__movetype_debug)
455         {
456                 vector mi, ma;
457                 if(this.solid == SOLID_BSP)
458                 {
459                         // TODO set the absolute bbox
460                         mi = this.mins;
461                         ma = this.maxs;
462                 }
463                 else
464                 {
465                         mi = this.mins;
466                         ma = this.maxs;
467                 }
468                 mi += this.origin;
469                 ma += this.origin;
470
471                 if(this.flags & FL_ITEM)
472                 {
473                         mi -= '15 15 1';
474                         ma += '15 15 1';
475                 }
476                 else
477                 {
478                         mi -= '1 1 1';
479                         ma += '1 1 1';
480                 }
481
482                 this.absmin = mi;
483                 this.absmax = ma;
484         }
485         else
486         {
487                 setorigin(this, this.origin); // calls SV_LinkEdict
488         #ifdef CSQC
489                 // NOTE: CSQC's version of setorigin doesn't expand
490                 this.absmin -= '1 1 1';
491                 this.absmax += '1 1 1';
492         #endif
493         }
494
495         if(touch_triggers)
496                 _Movetype_LinkEdict_TouchAreaGrid(this);
497 }
498
499 int _Movetype_ContentsMask(entity this)  // SV_GenericHitSuperContentsMask
500 {
501         if(this)
502         {
503                 if(this.dphitcontentsmask)
504                         return this.dphitcontentsmask;
505                 else if(this.solid == SOLID_SLIDEBOX)
506                 {
507                         if(this.flags & FL_MONSTER)
508                                 return DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_MONSTERCLIP;
509                         else
510                                 return DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP;
511                 }
512                 else if(this.solid == SOLID_CORPSE)
513                         return DPCONTENTS_SOLID | DPCONTENTS_BODY;
514                 else if(this.solid == SOLID_TRIGGER)
515                         return DPCONTENTS_SOLID | DPCONTENTS_BODY;
516                 else
517                         return DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
518         }
519         else
520                 return DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
521 }
522
523 entity _Movetype_TestEntityPosition_ent;
524 bool _Movetype_TestEntityPosition(vector ofs)  // SV_TestEntityPosition
525 {
526     entity this = _Movetype_TestEntityPosition_ent;
527         vector org = this.origin + ofs;
528
529         //int cont = this.dphitcontentsmask;
530         //this.dphitcontentsmask = DPCONTENTS_SOLID;
531         tracebox(org, this.mins, this.maxs, this.origin, ((this.move_movetype == MOVETYPE_FLY_WORLDONLY) ? MOVE_WORLDONLY : MOVE_NOMONSTERS), this);
532         //this.dphitcontentsmask = cont;
533         if(trace_dpstartcontents & _Movetype_ContentsMask(this))
534                 return true;
535
536         if(vlen2(trace_endpos - this.origin) >= 0.0001)
537         {
538                 tracebox(trace_endpos, this.mins, this.maxs, trace_endpos, MOVE_NOMONSTERS, this);
539                 if(!trace_startsolid)
540                         this.origin = trace_endpos;
541         }
542         return false;
543 }
544
545 bool _Movetype_TestEntityPosition_Offset(int offset)
546 {
547         // NOTE: expects _Movetype_TestEntityPosition_ent to be set to the correct entity
548         // returns true if stuck
549
550     // start at 2, since the first position has already been checked
551     for(int j = 2; j <= offset; ++j)
552     {
553         if(!_Movetype_TestEntityPosition('0 0 -1' * j))
554                 return false;
555         if(!_Movetype_TestEntityPosition('0 0 1' * j))
556                 return false;
557     }
558
559         return true;
560 }
561
562 int _Movetype_UnstickEntity(entity this)  // SV_UnstickEntity
563 {
564     _Movetype_TestEntityPosition_ent = this;
565         if (!_Movetype_TestEntityPosition(' 0  0  0')) {
566             return UNSTICK_FINE;
567         }
568         #define X(v) if (_Movetype_TestEntityPosition(v))
569         X('0  0  -1') X(' 0  0  1')
570         X('-1  0  0') X(' 1  0  0')
571         X(' 0 -1  0') X(' 0  1  0')
572         X('-1 -1  0') X(' 1 -1  0')
573         X('-1  1  0') X(' 1  1  0')
574         #undef X
575         {
576         if(_Movetype_TestEntityPosition_Offset(rint((this.maxs.z - this.mins.z) * 0.36)))
577         {
578             LOG_DEBUGF("Can't unstick an entity (edict: %d, classname: %s, origin: %s)",
579                 etof(this), this.classname, vtos(this.origin));
580             return UNSTICK_STUCK;
581         }
582         }
583         LOG_DEBUGF("Sucessfully unstuck an entity (edict: %d, classname: %s, origin: %s)",
584                 etof(this), this.classname, vtos(this.origin));
585         _Movetype_LinkEdict(this, false);
586         return UNSTICK_FIXED;
587 }
588
589 void _Movetype_CheckStuck(entity this)  // SV_CheckStuck
590 {
591         int unstick = _Movetype_UnstickEntity(this); // sets test position entity
592         switch(unstick)
593         {
594                 case UNSTICK_FINE:
595                         this.oldorigin = this.origin;
596                         break;
597                 case UNSTICK_FIXED:
598                         break; // already sorted
599                 case UNSTICK_STUCK:
600                         vector offset = this.oldorigin - this.origin;
601                         if(!_Movetype_TestEntityPosition(offset))
602                                 _Movetype_LinkEdict(this, false);
603                         // couldn't unstick, should we warn about this?
604                         break;
605         }
606 }
607
608 vector _Movetype_ClipVelocity(vector vel, vector norm, float f)  // SV_ClipVelocity
609 {
610         vel -= ((vel * norm) * norm) * f;
611
612         if(vel.x > -0.1 && vel.x < 0.1) vel.x = 0;
613         if(vel.y > -0.1 && vel.y < 0.1) vel.y = 0;
614         if(vel.z > -0.1 && vel.z < 0.1) vel.z = 0;
615
616         return vel;
617 }
618
619 void _Movetype_PushEntityTrace(entity this, vector push)
620 {
621         vector end = this.origin + push;
622         int type;
623         if(this.move_nomonsters)
624                 type = max(0, this.move_nomonsters);
625         else if(this.move_movetype == MOVETYPE_FLYMISSILE)
626                 type = MOVE_MISSILE;
627         else if(this.move_movetype == MOVETYPE_FLY_WORLDONLY)
628                 type = MOVE_WORLDONLY;
629         else if(this.solid == SOLID_TRIGGER || this.solid == SOLID_NOT)
630                 type = MOVE_NOMONSTERS;
631         else
632                 type = MOVE_NORMAL;
633
634         tracebox(this.origin, this.mins, this.maxs, end, type, this);
635 }
636
637 bool _Movetype_PushEntity(entity this, vector push, bool failonstartsolid, bool dolink)  // SV_PushEntity
638 {
639         _Movetype_PushEntityTrace(this, push);
640
641         // NOTE: this is a workaround for the QC's lack of a worldstartsolid trace parameter
642         if(trace_startsolid && failonstartsolid)
643         {
644                 int oldtype = this.move_nomonsters;
645                 this.move_nomonsters = MOVE_WORLDONLY;
646                 _Movetype_PushEntityTrace(this, push);
647                 this.move_nomonsters = oldtype;
648                 if(trace_startsolid)
649                         return true;
650         }
651
652         this.origin = trace_endpos;
653
654         vector last_origin = this.origin;
655
656         _Movetype_LinkEdict(this, dolink);
657
658         if((this.solid >= SOLID_TRIGGER && trace_fraction < 1 && (!IS_ONGROUND(this) || this.groundentity != trace_ent)))
659                 _Movetype_Impact(this, trace_ent);
660
661         return (this.origin == last_origin); // false if teleported by touch
662 }
663
664 void _Movetype_Physics_Frame(entity this, float movedt)
665 {
666         this.move_didgravity = -1;
667         switch (this.move_movetype)
668         {
669                 case MOVETYPE_PUSH:
670                 case MOVETYPE_FAKEPUSH:
671                         _Movetype_Physics_Push(this, movedt);
672                         break;
673                 case MOVETYPE_NONE:
674                         break;
675                 case MOVETYPE_FOLLOW:
676                         _Movetype_Physics_Follow(this);
677                         break;
678                 case MOVETYPE_NOCLIP:
679                         _Movetype_CheckWater(this);
680                         this.origin = this.origin + movedt * this.velocity;
681                         this.angles = this.angles + movedt * this.avelocity;
682                         _Movetype_LinkEdict(this, false);
683                         break;
684                 case MOVETYPE_STEP:
685                         _Movetype_Physics_Step(this, movedt);
686                         break;
687                 case MOVETYPE_WALK:
688                         _Movetype_Physics_Walk(this, movedt);
689                         break;
690                 case MOVETYPE_TOSS:
691                 case MOVETYPE_BOUNCE:
692                 case MOVETYPE_BOUNCEMISSILE:
693                 case MOVETYPE_FLYMISSILE:
694                 case MOVETYPE_FLY:
695                 case MOVETYPE_FLY_WORLDONLY:
696                         _Movetype_Physics_Toss(this, movedt);
697                         if(wasfreed(this))
698                                 return;
699                         _Movetype_LinkEdict(this, true);
700                         break;
701                 case MOVETYPE_PHYSICS:
702                         break;
703         }
704 }
705
706 void _Movetype_Physics_ClientFrame(entity this, float movedt)
707 {
708         this.move_didgravity = -1;
709         switch (this.move_movetype)
710         {
711                 case MOVETYPE_PUSH:
712                 case MOVETYPE_FAKEPUSH:
713                         LOG_DEBUG("Physics: Lacking QuakeC support for Push movetype, FIX ME by using engine physics!");
714                         break;
715                 case MOVETYPE_NONE:
716                         break;
717                 case MOVETYPE_FOLLOW:
718                         _Movetype_Physics_Follow(this);
719                         break;
720                 case MOVETYPE_NOCLIP:
721                         _Movetype_CheckWater(this);
722                         this.origin = this.origin + movedt * this.velocity;
723                         this.angles = this.angles + movedt * this.avelocity;
724                         break;
725                 case MOVETYPE_STEP:
726                         if (GAMEPLAYFIX_UNSTICKPLAYERS(this) == 2)
727                                 _Movetype_CheckStuck(this);
728                         _Movetype_Physics_Step(this, movedt);
729                         break;
730                 case MOVETYPE_WALK:
731                 case MOVETYPE_FLY:
732                 case MOVETYPE_FLY_WORLDONLY:
733                         if (movedt > 0 && GAMEPLAYFIX_UNSTICKPLAYERS(this) == 2)
734                                 _Movetype_CheckStuck(this);
735                         _Movetype_Physics_Walk(this, movedt);
736                         break;
737                 case MOVETYPE_TOSS:
738                 case MOVETYPE_BOUNCE:
739                 case MOVETYPE_BOUNCEMISSILE:
740                 case MOVETYPE_FLYMISSILE:
741                         if (GAMEPLAYFIX_UNSTICKPLAYERS(this) == 2)
742                                 _Movetype_CheckStuck(this);
743                         _Movetype_Physics_Toss(this, movedt);
744                         break;
745                 case MOVETYPE_PHYSICS:
746                         break;
747         }
748
749         //_Movetype_CheckVelocity(this);
750
751         _Movetype_LinkEdict(this, true);
752
753         //_Movetype_CheckVelocity(this);
754 }
755
756 void Movetype_Physics_NoMatchTicrate(entity this, float movedt, bool isclient)  // to be run every move frame
757 {
758         bool didmove = (this.move_time != 0);
759         this.move_time = time;
760
761         if(isclient)
762                 _Movetype_Physics_ClientFrame(this, movedt);
763         else
764         {
765                 // this doesn't apply to clients, and only applies to unmatched entities
766                 // don't run think/move on newly spawned projectiles as it messes up
767                 // movement interpolation and rocket trails, and is inconsistent with
768                 // respect to entities spawned in the same frame
769                 // (if an ent spawns a higher numbered ent, it moves in the same frame,
770                 //  but if it spawns a lower numbered ent, it doesn't - this never moves
771                 //  ents in the first frame regardless)
772                 if(!didmove && GAMEPLAYFIX_DELAYPROJECTILES(this) > 0)
773                         return;
774                 _Movetype_Physics_Frame(this, movedt);
775         }
776         if(wasfreed(this))
777                 return;
778
779         setorigin(this, this.origin);
780 }
781
782 void Movetype_Physics_NoMatchServer(entity this)  // optimized
783 {
784         float movedt = time - this.move_time;
785         this.move_time = time;
786
787         _Movetype_Physics_Frame(this, movedt);
788         if(wasfreed(this))
789                 return;
790
791         setorigin(this, this.origin);
792 }
793
794 void Movetype_Physics_MatchServer(entity this, bool sloppy)
795 {
796         Movetype_Physics_MatchTicrate(this, TICRATE, sloppy);
797 }
798
799 // saved .move_*
800 .vector tic_origin;
801 .vector tic_velocity;
802 .int tic_flags;
803 .vector tic_avelocity;
804 .vector tic_angles;
805
806 // saved .*
807 .vector tic_saved_origin;
808 .vector tic_saved_velocity;
809 .int tic_saved_flags;
810 .vector tic_saved_avelocity;
811 .vector tic_saved_angles;
812 void Movetype_Physics_MatchTicrate(entity this, float tr, bool sloppy)  // SV_Physics_Entity
813 {
814         // this hack exists to contain the physics feature
815         // (so entities can place themselves in the world and not need to update .tic_* themselves)
816 #define X(s) \
817         if(this.(s) != this.tic_saved_##s) \
818                 this.tic_##s = this.(s)
819
820         X(origin);
821         X(velocity);
822         X(flags);
823         X(avelocity);
824         X(angles);
825 #undef X
826
827         this.flags = this.tic_flags;
828         this.velocity = this.tic_velocity;
829         setorigin(this, this.tic_origin);
830         this.avelocity = this.tic_avelocity;
831         this.angles = this.tic_angles;
832
833         if(tr <= 0)
834         {
835                 Movetype_Physics_NoMatchServer(this);
836
837                 this.tic_saved_flags = this.tic_flags = this.flags;
838                 this.tic_saved_velocity = this.tic_velocity = this.velocity;
839                 this.tic_saved_origin = this.tic_origin = this.origin;
840                 this.tic_saved_avelocity = this.tic_avelocity = this.avelocity;
841                 this.tic_saved_angles = this.tic_angles = this.angles;
842                 return;
843         }
844
845         float dt = time - this.move_time;
846
847         int n = bound(0, floor(dt / tr), 32); // limit the number of frames to 32 (CL_MAX_USERCMDS, using DP_SMALLMEMORY value for consideration of QC's limitations)
848         dt -= n * tr;
849         this.move_time += n * tr;
850
851         if(!this.move_didgravity)
852                 this.move_didgravity = ((this.move_movetype == MOVETYPE_BOUNCE || this.move_movetype == MOVETYPE_TOSS) && !(this.tic_flags & FL_ONGROUND));
853
854         for (int j = 0; j < n; ++j)
855         {
856                 _Movetype_Physics_Frame(this, tr);
857                 if(wasfreed(this))
858                         return;
859         }
860
861         // update the physics fields
862         this.tic_origin = this.origin;
863         this.tic_velocity = this.velocity;
864         this.tic_avelocity = this.avelocity;
865         this.tic_angles = this.angles;
866         this.tic_flags = this.flags;
867
868         // restore their actual values
869         this.flags = this.tic_saved_flags;
870         this.velocity = this.tic_saved_velocity;
871         setorigin(this, this.tic_saved_origin);
872         //this.avelocity = this.tic_saved_avelocity;
873         this.angles = this.tic_saved_angles;
874
875         this.avelocity = this.tic_avelocity;
876
877         if(dt > 0 && this.move_movetype != MOVETYPE_NONE && !(this.tic_flags & FL_ONGROUND))
878         {
879                 // now continue the move from move_time to time
880                 this.velocity = this.tic_velocity;
881
882                 if(this.move_didgravity > 0)
883                 {
884                         this.velocity_z -= (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE ? 0.5 : 1)
885                             * dt
886                             * ((this.gravity) ? this.gravity : 1)
887                             * PHYS_GRAVITY(this);
888                 }
889
890                 this.angles = this.tic_angles + dt * this.avelocity;
891
892                 if(sloppy || this.move_movetype == MOVETYPE_NOCLIP)
893                 {
894                         setorigin(this, this.tic_origin + dt * this.velocity);
895                 }
896                 else
897                 {
898                         setorigin(this, this.tic_origin);
899                         _Movetype_PushEntityTrace(this, dt * this.velocity);
900                         if(!trace_startsolid)
901                                 setorigin(this, trace_endpos);
902                         else
903                                 setorigin(this, this.tic_saved_origin);
904                 }
905
906                 if(this.move_didgravity > 0 && GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
907                         this.velocity_z -= 0.5 * dt * ((this.gravity) ? this.gravity : 1) * PHYS_GRAVITY(this);
908         }
909         else
910         {
911                 this.velocity = this.tic_velocity;
912                 this.angles = this.tic_angles;
913                 setorigin(this, this.tic_origin);
914         }
915
916         this.flags = this.tic_flags;
917
918         this.tic_saved_flags = this.flags;
919         this.tic_saved_velocity = this.velocity;
920         this.tic_saved_origin = this.origin;
921         this.tic_saved_avelocity = this.avelocity;
922         this.tic_saved_angles = this.angles;
923 }