]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/physics.qc
feb799b57740b602523d8911c37b694f7a025912
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / physics.qc
1 // TODO: water prediction
2
3 // TODO: move to a common header
4 #define VLEN2(v) dotproduct(v, v)
5
6 // Client/server mappings
7 #ifdef CSQC
8
9         #define PHYS_INPUT_ANGLES(s)                            input_angles
10         #define PHYS_INPUT_BUTTONS(s)                           input_buttons
11
12         #define PHYS_INPUT_TIMELENGTH                           input_timelength
13
14         #define PHYS_INPUT_MOVEVALUES(s)                        input_movevalues
15
16         #define GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE  moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE
17         #define GAMEPLAYFIX_NOGRAVITYONGROUND                   moveflags & MOVEFLAG_NOGRAVITYONGROUND
18         #define GAMEPLAYFIX_Q2AIRACCELERATE                             moveflags & MOVEFLAG_Q2AIRACCELERATE
19
20         #define IS_DUCKED(s)                                            (s.pmove_flags & PMF_DUCKED)
21         #define SET_DUCKED(s)                                           s.pmove_flags |= PMF_DUCKED
22         #define UNSET_DUCKED(s)                                         s.pmove_flags &= ~PMF_DUCKED
23
24         #define IS_JUMP_HELD(s)                                         (s.pmove_flags & PMF_JUMP_HELD)
25         #define SET_JUMP_HELD(s)                                        s.pmove_flags |= PMF_JUMP_HELD
26         #define UNSET_JUMP_HELD(s)                                      s.pmove_flags &= ~PMF_JUMP_HELD
27
28         #define IS_ONGROUND(s)                                          (s.pmove_flags & PMF_ONGROUND)
29         #define SET_ONGROUND(s)                                         s.pmove_flags |= PMF_ONGROUND
30         #define UNSET_ONGROUND(s)                                       s.pmove_flags &= ~PMF_ONGROUND
31
32         #define PHYS_ACCELERATE                                         getstatf(STAT_MOVEVARS_ACCELERATE)
33         #define PHYS_AIRACCEL_QW                                        getstatf(STAT_MOVEVARS_AIRACCEL_QW)
34         #define PHYS_AIRACCEL_QW_STRETCHFACTOR          getstatf(STAT_MOVEVARS_AIRACCEL_QW_STRETCHFACTOR)
35         #define PHYS_AIRACCEL_SIDEWAYS_FRICTION         getstatf(STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION)
36         #define PHYS_AIRACCELERATE                                      getstatf(STAT_MOVEVARS_AIRACCELERATE)
37         #define PHYS_AIRCONTROL                                         getstatf(STAT_MOVEVARS_AIRCONTROL)
38         #define PHYS_AIRCONTROL_PENALTY                         getstatf(STAT_MOVEVARS_AIRCONTROL_PENALTY)
39         #define PHYS_AIRCONTROL_POWER                           getstatf(STAT_MOVEVARS_AIRCONTROL_POWER)
40         #define PHYS_AIRSPEEDLIMIT_NONQW                        getstatf(STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW)
41         #define PHYS_AIRSTOPACCELERATE                          getstatf(STAT_MOVEVARS_AIRSTOPACCELERATE)
42         #define PHYS_AIRSTRAFEACCEL_QW                          getstatf(STAT_MOVEVARS_AIRSTRAFEACCEL_QW)
43         #define PHYS_AIRSTRAFEACCELERATE                        getstatf(STAT_MOVEVARS_AIRSTRAFEACCELERATE)
44         #define PHYS_EDGEFRICTION                                       getstatf(STAT_MOVEVARS_EDGEFRICTION)
45         #define PHYS_ENTGRAVITY(s)                                      getstatf(STAT_MOVEVARS_ENTGRAVITY)
46         #define PHYS_FRICTION                                           getstatf(STAT_MOVEVARS_FRICTION)
47         #define PHYS_GRAVITY                                            getstatf(STAT_MOVEVARS_GRAVITY)
48         #define PHYS_JUMPVELOCITY                                       getstatf(STAT_MOVEVARS_JUMPVELOCITY)
49         #define PHYS_MAXAIRSPEED                                        getstatf(STAT_MOVEVARS_MAXAIRSPEED)
50         #define PHYS_MAXAIRSTRAFESPEED                          getstatf(STAT_MOVEVARS_MAXAIRSTRAFESPEED)
51         #define PHYS_MAXSPEED                                           getstatf(STAT_MOVEVARS_MAXSPEED)
52         #define PHYS_STEPHEIGHT                                         getstatf(STAT_MOVEVARS_STEPHEIGHT)
53         #define PHYS_STOPSPEED                                          getstatf(STAT_MOVEVARS_STOPSPEED)
54         #define PHYS_WARSOWBUNNY_ACCEL                          getstatf(STAT_MOVEVARS_WARSOWBUNNY_ACCEL)
55         #define PHYS_WARSOWBUNNY_BACKTOSIDERATIO        getstatf(STAT_MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO)
56         #define PHYS_WARSOWBUNNY_AIRFORWARDACCEL        getstatf(STAT_MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL)
57         #define PHYS_WARSOWBUNNY_TOPSPEED                       getstatf(STAT_MOVEVARS_WARSOWBUNNY_TOPSPEED)
58         #define PHYS_WARSOWBUNNY_TURNACCEL                      getstatf(STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL)
59
60 #elif defined(SVQC)
61
62         #define PHYS_INPUT_ANGLES(s)                            s.v_angle
63         // FIXME
64         #define PHYS_INPUT_BUTTONS(s)                           0
65
66         #define PHYS_INPUT_TIMELENGTH                           frametime
67
68         #define PHYS_INPUT_MOVEVALUES(s)                        s.movement
69
70         #define GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE  autocvar_sv_gameplayfix_gravityunaffectedbyticrate
71         #define GAMEPLAYFIX_NOGRAVITYONGROUND                   cvar("sv_gameplayfix_nogravityonground")
72         #define GAMEPLAYFIX_Q2AIRACCELERATE                             autocvar_sv_gameplayfix_q2airaccelerate
73
74         #define IS_DUCKED(s)                                            s.crouch
75         #define SET_DUCKED(s)                                           s.crouch = TRUE
76         #define UNSET_DUCKED(s)                                         s.crouch = FALSE
77
78         #define IS_JUMP_HELD(s)                                         (s.flags & FL_JUMPRELEASED == 0)
79         #define SET_JUMP_HELD(s)                                        s.flags &= ~FL_JUMPRELEASED
80         #define UNSET_JUMP_HELD(s)                                      s.flags |= FL_JUMPRELEASED
81
82         #define IS_ONGROUND(s)                                          (s.flags & FL_ONGROUND)
83         #define SET_ONGROUND(s)                                         s.flags |= FL_ONGROUND
84         #define UNSET_ONGROUND(s)                                       s.flags &= ~FL_ONGROUND
85
86         #define PHYS_ACCELERATE                                         autocvar_sv_accelerate
87         #define PHYS_AIRACCEL_QW                                        autocvar_sv_airaccel_qw
88         #define PHYS_AIRACCEL_QW_STRETCHFACTOR          autocvar_sv_airaccel_qw_stretchfactor
89         #define PHYS_AIRACCEL_SIDEWAYS_FRICTION         autocvar_sv_airaccel_sideways_friction
90         #define PHYS_AIRACCELERATE                                      autocvar_sv_airaccelerate
91         #define PHYS_AIRCONTROL                                         autocvar_sv_aircontrol
92         #define PHYS_AIRCONTROL_PENALTY                         autocvar_sv_aircontrol_penalty
93         #define PHYS_AIRCONTROL_POWER                           autocvar_sv_aircontrol_power
94         #define PHYS_AIRSPEEDLIMIT_NONQW                        autocvar_sv_airspeedlimit_nonqw
95         #define PHYS_AIRSTOPACCELERATE                          autocvar_sv_airstopaccelerate
96         #define PHYS_AIRSTRAFEACCEL_QW                          autocvar_sv_airstrafeaccel_qw
97         #define PHYS_AIRSTRAFEACCELERATE                        autocvar_sv_airstrafeaccelerate
98         #define PHYS_EDGEFRICTION                                       1
99         #define PHYS_ENTGRAVITY(s)                                      s.gravity
100         #define PHYS_FRICTION                                           autocvar_sv_friction
101         #define PHYS_GRAVITY                                            autocvar_sv_gravity
102         #define PHYS_JUMPVELOCITY                                       autocvar_sv_jumpvelocity
103         #define PHYS_MAXAIRSPEED                                        autocvar_sv_maxairspeed
104         #define PHYS_MAXAIRSTRAFESPEED                          autocvar_sv_maxairstrafespeed
105         #define PHYS_MAXSPEED                                           autocvar_sv_maxspeed
106         #define PHYS_STEPHEIGHT                                         autocvar_sv_stepheight
107         #define PHYS_STOPSPEED                                          autocvar_sv_stopspeed
108         #define PHYS_WARSOWBUNNY_ACCEL                          autocvar_sv_warsowbunny_accel
109         #define PHYS_WARSOWBUNNY_BACKTOSIDERATIO        autocvar_sv_warsowbunny_backtosideratio
110         #define PHYS_WARSOWBUNNY_AIRFORWARDACCEL        autocvar_sv_warsowbunny_airforwardaccel
111         #define PHYS_WARSOWBUNNY_TOPSPEED                       autocvar_sv_warsowbunny_topspeed
112         #define PHYS_WARSOWBUNNY_TURNACCEL                      autocvar_sv_warsowbunny_turnaccel
113
114 #endif
115
116 float IsMoveInDirection(vector mv, float angle) // key mix factor
117 {
118         if(mv_x == 0 && mv_y == 0)
119                 return 0; // avoid division by zero
120         angle -= RAD2DEG * atan2(mv_y, mv_x);
121         angle = remainder(angle, 360) / 45;
122         if(angle >  1)
123                 return 0;
124         if(angle < -1)
125                 return 0;
126         return 1 - fabs(angle);
127 }
128
129 float GeomLerp(float a, float lerp, float b)
130 {
131         if(a == 0)
132         {
133                 if(lerp < 1)
134                         return 0;
135                 else
136                         return b;
137         }
138         if(b == 0)
139         {
140                 if(lerp > 0)
141                         return 0;
142                 else
143                         return a;
144         }
145         return a * pow(fabs(b / a), lerp);
146 }
147
148 float pmove_waterjumptime; // weird engine flag we shouldn't really use but have to for now
149
150 const float unstick_count = 27;
151 vector unstick_offsets[unstick_count] =
152 {
153 // 1 no nudge (just return the original if this test passes)
154         '0.000   0.000  0.000',
155 // 6 simple nudges
156         ' 0.000  0.000  0.125', '0.000  0.000 -0.125',
157         '-0.125  0.000  0.000', '0.125  0.000  0.000',
158         ' 0.000 -0.125  0.000', '0.000  0.125  0.000',
159 // 4 diagonal flat nudges
160         '-0.125 -0.125  0.000', '0.125 -0.125  0.000',
161         '-0.125  0.125  0.000', '0.125  0.125  0.000',
162 // 8 diagonal upward nudges
163         '-0.125  0.000  0.125', '0.125  0.000  0.125',
164         ' 0.000 -0.125  0.125', '0.000  0.125  0.125',
165         '-0.125 -0.125  0.125', '0.125 -0.125  0.125',
166         '-0.125  0.125  0.125', '0.125  0.125  0.125',
167 // 8 diagonal downward nudges
168         '-0.125  0.000 -0.125', '0.125  0.000 -0.125',
169         ' 0.000 -0.125 -0.125', '0.000  0.125 -0.125',
170         '-0.125 -0.125 -0.125', '0.125 -0.125 -0.125',
171         '-0.125  0.125 -0.125', '0.125  0.125 -0.125',
172 };
173
174 void CSQC_ClientMovement_Unstick(entity s)
175 {
176         float i;
177         vector neworigin;
178         for (i = 0; i < unstick_count; i++)
179         {
180                 neworigin = unstick_offsets[i] + s.origin;
181                 tracebox(neworigin, PL_CROUCH_MIN, PL_CROUCH_MAX, neworigin, MOVE_NORMAL, s);
182                 if (!trace_startsolid)
183                 {
184                         s.origin = neworigin;
185                         return;// true;
186                 }
187         }
188 }
189
190 void CSQC_ClientMovement_UpdateStatus(entity s)
191 {
192         float f;
193         vector origin1, origin2;
194
195         // make sure player is not stuck
196         CSQC_ClientMovement_Unstick(s);
197
198         // set crouched
199         if (PHYS_INPUT_BUTTONS(s) & 16)
200         {
201                 // wants to crouch, this always works..
202                 if (!IS_DUCKED(s))
203                         SET_DUCKED(s);
204         }
205         else
206         {
207                 // wants to stand, if currently crouching we need to check for a
208                 // low ceiling first
209                 if (IS_DUCKED(s))
210                 {
211                         tracebox(s.origin, PL_MIN, PL_MAX, s.origin, MOVE_NORMAL, s);
212                         if (!trace_startsolid)
213                                 UNSET_DUCKED(s);
214                 }
215         }
216         if (IS_DUCKED(s))
217         {
218                 s.mins = PL_CROUCH_MIN;
219                 s.maxs = PL_CROUCH_MAX;
220         }
221         else
222         {
223                 s.mins = PL_MIN;
224                 s.maxs = PL_MAX;
225         }
226
227         // set onground
228         origin1 = s.origin;
229         origin1_z += 1;
230         origin2 = s.origin;
231     origin2_z -= 1; // -2 causes clientside doublejump bug at above 150fps, raising that to 300fps :)
232
233         tracebox(origin1, s.mins, s.maxs, origin2, MOVE_NORMAL, s);
234         if(trace_fraction < 1 && trace_plane_normal_z > 0.7)
235         {
236                 SET_ONGROUND(s);
237
238                 // this code actually "predicts" an impact; so let's clip velocity first
239                 f = dotproduct(s.velocity, trace_plane_normal);
240                 if(f < 0) // only if moving downwards actually
241                         s.velocity -= f * trace_plane_normal;
242         }
243         else
244                 UNSET_ONGROUND(s);
245
246         // set watertype/waterlevel
247         origin1 = s.origin;
248         origin1_z += s.mins_z + 1;
249         s.waterlevel = WATERLEVEL_NONE;
250         // TODO: convert
251 //      s.watertype = CL_TracePoint(origin1, MOVE_NOMONSTERS, s, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK;
252 //      if (s.watertype)
253 //      {
254 //              s.waterlevel = WATERLEVEL_WETFEET;
255 //              origin1[2] = s.origin[2] + (s.mins[2] + s.maxs[2]) * 0.5f;
256 //              if (CL_TracePoint(origin1, MOVE_NOMONSTERS, s, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK)
257 //              {
258 //                      s.waterlevel = WATERLEVEL_SWIMMING;
259 //                      origin1[2] = s.origin[2] + 22;
260 //                      if (CL_TracePoint(origin1, MOVE_NOMONSTERS, s, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK)
261 //                              s.waterlevel = WATERLEVEL_SUBMERGED;
262 //              }
263 //      }
264 //
265 //      // water jump prediction
266 //      if (IS_ONGROUND(s) || s.velocity_z <= 0 || pmove_waterjumptime <= 0)
267 //              pmove_waterjumptime = 0;
268 }
269
270 void CSQC_ClientMovement_Move(entity s)
271 {
272         float bump;
273         float t;
274         float f;
275         vector neworigin;
276         vector currentorigin2;
277         vector neworigin2;
278         vector primalvelocity;
279         float old_trace1_fraction;
280         vector old_trace1_endpos;
281         vector old_trace1_plane_normal;
282         float old_trace2_fraction;
283         vector old_trace2_plane_normal;
284         CSQC_ClientMovement_UpdateStatus(s);
285         primalvelocity = s.velocity;
286         for (bump = 0, t = PHYS_INPUT_TIMELENGTH; bump < 8 && VLEN2(s.velocity) > 0; bump++)
287         {
288                 neworigin = s.origin + t * s.velocity;
289                 tracebox(s.origin, s.mins, s.maxs, neworigin, MOVE_NORMAL, s);
290                 old_trace1_fraction = trace_fraction;
291                 old_trace1_endpos = trace_endpos;
292                 old_trace1_plane_normal = trace_plane_normal;
293                 if (trace_fraction < 1 && trace_plane_normal_z == 0)
294                 {
295                         // may be a step or wall, try stepping up
296                         // first move forward at a higher level
297                         currentorigin2 = s.origin;
298                         currentorigin2_z += PHYS_STEPHEIGHT;
299                         neworigin2 = neworigin;
300                         neworigin2_z = s.origin_z + PHYS_STEPHEIGHT;
301                         tracebox(currentorigin2, s.mins, s.maxs, neworigin2, MOVE_NORMAL, s);
302                         if (!trace_startsolid)
303                         {
304                                 // then move down from there
305                                 currentorigin2 = trace_endpos;
306                                 neworigin2 = trace_endpos;
307                                 neworigin2_z = s.origin_z;
308                                 old_trace2_fraction = trace_fraction;
309                                 old_trace2_plane_normal = trace_plane_normal;
310                                 tracebox(currentorigin2, s.mins, s.maxs, neworigin2, MOVE_NORMAL, s);
311                                 //Con_Printf("%f %f %f %f : %f %f %f %f : %f %f %f %f\n", trace.fraction, trace.endpos[0], trace.endpos[1], trace.endpos[2], trace2.fraction, trace2.endpos[0], trace2.endpos[1], trace2.endpos[2], trace3.fraction, trace3.endpos[0], trace3.endpos[1], trace3.endpos[2]);
312                                 // accept the new trace if it made some progress
313                                 if (fabs(trace_endpos_x - old_trace1_endpos_x) >= 0.03125 || fabs(trace_endpos_y - old_trace1_endpos_y) >= 0.03125)
314                                 {
315                                         trace_fraction = old_trace2_fraction;
316                                         trace_endpos = trace_endpos;
317                                         trace_plane_normal = old_trace2_plane_normal;
318                                 }
319                                 else
320                                 {
321                                         trace_fraction = old_trace1_fraction;
322                                         trace_endpos = old_trace1_endpos;
323                                         trace_plane_normal = old_trace1_plane_normal;
324                                 }
325                         }
326                 }
327
328                 // check if it moved at all
329                 if (trace_fraction >= 0.001)
330                         s.origin = trace_endpos;
331
332                 // check if it moved all the way
333                 if (trace_fraction == 1)
334                         break;
335
336                 // this is only really needed for nogravityonground combined with gravityunaffectedbyticrate
337                 // <LordHavoc> I'm pretty sure I commented it out solely because it seemed redundant
338                 // this got commented out in a change that supposedly makes the code match QW better
339                 // so if this is broken, maybe put it in an if(cls.protocol != PROTOCOL_QUAKEWORLD) block
340                 if (trace_plane_normal_z > 0.7)
341                         SET_ONGROUND(s);
342
343                 t -= t * trace_fraction;
344
345                 f = dotproduct(s.velocity, trace_plane_normal);
346                 s.velocity -= f * trace_plane_normal;
347         }
348         if (pmove_waterjumptime > 0)
349                 s.velocity = primalvelocity;
350 }
351
352 void CSQC_ClientMovement_Physics_CPM_PM_Aircontrol(entity s, vector wishdir, float wishspeed)
353 {
354         float zspeed, xyspeed, dot, k;
355
356 #if 0
357         // this doesn't play well with analog input
358         if(s.movement_x == 0 || s.movement_y != 0)
359                 return; // can't control movement if not moving forward or backward
360         k = 32;
361 #else
362         k = 32 * (2 * IsMoveInDirection(PHYS_INPUT_MOVEVALUES(s), 0) - 1);
363         if(k <= 0)
364                 return;
365 #endif
366
367         k *= bound(0, wishspeed / PHYS_MAXAIRSPEED, 1);
368
369         zspeed = s.velocity_z;
370         s.velocity_z = 0;
371         xyspeed = vlen(s.velocity); s.velocity = normalize(s.velocity);
372
373         dot = s.velocity * wishdir;
374
375         if(dot > 0) // we can't change direction while slowing down
376         {
377                 k *= pow(dot, PHYS_AIRCONTROL_POWER)*PHYS_INPUT_TIMELENGTH;
378                 xyspeed = max(0, xyspeed - PHYS_AIRCONTROL_PENALTY * sqrt(max(0, 1 - dot*dot)) * k/32);
379                 k *= PHYS_AIRCONTROL;
380                 s.velocity = normalize(s.velocity * xyspeed + wishdir * k);
381         }
382
383         s.velocity = s.velocity * xyspeed;
384         s.velocity_z = zspeed;
385 }
386
387 float CSQC_ClientMovement_Physics_AdjustAirAccelQW(float accelqw, float factor)
388 {
389         return copysign(bound(0.000001, 1 - (1 - fabs(accelqw)) * factor, 1), accelqw);
390 }
391
392 void CSQC_ClientMovement_Physics_PM_Accelerate(entity s, vector wishdir, float wishspeed, float wishspeed0, float accel, float accelqw, float stretchfactor, float sidefric, float speedlimit)
393 {
394         float vel_straight;
395         float vel_z;
396         vector vel_perpend;
397         float step;
398         vector vel_xy;
399         float vel_xy_current;
400         float vel_xy_backward, vel_xy_forward;
401         float speedclamp;
402
403         if(stretchfactor > 0)
404                 speedclamp = stretchfactor;
405         else if(accelqw < 0)
406                 speedclamp = 1;
407         else
408                 speedclamp = -1; // no clamping
409
410         if(accelqw < 0)
411                 accelqw = -accelqw;
412
413         if(GAMEPLAYFIX_Q2AIRACCELERATE)
414                 wishspeed0 = wishspeed; // don't need to emulate this Q1 bug
415
416         vel_straight = dotproduct(s.velocity, wishdir);
417         vel_z = s.velocity_z;
418         vel_xy = s.velocity;
419         vel_xy_z -= vel_z;
420         vel_perpend = vel_xy - vel_straight * wishdir;
421
422         step = accel * PHYS_INPUT_TIMELENGTH * wishspeed0;
423
424         vel_xy_current  = vlen(vel_xy);
425         if(speedlimit > 0)
426                 accelqw = CSQC_ClientMovement_Physics_AdjustAirAccelQW(accelqw, (speedlimit - bound(wishspeed, vel_xy_current, speedlimit)) / max(1, speedlimit - wishspeed));
427         vel_xy_forward  = vel_xy_current + bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw);
428         vel_xy_backward = vel_xy_current - bound(0, wishspeed + vel_xy_current, step) * accelqw - step * (1 - accelqw);
429         if(vel_xy_backward < 0)
430                 vel_xy_backward = 0; // not that it REALLY occurs that this would cause wrong behaviour afterwards
431
432         vel_straight    = vel_straight   + bound(0, wishspeed - vel_straight,   step) * accelqw + step * (1 - accelqw);
433
434         if(sidefric < 0 && VLEN2(vel_perpend))
435                 // negative: only apply so much sideways friction to stay below the speed you could get by "braking"
436         {
437                 float f, fmin;
438                 f = max(0, 1 + PHYS_INPUT_TIMELENGTH * wishspeed * sidefric);
439                 fmin = (vel_xy_backward*vel_xy_backward - vel_straight*vel_straight) / VLEN2(vel_perpend);
440                 // assume: fmin > 1
441                 // vel_xy_backward*vel_xy_backward - vel_straight*vel_straight > vel_perpend*vel_perpend
442                 // vel_xy_backward*vel_xy_backward > vel_straight*vel_straight + vel_perpend*vel_perpend
443                 // vel_xy_backward*vel_xy_backward > vel_xy * vel_xy
444                 // obviously, this cannot be
445                 if(fmin <= 0)
446                         vel_perpend *= f;
447                 else
448                 {
449                         fmin = sqrt(fmin);
450                         vel_perpend *= max(fmin, f);
451                 }
452         }
453         else
454                 vel_perpend *= max(0, 1 - PHYS_INPUT_TIMELENGTH * wishspeed * sidefric);
455
456         s.velocity = vel_perpend + vel_straight * wishdir;
457
458         if(speedclamp >= 0)
459         {
460                 float vel_xy_preclamp;
461                 vel_xy_preclamp = vlen(s.velocity);
462                 if(vel_xy_preclamp > 0) // prevent division by zero
463                 {
464                         vel_xy_current += (vel_xy_forward - vel_xy_current) * speedclamp;
465                         if(vel_xy_current < vel_xy_preclamp)
466                                 s.velocity *= (vel_xy_current / vel_xy_preclamp);
467                 }
468         }
469
470         s.velocity_z += vel_z;
471 }
472
473 void CSQC_ClientMovement_Physics_PM_AirAccelerate(entity s, vector wishdir, float wishspeed)
474 {
475         vector curvel, wishvel, acceldir, curdir;
476         float addspeed, accelspeed, curspeed, f;
477         float dot;
478
479         if(wishspeed == 0)
480                 return;
481
482         curvel = s.velocity;
483         curvel_z = 0;
484         curspeed = vlen(curvel);
485
486         if(wishspeed > curspeed * 1.01)
487         {
488                 wishspeed = min(wishspeed, curspeed + PHYS_WARSOWBUNNY_AIRFORWARDACCEL * PHYS_MAXSPEED * PHYS_INPUT_TIMELENGTH);
489         }
490         else
491         {
492                 f = max(0, (PHYS_WARSOWBUNNY_TOPSPEED - curspeed) / (PHYS_WARSOWBUNNY_TOPSPEED - PHYS_MAXSPEED));
493                 wishspeed = max(curspeed, PHYS_WARSOWBUNNY_ACCEL) + PHYS_WARSOWBUNNY_ACCEL * f * PHYS_WARSOWBUNNY_ACCEL * PHYS_INPUT_TIMELENGTH;
494         }
495         wishvel = wishdir * wishspeed;
496         acceldir = wishvel - curvel;
497         addspeed = vlen(acceldir);
498         acceldir = normalize(acceldir);
499
500         accelspeed = min(addspeed, PHYS_WARSOWBUNNY_TURNACCEL * PHYS_WARSOWBUNNY_ACCEL * PHYS_INPUT_TIMELENGTH);
501
502         if(PHYS_WARSOWBUNNY_BACKTOSIDERATIO < 1)
503         {
504                 curdir = normalize(curvel);
505                 dot = acceldir * curdir;
506                 if(dot < 0)
507                         acceldir = acceldir - (1 - PHYS_WARSOWBUNNY_BACKTOSIDERATIO) * dot * curdir;
508         }
509
510         s.velocity += accelspeed * acceldir;
511 }
512
513 void CSQC_ClientMovement_Physics_Walk(entity s)
514 {
515         float friction;
516         float wishspeed;
517         float addspeed;
518         float accelspeed;
519         float f;
520         float g;
521         vector wishvel;
522         vector wishdir;
523         vector yawangles;
524
525         // jump if on ground with jump button pressed but only if it has been
526         // released at least once since the last jump
527         if (PHYS_INPUT_BUTTONS(s) & 2)
528         {
529                 if (IS_ONGROUND(s) && (!IS_JUMP_HELD(s) || !cvar("cl_movement_track_canjump")))
530                 {
531                         s.velocity_z += PHYS_JUMPVELOCITY;
532                         UNSET_ONGROUND(s);
533                         SET_JUMP_HELD(s); // canjump = false
534                 }
535         }
536         else
537                 UNSET_JUMP_HELD(s); // canjump = true
538
539         // calculate movement vector
540         yawangles = '0 0 0';
541         yawangles_y = PHYS_INPUT_ANGLES(s)_y;
542         makevectors(yawangles);
543         wishvel = PHYS_INPUT_MOVEVALUES(s)_x * v_forward + PHYS_INPUT_MOVEVALUES(s)_y * v_right;
544
545         // split wishvel into wishspeed and wishdir
546         wishspeed = vlen(wishvel);
547         if (wishspeed)
548                 wishdir = wishvel / wishspeed;
549         else
550                 wishdir = '0 0 0';
551         // check if onground
552         if (IS_ONGROUND(s))
553         {
554                 wishspeed = min(wishspeed, PHYS_MAXSPEED);
555                 if (IS_DUCKED(s))
556                         wishspeed *= 0.5;
557
558                 // apply edge friction
559                 f = sqrt(s.velocity_x * s.velocity_x + s.velocity_y * s.velocity_y);
560                 if (f > 0)
561                 {
562                         friction = PHYS_FRICTION;
563                         if (PHYS_EDGEFRICTION != 1)
564                         {
565                                 vector neworigin2;
566                                 vector neworigin3;
567                                 // note: QW uses the full player box for the trace, and yet still
568                                 // uses s.origin_z + s.mins_z, which is clearly an bug, but
569                                 // this mimics it for compatibility
570                                 neworigin2 = s.origin;
571                                 neworigin2_x += s.velocity_x*(16/f);
572                                 neworigin2_y += s.velocity_y*(16/f);
573                                 neworigin2_z += s.mins_z;
574                                 neworigin3 = neworigin2;
575                                 neworigin3_z -= 34;
576                                 traceline(neworigin2, neworigin3, MOVE_NORMAL, s);
577                                 if (trace_fraction == 1 && !trace_startsolid)
578                                         friction *= PHYS_EDGEFRICTION;
579                         }
580                         // apply ground friction
581                         f = 1 - PHYS_INPUT_TIMELENGTH * friction * ((f < PHYS_STOPSPEED) ? (PHYS_STOPSPEED / f) : 1);
582                         f = max(f, 0);
583                         s.velocity *= f;
584                 }
585                 addspeed = wishspeed - dotproduct(s.velocity, wishdir);
586                 if (addspeed > 0)
587                 {
588                         accelspeed = min(PHYS_ACCELERATE * PHYS_INPUT_TIMELENGTH * wishspeed, addspeed);
589                         s.velocity += accelspeed * wishdir;
590                 }
591                 g = PHYS_GRAVITY * PHYS_ENTGRAVITY(s) * PHYS_INPUT_TIMELENGTH;
592                 if(!(GAMEPLAYFIX_NOGRAVITYONGROUND))
593                 {
594                         if(GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
595                                 s.velocity_z -= g * 0.5;
596                         else
597                                 s.velocity_z -= g;
598                 }
599                 if (VLEN2(s.velocity))
600                         CSQC_ClientMovement_Move(s);
601                 if(!(GAMEPLAYFIX_NOGRAVITYONGROUND) || !IS_ONGROUND(s))
602                 {
603                         if(GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
604                                 s.velocity_z -= g * 0.5;
605                 }
606         }
607         else
608         {
609                 if (pmove_waterjumptime <= 0)
610                 {
611                         // apply air speed limit
612                         float accel, wishspeed0, wishspeed2, accelqw, strafity;
613                         float accelerating;
614
615                         accelqw = PHYS_AIRACCEL_QW;
616                         wishspeed0 = wishspeed;
617                         wishspeed = min(wishspeed, PHYS_MAXAIRSPEED);
618                         if (IS_DUCKED(s))
619                                 wishspeed *= 0.5;
620                         accel = PHYS_AIRACCELERATE;
621
622                         accelerating = (dotproduct(s.velocity, wishdir) > 0);
623                         wishspeed2 = wishspeed;
624
625                         // CPM: air control
626                         if(PHYS_AIRSTOPACCELERATE != 0)
627                         {
628                                 vector curdir;
629                                 curdir_x = s.velocity_x;
630                                 curdir_y = s.velocity_y;
631                                 curdir_z = 0;
632                                 curdir = normalize(curdir);
633                                 accel = accel + (PHYS_AIRSTOPACCELERATE - accel) * max(0, -dotproduct(curdir, wishdir));
634                         }
635                         strafity = IsMoveInDirection(PHYS_INPUT_MOVEVALUES(s), -90) + IsMoveInDirection(PHYS_INPUT_MOVEVALUES(s), +90); // if one is nonzero, other is always zero
636                         if(PHYS_MAXAIRSTRAFESPEED)
637                                 wishspeed = min(wishspeed, GeomLerp(PHYS_MAXAIRSPEED, strafity, PHYS_MAXAIRSTRAFESPEED));
638                         if(PHYS_AIRSTRAFEACCELERATE)
639                                 accel = GeomLerp(PHYS_AIRACCELERATE, strafity, PHYS_AIRSTRAFEACCELERATE);
640                         if(PHYS_AIRSTRAFEACCEL_QW)
641                                 accelqw =
642                                         (((strafity > 0.5 ? PHYS_AIRSTRAFEACCEL_QW : PHYS_AIRACCEL_QW) >= 0) ? +1 : -1)
643                                         *
644                                         (1 - GeomLerp(1 - fabs(PHYS_AIRACCEL_QW), strafity, 1 - fabs(PHYS_AIRSTRAFEACCEL_QW)));
645                         // !CPM
646
647                         if(PHYS_WARSOWBUNNY_TURNACCEL && accelerating && PHYS_INPUT_MOVEVALUES(s)_y == 0 && PHYS_INPUT_MOVEVALUES(s)_x != 0)
648                                 CSQC_ClientMovement_Physics_PM_AirAccelerate(s, wishdir, wishspeed2);
649                         else
650                                 CSQC_ClientMovement_Physics_PM_Accelerate(s, wishdir, wishspeed, wishspeed0, accel, accelqw, PHYS_AIRACCEL_QW_STRETCHFACTOR, PHYS_AIRACCEL_SIDEWAYS_FRICTION / PHYS_MAXAIRSPEED, PHYS_AIRSPEEDLIMIT_NONQW);
651
652                         if(PHYS_AIRCONTROL)
653                                 CSQC_ClientMovement_Physics_CPM_PM_Aircontrol(s, wishdir, wishspeed2);
654                 }
655                 g = PHYS_GRAVITY * PHYS_ENTGRAVITY(s) * PHYS_INPUT_TIMELENGTH;
656                 if(GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
657                         s.velocity_z -= g * 0.5;
658                 else
659                         s.velocity_z -= g;
660                 CSQC_ClientMovement_Move(s);
661                 if(!(GAMEPLAYFIX_NOGRAVITYONGROUND) || !IS_ONGROUND(s))
662                 {
663                         if(GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
664                                 s.velocity_z -= g * 0.5;
665                 }
666         }
667 }
668
669 // TODO: merge this with main physics frame
670 void CSQC_ClientMovement_Physics_Swim(entity s)
671 {
672         // swimming
673         self.flags &= ~FL_ONGROUND;
674
675         makevectors(PHYS_INPUT_ANGLES(s));
676         //wishvel = v_forward * self.movement_x + v_right * self.movement_y + v_up * self.movement_z;
677         vector wishvel = v_forward * PHYS_INPUT_MOVEVALUES(s)_x + v_right * PHYS_INPUT_MOVEVALUES(s)_y + '0 0 1' * PHYS_INPUT_MOVEVALUES(s)_z;
678         if (wishvel == '0 0 0')
679                 wishvel = '0 0 -60'; // drift towards bottom
680
681         vector wishdir = normalize(wishvel);
682         float wishspeed = vlen(wishvel);
683         if (wishspeed > PHYS_MAXSPEED)
684                 wishspeed = PHYS_MAXSPEED;
685         wishspeed = wishspeed * 0.7;
686
687         // water friction
688         self.velocity = self.velocity * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION);
689
690         // water acceleration
691         CSQC_ClientMovement_Physics_PM_Accelerate(s, wishdir, wishspeed, wishspeed, PHYS_ACCELERATE, 1, 0, 0, 0);
692 }
693
694 void CSQC_ClientMovement_PlayerMove(entity s)
695 {
696         //Con_Printf(" %f", frametime);
697         if (!(PHYS_INPUT_BUTTONS(s) & 2)) // !jump
698                 UNSET_JUMP_HELD(s); // canjump = true
699         pmove_waterjumptime -= PHYS_INPUT_TIMELENGTH;
700         CSQC_ClientMovement_UpdateStatus(s);
701         if (s.waterlevel >= WATERLEVEL_SWIMMING)
702                 CSQC_ClientMovement_Physics_Swim(s);
703         else
704                 CSQC_ClientMovement_Physics_Walk(s);
705 }
706
707 void CSQC_ClientMovement_PlayerMove_Frame(entity s)
708 {
709         // if a move is more than 50ms, do it as two moves (matching qwsv)
710         //Con_Printf("%i ", s.cmd.msec);
711         if(PHYS_INPUT_TIMELENGTH > 0.0005)
712         {
713                 if (PHYS_INPUT_TIMELENGTH > 0.05)
714                 {
715                         PHYS_INPUT_TIMELENGTH /= 2;
716                         CSQC_ClientMovement_PlayerMove(s);
717                 }
718                 CSQC_ClientMovement_PlayerMove(s);
719         }
720         else
721         {
722                 // we REALLY need this handling to happen, even if the move is not executed
723                 if (!(PHYS_INPUT_BUTTONS(s) & 2)) // !jump
724                         UNSET_JUMP_HELD(s); // canjump = true
725         }
726 }
727
728 #undef VLEN2
729
730 #undef PHYS_INPUT_ANGLES
731 #undef PHYS_INPUT_BUTTONS
732
733 #undef PHYS_INPUT_TIMELENGTH
734
735 #undef PHYS_INPUT_MOVEVALUES
736
737 #undef GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE
738 #undef GAMEPLAYFIX_NOGRAVITYONGROUND
739 #undef GAMEPLAYFIX_Q2AIRACCELERATE
740
741 #undef IS_DUCKED
742 #undef SET_DUCKED
743 #undef UNSET_DUCKED
744
745 #undef IS_JUMP_HELD
746 #undef SET_JUMP_HELD
747 #undef UNSET_JUMP_HELD
748
749 #undef IS_ONGROUND
750 #undef SET_ONGROUND
751 #undef UNSET_ONGROUND
752
753 #undef PHYS_ACCELERATE
754 #undef PHYS_AIRACCEL_QW
755 #undef PHYS_AIRACCEL_QW_STRETCHFACTOR
756 #undef PHYS_AIRACCEL_SIDEWAYS_FRICTION
757 #undef PHYS_AIRACCELERATE
758 #undef PHYS_AIRCONTROL
759 #undef PHYS_AIRCONTROL_PENALTY
760 #undef PHYS_AIRCONTROL_POWER
761 #undef PHYS_AIRSPEEDLIMIT_NONQW
762 #undef PHYS_AIRSTOPACCELERATE
763 #undef PHYS_AIRSTRAFEACCEL_QW
764 #undef PHYS_AIRSTRAFEACCELERATE
765 #undef PHYS_EDGEFRICTION
766 #undef PHYS_ENTGRAVITY
767 #undef PHYS_FRICTION
768 #undef PHYS_GRAVITY
769 #undef PHYS_JUMPVELOCITY
770 #undef PHYS_MAXAIRSPEED
771 #undef PHYS_MAXAIRSTRAFESPEED
772 #undef PHYS_MAXSPEED
773 #undef PHYS_STEPHEIGHT
774 #undef PHYS_STOPSPEED
775 #undef PHYS_WARSOWBUNNY_ACCEL
776 #undef PHYS_WARSOWBUNNY_BACKTOSIDERATIO
777 #undef PHYS_WARSOWBUNNY_AIRFORWARDACCEL
778 #undef PHYS_WARSOWBUNNY_TOPSPEED
779 #undef PHYS_WARSOWBUNNY_TURNACCEL