summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/game/g_admin.c82
-rw-r--r--src/game/g_combat.c163
-rw-r--r--src/game/g_local.h36
-rw-r--r--src/game/g_main.c2
4 files changed, 194 insertions, 89 deletions
diff --git a/src/game/g_admin.c b/src/game/g_admin.c
index 90afa5c..2cb622c 100644
--- a/src/game/g_admin.c
+++ b/src/game/g_admin.c
@@ -4383,7 +4383,7 @@ G_admin_stats
qboolean G_admin_stats( gentity_t *ent )
{
gentity_t *targ;
- int i;
+ int i, j;
qboolean header = qfalse;
const static char *cswNames[ ] =
{
@@ -4419,77 +4419,47 @@ qboolean G_admin_stats( gentity_t *ent )
targ = ent;
}
+ if( level.time - level.combatRanksTime > 5000 )
+ G_CalculateCombatRanks( );
+
ADMBP_begin( );
- for( i = CSW_UNKNOWN + 1; i < MAX_COMBAT_STATS_WEAPONS; i++ )
+ for( i = CSW_UNKNOWN + 1; i < CSW_MAX; i++ )
{
- combatStats_t *cs = targ->client->pers.combatStats + i;
+ combatRanks_t *ranks = targ->client->pers.combatRanks + i;
- // skip unused weapons
- if( !cs->total )
- continue;
+ for( j = CSD_FIRST; j < CSD_MAX; j++ )
+ if( ranks->inuse[ j ] )
+ goto no_skip;
+ continue;
+ no_skip:
if( !header )
{
- ADMBP( va( "^3stats: ^7combat statistics of %s^7:\n", targ->client->pers.netname ) );
- ADMBP( va( "^3%*s Dmg Acc FAc BAC FBA SAc^7\n",
- CSW_MAX_NAME_LEN, "Weapon" ) );
header = qtrue;
+ ADMBP( va( "^3stats: ^7combat statistics of %s^7:\n"
+ "^3%*s ^7 E ^1pct ^7 EB ^1pct ^7 FF ^1pct ^7FBF ^1pct ^7Slf ^1pct^7\n",
+ targ->client->pers.netname,
+ CSW_MAX_NAME_LEN, "Weapon" ) );
}
- ADMBP( va( "%*s %8d", CSW_MAX_NAME_LEN, cswNames[ i ], cs->total ) );
+ ADMBP( va( "%*s", CSW_MAX_NAME_LEN, cswNames[ i ] ) );
-#define PRINT_ACC(a,b) \
-if( (b) == 0 ) \
- ADMBP( " ^0n/a" ); \
-else \
-{ \
- int _t = MIN( (int)round( (float)(a)/(b) * 100.0f ), 999 ); \
- ADMBP( va( " ^7%3d", _t ) ); \
-}
-
- PRINT_ACC( cs->enemy,
- cs->total -
- cs->friendly -
- cs->enemy_buildable -
- cs->friendly_buildable -
- cs->self )
-
- PRINT_ACC( cs->friendly,
- cs->total -
- cs->enemy -
- cs->enemy_buildable -
- cs->friendly_buildable -
- cs->self )
-
- PRINT_ACC( cs->enemy_buildable,
- cs->total -
- cs->enemy -
- cs->friendly -
- cs->friendly_buildable -
- cs->self )
-
- PRINT_ACC( cs->friendly_buildable,
- cs->total -
- cs->enemy -
- cs->enemy_buildable -
- cs->friendly -
- cs->self )
-
- PRINT_ACC( cs->self,
- cs->total -
- cs->enemy -
- cs->friendly -
- cs->enemy_buildable -
- cs->friendly_buildable )
-
-#undef PRINT_ACC
+ for( j = CSD_FIRST; j < CSD_MAX; j++ )
+ {
+ if( ranks->inuse[ j ] )
+ ADMBP( va( " ^7%3d ^1%3d",
+ MIN( (int)round( ranks->effs[ j ] * 100.0f ), 999 ),
+ (int)round( ranks->effs_pc[ j ] * 100.0f ) ) );
+ else
+ ADMBP( " ^0--- ---" );
+ }
ADMBP( "\n" );
}
if( !header )
- ADMBP( va( "^3stats: ^7no combat statistics are available for %s^7\n", targ->client->pers.netname ) );
+ ADMBP( va( "^3stats: ^7no combat statistics are available for %s^7 yet\n", targ->client->pers.netname ) );
ADMBP_end( );
diff --git a/src/game/g_combat.c b/src/game/g_combat.c
index 0e8db92..a40f7a7 100644
--- a/src/game/g_combat.c
+++ b/src/game/g_combat.c
@@ -1787,6 +1787,14 @@ const static char *cswStrings[ ] =
#undef CSW
};
+/*
+================
+G_CombatStats_Fire
+G_CombatStats_FireMOD
+
+Register a weapon shot
+================
+*/
void G_CombatStats_Fire( gentity_t *ent, combatStatsWeapon_t weapon, int damage )
{
combatStats_t *cs;
@@ -1794,8 +1802,7 @@ void G_CombatStats_Fire( gentity_t *ent, combatStatsWeapon_t weapon, int damage
if( !ent || !ent->client )
return;
- cs = ent->client->pers.combatStats + weapon;
- cs->total += damage;
+ ent->client->pers.combatStats[ weapon ].fired += damage;
if( g_debugDamage.integer > 3 )
Com_Printf( "player %i fired %s, damage: %i\n",
@@ -1809,32 +1816,37 @@ void G_CombatStats_FireMOD( gentity_t *ent, meansOfDeath_t mod, int damage )
G_CombatStats_Fire( ent, modToCsw[ mod ], damage );
}
+/*
+================
+G_CombatStats_Hit
+G_CombatStats_HitMOD
+
+Register a weapon hit
+================
+*/
void G_CombatStats_Hit( gentity_t *ent, gentity_t *hit, combatStatsWeapon_t weapon, int damage )
{
- combatStats_t *cs;
- int *stat;
+ combatStatsDmgType_t type;
if( !ent || !ent->client || !hit )
return;
- cs = ent->client->pers.combatStats + weapon;
-
if( hit == ent )
- stat = &cs->self;
+ type = CSD_SELF;
else if( hit->s.eType == ET_BUILDABLE )
{
if( ent->client->pers.teamSelection == hit->buildableTeam )
- stat = &cs->friendly_buildable;
+ type = CSD_FRIENDLY_BUILDABLE;
else
- stat = &cs->enemy_buildable;
+ type = CSD_ENEMY_BUILDABLE;
}
else if( hit->client )
{
if( ent->client->pers.teamSelection ==
hit->client->pers.teamSelection )
- stat = &cs->friendly;
+ type = CSD_FRIENDLY;
else
- stat = &cs->enemy;
+ type = CSD_ENEMY;
}
else
return;
@@ -1842,16 +1854,16 @@ void G_CombatStats_Hit( gentity_t *ent, gentity_t *hit, combatStatsWeapon_t weap
if( g_debugDamage.integer > 3 )
Com_Printf( "player %i hit %s %i with %s, damage: %i\n",
ent - g_entities,
- ( stat == &cs->friendly_buildable ) ? "a friendly buildable" :
- ( stat == &cs->enemy_buildable ) ? "an enemy buildable" :
- ( stat == &cs->friendly ) ? "a friendly player" :
- ( stat == &cs->enemy ) ? "an enemy player" :
+ ( type == CSD_FRIENDLY_BUILDABLE ) ? "a friendly buildable" :
+ ( type == CSD_ENEMY_BUILDABLE ) ? "an enemy buildable" :
+ ( type == CSD_FRIENDLY ) ? "a friendly player" :
+ ( type == CSD_ENEMY ) ? "an enemy player" :
"themselves",
hit - g_entities,
cswStrings[ weapon ],
damage );
- (*stat) += damage;
+ ent->client->pers.combatStats[ weapon ].dealt[ type ] += damage;
}
void G_CombatStats_HitMOD( gentity_t *ent, gentity_t *hit, meansOfDeath_t mod, int damage )
@@ -1859,29 +1871,132 @@ void G_CombatStats_HitMOD( gentity_t *ent, gentity_t *hit, meansOfDeath_t mod, i
G_CombatStats_Hit( ent, hit, modToCsw[ mod ], damage );
}
+
+/*
+================
+G_CalculateCombatRanking
+================
+*/
+
+typedef struct
+{
+ gentity_t *ent;
+ float value;
+} csrSample_t;
+
+int csrSampleCmp( const csrSample_t *a, const csrSample_t *b )
+{
+ return ( a->value < b->value ) ? 1 : -1;
+}
+
+void G_CalculateCombatRanks( void )
+{
+ gentity_t *ent;
+ combatStatsWeapon_t weapon;
+ combatStatsDmgType_t dmgtype;
+
+ // reset all ranks
+ for( ent = g_entities; ent < g_entities + MAX_CLIENTS; ent++ )
+ if( ent->client )
+ memset( &ent->client->pers.combatRanks, 0, sizeof( combatRanks_t ) );
+
+ for( weapon = CSW_UNKNOWN + 1; weapon < CSW_MAX; weapon++ )
+ for( dmgtype = CSD_FIRST; dmgtype < CSD_MAX; dmgtype++ )
+ {
+ int i, sample_count = 0, rank;
+ csrSample_t samples[ MAX_CLIENTS ];
+ float last;
+
+ for( ent = g_entities; ent < g_entities + MAX_CLIENTS; ent++ )
+ {
+ combatStats_t *stats;
+ combatRanks_t *ranks;
+ int potential;
+
+ if( !ent->inuse ||
+ !ent->client ||
+ ent->client->pers.connected == CON_CONNECTING )
+ continue;
+
+ stats = ent->client->pers.combatStats + weapon;
+ ranks = ent->client->pers.combatRanks + weapon;
+
+ if( !stats->fired )
+ continue;
+
+ potential = stats->fired;
+
+ for( i = 0; i < CSD_MAX; i++ )
+ if( i != dmgtype )
+ potential -= stats->dealt[ i ];
+
+ if( !potential )
+ continue;
+
+ ranks->inuse[ dmgtype ] = qtrue;
+ ranks->effs[ dmgtype ] = (float)stats->dealt[ dmgtype ] / potential;
+
+ samples[ sample_count ].ent = ent;
+ samples[ sample_count++ ].value = ranks->effs[ dmgtype ];
+ }
+
+ if( !sample_count )
+ continue;
+
+ qsort( samples, sample_count, sizeof( csrSample_t ), (int(*)(const void*,const void*))csrSampleCmp );
+
+ for( i = 0, rank = 0; i < sample_count; i++ )
+ {
+ combatRanks_t *ranks = samples[ i ].ent->client->pers.combatRanks + weapon;
+
+ if( i > 0 && fabs( last - samples[ i ].value ) > 1.0e-5 )
+ rank++;
+
+ ranks->effs_pc[ dmgtype ] = rank;
+
+ last = samples[ i ].value;
+ }
+
+ for( i = 0; i < sample_count; i++ )
+ {
+ float *eff = samples[ i ].ent->client->pers.combatRanks[ weapon ].effs_pc + dmgtype;
+ (*eff) = 1.0f - (*eff) / ( (float)rank + 1 );
+ }
+ }
+
+ level.combatRanksTime = level.time;
+}
+
+/*
+================
+G_LogCombatStats
+
+Write combat stats of a player to the game log
+================
+*/
void G_LogCombatStats( gentity_t *ent )
{
int i;
char buffer[ 4096 ], *p = buffer;
- for( i = 0; i < MAX_COMBAT_STATS_WEAPONS; i++ )
+ for( i = 0; i < CSW_MAX; i++ )
{
combatStats_t *cs = ent->client->pers.combatStats + i;
// skip unused weapons
- if( !cs->total )
+ if( !cs->fired )
continue;
Com_sprintf(
p, 4096 - ( p - buffer ),
" %s %i,%i,%i,%i,%i,%i",
cswStrings[ i ],
- cs->total,
- cs->enemy,
- cs->friendly,
- cs->enemy_buildable,
- cs->friendly_buildable,
- cs->self );
+ cs->fired,
+ cs->dealt[ CSD_ENEMY ],
+ cs->dealt[ CSD_FRIENDLY ],
+ cs->dealt[ CSD_ENEMY_BUILDABLE ],
+ cs->dealt[ CSD_FRIENDLY_BUILDABLE ],
+ cs->dealt[ CSD_SELF ] );
while( *p ) p++;
}
diff --git a/src/game/g_local.h b/src/game/g_local.h
index 0717e47..4819388 100644
--- a/src/game/g_local.h
+++ b/src/game/g_local.h
@@ -309,24 +309,37 @@ typedef struct namelog_s
typedef enum
{
+ CSD_FIRST = 0,
+ CSD_ENEMY = 0,
+ CSD_ENEMY_BUILDABLE,
+ CSD_FRIENDLY,
+ CSD_FRIENDLY_BUILDABLE,
+ CSD_SELF,
+ CSD_MAX
+} combatStatsDmgType_t;
+
+typedef enum
+{
#define CSW(a,b) a
#include "g_csw.h"
#undef CSW
,
- MAX_COMBAT_STATS_WEAPONS
+ CSW_MAX
} combatStatsWeapon_t;
typedef struct
{
- int total;
-
- int enemy;
- int enemy_buildable;
- int friendly;
- int friendly_buildable;
- int self;
+ int fired;
+ int dealt[ CSD_MAX ];
} combatStats_t;
+typedef struct
+{
+ qboolean inuse[ CSD_MAX ];
+ float effs[ CSD_MAX ];
+ float effs_pc[ CSD_MAX ];
+} combatRanks_t;
+
// client data that stays across multiple respawns, but is cleared
// on each level change or team change at ClientBegin()
typedef struct
@@ -377,7 +390,8 @@ typedef struct
// keep track of other players' info for tinfo
char cinfo[ MAX_CLIENTS ][ 16 ];
- combatStats_t combatStats[ MAX_COMBAT_STATS_WEAPONS ];
+ combatStats_t combatStats[ CSW_MAX ];
+ combatRanks_t combatRanks[ CSW_MAX ];
} clientPersistant_t;
#define MAX_UNLAGGED_MARKERS 10
@@ -740,6 +754,8 @@ typedef struct
qboolean humanNoBPFlash;
int alienNoBPFlashTime;
int humanNoBPFlashTime;
+
+ int combatRanksTime;
} level_locals_t;
#define CMD_CHEAT 0x0001
@@ -971,6 +987,8 @@ void G_CombatStats_Fire( gentity_t *ent, combatStatsWeapon_t weapon, int damage
void G_CombatStats_FireMOD( gentity_t *ent, meansOfDeath_t mod, int damage );
void G_CombatStats_Hit( gentity_t *ent, gentity_t *hit, combatStatsWeapon_t weapon, int damage );
void G_CombatStats_HitMOD( gentity_t *ent, gentity_t *hit, meansOfDeath_t mod, int damage );
+void G_CalculateCombatRanks( void );
+void G_LogCombatStats( gentity_t *ent );
//
// g_missile.c
diff --git a/src/game/g_main.c b/src/game/g_main.c
index fb11594..f31e7ec 100644
--- a/src/game/g_main.c
+++ b/src/game/g_main.c
@@ -1812,6 +1812,8 @@ void CalculateRanks( void )
// see if it is time to end the level
CheckExitRules( );
+ G_CalculateCombatRanks( );
+
// if we are at the intermission, send the new info to everyone
if( level.intermissiontime )
SendScoreboardMessageToAllClients( );