2 * Copyright (c) 2011 Rudolf Polzer
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:
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
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
23 var float autocvar_cl_movement_errorcompensation = 0;
24 var float autocvar_cl_movement = 2; // testing purposes
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
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;
39 vector CSQCPlayer_GetPredictionErrorO()
41 if(time >= csqcplayer_predictionerrortime)
43 return csqcplayer_predictionerroro * (csqcplayer_predictionerrortime - time) * csqcplayer_predictionerrorfactor;
46 vector CSQCPlayer_GetPredictionErrorV()
48 if(time >= csqcplayer_predictionerrortime)
50 return csqcplayer_predictionerrorv * (csqcplayer_predictionerrortime - time) * csqcplayer_predictionerrorfactor;
53 void CSQCPlayer_SetPredictionError(vector o, vector v, float onground_diff)
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
59 // FIXME we sometimes have disagreement in order of jump velocity. Do not act on them!
61 // commented out as this one did not help
64 printf("ONGROUND MISMATCH: %d x=%v v=%v\n", onground_diff, o, v);
68 if(vlen(o) > 32 || vlen(v) > 192)
70 //printf("TOO BIG: x=%v v=%v\n", o, v);
74 if(!autocvar_cl_movement_errorcompensation)
76 csqcplayer_predictionerrorfactor = 0;
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;
86 void CSQCPlayer_Unpredict()
88 if(csqcplayer_status == CSQCPLAYERSTATUS_UNPREDICTED)
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;
98 void CSQCPlayer_SetMinsMaxs()
100 if(self.pmove_flags & PMF_DUCKED)
102 self.mins = PL_CROUCH_MIN;
103 self.maxs = PL_CROUCH_MAX;
104 self.view_ofs = PL_CROUCH_VIEW_OFS;
110 self.view_ofs = PL_VIEW_OFS;
114 void CSQCPlayer_SavePrediction()
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;
123 // TODO: water prediction
124 float pmove_waterjumptime; // weird engine flag we shouldn't really use but have to for now
125 // TODO: move to a common header
126 #define vlen2(v) dotproduct(v, v)
127 vector _AngleVectors_forward, _AngleVectors_right, _AngleVectors_up;
128 void _AngleVectors (vector _angles, vector _forward, vector _right, vector _up)
130 float angle, sr, sp, sy, cr, cp, cy;
132 angle = _angles_y * (M_PI*2 / 360);
135 angle = _angles_x * (M_PI*2 / 360);
145 angle = _angles_z * (M_PI*2 / 360);
149 _right_x = -1*(sr*sp*cy+cr*-sy);
150 _right_y = -1*(sr*sp*sy+cr*cy);
151 _right_z = -1*(sr*cp);
153 _up_x = (cr*sp*cy+-sr*-sy);
154 _up_y = (cr*sp*sy+-sr*cy);
167 _AngleVectors_forward = _forward;
168 _AngleVectors_right = _right;
169 _AngleVectors_up = _up;
171 #define AngleVectors(angles, forward, right, up) do { \
172 _AngleVectors(angles, forward, right, up); \
173 forward = _AngleVectors_forward; \
174 right = _AngleVectors_right; \
175 up = _AngleVectors_up; \
178 // TODO: move these elsewhere
179 vector cl_playerstandmins = '-16 -16 -24';
180 vector cl_playerstandmaxs = '16 16 45';
181 vector cl_playercrouchmins = '-16 -16 -24';
182 vector cl_playercrouchmaxs = '16 16 25';
184 const float unstick_count = 27;
185 vector unstick_offsets[unstick_count] =
187 // 1 no nudge (just return the original if this test passes)
190 ' 0.000 0.000 0.125', '0.000 0.000 -0.125',
191 '-0.125 0.000 0.000', '0.125 0.000 0.000',
192 ' 0.000 -0.125 0.000', '0.000 0.125 0.000',
193 // 4 diagonal flat nudges
194 '-0.125 -0.125 0.000', '0.125 -0.125 0.000',
195 '-0.125 0.125 0.000', '0.125 0.125 0.000',
196 // 8 diagonal upward nudges
197 '-0.125 0.000 0.125', '0.125 0.000 0.125',
198 ' 0.000 -0.125 0.125', '0.000 0.125 0.125',
199 '-0.125 -0.125 0.125', '0.125 -0.125 0.125',
200 '-0.125 0.125 0.125', '0.125 0.125 0.125',
201 // 8 diagonal downward nudges
202 '-0.125 0.000 -0.125', '0.125 0.000 -0.125',
203 ' 0.000 -0.125 -0.125', '0.000 0.125 -0.125',
204 '-0.125 -0.125 -0.125', '0.125 -0.125 -0.125',
205 '-0.125 0.125 -0.125', '0.125 0.125 -0.125',
208 float CSQC_ClientMovement_Unstick(entity s)
212 for (i = 0; i < unstick_count; i++)
214 neworigin = unstick_offsets[i] + s.origin;
215 tracebox(neworigin, cl_playercrouchmins, cl_playercrouchmaxs, neworigin, MOVE_NORMAL, s);
216 if (!trace_startsolid)
218 s.origin = neworigin;
222 // if all offsets failed, give up
226 void CSQC_ClientMovement_UpdateStatus(entity s)
229 vector origin1, origin2;
231 // make sure player is not stuck
232 CSQC_ClientMovement_Unstick(s);
235 if (input_buttons & 16)
237 // wants to crouch, this always works..
238 if (!s.pmove_flags & PMF_DUCKED)
239 s.pmove_flags |= PMF_DUCKED;
243 // wants to stand, if currently crouching we need to check for a
245 if (s.pmove_flags & PMF_DUCKED)
247 tracebox(s.origin, cl_playerstandmins, cl_playerstandmaxs, s.origin, MOVE_NORMAL, s);
248 if (!trace_startsolid)
249 s.pmove_flags &= ~PMF_DUCKED;
252 if (s.pmove_flags & PMF_DUCKED)
254 s.mins = cl_playercrouchmins;
255 s.maxs = cl_playercrouchmaxs;
259 s.mins = cl_playerstandmins;
260 s.maxs = cl_playerstandmaxs;
267 origin2_z -= 1; // -2 causes clientside doublejump bug at above 150fps, raising that to 300fps :)
269 tracebox(origin1, s.mins, s.maxs, origin2, MOVE_NORMAL, s);
270 if(trace_fraction < 1 && trace_plane_normal_z > 0.7)
272 s.pmove_flags |= PMF_ONGROUND;
274 // this code actually "predicts" an impact; so let's clip velocity first
275 f = dotproduct(s.velocity, trace_plane_normal);
276 if(f < 0) // only if moving downwards actually
277 s.velocity -= f * trace_plane_normal;
280 s.pmove_flags &= ~PMF_ONGROUND; // onground = false;
282 // set watertype/waterlevel
284 origin1_z += s.mins_z + 1;
285 s.waterlevel = WATERLEVEL_NONE;
287 // s.watertype = CL_TracePoint(origin1, MOVE_NOMONSTERS, s, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK;
290 // s.waterlevel = WATERLEVEL_WETFEET;
291 // origin1[2] = s.origin[2] + (s.mins[2] + s.maxs[2]) * 0.5f;
292 // if (CL_TracePoint(origin1, MOVE_NOMONSTERS, s, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK)
294 // s.waterlevel = WATERLEVEL_SWIMMING;
295 // origin1[2] = s.origin[2] + 22;
296 // if (CL_TracePoint(origin1, MOVE_NOMONSTERS, s, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK)
297 // s.waterlevel = WATERLEVEL_SUBMERGED;
301 // // water jump prediction
302 // if ((s.pmove_flags & PMF_ONGROUND) || s.velocity_z <= 0 || pmove_waterjumptime <= 0)
303 // pmove_waterjumptime = 0;
306 void CSQC_ClientMovement_Move(entity s)
312 vector currentorigin2;
314 vector primalvelocity;
315 float old_trace1_fraction;
316 vector old_trace1_endpos;
317 vector old_trace1_plane_normal;
318 float old_trace2_fraction;
319 vector old_trace2_plane_normal;
320 CSQC_ClientMovement_UpdateStatus(s);
321 primalvelocity = s.velocity;
322 for (bump = 0, t = input_timelength; bump < 8 && vlen2(s.velocity) > 0; bump++)
324 neworigin = s.origin + t * s.velocity;
325 tracebox(s.origin, s.mins, s.maxs, neworigin, MOVE_NORMAL, s);
326 old_trace1_fraction = trace_fraction;
327 old_trace1_endpos = trace_endpos;
328 old_trace1_plane_normal = trace_plane_normal;
329 if (trace_fraction < 1 && trace_plane_normal_z == 0)
331 // may be a step or wall, try stepping up
332 // first move forward at a higher level
333 currentorigin2 = s.origin;
334 currentorigin2_z += getstatf(STAT_MOVEVARS_STEPHEIGHT);
335 neworigin2 = neworigin;
336 neworigin2_z = s.origin_z + getstatf(STAT_MOVEVARS_STEPHEIGHT);
337 tracebox(currentorigin2, s.mins, s.maxs, neworigin2, MOVE_NORMAL, s);
338 if (!trace_startsolid)
340 // then move down from there
341 currentorigin2 = trace_endpos;
342 neworigin2 = trace_endpos;
343 neworigin2_z = s.origin_z;
344 old_trace2_fraction = trace_fraction;
345 old_trace2_plane_normal = trace_plane_normal;
346 tracebox(currentorigin2, s.mins, s.maxs, neworigin2, MOVE_NORMAL, s);
347 //Con_Printf("%f %f %f %f : %f %f %f %f : %f %f %f %f\n", trace.fraction, trace.endpos[0], trace.endpos[1], trace.endpos[2], trace2.fraction, trace2.endpos[0], trace2.endpos[1], trace2.endpos[2], trace3.fraction, trace3.endpos[0], trace3.endpos[1], trace3.endpos[2]);
348 // accept the new trace if it made some progress
349 if (fabs(trace_endpos_x - old_trace1_endpos_x) >= 0.03125 || fabs(trace_endpos_y - old_trace1_endpos_y) >= 0.03125)
351 trace_fraction = old_trace2_fraction;
352 trace_endpos = trace_endpos;
353 trace_plane_normal = old_trace2_plane_normal;
357 trace_fraction = old_trace1_fraction;
358 trace_endpos = old_trace1_endpos;
359 trace_plane_normal = old_trace1_plane_normal;
364 // check if it moved at all
365 if (trace_fraction >= 0.001)
366 s.origin = trace_endpos;
368 // check if it moved all the way
369 if (trace_fraction == 1)
372 // this is only really needed for nogravityonground combined with gravityunaffectedbyticrate
373 // <LordHavoc> I'm pretty sure I commented it out solely because it seemed redundant
374 // this got commented out in a change that supposedly makes the code match QW better
375 // so if this is broken, maybe put it in an if(cls.protocol != PROTOCOL_QUAKEWORLD) block
376 if (trace_plane_normal_z > 0.7)
377 s.pmove_flags |= PMF_ONGROUND;
379 t -= t * trace_fraction;
381 f = dotproduct(s.velocity, trace_plane_normal);
382 s.velocity -= f * trace_plane_normal;
384 if (pmove_waterjumptime > 0)
385 s.velocity = primalvelocity;
388 float IsMoveInDirection(vector mv, float angle) // key mix factor
390 if(mv_x == 0 && mv_y == 0)
391 return 0; // avoid division by zero
392 angle -= RAD2DEG * atan2(mv_y, mv_x);
393 angle = remainder(angle, 360) / 45;
398 return 1 - fabs(angle);
401 // TODO: remove this and use above function
402 float CSQC_IsMoveInDirection(float forward, float side, float angle)
404 // TODO: move to a common header
405 #define RAD2DEG(a) ((a) * (180.0f / M_PI))
406 #define ANGLEMOD(a) ((a) - 360.0 * floor((a) / 360.0))
407 if(forward == 0 && side == 0)
408 return 0; // avoid division by zero
409 angle -= RAD2DEG(atan2(side, forward));
410 angle = (ANGLEMOD(angle + 180) - 180) / 45;
415 return 1 - fabs(angle);
420 float GeomLerp(float a, float lerp, float b)
436 return a * pow(fabs(b / a), lerp);
439 void CSQC_ClientMovement_Physics_CPM_PM_Aircontrol(entity s, vector wishdir, float wishspeed)
441 float zspeed, xyspeed, dot, k;
444 // this doesn't play well with analog input
445 if(s.movement_x == 0 || s.movement_y != 0)
446 return; // can't control movement if not moving forward or backward
449 k = 32 * (2 * IsMoveInDirection(input_movevalues, 0) - 1);
454 k *= bound(0, wishspeed / getstatf(STAT_MOVEVARS_MAXAIRSPEED), 1);
456 zspeed = s.velocity_z;
458 xyspeed = vlen(s.velocity); s.velocity = normalize(s.velocity);
460 dot = s.velocity * wishdir;
462 if(dot > 0) // we can't change direction while slowing down
464 k *= pow(dot, getstatf(STAT_MOVEVARS_AIRCONTROL_POWER))*input_timelength;
465 xyspeed = max(0, xyspeed - getstatf(STAT_MOVEVARS_AIRCONTROL_PENALTY) * sqrt(max(0, 1 - dot*dot)) * k/32);
466 k *= getstatf(STAT_MOVEVARS_AIRCONTROL);
467 s.velocity = normalize(s.velocity * xyspeed + wishdir * k);
470 s.velocity = s.velocity * xyspeed;
471 s.velocity_z = zspeed;
474 float CSQC_ClientMovement_Physics_AdjustAirAccelQW(float accelqw, float factor)
476 return copysign(bound(0.000001, 1 - (1 - fabs(accelqw)) * factor, 1), accelqw);
479 void CSQC_ClientMovement_Physics_PM_Accelerate(entity s, vector wishdir, float wishspeed, float wishspeed0, float accel, float accelqw, float stretchfactor, float sidefric, float speedlimit)
486 float vel_xy_current;
487 float vel_xy_backward, vel_xy_forward;
490 if(stretchfactor > 0)
491 speedclamp = stretchfactor;
495 speedclamp = -1; // no clamping
500 if(moveflags & MOVEFLAG_Q2AIRACCELERATE)
501 wishspeed0 = wishspeed; // don't need to emulate this Q1 bug
503 vel_straight = dotproduct(s.velocity, wishdir);
504 vel_z = s.velocity_z;
507 vel_perpend = vel_xy - vel_straight * wishdir;
509 step = accel * input_timelength * wishspeed0;
511 vel_xy_current = vlen(vel_xy);
513 accelqw = CSQC_ClientMovement_Physics_AdjustAirAccelQW(accelqw, (speedlimit - bound(wishspeed, vel_xy_current, speedlimit)) / max(1, speedlimit - wishspeed));
514 vel_xy_forward = vel_xy_current + bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw);
515 vel_xy_backward = vel_xy_current - bound(0, wishspeed + vel_xy_current, step) * accelqw - step * (1 - accelqw);
516 if(vel_xy_backward < 0)
517 vel_xy_backward = 0; // not that it REALLY occurs that this would cause wrong behaviour afterwards
519 vel_straight = vel_straight + bound(0, wishspeed - vel_straight, step) * accelqw + step * (1 - accelqw);
521 if(sidefric < 0 && vlen2(vel_perpend))
522 // negative: only apply so much sideways friction to stay below the speed you could get by "braking"
525 f = max(0, 1 + input_timelength * wishspeed * sidefric);
526 fmin = (vel_xy_backward*vel_xy_backward - vel_straight*vel_straight) / vlen2(vel_perpend);
528 // vel_xy_backward*vel_xy_backward - vel_straight*vel_straight > vel_perpend*vel_perpend
529 // vel_xy_backward*vel_xy_backward > vel_straight*vel_straight + vel_perpend*vel_perpend
530 // vel_xy_backward*vel_xy_backward > vel_xy * vel_xy
531 // obviously, this cannot be
537 vel_perpend *= max(fmin, f);
541 vel_perpend *= max(0, 1 - input_timelength * wishspeed * sidefric);
543 s.velocity = vel_perpend + vel_straight * wishdir;
547 float vel_xy_preclamp;
548 vel_xy_preclamp = vlen(s.velocity);
549 if(vel_xy_preclamp > 0) // prevent division by zero
551 vel_xy_current += (vel_xy_forward - vel_xy_current) * speedclamp;
552 if(vel_xy_current < vel_xy_preclamp)
553 s.velocity *= (vel_xy_current / vel_xy_preclamp);
557 s.velocity_z += vel_z;
560 void CSQC_ClientMovement_Physics_PM_AirAccelerate(entity s, vector wishdir, float wishspeed)
562 vector curvel, wishvel, acceldir, curdir;
563 float addspeed, accelspeed, curspeed, f;
571 curspeed = vlen(curvel);
573 if(wishspeed > curspeed * 1.01)
575 wishspeed = min(wishspeed, curspeed + getstatf(STAT_MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL) * getstatf(STAT_MOVEVARS_MAXSPEED) * input_timelength);
579 f = max(0, (getstatf(STAT_MOVEVARS_WARSOWBUNNY_TOPSPEED) - curspeed) / (getstatf(STAT_MOVEVARS_WARSOWBUNNY_TOPSPEED) - getstatf(STAT_MOVEVARS_MAXSPEED)));
580 wishspeed = max(curspeed, getstatf(STAT_MOVEVARS_WARSOWBUNNY_ACCEL)) + getstatf(STAT_MOVEVARS_WARSOWBUNNY_ACCEL) * f * getstatf(STAT_MOVEVARS_WARSOWBUNNY_ACCEL) * input_timelength;
582 wishvel = wishdir * wishspeed;
583 acceldir = wishvel - curvel;
584 addspeed = vlen(acceldir);
585 acceldir = normalize(acceldir);
587 accelspeed = min(addspeed, getstatf(STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL) * getstatf(STAT_MOVEVARS_WARSOWBUNNY_ACCEL) * input_timelength);
589 if(getstatf(STAT_MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO) < 1)
591 curdir = normalize(curvel);
592 dot = acceldir * curdir;
594 acceldir = acceldir - (1 - getstatf(STAT_MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO)) * dot * curdir;
597 s.velocity += accelspeed * acceldir;
600 void CSQC_ClientMovement_Physics_Walk(entity s)
608 vector forward = '0 0 0';
609 vector right = '0 0 0';
615 // jump if on ground with jump button pressed but only if it has been
616 // released at least once since the last jump
617 if (input_buttons & 2)
619 if ((s.pmove_flags & PMF_ONGROUND) && ((s.pmove_flags & PMF_JUMP_HELD) == 0 || !cvar("cl_movement_track_canjump")))
621 s.velocity_z += getstatf(STAT_MOVEVARS_JUMPVELOCITY);
622 s.pmove_flags &= ~PMF_ONGROUND;
623 s.pmove_flags |= PMF_JUMP_HELD; // canjump = false
627 s.pmove_flags &= ~PMF_JUMP_HELD; // canjump = true
629 // calculate movement vector
631 yawangles_y = input_angles_y;
632 AngleVectors(yawangles, forward, right, up);
633 wishvel = input_movevalues_x * forward + input_movevalues_y * right;
635 // split wishvel into wishspeed and wishdir
636 wishspeed = vlen(wishvel);
638 wishdir = wishvel / wishspeed;
642 if ((s.pmove_flags & PMF_ONGROUND))
644 wishspeed = min(wishspeed, getstatf(STAT_MOVEVARS_MAXSPEED));
645 if (s.pmove_flags & PMF_DUCKED)
648 // apply edge friction
649 f = sqrt(s.velocity_x * s.velocity_x + s.velocity_y * s.velocity_y);
652 friction = getstatf(STAT_MOVEVARS_FRICTION);
653 if (getstatf(STAT_MOVEVARS_EDGEFRICTION) != 1)
657 // note: QW uses the full player box for the trace, and yet still
658 // uses s.origin_z + s.mins_z, which is clearly an bug, but
659 // this mimics it for compatibility
660 neworigin2 = s.origin;
661 neworigin2_x += s.velocity_x*(16/f);
662 neworigin2_y += s.velocity_y*(16/f);
663 neworigin2_z += s.mins_z;
664 neworigin3 = neworigin2;
666 traceline(neworigin2, neworigin3, MOVE_NORMAL, s);
667 if (trace_fraction == 1 && !trace_startsolid)
668 friction *= getstatf(STAT_MOVEVARS_EDGEFRICTION);
670 // apply ground friction
671 f = 1 - input_timelength * friction * ((f < getstatf(STAT_MOVEVARS_STOPSPEED)) ? (getstatf(STAT_MOVEVARS_STOPSPEED) / f) : 1);
675 addspeed = wishspeed - dotproduct(s.velocity, wishdir);
678 accelspeed = min(getstatf(STAT_MOVEVARS_ACCELERATE) * input_timelength * wishspeed, addspeed);
679 s.velocity += accelspeed * wishdir;
681 gravity = getstatf(STAT_MOVEVARS_GRAVITY) * getstatf(STAT_MOVEVARS_ENTGRAVITY) * input_timelength;
682 if(!(moveflags & MOVEFLAG_NOGRAVITYONGROUND))
684 if(moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
685 s.velocity_z -= gravity * 0.5;
687 s.velocity_z -= gravity;
689 if (vlen2(s.velocity))
690 CSQC_ClientMovement_Move(s);
691 if(!(moveflags & MOVEFLAG_NOGRAVITYONGROUND) || !(s.pmove_flags & PMF_ONGROUND))
693 if(moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
694 s.velocity_z -= gravity * 0.5;
699 if (pmove_waterjumptime <= 0)
701 // apply air speed limit
702 float accel, wishspeed0, wishspeed2, accelqw, strafity;
705 accelqw = getstatf(STAT_MOVEVARS_AIRACCEL_QW);
706 wishspeed0 = wishspeed;
707 wishspeed = min(wishspeed, getstatf(STAT_MOVEVARS_MAXAIRSPEED));
708 if (s.pmove_flags & PMF_DUCKED)
710 accel = getstatf(STAT_MOVEVARS_AIRACCELERATE);
712 accelerating = (dotproduct(s.velocity, wishdir) > 0);
713 wishspeed2 = wishspeed;
716 if(getstatf(STAT_MOVEVARS_AIRSTOPACCELERATE) != 0)
719 curdir_x = s.velocity_x;
720 curdir_y = s.velocity_y;
722 curdir = normalize(curdir);
723 accel = accel + (getstatf(STAT_MOVEVARS_AIRSTOPACCELERATE) - accel) * max(0, -dotproduct(curdir, wishdir));
725 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
726 if(getstatf(STAT_MOVEVARS_MAXAIRSTRAFESPEED))
727 wishspeed = min(wishspeed, GeomLerp(getstatf(STAT_MOVEVARS_MAXAIRSPEED), strafity, getstatf(STAT_MOVEVARS_MAXAIRSTRAFESPEED)));
728 if(getstatf(STAT_MOVEVARS_AIRSTRAFEACCELERATE))
729 accel = GeomLerp(getstatf(STAT_MOVEVARS_AIRACCELERATE), strafity, getstatf(STAT_MOVEVARS_AIRSTRAFEACCELERATE));
730 if(getstatf(STAT_MOVEVARS_AIRSTRAFEACCEL_QW))
732 (((strafity > 0.5 ? getstatf(STAT_MOVEVARS_AIRSTRAFEACCEL_QW) : getstatf(STAT_MOVEVARS_AIRACCEL_QW)) >= 0) ? +1 : -1)
734 (1 - GeomLerp(1 - fabs(getstatf(STAT_MOVEVARS_AIRACCEL_QW)), strafity, 1 - fabs(getstatf(STAT_MOVEVARS_AIRSTRAFEACCEL_QW))));
737 if(getstatf(STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL) && accelerating && input_movevalues_y == 0 && input_movevalues_x != 0)
738 CSQC_ClientMovement_Physics_PM_AirAccelerate(s, wishdir, wishspeed2);
740 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));
742 if(getstatf(STAT_MOVEVARS_AIRCONTROL))
743 CSQC_ClientMovement_Physics_CPM_PM_Aircontrol(s, wishdir, wishspeed2);
745 gravity = getstatf(STAT_MOVEVARS_GRAVITY) * getstatf(STAT_MOVEVARS_ENTGRAVITY) * input_timelength;
746 if(moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
747 s.velocity_z -= gravity * 0.5;
749 s.velocity_z -= gravity;
750 CSQC_ClientMovement_Move(s);
751 if(!(moveflags & MOVEFLAG_NOGRAVITYONGROUND) || !(s.pmove_flags & PMF_ONGROUND))
753 if(moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
754 s.velocity_z -= gravity * 0.5;
759 void CSQC_ClientMovement_PlayerMove(entity s)
761 //Con_Printf(" %f", frametime);
762 if (!(input_buttons & 2)) // !jump
763 s.pmove_flags &= ~PMF_JUMP_HELD; // canjump = true
764 pmove_waterjumptime -= input_timelength;
765 CSQC_ClientMovement_UpdateStatus(s);
767 // if (s.waterlevel >= WATERLEVEL_SWIMMING)
768 // CL_ClientMovement_Physics_Swim(s);
770 CSQC_ClientMovement_Physics_Walk(s);
773 void CSQC_ClientMovement_PlayerMove_Frame(entity s)
775 // if a move is more than 50ms, do it as two moves (matching qwsv)
776 //Con_Printf("%i ", s.cmd.msec);
777 if(input_timelength > 0.0005)
779 if (input_timelength > 0.05)
781 input_timelength /= 2;
782 CSQC_ClientMovement_PlayerMove(s);
784 CSQC_ClientMovement_PlayerMove(s);
788 // we REALLY need this handling to happen, even if the move is not executed
789 if (!(input_buttons & 2)) // !jump
790 s.pmove_flags &= ~PMF_JUMP_HELD; // canjump = true
794 void CSQCPlayer_Physics(void)
796 switch(autocvar_cl_movement)
798 case 1: runstandardplayerphysics(self); break;
799 case 2: CSQC_ClientMovement_PlayerMove_Frame(self); break;
804 void CSQCPlayer_PredictTo(float endframe, float apply_error)
806 CSQCPlayer_Unpredict();
809 self.origin += CSQCPlayer_GetPredictionErrorO();
810 self.velocity += CSQCPlayer_GetPredictionErrorV();
812 CSQCPlayer_SetMinsMaxs();
814 csqcplayer_status = CSQCPLAYERSTATUS_PREDICTED;
817 // we don't need this
818 // darkplaces makes servercommandframe == 0 in these cases anyway
819 if (getstatf(STAT_HEALTH) <= 0)
821 csqcplayer_moveframe = clientcommandframe;
822 getinputstate(csqcplayer_moveframe-1);
823 print("the Weird code path got hit\n");
828 if(csqcplayer_moveframe >= endframe)
830 getinputstate(csqcplayer_moveframe - 1);
836 if (!getinputstate(csqcplayer_moveframe))
838 CSQCPlayer_Physics();
839 CSQCPlayer_SetMinsMaxs();
840 csqcplayer_moveframe++;
842 while(csqcplayer_moveframe < endframe);
845 //add in anything that was applied after (for low packet rate protocols)
846 input_angles = view_angles;
849 float CSQCPlayer_IsLocalPlayer()
851 return (self == csqcplayer);
854 void(entity e, float fl) V_CalcRefdef = #640; // DP_CSQC_V_CALCREFDEF
856 void CSQCPlayer_SetCamera()
859 v0 = pmove_vel; // TRICK: pmove_vel is set by the engine when we get here. No need to network velocity
867 #ifdef COMPAT_XON050_ENGINE
868 if(servercommandframe == 0 || clientcommandframe == 0 || !(checkextension("DP_CSQC_V_CALCREFDEF") || checkextension("DP_CSQC_V_CALCREFDEF_WIP1")))
870 if(servercommandframe == 0 || clientcommandframe == 0)
873 InterpolateOrigin_Do();
874 self.view_ofs = '0 0 1' * getstati(STAT_VIEWHEIGHT);
876 // get crouch state from the server
877 if(getstati(STAT_VIEWHEIGHT) == PL_VIEW_OFS_z)
878 self.pmove_flags &= ~PMF_DUCKED;
879 else if(getstati(STAT_VIEWHEIGHT) == PL_CROUCH_VIEW_OFS_z)
880 self.pmove_flags |= PMF_DUCKED;
882 // get onground state from the server
884 self.pmove_flags |= PMF_ONGROUND;
886 self.pmove_flags &= ~PMF_ONGROUND;
888 CSQCPlayer_SetMinsMaxs();
890 // override it back just in case
891 self.view_ofs = '0 0 1' * getstati(STAT_VIEWHEIGHT);
898 float flg = self.iflags;
899 self.iflags &= ~(IFLAG_ORIGIN | IFLAG_ANGLES);
900 InterpolateOrigin_Do();
903 if(csqcplayer_status == CSQCPLAYERSTATUS_FROMSERVER)
908 csqcplayer_status = CSQCPLAYERSTATUS_PREDICTED;
909 CSQCPlayer_PredictTo(servercommandframe + 1, FALSE);
910 CSQCPlayer_SetPredictionError(self.origin - o, self.velocity - v, pmove_onground - !!(self.pmove_flags & PMF_ONGROUND));
914 // get crouch state from the server
915 if(getstati(STAT_VIEWHEIGHT) == PL_VIEW_OFS_z)
916 self.pmove_flags &= ~PMF_DUCKED;
917 else if(getstati(STAT_VIEWHEIGHT) == PL_CROUCH_VIEW_OFS_z)
918 self.pmove_flags |= PMF_DUCKED;
920 // get onground state from the server
922 self.pmove_flags |= PMF_ONGROUND;
924 self.pmove_flags &= ~PMF_ONGROUND;
926 CSQCPlayer_SavePrediction();
928 CSQCPlayer_PredictTo(clientcommandframe + 1, TRUE);
930 #ifdef CSQCMODEL_SERVERSIDE_CROUCH
931 // get crouch state from the server (LAG)
932 if(getstati(STAT_VIEWHEIGHT) == PL_VIEW_OFS_z)
933 self.pmove_flags &= ~PMF_DUCKED;
934 else if(getstati(STAT_VIEWHEIGHT) == PL_CROUCH_VIEW_OFS_z)
935 self.pmove_flags |= PMF_DUCKED;
938 CSQCPlayer_SetMinsMaxs();
940 self.angles_y = input_angles_y;
944 setorigin(self, self.origin);
950 #ifdef COMPAT_XON050_ENGINE
951 view = CSQCModel_server2csqc((spectatee_status > 0) ? spectatee_status : player_localentnum);
953 view = CSQCModel_server2csqc(player_localentnum);
956 if(view && view != csqcplayer)
958 entity oldself = self;
960 InterpolateOrigin_Do();
961 self.view_ofs = '0 0 1' * getstati(STAT_VIEWHEIGHT);
965 #ifdef COMPAT_XON050_ENGINE
966 if(view && !(checkextension("DP_CSQC_V_CALCREFDEF") || checkextension("DP_CSQC_V_CALCREFDEF_WIP1")))
968 // legacy code, not totally correct, but good enough for not having V_CalcRefdef
969 setproperty(VF_ORIGIN, view.origin + '0 0 1' * getstati(STAT_VIEWHEIGHT));
970 setproperty(VF_ANGLES, view_angles);
976 var float refdefflags = 0;
978 if(view.csqcmodel_teleported)
979 refdefflags |= REFDEFFLAG_TELEPORTED;
981 if(input_buttons & 4)
982 refdefflags |= REFDEFFLAG_JUMPING;
984 // note: these two only work in WIP2, but are harmless in WIP1
985 if(getstati(STAT_HEALTH) <= 0)
986 refdefflags |= REFDEFFLAG_DEAD;
989 refdefflags |= REFDEFFLAG_INTERMISSION;
991 V_CalcRefdef(view, refdefflags);
995 // FIXME by CSQC spec we have to do this:
996 // but it breaks chase cam
998 setproperty(VF_ORIGIN, pmove_org + '0 0 1' * getstati(STAT_VIEWHEIGHT));
999 setproperty(VF_ANGLES, view_angles);
1003 { CSQCPLAYER_HOOK_POSTCAMERASETUP }
1006 void CSQCPlayer_Remove()
1009 cvar_settemp("cl_movement_replay", "1");
1012 float CSQCPlayer_PreUpdate()
1014 if(self != csqcplayer)
1016 if(csqcplayer_status != CSQCPLAYERSTATUS_FROMSERVER)
1017 CSQCPlayer_Unpredict();
1021 float CSQCPlayer_PostUpdate()
1023 if(self.entnum != player_localnum + 1)
1026 csqcplayer_status = CSQCPLAYERSTATUS_FROMSERVER;
1027 cvar_settemp("cl_movement_replay", "0");
1028 self.entremove = CSQCPlayer_Remove;