/* ================ ClientEvents Events will be passed on to the clients for presentation, but any server game effects are handled here ================ */ void ClientEvents( gentity_t *ent, int oldEventSequence ) { int i; int event; gclient_t *client; int damage; vec3_t dir; vec3_t point, mins; float fallDistance; pClass_t _class; client = ent->client; _class = (pClass_t)client->ps.stats[ STAT_PCLASS ]; if( oldEventSequence < client->ps.eventSequence - MAX_EVENTS ) oldEventSequence = client->ps.eventSequence - MAX_EVENTS; for( i = oldEventSequence; i < client->ps.eventSequence; i++ ) { event = client->ps.events[ i & ( MAX_EVENTS - 1 ) ]; switch( event ) { case EV_FALL_MEDIUM: case EV_FALL_FAR: if( ent->s.eType != ET_PLAYER ) break; // not in the player model fallDistance = ( (float)client->ps.stats[ STAT_FALLDIST ] - MIN_FALL_DISTANCE ) / ( MAX_FALL_DISTANCE - MIN_FALL_DISTANCE ); if( fallDistance < 0.0f ) fallDistance = 0.0f; else if( fallDistance > 1.0f ) fallDistance = 1.0f; damage = (int)( (float)BG_FindHealthForClass( _class ) * BG_FindFallDamageForClass( _class ) * fallDistance ); VectorSet( dir, 0, 0, 1 ); BG_FindBBoxForClass( _class, mins, NULL, NULL, NULL, NULL ); mins[ 0 ] = mins[ 1 ] = 0.0f; VectorAdd( client->ps.origin, mins, point ); ent->pain_debounce_time = level.time + 200; // no normal pain sound G_Damage( ent, NULL, NULL, dir, point, damage, DAMAGE_NO_LOCDAMAGE, MOD_FALLING ); break; case EV_FIRE_WEAPON: FireWeapon( ent ); break; case EV_FIRE_WEAPON2: FireWeapon2( ent ); break; case EV_FIRE_WEAPON3: FireWeapon3( ent ); break; case EV_NOAMMO: //if we just ran out of grenades, remove the inventory item if( ent->s.weapon == WP_GRENADE ) { int j; BG_RemoveWeaponFromInventory( ent->s.weapon, ent->client->ps.stats ); //switch to the first non blaster weapon for( j = WP_NONE + 1; j < WP_NUM_WEAPONS; j++ ) { if( j == WP_BLASTER ) continue; if( BG_InventoryContainsWeapon( j, ent->client->ps.stats ) ) { G_ForceWeaponChange( ent, (weapon_t)j ); break; } } //only got the blaster to switch to if( j == WP_NUM_WEAPONS ) G_ForceWeaponChange( ent, WP_BLASTER ); //update ClientInfo ClientUserinfoChanged( ent->client->ps.clientNum ); } break; default: break; } } }
/* ============= SpawnCorpse A player is respawning, so make an entity that looks just like the existing corpse to leave behind. ============= */ void SpawnCorpse( gentity_t *ent ) { gentity_t *body; int contents; vec3_t origin, dest; trace_t tr; float vDiff; //just return right away so bodies never appear and its cleaner + faster return; VectorCopy( ent->r.currentOrigin, origin ); trap_UnlinkEntity( ent ); // if client is in a nodrop area, don't leave the body contents = trap_PointContents( origin, -1 ); if( contents & CONTENTS_NODROP ) return; body = G_Spawn( ); VectorCopy( ent->s.apos.trBase, body->s.angles ); body->s.eFlags = EF_DEAD; body->s.eType = ET_CORPSE; body->s.number = body - g_entities; body->timestamp = level.time; body->s.event = 0; body->r.contents = CONTENTS_CORPSE; body->s.clientNum = ent->client->ps.stats[ STAT_PCLASS ]; body->nonSegModel = ent->client->ps.persistant[ PERS_STATE ] & PS_NONSEGMODEL; if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) body->classname = "humanCorpse"; else body->classname = "alienCorpse"; body->s.powerups = MAX_CLIENTS; body->think = BodySink; body->nextthink = level.time + 20000; body->s.legsAnim = ent->s.legsAnim; if( !body->nonSegModel ) { switch( body->s.legsAnim & ~ANIM_TOGGLEBIT ) { case BOTH_DEATH1: case BOTH_DEAD1: body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD1; break; case BOTH_DEATH2: case BOTH_DEAD2: body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD2; break; case BOTH_DEATH3: case BOTH_DEAD3: default: body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD3; break; } } else { switch( body->s.legsAnim & ~ANIM_TOGGLEBIT ) { case NSPA_DEATH1: case NSPA_DEAD1: body->s.legsAnim = NSPA_DEAD1; break; case NSPA_DEATH2: case NSPA_DEAD2: body->s.legsAnim = NSPA_DEAD2; break; case NSPA_DEATH3: case NSPA_DEAD3: default: body->s.legsAnim = NSPA_DEAD3; break; } } body->takedamage = qfalse; body->health = ent->health = ent->client->ps.stats[ STAT_HEALTH ]; ent->health = 0; //change body dimensions BG_FindBBoxForClass( ent->client->ps.stats[ STAT_PCLASS ], NULL, NULL, NULL, body->r.mins, body->r.maxs ); vDiff = body->r.mins[ 2 ] - ent->r.mins[ 2 ]; //drop down to match the *model* origins of ent and body VectorSet( dest, origin[ 0 ], origin[ 1 ], origin[ 2 ] - vDiff ); trap_Trace( &tr, origin, body->r.mins, body->r.maxs, dest, body->s.number, body->clipmask ); VectorCopy( tr.endpos, origin ); G_SetOrigin( body, origin ); VectorCopy( origin, body->s.origin ); body->s.pos.trType = TR_GRAVITY; body->s.pos.trTime = level.time; VectorCopy( ent->client->ps.velocity, body->s.pos.trDelta ); VectorCopy ( body->s.pos.trBase, body->r.currentOrigin ); trap_LinkEntity( body ); }
/* ============ G_TouchTriggers Find all trigger entities that ent's current position touches. Spectators will only interact with teleporters. ============ */ void G_TouchTriggers( gentity_t *ent ) { int i, num; int touch[MAX_GENTITIES]; gentity_t *hit; trace_t trace; vec3_t mins, maxs; vec3_t pmins, pmaxs; static vec3_t range = { 10, 10, 10 }; if( !ent->client ) return; // dead clients don't activate triggers! if( ent->client->ps.stats[ STAT_HEALTH ] <= 0 ) return; BG_FindBBoxForClass( ent->client->ps.stats[ STAT_PCLASS ], pmins, pmaxs, NULL, NULL, NULL ); VectorAdd( ent->client->ps.origin, pmins, mins ); VectorAdd( ent->client->ps.origin, pmaxs, maxs ); VectorSubtract( mins, range, mins ); VectorAdd( maxs, range, maxs ); num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); // can't use ent->absmin, because that has a one unit pad VectorAdd( ent->client->ps.origin, ent->r.mins, mins ); VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs ); for( i = 0; i < num; i++ ) { hit = &g_entities[ touch[ i ] ]; if( !hit->touch && !ent->touch ) continue; if( !( hit->r.contents & CONTENTS_TRIGGER ) ) continue; // ignore most entities if a spectator if( ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) || ( ent->client->ps.stats[ STAT_STATE ] & SS_INFESTING ) || ( ent->client->ps.stats[ STAT_STATE ] & SS_HOVELING ) ) { if( hit->s.eType != ET_TELEPORT_TRIGGER && // this is ugly but adding a new ET_? type will // most likely cause network incompatibilities hit->touch != Touch_DoorTrigger ) { //check for manually triggered doors manualTriggerSpectator( hit, ent ); continue; } } if( !trap_EntityContact( mins, maxs, hit ) ) continue; memset( &trace, 0, sizeof( trace ) ); if( hit->touch ) hit->touch( hit, ent, &trace ); if( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) ent->touch( ent, hit, &trace ); } // if we didn't touch a jump pad this pmove frame if( ent->client->ps.jumppad_frame != ent->client->ps.pmove_framecount ) { ent->client->ps.jumppad_frame = 0; ent->client->ps.jumppad_ent = 0; } }
/* =========== ClientSpawn Called every time a client is placed fresh in the world: after the first ClientBegin, and after each respawn Initializes all non-persistant parts of playerState ============ */ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles ) { int index; vec3_t spawn_origin, spawn_angles; gclient_t *client; int i; clientPersistant_t saved; clientSession_t savedSess; int persistant[ MAX_PERSISTANT ]; gentity_t *spawnPoint = NULL; int flags; int savedPing; int teamLocal; int eventSequence; char userinfo[ MAX_INFO_STRING ]; vec3_t up = { 0.0f, 0.0f, 1.0f }; int maxAmmo, maxClips; weapon_t weapon; index = ent - g_entities; client = ent->client; teamLocal = client->pers.teamSelection; //TA: only start client if chosen a class and joined a team if( client->pers.classSelection == PCL_NONE && teamLocal == PTE_NONE ) { client->sess.sessionTeam = TEAM_SPECTATOR; client->sess.spectatorState = SPECTATOR_FREE; } else if( client->pers.classSelection == PCL_NONE ) { client->sess.sessionTeam = TEAM_SPECTATOR; client->sess.spectatorState = SPECTATOR_LOCKED; } //if client is dead and following teammate, stop following before spawning if(ent->client->sess.spectatorClient!=-1) { ent->client->sess.spectatorClient = -1; ent->client->sess.spectatorState = SPECTATOR_FREE; } if( origin != NULL ) VectorCopy( origin, spawn_origin ); if( angles != NULL ) VectorCopy( angles, spawn_angles ); // find a spawn point // do it before setting health back up, so farthest // ranging doesn't count this client if( client->sess.sessionTeam == TEAM_SPECTATOR ) { if( teamLocal == PTE_NONE ) spawnPoint = G_SelectSpectatorSpawnPoint( spawn_origin, spawn_angles ); else if( teamLocal == PTE_ALIENS ) spawnPoint = G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles ); else if( teamLocal == PTE_HUMANS ) spawnPoint = G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles ); } else { if( spawn == NULL ) { G_Error( "ClientSpawn: spawn is NULL\n" ); return; } spawnPoint = spawn; if( ent != spawn ) { //start spawn animation on spawnPoint G_SetBuildableAnim( spawnPoint, BANIM_SPAWN1, qtrue ); if( spawnPoint->biteam == PTE_ALIENS ) spawnPoint->clientSpawnTime = ALIEN_SPAWN_REPEAT_TIME; else if( spawnPoint->biteam == PTE_HUMANS ) spawnPoint->clientSpawnTime = HUMAN_SPAWN_REPEAT_TIME; } } client->pers.teamState.state = TEAM_ACTIVE; // toggle the teleport bit so the client knows to not lerp flags = ent->client->ps.eFlags & ( EF_TELEPORT_BIT | EF_VOTED | EF_TEAMVOTED ); flags ^= EF_TELEPORT_BIT; G_UnlaggedClear( ent ); // clear everything but the persistant data saved = client->pers; savedSess = client->sess; savedPing = client->ps.ping; for( i = 0; i < MAX_PERSISTANT; i++ ) persistant[ i ] = client->ps.persistant[ i ]; eventSequence = client->ps.eventSequence; memset( client, 0, sizeof( *client ) ); client->pers = saved; client->sess = savedSess; client->ps.ping = savedPing; client->lastkilled_client = -1; for( i = 0; i < MAX_PERSISTANT; i++ ) client->ps.persistant[ i ] = persistant[ i ]; client->ps.eventSequence = eventSequence; // increment the spawncount so the client will detect the respawn client->ps.persistant[ PERS_SPAWN_COUNT ]++; client->ps.persistant[ PERS_TEAM ] = client->sess.sessionTeam; // restore really persistant things client->ps.persistant[ PERS_SCORE ] = client->pers.score; client->ps.persistant[ PERS_CREDIT ] = client->pers.credit; client->airOutTime = level.time + 12000; trap_GetUserinfo( index, userinfo, sizeof( userinfo ) ); client->ps.eFlags = flags; //Com_Printf( "ent->client->pers->pclass = %i\n", ent->client->pers.classSelection ); ent->s.groundEntityNum = ENTITYNUM_NONE; ent->client = &level.clients[ index ]; ent->takedamage = qtrue; ent->inuse = qtrue; ent->classname = "player"; ent->r.contents = CONTENTS_BODY; ent->clipmask = MASK_PLAYERSOLID; ent->die = player_die; ent->waterlevel = 0; ent->watertype = 0; ent->flags = 0; //TA: calculate each client's acceleration ent->evaluateAcceleration = qtrue; client->ps.stats[ STAT_WEAPONS ] = 0; client->ps.stats[ STAT_WEAPONS2 ] = 0; client->ps.stats[ STAT_SLOTS ] = 0; client->ps.eFlags = flags; client->ps.clientNum = index; BG_FindBBoxForClass( ent->client->pers.classSelection, ent->r.mins, ent->r.maxs, NULL, NULL, NULL ); if( client->sess.sessionTeam != TEAM_SPECTATOR ) client->pers.maxHealth = client->ps.stats[ STAT_MAX_HEALTH ] = BG_FindHealthForClass( ent->client->pers.classSelection ); else client->pers.maxHealth = client->ps.stats[ STAT_MAX_HEALTH ] = 100; // clear entity values if( ent->client->pers.classSelection == PCL_HUMAN ) { BG_AddWeaponToInventory( WP_BLASTER, client->ps.stats ); BG_AddUpgradeToInventory( UP_MEDKIT, client->ps.stats ); weapon = client->pers.humanItemSelection; } else if( client->sess.sessionTeam != TEAM_SPECTATOR ) weapon = BG_FindStartWeaponForClass( ent->client->pers.classSelection ); else weapon = WP_NONE; BG_FindAmmoForWeapon( weapon, &maxAmmo, &maxClips ); BG_AddWeaponToInventory( weapon, client->ps.stats ); BG_PackAmmoArray( weapon, client->ps.ammo, client->ps.powerups, maxAmmo, maxClips ); ent->client->ps.stats[ STAT_PCLASS ] = ent->client->pers.classSelection; ent->client->ps.stats[ STAT_PTEAM ] = ent->client->pers.teamSelection; ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; ent->client->ps.stats[ STAT_STATE ] = 0; VectorSet( ent->client->ps.grapplePoint, 0.0f, 0.0f, 1.0f ); // health will count down towards max_health ent->health = client->ps.stats[ STAT_HEALTH ] = client->ps.stats[ STAT_MAX_HEALTH ]; //* 1.25; //if evolving scale health if( ent == spawn ) { ent->health *= ent->client->pers.evolveHealthFraction; client->ps.stats[ STAT_HEALTH ] *= ent->client->pers.evolveHealthFraction; } //clear the credits array for( i = 0; i < MAX_CLIENTS; i++ ) ent->credits[ i ] = 0; client->ps.stats[ STAT_STAMINA ] = MAX_STAMINA; G_SetOrigin( ent, spawn_origin ); VectorCopy( spawn_origin, client->ps.origin ); #define UP_VEL 150.0f #define F_VEL 50.0f //give aliens some spawn velocity if( client->sess.sessionTeam != TEAM_SPECTATOR && client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) { if( ent == spawn ) { //evolution particle system G_AddPredictableEvent( ent, EV_ALIEN_EVOLVE, DirToByte( up ) ); } else { spawn_angles[ YAW ] += 180.0f; AngleNormalize360( spawn_angles[ YAW ] ); if( spawnPoint->s.origin2[ 2 ] > 0.0f ) { vec3_t forward, dir; AngleVectors( spawn_angles, forward, NULL, NULL ); VectorScale( forward, F_VEL, forward ); VectorAdd( spawnPoint->s.origin2, forward, dir ); VectorNormalize( dir ); VectorScale( dir, UP_VEL, client->ps.velocity ); } G_AddPredictableEvent( ent, EV_PLAYER_RESPAWN, 0 ); } } else if( client->sess.sessionTeam != TEAM_SPECTATOR && client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) { spawn_angles[ YAW ] += 180.0f; AngleNormalize360( spawn_angles[ YAW ] ); } // the respawned flag will be cleared after the attack and jump keys come up client->ps.pm_flags |= PMF_RESPAWNED; trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd ); G_SetClientViewAngle( ent, spawn_angles ); if( !( client->sess.sessionTeam == TEAM_SPECTATOR ) ) { /*G_KillBox( ent );*/ //blame this if a newly spawned client gets stuck in another trap_LinkEntity( ent ); // force the base weapon up client->ps.weapon = WP_NONE; client->ps.weaponstate = WEAPON_READY; } // don't allow full run speed for a bit client->ps.pm_flags |= PMF_TIME_KNOCKBACK; client->ps.pm_time = 100; client->respawnTime = level.time; client->lastKillTime = level.time; client->inactivityTime = level.time + g_inactivity.integer * 1000; client->latched_buttons = 0; // set default animations client->ps.torsoAnim = TORSO_STAND; client->ps.legsAnim = LEGS_IDLE; if( level.intermissiontime ) MoveClientToIntermission( ent ); else { // fire the targets of the spawn point if( !spawn ) G_UseTargets( spawnPoint, ent ); // select the highest weapon number available, after any // spawn given items have fired client->ps.weapon = 1; for( i = WP_NUM_WEAPONS - 1; i > 0 ; i-- ) { if( BG_InventoryContainsWeapon( i, client->ps.stats ) ) { client->ps.weapon = i; break; } } } // run a client frame to drop exactly to the floor, // initialize animations and other things client->ps.commandTime = level.time - 100; ent->client->pers.cmd.serverTime = level.time; ClientThink( ent-g_entities ); // positively link the client, even if the command times are weird if( client->sess.sessionTeam != TEAM_SPECTATOR ) { BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); trap_LinkEntity( ent ); } //TA: must do this here so the number of active clients is calculated CalculateRanks( ); // run the presend to set anything else ClientEndFrame( ent ); // clear entity state values BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); }