- wget -nv -O data/maps/stormkeep.waypoints https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints
- wget -nv -O data/maps/stormkeep.waypoints.cache https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints.cache
- - EXPECT=57325fe74835910e451ba42d31de8f34
+ - EXPECT=0697ef57c3dca0ff122074b29495c5da
- HASH=$(${ENGINE} +exec serverbench.cfg
| tee /dev/stderr
| grep '^:'
seta _hud_panel_strafehud_demo "0" "strafehud changes angle during configure"
seta hud_panel_strafehud_mode "0" "strafehud mode which controls whether the strafehud is centered at \"0\" = view angle, \"1\" = velocity angle"
-seta hud_panel_strafehud_range "90" "the angle range up to 360 degrees displayed on the strafehud, \"0\" = dynamic (chooses the minimum range required to still see the whole area needed for accelerating)"
+seta hud_panel_strafehud_range "90" "the angle range up to 360 degrees displayed on the strafehud, \"-1\" = current fov, \"0\" = dynamic (chooses the minimum range required to still see the whole area needed for accelerating)"
seta hud_panel_strafehud_style "2" "\"0\" = no styling, \"1\" = progress bar style for the strafe bar, \"2\" = gradient for the strafe bar"
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_timeout_turn "0.1" "time (in seconds) after releasing the strafe keys before changing mode (visually more consistent hud while switching between left/right strafe turning)"
seta hud_panel_strafehud_antiflicker_angle "0.01" "how many degrees from 0° to 180° the hud ignores if it could cause visual disturbances otherwise (and to counter rounding errors)"
seta hud_panel_strafehud_fps_update "0.5" "update interval (in seconds) of the frametime to calculate the optimal angle, smaller values may cause flickering"
+seta hud_panel_strafehud_sonar "0" "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 ""
SCO_LABEL(_("SCO^rounds won"), "rounds", " ", _("Number of rounds won"));
SCO_LABEL(_("SCO^rounds played"), "rounds_pl", " ", _("Number of rounds played"));
SCO_LABEL(_("SCO^score"), "score", " ", _("Total score"));
+ SCO_LABEL(_("SCO^average speed"), "avgspeed", " ", _("Average speed (CTS)"));
+ SCO_LABEL(_("SCO^top speed"), "topspeed", " ", _("Top speed (CTS)"));
+ SCO_LABEL(_("SCO^start speed"), "startspeed", " ", _("Start speed (CTS)"));
+ SCO_LABEL(_("SCO^strafe"), "strafe", " ", _("Strafe efficiency (CTS)"));
SCO_LABEL(_("SCO^suicides"), "suicides", " ", _("Number of suicides"));
SCO_LABEL(_("SCO^sum"), "sum", " ", _("Number of kills minus deaths"));
SCO_LABEL(_("SCO^survivals"), "survivals", " ", _("Number of survivals"));
" +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,tka/pickups +ka,tka/bckills +ka,tka/bctime +ft/revivals" \
" +dom/ticks +dom/takes" \
return sprintf("%.2f k", pl.(scores(field)) / (1000 * rounds_played));
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_speed_unit);
+ if(speed < 0) return "";
+ return sprintf("%d%s", speed, GetSpeedUnit(autocvar_hud_speed_unit));
+ }
+
default: case SP_SCORE:
tmp = pl.(scores(field));
f = scores_flags(field);
float range_minangle;
float text_offset_top = 0;
float text_offset_bottom = 0;
+ float hfov = getproperty(VF_FOVX);
// real_* variables which are always positive with no wishangle offset
float real_bestangle;
else
hudangle = range_minangle; // use minimum angle required if dynamically setting hud angle
}
+ else if(autocvar_hud_panel_strafehud_range < 0)
+ {
+ hudangle = hfov;
+ }
else
{
hudangle = bound(0, fabs(autocvar_hud_panel_strafehud_range), 360); // limit HUD range to 360 degrees, higher values don't make sense
}
+ // limit strafe-meter angle to values suitable for the current projection mode
+ switch(autocvar_hud_panel_strafehud_projection)
+ {
+ case STRAFEHUD_PROJECTION_PERSPECTIVE:
+ hudangle = min(hudangle, 170);
+ break;
+ case STRAFEHUD_PROJECTION_PANORAMIC:
+ hudangle = min(hudangle, 350);
+ break;
+ }
+
// detect air strafe turning
if((!strafekeys && vlen(vec2(movement)) > 0) || onground || autocvar__hud_configure)
{
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);
+ 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);
+ autocvar_hud_panel_strafehud_style, STRAFEHUD_GRADIENT_RIGHT,
+ true, hudangle);
// draw right acceleration zone
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);
+ 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);
+ autocvar_hud_panel_strafehud_style, STRAFEHUD_GRADIENT_LEFT,
+ true, hudangle);
// draw overturn zone
// this is technically incorrect
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);
+ autocvar_hud_panel_strafehud_style, STRAFEHUD_GRADIENT_BOTH,
+ true, hudangle);
// draw neutral zone
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);
+ autocvar_hud_panel_strafehud_style, STRAFEHUD_GRADIENT_NONE,
+ true, hudangle);
// only draw indicators if minspeed is reached
if(autocvar_hud_panel_strafehud_switch && speed >= minspeed && bestangle_width > 0 && autocvar_hud_panel_strafehud_switch_alpha > 0)
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)
{
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);
+ 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);
+ STRAFEHUD_STYLE_DRAWFILL, STRAFEHUD_GRADIENT_NONE,
+ false, hudangle);
}
}
DRAWFLAG_NORMAL);
}
+ 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);
+ }
+ }
+
// draw the actual strafe angle
if(!immobile)
{
if(autocvar_hud_panel_strafehud_style == STRAFEHUD_STYLE_GRADIENT)
currentangle_color = StrafeHUD_mixColors(autocvar_hud_panel_strafehud_angle_neutral_color, currentangle_color, fabs(strafe_ratio));
+
+ // reuse strafe ratio for strafe sonar
+ static float sonar_time = 0;
+
+ float sonar_start = bound(0, autocvar_hud_panel_strafehud_sonar_start, 1);
+ float sonar_ratio = strafe_ratio - sonar_start;
+ if(sonar_start != 1)
+ sonar_ratio /= 1 - sonar_start;
+ else
+ sonar_ratio = 1;
+
+ float sonar_interval = max(0, autocvar_hud_panel_strafehud_sonar_interval_start);
+ sonar_interval += autocvar_hud_panel_strafehud_sonar_interval_range * sonar_ratio ** max(1, autocvar_hud_panel_strafehud_sonar_interval_exponent);
+ bool sonar_ready = (sonar_time == 0) || ((time - sonar_time) >= sonar_interval);
+ if(autocvar_hud_panel_strafehud_sonar && sonar_ready && (strafe_ratio >= sonar_start))
+ {
+ sonar_time = time;
+
+ float sonar_volume = bound(0, autocvar_hud_panel_strafehud_sonar_volume_start, 1);
+ sonar_volume += autocvar_hud_panel_strafehud_sonar_volume_range * sonar_ratio ** max(1, autocvar_hud_panel_strafehud_sonar_volume_exponent);
+
+ float sonar_pitch = max(0, autocvar_hud_panel_strafehud_sonar_pitch_start);
+ sonar_pitch += autocvar_hud_panel_strafehud_sonar_pitch_range * sonar_ratio ** max(1, autocvar_hud_panel_strafehud_sonar_pitch_exponent);
+
+ if(sonarsound && (sonar_volume > 0))
+ sound7(csqcplayer, CH_INFO, sonarsound, bound(0, sonar_volume, 1) * VOL_BASE, ATTN_NONE, max(0.000001, sonar_pitch * 100), 0);
+ }
}
if(mode == STRAFEHUD_MODE_VIEW_CENTERED || straight_overturn)
if(autocvar_hud_panel_strafehud_bestangle && direction != STRAFEHUD_DIRECTION_NONE)
ghost_offset = bound(0, (odd_angles ? odd_bestangle_offset : bestangle_offset), panel_size.x);
+ currentangle_offset = StrafeHUD_projectOffset(currentangle_offset, hudangle);
+ ghost_offset = StrafeHUD_projectOffset(ghost_offset, hudangle);
+
switch(autocvar_hud_panel_strafehud_angle_style)
{
case STRAFEHUD_INDICATOR_SOLID:
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)
+ {
+ float vangle = -PHYS_INPUT_ANGLES(strafeplayer).x;
+ float vangle_height = autocvar_hud_panel_strafehud_vangle_size * panel_size.y;
+ string vangle_text = strcat(ftos_decimals(vangle, 2), "°");
+
+ bool was_drawn = StrafeHUD_drawTextIndicator(
+ vangle_text, vangle_height,
+ autocvar_hud_panel_strafehud_vangle_color, 1,
+ time, text_offset_bottom, STRAFEHUD_TEXT_BOTTOM);
+
+ if(was_drawn)
+ text_offset_bottom += vangle_height;
+ }
+ }
+
draw_beginBoldFont();
// show speed when crossing the start trigger
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, float hidden_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;
}
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_size.x = 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);
+ }
+
switch(type)
{
default:
StrafeHUD_drawGradient(
color, autocvar_hud_panel_strafehud_bar_neutral_color,
- mirror_size, original_width, mirror_offset,
- alpha, gradient_mirror_offset, gradientType);
+ 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,
- size, original_width, offset,
- alpha, gradient_offset, gradientType);
+ 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 segment_size = size;
color_ratio = alpha1 / (alpha1 + alpha2);
for(int i = 0; i < size.x; ++i)
{
- float ratio, alpha_ratio, combine_ratio1, combine_ratio2;
+ 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;
+ if(doProject)
+ {
+ segment_size.x = StrafeHUD_projectWidth(segment_offset, segment_size.x, range);
+ segment_offset = StrafeHUD_projectOffset(segment_offset, range);
+ }
ratio = (i + segment_size.x / 2 + gradientOffset) / original_width * (gradientType == STRAFEHUD_GRADIENT_BOTH ? 2 : 1);
if(ratio > 1) ratio = 2 - ratio;
if(gradientType != STRAFEHUD_GRADIENT_RIGHT) ratio = 1 - ratio;
if(alpha_ratio > 0)
drawfill(
- panel_pos + eX * (offset + i),
+ panel_pos + eX * segment_offset,
segment_size,
StrafeHUD_mixColors(color1, color2, ratio),
alpha_ratio,
#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 = 90;
-int autocvar_hud_panel_strafehud_style = 2;
-bool autocvar_hud_panel_strafehud_unit_show = true;
-bool autocvar_hud_panel_strafehud_uncapped = false;
-bool autocvar_hud_panel_strafehud_bar_preaccel = true;
-vector autocvar_hud_panel_strafehud_bar_neutral_color = '1 1 1';
-float autocvar_hud_panel_strafehud_bar_neutral_alpha = 0.1;
-vector autocvar_hud_panel_strafehud_bar_accel_color = '0 1 0';
-float autocvar_hud_panel_strafehud_bar_accel_alpha = 0.5;
-vector autocvar_hud_panel_strafehud_bar_overturn_color = '1 0 1';
-float autocvar_hud_panel_strafehud_bar_overturn_alpha = 0.5;
-int autocvar_hud_panel_strafehud_angle_style = 0;
-int autocvar_hud_panel_strafehud_angle_dashes = 4;
-float autocvar_hud_panel_strafehud_angle_alpha = 0.8;
-float autocvar_hud_panel_strafehud_angle_height = 1;
-float autocvar_hud_panel_strafehud_angle_width = 0.001;
-vector autocvar_hud_panel_strafehud_angle_neutral_color = '1 1 0';
-vector autocvar_hud_panel_strafehud_angle_accel_color = '0 1 1';
-vector autocvar_hud_panel_strafehud_angle_overturn_color = '1 0 1';
-int autocvar_hud_panel_strafehud_angle_arrow = 1;
-float autocvar_hud_panel_strafehud_angle_arrow_size = 0.5;
-bool autocvar_hud_panel_strafehud_bestangle = true;
-vector autocvar_hud_panel_strafehud_bestangle_color = '1 1 1';
-float autocvar_hud_panel_strafehud_bestangle_alpha = 0.5;
-bool autocvar_hud_panel_strafehud_switch = true;
-float autocvar_hud_panel_strafehud_switch_minspeed = -1;
-vector autocvar_hud_panel_strafehud_switch_color = '1 1 0';
-float autocvar_hud_panel_strafehud_switch_alpha = 1;
-float autocvar_hud_panel_strafehud_switch_width = 0.003;
-bool autocvar_hud_panel_strafehud_direction = false;
-vector autocvar_hud_panel_strafehud_direction_color = '0 0.5 1';
-float autocvar_hud_panel_strafehud_direction_alpha = 1;
-float autocvar_hud_panel_strafehud_direction_width = 0.25;
-float autocvar_hud_panel_strafehud_direction_length = 0.02;
-bool autocvar_hud_panel_strafehud_slickdetector = true;
-float autocvar_hud_panel_strafehud_slickdetector_range = 200;
-int autocvar_hud_panel_strafehud_slickdetector_granularity = 1;
-vector autocvar_hud_panel_strafehud_slickdetector_color = '0 1 1';
-float autocvar_hud_panel_strafehud_slickdetector_alpha = 0.5;
-float autocvar_hud_panel_strafehud_slickdetector_height = 0.125;
-bool autocvar_hud_panel_strafehud_startspeed = true;
-float autocvar_hud_panel_strafehud_startspeed_fade = 4;
-vector autocvar_hud_panel_strafehud_startspeed_color = '1 0.75 0';
-float autocvar_hud_panel_strafehud_startspeed_size = 1.5;
-bool autocvar_hud_panel_strafehud_jumpheight = false;
-float autocvar_hud_panel_strafehud_jumpheight_fade = 4;
-float autocvar_hud_panel_strafehud_jumpheight_min = 50;
-vector autocvar_hud_panel_strafehud_jumpheight_color = '0 1 0.75';
-float autocvar_hud_panel_strafehud_jumpheight_size = 1.5;
-float autocvar_hud_panel_strafehud_timeout_ground = 0.1;
-float autocvar_hud_panel_strafehud_timeout_turn = 0.1;
-float autocvar_hud_panel_strafehud_antiflicker_angle = 0.01;
-float autocvar_hud_panel_strafehud_fps_update = 0.5;
+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_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);
+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, float);
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_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),
{
bool reverse = false;
if((this.spawnflags & DOOR_CRUSH)
+ && !Q3COMPAT_COMMON
#ifdef SVQC
&& (blocker.takedamage != DAMAGE_NO)
#elif defined(CSQC)
else
{
#ifdef SVQC
- if((this.dmg) && (blocker.takedamage == DAMAGE_YES)) // Shall we bite?
+ if(this.dmg && blocker.takedamage != DAMAGE_NO) // Shall we bite?
Damage (blocker, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
#endif
- // don't change direction for dead or dying stuff
- if(IS_DEAD(blocker)
+ // don't change direction for dead or dying stuff
+ if(!IS_DEAD(blocker)
#ifdef SVQC
- && (blocker.takedamage == DAMAGE_NO)
+ && blocker.takedamage != DAMAGE_NO
#endif
+ && this.wait >= 0
+ && !(Q3COMPAT_COMMON && this.spawnflags & Q3_DOOR_CRUSHER)
)
{
- if (this.wait >= 0)
+ if (this.state == STATE_DOWN)
{
- if (this.state == STATE_DOWN)
- {
- if (this.classname == "door")
- door_go_up(this, NULL, NULL);
- else
- door_rotating_go_up(this, blocker);
- }
+ if (this.classname == "door")
+ door_go_up(this, NULL, NULL);
else
- {
- if (this.classname == "door")
- door_go_down(this);
- else
- door_rotating_go_down(this);
- }
- reverse = true;
+ door_rotating_go_up(this, blocker);
}
+ else
+ {
+ if (this.classname == "door")
+ door_go_down(this);
+ else
+ door_rotating_go_down(this);
+ }
+ reverse = true;
}
#ifdef SVQC
else
{
//gib dying stuff just to make sure
- if((this.dmg) && (blocker.takedamage != DAMAGE_NO)) // Shall we bite?
+ if(this.dmg && blocker.takedamage != DAMAGE_NO && IS_DEAD(blocker)) // Shall we bite?
Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
}
#endif
}
- if (!reverse && this.classname == "door")
+ // if we didn't change direction and are using a non-linear movement controller, we must pause it
+ if (!reverse && this.classname == "door" && this.move_controller)
SUB_CalcMovePause(this);
}
return false;
}
-void door_fire(entity this, entity actor, entity trigger)
+void door_use(entity this, entity actor, entity trigger)
{
- if (this.owner != this)
- objerror (this, "door_fire: this.owner != this");
+ //dprint("door_use (model: ");dprint(this.model);dprint(")\n");
+
+ if (!this.owner)
+ return;
+ this = this.owner;
if (this.spawnflags & DOOR_TOGGLE)
{
} while ((e != this) && (e != NULL));
}
-void door_use(entity this, entity actor, entity trigger)
-{
- //dprint("door_use (model: ");dprint(this.model);dprint(")\n");
-
- if (this.owner)
- door_fire(this.owner, actor, trigger);
-}
-
void door_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
{
if(this.spawnflags & NOSPLASH)
#endif
return;
- if (time < this.door_finished)
+ if (this.owner.state == STATE_UP)
return;
// check if door is locked
if (!door_check_keys(this, toucher))
return;
- this.door_finished = time + 1;
+ if (this.owner.state == STATE_TOP)
+ {
+ if (this.owner.nextthink < this.owner.ltime + this.owner.wait)
+ {
+ entity e = this.owner;
+ do {
+ e.nextthink = e.ltime + e.wait;
+ e = e.enemy;
+ } while (e != this.owner);
+ }
+ return;
+ }
door_use(this.owner, toucher, NULL);
}
entity LinkDoors_nextent(entity cur, entity near, entity pass)
{
- while((cur = find(cur, classname, pass.classname)) && ((cur.spawnflags & DOOR_DONT_LINK) || cur.enemy))
+ while((cur = find(cur, classname, pass.classname)) && ((cur.spawnflags & DOOR_DONT_LINK && !Q3COMPAT_COMMON) || cur.enemy))
{
}
return cur;
bool LinkDoors_isconnected(entity e1, entity e2, entity pass)
{
+ if(Q3COMPAT_COMMON)
+ return e1.team == e2.team;
+
float DELTA = 4;
if((e1.absmin_x > e2.absmax_x + DELTA)
|| (e1.absmin_y > e2.absmax_y + DELTA)
if (this.enemy)
return; // already linked by another door
- if (this.spawnflags & DOOR_DONT_LINK)
+
+ // Q3 door linking is done for teamed doors only and is not affected by spawnflags or bmodel proximity
+ if ((this.spawnflags & DOOR_DONT_LINK && !Q3COMPAT_COMMON) || (Q3COMPAT_COMMON && !this.team))
{
this.owner = this.enemy = this;
"speed" movement speed (100 default)
"wait" wait before returning (3 default, -1 = never return)
"lip" lip remaining at end of move (8 default)
-"dmg" damage to inflict when blocked (2 default)
+"dmg" damage to inflict when blocked (0 default)
"sounds"
0) no sound
1) stone
this.pos2 = this.pos1;
this.pos1 = this.origin;
-#ifdef SVQC
- this.SendFlags |= SF_TRIGGER_UPDATE;
-#endif
+// no longer needed: not using delayed initialisation for door_init_startopen()
+//#ifdef SVQC
+// this.SendFlags |= SF_TRIGGER_UPDATE;
+//#endif
}
void door_reset(entity this)
if (q3compat)
{
- // CPMA adds these fields for overriding the engine sounds
+ // CPMA adds these fields for overriding the Q3 default sounds
string s = GetField_fullspawndata(this, "sound_start", true);
string e = GetField_fullspawndata(this, "sound_end", true);
if (s)
this.noise2 = strzone(s);
+ else
+ {
+ // PK3s supporting Q3A sometimes include custom sounds at Q3 default paths
+ s = "sound/movers/doors/dr1_strt.wav";
+ if (FindFileInMapPack(s))
+ this.noise2 = s;
+ }
+
if (e)
this.noise1 = strzone(e);
+ else
+ {
+ e = "sound/movers/doors/dr1_end.wav";
+ if (FindFileInMapPack(e))
+ this.noise1 = e;
+ }
}
// sound when door stops moving
}
else if (!this.wait)
{
- this.wait = 3;
+ this.wait = q3compat ? 2 : 3;
}
if (!this.lip)
if(this.spawnflags & DOOR_NONSOLID)
this.solid = SOLID_NOT;
-// DOOR_START_OPEN is to allow an entity to be lighted in the closed position
-// but spawn in the open position
- if (this.spawnflags & DOOR_START_OPEN)
- InitializeEntity(this, door_init_startopen, INITPRIO_SETLOCATION);
-
door_init_shared(this);
this.pos1 = this.origin;
- this.pos2 = this.pos1 + this.movedir*(fabs(this.movedir*this.size) - this.lip);
+ vector absmovedir;
+ absmovedir.x = fabs(this.movedir.x);
+ absmovedir.y = fabs(this.movedir.y);
+ absmovedir.z = fabs(this.movedir.z);
+ this.pos2 = this.pos1 + this.movedir * (absmovedir * this.size - this.lip);
+
+// DOOR_START_OPEN is to allow an entity to be lighted in the closed position
+// but spawn in the open position
+ if (this.spawnflags & DOOR_START_OPEN)
+ door_init_startopen(this);
if(autocvar_sv_doors_always_open)
{
this.speed = 100;
}
+ if (q3compat)
+ {
+ if (!this.dmg)
+ this.dmg = 2;
+
+ if (!this.team)
+ {
+ string t = GetField_fullspawndata(this, "team");
+ // bones_was_here: same hack as used to support teamed items on Q3 maps
+ if(t) this.team = crc16(false, t);
+ }
+ }
+
settouch(this, door_touch);
// LinkDoors can't be done until all of the doors have been spawned, so
bool autocvar_sv_doors_always_open;
#endif
-const int DOOR_START_OPEN = BIT(0);
+const int DOOR_START_OPEN = BIT(0); // has same meaning in Q3: reverse position 1 and 2
const int DOOR_DONT_LINK = BIT(2);
const int SPAWNFLAGS_GOLD_KEY = BIT(3); // Quake 1 compat, can only be used with func_door!
const int SPAWNFLAGS_SILVER_KEY = BIT(4); // Quake 1 compat, can only be used with func_door!
const int DOOR_NONSOLID = BIT(10);
const int DOOR_CRUSH = BIT(11); // can't use CRUSH cause that is the same as DOOR_DONT_LINK
+#define Q3_DOOR_CRUSHER BIT(2) // in Q3 this disables the auto reverse so the blocking player takes damage every frame
#ifdef CSQC
// stuff for preload
void plat_delayedinit(entity this)
{
plat_link(this);
- plat_spawn_inside_trigger(this); // the "start moving" trigger
+ // Q3 uses only a truth check of .targetname to decide whether to spawn a trigger
+ if(!Q3COMPAT_COMMON || this.targetname == "")
+ plat_spawn_inside_trigger(this); // the "start moving" trigger
}
float plat_send(entity this, entity to, float sf)
spawnfunc(func_plat)
{
- if (this.spawnflags & CRUSH)
+ if (q3compat)
+ {
+ this.spawnflags = 0; // Q3 plats have no spawnflags
+ if (!this.dmg) this.dmg = 2;
+ }
+ else if (this.spawnflags & CRUSH)
{
this.dmg = 10000;
}
if (q3compat)
{
- // CPMA adds these fields for overriding the engine sounds
+ // CPMA adds these fields for overriding the Q3 default sounds
string s = GetField_fullspawndata(this, "sound_start", true);
string e = GetField_fullspawndata(this, "sound_end", true);
if (s)
this.noise = strzone(s);
+ else
+ {
+ // PK3s supporting Q3A sometimes include custom sounds at Q3 default paths
+ s = "sound/movers/plats/pt1_strt.wav";
+ if (FindFileInMapPack(s))
+ this.noise = s;
+ }
+
if (e)
this.noise1 = strzone(e);
+ else
+ {
+ e = "sound/movers/plats/pt1_end.wav";
+ if (FindFileInMapPack(e))
+ this.noise1 = e;
+ }
}
if(this.noise && this.noise != "")
setblocked(this, plat_crush);
- if (!this.speed) this.speed = 150;
- if (!this.lip) this.lip = 16;
+ if (!this.speed) this.speed = q3compat ? 200 : 150;
+ if (!this.lip) this.lip = q3compat ? 8 : 16;
if (!this.height) this.height = this.size.z - this.lip;
this.pos1 = this.origin;
set_movetype(this, MOVETYPE_PUSH);
this.move_time = time;
- plat_spawn_inside_trigger(this);
+ if(!Q3COMPAT_COMMON || this.targetname == "")
+ plat_spawn_inside_trigger(this);
}
if(sf & SF_TRIGGER_RESET)
plat_go_down(this);
}
+void plat_target_use(entity this, entity actor, entity trigger)
+{
+ if (this.state == STATE_TOP)
+ this.nextthink = this.ltime + 1;
+ else if (this.state != STATE_UP)
+ plat_go_up(this);
+}
+
// WARNING: backwards compatibility because people don't use already existing fields :(
// TODO: Check if any maps use these fields and remove these fields if it doesn't break maps
.string sound1, sound2;
void plat_reset(entity this)
{
- if(this.targetname && this.targetname != "")
+ if(this.targetname && this.targetname != "" && !Q3COMPAT_COMMON)
{
setorigin(this, this.pos1);
this.state = STATE_UP;
{
setorigin(this, this.pos2);
this.state = STATE_BOTTOM;
- this.use = plat_trigger_use;
+ this.use = (this.targetname != "" && Q3COMPAT_COMMON) ? plat_target_use : plat_trigger_use;
}
#ifdef SVQC
#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);
+
+ // speed cannot be negative except when subtracting
+ if(!is_add)
+ {
+ speed = max(speed, 0);
+ }
+
+ XYZ_ARRAY(pushvel);
+ VECTOR_TO_ARRAY(pushvel, pushed_entity.velocity);
+
+ FOR_XYZ(i)
+ {
+ // launcher can only be either positive or negative not both
+ if(is_launcher && is_positive[i] && is_negative[i])
+ {
+ is_positive[i] = is_negative[i] = false;
+ }
+
+ // ignore this direction
+ if(!is_positive[i] && !is_negative[i])
+ {
+ pushvel[i] = 0;
+ }
+ }
+
+ float oldspeed = vlen(ARRAY_AS_VECTOR(pushvel));
+
+ // the speed field is used to specify the percentage of the current speed
+ if(is_percentage)
+ {
+ speed = oldspeed * speed / 100;
+ }
+
+ float launcherspeed = 0;
+
+ // do this properly when not playing a Q3 map, do not put this in the loop
+ if(!STAT(Q3COMPAT, pushed_entity))
+ {
+ launcherspeed += speed;
+
+ // add the add speed in the same variable
+ // as it goes in the same direction
+ if(is_add) launcherspeed += oldspeed;
+ }
+
+ FOR_XYZ(i)
+ {
+ if(((pushvel[i] != 0) || is_launcher) && (is_positive[i] != is_negative[i]))
+ {
+ if(is_launcher)
+ {
+ // every direction weighs the same amount on launchers
+ // movedir does not matter
+ pushvel[i] = 1;
+
+ // this does not belong inside the loop
+ // only simulate this bug when playing a Q3 map
+ if(STAT(Q3COMPAT, pushed_entity))
+ {
+ launcherspeed += speed;
+
+ // add the add speed in the same variable
+ // as it goes in the same direction
+ if(is_add) launcherspeed += oldspeed;
+ }
+ }
+
+ 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)
+ {
+ // launcher will always launch you in the correct direction
+ // even if speed is set to a negative value, fabs() is correct
+ VECTOR_TO_ARRAY(pushvel, normalize(ARRAY_AS_VECTOR(pushvel)) * fabs(launcherspeed));
+ }
+ 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)
+ {
+ // preserve unaffected directions
+ if(!is_positive[i] && !is_negative[i])
+ {
+ pushvel[i] = oldvel[i];
+ }
+ }
+
+ 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
+
+
+#define SPEED_PERCENTAGE BIT(0)
+#define SPEED_ADD BIT(1)
+#define SPEED_POSITIVE_X BIT(2)
+#define SPEED_NEGATIVE_X BIT(3)
+#define SPEED_POSITIVE_Y BIT(4)
+#define SPEED_NEGATIVE_Y BIT(5)
+#define SPEED_POSITIVE_Z BIT(6)
+#define SPEED_NEGATIVE_Z BIT(7)
+#define SPEED_LAUNCHER BIT(8)
vector org = targ.origin;
- if(Q3COMPAT_COMMON || this.spawnflags & PUSH_STATIC)
+ if(STAT(Q3COMPAT, targ) || this.spawnflags & PUSH_STATIC)
+ {
org = (this.absmin + this.absmax) * 0.5;
+ }
+
+ bool already_pushed = false;
+ if(is_velocity_pad) // remember velocity jump pads
+ {
+ if(this == targ.last_pushed || (targ.last_pushed && !STAT(Q3COMPAT, targ))) // if q3compat is active overwrite last stored jump pad, otherwise ignore
+ {
+ already_pushed = true;
+ }
+ else
+ {
+ targ.last_pushed = this; // may be briefly out of sync between client and server if client prediction is toggled
+ }
+ }
bool already_pushed = false;
if(is_velocity_pad) // remember velocity jump pads
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); // xonotic-only, Q3 already behaves like this by default
+#define PUSH_STATIC BIT(12) // xonotic-only, Q3 already behaves like this by default
+
+#define PUSH_VELOCITY_PLAYERDIR_XY BIT(0)
+#define PUSH_VELOCITY_ADD_XY BIT(1)
+#define PUSH_VELOCITY_PLAYERDIR_Z BIT(2)
+#define PUSH_VELOCITY_ADD_Z BIT(3)
+#define PUSH_VELOCITY_BIDIRECTIONAL_XY BIT(4)
+#define PUSH_VELOCITY_BIDIRECTIONAL_Z BIT(5)
+#define PUSH_VELOCITY_CLAMP_NEGATIVE_ADDS BIT(6)
#define PUSH_VELOCITY_PLAYERDIR_XY BIT(0)
#define PUSH_VELOCITY_ADD_XY BIT(1)
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)
// FIXME: workaround for https://gitlab.com/xonotic/xonotic-data.pk3dir/-/issues/2812
#ifdef SVQC
#define Q3COMPAT_COMMON q3compat
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) \
#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); }
this.use = score_use;
}
+#define FRAGSFILTER_REMOVER BIT(0)
+#define FRAGSFILTER_RUNONCE BIT(1) // unused
+#define FRAGSFILTER_SILENT BIT(2)
+#define FRAGSFILTER_RESET BIT(3)
+
void fragsfilter_use(entity this, entity actor, entity trigger)
{
if(!IS_PLAYER(actor))
return;
if(actor.fragsfilter_cnt >= this.frags)
+ {
+ if(this.spawnflags & FRAGSFILTER_RESET)
+ actor.fragsfilter_cnt = 0;
+ else if(this.spawnflags & FRAGSFILTER_REMOVER)
+ actor.fragsfilter_cnt -= this.frags;
SUB_UseTargets(this, actor, trigger);
+ }
+ else if(!(this.spawnflags & FRAGSFILTER_SILENT))
+ {
+ int req_frags = this.frags - actor.fragsfilter_cnt;
+ centerprint(actor, sprintf("%d more frag%s needed", req_frags, req_frags > 1 ? "s" : ""));
+ play2(actor, SND(TALK));
+ }
}
spawnfunc(target_fragsFilter)
{
mirrordamage = M_ARGV(5, float);
force = M_ARGV(6, vector);
+ // Dr. Jaska:
+ // Quake 3 DeFRaG's cpm physic's rocketlauncher has 1.2x horizontal force multiplier
+ // This aims to reproduce it in Quake 3 DeFRaG cpm and XDF physics if damagepush_speedfactor is 0
+ // CPMA cpm physics should be active in .arena maps and Quake 3 DeFRaG cpm physics in .defi maps
+ // It is only intended to be used with 250 base force for devastator which matches Q3 if our
+ // target's damageforcescale is also 2
+ if(force)
+ if(q3compat & Q3COMPAT_DEFI)
+ if(autocvar_g_balance_damagepush_speedfactor == 0)
+ if(attacker.(weaponentity).m_weapon == WEP_DEVASTATOR)
+ if(autocvar_g_balance_devastator_force == 250)
+ if(targ.damageforcescale == 2)
+ if((cvar_string("g_mod_physics") == "CPMA") || (cvar_string("g_mod_physics") == "XDF"))
+ {
+ force.x *= 1.2;
+ force.y *= 1.2;
+ }
+
if(IS_PLAYER(targ) && damage > 0 && attacker)
{
for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
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);
}
return v;
}
+/*
+=============
+FindFileInMapPack
+
+Returns the first matching VFS file path that exists in the current map's pack.
+Returns string_null if no files match or the map isn't packaged.
+=============
+*/
+string FindFileInMapPack(string pattern)
+{
+ if(!checkextension("DP_QC_FS_SEARCH_PACKFILE"))
+ return string_null;
+
+ string base_pack = whichpack(strcat("maps/", mapname, ".bsp"));
+ if(base_pack == "" || !base_pack) // this map isn't packaged or there was an error
+ return string_null;
+
+ int glob = search_packfile_begin(pattern, true, true, base_pack);
+ if(glob < 0)
+ return string_null;
+
+ string file = search_getfilename(glob, 0);
+ search_end(glob);
+ return file;
+}
+
void WarpZone_PostInitialize_Callback()
{
// create waypoint links for warpzones
#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;
entity race_checkpoint_lastplayers[MAX_CHECKPOINTS];
.float race_checkpoint_record[MAX_CHECKPOINTS];
+.float current_checkpoint_record[MAX_CHECKPOINTS];
float race_highest_checkpoint;
float race_timed_checkpoint;
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;
{
// netname only used TEMPORARILY for printing
int newpos = race_readPos(map, t);
-
+ int i;
int player_prevpos = 0;
- for(int i = 1; i <= RANKINGS_CNT; ++i)
+ for(i = 1; i <= RANKINGS_CNT; ++i)
{
if(race_readUID(map, i) == myuid)
player_prevpos = i;
return;
}
+ string body = sprintf("RECORDv1\n%s\n%s\n%s\n%s\n%d\n", strftime(false, "%Y-%m-%dT%H:%M:%SZ"), map, e.crypto_idfp, e.netaddress, t);
+ bool first_cp = true;
+ for (i=0; i < MAX_CHECKPOINTS; i++) {
+ if (e.current_checkpoint_record[i] > 0) {
+ if (first_cp) {
+ body = strcat(body, sprintf("%d %d", i, TIME_ENCODE(e.current_checkpoint_record[i])));
+ first_cp = false;
+ } else {
+ body = strcat(body, sprintf(";%d %d", i, TIME_ENCODE(e.current_checkpoint_record[i])));
+ }
+ }
+ }
+ body = strcat(body, sprintf("\n%f\n%f\n%f\n%f\n%s", e.race_topspeed_best, e.race_avgspeed_best, e.race_startspeed_best, e.strafe_efficiency_best, e.netname));
+ float r;
+ float buf = buf_create();
+ bufstr_set(buf, 0, body);
+ r = crypto_uri_postbuf(autocvar_sv_checkpoint_house_url, URI_GET_CURL + curl_uri_get_pos, "text/plain", "&", buf, 0);
+ if (r) {
+ curl_uri_get_pos = (curl_uri_get_pos + 1) % (URI_GET_CURL_END - URI_GET_CURL + 1);
+ }
+ buf_del(buf);
+ 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;
player.race_lastpenalty = NULL;
- }
+ for (int i=0; i < MAX_CHECKPOINTS; i++) {
+ player.current_checkpoint_record[i] = 0;
+ }
+ } else {
+ player.current_checkpoint_record[this.race_checkpoint] = player.race_movetime;
+ }
if(g_race_qualifying)
race_SendNextCheckpoint(player, 0);
#pragma once
bool autocvar_g_allow_checkpoints;
+string autocvar_sv_checkpoint_house_url;
float race_teams;
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)"
set sv_quickmenu_file "" "filename of a custom server's quickmenu that will be selectable from the default client's quickmenu. This file must be sent in a pk3 archive and should have an unique name (e.g. quickmenu-servername.txt) to prevent overriding existing quickmenus"
+set sv_checkpoint_house_url "http://127.0.0.1:10876/new-record"
// Avoid perf problems near '0 0 0'; `set` without a description is because only current DP has this engine cvar
set sv_areagrid_link_SOLID_NOT 0