/** * @brief Shuffle active players onto teams */ void G_shuffleTeams(void) { int i; team_t cTeam; //, cMedian = level.numNonSpectatorClients / 2; int cnt = 0; int sortClients[MAX_CLIENTS]; gclient_t *cl; G_teamReset(TEAM_AXIS, qtrue); G_teamReset(TEAM_ALLIES, qtrue); for (i = 0; i < level.numConnectedClients; i++) { cl = level.clients + level.sortedClients[i]; if (cl->sess.sessionTeam != TEAM_AXIS && cl->sess.sessionTeam != TEAM_ALLIES) { continue; } sortClients[cnt++] = level.sortedClients[i]; } qsort(sortClients, cnt, sizeof(int), G_SortPlayersByXP); for (i = 0; i < cnt; i++) { cl = level.clients + sortClients[i]; // cTeam = (i % 2) + TEAM_AXIS; cTeam = (((i + 1) % 4) - ((i + 1) % 2)) / 2 + TEAM_AXIS; if (cl->sess.sessionTeam != cTeam) { G_LeaveTank(g_entities + sortClients[i], qfalse); G_RemoveClientFromFireteams(sortClients[i], qtrue, qfalse); if (g_landminetimeout.integer) { G_ExplodeMines(g_entities + sortClients[i]); } G_FadeItems(g_entities + sortClients[i], MOD_SATCHEL); } cl->sess.sessionTeam = cTeam; G_UpdateCharacter(cl); ClientUserinfoChanged(sortClients[i]); ClientBegin(sortClients[i]); } AP("cp \"^1Teams have been shuffled!\n\""); }
void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) { int contents = 0, i, killer = ENTITYNUM_WORLD; char *killerName = "<world>"; qboolean nogib = qtrue; gitem_t *item = NULL; gentity_t *ent; qboolean killedintank = qfalse; //float timeLived; weapon_t weap = BG_WeaponForMOD( meansOfDeath ); // G_Printf( "player_die\n" ); if(attacker == self) { if(self->client) { self->client->pers.playerStats.suicides++; trap_PbStat ( self - g_entities , "suicide" , va ( "%d %d %d" , self->client->sess.sessionTeam , self->client->sess.playerType , weap ) ) ; } } else if(OnSameTeam( self, attacker )) { G_LogTeamKill( attacker, weap ); } else { G_LogDeath( self, weap ); G_LogKill( attacker, weap ); if( g_gamestate.integer == GS_PLAYING ) { if( attacker->client ) { attacker->client->combatState |= (1<<COMBATSTATE_KILLEDPLAYER); } } } // RF, record this death in AAS system so that bots avoid areas which have high death rates if( !OnSameTeam( self, attacker ) ) { // LC - not needed // BotRecordTeamDeath( self->s.number ); self->isProp = qfalse; // were we teamkilled or not? } else { self->isProp = qtrue; } // if we got killed by a landmine, update our map if( self->client && meansOfDeath == MOD_LANDMINE ) { // if it's an enemy mine, update both teamlists /*int teamNum; mapEntityData_t *mEnt; mapEntityData_Team_t *teamList; teamNum = inflictor->s.teamNum % 4; teamList = self->client->sess.sessionTeam == TEAM_AXIS ? &mapEntityData[0] : &mapEntityData[1]; if((mEnt = G_FindMapEntityData(teamList, inflictor-g_entities)) != NULL) { G_FreeMapEntityData( teamList, mEnt ); } if( teamNum != self->client->sess.sessionTeam ) { teamList = self->client->sess.sessionTeam == TEAM_AXIS ? &mapEntityData[1] : &mapEntityData[0]; if((mEnt = G_FindMapEntityData(teamList, inflictor-g_entities)) != NULL) { G_FreeMapEntityData( teamList, mEnt ); } }*/ mapEntityData_t *mEnt; if((mEnt = G_FindMapEntityData(&mapEntityData[0], inflictor-g_entities)) != NULL) { G_FreeMapEntityData( &mapEntityData[0], mEnt ); } if((mEnt = G_FindMapEntityData(&mapEntityData[1], inflictor-g_entities)) != NULL) { G_FreeMapEntityData( &mapEntityData[1], mEnt ); } } { mapEntityData_t *mEnt; mapEntityData_Team_t *teamList = self->client->sess.sessionTeam == TEAM_AXIS ? &mapEntityData[1] : &mapEntityData[0]; // swapped, cause enemy team mEnt = G_FindMapEntityDataSingleClient( teamList, NULL, self->s.number, -1 ); while( mEnt ) { if( mEnt->type == ME_PLAYER_DISGUISED ) { mapEntityData_t* mEntFree = mEnt; mEnt = G_FindMapEntityDataSingleClient( teamList, mEnt, self->s.number, -1 ); G_FreeMapEntityData( teamList, mEntFree ); } else { mEnt = G_FindMapEntityDataSingleClient( teamList, mEnt, self->s.number, -1 ); } } } if( self->tankLink ) { G_LeaveTank( self, qfalse ); killedintank = qtrue; } if( self->client->ps.pm_type == PM_DEAD || g_gamestate.integer == GS_INTERMISSION ) { return; } // OSP - death stats handled out-of-band of G_Damage for external calls G_addStats(self, attacker, damage, meansOfDeath); // OSP self->client->ps.pm_type = PM_DEAD; G_AddEvent( self, EV_STOPSTREAMINGSOUND, 0); if(attacker) { killer = attacker->s.number; killerName = (attacker->client) ? attacker->client->pers.netname : "<non-client>"; } if(attacker == 0 || killer < 0 || killer >= MAX_CLIENTS) { killer = ENTITYNUM_WORLD; killerName = "<world>"; } if(g_gamestate.integer == GS_PLAYING) { char *obit; if(meansOfDeath < 0 || meansOfDeath >= sizeof(modNames) / sizeof(modNames[0])) { obit = "<bad obituary>"; } else { obit = modNames[meansOfDeath]; } G_LogPrintf("Kill: %i %i %i: %s killed %s by %s\n", killer, self->s.number, meansOfDeath, killerName, self->client->pers.netname, obit ); } // broadcast the death event to everyone ent = G_TempEntity( self->r.currentOrigin, EV_OBITUARY ); ent->s.eventParm = meansOfDeath; ent->s.otherEntityNum = self->s.number; ent->s.otherEntityNum2 = killer; ent->r.svFlags = SVF_BROADCAST; // send to everyone self->enemy = attacker; self->client->ps.persistant[PERS_KILLED]++; // JPW NERVE -- if player is holding ticking grenade, drop it if ((self->client->ps.grenadeTimeLeft) && (self->s.weapon != WP_DYNAMITE) && (self->s.weapon != WP_LANDMINE) && (self->s.weapon != WP_SATCHEL) && (self->s.weapon != WP_TRIPMINE)) { vec3_t launchvel, launchspot; launchvel[0] = crandom(); launchvel[1] = crandom(); launchvel[2] = random(); VectorScale( launchvel, 160, launchvel ); VectorCopy(self->r.currentOrigin, launchspot); launchspot[2] += 40; { // Gordon: fixes premature grenade explosion, ta bani ;) gentity_t *m = fire_grenade(self, launchspot, launchvel, self->s.weapon); m->damage = 0; } } if (attacker && attacker->client) { if ( attacker == self || OnSameTeam (self, attacker ) ) { // DHM - Nerve :: Complaint lodging if( attacker != self && level.warmupTime <= 0 && g_gamestate.integer == GS_PLAYING) { if( attacker->client->pers.localClient ) { trap_SendServerCommand( self-g_entities, "complaint -4" ); } else { if( meansOfDeath != MOD_CRUSH_CONSTRUCTION && meansOfDeath != MOD_CRUSH_CONSTRUCTIONDEATH && meansOfDeath != MOD_CRUSH_CONSTRUCTIONDEATH_NOATTACKER ) { if( g_complaintlimit.integer ) { if( !(meansOfDeath == MOD_LANDMINE && g_disableComplaints.integer & TKFL_MINES ) && !((meansOfDeath == MOD_ARTY || meansOfDeath == MOD_AIRSTRIKE) && g_disableComplaints.integer & TKFL_AIRSTRIKE ) && !(meansOfDeath == MOD_MORTAR && g_disableComplaints.integer & TKFL_MORTAR ) ) { trap_SendServerCommand( self-g_entities, va( "complaint %i", attacker->s.number ) ); self->client->pers.complaintClient = attacker->s.clientNum; self->client->pers.complaintEndTime = level.time + 20500; } } } } } // high penalty to offset medic heal /* AddScore( attacker, WOLF_FRIENDLY_PENALTY ); */ if( g_gametype.integer == GT_WOLF_LMS ) { AddKillScore( attacker, WOLF_FRIENDLY_PENALTY ); } } else { //G_AddExperience( attacker, 1 ); // JPW NERVE -- mostly added as conveneience so we can tweak from the #defines all in one place AddScore(attacker, WOLF_FRAG_BONUS); if( g_gametype.integer == GT_WOLF_LMS ) { if( level.firstbloodTeam == -1 ) level.firstbloodTeam = attacker->client->sess.sessionTeam; AddKillScore( attacker, WOLF_FRAG_BONUS ); } attacker->client->lastKillTime = level.time; } } else { AddScore( self, -1 ); if( g_gametype.integer == GT_WOLF_LMS ) AddKillScore( self, -1 ); } // Add team bonuses Team_FragBonuses(self, inflictor, attacker); // drop flag regardless if (self->client->ps.powerups[PW_REDFLAG]) { item = BG_FindItem("Red Flag"); if (!item) item = BG_FindItem("Objective"); self->client->ps.powerups[PW_REDFLAG] = 0; } if (self->client->ps.powerups[PW_BLUEFLAG]) { item = BG_FindItem("Blue Flag"); if (!item) item = BG_FindItem("Objective"); self->client->ps.powerups[PW_BLUEFLAG] = 0; } if (item) { vec3_t launchvel = { 0, 0, 0 }; gentity_t *flag = LaunchItem(item, self->r.currentOrigin, launchvel, self->s.number); flag->s.modelindex2 = self->s.otherEntityNum2;// JPW NERVE FIXME set player->otherentitynum2 with old modelindex2 from flag and restore here flag->message = self->message; // DHM - Nerve :: also restore item name // Clear out player's temp copies self->s.otherEntityNum2 = 0; self->message = NULL; } // send a fancy "MEDIC!" scream. Sissies, ain' they? if (self->client != NULL) { if( self->health > GIB_HEALTH && meansOfDeath != MOD_SUICIDE && meansOfDeath != MOD_SWITCHTEAM ) { G_AddEvent( self, EV_MEDIC_CALL, 0 ); } } Cmd_Score_f( self ); // show scores // send updated scores to any clients that are following this one, // or they would get stale scoreboards for(i=0; i<level.numConnectedClients; i++) { gclient_t *client = &level.clients[level.sortedClients[i]]; if(client->pers.connected != CON_CONNECTED) continue; if(client->sess.sessionTeam != TEAM_SPECTATOR) continue; if(client->sess.spectatorClient == self->s.number) { Cmd_Score_f(g_entities + level.sortedClients[i]); } } self->takedamage = qtrue; // can still be gibbed self->r.contents = CONTENTS_CORPSE; //self->s.angles[2] = 0; self->s.powerups = 0; self->s.loopSound = 0; self->client->limboDropWeapon = self->s.weapon; // store this so it can be dropped in limbo LookAtKiller( self, inflictor, attacker ); self->client->ps.viewangles[0] = 0; self->client->ps.viewangles[2] = 0; //VectorCopy( self->s.angles, self->client->ps.viewangles ); // trap_UnlinkEntity( self ); self->r.maxs[2] = self->client->ps.crouchMaxZ; //% 0; // ydnar: so bodies don't clip into world self->client->ps.maxs[2] = self->client->ps.crouchMaxZ; //% 0; // ydnar: so bodies don't clip into world trap_LinkEntity( self ); // don't allow respawn until the death anim is done // g_forcerespawn may force spawning at some later time self->client->respawnTime = level.timeCurrent + 800; // remove powerups memset( self->client->ps.powerups, 0, sizeof(self->client->ps.powerups) ); // never gib in a nodrop // FIXME: contents is always 0 here if ( self->health <= GIB_HEALTH && !(contents & CONTENTS_NODROP) ) { GibEntity( self, killer ); nogib = qfalse; } if(nogib){ // normal death // for the no-blood option, we need to prevent the health // from going to gib level if ( self->health <= GIB_HEALTH ) { self->health = GIB_HEALTH + 1; } // Arnout: re-enable this for flailing /* if( self->client->ps.groundEntityNum == ENTITYNUM_NONE ) { self->client->ps.pm_flags |= PMF_FLAILING; self->client->ps.pm_time = 750; BG_AnimScriptAnimation( &self->client->ps, ANIM_MT_FLAILING, qtrue ); // Face explosion directory { vec3_t angles; vectoangles( self->client->ps.velocity, angles ); self->client->ps.viewangles[YAW] = angles[YAW]; SetClientViewAngle( self, self->client->ps.viewangles ); } } else*/ // DHM - Play death animation, and set pm_time to delay 'fallen' animation //if( G_IsSinglePlayerGame() && self->client->sess.sessionTeam == TEAM_ALLIES ) { // // play "falldown" animation since allies bots won't ever die completely // self->client->ps.pm_time = BG_AnimScriptEvent( &self->client->ps, self->client->pers.character->animModelInfo, ANIM_ET_FALLDOWN, qfalse, qtrue ); // G_StartPlayerAppropriateSound(self, "death"); //} else { self->client->ps.pm_time = BG_AnimScriptEvent( &self->client->ps, self->client->pers.character->animModelInfo, ANIM_ET_DEATH, qfalse, qtrue ); // death animation script already contains sound //} // record the death animation to be used later on by the corpse self->client->torsoDeathAnim = self->client->ps.torsoAnim; self->client->legsDeathAnim = self->client->ps.legsAnim; G_AddEvent( self, EV_DEATH1 + 1, killer ); // the body can still be gibbed self->die = body_die; } if( meansOfDeath == MOD_MACHINEGUN ) { switch( self->client->sess.sessionTeam ) { case TEAM_AXIS: level.axisMG42Counter = level.time; break; case TEAM_ALLIES: level.alliesMG42Counter = level.time; break; } } G_FadeItems( self, MOD_SATCHEL ); CalculateRanks(); if( killedintank /*Gordon: automatically go to limbo from tank*/ ) { limbo( self, qfalse ); // but no corpse } else if ( (meansOfDeath == MOD_SUICIDE && g_gamestate.integer == GS_PLAYING) ) { limbo( self, qtrue ); } else if( g_gametype.integer == GT_WOLF_LMS ) { if( !G_CountTeamMedics( self->client->sess.sessionTeam, qtrue ) ) { limbo( self, qtrue ); } } }
/* =========== ClientDisconnect Called when a player drops from the server. Will not be called between levels. This should NOT be called directly by any game logic, call trap_DropClient(), which will call this and do server system housekeeping. ============ */ void ClientDisconnect(int clientNum) { gentity_t *ent; gentity_t *flag = NULL; gitem_t *item = NULL; vec3_t launchvel; int i; ent = g_entities + clientNum; if (!ent->client) { return; } G_RemoveClientFromFireteams(clientNum, qtrue, qfalse); G_RemoveFromAllIgnoreLists(clientNum); G_LeaveTank(ent, qfalse); // Nico, remove the client from all specInvited lists for (i = 0; i < level.numConnectedClients; ++i) { COM_BitClear(level.clients[level.sortedClients[i]].sess.specInvitedClients, clientNum); } // stop any following clients for (i = 0 ; i < level.numConnectedClients ; i++) { flag = g_entities + level.sortedClients[i]; if (flag->client->sess.sessionTeam == TEAM_SPECTATOR && flag->client->sess.spectatorState == SPECTATOR_FOLLOW && flag->client->sess.spectatorClient == clientNum) { StopFollowing(flag); } if (flag->client->ps.pm_flags & PMF_LIMBO && flag->client->sess.spectatorClient == clientNum) { Cmd_FollowCycle_f(flag, 1); } } G_FadeItems(ent, MOD_SATCHEL); // remove ourself from teamlists { mapEntityData_t *mEnt; mapEntityData_Team_t *teamList; for (i = 0; i < 2; i++) { teamList = &mapEntityData[i]; if ((mEnt = G_FindMapEntityData(&mapEntityData[0], ent - g_entities)) != NULL) { G_FreeMapEntityData(teamList, mEnt); } mEnt = G_FindMapEntityDataSingleClient(teamList, NULL, ent->s.number, -1); while (mEnt) { mapEntityData_t *mEntFree = mEnt; mEnt = G_FindMapEntityDataSingleClient(teamList, mEnt, ent->s.number, -1); G_FreeMapEntityData(teamList, mEntFree); } } } // send effect if they were completely connected if (ent->client->pers.connected == CON_CONNECTED && ent->client->sess.sessionTeam != TEAM_SPECTATOR && !(ent->client->ps.pm_flags & PMF_LIMBO)) { // They don't get to take powerups with them! // Especially important for stuff like CTF flags // New code for tossing flags if (ent->client->ps.powerups[PW_REDFLAG]) { item = BG_FindItem("Red Flag"); if (!item) { item = BG_FindItem("Objective"); } ent->client->ps.powerups[PW_REDFLAG] = 0; } if (ent->client->ps.powerups[PW_BLUEFLAG]) { item = BG_FindItem("Blue Flag"); if (!item) { item = BG_FindItem("Objective"); } ent->client->ps.powerups[PW_BLUEFLAG] = 0; } if (item) { // OSP - fix for suicide drop exploit through walls/gates launchvel[0] = 0; //crandom()*20; launchvel[1] = 0; //crandom()*20; launchvel[2] = 0; //10+random()*10; flag = LaunchItem(item, ent->r.currentOrigin, launchvel, ent - g_entities); flag->s.modelindex2 = ent->s.otherEntityNum2; // JPW NERVE FIXME set player->otherentitynum2 with old modelindex2 from flag and restore here flag->message = ent->message; // DHM - Nerve :: also restore item name // Clear out player's temp copies ent->s.otherEntityNum2 = 0; ent->message = NULL; } } G_LogPrintf("ClientDisconnect: %i\n", clientNum); trap_UnlinkEntity(ent); ent->s.modelindex = 0; ent->inuse = qfalse; ent->classname = "disconnected"; ent->client->pers.connected = CON_DISCONNECTED; ent->client->ps.persistant[PERS_TEAM] = TEAM_FREE; i = ent->client->sess.sessionTeam; ent->client->sess.sessionTeam = TEAM_FREE; ent->active = 0; trap_SetConfigstring(CS_PLAYERS + clientNum, ""); CalculateRanks(); }