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