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