]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into Juhu/scoreboard-strafe Juhu/scoreboard-strafe
authorJuhu <5894800-Juhu_@users.noreply.gitlab.com>
Thu, 22 Jun 2023 01:47:43 +0000 (03:47 +0200)
committerJuhu <5894800-Juhu_@users.noreply.gitlab.com>
Thu, 22 Jun 2023 01:47:43 +0000 (03:47 +0200)
Fixed merge conflict caused by new SCO_LABEL macro

.gitlab-ci.yml
qcsrc/client/hud/panel/scoreboard.qc
qcsrc/common/gamemodes/gamemode/cts/sv_cts.qc
qcsrc/common/scores.qh
qcsrc/server/_mod.inc
qcsrc/server/_mod.qh
qcsrc/server/race.qc
qcsrc/server/strafe.qc [new file with mode: 0644]
qcsrc/server/strafe.qh [new file with mode: 0644]

index 5637179f1953643a4cbb29999eaa708d710038b1..5af7d56d147d1570d3be94d65115d859ea1e3522 100644 (file)
@@ -75,7 +75,7 @@ test_sv_game:
     - 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 '^:'
index 35227ffab524c63bb077dfb624b4005f19ed4032..f3b1091bde9a1721dff195148af74fbdf09ca94c 100644 (file)
@@ -169,6 +169,10 @@ string Label_getInfo(string label, int mode)
                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"));
@@ -752,7 +756,7 @@ void Cmd_Scoreboard_Help()
 " +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" \
@@ -1147,6 +1151,23 @@ string Scoreboard_GetField(entity pl, PlayerScoreField field, bool per_round)
                                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);
index 0fbab7ae51f54159e1913b7fe723bd4795b77ed4..defeb686345f41da4ff1825d169d64beb61a0f1d 100644 (file)
@@ -57,6 +57,10 @@ void cts_ScoreRules()
     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);
@@ -141,6 +145,21 @@ MUTATOR_HOOKFUNCTION(cts, PlayerPhysics)
                                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)
@@ -153,6 +172,15 @@ 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);
@@ -179,6 +207,14 @@ MUTATOR_HOOKFUNCTION(cts, ClientConnect)
 
        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);
 }
@@ -248,6 +284,9 @@ MUTATOR_HOOKFUNCTION(cts, PlayerDies)
        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),
index 8a01893f1019c423927029d57b004baa8f66b88c..448712f78d7154bd7f7c3ef29f068ab1c4b5bc43 100644 (file)
@@ -26,6 +26,11 @@ REGISTER_SP(RACE_LAPS);
 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);
index c82e892f721815fdeff9c6b46d0260eee26fe86d..c62dbad50db98188ae950d32014101f13df51319 100644 (file)
@@ -25,6 +25,7 @@
 #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>
index 52574efecc1ac689a2d9a341cc9a10a7d40c6943..cc41db238f1e65908a7a3c68f3a6957251d0e8ae 100644 (file)
@@ -25,6 +25,7 @@
 #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>
index f8af3fc5f716c5ca12eb604c6f32ecd55c64d496..e2260e1cede0b56a37d8803baf7c41e6eff3348a 100644 (file)
 #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));
@@ -486,7 +495,21 @@ void race_SendTime(entity e, float cp, float t, float tvalid)
        {
                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);
@@ -771,6 +794,7 @@ void checkpoint_passed(entity this, entity player)
 
                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;
diff --git a/qcsrc/server/strafe.qc b/qcsrc/server/strafe.qc
new file mode 100644 (file)
index 0000000..c25b1ef
--- /dev/null
@@ -0,0 +1,188 @@
+#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;
+}
diff --git a/qcsrc/server/strafe.qh b/qcsrc/server/strafe.qh
new file mode 100644 (file)
index 0000000..ceecbb5
--- /dev/null
@@ -0,0 +1,7 @@
+#pragma once
+
+.float strafe_efficiency_sum;
+.float strafe_efficiency_time;
+.float strafe_efficiency_best;
+
+float calculate_strafe_efficiency(entity, vector, float);