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