diff --git a/data/scripts/mp/gametypes/_globallogic_player.gsc b/data/scripts/mp/gametypes/_globallogic_player.gsc new file mode 100644 index 00000000..b0673052 Binary files /dev/null and b/data/scripts/mp/gametypes/_globallogic_player.gsc differ diff --git a/data/scripts/mp/gametypes/_globallogic_player.gsc_raw b/data/scripts/mp/gametypes/_globallogic_player.gsc_raw new file mode 100644 index 00000000..6b8e3a86 --- /dev/null +++ b/data/scripts/mp/gametypes/_globallogic_player.gsc_raw @@ -0,0 +1,4524 @@ +#using scripts\shared\abilities\_ability_player; +#using scripts\shared\abilities\_ability_power; +#using scripts\shared\abilities\_ability_util; +#using scripts\shared\callbacks_shared; +#using scripts\shared\challenges_shared; +#using scripts\shared\clientfield_shared; +#using scripts\shared\damagefeedback_shared; +#using scripts\shared\demo_shared; +#using scripts\shared\flag_shared; +#using scripts\shared\hostmigration_shared; +#using scripts\shared\hud_message_shared; +#using scripts\shared\hud_util_shared; +#using scripts\shared\killstreaks_shared; +#using scripts\shared\math_shared; +#using scripts\shared\medals_shared; +#using scripts\shared\persistence_shared; +#using scripts\shared\player_shared; +#using scripts\shared\rank_shared; +#using scripts\shared\scoreevents_shared; +#using scripts\shared\tweakables_shared; +#using scripts\shared\util_shared; +#using scripts\shared\weapons_shared; +#using scripts\shared\weapons\_weapon_utils; +#using scripts\shared\weapons\_weapons; +#using scripts\shared\ai\systems\gib; + +#using scripts\mp\gametypes\_battlechatter; +#using scripts\mp\gametypes\_deathicons; +#using scripts\mp\gametypes\_globallogic; +#using scripts\mp\gametypes\_globallogic_audio; +#using scripts\mp\gametypes\_globallogic_score; +#using scripts\mp\gametypes\_globallogic_spawn; +#using scripts\mp\gametypes\_globallogic_ui; +#using scripts\mp\gametypes\_globallogic_utils; +#using scripts\mp\gametypes\_globallogic_vehicle; +#using scripts\mp\gametypes\_hostmigration; +#using scripts\mp\gametypes\_hud_message; +#using scripts\mp\gametypes\_killcam; +#using scripts\mp\gametypes\_loadout; +#using scripts\mp\gametypes\_spawning; +#using scripts\mp\gametypes\_spawnlogic; +#using scripts\mp\gametypes\_spectating; +#using scripts\mp\gametypes\_weapons; + +#using scripts\mp\_armor; +#using scripts\mp\_behavior_tracker; +#using scripts\shared\_burnplayer; +#using scripts\mp\_challenges; +#using scripts\mp\_contracts; +#using scripts\mp\_gamerep; +#using scripts\mp\_laststand; +#using scripts\mp\_teamops; +#using scripts\mp\_util; +#using scripts\mp\_vehicle; +#using scripts\mp\killstreaks\_killstreaks; +#using scripts\mp\teams\_teams; + +#namespace globallogic_player; + +function on_joined_team() +{ + // for cod caster update the top scorers + if ( !level.rankedMatch && !level.teambased ) + { + level thread update_ffa_top_scorers(); + } +} + +function freezePlayerForRoundEnd() +{ + self util::clearLowerMessage(); + + self closeInGameMenu(); + + self util::freeze_player_controls( true ); + + if( !SessionModeIsZombiesGame() ) + { + currentWeapon = self GetCurrentWeapon(); + if ( killstreaks::is_killstreak_weapon( currentWeapon ) && !currentWeapon.isCarriedKillstreak ) + self takeWeapon( currentWeapon ); + } + + //self util::_disableWeapon(); +} + +function ArrayToString( inputArray ) +{ + targetString = ""; + for (i=0; i < inputArray.size; i++) + { + targetString += inputArray[i]; + if (i != inputArray.size-1) + targetString += ","; + } + return targetString; +} + + +function recordEndGameComScoreEventForPlayer( player, result ) +{ + lpselfnum = player getEntityNumber(); + lpXuid = player getxuid(true); + bbPrint( "global_leave", "name %s client %s xuid %s", player.name, lpselfnum, lpXuid ); + + weeklyAContractId = 0; + weeklyAContractTarget = 0; + weeklyAContractCurrent = 0; + weeklyAContractCompleted = 0; + + weeklyBContractId = 0; + weeklyBContractTarget = 0; + weeklyBContractCurrent = 0; + weeklyBContractCompleted = 0; + + dailyContractId = 0; + dailyContractTarget = 0; + dailyContractCurrent = 0; + dailyContractCompleted = 0; + + specialContractId = 0; + specialContractTarget = 0; + specialContractCurent = 0; + specialContractCompleted = 0; + + if ( player util::is_bot() ) + { + currXP = 0; + prevXP = 0; + } + else + { + currXP = player rank::getRankXpStat(); + prevXP = player.pers["rankxp"]; + + if ( globallogic_score::canUpdateWeaponContractStats( player ) ) + { + specialContractId = 1; // this is a one-off contract. Use any non-zero value to indicate that it is populated. + specialContractTarget = GetDvarInt( "weapon_contract_target_value", 100 ); + specialContractCurent = player GetDStat( "weaponContractData", "currentValue" ); + if ( (isdefined(player GetDStat( "weaponContractData", "completeTimestamp" ))?player GetDStat( "weaponContractData", "completeTimestamp" ):0) != 0 ) + { + specialContractCompleted = 1; + } + } + + if ( player contracts::can_process_contracts() ) + { + contractId = player contracts::get_contract_stat( 0, "index" ); + if ( player contracts::is_contract_active( contractId ) ) + { + weeklyAContractId = contractId; + weeklyAContractTarget = player.pers[ "contracts" ][ weeklyAContractId ].target_value; + weeklyAContractCurrent = player contracts::get_contract_stat( 0, "progress" ); + weeklyAContractCompleted = player contracts::get_contract_stat( 0, "award_given" ); + } + + contractId = player contracts::get_contract_stat( 1, "index" ); + if ( player contracts::is_contract_active( contractId ) ) + { + weeklyBContractId = contractId; + weeklyBContractTarget = player.pers["contracts" ][ weeklyBContractId ].target_value; + weeklyBContractCurrent = player contracts::get_contract_stat( 1, "progress" ); + weeklyBContractCompleted = player contracts::get_contract_stat( 1, "award_given" ); + } + + contractId = player contracts::get_contract_stat( 2, "index" ); + if ( player contracts::is_contract_active( contractId ) ) + { + dailyContractId = contractId; + dailyContractTarget = player.pers[ "contracts" ][ dailyContractId ].target_value; + dailyContractCurrent = player contracts::get_contract_stat( 2, "progress" ); + dailyContractCompleted = player contracts::get_contract_stat( 2, "award_given" ); + } + } + } + + if ( !isdefined( prevXP ) ) + { + // most likely the player never fully connected in the first place + // it is possible to specify a default value for prevXP, + // however, the call to RecordComScoreEvent() will SRE as most of player.pers[] is missing + return; + } + + resultStr = result; + if ( isDefined(player.team) && result == player.team ) + resultStr ="win"; + else if ( result == "allies" || result == "axis" ) + resultStr = "lose"; + + xpEarned = currXP - prevXP; + + perkStr = ArrayToString( player GetPerks() ); + + primaryWeaponName = ""; + primaryWeaponAttachStr=""; + secondaryWeaponName = ""; + secondaryWeaponAttachStr=""; + grenadePrimaryName = ""; + grenadeSecondaryName = ""; + + + + if (isdefined( player.primaryLoadoutWeapon )) + { + primaryWeaponName = player.primaryLoadoutWeapon.name; + primaryWeaponAttachStr = ArrayToString( GetArrayKeys(player.primaryLoadoutWeapon.attachments) ); + } + if (isdefined( player.secondaryLoadoutWeapon )) + { + secondaryWeaponName = player.secondaryLoadoutWeapon.name; + secondaryWeaponAttachStr = ArrayToString( GetArrayKeys(player.secondaryLoadoutWeapon.attachments) ); + } + + if (isdefined( player.grenadeTypePrimary )) + grenadePrimaryName = player.grenadeTypePrimary.name; + if (isdefined( player.grenadeTypeSecondary )) + grenadeSecondaryName = player.grenadeTypeSecondary.name; + + killStreakStr = ArrayToString( player.killstreak ); + + gameLength = game["timepassed"] / 1000; + timePlayed = player globallogic::getTotalTimePlayed( gameLength ); + + totalKills = 0; + totalHits = 0; + totalDeaths = 0; + totalWins = 0; + totalXP = 0; + + if ( level.gametype != "fr" ) + { + totalKills = player GetDStat( "playerstatslist", "kills", "statValue" ); + totalHits = player GetDStat( "playerstatslist", "hits", "statValue" ); + totalDeaths = player GetDStat( "playerstatslist", "deaths", "statValue" ); + totalWins = player GetDStat( "playerstatslist", "wins", "statValue" ); + totalXP = player GetDStat( "playerstatslist", "rankxp", "statValue" ); + } + + killCount = 0; + hitCount = 0; + + if( level.mpCustomMatch ) + { + killCount = player.kills; + hitCount = player.shotshit; + } + else + { + if ( isdefined( player.startKills ) ) + killCount = totalKills - player.startKills; + + if ( isdefined( player.startHits ) ) + hitCount = totalHits - player.startHits; + } + + bestScore = "0"; + if ( isdefined( player.pers["lastHighestScore"] ) && player.score > 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" ); + } +}