Xonotic
player.qc
Go to the documentation of this file.
1 #include "player.qh"
2 
4 #include <common/viewloc.qh>
5 
6 #ifdef GAMEQC
7 REPLICATE(cvar_cl_physics, string, "cl_physics");
8 REPLICATE(cvar_cl_jetpack_jump, bool, "cl_jetpack_jump");
9 REPLICATE(cvar_cl_movement_track_canjump, bool, "cl_movement_track_canjump");
10 #endif
11 
12 #ifdef SVQC
15 #include <server/client.qh>
16 
17 // client side physics
18 bool Physics_Valid(string thecvar)
19 {
20  return thecvar != "" && thecvar && thecvar != "default" && strhasword(autocvar_g_physics_clientselect_options, thecvar);
21 }
22 
23 float Physics_ClientOption(entity this, string option, float defaultval)
24 {
25  if(!autocvar_g_physics_clientselect)
26  return defaultval;
27 
28  if(IS_REAL_CLIENT(this) && Physics_Valid(CS_CVAR(this).cvar_cl_physics))
29  {
30  string s = strcat("g_physics_", CS_CVAR(this).cvar_cl_physics, "_", option);
31  if(cvar_type(s) & CVAR_TYPEFLAG_EXISTS)
32  return cvar(s);
33  }
34  if(autocvar_g_physics_clientselect_default && autocvar_g_physics_clientselect_default != "" && autocvar_g_physics_clientselect_default != "default")
35  {
36  // NOTE: not using Physics_Valid here, so the default can be forced to something normally unavailable
37  string s = strcat("g_physics_", autocvar_g_physics_clientselect_default, "_", option);
38  if(cvar_type(s) & CVAR_TYPEFLAG_EXISTS)
39  return cvar(s);
40  }
41  return defaultval;
42 }
43 
44 void Physics_UpdateStats(entity this)
45 {
46  // update this first, as it's used on all stats (wouldn't want to update them all manually from a mutator hook now, would we?)
47  STAT(MOVEVARS_HIGHSPEED, this) = autocvar_g_movement_highspeed;
48 
49  MUTATOR_CALLHOOK(PlayerPhysics_UpdateStats, this);
50  float maxspd_mod = PHYS_HIGHSPEED(this) * ((this.swampslug.active == ACTIVE_ACTIVE) ? this.swampslug.swamp_slowdown : 1);
51  STAT(MOVEVARS_MAXSPEED, this) = Physics_ClientOption(this, "maxspeed", autocvar_sv_maxspeed) * maxspd_mod; // also slow walking
52  if (autocvar_g_movement_highspeed_q3_compat) {
53  STAT(MOVEVARS_AIRACCEL_QW, this) = Physics_ClientOption(this, "airaccel_qw", autocvar_sv_airaccel_qw);
54  STAT(MOVEVARS_AIRSTRAFEACCEL_QW, this) = Physics_ClientOption(this, "airstrafeaccel_qw", autocvar_sv_airstrafeaccel_qw);
55  STAT(MOVEVARS_AIRSPEEDLIMIT_NONQW, this) = Physics_ClientOption(this, "airspeedlimit_nonqw", autocvar_sv_airspeedlimit_nonqw);
56  } else {
57  STAT(MOVEVARS_AIRACCEL_QW, this) = AdjustAirAccelQW(Physics_ClientOption(this, "airaccel_qw", autocvar_sv_airaccel_qw), maxspd_mod);
58  STAT(MOVEVARS_AIRSTRAFEACCEL_QW, this) = (Physics_ClientOption(this, "airstrafeaccel_qw", autocvar_sv_airstrafeaccel_qw))
59  ? AdjustAirAccelQW(Physics_ClientOption(this, "airstrafeaccel_qw", autocvar_sv_airstrafeaccel_qw), maxspd_mod)
60  : 0;
61  STAT(MOVEVARS_AIRSPEEDLIMIT_NONQW, this) = Physics_ClientOption(this, "airspeedlimit_nonqw", autocvar_sv_airspeedlimit_nonqw) * maxspd_mod;
62  }
64  STAT(PL_MIN, this) = (q3hb) ? '-15 -15 -20' : autocvar_sv_player_mins;
65  STAT(PL_MAX, this) = (q3hb) ? '15 15 36' : autocvar_sv_player_maxs;
66  STAT(PL_VIEW_OFS, this) = (q3hb) ? '0 0 30' : autocvar_sv_player_viewoffset;
67  STAT(PL_CROUCH_MIN, this) = (q3hb) ? '-15 -15 -20' : autocvar_sv_player_crouch_mins;
68  STAT(PL_CROUCH_MAX, this) = (q3hb) ? '15 15 20' : autocvar_sv_player_crouch_maxs;
69  STAT(PL_CROUCH_VIEW_OFS, this) = (q3hb) ? '0 0 16' : autocvar_sv_player_crouch_viewoffset;
70 
71  // old stats
72  // fix some new settings
73  STAT(MOVEVARS_AIRACCEL_QW_STRETCHFACTOR, this) = Physics_ClientOption(this, "airaccel_qw_stretchfactor", autocvar_sv_airaccel_qw_stretchfactor);
74  STAT(MOVEVARS_MAXAIRSTRAFESPEED, this) = Physics_ClientOption(this, "maxairstrafespeed", autocvar_sv_maxairstrafespeed);
75  if (autocvar_g_movement_highspeed_q3_compat) {
76  STAT(MOVEVARS_MAXAIRSPEED, this) = Physics_ClientOption(this, "maxairspeed", autocvar_sv_maxairspeed) * maxspd_mod;
77  } else {
78  STAT(MOVEVARS_MAXAIRSPEED, this) = Physics_ClientOption(this, "maxairspeed", autocvar_sv_maxairspeed);
79  }
80 
81  STAT(MOVEVARS_AIRSTRAFEACCELERATE, this) = Physics_ClientOption(this, "airstrafeaccelerate", autocvar_sv_airstrafeaccelerate);
82  STAT(MOVEVARS_WARSOWBUNNY_TURNACCEL, this) = Physics_ClientOption(this, "warsowbunny_turnaccel", autocvar_sv_warsowbunny_turnaccel);
83  STAT(MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION, this) = Physics_ClientOption(this, "airaccel_sideways_friction", autocvar_sv_airaccel_sideways_friction);
84  STAT(MOVEVARS_AIRCONTROL, this) = Physics_ClientOption(this, "aircontrol", autocvar_sv_aircontrol);
85  STAT(MOVEVARS_AIRCONTROL_POWER, this) = Physics_ClientOption(this, "aircontrol_power", autocvar_sv_aircontrol_power);
86  STAT(MOVEVARS_AIRCONTROL_BACKWARDS, this) = Physics_ClientOption(this, "aircontrol_backwards", autocvar_sv_aircontrol_backwards);
87  STAT(MOVEVARS_AIRCONTROL_SIDEWARDS, this) = Physics_ClientOption(this, "aircontrol_sidewards", autocvar_sv_aircontrol_sidewards);
88  STAT(MOVEVARS_AIRCONTROL_PENALTY, this) = Physics_ClientOption(this, "aircontrol_penalty", autocvar_sv_aircontrol_penalty);
89  STAT(MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL, this) = Physics_ClientOption(this, "warsowbunny_airforwardaccel", autocvar_sv_warsowbunny_airforwardaccel);
90  STAT(MOVEVARS_WARSOWBUNNY_TOPSPEED, this) = Physics_ClientOption(this, "warsowbunny_topspeed", autocvar_sv_warsowbunny_topspeed);
91  STAT(MOVEVARS_WARSOWBUNNY_ACCEL, this) = Physics_ClientOption(this, "warsowbunny_accel", autocvar_sv_warsowbunny_accel);
92  STAT(MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO, this) = Physics_ClientOption(this, "warsowbunny_backtosideratio", autocvar_sv_warsowbunny_backtosideratio);
93  STAT(MOVEVARS_FRICTION, this) = Physics_ClientOption(this, "friction", autocvar_sv_friction);
94  STAT(MOVEVARS_ACCELERATE, this) = Physics_ClientOption(this, "accelerate", autocvar_sv_accelerate);
95  STAT(MOVEVARS_STOPSPEED, this) = Physics_ClientOption(this, "stopspeed", autocvar_sv_stopspeed);
96  STAT(MOVEVARS_AIRACCELERATE, this) = Physics_ClientOption(this, "airaccelerate", autocvar_sv_airaccelerate);
97  STAT(MOVEVARS_AIRSTOPACCELERATE, this) = Physics_ClientOption(this, "airstopaccelerate", autocvar_sv_airstopaccelerate);
98  STAT(MOVEVARS_JUMPVELOCITY, this) = Physics_ClientOption(this, "jumpvelocity", autocvar_sv_jumpvelocity);
99  STAT(MOVEVARS_JUMPVELOCITY_CROUCH, this) = Physics_ClientOption(this, "jumpvelocity_crouch", autocvar_sv_jumpvelocity_crouch);
100  STAT(MOVEVARS_TRACK_CANJUMP, this) = Physics_ClientOption(this, "track_canjump", autocvar_sv_track_canjump);
101 
102  MUTATOR_CALLHOOK(PlayerPhysics_PostUpdateStats, this, maxspd_mod);
103 }
104 #endif
105 
106 float IsMoveInDirection(vector mv, float ang) // key mix factor
107 {
108  if (mv_x == 0 && mv_y == 0)
109  return 0; // avoid division by zero
110  ang -= RAD2DEG * atan2(mv_y, mv_x);
111  ang = remainder(ang, 360) / 45;
112  return ang > 1 ? 0 : ang < -1 ? 0 : 1 - fabs(ang);
113 }
114 
115 float GeomLerp(float a, float _lerp, float b)
116 {
117  return a == 0 ? (_lerp < 1 ? 0 : b)
118  : b == 0 ? (_lerp > 0 ? 0 : a)
119  : a * (fabs(b / a) ** _lerp);
120 }
121 
123 {
124  if(!IS_PLAYER(this))
125  return;
126 
127  bool have_hook = false;
128  for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
129  {
130  #if defined(CSQC)
131  entity wepent = viewmodels[slot];
132  #elif defined(SVQC)
133  .entity weaponentity = weaponentities[slot];
134  entity wepent = this.(weaponentity);
135  #endif
136  if(wepent.hook && !wasfreed(wepent.hook))
137  {
138  have_hook = true;
139  break;
140  }
141  }
142  bool do_crouch = PHYS_INPUT_BUTTON_CROUCH(this);
143  if(this.viewloc && !(this.viewloc.spawnflags & VIEWLOC_FREEMOVE) && PHYS_CS(this).movement.x < 0)
144  do_crouch = true;
145  if (have_hook) {
146  do_crouch = false;
147  //} else if (this.waterlevel >= WATERLEVEL_SWIMMING) {
148  //do_crouch = false;
149  } else if (PHYS_INVEHICLE(this)) {
150  do_crouch = false;
151  } else if (STAT(FROZEN, this) || IS_DEAD(this)) {
152  do_crouch = false;
153  }
154 
155  MUTATOR_CALLHOOK(PlayerCanCrouch, this, do_crouch);
156  do_crouch = M_ARGV(1, bool);
157 
158  if (do_crouch) {
159  if (!IS_DUCKED(this)) {
160  SET_DUCKED(this);
161  this.view_ofs = STAT(PL_CROUCH_VIEW_OFS, this);
162  setsize(this, STAT(PL_CROUCH_MIN, this), STAT(PL_CROUCH_MAX, this));
163  // setanim(this, this.anim_duck, false, true, true); // this anim is BROKEN anyway
164  }
165  } else if (IS_DUCKED(this)) {
166  tracebox(this.origin, STAT(PL_MIN, this), STAT(PL_MAX, this), this.origin, false, this);
167  if (!trace_startsolid) {
168  UNSET_DUCKED(this);
169  this.view_ofs = STAT(PL_VIEW_OFS, this);
170  setsize(this, STAT(PL_MIN, this), STAT(PL_MAX, this));
171  }
172  }
173 
174  _Movetype_CheckWater(this); // needs to be run on the client, might as well use the latest on the server too!
175 }
176 
177 void CPM_PM_Aircontrol(entity this, float dt, vector wishdir, float wishspeed)
178 {
179  float movity = IsMoveInDirection(PHYS_CS(this).movement, 0);
180  if(PHYS_AIRCONTROL_BACKWARDS(this))
181  movity += IsMoveInDirection(PHYS_CS(this).movement, 180);
182  if(PHYS_AIRCONTROL_SIDEWARDS(this))
183  {
184  movity += IsMoveInDirection(PHYS_CS(this).movement, 90);
185  movity += IsMoveInDirection(PHYS_CS(this).movement, -90);
186  }
187 
188  float k = 32 * (2 * movity - 1);
189  if (k <= 0)
190  return;
191 
192  k *= bound(0, wishspeed / PHYS_MAXAIRSPEED(this), 1);
193 
194  float zspeed = this.velocity_z;
195  this.velocity_z = 0;
196  float xyspeed = vlen(this.velocity);
197  this.velocity = normalize(this.velocity);
198 
199  float dot = this.velocity * wishdir;
200 
201  if (dot > 0) // we can't change direction while slowing down
202  {
203  k *= (dot ** PHYS_AIRCONTROL_POWER(this)) * dt;
204  xyspeed = max(0, xyspeed - PHYS_AIRCONTROL_PENALTY(this) * sqrt(max(0, 1 - dot*dot)) * k/32);
205  k *= PHYS_AIRCONTROL(this);
206  this.velocity = normalize(this.velocity * xyspeed + wishdir * k);
207  }
208 
209  this.velocity = this.velocity * xyspeed;
210  this.velocity_z = zspeed;
211 }
212 
213 float AdjustAirAccelQW(float accelqw, float factor)
214 {
215  return copysign(bound(0.000001, 1 - (1 - fabs(accelqw)) * factor, 1), accelqw);
216 }
217 
218 // example config for alternate speed clamping:
219 // sv_airaccel_qw 0.8
220 // sv_airaccel_sideways_friction 0
221 // prvm_globalset server speedclamp_mode 1
222 // (or 2)
223 void PM_Accelerate(entity this, float dt, vector wishdir, float wishspeed, float wishspeed0, float accel, float accelqw, float stretchfactor, float sidefric, float speedlimit)
224 {
225  float speedclamp = stretchfactor > 0 ? stretchfactor
226  : accelqw < 0 ? 1 // full clamping, no stretch
227  : -1; // no clamping
228 
229  accelqw = fabs(accelqw);
230 
231  if (GAMEPLAYFIX_Q2AIRACCELERATE)
232  wishspeed0 = wishspeed; // don't need to emulate this Q1 bug
233 
234  float vel_straight = this.velocity * wishdir;
235  float vel_z = this.velocity_z;
236  vector vel_xy = vec2(this.velocity);
237  vector vel_perpend = vel_xy - vel_straight * wishdir;
238 
239  float step = accel * dt * wishspeed0;
240 
241  float vel_xy_current = vlen(vel_xy);
242  if (speedlimit)
243  accelqw = AdjustAirAccelQW(accelqw, (speedlimit - bound(wishspeed, vel_xy_current, speedlimit)) / max(1, speedlimit - wishspeed));
244  float vel_xy_forward = vel_xy_current + bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw);
245  float vel_xy_backward = vel_xy_current - bound(0, wishspeed + vel_xy_current, step) * accelqw - step * (1 - accelqw);
246  vel_xy_backward = max(0, vel_xy_backward); // not that it REALLY occurs that this would cause wrong behaviour afterwards
247  vel_straight = vel_straight + bound(0, wishspeed - vel_straight, step) * accelqw + step * (1 - accelqw);
248 
249  if (sidefric < 0 && (vel_perpend*vel_perpend))
250  // negative: only apply so much sideways friction to stay below the speed you could get by "braking"
251  {
252  float f = max(0, 1 + dt * wishspeed * sidefric);
253  float themin = (vel_xy_backward * vel_xy_backward - vel_straight * vel_straight) / (vel_perpend * vel_perpend);
254  // assume: themin > 1
255  // vel_xy_backward*vel_xy_backward - vel_straight*vel_straight > vel_perpend*vel_perpend
256  // vel_xy_backward*vel_xy_backward > vel_straight*vel_straight + vel_perpend*vel_perpend
257  // vel_xy_backward*vel_xy_backward > vel_xy * vel_xy
258  // obviously, this cannot be
259  if (themin <= 0)
260  vel_perpend *= f;
261  else
262  {
263  themin = sqrt(themin);
264  vel_perpend *= max(themin, f);
265  }
266  }
267  else
268  vel_perpend *= max(0, 1 - dt * wishspeed * sidefric);
269 
270  vel_xy = vel_straight * wishdir + vel_perpend;
271 
272  if (speedclamp >= 0)
273  {
274  float vel_xy_preclamp;
275  vel_xy_preclamp = vlen(vel_xy);
276  if (vel_xy_preclamp > 0) // prevent division by zero
277  {
278  vel_xy_current += (vel_xy_forward - vel_xy_current) * speedclamp;
279  if (vel_xy_current < vel_xy_preclamp)
280  vel_xy *= (vel_xy_current / vel_xy_preclamp);
281  }
282  }
283 
284  this.velocity = vel_xy + vel_z * '0 0 1';
285 }
286 
287 void PM_AirAccelerate(entity this, float dt, vector wishdir, float wishspeed)
288 {
289  if (wishspeed == 0)
290  return;
291 
292  vector curvel = this.velocity;
293  curvel_z = 0;
294  float curspeed = vlen(curvel);
295 
296  if (wishspeed > curspeed * 1.01)
297  wishspeed = min(wishspeed, curspeed + PHYS_WARSOWBUNNY_AIRFORWARDACCEL(this) * PHYS_MAXSPEED(this) * dt);
298  else
299  {
300  float f = max(0, (PHYS_WARSOWBUNNY_TOPSPEED(this) - curspeed) / (PHYS_WARSOWBUNNY_TOPSPEED(this) - PHYS_MAXSPEED(this)));
301  wishspeed = max(curspeed, PHYS_MAXSPEED(this)) + PHYS_WARSOWBUNNY_ACCEL(this) * f * PHYS_MAXSPEED(this) * dt;
302  }
303  vector wishvel = wishdir * wishspeed;
304  vector acceldir = wishvel - curvel;
305  float addspeed = vlen(acceldir);
306  acceldir = normalize(acceldir);
307 
308  float accelspeed = min(addspeed, PHYS_WARSOWBUNNY_TURNACCEL(this) * PHYS_MAXSPEED(this) * dt);
309 
310  if (PHYS_WARSOWBUNNY_BACKTOSIDERATIO(this) < 1)
311  {
312  vector curdir = normalize(curvel);
313  float dot = acceldir * curdir;
314  if (dot < 0)
315  acceldir -= (1 - PHYS_WARSOWBUNNY_BACKTOSIDERATIO(this)) * dot * curdir;
316  }
317 
318  this.velocity += accelspeed * acceldir;
319 }
320 
321 
322 /*
323 =============
324 PlayerJump
325 
326 When you press the jump key
327 returns true if handled
328 =============
329 */
330 bool PlayerJump(entity this)
331 {
332  if (PHYS_FROZEN(this))
333  return true; // no jumping in freezetag when frozen
334 
336  return true; // no jumping while typing
337 
338 #ifdef SVQC
339  if (this.player_blocked)
340  return true; // no jumping while blocked
341 #endif
342 
343  bool doublejump = false;
344  float mjumpheight = ((PHYS_JUMPVELOCITY_CROUCH(this) && IS_DUCKED(this)) ? PHYS_JUMPVELOCITY_CROUCH(this) : PHYS_JUMPVELOCITY(this));
345  bool track_jump = PHYS_CL_TRACK_CANJUMP(this);
346 
347  if (MUTATOR_CALLHOOK(PlayerJump, this, mjumpheight, doublejump))
348  return true;
349 
350  mjumpheight = M_ARGV(1, float);
351  doublejump = M_ARGV(2, bool);
352 
353  if (this.waterlevel >= WATERLEVEL_SWIMMING)
354  {
355  if(this.viewloc)
356  {
357  doublejump = true;
358  mjumpheight *= 0.7;
359  track_jump = true;
360  }
361  else
362  {
363  this.velocity_z = PHYS_MAXSPEED(this) * 0.7;
364  return true;
365  }
366  }
367 
368  if (!doublejump)
369  if (!IS_ONGROUND(this))
370  return IS_JUMP_HELD(this);
371 
372  if(PHYS_TRACK_CANJUMP(this))
373  track_jump = true;
374 
375  if (track_jump)
376  if (IS_JUMP_HELD(this))
377  return true;
378 
379  // sv_jumpspeedcap_min/sv_jumpspeedcap_max act as baseline
380  // velocity bounds. Final velocity is bound between (jumpheight *
381  // min + jumpheight) and (jumpheight * max + jumpheight);
382 
383  if(PHYS_JUMPSPEEDCAP_MIN != "")
384  {
385  float minjumpspeed = mjumpheight * stof(PHYS_JUMPSPEEDCAP_MIN);
386 
387  if (this.velocity_z < minjumpspeed)
388  mjumpheight += minjumpspeed - this.velocity_z;
389  }
390 
391  if(PHYS_JUMPSPEEDCAP_MAX != "")
392  {
393  // don't do jump speedcaps on ramps to preserve old xonotic ramjump style
394  tracebox(this.origin + '0 0 0.01', this.mins, this.maxs, this.origin - '0 0 0.01', MOVE_NORMAL, this);
395 
396  if (!(trace_fraction < 1 && trace_plane_normal_z < 0.98 && PHYS_JUMPSPEEDCAP_DISABLE_ONRAMPS(this)))
397  {
398  float maxjumpspeed = mjumpheight * stof(PHYS_JUMPSPEEDCAP_MAX);
399 
400  if (this.velocity_z > maxjumpspeed)
401  mjumpheight -= this.velocity_z - maxjumpspeed;
402  }
403  }
404 
405  if (!WAS_ONGROUND(this) && !WAS_ONSLICK(this))
406  {
407 #ifdef SVQC
408  if(autocvar_speedmeter)
409  LOG_TRACE("landing velocity: ", vtos(this.velocity), " (abs: ", ftos(vlen(this.velocity)), ")");
410 #endif
411  if(this.lastground < time - 0.3)
412  {
413  float f = (1 - PHYS_FRICTION_ONLAND(this));
414  this.velocity_x *= f;
415  this.velocity_y *= f;
416  }
417 #ifdef SVQC
418  if(this.jumppadcount > 1)
419  LOG_TRACE(ftos(this.jumppadcount), "x jumppad combo");
420  this.jumppadcount = 0;
421 #endif
422  }
423 
424  this.velocity_z += mjumpheight;
425 
426  UNSET_ONGROUND(this);
427  UNSET_ONSLICK(this);
428  SET_JUMP_HELD(this);
429 
430 #ifdef SVQC
432 
433  if (autocvar_g_jump_grunt)
434  PlayerSound(this, playersound_jump, CH_PLAYER, VOL_BASE, VOICETYPE_PLAYERSOUND);
435 #endif
436  return true;
437 }
438 
440 {
441 // check for a jump-out-of-water
442  makevectors(this.v_angle);
443  vector start = this.origin;
444  start_z += 8;
445  v_forward_z = 0;
447  vector end = start + v_forward*24;
448  traceline (start, end, true, this);
449  if (trace_fraction < 1)
450  { // solid at waist
451  start_z = start_z + this.maxs_z - 8;
452  end = start + v_forward*24;
453  this.movedir = trace_plane_normal * -50;
454  traceline(start, end, true, this);
455  if (trace_fraction == 1)
456  { // open at eye level
457  this.velocity_z = 225;
458  this.flags |= FL_WATERJUMP;
459  this.teleport_time = time + 2; // safety net
460  SET_JUMP_HELD(this);
461  }
462  }
463 }
464 
465 
466 #ifdef SVQC
467  #define JETPACK_JUMP(s) CS_CVAR(s).cvar_cl_jetpack_jump
468 #elif defined(CSQC)
469  float autocvar_cl_jetpack_jump;
470  #define JETPACK_JUMP(s) autocvar_cl_jetpack_jump
471 #endif
474 {
475 #ifdef SVQC
476  bool was_flying = boolean(ITEMS_STAT(this) & IT_USING_JETPACK);
477 #endif
478  if (JETPACK_JUMP(this) < 2)
479  ITEMS_STAT(this) &= ~IT_USING_JETPACK;
480 
482  {
483  bool playerjump = PlayerJump(this); // required
484 
485  bool air_jump = !playerjump || M_ARGV(2, bool);
486  bool activate = (JETPACK_JUMP(this) && air_jump && PHYS_INPUT_BUTTON_JUMP(this)) || PHYS_INPUT_BUTTON_JETPACK(this);
487  bool has_fuel = !PHYS_JETPACK_FUEL(this) || PHYS_AMMO_FUEL(this) || (ITEMS_STAT(this) & IT_UNLIMITED_AMMO);
488 
489  if (!(ITEMS_STAT(this) & ITEM_Jetpack.m_itemid)) { }
490  else if (this.jetpack_stopped) { }
491  else if (!has_fuel)
492  {
493 #ifdef SVQC
494  if (was_flying) // TODO: ran out of fuel message
495  Send_Notification(NOTIF_ONE, this, MSG_INFO, INFO_JETPACK_NOFUEL);
496  else if (activate)
497  Send_Notification(NOTIF_ONE, this, MSG_INFO, INFO_JETPACK_NOFUEL);
498 #endif
499  this.jetpack_stopped = true;
500  ITEMS_STAT(this) &= ~IT_USING_JETPACK;
501  }
502  else if (activate && !PHYS_FROZEN(this))
503  ITEMS_STAT(this) |= IT_USING_JETPACK;
504  }
505  else
506  {
507  this.jetpack_stopped = false;
508  ITEMS_STAT(this) &= ~IT_USING_JETPACK;
509  }
510  if (!PHYS_INPUT_BUTTON_JUMP(this))
511  UNSET_JUMP_HELD(this);
512 
513  if (this.waterlevel == WATERLEVEL_SWIMMING)
514  CheckWaterJump(this);
515 }
516 
517 #ifdef SVQC
518 string specialcommand = "xwxwxsxsxaxdxaxdx1x ";
519 .float specialcommand_pos;
520 void SpecialCommand(entity this)
521 {
522  if(autocvar_sv_cheats || this.maycheat)
523  {
524  if (!CheatImpulse(this, CHIMPULSE_GIVE_ALL.impulse))
525  LOG_INFO("A hollow voice says \"Plugh\".");
526  }
527 }
528 #endif
529 
530 bool PM_check_specialcommand(entity this, int buttons)
531 {
532 #ifdef SVQC
533  string c;
534  switch (buttons)
535  {
536  // buttons mapped in PHYS_INPUT_BUTTON_MASK
537  case 0: c = "x"; break;
538  case BIT(0): c = "1"; break;
539  case BIT(2): c = " "; break;
540  case BIT(7): c = "s"; break;
541  case BIT(8): c = "w"; break;
542  case BIT(9): c = "a"; break;
543  case BIT(10): c = "d"; break;
544  default: c = "?";
545  }
546 
547  if (c == substring(specialcommand, CS(this).specialcommand_pos, 1))
548  {
549  CS(this).specialcommand_pos += 1;
550  if (CS(this).specialcommand_pos >= strlen(specialcommand))
551  {
552  CS(this).specialcommand_pos = 0;
553  SpecialCommand(this);
554  return true;
555  }
556  }
557  else if (CS(this).specialcommand_pos && (c != substring(specialcommand, CS(this).specialcommand_pos - 1, 1)))
558  CS(this).specialcommand_pos = 0;
559 #endif
560  return false;
561 }
562 
564 {
565 #ifdef SVQC
566  if (time >= this.nickspamtime)
567  return;
568  if (this.nickspamcount >= autocvar_g_nick_flood_penalty_yellow)
569  {
570  // slight annoyance for nick change scripts
571  PHYS_CS(this).movement = -1 * PHYS_CS(this).movement;
573 
574  if (this.nickspamcount >= autocvar_g_nick_flood_penalty_red) // if you are persistent and the slight annoyance above does not stop you, I'll show you!
575  {
576  this.v_angle_x = random() * 360;
577  this.v_angle_y = random() * 360;
578  // at least I'm not forcing retardedview by also assigning to angles_z
579  this.fixangle = true;
580  }
581  }
582 #endif
583 }
584 
585 void PM_check_punch(entity this, float dt)
586 {
587 #ifdef SVQC
588  if (this.punchangle != '0 0 0')
589  {
590  float f = vlen(this.punchangle) - 10 * dt;
591  if (f > 0)
592  this.punchangle = normalize(this.punchangle) * f;
593  else
594  this.punchangle = '0 0 0';
595  }
596 
597  if (this.punchvector != '0 0 0')
598  {
599  float f = vlen(this.punchvector) - 30 * dt;
600  if (f > 0)
601  this.punchvector = normalize(this.punchvector) * f;
602  else
603  this.punchvector = '0 0 0';
604  }
605 #endif
606 }
607 
608 // predict frozen movement, as frozen players CAN move in some cases
610 {
611  if (!PHYS_FROZEN(this))
612  return;
613  if (PHYS_DODGING_FROZEN(this) && IS_CLIENT(this))
614  {
615  // bind movement to a very slow speed so dodging can use .movement for directional calculations
616  PHYS_CS(this).movement_x = bound(-2, PHYS_CS(this).movement.x, 2);
617  PHYS_CS(this).movement_y = bound(-2, PHYS_CS(this).movement.y, 2);
618  PHYS_CS(this).movement_z = bound(-2, PHYS_CS(this).movement.z, 2);
619  }
620  else
621  PHYS_CS(this).movement = '0 0 0';
622 }
623 
625 {
626 #ifdef SVQC
627  if (!this.wasFlying) return;
628  this.wasFlying = false;
629  if (this.waterlevel >= WATERLEVEL_SWIMMING) return;
630  if (this.ladder_entity) return;
631  for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
632  {
633  .entity weaponentity = weaponentities[slot];
634  if(this.(weaponentity).hook)
635  return;
636  }
637  this.nextstep = time + 0.3 + random() * 0.1;
639  tracebox(this.origin, this.mins, this.maxs, this.origin - '0 0 1', MOVE_NOMONSTERS, this);
642  ? GS_FALL_METAL
643  : GS_FALL;
644  float vol = ((IS_DUCKED(this)) ? VOL_MUFFLED : VOL_BASE);
645  GlobalSound(this, gs, CH_PLAYER, vol, VOICETYPE_PLAYERSOUND);
646 #endif
647 }
648 
650 {
651 #ifdef SVQC
652  if (!autocvar_g_footsteps) return;
653  if (IS_DUCKED(this)) return;
654  if (time >= this.lastground + 0.2) return;
655  if (vdist(this.velocity, <=, autocvar_sv_maxspeed * 0.6)) return;
656  if ((time > this.nextstep) || (time < (this.nextstep - 10.0)))
657  {
658  this.nextstep = time + 0.3 + random() * 0.1;
660  tracebox(this.origin, this.mins, this.maxs, this.origin - '0 0 1', MOVE_NOMONSTERS, this);
663  ? GS_STEP_METAL
664  : GS_STEP;
665  GlobalSound(this, gs, CH_PLAYER, VOL_BASE, VOICETYPE_PLAYERSOUND);
666  }
667 #endif
668 }
669 
671 {
672  if(!IS_ONGROUND(this))
673  return;
674 
676  tracebox(this.origin, this.mins, this.maxs, this.origin - '0 0 1', MOVE_NOMONSTERS, this);
678  SET_ONSLICK(this);
679  else
680  UNSET_ONSLICK(this);
681 }
682 
684 {
686  PHYS_CS(this).movement = '0 0 0';
687 #ifdef SVQC
688  if (!this.player_blocked)
689  return;
690  PHYS_CS(this).movement = '0 0 0';
691  this.disableclientprediction = 1;
692 #endif
693 }
694 
695 void PM_jetpack(entity this, float maxspd_mod, float dt)
696 {
697  //makevectors(this.v_angle.y * '0 1 0');
698  makevectors(this.v_angle);
699  vector wishvel = v_forward * PHYS_CS(this).movement_x
700  + v_right * PHYS_CS(this).movement_y;
701  // add remaining speed as Z component
702  float maxairspd = PHYS_MAXAIRSPEED(this) * max(1, maxspd_mod);
703  // fix speedhacks :P
704  wishvel = normalize(wishvel) * min(1, vlen(wishvel) / maxairspd);
705  // add the unused velocity as up component
706  wishvel_z = 0;
707 
708  // if (PHYS_INPUT_BUTTON_JUMP(this))
709  wishvel_z = sqrt(max(0, 1 - wishvel * wishvel));
710 
711  // it is now normalized, so...
712  float a_side = PHYS_JETPACK_ACCEL_SIDE(this);
713  float a_up = PHYS_JETPACK_ACCEL_UP(this);
714  float a_add = PHYS_JETPACK_ANTIGRAVITY(this) * PHYS_GRAVITY(this);
715 
717 
718  wishvel_x *= a_side;
719  wishvel_y *= a_side;
720  wishvel_z *= a_up;
721  wishvel_z += a_add;
722 
723  if(PHYS_JETPACK_REVERSE_THRUST(this) && PHYS_INPUT_BUTTON_CROUCH(this)) { wishvel_z *= -1; }
724 
725  float best = 0;
727  // finding the maximum over all vectors of above form
728  // with wishvel having an absolute value of 1
730  // we're finding the maximum over
731  // f(a_side, a_up, a_add, z) := a_side * (1 - z^2) + (a_add + a_up * z)^2;
732  // for z in the range from -1 to 1
734  // maximum is EITHER attained at the single extreme point:
735  float a_diff = a_side * a_side - a_up * a_up;
736  float f;
737  if (a_diff != 0)
738  {
739  f = a_add * a_up / a_diff; // this is the zero of diff(f(a_side, a_up, a_add, z), z)
740  if (f > -1 && f < 1) // can it be attained?
741  {
742  best = (a_diff + a_add * a_add) * (a_diff + a_up * a_up) / a_diff;
743  //print("middle\n");
744  }
745  }
746  // OR attained at z = 1:
747  f = (a_up + a_add) * (a_up + a_add);
748  if (f > best)
749  {
750  best = f;
751  //print("top\n");
752  }
753  // OR attained at z = -1:
754  f = (a_up - a_add) * (a_up - a_add);
755  if (f > best)
756  {
757  best = f;
758  //print("bottom\n");
759  }
760  best = sqrt(best);
762 
763  //print("best possible acceleration: ", ftos(best), "\n");
764 
765  float fxy, fz;
766  fxy = bound(0, 1 - (this.velocity * normalize(wishvel_x * '1 0 0' + wishvel_y * '0 1 0')) / PHYS_JETPACK_MAXSPEED_SIDE(this), 1);
767  if (wishvel_z - PHYS_GRAVITY(this) > 0)
768  fz = bound(0, 1 - this.velocity_z / PHYS_JETPACK_MAXSPEED_UP(this), 1);
769  else
770  fz = bound(0, 1 + this.velocity_z / PHYS_JETPACK_MAXSPEED_UP(this), 1);
771 
772  float fvel;
773  fvel = vlen(wishvel);
774  wishvel_x *= fxy;
775  wishvel_y *= fxy;
776  wishvel_z = (wishvel_z - PHYS_GRAVITY(this)) * fz + PHYS_GRAVITY(this);
777 
778  fvel = min(1, vlen(wishvel) / best);
779  if (PHYS_JETPACK_FUEL(this) && !(ITEMS_STAT(this) & IT_UNLIMITED_AMMO))
780  f = min(1, PHYS_AMMO_FUEL(this) / (PHYS_JETPACK_FUEL(this) * dt * fvel));
781  else
782  f = 1;
783 
784  //print("this acceleration: ", ftos(vlen(wishvel) * f), "\n");
785 
786  if (f > 0 && wishvel != '0 0 0')
787  {
788  this.velocity = this.velocity + wishvel * f * dt;
789  UNSET_ONGROUND(this);
790 
791 #ifdef SVQC
792  if (!(ITEMS_STAT(this) & IT_UNLIMITED_AMMO))
793  TakeResource(this, RES_FUEL, PHYS_JETPACK_FUEL(this) * dt * fvel * f);
794 
795  ITEMS_STAT(this) |= IT_USING_JETPACK;
796 
797  // jetpack also inhibits health regeneration, but only for 1 second
799 #endif
800  }
801 }
802 
803 // used for calculating airshots
804 bool IsFlying(entity this)
805 {
806  if(IS_ONGROUND(this))
807  return false;
808  if(this.waterlevel >= WATERLEVEL_SWIMMING)
809  return false;
810  tracebox(this.origin, this.mins, this.maxs, this.origin - '0 0 24', MOVE_NORMAL, this);
811  //traceline(this.origin, this.origin - '0 0 48', MOVE_NORMAL, this);
812  if(trace_fraction < 1)
813  return false;
814  return true;
815 }
816 
817 
818 void sys_phys_update(entity this, float dt);
819 #if defined(SVQC)
820 void SV_PlayerPhysics(entity this)
821 #elif defined(CSQC)
823 #endif
824 {
825 #ifdef SVQC
826  // needs to be called before physics are run!
827  if(IS_REAL_CLIENT(this))
828  PM_UpdateButtons(this, CS(this));
829 #endif
830 
831  sys_phys_update(this, PHYS_INPUT_TIMELENGTH);
832 
833 #ifdef SVQC
834  CS(this).pm_frametime = frametime;
835 #elif defined(CSQC)
836  if((ITEMS_STAT(this) & IT_USING_JETPACK) && !IS_DEAD(this) && !intermission)
838  else
840 #endif
841 }
#define PHYS_INPUT_BUTTON_ATCK2(s)
Definition: player.qh:148
#define PHYS_INPUT_BUTTON_JUMP(s)
Definition: player.qh:147
#define UNSET_ONSLICK(s)
Definition: movetypes.qh:21
#define PHYS_INPUT_BUTTON_CHAT(s)
Definition: player.qh:155
#define PHYS_INPUT_BUTTON_CROUCH(s)
Definition: player.qh:150
void CheckPlayerJump(entity this)
Definition: player.qc:473
#define PHYS_MAXAIRSPEED(s)
Definition: player.qh:130
#define PHYS_JETPACK_MAXSPEED_UP(s)
Definition: player.qh:123
void CPM_PM_Aircontrol(entity this, float dt, vector wishdir, float wishspeed)
Definition: player.qc:177
entity hook
Definition: hook.qh:19
const float VOL_MUFFLED
Definition: sound.qh:38
vector view_ofs
Definition: progsdefs.qc:151
#define PHYS_INPUT_BUTTON_HOOK(s)
Definition: player.qh:151
const int MF_ROCKET
float nickspamcount
Definition: client.qh:321
#define PHYS_JETPACK_REVERSE_THRUST(s)
Definition: player.qh:124
#define PHYS_AMMO_FUEL(s)
Definition: player.qh:106
float trace_dphitq3surfaceflags
#define PHYS_WARSOWBUNNY_AIRFORWARDACCEL(s)
Definition: player.qh:139
#define WAS_ONGROUND(s)
Definition: player.qh:203
const int VOICETYPE_PLAYERSOUND
Definition: globalsound.qh:64
float waterlevel
Definition: progsdefs.qc:181
#define IS_CLIENT(v)
Definition: utils.qh:13
bool _Movetype_CheckWater(entity this)
Definition: movetypes.qc:345
#define IS_DUCKED(s)
Definition: player.qh:206
float teleport_time
Definition: player.qh:212
const int ANIMACTION_JUMP
Definition: animdecide.qh:141
void PM_check_slick(entity this)
Definition: player.qc:670
void PM_Accelerate(entity this, float dt, vector wishdir, float wishspeed, float wishspeed0, float accel, float accelqw, float stretchfactor, float sidefric, float speedlimit)
Definition: player.qc:223
void PM_check_hitground(entity this)
Definition: player.qc:624
entity viewmodels[MAX_WEAPONSLOTS]
Definition: view.qh:104
float CVAR_TYPEFLAG_EXISTS
entity() spawn
float CheatImpulse(entity this, int imp)
Definition: cheats.qc:130
const float MOVE_NORMAL
Definition: csprogsdefs.qc:252
ClientState CS(Client this)
Definition: state.qh:47
#define PHYS_WARSOWBUNNY_BACKTOSIDERATIO(s)
Definition: player.qh:140
float copysign(float e, float f)
Definition: mathlib.qc:225
#define PHYS_WARSOWBUNNY_TOPSPEED(s)
Definition: player.qh:141
void CSQC_ClientMovement_PlayerMove_Frame(entity this)
vector v_angle
Definition: progsdefs.qc:161
#define IS_ONGROUND(s)
Definition: movetypes.qh:16
float intermission
Definition: csprogsdefs.qc:148
vector maxs
Definition: csprogsdefs.qc:113
#define PHYS_INPUT_BUTTON_ZOOM(s)
Definition: player.qh:149
float lastground
Definition: player.qh:61
REPLICATE(cvar_cl_casings, bool, "cl_casings")
#define CS_CVAR(this)
Definition: state.qh:51
#define WAS_ONSLICK(s)
Definition: player.qh:204
#define PHYS_JUMPSPEEDCAP_DISABLE_ONRAMPS(s)
Definition: player.qh:126
#define PHYS_JETPACK_FUEL(s)
Definition: player.qh:121
#define PHYS_DODGING_FROZEN(s)
Definition: player.qh:108
#define PHYS_AIRCONTROL_SIDEWARDS(s)
Definition: player.qh:100
float jumppadcount
Definition: jumppads.qh:15
void PM_ClientMovement_UpdateStatus(entity this)
Definition: player.qc:122
float Q3SURFACEFLAG_SLICK
bool wasFlying
Definition: player.qh:62
float Q3SURFACEFLAG_METALSTEPS
origin
Definition: ent_cs.qc:114
float autocvar_g_balance_pause_fuel_regen
Definition: sv_resources.qh:33
#define UNSET_DUCKED(s)
Definition: player.qh:208
float IsMoveInDirection(vector mv, float ang)
Definition: player.qc:106
#define strhasword(s, w)
Definition: string.qh:352
vector punchangle
#define UNSET_ONGROUND(s)
Definition: movetypes.qh:18
#define ITEMS_STAT(s)
Definition: player.qh:210
#define PHYS_AIRCONTROL_POWER(s)
Definition: player.qh:98
#define IS_REAL_CLIENT(v)
Definition: utils.qh:17
void PM_AirAccelerate(entity this, float dt, vector wishdir, float wishspeed)
Definition: player.qc:287
#define PHYS_FROZEN(s)
Definition: player.qh:114
void TakeResource(entity receiver, Resource res_type, float amount)
Takes an entity some resource.
Definition: cl_resources.qc:31
bool maycheat
Definition: cheats.qc:43
void PM_check_punch(entity this, float dt)
Definition: player.qc:585
#define UNSET_JUMP_HELD(s)
Definition: player.qh:201
float Q3SURFACEFLAG_NOSTEPS
#define BIT(n)
Only ever assign into the first 24 bits in QC (so max is BIT(23)).
Definition: bits.qh:8
int q3compat
Definition: quake3.qh:3
vector movedir
Definition: progsdefs.qc:203
#define PHYS_JETPACK_ANTIGRAVITY(s)
Definition: player.qh:120
const float MOVE_NOMONSTERS
Definition: csprogsdefs.qc:253
vector mins
Definition: csprogsdefs.qc:113
bool PM_check_specialcommand(entity this, int buttons)
Definition: player.qc:530
#define PHYS_WARSOWBUNNY_TURNACCEL(s)
Definition: player.qh:142
int csqcmodel_modelflags
void PM_Footsteps(entity this)
Definition: player.qc:649
vector punchvector
float GeomLerp(float a, float _lerp, float b)
Definition: player.qc:115
spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 f1 s1 strcat(_("Level %s: "), "^BG%s\3\, _("^BGPress ^F2%s^BG to enter the game"))
const int MAX_WEAPONSLOTS
Definition: weapon.qh:13
#define PHYS_INPUT_BUTTON_USE(s)
Definition: player.qh:154
void animdecide_setaction(entity e, float action, float restart)
Definition: animdecide.qc:338
float fixangle
Definition: progsdefs.qc:160
void PM_check_frozen(entity this)
Definition: player.qc:609
#define SET_ONSLICK(s)
Definition: movetypes.qh:20
const int ACTIVE_ACTIVE
Definition: defs.qh:37
bool autocvar_sv_q3compat_changehitbox
Definition: quake3.qh:7
float frametime
Definition: csprogsdefs.qc:17
#define LOG_INFO(...)
Definition: log.qh:70
const float VOL_BASE
Definition: sound.qh:36
void PM_jetpack(entity this, float maxspd_mod, float dt)
Definition: player.qc:695
#define PHYS_INPUT_BUTTON_ATCK(s)
Definition: player.qh:146
void PM_check_blocked(entity this)
Definition: player.qc:683
float disableclientprediction
#define M_ARGV(x, type)
Definition: events.qh:17
#define IS_DEAD(s)
Definition: utils.qh:26
int autocvar_sv_cheats
Definition: cheats.qh:5
#define PHYS_JETPACK_ACCEL_UP(s)
Definition: player.qh:119
bool IsFlying(entity this)
Definition: player.qc:804
#define SET_JUMP_HELD(s)
Definition: player.qh:200
vector movement
entity ladder_entity
Definition: ladder.qh:11
void PM_UpdateButtons(entity this, entity store)
Definition: client.qc:2776
#define SET_DUCKED(s)
Definition: player.qh:207
vector(float skel, float bonenum) _skel_get_boneabs_hidden
#define PHYS_JUMPVELOCITY(s)
Definition: player.qh:127
entity viewloc
Definition: viewloc.qh:13
float AdjustAirAccelQW(float accelqw, float factor)
Definition: player.qc:213
#define PHYS_JETPACK_MAXSPEED_SIDE(s)
Definition: player.qh:122
#define PHYS_WARSOWBUNNY_ACCEL(s)
Definition: player.qh:138
float flags
Definition: csprogsdefs.qc:129
float RAD2DEG
Definition: csprogsdefs.qc:962
#define vdist(v, cmp, f)
Vector distance comparison, avoids sqrt()
Definition: vector.qh:8
#define LOG_TRACE(...)
Definition: log.qh:81
#define PHYS_AIRCONTROL(s)
Definition: player.qh:96
float nickspamtime
Definition: client.qh:320
bool PlayerJump(entity this)
Definition: player.qc:330
const int VIEWLOC_FREEMOVE
Definition: viewloc.qh:6
float remainder(float e, float f)
Definition: mathlib.qc:212
#define PHYS_INPUT_BUTTON_JETPACK(s)
Definition: player.qh:158
vector v_right
Definition: csprogsdefs.qc:31
const int WATERLEVEL_SWIMMING
Definition: movetypes.qh:13
const int CH_PLAYER
Definition: sound.qh:20
#define PHYS_HIGHSPEED(s)
Definition: player.qh:116
#define MUTATOR_CALLHOOK(id,...)
Definition: base.qh:140
bool player_blocked
Definition: client.qh:337
#define vec2(...)
Definition: vector.qh:90
entity weaponentities[MAX_WEAPONSLOTS]
Definition: weapon.qh:14
float pauseregen_finished
Definition: client.qh:342
vector trace_plane_normal
Definition: csprogsdefs.qc:38
#define PHYS_AIRCONTROL_BACKWARDS(s)
Definition: player.qh:99
float trace_startsolid
Definition: csprogsdefs.qc:35
best
Definition: all.qh:77
float jetpack_stopped
Definition: player.qc:472
#define IS_JUMP_HELD(s)
Definition: player.qh:199
void sys_phys_update(entity this, float dt)
Definition: physics.qc:11
#define PHYS_JETPACK_ACCEL_SIDE(s)
Definition: player.qh:118
float time
Definition: csprogsdefs.qc:16
#define PHYS_AIRCONTROL_PENALTY(s)
Definition: player.qh:97
vector velocity
Definition: csprogsdefs.qc:103
void CheckWaterJump(entity this)
Definition: player.qc:439
#define PHYS_MAXSPEED(s)
Definition: player.qh:132
#define makevectors
Definition: post.qh:21
float trace_fraction
Definition: csprogsdefs.qc:36
#define PHYS_JUMPVELOCITY_CROUCH(s)
Definition: player.qh:128
#define boolean(value)
Definition: bool.qh:9
#define PHYS_INPUT_BUTTON_MINIGAME(s)
Definition: player.qh:160
#define IS_PLAYER(v)
Definition: utils.qh:9
#define PHYS_TRACK_CANJUMP(s)
Definition: player.qh:136
float FL_WATERJUMP
Definition: progsdefs.qc:242
void PM_check_nickspam(entity this)
Definition: player.qc:563
vector v_forward
Definition: csprogsdefs.qc:31
#define PHYS_FRICTION_ONLAND(s)
Definition: player.qh:111