/* * ClientCommand */ void ClientCommand( edict_t *ent ) { char *cmd; int i; if( !ent->r.client || trap_GetClientState( PLAYERNUM( ent ) ) < CS_SPAWNED ) return; // not fully in game yet cmd = trap_Cmd_Argv( 0 ); if( Q_stricmp( cmd, "cvarinfo" ) ) // skip cvarinfo cmds because they are automatic responses G_Client_UpdateActivity( ent->r.client ); // activity detected for( i = 0; i < MAX_GAMECOMMANDS; i++ ) { if( !g_Commands[i].name[0] ) break; if( !Q_stricmp( g_Commands[i].name, cmd ) ) { if( g_Commands[i].func ) g_Commands[i].func( ent ); else GT_asCallGameCommand( ent->r.client, cmd, trap_Cmd_Args(), trap_Cmd_Argc() - 1 ); return; } } G_PrintMsg( ent, "Bad user command: %s\n", cmd ); }
/* * ClientBegin * called when a client has finished connecting, and is ready * to be placed into the game. This will happen every level load. */ void ClientBegin( edict_t *ent ) { gclient_t *client = ent->r.client; const char *mm_login; memset( &client->ucmd, 0, sizeof( client->ucmd ) ); memset( &client->level, 0, sizeof( client->level ) ); client->level.timeStamp = level.time; G_Client_UpdateActivity( client ); // activity detected client->team = TEAM_SPECTATOR; G_ClientRespawn( ent, true ); // respawn as ghost ent->movetype = MOVETYPE_NOCLIP; // allow freefly G_UpdatePlayerMatchMsg( ent ); mm_login = Info_ValueForKey( client->userinfo, "cl_mm_login" ); if( mm_login && *mm_login && client->mm_session > 0 ) { G_PrintMsg( NULL, "%s" S_COLOR_WHITE " (" S_COLOR_YELLOW "%s" S_COLOR_WHITE ") entered the game\n", client->netname, mm_login ); } else { if( !level.gametype.disableObituaries || !(ent->r.svflags & SVF_FAKECLIENT ) ) G_PrintMsg( NULL, "%s" S_COLOR_WHITE " entered the game\n", client->netname ); } client->level.respawnCount = 0; // clear respawncount client->connecting = false; // schedule the next scoreboard update client->level.scoreboard_time = game.realtime + scoreboardInterval - ( game.realtime%scoreboardInterval ); AI_EnemyAdded( ent ); G_ClientEndSnapFrame( ent ); // make sure all view stuff is valid // let the gametype scripts now this client just entered the level G_Gametype_ScoreEvent( client, "enterGame", NULL ); }
/* * 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 ); }
/* * ClientConnect * Called when a player begins connecting to the server. * The game can refuse entrance to a client by returning false. * If the client is allowed, the connection process will continue * and eventually get to ClientBegin() * Changing levels will NOT cause this to be called again, but * loadgames will. */ bool ClientConnect( edict_t *ent, char *userinfo, bool fakeClient, bool tvClient ) { char *value; assert( ent ); assert( userinfo && Info_Validate( userinfo ) ); assert( Info_ValueForKey( userinfo, "ip" ) && Info_ValueForKey( userinfo, "socket" ) ); // verify that server gave us valid data if( !Info_Validate( userinfo ) ) { Info_SetValueForKey( userinfo, "rejtype", va( "%i", DROP_TYPE_GENERAL ) ); Info_SetValueForKey( userinfo, "rejflag", va( "%i", 0 ) ); Info_SetValueForKey( userinfo, "rejmsg", "Invalid userinfo" ); return false; } if( !Info_ValueForKey( userinfo, "ip" ) ) { Info_SetValueForKey( userinfo, "rejtype", va( "%i", DROP_TYPE_GENERAL ) ); Info_SetValueForKey( userinfo, "rejflag", va( "%i", 0 ) ); Info_SetValueForKey( userinfo, "rejmsg", "Error: Server didn't provide client IP" ); return false; } if( !Info_ValueForKey( userinfo, "ip" ) ) { Info_SetValueForKey( userinfo, "rejtype", va( "%i", DROP_TYPE_GENERAL ) ); Info_SetValueForKey( userinfo, "rejflag", va( "%i", 0 ) ); Info_SetValueForKey( userinfo, "rejmsg", "Error: Server didn't provide client socket" ); return false; } // check to see if they are on the banned IP list value = Info_ValueForKey( userinfo, "ip" ); if( SV_FilterPacket( value ) ) { Info_SetValueForKey( userinfo, "rejtype", va( "%i", DROP_TYPE_GENERAL ) ); Info_SetValueForKey( userinfo, "rejflag", va( "%i", 0 ) ); Info_SetValueForKey( userinfo, "rejmsg", "You're banned from this server" ); return false; } // check for a password value = Info_ValueForKey( userinfo, "password" ); if( !fakeClient && ( *password->string && ( !value || strcmp( password->string, value ) ) ) ) { Info_SetValueForKey( userinfo, "rejtype", va( "%i", DROP_TYPE_PASSWORD ) ); Info_SetValueForKey( userinfo, "rejflag", va( "%i", 0 ) ); if( value && value[0] ) { Info_SetValueForKey( userinfo, "rejmsg", "Incorrect password" ); } else { Info_SetValueForKey( userinfo, "rejmsg", "Password required" ); } return false; } // they can connect G_InitEdict( ent ); ent->s.modelindex = 0; ent->r.solid = SOLID_NOT; ent->r.client = game.clients + PLAYERNUM( ent ); ent->r.svflags = ( SVF_NOCLIENT | ( fakeClient ? SVF_FAKECLIENT : 0 ) ); memset( ent->r.client, 0, sizeof( gclient_t ) ); ent->r.client->ps.playerNum = PLAYERNUM( ent ); ent->r.client->connecting = true; ent->r.client->isTV = tvClient == true; ent->r.client->team = TEAM_SPECTATOR; G_Client_UpdateActivity( ent->r.client ); // activity detected ClientUserinfoChanged( ent, userinfo ); if( !fakeClient ) { char message[MAX_STRING_CHARS]; Q_snprintfz( message, sizeof( message ), "%s%s connected", ent->r.client->netname, S_COLOR_WHITE ); G_PrintMsg( NULL, "%s\n", message ); G_Printf( "%s%s connected from %s\n", ent->r.client->netname, S_COLOR_WHITE, ent->r.client->ip ); } // let the gametype scripts know this client just connected G_Gametype_ScoreEvent( ent->r.client, "connect", NULL ); G_CallVotes_ResetClient( PLAYERNUM( ent ) ); return true; }