//copy of PM_CheckLadder in bg_pmove.c bool BotOnLadder( gentity_t *self ) { vec3_t forward, end; vec3_t mins, maxs; trace_t trace; if ( !BG_ClassHasAbility( ( class_t ) self->client->ps.stats[ STAT_CLASS ], SCA_CANUSELADDERS ) ) { return false; } AngleVectors( self->client->ps.viewangles, forward, nullptr, nullptr ); forward[ 2 ] = 0.0f; BG_ClassBoundingBox( ( class_t ) self->client->ps.stats[ STAT_CLASS ], mins, maxs, nullptr, nullptr, nullptr ); VectorMA( self->s.origin, 1.0f, forward, end ); trap_Trace( &trace, self->s.origin, mins, maxs, end, self->s.number, MASK_PLAYERSOLID, 0 ); if ( trace.fraction < 1.0f && trace.surfaceFlags & SURF_LADDER ) { return true; } else { return false; } }
gentity_t* BotGetPathBlocker( gentity_t *self, const vec3_t dir ) { vec3_t playerMins, playerMaxs; vec3_t end; trace_t trace; const int TRACE_LENGTH = BOT_OBSTACLE_AVOID_RANGE; if ( !( self && self->client ) ) { return nullptr; } BG_ClassBoundingBox( ( class_t ) self->client->ps.stats[STAT_CLASS], playerMins, playerMaxs, nullptr, nullptr, nullptr ); //account for how large we can step playerMins[2] += STEPSIZE; playerMaxs[2] += STEPSIZE; VectorMA( self->s.origin, TRACE_LENGTH, dir, end ); trap_Trace( &trace, self->s.origin, playerMins, playerMaxs, end, self->s.number, MASK_SHOT, 0 ); if ( ( trace.fraction < 1.0f && trace.plane.normal[ 2 ] < 0.7f ) || g_entities[ trace.entityNum ].s.eType == entityType_t::ET_BUILDABLE ) { return &g_entities[trace.entityNum]; } return nullptr; }
/** * @brief Moves a tag to the parent entity's bounding box center. */ static void UpdateTagLocation( gentity_t *ent, gentity_t *parent ) { vec3_t mins, maxs, center; if ( !parent ) return; VectorCopy( parent->s.origin, center ); if ( parent->client ) { BG_ClassBoundingBox( parent->client->ps.stats[ STAT_CLASS ], mins, maxs, nullptr, nullptr, nullptr ); BG_MoveOriginToBBOXCenter( center, mins, maxs ); // Also update weapon for humans. if( parent->client->pers.team == TEAM_HUMANS ) { ent->s.bc_data = BG_GetPlayerWeapon( &parent->client->ps ); } } else if ( parent->s.eType == ET_BUILDABLE ) { BG_BuildableBoundingBox( parent->s.modelindex, mins, maxs ); BG_MoveOriginToBBOXCenter( center, mins, maxs ); } Move( ent, center ); }
bool BotShouldJump( gentity_t *self, gentity_t *blocker, const vec3_t dir ) { vec3_t playerMins; vec3_t playerMaxs; float jumpMagnitude; trace_t trace; const int TRACE_LENGTH = BOT_OBSTACLE_AVOID_RANGE; vec3_t end; //blocker is not on our team, so ignore if ( BotGetEntityTeam( self ) != BotGetEntityTeam( blocker ) ) { return false; } //already normalized BG_ClassBoundingBox( ( class_t ) self->client->ps.stats[STAT_CLASS], playerMins, playerMaxs, nullptr, nullptr, nullptr ); playerMins[2] += STEPSIZE; playerMaxs[2] += STEPSIZE; //Log::Debug(vtos(self->movedir)); VectorMA( self->s.origin, TRACE_LENGTH, dir, end ); //make sure we are moving into a block trap_Trace( &trace, self->s.origin, playerMins, playerMaxs, end, self->s.number, MASK_SHOT, 0 ); if ( trace.fraction >= 1.0f || blocker != &g_entities[trace.entityNum] ) { return false; } jumpMagnitude = BG_Class( ( class_t )self->client->ps.stats[STAT_CLASS] )->jumpMagnitude; //find the actual height of our jump jumpMagnitude = Square( jumpMagnitude ) / ( self->client->ps.gravity * 2 ); //prepare for trace playerMins[2] += jumpMagnitude; playerMaxs[2] += jumpMagnitude; //check if jumping will clear us of entity trap_Trace( &trace, self->s.origin, playerMins, playerMaxs, end, self->s.number, MASK_SHOT, 0 ); //if we can jump over it, then jump //note that we also test for a blocking barricade because barricades will collapse to let us through if ( blocker->s.modelindex == BA_A_BARRICADE || trace.fraction == 1.0f ) { return true; } else { return false; } }
void BotClampPos( gentity_t *self ) { float height = self->client->ps.origin[ 2 ]; vec3_t origin; trace_t trace; vec3_t mins, maxs; VectorSet( origin, self->botMind->nav.pos[ 0 ], self->botMind->nav.pos[ 1 ], height ); BG_ClassBoundingBox( self->client->ps.stats[ STAT_CLASS ], mins, maxs, nullptr, nullptr, nullptr ); trap_Trace( &trace, self->client->ps.origin, mins, maxs, origin, self->client->ps.clientNum, MASK_PLAYERSOLID, 0 ); G_SetOrigin( self, trace.endpos ); VectorCopy( trace.endpos, self->client->ps.origin ); }
/* ============= SpawnCorpse A player is respawning, so make an entity that looks just like the existing corpse to leave behind. ============= */ static void SpawnCorpse( gentity_t *ent ) { gentity_t *body; int contents; vec3_t origin, dest; trace_t tr; float vDiff; 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_CLASS ]; body->nonSegModel = ent->client->ps.persistant[ PERS_STATE ] & PS_NONSEGMODEL; if( ent->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) body->classname = "humanCorpse"; else body->classname = "alienCorpse"; body->s.misc = 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_ClassBoundingBox( ent->client->ps.stats[ STAT_CLASS ], 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 ); }
/* =========== 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; //if client is dead and following teammate, stop following before spawning if( client->sess.spectatorClient != -1 ) { client->sess.spectatorClient = -1; client->sess.spectatorState = SPECTATOR_FREE; } // only start client if chosen a class and joined a team if( client->pers.classSelection == PCL_NONE && teamLocal == TEAM_NONE ) client->sess.spectatorState = SPECTATOR_FREE; else if( client->pers.classSelection == PCL_NONE ) client->sess.spectatorState = SPECTATOR_LOCKED; // if client is dead and following teammate, stop following before spawning if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) G_StopFollowing( ent ); 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.spectatorState != SPECTATOR_NOT ) { if( teamLocal == TEAM_NONE ) spawnPoint = G_SelectSpectatorSpawnPoint( spawn_origin, spawn_angles ); else if( teamLocal == TEAM_ALIENS ) spawnPoint = G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles ); else if( teamLocal == TEAM_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->buildableTeam == TEAM_ALIENS ) spawnPoint->clientSpawnTime = ALIEN_SPAWN_REPEAT_TIME; else if( spawnPoint->buildableTeam == TEAM_HUMANS ) spawnPoint->clientSpawnTime = HUMAN_SPAWN_REPEAT_TIME; } } // toggle the teleport bit so the client knows to not lerp flags = ( ent->client->ps.eFlags & EF_TELEPORT_BIT ) ^ 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_SPECSTATE ] = client->sess.spectatorState; 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; // calculate each client's acceleration ent->evaluateAcceleration = qtrue; client->ps.stats[ STAT_MISC ] = 0; client->ps.eFlags = flags; client->ps.clientNum = index; BG_ClassBoundingBox( ent->client->pers.classSelection, ent->r.mins, ent->r.maxs, NULL, NULL, NULL ); if( client->sess.spectatorState == SPECTATOR_NOT ) client->ps.stats[ STAT_MAX_HEALTH ] = BG_Class( ent->client->pers.classSelection )->health; else client->ps.stats[ STAT_MAX_HEALTH ] = 100; // clear entity values if( ent->client->pers.classSelection == PCL_HUMAN ) { BG_AddUpgradeToInventory( UP_MEDKIT, client->ps.stats ); weapon = client->pers.humanItemSelection; } else if( client->sess.spectatorState == SPECTATOR_NOT ) weapon = BG_Class( ent->client->pers.classSelection )->startWeapon; else weapon = WP_NONE; maxAmmo = BG_Weapon( weapon )->maxAmmo; maxClips = BG_Weapon( weapon )->maxClips; client->ps.stats[ STAT_WEAPON ] = weapon; client->ps.ammo = maxAmmo; client->ps.clips = maxClips; // We just spawned, not changing weapons client->ps.persistant[ PERS_NEWWEAPON ] = 0; ent->client->ps.stats[ STAT_CLASS ] = ent->client->pers.classSelection; ent->client->ps.stats[ STAT_TEAM ] = 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 ] = STAMINA_MAX; 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.spectatorState == SPECTATOR_NOT && client->ps.stats[ STAT_TEAM ] == TEAM_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.spectatorState == SPECTATOR_NOT && client->ps.stats[ STAT_TEAM ] == TEAM_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.spectatorState == SPECTATOR_NOT ) { trap_LinkEntity( ent ); // force the base weapon up if( client->pers.teamSelection == TEAM_HUMANS ) G_ForceWeaponChange( ent, weapon ); 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; ent->nextRegenTime = 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 ); client->ps.weapon = client->ps.stats[ STAT_WEAPON ]; } // 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.spectatorState == SPECTATOR_NOT ) { BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); trap_LinkEntity( ent ); } // 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 ); client->pers.infoChangeTime = level.time; }
/* ============= SpawnCorpse A player is respawning, so make an entity that looks just like the existing corpse to leave behind. ============= */ static void SpawnCorpse( gentity_t *ent ) { gentity_t *body; int contents; vec3_t origin, mins; 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_NewEntity(); VectorCopy( ent->s.apos.trBase, body->s.angles ); body->s.eFlags = EF_DEAD; body->s.eType = entityType_t::ET_CORPSE; body->timestamp = level.time; body->s.event = 0; body->r.contents = CONTENTS_CORPSE; body->clipmask = MASK_DEADSOLID; body->s.clientNum = ent->client->ps.stats[ STAT_CLASS ]; body->nonSegModel = ent->client->ps.persistant[ PERS_STATE ] & PS_NONSEGMODEL; if ( ent->client->pers.team == TEAM_HUMANS ) { body->classname = "humanCorpse"; } else { body->classname = "alienCorpse"; } body->s.misc = 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; } } //change body dimensions BG_ClassBoundingBox( ent->client->ps.stats[ STAT_CLASS ], mins, nullptr, nullptr, body->r.mins, body->r.maxs ); //drop down to match the *model* origins of ent and body origin[2] += mins[ 2 ] - body->r.mins[ 2 ]; G_SetOrigin( body, origin ); body->s.pos.trType = trType_t::TR_GRAVITY; body->s.pos.trTime = level.time; VectorCopy( ent->client->ps.velocity, body->s.pos.trDelta ); trap_LinkEntity( body ); }
/* =========== ClientSpawn Called every time a client is placed fresh in the world: after the first ClientBegin, and after each respawn and evolve Initializes all non-persistent parts of playerState ============ */ void ClientSpawn( gentity_t *ent, gentity_t *spawn, const vec3_t origin, const vec3_t angles ) { int index; vec3_t spawn_origin, spawn_angles; gclient_t *client; int i; clientPersistant_t saved; clientSession_t savedSess; bool savedNoclip, savedCliprcontents; int persistant[ MAX_PERSISTANT ]; gentity_t *spawnPoint = nullptr; 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; ClientSpawnCBSE(ent, ent == spawn); index = ent - g_entities; client = ent->client; teamLocal = client->pers.team; //if client is dead and following teammate, stop following before spawning if ( client->sess.spectatorClient != -1 ) { client->sess.spectatorClient = -1; client->sess.spectatorState = SPECTATOR_FREE; } // only start client if chosen a class and joined a team if ( client->pers.classSelection == PCL_NONE && teamLocal == TEAM_NONE ) { client->sess.spectatorState = SPECTATOR_FREE; } else if ( client->pers.classSelection == PCL_NONE ) { client->sess.spectatorState = SPECTATOR_LOCKED; } // if client is dead and following teammate, stop following before spawning if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) { G_StopFollowing( ent ); } if ( origin != nullptr ) { VectorCopy( origin, spawn_origin ); } if ( angles != nullptr ) { 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.spectatorState != SPECTATOR_NOT ) { if ( teamLocal == TEAM_NONE ) { spawnPoint = G_SelectSpectatorSpawnPoint( spawn_origin, spawn_angles ); } else if ( teamLocal == TEAM_ALIENS ) { spawnPoint = G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles ); } else if ( teamLocal == TEAM_HUMANS ) { spawnPoint = G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles ); } } else { if ( spawn == nullptr ) { Com_Error(errorParm_t::ERR_DROP, "ClientSpawn: spawn is NULL" ); } spawnPoint = spawn; if ( spawnPoint->s.eType == entityType_t::ET_BUILDABLE ) { G_SetBuildableAnim( spawnPoint, BANIM_SPAWN1, true ); if ( spawnPoint->buildableTeam == TEAM_ALIENS ) { spawnPoint->clientSpawnTime = ALIEN_SPAWN_REPEAT_TIME; } else if ( spawnPoint->buildableTeam == TEAM_HUMANS ) { spawnPoint->clientSpawnTime = HUMAN_SPAWN_REPEAT_TIME; } } } // toggle the teleport bit so the client knows to not lerp flags = ( ent->client->ps.eFlags & EF_TELEPORT_BIT ) ^ EF_TELEPORT_BIT; G_UnlaggedClear( ent ); // clear everything but the persistent data saved = client->pers; savedSess = client->sess; savedPing = client->ps.ping; savedNoclip = client->noclip; savedCliprcontents = client->cliprcontents; 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->noclip = savedNoclip; client->cliprcontents = savedCliprcontents; 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_SPECSTATE ] = client->sess.spectatorState; client->airOutTime = level.time + 12000; trap_GetUserinfo( index, userinfo, sizeof( userinfo ) ); client->ps.eFlags = flags; //Log::Notice( "ent->client->pers->pclass = %i\n", ent->client->pers.classSelection ); ent->s.groundEntityNum = ENTITYNUM_NONE; ent->client = &level.clients[ index ]; ent->classname = S_PLAYER_CLASSNAME; if ( client->noclip ) { client->cliprcontents = CONTENTS_BODY; } else { ent->r.contents = CONTENTS_BODY; } ent->clipmask = MASK_PLAYERSOLID; ent->die = G_PlayerDie; ent->waterlevel = 0; ent->watertype = 0; ent->flags &= FL_GODMODE | FL_NOTARGET; // calculate each client's acceleration ent->evaluateAcceleration = true; client->ps.stats[ STAT_MISC ] = 0; client->ps.eFlags = flags; client->ps.clientNum = index; BG_ClassBoundingBox( ent->client->pers.classSelection, ent->r.mins, ent->r.maxs, nullptr, nullptr, nullptr ); // clear entity values if ( ent->client->pers.classSelection == PCL_HUMAN_NAKED ) { BG_AddUpgradeToInventory( UP_MEDKIT, client->ps.stats ); weapon = client->pers.humanItemSelection; } else if ( client->sess.spectatorState == SPECTATOR_NOT ) { weapon = BG_Class( ent->client->pers.classSelection )->startWeapon; } else { weapon = WP_NONE; } maxAmmo = BG_Weapon( weapon )->maxAmmo; maxClips = BG_Weapon( weapon )->maxClips; client->ps.stats[ STAT_WEAPON ] = weapon; client->ps.ammo = maxAmmo; client->ps.clips = maxClips; // We just spawned, not changing weapons client->ps.persistant[ PERS_NEWWEAPON ] = 0; client->ps.persistant[ PERS_TEAM ] = client->pers.team; // TODO: Check whether stats can be cleared at once instead of per field client->ps.stats[ STAT_STAMINA ] = STAMINA_MAX; client->ps.stats[ STAT_FUEL ] = JETPACK_FUEL_MAX; client->ps.stats[ STAT_CLASS ] = ent->client->pers.classSelection; client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; client->ps.stats[ STAT_PREDICTION ] = 0; client->ps.stats[ STAT_STATE ] = 0; VectorSet( client->ps.grapplePoint, 0.0f, 0.0f, 1.0f ); //clear the credits array // TODO: Handle in HealthComponent or ClientComponent. for ( i = 0; i < MAX_CLIENTS; i++ ) { ent->credits[ i ].value = 0.0f; ent->credits[ i ].time = 0; ent->credits[ i ].team = TEAM_NONE; } G_SetOrigin( ent, spawn_origin ); VectorCopy( spawn_origin, client->ps.origin ); //give aliens some spawn velocity if ( client->sess.spectatorState == SPECTATOR_NOT && client->pers.team == TEAM_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, nullptr, nullptr ); VectorAdd( spawnPoint->s.origin2, forward, dir ); VectorNormalize( dir ); VectorScale( dir, BG_Class( ent->client->pers.classSelection )->jumpMagnitude, client->ps.velocity ); } G_AddPredictableEvent( ent, EV_PLAYER_RESPAWN, 0 ); } } else if ( client->sess.spectatorState == SPECTATOR_NOT && client->pers.team == TEAM_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.spectatorState == SPECTATOR_NOT ) { trap_LinkEntity( ent ); // force the base weapon up if ( client->pers.team == TEAM_HUMANS ) { G_ForceWeaponChange( ent, weapon ); } 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; ent->nextRegenTime = level.time; client->inactivityTime = level.time + g_inactivity.integer * 1000; usercmdClearButtons( client->latched_buttons ); // 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 && spawnPoint ) { G_EventFireEntity( spawnPoint, ent, ON_SPAWN ); } // 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.spectatorState == SPECTATOR_NOT ) { BG_PlayerStateToEntityState( &client->ps, &ent->s, true ); VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); trap_LinkEntity( ent ); } // 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, true ); client->pers.infoChangeTime = level.time; // (re)tag the client for its team Beacon::DeleteTags( ent ); Beacon::Tag( ent, (team_t)ent->client->ps.persistant[ PERS_TEAM ], true ); }
bool BotFindSteerTarget( gentity_t *self, vec3_t dir ) { vec3_t forward; vec3_t testPoint1, testPoint2; vec3_t playerMins, playerMaxs; float yaw1, yaw2; trace_t trace1, trace2; int i; vec3_t angles; if ( !( self && self->client ) ) { return false; } //get bbox BG_ClassBoundingBox( ( class_t ) self->client->ps.stats[STAT_CLASS], playerMins, playerMaxs, nullptr, nullptr, nullptr ); //account for stepsize playerMins[2] += STEPSIZE; playerMaxs[2] += STEPSIZE; //get the yaw (left/right) we dont care about up/down vectoangles( dir, angles ); yaw1 = yaw2 = angles[ YAW ]; //directly infront of us is blocked, so dont test it yaw1 -= 15; yaw2 += 15; //forward vector is 2D forward[ 2 ] = 0; //find an unobstructed position //we check the full 180 degrees in front of us for ( i = 0; i < 5; i++, yaw1 -= 15 , yaw2 += 15 ) { //compute forward for right forward[0] = cos( DEG2RAD( yaw1 ) ); forward[1] = sin( DEG2RAD( yaw1 ) ); //forward is already normalized //try the right VectorMA( self->s.origin, BOT_OBSTACLE_AVOID_RANGE, forward, testPoint1 ); //test it trap_Trace( &trace1, self->s.origin, playerMins, playerMaxs, testPoint1, self->s.number, MASK_SHOT, 0 ); //check if unobstructed if ( trace1.fraction >= 1.0f ) { VectorCopy( forward, dir ); return true; } //compute forward for left forward[0] = cos( DEG2RAD( yaw2 ) ); forward[1] = sin( DEG2RAD( yaw2 ) ); //forward is already normalized //try the left VectorMA( self->s.origin, BOT_OBSTACLE_AVOID_RANGE, forward, testPoint2 ); //test it trap_Trace( &trace2, self->s.origin, playerMins, playerMaxs, testPoint2, self->s.number, MASK_SHOT, 0 ); //check if unobstructed if ( trace2.fraction >= 1.0f ) { VectorCopy( forward, dir ); return true; } } //we couldnt find a new position return false; }
/** * @brief Tags an entity. */ void Tag( gentity_t *ent, team_t team, bool permanent ) { int data; vec3_t origin, mins, maxs; bool dead, player; gentity_t *beacon, **attachment; team_t targetTeam; // Get the beacon attachment owned by the tagging team. switch( team ) { case TEAM_ALIENS: attachment = &ent->alienTag; break; case TEAM_HUMANS: attachment = &ent->humanTag; break; default: return; } // Just refresh existing tags. if ( *attachment ) { RefreshTag( *attachment, true ); return; } switch( ent->s.eType ) { case ET_BUILDABLE: targetTeam = ent->buildableTeam; data = ent->s.modelindex; dead = G_Dead( ent ); player = false; BG_BuildableBoundingBox( ent->s.modelindex, mins, maxs ); break; case ET_PLAYER: targetTeam = (team_t)ent->client->pers.team; dead = G_Dead( ent ); player = true; BG_ClassBoundingBox( ent->client->pers.classSelection, mins, maxs, nullptr, nullptr, nullptr ); // Set beacon data to class (aliens) or weapon (humans). switch( targetTeam ) { case TEAM_ALIENS: data = ent->client->ps.stats[ STAT_CLASS ]; break; case TEAM_HUMANS: data = BG_GetPlayerWeapon( &ent->client->ps ); break; default: return; } break; default: return; } // Don't tag dead targets. if ( dead ) return; // Set beacon origin to center of target bounding box. VectorCopy( ent->s.origin, origin ); BG_MoveOriginToBBOXCenter( origin, mins, maxs ); // Create new beacon and attach it. beacon = New( origin, BCT_TAG, data, team ); beacon->tagAttachment = attachment; *attachment = beacon; beacon->s.bc_target = ent - g_entities; // Reset the entity's tag score. ent->tagScore = 0; // Set flags. if( player ) beacon->s.eFlags |= EF_BC_TAG_PLAYER; if( team != targetTeam ) beacon->s.eFlags |= EF_BC_ENEMY; // Set expiration time. if( permanent ) beacon->s.bc_etime = 0; else RefreshTag( beacon, true ); // Update the base clusterings. if ( ent->s.eType == ET_BUILDABLE ) BaseClustering::Update( beacon ); Propagate( beacon ); }
/** * @brief Tags an entity. * @todo Don't create a new beacon entity if retagged since that triggers regeneration of base * clusterings. */ void Tag( gentity_t *ent, team_t team, int owner, qboolean permanent ) { int i, data; vec3_t origin, mins, maxs; qboolean dead, player; gentity_t *beacon, **attachment; team_t targetTeam; switch( ent->s.eType ) { case ET_BUILDABLE: targetTeam = ent->buildableTeam; BG_BuildableBoundingBox( ent->s.modelindex, mins, maxs ); data = ent->s.modelindex; dead = ( ent->health <= 0 ); player = qfalse; break; case ET_PLAYER: targetTeam = (team_t)ent->client->pers.team; BG_ClassBoundingBox( ent->client->pers.classSelection, mins, maxs, NULL, NULL, NULL ); dead = ( ent->client && ent->client->ps.stats[ STAT_HEALTH ] <= 0 ); player = qtrue; // data is the class (aliens) or the weapon number (humans) switch( targetTeam ) { case TEAM_ALIENS: data = ent->client->ps.stats[ STAT_CLASS ]; break; case TEAM_HUMANS: data = BG_GetPlayerWeapon( &ent->client->ps ); break; default: return; } break; default: return; } for( i = 0; i < 3; i++ ) origin[ i ] = ent->s.origin[ i ] + ( mins[ i ] + maxs[ i ] ) / 2.0; switch( team ) { case TEAM_ALIENS: attachment = &ent->alienTag; break; case TEAM_HUMANS: attachment = &ent->humanTag; break; default: return; } if ( *attachment ) Delete( *attachment ); beacon = New( origin, BCT_TAG, data, team, owner ); beacon->tagAttachment = attachment; beacon->s.otherEntityNum2 = ent - g_entities; ent->tagScore = 0; if( player ) beacon->s.eFlags |= EF_BC_TAG_PLAYER; if( team != targetTeam ) beacon->s.eFlags |= EF_BC_ENEMY; if( permanent ) beacon->s.time2 = 0; else RefreshTag( beacon, true ); if( dead ) Delete( beacon, true ); else { *attachment = beacon; if ( ent->s.eType == ET_BUILDABLE ) BaseClustering::Update( beacon ); } Propagate( beacon ); }
/* =========== 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, *event; int flags; int savedPing; int teamLocal; int eventSequence; char userinfo[ MAX_INFO_STRING ]; vec3_t up = { 0.0f, 0.0f, 1.0f }, implant_dir, implant_angles, spawnPoint_velocity; int maxAmmo, maxClips; weapon_t weapon; qboolean fromImplant = qfalse, hatchingFailed = qfalse; index = ent - g_entities; client = ent->client; teamLocal = client->pers.teamSelection; //if client is dead and following teammate, stop following before spawning if( client->sess.spectatorClient != -1 ) { client->sess.spectatorClient = -1; client->sess.spectatorState = SPECTATOR_FREE; } // only start client if chosen a class and joined a team if( client->pers.classSelection == PCL_NONE && teamLocal == TEAM_NONE ) client->sess.spectatorState = SPECTATOR_FREE; else if( client->pers.classSelection == PCL_NONE ) client->sess.spectatorState = SPECTATOR_LOCKED; // if client is dead and following teammate, stop following before spawning if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) G_StopFollowing( ent ); 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.spectatorState != SPECTATOR_NOT ) { if( teamLocal == TEAM_NONE ) spawnPoint = G_SelectSpectatorSpawnPoint( spawn_origin, spawn_angles ); else if( teamLocal == TEAM_ALIENS ) spawnPoint = G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles ); else if( teamLocal == TEAM_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 ) { if( !spawnPoint->client ) //might be a human { //start spawn animation on spawnPoint G_SetBuildableAnim( spawnPoint, BANIM_SPAWN1, qtrue ); if( spawnPoint->buildableTeam == TEAM_ALIENS ) spawnPoint->clientSpawnTime = ALIEN_SPAWN_REPEAT_TIME; else if( spawnPoint->buildableTeam == TEAM_HUMANS ) spawnPoint->clientSpawnTime = HUMAN_SPAWN_REPEAT_TIME; } else { qboolean crouch; int i; float zoffs = 0.0f; trace_t tr; vec3_t neworigin, mins, maxs; fromImplant = qtrue; //spawning from a human // move the origin a bit on the Z axis so the aliens jumps out of the chest, not knees // also prevents grangers from getting stuck in ceilings and floors crouch = spawnPoint->client->ps.pm_flags & PMF_DUCKED; switch( client->pers.classSelection ) { case PCL_ALIEN_BUILDER0: if( !crouch ) zoffs = 19.0f; else zoffs = -4.0f; break; case PCL_ALIEN_BUILDER0_UPG: if( !crouch ) zoffs = 16.5f; else zoffs = -4.0f; break; case PCL_ALIEN_LEVEL0: if( !crouch ) zoffs = 15.0f; else zoffs = -9.1f; break; } spawn_origin[ 2 ] += zoffs; // check if the spot would block BG_ClassBoundingBox( client->pers.classSelection, mins, maxs, NULL, NULL, NULL ); trap_Trace( &tr, spawn_origin, mins, maxs, spawn_origin, spawnPoint->s.number, MASK_PLAYERSOLID ); // try to unblock the player if( tr.startsolid ) { Com_Printf("DEBUG: player is stuck!\n"); for( i = 0; i < 16*2; i++ ) { float a, r; VectorCopy( spawn_origin, neworigin ); a = (float)i / 16.0f * 2.0f * M_PI; #define fmod(a,n) ((a)-(n)*floor((a)/(n))) r = ( i < 16 ? 5.5f : 11.0f ) * 1.0f / cos( fmod( a+0.25f*M_PI, 0.5f*M_PI ) - 0.25f*M_PI ); neworigin[ 0 ] += cos( a ) * r; neworigin[ 1 ] += sin( a ) * r; trap_Trace( &tr, neworigin, mins, maxs, neworigin, spawnPoint->s.number, MASK_PLAYERSOLID ); if( !tr.startsolid ) { Com_Printf("DEBUG: player position fixed at iteration %i\n",i); VectorCopy( neworigin, spawn_origin ); break; } } } //reward the player that implanted this one if( spawnPoint->client->impregnatedBy >= 0 ) { gentity_t *granger; granger = &g_entities[ spawnPoint->client->impregnatedBy ]; G_AddCreditToClient( granger->client, ALIEN_IMPREGNATION_REWARD, qtrue ); AddScore( granger, ALIEN_IMPREGNATION_REWARD_SCORE ); } // kill the human, set up angles and velocity for the new alien if( !BG_InventoryContainsUpgrade( UP_BATTLESUIT, spawnPoint->client->ps.stats ) //humans without battlesuits always die || spawnPoint->client->ps.stats[ STAT_HEALTH ] < ALIEN_HATCHING_MAX_BATTLESUIT_HEALTH ) //battlesuits survive if high hp { //save viewangles and spawn velocity for velocity calculation VectorCopy( spawnPoint->client->ps.viewangles, implant_angles ); AngleVectors( implant_angles, implant_dir, NULL, NULL ); VectorCopy( spawnPoint->client->ps.velocity, spawnPoint_velocity ); //fire a nice chest exploding effect event = G_TempEntity( spawnPoint->s.pos.trBase, EV_ALIEN_HATCH ); VectorCopy( implant_dir, event->s.angles ); //kill the player G_Damage( spawnPoint, NULL, ent, NULL, NULL, spawnPoint->client->ps.stats[ STAT_HEALTH ], DAMAGE_NO_ARMOR, MOD_ALIEN_HATCH ); } else //human survives { //clear impregnation so the human won't explode again spawnPoint->client->isImpregnated = qfalse; spawnPoint->client->isImplantMature = qfalse; //make a sound event = G_TempEntity( spawnPoint->s.pos.trBase, EV_ALIEN_HATCH_FAILURE ); //damage the human G_Damage( spawnPoint, NULL, ent, NULL, NULL, ALIEN_FAILED_HATCH_DAMAGE, DAMAGE_NO_ARMOR, MOD_ALIEN_HATCH ); //kill the newly spawned alien VectorCopy( spawnPoint->client->ps.viewangles, implant_angles ); implant_dir[0] = 0.0f; implant_dir[1] = 0.0f; implant_dir[2] = 0.0f; hatchingFailed = qtrue; } } } } // toggle the teleport bit so the client knows to not lerp flags = ( ent->client->ps.eFlags & EF_TELEPORT_BIT ) ^ 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_SPECSTATE ] = client->sess.spectatorState; 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; // calculate each client's acceleration ent->evaluateAcceleration = qtrue; client->ps.stats[ STAT_MISC ] = 0; client->buildTimer = 0; client->ps.eFlags = flags; client->ps.clientNum = index; BG_ClassBoundingBox( ent->client->pers.classSelection, ent->r.mins, ent->r.maxs, NULL, NULL, NULL ); if( client->sess.spectatorState == SPECTATOR_NOT ) client->ps.stats[ STAT_MAX_HEALTH ] = BG_Class( ent->client->pers.classSelection )->health; else client->ps.stats[ STAT_MAX_HEALTH ] = 100; // clear entity values if( ent->client->pers.classSelection == PCL_HUMAN ) { BG_AddUpgradeToInventory( UP_MEDKIT, client->ps.stats ); weapon = client->pers.humanItemSelection; } else if( client->sess.spectatorState == SPECTATOR_NOT ) weapon = BG_Class( ent->client->pers.classSelection )->startWeapon; else weapon = WP_NONE; maxAmmo = BG_Weapon( weapon )->maxAmmo; maxClips = BG_Weapon( weapon )->maxClips; client->ps.stats[ STAT_WEAPON ] = weapon; client->ps.ammo = maxAmmo; client->ps.clips = maxClips; // We just spawned, not changing weapons client->ps.persistant[ PERS_NEWWEAPON ] = 0; ent->client->ps.stats[ STAT_CLASS ] = ent->client->pers.classSelection; ent->client->ps.stats[ STAT_TEAM ] = 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 ] = STAMINA_MAX; //never impregnated after respawning client->isImpregnated = qfalse; client->isImplantMature = qfalse; 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.spectatorState == SPECTATOR_NOT && client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) { if( ent == spawn ) { //evolution particle system G_AddPredictableEvent( ent, EV_ALIEN_EVOLVE, DirToByte( up ) ); } else if( !fromImplant ) //regular egg { 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 //implanted egg { VectorCopy( implant_angles, spawn_angles ); VectorScale( implant_dir, ALIEN_HATCHING_VELOCITY, client->ps.velocity ); VectorAdd( client->ps.velocity, spawnPoint_velocity, client->ps.velocity ); } } else if( client->sess.spectatorState == SPECTATOR_NOT && client->ps.stats[ STAT_TEAM ] == TEAM_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.spectatorState == SPECTATOR_NOT ) { trap_LinkEntity( ent ); // force the base weapon up if( client->pers.teamSelection == TEAM_HUMANS ) G_ForceWeaponChange( ent, weapon ); 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; ent->nextRegenTime = 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; } } } client->lastRantBombTime = level.time; // 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.spectatorState == SPECTATOR_NOT ) { BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); trap_LinkEntity( ent ); } // 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 ); // kill him instantly after respawning if hatching failed if( fromImplant && hatchingFailed ) { VectorCopy( spawnPoint->client->ps.velocity, client->ps.velocity ); client->ps.stats[ STAT_HEALTH ] = ent->health = 0; player_die( ent, NULL, spawnPoint, 0, MOD_ALIEN_HATCH_FAILED ); } }