void G_PlayerAward( edict_t *ent, const char *awardMsg ) { edict_t *other; char cmd[MAX_STRING_CHARS]; gameaward_t *ga; int i, size; score_stats_t *stats; if( !awardMsg || !awardMsg[0] || !ent->r.client ) return; Q_snprintfz( cmd, sizeof( cmd ), "aw \"%s\"", awardMsg ); trap_GameCmd( ent, cmd ); if( dedicated->integer ) G_Printf( "%s", COM_RemoveColorTokens( va( "%s receives a '%s' award.\n", ent->r.client->netname, awardMsg ) ) ); ent->r.client->level.stats.awards++; teamlist[ent->s.team].stats.awards++; G_Gametype_ScoreEvent( ent->r.client, "award", awardMsg ); stats = &ent->r.client->level.stats; if( !stats->awardAllocator ) stats->awardAllocator = LinearAllocator( sizeof( gameaward_t ), 0, _G_LevelMalloc, _G_LevelFree ); // ch : this doesnt work for race right? if( GS_MatchState() == MATCH_STATE_PLAYTIME || GS_MatchState() == MATCH_STATE_POSTMATCH ) { // ch : we store this locally to send to MM // first check if we already have this one on the clients list size = LA_Size( stats->awardAllocator ); ga = NULL; for( i = 0; i < size; i++ ) { ga = ( gameaward_t * )LA_Pointer( stats->awardAllocator, i ); if( !strncmp( ga->name, awardMsg, sizeof(ga->name)-1 ) ) break; } if( i >= size ) { ga = ( gameaward_t * )LA_Alloc( stats->awardAllocator ); memset( ga, 0, sizeof(*ga) ); ga->name = G_RegisterLevelString( awardMsg ); } if( ga ) ga->count++; } // add it to every player who's chasing this player for( other = game.edicts + 1; PLAYERNUM( other ) < gs.maxclients; other++ ) { if( !other->r.client || !other->r.inuse || !other->r.client->resp.chase.active ) continue; if( other->r.client->resp.chase.target == ENTNUM( ent ) ) trap_GameCmd( other, cmd ); } }
/* * 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 ); }
/* * ClientDisconnect * Called when a player drops from the server. * Will not be called between levels. */ void ClientDisconnect( edict_t *ent, const char *reason ) { int team; if( !ent->r.client || !ent->r.inuse ) return; // always report in RACE mode if( GS_RaceGametype() || ( ent->r.client->team != TEAM_SPECTATOR && ( GS_MatchState() == MATCH_STATE_PLAYTIME || GS_MatchState() == MATCH_STATE_POSTMATCH ) ) ) G_AddPlayerReport( ent, GS_MatchState() == MATCH_STATE_POSTMATCH ); for( team = TEAM_PLAYERS; team < GS_MAX_TEAMS; team++ ) G_Teams_UnInvitePlayer( team, ent ); if( !level.gametype.disableObituaries || !(ent->r.svflags & SVF_FAKECLIENT ) ) { if( !reason ) G_PrintMsg( NULL, "%s" S_COLOR_WHITE " disconnected\n", ent->r.client->netname ); else G_PrintMsg( NULL, "%s" S_COLOR_WHITE " disconnected (%s" S_COLOR_WHITE ")\n", ent->r.client->netname, reason ); } // send effect if( ent->s.team > TEAM_SPECTATOR ) G_TeleportEffect( ent, false ); ent->r.client->team = TEAM_SPECTATOR; G_ClientRespawn( ent, true ); // respawn as ghost ent->movetype = MOVETYPE_NOCLIP; // allow freefly // let the gametype scripts know this client just disconnected G_Gametype_ScoreEvent( ent->r.client, "disconnect", NULL ); G_FreeAI( ent ); AI_EnemyRemoved( ent ); ent->r.inuse = false; ent->r.svflags = SVF_NOCLIENT; memset( ent->r.client, 0, sizeof( *ent->r.client ) ); ent->r.client->ps.playerNum = PLAYERNUM( ent ); trap_ConfigString( CS_PLAYERINFOS+PLAYERNUM( ent ), "" ); GClip_UnlinkEntity( ent ); G_Match_CheckReadys(); }
/* * G_PickupItem */ qboolean G_PickupItem( edict_t *ent, edict_t *other ) { gsitem_t *it; qboolean taken = qfalse; if( !ent || !other ) return qfalse; if( other->r.client && G_ISGHOSTING( other ) ) return qfalse; if( !ent->item || !( ent->item->flags & ITFLAG_PICKABLE ) ) return qfalse; it = ent->item; if( it->type & IT_WEAPON ) { taken = Pickup_Weapon( ent, other ); } else if( it->type & IT_AMMO ) { taken = Pickup_Ammo( ent, other ); } else if( it->type & IT_ARMOR ) { taken = Pickup_Armor( ent, other ); } else if( it->type & IT_HEALTH ) { taken = Pickup_Health( ent, other ); } else if( it->type & IT_POWERUP ) { taken = Pickup_Powerup( ent, other ); } if( taken && other->r.client ) G_Gametype_ScoreEvent( other->r.client, "pickup", it->classname ); return taken; }
/* * W_Touch_Projectile - Generic projectile touch func. Only for replacement in tests */ static void W_Touch_Projectile( edict_t *ent, edict_t *other, cplane_t *plane, int surfFlags ) { vec3_t dir, normal; int hitType; if( surfFlags & SURF_NOIMPACT ) { G_FreeEdict( ent ); return; } hitType = G_Projectile_HitStyle( ent, other ); if( hitType == PROJECTILE_TOUCH_NOT ) return; if( other->takedamage ) { VectorNormalize2( ent->velocity, dir ); if( hitType == PROJECTILE_TOUCH_DIRECTSPLASH ) // use hybrid direction from splash and projectile { G_SplashFrac4D( ENTNUM( other ), ent->s.origin, ent->projectileInfo.radius, dir, NULL, NULL, ent->timeDelta ); } else { VectorNormalize2( ent->velocity, dir ); } G_Damage( other, ent, ent->r.owner, dir, ent->velocity, ent->s.origin, ent->projectileInfo.maxDamage, ent->projectileInfo.maxKnockback, ent->projectileInfo.stun, 0, ent->style ); } G_RadiusDamage( ent, ent->r.owner, plane, other, MOD_EXPLOSIVE ); if( !plane->normal ) VectorSet( normal, 0, 0, 1 ); else VectorCopy( plane->normal, normal ); G_Gametype_ScoreEvent( NULL, "projectilehit", va( "%i %i %f %f %f", ent->s.number, surfFlags, normal[0], normal[1], normal[2] ) ); }
/* * ClientBegin * called when a client has finished connecting, and is ready * to be placed into the game. This will happen every level load. */ void ClientBegin( edict_t *ent ) { gclient_t *client = ent->r.client; const char *mm_login; memset( &client->ucmd, 0, sizeof( client->ucmd ) ); memset( &client->level, 0, sizeof( client->level ) ); client->level.timeStamp = level.time; G_Client_UpdateActivity( client ); // activity detected client->team = TEAM_SPECTATOR; G_ClientRespawn( ent, true ); // respawn as ghost ent->movetype = MOVETYPE_NOCLIP; // allow freefly G_UpdatePlayerMatchMsg( ent ); mm_login = Info_ValueForKey( client->userinfo, "cl_mm_login" ); if( mm_login && *mm_login && client->mm_session > 0 ) { G_PrintMsg( NULL, "%s" S_COLOR_WHITE " (" S_COLOR_YELLOW "%s" S_COLOR_WHITE ") entered the game\n", client->netname, mm_login ); } else { if( !level.gametype.disableObituaries || !(ent->r.svflags & SVF_FAKECLIENT ) ) G_PrintMsg( NULL, "%s" S_COLOR_WHITE " entered the game\n", client->netname ); } client->level.respawnCount = 0; // clear respawncount client->connecting = false; // schedule the next scoreboard update client->level.scoreboard_time = game.realtime + scoreboardInterval - ( game.realtime%scoreboardInterval ); AI_EnemyAdded( ent ); G_ClientEndSnapFrame( ent ); // make sure all view stuff is valid // let the gametype scripts now this client just entered the level G_Gametype_ScoreEvent( client, "enterGame", NULL ); }
void G_PlayerAward( edict_t *ent, const char *awardMsg ) { edict_t *other, *third; if( !awardMsg || !awardMsg[0] || !ent->r.client ) return; trap_GameCmd( ent, va( "aw \"%s\"", awardMsg ) ); if( dedicated->integer ) G_Printf( "%s", COM_RemoveColorTokens( va( "%s receives a '%s' award.\n", ent->r.client->netname, awardMsg ) ) ); ent->r.client->level.stats.awards++; teamlist[ent->s.team].stats.awards++; G_Gametype_ScoreEvent( ent->r.client, "award", awardMsg ); // add it to every player who's chasing this player for( other = game.edicts + 1; PLAYERNUM( other ) < gs.maxclients; other++ ) { if( !other->r.client || !other->r.inuse || !other->r.client->resp.chase.active ) continue; if( other->r.client->resp.chase.target == ent->s.number ) { trap_GameCmd( other, va( "aw \"%s\"", awardMsg ) ); // someone could also be chase-caming the guy in the chasecam for( third = game.edicts + 1; PLAYERNUM( third ) < gs.maxclients; third++ ) { if( !third->r.client || !third->r.inuse || !third->r.client->resp.chase.active ) continue; if( third->r.client->resp.chase.target == other->s.number ) trap_GameCmd( third, va( "aw \"%s\"", awardMsg ) ); } } } }
/* * G_PickupItem */ bool G_PickupItem( edict_t *other, const gsitem_t *it, int flags, int count, const int *invpack ) { bool taken = false; if( other->r.client && G_ISGHOSTING( other ) ) return false; if( !it || !( it->flags & ITFLAG_PICKABLE ) ) return false; if( it->type & IT_WEAPON ) { taken = Pickup_Weapon( other, it, flags, count ); } else if( it->type & IT_AMMO ) { taken = Pickup_Ammo( other, it, count, invpack ); } else if( it->type & IT_ARMOR ) { taken = Pickup_Armor( other, it ); } else if( it->type & IT_HEALTH ) { taken = Pickup_Health( other, it, flags ); } else if( it->type & IT_POWERUP ) { taken = Pickup_Powerup( other, it, flags, count ); } if( taken && other->r.client ) G_Gametype_ScoreEvent( other->r.client, "pickup", it->classname ); return taken; }
/* * ClientConnect * Called when a player begins connecting to the server. * The game can refuse entrance to a client by returning false. * If the client is allowed, the connection process will continue * and eventually get to ClientBegin() * Changing levels will NOT cause this to be called again, but * loadgames will. */ bool ClientConnect( edict_t *ent, char *userinfo, bool fakeClient, bool tvClient ) { char *value; assert( ent ); assert( userinfo && Info_Validate( userinfo ) ); assert( Info_ValueForKey( userinfo, "ip" ) && Info_ValueForKey( userinfo, "socket" ) ); // verify that server gave us valid data if( !Info_Validate( userinfo ) ) { Info_SetValueForKey( userinfo, "rejtype", va( "%i", DROP_TYPE_GENERAL ) ); Info_SetValueForKey( userinfo, "rejflag", va( "%i", 0 ) ); Info_SetValueForKey( userinfo, "rejmsg", "Invalid userinfo" ); return false; } if( !Info_ValueForKey( userinfo, "ip" ) ) { Info_SetValueForKey( userinfo, "rejtype", va( "%i", DROP_TYPE_GENERAL ) ); Info_SetValueForKey( userinfo, "rejflag", va( "%i", 0 ) ); Info_SetValueForKey( userinfo, "rejmsg", "Error: Server didn't provide client IP" ); return false; } if( !Info_ValueForKey( userinfo, "ip" ) ) { Info_SetValueForKey( userinfo, "rejtype", va( "%i", DROP_TYPE_GENERAL ) ); Info_SetValueForKey( userinfo, "rejflag", va( "%i", 0 ) ); Info_SetValueForKey( userinfo, "rejmsg", "Error: Server didn't provide client socket" ); return false; } // check to see if they are on the banned IP list value = Info_ValueForKey( userinfo, "ip" ); if( SV_FilterPacket( value ) ) { Info_SetValueForKey( userinfo, "rejtype", va( "%i", DROP_TYPE_GENERAL ) ); Info_SetValueForKey( userinfo, "rejflag", va( "%i", 0 ) ); Info_SetValueForKey( userinfo, "rejmsg", "You're banned from this server" ); return false; } // check for a password value = Info_ValueForKey( userinfo, "password" ); if( !fakeClient && ( *password->string && ( !value || strcmp( password->string, value ) ) ) ) { Info_SetValueForKey( userinfo, "rejtype", va( "%i", DROP_TYPE_PASSWORD ) ); Info_SetValueForKey( userinfo, "rejflag", va( "%i", 0 ) ); if( value && value[0] ) { Info_SetValueForKey( userinfo, "rejmsg", "Incorrect password" ); } else { Info_SetValueForKey( userinfo, "rejmsg", "Password required" ); } return false; } // they can connect G_InitEdict( ent ); ent->s.modelindex = 0; ent->r.solid = SOLID_NOT; ent->r.client = game.clients + PLAYERNUM( ent ); ent->r.svflags = ( SVF_NOCLIENT | ( fakeClient ? SVF_FAKECLIENT : 0 ) ); memset( ent->r.client, 0, sizeof( gclient_t ) ); ent->r.client->ps.playerNum = PLAYERNUM( ent ); ent->r.client->connecting = true; ent->r.client->isTV = tvClient == true; ent->r.client->team = TEAM_SPECTATOR; G_Client_UpdateActivity( ent->r.client ); // activity detected ClientUserinfoChanged( ent, userinfo ); if( !fakeClient ) { char message[MAX_STRING_CHARS]; Q_snprintfz( message, sizeof( message ), "%s%s connected", ent->r.client->netname, S_COLOR_WHITE ); G_PrintMsg( NULL, "%s\n", message ); G_Printf( "%s%s connected from %s\n", ent->r.client->netname, S_COLOR_WHITE, ent->r.client->ip ); } // let the gametype scripts know this client just connected G_Gametype_ScoreEvent( ent->r.client, "connect", NULL ); G_CallVotes_ResetClient( PLAYERNUM( ent ) ); return true; }
/* * ClientUserinfoChanged * called whenever the player updates a userinfo variable. * * The game can override any of the settings in place * (forcing skins or names, etc) before copying it off. */ void ClientUserinfoChanged( edict_t *ent, char *userinfo ) { char *s; char oldname[MAX_INFO_VALUE]; gclient_t *cl; int rgbcolor, i; assert( ent && ent->r.client ); assert( userinfo && Info_Validate( userinfo ) ); // check for malformed or illegal info strings if( !Info_Validate( userinfo ) ) { trap_DropClient( ent, DROP_TYPE_GENERAL, "Error: Invalid userinfo" ); return; } cl = ent->r.client; // ip s = Info_ValueForKey( userinfo, "ip" ); if( !s ) { trap_DropClient( ent, DROP_TYPE_GENERAL, "Error: Server didn't provide client IP" ); return; } Q_strncpyz( cl->ip, s, sizeof( cl->ip ) ); // socket s = Info_ValueForKey( userinfo, "socket" ); if( !s ) { trap_DropClient( ent, DROP_TYPE_GENERAL, "Error: Server didn't provide client socket" ); return; } Q_strncpyz( cl->socket, s, sizeof( cl->socket ) ); // color s = Info_ValueForKey( userinfo, "color" ); if( s ) rgbcolor = COM_ReadColorRGBString( s ); else rgbcolor = -1; if( rgbcolor != -1 ) { rgbcolor = COM_ValidatePlayerColor( rgbcolor ); Vector4Set( cl->color, COLOR_R( rgbcolor ), COLOR_G( rgbcolor ), COLOR_B( rgbcolor ), 255 ); } else { Vector4Set( cl->color, 255, 255, 255, 255 ); } // set name, it's validated and possibly changed first Q_strncpyz( oldname, cl->netname, sizeof( oldname ) ); G_SetName( ent, Info_ValueForKey( userinfo, "name" ) ); if( oldname[0] && Q_stricmp( oldname, cl->netname ) && !cl->isTV && !CheckFlood( ent, false ) ) G_PrintMsg( NULL, "%s%s is now known as %s%s\n", oldname, S_COLOR_WHITE, cl->netname, S_COLOR_WHITE ); if( !Info_SetValueForKey( userinfo, "name", cl->netname ) ) { trap_DropClient( ent, DROP_TYPE_GENERAL, "Error: Couldn't set userinfo (name)" ); return; } // clan tag G_SetClan( ent, Info_ValueForKey( userinfo, "clan" ) ); // handedness s = Info_ValueForKey( userinfo, "hand" ); if( !s ) cl->hand = 2; else cl->hand = bound( atoi( s ), 0, 2 ); // handicap s = Info_ValueForKey( userinfo, "handicap" ); if( s ) { i = atoi( s ); if( i > 90 || i < 0 ) { G_PrintMsg( ent, "Handicap must be defined in the [0-90] range.\n" ); cl->handicap = 0; } else { cl->handicap = i; } } s = Info_ValueForKey( userinfo, "cg_movementStyle" ); if( s ) { i = bound( atoi( s ), 0, GS_MAXBUNNIES - 1 ); if( trap_GetClientState( PLAYERNUM(ent) ) < CS_SPAWNED ) { if( i != cl->movestyle ) cl->movestyle = cl->movestyle_latched = i; } else if( cl->movestyle_latched != cl->movestyle ) { G_PrintMsg( ent, "A movement style change is already in progress. Please wait.\n" ); } else if( i != cl->movestyle_latched ) { cl->movestyle_latched = i; if( cl->movestyle_latched != cl->movestyle ) { edict_t *switcher; switcher = G_Spawn(); switcher->think = think_MoveTypeSwitcher; switcher->nextThink = level.time + 10000; switcher->s.ownerNum = ENTNUM( ent ); G_PrintMsg( ent, "Movement style will change in 10 seconds.\n" ); } } } // update the movement features depending on the movestyle if( !G_ISGHOSTING( ent ) && g_allow_bunny->integer ) { if( cl->movestyle == GS_CLASSICBUNNY ) cl->ps.pmove.stats[PM_STAT_FEATURES] &= ~PMFEAT_FWDBUNNY; else cl->ps.pmove.stats[PM_STAT_FEATURES] |= PMFEAT_FWDBUNNY; } s = Info_ValueForKey( userinfo, "cg_noAutohop" ); if( s && s[0] ) { if( atoi( s ) != 0 ) cl->ps.pmove.stats[PM_STAT_FEATURES] &= ~PMFEAT_CONTINOUSJUMP; else cl->ps.pmove.stats[PM_STAT_FEATURES] |= PMFEAT_CONTINOUSJUMP; } #ifdef UCMDTIMENUDGE s = Info_ValueForKey( userinfo, "cl_ucmdTimeNudge" ); if( !s ) { cl->ucmdTimeNudge = 0; } else { cl->ucmdTimeNudge = atoi( s ); clamp( cl->ucmdTimeNudge, -MAX_UCMD_TIMENUDGE, MAX_UCMD_TIMENUDGE ); } #endif // mm session // TODO: remove the key after storing it to gclient_t ! s = Info_ValueForKey( userinfo, "cl_mm_session" ); cl->mm_session = ( s == NULL ) ? 0 : atoi( s ); s = Info_ValueForKey( userinfo, "mmflags" ); cl->mmflags = ( s == NULL ) ? 0 : strtoul( s, NULL, 10 ); // tv if( cl->isTV ) { s = Info_ValueForKey( userinfo, "tv_port" ); cl->tv.port = s ? atoi( s ) : 0; s = Info_ValueForKey( userinfo, "tv_port6" ); cl->tv.port6 = s ? atoi( s ) : 0; s = Info_ValueForKey( userinfo, "max_cl" ); cl->tv.maxclients = s ? atoi( s ) : 0; s = Info_ValueForKey( userinfo, "num_cl" ); cl->tv.numclients = s ? atoi( s ) : 0; s = Info_ValueForKey( userinfo, "chan" ); cl->tv.channel = s ? atoi( s ) : 0; } if( !G_ISGHOSTING( ent ) && trap_GetClientState( PLAYERNUM( ent ) ) >= CS_SPAWNED ) G_Client_AssignTeamSkin( ent, userinfo ); // save off the userinfo in case we want to check something later Q_strncpyz( cl->userinfo, userinfo, sizeof( cl->userinfo ) ); G_UpdatePlayerInfoString( PLAYERNUM( ent ) ); G_UpdateMMPlayerInfoString( PLAYERNUM( ent ) ); G_Gametype_ScoreEvent( cl, "userinfochanged", oldname ); }
/* * 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 ); } }