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