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