- 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 '^:'
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);
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),
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);
#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/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));
{
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(!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;
--- /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);