]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/movetypes.qc
072f9114cb17c8bacf2a429fe48c1dc329b28755
[xonotic/xonotic-data.pk3dir.git] / qcsrc / client / movetypes.qc
1 const float STAT_MOVEFLAGS = 225;
2 const float MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE = 4;
3 #define GRAVITY_UNAFFECTED_BY_TICRATE (getstati(STAT_MOVEFLAGS) & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
4
5 .entity move_groundentity; // FIXME add move_groundnetworkentity?
6 .float move_suspendedinair;
7 .float move_didgravity;
8
9 void _Movetype_CheckVelocity() // SV_CheckVelocity
10 {
11 }
12
13 #if 0
14 int Mod_Q1BSP_SuperContentsFromNativeContents(dp_model_t *model, int nativecontents)
15 {
16         switch(nativecontents)
17         {
18                 case CONTENTS_EMPTY:
19                         return 0;
20                 case CONTENTS_SOLID:
21                         return SUPERCONTENTS_SOLID | SUPERCONTENTS_OPAQUE;
22                 case CONTENTS_WATER:
23                         return SUPERCONTENTS_WATER;
24                 case CONTENTS_SLIME:
25                         return SUPERCONTENTS_SLIME;
26                 case CONTENTS_LAVA:
27                         return SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP;
28                 case CONTENTS_SKY:
29                         return SUPERCONTENTS_SKY | SUPERCONTENTS_NODROP | SUPERCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
30         }
31         return 0;
32 }
33 int Mod_Q1BSP_NativeContentsFromSuperContents(dp_model_t *model, int supercontents)
34 {
35         if (supercontents & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY))
36                 return CONTENTS_SOLID;
37         if (supercontents & SUPERCONTENTS_SKY)
38                 return CONTENTS_SKY;
39         if (supercontents & SUPERCONTENTS_LAVA)
40                 return CONTENTS_LAVA;
41         if (supercontents & SUPERCONTENTS_SLIME)
42                 return CONTENTS_SLIME;
43         if (supercontents & SUPERCONTENTS_WATER)
44                 return CONTENTS_WATER;
45         return CONTENTS_EMPTY;
46 }
47 static qboolean SV_CheckWater (prvm_edict_t *ent)
48 {
49         prvm_prog_t *prog = SVVM_prog;
50         int cont;
51         int nNativeContents;
52         vec3_t point;
53
54         point[0] = PRVM_serveredictvector(ent, origin)[0];
55         point[1] = PRVM_serveredictvector(ent, origin)[1];
56         point[2] = PRVM_serveredictvector(ent, origin)[2] + PRVM_serveredictvector(ent, mins)[2] + 1;
57
58         // DRESK - Support for Entity Contents Transition Event
59         // NOTE: Some logic needed to be slightly re-ordered
60         // to not affect performance and allow for the feature.
61
62         // Acquire Super Contents Prior to Resets
63         cont = SV_PointSuperContents(point);
64         // Acquire Native Contents Here
65         nNativeContents = Mod_Q1BSP_NativeContentsFromSuperContents(NULL, cont);
66
67         // DRESK - Support for Entity Contents Transition Event
68         if(PRVM_serveredictfloat(ent, watertype))
69                 // Entity did NOT Spawn; Check
70                 SV_CheckContentsTransition(ent, nNativeContents);
71
72
73         PRVM_serveredictfloat(ent, waterlevel) = 0;
74         PRVM_serveredictfloat(ent, watertype) = CONTENTS_EMPTY;
75         cont = SV_PointSuperContents(point);
76         if (cont & (SUPERCONTENTS_LIQUIDSMASK))
77         {
78                 PRVM_serveredictfloat(ent, watertype) = nNativeContents;
79                 PRVM_serveredictfloat(ent, waterlevel) = 1;
80                 point[2] = PRVM_serveredictvector(ent, origin)[2] + (PRVM_serveredictvector(ent, mins)[2] + PRVM_serveredictvector(ent, maxs)[2])*0.5;
81                 if (SV_PointSuperContents(point) & (SUPERCONTENTS_LIQUIDSMASK))
82                 {
83                         PRVM_serveredictfloat(ent, waterlevel) = 2;
84                         point[2] = PRVM_serveredictvector(ent, origin)[2] + PRVM_serveredictvector(ent, view_ofs)[2];
85                         if (SV_PointSuperContents(point) & (SUPERCONTENTS_LIQUIDSMASK))
86                                 PRVM_serveredictfloat(ent, waterlevel) = 3;
87                 }
88         }
89
90         return PRVM_serveredictfloat(ent, waterlevel) > 1;
91 }
92 static int SV_CheckContentsTransition(prvm_edict_t *ent, const int nContents)
93 {
94         prvm_prog_t *prog = SVVM_prog;
95         int bValidFunctionCall;
96
97         // Default Valid Function Call to False
98         bValidFunctionCall = false;
99
100         if(PRVM_serveredictfloat(ent, watertype) != nContents)
101         { // Changed Contents
102                 // Acquire Contents Transition Function from QC
103                 if(PRVM_serveredictfunction(ent, contentstransition))
104                 { // Valid Function; Execute
105                         // Assign Valid Function
106                         bValidFunctionCall = true;
107                         // Prepare Parameters (Original Contents, New Contents)
108                         // Original Contents
109                         PRVM_G_FLOAT(OFS_PARM0) = PRVM_serveredictfloat(ent, watertype);
110                         // New Contents
111                         PRVM_G_FLOAT(OFS_PARM1) = nContents;
112                         // Assign Self
113                         PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent);
114                         // Set Time
115                         PRVM_serverglobalfloat(time) = sv.time;
116                         // Execute VM Function
117                         prog->ExecuteProgram(prog, PRVM_serveredictfunction(ent, contentstransition), "contentstransition: NULL function");
118                 }
119         }
120
121         // Return if Function Call was Valid
122         return bValidFunctionCall;
123 }
124
125 static void SV_CheckWaterTransition (prvm_edict_t *ent)
126 {
127         vec3_t entorigin;
128         prvm_prog_t *prog = SVVM_prog;
129         // LordHavoc: bugfixes in this function are keyed to the sv_gameplayfix_bugfixedcheckwatertransition cvar - if this cvar is 0 then all the original bugs should be reenabled for compatibility
130         int cont;
131         VectorCopy(PRVM_serveredictvector(ent, origin), entorigin);
132         cont = Mod_Q1BSP_NativeContentsFromSuperContents(NULL, SV_PointSuperContents(entorigin));
133         if (!PRVM_serveredictfloat(ent, watertype))
134         {
135                 // just spawned here
136                 if (!sv_gameplayfix_fixedcheckwatertransition.integer)
137                 {
138                         PRVM_serveredictfloat(ent, watertype) = cont;
139                         PRVM_serveredictfloat(ent, waterlevel) = 1;
140                         return;
141                 }
142         }
143         // DRESK - Support for Entity Contents Transition Event
144         // NOTE: Call here BEFORE updating the watertype below,
145         // and suppress watersplash sound if a valid function
146         // call was made to allow for custom "splash" sounds.
147         else if( !SV_CheckContentsTransition(ent, cont) )
148         { // Contents Transition Function Invalid; Potentially Play Water Sound
149                 // check if the entity crossed into or out of water
150                 if (sv_sound_watersplash.string && ((PRVM_serveredictfloat(ent, watertype) == CONTENTS_WATER || PRVM_serveredictfloat(ent, watertype) == CONTENTS_SLIME) != (cont == CONTENTS_WATER || cont == CONTENTS_SLIME)))
151                         SV_StartSound (ent, 0, sv_sound_watersplash.string, 255, 1, false, 1.0f);
152         }
153
154         if (cont <= CONTENTS_WATER)
155         {
156                 PRVM_serveredictfloat(ent, watertype) = cont;
157                 PRVM_serveredictfloat(ent, waterlevel) = 1;
158         }
159         else
160         {
161                 PRVM_serveredictfloat(ent, watertype) = CONTENTS_EMPTY;
162                 PRVM_serveredictfloat(ent, waterlevel) = sv_gameplayfix_fixedcheckwatertransition.integer ? 0 : cont;
163         }
164 }
165 #endif
166
167 .float watertype;
168 .float waterlevel;
169 float Mod_Q1BSP_SuperContentsFromNativeContents(float nativecontents)
170 {
171         switch(nativecontents)
172         {
173                 case CONTENT_EMPTY:
174                         return 0;
175                 case CONTENT_SOLID:
176                         return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
177                 case CONTENT_WATER:
178                         return DPCONTENTS_WATER;
179                 case CONTENT_SLIME:
180                         return DPCONTENTS_SLIME;
181                 case CONTENT_LAVA:
182                         return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
183                 case CONTENT_SKY:
184                         return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
185         }
186         return 0;
187 }
188
189 float Mod_Q1BSP_NativeContentsFromSuperContents(float supercontents)
190 {
191         if (supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
192                 return CONTENT_SOLID;
193         if (supercontents & DPCONTENTS_SKY)
194                 return CONTENT_SKY;
195         if (supercontents & DPCONTENTS_LAVA)
196                 return CONTENT_LAVA;
197         if (supercontents & DPCONTENTS_SLIME)
198                 return CONTENT_SLIME;
199         if (supercontents & DPCONTENTS_WATER)
200                 return CONTENT_WATER;
201         return CONTENT_EMPTY;
202 }
203
204 float _Movetype_CheckWater(entity ent) // SV_CheckWater
205 {
206         float contents;
207         float nativecontents;
208         vector point;
209
210         point = ent.move_origin;
211         point_z += (ent.mins_z + 1);
212
213         contents = pointcontents(point);
214         nativecontents = Mod_Q1BSP_NativeContentsFromSuperContents(contents);
215
216         if(ent.watertype)
217         if(ent.watertype != nativecontents)
218         {
219                 print(sprintf("_Movetype_CheckWater(): Original: '%d', New: '%d'\n", ent.watertype, nativecontents));
220                 //ent.contentstransition(ent.watertype, contents);
221         }
222
223         ent.waterlevel = 0;
224         ent.watertype = CONTENT_EMPTY;
225         
226         contents = pointcontents(point);
227         if(contents & DPCONTENTS_LIQUIDSMASK)
228         {
229                 ent.watertype = nativecontents;
230                 ent.waterlevel = 1;
231                 point_y = (ent.origin_y + ((ent.mins_z + ent.maxs_y) * 0.5));
232                 if(pointcontents(point) & DPCONTENTS_LIQUIDSMASK)
233                 {
234                         ent.waterlevel = 2;
235                         point_y = ent.origin_y + ent.view_ofs_y;
236                         if(pointcontents(point) & DPCONTENTS_LIQUIDSMASK)
237                                 ent.waterlevel = 3;
238                 }
239         }
240
241         return (ent.waterlevel > 1);
242 }
243
244 #if 0
245 void _Movetype_CheckContentsTransition(entity ent, float contents)
246 {
247         if(ent.watertype != contents)
248         {
249                 print(sprintf("_Movetype_CheckWaterTransition(): Original: '%d', New: '%d'\n", ent.watertype, contents));
250                 //ent.contentstransition(ent.watertype, contents);
251         }
252 }
253 #endif
254
255 float autocvar_sv_gameplayfix_fixedcheckwatertransition;
256 void _Movetype_CheckWaterTransition(entity ent) // SV_CheckWaterTransition
257 {
258         float contents = Mod_Q1BSP_NativeContentsFromSuperContents(pointcontents(ent.move_origin));
259         
260         if(!ent.watertype)
261         {
262                 // just spawned here
263                 if(!autocvar_sv_gameplayfix_fixedcheckwatertransition)
264                 {
265                         ent.watertype = contents;
266                         ent.waterlevel = 1;
267                         return;
268                 }
269         }
270         //else if(!_Movetype_CheckContentsTransition(ent, contents))
271         //{
272                 //if (sv_sound_watersplash.string && ((PRVM_serveredictfloat(ent, watertype) == CONTENTS_WATER || PRVM_serveredictfloat(ent, watertype) == CONTENTS_SLIME) != (cont == CONTENTS_WATER || cont == CONTENTS_SLIME)))
273                 //      SV_StartSound (ent, 0, sv_sound_watersplash.string, 255, 1, false, 1.0f);
274         //}
275         else if(ent.watertype != contents)
276         {
277                 print(sprintf("_Movetype_CheckWaterTransition(): Original: '%d', New: '%d'\n", ent.watertype, contents));
278                 //ent.contentstransition(ent.watertype, contents);
279         }
280
281         if(contents <= CONTENT_WATER)
282         {
283                 ent.watertype = contents;
284                 ent.waterlevel = 1;
285         }
286         else
287         {
288                 ent.watertype = CONTENT_EMPTY;
289                 ent.waterlevel = (autocvar_sv_gameplayfix_fixedcheckwatertransition ? 0 : contents);
290         }
291 }
292
293 void _Movetype_Impact(entity oth) // SV_Impact
294 {
295         entity oldother, oldself;
296
297         oldself = self;
298         oldother = other;
299
300         if(self.move_touch)
301         {
302                 other = oth;
303
304                 self.move_touch();
305
306                 other = oldother;
307         }
308
309         if(oth.move_touch)
310         {
311                 other = self;
312                 self = oth;
313
314                 self.move_touch();
315
316                 self = oldself;
317                 other = oldother;
318         }
319 }
320
321 void _Movetype_LinkEdict_TouchAreaGrid() // SV_LinkEdict_TouchAreaGrid
322 {
323         entity e, oldself, oldother;
324
325         oldself = self;
326         oldother = other;
327
328         for(e = findradius(0.5 * (self.absmin + self.absmax), 0.5 * vlen(self.absmax - self.absmin)); e; e = e.chain)
329         {
330                 if(e.move_touch)
331                 if(boxesoverlap(e.absmin, e.absmax, oldself.absmin, oldself.absmax))
332                 {
333                         self = e;
334                         other = oldself;
335
336                         trace_allsolid = FALSE;
337                         trace_startsolid = FALSE;
338                         trace_fraction = 1;
339                         trace_inwater = FALSE;
340                         trace_inopen = TRUE;
341                         trace_endpos = e.origin;
342                         trace_plane_normal = '0 0 1';
343                         trace_plane_dist = 0;
344                         trace_ent = oldself;
345
346                         e.move_touch();
347                 }
348         }
349
350         other = oldother;
351         self = oldself;
352 }
353
354 void _Movetype_LinkEdict(float touch_triggers) // SV_LinkEdict
355 {
356         vector mi, ma;
357         if(self.solid == SOLID_BSP)
358         {
359                 // TODO set the absolute bbox
360                 mi = self.mins;
361                 ma = self.maxs;
362         }
363         else
364         {
365                 mi = self.mins;
366                 ma = self.maxs;
367         }
368         mi = mi + self.origin;
369         ma = ma + self.origin;
370
371         if(self.move_flags & FL_ITEM)
372         {
373                 mi_x -= 15;
374                 mi_y -= 15;
375                 mi_z -= 1;
376                 ma_x += 15;
377                 ma_y += 15;
378                 ma_z += 1;
379         }
380         else
381         {
382                 mi_x -= 1;
383                 mi_y -= 1;
384                 mi_z -= 1;
385                 ma_x += 1;
386                 ma_y += 1;
387                 ma_z += 1;
388         }
389
390         self.absmin = mi;
391         self.absmax = ma;
392
393         if(touch_triggers)
394                 _Movetype_LinkEdict_TouchAreaGrid();
395 }
396
397 float _Movetype_TestEntityPosition(vector ofs) // SV_TestEntityPosition
398 {
399         vector org;
400         float cont;
401         org = self.move_origin + ofs;
402
403         cont = self.dphitcontentsmask;
404         self.dphitcontentsmask = DPCONTENTS_SOLID;
405         tracebox(self.move_origin, self.mins, self.maxs, self.move_origin, MOVE_NOMONSTERS, self);
406         self.dphitcontentsmask = cont;
407
408         if(trace_startsolid)
409                 return TRUE;
410
411         if(vlen(trace_endpos - self.move_origin) > 0.0001)
412                 self.move_origin = trace_endpos;
413         return FALSE;
414 }
415
416 float _Movetype_UnstickEntity() // SV_UnstickEntity
417 {
418         if(!_Movetype_TestEntityPosition('0 0 0'))
419                 return TRUE;
420         if(!_Movetype_TestEntityPosition('-1 0 0')) goto success;
421         if(!_Movetype_TestEntityPosition('1 0 0')) goto success;
422         if(!_Movetype_TestEntityPosition('0 -1 0')) goto success;
423         if(!_Movetype_TestEntityPosition('0 1 0')) goto success;
424         if(!_Movetype_TestEntityPosition('-1 -1 0')) goto success;
425         if(!_Movetype_TestEntityPosition('1 -1 0')) goto success;
426         if(!_Movetype_TestEntityPosition('-1 1 0')) goto success;
427         if(!_Movetype_TestEntityPosition('1 1 0')) goto success;
428         float i;
429         for(i = 1; i <= 17; ++i)
430         {
431                 if(!_Movetype_TestEntityPosition('0 0 -1' * i)) goto success;
432                 if(!_Movetype_TestEntityPosition('0 0 1' * i)) goto success;
433         }
434         dprint(sprintf(_("Can't unstick an entity (edict: %d, classname: %s, origin: %s)\n"), num_for_edict(self), self.classname, vtos(self.move_origin)));
435         return FALSE;
436 :success
437         dprint(sprintf(_("Sucessfully unstuck an entity (edict: %d, classname: %s, origin: %s)\n"), num_for_edict(self), self.classname, vtos(self.move_origin)));
438         _Movetype_LinkEdict(TRUE);
439         return TRUE;
440 }
441
442 vector _Movetype_ClipVelocity(vector vel, vector norm, float f) // SV_ClipVelocity
443 {
444         vel = vel - ((vel * norm) * norm) * f;
445
446         if(vel_x > -0.1 && vel_x < 0.1) vel_x = 0;
447         if(vel_y > -0.1 && vel_y < 0.1) vel_y = 0;
448         if(vel_z > -0.1 && vel_z < 0.1) vel_z = 0;
449
450         return vel;
451 }
452
453 void _Movetype_PushEntityTrace(vector push)
454 {
455         vector end;
456         float type;
457
458         end = self.move_origin + push;
459
460         if(self.move_nomonsters)
461                 type = max(0, self.move_nomonsters);
462         else if(self.move_movetype == MOVETYPE_FLYMISSILE)
463                 type = MOVE_MISSILE;
464         else if(self.solid == SOLID_TRIGGER || self.solid == SOLID_NOT)
465                 type = MOVE_NOMONSTERS;
466         else
467                 type = MOVE_NORMAL;
468
469         tracebox(self.move_origin, self.mins, self.maxs, end, type, self);
470 }
471
472 float _Movetype_PushEntity(vector push, float failonstartsolid) // SV_PushEntity
473 {
474         _Movetype_PushEntityTrace(push);
475
476         if(trace_startsolid && failonstartsolid)
477                 return trace_fraction;
478
479         self.move_origin = trace_endpos;
480
481         if(trace_fraction < 1)
482                 if(self.solid >= SOLID_TRIGGER && (!(self.move_flags & FL_ONGROUND) || (self.move_groundentity != trace_ent)))
483                         _Movetype_Impact(trace_ent);
484
485         return trace_fraction;
486 }
487
488 #define MAX_CLIP_PLANES 5
489 void _Movetype_Physics_Toss(float dt) // SV_Physics_Toss
490 {
491         if(self.move_flags & FL_ONGROUND)
492         {
493                 if(self.move_velocity_z >= 1/32)
494                         self.move_flags &= ~FL_ONGROUND;
495                 else if(!self.move_groundentity)
496                         return;
497                 else if(self.move_suspendedinair && wasfreed(self.move_groundentity))
498                 {
499                         self.move_groundentity = world;
500                         return;
501                 }
502         }
503
504         self.move_suspendedinair = FALSE;
505
506         _Movetype_CheckVelocity();
507
508         if(self.move_movetype == MOVETYPE_BOUNCE || self.move_movetype == MOVETYPE_TOSS)
509         {
510                 self.move_didgravity = 1;
511                 if(GRAVITY_UNAFFECTED_BY_TICRATE)
512                 {
513                         if(self.gravity)
514                                 self.move_velocity_z -= 0.5 * dt * self.gravity * getstatf(STAT_MOVEVARS_GRAVITY);
515                         else
516                                 self.move_velocity_z -= 0.5 * dt * getstatf(STAT_MOVEVARS_GRAVITY);
517                 }
518                 else
519                 {
520                         if(self.gravity)
521                                 self.move_velocity_z -= dt * self.gravity * getstatf(STAT_MOVEVARS_GRAVITY);
522                         else
523                                 self.move_velocity_z -= dt * getstatf(STAT_MOVEVARS_GRAVITY);
524                 }
525         }
526
527         self.move_angles = self.move_angles + self.move_avelocity * dt;
528
529         float movetime, bump;
530         movetime = dt;
531         for(bump = 0; bump < MAX_CLIP_PLANES && movetime > 0; ++bump)
532         {
533                 vector move;
534                 move = self.move_velocity * movetime;
535                 _Movetype_PushEntity(move, TRUE);
536                 if(wasfreed(self))
537                         return;
538
539                 if(trace_startsolid)
540                 {
541                         _Movetype_UnstickEntity();
542                         _Movetype_PushEntity(move, FALSE);
543                         if(wasfreed(self))
544                                 return;
545                 }
546
547                 if(trace_fraction == 1)
548                         break;
549
550                 movetime *= 1 - min(1, trace_fraction);
551
552                 if(self.move_movetype == MOVETYPE_BOUNCEMISSILE)
553                 {
554                         self.move_velocity = _Movetype_ClipVelocity(self.move_velocity, trace_plane_normal, 2.0);
555                         self.move_flags &= ~FL_ONGROUND;
556                 }
557                 else if(self.move_movetype == MOVETYPE_BOUNCE)
558                 {
559                         float d, bouncefac, bouncestop;
560
561                         bouncefac = self.move_bounce_factor;     if(!bouncefac)  bouncefac = 0.5;
562                         bouncestop = self.move_bounce_stopspeed; if(!bouncestop) bouncestop = 60 / 800;
563                         if(self.gravity)
564                                 bouncestop *= self.gravity * getstatf(STAT_MOVEVARS_GRAVITY);
565                         else
566                                 bouncestop *= getstatf(STAT_MOVEVARS_GRAVITY);
567
568                         self.move_velocity = _Movetype_ClipVelocity(self.move_velocity, trace_plane_normal, 1 + bouncefac);
569
570                         d = trace_plane_normal * self.move_velocity;
571                         if(trace_plane_normal_z > 0.7 && d < bouncestop && d > -bouncestop)
572                         {
573                                 self.move_flags |= FL_ONGROUND;
574                                 self.move_groundentity = trace_ent;
575                                 self.move_velocity = '0 0 0';
576                                 self.move_avelocity = '0 0 0';
577                         }
578                         else
579                                 self.move_flags &= ~FL_ONGROUND;
580                 }
581                 else
582                 {
583                         self.move_velocity = _Movetype_ClipVelocity(self.move_velocity, trace_plane_normal, 1.0);
584                         if(trace_plane_normal_z > 0.7)
585                         {
586                                 self.move_flags |= FL_ONGROUND;
587                                 self.move_groundentity = trace_ent;
588                                 if(trace_ent.solid == SOLID_BSP)
589                                         self.move_suspendedinair = TRUE;
590                                 self.move_velocity = '0 0 0';
591                                 self.move_avelocity = '0 0 0';
592                         }
593                         else
594                                 self.move_flags &= ~FL_ONGROUND;
595                 }
596
597                 // DP revision 8905 (just, WHY...)
598                 if(self.move_movetype == MOVETYPE_BOUNCEMISSILE)
599                         break;
600
601                 // DP revision 8918 (WHY...)
602                 if(self.move_flags & FL_ONGROUND)
603                         break;
604         }
605
606         if(GRAVITY_UNAFFECTED_BY_TICRATE)
607         if(self.move_didgravity > 0)
608         if(!(self.move_flags & FL_ONGROUND))
609         {
610                 if(self.gravity)
611                         self.move_velocity_z -= 0.5 * dt * self.gravity * getstatf(STAT_MOVEVARS_GRAVITY);
612                 else
613                         self.move_velocity_z -= 0.5 * dt * getstatf(STAT_MOVEVARS_GRAVITY);
614         }
615
616         _Movetype_CheckWaterTransition(self);
617 }
618
619 void _Movetype_Physics_Frame(float movedt)
620 {
621         self.move_didgravity = -1;
622         switch(self.move_movetype)
623         {
624                 case MOVETYPE_PUSH:
625                 case MOVETYPE_FAKEPUSH:
626                         error("SV_Physics_Pusher not implemented");
627                         break;
628                 case MOVETYPE_NONE:
629                         break;
630                 case MOVETYPE_FOLLOW:
631                         error("SV_Physics_Follow not implemented");
632                         break;
633                 case MOVETYPE_NOCLIP:
634                         _Movetype_CheckWater(self);
635                         self.move_origin = self.move_origin + ticrate * self.move_velocity;
636                         self.move_angles = self.move_angles + ticrate * self.move_avelocity;
637                         _Movetype_LinkEdict(FALSE);
638                         break;
639                 case MOVETYPE_STEP:
640                         error("SV_Physics_Step not implemented");
641                         break;
642                 case MOVETYPE_WALK:
643                         error("SV_Physics_Walk not implemented");
644                         break;
645                 case MOVETYPE_TOSS:
646                 case MOVETYPE_BOUNCE:
647                 case MOVETYPE_BOUNCEMISSILE:
648                 case MOVETYPE_FLYMISSILE:
649                 case MOVETYPE_FLY:
650                         _Movetype_Physics_Toss(movedt);
651                         break;
652         }
653 }
654
655 void Movetype_Physics_NoMatchServer() // optimized
656 {
657         float movedt;
658
659         movedt = time - self.move_time;
660         self.move_time = time;
661
662         _Movetype_Physics_Frame(movedt);
663         if(wasfreed(self))
664                 return;
665
666         self.avelocity = self.move_avelocity;
667         self.velocity = self.move_velocity;
668         self.angles = self.move_angles;
669         setorigin(self, self.move_origin);
670 }
671
672 void Movetype_Physics_MatchServer(float sloppy)
673 {
674         Movetype_Physics_MatchTicrate(ticrate, sloppy);
675 }
676
677 void Movetype_Physics_MatchTicrate(float tr, float sloppy) // SV_Physics_Entity
678 {
679         float n, i, dt, movedt;
680
681         if(tr <= 0)
682         {
683                 Movetype_Physics_NoMatchServer();
684                 return;
685         }
686
687         dt = time - self.move_time;
688
689         movedt = tr;
690         n = max(0, floor(dt / tr));
691         dt -= n * tr;
692         self.move_time += n * tr;
693
694         if(!self.move_didgravity)
695                 self.move_didgravity = ((self.move_movetype == MOVETYPE_BOUNCE || self.move_movetype == MOVETYPE_TOSS) && !(self.move_flags & FL_ONGROUND));
696
697         for(i = 0; i < n; ++i)
698         {
699                 _Movetype_Physics_Frame(movedt);
700                 if(wasfreed(self))
701                         return;
702         }
703
704         self.avelocity = self.move_avelocity;
705
706         if(dt > 0 && self.move_movetype != MOVETYPE_NONE && !(self.move_flags & FL_ONGROUND))
707         {
708                 // now continue the move from move_time to time
709                 self.velocity = self.move_velocity;
710
711                 if(self.move_didgravity > 0)
712                 {
713                         if(GRAVITY_UNAFFECTED_BY_TICRATE)
714                         {
715                                 if(self.gravity)
716                                         self.velocity_z -= 0.5 * dt * self.gravity * getstatf(STAT_MOVEVARS_GRAVITY);
717                                 else
718                                         self.velocity_z -= 0.5 * dt * getstatf(STAT_MOVEVARS_GRAVITY);
719                         }
720                         else
721                         {
722                                 if(self.gravity)
723                                         self.velocity_z -= dt * self.gravity * getstatf(STAT_MOVEVARS_GRAVITY);
724                                 else
725                                         self.velocity_z -= dt * getstatf(STAT_MOVEVARS_GRAVITY);
726                         }
727                 }
728
729                 self.angles = self.move_angles + dt * self.avelocity;
730
731                 if(sloppy || self.movetype == MOVETYPE_NOCLIP)
732                 {
733                         setorigin(self, self.move_origin + dt * self.velocity);
734                 }
735                 else
736                 {
737                         _Movetype_PushEntityTrace(dt * self.velocity);
738                         if(!trace_startsolid)
739                                 setorigin(self, trace_endpos);
740                 }
741
742                 if(self.move_didgravity > 0)
743                 {
744                         if(GRAVITY_UNAFFECTED_BY_TICRATE)
745                         {
746                                 if(self.gravity)
747                                         self.velocity_z -= 0.5 * dt * self.gravity * getstatf(STAT_MOVEVARS_GRAVITY);
748                                 else
749                                         self.velocity_z -= 0.5 * dt * getstatf(STAT_MOVEVARS_GRAVITY);
750                         }
751                 }
752         }
753         else
754         {
755                 self.velocity = self.move_velocity;
756                 self.angles = self.move_angles;
757                 setorigin(self, self.move_origin);
758         }
759 }