/* =========== PlayerSpawn Called every time a player is placed fresh in the world: after the first PlayerBegin, and after each respawn Initializes all non-persistant parts of playerState ============ */ void PlayerSpawn(gentity_t *ent) { int index; vec3_t spawn_origin, spawn_angles; gplayer_t *player; int i; playerPersistant_t saved; playerSession_t savedSess; int persistant[MAX_PERSISTANT]; gentity_t *spawnPoint; gentity_t *tent; int flags; int savedPing; // char *savedAreaBits; int accuracy_hits, accuracy_shots; int eventSequence; int startWeapon, startAmmo; index = ent - g_entities; player = ent->player; VectorClear(spawn_origin); // find a spawn point // do it before setting health back up, so farthest // ranging doesn't count this player if ( player->sess.sessionTeam == TEAM_SPECTATOR ) { spawnPoint = SelectSpectatorSpawnPoint ( spawn_origin, spawn_angles); } else if (g_gametype.integer >= GT_CTF ) { // all base oriented team games use the CTF spawn points spawnPoint = SelectCTFSpawnPoint ( player->sess.sessionTeam, player->pers.teamState.state, spawn_origin, spawn_angles, !!(ent->r.svFlags & SVF_BOT)); } else { // the first spawn should be at a good looking spot if ( player->pers.initialSpawn && player->pers.localClient ) { spawnPoint = SelectInitialSpawnPoint(spawn_origin, spawn_angles, !!(ent->r.svFlags & SVF_BOT)); } else { // don't spawn near existing origin if possible spawnPoint = SelectSpawnPoint ( player->ps.origin, spawn_origin, spawn_angles, !!(ent->r.svFlags & SVF_BOT)); } } player->pers.teamState.state = TEAM_ACTIVE; // always clear the kamikaze flag ent->s.eFlags &= ~EF_KAMIKAZE; // toggle the teleport bit so the client knows to not lerp // and never clear the voted flag flags = ent->player->ps.eFlags & (EF_TELEPORT_BIT | EF_VOTED | EF_TEAMVOTED); flags ^= EF_TELEPORT_BIT; // clear everything but the persistant data saved = player->pers; savedSess = player->sess; savedPing = player->ps.ping; // savedAreaBits = player->areabits; accuracy_hits = player->accuracy_hits; accuracy_shots = player->accuracy_shots; for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) { persistant[i] = player->ps.persistant[i]; } eventSequence = player->ps.eventSequence; Com_Memset (player, 0, sizeof(*player)); player->pers = saved; player->sess = savedSess; player->ps.ping = savedPing; // player->areabits = savedAreaBits; player->accuracy_hits = accuracy_hits; player->accuracy_shots = accuracy_shots; player->lastkilled_player = -1; for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) { player->ps.persistant[i] = persistant[i]; } player->ps.eventSequence = eventSequence; // increment the spawncount so the client will detect the respawn player->ps.persistant[PERS_SPAWN_COUNT]++; player->ps.persistant[PERS_TEAM] = player->sess.sessionTeam; player->airOutTime = level.time + 12000; // set max health player->pers.maxHealth = PlayerHandicap( player ); // clear entity values player->ps.stats[STAT_MAX_HEALTH] = player->pers.maxHealth; player->ps.eFlags = flags; player->ps.contents = CONTENTS_BODY; player->ps.collisionType = ( g_playerCapsule.integer == 1 ) ? CT_CAPSULE : CT_AABB; ent->s.groundEntityNum = ENTITYNUM_NONE; ent->player = &level.players[index]; ent->takedamage = qtrue; ent->inuse = qtrue; ent->classname = "player"; ent->clipmask = MASK_PLAYERSOLID; ent->die = player_die; ent->waterlevel = 0; ent->watertype = 0; ent->flags = 0; ent->player->headless = qfalse; VectorCopy (playerMins, player->ps.mins); VectorCopy (playerMaxs, player->ps.maxs); player->ps.playerNum = index; if ( g_instaGib.integer == 1 ) { startWeapon = WP_RAILGUN; startAmmo = 50; } else if ( g_instaGib.integer == 2 ) { startWeapon = WP_ROCKET_LAUNCHER; startAmmo = 50; } else { startWeapon = WP_MACHINEGUN; startAmmo = 100; } player->ps.stats[STAT_WEAPONS] = ( 1 << startWeapon ); player->ps.ammo[startWeapon] = startAmmo; player->ps.stats[STAT_WEAPONS] |= ( 1 << WP_GAUNTLET ); player->ps.ammo[WP_GAUNTLET] = -1; player->ps.ammo[WP_GRAPPLING_HOOK] = -1; // health will count down towards max_health ent->health = player->ps.stats[STAT_HEALTH] = player->ps.stats[STAT_MAX_HEALTH] = MAX_HEALTH; G_SetOrigin( ent, spawn_origin ); VectorCopy( spawn_origin, player->ps.origin ); // the respawned flag will be cleared after the attack and jump keys come up player->ps.pm_flags |= PMF_RESPAWNED; trap_GetUsercmd( player - level.players, &ent->player->pers.cmd ); SetPlayerViewAngle( ent, spawn_angles ); // don't allow full run speed for a bit player->ps.pm_flags |= PMF_TIME_KNOCKBACK; player->ps.pm_time = 100; player->respawnTime = level.time; player->inactivityTime = level.time + g_inactivity.integer * 1000; player->latched_buttons = 0; // set default animations player->ps.torsoAnim = TORSO_STAND; player->ps.legsAnim = LEGS_IDLE; if (!level.intermissiontime) { if (ent->player->sess.sessionTeam != TEAM_SPECTATOR) { G_KillBox(ent); // force the base weapon up player->ps.weapon = startWeapon; player->ps.weaponstate = WEAPON_READY; // fire the targets of the spawn point G_UseTargets(spawnPoint, ent); // select the highest weapon number available, after any spawn given items have fired player->ps.weapon = 1; for (i = WP_NUM_WEAPONS - 1 ; i > 0 ; i--) { if (player->ps.stats[STAT_WEAPONS] & (1 << i)) { player->ps.weapon = i; break; } } // positively link the player, even if the command times are weird VectorCopy(ent->player->ps.origin, ent->r.currentOrigin); tent = G_TempEntity(ent->player->ps.origin, EV_PLAYER_TELEPORT_IN); tent->s.playerNum = ent->s.playerNum; trap_LinkEntity (ent); } } else { // move players to intermission MovePlayerToIntermission(ent); } // run a player frame to drop exactly to the floor, // initialize animations and other things player->ps.commandTime = level.time - 100; ent->player->pers.cmd.serverTime = level.time; PlayerThink( ent-g_entities ); // run the presend to set anything else, follow spectators wait // until all players have been reconnected after map_restart if ( ent->player->sess.spectatorState != SPECTATOR_FOLLOW ) { PlayerEndFrame( ent ); } // clear entity state values BG_PlayerStateToEntityState( &player->ps, &ent->s, qtrue ); // we don't want players being backward-reconciled to the place they died G_ResetHistory( ent ); }
/* =========== G_ReachedPath =========== */ qboolean G_ReachedPath(gentity_t *ent, qboolean check) { gentity_t *point; qboolean backward; if (!(ent->s.eFlags & EF_PATHMODE)) { return qfalse; } if (ent->player) { backward = (ent->player->ps.eFlags & EF_TRAINBACKWARD); } else { backward = (ent->s.eFlags & EF_TRAINBACKWARD); } if (backward) { point = ent->prevTrain; } else { point = ent->nextTrain; } if (!point && ent->player) { // Previous point if (backward) point = ent->nextTrain; else point = ent->prevTrain; // Stop train return qfalse; } else if (!point && !ent->player) { // end of path if (!(ent->spawnflags & 1)) { // Stop train return qfalse; } // Previous point if (backward) point = ent->nextTrain; else point = ent->prevTrain; // Go back the way you came backward = !backward; ent->s.eFlags ^= EF_TRAINBACKWARD; // Next point if (backward) point = ent->prevTrain; else point = ent->nextTrain; if (!point) { return qfalse; } } if ((!backward && point == point->nextTrain) || (backward && point == point->prevTrain)) { // Entity points to self... G_Printf("DEBUG: Entity points to self!\n"); return qfalse; } // ZTM: Check if we have made it to the next train // Doesn't work with PATHF_AXIS! if (check) { vec3_t targetPos; vec3_t origin; vec_t dist; VectorCopy(point->s.origin, targetPos); if (ent->player) VectorCopy(ent->player->ps.origin, origin); else VectorCopy(ent->s.origin, origin); if (ent->player && ent->player->ps.pathMode == PATHMODE_SIDE) { // "2D" path origin[2] = targetPos[2] = 0; // Don't compare Z } dist = Distance(origin, targetPos); if (dist > 20.0f) { return qfalse; } } // fire all other targets G_UseTargets( point, ent ); // Setup next move if (backward) { ent->prevTrain = point->prevTrain; if (ent->prevTrain) { ent->nextTrain = point; } } else { ent->nextTrain = point->nextTrain; if (ent->nextTrain) { ent->prevTrain = point; } } // set the new trajectory if (ent->prevTrain) { VectorCopy( ent->prevTrain->s.origin, ent->pos1 ); } if (ent->nextTrain) { VectorCopy( ent->nextTrain->s.origin, ent->pos2 ); } if (ent->s.eType == ET_MOVER) { float speed; vec3_t move; float length; // if the path_corner has a speed, use that if ( point->speed ) { speed = point->speed; } else { // otherwise use the train's speed speed = ent->speed; } if ( speed < 1 ) { speed = 1; } // calculate duration VectorSubtract( ent->pos2, ent->pos1, move ); length = VectorLength( move ); ent->s.pos.trDuration = length * 1000 / speed; // Tequila comment: Be sure to send to clients after any fast move case ent->r.svFlags &= ~SVF_NOCLIENT; // Tequila comment: Fast move case if(ent->s.pos.trDuration<1) { // Tequila comment: As trDuration is used later in a division, we need to avoid that case now // With null trDuration, // the calculated rocks bounding box becomes infinite and the engine think for a short time // any entity is riding that mover but not the world entity... In rare case, I found it // can also stuck every map entities after func_door are used. // The desired effect with very very big speed is to have instant move, so any not null duration // lower than a frame duration should be sufficient. // Afaik, the negative case don't have to be supported. ent->s.pos.trDuration=1; // Tequila comment: Don't send entity to clients so it becomes really invisible ent->r.svFlags |= SVF_NOCLIENT; } // looping sound ent->s.loopSound = point->soundLoop; // start it going if (backward) SetMoverState( ent, MOVER_2TO1, level.time ); else SetMoverState( ent, MOVER_1TO2, level.time ); // if there is a "wait" value on the target, don't start moving yet if ( point->wait ) { ent->nextthink = level.time + point->wait * 1000; ent->think = Think_BeginMoving; ent->s.pos.trType = TR_STATIONARY; } } else if (ent->player) { vec3_t dir; vec3_t viewAngles; int mode; // Set path movement style mode = point->moverState; // Unset style means use pervious style. if (!mode) { mode = ent->player->ps.pathMode; } VectorCopy( ent->pos1, ent->player->ps.grapplePoint ); VectorCopy( ent->pos2, ent->player->ps.nextPoint ); if (backward) VectorSubtract( ent->pos1, ent->player->ps.origin, dir ); else VectorSubtract( ent->pos2, ent->player->ps.origin, dir ); vectoangles( dir, viewAngles ); viewAngles[ROLL] = ent->player->ps.viewangles[ROLL]; viewAngles[PITCH] = ent->player->ps.viewangles[PITCH]; SetPlayerViewAngle(ent, viewAngles); if (mode == PATHMODE_SIDE) { if (backward) { ent->player->ps.stats[STAT_DEAD_YAW] = viewAngles[YAW]-90; } else { ent->player->ps.stats[STAT_DEAD_YAW] = viewAngles[YAW]+90; } } else { ent->player->ps.stats[STAT_DEAD_YAW] = viewAngles[YAW]; } if (mode != ent->player->ps.pathMode) { // ZTM: Do we need to do anything when the mode changes? ent->player->ps.pathMode = mode; } } return qtrue; }