/* * G_Killed */ void G_Killed( edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t point, int mod ) { if( targ->health < -999 ) targ->health = -999; if( targ->deadflag == DEAD_DEAD ) return; targ->deadflag = DEAD_DEAD; targ->enemy = attacker; if( targ->r.client ) { if( attacker && targ != attacker ) { if( GS_IsTeamDamage( &targ->s, &attacker->s ) ) attacker->snap.teamkill = qtrue; else attacker->snap.kill = qtrue; } // count stats if( GS_MatchState() == MATCH_STATE_PLAYTIME ) { targ->r.client->level.stats.deaths++; teamlist[targ->s.team].stats.deaths++; if( !attacker || !attacker->r.client || attacker == targ || attacker == world ) { targ->r.client->level.stats.suicides++; teamlist[targ->s.team].stats.suicides++; } else { if( GS_IsTeamDamage( &targ->s, &attacker->s ) ) { attacker->r.client->level.stats.teamfrags++; teamlist[attacker->s.team].stats.teamfrags++; } else { attacker->r.client->level.stats.frags++; teamlist[attacker->s.team].stats.frags++; G_AwardPlayerKilled( targ, inflictor, attacker, mod ); } } } } G_Gametype_ScoreEvent( attacker ? attacker->r.client : NULL, "kill", va( "%i %i %i", targ->s.number, ( inflictor == world ) ? -1 : ENTNUM( inflictor ), ENTNUM( attacker ) ) ); G_CallDie( targ, inflictor, attacker, damage, point ); }
void G_Gametype_GENERIC_PlayerKilled( edict_t *targ, edict_t *attacker, edict_t *inflictor ) { if( !attacker || GS_MatchState() != MATCH_STATE_PLAYTIME || ( targ->r.svflags & SVF_CORPSE ) ) return; if( !attacker->r.client || attacker == targ || attacker == world ) teamlist[targ->s.team].stats.score--; else { if( GS_InvidualGameType() ) teamlist[attacker->s.team].stats.score = attacker->r.client->level.stats.score; if( GS_IsTeamDamage( &targ->s, &attacker->s ) ) teamlist[attacker->s.team].stats.score--; else teamlist[attacker->s.team].stats.score++; } // drop items if( targ->r.client && !( G_PointContents( targ->s.origin ) & CONTENTS_NODROP ) ) { // drop the weapon if ( targ->r.client->ps.stats[STAT_WEAPON] > WEAP_GUNBLADE ) { gsitem_t *weaponItem = GS_FindItemByTag( targ->r.client->ps.stats[STAT_WEAPON] ); if( weaponItem ) { edict_t *drop = Drop_Item( targ, weaponItem ); if( drop ) { drop->count = targ->r.client->ps.inventory[ weaponItem->weakammo_tag ]; targ->r.client->ps.inventory[ weaponItem->weakammo_tag ] = 0; } } } // drop ammo pack (won't drop anything if player doesn't have any strong ammo) Drop_Item( targ, GS_FindItemByTag( AMMO_PACK ) ); } }
/* * * - We will consider direct impacts as splash when the player is on the ground and the hit very close to the ground */ int G_Projectile_HitStyle( edict_t *projectile, edict_t *target ) { trace_t trace; vec3_t end; qboolean atGround = qfalse; edict_t *attacker; #define AIRHIT_MINHEIGHT 64 // don't hurt owner for the first second if( target == projectile->r.owner && target != world ) { if( !g_projectile_touch_owner->integer || ( g_projectile_touch_owner->integer && projectile->timeStamp + 1000 > level.time ) ) return PROJECTILE_TOUCH_NOT; } if( !target->takedamage || ISBRUSHMODEL( target->s.modelindex ) ) return PROJECTILE_TOUCH_DIRECTHIT; if( target->waterlevel > 1 ) return PROJECTILE_TOUCH_DIRECTHIT; // water hits are direct but don't count for awards attacker = ( projectile->r.owner && projectile->r.owner->r.client ) ? projectile->r.owner : NULL; // see if the target is at ground or a less than a step of height if( target->groundentity ) atGround = qtrue; else { VectorCopy( target->s.origin, end ); end[2] -= STEPSIZE; G_Trace4D( &trace, target->s.origin, target->r.mins, target->r.maxs, end, target, MASK_DEADSOLID, 0 ); if( ( trace.ent != -1 || trace.startsolid ) && ISWALKABLEPLANE( &trace.plane ) ) atGround = qtrue; } if( atGround ) { // when the player is at ground we will consider a direct hit only when // the hit is 16 units above the feet if( projectile->s.origin[2] <= 16 + target->s.origin[2] + target->r.mins[2] ) return PROJECTILE_TOUCH_DIRECTSPLASH; } else { // it's direct hit, but let's see if it's airhit VectorCopy( target->s.origin, end ); end[2] -= AIRHIT_MINHEIGHT; G_Trace4D( &trace, target->s.origin, target->r.mins, target->r.maxs, end, target, MASK_DEADSOLID, 0 ); if( ( trace.ent != -1 || trace.startsolid ) && ISWALKABLEPLANE( &trace.plane ) ) { // add directhit and airhit to awards counter if( attacker && !GS_IsTeamDamage( &attacker->s, &target->s ) && G_ModToAmmo( projectile->style ) != AMMO_NONE ) { projectile->r.owner->r.client->level.stats.accuracy_hits_direct[G_ModToAmmo( projectile->style )-AMMO_GUNBLADE]++; teamlist[projectile->r.owner->s.team].stats.accuracy_hits_direct[G_ModToAmmo( projectile->style )-AMMO_GUNBLADE]++; projectile->r.owner->r.client->level.stats.accuracy_hits_air[G_ModToAmmo( projectile->style )-AMMO_GUNBLADE]++; teamlist[projectile->r.owner->s.team].stats.accuracy_hits_air[G_ModToAmmo( projectile->style )-AMMO_GUNBLADE]++; } return PROJECTILE_TOUCH_DIRECTAIRHIT; } } // add directhit to awards counter if( attacker && !GS_IsTeamDamage( &attacker->s, &target->s ) && G_ModToAmmo( projectile->style ) != AMMO_NONE ) { projectile->r.owner->r.client->level.stats.accuracy_hits_direct[G_ModToAmmo( projectile->style )-AMMO_GUNBLADE]++; teamlist[projectile->r.owner->s.team].stats.accuracy_hits_direct[G_ModToAmmo( projectile->style )-AMMO_GUNBLADE]++; } return PROJECTILE_TOUCH_DIRECTHIT; #undef AIRHIT_MINHEIGHT }
/* * G_Chase_FindFollowPOV */ static int G_Chase_FindFollowPOV( edict_t *ent ) { int i, j; int quad, warshell, regen, scorelead; int maxteam; int flags[GS_MAX_TEAMS]; int newctfpov, newpoweruppov; int score_best; int newpov = -1; edict_t *target; static int ctfpov = -1, poweruppov = -1; static unsigned int flagswitchTime = 0; static unsigned int pwupswitchTime = 0; #define CARRIERSWITCHDELAY 8000 if( !ent->r.client || !ent->r.client->resp.chase.active || !ent->r.client->resp.chase.followmode ) return newpov; // follow killer, with a small delay if( ( ent->r.client->resp.chase.followmode & 8 ) ) { edict_t *targ; targ = &game.edicts[ent->r.client->resp.chase.target]; if( G_IsDead( targ ) && targ->deathTimeStamp + 400 < level.time ) { edict_t *attacker = targ->r.client->teamstate.last_killer; // ignore world and team kills if( attacker && attacker->r.client && !GS_IsTeamDamage( &targ->s, &attacker->s ) ) { newpov = ENTNUM( attacker ); } } return newpov; } // find what players have what score_best = 999999999; // racesow: changed from -999999999 to find lowest score (fastest time) if( level.gametype.inverseScore ) score_best *= -1; quad = warshell = regen = scorelead = -1; memset( flags, -1, sizeof( flags ) ); newctfpov = newpoweruppov = -1; maxteam = 0; for( i = 1; PLAYERNUM( (game.edicts+i) ) < gs.maxclients; i++ ) { target = game.edicts + i; if( !target->r.inuse || trap_GetClientState( PLAYERNUM( target ) ) < CS_SPAWNED ) { // check if old targets are still valid if( ctfpov == ENTNUM( target ) ) ctfpov = -1; if( poweruppov == ENTNUM( target ) ) poweruppov = -1; continue; } if( target->s.team <= 0 || target->s.team >= (int)(sizeof( flags ) / sizeof( flags[0] )) ) continue; if( ent->r.client->resp.chase.teamonly && ent->s.team != target->s.team ) continue; if( target->s.effects & EF_QUAD ) quad = ENTNUM( target ); if( target->s.effects & EF_SHELL ) warshell = ENTNUM( target ); if( target->s.effects & EF_REGEN ) regen = ENTNUM( target ); if( target->s.team && (target->s.effects & EF_CARRIER) ) { if( target->s.team > maxteam ) maxteam = target->s.team; flags[target->s.team-1] = ENTNUM( target ); } // find the scoring leader if( ( !level.gametype.inverseScore && target->r.client->ps.stats[STAT_SCORE] // racesow: find lowest non-zero score (fastest time) && target->r.client->ps.stats[STAT_SCORE] < score_best ) || ( level.gametype.inverseScore && target->r.client->ps.stats[STAT_SCORE] // racesow: find highest non-zero score (slowest time) && target->r.client->ps.stats[STAT_SCORE] > score_best ) ) { score_best = target->r.client->ps.stats[STAT_SCORE]; scorelead = ENTNUM( target ); } } // do some categorization for( i = 0; i < maxteam; i++ ) { if( flags[i] == -1 ) continue; // default new ctfpov to the first flag carrier if( newctfpov == -1 ) newctfpov = flags[i]; else break; } // do we have more than one flag carrier? if( i < maxteam ) { // default to old ctfpov if( ctfpov >= 0 ) newctfpov = ctfpov; if( ctfpov < 0 || level.time > flagswitchTime ) { // alternate between flag carriers for( i = 0; i < maxteam; i++ ) { if( flags[i] != ctfpov ) continue; for( j = 0; j < maxteam-1; j++ ) { if( flags[(i+j+1)%maxteam] != -1 ) { newctfpov = flags[(i+j+1)%maxteam]; break; } } break; } } if( newctfpov != ctfpov ) { ctfpov = newctfpov; flagswitchTime = level.time + CARRIERSWITCHDELAY; } } else { ctfpov = newctfpov; flagswitchTime = 0; } if( quad != -1 && warshell != -1 && quad != warshell ) { // default to old powerup if( poweruppov >= 0 ) newpoweruppov = poweruppov; if( poweruppov < 0 || level.time > pwupswitchTime ) { if( poweruppov == quad ) newpoweruppov = warshell; else if( poweruppov == warshell ) newpoweruppov = quad; else newpoweruppov = ( rand() & 1 ) ? quad : warshell; } if( poweruppov != newpoweruppov ) { poweruppov = newpoweruppov; pwupswitchTime = level.time + CARRIERSWITCHDELAY; } } else { if( quad != -1 ) newpoweruppov = quad; else if( warshell != -1 ) newpoweruppov = warshell; else if( regen != -1 ) newpoweruppov = regen; poweruppov = newpoweruppov; pwupswitchTime = 0; } // so, we got all, select what we prefer to show if( ctfpov != -1 && ( ent->r.client->resp.chase.followmode & 4 ) ) newpov = ctfpov; else if( poweruppov != -1 && ( ent->r.client->resp.chase.followmode & 2 ) ) newpov = poweruppov; else if( scorelead != -1 && ( ent->r.client->resp.chase.followmode & 1 ) ) newpov = scorelead; return newpov; #undef CARRIERSWITCHDELAY }
/* * G_SetClientStats */ void G_SetClientStats( edict_t *ent ) { gclient_t *client = ent->r.client; int team, i; if( ent->r.client->resp.chase.active ) // in chasecam it copies the other player stats return; // // layouts // client->ps.stats[STAT_LAYOUTS] = 0; // don't force scoreboard when dead during timeout if( ent->r.client->level.showscores || GS_MatchState() >= MATCH_STATE_POSTMATCH ) client->ps.stats[STAT_LAYOUTS] |= STAT_LAYOUT_SCOREBOARD; if( GS_TeamBasedGametype() && !GS_InvidualGameType() ) client->ps.stats[STAT_LAYOUTS] |= STAT_LAYOUT_TEAMTAB; if( GS_HasChallengers() && ent->r.client->queueTimeStamp ) client->ps.stats[STAT_LAYOUTS] |= STAT_LAYOUT_CHALLENGER; if( GS_MatchState() <= MATCH_STATE_WARMUP && level.ready[PLAYERNUM( ent )] ) client->ps.stats[STAT_LAYOUTS] |= STAT_LAYOUT_READY; if( G_SpawnQueue_GetSystem( ent->s.team ) == SPAWNSYSTEM_INSTANT ) client->ps.stats[STAT_LAYOUTS] |= STAT_LAYOUT_INSTANTRESPAWN; // // team // client->ps.stats[STAT_TEAM] = client->ps.stats[STAT_REALTEAM] = ent->s.team; // // health // if( ent->s.team == TEAM_SPECTATOR ) client->ps.stats[STAT_HEALTH] = STAT_NOTSET; // no health for spectator else client->ps.stats[STAT_HEALTH] = HEALTH_TO_INT( ent->health ); client->r.frags = client->ps.stats[STAT_SCORE]; // // armor // if( GS_Instagib() ) { if( g_instashield->integer ) client->ps.stats[STAT_ARMOR] = ARMOR_TO_INT( 100.0f * ( client->resp.instashieldCharge / INSTA_SHIELD_MAX ) ); else client->ps.stats[STAT_ARMOR] = 0; } else client->ps.stats[STAT_ARMOR] = ARMOR_TO_INT( client->resp.armor ); // // pickup message // if( level.time > client->resp.pickup_msg_time ) { client->ps.stats[STAT_PICKUP_ITEM] = 0; } // // frags // if( ent->s.team == TEAM_SPECTATOR ) { client->ps.stats[STAT_SCORE] = STAT_NOTSET; // no frags for spectators } else { client->ps.stats[STAT_SCORE] = ent->r.client->level.stats.score; } // // Team scores // if( GS_TeamBasedGametype() ) { // team based i = 0; for( team = TEAM_ALPHA; team < GS_MAX_TEAMS; team++ ) { client->ps.stats[STAT_TEAM_ALPHA_SCORE+i] = teamlist[team].stats.score; i++; } // mark the rest as not set for(; team < GS_MAX_TEAMS; team++ ) { client->ps.stats[STAT_TEAM_ALPHA_SCORE+i] = STAT_NOTSET; i++; } } else { // not team based i = 0; for( team = TEAM_ALPHA; team < GS_MAX_TEAMS; team++ ) { client->ps.stats[STAT_TEAM_ALPHA_SCORE+i] = STAT_NOTSET; i++; } } // spawn system client->ps.stats[STAT_NEXT_RESPAWN] = ceil( G_SpawnQueue_NextRespawnTime( client->team ) * 0.001f ); // pointed player client->ps.stats[STAT_POINTED_TEAMPLAYER] = 0; client->ps.stats[STAT_POINTED_PLAYER] = G_FindPointedPlayer( ent ); if( client->ps.stats[STAT_POINTED_PLAYER] && GS_TeamBasedGametype() ) { edict_t *e = &game.edicts[client->ps.stats[STAT_POINTED_PLAYER]]; if( e->s.team == ent->s.team ) { int pointedhealth = HEALTH_TO_INT( e->health ); int pointedarmor = 0; int available_bits = 0; bool mega = false; if( pointedhealth < 0 ) pointedhealth = 0; if( pointedhealth > 100 ) { pointedhealth -= 100; mega = true; if( pointedhealth > 100 ) pointedhealth = 100; } pointedhealth /= 3.2; if( GS_Armor_TagForCount( e->r.client->resp.armor ) ) { pointedarmor = ARMOR_TO_INT( e->r.client->resp.armor ); } if( pointedarmor > 150 ) { pointedarmor = 150; } pointedarmor /= 5; client->ps.stats[STAT_POINTED_TEAMPLAYER] = ( ( pointedhealth &0x1F )|( pointedarmor&0x3F )<<6|( available_bits&0xF )<<12 ); if( mega ) { client->ps.stats[STAT_POINTED_TEAMPLAYER] |= 0x20; } } } // last killer. ignore world and team kills if( client->teamstate.last_killer ) { edict_t *targ = ent, *attacker = client->teamstate.last_killer; client->ps.stats[STAT_LAST_KILLER] = (attacker->r.client && !GS_IsTeamDamage( &targ->s, &attacker->s ) ? ENTNUM( attacker ) : 0); } else { client->ps.stats[STAT_LAST_KILLER] = 0; } }
/* * G_Damage * targ entity that is being damaged * inflictor entity that is causing the damage * attacker entity that caused the inflictor to damage targ * example: targ=enemy, inflictor=rocket, attacker=player * * dir direction of the attack * point point at which the damage is being inflicted * normal normal vector from that point * damage amount of damage being inflicted * knockback force to be applied against targ as a result of the damage * * dflags these flags are used to control how T_Damage works */ void G_Damage( edict_t *targ, edict_t *inflictor, edict_t *attacker, const vec3_t pushdir, const vec3_t dmgdir, const vec3_t point, float damage, float knockback, float stun, int dflags, int mod ) { gclient_t *client; float take; float save; float asave; qboolean statDmg; if( !targ || !targ->takedamage ) return; if( !attacker ) { attacker = world; mod = MOD_TRIGGER_HURT; } meansOfDeath = mod; client = targ->r.client; // Cgg - race mode: players don't interact with one another if( GS_RaceGametype() ) { if( attacker->r.client && targ->r.client && attacker != targ ) return; } // push if( !( dflags & DAMAGE_NO_KNOCKBACK ) ) G_KnockBackPush( targ, attacker, pushdir, knockback, dflags ); // stun if( g_allow_stun->integer && !( dflags & (DAMAGE_NO_STUN|FL_GODMODE) ) && (int)stun > 0 && targ->r.client && targ->r.client->resp.takeStun && !GS_IsTeamDamage( &targ->s, &attacker->s ) && ( targ != attacker ) ) { if( dflags & DAMAGE_STUN_CLAMP ) { if( targ->r.client->ps.pmove.stats[PM_STAT_STUN] < (int)stun ) targ->r.client->ps.pmove.stats[PM_STAT_STUN] = (int)stun; } else targ->r.client->ps.pmove.stats[PM_STAT_STUN] += (int)stun; clamp( targ->r.client->ps.pmove.stats[PM_STAT_STUN], 0, MAX_STUN_TIME ); } // dont count self-damage cause it just adds the same to both stats statDmg = ( attacker != targ ) && ( mod != MOD_TELEFRAG ); // apply handicap on the damage given if( statDmg && attacker->r.client && !GS_Instagib() ) { // handicap is a percentage value if( attacker->r.client->handicap != 0 ) damage *= 1.0 - (attacker->r.client->handicap * 0.01f); } take = damage; save = 0; // check for cases where damage is protected if( !( dflags & DAMAGE_NO_PROTECTION ) ) { // check for godmode if( targ->flags & FL_GODMODE ) { take = 0; save = damage; } // never damage in timeout else if( GS_MatchPaused() ) { take = save = 0; } // ca has self splash damage disabled else if( ( dflags & DAMAGE_RADIUS ) && attacker == targ && !GS_SelfDamage() ) { take = save = 0; } // don't get damage from players in race else if( ( GS_RaceGametype() ) && attacker->r.client && targ->r.client && ( attacker->r.client != targ->r.client ) ) { take = save = 0; } // team damage avoidance else if( GS_IsTeamDamage( &targ->s, &attacker->s ) && !G_Gametype_CanTeamDamage( dflags ) ) { take = save = 0; } // apply warShell powerup protection else if( targ->r.client && targ->r.client->ps.inventory[POWERUP_SHELL] > 0 ) { // warshell offers full protection in instagib if( GS_Instagib() ) { take = 0; save = damage; } else { take = ( damage * 0.25f ); save = damage - take; } // todo : add protection sound } } asave = G_CheckArmor( targ, take, dflags ); take -= asave; //treat cheat/powerup savings the same as armor asave += save; // APPLY THE DAMAGES if( !take && !asave ) return; // do the damage if( take <= 0 ) return; // adding damage given/received to stats if( statDmg && attacker->r.client && !targ->deadflag && targ->movetype != MOVETYPE_PUSH && targ->s.type != ET_CORPSE ) { attacker->r.client->level.stats.total_damage_given += take + asave; teamlist[attacker->s.team].stats.total_damage_given += take + asave; if( GS_IsTeamDamage( &targ->s, &attacker->s ) ) { attacker->r.client->level.stats.total_teamdamage_given += take + asave; teamlist[attacker->s.team].stats.total_teamdamage_given += take + asave; } } G_Gametype_ScoreEvent( attacker->r.client, "dmg", va( "%i %f %i", targ->s.number, damage, attacker->s.number ) ); if( statDmg && client ) { client->level.stats.total_damage_received += take + asave; teamlist[targ->s.team].stats.total_damage_received += take + asave; if( GS_IsTeamDamage( &targ->s, &attacker->s ) ) { client->level.stats.total_teamdamage_received += take + asave; teamlist[targ->s.team].stats.total_teamdamage_received += take + asave; } } // accumulate received damage for snapshot effects { vec3_t dorigin; if( inflictor == world && mod == MOD_FALLING ) // it's fall damage targ->snap.damage_fall += take + save; if( point[0] != 0.0f || point[1] != 0.0f || point[2] != 0.0f ) VectorCopy( point, dorigin ); else VectorSet( dorigin, targ->s.origin[0], targ->s.origin[1], targ->s.origin[2] + targ->viewheight ); G_BlendFrameDamage( targ, take, &targ->snap.damage_taken, dorigin, dmgdir, targ->snap.damage_at, targ->snap.damage_dir ); G_BlendFrameDamage( targ, save, &targ->snap.damage_saved, dorigin, dmgdir, targ->snap.damage_at, targ->snap.damage_dir ); if( targ->r.client ) { if( mod != MOD_FALLING && mod != MOD_TELEFRAG && mod != MOD_SUICIDE ) { if( inflictor == world || attacker == world ) { // for world inflicted damage use always 'frontal' G_ClientAddDamageIndicatorImpact( targ->r.client, take + save, NULL ); } else if( dflags & DAMAGE_RADIUS ) { // for splash hits the direction is from the inflictor origin G_ClientAddDamageIndicatorImpact( targ->r.client, take + save, pushdir ); } else { // for direct hits the direction is the projectile direction G_ClientAddDamageIndicatorImpact( targ->r.client, take + save, dmgdir ); } } } } targ->health = targ->health - take; // add damage done to stats if( !GS_IsTeamDamage( &targ->s, &attacker->s ) && statDmg && G_ModToAmmo( mod ) != AMMO_NONE && client && attacker->r.client ) { attacker->r.client->level.stats.accuracy_hits[G_ModToAmmo( mod )-AMMO_GUNBLADE]++; attacker->r.client->level.stats.accuracy_damage[G_ModToAmmo( mod )-AMMO_GUNBLADE] += damage; teamlist[attacker->s.team].stats.accuracy_hits[G_ModToAmmo( mod )-AMMO_GUNBLADE]++; teamlist[attacker->s.team].stats.accuracy_damage[G_ModToAmmo( mod )-AMMO_GUNBLADE] += damage; G_AwardPlayerHit( targ, attacker, mod ); } // accumulate given damage for hit sounds if( ( take || asave ) && targ != attacker && client && !targ->deadflag ) { if( attacker ) { if( GS_IsTeamDamage( &targ->s, &attacker->s ) ) attacker->snap.damageteam_given += take + asave; // we want to know how good our hit was, so saved also matters else attacker->snap.damage_given += take + asave; } } if( G_IsDead( targ ) ) { if( client ) targ->flags |= FL_NO_KNOCKBACK; G_Killed( targ, inflictor, attacker, HEALTH_TO_INT( take ), point, mod ); } else { G_CallPain( targ, attacker, knockback, take ); } }