]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/hud/panel/strafehud.qc
strafehud: make forward/backward detection work correctly with all kinds of strafing
[xonotic/xonotic-data.pk3dir.git] / qcsrc / client / hud / panel / strafehud.qc
1 // Author: Juhu
2
3 #include "strafehud.qh"
4
5 #include <client/autocvars.qh>
6 #include <client/miscfunctions.qh>
7 #include <common/animdecide.qh>
8 #include <common/ent_cs.qh>
9 #include <common/mapinfo.qh>
10 #include <common/physics/movetypes/movetypes.qh>
11 #include <common/physics/player.qh>
12 #include <lib/csqcmodel/cl_player.qh>
13
14 // StrafeHUD (#25)
15
16 void HUD_StrafeHUD_Export(int fh)
17 {
18     // allow saving cvars that aesthetically change the panel into hud skin files
19 }
20
21 bool fwd = true;
22 bool state_fwd = true;
23 bool state_fwd_prev = true;
24 float hidden_width;
25 float demo_angle = -37;
26 float demo_direction = 1;
27 float demo_time = 0;
28 float state_onground_time = 0;
29 float state_strafekeys_time = 0;
30 float state_fwd_time = 0;
31 bool state_onground = false;
32 bool state_strafekeys = false;
33 bool turn = false;
34 float turnangle;
35
36 // provide basic panel cvars to old clients
37 // TODO remove them after a future release (0.8.2+)
38 noref string autocvar_hud_panel_strafehud_pos = "0.320000 0.570000";
39 noref string autocvar_hud_panel_strafehud_size = "0.360000 0.020000";
40 noref string autocvar_hud_panel_strafehud_bg = "0";
41 noref string autocvar_hud_panel_strafehud_bg_color = "";
42 noref string autocvar_hud_panel_strafehud_bg_color_team = "";
43 noref string autocvar_hud_panel_strafehud_bg_alpha = "0.7";
44 noref string autocvar_hud_panel_strafehud_bg_border = "";
45 noref string autocvar_hud_panel_strafehud_bg_padding = "";
46
47 void HUD_StrafeHUD()
48 {
49     entity strafeplayer;
50     bool islocal;
51
52     if(!autocvar__hud_configure)
53     {
54         if(!autocvar_hud_panel_strafehud) return;
55         if(spectatee_status == -1 && (autocvar_hud_panel_strafehud == 1 || autocvar_hud_panel_strafehud == 3)) return;
56         if(autocvar_hud_panel_strafehud == 3 && !(ISGAMETYPE(RACE) || ISGAMETYPE(CTS))) return;
57     }
58
59     HUD_Panel_LoadCvars();
60
61     if(autocvar_hud_panel_strafehud_dynamichud)
62         HUD_Scale_Enable();
63     else
64         HUD_Scale_Disable();
65     HUD_Panel_DrawBg();
66     if(panel_bg_padding)
67     {
68         panel_pos  += '1 1 0' * panel_bg_padding;
69         panel_size -= '2 2 0' * panel_bg_padding;
70     }
71
72     if(spectatee_status > 0 || isdemo())
73     {
74         islocal = false;
75         strafeplayer = CSQCModel_server2csqc(player_localentnum - 1);
76     }
77     else
78     {
79         islocal = true;
80         strafeplayer = csqcplayer;
81     }
82
83     // draw strafehud
84     if(csqcplayer && strafeplayer)
85     {
86         // physics
87         bool   onground                      = islocal ? IS_ONGROUND(strafeplayer) : !(strafeplayer.anim_implicit_state & ANIMIMPLICITSTATE_INAIR);
88         bool   strafekeys;
89         bool   is_swimming                   = strafeplayer.waterlevel >= WATERLEVEL_SWIMMING;
90         float  speed                         = !autocvar__hud_configure ? vlen(vec2(csqcplayer.velocity)) : 1337; // use local csqcmodel entity for this even when spectating, flickers too much otherwise
91         float  maxspeed_crouch_mod           = IS_DUCKED(strafeplayer) && !is_swimming ? .5 : 1;
92         float  maxspeed_water_mod            = is_swimming ? .7 : 1; // very simplified water physics, the hud will not work well (and is not supposed to) while swimming
93         float  maxspeed_phys                 = onground ? PHYS_MAXSPEED(strafeplayer) : PHYS_MAXAIRSPEED(strafeplayer);
94         float  maxspeed                      = !autocvar__hud_configure ? maxspeed_phys * maxspeed_crouch_mod * maxspeed_water_mod : 320;
95         float  vel_angle                     = vectoangles(strafeplayer.velocity).y;
96         float  view_angle                    = view_angles.y + 180;
97         float  angle;
98         int    direction;
99         vector movement                      = PHYS_INPUT_MOVEVALUES(strafeplayer);
100         int    keys                          = STAT(PRESSED_KEYS);
101         int    keys_fwd;
102         float  wishangle                     = 0;
103         float  moveangle;
104
105         // HUD
106         int    mode                          = autocvar_hud_panel_strafehud_mode >= 0 && autocvar_hud_panel_strafehud_mode <= 1 ? autocvar_hud_panel_strafehud_mode : 0;
107         float  antiflicker_angle             = bound(0, autocvar_hud_panel_strafehud_antiflicker_angle, 180);
108         float  antiflicker_speed             = max(0, autocvar_hud_panel_strafehud_antiflicker_speed);
109         float  minspeed;
110         bool   straight_overturn             = false;
111         float  hudangle;
112         float  bar_offset;
113         float  bar_width;
114         vector currentangle_color            = autocvar_hud_panel_strafehud_warning_color;
115         float  currentangle_offset;
116         vector currentangle_size             = '0 0 0';
117         bool   show_indicators;
118         float  bestangle;
119         bool   bestangle_anywhere            = false;
120         float  bestangle_offset;
121         float  bestangle_width;
122         bool   odd_angles                    = false;
123         float  switch_bestangle_offset;
124         float  odd_bestangle_offset          = 0;
125         float  switch_odd_bestangle_offset   = 0;
126         float  accelzone_offset;
127         float  accelzone_width;
128         float  odd_accelzone_offset;
129         float  odd_accelzone_width;
130         float  overturn_offset;
131         float  overturn_width;
132         float  overturn_width_visible;
133         vector direction_size_vertical       = '0 0 0';
134         vector direction_size_horizontal     = '0 0 0';
135         float  maxangle;
136         float  range_minangle;
137
138         // determine whether the player is pressing forwards or backwards keys
139         if(islocal) // if entity is local player
140         {
141             if(movement_x > 0)
142             {
143                 keys_fwd = 1;
144             }
145             else if(movement_x < 0)
146             {
147                 keys_fwd = -1;
148             }
149             else
150             {
151                 keys_fwd = 0;
152             }
153         }
154         else // alternatively determine direction by querying pressed keys
155         {
156             if((keys & KEY_FORWARD) && !(keys & KEY_BACKWARD))
157             {
158                 keys_fwd = 1;
159             }
160             else if(!(keys & KEY_FORWARD) && (keys & KEY_BACKWARD))
161             {
162                 keys_fwd = -1;
163             }
164             else
165             {
166                 keys_fwd = 0;
167             }
168         }
169
170         // determine player wishdir
171         if(islocal) // if entity is local player
172         {
173             if(movement_x == 0)
174             {
175                 if(movement_y < 0)
176                 {
177                     wishangle = -90;
178                 }
179                 else if(movement_y > 0)
180                 {
181                     wishangle = 90;
182                 }
183                 else
184                 {
185                     wishangle = 0;
186                 }
187             }
188             else
189             {
190                 if(movement_y == 0)
191                 {
192                     wishangle = 0;
193                 }
194                 else
195                 {
196                     wishangle = RAD2DEG * atan2(movement_y, movement_x);
197                     // wrap the wish angle if it exceeds ±90°
198                     if(fabs(wishangle) > 90)
199                     {
200                         if(wishangle < 0) wishangle += 180;
201                         else wishangle -= 180;
202                         wishangle = -wishangle;
203                     }
204                 }
205             }
206         }
207         else // alternatively calculate wishdir by querying pressed keys
208         {
209             if(keys & KEY_FORWARD || keys & KEY_BACKWARD)
210             {
211                 wishangle = 45;
212             }
213             else
214             {
215                 wishangle = 90;
216             }
217             if(keys & KEY_LEFT)
218             {
219                 wishangle *= -1;
220             }
221             else if(!(keys & KEY_RIGHT))
222             {
223                 wishangle = 0; // wraps at 180°
224             }
225         }
226
227         strafekeys = fabs(wishangle) == 90;
228
229         // determine minimum required angle to display full strafe range
230         range_minangle = fabs(wishangle) % 90; // maximum range is 90 degree
231         if(range_minangle > 45) // minimum angle range is 45
232         {
233             range_minangle = 45 - fabs(wishangle) % 45;
234         }
235         range_minangle = 90 - range_minangle; // calculate value which is never >90 or <45
236         range_minangle *= 2; // multiply to accommodate for both sides of the hud
237
238         if(autocvar_hud_panel_strafehud_angle == 0)
239         {
240             if(autocvar__hud_configure)
241             {
242                 hudangle = 90;
243             }
244             else
245             {
246                 hudangle = range_minangle; // use minimum angle required if dynamically setting hud angle
247             }
248         }
249         else
250         {
251             hudangle = bound(0, fabs(autocvar_hud_panel_strafehud_angle), 360); // limit HUD range to 360 degrees, higher values don't make sense
252         }
253
254         // detect air strafe turning
255         if(onground != state_onground)
256         {
257             state_onground_time = time;
258         }
259         state_onground = onground;
260         if(strafekeys != state_strafekeys)
261         {
262             state_strafekeys_time = time;
263         }
264         state_strafekeys = strafekeys;
265         if((keys & KEY_FORWARD) || (keys & KEY_BACKWARD) || is_swimming || autocvar__hud_configure)
266         {
267             turn = false;
268         }
269         else if(onground)
270         {
271             if((time - state_onground_time) >= autocvar_hud_panel_strafehud_timeout_ground) // timeout for strafe jumping in general
272             {
273                 turn = false;
274             }
275         }
276         else // air strafe only
277         {
278             if(strafekeys)
279             {
280                 if(((time - state_onground_time) >= autocvar_hud_panel_strafehud_timeout_air) || (keys & KEY_JUMP)) // timeout for slick ramps
281                 {
282                     turn = true; // CPMA turning
283                     turnangle = wishangle;
284                 }
285             }
286             else if((time - state_strafekeys_time) >= autocvar_hud_panel_strafehud_timeout_strafe) // timeout for jumping with strafe keys only
287             {
288                 turn = false;
289             }
290         }
291         if(turn)
292         {
293             maxspeed = PHYS_MAXAIRSTRAFESPEED(strafeplayer); // no modifiers here because they don't affect air strafing
294             wishangle = turnangle;
295         }
296
297         minspeed = autocvar_hud_panel_strafehud_indicator_minspeed < 0 ? maxspeed + antiflicker_speed : autocvar_hud_panel_strafehud_indicator_minspeed;
298         show_indicators = (autocvar_hud_panel_strafehud_indicators && (speed >= minspeed));
299
300         // get current strafing angle ranging from -180° to +180°
301         if(!autocvar__hud_configure)
302         {
303             if(speed > 0)
304             {
305                 // calculate view angle relative to the players current velocity direction
306                 angle = vel_angle - view_angle;
307
308                 // if the angle goes above 180° or below -180° wrap it to the opposite side
309                 if (angle > 180) angle -= 360;
310                 else if(angle < -180) angle += 360;
311
312                 // shift the strafe angle by 180° for hud calculations
313                 if(angle < 0) angle += 180;
314                 else angle -= 180;
315
316                 // determine whether the player is strafing forwards or backwards
317                 // if the player isn't strafe turning use forwards/backwards keys to determine direction
318                 if(!strafekeys)
319                 {
320                     if(keys_fwd > 0)
321                     {
322                     state_fwd = true;
323                     }
324                     else if(keys_fwd < 0)
325                     {
326                         state_fwd = false;
327                     }
328                     else
329                     {
330                         state_fwd = fabs(angle) <= 90;
331                     }
332                 }
333                 // otherwise determine by examining the strafe angle
334                 else
335                 {
336                     if(wishangle < 0) // detect direction since the direction is not yet set
337                     {
338                         state_fwd = angle <= -wishangle;
339                     }
340                     else
341                     {
342                         state_fwd = angle >= -wishangle;
343                     }
344                 }
345
346                 if(state_fwd_prev != state_fwd)
347                 {
348                     state_fwd_time = time;
349                 }
350                 state_fwd_prev = state_fwd;
351
352                 if((time - state_fwd_time) >= autocvar_hud_panel_strafehud_timeout_direction || speed < maxspeed || strafekeys) // timeout when changing between forwards and backwards movement
353                 {
354                     fwd = state_fwd;
355                 }
356
357                 // shift the strafe angle by 180° when strafing backwards
358                 if(!fwd)
359                 {
360                     if(angle < 0) angle += 180;
361                     else angle -= 180;
362                 }
363
364                 // don't make the angle indicator switch side too much at ±180° if anti flicker is turned on
365                 if(angle > (180 - antiflicker_angle) || angle < (-180 + antiflicker_angle))
366                 {
367                     straight_overturn = true;
368                 }
369             }
370             else
371             {
372                 angle = 0;
373             }
374         }
375         else // simulate turning for HUD setup
376         {
377             if(autocvar__hud_panel_strafehud_demo && ((time - demo_time) >= .025))
378             {
379                 demo_time = time;
380                 demo_angle += demo_direction;
381                 if(fabs(demo_angle) >= 55)
382                 {
383                     demo_direction = -demo_direction;
384                 }
385             }
386             angle = demo_angle;
387             wishangle = 45 * (demo_angle > 0 ? 1 : -1);
388         }
389
390         // invert the wish angle when strafing backwards
391         if(!fwd)
392         {
393             wishangle = -wishangle;
394         }
395
396         // flip angles if v_flipped is enabled
397         if(autocvar_v_flipped)
398         {
399             angle = -angle;
400             wishangle = -wishangle;
401         }
402
403         moveangle = angle + wishangle;
404
405         // determine whether the player is strafing left or right
406         if(wishangle != 0)
407         {
408             direction = wishangle > 0 ? 1 : -1;
409         }
410         else
411         {
412             direction = (angle > antiflicker_angle && angle < (180 - antiflicker_angle)) ? 1 : (angle < -antiflicker_angle && angle > (-180 + antiflicker_angle)) ? -1 : 0;
413         }
414
415         // decelerating at this angle
416         maxangle = 90 - fabs(wishangle);
417         // best angle to strafe at
418         bestangle = (speed > maxspeed ? acos(maxspeed / speed) : 0) * RAD2DEG * (direction < 0 ? -1 : 1) - wishangle;
419
420         // various offsets and size calculations of hud indicator elements
421         // how much is hidden by the current hud angle
422         hidden_width = (360 - hudangle) / hudangle * panel_size.x;
423         // current angle
424         currentangle_size.x = max(panel_size.x * autocvar_hud_panel_strafehud_angle_width, 1);
425         if(mode == 0)
426         {
427             currentangle_offset = angle/hudangle * panel_size.x;
428         }
429         else
430         {
431             currentangle_offset = bound(-hudangle/2, angle, hudangle/2)/hudangle * panel_size.x + panel_size.x/2;
432         }
433         currentangle_size.y = max(panel_size.y * autocvar_hud_panel_strafehud_angle_height, 1);
434         // best strafe acceleration angle
435         bestangle_offset        =  bestangle/hudangle * panel_size.x + panel_size.x/2;
436         switch_bestangle_offset = -bestangle/hudangle * panel_size.x + panel_size.x/2;
437
438         if(((angle > -wishangle && direction < 0) || (angle < -wishangle && direction > 0)) && (direction != 0))
439         {
440             odd_angles = true;
441             float odd_bestangle = -(bestangle + wishangle) - wishangle;
442             odd_bestangle_offset        = odd_bestangle/hudangle * panel_size.x + panel_size.x/2;
443             switch_odd_bestangle_offset = (odd_bestangle+bestangle*2)/hudangle * panel_size.x + panel_size.x/2;
444         }
445
446         if(show_indicators)
447         {
448             bestangle_width = max(panel_size.x * autocvar_hud_panel_strafehud_indicator_width, 1);
449         }
450         else
451         {
452             bestangle_width = 0;
453         }
454         // direction indicator
455         direction_size_vertical.x = max(panel_size.y * min(autocvar_hud_panel_strafehud_direction_width, .5), 1);
456         direction_size_vertical.y = panel_size.y;
457         direction_size_horizontal.x = max(panel_size.x * min(autocvar_hud_panel_strafehud_direction_length, .5), direction_size_vertical.x);
458         direction_size_horizontal.y = direction_size_vertical.x;
459         // overturn
460         overturn_width = 180/hudangle * panel_size.x;
461         overturn_width_visible = (hudangle/2 - maxangle) / hudangle * panel_size.x;
462
463         // the strafe bar fills the whole hud panel
464         if(speed <= (is_swimming ? antiflicker_speed : 0))
465         {
466             // add a background to the strafe-o-meter
467             if(panel_size.x > 0 && panel_size.y > 0)
468             {
469                 if(!autocvar_hud_panel_strafehud_unstyled)
470                 {
471                     HUD_Panel_DrawProgressBar(panel_pos, panel_size, "progressbar", 1, 0, 0, autocvar_hud_panel_strafehud_bar_color, autocvar_hud_panel_strafehud_bar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
472                 }
473                 else
474                 {
475                     drawfill(panel_pos, panel_size, autocvar_hud_panel_strafehud_bar_color, autocvar_hud_panel_strafehud_bar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
476                 }
477             }
478         }
479         else
480         {
481             // mark the ideal strafe angle
482             if(direction < 0) // turning left
483             {
484                 // calculate zone in which strafe acceleration happens
485                 accelzone_width = bestangle_offset;
486                 // calculate offset of overturn area
487                 overturn_offset = overturn_width_visible - overturn_width;
488                 // move/adjust acceleration zone
489                 accelzone_offset = overturn_width_visible;
490                 accelzone_width -= overturn_width_visible;
491                 // calculate zone in which strafe acceleration could also happen without changing wishdir
492                 odd_accelzone_width = accelzone_width;
493                 odd_accelzone_offset = overturn_offset - odd_accelzone_width;
494                 // calculate the background of the strafe-o-meter
495                 bar_offset = bestangle_offset;
496                 bar_width = 360/hudangle * panel_size.x - accelzone_width - odd_accelzone_width - overturn_width;
497             }
498             else // turning right or moving forward
499             {
500                 // calculate zone in which strafe acceleration happens
501                 accelzone_offset = bestangle_offset;
502                 accelzone_width = panel_size.x - accelzone_offset;
503                 // calculate offset of overturn area
504                 overturn_offset = panel_size.x - overturn_width_visible;
505                 // adjust acceleration zone
506                 accelzone_width -= overturn_width_visible;
507                 // calculate zone in which strafe acceleration could also happen without changing wishdir
508                 odd_accelzone_width = accelzone_width;
509                 odd_accelzone_offset = overturn_offset + overturn_width;
510                 // calculate the background of the strafe-o-meter
511                 bar_offset = odd_accelzone_offset + odd_accelzone_width;
512                 bar_width = 360/hudangle * panel_size.x - accelzone_width - odd_accelzone_width - overturn_width;
513             }
514
515             // remove indicator width from offset
516             if(direction < 0)
517             {
518                 bestangle_offset -= bestangle_width;
519                 switch_odd_bestangle_offset -= bestangle_width;
520             }
521             else
522             {
523                 switch_bestangle_offset -= bestangle_width;
524                 odd_bestangle_offset -= bestangle_width;
525             }
526
527             if(mode == 0)
528             {
529                 bar_offset -= currentangle_offset;
530                 accelzone_offset -= currentangle_offset;
531                 odd_accelzone_offset -= currentangle_offset;
532                 overturn_offset -= currentangle_offset;
533                 bestangle_offset -= currentangle_offset;
534                 switch_bestangle_offset -= currentangle_offset;
535                 odd_bestangle_offset -= currentangle_offset;
536                 switch_odd_bestangle_offset -= currentangle_offset;
537             }
538
539             if(!autocvar_hud_panel_strafehud_unstyled)
540             {
541                 // draw acceleration zone
542                 HUD_Panel_DrawStrafeHUD_ProgressBar(accelzone_offset, accelzone_width, autocvar_hud_panel_strafehud_indicator_color, autocvar_hud_panel_strafehud_bar_alpha);
543
544                 // draw odd acceleration zone
545                 HUD_Panel_DrawStrafeHUD_ProgressBar(odd_accelzone_offset, odd_accelzone_width, autocvar_hud_panel_strafehud_indicator_color, autocvar_hud_panel_strafehud_bar_alpha);
546
547                 // draw overturn area
548                 HUD_Panel_DrawStrafeHUD_ProgressBar(overturn_offset, overturn_width, autocvar_hud_panel_strafehud_alert_color, autocvar_hud_panel_strafehud_bar_alpha);
549
550                 // draw the strafe bar background
551                 HUD_Panel_DrawStrafeHUD_ProgressBar(bar_offset, bar_width, autocvar_hud_panel_strafehud_bar_color, autocvar_hud_panel_strafehud_bar_alpha);
552             }
553             else
554             {
555                 // draw acceleration zone
556                 HUD_Panel_DrawStrafeHUD_drawfill(accelzone_offset, accelzone_width, autocvar_hud_panel_strafehud_indicator_color, autocvar_hud_panel_strafehud_bar_alpha);
557
558                 // draw odd acceleration zone
559                 HUD_Panel_DrawStrafeHUD_drawfill(odd_accelzone_offset, odd_accelzone_width, autocvar_hud_panel_strafehud_indicator_color, autocvar_hud_panel_strafehud_bar_alpha);
560
561                 // draw overturn area
562                 HUD_Panel_DrawStrafeHUD_drawfill(overturn_offset, overturn_width, autocvar_hud_panel_strafehud_alert_color, autocvar_hud_panel_strafehud_bar_alpha);
563
564                 // draw the strafe bar background
565                 HUD_Panel_DrawStrafeHUD_drawfill(bar_offset, bar_width, autocvar_hud_panel_strafehud_bar_color, autocvar_hud_panel_strafehud_bar_alpha);
566             }
567
568             if(direction != 0)
569             {
570                 bool indicator_direction = direction < 0;
571                 // invert left/right when strafing backwards or when strafing towards the opposite side indicated by the direction variable
572                 // if both conditions are true then it's inverted twice hence not inverted at all
573                 if(!fwd != odd_angles)
574                 {
575                     indicator_direction = !indicator_direction;
576                 }
577                 // draw the direction indicator caps at the sides of the hud
578                 // vertical line
579                 drawfill(panel_pos + eX * (indicator_direction ? -direction_size_vertical.x : panel_size.x), direction_size_vertical, autocvar_hud_panel_strafehud_direction_color, panel_fg_alpha, DRAWFLAG_NORMAL);
580                 // top horizontal line
581                 drawfill(panel_pos + eX * (indicator_direction ? -direction_size_vertical.x : panel_size.x - direction_size_horizontal.x + direction_size_vertical.x) - eY * direction_size_horizontal.y, direction_size_horizontal, autocvar_hud_panel_strafehud_direction_color, panel_fg_alpha, DRAWFLAG_NORMAL);
582                 // bottom horizontal line
583                 drawfill(panel_pos + eX * (indicator_direction ? -direction_size_vertical.x : panel_size.x - direction_size_horizontal.x + direction_size_vertical.x) + eY * panel_size.y, direction_size_horizontal, autocvar_hud_panel_strafehud_direction_color, panel_fg_alpha, DRAWFLAG_NORMAL);
584             }
585
586             if(show_indicators) // only draw indicators if enabled and minspeed is reached
587             {
588                 // draw best angles for acceleration
589                 vector indicator_color;
590                 float offset = !odd_angles ? bestangle_offset : odd_bestangle_offset;
591                 float switch_offset = !odd_angles ? switch_bestangle_offset : switch_odd_bestangle_offset;
592                 // both indicators are yellow if no direction can be determined
593                 indicator_color = direction != 0 ? autocvar_hud_panel_strafehud_indicator_color : autocvar_hud_panel_strafehud_indicator_switch_color;
594                 HUD_Panel_DrawStrafeHUD_drawfill(switch_offset, bestangle_width, autocvar_hud_panel_strafehud_indicator_switch_color, 1);
595                 HUD_Panel_DrawStrafeHUD_drawfill(offset, bestangle_width, indicator_color, 1);
596             }
597         }
598
599         if(speed < (maxspeed + antiflicker_speed) && speed > 0)
600         {
601             bestangle_anywhere = true; // moving forward should suffice to gain speed
602         }
603
604         // draw the actual strafe angle
605         if(!bestangle_anywhere) // player gains speed with strafing
606         {
607             if((direction > 0 && (angle >= bestangle || angle <= -(bestangle + wishangle*2))) ||
608                 (direction < 0 && (angle <= bestangle || angle >= -(bestangle + wishangle*2))))
609             currentangle_color = autocvar_hud_panel_strafehud_good_color;
610         }
611
612         if(fabs(moveangle) > 90) // player is overturning
613         {
614             currentangle_color = autocvar_hud_panel_strafehud_alert_color;
615         }
616         else if(bestangle_anywhere) // player gains speed without strafing
617         {
618             currentangle_color = autocvar_hud_panel_strafehud_good_color;
619         }
620
621         if(mode == 0 || straight_overturn)
622         {
623             currentangle_offset = panel_size.x/2;
624         }
625         drawfill(panel_pos - eY * ((currentangle_size.y - panel_size.y) / 2) + eX * (currentangle_offset - currentangle_size.x/2), currentangle_size, currentangle_color, autocvar_hud_panel_strafehud_angle_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
626     }
627 }
628
629 // functions to make hud elements align perfectly in the hud area
630 void HUD_Panel_DrawStrafeHUD_ProgressBar(float offset, float width, vector color, float alpha)
631 {
632     float mirror_offset, mirror_width;
633     vector size = panel_size;
634     vector mirror_size = panel_size;
635
636     if(offset < 0)
637     {
638         mirror_width = min(fabs(offset), width);
639         mirror_offset = panel_size.x + hidden_width - fabs(offset);
640         width += offset;
641         offset = 0;
642     }
643     else
644     {
645         mirror_width = min(offset + width - panel_size.x - hidden_width, width);
646         mirror_offset = max(offset - panel_size.x - hidden_width, 0);
647     }
648     if((offset + width) > panel_size.x)
649     {
650         width = panel_size.x - offset;
651     }
652     if(mirror_offset < 0)
653     {
654         mirror_width += mirror_offset;
655         mirror_offset = 0;
656     }
657     if((mirror_offset + mirror_width) > panel_size.x)
658     {
659         mirror_width = panel_size.x - mirror_offset;
660     }
661
662     size.x = width;
663     mirror_size.x = mirror_width;
664     if(mirror_size.x > 0 && mirror_size.y > 0) HUD_Panel_DrawProgressBar(panel_pos + eX * mirror_offset, mirror_size, "progressbar", 1, 0, 0, color, alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
665     if(size.x > 0 && size.y > 0) HUD_Panel_DrawProgressBar(panel_pos + eX * offset, size, "progressbar", 1, 0, 0, color, alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
666 }
667
668 void HUD_Panel_DrawStrafeHUD_drawfill(float offset, float width, vector color, float alpha)
669 {
670     float mirror_offset, mirror_width;
671     vector size = panel_size;
672     vector mirror_size = panel_size;
673
674     if(offset < 0)
675     {
676         mirror_width = min(fabs(offset), width);
677         mirror_offset = panel_size.x + hidden_width - fabs(offset);
678         width += offset;
679         offset = 0;
680     }
681     else
682     {
683         mirror_width = min(offset + width - panel_size.x - hidden_width, width);
684         mirror_offset = max(offset - panel_size.x - hidden_width, 0);
685     }
686     if((offset + width) > panel_size.x)
687     {
688         width = panel_size.x - offset;
689     }
690     if(mirror_offset < 0)
691     {
692         mirror_width += mirror_offset;
693         mirror_offset = 0;
694     }
695     if((mirror_offset + mirror_width) > panel_size.x)
696     {
697         mirror_width = panel_size.x - mirror_offset;
698     }
699
700     size.x = width;
701     mirror_size.x = mirror_width;
702     if(mirror_width > 0) drawfill(panel_pos + eX * mirror_offset, mirror_size, color, alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
703     if(width > 0) drawfill(panel_pos + eX * offset, size, color, alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
704 }