/* * G_Match_LaunchState */ void G_Match_LaunchState( int matchState ) { static bool advance_queue = false; if( game.asEngine != NULL ) { // give the gametype a chance to refuse the state change, or to set up things for it if( !GT_asCallMatchStateFinished( matchState ) ) return; } else { // There isn't any script, run generic fuction if( !G_Gametype_GENERIC_MatchStateFinished( matchState ) ) return; } GS_GamestatSetFlag( GAMESTAT_FLAG_MATCHEXTENDED, false ); GS_GamestatSetFlag( GAMESTAT_FLAG_WAITING, false ); if( matchState == MATCH_STATE_POSTMATCH ) { level.finalMatchDuration = game.serverTime - GS_MatchStartTime(); } if( ( matchState == MATCH_STATE_POSTMATCH && GS_RaceGametype() ) || ( matchState != MATCH_STATE_POSTMATCH && gs.gameState.stats[GAMESTAT_MATCHSTATE] == MATCH_STATE_POSTMATCH ) ) { // entering postmatch in race or leaving postmatch in normal gt G_Match_SendReport(); trap_MM_GameState( false ); } switch( matchState ) { default: case MATCH_STATE_WARMUP: { advance_queue = false; level.forceStart = false; gs.gameState.stats[GAMESTAT_MATCHSTATE] = MATCH_STATE_WARMUP; gs.gameState.longstats[GAMELONG_MATCHDURATION] = (unsigned int)( fabs( g_warmup_timelimit->value * 60 ) * 1000 ); gs.gameState.longstats[GAMELONG_MATCHSTART] = game.serverTime; // race has playtime in warmup too, so flag the matchmaker about this if( GS_RaceGametype() ) trap_MM_GameState( true ); break; } case MATCH_STATE_COUNTDOWN: { advance_queue = true; gs.gameState.stats[GAMESTAT_MATCHSTATE] = MATCH_STATE_COUNTDOWN; gs.gameState.longstats[GAMELONG_MATCHDURATION] = (unsigned int)( fabs( g_countdown_time->value ) * 1000 ); gs.gameState.longstats[GAMELONG_MATCHSTART] = game.serverTime; break; } case MATCH_STATE_PLAYTIME: { // ch : should clear some statcollection memory from warmup? advance_queue = true; // shouldn't be needed here level.forceStart = false; gs.gameState.stats[GAMESTAT_MATCHSTATE] = MATCH_STATE_PLAYTIME; gs.gameState.longstats[GAMELONG_MATCHDURATION] = (unsigned int)( fabs( 60 * g_timelimit->value )*1000 ); gs.gameState.longstats[GAMELONG_MATCHSTART] = game.serverTime; // request a new match UUID trap_ConfigString( CS_MATCHUUID, "" ); // tell matchmaker that the game is on, so if // client disconnects before SendReport, it is flagged // as 'purgable' on MM side trap_MM_GameState( true ); } break; case MATCH_STATE_POSTMATCH: { gs.gameState.stats[GAMESTAT_MATCHSTATE] = MATCH_STATE_POSTMATCH; gs.gameState.longstats[GAMELONG_MATCHDURATION] = (unsigned int)fabs( g_postmatch_timelimit->value * 1000 ); // postmatch time in seconds gs.gameState.longstats[GAMELONG_MATCHSTART] = game.serverTime; G_Timeout_Reset(); level.teamlock = false; level.forceExit = false; G_Match_Autorecord_Stats(); } break; case MATCH_STATE_WAITEXIT: { if( advance_queue ) { G_Teams_AdvanceChallengersQueue(); advance_queue = true; } gs.gameState.stats[GAMESTAT_MATCHSTATE] = MATCH_STATE_WAITEXIT; gs.gameState.longstats[GAMELONG_MATCHDURATION] = 25000; gs.gameState.longstats[GAMELONG_MATCHSTART] = game.serverTime; level.exitNow = false; } break; } // give the gametype the chance to setup for the new state if( game.asEngine != NULL ) GT_asCallMatchStateStarted(); else G_Gametype_GENERIC_MatchStateStarted(); G_UpdatePlayersMatchMsgs(); }
void G_AwardPlayerKilled( edict_t *self, edict_t *inflictor, edict_t *attacker, int mod ) { trace_t trace; score_stats_t *stats; loggedFrag_t *lfrag; if( self->r.svflags & SVF_CORPSE ) return; if( !attacker->r.client ) return; if( !self->r.client ) return; if( attacker == self ) return; if( attacker->s.team == self->s.team && attacker->s.team > TEAM_PLAYERS ) return; if( mod == MOD_ROCKET_W || mod == MOD_ROCKET_S ) { // direct hit attacker->r.client->resp.awardInfo.directrocket_count++; if( attacker->r.client->resp.awardInfo.directrocket_count == DIRECTROCKET_FOR_AWARD ) { attacker->r.client->resp.awardInfo.directrocket_count = 0; attacker->r.client->resp.awardInfo.directrocket_award++; G_PlayerAward( attacker, S_COLOR_BLUE "Direct Rocket Hit!" ); } // Midair if( self->groundentity == NULL && !self->waterlevel ) { // check for height to the ground G_Trace( &trace, self->s.origin, self->r.mins, self->r.maxs, tv( self->s.origin[0], self->s.origin[1], self->s.origin[2] - 64 ), self, MASK_SOLID ); if( trace.fraction == 1.0f ) { attacker->r.client->resp.awardInfo.rl_midair_award++; G_PlayerAward( attacker, S_COLOR_BLUE "Air Rocket!" ); } } } if( mod == MOD_GRENADE_W || mod == MOD_GRENADE_S ) { // direct hit attacker->r.client->resp.awardInfo.directgrenade_count++; if( attacker->r.client->resp.awardInfo.directgrenade_count == DIRECTGRENADE_FOR_AWARD ) { attacker->r.client->resp.awardInfo.directgrenade_count = 0; attacker->r.client->resp.awardInfo.directgrenade_award++; G_PlayerAward( attacker, S_COLOR_BLUE "Direct Grenade Hit!" ); } // Midair if( self->groundentity == NULL && !self->waterlevel ) { // check for height to the ground G_Trace( &trace, self->s.origin, self->r.mins, self->r.maxs, tv( self->s.origin[0], self->s.origin[1], self->s.origin[2] - 64 ), self, MASK_SOLID ); if( trace.fraction == 1.0f ) { attacker->r.client->resp.awardInfo.gl_midair_award++; G_PlayerAward( attacker, S_COLOR_BLUE "Air Grenade!" ); } } } // Multikill if( game.serverTime - attacker->r.client->resp.awardInfo.multifrag_timer < MULTIKILL_INTERVAL ) attacker->r.client->resp.awardInfo.multifrag_count++; else attacker->r.client->resp.awardInfo.multifrag_count = 1; attacker->r.client->resp.awardInfo.multifrag_timer = game.serverTime; if( attacker->r.client->resp.awardInfo.multifrag_count > 1 ) { char s[MAX_CONFIGSTRING_CHARS]; s[0] = 0; switch( attacker->r.client->resp.awardInfo.multifrag_count ) { case 0: case 1: break; case 2: Q_strncpyz( s, S_COLOR_GREEN "Double Frag!", sizeof( s ) ); break; case 3: Q_strncpyz( s, S_COLOR_GREEN "Triple Frag!", sizeof( s ) ); break; case 4: Q_strncpyz( s, S_COLOR_GREEN "Quadruple Frag!", sizeof( s ) ); break; default: Q_snprintfz( s, sizeof( s ), S_COLOR_GREEN "Extermination! %i in a row!", attacker->r.client->resp.awardInfo.multifrag_count ); break; } G_PlayerAward( attacker, s ); } // Sprees attacker->r.client->resp.awardInfo.frag_count++; if( attacker->r.client->resp.awardInfo.frag_count && ( attacker->r.client->resp.awardInfo.frag_count % 5 == 0 ) ) { char s[MAX_CONFIGSTRING_CHARS]; s[0] = 0; switch( (int)( attacker->r.client->resp.awardInfo.frag_count / 5 ) ) { case 1: Q_strncpyz( s, S_COLOR_YELLOW "On Fire!", sizeof( s ) ); G_PrintMsg( NULL, "%s" S_COLOR_YELLOW " is On Fire!\n", attacker->r.client->netname ); break; case 2: Q_strncpyz( s, S_COLOR_YELLOW "Raging!", sizeof( s ) ); G_PrintMsg( NULL, "%s" S_COLOR_YELLOW " is Raging!\n", attacker->r.client->netname ); break; case 3: Q_strncpyz( s, S_COLOR_YELLOW "Fraglord!", sizeof( s ) ); G_PrintMsg( NULL, "%s" S_COLOR_YELLOW " is the Fraglord!\n", attacker->r.client->netname ); break; case 4: Q_strncpyz( s, S_COLOR_YELLOW "Extermination!", sizeof( s ) ); G_PrintMsg( NULL, "%s" S_COLOR_YELLOW " is Exterminating!\n", attacker->r.client->netname ); break; default: Q_strncpyz( s, S_COLOR_YELLOW "God Mode!", sizeof( s ) ); G_PrintMsg( NULL, "%s" S_COLOR_YELLOW " is in God Mode!\n", attacker->r.client->netname ); break; } G_PlayerAward( attacker, s ); } // ch : weapon specific frags if ( G_ModToAmmo( mod ) != AMMO_NONE ) attacker->r.client->level.stats.accuracy_frags[G_ModToAmmo( mod )-AMMO_GUNBLADE]++; if( GS_MatchState() == MATCH_STATE_PLAYTIME /* && !strcmp( "duel", gs.gametypeName ) */) { // ch : frag log stats = &attacker->r.client->level.stats; if( !stats->fragAllocator ) stats->fragAllocator = LinearAllocator( sizeof( loggedFrag_t ), 0, _G_LevelMalloc, _G_LevelFree ); lfrag = LA_Alloc( stats->fragAllocator ); lfrag->mm_attacker = attacker->r.client->mm_session; lfrag->mm_victim = self->r.client->mm_session; lfrag->weapon = G_ModToAmmo( mod ) - AMMO_GUNBLADE; lfrag->time = ( game.serverTime - GS_MatchStartTime() ) / 1000; } }
/* * G_UpdateServerInfo * update the cvars which show the match state at server browsers */ static void G_UpdateServerInfo( void ) { // g_match_time if( GS_MatchState() <= MATCH_STATE_WARMUP ) { trap_Cvar_ForceSet( "g_match_time", "Warmup" ); } else if( GS_MatchState() == MATCH_STATE_COUNTDOWN ) { trap_Cvar_ForceSet( "g_match_time", "Countdown" ); } else if( GS_MatchState() == MATCH_STATE_PLAYTIME ) { // partly from G_GetMatchState char extra[MAX_INFO_VALUE]; int clocktime, timelimit, mins, secs; if( GS_MatchDuration() ) timelimit = ( ( GS_MatchDuration() ) * 0.001 ) / 60; else timelimit = 0; clocktime = (float)( game.serverTime - GS_MatchStartTime() ) * 0.001f; if( clocktime <= 0 ) { mins = 0; secs = 0; } else { mins = clocktime / 60; secs = clocktime - mins * 60; } extra[0] = 0; if( GS_MatchExtended() ) { if( timelimit ) Q_strncatz( extra, " overtime", sizeof( extra ) ); else Q_strncatz( extra, " suddendeath", sizeof( extra ) ); } if( GS_MatchPaused() ) Q_strncatz( extra, " (in timeout)", sizeof( extra ) ); if( timelimit ) trap_Cvar_ForceSet( "g_match_time", va( "%02i:%02i / %02i:00%s", mins, secs, timelimit, extra ) ); else trap_Cvar_ForceSet( "g_match_time", va( "%02i:%02i%s", mins, secs, extra ) ); } else { trap_Cvar_ForceSet( "g_match_time", "Finished" ); } // g_match_score if( GS_MatchState() >= MATCH_STATE_PLAYTIME && GS_TeamBasedGametype() ) { char score[MAX_INFO_STRING]; score[0] = 0; Q_strncatz( score, va( " %s: %i", GS_TeamName( TEAM_ALPHA ), teamlist[TEAM_ALPHA].stats.score ), sizeof( score ) ); Q_strncatz( score, va( " %s: %i", GS_TeamName( TEAM_BETA ), teamlist[TEAM_BETA].stats.score ), sizeof( score ) ); if( strlen( score ) >= MAX_INFO_VALUE ) { // prevent "invalid info cvar value" flooding score[0] = '\0'; } trap_Cvar_ForceSet( "g_match_score", score ); } else { trap_Cvar_ForceSet( "g_match_score", "" ); } // g_needpass if( password->modified ) { if( password->string && strlen( password->string ) ) { trap_Cvar_ForceSet( "g_needpass", "1" ); } else { trap_Cvar_ForceSet( "g_needpass", "0" ); } password->modified = false; } // g_gametypes_available if( g_votable_gametypes->modified || g_disable_vote_gametype->modified ) { if( g_disable_vote_gametype->integer || !g_votable_gametypes->string || !strlen( g_votable_gametypes->string ) ) { trap_Cvar_ForceSet( "g_gametypes_available", "" ); } else { char *votable; char *name; size_t len; int count; len = 0; for( count = 0; ( name = G_ListNameForPosition( g_gametypes_list->string, count, CHAR_GAMETYPE_SEPARATOR ) ) != NULL; count++ ) { if( G_Gametype_IsVotable( name ) ) len += strlen( name ) + 1; } len++; votable = ( char * )G_Malloc( len ); votable[0] = 0; for( count = 0; ( name = G_ListNameForPosition( g_gametypes_list->string, count, CHAR_GAMETYPE_SEPARATOR ) ) != NULL; count++ ) { if( G_Gametype_IsVotable( name ) ) { Q_strncatz( votable, name, len ); Q_strncatz( votable, " ", len ); } } //votable[ strlen( votable )-2 ] = 0; // remove the last space trap_Cvar_ForceSet( "g_gametypes_available", votable ); G_Free( votable ); } g_votable_gametypes->modified = false; g_disable_vote_gametype->modified = false; } if( GS_RaceGametype() ) { trap_Cvar_ForceSet( "g_race_gametype", "1" ); } else { trap_Cvar_ForceSet( "g_race_gametype", "0" ); } }
/* * ClientThink */ void ClientThink( edict_t *ent, usercmd_t *ucmd, int timeDelta ) { gclient_t *client; int i, j; static pmove_t pm; int delta, count; client = ent->r.client; client->ps.POVnum = ENTNUM( ent ); client->ps.playerNum = PLAYERNUM( ent ); // anti-lag if( ent->r.svflags & SVF_FAKECLIENT ) { client->timeDelta = 0; } else { int nudge; int fixedNudge = ( game.snapFrameTime ) * 0.5; // fixme: find where this nudge comes from. // add smoothing to timeDelta between the last few ucmds and a small fine-tuning nudge. nudge = fixedNudge + g_antilag_timenudge->integer; timeDelta += nudge; clamp( timeDelta, -g_antilag_maxtimedelta->integer, 0 ); // smooth using last valid deltas i = client->timeDeltasHead - 6; if( i < 0 ) i = 0; for( count = 0, delta = 0; i < client->timeDeltasHead; i++ ) { if( client->timeDeltas[i & G_MAX_TIME_DELTAS_MASK] < 0 ) { delta += client->timeDeltas[i & G_MAX_TIME_DELTAS_MASK]; count++; } } if( !count ) client->timeDelta = timeDelta; else { delta /= count; client->timeDelta = ( delta + timeDelta ) * 0.5; } client->timeDeltas[client->timeDeltasHead & G_MAX_TIME_DELTAS_MASK] = timeDelta; client->timeDeltasHead++; #ifdef UCMDTIMENUDGE client->timeDelta += client->pers.ucmdTimeNudge; #endif } clamp( client->timeDelta, -g_antilag_maxtimedelta->integer, 0 ); // update activity if he touched any controls if( ucmd->forwardmove != 0 || ucmd->sidemove != 0 || ucmd->upmove != 0 || ( ucmd->buttons & ~BUTTON_BUSYICON ) != 0 || client->ucmd.angles[PITCH] != ucmd->angles[PITCH] || client->ucmd.angles[YAW] != ucmd->angles[YAW] ) G_Client_UpdateActivity( client ); client->ucmd = *ucmd; // can exit intermission after two seconds, not counting postmatch if( GS_MatchState() == MATCH_STATE_WAITEXIT && ( ucmd->buttons & BUTTON_ATTACK ) && game.serverTime > GS_MatchStartTime() + 2000 ) level.exitNow = true; // (is this really needed?:only if not cared enough about ps in the rest of the code) // refresh player state position from the entity VectorCopy( ent->s.origin, client->ps.pmove.origin ); VectorCopy( ent->velocity, client->ps.pmove.velocity ); VectorCopy( ent->s.angles, client->ps.viewangles ); client->ps.pmove.gravity = level.gravity; if( GS_MatchState() >= MATCH_STATE_POSTMATCH || GS_MatchPaused() || ( ent->movetype != MOVETYPE_PLAYER && ent->movetype != MOVETYPE_NOCLIP ) ) client->ps.pmove.pm_type = PM_FREEZE; else if( ent->s.type == ET_GIB ) client->ps.pmove.pm_type = PM_GIB; else if( ent->movetype == MOVETYPE_NOCLIP || client->isTV ) client->ps.pmove.pm_type = PM_SPECTATOR; else client->ps.pmove.pm_type = PM_NORMAL; // set up for pmove memset( &pm, 0, sizeof( pmove_t ) ); pm.playerState = &client->ps; if( !client->isTV ) pm.cmd = *ucmd; if( memcmp( &client->old_pmove, &client->ps.pmove, sizeof( pmove_state_t ) ) ) pm.snapinitial = true; // perform a pmove Pmove( &pm ); // save results of pmove client->old_pmove = client->ps.pmove; // update the entity with the new position VectorCopy( client->ps.pmove.origin, ent->s.origin ); VectorCopy( client->ps.pmove.velocity, ent->velocity ); VectorCopy( client->ps.viewangles, ent->s.angles ); ent->viewheight = client->ps.viewheight; VectorCopy( pm.mins, ent->r.mins ); VectorCopy( pm.maxs, ent->r.maxs ); ent->waterlevel = pm.waterlevel; ent->watertype = pm.watertype; if( pm.groundentity == -1 ) { ent->groundentity = NULL; } else { G_AwardResetPlayerComboStats( ent ); ent->groundentity = &game.edicts[pm.groundentity]; ent->groundentity_linkcount = ent->groundentity->linkcount; } GClip_LinkEntity( ent ); GS_AddLaserbeamPoint( &ent->r.client->resp.trail, &ent->r.client->ps, ucmd->serverTimeStamp ); // Regeneration if( ent->r.client->ps.inventory[POWERUP_REGEN] > 0 && ent->health < 200) { ent->health += ( game.frametime * 0.001f ) * 10.0f; // Regen expires if health reaches 200 if ( ent->health >= 199.0f ) ent->r.client->ps.inventory[POWERUP_REGEN]--; } // fire touch functions if( ent->movetype != MOVETYPE_NOCLIP ) { edict_t *other; // touch other objects for( i = 0; i < pm.numtouch; i++ ) { other = &game.edicts[pm.touchents[i]]; for( j = 0; j < i; j++ ) { if( &game.edicts[pm.touchents[j]] == other ) break; } if( j != i ) continue; // duplicated // player can't touch projectiles, only projectiles can touch the player G_CallTouch( other, ent, NULL, 0 ); } } ent->s.weapon = GS_ThinkPlayerWeapon( &client->ps, ucmd->buttons, ucmd->msec, client->timeDelta ); if( G_IsDead( ent ) ) { if( ent->deathTimeStamp + g_respawn_delay_min->integer <= level.time ) client->resp.snap.buttons |= ucmd->buttons; } else if( client->ps.pmove.stats[PM_STAT_NOUSERCONTROL] <= 0 ) client->resp.snap.buttons |= ucmd->buttons; // trigger the instashield if( GS_Instagib() && g_instashield->integer ) { if( client->ps.pmove.pm_type == PM_NORMAL && pm.cmd.upmove < 0 && client->resp.instashieldCharge == INSTA_SHIELD_MAX && client->ps.inventory[POWERUP_SHELL] == 0 ) { client->ps.inventory[POWERUP_SHELL] = client->resp.instashieldCharge; G_Sound( ent, CHAN_AUTO, trap_SoundIndex( GS_FindItemByTag( POWERUP_SHELL )->pickup_sound ), ATTN_NORM ); } } // if( client->ps.pmove.pm_type == PM_NORMAL ) client->level.stats.had_playtime = true; // generating plrkeys (optimized for net communication) ClientMakePlrkeys( client, ucmd ); }
/* * CG_AddLocalSounds */ static void CG_AddLocalSounds( void ) { static bool postmatchsound_set = false, demostream = false, background = false; static unsigned int lastSecond = 0; // add local announces if( GS_Countdown() ) { if( GS_MatchDuration() ) { unsigned int duration, curtime, remainingSeconds; float seconds; curtime = GS_MatchPaused() ? cg.frame.serverTime : cg.time; duration = GS_MatchDuration(); if( duration + GS_MatchStartTime() < curtime ) duration = curtime - GS_MatchStartTime(); // avoid negative results seconds = (float)( GS_MatchStartTime() + duration - curtime ) * 0.001f; remainingSeconds = (unsigned int)seconds; if( remainingSeconds != lastSecond ) { if( 1 + remainingSeconds < 4 ) { struct sfx_s *sound = trap_S_RegisterSound( va( S_ANNOUNCER_COUNTDOWN_COUNT_1_to_3_SET_1_to_2, 1 + remainingSeconds, 1 ) ); CG_AddAnnouncerEvent( sound, false ); } lastSecond = remainingSeconds; } } } else lastSecond = 0; // add sounds from announcer CG_ReleaseAnnouncerEvents(); // if in postmatch, play postmatch song if( GS_MatchState() >= MATCH_STATE_POSTMATCH ) { if( !postmatchsound_set && !demostream ) { trap_S_StartBackgroundTrack( S_PLAYLIST_POSTMATCH, NULL, 3 ); // loop random track from the playlist postmatchsound_set = true; background = false; } } else { if( cgs.demoPlaying && cgs.demoAudioStream && !demostream ) { trap_S_StartBackgroundTrack( cgs.demoAudioStream, NULL, 0 ); demostream = true; } if( postmatchsound_set ) { trap_S_StopBackgroundTrack(); postmatchsound_set = false; background = false; } if( (!postmatchsound_set && !demostream) && !background ) { CG_StartBackgroundTrack(); background = true; } // notice: these 2 sound files aren't used anymore //cgs.media.sfxTimerBipBip //cgs.media.sfxTimerPloink } }