]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/csqcmodellib/cl_player.qc
Switch from cvar() to getstatf()
[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
25 // engine stuff
26 #define REFDEFFLAG_TELEPORTED 1
27 #define REFDEFFLAG_JUMPING 2
28 float pmove_onground; // weird engine flag we shouldn't really use but have to for now
29
30 vector csqcplayer_origin, csqcplayer_velocity;
31 float csqcplayer_sequence, player_pmflags;
32 float csqcplayer_moveframe;
33 vector csqcplayer_predictionerroro;
34 vector csqcplayer_predictionerrorv;
35 float csqcplayer_predictionerrortime;
36 float csqcplayer_predictionerrorfactor;
37
38 vector CSQCPlayer_GetPredictionErrorO()
39 {
40         if(time >= csqcplayer_predictionerrortime)
41                 return '0 0 0';
42         return csqcplayer_predictionerroro * (csqcplayer_predictionerrortime - time) * csqcplayer_predictionerrorfactor;
43 }
44
45 vector CSQCPlayer_GetPredictionErrorV()
46 {
47         if(time >= csqcplayer_predictionerrortime)
48                 return '0 0 0';
49         return csqcplayer_predictionerrorv * (csqcplayer_predictionerrortime - time) * csqcplayer_predictionerrorfactor;
50 }
51
52 void CSQCPlayer_SetPredictionError(vector o, vector v, float onground_diff)
53 {
54         // error too big to compensate, we LIKELY hit a teleport or a
55         // jumppad, or it's a jump time disagreement that'll get fixed
56         // next frame
57
58         // FIXME we sometimes have disagreement in order of jump velocity. Do not act on them!
59         /*
60         // commented out as this one did not help
61         if(onground_diff)
62         {
63                 printf("ONGROUND MISMATCH: %d x=%v v=%v\n", onground_diff, o, v);
64                 return;
65         }
66         */
67         if(vlen(o) > 32 || vlen(v) > 192)
68         {
69                 //printf("TOO BIG: x=%v v=%v\n", o, v);
70                 return;
71         }
72
73         if(!autocvar_cl_movement_errorcompensation)
74         {
75                 csqcplayer_predictionerrorfactor = 0;
76                 return;
77         }
78
79         csqcplayer_predictionerroro = CSQCPlayer_GetPredictionErrorO() + o;
80         csqcplayer_predictionerrorv = CSQCPlayer_GetPredictionErrorV() + v;
81         csqcplayer_predictionerrorfactor = autocvar_cl_movement_errorcompensation / ticrate;
82         csqcplayer_predictionerrortime = time + 1.0 / csqcplayer_predictionerrorfactor;
83 }
84
85 void CSQCPlayer_Unpredict()
86 {
87         if(csqcplayer_status == CSQCPLAYERSTATUS_UNPREDICTED)
88                 return;
89         if(csqcplayer_status != CSQCPLAYERSTATUS_PREDICTED)
90                 error("Cannot unpredict in current status");
91         self.origin = csqcplayer_origin;
92         self.velocity = csqcplayer_velocity;
93         csqcplayer_moveframe = csqcplayer_sequence+1; //+1 because the recieved frame has the move already done (server side)
94         self.pmove_flags = player_pmflags;
95 }
96
97 void CSQCPlayer_SetMinsMaxs()
98 {
99         if(self.pmove_flags & PMF_DUCKED)
100         {
101                 self.mins = PL_CROUCH_MIN;
102                 self.maxs = PL_CROUCH_MAX;
103                 self.view_ofs = PL_CROUCH_VIEW_OFS;
104         }
105         else
106         {
107                 self.mins = PL_MIN;
108                 self.maxs = PL_MAX;
109                 self.view_ofs = PL_VIEW_OFS;
110         }
111 }
112
113 void CSQCPlayer_SavePrediction()
114 {
115         player_pmflags = self.pmove_flags;
116         csqcplayer_origin = self.origin;
117         csqcplayer_velocity = self.velocity;
118         csqcplayer_sequence = servercommandframe;
119         csqcplayer_status = CSQCPLAYERSTATUS_PREDICTED;
120 }
121
122 // TODO: cls.protocol == PROTOCOL_QUAKEWORLD ?
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 //                              if (cls.protocol == PROTOCOL_QUAKEWORLD)
613                                         tracebox(neworigin2, s.mins, s.maxs, neworigin3, MOVE_NORMAL, s);
614 //                              else
615 //                                      traceline(neworigin2, neworigin3, MOVE_NORMAL, s);
616                                 if (trace_fraction == 1 && !trace_startsolid)
617                                         friction *= getstatf(STAT_MOVEVARS_EDGEFRICTION);
618                         }
619                         // apply ground friction
620                         f = 1 - input_timelength * friction * ((f < getstatf(STAT_MOVEVARS_STOPSPEED)) ? (getstatf(STAT_MOVEVARS_STOPSPEED) / f) : 1);
621                         f = max(f, 0);
622                         s.velocity *= f;
623                 }
624                 addspeed = wishspeed - dotproduct(s.velocity, wishdir);
625                 if (addspeed > 0)
626                 {
627                         accelspeed = min(getstatf(STAT_MOVEVARS_ACCELERATE) * input_timelength * wishspeed, addspeed);
628                         s.velocity += accelspeed * wishdir;
629                 }
630                 gravity = getstatf(STAT_MOVEVARS_GRAVITY) * getstatf(STAT_MOVEVARS_ENTGRAVITY) * input_timelength;
631                 if(!(moveflags & MOVEFLAG_NOGRAVITYONGROUND))
632                 {
633                         if(moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
634                                 s.velocity_z -= gravity * 0.5;
635                         else
636                                 s.velocity_z -= gravity;
637                 }
638 //              if (cls.protocol == PROTOCOL_QUAKEWORLD)
639                         s.velocity_z = 0;
640                 if (vlen2(s.velocity))
641                         CSQC_ClientMovement_Move(s);
642                 if(!(moveflags & MOVEFLAG_NOGRAVITYONGROUND) || !(s.pmove_flags & PMF_ONGROUND))
643                 {
644                         if(moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
645                                 s.velocity_z -= gravity * 0.5;
646                 }
647         }
648         else
649         {
650                 if (pmove_waterjumptime <= 0)
651                 {
652                         // apply air speed limit
653                         float accel, wishspeed0, wishspeed2, accelqw, strafity;
654                         float accelerating;
655
656                         accelqw = getstatf(STAT_MOVEVARS_AIRACCEL_QW);
657                         wishspeed0 = wishspeed;
658                         wishspeed = min(wishspeed, getstatf(STAT_MOVEVARS_MAXAIRSPEED));
659                         if (s.pmove_flags & PMF_DUCKED)
660                                 wishspeed *= 0.5;
661                         accel = getstatf(STAT_MOVEVARS_AIRACCELERATE);
662
663                         accelerating = (dotproduct(s.velocity, wishdir) > 0);
664                         wishspeed2 = wishspeed;
665
666                         // CPM: air control
667                         if(getstatf(STAT_MOVEVARS_AIRSTOPACCELERATE) != 0)
668                         {
669                                 vector curdir;
670                                 curdir_x = s.velocity_x;
671                                 curdir_y = s.velocity_y;
672                                 curdir_z = 0;
673                                 curdir = normalize(curdir);
674                                 accel = accel + (getstatf(STAT_MOVEVARS_AIRSTOPACCELERATE) - accel) * max(0, -dotproduct(curdir, wishdir));
675                         }
676                         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
677                         if(getstatf(STAT_MOVEVARS_MAXAIRSTRAFESPEED))
678                                 wishspeed = min(wishspeed, CSQC_GeomLerp(getstatf(STAT_MOVEVARS_MAXAIRSPEED), strafity, getstatf(STAT_MOVEVARS_MAXAIRSTRAFESPEED)));
679                         if(getstatf(STAT_MOVEVARS_AIRSTRAFEACCELERATE))
680                                 accel = CSQC_GeomLerp(getstatf(STAT_MOVEVARS_AIRACCELERATE), strafity, getstatf(STAT_MOVEVARS_AIRSTRAFEACCELERATE));
681                         if(getstatf(STAT_MOVEVARS_AIRSTRAFEACCEL_QW))
682                                 accelqw =
683                                         (((strafity > 0.5 ? getstatf(STAT_MOVEVARS_AIRSTRAFEACCEL_QW) : getstatf(STAT_MOVEVARS_AIRACCEL_QW)) >= 0) ? +1 : -1)
684                                         *
685                                         (1 - CSQC_GeomLerp(1 - fabs(getstatf(STAT_MOVEVARS_AIRACCEL_QW)), strafity, 1 - fabs(getstatf(STAT_MOVEVARS_AIRSTRAFEACCEL_QW))));
686                         // !CPM
687
688 //                      if(getstatf(STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL) && accelerating && input_movevalues_y == 0 && input_movevalues_x != 0)
689 //                              CSQC_ClientMovement_Physics_PM_AirAccelerate(s, wishdir, wishspeed2);
690 //                      else
691                                 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));
692
693                         if(getstatf(STAT_MOVEVARS_AIRCONTROL))
694                                 CSQC_ClientMovement_Physics_CPM_PM_Aircontrol(s, wishdir, wishspeed2);
695                 }
696                 gravity = getstatf(STAT_MOVEVARS_GRAVITY) * getstatf(STAT_MOVEVARS_ENTGRAVITY) * input_timelength;
697                 if(moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
698                         s.velocity_z -= gravity * 0.5;
699                 else
700                         s.velocity_z -= gravity;
701                 CSQC_ClientMovement_Move(s);
702                 if(!(moveflags & MOVEFLAG_NOGRAVITYONGROUND) || !(s.pmove_flags & PMF_ONGROUND))
703                 {
704                         if(moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
705                                 s.velocity_z -= gravity * 0.5;
706                 }
707         }
708 }
709
710 void CSQC_ClientMovement_PlayerMove(entity s)
711 {
712         //Con_Printf(" %f", frametime);
713         if (!(input_buttons & 2)) // !jump
714                 s.pmove_flags &= ~PMF_JUMP_HELD; // canjump = true
715         pmove_waterjumptime -= input_timelength;
716         CSQC_ClientMovement_UpdateStatus(s);
717         // TODO
718 //      if (s.waterlevel >= WATERLEVEL_SWIMMING)
719 //              CL_ClientMovement_Physics_Swim(s);
720 //      else
721                 CSQC_ClientMovement_Physics_Walk(s);
722 }
723
724 void CSQC_ClientMovement_PlayerMove_Frame(entity s)
725 {
726         // if a move is more than 50ms, do it as two moves (matching qwsv)
727         //Con_Printf("%i ", s.cmd.msec);
728         if(input_timelength > 0.0005)
729         {
730                 if (input_timelength > 0.05)
731                 {
732                         input_timelength /= 2;
733                         CSQC_ClientMovement_PlayerMove(s);
734                 }
735                 CSQC_ClientMovement_PlayerMove(s);
736         }
737         else
738         {
739                 // we REALLY need this handling to happen, even if the move is not executed
740                 if (!(input_buttons & 2)) // !jump
741                         s.pmove_flags &= ~PMF_JUMP_HELD; // canjump = true
742         }
743 }
744
745 void CSQCPlayer_Physics(void)
746 {
747         switch(cvar("cl_movement")) {
748                 case 2: CSQC_ClientMovement_PlayerMove_Frame(self); break;
749                 case 1: runstandardplayerphysics(self); break;
750                 default: break;
751         }
752 }
753 #undef vlen2
754
755 void CSQCPlayer_PredictTo(float endframe, float apply_error)
756 {
757         CSQCPlayer_Unpredict();
758         if(apply_error)
759         {
760                 self.origin += CSQCPlayer_GetPredictionErrorO();
761                 self.velocity += CSQCPlayer_GetPredictionErrorV();
762         }
763         CSQCPlayer_SetMinsMaxs();
764
765         csqcplayer_status = CSQCPLAYERSTATUS_PREDICTED;
766
767 #if 0
768         // we don't need this
769         // darkplaces makes servercommandframe == 0 in these cases anyway
770         if (getstatf(STAT_HEALTH) <= 0)
771         {
772                 csqcplayer_moveframe = clientcommandframe;
773                 getinputstate(csqcplayer_moveframe-1);
774                 print("the Weird code path got hit\n");
775                 return;
776         }
777 #endif
778
779         if(csqcplayer_moveframe >= endframe)
780         {
781                 getinputstate(csqcplayer_moveframe - 1);
782         }
783         else
784         {
785                 do
786                 {
787                         if (!getinputstate(csqcplayer_moveframe))
788                                 break;
789                         CSQCPlayer_Physics();
790                         CSQCPlayer_SetMinsMaxs();
791                         csqcplayer_moveframe++;
792                 }
793                 while(csqcplayer_moveframe < endframe);
794         }
795
796         //add in anything that was applied after (for low packet rate protocols)
797         input_angles = view_angles;
798 }
799
800 float CSQCPlayer_IsLocalPlayer()
801 {
802         return (self == csqcplayer);
803 }
804
805 void(entity e, float fl) V_CalcRefdef = #640; // DP_CSQC_V_CALCREFDEF
806
807 void CSQCPlayer_SetCamera()
808 {
809         vector v0;
810         v0 = pmove_vel; // TRICK: pmove_vel is set by the engine when we get here. No need to network velocity
811
812         if(csqcplayer)
813         {
814                 entity oldself;
815                 oldself = self;
816                 self = csqcplayer;
817
818 #ifdef COMPAT_XON050_ENGINE
819                 if(servercommandframe == 0 || clientcommandframe == 0 || !(checkextension("DP_CSQC_V_CALCREFDEF") || checkextension("DP_CSQC_V_CALCREFDEF_WIP1")))
820 #else
821                 if(servercommandframe == 0 || clientcommandframe == 0)
822 #endif
823                 {
824                         InterpolateOrigin_Do();
825                         self.view_ofs = '0 0 1' * getstati(STAT_VIEWHEIGHT);
826
827                         // get crouch state from the server
828                         if(getstati(STAT_VIEWHEIGHT) == PL_VIEW_OFS_z)
829                                 self.pmove_flags &= ~PMF_DUCKED;
830                         else if(getstati(STAT_VIEWHEIGHT) == PL_CROUCH_VIEW_OFS_z)
831                                 self.pmove_flags |= PMF_DUCKED;
832
833                         // get onground state from the server
834                         if(pmove_onground)
835                                 self.pmove_flags |= PMF_ONGROUND;
836                         else
837                                 self.pmove_flags &= ~PMF_ONGROUND;
838
839                         CSQCPlayer_SetMinsMaxs();
840
841                         // override it back just in case
842                         self.view_ofs = '0 0 1' * getstati(STAT_VIEWHEIGHT);
843
844                         // set velocity
845                         self.velocity = v0;
846                 }
847                 else
848                 {
849                         float flg = self.iflags;
850                         self.iflags &= ~(IFLAG_ORIGIN | IFLAG_ANGLES);
851                         InterpolateOrigin_Do();
852                         self.iflags = flg;
853
854                         if(csqcplayer_status == CSQCPLAYERSTATUS_FROMSERVER)
855                         {
856                                 vector o, v;
857                                 o = self.origin;
858                                 v = v0;
859                                 csqcplayer_status = CSQCPLAYERSTATUS_PREDICTED;
860                                 CSQCPlayer_PredictTo(servercommandframe + 1, FALSE);
861                                 CSQCPlayer_SetPredictionError(self.origin - o, self.velocity - v, pmove_onground - !!(self.pmove_flags & PMF_ONGROUND));
862                                 self.origin = o;
863                                 self.velocity = v;
864
865                                 // get crouch state from the server
866                                 if(getstati(STAT_VIEWHEIGHT) == PL_VIEW_OFS_z)
867                                         self.pmove_flags &= ~PMF_DUCKED;
868                                 else if(getstati(STAT_VIEWHEIGHT) == PL_CROUCH_VIEW_OFS_z)
869                                         self.pmove_flags |= PMF_DUCKED;
870
871                                 // get onground state from the server
872                                 if(pmove_onground)
873                                         self.pmove_flags |= PMF_ONGROUND;
874                                 else
875                                         self.pmove_flags &= ~PMF_ONGROUND;
876
877                                 CSQCPlayer_SavePrediction();
878                         }
879                         CSQCPlayer_PredictTo(clientcommandframe + 1, TRUE);
880
881 #ifdef CSQCMODEL_SERVERSIDE_CROUCH
882                         // get crouch state from the server (LAG)
883                         if(getstati(STAT_VIEWHEIGHT) == PL_VIEW_OFS_z)
884                                 self.pmove_flags &= ~PMF_DUCKED;
885                         else if(getstati(STAT_VIEWHEIGHT) == PL_CROUCH_VIEW_OFS_z)
886                                 self.pmove_flags |= PMF_DUCKED;
887 #endif
888
889                         CSQCPlayer_SetMinsMaxs();
890
891                         self.angles_y = input_angles_y;
892                 }
893
894                 // relink
895                 setorigin(self, self.origin);
896
897                 self = oldself;
898         }
899
900         entity view;
901 #ifdef COMPAT_XON050_ENGINE
902         view = CSQCModel_server2csqc((spectatee_status > 0) ? spectatee_status : player_localentnum);
903 #else
904         view = CSQCModel_server2csqc(player_localentnum);
905 #endif
906
907         if(view && view != csqcplayer)
908         {
909                 entity oldself = self;
910                 self = view;
911                 InterpolateOrigin_Do();
912                 self.view_ofs = '0 0 1' * getstati(STAT_VIEWHEIGHT);
913                 self = oldself;
914         }
915
916 #ifdef COMPAT_XON050_ENGINE
917         if(view && !(checkextension("DP_CSQC_V_CALCREFDEF") || checkextension("DP_CSQC_V_CALCREFDEF_WIP1")))
918         {
919                 // legacy code, not totally correct, but good enough for not having V_CalcRefdef
920                 setproperty(VF_ORIGIN, view.origin + '0 0 1' * getstati(STAT_VIEWHEIGHT));
921                 setproperty(VF_ANGLES, view_angles);
922         }
923         else
924 #endif
925         if(view)
926         {
927                 var float refdefflags = 0;
928
929                 if(view.csqcmodel_teleported)
930                         refdefflags |= REFDEFFLAG_TELEPORTED;
931
932                 if(input_buttons & 4)
933                         refdefflags |= REFDEFFLAG_JUMPING;
934
935                 // note: these two only work in WIP2, but are harmless in WIP1
936                 if(getstati(STAT_HEALTH) <= 0)
937                         refdefflags |= REFDEFFLAG_DEAD;
938
939                 if(intermission)
940                         refdefflags |= REFDEFFLAG_INTERMISSION;
941
942                 V_CalcRefdef(view, refdefflags);
943         }
944         else
945         {
946                 // FIXME by CSQC spec we have to do this:
947                 // but it breaks chase cam
948                 /*
949                 setproperty(VF_ORIGIN, pmove_org + '0 0 1' * getstati(STAT_VIEWHEIGHT));
950                 setproperty(VF_ANGLES, view_angles);
951                 */
952         }
953
954         { CSQCPLAYER_HOOK_POSTCAMERASETUP }
955 }
956
957 void CSQCPlayer_Remove()
958 {
959         csqcplayer = world;
960         cvar_settemp("cl_movement_replay", "1");
961 }
962
963 float CSQCPlayer_PreUpdate()
964 {
965         if(self != csqcplayer)
966                 return 0;
967         if(csqcplayer_status != CSQCPLAYERSTATUS_FROMSERVER)
968                 CSQCPlayer_Unpredict();
969         return 1;
970 }
971
972 float CSQCPlayer_PostUpdate()
973 {
974         if(self.entnum != player_localnum + 1)
975                 return 0;
976         csqcplayer = self;
977         csqcplayer_status = CSQCPLAYERSTATUS_FROMSERVER;
978         cvar_settemp("cl_movement_replay", "0");
979         self.entremove = CSQCPlayer_Remove;
980         return 1;
981 }