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;
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
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;
38 vector CSQCPlayer_GetPredictionErrorO()
40 if(time >= csqcplayer_predictionerrortime)
42 return csqcplayer_predictionerroro * (csqcplayer_predictionerrortime - time) * csqcplayer_predictionerrorfactor;
45 vector CSQCPlayer_GetPredictionErrorV()
47 if(time >= csqcplayer_predictionerrortime)
49 return csqcplayer_predictionerrorv * (csqcplayer_predictionerrortime - time) * csqcplayer_predictionerrorfactor;
52 void CSQCPlayer_SetPredictionError(vector o, vector v, float onground_diff)
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
58 // FIXME we sometimes have disagreement in order of jump velocity. Do not act on them!
60 // commented out as this one did not help
63 printf("ONGROUND MISMATCH: %d x=%v v=%v\n", onground_diff, o, v);
67 if(vlen(o) > 32 || vlen(v) > 192)
69 //printf("TOO BIG: x=%v v=%v\n", o, v);
73 if(!autocvar_cl_movement_errorcompensation)
75 csqcplayer_predictionerrorfactor = 0;
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;
85 void CSQCPlayer_Unpredict()
87 if(csqcplayer_status == CSQCPLAYERSTATUS_UNPREDICTED)
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;
97 void CSQCPlayer_SetMinsMaxs()
99 if(self.pmove_flags & PMF_DUCKED)
101 self.mins = PL_CROUCH_MIN;
102 self.maxs = PL_CROUCH_MAX;
103 self.view_ofs = PL_CROUCH_VIEW_OFS;
109 self.view_ofs = PL_VIEW_OFS;
113 void CSQCPlayer_SavePrediction()
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;
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)
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);
148 angle = angles_z * (M_PI*2 / 360);
153 right_x = -1*(sr*sp*cy+cr*-sy);
154 right_y = -1*(sr*sp*sy+cr*cy);
155 right_z = -1*(sr*cp);
159 up_x = (cr*sp*cy+-sr*-sy);
160 up_y = (cr*sp*sy+-sr*cy);
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';
188 const float unstick_count = 27;
189 vector unstick_offsets[unstick_count] =
191 // 1 no nudge (just return the original if this test passes)
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',
212 float CSQC_ClientMovement_Unstick(entity s)
216 for (i = 0; i < unstick_count; i++)
218 neworigin = unstick_offsets[i] + s.origin;
219 tracebox(neworigin, cl_playercrouchmins, cl_playercrouchmaxs, neworigin, MOVE_NORMAL, s);
220 if (!trace_startsolid)
222 s.origin = neworigin;
226 // if all offsets failed, give up
230 void CSQC_ClientMovement_UpdateStatus(entity s)
233 vector origin1, origin2;
235 // make sure player is not stuck
236 CSQC_ClientMovement_Unstick(s);
239 if (input_buttons & 16)
241 // wants to crouch, this always works..
242 if (!s.pmove_flags & PMF_DUCKED)
243 s.pmove_flags |= PMF_DUCKED;
247 // wants to stand, if currently crouching we need to check for a
249 if (s.pmove_flags & PMF_DUCKED)
251 tracebox(s.origin, cl_playerstandmins, cl_playerstandmaxs, s.origin, MOVE_NORMAL, s);
252 if (!trace_startsolid)
253 s.pmove_flags &= ~PMF_DUCKED;
256 if (s.pmove_flags & PMF_DUCKED)
258 s.mins = cl_playercrouchmins;
259 s.maxs = cl_playercrouchmaxs;
263 s.mins = cl_playerstandmins;
264 s.maxs = cl_playerstandmaxs;
271 origin2_z -= 1; // -2 causes clientside doublejump bug at above 150fps, raising that to 300fps :)
273 tracebox(origin1, s.mins, s.maxs, origin2, MOVE_NORMAL, s);
274 if(trace_fraction < 1 && trace_plane_normal_z > 0.7)
276 s.pmove_flags |= PMF_ONGROUND;
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;
284 s.pmove_flags &= ~PMF_ONGROUND; // onground = false;
286 // set watertype/waterlevel
288 origin1_z += s.mins_z + 1;
289 s.waterlevel = WATERLEVEL_NONE;
291 // s.watertype = CL_TracePoint(origin1, MOVE_NOMONSTERS, s, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK;
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)
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;
305 // // water jump prediction
306 // if ((s.pmove_flags & PMF_ONGROUND) || s.velocity_z <= 0 || pmove_waterjumptime <= 0)
307 // pmove_waterjumptime = 0;
310 void CSQC_ClientMovement_Move(entity s)
316 vector currentorigin2;
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++)
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)
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)
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)
355 trace_fraction = old_trace2_fraction;
356 trace_endpos = trace_endpos;
357 trace_plane_normal = old_trace2_plane_normal;
361 trace_fraction = old_trace1_fraction;
362 trace_endpos = old_trace1_endpos;
363 trace_plane_normal = old_trace1_plane_normal;
368 // check if it moved at all
369 if (trace_fraction >= 0.001)
370 s.origin = trace_endpos;
372 // check if it moved all the way
373 if (trace_fraction == 1)
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;
383 t -= t * trace_fraction;
385 f = dotproduct(s.velocity, trace_plane_normal);
386 s.velocity -= f * trace_plane_normal;
388 if (pmove_waterjumptime > 0)
389 s.velocity = primalvelocity;
392 float CSQC_IsMoveInDirection(float forward, float side, float angle)
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;
405 return 1 - fabs(angle);
410 float CSQC_GeomLerp(float a, float lerp, float b)
426 return a * pow(fabs(b / a), lerp);
429 void CSQC_ClientMovement_Physics_CPM_PM_Aircontrol(entity s, vector wishdir, float wishspeed)
431 float zspeed, speed, dot, k;
433 k = 32 * (2 * CSQC_IsMoveInDirection(input_movevalues_x, input_movevalues_y, 0) - 1);
437 k *= bound(0, wishspeed / cvar("cl_movement_maxairspeed"), 1);
439 zspeed = s.velocity_z;
441 speed = vlen(s.velocity);
442 if (speed) s.velocity /= speed;
444 dot = dotproduct(s.velocity, wishdir);
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);
455 s.velocity_z = zspeed;
458 float CSQC_ClientMovement_Physics_AdjustAirAccelQW(float accelqw, float factor)
461 (accelqw < 0 ? -1 : +1)
463 bound(0.000001, 1 - (1 - fabs(accelqw)) * factor, 1);
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)
473 float vel_xy_current;
474 float vel_xy_backward, vel_xy_forward;
477 if(stretchfactor > 0)
478 speedclamp = stretchfactor;
482 speedclamp = -1; // no clamping
487 if(moveflags & MOVEFLAG_Q2AIRACCELERATE)
488 wishspeed0 = wishspeed; // don't need to emulate this Q1 bug
490 vel_straight = dotproduct(s.velocity, wishdir);
491 vel_z = s.velocity_z;
494 vel_perpend = vel_xy - vel_straight * wishdir;
496 step = accel * input_timelength * wishspeed0;
498 vel_xy_current = vlen(vel_xy);
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
506 vel_straight = vel_straight + bound(0, wishspeed - vel_straight, step) * accelqw + step * (1 - accelqw);
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"
512 f = max(0, 1 + input_timelength * wishspeed * sidefric);
513 fmin = (vel_xy_backward*vel_xy_backward - vel_straight*vel_straight) / vlen2(vel_perpend);
515 // vel_xy_backward*vel_xy_backward - vel_straight*vel_straight > vel_perpend*vel_perpend
516 // vel_xy_backward*vel_xy_backward > vel_straight*vel_straight + vel_perpend*vel_perpend
517 // vel_xy_backward*vel_xy_backward > vel_xy * vel_xy
518 // obviously, this cannot be
524 vel_perpend *= max(fmin, f);
528 vel_perpend *= max(0, 1 - input_timelength * wishspeed * sidefric);
530 s.velocity = vel_perpend + vel_straight * wishdir;
534 float vel_xy_preclamp;
535 vel_xy_preclamp = vlen(s.velocity);
536 if(vel_xy_preclamp > 0) // prevent division by zero
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);
544 s.velocity_z += vel_z;
547 void CSQC_ClientMovement_Physics_Walk(entity s)
555 vector forward = '0 0 0';
556 vector right = '0 0 0';
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)
566 if ((s.pmove_flags & PMF_ONGROUND) && ((s.pmove_flags & PMF_JUMP_HELD) == 0 || !cvar("cl_movement_track_canjump")))
568 s.velocity_z += cvar("cl_movement_jumpvelocity");
569 s.pmove_flags &= ~PMF_ONGROUND;
570 s.pmove_flags |= PMF_JUMP_HELD; // canjump = false
574 s.pmove_flags &= ~PMF_JUMP_HELD; // canjump = true
576 // calculate movement vector
578 yawangles_y = input_angles_y;
579 AngleVectors(yawangles, forward, right, up);
580 wishvel = input_movevalues_x * forward + input_movevalues_y * right;
582 // split wishvel into wishspeed and wishdir
583 wishspeed = vlen(wishvel);
585 wishdir = wishvel / wishspeed;
589 if ((s.pmove_flags & PMF_ONGROUND))
591 wishspeed = min(wishspeed, cvar("cl_movement_maxspeed"));
592 if (s.pmove_flags & PMF_DUCKED)
595 // apply edge friction
596 f = sqrt(s.velocity_x * s.velocity_x + s.velocity_y * s.velocity_y);
599 friction = cvar("cl_movement_friction");
600 if (cvar("cl_movement_edgefriction") != 1)
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;
613 // if (cls.protocol == PROTOCOL_QUAKEWORLD)
614 tracebox(neworigin2, s.mins, s.maxs, neworigin3, MOVE_NORMAL, s);
616 // traceline(neworigin2, neworigin3, MOVE_NORMAL, s);
617 if (trace_fraction == 1 && !trace_startsolid)
618 friction *= cvar("cl_movement_edgefriction");
620 // apply ground friction
621 f = 1 - input_timelength * friction * ((f < cvar("cl_movement_stopspeed")) ? (cvar("cl_movement_stopspeed") / f) : 1);
625 addspeed = wishspeed - dotproduct(s.velocity, wishdir);
628 accelspeed = min(cvar("cl_movement_accelerate") * input_timelength * wishspeed, addspeed);
629 s.velocity += accelspeed * wishdir;
631 gravity = cvar("cl_movement_gravity") * cvar("cl_movement_entgravity") * input_timelength;
632 if(!(moveflags & MOVEFLAG_NOGRAVITYONGROUND))
634 if(moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
635 s.velocity_z -= gravity * 0.5;
637 s.velocity_z -= gravity;
639 // if (cls.protocol == PROTOCOL_QUAKEWORLD)
641 if (vlen2(s.velocity))
642 CSQC_ClientMovement_Move(s);
643 if(!(moveflags & MOVEFLAG_NOGRAVITYONGROUND) || !(s.pmove_flags & PMF_ONGROUND))
645 if(moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
646 s.velocity_z -= gravity * 0.5;
651 if (pmove_waterjumptime <= 0)
653 // apply air speed limit
654 float accel, wishspeed0, wishspeed2, accelqw, strafity;
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)
662 accel = cvar("cl_movement_airaccelerate");
664 accelerating = (dotproduct(s.velocity, wishdir) > 0);
665 wishspeed2 = wishspeed;
668 if(cvar("cl_movement_airstopaccelerate") != 0)
671 curdir_x = s.velocity_x;
672 curdir_y = s.velocity_y;
674 curdir = normalize(curdir);
675 accel = accel + (cvar("cl_movement_airstopaccelerate") - accel) * max(0, -dotproduct(curdir, wishdir));
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"))
684 (((strafity > 0.5 ? cvar("cl_movement_airstrafeaccel_qw") : cvar("cl_movement_airaccel_qw")) >= 0) ? +1 : -1)
686 (1 - CSQC_GeomLerp(1 - fabs(cvar("cl_movement_airaccel_qw")), strafity, 1 - fabs(cvar("cl_movement_airstrafeaccel_qw"))));
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);
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"));
694 if(cvar("cl_movement_aircontrol"))
695 CSQC_ClientMovement_Physics_CPM_PM_Aircontrol(s, wishdir, wishspeed2);
697 gravity = cvar("cl_movement_gravity") * cvar("cl_movement_entgravity") * input_timelength;
698 if(moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
699 s.velocity_z -= gravity * 0.5;
701 s.velocity_z -= gravity;
702 CSQC_ClientMovement_Move(s);
703 if(!(moveflags & MOVEFLAG_NOGRAVITYONGROUND) || !(s.pmove_flags & PMF_ONGROUND))
705 if(moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
706 s.velocity_z -= gravity * 0.5;
711 void CSQC_ClientMovement_PlayerMove(entity s)
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);
719 // if (s.waterlevel >= WATERLEVEL_SWIMMING)
720 // CL_ClientMovement_Physics_Swim(s);
722 CSQC_ClientMovement_Physics_Walk(s);
725 void CSQC_ClientMovement_PlayerMove_Frame(entity s)
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)
731 if (input_timelength > 0.05)
733 input_timelength /= 2;
734 CSQC_ClientMovement_PlayerMove(s);
736 CSQC_ClientMovement_PlayerMove(s);
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
746 void CSQCPlayer_Physics(void)
748 switch(cvar("cl_movement")) {
749 case 2: CSQC_ClientMovement_PlayerMove_Frame(self); break;
750 case 1: runstandardplayerphysics(self); break;
756 void CSQCPlayer_PredictTo(float endframe, float apply_error)
758 CSQCPlayer_Unpredict();
761 self.origin += CSQCPlayer_GetPredictionErrorO();
762 self.velocity += CSQCPlayer_GetPredictionErrorV();
764 CSQCPlayer_SetMinsMaxs();
766 csqcplayer_status = CSQCPLAYERSTATUS_PREDICTED;
769 // we don't need this
770 // darkplaces makes servercommandframe == 0 in these cases anyway
771 if (getstatf(STAT_HEALTH) <= 0)
773 csqcplayer_moveframe = clientcommandframe;
774 getinputstate(csqcplayer_moveframe-1);
775 print("the Weird code path got hit\n");
780 if(csqcplayer_moveframe >= endframe)
782 getinputstate(csqcplayer_moveframe - 1);
788 if (!getinputstate(csqcplayer_moveframe))
790 CSQCPlayer_Physics();
791 CSQCPlayer_SetMinsMaxs();
792 csqcplayer_moveframe++;
794 while(csqcplayer_moveframe < endframe);
797 //add in anything that was applied after (for low packet rate protocols)
798 input_angles = view_angles;
801 float CSQCPlayer_IsLocalPlayer()
803 return (self == csqcplayer);
806 void(entity e, float fl) V_CalcRefdef = #640; // DP_CSQC_V_CALCREFDEF
808 void CSQCPlayer_SetCamera()
811 v0 = pmove_vel; // TRICK: pmove_vel is set by the engine when we get here. No need to network velocity
819 #ifdef COMPAT_XON050_ENGINE
820 if(servercommandframe == 0 || clientcommandframe == 0 || !(checkextension("DP_CSQC_V_CALCREFDEF") || checkextension("DP_CSQC_V_CALCREFDEF_WIP1")))
822 if(servercommandframe == 0 || clientcommandframe == 0)
825 InterpolateOrigin_Do();
826 self.view_ofs = '0 0 1' * getstati(STAT_VIEWHEIGHT);
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;
834 // get onground state from the server
836 self.pmove_flags |= PMF_ONGROUND;
838 self.pmove_flags &= ~PMF_ONGROUND;
840 CSQCPlayer_SetMinsMaxs();
842 // override it back just in case
843 self.view_ofs = '0 0 1' * getstati(STAT_VIEWHEIGHT);
850 float flg = self.iflags;
851 self.iflags &= ~(IFLAG_ORIGIN | IFLAG_ANGLES);
852 InterpolateOrigin_Do();
855 if(csqcplayer_status == CSQCPLAYERSTATUS_FROMSERVER)
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));
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;
872 // get onground state from the server
874 self.pmove_flags |= PMF_ONGROUND;
876 self.pmove_flags &= ~PMF_ONGROUND;
878 CSQCPlayer_SavePrediction();
880 CSQCPlayer_PredictTo(clientcommandframe + 1, TRUE);
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;
890 CSQCPlayer_SetMinsMaxs();
892 self.angles_y = input_angles_y;
896 setorigin(self, self.origin);
902 #ifdef COMPAT_XON050_ENGINE
903 view = CSQCModel_server2csqc((spectatee_status > 0) ? spectatee_status : player_localentnum);
905 view = CSQCModel_server2csqc(player_localentnum);
908 if(view && view != csqcplayer)
910 entity oldself = self;
912 InterpolateOrigin_Do();
913 self.view_ofs = '0 0 1' * getstati(STAT_VIEWHEIGHT);
917 #ifdef COMPAT_XON050_ENGINE
918 if(view && !(checkextension("DP_CSQC_V_CALCREFDEF") || checkextension("DP_CSQC_V_CALCREFDEF_WIP1")))
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);
928 var float refdefflags = 0;
930 if(view.csqcmodel_teleported)
931 refdefflags |= REFDEFFLAG_TELEPORTED;
933 if(input_buttons & 4)
934 refdefflags |= REFDEFFLAG_JUMPING;
936 // note: these two only work in WIP2, but are harmless in WIP1
937 if(getstati(STAT_HEALTH) <= 0)
938 refdefflags |= REFDEFFLAG_DEAD;
941 refdefflags |= REFDEFFLAG_INTERMISSION;
943 V_CalcRefdef(view, refdefflags);
947 // FIXME by CSQC spec we have to do this:
948 // but it breaks chase cam
950 setproperty(VF_ORIGIN, pmove_org + '0 0 1' * getstati(STAT_VIEWHEIGHT));
951 setproperty(VF_ANGLES, view_angles);
955 { CSQCPLAYER_HOOK_POSTCAMERASETUP }
958 void CSQCPlayer_Remove()
961 cvar_settemp("cl_movement_replay", "1");
964 float CSQCPlayer_PreUpdate()
966 if(self != csqcplayer)
968 if(csqcplayer_status != CSQCPLAYERSTATUS_FROMSERVER)
969 CSQCPlayer_Unpredict();
973 float CSQCPlayer_PostUpdate()
975 if(self.entnum != player_localnum + 1)
978 csqcplayer_status = CSQCPLAYERSTATUS_FROMSERVER;
979 cvar_settemp("cl_movement_replay", "0");
980 self.entremove = CSQCPlayer_Remove;