]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/hud/panel/strafehud.qc
add strafehud to _hud_panelorder and improve a few strafehud corner cases
[xonotic/xonotic-data.pk3dir.git] / qcsrc / client / hud / panel / strafehud.qc
1 // Name:   StrafeHUD
2 // Author: Juhu
3
4 #include "strafehud.qh"
5
6 #include <client/autocvars.qh>
7 #include <client/miscfunctions.qh>
8 #include <common/animdecide.qh>
9 #include <common/ent_cs.qh>
10 #include <common/mapinfo.qh>
11 #include <common/physics/movetypes/movetypes.qh>
12 #include <common/physics/player.qh>
13 #include <lib/csqcmodel/cl_player.qh>
14
15 bool strafehud_fwd = true;
16 float strafehud_demo_angle = -37;
17 float strafehud_demo_direction = 1;
18 float strafehud_demo_time = 0;
19 float strafehud_state_onground_time = 0;
20 float strafehud_state_strafekeys_time = 0;
21 bool strafehud_state_onground = true;
22 bool strafehud_state_strafekeys = false;
23 bool strafehud_turn = false;
24 float strafehud_turnangle;
25
26 // provide basic panel cvars to old clients
27 // TODO remove them after a future release (0.8.2+)
28 noref string autocvar_hud_panel_strafehud_pos = "0.320000 0.570000";
29 noref string autocvar_hud_panel_strafehud_size = "0.360000 0.020000";
30 noref string autocvar_hud_panel_strafehud_bg = "0";
31 noref string autocvar_hud_panel_strafehud_bg_color = "";
32 noref string autocvar_hud_panel_strafehud_bg_color_team = "";
33 noref string autocvar_hud_panel_strafehud_bg_alpha = "0.7";
34 noref string autocvar_hud_panel_strafehud_bg_border = "";
35 noref string autocvar_hud_panel_strafehud_bg_padding = "";
36
37 void HUD_StrafeHUD()
38 {
39     entity strafeplayer;
40     bool strafehud_islocal;
41
42     if(!autocvar__hud_configure)
43     {
44         if(!autocvar_hud_panel_strafehud) return;
45         if(spectatee_status == -1 && (autocvar_hud_panel_strafehud == 1 || autocvar_hud_panel_strafehud == 3)) return;
46         if(autocvar_hud_panel_strafehud == 3 && !(ISGAMETYPE(RACE) || ISGAMETYPE(CTS))) return;
47     }
48
49     HUD_Panel_LoadCvars();
50
51     if(autocvar_hud_panel_strafehud_dynamichud)
52         HUD_Scale_Enable();
53     else
54         HUD_Scale_Disable();
55     HUD_Panel_DrawBg();
56     if(panel_bg_padding)
57     {
58         panel_pos  += '1 1 0' * panel_bg_padding;
59         panel_size -= '2 2 0' * panel_bg_padding;
60     }
61
62     if(spectatee_status > 0 || isdemo())
63     {
64         strafehud_islocal = false;
65         strafeplayer = CSQCModel_server2csqc(player_localentnum - 1);
66     }
67     else
68     {
69         strafehud_islocal = true;
70         strafeplayer = csqcplayer;
71     }
72
73     // draw strafehud
74     if(csqcplayer && strafeplayer)
75     {
76         // autocvars
77         float strafehud_bar_alpha                  = autocvar_hud_panel_strafehud_bar_alpha;
78         vector strafehud_bar_color                 = autocvar_hud_panel_strafehud_bar_color;
79         vector strafehud_bestangle_color           = autocvar_hud_panel_strafehud_indicator_color;
80         vector strafehud_mirror_bestangle_color    = autocvar_hud_panel_strafehud_indicator_switch_color;
81         vector strafehud_good_color                = autocvar_hud_panel_strafehud_good_color;
82         vector strafehud_warning_color             = autocvar_hud_panel_strafehud_warning_color;
83         vector strafehud_alert_color               = autocvar_hud_panel_strafehud_alert_color;
84         vector strafehud_direction_color           = autocvar_hud_panel_strafehud_direction_color;
85         float strafehud_timeout_air                = autocvar_hud_panel_strafehud_timeout_air;    // timeout for slick ramps
86         float strafehud_timeout_ground             = autocvar_hud_panel_strafehud_timeout_ground; // timeout for strafe jumping in general
87         float strafehud_timeout_strafe             = autocvar_hud_panel_strafehud_timeout_strafe; // timeout for jumping with strafe keys only
88         float strafehud_indicator_minspeed         = autocvar_hud_panel_strafehud_indicator_minspeed;
89
90         // physics
91         bool   strafehud_onground                  = strafehud_islocal ? IS_ONGROUND(strafeplayer) : !(strafeplayer.anim_implicit_state & ANIMIMPLICITSTATE_INAIR);
92         bool   strafehud_strafekeys;
93         bool   strafehud_iswater                   = strafeplayer.waterlevel >= WATERLEVEL_SWIMMING;
94         float  strafehud_speed                     = !autocvar__hud_configure ? vlen(vec2(csqcplayer.velocity)) : 1337; // use local csqcmodel entity for this even when spectating, flickers too much otherwise
95         float  strafehud_maxspeed_crouch_mod       = IS_DUCKED(strafeplayer) && (!strafehud_iswater || IS_ONGROUND(strafeplayer)) ? .5 : 1;
96         float  strafehud_maxspeed_water_mod        = strafehud_iswater ? .7 : 1; // FIXME: water physics are way more complex than this
97         float  strafehud_maxspeed_phys             = strafehud_onground ? PHYS_MAXSPEED(strafeplayer) : PHYS_MAXAIRSPEED(strafeplayer);
98         float  strafehud_maxspeed                  = !autocvar__hud_configure ? strafehud_maxspeed_phys * strafehud_maxspeed_crouch_mod * strafehud_maxspeed_water_mod : 320;
99         float  strafehud_vel_angle                 = vectoangles(strafeplayer.velocity).y;
100         float  strafehud_view_angle                = view_angles.y + 180;
101         float  strafehud_angle;
102         float  strafehud_direction;
103         vector strafehud_movement                  = PHYS_INPUT_MOVEVALUES(strafeplayer);
104         int    strafehud_keys                      = STAT(PRESSED_KEYS);
105         float  strafehud_wishangle;
106         float  strafehud_moveangle;
107
108         // HUD
109         int    strafehud_mode                      = autocvar_hud_panel_strafehud_mode >= 0 && autocvar_hud_panel_strafehud_mode <= 1 ? autocvar_hud_panel_strafehud_mode : 0;
110         float  strafehud_hudangle;
111         float  strafehud_bar_offset;
112         vector strafehud_bar_size                  = panel_size;
113         vector strafehud_currentangle_color        = strafehud_warning_color;
114         float  strafehud_currentangle_offset;
115         vector strafehud_currentangle_size         = '0 0 0';
116         float  strafehud_bestangle;
117         bool   strafehud_bestangle_anywhere        = false;
118         float  strafehud_bestangle_offset;
119         float  strafehud_mirror_bestangle_offset;
120         vector strafehud_bestangle_size            = panel_size;
121         vector strafehud_mirror_bestangle_size;
122         float  strafehud_accelzone_offset;
123         vector strafehud_accelzone_size            = panel_size;
124         float  strafehud_overturn_offset;
125         vector strafehud_overturn_size             = panel_size;
126         float  strafehud_hidden_angle;
127         float  strafehud_hidden_size;
128         float  strafehud_mirrorangle;
129         float  strafehud_mirror_overturn_offset;
130         vector strafehud_mirror_overturn_size      = panel_size;
131         vector strafehud_direction_size_vertical   = '0 0 0';
132         vector strafehud_direction_size_horizontal = '0 0 0';
133         float  strafehud_maxangle;
134         float  strafehud_range_minangle;
135
136         // determine whether the player is strafing forwards or backwards
137         if(strafehud_islocal) // if entity is local player
138         {
139             if(strafehud_movement_x > 0)
140             {
141                 strafehud_fwd = true;
142             }
143             else if(strafehud_movement_x < 0)
144             {
145                 strafehud_fwd = false;
146             }
147         }
148         else // alternatively determine direction by querying pressed keys
149         {
150             if((strafehud_keys & KEY_FORWARD) && !(strafehud_keys & KEY_BACKWARD))
151             {
152                 strafehud_fwd = true;
153             }
154             else if(!(strafehud_keys & KEY_FORWARD) && (strafehud_keys & KEY_BACKWARD))
155             {
156                 strafehud_fwd = false;
157             }
158         }
159
160         // determine player wishdir
161         if(strafehud_islocal) // if entity is local player
162         {
163             if(strafehud_movement_x == 0)
164             {
165                 if(strafehud_movement_y < 0)
166                 {
167                     strafehud_wishangle = -90;
168                 }
169                 else if(strafehud_movement_y > 0)
170                 {
171                     strafehud_wishangle = 90;
172                 }
173                 else
174                 {
175                     strafehud_wishangle = 0;
176                 }
177             }
178             else
179             {
180                 if(strafehud_movement_y == 0)
181                 {
182                     strafehud_wishangle = 0;
183                 }
184                 else
185                 {
186                     strafehud_wishangle = RAD2DEG * atan2(strafehud_movement_y, strafehud_movement_x);
187                 }
188             }
189         }
190         else // alternatively calculate wishdir by querying pressed keys
191         {
192             if(strafehud_keys & KEY_FORWARD)
193             {
194                 strafehud_wishangle = 45;
195             }
196             else if(strafehud_keys & KEY_BACKWARD)
197             {
198                 strafehud_wishangle = 135;
199             }
200             else
201             {
202                 strafehud_wishangle = 90;
203             }
204             if(strafehud_keys & KEY_LEFT)
205             {
206                 strafehud_wishangle *= -1;
207             }
208             else if(!(strafehud_keys & KEY_RIGHT))
209             {
210                 strafehud_wishangle = 0;
211             }
212         }
213
214         strafehud_strafekeys = fabs(strafehud_wishangle) == 90;
215
216         // determine minimum required angle to display full strafe range
217         strafehud_range_minangle = fabs(strafehud_wishangle) % 90; // maximum range is 90 degree
218         if(strafehud_range_minangle > 45) // minimum angle range is 45
219         {
220             strafehud_range_minangle = 45 - fabs(strafehud_wishangle) % 45;
221         }
222         strafehud_range_minangle = 90 - strafehud_range_minangle; // calculate value which is never >90 or <45
223
224         if(autocvar_hud_panel_strafehud_angle == 0)
225         {
226             if(autocvar__hud_configure)
227             {
228                 strafehud_hudangle = 45;
229             }
230             else
231             {
232                 strafehud_hudangle = strafehud_range_minangle; // use minimum angle required if dynamically setting hud angle
233             }
234         }
235         else
236         {
237             strafehud_hudangle = bound(1, fabs(autocvar_hud_panel_strafehud_angle), 360) / 2; // limit HUD range to 360 degrees, higher values don't make sense
238         }
239
240         // detect air strafe turning
241         if(strafehud_onground != strafehud_state_onground)
242         {
243             strafehud_state_onground_time = time;
244         }
245         strafehud_state_onground = strafehud_onground;
246         if(strafehud_strafekeys != strafehud_state_strafekeys)
247         {
248             strafehud_state_strafekeys_time = time;
249         }
250         strafehud_state_strafekeys = strafehud_strafekeys;
251         if((strafehud_keys & KEY_FORWARD) || (strafehud_keys & KEY_BACKWARD) || strafehud_iswater || autocvar__hud_configure)
252         {
253             strafehud_turn = false;
254         }
255         else if(strafehud_onground)
256         {
257             if((time - strafehud_state_onground_time) >= strafehud_timeout_ground)
258             {
259                 strafehud_turn = false;
260             }
261         }
262         else // air strafe only
263         {
264             if(strafehud_strafekeys)
265             {
266                 if(((time - strafehud_state_onground_time) >= strafehud_timeout_air) || (strafehud_keys & KEY_JUMP))
267                 {
268                     strafehud_turn = true; // CPMA turning
269                     strafehud_turnangle = strafehud_wishangle;
270                 }
271             }
272             else if((time - strafehud_state_strafekeys_time) >= strafehud_timeout_strafe)
273             {
274                 strafehud_turn = false;
275             }
276         }
277         if(strafehud_turn)
278         {
279             strafehud_maxspeed = PHYS_MAXAIRSTRAFESPEED(strafeplayer); // no crouching here because it doesn't affect air strafing
280             strafehud_wishangle = strafehud_turnangle;
281         }
282
283         strafehud_indicator_minspeed = strafehud_indicator_minspeed < 0 ? strafehud_maxspeed + .1 : strafehud_indicator_minspeed;
284
285         // get current strafing angle ranging from -180° to +180°
286         if(!autocvar__hud_configure)
287         {
288             if(!strafehud_fwd) strafehud_wishangle += strafehud_wishangle < 0 ? 180 : strafehud_wishangle > 0 ? -180 : 0;
289             if(strafehud_speed > 0)
290             {
291                 if(!strafehud_fwd) strafehud_view_angle += strafehud_view_angle < 0 ? 180 : strafehud_view_angle > 0 ? -180 : 0;
292                 strafehud_angle = strafehud_view_angle - strafehud_vel_angle;
293
294                 if     (strafehud_angle >  180) strafehud_angle = -360 + strafehud_angle;
295                 else if(strafehud_angle < -180) strafehud_angle =  360 + strafehud_angle;
296
297                 strafehud_angle = 180 - strafehud_angle;
298                 if(strafehud_angle > 180)
299                 {
300                     strafehud_angle = -fabs(360 - strafehud_angle);
301                 }
302
303                 // making the hud less flickery in case of rounding errors
304                 if(strafehud_angle > 179.9 || strafehud_angle < -179.9)
305                 {
306                     strafehud_currentangle_color = strafehud_alert_color;
307                     strafehud_angle = 0;
308                 }
309                 if(strafehud_angle < .1 && strafehud_angle > -.1)
310                 {
311                     strafehud_angle = 0;
312                 }
313             }
314             else
315             {
316                 strafehud_angle = 0;
317             }
318         }
319         else // simulate turning for HUD setup
320         {
321             if(autocvar__hud_panel_strafehud_center)
322             {
323                 strafehud_angle = strafehud_demo_angle = 0;
324                 strafehud_demo_time = 0;
325                 strafehud_wishangle = 0;
326             }
327             else
328             {
329                 if(autocvar__hud_panel_strafehud_demo && ((time - strafehud_demo_time) >= .025))
330                 {
331                     strafehud_demo_time = time;
332                     strafehud_demo_angle += strafehud_demo_direction;
333                     if(fabs(strafehud_demo_angle) >= 55)
334                     {
335                         strafehud_demo_direction = -strafehud_demo_direction;
336                     }
337                 }
338                 strafehud_angle = strafehud_demo_angle;
339                 strafehud_wishangle = 45 * (strafehud_demo_angle > 0 ? 1 : -1);
340             }
341         }
342
343         if(autocvar_v_flipped)
344         {
345             strafehud_angle = -strafehud_angle;
346             strafehud_wishangle = -strafehud_wishangle;
347         }
348
349         strafehud_moveangle = strafehud_angle + strafehud_wishangle;
350
351         if(strafehud_wishangle != 0)
352         {
353             strafehud_direction = strafehud_wishangle > 0 ? 1 : -1;
354         }
355         else
356         {
357             strafehud_direction = strafehud_moveangle > 0 ? 1 : strafehud_moveangle < 0 ? -1 : 0;
358         }
359
360         // how much is hidden by the current hud angle
361         strafehud_hidden_angle = 180 - strafehud_hudangle;
362         // decelerating at this angle
363         strafehud_maxangle = 90 - fabs(strafehud_wishangle);
364         // best angle to strafe at
365         strafehud_bestangle = (strafehud_speed > strafehud_maxspeed ? acos(strafehud_maxspeed / strafehud_speed) : 0) * RAD2DEG * (strafehud_direction < 0 ? -1 : 1) - strafehud_wishangle;
366         // various offsets and size calculations of hud indicator elements
367         // current angle
368         strafehud_currentangle_size.x = panel_size.x * .005;
369         if(strafehud_currentangle_size.x < 1) strafehud_currentangle_size.x = 1;
370         if(strafehud_mode == 0)
371         {
372             strafehud_currentangle_offset = strafehud_angle/strafehud_hudangle * panel_size.x/2;
373         }
374         else
375         {
376             strafehud_currentangle_offset = bound(-strafehud_hudangle, strafehud_angle, strafehud_hudangle)/strafehud_hudangle * panel_size.x/2 + panel_size.x/2;
377         }
378         strafehud_currentangle_size.y = panel_size.y * 1.5;
379         // best strafe acceleration angle
380         strafehud_bestangle_offset        =  strafehud_bestangle/strafehud_hudangle * panel_size.x/2 + panel_size.x/2;
381         strafehud_mirror_bestangle_offset = -strafehud_bestangle/strafehud_hudangle * panel_size.x/2 + panel_size.x/2;
382         strafehud_bestangle_size.x = panel_size.x * .01;
383         if(strafehud_bestangle_size.x < 1) strafehud_bestangle_size.x = 1;
384         strafehud_mirror_bestangle_size = strafehud_bestangle_size;
385         // shift offset of best strafe angle in angle centered mode
386         if(strafehud_mode == 0)
387         {
388             strafehud_bestangle_offset -= strafehud_currentangle_offset;
389             strafehud_mirror_bestangle_offset -= strafehud_currentangle_offset;
390         }
391         // remove indicator width from offset
392         if(strafehud_direction < 0)
393         {
394             strafehud_bestangle_offset -= strafehud_bestangle_size.x;
395         }
396         else
397         {
398             strafehud_mirror_bestangle_offset -= strafehud_mirror_bestangle_size.x;
399         }
400         // don't draw the angle indicators outside of hud range
401         if(strafehud_bestangle_offset + strafehud_bestangle_size.x > panel_size.x)
402         {
403             if(strafehud_bestangle_offset < panel_size.x)
404             {
405                 strafehud_bestangle_size.x = panel_size.x - strafehud_bestangle_offset;
406             }
407             else
408             {
409                 strafehud_bestangle_size.x = 0;
410             }
411         }
412         if(strafehud_bestangle_offset < 0)
413         {
414             if(strafehud_bestangle_offset + strafehud_bestangle_size.x > 0)
415             {
416                 strafehud_bestangle_size.x += strafehud_bestangle_offset;
417                 strafehud_bestangle_offset = 0;
418             }
419             else
420             {
421                 strafehud_bestangle_size.x = 0;
422             }
423         }
424         // same for the mirrored angle
425         if(strafehud_mirror_bestangle_offset + strafehud_mirror_bestangle_size.x > panel_size.x)
426         {
427             if(strafehud_mirror_bestangle_offset < panel_size.x)
428             {
429                 strafehud_mirror_bestangle_size.x = panel_size.x - strafehud_mirror_bestangle_offset;
430             }
431             else
432             {
433                 strafehud_mirror_bestangle_size.x = 0;
434             }
435         }
436         if(strafehud_mirror_bestangle_offset < 0)
437         {
438             if(strafehud_mirror_bestangle_offset + strafehud_mirror_bestangle_size.x > 0)
439             {
440                 strafehud_mirror_bestangle_size.x += strafehud_mirror_bestangle_offset;
441                 strafehud_mirror_bestangle_offset = 0;
442             }
443             else
444             {
445                 strafehud_mirror_bestangle_size.x = 0;
446             }
447         }
448         // direction indicator
449         strafehud_direction_size_vertical.x = panel_size.x * .0075;
450         if(strafehud_direction_size_vertical.x < 1) strafehud_direction_size_vertical.x = 1;
451         strafehud_direction_size_vertical.y = panel_size.y;
452         strafehud_direction_size_horizontal.x = strafehud_direction_size_vertical.x * 3;
453         strafehud_direction_size_horizontal.y = strafehud_direction_size_vertical.x;
454         // overturn
455         strafehud_mirrorangle = strafehud_maxangle - strafehud_hidden_angle; // how many degrees of overturn area are on the opposite side of the hud
456         strafehud_overturn_size.x = panel_size.x * (strafehud_hudangle - strafehud_maxangle) / (strafehud_hudangle*2);
457         strafehud_mirror_overturn_size.x = panel_size.x * strafehud_mirrorangle / (strafehud_hudangle*2);
458         strafehud_hidden_size = panel_size.x * strafehud_hidden_angle / strafehud_hudangle;
459
460         // if the strafe bar fills the whole hud panel
461         if(!(strafehud_speed >= strafehud_indicator_minspeed) || !(strafehud_direction != 0))
462         {
463             // add a background to the strafe-o-meter
464             if(panel_size.x > 0 && panel_size.y > 0)
465             {
466                 HUD_Panel_DrawProgressBar(panel_pos, panel_size, "progressbar", 1, 0, 0, strafehud_bar_color, strafehud_bar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
467             }
468         }
469
470         // mark the ideal strafe angle
471         if(strafehud_speed >= strafehud_indicator_minspeed) // only draw indicators if strafing is required to gain speed
472         {
473             if(strafehud_direction != 0) // only draw acceleration zones if strafe direction can be determined
474             {
475                 if(strafehud_direction < 0) // turning left
476                 {
477                     // calculate zone in which strafe acceleration happens
478                     strafehud_accelzone_offset = 0;
479                     strafehud_accelzone_size.x = strafehud_bestangle_offset;
480
481                     // calculate overturn area and move acceleration zone
482
483                     // calculate offset of overturn area
484                     strafehud_overturn_offset = 0;
485                     // move/adjust acceleration zone
486                     strafehud_accelzone_offset += strafehud_overturn_size.x;
487                     strafehud_accelzone_size.x -= strafehud_overturn_size.x;
488                     // calculate the remainder of the overturn zone on the opposite side
489                     strafehud_mirror_overturn_offset = panel_size.x - strafehud_mirror_overturn_size.x;
490                     if(strafehud_mode == 0)
491                     {
492                         // acceleration zone shifts in angle centered
493                         strafehud_accelzone_size.x += strafehud_currentangle_offset; // make sure the size is correct even when the offset is shifted
494                         strafehud_accelzone_offset -= strafehud_currentangle_offset;
495
496                         // overturn zone shifts if angle centered
497                         strafehud_overturn_size.x -= strafehud_currentangle_offset;
498                         strafehud_mirror_overturn_size.x += strafehud_currentangle_offset;
499                         strafehud_mirror_overturn_offset -= strafehud_currentangle_offset;
500                         strafehud_mirrorangle += strafehud_angle;
501
502                         if((strafehud_mirror_overturn_size.x + strafehud_hidden_size) < 0)
503                         {
504                             strafehud_overturn_size.x += strafehud_mirror_overturn_size.x + strafehud_hidden_size;
505                             strafehud_overturn_offset -= strafehud_mirror_overturn_size.x + strafehud_hidden_size;
506                         }
507                     }
508                 }
509                 else // turning right
510                 {
511                     // calculate zone in which strafe acceleration happens
512                     strafehud_accelzone_offset = strafehud_bestangle_offset + strafehud_bestangle_size.x;
513                     strafehud_accelzone_size.x = panel_size.x - strafehud_accelzone_offset;
514
515                     // calculate overturn area and move acceleration zone
516
517                     // calculate offset of overturn area
518                     strafehud_overturn_offset = panel_size.x - strafehud_overturn_size.x;
519                     // adjust acceleration zone
520                     strafehud_accelzone_size.x -= strafehud_overturn_size.x;
521                     // calculate the remainder of the overturn zone on the opposite side
522                     strafehud_mirror_overturn_offset = 0;
523                     if(strafehud_mode == 0)
524                     {
525                         // acceleration zone shifts if angle centered
526                         strafehud_accelzone_size.x -= strafehud_currentangle_offset; // make sure the size is correct even when the offset is shifted
527
528                         // overturn zone shifts if angle centered
529                         strafehud_overturn_size.x += strafehud_currentangle_offset;
530                         strafehud_mirror_overturn_size.x -= strafehud_currentangle_offset;
531                         strafehud_overturn_offset -= strafehud_currentangle_offset;
532                         strafehud_mirrorangle -= strafehud_angle;
533
534                         if((strafehud_mirror_overturn_size.x + strafehud_hidden_size) < 0)
535                         {
536                             strafehud_overturn_size.x += strafehud_mirror_overturn_size.x + strafehud_hidden_size;
537                         }
538                     }
539                 }
540
541                 // prevent anything from being drawn outside of the hud if in angle centered mode
542                 if(strafehud_accelzone_size.x < 0)
543                 {
544                     strafehud_accelzone_size.x = 0;
545                 }
546                 if(strafehud_accelzone_offset < 0)
547                 {
548                     strafehud_accelzone_size.x += strafehud_accelzone_offset;
549                     strafehud_accelzone_offset = 0;
550                 }
551                 if((strafehud_accelzone_offset + strafehud_accelzone_size.x) > panel_size.x)
552                 {
553                     strafehud_accelzone_size.x = panel_size.x - strafehud_accelzone_offset;
554                 }
555                 if(strafehud_overturn_size.x < 0)
556                 {
557                     strafehud_overturn_size.x = 0;
558                 }
559                 if(strafehud_overturn_offset < 0)
560                 {
561                     strafehud_overturn_size.x += strafehud_overturn_offset;
562                     strafehud_overturn_offset = 0;
563                 }
564                 if((strafehud_overturn_offset + strafehud_overturn_size.x) > panel_size.x)
565                 {
566                     strafehud_overturn_size.x = panel_size.x - strafehud_overturn_offset;
567                 }
568                 strafehud_accelzone_size.x = max(strafehud_accelzone_size.x, 0);
569                 strafehud_overturn_size.x = max(strafehud_overturn_size.x, 0);
570                 strafehud_accelzone_offset = min(strafehud_accelzone_offset, panel_size.x);
571                 strafehud_overturn_offset = min(strafehud_overturn_offset, panel_size.x);
572
573                 // draw overturn area
574                 if(strafehud_overturn_size.x > 0 && strafehud_overturn_size.y > 0)
575                 {
576                     HUD_Panel_DrawProgressBar(panel_pos + eX * strafehud_overturn_offset, strafehud_overturn_size, "progressbar", 1, 0, 0, strafehud_alert_color, strafehud_bar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
577                 }
578                 // draw remaining overturn area on the opposite side if there is any (180 degree in total)
579                 if(strafehud_mirrorangle > 0 && strafehud_mirror_overturn_size.x > 0 && strafehud_mirror_overturn_size.y > 0)
580                 {
581                     HUD_Panel_DrawProgressBar(panel_pos + eX * strafehud_mirror_overturn_offset, strafehud_mirror_overturn_size, "progressbar", 1, 0, 0, strafehud_alert_color, strafehud_bar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
582                 }
583
584                 // draw acceleration zone
585                 if(strafehud_accelzone_size.x > 0 && strafehud_accelzone_size.y > 0)
586                 {
587                     HUD_Panel_DrawProgressBar(panel_pos + eX * strafehud_accelzone_offset, strafehud_accelzone_size, "progressbar", 1, 0, 0, strafehud_bestangle_color, strafehud_bar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
588                 }
589
590                 // add a background to the strafe-o-meter
591                 if(strafehud_direction < 0) // turning left
592                 {
593                     strafehud_bar_offset = bound(0, strafehud_bestangle_offset + strafehud_bestangle_size.x, panel_size.x);
594                     strafehud_bar_size.x = panel_size.x - strafehud_bar_offset - (panel_size.x - (strafehud_mirrorangle > 0 ? strafehud_mirror_overturn_offset : panel_size.x));
595                 }
596                 else // turning right
597                 {
598                     strafehud_bar_offset = strafehud_mirrorangle > 0 ? strafehud_mirror_overturn_size.x : 0;
599                     strafehud_bar_size.x = panel_size.x - strafehud_bar_offset - (panel_size.x - bound(0, strafehud_bestangle_offset, panel_size.x));
600                 }
601                 if(strafehud_bar_size.x > 0 && strafehud_bar_size.y > 0)
602                 {
603                     HUD_Panel_DrawProgressBar(panel_pos + eX * strafehud_bar_offset, strafehud_bar_size, "progressbar", 1, 0, 0, strafehud_bar_color, strafehud_bar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
604                 }
605                 // if there's free space behind the overturn zone
606                 if(strafehud_mirror_overturn_size.x < 0)
607                 {
608                     strafehud_bar_size.x += strafehud_mirror_overturn_size.x;
609                     if(strafehud_direction < 0) // turning left
610                     {
611                         strafehud_bar_offset = 0;
612                         strafehud_bar_size.x = strafehud_overturn_offset;
613                     }
614                     else // turning right
615                     {
616                         strafehud_bar_offset = strafehud_overturn_size.x + strafehud_overturn_offset;
617                         strafehud_bar_size.x = panel_size.x - strafehud_bar_offset;
618                     }
619                     if(strafehud_bar_size.x > 0 && strafehud_bar_size.y > 0)
620                     {
621                         HUD_Panel_DrawProgressBar(panel_pos + eX * strafehud_bar_offset, strafehud_bar_size, "progressbar", 1, 0, 0, strafehud_bar_color, strafehud_bar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
622                     }
623                 }
624
625                 // draw the direction indicator caps at the sides of the hud
626                 // vertical line
627                 drawfill(panel_pos + eX * (strafehud_direction < 0 ? -strafehud_direction_size_vertical.x : panel_size.x), strafehud_direction_size_vertical, strafehud_direction_color, panel_fg_alpha, DRAWFLAG_NORMAL);
628                 // top horizontal line
629                 drawfill(panel_pos + eX * (strafehud_direction < 0 ? -strafehud_direction_size_vertical.x : panel_size.x - strafehud_direction_size_horizontal.x + strafehud_direction_size_vertical.x) - eY * strafehud_direction_size_horizontal.y, strafehud_direction_size_horizontal, strafehud_direction_color, panel_fg_alpha, DRAWFLAG_NORMAL);
630                 // bottom horizontal line
631                 drawfill(panel_pos + eX * (strafehud_direction < 0 ? -strafehud_direction_size_vertical.x : panel_size.x - strafehud_direction_size_horizontal.x + strafehud_direction_size_vertical.x) + eY * panel_size.y, strafehud_direction_size_horizontal, strafehud_direction_color, panel_fg_alpha, DRAWFLAG_NORMAL);
632
633                 if(strafehud_mirror_bestangle_size.x > 0) // don't draw angle indicator if outside of hud range
634                 {
635                     // draw opposite best strafe angle
636                     drawfill(panel_pos + eX * strafehud_mirror_bestangle_offset, strafehud_mirror_bestangle_size, strafehud_mirror_bestangle_color, panel_fg_alpha, DRAWFLAG_NORMAL);
637                 }
638                 if(strafehud_bestangle_size.x > 0) // don't draw angle indicator if outside of hud range
639                 {
640                     // draw current best strafe angle
641                     drawfill(panel_pos + eX * strafehud_bestangle_offset, strafehud_bestangle_size, strafehud_bestangle_color, panel_fg_alpha, DRAWFLAG_NORMAL);
642                 }
643             }
644             else
645             {
646                 // draw best angles for acceleration
647                 if(strafehud_mirror_bestangle_size.x > 0) // don't draw angle indicator if outside of hud range
648                 {
649                     drawfill(panel_pos + eX * strafehud_mirror_bestangle_offset, strafehud_mirror_bestangle_size, strafehud_mirror_bestangle_color, panel_fg_alpha, DRAWFLAG_NORMAL);
650                 }
651                 if(strafehud_bestangle_size.x > 0) // don't draw angle indicator if outside of hud range
652                 {
653                     drawfill(panel_pos + eX * strafehud_bestangle_offset, strafehud_bestangle_size, strafehud_mirror_bestangle_color, panel_fg_alpha, DRAWFLAG_NORMAL);
654                 }
655             }
656         }
657         else
658         {
659             strafehud_bestangle_anywhere = true; // no indicators, moving forward should suffice to gain speed
660         }
661
662         // draw the actual strafe angle
663         if(!strafehud_bestangle_anywhere) // player gains speed with strafing
664         {
665             if((strafehud_direction > 0 && strafehud_angle >= strafehud_bestangle) ||
666                 (strafehud_direction < 0 && strafehud_angle <= strafehud_bestangle))
667             strafehud_currentangle_color = strafehud_good_color;
668         }
669
670         if(fabs(strafehud_moveangle) > 89.9) // player is overturning
671         {
672             strafehud_currentangle_color = strafehud_alert_color;
673         }
674
675         if(strafehud_speed <= (strafehud_maxspeed + .1) && strafehud_currentangle_color != strafehud_alert_color) // player gains speed without strafing
676         {
677             strafehud_currentangle_color = strafehud_good_color;
678         }
679
680         if(strafehud_mode == 0)
681         {
682             drawfill(panel_pos - eY * ((strafehud_currentangle_size.y - panel_size.y) / 2) + eX * (panel_size.x/2 - strafehud_currentangle_size.x/2), strafehud_currentangle_size, strafehud_currentangle_color, autocvar_hud_panel_strafehud_angle_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
683         }
684         else
685         {
686             drawfill(panel_pos - eY * ((strafehud_currentangle_size.y - panel_size.y) / 2) + eX * (strafehud_currentangle_offset - strafehud_currentangle_size.x/2), strafehud_currentangle_size, strafehud_currentangle_color, autocvar_hud_panel_strafehud_angle_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
687         }
688     }
689 }