// Add a player entity to another player's multiview list void G_smvAddView( gentity_t *ent, int pID ) { int i; mview_t *mv = NULL; gentity_t *v; if ( pID >= MAX_MVCLIENTS || G_smvLocateEntityInMVList( ent, pID, qfalse ) ) { return; } for ( i = 0; i < MULTIVIEW_MAXVIEWS; i++ ) { if ( !ent->client->pers.mv[i].fActive ) { mv = &ent->client->pers.mv[i]; break; } } if ( mv == NULL ) { CP( va( "print \"[lof]** [lon]Sorry, no more MV slots available (all[lof] %d [lon]in use)[lof]\n\"", MULTIVIEW_MAXVIEWS ) ); return; } mv->camera = G_Spawn(); if ( mv->camera == NULL ) { return; } if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR && /*ent->client->sess.sessionTeam != TEAM_SPECTATOR ||*/ ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) { SetTeam( ent, "s", qtrue, -1, -1, qfalse ); } else if ( ent->client->sess.sessionTeam != TEAM_SPECTATOR && !( ent->client->ps.pm_flags & PMF_LIMBO ) ) { limbo( ent, qtrue ); } ent->client->ps.clientNum = ent - g_entities; ent->client->sess.spectatorState = SPECTATOR_FREE; ent->client->pers.mvCount++; mv->fActive = qtrue; mv->entID = pID; v = mv->camera; v->classname = "misc_portal_surface"; v->r.svFlags = SVF_PORTAL | SVF_SINGLECLIENT; // Only merge snapshots for the target client v->r.singleClient = ent->s.number; v->s.eType = ET_PORTAL; VectorClear( v->r.mins ); VectorClear( v->r.maxs ); trap_LinkEntity( v ); v->target_ent = &g_entities[pID]; v->TargetFlag = pID; v->tagParent = ent; G_smvUpdateClientCSList( ent ); }
/* ============== ClientThink This will be called once for each client frame, which will usually be a couple times for each server frame on fast clients. If "g_synchronousClients 1" is set, this will be called exactly once for each server frame, which makes for smooth demo recording. ============== */ void ClientThink_real(gentity_t *ent) { int msec, oldEventSequence, speed, i, counter, Zaccel; pmove_t pm; usercmd_t *ucmd; gclient_t *client = ent->client; // don't think if the client is not yet connected (and thus not yet spawned in) if (client->pers.connected != CON_CONNECTED) { return; } if (ent->s.eFlags & EF_MOUNTEDTANK) { client->pmext.centerangles[YAW] = ent->tagParent->r.currentAngles[YAW]; client->pmext.centerangles[PITCH] = ent->tagParent->r.currentAngles[PITCH]; } // mark the time, so the connection sprite can be removed ucmd = &ent->client->pers.cmd; ent->client->ps.identifyClient = ucmd->identClient; // NERVE - SMF // sanity check the command time to prevent speedup cheating if (ucmd->serverTime > level.time + 200) { ucmd->serverTime = level.time + 200; } if (ucmd->serverTime < level.time - 1000) { ucmd->serverTime = level.time - 1000; } msec = ucmd->serverTime - client->ps.commandTime; // following others may result in bad times, but we still want // to check for follow toggles if (msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW) { return; } if (msec > 200) { msec = 200; } // Nico, pmove_fixed if (client->pers.pmoveFixed) { ucmd->serverTime = ((ucmd->serverTime + pmove_msec.integer - 1) / pmove_msec.integer) * pmove_msec.integer; } if (client->wantsscore) { G_SendScore(ent); client->wantsscore = qfalse; } // check for inactivity timer, but never drop the local client of a non-dedicated server // OSP - moved here to allow for spec inactivity checks as well if (!ClientInactivityTimer(client)) { return; } if (!(ucmd->flags & 0x01) || ucmd->forwardmove || ucmd->rightmove || ucmd->upmove || ucmd->wbuttons || ucmd->doubleTap) { ent->r.svFlags &= ~(SVF_SELF_PORTAL_EXCLUSIVE | SVF_SELF_PORTAL); } // spectators don't do much // DHM - Nerve :: In limbo use SpectatorThink // suburb, check for noclip not to override client->ps.speed if ((client->sess.sessionTeam == TEAM_SPECTATOR || client->ps.pm_flags & PMF_LIMBO) && !client->noclip) { SpectatorThink(ent, ucmd); return; } if (client->ps.eFlags & EF_VIEWING_CAMERA) { ucmd->buttons = 0; ucmd->forwardmove = 0; ucmd->rightmove = 0; ucmd->upmove = 0; ucmd->wbuttons = 0; ucmd->doubleTap = 0; // freeze player client->ps.pm_type = PM_FREEZE; } else if (client->noclip) { client->ps.pm_type = PM_NOCLIP; } else if (client->ps.stats[STAT_HEALTH] <= 0) { client->ps.pm_type = PM_DEAD; } else { client->ps.pm_type = PM_NORMAL; } client->ps.aiState = AISTATE_COMBAT; client->ps.gravity = DEFAULT_GRAVITY; client->ps.speed = DEFAULT_SPEED; if (client->speedScale) { // Goalitem speed scale client->ps.speed *= (client->speedScale * 0.01); } // set up for pmove oldEventSequence = client->ps.eventSequence; client->currentAimSpreadScale = (float)client->ps.aimSpreadScale / 255.0; memset(&pm, 0, sizeof (pm)); pm.ps = &client->ps; pm.pmext = &client->pmext; pm.character = client->pers.character; pm.cmd = *ucmd; pm.oldcmd = client->pers.oldcmd; // MrE: always use capsule for AI and player pm.trace = trap_TraceCapsule; // Nico, ghost players pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; if (pm.ps->pm_type == PM_DEAD) { pm.ps->eFlags |= EF_DEAD; } else if (pm.ps->pm_type == PM_SPECTATOR) { pm.trace = trap_TraceCapsuleNoEnts; } // Nico, end of ghost players //DHM - Nerve :: We've gone back to using normal bbox traces pm.pointcontents = trap_PointContents; pm.debugLevel = g_debugMove.integer; pm.noFootsteps = qfalse; // Nico, pmove_fixed // pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed; pm.pmove_fixed = client->pers.pmoveFixed; pm.pmove_msec = pmove_msec.integer; // suburb, Noclip speed scale pm.noclipSpeed = client->pers.noclipSpeed; // Nico, game physics pm.physics = physics.integer; pm.isTimerun = isTimerun.integer; pm.timerunActive = client->sess.timerunActive; pm.timerunStartTime = client->sess.timerunStartTime + 500; // Nico, store logins status in pmove if (client->sess.logged) { pm.isLogged = 1; } else { pm.isLogged = 0; } pm.noWeapClips = qfalse; VectorCopy(client->ps.origin, client->oldOrigin); // NERVE - SMF pm.ltChargeTime = level.lieutenantChargeTime[client->sess.sessionTeam - 1]; pm.soldierChargeTime = level.soldierChargeTime[client->sess.sessionTeam - 1]; pm.engineerChargeTime = level.engineerChargeTime[client->sess.sessionTeam - 1]; pm.medicChargeTime = level.medicChargeTime[client->sess.sessionTeam - 1]; // -NERVE - SMF client->pmext.airleft = ent->client->airOutTime - level.time; pm.covertopsChargeTime = level.covertopsChargeTime[client->sess.sessionTeam - 1]; // Gordon: bit hacky, stop the slight lag from client -> server even on locahost, switching back to the weapon you were holding // and then back to what weapon you should have, became VERY noticible for the kar98/carbine + gpg40, esp now i've added the // animation locking if (level.time - client->pers.lastSpawnTime < 1000) { pm.cmd.weapon = client->ps.weapon; } Pmove(&pm); // Gordon: thx to bani for this // ikkyo - fix leaning players bug VectorCopy(client->ps.velocity, ent->s.pos.trDelta); SnapVector(ent->s.pos.trDelta); // end // server cursor hints if (ent->lastHintCheckTime < level.time) { G_CheckForCursorHints(ent); ent->lastHintCheckTime = level.time + FRAMETIME; } // DHM - Nerve :: Set animMovetype to 1 if ducking if (ent->client->ps.pm_flags & PMF_DUCKED) { ent->s.animMovetype = 1; } else { ent->s.animMovetype = 0; } // save results of pmove if (ent->client->ps.eventSequence != oldEventSequence) { ent->eventTime = level.time; ent->r.eventTime = level.time; } // Ridah, fixes jittery zombie movement if (g_smoothClients.integer) { BG_PlayerStateToEntityStateExtraPolate(&ent->client->ps, &ent->s, level.time, qfalse); } else { BG_PlayerStateToEntityState(&ent->client->ps, &ent->s, qfalse); } if (!(ent->client->ps.eFlags & EF_FIRING)) { client->fireHeld = qfalse; // for grapple } // // use the snapped origin for linking so it matches client predicted versions VectorCopy(ent->s.pos.trBase, ent->r.currentOrigin); VectorCopy(pm.mins, ent->r.mins); VectorCopy(pm.maxs, ent->r.maxs); ent->waterlevel = pm.waterlevel; ent->watertype = pm.watertype; // execute client events ClientEvents(ent, oldEventSequence); // link entity now, after any personal teleporters have been used trap_LinkEntity(ent); if (!ent->client->noclip) { G_TouchTriggers(ent); } // NOTE: now copy the exact origin over otherwise clients can be snapped into solid VectorCopy(ent->client->ps.origin, ent->r.currentOrigin); // touch other objects ClientImpacts(ent, &pm); // save results of triggers and client events if (ent->client->ps.eventSequence != oldEventSequence) { ent->eventTime = level.time; } // swap and latch button actions client->oldbuttons = client->buttons; client->buttons = ucmd->buttons; client->latched_buttons = client->buttons & ~client->oldbuttons; //----(SA) added client->oldwbuttons = client->wbuttons; client->wbuttons = ucmd->wbuttons; client->latched_wbuttons = client->wbuttons & ~client->oldwbuttons; // suburb, return here for noclippers in spec to avoid following checks if (client->sess.sessionTeam == TEAM_SPECTATOR && client->noclip) { return; } // Rafael - Activate // Ridah, made it a latched event (occurs on keydown only) if (client->latched_buttons & BUTTON_ACTIVATE) { Cmd_Activate_f(ent); } if (g_entities[ent->client->ps.identifyClient].team != ent->team || !g_entities[ent->client->ps.identifyClient].client) { ent->client->ps.identifyClient = -1; } // check for respawning if (client->ps.stats[STAT_HEALTH] <= 0) { // Nico, forcing respawn limbo(ent); return; } // perform once-a-second actions ClientTimerActions(ent, msec); // Nico, check ping if (client->ps.ping > MAX_PLAYER_PING) { if (!client->pers.loadKillNeeded) { CP(va("cpm \"%s^w: ^1Too high ping detected, load or kill required.\n\"", GAME_VERSION_COLORED)); // suburb, prevent trigger bug client->pers.loadKillNeeded = qtrue; } } // Nico, pmove_fixed if (!client->pers.pmoveFixed) { CP(va("cpm \"%s^w: ^1You were removed from teams because you can not use pmove_fixed 0.\n\"", GAME_VERSION_COLORED)); trap_SendServerCommand(ent - g_entities, "pmoveon"); SetTeam(ent, "s", -1, -1, qfalse); } // Nico, check rate if (client->pers.rate < MIN_PLAYER_RATE_VALUE || client->pers.rate > MAX_PLAYER_RATE_VALUE) { CP(va("cpm \"%s^w: ^1You were removed from teams because you must use %d <= rate <= %d.\n\"", GAME_VERSION_COLORED, MIN_PLAYER_RATE_VALUE, MAX_PLAYER_RATE_VALUE)); trap_SendServerCommand(ent - g_entities, "resetRate"); SetTeam(ent, "s", -1, -1, qfalse); } // Nico, check snaps (unsigned int) if (client->pers.snaps > MAX_PLAYER_SNAPS_VALUE) { CP(va("cpm \"%s^w: ^1You were removed from teams because you must use %d <= snaps <= %d.\n\"", GAME_VERSION_COLORED, MIN_PLAYER_SNAPS_VALUE, MAX_PLAYER_SNAPS_VALUE)); trap_SendServerCommand(ent - g_entities, "resetSnaps"); SetTeam(ent, "s", -1, -1, qfalse); } // Nico, check timenudge if (client->pers.clientTimeNudge != FORCED_PLAYER_TIMENUDGE_VALUE) { CP(va("cpm \"%s^w: ^1You were removed from teams because you must use cl_timenudge %d.\n\"", GAME_VERSION_COLORED, FORCED_PLAYER_TIMENUDGE_VALUE)); trap_SendServerCommand(ent - g_entities, "resetTimeNudge"); SetTeam(ent, "s", -1, -1, qfalse); } // Nico, check maxpackets if (client->pers.clientMaxPackets < MIN_PLAYER_MAX_PACKETS_VALUE || client->pers.clientMaxPackets > MAX_PLAYER_MAX_PACKETS_VALUE) { CP(va("cpm \"%s^w: ^1You were removed from teams because you must use %d <= cl_maxpackets <= %d.\n\"", GAME_VERSION_COLORED, MIN_PLAYER_MAX_PACKETS_VALUE, MAX_PLAYER_MAX_PACKETS_VALUE)); trap_SendServerCommand(ent - g_entities, "resetMaxPackets"); SetTeam(ent, "s", -1, -1, qfalse); } // Nico, check max FPS if (client->pers.maxFPS < MIN_PLAYER_FPS_VALUE || client->pers.maxFPS > MAX_PLAYER_FPS_VALUE) { CP(va("cpm \"%s^w: ^1You were removed from teams because you must use %d <= com_maxfps <= %d.\n\"", GAME_VERSION_COLORED, MIN_PLAYER_FPS_VALUE, MAX_PLAYER_FPS_VALUE)); trap_SendServerCommand(ent - g_entities, "resetMaxFPS"); SetTeam(ent, "s", -1, -1, qfalse); } // suburb, force yawspeed 0 if (client->pers.yawspeed != 0) { CP(va("cpm \"%s^w: ^1You were removed from teams because you must use cl_yawspeed 0.\n\"", GAME_VERSION_COLORED)); trap_SendServerCommand(ent - g_entities, "resetYawspeed"); SetTeam(ent, "s", -1, -1, qfalse); } // suburb, force pitchspeed 0 if (client->pers.pitchspeed != 0) { CP(va("cpm \"%s^w: ^1You were removed from teams because you must use cl_pitchspeed 0.\n\"", GAME_VERSION_COLORED)); trap_SendServerCommand(ent - g_entities, "resetPitchspeed"); SetTeam(ent, "s", -1, -1, qfalse); } // Nico, force auto demo record in cup mode if (g_cupMode.integer != 0 && client->pers.autoDemo == 0) { CP(va("cpm \"%s^w: ^1You were removed from teams because you must use cg_autoDemo 1.\n\"", GAME_VERSION_COLORED)); trap_SendServerCommand(ent - g_entities, "autoDemoOn"); SetTeam(ent, "s", -1, -1, qfalse); } // Nico, force hide me in cup mode if (g_cupMode.integer != 0 && client->pers.hideme == 0) { CP(va("cpm \"%s^w: ^1You were removed from teams because you must use cg_hideMe 1.\n\"", GAME_VERSION_COLORED)); trap_SendServerCommand(ent - g_entities, "hideMeOn"); SetTeam(ent, "s", -1, -1, qfalse); } // suburb, force CGaz if disabled on server if (g_disableCGaz.integer != 0 && client->pers.cgaz != 0) { CP(va("cpm \"%s^w: ^1You were removed from teams because you must use cg_drawCGaz 0.\n\"", GAME_VERSION_COLORED)); trap_SendServerCommand(ent - g_entities, "CGazOff"); SetTeam(ent, "s", -1, -1, qfalse); } // suburb, force snapping hud if disabled on server or logged in if ((g_disableSnappingHUD.integer != 0 || (client->sess.logged && (client->sess.sessionTeam == TEAM_AXIS || client->sess.sessionTeam == TEAM_ALLIES))) && client->pers.snapping != 0) { CP(va("cpm \"%s^w: ^1You were removed from teams because you must use cg_drawVelocitySnapping 0.\n\"", GAME_VERSION_COLORED)); trap_SendServerCommand(ent - g_entities, "SnappingOff"); SetTeam(ent, "s", -1, -1, qfalse); } // suburb, prevent pronebug & wallbug counter = 0; for (i = 0; i < 3; ++i) { if (client->pers.oldPosition[i] == (int) pm.ps->origin[i]) { counter++; } } Zaccel = (int) pm.ps->velocity[2] - client->pers.oldZvelocity; speed = sqrt(pm.ps->velocity[0] * pm.ps->velocity[0] + pm.ps->velocity[1] * pm.ps->velocity[1]); if (client->sess.logged && ((!client->sess.timerunActive && speed > BUGGING_MAX_SPEED && counter == 3) || // prevent accelerating in brushes (pm.ps->eFlags & EF_PRONE && Zaccel > -6 && Zaccel < 0 && client->ps.groundEntityNum == ENTITYNUM_NONE && !client->pers.isTouchingJumppad))) { // prevent accelerating on steep slopes if (!client->pers.buggedLastFrame) { // only do something the second frame not to break jumppads client->pers.buggedLastFrame = qtrue; return; } if (!client->pers.loadKillNeeded) { CP(va("cpm \"%s^w: ^1Bugging detected, load or kill required.\n\"", GAME_VERSION_COLORED)); client->pers.loadKillNeeded = qtrue; } } // checking acceleration in brushes every frame would break corner skimming if (level.time - client->pers.lastBuggingCheck > BUGGING_CHECK_FREQUENCY) { for (i = 0; i < 3; ++i) { client->pers.oldPosition[i] = (int) pm.ps->origin[i]; client->pers.lastBuggingCheck = level.time; } } client->pers.oldZvelocity = (int) pm.ps->velocity[2]; client->pers.buggedLastFrame = qfalse; }
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 ); } } }
void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, vec3_t dir, vec3_t point, int damage, int dflags, int mod ) { gclient_t *client; int take; int save; int knockback; qboolean headShot; qboolean wasAlive; hitRegion_t hr = HR_NUM_HITREGIONS; if (!targ->takedamage) { return; } #ifdef SAVEGAME_SUPPORT if( g_gametype.integer == GT_SINGLE_PLAYER && ( g_reloading.integer || saveGamePending ) ) return; #endif // SAVEGAME_SUPPORT // trap_SendServerCommand( -1, va("print \"%i\n\"\n", targ->health) ); // the intermission has allready been qualified for, so don't // allow any extra scoring if ( level.intermissionQueued || (g_gamestate.integer != GS_PLAYING && match_warmupDamage.integer == 0)) { return; } if ( !inflictor ) { inflictor = &g_entities[ENTITYNUM_WORLD]; } if ( !attacker ) { attacker = &g_entities[ENTITYNUM_WORLD]; } // Arnout: invisible entities can't be damaged if( targ->entstate == STATE_INVISIBLE || targ->entstate == STATE_UNDERCONSTRUCTION ) { return; } // xkan, 12/23/2002 - was the bot alive before applying any damage? wasAlive = (targ->health > 0); // Arnout: combatstate if( targ->client && attacker && attacker->client && attacker != targ ) { /*vec_t dist = -1.f; if( targ->client->combatState < COMBATSTATE_HOT ) { vec3_t shotvec; VectorSubtract( targ->r.currentOrigin, attacker->r.currentOrigin, shotvec ); dist = VectorLengthSquared( shotvec ); if( dist < Square(1500.f) && targ->client->combatState == COMBATSTATE_WARM ) targ->client->combatState = COMBATSTATE_HOT; } if( attacker->client->combatState < COMBATSTATE_HOT ) { if( dist < 0.f ) { vec3_t shotvec; VectorSubtract( targ->r.currentOrigin, attacker->r.currentOrigin, shotvec ); dist = VectorLengthSquared( shotvec ); } if( dist > Square(1500.f) ) attacker->client->combatState = COMBATSTATE_WARM; else if( attacker->client->combatState == COMBATSTATE_WARM ) attacker->client->combatState = COMBATSTATE_HOT; }*/ if( g_gamestate.integer == GS_PLAYING ) { if( !OnSameTeam( attacker, targ ) ) { targ->client->combatState |= (1<<COMBATSTATE_DAMAGERECEIVED); attacker->client->combatState |= (1<<COMBATSTATE_DAMAGEDEALT); } } } // JPW NERVE if ((targ->waterlevel >= 3) && (mod == MOD_FLAMETHROWER)) return; // jpw // shootable doors / buttons don't actually have any health if ( targ->s.eType == ET_MOVER && !(targ->isProp) && !targ->scriptName) { if ( targ->use && targ->moverState == MOVER_POS1 ) { G_UseEntity( targ, inflictor, attacker ); } return; } // TAT 11/22/2002 // In the old code, this check wasn't done for props, so I put that check back in to make props_statue properly work // 4 means destructible if ( targ->s.eType == ET_MOVER && (targ->spawnflags & 4) && !targ->isProp ) { /*switch (mod) { case MOD_GRENADE: case MOD_GRENADE_LAUNCHER: case MOD_ROCKET: case MOD_AIRSTRIKE: case MOD_ARTY: case MOD_GRENADE_PINEAPPLE: case MOD_MAPMORTAR: case MOD_EXPLOSIVE: case MOD_DYNAMITE: case MOD_LANDMINE: case MOD_GPG40: case MOD_M7: case MOD_TELEFRAG: case MOD_PANZERFAUST: case MOD_SATCHEL: break; default: return; // no damage from other weapons }*/ if( !G_WeaponIsExplosive( mod ) ) { return; } // check for team if( G_GetTeamFromEntity( inflictor ) == G_GetTeamFromEntity( targ ) ) { return; } } else if ( targ->s.eType == ET_EXPLOSIVE ) { /*// 32 Explosive // 64 Dynamite only // 256 Airstrike/artillery only // 512 Satchel only if ((targ->spawnflags & 32) || (targ->spawnflags & 64) || (targ->spawnflags & 256) || (targ->spawnflags & 512)) { switch (mod) { case MOD_GRENADE: case MOD_GRENADE_LAUNCHER: case MOD_ROCKET: case MOD_GRENADE_PINEAPPLE: case MOD_MAPMORTAR: case MOD_EXPLOSIVE: case MOD_LANDMINE: case MOD_GPG40: case MOD_M7: if( !(targ->spawnflags & 32) ) return; break; case MOD_SATCHEL: if( !(targ->spawnflags & 512) ) return; break; case MOD_ARTY: case MOD_AIRSTRIKE: if( !(targ->spawnflags & 256) ) return; break; case MOD_DYNAMITE: if( !(targ->spawnflags & 64) ) return; break; default: return; } // check for team if( targ->s.teamNum == inflictor->s.teamNum ) { return; } }*/ if( targ->parent && G_GetWeaponClassForMOD( mod ) == 2 ) { return; } // check for team // if( G_GetWeaponClassForMOD( mod ) != -1 && targ->s.teamNum == inflictor->s.teamNum ) { // return; // } if( G_GetTeamFromEntity( inflictor ) == G_GetTeamFromEntity( targ ) ) { return; } if( G_GetWeaponClassForMOD( mod ) < targ->constructibleStats.weaponclass ) { return; } } else if ( targ->s.eType == ET_MISSILE && targ->methodOfDeath == MOD_LANDMINE ) { if( targ->s.modelindex2 ) { if( G_WeaponIsExplosive( mod ) ) { mapEntityData_t *mEnt; if((mEnt = G_FindMapEntityData(&mapEntityData[0], targ-g_entities)) != NULL) { G_FreeMapEntityData( &mapEntityData[0], mEnt ); } if((mEnt = G_FindMapEntityData(&mapEntityData[1], targ-g_entities)) != NULL) { G_FreeMapEntityData( &mapEntityData[1], mEnt ); } if( attacker && attacker->client ) { AddScore( attacker, 1 ); //G_AddExperience( attacker, 1.f ); } G_ExplodeMissile(targ); } } return; } else if ( targ->s.eType == ET_CONSTRUCTIBLE ) { if( G_GetTeamFromEntity( inflictor ) == G_GetTeamFromEntity( targ ) ) { return; } if( G_GetWeaponClassForMOD( mod ) < targ->constructibleStats.weaponclass ) { return; } } client = targ->client; if ( client ) { if ( client->noclip || client->ps.powerups[PW_INVULNERABLE] ) { return; } } // check for godmode if ( targ->flags & FL_GODMODE ) { return; } if ( !dir ) { dflags |= DAMAGE_NO_KNOCKBACK; } else { VectorNormalize(dir); } knockback = damage; if ( knockback > 200 ) { knockback = 200; } if ( targ->flags & FL_NO_KNOCKBACK ) { knockback = 0; } if ( dflags & DAMAGE_NO_KNOCKBACK ) { knockback = 0; } else if( dflags & DAMAGE_HALF_KNOCKBACK ) { knockback *= 0.5f; } // ydnar: set weapons means less knockback if( client && (client->ps.weapon == WP_MORTAR_SET || client->ps.weapon == WP_MOBILE_MG42_SET) ) knockback *= 0.5; if( targ->client && g_friendlyFire.integer && OnSameTeam(targ, attacker) ) { knockback = 0; } // figure momentum add, even if the damage won't be taken if ( knockback && targ->client ) { vec3_t kvel; float mass; mass = 200; VectorScale (dir, g_knockback.value * (float)knockback / mass, kvel); VectorAdd (targ->client->ps.velocity, kvel, targ->client->ps.velocity); /*if( mod == MOD_GRENADE || mod == MOD_GRENADE_LAUNCHER || mod == MOD_DYNAMITE || mod == MOD_GPG40 || mod == MOD_M7 || mod == MOD_LANDMINE ) { targ->client->ps.velocity[2] *= 2.f; // gimme air baby! targ->client->ps.groundEntityNum = ENTITYNUM_NONE; // flying high! } else if( mod == MOD_ROCKET ) { targ->client->ps.velocity[2] *= .75f; // but not to the moon please! targ->client->ps.groundEntityNum = ENTITYNUM_NONE; // flying high! }*/ if (targ == attacker && !( mod != MOD_ROCKET && mod != MOD_GRENADE && mod != MOD_GRENADE_LAUNCHER && mod != MOD_DYNAMITE && mod != MOD_GPG40 && mod != MOD_M7 && mod != MOD_LANDMINE )) { targ->client->ps.velocity[2] *= 0.25; } // set the timer so that the other client can't cancel // out the movement immediately if ( !targ->client->ps.pm_time ) { int t; t = knockback * 2; if ( t < 50 ) { t = 50; } if ( t > 200 ) { t = 200; } targ->client->ps.pm_time = t; targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; } } // check for completely getting out of the damage if ( !(dflags & DAMAGE_NO_PROTECTION) ) { // if TF_NO_FRIENDLY_FIRE is set, don't do damage to the target // if the attacker was on the same team if ( targ != attacker && OnSameTeam (targ, attacker) ) { if ( (g_gamestate.integer != GS_PLAYING && match_warmupDamage.integer == 1)) { return; } else if (!g_friendlyFire.integer) { return; } } } // add to the attacker's hit counter if ( attacker->client && targ != attacker && targ->health > 0 ) { if ( OnSameTeam( targ, attacker ) ) { attacker->client->ps.persistant[PERS_HITS] -= damage; } else { attacker->client->ps.persistant[PERS_HITS] += damage; } } if ( damage < 1 ) { damage = 1; } take = damage; save = 0; // adrenaline junkie! if( targ->client && targ->client->ps.powerups[PW_ADRENALINE] ) { take *= .5f; } // save some from flak jacket if( targ->client && targ->client->sess.skill[SK_EXPLOSIVES_AND_CONSTRUCTION] >= 4 && targ->client->sess.playerType == PC_ENGINEER ) { if( mod == MOD_GRENADE || mod == MOD_GRENADE_LAUNCHER || mod == MOD_ROCKET || mod == MOD_GRENADE_PINEAPPLE || mod == MOD_MAPMORTAR || mod == MOD_MAPMORTAR_SPLASH || mod == MOD_EXPLOSIVE || mod == MOD_LANDMINE || mod == MOD_GPG40 || mod == MOD_M7 || mod == MOD_SATCHEL || mod == MOD_ARTY || mod == MOD_AIRSTRIKE || mod == MOD_DYNAMITE || mod == MOD_MORTAR || mod == MOD_PANZERFAUST || mod == MOD_MAPMORTAR ) { take -= take * .5f; } } headShot = IsHeadShot(targ, dir, point, mod); if ( headShot ) { if( take * 2 < 50 ) // head shots, all weapons, do minimum 50 points damage take = 50; else take *= 2; // sniper rifles can do full-kill (and knock into limbo) if( dflags & DAMAGE_DISTANCEFALLOFF ) { vec_t dist; vec3_t shotvec; VectorSubtract( point, muzzleTrace, shotvec ); dist = VectorLength( shotvec ); if( dist > 1500.f ) { if( dist > 2500.f ) { take *= 0.2f; } else { float scale = 1.f - 0.2f * (1000.f / (dist - 1000.f)); take *= scale; } } } if( !(targ->client->ps.eFlags & EF_HEADSHOT) ) { // only toss hat on first headshot G_AddEvent( targ, EV_LOSE_HAT, DirToByte(dir) ); if( mod != MOD_K43_SCOPE && mod != MOD_GARAND_SCOPE ) { take *= .8f; // helmet gives us some protection } } targ->client->ps.eFlags |= EF_HEADSHOT; // OSP - Record the headshot if(client && attacker && attacker->client #ifndef DEBUG_STATS && attacker->client->sess.sessionTeam != targ->client->sess.sessionTeam #endif ) { G_addStatsHeadShot(attacker, mod); } if( g_debugBullets.integer ) { trap_SendServerCommand( attacker-g_entities, "print \"Head Shot\n\"\n"); } G_LogRegionHit( attacker, HR_HEAD ); hr = HR_HEAD; } else if ( IsLegShot(targ, dir, point, mod) ) { G_LogRegionHit( attacker, HR_LEGS ); hr = HR_LEGS; if( g_debugBullets.integer ) { trap_SendServerCommand( attacker-g_entities, "print \"Leg Shot\n\"\n"); } } else if ( IsArmShot(targ, attacker, point, mod) ) { G_LogRegionHit( attacker, HR_ARMS ); hr = HR_ARMS; if( g_debugBullets.integer ) { trap_SendServerCommand( attacker-g_entities, "print \"Arm Shot\n\"\n"); } } else if (targ->client && targ->health > 0 && IsHeadShotWeapon( mod ) ) { G_LogRegionHit( attacker, HR_BODY ); hr = HR_BODY; if( g_debugBullets.integer ) { trap_SendServerCommand( attacker-g_entities, "print \"Body Shot\n\"\n"); } } #ifndef DEBUG_STATS if ( g_debugDamage.integer ) #endif { G_Printf( "client:%i health:%i damage:%i mod:%s\n", targ->s.number, targ->health, take, modNames[mod] ); } // add to the damage inflicted on a player this frame // the total will be turned into screen blends and view angle kicks // at the end of the frame if ( client ) { if ( attacker ) { client->ps.persistant[PERS_ATTACKER] = attacker->s.number; } else { client->ps.persistant[PERS_ATTACKER] = ENTITYNUM_WORLD; } client->damage_blood += take; client->damage_knockback += knockback; if ( dir ) { VectorCopy ( dir, client->damage_from ); client->damage_fromWorld = qfalse; } else { VectorCopy ( targ->r.currentOrigin, client->damage_from ); client->damage_fromWorld = qtrue; } } // See if it's the player hurting the emeny flag carrier // Team_CheckHurtCarrier(targ, attacker); if (targ->client) { // set the last client who damaged the target targ->client->lasthurt_client = attacker->s.number; targ->client->lasthurt_mod = mod; } // do the damage if( take ) { targ->health -= take; // Gordon: don't ever gib POWS if( ( targ->health <= 0 ) && ( targ->r.svFlags & SVF_POW ) ) { targ->health = -1; } // Ridah, can't gib with bullet weapons (except VENOM) // Arnout: attacker == inflictor can happen in other cases as well! (movers trying to gib things) //if ( attacker == inflictor && targ->health <= GIB_HEALTH) { if( targ->health <= GIB_HEALTH ) { if( !G_WeaponIsExplosive( mod ) ) { targ->health = GIB_HEALTH + 1; } } // JPW NERVE overcome previous chunk of code for making grenades work again // if ((take > 190)) // 190 is greater than 2x mauser headshot, so headshots don't gib // Arnout: only player entities! messes up ents like func_constructibles and func_explosives otherwise if( ( (targ->s.number < MAX_CLIENTS) && (take > 190) ) && !(targ->r.svFlags & SVF_POW) ) { targ->health = GIB_HEALTH - 1; } if( targ->s.eType == ET_MOVER && !Q_stricmp( targ->classname, "script_mover" ) ) { targ->s.dl_intensity = 255.f * (targ->health / (float)targ->count); // send it to the client } //G_Printf("health at: %d\n", targ->health); if( targ->health <= 0 ) { if( client && !wasAlive ) { targ->flags |= FL_NO_KNOCKBACK; // OSP - special hack to not count attempts for body gibbage if( targ->client->ps.pm_type == PM_DEAD ) { G_addStats(targ, attacker, take, mod); } if( (targ->health < FORCE_LIMBO_HEALTH) && (targ->health > GIB_HEALTH) ) { limbo(targ, qtrue); } // xkan, 1/13/2003 - record the time we died. if (!client->deathTime) client->deathTime = level.time; } else { targ->sound1to2 = hr; targ->sound2to1 = mod; targ->sound2to3 = (dflags & DAMAGE_RADIUS) ? 1 : 0; if( client ) { if( G_GetTeamFromEntity( inflictor ) != G_GetTeamFromEntity( targ ) ) { G_AddKillSkillPoints( attacker, mod, hr, (dflags & DAMAGE_RADIUS) ); } } if( targ->health < -999 ) { targ->health = -999; } targ->enemy = attacker; targ->deathType = mod; // Ridah, mg42 doesn't have die func (FIXME) if( targ->die ) { // Kill the entity. Note that this funtion can set ->die to another // function pointer, so that next time die is applied to the dead body. targ->die( targ, inflictor, attacker, take, mod ); // OSP - kill stats in player_die function } if( targ->s.eType == ET_MOVER && !Q_stricmp( targ->classname, "script_mover" ) && (targ->spawnflags & 8) ) { return; // reseructable script mover doesn't unlink itself but we don't want a second death script to be called } // if we freed ourselves in death function if (!targ->inuse) return; // RF, entity scripting if (targ->health <= 0) { // might have revived itself in death function if ((targ->s.eType != ET_CONSTRUCTIBLE && targ->s.eType != ET_EXPLOSIVE) || (targ->s.eType == ET_CONSTRUCTIBLE && !targ->desstages)) { // call manually if using desstages G_Script_ScriptEvent( targ, "death", "" ); } } } } else if ( targ->pain ) { if (dir) { // Ridah, had to add this to fix NULL dir crash VectorCopy (dir, targ->rotate); VectorCopy (point, targ->pos3); // this will pass loc of hit } else { VectorClear( targ->rotate ); VectorClear( targ->pos3 ); } targ->pain (targ, attacker, take, point); } else { // OSP - update weapon/dmg stats G_addStats(targ, attacker, take, mod); // OSP } // RF, entity scripting G_Script_ScriptEvent( targ, "pain", va("%d %d", targ->health, targ->health+take) ); // Ridah, this needs to be done last, incase the health is altered in one of the event calls if ( targ->client ) { targ->client->ps.stats[STAT_HEALTH] = targ->health; } } }
void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, vec3_t dir, vec3_t point, int damage, int dflags, int mod ) { gclient_t *client; int take; int save; int asave; int knockback; if ( !targ->takedamage ) { return; } // the intermission has allready been qualified for, so don't // allow any extra scoring if ( level.intermissionQueued || g_gamestate.integer != GS_PLAYING ) { return; } if ( !inflictor ) { inflictor = &g_entities[ENTITYNUM_WORLD]; } if ( !attacker ) { attacker = &g_entities[ENTITYNUM_WORLD]; } // JPW NERVE if ( ( targ->waterlevel >= 3 ) && ( mod == MOD_FLAMETHROWER ) ) { return; } // jpw // shootable doors / buttons don't actually have any health if ( targ->s.eType == ET_MOVER && !( targ->aiName ) && !( targ->isProp ) && !targ->scriptName ) { if ( targ->use && targ->moverState == MOVER_POS1 ) { targ->use( targ, inflictor, attacker ); } return; } if ( targ->s.eType == ET_MOVER && targ->aiName && !( targ->isProp ) && !targ->scriptName ) { switch ( mod ) { case MOD_GRENADE: case MOD_GRENADE_SPLASH: case MOD_ROCKET: case MOD_ROCKET_SPLASH: break; default: return; // no damage from other weapons } } else if ( targ->s.eType == ET_EXPLOSIVE ) { // 32 Explosive // 64 Dynamite only if ( ( targ->spawnflags & 32 ) || ( targ->spawnflags & 64 ) ) { switch ( mod ) { case MOD_GRENADE: case MOD_GRENADE_SPLASH: case MOD_ROCKET: case MOD_ROCKET_SPLASH: case MOD_AIRSTRIKE: case MOD_GRENADE_PINEAPPLE: case MOD_MORTAR: case MOD_MORTAR_SPLASH: case MOD_EXPLOSIVE: if ( targ->spawnflags & 64 ) { return; } break; case MOD_DYNAMITE: case MOD_DYNAMITE_SPLASH: break; default: return; } } } client = targ->client; if ( client ) { if ( client->noclip ) { return; } } if ( !dir ) { dflags |= DAMAGE_NO_KNOCKBACK; } else { VectorNormalize( dir ); } knockback = damage; if ( knockback > 200 ) { knockback = 200; } if ( targ->flags & FL_NO_KNOCKBACK ) { knockback = 0; } if ( dflags & DAMAGE_NO_KNOCKBACK ) { knockback = 0; } // figure momentum add, even if the damage won't be taken if ( knockback && targ->client && ( g_friendlyFire.integer || !OnSameTeam( targ, attacker ) ) ) { vec3_t kvel; float mass; mass = 200; if ( mod == MOD_LIGHTNING && !( ( level.time + targ->s.number * 50 ) % 400 ) ) { knockback = 60; dir[2] = 0.3; } VectorScale( dir, g_knockback.value * (float)knockback / mass, kvel ); VectorAdd( targ->client->ps.velocity, kvel, targ->client->ps.velocity ); if ( targ == attacker && !( mod != MOD_ROCKET && mod != MOD_ROCKET_SPLASH && mod != MOD_GRENADE && mod != MOD_GRENADE_SPLASH && mod != MOD_DYNAMITE ) ) { targ->client->ps.velocity[2] *= 0.25; } // set the timer so that the other client can't cancel // out the movement immediately if ( !targ->client->ps.pm_time ) { int t; t = knockback * 2; if ( t < 50 ) { t = 50; } if ( t > 200 ) { t = 200; } targ->client->ps.pm_time = t; targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; } } // check for completely getting out of the damage if ( !( dflags & DAMAGE_NO_PROTECTION ) ) { // if TF_NO_FRIENDLY_FIRE is set, don't do damage to the target // if the attacker was on the same team if ( targ != attacker && OnSameTeam( targ, attacker ) ) { if ( !g_friendlyFire.integer ) { return; } } // check for godmode if ( targ->flags & FL_GODMODE ) { return; } // RF, warzombie defense position is basically godmode for the time being if ( targ->flags & FL_DEFENSE_GUARD ) { return; } // check for invulnerability // (SA) moved from below so DAMAGE_NO_PROTECTION will still work if ( client && client->ps.powerups[PW_INVULNERABLE] ) { //----(SA) added return; } } // battlesuit protects from all radius damage (but takes knockback) // and protects 50% against all damage if ( client && client->ps.powerups[PW_BATTLESUIT] ) { G_AddEvent( targ, EV_POWERUP_BATTLESUIT, 0 ); if ( dflags & DAMAGE_RADIUS ) { return; } damage *= 0.5; } // add to the attacker's hit counter if ( attacker->client && targ != attacker && targ->health > 0 ) { if ( OnSameTeam( targ, attacker ) ) { attacker->client->ps.persistant[PERS_HITS] -= damage; } else { attacker->client->ps.persistant[PERS_HITS] += damage; } } if ( damage < 1 ) { damage = 1; } take = damage; save = 0; // save some from armor asave = CheckArmor( targ, take, dflags ); take -= asave; if ( IsHeadShot( targ, qfalse, dir, point, mod ) ) { if ( take * 2 < 50 ) { // head shots, all weapons, do minimum 50 points damage take = 50; } else { take *= 2; // sniper rifles can do full-kill (and knock into limbo) } if ( !( targ->client->ps.eFlags & EF_HEADSHOT ) ) { // only toss hat on first headshot G_AddEvent( targ, EV_LOSE_HAT, DirToByte( dir ) ); } targ->client->ps.eFlags |= EF_HEADSHOT; } if ( g_debugDamage.integer ) { G_Printf( "client:%i health:%i damage:%i armor:%i\n", targ->s.number, targ->health, take, asave ); } // add to the damage inflicted on a player this frame // the total will be turned into screen blends and view angle kicks // at the end of the frame if ( client ) { if ( attacker ) { client->ps.persistant[PERS_ATTACKER] = attacker->s.number; } else { client->ps.persistant[PERS_ATTACKER] = ENTITYNUM_WORLD; } client->damage_armor += asave; client->damage_blood += take; client->damage_knockback += knockback; if ( dir ) { VectorCopy( dir, client->damage_from ); client->damage_fromWorld = qfalse; } else { VectorCopy( targ->r.currentOrigin, client->damage_from ); client->damage_fromWorld = qtrue; } } // See if it's the player hurting the emeny flag carrier Team_CheckHurtCarrier( targ, attacker ); if ( targ->client ) { // set the last client who damaged the target targ->client->lasthurt_client = attacker->s.number; targ->client->lasthurt_mod = mod; } // do the damage if ( take ) { targ->health = targ->health - take; // Ridah, can't gib with bullet weapons (except VENOM) if ( mod != MOD_VENOM && attacker == inflictor && targ->health <= GIB_HEALTH ) { if ( targ->aiCharacter != AICHAR_ZOMBIE ) { // zombie needs to be able to gib so we can kill him (although he doesn't actually GIB, he just dies) targ->health = GIB_HEALTH + 1; } } // JPW NERVE overcome previous chunk of code for making grenades work again if ( ( g_gametype.integer != GT_SINGLE_PLAYER ) && ( take > 190 ) ) { // 190 is greater than 2x mauser headshot, so headshots don't gib targ->health = GIB_HEALTH - 1; } // jpw //G_Printf("health at: %d\n", targ->health); if ( targ->health <= 0 ) { if ( client ) { targ->flags |= FL_NO_KNOCKBACK; // JPW NERVE -- repeated shooting sends to limbo if ( g_gametype.integer >= GT_WOLF ) { if ( ( targ->health < FORCE_LIMBO_HEALTH ) && ( targ->health > GIB_HEALTH ) && ( !( targ->client->ps.pm_flags & PMF_LIMBO ) ) ) { limbo( targ, qtrue ); } } // jpw } if ( targ->health < -999 ) { targ->health = -999; } targ->enemy = attacker; if ( targ->die ) { // Ridah, mg42 doesn't have die func (FIXME) targ->die( targ, inflictor, attacker, take, mod ); } // if we freed ourselves in death function if ( !targ->inuse ) { return; } // RF, entity scripting if ( targ->s.number >= MAX_CLIENTS && targ->health <= 0 ) { // might have revived itself in death function G_Script_ScriptEvent( targ, "death", "" ); } } else if ( targ->pain ) { if ( dir ) { // Ridah, had to add this to fix NULL dir crash VectorCopy( dir, targ->rotate ); VectorCopy( point, targ->pos3 ); // this will pass loc of hit } else { VectorClear( targ->rotate ); VectorClear( targ->pos3 ); } targ->pain( targ, attacker, take, point ); // RF, entity scripting if ( targ->s.number >= MAX_CLIENTS ) { G_Script_ScriptEvent( targ, "pain", va( "%d %d", targ->health, targ->health + take ) ); } } //G_ArmorDamage(targ); //----(SA) moved out to separate routine // Ridah, this needs to be done last, incase the health is altered in one of the event calls if ( targ->client ) { targ->client->ps.stats[STAT_HEALTH] = targ->health; } } }
void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) { gentity_t *ent; // TTimo might be used uninitialized int contents = 0; int killer; int i; const char *killerName, *obit; qboolean nogib = qtrue; gitem_t *item = NULL; // JPW NERVE for flag drop vec3_t launchvel,launchspot; // JPW NERVE gentity_t *flag; // JPW NERVE if ( self->client->ps.pm_type == PM_DEAD ) { return; } if ( level.intermissiontime ) { return; } self->client->ps.pm_type = PM_DEAD; G_AddEvent( self, EV_STOPSTREAMINGSOUND, 0 ); if ( attacker ) { killer = attacker->s.number; if ( attacker->client ) { killerName = attacker->client->pers.netname; } else { killerName = "<non-client>"; } } else { killer = ENTITYNUM_WORLD; killerName = "<world>"; } if ( killer < 0 || killer >= MAX_CLIENTS ) { killer = ENTITYNUM_WORLD; killerName = "<world>"; } 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 ( g_gametype.integer != GT_SINGLE_PLAYER ) { if ( ( self->client->ps.grenadeTimeLeft ) && ( self->s.weapon != WP_DYNAMITE ) ) { launchvel[0] = crandom(); launchvel[1] = crandom(); launchvel[2] = random(); VectorScale( launchvel, 160, launchvel ); VectorCopy( self->r.currentOrigin, launchspot ); launchspot[2] += 40; fire_grenade( self, launchspot, launchvel, self->s.weapon ); } } // jpw if ( attacker && attacker->client ) { if ( attacker == self || OnSameTeam( self, attacker ) ) { // DHM - Nerve :: Complaint lodging if ( attacker != self && level.warmupTime <= 0 ) { if ( attacker->client->pers.localClient ) { trap_SendServerCommand( self - g_entities, "complaint -4" ); } else { 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; } } // dhm // JPW NERVE if ( g_gametype.integer >= GT_WOLF ) { // high penalty to offset medic heal AddScore( attacker, WOLF_FRIENDLY_PENALTY ); } else { // jpw AddScore( attacker, -1 ); } } else { // JPW NERVE -- mostly added as conveneience so we can tweak from the #defines all in one place if ( g_gametype.integer >= GT_WOLF ) { AddScore( attacker, WOLF_FRAG_BONUS ); } else { // jpw AddScore( attacker, 1 ); } attacker->client->lastKillTime = level.time; } } else { AddScore( self, -1 ); } // Add team bonuses Team_FragBonuses( self, inflictor, attacker ); // if client is in a nodrop area, don't drop anything // JPW NERVE new drop behavior if ( g_gametype.integer == GT_SINGLE_PLAYER ) { // only drop here in single player; in multiplayer, drop @ limbo contents = trap_PointContents( self->r.currentOrigin, -1 ); if ( !( contents & CONTENTS_NODROP ) ) { TossClientItems( self ); } } // drop flag regardless if ( g_gametype.integer != GT_SINGLE_PLAYER ) { 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 ) { launchvel[0] = crandom() * 20; launchvel[1] = crandom() * 20; launchvel[2] = 10 + random() * 10; 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 ) { if ( self->client->sess.sessionTeam == TEAM_RED ) { if ( random() > 0.5 ) { G_AddEvent( self, EV_GENERAL_SOUND, G_SoundIndex( "sound/multiplayer/axis/g-medic2.wav" ) ); } else { G_AddEvent( self, EV_GENERAL_SOUND, G_SoundIndex( "sound/multiplayer/axis/g-medic3.wav" ) ); } } else { if ( random() > 0.5 ) { G_AddEvent( self, EV_GENERAL_SOUND, G_SoundIndex( "sound/multiplayer/allies/a-medic3.wav" ) ); } else { G_AddEvent( self, EV_GENERAL_SOUND, G_SoundIndex( "sound/multiplayer/allies/a-medic2.wav" ) ); } } } } } // jpw 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.maxclients ; i++ ) { gclient_t *client; client = &level.clients[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 + i ); } } self->takedamage = qtrue; // can still be gibbed self->r.contents = CONTENTS_CORPSE; self->s.powerups = 0; // JPW NERVE -- only corpse in SP; in MP, need CONTENTS_BODY so medic can operate if ( g_gametype.integer == GT_SINGLE_PLAYER ) { self->s.weapon = WP_NONE; self->s.angles[0] = 0; } else { self->client->limboDropWeapon = self->s.weapon; // store this so it can be dropped in limbo } // jpw self->s.angles[2] = 0; LookAtKiller( self, inflictor, attacker ); VectorCopy( self->s.angles, self->client->ps.viewangles ); self->s.loopSound = 0; trap_UnlinkEntity( self ); self->r.maxs[2] = 0; self->client->ps.maxs[2] = 0; 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.time + 800; // remove powerups memset( self->client->ps.powerups, 0, sizeof( self->client->ps.powerups ) ); // never gib in a nodrop 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; } // JPW NERVE for medic self->client->medicHealAmt = 0; // jpw // DHM - Play death animation, and set pm_time to delay 'fallen' animation self->client->ps.pm_time = BG_AnimScriptEvent( &self->client->ps, ANIM_ET_DEATH, qfalse, qtrue ); G_AddEvent( self, EV_DEATH1 + 1, killer ); // the body can still be gibbed self->die = body_die; } trap_LinkEntity( self ); if ( g_gametype.integer >= GT_WOLF && meansOfDeath == MOD_SUICIDE ) { limbo( self, qtrue ); } }
/* ============== ClientThink This will be called once for each client frame, which will usually be a couple times for each server frame on fast clients. If "g_synchronousClients 1" is set, this will be called exactly once for each server frame, which makes for smooth demo recording. ============== */ void ClientThink_real( gentity_t *ent ) { gclient_t *client; pmove_t pm; // vec3_t oldOrigin; int oldEventSequence; int msec; usercmd_t *ucmd; int monsterslick = 0; // JPW NERVE int i; vec3_t muzzlebounce; gitem_t *item; gentity_t *ent2; vec3_t velocity, org, offset; vec3_t angles,mins,maxs; int weapon; trace_t tr; // jpw // Rafael wolfkick //int validkick; //static int wolfkicktimer = 0; client = ent->client; // don't think if the client is not yet connected (and thus not yet spawned in) if ( client->pers.connected != CON_CONNECTED ) { return; } if ( client->cameraPortal ) { G_SetOrigin( client->cameraPortal, client->ps.origin ); trap_LinkEntity( client->cameraPortal ); VectorCopy( client->cameraOrigin, client->cameraPortal->s.origin2 ); } // mark the time, so the connection sprite can be removed ucmd = &ent->client->pers.cmd; ent->client->ps.identifyClient = ucmd->identClient; // NERVE - SMF // JPW NERVE -- update counter for capture & hold display if ( g_gametype.integer == GT_WOLF_CPH ) { client->ps.stats[STAT_CAPTUREHOLD_RED] = level.capturetimes[TEAM_RED]; client->ps.stats[STAT_CAPTUREHOLD_BLUE] = level.capturetimes[TEAM_BLUE]; } // jpw // sanity check the command time to prevent speedup cheating if ( ucmd->serverTime > level.time + 200 ) { ucmd->serverTime = level.time + 200; // G_Printf("serverTime <<<<<\n" ); } if ( ucmd->serverTime < level.time - 1000 ) { ucmd->serverTime = level.time - 1000; // G_Printf("serverTime >>>>>\n" ); } msec = ucmd->serverTime - client->ps.commandTime; // following others may result in bad times, but we still want // to check for follow toggles if ( msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW ) { return; /* // Ridah, fixes savegame timing issue if (msec < -100) { client->ps.commandTime = ucmd->serverTime - 100; msec = 100; } else { return; } */ // done. } if ( msec > 200 ) { msec = 200; } if ( pmove_msec.integer < 8 ) { trap_Cvar_Set( "pmove_msec", "8" ); } else if ( pmove_msec.integer > 33 ) { trap_Cvar_Set( "pmove_msec", "33" ); } if ( pmove_fixed.integer || client->pers.pmoveFixed ) { ucmd->serverTime = ( ( ucmd->serverTime + pmove_msec.integer - 1 ) / pmove_msec.integer ) * pmove_msec.integer; //if (ucmd->serverTime - client->ps.commandTime <= 0) // return; } // // check for exiting intermission // if ( level.intermissiontime ) { ClientIntermissionThink( client ); return; } // spectators don't do much // DHM - Nerve :: In limbo use SpectatorThink if ( client->sess.sessionTeam == TEAM_SPECTATOR || client->ps.pm_flags & PMF_LIMBO ) { if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { return; } SpectatorThink( ent, ucmd ); return; } // JPW NERVE do some time-based muzzle flip -- this never gets touched in single player (see g_weapon.c) // #define RIFLE_SHAKE_TIME 150 // JPW NERVE this one goes with the commented out old damped "realistic" behavior below #define RIFLE_SHAKE_TIME 300 // per Id request, longer recoil time if ( client->sniperRifleFiredTime ) { if ( level.time - client->sniperRifleFiredTime > RIFLE_SHAKE_TIME ) { client->sniperRifleFiredTime = 0; } else { VectorCopy( client->ps.viewangles,muzzlebounce ); // JPW per Id request, longer recoil time muzzlebounce[PITCH] -= 2 * cos( 2.5 * ( level.time - client->sniperRifleFiredTime ) / RIFLE_SHAKE_TIME ); muzzlebounce[YAW] += 0.5*client->sniperRifleMuzzleYaw*cos( 1.0 - ( level.time - client->sniperRifleFiredTime ) * 3 / RIFLE_SHAKE_TIME ); muzzlebounce[PITCH] -= 0.25 * random() * ( 1.0f - ( level.time - client->sniperRifleFiredTime ) / RIFLE_SHAKE_TIME ); muzzlebounce[YAW] += 0.5 * crandom() * ( 1.0f - ( level.time - client->sniperRifleFiredTime ) / RIFLE_SHAKE_TIME ); SetClientViewAngle( ent,muzzlebounce ); } } if ( client->ps.stats[STAT_PLAYER_CLASS] == PC_MEDIC ) { if ( level.time > client->ps.powerups[PW_REGEN] + 5000 ) { client->ps.powerups[PW_REGEN] = level.time; } } // also update weapon recharge time // JPW drop button drops secondary weapon so new one can be picked up // TTimo explicit braces to avoid ambiguous 'else' if ( g_gametype.integer != GT_SINGLE_PLAYER ) { if ( ucmd->wbuttons & WBUTTON_DROP ) { if ( !client->dropWeaponTime ) { client->dropWeaponTime = 1; // just latch it for now if ( ( client->ps.stats[STAT_PLAYER_CLASS] == PC_SOLDIER ) || ( client->ps.stats[STAT_PLAYER_CLASS] == PC_LT ) ) { for ( i = 0; i < MAX_WEAPS_IN_BANK_MP; i++ ) { weapon = weapBanksMultiPlayer[3][i]; if ( COM_BitCheck( client->ps.weapons,weapon ) ) { item = BG_FindItemForWeapon( weapon ); VectorCopy( client->ps.viewangles, angles ); // clamp pitch if ( angles[PITCH] < -30 ) { angles[PITCH] = -30; } else if ( angles[PITCH] > 30 ) { angles[PITCH] = 30; } AngleVectors( angles, velocity, NULL, NULL ); VectorScale( velocity, 64, offset ); offset[2] += client->ps.viewheight / 2; VectorScale( velocity, 75, velocity ); velocity[2] += 50 + random() * 35; VectorAdd( client->ps.origin,offset,org ); VectorSet( mins, -ITEM_RADIUS, -ITEM_RADIUS, 0 ); VectorSet( maxs, ITEM_RADIUS, ITEM_RADIUS, 2 * ITEM_RADIUS ); trap_Trace( &tr, client->ps.origin, mins, maxs, org, ent->s.number, MASK_SOLID ); VectorCopy( tr.endpos, org ); ent2 = LaunchItem( item, org, velocity, client->ps.clientNum ); COM_BitClear( client->ps.weapons,weapon ); if ( weapon == WP_MAUSER ) { COM_BitClear( client->ps.weapons,WP_SNIPERRIFLE ); } // Clear out empty weapon, change to next best weapon G_AddEvent( ent, EV_NOAMMO, 0 ); i = MAX_WEAPS_IN_BANK_MP; // show_bug.cgi?id=568 if ( client->ps.weapon == weapon ) { client->ps.weapon = 0; } ent2->count = client->ps.ammoclip[BG_FindClipForWeapon( weapon )]; ent2->item->quantity = client->ps.ammoclip[BG_FindClipForWeapon( weapon )]; client->ps.ammoclip[BG_FindClipForWeapon( weapon )] = 0; } } } } } else { client->dropWeaponTime = 0; } } // jpw // check for inactivity timer, but never drop the local client of a non-dedicated server if ( !ClientInactivityTimer( client ) ) { return; } if ( reloading || client->cameraPortal ) { ucmd->buttons = 0; ucmd->forwardmove = 0; ucmd->rightmove = 0; ucmd->upmove = 0; ucmd->wbuttons = 0; ucmd->wolfkick = 0; if ( client->cameraPortal ) { client->ps.pm_type = PM_FREEZE; } } else if ( client->noclip ) { client->ps.pm_type = PM_NOCLIP; } else if ( client->ps.stats[STAT_HEALTH] <= 0 ) { client->ps.pm_type = PM_DEAD; } else { client->ps.pm_type = PM_NORMAL; } // set parachute anim condition flag BG_UpdateConditionValue( ent->s.number, ANIM_COND_PARACHUTE, ( ent->flags & FL_PARACHUTE ) != 0, qfalse ); // all playing clients are assumed to be in combat mode if ( !client->ps.aiChar ) { client->ps.aiState = AISTATE_COMBAT; } client->ps.gravity = g_gravity.value; // set speed client->ps.speed = g_speed.value; if ( client->ps.powerups[PW_HASTE] ) { client->ps.speed *= 1.3; } // set up for pmove oldEventSequence = client->ps.eventSequence; client->currentAimSpreadScale = (float)client->ps.aimSpreadScale / 255.0; memset( &pm, 0, sizeof( pm ) ); pm.ps = &client->ps; pm.pmext = &client->pmext; pm.cmd = *ucmd; pm.oldcmd = client->pers.oldcmd; if ( pm.ps->pm_type == PM_DEAD ) { pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; // DHM-Nerve added:: EF_DEAD is checked for in Pmove functions, but wasn't being set // until after Pmove pm.ps->eFlags |= EF_DEAD; // dhm-Nerve end } else { pm.tracemask = MASK_PLAYERSOLID; } // MrE: always use capsule for AI and player //pm.trace = trap_TraceCapsule;//trap_Trace; //DHM - Nerve :: We've gone back to using normal bbox traces pm.trace = trap_Trace; pm.pointcontents = trap_PointContents; pm.debugLevel = g_debugMove.integer; pm.noFootsteps = ( g_dmflags.integer & DF_NO_FOOTSTEPS ) > 0; pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed; pm.pmove_msec = pmove_msec.integer; pm.noWeapClips = ( g_dmflags.integer & DF_NO_WEAPRELOAD ) > 0; if ( ent->aiCharacter && AICast_NoReload( ent->s.number ) ) { pm.noWeapClips = qtrue; // ensure AI characters don't use clips if they're not supposed to. } // Ridah // if (ent->r.svFlags & SVF_NOFOOTSTEPS) // pm.noFootsteps = qtrue; VectorCopy( client->ps.origin, client->oldOrigin ); // NERVE - SMF pm.gametype = g_gametype.integer; pm.ltChargeTime = g_LTChargeTime.integer; pm.soldierChargeTime = g_soldierChargeTime.integer; pm.engineerChargeTime = g_engineerChargeTime.integer; pm.medicChargeTime = g_medicChargeTime.integer; // -NERVE - SMF monsterslick = Pmove( &pm ); if ( monsterslick && !( ent->flags & FL_NO_MONSTERSLICK ) ) { //vec3_t dir; //vec3_t kvel; //vec3_t forward; // TTimo gcc: might be used unitialized in this function float angle = 0.0f; qboolean bogus = qfalse; // NE if ( ( monsterslick & SURF_MONSLICK_N ) && ( monsterslick & SURF_MONSLICK_E ) ) { angle = 45; } // NW else if ( ( monsterslick & SURF_MONSLICK_N ) && ( monsterslick & SURF_MONSLICK_W ) ) { angle = 135; } // N else if ( monsterslick & SURF_MONSLICK_N ) { angle = 90; } // SE else if ( ( monsterslick & SURF_MONSLICK_S ) && ( monsterslick & SURF_MONSLICK_E ) ) { angle = 315; } // SW else if ( ( monsterslick & SURF_MONSLICK_S ) && ( monsterslick & SURF_MONSLICK_W ) ) { angle = 225; } // S else if ( monsterslick & SURF_MONSLICK_S ) { angle = 270; } // E else if ( monsterslick & SURF_MONSLICK_E ) { angle = 0; } // W else if ( monsterslick & SURF_MONSLICK_W ) { angle = 180; } else { bogus = qtrue; } } // server cursor hints if ( ent->lastHintCheckTime < level.time ) { G_CheckForCursorHints( ent ); ent->lastHintCheckTime = level.time + FRAMETIME; } // DHM - Nerve :: Set animMovetype to 1 if ducking if ( ent->client->ps.pm_flags & PMF_DUCKED ) { ent->s.animMovetype = 1; } else { ent->s.animMovetype = 0; } // save results of pmove if ( ent->client->ps.eventSequence != oldEventSequence ) { ent->eventTime = level.time; ent->r.eventTime = level.time; } // Ridah, fixes jittery zombie movement if ( g_smoothClients.integer ) { BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); } else { BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); } if ( !( ent->client->ps.eFlags & EF_FIRING ) ) { client->fireHeld = qfalse; // for grapple } // // // use the precise origin for linking // VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); // // // use the snapped origin for linking so it matches client predicted versions VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); VectorCopy( pm.mins, ent->r.mins ); VectorCopy( pm.maxs, ent->r.maxs ); ent->waterlevel = pm.waterlevel; ent->watertype = pm.watertype; // execute client events ClientEvents( ent, oldEventSequence ); // link entity now, after any personal teleporters have been used trap_LinkEntity( ent ); if ( !ent->client->noclip ) { G_TouchTriggers( ent ); } // NOTE: now copy the exact origin over otherwise clients can be snapped into solid VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); // store the client's current position for antilag traces G_StoreClientPosition( ent ); // touch other objects ClientImpacts( ent, &pm ); // save results of triggers and client events if ( ent->client->ps.eventSequence != oldEventSequence ) { ent->eventTime = level.time; } // swap and latch button actions client->oldbuttons = client->buttons; client->buttons = ucmd->buttons; client->latched_buttons = client->buttons & ~client->oldbuttons; // client->latched_buttons |= client->buttons & ~client->oldbuttons; // FIXME:? (SA) MP method (causes problems for us. activate 'sticks') //----(SA) added client->oldwbuttons = client->wbuttons; client->wbuttons = ucmd->wbuttons; client->latched_wbuttons = client->wbuttons & ~client->oldwbuttons; // client->latched_wbuttons |= client->wbuttons & ~client->oldwbuttons; // FIXME:? (SA) MP method // Rafael - Activate // Ridah, made it a latched event (occurs on keydown only) if ( client->latched_buttons & BUTTON_ACTIVATE ) { Cmd_Activate_f( ent ); } if ( ent->flags & FL_NOFATIGUE ) { ent->client->ps.sprintTime = 20000; } // check for respawning if ( client->ps.stats[STAT_HEALTH] <= 0 ) { // DHM - Nerve if ( g_gametype.integer >= GT_WOLF ) { WolfFindMedic( ent ); } // dhm - end // wait for the attack button to be pressed if ( level.time > client->respawnTime ) { // forcerespawn is to prevent users from waiting out powerups if ( ( g_gametype.integer != GT_SINGLE_PLAYER ) && ( g_forcerespawn.integer > 0 ) && ( ( level.time - client->respawnTime ) > g_forcerespawn.integer * 1000 ) && ( !( ent->client->ps.pm_flags & PMF_LIMBO ) ) ) { // JPW NERVE // JPW NERVE if ( g_gametype.integer >= GT_WOLF ) { limbo( ent, qtrue ); } else { respawn( ent ); } // jpw return; } // DHM - Nerve :: Single player game respawns immediately as before, // but in multiplayer, require button press before respawn if ( g_gametype.integer == GT_SINGLE_PLAYER ) { respawn( ent ); } // NERVE - SMF - we want to only respawn on jump button now else if ( ( ucmd->upmove > 0 ) && ( !( ent->client->ps.pm_flags & PMF_LIMBO ) ) ) { // JPW NERVE // JPW NERVE if ( g_gametype.integer >= GT_WOLF ) { limbo( ent, qtrue ); } else { respawn( ent ); } // jpw } // dhm - Nerve :: end // NERVE - SMF - we want to immediately go to limbo mode if gibbed else if ( client->ps.stats[STAT_HEALTH] <= GIB_HEALTH && !( ent->client->ps.pm_flags & PMF_LIMBO ) ) { if ( g_gametype.integer >= GT_WOLF ) { limbo( ent, qfalse ); } else { respawn( ent ); } } // -NERVE - SMF } return; } // perform once-a-second actions ClientTimerActions( ent, msec ); }
void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, const vec3_t in_dir, vec3_t point, int damage, int dflags, int mod ) { gclient_t *client; int take; int save; int knockback; qboolean wasAlive; hitRegion_t hr = HR_NUM_HITREGIONS; int limbo_health; limbo_health = FORCE_LIMBO_HEALTH; if (!targ->takedamage) { return; } // the intermission has already been qualified for, so don't // allow any extra scoring // CHRUKER: b024 - Don't do damage if at warmup and warmupdamage is set to 'None' and the target is a client. if ( level.intermissionQueued || (cvars::gameState.ivalue != GS_PLAYING && match_warmupDamage.integer == 0 && targ->client)) { return; } if ( !inflictor ) { inflictor = &g_entities[ENTITYNUM_WORLD]; } if ( !attacker ) { attacker = &g_entities[ENTITYNUM_WORLD]; } // Arnout: invisible entities can't be damaged if( targ->entstate == STATE_INVISIBLE || targ->entstate == STATE_UNDERCONSTRUCTION ) { return; } // xkan, 12/23/2002 - was the bot alive before applying any damage? wasAlive = (targ->health > 0) ? qtrue : qfalse; // Arnout: combatstate if( targ->client && attacker && attacker->client && attacker != targ ) { if( cvars::gameState.ivalue == GS_PLAYING ) { if( !OnSameTeam( attacker, targ ) ) { targ->client->combatState = (combatstate_t)( targ->client->combatState | (1<<COMBATSTATE_DAMAGERECEIVED) ); attacker->client->combatState = (combatstate_t)( attacker->client->combatState | (1<<COMBATSTATE_DAMAGEDEALT) ); } } } // JPW NERVE if ((targ->waterlevel >= 3) && (mod == MOD_FLAMETHROWER)) return; // jpw // shootable doors / buttons don't actually have any health if ( targ->s.eType == ET_MOVER && !(targ->isProp) && !targ->scriptName) { if ( targ->use && targ->moverState == MOVER_POS1 ) { G_UseEntity( targ, inflictor, attacker ); } return; } // TAT 11/22/2002 // In the old code, this check wasn't done for props, so I put that check back in to make props_statue properly work // 4 means destructible if ( targ->s.eType == ET_MOVER && (targ->spawnflags & 4) && !targ->isProp ) { if( !G_WeaponIsExplosive( (meansOfDeath_t)mod ) ) { return; } // check for team if( G_GetTeamFromEntity( inflictor ) == G_GetTeamFromEntity( targ ) ) { return; } } else if ( targ->s.eType == ET_EXPLOSIVE ) { if( targ->parent && G_GetWeaponClassForMOD( (meansOfDeath_t)mod ) == 2 ) { return; } if( G_GetTeamFromEntity( inflictor ) == G_GetTeamFromEntity( targ ) ) { return; } if( G_GetWeaponClassForMOD( (meansOfDeath_t)mod ) < targ->constructibleStats.weaponclass ) { return; } } else if ( targ->s.eType == ET_MISSILE && targ->methodOfDeath == MOD_LANDMINE ) { if( targ->s.modelindex2 ) { if( G_WeaponIsExplosive( (meansOfDeath_t)mod ) ) { mapEntityData_t *mEnt; if((mEnt = G_FindMapEntityData(&mapEntityData[0], targ-g_entities)) != NULL) { G_FreeMapEntityData( &mapEntityData[0], mEnt ); } if((mEnt = G_FindMapEntityData(&mapEntityData[1], targ-g_entities)) != NULL) { G_FreeMapEntityData( &mapEntityData[1], mEnt ); } if( attacker && attacker->client ) { AddScore( attacker, 1 ); } G_ExplodeMissile(targ); } } return; } else if ( targ->s.eType == ET_CONSTRUCTIBLE ) { if( G_GetTeamFromEntity( inflictor ) == G_GetTeamFromEntity( targ ) ) { return; } if( G_GetWeaponClassForMOD( (meansOfDeath_t)mod ) < targ->constructibleStats.weaponclass ) { return; } //bani - fix #238 if ( mod == MOD_DYNAMITE ) { if( !( inflictor->etpro_misc_1 & 1 ) ) return; } } client = targ->client; if ( client ) { if ( client->noclip || ( client->ps.powerups[PW_INVULNERABLE] && !( dflags & DAMAGE_JAY_NO_PROTECTION ))) { return; } } // check for godmode if ( targ->flags & FL_GODMODE ) { return; } // ugly-ass code but we do this to make in_dir read-only vec3_t dir; if ( !in_dir ) { dflags |= DAMAGE_NO_KNOCKBACK; } else { VectorCopy( in_dir, dir ); VectorNormalize( dir ); } knockback = damage; if ( knockback > 200 ) { knockback = 200; } if ( targ->flags & FL_NO_KNOCKBACK ) { knockback = 0; } if ( dflags & DAMAGE_NO_KNOCKBACK ) { knockback = 0; } else if( dflags & DAMAGE_HALF_KNOCKBACK ) { knockback = int( knockback * 0.5f ); } // ydnar: set weapons means less knockback if( client && (client->ps.weapon == WP_MORTAR_SET || client->ps.weapon == WP_MOBILE_MG42_SET) ) knockback = int( knockback * 0.5f ); if( targ->client && g_friendlyFire.integer && OnSameTeam(targ, attacker) ) { knockback = 0; } // figure momentum add, even if the damage won't be taken if ( knockback && targ->client ) { vec3_t kvel; float mass; mass = 200; VectorScale (dir, g_knockback.value * (float)knockback / mass, kvel); VectorAdd (targ->client->ps.velocity, kvel, targ->client->ps.velocity); // From NoQuarter, I'm not sure I need this if ( attacker && attacker->client && ( targ->client->ps.groundEntityNum != ENTITYNUM_NONE || G_WeaponIsExplosive((meansOfDeath_t)mod) )){ targ->client->pmext.wasShoved = qtrue; targ->client->pmext.shover = attacker - g_entities; } if (targ == attacker && !( mod != MOD_ROCKET && mod != MOD_GRENADE && mod != MOD_GRENADE_LAUNCHER && mod != MOD_DYNAMITE && mod != MOD_GPG40 && mod != MOD_M7 && mod != MOD_LANDMINE )) { targ->client->ps.velocity[2] *= 0.25; } // set the timer so that the other client can't cancel // out the movement immediately if ( !targ->client->ps.pm_time ) { int t; t = knockback * 2; if ( t < 50 ) { t = 50; } if ( t > 200 ) { t = 200; } targ->client->ps.pm_time = t; targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; } } // skip damage if friendly fire is disabled if (!(dflags & DAMAGE_NO_PROTECTION) && targ != attacker && OnSameTeam( targ, attacker ) && !g_friendlyFire.integer) { return; } if (damage < 1) damage = 1; take = damage; save = 0; if ( attacker->client && targ->client && targ != attacker && targ->health > 0 ) { // Jaybird - Hitsounds // vsay "hold your fire" on the first hit of a teammate // only applies if the player has been hurt before // and the match is not in warmup. if( OnSameTeam( targ, attacker )) { if(( !client->lasthurt_mod || client->lasthurt_client != attacker->s.number ) && cvars::gameState.ivalue == GS_PLAYING && ( targ->health - take ) > limbo_health ) { if( client->sess.sessionTeam == TEAM_AXIS ) G_ClientSound( attacker, "sound/chat/axis/26a.wav" ); else G_ClientSound( attacker, "sound/chat/allies/26a.wav" ); } if (mod != MOD_GOOMBA && mod != MOD_POISON_SYRINGE) { g_clientObjects[attacker->s.number].recordHit( AbstractHitVolume::ZONE_BODY, true ); } } else { if (mod != MOD_GOOMBA && mod != MOD_POISON_SYRINGE) { g_clientObjects[attacker->s.number].recordHit( AbstractHitVolume::ZONE_BODY, false ); } } } // adrenaline junkie! if( targ->client && targ->client->ps.powerups[PW_ADRENALINE] ) { take = int( take * 0.5f ); } // save some from flak jacket // Jaybird - engineer class carryover if( targ->client && targ->client->sess.skill[SK_EXPLOSIVES_AND_CONSTRUCTION] >= 4 && ( targ->client->sess.playerType == PC_ENGINEER || ( cvars::bg_skills.ivalue & SBS_ENGI ))) { if( mod == MOD_GRENADE || mod == MOD_GRENADE_LAUNCHER || mod == MOD_ROCKET || mod == MOD_GRENADE_PINEAPPLE || mod == MOD_MAPMORTAR || mod == MOD_MAPMORTAR_SPLASH || mod == MOD_EXPLOSIVE || mod == MOD_LANDMINE || mod == MOD_GPG40 || mod == MOD_M7 || mod == MOD_SATCHEL || mod == MOD_ARTY || mod == MOD_AIRSTRIKE || mod == MOD_DYNAMITE || mod == MOD_MORTAR || mod == MOD_PANZERFAUST || mod == MOD_MAPMORTAR ) { take -= int( take * 0.5f ); } } #ifndef DEBUG_STATS if ( g_debugDamage.integer ) #endif { G_Printf( "client:%i health:%i damage:%i mod:%s\n", targ->s.number, targ->health, take, modNames[mod] ); } if( targ && targ->client && attacker && attacker->client && targ != attacker && targ->health > 0 && OnSameTeam( targ, attacker ) && g_friendlyFire.integer == 2 && IsReflectable( mod )) { int ffDamage; // Percentage based reflect ffDamage = int( take * g_reflectFriendlyFire.value / 100.f ); if( ffDamage <= 0 ) { ffDamage = 0; } attacker->health -= ffDamage; // Give them pain! attacker->client->damage_blood += take; attacker->client->damage_knockback += knockback; // Set the lasthurt stuff so hitsounds do not replay targ->client->lasthurt_mod = mod; targ->client->lasthurt_client = attacker - g_entities; // Kill the player if necessary if( attacker->health <= 0 ) { attacker->deathType = MOD_REFLECTED_FF; attacker->enemy = attacker; if( attacker->die ) { attacker->die( attacker, attacker, attacker, ffDamage, MOD_REFLECTED_FF ); } } } // add to the damage inflicted on a player this frame // the total will be turned into screen blends and view angle kicks // at the end of the frame if ( client ) { if ( attacker ) { client->ps.persistant[PERS_ATTACKER] = attacker->s.number; } else { client->ps.persistant[PERS_ATTACKER] = ENTITYNUM_WORLD; } client->damage_blood += take; client->damage_knockback += knockback; if ( dir ) { VectorCopy ( dir, client->damage_from ); client->damage_fromWorld = qfalse; } else { VectorCopy ( targ->r.currentOrigin, client->damage_from ); client->damage_fromWorld = qtrue; } } // See if it's the player hurting the emeny flag carrier // Team_CheckHurtCarrier(targ, attacker); if (targ->client) { // set the last client who damaged the target targ->client->lasthurt_client = attacker->s.number; targ->client->lasthurt_mod = mod; targ->client->lasthurt_time = level.time; } // do the damage if( take ) { targ->health -= take; // Gordon: don't ever gib POWS if( ( targ->health <= 0 ) && ( targ->r.svFlags & SVF_POW ) ) { targ->health = -1; } // Ridah, can't gib with bullet weapons (except VENOM) // Arnout: attacker == inflictor can happen in other cases as well! (movers trying to gib things) //if ( attacker == inflictor && targ->health <= GIB_HEALTH) { if( targ->health <= GIB_HEALTH ) { if( !G_WeaponIsExplosive( (meansOfDeath_t)mod ) ) { targ->health = GIB_HEALTH + 1; } } if( g_damagexp.integer && client && G_GetTeamFromEntity( inflictor ) != G_GetTeamFromEntity( targ )) { // Jaybird - give them some per hit // They get 1 XP per 50 damage, so multiple .02 * take int skill = G_SkillForMOD( mod ); if( skill >= 0 ) G_AddSkillPoints( attacker, (skillType_t)skill, take * .02 ); } // JPW NERVE overcome previous chunk of code for making grenades work again // if ((take > 190)) // 190 is greater than 2x mauser headshot, so headshots don't gib // Arnout: only player entities! messes up ents like func_constructibles and func_explosives otherwise if( ( (targ->s.number < MAX_CLIENTS) && (take > 190) ) && !(targ->r.svFlags & SVF_POW) ) { targ->health = GIB_HEALTH - 1; } if( targ->s.eType == ET_MOVER && !Q_stricmp( targ->classname, "script_mover" ) ) { targ->s.dl_intensity = int( 255.f * (targ->health / (float)targ->count) ); // send it to the client } //G_Printf("health at: %d\n", targ->health); if( targ->health <= 0 ) { if( client && !wasAlive ) { targ->flags |= FL_NO_KNOCKBACK; // OSP - special hack to not count attempts for body gibbage if( targ->client->ps.pm_type == PM_DEAD ) { G_addStats(targ, attacker, take, mod); } if( (targ->health < FORCE_LIMBO_HEALTH) && (targ->health > GIB_HEALTH) ) { limbo(targ, qtrue); } // xkan, 1/13/2003 - record the time we died. if (!client->deathTime) client->deathTime = level.time; //bani - #389 if( targ->health <= GIB_HEALTH ) { GibEntity( targ, 0 ); } } else { targ->sound1to2 = hr; targ->sound2to1 = mod; targ->sound2to3 = (dflags & DAMAGE_RADIUS) ? 1 : 0; if( client ) { if( G_GetTeamFromEntity( inflictor ) != G_GetTeamFromEntity( targ ) ) { G_AddKillSkillPoints( attacker, (meansOfDeath_t)mod, hr, (qboolean)(dflags & DAMAGE_RADIUS) ); } } if( targ->health < -999 ) { targ->health = -999; } targ->enemy = attacker; targ->deathType = (meansOfDeath_t)mod; // Ridah, mg42 doesn't have die func (FIXME) if( targ->die ) { // Kill the entity. Note that this funtion can set ->die to another // function pointer, so that next time die is applied to the dead body. targ->die( targ, inflictor, attacker, take, mod ); // OSP - kill stats in player_die function } if( targ->s.eType == ET_MOVER && !Q_stricmp( targ->classname, "script_mover" ) && (targ->spawnflags & 8) ) { return; // reseructable script mover doesn't unlink itself but we don't want a second death script to be called } // if we freed ourselves in death function if (!targ->inuse) return; // RF, entity scripting if ( targ->health <= 0) { // might have revived itself in death function if( targ->r.svFlags & SVF_BOT ) { // Removed } else if( ( targ->s.eType != ET_CONSTRUCTIBLE && targ->s.eType != ET_EXPLOSIVE ) || ( targ->s.eType == ET_CONSTRUCTIBLE && !targ->desstages ) ) { // call manually if using desstages G_Script_ScriptEvent( targ, "death", "" ); } } } } else if ( targ->pain ) { if (dir) { // Ridah, had to add this to fix NULL dir crash VectorCopy (dir, targ->rotate); VectorCopy (point, targ->pos3); // this will pass loc of hit } else { VectorClear( targ->rotate ); VectorClear( targ->pos3 ); } targ->pain (targ, attacker, take, point); } else { // OSP - update weapon/dmg stats G_addStats(targ, attacker, take, mod); // OSP } // RF, entity scripting G_Script_ScriptEvent( targ, "pain", va("%d %d", targ->health, targ->health+take) ); // RF, record bot pain if (targ->s.number < level.maxclients) { // notify omni-bot framework Bot_Event_TakeDamage(targ-g_entities, attacker); } // Ridah, this needs to be done last, incase the health is altered in one of the event calls // Jaybird - playdead check if ( targ->client ) { targ->client->ps.stats[STAT_HEALTH] = targ->health; } // Cheap way to ID inflictor entity as poison smoke. if (inflictor->poisonGasAlarm && mod == MOD_POISON_GAS && targ->health >= 0) G_AddEvent(targ, EV_COUGH, 0); } }
/* =========== ClientBegin 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 ClientBegin(int clientNum) { gentity_t *ent; gclient_t *client; int flags; int spawn_count; ent = g_entities + clientNum; client = level.clients + clientNum; if (ent->r.linked) { trap_UnlinkEntity(ent); } G_InitGentity(ent); ent->touch = 0; ent->pain = 0; ent->client = client; client->pers.connected = CON_CONNECTED; 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 // DHM - Nerve :: Also save PERS_SPAWN_COUNT, so that CG_Respawn happens spawn_count = client->ps.persistant[PERS_SPAWN_COUNT]; //bani - proper fix for #328 flags = client->ps.eFlags; memset(&client->ps, 0, sizeof (client->ps)); client->ps.eFlags = flags; client->ps.persistant[PERS_SPAWN_COUNT] = spawn_count; // locate ent at a spawn point ClientSpawn(ent); // DHM - Nerve :: Start players in limbo mode if they change teams during the match if (client->sess.sessionTeam != TEAM_SPECTATOR && (level.time - level.startTime > FRAMETIME * GAME_INIT_FRAMES)) { ent->health = 0; ent->r.contents = CONTENTS_CORPSE; client->ps.pm_type = PM_DEAD; client->ps.stats[STAT_HEALTH] = 0; limbo(ent); } if (client->sess.sessionTeam != TEAM_SPECTATOR) { trap_SendServerCommand(-1, va("print \"[lof]%s" S_COLOR_WHITE " [lon]entered the game\n\"", client->pers.netname)); } // count current clients and rank for scoreboard CalculateRanks(); // No surface determined yet. ent->surfaceFlags = 0; // Nico, check for autologin if (g_useAPI.integer && client->pers.autoLogin && !client->sess.logged) { G_LogPrintf("ClientBegin: login client %d via autoLogin\n", clientNum); Cmd_Login_f(ent); } // Nico, check for checkpoints auto loading // if client is not spectator and if we are not in cupMode if (g_useAPI.integer && g_cupMode.integer == 0 && client->pers.autoLoadCheckpoints && client->sess.sessionTeam != TEAM_SPECTATOR) { G_LogPrintf("ClientBegin: loading checkpoints for client %d via autoLoadCheckpoints\n", clientNum); Cmd_LoadCheckpoints_real(ent, "0", 0); // "0" as ignore vlaue, because we load player checkpoints } G_LogPrintf("ClientBegin: %i\n", clientNum); }