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_DOUBLEJUMP, AS_INT, stat_doublejump);
80 addstat(STAT_MOVEVARS_JUMPSPEEDCAP_DISABLE_ONRAMPS, AS_INT, stat_jumpspeedcap_disable_onramps);
83 addstat(STAT_MOVEVARS_FRICTION_ONLAND, AS_FLOAT, stat_sv_friction_on_land);
84 addstat(STAT_MOVEVARS_FRICTION_SLICK, AS_FLOAT, stat_sv_friction_slick);
85 addstat(STAT_GAMEPLAYFIX_EASIERWATERJUMP, AS_INT, stat_gameplayfix_easierwaterjump);
88 addstat(STAT_MOVEVARS_JUMPVELOCITY, AS_FLOAT, stat_sv_jumpvelocity);
89 addstat(STAT_MOVEVARS_AIRACCEL_QW_STRETCHFACTOR, AS_FLOAT, stat_sv_airaccel_qw_stretchfactor);
90 addstat(STAT_MOVEVARS_MAXAIRSTRAFESPEED, AS_FLOAT, stat_sv_maxairstrafespeed);
91 addstat(STAT_MOVEVARS_MAXAIRSPEED, AS_FLOAT, stat_sv_maxairspeed);
92 addstat(STAT_MOVEVARS_AIRSTRAFEACCELERATE, AS_FLOAT, stat_sv_airstrafeaccelerate);
93 addstat(STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL, AS_FLOAT, stat_sv_warsowbunny_turnaccel);
94 addstat(STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION, AS_FLOAT, stat_sv_airaccel_sideways_friction);
95 addstat(STAT_MOVEVARS_AIRCONTROL, AS_FLOAT, stat_sv_aircontrol);
96 addstat(STAT_MOVEVARS_AIRCONTROL_POWER, AS_FLOAT, stat_sv_aircontrol_power);
97 addstat(STAT_MOVEVARS_AIRCONTROL_PENALTY, AS_FLOAT, stat_sv_aircontrol_penalty);
98 addstat(STAT_MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL, AS_FLOAT, stat_sv_warsowbunny_airforwardaccel);
99 addstat(STAT_MOVEVARS_WARSOWBUNNY_TOPSPEED, AS_FLOAT, stat_sv_warsowbunny_topspeed);
100 addstat(STAT_MOVEVARS_WARSOWBUNNY_ACCEL, AS_FLOAT, stat_sv_warsowbunny_accel);
101 addstat(STAT_MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO, AS_FLOAT, stat_sv_warsowbunny_backtosideratio);
102 addstat(STAT_MOVEVARS_FRICTION, AS_FLOAT, stat_sv_friction);
103 addstat(STAT_MOVEVARS_ACCELERATE, AS_FLOAT, stat_sv_accelerate);
104 addstat(STAT_MOVEVARS_STOPSPEED, AS_FLOAT, stat_sv_stopspeed);
105 addstat(STAT_MOVEVARS_AIRACCELERATE, AS_FLOAT, stat_sv_airaccelerate);
106 addstat(STAT_MOVEVARS_AIRSTOPACCELERATE, AS_FLOAT, stat_sv_airstopaccelerate);
108 addstat(STAT_GAMEPLAYFIX_UPVELOCITYCLEARSONGROUND, AS_INT, stat_gameplayfix_upvelocityclearsonground);
111 void Physics_UpdateStats(float maxspd_mod)
114 self.stat_pl_view_ofs = PL_VIEW_OFS;
115 self.stat_pl_crouch_view_ofs = PL_CROUCH_VIEW_OFS;
117 self.stat_pl_min = PL_MIN;
118 self.stat_pl_max = PL_MAX;
119 self.stat_pl_crouch_min = PL_CROUCH_MIN;
120 self.stat_pl_crouch_max = PL_CROUCH_MAX;
123 self.stat_sv_airaccel_qw = AdjustAirAccelQW(Physics_ClientOption(self, "airaccel_qw"), maxspd_mod);
124 if(Physics_ClientOption(self, "airstrafeaccel_qw"))
125 self.stat_sv_airstrafeaccel_qw = AdjustAirAccelQW(Physics_ClientOption(self, "airstrafeaccel_qw"), maxspd_mod);
127 self.stat_sv_airstrafeaccel_qw = 0;
128 self.stat_sv_airspeedlimit_nonqw = Physics_ClientOption(self, "airspeedlimit_nonqw") * maxspd_mod;
129 self.stat_sv_maxspeed = Physics_ClientOption(self, "maxspeed") * maxspd_mod; // also slow walking
130 self.stat_movement_highspeed = PHYS_HIGHSPEED; // TODO: remove this!
132 self.stat_doublejump = PHYS_DOUBLEJUMP;
134 self.stat_jetpack_antigravity = PHYS_JETPACK_ANTIGRAVITY;
135 self.stat_jetpack_accel_up = PHYS_JETPACK_ACCEL_UP;
136 self.stat_jetpack_accel_side = PHYS_JETPACK_ACCEL_SIDE;
137 self.stat_jetpack_maxspeed_side = PHYS_JETPACK_MAXSPEED_SIDE;
138 self.stat_jetpack_maxspeed_up = PHYS_JETPACK_MAXSPEED_UP;
139 self.stat_jetpack_fuel = PHYS_JETPACK_FUEL;
141 self.stat_jumpspeedcap_disable_onramps = PHYS_JUMPSPEEDCAP_DISABLE_ONRAMPS;
143 self.stat_sv_friction_on_land = PHYS_FRICTION_ONLAND;
144 self.stat_sv_friction_slick = PHYS_FRICTION_SLICK;
146 self.stat_gameplayfix_easierwaterjump = GAMEPLAYFIX_EASIERWATERJUMP;
150 // fix some new settings
151 self.stat_sv_airaccel_qw_stretchfactor = Physics_ClientOption(self, "airaccel_qw_stretchfactor");
152 self.stat_sv_maxairstrafespeed = Physics_ClientOption(self, "maxairstrafespeed");
153 self.stat_sv_maxairspeed = Physics_ClientOption(self, "maxairspeed");
154 self.stat_sv_airstrafeaccelerate = Physics_ClientOption(self, "airstrafeaccelerate");
155 self.stat_sv_warsowbunny_turnaccel = Physics_ClientOption(self, "warsowbunny_turnaccel");
156 self.stat_sv_airaccel_sideways_friction = Physics_ClientOption(self, "airaccel_sideways_friction");
157 self.stat_sv_aircontrol = Physics_ClientOption(self, "aircontrol");
158 self.stat_sv_aircontrol_power = Physics_ClientOption(self, "aircontrol_power");
159 self.stat_sv_aircontrol_penalty = Physics_ClientOption(self, "aircontrol_penalty");
160 self.stat_sv_warsowbunny_airforwardaccel = Physics_ClientOption(self, "warsowbunny_airforwardaccel");
161 self.stat_sv_warsowbunny_topspeed = Physics_ClientOption(self, "warsowbunny_topspeed");
162 self.stat_sv_warsowbunny_accel = Physics_ClientOption(self, "warsowbunny_accel");
163 self.stat_sv_warsowbunny_backtosideratio = Physics_ClientOption(self, "warsowbunny_backtosideratio");
164 self.stat_sv_friction = Physics_ClientOption(self, "friction");
165 self.stat_sv_accelerate = Physics_ClientOption(self, "accelerate");
166 self.stat_sv_stopspeed = Physics_ClientOption(self, "stopspeed");
167 self.stat_sv_airaccelerate = Physics_ClientOption(self, "airaccelerate");
168 self.stat_sv_airstopaccelerate = Physics_ClientOption(self, "airstopaccelerate");
169 self.stat_sv_jumpvelocity = Physics_ClientOption(self, "jumpvelocity");
171 self.stat_sv_track_canjump = Physics_ClientOption(self, "track_canjump");
173 self.stat_gameplayfix_upvelocityclearsonground = UPWARD_VELOCITY_CLEARS_ONGROUND;
177 float IsMoveInDirection(vector mv, float ang) // key mix factor
179 if (mv_x == 0 && mv_y == 0)
180 return 0; // avoid division by zero
181 ang -= RAD2DEG * atan2(mv_y, mv_x);
182 ang = remainder(ang, 360) / 45;
183 return ang > 1 ? 0 : ang < -1 ? 0 : 1 - fabs(ang);
186 float GeomLerp(float a, float lerp, float b)
188 return a == 0 ? (lerp < 1 ? 0 : b)
189 : b == 0 ? (lerp > 0 ? 0 : a)
190 : a * pow(fabs(b / a), lerp);
193 noref float pmove_waterjumptime;
195 #define unstick_offsets(X) \
196 /* 1 no nudge (just return the original if this test passes) */ \
197 X(' 0.000 0.000 0.000') \
198 /* 6 simple nudges */ \
199 X(' 0.000 0.000 0.125') X('0.000 0.000 -0.125') \
200 X('-0.125 0.000 0.000') X('0.125 0.000 0.000') \
201 X(' 0.000 -0.125 0.000') X('0.000 0.125 0.000') \
202 /* 4 diagonal flat nudges */ \
203 X('-0.125 -0.125 0.000') X('0.125 -0.125 0.000') \
204 X('-0.125 0.125 0.000') X('0.125 0.125 0.000') \
205 /* 8 diagonal upward 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') \
210 /* 8 diagonal downward nudges */ \
211 X('-0.125 0.000 -0.125') X('0.125 0.000 -0.125') \
212 X(' 0.000 -0.125 -0.125') X('0.000 0.125 -0.125') \
213 X('-0.125 -0.125 -0.125') X('0.125 -0.125 -0.125') \
214 X('-0.125 0.125 -0.125') X('0.125 0.125 -0.125') \
217 void PM_ClientMovement_Unstick(entity this)
219 #define X(unstick_offset) \
221 vector neworigin = unstick_offset + this.origin; \
222 tracebox(neworigin, PL_CROUCH_MIN, PL_CROUCH_MAX, neworigin, MOVE_NORMAL, this); \
223 if (!trace_startsolid) \
225 setorigin(this, neworigin); \
233 void PM_ClientMovement_UpdateStatus(entity this, bool ground)
235 // make sure player is not stuck
236 PM_ClientMovement_Unstick(this);
239 if (PHYS_INPUT_BUTTON_CROUCH(this))
241 // wants to crouch, this always works
242 if (!IS_DUCKED(this)) SET_DUCKED(this);
246 // wants to stand, if currently crouching we need to check for a low ceiling first
249 tracebox(this.origin, PL_MIN, PL_MAX, this.origin, MOVE_NORMAL, this);
250 if (!trace_startsolid) UNSET_DUCKED(this);
255 vector origin1 = this.origin + '0 0 1';
256 vector origin2 = this.origin - '0 0 1';
260 tracebox(origin1, this.mins, this.maxs, origin2, MOVE_NORMAL, this);
261 if (trace_fraction < 1.0 && trace_plane_normal.z > 0.7)
265 // this code actually "predicts" an impact; so let's clip velocity first
266 this.velocity -= this.velocity * trace_plane_normal * trace_plane_normal;
269 UNSET_ONGROUND(this);
272 // set watertype/waterlevel
273 origin1 = this.origin;
274 origin1.z += this.mins_z + 1;
275 this.waterlevel = WATERLEVEL_NONE;
277 int thepoint = pointcontents(origin1);
279 this.watertype = (thepoint == CONTENT_WATER || thepoint == CONTENT_LAVA || thepoint == CONTENT_SLIME);
283 this.waterlevel = WATERLEVEL_WETFEET;
284 origin1.z = this.origin.z + (this.mins.z + this.maxs.z) * 0.5;
285 thepoint = pointcontents(origin1);
286 if (thepoint == CONTENT_WATER || thepoint == CONTENT_LAVA || thepoint == CONTENT_SLIME)
288 this.waterlevel = WATERLEVEL_SWIMMING;
289 origin1.z = this.origin.z + 22;
290 thepoint = pointcontents(origin1);
291 if (thepoint == CONTENT_WATER || thepoint == CONTENT_LAVA || thepoint == CONTENT_SLIME)
292 this.waterlevel = WATERLEVEL_SUBMERGED;
296 if (IS_ONGROUND(this) || this.velocity.z <= 0 || pmove_waterjumptime <= 0)
297 pmove_waterjumptime = 0;
300 void PM_ClientMovement_Move()
307 vector currentorigin2;
309 vector primalvelocity;
311 vector trace1_endpos = '0 0 0';
312 vector trace2_endpos = '0 0 0';
313 vector trace3_endpos = '0 0 0';
314 float trace1_fraction = 0;
315 float trace2_fraction = 0;
316 float trace3_fraction = 0;
317 vector trace1_plane_normal = '0 0 0';
318 vector trace2_plane_normal = '0 0 0';
319 vector trace3_plane_normal = '0 0 0';
322 PM_ClientMovement_UpdateStatus(this, false);
323 primalvelocity = self.velocity;
324 for(bump = 0, t = PHYS_INPUT_TIMELENGTH; bump < 8 && (self.velocity * self.velocity) > 0; bump++)
326 neworigin = self.origin + t * self.velocity;
327 tracebox(self.origin, self.mins, self.maxs, neworigin, MOVE_NORMAL, self);
328 trace1_endpos = trace_endpos;
329 trace1_fraction = trace_fraction;
330 trace1_plane_normal = trace_plane_normal;
331 if(trace1_fraction < 1 && trace1_plane_normal_z == 0)
333 // may be a step or wall, try stepping up
334 // first move forward at a higher level
335 currentorigin2 = self.origin;
336 currentorigin2_z += PHYS_STEPHEIGHT;
337 neworigin2 = neworigin;
338 neworigin2_z += PHYS_STEPHEIGHT;
339 tracebox(currentorigin2, self.mins, self.maxs, neworigin2, MOVE_NORMAL, self);
340 trace2_endpos = trace_endpos;
341 trace2_fraction = trace_fraction;
342 trace2_plane_normal = trace_plane_normal;
343 if(!trace_startsolid)
345 // then move down from there
346 currentorigin2 = trace2_endpos;
347 neworigin2 = trace2_endpos;
348 neworigin2_z = self.origin_z;
349 tracebox(currentorigin2, self.mins, self.maxs, neworigin2, MOVE_NORMAL, self);
350 trace3_endpos = trace_endpos;
351 trace3_fraction = trace_fraction;
352 trace3_plane_normal = trace_plane_normal;
353 // accept the new trace if it made some progress
354 if(fabs(trace3_endpos_x - trace1_endpos_x) >= 0.03125 || fabs(trace3_endpos_y - trace1_endpos_y) >= 0.03125)
356 trace1_endpos = trace2_endpos;
357 trace1_fraction = trace2_fraction;
358 trace1_plane_normal = trace2_plane_normal;
359 trace1_endpos = trace3_endpos;
364 // check if it moved at all
365 if(trace1_fraction >= 0.001)
366 setorigin(self, trace1_endpos);
368 // check if it moved all the way
369 if(trace1_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(trace1_plane_normal_z > 0.7)
379 t -= t * trace1_fraction;
381 f = (self.velocity * trace1_plane_normal);
382 self.velocity = self.velocity + -f * trace1_plane_normal;
384 if(pmove_waterjumptime > 0)
385 self.velocity = primalvelocity;
389 void CPM_PM_Aircontrol(vector wishdir, float wishspeed)
391 float k = 32 * (2 * IsMoveInDirection(self.movement, 0) - 1);
395 k *= bound(0, wishspeed / PHYS_MAXAIRSPEED(self), 1);
397 float zspeed = self.velocity_z;
399 float xyspeed = vlen(self.velocity);
400 self.velocity = normalize(self.velocity);
402 float dot = self.velocity * wishdir;
404 if (dot > 0) // we can't change direction while slowing down
406 k *= pow(dot, PHYS_AIRCONTROL_POWER) * PHYS_INPUT_TIMELENGTH;
407 xyspeed = max(0, xyspeed - PHYS_AIRCONTROL_PENALTY * sqrt(max(0, 1 - dot*dot)) * k/32);
408 k *= PHYS_AIRCONTROL;
409 self.velocity = normalize(self.velocity * xyspeed + wishdir * k);
412 self.velocity = self.velocity * xyspeed;
413 self.velocity_z = zspeed;
416 float AdjustAirAccelQW(float accelqw, float factor)
418 return copysign(bound(0.000001, 1 - (1 - fabs(accelqw)) * factor, 1), accelqw);
421 // example config for alternate speed clamping:
422 // sv_airaccel_qw 0.8
423 // sv_airaccel_sideways_friction 0
424 // prvm_globalset server speedclamp_mode 1
426 void PM_Accelerate(vector wishdir, float wishspeed, float wishspeed0, float accel, float accelqw, float stretchfactor, float sidefric, float speedlimit)
428 float speedclamp = stretchfactor > 0 ? stretchfactor
429 : accelqw < 0 ? 1 // full clamping, no stretch
432 accelqw = fabs(accelqw);
434 if (GAMEPLAYFIX_Q2AIRACCELERATE)
435 wishspeed0 = wishspeed; // don't need to emulate this Q1 bug
437 float vel_straight = self.velocity * wishdir;
438 float vel_z = self.velocity_z;
439 vector vel_xy = vec2(self.velocity);
440 vector vel_perpend = vel_xy - vel_straight * wishdir;
442 float step = accel * PHYS_INPUT_TIMELENGTH * wishspeed0;
444 float vel_xy_current = vlen(vel_xy);
446 accelqw = AdjustAirAccelQW(accelqw, (speedlimit - bound(wishspeed, vel_xy_current, speedlimit)) / max(1, speedlimit - wishspeed));
447 float vel_xy_forward = vel_xy_current + bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw);
448 float vel_xy_backward = vel_xy_current - bound(0, wishspeed + vel_xy_current, step) * accelqw - step * (1 - accelqw);
449 vel_xy_backward = max(0, vel_xy_backward); // not that it REALLY occurs that this would cause wrong behaviour afterwards
450 vel_straight = vel_straight + bound(0, wishspeed - vel_straight, step) * accelqw + step * (1 - accelqw);
452 if (sidefric < 0 && (vel_perpend*vel_perpend))
453 // negative: only apply so much sideways friction to stay below the speed you could get by "braking"
455 float f = max(0, 1 + PHYS_INPUT_TIMELENGTH * wishspeed * sidefric);
456 float fmin = (vel_xy_backward * vel_xy_backward - vel_straight * vel_straight) / (vel_perpend * vel_perpend);
458 // vel_xy_backward*vel_xy_backward - vel_straight*vel_straight > vel_perpend*vel_perpend
459 // vel_xy_backward*vel_xy_backward > vel_straight*vel_straight + vel_perpend*vel_perpend
460 // vel_xy_backward*vel_xy_backward > vel_xy * vel_xy
461 // obviously, this cannot be
467 vel_perpend *= max(fmin, f);
471 vel_perpend *= max(0, 1 - PHYS_INPUT_TIMELENGTH * wishspeed * sidefric);
473 vel_xy = vel_straight * wishdir + vel_perpend;
477 float vel_xy_preclamp;
478 vel_xy_preclamp = vlen(vel_xy);
479 if (vel_xy_preclamp > 0) // prevent division by zero
481 vel_xy_current += (vel_xy_forward - vel_xy_current) * speedclamp;
482 if (vel_xy_current < vel_xy_preclamp)
483 vel_xy *= (vel_xy_current / vel_xy_preclamp);
487 self.velocity = vel_xy + vel_z * '0 0 1';
490 void PM_AirAccelerate(vector wishdir, float wishspeed)
495 vector curvel = self.velocity;
497 float curspeed = vlen(curvel);
499 if (wishspeed > curspeed * 1.01)
500 wishspeed = min(wishspeed, curspeed + PHYS_WARSOWBUNNY_AIRFORWARDACCEL * PHYS_MAXSPEED(self) * PHYS_INPUT_TIMELENGTH);
503 float f = max(0, (PHYS_WARSOWBUNNY_TOPSPEED - curspeed) / (PHYS_WARSOWBUNNY_TOPSPEED - PHYS_MAXSPEED(self)));
504 wishspeed = max(curspeed, PHYS_MAXSPEED(self)) + PHYS_WARSOWBUNNY_ACCEL * f * PHYS_MAXSPEED(self) * PHYS_INPUT_TIMELENGTH;
506 vector wishvel = wishdir * wishspeed;
507 vector acceldir = wishvel - curvel;
508 float addspeed = vlen(acceldir);
509 acceldir = normalize(acceldir);
511 float accelspeed = min(addspeed, PHYS_WARSOWBUNNY_TURNACCEL * PHYS_MAXSPEED(self) * PHYS_INPUT_TIMELENGTH);
513 if (PHYS_WARSOWBUNNY_BACKTOSIDERATIO < 1)
515 vector curdir = normalize(curvel);
516 float dot = acceldir * curdir;
518 acceldir -= (1 - PHYS_WARSOWBUNNY_BACKTOSIDERATIO) * dot * curdir;
521 self.velocity += accelspeed * acceldir;
529 When you press the jump key
530 returns true if handled
535 if (PHYS_FROZEN(self))
536 return true; // no jumping in freezetag when frozen
539 if (self.player_blocked)
540 return true; // no jumping while blocked
543 bool doublejump = false;
544 float mjumpheight = PHYS_JUMPVELOCITY;
546 player_multijump = doublejump;
547 player_jumpheight = mjumpheight;
550 if (MUTATOR_CALLHOOK(PlayerJump, doublejump, mjumpheight))
553 doublejump = player_multijump;
554 mjumpheight = player_jumpheight;
558 tracebox(self.origin + '0 0 0.01', self.mins, self.maxs, self.origin - '0 0 0.01', MOVE_NORMAL, self);
559 if (trace_fraction < 1 && trace_plane_normal_z > 0.7)
563 // we MUST clip velocity here!
565 f = self.velocity * trace_plane_normal;
567 self.velocity -= f * trace_plane_normal;
571 if (self.waterlevel >= WATERLEVEL_SWIMMING)
573 self.velocity_z = PHYS_MAXSPEED(self) * 0.7;
578 if (!IS_ONGROUND(self))
579 return IS_JUMP_HELD(self);
581 bool track_jump = PHYS_CL_TRACK_CANJUMP(self);
582 if(PHYS_TRACK_CANJUMP(self))
586 if (IS_JUMP_HELD(self))
589 // sv_jumpspeedcap_min/sv_jumpspeedcap_max act as baseline
590 // velocity bounds. Final velocity is bound between (jumpheight *
591 // min + jumpheight) and (jumpheight * max + jumpheight);
593 if(PHYS_JUMPSPEEDCAP_MIN != "")
595 float minjumpspeed = mjumpheight * stof(PHYS_JUMPSPEEDCAP_MIN);
597 if (self.velocity_z < minjumpspeed)
598 mjumpheight += minjumpspeed - self.velocity_z;
601 if(PHYS_JUMPSPEEDCAP_MAX != "")
603 // don't do jump speedcaps on ramps to preserve old xonotic ramjump style
604 tracebox(self.origin + '0 0 0.01', self.mins, self.maxs, self.origin - '0 0 0.01', MOVE_NORMAL, self);
606 if (!(trace_fraction < 1 && trace_plane_normal_z < 0.98 && PHYS_JUMPSPEEDCAP_DISABLE_ONRAMPS))
608 float maxjumpspeed = mjumpheight * stof(PHYS_JUMPSPEEDCAP_MAX);
610 if (self.velocity_z > maxjumpspeed)
611 mjumpheight -= self.velocity_z - maxjumpspeed;
615 if (!WAS_ONGROUND(self))
618 if(autocvar_speedmeter)
619 LOG_TRACE(strcat("landing velocity: ", vtos(self.velocity), " (abs: ", ftos(vlen(self.velocity)), ")\n"));
621 if(self.lastground < time - 0.3)
623 self.velocity_x *= (1 - PHYS_FRICTION_ONLAND);
624 self.velocity_y *= (1 - PHYS_FRICTION_ONLAND);
627 if(self.jumppadcount > 1)
628 LOG_TRACE(strcat(ftos(self.jumppadcount), "x jumppad combo\n"));
629 self.jumppadcount = 0;
633 self.velocity_z += mjumpheight;
635 UNSET_ONGROUND(self);
640 self.oldvelocity_z = self.velocity_z;
642 animdecide_setaction(self, ANIMACTION_JUMP, true);
644 if (autocvar_g_jump_grunt)
645 PlayerSound(playersound_jump, CH_PLAYER, VOICETYPE_PLAYERSOUND);
650 void CheckWaterJump()
652 // check for a jump-out-of-water
653 makevectors(self.v_angle);
654 vector start = self.origin;
657 normalize(v_forward);
658 vector end = start + v_forward*24;
659 traceline (start, end, true, self);
660 if (trace_fraction < 1)
662 start_z = start_z + self.maxs_z - 8;
663 end = start + v_forward*24;
664 self.movedir = trace_plane_normal * -50;
665 traceline(start, end, true, self);
666 if (trace_fraction == 1)
667 { // open at eye level
668 self.velocity_z = 225;
669 self.flags |= FL_WATERJUMP;
672 self.teleport_time = time + 2; // safety net
674 pmove_waterjumptime = time + 2;
682 #define JETPACK_JUMP(s) s.cvar_cl_jetpack_jump
684 float autocvar_cl_jetpack_jump;
685 #define JETPACK_JUMP(s) autocvar_cl_jetpack_jump
687 .float jetpack_stopped;
688 // Hack: shouldn't need to know about this
689 .float multijump_count;
690 void CheckPlayerJump()
693 float was_flying = ITEMS_STAT(self) & IT_USING_JETPACK;
695 if (JETPACK_JUMP(self) < 2)
696 ITEMS_STAT(self) &= ~IT_USING_JETPACK;
698 if(PHYS_INPUT_BUTTON_JUMP(self) || PHYS_INPUT_BUTTON_JETPACK(self))
700 float air_jump = !PlayerJump() || self.multijump_count > 0; // PlayerJump() has important side effects
701 float activate = JETPACK_JUMP(self) && air_jump && PHYS_INPUT_BUTTON_JUMP(self) || PHYS_INPUT_BUTTON_JETPACK(self);
702 float has_fuel = !PHYS_JETPACK_FUEL || PHYS_AMMO_FUEL(self) || ITEMS_STAT(self) & IT_UNLIMITED_WEAPON_AMMO;
704 if (!(ITEMS_STAT(self) & ITEM_Jetpack.m_itemid)) { }
705 else if (self.jetpack_stopped) { }
709 if (was_flying) // TODO: ran out of fuel message
710 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_JETPACK_NOFUEL);
712 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_JETPACK_NOFUEL);
714 self.jetpack_stopped = true;
715 ITEMS_STAT(self) &= ~IT_USING_JETPACK;
717 else if (activate && !PHYS_FROZEN(self))
718 ITEMS_STAT(self) |= IT_USING_JETPACK;
722 self.jetpack_stopped = false;
723 ITEMS_STAT(self) &= ~IT_USING_JETPACK;
725 if (!PHYS_INPUT_BUTTON_JUMP(self))
726 UNSET_JUMP_HELD(self);
728 if (self.waterlevel == WATERLEVEL_SWIMMING)
732 float racecar_angle(float forward, float down)
740 float ret = vectoyaw('0 1 0' * down + '1 0 0' * forward);
742 float angle_mult = forward / (800 + forward);
745 return ret * angle_mult + 360 * (1 - angle_mult);
747 return ret * angle_mult;
750 void RaceCarPhysics()
753 // using this move type for "big rigs"
754 // the engine does not push the entity!
758 vector angles_save = self.angles;
759 float accel = bound(-1, self.movement.x / PHYS_MAXSPEED(self), 1);
760 float steer = bound(-1, self.movement.y / PHYS_MAXSPEED(self), 1);
762 if (g_bugrigs_reverse_speeding)
766 // back accel is DIGITAL
767 // to prevent speedhack
777 makevectors(self.angles); // new forward direction!
779 if (IS_ONGROUND(self) || g_bugrigs_air_steering)
781 float myspeed = self.velocity * v_forward;
782 float upspeed = self.velocity * v_up;
784 // responsiveness factor for steering and acceleration
785 float f = 1 / (1 + pow(max(-myspeed, myspeed) / g_bugrigs_speed_ref, g_bugrigs_speed_pow));
786 //MAXIMA: f(v) := 1 / (1 + (v / g_bugrigs_speed_ref) ^ g_bugrigs_speed_pow);
789 if (myspeed < 0 && g_bugrigs_reverse_spinning)
790 steerfactor = -myspeed * g_bugrigs_steer;
792 steerfactor = -myspeed * f * g_bugrigs_steer;
795 if (myspeed < 0 && g_bugrigs_reverse_speeding)
796 accelfactor = g_bugrigs_accel;
798 accelfactor = f * g_bugrigs_accel;
799 //MAXIMA: accel(v) := f(v) * g_bugrigs_accel;
805 myspeed = max(0, myspeed - PHYS_INPUT_TIMELENGTH * (g_bugrigs_friction_floor - g_bugrigs_friction_brake * accel));
809 if (!g_bugrigs_reverse_speeding)
810 myspeed = min(0, myspeed + PHYS_INPUT_TIMELENGTH * g_bugrigs_friction_floor);
817 myspeed = max(0, myspeed - PHYS_INPUT_TIMELENGTH * g_bugrigs_friction_floor);
821 if (g_bugrigs_reverse_stopping)
824 myspeed = min(0, myspeed + PHYS_INPUT_TIMELENGTH * (g_bugrigs_friction_floor + g_bugrigs_friction_brake * accel));
827 // terminal velocity = velocity at which 50 == accelfactor, that is, 1549 units/sec
828 //MAXIMA: friction(v) := g_bugrigs_friction_floor;
830 self.angles_y += steer * PHYS_INPUT_TIMELENGTH * steerfactor; // apply steering
831 makevectors(self.angles); // new forward direction!
833 myspeed += accel * accelfactor * PHYS_INPUT_TIMELENGTH;
835 rigvel = myspeed * v_forward + '0 0 1' * upspeed;
839 float myspeed = vlen(self.velocity);
841 // responsiveness factor for steering and acceleration
842 float f = 1 / (1 + pow(max(0, myspeed / g_bugrigs_speed_ref), g_bugrigs_speed_pow));
843 float steerfactor = -myspeed * f;
844 self.angles_y += steer * PHYS_INPUT_TIMELENGTH * steerfactor; // apply steering
846 rigvel = self.velocity;
847 makevectors(self.angles); // new forward direction!
850 rigvel *= max(0, 1 - vlen(rigvel) * g_bugrigs_friction_air * PHYS_INPUT_TIMELENGTH);
851 //MAXIMA: airfriction(v) := v * v * g_bugrigs_friction_air;
852 //MAXIMA: total_acceleration(v) := accel(v) - friction(v) - airfriction(v);
853 //MAXIMA: solve(total_acceleration(v) = 0, v);
855 if (g_bugrigs_planar_movement)
857 vector rigvel_xy, neworigin, up;
860 rigvel_z -= PHYS_INPUT_TIMELENGTH * PHYS_GRAVITY; // 4x gravity plays better
861 rigvel_xy = vec2(rigvel);
863 if (g_bugrigs_planar_movement_car_jumping)
866 mt = MOVE_NOMONSTERS;
868 tracebox(self.origin, self.mins, self.maxs, self.origin + '0 0 1024', mt, self);
869 up = trace_endpos - self.origin;
871 // BUG RIGS: align the move to the surface instead of doing collision testing
873 tracebox(trace_endpos, self.mins, self.maxs, trace_endpos + rigvel_xy * PHYS_INPUT_TIMELENGTH, mt, self);
876 tracebox(trace_endpos, self.mins, self.maxs, trace_endpos - up + '0 0 1' * rigvel_z * PHYS_INPUT_TIMELENGTH, mt, self);
878 if (trace_fraction < 0.5)
881 neworigin = self.origin;
884 neworigin = trace_endpos;
886 if (trace_fraction < 1)
888 // now set angles_x so that the car points parallel to the surface
889 self.angles = vectoangles(
890 '1 0 0' * v_forward_x * trace_plane_normal_z
892 '0 1 0' * v_forward_y * trace_plane_normal_z
894 '0 0 1' * -(v_forward_x * trace_plane_normal_x + v_forward_y * trace_plane_normal_y)
900 // now set angles_x so that the car points forward, but is tilted in velocity direction
901 UNSET_ONGROUND(self);
904 self.velocity = (neworigin - self.origin) * (1.0 / PHYS_INPUT_TIMELENGTH);
905 self.movetype = MOVETYPE_NOCLIP;
909 rigvel_z -= PHYS_INPUT_TIMELENGTH * PHYS_GRAVITY; // 4x gravity plays better
910 self.velocity = rigvel;
911 self.movetype = MOVETYPE_FLY;
915 tracebox(self.origin, self.mins, self.maxs, self.origin - '0 0 4', MOVE_NORMAL, self);
916 if (trace_fraction != 1)
918 self.angles = vectoangles2(
919 '1 0 0' * v_forward_x * trace_plane_normal_z
921 '0 1 0' * v_forward_y * trace_plane_normal_z
923 '0 0 1' * -(v_forward_x * trace_plane_normal_x + v_forward_y * trace_plane_normal_y),
931 vel_local_x = v_forward * self.velocity;
932 vel_local_y = v_right * self.velocity;
933 vel_local_z = v_up * self.velocity;
935 self.angles_x = racecar_angle(vel_local_x, vel_local_z);
936 self.angles_z = racecar_angle(-vel_local_y, vel_local_z);
940 vector vf1, vu1, smoothangles;
941 makevectors(self.angles);
942 float f = bound(0, PHYS_INPUT_TIMELENGTH * g_bugrigs_angle_smoothing, 1);
947 makevectors(angles_save);
948 vf1 = vf1 + v_forward * (1 - f);
949 vu1 = vu1 + v_up * (1 - f);
950 smoothangles = vectoangles2(vf1, vu1);
951 self.angles_x = -smoothangles_x;
952 self.angles_z = smoothangles_z;
956 string specialcommand = "xwxwxsxsxaxdxaxdx1x ";
957 .float specialcommand_pos;
958 void SpecialCommand()
964 if (!CheatImpulse(99))
965 LOG_INFO("A hollow voice says \"Plugh\".\n");
970 float PM_check_specialcommand(float buttons)
976 else if (buttons == 1)
978 else if (buttons == 2)
980 else if (buttons == 128)
982 else if (buttons == 256)
984 else if (buttons == 512)
986 else if (buttons == 1024)
991 if (c == substring(specialcommand, self.specialcommand_pos, 1))
993 self.specialcommand_pos += 1;
994 if (self.specialcommand_pos >= strlen(specialcommand))
996 self.specialcommand_pos = 0;
1001 else if (self.specialcommand_pos && (c != substring(specialcommand, self.specialcommand_pos - 1, 1)))
1002 self.specialcommand_pos = 0;
1007 void PM_check_nickspam()
1010 if (time >= self.nickspamtime)
1012 if (self.nickspamcount >= autocvar_g_nick_flood_penalty_yellow)
1014 // slight annoyance for nick change scripts
1015 self.movement = -1 * self.movement;
1016 self.BUTTON_ATCK = self.BUTTON_JUMP = self.BUTTON_ATCK2 = self.BUTTON_ZOOM = self.BUTTON_CROUCH = self.BUTTON_HOOK = self.BUTTON_USE = 0;
1018 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!
1020 self.v_angle_x = random() * 360;
1021 self.v_angle_y = random() * 360;
1022 // at least I'm not forcing retardedview by also assigning to angles_z
1023 self.fixangle = true;
1029 void PM_check_punch()
1032 if (self.punchangle != '0 0 0')
1034 float f = vlen(self.punchangle) - 10 * PHYS_INPUT_TIMELENGTH;
1036 self.punchangle = normalize(self.punchangle) * f;
1038 self.punchangle = '0 0 0';
1041 if (self.punchvector != '0 0 0')
1043 float f = vlen(self.punchvector) - 30 * PHYS_INPUT_TIMELENGTH;
1045 self.punchvector = normalize(self.punchvector) * f;
1047 self.punchvector = '0 0 0';
1052 void PM_check_spider()
1055 if (time >= self.spider_slowness)
1057 PHYS_MAXSPEED(self) *= 0.5; // half speed while slow from spider
1058 PHYS_MAXAIRSPEED(self) *= 0.5;
1059 PHYS_AIRSPEEDLIMIT_NONQW(self) *= 0.5;
1060 PHYS_AIRSTRAFEACCELERATE(self) *= 0.5;
1064 // predict frozen movement, as frozen players CAN move in some cases
1065 void PM_check_frozen()
1067 if (!PHYS_FROZEN(self))
1069 if (PHYS_DODGING_FROZEN
1071 && IS_REAL_CLIENT(self)
1075 self.movement_x = bound(-5, self.movement.x, 5);
1076 self.movement_y = bound(-5, self.movement.y, 5);
1077 self.movement_z = bound(-5, self.movement.z, 5);
1080 self.movement = '0 0 0';
1082 vector midpoint = ((self.absmin + self.absmax) * 0.5);
1083 if (pointcontents(midpoint) == CONTENT_WATER)
1085 self.velocity = self.velocity * 0.5;
1087 if (pointcontents(midpoint + '0 0 16') == CONTENT_WATER)
1088 self.velocity_z = 200;
1092 void PM_check_hitground()
1095 if (!IS_PLAYER(this)) return; // no fall sounds for observers thank you very much
1096 if (!IS_ONGROUND(this)) return;
1097 if (!this.wasFlying) return;
1098 this.wasFlying = false;
1099 if (this.waterlevel >= WATERLEVEL_SWIMMING) return;
1100 if (time < this.ladder_time) return;
1101 if (this.hook) return;
1102 this.nextstep = time + 0.3 + random() * 0.1;
1103 trace_dphitq3surfaceflags = 0;
1104 tracebox(this.origin, this.mins, this.maxs, this.origin - '0 0 1', MOVE_NOMONSTERS, this);
1105 if ((trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOSTEPS)) return;
1106 entity fall = (trace_dphitq3surfaceflags & Q3SURFACEFLAG_METALSTEPS) ? GS_FALL_METAL : GS_FALL;
1107 GlobalSound(fall, CH_PLAYER, VOICETYPE_PLAYERSOUND);
1111 void PM_check_blocked()
1114 if (!self.player_blocked)
1116 self.movement = '0 0 0';
1117 self.disableclientprediction = 1;
1121 void PM_check_vortex()
1125 float xyspeed = vlen(vec2(self.velocity));
1126 if (self.weapon == WEP_VORTEX.m_id && WEP_CVAR(vortex, charge) && WEP_CVAR(vortex, charge_velocity_rate) && xyspeed > WEP_CVAR(vortex, charge_minspeed))
1128 // add a maximum of charge_velocity_rate when going fast (f = 1), gradually increasing from minspeed (f = 0) to maxspeed
1129 xyspeed = min(xyspeed, WEP_CVAR(vortex, charge_maxspeed));
1130 float f = (xyspeed - WEP_CVAR(vortex, charge_minspeed)) / (WEP_CVAR(vortex, charge_maxspeed) - WEP_CVAR(vortex, charge_minspeed));
1131 // add the extra charge
1132 self.vortex_charge = min(1, self.vortex_charge + WEP_CVAR(vortex, charge_velocity_rate) * f * PHYS_INPUT_TIMELENGTH);
1137 void PM_fly(float maxspd_mod)
1139 // noclipping or flying
1140 UNSET_ONGROUND(self);
1142 self.velocity = self.velocity * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION);
1143 makevectors(self.v_angle);
1144 //wishvel = v_forward * self.movement.x + v_right * self.movement.y + v_up * self.movement.z;
1145 vector wishvel = v_forward * self.movement.x
1146 + v_right * self.movement.y
1147 + '0 0 1' * self.movement.z;
1149 vector wishdir = normalize(wishvel);
1150 float wishspeed = min(vlen(wishvel), PHYS_MAXSPEED(self) * maxspd_mod);
1152 if (time >= self.teleport_time)
1154 PM_Accelerate(wishdir, wishspeed, wishspeed, PHYS_ACCELERATE * maxspd_mod, 1, 0, 0, 0);
1155 PM_ClientMovement_Move();
1158 void PM_swim(float maxspd_mod)
1161 UNSET_ONGROUND(self);
1163 float jump = PHYS_INPUT_BUTTON_JUMP(self);
1164 // water jump only in certain situations
1165 // this mimics quakeworld code
1166 if (jump && self.waterlevel == WATERLEVEL_SWIMMING && self.velocity_z >= -180)
1168 vector yawangles = '0 1 0' * self.v_angle.y;
1169 makevectors(yawangles);
1170 vector forward = v_forward;
1171 vector spot = self.origin + 24 * forward;
1173 traceline(spot, spot, MOVE_NOMONSTERS, self);
1174 if (trace_startsolid)
1177 traceline(spot, spot, MOVE_NOMONSTERS, self);
1178 if (!trace_startsolid)
1180 self.velocity = forward * 50;
1181 self.velocity_z = 310;
1182 pmove_waterjumptime = 2;
1183 UNSET_ONGROUND(self);
1184 SET_JUMP_HELD(self);
1188 makevectors(self.v_angle);
1189 //wishvel = v_forward * self.movement.x + v_right * self.movement.y + v_up * self.movement.z;
1190 vector wishvel = v_forward * self.movement.x
1191 + v_right * self.movement.y
1192 + '0 0 1' * self.movement.z;
1193 if (wishvel == '0 0 0')
1194 wishvel = '0 0 -60'; // drift towards bottom
1196 vector wishdir = normalize(wishvel);
1197 float wishspeed = min(vlen(wishvel), PHYS_MAXSPEED(self) * maxspd_mod) * 0.7;
1199 if (IS_DUCKED(self))
1202 // if (pmove_waterjumptime <= 0) // TODO: use
1205 float f = 1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION;
1206 f = min(max(0, f), 1);
1209 f = wishspeed - self.velocity * wishdir;
1212 float accelspeed = min(PHYS_ACCELERATE * PHYS_INPUT_TIMELENGTH * wishspeed, f);
1213 self.velocity += accelspeed * wishdir;
1216 // holding jump button swims upward slowly
1220 if (self.watertype & CONTENT_LAVA)
1221 self.velocity_z = 50;
1222 else if (self.watertype & CONTENT_SLIME)
1223 self.velocity_z = 80;
1226 if (IS_NEXUIZ_DERIVED(gamemode))
1228 self.velocity_z = 200;
1231 self.velocity_z = 100;
1236 // water acceleration
1237 PM_Accelerate(wishdir, wishspeed, wishspeed, PHYS_ACCELERATE * maxspd_mod, 1, 0, 0, 0);
1238 PM_ClientMovement_Move();
1241 void PM_ladder(float maxspd_mod)
1243 // on a spawnfunc_func_ladder or swimming in spawnfunc_func_water
1244 UNSET_ONGROUND(self);
1247 g = PHYS_GRAVITY * PHYS_INPUT_TIMELENGTH;
1248 if (PHYS_ENTGRAVITY(self))
1249 g *= PHYS_ENTGRAVITY(self);
1250 if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1253 self.velocity_z += g;
1256 self.velocity = self.velocity * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION);
1257 makevectors(self.v_angle);
1258 //wishvel = v_forward * self.movement.x + v_right * self.movement.y + v_up * self.movement.z;
1259 vector wishvel = v_forward * self.movement_x
1260 + v_right * self.movement_y
1261 + '0 0 1' * self.movement_z;
1262 self.velocity_z += g;
1263 if (self.ladder_entity.classname == "func_water")
1265 float f = vlen(wishvel);
1266 if (f > self.ladder_entity.speed)
1267 wishvel *= (self.ladder_entity.speed / f);
1269 self.watertype = self.ladder_entity.skin;
1270 f = self.ladder_entity.origin_z + self.ladder_entity.maxs_z;
1271 if ((self.origin_z + self.view_ofs_z) < f)
1272 self.waterlevel = WATERLEVEL_SUBMERGED;
1273 else if ((self.origin_z + (self.mins_z + self.maxs_z) * 0.5) < f)
1274 self.waterlevel = WATERLEVEL_SWIMMING;
1275 else if ((self.origin_z + self.mins_z + 1) < f)
1276 self.waterlevel = WATERLEVEL_WETFEET;
1279 self.waterlevel = WATERLEVEL_NONE;
1280 self.watertype = CONTENT_EMPTY;
1284 vector wishdir = normalize(wishvel);
1285 float wishspeed = min(vlen(wishvel), PHYS_MAXSPEED(self) * maxspd_mod);
1287 if (time >= self.teleport_time)
1289 // water acceleration
1290 PM_Accelerate(wishdir, wishspeed, wishspeed, PHYS_ACCELERATE*maxspd_mod, 1, 0, 0, 0);
1291 PM_ClientMovement_Move();
1294 void PM_jetpack(float maxspd_mod)
1296 //makevectors(self.v_angle.y * '0 1 0');
1297 makevectors(self.v_angle);
1298 vector wishvel = v_forward * self.movement_x
1299 + v_right * self.movement_y;
1300 // add remaining speed as Z component
1301 float maxairspd = PHYS_MAXAIRSPEED(self) * max(1, maxspd_mod);
1302 // fix speedhacks :P
1303 wishvel = normalize(wishvel) * min(1, vlen(wishvel) / maxairspd);
1304 // add the unused velocity as up component
1307 // if (self.BUTTON_JUMP)
1308 wishvel_z = sqrt(max(0, 1 - wishvel * wishvel));
1310 // it is now normalized, so...
1311 float a_side = PHYS_JETPACK_ACCEL_SIDE;
1312 float a_up = PHYS_JETPACK_ACCEL_UP;
1313 float a_add = PHYS_JETPACK_ANTIGRAVITY * PHYS_GRAVITY;
1315 wishvel_x *= a_side;
1316 wishvel_y *= a_side;
1321 //////////////////////////////////////////////////////////////////////////////////////
1322 // finding the maximum over all vectors of above form
1323 // with wishvel having an absolute value of 1
1324 //////////////////////////////////////////////////////////////////////////////////////
1325 // we're finding the maximum over
1326 // f(a_side, a_up, a_add, z) := a_side * (1 - z^2) + (a_add + a_up * z)^2;
1327 // for z in the range from -1 to 1
1328 //////////////////////////////////////////////////////////////////////////////////////
1329 // maximum is EITHER attained at the single extreme point:
1330 float a_diff = a_side * a_side - a_up * a_up;
1334 f = a_add * a_up / a_diff; // this is the zero of diff(f(a_side, a_up, a_add, z), z)
1335 if (f > -1 && f < 1) // can it be attained?
1337 best = (a_diff + a_add * a_add) * (a_diff + a_up * a_up) / a_diff;
1338 //print("middle\n");
1341 // OR attained at z = 1:
1342 f = (a_up + a_add) * (a_up + a_add);
1348 // OR attained at z = -1:
1349 f = (a_up - a_add) * (a_up - a_add);
1353 //print("bottom\n");
1356 //////////////////////////////////////////////////////////////////////////////////////
1358 //print("best possible acceleration: ", ftos(best), "\n");
1361 fxy = bound(0, 1 - (self.velocity * normalize(wishvel_x * '1 0 0' + wishvel_y * '0 1 0')) / PHYS_JETPACK_MAXSPEED_SIDE, 1);
1362 if (wishvel_z - PHYS_GRAVITY > 0)
1363 fz = bound(0, 1 - self.velocity_z / PHYS_JETPACK_MAXSPEED_UP, 1);
1365 fz = bound(0, 1 + self.velocity_z / PHYS_JETPACK_MAXSPEED_UP, 1);
1368 fvel = vlen(wishvel);
1371 wishvel_z = (wishvel_z - PHYS_GRAVITY) * fz + PHYS_GRAVITY;
1373 fvel = min(1, vlen(wishvel) / best);
1374 if (PHYS_JETPACK_FUEL && !(ITEMS_STAT(self) & IT_UNLIMITED_WEAPON_AMMO))
1375 f = min(1, PHYS_AMMO_FUEL(self) / (PHYS_JETPACK_FUEL * PHYS_INPUT_TIMELENGTH * fvel));
1379 //print("this acceleration: ", ftos(vlen(wishvel) * f), "\n");
1381 if (f > 0 && wishvel != '0 0 0')
1383 self.velocity = self.velocity + wishvel * f * PHYS_INPUT_TIMELENGTH;
1384 UNSET_ONGROUND(self);
1387 if (!(ITEMS_STAT(self) & IT_UNLIMITED_WEAPON_AMMO))
1388 self.ammo_fuel -= PHYS_JETPACK_FUEL * PHYS_INPUT_TIMELENGTH * fvel * f;
1390 ITEMS_STAT(self) |= IT_USING_JETPACK;
1392 // jetpack also inhibits health regeneration, but only for 1 second
1393 self.pauseregen_finished = max(self.pauseregen_finished, time + autocvar_g_balance_pause_fuel_regen);
1398 float g = PHYS_GRAVITY * PHYS_ENTGRAVITY(self) * PHYS_INPUT_TIMELENGTH;
1399 if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1400 self.velocity_z -= g * 0.5;
1402 self.velocity_z -= g;
1403 PM_ClientMovement_Move();
1404 if (!IS_ONGROUND(self) || !(GAMEPLAYFIX_NOGRAVITYONGROUND))
1405 if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1406 self.velocity_z -= g * 0.5;
1410 void PM_walk(entity this, float maxspd_mod)
1412 if (!WAS_ONGROUND(this))
1415 if (autocvar_speedmeter)
1416 LOG_TRACE(strcat("landing velocity: ", vtos(this.velocity), " (abs: ", ftos(vlen(this.velocity)), ")\n"));
1418 if (this.lastground < time - 0.3)
1419 this.velocity *= (1 - PHYS_FRICTION_ONLAND);
1421 if (this.jumppadcount > 1)
1422 LOG_TRACE(strcat(ftos(this.jumppadcount), "x jumppad combo\n"));
1423 this.jumppadcount = 0;
1428 makevectors(this.v_angle.y * '0 1 0');
1429 const vector wishvel = v_forward * this.movement.x
1430 + v_right * this.movement.y;
1432 const vector wishdir = normalize(wishvel);
1433 float wishspeed = vlen(wishvel);
1434 wishspeed = min(wishspeed, PHYS_MAXSPEED(this) * maxspd_mod);
1435 if (IS_DUCKED(this)) wishspeed *= 0.5;
1437 // apply edge friction
1438 const float f2 = vlen2(vec2(this.velocity));
1441 trace_dphitq3surfaceflags = 0;
1442 tracebox(this.origin, this.mins, this.maxs, this.origin - '0 0 1', MOVE_NOMONSTERS, this);
1443 // TODO: apply edge friction
1444 // apply ground friction
1445 const int realfriction = (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK)
1446 ? PHYS_FRICTION_SLICK
1450 f = 1 - PHYS_INPUT_TIMELENGTH * realfriction * ((f < PHYS_STOPSPEED) ? (PHYS_STOPSPEED / f) : 1);
1454 Mathematical analysis time!
1456 Our goal is to invert this mess.
1458 For the two cases we get:
1459 v = v0 * (1 - PHYS_INPUT_TIMELENGTH * (PHYS_STOPSPEED / v0) * PHYS_FRICTION)
1460 = v0 - PHYS_INPUT_TIMELENGTH * PHYS_STOPSPEED * PHYS_FRICTION
1461 v0 = v + PHYS_INPUT_TIMELENGTH * PHYS_STOPSPEED * PHYS_FRICTION
1463 v = v0 * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION)
1464 v0 = v / (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION)
1466 These cases would be chosen ONLY if:
1468 v + PHYS_INPUT_TIMELENGTH * PHYS_STOPSPEED * PHYS_FRICTION < PHYS_STOPSPEED
1469 v < PHYS_STOPSPEED * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION)
1471 v0 >= PHYS_STOPSPEED
1472 v / (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION) >= PHYS_STOPSPEED
1473 v >= PHYS_STOPSPEED * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION)
1476 const float addspeed = wishspeed - this.velocity * wishdir;
1479 const float accelspeed = min(PHYS_ACCELERATE * PHYS_INPUT_TIMELENGTH * wishspeed, addspeed);
1480 this.velocity += accelspeed * wishdir;
1482 const float g = PHYS_GRAVITY * PHYS_ENTGRAVITY(this) * PHYS_INPUT_TIMELENGTH;
1483 if (!(GAMEPLAYFIX_NOGRAVITYONGROUND))
1484 this.velocity_z -= g * (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE ? 0.5 : 1);
1485 if (vdist(this.velocity, >, 0))
1486 PM_ClientMovement_Move();
1487 if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1488 if (!IS_ONGROUND(this) || !GAMEPLAYFIX_NOGRAVITYONGROUND)
1489 this.velocity_z -= g * 0.5;
1492 void PM_air(float buttons_prev, float maxspd_mod)
1494 makevectors(self.v_angle.y * '0 1 0');
1495 vector wishvel = v_forward * self.movement.x
1496 + v_right * self.movement.y;
1498 vector wishdir = normalize(wishvel);
1499 float wishspeed = vlen(wishvel);
1502 if (time >= self.teleport_time)
1504 if (pmove_waterjumptime <= 0)
1507 float maxairspd = PHYS_MAXAIRSPEED(self) * min(maxspd_mod, 1);
1509 // apply air speed limit
1510 float airaccelqw = PHYS_AIRACCEL_QW(self);
1511 float wishspeed0 = wishspeed;
1512 wishspeed = min(wishspeed, maxairspd);
1513 if (IS_DUCKED(self))
1515 float airaccel = PHYS_AIRACCELERATE * min(maxspd_mod, 1);
1517 float accelerating = (self.velocity * wishdir > 0);
1518 float wishspeed2 = wishspeed;
1521 if (PHYS_AIRSTOPACCELERATE)
1523 vector curdir = normalize(vec2(self.velocity));
1524 airaccel += (PHYS_AIRSTOPACCELERATE*maxspd_mod - airaccel) * max(0, -(curdir * wishdir));
1526 // note that for straight forward jumping:
1527 // step = accel * PHYS_INPUT_TIMELENGTH * wishspeed0;
1528 // accel = bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw);
1530 // dv/dt = accel * maxspeed (when slow)
1531 // dv/dt = accel * maxspeed * (1 - accelqw) (when fast)
1532 // log dv/dt = logaccel + logmaxspeed (when slow)
1533 // log dv/dt = logaccel + logmaxspeed + log(1 - accelqw) (when fast)
1534 float strafity = IsMoveInDirection(self.movement, -90) + IsMoveInDirection(self.movement, +90); // if one is nonzero, other is always zero
1535 if (PHYS_MAXAIRSTRAFESPEED)
1536 wishspeed = min(wishspeed, GeomLerp(PHYS_MAXAIRSPEED(self)*maxspd_mod, strafity, PHYS_MAXAIRSTRAFESPEED*maxspd_mod));
1537 if (PHYS_AIRSTRAFEACCELERATE(self))
1538 airaccel = GeomLerp(airaccel, strafity, PHYS_AIRSTRAFEACCELERATE(self)*maxspd_mod);
1539 if (PHYS_AIRSTRAFEACCEL_QW(self))
1541 (((strafity > 0.5 ? PHYS_AIRSTRAFEACCEL_QW(self) : PHYS_AIRACCEL_QW(self)) >= 0) ? +1 : -1)
1543 (1 - GeomLerp(1 - fabs(PHYS_AIRACCEL_QW(self)), strafity, 1 - fabs(PHYS_AIRSTRAFEACCEL_QW(self))));
1546 if (PHYS_WARSOWBUNNY_TURNACCEL && accelerating && self.movement.y == 0 && self.movement.x != 0)
1547 PM_AirAccelerate(wishdir, wishspeed2);
1549 PM_Accelerate(wishdir, wishspeed, wishspeed0, airaccel, airaccelqw, PHYS_AIRACCEL_QW_STRETCHFACTOR(self), PHYS_AIRACCEL_SIDEWAYS_FRICTION / maxairspd, PHYS_AIRSPEEDLIMIT_NONQW(self));
1551 if (PHYS_AIRCONTROL)
1552 CPM_PM_Aircontrol(wishdir, wishspeed2);
1554 float g = PHYS_GRAVITY * PHYS_ENTGRAVITY(self) * PHYS_INPUT_TIMELENGTH;
1555 if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1556 self.velocity_z -= g * 0.5;
1558 self.velocity_z -= g;
1559 PM_ClientMovement_Move();
1560 if (!IS_ONGROUND(self) || !(GAMEPLAYFIX_NOGRAVITYONGROUND))
1561 if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1562 self.velocity_z -= g * 0.5;
1565 // used for calculating airshots
1566 bool IsFlying(entity a)
1570 if(a.waterlevel >= WATERLEVEL_SWIMMING)
1572 traceline(a.origin, a.origin - '0 0 48', MOVE_NORMAL, a);
1573 if(trace_fraction < 1)
1578 void PM_Main(entity this)
1580 int buttons = PHYS_INPUT_BUTTON_MASK(this);
1582 this.items = getstati(STAT_ITEMS, 0, 24);
1584 this.movement = PHYS_INPUT_MOVEVALUES(this);
1586 vector oldv_angle = this.v_angle;
1587 vector oldangles = this.angles; // we need to save these, as they're abused by other code
1588 this.v_angle = PHYS_INPUT_ANGLES(this);
1589 this.angles = PHYS_WORLD_ANGLES(this);
1591 this.team = myteam + 1; // is this correct?
1592 if (!(PHYS_INPUT_BUTTON_JUMP(this))) // !jump
1593 UNSET_JUMP_HELD(this); // canjump = true
1594 pmove_waterjumptime -= PHYS_INPUT_TIMELENGTH;
1596 PM_ClientMovement_UpdateStatus(this, true);
1601 WarpZone_PlayerPhysics_FixVAngle();
1603 float maxspeed_mod = 1;
1604 maxspeed_mod *= PHYS_HIGHSPEED;
1607 Physics_UpdateStats(maxspeed_mod);
1609 if (this.PlayerPhysplug)
1610 if (this.PlayerPhysplug())
1615 anticheat_physics();
1618 if (PM_check_specialcommand(buttons))
1623 if (buttons != this.buttons_old || this.movement != this.movement_old || this.v_angle != this.v_angle_old)
1624 this.parm_idlesince = time;
1627 int buttons_prev = this.buttons_old;
1628 this.buttons_old = buttons;
1629 this.movement_old = this.movement;
1630 this.v_angle_old = this.v_angle;
1632 PM_check_nickspam();
1636 if (IS_BOT_CLIENT(this))
1638 if (playerdemo_read())
1645 if (IS_PLAYER(this))
1647 const bool allowed_to_move = (time >= game_starttime);
1648 if (!allowed_to_move)
1650 this.velocity = '0 0 0';
1651 this.movetype = MOVETYPE_NONE;
1652 this.disableclientprediction = 2;
1654 else if (this.disableclientprediction == 2)
1656 if (this.movetype == MOVETYPE_NONE)
1657 this.movetype = MOVETYPE_WALK;
1658 this.disableclientprediction = 0;
1664 if (this.movetype == MOVETYPE_NONE)
1667 // when we get here, disableclientprediction cannot be 2
1668 this.disableclientprediction = 0;
1671 viewloc_PlayerPhysics();
1682 maxspeed_mod *= this.swamp_slowdown; //cvar("g_balance_swamp_moverate");
1684 // conveyors: first fix velocity
1685 if (this.conveyor.state)
1686 this.velocity -= this.conveyor.movedir;
1688 MUTATOR_CALLHOOK(PlayerPhysics);
1690 // float forcedodge = 1;
1693 // PM_dodging_checkpressedkeys();
1696 // PM_ClientMovement_Move();
1701 if (!IS_PLAYER(this))
1703 maxspeed_mod = autocvar_sv_spectator_speed_multiplier;
1704 if (!this.spectatorspeed)
1705 this.spectatorspeed = maxspeed_mod;
1706 if (this.impulse && this.impulse <= 19 || (this.impulse >= 200 && this.impulse <= 209) || (this.impulse >= 220 && this.impulse <= 229))
1708 if (this.lastclassname != "player")
1710 if (this.impulse == 10 || this.impulse == 15 || this.impulse == 18 || (this.impulse >= 200 && this.impulse <= 209))
1711 this.spectatorspeed = bound(1, this.spectatorspeed + 0.5, 5);
1712 else if (this.impulse == 11)
1713 this.spectatorspeed = maxspeed_mod;
1714 else if (this.impulse == 12 || this.impulse == 16 || this.impulse == 19 || (this.impulse >= 220 && this.impulse <= 229))
1715 this.spectatorspeed = bound(1, this.spectatorspeed - 0.5, 5);
1716 else if (this.impulse >= 1 && this.impulse <= 9)
1717 this.spectatorspeed = 1 + 0.5 * (this.impulse - 1);
1718 } // otherwise just clear
1721 maxspeed_mod = this.spectatorspeed;
1724 float spd = max(PHYS_MAXSPEED(this), PHYS_MAXAIRSPEED(this)) * maxspeed_mod;
1725 if(this.speed != spd)
1728 string temps = ftos(spd);
1729 stuffcmd(this, strcat("cl_forwardspeed ", temps, "\n"));
1730 stuffcmd(this, strcat("cl_backspeed ", temps, "\n"));
1731 stuffcmd(this, strcat("cl_sidespeed ", temps, "\n"));
1732 stuffcmd(this, strcat("cl_upspeed ", temps, "\n"));
1735 if(this.stat_jumpspeedcap_min != PHYS_JUMPSPEEDCAP_MIN)
1737 this.stat_jumpspeedcap_min = PHYS_JUMPSPEEDCAP_MIN;
1738 stuffcmd(this, strcat("cl_jumpspeedcap_min ", PHYS_JUMPSPEEDCAP_MIN, "\n"));
1740 if(this.stat_jumpspeedcap_max != PHYS_JUMPSPEEDCAP_MAX)
1742 this.stat_jumpspeedcap_min = PHYS_JUMPSPEEDCAP_MAX;
1743 stuffcmd(this, strcat("cl_jumpspeedcap_max ", PHYS_JUMPSPEEDCAP_MAX, "\n"));
1749 // handle water here
1750 vector midpoint = ((this.absmin + this.absmax) * 0.5);
1751 if(pointcontents(midpoint) == CONTENT_WATER)
1753 this.velocity = this.velocity * 0.5;
1756 //if(pointcontents(midpoint + '0 0 2') == CONTENT_WATER)
1757 //{ this.velocity_z = 70; }
1763 if (!this.fixangle && !g_bugrigs)
1764 this.angles = '0 1 0' * this.v_angle.y;
1767 PM_check_hitground();
1772 if (IS_PLAYER(this))
1775 if (this.flags & FL_WATERJUMP)
1777 this.velocity_x = this.movedir.x;
1778 this.velocity_y = this.movedir.y;
1779 if (time > this.teleport_time || this.waterlevel == WATERLEVEL_NONE)
1781 this.flags &= ~FL_WATERJUMP;
1782 this.teleport_time = 0;
1787 else if (g_bugrigs && IS_PLAYER(this))
1791 else if (this.movetype == MOVETYPE_NOCLIP || this.movetype == MOVETYPE_FLY || this.movetype == MOVETYPE_FLY_WORLDONLY || MUTATOR_CALLHOOK(IsFlying, this))
1792 PM_fly(maxspeed_mod);
1794 else if (this.waterlevel >= WATERLEVEL_SWIMMING)
1795 PM_swim(maxspeed_mod);
1797 else if (time < this.ladder_time)
1798 PM_ladder(maxspeed_mod);
1800 else if (ITEMS_STAT(this) & IT_USING_JETPACK)
1801 PM_jetpack(maxspeed_mod);
1803 else if (IS_ONGROUND(this))
1804 PM_walk(this, maxspeed_mod);
1807 PM_air(buttons_prev, maxspeed_mod);
1812 if (IS_ONGROUND(this))
1813 this.lastground = time;
1815 // conveyors: then break velocity again
1816 if(this.conveyor.state)
1817 this.velocity += this.conveyor.movedir;
1819 this.lastflags = this.flags;
1821 this.lastclassname = this.classname;
1824 this.v_angle = oldv_angle;
1825 this.angles = oldangles;
1830 void SV_PlayerPhysics()
1832 void CSQC_ClientMovement_PlayerMove_Frame(entity this)
1841 ((this.flags & FL_DUCKED) ? PMF_DUCKED : 0) |
1842 (!(this.flags & FL_JUMPRELEASED) ? PMF_JUMP_HELD : 0) |
1843 ((this.flags & FL_ONGROUND) ? PMF_ONGROUND : 0);