// Copyright (C) 1999-2000 Id Software, Inc. // #include "g_client.h" #include "g_local.h" #include "g_groups.h" #include "g_main.h" #include "g_cmds.h" #include "g_main.h" #include "g_spawn.h" #include "g_items.h" #include "g_lua.h" #include "g_logger.h" #include "g_syscalls.h" #include "ai_common.h" // predeclare ai main functions extern int32_t AI_main_BotAISetupClient(int32_t client, bot_settings_t* settings); extern int32_t AI_main_BotAIShutdownClient(int32_t client); enum g_clientLimits_e { MAX_SPAWN_POINTS = 256 }; reconData_t g_reconData[MAX_RECON_NAMES]; //!< recon data for a limited ammount of clients int32_t g_reconNum; // g_client.c -- client functions that don't happen every frame void G_Client_StoreClientInitialStatus(gentity_t *ent); //! players mins static vec3_t playerMins = { -12, -12, -24 }; //RPG-X : TiM - {-15, -15, -24} //! players maxs static vec3_t playerMaxs = { 12, 12, 32 }; // {15, 15, 32} clInitStatus_t clientInitialStatus[MAX_CLIENTS]; //TiM: For easier transport setup /** * Function that makes transport setup easier * \author Ubergames - TiM */ void G_InitTransport(int32_t clientNum, vec3_t origin, vec3_t angles) { gentity_t* tent = NULL; TransDat[clientNum].beamTime = level.time + 8000; g_entities[clientNum].client->ps.powerups[PW_BEAM_OUT] = level.time + 8000; //Transfer stored data to active beamer VectorCopy(origin, TransDat[clientNum].currentCoord.origin); VectorCopy(angles, TransDat[clientNum].currentCoord.angles); tent = G_TempEntity(g_entities[clientNum].client->ps.origin, EV_PLAYER_TRANSPORT_OUT); tent->s.clientNum = clientNum; } /*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) INITIAL -----DESCRIPTION----- potential spawning position for deathmatch games. -----SPAWNFLAGS----- 1: INITIAL - Preferred spawn for the first spawn of a client when entering a match. -----KEYS----- "target" - entities with matching targetname will be fired if someone spawns here. "nobots" - if 1 will prevent bots from using this spot. "nohumans" - if 1 will prevent non-bots from using this spot. */ /** * Spawn function for deathmatch spawnpoint */ void SP_info_player_deathmatch(gentity_t *ent) { int32_t i = 0; ent->type = ENT_INFO_PLAYER_START; if (strcmp(ent->classname, "info_player_deathmatch") != 0) { ent->classname = "info_player_deathmatch"; } G_SpawnInt("nobots", "0", &i); if (i != 0) { ent->flags |= FL_NO_BOTS; } G_SpawnInt("nohumans", "0", &i); if (i != 0) { ent->flags |= FL_NO_HUMANS; } trap_LinkEntity(ent); } /*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) -----DESCRIPTION----- The intermission will be viewed from this point. It is also used to spawn spectators. Target an info_notnull or similar for the view direction. -----SPAWNFLAGS----- none -----KEYS----- none */ /** * Spawn function for intermission entity. */ void SP_info_player_intermission(gentity_t *ent) { ent->type = ENT_INFO_PLAYER_INTERMISSION; } /** * Determine whether spot would telefrag. * * \param spot Spot to check. * \return Whether this spot would telefrag. */ static qboolean G_Client_SpotWouldTelefrag(gentity_t *spot) { int32_t i = 0; int32_t num = 0; int touch[MAX_GENTITIES]; gentity_t* hit = NULL; vec3_t mins; vec3_t maxs; memset(touch, 0, sizeof(touch)); VectorAdd(spot->s.origin, playerMins, mins); VectorAdd(spot->s.origin, playerMaxs, maxs); num = trap_EntitiesInBox(mins, maxs, touch, MAX_GENTITIES); for (; iclient && hit->client->ps.stats[STAT_HEALTH] > 0) { return qtrue; } if (hit && hit->s.eType == ET_USEABLE && hit->s.modelindex == HI_SHIELD) { //hit a portable force field return qtrue; } } return qfalse; } /* ================ SelectNearestDeathmatchSpawnPoint Find the spot that we DON'T want to use ================ */ /** * Find the spot that we DON'T want to use */ static gentity_t* SelectNearestDeathmatchSpawnPoint(vec3_t from) { gentity_t* spot = NULL; vec3_t delta; double dist = 0.0; double nearestDist = 999999.0; gentity_t* nearestSpot = NULL; while ((spot = G_Find(spot, FOFS(classname), "info_player_deathmatch")) != NULL) { VectorSubtract(spot->s.origin, from, delta); dist = VectorLength(delta); if (dist < nearestDist) { nearestDist = dist; nearestSpot = spot; } } return nearestSpot; } /* ================ SelectRandomDeathmatchSpawnPoint go to a random point that doesn't telefrag ================ */ /** * go to a random point that doesn't telefrag */ static gentity_t* SelectRandomDeathmatchSpawnPoint(void) { gentity_t* spot = NULL; int32_t count = 0; int32_t selection = 0; gentity_t* spots[MAX_SPAWN_POINTS]; memset(spots, 0, sizeof(spots)); while ((spot = G_Find(spot, FOFS(classname), "info_player_deathmatch")) != NULL) { if (G_Client_SpotWouldTelefrag(spot)) { continue; } spots[count] = spot; count++; } if (count == 0) { // no spots that won't telefrag return G_Find(NULL, FOFS(classname), "info_player_deathmatch"); } selection = rand() % count; return spots[selection]; } gentity_t* G_Client_SelectSpawnPoint(vec3_t avoidPoint, vec3_t origin, vec3_t angles) { gentity_t* spot = NULL; gentity_t* nearestSpot = SelectNearestDeathmatchSpawnPoint(avoidPoint); spot = SelectRandomDeathmatchSpawnPoint(); if (spot == nearestSpot) { // roll again if it would be real close to point of death spot = SelectRandomDeathmatchSpawnPoint(); if (spot == nearestSpot) { // last try spot = SelectRandomDeathmatchSpawnPoint(); } } // find a single player start spot if (spot == NULL) { G_Error("Couldn't find a spawn point"); return spot; } VectorCopy(spot->s.origin, origin); origin[2] += 9; VectorCopy(spot->s.angles, angles); return spot; } /* =========== SelectInitialSpawnPoint Try to find a spawn point marked 'initial', otherwise use normal spawn selection. ============ */ /** * Try to find a spawn point marked 'initial', otherwise * use normal spawn selection. */ static gentity_t* SelectInitialSpawnPoint(vec3_t origin, vec3_t angles) { gentity_t* spot = NULL; spot = NULL; while ((spot = G_Find(spot, FOFS(classname), "info_player_deathmatch")) != NULL) { if (spot->spawnflags & 1) { break; } } if (spot == NULL || G_Client_SpotWouldTelefrag(spot)) { return G_Client_SelectSpawnPoint(vec3_origin, origin, angles); } VectorCopy(spot->s.origin, origin); origin[2] += 9; VectorCopy(spot->s.angles, angles); return spot; } /* =========== SelectSpectatorSpawnPoint ============ */ static gentity_t* SelectSpectatorSpawnPoint(vec3_t origin, vec3_t angles) { FindIntermissionPoint(); VectorCopy(level.intermission_origin, origin); VectorCopy(level.intermission_angle, angles); return NULL; } /* ======================================================================= BODYQUE ======================================================================= */ static int32_t bodyFadeSound = 0; /* =============== G_Client_InitBodyQue =============== */ void G_Client_InitBodyQue(void) { int32_t i = 0; gentity_t* ent = NULL; level.bodyQueIndex = 0; for (; i < BODY_QUEUE_SIZE; i++) { ent = G_Spawn(); if (ent == NULL) { // TODO print error? return; } ent->classname = "bodyque"; ent->neverFree = qtrue; level.bodyQue[i] = ent; } if (bodyFadeSound == 0) { // Initialize this sound. bodyFadeSound = G_SoundIndex("sound/enemies/borg/walkthroughfield.wav"); } } /* ============= BodyRezOut After sitting around for five seconds, fade out. ============= */ /** * After sitting around for five seconds, fade out. */ static void BodyRezOut(gentity_t *ent) { if (level.time - ent->timestamp >= 7500) { // the body ques are never actually freed, they are just unlinked trap_UnlinkEntity(ent); ent->physicsObject = qfalse; return; } ent->nextthink = level.time + 2500; ent->s.time = level.time + 2500; G_AddEvent(ent, EV_GENERAL_SOUND, bodyFadeSound); } /* ============= CopyToBodyQue A player is respawning, so make an entity that looks just like the existing corpse to leave behind. ============= */ /** * A player is respawning, so make an entity that looks * just like the existing corpse to leave behind. */ static void CopyToBodyQue(gentity_t *ent) { gentity_t* body = NULL; int32_t contents = 0; entityState_t* eState = NULL; trap_UnlinkEntity(ent); // if client is in a nodrop area, don't leave the body contents = trap_PointContents(ent->s.origin, -1); if ((contents & CONTENTS_NODROP) != 0) { ent->s.eFlags &= ~EF_NODRAW; // Just in case we died from a bottomless pit, reset EF_NODRAW return; } // grab a body que and cycle to the next one body = level.bodyQue[level.bodyQueIndex]; level.bodyQueIndex = (level.bodyQueIndex + 1) % BODY_QUEUE_SIZE; trap_UnlinkEntity(body); eState = &ent->s; eState->eFlags = EF_DEAD; // clear EF_TALK, etc eState->powerups = 0; // clear powerups eState->loopSound = 0; // clear lava burning eState->number = body - g_entities; body->timestamp = level.time; body->physicsObject = qtrue; body->physicsBounce = 0; // don't bounce if (eState->groundEntityNum == ENTITYNUM_NONE) { eState->pos.trType = TR_GRAVITY; eState->pos.trTime = level.time; VectorCopy(ent->client->ps.velocity, eState->pos.trDelta); } else { eState->pos.trType = TR_STATIONARY; } eState->event = 0; // change the animation to the last-frame only, so the sequence // doesn't repeat anew for the body switch (eState->legsAnim & ~ANIM_TOGGLEBIT) { case BOTH_DEATH1: case BOTH_DEAD1: eState->torsoAnim = eState->legsAnim = BOTH_DEAD1; break; case BOTH_DEATH2: case BOTH_DEAD2: eState->torsoAnim = eState->legsAnim = BOTH_DEAD2; break; default: eState->torsoAnim = eState->legsAnim = BOTH_DEAD1; //DEAD3 break; } body->r.svFlags = ent->r.svFlags; VectorCopy(ent->r.mins, body->r.mins); VectorCopy(ent->r.maxs, body->r.maxs); VectorCopy(ent->r.absmin, body->r.absmin); VectorCopy(ent->r.absmax, body->r.absmax); body->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; body->r.contents = CONTENTS_CORPSE; body->r.ownerNum = ent->r.ownerNum; body->nextthink = level.time + 5000; body->think = BodyRezOut; body->die = body_die; // if there shouldn't be a body, don't show one. if (ent->client != NULL && ((level.time - ent->client->ps.powerups[PW_DISINTEGRATE]) < 10000 || (level.time - ent->client->ps.powerups[PW_EXPLODE]) < 10000)) { eState->eFlags |= EF_NODRAW; } // don't take more damage if already gibbed //RPG-X: RedTechie - Check for medicrevive if (rpg_medicsrevive.integer == 0){ if (ent->health <= GIB_HEALTH) { body->takedamage = qfalse; } else { body->takedamage = qtrue; } } else { body->takedamage = qfalse; } VectorCopy(eState->pos.trBase, body->r.currentOrigin); trap_LinkEntity(body); } //====================================================================== /* ================== G_Client_SetViewAngle ================== */ void G_Client_SetViewAngle(gentity_t *ent, vec3_t angle) { int i; // set the delta angle for (i = 0; i < 3; i++) { int cmdAngle; cmdAngle = ANGLE2SHORT(angle[i]); ent->client->ps.delta_angles[i] = cmdAngle - ent->client->pers.cmd.angles[i]; } VectorCopy(angle, ent->s.angles); VectorCopy(ent->s.angles, ent->client->ps.viewangles); } /* ================ G_Client_Respawn ================ */ void G_Client_Respawn(gentity_t *ent) { qboolean borg = qfalse; gentity_t* tent = NULL; playerState_t* ps = NULL; CopyToBodyQue(ent); G_Client_Spawn(ent, 0, qfalse);//RPG-X: RedTechie - Modifyed ps = &ent->client->ps; // add a teleportation effect if (borg) { tent = G_TempEntity(ps->origin, EV_BORG_TELEPORT); } else { tent = G_TempEntity(ps->origin, EV_PLAYER_TRANSPORT_IN); ps->powerups[PW_QUAD] = level.time + 4000; } tent->s.clientNum = ent->s.clientNum; } /** * Get number of clients in team. * * \param ignoreClientNum Client to ignore. * \param team Team. * \reutrn Number of clients in team. */ static team_t G_Client_TeamCount(int ignoreClientNum, int team) { int i; int count = 0; for (i = 0; i < level.maxclients; i++) { if (i == ignoreClientNum) { continue; } if (level.clients[i].pers.connected == CON_DISCONNECTED) { continue; } if (level.clients[i].sess.sessionTeam == team) { count++; } } return (team_t)count; } team_t G_Client_PickTeam(int ignoreClientNum) { int counts[TEAM_NUM_TEAMS]; counts[TEAM_BLUE] = G_Client_TeamCount(ignoreClientNum, TEAM_BLUE); counts[TEAM_RED] = G_Client_TeamCount(ignoreClientNum, TEAM_RED); if (counts[TEAM_BLUE] > counts[TEAM_RED]) { return TEAM_RED; } if (counts[TEAM_RED] > counts[TEAM_BLUE]) { return TEAM_BLUE; } // equal team count, so join the team with the lowest score if (level.teamScores[TEAM_BLUE] > level.teamScores[TEAM_RED]) { return TEAM_RED; } if (level.teamScores[TEAM_BLUE] < level.teamScores[TEAM_RED]) { return TEAM_BLUE; } return (team_t)irandom(TEAM_RED, TEAM_BLUE); } /* =========== ForceClientSkin Forces a client's skin (for teamplay) HEAVILY modified for the RPG-X Player Model system :P =========== */ /** * Forces a client's skin (for teamplay) * HEAVILY modified for the RPG-X * Player Model system */ static void ForceClientSkin(char* model, const char* skin) { char* p = NULL; char* q = NULL; //we expect model to equal 'char/model/skin' p = strchr(model, '/'); //if no slashes at all if (p == NULL || p[0] == 0 || p[1] == 0) { //input everything strncat(model, "/", MAX_QPATH); strncat(model, "main", MAX_QPATH); strncat(model, "/", MAX_QPATH); strncat(model, skin, MAX_QPATH); } else { //ie we got a slash (which should be the first of two p++; q = strchr(p, '/'); //okay, we should get another one if one was already found if (q == NULL || q[0] == 0 || q[1] == 0) { //no slashes were found?? >.< //okay, let's assume they specified the .model file, no skin //so just add the skin to the end :P strncat(model, "/", MAX_QPATH); strncat(model, skin, MAX_QPATH); } else { q++; *q = '\0'; strncat(model, skin, MAX_QPATH); } } } /* =========== ClientCheckName ============ */ static void ClientCleanName(const char* in, char* out, int outSize) { int32_t len = 0; int32_t colorlessLen = 0; char ch = 0; char* p = NULL; int32_t spaces = 0; //save room for trailing null byte outSize--; p = out; *p = 0; spaces = 0; while (1) { ch = *in++; if (ch == 0) { break; } // don't allow leading spaces if (!*p && ch == ' ') { continue; } // check colors if (ch == Q_COLOR_ESCAPE) { // solo trailing carat is not a color prefix if (!*in) { break; } // don't allow black in a name, period if (ColorIndex(*in) == 0) { in++; continue; } // make sure room in dest for both chars if (len > outSize - 2) { break; } *out++ = ch; *out++ = *in++; len += 2; continue; } // don't allow too many consecutive spaces if (ch == ' ') { spaces++; if (spaces > 3) { continue; } } else { spaces = 0; } if (len > outSize - 1) { break; } *out++ = ch; colorlessLen++; len++; } *out = 0; // don't allow empty names if (*p == 0 || colorlessLen == 0) { Q_strncpyz(p, "RedShirt", outSize); } } /* =========== legalSkin Compare a list of races with an incoming race name. Used to decide if in a CTF game where a race is specified for a given team if a skin is actually already legal. =========== */ /** * Compare a list of races with an incoming race name. * Used to decide if in a CTF game where a race is specified for a given team if a skin is actually already legal. */ static qboolean legalSkin(const char*race_list, const char* race) { char current_race_name[125]; const char *s = race_list; const char *max_place = race_list + strlen(race_list); const char *marker = NULL; memset(current_race_name, 0, sizeof(current_race_name)); // look through the list till it's empty while (s < max_place) { marker = s; // figure out from where we are where the next ',' or 0 is while (*s != ',' && *s != 0) { s++; } // copy just that name strncpy(current_race_name, marker, (s - marker) + 1); // avoid the comma or increment us past the end of the string so we fail the main while loop s++; // compare and see if this race is the same as the one we want if (Q_stricmp(current_race_name, race) == 0) { return qtrue; } } return qfalse; } /* =========== randomSkin given a race name, go find all the skins that use it, and randomly select one =========== */ /** * given a race name, go find all the skins that use it, and randomly select one */ static void randomSkin(const char* race, char* model, int32_t current_team, int32_t clientNum) { char** skinsForRace = NULL; int32_t howManySkins = 0; int32_t i = 0; int32_t x = 0; int32_t temp = 0; int32_t skin_count_check = 0; char** skinNamesAlreadyUsed = NULL; int32_t current_skin_count = 0; gentity_t* ent = NULL; char* userinfo = NULL; char temp_model[MAX_QPATH]; memset(temp_model, 0, sizeof(temp_model)); skinsForRace = malloc(MAX_SKINS_FOR_RACE * 128 * sizeof(char)); if (skinsForRace == NULL) { G_Error("Was unable to allocate %i bytes.\n", MAX_SKINS_FOR_RACE * 128 * sizeof(char)); return; } skinNamesAlreadyUsed = malloc(16 * 128 * sizeof(char)); if (skinNamesAlreadyUsed == NULL) { G_Error("Was unable to allocate %i bytes.\n", 16 * 128 * sizeof(char)); return; } memset(skinsForRace, 0, MAX_SKINS_FOR_RACE * 128 * sizeof(char)); memset(skinNamesAlreadyUsed, 0, 16 * 128 * sizeof(char)); // first up, check to see if we want to select a skin from someone that's already playing on this guys team skin_count_check = g_random_skin_limit.integer; if (skin_count_check != 0) { // sanity check the skins to compare against count if (skin_count_check > 16) { skin_count_check = 16; } // now construct an array of the names already used for (; i < g_maxclients.integer; i++) { // did we find enough skins to grab a random one from yet? if (current_skin_count == skin_count_check) { break; } ent = g_entities + i; if (ent == NULL || !ent->inuse || i == clientNum) { continue; } // no, so look at the next one, and see if it's in the list we are constructing // same team? if (ent->client != NULL && ent->client->sess.sessionTeam == current_team) { userinfo = malloc(MAX_INFO_STRING * sizeof(char)); if (userinfo == NULL) { G_Error("Was unable to allocate %i bytes.\n", MAX_INFO_STRING * sizeof(char)); return; } // so what's this clients model then? trap_GetUserinfo(i, userinfo, MAX_INFO_STRING * sizeof(char)); Q_strncpyz(temp_model, Info_ValueForKey(userinfo, "model"), sizeof(temp_model)); free(userinfo); // check the name for (x = 0; x < current_skin_count; x++) { // are we the same? if (Q_stricmp(skinNamesAlreadyUsed[x], temp_model) == 0) { // yeah - ok we already got this one break; } } // ok, did we match anything? if (x == current_skin_count) { // no - better add this name in Q_strncpyz(skinNamesAlreadyUsed[current_skin_count], temp_model, sizeof(skinNamesAlreadyUsed[current_skin_count])); current_skin_count++; } } } // ok, array constructed. Did we get enough? if (current_skin_count >= skin_count_check) { // yeah, we did - so select a skin from one of these then temp = rand() % current_skin_count; Q_strncpyz(model, skinNamesAlreadyUsed[temp], MAX_QPATH); ForceClientSkin(model, ""); free(skinNamesAlreadyUsed); free(skinsForRace); return; } } // search through each and every skin we can find for (i = 0; i < group_count && howManySkins < MAX_SKINS_FOR_RACE; i++){ // if this models race list contains the race we want, then add it to the list if (legalSkin(group_list[i].text, race)) { Q_strncpyz(skinsForRace[howManySkins++], group_list[i].name, 128); } } // set model to a random one if (howManySkins > 0) { temp = rand() % howManySkins; Q_strncpyz(model, skinsForRace[temp], MAX_QPATH); } else { model[0] = 0; } free(skinsForRace); free(skinNamesAlreadyUsed); } /* =========== getNewSkin Go away and actually get a random new skin based on a group name ============ */ /** * Go away and actually get a random new skin based on a group name */ static qboolean getNewSkin(const char* group, char* model, const char* color, const gclient_t* client, int32_t clientNum) { char* temp_string = NULL; // go away and get what ever races this skin is attached to. // remove blue or red name ForceClientSkin(model, ""); temp_string = G_searchGroupList(model); // are any of the races legal for this team race? if (legalSkin(temp_string, group)) { ForceClientSkin(model, color); return qfalse; } //if we got this far, then we need to reset the skin to something appropriate randomSkin(group, model, client->sess.sessionTeam, clientNum); return qtrue; } /* =========== G_Client_UserinfoChanged ============ */ /** * Called from G_Client_Connect when the player first connects and * directly by the server system when the player updates a userinfo variable. * * The game can override any of the settings and call trap_SetUserinfo * if desired. */ void G_Client_UserinfoChanged(int32_t clientNum) { gentity_t* ent = NULL; int32_t i = 0; int32_t modelOffset = 0; char* s = NULL; char model[MAX_QPATH]; char oldname[MAX_STRING_CHARS]; char userinfo[MAX_INFO_STRING]; char age[MAX_NAME_LENGTH]; char race[MAX_NAME_LENGTH]; char sHeight[10]; char sWeight[10]; double weight; double height; gclient_t* client = NULL; qboolean reset; qboolean changeName = qtrue; //TiM : For the name filter clientPersistant_t* pers = NULL; clientSession_t* sess = NULL; ent = g_entities + clientNum; if (ent == NULL) { return; } memset(model, 0, sizeof(model)); memset(oldname, 0, sizeof(oldname)); memset(userinfo, 0, sizeof(userinfo)); memset(age, 0, sizeof(age)); memset(race, 0, sizeof(race)); memset(sHeight, 0, sizeof(sHeight)); memset(sWeight, 0, sizeof(sWeight)); client = ent->client; pers = &client->pers; sess = &client->sess; //TiM - Exit if this user has had their info clamped if ((ent->flags & FL_CLAMPED) != 0) { return; } trap_GetUserinfo(clientNum, userinfo, sizeof(userinfo)); // check for malformed or illegal info strings if (!Info_Validate(userinfo)) { strcpy(userinfo, "\\name\\badinfo"); } // check for local client s = Info_ValueForKey(userinfo, "ip"); if (strcmp(s, "localhost") == 0) { pers->localClient = qtrue; } // check the item prediction s = Info_ValueForKey(userinfo, "cg_predictItems"); if (atoi(s) == 0) { pers->predictItemPickup = qfalse; } else { pers->predictItemPickup = qtrue; } // set name //TiM: Filter for if a player is already on this server with that name. s = Info_ValueForKey(userinfo, "name"); if ((rpg_uniqueNames.integer != 0) && ((ent->r.svFlags & SVF_BOT) == 0)) { char newName[36]; char activeName[36]; memset(newName, 0, sizeof(newName)); memset(activeName, 0, sizeof(activeName)); ClientCleanName(s, newName, sizeof(newName)); Q_CleanStr(newName); //loop thru all the clients, and see if we have one that has the same name as our proposed one for (i = 0; i < level.numConnectedClients; i++) { Q_strncpyz(activeName, g_entities[i].client->pers.netname, sizeof(activeName)); Q_CleanStr(activeName); if ((g_entities[i].client->ps.clientNum != client->ps.clientNum) && (Q_stricmp(newName, activeName) == 0)) { trap_SendServerCommand(ent - g_entities, " print \"Unable to change name. A player already has that name on this server.\n\" "); changeName = qfalse; break; } } } if (changeName) { Q_strncpyz(oldname, pers->netname, sizeof(oldname)); ClientCleanName(s, pers->netname, sizeof(pers->netname)); if (sess->sessionTeam == TEAM_SPECTATOR) { if (sess->spectatorState == SPECTATOR_SCOREBOARD) { Q_strncpyz(pers->netname, "scoreboard", sizeof(pers->netname)); } } if (pers->connected == CON_CONNECTED) { if (strcmp(oldname, pers->netname) != 0) { if ((!levelExiting) && (level.intermissiontime == 0)) { //no need to do this during level changes trap_SendServerCommand(-1, va("print \"%s" S_COLOR_WHITE " renamed to %s\n\"", oldname, pers->netname)); } } } } pers->pms_height = atof(Info_ValueForKey(userinfo, "height")); if (pers->pms_height == 0) { pers->pms_height = 1.0f; } pers->maxHealth = atoi(Info_ValueForKey(userinfo, "handicap")); if (pers->maxHealth < 1 || pers->maxHealth > 100) { pers->maxHealth = 100; } //if you have a class, ignores handicap and 100 limit, sorry client->ps.stats[STAT_MAX_HEALTH] = pers->maxHealth; Q_strncpyz(model, Info_ValueForKey(userinfo, "model"), sizeof(model)); // team switch (sess->sessionTeam) { case TEAM_RED: { // decide if we are going to have to reset a skin cos it's not applicable to a race selected if ((g_gametype.integer < GT_TEAM) || (Q_stricmp("", g_team_group_red.string) == 0)) { ForceClientSkin(model, "red"); break; } else { // at this point, we are playing CTF and there IS a race specified for this game reset = getNewSkin(g_team_group_red.string, model, "red", client, clientNum); // did we get a model name back? if (model[0] == 0) { // no - this almost certainly means we had a bogus race is the g_team_group_team cvar // so reset it to starfleet and try it again Com_Printf("WARNING! - Red Group %s is unknown - resetting Red Group to Allow Any Group\n", g_team_group_red.string); trap_Cvar_Set("g_team_group_red", ""); trap_Cvar_Register(&g_team_group_red, "g_team_group_red", "", CVAR_LATCH); // Since we are allow any group now, just get his normal model and carry on Q_strncpyz(model, Info_ValueForKey(userinfo, "model"), sizeof(model)); ForceClientSkin(model, "red"); reset = qfalse; } if (reset) { if (!levelExiting) { //no need to do this during level changes trap_SendServerCommand(-1, va("print \"In-appropriate skin selected for %s on team %s\nSkin selection overridden from skin %s to skin %s\n\"", pers->netname, g_team_group_red.string, Info_ValueForKey(userinfo, "model"), model)); } ForceClientSkin(model, "red"); // change the value in out local copy, then update it on the server Info_SetValueForKey(userinfo, "model", model); trap_SetUserinfo(clientNum, userinfo); } break; } } case TEAM_BLUE: { // decide if we are going to have to reset a skin cos it's not applicable to a race selected if ((g_gametype.integer < GT_TEAM) || (Q_stricmp("", g_team_group_blue.string) == 0)) { ForceClientSkin(model, "blue"); break; } else { // at this point, we are playing CTF and there IS a race specified for this game // go away and get what ever races this skin is attached to. reset = getNewSkin(g_team_group_blue.string, model, "blue", client, clientNum); // did we get a model name back? if (model[0] == 0) { // no - this almost certainly means we had a bogus race is the g_team_group_team cvar // so reset it to klingon and try it again Com_Printf("WARNING! - Blue Group %s is unknown - resetting Blue Group to Allow Any Group\n", g_team_group_blue.string); trap_Cvar_Set("g_team_group_blue", ""); trap_Cvar_Register(&g_team_group_blue, "g_team_group_blue", "", CVAR_LATCH); // Since we are allow any group now, just get his normal model and carry on Q_strncpyz(model, Info_ValueForKey(userinfo, "model"), sizeof(model)); ForceClientSkin(model, "blue"); reset = qfalse; } if (reset) { if (!levelExiting) { //no need to do this during level changes trap_SendServerCommand(-1, va("print \"In-appropriate skin selected for %s on team %s\nSkin selection overridden from skin %s to skin %s\n\"", pers->netname, g_team_group_blue.string, Info_ValueForKey(userinfo, "model"), model)); } ForceClientSkin(model, "blue"); // change the value in out local copy, then update it on the server Info_SetValueForKey(userinfo, "model", model); trap_SetUserinfo(clientNum, userinfo); } break; } } default: break; } if ((g_gametype.integer >= GT_TEAM) && (sess->sessionTeam == TEAM_SPECTATOR)) { // don't ever use a default skin in teamplay, it would just waste memory ForceClientSkin(model, "red"); } if ((rpg_rpg.integer != 0) && (rpg_forceclasscolor.integer != 0) && (g_gametype.integer < GT_TEAM)) { ForceClientSkin(model, g_classData[sess->sessionClass].modelSkin); } //TiM : For when an admin chooses not to see admin messages //Marcin : and check for privacy mode - 24/12/2008 s = Info_ValueForKey(userinfo, "noAdminChat"); if (atoi(s) > 0) { client->noAdminChat = qtrue; } else { client->noAdminChat = qfalse; } // teamInfo s = Info_ValueForKey(userinfo, "teamoverlay"); if (!*s || atoi(s) != 0) { pers->teamInfo = qtrue; } else { pers->teamInfo = qfalse; } //PMS system - lock down the values s = Info_ValueForKey(userinfo, "height"); height = atof(s); if (height > (float)rpg_maxHeight.value) { Q_strncpyz(sHeight, rpg_maxHeight.string, sizeof(sHeight)); } else if (height < (float)rpg_minHeight.value) { Q_strncpyz(sHeight, rpg_minHeight.string, sizeof(sHeight)); } else { Q_strncpyz(sHeight, s, sizeof(sHeight)); } //TiM - needed for height offset pers->pms_height = atof(sHeight); //PMS system - lock down the values s = Info_ValueForKey(userinfo, "weight"); weight = atof(s); if (weight > (float)rpg_maxWeight.value) { Q_strncpyz(sWeight, rpg_maxWeight.string, sizeof(sWeight)); } else if (weight < (float)rpg_minWeight.value) { Q_strncpyz(sWeight, rpg_minWeight.string, sizeof(sWeight)); } else { Q_strncpyz(sWeight, s, sizeof(sWeight)); } s = Info_ValueForKey(userinfo, "age"); Q_strncpyz(age, s, sizeof(age)); s = Info_ValueForKey(userinfo, "race"); Q_strncpyz(race, s, sizeof(race)); s = Info_ValueForKey(userinfo, "modelOffset"); modelOffset = atoi(s); // send over a subset of the userinfo keys so other clients can // print scoreboards, display models, and play custom sounds //FIXME: In future, we'll lock down these PMS values so we can't have overloaded transmission data if ((ent->r.svFlags & SVF_BOT) != 0) { s = va("n\\%s\\t\\%i\\p\\%i\\model\\%s\\hc\\%i\\w\\%i\\l\\%i\\skill\\%s\\age\\25\\height\\%s\\weight\\%s\\race\\Bot\\of\\%i\\admin\\0", pers->netname, sess->sessionTeam, sess->sessionClass, model, pers->maxHealth, sess->wins, sess->losses, Info_ValueForKey(userinfo, "skill"), sHeight, sWeight, modelOffset); } else { s = va("n\\%s\\t\\%i\\p\\%i\\model\\%s\\hc\\%i\\w\\%i\\l\\%i\\age\\%s\\height\\%s\\weight\\%s\\race\\%s\\of\\%i\\admin\\%i", pers->netname, sess->sessionTeam, sess->sessionClass, model, pers->maxHealth, sess->wins, sess->losses, age, sHeight, sWeight, race, modelOffset, ((int)G_Client_IsAdmin(g_entities + clientNum))); } trap_SetConfigstring(CS_PLAYERS + clientNum, s); G_LogPrintf("ClientUserinfoChanged: %i %s\n", clientNum, s); // no ip logging here as string might get to long } /* =========== G_Client_Connect ============ */ /** * Called when a player begins connecting to the server. * Called again for every map change or tournement restart. * * The session information will be valid after exit. * * Return NULL if the client should be allowed, otherwise return * a string with the reason for denial. * * Otherwise, the client will be sent the current gamestate * and will eventually get to G_Client_Begin. * * firstTime will be qtrue the very first time a client connects * to the server machine, but qfalse on map changes and tournement * restarts. */ char* G_Client_Connect(int32_t clientNum, qboolean firstTime, qboolean isBot) { char* value = NULL; char* newClass = NULL; char* newRank = NULL; char ip[64]; //TiM : Saved the IP data for player recon feature char userinfo[MAX_INFO_STRING]; gclient_t* client = NULL; gentity_t* ent = NULL; vmCvar_t mapname; vmCvar_t sv_hostname; ent = &g_entities[clientNum]; if (ent == NULL) { return "Critical Error: Client entity was NULL"; } memset(ip, 0, sizeof(ip)); memset(userinfo, 0, sizeof(userinfo)); trap_Cvar_Register(&mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM); trap_Cvar_Register(&sv_hostname, "sv_hostname", "", CVAR_SERVERINFO | CVAR_ROM); trap_GetUserinfo(clientNum, userinfo, sizeof(userinfo)); // check to see if they are on the banned IP list value = Info_ValueForKey(userinfo, "ip"); Q_strncpyz(ip, value, sizeof(ip)); if (G_FilterPacket(value) || CheckID(Info_ValueForKey(userinfo, "sv_securityCode"))) { return "Banned from this server"; } // check for a password if (!isBot) { value = Info_ValueForKey(userinfo, "password"); if (g_password.string[0] && Q_stricmp(g_password.string, "none") != 0 && strcmp(g_password.string, value) != 0) { return "Invalid password"; } } //TiM: If need be, chack to see if any other players have the current name... //evil impersonators and the likes if (rpg_uniqueNames.integer != 0 && !isBot) { char name[36]; char oldName[36]; int32_t i = 0; memset(name, 0, sizeof(name)); memset(oldName, 0, sizeof(oldName)); //get the name value = Info_ValueForKey(userinfo, "name"); //Clean the data ClientCleanName(value, name, sizeof(name)); //Now, do a compare with all clients in the server for (i = 0; i < MAX_CLIENTS; i++) { if ((g_entities[i].client == NULL) || (g_entities[i].client->pers.connected != CON_CONNECTED)) { continue; } if (g_entities[i].client->pers.netname[0]) { //local copy the string and work on that, else we risk wrecking other people's names Q_strncpyz(oldName, g_entities[i].client->pers.netname, sizeof(oldName)); if ((Q_stricmp(Q_CleanStr(name), Q_CleanStr(oldName)) == 0) && !isBot) { return "There is already a user with that name."; } } } } // they can connect ent->client = level.clients + clientNum; client = ent->client; memset(client, 0, sizeof(*client)); client->pers.connected = CON_CONNECTING; // read or initialize the session data if (firstTime || level.newSession) { G_InitSessionData(client, userinfo); } G_ReadSessionData(client); if (isBot) { ent->r.svFlags |= SVF_BOT; ent->inuse = qtrue; if (!G_BotConnect(clientNum, !firstTime)) { return "BotConnectfailed"; } } // get and distribute relevent paramters G_LogPrintf("ClientConnect: %i (%s)\n", clientNum, g_entities[clientNum].client->pers.ip); if (rpg_rpg.integer != 0 /*&& firstTime*/) { //TiM: Code for automatic class + rank switching //======================================================== if (isBot) { client->sess.sessionClass = 0; client->ps.persistant[PERS_SCORE] = 1; } else { int32_t tmpScore = 0; int32_t i = 0; qboolean changeRank = qfalse; newClass = Info_ValueForKey(userinfo, "ui_playerClass"); newRank = Info_ValueForKey(userinfo, "ui_playerRank"); //Com_Printf( S_COLOR_RED "Data: %s %s\n", newClass, newRank ); if (newClass[0] != 0) { client->sess.sessionClass = ValueNameForClass(newClass); //TiM: BOOYEAH! :) //if class doesn't exist, default to 0 if (client->sess.sessionClass < 0) { client->sess.sessionClass = 0; } } else { client->sess.sessionClass = 0; } for (i = 0; i < MAX_RANKS; i++) { if (rpg_startingRank.string[0] == 0 && newRank[0] != 0) { if (Q_stricmp(newRank, g_rankNames[i].consoleName) == 0) { tmpScore = i;//1 << i; if (rpg_changeRanks.integer != 0) { changeRank = qtrue; } break; } } else { if ((rpg_startingRank.string[0] != 0) && (Q_stricmp(g_rankNames[i].consoleName, rpg_startingRank.string) == 0)) { tmpScore = i;// 1 << i; changeRank = qtrue; break; } } } if (changeRank) { ent->client->UpdateScore = qtrue; G_Client_SetScore(ent, tmpScore); } } //======================================================== } G_Client_UserinfoChanged(clientNum); //RPG-X: Save the ip for later - has to be down here, since it gets flushed in the above function Q_strncpyz(ent->client->pers.ip, ip, sizeof(ent->client->pers.ip)); // don't do the "xxx connected" messages if they were caried over from previous level if (firstTime) { if (!levelExiting) {//no need to do this during level changes qboolean nameFound = qfalse; //Check to see if this player already connected on this server if ((rpg_renamedPlayers.integer != 0) && ((ent->r.svFlags & SVF_BOT) == 0)) { int32_t i = 0; for (i = 0; i < MAX_RECON_NAMES; i++) { if (g_reconData[i].previousName[0] == 0) { continue; } if ((Q_stricmp(client->pers.ip, g_reconData[i].ipAddress) == 0) && (Q_stricmp(client->pers.netname, g_reconData[i].previousName) != 0)) { trap_SendServerCommand(-1, va("print \"%s" S_COLOR_WHITE " (With the previous name of %s" S_COLOR_WHITE ") connected\n\"", client->pers.netname, g_reconData[i].previousName)); nameFound = qtrue; break; } } } if (!nameFound) { trap_SendServerCommand(-1, va("print \"%s" S_COLOR_WHITE " connected\n\"", client->pers.netname)); } //RPG-X | Phenix | 07/04/2005 client->AdminFailed = 0; ent->n00bCount = 0; client->LoggedAsDeveloper = qfalse; } } if ((g_gametype.integer >= GT_TEAM) && (client->sess.sessionTeam != TEAM_SPECTATOR)) { BroadcastTeamChange(client, -1); } // count current clients and rank for scoreboard //G_Client_CalculateRanks( qfalse ); //RPG-X: J2J - Reset Variables DragDat[clientNum].AdminId = -1; DragDat[clientNum].distance = 0; g_entities[clientNum].client->noclip = qfalse; return NULL; } extern holoData_t holoData; //! Think function for temporal entity that transmits the holodeck date to the client static void holoTent_think(gentity_t* ent) { if (ent->count == 0) { trap_SendServerCommand(ent - g_entities, va("holo_data %i", holoData.numProgs)); ent->count = 1; ent->health = 0; ent->nextthink = level.time + 250; return; } if (ent->health == holoData.numProgs) { ent->count++; ent->health = 0; } switch (ent->count) { case 1: // name trap_SendServerCommand(ent - g_entities, va("holo_data \"n%i\\%s\\\"", ent->health, holoData.name[ent->health])); break; case 2: // desc1 trap_SendServerCommand(ent - g_entities, va("holo_data \"da%i\\%s\\\"", ent->health, holoData.desc1[ent->health])); break; case 3: // desc2 trap_SendServerCommand(ent - g_entities, va("holo_data \"db%i\\%s\\\"", ent->health, holoData.desc2[ent->health])); break; case 4: // image trap_SendServerCommand(ent - g_entities, va("holo_data \"i%i\\%s\\\"", ent->health, holoData.image[ent->health])); break; } ent->health++; if (ent->count > 4) { G_PrintfClient(ent, "Received data of %i holodeck programs.\n", holoData.numProgs); G_FreeEntity(ent); return; } ent->nextthink = level.time + 250; } //! Create a temporal entity that sends over the holodata to the client static void G_SendHoloData(int32_t clientNum) { gentity_t *holoTent; holoTent = G_Spawn(); holoTent->classname = "holoTent"; holoTent->target_ent = g_entities + clientNum; holoTent->think = holoTent_think; holoTent->nextthink = level.time + 2500; } //! Think function for temporal entity that transmits the server change data and map change data for transporter UI static void transTent_think(gentity_t* ent) { char temp[MAX_STRING_CHARS]; int32_t i; memset(temp, 0, sizeof(temp)); for (i = 0; i < level.srvChangeData.count; i++) { if (temp[0] == 0) { Com_sprintf(temp, sizeof(temp), "ui_trdata d%i\\%s\\", i, level.srvChangeData.name[i]); } else { Com_sprintf(temp, sizeof(temp), "%sd%i\\%s\\", temp, i, level.srvChangeData.name[i]); } } trap_SendServerCommand(ent->target_ent - g_entities, temp); G_FreeEntity(ent); } //! creates an entity that transmits the server change data to the client static void G_SendTransData(int32_t clientNum) { gentity_t *transTent; transTent = G_Spawn(); transTent->classname = "transTent"; transTent->target_ent = g_entities + clientNum; transTent->think = transTent_think; transTent->nextthink = level.time + 500; } /* =========== G_Client_Begin ============ */ /** * called when a client has finished connecting, and is ready * to be placed into the level. This will happen every level load, * and on transition between teams, but doesn't happen on respawns */ void G_Client_Begin(int32_t clientNum, qboolean careAboutWarmup, qboolean isBot, qboolean first) { gentity_t* ent = NULL; gclient_t* client = NULL; gentity_t* tent = NULL; gentity_t* selfdestruct = NULL; int32_t flags = 0; qboolean alreadyIn = qfalse; int32_t score = 0; ent = g_entities + clientNum; if (ent == NULL) { return; } if (ent->botDelayBegin) { G_QueueBotBegin(clientNum); ent->botDelayBegin = qfalse; return; } client = level.clients + clientNum; if (ent->r.linked) { trap_UnlinkEntity(ent); } G_InitGentity(ent); ent->touch = 0; ent->pain = 0; ent->client = client; if (client->pers.connected == CON_CONNECTED) { alreadyIn = qtrue; } client->pers.connected = CON_CONNECTED; client->pers.enterTime = level.time; client->pers.teamState.state = TEAM_BEGIN; // save eflags around this, because changing teams will // cause this to happen with a valid entity, and we // want to make sure the teleport bit is set right // so the viewpoint doesn't interpolate through the // world to the new position //TiM... I think this is why my damn RANK SYSTEM ENHANCEMENT HAS BEEN BUGGING OUT!!@!@!! //ARRRGRGRGRGRGRGRGRGRGRGRGHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH!!!!! D:< flags = client->ps.eFlags; score = client->ps.persistant[PERS_SCORE]; memset(&client->ps, 0, sizeof(client->ps)); client->ps.eFlags = flags; client->UpdateScore = qtrue; G_Client_SetScore(ent, score); // locate ent at a spawn point G_Client_Spawn(ent, 0, qfalse);//RPG-X: RedTechie - Modifyed if (client->sess.sessionTeam != TEAM_SPECTATOR) { // Don't use transporter FX for spectators or those watching the holodoors. // send event ent->client->ps.powerups[PW_QUAD] = level.time + 4000; tent = G_TempEntity(ent->client->ps.origin, EV_PLAYER_TRANSPORT_IN); tent->s.clientNum = ent->s.clientNum; } G_LogPrintf("ClientBegin: %i (%s)\n", clientNum, g_entities[clientNum].client->pers.ip); // count current clients and rank for scoreboard G_Client_CalculateRanks(qfalse); //TiM - This appears to be a flaw in Raven's design //When a client connects, or if they enter admin or medics class //ensure the relevant health data is sent to them, or else they'll //see anomalies when scanning players if ((client->sess.sessionTeam == TEAM_SPECTATOR) || (g_classData[client->sess.sessionClass].isMedical) || (g_classData[client->sess.sessionClass].isAdmin)) { int32_t i = 0; char entry[16]; char command[1024]; int32_t numPlayers = 0; gentity_t *player; int32_t len = 0; int32_t cmdLen = 0; memset(entry, 0, sizeof(entry)); memset(command, 0, sizeof(command)); for (; i < g_maxclients.integer; i++) { player = g_entities + i; if ((player == NULL) || (player == ent) || !player->inuse) { continue; } Com_sprintf(entry, sizeof(entry), " %i %i", i, player->health >= 0 ? player->health : 0); len = strlen(entry); if (cmdLen + len > sizeof(command)) { break; } strcpy(command + cmdLen, entry); cmdLen += len; numPlayers++; } if (numPlayers > 0) { trap_SendServerCommand(clientNum, va("hinfo %i%s", numPlayers, command)); } } //RPG-X: RedTechie - But we dont care about warmup! if (careAboutWarmup) { if (level.restarted || g_restarted.integer != 0) { trap_Cvar_Set("g_restarted", "0"); level.restarted = qfalse; } } //RPG-X | Phenix | 21/11/2004 //BOOKMARK FOR INIT if (!alreadyIn) { // RPG-X | Phenix | 06/04/2005 ent->client->n00bTime = -1; } ent->client->fraggerTime = -1; // kef -- should reset all of our awards-related stuff G_ClearClientLog(clientNum); //TiM - if our user's security key was default, transmit the received IP bak to //the client and get it to encode it into our new key //Scooter's filter list if (Q_stricmp(ent->client->pers.ip, "localhost") != 0 //localhost && Q_strncmp(ent->client->pers.ip, "10.", 3) != 0 //class A && Q_strncmp(ent->client->pers.ip, "172.16.", 7) != 0 //class B && Q_strncmp(ent->client->pers.ip, "192.168.", 8) != 0 //class C && Q_strncmp(ent->client->pers.ip, "127.", 4) != 0 //loopback && Q_strncmp(ent->client->pers.ip, "169.254.", 8) != 0 //link-local ) { char userInfo[MAX_TOKEN_CHARS]; uint64_t securityID = 0; memset(userInfo, 0, sizeof(userInfo)); trap_GetUserinfo(clientNum, userInfo, sizeof(userInfo)); if (userInfo[0] == 0) { return; } securityID = (uint64_t)atoul(Info_ValueForKey(userInfo, "sv_securityCode")); if ((securityID <= 0) || (securityID >= 0xffffffff)) { trap_SendServerCommand(clientNum, va("configID %s", ent->client->pers.ip)); } } // send srv change data to ui if (!isBot && first) { if (level.srvChangeData.count > 0) { G_SendTransData(clientNum); } } // send holo data to ui if (!isBot && first) { if (holoData.numProgs) { G_SendHoloData(clientNum); } } //RPG-X: Marcin: show the server motd - 15/12/2008 if (!isBot && first) { trap_SendServerCommand(ent->s.number, "motd"); } if (!isBot) { qboolean last = qfalse; int32_t len = 0; fileHandle_t file = 0; char* p = NULL; char* q = NULL; char buf[16000]; // TODO move to heap ? len = trap_FS_FOpenFile(rpg_motdFile.string, &file, FS_READ); if (file == 0 || len == 0) { trap_SendServerCommand(ent->s.number, va("motd_line \"^1%s not found or empty^7\"", rpg_motdFile.string)); return; } memset(buf, 0, sizeof(buf)); trap_FS_Read(buf, len, file); p = &buf[0]; q = p; buf[len] = '\0'; while (!last) { p = q; while (*q != '\n') { if (!*q) { last = qtrue; } if ((*q == ' ') && (EndWord(q) - p) > 78) { break; } q++; } *q = '\0'; trap_SendServerCommand(ent->s.number, va("motd_line \"%s\"", p)); q++; } } //we may currently be selfdestructing, so send stuff for that case selfdestruct = G_Find(NULL, FOFS(classname), "target_selfdestruct"); if ((selfdestruct != NULL) && ((selfdestruct->spawnflags & 1) != 0)) { trap_SendServerCommand(ent->s.number, va("selfdestructupdate %i", selfdestruct->damage - level.time)); } else { trap_SendServerCommand(ent->s.number, va("selfdestructupdate %i", -1)); } } /** * Get weapons for a class. * * \param client The client. * \param pclass Class to get weapons for. * \author PHENIX1 */ static void G_Client_WeaponsForClass(gclient_t* client, pclass_t pclass) { int32_t i = WP_1; int32_t Bits = 0; Bits = (1 << WP_1); Bits |= g_classData[pclass].weaponsFlags; for (; i < MAX_WEAPONS; i++) { //if we want no weapons and aren't an admin, skip this particular weapon if ((rpg_noweapons.integer != 0) && !g_classData[pclass].isAdmin) { if ((i >= WP_5) && (i <= WP_10)) { continue; } } if ((Bits & (1 << i)) != 0) { client->ps.stats[STAT_WEAPONS] |= (1 << i); client->ps.ammo[i] = Min_Weapon(i); } } } /** * Get holdable items for a class. * * \param client The client. * \param pclass Class for which to get holdables. */ static void G_Client_HoldablesForClass(gclient_t* client, pclass_t pclass) { if (g_classData[pclass].isMarine) { client->ps.stats[STAT_HOLDABLE_ITEM] = BG_FindItemForHoldable(HI_TRANSPORTER) - bg_itemlist; } else if (g_classData[pclass].isAdmin) { client->ps.stats[STAT_HOLDABLE_ITEM] = BG_FindItemForHoldable(HI_SHIELD) - bg_itemlist; } } void G_Client_StoreClientInitialStatus(gentity_t* ent) { char userinfo[MAX_INFO_STRING]; if (clientInitialStatus[ent->s.number].initialized) { //already set return; } if (ent->client->sess.sessionTeam == TEAM_SPECTATOR) { //don't store their data if they're just a spectator return; } memset(userinfo, 0, sizeof(userinfo)); trap_GetUserinfo(ent->s.number, userinfo, sizeof(userinfo)); Q_strncpyz(clientInitialStatus[ent->s.number].model, Info_ValueForKey(userinfo, "model"), sizeof(clientInitialStatus[ent->s.number].model)); clientInitialStatus[ent->s.number].pClass = ent->client->sess.sessionClass; clientInitialStatus[ent->s.number].team = ent->client->sess.sessionTeam; clientInitialStatus[ent->s.number].initialized = qtrue; ent->client->classChangeDebounceTime = 0; } /* =========== G_Client_Spawn Called every time a client is placed fresh in the world: after the first G_Client_Begin, and after each respawn Initializes all non-persistant parts of playerState ------------------------------------ Modifyed By: RedTechie And also by Marcin - 30/12/2008 ============ */ void G_Client_Spawn(gentity_t* ent, int32_t rpgx_spawn, qboolean fromDeath) { int32_t index = 0; int32_t i = 0; int32_t persistant[MAX_PERSISTANT]; int32_t flags = 0; int32_t savedPing = 0; int32_t cCDT = 0; int32_t clientNum = 0; vec3_t spawn_origin; vec3_t spawn_angles; gclient_t* client = NULL; clientPersistant_t saved; clientSession_t savedSess; gentity_t* spawnPoint = NULL; pclass_t pClass = 0; pclass_t oClass = 0; if (ent == NULL) { return; } memset(persistant, 0, sizeof(persistant)); index = ent - g_entities; client = ent->client; clientNum = ent->client->ps.clientNum; // find a spawn point // do it before setting health back up, so farthest // ranging doesn't count this client if (rpgx_spawn != 1){//RPG-X: RedTechie - Make sure the spawn is regular spawn or spawn at current position (rpgx_spawn = current possition) if (client->sess.sessionTeam == TEAM_SPECTATOR) { spawnPoint = SelectSpectatorSpawnPoint(spawn_origin, spawn_angles); } else { do { // the first spawn should be at a good looking spot if (!client->pers.initialSpawn && client->pers.localClient) { client->pers.initialSpawn = qtrue; spawnPoint = SelectInitialSpawnPoint(spawn_origin, spawn_angles); } else { // don't spawn near existing origin if possible spawnPoint = G_Client_SelectSpawnPoint(client->ps.origin, spawn_origin, spawn_angles); } // Tim needs to prevent bots from spawning at the initial point // on q3dm0... if (((spawnPoint->flags & FL_NO_BOTS) != 0) && ((ent->r.svFlags & SVF_BOT) != 0)) { continue; // try again } // just to be symetric, we have a nohumans option... if (((spawnPoint->flags & FL_NO_HUMANS) != 0) && ((ent->r.svFlags & SVF_BOT) == 0)) { continue; // try again } break; } while (1); } } //RPG-X: RedTechie - End rpgx_spawn check client->pers.teamState.state = TEAM_ACTIVE; // toggle the teleport bit so the client knows to not lerp if (rpgx_spawn != 1){ flags = ent->client->ps.eFlags & EF_TELEPORT_BIT; flags ^= EF_TELEPORT_BIT; } // 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]; } //okay, this is hacky, but we need to keep track of this, even if uninitialized first time you spawn, it will be stomped anyway //RPG-X: RedTechie - Damn thing screwed my function up if (rpgx_spawn != 1){ if (client->classChangeDebounceTime) { cCDT = client->classChangeDebounceTime; } memset(client, 0, sizeof(*client)); client->classChangeDebounceTime = cCDT; } // client->pers = saved; client->sess = savedSess; client->ps.ping = savedPing; for (i = 0; i < MAX_PERSISTANT; i++) { client->ps.persistant[i] = persistant[i]; } // increment the spawncount so the client will detect the respawn if (rpgx_spawn != 1) { client->ps.persistant[PERS_SPAWN_COUNT]++; client->airOutTime = level.time + 12000; } if (client->sess.sessionTeam != TEAM_SPECTATOR) { client->sess.sessionTeam = TEAM_FREE; } client->ps.persistant[PERS_TEAM] = client->sess.sessionTeam; // clear entity values client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; client->ps.eFlags = flags; client->streakCount = 0; ent->client->ps.pm_type = PM_NORMAL; 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 = G_Client_Die; ent->waterlevel = 0; ent->watertype = 0; ent->flags = 0; if (rpgx_spawn != 1) { VectorCopy(playerMins, ent->r.mins); VectorCopy(playerMaxs, ent->r.maxs); } client->ps.clientNum = index; // health will count down towards max_health ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] * 1.25; oClass = client->sess.sessionClass; if (oClass != client->sess.sessionClass) { //need to send the class change G_Client_UserinfoChanged(client->ps.clientNum); } client->ps.persistant[PERS_CLASS] = client->sess.sessionClass; pClass = client->sess.sessionClass; if (pClass != 0) { //no health boost on spawn for playerclasses ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; } if (!fromDeath || (rpg_dropOnDeath.integer == 0) || (rpg_allowWeaponDrop.integer == 0)) { G_Client_WeaponsForClass(client, pClass); } else { // Marcin: just a hand G_Client_WeaponsForClass(client, 0); } G_Client_HoldablesForClass(client, pClass); if (rpgx_spawn != 1) { G_SetOrigin(ent, spawn_origin); VectorCopy(spawn_origin, client->ps.origin); } // the respawned flag will be cleared after the attack and jump keys come up if (rpgx_spawn != 1) { client->ps.pm_flags |= PMF_RESPAWNED; } trap_GetUsercmd(client - level.clients, &ent->client->pers.cmd); if (rpgx_spawn != 1){ G_Client_SetViewAngle(ent, spawn_angles); } if (rpgx_spawn != 1) { if (ent->client->sess.sessionTeam == TEAM_SPECTATOR) { } else { G_MoveBox(ent); trap_LinkEntity(ent); // force the base weapon up client->ps.weapon = WP_1; //TiM: WP_5 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; if (rpgx_spawn != 1) { client->respawnTime = level.time; } client->inactivityTime = level.time + g_inactivity.integer * 1000; client->latched_buttons = 0; // set default animations if (rpgx_spawn != 1) { client->ps.stats[TORSOANIM] = BOTH_STAND1; client->ps.stats[LEGSANIM] = BOTH_STAND1; } if (level.intermissiontime) { MoveClientToIntermission(ent); } else { // fire the targets of the spawn point if (rpgx_spawn != 1) { G_UseTargets(spawnPoint, ent); } // select the highest weapon number available, after any // spawn given items have fired client->ps.weapon = 1; //TiM - Always default to the null hand client->ps.weapon = WP_1; } // 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 (ent->client->sess.sessionTeam != TEAM_SPECTATOR) { BG_PlayerStateToEntityState(&client->ps, &ent->s, qtrue); if (rpgx_spawn != 1) { VectorCopy(ent->client->ps.origin, ent->r.currentOrigin); } trap_LinkEntity(ent); } // run the presend to set anything else ClientEndFrame(ent); // clear entity state values BG_PlayerStateToEntityState(&client->ps, &ent->s, qtrue); //start-up messages //FIXME: externalize all this text! //FIXME: make the gametype titles be graphics! //FIXME: make it do this on a map_restart also if (ent->client->sess.sessionTeam == TEAM_SPECTATOR) { //spectators just get the title of the game switch (g_gametype.integer) { case GT_FFA: // free for all trap_SendServerCommand(ent - g_entities, va("cp \"%s\"", rpg_welcomemessage.string)); break; case GT_TOURNAMENT: // one on one tournament trap_SendServerCommand(ent - g_entities, va("cp \"%s\"", rpg_welcomemessage.string)); break; case GT_SINGLE_PLAYER: // single player tournament trap_SendServerCommand(ent - g_entities, va("cp \"%s\"", rpg_welcomemessage.string)); break; case GT_TEAM: // team deathmatch trap_SendServerCommand(ent - g_entities, va("cp \"%s\"", rpg_welcomemessage.string)); break; case GT_CTF: // capture the flag trap_SendServerCommand(ent - g_entities, va("cp \"%s\"", rpg_welcomemessage.string)); break; } } else { if (!clientInitialStatus[ent->s.number].initialized) { //first time coming in switch (g_gametype.integer) { case GT_FFA: // free for all trap_SendServerCommand(ent - g_entities, va("cp \"%s\"", rpg_welcomemessage.string)); break; case GT_TOURNAMENT: // one on one tournament trap_SendServerCommand(ent - g_entities, va("cp \"%s\"", rpg_welcomemessage.string)); break; case GT_SINGLE_PLAYER: // single player tournament trap_SendServerCommand(ent - g_entities, va("cp \"%s\"", rpg_welcomemessage.string)); break; case GT_TEAM: // team deathmatch trap_SendServerCommand(ent - g_entities, va("cp \"%s\"", rpg_welcomemessage.string)); break; case GT_CTF: // capture the flag trap_SendServerCommand(ent - g_entities, va("cp \"%s\"", rpg_welcomemessage.string)); break; } } } if (rpgx_spawn != 0) { if ((client->sess.sessionTeam == TEAM_SPECTATOR) || g_classData[client->sess.sessionClass].isMedical || g_classData[client->sess.sessionClass].isAdmin) { int32_t l = 0; int32_t numPlayers = 0; int32_t len = 0; int32_t cmdLen = 0; char entry[16]; char command[1024]; gentity_t* player = NULL; memset(entry, 0, sizeof(entry)); memset(command, 0, sizeof(entry)); for (; l < g_maxclients.integer; l++) { player = g_entities + l; if ((player == NULL) || (player == ent) || !player->inuse) { continue; } Com_sprintf(entry, sizeof(entry), " %i %i", l, player->health >= 0 ? player->health : 0); len = strlen(entry); if (cmdLen + len > sizeof(command)) { break; } strcpy(command + cmdLen, entry); cmdLen += len; numPlayers++; } if (numPlayers > 0) { trap_SendServerCommand(clientNum, va("hinfo %i%s", numPlayers, command)); } } } //store intial client values //FIXME: when purposely change teams, this gets confused? G_Client_StoreClientInitialStatus(ent); //RPG-X: Marcin: stuff was here previously - 22/12/2008 } /* =========== G_Client_Disconnect 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 G_Client_Disconnect(int32_t clientNum) { gentity_t* ent = NULL; gentity_t* tent = NULL; int32_t i = 0; ent = &g_entities[clientNum]; if ((ent == NULL) || (ent->client == NULL)) { return; } // stop any following clients for (i = 0; i < level.maxclients; i++) { if (level.clients[i].sess.sessionTeam == TEAM_SPECTATOR && level.clients[i].sess.spectatorState == SPECTATOR_FOLLOW && level.clients[i].sess.spectatorClient == clientNum) { StopFollowing(&g_entities[i]); } } //RPG-X: J2J - Stop any dragging. DragDat[clientNum].AdminId = -1; DragDat[clientNum].distance = 0; g_entities[clientNum].client->noclip = qfalse; //TiM: Log the player's IP and name. If they reconnect again, it'll announce their deceipt >:) if ((rpg_renamedPlayers.integer != 0) && ((ent->r.svFlags & SVF_BOT) == 0)) { int32_t l = 0; qboolean foundName = qfalse; //Do a chek to see if this player has disconnected b4. else we'll be wasting a slot. for (; l < MAX_RECON_NAMES; l++) { if (g_reconData[l].ipAddress[0] == 0) { continue; } if (Q_stricmp(ent->client->pers.ip, g_reconData[l].ipAddress) == 0) { foundName = qtrue; break; } } if (foundName) { memset(&g_reconData[l], 0, sizeof(g_reconData[l])); //IP Address Q_strncpyz(g_reconData[l].ipAddress, ent->client->pers.ip, sizeof(g_reconData[l].ipAddress)); //Player Name Q_strncpyz(g_reconData[l].previousName, ent->client->pers.netname, sizeof(g_reconData[l].previousName)); } else { memset(&g_reconData[g_reconNum], 0, sizeof(g_reconData[g_reconNum])); //IP Address Q_strncpyz(g_reconData[g_reconNum].ipAddress, ent->client->pers.ip, sizeof(g_reconData[g_reconNum].ipAddress)); //Player Name Q_strncpyz(g_reconData[g_reconNum].previousName, ent->client->pers.netname, sizeof(g_reconData[g_reconNum].previousName)); g_reconNum++; //cap reconNum just in case. if (g_reconNum >= MAX_RECON_NAMES) { g_reconNum = 0; } } } // send effect if they were completely connected if ((ent->client->pers.connected == CON_CONNECTED) && (ent->client->sess.sessionTeam != TEAM_SPECTATOR)) { vec3_t org; VectorCopy(ent->client->ps.origin, org); org[2] += (ent->client->ps.viewheight >> 1); tent = G_TempEntity(org, EV_PLAYER_TELEPORT_OUT); tent->s.clientNum = ent->s.clientNum; // They don't get to take powerups with them! // Especially important for stuff like CTF flags G_Client_TossClientItems(ent, qtrue); } G_LogPrintf("ClientDisconnect: %i (%s)\n", clientNum, g_entities[clientNum].client->pers.ip); // if we are playing in tourney mode and losing, give a win to the other player if ((g_gametype.integer == GT_TOURNAMENT) && (level.intermissiontime == 0) && (level.warmupTime == 0) && (level.sortedClients[1] == clientNum)) { level.clients[level.sortedClients[0]].sess.wins++; G_Client_UserinfoChanged(level.sortedClients[0]); } if ((g_gametype.integer == GT_TOURNAMENT) && (ent->client->sess.sessionTeam == TEAM_FREE) && (level.intermissiontime != 0)) { trap_SendConsoleCommand(EXEC_APPEND, "map_restart 0\n"); level.restarted = qtrue; level.intermissiontime = 0; } trap_UnlinkEntity(ent); memset(ent, 0, sizeof(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; ent->client->ps.persistant[PERS_CLASS] = 0;//PC_NOCLASS; ent->client->sess.sessionTeam = TEAM_FREE; ent->client->sess.sessionClass = 0;//PC_NOCLASS; trap_SetConfigstring(CS_PLAYERS + clientNum, ""); G_Client_CalculateRanks(qfalse); if ((ent->r.svFlags & SVF_BOT) != 0) { AI_main_BotAIShutdownClient(clientNum); } // kef -- if this guy contributed to any of our kills/deaths/weapons logs, clean 'em out G_ClearClientLog(clientNum); //also remove any initial data clientInitialStatus[clientNum].initialized = qfalse; } /* ================ IsAdmin RPG-X | Phenix | 21/11/2004 ================ */ /** * Checks if player is an admin. * \param ent the player * * \author Ubergames - Phenix * \date 21/11/2004 */ qboolean G_Client_IsAdmin(gentity_t *ent) { if (ent == NULL) { return qfalse; } if (ent->client == NULL) { return qfalse; } if ((g_classData[ent->client->sess.sessionClass].isAdmin) || (ent->client->LoggedAsAdmin == qtrue) || (ent->client->LoggedAsDeveloper == qtrue)) { return qtrue; } else { return qfalse; } } /* =========== G_Client_GetLocation Report a location for the player. Uses placed nearby target_location entities ============ */ static gentity_t* G_Client_GetLocation(gentity_t* ent) { gentity_t* eloc = NULL; gentity_t* best = NULL; double bestlen = 0.0; double len = 0.0; vec3_t origin; bestlen = 3 * 8192.0*8192.0; VectorCopy(ent->r.currentOrigin, origin); for (eloc = level.locationHead; eloc; eloc = eloc->nextTrain) { len = (origin[0] - eloc->r.currentOrigin[0]) * (origin[0] - eloc->r.currentOrigin[0]) + (origin[1] - eloc->r.currentOrigin[1]) * (origin[1] - eloc->r.currentOrigin[1]) + (origin[2] - eloc->r.currentOrigin[2]) * (origin[2] - eloc->r.currentOrigin[2]); if (len > bestlen) { continue; } if (!trap_InPVS(origin, eloc->r.currentOrigin)) { continue; } bestlen = len; best = eloc; } return best; } /* =========== G_Client_GetLocationMsg Report a location for the player. Uses placed nearby target_location entities ============ */ qboolean G_Client_GetLocationMsg(gentity_t *ent, char *loc, int32_t loclen) { gentity_t* best = NULL; best = G_Client_GetLocation(ent); if (best == NULL) { return qfalse; } if (best->count != 0) { if (best->count < 0) best->count = 0; if (best->count > 7) best->count = 7; Com_sprintf(loc, loclen, "%c%c%s" S_COLOR_WHITE, Q_COLOR_ESCAPE, best->count + '0', best->message); } else { Com_sprintf(loc, loclen, "%s", best->message); } return qtrue; } /* ================== G_Client_CheckHealthInfoMessage Sends Health Changes to proper clients Format: clientNum health ================== */ static void G_Client_CheckHealthInfoMessage(void) { char entry[1024]; char string[1400]; int32_t stringlength = 0; int32_t i = 0; int32_t j = 0; int32_t t = 0; int32_t sendToCnt = 0; int32_t cnt = 0; int32_t sentCnt = 0; int32_t h = 0; int32_t clients[MAX_CLIENTS]; int32_t sendToClients[MAX_CLIENTS]; gentity_t* player = NULL; gentity_t* ent = NULL; memset(entry, 0, sizeof(entry)); memset(string, 0, sizeof(string)); memset(clients, 0, sizeof(clients)); memset(sendToClients, 0, sizeof(sendToClients)); //only send this to medics or spectators or adminz for (; i < g_maxclients.integer; i++) { if ((level.clients[i].pers.connected == CON_CONNECTED) && (level.clients[i].ps.stats[STAT_HEALTH] > 0) && //make sure they've actually spawned in already ((level.clients[i].sess.sessionTeam == TEAM_SPECTATOR) || (g_classData[level.clients[i].sess.sessionClass].isMedical) || (g_classData[level.clients[i].sess.sessionClass].isAdmin))) { sendToClients[sendToCnt++] = i; } } if (sendToCnt == 0) { //no-one to send to return; } //only send those clients whose health has changed this cycle //NB: there's a prob with client 0 in here.... for (i = 0, cnt = 0; i < g_maxclients.integer; i++) { player = g_entities + i; if ((player != NULL) && player->inuse && (player->old_health != player->health) && ((player->health > 0) || (player->old_health > 0))) { clients[cnt++] = i; player->old_health = player->health; } } if (cnt == 0) { //no-one relevant changed health return; } for (t = 0; t < sendToCnt; t++) { ent = g_entities + sendToClients[t]; sentCnt = 0; // send the latest information on all clients string[0] = 0; stringlength = 0; for (i = 0; i < cnt; i++) { player = g_entities + clients[i]; if (ent == player) {//don't send the ent his own health continue; } //send this one sentCnt++; h = player->health; if (h < 0) h = 0; Com_sprintf(entry, sizeof(entry), " %i %i", clients[i], h); j = strlen(entry); if (stringlength + j > sizeof(string)) break; strcpy(string + stringlength, entry); stringlength += j; } if (sentCnt != 0) { trap_SendServerCommand(sendToClients[t], va("hinfo %i%s", sentCnt, string)); } } } /** * Send client location information. * * \param ent The client. */ static void G_Client_LocationsMessage(gentity_t *ent) { char entry[1024]; char string[1400]; int32_t stringlength = 0; int32_t i = 0; int32_t j = 0; int32_t cnt = 0; gentity_t* player = NULL; //don't bother sending during intermission? if (level.intermissiontime != 0) { return; } memset(entry, 0, sizeof(entry)); memset(string, 0, sizeof(string)); // figure out what client should be on the display // we are limited to 8, but we want to use the top eight players // but in client order (so they don't keep changing position on the overlay) for (; i < g_maxclients.integer && cnt < TEAM_MAXOVERLAY; i++) { player = g_entities + level.sortedClients[i]; if ((player != NULL) && (player->inuse) && (player->client->sess.sessionTeam == ent->client->sess.sessionTeam)) { // TODO huh? remove? } } // send the latest information on all clients for (i = 0, cnt = 0; i < g_maxclients.integer && cnt < TEAM_MAXOVERLAY; i++) { player = g_entities + i; //RPG-X | Phenix | 05/03/2005 if ((player != NULL) && player->inuse) { //to counter for the fact we could pwn the server doing this, remove all superfluous data Com_sprintf(entry, sizeof(entry), " %i %i ", i, player->client->pers.teamState.location); j = strlen(entry); if (stringlength + j > sizeof(string)) { break; } strcpy(string + stringlength, entry); stringlength += j; cnt++; } } trap_SendServerCommand(ent - g_entities, va("tinfo %i%s", cnt, string)); } //TiM - Modified to work with RPG-X void G_Client_CheckClientStatus(void) { gentity_t* loc = NULL; gentity_t* ent = NULL; if (level.time - level.lastTeamLocationTime > TEAM_LOCATION_UPDATE_TIME) { int32_t i = 0; level.lastTeamLocationTime = level.time; for (; i < g_maxclients.integer; i++) { ent = g_entities + i; if ((ent != NULL) && (ent->inuse)) { loc = G_Client_GetLocation(ent); if (loc != NULL) { ent->client->pers.teamState.location = loc->health; } else { ent->client->pers.teamState.location = 0; } } } for (i = 0; i < g_maxclients.integer; i++) { ent = g_entities + i; if ((ent != NULL) && (ent->inuse)) { G_Client_LocationsMessage(ent); } } G_Client_CheckHealthInfoMessage(); } } /* ============ G_Client_AddScore Adds score to both the client and his team ============ */ void G_Client_AddScore(gentity_t *ent, int score) { if (!ent) { return; } if (!ent->client) { return; } if (!ent->client->UpdateScore) { return; } ent->client->ps.persistant[PERS_SCORE] += score; //don't add score to team score during elimination if (g_gametype.integer == GT_TEAM) {//this isn't capture score level.teamScores[ent->client->ps.persistant[PERS_TEAM]] += score; } G_Client_CalculateRanks(qfalse); //RPG-X: RedTechie - Lets enable score updating without this scores will not be updated ent->client->UpdateScore = qfalse; } /* ============= SortRanks ============= */ static int QDECL SortRanks(const void *a, const void *b) { gclient_t *ca, *cb; ca = &level.clients[*(int *)a]; cb = &level.clients[*(int *)b]; // sort special clients last if (ca->sess.spectatorState == SPECTATOR_SCOREBOARD || ca->sess.spectatorClient < 0) { return 1; } if (cb->sess.spectatorState == SPECTATOR_SCOREBOARD || cb->sess.spectatorClient < 0) { return -1; } // then connecting clients if (ca->pers.connected == CON_CONNECTING) { return 1; } if (cb->pers.connected == CON_CONNECTING) { return -1; } // then spectators if (ca->sess.sessionTeam == TEAM_SPECTATOR && cb->sess.sessionTeam == TEAM_SPECTATOR) { if (ca->sess.spectatorTime < cb->sess.spectatorTime) { return -1; } if (ca->sess.spectatorTime > cb->sess.spectatorTime) { return 1; } return 0; } if (ca->sess.sessionTeam == TEAM_SPECTATOR) { return 1; } if (cb->sess.sessionTeam == TEAM_SPECTATOR) { return -1; } // then sort by score & number of times killed if (ca->ps.persistant[PERS_SCORE] > cb->ps.persistant[PERS_SCORE]) { return -1; } if ((ca->ps.persistant[PERS_SCORE] == cb->ps.persistant[PERS_SCORE]) && (ca->ps.persistant[PERS_KILLED] < cb->ps.persistant[PERS_KILLED])) { return -1; } if (ca->ps.persistant[PERS_SCORE] < cb->ps.persistant[PERS_SCORE]) { return 1; } if ((ca->ps.persistant[PERS_SCORE] == cb->ps.persistant[PERS_SCORE]) && (ca->ps.persistant[PERS_KILLED] > cb->ps.persistant[PERS_KILLED])) { return 1; } return 0; } /* ============ G_Client_CalculateRanks Recalculates the score ranks of all players This will be called on every client connect, begin, disconnect, death, and team change. FIXME: for elimination, the last man standing must be ranked first ============ */ void G_Client_CalculateRanks(qboolean fromExit) { int32_t i = 0; gclient_t *cl; level.follow1 = -1; level.follow2 = -1; level.numConnectedClients = 0; level.numNonSpectatorClients = 0; level.numPlayingClients = 0; level.numVotingClients = 0; // don't count bots for (; i < level.maxclients; i++) { if (level.clients[i].pers.connected != CON_DISCONNECTED) { level.sortedClients[level.numConnectedClients] = i; level.numConnectedClients++; if (level.clients[i].sess.sessionTeam != TEAM_SPECTATOR) { level.numNonSpectatorClients++; // decide if this should be auto-followed if (level.clients[i].pers.connected == CON_CONNECTED) { level.numPlayingClients++; if ((g_entities[i].r.svFlags & SVF_BOT) == 0) { level.numVotingClients++; } if (level.follow1 == -1) { level.follow1 = i; } else if (level.follow2 == -1) { level.follow2 = i; } } } } } qsort(level.sortedClients, (size_t)level.numConnectedClients, sizeof(level.sortedClients[0]), SortRanks); // set the rank value for all clients that are connected and not spectators if (g_gametype.integer >= GT_TEAM) { // in team games, rank is just the order of the teams, 0=red, 1=blue, 2=tied for (i = 0; i < level.numConnectedClients; i++) { cl = &level.clients[level.sortedClients[i]]; if (level.teamScores[TEAM_RED] == level.teamScores[TEAM_BLUE]) { cl->ps.persistant[PERS_RANK] = 2; } else if (level.teamScores[TEAM_RED] > level.teamScores[TEAM_BLUE]) { cl->ps.persistant[PERS_RANK] = 0; } else { cl->ps.persistant[PERS_RANK] = 1; } } } else { int32_t rank = -1; int32_t score = 0; for (i = 0; i < level.numPlayingClients; i++) { int32_t newScore = 0; cl = &level.clients[level.sortedClients[i]]; newScore = cl->ps.persistant[PERS_SCORE]; if (i == 0 || newScore != score) { rank = i; // assume we aren't tied until the next client is checked level.clients[level.sortedClients[i]].ps.persistant[PERS_RANK] = rank; } else { // we are tied with the previous client level.clients[level.sortedClients[i - 1]].ps.persistant[PERS_RANK] = rank | RANK_TIED_FLAG; level.clients[level.sortedClients[i]].ps.persistant[PERS_RANK] = rank | RANK_TIED_FLAG; } score = newScore; if (g_gametype.integer == GT_SINGLE_PLAYER && level.numPlayingClients == 1) { level.clients[level.sortedClients[i]].ps.persistant[PERS_RANK] = rank | RANK_TIED_FLAG; } } } // set the CS_SCORES1/2 configstrings, which will be visible to everyone if (g_gametype.integer >= GT_TEAM) { trap_SetConfigstring(CS_SCORES1, va("%i", level.teamScores[TEAM_RED])); trap_SetConfigstring(CS_SCORES2, va("%i", level.teamScores[TEAM_BLUE])); } else { if (level.numConnectedClients == 0) { trap_SetConfigstring(CS_SCORES1, va("%i", SCORE_NOT_PRESENT)); trap_SetConfigstring(CS_SCORES2, va("%i", SCORE_NOT_PRESENT)); } else if (level.numConnectedClients == 1) { trap_SetConfigstring(CS_SCORES1, va("%i", level.clients[level.sortedClients[0]].ps.persistant[PERS_SCORE])); trap_SetConfigstring(CS_SCORES2, va("%i", SCORE_NOT_PRESENT)); } else { trap_SetConfigstring(CS_SCORES1, va("%i", level.clients[level.sortedClients[0]].ps.persistant[PERS_SCORE])); trap_SetConfigstring(CS_SCORES2, va("%i", level.clients[level.sortedClients[1]].ps.persistant[PERS_SCORE])); } } // if we are at the intermission, send the new info to everyone if (level.intermissiontime != 0) { SendScoreboardMessageToAllClients(); } } void G_Client_UpdateSoundZones(void) { int32_t i = 0; int32_t b = 0; int32_t entlist[MAX_GENTITIES]; int32_t zones[MAX_CLIENTS]; int32_t count = 0; char supdate[MAX_STRING_CHARS]; memset(&zones, 0, sizeof(zones)); memset(&supdate, 0, sizeof(supdate)); for (; i < MAX_GENTITIES; i++) { if ((g_entities[i].type == ENT_TARGET_ZONE) && (g_entities[i].count == 3)) { memset(&entlist, 0, sizeof(entlist)); count = trap_EntitiesInBox(g_entities[i].r.mins, g_entities[i].r.maxs, (int32_t*)&entlist, MAX_GENTITIES); for (b = 0; b < count; b++) { if (g_entities[entlist[b]].client != NULL) { zones[g_entities[entlist[b]].client->ps.clientNum] = g_entities[i].s.number; } } } } for (i = 0; i < MAX_CLIENTS; i++) { if (strlen(supdate) != 0) { Com_sprintf(supdate, sizeof(supdate), "%s\\c%d\\%d", supdate, i, zones[i]); } else { Com_sprintf(supdate, sizeof(supdate), "c%d\\%d", i, zones[i]); } } for (i = 0; i < MAX_GENTITIES; i++) { if (g_entities[i].client != NULL) { trap_SendServerCommand(i, va("slup %s", supdate)); } } } void G_Client_SetScore(gentity_t* ent, int32_t score) { G_Assert(ent, (void)0); G_Assert(ent->client, (void)0); if (!ent->client->UpdateScore) { return; } ent->client->ps.persistant[PERS_SCORE] = score; G_Client_CalculateRanks(qfalse); // TiM: send the current scoring to all clients SendScoreboardMessageToAllClients(); //RPG-X: RedTechie - Lets enable score updating without this scores will not be updated ent->client->UpdateScore = qfalse; } void G_Client_TossClientItems(gentity_t* self, qboolean dis_con) { double angle = 0; int32_t i = 0; int32_t times = 0; gentity_t* drop = NULL; playerState_t *ps = NULL; gitem_t* item = NULL; G_Assert(self, (void)0); G_Assert(self->client, (void)0); ps = &self->client->ps; if ((self->flags & FL_CLOAK) != 0) { // remove the invisible powerup if the player is cloaked. //RPG-X: RedTechie - Also remove ghost ps->powerups[PW_GHOST] = level.time; ps->powerups[PW_INVIS] = level.time; } if ((self->flags & FL_FLY) != 0) { // remove the flying powerup if the player is flying. ps->powerups[PW_FLIGHT] = level.time; } //RPG-X | Phenix | 8/8/2004 if ((self->flags & FL_EVOSUIT) != 0) { // remove the evosuit powerup ps->powerups[PW_EVOSUIT] = level.time; } // drop all the powerups if not in teamplay if (g_gametype.integer != GT_TEAM) { angle = 45; for (i = 1; i < PW_NUM_POWERUPS; i++) { if (ps->powerups[i] > level.time) { item = BG_FindItemForPowerup(i); if (item == NULL) { continue; } drop = Drop_Item(self, item, angle); // decide how many seconds it has left drop->count = (ps->powerups[i] - level.time) / 1000; if (drop->count < 1) { drop->count = 1; } angle += 45; } } } // RPG-X | Marcin | 30/12/2008 // ... if (rpg_allowWeaponDrop.integer == 0 || rpg_dropOnDeath.integer == 0 || dis_con) { return; } // Drop ALL weapons in inventory for (i = 0; i < WP_NUM_WEAPONS; ++i) { // these weapons should not be tossed (hand and null) if (Max_Weapons[i] == NULL) { continue; } //RPG-X | GSIO01 | 08/05/2009: let's make sure we only drop weapons the player has item = NULL; if (ps->ammo[i]) { times = ps->ammo[i]; item = BG_FindItemForWeapon((weapon_t)i); while (times-- > 0) { // the 'goes towards' operator :p Drop_Item(self, item, 0); } } } // then remove weapons for (i = 0; i < WP_NUM_WEAPONS; ++i) { ps->stats[STAT_WEAPONS] &= ~i; ps->ammo[i] = 0; } } void body_die(gentity_t* self, gentity_t* inflictor, gentity_t* attacker, int32_t damage, int32_t meansOfDeath) { int32_t contents = 0; G_Assert(self, (void)0); contents = trap_PointContents(self->r.currentOrigin, -1); if (rpg_medicsrevive.integer == 1 && !(contents & CONTENTS_NODROP) && (meansOfDeath != MOD_TRIGGER_HURT)) { if (self->health > GIB_HEALTH_IMPOSSIBLE) { return; } } else { if (self->health > GIB_HEALTH) { return; } } G_Combat_GibEntity(self, 0); } char* G_Client_ClassNameForValue(pclass_t pClass) { static char buffer[MAX_QPATH]; char *ptr = NULL; trap_Cvar_VariableStringBuffer(va("rpg_%sPass", g_classData[pClass].consoleName), buffer, sizeof(buffer)); ptr = buffer; return ptr; } // these are just for logging, the client prints its own messages char* modNames[MOD_MAX] = { "MOD_UNKNOWN", "MOD_WATER", "MOD_SLIME", "MOD_LAVA", "MOD_CRUSH", "MOD_TELEFRAG", "MOD_FALLING", "MOD_SUICIDE", "MOD_TARGET_LASER", "MOD_TRIGGER_HURT", // Trek weapons "MOD_PHASER", "MOD_PHASER_ALT", "MOD_CRIFLE", "MOD_CRIFLE_SPLASH", "MOD_CRIFLE_ALT", "MOD_CRIFLE_ALT_SPLASH", "MOD_IMOD", "MOD_IMOD_ALT", "MOD_SCAVENGER", "MOD_SCAVENGER_ALT", "MOD_SCAVENGER_ALT_SPLASH", "MOD_STASIS", "MOD_STASIS_ALT", "MOD_GRENADE", "MOD_GRENADE_ALT", "MOD_GRENADE_SPLASH", "MOD_GRENADE_ALT_SPLASH", "MOD_TETRYON", "MOD_TETRYON_ALT", "MOD_DREADNOUGHT", "MOD_DREADNOUGHT_ALT", "MOD_QUANTUM", "MOD_QUANTUM_SPLASH", "MOD_QUANTUM_ALT", "MOD_QUANTUM_ALT_SPLASH", "MOD_DETPACK", "MOD_SEEKER" //expansion pack "MOD_KNOCKOUT", "MOD_ASSIMILATE", "MOD_BORG", "MOD_BORG_ALT", "MOD_RESPAWN", "MOD_EXPLOSION", }; //must be kept up to date with bg_public, meansOfDeath_t /* ================== G_Client_Die Heavly Modifyed By: RedTechie RPG-X: Marcin: a little bit modified - 30/12/2008 ================== */ void G_Client_Die(gentity_t* self, gentity_t* inflictor, gentity_t* attacker, int32_t damage, int32_t meansOfDeath) { //--------------------- //RPG-X: RedTechie - Check to see if medics revive people and not respawn if true use my fake death insead :) //--------------------- int32_t contents = 0; G_Assert(self, (void)0); G_Assert(self->client, (void)0); //RPG-X: RedTechie - Make sure there not getting killed by a trigger kill or the medics wont be able to heal them contents = trap_PointContents(self->r.currentOrigin, -1); if (rpg_medicsrevive.integer == 1 && !(contents & CONTENTS_NODROP) && (meansOfDeath != MOD_TRIGGER_HURT)) { char* classname = NULL; char* killerName = NULL; char* obit = NULL; int32_t anim = 0; int32_t killer = 0; int32_t i = 0; gentity_t* detpack = NULL; gentity_t* ent = NULL; playerState_t* ps = &self->client->ps; //RPG-X: RedTechie - Blow up a detpack if some one placed it and died classname = BG_FindClassnameForHoldable(HI_DETPACK); if (classname != NULL) { while ((detpack = G_Find(detpack, FOFS(classname), classname)) != NULL) { if (detpack->parent == self) { detpack->think = DetonateDetpack; // Detonate next think. detpack->nextthink = level.time; } } } //RPG-X: Redtechie - Do some score keeping witch we commented out and log if (attacker != NULL) { killer = attacker->s.number; if (attacker->client != NULL) { killerName = attacker->client->pers.netname; } else { killerName = ""; } } else { killer = ENTITYNUM_WORLD; killerName = ""; } if (killer < 0 || killer >= MAX_CLIENTS) { killer = ENTITYNUM_WORLD; killerName = ""; } if (meansOfDeath < 0 || meansOfDeath >= sizeof(modNames) / sizeof(modNames[0])) { obit = ""; } 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); G_LogWeaponKill(killer, meansOfDeath); G_LogWeaponDeath(self->s.number, self->s.weapon); if (attacker != NULL && attacker->client != NULL && attacker->inuse) { G_LogWeaponFrag(killer, self->s.number); } if (meansOfDeath != MOD_RESPAWN && meansOfDeath != MOD_CUSTOM_DIE) { // 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; ps->persistant[PERS_KILLED]++; if (self == attacker) { self->client->pers.teamState.suicides++; } else { //RPG-X | Phenix | 06/04/2005 // N00b Protection, you kill two people and puff your auto n00b! if (attacker != NULL) { if (attacker->client != NULL) { if (G_Client_IsAdmin(attacker) == qfalse) { attacker->n00bCount++; attacker->client->fraggerTime = level.time + (rpg_fraggerSpawnDelay.integer * 1000); if (rpg_kickAfterXkills.integer < 1) { trap_SendServerCommand(attacker - g_entities, va("print \"^7Server: You have been caught n00bing, you have been temporary put in the n00b class.\n\"")); } else { trap_SendServerCommand(attacker - g_entities, va("print \"^7Server: You have been caught n00bing, %i more times and you will be kicked.\n\"", (rpg_kickAfterXkills.integer - attacker->n00bCount))); } if ((attacker->n00bCount >= rpg_kickAfterXkills.integer) && (rpg_kickAfterXkills.integer != 0)) { trap_DropClient(attacker->s.number, "Kicked: Do Not N00b!"); } else { for (i = 0; g_classData[i].consoleName[0] && i < MAX_CLASSES; i++) { if (g_classData[i].isn00b) { char conName[64]; trap_Cvar_VariableStringBuffer(va("rpg_%sPass", conName), conName, sizeof(conName)); Q_strncpyz(attacker->client->origClass, G_Client_ClassNameForValue(attacker->client->sess.sessionClass), sizeof(attacker->client->origClass)); attacker->client->n00bTime = level.time + 10000; SetClass(attacker, conName, NULL, qfalse); break; } } } } } } } //RPG-X: RedTechie no noclip if (self->client->noclip) { self->client->noclip = qfalse; } //RPG-X: RedTechie - Toss items //RPG-X: Marcin - not when respawning - 30/12/2008 if (meansOfDeath != MOD_RESPAWN) { G_Client_TossClientItems(self, qfalse); } ps->pm_type = PM_DEAD; self->takedamage = qfalse; ps->weapon = WP_0; ps->weaponstate = WEAPON_READY; self->r.contents = CONTENTS_CORPSE; //-TiM self->s.loopSound = 0; self->r.maxs[2] = -8; //RPG-X: RedTechie - Wait....forever self->client->respawnTime = level.time + 1000000000; //Play death sound //RPG-X: RedTechie - No pain sound when they change class if (meansOfDeath != MOD_RESPAWN) { G_AddEvent(self, irandom(EV_DEATH1, EV_DEATH3), killer); //if we died from falling, add a nice "splat' sound lol if (meansOfDeath == MOD_FALLING) { G_AddEvent(self, EV_SPLAT, killer); } } //RPG-X : Model system - Death animations now based on vector hit if (meansOfDeath == MOD_FALLING) { anim = BOTH_FALLDEATH1LAND; } else if (self->waterlevel == 3) { anim = BOTH_FLOAT2; } else { if (meansOfDeath == MOD_PHASER || meansOfDeath == MOD_PHASER_ALT) { if ((self->client->lasthurt_location & LOCATION_FRONT) != 0) { anim = BOTH_DEATHBACKWARD1; } else if ((self->client->lasthurt_location & LOCATION_BACK) != 0) { anim = BOTH_DEATHFORWARD2; } else if ((self->client->lasthurt_location & LOCATION_LEFT) != 0) { anim = BOTH_DEATH2; } else if ((self->client->lasthurt_location & LOCATION_RIGHT) != 0) { anim = BOTH_DEATH2; } else { anim = BOTH_DEATH1; } } else { if ((self->client->lasthurt_location & LOCATION_FRONT) != 0) { anim = BOTH_DEATHBACKWARD2; } else if ((self->client->lasthurt_location & LOCATION_BACK) != 0) { anim = BOTH_DEATHFORWARD1; } else if ((self->client->lasthurt_location & LOCATION_LEFT) != 0) { anim = BOTH_DEATHFORWARD2; } else if ((self->client->lasthurt_location & LOCATION_RIGHT) != 0) { anim = BOTH_DEATHFORWARD2; } else { anim = BOTH_DEATH1; } } } //TiM ps->stats[LEGSANIM] = ((ps->stats[LEGSANIM] & ANIM_TOGGLEBIT) ^ ANIM_TOGGLEBIT) | anim; ps->stats[TORSOANIM] = ((ps->stats[TORSOANIM] & ANIM_TOGGLEBIT) ^ ANIM_TOGGLEBIT) | anim; trap_LinkEntity(self); BG_PlayerStateToEntityState(&self->client->ps, &self->s, qtrue); G_Client_UserinfoChanged(self->s.clientNum); ClientEndFrame(self); G_Client_StoreClientInitialStatus(self); //--------------------- //RPG-X: RedTechie - If it dose equal 0 use regular die //--------------------- } else { char* killerName = NULL; char* obit = NULL; char* classname = NULL; int anim = 0; int killer = 0; int i = 0; int BottomlessPitDeath = 0; static int deathNum; gentity_t* ent = NULL; gentity_t* detpack = NULL; playerState_t* ps = &self->client->ps; if (ps->pm_type == PM_DEAD) { return; } if (level.intermissiontime != 0) { return; } //RPG-X: RedTechie - Trying to make sure player dies when there health is 1 without medics revive turned on //RPG-X | Phenix | 05/04/2005 - Read learn that "=" sets where "==" is an if statement!!! if (self->health == 1) { self->health = 0; } ps->pm_type = PM_DEAD; //need to copy health here because pm_type was getting reset to PM_NORMAL if ClientThink_real was called before the STAT_HEALTH was updated ps->stats[STAT_HEALTH] = self->health; // check if we are in a NODROP Zone and died from a TRIGGER HURT // if so, we assume that this resulted from a fall to a "bottomless pit" and // treat it differently... // // Any problems with other places in the code? // BottomlessPitDeath = 0; // initialize contents = trap_PointContents(self->r.currentOrigin, -1); if ((contents & CONTENTS_NODROP) != 0 && (meansOfDeath == MOD_TRIGGER_HURT)) { BottomlessPitDeath = 1; } // similarly, if El Corpso here has already dropped a detpack, blow it up classname = BG_FindClassnameForHoldable(HI_DETPACK); if (classname) { while ((detpack = G_Find(detpack, FOFS(classname), classname)) != NULL) { if (detpack->parent == self) { detpack->think = DetonateDetpack; // Detonate next think. detpack->nextthink = level.time; } } } if (attacker != NULL) { killer = attacker->s.number; if (attacker->client != NULL) { killerName = attacker->client->pers.netname; } else { killerName = ""; } } else { killer = ENTITYNUM_WORLD; killerName = ""; } if (killer < 0 || killer >= MAX_CLIENTS) { killer = ENTITYNUM_WORLD; killerName = ""; } if (meansOfDeath < 0 || meansOfDeath >= sizeof(modNames) / sizeof(modNames[0])) { obit = ""; } 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); G_LogWeaponKill(killer, meansOfDeath); G_LogWeaponDeath(self->s.number, self->s.weapon); if (attacker != NULL && attacker->client != NULL && attacker->inuse) { G_LogWeaponFrag(killer, self->s.number); } if (meansOfDeath != MOD_RESPAWN) { // 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; ps->persistant[PERS_KILLED]++; if (self == attacker) { self->client->pers.teamState.suicides++; } //RPG-X: Redtechie - No awards or score calculations //////////////////////////////////////////////////////////////////////// if (attacker != NULL && attacker->client != NULL) { if (attacker == self) { if (meansOfDeath != MOD_RESPAWN) {//just changing class G_Client_AddScore(attacker, -1); } } else { attacker->client->pers.teamState.frags++; G_Client_AddScore(attacker, 1); // Check to see if the player is on a streak. attacker->client->streakCount++; attacker->client->lastKillTime = level.time; } } else { if (meansOfDeath != MOD_RESPAWN) {//not just changing class G_Client_AddScore(self, -1); } } //////////////////////////////////////////////////////////////////////// //RPG-X: Redtechie - agian no need // Add team bonuses //Team_FragBonuses(self, inflictor, attacker); // if client is in a nodrop area, don't drop anything (but return CTF flags!) if ((contents & CONTENTS_NODROP) == 0 && meansOfDeath != MOD_SUICIDE && meansOfDeath != MOD_RESPAWN) {//action hero doesn't drop stuff //don't drop stuff in specialty mode if (meansOfDeath != MOD_RESPAWN) { G_Client_TossClientItems(self, qfalse); } } DeathmatchScoreboardMessage(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.maxclients; i++) { gclient_t* client = NULL; client = &level.clients[i]; if (client == NULL) { continue; } if (client->pers.connected != CON_CONNECTED) { continue; } if (client->sess.sessionTeam != TEAM_SPECTATOR) { continue; } if (client->sess.spectatorClient == self->s.number) { DeathmatchScoreboardMessage(g_entities + i); } } self->takedamage = qtrue; // can still be gibbed self->s.weapon = WP_0; self->s.powerups = 0; self->r.contents = CONTENTS_CORPSE; self->s.loopSound = 0; self->r.maxs[2] = -8; // don't allow respawn until the death anim is done // g_forcerespawn may force spawning at some later time self->client->respawnTime = level.time + 1700; // We always want to see the body for special animations, so make sure not to gib right away: if (meansOfDeath == MOD_CRIFLE_ALT || meansOfDeath == MOD_DETPACK || meansOfDeath == MOD_QUANTUM_ALT || meansOfDeath == MOD_DREADNOUGHT_ALT || meansOfDeath == MOD_PHASER_ALT)//RPG-X: RedTechie - Added phaser alt disnt { self->health = 0; } //RPG-X : Model system - Death animations now based on vector hit if (meansOfDeath == MOD_FALLING) { anim = BOTH_FALLDEATH1LAND; } else if (self->waterlevel == 3) { anim = BOTH_FLOAT2; } else { if (meansOfDeath == MOD_PHASER || meansOfDeath == MOD_PHASER_ALT) { if ((self->client->lasthurt_location & LOCATION_FRONT) != 0) { anim = BOTH_DEATHBACKWARD1; } else if ((self->client->lasthurt_location & LOCATION_BACK) != 0) { anim = BOTH_DEATHFORWARD2; } else if ((self->client->lasthurt_location & LOCATION_LEFT) != 0) { anim = BOTH_DEATH2; } else if ((self->client->lasthurt_location & LOCATION_RIGHT) != 0) { anim = BOTH_DEATH2; } else { anim = BOTH_DEATH1; } } else { if ((self->client->lasthurt_location & LOCATION_FRONT) != 0) { anim = BOTH_DEATHBACKWARD2; } else if ((self->client->lasthurt_location & LOCATION_BACK) != 0) { anim = BOTH_DEATHFORWARD1; } else if ((self->client->lasthurt_location & LOCATION_LEFT) != 0) { anim = BOTH_DEATHFORWARD2; } else if ((self->client->lasthurt_location & LOCATION_RIGHT) != 0) { anim = BOTH_DEATHFORWARD2; } else { anim = BOTH_DEATH1; } } } ps->stats[LEGSANIM] = ((ps->stats[LEGSANIM] & ANIM_TOGGLEBIT) ^ ANIM_TOGGLEBIT) | anim; ps->stats[TORSOANIM] = ((ps->stats[TORSOANIM] & ANIM_TOGGLEBIT) ^ ANIM_TOGGLEBIT) | anim; if ((BottomlessPitDeath == 1) && (killer == ENTITYNUM_WORLD)) { //G_AddEvent( self, EV_FALL_FAR, killer ); ?? Need to play falling SF now, or // use designer trigger?? //FIXME: need *some* kind of death anim! } else { // normal death switch (meansOfDeath) { case MOD_PHASER_ALT: //RPG-X: RedTechie - Added better effect for alt phaser if (rpg_phaserdisintegrates.integer == 1) {//RPG-X: RedTechie - Check to see if we want this G_AddEvent(self, EV_DISINTEGRATION, killer); ps->powerups[PW_DISINTEGRATE] = level.time + 100000; VectorClear(ps->velocity); self->takedamage = qfalse; self->r.contents = 0; } break; case MOD_CRIFLE_ALT: break; case MOD_QUANTUM_ALT: G_AddEvent(self, EV_DISINTEGRATION2, killer); ps->powerups[PW_EXPLODE] = level.time + 100000; VectorClear(ps->velocity); self->takedamage = qfalse; self->r.contents = 0; break; case MOD_SCAVENGER_ALT: case MOD_SCAVENGER_ALT_SPLASH: case MOD_GRENADE: case MOD_GRENADE_ALT: case MOD_GRENADE_SPLASH: case MOD_GRENADE_ALT_SPLASH: case MOD_QUANTUM: case MOD_QUANTUM_SPLASH: case MOD_QUANTUM_ALT_SPLASH: case MOD_DETPACK: G_AddEvent(self, EV_EXPLODESHELL, killer); ps->powerups[PW_EXPLODE] = level.time + 100000; VectorClear(ps->velocity); self->takedamage = qfalse; self->r.contents = 0; break; case MOD_DREADNOUGHT: case MOD_DREADNOUGHT_ALT: G_AddEvent(self, EV_ARCWELD_DISINT, killer); ps->powerups[PW_ARCWELD_DISINT] = level.time + 100000; VectorClear(ps->velocity); self->takedamage = qfalse; self->r.contents = 0; break; case MOD_FALLING: G_AddEvent(self, EV_SPLAT, killer); break; default: G_AddEvent(self, irandom(EV_DEATH1, EV_DEATH3), killer); break; } // the body can still be gibbed self->die = body_die; } // globally cycle through the different death animations deathNum = (deathNum + 1) % 3; trap_LinkEntity(self); }//RPG-X: RedTechie - End of my if statment for medics revive check }//RPG-X: RedTechie - End of void qboolean G_Client_IsBorg(gentity_t* ent) { if (ent == NULL) { return qfalse; } if (ent->client == NULL) { return qfalse; } if (g_classData[ent->client->sess.sessionClass].isBorg) { return qtrue; } else { return qfalse; } }