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