Xonotic QuakeC
The free, fast arena FPS with crisp movement and a wide array of weapons
strafehud.qc
Go to the documentation of this file.
1// Author: Juhu
2
3#include "strafehud.qh"
4
5#include <client/draw.qh>
9#include "racetimer.qh"
10
11// non-essential
12#include <client/view.qh> // for v_flipped state
13
14// StrafeHUD (#25)
15
17{
18 // allow saving cvars that aesthetically change the panel into hud skin files
19}
20
21void HUD_StrafeHUD(bool should_draw)
22{
23 static float hud_lasttime = 0;
24
25 // generic hud routines
26 if (!should_draw)
27 return;
29 {
32 || (autocvar_hud_panel_strafehud == 3 && !MUTATOR_CALLHOOK(HUD_StrafeHUD_showoptional)))
33 {
34 hud_lasttime = time;
35 return;
36 }
37 }
38
40
43 else
45
47
49 {
50 panel_pos += '1 1 0' * panel_bg_padding;
51 panel_size -= '2 2 0' * panel_bg_padding;
52 }
53
54 bool is_local = !(spectatee_status > 0 || isdemo());
55 entity strafeplayer = StrafeHUD_GetStrafeplayer(is_local);
56
57 if (!csqcplayer || !strafeplayer)
58 {
59 hud_lasttime = time;
60 return;
61 }
62
63 // draw strafehud
64
65 int keys = STAT(PRESSED_KEYS);
66 bool jumpheld = StrafeHUD_DetermineJumpHeld(strafeplayer, keys, is_local);
67
68 // does not get changed by ground timeout and is not affected by jump input
69 bool real_onground = StrafeHUD_DetermineOnGround(strafeplayer, is_local);
70
71 // does not get changed by ground timeout
72 bool real_onslick = false;
73
74 // if jump is held assume we are in air, avoids flickering of the hud when hitting the ground
75 bool onground = real_onground && !jumpheld;
76 bool onslick = real_onslick;
77
78 // the hud will not work well while swimming
79 float strafe_waterlevel = StrafeHUD_DetermineWaterLevel(strafeplayer);
80 bool swimming = strafe_waterlevel >= WATERLEVEL_SWIMMING;
81
82 static float onground_lasttime = 0;
83 static bool onslick_last = false;
84 if (onground)
85 {
86 real_onslick = onslick = StrafeHUD_DetermineOnSlick(strafeplayer);
87
88 onground_lasttime = time;
89 onslick_last = onslick;
90 }
91 else if (jumpheld || swimming)
92 onground_lasttime = 0;
93
94 bool onground_expired;
95 if (onground_lasttime == 0)
96 onground_expired = true;
97 else
98 onground_expired = (time - onground_lasttime) >= autocvar_hud_panel_strafehud_timeout_ground; // timeout for slick ramps
99
100 // only the local csqcplayer entity contains this information even when spectating
101 float maxspeed_mod = IS_DUCKED(csqcplayer) ? 0.5 : 1;
102 float maxspeed_phys = onground ? PHYS_MAXSPEED(strafeplayer) : PHYS_MAXAIRSPEED(strafeplayer);
103 float maxspeed = !autocvar__hud_configure ? maxspeed_phys * maxspeed_mod : 320;
104 float maxaccel_phys = onground ? PHYS_ACCELERATE(strafeplayer) : PHYS_AIRACCELERATE(strafeplayer);
105 float maxaccel = !autocvar__hud_configure ? maxaccel_phys : 1;
106
107 if (!onground && !onground_expired) // if ground timeout has not expired yet use ground physics
108 {
109 onground = true;
110 onslick = onslick_last;
111
113 {
114 maxspeed = PHYS_MAXSPEED(strafeplayer) * maxspeed_mod;
115 maxaccel = PHYS_ACCELERATE(strafeplayer);
116 }
117 }
118 else if (onslick)
119 {
121 maxaccel = PHYS_SLICKACCELERATE(strafeplayer);
122 }
123
124 // move values are only valid for the local player
125 vector movement = PHYS_INPUT_MOVEVALUES(strafeplayer);
126
127 float movespeed;
128 if (is_local)
129 {
130 movespeed = min(vlen(vec2(movement)), maxspeed);
131
132 // assume maxspeed so that the hud remains useful even if no direction keys are pressed
133 if (movespeed == 0)
134 movespeed = maxspeed;
135 }
136 else
137 {
138 // the only information available is whether a movement key is pressed or not
139 // which means the movespeed would be either maxspeed or zero
140 // since we set it to maxspeed if it is zero the movespeed will always equal maxspeed if the player is not local
141 movespeed = maxspeed;
142 }
143
144 // the following functions have to check themselves whether the player is local and use the move values accordingly
145 int keys_fwd = StrafeHUD_DetermineForwardKeys(movement, keys, is_local);
146 float wishangle = StrafeHUD_DetermineWishAngle(movement, keys, is_local);
147 float absolute_wishangle = fabs(wishangle); // unmodified by side strafing code
148 bool strafekeys = fabs(wishangle) > 45;
149
150 // detect air strafe turning
151 static bool turn = false;
152 float strafity = 0;
153 if (!strafekeys || onground || autocvar__hud_configure)
154 turn = false;
155 else // air strafe only
156 {
157 static float turn_lasttime = 0;
158 static float turnangle;
159 bool turn_expired = (time - turn_lasttime) >= autocvar_hud_panel_strafehud_timeout_turn; // timeout for jumping with strafe keys only
160
161 if (strafekeys)
162 turn = true;
163 else if (turn_expired)
164 turn = false;
165
166 if (turn) // side strafing (A/D)
167 {
168 if (strafekeys)
169 {
170 turn_lasttime = time;
171 turnangle = wishangle;
172 }
173 else // retain last state until strafe turning times out
174 wishangle = turnangle;
175
176 // calculate the maximum air strafe speed and acceleration
177 strafity = 1 - (90 - fabs(wishangle)) / 45;
178
179 if (PHYS_MAXAIRSTRAFESPEED(strafeplayer) != 0)
180 maxspeed = min(maxspeed, GeomLerp(PHYS_MAXAIRSPEED(strafeplayer), strafity, PHYS_MAXAIRSTRAFESPEED(strafeplayer)));
181
182 movespeed = min(movespeed, maxspeed);
183
184 if (PHYS_AIRSTRAFEACCELERATE(strafeplayer) != 0)
185 maxaccel = GeomLerp(PHYS_AIRACCELERATE(strafeplayer), strafity, PHYS_AIRSTRAFEACCELERATE(strafeplayer));
186 }
187 }
188
189 float dt = StrafeHUD_DetermineFrameTime();
190
191 maxaccel *= dt * movespeed;
192 float bestspeed = max(movespeed - maxaccel, 0); // target speed to gain maximum acceleration
193
194 // use local csqcmodel entity for this even when spectating, flickers too much otherwise
195 vector strafevelocity = csqcplayer.velocity;
196
197 float speed = !autocvar__hud_configure ? vlen(vec2(strafevelocity)) : 1337;
198 bool moving = speed > 0;
199
200 float frictionspeed; // speed lost from friction
201 float strafespeed; // speed minus friction
202
203 if (moving && onground)
204 {
205 float strafefriction = onslick ? PHYS_FRICTION_SLICK(strafeplayer) : PHYS_FRICTION(strafeplayer);
206
207 if (strafefriction > 0)
208 {
209 float S = PHYS_STOPSPEED(strafeplayer);
210 //strafespeed = speed * (1 - dt * strafefriction * ((speed < S) ? (S / speed) : 1));
211 // NOTE: this is copied from ecs/systems/physics.qc
212 float dt_r = PHYS_FRICTION_REPLICA_DT;
213 //if (strafefriction * dt_r >= 1)
214 // strafespeed = 0;
215 //else
216 {
217 float independent_geometric = (1 - strafefriction * dt_r) ** (dt / dt_r);
218 if (S < speed && speed < S / independent_geometric)
219 strafespeed = S - S * strafefriction * (dt - (dt_r * log(S / speed)) / log(1 - strafefriction * dt_r));
220 else if (speed >= S)
221 strafespeed = speed * independent_geometric;
222 else
223 strafespeed = speed - strafefriction * dt * S;
224 strafespeed = max(0, strafespeed);
225 }
226 }
227 else
228 strafespeed = speed;
229 frictionspeed = speed - strafespeed;
230 }
231 else
232 {
233 frictionspeed = 0;
234 strafespeed = speed;
235 }
236
237 // get current strafing angle ranging from -180° to +180°
238 float angle;
239 bool fwd; // left & right variables are flipped when !fwd
240
242 {
243 if (moving)
244 {
245 // change the range from 0° - 360° to -180° - 180° to match how view_angle represents angles
246 float vel_angle = vectoangles(strafevelocity).y;
247 if (vel_angle > 180)
248 vel_angle -= 360;
249 float view_angle = PHYS_INPUT_ANGLES(strafeplayer).y;
250
251 // calculate view angle relative to the players current velocity direction
252 angle = vel_angle - view_angle;
253
254 // if the angle goes above 180° or below -180° wrap it to the opposite side since we want the interior angle
255 if (angle > 180)
256 angle -= 360;
257 else if (angle < -180)
258 angle += 360;
259
260 // determine whether the player is strafing forwards or backwards
261 // if the player is not strafe turning use forwards/backwards keys to determine direction
262 if (fabs(wishangle) != 90)
263 {
264 if (keys_fwd == STRAFEHUD_KEYS_FORWARD)
265 fwd = true;
266 else if (keys_fwd == STRAFEHUD_KEYS_BACKWARD)
267 fwd = false;
268 else
269 fwd = fabs(angle) <= 90;
270 }
271 // otherwise determine by examining the strafe angle
272 else
273 {
274 if (wishangle < 0) // detect direction using wishangle since the direction is not yet set
275 fwd = angle <= -wishangle;
276 else
277 fwd = angle >= -wishangle;
278 }
279
280 // shift the strafe angle by 180° when strafing backwards
281 if (!fwd)
282 {
283 if (angle < 0)
284 angle += 180;
285 else
286 angle -= 180;
287 }
288 }
289 else
290 {
291 angle = 0;
292 fwd = true;
293 }
294 }
295 else // simulate turning for HUD setup
296 {
297 const float demo_maxangle = 55; // maximum angle before changing direction
298 const float demo_turnspeed = 40; // turning speed in degrees per second
299 static float demo_position = -37 / demo_maxangle; // current positioning value between -1 and +1
300
302 {
303 float demo_dt = time - hud_lasttime;
304 float demo_step = (demo_turnspeed / demo_maxangle) * demo_dt;
305 demo_position = ((demo_position + demo_step) % 4 + 4) % 4;
306 }
307
308 // triangle wave function
309 if (demo_position > 3)
310 angle = -1 + (demo_position - 3);
311 else if (demo_position > 1)
312 angle = +1 - (demo_position - 1);
313 else
314 angle = demo_position;
315 angle *= demo_maxangle;
316
317 fwd = true;
318 wishangle = 45;
319 if (angle < 0)
320 wishangle *= -1;
321 }
322
323 // invert the wish angle when strafing backwards
324 if (!fwd)
325 wishangle *= -1;
326
327 // flip angles if v_flipped is enabled
329 {
330 angle *= -1;
331 wishangle *= -1;
332 }
333
334 float airstopaccel = PHYS_AIRSTOPACCELERATE(strafeplayer);
335 if (airstopaccel == 0 || turn) // no airstopaccel while side strafing
336 airstopaccel = 1; // values of 0 are equivalent to 1
337
338 // best angle to strafe at
339 // k9er: documentation here https://gitlab.com/otta8634/xonotic-physics
340 float bestangle;
341 float prebestangle;
342 float overturnangle;
343 if (!moving)
344 {
345 // these are unused (neutral fills whole strafe bar)
346 prebestangle = bestangle = 0;
347 overturnangle = 180;
348 }
350 {
351 // draw ground angles
352 {
353 // delta_opt = acos((s - a) / v_f), same in air
354 bestangle = strafespeed > bestspeed
355 ? acos(bestspeed / strafespeed) * RAD2DEG // case 1
356 : 0; // case 2
357 // case 1: normal. case 2: low speed, best angle is forwards
358 }
359 {
360 // needed later if autocvar_hud_panel_strafehud_wturn != STRAFEHUD_WTURN_NONE,
361 // ... so calculate even if autocvar_hud_panel_strafehud_bar_preaccel == 0
362 float prebestangle_sqrt = movespeed * movespeed + strafespeed * strafespeed - speed * speed;
363 // delta_min = acos(sqrt(s^2 - v_f^2 + v^2) / v_f), or just acos(s / v) in air
364 prebestangle = (prebestangle_sqrt > 0 && strafespeed > sqrt(prebestangle_sqrt))
365 ? acos(sqrt(prebestangle_sqrt) / strafespeed) * RAD2DEG // case 1
366 : (prebestangle_sqrt > 0 ? 0 : 90); // case 2 : case 3
367 // case 1: normal. case 2: low speed, best angle is forwards. case 3: landed at high speed, neutral zone is very large (see explanation below)
368 }
369 {
370 float overturn_numer = speed * speed - strafespeed * strafespeed - maxaccel * maxaccel;
371 float overturn_denom = 2 * maxaccel * strafespeed;
372 // delta_max = acos((v^2 - v_f^2 - a^2) / (2av_f)), or just acos(-a / 2v) if in air
373 overturnangle = overturn_denom > fabs(overturn_numer)
374 ? acos(overturn_numer / overturn_denom) * RAD2DEG // case 1
375 : (overturn_numer < 0 ? 180 : 0); // case 2 : case 3
376 // case 1: normal. case 2: low speed, turning anywhere will gain speed. case 3: landed at high speed, turning anywhere will lose speed (due to friction)
377 }
378 if (overturnangle < bestangle || bestangle < prebestangle)
379 {
380 // these conditions occur when you land at high speed (above max onground speed), such that every wishangle will result in a speed loss due to friction
382 {
383 // make overturn fill the whole strafe bar
384 // most correct option by the true definition of accel, since every angle results in deceleration
385 prebestangle = bestangle = 0;
386 overturnangle = 0;
387 }
389 {
390 /* k9er: these aren't the true angles -- the real ones are very convoluted and difficult to understand
391 * essentially the prior definitions of the zones now overlap,
392 * ... with the overturn zone extending below bestangle, and eventually covering the whole hud
393 * ... and somehow the neutral zone extends above bestangle, and eventually covers the whole hud (i think)
394 * overall showing it accurately is just confusing and unnecessary to add
395 * thankfully the bestangle formula is unchanged, so the least confusing option is likely as follows:
396 */
397 overturnangle = bestangle;
398 prebestangle = bestangle;
399 }
400 else
401 {
402 // use angles as if in air (see below), no range checks are needed
403 // no need to check if numerator < denominator, since all numerators < max onground speed < speed = all denominators
404 bestangle = acos(bestspeed / speed) * RAD2DEG;
405 prebestangle = acos(movespeed / speed) * RAD2DEG;
406 overturnangle = (airstopaccel == 1 || PHYS_AIRSTOPACCELERATE_FULL(strafeplayer))
407 ? acos(-(airstopaccel * maxaccel * 0.5) / speed) * RAD2DEG
408 : acos(-maxaccel / (2 * speed - (airstopaccel - 1) * maxaccel)) * RAD2DEG;
409 }
410 }
411 }
412 else
413 {
414 // draw airborne angles. see above for documentation
415 bestangle = speed > bestspeed
416 ? acos(bestspeed / speed) * RAD2DEG
417 : 0;
418 prebestangle = speed > movespeed
419 ? acos(movespeed / speed) * RAD2DEG
420 : 0;
421 // with airstopaccel, delta_max = acos(airstopaccel * -a / 2v), only in air,
422 // ... or if not applied fully, delta_max = acos(-a / (2v - (airstopaccel - 1) * a))
423 overturnangle = speed > airstopaccel * maxaccel * 0.5
424 ? ((airstopaccel == 1 || PHYS_AIRSTOPACCELERATE_FULL(strafeplayer))
425 ? acos(-(airstopaccel * maxaccel * 0.5) / speed) * RAD2DEG
426 : acos(-maxaccel / (2 * speed - (airstopaccel - 1) * maxaccel)) * RAD2DEG)
427 : 180;
428 }
429 // absolute_* variables which are always positive with no wishangle offset
430 float absolute_bestangle = bestangle;
431 float absolute_prebestangle = prebestangle;
432 float absolute_overturnangle = overturnangle;
433
434 float aircontrol = PHYS_AIRCONTROL(strafeplayer);
435 int aircontrol_flags = PHYS_AIRCONTROL_FLAGS(strafeplayer);
436 bool is_aircontrol_keys = (keys_fwd == STRAFEHUD_KEYS_FORWARD)
437 || ((aircontrol_flags & BIT(0)) && keys_fwd == STRAFEHUD_KEYS_BACKWARD);
438 bool is_aircontrol_direction = fwd || (aircontrol_flags & BIT(0));
439 bool airaccel_qw = PHYS_AIRACCEL_QW(strafeplayer) == 1;
440
441 /*
442 * k9er: proper W-turn angle assuming sv_aircontrol_power == 2 is acos(-speed/a * (cos((acos(V) + M_PI * 2) / 3) * 2 + 1)) rad,
443 * ... where a=dt*32*aircontrol, and V=1-(a*a)/(speed*speed),
444 * ... but this very quickly loses accuracy -- should be a strictly decreasing function, yet it increases at only speed=722 with 125 fps
445 * also note this is only valid when such angle is not in the accelzone, formula taking acceleration into account is unfathomably complicated
446 * afaik there's no simplified version of this formula that doesn't involve complex numbers, other than one valid for only speed<27.1 roughly
447 * furthermore, this function quite rapidly approaches its asymptote of ~35.26, e.g. being ~0.68 away when at only speed=600
448 * this asymptote is independent of whether the player is crouching or has haste, although they must be airborne
449 * thus, the best option is to just draw the asymptote (acos(sqrt(2/3))),
450 * ... but the proper angle can be drawn too if the player wants (hud_panel_strafehud_wturn_proper 1)
451 * this is only enabled if sv_airaccel_qw == 1 since otherwise W-turning gives acceleration, unless hud_panel_strafehud_wturn_unrestricted 1
452 * when sv_aircontrol_power != 2 (abbr. "p"), the asymptote is instead acos(sqrt(p/(p+1))). full formula is too difficult to calculate,
453 * ... so the angle will only be shown with hud_panel_strafehud_wturn_proper 0
454 * this doesn't have support for sv_aircontrol_sideways == 1
455 */
456 bool wturning = (wishangle == 0) && !onground && is_aircontrol_keys;
457 bool wturn_valid = false;
458 float wturn_bestangle = 0;
460 && aircontrol && PHYS_AIRCONTROL_PENALTY(strafeplayer) == 0
462 {
463 float wturn_power = PHYS_AIRCONTROL_POWER(strafeplayer);
464 if (wturn_power == 2)
465 {
466 float wturn_a = 32 * fabs(aircontrol) * dt;
467 if (aircontrol_flags & BIT(2)) // crouching has no impact
468 wturn_a *= maxspeed / maxspeed_phys;
469 float wturn_V = 1 - (wturn_a * wturn_a) / (speed * speed);
470 if (autocvar_hud_panel_strafehud_wturn_proper && wturn_a > 1 && wturn_V < 1 && wturn_V > -1)
471 wturn_bestangle = acos(-speed / wturn_a * (cos((acos(wturn_V) + M_PI * 2) / 3) * 2 + 1)) * RAD2DEG;
472 else
473 wturn_bestangle = ACOS_SQRT2_3_DEG;
474 wturn_valid = true;
475 }
476 else if (!autocvar_hud_panel_strafehud_wturn_proper && wturn_power >= 0)
477 {
478 wturn_bestangle = acos(sqrt(wturn_power / (wturn_power + 1))) * RAD2DEG;
479 wturn_valid = true;
480 }
481 }
482 float absolute_wturn_bestangle = wturn_bestangle;
483
484 // draw the switch indicators as if strafing normally (W+A style), while W-turning or side strafing
485 float n_bestangle = 0;
486 float absolute_n_prebestangle = 0; // also needed for W-turn angles
487 bool draw_normal = ((autocvar_hud_panel_strafehud_switch >= STRAFEHUD_SWITCH_NORMAL && wturning)
489 if (draw_normal || wturn_valid)
490 {
491 // recalculate bestangle as if strafing normally
492 float n_maxspeed = PHYS_MAXAIRSPEED(strafeplayer) * maxspeed_mod;
493 float n_movespeed = n_maxspeed;
494 float n_maxaccel = PHYS_AIRACCELERATE(strafeplayer) * dt * n_movespeed;
495 float n_bestspeed = max(n_movespeed - n_maxaccel, 0);
496 n_bestangle = speed > n_bestspeed
497 ? acos(n_bestspeed / speed) * RAD2DEG - 45
498 : -45;
499 absolute_n_prebestangle = speed > n_movespeed
500 ? acos(n_movespeed / speed) * RAD2DEG
501 : 0;
502 }
503
504 float hudangle = StrafeHUD_DetermineHudAngle(absolute_wishangle, absolute_overturnangle, strafity);
505
506 float antiflicker_angle = bound(0, autocvar_hud_panel_strafehud_antiflicker_angle, 180);
507 float direction = StrafeHUD_DetermineDirection(angle, wishangle, antiflicker_angle);
508
509 if (direction == STRAFEHUD_DIRECTION_LEFT) // the angle becomes negative in case we strafe left
510 {
511 n_bestangle *= -1;
512 bestangle *= -1;
513 prebestangle *= -1;
514 overturnangle *= -1;
515 }
516 float opposite_bestangle = -bestangle;
517 float n_opposite_bestangle = -n_bestangle;
518
519 bestangle -= wishangle;
520 opposite_bestangle -= wishangle;
521 n_opposite_bestangle -= wishangle;
522 prebestangle -= wishangle;
523 overturnangle -= wishangle;
524
525 int mode;
528 else
530
531 // best strafe acceleration angle
532 float changeangle = -bestangle;
533 float n_changeangle = -n_bestangle;
534 float n_opposite_changeangle = n_opposite_bestangle + n_bestangle * 2;
535
536 // minimum speed for change indicators
538 if (minspeed < 0)
539 minspeed = bestspeed + frictionspeed;
540
541 bool opposite_direction = false;
542 float opposite_changeangle = 0;
543 if ((angle > -wishangle && direction == STRAFEHUD_DIRECTION_LEFT)
544 || (angle < -wishangle && direction == STRAFEHUD_DIRECTION_RIGHT))
545 {
546 opposite_direction = true;
547 opposite_changeangle = opposite_bestangle + bestangle * 2;
548 }
549
550 // best angle to aim at when W-turning to maximally rotate velocity vector
551 float wturn_left_bestangle = wturn_bestangle;
552 float wturn_right_bestangle = -wturn_bestangle;
553
554 // shift hud if operating in view angle centered mode
555 float shiftangle = 0;
557 {
558 shiftangle = -angle;
559 bestangle += shiftangle;
560 changeangle += shiftangle;
561 opposite_bestangle += shiftangle;
562 opposite_changeangle += shiftangle;
563 n_bestangle += shiftangle;
564 n_changeangle += shiftangle;
565 n_opposite_bestangle += shiftangle;
566 n_opposite_changeangle += shiftangle;
567 wturn_left_bestangle += shiftangle;
568 wturn_right_bestangle += shiftangle;
569 }
570
571 StrafeHUD_DrawStrafeMeter(shiftangle, wishangle, absolute_bestangle, absolute_prebestangle, absolute_overturnangle, moving, hudangle);
572
573 float text_offset_top;
574 float text_offset_bottom;
575 bool all_slick = PHYS_FRICTION(strafeplayer) == 0;
576 text_offset_top = text_offset_bottom = StrafeHUD_DrawSlickDetector(strafeplayer, (all_slick && real_onground) || real_onslick);
577
579 StrafeHUD_DrawDirectionIndicator(direction, opposite_direction, fwd);
580
581 // determine the strafing ratio and the angle indicator color
583 float strafe_ratio = 0;
584 if (moving)
585 {
586 float moveangle = fabs(angle + wishangle);
587 if (moveangle > 180) moveangle = 360 - moveangle; // restricted to between 0 and 180
588
589 // player is overturning
590 if (moveangle >= absolute_overturnangle)
591 {
592 if (moveangle == absolute_overturnangle && absolute_overturnangle == 180)
593 {} // everywhere gives acceleration, keep strafe_ratio as 0
594 else
595 {
597 strafe_ratio = (moveangle - absolute_overturnangle) / (180 - absolute_overturnangle);
598 // moveangle is always <= 180, so this code won't run if absolute_overturnangle == 180
599 strafe_ratio *= -1;
600 }
601 }
602 // player gains speed by strafing
603 else if (moveangle >= absolute_bestangle)
604 {
606 strafe_ratio = (absolute_overturnangle - moveangle) / (absolute_overturnangle - absolute_bestangle);
607 // if absolute_overturnangle == absolute_bestangle, this code won't run, no need to check if their difference is 0
608 }
609 else if (moveangle >= absolute_prebestangle)
610 {
613 strafe_ratio = (moveangle - absolute_prebestangle) / (absolute_bestangle - absolute_prebestangle);
614 }
615
617 currentangle_color = StrafeHUD_MixColors(
619 currentangle_color, fabs(strafe_ratio));
620 }
621
622 float currentangle = 0;
624 {
625 // avoid switching side too much at ±180° if anti flicker is triggered
626 if (fabs(angle) <= 180 - antiflicker_angle)
627 currentangle = angle;
628 }
629
630 float max_line_height = 0;
631 float max_top_arrow_size = 0;
632 float max_bottom_arrow_size = 0;
633
634 // only draw switch indicators if minspeed is reached
636 {
637 // change angle indicator style
638 vector indicator_size;
641 indicator_size.z = 0;
642
645 bool has_bottom_arrow = autocvar_hud_panel_strafehud_switch_arrow >= 2;
646
647 // there's only one size cvar for the arrows, they will always have a 45° angle to ensure proper rendering without antialiasing
649
650 if (num_dashes > 0)
651 max_line_height = max(max_line_height, indicator_size.y);
652 if (has_top_arrow)
653 max_top_arrow_size = max(max_top_arrow_size, arrow_size);
654 if (has_bottom_arrow)
655 max_bottom_arrow_size = max(max_bottom_arrow_size, arrow_size);
656
657 // draw the change indicator(s)
658 float current_changeangle = draw_normal
659 ? (opposite_direction ? n_opposite_changeangle : n_changeangle)
660 : (opposite_direction ? opposite_changeangle : changeangle);
661 float opposite_changeangle = draw_normal
662 ? (opposite_direction ? n_opposite_bestangle : n_bestangle)
663 : (opposite_direction ? opposite_bestangle : bestangle);
664
666 current_changeangle, indicator_size, arrow_size, num_dashes,
667 has_top_arrow, has_bottom_arrow, autocvar_hud_panel_strafehud_switch_color,
669
670 if (direction == STRAFEHUD_DIRECTION_NONE || draw_normal)
672 opposite_changeangle, indicator_size, arrow_size, num_dashes,
673 has_top_arrow, has_bottom_arrow, autocvar_hud_panel_strafehud_switch_color,
675 }
676
679 {
680 // best angle indicator style
681 vector indicator_size;
684 indicator_size.z = 0;
685
688 bool has_bottom_arrow = autocvar_hud_panel_strafehud_bestangle_arrow >= 2;
689
690 // there's only one size cvar for the arrows, they will always have a 45° angle to ensure proper rendering without antialiasing
692
693 if (num_dashes > 0)
694 max_line_height = max(max_line_height, indicator_size.y);
695 if (has_top_arrow)
696 max_top_arrow_size = max(max_top_arrow_size, arrow_size);
697 if (has_bottom_arrow)
698 max_bottom_arrow_size = max(max_bottom_arrow_size, arrow_size);
699
700 float ghostangle = opposite_direction ? opposite_bestangle : bestangle;
701
703 ghostangle, indicator_size, arrow_size, num_dashes,
704 has_top_arrow, has_bottom_arrow, autocvar_hud_panel_strafehud_bestangle_color,
706 }
707
708 // only draw wturn indicators if conditions were met
709 if (wturn_valid && !onground && is_aircontrol_direction
711 && absolute_wturn_bestangle < absolute_n_prebestangle
712 && ((autocvar_hud_panel_strafehud_wturn && wturning)
715 {
716 // wturn angle indicator style
717 vector indicator_size;
718 indicator_size.x = max(panel_size.x * min(autocvar_hud_panel_strafehud_wturn_line_width, 10), 1);
720 indicator_size.z = 0;
721
724 bool has_bottom_arrow = autocvar_hud_panel_strafehud_wturn_arrow >= 2;
725
726 // there's only one size cvar for the arrows, they will always have a 45° angle to ensure proper rendering without antialiasing
727 float arrow_size = max(panel_size.y * min(autocvar_hud_panel_strafehud_wturn_arrow_size, 10), 1);
728
729 if (num_dashes > 0)
730 max_line_height = max(max_line_height, indicator_size.y);
731 if (has_top_arrow)
732 max_top_arrow_size = max(max_top_arrow_size, arrow_size);
733 if (has_bottom_arrow)
734 max_bottom_arrow_size = max(max_bottom_arrow_size, arrow_size);
735
736 // draw the wturn indicators
738 wturn_left_bestangle, indicator_size, arrow_size, num_dashes,
739 has_top_arrow, has_bottom_arrow, autocvar_hud_panel_strafehud_wturn_color,
742 wturn_right_bestangle, indicator_size, arrow_size, num_dashes,
743 has_top_arrow, has_bottom_arrow, autocvar_hud_panel_strafehud_wturn_color,
745 }
746
748 {
749 // current angle indicator style
750 vector indicator_size;
751 indicator_size.x = max(panel_size.x * min(autocvar_hud_panel_strafehud_angle_line_width, 10), 1);
753 indicator_size.z = 0;
754
757 bool has_bottom_arrow = autocvar_hud_panel_strafehud_angle_arrow >= 2;
758
759 // there's only one size cvar for the arrows, they will always have a 45° angle to ensure proper rendering without antialiasing
760 float arrow_size = max(panel_size.y * min(autocvar_hud_panel_strafehud_angle_arrow_size, 10), 1);
761
762 if (num_dashes > 0)
763 max_line_height = max(max_line_height, indicator_size.y);
764 if (has_top_arrow)
765 max_top_arrow_size = max(max_top_arrow_size, arrow_size);
766 if (has_bottom_arrow)
767 max_bottom_arrow_size = max(max_bottom_arrow_size, arrow_size);
768
770 currentangle, indicator_size, arrow_size, num_dashes,
771 has_top_arrow, has_bottom_arrow, currentangle_color,
773 }
774
775 // offset text by amount the angle indicator extrudes from the strafehud bar
776 {
777 float line_height_offset = max_line_height;
778
779 // amount line extrudes from the strafehud bar
780 line_height_offset = (line_height_offset - panel_size.y) / 2;
781
782 // further offset the top text offset if the top arrow is drawn
783 float angle_offset_top = line_height_offset + max_top_arrow_size;
784
785 // further offset the bottom text offset if the bottom arrow is drawn
786 float angle_offset_bottom = line_height_offset + max_bottom_arrow_size;
787
788 // make sure text does not draw inside the strafehud bar
789 text_offset_top = max(angle_offset_top, text_offset_top);
790 text_offset_bottom = max(angle_offset_bottom, text_offset_bottom);
791 }
792
793 StrafeHUD_DrawVerticalAngle(strafeplayer, text_offset_top, text_offset_bottom);
794
796 StrafeHUD_DrawStartSpeed(race_timespeed, text_offset_top, text_offset_bottom);
797 StrafeHUD_DrawStrafeEfficiency(strafe_ratio, text_offset_top, text_offset_bottom);
798 StrafeHUD_DrawJumpHeight(strafeplayer, real_onground, swimming, text_offset_top, text_offset_bottom);
800
802
803 hud_lasttime = time;
804}
#define MUTATOR_CALLHOOK(id,...)
Definition base.qh:143
#define BIT(n)
Only ever assign into the first 24 bits in QC (so max is BIT(23)).
Definition bits.qh:8
var entity(vector mins, vector maxs,.entity tofield) findbox_tofield_OrFallback
entity csqcplayer
Definition cl_player.qh:26
#define draw_beginBoldFont()
Definition draw.qh:4
#define draw_endBoldFont()
Definition draw.qh:5
bool StrafeHUD_IsGradient(int style)
Definition draw.qc:341
entity StrafeHUD_GetStrafeplayer(bool is_local)
Definition util.qc:109
float StrafeHUD_DetermineDirection(float angle, float wishangle, float antiflicker_angle)
Definition util.qc:296
bool StrafeHUD_DetermineOnSlick(entity e)
Definition util.qc:341
float StrafeHUD_DetermineWishAngle(vector movement, int keys, bool is_local)
Definition util.qc:178
int StrafeHUD_DetermineForwardKeys(vector movement, int keys, bool is_local)
Definition util.qc:216
float StrafeHUD_DetermineFrameTime()
Definition util.qc:138
bool StrafeHUD_DetermineJumpHeld(entity e, int keys, bool is_local)
Definition util.qc:316
float StrafeHUD_DetermineWaterLevel(entity e)
Definition util.qc:118
bool StrafeHUD_DetermineOnGround(entity e, bool is_local)
Definition util.qc:334
float StrafeHUD_DetermineHudAngle(float absolute_wishangle, float absolute_overturnangle, float strafity)
Definition util.qc:238
vector StrafeHUD_MixColors(vector color1, vector color2, float ratio)
Definition util.qc:351
int spectatee_status
the -1 disables HUD panels before CSQC receives necessary data
Definition main.qh:197
float GeomLerp(float a, float _lerp, float b)
Definition player.qc:163
#define PHYS_FRICTION_SLICK(s)
Definition player.qh:116
vector movement
Definition player.qh:228
#define PHYS_MAXAIRSTRAFESPEED(s)
Definition player.qh:137
#define PHYS_AIRCONTROL_PENALTY(s)
Definition player.qh:102
#define PHYS_AIRACCELERATE(s)
Definition player.qh:95
#define PHYS_STOPSPEED(s)
Definition player.qh:140
#define PHYS_FRICTION(s)
Definition player.qh:114
#define PHYS_MAXAIRSPEED(s)
Definition player.qh:136
#define PHYS_INPUT_ANGLES(s)
Definition player.qh:249
#define PHYS_AIRACCEL_QW(s)
Definition player.qh:97
#define PHYS_AIRSTRAFEACCELERATE(s)
Definition player.qh:107
#define PHYS_SLICKACCELERATE(s)
Definition player.qh:96
#define PHYS_ACCELERATE(s)
Definition player.qh:94
#define IS_DUCKED(s)
Definition player.qh:212
#define PHYS_MAXSPEED(s)
Definition player.qh:138
#define PHYS_AIRSTOPACCELERATE_FULL(s)
Definition player.qh:106
#define PHYS_FRICTION_REPLICA_DT
Definition player.qh:331
#define PHYS_AIRCONTROL_FLAGS(s)
Definition player.qh:101
#define PHYS_AIRSTOPACCELERATE(s)
Definition player.qh:105
#define PHYS_AIRCONTROL_POWER(s)
Definition player.qh:103
#define PHYS_AIRCONTROL(s)
Definition player.qh:100
#define PHYS_INPUT_MOVEVALUES(s)
Definition player.qh:256
float RAD2DEG
float time
float log(float f)
void StrafeHUD_DrawStrafeMeter(float shiftangle, float wishangle, float absolute_bestangle, float absolute_prebestangle, float absolute_overturnangle, bool moving, float hudangle)
Definition draw_core.qc:5
void StrafeHUD_DrawDirectionIndicator(int direction, bool opposite_direction, bool fwd)
Definition draw_core.qc:200
void StrafeHUD_DrawAngleIndicator(float angle, vector line_size, float arrow_size, int num_dashes, bool has_top_arrow, bool has_bottom_arrow, vector color, float alpha, float hudangle)
Definition draw_core.qc:140
float speed
Definition dynlight.qc:9
void StrafeHUD_DrawStartSpeed(float speed, float text_offset_top, float text_offset_bottom)
Definition extra.qc:186
void StrafeHUD_DrawStrafeEfficiency(float strafe_ratio, float text_offset_top, float text_offset_bottom)
Definition extra.qc:168
string StrafeHUD_UpdateSonarSound()
Definition extra.qc:246
void StrafeHUD_DrawJumpHeight(entity e, bool onground, bool swimming, float text_offset_top, float text_offset_bottom)
Definition extra.qc:127
void StrafeHUD_Sonar(float strafe_ratio, string sonarsound)
Definition extra.qc:216
void StrafeHUD_DrawVerticalAngle(entity e, float text_offset_top, float text_offset_bottom)
Definition extra.qc:108
float StrafeHUD_DrawSlickDetector(entity e, bool already_detected)
Definition extra.qc:23
void HUD_Panel_LoadCvars()
Definition hud.qc:215
void HUD_Scale_Enable()
Definition hud.qc:91
void HUD_Scale_Disable()
Definition hud.qc:84
vector panel_size
Definition hud.qh:163
float panel_bg_padding
Definition hud.qh:174
#define HUD_Panel_DrawBg()
Definition hud.qh:55
vector panel_pos
Definition hud.qh:162
bool autocvar__hud_configure
Definition hud_config.qh:3
#define STAT(...)
Definition stats.qh:82
float angle
Definition viewloc.qc:114
#define M_PI
Definition mathlib.qh:108
float isdemo()
float bound(float min, float value, float max)
float cos(float f)
float vlen(vector v)
vector vectoangles(vector v)
float sqrt(float f)
float min(float f,...)
float rint(float f)
float fabs(float f)
float max(float f,...)
const int WATERLEVEL_SWIMMING
Definition movetypes.qh:13
float race_timespeed
Definition racetimer.qh:10
vector
Definition self.qh:92
void HUD_StrafeHUD(bool should_draw)
Definition strafehud.qc:21
void HUD_StrafeHUD_Export(int fh)
Definition strafehud.qc:16
const int STRAFEHUD_WTURN_SIDESTRAFE
Definition strafehud.qh:134
vector autocvar_hud_panel_strafehud_wturn_color
Definition strafehud.qh:59
int autocvar_hud_panel_strafehud_wturn
Definition strafehud.qh:58
float autocvar_hud_panel_strafehud_wturn_line_height
Definition strafehud.qh:65
int autocvar_hud_panel_strafehud_onground_mode
Definition strafehud.qh:13
int autocvar_hud_panel_strafehud
Definition strafehud.qh:5
float autocvar_hud_panel_strafehud_timeout_turn
Definition strafehud.qh:17
vector autocvar_hud_panel_strafehud_angle_preaccel_color
Definition strafehud.qh:32
float autocvar_hud_panel_strafehud_switch_alpha
Definition strafehud.qh:52
int autocvar_hud_panel_strafehud_switch_arrow
Definition strafehud.qh:56
const int STRAFEHUD_DIRECTION_RIGHT
Definition strafehud.qh:124
bool autocvar__hud_panel_strafehud_demo
Definition strafehud.qh:6
bool autocvar_hud_panel_strafehud_onground_friction
Definition strafehud.qh:14
float autocvar_hud_panel_strafehud_bestangle_line_width
Definition strafehud.qh:45
int autocvar_hud_panel_strafehud_bestangle_arrow
Definition strafehud.qh:47
float autocvar_hud_panel_strafehud_angle_alpha
Definition strafehud.qh:31
bool autocvar_hud_panel_strafehud_dynamichud
Definition strafehud.qh:7
float autocvar_hud_panel_strafehud_switch_line_height
Definition strafehud.qh:55
float autocvar_hud_panel_strafehud_wturn_line_width
Definition strafehud.qh:64
const int STRAFEHUD_KEYS_FORWARD
Definition strafehud.qh:137
const int STRAFEHUD_MODE_VELOCITY_CENTERED
Definition strafehud.qh:116
float autocvar_hud_panel_strafehud_angle_line_height
Definition strafehud.qh:38
int autocvar_hud_panel_strafehud_switch_line
Definition strafehud.qh:53
float autocvar_hud_panel_strafehud_bestangle_line_height
Definition strafehud.qh:46
float autocvar_hud_panel_strafehud_antiflicker_angle
Definition strafehud.qh:18
const int STRAFEHUD_SWITCH_SIDESTRAFE
Definition strafehud.qh:129
const int STRAFEHUD_ONGROUND_OVERTURN
Definition strafehud.qh:118
int autocvar_hud_panel_strafehud_wturn_line
Definition strafehud.qh:63
const int STRAFEHUD_SWITCH_NORMAL
Definition strafehud.qh:128
const int STRAFEHUD_MODE_VIEW_CENTERED
Definition strafehud.qh:115
vector autocvar_hud_panel_strafehud_switch_color
Definition strafehud.qh:51
const int STRAFEHUD_WTURN_NORMAL
Definition strafehud.qh:133
int autocvar_hud_panel_strafehud_mode
Definition strafehud.qh:8
float autocvar_hud_panel_strafehud_angle_line_width
Definition strafehud.qh:37
float autocvar_hud_panel_strafehud_wturn_arrow_size
Definition strafehud.qh:67
const int STRAFEHUD_KEYS_BACKWARD
Definition strafehud.qh:138
float autocvar_hud_panel_strafehud_bestangle_arrow_size
Definition strafehud.qh:48
int autocvar_hud_panel_strafehud_bestangle_line
Definition strafehud.qh:44
vector autocvar_hud_panel_strafehud_bestangle_color
Definition strafehud.qh:42
float autocvar_hud_panel_strafehud_switch_arrow_size
Definition strafehud.qh:57
int autocvar_hud_panel_strafehud_switch
Definition strafehud.qh:49
int autocvar_hud_panel_strafehud_angle_line
Definition strafehud.qh:36
bool autocvar_hud_panel_strafehud_wturn_proper
Definition strafehud.qh:61
float autocvar_hud_panel_strafehud_bestangle_alpha
Definition strafehud.qh:43
vector autocvar_hud_panel_strafehud_angle_neutral_color
Definition strafehud.qh:33
int autocvar_hud_panel_strafehud_wturn_arrow
Definition strafehud.qh:66
float autocvar_hud_panel_strafehud_wturn_alpha
Definition strafehud.qh:60
int autocvar_hud_panel_strafehud_style
Definition strafehud.qh:11
const int STRAFEHUD_ONGROUND_GROUND
Definition strafehud.qh:119
float autocvar_hud_panel_strafehud_timeout_ground
Definition strafehud.qh:16
bool autocvar_hud_panel_strafehud_bar_preaccel
Definition strafehud.qh:21
vector autocvar_hud_panel_strafehud_angle_accel_color
Definition strafehud.qh:34
float autocvar_hud_panel_strafehud_switch_minspeed
Definition strafehud.qh:50
vector autocvar_hud_panel_strafehud_angle_overturn_color
Definition strafehud.qh:35
int autocvar_hud_panel_strafehud_angle_arrow
Definition strafehud.qh:39
const float ACOS_SQRT2_3_DEG
Definition strafehud.qh:154
float autocvar_hud_panel_strafehud_angle_arrow_size
Definition strafehud.qh:40
const int STRAFEHUD_DIRECTION_LEFT
Definition strafehud.qh:123
bool autocvar_hud_panel_strafehud_wturn_unrestricted
Definition strafehud.qh:62
const int STRAFEHUD_DIRECTION_NONE
Definition strafehud.qh:122
float autocvar_hud_panel_strafehud_switch_line_width
Definition strafehud.qh:54
int autocvar_hud_panel_strafehud_bestangle
Definition strafehud.qh:41
bool autocvar_hud_panel_strafehud_direction
Definition strafehud.qh:69
#define vec2(...)
Definition vector.qh:90
bool autocvar_v_flipped
Definition view.qh:73