]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/csqcmodellib/cl_player.qc
Fix movement prediction
[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 CSQC_IsMoveInDirection(float forward, float side, float angle)
389 {
390         // TODO: move to a common header
391         #define RAD2DEG(a) ((a) * (180.0f / M_PI))
392         #define ANGLEMOD(a) ((a) - 360.0 * floor((a) / 360.0))
393         if(forward == 0 && side == 0)
394                 return 0; // avoid division by zero
395         angle -= RAD2DEG(atan2(side, forward));
396         angle = (ANGLEMOD(angle + 180) - 180) / 45;
397         if(angle > 1)
398                 return 0;
399         if(angle < -1)
400                 return 0;
401         return 1 - fabs(angle);
402         #undef RAD2DEG
403         #undef ANGLEMOD
404 }
405
406 float CSQC_GeomLerp(float a, float lerp, float b)
407 {
408         if(a == 0)
409         {
410                 if(lerp < 1)
411                         return 0;
412                 else
413                         return b;
414         }
415         if(b == 0)
416         {
417                 if(lerp > 0)
418                         return 0;
419                 else
420                         return a;
421         }
422         return a * pow(fabs(b / a), lerp);
423 }
424
425 void CSQC_ClientMovement_Physics_CPM_PM_Aircontrol(entity s, vector wishdir, float wishspeed)
426 {
427         float zspeed, speed, dot, k;
428
429         k = 32 * (2 * CSQC_IsMoveInDirection(input_movevalues_x, input_movevalues_y, 0) - 1);
430         if(k <= 0)
431                 return;
432
433         k *= bound(0, wishspeed / getstatf(STAT_MOVEVARS_MAXAIRSPEED), 1);
434
435         zspeed = s.velocity_z;
436         s.velocity_z = 0;
437         speed = vlen(s.velocity);
438         if (speed) s.velocity /= speed;
439
440         dot = dotproduct(s.velocity, wishdir);
441
442         if(dot > 0) { // we can't change direction while slowing down
443                 k *= pow(dot, getstatf(STAT_MOVEVARS_AIRCONTROL_POWER))*input_timelength;
444                 speed = max(0, speed - getstatf(STAT_MOVEVARS_AIRCONTROL_PENALTY) * sqrt(max(0, 1 - dot*dot)) * k/32);
445                 k *= getstatf(STAT_MOVEVARS_AIRCONTROL);
446                 s.velocity = speed * s.velocity + k * wishdir;
447                 s.velocity = normalize(s.velocity);
448         }
449
450         s.velocity *= speed;
451         s.velocity_z = zspeed;
452 }
453
454 float CSQC_ClientMovement_Physics_AdjustAirAccelQW(float accelqw, float factor)
455 {
456         return
457                 (accelqw < 0 ? -1 : +1)
458                 *
459                 bound(0.000001, 1 - (1 - fabs(accelqw)) * factor, 1);
460 }
461
462 void CSQC_ClientMovement_Physics_PM_Accelerate(entity s, vector wishdir, float wishspeed, float wishspeed0, float accel, float accelqw, float stretchfactor, float sidefric, float speedlimit)
463 {
464         float vel_straight;
465         float vel_z;
466         vector vel_perpend;
467         float step;
468         vector vel_xy;
469         float vel_xy_current;
470         float vel_xy_backward, vel_xy_forward;
471         float speedclamp;
472
473         if(stretchfactor > 0)
474                 speedclamp = stretchfactor;
475         else if(accelqw < 0)
476                 speedclamp = 1;
477         else
478                 speedclamp = -1; // no clamping
479
480         if(accelqw < 0)
481                 accelqw = -accelqw;
482
483         if(moveflags & MOVEFLAG_Q2AIRACCELERATE)
484                 wishspeed0 = wishspeed; // don't need to emulate this Q1 bug
485
486         vel_straight = dotproduct(s.velocity, wishdir);
487         vel_z = s.velocity_z;
488         vel_xy = s.velocity;
489         vel_xy_z -= vel_z;
490         vel_perpend = vel_xy - vel_straight * wishdir;
491
492         step = accel * input_timelength * wishspeed0;
493
494         vel_xy_current  = vlen(vel_xy);
495         if(speedlimit > 0)
496                 accelqw = CSQC_ClientMovement_Physics_AdjustAirAccelQW(accelqw, (speedlimit - bound(wishspeed, vel_xy_current, speedlimit)) / max(1, speedlimit - wishspeed));
497         vel_xy_forward  = vel_xy_current + bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw);
498         vel_xy_backward = vel_xy_current - bound(0, wishspeed + vel_xy_current, step) * accelqw - step * (1 - accelqw);
499         if(vel_xy_backward < 0)
500                 vel_xy_backward = 0; // not that it REALLY occurs that this would cause wrong behaviour afterwards
501
502         vel_straight    = vel_straight   + bound(0, wishspeed - vel_straight,   step) * accelqw + step * (1 - accelqw);
503
504         if(sidefric < 0 && vlen2(vel_perpend))
505                 // negative: only apply so much sideways friction to stay below the speed you could get by "braking"
506         {
507                 float f, fmin;
508                 f = max(0, 1 + input_timelength * wishspeed * sidefric);
509                 fmin = (vel_xy_backward*vel_xy_backward - vel_straight*vel_straight) / vlen2(vel_perpend);
510                 // assume: fmin > 1
511                 // vel_xy_backward*vel_xy_backward - vel_straight*vel_straight > vel_perpend*vel_perpend
512                 // vel_xy_backward*vel_xy_backward > vel_straight*vel_straight + vel_perpend*vel_perpend
513                 // vel_xy_backward*vel_xy_backward > vel_xy * vel_xy
514                 // obviously, this cannot be
515                 if(fmin <= 0)
516                         vel_perpend *= f;
517                 else
518                 {
519                         fmin = sqrt(fmin);
520                         vel_perpend *= max(fmin, f);
521                 }
522         }
523         else
524                 vel_perpend *= max(0, 1 - input_timelength * wishspeed * sidefric);
525
526         s.velocity = vel_perpend + vel_straight * wishdir;
527
528         if(speedclamp >= 0)
529         {
530                 float vel_xy_preclamp;
531                 vel_xy_preclamp = vlen(s.velocity);
532                 if(vel_xy_preclamp > 0) // prevent division by zero
533                 {
534                         vel_xy_current += (vel_xy_forward - vel_xy_current) * speedclamp;
535                         if(vel_xy_current < vel_xy_preclamp)
536                                 s.velocity *= (vel_xy_current / vel_xy_preclamp);
537                 }
538         }
539
540         s.velocity_z += vel_z;
541 }
542
543 void CSQC_ClientMovement_Physics_Walk(entity s)
544 {
545         float friction;
546         float wishspeed;
547         float addspeed;
548         float accelspeed;
549         float f;
550         float gravity;
551         vector forward = '0 0 0';
552         vector right = '0 0 0';
553         vector up = '0 0 0';
554         vector wishvel;
555         vector wishdir;
556         vector yawangles;
557
558         // jump if on ground with jump button pressed but only if it has been
559         // released at least once since the last jump
560         if (input_buttons & 2)
561         {
562                 if ((s.pmove_flags & PMF_ONGROUND) && ((s.pmove_flags & PMF_JUMP_HELD) == 0 || !cvar("cl_movement_track_canjump")))
563                 {
564                         s.velocity_z += getstatf(STAT_MOVEVARS_JUMPVELOCITY);
565                         s.pmove_flags &= ~PMF_ONGROUND;
566                         s.pmove_flags |= PMF_JUMP_HELD; // canjump = false
567                 }
568         }
569         else
570                 s.pmove_flags &= ~PMF_JUMP_HELD; // canjump = true
571
572         // calculate movement vector
573         yawangles = '0 0 0';
574         yawangles_y = input_angles_y;
575         AngleVectors(yawangles, forward, right, up);
576         wishvel = input_movevalues_x * forward + input_movevalues_y * right;
577
578         // split wishvel into wishspeed and wishdir
579         wishspeed = vlen(wishvel);
580         if (wishspeed)
581                 wishdir = wishvel / wishspeed;
582         else
583                 wishdir = '0 0 0';
584         // check if onground
585         if ((s.pmove_flags & PMF_ONGROUND))
586         {
587                 wishspeed = min(wishspeed, getstatf(STAT_MOVEVARS_MAXSPEED));
588                 if (s.pmove_flags & PMF_DUCKED)
589                         wishspeed *= 0.5;
590
591                 // apply edge friction
592                 f = sqrt(s.velocity_x * s.velocity_x + s.velocity_y * s.velocity_y);
593                 if (f > 0)
594                 {
595                         friction = getstatf(STAT_MOVEVARS_FRICTION);
596                         if (getstatf(STAT_MOVEVARS_EDGEFRICTION) != 1)
597                         {
598                                 vector neworigin2;
599                                 vector neworigin3;
600                                 // note: QW uses the full player box for the trace, and yet still
601                                 // uses s.origin_z + s.mins_z, which is clearly an bug, but
602                                 // this mimics it for compatibility
603                                 neworigin2 = s.origin;
604                                 neworigin2_x += s.velocity_x*(16/f);
605                                 neworigin2_y += s.velocity_y*(16/f);
606                                 neworigin2_z += s.mins_z;
607                                 neworigin3 = neworigin2;
608                                 neworigin3_z -= 34;
609                                 traceline(neworigin2, neworigin3, MOVE_NORMAL, s);
610                                 if (trace_fraction == 1 && !trace_startsolid)
611                                         friction *= getstatf(STAT_MOVEVARS_EDGEFRICTION);
612                         }
613                         // apply ground friction
614                         f = 1 - input_timelength * friction * ((f < getstatf(STAT_MOVEVARS_STOPSPEED)) ? (getstatf(STAT_MOVEVARS_STOPSPEED) / f) : 1);
615                         f = max(f, 0);
616                         s.velocity *= f;
617                 }
618                 addspeed = wishspeed - dotproduct(s.velocity, wishdir);
619                 if (addspeed > 0)
620                 {
621                         accelspeed = min(getstatf(STAT_MOVEVARS_ACCELERATE) * input_timelength * wishspeed, addspeed);
622                         s.velocity += accelspeed * wishdir;
623                 }
624                 gravity = getstatf(STAT_MOVEVARS_GRAVITY) * getstatf(STAT_MOVEVARS_ENTGRAVITY) * input_timelength;
625                 if(!(moveflags & MOVEFLAG_NOGRAVITYONGROUND))
626                 {
627                         if(moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
628                                 s.velocity_z -= gravity * 0.5;
629                         else
630                                 s.velocity_z -= gravity;
631                 }
632                 if (vlen2(s.velocity))
633                         CSQC_ClientMovement_Move(s);
634                 if(!(moveflags & MOVEFLAG_NOGRAVITYONGROUND) || !(s.pmove_flags & PMF_ONGROUND))
635                 {
636                         if(moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
637                                 s.velocity_z -= gravity * 0.5;
638                 }
639         }
640         else
641         {
642                 if (pmove_waterjumptime <= 0)
643                 {
644                         // apply air speed limit
645                         float accel, wishspeed0, wishspeed2, accelqw, strafity;
646                         float accelerating;
647
648                         accelqw = getstatf(STAT_MOVEVARS_AIRACCEL_QW);
649                         wishspeed0 = wishspeed;
650                         wishspeed = min(wishspeed, getstatf(STAT_MOVEVARS_MAXAIRSPEED));
651                         if (s.pmove_flags & PMF_DUCKED)
652                                 wishspeed *= 0.5;
653                         accel = getstatf(STAT_MOVEVARS_AIRACCELERATE);
654
655                         accelerating = (dotproduct(s.velocity, wishdir) > 0);
656                         wishspeed2 = wishspeed;
657
658                         // CPM: air control
659                         if(getstatf(STAT_MOVEVARS_AIRSTOPACCELERATE) != 0)
660                         {
661                                 vector curdir;
662                                 curdir_x = s.velocity_x;
663                                 curdir_y = s.velocity_y;
664                                 curdir_z = 0;
665                                 curdir = normalize(curdir);
666                                 accel = accel + (getstatf(STAT_MOVEVARS_AIRSTOPACCELERATE) - accel) * max(0, -dotproduct(curdir, wishdir));
667                         }
668                         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
669                         if(getstatf(STAT_MOVEVARS_MAXAIRSTRAFESPEED))
670                                 wishspeed = min(wishspeed, CSQC_GeomLerp(getstatf(STAT_MOVEVARS_MAXAIRSPEED), strafity, getstatf(STAT_MOVEVARS_MAXAIRSTRAFESPEED)));
671                         if(getstatf(STAT_MOVEVARS_AIRSTRAFEACCELERATE))
672                                 accel = CSQC_GeomLerp(getstatf(STAT_MOVEVARS_AIRACCELERATE), strafity, getstatf(STAT_MOVEVARS_AIRSTRAFEACCELERATE));
673                         if(getstatf(STAT_MOVEVARS_AIRSTRAFEACCEL_QW))
674                                 accelqw =
675                                         (((strafity > 0.5 ? getstatf(STAT_MOVEVARS_AIRSTRAFEACCEL_QW) : getstatf(STAT_MOVEVARS_AIRACCEL_QW)) >= 0) ? +1 : -1)
676                                         *
677                                         (1 - CSQC_GeomLerp(1 - fabs(getstatf(STAT_MOVEVARS_AIRACCEL_QW)), strafity, 1 - fabs(getstatf(STAT_MOVEVARS_AIRSTRAFEACCEL_QW))));
678                         // !CPM
679
680 //                      if(getstatf(STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL) && accelerating && input_movevalues_y == 0 && input_movevalues_x != 0)
681 //                              CSQC_ClientMovement_Physics_PM_AirAccelerate(s, wishdir, wishspeed2);
682 //                      else
683                                 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));
684
685                         if(getstatf(STAT_MOVEVARS_AIRCONTROL))
686                                 CSQC_ClientMovement_Physics_CPM_PM_Aircontrol(s, wishdir, wishspeed2);
687                 }
688                 gravity = getstatf(STAT_MOVEVARS_GRAVITY) * getstatf(STAT_MOVEVARS_ENTGRAVITY) * input_timelength;
689                 if(moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
690                         s.velocity_z -= gravity * 0.5;
691                 else
692                         s.velocity_z -= gravity;
693                 CSQC_ClientMovement_Move(s);
694                 if(!(moveflags & MOVEFLAG_NOGRAVITYONGROUND) || !(s.pmove_flags & PMF_ONGROUND))
695                 {
696                         if(moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
697                                 s.velocity_z -= gravity * 0.5;
698                 }
699         }
700 }
701
702 void CSQC_ClientMovement_PlayerMove(entity s)
703 {
704         //Con_Printf(" %f", frametime);
705         if (!(input_buttons & 2)) // !jump
706                 s.pmove_flags &= ~PMF_JUMP_HELD; // canjump = true
707         pmove_waterjumptime -= input_timelength;
708         CSQC_ClientMovement_UpdateStatus(s);
709         // TODO
710 //      if (s.waterlevel >= WATERLEVEL_SWIMMING)
711 //              CL_ClientMovement_Physics_Swim(s);
712 //      else
713                 CSQC_ClientMovement_Physics_Walk(s);
714 }
715
716 void CSQC_ClientMovement_PlayerMove_Frame(entity s)
717 {
718         // if a move is more than 50ms, do it as two moves (matching qwsv)
719         //Con_Printf("%i ", s.cmd.msec);
720         if(input_timelength > 0.0005)
721         {
722                 if (input_timelength > 0.05)
723                 {
724                         input_timelength /= 2;
725                         CSQC_ClientMovement_PlayerMove(s);
726                 }
727                 CSQC_ClientMovement_PlayerMove(s);
728         }
729         else
730         {
731                 // we REALLY need this handling to happen, even if the move is not executed
732                 if (!(input_buttons & 2)) // !jump
733                         s.pmove_flags &= ~PMF_JUMP_HELD; // canjump = true
734         }
735 }
736
737 void CSQCPlayer_Physics(void)
738 {
739         switch(autocvar_cl_movement)
740         {
741                 case 1: runstandardplayerphysics(self); break;
742                 case 2: CSQC_ClientMovement_PlayerMove_Frame(self); break;
743         }
744 }
745 #undef vlen2
746
747 void CSQCPlayer_PredictTo(float endframe, float apply_error)
748 {
749         CSQCPlayer_Unpredict();
750         if(apply_error)
751         {
752                 self.origin += CSQCPlayer_GetPredictionErrorO();
753                 self.velocity += CSQCPlayer_GetPredictionErrorV();
754         }
755         CSQCPlayer_SetMinsMaxs();
756
757         csqcplayer_status = CSQCPLAYERSTATUS_PREDICTED;
758
759 #if 0
760         // we don't need this
761         // darkplaces makes servercommandframe == 0 in these cases anyway
762         if (getstatf(STAT_HEALTH) <= 0)
763         {
764                 csqcplayer_moveframe = clientcommandframe;
765                 getinputstate(csqcplayer_moveframe-1);
766                 print("the Weird code path got hit\n");
767                 return;
768         }
769 #endif
770
771         if(csqcplayer_moveframe >= endframe)
772         {
773                 getinputstate(csqcplayer_moveframe - 1);
774         }
775         else
776         {
777                 do
778                 {
779                         if (!getinputstate(csqcplayer_moveframe))
780                                 break;
781                         CSQCPlayer_Physics();
782                         CSQCPlayer_SetMinsMaxs();
783                         csqcplayer_moveframe++;
784                 }
785                 while(csqcplayer_moveframe < endframe);
786         }
787
788         //add in anything that was applied after (for low packet rate protocols)
789         input_angles = view_angles;
790 }
791
792 float CSQCPlayer_IsLocalPlayer()
793 {
794         return (self == csqcplayer);
795 }
796
797 void(entity e, float fl) V_CalcRefdef = #640; // DP_CSQC_V_CALCREFDEF
798
799 void CSQCPlayer_SetCamera()
800 {
801         vector v0;
802         v0 = pmove_vel; // TRICK: pmove_vel is set by the engine when we get here. No need to network velocity
803
804         if(csqcplayer)
805         {
806                 entity oldself;
807                 oldself = self;
808                 self = csqcplayer;
809
810 #ifdef COMPAT_XON050_ENGINE
811                 if(servercommandframe == 0 || clientcommandframe == 0 || !(checkextension("DP_CSQC_V_CALCREFDEF") || checkextension("DP_CSQC_V_CALCREFDEF_WIP1")))
812 #else
813                 if(servercommandframe == 0 || clientcommandframe == 0)
814 #endif
815                 {
816                         InterpolateOrigin_Do();
817                         self.view_ofs = '0 0 1' * getstati(STAT_VIEWHEIGHT);
818
819                         // get crouch state from the server
820                         if(getstati(STAT_VIEWHEIGHT) == PL_VIEW_OFS_z)
821                                 self.pmove_flags &= ~PMF_DUCKED;
822                         else if(getstati(STAT_VIEWHEIGHT) == PL_CROUCH_VIEW_OFS_z)
823                                 self.pmove_flags |= PMF_DUCKED;
824
825                         // get onground state from the server
826                         if(pmove_onground)
827                                 self.pmove_flags |= PMF_ONGROUND;
828                         else
829                                 self.pmove_flags &= ~PMF_ONGROUND;
830
831                         CSQCPlayer_SetMinsMaxs();
832
833                         // override it back just in case
834                         self.view_ofs = '0 0 1' * getstati(STAT_VIEWHEIGHT);
835
836                         // set velocity
837                         self.velocity = v0;
838                 }
839                 else
840                 {
841                         float flg = self.iflags;
842                         self.iflags &= ~(IFLAG_ORIGIN | IFLAG_ANGLES);
843                         InterpolateOrigin_Do();
844                         self.iflags = flg;
845
846                         if(csqcplayer_status == CSQCPLAYERSTATUS_FROMSERVER)
847                         {
848                                 vector o, v;
849                                 o = self.origin;
850                                 v = v0;
851                                 csqcplayer_status = CSQCPLAYERSTATUS_PREDICTED;
852                                 CSQCPlayer_PredictTo(servercommandframe + 1, FALSE);
853                                 CSQCPlayer_SetPredictionError(self.origin - o, self.velocity - v, pmove_onground - !!(self.pmove_flags & PMF_ONGROUND));
854                                 self.origin = o;
855                                 self.velocity = v;
856
857                                 // get crouch state from the server
858                                 if(getstati(STAT_VIEWHEIGHT) == PL_VIEW_OFS_z)
859                                         self.pmove_flags &= ~PMF_DUCKED;
860                                 else if(getstati(STAT_VIEWHEIGHT) == PL_CROUCH_VIEW_OFS_z)
861                                         self.pmove_flags |= PMF_DUCKED;
862
863                                 // get onground state from the server
864                                 if(pmove_onground)
865                                         self.pmove_flags |= PMF_ONGROUND;
866                                 else
867                                         self.pmove_flags &= ~PMF_ONGROUND;
868
869                                 CSQCPlayer_SavePrediction();
870                         }
871                         CSQCPlayer_PredictTo(clientcommandframe + 1, TRUE);
872
873 #ifdef CSQCMODEL_SERVERSIDE_CROUCH
874                         // get crouch state from the server (LAG)
875                         if(getstati(STAT_VIEWHEIGHT) == PL_VIEW_OFS_z)
876                                 self.pmove_flags &= ~PMF_DUCKED;
877                         else if(getstati(STAT_VIEWHEIGHT) == PL_CROUCH_VIEW_OFS_z)
878                                 self.pmove_flags |= PMF_DUCKED;
879 #endif
880
881                         CSQCPlayer_SetMinsMaxs();
882
883                         self.angles_y = input_angles_y;
884                 }
885
886                 // relink
887                 setorigin(self, self.origin);
888
889                 self = oldself;
890         }
891
892         entity view;
893 #ifdef COMPAT_XON050_ENGINE
894         view = CSQCModel_server2csqc((spectatee_status > 0) ? spectatee_status : player_localentnum);
895 #else
896         view = CSQCModel_server2csqc(player_localentnum);
897 #endif
898
899         if(view && view != csqcplayer)
900         {
901                 entity oldself = self;
902                 self = view;
903                 InterpolateOrigin_Do();
904                 self.view_ofs = '0 0 1' * getstati(STAT_VIEWHEIGHT);
905                 self = oldself;
906         }
907
908 #ifdef COMPAT_XON050_ENGINE
909         if(view && !(checkextension("DP_CSQC_V_CALCREFDEF") || checkextension("DP_CSQC_V_CALCREFDEF_WIP1")))
910         {
911                 // legacy code, not totally correct, but good enough for not having V_CalcRefdef
912                 setproperty(VF_ORIGIN, view.origin + '0 0 1' * getstati(STAT_VIEWHEIGHT));
913                 setproperty(VF_ANGLES, view_angles);
914         }
915         else
916 #endif
917         if(view)
918         {
919                 var float refdefflags = 0;
920
921                 if(view.csqcmodel_teleported)
922                         refdefflags |= REFDEFFLAG_TELEPORTED;
923
924                 if(input_buttons & 4)
925                         refdefflags |= REFDEFFLAG_JUMPING;
926
927                 // note: these two only work in WIP2, but are harmless in WIP1
928                 if(getstati(STAT_HEALTH) <= 0)
929                         refdefflags |= REFDEFFLAG_DEAD;
930
931                 if(intermission)
932                         refdefflags |= REFDEFFLAG_INTERMISSION;
933
934                 V_CalcRefdef(view, refdefflags);
935         }
936         else
937         {
938                 // FIXME by CSQC spec we have to do this:
939                 // but it breaks chase cam
940                 /*
941                 setproperty(VF_ORIGIN, pmove_org + '0 0 1' * getstati(STAT_VIEWHEIGHT));
942                 setproperty(VF_ANGLES, view_angles);
943                 */
944         }
945
946         { CSQCPLAYER_HOOK_POSTCAMERASETUP }
947 }
948
949 void CSQCPlayer_Remove()
950 {
951         csqcplayer = world;
952         cvar_settemp("cl_movement_replay", "1");
953 }
954
955 float CSQCPlayer_PreUpdate()
956 {
957         if(self != csqcplayer)
958                 return 0;
959         if(csqcplayer_status != CSQCPLAYERSTATUS_FROMSERVER)
960                 CSQCPlayer_Unpredict();
961         return 1;
962 }
963
964 float CSQCPlayer_PostUpdate()
965 {
966         if(self.entnum != player_localnum + 1)
967                 return 0;
968         csqcplayer = self;
969         csqcplayer_status = CSQCPLAYERSTATUS_FROMSERVER;
970         cvar_settemp("cl_movement_replay", "0");
971         self.entremove = CSQCPlayer_Remove;
972         return 1;
973 }