From 01b7a3dae143f5a3c47d726928f55118707ed88c Mon Sep 17 00:00:00 2001 From: efinst0rm Date: Sun, 14 May 2023 12:30:13 -0500 Subject: [PATCH] Restores game events to games_mp log Remove dev blocks from logprint function. Sets bots xuid's as "bot0" --- .../mp/gametypes/_globallogic_player.gsc | Bin 0 -> 91376 bytes .../mp/gametypes/_globallogic_player.gsc_raw | 4524 +++++++++++++++++ 2 files changed, 4524 insertions(+) create mode 100644 data/scripts/mp/gametypes/_globallogic_player.gsc create mode 100644 data/scripts/mp/gametypes/_globallogic_player.gsc_raw diff --git a/data/scripts/mp/gametypes/_globallogic_player.gsc b/data/scripts/mp/gametypes/_globallogic_player.gsc new file mode 100644 index 0000000000000000000000000000000000000000..b06730520ec037ff3894aa418bc42df0989289f9 GIT binary patch literal 91376 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 player.pers["lastHighestScore"] ) + bestScore = "1"; + + bestKills = "0"; + if ( isdefined( player.pers["lastHighestKills"] ) && killCount > player.pers["lastHighestKills"] ) + bestKills = "1"; + + totalMatchShots = 0; + if ( isdefined( player.totalMatchShots) ) + totalMatchShots = player.totalMatchShots; + + deaths = player.deaths; + if (deaths == 0) + deaths = 1; + kdRatio = player.kills*1000/deaths; + bestKDRatio = "0"; + if ( isdefined( player.pers["lastHighestKDRatio"] ) && kdRatio > player.pers["lastHighestKDRatio"] ) + bestKDRatio = "1"; + + showcaseWeapon = player GetPlayerShowcaseWeapon(); + + RecordComScoreEvent( "end_match", + "match_id", getDemoFileID(), + "game_variant", "mp", + "game_mode", level.gametype, + "private_match", SessionModeIsPrivate(), + "esports_flag", level.leagueMatch, + "ranked_play_flag", level.arenaMatch, + "league_team_id", player getLeagueTeamID(), + "game_map", GetDvarString( "mapname" ), + "player_xuid", player getxuid(true), + "player_ip", player getipaddress(), + "match_kills", killCount, + "match_deaths", player.deaths, + "match_xp", xpEarned, + "match_score", player.score, + "match_streak", player.pers["best_kill_streak"], + "match_captures", player.pers["captures"], + "match_defends", player.pers["defends"], + "match_headshots", player.pers["headshots"], + "match_longshots", player.pers["longshots"], + "match_objtime", player.pers["objtime"], + "match_plants", player.pers["plants"], + "match_defuses", player.pers["defuses"], + "match_throws", player.pers["throws"], + "match_carries", player.pers["carries"], + "match_returns", player.pers["returns"], + "prestige_max", player.pers["plevel"], + "level_max", player.pers["rank"], + "match_result", resultStr, + "match_duration", timePlayed, + "match_shots", totalMatchShots, + "match_hits", hitCount, + "player_gender", player GetPlayerGenderType( CurrentSessionMode() ), + "specialist_kills", player.heroweaponKillCount, + "specialist_used", player GetMpDialogName(), + "season_pass_owned", player HasSeasonPass(0), + "loadout_perks", perkStr, + "loadout_lethal", grenadePrimaryName, + "loadout_tactical", grenadeSecondaryName, + "loadout_scorestreaks", killStreakStr, + "loadout_primary_weapon", primaryWeaponName, + "loadout_secondary_weapon", secondaryWeaponName, + "dlc_owned", player GetDLCAvailable(), + "loadout_primary_attachments", primaryWeaponAttachStr, + "loadout_secondary_attachments",secondaryWeaponAttachStr, + "best_score", bestScore, + "best_kills", bestKills, + "best_kd", bestKDRatio, + "total_kills", totalKills, + "total_deaths", totalDeaths, + "total_wins", totalWins, + "total_xp", totalXP, + "daily_contract_id", dailyContractId, + "daily_contract_target", dailyContractTarget, + "daily_contract_current", dailyContractCurrent, + "daily_contract_completed", dailyContractCompleted, + "weeklyA_contract_id", weeklyAContractId, + "weeklyA_contract_target", weeklyAContractTarget, + "weeklyA_contract_current", weeklyAContractCurrent, + "weeklyA_contract_completed", weeklyAContractCompleted, + "weeklyB_contract_id", weeklyBContractId, + "weeklyB_contract_target", weeklyBContractTarget, + "weeklyB_contract_current", weeklyBContractCurrent, + "weeklyB_contract_completed", weeklyBContractCompleted, + "special_contract_id ", specialContractId, + "special_contract_target", specialContractTarget, + "special_contract_curent", specialContractCurent, + "special_contract_completed", specialContractCompleted, + "specialist_power", player.heroabilityname, + "specialist_head", player GetCharacterHelmetModel(), + "specialist_body", player GetCharacterBodyModel(), + "specialist_taunt", player GetPlayerSelectedTauntName( 0 ), + "specialist_goodgame", player GetPlayerSelectedGestureName( 0 ), + "specialist_threaten", player GetPlayerSelectedGestureName( 1 ), + "specialist_boast", player GetPlayerSelectedGestureName( 2 ), + "specialist_showcase", showcaseWeapon.weapon.name + ); +} + +function player_monitor_travel_dist() +{ + self endon ( "death" ); + self endon ( "disconnect" ); + waitTime = 1; + minimumMoveDistance = 16; + + //Ignore data immediatly after spawn + wait 4; + + prevpos = self.origin; + positionPTM = self.origin; + while( 1 ) + { + wait waitTime; + + if (self util::isUsingRemote()) + { + self waittill ("stopped_using_remote"); + prevpos = self.origin; + positionPTM = self.origin; + continue; + } + + distance = distance( self.origin, prevpos ); + self.pers["total_distance_travelled"] += distance; + self.pers["movement_Update_Count"]++; + prevpos = self.origin; + + if ((self.pers["movement_Update_Count"] % 5) == 0) + { + distanceMoving = distance(self.origin, positionPTM); + positionPTM = self.origin; + if ( distanceMoving > minimumMoveDistance ) + { + self.pers["num_speeds_when_moving_entries"]++; + self.pers["total_speeds_when_moving"] += ( distanceMoving / waitTime ); + self.pers["time_played_moving"] += waitTime; + } + } + + + } +} + +function record_special_move_data_for_life( killer ) +{ + // safe to assume fields on self exist? + if( !isDefined( self.lastSwimmingStartTime) || !isDefined( self.lastWallRunStartTime) || !isDefined( self.lastSlideStartTime) || !isDefined( self.lastDoubleJumpStartTime) || + !isDefined( self.timeSpentSwimmingInLife) || !isDefined( self.timeSpentWallRunningInLife) || !isDefined( self.numberOfDoubleJumpsInLife) || !isDefined( self.numberOfSlidesInLife) ) + { + /# + println( "record_special_move_data_for_life - fields on self not defined!"); + #/ + return; + } + + if( isDefined(killer) ) + { + if( !isDefined( killer.lastSwimmingStartTime) || !isDefined( killer.lastWallRunStartTime) || !isDefined( killer.lastSlideStartTime) || !isDefined( killer.lastDoubleJumpStartTime) ) + { + /# + println( "record_special_move_data_for_life - fields one killer not defined!"); + #/ + return; + } + matchRecordLogSpecialMoveDataForLife( self, self.lastSwimmingStartTime, self.lastWallRunStartTime, self.lastSlideStartTime, self.lastDoubleJumpStartTime, + self.timeSpentSwimmingInLife, self.timeSpentWallRunningInLife, self.numberOfDoubleJumpsInLife, self.numberOfSlidesInLife, + killer, killer.lastSwimmingStartTime, killer.lastWallRunStartTime, killer.lastSlideStartTime, killer.lastDoubleJumpStartTime ); + } + else + { + matchRecordLogSpecialMoveDataForLife( self, self.lastSwimmingStartTime, self.lastWallRunStartTime, self.lastSlideStartTime, self.lastDoubleJumpStartTime, + self.timeSpentSwimmingInLife, self.timeSpentWallRunningInLife, self.numberOfDoubleJumpsInLife, self.numberOfSlidesInLife ); + } + +} + +function player_monitor_wall_run() +{ + self endon ( "disconnect" ); + + // make sure no other stray threads running on this dude + self notify("stop_player_monitor_wall_run"); + self endon("stop_player_monitor_wall_run"); + + self.lastWallRunStartTime = 0; + self.timeSpentWallRunningInLife = 0; + while ( true ) + { + notification = self util::waittill_any_return( "wallrun_begin", "death", "disconnect", "stop_player_monitor_wall_run" ); + if( notification == "death" ) + break; // end thread + + self.lastWallRunStartTime = getTime(); + + notification = self util::waittill_any_return( "wallrun_end", "death", "disconnect", "stop_player_monitor_wall_run" ); + + self.timeSpentWallRunningInLife += (getTime() - self.lastWallRunStartTime); + + if( notification == "death" ) + break; // end thread + + } +} + +function player_monitor_swimming() +{ + self endon ( "disconnect" ); + + // make sure no other stray threads running on this dude + self notify("stop_player_monitor_swimming"); + self endon("stop_player_monitor_swimming"); + + self.lastSwimmingStartTime = 0; + self.timeSpentSwimmingInLife = 0; + while ( true ) + { + notification = self util::waittill_any_return( "swimming_begin", "death", "disconnect", "stop_player_monitor_swimming" ); + if( notification == "death" ) + break; // end thread + + self.lastSwimmingStartTime = getTime(); + + notification = self util::waittill_any_return( "swimming_end", "death", "disconnect", "stop_player_monitor_swimming" ); + + self.timeSpentSwimmingInLife += (getTime() - self.lastSwimmingStartTime); + + if( notification == "death" ) + break; // end thread + + } +} + +function player_monitor_slide() +{ + self endon ( "disconnect" ); + + // make sure no other stray threads running on this dude + self notify("stop_player_monitor_slide"); + self endon("stop_player_monitor_slide"); + + self.lastSlideStartTime = 0; + self.numberOfSlidesInLife = 0; + while ( true ) + { + notification = self util::waittill_any_return( "slide_begin", "death", "disconnect", "stop_player_monitor_slide" ); + if( notification == "death" ) + break; // end thread + + self.lastSlideStartTime = getTime(); + self.numberOfSlidesInLife++; + + notification = self util::waittill_any_return( "slide_end", "death", "disconnect", "stop_player_monitor_slide" ); + + if( notification == "death" ) + break; // end thread + } +} + +function player_monitor_doublejump() +{ + self endon ( "disconnect" ); + + // make sure no other stray threads running on this dude + self notify("stop_player_monitor_doublejump"); + self endon("stop_player_monitor_doublejump"); + + self.lastDoubleJumpStartTime = 0; + self.numberOfDoubleJumpsInLife = 0; + while ( true ) + { + notification = self util::waittill_any_return( "doublejump_begin", "death", "disconnect", "stop_player_monitor_doublejump" ); + if( notification == "death" ) + break; // end thread + + self.lastDoubleJumpStartTime = getTime(); + self.numberOfDoubleJumpsInLife++; + + notification = self util::waittill_any_return( "doublejump_end", "death", "disconnect", "stop_player_monitor_doublejump" ); + + if( notification == "death" ) + break; // end thread + } +} + + +function player_monitor_inactivity() +{ + self endon ( "disconnect" ); + + self notify( "player_monitor_inactivity" ); + self endon( "player_monitor_inactivity" ); + + wait 10; + + while( true ) + { + if ( isdefined( self ) ) + { + if ( self isRemoteControlling() || self util::isUsingRemote() ) + { + self ResetInactivityTimer(); + } + } + wait 5; + } +} + +function Callback_PlayerConnect() +{ + thread notifyConnecting(); + + self.statusicon = "hud_status_connecting"; + self waittill( "begin" ); + + if( isdefined( level.reset_clientdvars ) ) + self [[level.reset_clientdvars]](); + + waittillframeend; + self.statusicon = ""; + + self.guid = self getXuid(); + + self.killstreak = []; + + self.leaderDialogQueue = []; + self.killstreakDialogQueue = []; + + profilelog_begintiming( 4, "ship" ); + + level notify( "connected", self ); + callback::callback( #"on_player_connect" ); + + if ( self IsHost() ) + self thread globallogic::listenForGameEnd(); + + // only print that we connected if we haven't connected in a previous round + if( !level.splitscreen && !isdefined( self.pers["score"] ) ) + { + iPrintLn(&"MP_CONNECTED", self); + } + + if( !isdefined( self.pers["score"] ) ) + { + self thread persistence::adjust_recent_stats(); + self persistence::set_after_action_report_stat( "valid", 0 ); + if ( GameModeIsMode( 3 ) && !( self IsHost() ) ) + self persistence::set_after_action_report_stat( "wagerMatchFailed", 1 ); + else + self persistence::set_after_action_report_stat( "wagerMatchFailed", 0 ); + } + + // track match and hosting stats once per match + if( ( level.rankedMatch || level.wagerMatch || level.leagueMatch ) && !isdefined( self.pers["matchesPlayedStatsTracked"] ) ) + { + gameMode = util::GetCurrentGameMode(); + self globallogic::IncrementMatchCompletionStat( gameMode, "played", "started" ); + + if ( !isdefined( self.pers["matchesHostedStatsTracked"] ) && self IsLocalToHost() ) + { + self globallogic::IncrementMatchCompletionStat( gameMode, "hosted", "started" ); + self.pers["matchesHostedStatsTracked"] = true; + } + + self.pers["matchesPlayedStatsTracked"] = true; + self thread persistence::upload_stats_soon(); + } + + self gamerep::gameRepPlayerConnected(); + + lpselfnum = self getEntityNumber(); + lpGuid = self getXuid(); + lpXuid = self getxuid(true); + + if (self util::is_bot()) + { + lpGuid = "bot0"; + } + + logPrint("J;" + lpGuid + ";" + lpselfnum + ";" + self.name + "\n"); + bbPrint( "global_joins", "name %s client %s xuid %s", self.name, lpselfnum, lpXuid ); + + // needed for cross-referencing into player breadcrumb buffer + // will get out of sync with self.clientId with disconnects/connects + recordPlayerStats( self, "codeClientNum", lpselfnum); + + if( !SessionModeIsZombiesGame() ) // it will be set after intro screen is faded out for zombie + { + self setClientUIVisibilityFlag( "hud_visible", 1 ); + self setClientUIVisibilityFlag( "weapon_hud_visible", 1 ); + } + + self SetClientPlayerSprintTime( level.playerSprintTime ); + self SetClientNumLives( level.numLives ); + + //makeDvarServerInfo( "cg_drawTalk", 1 ); + + if ( level.hardcoreMode ) + { + self SetClientDrawTalk( 3 ); + } + + if( SessionModeIsZombiesGame() ) + { + // initial zombies stats + self [[level.player_stats_init]](); + } + else + { + + self globallogic_score::initPersStat( "score" ); + if ( level.resetPlayerScoreEveryRound ) + { + self.pers["score"] = 0; + } + self.score = self.pers["score"]; + + self globallogic_score::initPersStat( "pointstowin" ); + if ( level.scoreRoundWinBased ) + { + self.pers["pointstowin"] = 0; + } + self.pointstowin = self.pers["pointstowin"]; + + self globallogic_score::initPersStat( "momentum", false ); + self.momentum = self globallogic_score::getPersStat( "momentum" ); + + self globallogic_score::initPersStat( "suicides" ); + self.suicides = self globallogic_score::getPersStat( "suicides" ); + + self globallogic_score::initPersStat( "headshots" ); + self.headshots = self globallogic_score::getPersStat( "headshots" ); + + self globallogic_score::initPersStat( "challenges" ); + self.challenges = self globallogic_score::getPersStat( "challenges" ); + + self globallogic_score::initPersStat( "kills" ); + self.kills = self globallogic_score::getPersStat( "kills" ); + + self globallogic_score::initPersStat( "deaths" ); + self.deaths = self globallogic_score::getPersStat( "deaths" ); + + self globallogic_score::initPersStat( "assists" ); + self.assists = self globallogic_score::getPersStat( "assists" ); + + self globallogic_score::initPersStat( "defends", false ); + self.defends = self globallogic_score::getPersStat( "defends" ); + + self globallogic_score::initPersStat( "offends", false ); + self.offends = self globallogic_score::getPersStat( "offends" ); + + self globallogic_score::initPersStat( "plants", false ); + self.plants = self globallogic_score::getPersStat( "plants" ); + + self globallogic_score::initPersStat( "defuses", false ); + self.defuses = self globallogic_score::getPersStat( "defuses" ); + + self globallogic_score::initPersStat( "returns", false ); + self.returns = self globallogic_score::getPersStat( "returns" ); + + self globallogic_score::initPersStat( "captures", false ); + self.captures = self globallogic_score::getPersStat( "captures" ); + + self globallogic_score::initPersStat( "objtime", false ); + self.objtime = self globallogic_score::getPersStat( "objtime" ); + + self globallogic_score::initPersStat( "carries", false ); + self.carries = self globallogic_score::getPersStat( "carries" ); + + self globallogic_score::initPersStat( "throws", false ); + self.throws = self globallogic_score::getPersStat( "throws" ); + + self globallogic_score::initPersStat( "destructions", false ); + self.destructions = self globallogic_score::getPersStat( "destructions" ); + + self globallogic_score::initPersStat( "disables", false ); + self.disables = self globallogic_score::getPersStat( "disables" ); + + self globallogic_score::initPersStat( "escorts", false ); + self.escorts = self globallogic_score::getPersStat( "escorts" ); + + self globallogic_score::initPersStat( "infects", false ); + self.infects = self globallogic_score::getPersStat( "infects" ); + + self globallogic_score::initPersStat( "sbtimeplayed", false ); + self.sbtimeplayed = self globallogic_score::getPersStat( "sbtimeplayed" ); + + self globallogic_score::initPersStat( "backstabs", false ); + self.backstabs = self globallogic_score::getPersStat( "backstabs" ); + + self globallogic_score::initPersStat( "longshots", false ); + self.longshots = self globallogic_score::getPersStat( "longshots" ); + + self globallogic_score::initPersStat( "survived", false ); + self.survived = self globallogic_score::getPersStat( "survived" ); + + self globallogic_score::initPersStat( "stabs", false ); + self.stabs = self globallogic_score::getPersStat( "stabs" ); + + self globallogic_score::initPersStat( "tomahawks", false ); + self.tomahawks = self globallogic_score::getPersStat( "tomahawks" ); + + self globallogic_score::initPersStat( "humiliated", false ); + self.humiliated = self globallogic_score::getPersStat( "humiliated" ); + + self globallogic_score::initPersStat( "x2score", false ); + self.x2score = self globallogic_score::getPersStat( "x2score" ); + + self globallogic_score::initPersStat( "agrkills", false ); + self.x2score = self globallogic_score::getPersStat( "agrkills" ); + + self globallogic_score::initPersStat( "hacks", false ); + self.x2score = self globallogic_score::getPersStat( "hacks" ); + + self globallogic_score::initPersStat( "killsconfirmed", false ); + self.killsconfirmed = self globallogic_score::getPersStat( "killsconfirmed" ); + + self globallogic_score::initPersStat( "killsdenied", false ); + self.killsdenied = self globallogic_score::getPersStat( "killsdenied" ); + + self globallogic_score::initPersStat( "rescues", false ); + self.rescues = self globallogic_score::getPersStat( "rescues" ); + + self globallogic_score::initPersStat( "shotsfired", false ); + self.shotsfired = self globallogic_score::getPersStat( "shotsfired" ); + + self globallogic_score::initPersStat( "shotshit", false ); + self.shotshit = self globallogic_score::getPersStat( "shotshit" ); + + self globallogic_score::initPersStat( "shotsmissed", false ); + self.shotsmissed = self globallogic_score::getPersStat( "shotsmissed" ); + + self globallogic_score::initPersStat( "cleandeposits", false ); + self.cleandeposits = self globallogic_score::getPersStat( "cleandeposits" ); + + self globallogic_score::initPersStat( "cleandenies", false ); + self.cleandenies = self globallogic_score::getPersStat( "cleandenies" ); + + self globallogic_score::initPersStat( "victory", false ); + self.victory = self globallogic_score::getPersStat( "victory" ); + + self globallogic_score::initPersStat( "sessionbans", false ); + self.sessionbans = self globallogic_score::getPersStat( "sessionbans" ); + self globallogic_score::initPersStat( "gametypeban", false ); + self globallogic_score::initPersStat( "time_played_total", false ); + self globallogic_score::initPersStat( "time_played_alive", false ); + + self globallogic_score::initPersStat( "teamkills", false ); + self globallogic_score::initPersStat( "teamkills_nostats", false ); + + // used by match recorder for analyzing play styles + self globallogic_score::initPersStat( "kill_distances", false ); + self globallogic_score::initPersStat( "num_kill_distance_entries", false ); + self globallogic_score::initPersStat( "time_played_moving", false ); + self globallogic_score::initPersStat( "total_speeds_when_moving", false ); + self globallogic_score::initPersStat( "num_speeds_when_moving_entries", false ); + self globallogic_score::initPersStat( "total_distance_travelled", false ); + self globallogic_score::initPersStat( "movement_Update_Count", false ); + + self.teamKillPunish = false; + if ( level.minimumAllowedTeamKills >= 0 && self.pers["teamkills_nostats"] > level.minimumAllowedTeamKills ) + self thread reduceTeamKillsOverTime(); + + self behaviorTracker::Initialize(); + } + + self.killedPlayersCurrent = []; + + if ( !isdefined( self.pers["totalTimePlayed"] ) ) + { + self setEnterTime( getTime() ); + self.pers["totalTimePlayed"] = 0; + } + + if ( !isdefined( self.pers["totalMatchBonus"] ) ) + { + self.pers["totalMatchBonus"] = 0; + } + + if( !isdefined( self.pers["best_kill_streak"] ) ) + { + self.pers["killed_players"] = []; + self.pers["killed_by"] = []; + self.pers["nemesis_tracking"] = []; + self.pers["artillery_kills"] = 0; + self.pers["dog_kills"] = 0; + self.pers["nemesis_name"] = ""; + self.pers["nemesis_rank"] = 0; + self.pers["nemesis_rankIcon"] = 0; + self.pers["nemesis_xp"] = 0; + self.pers["nemesis_xuid"] = ""; + self.pers["killed_players_with_specialist"] = []; + + /*self.killstreakKills["artillery"] = 0; + self.killstreakKills["dogs"] = 0; + self.killstreaksUsed["radar"] = 0; + self.killstreaksUsed["artillery"] = 0; + self.killstreaksUsed["dogs"] = 0;*/ + self.pers["best_kill_streak"] = 0; + } + +// Adding Music tracking per player CDC + if( !isdefined( self.pers["music"] ) ) + { + self.pers["music"] = spawnstruct(); + self.pers["music"].spawn = false; + self.pers["music"].inque = false; + self.pers["music"].currentState = "SILENT"; + self.pers["music"].previousState = "SILENT"; + self.pers["music"].nextstate = "UNDERSCORE"; + self.pers["music"].returnState = "UNDERSCORE"; + + } + + if ( self.team != "spectator" ) + { + self thread globallogic_audio::set_music_on_player( "spawnPreLoop" ); + } + + if ( !isdefined( self.pers["cur_kill_streak"] ) ) + { + self.pers["cur_kill_streak"] = 0; + } + + if ( !isdefined( self.pers["cur_total_kill_streak"] ) ) + { + self.pers["cur_total_kill_streak"] = 0; + self setplayercurrentstreak( 0 ); + } + + if ( !isdefined( self.pers["totalKillstreakCount"] ) ) + self.pers["totalKillstreakCount"] = 0; + + //Keep track of how many killstreaks have been earned in the current streak + if ( !isdefined( self.pers["killstreaksEarnedThisKillstreak"] ) ) + self.pers["killstreaksEarnedThisKillstreak"] = 0; + + if ( isdefined( level.usingScoreStreaks ) && level.usingScoreStreaks && !isdefined( self.pers["killstreak_quantity"] ) ) + self.pers["killstreak_quantity"] = []; + + if ( isdefined( level.usingScoreStreaks ) && level.usingScoreStreaks && !isdefined( self.pers["held_killstreak_ammo_count"] ) ) + self.pers["held_killstreak_ammo_count"] = []; + + if ( IsDefined( level.usingScoreStreaks ) && level.usingScoreStreaks && !IsDefined( self.pers["held_killstreak_clip_count"] ) ) + self.pers["held_killstreak_clip_count"] = []; + + if( !isDefined( self.pers["changed_class"] ) ) + self.pers["changed_class"] = false; + + if( !isDefined( self.pers["lastroundscore"] ) ) + self.pers["lastroundscore"] = 0; + + self.lastKillTime = 0; + + self.cur_death_streak = 0; + self disabledeathstreak(); + self.death_streak = 0; + self.kill_streak = 0; + self.gametype_kill_streak = 0; + self.spawnQueueIndex = -1; + self.deathTime = 0; + + self.aliveTimes = []; + for( index = 0; index < level.aliveTimeMaxCount; index++ ) + { + self.aliveTimes[index] = 0; + } + + self.aliveTimeCurrentIndex = 0; + + if ( level.onlineGame && !( isdefined( level.freerun ) && level.freerun ) ) + { + self.death_streak = self getDStat( "HighestStats", "death_streak" ); + self.kill_streak = self getDStat( "HighestStats", "kill_streak" ); + self.gametype_kill_streak = self persistence::stat_get_with_gametype( "kill_streak" ); + } + + self.lastGrenadeSuicideTime = -1; + + self.teamkillsThisRound = 0; + + if ( !isdefined( level.livesDoNotReset ) || !level.livesDoNotReset || !isdefined( self.pers["lives"] ) ) + { + self.pers["lives"] = level.numLives; + } + + // multi round FFA games in custom game mode should maintain team in-between rounds + if ( !level.teamBased ) + { + self.pers["team"] = undefined; + } + + self.hasSpawned = false; + self.waitingToSpawn = false; + self.wantSafeSpawn = false; + self.deathCount = 0; + + self.wasAliveAtMatchStart = false; + + level.players[level.players.size] = self; + + if( level.splitscreen ) + SetDvar( "splitscreen_playerNum", level.players.size ); + // removed underscore for debug CDC + // When joining a game in progress, if the game is at the post game state (scoreboard) the connecting player should spawn into intermission + if ( game["state"] == "postgame" ) + { + self.pers["needteam"] = 1; + self.pers["team"] = "spectator"; + self.team = self.sessionteam; + + self setClientUIVisibilityFlag( "hud_visible", 0 ); + + self [[level.spawnIntermission]](); + self closeInGameMenu(); + profilelog_endtiming( 4, "gs=" + game["state"] + " zom=" + SessionModeIsZombiesGame() ); + return; + } + + // don't count losses for CTF and S&D and War at each round. + if ( ( level.rankedMatch || level.wagerMatch || level.leagueMatch ) && !isdefined( self.pers["lossAlreadyReported"] ) ) + { + if ( level.leagueMatch ) + { + self recordLeaguePreLoser(); + } + + globallogic_score::updateLossStats( self ); + + self.pers["lossAlreadyReported"] = true; + } + if ((level.rankedMatch || level.leagueMatch) && !isDefined( self.pers["lateJoin"] ) ) + { + if (game["state"] == "playing" && !level.inPrematchPeriod ) + { + self.pers["lateJoin"] = true; + } + else + { + self.pers["lateJoin"] = false; + } + } + + // don't redo winstreak save to pers array for each round of round based games. + if ( !isdefined( self.pers["winstreakAlreadyCleared"] ) ) + { + self globallogic_score::backupAndClearWinStreaks(); + self.pers["winstreakAlreadyCleared"] = true; + } + + if( self istestclient() ) + { + self.pers[ "isBot" ] = true; + recordPlayerStats( self, "isBot", true); + } + + if ( level.rankedMatch || level.leagueMatch ) + { + self persistence::set_after_action_report_stat( "demoFileID", "0" ); + } + + level endon( "game_ended" ); + + if ( isdefined( level.hostMigrationTimer ) ) + self thread hostmigration::hostMigrationTimerThink(); + + if ( isdefined( self.pers["team"] ) ) + self.team = self.pers["team"]; + + if ( isdefined( self.pers["class"] ) ) + self.curClass = self.pers["class"]; + + if ( !isdefined( self.pers["team"] ) || isdefined( self.pers["needteam"] ) ) + { + // Don't set .sessionteam until we've gotten the assigned team from code, + // because it overrides the assigned team. + self.pers["needteam"] = undefined; + self.pers["team"] = "spectator"; + self.team = "spectator"; + self.sessionstate = "dead"; + + self globallogic_ui::updateObjectiveText(); + + [[level.spawnSpectator]](); + + [[level.autoassign]]( false ); + if ( level.rankedMatch || level.leagueMatch ) + { + self thread globallogic_spawn::kickIfDontSpawn(); + } + + if ( self.pers["team"] == "spectator" ) + { + self.sessionteam = "spectator"; + self thread spectate_player_watcher(); + } + + if ( level.teamBased ) + { + // set team and spectate permissions so the map shows waypoint info on connect + self.sessionteam = self.pers["team"]; + if ( !isAlive( self ) ) + self.statusicon = "hud_status_dead"; + self thread spectating::set_permissions(); + } + } + else if ( self.pers["team"] == "spectator" ) + { + self SetClientScriptMainMenu( game[ "menu_start_menu" ] ); + [[level.spawnSpectator]](); + self.sessionteam = "spectator"; + self.sessionstate = "spectator"; + self thread spectate_player_watcher(); + } + else + { + self.sessionteam = self.pers["team"]; + self.sessionstate = "dead"; + + self globallogic_ui::updateObjectiveText(); + + [[level.spawnSpectator]](); + + if ( globallogic_utils::isValidClass( self.pers["class"] ) || util::IsPropHuntGametype() ) + { + if ( !globallogic_utils::isValidClass( self.pers["class"] ) ) + { + self.pers["class"] = level.defaultClass; + self.curClass = level.defaultClass; + self SetClientScriptMainMenu( game[ "menu_start_menu" ] ); + } + self thread [[level.spawnClient]](); + } + else + { + self globallogic_ui::showMainMenuForTeam(); + } + + self thread spectating::set_permissions(); + } + + if ( self.sessionteam != "spectator" ) + { + self thread spawning::onSpawnPlayer(true); + } + + if ( level.forceRadar == 1 ) // radar always sweeping + { + self.pers["hasRadar"] = true; + self.hasSpyplane = true; + + if ( level.teambased ) + { + level.activeUAVs[self.team]++; + } + else + { + level.activeUAVs[self getEntityNumber()]++; + } + + level.activePlayerUAVs[self getEntityNumber()]++; + } + + if ( level.forceRadar == 2 ) // radar constant + { + self setClientUIVisibilityFlag( "g_compassShowEnemies", level.forceRadar ); + } + else + { + self SetClientUIVisibilityFlag( "g_compassShowEnemies", 0 ); + } + + profilelog_endtiming( 4, "gs=" + game["state"] + " zom=" + SessionModeIsZombiesGame() ); + + if ( isdefined( self.pers["isBot"] ) ) + return; + + self record_global_mp_stats_for_player_at_match_start(); + + //T7 - moved from load_shared to make sure this doesn't get set on CP until level.players is ready + num_con = getnumconnectedplayers(); + num_exp = getnumexpectedplayers(); + /#println( "all_players_connected(): getnumconnectedplayers=", num_con, "getnumexpectedplayers=", num_exp );#/ + + if(num_con == num_exp && (num_exp != 0)) + { + level flag::set( "all_players_connected" ); + // CODER_MOD: GMJ (08/28/08): Setting dvar for use by code + SetDvar( "all_players_are_connected", "1" ); + } + + globallogic_score::updateWeaponContractStart( self ); +} + +function record_global_mp_stats_for_player_at_match_start() +{ + // not sure if we even want/need this test + // if ( level.onlinegame && !SessionModeIsPrivate() ) ? + // if ( level.rankedMatch ) ? + // + // just copy from mp_stats, and it will do what it's supposed to + // (i.e. return something or 0) + + if( isdefined( level.disableStatTracking ) && level.disableStatTracking == true ) + { + return; + } + + startKills = self GetDStat( "playerstatslist", "kills", "statValue" ); + startDeaths = self GetDStat( "playerstatslist", "deaths", "statValue" ); + startWins = self GetDStat( "playerstatslist", "wins", "statValue" ); + startLosses = self GetDStat( "playerstatslist", "losses", "statValue" ); + startHits = self GetDStat( "playerstatslist", "hits", "statValue" ); + startMisses = self GetDStat( "playerstatslist", "misses", "statValue" ); + startTimePlayedTotal = self GetDStat( "playerstatslist", "time_played_total", "statValue" ); + startScore = self GetDStat( "playerstatslist", "score", "statValue" ); + startPrestige = self GetDStat( "playerstatslist", "plevel", "statValue" ); + startUnlockPoints = self GetDStat( "unlocks", 0); + + ties = self GetDStat( "playerstatslist", "ties", "statValue" ); + startGamesPlayed = startWins + startLosses + ties; + + self.startKills = startKills; + self.startHits = startHits; + self.totalMatchShots = 0; + + // note: xp_start - already exists - written in code - reads RANKXP + + recordPlayerStats( self, "startKills", startKills ); + recordPlayerStats( self, "startDeaths", startDeaths ); + recordPlayerStats( self, "startWins", startWins ); + recordPlayerStats( self, "startLosses", startLosses ); + recordPlayerStats( self, "startHits", startHits ); + recordPlayerStats( self, "startMisses", startMisses ); + recordPlayerStats( self, "startTimePlayedTotal", startTimePlayedTotal ); + recordPlayerStats( self, "startScore", startScore ); + recordPlayerStats( self, "startPrestige", startPrestige ); + recordPlayerStats( self, "startUnlockPoints", startUnlockPoints ); + recordPlayerStats( self, "startGamesPlayed", startGamesPlayed ); + + // temp commenting out; the getdstat calls here fail + lootXPBeforeMatch = self GetDStat( "AfterActionReportStats", "lootXPBeforeMatch" ); + cryptoKeysBeforeMatch = self GetDStat( "AfterActionReportStats", "cryptoKeysBeforeMatch" ); + recordPlayerStats( self, "lootXPBeforeMatch", lootXPBeforeMatch ); + recordPlayerStats( self, "cryptoKeysBeforeMatch", cryptoKeysBeforeMatch ); + +} + +function record_global_mp_stats_for_player_at_match_end() +{ + if( isdefined( level.disableStatTracking ) && level.disableStatTracking == true ) + { + return; + } + + endKills = self GetDStat( "playerstatslist", "kills", "statValue" ); + endDeaths = self GetDStat( "playerstatslist", "deaths", "statValue" ); + endWins = self GetDStat( "playerstatslist", "wins", "statValue" ); + endLosses = self GetDStat( "playerstatslist", "losses", "statValue" ); + endHits = self GetDStat( "playerstatslist", "hits", "statValue" ); + endMisses = self GetDStat( "playerstatslist", "misses", "statValue" ); + endTimePlayedTotal = self GetDStat( "playerstatslist", "time_played_total", "statValue" ); + endScore = self GetDStat( "playerstatslist", "score", "statValue" ); + endPrestige = self GetDStat( "playerstatslist", "plevel", "statValue" ); + endUnlockPoints = self GetDStat( "unlocks", 0); + + ties = self GetDStat( "playerstatslist", "ties", "statValue" ); + endGamesPlayed = endWins + endLosses + ties; + + // note: xp_end - already exists - written in code - reads RANKXP + + recordPlayerStats( self, "endKills", endKills ); + recordPlayerStats( self, "endDeaths", endDeaths ); + recordPlayerStats( self, "endWins", endWins ); + recordPlayerStats( self, "endLosses", endLosses ); + recordPlayerStats( self, "endHits", endHits ); + recordPlayerStats( self, "endMisses", endMisses ); + recordPlayerStats( self, "endTimePlayedTotal", endTimePlayedTotal ); + recordPlayerStats( self, "endScore", endScore ); + recordPlayerStats( self, "endPrestige", endPrestige ); + recordPlayerStats( self, "endUnlockPoints", endUnlockPoints ); + recordPlayerStats( self, "endGamesPlayed", endGamesPlayed ); + +} + +function record_misc_player_stats() +{ + if( isdefined( level.disableStatTracking ) && level.disableStatTracking == true ) + { + return; + } + + // common either for match end or on disconnect + recordPlayerStats( self, "UTCEndTimeSeconds", getUTC() ); + if( isdefined( self.weaponPickupsCount ) ) + { + recordPlayerStats( self, "weaponPickupsCount", self.weaponPickupsCount ); + } + if( isdefined( self.killcamsSkipped) ) + { + recordPlayerStats( self, "totalKillcamsSkipped", self.killcamsSkipped ); + } + if( isdefined( self.matchBonus) ) + { + recordPlayerStats( self, "matchXp", self.matchBonus ); + } + if( isdefined( self.killsdenied ) ) + { + recordPlayerStats( self, "killsDenied", self.killsdenied ); + } + if( isdefined( self.killsconfirmed ) ) + { + recordPlayerStats( self, "killsConfirmed", self.killsconfirmed ); + } + if( self IsSplitscreen() ) + { + recordPlayerStats( self, "isSplitscreen", true ); + } + if( self.objtime ) + { + recordPlayerStats( self, "objectiveTime", self.objtime ); + } + if( self.escorts ) + { + recordPlayerStats( self, "escortTime", self.escorts ); + } +} + +function spectate_player_watcher() +{ + self endon( "disconnect" ); + + // Setup the perks hud elem for the spectator if its not yet initalized + // We have to do it here, since the perk hudelem is generally initalized only on spawn, and the spectator will not able able to + // look at the perk loadout of some player. + if ( !level.splitscreen && !level.hardcoreMode && GetDvarint( "scr_showperksonspawn" ) == 1 && game["state"] != "postgame" && !isdefined( self.perkHudelem ) ) + { + if ( level.perksEnabled == 1 ) + { + self hud::showPerks( ); + } + } + + self.watchingActiveClient = true; + self.waitingForPlayersText = undefined; + + while ( 1 ) + { + if ( self.pers["team"] != "spectator" || level.gameEnded ) + { + self hud_message::clearShoutcasterWaitingMessage(); + if ( !( isdefined( level.inPrematchPeriod ) && level.inPrematchPeriod ) ) + { + self FreezeControls( false ); + } + self.watchingActiveClient = false; + break; + } + else + { + count = 0; + for ( i = 0; i < level.players.size; i++ ) + { + if ( level.players[i].team != "spectator" ) + { + count++; + break; + } + } + + if ( count > 0 ) + { + if ( !self.watchingActiveClient ) + { + self hud_message::clearShoutcasterWaitingMessage(); + self FreezeControls( false ); + + // Make sure that the player spawned notify happens when we start watching a player. + self LUINotifyEvent( &"player_spawned", 0 ); + } + + self.watchingActiveClient = true; + } + else + { + if ( self.watchingActiveClient ) + { + [[level.onSpawnSpectator]](); + self FreezeControls( true ); + self hud_message::setShoutcasterWaitingMessage(); + } + + self.watchingActiveClient = false; + } + + wait( 0.5 ); + } + } +} + +function Callback_PlayerMigrated() +{ +/# println( "Player " + self.name + " finished migrating at time " + gettime() ); #/ + + if ( isdefined( self.connected ) && self.connected ) + { + self globallogic_ui::updateObjectiveText(); +// self globallogic_ui::updateObjectiveText(); +// self updateMainMenu(); + +// if ( level.teambased ) +// self updateScores(); + } + + level.hostMigrationReturnedPlayerCount++; + if ( level.hostMigrationReturnedPlayerCount >= level.players.size * 2 / 3 ) + { + /# println( "2/3 of players have finished migrating" ); #/ + level notify( "hostmigration_enoughplayers" ); + } +} + +function Callback_PlayerDisconnect() +{ + profilelog_begintiming( 5, "ship" ); + + if ( game["state"] != "postgame" && !level.gameEnded ) + { + gameLength = game["timepassed"]; + self globallogic::bbPlayerMatchEnd( gameLength, "MP_PLAYER_DISCONNECT", 0 ); + + if( util::isRoundBased() ) + { + recordPlayerStats( self, "playerQuitRoundNumber", game["roundsplayed"] + 1 ); + } + + if( level.teambased ) + { + ourTeam = self.team; // only expecting: "allies" or "axis" + if( ourTeam == "allies" || ourTeam == "axis" ) + { + theirTeam = ""; + if( ourTeam == "allies" ) + { + theirTeam = "axis"; + } + else if( ourTeam == "axis" ) + { + theirTeam = "allies"; + } + recordPlayerStats( self, "playerQuitTeamScore", getTeamScore( ourTeam ) ); + recordPlayerStats( self, "playerQuitOpposingTeamScore", getTeamScore( theirTeam ) ); + } + } + + recordEndGameComScoreEventForPlayer( self, "disconnect" ); + + } + + self behaviorTracker::Finalize(); + + ArrayRemoveValue( level.players, self ); + + if ( level.splitscreen ) + { + players = level.players; + + if ( players.size <= 1 ) + level thread globallogic::forceEnd(); + + // passing number of players to menus in splitscreen to display leave or end game option + SetDvar( "splitscreen_playerNum", players.size ); + } + + if ( isdefined( self.score ) && isdefined( self.pers["team"] ) ) + { + /#print( "team: score " + self.pers["team"] + ":" + self.score );#/ + level.dropTeam += 1; + } + + [[level.onPlayerDisconnect]](); + + lpselfnum = self getEntityNumber(); + lpGuid = self getXuid(); + + if (self util::is_bot()) + { + lpGuid = "bot0"; + } + + logPrint("Q;" + lpGuid + ";" + lpselfnum + ";" + self.name + "\n"); + + self record_global_mp_stats_for_player_at_match_end(); + self record_special_move_data_for_life( undefined ); + + self record_misc_player_stats(); + + self gamerep::gameRepPlayerDisconnected(); + + for ( entry = 0; entry < level.players.size; entry++ ) + { + if ( level.players[entry] == self ) + { + while ( entry < level.players.size-1 ) + { + level.players[entry] = level.players[entry+1]; + entry++; + } + level.players[entry] = undefined; + break; + } + } + for ( entry = 0; entry < level.players.size; entry++ ) + { + if ( isdefined( level.players[entry].pers["killed_players"][self.name] ) ) + level.players[entry].pers["killed_players"][self.name] = undefined; + + if ( isdefined( level.players[entry].pers["killed_players_with_specialist"][self.name] ) ) + level.players[entry].pers["killed_players_with_specialist"][self.name] = undefined; + + if ( isdefined( level.players[entry].killedPlayersCurrent[self.name] ) ) + level.players[entry].killedPlayersCurrent[self.name] = undefined; + + if ( isdefined( level.players[entry].pers["killed_by"][self.name] ) ) + level.players[entry].pers["killed_by"][self.name] = undefined; + + if ( isdefined( level.players[entry].pers["nemesis_tracking"][self.name] ) ) + level.players[entry].pers["nemesis_tracking"][self.name] = undefined; + + // player that disconnected was our nemesis + if ( level.players[entry].pers["nemesis_name"] == self.name ) + { + level.players[entry] chooseNextBestNemesis(); + } + } + + if ( level.gameEnded ) + self globallogic::removeDisconnectedPlayerFromPlacement(); + + level thread globallogic::updateTeamStatus(); + level thread globallogic::updateAllAliveTimes(); + + profilelog_endtiming( 5, "gs=" + game["state"] + " zom=" + SessionModeIsZombiesGame() ); +} + +function Callback_PlayerMelee( eAttacker, iDamage, weapon, vOrigin, vDir, boneIndex, shieldHit, fromBehind ) +{ + hit = true; + + if ( level.teamBased && self.team == eAttacker.team ) + { + if ( level.friendlyfire == 0 ) // no one takes damage + { + hit = false; + } + } + + self finishMeleeHit( eAttacker, weapon, vOrigin, vDir, boneIndex, shieldHit, hit, fromBehind ); +} + +function chooseNextBestNemesis() +{ + nemesisArray = self.pers["nemesis_tracking"]; + nemesisArrayKeys = getArrayKeys( nemesisArray ); + nemesisAmount = 0; + nemesisName = ""; + + if ( nemesisArrayKeys.size > 0 ) + { + for ( i = 0; i < nemesisArrayKeys.size; i++ ) + { + nemesisArrayKey = nemesisArrayKeys[i]; + if ( nemesisArray[nemesisArrayKey] > nemesisAmount ) + { + nemesisName = nemesisArrayKey; + nemesisAmount = nemesisArray[nemesisArrayKey]; + } + + } + } + + self.pers["nemesis_name"] = nemesisName; + + if ( nemesisName != "" ) + { + playerIndex = 0; + for( ; playerIndex < level.players.size; playerIndex++ ) + { + if ( level.players[playerIndex].name == nemesisName ) + { + nemesisPlayer = level.players[playerIndex]; + self.pers["nemesis_rank"] = nemesisPlayer.pers["rank"]; + self.pers["nemesis_rankIcon"] = nemesisPlayer.pers["rankxp"]; + self.pers["nemesis_xp"] = nemesisPlayer.pers["prestige"]; + self.pers["nemesis_xuid"] = nemesisPlayer GetXUID(); + break; + } + } + } + else + { + self.pers["nemesis_xuid"] = ""; + } +} + +function custom_gamemodes_modified_damage( victim, eAttacker, iDamage, sMeansOfDeath, weapon, eInflictor, sHitLoc ) +{ + // regular public matches should early out + if ( level.onlinegame && !SessionModeIsPrivate() ) + { + return iDamage; + } + + if( isdefined( eAttacker) && isdefined( eAttacker.damageModifier ) ) + { + iDamage *= eAttacker.damageModifier; + } + if ( ( sMeansOfDeath == "MOD_PISTOL_BULLET" ) || ( sMeansOfDeath == "MOD_RIFLE_BULLET" ) ) + { + iDamage = int( iDamage * level.bulletDamageScalar ); + } + + return iDamage; +} + +function figure_out_attacker( eAttacker ) +{ + if ( isdefined(eAttacker) ) + { + if( isai(eAttacker) && isdefined( eAttacker.script_owner ) ) + { + team = self.team; + + if ( eAttacker.script_owner.team != team ) + eAttacker = eAttacker.script_owner; + } + + if( eAttacker.classname == "script_vehicle" && isdefined( eAttacker.owner ) ) + eAttacker = eAttacker.owner; + else if( eAttacker.classname == "auto_turret" && isdefined( eAttacker.owner ) ) + eAttacker = eAttacker.owner; + else if( eAttacker.classname == "actor_spawner_bo3_robot_grunt_assault_mp" && isdefined( eAttacker.owner ) ) + eAttacker = eAttacker.owner; + } + + return eAttacker; +} + +function player_damage_figure_out_weapon( weapon, eInflictor ) +{ + // explosive barrel/car detection + if ( weapon == level.weaponNone && isdefined( eInflictor ) ) + { + if ( isdefined( eInflictor.targetname ) && eInflictor.targetname == "explodable_barrel" ) + { + weapon = GetWeapon( "explodable_barrel" ); + } + else if ( isdefined( eInflictor.destructible_type ) && isSubStr( eInflictor.destructible_type, "vehicle_" ) ) + { + weapon = GetWeapon( "destructible_car" ); + } + else if( isdefined( eInflictor.scriptvehicletype ) ) + { + veh_weapon = GetWeapon( eInflictor.scriptvehicletype ); + if( isdefined( veh_weapon ) ) + { + weapon = veh_weapon; + } + } + } + + if ( isdefined( eInflictor ) && isdefined( eInflictor.script_noteworthy ) ) + { + if ( IsDefined( level.overrideWeaponFunc ) ) + { + weapon = [[level.overrideWeaponFunc]]( weapon, eInflictor.script_noteworthy ); + } + } + + return weapon; +} + +function figure_out_friendly_fire( victim ) +{ + if ( level.hardcoreMode && level.friendlyfire > 0 && isdefined( victim ) && victim.is_capturing_own_supply_drop === true ) + { + return 2; // FF 2 = reflect; design wants reflect friendly fire whenever a player is capturing their own supply drop + } + + if ( killstreaks::is_ricochet_protected( victim ) ) + { + return 2; + } + + // note, keep, non-gametype specific friendly fire logic above this line + + if ( isdefined( level.figure_out_gametype_friendly_fire ) ) + { + return [[ level.figure_out_gametype_friendly_fire ]]( victim ); + } + + return level.friendlyfire; +} + +function isPlayerImmuneToKillstreak( eAttacker, weapon ) +{ + if ( level.hardcoreMode ) + return false; + + if ( !isdefined( eAttacker ) ) + return false; + + if ( self != eAttacker ) + return false; + + return weapon.doNotDamageOwner; +} + + +function should_do_player_damage( eAttacker, weapon, sMeansOfDeath, iDFlags ) +{ + if ( game["state"] == "postgame" ) + return false; + + if ( self.sessionteam == "spectator" ) + return false; + + if ( isdefined( self.canDoCombat ) && !self.canDoCombat ) + return false; + + if ( isdefined( eAttacker ) && isPlayer( eAttacker ) && isdefined( eAttacker.canDoCombat ) && !eAttacker.canDoCombat ) + return false; + + if ( isdefined( level.hostMigrationTimer ) ) + return false; + + if ( level.onlyHeadShots ) + { + if ( sMeansOfDeath == "MOD_PISTOL_BULLET" || sMeansOfDeath == "MOD_RIFLE_BULLET" ) + return false; + } + + // Make all vehicle drivers invulnerable to bullets + if ( self vehicle::player_is_occupant_invulnerable( sMeansOfDeath ) ) + return false; + + if ( weapon.isSupplyDropWeapon && !weapon.isGrenadeWeapon && ( smeansofdeath != "MOD_TRIGGER_HURT" ) ) + return false; + + if ( self.scene_takedamage === false ) + return false; + + // prevent spawn kill wall bangs + if ( (iDFlags & 8) && self player::is_spawn_protected() ) + return false; + +return true; +} + +function apply_damage_to_armor( eInflictor, eAttacker, iDamage, sMeansOfDeath, weapon, sHitLoc, friendlyFire, ignore_round_start_friendly_fire ) +{ + victim = self; + + if ( friendlyFire && !player_damage_does_friendly_fire_damage_victim( ignore_round_start_friendly_fire ) ) + return iDamage; + + // Handle armor damage + if( IsDefined( victim.lightArmorHP ) ) + { + if ( weapon.ignoresLightArmor && sMeansOfDeath != "MOD_MELEE" ) + { + return iDamage; + } + else if ( weapon.meleeIgnoresLightArmor && sMeansOfDeath == "MOD_MELEE" ) + { + return iDamage; + } + // anything stuck to the player does health damage + else if( IsDefined( eInflictor ) && IsDefined( eInflictor.stuckToPlayer ) && eInflictor.stuckToPlayer == victim ) + { + iDamage = victim.health; + } + else + { + // Handle Armor Damage + // no armor damage in case of falling, melee, fmj or head shots + if ( sMeansOfDeath != "MOD_FALLING" + && !weapon_utils::isMeleeMOD( sMeansOfDeath ) + && !globallogic_utils::isHeadShot( weapon, sHitLoc, sMeansOfDeath, eAttacker ) + ) + { + victim armor::setLightArmorHP( victim.lightArmorHP - ( iDamage ) ); + + iDamage = 0; + if ( victim.lightArmorHP <= 0 ) + { + // since the light armor is gone, adjust the damage to be the excess damage that happens after the light armor hp is reduced + iDamage = abs( victim.lightArmorHP ); + armor::unsetLightArmor(); + } + } + } + } + + return iDamage; +} + +function make_sure_damage_is_not_zero( iDamage ) +{ + // Make sure at least one point of damage is done & give back 1 health because of this if you have power armor + if ( iDamage < 1 ) + { + if( ( self ability_util::gadget_power_armor_on() ) && isDefined( self.maxHealth ) && ( self.health < self.maxHealth ) ) + { + self.health += 1; + } + iDamage = 1; + } + + return int(iDamage); +} + +function modify_player_damage_friendlyfire( iDamage ) +{ + friendlyfire = [[ level.figure_out_friendly_fire ]]( self ); + + // half damage + if ( friendlyfire == 2 || friendlyfire == 3 ) + { + iDamage = int(iDamage * .5); + } + + return iDamage; +} + +function modify_player_damage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex ) +{ + if ( isdefined( self.overridePlayerDamage ) ) + { + iDamage = self [[self.overridePlayerDamage]]( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex ); + } + else if ( isdefined( level.overridePlayerDamage ) ) + { + iDamage = self [[level.overridePlayerDamage]]( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex ); + } + + assert(isdefined(iDamage), "You must return a value from a damage override function."); + + if ( isdefined( eAttacker ) ) + { + iDamage = loadout::cac_modified_damage( self, eAttacker, iDamage, sMeansOfDeath, weapon, eInflictor, sHitLoc ); + + if( isdefined( eAttacker.pickup_damage_scale ) && eAttacker.pickup_damage_scale_time > GetTime() ) + { + iDamage = iDamage * eAttacker.pickup_damage_scale; + } + } + iDamage = custom_gamemodes_modified_damage( self, eAttacker, iDamage, sMeansOfDeath, weapon, eInflictor, sHitLoc ); + + if ( level.onPlayerDamage != &globallogic::blank ) + { + modifiedDamage = [[level.onPlayerDamage]]( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime ); + + if ( isdefined( modifiedDamage ) ) + { + if ( modifiedDamage <= 0 ) + return; + + iDamage = modifiedDamage; + } + } + + if ( level.onlyHeadShots ) + { + if ( sMeansOfDeath == "MOD_HEAD_SHOT" ) + iDamage = 150; + } + + if ( weapon.damageAlwaysKillsPlayer ) + { + iDamage = self.maxHealth + 1; + } + + if ( sHitLoc == "riotshield" ) + { + if ( iDFlags & 32 ) + { + if ( !(iDFlags & 64) ) + { + iDamage *= 0.0; + } + } + else if ( iDFlags & 128 ) + { + if ( isdefined( eInflictor ) && isdefined( eInflictor.stuckToPlayer ) && eInflictor.stuckToPlayer == self ) + { + //does enough damage to shield carrier to ensure death + iDamage = self.maxhealth + 1; + } + } + } + + return int(iDamage); +} + +function modify_player_damage_meansofdeath( eInflictor, eAttacker, sMeansOfDeath, weapon, sHitLoc ) +{ + if ( globallogic_utils::isHeadShot( weapon, sHitLoc, sMeansOfDeath, eInflictor ) && isPlayer(eAttacker) && !weapon_utils::ismeleemod( sMeansOfDeath ) ) + { + sMeansOfDeath = "MOD_HEAD_SHOT"; + } + + if ( isdefined( eInflictor ) && isdefined( eInflictor.script_noteworthy ) ) + { + if ( eInflictor.script_noteworthy == "ragdoll_now" ) + { + sMeansOfDeath = "MOD_FALLING"; + } + } + + return sMeansOfDeath; +} + +function player_damage_update_attacker( eInflictor, eAttacker, sMeansOfDeath ) +{ + if ( isdefined( eInflictor ) && isPlayer( eAttacker ) && eAttacker == eInflictor ) + { + if ( sMeansOfDeath == "MOD_HEAD_SHOT" || sMeansOfDeath == "MOD_PISTOL_BULLET" || sMeansOfDeath == "MOD_RIFLE_BULLET" ) + { + //if ( isPlayer( eAttacker ) ) already tested for above + { + eAttacker.hits++; + } + } + } + + if ( isPlayer( eAttacker ) ) + eAttacker.pers["participation"]++; +} + +function player_is_spawn_protected_from_explosive( eInflictor, weapon, sMeansOfDeath ) +{ + if ( !self player::is_spawn_protected() ) + return false; + + // if we are using this as a impact damage only projectile then no protection + // we should probably add a bool to the weapon to indicate that it spawn protects + if ( weapon.explosionradius == 0 ) + return false; + + distSqr = ( ( isdefined( eInflictor ) && isdefined( self.lastSpawnPoint ) ) ? DistanceSquared( eInflictor.origin, self.lastSpawnPoint.origin ) : 0 ); + + // protect players from spawnkill grenades, tabun and incendiary + if ( distSqr < ( (250) * (250) ) ) + { + if ( sMeansOfDeath == "MOD_GRENADE" || sMeansOfDeath == "MOD_GRENADE_SPLASH" ) + { + return true; + } + + if ( sMeansOfDeath == "MOD_PROJECTILE" || sMeansOfDeath == "MOD_PROJECTILE_SPLASH" ) + { + return true; + } + + if ( sMeansOfDeath == "MOD_EXPLOSIVE" ) + { + return true; + } + } + + if ( killstreaks::is_killstreak_weapon( weapon ) ) + { + return true; + } + + return false; +} + +function player_damage_update_explosive_info( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex ) +{ + is_explosive_damage = loadout::isExplosiveDamage( sMeansOfDeath ); + + if ( is_explosive_damage ) + { + // protect players from spawnkill grenades, tabun, incendiaries, and scorestreaks + if ( self player_is_spawn_protected_from_explosive( eInflictor, weapon, sMeansOfDeath ) ) + { + return false; + } + + // protect players from their own non-player controlled killstreaks + if ( self isPlayerImmuneToKillstreak( eAttacker, weapon ) ) + { + return false; + } + } + + if ( isdefined( eInflictor ) && ( sMeansOfDeath == "MOD_GAS" || is_explosive_damage ) ) + { + self.explosiveInfo = []; + self.explosiveInfo["damageTime"] = getTime(); + self.explosiveInfo["damageId"] = eInflictor getEntityNumber(); + self.explosiveInfo["originalOwnerKill"] = false; + self.explosiveInfo["bulletPenetrationKill"] = false; + self.explosiveInfo["chainKill"] = false; + self.explosiveInfo["damageExplosiveKill"] = false; + self.explosiveInfo["chainKill"] = false; + self.explosiveInfo["cookedKill"] = false; + self.explosiveInfo["weapon"] = weapon; + self.explosiveInfo["originalowner"] = eInflictor.originalowner; + + isFrag = ( weapon.rootweapon.name == "frag_grenade" ); + + if ( isdefined( eAttacker ) && eAttacker != self ) + { + if ( isdefined( eAttacker ) && isdefined( eInflictor.owner ) && (weapon.name == "satchel_charge" || weapon.name == "claymore" || weapon.name == "bouncingbetty") ) + { + self.explosiveInfo["originalOwnerKill"] = (eInflictor.owner == self); + self.explosiveInfo["damageExplosiveKill"] = isdefined( eInflictor.wasDamaged ); + self.explosiveInfo["chainKill"] = isdefined( eInflictor.wasChained ); + self.explosiveInfo["wasJustPlanted"] = isdefined( eInflictor.wasJustPlanted ); + self.explosiveInfo["bulletPenetrationKill"] = isdefined( eInflictor.wasDamagedFromBulletPenetration ); + self.explosiveInfo["cookedKill"] = false; + } + if ( isdefined( eInflictor ) && isdefined( eInflictor.stuckToPlayer ) && weapon.projExplosionType == "grenade" ) + { + self.explosiveInfo["stuckToPlayer"] = eInflictor.stuckToPlayer; + } + if ( weapon.doStun ) + { + self.lastStunnedBy = eAttacker; + self.lastStunnedTime = self.iDFlagsTime; + } + if ( isdefined( eAttacker.lastGrenadeSuicideTime ) && eAttacker.lastGrenadeSuicideTime >= gettime() - 50 && isFrag ) + { + self.explosiveInfo["suicideGrenadeKill"] = true; + } + else + { + self.explosiveInfo["suicideGrenadeKill"] = false; + } + } + + if ( isFrag ) + { + self.explosiveInfo["cookedKill"] = isdefined( eInflictor.isCooked ); + self.explosiveInfo["throwbackKill"] = isdefined( eInflictor.threwBack ); + } + + if( isdefined( eAttacker ) && isPlayer( eAttacker ) && eAttacker != self ) + { + self globallogic_score::setInflictorStat( eInflictor, eAttacker, weapon ); + } + } + + if( sMeansOfDeath == "MOD_IMPACT" && isdefined( eAttacker ) && isPlayer( eAttacker ) && eAttacker != self ) + { + if ( weapon != level.weaponBallisticKnife ) + { + self globallogic_score::setInflictorStat( eInflictor, eAttacker, weapon ); + } + + if ( weapon.rootweapon.name == "hatchet" && isdefined( eInflictor ) ) + { + self.explosiveInfo["projectile_bounced"] = isdefined( eInflictor.bounced ); + } + } + + return true; +} + +function player_damage_is_friendly_fire_at_round_start() +{ + //check for friendly fire at the begining of the match. apply the damage to the attacker only + if( level.friendlyFireDelay && level.friendlyFireDelayTime >= ( ( ( gettime() - level.startTime ) - level.discardTime ) / 1000 ) ) + { + return true; + } + + return false; +} + +function player_damage_does_friendly_fire_damage_attacker( eAttacker, ignore_round_start_friendly_fire ) +{ + if ( !IsAlive( eAttacker ) ) + return false; + + friendlyfire = [[ level.figure_out_friendly_fire ]]( self ); + + if ( friendlyfire == 1 ) // the friendly takes damage + { + //check for friendly fire at the begining of the match. apply the damage to the attacker only + if ( player_damage_is_friendly_fire_at_round_start() && ( ignore_round_start_friendly_fire == false ) ) + { + return true; + } + } + + if ( friendlyfire == 2 ) // only the attacker takes damage + { + return true; + } + + if ( friendlyfire == 3 ) // both friendly and attacker take damage + { + return true; + } + + return false; +} + +function player_damage_does_friendly_fire_damage_victim( ignore_round_start_friendly_fire ) +{ + friendlyfire = [[ level.figure_out_friendly_fire ]]( self ); + + if ( friendlyfire == 1 ) // the friendly takes damage + { + //check for friendly fire at the begining of the match. apply the damage to the attacker only + if ( player_damage_is_friendly_fire_at_round_start() && ( ignore_round_start_friendly_fire == false ) ) + { + return false; + } + + return true; + } + + if ( friendlyfire == 3 ) // both friendly and attacker take damage + { + return true; + } + + return false; +} + +function player_damage_riotshield_hit( eAttacker, iDamage, sMeansOfDeath, weapon, attackerIsHittingTeammate) +{ + if (( sMeansOfDeath == "MOD_PISTOL_BULLET" || sMeansOfDeath == "MOD_RIFLE_BULLET" ) && + ( !killstreaks::is_killstreak_weapon( weapon )) && + ( !attackerIsHittingTeammate ) ) + { + if ( self.hasRiotShieldEquipped ) + { + if ( isPlayer( eAttacker )) + { + eAttacker.lastAttackedShieldPlayer = self; + eAttacker.lastAttackedShieldTime = getTime(); + } + + previous_shield_damage = self.shieldDamageBlocked; + self.shieldDamageBlocked += iDamage; + + if (( self.shieldDamageBlocked % 400 /*riotshield_damage_score_threshold*/ ) < ( previous_shield_damage % 400 /*riotshield_damage_score_threshold*/ )) + { + score_event = "shield_blocked_damage"; + + if (( self.shieldDamageBlocked > 2000 /*riotshield_damage_score_max*/ )) + { + score_event = "shield_blocked_damage_reduced"; + } + + if ( isdefined( level.scoreInfo[ score_event ]["value"] ) ) + { + // need to get the actual riot shield weapon here + self AddWeaponStat( level.weaponRiotshield, "score_from_blocked_damage", level.scoreInfo[ score_event ]["value"] ); + } + + scoreevents::processScoreEvent( score_event, self ); + } + } + } + +} + +function does_player_completely_avoid_damage(iDFlags, sHitLoc, weapon, friendlyFire, attackerIsHittingSelf, sMeansOfDeath ) +{ + if( iDFlags & 2048 ) + return true; + + if ( friendlyFire && level.friendlyfire == 0 ) + return true; + + if ( sHitLoc == "riotshield" ) + { + if ( !(iDFlags & (32|128)) ) + return true; + } + + + if( weapon.isEmp && sMeansOfDeath == "MOD_GRENADE_SPLASH" ) + { + if( self hasperk("specialty_immuneemp") ) + return true; + } + + if ( isdefined( level.playerAvoidDamageGameMode ) && self [[ level.playerAvoidDamageGameMode ]]( iDFlags, sHitLoc, weapon, friendlyFire, attackerIsHittingSelf, sMeansOfDeath ) ) + return true; + + return false; +} + +function player_damage_log( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex ) +{ + pixbeginevent( "PlayerDamage log" ); + +/# + // Do debug print if it's enabled + if(GetDvarint( "g_debugDamage")) + println("client:" + self getEntityNumber() + " health:" + self.health + " attacker:" + eAttacker.clientid + " inflictor is player:" + isPlayer(eInflictor) + " damage:" + iDamage + " hitLoc:" + sHitLoc); +#/ + + if(self.sessionstate != "dead") + { + lpselfnum = self getEntityNumber(); + lpselfname = self.name; + lpselfteam = self.team; + lpselfGuid = self getXuid(); + + if (self util::is_bot()) + { + lpselfGuid = "bot0"; + } + + lpattackerteam = ""; + lpattackerorigin = ( 0, 0, 0 ); + + if(isPlayer(eAttacker)) + { + lpattacknum = eAttacker getEntityNumber(); + lpattackGuid = eAttacker getXuid(); + + if (eAttacker util::is_bot()) + { + lpattackGuid = "bot0"; + } + + lpattackname = eAttacker.name; + lpattackerteam = eAttacker.team; + lpattackerorigin = eAttacker.origin; + isusingheropower = 0; + + if ( eAttacker ability_player::is_using_any_gadget() ) + isusingheropower = 1; + + bbPrint( "mpattacks", "gametime %d attackerspawnid %d attackerweapon %s attackerx %d attackery %d attackerz %d victimspawnid %d victimx %d victimy %d victimz %d damage %d damagetype %s damagelocation %s death %d isusingheropower %d", + gettime(), getplayerspawnid( eAttacker ), weapon.name, lpattackerorigin, getplayerspawnid( self ), self.origin, iDamage, sMeansOfDeath, sHitLoc, 0, isusingheropower ); + } + else + { + lpattacknum = -1; + lpattackGuid = ""; + lpattackname = ""; + lpattackerteam = "world"; + bbPrint( "mpattacks", "gametime %d attackerweapon %s victimspawnid %d victimx %d victimy %d victimz %d damage %d damagetype %s damagelocation %s death %d isusingheropower %d", + gettime(), weapon.name, getplayerspawnid( self ), self.origin, iDamage, sMeansOfDeath, sHitLoc, 0, 0 ); + } + logPrint("D;" + lpselfGuid + ";" + lpselfnum + ";" + lpselfteam + ";" + lpselfname + ";" + lpattackGuid + ";" + lpattacknum + ";" + lpattackerteam + ";" + lpattackname + ";" + weapon.name + ";" + iDamage + ";" + sMeansOfDeath + ";" + sHitLoc + "\n"); + } + + pixendevent(); // "END: PlayerDamage log" +} + +function should_allow_postgame_damage( sMeansOfDeath ) +{ + if ( sMeansOfDeath == "MOD_TRIGGER_HURT" || sMeansOfDeath == "MOD_CRUSH" ) + return true; + + return false; +} + +function do_post_game_damage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, boneIndex, vSurfaceNormal ) +{ + if ( game["state"] != "postgame" ) + return; + + if ( !should_allow_postgame_damage( sMeansOfDeath ) ) + return; + + // just pass it along + self finishPlayerDamage( eInflictor, eAttacker, iDamage, iDFlags, "MOD_POST_GAME", weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, boneIndex, vSurfaceNormal ); +} + +function Callback_PlayerDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, boneIndex, vSurfaceNormal ) +{ + profilelog_begintiming( 6, "ship" ); + + do_post_game_damage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, boneIndex, vSurfaceNormal ); + + if ( sMeansOfDeath == "MOD_CRUSH" && isdefined( eInflictor ) && ( eInflictor.deal_no_crush_damage === true ) ) + { + return; + } + + if ( isdefined( eInflictor ) && eInflictor.killstreakType === "siegebot" ) + { + if ( eInflictor.team === "neutral" ) + return; + } + + self.iDFlags = iDFlags; + self.iDFlagsTime = getTime(); + + // determine if we should treat owner damage as friendly fire + if ( !IsPlayer( eAttacker ) && isdefined( eAttacker ) && eAttacker.owner === self ) + { + treat_self_damage_as_friendly_fire = eAttacker.treat_owner_damage_as_friendly_fire; + } + + // determine if we should ignore_round_start_friendly_fire + ignore_round_start_friendly_fire = ( isdefined( eInflictor ) && ( sMeansOfDeath == "MOD_CRUSH" ) || sMeansOfDeath == "MOD_HIT_BY_OBJECT" ); + + eAttacker = figure_out_attacker( eAttacker ); + + // no damage from people who have dropped into laststand + if ( IsPlayer( eAttacker ) && ( isdefined( eAttacker.laststand ) && eAttacker.laststand ) ) + { + return; + } + + sMeansOfDeath = modify_player_damage_meansofdeath( eInflictor, eAttacker, sMeansOfDeath, weapon, sHitLoc ); + + if ( !(self should_do_player_damage( eAttacker, weapon, sMeansOfDeath, iDFlags )) ) + return; + + player_damage_update_attacker( eInflictor, eAttacker, sMeansOfDeath ); + + weapon = player_damage_figure_out_weapon( weapon, eInflictor ); + + pixbeginevent( "PlayerDamage flags/tweaks" ); + + // Don't do knockback if the damage direction was not specified + if( !isdefined( vDir ) ) + iDFlags |= 4; + + attackerIsHittingTeammate = isPlayer( eAttacker ) && ( self util::IsEnemyPlayer( eAttacker ) == false ); + attackerIsHittingSelf = IsPlayer( eAttacker ) && (self == eAttacker); + + friendlyFire = ( ( attackerIsHittingSelf && treat_self_damage_as_friendly_fire === true ) // some killstreaks treak owner damage as friendly-fire + || ( level.teamBased && !attackerIsHittingSelf && attackerIsHittingTeammate ) ); // teammates are always friendly-fire, but self is handled above + + pixendevent(); // "END: PlayerDamage flags/tweaks" + + iDamage = modify_player_damage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex ); + if ( friendlyFire ) + { + iDamage = modify_player_damage_friendlyfire( iDamage ); + } + + if( ( isdefined( self.power_armor_took_damage ) && self.power_armor_took_damage ) ) + { + iDFlags |= 1024; + } + + if ( sHitLoc == "riotshield" ) + { + // do we want all of the damage modifiers that get applied for the player to get applied to this damage? + // or friendly fire? + player_damage_riotshield_hit( eAttacker, iDamage, sMeansOfDeath, weapon, attackerIsHittingTeammate); + } + + // check for completely getting out of the damage + if ( self does_player_completely_avoid_damage(iDFlags, sHitLoc, weapon, friendlyFire, attackerIsHittingSelf, sMeansOfDeath ) ) + { + return; + } + + // do we want this called pre or post damage application? + self callback::callback( #"on_player_damage" ); + + armor = self armor::getArmor(); + + iDamage = apply_damage_to_armor( eInflictor, eAttacker, iDamage, sMeansOfDeath, weapon, sHitLoc, friendlyFire, ignore_round_start_friendly_fire ); + iDamage = make_sure_damage_is_not_zero( iDamage ); + + armor_damaged = (armor != self armor::getArmor()); + + // this must be below the damage modification functions as they use this to determine riotshield hits + if ( sHitLoc == "riotshield" ) + { + sHitLoc = "none"; // code ignores any damage to a "shield" bodypart. + } + + if ( !player_damage_update_explosive_info( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex ) ) + return; + + prevHealthRatio = self.health / self.maxhealth; + + if ( friendlyFire ) + { + pixmarker( "BEGIN: PlayerDamage player" ); // profs automatically end when the function returns + + if ( player_damage_does_friendly_fire_damage_victim( ignore_round_start_friendly_fire ) ) + { + self.lastDamageWasFromEnemy = false; + + self finishPlayerDamageWrapper(eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, boneIndex, vSurfaceNormal); + } + else if ( weapon.forceDamageShellshockAndRumble ) + { + self damageShellshockAndRumble( eAttacker, eInflictor, weapon, sMeansOfDeath, iDamage ); + } + + if ( player_damage_does_friendly_fire_damage_attacker( eAttacker, ignore_round_start_friendly_fire ) ) + { + eAttacker.lastDamageWasFromEnemy = false; + + eAttacker.friendlydamage = true; + eAttacker finishPlayerDamageWrapper(eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, boneIndex, vSurfaceNormal); + eAttacker.friendlydamage = undefined; + } + + pixmarker( "END: PlayerDamage player" ); + } + else + { + behaviorTracker::UpdatePlayerDamage( eAttacker, self, iDamage ); + + self.lastAttackWeapon = weapon; + + giveAttackerAndInflictorOwnerAssist( eAttacker, eInflictor, iDamage, sMeansOfDeath, weapon ); + + if ( isdefined( eAttacker ) ) + level.lastLegitimateAttacker = eAttacker; + + if ( ( sMeansOfDeath == "MOD_GRENADE" || sMeansOfDeath == "MOD_GRENADE_SPLASH" ) && isdefined( eInflictor ) && isdefined( eInflictor.isCooked ) ) + self.wasCooked = getTime(); + else + self.wasCooked = undefined; + + self.lastDamageWasFromEnemy = (isdefined( eAttacker ) && (eAttacker != self)); + + if ( self.lastDamageWasFromEnemy ) + { + if ( isplayer( eAttacker ) ) + { + if ( isdefined ( eAttacker.damagedPlayers[ self.clientId ] ) == false ) + eAttacker.damagedPlayers[ self.clientId ] = spawnstruct(); + + eAttacker.damagedPlayers[ self.clientId ].time = getTime(); + eAttacker.damagedPlayers[ self.clientId ].entity = self; + } + } + + if( isPlayer( eAttacker ) && isDefined(weapon.gadget_type) && weapon.gadget_type == 14 ) + { + if( isDefined(eAttacker.heroweaponHits) ) + { + eAttacker.heroweaponHits++; + } + } + + self finishPlayerDamageWrapper(eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, boneIndex, vSurfaceNormal); + } + + if ( isdefined( eAttacker ) && !attackerIsHittingSelf ) + { + if ( damagefeedback::doDamageFeedback( weapon, eInflictor, iDamage, sMeansOfDeath ) ) + { + // the perk feedback should be shown only if the enemy is damaged and not killed. + if ( iDamage > 0 && self.health > 0 ) + { + perkFeedback = doPerkFeedBack( self, weapon, sMeansOfDeath, eInflictor, armor_damaged ); + } + + eAttacker thread damagefeedback::update( sMeansOfDeath, eInflictor, perkFeedback, weapon, self, psOffsetTime, sHitLoc ); + } + } + + if( !isdefined(eAttacker) || !friendlyFire || ( isdefined( level.hardcoreMode ) && level.hardcoreMode ) ) + { + if ( isdefined( level.customPlayPainSound ) ) + self [[ level.customPlayPainSound ]]( sMeansOfDeath ); + else + self battlechatter::pain_vox( sMeansOfDeath ); + } + + self.hasDoneCombat = true; + + if( weapon.isEmp && sMeansOfDeath == "MOD_GRENADE_SPLASH" ) + { + if( !self hasperk("specialty_immuneemp") ) + { + self notify( "emp_grenaded", eAttacker, vPoint ); + } + } + + if ( isdefined( eAttacker ) && eAttacker != self && !friendlyFire ) + level.useStartSpawns = false; + + player_damage_log( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex ); + + profilelog_endtiming( 6, "gs=" + game["state"] + " zom=" + SessionModeIsZombiesGame() ); +} + +function resetAttackerList() +{ + self.attackers = []; + self.attackerData = []; + self.attackerDamage = []; + self.firstTimeDamaged = 0; +} + +function resetAttackersThisSpawnList() +{ + self.attackersThisSpawn = []; +} + +function doPerkFeedBack( player, weapon, sMeansOfDeath, eInflictor, armor_damaged ) +{ + perkFeedback = undefined; + hasTacticalMask = loadout::hasTacticalMask( player ); + hasFlakJacket = ( player HasPerk( "specialty_flakjacket" ) ); + isExplosiveDamage = loadout::isExplosiveDamage( sMeansOfDeath ); + isFlashOrStunDamage = weapon_utils::isFlashOrStunDamage( weapon, sMeansOfDeath ); + + if ( isFlashOrStunDamage && hasTacticalMask ) + { + perkFeedback = "tacticalMask"; + } + else if ( player HasPerk( "specialty_fireproof" ) && loadout::isFireDamage( weapon, sMeansOfDeath ) ) + { + perkFeedback = "flakjacket"; + } + else if ( isExplosiveDamage && hasFlakJacket && !weapon.ignoresFlakJacket && ( !isAIKillstreakDamage( weapon, eInflictor ) ) ) + { + perkFeedback = "flakjacket"; + } + else if ( armor_damaged ) + { + perkFeedback = "armor"; + } + + return perkFeedback; +} + +function isAIKillstreakDamage( weapon, eInflictor ) +{ + if ( weapon.isAIKillstreakDamage ) + { + if ( weapon.name != "ai_tank_drone_rocket" || isdefined( eInflictor.firedByAI ) ) + { + return true; + } + } + + return false; +} + +function finishPlayerDamageWrapper( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, boneIndex, vSurfaceNormal ) +{ + pixbeginevent("finishPlayerDamageWrapper"); + + if( !level.console && iDFlags & 8 && isplayer ( eAttacker ) ) + { + /# + println("penetrated:" + self getEntityNumber() + " health:" + self.health + " attacker:" + eAttacker.clientid + " inflictor is player:" + isPlayer(eInflictor) + " damage:" + iDamage + " hitLoc:" + sHitLoc); + #/ + eAttacker AddPlayerStat( "penetration_shots", 1 ); + } + + if ( GetDvarString( "scr_csmode" ) != "" ) + self shellShock( "damage_mp", 0.2 ); + + if ( isdefined( level.customDamageShellshockAndRumble ) ) + self [[ level.customDamageShellshockAndRumble ]]( eAttacker, eInflictor, weapon, sMeansOfDeath, iDamage, vPoint ); + else + self damageShellshockAndRumble( eAttacker, eInflictor, weapon, sMeansOfDeath, iDamage ); + + self ability_power::power_loss_event_took_damage( eAttacker, eInflictor, weapon, sMeansOfDeath, iDamage ); + + if( isPlayer( eAttacker) ) + { + self.lastShotBy = eAttacker.clientid; + } + + if ( sMeansOfDeath == "MOD_BURNED" ) + { + self burnplayer::TakingBurnDamage( eAttacker, weapon, sMeansOfDeath ); + } + + self.gadget_was_active_last_damage = self GadgetIsActive( 0 ); + + self finishPlayerDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, boneIndex, vSurfaceNormal ); + + pixendevent(); +} + +function allowedAssistWeapon( weapon ) +{ + if ( !killstreaks::is_killstreak_weapon( weapon ) ) + return true; + + if (killstreaks::is_killstreak_weapon_assist_allowed( weapon ) ) + return true; + + return false; +} + +function PlayerKilled_Killstreaks( attacker, weapon ) +{ + if( !isdefined( self.switching_teams ) ) + { + // if team killed we reset kill streak, but dont count death and death streak + if ( isPlayer( attacker ) && level.teamBased && ( attacker != self ) && ( self.team == attacker.team ) ) + { + + self.pers["cur_kill_streak"] = 0; + self.pers["cur_total_kill_streak"] = 0; + self.pers["totalKillstreakCount"] = 0; + self.pers["killstreaksEarnedThisKillstreak"] = 0; + self setplayercurrentstreak( 0 ); + } + else + { + self globallogic_score::incPersStat( "deaths", 1, true, true ); + self.deaths = self globallogic_score::getPersStat( "deaths" ); + self UpdateStatRatio( "kdratio", "kills", "deaths" ); + + if( self.pers["cur_kill_streak"] > self.pers["best_kill_streak"] ) + self.pers["best_kill_streak"] = self.pers["cur_kill_streak"]; + + // need to keep the current killstreak to see if this was a buzzkill later + self.pers["kill_streak_before_death"] = self.pers["cur_kill_streak"]; + + + self.pers["cur_kill_streak"] = 0; + self.pers["cur_total_kill_streak"] = 0; + self.pers["totalKillstreakCount"] = 0; + self.pers["killstreaksEarnedThisKillstreak"] = 0; + self setplayercurrentstreak( 0 ); + + self.cur_death_streak++; + + if ( self.cur_death_streak > self.death_streak ) + { + if ( level.rankedMatch && !level.disableStatTracking ) + { + self setDStat( "HighestStats", "death_streak", self.cur_death_streak ); + } + self.death_streak = self.cur_death_streak; + } + + if( self.cur_death_streak >= GetDvarint( "perk_deathStreakCountRequired" ) ) + { + self enabledeathstreak(); + } + } + } + else + { + self.pers["totalKillstreakCount"] = 0; + self.pers["killstreaksEarnedThisKillstreak"] = 0; + } + + if ( !SessionModeIsZombiesGame() && killstreaks::is_killstreak_weapon( weapon ) ) + { + level.globalKillstreaksDeathsFrom++; + } +} + +function PlayerKilled_WeaponStats( attacker, weapon, sMeansOfDeath, wasInLastStand, lastWeaponBeforeDroppingIntoLastStand, inflictor ) +{ + // Don't increment weapon stats for team kills or deaths + if ( isPlayer( attacker ) && attacker != self && ( !level.teamBased || ( level.teamBased && self.team != attacker.team ) ) ) + { + attackerWeaponPickedUp = false; + if( isdefined( attacker.pickedUpWeapons ) && isdefined( attacker.pickedUpWeapons[weapon] ) ) + { + attackerWeaponPickedUp = true; + } + self AddWeaponStat( weapon, "deaths", 1, self.class_num, attackerWeaponPickedUp, undefined, self.primaryLoadoutGunSmithVariantIndex, self.secondaryLoadoutGunSmithVariantIndex ); + + if ( wasInLastStand && isdefined( lastWeaponBeforeDroppingIntoLastStand ) ) + victim_weapon = lastWeaponBeforeDroppingIntoLastStand; + else + victim_weapon = self.lastdroppableweapon; + + if ( isdefined( victim_weapon ) ) + { + victimWeaponPickedUp = false; + if( isdefined( self.pickedUpWeapons ) && isdefined( self.pickedUpWeapons[victim_weapon] ) ) + { + victimWeaponPickedUp = true; + } + self AddWeaponStat( victim_weapon, "deathsDuringUse", 1, self.class_num, victimWeaponPickedUp, undefined, self.primaryLoadoutGunSmithVariantIndex, self.secondaryLoadoutGunSmithVariantIndex ); + } + + + recordWeaponStatKills = true; + if ( ( attacker.isThief === true ) && isdefined( weapon ) && ( weapon.isHeroWeapon === true ) ) + { + recordWeaponStatKills = false; // Blackjack's Rogue kills are tracked as specialiststats[9].stats.kills_weapon + } + + if ( sMeansOfDeath != "MOD_FALLING" && recordWeaponStatKills ) + { + if ( weapon.name == "explosive_bolt" && IsDefined( inflictor ) && IsDefined( inflictor.ownerWeaponAtLaunch ) && inflictor.ownerAdsAtLaunch ) + { + inflictorOwnerWeaponAtLaunchPickedUp = false; + if( isdefined( attacker.pickedUpWeapons ) && isdefined( attacker.pickedUpWeapons[inflictor.ownerWeaponAtLaunch] ) ) + { + inflictorOwnerWeaponAtLaunchPickedUp = true; // ever the case? + } + attacker AddWeaponStat( inflictor.ownerWeaponAtLaunch, "kills", 1, attacker.class_num, inflictorOwnerWeaponAtLaunchPickedUp, true, attacker.primaryLoadoutGunSmithVariantIndex, attacker.secondaryLoadoutGunSmithVariantIndex ); + } + else + { + if ( isdefined( attacker ) && isdefined( attacker.class_num ) ) + attacker AddWeaponStat( weapon, "kills", 1, attacker.class_num, attackerWeaponPickedUp, undefined, attacker.primaryLoadoutGunSmithVariantIndex, attacker.secondaryLoadoutGunSmithVariantIndex ); + } + } + + if ( sMeansOfDeath == "MOD_HEAD_SHOT" ) + { + attacker AddWeaponStat( weapon, "headshots", 1, attacker.class_num, attackerWeaponPickedUp, undefined, attacker.primaryLoadoutGunSmithVariantIndex, attacker.secondaryLoadoutGunSmithVariantIndex ); + } + + if ( sMeansOfDeath == "MOD_PROJECTILE" || ( ( sMeansOfDeath == "MOD_GRENADE" || sMeansOfDeath == "MOD_IMPACT" ) && weapon.rootWeapon.statIndex == level.weaponLauncherEx41.statIndex ) ) + { + attacker AddWeaponStat( weapon, "direct_hit_kills", 1 ); + } + + victimIsRoulette = ( self.isRoulette === true ); + if ( self ability_player::gadget_CheckHeroAbilityKill( attacker ) && !victimIsRoulette ) + { + attacker AddWeaponStat( attacker.heroAbility, "kills_while_active", 1 ); + } + } +} + +function PlayerKilled_Obituary( attacker, eInflictor, weapon, sMeansOfDeath ) +{ + if ( !isplayer( attacker ) || ( self util::IsEnemyPlayer( attacker ) == false ) || ( isdefined ( weapon ) && killstreaks::is_killstreak_weapon( weapon ) ) ) + { + level notify( "reset_obituary_count" ); + level.lastObituaryPlayerCount = 0; + level.lastObituaryPlayer = undefined; + } + else + { + if ( isdefined( level.lastObituaryPlayer ) && level.lastObituaryPlayer == attacker ) + { + level.lastObituaryPlayerCount++; + } + else + { + level notify( "reset_obituary_count" ); + level.lastObituaryPlayer = attacker; + level.lastObituaryPlayerCount = 1; + } + + level thread scoreevents::decrementLastObituaryPlayerCountAfterFade(); + + if ( level.lastObituaryPlayerCount >= 4 ) + { + level notify( "reset_obituary_count" ); + level.lastObituaryPlayerCount = 0; + level.lastObituaryPlayer = undefined; + self thread scoreevents::uninterruptedObitFeedKills( attacker, weapon ); + } + } + + if ( !isplayer( attacker ) || ( isdefined( weapon ) && !killstreaks::is_killstreak_weapon( weapon ) ) ) + { + behaviorTracker::UpdatePlayerKilled( attacker, self ); + } + + overrideEntityCamera = killstreaks::should_override_entity_camera_in_demo( attacker, weapon ); + + if( isdefined( eInflictor ) && ( eInflictor.archetype === "robot" ) ) + { + if( sMeansOfDeath == "MOD_HIT_BY_OBJECT" ) + weapon = GetWeapon( "combat_robot_marker" ); + sMeansOfDeath = "MOD_RIFLE_BULLET"; + } + // send out an obituary message to all clients about the kill + if( level.teamBased && isdefined( attacker.pers ) && self.team == attacker.team && sMeansOfDeath == "MOD_GRENADE" && level.friendlyfire == 0 ) + { + obituary(self, self, weapon, sMeansOfDeath); + demo::bookmark( "kill", gettime(), self, self, 0, eInflictor, overrideEntityCamera ); + } + else + { + obituary(self, attacker, weapon, sMeansOfDeath); + demo::bookmark( "kill", gettime(), attacker, self, 0, eInflictor, overrideEntityCamera ); + } +} + +function PlayerKilled_Suicide( eInflictor, attacker, sMeansOfDeath, weapon, sHitLoc ) +{ + awardAssists = false; + self.suicide = false; + + // switching teams + if ( isdefined( self.switching_teams ) ) + { + + if ( !level.teamBased && ( isdefined( level.teams[ self.leaving_team ] ) && isdefined( level.teams[ self.joining_team ] ) && level.teams[ self.leaving_team ] != level.teams[ self.joining_team ] ) ) + { + playerCounts = self teams::count_players(); + playerCounts[self.leaving_team]--; + playerCounts[self.joining_team]++; + + if( (playerCounts[self.joining_team] - playerCounts[self.leaving_team]) > 1 ) + { + scoreevents::processScoreEvent( "suicide", self ); + self thread rank::giveRankXP( "suicide" ); + self globallogic_score::incPersStat( "suicides", 1 ); + self.suicides = self globallogic_score::getPersStat( "suicides" ); + self.suicide = true; + } + } + } + else + { + scoreevents::processScoreEvent( "suicide", self ); + self globallogic_score::incPersStat( "suicides", 1 ); + self.suicides = self globallogic_score::getPersStat( "suicides" ); + + if ( sMeansOfDeath == "MOD_SUICIDE" && sHitLoc == "none" && self.throwingGrenade ) + { + self.lastGrenadeSuicideTime = gettime(); + } + + if ( level.maxSuicidesBeforeKick > 0 && level.maxSuicidesBeforeKick <= self.suicides ) + { + // should change "teamKillKicked" to just kicked for the next game + self notify( "teamKillKicked" ); + self SuicideKick(); + } + + //Check for player death related battlechatter + thread battlechatter::on_player_suicide_or_team_kill( self, "suicide" ); //Play suicide battlechatter + + //check if assist points should be awarded + awardAssists = true; + self.suicide = true; + } + + if( isdefined( self.friendlydamage ) ) + { + self iPrintLn(&"MP_FRIENDLY_FIRE_WILL_NOT"); + if ( level.teamKillPointLoss ) + { + scoreSub = self [[level.getTeamKillScore]]( eInflictor, attacker, sMeansOfDeath, weapon); + + score = globallogic_score::_getPlayerScore( attacker ) - scoreSub; + + if ( score < 0 ) + score = 0; + + globallogic_score::_setPlayerScore( attacker, score ); + } + } + + return awardAssists; +} + +function PlayerKilled_TeamKill( eInflictor, attacker, sMeansOfDeath, weapon, sHitLoc ) +{ + scoreevents::processScoreEvent( "team_kill", attacker ); + + self.teamKilled = true; + + if ( !IgnoreTeamKills( weapon, sMeansOfDeath, eInflictor ) ) + { + teamkill_penalty = self [[level.getTeamKillPenalty]]( eInflictor, attacker, sMeansOfDeath, weapon); + + attacker globallogic_score::incPersStat( "teamkills_nostats", teamkill_penalty, false ); + attacker globallogic_score::incPersStat( "teamkills", 1 ); //save team kills to player stats + attacker.teamkillsThisRound++; + + if ( level.teamKillPointLoss ) + { + scoreSub = self [[level.getTeamKillScore]]( eInflictor, attacker, sMeansOfDeath, weapon); + + score = globallogic_score::_getPlayerScore( attacker ) - scoreSub; + + if ( score < 0 ) + { + score = 0; + } + + globallogic_score::_setPlayerScore( attacker, score ); + } + + if ( globallogic_utils::getTimePassed() < 5000 ) + teamKillDelay = 1; + else if ( attacker.pers["teamkills_nostats"] > 1 && globallogic_utils::getTimePassed() < (8000 + (attacker.pers["teamkills_nostats"] * 1000)) ) + teamKillDelay = 1; + else + teamKillDelay = attacker TeamKillDelay(); + + if ( teamKillDelay > 0 ) + { + attacker.teamKillPunish = true; + attacker thread wait_and_suicide(); // can't eject the teamkilling player same frame bc it purges EV_FIRE_WEAPON fx + + if ( attacker ShouldTeamKillKick(teamKillDelay) ) + { + // should change "teamKillKicked" to just kicked for the next game + attacker notify( "teamKillKicked" ); + attacker thread TeamKillKick(); + } + + attacker thread reduceTeamKillsOverTime(); + } + + //Play teamkill battlechatter + if( isPlayer( attacker ) ) + thread battlechatter::on_player_suicide_or_team_kill( attacker, "teamkill" ); + } +} + +function wait_and_suicide() // self == player +{ + self endon( "disconnect" ); + self util::freeze_player_controls( true ); + + wait .25; + + self suicide(); +} + +function PlayerKilled_AwardAssists( eInflictor, attacker, weapon, lpattackteam ) +{ + pixbeginevent( "PlayerKilled assists" ); + + if ( isdefined( self.attackers ) ) + { + for ( j = 0; j < self.attackers.size; j++ ) + { + player = self.attackers[j]; + + if ( !isdefined( player ) ) + continue; + + if ( player == attacker ) + continue; + + if ( player.team != lpattackteam ) + continue; + + damage_done = self.attackerDamage[player.clientId].damage; + player thread globallogic_score::processAssist( self, damage_done, self.attackerDamage[player.clientId].weapon ); + } + } + + if ( level.teamBased ) + { + self globallogic_score::processKillstreakAssists( attacker, eInflictor, weapon ); + } + + if ( isdefined( self.lastAttackedShieldPlayer ) && isdefined( self.lastAttackedShieldTime ) && self.lastAttackedShieldPlayer != attacker ) + { + if ( gettime() - self.lastAttackedShieldTime < 4000 ) + { + self.lastAttackedShieldPlayer thread globallogic_score::processShieldAssist( self ); + } + } + + pixendevent(); //"END: PlayerKilled assists" +} + +function PlayerKilled_Kill( eInflictor, attacker, sMeansOfDeath, weapon, sHitLoc ) +{ + if( !isdefined( killstreaks::get_killstreak_for_weapon( weapon ) ) || ( isdefined( level.killstreaksGiveGameScore ) && level.killstreaksGiveGameScore ) ) + globallogic_score::incTotalKills(attacker.team); + + if( GetDvarInt( "teamOpsEnabled" ) == 1 ) + { + if( isdefined( eInflictor ) && ( isdefined( eInflictor.teamops ) && eInflictor.teamops ) ) + { + if( !isdefined( killstreaks::get_killstreak_for_weapon( weapon ) ) || ( isdefined( level.killstreaksGiveGameScore ) && level.killstreaksGiveGameScore ) ) + globallogic_score::giveTeamScore( "kill", attacker.team, undefined, self ); + return; + } + } + + attacker thread globallogic_score::giveKillStats( sMeansOfDeath, weapon, self ); + + + if ( isAlive( attacker ) ) + { + pixbeginevent("killstreak"); + + if ( !isdefined( eInflictor ) || !isdefined( eInflictor.requiredDeathCount ) || attacker.deathCount == eInflictor.requiredDeathCount ) + { + shouldGiveKillstreak = killstreaks::should_give_killstreak( weapon ); + //attacker thread _properks::earnedAKill(); + + if ( shouldGiveKillstreak ) + { + attacker killstreaks::add_to_killstreak_count( weapon ); + } + + attacker.pers["cur_total_kill_streak"]++; + attacker setplayercurrentstreak( attacker.pers["cur_total_kill_streak"] ); + + //Kills gotten through killstreak weapons should not the players killstreak + if ( isdefined( level.killstreaks ) && shouldGiveKillstreak ) + { + attacker.pers["cur_kill_streak"]++; + + if ( attacker.pers["cur_kill_streak"] >= 2 ) + { + if ( attacker.pers["cur_kill_streak"] == 10 ) + { + attacker challenges::killstreakTen(); + } + if ( attacker.pers["cur_kill_streak"] <= 30 ) + { + scoreevents::processScoreEvent( "killstreak_" + attacker.pers["cur_kill_streak"], attacker, self, weapon ); + + if ( attacker.pers["cur_kill_streak"] == 30 ) + { + attacker challenges::killstreak_30_noscorestreaks(); + } + } + else + { + scoreevents::processScoreEvent( "killstreak_more_than_30", attacker, self, weapon ); + } + } + + if ( !isdefined( level.usingMomentum ) || !level.usingMomentum ) + { + if( GetDvarInt( "teamOpsEnabled" ) == 0 ) + attacker thread killstreaks::give_for_streak(); + } + } + } + + pixendevent(); // "killstreak" + } + + if ( attacker.pers["cur_kill_streak"] > attacker.kill_streak ) + { + if ( level.rankedMatch && !level.disableStatTracking ) + { + attacker setDStat( "HighestStats", "kill_streak", attacker.pers["totalKillstreakCount"] ); + } + attacker.kill_streak = attacker.pers["cur_kill_streak"]; + } + + + if ( attacker.pers["cur_kill_streak"] > attacker.gametype_kill_streak ) + { + attacker persistence::stat_set_with_gametype( "kill_streak", attacker.pers["cur_kill_streak"] ); + attacker.gametype_kill_streak = attacker.pers["cur_kill_streak"]; + } + + killstreak = killstreaks::get_killstreak_for_weapon( weapon ); + + if ( isdefined( killstreak ) ) + { + if ( scoreevents::isRegisteredEvent( killstreak ) ) + { + scoreevents::processScoreEvent( killstreak, attacker, self, weapon ); + } + + if( isdefined( eInflictor ) && ( killstreak == "dart" || killstreak == "inventory_dart" ) ) + { + eInflictor notify( "veh_collision" ); + } + } + else + { + scoreevents::processScoreEvent( "kill", attacker, self, weapon ); + + // if ( sMeansOfDeath == "MOD_HEAD_SHOT" || ( sMeansOfDeath == "MOD_IMPACT" && sHitLoc == "head" ) ) // TODO: add back when applicable LOOT6 weapon is ready + if ( sMeansOfDeath == "MOD_HEAD_SHOT" ) + { + scoreevents::processScoreEvent( "headshot", attacker, self, weapon ); + attacker util::player_contract_event( "headshot" ); + } + else if ( weapon_utils::isMeleeMOD( sMeansOfDeath ) ) + { + scoreevents::processScoreEvent( "melee_kill", attacker, self, weapon ); + } + } + + attacker thread globallogic_score::trackAttackerKill( self.name, self.pers["rank"], self.pers["rankxp"], self.pers["prestige"], self getXuid(), weapon ); + + attackerName = attacker.name; + self thread globallogic_score::trackAttackeeDeath( attackerName, attacker.pers["rank"], attacker.pers["rankxp"], attacker.pers["prestige"], attacker getXuid() ); + self thread medals::setLastKilledBy( attacker ); + + attacker thread globallogic_score::incKillstreakTracker( weapon ); + + // to prevent spectator gain score for team-spectator after throwing a granade and killing someone before he switched + if ( level.teamBased && attacker.team != "spectator") + { + if( !isdefined( killstreak ) || ( isdefined( level.killstreaksGiveGameScore ) && level.killstreaksGiveGameScore ) ) + globallogic_score::giveTeamScore( "kill", attacker.team, attacker, self ); + } + + scoreSub = level.deathPointLoss; + if ( scoreSub != 0 ) + { + globallogic_score::_setPlayerScore( self, globallogic_score::_getPlayerScore( self ) - scoreSub ); + } + + level thread playKillBattleChatter( attacker, weapon, self, eInflictor ); +} + +function should_allow_postgame_death( sMeansOfDeath ) +{ + if ( sMeansOfDeath == "MOD_POST_GAME" ) + return true; + + return false; +} + +function do_post_game_death(eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration) +{ + if ( !should_allow_postgame_death( sMeansOfDeath ) ) + return; + + self weapons::detach_carry_object_model(); + + self.sessionstate = "dead"; + self.spectatorclient = -1; + self.killcamentity = -1; + self.archivetime = 0; + self.psoffsettime = 0; + + clone_weapon = weapon; + + // we do not want the weapon death fx to play if this is not a melee weapon and its a melee attack + // ideally the mod be passed to the client side and let it decide but this is post ship t7 and this is safest + if ( weapon_utils::isMeleeMOD(sMeansOfDeath) && clone_weapon.type != "melee" ) + { + clone_weapon = level.weaponNone; + } + body = self clonePlayer( deathAnimDuration, clone_weapon, attacker ); + + if ( isdefined( body ) ) + { + self createDeadBody( attacker, iDamage, sMeansOfDeath, weapon, sHitLoc, vDir, (0,0,0), deathAnimDuration, eInflictor, body ); + } +} + +function Callback_PlayerKilled(eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration, enteredResurrect = false) +{ + profilelog_begintiming( 7, "ship" ); + + self endon( "spawned" ); + + + if ( game["state"] == "postgame" ) + { + do_post_game_death(eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration); + return; + } + + if ( self.sessionteam == "spectator" ) + return; + + self notify( "killed_player" ); + self callback::callback( #"on_player_killed" ); + + self needsRevive( false ); + + if ( isdefined( self.burning ) && self.burning == true ) + { + self setburn( 0 ); + } + + self.suicide = false; + self.teamKilled = false; + + if ( isdefined( level.takeLivesOnDeath ) && ( level.takeLivesOnDeath == true ) ) + { + if ( self.pers["lives"] ) + { + self.pers["lives"]--; + if ( self.pers["lives"] == 0 ) + { + level notify( "player_eliminated" ); + self notify( "player_eliminated" ); + } + } + if ( game[self.team + "_lives"] ) + { + game[self.team + "_lives"]--; + if ( game[self.team + "_lives"] == 0 ) + { + level notify( "player_eliminated" ); + self notify( "player_eliminated" ); + } + } + } + + self thread globallogic_audio::flush_leader_dialog_key_on_player( "equipmentDestroyed" ); + //self thread globallogic_audio::flush_leader_dialog_key_on_player( "equipmentHacked" ); + + weapon = updateWeapon( eInflictor, weapon ); + + pixbeginevent( "PlayerKilled pre constants" ); + + wasInLastStand = false; + bledOut = false; + deathTimeOffset = 0; + lastWeaponBeforeDroppingIntoLastStand = undefined; + attackerStance = undefined; + self.lastStandThisLife = undefined; + self.vAttackerOrigin = undefined; + + // need to get this before changing the sessionstate + weapon_at_time_of_death = self GetCurrentWeapon(); + + if ( isdefined( self.useLastStandParams ) && enteredResurrect == false ) + { + self.useLastStandParams = undefined; + + assert( isdefined( self.lastStandParams ) ); + if ( !level.teamBased || ( !isdefined( attacker ) || !isplayer( attacker ) || attacker.team != self.team || attacker == self ) ) + { + eInflictor = self.lastStandParams.eInflictor; + attacker = self.lastStandParams.attacker; + attackerStance = self.lastStandParams.attackerStance; + iDamage = self.lastStandParams.iDamage; + sMeansOfDeath = self.lastStandParams.sMeansOfDeath; + weapon = self.lastStandParams.sWeapon; + vDir = self.lastStandParams.vDir; + sHitLoc = self.lastStandParams.sHitLoc; + self.vAttackerOrigin = self.lastStandParams.vAttackerOrigin; + self.killcam_entity_info_cached = self.lastStandParams.killcam_entity_info_cached; + deathTimeOffset = (gettime() - self.lastStandParams.lastStandStartTime) / 1000; + bledOut = true; + if ( isdefined( self.previousPrimary ) ) + { + wasInLastStand = true; + lastWeaponBeforeDroppingIntoLastStand = self.previousPrimary; + } + } + self.lastStandParams = undefined; + } + + self StopSounds(); + + bestPlayer = undefined; + bestPlayerMeansOfDeath = undefined; + obituaryMeansOfDeath = undefined; + bestPlayerWeapon = undefined; + obituaryWeapon = weapon; + assistedSuicide = false; + + if ( isdefined( level.gameModeAssistedSuicide ) ) + { + result = self [[ level.gameModeAssistedSuicide ]]( attacker, sMeansOfDeath, weapon ); + if ( isdefined( result ) ) + { + bestPlayer = result["bestPlayer"]; + bestPlayerMeansOfDeath = result["bestPlayerMeansOfDeath"]; + bestPlayerWeapon = result["bestPlayerWeapon"]; + } + } + + if ( (!isdefined( attacker ) || attacker.classname == "trigger_hurt" || attacker.classname == "worldspawn" || ( isdefined( attacker.isMagicBullet ) && attacker.isMagicBullet == true ) || attacker == self ) && isdefined( self.attackers ) && !self IsPlayerUnderwater() ) + { + if ( !isdefined(bestPlayer) ) + { + for ( i = 0; i < self.attackers.size; i++ ) + { + player = self.attackers[i]; + if ( !isdefined( player ) ) + continue; + + if (!isdefined( self.attackerDamage[ player.clientId ] ) || ! isdefined( self.attackerDamage[ player.clientId ].damage ) ) + continue; + + if ( player == self || (level.teamBased && player.team == self.team ) ) + continue; + + if ( self.attackerDamage[ player.clientId ].lasttimedamaged + 2500 < getTime() ) + continue; + + if ( !allowedAssistWeapon( self.attackerDamage[ player.clientId ].weapon ) ) + continue; + + if ( self.attackerDamage[ player.clientId ].damage > 1 && ! isdefined( bestPlayer ) ) + { + bestPlayer = player; + bestPlayerMeansOfDeath = self.attackerDamage[ player.clientId ].meansOfDeath; + bestPlayerWeapon = self.attackerDamage[ player.clientId ].weapon; + } + else if ( isdefined( bestPlayer ) && self.attackerDamage[ player.clientId ].damage > self.attackerDamage[ bestPlayer.clientId ].damage ) + { + bestPlayer = player; + bestPlayerMeansOfDeath = self.attackerDamage[ player.clientId ].meansOfDeath; + bestPlayerWeapon = self.attackerDamage[ player.clientId ].weapon; + } + } + } + if ( isdefined ( bestPlayer ) ) + { + scoreevents::processScoreEvent( "assisted_suicide", bestPlayer, self, weapon ); + self RecordKillModifier("assistedsuicide"); + assistedSuicide = true; + } + } + + if ( isdefined ( bestPlayer ) ) + { + attacker = bestPlayer; + obituaryMeansOfDeath = bestPlayerMeansOfDeath; + obituaryWeapon = bestPlayerWeapon; + if ( isdefined( bestPlayerWeapon ) ) + { + weapon = bestPlayerWeapon; + } + } + + if ( isplayer( attacker ) && isdefined( attacker.damagedPlayers ) ) + attacker.damagedPlayers[self.clientid] = undefined; + + if ( enteredResurrect == false ) + { + globallogic::DoWeaponSpecificKillEffects(eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime); + } + + self.deathTime = getTime(); + + if ( attacker != self && (!level.teamBased || attacker.team != self.team )) + { + assert( IsDefined( self.lastspawntime ) ); + self.aliveTimes[self.aliveTimeCurrentIndex] = self.deathTime - self.lastspawntime; + self.aliveTimeCurrentIndex = (self.aliveTimeCurrentIndex + 1) % level.aliveTimeMaxCount; + } + + attacker = updateAttacker( attacker, weapon ); + eInflictor = updateInflictor( eInflictor ); + + sMeansOfDeath = self PlayerKilled_UpdateMeansOfDeath( attacker, eInflictor, weapon, sMeansOfDeath, sHitLoc ); + + if ( !isdefined( obituaryMeansOfDeath ) ) + obituaryMeansOfDeath = sMeansOfDeath; + + self.hasRiotShield = false; + self.hasRiotShieldEquipped = false; + + self thread updateGlobalBotKilledCounter(); + + self PlayerKilled_WeaponStats( attacker, weapon, sMeansOfDeath, wasInLastStand, lastWeaponBeforeDroppingIntoLastStand, eInflictor ); + + if ( bledOut == false ) + { + if( GetDvarInt( "teamOpsEnabled" ) == 1 && ( isdefined( eInflictor ) && ( isdefined( eInflictor.teamops ) && eInflictor.teamops ) ) ) + { + self PlayerKilled_Obituary( eInflictor, eInflictor, obituaryWeapon, obituaryMeansOfDeath ); + } + else + { + self PlayerKilled_Obituary( attacker, eInflictor, obituaryWeapon, obituaryMeansOfDeath ); + } + } + + if ( enteredResurrect == false ) + { +// spawnlogic::death_occured(self, attacker); + + self.sessionstate = "dead"; + self.statusicon = "hud_status_dead"; + } + + self.pers["weapon"] = undefined; + + self.killedPlayersCurrent = []; + + self.deathCount++; + +/# + println( "players("+self.clientId+") death count ++: " + self.deathCount ); +#/ + + if ( bledout == false ) + { + self PlayerKilled_Killstreaks( attacker, weapon ); + } + + lpselfnum = self getEntityNumber(); + lpselfname = self.name; + lpattackGuid = ""; + lpattackname = ""; + lpselfteam = self.team; + lpselfguid = self getXuid(); + + if (self util::is_bot()) + { + lpselfGuid = "bot0"; + } + + lpattackteam = ""; + lpattackorigin = ( 0, 0, 0 ); + + lpattacknum = -1; + + //check if we should award assist points + awardAssists = false; + wasTeamKill = false; + wasSuicide = false; + + pixendevent(); // "PlayerKilled pre constants" ); + + scoreevents::processScoreEvent( "death", self, self, weapon ); + self.pers["resetMomentumOnSpawn"] = level.scoreResetOnDeath; + + + if( isPlayer( attacker ) ) + { + lpattackGuid = attacker getXuid(); + + if (attacker util::is_bot()) + { + lpattackGuid = "bot0"; + } + + lpattackname = attacker.name; + lpattackteam = attacker.team; + lpattackorigin = attacker.origin; + + if ( attacker == self || assistedSuicide == true ) // killed himself + { + doKillcam = false; + wasSuicide = true; + + awardAssists = self PlayerKilled_Suicide( eInflictor, attacker, sMeansOfDeath, weapon, sHitLoc ); + if( assistedSuicide == true ) + attacker thread globallogic_score::giveKillStats( sMeansOfDeath, weapon, self ); + } + else + { + pixbeginevent( "PlayerKilled attacker" ); + + lpattacknum = attacker getEntityNumber(); + + doKillcam = true; + + if ( level.teamBased && self.team == attacker.team && sMeansOfDeath == "MOD_GRENADE" && level.friendlyfire == 0 ) + { + } + else if ( level.teamBased && self.team == attacker.team ) // killed by a friendly + { + wasTeamKill = true; + + self PlayerKilled_TeamKill( eInflictor, attacker, sMeansOfDeath, weapon, sHitLoc ); + } + else + { + if ( bledOut == false ) + { + self PlayerKilled_Kill( eInflictor, attacker, sMeansOfDeath, weapon, sHitLoc ); + + if ( level.teamBased ) + { + //check if assist points should be awarded + awardAssists = true; + } + } + } + + pixendevent(); //"PlayerKilled attacker" + } + } + else if ( isdefined( attacker ) && ( attacker.classname == "trigger_hurt" || attacker.classname == "worldspawn" ) ) + { + doKillcam = false; + + lpattacknum = -1; + lpattackguid = ""; + lpattackname = ""; + lpattackteam = "world"; + + scoreevents::processScoreEvent( "suicide", self ); + self globallogic_score::incPersStat( "suicides", 1 ); + self.suicides = self globallogic_score::getPersStat( "suicides" ); + + self.suicide = true; + + //Check for player death related battlechatter + thread battlechatter::on_player_suicide_or_team_kill( self, "suicide" ); //Play suicide battlechatter + + //check if assist points should be awarded + awardAssists = true; + + if ( level.maxSuicidesBeforeKick > 0 && level.maxSuicidesBeforeKick <= self.suicides ) + { + // should change "teamKillKicked" to just kicked for the next game + self notify( "teamKillKicked" ); + self SuicideKick(); + } + } + else + { + doKillcam = false; + + lpattacknum = -1; + lpattackguid = ""; + lpattackname = ""; + lpattackteam = "world"; + + wasSuicide = true; + + // we may have a killcam on an world entity like the rocket in cosmodrome + if ( isdefined( eInflictor ) && isdefined( eInflictor.killCamEnt ) ) + { + doKillcam = true; + lpattacknum = self getEntityNumber(); + wasSuicide = false; + } + + // even if the attacker isn't a player, it might be on a team + if ( isdefined( attacker ) && isdefined( attacker.team ) && ( isdefined( level.teams[attacker.team] ) ) ) + { + if ( attacker.team != self.team ) + { + if ( level.teamBased ) + { + if( !isdefined( killstreaks::get_killstreak_for_weapon( weapon ) ) || ( isdefined( level.killstreaksGiveGameScore ) && level.killstreaksGiveGameScore ) ) + globallogic_score::giveTeamScore( "kill", attacker.team, attacker, self ); + } + + wasSuicide = false; + } + } + + //check if assist points should be awarded + awardAssists = true; + } + + if ( !level.inGracePeriod && enteredResurrect == false ) + { + if ( sMeansOfDeath != "MOD_GRENADE" && sMeansOfDeath != "MOD_GRENADE_SPLASH" && sMeansOfDeath != "MOD_EXPLOSIVE" && sMeansOfDeath != "MOD_EXPLOSIVE_SPLASH" && sMeansOfDeath != "MOD_PROJECTILE_SPLASH" && sMeansOfDeath != "MOD_FALLING" ) + { + if ( weapon.name != "incendiary_fire" ) + { + self weapons::drop_scavenger_for_death( attacker ); + } + } + + if ( should_drop_weapon_on_death( wasTeamkill, wasSuicide, weapon_at_time_of_death, sMeansOfDeath ) ) + { + self weapons::drop_for_death( attacker, weapon, sMeansOfDeath ); + } + } + + //award assist points if needed + if( awardAssists ) + { + self PlayerKilled_AwardAssists( eInflictor, attacker, weapon, lpattackteam ); + } + + pixbeginevent( "PlayerKilled post constants" ); + + self.lastAttacker = attacker; + self.lastDeathPos = self.origin; + + if ( isdefined( attacker ) && isPlayer( attacker ) && attacker != self && (!level.teambased || attacker.team != self.team) ) + { + attacker notify( "killed_enemy_player", self, weapon ); + if( isDefined( attacker.gadget_thief_kill_callback ) ) + { + attacker [[attacker.gadget_thief_kill_callback]]( self, weapon ); + } + self thread challenges::playerKilled(eInflictor, attacker, iDamage, sMeansOfDeath, weapon, sHitLoc, attackerStance, bledOut ); + } + else + { + + self notify("playerKilledChallengesProcessed"); + } + + if ( isdefined ( self.attackers )) + self.attackers = []; + + + // minimize repeat checks of things like isPlayer + killerHeroPowerActive = 0; + killer = undefined; + killerLoadoutIndex = -1; + killerWasADS = false; + killerInVictimFOV = false; + victimInKillerFOV = false; + + if( isPlayer( attacker ) ) + { + attacker.lastKillTime = gettime(); + + killer = attacker; + if ( isdefined( attacker.class_num ) ) + killerLoadoutIndex = attacker.class_num; + killerWasADS = attacker playerADS() >= 1; + + killerInVictimFOV = util::within_fov( self.origin, self.angles, attacker.origin, self.fovcosine ); + victimInKillerFOV = util::within_fov( attacker.origin, attacker.angles, self.origin, attacker.fovcosine ); + + if ( attacker ability_player::is_using_any_gadget() ) + killerHeroPowerActive = 1; + + if( killstreaks::is_killstreak_weapon( weapon ) ) + { + killstreak = killstreaks::get_killstreak_for_weapon_for_stats( weapon ); + + bbPrint( "mpattacks", "gametime %d attackerspawnid %d attackerweapon %s attackerx %d attackery %d attackerz %d victimspawnid %d victimx %d victimy %d victimz %d damage %d damagetype %s damagelocation %s death %d isusingheropower %d killstreak %s", + gettime(), getplayerspawnid( attacker ), weapon.name, lpattackorigin, getplayerspawnid( self ), self.origin, iDamage, sMeansOfDeath, sHitLoc, 1, killerHeroPowerActive, killstreak ); + } + else + { + bbPrint( "mpattacks", "gametime %d attackerspawnid %d attackerweapon %s attackerx %d attackery %d attackerz %d victimspawnid %d victimx %d victimy %d victimz %d damage %d damagetype %s damagelocation %s death %d isusingheropower %d", + gettime(), getplayerspawnid( attacker ), weapon.name, lpattackorigin, getplayerspawnid( self ), self.origin, iDamage, sMeansOfDeath, sHitLoc, 1, killerHeroPowerActive ); + } + + attacker thread weapons::bestweapon_kill( weapon ); + } + else + { + bbPrint( "mpattacks", "gametime %d attackerweapon %s victimspawnid %d victimx %d victimy %d victimz %d damage %d damagetype %s damagelocation %s death %d isusingheropower %d", + gettime(), weapon.name, getplayerspawnid( self ), self.origin, iDamage, sMeansOfDeath, sHitLoc, 1, 0 ); + } + + victimWeapon = undefined; + victimWeaponPickedUp = false; + victimKillstreakWeaponIndex = 0; + if( isdefined( weapon_at_time_of_death ) ) + { + victimWeapon = weapon_at_time_of_death; + if( isdefined( self.pickedUpWeapons ) && isdefined( self.pickedUpWeapons[victimWeapon] ) ) + { + victimWeaponPickedUp = true; + } + + if( killstreaks::is_killstreak_weapon( victimWeapon ) ) + { + killstreak = killstreaks::get_killstreak_for_weapon_for_stats( victimWeapon ); + if( isdefined( level.killstreaks[killstreak].menuname ) ) + { + victimKillstreakWeaponIndex = level.killstreakindices[level.killstreaks[killstreak].menuname]; + } + } + } + victimWasADS = self playerADS() >= 1; + victimHeroPowerActive = self ability_player::is_using_any_gadget(); + + killerWeaponPickedUp = false; + killerKillstreakWeaponIndex = 0; + killerKillstreakEventIndex = 125; // 125 = not a killstreak + if( isdefined( weapon ) ) + { + if( isdefined( killer ) && isdefined( killer.pickedUpWeapons ) && isdefined( killer.pickedUpWeapons[weapon] ) ) + { + killerWeaponPickedUp = true; + } + + if( killstreaks::is_killstreak_weapon( weapon ) ) + { + killstreak = killstreaks::get_killstreak_for_weapon_for_stats( weapon ); + if( isdefined( level.killstreaks[killstreak].menuname ) ) + { + killerKillstreakWeaponIndex = level.killstreakindices[level.killstreaks[killstreak].menuname]; + + if( isdefined( killer.killstreakEvents ) && isdefined( killer.killstreakEvents[ killerkillstreakweaponindex ] ) ) + { + killerKillstreakEventIndex = killer.killstreakEvents[killerkillstreakweaponindex]; + } + else + { + killerkillstreakeventindex = 126; // 126 = was a killstreak but no event index + } + } + } + } + + // + // Log additional stuff in match record on death. + // Mostly values we can't easily access in the existing MatchRecordDeath function in code. + // + + matchRecordLogAdditionalDeathInfo( self, killer, victimWeapon, weapon, + self.class_num, victimWeaponPickedUp, victimWasADS, + killerLoadoutIndex, killerWeaponPickedUp, killerWasADS, + victimHeroPowerActive, killerHeroPowerActive, + victimInKillerFOV, killerInVictimFOV, + killerKillstreakWeaponIndex, victimKillstreakWeaponIndex, + killerkillstreakeventindex); + + + self record_special_move_data_for_life( killer ); + + self.pickedUpWeapons = []; // reset on each death + + + logPrint( "K;" + lpselfguid + ";" + lpselfnum + ";" + lpselfteam + ";" + lpselfname + ";" + lpattackguid + ";" + lpattacknum + ";" + lpattackteam + ";" + lpattackname + ";" + weapon.name + ";" + iDamage + ";" + sMeansOfDeath + ";" + sHitLoc + "\n" ); + attackerString = "none"; + if ( isPlayer( attacker ) ) // attacker can be the worldspawn if it's not a player + attackerString = attacker getXuid() + "(" + lpattackname + ")"; + /#print( "d " + sMeansOfDeath + "(" + weapon.name + ") a:" + attackerString + " d:" + iDamage + " l:" + sHitLoc + " @ " + int( self.origin[0] ) + " " + int( self.origin[1] ) + " " + int( self.origin[2] ) );#/ + + // for cod caster update the top scorers + if ( !level.rankedMatch && !level.teambased ) + { + level thread update_ffa_top_scorers(); + } + + level thread globallogic::updateTeamStatus(); + level thread globallogic::updateAliveTimes(self.team); + + if ( isdefined( self.killcam_entity_info_cached ) ) + { + killcam_entity_info = self.killcam_entity_info_cached; + self.killcam_entity_info_cached = undefined; + } + else + { + killcam_entity_info = killcam::get_killcam_entity_info( attacker, eInflictor, weapon ); + } + + + // no killcam if the player is still involved with a killstreak + if ( isdefined( self.killstreak_delay_killcam ) ) + doKillcam = false; + + self weapons::detach_carry_object_model(); + + pixendevent(); //"END: PlayerKilled post constants" + + pixbeginevent( "PlayerKilled body and gibbing" ); + vAttackerOrigin = undefined; + if ( isdefined( attacker ) ) + { + vAttackerOrigin = attacker.origin; + } + + if ( enteredResurrect == false ) + { + clone_weapon = weapon; + + // we do not want the weapon death fx to play if this is not a melee weapon and its a melee attack + // ideally the mod be passed to the client side and let it decide but this is post ship t7 and this is safest + if ( weapon_utils::isMeleeMOD(sMeansOfDeath) && clone_weapon.type != "melee" ) + { + clone_weapon = level.weaponNone; + } + body = self clonePlayer( deathAnimDuration, clone_weapon, attacker ); + + if ( isdefined( body ) ) + { + self createDeadBody( attacker, iDamage, sMeansOfDeath, weapon, sHitLoc, vDir, vAttackerOrigin, deathAnimDuration, eInflictor, body ); + + if ( isdefined( level.customPlayDeathSound ) ) + self [[ level.customPlayDeathSound ]]( body, attacker, weapon, sMeansOfDeath ); + else + self battlechatter::play_death_vox( body, attacker, weapon, sMeansOfDeath ); + + globallogic::DoWeaponSpecificCorpseEffects(body, eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime); + } + } + + + pixendevent();// "END: PlayerKilled body and gibbing" + + if ( enteredResurrect ) + { + thread globallogic_spawn::spawnQueuedClient( self.team, attacker ); + } + + self.switching_teams = undefined; + self.joining_team = undefined; + self.leaving_team = undefined; + + if ( bledOut == false ) // handled in PlayerLastStand + { + self thread [[level.onPlayerKilled]](eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration); + } + + if ( isdefined( level.teamopsOnPlayerKilled ) ) + { + self [[level.teamopsOnPlayerKilled]]( eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration); + } + + for ( iCB = 0; iCB < level.onPlayerKilledExtraUnthreadedCBs.size; iCB++ ) + { + self [[ level.onPlayerKilledExtraUnthreadedCBs[ iCB ] ]]( + eInflictor, + attacker, + iDamage, + sMeansOfDeath, + weapon, + vDir, + sHitLoc, + psOffsetTime, + deathAnimDuration ); + } + + self.wantSafeSpawn = false; + perks = []; + // perks = globallogic::getPerks( attacker ); + killstreaks = globallogic::getKillstreaks( attacker ); + + if( !isdefined( self.killstreak_delay_killcam ) ) + { + // start the prediction now so the client gets updates while waiting to spawn + self thread [[level.spawnPlayerPrediction]](); + } + + profilelog_endtiming( 7, "gs=" + game["state"] + " zom=" + SessionModeIsZombiesGame() ); + + // record the kill cam values for the final kill cam + if ( wasTeamKill == false && assistedSuicide == false && sMeansOfDeath != "MOD_SUICIDE" && !( !isdefined( attacker ) || attacker.classname == "trigger_hurt" || attacker.classname == "worldspawn" || attacker == self || isdefined ( attacker.disableFinalKillcam ) ) ) + { + level thread killcam::record_settings( lpattacknum, self getEntityNumber(), weapon, sMeansOfDeath, self.deathTime, deathTimeOffset, psOffsetTime, killcam_entity_info, perks, killstreaks, attacker ); + } + if ( enteredResurrect ) + { + return; + } + + // let the player watch themselves die + wait ( 0.25 ); + + //check if killed by a sniper + weaponClass = util::getWeaponClass( weapon ); + if( isdefined( weaponClass ) && weaponClass == "weapon_sniper" ) + { + self thread battlechatter::killed_by_sniper( attacker ); + } + else + { + self thread battlechatter::player_killed( attacker, killstreak ); + } + self.cancelKillcam = false; + self thread killcam::cancel_on_use(); + + // initial death cam + self playerkilled_watch_death(weapon, sMeansOfDeath, deathAnimDuration); + + // killcam +/# + if ( GetDvarint( "scr_forcekillcam" ) != 0 ) + { + doKillcam = true; + + if ( lpattacknum < 0 ) + lpattacknum = self getEntityNumber(); + } +#/ + + if ( game["state"] != "playing" ) + { + return; + } + + self.respawnTimerStartTime = gettime(); + keep_deathcam = false; + if ( isdefined( self.overridePlayerDeadStatus ) ) + { + keep_deathcam = self [[ self.overridePlayerDeadStatus ]](); + } + + if ( !self.cancelKillcam && doKillcam && level.killcam && ( wasTeamKill == false ) ) + { + livesLeft = !(level.numLives && !self.pers["lives"]) && !(level.numTeamLives && !game[self.team+"_lives"]); + timeUntilSpawn = globallogic_spawn::TimeUntilSpawn( true ); + willRespawnImmediately = livesLeft && (timeUntilSpawn <= 0) && !level.playerQueuedRespawn; + + self killcam::killcam( lpattacknum, self getEntityNumber(), killcam_entity_info, weapon, sMeansOfDeath, self.deathTime, deathTimeOffset, psOffsetTime, willRespawnImmediately, globallogic_utils::timeUntilRoundEnd(), perks, killstreaks, attacker, keep_deathcam ); + } + else if( self.cancelKillcam ) + { + // copy of code from wait_skip_killcam_button + // because fast button mashers (not hard to do) will "skip" the killcam + // before it even starts + if( isdefined( self.killcamsSkipped) ) + { + self.killcamsSkipped++; + } + else + { + self.killcamsSkipped = 1; + } + } + + // secondary deathcam for resurrection + + secondary_deathcam = 0.0; + + timeUntilSpawn = globallogic_spawn::TimeUntilSpawn( true ); + shouldDoSecondDeathCam = timeUntilSpawn > 0; + + if ( shouldDoSecondDeathCam && IsDefined(self.secondaryDeathCamTime) ) + { + secondary_deathcam = self [[self.secondaryDeathCamTime]](); + } + + if ( secondary_deathcam > 0.0 && !self.cancelKillcam ) + { + self.spectatorclient = -1; + self.killcamentity = -1; + self.archivetime = 0; + self.psoffsettime = 0; + self.spectatekillcam = false; + globallogic_utils::waitForTimeOrNotify( secondary_deathcam, "end_death_delay" ); + self notify ( "death_delay_finished" ); + } + + // secondary deathcam is complete + + if ( !self.cancelKillcam && doKillcam && level.killcam && keep_deathcam ) + { + self.sessionstate = "dead"; + self.spectatorclient = -1; + self.killcamentity = -1; + self.archivetime = 0; + self.psoffsettime = 0; + self.spectatekillcam = false; + } + + if ( game["state"] != "playing" ) + { + self.sessionstate = "dead"; + self.spectatorclient = -1; + self.killcamtargetentity = -1; + self.killcamentity = -1; + self.archivetime = 0; + self.psoffsettime = 0; + self.spectatekillcam = false; + return; + } + + WaitTillKillStreakDone(); + useRespawnTime = true; + if( isDefined( level.hostMigrationTimer ) ) + { + useRespawnTime = false; + } + hostmigration::waittillHostMigrationCountDown(); + //if ( isDefined( level.hostMigrationTimer ) ) + //return; + + // class may be undefined if we have changed teams + if ( globallogic_utils::isValidClass( self.curClass ) ) + { + timePassed = undefined; + + if ( isdefined( self.respawnTimerStartTime ) && useRespawnTime ) + { + timePassed = (gettime() - self.respawnTimerStartTime) / 1000; + } + + self thread [[level.spawnClient]]( timePassed ); + self.respawnTimerStartTime = undefined; + } +} + +function update_ffa_top_scorers() +{ + waittillframeend; + + if ( !level.players.size || level.gameEnded ) + return; + + placement = []; + foreach ( player in level.players ) + { + if ( player.team != "spectator" ) + placement[placement.size] = player; + } + + for ( i = 1; i < placement.size; i++ ) + { + player = placement[i]; + playerScore = player.pointstowin; + for ( j = i - 1; j >= 0 && (playerScore > placement[j].pointstowin || (playerScore == placement[j].pointstowin && player.deaths < placement[j].deaths) || (playerScore == placement[j].pointstowin && player.deaths == placement[j].deaths && player.lastKillTime > placement[j].lastKillTime)); j-- ) + placement[j + 1] = placement[j]; + placement[j + 1] = player; + } + + ClearTopScorers(); + for ( i = 0; i < placement.size && i < 3; i++ ) + { + SetTopScorer( i, placement[i], 0, 0, 0, 0, level.weaponNone ); + } +} + +function playerkilled_watch_death(weapon, sMeansOfDeath, deathAnimDuration) +{ + defaultPlayerDeathWatchTime = 1.75; + if ( sMeansOfDeath == "MOD_MELEE_ASSASSINATE" || 0 > weapon.deathCamTime ) + { + defaultPlayerDeathWatchTime = (deathAnimDuration * 0.001) + 0.5; + } + else if ( 0 < weapon.deathCamTime ) + { + defaultPlayerDeathWatchTime = weapon.deathCamTime; + } + + if ( isdefined ( level.overridePlayerDeathWatchTimer ) ) + { + defaultPlayerDeathWatchTime = [[level.overridePlayerDeathWatchTimer]]( defaultPlayerDeathWatchTime ); + } + + globallogic_utils::waitForTimeOrNotify( defaultPlayerDeathWatchTime, "end_death_delay" ); + + self notify ( "death_delay_finished" ); +} + +function should_drop_weapon_on_death( wasTeamKill, wasSuicide, current_weapon, sMeansOfDeath ) +{ + // to avoid exploits dont allow weapon drops on suicide or teamkills. + if ( wasTeamKill ) + return false; + + if ( wasSuicide ) + return false; + + // assuming this means that they are in a death trigger out of bounds and falling + if ( sMeansOfDeath == "MOD_TRIGGER_HURT" && !self IsOnGround()) + return false; + + // dont drop any weapon if they were holding a hero weapon + if ( IsDefined(current_weapon) && current_weapon.isHeroWeapon ) + return false; + + return true; +} + +function updateGlobalBotKilledCounter() +{ + if ( isdefined( self.pers["isBot"] ) ) + { + level.globalLarrysKilled++; + } +} + + +function WaitTillKillStreakDone() +{ + if( isdefined( self.killstreak_delay_killcam ) ) + { + while( isdefined( self.killstreak_delay_killcam ) ) + { + wait( 0.1 ); + } + + //Plus a small amount so we can see our dead body + wait( 2.0 ); + + self killstreaks::reset_killstreak_delay_killcam(); + } +} + +function SuicideKick() +{ + self globallogic_score::incPersStat( "sessionbans", 1 ); + + self endon("disconnect"); + waittillframeend; + + globallogic::gameHistoryPlayerKicked(); + + ban( self getentitynumber() ); + globallogic_audio::leader_dialog( "gamePlayerKicked" ); +} + +function TeamKillKick() +{ + self globallogic_score::incPersStat( "sessionbans", 1 ); + + self endon("disconnect"); + waittillframeend; + + //for test purposes lets lock them out of certain game type for 2mins + + playlistbanquantum = tweakables::getTweakableValue( "team", "teamkillerplaylistbanquantum" ); + playlistbanpenalty = tweakables::getTweakableValue( "team", "teamkillerplaylistbanpenalty" ); + if ( playlistbanquantum > 0 && playlistbanpenalty > 0 ) + { + timeplayedtotal = self GetDStat( "playerstatslist", "time_played_total", "StatValue" ); + minutesplayed = timeplayedtotal / 60; + + freebees = 2; + + banallowance = int( floor(minutesplayed / playlistbanquantum) ) + freebees; + + if ( self.sessionbans > banallowance ) + { + self SetDStat( "playerstatslist", "gametypeban", "StatValue", timeplayedtotal + (playlistbanpenalty * 60) ); + } + } + + globallogic::gameHistoryPlayerKicked(); + + ban( self getentitynumber() ); + globallogic_audio::leader_dialog( "gamePlayerKicked" ); +} + +function TeamKillDelay() +{ + teamkills = self.pers["teamkills_nostats"]; + if ( level.minimumAllowedTeamKills < 0 || teamkills <= level.minimumAllowedTeamKills ) + return 0; + + exceeded = (teamkills - level.minimumAllowedTeamKills); + return level.teamKillSpawnDelay * exceeded; +} + + +function ShouldTeamKillKick(teamKillDelay) +{ + if ( teamKillDelay && ( level.minimumAllowedTeamKills >= 0 ) ) + { + // if its more then 5 seconds into the match and we have a delay then just kick them + if ( globallogic_utils::getTimePassed() >= 5000 ) + { + return true; + } + + // if its under 5 seconds into the match only kick them if they have killed more then one players so far + if ( self.pers["teamkills_nostats"] > 1 ) + { + return true; + } + } + + return false; +} + +function reduceTeamKillsOverTime() +{ + timePerOneTeamkillReduction = 20.0; + reductionPerSecond = 1.0 / timePerOneTeamkillReduction; + + while(1) + { + if ( isAlive( self ) ) + { + self.pers["teamkills_nostats"] -= reductionPerSecond; + if ( self.pers["teamkills_nostats"] < level.minimumAllowedTeamKills ) + { + self.pers["teamkills_nostats"] = level.minimumAllowedTeamKills; + break; + } + } + wait 1; + } +} + + +function IgnoreTeamKills( weapon, sMeansOfDeath, eInflictor ) +{ + if ( weapon_utils::isMeleeMOD( sMeansOfDeath ) ) + return false; + + if ( weapon.ignoreTeamKills ) + return true; + + if ( isdefined( eInflictor ) && eInflictor.ignore_team_kills === true ) + return true; + + if( isDefined( eInflictor ) && isDefined( eInflictor.destroyedBy ) && isDefined( eInflictor.owner ) && eInflictor.destroyedBy != eInflictor.owner ) + return true; + + if ( isDefined( eInflictor ) && eInflictor.classname == "worldspawn" ) + return true; + + return false; +} + + +function Callback_PlayerLastStand( eInflictor, eAttacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration ) +{ + laststand::PlayerLastStand( eInflictor, eAttacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration ); +} + +function damageShellshockAndRumble( eAttacker, eInflictor, weapon, sMeansOfDeath, iDamage ) +{ + self thread weapons::on_damage( eAttacker, eInflictor, weapon, sMeansOfDeath, iDamage ); + + if ( !self util::isUsingRemote() ) + { + self PlayRumbleOnEntity( "damage_heavy" ); + } +} + + +function createDeadBody( attacker, iDamage, sMeansOfDeath, weapon, sHitLoc, vDir, vAttackerOrigin, deathAnimDuration, eInflictor, body ) +{ + if ( sMeansOfDeath == "MOD_HIT_BY_OBJECT" && self GetStance() == "prone" ) + { + self.body = body; + if ( !isdefined( self.switching_teams ) ) + thread deathicons::add( body, self, self.team, 5.0 ); + + return; + } + + ragdoll_now = false; + if( isdefined(self.usingvehicle) && self.usingvehicle && isdefined(self.vehicleposition) && self.vehicleposition == 1 ) + { + ragdoll_now = true; + } + + if ( isdefined( level.ragdoll_override ) && self [[level.ragdoll_override]]( iDamage, sMeansOfDeath, weapon, sHitLoc, vDir, vAttackerOrigin, deathAnimDuration, eInflictor, ragdoll_now, body ) ) + { + return; + } + + if ( ( ragdoll_now ) || self isOnLadder() || self isMantling() || sMeansOfDeath == "MOD_CRUSH" || sMeansOfDeath == "MOD_HIT_BY_OBJECT" ) + body startRagDoll(); + + if ( !self IsOnGround() && sMeansOfDeath != "MOD_FALLING" ) + { + if ( GetDvarint( "scr_disable_air_death_ragdoll" ) == 0 ) + { + body startRagDoll(); + } + } + + if( sMeansOfDeath == "MOD_MELEE_ASSASSINATE" && !attacker isOnGround() ) + { + body start_death_from_above_ragdoll( vDir ); + } + + if ( self is_explosive_ragdoll( weapon, eInflictor ) ) + { + body start_explosive_ragdoll( vDir, weapon ); + } + + thread delayStartRagdoll( body, sHitLoc, vDir, weapon, eInflictor, sMeansOfDeath ); + + if ( sMeansOfDeath == "MOD_CRUSH" ) + { + body globallogic_vehicle::vehicleCrush(); + } + + self.body = body; + if ( !isdefined( self.switching_teams ) ) + thread deathicons::add( body, self, self.team, 5.0 ); +} + +function is_explosive_ragdoll( weapon, inflictor ) +{ + if ( !isdefined( weapon ) ) + { + return false; + } + + // destructible explosives + if ( weapon.name == "destructible_car" || weapon.name == "explodable_barrel" ) + { + return true; + } + + // special explosive weapons + if ( weapon.projExplosionType == "grenade" ) + { + if ( isdefined( inflictor ) && isdefined( inflictor.stuckToPlayer ) ) + { + if ( inflictor.stuckToPlayer == self ) + { + return true; + } + } + } + + return false; +} + +function start_explosive_ragdoll( dir, weapon ) +{ + if ( !isdefined( self ) ) + { + return; + } + + x = RandomIntRange( 50, 100 ); + y = RandomIntRange( 50, 100 ); + z = RandomIntRange( 10, 20 ); + + if ( isdefined( weapon ) && (weapon.name == "sticky_grenade" || weapon.name == "explosive_bolt") ) + { + if ( isdefined( dir ) && LengthSquared( dir ) > 0 ) + { + x = dir[0] * x; + y = dir[1] * y; + } + } + else + { + if ( math::cointoss() ) + { + x = x * -1; + } + if ( math::cointoss() ) + { + y = y * -1; + } + } + + self StartRagdoll(); + self LaunchRagdoll( ( x, y, z ) ); +} + +function start_death_from_above_ragdoll( dir ) +{ + if ( !isdefined( self ) ) + { + return; + } + + self StartRagdoll(); + self LaunchRagdoll( ( 0, 0, -100 ) ); +} + + +function notifyConnecting() +{ + waittillframeend; + + if( isdefined( self ) ) + { + level notify( "connecting", self ); + } + + callback::callback( #"on_player_connecting" ); +} + + +function delayStartRagdoll( ent, sHitLoc, vDir, weapon, eInflictor, sMeansOfDeath ) +{ + if ( isdefined( ent ) ) + { + deathAnim = ent getcorpseanim(); + if ( animhasnotetrack( deathAnim, "ignore_ragdoll" ) ) + return; + } + + waittillframeend; + + if ( !isdefined( ent ) ) + return; + + if ( ent isRagDoll() ) + return; + + deathAnim = ent getcorpseanim(); + + startFrac = 0.35; + + if ( animhasnotetrack( deathAnim, "start_ragdoll" ) ) + { + times = getnotetracktimes( deathAnim, "start_ragdoll" ); + if ( isdefined( times ) ) + startFrac = times[0]; + } + + waitTime = startFrac * getanimlength( deathAnim ); + + //waitTime -= 0.2; // account for the wait above + if( waitTime > 0 ) + wait( waitTime ); + + if ( isdefined( ent ) ) + { + ent startragdoll(); + } +} + +function trackAttackerDamage( eAttacker, iDamage, sMeansOfDeath, weapon ) +{ + if( !IsDefined( eAttacker ) ) + return; + + if ( !IsPlayer( eAttacker ) ) + return; + + if ( self.attackerData.size == 0 ) + { + self.firstTimeDamaged = getTime(); + } + if ( !isdefined( self.attackerData[eAttacker.clientid] ) ) + { + self.attackerDamage[eAttacker.clientid] = spawnstruct(); + self.attackerDamage[eAttacker.clientid].damage = iDamage; + self.attackerDamage[eAttacker.clientid].meansOfDeath = sMeansOfDeath; + self.attackerDamage[eAttacker.clientid].weapon = weapon; + self.attackerDamage[eAttacker.clientid].time = getTime(); + + self.attackers[ self.attackers.size ] = eAttacker; + + // we keep an array of attackers by their client ID so we can easily tell + // if they're already one of the existing attackers in the above if(). + // we store in this array data that is useful for other things, like challenges + self.attackerData[eAttacker.clientid] = false; + } + else + { + self.attackerDamage[eAttacker.clientid].damage += iDamage; + self.attackerDamage[eAttacker.clientid].meansOfDeath = sMeansOfDeath; + self.attackerDamage[eAttacker.clientid].weapon = weapon; + if ( !isdefined( self.attackerDamage[eAttacker.clientid].time ) ) + self.attackerDamage[eAttacker.clientid].time = getTime(); + } + + if ( IsArray( self.attackersThisSpawn ) ) + { + self.attackersThisSpawn[ eAttacker.clientid ] = eAttacker; + } + + self.attackerDamage[eAttacker.clientid].lasttimedamaged = getTime(); + if ( weapons::is_primary_weapon( weapon ) ) + self.attackerData[eAttacker.clientid] = true; +} + +function giveAttackerAndInflictorOwnerAssist( eAttacker, eInflictor, iDamage, sMeansOfDeath, weapon ) +{ + if ( !allowedAssistWeapon( weapon ) ) + return; + + self trackAttackerDamage( eAttacker, iDamage, sMeansOfDeath, weapon ); + + if ( !isdefined( eInflictor ) ) + return; + + if ( !isdefined( eInflictor.owner ) ) + return; + + if ( !isdefined( eInflictor.ownerGetsAssist ) ) + return; + + if ( !eInflictor.ownerGetsAssist ) + return; + + // if attacker and inflictor owner are the same no additional points + // I dont ever know if they are different + if ( isdefined( eAttacker ) && eAttacker == eInflictor.owner ) + return; + + self trackAttackerDamage( eInflictor.owner, iDamage, sMeansOfDeath, weapon ); +} + +function PlayerKilled_UpdateMeansOfDeath( attacker, eInflictor, weapon, sMeansOfDeath, sHitLoc ) +{ + if( globallogic_utils::isHeadShot( weapon, sHitLoc, sMeansOfDeath, eInflictor ) && isPlayer( attacker ) && !weapon_utils::ismeleemod( sMeansOfDeath ) ) + { + return "MOD_HEAD_SHOT"; + } + + // we do not want the melee icon to show up for dog attacks + switch( weapon.name ) + { + case "dog_bite": + sMeansOfDeath = "MOD_PISTOL_BULLET"; + break; + case "destructible_car": + sMeansOfDeath = "MOD_EXPLOSIVE"; + break; + case "explodable_barrel": + sMeansOfDeath = "MOD_EXPLOSIVE"; + break; + } + + return sMeansOfDeath; +} + +function updateAttacker( attacker, weapon ) +{ + if( isai(attacker) && isdefined( attacker.script_owner ) ) + { + // if the person who called the dogs in switched teams make sure they don't + // get penalized for the kill + if ( !level.teambased || attacker.script_owner.team != self.team ) + attacker = attacker.script_owner; + } + + if( attacker.classname == "script_vehicle" && isdefined( attacker.owner ) ) + { + attacker notify("killed",self); + + attacker = attacker.owner; + } + + if( isai(attacker) ) + attacker notify("killed",self); + + if ( ( isdefined ( self.capturingLastFlag ) ) && ( self.capturingLastFlag == true ) ) + { + attacker.lastCapKiller = true; + } + + if( isdefined( attacker ) && attacker != self && isdefined( weapon ) ) + { + if ( weapon.name == "planemortar" ) + { + if(!isdefined(attacker.planeMortarBda))attacker.planeMortarBda=0; + attacker.planeMortarBda++; + } + else if( weapon.name == "dart" || + weapon.name == "dart_turret" ) + { + if(!isdefined(attacker.dartBda))attacker.dartBda=0; + attacker.dartBda++; + } + else if( weapon.name == "straferun_rockets" || weapon.name == "straferun_gun") + { + if( isdefined( attacker.strafeRunbda ) ) + { + attacker.strafeRunbda++; + } + } + else if ( weapon.name == "remote_missile_missile" || weapon.name == "remote_missile_bomblet" ) + { + if(!isdefined(attacker.remotemissileBda))attacker.remotemissileBda=0; + attacker.remotemissileBda++; + } + } + + return attacker; +} + +function updateInflictor( eInflictor ) +{ + if( isdefined( eInflictor ) && eInflictor.classname == "script_vehicle" ) + { + eInflictor notify("killed",self); + + if ( isdefined( eInflictor.bda ) ) + { + eInflictor.bda++; + } + } + + return eInflictor; +} + +function updateWeapon( eInflictor, weapon ) +{ + // explosive barrel/car detection + if ( weapon == level.weaponNone && isdefined( eInflictor ) ) + { + if ( isdefined( eInflictor.targetname ) && eInflictor.targetname == "explodable_barrel" ) + weapon = GetWeapon( "explodable_barrel" ); + else if ( isdefined( eInflictor.destructible_type ) && isSubStr( eInflictor.destructible_type, "vehicle_" ) ) + weapon = GetWeapon( "destructible_car" ); + } + + return weapon; +} + +function playKillBattleChatter( attacker, weapon, victim, eInflictor ) +{ + if( IsPlayer( attacker ) ) + { + if ( !killstreaks::is_killstreak_weapon( weapon ) ) + { + level thread battlechatter::say_kill_battle_chatter( attacker, weapon, victim, eInflictor ); + } + } + + if( isdefined( eInflictor ) ) + { + eInflictor notify( "bhtn_action_notify", "attack_kill" ); + } +}