]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/csqcmodellib/cl_player.qc
Copy some stuff from the server side implementation of QC physics
[xonotic/xonotic-data.pk3dir.git] / qcsrc / csqcmodellib / cl_player.qc
1 /*
2  * Copyright (c) 2011 Rudolf Polzer
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to
6  * deal in the Software without restriction, including without limitation the
7  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8  * sell copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20  * IN THE SOFTWARE.
21  */
22
23 var float autocvar_cl_movement_errorcompensation = 0;
24 var float autocvar_cl_movement = 2; // testing purposes
25
26 // engine stuff
27 #define REFDEFFLAG_TELEPORTED 1
28 #define REFDEFFLAG_JUMPING 2
29 float pmove_onground; // weird engine flag we shouldn't really use but have to for now
30
31 vector csqcplayer_origin, csqcplayer_velocity;
32 float csqcplayer_sequence, player_pmflags;
33 float csqcplayer_moveframe;
34 vector csqcplayer_predictionerroro;
35 vector csqcplayer_predictionerrorv;
36 float csqcplayer_predictionerrortime;
37 float csqcplayer_predictionerrorfactor;
38
39 vector CSQCPlayer_GetPredictionErrorO()
40 {
41         if(time >= csqcplayer_predictionerrortime)
42                 return '0 0 0';
43         return csqcplayer_predictionerroro * (csqcplayer_predictionerrortime - time) * csqcplayer_predictionerrorfactor;
44 }
45
46 vector CSQCPlayer_GetPredictionErrorV()
47 {
48         if(time >= csqcplayer_predictionerrortime)
49                 return '0 0 0';
50         return csqcplayer_predictionerrorv * (csqcplayer_predictionerrortime - time) * csqcplayer_predictionerrorfactor;
51 }
52
53 void CSQCPlayer_SetPredictionError(vector o, vector v, float onground_diff)
54 {
55         // error too big to compensate, we LIKELY hit a teleport or a
56         // jumppad, or it's a jump time disagreement that'll get fixed
57         // next frame
58
59         // FIXME we sometimes have disagreement in order of jump velocity. Do not act on them!
60         /*
61         // commented out as this one did not help
62         if(onground_diff)
63         {
64                 printf("ONGROUND MISMATCH: %d x=%v v=%v\n", onground_diff, o, v);
65                 return;
66         }
67         */
68         if(vlen(o) > 32 || vlen(v) > 192)
69         {
70                 //printf("TOO BIG: x=%v v=%v\n", o, v);
71                 return;
72         }
73
74         if(!autocvar_cl_movement_errorcompensation)
75         {
76                 csqcplayer_predictionerrorfactor = 0;
77                 return;
78         }
79
80         csqcplayer_predictionerroro = CSQCPlayer_GetPredictionErrorO() + o;
81         csqcplayer_predictionerrorv = CSQCPlayer_GetPredictionErrorV() + v;
82         csqcplayer_predictionerrorfactor = autocvar_cl_movement_errorcompensation / ticrate;
83         csqcplayer_predictionerrortime = time + 1.0 / csqcplayer_predictionerrorfactor;
84 }
85
86 void CSQCPlayer_Unpredict()
87 {
88         if(csqcplayer_status == CSQCPLAYERSTATUS_UNPREDICTED)
89                 return;
90         if(csqcplayer_status != CSQCPLAYERSTATUS_PREDICTED)
91                 error("Cannot unpredict in current status");
92         self.origin = csqcplayer_origin;
93         self.velocity = csqcplayer_velocity;
94         csqcplayer_moveframe = csqcplayer_sequence+1; //+1 because the recieved frame has the move already done (server side)
95         self.pmove_flags = player_pmflags;
96 }
97
98 void CSQCPlayer_SetMinsMaxs()
99 {
100         if(self.pmove_flags & PMF_DUCKED)
101         {
102                 self.mins = PL_CROUCH_MIN;
103                 self.maxs = PL_CROUCH_MAX;
104                 self.view_ofs = PL_CROUCH_VIEW_OFS;
105         }
106         else
107         {
108                 self.mins = PL_MIN;
109                 self.maxs = PL_MAX;
110                 self.view_ofs = PL_VIEW_OFS;
111         }
112 }
113
114 void CSQCPlayer_SavePrediction()
115 {
116         player_pmflags = self.pmove_flags;
117         csqcplayer_origin = self.origin;
118         csqcplayer_velocity = self.velocity;
119         csqcplayer_sequence = servercommandframe;
120         csqcplayer_status = CSQCPLAYERSTATUS_PREDICTED;
121 }
122
123 // TODO: water prediction
124 float pmove_waterjumptime; // weird engine flag we shouldn't really use but have to for now
125 // TODO: move to a common header
126 #define vlen2(v) dotproduct(v, v)
127 vector _AngleVectors_forward, _AngleVectors_right, _AngleVectors_up;
128 void _AngleVectors (vector _angles, vector _forward, vector _right, vector _up)
129 {
130         float angle, sr, sp, sy, cr, cp, cy;
131
132         angle = _angles_y * (M_PI*2 / 360);
133         sy = sin(angle);
134         cy = cos(angle);
135         angle = _angles_x * (M_PI*2 / 360);
136         sp = sin(angle);
137         cp = cos(angle);
138
139         _forward_x = cp*cy;
140         _forward_y = cp*sy;
141         _forward_z = -sp;
142
143         if (angles_z)
144         {
145                 angle = _angles_z * (M_PI*2 / 360);
146                 sr = sin(angle);
147                 cr = cos(angle);
148
149                 _right_x = -1*(sr*sp*cy+cr*-sy);
150                 _right_y = -1*(sr*sp*sy+cr*cy);
151                 _right_z = -1*(sr*cp);
152
153                 _up_x = (cr*sp*cy+-sr*-sy);
154                 _up_y = (cr*sp*sy+-sr*cy);
155                 _up_z = cr*cp;
156         }
157         else
158         {
159                 _right_x = sy;
160                 _right_y = -cy;
161                 _right_z = 0;
162
163                 _up_x = (sp*cy);
164                 _up_y = (sp*sy);
165                 _up_z = cp;
166         }
167         _AngleVectors_forward = _forward;
168         _AngleVectors_right = _right;
169         _AngleVectors_up = _up;
170 }
171 #define AngleVectors(angles, forward, right, up) do { \
172         _AngleVectors(angles, forward, right, up); \
173         forward = _AngleVectors_forward; \
174         right = _AngleVectors_right; \
175         up = _AngleVectors_up; \
176 } while(0)
177
178 // TODO: move these elsewhere
179 vector cl_playerstandmins = '-16 -16 -24';
180 vector cl_playerstandmaxs = '16 16 45';
181 vector cl_playercrouchmins = '-16 -16 -24';
182 vector cl_playercrouchmaxs = '16 16 25';
183
184 const float unstick_count = 27;
185 vector unstick_offsets[unstick_count] =
186 {
187 // 1 no nudge (just return the original if this test passes)
188         '0.000   0.000  0.000',
189 // 6 simple nudges
190         ' 0.000  0.000  0.125', '0.000  0.000 -0.125',
191         '-0.125  0.000  0.000', '0.125  0.000  0.000',
192         ' 0.000 -0.125  0.000', '0.000  0.125  0.000',
193 // 4 diagonal flat nudges
194         '-0.125 -0.125  0.000', '0.125 -0.125  0.000',
195         '-0.125  0.125  0.000', '0.125  0.125  0.000',
196 // 8 diagonal upward nudges
197         '-0.125  0.000  0.125', '0.125  0.000  0.125',
198         ' 0.000 -0.125  0.125', '0.000  0.125  0.125',
199         '-0.125 -0.125  0.125', '0.125 -0.125  0.125',
200         '-0.125  0.125  0.125', '0.125  0.125  0.125',
201 // 8 diagonal downward nudges
202         '-0.125  0.000 -0.125', '0.125  0.000 -0.125',
203         ' 0.000 -0.125 -0.125', '0.000  0.125 -0.125',
204         '-0.125 -0.125 -0.125', '0.125 -0.125 -0.125',
205         '-0.125  0.125 -0.125', '0.125  0.125 -0.125',
206 };
207
208 float CSQC_ClientMovement_Unstick(entity s)
209 {
210         float i;
211         vector neworigin;
212         for (i = 0; i < unstick_count; i++)
213         {
214                 neworigin = unstick_offsets[i] + s.origin;
215                 tracebox(neworigin, cl_playercrouchmins, cl_playercrouchmaxs, neworigin, MOVE_NORMAL, s);
216                 if (!trace_startsolid)
217                 {
218                         s.origin = neworigin;
219                         return true;
220                 }
221         }
222         // if all offsets failed, give up
223         return false;
224 }
225
226 void CSQC_ClientMovement_UpdateStatus(entity s)
227 {
228         float f;
229         vector origin1, origin2;
230
231         // make sure player is not stuck
232         CSQC_ClientMovement_Unstick(s);
233
234         // set crouched
235         if (input_buttons & 16)
236         {
237                 // wants to crouch, this always works..
238                 if (!s.pmove_flags & PMF_DUCKED)
239                         s.pmove_flags |= PMF_DUCKED;
240         }
241         else
242         {
243                 // wants to stand, if currently crouching we need to check for a
244                 // low ceiling first
245                 if (s.pmove_flags & PMF_DUCKED)
246                 {
247                         tracebox(s.origin, cl_playerstandmins, cl_playerstandmaxs, s.origin, MOVE_NORMAL, s);
248                         if (!trace_startsolid)
249                                 s.pmove_flags &= ~PMF_DUCKED;
250                 }
251         }
252         if (s.pmove_flags & PMF_DUCKED)
253         {
254                 s.mins = cl_playercrouchmins;
255                 s.maxs = cl_playercrouchmaxs;
256         }
257         else
258         {
259                 s.mins = cl_playerstandmins;
260                 s.maxs = cl_playerstandmaxs;
261         }
262
263         // set onground
264         origin1 = s.origin;
265         origin1_z += 1;
266         origin2 = s.origin;
267     origin2_z -= 1; // -2 causes clientside doublejump bug at above 150fps, raising that to 300fps :)
268
269         tracebox(origin1, s.mins, s.maxs, origin2, MOVE_NORMAL, s);
270         if(trace_fraction < 1 && trace_plane_normal_z > 0.7)
271         {
272                 s.pmove_flags |= PMF_ONGROUND;
273
274                 // this code actually "predicts" an impact; so let's clip velocity first
275                 f = dotproduct(s.velocity, trace_plane_normal);
276                 if(f < 0) // only if moving downwards actually
277                         s.velocity -= f * trace_plane_normal;
278         }
279         else
280                 s.pmove_flags &= ~PMF_ONGROUND; // onground = false;
281
282         // set watertype/waterlevel
283         origin1 = s.origin;
284         origin1_z += s.mins_z + 1;
285         s.waterlevel = WATERLEVEL_NONE;
286         // TODO: convert
287 //      s.watertype = CL_TracePoint(origin1, MOVE_NOMONSTERS, s, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK;
288 //      if (s.watertype)
289 //      {
290 //              s.waterlevel = WATERLEVEL_WETFEET;
291 //              origin1[2] = s.origin[2] + (s.mins[2] + s.maxs[2]) * 0.5f;
292 //              if (CL_TracePoint(origin1, MOVE_NOMONSTERS, s, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK)
293 //              {
294 //                      s.waterlevel = WATERLEVEL_SWIMMING;
295 //                      origin1[2] = s.origin[2] + 22;
296 //                      if (CL_TracePoint(origin1, MOVE_NOMONSTERS, s, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK)
297 //                              s.waterlevel = WATERLEVEL_SUBMERGED;
298 //              }
299 //      }
300 //
301 //      // water jump prediction
302 //      if ((s.pmove_flags & PMF_ONGROUND) || s.velocity_z <= 0 || pmove_waterjumptime <= 0)
303 //              pmove_waterjumptime = 0;
304 }
305
306 void CSQC_ClientMovement_Move(entity s)
307 {
308         float bump;
309         float t;
310         float f;
311         vector neworigin;
312         vector currentorigin2;
313         vector neworigin2;
314         vector primalvelocity;
315         float old_trace1_fraction;
316         vector old_trace1_endpos;
317         vector old_trace1_plane_normal;
318         float old_trace2_fraction;
319         vector old_trace2_plane_normal;
320         CSQC_ClientMovement_UpdateStatus(s);
321         primalvelocity = s.velocity;
322         for (bump = 0, t = input_timelength; bump < 8 && vlen2(s.velocity) > 0; bump++)
323         {
324                 neworigin = s.origin + t * s.velocity;
325                 tracebox(s.origin, s.mins, s.maxs, neworigin, MOVE_NORMAL, s);
326                 old_trace1_fraction = trace_fraction;
327                 old_trace1_endpos = trace_endpos;
328                 old_trace1_plane_normal = trace_plane_normal;
329                 if (trace_fraction < 1 && trace_plane_normal_z == 0)
330                 {
331                         // may be a step or wall, try stepping up
332                         // first move forward at a higher level
333                         currentorigin2 = s.origin;
334                         currentorigin2_z += getstatf(STAT_MOVEVARS_STEPHEIGHT);
335                         neworigin2 = neworigin;
336                         neworigin2_z = s.origin_z + getstatf(STAT_MOVEVARS_STEPHEIGHT);
337                         tracebox(currentorigin2, s.mins, s.maxs, neworigin2, MOVE_NORMAL, s);
338                         if (!trace_startsolid)
339                         {
340                                 // then move down from there
341                                 currentorigin2 = trace_endpos;
342                                 neworigin2 = trace_endpos;
343                                 neworigin2_z = s.origin_z;
344                                 old_trace2_fraction = trace_fraction;
345                                 old_trace2_plane_normal = trace_plane_normal;
346                                 tracebox(currentorigin2, s.mins, s.maxs, neworigin2, MOVE_NORMAL, s);
347                                 //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]);
348                                 // accept the new trace if it made some progress
349                                 if (fabs(trace_endpos_x - old_trace1_endpos_x) >= 0.03125 || fabs(trace_endpos_y - old_trace1_endpos_y) >= 0.03125)
350                                 {
351                                         trace_fraction = old_trace2_fraction;
352                                         trace_endpos = trace_endpos;
353                                         trace_plane_normal = old_trace2_plane_normal;
354                                 }
355                                 else
356                                 {
357                                         trace_fraction = old_trace1_fraction;
358                                         trace_endpos = old_trace1_endpos;
359                                         trace_plane_normal = old_trace1_plane_normal;
360                                 }
361                         }
362                 }
363
364                 // check if it moved at all
365                 if (trace_fraction >= 0.001)
366                         s.origin = trace_endpos;
367
368                 // check if it moved all the way
369                 if (trace_fraction == 1)
370                         break;
371
372                 // this is only really needed for nogravityonground combined with gravityunaffectedbyticrate
373                 // <LordHavoc> I'm pretty sure I commented it out solely because it seemed redundant
374                 // this got commented out in a change that supposedly makes the code match QW better
375                 // so if this is broken, maybe put it in an if(cls.protocol != PROTOCOL_QUAKEWORLD) block
376                 if (trace_plane_normal_z > 0.7)
377                         s.pmove_flags |= PMF_ONGROUND;
378
379                 t -= t * trace_fraction;
380
381                 f = dotproduct(s.velocity, trace_plane_normal);
382                 s.velocity -= f * trace_plane_normal;
383         }
384         if (pmove_waterjumptime > 0)
385                 s.velocity = primalvelocity;
386 }
387
388 float IsMoveInDirection(vector mv, float angle) // key mix factor
389 {
390         if(mv_x == 0 && mv_y == 0)
391                 return 0; // avoid division by zero
392         angle -= RAD2DEG * atan2(mv_y, mv_x);
393         angle = remainder(angle, 360) / 45;
394         if(angle >  1)
395                 return 0;
396         if(angle < -1)
397                 return 0;
398         return 1 - fabs(angle);
399 }
400
401 // TODO: remove this and use above function
402 float CSQC_IsMoveInDirection(float forward, float side, float angle)
403 {
404         // TODO: move to a common header
405         #define RAD2DEG(a) ((a) * (180.0f / M_PI))
406         #define ANGLEMOD(a) ((a) - 360.0 * floor((a) / 360.0))
407         if(forward == 0 && side == 0)
408                 return 0; // avoid division by zero
409         angle -= RAD2DEG(atan2(side, forward));
410         angle = (ANGLEMOD(angle + 180) - 180) / 45;
411         if(angle > 1)
412                 return 0;
413         if(angle < -1)
414                 return 0;
415         return 1 - fabs(angle);
416         #undef RAD2DEG
417         #undef ANGLEMOD
418 }
419
420 float GeomLerp(float a, float lerp, float b)
421 {
422         if(a == 0)
423         {
424                 if(lerp < 1)
425                         return 0;
426                 else
427                         return b;
428         }
429         if(b == 0)
430         {
431                 if(lerp > 0)
432                         return 0;
433                 else
434                         return a;
435         }
436         return a * pow(fabs(b / a), lerp);
437 }
438
439 void CSQC_ClientMovement_Physics_CPM_PM_Aircontrol(entity s, vector wishdir, float wishspeed)
440 {
441         float zspeed, xyspeed, dot, k;
442
443 #if 0
444         // this doesn't play well with analog input
445         if(s.movement_x == 0 || s.movement_y != 0)
446                 return; // can't control movement if not moving forward or backward
447         k = 32;
448 #else
449         k = 32 * (2 * IsMoveInDirection(input_movevalues, 0) - 1);
450         if(k <= 0)
451                 return;
452 #endif
453
454         k *= bound(0, wishspeed / getstatf(STAT_MOVEVARS_MAXAIRSPEED), 1);
455
456         zspeed = s.velocity_z;
457         s.velocity_z = 0;
458         xyspeed = vlen(s.velocity); s.velocity = normalize(s.velocity);
459
460         dot = s.velocity * wishdir;
461
462         if(dot > 0) // we can't change direction while slowing down
463         {
464                 k *= pow(dot, getstatf(STAT_MOVEVARS_AIRCONTROL_POWER))*input_timelength;
465                 xyspeed = max(0, xyspeed - getstatf(STAT_MOVEVARS_AIRCONTROL_PENALTY) * sqrt(max(0, 1 - dot*dot)) * k/32);
466                 k *= getstatf(STAT_MOVEVARS_AIRCONTROL);
467                 s.velocity = normalize(s.velocity * xyspeed + wishdir * k);
468         }
469
470         s.velocity = s.velocity * xyspeed;
471         s.velocity_z = zspeed;
472 }
473
474 float CSQC_ClientMovement_Physics_AdjustAirAccelQW(float accelqw, float factor)
475 {
476         return copysign(bound(0.000001, 1 - (1 - fabs(accelqw)) * factor, 1), accelqw);
477 }
478
479 void CSQC_ClientMovement_Physics_PM_Accelerate(entity s, vector wishdir, float wishspeed, float wishspeed0, float accel, float accelqw, float stretchfactor, float sidefric, float speedlimit)
480 {
481         float vel_straight;
482         float vel_z;
483         vector vel_perpend;
484         float step;
485         vector vel_xy;
486         float vel_xy_current;
487         float vel_xy_backward, vel_xy_forward;
488         float speedclamp;
489
490         if(stretchfactor > 0)
491                 speedclamp = stretchfactor;
492         else if(accelqw < 0)
493                 speedclamp = 1;
494         else
495                 speedclamp = -1; // no clamping
496
497         if(accelqw < 0)
498                 accelqw = -accelqw;
499
500         if(moveflags & MOVEFLAG_Q2AIRACCELERATE)
501                 wishspeed0 = wishspeed; // don't need to emulate this Q1 bug
502
503         vel_straight = dotproduct(s.velocity, wishdir);
504         vel_z = s.velocity_z;
505         vel_xy = s.velocity;
506         vel_xy_z -= vel_z;
507         vel_perpend = vel_xy - vel_straight * wishdir;
508
509         step = accel * input_timelength * wishspeed0;
510
511         vel_xy_current  = vlen(vel_xy);
512         if(speedlimit > 0)
513                 accelqw = CSQC_ClientMovement_Physics_AdjustAirAccelQW(accelqw, (speedlimit - bound(wishspeed, vel_xy_current, speedlimit)) / max(1, speedlimit - wishspeed));
514         vel_xy_forward  = vel_xy_current + bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw);
515         vel_xy_backward = vel_xy_current - bound(0, wishspeed + vel_xy_current, step) * accelqw - step * (1 - accelqw);
516         if(vel_xy_backward < 0)
517                 vel_xy_backward = 0; // not that it REALLY occurs that this would cause wrong behaviour afterwards
518
519         vel_straight    = vel_straight   + bound(0, wishspeed - vel_straight,   step) * accelqw + step * (1 - accelqw);
520
521         if(sidefric < 0 && vlen2(vel_perpend))
522                 // negative: only apply so much sideways friction to stay below the speed you could get by "braking"
523         {
524                 float f, fmin;
525                 f = max(0, 1 + input_timelength * wishspeed * sidefric);
526                 fmin = (vel_xy_backward*vel_xy_backward - vel_straight*vel_straight) / vlen2(vel_perpend);
527                 // assume: fmin > 1
528                 // vel_xy_backward*vel_xy_backward - vel_straight*vel_straight > vel_perpend*vel_perpend
529                 // vel_xy_backward*vel_xy_backward > vel_straight*vel_straight + vel_perpend*vel_perpend
530                 // vel_xy_backward*vel_xy_backward > vel_xy * vel_xy
531                 // obviously, this cannot be
532                 if(fmin <= 0)
533                         vel_perpend *= f;
534                 else
535                 {
536                         fmin = sqrt(fmin);
537                         vel_perpend *= max(fmin, f);
538                 }
539         }
540         else
541                 vel_perpend *= max(0, 1 - input_timelength * wishspeed * sidefric);
542
543         s.velocity = vel_perpend + vel_straight * wishdir;
544
545         if(speedclamp >= 0)
546         {
547                 float vel_xy_preclamp;
548                 vel_xy_preclamp = vlen(s.velocity);
549                 if(vel_xy_preclamp > 0) // prevent division by zero
550                 {
551                         vel_xy_current += (vel_xy_forward - vel_xy_current) * speedclamp;
552                         if(vel_xy_current < vel_xy_preclamp)
553                                 s.velocity *= (vel_xy_current / vel_xy_preclamp);
554                 }
555         }
556
557         s.velocity_z += vel_z;
558 }
559
560 void CSQC_ClientMovement_Physics_PM_AirAccelerate(entity s, vector wishdir, float wishspeed)
561 {
562         vector curvel, wishvel, acceldir, curdir;
563         float addspeed, accelspeed, curspeed, f;
564         float dot;
565
566         if(wishspeed == 0)
567                 return;
568
569         curvel = s.velocity;
570         curvel_z = 0;
571         curspeed = vlen(curvel);
572
573         if(wishspeed > curspeed * 1.01)
574         {
575                 wishspeed = min(wishspeed, curspeed + getstatf(STAT_MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL) * getstatf(STAT_MOVEVARS_MAXSPEED) * input_timelength);
576         }
577         else
578         {
579                 f = max(0, (getstatf(STAT_MOVEVARS_WARSOWBUNNY_TOPSPEED) - curspeed) / (getstatf(STAT_MOVEVARS_WARSOWBUNNY_TOPSPEED) - getstatf(STAT_MOVEVARS_MAXSPEED)));
580                 wishspeed = max(curspeed, getstatf(STAT_MOVEVARS_WARSOWBUNNY_ACCEL)) + getstatf(STAT_MOVEVARS_WARSOWBUNNY_ACCEL) * f * getstatf(STAT_MOVEVARS_WARSOWBUNNY_ACCEL) * input_timelength;
581         }
582         wishvel = wishdir * wishspeed;
583         acceldir = wishvel - curvel;
584         addspeed = vlen(acceldir);
585         acceldir = normalize(acceldir);
586
587         accelspeed = min(addspeed, getstatf(STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL) * getstatf(STAT_MOVEVARS_WARSOWBUNNY_ACCEL) * input_timelength);
588
589         if(getstatf(STAT_MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO) < 1)
590         {
591                 curdir = normalize(curvel);
592                 dot = acceldir * curdir;
593                 if(dot < 0)
594                         acceldir = acceldir - (1 - getstatf(STAT_MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO)) * dot * curdir;
595         }
596
597         s.velocity += accelspeed * acceldir;
598 }
599
600 void CSQC_ClientMovement_Physics_Walk(entity s)
601 {
602         float friction;
603         float wishspeed;
604         float addspeed;
605         float accelspeed;
606         float f;
607         float gravity;
608         vector forward = '0 0 0';
609         vector right = '0 0 0';
610         vector up = '0 0 0';
611         vector wishvel;
612         vector wishdir;
613         vector yawangles;
614
615         // jump if on ground with jump button pressed but only if it has been
616         // released at least once since the last jump
617         if (input_buttons & 2)
618         {
619                 if ((s.pmove_flags & PMF_ONGROUND) && ((s.pmove_flags & PMF_JUMP_HELD) == 0 || !cvar("cl_movement_track_canjump")))
620                 {
621                         s.velocity_z += getstatf(STAT_MOVEVARS_JUMPVELOCITY);
622                         s.pmove_flags &= ~PMF_ONGROUND;
623                         s.pmove_flags |= PMF_JUMP_HELD; // canjump = false
624                 }
625         }
626         else
627                 s.pmove_flags &= ~PMF_JUMP_HELD; // canjump = true
628
629         // calculate movement vector
630         yawangles = '0 0 0';
631         yawangles_y = input_angles_y;
632         AngleVectors(yawangles, forward, right, up);
633         wishvel = input_movevalues_x * forward + input_movevalues_y * right;
634
635         // split wishvel into wishspeed and wishdir
636         wishspeed = vlen(wishvel);
637         if (wishspeed)
638                 wishdir = wishvel / wishspeed;
639         else
640                 wishdir = '0 0 0';
641         // check if onground
642         if ((s.pmove_flags & PMF_ONGROUND))
643         {
644                 wishspeed = min(wishspeed, getstatf(STAT_MOVEVARS_MAXSPEED));
645                 if (s.pmove_flags & PMF_DUCKED)
646                         wishspeed *= 0.5;
647
648                 // apply edge friction
649                 f = sqrt(s.velocity_x * s.velocity_x + s.velocity_y * s.velocity_y);
650                 if (f > 0)
651                 {
652                         friction = getstatf(STAT_MOVEVARS_FRICTION);
653                         if (getstatf(STAT_MOVEVARS_EDGEFRICTION) != 1)
654                         {
655                                 vector neworigin2;
656                                 vector neworigin3;
657                                 // note: QW uses the full player box for the trace, and yet still
658                                 // uses s.origin_z + s.mins_z, which is clearly an bug, but
659                                 // this mimics it for compatibility
660                                 neworigin2 = s.origin;
661                                 neworigin2_x += s.velocity_x*(16/f);
662                                 neworigin2_y += s.velocity_y*(16/f);
663                                 neworigin2_z += s.mins_z;
664                                 neworigin3 = neworigin2;
665                                 neworigin3_z -= 34;
666                                 traceline(neworigin2, neworigin3, MOVE_NORMAL, s);
667                                 if (trace_fraction == 1 && !trace_startsolid)
668                                         friction *= getstatf(STAT_MOVEVARS_EDGEFRICTION);
669                         }
670                         // apply ground friction
671                         f = 1 - input_timelength * friction * ((f < getstatf(STAT_MOVEVARS_STOPSPEED)) ? (getstatf(STAT_MOVEVARS_STOPSPEED) / f) : 1);
672                         f = max(f, 0);
673                         s.velocity *= f;
674                 }
675                 addspeed = wishspeed - dotproduct(s.velocity, wishdir);
676                 if (addspeed > 0)
677                 {
678                         accelspeed = min(getstatf(STAT_MOVEVARS_ACCELERATE) * input_timelength * wishspeed, addspeed);
679                         s.velocity += accelspeed * wishdir;
680                 }
681                 gravity = getstatf(STAT_MOVEVARS_GRAVITY) * getstatf(STAT_MOVEVARS_ENTGRAVITY) * input_timelength;
682                 if(!(moveflags & MOVEFLAG_NOGRAVITYONGROUND))
683                 {
684                         if(moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
685                                 s.velocity_z -= gravity * 0.5;
686                         else
687                                 s.velocity_z -= gravity;
688                 }
689                 if (vlen2(s.velocity))
690                         CSQC_ClientMovement_Move(s);
691                 if(!(moveflags & MOVEFLAG_NOGRAVITYONGROUND) || !(s.pmove_flags & PMF_ONGROUND))
692                 {
693                         if(moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
694                                 s.velocity_z -= gravity * 0.5;
695                 }
696         }
697         else
698         {
699                 if (pmove_waterjumptime <= 0)
700                 {
701                         // apply air speed limit
702                         float accel, wishspeed0, wishspeed2, accelqw, strafity;
703                         float accelerating;
704
705                         accelqw = getstatf(STAT_MOVEVARS_AIRACCEL_QW);
706                         wishspeed0 = wishspeed;
707                         wishspeed = min(wishspeed, getstatf(STAT_MOVEVARS_MAXAIRSPEED));
708                         if (s.pmove_flags & PMF_DUCKED)
709                                 wishspeed *= 0.5;
710                         accel = getstatf(STAT_MOVEVARS_AIRACCELERATE);
711
712                         accelerating = (dotproduct(s.velocity, wishdir) > 0);
713                         wishspeed2 = wishspeed;
714
715                         // CPM: air control
716                         if(getstatf(STAT_MOVEVARS_AIRSTOPACCELERATE) != 0)
717                         {
718                                 vector curdir;
719                                 curdir_x = s.velocity_x;
720                                 curdir_y = s.velocity_y;
721                                 curdir_z = 0;
722                                 curdir = normalize(curdir);
723                                 accel = accel + (getstatf(STAT_MOVEVARS_AIRSTOPACCELERATE) - accel) * max(0, -dotproduct(curdir, wishdir));
724                         }
725                         strafity = CSQC_IsMoveInDirection(input_movevalues_x, input_movevalues_y, -90) + CSQC_IsMoveInDirection(input_movevalues_x, input_movevalues_y, +90); // if one is nonzero, other is always zero
726                         if(getstatf(STAT_MOVEVARS_MAXAIRSTRAFESPEED))
727                                 wishspeed = min(wishspeed, GeomLerp(getstatf(STAT_MOVEVARS_MAXAIRSPEED), strafity, getstatf(STAT_MOVEVARS_MAXAIRSTRAFESPEED)));
728                         if(getstatf(STAT_MOVEVARS_AIRSTRAFEACCELERATE))
729                                 accel = GeomLerp(getstatf(STAT_MOVEVARS_AIRACCELERATE), strafity, getstatf(STAT_MOVEVARS_AIRSTRAFEACCELERATE));
730                         if(getstatf(STAT_MOVEVARS_AIRSTRAFEACCEL_QW))
731                                 accelqw =
732                                         (((strafity > 0.5 ? getstatf(STAT_MOVEVARS_AIRSTRAFEACCEL_QW) : getstatf(STAT_MOVEVARS_AIRACCEL_QW)) >= 0) ? +1 : -1)
733                                         *
734                                         (1 - GeomLerp(1 - fabs(getstatf(STAT_MOVEVARS_AIRACCEL_QW)), strafity, 1 - fabs(getstatf(STAT_MOVEVARS_AIRSTRAFEACCEL_QW))));
735                         // !CPM
736
737                         if(getstatf(STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL) && accelerating && input_movevalues_y == 0 && input_movevalues_x != 0)
738                                 CSQC_ClientMovement_Physics_PM_AirAccelerate(s, wishdir, wishspeed2);
739                         else
740                                 CSQC_ClientMovement_Physics_PM_Accelerate(s, wishdir, wishspeed, wishspeed0, accel, accelqw, getstatf(STAT_MOVEVARS_AIRACCEL_QW_STRETCHFACTOR), getstatf(STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION) / getstatf(STAT_MOVEVARS_MAXAIRSPEED), getstatf(STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW));
741
742                         if(getstatf(STAT_MOVEVARS_AIRCONTROL))
743                                 CSQC_ClientMovement_Physics_CPM_PM_Aircontrol(s, wishdir, wishspeed2);
744                 }
745                 gravity = getstatf(STAT_MOVEVARS_GRAVITY) * getstatf(STAT_MOVEVARS_ENTGRAVITY) * input_timelength;
746                 if(moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
747                         s.velocity_z -= gravity * 0.5;
748                 else
749                         s.velocity_z -= gravity;
750                 CSQC_ClientMovement_Move(s);
751                 if(!(moveflags & MOVEFLAG_NOGRAVITYONGROUND) || !(s.pmove_flags & PMF_ONGROUND))
752                 {
753                         if(moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
754                                 s.velocity_z -= gravity * 0.5;
755                 }
756         }
757 }
758
759 void CSQC_ClientMovement_PlayerMove(entity s)
760 {
761         //Con_Printf(" %f", frametime);
762         if (!(input_buttons & 2)) // !jump
763                 s.pmove_flags &= ~PMF_JUMP_HELD; // canjump = true
764         pmove_waterjumptime -= input_timelength;
765         CSQC_ClientMovement_UpdateStatus(s);
766         // TODO
767 //      if (s.waterlevel >= WATERLEVEL_SWIMMING)
768 //              CL_ClientMovement_Physics_Swim(s);
769 //      else
770                 CSQC_ClientMovement_Physics_Walk(s);
771 }
772
773 void CSQC_ClientMovement_PlayerMove_Frame(entity s)
774 {
775         // if a move is more than 50ms, do it as two moves (matching qwsv)
776         //Con_Printf("%i ", s.cmd.msec);
777         if(input_timelength > 0.0005)
778         {
779                 if (input_timelength > 0.05)
780                 {
781                         input_timelength /= 2;
782                         CSQC_ClientMovement_PlayerMove(s);
783                 }
784                 CSQC_ClientMovement_PlayerMove(s);
785         }
786         else
787         {
788                 // we REALLY need this handling to happen, even if the move is not executed
789                 if (!(input_buttons & 2)) // !jump
790                         s.pmove_flags &= ~PMF_JUMP_HELD; // canjump = true
791         }
792 }
793
794 void CSQCPlayer_Physics(void)
795 {
796         switch(autocvar_cl_movement)
797         {
798                 case 1: runstandardplayerphysics(self); break;
799                 case 2: CSQC_ClientMovement_PlayerMove_Frame(self); break;
800         }
801 }
802 #undef vlen2
803
804 void CSQCPlayer_PredictTo(float endframe, float apply_error)
805 {
806         CSQCPlayer_Unpredict();
807         if(apply_error)
808         {
809                 self.origin += CSQCPlayer_GetPredictionErrorO();
810                 self.velocity += CSQCPlayer_GetPredictionErrorV();
811         }
812         CSQCPlayer_SetMinsMaxs();
813
814         csqcplayer_status = CSQCPLAYERSTATUS_PREDICTED;
815
816 #if 0
817         // we don't need this
818         // darkplaces makes servercommandframe == 0 in these cases anyway
819         if (getstatf(STAT_HEALTH) <= 0)
820         {
821                 csqcplayer_moveframe = clientcommandframe;
822                 getinputstate(csqcplayer_moveframe-1);
823                 print("the Weird code path got hit\n");
824                 return;
825         }
826 #endif
827
828         if(csqcplayer_moveframe >= endframe)
829         {
830                 getinputstate(csqcplayer_moveframe - 1);
831         }
832         else
833         {
834                 do
835                 {
836                         if (!getinputstate(csqcplayer_moveframe))
837                                 break;
838                         CSQCPlayer_Physics();
839                         CSQCPlayer_SetMinsMaxs();
840                         csqcplayer_moveframe++;
841                 }
842                 while(csqcplayer_moveframe < endframe);
843         }
844
845         //add in anything that was applied after (for low packet rate protocols)
846         input_angles = view_angles;
847 }
848
849 float CSQCPlayer_IsLocalPlayer()
850 {
851         return (self == csqcplayer);
852 }
853
854 void(entity e, float fl) V_CalcRefdef = #640; // DP_CSQC_V_CALCREFDEF
855
856 void CSQCPlayer_SetCamera()
857 {
858         vector v0;
859         v0 = pmove_vel; // TRICK: pmove_vel is set by the engine when we get here. No need to network velocity
860
861         if(csqcplayer)
862         {
863                 entity oldself;
864                 oldself = self;
865                 self = csqcplayer;
866
867 #ifdef COMPAT_XON050_ENGINE
868                 if(servercommandframe == 0 || clientcommandframe == 0 || !(checkextension("DP_CSQC_V_CALCREFDEF") || checkextension("DP_CSQC_V_CALCREFDEF_WIP1")))
869 #else
870                 if(servercommandframe == 0 || clientcommandframe == 0)
871 #endif
872                 {
873                         InterpolateOrigin_Do();
874                         self.view_ofs = '0 0 1' * getstati(STAT_VIEWHEIGHT);
875
876                         // get crouch state from the server
877                         if(getstati(STAT_VIEWHEIGHT) == PL_VIEW_OFS_z)
878                                 self.pmove_flags &= ~PMF_DUCKED;
879                         else if(getstati(STAT_VIEWHEIGHT) == PL_CROUCH_VIEW_OFS_z)
880                                 self.pmove_flags |= PMF_DUCKED;
881
882                         // get onground state from the server
883                         if(pmove_onground)
884                                 self.pmove_flags |= PMF_ONGROUND;
885                         else
886                                 self.pmove_flags &= ~PMF_ONGROUND;
887
888                         CSQCPlayer_SetMinsMaxs();
889
890                         // override it back just in case
891                         self.view_ofs = '0 0 1' * getstati(STAT_VIEWHEIGHT);
892
893                         // set velocity
894                         self.velocity = v0;
895                 }
896                 else
897                 {
898                         float flg = self.iflags;
899                         self.iflags &= ~(IFLAG_ORIGIN | IFLAG_ANGLES);
900                         InterpolateOrigin_Do();
901                         self.iflags = flg;
902
903                         if(csqcplayer_status == CSQCPLAYERSTATUS_FROMSERVER)
904                         {
905                                 vector o, v;
906                                 o = self.origin;
907                                 v = v0;
908                                 csqcplayer_status = CSQCPLAYERSTATUS_PREDICTED;
909                                 CSQCPlayer_PredictTo(servercommandframe + 1, FALSE);
910                                 CSQCPlayer_SetPredictionError(self.origin - o, self.velocity - v, pmove_onground - !!(self.pmove_flags & PMF_ONGROUND));
911                                 self.origin = o;
912                                 self.velocity = v;
913
914                                 // get crouch state from the server
915                                 if(getstati(STAT_VIEWHEIGHT) == PL_VIEW_OFS_z)
916                                         self.pmove_flags &= ~PMF_DUCKED;
917                                 else if(getstati(STAT_VIEWHEIGHT) == PL_CROUCH_VIEW_OFS_z)
918                                         self.pmove_flags |= PMF_DUCKED;
919
920                                 // get onground state from the server
921                                 if(pmove_onground)
922                                         self.pmove_flags |= PMF_ONGROUND;
923                                 else
924                                         self.pmove_flags &= ~PMF_ONGROUND;
925
926                                 CSQCPlayer_SavePrediction();
927                         }
928                         CSQCPlayer_PredictTo(clientcommandframe + 1, TRUE);
929
930 #ifdef CSQCMODEL_SERVERSIDE_CROUCH
931                         // get crouch state from the server (LAG)
932                         if(getstati(STAT_VIEWHEIGHT) == PL_VIEW_OFS_z)
933                                 self.pmove_flags &= ~PMF_DUCKED;
934                         else if(getstati(STAT_VIEWHEIGHT) == PL_CROUCH_VIEW_OFS_z)
935                                 self.pmove_flags |= PMF_DUCKED;
936 #endif
937
938                         CSQCPlayer_SetMinsMaxs();
939
940                         self.angles_y = input_angles_y;
941                 }
942
943                 // relink
944                 setorigin(self, self.origin);
945
946                 self = oldself;
947         }
948
949         entity view;
950 #ifdef COMPAT_XON050_ENGINE
951         view = CSQCModel_server2csqc((spectatee_status > 0) ? spectatee_status : player_localentnum);
952 #else
953         view = CSQCModel_server2csqc(player_localentnum);
954 #endif
955
956         if(view && view != csqcplayer)
957         {
958                 entity oldself = self;
959                 self = view;
960                 InterpolateOrigin_Do();
961                 self.view_ofs = '0 0 1' * getstati(STAT_VIEWHEIGHT);
962                 self = oldself;
963         }
964
965 #ifdef COMPAT_XON050_ENGINE
966         if(view && !(checkextension("DP_CSQC_V_CALCREFDEF") || checkextension("DP_CSQC_V_CALCREFDEF_WIP1")))
967         {
968                 // legacy code, not totally correct, but good enough for not having V_CalcRefdef
969                 setproperty(VF_ORIGIN, view.origin + '0 0 1' * getstati(STAT_VIEWHEIGHT));
970                 setproperty(VF_ANGLES, view_angles);
971         }
972         else
973 #endif
974         if(view)
975         {
976                 var float refdefflags = 0;
977
978                 if(view.csqcmodel_teleported)
979                         refdefflags |= REFDEFFLAG_TELEPORTED;
980
981                 if(input_buttons & 4)
982                         refdefflags |= REFDEFFLAG_JUMPING;
983
984                 // note: these two only work in WIP2, but are harmless in WIP1
985                 if(getstati(STAT_HEALTH) <= 0)
986                         refdefflags |= REFDEFFLAG_DEAD;
987
988                 if(intermission)
989                         refdefflags |= REFDEFFLAG_INTERMISSION;
990
991                 V_CalcRefdef(view, refdefflags);
992         }
993         else
994         {
995                 // FIXME by CSQC spec we have to do this:
996                 // but it breaks chase cam
997                 /*
998                 setproperty(VF_ORIGIN, pmove_org + '0 0 1' * getstati(STAT_VIEWHEIGHT));
999                 setproperty(VF_ANGLES, view_angles);
1000                 */
1001         }
1002
1003         { CSQCPLAYER_HOOK_POSTCAMERASETUP }
1004 }
1005
1006 void CSQCPlayer_Remove()
1007 {
1008         csqcplayer = world;
1009         cvar_settemp("cl_movement_replay", "1");
1010 }
1011
1012 float CSQCPlayer_PreUpdate()
1013 {
1014         if(self != csqcplayer)
1015                 return 0;
1016         if(csqcplayer_status != CSQCPLAYERSTATUS_FROMSERVER)
1017                 CSQCPlayer_Unpredict();
1018         return 1;
1019 }
1020
1021 float CSQCPlayer_PostUpdate()
1022 {
1023         if(self.entnum != player_localnum + 1)
1024                 return 0;
1025         csqcplayer = self;
1026         csqcplayer_status = CSQCPLAYERSTATUS_FROMSERVER;
1027         cvar_settemp("cl_movement_replay", "0");
1028         self.entremove = CSQCPlayer_Remove;
1029         return 1;
1030 }