Replace some per-frame physics loops with intrusive lists
[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 entity _Movetype_TestEntityPosition_ent;
417 bool _Movetype_TestEntityPosition(vector ofs)  // SV_TestEntityPosition
418 {
419     entity this = _Movetype_TestEntityPosition_ent;
420         vector org = this.origin + ofs;
421
422         //int cont = this.dphitcontentsmask;
423         //this.dphitcontentsmask = DPCONTENTS_SOLID;
424         tracebox(org, this.mins, this.maxs, org, ((this.move_movetype == MOVETYPE_FLY_WORLDONLY) ? MOVE_WORLDONLY : MOVE_NOMONSTERS), this);
425         //this.dphitcontentsmask = cont;
426
427         if(trace_startsolid)
428                 return true;
429
430         if(vdist(trace_endpos - this.origin, >, 0.0001))
431         {
432                 tracebox(trace_endpos, this.mins, this.maxs, trace_endpos, MOVE_NOMONSTERS, this);
433                 if(!trace_startsolid)
434                         this.origin = trace_endpos;
435         }
436         return false;
437 }
438
439 int _Movetype_UnstickEntity(entity this)  // SV_UnstickEntity
440 {
441     _Movetype_TestEntityPosition_ent = this;
442         if (!_Movetype_TestEntityPosition(' 0  0  0')) {
443             return UNSTICK_FINE;
444         }
445         #define X(v) if (_Movetype_TestEntityPosition(v))
446         X('0  0  -1') X(' 0  0  1')
447         X('-1  0  0') X(' 1  0  0')
448         X(' 0 -1  0') X(' 0  1  0')
449         X('-1 -1  0') X(' 1 -1  0')
450         X('-1  1  0') X(' 1  1  0')
451         #undef X
452         {
453         #define X(i) \
454             if (_Movetype_TestEntityPosition('0 0 -1' * i)) \
455             if (_Movetype_TestEntityPosition('0 0 1' * i))
456         X(2) X(3) X(4) X(5) X(6) X(7) X(8)
457         X(9) X(10) X(11) X(12) X(13) X(14) X(15) X(16)
458         X(17)
459         #undef X
460         {
461             LOG_DEBUGF("Can't unstick an entity (edict: %d, classname: %s, origin: %s)",
462                 etof(this), this.classname, vtos(this.origin));
463             return UNSTICK_STUCK;
464         }
465         }
466         LOG_DEBUGF("Sucessfully unstuck an entity (edict: %d, classname: %s, origin: %s)",
467                 etof(this), this.classname, vtos(this.origin));
468         _Movetype_LinkEdict(this, false);
469         return UNSTICK_FIXED;
470 }
471
472 void _Movetype_CheckStuck(entity this)  // SV_CheckStuck
473 {
474         int unstick = _Movetype_UnstickEntity(this); // sets test position entity
475         switch(unstick)
476         {
477                 case UNSTICK_FINE:
478                         this.oldorigin = this.origin;
479                         break;
480                 case UNSTICK_FIXED:
481                         break; // already sorted
482                 case UNSTICK_STUCK:
483                         vector offset = this.oldorigin - this.origin;
484                         if(!_Movetype_TestEntityPosition(offset))
485                                 _Movetype_LinkEdict(this, false);
486                         // couldn't unstick, should we warn about this?
487                         break;
488         }
489 }
490
491 vector _Movetype_ClipVelocity(vector vel, vector norm, float f)  // SV_ClipVelocity
492 {
493         vel -= ((vel * norm) * norm) * f;
494
495         if(vel.x > -0.1 && vel.x < 0.1) vel.x = 0;
496         if(vel.y > -0.1 && vel.y < 0.1) vel.y = 0;
497         if(vel.z > -0.1 && vel.z < 0.1) vel.z = 0;
498
499         return vel;
500 }
501
502 void _Movetype_PushEntityTrace(entity this, vector push)
503 {
504         vector end = this.origin + push;
505         int type;
506         if(this.move_nomonsters)
507                 type = max(0, this.move_nomonsters);
508         else if(this.move_movetype == MOVETYPE_FLYMISSILE)
509                 type = MOVE_MISSILE;
510         else if(this.move_movetype == MOVETYPE_FLY_WORLDONLY)
511                 type = MOVE_WORLDONLY;
512         else if(this.solid == SOLID_TRIGGER || this.solid == SOLID_NOT)
513                 type = MOVE_NOMONSTERS;
514         else
515                 type = MOVE_NORMAL;
516
517         tracebox(this.origin, this.mins, this.maxs, end, type, this);
518 }
519
520 bool _Movetype_PushEntity(entity this, vector push, bool failonstartsolid, bool dolink)  // SV_PushEntity
521 {
522         _Movetype_PushEntityTrace(this, push);
523
524         // NOTE: this is a workaround for the QC's lack of a worldstartsolid trace parameter
525         if(trace_startsolid && failonstartsolid)
526         {
527                 int oldtype = this.move_nomonsters;
528                 this.move_nomonsters = MOVE_WORLDONLY;
529                 _Movetype_PushEntityTrace(this, push);
530                 this.move_nomonsters = oldtype;
531                 if(trace_startsolid)
532                         return true;
533         }
534
535         this.origin = trace_endpos;
536
537         vector last_origin = this.origin;
538
539         _Movetype_LinkEdict(this, dolink);
540
541         if((this.solid >= SOLID_TRIGGER && trace_fraction < 1 && (!IS_ONGROUND(this) || this.groundentity != trace_ent)))
542                 _Movetype_Impact(this, trace_ent);
543
544         return (this.origin == last_origin); // false if teleported by touch
545 }
546
547
548 .float ltime;
549 .void() blocked;
550
551 void _Movetype_Physics_Frame(entity this, float movedt)
552 {
553         this.move_didgravity = -1;
554         switch (this.move_movetype)
555         {
556                 case MOVETYPE_PUSH:
557                 case MOVETYPE_FAKEPUSH:
558                         LOG_DEBUG("Physics: Lacking QuakeC support for Push movetype, FIX ME by using engine physics!");
559                         break;
560                 case MOVETYPE_NONE:
561                         break;
562                 case MOVETYPE_FOLLOW:
563                         _Movetype_Physics_Follow(this);
564                         break;
565                 case MOVETYPE_NOCLIP:
566                         _Movetype_CheckWater(this);
567                         this.origin = this.origin + movedt * this.velocity;
568                         this.angles = this.angles + movedt * this.avelocity;
569                         _Movetype_LinkEdict(this, false);
570                         break;
571                 case MOVETYPE_STEP:
572                         _Movetype_Physics_Step(this, movedt);
573                         break;
574                 case MOVETYPE_WALK:
575                         _Movetype_Physics_Walk(this, movedt);
576                         break;
577                 case MOVETYPE_TOSS:
578                 case MOVETYPE_BOUNCE:
579                 case MOVETYPE_BOUNCEMISSILE:
580                 case MOVETYPE_FLYMISSILE:
581                 case MOVETYPE_FLY:
582                 case MOVETYPE_FLY_WORLDONLY:
583                         _Movetype_Physics_Toss(this, movedt);
584                         if(wasfreed(this))
585                                 return;
586                         _Movetype_LinkEdict(this, true);
587                         break;
588                 case MOVETYPE_PHYSICS:
589                         break;
590         }
591 }
592
593 void _Movetype_Physics_ClientFrame(entity this, float movedt)
594 {
595         this.move_didgravity = -1;
596         switch (this.move_movetype)
597         {
598                 case MOVETYPE_PUSH:
599                 case MOVETYPE_FAKEPUSH:
600                         LOG_DEBUG("Physics: Lacking QuakeC support for Push movetype, FIX ME by using engine physics!");
601                         break;
602                 case MOVETYPE_NONE:
603                         break;
604                 case MOVETYPE_FOLLOW:
605                         _Movetype_Physics_Follow(this);
606                         break;
607                 case MOVETYPE_NOCLIP:
608                         _Movetype_CheckWater(this);
609                         this.origin = this.origin + movedt * this.velocity;
610                         this.angles = this.angles + movedt * this.avelocity;
611                         break;
612                 case MOVETYPE_STEP:
613                         _Movetype_Physics_Step(this, movedt);
614                         break;
615                 case MOVETYPE_WALK:
616                 case MOVETYPE_FLY:
617                 case MOVETYPE_FLY_WORLDONLY:
618                         _Movetype_Physics_Walk(this, movedt);
619                         break;
620                 case MOVETYPE_TOSS:
621                 case MOVETYPE_BOUNCE:
622                 case MOVETYPE_BOUNCEMISSILE:
623                 case MOVETYPE_FLYMISSILE:
624                         _Movetype_Physics_Toss(this, movedt);
625                         break;
626                 case MOVETYPE_PHYSICS:
627                         break;
628         }
629
630         //_Movetype_CheckVelocity(this);
631
632         _Movetype_LinkEdict(this, true);
633
634         //_Movetype_CheckVelocity(this);
635 }
636
637 void Movetype_Physics_NoMatchTicrate(entity this, float movedt, bool isclient)  // to be run every move frame
638 {
639         this.move_time = time;
640
641         if(isclient)
642                 _Movetype_Physics_ClientFrame(this, movedt);
643         else
644                 _Movetype_Physics_Frame(this, movedt);
645         if(wasfreed(this))
646                 return;
647
648         setorigin(this, this.origin);
649 }
650
651 void Movetype_Physics_NoMatchServer(entity this)  // optimized
652 {
653         float movedt = time - this.move_time;
654         this.move_time = time;
655
656         _Movetype_Physics_Frame(this, movedt);
657         if(wasfreed(this))
658                 return;
659
660         setorigin(this, this.origin);
661 }
662
663 void Movetype_Physics_MatchServer(entity this, bool sloppy)
664 {
665         Movetype_Physics_MatchTicrate(this, TICRATE, sloppy);
666 }
667
668 // saved .move_*
669 .vector tic_origin;
670 .vector tic_velocity;
671 .int tic_flags;
672 .vector tic_avelocity;
673 .vector tic_angles;
674
675 // saved .*
676 .vector tic_saved_origin;
677 .vector tic_saved_velocity;
678 .int tic_saved_flags;
679 .vector tic_saved_avelocity;
680 .vector tic_saved_angles;
681 void Movetype_Physics_MatchTicrate(entity this, float tr, bool sloppy)  // SV_Physics_Entity
682 {
683         // this hack exists to contain the physics feature
684         // (so entities can place themselves in the world and not need to update .tic_* themselves)
685 #define X(s) \
686         if(this.(s) != this.tic_saved_##s) \
687                 this.tic_##s = this.(s)
688
689         X(origin);
690         X(velocity);
691         X(flags);
692         X(avelocity);
693         X(angles);
694 #undef X
695
696         this.flags = this.tic_flags;
697         this.velocity = this.tic_velocity;
698         setorigin(this, this.tic_origin);
699         this.avelocity = this.tic_avelocity;
700         this.angles = this.tic_angles;
701
702         if(tr <= 0)
703         {
704                 Movetype_Physics_NoMatchServer(this);
705
706                 this.tic_saved_flags = this.tic_flags = this.flags;
707                 this.tic_saved_velocity = this.tic_velocity = this.velocity;
708                 this.tic_saved_origin = this.tic_origin = this.origin;
709                 this.tic_saved_avelocity = this.tic_avelocity = this.avelocity;
710                 this.tic_saved_angles = this.tic_angles = this.angles;
711                 return;
712         }
713
714         float dt = time - this.move_time;
715
716         int n = max(0, floor(dt / tr));
717         dt -= n * tr;
718         this.move_time += n * tr;
719
720         if(!this.move_didgravity)
721                 this.move_didgravity = ((this.move_movetype == MOVETYPE_BOUNCE || this.move_movetype == MOVETYPE_TOSS) && !(this.tic_flags & FL_ONGROUND));
722
723         for (int j = 0; j < n; ++j)
724         {
725                 _Movetype_Physics_Frame(this, tr);
726                 if(wasfreed(this))
727                         return;
728         }
729
730         // update the physics fields
731         this.tic_origin = this.origin;
732         this.tic_velocity = this.velocity;
733         this.tic_avelocity = this.avelocity;
734         this.tic_angles = this.angles;
735         this.tic_flags = this.flags;
736
737         // restore their actual values
738         this.flags = this.tic_saved_flags;
739         this.velocity = this.tic_saved_velocity;
740         setorigin(this, this.tic_saved_origin);
741         //this.avelocity = this.tic_saved_avelocity;
742         this.angles = this.tic_saved_angles;
743
744         this.avelocity = this.tic_avelocity;
745
746         if(dt > 0 && this.move_movetype != MOVETYPE_NONE && !(this.tic_flags & FL_ONGROUND))
747         {
748                 // now continue the move from move_time to time
749                 this.velocity = this.tic_velocity;
750
751                 if(this.move_didgravity > 0)
752                 {
753                         this.velocity_z -= (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE ? 0.5 : 1)
754                             * dt
755                             * ((this.gravity) ? this.gravity : 1)
756                             * PHYS_GRAVITY(this);
757                 }
758
759                 this.angles = this.tic_angles + dt * this.avelocity;
760
761                 if(sloppy || this.move_movetype == MOVETYPE_NOCLIP)
762                 {
763                         setorigin(this, this.tic_origin + dt * this.velocity);
764                 }
765                 else
766                 {
767                         setorigin(this, this.tic_origin);
768                         _Movetype_PushEntityTrace(this, dt * this.velocity);
769                         if(!trace_startsolid)
770                                 setorigin(this, trace_endpos);
771                         else
772                                 setorigin(this, this.tic_saved_origin);
773                 }
774
775                 if(this.move_didgravity > 0 && GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
776                         this.velocity_z -= 0.5 * dt * ((this.gravity) ? this.gravity : 1) * PHYS_GRAVITY(this);
777         }
778         else
779         {
780                 this.velocity = this.tic_velocity;
781                 this.angles = this.tic_angles;
782                 setorigin(this, this.tic_origin);
783         }
784
785         this.flags = this.tic_flags;
786
787         this.tic_saved_flags = this.flags;
788         this.tic_saved_velocity = this.velocity;
789         this.tic_saved_origin = this.origin;
790         this.tic_saved_avelocity = this.avelocity;
791         this.tic_saved_angles = this.angles;
792 }