/* * G_ClearSnap * We just run G_SnapFrame, the server just sent the snap to the clients, * it's now time to clean up snap specific data to start the next snap from clean. */ void G_ClearSnap( void ) { edict_t *ent; game.realtime = trap_Milliseconds(); // level.time etc. might not be real time // clear gametype's clock override gs.gameState.longstats[GAMELONG_CLOCKOVERRIDE] = 0; // clear all events in the snap for( ent = &game.edicts[0]; ENTNUM( ent ) < game.numentities; ent++ ) { if( ISEVENTENTITY( &ent->s ) ) // events do not persist after a snapshot { G_FreeEdict( ent ); continue; } // events only last for a single message ent->s.events[0] = ent->s.events[1] = 0; ent->s.eventParms[0] = ent->s.eventParms[1] = 0; ent->numEvents = 0; ent->eventPriority[0] = ent->eventPriority[1] = false; ent->s.teleported = qfalse; // remove teleported bit. // remove effect bits that are (most likely) added from gametypes ent->s.effects = ( ent->s.effects & (EF_TAKEDAMAGE|EF_CARRIER|EF_FLAG_TRAIL|EF_ROTATE_AND_BOB|EF_STRONG_WEAPON|EF_GHOST) ); } // recover some info, let players respawn and finally clear the snap structures for( ent = &game.edicts[0]; ENTNUM( ent ) < game.numentities; ent++ ) { if( !GS_MatchPaused() ) { // copy origin to old origin ( this old_origin is for snaps ) if( !( ent->r.svflags & SVF_TRANSMITORIGIN2 ) ) VectorCopy( ent->s.origin, ent->s.old_origin ); G_CheckClientRespawnClick( ent ); } if( GS_MatchPaused() ) ent->s.sound = entity_sound_backup[ENTNUM( ent )]; // clear the snap temp info memset( &ent->snap, 0, sizeof( ent->snap ) ); if( ent->r.client && trap_GetClientState( PLAYERNUM( ent ) ) >= CS_SPAWNED ) { memset( &ent->r.client->resp.snap, 0, sizeof( ent->r.client->resp.snap ) ); // set race stats to invisible RS_clearHUDStats( ent->r.client ); // racesow - clear with our function } } g_snapStarted = false; }
/* * G_RunFrame * Advances the world */ void G_RunFrame( unsigned int msec, unsigned int serverTime ) { G_CheckCvars(); game.localTime = time( NULL ); game.serverTime = serverTime; G_UpdateFrameTime( msec ); if( !g_snapStarted ) G_StartFrameSnap(); G_CallVotes_Think(); // "freeze" match clock if( GS_MatchWaiting() || GS_MatchPaused() ) { gs.gameState.longstats[GAMELONG_MATCHSTART] += msec; } if( GS_MatchPaused() ) { edict_t *ent; // "freeze" linear projectiles for( ent = game.edicts + gs.maxclients; ENTNUM( ent ) < game.numentities; ent++ ) { if( ent->s.linearProjectile ) ent->s.linearProjectileTimeStamp += msec; } G_RunClients(); G_RunGametype(); G_LevelGarbageCollect(); return; } level.framenum++; level.time += msec; level.think_client_entity = G_GetNextThinkClient( level.think_client_entity ); G_SpawnQueue_Think(); // run the world G_asCallMapPreThink(); G_RunClients(); G_RunEntities(); G_RunGametype(); G_asCallMapPostThink(); GClip_BackUpCollisionFrame(); G_LevelGarbageCollect(); }
/* * Cmd_Timeout_f */ static void Cmd_Timeout_f( edict_t *ent ) { int num; if( ent->s.team == TEAM_SPECTATOR || GS_MatchState() != MATCH_STATE_PLAYTIME ) return; if( GS_TeamBasedGametype() ) num = ent->s.team; else num = ENTNUM( ent )-1; if( GS_MatchPaused() && ( level.timeout.endtime - level.timeout.time ) >= 2*TIMEIN_TIME ) { G_PrintMsg( ent, "Timeout already in progress\n" ); return; } if( g_maxtimeouts->integer != -1 && level.timeout.used[num] >= g_maxtimeouts->integer ) { if( g_maxtimeouts->integer == 0 ) G_PrintMsg( ent, "Timeouts are not allowed on this server\n" ); else if( GS_TeamBasedGametype() ) G_PrintMsg( ent, "Your team doesn't have any timeouts left\n" ); else G_PrintMsg( ent, "You don't have any timeouts left\n" ); return; } G_PrintMsg( NULL, "%s%s called a timeout\n", ent->r.client->netname, S_COLOR_WHITE ); if( !GS_MatchPaused() ) G_AnnouncerSound( NULL, trap_SoundIndex( va( S_ANNOUNCER_TIMEOUT_TIMEOUT_1_to_2, ( rand()&1 )+1 ) ), GS_MAX_TEAMS, true, NULL ); level.timeout.used[num]++; GS_GamestatSetFlag( GAMESTAT_FLAG_PAUSED, true ); level.timeout.caller = num; level.timeout.endtime = level.timeout.time + TIMEOUT_TIME + FRAMETIME; }
void G_Teams_Coach( edict_t *ent ) { if( GS_TeamBasedGametype() && !GS_InvidualGameType() && ent->s.team != TEAM_SPECTATOR ) { if( !teamlist[ent->s.team].has_coach ) { if( GS_MatchState() > MATCH_STATE_WARMUP && !GS_MatchPaused() ) { G_PrintMsg( ent, "Can't set coach mode with the match in progress\n" ); } else { // move to coach mode ent->r.client->teamstate.is_coach = true; G_GhostClient( ent ); ent->health = ent->max_health; ent->deadflag = DEAD_NO; G_ChasePlayer( ent, NULL, true, 0 ); //clear up his scores G_Match_Ready( ent ); // set ready and check readys memset( &ent->r.client->level.stats, 0, sizeof( ent->r.client->level.stats ) ); teamlist[ent->s.team].has_coach = true; G_PrintMsg( NULL, "%s%s is now team %s coach \n", ent->r.client->netname, S_COLOR_WHITE, GS_TeamName( ent->s.team ) ); } } else if( ent->r.client->teamstate.is_coach ) { // if you are this team coach, resign ent->r.client->teamstate.is_coach = false; G_PrintMsg( NULL, "%s%s is no longer team %s coach \n", ent->r.client->netname, S_COLOR_WHITE, GS_TeamName( ent->s.team ) ); G_Teams_SetTeam( ent, ent->s.team ); } else G_PrintMsg( ent, "Your team already has a coach.\n" ); } else G_PrintMsg( ent, "Coaching only valid while on a team in Team based Gametypes.\n" ); }
/* * Cmd_Timeout_f */ static void Cmd_Timein_f( edict_t *ent ) { int num; if( ent->s.team == TEAM_SPECTATOR ) return; if( !GS_MatchPaused() ) { G_PrintMsg( ent, "No timeout in progress.\n" ); return; } if( level.timeout.endtime - level.timeout.time <= 2 * TIMEIN_TIME ) { G_PrintMsg( ent, "The timeout is about to end already.\n" ); return; } if( GS_TeamBasedGametype() ) num = ent->s.team; else num = ENTNUM( ent )-1; if( level.timeout.caller != num ) { if( GS_TeamBasedGametype() ) G_PrintMsg( ent, "Your team didn't call this timeout.\n" ); else G_PrintMsg( ent, "You didn't call this timeout.\n" ); return; } level.timeout.endtime = level.timeout.time + TIMEIN_TIME + FRAMETIME; G_AnnouncerSound( NULL, trap_SoundIndex( va( S_ANNOUNCER_TIMEOUT_TIMEIN_1_to_2, ( rand()&1 )+1 ) ), GS_MAX_TEAMS, true, NULL ); G_PrintMsg( NULL, "%s%s called a timein\n", ent->r.client->netname, S_COLOR_WHITE ); }
bool AI_NodeHasTimedOut( edict_t *self ) { if( self->ai->goal_node == NODE_INVALID ) return true; if( !GS_MatchPaused() ) self->ai->node_timeout += game.frametime; // Try again? if( self->ai->node_timeout > NODE_TIMEOUT || self->ai->next_node == NODE_INVALID ) { if( self->ai->tries++ > 3 ) return true; else AI_SetGoal( self, self->ai->goal_node ); } if( self->ai->current_node == NODE_INVALID || self->ai->next_node == NODE_INVALID ) return true; return false; }
static void Cmd_CoinToss_f( edict_t *ent ) { bool qtails; char *s; char upper[MAX_STRING_CHARS]; if( GS_MatchState() > MATCH_STATE_WARMUP && !GS_MatchPaused() ) { G_PrintMsg( ent, "You can only toss coins during warmup or timeouts\n" ); return; } if( CheckFlood( ent, false ) ) return; if( trap_Cmd_Argc() < 2 || ( Q_stricmp( "heads", trap_Cmd_Argv( 1 ) ) && Q_stricmp( "tails", trap_Cmd_Argv( 1 ) ) ) ) { //it isn't a valid token G_PrintMsg( ent, "You have to choose heads or tails when tossing a coin\n" ); return; } Q_strncpyz( upper, trap_Cmd_Argv( 1 ), sizeof( upper ) ); s = upper; while( *s ) { *s = toupper( *s ); s++; } qtails = ( Q_stricmp( "heads", trap_Cmd_Argv( 1 ) ) != 0 ) ? true : false; if( qtails == ( rand() & 1 ) ) { G_PrintMsg( NULL, S_COLOR_YELLOW "COINTOSS %s: " S_COLOR_WHITE "It was %s! %s " S_COLOR_WHITE "tossed a coin and " S_COLOR_GREEN "won!\n", upper, trap_Cmd_Argv( 1 ), ent->r.client->netname ); return; } G_PrintMsg( NULL, S_COLOR_YELLOW "COINTOSS %s: " S_COLOR_WHITE "It was %s! %s " S_COLOR_WHITE "tossed a coin and " S_COLOR_RED "lost!\n", upper, qtails ? "heads" : "tails", ent->r.client->netname ); }
/* * 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 ); }
/* * G_SnapFrame * It's time to send a new snap, so set the world up for sending */ void G_SnapFrame( void ) { edict_t *ent; game.realtime = trap_Milliseconds(); // level.time etc. might not be real time //others G_UpdateServerInfo(); // exit level if( level.exitNow ) { G_ExitLevel(); return; } AITools_Frame(); //MbotGame //give think time to AI debug tools // finish snap G_SnapClients(); // build the playerstate_t structures for all players G_SnapEntities(); // add effects based on accumulated info along the frame // set entity bits (prepare entities for being sent in the snap) for( ent = &game.edicts[0]; ENTNUM( ent ) < game.numentities; ent++ ) { if( ent->s.number != ENTNUM( ent ) ) { if( developer->integer ) G_Printf( "fixing ent->s.number (etype:%i, classname:%s)\n", ent->s.type, ent->classname ? ent->classname : "noclassname" ); ent->s.number = ENTNUM( ent ); } // temporary filter (Q2 system to ensure reliability) // ignore ents without visible models unless they have an effect if( !ent->r.inuse ) { ent->r.svflags |= SVF_NOCLIENT; continue; } else if( ent->s.type >= ET_TOTAL_TYPES || ent->s.type < 0 ) { if( developer->integer ) G_Printf( "'G_SnapFrame': Inhibiting invalid entity type %i\n", ent->s.type ); ent->r.svflags |= SVF_NOCLIENT; continue; } else if( !( ent->r.svflags & SVF_NOCLIENT ) && !ent->s.modelindex && !ent->s.effects && !ent->s.sound && !ISEVENTENTITY( &ent->s ) && !ent->s.light && !ent->r.client ) { if( developer->integer ) G_Printf( "'G_SnapFrame': fixing missing SVF_NOCLIENT flag (no effect)\n" ); ent->r.svflags |= SVF_NOCLIENT; continue; } ent->s.effects &= ~EF_TAKEDAMAGE; if( ent->takedamage ) ent->s.effects |= EF_TAKEDAMAGE; if( GS_MatchPaused() ) { // when in timeout, we don't send entity sounds entity_sound_backup[ENTNUM( ent )] = ent->s.sound; ent->s.sound = 0; } } }
/* * G_Timeout_Update * * Updates the timeout struct and informs clients about the status of the pause */ static void G_Timeout_Update( unsigned int msec ) { static int timeout_printtime = 0; static int timeout_last_endtime = 0; static int countdown_set = 1; if( !GS_MatchPaused() ) return; game.frametime = 0; if( timeout_last_endtime != level.timeout.endtime ) // force print when endtime is changed { timeout_printtime = 0; timeout_last_endtime = level.timeout.endtime; } level.timeout.time += msec; if( level.timeout.endtime && level.timeout.time >= level.timeout.endtime ) { level.timeout.time = 0; level.timeout.caller = -1; GS_GamestatSetFlag( GAMESTAT_FLAG_PAUSED, false ); timeout_printtime = 0; timeout_last_endtime = -1; G_AnnouncerSound( NULL, trap_SoundIndex( va( S_ANNOUNCER_TIMEOUT_MATCH_RESUMED_1_to_2, ( rand()&1 )+1 ) ), GS_MAX_TEAMS, true, NULL ); G_CenterPrintMsg( NULL, "Match resumed" ); G_PrintMsg( NULL, "Match resumed\n" ); } else if( timeout_printtime == 0 || level.timeout.time - timeout_printtime >= 1000 ) { if( level.timeout.endtime ) { int seconds_left = (int)( ( level.timeout.endtime - level.timeout.time ) / 1000.0 + 0.5 ); if( seconds_left == ( TIMEIN_TIME * 2 ) / 1000 ) { G_AnnouncerSound( NULL, trap_SoundIndex( va( S_ANNOUNCER_COUNTDOWN_READY_1_to_2, ( rand()&1 )+1 ) ), GS_MAX_TEAMS, false, NULL ); countdown_set = ( rand()&1 )+1; } else if( seconds_left >= 1 && seconds_left <= 3 ) { G_AnnouncerSound( NULL, trap_SoundIndex( va( S_ANNOUNCER_COUNTDOWN_COUNT_1_to_3_SET_1_to_2, seconds_left, countdown_set ) ), GS_MAX_TEAMS, false, NULL ); } if( seconds_left > 1 ) G_CenterPrintFormatMsg( NULL, "Match will resume in %s seconds", va( "%i", seconds_left ), NULL ); else G_CenterPrintMsg( NULL, "Match will resume in 1 second" ); } else { G_CenterPrintMsg( NULL, "Match paused" ); } timeout_printtime = level.timeout.time; } }
/* * 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" ); } }
/* * GS_ThinkPlayerWeapon */ int GS_ThinkPlayerWeapon( player_state_t *playerState, int buttons, int msecs, int timeDelta ) { firedef_t *firedef; qboolean refire = qfalse; assert( playerState->stats[STAT_PENDING_WEAPON] >= 0 && playerState->stats[STAT_PENDING_WEAPON] < WEAP_TOTAL ); if( GS_MatchPaused() ) return playerState->stats[STAT_WEAPON]; if( playerState->pmove.pm_type != PM_NORMAL ) { playerState->weaponState = WEAPON_STATE_READY; playerState->stats[STAT_PENDING_WEAPON] = playerState->stats[STAT_WEAPON] = WEAP_NONE; playerState->stats[STAT_WEAPON_TIME] = 0; return playerState->stats[STAT_WEAPON]; } if( playerState->pmove.stats[PM_STAT_NOUSERCONTROL] > 0 ) buttons = 0; if( !msecs ) goto done; if( playerState->stats[STAT_WEAPON_TIME] > 0 ) playerState->stats[STAT_WEAPON_TIME] -= msecs; else playerState->stats[STAT_WEAPON_TIME] = 0; firedef = GS_FiredefForPlayerState( playerState, playerState->stats[STAT_WEAPON] ); // during cool-down time it can shoot again or go into reload time if( playerState->weaponState == WEAPON_STATE_REFIRE || playerState->weaponState == WEAPON_STATE_REFIRESTRONG ) { int last_firemode; if( playerState->stats[STAT_WEAPON_TIME] > 0 ) goto done; last_firemode = ( playerState->weaponState == WEAPON_STATE_REFIRESTRONG ) ? FIRE_MODE_STRONG : FIRE_MODE_WEAK; if( last_firemode == firedef->fire_mode ) refire = qtrue; playerState->weaponState = WEAPON_STATE_READY; } // nothing can be done during reload time if( playerState->weaponState == WEAPON_STATE_RELOADING ) { if( playerState->stats[STAT_WEAPON_TIME] > 0 ) goto done; playerState->weaponState = WEAPON_STATE_READY; } if( playerState->weaponState == WEAPON_STATE_NOAMMOCLICK ) { if( playerState->stats[STAT_WEAPON_TIME] > 0 ) goto done; if( playerState->stats[STAT_WEAPON] != playerState->stats[STAT_PENDING_WEAPON] ) playerState->weaponState = WEAPON_STATE_READY; } // there is a weapon to be changed if( playerState->stats[STAT_WEAPON] != playerState->stats[STAT_PENDING_WEAPON] ) { if( ( playerState->weaponState == WEAPON_STATE_READY ) || ( playerState->weaponState == WEAPON_STATE_DROPPING ) || ( playerState->weaponState == WEAPON_STATE_ACTIVATING ) ) { if( playerState->weaponState != WEAPON_STATE_DROPPING ) { playerState->weaponState = WEAPON_STATE_DROPPING; playerState->stats[STAT_WEAPON_TIME] += firedef->weapondown_time; if( firedef->weapondown_time ) module_PredictedEvent( playerState->POVnum, EV_WEAPONDROP, 0 ); } } } // do the change if( playerState->weaponState == WEAPON_STATE_DROPPING ) { if( playerState->stats[STAT_WEAPON_TIME] > 0 ) goto done; playerState->stats[STAT_WEAPON] = playerState->stats[STAT_PENDING_WEAPON]; // update the firedef firedef = GS_FiredefForPlayerState( playerState, playerState->stats[STAT_WEAPON] ); playerState->weaponState = WEAPON_STATE_ACTIVATING; playerState->stats[STAT_WEAPON_TIME] += firedef->weaponup_time; module_PredictedEvent( playerState->POVnum, EV_WEAPONACTIVATE, playerState->stats[STAT_WEAPON] ); } if( playerState->weaponState == WEAPON_STATE_ACTIVATING ) { if( playerState->stats[STAT_WEAPON_TIME] > 0 ) goto done; playerState->weaponState = WEAPON_STATE_READY; } if( ( playerState->weaponState == WEAPON_STATE_READY ) || ( playerState->weaponState == WEAPON_STATE_NOAMMOCLICK ) ) { if( playerState->stats[STAT_WEAPON_TIME] > 0 ) goto done; if( !GS_ShootingDisabled() ) { if( buttons & BUTTON_ATTACK ) { if( GS_CheckAmmoInWeapon( playerState, playerState->stats[STAT_WEAPON] ) ) { playerState->weaponState = WEAPON_STATE_FIRING; } else { // player has no ammo nor clips if( playerState->weaponState == WEAPON_STATE_NOAMMOCLICK ) { playerState->weaponState = WEAPON_STATE_RELOADING; playerState->stats[STAT_WEAPON_TIME] += NOAMMOCLICK_AUTOSWITCH; if( playerState->stats[STAT_PENDING_WEAPON] == playerState->stats[STAT_WEAPON] ) playerState->stats[STAT_PENDING_WEAPON] = GS_SelectBestWeapon( playerState ); } else { playerState->weaponState = WEAPON_STATE_NOAMMOCLICK; playerState->stats[STAT_WEAPON_TIME] += NOAMMOCLICK_PENALTY; module_PredictedEvent( playerState->POVnum, EV_NOAMMOCLICK, 0 ); goto done; } } } // gunblade auto attack is special else if( playerState->stats[STAT_WEAPON] == WEAP_GUNBLADE && playerState->pmove.stats[PM_STAT_NOUSERCONTROL] <= 0 && playerState->pmove.stats[PM_STAT_NOAUTOATTACK] <= 0 && GS_CheckBladeAutoAttack( playerState, timeDelta ) ) { firedef = &GS_GetWeaponDef( WEAP_GUNBLADE )->firedef_weak; playerState->weaponState = WEAPON_STATE_FIRING; } } } if( playerState->weaponState == WEAPON_STATE_FIRING ) { int parm = playerState->stats[STAT_WEAPON]; if( firedef->fire_mode == FIRE_MODE_STRONG ) parm |= EV_INVERSE; playerState->stats[STAT_WEAPON_TIME] += firedef->reload_time; if( firedef->fire_mode == FIRE_MODE_STRONG ) playerState->weaponState = WEAPON_STATE_REFIRESTRONG; else playerState->weaponState = WEAPON_STATE_REFIRE; if( refire && firedef->smooth_refire ) module_PredictedEvent( playerState->POVnum, EV_SMOOTHREFIREWEAPON, parm ); else module_PredictedEvent( playerState->POVnum, EV_FIREWEAPON, parm ); // waste ammo if( !GS_InfiniteAmmo() ) { if( firedef->ammo_id != AMMO_NONE && firedef->usage_count ) playerState->inventory[firedef->ammo_id] -= firedef->usage_count; } } done: return playerState->stats[STAT_WEAPON]; }
/* * G_Damage * targ entity that is being damaged * inflictor entity that is causing the damage * attacker entity that caused the inflictor to damage targ * example: targ=enemy, inflictor=rocket, attacker=player * * dir direction of the attack * point point at which the damage is being inflicted * normal normal vector from that point * damage amount of damage being inflicted * knockback force to be applied against targ as a result of the damage * * dflags these flags are used to control how T_Damage works */ void G_Damage( edict_t *targ, edict_t *inflictor, edict_t *attacker, const vec3_t pushdir, const vec3_t dmgdir, const vec3_t point, float damage, float knockback, float stun, int dflags, int mod ) { gclient_t *client; float take; float save; float asave; qboolean statDmg; if( !targ || !targ->takedamage ) return; if( !attacker ) { attacker = world; mod = MOD_TRIGGER_HURT; } meansOfDeath = mod; client = targ->r.client; // Cgg - race mode: players don't interact with one another if( GS_RaceGametype() ) { if( attacker->r.client && targ->r.client && attacker != targ ) return; } // push if( !( dflags & DAMAGE_NO_KNOCKBACK ) ) G_KnockBackPush( targ, attacker, pushdir, knockback, dflags ); // stun if( g_allow_stun->integer && !( dflags & (DAMAGE_NO_STUN|FL_GODMODE) ) && (int)stun > 0 && targ->r.client && targ->r.client->resp.takeStun && !GS_IsTeamDamage( &targ->s, &attacker->s ) && ( targ != attacker ) ) { if( dflags & DAMAGE_STUN_CLAMP ) { if( targ->r.client->ps.pmove.stats[PM_STAT_STUN] < (int)stun ) targ->r.client->ps.pmove.stats[PM_STAT_STUN] = (int)stun; } else targ->r.client->ps.pmove.stats[PM_STAT_STUN] += (int)stun; clamp( targ->r.client->ps.pmove.stats[PM_STAT_STUN], 0, MAX_STUN_TIME ); } // dont count self-damage cause it just adds the same to both stats statDmg = ( attacker != targ ) && ( mod != MOD_TELEFRAG ); // apply handicap on the damage given if( statDmg && attacker->r.client && !GS_Instagib() ) { // handicap is a percentage value if( attacker->r.client->handicap != 0 ) damage *= 1.0 - (attacker->r.client->handicap * 0.01f); } take = damage; save = 0; // check for cases where damage is protected if( !( dflags & DAMAGE_NO_PROTECTION ) ) { // check for godmode if( targ->flags & FL_GODMODE ) { take = 0; save = damage; } // never damage in timeout else if( GS_MatchPaused() ) { take = save = 0; } // ca has self splash damage disabled else if( ( dflags & DAMAGE_RADIUS ) && attacker == targ && !GS_SelfDamage() ) { take = save = 0; } // don't get damage from players in race else if( ( GS_RaceGametype() ) && attacker->r.client && targ->r.client && ( attacker->r.client != targ->r.client ) ) { take = save = 0; } // team damage avoidance else if( GS_IsTeamDamage( &targ->s, &attacker->s ) && !G_Gametype_CanTeamDamage( dflags ) ) { take = save = 0; } // apply warShell powerup protection else if( targ->r.client && targ->r.client->ps.inventory[POWERUP_SHELL] > 0 ) { // warshell offers full protection in instagib if( GS_Instagib() ) { take = 0; save = damage; } else { take = ( damage * 0.25f ); save = damage - take; } // todo : add protection sound } } asave = G_CheckArmor( targ, take, dflags ); take -= asave; //treat cheat/powerup savings the same as armor asave += save; // APPLY THE DAMAGES if( !take && !asave ) return; // do the damage if( take <= 0 ) return; // adding damage given/received to stats if( statDmg && attacker->r.client && !targ->deadflag && targ->movetype != MOVETYPE_PUSH && targ->s.type != ET_CORPSE ) { attacker->r.client->level.stats.total_damage_given += take + asave; teamlist[attacker->s.team].stats.total_damage_given += take + asave; if( GS_IsTeamDamage( &targ->s, &attacker->s ) ) { attacker->r.client->level.stats.total_teamdamage_given += take + asave; teamlist[attacker->s.team].stats.total_teamdamage_given += take + asave; } } G_Gametype_ScoreEvent( attacker->r.client, "dmg", va( "%i %f %i", targ->s.number, damage, attacker->s.number ) ); if( statDmg && client ) { client->level.stats.total_damage_received += take + asave; teamlist[targ->s.team].stats.total_damage_received += take + asave; if( GS_IsTeamDamage( &targ->s, &attacker->s ) ) { client->level.stats.total_teamdamage_received += take + asave; teamlist[targ->s.team].stats.total_teamdamage_received += take + asave; } } // accumulate received damage for snapshot effects { vec3_t dorigin; if( inflictor == world && mod == MOD_FALLING ) // it's fall damage targ->snap.damage_fall += take + save; if( point[0] != 0.0f || point[1] != 0.0f || point[2] != 0.0f ) VectorCopy( point, dorigin ); else VectorSet( dorigin, targ->s.origin[0], targ->s.origin[1], targ->s.origin[2] + targ->viewheight ); G_BlendFrameDamage( targ, take, &targ->snap.damage_taken, dorigin, dmgdir, targ->snap.damage_at, targ->snap.damage_dir ); G_BlendFrameDamage( targ, save, &targ->snap.damage_saved, dorigin, dmgdir, targ->snap.damage_at, targ->snap.damage_dir ); if( targ->r.client ) { if( mod != MOD_FALLING && mod != MOD_TELEFRAG && mod != MOD_SUICIDE ) { if( inflictor == world || attacker == world ) { // for world inflicted damage use always 'frontal' G_ClientAddDamageIndicatorImpact( targ->r.client, take + save, NULL ); } else if( dflags & DAMAGE_RADIUS ) { // for splash hits the direction is from the inflictor origin G_ClientAddDamageIndicatorImpact( targ->r.client, take + save, pushdir ); } else { // for direct hits the direction is the projectile direction G_ClientAddDamageIndicatorImpact( targ->r.client, take + save, dmgdir ); } } } } targ->health = targ->health - take; // add damage done to stats if( !GS_IsTeamDamage( &targ->s, &attacker->s ) && statDmg && G_ModToAmmo( mod ) != AMMO_NONE && client && attacker->r.client ) { attacker->r.client->level.stats.accuracy_hits[G_ModToAmmo( mod )-AMMO_GUNBLADE]++; attacker->r.client->level.stats.accuracy_damage[G_ModToAmmo( mod )-AMMO_GUNBLADE] += damage; teamlist[attacker->s.team].stats.accuracy_hits[G_ModToAmmo( mod )-AMMO_GUNBLADE]++; teamlist[attacker->s.team].stats.accuracy_damage[G_ModToAmmo( mod )-AMMO_GUNBLADE] += damage; G_AwardPlayerHit( targ, attacker, mod ); } // accumulate given damage for hit sounds if( ( take || asave ) && targ != attacker && client && !targ->deadflag ) { if( attacker ) { if( GS_IsTeamDamage( &targ->s, &attacker->s ) ) attacker->snap.damageteam_given += take + asave; // we want to know how good our hit was, so saved also matters else attacker->snap.damage_given += take + asave; } } if( G_IsDead( targ ) ) { if( client ) targ->flags |= FL_NO_KNOCKBACK; G_Killed( targ, inflictor, attacker, HEALTH_TO_INT( take ), point, mod ); } else { G_CallPain( targ, attacker, knockback, take ); } }
/* * 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 } }