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