From 3dc54172e26c6301c1404e021390db08452adc7e Mon Sep 17 00:00:00 2001 From: Juhu <5894800-Juhu_@users.noreply.gitlab.com> Date: Wed, 22 Jan 2025 11:43:57 +0000 Subject: [PATCH] StrafeHUD-Next, new StrafeHUD features + fixes and refactoring --- _hud_common.cfg | 69 +- qcsrc/client/hud/panel/_mod.inc | 2 + qcsrc/client/hud/panel/_mod.qh | 2 + qcsrc/client/hud/panel/strafehud.qc | 1978 +++++------------ qcsrc/client/hud/panel/strafehud.qh | 89 +- qcsrc/client/hud/panel/strafehud/_mod.inc | 5 + qcsrc/client/hud/panel/strafehud/_mod.qh | 5 + qcsrc/client/hud/panel/strafehud/draw.qc | 336 +++ qcsrc/client/hud/panel/strafehud/draw.qh | 9 + qcsrc/client/hud/panel/strafehud/draw_core.qc | 248 +++ qcsrc/client/hud/panel/strafehud/draw_core.qh | 8 + qcsrc/client/hud/panel/strafehud/extra.qc | 270 +++ qcsrc/client/hud/panel/strafehud/extra.qh | 12 + qcsrc/client/hud/panel/strafehud/util.qc | 336 +++ qcsrc/client/hud/panel/strafehud/util.qh | 23 + 15 files changed, 1979 insertions(+), 1413 deletions(-) create mode 100644 qcsrc/client/hud/panel/strafehud/_mod.inc create mode 100644 qcsrc/client/hud/panel/strafehud/_mod.qh create mode 100644 qcsrc/client/hud/panel/strafehud/draw.qc create mode 100644 qcsrc/client/hud/panel/strafehud/draw.qh create mode 100644 qcsrc/client/hud/panel/strafehud/draw_core.qc create mode 100644 qcsrc/client/hud/panel/strafehud/draw_core.qh create mode 100644 qcsrc/client/hud/panel/strafehud/extra.qc create mode 100644 qcsrc/client/hud/panel/strafehud/extra.qh create mode 100644 qcsrc/client/hud/panel/strafehud/util.qc create mode 100644 qcsrc/client/hud/panel/strafehud/util.qh diff --git a/_hud_common.cfg b/_hud_common.cfg index 4266918ce..05992fce3 100644 --- a/_hud_common.cfg +++ b/_hud_common.cfg @@ -166,11 +166,10 @@ seta hud_panel_scoreboard_itemstats_showdelay_minpos 0.75 "delay displaying the seta _hud_panel_strafehud_demo "0" "StrafeHUD changes angle during configure" seta hud_panel_strafehud_mode "0" "StrafeHUD mode which controls whether the StrafeHUD is centered at \"0\" = view angle, \"1\" = velocity angle" -seta hud_panel_strafehud_range "90" "the angle range up to 360 degrees displayed on the StrafeHUD, \"0\" = dynamic (chooses the minimum range required to still see the whole area needed for accelerating)" -seta hud_panel_strafehud_range_sidestrafe "-1" "the angle range up to 360 degrees displayed on the StrafeHUD when side strafing, \"0\" = dynamic (chooses the minimum range required to still see the whole area needed for accelerating), \"-1\" = same as the normal range" -seta hud_panel_strafehud_style "2" "\"0\" = no styling, \"1\" = progress bar style for the strafe bar, \"2\" = gradient for the strafe bar" +seta hud_panel_strafehud_range "90" "the angle range up to 360 degrees displayed on the StrafeHUD; \"-1\" = current fov, \"0\" = dynamic (chooses the minimum range required to still see the whole area needed for accelerating)" +seta hud_panel_strafehud_range_sidestrafe "-2" "the angle range up to 360 degrees displayed on the StrafeHUD when side strafing, \"0\" = dynamic (chooses the minimum range required to still see the whole area needed for accelerating), \"-1\" = current fov, \"-2\" = same as the normal range" +seta hud_panel_strafehud_style "2" "\"0\" = no styling, \"1\" = progress bar style for the strafe bar, \"2\" = accelerated gradient for the strafe bar (no nonlinear projection for gradient color/opacity), \"3\" = software gradient for the strafe bar (slow)" seta hud_panel_strafehud_unit_show "1" "show units" -seta hud_panel_strafehud_uncapped "0" "remove some safety restrictions, useful to set thinner indicator lines down to 1px or for trying out higher values for some performance degrading operations (WARNING: elements may turn invisible if too thin, other configurations may crash your game or look horribly ugly)" seta hud_panel_strafehud_onground_mode "2" "handling of landing at speeds where friction is higher than optimal acceleration; \"0\" = fill the whole HUD with overturn, \"1\" = show zones regardless, \"2\" = show the zones as if airborne (useful for quake2 and quake3 physics)" seta hud_panel_strafehud_onground_friction "1" "account for friction in calculations" seta hud_panel_strafehud_bar_preaccel "1" "extend the acceleration zone by the strafe meter zone before full acceleration can be achieved" @@ -182,31 +181,43 @@ seta hud_panel_strafehud_bar_accel_color "0 1 0" "color of the strafe meter acce seta hud_panel_strafehud_bar_accel_alpha "0.5" "opacity of the strafe meter acceleration zone" seta hud_panel_strafehud_bar_overturn_color "1 0 1" "color of the strafe meter overturn zone" seta hud_panel_strafehud_bar_overturn_alpha "0.5" "opacity of the strafe meter overturn zone" -seta hud_panel_strafehud_angle_style "0" "set the angle indicator style; \"0\" = none, \"1\" = solid line, \"2\" = dashed line" -seta hud_panel_strafehud_angle_dashes "4" "determines the amount of dashes if the angle indicator uses a dashed line" seta hud_panel_strafehud_angle_alpha "0.8" "opacity of the indicator showing the player's current angle" -seta hud_panel_strafehud_angle_height "1" "height of the indicator showing the player's current angle (relative to the panel height)" -seta hud_panel_strafehud_angle_width "0.001" "width of the indicator showing the player's current angle (relative to the panel width)" seta hud_panel_strafehud_angle_preaccel_color "0 1 1" "color of the indicator showing the player's current angle if it is within the pre-acceleration zone" seta hud_panel_strafehud_angle_neutral_color "1 1 0" "color of the indicator showing the player's current angle if it is within the neutral zone" seta hud_panel_strafehud_angle_accel_color "0 1 1" "color of the indicator showing the player's current angle if it is within the acceleration zone" seta hud_panel_strafehud_angle_overturn_color "1 0 1" "color of the indicator showing the player's current angle if it is within the overturn zone" -seta hud_panel_strafehud_angle_arrow "1" "set the angle indicator's arrow style; \"0\" = none, \"1\" = top, \"2\" = bottom, \"3\" = both" -seta hud_panel_strafehud_angle_arrow_size "0.5" "size of the arrow (relative to the panel height)" +seta hud_panel_strafehud_angle_line "0" "defines the number of dashes of the indicator line showing the player's current angle; \"0\" = no line, \"1\" = solid line" +seta hud_panel_strafehud_angle_line_width "0.001" "width of the indicator line showing the player's current angle (relative to the panel width)" +seta hud_panel_strafehud_angle_line_height "1" "height of the indicator line showing the player's current angle (relative to the panel height)" +seta hud_panel_strafehud_angle_arrow "1" "arrow style of the angle indicator showing the player's current angle; \"0\" = none, \"1\" = top, \"2\" = bottom, \"3\" = both" +seta hud_panel_strafehud_angle_arrow_size "0.5" "arrow size of the indicator showing the player's current angle (relative to the panel height)" seta hud_panel_strafehud_bestangle "1" "\"1\" = enable a ghost angle indicator showing the best angle to gain maximum acceleration, \"2\" = only when side strafing" seta hud_panel_strafehud_bestangle_color "1 1 1" "color of the indicator showing the best angle to gain maximum acceleration" seta hud_panel_strafehud_bestangle_alpha "0.5" "opacity of the indicator showing the best angle to gain maximum acceleration" -seta hud_panel_strafehud_switch "1" "\"1\" = enable the switch indicator showing the angle to move to when switching sides, \"2\" = show the normal switch indicators when W-turning, \"3\" = also while side strafing" -seta hud_panel_strafehud_switch_minspeed "-1" "minimum speed in qu/s at which switch indicator(s) which are used to aid changing strafe direction will be shown; \"-1\" = dynamic" -seta hud_panel_strafehud_switch_color "1 1 0" "color of the switch indicator" -seta hud_panel_strafehud_switch_alpha "1" "opacity of the switch indicator" -seta hud_panel_strafehud_switch_width "0.003" "width of the strafe angle indicator(s) (relative to the strafe bar width)" +seta hud_panel_strafehud_bestangle_line "0" "defines the number of dashes of the best angle indicator line; \"0\" = no line, \"1\" = solid line" +seta hud_panel_strafehud_bestangle_line_width "0.001" "width of the best angle indicator line (relative to the panel width)" +seta hud_panel_strafehud_bestangle_line_height "1" "height of the best angle indicator line (relative to the panel height)" +seta hud_panel_strafehud_bestangle_arrow "1" "arrow style of the best angle indicator; \"0\" = none, \"1\" = top, \"2\" = bottom, \"3\" = both" +seta hud_panel_strafehud_bestangle_arrow_size "0.5" "arrow size of the best angle indicator (relative to the panel height)" +seta hud_panel_strafehud_switch "1" "\"1\" = enable the strafe angle indicator showing the angle to move to when changing side, \"2\" = show the normal switch indicators when W-turning, \"3\" = also while side strafing" +seta hud_panel_strafehud_switch_minspeed "-1" "minimum speed in qu/s at which angle indicator(s) which are used to aid changing strafe direction will be shown; \"-1\" = dynamic" +seta hud_panel_strafehud_switch_color "1 1 0" "color of the strafe angle indicators for changing strafe direction" +seta hud_panel_strafehud_switch_alpha "0.5" "opacity of the strafe angle indicators for changing strafe direction" +seta hud_panel_strafehud_switch_line "0" "defines the number of dashes of the change angle indicator line; \"0\" = no line, \"1\" = solid line" +seta hud_panel_strafehud_switch_line_width "0.001" "width of the change angle indicator line (relative to the panel width)" +seta hud_panel_strafehud_switch_line_height "1" "height of the change angle indicator line (relative to the panel height)" +seta hud_panel_strafehud_switch_arrow "1" "arrow style of the change angle indicator; \"0\" = none, \"1\" = top, \"2\" = bottom, \"3\" = both" +seta hud_panel_strafehud_switch_arrow_size "0.5" "arrow size of the change angle indicator (relative to the panel height)" seta hud_panel_strafehud_wturn "1" "enable W-turn indicators showing the angle to rotate your velocity as fast as possible; \"1\" = only if W-turning, \"2\" = also while strafing normally, \"3\" = also while side strafing" -seta hud_panel_strafehud_wturn_color "0 1 1" "color of the W-turn indicators" -seta hud_panel_strafehud_wturn_alpha "1" "opacity of the W-turn indicators" -seta hud_panel_strafehud_wturn_width "0.003" "width of the W-turn indicators (relative to the strafe bar width)" +seta hud_panel_strafehud_wturn_color "0 0 1" "color of the W-turn indicators" +seta hud_panel_strafehud_wturn_alpha "0.5" "opacity of the W-turn indicators" seta hud_panel_strafehud_wturn_proper "0" "use the proper formula to calculate W-turn indicators (WARNING: loses accuracy at high speeds)" seta hud_panel_strafehud_wturn_unrestricted "0" "enable W-turn indicators even when W-turning gives acceleration (WARNING: not completely accurate)" +seta hud_panel_strafehud_wturn_line "0" "defines the number of dashes of the W-turn angle indicator line; \"0\" = no line, \"1\" = solid line" +seta hud_panel_strafehud_wturn_line_width "0.001" "width of the W-turn angle indicator line (relative to the panel width)" +seta hud_panel_strafehud_wturn_line_height "1" "height of the W-turn angle indicator line (relative to the panel height)" +seta hud_panel_strafehud_wturn_arrow "1" "arrow style of the W-turn angle indicator; \"0\" = none, \"1\" = top, \"2\" = bottom, \"3\" = both" +seta hud_panel_strafehud_wturn_arrow_size "0.5" "arrow size of the W-turn angle indicator (relative to the panel height)" seta hud_panel_strafehud_direction "0" "enable direction caps to see in which direction you are currently strafing" seta hud_panel_strafehud_direction_color "0 0.5 1" "color of the direction caps which indicate the direction the player is currently strafing towards" seta hud_panel_strafehud_direction_alpha "1" "opacity of the direction caps which indicate the direction the player is currently strafing towards" @@ -221,16 +232,38 @@ seta hud_panel_strafehud_slickdetector_height "0.125" "height of the slick detec seta hud_panel_strafehud_startspeed "1" "enable the start speed indicator which shows you the speed you had while passing the start trigger of a race map" seta hud_panel_strafehud_startspeed_fade "4" "fade time (in seconds) of the start speed text" seta hud_panel_strafehud_startspeed_color "1 0.75 0" "color of the start speed text" +seta hud_panel_strafehud_startspeed_pos "0 -1" "position of the start speed text (relative to the panel), the Y coordinate must be <= -1 (below) or >= 1 (above) the panel" seta hud_panel_strafehud_startspeed_size "1.5" "size of the start speed text (relative to the panel height)" seta hud_panel_strafehud_jumpheight "0" "enable the jump height indicator which tells you how high you jumped" seta hud_panel_strafehud_jumpheight_fade "4" "fade time (in seconds) of the jump height text" seta hud_panel_strafehud_jumpheight_min "50" "minimum jump height to display in the selected unit" seta hud_panel_strafehud_jumpheight_color "0 1 0.75" "color of the jump height text" +seta hud_panel_strafehud_jumpheight_pos "0 -2" "position of the jump height text (relative to the panel), the Y coordinate must be <= -1 (below) or >= 1 (above) the panel" seta hud_panel_strafehud_jumpheight_size "1.5" "size of the jump height text (relative to the panel height)" seta hud_panel_strafehud_timeout_ground "0.1" "time (in seconds) after take off before changing to air strafe physics when not jumping (visually more consistent HUD while on downwards slick ramps)" seta hud_panel_strafehud_timeout_turn "0.1" "time (in seconds) after releasing the strafe keys before changing mode (visually more consistent HUD while switching between left/right strafe turning)" seta hud_panel_strafehud_antiflicker_angle "0.01" "how many degrees from 0° to 180° the HUD ignores if it could cause visual disturbances otherwise (and to counter rounding errors)" seta hud_panel_strafehud_fps_update "0.5" "update interval (in seconds) of the frametime to calculate the optimal angle, smaller values may cause flickering" +seta hud_panel_strafehud_sonar "0" "\"1\" = enable the strafe sonar" +seta hud_panel_strafehud_sonar_audio "misc/talk" "audio to play for sonar" +seta hud_panel_strafehud_sonar_start "0.5" "how optimal from 0 to 1 your strafing angle has to be for the strafe sonar to activate" +seta hud_panel_strafehud_sonar_interval_start "0.333333" "strafe sonar sound interval in seconds" +seta hud_panel_strafehud_sonar_interval_range "-0.222222" "dynamic sound interval range in seconds of the strafe sonar as you approach the optimal angle" +seta hud_panel_strafehud_sonar_interval_exponent "1" "exponent of the dynamic sound interval range of the strafe sonar" +seta hud_panel_strafehud_sonar_volume_start "0.333333" "sound volume of the strafe sonar" +seta hud_panel_strafehud_sonar_volume_range "0.666666" "dynamic volume range of the strafe sonar as you approach the optimal angle" +seta hud_panel_strafehud_sonar_volume_exponent "1" "exponent of the dynamic volume range of the strafe sonar" +seta hud_panel_strafehud_sonar_pitch_start "0.9" "playback speed of the strafe sonar" +seta hud_panel_strafehud_sonar_pitch_range "0.1" "dynamic playback speed range of the strafe sonar as you approach the optimal angle" +seta hud_panel_strafehud_sonar_pitch_exponent "1" "exponent of the dynamic playback speed range of the strafe sonar" +seta hud_panel_strafehud_vangle "0" "\"1\" = enable the vertical angle indicator" +seta hud_panel_strafehud_vangle_color "0.75 0.75 0.75" "color of the vertical angle text" +seta hud_panel_strafehud_vangle_pos "-0.25 1" "position of the vertical angle text (relative to the panel), the Y coordinate must be <= -1 (below) or >= 1 (above) the panel" +seta hud_panel_strafehud_vangle_size "1" "size of the vertical angle text (relative to the panel height)" +seta hud_panel_strafehud_strafeefficiency "0" "\"1\" = enable the strafe efficiency indicator" +seta hud_panel_strafehud_strafeefficiency_pos "0.25 1" "position of the strafe efficiency text (relative to the panel), the Y coordinate must be <= -1 (below) or >= 1 (above) the panel" +seta hud_panel_strafehud_strafeefficiency_size "1" "size of the strafe efficiency text (relative to the panel height)" +seta hud_panel_strafehud_projection "0" "StrafeHUD projection mode; \"0\" = linear, \"1\" = perspective, \"2\" = panoramic" // HUD panel aliases alias quickmenu "cl_cmd hud quickmenu ${* ?}" diff --git a/qcsrc/client/hud/panel/_mod.inc b/qcsrc/client/hud/panel/_mod.inc index 78558773e..73f61c3a7 100644 --- a/qcsrc/client/hud/panel/_mod.inc +++ b/qcsrc/client/hud/panel/_mod.inc @@ -21,3 +21,5 @@ #include #include #include + +#include diff --git a/qcsrc/client/hud/panel/_mod.qh b/qcsrc/client/hud/panel/_mod.qh index df7910b54..4a6ca59c8 100644 --- a/qcsrc/client/hud/panel/_mod.qh +++ b/qcsrc/client/hud/panel/_mod.qh @@ -21,3 +21,5 @@ #include #include #include + +#include diff --git a/qcsrc/client/hud/panel/strafehud.qc b/qcsrc/client/hud/panel/strafehud.qc index 15ccac0dc..6601f3814 100644 --- a/qcsrc/client/hud/panel/strafehud.qc +++ b/qcsrc/client/hud/panel/strafehud.qc @@ -6,6 +6,7 @@ #include #include #include +#include "racetimer.qh" // non-essential #include // for v_flipped state @@ -14,13 +15,6 @@ #include // anim_implicit_state #include // CSQCModel_server2csqc() -// start speed -#include // checkpoint information (race_*) - -// jump height -#include // for IS_PLAYER() macro -#include // IS_DEAD() macro - // StrafeHUD (#25) void HUD_StrafeHUD_Export(int fh) @@ -28,20 +22,22 @@ void HUD_StrafeHUD_Export(int fh) // allow saving cvars that aesthetically change the panel into hud skin files } -float GeomLerp(float a, float _lerp, float b); // declare GeomLerp here since there's no header file for it - void HUD_StrafeHUD() { static float hud_lasttime = 0; entity strafeplayer; - bool islocal; + bool is_local; // generic hud routines if(!autocvar__hud_configure) { - if(!autocvar_hud_panel_strafehud || - (spectatee_status == -1 && (autocvar_hud_panel_strafehud == 1 || autocvar_hud_panel_strafehud == 3)) || - (autocvar_hud_panel_strafehud == 3 && !MUTATOR_CALLHOOK(HUD_StrafeHUD_showoptional))) { hud_lasttime = time; return; } + if(!autocvar_hud_panel_strafehud + || (spectatee_status == -1 && (autocvar_hud_panel_strafehud == 1 || autocvar_hud_panel_strafehud == 3)) + || (autocvar_hud_panel_strafehud == 3 && !MUTATOR_CALLHOOK(HUD_StrafeHUD_showoptional))) + { + hud_lasttime = time; + return; + } } HUD_Panel_LoadCvars(); @@ -62,1487 +58,735 @@ void HUD_StrafeHUD() // find out whether the local csqcmodel entity is valid if(spectatee_status > 0 || isdemo()) { - islocal = false; + is_local = false; strafeplayer = CSQCModel_server2csqc(player_localentnum - 1); } else { - islocal = true; + is_local = true; strafeplayer = csqcplayer; } - // draw strafehud - if(csqcplayer && strafeplayer) + if(!csqcplayer || !strafeplayer) { - float strafe_waterlevel; + hud_lasttime = time; + return; + } - // check the player waterlevel without affecting the player entity, this way we can fetch waterlevel even if client prediction is disabled - { - // store old values - void old_contentstransition(int, int) = strafeplayer.contentstransition; - float old_watertype = strafeplayer.watertype; - float old_waterlevel = strafeplayer.waterlevel; - - strafeplayer.contentstransition = func_null; // unset the contentstransition function if present - _Movetype_CheckWater(strafeplayer); - strafe_waterlevel = strafeplayer.waterlevel; // store the player waterlevel - - // restore old values - strafeplayer.contentstransition = old_contentstransition; - strafeplayer.watertype = old_watertype; - strafeplayer.waterlevel = old_waterlevel; - } + // draw strafehud - int keys = STAT(PRESSED_KEYS); - // try to ignore if track_canjump is enabled, doesn't work in spectator mode if spectated player uses +jetpack or cl_movement_track_canjump - bool jumpheld = false; - if(islocal) - { - if((PHYS_INPUT_BUTTON_JUMP(strafeplayer) || PHYS_INPUT_BUTTON_JETPACK(strafeplayer)) && !PHYS_CL_TRACK_CANJUMP(strafeplayer)) - jumpheld = true; - } - else - { - if((keys & KEY_JUMP) && !PHYS_TRACK_CANJUMP(strafeplayer)) - jumpheld = true; - } + int keys = STAT(PRESSED_KEYS); + bool jumpheld = StrafeHUD_DetermineJumpHeld(strafeplayer, keys, is_local); - // persistent - static float onground_lasttime = 0; - static bool onslick_last = false; - static float turn_lasttime = 0; - static bool turn = false; - static float turnangle; - static float dt_update = 0; - static int dt_time = 0; - static float dt_sum = 0; - static float dt = 0; - - // physics - // doesn't get changed by ground timeout and isn't affected by jump input - bool real_onground = islocal ? IS_ONGROUND(strafeplayer) : !(strafeplayer.anim_implicit_state & ANIMIMPLICITSTATE_INAIR); - // doesn't get changed by ground timeout - bool real_onslick = false; - // if jump is held assume we are in air, avoids flickering of the hud when hitting the ground - bool onground = real_onground && !jumpheld; - bool onslick = real_onslick; - bool onground_expired; - bool strafekeys; - // the hud will not work well while swimming - bool swimming = strafe_waterlevel >= WATERLEVEL_SWIMMING; - // use local csqcmodel entity for this even when spectating, flickers too much otherwise - float speed = !autocvar__hud_configure ? vlen(vec2(csqcplayer.velocity)) : 1337; - // only the local csqcplayer entity contains this information even when spectating - float maxspeed_mod = IS_DUCKED(csqcplayer) ? .5 : 1; - float maxspeed_phys = onground ? PHYS_MAXSPEED(strafeplayer) : PHYS_MAXAIRSPEED(strafeplayer); - float maxspeed = !autocvar__hud_configure ? maxspeed_phys * maxspeed_mod : 320; - float movespeed; - float bestspeed; - float maxaccel_phys = onground ? PHYS_ACCELERATE(strafeplayer) : PHYS_AIRACCELERATE(strafeplayer); - float maxaccel = !autocvar__hud_configure ? maxaccel_phys : 1; - float airstopaccel = PHYS_AIRSTOPACCELERATE(strafeplayer); - float aircontrol = PHYS_AIRCONTROL(strafeplayer); - bool aircontrol_backwards = PHYS_AIRCONTROL_BACKWARDS(strafeplayer) == 1; - bool airaccel_qw = PHYS_AIRACCEL_QW(strafeplayer) == 1; - // change the range from 0° - 360° to -180° - 180° to match how view_angle represents angles - float vel_angle = vectoangles(strafeplayer.velocity).y - (vectoangles(strafeplayer.velocity).y > 180 ? 360 : 0); - float view_angle = PHYS_INPUT_ANGLES(strafeplayer).y; - float angle; - vector movement = PHYS_INPUT_MOVEVALUES(strafeplayer); - bool fwd; // left & right variables are flipped when !fwd - int keys_fwd; - float wishangle; - int direction; - float strafity = 0; - - // HUD - int mode; - float speed_conversion_factor = GetSpeedUnitFactor(autocvar_hud_speed_unit); - float length_conversion_factor = GetLengthUnitFactor(autocvar_hud_speed_unit); - // use more decimals when displaying km or miles - int length_decimals = autocvar_hud_speed_unit >= 3 && autocvar_hud_speed_unit <= 5 ? 6 : 2; - float antiflicker_angle = bound(0, autocvar_hud_panel_strafehud_antiflicker_angle, 180); - float minspeed; - float shift_offset = 0; - bool straight_overturn = false; - bool immobile = speed <= 0; - float hudangle; - float hidden_width; - float neutral_offset; - float neutral_width; - vector currentangle_color = autocvar_hud_panel_strafehud_angle_neutral_color; - float currentangle_offset; - vector currentangle_size; - float bestangle; - float prebestangle; - float overturn_angle; - float odd_bestangle; - float bestangle_offset = 0; - float switch_bestangle_offset = 0; - bool odd_angles = false; - float odd_bestangle_offset = 0; - float switch_odd_bestangle_offset = 0; - float switch_bestangle_width = 0; - float wturn_bestangle = 0; - float wturn_left_bestangle_offset = 0; - float wturn_right_bestangle_offset = 0; - float wturn_bestangle_width = 0; - float accelzone_left_offset; - float accelzone_right_offset; - float accelzone_width; - float preaccelzone_left_offset; - float preaccelzone_right_offset; - float preaccelzone_width; - float overturn_offset; - float overturn_width; - float slickdetector_height; - vector direction_size_vertical = '0 0 0'; - vector direction_size_horizontal = '0 0 0'; - float range_minangle; - float text_offset_top = 0; - float text_offset_bottom = 0; - - // real_* variables which are always positive with no wishangle offset - float real_bestangle; - float real_prebestangle; - float real_overturn_angle; - float real_wturn_bestangle = 0; - - if(autocvar_hud_panel_strafehud_mode >= 0 && autocvar_hud_panel_strafehud_mode <= 1) - mode = autocvar_hud_panel_strafehud_mode; - else - mode = STRAFEHUD_MODE_VIEW_CENTERED; + // does not get changed by ground timeout and is not affected by jump input + bool real_onground = is_local ? IS_ONGROUND(strafeplayer) : !(strafeplayer.anim_implicit_state & ANIMIMPLICITSTATE_INAIR); - // there's only one size cvar for the arrows, they will always have a 45° angle to ensure proper rendering without antialiasing - float arrow_size = max(panel_size.y * min(autocvar_hud_panel_strafehud_angle_arrow_size, 10), 0); + // does not get changed by ground timeout + bool real_onslick = false; - if(onground) - { - if(PHYS_FRICTION(strafeplayer) == 0) - { - onslick = true; - } - else // don't use IS_ONSLICK(), it only works for the local player and only if client prediction is enabled - { - trace_dphitq3surfaceflags = 0; - tracebox(strafeplayer.origin, strafeplayer.mins, strafeplayer.maxs, strafeplayer.origin - '0 0 1', MOVE_NOMONSTERS, strafeplayer); - onslick = trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK; - } - real_onslick = onslick; + // if jump is held assume we are in air, avoids flickering of the hud when hitting the ground + bool onground = real_onground && !jumpheld; + bool onslick = real_onslick; - onground_lasttime = time; - onslick_last = onslick; - } - else if(jumpheld || swimming) - { - onground_lasttime = 0; - } + // the hud will not work well while swimming + float strafe_waterlevel = StrafeHUD_DetermineWaterLevel(strafeplayer); + bool swimming = strafe_waterlevel >= WATERLEVEL_SWIMMING; - if(onground_lasttime == 0) - onground_expired = true; - else - onground_expired = (time - onground_lasttime) >= autocvar_hud_panel_strafehud_timeout_ground; // timeout for slick ramps + static float onground_lasttime = 0; + static bool onslick_last = false; + if(onground) + { + // do not use IS_ONSLICK(), it only works for the local player and only if client prediction is enabled + trace_dphitq3surfaceflags = 0; + tracebox(strafeplayer.origin, strafeplayer.mins, strafeplayer.maxs, strafeplayer.origin - '0 0 1', MOVE_NOMONSTERS, strafeplayer); + real_onslick = onslick = trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK; - if(!onground && !onground_expired) // if ground timeout hasn't expired yet use ground physics - { - onground = true; - onslick = onslick_last; + onground_lasttime = time; + onslick_last = onslick; + } + else if(jumpheld || swimming) + { + onground_lasttime = 0; + } - if(!autocvar__hud_configure) - { - maxspeed = PHYS_MAXSPEED(strafeplayer) * maxspeed_mod; - maxaccel = PHYS_ACCELERATE(strafeplayer); - } - } + bool onground_expired; + if(onground_lasttime == 0) + onground_expired = true; + else + onground_expired = (time - onground_lasttime) >= autocvar_hud_panel_strafehud_timeout_ground; // timeout for slick ramps - movespeed = vlen(vec2(movement)); - if(movespeed == 0) - movespeed = maxspeed; - else - movespeed = min(movespeed, maxspeed); + // only the local csqcplayer entity contains this information even when spectating + float maxspeed_mod = IS_DUCKED(csqcplayer) ? .5 : 1; + float maxspeed_phys = onground ? PHYS_MAXSPEED(strafeplayer) : PHYS_MAXAIRSPEED(strafeplayer); + float maxspeed = !autocvar__hud_configure ? maxspeed_phys * maxspeed_mod : 320; + float maxaccel_phys = onground ? PHYS_ACCELERATE(strafeplayer) : PHYS_AIRACCELERATE(strafeplayer); + float maxaccel = !autocvar__hud_configure ? maxaccel_phys : 1; - if(!autocvar_hud_panel_strafehud_uncapped) - arrow_size = max(arrow_size, 1); + if(!onground && !onground_expired) // if ground timeout has not expired yet use ground physics + { + onground = true; + onslick = onslick_last; - // determine frametime - if((csqcplayer_status == CSQCPLAYERSTATUS_PREDICTED) && (input_timelength > 0)) + if(!autocvar__hud_configure) { - float dt_client = input_timelength; + maxspeed = PHYS_MAXSPEED(strafeplayer) * maxspeed_mod; + maxaccel = PHYS_ACCELERATE(strafeplayer); + } + } - if(dt_client > .05) // server splits frames longer than 50 ms into two moves - dt_client /= 2; // doesn't ensure frames are smaller than 50 ms, just splits large frames in half, matches server behaviour + // move values are only valid for the local player + vector movement = PHYS_INPUT_MOVEVALUES(strafeplayer); - // calculate average frametime - dt_sum += dt_client * dt_client; - dt_time += dt_client; + float movespeed; + if(is_local) + { + movespeed = min(vlen(vec2(movement)), maxspeed); - if(((time - dt_update) > autocvar_hud_panel_strafehud_fps_update) || (dt_update == 0)) - { - dt = dt_sum / dt_time; - dt_update = time; - dt_time = dt_sum = 0; - } - } - else // when spectating other players server ticrate will be used, this may not be accurate but there is no way to find other player's frametime - { - dt = ticrate; - dt_update = dt_time = dt_sum = 0; - } + // assume maxspeed so that the hud remains useful even if no direction keys are pressed + if(movespeed == 0) movespeed = maxspeed; + } + else + { + // the only information available is whether a movement key is pressed or not + // which means the movespeed would be either maxspeed or zero + // since we set it to maxspeed if it is zero the movespeed will always equal maxspeed if the player is not local + movespeed = maxspeed; + } - // determine whether the player is pressing forwards or backwards keys - if(islocal) // if entity is local player - { - if(movement.x > 0) - keys_fwd = STRAFEHUD_KEYS_FORWARD; - else if(movement.x < 0) - keys_fwd = STRAFEHUD_KEYS_BACKWARD; - else - keys_fwd = STRAFEHUD_KEYS_NONE; - } - else // alternatively determine direction by querying pressed keys - { - if((keys & KEY_FORWARD) && !(keys & KEY_BACKWARD)) - keys_fwd = STRAFEHUD_KEYS_FORWARD; - else if(!(keys & KEY_FORWARD) && (keys & KEY_BACKWARD)) - keys_fwd = STRAFEHUD_KEYS_BACKWARD; - else - keys_fwd = STRAFEHUD_KEYS_NONE; - } + // the following functions have to check themselves whether the player is local and use the move values accordingly + int keys_fwd = StrafeHUD_DetermineForwardKeys(movement, keys, is_local); + float wishangle = StrafeHUD_DetermineWishAngle(movement, keys, is_local); + float absolute_wishangle = fabs(wishangle); // unmodified by side strafing code + bool strafekeys = fabs(wishangle) > 45; - // determine player wishdir - if(islocal) // if entity is local player - { - if(movement.x == 0) - { - if(movement.y < 0) - wishangle = -90; - else if(movement.y > 0) - wishangle = 90; - else - wishangle = 0; - } - else - { - if(movement.y == 0) - { - wishangle = 0; - } - else - { - wishangle = RAD2DEG * atan2(movement.y, movement.x); - // wrap the wish angle if it exceeds ±90° - if(fabs(wishangle) > 90) - { - if(wishangle < 0) - wishangle += 180; - else - wishangle -= 180; - - wishangle *= -1; - } - } - } - } - else // alternatively calculate wishdir by querying pressed keys - { - if(keys & (KEY_FORWARD | KEY_BACKWARD)) - wishangle = 45; - else - wishangle = 90; - if(keys & KEY_LEFT) - wishangle *= -1; - else if(!(keys & KEY_RIGHT)) - wishangle = 0; // wraps at 180° - } - - float real_wishangle = fabs(wishangle); // unmodified by side strafing code - strafekeys = real_wishangle > 45; + // detect air strafe turning + static bool turn = false; + float strafity = 0; + if(!strafekeys || onground || autocvar__hud_configure) + { + turn = false; + } + else // air strafe only + { + static float turn_lasttime = 0; + static float turnangle; + bool turn_expired = (time - turn_lasttime) >= autocvar_hud_panel_strafehud_timeout_turn; // timeout for jumping with strafe keys only - // detect air strafe turning - if((!strafekeys && vlen(vec2(movement)) > 0) || onground || autocvar__hud_configure) - { + if(strafekeys) + turn = true; + else if(turn_expired) turn = false; - } - else // air strafe only - { - bool turn_expired = (time - turn_lasttime) >= autocvar_hud_panel_strafehud_timeout_turn; // timeout for jumping with strafe keys only + if(turn) // side strafing (A/D) + { if(strafekeys) - turn = true; - else if(turn_expired) - turn = false; - - if(turn) // side strafing (A/D) { - if(strafekeys) - { - turn_lasttime = time; - turnangle = wishangle; - } - else // retain last state until strafe turning times out - { - wishangle = turnangle; - } - - // calculate the maximum air strafe speed and acceleration - strafity = 1 - (90 - fabs(wishangle)) / 45; - - if(PHYS_MAXAIRSTRAFESPEED(strafeplayer) != 0) - maxspeed = min(maxspeed, GeomLerp(PHYS_MAXAIRSPEED(strafeplayer), strafity, PHYS_MAXAIRSTRAFESPEED(strafeplayer))); - - movespeed = min(movespeed, maxspeed); - - if(PHYS_AIRSTRAFEACCELERATE(strafeplayer) != 0) - maxaccel = GeomLerp(PHYS_AIRACCELERATE(strafeplayer), strafity, PHYS_AIRSTRAFEACCELERATE(strafeplayer)); + turn_lasttime = time; + turnangle = wishangle; + } + else // retain last state until strafe turning times out + { + wishangle = turnangle; } - } - - maxaccel *= dt * movespeed; - bestspeed = max(movespeed - maxaccel, 0); // target speed to gain maximum acceleration - - float frictionspeed; // speed lost from friction - float strafespeed; // speed minus friction - if((speed > 0) && onground) - { - float strafefriction = onslick ? PHYS_FRICTION_SLICK(strafeplayer) : PHYS_FRICTION(strafeplayer); + // calculate the maximum air strafe speed and acceleration + strafity = 1 - (90 - fabs(wishangle)) / 45; - frictionspeed = speed * dt * strafefriction * max(PHYS_STOPSPEED(strafeplayer) / speed, 1); - strafespeed = max(speed - frictionspeed, 0); - } - else - { - frictionspeed = 0; - strafespeed = speed; - } + if(PHYS_MAXAIRSTRAFESPEED(strafeplayer) != 0) + maxspeed = min(maxspeed, GeomLerp(PHYS_MAXAIRSPEED(strafeplayer), strafity, PHYS_MAXAIRSTRAFESPEED(strafeplayer))); - minspeed = autocvar_hud_panel_strafehud_switch_minspeed; - if(minspeed < 0) - minspeed = bestspeed + frictionspeed; + movespeed = min(movespeed, maxspeed); - // get current strafing angle ranging from -180° to +180° - if(!autocvar__hud_configure) - { - if(speed > 0) - { - // calculate view angle relative to the players current velocity direction - angle = vel_angle - view_angle; - - // if the angle goes above 180° or below -180° wrap it to the opposite side since we want the interior angle - if(angle > 180) - angle -= 360; - else if(angle < -180) - angle += 360; - - // determine whether the player is strafing forwards or backwards - // if the player isn't strafe turning use forwards/backwards keys to determine direction - if(fabs(wishangle) != 90) - { - if(keys_fwd == STRAFEHUD_KEYS_FORWARD) - fwd = true; - else if(keys_fwd == STRAFEHUD_KEYS_BACKWARD) - fwd = false; - else - fwd = fabs(angle) <= 90; - } - // otherwise determine by examining the strafe angle - else - { - if(wishangle < 0) // detect direction using wishangle since the direction is not yet set - fwd = angle <= -wishangle; - else - fwd = angle >= -wishangle; - } - - // shift the strafe angle by 180° when strafing backwards - if(!fwd) - { - if(angle < 0) - angle += 180; - else - angle -= 180; - } - - // don't make the angle indicator switch side too much at ±180° if anti flicker is turned on - if(angle > (180 - antiflicker_angle) || angle < (-180 + antiflicker_angle)) - straight_overturn = true; - } - else - { - angle = 0; - fwd = true; - } + if(PHYS_AIRSTRAFEACCELERATE(strafeplayer) != 0) + maxaccel = GeomLerp(PHYS_AIRACCELERATE(strafeplayer), strafity, PHYS_AIRSTRAFEACCELERATE(strafeplayer)); } - else // simulate turning for HUD setup - { - const float demo_maxangle = 55; // maximum angle before changing direction - const float demo_turnspeed = 40; // turning speed in degrees per second + } - static float demo_position = -37 / demo_maxangle; // current positioning value between -1 and +1 + float dt = StrafeHUD_DetermineFrameTime(); - if(autocvar__hud_panel_strafehud_demo) - { - float demo_dt = time - hud_lasttime; - float demo_step = (demo_turnspeed / demo_maxangle) * demo_dt; - demo_position = ((demo_position + demo_step) % 4 + 4) % 4; - } + maxaccel *= dt * movespeed; + float bestspeed = max(movespeed - maxaccel, 0); // target speed to gain maximum acceleration - // triangle wave function - if(demo_position > 3) - angle = -1 + (demo_position - 3); - else if(demo_position > 1) - angle = +1 - (demo_position - 1); - else - angle = demo_position; - angle *= demo_maxangle; + // use local csqcmodel entity for this even when spectating, flickers too much otherwise + vector strafevelocity = csqcplayer.velocity; - fwd = true; - wishangle = 45; - if(angle < 0) - wishangle *= -1; - } + float speed = !autocvar__hud_configure ? vlen(vec2(strafevelocity)) : 1337; + bool moving = speed > 0; - // invert the wish angle when strafing backwards - if(!fwd) - wishangle *= -1; + float frictionspeed; // speed lost from friction + float strafespeed; // speed minus friction - // flip angles if v_flipped is enabled - if(autocvar_v_flipped) - { - angle *= -1; - wishangle *= -1; - } + if(moving && onground) + { + float strafefriction = onslick ? PHYS_FRICTION_SLICK(strafeplayer) : PHYS_FRICTION(strafeplayer); - // determine whether the player is strafing left or right - if(wishangle > 0) - { - direction = STRAFEHUD_DIRECTION_RIGHT; - } - else if(wishangle < 0) - { - direction = STRAFEHUD_DIRECTION_LEFT; - } - else - { - if(angle > antiflicker_angle && angle < (180 - antiflicker_angle)) - direction = STRAFEHUD_DIRECTION_RIGHT; - else if(angle < -antiflicker_angle && angle > (-180 + antiflicker_angle)) - direction = STRAFEHUD_DIRECTION_LEFT; - else - direction = STRAFEHUD_DIRECTION_NONE; - } + frictionspeed = speed * dt * strafefriction * max(PHYS_STOPSPEED(strafeplayer) / speed, 1); + strafespeed = max(speed - frictionspeed, 0); + } + else + { + frictionspeed = 0; + strafespeed = speed; + } - if(airstopaccel == 0) - airstopaccel = 1; // values of 0 are equivalent to 1 + // get current strafing angle ranging from -180° to +180° + float angle; + bool fwd; // left & right variables are flipped when !fwd - // best angle to strafe at - if(immobile) - { - // these are unused (neutral fills whole strafe bar) - prebestangle = bestangle = 0; - overturn_angle = 180; - } - else if(onground && autocvar_hud_panel_strafehud_onground_friction) - { - // draw ground angles - { - // delta_opt = acos((s - a) / v_f), same in air - bestangle = strafespeed > bestspeed - ? acos(bestspeed / strafespeed) * RAD2DEG // case 1 - : 0; // case 2 - // case 1: normal. case 2: low speed, best angle is forwards - } - { - // needed later if autocvar_hud_panel_strafehud_wturn != STRAFEHUD_WTURN_NONE, - // ... so calculate even if autocvar_hud_panel_strafehud_bar_preaccel == 0 - float prebestangle_sqrt = movespeed * movespeed + strafespeed * strafespeed - speed * speed; - // delta_min = acos(sqrt(s^2 - v_f^2 + v^2) / v_f), or just acos(s / v) in air - prebestangle = (prebestangle_sqrt > 0 && strafespeed > sqrt(prebestangle_sqrt)) - ? acos(sqrt(prebestangle_sqrt) / strafespeed) * RAD2DEG // case 1 - : (prebestangle_sqrt > 0 ? 0 : 90); // case 2 : case 3 - // case 1: normal. case 2: low speed, best angle is forwards. case 3: landed at high speed, neutral zone is very large (see explanation below) + if(!autocvar__hud_configure) + { + if(moving) + { + // change the range from 0° - 360° to -180° - 180° to match how view_angle represents angles + float vel_angle = vectoangles(strafevelocity).y; + if(vel_angle > 180) vel_angle -= 360; + float view_angle = PHYS_INPUT_ANGLES(strafeplayer).y; + + // calculate view angle relative to the players current velocity direction + angle = vel_angle - view_angle; + + // if the angle goes above 180° or below -180° wrap it to the opposite side since we want the interior angle + if(angle > 180) + angle -= 360; + else if(angle < -180) + angle += 360; + + // determine whether the player is strafing forwards or backwards + // if the player is not strafe turning use forwards/backwards keys to determine direction + if(fabs(wishangle) != 90) + { + if(keys_fwd == STRAFEHUD_KEYS_FORWARD) + fwd = true; + else if(keys_fwd == STRAFEHUD_KEYS_BACKWARD) + fwd = false; + else + fwd = fabs(angle) <= 90; } + // otherwise determine by examining the strafe angle + else { - float overturn_numer = speed * speed - strafespeed * strafespeed - maxaccel * maxaccel; - float overturn_denom = 2 * maxaccel * strafespeed; - // delta_max = acos((v^2 - v_f^2 - a^2) / (2av_f)), or just acos(-a / 2v) if in air - overturn_angle = overturn_denom > fabs(overturn_numer) - ? acos(overturn_numer / overturn_denom) * RAD2DEG // case 1 - : (overturn_numer < 0 ? 180 : 0); // case 2 : case 3 - // case 1: normal. case 2: low speed, turning anywhere will gain speed. case 3: landed at high speed, turning anywhere will lose speed (due to friction) + if(wishangle < 0) // detect direction using wishangle since the direction is not yet set + fwd = angle <= -wishangle; + else + fwd = angle >= -wishangle; } - if(overturn_angle < bestangle || bestangle < prebestangle) + + // shift the strafe angle by 180° when strafing backwards + if(!fwd) { - // these conditions occur when you land at high speed (above max onground speed), such that every wishangle will result in a speed loss due to friction - if(autocvar_hud_panel_strafehud_onground_mode == STRAFEHUD_ONGROUND_OVERTURN) - { - // make overturn fill the whole strafe bar - // most correct option by the true definition of accel, since every angle results in deceleration - prebestangle = bestangle = 0; - overturn_angle = 0; - } - else if(autocvar_hud_panel_strafehud_onground_mode == STRAFEHUD_ONGROUND_GROUND) - { - /* k9er: these aren't the true angles -- the real ones are very convoluted and difficult to understand - * essentially the prior definitions of the zones now overlap, - * ... with the overturn zone extending below bestangle, and eventually covering the whole hud - * ... and somehow the neutral zone extends above bestangle, and eventually covers the whole hud (i think) - * overall showing it accurately is just confusing and unnecessary to add - * thankfully the bestangle formula is unchanged, so the least confusing option is likely as follows: - */ - overturn_angle = bestangle; - prebestangle = bestangle; - } + if(angle < 0) + angle += 180; else - { - // use angles as if in air - // no need to check if numerator < denominator, since all numerators < max onground speed < speed = all denominators - bestangle = acos(bestspeed / speed) * RAD2DEG; - prebestangle = acos(movespeed / speed) * RAD2DEG; - overturn_angle = acos(-(airstopaccel * maxaccel / 2) / speed) * RAD2DEG; - } + angle -= 180; } } else { - // draw airborne angles. see above for documentation - bestangle = speed > bestspeed - ? acos(bestspeed / speed) * RAD2DEG - : 0; - prebestangle = speed > movespeed - ? acos(movespeed / speed) * RAD2DEG - : 0; - // with airstopaccel, delta_max = acos(airstopaccel * -a / 2v), only in air - overturn_angle = speed > airstopaccel * maxaccel / 2 - ? acos(-(airstopaccel * maxaccel / 2) / speed) * RAD2DEG - : 180; - } - real_bestangle = bestangle; - real_prebestangle = prebestangle; - real_overturn_angle = overturn_angle; - - /* - * k9er: proper W-turn angle assuming sv_aircontrol_power == 2 is acos(-speed/a * (cos((acos(V) + M_PI * 2) / 3) * 2 + 1)) rad, - * ... where a=dt*32*aircontrol, and V=1-(a*a)/(speed*speed), - * ... but this very quickly loses accuracy -- should be a strictly decreasing function, yet it increases at only speed=722 with 125 fps - * also note this is only valid when such angle is not in the accelzone, formula taking acceleration into account is unfathomably complicated - * afaik there's no simplified version of this formula that doesn't involve complex numbers, other than one valid for only speed<27.1 roughly - * furthermore, this function quite rapidly approaches its asymptote of ~35.26, e.g. being ~0.68 away when at only speed=600 - * this asymptote is independent of whether the player is crouching or has haste, although they must be airborne - * thus, the best option is to just draw the asymptote (acos(sqrt(2/3))), - * ... but the proper angle can be drawn too if the player wants (hud_panel_strafehud_wturn_proper 1) - * this is only enabled if sv_airaccel_qw == 1 since otherwise W-turning gives acceleration, unless hud_panel_strafehud_wturn_unrestricted 1 - * when sv_aircontrol_power != 2 (abbr. "p"), the asymptote is instead acos(sqrt(p/(p+1))). full formula is too difficult to calculate, - * ... so the angle will only be shown with hud_panel_strafehud_wturn_proper 0 - * this doesn't have support for sv_aircontrol_sideways == 1 - */ - bool wturning = !onground && wishangle == 0 && (keys_fwd == STRAFEHUD_KEYS_FORWARD || (aircontrol_backwards && keys_fwd == STRAFEHUD_KEYS_BACKWARD)); - bool wturn_valid = aircontrol && PHYS_AIRCONTROL_PENALTY(strafeplayer) == 0 && (airaccel_qw || autocvar_hud_panel_strafehud_wturn_unrestricted == 1); - bool wturn_check = autocvar_hud_panel_strafehud_wturn && !immobile && wturn_valid; - if(wturn_check) - { - float wturn_power = PHYS_AIRCONTROL_POWER(strafeplayer); - if(wturn_power == 2) - { - float wturn_a = 32 * aircontrol * dt; - float wturn_V = 1 - (wturn_a * wturn_a) / (speed * speed); - if(autocvar_hud_panel_strafehud_wturn_proper && wturn_a > 1 && wturn_V < 1 && wturn_V > -1) - wturn_bestangle = acos(-speed / wturn_a * (cos((acos(wturn_V) + M_PI * 2) / 3) * 2 + 1)) * RAD2DEG; - else - wturn_bestangle = ACOS_SQRT2_3_DEG; - real_wturn_bestangle = wturn_bestangle; - } - else if(!autocvar_hud_panel_strafehud_wturn_proper && wturn_power >= 0) - { - wturn_bestangle = acos(sqrt(wturn_power / (wturn_power + 1))) * RAD2DEG; - real_wturn_bestangle = wturn_bestangle; - } - else - { - wturn_valid = false; - wturn_check = false; - } + angle = 0; + fwd = true; } + } + else // simulate turning for HUD setup + { + const float demo_maxangle = 55; // maximum angle before changing direction + const float demo_turnspeed = 40; // turning speed in degrees per second + static float demo_position = -37 / demo_maxangle; // current positioning value between -1 and +1 - // draw the switch indicators as if strafing normally (W+A style), while W-turning or side strafing - float n_bestangle = 0; - float n_odd_bestangle; - float n_bestangle_offset = 0; - float n_switch_bestangle_offset = 0; - float n_odd_bestangle_offset = 0; - float n_switch_odd_bestangle_offset = 0; - bool draw_normal = ((autocvar_hud_panel_strafehud_switch >= STRAFEHUD_SWITCH_NORMAL && wturning) - || (autocvar_hud_panel_strafehud_switch == STRAFEHUD_SWITCH_SIDESTRAFE && turn)); - if(draw_normal) + if(autocvar__hud_panel_strafehud_demo) { - // recalculate bestangle as if strafing normally - float n_maxspeed = PHYS_MAXAIRSPEED(strafeplayer) * maxspeed_mod; - float n_movespeed = n_maxspeed; - float n_maxaccel = PHYS_AIRACCELERATE(strafeplayer) * dt * n_movespeed; - float n_bestspeed = max(n_movespeed - n_maxaccel, 0); - n_bestangle = speed > n_bestspeed - ? acos(n_bestspeed / speed) * RAD2DEG - 45 - : -45; + float demo_dt = time - hud_lasttime; + float demo_step = (demo_turnspeed / demo_maxangle) * demo_dt; + demo_position = ((demo_position + demo_step) % 4 + 4) % 4; } - // determine the minimal required HUD angle to contain the full strafing angle range - // this is useful for the velocity centered mode where the zones do not follow the strafing angle - // how it works: - // the angle where the most acceleration occurs moves relative to the player velocity - // from 0 - wishangle to real_overturn_angle - wishangle - // the angle farther away from the center is the maximum the optimal strafing angle can - // diverge from the direction of velocity - // this angle has to be multiplied by two since the HUD extends in both directions which - // halves the amount it extends in a single direction - range_minangle = max(real_wishangle, real_overturn_angle - real_wishangle) * 2; - - float range_normal = autocvar_hud_panel_strafehud_range; - float range_side = autocvar_hud_panel_strafehud_range_sidestrafe; - float range_used; - if(range_normal == 0) - range_normal = autocvar__hud_configure ? 90 : range_minangle; // use minimum angle required if dynamically setting hud angle - if(range_side == -1) // use the normal range - range_used = range_normal; + // triangle wave function + if(demo_position > 3) + angle = -1 + (demo_position - 3); + else if(demo_position > 1) + angle = +1 - (demo_position - 1); else - { - if(range_side == 0) - range_side = autocvar__hud_configure ? 90 : range_minangle; - range_used = GeomLerp(range_normal, strafity, range_side); - } - hudangle = bound(0, fabs(range_used), 360); // limit HUD range to 360 degrees, higher values don't make sense + angle = demo_position; + angle *= demo_maxangle; - if(direction == STRAFEHUD_DIRECTION_LEFT) // the angle becomes negative in case we strafe left - { - n_bestangle *= -1; - bestangle *= -1; - prebestangle *= -1; - overturn_angle *= -1; - } - odd_bestangle = -bestangle - wishangle; - n_odd_bestangle = -n_bestangle - wishangle; - bestangle -= wishangle; - prebestangle -= wishangle; - overturn_angle -= wishangle; - - // various offsets and size calculations of hud indicator elements - // how much is hidden by the current hud angle - hidden_width = (360 - hudangle) / hudangle * panel_size.x; - - // current angle - currentangle_size.x = autocvar_hud_panel_strafehud_angle_width; - currentangle_size.y = autocvar_hud_panel_strafehud_angle_height; - currentangle_size.z = 0; - if(!autocvar_hud_panel_strafehud_uncapped) - { - currentangle_size.x = min(currentangle_size.x, 10); - currentangle_size.y = min(currentangle_size.y, 10); - } - currentangle_size.x *= panel_size.x; - currentangle_size.y *= panel_size.y; - if(!autocvar_hud_panel_strafehud_uncapped) - { - currentangle_size.x = max(currentangle_size.x, 1); - currentangle_size.y = max(currentangle_size.y, 1); - } - else - { - currentangle_size.y = max(currentangle_size.y, 0); - } - if(mode == STRAFEHUD_MODE_VIEW_CENTERED) - currentangle_offset = angle / hudangle * panel_size.x; - else - currentangle_offset = bound(-hudangle / 2, angle, hudangle / 2) / hudangle * panel_size.x + panel_size.x / 2; + fwd = true; + wishangle = 45; + if(angle < 0) + wishangle *= -1; + } - // best strafe acceleration angle - if((autocvar_hud_panel_strafehud_switch || autocvar_hud_panel_strafehud_bestangle) && speed >= minspeed) - { - bestangle_offset = bestangle / hudangle * panel_size.x + panel_size.x / 2; - switch_bestangle_offset = -bestangle / hudangle * panel_size.x + panel_size.x / 2; - switch_bestangle_width = panel_size.x * autocvar_hud_panel_strafehud_switch_width; - if(!autocvar_hud_panel_strafehud_uncapped) - switch_bestangle_width = max(switch_bestangle_width, 1); + // invert the wish angle when strafing backwards + if(!fwd) + wishangle *= -1; - if((angle > -wishangle && direction == STRAFEHUD_DIRECTION_LEFT) || (angle < -wishangle && direction == STRAFEHUD_DIRECTION_RIGHT)) - { - odd_angles = true; - odd_bestangle_offset = odd_bestangle / hudangle * panel_size.x + panel_size.x / 2; - switch_odd_bestangle_offset = (odd_bestangle + bestangle * 2) / hudangle * panel_size.x + panel_size.x / 2; + // flip angles if v_flipped is enabled + if(autocvar_v_flipped) + { + angle *= -1; + wishangle *= -1; + } + + float airstopaccel = PHYS_AIRSTOPACCELERATE(strafeplayer); + if(airstopaccel == 0) + airstopaccel = 1; // values of 0 are equivalent to 1 + + // best angle to strafe at + float bestangle; + float prebestangle; + float overturnangle; + if(!moving) + { + // these are unused (neutral fills whole strafe bar) + prebestangle = bestangle = 0; + overturnangle = 180; + } + else if(onground && autocvar_hud_panel_strafehud_onground_friction) + { + // draw ground angles + { + // delta_opt = acos((s - a) / v_f), same in air + bestangle = strafespeed > bestspeed + ? acos(bestspeed / strafespeed) * RAD2DEG // case 1 + : 0; // case 2 + // case 1: normal. case 2: low speed, best angle is forwards + } + { + // needed later if autocvar_hud_panel_strafehud_wturn != STRAFEHUD_WTURN_NONE, + // ... so calculate even if autocvar_hud_panel_strafehud_bar_preaccel == 0 + float prebestangle_sqrt = movespeed * movespeed + strafespeed * strafespeed - speed * speed; + // delta_min = acos(sqrt(s^2 - v_f^2 + v^2) / v_f), or just acos(s / v) in air + prebestangle = (prebestangle_sqrt > 0 && strafespeed > sqrt(prebestangle_sqrt)) + ? acos(sqrt(prebestangle_sqrt) / strafespeed) * RAD2DEG // case 1 + : (prebestangle_sqrt > 0 ? 0 : 90); // case 2 : case 3 + // case 1: normal. case 2: low speed, best angle is forwards. case 3: landed at high speed, neutral zone is very large (see explanation below) + } + { + float overturn_numer = speed * speed - strafespeed * strafespeed - maxaccel * maxaccel; + float overturn_denom = 2 * maxaccel * strafespeed; + // delta_max = acos((v^2 - v_f^2 - a^2) / (2av_f)), or just acos(-a / 2v) if in air + overturnangle = overturn_denom > fabs(overturn_numer) + ? acos(overturn_numer / overturn_denom) * RAD2DEG // case 1 + : (overturn_numer < 0 ? 180 : 0); // case 2 : case 3 + // case 1: normal. case 2: low speed, turning anywhere will gain speed. case 3: landed at high speed, turning anywhere will lose speed (due to friction) + } + if(overturnangle < bestangle || bestangle < prebestangle) + { + // these conditions occur when you land at high speed (above max onground speed), such that every wishangle will result in a speed loss due to friction + if(autocvar_hud_panel_strafehud_onground_mode == STRAFEHUD_ONGROUND_OVERTURN) + { + // make overturn fill the whole strafe bar + // most correct option by the true definition of accel, since every angle results in deceleration + prebestangle = bestangle = 0; + overturnangle = 0; + } + else if(autocvar_hud_panel_strafehud_onground_mode == STRAFEHUD_ONGROUND_GROUND) + { + /* k9er: these aren't the true angles -- the real ones are very convoluted and difficult to understand + * essentially the prior definitions of the zones now overlap, + * ... with the overturn zone extending below bestangle, and eventually covering the whole hud + * ... and somehow the neutral zone extends above bestangle, and eventually covers the whole hud (i think) + * overall showing it accurately is just confusing and unnecessary to add + * thankfully the bestangle formula is unchanged, so the least confusing option is likely as follows: + */ + overturnangle = bestangle; + prebestangle = bestangle; } - if(draw_normal) + else { - n_bestangle_offset = n_bestangle / hudangle * panel_size.x + panel_size.x / 2; - n_switch_bestangle_offset = -n_bestangle / hudangle * panel_size.x + panel_size.x / 2; - if(odd_angles) - { - n_odd_bestangle_offset = n_odd_bestangle / hudangle * panel_size.x + panel_size.x / 2; - n_switch_odd_bestangle_offset = (n_odd_bestangle + n_bestangle * 2) / hudangle * panel_size.x + panel_size.x / 2; - } + // use angles as if in air + // no need to check if numerator < denominator, since all numerators < max onground speed < speed = all denominators + bestangle = acos(bestspeed / speed) * RAD2DEG; + prebestangle = acos(movespeed / speed) * RAD2DEG; + overturnangle = acos(-(airstopaccel * maxaccel / 2) / speed) * RAD2DEG; } } - - // best angle to aim at when W-turning to maximally rotate velocity vector - if(wturn_check) + } + else + { + // draw airborne angles. see above for documentation + bestangle = speed > bestspeed + ? acos(bestspeed / speed) * RAD2DEG + : 0; + prebestangle = speed > movespeed + ? acos(movespeed / speed) * RAD2DEG + : 0; + // with airstopaccel, delta_max = acos(airstopaccel * -a / 2v), only in air + overturnangle = speed > airstopaccel * maxaccel / 2 + ? acos(-(airstopaccel * maxaccel / 2) / speed) * RAD2DEG + : 180; + } + // absolute_* variables which are always positive with no wishangle offset + float absolute_bestangle = bestangle; + float absolute_prebestangle = prebestangle; + float absolute_overturnangle = overturnangle; + + float aircontrol = PHYS_AIRCONTROL(strafeplayer); + bool aircontrol_backwards = PHYS_AIRCONTROL_BACKWARDS(strafeplayer); + bool is_aircontrol_keys = keys_fwd == STRAFEHUD_KEYS_FORWARD || (aircontrol_backwards && keys_fwd == STRAFEHUD_KEYS_BACKWARD); + bool is_aircontrol_direction = fwd || aircontrol_backwards; + bool airaccel_qw = PHYS_AIRACCEL_QW(strafeplayer) == 1; + + /* + * k9er: proper W-turn angle assuming sv_aircontrol_power == 2 is acos(-speed/a * (cos((acos(V) + M_PI * 2) / 3) * 2 + 1)) rad, + * ... where a=dt*32*aircontrol, and V=1-(a*a)/(speed*speed), + * ... but this very quickly loses accuracy -- should be a strictly decreasing function, yet it increases at only speed=722 with 125 fps + * also note this is only valid when such angle is not in the accelzone, formula taking acceleration into account is unfathomably complicated + * afaik there's no simplified version of this formula that doesn't involve complex numbers, other than one valid for only speed<27.1 roughly + * furthermore, this function quite rapidly approaches its asymptote of ~35.26, e.g. being ~0.68 away when at only speed=600 + * this asymptote is independent of whether the player is crouching or has haste, although they must be airborne + * thus, the best option is to just draw the asymptote (acos(sqrt(2/3))), + * ... but the proper angle can be drawn too if the player wants (hud_panel_strafehud_wturn_proper 1) + * this is only enabled if sv_airaccel_qw == 1 since otherwise W-turning gives acceleration, unless hud_panel_strafehud_wturn_unrestricted 1 + * when sv_aircontrol_power != 2 (abbr. "p"), the asymptote is instead acos(sqrt(p/(p+1))). full formula is too difficult to calculate, + * ... so the angle will only be shown with hud_panel_strafehud_wturn_proper 0 + * this doesn't have support for sv_aircontrol_sideways == 1 + */ + bool wturning = (wishangle == 0) && !onground && is_aircontrol_keys; + bool wturn_valid = false; + float wturn_bestangle = 0; + if(autocvar_hud_panel_strafehud_wturn && moving && + aircontrol && PHYS_AIRCONTROL_PENALTY(strafeplayer) == 0 && + (airaccel_qw || autocvar_hud_panel_strafehud_wturn_unrestricted == 1)) + { + float wturn_power = PHYS_AIRCONTROL_POWER(strafeplayer); + if(wturn_power == 2) { - bool wturn_show = autocvar_hud_panel_strafehud_wturn == STRAFEHUD_WTURN_SIDESTRAFE ? (fwd || aircontrol_backwards) - : autocvar_hud_panel_strafehud_wturn == STRAFEHUD_WTURN_NORMAL ? ((fwd || aircontrol_backwards) && !turn) - : autocvar_hud_panel_strafehud_wturn == STRAFEHUD_WTURN_NONE ? false - : wturning; - if(wturn_show && real_wturn_bestangle < real_prebestangle && !onground) - { - wturn_left_bestangle_offset = wturn_bestangle / hudangle * panel_size.x + panel_size.x / 2; - wturn_right_bestangle_offset = -wturn_bestangle / hudangle * panel_size.x + panel_size.x / 2; - wturn_bestangle_width = panel_size.x * autocvar_hud_panel_strafehud_wturn_width; - if(!autocvar_hud_panel_strafehud_uncapped) - wturn_bestangle_width = max(wturn_bestangle_width, 1); - } + float wturn_a = 32 * aircontrol * dt; + float wturn_V = 1 - (wturn_a * wturn_a) / (speed * speed); + if(autocvar_hud_panel_strafehud_wturn_proper && wturn_a > 1 && wturn_V < 1 && wturn_V > -1) + wturn_bestangle = acos(-speed / wturn_a * (cos((acos(wturn_V) + M_PI * 2) / 3) * 2 + 1)) * RAD2DEG; + else + wturn_bestangle = ACOS_SQRT2_3_DEG; + wturn_valid = true; } - - // the neutral zone fills the whole strafe bar - if(immobile) + else if(!autocvar_hud_panel_strafehud_wturn_proper && wturn_power >= 0) { - // draw neutral zone - if(panel_size.x > 0 && panel_size.y > 0 && autocvar_hud_panel_strafehud_bar_neutral_alpha * panel_fg_alpha > 0) - { - switch(autocvar_hud_panel_strafehud_style) - { - default: - case STRAFEHUD_STYLE_DRAWFILL: - drawfill( - panel_pos, panel_size, - autocvar_hud_panel_strafehud_bar_neutral_color, - autocvar_hud_panel_strafehud_bar_neutral_alpha * panel_fg_alpha, - DRAWFLAG_NORMAL); - break; - - case STRAFEHUD_STYLE_PROGRESSBAR: - HUD_Panel_DrawProgressBar( - panel_pos, panel_size, "progressbar", 1, 0, 0, - autocvar_hud_panel_strafehud_bar_neutral_color, - autocvar_hud_panel_strafehud_bar_neutral_alpha * panel_fg_alpha, - DRAWFLAG_NORMAL); - } - } + wturn_bestangle = acos(sqrt(wturn_power / (wturn_power + 1))) * RAD2DEG; + wturn_valid = true; } - else - { - // calculate various zones of the strafe-o-meter - if(autocvar_hud_panel_strafehud_bar_preaccel) - preaccelzone_width = fabs(real_bestangle - real_prebestangle) / hudangle * panel_size.x; - else - preaccelzone_width = 0; - accelzone_width = (real_overturn_angle - real_bestangle) / hudangle * panel_size.x; - overturn_width = (360 - real_overturn_angle * 2) / hudangle * panel_size.x; - neutral_width = 360 / hudangle * panel_size.x - accelzone_width * 2 - preaccelzone_width * 2 - overturn_width; - - { - float current_offset = 0; - preaccelzone_right_offset = current_offset; - current_offset += preaccelzone_width; - - accelzone_right_offset = current_offset; - current_offset += accelzone_width; - - overturn_offset = current_offset; - current_offset += overturn_width; - - accelzone_left_offset = current_offset; - current_offset += accelzone_width; + } + float absolute_wturn_bestangle = wturn_bestangle; + + // draw the switch indicators as if strafing normally (W+A style), while W-turning or side strafing + float n_bestangle = 0; + float absolute_n_prebestangle = 0; // also needed for W-turn angles + bool draw_normal = ((autocvar_hud_panel_strafehud_switch >= STRAFEHUD_SWITCH_NORMAL && wturning) + || (autocvar_hud_panel_strafehud_switch == STRAFEHUD_SWITCH_SIDESTRAFE && turn)); + if(draw_normal || wturn_valid) + { + // recalculate bestangle as if strafing normally + float n_maxspeed = PHYS_MAXAIRSPEED(strafeplayer) * maxspeed_mod; + float n_movespeed = n_maxspeed; + float n_maxaccel = PHYS_AIRACCELERATE(strafeplayer) * dt * n_movespeed; + float n_bestspeed = max(n_movespeed - n_maxaccel, 0); + n_bestangle = speed > n_bestspeed + ? acos(n_bestspeed / speed) * RAD2DEG - 45 + : -45; + absolute_n_prebestangle = speed > n_movespeed + ? acos(n_movespeed / speed) * RAD2DEG + : 0; + } - preaccelzone_left_offset = current_offset; - current_offset += preaccelzone_width; + float hudangle = StrafeHUD_DetermineHudAngle(absolute_wishangle, absolute_overturnangle, strafity); - // the wrapping code may struggle if we always append it on the right side - neutral_offset = direction == STRAFEHUD_DIRECTION_LEFT ? current_offset : -neutral_width; - } + float antiflicker_angle = bound(0, autocvar_hud_panel_strafehud_antiflicker_angle, 180); + float direction = StrafeHUD_DetermineDirection(angle, wishangle, antiflicker_angle); - // shift hud if operating in view angle centered mode - if(mode == STRAFEHUD_MODE_VIEW_CENTERED) - { - shift_offset = -currentangle_offset; - bestangle_offset += shift_offset; - switch_bestangle_offset += shift_offset; - odd_bestangle_offset += shift_offset; - switch_odd_bestangle_offset += shift_offset; - n_bestangle_offset += shift_offset; - n_switch_bestangle_offset += shift_offset; - n_odd_bestangle_offset += shift_offset; - n_switch_odd_bestangle_offset += shift_offset; - wturn_left_bestangle_offset += shift_offset; - wturn_right_bestangle_offset += shift_offset; - } - if(direction == STRAFEHUD_DIRECTION_LEFT) - shift_offset += -360 / hudangle * panel_size.x; - - // calculate how far off-center the strafe zones currently are - shift_offset += (panel_size.x + neutral_width) / 2 - wishangle / hudangle * panel_size.x; - - // shift strafe zones into correct place - neutral_offset += shift_offset; - accelzone_left_offset += shift_offset; - accelzone_right_offset += shift_offset; - preaccelzone_left_offset += shift_offset; - preaccelzone_right_offset += shift_offset; - overturn_offset += shift_offset; - - // draw left acceleration zone - if(accelzone_width > 0) - HUD_Panel_DrawStrafeHUD( - accelzone_left_offset, accelzone_width, hidden_width, - autocvar_hud_panel_strafehud_bar_accel_color, - autocvar_hud_panel_strafehud_bar_accel_alpha * panel_fg_alpha, - autocvar_hud_panel_strafehud_style, STRAFEHUD_GRADIENT_LEFT, false); - - if(autocvar_hud_panel_strafehud_bar_preaccel && preaccelzone_width > 0) - HUD_Panel_DrawStrafeHUD( - preaccelzone_left_offset, preaccelzone_width, hidden_width, - autocvar_hud_panel_strafehud_bar_preaccel_color, - autocvar_hud_panel_strafehud_bar_preaccel_alpha * panel_fg_alpha, - autocvar_hud_panel_strafehud_style, STRAFEHUD_GRADIENT_RIGHT, false); - - // draw right acceleration zone - if(accelzone_width > 0) - HUD_Panel_DrawStrafeHUD( - accelzone_right_offset, accelzone_width, hidden_width, - autocvar_hud_panel_strafehud_bar_accel_color, - autocvar_hud_panel_strafehud_bar_accel_alpha * panel_fg_alpha, - autocvar_hud_panel_strafehud_style, STRAFEHUD_GRADIENT_RIGHT, false); - - if(autocvar_hud_panel_strafehud_bar_preaccel && preaccelzone_width > 0) - HUD_Panel_DrawStrafeHUD( - preaccelzone_right_offset, preaccelzone_width, hidden_width, - autocvar_hud_panel_strafehud_bar_preaccel_color, - autocvar_hud_panel_strafehud_bar_preaccel_alpha * panel_fg_alpha, - autocvar_hud_panel_strafehud_style, STRAFEHUD_GRADIENT_LEFT, false); - - // draw overturn zone - if(overturn_width > 0) - HUD_Panel_DrawStrafeHUD( - overturn_offset, overturn_width, hidden_width, - autocvar_hud_panel_strafehud_bar_overturn_color, - autocvar_hud_panel_strafehud_bar_overturn_alpha * panel_fg_alpha, - autocvar_hud_panel_strafehud_style, STRAFEHUD_GRADIENT_BOTH, false); - - // draw neutral zone - if(neutral_width > 0) - HUD_Panel_DrawStrafeHUD( - neutral_offset, neutral_width, hidden_width, - autocvar_hud_panel_strafehud_bar_neutral_color, - autocvar_hud_panel_strafehud_bar_neutral_alpha * panel_fg_alpha, - autocvar_hud_panel_strafehud_style, STRAFEHUD_GRADIENT_NONE, false); - - // only draw switch indicators if minspeed is reached (switch_bestangle_width init to 0) - if(autocvar_hud_panel_strafehud_switch && switch_bestangle_width > 0 && autocvar_hud_panel_strafehud_switch_alpha > 0) - { - // draw the switch indicator(s) - float offset = draw_normal - ? (odd_angles ? n_odd_bestangle_offset : n_bestangle_offset) - : (odd_angles ? odd_bestangle_offset : bestangle_offset); - float switch_offset = draw_normal - ? (odd_angles ? n_switch_odd_bestangle_offset : n_switch_bestangle_offset) - : (odd_angles ? switch_odd_bestangle_offset : switch_bestangle_offset); - - HUD_Panel_DrawStrafeHUD( - switch_offset, switch_bestangle_width, hidden_width, - autocvar_hud_panel_strafehud_switch_color, - autocvar_hud_panel_strafehud_switch_alpha * panel_fg_alpha, - STRAFEHUD_STYLE_DRAWFILL, STRAFEHUD_GRADIENT_NONE, true); - - if(direction == STRAFEHUD_DIRECTION_NONE || draw_normal) - HUD_Panel_DrawStrafeHUD( - offset, switch_bestangle_width, hidden_width, - autocvar_hud_panel_strafehud_switch_color, - autocvar_hud_panel_strafehud_switch_alpha * panel_fg_alpha, - STRAFEHUD_STYLE_DRAWFILL, STRAFEHUD_GRADIENT_NONE, true); - } + if(direction == STRAFEHUD_DIRECTION_LEFT) // the angle becomes negative in case we strafe left + { + n_bestangle *= -1; + bestangle *= -1; + prebestangle *= -1; + overturnangle *= -1; + } + float opposite_bestangle = -bestangle; + float n_opposite_bestangle = -n_bestangle; + + bestangle -= wishangle; + opposite_bestangle -= wishangle; + n_opposite_bestangle -= wishangle; + prebestangle -= wishangle; + overturnangle -= wishangle; + + int mode; + if(autocvar_hud_panel_strafehud_mode >= 0 && autocvar_hud_panel_strafehud_mode <= 1) + mode = autocvar_hud_panel_strafehud_mode; + else + mode = STRAFEHUD_MODE_VIEW_CENTERED; - // only draw wturn indicators if conditions were met (wturn_bestangle_width init to 0) - if(autocvar_hud_panel_strafehud_wturn && wturn_bestangle_width > 0 && autocvar_hud_panel_strafehud_wturn_alpha > 0) - { - // draw the wturn indicators - HUD_Panel_DrawStrafeHUD( - wturn_left_bestangle_offset, wturn_bestangle_width, hidden_width, - autocvar_hud_panel_strafehud_wturn_color, - autocvar_hud_panel_strafehud_wturn_alpha * panel_fg_alpha, - STRAFEHUD_STYLE_DRAWFILL, STRAFEHUD_GRADIENT_NONE, true); - HUD_Panel_DrawStrafeHUD( - wturn_right_bestangle_offset, wturn_bestangle_width, hidden_width, - autocvar_hud_panel_strafehud_wturn_color, - autocvar_hud_panel_strafehud_wturn_alpha * panel_fg_alpha, - STRAFEHUD_STYLE_DRAWFILL, STRAFEHUD_GRADIENT_NONE, true); - } - } + // best strafe acceleration angle + float changeangle = -bestangle; + float n_changeangle = -n_bestangle; + float n_opposite_changeangle = n_opposite_bestangle + n_bestangle * 2; - // slick detector - slickdetector_height = max(autocvar_hud_panel_strafehud_slickdetector_height, 0); - if(!autocvar_hud_panel_strafehud_uncapped) - slickdetector_height = min(slickdetector_height, 1); - slickdetector_height *= panel_size.y; - if(autocvar_hud_panel_strafehud_slickdetector && - autocvar_hud_panel_strafehud_slickdetector_range > 0 && - autocvar_hud_panel_strafehud_slickdetector_alpha > 0 && - slickdetector_height > 0 && - panel_size.x > 0) - { - float slicksteps = max(autocvar_hud_panel_strafehud_slickdetector_granularity, 0); - bool slickdetected = false; + // minimum speed for change indicators + float minspeed = autocvar_hud_panel_strafehud_switch_minspeed; + if(minspeed < 0) + minspeed = bestspeed + frictionspeed; - if(!autocvar_hud_panel_strafehud_uncapped) - slicksteps = min(slicksteps, 4); - slicksteps = 90 * DEG2RAD / 2 ** slicksteps; + bool opposite_direction = false; + float opposite_changeangle = 0; + if((angle > -wishangle && direction == STRAFEHUD_DIRECTION_LEFT) || (angle < -wishangle && direction == STRAFEHUD_DIRECTION_RIGHT)) + { + opposite_direction = true; + opposite_changeangle = opposite_bestangle + bestangle * 2; + } - slickdetected = real_onslick; // don't need to traceline if already touching slick + // best angle to aim at when W-turning to maximally rotate velocity vector + float wturn_left_bestangle = wturn_bestangle; + float wturn_right_bestangle = -wturn_bestangle; - // traceline into every direction - trace_dphitq3surfaceflags = 0; - vector traceorigin = strafeplayer.origin + eZ * strafeplayer.mins.z; - for(float i = 0; i < 90 * DEG2RAD - 0.00001 && !slickdetected; i += slicksteps) - { - vector slickoffset; - float slickrotate; - slickoffset.z = -cos(i) * autocvar_hud_panel_strafehud_slickdetector_range; - slickrotate = sin(i) * autocvar_hud_panel_strafehud_slickdetector_range; - - for(float j = 0; j < 360 * DEG2RAD - 0.00001 && !slickdetected; j += slicksteps) - { - slickoffset.x = sin(j) * slickrotate; - slickoffset.y = cos(j) * slickrotate; - - traceline(traceorigin, traceorigin + slickoffset, MOVE_NOMONSTERS, strafeplayer); - if((PHYS_FRICTION(strafeplayer) == 0 && trace_fraction < 1) - || (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK)) - slickdetected = true; - if(i == 0) - break; - } - } + // shift hud if operating in view angle centered mode + float shiftangle = 0; + if(mode == STRAFEHUD_MODE_VIEW_CENTERED) + { + shiftangle = -angle; + bestangle += shiftangle; + changeangle += shiftangle; + opposite_bestangle += shiftangle; + opposite_changeangle += shiftangle; + n_bestangle += shiftangle; + n_changeangle += shiftangle; + n_opposite_bestangle += shiftangle; + n_opposite_changeangle += shiftangle; + wturn_left_bestangle += shiftangle; + wturn_right_bestangle += shiftangle; + } - // if a traceline hit a slick surface - if(slickdetected) - { - vector slickdetector_size = panel_size; - slickdetector_size.y = slickdetector_height; - - // top horizontal line - drawfill( - panel_pos - eY * slickdetector_size.y, slickdetector_size, - autocvar_hud_panel_strafehud_slickdetector_color, - autocvar_hud_panel_strafehud_slickdetector_alpha * panel_fg_alpha, - DRAWFLAG_NORMAL); - - // bottom horizontal line - drawfill( - panel_pos + eY * panel_size.y, - slickdetector_size, autocvar_hud_panel_strafehud_slickdetector_color, - autocvar_hud_panel_strafehud_slickdetector_alpha * panel_fg_alpha, - DRAWFLAG_NORMAL); - } + StrafeHUD_DrawStrafeMeter(shiftangle, wishangle, absolute_bestangle, absolute_prebestangle, absolute_overturnangle, moving, hudangle); - text_offset_top = text_offset_bottom = slickdetector_height; - } + float text_offset_top; + float text_offset_bottom; + bool all_slick = PHYS_FRICTION(strafeplayer) == 0; + text_offset_top = text_offset_bottom = StrafeHUD_DrawSlickDetector(strafeplayer, all_slick && real_onground ? true : real_onslick); - // direction indicator - if(autocvar_hud_panel_strafehud_direction) - { - direction_size_vertical.x = autocvar_hud_panel_strafehud_direction_width; - if(!autocvar_hud_panel_strafehud_uncapped) - direction_size_vertical.x = min(direction_size_vertical.x, 1); - direction_size_vertical.x *= panel_size.y; - if(!autocvar_hud_panel_strafehud_uncapped) - direction_size_vertical.x = max(direction_size_vertical.x, 1); - direction_size_vertical.y = panel_size.y + direction_size_vertical.x * 2; - direction_size_vertical.z = 0; - direction_size_horizontal.x = panel_size.x * min(autocvar_hud_panel_strafehud_direction_length, .5); - direction_size_horizontal.y = direction_size_vertical.x; - direction_size_horizontal.z = 0; - } + if(autocvar_hud_panel_strafehud_direction) + StrafeHUD_DrawDirectionIndicator(direction, opposite_direction, fwd); - if(autocvar_hud_panel_strafehud_direction && - direction != STRAFEHUD_DIRECTION_NONE && - direction_size_vertical.x > 0 && - autocvar_hud_panel_strafehud_direction_alpha * panel_fg_alpha > 0) - { - bool indicator_direction = direction == STRAFEHUD_DIRECTION_LEFT; - // invert left/right when strafing backwards or when strafing towards the opposite side indicated by the direction variable - // if both conditions are true then it's inverted twice hence not inverted at all - if(!fwd != odd_angles) - indicator_direction = !indicator_direction; - - // draw the direction indicator caps at the sides of the hud - // vertical line - if(direction_size_vertical.y > 0) - drawfill( - panel_pos - eY * direction_size_horizontal.y + eX * (indicator_direction ? -direction_size_vertical.x : panel_size.x), - direction_size_vertical, autocvar_hud_panel_strafehud_direction_color, - autocvar_hud_panel_strafehud_direction_alpha * panel_fg_alpha, - DRAWFLAG_NORMAL); - - // top horizontal line - drawfill( - panel_pos + eX * (indicator_direction ? 0 : panel_size.x - direction_size_horizontal.x) - eY * direction_size_horizontal.y, - direction_size_horizontal, autocvar_hud_panel_strafehud_direction_color, - autocvar_hud_panel_strafehud_direction_alpha * panel_fg_alpha, - DRAWFLAG_NORMAL); - - // bottom horizontal line - drawfill( - panel_pos + eX * (indicator_direction ? 0 : panel_size.x - direction_size_horizontal.x) + eY * panel_size.y, - direction_size_horizontal, autocvar_hud_panel_strafehud_direction_color, - autocvar_hud_panel_strafehud_direction_alpha * panel_fg_alpha, - DRAWFLAG_NORMAL); - } + // determine the strafing ratio and the angle indicator color + vector currentangle_color = autocvar_hud_panel_strafehud_angle_neutral_color; + float strafe_ratio = 0; + if(moving) + { + float moveangle = fabs(angle + wishangle); + if(moveangle > 180) moveangle = 360 - moveangle; // restricted to between 0 and 180 - // draw the actual strafe angle - if(!immobile) + // player is overturning + if(moveangle >= absolute_overturnangle) { - float moveangle = fabs(angle + wishangle); - if(moveangle > 180) moveangle = 360 - moveangle; // restricted to between 0 and 180 - float strafe_ratio = 0; - - // player is overturning - if(moveangle >= real_overturn_angle) - { - if(moveangle == real_overturn_angle && real_overturn_angle == 180) + if(moveangle == absolute_overturnangle && absolute_overturnangle == 180) ; // everywhere gives acceleration, keep strafe_ratio as 0 - else - { + else + { currentangle_color = autocvar_hud_panel_strafehud_angle_overturn_color; - strafe_ratio = (moveangle - real_overturn_angle) / (180 - real_overturn_angle); - // moveangle is always <= 180, so this code won't run if real_overturn_angle == 180 + strafe_ratio = (moveangle - absolute_overturnangle) / (180 - absolute_overturnangle); + // moveangle is always <= 180, so this code won't run if absolute_overturnangle == 180 strafe_ratio *= -1; - } } - // player gains speed by strafing - else if(moveangle >= real_bestangle) - { - currentangle_color = autocvar_hud_panel_strafehud_angle_accel_color; - strafe_ratio = (real_overturn_angle - moveangle) / (real_overturn_angle - real_bestangle); - // if real_overturn_angle == real_bestangle, this code won't run, no need to check if their difference is 0 - } - else if(moveangle >= real_prebestangle) - { - if(autocvar_hud_panel_strafehud_bar_preaccel) - currentangle_color = autocvar_hud_panel_strafehud_angle_preaccel_color; - strafe_ratio = (moveangle - real_prebestangle) / (real_bestangle - real_prebestangle); - } - - if(autocvar_hud_panel_strafehud_style == STRAFEHUD_STYLE_GRADIENT) - currentangle_color = StrafeHUD_mixColors(autocvar_hud_panel_strafehud_angle_neutral_color, currentangle_color, fabs(strafe_ratio)); - } - - if(mode == STRAFEHUD_MODE_VIEW_CENTERED || straight_overturn) - currentangle_offset = panel_size.x / 2; - - float angleheight_offset = currentangle_size.y; - float ghost_offset = 0; - bool draw_bestangle = autocvar_hud_panel_strafehud_bestangle && (autocvar_hud_panel_strafehud_bestangle == 1 || turn) && direction != STRAFEHUD_DIRECTION_NONE; - if(draw_bestangle) - ghost_offset = bound(0, (odd_angles ? odd_bestangle_offset : bestangle_offset), panel_size.x); - - switch(autocvar_hud_panel_strafehud_angle_style) - { - case STRAFEHUD_INDICATOR_SOLID: - if(currentangle_size.x > 0 && currentangle_size.y > 0) - { - if(draw_bestangle) - drawfill( - panel_pos - eY * ((currentangle_size.y - panel_size.y) / 2) + eX * (ghost_offset - currentangle_size.x / 2), - currentangle_size, autocvar_hud_panel_strafehud_bestangle_color, - autocvar_hud_panel_strafehud_bestangle_alpha * panel_fg_alpha, - DRAWFLAG_NORMAL); - 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); - } - break; - case STRAFEHUD_INDICATOR_DASHED: - if(currentangle_size.x > 0 && currentangle_size.y > 0) - { - vector line_size = currentangle_size; - line_size.y = currentangle_size.y / (bound(2, autocvar_hud_panel_strafehud_angle_dashes, currentangle_size.y) * 2 - 1); - for(float i = 0; i < currentangle_size.y; i += line_size.y * 2) - { - if(i + line_size.y * 2 >= currentangle_size.y) - line_size.y = currentangle_size.y - i; - if(draw_bestangle) - drawfill( - panel_pos - eY * ((currentangle_size.y - panel_size.y) / 2 - i) + eX * (ghost_offset - line_size.x / 2), - line_size, autocvar_hud_panel_strafehud_bestangle_color, - autocvar_hud_panel_strafehud_bestangle_alpha * panel_fg_alpha, DRAWFLAG_NORMAL); - drawfill( - panel_pos - eY * ((currentangle_size.y - panel_size.y) / 2 - i) + eX * (currentangle_offset - line_size.x / 2), - line_size, currentangle_color, - autocvar_hud_panel_strafehud_angle_alpha * panel_fg_alpha, DRAWFLAG_NORMAL); - } - } - break; - case STRAFEHUD_INDICATOR_NONE: - default: - // don't offset text and arrows if the angle indicator line isn't drawn - angleheight_offset = panel_size.y; - currentangle_size = '0 0 0'; } - - float angle_offset_top = 0, angle_offset_bottom = 0; - - // offset text if any angle indicator is drawn - if((autocvar_hud_panel_strafehud_angle_alpha > 0) || - (autocvar_hud_panel_strafehud_bestangle && autocvar_hud_panel_strafehud_bestangle_alpha > 0)) + // player gains speed by strafing + else if(moveangle >= absolute_bestangle) { - // offset text by amount the angle indicator extrudes from the strafehud bar - angle_offset_top = angle_offset_bottom = (angleheight_offset - panel_size.y) / 2; + currentangle_color = autocvar_hud_panel_strafehud_angle_accel_color; + strafe_ratio = (absolute_overturnangle - moveangle) / (absolute_overturnangle - absolute_bestangle); + // if absolute_overturnangle == absolute_bestangle, this code won't run, no need to check if their difference is 0 } - - if(autocvar_hud_panel_strafehud_angle_arrow > 0) + else if(moveangle >= absolute_prebestangle) { - if(arrow_size > 0) - { - if(autocvar_hud_panel_strafehud_angle_arrow == 1 || autocvar_hud_panel_strafehud_angle_arrow >= 3) - { - if(draw_bestangle) - StrafeHUD_drawStrafeArrow( - panel_pos + eY * ((panel_size.y - angleheight_offset) / 2) + eX * ghost_offset, - arrow_size, autocvar_hud_panel_strafehud_bestangle_color, - autocvar_hud_panel_strafehud_bestangle_alpha * panel_fg_alpha, true, currentangle_size.x); - StrafeHUD_drawStrafeArrow( - panel_pos + eY * ((panel_size.y - angleheight_offset) / 2) + eX * currentangle_offset, - arrow_size, currentangle_color, - autocvar_hud_panel_strafehud_angle_alpha * panel_fg_alpha, true, currentangle_size.x); - - angle_offset_top += arrow_size; // further offset the top text offset if the top arrow is drawn - } - if(autocvar_hud_panel_strafehud_angle_arrow >= 2) - { - if(draw_bestangle) - StrafeHUD_drawStrafeArrow( - panel_pos + eY * ((panel_size.y - angleheight_offset) / 2 + angleheight_offset) + eX * ghost_offset, - arrow_size, autocvar_hud_panel_strafehud_bestangle_color, - autocvar_hud_panel_strafehud_bestangle_alpha * panel_fg_alpha, false, currentangle_size.x); - StrafeHUD_drawStrafeArrow( - panel_pos + eY * ((panel_size.y - angleheight_offset) / 2 + angleheight_offset) + eX * currentangle_offset, - arrow_size, currentangle_color, - autocvar_hud_panel_strafehud_angle_alpha * panel_fg_alpha, false, currentangle_size.x); - - angle_offset_bottom += arrow_size; // further offset the bottom text offset if the bottom arrow is drawn - } - } + if(autocvar_hud_panel_strafehud_bar_preaccel) + currentangle_color = autocvar_hud_panel_strafehud_angle_preaccel_color; + strafe_ratio = (moveangle - absolute_prebestangle) / (absolute_bestangle - absolute_prebestangle); } - // make sure text doesn't draw inside the strafehud bar - text_offset_top = max(angle_offset_top, text_offset_top); - text_offset_bottom = max(angle_offset_bottom, text_offset_bottom); - - draw_beginBoldFont(); - - // show speed when crossing the start trigger - { - static float startspeed = 0, starttime = 0; // displayed value and timestamp for fade out + if(StrafeHUD_IsGradient(autocvar_hud_panel_strafehud_style)) + currentangle_color = StrafeHUD_MixColors( + autocvar_hud_panel_strafehud_angle_neutral_color, + currentangle_color, fabs(strafe_ratio)); + } - // check if the start trigger was hit (will also trigger if the finish trigger was hit if those have the same ID) - if((race_nextcheckpoint == 1) || (race_checkpoint == 254 && race_nextcheckpoint == 255)) - { - if((race_checkpointtime > 0) && (starttime != race_checkpointtime)) - { - starttime = race_checkpointtime; - startspeed = race_timespeed; - } - } + float currentangle = 0; + if(mode == STRAFEHUD_MODE_VELOCITY_CENTERED) + { + // avoid switching side too much at ±180° if anti flicker is triggered + if(fabs(angle) <= 180 - antiflicker_angle) + currentangle = angle; + } - if(autocvar_hud_panel_strafehud_startspeed) - { - float startspeed_height = autocvar_hud_panel_strafehud_startspeed_size * panel_size.y; - string startspeed_text = ftos_decimals(startspeed * speed_conversion_factor, 2); - if(autocvar_hud_panel_strafehud_unit_show) - startspeed_text = strcat(startspeed_text, GetSpeedUnit(autocvar_hud_speed_unit)); - - bool was_drawn = StrafeHUD_drawTextIndicator( - startspeed_text, startspeed_height, - autocvar_hud_panel_strafehud_startspeed_color, - autocvar_hud_panel_strafehud_startspeed_fade, - starttime, text_offset_bottom, STRAFEHUD_TEXT_BOTTOM); - - if(was_drawn) - text_offset_bottom += startspeed_height; - } - } + float max_line_height = 0; + float max_top_arrow_size = 0; + float max_bottom_arrow_size = 0; - // show height achieved by a single jump - // FIXME: checking z position differences is unreliable (warpzones, teleporter, kill, etc) but using velocity to calculate jump height would be - // inaccurate in hud code (possibly different tick rate than physics, doesn't run when hud isn't drawn, rounding errors) - { - static float height_min = 0, height_max = 0; // ground and peak of jump z coordinates - static float jumpheight = 0, jumptime = 0; // displayed value and timestamp for fade out + // only draw switch indicators if minspeed is reached + if(autocvar_hud_panel_strafehud_switch && autocvar_hud_panel_strafehud_switch_alpha > 0 && speed >= minspeed) + { + // change angle indicator style + vector indicator_size; + indicator_size.x = max(panel_size.x * min(autocvar_hud_panel_strafehud_switch_line_width, 10), 1); + indicator_size.y = max(panel_size.y * min(autocvar_hud_panel_strafehud_switch_line_height, 10), 1); + indicator_size.z = 0; - // tries to catch kill and spectate but those are not reliable - if((strafeplayer.velocity.z <= 0) || real_onground || swimming || IS_DEAD(strafeplayer) || !IS_PLAYER(strafeplayer)) - { - height_min = height_max = strafeplayer.origin.z; - } - else if(strafeplayer.origin.z > height_max) - { - height_max = strafeplayer.origin.z; - float jumpheight_new = height_max - height_min; - - if((jumpheight_new * length_conversion_factor) > max(autocvar_hud_panel_strafehud_jumpheight_min, 0)) - { - jumpheight = jumpheight_new; - jumptime = time; - } - } + float num_dashes = rint(autocvar_hud_panel_strafehud_switch_line); + bool has_top_arrow = autocvar_hud_panel_strafehud_switch_arrow == 1 || autocvar_hud_panel_strafehud_switch_arrow >= 3; + bool has_bottom_arrow = autocvar_hud_panel_strafehud_switch_arrow >= 2; - if(autocvar_hud_panel_strafehud_jumpheight) - { - float jumpheight_height = autocvar_hud_panel_strafehud_jumpheight_size * panel_size.y; - string jumpheight_text = ftos_decimals(jumpheight * length_conversion_factor, length_decimals); - if(autocvar_hud_panel_strafehud_unit_show) - jumpheight_text = strcat(jumpheight_text, GetLengthUnit(autocvar_hud_speed_unit)); - - bool was_drawn = StrafeHUD_drawTextIndicator( - jumpheight_text, jumpheight_height, - autocvar_hud_panel_strafehud_jumpheight_color, - autocvar_hud_panel_strafehud_jumpheight_fade, - jumptime, text_offset_top, STRAFEHUD_TEXT_TOP); - - if(was_drawn) - text_offset_top += jumpheight_height; - } + // there's only one size cvar for the arrows, they will always have a 45° angle to ensure proper rendering without antialiasing + float arrow_size = max(panel_size.y * min(autocvar_hud_panel_strafehud_switch_arrow_size, 10), 1); + + if(num_dashes > 0) + max_line_height = max(max_line_height, indicator_size.y); + if(has_top_arrow) + max_top_arrow_size = max(max_top_arrow_size, arrow_size); + if(has_bottom_arrow) + max_bottom_arrow_size = max(max_bottom_arrow_size, arrow_size); + + // draw the change indicator(s) + float current_changeangle = draw_normal + ? (opposite_direction ? n_opposite_changeangle : n_changeangle) + : (opposite_direction ? opposite_changeangle : changeangle); + float opposite_changeangle = draw_normal + ? (opposite_direction ? n_opposite_bestangle : n_bestangle) + : (opposite_direction ? opposite_bestangle : bestangle); + + StrafeHUD_DrawAngleIndicator( + current_changeangle, indicator_size, arrow_size, num_dashes, + has_top_arrow, has_bottom_arrow, autocvar_hud_panel_strafehud_switch_color, + autocvar_hud_panel_strafehud_switch_alpha, hudangle); + + if(direction == STRAFEHUD_DIRECTION_NONE || draw_normal) + { + StrafeHUD_DrawAngleIndicator( + opposite_changeangle, indicator_size, arrow_size, num_dashes, + has_top_arrow, has_bottom_arrow, autocvar_hud_panel_strafehud_switch_color, + autocvar_hud_panel_strafehud_switch_alpha, hudangle); } - - draw_endBoldFont(); } - hud_lasttime = time; -} -// functions to make hud elements align perfectly in the hud area -void HUD_Panel_DrawStrafeHUD(float offset, float width, float hidden_width, vector color, float alpha, int type, int gradientType, bool offset_centered) -{ - float mirror_offset, mirror_width; - vector size = panel_size; - vector mirror_size = panel_size; - float overflow_width = 0, overflow_mirror_width = 0; - float original_width = width; // required for gradient + if(autocvar_hud_panel_strafehud_bestangle && autocvar_hud_panel_strafehud_bestangle_alpha > 0 && + (autocvar_hud_panel_strafehud_bestangle == 1 || turn) && direction != STRAFEHUD_DIRECTION_NONE) + { + // best angle indicator style + vector indicator_size; + indicator_size.x = max(panel_size.x * min(autocvar_hud_panel_strafehud_bestangle_line_width, 10), 1); + indicator_size.y = max(panel_size.y * min(autocvar_hud_panel_strafehud_bestangle_line_height, 10), 1); + indicator_size.z = 0; - if(offset_centered) // offset gives the center of the bar, not left edge - offset -= original_width / 2; + float num_dashes = rint(autocvar_hud_panel_strafehud_bestangle_line); + bool has_top_arrow = autocvar_hud_panel_strafehud_bestangle_arrow == 1 || autocvar_hud_panel_strafehud_bestangle_arrow >= 3; + bool has_bottom_arrow = autocvar_hud_panel_strafehud_bestangle_arrow >= 2; - if(type == STRAFEHUD_STYLE_GRADIENT && gradientType == STRAFEHUD_GRADIENT_NONE) - type = STRAFEHUD_STYLE_DRAWFILL; + // there's only one size cvar for the arrows, they will always have a 45° angle to ensure proper rendering without antialiasing + float arrow_size = max(panel_size.y * min(autocvar_hud_panel_strafehud_bestangle_arrow_size, 10), 1); - if(alpha <= 0 && type != STRAFEHUD_STYLE_GRADIENT || width <= 0) - return; + if(num_dashes > 0) + max_line_height = max(max_line_height, indicator_size.y); + if(has_top_arrow) + max_top_arrow_size = max(max_top_arrow_size, arrow_size); + if(has_bottom_arrow) + max_bottom_arrow_size = max(max_bottom_arrow_size, arrow_size); - if(offset < 0) - { - mirror_width = min(fabs(offset), width); - mirror_offset = panel_size.x + hidden_width - fabs(offset); - width += offset; - offset = 0; - } - else - { - mirror_width = min(offset + width - panel_size.x - hidden_width, width); - mirror_offset = max(offset - panel_size.x - hidden_width, 0); - } + float ghostangle = opposite_direction ? opposite_bestangle : bestangle; - width = max(width, 0); - if((offset + width) > panel_size.x) - { - overflow_width = (offset + width) - panel_size.x; - width = panel_size.x - offset; + StrafeHUD_DrawAngleIndicator( + ghostangle, indicator_size, arrow_size, num_dashes, + has_top_arrow, has_bottom_arrow, autocvar_hud_panel_strafehud_bestangle_color, + autocvar_hud_panel_strafehud_bestangle_alpha, hudangle); } - size.x = width; - if(mirror_offset < 0) + // only draw wturn indicators if conditions were met + if(wturn_valid && !onground && is_aircontrol_direction && + autocvar_hud_panel_strafehud_wturn_alpha > 0 && + absolute_wturn_bestangle < absolute_n_prebestangle && + ((autocvar_hud_panel_strafehud_wturn && wturning) || + (autocvar_hud_panel_strafehud_wturn == STRAFEHUD_WTURN_NORMAL && !turn) || + (autocvar_hud_panel_strafehud_wturn == STRAFEHUD_WTURN_SIDESTRAFE))) { - mirror_width += mirror_offset; - mirror_offset = 0; - } + // wturn angle indicator style + vector indicator_size; + indicator_size.x = max(panel_size.x * min(autocvar_hud_panel_strafehud_wturn_line_width, 10), 1); + indicator_size.y = max(panel_size.y * min(autocvar_hud_panel_strafehud_wturn_line_height, 10), 1); + indicator_size.z = 0; - mirror_width = max(mirror_width, 0); - if((mirror_offset + mirror_width) > panel_size.x) - { - overflow_mirror_width = (mirror_offset + mirror_width) - panel_size.x; - mirror_width = panel_size.x - mirror_offset; + float num_dashes = rint(autocvar_hud_panel_strafehud_wturn_line); + bool has_top_arrow = autocvar_hud_panel_strafehud_wturn_arrow == 1 || autocvar_hud_panel_strafehud_wturn_arrow >= 3; + bool has_bottom_arrow = autocvar_hud_panel_strafehud_wturn_arrow >= 2; + + // there's only one size cvar for the arrows, they will always have a 45° angle to ensure proper rendering without antialiasing + float arrow_size = max(panel_size.y * min(autocvar_hud_panel_strafehud_wturn_arrow_size, 10), 1); + + if(num_dashes > 0) + max_line_height = max(max_line_height, indicator_size.y); + if(has_top_arrow) + max_top_arrow_size = max(max_top_arrow_size, arrow_size); + if(has_bottom_arrow) + max_bottom_arrow_size = max(max_bottom_arrow_size, arrow_size); + + // draw the wturn indicators + StrafeHUD_DrawAngleIndicator( + wturn_left_bestangle, indicator_size, arrow_size, num_dashes, + has_top_arrow, has_bottom_arrow, autocvar_hud_panel_strafehud_wturn_color, + autocvar_hud_panel_strafehud_wturn_alpha, hudangle); + StrafeHUD_DrawAngleIndicator( + wturn_right_bestangle, indicator_size, arrow_size, num_dashes, + has_top_arrow, has_bottom_arrow, autocvar_hud_panel_strafehud_wturn_color, + autocvar_hud_panel_strafehud_wturn_alpha, hudangle); } - mirror_size.x = mirror_width; - switch(type) + if(autocvar_hud_panel_strafehud_angle_alpha > 0) { - default: - case STRAFEHUD_STYLE_DRAWFILL: // no styling (drawfill) - if(mirror_size.x > 0 && mirror_size.y > 0) - drawfill(panel_pos + eX * mirror_offset, mirror_size, color, alpha, DRAWFLAG_NORMAL); - if(size.x > 0 && size.y > 0) - drawfill(panel_pos + eX * offset, size, color, alpha, DRAWFLAG_NORMAL); - break; - - case STRAFEHUD_STYLE_PROGRESSBAR: // progress bar style - 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, DRAWFLAG_NORMAL); - if(size.x > 0 && size.y > 0) - HUD_Panel_DrawProgressBar( - panel_pos + eX * offset, - size, "progressbar", - 1, 0, 0, color, alpha, DRAWFLAG_NORMAL); - break; - - case STRAFEHUD_STYLE_GRADIENT: // gradient style (types: 1 = left, 2 = right, 3 = both) - // determine whether the gradient starts in the mirrored or the non-mirrored area - int gradient_start; - float gradient_offset, gradient_mirror_offset; - - if(offset == 0 && mirror_offset == 0) - gradient_start = width > mirror_width ? 2 : 1; - else if(offset == 0) - gradient_start = 2; - else if(mirror_offset == 0) - gradient_start = 1; - else - gradient_start = 0; - - switch(gradient_start) - { - default: - case 0: // no offset required - gradient_offset = gradient_mirror_offset = 0; - break; - case 1: // offset starts in non-mirrored area, mirrored area requires offset - gradient_offset = 0; - gradient_mirror_offset = original_width - (mirror_width + overflow_mirror_width); - break; - case 2: // offset starts in mirrored area, non-mirrored area requires offset - gradient_offset = original_width - (width + overflow_width); - gradient_mirror_offset = 0; - } + // current angle indicator style + vector indicator_size; + indicator_size.x = max(panel_size.x * min(autocvar_hud_panel_strafehud_angle_line_width, 10), 1); + indicator_size.y = max(panel_size.y * min(autocvar_hud_panel_strafehud_angle_line_height, 10), 1); + indicator_size.z = 0; - StrafeHUD_drawGradient( - color, autocvar_hud_panel_strafehud_bar_neutral_color, - mirror_size, original_width, mirror_offset, - alpha, gradient_mirror_offset, gradientType); + float num_dashes = rint(autocvar_hud_panel_strafehud_angle_line); + bool has_top_arrow = autocvar_hud_panel_strafehud_angle_arrow == 1 || autocvar_hud_panel_strafehud_angle_arrow >= 3; + bool has_bottom_arrow = autocvar_hud_panel_strafehud_angle_arrow >= 2; - StrafeHUD_drawGradient( - color, autocvar_hud_panel_strafehud_bar_neutral_color, - size, original_width, offset, - alpha, gradient_offset, gradientType); + // there's only one size cvar for the arrows, they will always have a 45° angle to ensure proper rendering without antialiasing + float arrow_size = max(panel_size.y * min(autocvar_hud_panel_strafehud_angle_arrow_size, 10), 1); + + if(num_dashes > 0) + max_line_height = max(max_line_height, indicator_size.y); + if(has_top_arrow) + max_top_arrow_size = max(max_top_arrow_size, arrow_size); + if(has_bottom_arrow) + max_bottom_arrow_size = max(max_bottom_arrow_size, arrow_size); + + StrafeHUD_DrawAngleIndicator( + currentangle, indicator_size, arrow_size, num_dashes, + has_top_arrow, has_bottom_arrow, currentangle_color, + autocvar_hud_panel_strafehud_angle_alpha, hudangle); } -} - -vector StrafeHUD_mixColors(vector color1, vector color2, float ratio) -{ - vector mixedColor; - if(ratio <= 0) return color1; - if(ratio >= 1) return color2; - mixedColor.x = color1.x + (color2.x - color1.x) * ratio; - mixedColor.y = color1.y + (color2.y - color1.y) * ratio; - mixedColor.z = color1.z + (color2.z - color1.z) * ratio; - return mixedColor; -} -void StrafeHUD_drawGradient(vector color1, vector color2, vector size, float original_width, float offset, float alpha, float gradientOffset, int gradientType) -{ - float color_ratio, alpha1, alpha2; - vector segment_size = size; - alpha1 = bound(0, alpha, 1); - alpha2 = bound(0, autocvar_hud_panel_strafehud_bar_neutral_alpha * panel_fg_alpha, 1); - if((alpha1 + alpha2) == 0) return; - color_ratio = alpha1 / (alpha1 + alpha2); - for(int i = 0; i < size.x; ++i) + // offset text by amount the angle indicator extrudes from the strafehud bar { - float ratio, alpha_ratio, combine_ratio1, combine_ratio2; - segment_size.x = min(size.x - i, 1); // each gradient segment is 1 unit wide except if there is less than 1 unit of gradient remaining - ratio = (i + segment_size.x / 2 + gradientOffset) / original_width * (gradientType == STRAFEHUD_GRADIENT_BOTH ? 2 : 1); - if(ratio > 1) ratio = 2 - ratio; - if(gradientType != STRAFEHUD_GRADIENT_RIGHT) ratio = 1 - ratio; - alpha_ratio = alpha1 - (alpha1 - alpha2) * ratio; - combine_ratio1 = ratio * (1 - color_ratio); - combine_ratio2 = (1 - ratio) * color_ratio; - ratio = (combine_ratio1 + combine_ratio2) == 0 ? 1 : combine_ratio1 / (combine_ratio1 + combine_ratio2); - - if(alpha_ratio > 0) - drawfill( - panel_pos + eX * (offset + i), - segment_size, - StrafeHUD_mixColors(color1, color2, ratio), - alpha_ratio, - DRAWFLAG_NORMAL); - } -} + float line_height_offset = max_line_height; -// draw the strafe arrows (inspired by drawspritearrow() in common/mutators/mutator/waypoints/waypointsprites.qc) -void StrafeHUD_drawStrafeArrow(vector origin, float size, vector color, float alpha, bool flipped, float connection_width) -{ - origin = HUD_Shift(origin); - float width = HUD_ScaleX(size * 2 + connection_width); - float height = HUD_ScaleY(size); - if(flipped) origin -= size * eY; - R_BeginPolygon("", DRAWFLAG_NORMAL, true); - if(connection_width > 0) - { - R_PolygonVertex(origin + (connection_width / 2 * eX) + (flipped ? height * eY : '0 0 0'), '0 0 0', color, alpha); - R_PolygonVertex(origin - (connection_width / 2 * eX) + (flipped ? height * eY : '0 0 0'), '0 0 0', color, alpha); - } - else - { - R_PolygonVertex(origin + (flipped ? height * eY : '0 0 0'), '0 0 0', color, alpha); - } - R_PolygonVertex(origin + (flipped ? '0 0 0' : height * eY) - (width / 2) * eX, '0 0 0', color, alpha); - R_PolygonVertex(origin + (flipped ? '0 0 0' : height * eY) + (width / 2) * eX, '0 0 0', color, alpha); - R_EndPolygon(); -} + // amount line extrudes from the strafehud bar + line_height_offset = (line_height_offset - panel_size.y) / 2; -// draw a fading text indicator above or below the strafe meter, return true if something was displayed -bool StrafeHUD_drawTextIndicator(string text, float height, vector color, float fadetime, float lasttime, float offset, int position) -{ - if((height <= 0) || (lasttime <= 0) || (fadetime <= 0) || ((time - lasttime) >= fadetime)) - return false; + // further offset the top text offset if the top arrow is drawn + float angle_offset_top; + angle_offset_top = line_height_offset + max_top_arrow_size; - float alpha = cos(((time - lasttime) / fadetime) * M_PI_2); // fade non-linear like the physics panel does - vector size = panel_size; - size.y = height; + // further offset the bottom text offset if the bottom arrow is drawn + float angle_offset_bottom; + angle_offset_bottom = line_height_offset + max_bottom_arrow_size; - switch(position) - { - case STRAFEHUD_TEXT_TOP: - offset += size.y; - offset *= -1; - break; - case STRAFEHUD_TEXT_BOTTOM: - offset += panel_size.y; - break; + // make sure text does not draw inside the strafehud bar + text_offset_top = max(angle_offset_top, text_offset_top); + text_offset_bottom = max(angle_offset_bottom, text_offset_bottom); } - drawstring_aspect(panel_pos + eY * offset, text, size, color, alpha * panel_fg_alpha, DRAWFLAG_NORMAL); - return true; -} + StrafeHUD_DrawVerticalAngle(strafeplayer, text_offset_top, text_offset_bottom); -// length unit conversion (km and miles are only included to match the GetSpeedUnit* functions) -float GetLengthUnitFactor(int length_unit) -{ - switch(length_unit) - { - default: - case 1: return 1.0; - case 2: return 0.0254; - case 3: return 0.0254 * 0.001; - case 4: return 0.0254 * 0.001 * 0.6213711922; - case 5: return 0.0254 * 0.001 * 0.5399568035; - } -} + draw_beginBoldFont(); + StrafeHUD_DrawStartSpeed(race_timespeed, text_offset_top, text_offset_bottom); + StrafeHUD_DrawStrafeEfficiency(strafe_ratio, text_offset_top, text_offset_bottom); + StrafeHUD_DrawJumpHeight(strafeplayer, real_onground, swimming, text_offset_top, text_offset_bottom); + draw_endBoldFont(); -string GetLengthUnit(int length_unit) -{ - switch(length_unit) - { - // translator-friendly strings without the initial space - default: - case 1: return strcat(" ", _("qu")); - case 2: return strcat(" ", _("m")); - case 3: return strcat(" ", _("km")); - case 4: return strcat(" ", _("mi")); - case 5: return strcat(" ", _("nmi")); - } + StrafeHUD_Sonar(strafe_ratio, StrafeHUD_UpdateSonarSound()); + + hud_lasttime = time; } diff --git a/qcsrc/client/hud/panel/strafehud.qh b/qcsrc/client/hud/panel/strafehud.qh index 398ab5597..a84d8abf5 100644 --- a/qcsrc/client/hud/panel/strafehud.qh +++ b/qcsrc/client/hud/panel/strafehud.qh @@ -1,17 +1,23 @@ #pragma once #include "../panel.qh" +#include int autocvar_hud_panel_strafehud = 3; bool autocvar__hud_panel_strafehud_demo = false; bool autocvar_hud_panel_strafehud_dynamichud = true; int autocvar_hud_panel_strafehud_mode = 0; float autocvar_hud_panel_strafehud_range = 90; -float autocvar_hud_panel_strafehud_range_sidestrafe = -1; +float autocvar_hud_panel_strafehud_range_sidestrafe = -2; int autocvar_hud_panel_strafehud_style = 2; bool autocvar_hud_panel_strafehud_unit_show = true; -bool autocvar_hud_panel_strafehud_uncapped = false; int autocvar_hud_panel_strafehud_onground_mode = 2; bool autocvar_hud_panel_strafehud_onground_friction = true; + +float autocvar_hud_panel_strafehud_timeout_ground = 0.1; +float autocvar_hud_panel_strafehud_timeout_turn = 0.1; +float autocvar_hud_panel_strafehud_antiflicker_angle = 0.01; +float autocvar_hud_panel_strafehud_fps_update = 0.5; + bool autocvar_hud_panel_strafehud_bar_preaccel = true; vector autocvar_hud_panel_strafehud_bar_preaccel_color = '0 1 0'; float autocvar_hud_panel_strafehud_bar_preaccel_alpha = 0.5; @@ -21,63 +27,90 @@ vector autocvar_hud_panel_strafehud_bar_accel_color = '0 1 0'; float autocvar_hud_panel_strafehud_bar_accel_alpha = 0.5; vector autocvar_hud_panel_strafehud_bar_overturn_color = '1 0 1'; float autocvar_hud_panel_strafehud_bar_overturn_alpha = 0.5; -int autocvar_hud_panel_strafehud_angle_style = 0; -int autocvar_hud_panel_strafehud_angle_dashes = 4; + float autocvar_hud_panel_strafehud_angle_alpha = 0.8; -float autocvar_hud_panel_strafehud_angle_height = 1; -float autocvar_hud_panel_strafehud_angle_width = 0.001; vector autocvar_hud_panel_strafehud_angle_preaccel_color = '0 1 1'; vector autocvar_hud_panel_strafehud_angle_neutral_color = '1 1 0'; vector autocvar_hud_panel_strafehud_angle_accel_color = '0 1 1'; vector autocvar_hud_panel_strafehud_angle_overturn_color = '1 0 1'; +int autocvar_hud_panel_strafehud_angle_line = 0; +float autocvar_hud_panel_strafehud_angle_line_width = 0.001; +float autocvar_hud_panel_strafehud_angle_line_height = 1; int autocvar_hud_panel_strafehud_angle_arrow = 1; float autocvar_hud_panel_strafehud_angle_arrow_size = 0.5; int autocvar_hud_panel_strafehud_bestangle = 1; vector autocvar_hud_panel_strafehud_bestangle_color = '1 1 1'; float autocvar_hud_panel_strafehud_bestangle_alpha = 0.5; +int autocvar_hud_panel_strafehud_bestangle_line = 0; +float autocvar_hud_panel_strafehud_bestangle_line_width = 0.001; +float autocvar_hud_panel_strafehud_bestangle_line_height = 1; +int autocvar_hud_panel_strafehud_bestangle_arrow = 1; +float autocvar_hud_panel_strafehud_bestangle_arrow_size = 0.5; int autocvar_hud_panel_strafehud_switch = 1; float autocvar_hud_panel_strafehud_switch_minspeed = -1; vector autocvar_hud_panel_strafehud_switch_color = '1 1 0'; -float autocvar_hud_panel_strafehud_switch_alpha = 1; -float autocvar_hud_panel_strafehud_switch_width = 0.003; +float autocvar_hud_panel_strafehud_switch_alpha = 0.5; +int autocvar_hud_panel_strafehud_switch_line = 0; +float autocvar_hud_panel_strafehud_switch_line_width = 0.001; +float autocvar_hud_panel_strafehud_switch_line_height = 1; +int autocvar_hud_panel_strafehud_switch_arrow = 1; +float autocvar_hud_panel_strafehud_switch_arrow_size = 0.5; int autocvar_hud_panel_strafehud_wturn = 1; -vector autocvar_hud_panel_strafehud_wturn_color = '0 1 1'; -float autocvar_hud_panel_strafehud_wturn_alpha = 1; -float autocvar_hud_panel_strafehud_wturn_width = 0.003; +vector autocvar_hud_panel_strafehud_wturn_color = '0 0 1'; +float autocvar_hud_panel_strafehud_wturn_alpha = 0.5; bool autocvar_hud_panel_strafehud_wturn_proper = false; bool autocvar_hud_panel_strafehud_wturn_unrestricted = false; +int autocvar_hud_panel_strafehud_wturn_line = 0; +float autocvar_hud_panel_strafehud_wturn_line_width = 0.001; +float autocvar_hud_panel_strafehud_wturn_line_height = 1; +int autocvar_hud_panel_strafehud_wturn_arrow = 1; +float autocvar_hud_panel_strafehud_wturn_arrow_size = 0.5; + bool autocvar_hud_panel_strafehud_direction = false; vector autocvar_hud_panel_strafehud_direction_color = '0 0.5 1'; float autocvar_hud_panel_strafehud_direction_alpha = 1; float autocvar_hud_panel_strafehud_direction_width = 0.25; float autocvar_hud_panel_strafehud_direction_length = 0.02; + bool autocvar_hud_panel_strafehud_slickdetector = true; float autocvar_hud_panel_strafehud_slickdetector_range = 200; int autocvar_hud_panel_strafehud_slickdetector_granularity = 1; vector autocvar_hud_panel_strafehud_slickdetector_color = '0 1 1'; float autocvar_hud_panel_strafehud_slickdetector_alpha = 0.5; float autocvar_hud_panel_strafehud_slickdetector_height = 0.125; + bool autocvar_hud_panel_strafehud_startspeed = true; float autocvar_hud_panel_strafehud_startspeed_fade = 4; vector autocvar_hud_panel_strafehud_startspeed_color = '1 0.75 0'; +vector autocvar_hud_panel_strafehud_startspeed_pos = '0 -1 0'; float autocvar_hud_panel_strafehud_startspeed_size = 1.5; bool autocvar_hud_panel_strafehud_jumpheight = false; float autocvar_hud_panel_strafehud_jumpheight_fade = 4; float autocvar_hud_panel_strafehud_jumpheight_min = 50; vector autocvar_hud_panel_strafehud_jumpheight_color = '0 1 0.75'; +vector autocvar_hud_panel_strafehud_jumpheight_pos = '0 -2 0'; float autocvar_hud_panel_strafehud_jumpheight_size = 1.5; -float autocvar_hud_panel_strafehud_timeout_ground = 0.1; -float autocvar_hud_panel_strafehud_timeout_turn = 0.1; -float autocvar_hud_panel_strafehud_antiflicker_angle = 0.01; -float autocvar_hud_panel_strafehud_fps_update = 0.5; +bool autocvar_hud_panel_strafehud_vangle = false; +vector autocvar_hud_panel_strafehud_vangle_color = '0.75 0.75 0.75'; +vector autocvar_hud_panel_strafehud_vangle_pos = '-0.25 1 0'; +float autocvar_hud_panel_strafehud_vangle_size = 1; +bool autocvar_hud_panel_strafehud_strafeefficiency = false; +vector autocvar_hud_panel_strafehud_strafeefficiency_pos = '0.25 1 0'; +float autocvar_hud_panel_strafehud_strafeefficiency_size = 1; +int autocvar_hud_panel_strafehud_projection = 0; -void HUD_Panel_DrawStrafeHUD(float, float, float, vector, float, int, int, bool); -vector StrafeHUD_mixColors(vector, vector, float); -void StrafeHUD_drawGradient(vector, vector, vector, float, float, float, float, int); -float GetLengthUnitFactor(int); -string GetLengthUnit(int); -void StrafeHUD_drawStrafeArrow(vector, float, vector, float, bool, float); -bool StrafeHUD_drawTextIndicator(string, float, vector, float, float, float, int); +bool autocvar_hud_panel_strafehud_sonar = false; +string autocvar_hud_panel_strafehud_sonar_audio = "misc/talk"; +float autocvar_hud_panel_strafehud_sonar_start = 0.5; +float autocvar_hud_panel_strafehud_sonar_interval_start = 0.333333; +float autocvar_hud_panel_strafehud_sonar_interval_range = -0.222222; +float autocvar_hud_panel_strafehud_sonar_interval_exponent = 1; +float autocvar_hud_panel_strafehud_sonar_volume_start = 0.333333; +float autocvar_hud_panel_strafehud_sonar_volume_range = 0.666666; +float autocvar_hud_panel_strafehud_sonar_volume_exponent = 1; +float autocvar_hud_panel_strafehud_sonar_pitch_start = 0.9; +float autocvar_hud_panel_strafehud_sonar_pitch_range = 0.1; +float autocvar_hud_panel_strafehud_sonar_pitch_exponent = 1; const int STRAFEHUD_MODE_VIEW_CENTERED = 0; const int STRAFEHUD_MODE_VELOCITY_CENTERED = 1; @@ -107,17 +140,17 @@ const int STRAFEHUD_KEYS_BACKWARD = 2; const int STRAFEHUD_STYLE_DRAWFILL = 0; const int STRAFEHUD_STYLE_PROGRESSBAR = 1; const int STRAFEHUD_STYLE_GRADIENT = 2; +const int STRAFEHUD_STYLE_SOFT_GRADIENT = 3; const int STRAFEHUD_GRADIENT_NONE = 0; const int STRAFEHUD_GRADIENT_LEFT = 1; const int STRAFEHUD_GRADIENT_RIGHT = 2; const int STRAFEHUD_GRADIENT_BOTH = 3; -const int STRAFEHUD_INDICATOR_NONE = 0; -const int STRAFEHUD_INDICATOR_SOLID = 1; -const int STRAFEHUD_INDICATOR_DASHED = 2; - -const int STRAFEHUD_TEXT_TOP = 0; -const int STRAFEHUD_TEXT_BOTTOM = 1; +const int STRAFEHUD_PROJECTION_LINEAR = 0; +const int STRAFEHUD_PROJECTION_PERSPECTIVE = 1; +const int STRAFEHUD_PROJECTION_PANORAMIC = 2; const float ACOS_SQRT2_3_DEG = 35.2643896827546543153; /* acos(sqrt(2/3)) * RAD2DEG */ + +float GeomLerp(float a, float _lerp, float b); // declare GeomLerp here since there's no header file for it diff --git a/qcsrc/client/hud/panel/strafehud/_mod.inc b/qcsrc/client/hud/panel/strafehud/_mod.inc new file mode 100644 index 000000000..56ddc7bbc --- /dev/null +++ b/qcsrc/client/hud/panel/strafehud/_mod.inc @@ -0,0 +1,5 @@ +// genmod.sh autogenerated file; do not modify +#include +#include +#include +#include diff --git a/qcsrc/client/hud/panel/strafehud/_mod.qh b/qcsrc/client/hud/panel/strafehud/_mod.qh new file mode 100644 index 000000000..0a656e068 --- /dev/null +++ b/qcsrc/client/hud/panel/strafehud/_mod.qh @@ -0,0 +1,5 @@ +// genmod.sh autogenerated file; do not modify +#include +#include +#include +#include diff --git a/qcsrc/client/hud/panel/strafehud/draw.qc b/qcsrc/client/hud/panel/strafehud/draw.qc new file mode 100644 index 000000000..51f92f8c9 --- /dev/null +++ b/qcsrc/client/hud/panel/strafehud/draw.qc @@ -0,0 +1,336 @@ +#include "draw.qh" + +#include + +// draw the strafe-o-meter bar +// aligns HUD elements perfectly in the hud area +// also deals with wrapping around on edges, different HUD styles, etc. +void StrafeHUD_DrawStrafeHUD(float startangle, float offsetangle, vector color, float alpha, int type, int gradient_type, float range) +{ + float offset = StrafeHUD_AngleToOffset(startangle % 360, range); + float width = StrafeHUD_AngleToWidth(offsetangle, range); + float mirror_offset; + float mirror_width; + + if(width <= 0) return; + + if(StrafeHUD_IsGradient(type)) + { + if(gradient_type == STRAFEHUD_GRADIENT_NONE) + { + type = STRAFEHUD_STYLE_DRAWFILL; + if(alpha <= 0) return; + } + } + else if(alpha <= 0) return; + + // how much is hidden by the current hud angle + float hidden_width = (360 - range) / range * panel_size.x; + float total_width = panel_size.x + hidden_width; + float original_width = width; // required for gradient + + if(offset < 0) + { + mirror_width = min(fabs(offset), width); + mirror_offset = offset + total_width; + width += offset; + offset = 0; + } + else + { + mirror_offset = offset - total_width; + mirror_width = min(mirror_offset + width, width); + if(mirror_offset < 0) mirror_offset = 0; + } + + float overflow_width = offset + width - panel_size.x; + width = max(width, 0); + if(overflow_width > 0) + width = panel_size.x - offset; + else + overflow_width = 0; + + vector size = panel_size; + size.x = width; + + float original_offset = offset; + + // the accelerated gradient does the projection later + if(type != STRAFEHUD_STYLE_GRADIENT) + { + if(size.x > 0) + size.x = StrafeHUD_ProjectWidth(offset, size.x, range); + + offset = StrafeHUD_ProjectOffset(offset, range, false); + } + + if(mirror_offset < 0) + { + mirror_width += mirror_offset; + mirror_offset = 0; + } + + float overflow_mirror_width = mirror_offset + mirror_width - panel_size.x; + mirror_width = max(mirror_width, 0); + if(overflow_mirror_width > 0) + mirror_width = panel_size.x - mirror_offset; + else + overflow_mirror_width = 0; + + vector mirror_size = panel_size; + mirror_size.x = mirror_width; + + float original_mirror_offset = mirror_offset; + + // the accelerated gradient does the projection later + if(type != STRAFEHUD_STYLE_GRADIENT) + { + if(mirror_size.x > 0) + mirror_size.x = StrafeHUD_ProjectWidth(mirror_offset, mirror_size.x, range); + + mirror_offset = StrafeHUD_ProjectOffset(mirror_offset, range, false); + } + + switch(type) + { + default: + case STRAFEHUD_STYLE_DRAWFILL: // no styling (drawfill) + if(mirror_size.x > 0 && mirror_size.y > 0) + drawfill(panel_pos + eX * mirror_offset, mirror_size, color, alpha, DRAWFLAG_NORMAL); + if(size.x > 0 && size.y > 0) + drawfill(panel_pos + eX * offset, size, color, alpha, DRAWFLAG_NORMAL); + break; + + case STRAFEHUD_STYLE_PROGRESSBAR: // progress bar style + 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, DRAWFLAG_NORMAL); + if(size.x > 0 && size.y > 0) + HUD_Panel_DrawProgressBar( + panel_pos + eX * offset, + size, "progressbar", + 1, 0, 0, color, alpha, DRAWFLAG_NORMAL); + break; + + case STRAFEHUD_STYLE_GRADIENT: // gradient style (types: 1 = left, 2 = right, 3 = both) + case STRAFEHUD_STYLE_SOFT_GRADIENT: + // determine whether the gradient starts in the mirrored or the non-mirrored area + int gradient_start; + float gradient_offset, gradient_mirror_offset; + + if(offset == 0 && mirror_offset == 0) + gradient_start = width > mirror_width ? 2 : 1; + else if(offset == 0) + gradient_start = 2; + else if(mirror_offset == 0) + gradient_start = 1; + else + gradient_start = 0; + + switch(gradient_start) + { + default: + case 0: // no offset required + gradient_offset = gradient_mirror_offset = 0; + break; + case 1: // offset starts in non-mirrored area, mirrored area requires offset + gradient_offset = 0; + gradient_mirror_offset = original_width - (mirror_width + overflow_mirror_width); + break; + case 2: // offset starts in mirrored area, non-mirrored area requires offset + gradient_offset = original_width - (width + overflow_width); + gradient_mirror_offset = 0; + } + + if(type == STRAFEHUD_STYLE_GRADIENT) + { + if(mirror_size.x > 0) + StrafeHUD_DrawGradient( + color, autocvar_hud_panel_strafehud_bar_neutral_color, + mirror_size, original_width, mirror_offset, alpha, + gradient_mirror_offset, gradient_type, range); + + if(size.x > 0) + StrafeHUD_DrawGradient( + color, autocvar_hud_panel_strafehud_bar_neutral_color, + size, original_width, offset, alpha, + gradient_offset, gradient_type, range); + } + else + { + if(mirror_size.x > 0) + StrafeHUD_DrawSoftGradient( + color, autocvar_hud_panel_strafehud_bar_neutral_color, + mirror_size, original_width, mirror_offset, original_mirror_offset, + alpha, gradient_mirror_offset, gradient_type, range); + + if(size.x > 0) + StrafeHUD_DrawSoftGradient( + color, autocvar_hud_panel_strafehud_bar_neutral_color, + size, original_width, offset, original_offset, + alpha, gradient_offset, gradient_type, range); + } + } +} + +// accelerated gradient, does not support non-linear projection of the color and opacity within individual segments +void StrafeHUD_DrawGradient( + vector color1, vector color2, vector size, float original_width, + float offset, float alpha, float gradient_offset, int gradient_type, float range) +{ + if(gradient_type == STRAFEHUD_GRADIENT_BOTH) + { + original_width /= 2; + + vector size1 = size; + size1.x = bound(0, original_width - gradient_offset, size.x); + + vector size2 = size; + size2.x = size.x - size1.x; + + if(size1.x > 0) + StrafeHUD_DrawGradient(color1, color2, size1, original_width, offset, alpha, gradient_offset, STRAFEHUD_GRADIENT_LEFT, range); + + if(size2.x > 0) + StrafeHUD_DrawGradient(color1, color2, size2, original_width, offset + size1.x, alpha, max(0, gradient_offset - original_width), STRAFEHUD_GRADIENT_RIGHT, range); + + return; + } + + vector gradient_start = eX * offset; + float gradient_width = StrafeHUD_ProjectWidth(gradient_start.x, size.x, range); + gradient_start.x = StrafeHUD_ProjectOffset(gradient_start.x, range, false); + vector gradient_end = gradient_start + eX * gradient_width; + vector gradient_height = eY * size.y; + + float alpha1 = bound(0, alpha, 1); + float alpha2 = bound(0, autocvar_hud_panel_strafehud_bar_neutral_alpha * panel_fg_alpha, 1); + if(alpha1 + alpha2 == 0) return; + + float ratio1 = gradient_offset / original_width; + float ratio2 = (gradient_offset + size.x) / original_width; + if(gradient_type == STRAFEHUD_GRADIENT_LEFT) + { + ratio1 = 1 - ratio1; + ratio2 = 1 - ratio2; + } + + vector origin = HUD_Shift(panel_pos); + gradient_start.x = HUD_ScaleX(gradient_start.x); + gradient_end.x = HUD_ScaleX(gradient_end.x); + gradient_height.y = HUD_ScaleY(gradient_height.y); + + R_BeginPolygon("", DRAWFLAG_NORMAL, true); + R_PolygonVertex(origin + gradient_start, '0 0 0', color1, alpha1 * (1 - ratio1)); + R_PolygonVertex(origin + gradient_start + gradient_height, '0 0 0', color1, alpha1 * (1 - ratio1)); + R_PolygonVertex(origin + gradient_end + gradient_height, '0 0 0', color1, alpha1 * (1 - ratio2)); + R_PolygonVertex(origin + gradient_end, '0 0 0', color1, alpha1 * (1 - ratio2)); + R_EndPolygon(); + + R_BeginPolygon("", DRAWFLAG_NORMAL, true); + R_PolygonVertex(origin + gradient_start, '0 0 0', color2, alpha2 * ratio1); + R_PolygonVertex(origin + gradient_start + gradient_height, '0 0 0', color2, alpha2 * ratio1); + R_PolygonVertex(origin + gradient_end + gradient_height, '0 0 0', color2, alpha2 * ratio2); + R_PolygonVertex(origin + gradient_end, '0 0 0', color2, alpha2 * ratio2); + R_EndPolygon(); +} + +// more expensive gradient rendering which does not rely on vertex gradients (required to properly render the color/opacity of individual segments in non-linear projection modes) +void StrafeHUD_DrawSoftGradient( + vector color1, vector color2, vector size, float original_width, float offset, float original_offset, + float alpha, float gradient_offset, int gradient_type, float range) +{ + float alpha1 = bound(0, alpha, 1); + float alpha2 = bound(0, autocvar_hud_panel_strafehud_bar_neutral_alpha * panel_fg_alpha, 1); + if(alpha1 + alpha2 == 0) return; + + float color_ratio = alpha1 / (alpha1 + alpha2); + vector segment_size = size; + for(int i = 0; i < size.x; ++i) + { + segment_size.x = min(size.x - i, 1); // each gradient segment is 1 unit wide except if there is less than 1 unit of gradient remaining + float segment_offset = offset + i; + float ratio_offset = segment_offset + segment_size.x / 2; + ratio_offset = StrafeHUD_ProjectOffset(ratio_offset, range, true); + ratio_offset += gradient_offset; + float ratio = (ratio_offset - original_offset) / original_width * (gradient_type == STRAFEHUD_GRADIENT_BOTH ? 2 : 1); + if(ratio > 1) ratio = 2 - ratio; + if(gradient_type != STRAFEHUD_GRADIENT_RIGHT) ratio = 1 - ratio; + float alpha_ratio = alpha1 - (alpha1 - alpha2) * ratio; + float combine_ratio1 = ratio * (1 - color_ratio); + float combine_ratio2 = (1 - ratio) * color_ratio; + ratio = (combine_ratio1 + combine_ratio2) == 0 ? 1 : combine_ratio1 / (combine_ratio1 + combine_ratio2); + + if(alpha_ratio > 0) + drawfill( + panel_pos + eX * segment_offset, + segment_size, + StrafeHUD_MixColors(color1, color2, ratio), + alpha_ratio, + DRAWFLAG_NORMAL); + } +} + +// draw the strafe arrows (inspired by drawspritearrow() in common/mutators/mutator/waypoints/waypointsprites.qc) +void StrafeHUD_DrawStrafeArrow(vector origin, float size, vector color, float alpha, bool flipped, float connection_width) +{ + // alpha and size already checked + + origin = HUD_Shift(origin); + float width = HUD_ScaleX(size * 2 + connection_width); + float height = HUD_ScaleY(size); + if(flipped) origin.y -= size; + R_BeginPolygon("", DRAWFLAG_NORMAL, true); + if(connection_width > 0) + { + R_PolygonVertex(origin + (connection_width / 2 * eX) + (flipped ? height * eY : '0 0 0'), '0 0 0', color, alpha); + R_PolygonVertex(origin - (connection_width / 2 * eX) + (flipped ? height * eY : '0 0 0'), '0 0 0', color, alpha); + } + else + { + R_PolygonVertex(origin + (flipped ? height * eY : '0 0 0'), '0 0 0', color, alpha); + } + R_PolygonVertex(origin + (flipped ? '0 0 0' : height * eY) - (width / 2) * eX, '0 0 0', color, alpha); + R_PolygonVertex(origin + (flipped ? '0 0 0' : height * eY) + (width / 2) * eX, '0 0 0', color, alpha); + R_EndPolygon(); +} + +// draw a fading text indicator above or below the strafe meter +void StrafeHUD_DrawTextIndicator( + string text, float height, vector color, float fadetime, float lasttime, + vector pos, float offset_top, float offset_bottom) +{ + float time_frac = (time - lasttime) / fadetime; + if(height <= 0 || lasttime <= 0 || fadetime <= 0 || time_frac > 1) + return; + + float alpha = cos(time_frac * M_PI_2); // fade non-linear like the physics panel does + vector size = panel_size; + size.y = height; + + if(pos.y >= 1) + { + --pos.y; // for calculations the position should not start at +1 + pos = StrafeHUD_CalculateTextIndicatorPosition(pos); + pos.y += size.y + offset_top; + pos.y *= -1; // it's more intuitive for up to be positive + } + else if(pos.y <= -1) + { + ++pos.y; // for calculations the position should not start at -1 + pos = StrafeHUD_CalculateTextIndicatorPosition(pos); + pos.y *= -1; // it's more intuitive for down to be negative + pos.y += panel_size.y + offset_bottom; + } + else return; + + drawstring_aspect(panel_pos + pos, text, size, color, alpha * panel_fg_alpha, DRAWFLAG_NORMAL); +} + +// checks whether the current style is a gradient style +bool StrafeHUD_IsGradient(int style) +{ + return style == STRAFEHUD_STYLE_GRADIENT || style == STRAFEHUD_STYLE_SOFT_GRADIENT; +} diff --git a/qcsrc/client/hud/panel/strafehud/draw.qh b/qcsrc/client/hud/panel/strafehud/draw.qh new file mode 100644 index 000000000..1bab2ea75 --- /dev/null +++ b/qcsrc/client/hud/panel/strafehud/draw.qh @@ -0,0 +1,9 @@ +#pragma once +#include "../strafehud.qh" + +void StrafeHUD_DrawStrafeHUD(float, float, vector, float, int, int, float); +void StrafeHUD_DrawGradient(vector, vector, vector, float, float, float, float, int, float); +void StrafeHUD_DrawSoftGradient(vector, vector, vector, float, float, float, float, float, int, float); +void StrafeHUD_DrawStrafeArrow(vector, float, vector, float, bool, float); +void StrafeHUD_DrawTextIndicator(string, float, vector, float, float, vector, float, float); +bool StrafeHUD_IsGradient(int); diff --git a/qcsrc/client/hud/panel/strafehud/draw_core.qc b/qcsrc/client/hud/panel/strafehud/draw_core.qc new file mode 100644 index 000000000..707f21111 --- /dev/null +++ b/qcsrc/client/hud/panel/strafehud/draw_core.qc @@ -0,0 +1,248 @@ +#include "draw_core.qh" + +#include + +void StrafeHUD_DrawStrafeMeter( + float shiftangle, float wishangle, float absolute_bestangle, + float absolute_prebestangle, float absolute_overturnangle, + bool moving, float hudangle) +{ + // the neutral zone fills the whole strafe bar + if(!moving) + { + // draw neutral zone + if(panel_size.x > 0 && panel_size.y > 0 && autocvar_hud_panel_strafehud_bar_neutral_alpha > 0) + { + switch(autocvar_hud_panel_strafehud_style) + { + default: + case STRAFEHUD_STYLE_DRAWFILL: + drawfill( + panel_pos, panel_size, + autocvar_hud_panel_strafehud_bar_neutral_color, + autocvar_hud_panel_strafehud_bar_neutral_alpha * panel_fg_alpha, + DRAWFLAG_NORMAL); + break; + + case STRAFEHUD_STYLE_PROGRESSBAR: + HUD_Panel_DrawProgressBar( + panel_pos, panel_size, "progressbar", 1, 0, 0, + autocvar_hud_panel_strafehud_bar_neutral_color, + autocvar_hud_panel_strafehud_bar_neutral_alpha * panel_fg_alpha, + DRAWFLAG_NORMAL); + } + } + } + else + { + // calculate various zones of the strafe-o-meter + float accelzone_left_startangle; + float accelzone_right_startangle; + float preaccelzone_left_startangle; + float preaccelzone_right_startangle; + float neutral_startangle; + float overturn_startangle; + + float accelzone_offsetangle = absolute_overturnangle - absolute_bestangle; + float preaccelzone_offsetangle = fabs(absolute_bestangle - absolute_prebestangle); + float neutral_offsetangle = 360; + float overturn_offsetangle = 360 - absolute_overturnangle * 2; + + if(!autocvar_hud_panel_strafehud_bar_preaccel) + preaccelzone_offsetangle = 0; + + // assign starting angles and shift the current offset for every element + float current_startangle = 0; + + preaccelzone_right_startangle = current_startangle; + current_startangle += preaccelzone_offsetangle; + + accelzone_right_startangle = current_startangle; + current_startangle += accelzone_offsetangle; + + overturn_startangle = current_startangle; + current_startangle += overturn_offsetangle; + + accelzone_left_startangle = current_startangle; + current_startangle += accelzone_offsetangle; + + preaccelzone_left_startangle = current_startangle; + current_startangle += preaccelzone_offsetangle; + + neutral_startangle = current_startangle; + neutral_offsetangle = 360 - current_startangle; + + // calculate how far off-center the strafe zones currently are + shiftangle += neutral_offsetangle / 2 - wishangle; + + // shift strafe zones into correct place + neutral_startangle += shiftangle; + accelzone_left_startangle += shiftangle; + accelzone_right_startangle += shiftangle; + preaccelzone_left_startangle += shiftangle; + preaccelzone_right_startangle += shiftangle; + overturn_startangle += shiftangle; + + // draw left acceleration zone + if(accelzone_offsetangle > 0) + StrafeHUD_DrawStrafeHUD( + accelzone_left_startangle, accelzone_offsetangle, + autocvar_hud_panel_strafehud_bar_accel_color, + autocvar_hud_panel_strafehud_bar_accel_alpha * panel_fg_alpha, + autocvar_hud_panel_strafehud_style, STRAFEHUD_GRADIENT_LEFT, + hudangle); + + if(autocvar_hud_panel_strafehud_bar_preaccel && preaccelzone_offsetangle > 0) + StrafeHUD_DrawStrafeHUD( + preaccelzone_left_startangle, preaccelzone_offsetangle, + autocvar_hud_panel_strafehud_bar_preaccel_color, + autocvar_hud_panel_strafehud_bar_preaccel_alpha * panel_fg_alpha, + autocvar_hud_panel_strafehud_style, STRAFEHUD_GRADIENT_RIGHT, + hudangle); + + // draw right acceleration zone + if(accelzone_offsetangle > 0) + StrafeHUD_DrawStrafeHUD( + accelzone_right_startangle, accelzone_offsetangle, + autocvar_hud_panel_strafehud_bar_accel_color, + autocvar_hud_panel_strafehud_bar_accel_alpha * panel_fg_alpha, + autocvar_hud_panel_strafehud_style, STRAFEHUD_GRADIENT_RIGHT, + hudangle); + + if(autocvar_hud_panel_strafehud_bar_preaccel && preaccelzone_offsetangle > 0) + StrafeHUD_DrawStrafeHUD( + preaccelzone_right_startangle, preaccelzone_offsetangle, + autocvar_hud_panel_strafehud_bar_preaccel_color, + autocvar_hud_panel_strafehud_bar_preaccel_alpha * panel_fg_alpha, + autocvar_hud_panel_strafehud_style, STRAFEHUD_GRADIENT_LEFT, + hudangle); + + // draw overturn zone + if(overturn_offsetangle > 0) + StrafeHUD_DrawStrafeHUD( + overturn_startangle, overturn_offsetangle, + autocvar_hud_panel_strafehud_bar_overturn_color, + autocvar_hud_panel_strafehud_bar_overturn_alpha * panel_fg_alpha, + autocvar_hud_panel_strafehud_style, STRAFEHUD_GRADIENT_BOTH, + hudangle); + + // draw neutral zone + if(neutral_offsetangle > 0) + StrafeHUD_DrawStrafeHUD( + neutral_startangle, neutral_offsetangle, + autocvar_hud_panel_strafehud_bar_neutral_color, + autocvar_hud_panel_strafehud_bar_neutral_alpha * panel_fg_alpha, + autocvar_hud_panel_strafehud_style, STRAFEHUD_GRADIENT_NONE, + hudangle); + } +} + +// draw the actual strafe angle indicator +void StrafeHUD_DrawAngleIndicator( + float angle, vector line_size, float arrow_size, int num_dashes, + bool has_top_arrow, bool has_bottom_arrow, vector color, float alpha, float hudangle) +{ + if(alpha <= 0) return; + + // bound to HUD area + angle = bound(-hudangle / 2, angle, hudangle / 2); + + float offset = StrafeHUD_AngleToOffset(angle, hudangle); + offset = StrafeHUD_ProjectOffset(offset, hudangle, false); + + StrafeHUD_DrawAngleIndicatorLine(line_size, offset, num_dashes, color, alpha); + + if(has_top_arrow) + StrafeHUD_DrawAngleIndicatorArrow(arrow_size, offset, line_size, color, alpha, true); + + if(has_bottom_arrow) + StrafeHUD_DrawAngleIndicatorArrow(arrow_size, offset, line_size, color, alpha, false); +} + +// draw the line of the angle indicator +void StrafeHUD_DrawAngleIndicatorLine(vector size, float offset, int num_dashes, vector color, float alpha) +{ + if(num_dashes <= 0 || size.x <= 0 || size.y <= 0) return; + + vector segment_size = size; + segment_size.y = size.y / (bound(1, num_dashes, size.y) * 2 - 1); + + for(float i = 0; i < size.y; i += segment_size.y * 2) + { + // check if last iteration + if(i + segment_size.y * 2 >= size.y) + segment_size.y = size.y - i; + + drawfill( + panel_pos - eY * ((size.y - panel_size.y) / 2 - i) + eX * (offset - segment_size.x / 2), + segment_size, color, alpha * panel_fg_alpha, DRAWFLAG_NORMAL); + } +} + +// draw the arrows on the angle indicator +void StrafeHUD_DrawAngleIndicatorArrow(float size, float offset, vector line_size, vector color, float alpha, bool top) +{ + if(size <= 0) return; + + if(top) + { + StrafeHUD_DrawStrafeArrow( + panel_pos + eY * ((panel_size.y - line_size.y) / 2) + eX * offset, + size, color, alpha * panel_fg_alpha, true, line_size.x); + } + else + { + StrafeHUD_DrawStrafeArrow( + panel_pos + eY * ((panel_size.y - line_size.y) / 2 + line_size.y) + eX * offset, + size, color, alpha * panel_fg_alpha, false, line_size.x); + } +} + +// direction indicator +void StrafeHUD_DrawDirectionIndicator(int direction, bool opposite_direction, bool fwd) +{ + vector direction_size_vertical; + direction_size_vertical.x = max(panel_size.y * min(autocvar_hud_panel_strafehud_direction_width, 1), 1); + direction_size_vertical.y = panel_size.y + direction_size_vertical.x * 2; + direction_size_vertical.z = 0; + + vector direction_size_horizontal; + direction_size_horizontal.x = panel_size.x * min(autocvar_hud_panel_strafehud_direction_length, .5); + direction_size_horizontal.y = direction_size_vertical.x; + direction_size_horizontal.z = 0; + + if(direction != STRAFEHUD_DIRECTION_NONE && + direction_size_vertical.x > 0 && + autocvar_hud_panel_strafehud_direction_alpha > 0) + { + bool indicator_direction = direction == STRAFEHUD_DIRECTION_LEFT; + + // invert left/right when strafing backwards or when strafing towards the opposite side indicated by the direction variable + // if both conditions are true then it's inverted twice hence not inverted at all + if(!fwd != opposite_direction) + indicator_direction = !indicator_direction; + + // draw the direction indicator caps at the sides of the hud + // vertical line + if(direction_size_vertical.y > 0) + drawfill( + panel_pos - eY * direction_size_horizontal.y + eX * (indicator_direction ? -direction_size_vertical.x : panel_size.x), + direction_size_vertical, autocvar_hud_panel_strafehud_direction_color, + autocvar_hud_panel_strafehud_direction_alpha * panel_fg_alpha, + DRAWFLAG_NORMAL); + + // top horizontal line + drawfill( + panel_pos + eX * (indicator_direction ? 0 : panel_size.x - direction_size_horizontal.x) - eY * direction_size_horizontal.y, + direction_size_horizontal, autocvar_hud_panel_strafehud_direction_color, + autocvar_hud_panel_strafehud_direction_alpha * panel_fg_alpha, + DRAWFLAG_NORMAL); + + // bottom horizontal line + drawfill( + panel_pos + eX * (indicator_direction ? 0 : panel_size.x - direction_size_horizontal.x) + eY * panel_size.y, + direction_size_horizontal, autocvar_hud_panel_strafehud_direction_color, + autocvar_hud_panel_strafehud_direction_alpha * panel_fg_alpha, + DRAWFLAG_NORMAL); + } +} diff --git a/qcsrc/client/hud/panel/strafehud/draw_core.qh b/qcsrc/client/hud/panel/strafehud/draw_core.qh new file mode 100644 index 000000000..8ed5d53a9 --- /dev/null +++ b/qcsrc/client/hud/panel/strafehud/draw_core.qh @@ -0,0 +1,8 @@ +#pragma once +#include "../strafehud.qh" + +void StrafeHUD_DrawStrafeMeter(float, float, float, float, float, bool, float); +void StrafeHUD_DrawAngleIndicator(float, vector, float, int, bool, bool, vector, float, float); +void StrafeHUD_DrawAngleIndicatorLine(vector, float, int, vector, float); +void StrafeHUD_DrawAngleIndicatorArrow(float, float, vector, vector, float, bool); +void StrafeHUD_DrawDirectionIndicator(int, bool, bool); diff --git a/qcsrc/client/hud/panel/strafehud/extra.qc b/qcsrc/client/hud/panel/strafehud/extra.qc new file mode 100644 index 000000000..e58039554 --- /dev/null +++ b/qcsrc/client/hud/panel/strafehud/extra.qc @@ -0,0 +1,270 @@ +#include "extra.qh" + +#include +#include +#include +#include + +// start speed +#include // checkpoint information (race_*) + +// jump height +#include // for IS_PLAYER() macro +#include // IS_DEAD() macro + +// epsilon value for the slick detector steps to avoid +// an infinite loop due to floating point rounding errors +// (works with current limits) +#define SLICKDETECT_STEPS_EPSILON 0.00001 + +// slick detector +// scans for slick in every direction downwards from the player's feet +// may cause performance issues on slower machines +float StrafeHUD_DrawSlickDetector(entity e, bool already_detected) +{ + float slickdetector_height = bound(0, autocvar_hud_panel_strafehud_slickdetector_height, 1); + slickdetector_height *= panel_size.y; + if(autocvar_hud_panel_strafehud_slickdetector && + autocvar_hud_panel_strafehud_slickdetector_range > 0 && + autocvar_hud_panel_strafehud_slickdetector_alpha > 0 && + slickdetector_height > 0 && + panel_size.x > 0) + { + float slicksteps = bound(0, autocvar_hud_panel_strafehud_slickdetector_granularity, 4); + bool allslick = PHYS_FRICTION(e) == 0; + bool slickdetected = false; + + slicksteps = 90 * DEG2RAD / 2 ** slicksteps; + + // don't need to traceline if already touching slick + slickdetected = already_detected; + + // coordinates at the bottom center of the player bbox + vector traceorigin = e.origin + eZ * e.mins.z; + + // traceline downwards into every direction + trace_dphitq3surfaceflags = 0; + for(float i = 0; i < 90 * DEG2RAD - SLICKDETECT_STEPS_EPSILON && !slickdetected; i += slicksteps) + { + vector slickoffset; + float slickrotate; + + // creates a vector angled 'i' degrees relative to the Z vector + // negative cosine value to face downwards + slickoffset.z = -cos(i) * autocvar_hud_panel_strafehud_slickdetector_range; + slickrotate = sin(i) * autocvar_hud_panel_strafehud_slickdetector_range; + + for(float j = 0; j < 360 * DEG2RAD - SLICKDETECT_STEPS_EPSILON && !slickdetected; j += slicksteps) + { + // adjusts the vector so that it rotates 'j' degrees around the Z vector + slickoffset.x = sin(j) * slickrotate; + slickoffset.y = cos(j) * slickrotate; + + // trace a line, we hit slick if: + // - it hits something and surface friction is disabled + // - the slick surface flag got set + // note: it is not guaranteed that the detected surface is actually + // a zero friction surface if PHYS_FRICTION_SLICK() does not equal zero + traceline(traceorigin, traceorigin + slickoffset, MOVE_NOMONSTERS, e); + if((allslick && trace_fraction < 1) + || (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK)) + slickdetected = true; + + // rotation does nothing when we are perpendicular to the ground, hence only one iteration + if(i == 0) break; + } + } + + // if a traceline hit a slick surface + if(slickdetected) + { + vector slickdetector_size = panel_size; + slickdetector_size.y = slickdetector_height; + + // horizontal lines + for(int i = 0; i <= 1; ++i) + { + float y_offset = (i == 0) + ? -slickdetector_size.y // top + : panel_size.y; // bottom + drawfill( + panel_pos + eY * y_offset, + slickdetector_size, autocvar_hud_panel_strafehud_slickdetector_color, + autocvar_hud_panel_strafehud_slickdetector_alpha * panel_fg_alpha, + DRAWFLAG_NORMAL); + } + } + + return slickdetector_height; + } + + return 0; +} + +// vertical angle for weapon jumps +void StrafeHUD_DrawVerticalAngle(entity e, float text_offset_top, float text_offset_bottom) +{ + if(!autocvar_hud_panel_strafehud_vangle) return; + + float vangle = -PHYS_INPUT_ANGLES(e).x; + float vangle_height = autocvar_hud_panel_strafehud_vangle_size * panel_size.y; + string vangle_text = strcat(ftos_decimals(vangle, 2), "°"); + + StrafeHUD_DrawTextIndicator( + vangle_text, vangle_height, + autocvar_hud_panel_strafehud_vangle_color, 1, + time, autocvar_hud_panel_strafehud_vangle_pos, + text_offset_top, text_offset_bottom); +} + +// show height achieved by a single jump +// FIXME: checking z position differences is unreliable (warpzones, teleporter, kill, etc), use velocity to calculate jump height instead +// FIXME: move capturing the jump height value out of the HUD +void StrafeHUD_DrawJumpHeight(entity e, bool onground, bool swimming, float text_offset_top, float text_offset_bottom) +{ + float length_conversion_factor = StrafeHUD_GetLengthUnitFactor(autocvar_hud_speed_unit); + static float height_min = 0, height_max = 0; // ground and peak of jump z coordinates + static float jumpheight = 0, jumptime = 0; // displayed value and timestamp for fade out + + // tries to catch kill and spectate but those are not reliable + if((e.velocity.z <= 0) || onground || swimming || IS_DEAD(e) || !IS_PLAYER(e)) + { + height_min = height_max = e.origin.z; + } + else if(e.origin.z > height_max) + { + height_max = e.origin.z; + float jumpheight_new = height_max - height_min; + + if((jumpheight_new * length_conversion_factor) > max(autocvar_hud_panel_strafehud_jumpheight_min, 0)) + { + jumpheight = jumpheight_new; + jumptime = time; + } + } + + if(!autocvar_hud_panel_strafehud_jumpheight) return; + + // use more decimals when displaying km or miles + int length_decimals = autocvar_hud_speed_unit >= 3 && autocvar_hud_speed_unit <= 5 ? 6 : 2; + + float jumpheight_height = autocvar_hud_panel_strafehud_jumpheight_size * panel_size.y; + string jumpheight_text = ftos_decimals(jumpheight * length_conversion_factor, length_decimals); + if(autocvar_hud_panel_strafehud_unit_show) + jumpheight_text = strcat(jumpheight_text, StrafeHUD_GetLengthUnit(autocvar_hud_speed_unit)); + + StrafeHUD_DrawTextIndicator( + jumpheight_text, jumpheight_height, + autocvar_hud_panel_strafehud_jumpheight_color, + autocvar_hud_panel_strafehud_jumpheight_fade, + jumptime, autocvar_hud_panel_strafehud_jumpheight_pos, + text_offset_top, text_offset_bottom); +} + +// strafe efficiency, percentage of how far away the current angle is from the optimal angle +// the percentage changes linearly with angular distance +void StrafeHUD_DrawStrafeEfficiency(float strafe_ratio, float text_offset_top, float text_offset_bottom) +{ + if(!autocvar_hud_panel_strafehud_strafeefficiency) return; + + float strafeeff_height = autocvar_hud_panel_strafehud_strafeefficiency_size * panel_size.y; + string strafeeff_text = strcat(ftos_decimals(strafe_ratio * 100, 2), "%"); + vector strafeeff_color = StrafeHUD_MixColors('1 1 1', (strafe_ratio > 0 ? '0 1 0' : '1 0 0'), fabs(strafe_ratio)); + + StrafeHUD_DrawTextIndicator( + strafeeff_text, strafeeff_height, + strafeeff_color, 1, + time, autocvar_hud_panel_strafehud_strafeefficiency_pos, + text_offset_top, text_offset_bottom); +} + +// show speed when crossing the start trigger +// FIXME: move capturing the race start speed value out of the HUD +void StrafeHUD_DrawStartSpeed(float speed, float text_offset_top, float text_offset_bottom) +{ + static float startspeed = 0, starttime = 0; // displayed value and timestamp for fade out + + // check if the start trigger was hit (will also trigger if the finish trigger was hit if those have the same ID) + if((race_nextcheckpoint == 1) || (race_checkpoint == 254 && race_nextcheckpoint == 255)) + { + if((race_checkpointtime > 0) && (starttime != race_checkpointtime)) + { + starttime = race_checkpointtime; + startspeed = speed; + } + } + + if(!autocvar_hud_panel_strafehud_startspeed) return; + + float speed_conversion_factor = GetSpeedUnitFactor(autocvar_hud_speed_unit); + float startspeed_height = autocvar_hud_panel_strafehud_startspeed_size * panel_size.y; + string startspeed_text = ftos_decimals(startspeed * speed_conversion_factor, 2); + if(autocvar_hud_panel_strafehud_unit_show) + startspeed_text = strcat(startspeed_text, GetSpeedUnit(autocvar_hud_speed_unit)); + + StrafeHUD_DrawTextIndicator( + startspeed_text, startspeed_height, + autocvar_hud_panel_strafehud_startspeed_color, + autocvar_hud_panel_strafehud_startspeed_fade, + starttime, autocvar_hud_panel_strafehud_startspeed_pos, + text_offset_top, text_offset_bottom); +} + +// strafe sonar for audible feedback when strafing +void StrafeHUD_Sonar(float strafe_ratio, string sonarsound) +{ + static float sonar_time = 0; + + float sonar_start = bound(0, autocvar_hud_panel_strafehud_sonar_start, 1); + float sonar_ratio = strafe_ratio - sonar_start; + if(sonar_start != 1) + sonar_ratio /= 1 - sonar_start; + else + sonar_ratio = 1; + + float sonar_interval = max(0, autocvar_hud_panel_strafehud_sonar_interval_start); + sonar_interval += autocvar_hud_panel_strafehud_sonar_interval_range * sonar_ratio ** max(1, autocvar_hud_panel_strafehud_sonar_interval_exponent); + bool sonar_ready = (sonar_time == 0) || ((time - sonar_time) >= sonar_interval); + if(autocvar_hud_panel_strafehud_sonar && sonar_ready && (strafe_ratio >= sonar_start)) + { + sonar_time = time; + + float sonar_volume = bound(0, autocvar_hud_panel_strafehud_sonar_volume_start, 1); + sonar_volume += autocvar_hud_panel_strafehud_sonar_volume_range * sonar_ratio ** max(1, autocvar_hud_panel_strafehud_sonar_volume_exponent); + + float sonar_pitch = max(0, autocvar_hud_panel_strafehud_sonar_pitch_start); + sonar_pitch += autocvar_hud_panel_strafehud_sonar_pitch_range * sonar_ratio ** max(1, autocvar_hud_panel_strafehud_sonar_pitch_exponent); + + if(sonarsound && (sonar_volume > 0)) + sound7(csqcplayer, CH_INFO, sonarsound, bound(0, sonar_volume, 1) * VOL_BASE, ATTN_NONE, max(0.000001, sonar_pitch * 100), 0); + } +} + +// update and precache the sonar sound and store the proper sound path +string StrafeHUD_UpdateSonarSound() +{ + string newsound = autocvar_hud_panel_strafehud_sonar_audio; + static string cursound = string_null; + static string sonarsound = string_null; + if(newsound == "") + { + strfree(cursound); + strfree(sonarsound); + cursound = sonarsound = string_null; + } + else if(newsound != cursound) + { + strfree(cursound); + cursound = strzone(newsound); + + strfree(sonarsound); + sonarsound = _Sound_fixpath(newsound); + if(sonarsound) + { + sonarsound = strzone(sonarsound); + precache_sound(sonarsound); + } + } + + return sonarsound; +} diff --git a/qcsrc/client/hud/panel/strafehud/extra.qh b/qcsrc/client/hud/panel/strafehud/extra.qh new file mode 100644 index 000000000..836d43d62 --- /dev/null +++ b/qcsrc/client/hud/panel/strafehud/extra.qh @@ -0,0 +1,12 @@ +#pragma once +#include "../strafehud.qh" + +float StrafeHUD_DrawSlickDetector(entity, bool); + +void StrafeHUD_DrawVerticalAngle(entity, float, float); +void StrafeHUD_DrawJumpHeight(entity, bool, bool, float, float); +void StrafeHUD_DrawStrafeEfficiency(float, float, float); +void StrafeHUD_DrawStartSpeed(float, float, float); + +void StrafeHUD_Sonar(float, string); +string StrafeHUD_UpdateSonarSound(); diff --git a/qcsrc/client/hud/panel/strafehud/util.qc b/qcsrc/client/hud/panel/strafehud/util.qc new file mode 100644 index 000000000..c3786e1a5 --- /dev/null +++ b/qcsrc/client/hud/panel/strafehud/util.qc @@ -0,0 +1,336 @@ +#include "util.qh" + +#include +#include +#include + +// convert a strafe angle into a HUD width value +float StrafeHUD_AngleToWidth(float angle, float range) +{ + return angle / range * panel_size.x; +} + +// convert a strafe angle into a centered HUD offset value +float StrafeHUD_AngleToOffset(float angle, float range) +{ + return StrafeHUD_AngleToWidth(angle, range) + panel_size.x / 2; +} + +// turn a ratio into a projected ratio based on the total angular distance +float StrafeHUD_Project(float ratio, float range, bool reverse) +{ + range *= DEG2RAD / 2; + switch(autocvar_hud_panel_strafehud_projection) + { + default: + case STRAFEHUD_PROJECTION_LINEAR: + return ratio; + case STRAFEHUD_PROJECTION_PERSPECTIVE: + if(!reverse) + { + ratio *= range; + ratio = tan(ratio) / tan(range); + } + else + { + ratio = atan(ratio * tan(range)); + ratio /= range; + } + break; + case STRAFEHUD_PROJECTION_PANORAMIC: + if(!reverse) + { + ratio *= range; + ratio = tan(ratio / 2) / tan(range / 2); + } + else + { + ratio = atan(ratio * tan(range / 2)) * 2; + ratio /= range; + } + break; + } + return ratio; +} + +// project a centered HUD offset value +float StrafeHUD_ProjectOffset(float offset, float range, bool reverse) +{ + if(autocvar_hud_panel_strafehud_projection == STRAFEHUD_PROJECTION_LINEAR) + return offset; + + float ratio = (offset - (panel_size.x / 2)) / (panel_size.x / 2); + ratio = StrafeHUD_Project(ratio, range, reverse); + offset = ratio * (panel_size.x / 2) + (panel_size.x / 2); + return offset; +} + +// project a HUD width value +float StrafeHUD_ProjectWidth(float offset, float width, float range) +{ + if(autocvar_hud_panel_strafehud_projection == STRAFEHUD_PROJECTION_LINEAR) + return width; + + return StrafeHUD_ProjectOffset(offset + width, range, false) - StrafeHUD_ProjectOffset(offset, range, false); +} + +// length unit conversion (km and miles are only included to match the GetSpeedUnit* functions) +float StrafeHUD_GetLengthUnitFactor(int length_unit) +{ + switch(length_unit) + { + default: + case 1: return 1.0; + case 2: return 0.0254; + case 3: return 0.0254 * 0.001; + case 4: return 0.0254 * 0.001 * 0.6213711922; + case 5: return 0.0254 * 0.001 * 0.5399568035; + } +} + +string StrafeHUD_GetLengthUnit(int length_unit) +{ + switch(length_unit) + { + // translator-friendly strings without the initial space + default: + case 1: return strcat(" ", _("qu")); + case 2: return strcat(" ", _("m")); + case 3: return strcat(" ", _("km")); + case 4: return strcat(" ", _("mi")); + case 5: return strcat(" ", _("nmi")); + } +} + +// check the player waterlevel without affecting the player entity, this way we can fetch waterlevel even if client prediction is disabled +float StrafeHUD_DetermineWaterLevel(entity e) +{ + // store old values + void old_contentstransition(int, int) = e.contentstransition; + float old_watertype = e.watertype; + float old_waterlevel = e.waterlevel; + + e.contentstransition = func_null; // unset the contentstransition function if present + _Movetype_CheckWater(e); + float new_waterlevel = e.waterlevel; // store the player waterlevel + + // restore old values + e.contentstransition = old_contentstransition; + e.watertype = old_watertype; + e.waterlevel = old_waterlevel; + + return new_waterlevel; +} + +// determine frametime, to avoid jitter, average the frametime in case client prediction is used +float StrafeHUD_DetermineFrameTime() +{ + static float dt_update = 0; + static int dt_time = 0; + static float dt_sum = 0; + static float dt = 0; + if((csqcplayer_status == CSQCPLAYERSTATUS_PREDICTED) && (input_timelength > 0)) + { + float dt_client = input_timelength; + + if(dt_client > .05) // server splits frames longer than 50 ms into two moves (DarkPlaces behaviour) + dt_client /= 2; // does not ensure frames are smaller than 50 ms, just splits large frames in half, matches server behaviour + + /* calculate average frametime + * calculated using a weighted arithmetic mean, where the weighting is equal to the frametime itself + * for example, given a 1 ms frame and a 9 ms frame we have: + * a total time of 10 ms + * a weighted sum of 1 ms * 1 ms + 9 ms * 9 ms = 82 ms^2 + * the final result is 82 ms^2 / 10 ms = 8.2 ms + */ + dt_sum += dt_client * dt_client; // weighted sum of all frametimes (mean numerator) + dt_time += dt_client; // time spent averaging (mean denominator) + + if(((time - dt_update) > autocvar_hud_panel_strafehud_fps_update) || (dt_update == 0)) + { + dt = dt_sum / dt_time; + dt_update = time; + dt_time = dt_sum = 0; + } + } + else // when spectating other players server ticrate will be used, this may not be accurate but there is no way to find other player's frametime + { + dt = ticrate; + dt_update = dt_time = dt_sum = 0; + } + + return dt; +} + +// determine player wishdir, non-local player movement is limited to 45 degree steps +float StrafeHUD_DetermineWishAngle(vector movement, int keys, bool is_local) +{ + float wishangle; + if(is_local) // if entity is local player + { + if(movement.y == 0) + wishangle = 0; + else + { + wishangle = RAD2DEG * atan2(movement.y, movement.x); + // wrap the wish angle if it exceeds ±90° + if(fabs(wishangle) > 90) + { + if(wishangle < 0) + wishangle += 180; + else + wishangle -= 180; + + wishangle *= -1; + } + } + } + else // alternatively calculate wishdir by querying pressed keys + { + if(keys & (KEY_FORWARD | KEY_BACKWARD)) + wishangle = 45; + else + wishangle = 90; + if(keys & KEY_LEFT) + wishangle *= -1; + else if(!(keys & KEY_RIGHT)) + wishangle = 0; // wraps at 180° + } + + return wishangle; +} + +// determine whether the player is pressing forwards or backwards keys +int StrafeHUD_DetermineForwardKeys(vector movement, int keys, bool is_local) +{ + if(is_local) // if entity is local player + { + if(movement.x > 0) + return STRAFEHUD_KEYS_FORWARD; + else if(movement.x < 0) + return STRAFEHUD_KEYS_BACKWARD; + else + return STRAFEHUD_KEYS_NONE; + } + else // alternatively determine direction by querying pressed keys + { + if((keys & KEY_FORWARD) && !(keys & KEY_BACKWARD)) + return STRAFEHUD_KEYS_FORWARD; + else if(!(keys & KEY_FORWARD) && (keys & KEY_BACKWARD)) + return STRAFEHUD_KEYS_BACKWARD; + else + return STRAFEHUD_KEYS_NONE; + } +} + +float StrafeHUD_DetermineHudAngle(float absolute_wishangle, float absolute_overturnangle, float strafity) +{ + + // determine the minimal required HUD angle to contain the full strafing angle range + // this is useful for the velocity centered mode where the zones do not follow the strafing angle + // how it works: + // the angle where the most acceleration occurs moves relative to the player velocity + // from 0 - wishangle to absolute_overturnangle - wishangle + // the angle farther away from the center is the maximum the optimal strafing angle can + // diverge from the direction of velocity + // this angle has to be multiplied by two since the HUD extends in both directions which + // halves the amount it extends in a single direction + float range_minangle = max(absolute_wishangle, absolute_overturnangle - absolute_wishangle) * 2; + + float range_normal = autocvar_hud_panel_strafehud_range; + float range_side = autocvar_hud_panel_strafehud_range_sidestrafe; + float range_used; + + float hfov = getproperty(VF_FOVX); + if(isnan(range_normal) || isnan(range_side) || isnan(hfov)) return 360; + + // negative values enable different behaviour + // no exact matching so that all negative values are caught + if(range_normal == 0) // range = 0, use minimum angle required if dynamically setting hud angle + range_normal = autocvar__hud_configure ? 90 : range_minangle; + else if(range_normal < 0) // range = -1, use the current field of view + range_normal = hfov; + + if(range_side < -1) // range = -2, use the normal range + range_used = range_normal; + else + { + if(range_side == 0) // range = 0, use minimum angle required if dynamically setting hud angle + range_side = autocvar__hud_configure ? 90 : range_minangle; + else if(range_side < 0) // range = -1, use the current field of view + range_side = hfov; + + range_used = GeomLerp(range_normal, strafity, range_side); + } + float hudangle = bound(0, fabs(range_used), 360); // limit HUD range to 360 degrees, higher values don't make sense + + // limit strafe-meter angle to values suitable for the current projection mode + switch(autocvar_hud_panel_strafehud_projection) + { + // those limits are a little less than the maximal FOV the game allows + // however, they suffice for all realistic use cases + case STRAFEHUD_PROJECTION_PERSPECTIVE: + hudangle = min(hudangle, 170); + break; + case STRAFEHUD_PROJECTION_PANORAMIC: + hudangle = min(hudangle, 350); + break; + } + + return hudangle; +} + +// determine whether the player is strafing left or right +float StrafeHUD_DetermineDirection(float angle, float wishangle, float antiflicker_angle) +{ + if(wishangle > 0) + return STRAFEHUD_DIRECTION_RIGHT; + else if(wishangle < 0) + return STRAFEHUD_DIRECTION_LEFT; + else + { + if(angle > antiflicker_angle && angle < (180 - antiflicker_angle)) + return STRAFEHUD_DIRECTION_RIGHT; + else if(angle < -antiflicker_angle && angle > (-180 + antiflicker_angle)) + return STRAFEHUD_DIRECTION_LEFT; + else + return STRAFEHUD_DIRECTION_NONE; + } +} + +// determine whether the player holds the jump key +// try to ignore if track_canjump is enabled +// does not work in spectator mode if the spectated player uses +jetpack or cl_movement_track_canjump +bool StrafeHUD_DetermineJumpHeld(entity e, int keys, bool is_local) +{ + if(is_local) + { + if((PHYS_INPUT_BUTTON_JUMP(e) || PHYS_INPUT_BUTTON_JETPACK(e)) && !PHYS_CL_TRACK_CANJUMP(e)) + return true; + } + else + { + if((keys & KEY_JUMP) && !PHYS_TRACK_CANJUMP(e)) + return true; + } + + return false; +} + +// mix two colors based on a ratio +// TODO: move mixing colors out of the HUD, this could be useful for other code +vector StrafeHUD_MixColors(vector color1, vector color2, float ratio) +{ + if(ratio <= 0) return color1; + if(ratio >= 1) return color2; + return color1 + (color2 - color1) * ratio; +} + +vector StrafeHUD_CalculateTextIndicatorPosition(vector pos) +{ + pos.x *= panel_size.x / 2; // more intuitive since we start in the middle, this turns the range from -0.5 to +0.5 into -1 to +1 + pos.y *= panel_size.y; + pos.z = 0; + + return pos; +} diff --git a/qcsrc/client/hud/panel/strafehud/util.qh b/qcsrc/client/hud/panel/strafehud/util.qh new file mode 100644 index 000000000..11d8064c0 --- /dev/null +++ b/qcsrc/client/hud/panel/strafehud/util.qh @@ -0,0 +1,23 @@ +#pragma once +#include "../strafehud.qh" + +float StrafeHUD_AngleToWidth(float, float); +float StrafeHUD_AngleToOffset(float, float); +float StrafeHUD_Project(float, float, bool); +float StrafeHUD_ProjectOffset(float, float, bool); +float StrafeHUD_ProjectWidth(float, float, float); + +float StrafeHUD_GetLengthUnitFactor(int); +string StrafeHUD_GetLengthUnit(int); + +float StrafeHUD_DetermineWaterLevel(entity); +float StrafeHUD_DetermineFrameTime(); +float StrafeHUD_DetermineWishAngle(vector, int, bool); +int StrafeHUD_DetermineForwardKeys(vector, int, bool); +float StrafeHUD_DetermineHudAngle(float, float, float); +float StrafeHUD_DetermineDirection(float, float, float); +bool StrafeHUD_DetermineJumpHeld(entity, int, bool); + +vector StrafeHUD_MixColors(vector, vector, float); + +vector StrafeHUD_CalculateTextIndicatorPosition(vector); -- 2.39.5