]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/hud/panel/strafehud.qc
cleaned up strafehud core logic code
[xonotic/xonotic-data.pk3dir.git] / qcsrc / client / hud / panel / strafehud.qc
1 // Name:   StrafeHUD
2 // Author: Juhu
3
4 // FIXME: strafehud doesn't work properly in spectate due to lack of IS_ONGROUND()
5
6 #include "strafehud.qh"
7
8 #include <client/autocvars.qh>
9 #include <client/miscfunctions.qh>
10 #include <common/ent_cs.qh>
11 #include <common/mapinfo.qh>
12 #include <common/mapobjects/trigger/swamp.qh>
13 #include <common/physics/movetypes/movetypes.qh>
14 #include <common/physics/player.qh>
15 #include <lib/csqcmodel/cl_player.qh>
16
17 bool strafehud_fwd = true;
18 float strafehud_demo_angle = -37;
19 float strafehud_demo_direction = 1;
20 float strafehud_demo_time = 0;
21 float strafehud_state_onground_time = 0;
22 float strafehud_state_strafekeys_time = 0;
23 bool strafehud_state_onground = true;
24 bool strafehud_state_strafekeys = false;
25 bool strafehud_turn = false;
26 float strafehud_turnangle;
27
28 void HUD_StrafeHUD()
29 {
30     entity strafeplayer;
31
32     if(!autocvar__hud_configure)
33     {
34         if(!autocvar_hud_panel_strafehud) return;
35         if(spectatee_status == -1 && (autocvar_hud_panel_strafehud == 1 || autocvar_hud_panel_strafehud == 3)) return;
36         if(autocvar_hud_panel_strafehud == 3 && !(ISGAMETYPE(RACE) || ISGAMETYPE(CTS))) return;
37     }
38
39     HUD_Panel_LoadCvars();
40
41     if (autocvar_hud_panel_strafehud_dynamichud)
42         HUD_Scale_Enable();
43     else
44         HUD_Scale_Disable();
45     HUD_Panel_DrawBg();
46     if(panel_bg_padding)
47     {
48         panel_pos  += '1 1 0' * panel_bg_padding;
49         panel_size -= '2 2 0' * panel_bg_padding;
50     }
51
52     if(spectatee_status > 0)
53     {
54         strafeplayer = CSQCModel_server2csqc(player_localentnum - 1);
55     }
56     else
57     {
58         strafeplayer = csqcplayer;
59     }
60
61     // draw strafehud
62     if(csqcplayer && strafeplayer)
63     {
64         // autocvars
65         float strafehud_bar_alpha                  = autocvar_hud_panel_strafehud_bar_alpha;
66         vector strafehud_bar_color                 = autocvar_hud_panel_strafehud_bar_color;
67         vector strafehud_bestangle_color           = autocvar_hud_panel_strafehud_indicator_color;
68         vector strafehud_bestangle_opposite_color  = autocvar_hud_panel_strafehud_indicator_switch_color;
69         vector strafehud_good_color                = autocvar_hud_panel_strafehud_good_color;
70         vector strafehud_warning_color             = autocvar_hud_panel_strafehud_warning_color;
71         vector strafehud_alert_color               = autocvar_hud_panel_strafehud_alert_color;
72         vector strafehud_direction_color           = autocvar_hud_panel_strafehud_direction_color;
73         float strafehud_timeout_air                = autocvar_hud_panel_strafehud_timeout_air;    // timeout for slick ramps
74         float strafehud_timeout_ground             = autocvar_hud_panel_strafehud_timeout_ground; // timeout for strafe jumping in general
75         float strafehud_timeout_strafe             = autocvar_hud_panel_strafehud_timeout_strafe; // timeout for jumping with strafe keys only
76         float strafehud_indicator_minspeed         = autocvar_hud_panel_strafehud_indicator_minspeed;
77
78         // physics
79         float  strafehud_onground                  = IS_ONGROUND(strafeplayer);
80         float  strafehud_speed                     = !autocvar__hud_configure ? vlen(vec2(csqcplayer.velocity)) : 1337; // use local csqcmodel entity for this even when spectating, flickers too much otherwise
81         float  strafehud_maxspeed_crouch_mod       = IS_DUCKED(strafeplayer) ? .5 : 1;
82         float  strafehud_maxspeed_swamp_mod        = strafeplayer.in_swamp ? strafeplayer.swamp_slowdown : 1;
83         float  strafehud_maxspeed_phys             = strafehud_onground ? PHYS_MAXSPEED(strafeplayer) : PHYS_MAXAIRSPEED(strafeplayer);
84         float  strafehud_maxspeed                  = !autocvar__hud_configure ? (strafehud_maxspeed_phys * strafehud_maxspeed_crouch_mod * strafehud_maxspeed_swamp_mod) : 320;
85         float  strafehud_vel_angle                 = vectoangles(strafeplayer.velocity).y;
86         float  strafehud_view_angle                = view_angles.y + 180;
87         float  strafehud_angle;
88         float  strafehud_direction;
89         vector strafehud_movement                  = PHYS_INPUT_MOVEVALUES(strafeplayer);
90         int    strafehud_keys                      = STAT(PRESSED_KEYS);
91         float  strafehud_wishangle;
92         float  strafehud_shiftangle;
93         float  strafehud_moveangle;
94
95         // HUD
96         float  strafehud_hudangle;
97         vector strafehud_currentangle_color        = strafehud_warning_color;
98         vector strafehud_currentangle_size         = '0 0 0';
99         float  strafehud_currentangle_offset;
100         vector strafehud_bestangle_size            = '0 0 0';
101         bool   strafehud_bestangle_anywhere        = false;
102         float  strafehud_bestangle                 = 0;
103         float  strafehud_bestangle_offset;
104         float  strafehud_bestangle_opposite_offset;
105         float  strafehud_accelzone_offset;
106         vector strafehud_accelzone_size            = panel_size;
107         float  strafehud_overturn_offset;
108         vector strafehud_overturn_size             = panel_size;
109         float  strafehud_mirrorangle;
110         float  strafehud_mirror_overturn_offset;
111         vector strafehud_mirror_overturn_size      = panel_size;
112         vector strafehud_direction_size_vertical   = '0 0 0';
113         vector strafehud_direction_size_horizontal = '0 0 0';
114         float  strafehud_maxangle;
115
116         strafehud_indicator_minspeed = strafehud_indicator_minspeed < 0 ? strafehud_maxspeed + .1 : strafehud_indicator_minspeed;
117
118         // determine whether the player is strafing forwards or backwards
119         if(strafeplayer == csqcplayer) // if entity is local player
120         {
121             if(strafehud_movement_x > 0)
122             {
123                 strafehud_fwd = true;
124             }
125             else if(strafehud_movement_x < 0)
126             {
127                 strafehud_fwd = false;
128             }
129         }
130         else // alternatively determine direction by querying pressed keys
131         {
132             if((strafehud_keys & KEY_FORWARD) && !(strafehud_keys & KEY_BACKWARD))
133             {
134                 strafehud_fwd = true;
135             }
136             else if(!(strafehud_keys & KEY_FORWARD) && (strafehud_keys & KEY_BACKWARD))
137             {
138                 strafehud_fwd = false;
139             }
140         }
141
142         // determine player wishdir
143         if(strafeplayer == csqcplayer) // if entity is local player
144         {
145             if(strafehud_movement_x == 0)
146             {
147                 if(strafehud_movement_y < 0)
148                 {
149                     strafehud_wishangle = -90;
150                 }
151                 else if(strafehud_movement_y > 0)
152                 {
153                     strafehud_wishangle = 90;
154                 }
155                 else
156                 {
157                     strafehud_wishangle = 0;
158                 }
159             }
160             else
161             {
162                 if(strafehud_movement_y == 0)
163                 {
164                     strafehud_wishangle = 0;
165                 }
166                 else
167                 {
168                     strafehud_wishangle = RAD2DEG * atan2(strafehud_movement_y, strafehud_movement_x);
169                 }
170             }
171         }
172         else // alternatively calculate wishdir by querying pressed keys
173         {
174             if(strafehud_keys & KEY_FORWARD)
175             {
176                 strafehud_wishangle = 45;
177             }
178             else if(strafehud_keys & KEY_BACKWARD)
179             {
180                 strafehud_wishangle = 135;
181             }
182             else
183             {
184                 strafehud_wishangle = 90;
185             }
186             if(strafehud_keys & KEY_LEFT)
187             {
188                 strafehud_wishangle *= -1;
189             }
190             else if(!(strafehud_keys & KEY_RIGHT))
191             {
192                 strafehud_wishangle = 0;
193             }
194         }
195
196         // determine how much the angle shifts in the hud
197         strafehud_shiftangle = fabs(strafehud_wishangle) % 90;
198         if(strafehud_shiftangle > 45)
199         {
200             strafehud_shiftangle = 45 - fabs(remainder(strafehud_wishangle, 45));
201         }
202         strafehud_shiftangle = 90 - strafehud_shiftangle;
203
204         if(autocvar_hud_panel_strafehud_angle == 0)
205         {
206             if(autocvar__hud_configure)
207             {
208                 strafehud_hudangle = 45;
209             }
210             else
211             {
212                 strafehud_hudangle = strafehud_shiftangle;
213             }
214         }
215         else
216         {
217             strafehud_hudangle = bound(1, fabs(autocvar_hud_panel_strafehud_angle), 360) / 2; // limit HUD range to 360 degrees, higher values don't make sense and break the code
218         }
219
220         // detecting strafe turning
221         if(!autocvar__hud_configure)
222         {
223             if(strafehud_onground != strafehud_state_onground)
224             {
225                 strafehud_state_onground_time = time;
226             }
227             strafehud_state_onground = strafehud_onground;
228             if((fabs(strafehud_wishangle) == 90) != strafehud_state_strafekeys)
229             {
230                 strafehud_state_strafekeys_time = time;
231             }
232             strafehud_state_strafekeys = fabs(strafehud_wishangle) == 90;
233             if((strafehud_keys & KEY_FORWARD) || (strafehud_keys & KEY_BACKWARD))
234             {
235                 strafehud_turn = false;
236             }
237             else if(strafehud_onground)
238             {
239                 if((time - strafehud_state_onground_time) >= strafehud_timeout_ground)
240                 {
241                     strafehud_turn = false;
242                 }
243             }
244             else // air strafe only
245             {
246                 if(fabs(strafehud_wishangle) == 90)
247                 {
248                     if((time - strafehud_state_onground_time) >= strafehud_timeout_air)
249                     {
250                         strafehud_turn = true; // CPMA turning
251                         strafehud_turnangle = strafehud_wishangle;
252                     }
253                 }
254                 else if((time - strafehud_state_strafekeys_time) >= strafehud_timeout_strafe)
255                 {
256                     strafehud_turn = false;
257                 }
258             }
259             if(strafehud_turn)
260             {
261                 strafehud_maxspeed = PHYS_MAXAIRSTRAFESPEED(strafeplayer) * strafehud_maxspeed_swamp_mod; // no crouching here because it doesn't affect air strafing
262                 strafehud_wishangle = strafehud_turnangle;
263             }
264         }
265
266         // add a background to the strafe-o-meter
267         HUD_Panel_DrawProgressBar(panel_pos, panel_size, "progressbar", 1, 0, 0, strafehud_bar_color, strafehud_bar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
268
269         // get current strafing angle ranging from -180° to +180°
270         if(!autocvar__hud_configure)
271         {
272             if(!strafehud_fwd) strafehud_wishangle += strafehud_wishangle < 0 ? 180 : strafehud_wishangle > 0 ? -180 : 0;
273             if(strafehud_speed > 0)
274             {
275                 if(!strafehud_fwd) strafehud_view_angle += strafehud_view_angle < 0 ? 180 : strafehud_view_angle > 0 ? -180 : 0;
276                 strafehud_angle = strafehud_view_angle - strafehud_vel_angle;
277
278                 if     (strafehud_angle >  180) strafehud_angle = -360 + strafehud_angle;
279                 else if(strafehud_angle < -180) strafehud_angle =  360 + strafehud_angle;
280
281                 strafehud_angle = 180 - strafehud_angle;
282                 if(strafehud_angle > 180)
283                 {
284                     strafehud_angle = -fabs(360 - strafehud_angle);
285                 }
286
287                 // making the hud less flickery in case of rounding errors
288                 if(strafehud_angle > 179.9 || strafehud_angle < -179.9)
289                 {
290                     strafehud_currentangle_color = strafehud_alert_color;
291                     strafehud_angle = 0;
292                 }
293                 if(strafehud_angle < .1 && strafehud_angle > -.1)
294                 {
295                     strafehud_angle = 0;
296                 }
297             }
298             else
299             {
300                 strafehud_angle = 0;
301             }
302         }
303         else // simulate turning for HUD setup
304         {
305             if(autocvar__hud_panel_strafehud_demo && ((time - strafehud_demo_time) >= .025))
306             {
307                 strafehud_demo_time = time;
308                 strafehud_demo_angle += 1 * strafehud_demo_direction;
309                 if(fabs(strafehud_demo_angle) >= 55)
310                 {
311                     strafehud_demo_direction = -strafehud_demo_direction;
312                 }
313             }
314             strafehud_angle = strafehud_demo_angle;
315             strafehud_wishangle = 45 * (strafehud_demo_angle > 0 ? 1 : -1);
316         }
317
318         if (autocvar_v_flipped)
319         {
320             strafehud_angle = -strafehud_angle;
321             strafehud_wishangle = -strafehud_wishangle;
322         }
323
324         strafehud_moveangle = strafehud_angle + strafehud_wishangle;
325
326         if(strafehud_wishangle != 0)
327         {
328             strafehud_direction = strafehud_wishangle > 0 ? 1 : -1;
329         }
330         else
331         {
332             strafehud_direction = strafehud_moveangle > 0 ? 1 : strafehud_moveangle < 0 ? -1 : 0;
333         }
334
335         // decelerating at this angle
336         strafehud_maxangle = 90 - fabs(strafehud_wishangle);
337         // best angle to strafe at
338         strafehud_bestangle = (strafehud_speed > strafehud_maxspeed ? acos(strafehud_maxspeed / strafehud_speed) : 0) * RAD2DEG * (strafehud_direction < 0 ? -1 : 1) - strafehud_wishangle;
339         // various offsets and size calculations of hud indicators elements
340         // best strafe acceleration angle
341         strafehud_bestangle_offset          = floor( strafehud_bestangle/strafehud_hudangle * panel_size.x/2 + panel_size.x/2 + .5);
342         strafehud_bestangle_opposite_offset = floor(-strafehud_bestangle/strafehud_hudangle * panel_size.x/2 + panel_size.x/2 + .5);
343         strafehud_bestangle_size.x = floor(panel_size.x * .01 + .5);
344         strafehud_bestangle_size.y = panel_size.y;
345         // current angle
346         strafehud_currentangle_offset = floor(bound(-strafehud_hudangle, strafehud_angle, strafehud_hudangle)/strafehud_hudangle * panel_size.x/2 + panel_size.x/2 + .5);
347         strafehud_currentangle_size.x = floor(panel_size.x * .005 + .5);
348         strafehud_currentangle_size.y = floor(panel_size.y * 1.5 + .5);
349         // direction indicator
350         strafehud_direction_size_vertical.x = floor(panel_size.x * .0075 + .5);
351         strafehud_direction_size_vertical.y = panel_size.y;
352         strafehud_direction_size_horizontal.x = floor(strafehud_direction_size_vertical.x * 3 + .5);
353         strafehud_direction_size_horizontal.y = strafehud_direction_size_vertical.x;
354         // overturn
355         strafehud_mirrorangle = 90 - strafehud_shiftangle - (180 - strafehud_hudangle);
356         strafehud_overturn_size.x = floor((panel_size.x * (strafehud_hudangle - strafehud_maxangle) / strafehud_hudangle) / 2 + .5);
357         strafehud_mirror_overturn_size.x = panel_size.x * strafehud_mirrorangle / 360;
358
359         switch(autocvar_hud_panel_strafehud_mode)
360         {
361             default:
362             case 0: // view centered
363             // mark the ideal strafe angle
364             if(strafehud_speed >= strafehud_indicator_minspeed) // draw indicators if strafing is required to gain speed
365             {
366                 if (fabs(strafehud_bestangle) <= strafehud_hudangle) // don't draw angle indicator and acceleration zones if outside of hud range
367                 {
368                     if (strafehud_direction != 0) // only draw acceleration zones if strafe direction can be determined
369                     {
370                         // calculate zone in which strafe acceleration happens
371                         if(strafehud_direction < 0) // moving left
372                         {
373                             strafehud_accelzone_offset = 0;
374                             strafehud_accelzone_size.x = strafehud_bestangle_offset;
375                         }
376                         else // moving right
377                         {
378                             strafehud_accelzone_offset = strafehud_bestangle_offset + strafehud_bestangle_size.x;
379                             strafehud_accelzone_size.x = panel_size.x - strafehud_accelzone_offset;
380                         }
381                         if(strafehud_hudangle > strafehud_maxangle) // draw overturn area and move acceleration zone
382                         {
383                             if(strafehud_direction < 0) // moving left
384                             {
385                                 // calculate offset of overturn area
386                                 strafehud_overturn_offset = 0;
387                                 // move/adjust acceleration zone
388                                 strafehud_accelzone_offset += strafehud_overturn_size.x;
389                                 strafehud_accelzone_size.x -= strafehud_overturn_size.x;
390                                 // draw the remainder of the overturn zone on the opposite side
391                                 strafehud_mirror_overturn_offset = panel_size.x - strafehud_mirror_overturn_size.x;
392                             }
393                             else // moving right
394                             {
395                                 // calculate offset of overturn area
396                                 strafehud_overturn_offset = panel_size.x - strafehud_overturn_size.x;
397                                 // adjust acceleration zone
398                                 strafehud_accelzone_size.x -= strafehud_overturn_size.x;
399                                 // draw the remainder of the overturn zone on the opposite side
400                                 strafehud_mirror_overturn_offset = 0;
401                             }
402                             // draw overturn area
403                             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);
404                             // draw remaining overturn area on the opposite side if there is any (180 degree in total)
405                             if(strafehud_mirrorangle > 0)
406                             {
407                                 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);
408                             }
409                         }
410                         // draw acceleration zone
411                         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);
412
413                         // draw the direction indicator caps at the sides of the hud
414                         // vertical line
415                         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);
416                         // top horizontal line
417                         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);
418                         // bottom horizontal line
419                         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);
420
421                         // draw opposite best strafe angle zone
422                         drawfill(panel_pos + eX * (strafehud_bestangle_opposite_offset - (-strafehud_direction < 0 ? strafehud_bestangle_size.x : 0)), strafehud_bestangle_size, strafehud_bestangle_opposite_color, panel_fg_alpha, DRAWFLAG_NORMAL);
423                         // draw current best strafe angle zone
424                         drawfill(panel_pos + eX * (strafehud_bestangle_offset - (strafehud_direction < 0 ? strafehud_bestangle_size.x : 0)), strafehud_bestangle_size, strafehud_bestangle_color, panel_fg_alpha, DRAWFLAG_NORMAL);
425                     }
426                     else
427                     {
428                         // draw best angles for acceleration
429                         drawfill(panel_pos + eX * (strafehud_bestangle_opposite_offset - strafehud_bestangle_size.x), strafehud_bestangle_size, strafehud_bestangle_opposite_color, panel_fg_alpha, DRAWFLAG_NORMAL);
430                         drawfill(panel_pos + eX * (strafehud_bestangle_offset), strafehud_bestangle_size, strafehud_bestangle_opposite_color, panel_fg_alpha, DRAWFLAG_NORMAL);
431                     }
432                 }
433             }
434             else
435             {
436                 strafehud_bestangle_anywhere = true; // no indicators, moving forward should suffice to gain speed
437             }
438
439             // draw the actual strafe angle
440             if (!strafehud_bestangle_anywhere) // player gains speed with strafing
441             {
442                 if ((strafehud_direction > 0 && strafehud_angle >= strafehud_bestangle) ||
443                     (strafehud_direction < 0 && strafehud_angle <= strafehud_bestangle))
444                 strafehud_currentangle_color = strafehud_good_color;
445             }
446
447             if (fabs(strafehud_moveangle) > 89.9) // player is overturning
448             {
449                 strafehud_currentangle_color = strafehud_alert_color;
450             }
451
452             if (strafehud_speed <= (strafehud_maxspeed + .1) && strafehud_currentangle_color != strafehud_alert_color) // player gains speed without strafing
453             {
454                 strafehud_currentangle_color = strafehud_good_color;
455             }
456
457             drawfill(panel_pos - '0 1 0'*floor(panel_size.y * .25 + .5) + 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);
458             break;
459             case 1: // angle centered
460                 // TODO: implement angle centered strafehud
461         }
462     }
463 }