]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/csqcmodellib/cl_player.qc
af35644763bdd73be5d3c0bec67e894c98dd7948
[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 void AngleVectors (vector angles, vector forward, vector right, vector up)
128 {
129         float angle, sr, sp, sy, cr, cp, cy;
130
131         angle = angles_y * (M_PI*2 / 360);
132         sy = sin(angle);
133         cy = cos(angle);
134         angle = angles_x * (M_PI*2 / 360);
135         sp = sin(angle);
136         cp = cos(angle);
137         if (forward)
138         {
139                 forward_x = cp*cy;
140                 forward_y = cp*sy;
141                 forward_z = -sp;
142         }
143         if (right || up)
144         {
145                 if (angles_z)
146                 {
147                         angle = angles_z * (M_PI*2 / 360);
148                         sr = sin(angle);
149                         cr = cos(angle);
150                         if (right)
151                         {
152                                 right_x = -1*(sr*sp*cy+cr*-sy);
153                                 right_y = -1*(sr*sp*sy+cr*cy);
154                                 right_z = -1*(sr*cp);
155                         }
156                         if (up)
157                         {
158                                 up_x = (cr*sp*cy+-sr*-sy);
159                                 up_y = (cr*sp*sy+-sr*cy);
160                                 up_z = cr*cp;
161                         }
162                 }
163                 else
164                 {
165                         if (right)
166                         {
167                                 right_x = sy;
168                                 right_y = -cy;
169                                 right_z = 0;
170                         }
171                         if (up)
172                         {
173                                 up_x = (sp*cy);
174                                 up_y = (sp*sy);
175                                 up_z = cp;
176                         }
177                 }
178         }
179 }
180
181 // TODO: move these elsewhere
182 vector cl_playerstandmins = '-16 -16 -24';
183 vector cl_playerstandmaxs = '16 16 45';
184 vector cl_playercrouchmins = '-16 -16 -24';
185 vector cl_playercrouchmaxs = '16 16 25';
186
187 const float unstick_count = 27;
188 vector unstick_offsets[unstick_count] =
189 {
190 // 1 no nudge (just return the original if this test passes)
191         '0.000   0.000  0.000',
192 // 6 simple nudges
193         ' 0.000  0.000  0.125', '0.000  0.000 -0.125',
194         '-0.125  0.000  0.000', '0.125  0.000  0.000',
195         ' 0.000 -0.125  0.000', '0.000  0.125  0.000',
196 // 4 diagonal flat nudges
197         '-0.125 -0.125  0.000', '0.125 -0.125  0.000',
198         '-0.125  0.125  0.000', '0.125  0.125  0.000',
199 // 8 diagonal upward nudges
200         '-0.125  0.000  0.125', '0.125  0.000  0.125',
201         ' 0.000 -0.125  0.125', '0.000  0.125  0.125',
202         '-0.125 -0.125  0.125', '0.125 -0.125  0.125',
203         '-0.125  0.125  0.125', '0.125  0.125  0.125',
204 // 8 diagonal downward nudges
205         '-0.125  0.000 -0.125', '0.125  0.000 -0.125',
206         ' 0.000 -0.125 -0.125', '0.000  0.125 -0.125',
207         '-0.125 -0.125 -0.125', '0.125 -0.125 -0.125',
208         '-0.125  0.125 -0.125', '0.125  0.125 -0.125',
209 };
210
211 float CSQC_ClientMovement_Unstick(entity s)
212 {
213         float i;
214         vector neworigin;
215         for (i = 0; i < unstick_count; i++)
216         {
217                 neworigin = unstick_offsets[i] + s.origin;
218                 tracebox(neworigin, cl_playercrouchmins, cl_playercrouchmaxs, neworigin, MOVE_NORMAL, s);
219                 if (!trace_startsolid)
220                 {
221                         s.origin = neworigin;
222                         return true;
223                 }
224         }
225         // if all offsets failed, give up
226         return false;
227 }
228
229 void CSQC_ClientMovement_UpdateStatus(entity s)
230 {
231         float f;
232         vector origin1, origin2;
233
234         // make sure player is not stuck
235         CSQC_ClientMovement_Unstick(s);
236
237         // set crouched
238         if (input_buttons & 16)
239         {
240                 // wants to crouch, this always works..
241                 if (!s.pmove_flags & PMF_DUCKED)
242                         s.pmove_flags |= PMF_DUCKED;
243         }
244         else
245         {
246                 // wants to stand, if currently crouching we need to check for a
247                 // low ceiling first
248                 if (s.pmove_flags & PMF_DUCKED)
249                 {
250                         tracebox(s.origin, cl_playerstandmins, cl_playerstandmaxs, s.origin, MOVE_NORMAL, s);
251                         if (!trace_startsolid)
252                                 s.pmove_flags &= ~PMF_DUCKED;
253                 }
254         }
255         if (s.pmove_flags & PMF_DUCKED)
256         {
257                 s.mins = cl_playercrouchmins;
258                 s.maxs = cl_playercrouchmaxs;
259         }
260         else
261         {
262                 s.mins = cl_playerstandmins;
263                 s.maxs = cl_playerstandmaxs;
264         }
265
266         // set onground
267         origin1 = s.origin;
268         origin1_z += 1;
269         origin2 = s.origin;
270     origin2_z -= 1; // -2 causes clientside doublejump bug at above 150fps, raising that to 300fps :)
271
272         tracebox(origin1, s.mins, s.maxs, origin2, MOVE_NORMAL, s);
273         if(trace_fraction < 1 && trace_plane_normal_z > 0.7)
274         {
275                 s.pmove_flags |= PMF_ONGROUND;
276
277                 // this code actually "predicts" an impact; so let's clip velocity first
278                 f = dotproduct(s.velocity, trace_plane_normal);
279                 if(f < 0) // only if moving downwards actually
280                         s.velocity -= f * trace_plane_normal;
281         }
282         else
283                 s.pmove_flags &= ~PMF_ONGROUND; // onground = false;
284
285         // set watertype/waterlevel
286         origin1 = s.origin;
287         origin1_z += s.mins_z + 1;
288         s.waterlevel = WATERLEVEL_NONE;
289         // TODO: convert
290 //      s.watertype = CL_TracePoint(origin1, MOVE_NOMONSTERS, s, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK;
291 //      if (s.watertype)
292 //      {
293 //              s.waterlevel = WATERLEVEL_WETFEET;
294 //              origin1[2] = s.origin[2] + (s.mins[2] + s.maxs[2]) * 0.5f;
295 //              if (CL_TracePoint(origin1, MOVE_NOMONSTERS, s, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK)
296 //              {
297 //                      s.waterlevel = WATERLEVEL_SWIMMING;
298 //                      origin1[2] = s.origin[2] + 22;
299 //                      if (CL_TracePoint(origin1, MOVE_NOMONSTERS, s, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK)
300 //                              s.waterlevel = WATERLEVEL_SUBMERGED;
301 //              }
302 //      }
303 //
304 //      // water jump prediction
305 //      if ((s.pmove_flags & PMF_ONGROUND) || s.velocity_z <= 0 || pmove_waterjumptime <= 0)
306 //              pmove_waterjumptime = 0;
307 }
308
309 void CSQC_ClientMovement_Move(entity s)
310 {
311         float bump;
312         float t;
313         float f;
314         vector neworigin;
315         vector currentorigin2;
316         vector neworigin2;
317         vector primalvelocity;
318         float old_trace1_fraction;
319         vector old_trace1_endpos;
320         vector old_trace1_plane_normal;
321         float old_trace2_fraction;
322         vector old_trace2_plane_normal;
323         CSQC_ClientMovement_UpdateStatus(s);
324         primalvelocity = s.velocity;
325         for (bump = 0, t = input_timelength; bump < 8 && vlen2(s.velocity) > 0; bump++)
326         {
327                 neworigin = s.origin + t * s.velocity;
328                 tracebox(s.origin, s.mins, s.maxs, neworigin, MOVE_NORMAL, s);
329                 old_trace1_fraction = trace_fraction;
330                 old_trace1_endpos = trace_endpos;
331                 old_trace1_plane_normal = trace_plane_normal;
332                 if (trace_fraction < 1 && trace_plane_normal_z == 0)
333                 {
334                         // may be a step or wall, try stepping up
335                         // first move forward at a higher level
336                         currentorigin2 = s.origin;
337                         currentorigin2_z += getstatf(STAT_MOVEVARS_STEPHEIGHT);
338                         neworigin2 = neworigin;
339                         neworigin2_z = s.origin_z + getstatf(STAT_MOVEVARS_STEPHEIGHT);
340                         tracebox(currentorigin2, s.mins, s.maxs, neworigin2, MOVE_NORMAL, s);
341                         if (!trace_startsolid)
342                         {
343                                 // then move down from there
344                                 currentorigin2 = trace_endpos;
345                                 neworigin2 = trace_endpos;
346                                 neworigin2_z = s.origin_z;
347                                 old_trace2_fraction = trace_fraction;
348                                 old_trace2_plane_normal = trace_plane_normal;
349                                 tracebox(currentorigin2, s.mins, s.maxs, neworigin2, MOVE_NORMAL, s);
350                                 //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]);
351                                 // accept the new trace if it made some progress
352                                 if (fabs(trace_endpos_x - old_trace1_endpos_x) >= 0.03125 || fabs(trace_endpos_y - old_trace1_endpos_y) >= 0.03125)
353                                 {
354                                         trace_fraction = old_trace2_fraction;
355                                         trace_endpos = trace_endpos;
356                                         trace_plane_normal = old_trace2_plane_normal;
357                                 }
358                                 else
359                                 {
360                                         trace_fraction = old_trace1_fraction;
361                                         trace_endpos = old_trace1_endpos;
362                                         trace_plane_normal = old_trace1_plane_normal;
363                                 }
364                         }
365                 }
366
367                 // check if it moved at all
368                 if (trace_fraction >= 0.001)
369                         s.origin = trace_endpos;
370
371                 // check if it moved all the way
372                 if (trace_fraction == 1)
373                         break;
374
375                 // this is only really needed for nogravityonground combined with gravityunaffectedbyticrate
376                 // <LordHavoc> I'm pretty sure I commented it out solely because it seemed redundant
377                 // this got commented out in a change that supposedly makes the code match QW better
378                 // so if this is broken, maybe put it in an if(cls.protocol != PROTOCOL_QUAKEWORLD) block
379                 if (trace_plane_normal_z > 0.7)
380                         s.pmove_flags |= PMF_ONGROUND;
381
382                 t -= t * trace_fraction;
383
384                 f = dotproduct(s.velocity, trace_plane_normal);
385                 s.velocity -= f * trace_plane_normal;
386         }
387         if (pmove_waterjumptime > 0)
388                 s.velocity = primalvelocity;
389 }
390
391 float CSQC_IsMoveInDirection(float forward, float side, float angle)
392 {
393         // TODO: move to a common header
394         #define RAD2DEG(a) ((a) * (180.0f / M_PI))
395         #define ANGLEMOD(a) ((a) - 360.0 * floor((a) / 360.0))
396         if(forward == 0 && side == 0)
397                 return 0; // avoid division by zero
398         angle -= RAD2DEG(atan2(side, forward));
399         angle = (ANGLEMOD(angle + 180) - 180) / 45;
400         if(angle > 1)
401                 return 0;
402         if(angle < -1)
403                 return 0;
404         return 1 - fabs(angle);
405         #undef RAD2DEG
406         #undef ANGLEMOD
407 }
408
409 float CSQC_GeomLerp(float a, float lerp, float b)
410 {
411         if(a == 0)
412         {
413                 if(lerp < 1)
414                         return 0;
415                 else
416                         return b;
417         }
418         if(b == 0)
419         {
420                 if(lerp > 0)
421                         return 0;
422                 else
423                         return a;
424         }
425         return a * pow(fabs(b / a), lerp);
426 }
427
428 void CSQC_ClientMovement_Physics_CPM_PM_Aircontrol(entity s, vector wishdir, float wishspeed)
429 {
430         float zspeed, speed, dot, k;
431
432         k = 32 * (2 * CSQC_IsMoveInDirection(input_movevalues_x, input_movevalues_y, 0) - 1);
433         if(k <= 0)
434                 return;
435
436         k *= bound(0, wishspeed / getstatf(STAT_MOVEVARS_MAXAIRSPEED), 1);
437
438         zspeed = s.velocity_z;
439         s.velocity_z = 0;
440         speed = vlen(s.velocity);
441         if (speed) s.velocity /= speed;
442
443         dot = dotproduct(s.velocity, wishdir);
444
445         if(dot > 0) { // we can't change direction while slowing down
446                 k *= pow(dot, getstatf(STAT_MOVEVARS_AIRCONTROL_POWER))*input_timelength;
447                 speed = max(0, speed - getstatf(STAT_MOVEVARS_AIRCONTROL_PENALTY) * sqrt(max(0, 1 - dot*dot)) * k/32);
448                 k *= getstatf(STAT_MOVEVARS_AIRCONTROL);
449                 s.velocity = speed * s.velocity + k * wishdir;
450                 s.velocity = normalize(s.velocity);
451         }
452
453         s.velocity *= speed;
454         s.velocity_z = zspeed;
455 }
456
457 float CSQC_ClientMovement_Physics_AdjustAirAccelQW(float accelqw, float factor)
458 {
459         return
460                 (accelqw < 0 ? -1 : +1)
461                 *
462                 bound(0.000001, 1 - (1 - fabs(accelqw)) * factor, 1);
463 }
464
465 void CSQC_ClientMovement_Physics_PM_Accelerate(entity s, vector wishdir, float wishspeed, float wishspeed0, float accel, float accelqw, float stretchfactor, float sidefric, float speedlimit)
466 {
467         float vel_straight;
468         float vel_z;
469         vector vel_perpend;
470         float step;
471         vector vel_xy;
472         float vel_xy_current;
473         float vel_xy_backward, vel_xy_forward;
474         float speedclamp;
475
476         if(stretchfactor > 0)
477                 speedclamp = stretchfactor;
478         else if(accelqw < 0)
479                 speedclamp = 1;
480         else
481                 speedclamp = -1; // no clamping
482
483         if(accelqw < 0)
484                 accelqw = -accelqw;
485
486         if(moveflags & MOVEFLAG_Q2AIRACCELERATE)
487                 wishspeed0 = wishspeed; // don't need to emulate this Q1 bug
488
489         vel_straight = dotproduct(s.velocity, wishdir);
490         vel_z = s.velocity_z;
491         vel_xy = s.velocity;
492         vel_xy_z -= vel_z;
493         vel_perpend = vel_xy - vel_straight * wishdir;
494
495         step = accel * input_timelength * wishspeed0;
496
497         vel_xy_current  = vlen(vel_xy);
498         if(speedlimit > 0)
499                 accelqw = CSQC_ClientMovement_Physics_AdjustAirAccelQW(accelqw, (speedlimit - bound(wishspeed, vel_xy_current, speedlimit)) / max(1, speedlimit - wishspeed));
500         vel_xy_forward  = vel_xy_current + bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw);
501         vel_xy_backward = vel_xy_current - bound(0, wishspeed + vel_xy_current, step) * accelqw - step * (1 - accelqw);
502         if(vel_xy_backward < 0)
503                 vel_xy_backward = 0; // not that it REALLY occurs that this would cause wrong behaviour afterwards
504
505         vel_straight    = vel_straight   + bound(0, wishspeed - vel_straight,   step) * accelqw + step * (1 - accelqw);
506
507         if(sidefric < 0 && vlen2(vel_perpend))
508                 // negative: only apply so much sideways friction to stay below the speed you could get by "braking"
509         {
510                 float f, fmin;
511                 f = max(0, 1 + input_timelength * wishspeed * sidefric);
512                 fmin = (vel_xy_backward*vel_xy_backward - vel_straight*vel_straight) / vlen2(vel_perpend);
513                 // assume: fmin > 1
514                 // vel_xy_backward*vel_xy_backward - vel_straight*vel_straight > vel_perpend*vel_perpend
515                 // vel_xy_backward*vel_xy_backward > vel_straight*vel_straight + vel_perpend*vel_perpend
516                 // vel_xy_backward*vel_xy_backward > vel_xy * vel_xy
517                 // obviously, this cannot be
518                 if(fmin <= 0)
519                         vel_perpend *= f;
520                 else
521                 {
522                         fmin = sqrt(fmin);
523                         vel_perpend *= max(fmin, f);
524                 }
525         }
526         else
527                 vel_perpend *= max(0, 1 - input_timelength * wishspeed * sidefric);
528
529         s.velocity = vel_perpend + vel_straight * wishdir;
530
531         if(speedclamp >= 0)
532         {
533                 float vel_xy_preclamp;
534                 vel_xy_preclamp = vlen(s.velocity);
535                 if(vel_xy_preclamp > 0) // prevent division by zero
536                 {
537                         vel_xy_current += (vel_xy_forward - vel_xy_current) * speedclamp;
538                         if(vel_xy_current < vel_xy_preclamp)
539                                 s.velocity *= (vel_xy_current / vel_xy_preclamp);
540                 }
541         }
542
543         s.velocity_z += vel_z;
544 }
545
546 void CSQC_ClientMovement_Physics_Walk(entity s)
547 {
548         float friction;
549         float wishspeed;
550         float addspeed;
551         float accelspeed;
552         float f;
553         float gravity;
554         vector forward = '0 0 0';
555         vector right = '0 0 0';
556         vector up = '0 0 0';
557         vector wishvel;
558         vector wishdir;
559         vector yawangles;
560
561         // jump if on ground with jump button pressed but only if it has been
562         // released at least once since the last jump
563         if (input_buttons & 2)
564         {
565                 if ((s.pmove_flags & PMF_ONGROUND) && ((s.pmove_flags & PMF_JUMP_HELD) == 0 || !cvar("cl_movement_track_canjump")))
566                 {
567                         s.velocity_z += getstatf(STAT_MOVEVARS_JUMPVELOCITY);
568                         s.pmove_flags &= ~PMF_ONGROUND;
569                         s.pmove_flags |= PMF_JUMP_HELD; // canjump = false
570                 }
571         }
572         else
573                 s.pmove_flags &= ~PMF_JUMP_HELD; // canjump = true
574
575         // calculate movement vector
576         yawangles = '0 0 0';
577         yawangles_y = input_angles_y;
578         AngleVectors(yawangles, forward, right, up);
579         wishvel = input_movevalues_x * forward + input_movevalues_y * right;
580
581         // split wishvel into wishspeed and wishdir
582         wishspeed = vlen(wishvel);
583         if (wishspeed)
584                 wishdir = wishvel / wishspeed;
585         else
586                 wishdir = '0 0 0';
587         // check if onground
588         if ((s.pmove_flags & PMF_ONGROUND))
589         {
590                 wishspeed = min(wishspeed, getstatf(STAT_MOVEVARS_MAXSPEED));
591                 if (s.pmove_flags & PMF_DUCKED)
592                         wishspeed *= 0.5;
593
594                 // apply edge friction
595                 f = sqrt(s.velocity_x * s.velocity_x + s.velocity_y * s.velocity_y);
596                 if (f > 0)
597                 {
598                         friction = getstatf(STAT_MOVEVARS_FRICTION);
599                         if (getstatf(STAT_MOVEVARS_EDGEFRICTION) != 1)
600                         {
601                                 vector neworigin2;
602                                 vector neworigin3;
603                                 // note: QW uses the full player box for the trace, and yet still
604                                 // uses s.origin_z + s.mins_z, which is clearly an bug, but
605                                 // this mimics it for compatibility
606                                 neworigin2 = s.origin;
607                                 neworigin2_x += s.velocity_x*(16/f);
608                                 neworigin2_y += s.velocity_y*(16/f);
609                                 neworigin2_z += s.mins_z;
610                                 neworigin3 = neworigin2;
611                                 neworigin3_z -= 34;
612                                 traceline(neworigin2, neworigin3, MOVE_NORMAL, s);
613                                 if (trace_fraction == 1 && !trace_startsolid)
614                                         friction *= getstatf(STAT_MOVEVARS_EDGEFRICTION);
615                         }
616                         // apply ground friction
617                         f = 1 - input_timelength * friction * ((f < getstatf(STAT_MOVEVARS_STOPSPEED)) ? (getstatf(STAT_MOVEVARS_STOPSPEED) / f) : 1);
618                         f = max(f, 0);
619                         s.velocity *= f;
620                 }
621                 addspeed = wishspeed - dotproduct(s.velocity, wishdir);
622                 if (addspeed > 0)
623                 {
624                         accelspeed = min(getstatf(STAT_MOVEVARS_ACCELERATE) * input_timelength * wishspeed, addspeed);
625                         s.velocity += accelspeed * wishdir;
626                 }
627                 gravity = getstatf(STAT_MOVEVARS_GRAVITY) * getstatf(STAT_MOVEVARS_ENTGRAVITY) * input_timelength;
628                 if(!(moveflags & MOVEFLAG_NOGRAVITYONGROUND))
629                 {
630                         if(moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
631                                 s.velocity_z -= gravity * 0.5;
632                         else
633                                 s.velocity_z -= gravity;
634                 }
635                 if (vlen2(s.velocity))
636                         CSQC_ClientMovement_Move(s);
637                 if(!(moveflags & MOVEFLAG_NOGRAVITYONGROUND) || !(s.pmove_flags & PMF_ONGROUND))
638                 {
639                         if(moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
640                                 s.velocity_z -= gravity * 0.5;
641                 }
642         }
643         else
644         {
645                 if (pmove_waterjumptime <= 0)
646                 {
647                         // apply air speed limit
648                         float accel, wishspeed0, wishspeed2, accelqw, strafity;
649                         float accelerating;
650
651                         accelqw = getstatf(STAT_MOVEVARS_AIRACCEL_QW);
652                         wishspeed0 = wishspeed;
653                         wishspeed = min(wishspeed, getstatf(STAT_MOVEVARS_MAXAIRSPEED));
654                         if (s.pmove_flags & PMF_DUCKED)
655                                 wishspeed *= 0.5;
656                         accel = getstatf(STAT_MOVEVARS_AIRACCELERATE);
657
658                         accelerating = (dotproduct(s.velocity, wishdir) > 0);
659                         wishspeed2 = wishspeed;
660
661                         // CPM: air control
662                         if(getstatf(STAT_MOVEVARS_AIRSTOPACCELERATE) != 0)
663                         {
664                                 vector curdir;
665                                 curdir_x = s.velocity_x;
666                                 curdir_y = s.velocity_y;
667                                 curdir_z = 0;
668                                 curdir = normalize(curdir);
669                                 accel = accel + (getstatf(STAT_MOVEVARS_AIRSTOPACCELERATE) - accel) * max(0, -dotproduct(curdir, wishdir));
670                         }
671                         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
672                         if(getstatf(STAT_MOVEVARS_MAXAIRSTRAFESPEED))
673                                 wishspeed = min(wishspeed, CSQC_GeomLerp(getstatf(STAT_MOVEVARS_MAXAIRSPEED), strafity, getstatf(STAT_MOVEVARS_MAXAIRSTRAFESPEED)));
674                         if(getstatf(STAT_MOVEVARS_AIRSTRAFEACCELERATE))
675                                 accel = CSQC_GeomLerp(getstatf(STAT_MOVEVARS_AIRACCELERATE), strafity, getstatf(STAT_MOVEVARS_AIRSTRAFEACCELERATE));
676                         if(getstatf(STAT_MOVEVARS_AIRSTRAFEACCEL_QW))
677                                 accelqw =
678                                         (((strafity > 0.5 ? getstatf(STAT_MOVEVARS_AIRSTRAFEACCEL_QW) : getstatf(STAT_MOVEVARS_AIRACCEL_QW)) >= 0) ? +1 : -1)
679                                         *
680                                         (1 - CSQC_GeomLerp(1 - fabs(getstatf(STAT_MOVEVARS_AIRACCEL_QW)), strafity, 1 - fabs(getstatf(STAT_MOVEVARS_AIRSTRAFEACCEL_QW))));
681                         // !CPM
682
683 //                      if(getstatf(STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL) && accelerating && input_movevalues_y == 0 && input_movevalues_x != 0)
684 //                              CSQC_ClientMovement_Physics_PM_AirAccelerate(s, wishdir, wishspeed2);
685 //                      else
686                                 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));
687
688                         if(getstatf(STAT_MOVEVARS_AIRCONTROL))
689                                 CSQC_ClientMovement_Physics_CPM_PM_Aircontrol(s, wishdir, wishspeed2);
690                 }
691                 gravity = getstatf(STAT_MOVEVARS_GRAVITY) * getstatf(STAT_MOVEVARS_ENTGRAVITY) * input_timelength;
692                 if(moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
693                         s.velocity_z -= gravity * 0.5;
694                 else
695                         s.velocity_z -= gravity;
696                 CSQC_ClientMovement_Move(s);
697                 if(!(moveflags & MOVEFLAG_NOGRAVITYONGROUND) || !(s.pmove_flags & PMF_ONGROUND))
698                 {
699                         if(moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
700                                 s.velocity_z -= gravity * 0.5;
701                 }
702         }
703 }
704
705 void CSQC_ClientMovement_PlayerMove(entity s)
706 {
707         //Con_Printf(" %f", frametime);
708         if (!(input_buttons & 2)) // !jump
709                 s.pmove_flags &= ~PMF_JUMP_HELD; // canjump = true
710         pmove_waterjumptime -= input_timelength;
711         CSQC_ClientMovement_UpdateStatus(s);
712         // TODO
713 //      if (s.waterlevel >= WATERLEVEL_SWIMMING)
714 //              CL_ClientMovement_Physics_Swim(s);
715 //      else
716                 CSQC_ClientMovement_Physics_Walk(s);
717 }
718
719 void CSQC_ClientMovement_PlayerMove_Frame(entity s)
720 {
721         // if a move is more than 50ms, do it as two moves (matching qwsv)
722         //Con_Printf("%i ", s.cmd.msec);
723         if(input_timelength > 0.0005)
724         {
725                 if (input_timelength > 0.05)
726                 {
727                         input_timelength /= 2;
728                         CSQC_ClientMovement_PlayerMove(s);
729                 }
730                 CSQC_ClientMovement_PlayerMove(s);
731         }
732         else
733         {
734                 // we REALLY need this handling to happen, even if the move is not executed
735                 if (!(input_buttons & 2)) // !jump
736                         s.pmove_flags &= ~PMF_JUMP_HELD; // canjump = true
737         }
738 }
739
740 void CSQCPlayer_Physics(void)
741 {
742         switch(autocvar_cl_movement)
743         {
744                 case 1: runstandardplayerphysics(self); break;
745                 case 2: CSQC_ClientMovement_PlayerMove_Frame(self); break;
746         }
747 }
748 #undef vlen2
749
750 void CSQCPlayer_PredictTo(float endframe, float apply_error)
751 {
752         CSQCPlayer_Unpredict();
753         if(apply_error)
754         {
755                 self.origin += CSQCPlayer_GetPredictionErrorO();
756                 self.velocity += CSQCPlayer_GetPredictionErrorV();
757         }
758         CSQCPlayer_SetMinsMaxs();
759
760         csqcplayer_status = CSQCPLAYERSTATUS_PREDICTED;
761
762 #if 0
763         // we don't need this
764         // darkplaces makes servercommandframe == 0 in these cases anyway
765         if (getstatf(STAT_HEALTH) <= 0)
766         {
767                 csqcplayer_moveframe = clientcommandframe;
768                 getinputstate(csqcplayer_moveframe-1);
769                 print("the Weird code path got hit\n");
770                 return;
771         }
772 #endif
773
774         if(csqcplayer_moveframe >= endframe)
775         {
776                 getinputstate(csqcplayer_moveframe - 1);
777         }
778         else
779         {
780                 do
781                 {
782                         if (!getinputstate(csqcplayer_moveframe))
783                                 break;
784                         CSQCPlayer_Physics();
785                         CSQCPlayer_SetMinsMaxs();
786                         csqcplayer_moveframe++;
787                 }
788                 while(csqcplayer_moveframe < endframe);
789         }
790
791         //add in anything that was applied after (for low packet rate protocols)
792         input_angles = view_angles;
793 }
794
795 float CSQCPlayer_IsLocalPlayer()
796 {
797         return (self == csqcplayer);
798 }
799
800 void(entity e, float fl) V_CalcRefdef = #640; // DP_CSQC_V_CALCREFDEF
801
802 void CSQCPlayer_SetCamera()
803 {
804         vector v0;
805         v0 = pmove_vel; // TRICK: pmove_vel is set by the engine when we get here. No need to network velocity
806
807         if(csqcplayer)
808         {
809                 entity oldself;
810                 oldself = self;
811                 self = csqcplayer;
812
813 #ifdef COMPAT_XON050_ENGINE
814                 if(servercommandframe == 0 || clientcommandframe == 0 || !(checkextension("DP_CSQC_V_CALCREFDEF") || checkextension("DP_CSQC_V_CALCREFDEF_WIP1")))
815 #else
816                 if(servercommandframe == 0 || clientcommandframe == 0)
817 #endif
818                 {
819                         InterpolateOrigin_Do();
820                         self.view_ofs = '0 0 1' * getstati(STAT_VIEWHEIGHT);
821
822                         // get crouch state from the server
823                         if(getstati(STAT_VIEWHEIGHT) == PL_VIEW_OFS_z)
824                                 self.pmove_flags &= ~PMF_DUCKED;
825                         else if(getstati(STAT_VIEWHEIGHT) == PL_CROUCH_VIEW_OFS_z)
826                                 self.pmove_flags |= PMF_DUCKED;
827
828                         // get onground state from the server
829                         if(pmove_onground)
830                                 self.pmove_flags |= PMF_ONGROUND;
831                         else
832                                 self.pmove_flags &= ~PMF_ONGROUND;
833
834                         CSQCPlayer_SetMinsMaxs();
835
836                         // override it back just in case
837                         self.view_ofs = '0 0 1' * getstati(STAT_VIEWHEIGHT);
838
839                         // set velocity
840                         self.velocity = v0;
841                 }
842                 else
843                 {
844                         float flg = self.iflags;
845                         self.iflags &= ~(IFLAG_ORIGIN | IFLAG_ANGLES);
846                         InterpolateOrigin_Do();
847                         self.iflags = flg;
848
849                         if(csqcplayer_status == CSQCPLAYERSTATUS_FROMSERVER)
850                         {
851                                 vector o, v;
852                                 o = self.origin;
853                                 v = v0;
854                                 csqcplayer_status = CSQCPLAYERSTATUS_PREDICTED;
855                                 CSQCPlayer_PredictTo(servercommandframe + 1, FALSE);
856                                 CSQCPlayer_SetPredictionError(self.origin - o, self.velocity - v, pmove_onground - !!(self.pmove_flags & PMF_ONGROUND));
857                                 self.origin = o;
858                                 self.velocity = v;
859
860                                 // get crouch state from the server
861                                 if(getstati(STAT_VIEWHEIGHT) == PL_VIEW_OFS_z)
862                                         self.pmove_flags &= ~PMF_DUCKED;
863                                 else if(getstati(STAT_VIEWHEIGHT) == PL_CROUCH_VIEW_OFS_z)
864                                         self.pmove_flags |= PMF_DUCKED;
865
866                                 // get onground state from the server
867                                 if(pmove_onground)
868                                         self.pmove_flags |= PMF_ONGROUND;
869                                 else
870                                         self.pmove_flags &= ~PMF_ONGROUND;
871
872                                 CSQCPlayer_SavePrediction();
873                         }
874                         CSQCPlayer_PredictTo(clientcommandframe + 1, TRUE);
875
876 #ifdef CSQCMODEL_SERVERSIDE_CROUCH
877                         // get crouch state from the server (LAG)
878                         if(getstati(STAT_VIEWHEIGHT) == PL_VIEW_OFS_z)
879                                 self.pmove_flags &= ~PMF_DUCKED;
880                         else if(getstati(STAT_VIEWHEIGHT) == PL_CROUCH_VIEW_OFS_z)
881                                 self.pmove_flags |= PMF_DUCKED;
882 #endif
883
884                         CSQCPlayer_SetMinsMaxs();
885
886                         self.angles_y = input_angles_y;
887                 }
888
889                 // relink
890                 setorigin(self, self.origin);
891
892                 self = oldself;
893         }
894
895         entity view;
896 #ifdef COMPAT_XON050_ENGINE
897         view = CSQCModel_server2csqc((spectatee_status > 0) ? spectatee_status : player_localentnum);
898 #else
899         view = CSQCModel_server2csqc(player_localentnum);
900 #endif
901
902         if(view && view != csqcplayer)
903         {
904                 entity oldself = self;
905                 self = view;
906                 InterpolateOrigin_Do();
907                 self.view_ofs = '0 0 1' * getstati(STAT_VIEWHEIGHT);
908                 self = oldself;
909         }
910
911 #ifdef COMPAT_XON050_ENGINE
912         if(view && !(checkextension("DP_CSQC_V_CALCREFDEF") || checkextension("DP_CSQC_V_CALCREFDEF_WIP1")))
913         {
914                 // legacy code, not totally correct, but good enough for not having V_CalcRefdef
915                 setproperty(VF_ORIGIN, view.origin + '0 0 1' * getstati(STAT_VIEWHEIGHT));
916                 setproperty(VF_ANGLES, view_angles);
917         }
918         else
919 #endif
920         if(view)
921         {
922                 var float refdefflags = 0;
923
924                 if(view.csqcmodel_teleported)
925                         refdefflags |= REFDEFFLAG_TELEPORTED;
926
927                 if(input_buttons & 4)
928                         refdefflags |= REFDEFFLAG_JUMPING;
929
930                 // note: these two only work in WIP2, but are harmless in WIP1
931                 if(getstati(STAT_HEALTH) <= 0)
932                         refdefflags |= REFDEFFLAG_DEAD;
933
934                 if(intermission)
935                         refdefflags |= REFDEFFLAG_INTERMISSION;
936
937                 V_CalcRefdef(view, refdefflags);
938         }
939         else
940         {
941                 // FIXME by CSQC spec we have to do this:
942                 // but it breaks chase cam
943                 /*
944                 setproperty(VF_ORIGIN, pmove_org + '0 0 1' * getstati(STAT_VIEWHEIGHT));
945                 setproperty(VF_ANGLES, view_angles);
946                 */
947         }
948
949         { CSQCPLAYER_HOOK_POSTCAMERASETUP }
950 }
951
952 void CSQCPlayer_Remove()
953 {
954         csqcplayer = world;
955         cvar_settemp("cl_movement_replay", "1");
956 }
957
958 float CSQCPlayer_PreUpdate()
959 {
960         if(self != csqcplayer)
961                 return 0;
962         if(csqcplayer_status != CSQCPLAYERSTATUS_FROMSERVER)
963                 CSQCPlayer_Unpredict();
964         return 1;
965 }
966
967 float CSQCPlayer_PostUpdate()
968 {
969         if(self.entnum != player_localnum + 1)
970                 return 0;
971         csqcplayer = self;
972         csqcplayer_status = CSQCPLAYERSTATUS_FROMSERVER;
973         cvar_settemp("cl_movement_replay", "0");
974         self.entremove = CSQCPlayer_Remove;
975         return 1;
976 }