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)")));
" +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" \
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);
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);
CS(player).movement_y = -M_SQRT1_2 * wishspeed;
}
}
+ calculate_strafe_efficiency(player, CS(player).movement);
}
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);
{
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);
}
}
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
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),
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)
//REGISTER_SP(CTS_TIME);
//REGISTER_SP(CTS_LAPS);
//REGISTER_SP(CTS_FASTEST);
+REGISTER_SP(CTS_STRAFE);
REGISTER_SP(ASSAULT_OBJECTIVES);
#include <server/scores_rules.qc>
#include <server/spawnpoints.qc>
#include <server/steerlib.qc>
+#include <server/strafe.qc>
#ifdef SVQC
#include <server/sv_main.qc>
#endif
#include <server/scores_rules.qh>
#include <server/spawnpoints.qh>
#include <server/steerlib.qh>
+#include <server/strafe.qh>
#ifdef SVQC
#include <server/sv_main.qh>
#endif
--- /dev/null
+#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;
+ }
+}
--- /dev/null
+#pragma once
+
+void calculate_strafe_efficiency(entity, vector);