/* Copyright (C) 1997-2001 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "g_local.h" #include "g_gametypes.h" /* ====================================================================== PLAYER SCOREBOARDS ====================================================================== */ //newgametypes[start] static unsigned int scoreboardevent_time = 0; //================== //ClientScoreboardMessage // //Tells that something has changed _important_ scoreboard information (player join, frag...) //================== void G_ScoreboardEvent( void ) { scoreboardevent_time = game.realtime; } //================== //ClientScoreboardMessage //================== char *ClientScoreboardMessage( edict_t *ent, edict_t *killer ) { if( ent->r.svflags & SVF_FAKECLIENT ) return NULL; return G_Gametype_ScoreboardMessage( ent, ent->enemy ); } //================== //ClientEndServerFrames_UpdateScoreBoardMessages // //Show the scoreboard messages if the scoreboards are active //================== void ClientEndServerFrames_UpdateScoreBoardMessages( void ) { edict_t *ent; gclient_t *client; for( ent = game.edicts + 1 ; PLAYERNUM(ent) < game.maxclients ; ent++ ) { if( trap_GetClientState(PLAYERNUM(ent)) != CS_SPAWNED ) continue; if( !ent->r.inuse || !ent->r.client || ent->r.svflags & SVF_FAKECLIENT ) continue; client = ent->r.client; // 1 second delay if scoreboard is visible, 2.5 if an event has happened since last update, 10 seconds otherwise if( !client->scoreboard_time || game.realtime > client->scoreboard_time + 10000 || ((client->scoreboard_time < scoreboardevent_time) && (game.realtime > client->scoreboard_time + 2500)) || ((client->ps.stats[STAT_LAYOUTS] & STAT_LAYOUT_SCOREBOARD) && (game.realtime > client->scoreboard_time + 1000)) ) { client->scoreboard_time = game.realtime; trap_ServerCmd( ent, ClientScoreboardMessage(ent, ent->enemy) ); } } } //================== //G_ScoreboardMessage_AddPlayerStats //generic one to add the stats of the current player into the scoreboard message //================== void G_ScoreboardMessage_AddPlayerStats( edict_t *ent ) { char entry[MAX_TOKEN_CHARS]; size_t len; gitem_t *it; int i; int weakhit, weakshot; int hit, shot, percent; gclient_t *client; // sanity len = strlen(scoreboardString); if( !len ) return; // when chasing generate from target client = ent->r.client; if( client->chase.active && game.edicts[client->chase.target].r.client ) client = game.edicts[client->chase.target].r.client; // message header *entry = '\0'; Q_snprintfz( entry, sizeof(entry), "&z"); // weapon loop for( i = WEAP_GUNBLADE; i < WEAP_TOTAL; i++ ) { if( i == WEAP_SHOCKWAVE ) continue; it = GS_FindItemByTag( i ); if( it->weakammo_tag != AMMO_NONE ) { weakhit = client->resp.accuracy_hits[it->weakammo_tag-AMMO_CELLS]; weakshot = client->resp.accuracy_shots[it->weakammo_tag-AMMO_CELLS]; } else { weakhit = 0; weakshot = 0; } if( it->ammo_tag != AMMO_NONE ) { hit = client->resp.accuracy_hits[it->ammo_tag-AMMO_CELLS]; shot = client->resp.accuracy_shots[it->ammo_tag-AMMO_CELLS]; } else { hit = 0; shot = 0; } if( i == WEAP_LASERGUN || i == WEAP_ELECTROBOLT ) { // weak if( weakshot > 0 ) percent = min((int)(floor((100.0f * weakhit) / ((float)weakshot)) + 0.5), 99); else percent = -1; Q_strncatz( entry, va(" %2d", percent), sizeof(entry)); // strong if( shot > 0 ) percent = min((int)(floor((100.0f * hit) / ((float)shot)) + 0.5), 99); else percent = -1; Q_strncatz( entry, va(" %2d", percent), sizeof(entry) ); } else { // both in one if( weakshot+shot > 0 ) percent = min((int)(floor((100.0f * (weakhit+hit)) / ((float)(weakshot+shot))) + 0.5), 99); else percent = -1; Q_strncatz( entry, va(" %2d", percent), sizeof(entry) ); } } // add it to the message if( SCOREBOARD_MSG_MAXSIZE - len > strlen(entry) ) { Q_strncatz( scoreboardString, entry, sizeof(scoreboardString) ); len = strlen( scoreboardString ); } } //================== //G_ScoreboardMessage_AddSpectators //generic one to add the same spectator entries to all scoreboards //================== edict_t *G_Teams_BestInChallengersQueue( unsigned int lastTimeStamp, edict_t *ignore ); void G_ScoreboardMessage_AddSpectators( void ) { char entry[MAX_TOKEN_CHARS]; int i, clstate; edict_t *e; size_t len; len = strlen(scoreboardString); if( !len ) return; e = G_Teams_BestInChallengersQueue( 0, NULL ); while( e ) { //spectator tab entry if( e->r.client->pers.connecting == qtrue || trap_GetClientState(PLAYERNUM(e)) < CS_SPAWNED ) { } else { *entry = 0; Q_snprintfz( entry, sizeof(entry), "&w %i %i ", PLAYERNUM(e), e->r.client->r.ping > 999 ? 999 : e->r.client->r.ping); if( SCOREBOARD_MSG_MAXSIZE - len > strlen(entry) ) { Q_strncatz( scoreboardString, entry, sizeof(scoreboardString) ); len = strlen( scoreboardString ); } } e = G_Teams_BestInChallengersQueue( e->r.client->pers.queueTimeStamp, e ); } //add spectator team for( i = 0; teamlist[TEAM_SPECTATOR].playerIndices[i] != -1; i++ ) { e = game.edicts + teamlist[TEAM_SPECTATOR].playerIndices[i]; if( e->r.client->pers.connecting == qtrue || trap_GetClientState(PLAYERNUM(e)) < CS_SPAWNED ) { continue; } //spectator tab entry *entry = 0; if( !e->r.client->pers.queueTimeStamp ) { // not in challenger queue Q_snprintfz( entry, sizeof(entry), "&s %i %i ", PLAYERNUM(e), e->r.client->r.ping > 999 ? 999 : e->r.client->r.ping ); } if( *entry ) { if( SCOREBOARD_MSG_MAXSIZE - len > strlen(entry) ) { Q_strncatz( scoreboardString, entry, sizeof(scoreboardString) ); len = strlen( scoreboardString ); } } } //add connecting spectators for( i = 0; teamlist[TEAM_SPECTATOR].playerIndices[i] != -1; i++ ) { e = game.edicts + teamlist[TEAM_SPECTATOR].playerIndices[i]; //spectator tab entry *entry = 0; clstate = trap_GetClientState( PLAYERNUM(e) ); if( e->r.client->pers.connecting == qtrue || ( clstate >= CS_CONNECTED && clstate < CS_SPAWNED) ) { Q_snprintfz( entry, sizeof(entry), "&c %i", PLAYERNUM(e) ); } if( *entry ) { if( SCOREBOARD_MSG_MAXSIZE - len > strlen(entry) ) { Q_strncatz( scoreboardString, entry, sizeof(scoreboardString) ); len = strlen( scoreboardString ); } } } } /* ====================================================================== PLAYER WEAPONLISTS ====================================================================== */ //================== //ClientUpdateWeaponListStats //================== void ClientUpdateWeaponListStats( gclient_t *client ) { int i; gclient_t *source; if( !client ) return; // when chasing generate from target if( client->chase.active && game.edicts[client->chase.target].r.client ) source = game.edicts[client->chase.target].r.client; else source = client; memset( &client->ps.weaponlist, 0, sizeof(client->ps.weaponlist) ); // note that weaponlist only supports up to 32 weapons for( i = 0; (i < WEAP_TOTAL-1) && (i < MAX_WEAPLIST_STATS); i++ ) { client->ps.weaponlist[i][0] = min( source->inventory[WEAP_GUNBLADE + i], 255 ); // weapon client->ps.weaponlist[i][1] = min( source->inventory[AMMO_CELLS + i], 255 ); // strong ammo client->ps.weaponlist[i][2] = min( source->inventory[AMMO_WEAK_GUNBLADE + i], 255 ); // weak ammo } } //================== //ClientEndServerFrames_UpdateWeaponListMessages // //Send weaponlist updates if needed //================== void ClientEndServerFrames_UpdateWeaponListMessages( void ) { edict_t *ent; for( ent = game.edicts + 1; PLAYERNUM(ent) < game.maxclients; ent++ ) { if( !ent->r.inuse || !ent->r.client ) continue; ClientUpdateWeaponListStats( ent->r.client ); } } //======================================================================= unsigned int G_FindPointedPlayer( edict_t *self ) { trace_t trace; int i, j, bestNum = 0; vec3_t boxpoints[8]; float value, dist, value_best = 0.90f; // if nothing better is found, print nothing edict_t *other; vec3_t vieworg, dir, viewforward; // we can't handle the thirdperson modifications in server side :/ VectorSet( vieworg, self->s.origin[0], self->s.origin[1], self->s.origin[2] + self->viewheight ); AngleVectors( self->s.angles, viewforward, NULL, NULL ); for( i = 0; i < game.maxclients; i++ ) { other = game.edicts + i + 1; if( !other->r.inuse ) continue; if( !other->r.client ) continue; if( other == self ) continue; VectorSubtract( other->s.origin, self->s.origin, dir ); dist = VectorNormalize2( dir, dir ); if( dist > 1000 ) continue; value = DotProduct( dir, viewforward ); if( value > value_best ) { BuildBoxPoints( boxpoints, other->s.origin, tv(4, 4, 4), tv(4, 4, 4) ); for( j = 0; j < 8; j++ ) { trap_Trace( &trace, vieworg, vec3_origin, vec3_origin, boxpoints[j], self, MASK_SHOT|MASK_OPAQUE ); if( trace.ent && trace.ent == ENTNUM(other) ) { value_best = value; bestNum = ENTNUM(other); } } } } return bestNum; } /* =============== G_SetStats =============== */ void G_SetStats( edict_t *ent ) { gclient_t *client = ent->r.client; if( ent->r.client->chase.active ) //in chasecam it copies the other player stats return; // // layouts // client->ps.stats[STAT_LAYOUTS] = 0; /* don't force scoreboard when dead during timeout */ if( (ent->deadflag && !gtimeout.active) || ent->r.client->showscores || match.state >= MATCH_STATE_POSTMATCH ) client->ps.stats[STAT_LAYOUTS] |= STAT_LAYOUT_SCOREBOARD; if( client->showinventory && !G_IsDead(ent) ) client->ps.stats[STAT_LAYOUTS] |= STAT_LAYOUT_INVENTORY; if( GS_Gametype_IsTeamBased(game.gametype) && game.gametype != GAMETYPE_DUEL ) client->ps.stats[STAT_LAYOUTS] |= STAT_LAYOUT_TEAMTAB; if( G_Gametype_hasChallengersQueue( game.gametype ) && ent->r.client->pers.queueTimeStamp ) client->ps.stats[STAT_LAYOUTS] |= STAT_LAYOUT_CHALLENGER; if( match.state <= MATCH_STATE_WARMUP && match.ready[PLAYERNUM(ent)] ) client->ps.stats[STAT_LAYOUTS] |= STAT_LAYOUT_READY; client->ps.stats[STAT_CHASING] = STAT_NOTSET; //it is set up in the chasecam code // gametype client->ps.stats[STAT_GAMETYPE] = game.gametype; // // team // client->ps.stats[STAT_TEAM] = ent->s.team; // // health // if( ent->s.team == TEAM_SPECTATOR ) client->ps.stats[STAT_HEALTH] = STAT_NOTSET; // no health for spectator else client->ps.stats[STAT_HEALTH] = HEALTH_TO_INT(ent->health); client->r.frags = client->ps.stats[STAT_FRAGS]; // // ammo // if (!client->ammo_index /* || !client->inventory[client->ammo_index] */) { client->ps.stats[STAT_AMMO_ITEM] = 0; client->ps.stats[STAT_AMMO] = 0; } else { client->ps.stats[STAT_AMMO_ITEM] = client->ammo_index; client->ps.stats[STAT_AMMO] = client->inventory[client->ammo_index]; } if (!client->ammo_weak_index /* || !client->inventory[client->ammo_index] */) { client->ps.stats[STAT_WEAK_AMMO] = 0; } else { client->ps.stats[STAT_WEAK_AMMO] = client->inventory[client->ammo_weak_index]; } // // armor // if (client->armortag) { //client->ps.stats[STAT_ARMOR_ICON] = trap_ModelIndex (game.items[armor_tag]->world_model[0]); client->ps.stats[STAT_ARMOR_ITEM] = client->armortag; client->ps.stats[STAT_ARMOR] = ARMOR_TO_INT(client->armor); } else { client->ps.stats[STAT_ARMOR_ITEM] = 0; client->ps.stats[STAT_ARMOR] = 0; } // // pickup message // if (level.time > client->pickup_msg_time) { client->ps.stats[STAT_PICKUP_ITEM] = 0; } // // timers // if( client->quad_timeout > level.timemsec ) { client->ps.stats[STAT_POWERUP_ITEM] = POWERUP_QUAD; } else { client->ps.stats[STAT_POWERUP_ITEM] = 0; } // // selected item // if( client->selected_item < 0 || !game.items[client->selected_item] ) client->ps.stats[STAT_SELECTED_ITEM] = 0; else client->ps.stats[STAT_SELECTED_ITEM] = client->selected_item; // // frags // if( ent->s.team == TEAM_SPECTATOR ) client->ps.stats[STAT_FRAGS] = STAT_NOTSET; // no frags for spectators else client->ps.stats[STAT_FRAGS] = match.scores[PLAYERNUM(ent)].score; // // Team scores // if( GS_Gametype_IsTeamBased(game.gametype) ) { int team,i; // team based i=0; for( team = TEAM_RED; team < TEAM_RED + g_maxteams->integer; team++ ) { client->ps.stats[STAT_TEAM_RED_SCORE+i] = teamlist[team].teamscore; i++; } // mark the rest as not set for(;teamps.stats[STAT_TEAM_RED_SCORE+i] = STAT_NOTSET; i++; } } else { int team,i; // not team based i=0; for(team=TEAM_RED;teamps.stats[STAT_TEAM_RED_SCORE+i] = STAT_NOTSET; i++; } } // // weapon // if( ent->s.weapon > 0 ) client->ps.stats[STAT_WEAPON_ITEM] = ent->s.weapon; else client->ps.stats[STAT_WEAPON_ITEM] = 0; // always set them to noset before client->ps.stats[STAT_RACE_TIME] = STAT_NOTSET; client->ps.stats[STAT_RACE_MATCHBESTTIME] = STAT_NOTSET; client->ps.stats[STAT_RACE_PLAYERBESTTIME] = STAT_NOTSET; client->ps.stats[STAT_CTF_RED_FLAG] = STAT_NOTSET; client->ps.stats[STAT_CTF_BLUE_FLAG] = STAT_NOTSET; if( game.gametype == GAMETYPE_RACE ) { if( !ent->r.client->resp.race_in_race ) { // race is not started client->ps.stats[STAT_RACE_TIME]=0; } else { // the race is on client->ps.stats[STAT_RACE_TIME]=(level.timemsec-ent->r.client->resp.race_start_time)/100.0f; } client->ps.stats[STAT_RACE_PLAYERBESTTIME] = ent->r.client->teamchange.race_best_lap_time/100.0f; client->ps.stats[STAT_RACE_MATCHBESTTIME] = game.besttime/100.0f; } else if( game.gametype == GAMETYPE_CTF ) { if( g_tctf->integer ) { int capturedFlagTimer = G_Gametype_CTF_CapturedFlagTimer( ent->s.team )/100; if( capturedFlagTimer ) { client->ps.stats[STAT_RACE_TIME] = capturedFlagTimer; } } client->ps.stats[STAT_CTF_RED_FLAG] = G_Gametype_CTF_FlagStatus( TEAM_RED ); client->ps.stats[STAT_CTF_BLUE_FLAG] = G_Gametype_CTF_FlagStatus( TEAM_BLUE ); } client->ps.stats[STAT_POINTED_TEAMPLAYER] = 0; client->ps.stats[STAT_POINTED_PLAYER] = G_FindPointedPlayer(ent); if( client->ps.stats[STAT_POINTED_PLAYER] && GS_Gametype_IsTeamBased(game.gametype) ) { edict_t *e = &game.edicts[ client->ps.stats[STAT_POINTED_PLAYER] ]; if( e->s.team == ent->s.team ) { int pointedhealth = HEALTH_TO_INT(e->health); int pointedarmor = 0; int armor_type = 0; qboolean mega = qfalse; if( pointedhealth < 0 ) pointedhealth = 0; if( pointedhealth > 100 ) { pointedhealth -= 100; mega = qtrue; if( pointedhealth > 100 ) pointedhealth = 100; } pointedhealth /= 3.2; if( e->r.client->armortag ) { pointedarmor = ARMOR_TO_INT(e->r.client->armor); armor_type = 1 + e->r.client->armortag - ARMOR_GA; } if( pointedarmor > 150 ) pointedarmor = 150; pointedarmor /= 5; client->ps.stats[STAT_POINTED_TEAMPLAYER] = ( (pointedhealth &0x1F)|(pointedarmor&0x3F)<<6|(armor_type &0xF)<<12); if( mega ) client->ps.stats[STAT_POINTED_TEAMPLAYER] |= 0x20; } } //ZOID // SetCTFStats(ent); //ZOID }