From: Juhu <5894800-Juhu_@users.noreply.gitlab.com> Date: Tue, 24 Sep 2024 18:32:57 +0000 (+0200) Subject: Merge branch 'master' into Juhu/strafehud-next X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=18450610b0bafbece810de376db8205e2b149ccb;p=xonotic%2Fxonotic-data.pk3dir.git Merge branch 'master' into Juhu/strafehud-next variable declaration moved down to relevant code since the initialization block no longer exists hud_panel_strafehud_wturn_alpha default changed from 1 to 0.5 to match hud_panel_strafehud_switch_alpha removed hud_panel_strafehud_wturn_width since the wturn indicator uses the arrow style now removed the offset_centered parameter from StrafeHUD_DrawStrafeHUD() since the old indicators which this was used for no longer exist changed naming of some variables to match new naming: odd -> opposite switch -> change real -> absolute --- 18450610b0bafbece810de376db8205e2b149ccb diff --cc _hud_common.cfg index d6f5648ab,6af283e01..040f8eb54 --- a/_hud_common.cfg +++ b/_hud_common.cfg @@@ -156,10 -167,16 +167,15 @@@ seta hud_panel_scoreboard_itemstats_sho 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 "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 "-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_style "2" "\"0\" = no styling, \"1\" = progress bar style for the strafe bar, \"2\" = gradient for the strafe bar, \"3\" = fast gradient for the strafe bar (requires linear projection mode)" seta hud_panel_strafehud_unit_show "1" "show units" -seta hud_panel_strafehud_uncapped "0" "set to \"1\" to 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" "set to \"1\" to account for friction in calculations" seta hud_panel_strafehud_bar_preaccel "1" "set to \"1\" to extend the acceleration zone by the strafe meter zone before full acceleration can be achieved" + seta hud_panel_strafehud_bar_preaccel_color "0 1 0" "color of the strafe meter pre-acceleration zone" + seta hud_panel_strafehud_bar_preaccel_alpha "0.5" "opacity of the strafe meter pre-acceleration zone" seta hud_panel_strafehud_bar_neutral_color "1 1 1" "color of the strafe meter neutral zone" seta hud_panel_strafehud_bar_neutral_alpha "0.1" "opacity of the strafe meter neutral zone" seta hud_panel_strafehud_bar_accel_color "0 1 0" "color of the strafe meter acceleration zone" @@@ -176,13 -194,20 +193,18 @@@ seta hud_panel_strafehud_angle_accel_co 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_bestangle "1" "set to \"1\" to enable a ghost angle indicator showing the best angle to gain maximum acceleration" + seta hud_panel_strafehud_bestangle "1" "set to \"1\" to 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" "set to \"1\" to enable the strafe angle indicator showing the angle to move to when changing side" -seta hud_panel_strafehud_switch "1" "set to \"1\" to 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 (set to -1 for dynamic minspeed)" -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_switch "1" "set to \"1\" to 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 (set to -1 for dynamic minspeed)" +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_wturn "1" "enable the 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_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" "set to \"1\" to enable the W-turn indicators even when W-turning gives acceleration (warning: not completely accurate)" seta hud_panel_strafehud_direction "0" "set to \"1\" to enable the 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" diff --cc qcsrc/client/hud/panel/strafehud.qc index 5bc8e4a90,f81ef075e..d9213897e --- a/qcsrc/client/hud/panel/strafehud.qc +++ b/qcsrc/client/hud/panel/strafehud.qc @@@ -64,496 -71,1478 +64,697 @@@ void HUD_StrafeHUD( strafeplayer = csqcplayer; } + if(!csqcplayer || !strafeplayer) { hud_lasttime = time; return; } + // draw strafehud - if(csqcplayer && strafeplayer) - { - float strafe_waterlevel; - // 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; - } + int keys = STAT(PRESSED_KEYS); + bool jumpheld = StrafeHUD_DetermineJumpHeld(strafeplayer, keys, islocal); - 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; - } + // does not get changed by ground timeout and is not affected by jump input + bool real_onground = islocal ? IS_ONGROUND(strafeplayer) : !(strafeplayer.anim_implicit_state & ANIMIMPLICITSTATE_INAIR); - // 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 + bool real_onslick = false; - // 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); + // 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; - 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; + // the hud will not work well while swimming + float strafe_waterlevel = StrafeHUD_DetermineWaterLevel(strafeplayer); + bool swimming = strafe_waterlevel >= WATERLEVEL_SWIMMING; - onground_lasttime = time; - onslick_last = onslick; - } - else if(jumpheld || swimming) + static float onground_lasttime = 0; + static bool onslick_last = false; + if(onground) + { + if(PHYS_FRICTION(strafeplayer) == 0) { - onground_lasttime = 0; + onslick = true; } - - if(onground_lasttime == 0) - onground_expired = true; - else - onground_expired = (time - onground_lasttime) >= autocvar_hud_panel_strafehud_timeout_ground; // timeout for slick ramps - - if(!onground && !onground_expired) // if ground timeout hasn't expired yet use ground physics + else // do not use IS_ONSLICK(), it only works for the local player and only if client prediction is enabled { - onground = true; - onslick = onslick_last; - - if(!autocvar__hud_configure) - { - maxspeed = PHYS_MAXSPEED(strafeplayer) * maxspeed_mod; - maxaccel = PHYS_ACCELERATE(strafeplayer); - } + 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; - movespeed = vlen(vec2(movement)); - if(movespeed == 0) - movespeed = maxspeed; - else - movespeed = min(movespeed, maxspeed); - - if(!autocvar_hud_panel_strafehud_uncapped) - arrow_size = max(arrow_size, 1); + onground_lasttime = time; + onslick_last = onslick; + } + else if(jumpheld || swimming) + { + onground_lasttime = 0; + } - // determine frametime - if((csqcplayer_status == CSQCPLAYERSTATUS_PREDICTED) && (input_timelength > 0)) - { - float dt_client = input_timelength; + 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 - 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 + // 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; - // calculate average frametime - dt_sum += dt_client * dt_client; - dt_time += dt_client; + if(!onground && !onground_expired) // if ground timeout has not expired yet use ground physics + { + onground = true; + onslick = onslick_last; - 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 + if(!autocvar__hud_configure) { - dt = ticrate; - dt_update = dt_time = dt_sum = 0; + maxspeed = PHYS_MAXSPEED(strafeplayer) * maxspeed_mod; + maxaccel = PHYS_ACCELERATE(strafeplayer); } + } - // 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; - } + vector movement = PHYS_INPUT_MOVEVALUES(strafeplayer); + float movespeed = vlen(vec2(movement)); + if(movespeed == 0) + movespeed = maxspeed; + else + movespeed = min(movespeed, maxspeed); - // 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) || (keys & KEY_BACKWARD)) - wishangle = 45; - else - wishangle = 90; - if(keys & KEY_LEFT) - wishangle *= -1; - else if(!(keys & KEY_RIGHT)) - wishangle = 0; // wraps at 180° - } + // determine whether the player is pressing forwards or backwards keys + int keys_fwd; + 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; + } - float real_wishangle = fabs(wishangle); // unmodified by side strafing code - strafekeys = real_wishangle > 45; + float wishangle = StrafeHUD_DetermineWishAngle(movement, keys, islocal); ++ float absolute_wishangle = fabs(wishangle); // unmodified by side strafing code + bool strafekeys = fabs(wishangle) > 45; - float hudangle = StrafeHUD_DetermineHudAngle(wishangle); - - // detect air strafe turning - if((!strafekeys && vlen(vec2(movement)) > 0) || onground || autocvar__hud_configure) - { + // detect air strafe turning + static bool turn = false; ++ float strafity = 0; + if((!strafekeys && vlen(vec2(movement)) > 0) || 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 + + 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) // CPMA turning ++ 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 + // calculate the maximum air strafe speed and acceleration - float strafity = 1 - (90 - fabs(wishangle)) / 45; ++ strafity = 1 - (90 - fabs(wishangle)) / 45; - float frictionspeed; // speed lost from friction - float strafespeed; // speed minus friction + if(PHYS_MAXAIRSTRAFESPEED(strafeplayer) != 0) + maxspeed = min(maxspeed, GeomLerp(PHYS_MAXAIRSPEED(strafeplayer), strafity, PHYS_MAXAIRSTRAFESPEED(strafeplayer))); - if((speed > 0) && onground) - { - float strafefriction = onslick ? PHYS_FRICTION_SLICK(strafeplayer) : PHYS_FRICTION(strafeplayer); + movespeed = min(movespeed, maxspeed); - frictionspeed = speed * dt * strafefriction * max(PHYS_STOPSPEED(strafeplayer) / speed, 1); - strafespeed = max(speed - frictionspeed, 0); - } - else - { - frictionspeed = 0; - strafespeed = speed; + if(PHYS_AIRSTRAFEACCELERATE(strafeplayer) != 0) + maxaccel = GeomLerp(PHYS_AIRACCELERATE(strafeplayer), strafity, PHYS_AIRSTRAFEACCELERATE(strafeplayer)); } + } - minspeed = autocvar_hud_panel_strafehud_switch_minspeed; - if(minspeed < 0) - minspeed = bestspeed + frictionspeed; + float dt = StrafeHUD_DetermineFrameTime(); - // 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; - } - } - 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 + maxaccel *= dt * movespeed; + float bestspeed = max(movespeed - maxaccel, 0); // target speed to gain maximum acceleration - static float demo_position = -37 / demo_maxangle; // current positioning value between -1 and +1 + // use local csqcmodel entity for this even when spectating, flickers too much otherwise + float speed = !autocvar__hud_configure ? vlen(vec2(csqcplayer.velocity)) : 1337; + bool moving = speed > 0; - 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; - } + float frictionspeed; // speed lost from friction + float strafespeed; // speed minus friction - // 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; + if(moving && onground) + { + float strafefriction = onslick ? PHYS_FRICTION_SLICK(strafeplayer) : PHYS_FRICTION(strafeplayer); - fwd = true; - wishangle = 45; - if(angle < 0) - wishangle *= -1; - } + frictionspeed = speed * dt * strafefriction * max(PHYS_STOPSPEED(strafeplayer) / speed, 1); + strafespeed = max(speed - frictionspeed, 0); + } + else + { + frictionspeed = 0; + strafespeed = speed; + } - // invert the wish angle when strafing backwards - if(!fwd) - wishangle *= -1; + // get current strafing angle ranging from -180° to +180° + float angle; - bool fwd; ++ bool fwd; // left & right variables are flipped when !fwd - // flip angles if v_flipped is enabled - if(autocvar_v_flipped) + if(!autocvar__hud_configure) + { + if(moving) { - angle *= -1; - wishangle *= -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; - // 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; - } + // calculate view angle relative to the players current velocity direction + angle = vel_angle - view_angle; - if(airstopaccel == 0) - airstopaccel = 1; // values of 0 are equivalent to 1 + // 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; - // 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 - } + // 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) { - // 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(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; + } + - // best angle to strafe at - // in case of ground friction we may decelerate if the acceleration is smaller than the speed loss from friction - float bestangle = (strafespeed > bestspeed ? acos(bestspeed / strafespeed) * RAD2DEG : 0); - float prebestangle = (strafespeed > movespeed ? acos(movespeed / strafespeed) * RAD2DEG : 0); - float opposite_bestangle = -bestangle; ++ 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 overturn_angle; ++ if(!moving) ++ { ++ // 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) ++ } ++ { ++ 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(overturn_angle < 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; ++ 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(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; ++ overturn_angle = 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 ++ overturn_angle = 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_overturn_angle = overturn_angle; ++ ++ float aircontrol = PHYS_AIRCONTROL(strafeplayer); ++ bool aircontrol_backwards = PHYS_AIRCONTROL_BACKWARDS(strafeplayer) == 1; ++ bool airaccel_qw = PHYS_AIRACCEL_QW(strafeplayer) == 1; ++ ++ float wturn_bestangle = 0; ++ float absolute_wturn_bestangle = 0; ++ ++ /* ++ * 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 && moving && wturn_valid; ++ if(wturn_check) ++ { ++ 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; ++ absolute_wturn_bestangle = wturn_bestangle; + } - - // 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; ++ absolute_wturn_bestangle = wturn_bestangle; + } + 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; ++ wturn_valid = false; ++ wturn_check = false; ++ } ++ } + - accelzone_right_offset = current_offset; - current_offset += accelzone_width; ++ float n_bestangle = 0; + - overturn_offset = current_offset; - current_offset += overturn_width; ++ // draw the switch indicators as if strafing normally (W+A style), while W-turning or side strafing ++ bool draw_normal = ((autocvar_hud_panel_strafehud_switch >= STRAFEHUD_SWITCH_NORMAL && wturning) ++ || (autocvar_hud_panel_strafehud_switch == STRAFEHUD_SWITCH_SIDESTRAFE && turn)); ++ if(draw_normal) ++ { ++ // 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; ++ } + - accelzone_left_offset = current_offset; - current_offset += accelzone_width; ++ float hudangle = StrafeHUD_DetermineHudAngle(absolute_wishangle, absolute_overturn_angle, strafity); - preaccelzone_left_offset = current_offset; - current_offset += preaccelzone_width; + float antiflicker_angle = bound(0, autocvar_hud_panel_strafehud_antiflicker_angle, 180); + float direction = StrafeHUD_DetermineDirection(angle, wishangle, antiflicker_angle); - // the wrapping code may struggle if we always append it on the right side - neutral_offset = direction == STRAFEHUD_DIRECTION_LEFT ? current_offset : -neutral_width; - } - - // 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; - opposite_bestangle *= -1; + prebestangle *= -1; ++ overturn_angle *= -1; + } ++ float opposite_bestangle = -bestangle; ++ float n_opposite_bestangle = -n_bestangle; + + bestangle -= wishangle; + opposite_bestangle -= wishangle; ++ n_opposite_bestangle -= wishangle; + prebestangle -= wishangle; ++ overturn_angle -= 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; + } - StrafeHUD_DrawStrafeMeter(shiftangle, wishangle, absolute_bestangle, absolute_prebestangle, moving, hudangle); - // 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_overturn_angle, moving, hudangle); - text_offset_top = text_offset_bottom = slickdetector_height; - } + float text_offset_top; + float text_offset_bottom; + text_offset_top = text_offset_bottom = StrafeHUD_DrawSlickDetector(strafeplayer, real_onslick); - StrafeHUD_DrawDirectionIndicator(direction, opposite_direction, fwd); - // 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 >= 90) ++ if(moveangle >= absolute_overturn_angle) { - currentangle_color = autocvar_hud_panel_strafehud_angle_overturn_color; - strafe_ratio = (moveangle - 90) / 90; - if(strafe_ratio > 1) strafe_ratio = 2 - strafe_ratio; - strafe_ratio *= -1; - 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_overturn_angle && absolute_overturn_angle == 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_overturn_angle) / (180 - absolute_overturn_angle); ++ // moveangle is always <= 180, so this code won't run if absolute_overturn_angle == 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; - } - - if(autocvar_hud_panel_strafehud_angle_arrow > 0) - { - 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 - } - } + currentangle_color = autocvar_hud_panel_strafehud_angle_accel_color; - strafe_ratio = (90 - moveangle) / (90 - absolute_bestangle); ++ strafe_ratio = (absolute_overturn_angle - moveangle) / (absolute_overturn_angle - absolute_bestangle); ++ // if absolute_overturn_angle == absolute_bestangle, this code won't run, no need to check if their difference is 0 } - - // 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 + else if(moveangle >= absolute_prebestangle) { - 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) - { - 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; - } - } - - // 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 - - // 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; - } - } - - 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; - } + if(autocvar_hud_panel_strafehud_bar_preaccel) - currentangle_color = autocvar_hud_panel_strafehud_angle_accel_color; ++ currentangle_color = autocvar_hud_panel_strafehud_angle_preaccel_color; + strafe_ratio = (moveangle - absolute_prebestangle) / (absolute_bestangle - absolute_prebestangle); } - draw_endBoldFont(); + if(autocvar_hud_panel_strafehud_style == STRAFEHUD_STYLE_GRADIENT || autocvar_hud_panel_strafehud_style == STRAFEHUD_STYLE_FAST_GRADIENT) + currentangle_color = StrafeHUD_MixColors( + autocvar_hud_panel_strafehud_angle_neutral_color, + currentangle_color, fabs(strafe_ratio)); } - 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(offset_centered) // offset gives the center of the bar, not left edge - offset -= original_width / 2; - if(type == STRAFEHUD_STYLE_GRADIENT && gradientType == STRAFEHUD_GRADIENT_NONE) - type = STRAFEHUD_STYLE_DRAWFILL; - - if(alpha <= 0 && type != STRAFEHUD_STYLE_GRADIENT || width <= 0) - return; - - if(offset < 0) - { - mirror_width = min(fabs(offset), width); - mirror_offset = panel_size.x + hidden_width - fabs(offset); - width += offset; - offset = 0; - } - else + float currentangle = 0; + if(mode == STRAFEHUD_MODE_VELOCITY_CENTERED) { - mirror_width = min(offset + width - panel_size.x - hidden_width, width); - mirror_offset = max(offset - panel_size.x - hidden_width, 0); + // avoid switching side too much at ±180° if anti flicker is triggered + if(fabs(angle) <= 180 - antiflicker_angle) + currentangle = angle; } - width = max(width, 0); - if((offset + width) > panel_size.x) - { - overflow_width = (offset + width) - panel_size.x; - width = panel_size.x - offset; - } - size.x = width; + // current angle size calculation + vector currentangle_size; + currentangle_size.x = max(panel_size.x * min(autocvar_hud_panel_strafehud_angle_width, 10), 1); + currentangle_size.y = max(panel_size.y * min(autocvar_hud_panel_strafehud_angle_height, 10), 1); + currentangle_size.z = 0; - if(mirror_offset < 0) - { - mirror_width += mirror_offset; - mirror_offset = 0; - } + float num_dashes = nearbyint(autocvar_hud_panel_strafehud_angle_dashes); - mirror_width = max(mirror_width, 0); - if((mirror_offset + mirror_width) > panel_size.x) + // adjust angle indicator line depending on style + switch(autocvar_hud_panel_strafehud_angle_style) { - overflow_mirror_width = (mirror_offset + mirror_width) - panel_size.x; - mirror_width = panel_size.x - mirror_offset; + case STRAFEHUD_INDICATOR_DASHED: + break; + case STRAFEHUD_INDICATOR_SOLID: + num_dashes = 1; + break; + case STRAFEHUD_INDICATOR_NONE: + default: + num_dashes = 0; + break; } - mirror_size.x = mirror_width; - switch(type) + 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; + + // 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 || has_top_arrow || has_bottom_arrow) { - 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; + bool angle_indicator_visible = false; - // minimum speed for change indicators - float minspeed = autocvar_hud_panel_strafehud_switch_minspeed; - if(minspeed < 0) - minspeed = bestspeed + frictionspeed; - - // only draw change indicators if minspeed is reached - if(autocvar_hud_panel_strafehud_switch && speed >= minspeed) - 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; ++ // only draw switch indicators if minspeed is reached ++ if(autocvar_hud_panel_strafehud_switch && autocvar_hud_panel_strafehud_switch_alpha > 0 && speed >= minspeed) + { + // draw the change indicator(s) - float current_changeangle = opposite_direction ? opposite_changeangle : changeangle; - float opposite_changeangle = opposite_direction ? opposite_bestangle : bestangle; ++ 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); - 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; + StrafeHUD_DrawAngleIndicator( + current_changeangle, currentangle_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) - switch(gradient_start) ++ if(direction == STRAFEHUD_DIRECTION_NONE || draw_normal) { - 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; + StrafeHUD_DrawAngleIndicator( + opposite_changeangle, currentangle_size, arrow_size, num_dashes, + has_top_arrow, has_bottom_arrow, autocvar_hud_panel_strafehud_switch_color, + autocvar_hud_panel_strafehud_switch_alpha, hudangle); } - StrafeHUD_drawGradient( - color, autocvar_hud_panel_strafehud_bar_neutral_color, - mirror_size, original_width, mirror_offset, - alpha, gradient_mirror_offset, gradientType); + if(autocvar_hud_panel_strafehud_switch_alpha > 0) + angle_indicator_visible = true; + } - if(autocvar_hud_panel_strafehud_bestangle && direction != STRAFEHUD_DIRECTION_NONE) - StrafeHUD_drawGradient( - color, autocvar_hud_panel_strafehud_bar_neutral_color, - size, original_width, offset, - alpha, gradient_offset, gradientType); - } -} ++ bool draw_bestangle = autocvar_hud_panel_strafehud_bestangle && (autocvar_hud_panel_strafehud_bestangle == 1 || turn) && direction != STRAFEHUD_DIRECTION_NONE; ++ if(draw_bestangle) + { + float ghostangle = opposite_direction ? opposite_bestangle : bestangle; -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; -} + StrafeHUD_DrawAngleIndicator( + ghostangle, currentangle_size, arrow_size, num_dashes, + has_top_arrow, has_bottom_arrow, autocvar_hud_panel_strafehud_bestangle_color, + autocvar_hud_panel_strafehud_bestangle_alpha, hudangle); -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) - { - 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); - } -} + if(autocvar_hud_panel_strafehud_bestangle_alpha > 0) + angle_indicator_visible = true; + } -// 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(); -} ++ // only draw wturn indicators if conditions were met ++ 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_check && wturn_show && absolute_wturn_bestangle < absolute_prebestangle && !onground && autocvar_hud_panel_strafehud_wturn && autocvar_hud_panel_strafehud_wturn_alpha > 0) ++ { ++ // draw the wturn indicators ++ StrafeHUD_DrawAngleIndicator( ++ wturn_left_bestangle, currentangle_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, currentangle_size, arrow_size, num_dashes, ++ has_top_arrow, has_bottom_arrow, autocvar_hud_panel_strafehud_wturn_color, ++ autocvar_hud_panel_strafehud_wturn_alpha, hudangle); ++ } + -// 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; + StrafeHUD_DrawAngleIndicator( + currentangle, currentangle_size, arrow_size, num_dashes, + has_top_arrow, has_bottom_arrow, currentangle_color, + autocvar_hud_panel_strafehud_angle_alpha, hudangle); - float alpha = cos(((time - lasttime) / fadetime) * M_PI_2); // fade non-linear like the physics panel does - vector size = panel_size; - size.y = height; + if(autocvar_hud_panel_strafehud_angle_alpha > 0) + angle_indicator_visible = true; - switch(position) - { - case STRAFEHUD_TEXT_TOP: - offset += size.y; - offset *= -1; - break; - case STRAFEHUD_TEXT_BOTTOM: - offset += panel_size.y; - break; - } + // offset text by amount the angle indicator extrudes from the strafehud bar + if(angle_indicator_visible) + { + float line_height_offset = currentangle_size.y; - drawstring_aspect(panel_pos + eY * offset, text, size, color, alpha * panel_fg_alpha, DRAWFLAG_NORMAL); - return true; -} + // amount line extrudes from the strafehud bar + line_height_offset = (line_height_offset - panel_size.y) / 2; -// 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; - } -} + // further offset the top text offset if the top arrow is drawn + float angle_offset_top; + if(has_top_arrow) + angle_offset_top = line_height_offset + arrow_size; + else if(num_dashes > 0) + angle_offset_top = line_height_offset; + else + angle_offset_top = 0; + + // further offset the bottom text offset if the bottom arrow is drawn + float angle_offset_bottom; + if(has_bottom_arrow) + angle_offset_bottom = line_height_offset + arrow_size; + else if(num_dashes > 0) + angle_offset_bottom = line_height_offset; + else + angle_offset_bottom = 0; -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")); + // 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); + } } + + text_offset_bottom += StrafeHUD_DrawVerticalAngle(text_offset_bottom); + + draw_beginBoldFont(); + text_offset_bottom += StrafeHUD_DrawStartSpeed(speed, text_offset_bottom); + text_offset_top += StrafeHUD_DrawStrafeEfficiency(strafe_ratio, text_offset_top); + text_offset_top += StrafeHUD_DrawJumpHeight(strafeplayer, real_onground, swimming, text_offset_top); + draw_endBoldFont(); + + StrafeHUD_Sonar(strafe_ratio, StrafeHUD_UpdateSonarSound()); + + hud_lasttime = time; } diff --cc qcsrc/client/hud/panel/strafehud.qh index 0ce6cb515,398ab5597..f7b6e5e40 --- a/qcsrc/client/hud/panel/strafehud.qh +++ b/qcsrc/client/hud/panel/strafehud.qh @@@ -10,9 -6,15 +10,14 @@@ bool autocvar__hud_panel_strafehud_dem 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; 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; 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; vector autocvar_hud_panel_strafehud_bar_neutral_color = '1 1 1'; float autocvar_hud_panel_strafehud_bar_neutral_alpha = 0.1; vector autocvar_hud_panel_strafehud_bar_accel_color = '0 1 0'; @@@ -29,13 -32,20 +35,18 @@@ vector autocvar_hud_panel_strafehud_ang vector autocvar_hud_panel_strafehud_angle_overturn_color = '1 0 1'; int autocvar_hud_panel_strafehud_angle_arrow = 1; float autocvar_hud_panel_strafehud_angle_arrow_size = 0.5; - bool autocvar_hud_panel_strafehud_bestangle = true; + 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; - bool autocvar_hud_panel_strafehud_switch = true; + 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_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; ++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; 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; @@@ -107,6 -120,4 +132,8 @@@ const int STRAFEHUD_INDICATOR_DASHED = 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 */ diff --cc qcsrc/client/hud/panel/strafehud/core.qc index fa06ae1a2,000000000..dae979549 mode 100644,000000..100644 --- a/qcsrc/client/hud/panel/strafehud/core.qc +++ b/qcsrc/client/hud/panel/strafehud/core.qc @@@ -1,247 -1,0 +1,248 @@@ +#include "core.qh" + +#include + +void StrafeHUD_DrawStrafeMeter( + float shiftangle, float wishangle, float absolute_bestangle, - float absolute_prebestangle, bool moving, float hudangle) ++ float absolute_prebestangle, float absolute_overturn_angle, ++ 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 = 90 - absolute_bestangle; - float preaccelzone_offsetangle = absolute_bestangle - absolute_prebestangle; ++ float accelzone_offsetangle = absolute_overturn_angle - absolute_bestangle; ++ float preaccelzone_offsetangle = fabs(absolute_bestangle - absolute_prebestangle); + float neutral_offsetangle = 360; - float overturn_offsetangle = 180; ++ float overturn_offsetangle = 360 - absolute_overturn_angle * 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 - 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, - true, hudangle); - - if(autocvar_hud_panel_strafehud_bar_preaccel) ++ if(accelzone_offsetangle > 0) + StrafeHUD_DrawStrafeHUD( - preaccelzone_left_startangle, preaccelzone_offsetangle, ++ 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, ++ true, 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, + true, hudangle); + + // draw right acceleration zone - 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, - true, hudangle); - - if(autocvar_hud_panel_strafehud_bar_preaccel) ++ if(accelzone_offsetangle > 0) + StrafeHUD_DrawStrafeHUD( - preaccelzone_right_startangle, preaccelzone_offsetangle, ++ 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, ++ true, 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, + true, hudangle); + + // draw overturn zone - // this is technically incorrect - // acceleration decreases at 90 degrees but speed loss happens a little bit after 90 degrees, - // however due to sv_airstopaccelerate that's hard to calculate - 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, - true, hudangle); ++ 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, ++ true, hudangle); + + // draw neutral zone - 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, - true, hudangle); ++ 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, ++ true, 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(autocvar_hud_panel_strafehud_direction && - direction != STRAFEHUD_DIRECTION_NONE && ++ 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 --cc qcsrc/client/hud/panel/strafehud/core.qh index 272b08840,000000000..8ed5d53a9 mode 100644,000000..100644 --- a/qcsrc/client/hud/panel/strafehud/core.qh +++ b/qcsrc/client/hud/panel/strafehud/core.qh @@@ -1,8 -1,0 +1,8 @@@ +#pragma once +#include "../strafehud.qh" + - void StrafeHUD_DrawStrafeMeter(float, float, float, float, bool, float); ++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 --cc qcsrc/client/hud/panel/strafehud/draw.qc index fdb185238,000000000..5cc919104 mode 100644,000000..100644 --- a/qcsrc/client/hud/panel/strafehud/draw.qc +++ b/qcsrc/client/hud/panel/strafehud/draw.qc @@@ -1,296 -1,0 +1,296 @@@ +#include "draw.qh" + +#include + +// functions to make hud elements align perfectly in the hud area +void StrafeHUD_DrawStrafeHUD(float startangle, float offsetangle, vector color, float alpha, int type, int gradientType, bool projectWidth, float range) +{ + float offset = StrafeHUD_AngleToOffset(startangle % 360, range); + float width = StrafeHUD_AngleToWidth(offsetangle, range); + float mirror_offset; + float mirror_width; + + if(type == STRAFEHUD_STYLE_GRADIENT || type == STRAFEHUD_STYLE_FAST_GRADIENT) + { + projectWidth = true; // must be fully projected for gradients + if(gradientType == STRAFEHUD_GRADIENT_NONE) + type = STRAFEHUD_STYLE_DRAWFILL; + } + + if(alpha <= 0 && type != STRAFEHUD_STYLE_GRADIENT && type != STRAFEHUD_STYLE_FAST_GRADIENT || width <= 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; + if(projectWidth && 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; + if(projectWidth && 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_FAST_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_FAST_GRADIENT && autocvar_hud_panel_strafehud_projection == STRAFEHUD_PROJECTION_LINEAR) + { + if(mirror_size.x > 0) + StrafeHUD_DrawGradientFast( + color, autocvar_hud_panel_strafehud_bar_neutral_color, + mirror_size, original_width, mirror_offset, alpha, + gradient_mirror_offset, gradientType); + + if(size.x > 0) + StrafeHUD_DrawGradientFast( + color, autocvar_hud_panel_strafehud_bar_neutral_color, + size, original_width, offset, alpha, + gradient_offset, gradientType); + } + else + { + if(mirror_size.x > 0) + StrafeHUD_DrawGradient( + color, autocvar_hud_panel_strafehud_bar_neutral_color, + mirror_size, original_width, mirror_offset, original_mirror_offset, + alpha, gradient_mirror_offset, gradientType, range); + + if(size.x > 0) + StrafeHUD_DrawGradient( + color, autocvar_hud_panel_strafehud_bar_neutral_color, + size, original_width, offset, original_offset, + alpha, gradient_offset, gradientType, range); + } + } +} + +// slow gradient, required for projection modes other than linear +void StrafeHUD_DrawGradient(vector color1, vector color2, vector size, float original_width, float offset, float original_offset, float alpha, float gradientOffset, int gradientType, 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 += gradientOffset; + float ratio = (ratio_offset - original_offset) / original_width * (gradientType == STRAFEHUD_GRADIENT_BOTH ? 2 : 1); + if(ratio > 1) ratio = 2 - ratio; + if(gradientType != 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); + } +} + +// optimized gradient, does not work with projection modes other than linear, decreased visual fidelity +void StrafeHUD_DrawGradientFast(vector color1, vector color2, vector size, float original_width, float offset, float alpha, float gradientOffset, int gradientType) +{ + if(gradientType == STRAFEHUD_GRADIENT_BOTH) + { + original_width /= 2; + + vector size1 = size; + size1.x = bound(0, original_width - gradientOffset, size.x); + + vector size2 = size; + size2.x = size.x - size1.x; + + if(size1.x > 0) + StrafeHUD_DrawGradientFast(color1, color2, size1, original_width, offset, alpha, gradientOffset, STRAFEHUD_GRADIENT_LEFT); + + if(size2.x > 0) + StrafeHUD_DrawGradientFast(color1, color2, size2, original_width, offset + size1.x, alpha, max(0, gradientOffset - original_width), STRAFEHUD_GRADIENT_RIGHT); + + return; + } + + 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 = gradientOffset / original_width; + if(gradientType == STRAFEHUD_GRADIENT_LEFT) + ratio1 = 1 - ratio1; + + float ratio2 = (gradientOffset + size.x) / original_width; + if(gradientType == STRAFEHUD_GRADIENT_LEFT) + ratio2 = 1 - ratio2; + + vector origin = HUD_Shift(panel_pos); + offset = HUD_ScaleX(offset); + size = HUD_Scale(size); + + R_BeginPolygon("", DRAWFLAG_NORMAL, true); + R_PolygonVertex(origin + eX * offset, '0 0 0', color1, alpha1 * (1 - ratio1)); + R_PolygonVertex(origin + eX * offset + eY * size.y, '0 0 0', color1, alpha1 * (1 - ratio1)); + R_PolygonVertex(origin + eX * (offset + size.x) + eY * size.y, '0 0 0', color1, alpha1 * (1 - ratio2)); + R_PolygonVertex(origin + eX * (offset + size.x), '0 0 0', color1, alpha1 * (1 - ratio2)); + R_EndPolygon(); + + R_BeginPolygon("", DRAWFLAG_NORMAL, true); + R_PolygonVertex(origin + eX * offset, '0 0 0', color2, alpha2 * ratio1); + R_PolygonVertex(origin + eX * offset + eY * size.y, '0 0 0', color2, alpha2 * ratio1); + R_PolygonVertex(origin + eX * (offset + size.x) + eY * size.y, '0 0 0', color2, alpha2 * ratio2); + R_PolygonVertex(origin + eX * (offset + size.x), '0 0 0', color2, alpha2 * ratio2); + R_EndPolygon(); +} + +// 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.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, 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; + - float alpha = cos(((time - lasttime) / fadetime) * 90 * DEG2RAD); // fade non-linear like the physics panel does ++ float alpha = cos(((time - lasttime) / fadetime) * M_PI_2); // fade non-linear like the physics panel does + vector size = panel_size; + size.y = height; + + switch(position) + { + case STRAFEHUD_TEXT_TOP: + offset += size.y; + offset *= -1; + break; + case STRAFEHUD_TEXT_BOTTOM: + offset += panel_size.y; + break; + } + + drawstring_aspect(panel_pos + eY * offset, text, size, color, alpha * panel_fg_alpha, DRAWFLAG_NORMAL); + return true; +} diff --cc qcsrc/client/hud/panel/strafehud/extra.qc index 3ac37027b,000000000..c092dad5a mode 100644,000000..100644 --- a/qcsrc/client/hud/panel/strafehud/extra.qc +++ b/qcsrc/client/hud/panel/strafehud/extra.qc @@@ -1,269 -1,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 + +// slick detector +float StrafeHUD_DrawSlickDetector(entity e, bool onslick) +{ + 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 slickdetected = false; + - slicksteps = 90 / 2 ** slicksteps; ++ slicksteps = 90 * DEG2RAD / 2 ** slicksteps; + + slickdetected = onslick; // do not need to traceline if already touching slick + + // traceline downwards into every direction + trace_dphitq3surfaceflags = 0; + vector traceorigin = e.origin + eZ * e.mins.z; - for(float i = 0; i < 90 && !slickdetected; i += slicksteps) ++ for(float i = 0; i < 90 * DEG2RAD - 0.00001 && !slickdetected; i += slicksteps) + { + vector slickoffset; + float slickrotate; - slickoffset.z = -cos(i * DEG2RAD) * autocvar_hud_panel_strafehud_slickdetector_range; - slickrotate = sin(i * DEG2RAD) * autocvar_hud_panel_strafehud_slickdetector_range; ++ 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 && !slickdetected; j += slicksteps) ++ for(float j = 0; j < 360 * DEG2RAD - 0.00001 && !slickdetected; j += slicksteps) + { - slickoffset.x = sin(j * DEG2RAD) * slickrotate; - slickoffset.y = cos(j * DEG2RAD) * slickrotate; ++ slickoffset.x = sin(j) * slickrotate; ++ slickoffset.y = cos(j) * slickrotate; + + traceline(traceorigin, traceorigin + slickoffset, MOVE_NOMONSTERS, e); - if((PHYS_FRICTION(e) == 0 && trace_fraction < 1) || trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK) ++ if((PHYS_FRICTION(e) == 0 && trace_fraction < 1) ++ || (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK)) + slickdetected = true; + if(i == 0) + break; + } + } + + // 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); + } + + return slickdetector_height; + } + + return 0; +} + +// vertical angle for weapon jumps +float StrafeHUD_DrawVerticalAngle(float text_offset_bottom) +{ + if(autocvar_hud_panel_strafehud_vangle) + { + float vangle = -PHYS_INPUT_ANGLES(strafeplayer).x; + float vangle_height = autocvar_hud_panel_strafehud_vangle_size * panel_size.y; + string vangle_text = strcat(ftos_decimals(vangle, 2), "°"); + + bool was_drawn = StrafeHUD_DrawTextIndicator( + vangle_text, vangle_height, + autocvar_hud_panel_strafehud_vangle_color, 1, + time, text_offset_bottom, STRAFEHUD_TEXT_BOTTOM); + + if(was_drawn) + return vangle_height; + } + + return 0; +} + +// strafe sonar +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); + } +} + +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; +} + +// 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 +float StrafeHUD_DrawJumpHeight(entity e, bool onground, bool swimming, float text_offset_top) +{ + 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) + { + // 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)); + + 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) + return jumpheight_height; + } + + return 0; +} + +// strafe efficiency +float StrafeHUD_DrawStrafeEfficiency(float strafe_ratio, float text_offset_top) +{ + { + if(autocvar_hud_panel_strafehud_strafeefficiency) + { + 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 = '1 1 1' - (strafe_ratio > 0 ? '1 0 1' : '0 1 1') * fabs(strafe_ratio); + + bool was_drawn = StrafeHUD_DrawTextIndicator( + strafeeff_text, strafeeff_height, + strafeeff_color, 1, + time, text_offset_top, STRAFEHUD_TEXT_TOP); + + if(was_drawn) + return strafeeff_height; + } + } + + return 0; +} + +// show speed when crossing the start trigger +// FIXME: move capturing the race start speed value out of the HUD +float StrafeHUD_DrawStartSpeed(float speed, 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) + { + 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)); + + 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) + return startspeed_height; + } + + return 0; +} diff --cc qcsrc/client/hud/panel/strafehud/util.qc index c15c99b19,000000000..9f29d466c mode 100644,000000..100644 --- a/qcsrc/client/hud/panel/strafehud/util.qc +++ b/qcsrc/client/hud/panel/strafehud/util.qc @@@ -1,304 -1,0 +1,303 @@@ +#include "util.qh" + +#include +#include +#include + +float StrafeHUD_AngleToWidth(float angle, float range) +{ + return angle / range * panel_size.x; +} + +float StrafeHUD_AngleToOffset(float angle, float range) +{ + return StrafeHUD_AngleToWidth(angle, range) + panel_size.x / 2; +} + +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; +} + +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; +} + +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 +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 + dt_sum += dt_client * dt_client; + dt_time += dt_client; + + 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 +float StrafeHUD_DetermineWishAngle(vector movement, int keys, bool islocal) +{ + float wishangle; + 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 || keys & KEY_BACKWARD) ++ if((keys & KEY_FORWARD) || (keys & 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; +} + - float StrafeHUD_DetermineHudAngle(float wishangle) ++float StrafeHUD_DetermineHudAngle(float absolute_wishangle, float absolute_overturn_angle, float strafity) +{ - float hudangle; - if(isnan(autocvar_hud_panel_strafehud_range)) - { - hudangle = 0; - } - else if(autocvar_hud_panel_strafehud_range == 0) - { - if(autocvar__hud_configure) - { - hudangle = 90; - } - else - { - // 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 90 - 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 absolute_wishangle = fabs(wishangle); - hudangle = max(absolute_wishangle, 90 - absolute_wishangle) * 2; - } - } - else if(autocvar_hud_panel_strafehud_range < 0) ++ ++ // 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_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 ++ float range_minangle = max(absolute_wishangle, absolute_overturn_angle - absolute_wishangle) * 2; ++ ++ float range_normal = autocvar_hud_panel_strafehud_range; ++ float range_side = autocvar_hud_panel_strafehud_range_sidestrafe; ++ float range_used; ++ ++ if(isnan(range_normal) || isnan(range_side)) return 0; ++ ++ if(range_normal == 0) ++ range_normal = autocvar__hud_configure ? 90 : range_minangle; // use minimum angle required if dynamically setting hud angle ++ else if(range_normal < 0) + { + float hfov = getproperty(VF_FOVX); + if(isnan(hfov)) hfov = 0; + - hudangle = hfov; ++ range_normal = hfov; + } ++ if(range_side == -1) // use the normal range ++ range_used = range_normal; + else + { - hudangle = bound(0, fabs(autocvar_hud_panel_strafehud_range), 360); // limit HUD range to 360 degrees, higher values do not make sense ++ if(range_side == 0) ++ range_side = autocvar__hud_configure ? 90 : range_minangle; ++ 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) + { + 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; + } +} + +// try to ignore if track_canjump is enabled, does not work in spectator mode if spectated player uses +jetpack or cl_movement_track_canjump +bool StrafeHUD_DetermineJumpHeld(entity e, int keys, bool islocal) +{ + if(islocal) + { + 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; +} + +vector StrafeHUD_MixColors(vector color1, vector color2, float ratio) +{ + if(ratio <= 0) return color1; + if(ratio >= 1) return color2; + return color1 + (color2 - color1) * ratio; +} diff --cc qcsrc/client/hud/panel/strafehud/util.qh index b8963bebd,000000000..b3a73e094 mode 100644,000000..100644 --- a/qcsrc/client/hud/panel/strafehud/util.qh +++ b/qcsrc/client/hud/panel/strafehud/util.qh @@@ -1,17 -1,0 +1,17 @@@ +#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); - float StrafeHUD_DetermineHudAngle(float); ++float StrafeHUD_DetermineHudAngle(float, float, float); +float StrafeHUD_DetermineDirection(float, float, float); +bool StrafeHUD_DetermineJumpHeld(entity, int, bool); +vector StrafeHUD_MixColors(vector, vector, float);