From a3d67fdf7ea5d02d11bfd17a9df335c8a8fa248f Mon Sep 17 00:00:00 2001 From: enneract Date: Sat, 20 Dec 2014 16:15:04 +0100 Subject: Implement percentile ranking of combat stats.. --- src/game/g_admin.c | 82 +++++++++----------------- src/game/g_combat.c | 163 ++++++++++++++++++++++++++++++++++++++++++++-------- src/game/g_local.h | 36 +++++++++--- src/game/g_main.c | 2 + 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 @@ -307,26 +307,39 @@ typedef struct namelog_s int id; } namelog_t; +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( ); -- cgit