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