+ // 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
+ {
+ float slicksteps = 90 / pow(2, bound(0, autocvar_hud_panel_strafehud_slickdetector_granularity, 4));
+ bool slickdetected = false;
+
+ slickdetected = IS_ONSLICK(strafeplayer); // 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 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
+ {
+ slickoffset.x = slickoffset.y = 0;
+ traceline(strafeplayer.origin, strafeplayer.origin + slickoffset, MOVE_WORLDONLY, NULL);
+ if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK) slickdetected = true;
+ }
+ }
+
+ // if a traceline hit a slick surface
+ if(slickdetected)
+ {
+ vector slickdetector_size = panel_size;
+ slickdetector_size.y = slickdetector_height;
+ // top horizontal line
+ drawfill(panel_pos - eY * slickdetector_size.y, slickdetector_size, autocvar_hud_panel_strafehud_slickdetector_color, autocvar_hud_panel_strafehud_slickdetector_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
+ // bottom horizontal line
+ drawfill(panel_pos + eY * panel_size.y, slickdetector_size, autocvar_hud_panel_strafehud_slickdetector_color, autocvar_hud_panel_strafehud_slickdetector_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
+ }
+ }
+
+ draw_beginBoldFont();
+ // show speed when crossing the start trigger
+ if(autocvar_hud_panel_strafehud_startspeed_fade > 0)
+ {
+ float text_alpha = 0;
+ if(race_checkpoint == 254) // checkpoint 254 is the start trigger
+ {
+ 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);
+ }
+ }
+ else
+ {
+ starttime = 0;
+ startspeed = -1;
+ }
+
+ // experimental: show height achieved by a single jump (doesn't work in low gravity and may not be 100% accurate)
+ if(autocvar_hud_panel_strafehud_jumpheight_fade > 0)
+ {
+ float text_alpha = 0;
+ float jumpheight_min = max(autocvar_hud_panel_strafehud_jumpheight_min, 0);
+ float jumpheight_current = strafeplayer.origin.z;
+ float jumpspeed_current = strafeplayer.velocity.z;
+ if(jumpspeed_prev <= jumpspeed_current || jumpheight_prev > jumpheight_current || IS_ONGROUND(strafeplayer) || swimming || IS_DEAD(strafeplayer) || spectating)
+ {
+ // tries to catch kill and spectate but those are not reliable, should just hook to kill/spectate/teleport and reset jump height there
+ jumprestart = true;
+ }
+ else
+ {
+ if(jumpheight < 0 || jumprestart)
+ {
+ jumprestart = false;
+ jumpheight = 0;
+ }
+ else
+ {
+ jumpheight += jumpheight_current - jumpheight_prev;
+ }
+ if((jumpheight * length_conversion_factor) > jumpheight_min && jumpheight > jumpheight_persistent)
+ {
+ jumptime = time;
+ jumpheight_persistent = jumpheight;
+ }
+ }
+ jumpheight_prev = jumpheight_current;
+ jumpspeed_prev = jumpspeed_current;
+ if(jumpheight_persistent > 0)
+ {
+ text_alpha = cos(((time - jumptime) / autocvar_hud_panel_strafehud_jumpheight_fade) * 90 * DEG2RAD); // fade non-linear like the physics panel does
+ if((time - jumptime) > autocvar_hud_panel_strafehud_jumpheight_fade)
+ {
+ jumpheight_persistent = -1;
+ }
+ }
+ if(jumpheight_persistent > 0 && text_alpha > 0 && autocvar_hud_panel_strafehud_jumpheight_size > 0)
+ {
+ 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_persistent * length_conversion_factor, 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);
+ }
+ }
+ else
+ {
+ jumpheight_prev = jumpspeed_prev = 0;
+ jumpheight = jumpheight_persistent = -1;
+ }
+ draw_endBoldFont();
+
+ if(speed < (maxspeed + antiflicker_speed) && !immobile)