2 #include "triggers/include.qh"
7 #include "../server/miscfunctions.qh"
8 #include "triggers/trigger/viewloc.qh"
10 // client side physics
11 bool Physics_Valid(string thecvar)
13 return autocvar_g_physics_clientselect && strhasword(autocvar_g_physics_clientselect_options, thecvar);
16 float Physics_ClientOption(entity pl, string option)
18 if(Physics_Valid(pl.cvar_cl_physics))
20 string s = sprintf("g_physics_%s_%s", pl.cvar_cl_physics, option);
21 if(cvar_type(s) & CVAR_TYPEFLAG_EXISTS)
24 if(autocvar_g_physics_clientselect && autocvar_g_physics_clientselect_default)
26 string s = sprintf("g_physics_%s_%s", autocvar_g_physics_clientselect_default, option);
27 if(cvar_type(s) & CVAR_TYPEFLAG_EXISTS)
30 return cvar(strcat("sv_", option));
33 void Physics_AddStats()
35 // static view offset and hitbox vectors
36 // networked for all you bandwidth pigs out there
37 addstat(STAT_PL_VIEW_OFS1, AS_FLOAT, stat_pl_view_ofs_x);
38 addstat(STAT_PL_VIEW_OFS2, AS_FLOAT, stat_pl_view_ofs_y);
39 addstat(STAT_PL_VIEW_OFS3, AS_FLOAT, stat_pl_view_ofs_z);
40 addstat(STAT_PL_CROUCH_VIEW_OFS1, AS_FLOAT, stat_pl_crouch_view_ofs_x);
41 addstat(STAT_PL_CROUCH_VIEW_OFS2, AS_FLOAT, stat_pl_crouch_view_ofs_y);
42 addstat(STAT_PL_CROUCH_VIEW_OFS3, AS_FLOAT, stat_pl_crouch_view_ofs_z);
44 addstat(STAT_PL_MIN1, AS_FLOAT, stat_pl_min_x);
45 addstat(STAT_PL_MIN2, AS_FLOAT, stat_pl_min_y);
46 addstat(STAT_PL_MIN3, AS_FLOAT, stat_pl_min_z);
47 addstat(STAT_PL_MAX1, AS_FLOAT, stat_pl_max_x);
48 addstat(STAT_PL_MAX2, AS_FLOAT, stat_pl_max_y);
49 addstat(STAT_PL_MAX3, AS_FLOAT, stat_pl_max_z);
50 addstat(STAT_PL_CROUCH_MIN1, AS_FLOAT, stat_pl_crouch_min_x);
51 addstat(STAT_PL_CROUCH_MIN2, AS_FLOAT, stat_pl_crouch_min_y);
52 addstat(STAT_PL_CROUCH_MIN3, AS_FLOAT, stat_pl_crouch_min_z);
53 addstat(STAT_PL_CROUCH_MAX1, AS_FLOAT, stat_pl_crouch_max_x);
54 addstat(STAT_PL_CROUCH_MAX2, AS_FLOAT, stat_pl_crouch_max_y);
55 addstat(STAT_PL_CROUCH_MAX3, AS_FLOAT, stat_pl_crouch_max_z);
57 // g_movementspeed hack
58 addstat(STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW, AS_FLOAT, stat_sv_airspeedlimit_nonqw);
59 addstat(STAT_MOVEVARS_MAXSPEED, AS_FLOAT, stat_sv_maxspeed);
60 addstat(STAT_MOVEVARS_AIRACCEL_QW, AS_FLOAT, stat_sv_airaccel_qw);
61 addstat(STAT_MOVEVARS_AIRSTRAFEACCEL_QW, AS_FLOAT, stat_sv_airstrafeaccel_qw);
62 addstat(STAT_MOVEVARS_HIGHSPEED, AS_FLOAT, stat_movement_highspeed);
65 addstat(STAT_JETPACK_ACCEL_SIDE, AS_FLOAT, stat_jetpack_accel_side);
66 addstat(STAT_JETPACK_ACCEL_UP, AS_FLOAT, stat_jetpack_accel_up);
67 addstat(STAT_JETPACK_ANTIGRAVITY, AS_FLOAT, stat_jetpack_antigravity);
68 addstat(STAT_JETPACK_FUEL, AS_FLOAT, stat_jetpack_fuel);
69 addstat(STAT_JETPACK_MAXSPEED_UP, AS_FLOAT, stat_jetpack_maxspeed_up);
70 addstat(STAT_JETPACK_MAXSPEED_SIDE, AS_FLOAT, stat_jetpack_maxspeed_side);
72 // hack to fix track_canjump
73 addstat(STAT_MOVEVARS_CL_TRACK_CANJUMP, AS_INT, cvar_cl_movement_track_canjump);
74 addstat(STAT_MOVEVARS_TRACK_CANJUMP, AS_INT, stat_sv_track_canjump);
77 addstat(STAT_MOVEVARS_JUMPSPEEDCAP_DISABLE_ONRAMPS, AS_INT, stat_jumpspeedcap_disable_onramps);
80 addstat(STAT_MOVEVARS_FRICTION_ONLAND, AS_FLOAT, stat_sv_friction_on_land);
81 addstat(STAT_MOVEVARS_FRICTION_SLICK, AS_FLOAT, stat_sv_friction_slick);
82 addstat(STAT_GAMEPLAYFIX_EASIERWATERJUMP, AS_INT, stat_gameplayfix_easierwaterjump);
85 addstat(STAT_MOVEVARS_JUMPVELOCITY, AS_FLOAT, stat_sv_jumpvelocity);
86 addstat(STAT_MOVEVARS_AIRACCEL_QW_STRETCHFACTOR, AS_FLOAT, stat_sv_airaccel_qw_stretchfactor);
87 addstat(STAT_MOVEVARS_MAXAIRSTRAFESPEED, AS_FLOAT, stat_sv_maxairstrafespeed);
88 addstat(STAT_MOVEVARS_MAXAIRSPEED, AS_FLOAT, stat_sv_maxairspeed);
89 addstat(STAT_MOVEVARS_AIRSTRAFEACCELERATE, AS_FLOAT, stat_sv_airstrafeaccelerate);
90 addstat(STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL, AS_FLOAT, stat_sv_warsowbunny_turnaccel);
91 addstat(STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION, AS_FLOAT, stat_sv_airaccel_sideways_friction);
92 addstat(STAT_MOVEVARS_AIRCONTROL, AS_FLOAT, stat_sv_aircontrol);
93 addstat(STAT_MOVEVARS_AIRCONTROL_POWER, AS_FLOAT, stat_sv_aircontrol_power);
94 addstat(STAT_MOVEVARS_AIRCONTROL_PENALTY, AS_FLOAT, stat_sv_aircontrol_penalty);
95 addstat(STAT_MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL, AS_FLOAT, stat_sv_warsowbunny_airforwardaccel);
96 addstat(STAT_MOVEVARS_WARSOWBUNNY_TOPSPEED, AS_FLOAT, stat_sv_warsowbunny_topspeed);
97 addstat(STAT_MOVEVARS_WARSOWBUNNY_ACCEL, AS_FLOAT, stat_sv_warsowbunny_accel);
98 addstat(STAT_MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO, AS_FLOAT, stat_sv_warsowbunny_backtosideratio);
99 addstat(STAT_MOVEVARS_FRICTION, AS_FLOAT, stat_sv_friction);
100 addstat(STAT_MOVEVARS_ACCELERATE, AS_FLOAT, stat_sv_accelerate);
101 addstat(STAT_MOVEVARS_STOPSPEED, AS_FLOAT, stat_sv_stopspeed);
102 addstat(STAT_MOVEVARS_AIRACCELERATE, AS_FLOAT, stat_sv_airaccelerate);
103 addstat(STAT_MOVEVARS_AIRSTOPACCELERATE, AS_FLOAT, stat_sv_airstopaccelerate);
105 addstat(STAT_GAMEPLAYFIX_UPVELOCITYCLEARSONGROUND, AS_INT, stat_gameplayfix_upvelocityclearsonground);
108 void Physics_UpdateStats(float maxspd_mod)
111 self.stat_pl_view_ofs = PL_VIEW_OFS;
112 self.stat_pl_crouch_view_ofs = PL_CROUCH_VIEW_OFS;
114 self.stat_pl_min = PL_MIN;
115 self.stat_pl_max = PL_MAX;
116 self.stat_pl_crouch_min = PL_CROUCH_MIN;
117 self.stat_pl_crouch_max = PL_CROUCH_MAX;
120 self.stat_sv_airaccel_qw = AdjustAirAccelQW(Physics_ClientOption(self, "airaccel_qw"), maxspd_mod);
121 if(Physics_ClientOption(self, "airstrafeaccel_qw"))
122 self.stat_sv_airstrafeaccel_qw = AdjustAirAccelQW(Physics_ClientOption(self, "airstrafeaccel_qw"), maxspd_mod);
124 self.stat_sv_airstrafeaccel_qw = 0;
125 self.stat_sv_airspeedlimit_nonqw = Physics_ClientOption(self, "airspeedlimit_nonqw") * maxspd_mod;
126 self.stat_sv_maxspeed = Physics_ClientOption(self, "maxspeed") * maxspd_mod; // also slow walking
127 self.stat_movement_highspeed = PHYS_HIGHSPEED; // TODO: remove this!
129 self.stat_jetpack_antigravity = PHYS_JETPACK_ANTIGRAVITY;
130 self.stat_jetpack_accel_up = PHYS_JETPACK_ACCEL_UP;
131 self.stat_jetpack_accel_side = PHYS_JETPACK_ACCEL_SIDE;
132 self.stat_jetpack_maxspeed_side = PHYS_JETPACK_MAXSPEED_SIDE;
133 self.stat_jetpack_maxspeed_up = PHYS_JETPACK_MAXSPEED_UP;
134 self.stat_jetpack_fuel = PHYS_JETPACK_FUEL;
136 self.stat_jumpspeedcap_disable_onramps = PHYS_JUMPSPEEDCAP_DISABLE_ONRAMPS;
138 self.stat_sv_friction_on_land = PHYS_FRICTION_ONLAND;
139 self.stat_sv_friction_slick = PHYS_FRICTION_SLICK;
141 self.stat_gameplayfix_easierwaterjump = GAMEPLAYFIX_EASIERWATERJUMP;
145 // fix some new settings
146 self.stat_sv_airaccel_qw_stretchfactor = Physics_ClientOption(self, "airaccel_qw_stretchfactor");
147 self.stat_sv_maxairstrafespeed = Physics_ClientOption(self, "maxairstrafespeed");
148 self.stat_sv_maxairspeed = Physics_ClientOption(self, "maxairspeed");
149 self.stat_sv_airstrafeaccelerate = Physics_ClientOption(self, "airstrafeaccelerate");
150 self.stat_sv_warsowbunny_turnaccel = Physics_ClientOption(self, "warsowbunny_turnaccel");
151 self.stat_sv_airaccel_sideways_friction = Physics_ClientOption(self, "airaccel_sideways_friction");
152 self.stat_sv_aircontrol = Physics_ClientOption(self, "aircontrol");
153 self.stat_sv_aircontrol_power = Physics_ClientOption(self, "aircontrol_power");
154 self.stat_sv_aircontrol_penalty = Physics_ClientOption(self, "aircontrol_penalty");
155 self.stat_sv_warsowbunny_airforwardaccel = Physics_ClientOption(self, "warsowbunny_airforwardaccel");
156 self.stat_sv_warsowbunny_topspeed = Physics_ClientOption(self, "warsowbunny_topspeed");
157 self.stat_sv_warsowbunny_accel = Physics_ClientOption(self, "warsowbunny_accel");
158 self.stat_sv_warsowbunny_backtosideratio = Physics_ClientOption(self, "warsowbunny_backtosideratio");
159 self.stat_sv_friction = Physics_ClientOption(self, "friction");
160 self.stat_sv_accelerate = Physics_ClientOption(self, "accelerate");
161 self.stat_sv_stopspeed = Physics_ClientOption(self, "stopspeed");
162 self.stat_sv_airaccelerate = Physics_ClientOption(self, "airaccelerate");
163 self.stat_sv_airstopaccelerate = Physics_ClientOption(self, "airstopaccelerate");
164 self.stat_sv_jumpvelocity = Physics_ClientOption(self, "jumpvelocity");
166 self.stat_sv_track_canjump = Physics_ClientOption(self, "track_canjump");
168 self.stat_gameplayfix_upvelocityclearsonground = UPWARD_VELOCITY_CLEARS_ONGROUND;
172 float IsMoveInDirection(vector mv, float ang) // key mix factor
174 if (mv_x == 0 && mv_y == 0)
175 return 0; // avoid division by zero
176 ang -= RAD2DEG * atan2(mv_y, mv_x);
177 ang = remainder(ang, 360) / 45;
178 return ang > 1 ? 0 : ang < -1 ? 0 : 1 - fabs(ang);
181 float GeomLerp(float a, float lerp, float b)
183 return a == 0 ? (lerp < 1 ? 0 : b)
184 : b == 0 ? (lerp > 0 ? 0 : a)
185 : a * pow(fabs(b / a), lerp);
188 noref float pmove_waterjumptime;
190 #define unstick_offsets(X) \
191 /* 1 no nudge (just return the original if this test passes) */ \
192 X(' 0.000 0.000 0.000') \
193 /* 6 simple nudges */ \
194 X(' 0.000 0.000 0.125') X('0.000 0.000 -0.125') \
195 X('-0.125 0.000 0.000') X('0.125 0.000 0.000') \
196 X(' 0.000 -0.125 0.000') X('0.000 0.125 0.000') \
197 /* 4 diagonal flat nudges */ \
198 X('-0.125 -0.125 0.000') X('0.125 -0.125 0.000') \
199 X('-0.125 0.125 0.000') X('0.125 0.125 0.000') \
200 /* 8 diagonal upward nudges */ \
201 X('-0.125 0.000 0.125') X('0.125 0.000 0.125') \
202 X(' 0.000 -0.125 0.125') X('0.000 0.125 0.125') \
203 X('-0.125 -0.125 0.125') X('0.125 -0.125 0.125') \
204 X('-0.125 0.125 0.125') X('0.125 0.125 0.125') \
205 /* 8 diagonal downward nudges */ \
206 X('-0.125 0.000 -0.125') X('0.125 0.000 -0.125') \
207 X(' 0.000 -0.125 -0.125') X('0.000 0.125 -0.125') \
208 X('-0.125 -0.125 -0.125') X('0.125 -0.125 -0.125') \
209 X('-0.125 0.125 -0.125') X('0.125 0.125 -0.125') \
212 void PM_ClientMovement_Unstick(entity this)
214 #define X(unstick_offset) \
216 vector neworigin = unstick_offset + this.origin; \
217 tracebox(neworigin, PL_CROUCH_MIN, PL_CROUCH_MAX, neworigin, MOVE_NORMAL, this); \
218 if (!trace_startsolid) \
220 setorigin(this, neworigin); \
228 void PM_ClientMovement_UpdateStatus(entity this, bool ground)
230 // make sure player is not stuck
231 PM_ClientMovement_Unstick(this);
234 if (PHYS_INPUT_BUTTON_CROUCH(this))
236 // wants to crouch, this always works
237 if (!IS_DUCKED(this)) SET_DUCKED(this);
241 // wants to stand, if currently crouching we need to check for a low ceiling first
244 tracebox(this.origin, PL_MIN, PL_MAX, this.origin, MOVE_NORMAL, this);
245 if (!trace_startsolid) UNSET_DUCKED(this);
250 vector origin1 = this.origin + '0 0 1';
251 vector origin2 = this.origin - '0 0 1';
255 tracebox(origin1, this.mins, this.maxs, origin2, MOVE_NORMAL, this);
256 if (trace_fraction < 1.0 && trace_plane_normal.z > 0.7)
260 // this code actually "predicts" an impact; so let's clip velocity first
261 this.velocity -= this.velocity * trace_plane_normal * trace_plane_normal;
264 UNSET_ONGROUND(this);
267 // set watertype/waterlevel
268 origin1 = this.origin;
269 origin1.z += this.mins_z + 1;
270 this.waterlevel = WATERLEVEL_NONE;
272 int thepoint = pointcontents(origin1);
274 this.watertype = (thepoint == CONTENT_WATER || thepoint == CONTENT_LAVA || thepoint == CONTENT_SLIME);
278 this.waterlevel = WATERLEVEL_WETFEET;
279 origin1.z = this.origin.z + (this.mins.z + this.maxs.z) * 0.5;
280 thepoint = pointcontents(origin1);
281 if (thepoint == CONTENT_WATER || thepoint == CONTENT_LAVA || thepoint == CONTENT_SLIME)
283 this.waterlevel = WATERLEVEL_SWIMMING;
284 origin1.z = this.origin.z + 22;
285 thepoint = pointcontents(origin1);
286 if (thepoint == CONTENT_WATER || thepoint == CONTENT_LAVA || thepoint == CONTENT_SLIME)
287 this.waterlevel = WATERLEVEL_SUBMERGED;
291 if (IS_ONGROUND(this) || this.velocity.z <= 0 || pmove_waterjumptime <= 0)
292 pmove_waterjumptime = 0;
295 void PM_ClientMovement_Move()
302 vector currentorigin2;
304 vector primalvelocity;
306 vector trace1_endpos = '0 0 0';
307 vector trace2_endpos = '0 0 0';
308 vector trace3_endpos = '0 0 0';
309 float trace1_fraction = 0;
310 float trace2_fraction = 0;
311 float trace3_fraction = 0;
312 vector trace1_plane_normal = '0 0 0';
313 vector trace2_plane_normal = '0 0 0';
314 vector trace3_plane_normal = '0 0 0';
317 PM_ClientMovement_UpdateStatus(this, false);
318 primalvelocity = self.velocity;
319 for(bump = 0, t = PHYS_INPUT_TIMELENGTH; bump < 8 && (self.velocity * self.velocity) > 0; bump++)
321 neworigin = self.origin + t * self.velocity;
322 tracebox(self.origin, self.mins, self.maxs, neworigin, MOVE_NORMAL, self);
323 trace1_endpos = trace_endpos;
324 trace1_fraction = trace_fraction;
325 trace1_plane_normal = trace_plane_normal;
326 if(trace1_fraction < 1 && trace1_plane_normal_z == 0)
328 // may be a step or wall, try stepping up
329 // first move forward at a higher level
330 currentorigin2 = self.origin;
331 currentorigin2_z += PHYS_STEPHEIGHT;
332 neworigin2 = neworigin;
333 neworigin2_z += PHYS_STEPHEIGHT;
334 tracebox(currentorigin2, self.mins, self.maxs, neworigin2, MOVE_NORMAL, self);
335 trace2_endpos = trace_endpos;
336 trace2_fraction = trace_fraction;
337 trace2_plane_normal = trace_plane_normal;
338 if(!trace_startsolid)
340 // then move down from there
341 currentorigin2 = trace2_endpos;
342 neworigin2 = trace2_endpos;
343 neworigin2_z = self.origin_z;
344 tracebox(currentorigin2, self.mins, self.maxs, neworigin2, MOVE_NORMAL, self);
345 trace3_endpos = trace_endpos;
346 trace3_fraction = trace_fraction;
347 trace3_plane_normal = trace_plane_normal;
348 // accept the new trace if it made some progress
349 if(fabs(trace3_endpos_x - trace1_endpos_x) >= 0.03125 || fabs(trace3_endpos_y - trace1_endpos_y) >= 0.03125)
351 trace1_endpos = trace2_endpos;
352 trace1_fraction = trace2_fraction;
353 trace1_plane_normal = trace2_plane_normal;
354 trace1_endpos = trace3_endpos;
359 // check if it moved at all
360 if(trace1_fraction >= 0.001)
361 setorigin(self, trace1_endpos);
363 // check if it moved all the way
364 if(trace1_fraction == 1)
367 // this is only really needed for nogravityonground combined with gravityunaffectedbyticrate
368 // <LordHavoc> I'm pretty sure I commented it out solely because it seemed redundant
369 // this got commented out in a change that supposedly makes the code match QW better
370 // so if this is broken, maybe put it in an if(cls.protocol != PROTOCOL_QUAKEWORLD) block
371 if(trace1_plane_normal_z > 0.7)
374 t -= t * trace1_fraction;
376 f = (self.velocity * trace1_plane_normal);
377 self.velocity = self.velocity + -f * trace1_plane_normal;
379 if(pmove_waterjumptime > 0)
380 self.velocity = primalvelocity;
384 void CPM_PM_Aircontrol(vector wishdir, float wishspeed)
386 float k = 32 * (2 * IsMoveInDirection(self.movement, 0) - 1);
390 k *= bound(0, wishspeed / PHYS_MAXAIRSPEED(self), 1);
392 float zspeed = self.velocity_z;
394 float xyspeed = vlen(self.velocity);
395 self.velocity = normalize(self.velocity);
397 float dot = self.velocity * wishdir;
399 if (dot > 0) // we can't change direction while slowing down
401 k *= pow(dot, PHYS_AIRCONTROL_POWER) * PHYS_INPUT_TIMELENGTH;
402 xyspeed = max(0, xyspeed - PHYS_AIRCONTROL_PENALTY * sqrt(max(0, 1 - dot*dot)) * k/32);
403 k *= PHYS_AIRCONTROL;
404 self.velocity = normalize(self.velocity * xyspeed + wishdir * k);
407 self.velocity = self.velocity * xyspeed;
408 self.velocity_z = zspeed;
411 float AdjustAirAccelQW(float accelqw, float factor)
413 return copysign(bound(0.000001, 1 - (1 - fabs(accelqw)) * factor, 1), accelqw);
416 // example config for alternate speed clamping:
417 // sv_airaccel_qw 0.8
418 // sv_airaccel_sideways_friction 0
419 // prvm_globalset server speedclamp_mode 1
421 void PM_Accelerate(vector wishdir, float wishspeed, float wishspeed0, float accel, float accelqw, float stretchfactor, float sidefric, float speedlimit)
423 float speedclamp = stretchfactor > 0 ? stretchfactor
424 : accelqw < 0 ? 1 // full clamping, no stretch
427 accelqw = fabs(accelqw);
429 if (GAMEPLAYFIX_Q2AIRACCELERATE)
430 wishspeed0 = wishspeed; // don't need to emulate this Q1 bug
432 float vel_straight = self.velocity * wishdir;
433 float vel_z = self.velocity_z;
434 vector vel_xy = vec2(self.velocity);
435 vector vel_perpend = vel_xy - vel_straight * wishdir;
437 float step = accel * PHYS_INPUT_TIMELENGTH * wishspeed0;
439 float vel_xy_current = vlen(vel_xy);
441 accelqw = AdjustAirAccelQW(accelqw, (speedlimit - bound(wishspeed, vel_xy_current, speedlimit)) / max(1, speedlimit - wishspeed));
442 float vel_xy_forward = vel_xy_current + bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw);
443 float vel_xy_backward = vel_xy_current - bound(0, wishspeed + vel_xy_current, step) * accelqw - step * (1 - accelqw);
444 vel_xy_backward = max(0, vel_xy_backward); // not that it REALLY occurs that this would cause wrong behaviour afterwards
445 vel_straight = vel_straight + bound(0, wishspeed - vel_straight, step) * accelqw + step * (1 - accelqw);
447 if (sidefric < 0 && (vel_perpend*vel_perpend))
448 // negative: only apply so much sideways friction to stay below the speed you could get by "braking"
450 float f = max(0, 1 + PHYS_INPUT_TIMELENGTH * wishspeed * sidefric);
451 float fmin = (vel_xy_backward * vel_xy_backward - vel_straight * vel_straight) / (vel_perpend * vel_perpend);
453 // vel_xy_backward*vel_xy_backward - vel_straight*vel_straight > vel_perpend*vel_perpend
454 // vel_xy_backward*vel_xy_backward > vel_straight*vel_straight + vel_perpend*vel_perpend
455 // vel_xy_backward*vel_xy_backward > vel_xy * vel_xy
456 // obviously, this cannot be
462 vel_perpend *= max(fmin, f);
466 vel_perpend *= max(0, 1 - PHYS_INPUT_TIMELENGTH * wishspeed * sidefric);
468 vel_xy = vel_straight * wishdir + vel_perpend;
472 float vel_xy_preclamp;
473 vel_xy_preclamp = vlen(vel_xy);
474 if (vel_xy_preclamp > 0) // prevent division by zero
476 vel_xy_current += (vel_xy_forward - vel_xy_current) * speedclamp;
477 if (vel_xy_current < vel_xy_preclamp)
478 vel_xy *= (vel_xy_current / vel_xy_preclamp);
482 self.velocity = vel_xy + vel_z * '0 0 1';
485 void PM_AirAccelerate(vector wishdir, float wishspeed)
490 vector curvel = self.velocity;
492 float curspeed = vlen(curvel);
494 if (wishspeed > curspeed * 1.01)
495 wishspeed = min(wishspeed, curspeed + PHYS_WARSOWBUNNY_AIRFORWARDACCEL * PHYS_MAXSPEED(self) * PHYS_INPUT_TIMELENGTH);
498 float f = max(0, (PHYS_WARSOWBUNNY_TOPSPEED - curspeed) / (PHYS_WARSOWBUNNY_TOPSPEED - PHYS_MAXSPEED(self)));
499 wishspeed = max(curspeed, PHYS_MAXSPEED(self)) + PHYS_WARSOWBUNNY_ACCEL * f * PHYS_MAXSPEED(self) * PHYS_INPUT_TIMELENGTH;
501 vector wishvel = wishdir * wishspeed;
502 vector acceldir = wishvel - curvel;
503 float addspeed = vlen(acceldir);
504 acceldir = normalize(acceldir);
506 float accelspeed = min(addspeed, PHYS_WARSOWBUNNY_TURNACCEL * PHYS_MAXSPEED(self) * PHYS_INPUT_TIMELENGTH);
508 if (PHYS_WARSOWBUNNY_BACKTOSIDERATIO < 1)
510 vector curdir = normalize(curvel);
511 float dot = acceldir * curdir;
513 acceldir -= (1 - PHYS_WARSOWBUNNY_BACKTOSIDERATIO) * dot * curdir;
516 self.velocity += accelspeed * acceldir;
524 When you press the jump key
525 returns true if handled
530 if (PHYS_FROZEN(self))
531 return true; // no jumping in freezetag when frozen
534 if (self.player_blocked)
535 return true; // no jumping while blocked
538 bool doublejump = false;
539 float mjumpheight = PHYS_JUMPVELOCITY;
541 if (MUTATOR_CALLHOOK(PlayerJump, doublejump, mjumpheight))
544 doublejump = player_multijump;
545 mjumpheight = player_jumpheight;
547 if (self.waterlevel >= WATERLEVEL_SWIMMING)
549 self.velocity_z = PHYS_MAXSPEED(self) * 0.7;
554 if (!IS_ONGROUND(self))
555 return IS_JUMP_HELD(self);
557 bool track_jump = PHYS_CL_TRACK_CANJUMP(self);
558 if(PHYS_TRACK_CANJUMP(self))
562 if (IS_JUMP_HELD(self))
565 // sv_jumpspeedcap_min/sv_jumpspeedcap_max act as baseline
566 // velocity bounds. Final velocity is bound between (jumpheight *
567 // min + jumpheight) and (jumpheight * max + jumpheight);
569 if(PHYS_JUMPSPEEDCAP_MIN != "")
571 float minjumpspeed = mjumpheight * stof(PHYS_JUMPSPEEDCAP_MIN);
573 if (self.velocity_z < minjumpspeed)
574 mjumpheight += minjumpspeed - self.velocity_z;
577 if(PHYS_JUMPSPEEDCAP_MAX != "")
579 // don't do jump speedcaps on ramps to preserve old xonotic ramjump style
580 tracebox(self.origin + '0 0 0.01', self.mins, self.maxs, self.origin - '0 0 0.01', MOVE_NORMAL, self);
582 if (!(trace_fraction < 1 && trace_plane_normal_z < 0.98 && PHYS_JUMPSPEEDCAP_DISABLE_ONRAMPS))
584 float maxjumpspeed = mjumpheight * stof(PHYS_JUMPSPEEDCAP_MAX);
586 if (self.velocity_z > maxjumpspeed)
587 mjumpheight -= self.velocity_z - maxjumpspeed;
591 if (!WAS_ONGROUND(self))
594 if(autocvar_speedmeter)
595 LOG_TRACE(strcat("landing velocity: ", vtos(self.velocity), " (abs: ", ftos(vlen(self.velocity)), ")\n"));
597 if(self.lastground < time - 0.3)
599 self.velocity_x *= (1 - PHYS_FRICTION_ONLAND);
600 self.velocity_y *= (1 - PHYS_FRICTION_ONLAND);
603 if(self.jumppadcount > 1)
604 LOG_TRACE(strcat(ftos(self.jumppadcount), "x jumppad combo\n"));
605 self.jumppadcount = 0;
609 self.velocity_z += mjumpheight;
611 UNSET_ONGROUND(self);
616 self.oldvelocity_z = self.velocity_z;
618 animdecide_setaction(self, ANIMACTION_JUMP, true);
620 if (autocvar_g_jump_grunt)
621 PlayerSound(playersound_jump, CH_PLAYER, VOICETYPE_PLAYERSOUND);
626 void CheckWaterJump()
628 // check for a jump-out-of-water
629 makevectors(self.v_angle);
630 vector start = self.origin;
633 normalize(v_forward);
634 vector end = start + v_forward*24;
635 traceline (start, end, true, self);
636 if (trace_fraction < 1)
638 start_z = start_z + self.maxs_z - 8;
639 end = start + v_forward*24;
640 self.movedir = trace_plane_normal * -50;
641 traceline(start, end, true, self);
642 if (trace_fraction == 1)
643 { // open at eye level
644 self.velocity_z = 225;
645 self.flags |= FL_WATERJUMP;
648 self.teleport_time = time + 2; // safety net
650 pmove_waterjumptime = time + 2;
658 #define JETPACK_JUMP(s) s.cvar_cl_jetpack_jump
660 float autocvar_cl_jetpack_jump;
661 #define JETPACK_JUMP(s) autocvar_cl_jetpack_jump
663 .float jetpack_stopped;
664 void CheckPlayerJump()
667 float was_flying = ITEMS_STAT(self) & IT_USING_JETPACK;
669 if (JETPACK_JUMP(self) < 2)
670 ITEMS_STAT(self) &= ~IT_USING_JETPACK;
672 if(PHYS_INPUT_BUTTON_JUMP(self) || PHYS_INPUT_BUTTON_JETPACK(self))
674 float air_jump = !PlayerJump() || player_multijump; // PlayerJump() has important side effects
675 float activate = JETPACK_JUMP(self) && air_jump && PHYS_INPUT_BUTTON_JUMP(self) || PHYS_INPUT_BUTTON_JETPACK(self);
676 float has_fuel = !PHYS_JETPACK_FUEL || PHYS_AMMO_FUEL(self) || ITEMS_STAT(self) & IT_UNLIMITED_WEAPON_AMMO;
678 if (!(ITEMS_STAT(self) & ITEM_Jetpack.m_itemid)) { }
679 else if (self.jetpack_stopped) { }
683 if (was_flying) // TODO: ran out of fuel message
684 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_JETPACK_NOFUEL);
686 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_JETPACK_NOFUEL);
688 self.jetpack_stopped = true;
689 ITEMS_STAT(self) &= ~IT_USING_JETPACK;
691 else if (activate && !PHYS_FROZEN(self))
692 ITEMS_STAT(self) |= IT_USING_JETPACK;
696 self.jetpack_stopped = false;
697 ITEMS_STAT(self) &= ~IT_USING_JETPACK;
699 if (!PHYS_INPUT_BUTTON_JUMP(self))
700 UNSET_JUMP_HELD(self);
702 if (self.waterlevel == WATERLEVEL_SWIMMING)
706 float racecar_angle(float forward, float down)
714 float ret = vectoyaw('0 1 0' * down + '1 0 0' * forward);
716 float angle_mult = forward / (800 + forward);
719 return ret * angle_mult + 360 * (1 - angle_mult);
721 return ret * angle_mult;
724 string specialcommand = "xwxwxsxsxaxdxaxdx1x ";
725 .float specialcommand_pos;
726 void SpecialCommand()
729 if (!CheatImpulse(99))
730 LOG_INFO("A hollow voice says \"Plugh\".\n");
734 float PM_check_specialcommand(float buttons)
740 else if (buttons == 1)
742 else if (buttons == 2)
744 else if (buttons == 128)
746 else if (buttons == 256)
748 else if (buttons == 512)
750 else if (buttons == 1024)
755 if (c == substring(specialcommand, self.specialcommand_pos, 1))
757 self.specialcommand_pos += 1;
758 if (self.specialcommand_pos >= strlen(specialcommand))
760 self.specialcommand_pos = 0;
765 else if (self.specialcommand_pos && (c != substring(specialcommand, self.specialcommand_pos - 1, 1)))
766 self.specialcommand_pos = 0;
771 void PM_check_nickspam()
774 if (time >= self.nickspamtime)
776 if (self.nickspamcount >= autocvar_g_nick_flood_penalty_yellow)
778 // slight annoyance for nick change scripts
779 self.movement = -1 * self.movement;
780 self.BUTTON_ATCK = self.BUTTON_JUMP = self.BUTTON_ATCK2 = self.BUTTON_ZOOM = self.BUTTON_CROUCH = self.BUTTON_HOOK = self.BUTTON_USE = 0;
782 if (self.nickspamcount >= autocvar_g_nick_flood_penalty_red) // if you are persistent and the slight annoyance above does not stop you, I'll show you!
784 self.v_angle_x = random() * 360;
785 self.v_angle_y = random() * 360;
786 // at least I'm not forcing retardedview by also assigning to angles_z
787 self.fixangle = true;
793 void PM_check_punch()
796 if (self.punchangle != '0 0 0')
798 float f = vlen(self.punchangle) - 10 * PHYS_INPUT_TIMELENGTH;
800 self.punchangle = normalize(self.punchangle) * f;
802 self.punchangle = '0 0 0';
805 if (self.punchvector != '0 0 0')
807 float f = vlen(self.punchvector) - 30 * PHYS_INPUT_TIMELENGTH;
809 self.punchvector = normalize(self.punchvector) * f;
811 self.punchvector = '0 0 0';
816 // predict frozen movement, as frozen players CAN move in some cases
817 void PM_check_frozen()
819 if (!PHYS_FROZEN(self))
821 if (PHYS_DODGING_FROZEN
823 && IS_REAL_CLIENT(self)
827 self.movement_x = bound(-5, self.movement.x, 5);
828 self.movement_y = bound(-5, self.movement.y, 5);
829 self.movement_z = bound(-5, self.movement.z, 5);
832 self.movement = '0 0 0';
834 vector midpoint = ((self.absmin + self.absmax) * 0.5);
835 if (pointcontents(midpoint) == CONTENT_WATER)
837 self.velocity = self.velocity * 0.5;
839 if (pointcontents(midpoint + '0 0 16') == CONTENT_WATER)
840 self.velocity_z = 200;
844 void PM_check_hitground()
847 if (!IS_PLAYER(this)) return; // no fall sounds for observers thank you very much
848 if (!IS_ONGROUND(this)) return;
849 if (!this.wasFlying) return;
850 this.wasFlying = false;
851 if (this.waterlevel >= WATERLEVEL_SWIMMING) return;
852 if (time < this.ladder_time) return;
853 if (this.hook) return;
854 this.nextstep = time + 0.3 + random() * 0.1;
855 trace_dphitq3surfaceflags = 0;
856 tracebox(this.origin, this.mins, this.maxs, this.origin - '0 0 1', MOVE_NOMONSTERS, this);
857 if ((trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOSTEPS)) return;
858 entity fall = (trace_dphitq3surfaceflags & Q3SURFACEFLAG_METALSTEPS) ? GS_FALL_METAL : GS_FALL;
859 GlobalSound(fall, CH_PLAYER, VOICETYPE_PLAYERSOUND);
863 void PM_check_blocked()
866 if (!self.player_blocked)
868 self.movement = '0 0 0';
869 self.disableclientprediction = 1;
873 void PM_fly(float maxspd_mod)
875 // noclipping or flying
876 UNSET_ONGROUND(self);
878 self.velocity = self.velocity * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION);
879 makevectors(self.v_angle);
880 //wishvel = v_forward * self.movement.x + v_right * self.movement.y + v_up * self.movement.z;
881 vector wishvel = v_forward * self.movement.x
882 + v_right * self.movement.y
883 + '0 0 1' * self.movement.z;
885 vector wishdir = normalize(wishvel);
886 float wishspeed = min(vlen(wishvel), PHYS_MAXSPEED(self) * maxspd_mod);
888 if (time >= self.teleport_time)
890 PM_Accelerate(wishdir, wishspeed, wishspeed, PHYS_ACCELERATE * maxspd_mod, 1, 0, 0, 0);
891 PM_ClientMovement_Move();
894 void PM_swim(float maxspd_mod)
897 UNSET_ONGROUND(self);
899 float jump = PHYS_INPUT_BUTTON_JUMP(self);
900 // water jump only in certain situations
901 // this mimics quakeworld code
902 if (jump && self.waterlevel == WATERLEVEL_SWIMMING && self.velocity_z >= -180)
904 vector yawangles = '0 1 0' * self.v_angle.y;
905 makevectors(yawangles);
906 vector forward = v_forward;
907 vector spot = self.origin + 24 * forward;
909 traceline(spot, spot, MOVE_NOMONSTERS, self);
910 if (trace_startsolid)
913 traceline(spot, spot, MOVE_NOMONSTERS, self);
914 if (!trace_startsolid)
916 self.velocity = forward * 50;
917 self.velocity_z = 310;
918 pmove_waterjumptime = 2;
919 UNSET_ONGROUND(self);
924 makevectors(self.v_angle);
925 //wishvel = v_forward * self.movement.x + v_right * self.movement.y + v_up * self.movement.z;
926 vector wishvel = v_forward * self.movement.x
927 + v_right * self.movement.y
928 + '0 0 1' * self.movement.z;
929 if (wishvel == '0 0 0')
930 wishvel = '0 0 -60'; // drift towards bottom
932 vector wishdir = normalize(wishvel);
933 float wishspeed = min(vlen(wishvel), PHYS_MAXSPEED(self) * maxspd_mod) * 0.7;
938 // if (pmove_waterjumptime <= 0) // TODO: use
941 float f = 1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION;
942 f = min(max(0, f), 1);
945 f = wishspeed - self.velocity * wishdir;
948 float accelspeed = min(PHYS_ACCELERATE * PHYS_INPUT_TIMELENGTH * wishspeed, f);
949 self.velocity += accelspeed * wishdir;
952 // holding jump button swims upward slowly
956 if (self.watertype & CONTENT_LAVA)
957 self.velocity_z = 50;
958 else if (self.watertype & CONTENT_SLIME)
959 self.velocity_z = 80;
962 if (IS_NEXUIZ_DERIVED(gamemode))
964 self.velocity_z = 200;
967 self.velocity_z = 100;
972 // water acceleration
973 PM_Accelerate(wishdir, wishspeed, wishspeed, PHYS_ACCELERATE * maxspd_mod, 1, 0, 0, 0);
974 PM_ClientMovement_Move();
977 void PM_ladder(float maxspd_mod)
979 // on a spawnfunc_func_ladder or swimming in spawnfunc_func_water
980 UNSET_ONGROUND(self);
983 g = PHYS_GRAVITY * PHYS_INPUT_TIMELENGTH;
984 if (PHYS_ENTGRAVITY(self))
985 g *= PHYS_ENTGRAVITY(self);
986 if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
989 self.velocity_z += g;
992 self.velocity = self.velocity * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION);
993 makevectors(self.v_angle);
994 //wishvel = v_forward * self.movement.x + v_right * self.movement.y + v_up * self.movement.z;
995 vector wishvel = v_forward * self.movement_x
996 + v_right * self.movement_y
997 + '0 0 1' * self.movement_z;
998 self.velocity_z += g;
999 if (self.ladder_entity.classname == "func_water")
1001 float f = vlen(wishvel);
1002 if (f > self.ladder_entity.speed)
1003 wishvel *= (self.ladder_entity.speed / f);
1005 self.watertype = self.ladder_entity.skin;
1006 f = self.ladder_entity.origin_z + self.ladder_entity.maxs_z;
1007 if ((self.origin_z + self.view_ofs_z) < f)
1008 self.waterlevel = WATERLEVEL_SUBMERGED;
1009 else if ((self.origin_z + (self.mins_z + self.maxs_z) * 0.5) < f)
1010 self.waterlevel = WATERLEVEL_SWIMMING;
1011 else if ((self.origin_z + self.mins_z + 1) < f)
1012 self.waterlevel = WATERLEVEL_WETFEET;
1015 self.waterlevel = WATERLEVEL_NONE;
1016 self.watertype = CONTENT_EMPTY;
1020 vector wishdir = normalize(wishvel);
1021 float wishspeed = min(vlen(wishvel), PHYS_MAXSPEED(self) * maxspd_mod);
1023 if (time >= self.teleport_time)
1025 // water acceleration
1026 PM_Accelerate(wishdir, wishspeed, wishspeed, PHYS_ACCELERATE*maxspd_mod, 1, 0, 0, 0);
1027 PM_ClientMovement_Move();
1030 void PM_jetpack(float maxspd_mod)
1032 //makevectors(self.v_angle.y * '0 1 0');
1033 makevectors(self.v_angle);
1034 vector wishvel = v_forward * self.movement_x
1035 + v_right * self.movement_y;
1036 // add remaining speed as Z component
1037 float maxairspd = PHYS_MAXAIRSPEED(self) * max(1, maxspd_mod);
1038 // fix speedhacks :P
1039 wishvel = normalize(wishvel) * min(1, vlen(wishvel) / maxairspd);
1040 // add the unused velocity as up component
1043 // if (self.BUTTON_JUMP)
1044 wishvel_z = sqrt(max(0, 1 - wishvel * wishvel));
1046 // it is now normalized, so...
1047 float a_side = PHYS_JETPACK_ACCEL_SIDE;
1048 float a_up = PHYS_JETPACK_ACCEL_UP;
1049 float a_add = PHYS_JETPACK_ANTIGRAVITY * PHYS_GRAVITY;
1051 wishvel_x *= a_side;
1052 wishvel_y *= a_side;
1057 //////////////////////////////////////////////////////////////////////////////////////
1058 // finding the maximum over all vectors of above form
1059 // with wishvel having an absolute value of 1
1060 //////////////////////////////////////////////////////////////////////////////////////
1061 // we're finding the maximum over
1062 // f(a_side, a_up, a_add, z) := a_side * (1 - z^2) + (a_add + a_up * z)^2;
1063 // for z in the range from -1 to 1
1064 //////////////////////////////////////////////////////////////////////////////////////
1065 // maximum is EITHER attained at the single extreme point:
1066 float a_diff = a_side * a_side - a_up * a_up;
1070 f = a_add * a_up / a_diff; // this is the zero of diff(f(a_side, a_up, a_add, z), z)
1071 if (f > -1 && f < 1) // can it be attained?
1073 best = (a_diff + a_add * a_add) * (a_diff + a_up * a_up) / a_diff;
1074 //print("middle\n");
1077 // OR attained at z = 1:
1078 f = (a_up + a_add) * (a_up + a_add);
1084 // OR attained at z = -1:
1085 f = (a_up - a_add) * (a_up - a_add);
1089 //print("bottom\n");
1092 //////////////////////////////////////////////////////////////////////////////////////
1094 //print("best possible acceleration: ", ftos(best), "\n");
1097 fxy = bound(0, 1 - (self.velocity * normalize(wishvel_x * '1 0 0' + wishvel_y * '0 1 0')) / PHYS_JETPACK_MAXSPEED_SIDE, 1);
1098 if (wishvel_z - PHYS_GRAVITY > 0)
1099 fz = bound(0, 1 - self.velocity_z / PHYS_JETPACK_MAXSPEED_UP, 1);
1101 fz = bound(0, 1 + self.velocity_z / PHYS_JETPACK_MAXSPEED_UP, 1);
1104 fvel = vlen(wishvel);
1107 wishvel_z = (wishvel_z - PHYS_GRAVITY) * fz + PHYS_GRAVITY;
1109 fvel = min(1, vlen(wishvel) / best);
1110 if (PHYS_JETPACK_FUEL && !(ITEMS_STAT(self) & IT_UNLIMITED_WEAPON_AMMO))
1111 f = min(1, PHYS_AMMO_FUEL(self) / (PHYS_JETPACK_FUEL * PHYS_INPUT_TIMELENGTH * fvel));
1115 //print("this acceleration: ", ftos(vlen(wishvel) * f), "\n");
1117 if (f > 0 && wishvel != '0 0 0')
1119 self.velocity = self.velocity + wishvel * f * PHYS_INPUT_TIMELENGTH;
1120 UNSET_ONGROUND(self);
1123 if (!(ITEMS_STAT(self) & IT_UNLIMITED_WEAPON_AMMO))
1124 self.ammo_fuel -= PHYS_JETPACK_FUEL * PHYS_INPUT_TIMELENGTH * fvel * f;
1126 ITEMS_STAT(self) |= IT_USING_JETPACK;
1128 // jetpack also inhibits health regeneration, but only for 1 second
1129 self.pauseregen_finished = max(self.pauseregen_finished, time + autocvar_g_balance_pause_fuel_regen);
1134 float g = PHYS_GRAVITY * PHYS_ENTGRAVITY(self) * PHYS_INPUT_TIMELENGTH;
1135 if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1136 self.velocity_z -= g * 0.5;
1138 self.velocity_z -= g;
1139 PM_ClientMovement_Move();
1140 if (!IS_ONGROUND(self) || !(GAMEPLAYFIX_NOGRAVITYONGROUND))
1141 if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1142 self.velocity_z -= g * 0.5;
1146 void PM_walk(entity this, float maxspd_mod)
1148 if (!WAS_ONGROUND(this))
1151 if (autocvar_speedmeter)
1152 LOG_TRACE(strcat("landing velocity: ", vtos(this.velocity), " (abs: ", ftos(vlen(this.velocity)), ")\n"));
1154 if (this.lastground < time - 0.3)
1155 this.velocity *= (1 - PHYS_FRICTION_ONLAND);
1157 if (this.jumppadcount > 1)
1158 LOG_TRACE(strcat(ftos(this.jumppadcount), "x jumppad combo\n"));
1159 this.jumppadcount = 0;
1164 makevectors(this.v_angle.y * '0 1 0');
1165 const vector wishvel = v_forward * this.movement.x
1166 + v_right * this.movement.y;
1168 const vector wishdir = normalize(wishvel);
1169 float wishspeed = vlen(wishvel);
1170 wishspeed = min(wishspeed, PHYS_MAXSPEED(this) * maxspd_mod);
1171 if (IS_DUCKED(this)) wishspeed *= 0.5;
1173 // apply edge friction
1174 const float f2 = vlen2(vec2(this.velocity));
1177 trace_dphitq3surfaceflags = 0;
1178 tracebox(this.origin, this.mins, this.maxs, this.origin - '0 0 1', MOVE_NOMONSTERS, this);
1179 // TODO: apply edge friction
1180 // apply ground friction
1181 const int realfriction = (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK)
1182 ? PHYS_FRICTION_SLICK
1186 f = 1 - PHYS_INPUT_TIMELENGTH * realfriction * ((f < PHYS_STOPSPEED) ? (PHYS_STOPSPEED / f) : 1);
1190 Mathematical analysis time!
1192 Our goal is to invert this mess.
1194 For the two cases we get:
1195 v = v0 * (1 - PHYS_INPUT_TIMELENGTH * (PHYS_STOPSPEED / v0) * PHYS_FRICTION)
1196 = v0 - PHYS_INPUT_TIMELENGTH * PHYS_STOPSPEED * PHYS_FRICTION
1197 v0 = v + PHYS_INPUT_TIMELENGTH * PHYS_STOPSPEED * PHYS_FRICTION
1199 v = v0 * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION)
1200 v0 = v / (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION)
1202 These cases would be chosen ONLY if:
1204 v + PHYS_INPUT_TIMELENGTH * PHYS_STOPSPEED * PHYS_FRICTION < PHYS_STOPSPEED
1205 v < PHYS_STOPSPEED * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION)
1207 v0 >= PHYS_STOPSPEED
1208 v / (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION) >= PHYS_STOPSPEED
1209 v >= PHYS_STOPSPEED * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION)
1212 const float addspeed = wishspeed - this.velocity * wishdir;
1215 const float accelspeed = min(PHYS_ACCELERATE * PHYS_INPUT_TIMELENGTH * wishspeed, addspeed);
1216 this.velocity += accelspeed * wishdir;
1218 const float g = PHYS_GRAVITY * PHYS_ENTGRAVITY(this) * PHYS_INPUT_TIMELENGTH;
1219 if (!(GAMEPLAYFIX_NOGRAVITYONGROUND))
1220 this.velocity_z -= g * (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE ? 0.5 : 1);
1221 if (vdist(this.velocity, >, 0))
1222 PM_ClientMovement_Move();
1223 if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1224 if (!IS_ONGROUND(this) || !GAMEPLAYFIX_NOGRAVITYONGROUND)
1225 this.velocity_z -= g * 0.5;
1228 void PM_air(float buttons_prev, float maxspd_mod)
1230 makevectors(self.v_angle.y * '0 1 0');
1231 vector wishvel = v_forward * self.movement.x
1232 + v_right * self.movement.y;
1234 vector wishdir = normalize(wishvel);
1235 float wishspeed = vlen(wishvel);
1238 if (time >= self.teleport_time)
1240 if (pmove_waterjumptime <= 0)
1243 float maxairspd = PHYS_MAXAIRSPEED(self) * min(maxspd_mod, 1);
1245 // apply air speed limit
1246 float airaccelqw = PHYS_AIRACCEL_QW(self);
1247 float wishspeed0 = wishspeed;
1248 wishspeed = min(wishspeed, maxairspd);
1249 if (IS_DUCKED(self))
1251 float airaccel = PHYS_AIRACCELERATE * min(maxspd_mod, 1);
1253 float accelerating = (self.velocity * wishdir > 0);
1254 float wishspeed2 = wishspeed;
1257 if (PHYS_AIRSTOPACCELERATE)
1259 vector curdir = normalize(vec2(self.velocity));
1260 airaccel += (PHYS_AIRSTOPACCELERATE*maxspd_mod - airaccel) * max(0, -(curdir * wishdir));
1262 // note that for straight forward jumping:
1263 // step = accel * PHYS_INPUT_TIMELENGTH * wishspeed0;
1264 // accel = bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw);
1266 // dv/dt = accel * maxspeed (when slow)
1267 // dv/dt = accel * maxspeed * (1 - accelqw) (when fast)
1268 // log dv/dt = logaccel + logmaxspeed (when slow)
1269 // log dv/dt = logaccel + logmaxspeed + log(1 - accelqw) (when fast)
1270 float strafity = IsMoveInDirection(self.movement, -90) + IsMoveInDirection(self.movement, +90); // if one is nonzero, other is always zero
1271 if (PHYS_MAXAIRSTRAFESPEED)
1272 wishspeed = min(wishspeed, GeomLerp(PHYS_MAXAIRSPEED(self)*maxspd_mod, strafity, PHYS_MAXAIRSTRAFESPEED*maxspd_mod));
1273 if (PHYS_AIRSTRAFEACCELERATE(self))
1274 airaccel = GeomLerp(airaccel, strafity, PHYS_AIRSTRAFEACCELERATE(self)*maxspd_mod);
1275 if (PHYS_AIRSTRAFEACCEL_QW(self))
1277 (((strafity > 0.5 ? PHYS_AIRSTRAFEACCEL_QW(self) : PHYS_AIRACCEL_QW(self)) >= 0) ? +1 : -1)
1279 (1 - GeomLerp(1 - fabs(PHYS_AIRACCEL_QW(self)), strafity, 1 - fabs(PHYS_AIRSTRAFEACCEL_QW(self))));
1282 if (PHYS_WARSOWBUNNY_TURNACCEL && accelerating && self.movement.y == 0 && self.movement.x != 0)
1283 PM_AirAccelerate(wishdir, wishspeed2);
1285 PM_Accelerate(wishdir, wishspeed, wishspeed0, airaccel, airaccelqw, PHYS_AIRACCEL_QW_STRETCHFACTOR(self), PHYS_AIRACCEL_SIDEWAYS_FRICTION / maxairspd, PHYS_AIRSPEEDLIMIT_NONQW(self));
1287 if (PHYS_AIRCONTROL)
1288 CPM_PM_Aircontrol(wishdir, wishspeed2);
1290 float g = PHYS_GRAVITY * PHYS_ENTGRAVITY(self) * PHYS_INPUT_TIMELENGTH;
1291 if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1292 self.velocity_z -= g * 0.5;
1294 self.velocity_z -= g;
1295 PM_ClientMovement_Move();
1296 if (!IS_ONGROUND(self) || !(GAMEPLAYFIX_NOGRAVITYONGROUND))
1297 if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1298 self.velocity_z -= g * 0.5;
1301 // used for calculating airshots
1302 bool IsFlying(entity a)
1306 if(a.waterlevel >= WATERLEVEL_SWIMMING)
1308 traceline(a.origin, a.origin - '0 0 48', MOVE_NORMAL, a);
1309 if(trace_fraction < 1)
1314 void PM_Main(entity this)
1316 int buttons = PHYS_INPUT_BUTTON_MASK(this);
1318 this.items = getstati(STAT_ITEMS, 0, 24);
1320 this.movement = PHYS_INPUT_MOVEVALUES(this);
1322 vector oldv_angle = this.v_angle;
1323 vector oldangles = this.angles; // we need to save these, as they're abused by other code
1324 this.v_angle = PHYS_INPUT_ANGLES(this);
1325 this.angles = PHYS_WORLD_ANGLES(this);
1327 this.team = myteam + 1; // is this correct?
1328 if (!(PHYS_INPUT_BUTTON_JUMP(this))) // !jump
1329 UNSET_JUMP_HELD(this); // canjump = true
1330 pmove_waterjumptime -= PHYS_INPUT_TIMELENGTH;
1332 PM_ClientMovement_UpdateStatus(this, true);
1337 WarpZone_PlayerPhysics_FixVAngle();
1339 float maxspeed_mod = 1;
1340 maxspeed_mod *= PHYS_HIGHSPEED;
1343 Physics_UpdateStats(maxspeed_mod);
1345 if (this.PlayerPhysplug)
1346 if (this.PlayerPhysplug())
1351 anticheat_physics();
1354 if (PM_check_specialcommand(buttons))
1359 if (buttons != this.buttons_old || this.movement != this.movement_old || this.v_angle != this.v_angle_old)
1360 this.parm_idlesince = time;
1363 int buttons_prev = this.buttons_old;
1364 this.buttons_old = buttons;
1365 this.movement_old = this.movement;
1366 this.v_angle_old = this.v_angle;
1368 PM_check_nickspam();
1372 if (IS_BOT_CLIENT(this))
1374 if (playerdemo_read())
1381 if (IS_PLAYER(this))
1383 const bool allowed_to_move = (time >= game_starttime);
1384 if (!allowed_to_move)
1386 this.velocity = '0 0 0';
1387 this.movetype = MOVETYPE_NONE;
1388 this.disableclientprediction = 2;
1390 else if (this.disableclientprediction == 2)
1392 if (this.movetype == MOVETYPE_NONE)
1393 this.movetype = MOVETYPE_WALK;
1394 this.disableclientprediction = 0;
1400 if (this.movetype == MOVETYPE_NONE)
1403 // when we get here, disableclientprediction cannot be 2
1404 this.disableclientprediction = 0;
1407 viewloc_PlayerPhysics();
1416 maxspeed_mod *= this.swamp_slowdown; //cvar("g_balance_swamp_moverate");
1418 // conveyors: first fix velocity
1419 if (this.conveyor.state)
1420 this.velocity -= this.conveyor.movedir;
1422 MUTATOR_CALLHOOK(PlayerPhysics);
1425 if (!IS_PLAYER(this))
1427 maxspeed_mod = autocvar_sv_spectator_speed_multiplier;
1428 if (!this.spectatorspeed)
1429 this.spectatorspeed = maxspeed_mod;
1430 if (this.impulse && this.impulse <= 19 || (this.impulse >= 200 && this.impulse <= 209) || (this.impulse >= 220 && this.impulse <= 229))
1432 if (this.lastclassname != "player")
1434 if (this.impulse == 10 || this.impulse == 15 || this.impulse == 18 || (this.impulse >= 200 && this.impulse <= 209))
1435 this.spectatorspeed = bound(1, this.spectatorspeed + 0.5, 5);
1436 else if (this.impulse == 11)
1437 this.spectatorspeed = maxspeed_mod;
1438 else if (this.impulse == 12 || this.impulse == 16 || this.impulse == 19 || (this.impulse >= 220 && this.impulse <= 229))
1439 this.spectatorspeed = bound(1, this.spectatorspeed - 0.5, 5);
1440 else if (this.impulse >= 1 && this.impulse <= 9)
1441 this.spectatorspeed = 1 + 0.5 * (this.impulse - 1);
1442 } // otherwise just clear
1445 maxspeed_mod = this.spectatorspeed;
1448 float spd = max(PHYS_MAXSPEED(this), PHYS_MAXAIRSPEED(this)) * maxspeed_mod;
1449 if(this.speed != spd)
1452 string temps = ftos(spd);
1453 stuffcmd(this, strcat("cl_forwardspeed ", temps, "\n"));
1454 stuffcmd(this, strcat("cl_backspeed ", temps, "\n"));
1455 stuffcmd(this, strcat("cl_sidespeed ", temps, "\n"));
1456 stuffcmd(this, strcat("cl_upspeed ", temps, "\n"));
1459 if(this.stat_jumpspeedcap_min != PHYS_JUMPSPEEDCAP_MIN)
1461 this.stat_jumpspeedcap_min = PHYS_JUMPSPEEDCAP_MIN;
1462 stuffcmd(this, strcat("cl_jumpspeedcap_min ", PHYS_JUMPSPEEDCAP_MIN, "\n"));
1464 if(this.stat_jumpspeedcap_max != PHYS_JUMPSPEEDCAP_MAX)
1466 this.stat_jumpspeedcap_min = PHYS_JUMPSPEEDCAP_MAX;
1467 stuffcmd(this, strcat("cl_jumpspeedcap_max ", PHYS_JUMPSPEEDCAP_MAX, "\n"));
1473 // handle water here
1474 vector midpoint = ((this.absmin + this.absmax) * 0.5);
1475 if(pointcontents(midpoint) == CONTENT_WATER)
1477 this.velocity = this.velocity * 0.5;
1480 //if(pointcontents(midpoint + '0 0 2') == CONTENT_WATER)
1481 //{ this.velocity_z = 70; }
1488 this.angles = '0 1 0' * this.v_angle.y;
1491 PM_check_hitground();
1496 if (IS_PLAYER(this))
1499 if (this.flags & FL_WATERJUMP)
1501 this.velocity_x = this.movedir.x;
1502 this.velocity_y = this.movedir.y;
1503 if (time > this.teleport_time || this.waterlevel == WATERLEVEL_NONE)
1505 this.flags &= ~FL_WATERJUMP;
1506 this.teleport_time = 0;
1510 else if (MUTATOR_CALLHOOK(PM_Physics, maxspeed_mod))
1513 else if (this.movetype == MOVETYPE_NOCLIP || this.movetype == MOVETYPE_FLY || this.movetype == MOVETYPE_FLY_WORLDONLY || MUTATOR_CALLHOOK(IsFlying, this))
1514 PM_fly(maxspeed_mod);
1516 else if (this.waterlevel >= WATERLEVEL_SWIMMING)
1517 PM_swim(maxspeed_mod);
1519 else if (time < this.ladder_time)
1520 PM_ladder(maxspeed_mod);
1522 else if (ITEMS_STAT(this) & IT_USING_JETPACK)
1523 PM_jetpack(maxspeed_mod);
1525 else if (IS_ONGROUND(this))
1526 PM_walk(this, maxspeed_mod);
1529 PM_air(buttons_prev, maxspeed_mod);
1532 if (IS_ONGROUND(this))
1533 this.lastground = time;
1535 // conveyors: then break velocity again
1536 if(this.conveyor.state)
1537 this.velocity += this.conveyor.movedir;
1539 this.lastflags = this.flags;
1541 this.lastclassname = this.classname;
1544 this.v_angle = oldv_angle;
1545 this.angles = oldangles;
1550 void SV_PlayerPhysics()
1552 void CSQC_ClientMovement_PlayerMove_Frame(entity this)
1561 ((this.flags & FL_DUCKED) ? PMF_DUCKED : 0) |
1562 (!(this.flags & FL_JUMPRELEASED) ? PMF_JUMP_HELD : 0) |
1563 ((this.flags & FL_ONGROUND) ? PMF_ONGROUND : 0);