]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
add strafe efficiency field for CTS scoreboard
authorJuhu <5894800-Juhu_@users.noreply.gitlab.com>
Fri, 7 Aug 2020 16:33:55 +0000 (18:33 +0200)
committerJuhu <5894800-Juhu_@users.noreply.gitlab.com>
Fri, 7 Aug 2020 16:33:55 +0000 (18:33 +0200)
.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/strafe.qc [new file with mode: 0644]
qcsrc/server/strafe.qh [new file with mode: 0644]

index 2f2fd4f2f5d9c43bf212ed09f420eb3178fec310..ef8d0847b0865cd4daf4de75d261d24e71bfd488 100644 (file)
@@ -29,7 +29,7 @@ test_sv_game:
     - wget -O data/maps/stormkeep.waypoints https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints
     - wget -O data/maps/stormkeep.waypoints.cache https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints.cache
     - make
-    - EXPECT=040aeef53953a85c5891c0c39cf9860f
+    - EXPECT=d357b667dc459574d1b4ae1a195d3d1f
     - HASH=$(${ENGINE} -noconfig -nohome +exec serverbench.cfg
       | tee /dev/stderr
       | grep '^:'
index 05715bd6cf78dd3ae6dd9f6aa3d283a1064eca68..a4a8f7028edc7bf12c7636e3142ee7ba04362fcc 100644 (file)
@@ -136,6 +136,7 @@ string Label_getInfo(string label, int mode)
                case "revivals":     if (!mode) return CTX(_("SCO^revivals"));     else LOG_HELP(strcat("^3", "revivals", "           ^7", _("Number of revivals")));
                case "rounds":       if (!mode) return CTX(_("SCO^rounds won"));   else LOG_HELP(strcat("^3", "rounds", "             ^7", _("Number of rounds won")));
                case "score":        if (!mode) return CTX(_("SCO^score"));        else LOG_HELP(strcat("^3", "score", "              ^7", _("Total score")));
+               case "strafe":       if (!mode) return CTX(_("SCO^strafe"));       else LOG_HELP(strcat("^3", "strafe", "             ^7", _("Strafe efficiency (CTS)")));
                case "suicides":     if (!mode) return CTX(_("SCO^suicides"));     else LOG_HELP(strcat("^3", "suicides", "           ^7", _("Number of suicides")));
                case "sum":          if (!mode) return CTX(_("SCO^sum"));          else LOG_HELP(strcat("^3", "sum", "                ^7", _("Number of kills minus deaths")));
                case "takes":        if (!mode) return CTX(_("SCO^takes"));        else LOG_HELP(strcat("^3", "takes", "              ^7", _("Number of domination points taken (Domination)")));
@@ -372,7 +373,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 +rc,cts/fastest" \
 " +as/objectives +nb/faults +nb/goals" \
 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
 " +dom/ticks +dom/takes" \
@@ -696,6 +697,14 @@ string Scoreboard_GetField(entity pl, PlayerScoreField field)
                case SP_DMG: case SP_DMGTAKEN:
                        return sprintf("%.1f k", pl.(scores(field)) / 1000);
 
+               case SP_CTS_STRAFE:
+               {
+                       float strafe_efficiency = pl.(scores(field)) / 10000;
+                       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("%.2f%%", strafe_efficiency * 100);
+               }
+
                default: case SP_SCORE:
                        tmp = pl.(scores(field));
                        f = scores_flags(field);
index 1550c8e8927878d1320270d8ee3e7470217a4a3d..498d0bc894d58b97139ebe9a6836fc2142ebdea1 100644 (file)
@@ -55,6 +55,7 @@ void cts_ScoreRules()
     GameRules_score_enabled(false);
     GameRules_scoring(0, 0, 0, {
         if (g_race_qualifying) {
+            field(SP_CTS_STRAFE, "strafe", 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);
@@ -139,6 +140,7 @@ MUTATOR_HOOKFUNCTION(cts, PlayerPhysics)
                                CS(player).movement_y = -M_SQRT1_2 * wishspeed;
                }
        }
+       calculate_strafe_efficiency(player, CS(player).movement);
 }
 
 MUTATOR_HOOKFUNCTION(cts, reset_map_global)
@@ -151,6 +153,9 @@ MUTATOR_HOOKFUNCTION(cts, reset_map_global)
        PlayerScore_Sort(race_place, 0, 1, 0);
 
        FOREACH_CLIENT(true, {
+               it.strafe_efficiency_best = -2;
+               PlayerScore_Set(it, SP_CTS_STRAFE, it.strafe_efficiency_best * 10000);
+
                if(it.race_place)
                {
                        s = GameRules_scoring_add(it, RACE_FASTEST, 0);
@@ -197,6 +202,10 @@ MUTATOR_HOOKFUNCTION(cts, ClientConnect)
                {
                        race_SendRankings(i, 0, 0, MSG_ONE);
                }
+
+               player.strafe_efficiency_average = player.strafe_efficiency_tics = 0;
+               player.strafe_efficiency_best = -2;
+               PlayerScore_Set(player, SP_CTS_STRAFE, player.strafe_efficiency_best * 10000);
        }
 }
 
@@ -211,7 +220,6 @@ MUTATOR_HOOKFUNCTION(cts, AbortSpeedrun)
 MUTATOR_HOOKFUNCTION(cts, MakePlayerObserver)
 {
        entity player = M_ARGV(0, entity);
-
        if(GameRules_scoring_add(player, RACE_FASTEST, 0))
                player.frags = FRAGS_PLAYER_OUT_OF_GAME;
        else
@@ -260,6 +268,8 @@ MUTATOR_HOOKFUNCTION(cts, PlayerDies)
        frag_target.respawn_flags |= RESPAWN_FORCE;
        race_AbandonRaceCheck(frag_target);
 
+       frag_target.strafe_efficiency_average = frag_target.strafe_efficiency_tics = 0;
+
        if(autocvar_g_cts_removeprojectiles)
        {
                IL_EACH(g_projectiles, it.owner == frag_target && (it.flags & FL_PROJECTILE),
@@ -385,6 +395,13 @@ MUTATOR_HOOKFUNCTION(cts, ClientKill)
 MUTATOR_HOOKFUNCTION(cts, Race_FinalCheckpoint)
 {
        entity player = M_ARGV(0, entity);
+       float strafe_efficiency_current = player.strafe_efficiency_average / player.strafe_efficiency_tics;
+
+       if(player.strafe_efficiency_best < strafe_efficiency_current)
+       {
+               player.strafe_efficiency_best = strafe_efficiency_current;
+               PlayerScore_Set(player, SP_CTS_STRAFE, player.strafe_efficiency_best * 10000);
+       }
 
        // useful to prevent cheating by running back to the start line and starting out with more speed
        if(autocvar_g_cts_finish_kill_delay)
index 3bc6c55636c65fa4e3875d0bf2b328e0f3a0aa02..deb168fed5356be31ae6c4aaaed53191bbc8fece 100644 (file)
@@ -51,6 +51,7 @@ REGISTER_SP(RACE_FASTEST);
 //REGISTER_SP(CTS_TIME);
 //REGISTER_SP(CTS_LAPS);
 //REGISTER_SP(CTS_FASTEST);
+REGISTER_SP(CTS_STRAFE);
 
 REGISTER_SP(ASSAULT_OBJECTIVES);
 
index 0da2e2be4cc5f09c8d3452f798ab10801619b675..b2139aa90b3805b7377f63cfc3146cb87bbe0cf9 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>
 
index 67080b3687838d623afcfe4e11e8eaa602730df8..7303455197a7355575cf0e23ae1bb08f45c28c4e 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>
 
diff --git a/qcsrc/server/strafe.qc b/qcsrc/server/strafe.qc
new file mode 100644 (file)
index 0000000..a7e6efa
--- /dev/null
@@ -0,0 +1,188 @@
+#include "strafe.qh"
+
+#include <server/miscfunctions.qh>
+
+.float race_checkpoint;
+
+.float strafe_efficiency_average;
+.float strafe_efficiency_tics;
+.float strafe_efficiency_best;
+
+void calculate_strafe_efficiency(entity strafeplayer, vector movement)
+{
+    float efficiency = 0;
+
+    if(strafeplayer)
+    {
+        // physics
+        bool   onground                      = IS_ONGROUND(strafeplayer);
+        bool   strafekeys;
+        bool   swimming                      = strafeplayer.waterlevel >= WATERLEVEL_SWIMMING;
+        float  speed                         = vlen(vec2(strafeplayer.velocity));
+        float  maxspeed_crouch_mod           = IS_DUCKED(strafeplayer) ? .5 : 1;
+        float  maxspeed_phys                 = onground ? PHYS_MAXSPEED(strafeplayer) : PHYS_MAXAIRSPEED(strafeplayer);
+        float  maxspeed                      = maxspeed_phys * maxspeed_crouch_mod;
+        float  vel_angle                     = vectoangles(strafeplayer.velocity).y;
+        float  view_angle                    = PHYS_INPUT_ANGLES(strafeplayer).y + 180;
+        float  angle;
+        int    direction;
+        int    keys_fwd;
+        float  wishangle                     = 0;
+        float  moveangle;
+        bool   fwd                           = true;
+        float  bestangle;
+
+        // 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 && !swimming)
+        {
+            maxspeed = PHYS_MAXAIRSTRAFESPEED(strafeplayer); // no modifiers here because they don't affect air strafing
+        }
+
+        // get current strafing angle ranging from -180° to +180°
+        if(speed > 0)
+        {
+            // calculate view angle relative to the players current velocity direction
+            angle = vel_angle - view_angle;
+
+            // if the angle goes above 180° or below -180° wrap it to the opposite side
+            if (angle > 180) angle -= 360;
+            else if(angle < -180) angle += 360;
+
+            // shift the strafe angle by 180° for further calculations
+            if(angle < 0) angle += 180;
+            else angle -= 180;
+
+            // determine whether the player is strafing forwards or backwards
+            // if the player isn't strafe turning use forwards/backwards keys to determine direction
+            if(!strafekeys)
+            {
+                if(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;
+            }
+        }
+        else
+        {
+            angle = 0;
+        }
+
+        // invert the wish angle when strafing backwards
+        if(!fwd)
+        {
+            wishangle = -wishangle;
+        }
+
+        moveangle = angle + wishangle;
+
+        // best angle to strafe at
+        bestangle = (speed > maxspeed ? acos(maxspeed / speed) : 0) * RAD2DEG;
+
+        if(speed > 0 && !swimming && strafeplayer.race_checkpoint > 0) // only calculate a new average if all conditions are met
+        {
+            ++strafeplayer.strafe_efficiency_tics;
+            if(fabs(vlen(vec2(movement))) > 0)
+            {
+                if(fabs(moveangle) > 90)
+                {
+                    efficiency = -((fabs(moveangle) - 90) / 90);
+                    if(efficiency < -1) efficiency = -2 - efficiency;
+                }
+                else
+                {
+                    if((moveangle) >= bestangle)
+                    {
+                        efficiency = 1 - (moveangle - bestangle) / (90 - bestangle);
+                    }
+                    else if((moveangle) <= -bestangle)
+                    {
+                        efficiency = 1 - (moveangle + bestangle) / (-90 + bestangle);
+                    }
+                }
+            }
+        }
+        else if(strafeplayer.race_checkpoint <= 0)
+        {
+            strafeplayer.strafe_efficiency_average = strafeplayer.strafe_efficiency_tics = 0;
+        }
+
+        strafeplayer.strafe_efficiency_average += efficiency;
+    }
+}
diff --git a/qcsrc/server/strafe.qh b/qcsrc/server/strafe.qh
new file mode 100644 (file)
index 0000000..51ab72c
--- /dev/null
@@ -0,0 +1,3 @@
+#pragma once
+
+void calculate_strafe_efficiency(entity, vector);