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 "0" "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 at once)"
-seta hud_panel_strafehud_style "1" "\"0\" = no styling, \"1\" = progress bar style for the strafe bar, \"2\" = gradient for the strafe bar"
+seta hud_panel_strafehud_range "90" "the angle range up to 360 degrees displayed on the strafehud, \"-1\" = current fov, \"0\" = dynamic (chooses the minimum range required to still see the whole area needed for accelerating)"
+seta hud_panel_strafehud_style "2" "\"0\" = no styling, \"1\" = progress bar style for the strafe bar, \"2\" = gradient for the strafe bar"
seta hud_panel_strafehud_unit "1" "speed unit (1 = qu/s, 2 = m/s, 3 = km/h, 4 = mph, 5 = knots), length unit (1 = qu, 2 = m, 3 = km, 4 = mi, 5 = nmi)"
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_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_neutral_color "1 1 1" "color of the strafe meter neutral zone"
-seta hud_panel_strafehud_bar_neutral_alpha "0.3" "opacity 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"
-seta hud_panel_strafehud_bar_accel_alpha "0.3" "opacity of the strafe meter acceleration zone"
+seta hud_panel_strafehud_bar_accel_alpha "0.5" "opacity of the strafe meter acceleration zone"
seta hud_panel_strafehud_bar_overturn_color "1 0 1" "color of the strafe meter overturn zone"
-seta hud_panel_strafehud_bar_overturn_alpha "0.3" "opacity of the strafe meter overturn zone"
+seta hud_panel_strafehud_bar_overturn_alpha "0.5" "opacity of the strafe meter overturn zone"
+seta hud_panel_strafehud_angle_style "0" "set the angle indicator style: 0 = none, 1 = solid line, 2 = dashed line"
+seta hud_panel_strafehud_angle_dashes "4" "determines the amount of dashes if the angle indicator uses a dashed line"
seta hud_panel_strafehud_angle_alpha "0.8" "opacity of the indicator showing the player's current angle"
-seta hud_panel_strafehud_angle_height "1.5" "height of the indicator showing the player's current angle (relative to the panel height)"
-seta hud_panel_strafehud_angle_width "0.005" "width of the indicator showing the player's current angle (relative to the panel width)"
-seta hud_panel_strafehud_angle_neutral_color "1 1 0" "color of the indicator showing the player's current angle if the player's angle is within the neutral zone"
-seta hud_panel_strafehud_angle_accel_color "0 1 1" "color of the indicator showing the player's current angle if the player's angle is within the acceleration zone"
-seta hud_panel_strafehud_angle_overturn_color "1 0 1" "color of the indicator showing the player's current angle if the player's angle is within the overturn zone"
-seta hud_panel_strafehud_switch_minspeed "-1" "minimum speed in qu/s at which switch indicators which are used to aid changing strafe direction will be shown (uses physics maxspeed + antiflicker speed if negative)"
-seta hud_panel_strafehud_switch_active_color "0 1 0" "color of the switch indicator on the current side"
-seta hud_panel_strafehud_switch_active_alpha "1" "opacity of the switch indicator on the current side"
-seta hud_panel_strafehud_switch_inactive_color "1 1 0" "color of the switch indicator on the opposite side"
-seta hud_panel_strafehud_switch_inactive_alpha "1" "opacity of the switch indicator on the opposite side"
-seta hud_panel_strafehud_switch_width "0.0075" "width of the strafe angle indicators (relative to the strafe bar width)"
+seta hud_panel_strafehud_angle_height "1" "height of the indicator showing the player's current angle (relative to the panel height)"
+seta hud_panel_strafehud_angle_width "0.001" "width of the indicator showing the player's current angle (relative to the panel width)"
+seta hud_panel_strafehud_angle_neutral_color "1 1 0" "color of the indicator showing the player's current angle if it is within the neutral zone"
+seta hud_panel_strafehud_angle_accel_color "0 1 1" "color of the indicator showing the player's current angle if it is within the acceleration zone"
+seta hud_panel_strafehud_angle_overturn_color "1 0 1" "color of the indicator showing the player's current angle if it is within the overturn zone"
+seta hud_panel_strafehud_angle_arrow "1" "set the angle indicator's arrow style: 0 = none, 1 = top, 2 = bottom, 3 = both"
+seta hud_panel_strafehud_angle_arrow_size "0.5" "size of the arrow (relative to the panel height)"
+seta hud_panel_strafehud_bestangle "1" "set to \"1\" to enable a ghost angle indicator showing the best angle to gain maximum acceleration"
+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 switch indicator showing the angle to move to when switching sides"
+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_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"
seta hud_panel_strafehud_direction_width "0.25" "stroke width of the direction caps which indicate the direction the player is currently strafing towards (relative to the panel height)"
seta hud_panel_strafehud_direction_length "0.02" "length of the horizontal component of the direction caps which indicate the direction the player is currently strafing towards (relative to the panel width)"
-seta hud_panel_strafehud_slickdetector_range "0" "range of the slick detector in qu, \"0\" to disable"
-seta hud_panel_strafehud_slickdetector_granularity "2" "value from 0 to 4 which defines how exact the search for slick should be, higher values may yield better results but require more computation"
+seta hud_panel_strafehud_slickdetector "1" "set to \"1\" to enable the slick detector which notifies you if there is slick near you"
+seta hud_panel_strafehud_slickdetector_range "200" "range of the slick detector in qu"
+seta hud_panel_strafehud_slickdetector_granularity "1" "value from 0 to 4 which defines how exact the search for slick should be, higher values may yield better results but require more computation"
seta hud_panel_strafehud_slickdetector_color "0 1 1" "color of the slick detector indicator"
seta hud_panel_strafehud_slickdetector_alpha "0.5" "opacity of the slick detector indicator"
seta hud_panel_strafehud_slickdetector_height "0.125" "height of the slick detector indicator (relative to the panel height)"
-seta hud_panel_strafehud_startspeed_fade "0" "fade time (in seconds) of the start speed text or \"0\" to disable"
+seta hud_panel_strafehud_startspeed "1" "set to \"1\" to enable the start speed indicator which shows you the speed you had while passing the start trigger of a race map"
+seta hud_panel_strafehud_startspeed_fade "4" "fade time (in seconds) of the start speed text"
seta hud_panel_strafehud_startspeed_color "1 0.75 0" "color of the start speed text"
seta hud_panel_strafehud_startspeed_size "1.5" "size of the start speed text (relative to the panel height)"
-seta hud_panel_strafehud_jumpheight_fade "0" "fade time (in seconds) of the jump height text or \"0\" to disable"
+seta hud_panel_strafehud_jumpheight "0" "set to \"1\" to enable the jump height indicator which tells you how high you jumped"
+seta hud_panel_strafehud_jumpheight_fade "4" "fade time (in seconds) of the jump height text"
seta hud_panel_strafehud_jumpheight_min "50" "minimum jump height to display in the selected unit"
seta hud_panel_strafehud_jumpheight_color "0 1 0.75" "color of the jump height text"
seta hud_panel_strafehud_jumpheight_size "1.5" "size of the jump height text (relative to the panel height)"
-seta hud_panel_strafehud_timeout_air "0.1" "time (in seconds) after take off before changing to air strafe physics when not jumping (visually more consistent hud while on slick downwards ramps)"
-seta hud_panel_strafehud_timeout_ground "0.03333333" "time (in seconds) after landing before changing to non-air strafe physics (visually more consistent hud while strafe turning when touching the floor after every hop)"
+seta hud_panel_strafehud_timeout_ground "0.1" "time (in seconds) after take off before changing to air strafe physics when not jumping (visually more consistent hud while on slick downwards ramps)"
seta hud_panel_strafehud_timeout_turn "0.1" "time (in seconds) after releasing the strafe keys before changing mode (visually more consistent hud while switching between left/right strafe turning)"
-seta hud_panel_strafehud_timeout_direction "0.5" "time (in seconds) it takes until direction changes (forward or backward movement) are applied (set to zero if you intend to sideways strafe)"
seta hud_panel_strafehud_antiflicker_angle "0.01" "how many degrees from 0° to 180° the hud ignores if it could cause visual disturbances otherwise (and to counter rounding errors)"
-seta hud_panel_strafehud_antiflicker_speed "0.0001" "how many qu/s the hud ignores if it could cause visual disturbances otherwise (and to counter rounding errors)"
+seta hud_panel_strafehud_fps_update "0.5" "update interval (in seconds) of the frametime to calculate the optimal angle, smaller values may cause flickering"
+seta hud_panel_strafehud_sonar "0" "set to \"1\" to enable the strafe sonar"
+seta hud_panel_strafehud_sonar_audio "misc/talk" "audio to play for sonar"
+seta hud_panel_strafehud_sonar_start "0.5" "how optimal from 0 to 1 your strafing angle has to be for the strafe sonar to activate"
+seta hud_panel_strafehud_sonar_interval_start "0.333333" "strafe sonar sound interval in seconds"
+seta hud_panel_strafehud_sonar_interval_range "-0.222222" "dynamic sound interval range in seconds of the strafe sonar as you approach the optimal angle"
+seta hud_panel_strafehud_sonar_interval_exponent "1" "exponent of the dynamic sound interval range of the strafe sonar"
+seta hud_panel_strafehud_sonar_volume_start "0.333333" "sound volume of the strafe sonar"
+seta hud_panel_strafehud_sonar_volume_range "0.666666" "dynamic volume range of the strafe sonar as you approach the optimal angle"
+seta hud_panel_strafehud_sonar_volume_exponent "1" "exponent of the dynamic volume range of the strafe sonar"
+seta hud_panel_strafehud_sonar_pitch_start "0.9" "playback speed of the strafe sonar"
+seta hud_panel_strafehud_sonar_pitch_range "0.1" "dynamic playback speed range of the strafe sonar as you approach the optimal angle"
+seta hud_panel_strafehud_sonar_pitch_exponent "1" "exponent of the dynamic playback speed range of the strafe sonar"
+seta hud_panel_strafehud_vangle "0" "set to \"1\" to enable the vertical angle indicator"
+seta hud_panel_strafehud_vangle_color "0.75 0.75 0.75" "color of the vertical angle text"
+seta hud_panel_strafehud_vangle_size "1" "size of the vertical angle text (relative to the panel height)"
+seta hud_panel_strafehud_projection "0" "strafehud projection mode, \"0\" = linear, \"1\" = perspective, \"2\" = panoramic"
// hud panel aliases
alias quickmenu "cl_cmd hud quickmenu ${* ?}"
set g_balance_crylink_secondary_speed 4000
set g_balance_crylink_secondary_spread 0.08
set g_balance_crylink_secondary_spreadtype 0
+set g_balance_crylink_swap_attacks 0
set g_balance_crylink_switchdelay_drop 0.2
set g_balance_crylink_switchdelay_raise 0.2
set g_balance_crylink_weaponreplace ""
set g_balance_crylink_secondary_speed 7000
set g_balance_crylink_secondary_spread 0.08
set g_balance_crylink_secondary_spreadtype 0
+set g_balance_crylink_swap_attacks 0
set g_balance_crylink_switchdelay_drop 0.15
set g_balance_crylink_switchdelay_raise 0.15
set g_balance_crylink_weaponreplace ""
set g_balance_crylink_secondary_speed 3000
set g_balance_crylink_secondary_spread 0.01
set g_balance_crylink_secondary_spreadtype 1
+set g_balance_crylink_swap_attacks 0
set g_balance_crylink_switchdelay_drop 0.2
set g_balance_crylink_switchdelay_raise 0.2
set g_balance_crylink_weaponreplace ""
set g_balance_crylink_secondary_speed 2000
set g_balance_crylink_secondary_spread 0
set g_balance_crylink_secondary_spreadtype 1
+set g_balance_crylink_swap_attacks 0
set g_balance_crylink_switchdelay_drop 0
set g_balance_crylink_switchdelay_raise 0
set g_balance_crylink_weaponreplace ""
set g_balance_crylink_secondary_speed 3000
set g_balance_crylink_secondary_spread 0.01
set g_balance_crylink_secondary_spreadtype 1
+set g_balance_crylink_swap_attacks 0
set g_balance_crylink_switchdelay_drop 0.2
set g_balance_crylink_switchdelay_raise 0.2
set g_balance_crylink_weaponreplace ""
case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_HELP(strcat("^3", "revivals", " ^7", _("Number of revivals")));
case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_HELP(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
case "score": if (!mode) return CTX(_("SCO^score")); else LOG_HELP(strcat("^3", "score", " ^7", _("Total score")));
+ case "avgspeed": if (!mode) return CTX(_("SCO^average speed"));else LOG_HELP(strcat("^3", "avgspeed", " ^7", _("Average speed (CTS)")));
+ case "topspeed": if (!mode) return CTX(_("SCO^top speed")); else LOG_HELP(strcat("^3", "topspeed", " ^7", _("Top speed (CTS)")));
+ case "startspeed": if (!mode) return CTX(_("SCO^start speed")); else LOG_HELP(strcat("^3", "startspeed", " ^7", _("Start speed (CTS)")));
+ case "strafe": if (!mode) return CTX(_("SCO^strafe")); else LOG_HELP(strcat("^3", "strafe", " ^7", _("Strafe efficiency (CTS)")));
case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
" +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
" +lms/lives +lms/rank" \
" +kh/kckills +kh/losses +kh/caps" \
-" ?+rc/laps ?+rc/time +rc,cts/fastest" \
+" ?+rc/laps ?+rc/time ?+cts/strafe ?+cts/startspeed ?+cts/avgspeed ?+cts/topspeed +rc,cts/fastest" \
" +as/objectives +nb/faults +nb/goals" \
" +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
" +dom/ticks +dom/takes" \
case SP_DMG: case SP_DMGTAKEN:
return sprintf("%.1f k", pl.(scores(field)) / 1000);
+ case SP_CTS_STRAFE:
+ {
+ float strafe_efficiency = pl.(scores(field)) / 1000;
+ if(strafe_efficiency < -1) return "";
+ sbt_field_rgb = '1 1 1' - (strafe_efficiency > 0 ? '1 0 1' : '0 1 1') * fabs(strafe_efficiency);
+ return sprintf("%.1f%%", strafe_efficiency * 100);
+ }
+
+ case SP_CTS_STARTSPEED:
+ case SP_CTS_AVGSPEED:
+ case SP_CTS_TOPSPEED:
+ {
+ float speed = pl.(scores(field)) * GetSpeedUnitFactor(autocvar_hud_panel_physics_speed_unit);
+ if(speed < 0) return "";
+ return sprintf("%d%s", speed, GetSpeedUnit(autocvar_hud_panel_physics_speed_unit));
+ }
+
default: case SP_SCORE:
tmp = pl.(scores(field));
f = scores_flags(field);
#include "strafehud.qh"
#include <client/draw.qh>
-#include <client/hud/panel/racetimer.qh>
-#include <client/view.qh>
-#include <common/animdecide.qh>
-#include <common/ent_cs.qh>
-#include <common/mapinfo.qh>
-#include <common/physics/movetypes/movetypes.qh>
-#include <common/physics/player.qh>
-#include <common/resources/cl_resources.qh>
#include <lib/csqcmodel/cl_player.qh>
+#include <common/physics/player.qh>
+#include <common/physics/movetypes/movetypes.qh>
+
+// non-essential
+#include <client/view.qh> // for v_flipped state
+
+// non-local players
+#include <common/animdecide.qh> // anim_implicit_state
+#include <common/ent_cs.qh> // CSQCModel_server2csqc()
+
+// start speed
+#include <client/hud/panel/racetimer.qh> // checkpoint information (race_*)
+
+// jump height
+#include <lib/csqcmodel/common.qh> // for IS_PLAYER() macro
+#include <common/resources/cl_resources.qh> // IS_DEAD() macro
+
+AUTOCVAR_SAVE(hud_panel_strafehud_pos, string, "0.320000 0.570000", "position of this base of the panel");
+AUTOCVAR_SAVE(hud_panel_strafehud_size, string, "0.360000 0.020000", "size of this panel");
+AUTOCVAR_SAVE(hud_panel_strafehud_bg, string, "0", "if set to something else than \"\" = override default background");
+AUTOCVAR_SAVE(hud_panel_strafehud_bg_color, string, "", "if set to something else than \"\" = override default panel background color");
+AUTOCVAR_SAVE(hud_panel_strafehud_bg_color_team, string, "", "override panel color with team color in team based games");
+AUTOCVAR_SAVE(hud_panel_strafehud_bg_alpha, string, "0.7", "if set to something else than \"\" = override default panel background alpha");
+AUTOCVAR_SAVE(hud_panel_strafehud_bg_border, string, "", "if set to something else than \"\" = override default size of border around the background");
+AUTOCVAR_SAVE(hud_panel_strafehud_bg_padding, string, "", "if set to something else than \"\" = override default padding of contents from border");
// StrafeHUD (#25)
// allow saving cvars that aesthetically change the panel into hud skin files
}
-float hidden_width;
-int direction;
-float demo_angle = -37;
-float demo_direction = 1;
-float demo_time = 0;
-bool state_onground = false;
-float state_onground_time = 0;
-bool state_strafekeys = false;
-float state_strafekeys_time = 0;
-bool turn = false;
-float turnangle;
-bool fwd = true;
-bool state_fwd = true;
-bool state_fwd_prev = true;
-float state_fwd_time = 0;
-float starttime = 0;
-float startspeed = -1;
-
-// provide basic panel cvars to old clients
-// TODO remove them after a future release (0.8.2+)
-noref string autocvar_hud_panel_strafehud_pos = "0.320000 0.570000";
-noref string autocvar_hud_panel_strafehud_size = "0.360000 0.020000";
-noref string autocvar_hud_panel_strafehud_bg = "0";
-noref string autocvar_hud_panel_strafehud_bg_color = "";
-noref string autocvar_hud_panel_strafehud_bg_color_team = "";
-noref string autocvar_hud_panel_strafehud_bg_alpha = "0.7";
-noref string autocvar_hud_panel_strafehud_bg_border = "";
-noref string autocvar_hud_panel_strafehud_bg_padding = "";
+float GeomLerp(float a, float _lerp, float b); // declare GeomLerp here since there's no header file for it
void HUD_StrafeHUD()
{
+ static float hud_lasttime = 0;
entity strafeplayer;
bool islocal;
+ if(false)
+ {
+ autocvar_hud_panel_strafehud_pos = autocvar_hud_panel_strafehud_pos;
+ autocvar_hud_panel_strafehud_size = autocvar_hud_panel_strafehud_size;
+ autocvar_hud_panel_strafehud_bg = autocvar_hud_panel_strafehud_bg;
+ autocvar_hud_panel_strafehud_bg_color = autocvar_hud_panel_strafehud_bg_color;
+ autocvar_hud_panel_strafehud_bg_color_team = autocvar_hud_panel_strafehud_bg_color_team;
+ autocvar_hud_panel_strafehud_bg_alpha = autocvar_hud_panel_strafehud_bg_alpha;
+ autocvar_hud_panel_strafehud_bg_border = autocvar_hud_panel_strafehud_bg_border;
+ autocvar_hud_panel_strafehud_bg_padding = autocvar_hud_panel_strafehud_bg_padding;
+ }
+
// generic hud routines
if(!autocvar__hud_configure)
{
if(!autocvar_hud_panel_strafehud ||
(spectatee_status == -1 && (autocvar_hud_panel_strafehud == 1 || autocvar_hud_panel_strafehud == 3)) ||
- (autocvar_hud_panel_strafehud == 3 && !MUTATOR_CALLHOOK(HUD_StrafeHUD_showoptional))) return;
+ (autocvar_hud_panel_strafehud == 3 && !MUTATOR_CALLHOOK(HUD_StrafeHUD_showoptional))) { hud_lasttime = time; return; }
}
HUD_Panel_LoadCvars();
// 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;
+ }
+
+ // 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
- bool onground = islocal ? IS_ONGROUND(strafeplayer) : !(strafeplayer.anim_implicit_state & ANIMIMPLICITSTATE_INAIR);
+ int keys = STAT(PRESSED_KEYS);
+ bool jumpheld = (islocal ? ((PHYS_INPUT_BUTTON_JUMP(strafeplayer) || PHYS_INPUT_BUTTON_JETPACK(strafeplayer))) && !PHYS_CL_TRACK_CANJUMP(strafeplayer) : (keys & KEY_JUMP)) && !PHYS_TRACK_CANJUMP(strafeplayer); // 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 real_onground = islocal ? IS_ONGROUND(strafeplayer) : !(strafeplayer.anim_implicit_state & ANIMIMPLICITSTATE_INAIR); // doesn't get changed by ground timeout and isn't affected by jump input
+ bool real_onslick = false; // doesn't get changed by ground timeout
+ bool onground = real_onground && !jumpheld; // if jump is held assume we are in air, avoids flickering of the hud when hitting the ground
+ bool onslick = real_onslick;
+ bool onground_expired;
bool strafekeys;
- bool swimming = strafeplayer.waterlevel >= WATERLEVEL_SWIMMING;
- bool spectating = entcs_GetSpecState(strafeplayer.sv_entnum) == ENTCS_SPEC_PURE;
+ bool swimming = strafe_waterlevel >= WATERLEVEL_SWIMMING; // the hud will not work well while swimming
float speed = !autocvar__hud_configure ? vlen(vec2(csqcplayer.velocity)) : 1337; // use local csqcmodel entity for this even when spectating, flickers too much otherwise
- float maxspeed_crouch_mod = IS_DUCKED(strafeplayer) && !swimming ? .5 : 1;
- float maxspeed_water_mod = swimming ? .7 : 1; // very simplified water physics, the hud will not work well (and is not supposed to) while swimming
+ float maxspeed_mod = IS_DUCKED(csqcplayer) ? .5 : 1; // only the local csqcplayer entity contains this information even when spectating
float maxspeed_phys = onground ? PHYS_MAXSPEED(strafeplayer) : PHYS_MAXAIRSPEED(strafeplayer);
- float maxspeed = !autocvar__hud_configure ? maxspeed_phys * maxspeed_crouch_mod * maxspeed_water_mod : 320;
- float vel_angle = vectoangles(strafeplayer.velocity).y;
- float view_angle = PHYS_INPUT_ANGLES(strafeplayer).y + 180;
+ 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 vel_angle = vectoangles(strafeplayer.velocity).y - (vectoangles(strafeplayer.velocity).y > 180 ? 360 : 0); // change the range from 0° - 360° to -180° - 180° to match how view_angle represents angles
+ float view_angle = PHYS_INPUT_ANGLES(strafeplayer).y;
float angle;
vector movement = PHYS_INPUT_MOVEVALUES(strafeplayer);
- int keys = STAT(PRESSED_KEYS);
+ bool fwd;
int keys_fwd;
- float wishangle = 0;
+ float wishangle;
+ int direction;
// HUD
- int mode = autocvar_hud_panel_strafehud_mode >= 0 && autocvar_hud_panel_strafehud_mode <= 1 ? autocvar_hud_panel_strafehud_mode : 0;
+ int mode = autocvar_hud_panel_strafehud_mode >= 0 && autocvar_hud_panel_strafehud_mode <= 1 ? autocvar_hud_panel_strafehud_mode : STRAFEHUD_MODE_VIEW_CENTERED;
float speed_conversion_factor = GetSpeedUnitFactor(autocvar_hud_panel_strafehud_unit);
float length_conversion_factor = GetLengthUnitFactor(autocvar_hud_panel_strafehud_unit);
int length_decimals = autocvar_hud_panel_strafehud_unit >= 3 && autocvar_hud_panel_strafehud_unit <= 5 ? 6 : 2; // use more decimals when displaying km or miles
float antiflicker_angle = bound(0, autocvar_hud_panel_strafehud_antiflicker_angle, 180);
- float antiflicker_speed = max(0, autocvar_hud_panel_strafehud_antiflicker_speed);
float minspeed;
float shift_offset = 0;
bool straight_overturn = false;
- bool immobile = speed <= (swimming ? antiflicker_speed : 0);
+ 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 = '0 0 0';
+ vector currentangle_size;
+ float real_bestangle; // positive with no wishangle offset
+ float real_prebestangle; // positive with no wishangle offset
float bestangle;
+ float prebestangle;
float odd_bestangle;
- bool bestangle_anywhere = false;
float bestangle_offset;
float switch_bestangle_offset;
bool odd_angles = false;
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';
+ vector direction_size_vertical;
+ vector direction_size_horizontal;
float range_minangle;
+ float arrow_size = max(panel_size.y * min(autocvar_hud_panel_strafehud_angle_arrow_size, 10), 0); // there's only one size cvar for the arrows, they will always have a 45° angle to ensure proper rendering without antialiasing
+ float text_offset_top = 0;
+ float text_offset_bottom = 0;
+ float hfov = getproperty(VF_FOVX);
+
+ 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;
+
+ onground_lasttime = time;
+ onslick_last = onslick;
+ }
+ else if(jumpheld || swimming) onground_lasttime = 0;
+
+ 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
+ {
+ onground = true;
+ onslick = onslick_last;
+
+ if(!autocvar__hud_configure)
+ {
+ maxspeed = PHYS_MAXSPEED(strafeplayer) * maxspeed_mod;
+ maxaccel = PHYS_ACCELERATE(strafeplayer);
+ }
+ }
+
+ 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);
+
+ // determine frametime
+ 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
+ dt_client /= 2; // doesn't 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;
+ }
// determine whether the player is pressing forwards or backwards keys
if(islocal) // if entity is local player
{
if(movement.x > 0)
{
- keys_fwd = 1;
+ keys_fwd = STRAFEHUD_KEYS_FORWARD;
}
else if(movement.x < 0)
{
- keys_fwd = -1;
+ keys_fwd = STRAFEHUD_KEYS_BACKWARD;
}
else
{
- keys_fwd = 0;
+ keys_fwd = STRAFEHUD_KEYS_NONE;
}
}
else // alternatively determine direction by querying pressed keys
{
if((keys & KEY_FORWARD) && !(keys & KEY_BACKWARD))
{
- keys_fwd = 1;
+ keys_fwd = STRAFEHUD_KEYS_FORWARD;
}
else if(!(keys & KEY_FORWARD) && (keys & KEY_BACKWARD))
{
- keys_fwd = -1;
+ keys_fwd = STRAFEHUD_KEYS_BACKWARD;
}
else
{
- keys_fwd = 0;
+ keys_fwd = STRAFEHUD_KEYS_NONE;
}
}
}
}
- strafekeys = fabs(wishangle) == 90;
+ strafekeys = fabs(wishangle) > 45;
// determine minimum required angle to display full strafe range
range_minangle = fabs(wishangle) % 90; // maximum range is 90 degree
hudangle = range_minangle; // use minimum angle required if dynamically setting hud angle
}
}
- else
+ else if(autocvar_hud_panel_strafehud_range < 0)
{
- hudangle = bound(0, fabs(autocvar_hud_panel_strafehud_range), 360); // limit HUD range to 360 degrees, higher values don't make sense
+ hudangle = hfov;
}
-
- // detect air strafe turning
- if(onground != state_onground)
+ else
{
- state_onground_time = time;
+ hudangle = bound(0, fabs(autocvar_hud_panel_strafehud_range), 360); // limit HUD range to 360 degrees, higher values don't make sense
}
- state_onground = onground;
- if(strafekeys != state_strafekeys)
- {
- state_strafekeys_time = time;
+ // 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;
}
- state_strafekeys = strafekeys;
- if((keys & KEY_FORWARD) || (keys & KEY_BACKWARD) || swimming || autocvar__hud_configure)
+ // detect air strafe turning
+ if((!strafekeys && vlen(vec2(movement)) > 0) || onground || autocvar__hud_configure)
{
turn = false;
}
- else if(onground)
- {
- if((time - state_onground_time) >= autocvar_hud_panel_strafehud_timeout_ground) // timeout for strafe jumping in general
- {
- 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(strafekeys)
+ turn = true;
+ else if(turn_expired)
+ turn = false;
+
+ if(turn) // CPMA turning
{
- if(((time - state_onground_time) >= autocvar_hud_panel_strafehud_timeout_air) || (keys & KEY_JUMP)) // timeout for slick ramps
+ if(strafekeys)
{
- turn = true; // CPMA turning
+ turn_lasttime = time;
turnangle = wishangle;
}
- }
- else if((time - state_strafekeys_time) >= autocvar_hud_panel_strafehud_timeout_turn) // timeout for jumping with strafe keys only
- {
- turn = false;
+ else // retain last state until strafe turning times out
+ {
+ wishangle = turnangle;
+ }
+
+ // calculate the maximum air strafe speed and acceleration
+ float 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));
+ }
}
}
- if(turn)
+
+ maxaccel *= dt * movespeed;
+ bestspeed = max(movespeed - maxaccel, 0); // target speed to gain maximum acceleration
+
+ float frictionspeed; // speed lost from friction
+ float strafespeed; // speed minus friction
+
+ if((speed > 0) && onground)
+ {
+ float strafefriction = onslick ? PHYS_FRICTION_SLICK(strafeplayer) : PHYS_FRICTION(strafeplayer);
+
+ frictionspeed = speed * dt * strafefriction * max(PHYS_STOPSPEED(strafeplayer) / speed, 1);
+ strafespeed = max(speed - frictionspeed, 0);
+ }
+ else
{
- maxspeed = PHYS_MAXAIRSTRAFESPEED(strafeplayer); // no modifiers here because they don't affect air strafing
- wishangle = turnangle;
+ frictionspeed = 0;
+ strafespeed = speed;
}
- minspeed = autocvar_hud_panel_strafehud_switch_minspeed < 0 ? maxspeed + antiflicker_speed : autocvar_hud_panel_strafehud_switch_minspeed;
+ minspeed = autocvar_hud_panel_strafehud_switch_minspeed;
+ if(minspeed < 0) minspeed = bestspeed + frictionspeed;
// get current strafing angle ranging from -180° to +180°
if(!autocvar__hud_configure)
// 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
+ // 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;
- // shift the strafe angle by 180° for hud calculations
- if(angle < 0) angle += 180;
- else angle -= 180;
-
// determine whether the player is strafing forwards or backwards
// if the player isn't strafe turning use forwards/backwards keys to determine direction
- if(!strafekeys)
+ if(fabs(wishangle) != 90)
{
- if(keys_fwd > 0)
+ if(keys_fwd == STRAFEHUD_KEYS_FORWARD)
{
- state_fwd = true;
+ fwd = true;
}
- else if(keys_fwd < 0)
+ else if(keys_fwd == STRAFEHUD_KEYS_BACKWARD)
{
- state_fwd = false;
+ fwd = false;
}
else
{
- state_fwd = fabs(angle) <= 90;
+ fwd = fabs(angle) <= 90;
}
}
// otherwise determine by examining the strafe angle
{
if(wishangle < 0) // detect direction using wishangle since the direction is not yet set
{
- state_fwd = angle <= -wishangle;
+ fwd = angle <= -wishangle;
}
else
{
- state_fwd = angle >= -wishangle;
+ fwd = angle >= -wishangle;
}
}
- if(state_fwd_prev != state_fwd)
- {
- state_fwd_time = time;
- }
- state_fwd_prev = state_fwd;
-
- if((time - state_fwd_time) >= autocvar_hud_panel_strafehud_timeout_direction || speed < maxspeed || (strafekeys && mode == 0)) // timeout when changing between forwards and backwards movement
- {
- fwd = state_fwd;
- }
-
// shift the strafe angle by 180° when strafing backwards
if(!fwd)
{
else
{
angle = 0;
+ fwd = true;
}
}
else // simulate turning for HUD setup
{
- fwd = true;
- if(autocvar__hud_panel_strafehud_demo && ((time - demo_time) >= .025))
+ 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
+
+ if(autocvar__hud_panel_strafehud_demo)
{
- demo_time = time;
- demo_angle += demo_direction;
- if(fabs(demo_angle) >= 55)
- {
- demo_direction = -demo_direction;
- }
+ float demo_dt = time - hud_lasttime;
+ float demo_step = (demo_turnspeed / demo_maxangle) * demo_dt;
+ demo_position = ((demo_position + demo_step) % 4 + 4) % 4;
}
- angle = demo_angle;
- wishangle = 45 * (demo_angle > 0 ? 1 : -1);
+
+ // 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;
+
+ fwd = true;
+ wishangle = 45;
+ if(angle < 0) wishangle *= -1;
}
// invert the wish angle when strafing backwards
}
// determine whether the player is strafing left or right
- if(wishangle != 0)
+ if(wishangle > 0)
{
- direction = wishangle > 0 ? 1 : -1;
+ direction = STRAFEHUD_DIRECTION_RIGHT;
+ }
+ else if(wishangle < 0)
+ {
+ direction = STRAFEHUD_DIRECTION_LEFT;
}
else
{
- direction = (angle > antiflicker_angle && angle < (180 - antiflicker_angle)) ? 1 : (angle < -antiflicker_angle && angle > (-180 + antiflicker_angle)) ? -1 : 0;
+ 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;
}
// best angle to strafe at
- bestangle = (speed > maxspeed ? acos(maxspeed / speed) : 0) * RAD2DEG * (direction < 0 ? -1 : 1);
+ // in case of ground friction we may decelerate if the acceleration is smaller than the speed loss from friction
+ real_bestangle = bestangle = (strafespeed > bestspeed ? acos(bestspeed / strafespeed) * RAD2DEG : 0);
+ real_prebestangle = prebestangle = (strafespeed > movespeed ? acos(movespeed / strafespeed) * RAD2DEG : 0);
+ if(direction == STRAFEHUD_DIRECTION_LEFT) // the angle becomes negative in case we strafe left
+ {
+ bestangle *= -1;
+ prebestangle *= -1;
+ }
odd_bestangle = -bestangle - wishangle;
bestangle -= wishangle;
+ prebestangle -= 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 = max(panel_size.x * autocvar_hud_panel_strafehud_angle_width, 1);
- if(mode == 0)
+ 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;
}
{
currentangle_offset = bound(-hudangle/2, angle, hudangle/2)/hudangle * panel_size.x + panel_size.x/2;
}
- currentangle_size.y = max(panel_size.y * min(autocvar_hud_panel_strafehud_angle_height, 2), 1);
// best strafe acceleration angle
bestangle_offset = bestangle/hudangle * panel_size.x + panel_size.x/2;
switch_bestangle_offset = -bestangle/hudangle * panel_size.x + panel_size.x/2;
- bestangle_width = max(panel_size.x * autocvar_hud_panel_strafehud_switch_width, 1);
+ bestangle_width = panel_size.x * autocvar_hud_panel_strafehud_switch_width;
+ if(!autocvar_hud_panel_strafehud_uncapped)
+ bestangle_width = max(bestangle_width, 1);
- if(((angle > -wishangle && direction < 0) || (angle < -wishangle && direction > 0)) && (direction != 0))
+ 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;
}
// direction indicator
- direction_size_vertical.x = max(panel_size.y * min(autocvar_hud_panel_strafehud_direction_width, .5), 1);
- direction_size_vertical.y = panel_size.y;
- direction_size_horizontal.x = max(panel_size.x * min(autocvar_hud_panel_strafehud_direction_length, .5), direction_size_vertical.x);
+ 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;
- // overturn
- overturn_width = 180/hudangle * panel_size.x;
+ direction_size_horizontal.z = 0;
// the neutral zone fills the whole strafe bar
if(immobile)
switch(autocvar_hud_panel_strafehud_style)
{
default:
- case 0:
+ 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 1:
+ 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
- accelzone_width = overturn_offset = (90 - fabs(bestangle + wishangle))/hudangle * panel_size.x;
- accelzone_right_offset = 0;
- accelzone_left_offset = overturn_offset + overturn_width;
- neutral_width = 360/hudangle * panel_size.x - accelzone_width*2 - overturn_width;
- neutral_offset = direction < 0 ? accelzone_left_offset + accelzone_width : -neutral_width;
-
- // remove switch indicator width from offset
- if(direction < 0)
- {
- bestangle_offset -= bestangle_width;
- switch_odd_bestangle_offset -= bestangle_width;
- }
+ if(autocvar_hud_panel_strafehud_bar_preaccel)
+ preaccelzone_width = (fabs(bestangle - prebestangle))/hudangle * panel_size.x;
else
+ preaccelzone_width = 0;
+ accelzone_width = (90 - fabs(bestangle + wishangle))/hudangle * panel_size.x;
+ overturn_width = 180/hudangle * panel_size.x;
+ neutral_width = 360/hudangle * panel_size.x - accelzone_width*2 - preaccelzone_width*2 - overturn_width;
+
{
- switch_bestangle_offset -= bestangle_width;
- odd_bestangle_offset -= bestangle_width;
+ float current_offset = 0;
+ preaccelzone_right_offset = current_offset;
+ current_offset += preaccelzone_width;
+
+ accelzone_right_offset = current_offset;
+ current_offset += accelzone_width;
+
+ overturn_offset = current_offset;
+ current_offset += overturn_width;
+
+ accelzone_left_offset = current_offset;
+ current_offset += accelzone_width;
+
+ preaccelzone_left_offset = current_offset;
+ current_offset += preaccelzone_width;
+
+ neutral_offset = direction == STRAFEHUD_DIRECTION_LEFT ? current_offset : -neutral_width; // the wrapping code may struggle if we always append it on the right side
}
// shift hud if operating in view angle centered mode
- if(mode == 0)
+ if(mode == STRAFEHUD_MODE_VIEW_CENTERED)
{
shift_offset = -currentangle_offset;
bestangle_offset += shift_offset;
odd_bestangle_offset += shift_offset;
switch_odd_bestangle_offset += shift_offset;
}
- if(direction < 0) shift_offset += -360/hudangle * panel_size.x;
+ 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
- HUD_Panel_DrawStrafeHUD(accelzone_left_offset, accelzone_width, autocvar_hud_panel_strafehud_bar_accel_color, autocvar_hud_panel_strafehud_bar_accel_alpha * panel_fg_alpha, autocvar_hud_panel_strafehud_style, 1);
+ 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, true, hudangle);
+ if(autocvar_hud_panel_strafehud_bar_preaccel)
+ HUD_Panel_DrawStrafeHUD(preaccelzone_left_offset, preaccelzone_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, true, hudangle);
// draw right acceleration zone
- HUD_Panel_DrawStrafeHUD(accelzone_right_offset, accelzone_width, autocvar_hud_panel_strafehud_bar_accel_color, autocvar_hud_panel_strafehud_bar_accel_alpha * panel_fg_alpha, autocvar_hud_panel_strafehud_style, 2);
+ 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, true, hudangle);
+ if(autocvar_hud_panel_strafehud_bar_preaccel)
+ HUD_Panel_DrawStrafeHUD(preaccelzone_right_offset, preaccelzone_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, true, hudangle);
- // draw overturn zone
- HUD_Panel_DrawStrafeHUD(overturn_offset, overturn_width, autocvar_hud_panel_strafehud_bar_overturn_color, autocvar_hud_panel_strafehud_bar_overturn_alpha * panel_fg_alpha, autocvar_hud_panel_strafehud_style, 3);
+ // draw overturn zone (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)
+ 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, true, hudangle);
// draw neutral zone
- HUD_Panel_DrawStrafeHUD(neutral_offset, neutral_width, autocvar_hud_panel_strafehud_bar_neutral_color, autocvar_hud_panel_strafehud_bar_neutral_alpha * panel_fg_alpha, autocvar_hud_panel_strafehud_style, 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, true, hudangle);
- if(direction != 0 && direction_size_vertical.x > 0 && autocvar_hud_panel_strafehud_direction_alpha * panel_fg_alpha > 0)
+ if(autocvar_hud_panel_strafehud_switch && speed >= minspeed && bestangle_width > 0 && autocvar_hud_panel_strafehud_switch_alpha > 0) // only draw indicators if minspeed is reached
{
- bool indicator_direction = direction < 0;
- // 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)
+ // draw the switch indicator(s)
+ float offset = !odd_angles ? bestangle_offset : odd_bestangle_offset;
+ float switch_offset = !odd_angles ? switch_bestangle_offset : switch_odd_bestangle_offset;
+
+ offset = StrafeHUD_projectOffset(offset, hudangle);
+ switch_offset = StrafeHUD_projectOffset(switch_offset, hudangle);
+
+ // remove switch indicator width from offset
+ if(direction == STRAFEHUD_DIRECTION_LEFT)
{
- indicator_direction = !indicator_direction;
+ if(!odd_angles)
+ offset -= bestangle_width;
+ else
+ switch_offset -= bestangle_width;
+ }
+ else
+ {
+ if(!odd_angles)
+ switch_offset -= bestangle_width;
+ else
+ offset -= bestangle_width;
}
- // draw the direction indicator caps at the sides of the hud
- // vertical line
- if(direction_size_vertical.y > 0) drawfill(panel_pos + 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 ? -direction_size_vertical.x : panel_size.x - direction_size_horizontal.x + direction_size_vertical.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 ? -direction_size_vertical.x : panel_size.x - direction_size_horizontal.x + direction_size_vertical.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);
- }
- if(speed >= minspeed) // only draw indicators if minspeed is reached
- {
- // draw best angles for acceleration
- float offset = !odd_angles ? bestangle_offset : odd_bestangle_offset;
- float switch_offset = !odd_angles ? switch_bestangle_offset : switch_odd_bestangle_offset;
- // both indicators are inactive if no direction can be determined
- vector switch_color = direction != 0 ? autocvar_hud_panel_strafehud_switch_active_color : autocvar_hud_panel_strafehud_switch_inactive_color;
- float switch_alpha = direction != 0 ? autocvar_hud_panel_strafehud_switch_active_alpha : autocvar_hud_panel_strafehud_switch_inactive_alpha;
- // draw the switch indicators
- HUD_Panel_DrawStrafeHUD(switch_offset, bestangle_width, autocvar_hud_panel_strafehud_switch_inactive_color, autocvar_hud_panel_strafehud_switch_inactive_alpha * panel_fg_alpha, 0, 0);
- HUD_Panel_DrawStrafeHUD(offset, bestangle_width, switch_color, switch_alpha * panel_fg_alpha, 0, 0);
+ HUD_Panel_DrawStrafeHUD(switch_offset, 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, false, hudangle);
+ if(direction == STRAFEHUD_DIRECTION_NONE) HUD_Panel_DrawStrafeHUD(offset, 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, false, hudangle);
}
}
- // experimental: slick detector
- slickdetector_height = panel_size.y * bound(0, autocvar_hud_panel_strafehud_slickdetector_height, 0.5);
- if(autocvar_hud_panel_strafehud_slickdetector_range > 0 && autocvar_hud_panel_strafehud_slickdetector_alpha > 0 && slickdetector_height > 0 && panel_size.x > 0) // dunno if slick detection works in spectate
+ // 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 = 90 / pow(2, bound(0, autocvar_hud_panel_strafehud_slickdetector_granularity, 4));
+ float slicksteps = max(autocvar_hud_panel_strafehud_slickdetector_granularity, 0);
bool slickdetected = false;
- slickdetected = IS_ONSLICK(strafeplayer); // don't need to traceline if already touching slick
+ if(!autocvar_hud_panel_strafehud_uncapped)
+ slicksteps = min(slicksteps, 4);
+ slicksteps = 90 / 2 ** slicksteps;
+
+ slickdetected = real_onslick; // don't need to traceline if already touching slick
// traceline into every direction
trace_dphitq3surfaceflags = 0;
- for(float i = 0; i < 360 && !slickdetected; i += slicksteps)
+ vector traceorigin = strafeplayer.origin + eZ * strafeplayer.mins.z;
+ for(float i = 0; i < 90 && !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;
- if(i != 0 && i != 180)
- {
- for(float j = 0; j < 180 && !slickdetected; j += slicksteps)
- {
- slickoffset.x = sin(j * DEG2RAD) * slickrotate;
- slickoffset.y = cos(j * DEG2RAD) * slickrotate;
- traceline(strafeplayer.origin, strafeplayer.origin + slickoffset, MOVE_WORLDONLY, NULL);
- if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK) slickdetected = true;
- }
- }
- else
+ for(float j = 0; j < 360 && !slickdetected; j += slicksteps)
{
- slickoffset.x = slickoffset.y = 0;
- traceline(strafeplayer.origin, strafeplayer.origin + slickoffset, MOVE_WORLDONLY, NULL);
- if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK) slickdetected = true;
+ slickoffset.x = sin(j * DEG2RAD) * slickrotate;
+ slickoffset.y = cos(j * DEG2RAD) * 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;
}
}
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);
+
+ text_offset_top = text_offset_bottom = slickdetector_height;
}
}
- draw_beginBoldFont();
- // show speed when crossing the start trigger
- if(autocvar_hud_panel_strafehud_startspeed_fade > 0)
+ 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)
{
- float text_alpha = 0;
- if(race_checkpoint == 254) // checkpoint 254 is the start trigger
+ 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)
{
- if(starttime != race_checkpointtime)
- {
- starttime = race_checkpointtime;
- startspeed = speed;
- }
- }
- if(startspeed >= 0)
- {
- text_alpha = cos(((time - starttime) / autocvar_hud_panel_strafehud_startspeed_fade) * 90 * DEG2RAD); // fade non-linear like the physics panel does
- if((time - starttime) > autocvar_hud_panel_strafehud_startspeed_fade)
- {
- startspeed = -1;
- }
- }
- if(startspeed >= 0 && text_alpha > 0 && autocvar_hud_panel_strafehud_startspeed_size > 0)
- {
- vector startspeed_size = panel_size;
- startspeed_size.y = panel_size.y * min(autocvar_hud_panel_strafehud_startspeed_size, 5);
- string speed_unit = GetSpeedUnit(autocvar_hud_panel_strafehud_unit);
- drawstring_aspect(panel_pos + eY * panel_size.y, strcat(ftos_decimals(startspeed * speed_conversion_factor, 2), autocvar_hud_panel_strafehud_unit_show ? speed_unit : ""), startspeed_size, autocvar_hud_panel_strafehud_startspeed_color, text_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
+ 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);
}
- else
- {
- starttime = 0;
- startspeed = -1;
+
+ string newsound = autocvar_hud_panel_strafehud_sonar_audio;
+ static string cursound = string_null;
+ static string sonarsound = string_null;
+ if(newsound == "") {
+ 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);
+ }
}
- // show height achieved by a single jump
- if(autocvar_hud_panel_strafehud_jumpheight_fade > 0 && autocvar_hud_panel_strafehud_jumpheight_size > 0)
- {
- 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
+ // draw the actual strafe angle
+ if(!immobile) {
+ float moveangle = fabs(angle + wishangle);
+ float strafe_ratio = 0;
- // tries to catch kill and spectate but those are not reliable, should just hook to kill/spectate/teleport and reset jump height there
- if((strafeplayer.velocity.z <= 0 && height_max >= strafeplayer.origin.z) || IS_ONGROUND(strafeplayer) || swimming || IS_DEAD(strafeplayer) || spectating)
+ // player is overturning
+ if(moveangle >= 90)
{
- height_min = height_max = strafeplayer.origin.z;
+ 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;
}
- else if(strafeplayer.origin.z > height_max)
+ // player gains speed by strafing
+ else if(moveangle >= real_bestangle)
{
- height_max = strafeplayer.origin.z;
- jumpheight = (height_max - height_min) * length_conversion_factor;
-
- if(jumpheight > max(autocvar_hud_panel_strafehud_jumpheight_min, 0))
- jumptime = time;
+ currentangle_color = autocvar_hud_panel_strafehud_angle_accel_color;
+ strafe_ratio = (90 - moveangle) / (90 - real_bestangle);
+ }
+ else if(moveangle >= real_prebestangle)
+ {
+ if(autocvar_hud_panel_strafehud_bar_preaccel)
+ currentangle_color = autocvar_hud_panel_strafehud_angle_accel_color;
+ strafe_ratio = (moveangle - real_prebestangle) / (real_bestangle - real_prebestangle);
}
- if((time - jumptime) <= autocvar_hud_panel_strafehud_jumpheight_fade)
+ if(autocvar_hud_panel_strafehud_style == STRAFEHUD_STYLE_GRADIENT)
{
- float text_alpha = cos(((time - jumptime) / autocvar_hud_panel_strafehud_jumpheight_fade) * 90 * DEG2RAD); // fade non-linear like the physics panel does
- vector jumpheight_size = panel_size;
- jumpheight_size.y = panel_size.y * min(autocvar_hud_panel_strafehud_jumpheight_size, 5);
- string length_unit = GetLengthUnit(autocvar_hud_panel_strafehud_unit);
- drawstring_aspect(panel_pos - eY * jumpheight_size.y, strcat(ftos_decimals(jumpheight, length_decimals), autocvar_hud_panel_strafehud_unit_show ? length_unit : ""), jumpheight_size, autocvar_hud_panel_strafehud_jumpheight_color, text_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
+ currentangle_color = StrafeHUD_mixColors(autocvar_hud_panel_strafehud_angle_neutral_color, currentangle_color, fabs(strafe_ratio));
}
- }
- draw_endBoldFont();
- if(speed < (maxspeed + antiflicker_speed) && !immobile)
- {
- bestangle_anywhere = true; // moving forward should suffice to gain speed
- }
+ // reuse strafe ratio for strafe sonar
+ static float sonar_time = 0;
- // draw the actual strafe angle
- if(!bestangle_anywhere && !immobile) // player gains speed with strafing
- {
- if((direction > 0 && (angle >= bestangle || angle <= -(bestangle + wishangle*2))) ||
- (direction < 0 && (angle <= bestangle || angle >= -(bestangle + wishangle*2))))
- currentangle_color = autocvar_hud_panel_strafehud_angle_accel_color;
+ 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);
+ }
+ }
}
- if(fabs(angle + wishangle) > 90) // player is overturning
+ if(mode == STRAFEHUD_MODE_VIEW_CENTERED || straight_overturn)
{
- currentangle_color = autocvar_hud_panel_strafehud_angle_overturn_color;
+ currentangle_offset = panel_size.x/2;
}
- else if(bestangle_anywhere) // player gains speed without strafing
+
+ float angleheight_offset = currentangle_size.y;
+ float ghost_offset = 0;
+ if(autocvar_hud_panel_strafehud_bestangle && direction != STRAFEHUD_DIRECTION_NONE)
{
- currentangle_color = autocvar_hud_panel_strafehud_angle_accel_color;
+ ghost_offset = bound(0, (odd_angles ? odd_bestangle_offset : bestangle_offset), panel_size.x);
}
- if(mode == 0 || straight_overturn)
+ currentangle_offset = StrafeHUD_projectOffset(currentangle_offset, hudangle);
+ ghost_offset = StrafeHUD_projectOffset(ghost_offset, hudangle);
+
+ switch(autocvar_hud_panel_strafehud_angle_style)
{
- currentangle_offset = panel_size.x/2;
+ case STRAFEHUD_INDICATOR_SOLID:
+ if(currentangle_size.x > 0 && currentangle_size.y > 0)
+ {
+ if(autocvar_hud_panel_strafehud_bestangle && direction != STRAFEHUD_DIRECTION_NONE) 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(autocvar_hud_panel_strafehud_bestangle && direction != STRAFEHUD_DIRECTION_NONE) 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;
}
- if(autocvar_hud_panel_strafehud_style == 2 && !immobile)
+ 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))
+ angle_offset_top = angle_offset_bottom = (angleheight_offset - panel_size.y) / 2; // offset text by amount the angle indicator extrudes from the strafehud bar
+
+ if(autocvar_hud_panel_strafehud_angle_arrow > 0)
{
- float moveangle = angle + wishangle;
- float strafeangle = (bestangle + wishangle) * (direction < 0 ? -1 : 1);
- float strafe_ratio = 0;
- if(fabs(moveangle) > 90)
+ if(arrow_size > 0)
{
- strafe_ratio = -((fabs(moveangle) - 90) / 90);
- if(strafe_ratio < -1) strafe_ratio = -2 - strafe_ratio;
- }
- else
- {
- if(moveangle >= strafeangle)
+ if(autocvar_hud_panel_strafehud_angle_arrow == 1 || autocvar_hud_panel_strafehud_angle_arrow >= 3)
{
- strafe_ratio = 1 - (moveangle - strafeangle) / (90 - strafeangle);
+ if(autocvar_hud_panel_strafehud_bestangle && direction != STRAFEHUD_DIRECTION_NONE) 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);
+ 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);
+
+ angle_offset_top += arrow_size; // further offset the top text offset if the top arrow is drawn
}
- else if(moveangle <= -strafeangle)
+ if(autocvar_hud_panel_strafehud_angle_arrow >= 2)
{
- strafe_ratio = 1 - (moveangle + strafeangle) / (-90 + strafeangle);
+ if(autocvar_hud_panel_strafehud_bestangle && direction != STRAFEHUD_DIRECTION_NONE) 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);
+ 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);
+
+ angle_offset_bottom += arrow_size; // further offset the bottom text offset if the bottom arrow is drawn
}
}
- if(strafe_ratio < 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);
+
+ // vertical angle for weapon jumps
+ {
+ if(autocvar_hud_panel_strafehud_vangle)
{
- currentangle_color = StrafeHUD_mixColors(autocvar_hud_panel_strafehud_angle_neutral_color, autocvar_hud_panel_strafehud_angle_overturn_color, -strafe_ratio);
+ 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), "°");
+
+ if(StrafeHUD_drawTextIndicator(vangle_text, vangle_height, autocvar_hud_panel_strafehud_vangle_color, 1, time, text_offset_bottom, STRAFEHUD_TEXT_BOTTOM))
+ text_offset_bottom += vangle_height;
}
- else
+ }
+
+ draw_beginBoldFont();
+
+ // show speed when crossing the start trigger
+ {
+ static float startspeed = 0, starttime = 0; // displayed value and timestamp for fade out
+
+ if((race_nextcheckpoint == 1) || (race_checkpoint == 254 && race_nextcheckpoint == 255)) // check if the start trigger was hit (will also trigger if the finish trigger was hit if those have the same ID)
{
- currentangle_color = StrafeHUD_mixColors(autocvar_hud_panel_strafehud_angle_neutral_color, autocvar_hud_panel_strafehud_angle_accel_color, strafe_ratio);
+ 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_panel_strafehud_unit));
+
+ if(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))
+ text_offset_bottom += startspeed_height;
}
}
- if(currentangle_size.x > 0 && currentangle_size.y > 0 && autocvar_hud_panel_strafehud_angle_alpha * panel_fg_alpha > 0)
+ // show height achieved by a single jump
+ // FIXME: checking z position differences is unreliable (warpzones, teleporter, kill, etc) but using velocity to calculate jump height would be
+ // inaccurate in hud code (possibly different tick rate than physics, doesn't run when hud isn't drawn, rounding errors)
{
- 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);
+ 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_panel_strafehud_unit));
+
+ if(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))
+ text_offset_top += jumpheight_height;
+ }
}
+
+ draw_endBoldFont();
+ }
+ hud_lasttime = time;
+}
+
+float StrafeHUD_projectOffset(float offset, float range) {
+ range *= DEG2RAD / 2;
+ float angle = (offset - (panel_size.x/2)) / (panel_size.x/2) * range;
+ switch(autocvar_hud_panel_strafehud_projection) {
+ default:
+ case STRAFEHUD_PROJECTION_LINEAR:
+ return offset;
+ case STRAFEHUD_PROJECTION_PERSPECTIVE:
+ offset = tan(angle) / tan(range);
+ break;
+ case STRAFEHUD_PROJECTION_PANORAMIC:
+ offset = tan(angle/2) / tan(range/2);
+ break;
}
+ offset = offset * (panel_size.x/2) + (panel_size.x/2);
+ return offset;
+}
+
+float StrafeHUD_projectWidth(float offset, float width, float range) {
+ return StrafeHUD_projectOffset(offset + width, range) - StrafeHUD_projectOffset(offset, range);
}
// functions to make hud elements align perfectly in the hud area
-void HUD_Panel_DrawStrafeHUD(float offset, float width, vector color, float alpha, int type, int gradientType)
+void HUD_Panel_DrawStrafeHUD(float offset, float width, float hidden_width, vector color, float alpha, int type, int gradientType, bool doProject, float range)
{
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
- float original_width = width;
- float hiddencolor_width;
+ if(type == STRAFEHUD_STYLE_GRADIENT && gradientType == STRAFEHUD_GRADIENT_NONE) type = STRAFEHUD_STYLE_DRAWFILL;
- if(alpha <= 0 && type != 2 || width <= 0) return;
-
- if(type == 2 && gradientType == 0) type = 0;
+ if(alpha <= 0 && type != STRAFEHUD_STYLE_GRADIENT || width <= 0) return;
if(offset < 0)
{
mirror_width = min(offset + width - panel_size.x - hidden_width, width);
mirror_offset = max(offset - panel_size.x - hidden_width, 0);
}
+
+ 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;
+
+ vector original_size = size;
+ float original_offset = offset;
+ if(doProject) {
+ if(size.x > 0) size.x = StrafeHUD_projectWidth(offset, size.x, range);
+ offset = StrafeHUD_projectOffset(offset, range);
+ }
+
if(mirror_offset < 0)
{
mirror_width += mirror_offset;
mirror_offset = 0;
}
+
+ mirror_width = max(mirror_width, 0);
if((mirror_offset + mirror_width) > panel_size.x)
{
+ overflow_mirror_width = (mirror_offset + mirror_width) - panel_size.x;
mirror_width = panel_size.x - mirror_offset;
}
+ mirror_size.x = mirror_width;
- if(width < 0) width = 0;
- if(mirror_width < 0) mirror_width = 0;
- hiddencolor_width = original_width - width - mirror_width;
-
- if(direction < 0) // swap mirror and non-mirror values if direction points left
- {
- offset += mirror_offset;
- mirror_offset = offset - mirror_offset;
- offset -= mirror_offset;
-
- width += mirror_width;
- mirror_width = width - mirror_width;
- width -= mirror_width;
+ vector original_mirror_size = mirror_size;
+ float original_mirror_offset = mirror_offset;
+ if(doProject) {
+ if(mirror_size.x > 0) mirror_size.x = StrafeHUD_projectWidth(mirror_offset, mirror_size.x, range);
+ mirror_offset = StrafeHUD_projectOffset(mirror_offset, range);
}
- size.x = width;
- mirror_size.x = mirror_width;
-
switch(type)
{
default:
- case 0: // no styling (drawfill)
+ 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 1: // progress bar style
+ 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 2: // gradient style (types: 1 = left, 2 = right, 3 = both)
- StrafeHUD_drawGradient(color, autocvar_hud_panel_strafehud_bar_neutral_color, mirror_size, original_width, mirror_offset, alpha, width + (mirror_offset == 0 ? hiddencolor_width : 0), gradientType);
- StrafeHUD_drawGradient(color, autocvar_hud_panel_strafehud_bar_neutral_color, size, original_width, offset, alpha, (offset == 0 ? hiddencolor_width : 0), gradientType);
+ case STRAFEHUD_STYLE_GRADIENT: // gradient style (types: 1 = left, 2 = right, 3 = both)
+ // determine whether the gradient starts in the mirrored or the non-mirrored area
+ int gradient_start;
+ float gradient_offset, gradient_mirror_offset;
+
+ if(offset == 0 && mirror_offset == 0) gradient_start = width > mirror_width ? 2 : 1;
+ else if(offset == 0) gradient_start = 2;
+ else if(mirror_offset == 0) gradient_start = 1;
+ else gradient_start = 0;
+
+ switch(gradient_start)
+ {
+ default:
+ case 0: // no offset required
+ gradient_offset = gradient_mirror_offset = 0;
+ break;
+ case 1: // offset starts in non-mirrored area, mirrored area requires offset
+ gradient_offset = 0;
+ gradient_mirror_offset = original_width - (mirror_width + overflow_mirror_width);
+ break;
+ case 2: // offset starts in mirrored area, non-mirrored area requires offset
+ gradient_offset = original_width - (width + overflow_width);
+ gradient_mirror_offset = 0;
+ }
+
+ StrafeHUD_drawGradient(color, autocvar_hud_panel_strafehud_bar_neutral_color, original_mirror_size, original_width, original_mirror_offset, alpha, gradient_mirror_offset, gradientType, doProject, range);
+ StrafeHUD_drawGradient(color, autocvar_hud_panel_strafehud_bar_neutral_color, original_size, original_width, original_offset, alpha, gradient_offset, gradientType, doProject, range);
}
}
return mixedColor;
}
-void StrafeHUD_drawGradient(vector color1, vector color2, vector size, float original_width, float offset, float alpha, float gradientOffset, int gradientType)
+void StrafeHUD_drawGradient(vector color1, vector color2, vector size, float original_width, float offset, float alpha, float gradientOffset, int gradientType, bool doProject, float range)
{
float color_ratio, alpha1, alpha2;
- vector gradient_size = size;
+ vector segment_size = size;
alpha1 = bound(0, alpha, 1);
alpha2 = bound(0, autocvar_hud_panel_strafehud_bar_neutral_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;
- gradient_size.x = size.x - i < 1 ? size.x - i : 1;
- ratio = (i + gradientOffset) / original_width * (gradientType == 3 ? 2 : 1);
+ float ratio, alpha_ratio, combine_ratio1, combine_ratio2, segment_offset;
+ 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
+ segment_offset = offset + i;
+ ratio = (i + segment_size.x/2 + gradientOffset) / original_width * (gradientType == STRAFEHUD_GRADIENT_BOTH ? 2 : 1);
+ if(doProject)
+ {
+ segment_size.x = StrafeHUD_projectWidth(segment_offset, segment_size.x, range);
+ segment_offset = StrafeHUD_projectOffset(segment_offset, range);
+ }
if(ratio > 1) ratio = 2 - ratio;
- if(gradientType != 2) ratio = 1 - 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), gradient_size, StrafeHUD_mixColors(color1, color2, ratio), alpha_ratio, DRAWFLAG_NORMAL);
+ if(alpha_ratio > 0) drawfill(panel_pos + eX * segment_offset, segment_size, StrafeHUD_mixColors(color1, color2, ratio), alpha_ratio, DRAWFLAG_NORMAL);
+ }
+}
+
+// draw the strafe arrows (inspired by drawspritearrow() in common/mutators/mutator/waypoints/waypointsprites.qc)
+void StrafeHUD_drawStrafeArrow(vector origin, float size, vector color, float alpha, bool flipped)
+{
+ origin = HUD_Shift(origin);
+ size = HUD_ScaleX(size);
+ if(flipped) origin -= size*eY;
+ R_BeginPolygon("", DRAWFLAG_NORMAL, true);
+ R_PolygonVertex(origin + (flipped ? size*eY : '0 0 0') , '0 0 0', color, alpha);
+ R_PolygonVertex(origin + (flipped ? '0 0 0' : size*eY) - size*eX, '0 0 0', color, alpha);
+ R_PolygonVertex(origin + (flipped ? '0 0 0' : size*eY) + size*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
+ 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;
}
// 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;
- }
+ 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 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"));
- }
+ 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"));
+ }
}
#pragma once
#include "../panel.qh"
-int autocvar_hud_panel_strafehud = 3;
-bool autocvar__hud_panel_strafehud_demo = false;
-bool autocvar_hud_panel_strafehud_dynamichud = true;
-int autocvar_hud_panel_strafehud_mode = 0;
-float autocvar_hud_panel_strafehud_range = 0;
-int autocvar_hud_panel_strafehud_style = 1;
-int autocvar_hud_panel_strafehud_unit = 1;
-bool autocvar_hud_panel_strafehud_unit_show = true;
-vector autocvar_hud_panel_strafehud_bar_neutral_color = '1 1 1';
-float autocvar_hud_panel_strafehud_bar_neutral_alpha = 0.3;
-vector autocvar_hud_panel_strafehud_bar_accel_color = '0 1 0';
-float autocvar_hud_panel_strafehud_bar_accel_alpha = 0.3;
-vector autocvar_hud_panel_strafehud_bar_overturn_color = '1 0 1';
-float autocvar_hud_panel_strafehud_bar_overturn_alpha = 0.3;
-float autocvar_hud_panel_strafehud_angle_alpha = 0.8;
-float autocvar_hud_panel_strafehud_angle_height = 1.5;
-float autocvar_hud_panel_strafehud_angle_width = 0.005;
-vector autocvar_hud_panel_strafehud_angle_neutral_color = '1 1 0';
-vector autocvar_hud_panel_strafehud_angle_accel_color = '0 1 1';
-vector autocvar_hud_panel_strafehud_angle_overturn_color = '1 0 1';
-float autocvar_hud_panel_strafehud_switch_minspeed = -1;
-vector autocvar_hud_panel_strafehud_switch_active_color = '0 1 0';
-float autocvar_hud_panel_strafehud_switch_active_alpha = 1;
-vector autocvar_hud_panel_strafehud_switch_inactive_color = '1 1 0';
-float autocvar_hud_panel_strafehud_switch_inactive_alpha = 1;
-float autocvar_hud_panel_strafehud_switch_width = 0.0075;
-vector autocvar_hud_panel_strafehud_direction_color = '0 0.5 1';
-float autocvar_hud_panel_strafehud_direction_alpha = 1;
-float autocvar_hud_panel_strafehud_direction_width = 0.25;
-float autocvar_hud_panel_strafehud_direction_length = 0.02;
-float autocvar_hud_panel_strafehud_slickdetector_range = 0;
-int autocvar_hud_panel_strafehud_slickdetector_granularity = 2;
-vector autocvar_hud_panel_strafehud_slickdetector_color = '0 1 1';
-float autocvar_hud_panel_strafehud_slickdetector_alpha = 0.5;
-float autocvar_hud_panel_strafehud_slickdetector_height = 0.125;
-float autocvar_hud_panel_strafehud_startspeed_fade = 0;
-vector autocvar_hud_panel_strafehud_startspeed_color = '1 0.75 0';
-float autocvar_hud_panel_strafehud_startspeed_size = 1.5;
-float autocvar_hud_panel_strafehud_jumpheight_fade = 0;
-float autocvar_hud_panel_strafehud_jumpheight_min = 50;
-vector autocvar_hud_panel_strafehud_jumpheight_color = '0 1 0.75';
-float autocvar_hud_panel_strafehud_jumpheight_size = 1.5;
-float autocvar_hud_panel_strafehud_timeout_air = 0.1;
-float autocvar_hud_panel_strafehud_timeout_ground = 0.03333333;
-float autocvar_hud_panel_strafehud_timeout_turn = 0.1;
-float autocvar_hud_panel_strafehud_timeout_direction = 0.5;
-float autocvar_hud_panel_strafehud_antiflicker_angle = 0.01;
-float autocvar_hud_panel_strafehud_antiflicker_speed = 0.0001;
-
-void HUD_Panel_DrawStrafeHUD(float, float, vector, float, int, int);
+AUTOCVAR_SAVE(hud_panel_strafehud, int, 3, "enable this panel, 1 = show if not observing, 2 = show always, 3 = show only in race/cts if not observing");
+AUTOCVAR_SAVE(_hud_panel_strafehud_demo, bool, false, "strafehud changes angle during configure");
+AUTOCVAR_SAVE(hud_panel_strafehud_dynamichud, bool, true, "apply the dynamic hud effects to this panel");
+AUTOCVAR_SAVE(hud_panel_strafehud_mode, int, 0, "strafehud mode which controls whether the strafehud is centered at \"0\" = view angle, \"1\" = velocity angle");
+AUTOCVAR_SAVE(hud_panel_strafehud_range, float, 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)");
+AUTOCVAR_SAVE(hud_panel_strafehud_style, int, 2, "\"0\" = no styling, \"1\" = progress bar style for the strafe bar, \"2\" = gradient for the strafe bar");
+AUTOCVAR_SAVE(hud_panel_strafehud_unit, int, 1, "speed unit (1 = qu/s, 2 = m/s, 3 = km/h, 4 = mph, 5 = knots), length unit (1 = qu, 2 = m, 3 = km, 4 = mi, 5 = nmi)");
+AUTOCVAR_SAVE(hud_panel_strafehud_unit_show, bool, true, "show units");
+AUTOCVAR_SAVE(hud_panel_strafehud_uncapped, bool, false, "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)");
+AUTOCVAR_SAVE(hud_panel_strafehud_bar_preaccel, bool, true, "set to \"1\" to extend the acceleration zone by the strafe meter zone before full acceleration can be achieved");
+AUTOCVAR_SAVE(hud_panel_strafehud_bar_neutral_color, vector, '1 1 1', "color of the strafe meter neutral zone");
+AUTOCVAR_SAVE(hud_panel_strafehud_bar_neutral_alpha, float, 0.1, "opacity of the strafe meter neutral zone");
+AUTOCVAR_SAVE(hud_panel_strafehud_bar_accel_color, vector, '0 1 0', "color of the strafe meter acceleration zone");
+AUTOCVAR_SAVE(hud_panel_strafehud_bar_accel_alpha, float, 0.5, "opacity of the strafe meter acceleration zone");
+AUTOCVAR_SAVE(hud_panel_strafehud_bar_overturn_color, vector, '1 0 1', "color of the strafe meter overturn zone");
+AUTOCVAR_SAVE(hud_panel_strafehud_bar_overturn_alpha, float, 0.5, "opacity of the strafe meter overturn zone");
+AUTOCVAR_SAVE(hud_panel_strafehud_angle_style, int, 0, "set the angle indicator style: 0 = none, 1 = solid line, 2 = dashed line");
+AUTOCVAR_SAVE(hud_panel_strafehud_angle_dashes, int, 4, "determines the amount of dashes if the angle indicator uses a dashed line");
+AUTOCVAR_SAVE(hud_panel_strafehud_angle_alpha, float, 0.8, "opacity of the indicator showing the player's current angle");
+AUTOCVAR_SAVE(hud_panel_strafehud_angle_height, float, 1, "height of the indicator showing the player's current angle (relative to the panel height)");
+AUTOCVAR_SAVE(hud_panel_strafehud_angle_width, float, 0.001, "width of the indicator showing the player's current angle (relative to the panel width)");
+AUTOCVAR_SAVE(hud_panel_strafehud_angle_neutral_color, vector, '1 1 0', "color of the indicator showing the player's current angle if it is within the neutral zone");
+AUTOCVAR_SAVE(hud_panel_strafehud_angle_accel_color, vector, '0 1 1', "color of the indicator showing the player's current angle if it is within the acceleration zone");
+AUTOCVAR_SAVE(hud_panel_strafehud_angle_overturn_color, vector, '1 0 1', "color of the indicator showing the player's current angle if it is within the overturn zone");
+AUTOCVAR_SAVE(hud_panel_strafehud_angle_arrow, int, 1, "set the angle indicator's arrow style: 0 = none, 1 = top, 2 = bottom, 3 = both");
+AUTOCVAR_SAVE(hud_panel_strafehud_angle_arrow_size, float, 0.5, "size of the arrow (relative to the panel height)");
+AUTOCVAR_SAVE(hud_panel_strafehud_bestangle, bool, true, "set to \"1\" to enable a ghost angle indicator showing the best angle to gain maximum acceleration");
+AUTOCVAR_SAVE(hud_panel_strafehud_bestangle_color, vector, '1 1 1', "color of the indicator showing the best angle to gain maximum acceleration");
+AUTOCVAR_SAVE(hud_panel_strafehud_bestangle_alpha, float, 0.5, "opacity of the indicator showing the best angle to gain maximum acceleration");
+AUTOCVAR_SAVE(hud_panel_strafehud_switch, bool, true, "set to \"1\" to enable the switch indicator showing the angle to move to when switching sides");
+AUTOCVAR_SAVE(hud_panel_strafehud_switch_minspeed, float, -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)");
+AUTOCVAR_SAVE(hud_panel_strafehud_switch_color, vector, '1 1 0', "color of the switch indicator");
+AUTOCVAR_SAVE(hud_panel_strafehud_switch_alpha, float, 1, "opacity of the switch indicator");
+AUTOCVAR_SAVE(hud_panel_strafehud_switch_width, float, 0.003, "width of the strafe angle indicator(s) (relative to the strafe bar width)");
+AUTOCVAR_SAVE(hud_panel_strafehud_direction, bool, false, "set to \"1\" to enable the direction caps to see in which direction you are currently strafing");
+AUTOCVAR_SAVE(hud_panel_strafehud_direction_color, vector, '0 0.5 1', "color of the direction caps which indicate the direction the player is currently strafing towards");
+AUTOCVAR_SAVE(hud_panel_strafehud_direction_alpha, float, 1, "opacity of the direction caps which indicate the direction the player is currently strafing towards");
+AUTOCVAR_SAVE(hud_panel_strafehud_direction_width, float, 0.25, "stroke width of the direction caps which indicate the direction the player is currently strafing towards (relative to the panel height)");
+AUTOCVAR_SAVE(hud_panel_strafehud_direction_length, float, 0.02, "length of the horizontal component of the direction caps which indicate the direction the player is currently strafing towards (relative to the panel width)");
+AUTOCVAR_SAVE(hud_panel_strafehud_slickdetector, bool, true, "set to \"1\" to enable the slick detector which notifies you if there is slick near you");
+AUTOCVAR_SAVE(hud_panel_strafehud_slickdetector_range, float, 200, "range of the slick detector in qu");
+AUTOCVAR_SAVE(hud_panel_strafehud_slickdetector_granularity, int, 1, "value from 0 to 4 which defines how exact the search for slick should be, higher values may yield better results but require more computation");
+AUTOCVAR_SAVE(hud_panel_strafehud_slickdetector_color, vector, '0 1 1', "color of the slick detector indicator");
+AUTOCVAR_SAVE(hud_panel_strafehud_slickdetector_alpha, float, 0.5, "opacity of the slick detector indicator");
+AUTOCVAR_SAVE(hud_panel_strafehud_slickdetector_height, float, 0.125, "height of the slick detector indicator (relative to the panel height)");
+AUTOCVAR_SAVE(hud_panel_strafehud_startspeed, bool, true, "set to \"1\" to enable the start speed indicator which shows you the speed you had while passing the start trigger of a race map");
+AUTOCVAR_SAVE(hud_panel_strafehud_startspeed_fade, float, 4, "fade time (in seconds) of the start speed text");
+AUTOCVAR_SAVE(hud_panel_strafehud_startspeed_color, vector, '1 0.75 0', "color of the start speed text");
+AUTOCVAR_SAVE(hud_panel_strafehud_startspeed_size, float, 1.5, "size of the start speed text (relative to the panel height)");
+AUTOCVAR_SAVE(hud_panel_strafehud_jumpheight, bool, false, "set to \"1\" to enable the jump height indicator which tells you how high you jumped");
+AUTOCVAR_SAVE(hud_panel_strafehud_jumpheight_fade, float, 4, "fade time (in seconds) of the jump height text");
+AUTOCVAR_SAVE(hud_panel_strafehud_jumpheight_min, float, 50, "minimum jump height to display in the selected unit");
+AUTOCVAR_SAVE(hud_panel_strafehud_jumpheight_color, vector, '0 1 0.75', "color of the jump height text");
+AUTOCVAR_SAVE(hud_panel_strafehud_jumpheight_size, float, 1.5, "size of the jump height text (relative to the panel height)");
+AUTOCVAR_SAVE(hud_panel_strafehud_timeout_ground, float, 0.1, "time (in seconds) after take off before changing to air strafe physics when not jumping (visually more consistent hud while on slick downwards ramps)");
+AUTOCVAR_SAVE(hud_panel_strafehud_timeout_turn, float, 0.1, "time (in seconds) after releasing the strafe keys before changing mode (visually more consistent hud while switching between left/right strafe turning)");
+AUTOCVAR_SAVE(hud_panel_strafehud_antiflicker_angle, float, 0.01, "how many degrees from 0° to 180° the hud ignores if it could cause visual disturbances otherwise (and to counter rounding errors)");
+AUTOCVAR_SAVE(hud_panel_strafehud_fps_update, float, 0.5, "update interval (in seconds) of the frametime to calculate the optimal angle, smaller values may cause flickering");
+AUTOCVAR_SAVE(hud_panel_strafehud_sonar, bool, false, "set to \"1\" to enable the strafe sonar");
+AUTOCVAR_SAVE(hud_panel_strafehud_sonar_audio, string, "misc/talk", "audio to play for sonar");
+AUTOCVAR_SAVE(hud_panel_strafehud_sonar_start, float, 0.5, "how optimal from 0 to 1 your strafing angle has to be for the strafe sonar to activate");
+AUTOCVAR_SAVE(hud_panel_strafehud_sonar_interval_start, float, 0.333333, "strafe sonar sound interval in seconds");
+AUTOCVAR_SAVE(hud_panel_strafehud_sonar_interval_range, float, -0.222222, "dynamic sound interval range in seconds of the strafe sonar as you approach the optimal angle");
+AUTOCVAR_SAVE(hud_panel_strafehud_sonar_interval_exponent, float, 1, "exponent of the dynamic sound interval range of the strafe sonar");
+AUTOCVAR_SAVE(hud_panel_strafehud_sonar_volume_start, float, 0.333333, "sound volume of the strafe sonar");
+AUTOCVAR_SAVE(hud_panel_strafehud_sonar_volume_range, float, 0.666666, "dynamic volume range of the strafe sonar as you approach the optimal angle");
+AUTOCVAR_SAVE(hud_panel_strafehud_sonar_volume_exponent, float, 1, "exponent of the dynamic volume range of the strafe sonar");
+AUTOCVAR_SAVE(hud_panel_strafehud_sonar_pitch_start, float, 0.9, "playback speed of the strafe sonar");
+AUTOCVAR_SAVE(hud_panel_strafehud_sonar_pitch_range, float, 0.1, "dynamic playback speed range of the strafe sonar as you approach the optimal angle");
+AUTOCVAR_SAVE(hud_panel_strafehud_sonar_pitch_exponent, float, 1, "exponent of the dynamic playback speed range of the strafe sonar");
+AUTOCVAR_SAVE(hud_panel_strafehud_vangle, bool, false, "set to \"1\" to enable the vertical angle indicator");
+AUTOCVAR_SAVE(hud_panel_strafehud_vangle_color, vector, '0.75 0.75 0.75', "color of the vertical angle text");
+AUTOCVAR_SAVE(hud_panel_strafehud_vangle_size, float, 1, "size of the vertical angle text (relative to the panel height)");
+AUTOCVAR_SAVE(hud_panel_strafehud_projection, int, 0, "strafehud projection mode, \"0\" = linear, \"1\" = perspective, \"2\" = panoramic");
+
+void HUD_Panel_DrawStrafeHUD(float, float, float, vector, float, int, int, bool, float);
vector StrafeHUD_mixColors(vector, vector, float);
-void StrafeHUD_drawGradient(vector, vector, vector, float, float, float, float, int);
+void StrafeHUD_drawGradient(vector, vector, vector, float, float, float, float, int, bool, float);
float GetLengthUnitFactor(int);
string GetLengthUnit(int);
+void StrafeHUD_drawStrafeArrow(vector, float, vector, float, bool);
+bool StrafeHUD_drawTextIndicator(string, float, vector, float, float, float, int);
+float StrafeHUD_projectOffset(float, float);
+float StrafeHUD_projectWidth(float, float, float);
+
+const int STRAFEHUD_MODE_VIEW_CENTERED = 0;
+const int STRAFEHUD_MODE_VELOCITY_CENTERED = 1;
+
+const int STRAFEHUD_DIRECTION_NONE = 0;
+const int STRAFEHUD_DIRECTION_LEFT = 1;
+const int STRAFEHUD_DIRECTION_RIGHT = 2;
+
+const int STRAFEHUD_KEYS_NONE = 0;
+const int STRAFEHUD_KEYS_FORWARD = 1;
+const int STRAFEHUD_KEYS_BACKWARD = 2;
+
+const int STRAFEHUD_STYLE_DRAWFILL = 0;
+const int STRAFEHUD_STYLE_PROGRESSBAR = 1;
+const int STRAFEHUD_STYLE_GRADIENT = 2;
+
+const int STRAFEHUD_GRADIENT_NONE = 0;
+const int STRAFEHUD_GRADIENT_LEFT = 1;
+const int STRAFEHUD_GRADIENT_RIGHT = 2;
+const int STRAFEHUD_GRADIENT_BOTH = 3;
+
+const int STRAFEHUD_INDICATOR_NONE = 0;
+const int STRAFEHUD_INDICATOR_SOLID = 1;
+const int STRAFEHUD_INDICATOR_DASHED = 2;
+
+const int STRAFEHUD_TEXT_TOP = 0;
+const int STRAFEHUD_TEXT_BOTTOM = 1;
+
+const int STRAFEHUD_PROJECTION_LINEAR = 0;
+const int STRAFEHUD_PROJECTION_PERSPECTIVE = 1;
+const int STRAFEHUD_PROJECTION_PANORAMIC = 2;
return;
}
+ case "rebuild": // rebuilds maplist to include available maps, useful after doing fs_rescan
+ {
+ cvar_set("g_maplist", MapInfo_ListAllowedMaps(MapInfo_CurrentGametype(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags()));
+ return;
+ }
+
case "remove": // scans maplist and only adds back whatever maps were not provided in argv(2)
{
if(argc == 3)
const int SPECIES_RESERVED = 15;
#ifdef GAMEQC
-const int RANKINGS_CNT = 99;
+const int RANKINGS_CNT = 256;
///////////////////////////
// keys pressed
ctf_captimerecord = cap_time;
db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
- write_recordmarker(player, flag.ctf_pickuptime, cap_time);
+ write_recordmarker(player, 1, flag.ctf_pickuptime, cap_time);
}
if(autocvar_g_ctf_leaderboard && !ctf_oneflag)
GameRules_score_enabled(false);
GameRules_scoring(0, 0, 0, {
if (g_race_qualifying) {
+ field(SP_CTS_STRAFE, "strafe", 0);
+ field(SP_CTS_STARTSPEED, "startspeed", 0);
+ field(SP_CTS_AVGSPEED, "avgspeed", 0);
+ field(SP_CTS_TOPSPEED, "topspeed", 0);
field(SP_RACE_FASTEST, "fastest", SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME);
} else {
field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
CS(player).movement_y = -M_SQRT1_2 * wishspeed;
}
}
+ player.strafe_efficiency_sum += calculate_strafe_efficiency(player, CS(player).movement, dt) * dt;
+ if(player.race_started)
+ {
+ float current_speed = vlen(vec2(player.velocity));
+ if(player.race_topspeed < current_speed)
+ {
+ player.race_topspeed = current_speed;
+ }
+ player.race_avgspeed_sum += current_speed * dt;
+ player.race_avgspeed_time += dt;
+ }
+ else
+ {
+ player.race_startspeed = player.race_avgspeed_sum = player.race_avgspeed_time = player.race_topspeed = 0;
+ }
}
MUTATOR_HOOKFUNCTION(cts, reset_map_global)
PlayerScore_Sort(race_place, 0, true, false);
FOREACH_CLIENT(true, {
+ it.strafe_efficiency_best = -2;
+ it.strafe_efficiency_sum = it.strafe_efficiency_time = 0;
+ PlayerScore_Set(it, SP_CTS_STRAFE, -20000);
+ it.race_startspeed_best = it.race_avgspeed_best = it.race_topspeed_best = -1;
+ it.race_startspeed = it.race_avgspeed_sum = it.race_avgspeed_time = it.race_topspeed = 0;
+ PlayerScore_Set(it, SP_CTS_STARTSPEED, -1);
+ PlayerScore_Set(it, SP_CTS_AVGSPEED, -1);
+ PlayerScore_Set(it, SP_CTS_TOPSPEED, -1);
+
if(it.race_place)
{
s = GameRules_scoring_add(it, RACE_FASTEST, 0);
race_PreparePlayer(player);
player.race_checkpoint = -1;
+ player.strafe_efficiency_best = -2;
+ player.strafe_efficiency_sum = player.strafe_efficiency_time = 0;
+ PlayerScore_Set(player, SP_CTS_STRAFE, -20000);
+ player.race_startspeed_best = player.race_avgspeed_best = player.race_topspeed_best = -1;
+ player.race_startspeed = player.race_avgspeed_sum = player.race_avgspeed_time = player.race_topspeed = 0;
+ PlayerScore_Set(player, SP_CTS_STARTSPEED, -1);
+ PlayerScore_Set(player, SP_CTS_AVGSPEED, -1);
+ PlayerScore_Set(player, SP_CTS_TOPSPEED, -1);
race_SendAll(player, false);
}
frag_target.respawn_flags |= RESPAWN_FORCE;
race_AbandonRaceCheck(frag_target);
+ frag_target.strafe_efficiency_sum = frag_target.strafe_efficiency_time = 0;
+ frag_target.race_startspeed = frag_target.race_avgspeed_sum = frag_target.race_avgspeed_time = frag_target.race_topspeed = 0;
+
if(autocvar_g_cts_removeprojectiles)
{
IL_EACH(g_projectiles, it.owner == frag_target && (it.flags & FL_PROJECTILE),
#include <common/mapobjects/target/spawn.qc>
#include <common/mapobjects/target/spawnpoint.qc>
#include <common/mapobjects/target/speaker.qc>
+#include <common/mapobjects/target/speed.qc>
#include <common/mapobjects/target/voicescript.qc>
#include <common/mapobjects/target/spawn.qh>
#include <common/mapobjects/target/spawnpoint.qh>
#include <common/mapobjects/target/speaker.qh>
+#include <common/mapobjects/target/speed.qh>
#include <common/mapobjects/target/voicescript.qh>
--- /dev/null
+#include "speed.qh"
+
+#define XYZ_ARRAY(name) float name[3]
+#define ARRAY_AS_VECTOR(a) ((a)[0] * '1 0 0' + (a)[1] * '0 1 0' + (a)[2] * '0 0 1')
+#define VECTOR_TO_ARRAY(a, e) { vector v = (e); (a)[0] = v.x; (a)[1] = v.y; (a)[2] = v.z; }
+#define FOR_XYZ(idx) for(int idx = 0; idx < 3; ++idx)
+vector target_speed_calculatevelocity(entity this, float speed, entity pushed_entity)
+{
+ bool is_percentage = boolean(this.spawnflags & SPEED_PERCENTAGE);
+ bool is_add = boolean(this.spawnflags & SPEED_ADD);
+ bool is_launcher = boolean(this.spawnflags & SPEED_LAUNCHER);
+
+ bool is_positive[3];
+ is_positive[0] = boolean(this.spawnflags & SPEED_POSITIVE_X);
+ is_positive[1] = boolean(this.spawnflags & SPEED_POSITIVE_Y);
+ is_positive[2] = boolean(this.spawnflags & SPEED_POSITIVE_Z);
+
+ bool is_negative[3];
+ is_negative[0] = boolean(this.spawnflags & SPEED_NEGATIVE_X);
+ is_negative[1] = boolean(this.spawnflags & SPEED_NEGATIVE_Y);
+ is_negative[2] = boolean(this.spawnflags & SPEED_NEGATIVE_Z);
+
+ if(!is_add)
+ {
+ speed = max(speed, 0); // speed cannot be negative except when subtracting
+ }
+
+ XYZ_ARRAY(pushvel);
+ VECTOR_TO_ARRAY(pushvel, pushed_entity.velocity);
+
+ FOR_XYZ(i)
+ {
+ if(is_launcher && is_positive[i] && is_negative[i])
+ {
+ is_positive[i] = is_negative[i] = false; // launcher can only be either positive or negative not both
+ }
+
+ if(!is_positive[i] && !is_negative[i])
+ {
+ pushvel[i] = 0; // ignore this direction
+ }
+ }
+
+ float oldspeed = vlen(ARRAY_AS_VECTOR(pushvel));
+
+ if(is_percentage)
+ {
+ speed = oldspeed * speed / 100; // the speed field is used to specify the percentage of the current speed
+ }
+
+ float launcherspeed = 0;
+
+ if(!STAT(Q3COMPAT, pushed_entity)) // no need to simulate this bug
+ {
+ launcherspeed += speed;
+ if(is_add) launcherspeed += oldspeed; // add the add speed in the same variable as it goes in the same direction
+ }
+
+ FOR_XYZ(i)
+ {
+ if(((pushvel[i] != 0) || is_launcher) && (is_positive[i] != is_negative[i]))
+ {
+ if(is_launcher)
+ {
+ pushvel[i] = 1; // every direction weighs the same amount on launchers, movedir does not matter
+ if(STAT(Q3COMPAT, pushed_entity))
+ {
+ launcherspeed += speed;
+ if(is_add) launcherspeed += oldspeed; // add the add speed in the same variable as it goes in the same direction
+ }
+ }
+
+ if(is_positive[i])
+ {
+ pushvel[i] = copysign(pushvel[i], 1);
+ }
+ else if(is_negative[i])
+ {
+ pushvel[i] = copysign(pushvel[i], -1);
+ }
+ }
+ }
+
+ XYZ_ARRAY(oldvel);
+ VECTOR_TO_ARRAY(oldvel, pushed_entity.velocity);
+
+ if(is_launcher)
+ {
+ VECTOR_TO_ARRAY(pushvel, normalize(ARRAY_AS_VECTOR(pushvel)) * fabs(launcherspeed)); // launcher will always launch you in the correct direction even if speed is negative, fabs() is correct
+ }
+ else
+ {
+ VECTOR_TO_ARRAY(pushvel, normalize(ARRAY_AS_VECTOR(pushvel)) * speed)
+
+ if(is_add)
+ {
+ VECTOR_TO_ARRAY(pushvel, ARRAY_AS_VECTOR(pushvel) + ARRAY_AS_VECTOR(oldvel));
+ }
+ }
+
+ FOR_XYZ(i)
+ {
+ if(!is_positive[i] && !is_negative[i])
+ {
+ pushvel[i] = oldvel[i]; // preserve unaffected directions
+ }
+ }
+
+ return ARRAY_AS_VECTOR(pushvel);
+}
+#undef XYZ_ARRAY
+#undef ARRAY_AS_VECTOR
+#undef VECTOR_TO_ARRAY
+#undef FOR_XYZ
+
+REGISTER_NET_LINKED(ENT_CLIENT_TARGET_SPEED)
+
+void target_speed_use(entity this, entity actor, entity trigger)
+{
+ if(this.active != ACTIVE_ACTIVE)
+ return;
+
+ actor.velocity = target_speed_calculatevelocity(this, this.speed, actor);
+}
+
+void target_speed_reset(entity this)
+{
+ this.active = ACTIVE_ACTIVE;
+}
+
+#ifdef SVQC
+void target_speed_link(entity this);
+
+/*
+ * ENTITY PARAMETERS:
+ *
+ * targetname: Activating trigger points to this.
+ * speed: Speed value to set (default: 100).
+ */
+spawnfunc(target_speed)
+{
+ this.active = ACTIVE_ACTIVE;
+ this.setactive = generic_netlinked_setactive;
+ this.use = target_speed_use;
+ this.reset = target_speed_reset;
+
+ // FIXME: zero and unset cannot be disambiguated in xonotic
+ //if (!this.speed)
+ // this.speed = 100;
+
+ target_speed_link(this);
+}
+
+bool target_speed_send(entity this, entity to, float sf)
+{
+ WriteHeader(MSG_ENTITY, ENT_CLIENT_TARGET_SPEED);
+
+ WriteInt24_t(MSG_ENTITY, this.spawnflags);
+ WriteByte(MSG_ENTITY, this.active);
+ WriteString(MSG_ENTITY, this.targetname);
+ WriteCoord(MSG_ENTITY, this.speed);
+
+ return true;
+}
+
+void target_speed_link(entity this)
+{
+ Net_LinkEntity(this, false, 0, target_speed_send);
+}
+
+#elif defined(CSQC)
+
+void target_speed_remove(entity this)
+{
+ strfree(this.targetname);
+}
+
+NET_HANDLE(ENT_CLIENT_TARGET_SPEED, bool isnew)
+{
+ this.spawnflags = ReadInt24_t();
+ this.active = ReadByte();
+ this.targetname = strzone(ReadString());
+ this.speed = ReadCoord();
+
+ this.use = target_speed_use;
+ this.entremove = target_speed_remove;
+
+ return true;
+}
+#endif
--- /dev/null
+#pragma once
+
+
+const int SPEED_PERCENTAGE = BIT(0);
+const int SPEED_ADD = BIT(1);
+const int SPEED_POSITIVE_X = BIT(2);
+const int SPEED_NEGATIVE_X = BIT(3);
+const int SPEED_POSITIVE_Y = BIT(4);
+const int SPEED_NEGATIVE_Y = BIT(5);
+const int SPEED_POSITIVE_Z = BIT(6);
+const int SPEED_NEGATIVE_Z = BIT(7);
+const int SPEED_LAUNCHER = BIT(8);
if(vdist(player.velocity, >, e.speed))
player.velocity = normalize(player.velocity) * max(0, e.speed);
- if(STAT(TELEPORT_MAXSPEED, player))
- if(vdist(player.velocity, >, STAT(TELEPORT_MAXSPEED, player)))
- player.velocity = normalize(player.velocity) * max(0, STAT(TELEPORT_MAXSPEED, player));
+ if(!(teleporter.classname == "trigger_teleport" && teleporter.spawnflags & TELEPORT_KEEP_SPEED))
+ if(STAT(TELEPORT_MAXSPEED, player))
+ if(vdist(player.velocity, >, STAT(TELEPORT_MAXSPEED, player)))
+ player.velocity = normalize(player.velocity) * max(0, STAT(TELEPORT_MAXSPEED, player));
locout = e.origin + '0 0 1' * (1 - player.mins.z - 24);
#endif
REGISTER_NET_LINKED(ENT_CLIENT_TRIGGER_PUSH)
+REGISTER_NET_LINKED(ENT_CLIENT_TRIGGER_PUSH_VELOCITY)
REGISTER_NET_LINKED(ENT_CLIENT_TARGET_PUSH)
/*
return sdir * vs + '0 0 1' * vz;
}
-bool jumppad_push(entity this, entity targ)
+vector trigger_push_velocity_calculatevelocity(entity this, vector org, entity tgt, float speed, float count, entity pushed_entity, bool is_pushed)
+{
+ bool is_playerdir_xy = boolean(this.spawnflags & PUSH_VELOCITY_PLAYERDIR_XY);
+ bool is_add_xy = boolean(this.spawnflags & PUSH_VELOCITY_ADD_XY);
+ bool is_playerdir_z = boolean(this.spawnflags & PUSH_VELOCITY_PLAYERDIR_Z);
+ bool is_add_z = boolean(this.spawnflags & PUSH_VELOCITY_ADD_Z);
+ bool is_bidirectional_xy = boolean(this.spawnflags & PUSH_VELOCITY_BIDIRECTIONAL_XY);
+ bool is_bidirectional_z = boolean(this.spawnflags & PUSH_VELOCITY_BIDIRECTIONAL_Z);
+ bool is_clamp_negative_adds = boolean(this.spawnflags & PUSH_VELOCITY_CLAMP_NEGATIVE_ADDS);
+
+ vector sdir = normalize(vec2(pushed_entity.velocity));
+ float zdir = pushed_entity.velocity.z;
+ if(zdir != 0) zdir = copysign(1, zdir);
+
+ vector vs_tgt = '0 0 0';
+ float vz_tgt = 0;
+ if (!is_playerdir_xy || !is_playerdir_z)
+ {
+ vector vel_tgt = trigger_push_calculatevelocity(org, tgt, 0, pushed_entity);
+ vs_tgt = vec2(vel_tgt);
+ vz_tgt = vel_tgt.z;
+
+ // bidirectional jump pads do not play nicely with xonotic's jump pad targets
+ if (is_bidirectional_xy)
+ {
+ if (normalize(vs_tgt) * sdir < 0)
+ {
+ vs_tgt *= -1;
+ }
+ }
+
+ if (is_bidirectional_z)
+ {
+ if (signbit(vz_tgt) != signbit(zdir))
+ {
+ vz_tgt *= -1;
+ }
+ }
+ }
+
+ vector vs;
+ if (is_playerdir_xy)
+ {
+ vs = sdir * speed;
+ }
+ else
+ {
+ vs = vs_tgt;
+ }
+
+ float vz;
+ if (is_playerdir_z)
+ {
+ vz = zdir * count;
+ }
+ else
+ {
+ vz = vz_tgt;
+ }
+
+ if (is_add_xy)
+ {
+ vector vs_add = vec2(pushed_entity.velocity);
+ if (is_pushed)
+ {
+ vs = vs_add;
+ }
+ else
+ {
+ vs += vs_add;
+
+ if (is_clamp_negative_adds)
+ {
+ if ((normalize(vs) * sdir) < 0)
+ {
+ vs = '0 0 0';
+ }
+ }
+ }
+ }
+
+ if (is_add_z)
+ {
+ float vz_add = pushed_entity.velocity.z;
+ if (is_pushed)
+ {
+ vz = vz_add;
+ }
+ else
+ {
+ vz += vz_add;
+
+ if (is_clamp_negative_adds)
+ {
+ if (signbit(vz) != signbit(zdir))
+ {
+ vz = 0;
+ }
+ }
+ }
+ }
+
+ return vs + '0 0 1' * vz;
+}
+
+// TODO: move this check to player/projectile physics?
+void check_pushed(entity this) // first jump pad to think thinks for every jump pad
+{
+ IL_EACH(are_pushed, true,
+ {
+ bool empty = true;
+ for(int i = 0; i < MAX_PUSHED; ++i)
+ {
+ if(!it.has_pushed[i]) continue;
+ if(WarpZoneLib_ExactTrigger_Touch(it.has_pushed[i], it))
+ {
+ it.has_pushed[i] = NULL;
+ continue;
+ }
+ empty = false;
+ }
+ if(empty)
+ {
+ IL_REMOVE(are_pushed, it);
+ }
+ });
+
+ if(!IL_EMPTY(are_pushed))
+ {
+ this.nextthink = time;
+ }
+}
+
+bool jumppad_push(entity this, entity targ, bool is_velocity_pad)
{
if (!isPushable(targ))
return false;
vector org = targ.origin;
- if(STAT(Q3COMPAT))
+ if(STAT(Q3COMPAT, targ) || this.spawnflags & PUSH_STATIC)
{
- org.z += targ.mins_z;
- org.z += 1; // off by 1!
+ org = (this.absmin + this.absmax) * 0.5;
+ }
+
+ bool is_pushed = false;
+ if(is_velocity_pad)
+ {
+ for(int i = 0; i < MAX_PUSHED; ++i)
+ {
+ if(this == targ.has_pushed[i])
+ {
+ is_pushed = true;
+ break;
+ }
+ }
+
+ if(!is_pushed) // remember velocity jump pads
+ {
+ bool limit_reached = true;
+ for(int i = 0; i < MAX_PUSHED; ++i)
+ {
+ if(targ.has_pushed[i]) continue;
+ limit_reached = false;
+ targ.has_pushed[i] = this; // may be briefly out of sync between client and server if client prediction is toggled
+ break;
+ }
+ if(limit_reached)
+ {
+ return false; // too many overlapping jump pads
+ }
+ IL_PUSH(are_pushed, targ);
+ this.nextthink = time;
+ }
}
if(this.enemy)
{
- targ.velocity = trigger_push_calculatevelocity(org, this.enemy, this.height, targ);
+ if(!is_velocity_pad)
+ {
+ targ.velocity = trigger_push_calculatevelocity(org, this.enemy, this.height, targ);
+ }
+ else
+ {
+ targ.velocity = trigger_push_velocity_calculatevelocity(this, org, this.enemy, this.speed, this.count, targ, is_pushed);
+ }
}
else if(this.target && this.target != "")
{
else
RandomSelection_AddEnt(e, 1, 1);
}
- targ.velocity = trigger_push_calculatevelocity(org, RandomSelection_chosen_ent, this.height, targ);
+ if(!is_velocity_pad)
+ {
+ targ.velocity = trigger_push_calculatevelocity(org, RandomSelection_chosen_ent, this.height, targ);
+ }
+ else
+ {
+ targ.velocity = trigger_push_velocity_calculatevelocity(this, org, RandomSelection_chosen_ent, this.speed, this.count, targ, is_pushed);
+ }
}
else
{
- targ.velocity = this.movedir;
+ if(!is_velocity_pad)
+ {
+ targ.velocity = this.movedir;
+ }
+ else
+ {
+#ifdef SVQC
+ objerror (this, "Jumppad with no target");
+#endif
+ return false;
+ }
}
- UNSET_ONGROUND(targ);
+ if(!is_velocity_pad) UNSET_ONGROUND(targ);
#ifdef CSQC
if (targ.flags & FL_PROJECTILE)
// prevent sound spam when a player hits the jumppad more than once
// or when a dead player gets stuck in the jumppad for some reason
- if(this.pushltime < time && !(IS_DEAD(targ) && targ.velocity == '0 0 0'))
+ if(!is_pushed && this.pushltime < time && !(IS_DEAD(targ) && targ.velocity == '0 0 0'))
{
// flash when activated
Send_Effect(EFFECT_JUMPPAD, targ.origin, targ.velocity, 1);
EXACTTRIGGER_TOUCH(this, toucher);
- noref bool success = jumppad_push(this, toucher);
+ noref bool success = jumppad_push(this, toucher, false);
#ifdef SVQC
if (success && (this.spawnflags & PUSH_ONCE))
#endif
}
+void trigger_push_velocity_touch(entity this, entity toucher)
+{
+ if (this.active == ACTIVE_NOT)
+ return;
+
+ if(this.team && DIFF_TEAM(this, toucher))
+ return;
+
+ EXACTTRIGGER_TOUCH(this, toucher);
+
+ noref bool success = jumppad_push(this, toucher, true);
+}
+
#ifdef SVQC
void trigger_push_link(entity this);
void trigger_push_updatelink(entity this);
return true;
}
+float trigger_push_velocity_send(entity this, entity to, float sf)
+{
+ WriteHeader(MSG_ENTITY, ENT_CLIENT_TRIGGER_PUSH_VELOCITY);
+
+ WriteByte(MSG_ENTITY, this.team);
+ WriteInt24_t(MSG_ENTITY, this.spawnflags);
+ WriteByte(MSG_ENTITY, this.active);
+ WriteCoord(MSG_ENTITY, this.speed);
+ WriteCoord(MSG_ENTITY, this.count);
+
+ trigger_common_write(this, true);
+
+ return true;
+}
+
void trigger_push_updatelink(entity this)
{
this.SendFlags |= SF_TRIGGER_INIT;
trigger_link(this, trigger_push_send);
}
+void trigger_push_velocity_link(entity this)
+{
+ trigger_link(this, trigger_push_velocity_send);
+}
+
/*
* ENTITY PARAMETERS:
*
InitializeEntity(this, trigger_push_findtarget, INITPRIO_FINDTARGET);
}
+/*
+ * ENTITY PARAMETERS:
+ *
+ * target: this points to the target_position to which the player will jump.
+ * speed: XY speed for player-directional velocity pads - either sets or adds to the player's horizontal velocity.
+ * count: Z speed for player-directional velocity pads - either sets or adds to the player's vertical velocity.
+ */
+spawnfunc(trigger_push_velocity)
+{
+ trigger_init(this);
+
+ this.active = ACTIVE_ACTIVE;
+ this.use = trigger_push_use;
+ settouch(this, trigger_push_velocity_touch);
+
+ // normal push setup
+ if (!this.noise)
+ this.noise = "misc/jumppad.wav";
+ precache_sound (this.noise);
+
+ trigger_push_velocity_link(this); // link it now
+
+ setthink(this, check_pushed);
+}
+
bool target_push_send(entity this, entity to, float sf)
{
if(trigger.classname == "trigger_push" || trigger == this)
return; // WTF, why is this a thing
- jumppad_push(this, actor);
+ jumppad_push(this, actor, false);
}
void target_push_link(entity this)
return true;
}
+NET_HANDLE(ENT_CLIENT_TRIGGER_PUSH_VELOCITY, bool isnew)
+{
+ int mytm = ReadByte(); if(mytm) { this.team = mytm - 1; }
+ this.spawnflags = ReadInt24_t();
+ this.active = ReadByte();
+ this.speed = ReadCoord();
+ this.count = ReadCoord();
+
+ trigger_common_read(this, true);
+
+ this.entremove = trigger_remove_generic;
+ this.solid = SOLID_TRIGGER;
+ settouch(this, trigger_push_velocity_touch);
+ this.move_time = time;
+ setthink(this, check_pushed);
+
+ return true;
+}
+
void target_push_remove(entity this)
{
// strfree(this.classname);
const int PUSH_ONCE = BIT(0); // legacy, deactivate with relay instead
const int PUSH_SILENT = BIT(1); // not used?
+const int PUSH_STATIC = BIT(12);
+
+const int PUSH_VELOCITY_PLAYERDIR_XY = BIT(0);
+const int PUSH_VELOCITY_ADD_XY = BIT(1);
+const int PUSH_VELOCITY_PLAYERDIR_Z = BIT(2);
+const int PUSH_VELOCITY_ADD_Z = BIT(3);
+const int PUSH_VELOCITY_BIDIRECTIONAL_XY = BIT(4);
+const int PUSH_VELOCITY_BIDIRECTIONAL_Z = BIT(5);
+const int PUSH_VELOCITY_CLAMP_NEGATIVE_ADDS = BIT(6);
IntrusiveList g_jumppads;
STATIC_INIT(g_jumppads) { g_jumppads = IL_NEW(); }
.bool istypefrag;
.float height;
+const int MAX_PUSHED = 16; // maximum amount of jump pads which are allowed to push simultaneously
+.entity has_pushed[MAX_PUSHED];
+IntrusiveList are_pushed;
+STATIC_INIT(are_pushed) { are_pushed = IL_NEW(); }
+
const int NUM_JUMPPADSUSED = 3;
.float jumppadcount;
.entity jumppadsused[NUM_JUMPPADSUSED];
void trigger_push_findtarget(entity this);
/*
- * ENTITY PARAMETERS:
+ * ENTITY PARAMETERS trigger_push:
*
* target: target of jump
* height: the absolute value is the height of the highest point of the jump
* values to target a point on the ceiling.
* movedir: if target is not set, this * speed * 10 is the velocity to be reached.
*/
+
+/*
+ * ENTITY PARAMETERS trigger_push_velocity:
+ *
+ * target: this points to the target_position to which the player will jump.
+ * speed: XY speed for player-directional velocity pads - either sets or adds to the player's horizontal velocity.
+ * count: Z speed for player-directional velocity pads - either sets or adds to the player's vertical velocity.
+ */
#ifdef SVQC
spawnfunc(trigger_push);
+spawnfunc(trigger_push_velocity);
spawnfunc(target_push);
spawnfunc(info_notnull);
if(IS_TURRET(player))
return false;
+
+ if((this.spawnflags & TELEPORT_SPECTATOR) && !IS_SPEC(player))
+ return false;
#elif defined(CSQC)
if(!IS_PLAYER(player))
return false;
#pragma once
+
+
+const int TELEPORT_SPECTATOR = BIT(0);
+const int TELEPORT_KEEP_SPEED = BIT(1);
{
// Secondary uses it's own refire timer if refire_type is 1.
actor.jump_interval = time + WEP_CVAR_SEC(okhmg, refire) * W_WeaponRateFactor(actor);
- BLASTER_SECONDARY_ATTACK(okhmg, actor, weaponentity);
+ makevectors(actor.v_angle);
+ W_Blaster_Attack(actor, weaponentity, WEP_BLASTER.m_id | HITTYPE_SECONDARY);
if ((actor.(weaponentity).wframe == WFRAME_IDLE) ||
(actor.(weaponentity).wframe == WFRAME_FIRE2))
{
{
return;
}
- BLASTER_SECONDARY_ATTACK(okhmg, actor, weaponentity);
+ makevectors(actor.v_angle);
+ W_Blaster_Attack(actor, weaponentity, WEP_BLASTER.m_id | HITTYPE_SECONDARY);
weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(okhmg, animtime), w_ready);
}
}
{
// Secondary uses it's own refire timer if refire_type is 1.
actor.jump_interval = time + WEP_CVAR_SEC(okmachinegun, refire) * W_WeaponRateFactor(actor);
- BLASTER_SECONDARY_ATTACK(okmachinegun, actor, weaponentity);
+ makevectors(actor.v_angle);
+ W_Blaster_Attack(actor, weaponentity, WEP_BLASTER.m_id | HITTYPE_SECONDARY);
if ((actor.(weaponentity).wframe == WFRAME_IDLE) ||
(actor.(weaponentity).wframe == WFRAME_FIRE2))
{
{
return;
}
- BLASTER_SECONDARY_ATTACK(okmachinegun, actor, weaponentity);
+ makevectors(actor.v_angle);
+ W_Blaster_Attack(actor, weaponentity, WEP_BLASTER.m_id | HITTYPE_SECONDARY);
weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(okmachinegun, animtime), w_ready);
}
}
{
// Secondary uses it's own refire timer if refire_type is 1.
actor.jump_interval = time + WEP_CVAR_SEC(oknex, refire) * W_WeaponRateFactor(actor);
- BLASTER_SECONDARY_ATTACK(oknex, actor, weaponentity);
+ makevectors(actor.v_angle);
+ W_Blaster_Attack(actor, weaponentity, WEP_BLASTER.m_id | HITTYPE_SECONDARY);
if ((actor.(weaponentity).wframe == WFRAME_IDLE) ||
(actor.(weaponentity).wframe == WFRAME_FIRE2))
{
{
return;
}
- BLASTER_SECONDARY_ATTACK(oknex, actor, weaponentity);
+ makevectors(actor.v_angle);
+ W_Blaster_Attack(actor, weaponentity, WEP_BLASTER.m_id | HITTYPE_SECONDARY);
weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(oknex, animtime), w_ready);
return;
}
{
// Secondary uses it's own refire timer if refire_type is 1.
actor.jump_interval = time + WEP_CVAR_SEC(okrpc, refire) * W_WeaponRateFactor(actor);
- BLASTER_SECONDARY_ATTACK(okrpc, actor, weaponentity);
+ makevectors(actor.v_angle);
+ W_Blaster_Attack(actor, weaponentity, WEP_BLASTER.m_id | HITTYPE_SECONDARY);
if ((actor.(weaponentity).wframe == WFRAME_IDLE) ||
(actor.(weaponentity).wframe == WFRAME_FIRE2))
{
{
return;
}
- BLASTER_SECONDARY_ATTACK(okrpc, actor, weaponentity);
+ makevectors(actor.v_angle);
+ W_Blaster_Attack(actor, weaponentity, WEP_BLASTER.m_id | HITTYPE_SECONDARY);
weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(okrpc, animtime), w_ready);
}
}
{
// Secondary uses it's own refire timer if refire_type is 1.
actor.jump_interval = time + WEP_CVAR_SEC(okshotgun, refire) * W_WeaponRateFactor(actor);
- BLASTER_SECONDARY_ATTACK(okshotgun, actor, weaponentity);
+ makevectors(actor.v_angle);
+ W_Blaster_Attack(actor, weaponentity, WEP_BLASTER.m_id | HITTYPE_SECONDARY);
if ((actor.(weaponentity).wframe == WFRAME_IDLE) ||
(actor.(weaponentity).wframe == WFRAME_FIRE2))
{
{
return;
}
- BLASTER_SECONDARY_ATTACK(okshotgun, actor, weaponentity);
+ makevectors(actor.v_angle);
+ W_Blaster_Attack(actor, weaponentity, WEP_BLASTER.m_id | HITTYPE_SECONDARY);
weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(okshotgun, animtime), w_ready);
}
}
void set_movetype(entity this, int mt)
{
this.move_movetype = mt;
- this.move_qcphysics = (mt != MOVETYPE_PHYSICS);
+ this.move_qcphysics = (mt != MOVETYPE_PHYSICS && !use_engine_physics);
if(!IL_CONTAINS(g_moveables, this))
IL_PUSH(g_moveables, this); // add it to the moveable entities list (even if it doesn't move!) logic: if an object never sets its movetype, we assume it never does anything notable
this.movetype = (this.move_qcphysics) ? MOVETYPE_QCENTITY : mt;
#ifdef SVQC
// undefined on client, engine cvar
bool autocvar_physics_ode;
+
+bool use_engine_physics; // debug option for testing legacy engine code
#endif
// water levels
REGISTER_SP(RACE_TIME);
REGISTER_SP(RACE_FASTEST);
+REGISTER_SP(CTS_STRAFE);
+REGISTER_SP(CTS_STARTSPEED);
+REGISTER_SP(CTS_AVGSPEED);
+REGISTER_SP(CTS_TOPSPEED);
+
REGISTER_SP(ASSAULT_OBJECTIVES);
REGISTER_SP(CTF_CAPS);
#endif
REGISTER_STAT(SLICK_APPLYGRAVITY, bool, autocvar_sv_slick_applygravity)
+#ifdef SVQC
+int autocvar_sv_q3compat_jumppads;
+#endif
REGISTER_STAT(Q3COMPAT, int, q3compat)
+REGISTER_STAT(Q3COMPAT_JUMPPADS, int, autocvar_sv_q3compat_jumppads)
#ifdef SVQC
#include "physics/movetypes/movetypes.qh"
PROJECTILE_TOUCH(this, toucher);
this.event_damage = func_null;
+ bool isprimary = !(this.projectiledeathtype & HITTYPE_SECONDARY);
RadiusDamageForSource(
this,
(this.origin + (this.mins + this.maxs) * 0.5),
this.velocity,
this.realowner,
- this.blaster_damage,
- this.blaster_edgedamage,
- this.blaster_radius,
+ WEP_CVAR_BOTH(blaster, isprimary, damage),
+ WEP_CVAR_BOTH(blaster, isprimary, edgedamage),
+ WEP_CVAR_BOTH(blaster, isprimary, radius),
NULL,
NULL,
false,
- this.blaster_force,
- this.blaster_force_zscale,
+ WEP_CVAR_BOTH(blaster, isprimary, force),
+ WEP_CVAR_BOTH(blaster, isprimary, force_zscale),
this.projectiledeathtype,
this.weaponentity_fld,
toucher
{
set_movetype(this, MOVETYPE_FLY);
setthink(this, SUB_Remove);
- this.nextthink = time + this.blaster_lifetime;
+ bool isprimary = !(this.projectiledeathtype & HITTYPE_SECONDARY);
+ this.nextthink = time + WEP_CVAR_BOTH(blaster, isprimary, lifetime);
CSQCProjectile(this, true, PROJECTILE_BLASTER, true);
}
void W_Blaster_Attack(
entity actor,
.entity weaponentity,
- float atk_deathtype,
- float atk_shotangle,
- float atk_damage,
- float atk_edgedamage,
- float atk_radius,
- float atk_force,
- float atk_force_zscale,
- float atk_speed,
- float atk_spread,
- float atk_delay,
- float atk_lifetime)
+ float atk_deathtype)
{
+ bool isprimary = !(atk_deathtype & HITTYPE_SECONDARY);
+ float atk_shotangle = WEP_CVAR_BOTH(blaster, isprimary, shotangle);
+ float atk_damage = WEP_CVAR_BOTH(blaster, isprimary, damage);
vector s_forward = v_forward * cos(atk_shotangle * DEG2RAD) + v_up * sin(atk_shotangle * DEG2RAD);
W_SetupShot_Dir(actor, weaponentity, s_forward, false, 3, SND_LASERGUN_FIRE, CH_WEAPON_B, atk_damage, atk_deathtype);
missile.bot_dodgerating = atk_damage;
PROJECTILE_MAKETRIGGER(missile);
- missile.blaster_damage = atk_damage;
- missile.blaster_edgedamage = atk_edgedamage;
- missile.blaster_radius = atk_radius;
- missile.blaster_force = atk_force;
- missile.blaster_force_zscale = atk_force_zscale;
- missile.blaster_lifetime = atk_lifetime;
-
setorigin(missile, w_shotorg);
setsize(missile, '0 0 0', '0 0 0');
- W_SetupProjVelocity_Explicit(
- missile,
- w_shotdir,
- v_up,
- atk_speed,
- 0,
- 0,
- atk_spread,
- false
- );
+ float atk_speed = WEP_CVAR_BOTH(blaster, isprimary, speed);
+ float atk_spread = WEP_CVAR_BOTH(blaster, isprimary, spread);
+ W_SetupProjVelocity_Explicit(missile, w_shotdir, v_up, atk_speed, 0, 0, atk_spread, false);
missile.angles = vectoangles(missile.velocity);
missile.projectiledeathtype = atk_deathtype;
missile.weaponentity_fld = weaponentity;
setthink(missile, W_Blaster_Think);
- missile.nextthink = time + atk_delay;
+ missile.nextthink = time + WEP_CVAR_BOTH(blaster, isprimary, delay);
MUTATOR_CALLHOOK(EditProjectile, actor, missile);
{
if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(blaster, refire)))
{
- W_Blaster_Attack(
- actor,
- weaponentity,
- WEP_BLASTER.m_id,
- WEP_CVAR_PRI(blaster, shotangle),
- WEP_CVAR_PRI(blaster, damage),
- WEP_CVAR_PRI(blaster, edgedamage),
- WEP_CVAR_PRI(blaster, radius),
- WEP_CVAR_PRI(blaster, force),
- WEP_CVAR_PRI(blaster, force_zscale),
- WEP_CVAR_PRI(blaster, speed),
- WEP_CVAR_PRI(blaster, spread),
- WEP_CVAR_PRI(blaster, delay),
- WEP_CVAR_PRI(blaster, lifetime)
- );
+ W_Blaster_Attack(actor, weaponentity, WEP_BLASTER.m_id);
weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(blaster, animtime), w_ready);
}
}
{
if(weapon_prepareattack(thiswep, actor, weaponentity, true, WEP_CVAR_SEC(blaster, refire)))
{
- W_Blaster_Attack(
- actor,
- weaponentity,
- WEP_BLASTER.m_id | HITTYPE_SECONDARY,
- WEP_CVAR_SEC(blaster, shotangle),
- WEP_CVAR_SEC(blaster, damage),
- WEP_CVAR_SEC(blaster, edgedamage),
- WEP_CVAR_SEC(blaster, radius),
- WEP_CVAR_SEC(blaster, force),
- WEP_CVAR_SEC(blaster, force_zscale),
- WEP_CVAR_SEC(blaster, speed),
- WEP_CVAR_SEC(blaster, spread),
- WEP_CVAR_SEC(blaster, delay),
- WEP_CVAR_SEC(blaster, lifetime)
- );
+ W_Blaster_Attack(actor, weaponentity, WEP_BLASTER.m_id | HITTYPE_SECONDARY);
weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(blaster, animtime), w_ready);
}
}
actor.jump_interval = time + WEP_CVAR_SEC(blaster, refire) * W_WeaponRateFactor(actor);
.entity weaponentity = weaponentities[1];
- BLASTER_SECONDARY_ATTACK(blaster, actor, weaponentity);
+ makevectors(actor.v_angle);
+ W_Blaster_Attack(actor, weaponentity, WEP_BLASTER.m_id | HITTYPE_SECONDARY);
}
#endif
ENDCLASS(OffhandBlaster)
OffhandBlaster OFFHAND_BLASTER;
STATIC_INIT(OFFHAND_BLASTER) { OFFHAND_BLASTER = NEW(OffhandBlaster); }
-
-#ifdef SVQC
-.float blaster_damage;
-.float blaster_edgedamage;
-.float blaster_radius;
-.float blaster_force;
-.float blaster_force_zscale;
-.float blaster_lifetime;
-
-// Will be demacroed after WEP_CVAR macros are also demacroed.
-#define BLASTER_SECONDARY_ATTACK(weapon_name, actor, weaponentity) \
- makevectors(actor.v_angle); \
- W_Blaster_Attack( \
- actor, \
- weaponentity, \
- WEP_BLASTER.m_id | HITTYPE_SECONDARY, \
- WEP_CVAR_SEC(weapon_name, shotangle), \
- WEP_CVAR_SEC(weapon_name, damage), \
- WEP_CVAR_SEC(weapon_name, edgedamage), \
- WEP_CVAR_SEC(weapon_name, radius), \
- WEP_CVAR_SEC(weapon_name, force), \
- WEP_CVAR_SEC(weapon_name, force_zscale), \
- WEP_CVAR_SEC(weapon_name, speed), \
- WEP_CVAR_SEC(weapon_name, spread), \
- WEP_CVAR_SEC(weapon_name, delay), \
- WEP_CVAR_SEC(weapon_name, lifetime) \
- );
-
-#endif
thiswep.wr_reload(thiswep, actor, weaponentity);
}
- if(fire & 1)
+ // attack swapping is useful for emulating BFG behavior in XDF
+ int primary_fire = autocvar_g_balance_crylink_swap_attacks ? fire & 2 : fire & 1;
+ int secondary_fire = autocvar_g_balance_crylink_swap_attacks ? fire & 1 : fire & 2;
+
+ if(primary_fire)
{
if(actor.(weaponentity).crylink_waitrelease != 1)
if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(crylink, refire)))
}
}
- if((fire & 2) && autocvar_g_balance_crylink_secondary)
+ if((secondary_fire) && autocvar_g_balance_crylink_secondary)
{
if(actor.(weaponentity).crylink_waitrelease != 2)
if(weapon_prepareattack(thiswep, actor, weaponentity, true, WEP_CVAR_SEC(crylink, refire)))
}
}
- if((actor.(weaponentity).crylink_waitrelease == 1 && !(fire & 1)) || (actor.(weaponentity).crylink_waitrelease == 2 && !(fire & 2)))
+ if((actor.(weaponentity).crylink_waitrelease == 1 && !(primary_fire)) || (actor.(weaponentity).crylink_waitrelease == 2 && !(secondary_fire)))
{
if(!actor.(weaponentity).crylink_lastgroup || time > actor.(weaponentity).crylink_lastgroup.teleport_time)
{
P(class, prefix, speed, float, BOTH) \
P(class, prefix, spreadtype, float, SEC) \
P(class, prefix, spread, float, BOTH) \
+ P(class, prefix, swap_attacks, float, NONE) \
P(class, prefix, switchdelay_drop, float, NONE) \
P(class, prefix, switchdelay_raise, float, NONE) \
P(class, prefix, weaponreplace, string, NONE) \
if(WEP_CVAR_SEC(vaporizer, ammo))
W_DecreaseAmmo(thiswep, actor, WEP_CVAR_SEC(vaporizer, ammo), weaponentity);
- BLASTER_SECONDARY_ATTACK(vaporizer, actor, weaponentity);
+ makevectors(actor.v_angle);
+ W_Blaster_Attack(actor, weaponentity, WEP_BLASTER.m_id | HITTYPE_SECONDARY);
// now do normal refire
weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(vaporizer, animtime), w_ready);
cvar_set("hud_panel_strafehud_angle_accel_color", cvar_defstring("hud_panel_strafehud_angle_accel_color"));
cvar_set("hud_panel_strafehud_angle_neutral_color", cvar_defstring("hud_panel_strafehud_angle_neutral_color"));
cvar_set("hud_panel_strafehud_angle_overturn_color", cvar_defstring("hud_panel_strafehud_angle_overturn_color"));
- cvar_set("hud_panel_strafehud_switch_active_color", cvar_defstring("hud_panel_strafehud_switch_active_color"));
- cvar_set("hud_panel_strafehud_switch_inactive_color", cvar_defstring("hud_panel_strafehud_switch_inactive_color"));
- cvar_set("hud_panel_strafehud_direction_color", cvar_defstring("hud_panel_strafehud_direction_color"));
+ cvar_set("hud_panel_strafehud_switch_color", cvar_defstring("hud_panel_strafehud_switch_color"));
+ cvar_set("hud_panel_strafehud_bestangle_color", cvar_defstring("hud_panel_strafehud_bestangle_color"));
}
void XonoticHUDStrafeHUDDialog_fill(entity me)
me.TD(me, 1, 1.9, e = makeXonoticSlider(0, 1, 0.1, "hud_panel_strafehud_angle_alpha"));
me.TR(me);
- me.TD(me, 1, 1.9, e = makeXonoticTextLabel(0, _("Switch indicators:")));
+ me.TD(me, 1, 1.9, e = makeXonoticTextLabel(0, _("Switch indicator:")));
me.TDempty(me, 0.2);
- me.TD(me, 1, 1.9, e = makeXonoticTextLabel(0, _("Direction caps:")));
+ me.TD(me, 1, 1.9, e = makeXonoticTextLabel(0, _("Best angle indicator:")));
me.TR(me);
- me.TD(me, 1, 0.9, e = makeXonoticTextLabel(0, _("Active:")));
+ me.TD(me, 2, 1.9, e = makeXonoticColorpickerString("hud_panel_strafehud_switch_color", "hud_panel_strafehud_switch_color"));
me.TDempty(me, 0.2);
- me.TD(me, 1, 0.9, e = makeXonoticTextLabel(0, _("Inactive:")));
- me.TR(me);
- me.TD(me, 2, 0.85, e = makeXonoticColorpickerString("hud_panel_strafehud_switch_active_color", "hud_panel_strafehud_switch_active_color"));
- me.TDempty(me, 0.2);
- me.TD(me, 2, 0.85, e = makeXonoticColorpickerString("hud_panel_strafehud_switch_inactive_color", "hud_panel_strafehud_switch_inactive_color"));
-
- me.TDempty(me, 0.2);
-
- me.TD(me, 2, 1.9, e = makeXonoticColorpickerString("hud_panel_strafehud_direction_color", "hud_panel_strafehud_direction_color"));
+ me.TD(me, 2, 1.9, e = makeXonoticColorpickerString("hud_panel_strafehud_bestangle_color", "hud_panel_strafehud_bestangle_color"));
me.TR(me);
me.TR(me);
- me.TD(me, 1, 0.9, e = makeXonoticSlider(0, 1, 0.1, "hud_panel_strafehud_switch_active_alpha"));
+ me.TD(me, 1, 0.9, e = makeXonoticSlider(0, 1, 0.1, "hud_panel_strafehud_switch_alpha"));
me.TDempty(me, 0.1);
- me.TD(me, 1, 0.9, e = makeXonoticSlider(0, 1, 0.1, "hud_panel_strafehud_switch_inactive_alpha"));
+ me.TD(me, 1, 0.9, e = makeXonoticCheckBoxString("1", "0", "hud_panel_strafehud_switch", _("Enable")));
me.TDempty(me, 0.2);
- me.TD(me, 1, 1.9, e = makeXonoticSlider(0, 1, 0.1, "hud_panel_strafehud_direction_alpha"));
+ me.TD(me, 1, 0.9, e = makeXonoticSlider(0, 1, 0.1, "hud_panel_strafehud_bestangle_alpha"));
+ me.TDempty(me, 0.1);
+ me.TD(me, 1, 0.9, e = makeXonoticCheckBoxString("1", "0", "hud_panel_strafehud_bestangle", _("Enable")));
}
ATTRIB(XonoticHUDStrafeHUDDialog, title, string, _("StrafeHUD Panel"));
ATTRIB(XonoticHUDStrafeHUDDialog, color, vector, SKINCOLOR_DIALOG_TEAMSELECT);
ATTRIB(XonoticHUDStrafeHUDDialog, intendedWidth, float, 0.4);
- ATTRIB(XonoticHUDStrafeHUDDialog, rows, float, 23.5);
+ ATTRIB(XonoticHUDStrafeHUDDialog, rows, float, 22.5);
ATTRIB(XonoticHUDStrafeHUDDialog, columns, float, 4);
ATTRIB(XonoticHUDStrafeHUDDialog, name, string, "HUDstrafehud");
ATTRIB(XonoticHUDStrafeHUDDialog, requiresConnection, float, true);
#include <server/scores_rules.qc>
#include <server/spawnpoints.qc>
#include <server/steerlib.qc>
+#include <server/strafe.qc>
#include <server/teamplay.qc>
#include <server/tests.qc>
#include <server/world.qc>
#include <server/scores_rules.qh>
#include <server/spawnpoints.qh>
#include <server/steerlib.qh>
+#include <server/strafe.qh>
#include <server/teamplay.qh>
#include <server/tests.qh>
#include <server/world.qh>
#include <server/scores_rules.qh>
#include <server/teamplay.qh>
#include <server/world.qh>
+#include <lib/misc.qh>
// used by GameCommand_make_mapinfo()
void make_mapinfo_Think(entity this)
}
}
+void GameCommand_printplayer(int request, int argc)
+{
+ switch (request)
+ {
+ case CMD_REQUEST_COMMAND:
+ {
+ entity player = GetIndexedEntity(argc, 1);
+ if (player.playerid)
+ {
+ GameLogEcho(strcat(
+ strcat(
+ ":playerinfo:", ftos(player.playerid),
+ ":", ftos(etof(player)),
+ ":", ftos(CS_CVAR(player).cvar_cl_allow_uidtracking),
+ ":", ftos(CS_CVAR(player).cvar_cl_allow_uid2name)),
+ strcat(
+ ":", ftos(CS_CVAR(player).cvar_cl_allow_uidranking),
+ ":", ((IS_REAL_CLIENT(player)) ? GameLog_ProcessIP(player.netaddress) : "bot"),
+ ":", player.crypto_idfp,
+ ":", playername(player.netname, player.team, false))));
+ }
+ return;
+ }
+ default:
+ case CMD_REQUEST_USAGE:
+ {
+ LOG_HELP("Usage:^3 sv_cmd printplayer <player_entity_id>");
+ return;
+ }
+ }
+}
+
void GameCommand_printstats(int request)
{
switch (request)
}
}
+void IRCSay(string msgstr)
+{
+ if(msgstr == "")
+ return;
+
+ string prefix;
+ if(substring(msgstr, 0, 3) == "^4*") // actions
+ prefix = "\{3}";
+ else
+ prefix = "\{1}";
+
+ msgstr = strcat(prefix, strreplace("\n", " ", msgstr), "\n"); // newlines only are good for centerprint
+
+ FOREACH_CLIENTSLOT(true,
+ {
+ if(!intermission_running)
+ if((autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !(warmup_stage || game_stopped)))
+ if(IS_PLAYER(it))
+ continue;
+ if(IS_REAL_CLIENT(it))
+ sprint(it, msgstr);
+ });
+}
+
+void GameCommand_ircmsg(int request, int argc, string command)
+{
+ IRCSay(substring(command, strlen(argv(0))+1, strlen(command)));
+ return;
+}
+
/* use this when creating a new command, making sure to place it in alphabetical order... also,
** ADD ALL NEW COMMANDS TO commands.cfg WITH PROPER ALIASES IN THE SAME FASHION!
void GameCommand_(int request)
SERVER_COMMAND(gametype, "Simple command to change the active gametype") { GameCommand_gametype(request, arguments); }
SERVER_COMMAND(gettaginfo, "Get specific information about a weapon model") { GameCommand_gettaginfo(request, arguments); }
SERVER_COMMAND(gotomap, "Simple command to switch to another map") { GameCommand_gotomap(request, arguments); }
+SERVER_COMMAND(ircmsg, "Utility function to forward chat messages from IRC/discord/whatever") { GameCommand_ircmsg(request, arguments, command); }
SERVER_COMMAND(lockteams, "Disable the ability for players to switch or enter teams") { GameCommand_lockteams(request); }
SERVER_COMMAND(make_mapinfo, "Automatically rebuild mapinfo files") { GameCommand_make_mapinfo(request); }
SERVER_COMMAND(moveplayer, "Change the team/status of a player") { GameCommand_moveplayer(request, arguments); }
SERVER_COMMAND(nospectators, "Automatically remove spectators from a match") { GameCommand_nospectators(request); }
+SERVER_COMMAND(printplayer, "Print information about a player") { GameCommand_printplayer(request, arguments); }
SERVER_COMMAND(printstats, "Dump eventlog player stats and other score information") { GameCommand_printstats(request); }
SERVER_COMMAND(radarmap, "Generate a radar image of the map") { GameCommand_radarmap(request, arguments); }
SERVER_COMMAND(reducematchtime, "Decrease the timelimit value incrementally") { GameCommand_reducematchtime(request); }
void remove_unsafely(entity e)
{
if(e.classname == "spike")
- error("Removing spikes is forbidden (crylink bug), please report");
+ LOG_WARN("Removing spikes is forbidden (crylink bug), please report");
builtin_remove(e);
}
#include <server/spawnpoints.qh>
#include <server/weapons/common.qh>
#include <server/world.qh>
+#include <server/strafe.qh>
.string stored_netname; // TODO: store this information independently of race-based gamemodes
+.float race_startspeed;
+.float race_startspeed_best;
+.float race_avgspeed_sum;
+.float race_avgspeed_time;
+.float race_avgspeed_best;
+.float race_topspeed;
+.float race_topspeed_best;
+
string uid2name(string myuid)
{
string s = db_get(ServerProgsDB, strcat("/uid2name/", myuid));
return s;
}
-void write_recordmarker(entity pl, float tstart, float dt)
+void write_recordmarker(entity pl, float newpos, float tstart, float dt)
{
- GameLogEcho(strcat(":recordset:", ftos(pl.playerid), ":", ftos(dt)));
+ GameLogEcho(strcat(":recordset:", ftos(newpos), ":", ftos(pl.playerid), ":", ftos(etof(pl)), ":", ftos(dt)));
// also write a marker into demo files for demotc-race-record-extractor to find
- stuffcmd(pl,
- strcat(
- strcat("//", strconv(2, 0, 0, GetGametype()), " RECORD SET ", TIME_ENCODED_TOSTRING(TIME_ENCODE(dt), false)),
- " ", ftos(tstart), " ", ftos(dt), "\n"));
+ if (pl.crypto_idfp != "") {
+ stuffcmd(pl,
+ strcat(
+ strcat("//", strconv(2, 0, 0, GetGametype()), " RECORD SET ", TIME_ENCODED_TOSTRING(TIME_ENCODE(dt), false)),
+ strcat(" ", ftos(tstart), " ", ftos(dt), " ", ftos(newpos), " "),
+ strcat(pl.crypto_idfp, "\n")));
+ } else {
+ stuffcmd(pl,
+ strcat(
+ strcat("//", strconv(2, 0, 0, GetGametype()), " RECORD SET ", TIME_ENCODED_TOSTRING(TIME_ENCODE(dt), false)),
+ strcat(" ", ftos(tstart), " ", ftos(dt), " ", ftos(newpos), " ANONYMOUS\n")));
+ }
}
IntrusiveList g_race_targets;
return;
int cp = e.race_checkpoint;
- float recordtime = race_checkpoint_records[cp];
- float myrecordtime = e.race_checkpoint_record[cp];
- string recordholder = race_checkpoint_recordholders[cp];
- if(recordholder == e.netname)
+ float myrecordtime = e.race_checkpoint_record[cp];
+ float recordtime;
+ string recordholder;
+ if (autocvar_g_cts_cptimes_onlyself && (race_CheckpointNetworkID(cp) < 254)) { // cp 254 - start line, cp 255 - finish line
+ recordtime = myrecordtime;
+ recordholder = "";
+ } else {
+ recordtime = race_checkpoint_records[cp];
+
+ recordholder = race_checkpoint_recordholders[cp];
+ if(recordholder == e.netname)
recordholder = "";
+ }
if(!IS_REAL_CLIENT(e))
return;
return;
}
+ write_recordmarker(e, newpos, time - TIME_DECODE(t), TIME_DECODE(t));
+
// if we didn't hit a return yet, we have a new record!
// if the player does not have a UID we can unfortunately not store the record, as the rankings system relies on UIDs
// store new ranking
race_writeTime(GetMapname(), t, myuid);
- if (newpos == 1 && showmessage)
+ if (showmessage)
{
- write_recordmarker(e, time - TIME_DECODE(t), TIME_DECODE(t));
race_send_recordtime(MSG_ALL);
}
{
int s = GameRules_scoring_add(e, RACE_FASTEST, 0);
if(!s || t < s)
+ {
GameRules_scoring_add(e, RACE_FASTEST, t - s);
+
+ e.strafe_efficiency_best = e.strafe_efficiency_sum / e.strafe_efficiency_time;
+ PlayerScore_Set(e, SP_CTS_STRAFE, floor(e.strafe_efficiency_best * 1000 + .5));
+
+ e.race_startspeed_best = e.race_startspeed;
+ PlayerScore_Set(e, SP_CTS_STARTSPEED, floor(e.race_startspeed_best + .5));
+
+ e.race_avgspeed_best = e.race_avgspeed_sum / e.race_avgspeed_time;
+ PlayerScore_Set(e, SP_CTS_AVGSPEED, floor(e.race_avgspeed_best + .5));
+
+ e.race_topspeed_best = e.race_topspeed;
+ PlayerScore_Set(e, SP_CTS_TOPSPEED, floor(e.race_topspeed_best + .5));
+ }
if(!g_race_qualifying)
{
s = GameRules_scoring_add(e, RACE_TIME, 0);
if(tvalid)
{
- recordtime = race_checkpoint_records[cp];
- float myrecordtime = e.race_checkpoint_record[cp];
- recordholder = strcat1(race_checkpoint_recordholders[cp]); // make a tempstring copy, as we'll possibly strunzone it!
- if(recordholder == e.netname)
+ float myrecordtime = e.race_checkpoint_record[cp];
+ if (autocvar_g_cts_cptimes_onlyself && (race_CheckpointNetworkID(cp) < 254)) { // cp 254 - start line, cp 255 - finish line
+ recordtime = myrecordtime;
+ recordholder = "";
+ } else {
+ recordtime = race_checkpoint_records[cp];
+ recordholder = strcat1(race_checkpoint_recordholders[cp]); // make a tempstring copy, as we'll possibly strunzone it
+ if(recordholder == e.netname)
recordholder = "";
+ }
if(t != 0)
{
if(cp == race_timed_checkpoint)
if(!this.race_checkpoint) // start line
{
+ player.race_startspeed = vlen(vec2(player.velocity));
player.race_laptime = time;
player.race_movetime = player.race_movetime_frac = player.race_movetime_count = 0;
player.race_penalty_accumulator = 0;
const float ST_RACE_LAPS = 1;
int autocvar_g_cts_send_rankings_cnt = 15;
+bool autocvar_g_cts_cptimes_onlyself = false;
int g_race_qualifying;
.entity race_respawn_spotref; // try THIS spawn in case you respawn
// definitions for functions used outside race.qc
-void write_recordmarker(entity pl, float tstart, float dt);
+void write_recordmarker(entity pl, float newpos, float tstart, float dt);
float race_PreviousCheckpoint(float f);
float race_NextCheckpoint(float f);
--- /dev/null
+#include "strafe.qh"
+
+#include <common/physics/movetypes/movetypes.qh>
+#include <common/physics/player.qh>
+#include <common/stats.qh>
+
+.float race_started;
+
+float calculate_strafe_efficiency(entity strafeplayer, vector movement, float dt)
+{
+ if(!strafeplayer) return 0;
+
+ bool swimming = strafeplayer.waterlevel >= WATERLEVEL_SWIMMING;
+ float speed = vlen(vec2(strafeplayer.velocity));
+
+ if(speed <= 0 || swimming || !strafeplayer.race_started) return 0; // only calculate the efficiency if all conditions are met
+ strafeplayer.strafe_efficiency_time += dt;
+
+ // physics
+ bool onground = IS_ONGROUND(strafeplayer) && !(PHYS_INPUT_BUTTON_JUMP(strafeplayer) || PHYS_INPUT_BUTTON_JETPACK(strafeplayer));
+ bool onslick = IS_ONSLICK(strafeplayer);
+ bool strafekeys;
+ float maxspeed_mod = IS_DUCKED(strafeplayer) ? .5 : 1;
+ float maxspeed_phys = onground ? PHYS_MAXSPEED(strafeplayer) : PHYS_MAXAIRSPEED(strafeplayer);
+ float maxspeed = maxspeed_phys * maxspeed_mod;
+ float movespeed;
+ float bestspeed;
+ float maxaccel_phys = onground ? PHYS_ACCELERATE(strafeplayer) : PHYS_AIRACCELERATE(strafeplayer);
+ float maxaccel = maxaccel_phys;
+ float vel_angle = vectoangles(strafeplayer.velocity).y - (vectoangles(strafeplayer.velocity).y > 180 ? 360 : 0); // change the range from 0° - 360° to -180° - 180° to match how view_angle represents angles
+ float view_angle = PHYS_INPUT_ANGLES(strafeplayer).y;
+ float angle;
+ int keys_fwd;
+ float wishangle;
+ bool fwd = true;
+
+ // determine whether the player is pressing forwards or backwards keys
+ if(movement.x > 0)
+ {
+ keys_fwd = 1;
+ }
+ else if(movement.x < 0)
+ {
+ keys_fwd = -1;
+ }
+ else
+ {
+ keys_fwd = 0;
+ }
+
+ // determine player wishdir
+ 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 = -wishangle;
+ }
+ }
+ }
+
+ strafekeys = fabs(wishangle) == 90;
+
+ if(strafekeys && !onground && !swimming)
+ {
+ if(PHYS_MAXAIRSTRAFESPEED(strafeplayer) != 0)
+ maxspeed = min(PHYS_MAXAIRSTRAFESPEED(strafeplayer), PHYS_MAXAIRSPEED(strafeplayer) * maxspeed_mod);
+ if(PHYS_AIRSTRAFEACCELERATE(strafeplayer) != 0)
+ maxaccel = PHYS_AIRSTRAFEACCELERATE(strafeplayer);
+ }
+
+ movespeed = min(vlen(vec2(movement)), maxspeed);
+
+ maxaccel *= dt * movespeed;
+ bestspeed = max(movespeed - maxaccel, 0);
+
+ float strafespeed = speed; // speed minus friction
+
+ if((strafespeed > 0) && onground){
+ float strafefriction = onslick ? PHYS_FRICTION_SLICK(strafeplayer) : PHYS_FRICTION(strafeplayer);
+ float f = 1 - dt * strafefriction * max(PHYS_STOPSPEED(strafeplayer) / strafespeed, 1);
+
+ if(f <= 0)
+ strafespeed = 0;
+ else
+ strafespeed *= f;
+ }
+
+ // get current strafing angle ranging from -180° to +180°
+ // 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(!strafekeys)
+ {
+ if(keys_fwd > 0)
+ {
+ fwd = true;
+ }
+ else if(keys_fwd < 0)
+ {
+ fwd = false;
+ }
+ else
+ {
+ fwd = fabs(angle) <= 90;
+ }
+ }
+ // otherwise determine by examining the strafe angle
+ else
+ {
+ if(wishangle < 0) // detect direction 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;
+ }
+
+ // invert the wish angle when strafing backwards
+ if(!fwd)
+ {
+ wishangle = -wishangle;
+ }
+
+ // note about accuracy: a few ticks after dying do still have race_started set to true causing minimal interference in the efficiency total
+ float efficiency = 0;
+ float moveangle = fabs(angle + wishangle);
+ float bestangle = (strafespeed > bestspeed ? acos(bestspeed / strafespeed) : 0) * RAD2DEG;
+ float prebestangle = (strafespeed > movespeed ? acos(movespeed / strafespeed) : 0) * RAD2DEG;
+
+ if(fabs(vlen(vec2(movement))) > 0)
+ {
+ if(moveangle >= 90)
+ {
+ efficiency = (moveangle - 90) / 90;
+ if(efficiency > 1) efficiency = 2 - efficiency;
+ efficiency *= -1;
+ }
+ else if(moveangle >= bestangle)
+ {
+ efficiency = (90 - moveangle) / (90 - bestangle);
+ }
+ else if(moveangle >= prebestangle)
+ {
+ efficiency = (moveangle - prebestangle) / (bestangle - prebestangle);
+ }
+ }
+ return efficiency;
+}
--- /dev/null
+#pragma once
+
+.float strafe_efficiency_sum;
+.float strafe_efficiency_time;
+.float strafe_efficiency_best;
+
+float calculate_strafe_efficiency(entity, vector, float);
sv_gameplayfix_nogravityonground 1
set sv_q3compat_changehitbox 0 "use Q3 player hitbox dimensions and camera height on Q3 maps (maps with an entry in a .arena or .defi file)"
+set sv_q3compat_jumppads 1 "calculate the jump pad trajectory starting at the center of the push trigger instead of the player origin, \"0\" = never, \"1\" = on Q3 maps, \"2\" = on all maps"
set g_movement_highspeed 1 "multiplier scale for movement speed (applies to sv_maxspeed and sv_maxairspeed, also applies to air acceleration when g_movement_highspeed_q3_compat is set to 0)"
set g_movement_highspeed_q3_compat 0 "apply speed modifiers to air movement in a more Q3-compatible way (only apply speed buffs and g_movement_highspeed to max air speed, not to acceleration)"