From d2b15bcd3b4b054139651d4cde499949938d3e83 Mon Sep 17 00:00:00 2001 From: INeedBots Date: Sat, 19 Sep 2020 20:29:32 -0600 Subject: [PATCH] Added forcekill/vote scripts --- userraw/maps/mp/gametypes/_damage.gsc | 2564 ++++++++++++++++++ userraw/maps/mp/gametypes/_gamelogic.gsc | 3083 ++++++++++++++++++++++ 2 files changed, 5647 insertions(+) create mode 100644 userraw/maps/mp/gametypes/_damage.gsc create mode 100644 userraw/maps/mp/gametypes/_gamelogic.gsc diff --git a/userraw/maps/mp/gametypes/_damage.gsc b/userraw/maps/mp/gametypes/_damage.gsc new file mode 100644 index 0000000..3f23a29 --- /dev/null +++ b/userraw/maps/mp/gametypes/_damage.gsc @@ -0,0 +1,2564 @@ +#include maps\mp\_utility; +#include maps\mp\gametypes\_hud_util; +#include common_scripts\utility; + + +isSwitchingTeams() +{ + if ( isDefined( self.switching_teams ) ) + return true; + + return false; +} + + +isTeamSwitchBalanced() +{ + playerCounts = self maps\mp\gametypes\_teams::CountPlayers(); + playerCounts[ self.leaving_team ] -- ; + playerCounts[ self.joining_team ]++ ; + + return( ( playerCounts[ self.joining_team ] - playerCounts[ self.leaving_team ] ) < 2 ); +} + + +isFriendlyFire( victim, attacker ) +{ + if ( !level.teamBased ) + return false; + + if ( !isDefined( attacker ) ) + return false; + + if ( !isPlayer( attacker ) && !isDefined( attacker.team ) ) + return false; + + if ( victim.team != attacker.team ) + return false; + + if ( victim == attacker ) + return false; + + return true; +} + + +killedSelf( attacker ) +{ + if ( !isPlayer( attacker ) ) + return false; + + if ( attacker != self ) + return false; + + return true; +} + + +isHeadShot( sWeapon, sHitLoc, sMeansOfDeath, attacker ) +{ + if ( isDefined( attacker ) ) + { + if ( attacker.code_classname == "script_vehicle" && isDefined( attacker.owner ) ) + return false; + if ( attacker.code_classname == "misc_turret" && isDefined( attacker.owner ) ) + return false; + if ( attacker.code_classname == "script_model" && isDefined( attacker.owner ) ) + return false; + } + + return( sHitLoc == "head" || sHitLoc == "helmet" ) && sMeansOfDeath != "MOD_MELEE" && sMeansOfDeath != "MOD_IMPACT" && !isMG( sWeapon ); +} + + +handleTeamChangeDeath() +{ + if ( !level.teamBased ) + return; + + // this might be able to happen now, but we should remove instances where it can + assert( self.leaving_team != self.joining_team ); + + if ( self.joining_team == "spectator" || !isTeamSwitchBalanced() ) + { + self thread [[ level.onXPEvent ]]( "suicide" ); + self incPersStat( "suicides", 1 ); + self.suicides = self getPersStat( "suicides" ); + } +} + + +handleWorldDeath( attacker, lifeId, sMeansOfDeath, sHitLoc ) +{ + if ( !isDefined( attacker ) ) + return; + + if ( !isDefined( attacker.team ) ) + { + handleSuicideDeath( sMeansOfDeath, sHitLoc ); + return; + } + + assert( attacker.team == "axis" || attacker.team == "allies" ); + + if ( level.teamBased && attacker.team != self.team ) + { + if ( isDefined( level.onNormalDeath ) && isPlayer( attacker ) && attacker.team != "spectator" ) + [[ level.onNormalDeath ]]( self, attacker, lifeId ); + } +} + + +handleSuicideDeath( sMeansOfDeath, sHitLoc ) +{ + self SetCardDisplaySlot( self, 7 ); + self openMenu( "killedby_card_display" ); + + self thread [[ level.onXPEvent ]]( "suicide" ); + self incPersStat( "suicides", 1 ); + self.suicides = self getPersStat( "suicides" ); + + if ( !matchMakingGame() ) + self incPlayerStat( "suicides", 1 ); + + scoreSub = maps\mp\gametypes\_tweakables::getTweakableValue( "game", "suicidepointloss" ); + maps\mp\gametypes\_gamescore::_setPlayerScore( self, maps\mp\gametypes\_gamescore::_getPlayerScore( self ) - scoreSub ); + + if ( sMeansOfDeath == "MOD_SUICIDE" && sHitLoc == "none" && isDefined( self.throwingGrenade ) ) + self.lastGrenadeSuicideTime = gettime(); + + // suicide was caused by too many team kills + if ( isDefined( self.friendlydamage ) ) + self iPrintLnBold( &"MP_FRIENDLY_FIRE_WILL_NOT" ); +} + + +handleFriendlyFireDeath( attacker ) +{ + attacker SetCardDisplaySlot( self, 8 ); + attacker openMenu( "youkilled_card_display" ); + + self SetCardDisplaySlot( attacker, 7 ); + self openMenu( "killedby_card_display" ); + + attacker thread [[ level.onXPEvent ]]( "teamkill" ); + attacker.pers[ "teamkills" ] += 1.0; + + attacker.teamkillsThisRound++ ; + + if ( maps\mp\gametypes\_tweakables::getTweakableValue( "team", "teamkillpointloss" ) ) + { + scoreSub = maps\mp\gametypes\_rank::getScoreInfoValue( "kill" ); + maps\mp\gametypes\_gamescore::_setPlayerScore( attacker, maps\mp\gametypes\_gamescore::_getPlayerScore( attacker ) - scoreSub ); + } + + if ( level.maxAllowedTeamkills < 0 ) + return; + + if ( level.inGracePeriod ) + { + teamKillDelay = 1; + attacker.pers["teamkills"] += level.maxAllowedTeamkills; + } + else if ( attacker.pers[ "teamkills" ] > 1 && getTimePassed() < ( (level.gracePeriod * 1000) + 8000 + ( attacker.pers[ "teamkills" ] * 1000 ) ) ) + { + teamKillDelay = 1; + attacker.pers["teamkills"] += level.maxAllowedTeamkills; + } + else + { + teamKillDelay = attacker maps\mp\gametypes\_playerlogic::TeamKillDelay(); + } + + if ( teamKillDelay > 0 ) + { + attacker.pers["teamKillPunish"] = true; + attacker _suicide(); + } +} + + +handleNormalDeath( lifeId, attacker, eInflictor, sWeapon, sMeansOfDeath ) +{ + attacker thread maps\mp\_events::killedPlayer( lifeId, self, sWeapon, sMeansOfDeath ); + + //if ( attacker.pers["teamkills"] <= level.maxAllowedTeamkills ) + // attacker.pers["teamkills"] = max( attacker.pers["teamkills"] - 1, 0 ); + + attacker SetCardDisplaySlot( self, 8 ); + attacker openMenu( "youkilled_card_display" ); + + self SetCardDisplaySlot( attacker, 7 ); + self openMenu( "killedby_card_display" ); + + if ( sMeansOfDeath == "MOD_HEAD_SHOT" ) + { + attacker incPersStat( "headshots", 1 ); + attacker.headshots = attacker getPersStat( "headshots" ); + attacker incPlayerStat( "headshots", 1 ); + + if ( isDefined( attacker.lastStand ) ) + value = maps\mp\gametypes\_rank::getScoreInfoValue( "kill" ) * 2; + else + value = undefined; + + if( !level.extraDamageFeedback ) + attacker playLocalSound( "bullet_impact_headshot_2" ); + } + else + { + if ( isDefined( attacker.lastStand ) ) + value = maps\mp\gametypes\_rank::getScoreInfoValue( "kill" ) * 2; + else + value = undefined; + } + + attacker thread maps\mp\gametypes\_rank::giveRankXP( "kill", value ); + + attacker incPersStat( "kills", 1 ); + attacker.kills = attacker getPersStat( "kills" ); + attacker updatePersRatio( "kdRatio", "kills", "deaths" ); + attacker maps\mp\gametypes\_persistence::statSetChild( "round", "kills", attacker.kills ); + attacker incPlayerStat( "kills", 1 ); + + if ( isFlankKill( self, attacker ) ) + { + attacker incPlayerStat( "flankkills", 1 ); + + self incPlayerStat( "flankdeaths", 1 ); + } + + lastKillStreak = attacker.pers["cur_kill_streak"]; + + self.pers["copyCatLoadout"] = undefined; + + if ( self _hasPerk( "specialty_copycat" ) ) + self.pers["copyCatLoadout"] = attacker maps\mp\gametypes\_class::cloneLoadout(); + + if ( isAlive( attacker ) ) + { + // killstreaks only advance from kills earned this life + if ( isDefined( level.killStreakSpecialCaseWeapons[sWeapon] ) || sWeapon == "nuke_mp" ) // this is an optimization + { + switch ( sWeapon ) + { + case "ac130_105mm_mp": + case "ac130_40mm_mp": + case "ac130_25mm_mp": + if ( attacker.ac130LifeId == attacker.pers["deaths"] && !level.scriptIncKillstreak ) + attacker.pers["cur_kill_streak"]++; + break; + case "cobra_player_minigun_mp": + case "weapon_cobra_mk19_mp": + if ( attacker.heliRideLifeId == attacker.pers["deaths"] && !level.scriptIncKillstreak ) + attacker.pers["cur_kill_streak"]++; + break; + case "cobra_20mm_mp": + case "artillery_mp": + case "stealth_bomb_mp": + case "remotemissile_projectile_mp": + case "sentry_minigun_mp": + case "harrier_20mm_mp": + case "pavelow_minigun_mp": + case "nuke_mp": + if ( isDefined( eInflictor ) && isDefined( eInflictor.lifeId ) ) + killstreakLifeId = eInflictor.lifeId; + else + killstreakLifeId = attacker.lifeId; + + if ( killstreakLifeId == attacker.pers["deaths"] && !level.scriptIncKillstreak && (level.nukeIncreasesStreak || sWeapon != "nuke_mp") ) + attacker.pers["cur_kill_streak"]++; + break; + default: + if( !level.scriptIncKillstreak ) + attacker.pers["cur_kill_streak"]++; + break; + } + } + else + { + if( !level.scriptIncKillstreak ) + attacker.pers["cur_kill_streak"]++; + } + + attacker setPlayerStatIfGreater( "killstreak", attacker.pers["cur_kill_streak"] ); + + if ( attacker.pers["cur_kill_streak"] > attacker getPersStat( "longestStreak" ) ) + attacker setPersStat( "longestStreak", attacker.pers["cur_kill_streak"] ); + } + + attacker.pers["cur_death_streak"] = 0; + + if ( attacker.pers["cur_kill_streak"] > attacker maps\mp\gametypes\_persistence::statGetChild( "round", "killStreak" ) ) + { + attacker maps\mp\gametypes\_persistence::statSetChild( "round", "killStreak", attacker.pers["cur_kill_streak"] ); + } + + if ( attacker.pers["cur_kill_streak"] > attacker.kill_streak ) + { + attacker maps\mp\gametypes\_persistence::statSet( "killStreak", attacker.pers["cur_kill_streak"] ); + attacker.kill_streak = attacker.pers["cur_kill_streak"]; + } + + maps\mp\gametypes\_gamescore::givePlayerScore( "kill", attacker, self ); + maps\mp\_skill::processKill( attacker, self ); + + scoreSub = maps\mp\gametypes\_tweakables::getTweakableValue( "game", "deathpointloss" ); + maps\mp\gametypes\_gamescore::_setPlayerScore( self, maps\mp\gametypes\_gamescore::_getPlayerScore( self ) - scoreSub ); + + if ( isDefined( level.ac130player ) && level.ac130player == attacker ) + level notify( "ai_killed", self ); + + //if ( lastKillStreak != attacker.pers["cur_kill_streak"] ) + level notify ( "player_got_killstreak_" + attacker.pers["cur_kill_streak"], attacker ); + + if ( isAlive( attacker ) ) + attacker thread maps\mp\killstreaks\_killstreaks::checkKillstreakReward( attacker.pers["cur_kill_streak"] ); + + attacker notify ( "killed_enemy" ); + + if ( !level.teamBased ) + { + self.attackers = []; + return; + } + + if ( isDefined( level.onNormalDeath ) && attacker.pers[ "team" ] != "spectator" ) + [[ level.onNormalDeath ]]( self, attacker, lifeId ); + + if ( isDefined( level.onNormalDeath2 ) && attacker.pers[ "team" ] != "spectator" ) + [[ level.onNormalDeath2 ]]( self, attacker, sMeansOfDeath ); + + level thread maps\mp\gametypes\_battlechatter_mp::sayLocalSoundDelayed( attacker, "kill", 0.75 ); + + if ( isDefined( self.lastAttackedShieldPlayer ) && isDefined( self.lastAttackedShieldTime ) && self.lastAttackedShieldPlayer != attacker ) + { + if ( getTime() - self.lastAttackedShieldTime < 2500 ) + { + self.lastAttackedShieldPlayer thread maps\mp\gametypes\_gamescore::processShieldAssist( self ); + } + else if ( isAlive( self.lastAttackedShieldPlayer ) && getTime() - self.lastAttackedShieldTime < 5000 ) + { + forwardVec = vectorNormalize( anglesToForward( self.angles ) ); + shieldVec = vectorNormalize( self.lastAttackedShieldPlayer.origin - self.origin ); + + if ( vectorDot( shieldVec, forwardVec ) > 0.925 ) + self.lastAttackedShieldPlayer thread maps\mp\gametypes\_gamescore::processShieldAssist( self ); + } + } + + if ( isDefined( self.attackers ) ) + { + foreach ( player in self.attackers ) + { + if ( !isDefined( player ) ) + continue; + + if ( player == attacker ) + continue; + + player thread maps\mp\gametypes\_gamescore::processAssist( self ); + } + self.attackers = []; + } +} + +isPlayerWeapon( weaponName ) +{ + if ( weaponClass( weaponName ) == "non-player" ) + return false; + + if ( weaponClass( weaponName ) == "turret" ) + return false; + + if ( weaponInventoryType( weaponName ) == "primary" || weaponInventoryType( weaponName ) == "altmode" ) + return true; + + return false; +} + +Callback_PlayerKilled( eInflictor, attacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration ) +{ + PlayerKilled_internal( eInflictor, attacker, self, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration, false ); +} + + +QueueShieldForRemoval( shield ) +{ + MY_MAX_SHIELDS_AT_A_TIME = 5; + + if ( !isDefined( level.shieldTrashArray ) ) + level.shieldTrashArray = []; + + if ( level.shieldTrashArray.size >= MY_MAX_SHIELDS_AT_A_TIME ) + { + idxMax = (level.shieldTrashArray.size - 1); + level.shieldTrashArray[0] delete(); + for ( idx = 0; idx < idxMax; idx++ ) + level.shieldTrashArray[idx] = level.shieldTrashArray[idx + 1]; + level.shieldTrashArray[idxMax] = undefined; + } + + level.shieldTrashArray[level.shieldTrashArray.size] = shield; +} + + +LaunchShield( damage, meansOfDeath ) +{ + shieldModel = "weapon_riot_shield_mp"; + + self DetachShieldModel( shieldModel, "tag_weapon_left" ); + self.hasRiotShield = false; + self.hasRiotShieldEquipped = false; +} + + +PlayerKilled_internal( eInflictor, attacker, victim, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration, isFauxDeath ) +{ + prof_begin( "PlayerKilled" ); + //prof_begin( " PlayerKilled_1" ); + + victim endon( "spawned" ); + victim notify( "killed_player" ); + + assert( victim.sessionteam != "spectator" ); + + if ( isDefined( attacker ) ) + attacker.assistedSuicide = undefined; + + if ( !isDefined( victim.idFlags ) ) + { + if ( sMeansOfDeath == "MOD_SUICIDE" ) + victim.idFlags = 0; + else if ( sMeansOfDeath == "MOD_GRENADE" && isSubstr( sWeapon, "frag_grenade" ) && iDamage == 100000 ) + victim.idFlags = 0; + else if ( sWeapon == "nuke_mp" && !level.forceFinalKillcam ) + victim.idFlags = 0; + else if ( level.friendlyfire >= 2) + victim.idFlags = 0; + else + assertEx( 0, "Victims ID flags not set, but means of death was gr or nuke: " + sMeansOfDeath ); + } + + if ( victim.hasRiotShieldEquipped ) + victim LaunchShield( iDamage, sMeansofDeath ); + + //victim thread checkForceBleedOut(); + + if ( !isFauxDeath ) + { + if ( isDefined( victim.endGame ) ) + { + victim VisionSetNakedForPlayer( getDvar( "mapname" ), 2 ); + } + else if ( !isDefined( victim.nuked ) ) + { + victim VisionSetNakedForPlayer( getDvar( "mapname" ), 0 ); + victim ThermalVisionOff(); + } + } + else + { + victim.fauxDead = true; + self notify ( "death" ); + } + + if ( game[ "state" ] == "postgame" ) + { + //prof_end( " PlayerKilled_1" ); + prof_end( "PlayerKilled" ); + return; + } + + // replace params with last stand info + deathTimeOffset = 0; + + if ( !isPlayer( eInflictor ) && isDefined( eInflictor.primaryWeapon ) ) + sPrimaryWeapon = eInflictor.primaryWeapon; + else if ( isDefined( attacker ) && isPlayer( attacker ) && attacker getCurrentPrimaryWeapon() != "none" ) + sPrimaryWeapon = attacker getCurrentPrimaryWeapon(); + else + sPrimaryWeapon = undefined; + + if ( isdefined( victim.useLastStandParams ) ) + { + victim ensureLastStandParamsValidity(); + victim.useLastStandParams = undefined; + + assert( isdefined( victim.lastStandParams ) ); + + eInflictor = victim.lastStandParams.eInflictor; + attacker = victim.lastStandParams.attacker; + iDamage = victim.lastStandParams.iDamage; + sMeansOfDeath = victim.lastStandParams.sMeansOfDeath; + sWeapon = victim.lastStandParams.sWeapon; + sPrimaryWeapon = victim.lastStandParams.sPrimaryWeapon; + vDir = victim.lastStandParams.vDir; + sHitLoc = victim.lastStandParams.sHitLoc; + + deathTimeOffset = ( gettime() - victim.lastStandParams.lastStandStartTime ) / 1000; + victim.lastStandParams = undefined; + } + + //prof_end( " PlayerKilled_1" ); + //prof_begin( " PlayerKilled_2" ); + + //used for endgame perk and assisted suicide. + if ( (!isDefined( attacker ) || attacker.classname == "trigger_hurt" || attacker.classname == "worldspawn" || attacker == victim ) && isDefined( self.attackers ) ) + { + bestPlayer = undefined; + + foreach ( player in self.attackers ) + { + if ( !isDefined( player ) ) + continue; + + if (! isDefined( victim.attackerData[ player.guid ].damage ) ) + continue; + + if ( player == victim || (level.teamBased && player.team == victim.team ) ) + continue; + + if ( victim.attackerData[ player.guid ].lasttimedamaged + 2500 < getTime() ) + continue; + + if ( victim.attackerData[ player.guid ].damage > 1 && ! isDefined( bestPlayer ) ) + bestPlayer = player; + else if ( isDefined( bestPlayer ) && victim.attackerData[ player.guid ].damage > victim.attackerData[ bestPlayer.guid ].damage ) + bestPlayer = player; + } + + if ( isDefined( bestPlayer ) ) + { + attacker = bestPlayer; + attacker.assistedSuicide = true; + sWeapon = victim.attackerData[ bestPlayer.guid ].weapon; + vDir = victim.attackerData[ bestPlayer.guid ].vDir; + sHitLoc = victim.attackerData[ bestPlayer.guid ].sHitLoc; + psOffsetTime = victim.attackerData[ bestPlayer.guid ].psOffsetTime; + sMeansOfDeath = victim.attackerData[ bestPlayer.guid ].sMeansOfDeath; + iDamage = victim.attackerData[ bestPlayer.guid ].damage; + sPrimaryWeapon = victim.attackerData[ bestPlayer.guid ].sPrimaryWeapon; + eInflictor = attacker; + } + } + else + { + if ( isDefined( attacker ) ) + attacker.assistedSuicide = undefined; + } + + // override MOD + if ( isHeadShot( sWeapon, sHitLoc, sMeansOfDeath, attacker ) ) + { + sMeansOfDeath = "MOD_HEAD_SHOT"; + if( level.headShotDetachHead ) + victim detach(victim.headmodel); + } + else if ( sMeansOfDeath != "MOD_MELEE" && !isDefined( victim.nuked ) ) + victim playDeathSound(); + + friendlyFire = isFriendlyFire( victim, attacker ); + + if ( isDefined( attacker ) ) + { + // override attacker if it's a vehicle + if ( attacker.code_classname == "script_vehicle" && isDefined( attacker.owner ) ) + attacker = attacker.owner; + + // override attacker if it's a sentry + if ( attacker.code_classname == "misc_turret" && isDefined( attacker.owner ) ) + attacker = attacker.owner; + + // override attacker if it's a crate + if ( attacker.code_classname == "script_model" && isDefined( attacker.owner ) ) + { + attacker = attacker.owner; + + if ( !isFriendlyFire( victim, attacker ) && attacker != victim ) + attacker notify( "crushed_enemy" ); + } + } + + //prof_end( " PlayerKilled_2" ); + //prof_begin( " PlayerKilled_3" ); + + //prof_begin( " PlayerKilled_3_drop" ); + // drop weapons from killed player + victim maps\mp\gametypes\_weapons::dropScavengerForDeath( attacker ); // must be done before dropWeaponForDeath, since we use some weapon information + victim maps\mp\gametypes\_weapons::dropWeaponForDeath( attacker ); + //prof_end( " PlayerKilled_3_drop" ); + + if ( !isFauxDeath ) + { + victim.sessionstate = "dead"; + victim.statusicon = "hud_status_dead"; + } + + // UTS update aliveCount + victim maps\mp\gametypes\_playerlogic::removeFromAliveCount(); + + if ( !isDefined( victim.switching_teams ) ) + { + // update our various stats + victim incPersStat( "deaths", 1 ); + victim.deaths = victim getPersStat( "deaths" ); + victim updatePersRatio( "kdRatio", "kills", "deaths" ); + victim maps\mp\gametypes\_persistence::statSetChild( "round", "deaths", victim.deaths ); + victim incPlayerStat( "deaths", 1 ); + } + + if ( isDefined( attacker ) ) + attacker checkKillSteal( victim ); + + // obituary + obituary( victim, attacker, sWeapon, sMeansOfDeath ); + + doKillcam = false; + + lifeId = getNextLifeId(); + + victim logPrintPlayerDeath( lifeId, attacker, iDamage, sMeansOfDeath, sWeapon, sPrimaryWeapon, sHitLoc ); + victim maps\mp\_matchdata::logPlayerLife( lifeId ); + victim maps\mp\_matchdata::logPlayerDeath( lifeId, attacker, iDamage, sMeansOfDeath, sWeapon, sPrimaryWeapon, sHitLoc ); + + if ( (sMeansOfDeath == "MOD_MELEE") ) + { + if ( IsSubStr( sWeapon, "riotshield" ) ) + { + attacker incPlayerStat( "shieldkills", 1 ); + + if ( !matchMakingGame() ) + victim incPlayerStat( "shielddeaths", 1 ); + } + else + attacker incPlayerStat( "knifekills", 1 ); + } + + //prof_end( " PlayerKilled_3" ); + //prof_begin( " PlayerKilled_4" ); + + if ( victim isSwitchingTeams() ) + { + handleTeamChangeDeath(); + } + else if ( !isPlayer( attacker ) || (isPlayer( attacker ) && sMeansOfDeath == "MOD_FALLING") ) + { + handleWorldDeath( attacker, lifeId, sMeansOfDeath, sHitLoc ); + if ( level.failCam ) + doKillcam = true; + } + else if ( attacker == victim ) + { + handleSuicideDeath( sMeansOfDeath, sHitLoc ); + if ( level.failCam ) + doKillcam = true; + } + else if ( friendlyFire ) + { + if ( !isDefined( victim.nuked ) ) + { + handleFriendlyFireDeath( attacker ); + } + if ( level.failCam ) + doKillcam = true; + } + else + { + if ( sMeansOfDeath == "MOD_GRENADE" && eInflictor == attacker ) + addAttacker( victim, attacker, eInflictor, sWeapon, iDamage, (0,0,0), vDir, sHitLoc, psOffsetTime, sMeansOfDeath ); + + doKillcam = true; + handleNormalDeath( lifeId, attacker, eInflictor, sWeapon, sMeansOfDeath ); + victim thread maps\mp\gametypes\_missions::playerKilled( eInflictor, attacker, iDamage, sMeansOfDeath, sWeapon, sPrimaryWeapon, sHitLoc, attacker.modifiers ); + + victim.pers["cur_death_streak"]++; + + if ( !getGametypeNumLives() && !matchMakingGame() ) + victim setPlayerStatIfGreater( "deathstreak", victim.pers["cur_death_streak"] ); + } + + //prof_end( " PlayerKilled_4" ); + //prof_begin( " PlayerKilled_5" ); + + // clear any per life variables + victim resetPlayerVariables(); + victim.lastAttacker = attacker; + victim.lastDeathPos = victim.origin; + victim.deathTime = getTime(); + victim.wantSafeSpawn = false; + victim.revived = false; + victim.sameShotDamage = 0; + + if ( isFauxDeath ) + { + doKillcam = false; + deathAnimDuration = (victim PlayerForceDeathAnim( eInflictor, sMeansOfDeath, sWeapon, sHitLoc, vDir )); + } + + victim.body = victim clonePlayer( deathAnimDuration ); + + if ( isFauxDeath ) + victim PlayerHide(); + + if ( victim isOnLadder() || victim isMantling() || !victim isOnGround() || isDefined( victim.nuked ) ) + victim.body startRagDoll(); + + if ( !isDefined( victim.switching_teams ) ) + thread maps\mp\gametypes\_deathicons::addDeathicon( victim.body, victim, victim.team, 5.0 ); + + thread delayStartRagdoll( victim.body, sHitLoc, vDir, sWeapon, eInflictor, sMeansOfDeath ); + + // allow per gametype death handling + victim thread [[ level.onPlayerKilled ]]( eInflictor, attacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration, lifeId ); + + if ( isPlayer( attacker ) ) + attackerNum = attacker getEntityNumber(); + else if ( (sMeansOfDeath == "MOD_FALLING" || eInflictor.classname == "trigger_hurt") && level.failCam )//banz + attackerNum = victim getEntityNumber(); + else + attackerNum = -1; + killcamentity = victim getKillcamEntity( attacker, eInflictor, sWeapon, sMeansOfDeath ); + killcamentityindex = -1; + killcamentitystarttime = 0; + + if ( isDefined( killcamentity ) ) + { + killcamentityindex = killcamentity getEntityNumber();// must do this before any waiting lest the entity be deleted + killcamentitystarttime = killcamentity.birthtime; + if ( !isdefined( killcamentitystarttime ) ) + killcamentitystarttime = 0; + } + + /# + if ( getDvarInt( "scr_forcekillcam" ) != 0 ) + doKillcam = true; + #/ + + // record the kill cam values for the final kill cam + if ( level.forceFinalKillcam && doKillcam ) + maps\mp\gametypes\_gamelogic::recordFinalKillCam( 5.0, victim, attacker, attackerNum, killcamentityindex, killcamentitystarttime, sWeapon, deathTimeOffset, psOffsetTime ); + + if ( !level.forceFinalKillcam && level.allowFinalKillcam ) + { + if ( isDefined( attacker.finalKill ) ) + maps\mp\_awards::addAwardWinner( "finalkill", attacker.clientid ); + + //prof_end( " PlayerKilled_5" ); + //prof_begin( " PlayerKilled_6" ); + + if ( isDefined( attacker.finalKill ) && doKillcam && !isDefined( level.nukeDetonated ) ) + { + level thread doFinalKillcam( 5.0, victim, attacker, attackerNum, killcamentityindex, killcamentitystarttime, sWeapon, deathTimeOffset, psOffsetTime ); + + if ( !isFauxDeath ) + wait ( 1.0 ); + } + } + + if ( !isFauxDeath ) + { + if ( !level.showingFinalKillcam && !level.killcam && doKillcam ) + { + if ( victim _hasPerk( "specialty_copycat" ) && isDefined( victim.pers["copyCatLoadout"] ) ) + { + victim thread maps\mp\gametypes\_killcam::waitDeathCopyCatButton( attacker ); + wait ( 1.0 ); + } + } + + // let the player watch themselves die + wait( 0.25 ); + victim thread maps\mp\gametypes\_killcam::cancelKillCamOnUse(); + wait( 0.25 ); + + self.respawnTimerStartTime = gettime() + 1000; + timeUntilSpawn = maps\mp\gametypes\_playerlogic::TimeUntilSpawn( true ); + if ( timeUntilSpawn < 1 ) + timeUntilSpawn = 1; + victim thread maps\mp\gametypes\_playerlogic::predictAboutToSpawnPlayerOverTime( timeUntilSpawn ); + + wait( 1.0 ); + victim notify( "death_delay_finished" ); + } + + postDeathDelay = ( getTime() - victim.deathTime ) / 1000; + self.respawnTimerStartTime = gettime(); + + if ( !(isDefined( victim.cancelKillcam) && victim.cancelKillcam) && doKillcam && level.killcam && game[ "state" ] == "playing" && !victim isUsingRemote() && !level.showingFinalKillcam ) + { + livesLeft = !( getGametypeNumLives() && !victim.pers[ "lives" ] ); + timeUntilSpawn = maps\mp\gametypes\_playerlogic::TimeUntilSpawn( true ); + willRespawnImmediately = livesLeft && ( timeUntilSpawn <= 0 ); + + if ( !livesLeft ) + timeUntilSpawn = -1; + + victim maps\mp\gametypes\_killcam::killcam( attackerNum, killcamentityindex, killcamentitystarttime, sWeapon, postDeathDelay + deathTimeOffset, psOffsetTime, timeUntilSpawn, maps\mp\gametypes\_gamelogic::timeUntilRoundEnd(), attacker, victim ); + } + + //prof_end( " PlayerKilled_6" ); + //prof_begin( " PlayerKilled_7" ); + + //self openMenu( "killedby_card_hide" ); + + if ( game[ "state" ] != "playing" ) + { + if ( !level.showingFinalKillcam ) + { + victim.sessionstate = "dead"; + victim ClearKillcamState(); + } + + //prof_end( " PlayerKilled_7" ); + prof_end( "PlayerKilled" ); + return; + } + + // class may be undefined if we have changed teams + if ( isValidClass( victim.class ) ) + { + victim thread maps\mp\gametypes\_playerlogic::spawnClient(); + } + + //prof_end( " PlayerKilled_7" ); + prof_end( "PlayerKilled" ); +} + +checkForceBleedout() +{ + if ( level.dieHardMode != 1 ) + return false; + + if ( !getGametypeNumLives() ) + return false; + + if ( level.livesCount[self.team] > 0 ) + return false; + + foreach ( player in level.players ) + { + if ( !isAlive( player ) ) + continue; + + if ( player.team != self.team ) + continue; + + if ( player == self ) + continue; + + if ( !player.inLastStand ) + return false; + } + + foreach ( player in level.players ) + { + if ( !isAlive( player ) ) + continue; + + if ( player.team != self.team ) + continue; + + if ( player.inLastStand && player != self ) + player lastStandBleedOut(false); + } + + return true; +} + +checkKillSteal( vic ) +{ + if ( matchMakingGame() ) + return; + + greatestDamage = 0; + greatestAttacker = undefined; + + if ( isDefined( vic.attackerdata ) && vic.attackerdata.size > 1 ) + { + foreach ( attacker in vic.attackerdata ) + { + if ( attacker.damage > greatestDamage ) + { + greatestDamage = attacker.damage; + greatestAttacker = attacker.attackerEnt; + } + } + + if ( isDefined( greatestAttacker ) && greatestAttacker != self ) + self incPlayerStat( "killsteals", 1 ); + } +} + +doFinalKillcam( delay, victim, attacker, attackerNum, killcamentityindex, killcamentitystarttime, sWeapon, deathTimeOffset, psOffsetTime ) +{ + level.showingFinalKillcam = true; + + level waittill ( "round_end_finished" ); + + if ( !isDefined( victim ) || !isDefined( attacker ) ) + { + level.showingFinalKillcam = false; + return; + } + + postDeathDelay = (( getTime() - victim.deathTime ) / 1000); + + foreach ( player in level.players ) + { + player closePopupMenu(); + player closeInGameMenu(); + player VisionSetNakedForPlayer( getDvar( "mapname" ), 0 ); + player.killcamentitylookat = victim getEntityNumber(); + + if ( (player != victim || (!isRoundBased() || isLastRound())) && player _hasPerk( "specialty_copycat" ) ) + player _unsetPerk( "specialty_copycat" ); + + player thread maps\mp\gametypes\_killcam::killcam( attackerNum, killcamentityindex, killcamentitystarttime, sWeapon, postDeathDelay + deathTimeOffset, psOffsetTime, 0, 10000, attacker, victim ); + } + + wait( 0.1 ); + + while ( anyPlayersInKillcam() ) + wait( 0.05 ); + + level.showingFinalKillcam = false; +} + + +anyPlayersInKillcam() +{ + foreach ( player in level.players ) + { + if ( isDefined( player.killcam ) ) + return true; + } + + return false; +} + + +resetPlayerVariables() +{ + self.killedPlayersCurrent = []; + self.switching_teams = undefined; + self.joining_team = undefined; + self.leaving_team = undefined; + + self.pers["cur_kill_streak"] = 0; + + self maps\mp\gametypes\_gameobjects::detachUseModels();// want them detached before we create our corpse +} + + +getKillcamEntity( attacker, eInflictor, sWeapon, sMeansOfDeath ) +{ + if ( !isDefined( eInflictor ) ) + return undefined; + + if( (sMeansOfDeath == "MOD_FALLING" || eInflictor.classname == "trigger_hurt") && level.failCam ) + return attacker; + + if ( eInflictor == attacker ) + return undefined; + + if ( isSubStr( sWeapon, "ac130_" ) ) + return undefined; + + if ( sWeapon == "cobra_player_minigun_mp" ) + return undefined; + + if ( sWeapon == "artillery_mp" || sWeapon == "stealth_bomb_mp" || sWeapon == "pavelow_minigun_mp" ) + return eInflictor.killCamEnt; + + if ( isDefined( eInflictor.script_gameobjectname ) && eInflictor.script_gameobjectname == "bombzone" ) + return eInflictor.killCamEnt; + + if ( eInflictor.classname == "script_origin" || eInflictor.classname == "script_model" || eInflictor.classname == "script_brushmodel" ) + return undefined; // probably a barrel or a car... code does airstrike cam for these things which looks bad + + if ( issubstr( sWeapon, "remotemissile_" ) ) + return undefined; + if ( issubstr( sWeapon, "ac130_" ) ) + return undefined; + + return eInflictor; +} + + +HitlocDebug( attacker, victim, damage, hitloc, dflags ) +{ + colors = []; + colors[ 0 ] = 2; + colors[ 1 ] = 3; + colors[ 2 ] = 5; + colors[ 3 ] = 7; + + if ( !getdvarint( "scr_hitloc_debug" ) ) + return; + + if ( !isdefined( attacker.hitlocInited ) ) + { + for ( i = 0; i < 6; i++ ) + { + attacker setClientDvar( "ui_hitloc_" + i, "" ); + } + attacker.hitlocInited = true; + } + + if ( level.splitscreen || !isPLayer( attacker ) ) + return; + + elemcount = 6; + if ( !isdefined( attacker.damageInfo ) ) + { + attacker.damageInfo = []; + for ( i = 0; i < elemcount; i++ ) + { + attacker.damageInfo[ i ] = spawnstruct(); + attacker.damageInfo[ i ].damage = 0; + attacker.damageInfo[ i ].hitloc = ""; + attacker.damageInfo[ i ].bp = false; + attacker.damageInfo[ i ].jugg = false; + attacker.damageInfo[ i ].colorIndex = 0; + } + attacker.damageInfoColorIndex = 0; + attacker.damageInfoVictim = undefined; + } + + for ( i = elemcount - 1; i > 0; i -- ) + { + attacker.damageInfo[ i ].damage = attacker.damageInfo[ i - 1 ].damage; + attacker.damageInfo[ i ].hitloc = attacker.damageInfo[ i - 1 ].hitloc; + attacker.damageInfo[ i ].bp = attacker.damageInfo[ i - 1 ].bp; + attacker.damageInfo[ i ].jugg = attacker.damageInfo[ i - 1 ].jugg; + attacker.damageInfo[ i ].colorIndex = attacker.damageInfo[ i - 1 ].colorIndex; + } + attacker.damageInfo[ 0 ].damage = damage; + attacker.damageInfo[ 0 ].hitloc = hitloc; + attacker.damageInfo[ 0 ].bp = ( dflags & level.iDFLAGS_PENETRATION ); + attacker.damageInfo[ 0 ].jugg = victim hasPerk( "specialty_armorvest", true ); + if ( isdefined( attacker.damageInfoVictim ) && ( attacker.damageInfoVictim != victim ) ) + { + attacker.damageInfoColorIndex++ ; + if ( attacker.damageInfoColorIndex == colors.size ) + attacker.damageInfoColorIndex = 0; + } + attacker.damageInfoVictim = victim; + attacker.damageInfo[ 0 ].colorIndex = attacker.damageInfoColorIndex; + + for ( i = 0; i < elemcount; i++ ) + { + color = "^" + colors[ attacker.damageInfo[ i ].colorIndex ]; + if ( attacker.damageInfo[ i ].hitloc != "" ) + { + val = color + attacker.damageInfo[ i ].hitloc; + if ( attacker.damageInfo[ i ].bp ) + val += " (BP)"; + if ( attacker.damageInfo[ i ].jugg ) + val += " (Jugg)"; + attacker setClientDvar( "ui_hitloc_" + i, val ); + } + attacker setClientDvar( "ui_hitloc_damage_" + i, color + attacker.damageInfo[ i ].damage ); + } +} + +giveRecentShieldXP() +{ + self endon ( "death" ); + self endon ( "disconnect" ); + + self notify ( "giveRecentShieldXP" ); + self endon ( "giveRecentShieldXP" ); + + self.recentShieldXP++; + + wait ( 20.0 ); + + self.recentShieldXP = 0; +} + + +Callback_PlayerDamage_internal( eInflictor, eAttacker, victim, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime ) +{ + if( level.disableKnife && sMeansOfDeath == "MOD_MELEE" && sWeapon != "riotshield_mp" ) + return; + + if( level.disableTurret && sWeapon == "turret_minigun_mp" ) + return; + + if ( !isReallyAlive( victim ) ) + return; + + if ( isDefined( eAttacker ) && eAttacker.classname == "script_origin" && isDefined( eAttacker.type ) && eAttacker.type == "soft_landing" ) + return; + + if ( isDefined( level.hostMigrationTimer ) ) + return; + + if ( sMeansOfDeath == "MOD_FALLING" ) + victim thread emitFallDamage( iDamage ); + + if ( sMeansOfDeath == "MOD_EXPLOSIVE_BULLET" && iDamage != 1 ) + { + iDamage *= getDvarFloat( "scr_explBulletMod" ); + iDamage = int( iDamage ); + } + + if ( isDefined( eAttacker ) && eAttacker.classname == "worldspawn" ) + eAttacker = undefined; + + if ( isDefined( eAttacker ) && isDefined( eAttacker.gunner ) ) + eAttacker = eAttacker.gunner; + + attackerIsNPC = isDefined( eAttacker ) && !isDefined( eAttacker.gunner ) && (eAttacker.classname == "script_vehicle" || eAttacker.classname == "misc_turret" || eAttacker.classname == "script_model"); + attackerIsHittingTeammate = level.teamBased && isDefined( eAttacker ) && ( victim != eAttacker ) && isDefined( eAttacker.team ) && ( victim.pers[ "team" ] == eAttacker.team ); + + stunFraction = 0.0; + + if ( iDFlags & level.iDFLAGS_STUN ) + { + stunFraction = 0.0; + //victim StunPlayer( 1.0 ); + iDamage = 0.0; + } + else if ( sHitLoc == "shield" ) + { + if ( attackerIsHittingTeammate && level.friendlyfire == 0 ) + return; + + if ( sMeansOfDeath == "MOD_PISTOL_BULLET" || sMeansOfDeath == "MOD_RIFLE_BULLET" || sMeansOfDeath == "MOD_EXPLOSIVE_BULLET" && !attackerIsHittingTeammate ) + { + if ( isPlayer( eAttacker ) ) + { + eAttacker.lastAttackedShieldPlayer = victim; + eAttacker.lastAttackedShieldTime = getTime(); + } + victim notify ( "shield_blocked" ); + + // fix turret + shield challenge exploits + if ( sWeapon == "turret_minigun_mp" ) + shieldDamage = 25; + else + shieldDamage = maps\mp\perks\_perks::cac_modified_damage( victim, eAttacker, iDamage, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc ); + + victim.shieldDamage += shieldDamage; + + // fix turret + shield challenge exploits + if ( sWeapon != "turret_minigun_mp" || cointoss() ) + victim.shieldBulletHits++; + + if ( victim.shieldBulletHits >= level.riotShieldXPBullets ) + { + if ( self.recentShieldXP > 4 ) + xpVal = int( 50 / self.recentShieldXP ); + else + xpVal = 50; + + printLn( xpVal ); + + victim thread maps\mp\gametypes\_rank::giveRankXP( "shield_damage", xpVal ); + victim thread giveRecentShieldXP(); + + victim thread maps\mp\gametypes\_missions::genericChallenge( "shield_damage", victim.shieldDamage ); + + victim thread maps\mp\gametypes\_missions::genericChallenge( "shield_bullet_hits", victim.shieldBulletHits ); + + victim.shieldDamage = 0; + victim.shieldBulletHits = 0; + } + } + + if ( iDFlags & level.iDFLAGS_SHIELD_EXPLOSIVE_IMPACT ) + { + if ( !attackerIsHittingTeammate ) + victim thread maps\mp\gametypes\_missions::genericChallenge( "shield_explosive_hits", 1 ); + + sHitLoc = "none"; // code ignores any damage to a "shield" bodypart. + if ( !(iDFlags & level.iDFLAGS_SHIELD_EXPLOSIVE_IMPACT_HUGE) ) + iDamage *= 0.0; + } + else if ( iDFlags & level.iDFLAGS_SHIELD_EXPLOSIVE_SPLASH ) + { + if ( isDefined( eInflictor ) && isDefined( eInflictor.stuckEnemyEntity ) && eInflictor.stuckEnemyEntity == victim ) //does enough damage to shield carrier to ensure death + iDamage = 101; + + victim thread maps\mp\gametypes\_missions::genericChallenge( "shield_explosive_hits", 1 ); + sHitLoc = "none"; // code ignores any damage to a "shield" bodypart. + } + else + { + return; + } + } + else if ( (smeansofdeath == "MOD_MELEE") && IsSubStr( sweapon, "riotshield" ) ) + { + if ( !(attackerIsHittingTeammate && (level.friendlyfire == 0)) ) + { + stunFraction = 0.0; + victim StunPlayer( 0.0 ); + } + } + + if ( !attackerIsHittingTeammate ) + iDamage = maps\mp\perks\_perks::cac_modified_damage( victim, eAttacker, iDamage, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc ); + + if ( !iDamage ) + return false; + + victim.iDFlags = iDFlags; + victim.iDFlagsTime = getTime(); + + if ( game[ "state" ] == "postgame" ) + return; + if ( victim.sessionteam == "spectator" ) + return; + if ( isDefined( victim.canDoCombat ) && !victim.canDoCombat ) + return; + if ( isDefined( eAttacker ) && isPlayer( eAttacker ) && isDefined( eAttacker.canDoCombat ) && !eAttacker.canDoCombat ) + return; + + // handle vehicles/turrets and friendly fire + if ( attackerIsNPC && attackerIsHittingTeammate ) + { + if ( sMeansOfDeath == "MOD_CRUSH" ) + { + victim _suicide(); + return; + } + + if ( !level.friendlyfire ) + return; + } + + prof_begin( "PlayerDamage flags/tweaks" ); + + // Don't do knockback if the damage direction was not specified + if ( !isDefined( vDir ) ) + iDFlags |= level.iDFLAGS_NO_KNOCKBACK; + + friendly = false; + + if ( ( victim.health == victim.maxhealth && ( !isDefined( victim.lastStand ) || !victim.lastStand ) ) || !isDefined( victim.attackers ) && !isDefined( victim.lastStand ) ) + { + victim.attackers = []; + victim.attackerData = []; + } + + if ( isHeadShot( sWeapon, sHitLoc, sMeansOfDeath, eAttacker ) ) + sMeansOfDeath = "MOD_HEAD_SHOT"; + + if ( maps\mp\gametypes\_tweakables::getTweakableValue( "game", "onlyheadshots" ) ) + { + if ( sMeansOfDeath == "MOD_PISTOL_BULLET" || sMeansOfDeath == "MOD_RIFLE_BULLET" || sMeansOfDeath == "MOD_EXPLOSIVE_BULLET" ) + return; + else if ( sMeansOfDeath == "MOD_HEAD_SHOT" ) + iDamage = 150; + } + + // explosive barrel/car detection + if ( sWeapon == "none" && isDefined( eInflictor ) ) + { + if ( isDefined( eInflictor.destructible_type ) && isSubStr( eInflictor.destructible_type, "vehicle_" ) ) + sWeapon = "destructible_car"; + } + + prof_end( "PlayerDamage flags/tweaks" ); + + // check for completely getting out of the damage + if ( !(iDFlags & level.iDFLAGS_NO_PROTECTION) ) + { + // items you own don't damage you in FFA + if ( !level.teamBased && attackerIsNPC && isDefined( eAttacker.owner ) && eAttacker.owner == victim ) + { + prof_end( "PlayerDamage player" ); + + if ( sMeansOfDeath == "MOD_CRUSH" ) + victim _suicide(); + + return; + } + + if ( ( isSubStr( sMeansOfDeath, "MOD_GRENADE" ) || isSubStr( sMeansOfDeath, "MOD_EXPLOSIVE" ) || isSubStr( sMeansOfDeath, "MOD_PROJECTILE" ) ) && isDefined( eInflictor ) && isDefined( eAttacker ) ) + { + // protect players from spawnkill grenades + if ( eInflictor.classname == "grenade" && ( victim.lastSpawnTime + 3500 ) > getTime() && isDefined( victim.lastSpawnPoint ) && distance( eInflictor.origin, victim.lastSpawnPoint.origin ) < 250 ) + { + prof_end( "PlayerDamage player" ); + return; + } + + victim.explosiveInfo = []; + victim.explosiveInfo[ "damageTime" ] = getTime(); + victim.explosiveInfo[ "damageId" ] = eInflictor getEntityNumber(); + victim.explosiveInfo[ "returnToSender" ] = false; + victim.explosiveInfo[ "counterKill" ] = false; + victim.explosiveInfo[ "chainKill" ] = false; + victim.explosiveInfo[ "cookedKill" ] = false; + victim.explosiveInfo[ "throwbackKill" ] = false; + victim.explosiveInfo[ "suicideGrenadeKill" ] = false; + victim.explosiveInfo[ "weapon" ] = sWeapon; + + isFrag = isSubStr( sWeapon, "frag_" ); + + if ( eAttacker != victim ) + { + if ( ( isSubStr( sWeapon, "c4_" ) || isSubStr( sWeapon, "claymore_" ) ) && isDefined( eAttacker ) && isDefined( eInflictor.owner ) ) + { + victim.explosiveInfo[ "returnToSender" ] = ( eInflictor.owner == victim ); + victim.explosiveInfo[ "counterKill" ] = isDefined( eInflictor.wasDamaged ); + victim.explosiveInfo[ "chainKill" ] = isDefined( eInflictor.wasChained ); + victim.explosiveInfo[ "bulletPenetrationKill" ] = isDefined( eInflictor.wasDamagedFromBulletPenetration ); + victim.explosiveInfo[ "cookedKill" ] = false; + } + + if ( isDefined( eAttacker.lastGrenadeSuicideTime ) && eAttacker.lastGrenadeSuicideTime >= gettime() - 50 && isFrag ) + victim.explosiveInfo[ "suicideGrenadeKill" ] = true; + } + + if ( isFrag ) + { + victim.explosiveInfo[ "cookedKill" ] = isDefined( eInflictor.isCooked ); + victim.explosiveInfo[ "throwbackKill" ] = isDefined( eInflictor.threwBack ); + } + + victim.explosiveInfo[ "stickKill" ] = isDefined( eInflictor.isStuck ) && eInflictor.isStuck == "enemy"; + victim.explosiveInfo[ "stickFriendlyKill" ] = isDefined( eInflictor.isStuck ) && eInflictor.isStuck == "friendly"; + } + + if ( isPlayer( eAttacker ) ) + eAttacker.pers[ "participation" ]++ ; + + prevHealthRatio = victim.health / victim.maxhealth; + + if ( attackerIsHittingTeammate ) + { + if ( !matchMakingGame() && isPlayer(eAttacker) ) + eAttacker incPlayerStat( "mostff", 1 ); + + prof_begin( "PlayerDamage player" );// profs automatically end when the function returns + if ( level.friendlyfire == 0 || ( !isPlayer(eAttacker) && level.friendlyfire != 1 ) )// no one takes damage + { + if ( sWeapon == "artillery_mp" || sWeapon == "stealth_bomb_mp" ) + victim damageShellshockAndRumble( eInflictor, sWeapon, sMeansOfDeath, iDamage, iDFlags, eAttacker ); + return; + } + else if ( level.friendlyfire == 1 )// the friendly takes damage + { + if ( iDamage < 1 ) + iDamage = 1; + + victim.lastDamageWasFromEnemy = false; + + victim finishPlayerDamageWrapper( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime, stunFraction ); + } + else if ( ( level.friendlyfire == 2 ) && isReallyAlive( eAttacker ) )// only the attacker takes damage + { + iDamage = int( iDamage * .5 ); + if ( iDamage < 1 ) + iDamage = 1; + + eAttacker.lastDamageWasFromEnemy = false; + + eAttacker.friendlydamage = true; + eAttacker finishPlayerDamageWrapper( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime, stunFraction ); + eAttacker.friendlydamage = undefined; + } + else if ( level.friendlyfire == 3 && isReallyAlive( eAttacker ) )// both friendly and attacker take damage + { + iDamage = int( iDamage * .5 ); + if ( iDamage < 1 ) + iDamage = 1; + + victim.lastDamageWasFromEnemy = false; + eAttacker.lastDamageWasFromEnemy = false; + + victim finishPlayerDamageWrapper( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime, stunFraction ); + if ( isReallyAlive( eAttacker ) )// may have died due to friendly fire punishment + { + eAttacker.friendlydamage = true; + eAttacker finishPlayerDamageWrapper( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime, stunFraction ); + eAttacker.friendlydamage = undefined; + } + } + + friendly = true; + + } + else// not hitting teammate + { + prof_begin( "PlayerDamage world" ); + + if ( iDamage < 1 ) + iDamage = 1; + + if ( isDefined( eAttacker ) && isPlayer( eAttacker ) ) + addAttacker( victim, eAttacker, eInflictor, sWeapon, iDamage, vPoint, vDir, sHitLoc, psOffsetTime, sMeansOfDeath ); + + if ( sMeansOfDeath == "MOD_EXPLOSIVE" || sMeansOfDeath == "MOD_GRENADE_SPLASH" && iDamage < victim.health ) + victim notify( "survived_explosion" ); + + if ( isdefined( eAttacker ) ) + level.lastLegitimateAttacker = eAttacker; + + if ( isdefined( eAttacker ) && isPlayer( eAttacker ) && isDefined( sWeapon ) ) + eAttacker thread maps\mp\gametypes\_weapons::checkHit( sWeapon, victim ); + + if ( isdefined( eAttacker ) && isPlayer( eAttacker ) && isDefined( sWeapon ) && eAttacker != victim ) + { + eAttacker thread maps\mp\_events::damagedPlayer( self, iDamage, sWeapon ); + victim.attackerPosition = eAttacker.origin; + } + else + { + victim.attackerPosition = undefined; + } + + if ( issubstr( sMeansOfDeath, "MOD_GRENADE" ) && isDefined( eInflictor.isCooked ) ) + victim.wasCooked = getTime(); + else + victim.wasCooked = undefined; + + victim.lastDamageWasFromEnemy = ( isDefined( eAttacker ) && ( eAttacker != victim ) ); + + if ( victim.lastDamageWasFromEnemy ) + eAttacker.damagedPlayers[ victim.guid ] = getTime(); + + victim finishPlayerDamageWrapper( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime, stunFraction ); + + if ( isDefined( level.ac130player ) && isDefined( eAttacker ) && ( level.ac130player == eAttacker ) ) + level notify( "ai_pain", victim ); + + victim thread maps\mp\gametypes\_missions::playerDamaged( eInflictor, eAttacker, iDamage, sMeansOfDeath, sWeapon, sHitLoc ); + + prof_end( "PlayerDamage world" ); + + } + + if ( attackerIsNPC && isDefined( eAttacker.gunner ) ) + damager = eAttacker.gunner; + else if( level.extraDamageFeedback && isDefined( eAttacker.owner ) ) + damager = eAttacker.owner; + else + damager = eAttacker; + + if ( isDefined( damager) && damager != victim && iDamage > 0 ) + { + if ( iDFlags & level.iDFLAGS_STUN ) + typeHit = "stun"; + else if ( victim _hasPerk( "specialty_armorvest" ) || (isExplosiveDamage( sMeansOfDeath ) && victim _hasPerk( "_specialty_blastshield" )) ) + typeHit = "hitBodyArmor"; + else if ( victim _hasPerk( "specialty_combathigh") ) + typeHit = "hitEndGame"; + else + typeHit = "standard"; + + damager thread maps\mp\gametypes\_damagefeedback::updateDamageFeedback( typeHit ); + } + + victim.hasDoneCombat = true; + } + + if ( isdefined( eAttacker ) && ( eAttacker != victim ) && !friendly ) + level.useStartSpawns = false; + + + //================= + // Damage Logging + //================= + + prof_begin( "PlayerDamage log" ); + + // why getEntityNumber() for victim and .clientid for attacker? + if ( getDvarInt( "g_debugDamage" ) ) + println( "client:" + victim getEntityNumber() + " health:" + victim.health + " attacker:" + eAttacker.clientid + " inflictor is player:" + isPlayer( eInflictor ) + " damage:" + iDamage + " hitLoc:" + sHitLoc ); + + if ( victim.sessionstate != "dead" ) + { + lpselfnum = victim getEntityNumber(); + lpselfname = victim.name; + lpselfteam = victim.pers[ "team" ]; + lpselfGuid = victim.guid; + lpattackerteam = ""; + + if ( isPlayer( eAttacker ) ) + { + lpattacknum = eAttacker getEntityNumber(); + lpattackGuid = eAttacker.guid; + lpattackname = eAttacker.name; + lpattackerteam = eAttacker.pers[ "team" ]; + } + else + { + lpattacknum = -1; + lpattackGuid = ""; + lpattackname = ""; + lpattackerteam = "world"; + } + + logPrint( "D;" + lpselfGuid + ";" + lpselfnum + ";" + lpselfteam + ";" + lpselfname + ";" + lpattackGuid + ";" + lpattacknum + ";" + lpattackerteam + ";" + lpattackname + ";" + sWeapon + ";" + iDamage + ";" + sMeansOfDeath + ";" + sHitLoc + "\n" ); + } + + HitlocDebug( eAttacker, victim, iDamage, sHitLoc, iDFlags ); + + /*if( isDefined( eAttacker ) && eAttacker != victim ) + { + if ( isPlayer( eAttacker ) ) + eAttacker incPlayerStat( "damagedone", iDamage ); + + victim incPlayerStat( "damagetaken", iDamage ); + }*/ + + prof_end( "PlayerDamage log" ); +} + + +addAttacker( victim, eAttacker, eInflictor, sWeapon, iDamage, vPoint, vDir, sHitLoc, psOffsetTime, sMeansOfDeath ) +{ + if ( !isDefined( victim.attackerData ) ) + victim.attackerData = []; + + if ( !isDefined( victim.attackerData[ eAttacker.guid ] ) ) + { + victim.attackers[ eAttacker.guid ] = 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 + victim.attackerData[ eAttacker.guid ] = SpawnStruct(); + victim.attackerData[ eAttacker.guid ].damage = 0; + victim.attackerData[ eAttacker.guid ].attackerEnt = eAttacker; + victim.attackerData[ eAttacker.guid ].firstTimeDamaged = getTime(); + } + if ( maps\mp\gametypes\_weapons::isPrimaryWeapon( sWeapon ) && ! maps\mp\gametypes\_weapons::isSideArm( sWeapon ) ) + victim.attackerData[ eAttacker.guid ].isPrimary = true; + + victim.attackerData[ eAttacker.guid ].damage += iDamage; + victim.attackerData[ eAttacker.guid ].weapon = sWeapon; + victim.attackerData[ eAttacker.guid ].vPoint = vPoint; + victim.attackerData[ eAttacker.guid ].vDir = vDir; + victim.attackerData[ eAttacker.guid ].sHitLoc = sHitLoc; + victim.attackerData[ eAttacker.guid ].psOffsetTime = psOffsetTime; + victim.attackerData[ eAttacker.guid ].sMeansOfDeath = sMeansOfDeath; + victim.attackerData[ eAttacker.guid ].attackerEnt = eAttacker; + victim.attackerData[ eAttacker.guid ].lasttimeDamaged = getTime(); + + if ( isDefined( eInflictor ) && !isPlayer( eInflictor ) && isDefined( eInflictor.primaryWeapon ) ) + victim.attackerData[ eAttacker.guid ].sPrimaryWeapon = eInflictor.primaryWeapon; + else if ( isDefined( eAttacker ) && isPlayer( eAttacker ) && eAttacker getCurrentPrimaryWeapon() != "none" ) + victim.attackerData[ eAttacker.guid ].sPrimaryWeapon = eAttacker getCurrentPrimaryWeapon(); + else + victim.attackerData[ eAttacker.guid ].sPrimaryWeapon = undefined; +} + +resetAttackerList() +{ + self endon( "disconnect" ); + self endon( "death" ); + level endon( "game_ended" ); + + //wait is to offset premature calling in _healthOverlay + wait( 1.75 ); + self.attackers = []; + self.attackerData = []; +} + + +Callback_PlayerDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime ) +{ + Callback_PlayerDamage_internal( eInflictor, eAttacker, self, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime ); +} + + +finishPlayerDamageWrapper( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime, stunFraction ) +{ + if( level.allowPrintDamage ) + { + if ( isDefined( eAttacker ) && isPlayer( eAttacker ) && eAttacker.printDamage ) + eAttacker iPrintLnBold( iDamage ); + else if( isDefined( eAttacker.owner ) && isPlayer( eAttacker.owner ) && eAttacker.owner.printDamage ) + eAttacker.owner iPrintLnBold( iDamage ); + } + + if( level.extraDamageFeedback ) + { + if( sWeapon == "nuke_mp" ) + { + if ( self _hasPerk( "specialty_armorvest" ) || self _hasPerk( "_specialty_blastshield") ) + typeHit = "hitBodyArmor"; + else if ( self _hasPerk( "specialty_combathigh") ) + typeHit = "hitEndGame"; + else + typeHit = "standard"; + + eAttacker thread maps\mp\gametypes\_damagefeedback::updateDamageFeedback( typeHit ); + } + + if( sHitLoc == "head" || sHitLoc == "helmet" ) + { + if ( isDefined( eAttacker ) && isPlayer( eAttacker ) ) + eAttacker playLocalSound( "bullet_impact_headshot_2" ); + else if( isDefined( eAttacker.owner ) && isPlayer( eAttacker.owner ) ) + eAttacker.owner playLocalSound( "bullet_impact_headshot_2" ); + } + } + + if ( (self isUsingRemote() ) && (iDamage >= self.health) && !(iDFlags & level.iDFLAGS_STUN) ) + { + if ( !isDefined( vDir ) ) + vDir = ( 0,0,0 ); + + if ( !isDefined( eAttacker ) && !isDefined( eInflictor ) ) + { + eAttacker = self; + eInflictor = eAttacker; + } + + assert( isDefined( eAttacker ) ); + assert( isDefined( eInflictor ) ); + + PlayerKilled_internal( eInflictor, eAttacker, self, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, psOffsetTime, 0, true ); + } + else + { + if ( !self Callback_KillingBlow( eInflictor, eAttacker, iDamage - (iDamage * stunFraction), iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime ) ) + return; + + self finishPlayerDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime, stunFraction ); + } + + if ( sMeansOfDeath == "MOD_EXPLOSIVE_BULLET" ) + self shellShock( "damage_mp", getDvarFloat( "scr_csmode" ) ); + + self damageShellshockAndRumble( eInflictor, sWeapon, sMeansOfDeath, iDamage, iDFlags, eAttacker ); +} + + +Callback_PlayerLastStand( eInflictor, attacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration ) +{ + + lastStandParams = spawnStruct(); + lastStandParams.eInflictor = eInflictor; + lastStandParams.attacker = attacker; + lastStandParams.iDamage = iDamage; + lastStandParams.attackerPosition = attacker.origin; + if ( attacker == self ) + lastStandParams.sMeansOfDeath = "MOD_SUICIDE"; + else + lastStandParams.sMeansOfDeath = sMeansOfDeath; + + lastStandParams.sWeapon = sWeapon; + if ( isDefined( attacker ) && isPlayer( attacker ) && attacker getCurrentPrimaryWeapon() != "none" ) + lastStandParams.sPrimaryWeapon = attacker getCurrentPrimaryWeapon(); + else + lastStandParams.sPrimaryWeapon = undefined; + lastStandParams.vDir = vDir; + lastStandParams.sHitLoc = sHitLoc; + lastStandParams.lastStandStartTime = getTime(); + + mayDoLastStand = mayDoLastStand( sWeapon, sMeansOfDeath, sHitLoc ); + + //if ( mayDoLastStand ) + // mayDoLastStand = !self checkForceBleedOut(); + + if ( isDefined( self.endGame ) ) + mayDoLastStand = false; + + if ( level.teamBased && isDefined( attacker.team ) && attacker.team == self.team ) + mayDoLastStand = false; + + /# + if ( getdvar( "scr_forcelaststand" ) == "1" ) + mayDoLastStand = true; + #/ + + if ( !mayDoLastStand ) + { + self.lastStandParams = lastStandParams; + self.useLastStandParams = true; + self _suicide(); + return; + } + + self.inLastStand = true; + + notifyData = spawnStruct(); + if ( self _hasPerk( "specialty_finalstand" ) ) + { + notifyData.titleText = game[ "strings" ][ "final_stand" ]; + notifyData.iconName = "specialty_finalstand"; + } + else + { + notifyData.titleText = game[ "strings" ][ "last_stand" ]; + notifyData.iconName = "specialty_pistoldeath"; + } + notifyData.glowColor = ( 1, 0, 0 ); + notifyData.sound = "mp_last_stand"; + notifyData.duration = 2.0; + + self.health = 1; + + self thread maps\mp\gametypes\_hud_message::notifyMessage( notifyData ); + + grenadeTypePrimary = "frag_grenade_mp"; + + if ( isDefined( level.ac130player ) && isDefined( attacker ) && level.ac130player == attacker ) + level notify( "ai_crawling", self ); + + if ( self _hasPerk( "specialty_finalstand" ) ) + { + self.lastStandParams = lastStandParams; + self.inFinalStand = true; + + weaponList = self GetWeaponsListExclusives(); + foreach ( weapon in weaponList ) + self takeWeapon( weapon ); + + self _disableUsability(); + + self thread enableLastStandWeapons(); + self thread lastStandTimer( 20, true ); + } + /* + else if ( self _hasPerk( "specialty_c4death" ) ) + { + self.lastStandParams = lastStandParams; + + self takeAllWeapons(); + self giveWeapon( "c4Death_mp", 0, false ); + self switchToWeapon( "c4Death_mp" ); + self _disableUsability(); + self.inC4Death = true; + + //self thread dieAfterTime( 7 ); + self thread lastStandTimer( 10, false ); + self thread detonateOnUse(); + //self thread detonateOnDeath(); + } + */ + else if ( level.dieHardMode ) + { + self.lastStandParams = lastStandParams; + self thread enableLastStandWeapons(); + self thread lastStandTimer( 20, false ); + self _disableUsability(); + } + else // normal last stand + { + self.lastStandParams = lastStandParams; + + pistolWeapon = undefined; + + weaponsList = self GetWeaponsListPrimaries(); + foreach ( weapon in weaponsList ) + { + if ( maps\mp\gametypes\_weapons::isSideArm( weapon ) ) + pistolWeapon = weapon; + } + + if ( !isDefined( pistolWeapon ) ) + { + pistolWeapon = "beretta_mp"; + self _giveWeapon( pistolWeapon ); + } + + self giveMaxAmmo( pistolWeapon ); + self DisableWeaponSwitch(); + self _disableUsability(); + + if ( !self _hasPerk("specialty_laststandoffhand") ) + self DisableOffhandWeapons(); + + self switchToWeapon( pistolWeapon ); + + self thread lastStandTimer( 10, false ); + } +} + +dieAfterTime( time ) +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "joined_team" ); + level endon( "game_ended" ); + + wait ( time ); + self.useLastStandParams = true; + self _suicide(); +} + +detonateOnUse() +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "joined_team" ); + level endon( "game_ended" ); + + self waittill( "detonate" ); + self.useLastStandParams = true; + self c4DeathDetonate(); +} + +detonateOnDeath() +{ + self endon( "detonate" ); + self endon( "disconnect" ); + self endon( "joined_team" ); + level endon( "game_ended" ); + + self waittill( "death" ); + self c4DeathDetonate(); +} + +c4DeathDetonate() +{ + self playSound( "detpack_explo_default" ); + self.c4DeathEffect = playFX( level.c4Death, self.origin ); + RadiusDamage( self.origin, 400, 100, 100, self ); + + if ( isAlive( self ) ) + self _suicide(); +} + +enableLastStandWeapons() +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon( "game_ended" ); + + self freezeControlsWrapper( true ); + wait .30; + + self freezeControlsWrapper( false ); +} + +lastStandTimer( delay, isFinalStand ) +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "revive"); + level endon( "game_ended" ); + + level notify ( "player_last_stand" ); + + self thread lastStandWaittillDeath(); + + self.lastStand = true; + + if ( !isFinalStand && !level.dieHardMode && ( !isDefined( self.inC4Death ) || !self.inC4Death ) ) + { + self thread lastStandAllowSuicide(); + self setLowerMessage( "last_stand", &"PLATFORM_COWARDS_WAY_OUT" ); + self thread lastStandKeepOverlay(); + } + + if ( level.dieHardMode == 1 && level.dieHardMode != 2 ) + { + reviveEnt = spawn( "script_model", self.origin ); + reviveEnt setModel( "tag_origin" ); + reviveEnt setCursorHint( "HINT_NOICON" ); + reviveEnt setHintString( &"PLATFORM_REVIVE" ); + + reviveEnt reviveSetup( self ); + reviveEnt endon ( "death" ); + + reviveIcon = newTeamHudElem( self.team ); + reviveIcon setShader( "waypoint_revive", 8, 8 ); + reviveIcon setWaypoint( true, true ); + reviveIcon SetTargetEnt( self ); + reviveIcon thread destroyOnReviveEntDeath( reviveEnt ); + + reviveIcon.color = (0.33, 0.75, 0.24); + self playDeathSound(); + + if ( isFinalStand ) + { + wait( delay ); + + if ( self.inFinalStand ) + self thread lastStandBleedOut( isFinalStand, reviveEnt ); + } + + return; + } + else if( level.dieHardMode == 2 ) + { + self thread lastStandKeepOverlay(); + reviveEnt = spawn( "script_model", self.origin ); + reviveEnt setModel( "tag_origin" ); + reviveEnt setCursorHint( "HINT_NOICON" ); + reviveEnt setHintString( &"PLATFORM_REVIVE" ); + + reviveEnt reviveSetup( self ); + reviveEnt endon ( "death" ); + + reviveIcon = newTeamHudElem( self.team ); + reviveIcon setShader( "waypoint_revive", 8, 8 ); + reviveIcon setWaypoint( true, true ); + reviveIcon SetTargetEnt( self ); + reviveIcon thread destroyOnReviveEntDeath( reviveEnt ); + + reviveIcon.color = (0.33, 0.75, 0.24); + self playDeathSound(); + + if ( isFinalStand ) + { + wait( delay ); + + if ( self.inFinalStand ) + self thread lastStandBleedOut( isFinalStand, reviveEnt ); + } + + wait delay / 3; + reviveIcon.color = (1.0, 0.64, 0.0); + + while ( reviveEnt.inUse ) + wait ( 0.05 ); + + self playDeathSound(); + wait delay / 3; + reviveIcon.color = (1.0, 0.0, 0.0); + + while ( reviveEnt.inUse ) + wait ( 0.05 ); + + self playDeathSound(); + wait delay / 3; + + while ( reviveEnt.inUse ) + wait ( 0.05 ); + + wait( 0.05 ); + self thread lastStandBleedOut( isFinalStand ); + return; + } + + wait( delay ); + self thread lastStandBleedout( isFinalStand ); + +} + +maxHealthOverlay( maxHealth, refresh ) +{ + self endon( "stop_maxHealthOverlay" ); + self endon( "revive" ); + self endon( "death" ); + + for( ;; ) + { + self.health -= 1; + self.maxHealth = maxHealth; + wait( .05 ); + self.maxHealth = 50; + self.health += 1; + + wait ( .50 ); + } +} + +lastStandBleedOut( reviveOnBleedOut, reviveEnt ) +{ + if ( reviveOnBleedOut ) + { + self.lastStand = undefined; + self.inFinalStand = false; + self clearLowerMessage( "last_stand" ); + maps\mp\gametypes\_playerlogic::lastStandRespawnPlayer(); + + if( isDefined( reviveEnt ) ) + reviveEnt Delete(); + } + else + { + self.useLastStandParams = true; + self.beingRevived = false; + self _suicide(); + } +} + + +lastStandAllowSuicide() +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "game_ended" ); + self endon( "revive"); + + while ( 1 ) + { + if ( self useButtonPressed() ) + { + pressStartTime = gettime(); + while ( self useButtonPressed() ) + { + wait .05; + if ( gettime() - pressStartTime > 700 ) + break; + } + if ( gettime() - pressStartTime > 700 ) + break; + } + wait .05; + } + + self thread lastStandBleedOut( false ); +} + +lastStandKeepOverlay() +{ + level endon( "game_ended" ); + self endon( "death" ); + self endon( "disconnect" ); + self endon( "revive" ); + + // keep the health overlay going by making code think the player is getting damaged + while ( !level.gameEnded ) + { + self.health = 2; + wait .05; + self.health = 1; + wait .5; + } + + self.health = self.maxhealth; +} + + +lastStandWaittillDeath() +{ + self endon( "disconnect" ); + self endon( "revive" ); + level endon( "game_ended" ); + self waittill( "death" ); + + self clearLowerMessage( "last_stand" ); + self.lastStand = undefined; +} + + +mayDoLastStand( sWeapon, sMeansOfDeath, sHitLoc ) +{ + if ( sMeansOfDeath == "MOD_TRIGGER_HURT" ) + return false; + + if ( sMeansOfDeath != "MOD_PISTOL_BULLET" && sMeansOfDeath != "MOD_RIFLE_BULLET" && sMeansOfDeath != "MOD_FALLING" && sMeansOfDeath != "MOD_EXPLOSIVE_BULLET" ) + return false; + + if ( sMeansOfDeath == "MOD_IMPACT" && sWeapon == "throwingknife_mp" ) + return false; + + if ( sMeansOfDeath == "MOD_IMPACT" && ( sWeapon == "m79_mp" || isSubStr(sWeapon, "gl_") ) ) + return false; + + if ( isHeadShot( sWeapon, sHitLoc, sMeansOfDeath ) ) + return false; + + if ( self isUsingRemote() ) + return false; + + return true; +} + + +ensureLastStandParamsValidity() +{ + // attacker may have become undefined if the player that killed me has disconnected + if ( !isDefined( self.lastStandParams.attacker ) ) + self.lastStandParams.attacker = self; +} + +getHitLocHeight( sHitLoc ) +{ + switch( sHitLoc ) + { + case "helmet": + case "head": + case "neck": + return 60; + case "torso_upper": + case "right_arm_upper": + case "left_arm_upper": + case "right_arm_lower": + case "left_arm_lower": + case "right_hand": + case "left_hand": + case "gun": + return 48; + case "torso_lower": + return 40; + case "right_leg_upper": + case "left_leg_upper": + return 32; + case "right_leg_lower": + case "left_leg_lower": + return 10; + case "right_foot": + case "left_foot": + return 5; + } + return 48; +} + +delayStartRagdoll( ent, sHitLoc, vDir, sWeapon, eInflictor, sMeansOfDeath ) +{ + if ( isDefined( ent ) ) + { + deathAnim = ent getCorpseAnim(); + if ( animhasnotetrack( deathAnim, "ignore_ragdoll" ) ) + return; + } + + wait( 0.2 ); + + 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 ); + wait( waitTime ); + + if ( isDefined( ent ) ) + { + ent startragdoll( 1 ); + } +} + + +getMostKilledBy() +{ + mostKilledBy = ""; + killCount = 0; + + killedByNames = getArrayKeys( self.killedBy ); + + for ( index = 0; index < killedByNames.size; index++ ) + { + killedByName = killedByNames[ index ]; + if ( self.killedBy[ killedByName ] <= killCount ) + continue; + + killCount = self.killedBy[ killedByName ]; + mostKilleBy = killedByName; + } + + return mostKilledBy; +} + + +getMostKilled() +{ + mostKilled = ""; + killCount = 0; + + killedNames = getArrayKeys( self.killedPlayers ); + + for ( index = 0; index < killedNames.size; index++ ) + { + killedName = killedNames[ index ]; + if ( self.killedPlayers[ killedName ] <= killCount ) + continue; + + killCount = self.killedPlayers[ killedName ]; + mostKilled = killedName; + } + + return mostKilled; +} + + +damageShellshockAndRumble( eInflictor, sWeapon, sMeansOfDeath, iDamage, iDFlags, eAttacker ) +{ + self thread maps\mp\gametypes\_weapons::onWeaponDamage( eInflictor, sWeapon, sMeansOfDeath, iDamage, eAttacker ); + self PlayRumbleOnEntity( "damage_heavy" ); +} + + +reviveSetup( owner ) +{ + team = owner.team; + + self linkTo( owner, "tag_origin" ); + + self.owner = owner; + self.inUse = false; + self makeUsable(); + self updateUsableByTeam( team ); + self thread trackTeamChanges( team ); + + self thread reviveTriggerThink( team ); + + self thread deleteOnReviveOrDeathOrDisconnect(); +} + + +deleteOnReviveOrDeathOrDisconnect() +{ + self endon ( "death" ); + + self.owner waittill_any ( "death", "disconnect" ); + + self delete(); +} + + +updateUsableByTeam( team ) +{ + foreach (player in level.players) + { + if ( team == player.team && player != self.owner ) + self enablePlayerUse( player ); + else + self disablePlayerUse( player ); + } +} + + +trackTeamChanges( team ) +{ + self endon ( "death" ); + + while ( true ) + { + level waittill ( "joined_team" ); + + self updateUsableByTeam( team ); + } +} + + +trackLastStandChanges( team ) +{ + self endon ( "death" ); + + while ( true ) + { + level waittill ( "player_last_stand" ); + + self updateUsableByTeam( team ); + } +} + + +reviveTriggerThink( team ) +{ + self endon ( "death" ); + level endon ( "game_ended" ); + + for ( ;; ) + { + self waittill ( "trigger", player ); + self.owner.beingRevived = true; + + if ( isDefined(player.beingRevived) && player.beingRevived ) + { + self.owner.beingRevived = false; + continue; + } + + self makeUnUsable(); + self.owner freezeControlsWrapper( true ); + + revived = self useHoldThink( player ); + self.owner.beingRevived = false; + + if ( !isAlive( self.owner ) ) + { + self delete(); + return; + } + + self.owner freezeControlsWrapper( false ); + + if ( revived ) + { + player thread maps\mp\gametypes\_hud_message::SplashNotifyDelayed( "reviver", 200 ); + player thread maps\mp\gametypes\_rank::giveRankXP( "reviver", 200 ); + + self.owner.lastStand = undefined; + self.owner clearLowerMessage( "last_stand" ); + + if ( self.owner _hasPerk( "specialty_lightweight" ) ) + self.owner.moveSpeedScaler = 1.07; + else + self.owner.moveSpeedScaler = 1; + + self.owner.maxHealth = 100; + + self.owner maps\mp\gametypes\_weapons::updateMoveSpeedScale( "primary" ); + self.owner maps\mp\gametypes\_playerlogic::lastStandRespawnPlayer(); + + self.owner setPerk( "specialty_pistoldeath", true ); + self.owner.beingRevived = false; + + self delete(); + return; + } + + self makeUsable(); + self updateUsableByTeam( team ); + } +} + + + +/* +============= +useHoldThink + +Claims the use trigger for player and displays a use bar +Returns true if the player sucessfully fills the use bar +============= +*/ +useHoldThink( player ) +{ + reviveSpot = spawn( "script_origin", self.origin ); + reviveSpot hide(); + player playerLinkTo( reviveSpot ); + player PlayerLinkedOffsetEnable(); + + player _disableWeapon(); + + self.curProgress = 0; + self.inUse = true; + self.useRate = 0; + self.useTime = 3000; + + player thread personalUseBar( self ); + + result = useHoldThinkLoop( player ); + + if ( isDefined( player ) && isReallyAlive( player ) ) + { + player Unlink(); + player _enableWeapon(); + } + + if ( isDefined( result ) && result ) + { + self.owner thread maps\mp\gametypes\_hud_message::playerCardSplashNotify( "revived", player ); + self.owner.inlaststand = false; + return true; + } + + self.inUse = false; + reviveSpot Delete(); + return false; +} + + +personalUseBar( object ) +{ + useBar = self createPrimaryProgressBar(); + useBarText = self createPrimaryProgressBarText(); + useBarText setText( &"MPUI_REVIVING" ); + + objUseBar = object.owner createPrimaryProgressBar(); + objUseBarText = object.owner createPrimaryProgressBarText(); + objUseBarText setText( &"MPUI_BEING_REVIVED" ); + + lastRate = -1; + while ( isReallyAlive( self ) && isDefined( object ) && object.inUse && !level.gameEnded && isDefined( self ) ) + { + if ( lastRate != object.useRate ) + { + if( object.curProgress > object.useTime) + object.curProgress = object.useTime; + + useBar updateBar( object.curProgress / object.useTime, (1000 / object.useTime) * object.useRate ); + objUseBar updateBar( object.curProgress / object.useTime, (1000 / object.useTime) * object.useRate ); + + if ( !object.useRate ) + { + useBar hideElem(); + useBarText hideElem(); + + objUseBar hideElem(); + objUseBarText hideElem(); + } + else + { + useBar showElem(); + useBarText showElem(); + + objUseBar showElem(); + objUseBarText showElem(); + } + } + lastRate = object.useRate; + wait ( 0.05 ); + } + + // when the players disconnect the hudElems are destroyed automatically + if ( isDefined( useBar ) ) + useBar destroyElem(); + if ( isDefined( useBarText ) ) + useBarText destroyElem(); + + if ( isDefined( objUseBar ) ) + objUseBar destroyElem(); + if ( isDefined( objUseBarText ) ) + objUseBarText destroyElem(); +} + + +useHoldThinkLoop( player ) +{ + level endon ( "game_ended" ); + self.owner endon( "death" ); + self.owner endon( "disconnect" ); + + while( isReallyAlive( player ) && player useButtonPressed() && self.curProgress < self.useTime ) + { + self.curProgress += (50 * self.useRate); + self.useRate = 1; /* * player.objectiveScaler;*/ + + if ( self.curProgress >= self.useTime ) + { + self.inUse = false; + + return isReallyAlive( player ); + } + + wait 0.05; + } + + return false; +} + + +Callback_KillingBlow( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime ) +{ + if ( isDefined(self.lastDamageWasFromEnemy) && self.lastDamageWasFromEnemy && iDamage >= self.health && isDefined( self.combatHigh ) && self.combatHigh == "specialty_endgame" ) + { + self setAdrenaline( 0 ); + self _setPerk( "specialty_endgame" ); + return false; + } + + return true; +} + + +emitFallDamage( iDamage ) +{ + PhysicsExplosionSphere( self.origin, 64, 64, 1 ); + + // get the entities we landed on + damageEnts = []; + for ( testAngle = 0; testAngle < 360; testAngle += 30 ) + { + xOffset = cos( testAngle ) * 16; + yOffset = sin( testAngle ) * 16; + + traceData = bulletTrace( self.origin + (xOffset, yOffset, 4), self.origin + (xOffset,yOffset,-6), true, self ); + //thread drawLine( self.origin + (xOffset, yOffset, 4), self.origin + (xOffset,yOffset,-6), 10.0 ); + + if ( isDefined( traceData["entity"] ) && isDefined( traceData["entity"].targetname ) && (traceData["entity"].targetname == "destructible_vehicle" || traceData["entity"].targetname == "destructible_toy") ) + damageEnts[damageEnts.size] = traceData["entity"]; + } + + if ( damageEnts.size ) + { + damageOwner = spawn( "script_origin", self.origin ); + damageOwner hide(); + damageOwner.type = "soft_landing"; + damageOwner.destructibles = damageEnts; + radiusDamage( self.origin, 64, 100, 100, damageOwner ); + + wait ( 0.1 ); + damageOwner delete(); + } +} + +drawLine( start, end, timeSlice ) +{ + drawTime = int(timeSlice * 20); + for( time = 0; time < drawTime; time++ ) + { + line( start, end, (1,0,0),false, 1 ); + wait ( 0.05 ); + } +} + +isFlankKill( victim, attacker ) +{ + victimForward = anglestoforward( victim.angles ); + victimForward = ( victimForward[0], victimForward[1], 0 ); + victimForward = VectorNormalize( victimForward ); + + attackDirection = victim.origin - attacker.origin; + attackDirection = ( attackDirection[0], attackDirection[1], 0 ); + attackDirection = VectorNormalize( attackDirection ); + + dotProduct = VectorDot( victimForward, attackDirection ); + if ( dotProduct > 0 ) // 0 = cos( 90 ), 180 degree arc total + return true; + else + return false; +} + +_obituary( victim, attacker, sWeapon, sMeansOfDeath ) +{ + victimTeam = victim.team; + + foreach ( player in level.players ) + { + playerTeam = player.team; + if ( playerTeam == "spectator" ) + player iPrintLn( &"MP_OBITUARY_NEUTRAL", attacker.name, victim.name ); + else if ( playerTeam == victimTeam ) + player iPrintLn( &"MP_OBITUARY_ENEMY", attacker.name, victim.name ); + else + player iPrintLn( &"MP_OBITUARY_FRIENDLY", attacker.name, victim.name ); + } +} + + +logPrintPlayerDeath( lifeId, attacker, iDamage, sMeansOfDeath, sWeapon, sPrimaryWeapon, sHitLoc ) +{ + // create a lot of redundant data for the log print + lpselfnum = self getEntityNumber(); + lpselfname = self.name; + lpselfteam = self.team; + lpselfguid = self.guid; + + if ( isPlayer( attacker ) ) + { + lpattackGuid = attacker.guid; + lpattackname = attacker.name; + lpattackerteam = attacker.team; + lpattacknum = attacker getEntityNumber(); + attackerString = attacker getXuid() + "(" + lpattackname + ")"; + } + else + { + lpattackGuid = ""; + lpattackname = ""; + lpattackerteam = "world"; + lpattacknum = -1; + attackerString = "none"; + } + + logPrint( "K;" + lpselfguid + ";" + lpselfnum + ";" + lpselfteam + ";" + lpselfname + ";" + lpattackguid + ";" + lpattacknum + ";" + lpattackerteam + ";" + lpattackname + ";" + sWeapon + ";" + iDamage + ";" + sMeansOfDeath + ";" + sHitLoc + "\n" ); +} + + +destroyOnReviveEntDeath( reviveEnt ) +{ + reviveEnt waittill ( "death" ); + + self destroy(); +} diff --git a/userraw/maps/mp/gametypes/_gamelogic.gsc b/userraw/maps/mp/gametypes/_gamelogic.gsc new file mode 100644 index 0000000..77ee8c2 --- /dev/null +++ b/userraw/maps/mp/gametypes/_gamelogic.gsc @@ -0,0 +1,3083 @@ +#include maps\mp\_utility; +#include maps\mp\gametypes\_hud_util; +#include common_scripts\utility; + +FACTION_REF_COL = 0; +FACTION_NAME_COL = 1; +FACTION_SHORT_NAME_COL = 1; +FACTION_WIN_GAME_COL = 3; +FACTION_WIN_ROUND_COL = 4; +FACTION_MISSION_ACCOMPLISHED_COL = 5; +FACTION_ELIMINATED_COL = 6; +FACTION_FORFEITED_COL = 7; +FACTION_ICON_COL = 8; +FACTION_HUD_ICON_COL = 9; +FACTION_VOICE_PREFIX_COL = 10; +FACTION_SPAWN_MUSIC_COL = 11; +FACTION_WIN_MUSIC_COL = 12; +FACTION_COLOR_R_COL = 13; +FACTION_COLOR_G_COL = 14; +FACTION_COLOR_B_COL = 15; + +// when a team leaves completely, that team forfeited, team left wins round, ends game +onForfeit( team ) +{ + if ( isDefined( level.forfeitInProgress ) ) + return; + + level endon( "abort_forfeit" ); //end if the team is no longer in forfeit status + + level.forfeitInProgress = true; + + // in 1v1 DM, give players time to change teams + if ( !level.teambased && level.players.size > 1 ) + wait 10; + + forfeit_delay = 20.0; //forfeit wait, for switching teams and such + + foreach ( player in level.players ) + { + player setLowerMessage( "forfeit_warning", game["strings"]["opponent_forfeiting_in"], forfeit_delay, 100 ); + player thread forfeitWaitforAbort(); + } + + wait ( forfeit_delay ); + + endReason = &""; + if ( !isDefined( team ) ) + { + endReason = game["strings"]["players_forfeited"]; + winner = level.players[0]; + } + else if ( team == "allies" ) + { + endReason = game["strings"]["allies_forfeited"]; + winner = "axis"; + } + else if ( team == "axis" ) + { + endReason = game["strings"]["axis_forfeited"]; + winner = "allies"; + } + else + { + //shouldn't get here + assertEx( isdefined( team ), "Forfeited team is not defined" ); + assertEx( 0, "Forfeited team " + team + " is not allies or axis" ); + winner = "tie"; + } + //exit game, last round, no matter if round limit reached or not + level.forcedEnd = true; + + if ( isPlayer( winner ) ) + logString( "forfeit, win: " + winner getXuid() + "(" + winner.name + ")" ); + else + logString( "forfeit, win: " + winner + ", allies: " + game["teamScores"]["allies"] + ", opfor: " + game["teamScores"]["axis"] ); + thread endGame( winner, endReason ); +} + + +forfeitWaitforAbort() +{ + self endon ( "disconnect" ); + level endon ( "game_ended" ); + + level waittill ( "abort_forfeit" ); + + self clearLowerMessage( "forfeit_warning" ); +} + + +default_onDeadEvent( team ) +{ + if ( team == "allies" ) + { + iPrintLn( game["strings"]["allies_eliminated"] ); + + logString( "team eliminated, win: opfor, allies: " + game["teamScores"]["allies"] + ", opfor: " + game["teamScores"]["axis"] ); + + thread endGame( "axis", game["strings"]["allies_eliminated"] ); + } + else if ( team == "axis" ) + { + iPrintLn( game["strings"]["axis_eliminated"] ); + + logString( "team eliminated, win: allies, allies: " + game["teamScores"]["allies"] + ", opfor: " + game["teamScores"]["axis"] ); + + thread endGame( "allies", game["strings"]["axis_eliminated"] ); + } + else + { + logString( "tie, allies: " + game["teamScores"]["allies"] + ", opfor: " + game["teamScores"]["axis"] ); + + if ( level.teamBased ) + thread endGame( "tie", game["strings"]["tie"] ); + else + thread endGame( undefined, game["strings"]["tie"] ); + } +} + + +default_onOneLeftEvent( team ) +{ + if ( level.teamBased ) + { + assert( team == "allies" || team == "axis" ); + + lastPlayer = getLastLivingPlayer( team ); + lastPlayer thread giveLastOnTeamWarning(); + } + else + { + lastPlayer = getLastLivingPlayer(); + + logString( "last one alive, win: " + lastPlayer.name ); + thread endGame( lastPlayer, &"MP_ENEMIES_ELIMINATED" ); + } + + return true; +} + + +default_onTimeLimit() +{ + winner = undefined; + + if ( level.teamBased ) + { + if ( game["teamScores"]["allies"] == game["teamScores"]["axis"] ) + winner = "tie"; + else if ( game["teamScores"]["axis"] > game["teamScores"]["allies"] ) + winner = "axis"; + else + winner = "allies"; + + logString( "time limit, win: " + winner + ", allies: " + game["teamScores"]["allies"] + ", opfor: " + game["teamScores"]["axis"] ); + } + else + { + winner = maps\mp\gametypes\_gamescore::getHighestScoringPlayer(); + + if ( isDefined( winner ) ) + logString( "time limit, win: " + winner.name ); + else + logString( "time limit, tie" ); + } + + thread endGame( winner, game["strings"]["time_limit_reached"] ); +} + + +default_onHalfTime() +{ + winner = undefined; + + thread endGame( "halftime", game["strings"]["time_limit_reached"] ); +} + + +forceEnd() +{ + if ( level.hostForcedEnd || level.forcedEnd ) + return; + + winner = undefined; + + if ( level.teamBased ) + { + if ( game["teamScores"]["allies"] == game["teamScores"]["axis"] ) + winner = "tie"; + else if ( game["teamScores"]["axis"] > game["teamScores"]["allies"] ) + winner = "axis"; + else + winner = "allies"; + logString( "host ended game, win: " + winner + ", allies: " + game["teamScores"]["allies"] + ", opfor: " + game["teamScores"]["axis"] ); + } + else + { + winner = maps\mp\gametypes\_gamescore::getHighestScoringPlayer(); + if ( isDefined( winner ) ) + logString( "host ended game, win: " + winner.name ); + else + logString( "host ended game, tie" ); + } + + level.forcedEnd = true; + level.hostForcedEnd = true; + + if ( level.splitscreen ) + endString = &"MP_ENDED_GAME"; + else + endString = &"MP_HOST_ENDED_GAME"; + + thread endGame( winner, endString ); +} + + +onScoreLimit() +{ + scoreText = game["strings"]["score_limit_reached"]; + winner = undefined; + + if ( level.teamBased ) + { + if ( game["teamScores"]["allies"] == game["teamScores"]["axis"] ) + winner = "tie"; + else if ( game["teamScores"]["axis"] > game["teamScores"]["allies"] ) + winner = "axis"; + else + winner = "allies"; + logString( "scorelimit, win: " + winner + ", allies: " + game["teamScores"]["allies"] + ", opfor: " + game["teamScores"]["axis"] ); + } + else + { + winner = maps\mp\gametypes\_gamescore::getHighestScoringPlayer(); + if ( isDefined( winner ) ) + logString( "scorelimit, win: " + winner.name ); + else + logString( "scorelimit, tie" ); + } + + thread endGame( winner, scoreText ); + return true; +} + + +updateGameEvents() +{ + if ( matchMakingGame() && !level.inGracePeriod ) + { + if ( level.teamBased ) + { + // if allies disconnected, and axis still connected, axis wins round and game ends to lobby + if ( level.teamCount["allies"] < 1 && level.teamCount["axis"] > 0 && game["state"] == "playing" ) + { + //allies forfeited + thread onForfeit( "allies" ); + return; + } + + // if axis disconnected, and allies still connected, allies wins round and game ends to lobby + if ( level.teamCount["axis"] < 1 && level.teamCount["allies"] > 0 && game["state"] == "playing" ) + { + //axis forfeited + thread onForfeit( "axis" ); + return; + } + + if ( level.teamCount["axis"] > 0 && level.teamCount["allies"] > 0 ) + { + level.forfeitInProgress = undefined; + level notify( "abort_forfeit" ); + } + } + else + { + if ( level.teamCount["allies"] + level.teamCount["axis"] == 1 && level.maxPlayerCount > 1 ) + { + thread onForfeit(); + return; + } + + if ( level.teamCount["axis"] + level.teamCount["allies"] > 1 ) + { + level.forfeitInProgress = undefined; + level notify( "abort_forfeit" ); + } + } + } + + if ( !getGametypeNumLives() && (!isDefined( level.disableSpawning ) || !level.disableSpawning) ) + return; + + if ( !gameHasStarted() ) + return; + + if ( level.inGracePeriod ) + return; + + if ( level.teamBased ) + { + livesCount["allies"] = level.livesCount["allies"]; + livesCount["axis"] = level.livesCount["axis"]; + + if ( isDefined( level.disableSpawning ) && level.disableSpawning ) + { + livesCount["allies"] = 0; + livesCount["axis"] = 0; + } + + // if both allies and axis were alive and now they are both dead in the same instance + if ( !level.aliveCount["allies"] && !level.aliveCount["axis"] && !livesCount["allies"] && !livesCount["axis"] ) + { + return [[level.onDeadEvent]]( "all" ); + } + + // if allies were alive and now they are not + if ( !level.aliveCount["allies"] && !livesCount["allies"] ) + { + return [[level.onDeadEvent]]( "allies" ); + } + + // if axis were alive and now they are not + if ( !level.aliveCount["axis"] && !livesCount["axis"] ) + { + return [[level.onDeadEvent]]( "axis" ); + } + + // one ally left + if ( level.aliveCount["allies"] == 1 && !livesCount["allies"] ) + { + if ( !isDefined( level.oneLeftTime["allies"] ) ) + { + level.oneLeftTime["allies"] = getTime(); + return [[level.onOneLeftEvent]]( "allies" ); + } + } + + // one axis left + if ( level.aliveCount["axis"] == 1 && !livesCount["axis"] ) + { + if ( !isDefined( level.oneLeftTime["axis"] ) ) + { + level.oneLeftTime["axis"] = getTime(); + return [[level.onOneLeftEvent]]( "axis" ); + } + } + } + else + { + // everyone is dead + if ( (!level.aliveCount["allies"] && !level.aliveCount["axis"]) && (!level.livesCount["allies"] && !level.livesCount["axis"]) ) + { + return [[level.onDeadEvent]]( "all" ); + } + + livePlayers = getPotentialLivingPlayers(); + + if ( livePlayers.size == 1 ) + { + return [[level.onOneLeftEvent]]( "all" ); + } + } +} + + +waittillFinalKillcamDone() +{ + if ( level.forceFinalKillcam ) + { + waittillframeend; // give "round_end_finished" notifies time to process + + if( level.showingFinalKillcam ) + { + foreach ( player in level.players ) + player notify( "reset_outcome" ); + + level notify( "game_cleanup" ); + + while ( level.showingFinalKillcam ) + wait ( 0.05 ); + } + } + else + { + if ( !level.showingFinalKillcam ) + return false; + + while ( level.showingFinalKillcam ) + wait ( 0.05 ); + + return true; + } +} + +timeLimitClock_Intermission( waitTime, clockColor ) +{ + if( !isDefined( clockColor ) ) + clockColor = (0, 0, 0); + + NewWaitTime = waitTime*1000; + + setGameEndTime( getTime() + int(NewWaitTime) ); + clockObject = spawn( "script_origin", (0,0,0) ); + clockObject hide(); + + matchStartTimer = createServerTimer("objective", 1.4); + matchStartTimer setPoint( "TOPRIGHT", "TOPRIGHT", -5, 0 ); //("CENTER", "CENTER", 0, -45); + matchStartTimer setTimer( waitTime - 1 ); + matchStartTimer.sort = 1001; + matchStartTimer.foreground = false; + matchStartTimer.hideWhenInMenu = false; + matchStartTimer.color = clockColor; + + currentTime = getTime(); + timeRunning = ( getTime() - currentTime ); + while ( timeRunning <= NewWaitTime ) + { + wait 0.05; + if ( (timeRunning - NewWaitTime) >= -10000 && timeRunning%1000 == 0 ) + clockObject playSound( "ui_mp_timer_countdown" ); + + timeRunning = ( getTime() - currentTime ); + } + + clockObject delete(); + matchStartTimer destroyElem(); +} + + +waitForPlayers( maxTime ) +{ + endTime = gettime() + maxTime * 1000 - 200; + + if ( level.teamBased ) + while( (!level.hasSpawned[ "axis" ] || !level.hasSpawned[ "allies" ]) && gettime() < endTime ) + wait ( 0.05 ); + else + while ( level.maxPlayerCount < 2 && gettime() < endTime ) + wait ( 0.05 ); +} + + +prematchPeriod() +{ + level endon( "game_ended" ); + + if ( level.prematchPeriod > 0 ) + { + if ( level.console ) + { + thread matchStartTimer( "match_starting_in", level.prematchPeriod ); + wait ( level.prematchPeriod ); + } + else + { + matchStartTimerPC(); + } + } + else + { + matchStartTimerSkip(); + } + + for ( index = 0; index < level.players.size; index++ ) + { + level.players[index] freezeControlsWrapper( false ); + level.players[index] enableWeapons(); + + hintMessage = getObjectiveHintText( level.players[index].pers["team"] ); + if ( !isDefined( hintMessage ) || !level.players[index].hasSpawned ) + continue; + + level.players[index] setClientDvar( "scr_objectiveText", hintMessage ); + level.players[index] thread maps\mp\gametypes\_hud_message::hintMessage( hintMessage ); + } + + if ( game["state"] != "playing" ) + return; +} + + +gracePeriod() +{ + level endon("game_ended"); + + while ( level.inGracePeriod ) + { + wait ( 1.0 ); + level.inGracePeriod--; + } + + //wait ( level.gracePeriod ); + + level notify ( "grace_period_ending" ); + wait ( 0.05 ); + + gameFlagSet( "graceperiod_done" ); + level.inGracePeriod = false; + + if ( game["state"] != "playing" ) + return; + + if ( getGametypeNumLives() ) + { + // Players on a team but without a weapon show as dead since they can not get in this round + players = level.players; + + for ( i = 0; i < players.size; i++ ) + { + player = players[i]; + + if ( !player.hasSpawned && player.sessionteam != "spectator" && !isAlive( player ) ) + player.statusicon = "hud_status_dead"; + } + } + + level thread updateGameEvents(); +} + + +updateWinStats( winner ) +{ + if ( !winner rankingEnabled() ) + return; + + winner maps\mp\gametypes\_persistence::statAdd( "losses", -1 ); + + println( "setting winner: " + winner maps\mp\gametypes\_persistence::statGet( "wins" ) ); + winner maps\mp\gametypes\_persistence::statAdd( "wins", 1 ); + winner updatePersRatio( "winLossRatio", "wins", "losses" ); + winner maps\mp\gametypes\_persistence::statAdd( "currentWinStreak", 1 ); + + cur_win_streak = winner maps\mp\gametypes\_persistence::statGet( "currentWinStreak" ); + if ( cur_win_streak > winner maps\mp\gametypes\_persistence::statGet( "winStreak" ) ) + winner maps\mp\gametypes\_persistence::statSet( "winStreak", cur_win_streak ); + + winner maps\mp\gametypes\_persistence::statSetChild( "round", "win", true ); + winner maps\mp\gametypes\_persistence::statSetChild( "round", "loss", false ); +} + + +updateLossStats( loser ) +{ + if ( !loser rankingEnabled() ) + return; + + loser maps\mp\gametypes\_persistence::statAdd( "losses", 1 ); + loser updatePersRatio( "winLossRatio", "wins", "losses" ); + loser maps\mp\gametypes\_persistence::statSetChild( "round", "loss", true ); +} + + +updateTieStats( loser ) +{ + if ( !loser rankingEnabled() ) + return; + + loser maps\mp\gametypes\_persistence::statAdd( "losses", -1 ); + + loser maps\mp\gametypes\_persistence::statAdd( "ties", 1 ); + loser updatePersRatio( "winLossRatio", "wins", "losses" ); + loser maps\mp\gametypes\_persistence::statSet( "currentWinStreak", 0 ); +} + + +updateWinLossStats( winner ) +{ + //if ( privateMatch() ) + // return; + + if ( !wasLastRound() ) + return; + + players = level.players; + + if ( !isDefined( winner ) || ( isDefined( winner ) && isString( winner ) && winner == "tie" ) ) + { + foreach ( player in level.players ) + { + if ( isDefined( player.connectedPostGame ) ) + continue; + + if ( level.hostForcedEnd && player isHost() ) + { + player maps\mp\gametypes\_persistence::statSet( "currentWinStreak", 0 ); + continue; + } + + updateTieStats( player ); + } + } + else if ( isPlayer( winner ) ) + { + if ( level.hostForcedEnd && winner isHost() ) + { + winner maps\mp\gametypes\_persistence::statSet( "currentWinStreak", 0 ); + return; + } + + updateWinStats( winner ); + } + else if ( isString( winner ) ) + { + foreach ( player in level.players ) + { + if ( isDefined( player.connectedPostGame ) ) + continue; + + if ( level.hostForcedEnd && player isHost() ) + { + player maps\mp\gametypes\_persistence::statSet( "currentWinStreak", 0 ); + continue; + } + + if ( winner == "tie" ) + updateTieStats( player ); + else if ( player.pers["team"] == winner ) + updateWinStats( player ); + else + player maps\mp\gametypes\_persistence::statSet( "currentWinStreak", 0 ); + } + } +} + + +freezePlayerForRoundEnd( delay ) +{ + self endon ( "disconnect" ); + self clearLowerMessages(); + + if ( !isDefined( delay ) ) + delay = 0.05; + + self closepopupMenu(); + self closeInGameMenu(); + + wait ( delay ); + self freezeControlsWrapper( true ); +// self disableWeapons(); +} + + +updateMatchBonusScores( winner ) +{ + if ( !game["timePassed"] ) + return; + + //if ( !matchMakingGame() ) + // return; + + if ( !getTimeLimit() || level.forcedEnd ) + { + gameLength = getTimePassed() / 1000; + // cap it at 20 minutes to avoid exploiting + gameLength = min( gameLength, 1200 ); + } + else + { + gameLength = getTimeLimit() * 60; + } + + if ( level.teamBased ) + { + if ( winner == "allies" ) + { + winningTeam = "allies"; + losingTeam = "axis"; + } + else if ( winner == "axis" ) + { + winningTeam = "axis"; + losingTeam = "allies"; + } + else + { + winningTeam = "tie"; + losingTeam = "tie"; + } + + if ( winningTeam != "tie" ) + { + winnerScale = maps\mp\gametypes\_rank::getScoreInfoValue( "win" ); + loserScale = maps\mp\gametypes\_rank::getScoreInfoValue( "loss" ); + setWinningTeam( winningTeam ); + } + else + { + winnerScale = maps\mp\gametypes\_rank::getScoreInfoValue( "tie" ); + loserScale = maps\mp\gametypes\_rank::getScoreInfoValue( "tie" ); + } + + foreach ( player in level.players ) + { + if ( isDefined( player.connectedPostGame ) ) + continue; + + if ( !player rankingEnabled() ) + continue; + + if ( player.timePlayed["total"] < 1 || player.pers["participation"] < 1 ) + { + player thread maps\mp\gametypes\_rank::endGameUpdate(); + continue; + } + + // no bonus for hosts who force ends + if ( level.hostForcedEnd && player isHost() ) + continue; + + spm = player maps\mp\gametypes\_rank::getSPM(); + if ( winningTeam == "tie" ) + { + playerScore = int( (winnerScale * ((gameLength/60) * spm)) * (player.timePlayed["total"] / gameLength) ); + player thread giveMatchBonus( "tie", playerScore ); + player.matchBonus = playerScore; + } + else if ( isDefined( player.pers["team"] ) && player.pers["team"] == winningTeam ) + { + playerScore = int( (winnerScale * ((gameLength/60) * spm)) * (player.timePlayed["total"] / gameLength) ); + player thread giveMatchBonus( "win", playerScore ); + player.matchBonus = playerScore; + } + else if ( isDefined(player.pers["team"] ) && player.pers["team"] == losingTeam ) + { + playerScore = int( (loserScale * ((gameLength/60) * spm)) * (player.timePlayed["total"] / gameLength) ); + player thread giveMatchBonus( "loss", playerScore ); + player.matchBonus = playerScore; + } + } + } + else + { + if ( isDefined( winner ) ) + { + winnerScale = maps\mp\gametypes\_rank::getScoreInfoValue( "win" ); + loserScale = maps\mp\gametypes\_rank::getScoreInfoValue( "loss" ); + } + else + { + winnerScale = maps\mp\gametypes\_rank::getScoreInfoValue( "tie" ); + loserScale = maps\mp\gametypes\_rank::getScoreInfoValue( "tie" ); + } + + foreach ( player in level.players ) + { + if ( isDefined( player.connectedPostGame ) ) + continue; + + if ( player.timePlayed["total"] < 1 || player.pers["participation"] < 1 ) + { + player thread maps\mp\gametypes\_rank::endGameUpdate(); + continue; + } + + spm = player maps\mp\gametypes\_rank::getSPM(); + + isWinner = false; + for ( pIdx = 0; pIdx < min( level.placement["all"].size, 3 ); pIdx++ ) + { + if ( level.placement["all"][pIdx] != player ) + continue; + isWinner = true; + } + + if ( isWinner ) + { + playerScore = int( (winnerScale * ((gameLength/60) * spm)) * (player.timePlayed["total"] / gameLength) ); + player thread giveMatchBonus( "win", playerScore ); + player.matchBonus = playerScore; + } + else + { + playerScore = int( (loserScale * ((gameLength/60) * spm)) * (player.timePlayed["total"] / gameLength) ); + player thread giveMatchBonus( "loss", playerScore ); + player.matchBonus = playerScore; + } + } + } +} + + +giveMatchBonus( scoreType, score ) +{ + self endon ( "disconnect" ); + + level waittill ( "give_match_bonus" ); + + self maps\mp\gametypes\_rank::giveRankXP( scoreType, score ); + //logXPGains(); + + self maps\mp\gametypes\_rank::endGameUpdate(); +} + + +setXenonRanks( winner ) +{ + players = level.players; + + for ( i = 0; i < players.size; i++ ) + { + player = players[i]; + + if( !isdefined(player.score) || !isdefined(player.pers["team"]) ) + continue; + + } + + for ( i = 0; i < players.size; i++ ) + { + player = players[i]; + + if( !isdefined(player.score) || !isdefined(player.pers["team"]) ) + continue; + + setPlayerTeamRank( player, player.clientid, player.score - 5 * player.deaths ); + } + sendranks(); +} + + +checkTimeLimit( prevTimePassed ) +{ + if ( isDefined( level.timeLimitOverride ) && level.timeLimitOverride ) + return; + + if ( game["state"] != "playing" ) + { + setGameEndTime( 0 ); + return; + } + + if ( getTimeLimit() <= 0 ) + { + if ( isDefined( level.startTime ) ) + setGameEndTime( level.startTime ); + else + setGameEndTime( 0 ); + return; + } + + if ( !gameFlag( "prematch_done" ) ) + { + setGameEndTime( 0 ); + return; + } + + if ( !isdefined( level.startTime ) ) + return; + + timeLeft = getTimeRemaining(); + + // want this accurate to the millisecond +// if ( getHalfTime() && game["status"] != "halftime" ) +// setGameEndTime( getTime() + (int(timeLeft) - int(getTimeLimit()*60*1000*0.5)) ); +// else + setGameEndTime( getTime() + int(timeLeft) ); + + if ( timeLeft > 0 ) + { + if ( getHalfTime() && checkHalfTime( prevTimePassed ) ) + [[level.onHalfTime]](); + + return; + } + + [[level.onTimeLimit]](); +} + + +checkHalfTime( prevTimePassed ) +{ + if ( !level.teamBased ) + return false; + + if ( getTimeLimit() ) + { + halfTime = (getTimeLimit() * 60 * 1000) * 0.5; + + if ( getTimePassed() >= halfTime && prevTimePassed < halfTime && prevTimePassed > 0 ) + { + game["roundMillisecondsAlreadyPassed"] = getTimePassed(); + return true; + } + } + + return false; +} + + + +getTimeRemaining() +{ + return getTimeLimit() * 60 * 1000 - getTimePassed(); +} + + +checkTeamScoreLimitSoon( team ) +{ + assert( isDefined( team ) ); + + if ( getWatchedDvar( "scorelimit" ) <= 0 || isObjectiveBased() ) + return; + + if ( isDefined( level.scoreLimitOverride ) && level.scoreLimitOverride ) + return; + + if ( !level.teamBased ) + return; + + // No checks until a minute has passed to let wild data settle + if ( getTimePassed() < (60 * 1000) ) // 1 min + return; + + timeLeft = estimatedTimeTillScoreLimit( team ); + + if ( timeLeft < 2 ) + level notify( "match_ending_soon", "score" ); +} + + +checkPlayerScoreLimitSoon() +{ + if ( getWatchedDvar( "scorelimit" ) <= 0 || isObjectiveBased() ) + return; + + if ( level.teamBased ) + return; + + // No checks until a minute has passed to let wild data settle + if ( getTimePassed() < (60 * 1000) ) // 1 min + return; + + timeLeft = self estimatedTimeTillScoreLimit(); + + if ( timeLeft < 2 ) + level notify( "match_ending_soon", "score" ); +} + + +checkScoreLimit() +{ + if ( isObjectiveBased() ) + return false; + + if ( isDefined( level.scoreLimitOverride ) && level.scoreLimitOverride ) + return false; + + if ( game["state"] != "playing" ) + return false; + + if ( getWatchedDvar( "scorelimit" ) <= 0 ) + return false; + + if ( level.teamBased ) + { + if( game["teamScores"]["allies"] < getWatchedDvar( "scorelimit" ) && game["teamScores"]["axis"] < getWatchedDvar( "scorelimit" ) ) + return false; + } + else + { + if ( !isPlayer( self ) ) + return false; + + if ( self.score < getWatchedDvar( "scorelimit" ) ) + return false; + } + + return onScoreLimit(); +} + + +updateGameTypeDvars() +{ + level endon ( "game_ended" ); + + while ( game["state"] == "playing" ) + { + // make sure we check time limit right when game ends + if ( isdefined( level.startTime ) ) + { + if ( getTimeRemaining() < 3000 ) + { + wait .1; + continue; + } + } + wait 1; + } +} + + +matchStartTimerPC() +{ + thread matchStartTimer( "waiting_for_teams", level.prematchPeriod + level.prematchPeriodEnd ); + + waitForPlayers( level.prematchPeriod ); + + if ( level.prematchPeriodEnd > 0 ) + matchStartTimer( "match_starting_in", level.prematchPeriodEnd ); +} + +matchStartTimer_Internal( countTime, matchStartTimer ) +{ + waittillframeend; // wait till cleanup of previous start timer if multiple happen at once + visionSetNaked( "mpIntro", 0 ); + + level endon( "match_start_timer_beginning" ); + while ( countTime > 0 && !level.gameEnded ) + { + matchStartTimer thread maps\mp\gametypes\_hud::fontPulse( level ); + wait ( matchStartTimer.inFrames * 0.05 ); + matchStartTimer setValue( countTime ); + if ( countTime == 2 ) + visionSetNaked( getDvar( "mapname" ), 3.0 ); + countTime--; + wait ( 1 - (matchStartTimer.inFrames * 0.05) ); + } +} + +matchStartTimer( type, duration ) +{ + level notify( "match_start_timer_beginning" ); + + matchStartText = createServerFontString( "objective", 1.5 ); + matchStartText setPoint( "CENTER", "CENTER", 0, -40 ); + matchStartText.sort = 1001; + matchStartText setText( game["strings"]["waiting_for_teams"] ); + matchStartText.foreground = false; + matchStartText.hidewheninmenu = true; + + matchStartText setText( game["strings"][type] ); // "match begins in:" + + matchStartTimer = createServerFontString( "hudbig", 1 ); + matchStartTimer setPoint( "CENTER", "CENTER", 0, 0 ); + matchStartTimer.sort = 1001; + matchStartTimer.color = (1,1,0); + matchStartTimer.foreground = false; + matchStartTimer.hidewheninmenu = true; + + matchStartTimer maps\mp\gametypes\_hud::fontPulseInit(); + + countTime = int( duration ); + + if ( countTime >= 2 ) + { + matchStartTimer_Internal( countTime, matchStartTimer ); + visionSetNaked( getDvar( "mapname" ), 3.0 ); + } + else + { + visionSetNaked( "mpIntro", 0 ); + visionSetNaked( getDvar( "mapname" ), 1.0 ); + } + + matchStartTimer destroyElem(); + matchStartText destroyElem(); +} + +matchStartTimerSkip() +{ + visionSetNaked( getDvar( "mapname" ), 0 ); +} + + +onRoundSwitch() +{ + if ( !isDefined( game["switchedsides"] ) ) + game["switchedsides"] = false; + + // overtime + if ( game["roundsWon"]["allies"] == getWatchedDvar( "winlimit" ) - 1 && game["roundsWon"]["axis"] == getWatchedDvar( "winlimit" ) - 1 ) + { + aheadTeam = getBetterTeam(); + if ( aheadTeam != game["defenders"] ) + { + game["switchedsides"] = !game["switchedsides"]; + } + else + { + level.halftimeSubCaption = ""; + } + level.halftimeType = "overtime"; + } + else + { + level.halftimeType = "halftime"; + game["switchedsides"] = !game["switchedsides"]; + } +} + + +checkRoundSwitch() +{ + if ( !level.teamBased ) + return false; + + if ( !isDefined( level.roundSwitch ) || !level.roundSwitch ) + return false; + + assert( game["roundsPlayed"] > 0 ); + if ( game["roundsPlayed"] % level.roundSwitch == 0 ) + { + onRoundSwitch(); + return true; + } + + return false; +} + + +// returns the best guess of the exact time until the scoreboard will be displayed and player control will be lost. +// returns undefined if time is not known +timeUntilRoundEnd() +{ + if ( level.gameEnded ) + { + timePassed = (getTime() - level.gameEndTime) / 1000; + timeRemaining = level.postRoundTime - timePassed; + + if ( timeRemaining < 0 ) + return 0; + + return timeRemaining; + } + + if ( getTimeLimit() <= 0 ) + return undefined; + + if ( !isDefined( level.startTime ) ) + return undefined; + + tl = getTimeLimit(); + + timePassed = (getTime() - level.startTime)/1000; + timeRemaining = (getTimeLimit() * 60) - timePassed; + + if ( isDefined( level.timePaused ) ) + timeRemaining += level.timePaused; + + return timeRemaining + level.postRoundTime; +} + + + +freeGameplayHudElems() +{ + // free up some hud elems so we have enough for other things. + + // perk icons + if ( isdefined( self.perkicon ) ) + { + if ( isdefined( self.perkicon[0] ) ) + { + self.perkicon[0] destroyElem(); + self.perkname[0] destroyElem(); + } + if ( isdefined( self.perkicon[1] ) ) + { + self.perkicon[1] destroyElem(); + self.perkname[1] destroyElem(); + } + if ( isdefined( self.perkicon[2] ) ) + { + self.perkicon[2] destroyElem(); + self.perkname[2] destroyElem(); + } + } + self notify("perks_hidden"); // stop any threads that are waiting to hide the perk icons + + // lower message + self.lowerMessage destroyElem(); + self.lowerTimer destroyElem(); + + // progress bar + if ( isDefined( self.proxBar ) ) + self.proxBar destroyElem(); + if ( isDefined( self.proxBarText ) ) + self.proxBarText destroyElem(); +} + + +getHostPlayer() +{ + players = getEntArray( "player", "classname" ); + + for ( index = 0; index < players.size; index++ ) + { + if ( players[index] isHost() ) + return players[index]; + } +} + + +hostIdledOut() +{ + hostPlayer = getHostPlayer(); + + // host never spawned + if ( isDefined( hostPlayer ) && !hostPlayer.hasSpawned && !isDefined( hostPlayer.selectedClass ) ) + return true; + + return false; +} + + + +roundEndWait( defaultDelay, matchBonus ) +{ + //setSlowMotion( 1.0, 0.15, defaultDelay / 2 ); + + notifiesDone = false; + while ( !notifiesDone ) + { + players = level.players; + notifiesDone = true; + + foreach ( player in players ) + { + if ( !isDefined( player.doingSplash ) ) + continue; + + if ( !player maps\mp\gametypes\_hud_message::isDoingSplash() ) + continue; + + notifiesDone = false; + } + wait ( 0.5 ); + } + + if ( !matchBonus ) + { + wait ( defaultDelay ); + level notify ( "round_end_finished" ); + //setSlowMotion( 1.0, 1.0, 0.05 ); + return; + } + + wait ( defaultDelay / 2 ); + level notify ( "give_match_bonus" ); + wait ( defaultDelay / 2 ); + + notifiesDone = false; + while ( !notifiesDone ) + { + players = level.players; + notifiesDone = true; + foreach ( player in players ) + { + if ( !isDefined( player.doingSplash ) ) + continue; + + if ( !player maps\mp\gametypes\_hud_message::isDoingSplash() ) + continue; + + notifiesDone = false; + } + wait ( 0.5 ); + } + //setSlowMotion( 1.0, 1.0, 0.05); + + level notify ( "round_end_finished" ); +} + + +roundEndDOF( time ) +{ + self setDepthOfField( 0, 128, 512, 4000, 6, 1.8 ); +} + +randomizeMaps() +{ + tok = strTok( level.votingMaps, "," ); + + randomArray = []; + for( i = 0; i < 9; i++ ) + { + selectedRand = randomint( tok.size ); + randomArray[ i ] = tok[ selectedRand ]; + tok = restructMapArray( tok, selectedRand ); + } + + return randomArray; +} + +restructMapArray(oldArray, index) +{ + restructArray = []; + + for( i=0; i < oldArray.size; i++ ) + { + if( i < index ) + restructArray[ i ] = oldArray[ i ]; + else if( i > index ) + restructArray[ i - 1 ] = oldArray[ i ]; + } + + return restructArray; +} + + +Callback_StartGameType() +{ + maps\mp\_load::main(); + + levelFlagInit( "round_over", false ); + levelFlagInit( "game_over", false ); + levelFlagInit( "block_notifies", false ); + + level.prematchPeriod = 0; + level.prematchPeriodEnd = 0; + level.postGameNotifies = 0; + + level.intermission = false; + + + setDvarIfUninitialized( "scr_extraDamageFeedback", false ); + setDvarIfUninitialized( "scr_printDamage", false ); + setDvarIfUninitialized( "scr_disableKnife", false ); + setDvarIfUninitialized( "scr_intermission_time", 30.0 ); + setDvarIfUninitialized( "scr_forceKillcam", false ); + setDvarIfUninitialized( "scr_forceKillcam_winnersKill", true ); + setDvarIfUninitialized( "scr_game_allowFinalKillcam", true ); + setDvarIfUninitialized( "scr_disableTurret", false ); + setDvarIfUninitialized( "scr_failCam", false ); + setDvarIfUninitialized( "scr_voting", false ); + setDvarIfUninitialized( "scr_voting_maps", "mp_rust,mp_terminal" ); + setDvarIfUninitialized( "scr_voting_time", 26.0 ); + setDvarIfUninitialized( "scr_voting_winTime", 4.0 ); + setDvarIfUninitialized( "scr_allow_intermission", false );//vanilla as possible + setDvarIfUninitialized( "scr_voting_bots", false ); + setDvarIfUninitialized( "scr_nuke_increases_streak", true ); + setDvarIfUninitialized( "headshot_detach_head", false ); + + level.extraDamageFeedback = getDvarInt("scr_extraDamageFeedback"); + level.allowPrintDamage = getDvarInt("scr_printDamage"); + level.disableKnife = getDvarInt("scr_disableKnife"); + level.intermissionTime = getDvarFloat("scr_intermission_time"); + level.forceFinalKillcam = getDvarInt("scr_forceKillcam"); + level.forceFinalKillcamWinnersKill = getDvarInt("scr_forceKillcam_winnersKill"); + level.allowFinalKillcam = getDvarInt("scr_game_allowFinalKillcam"); + level.disableTurret = getDvarInt( "scr_disableTurret" ); + level.failCam = getDvarInt( "scr_failCam" ); + level.voting = getDvarInt( "scr_voting" ); + level.votingMaps = getDvar( "scr_voting_maps" ); + level.voteWinTime = getDvarInt( "scr_voting_winTime" ); + level.voteTime = getDvarInt( "scr_voting_time" ); + level.allowIntermission = getDvarInt( "scr_allow_intermission" ); + level.hardpointsRollover = getDvarInt( "scr_hardpoints_rollover" ); + level.botsVote = getDvarInt( "scr_voting_bots" ); + level.headShotDetachHead = getDvarInt("headshot_detach_head"); + level.nukeIncreasesStreak = getDvarInt( "scr_nuke_increases_streak" ); + + if ( level.voting ) + level.votingMapsTok = randomizeMaps(); + else + level.votingMapsTok = strTok( level.votingMaps, "," ); + + level.scriptIncKillstreak = false; + + level.mapVotes = []; + for( i=0; i < level.votingMapsTok.size; i++ ) + level.mapVotes[ i ] = 0; + + level.inVoting = false; + level.inShowingWinner = false; + level.inIntermission = false; + level.highestVotedMap = -1; + + + makeDvarServerInfo( "cg_thirdPersonAngle", 356 ); + + makeDvarServerInfo( "scr_gameended", 0 ); + + if ( !isDefined( game["gamestarted"] ) ) + { + game["clientid"] = 0; + + alliesCharSet = getMapCustom( "allieschar" ); + if ( (!isDefined( alliesCharSet ) || alliesCharSet == "") ) + { + if ( !isDefined( game["allies"] ) ) + alliesCharSet = "us_army"; + else + alliesCharSet = game["allies"]; + } + + axisCharSet = getMapCustom( "axischar" ); + if ( (!isDefined( axisCharSet ) || axisCharSet == "") ) + { + if ( !isDefined( game["axis"] ) ) + axisCharSet = "opforce_composite"; + else + axisCharSet = game["axis"]; + } + + game["allies"] = alliesCharSet; + game["axis"] = axisCharSet; + + if ( !isDefined( game["attackers"] ) || !isDefined( game["defenders"] ) ) + thread error( "No attackers or defenders team defined in level .gsc." ); + + if ( !isDefined( game["attackers"] ) ) + game["attackers"] = "allies"; + if ( !isDefined( game["defenders"] ) ) + game["defenders"] = "axis"; + + if ( !isDefined( game["state"] ) ) + game["state"] = "playing"; + + precacheStatusIcon( "hud_status_dead" ); + precacheStatusIcon( "hud_status_connecting" ); + precacheString( &"MPUI_REVIVING" ); + precacheString( &"MPUI_BEING_REVIVED" ); + + precacheRumble( "damage_heavy" ); + + precacheShader( "white" ); + precacheShader( "black" ); + + game["menu_vote"] = "vote"; + + if ( level.voting && level.voteTime > 0.0 && level.votingMapsTok.size ) + precacheMenu( game["menu_vote"] ); + + game["menu_popup_summary"] = "popup_summary"; + + if ( level.allowIntermission && level.intermissionTime > 0.0 ) + precacheMenu( game["menu_popup_summary"] ); + + game["strings"]["press_to_spawn"] = &"PLATFORM_PRESS_TO_SPAWN"; + if ( level.teamBased ) + { + game["strings"]["waiting_for_teams"] = &"MP_WAITING_FOR_TEAMS"; + game["strings"]["opponent_forfeiting_in"] = &"MP_OPPONENT_FORFEITING_IN"; + } + else + { + game["strings"]["waiting_for_teams"] = &"MP_WAITING_FOR_MORE_PLAYERS"; + game["strings"]["opponent_forfeiting_in"] = &"MP_OPPONENT_FORFEITING_IN"; + } + game["strings"]["match_starting_in"] = &"MP_MATCH_STARTING_IN"; + game["strings"]["match_resuming_in"] = &"MP_MATCH_RESUMING_IN"; + game["strings"]["waiting_for_players"] = &"MP_WAITING_FOR_PLAYERS"; + game["strings"]["spawn_next_round"] = &"MP_SPAWN_NEXT_ROUND"; + game["strings"]["waiting_to_spawn"] = &"MP_WAITING_TO_SPAWN"; + game["strings"]["waiting_to_safespawn"] = &"MP_WAITING_TO_SAFESPAWN"; + game["strings"]["match_starting"] = &"MP_MATCH_STARTING"; + game["strings"]["change_class"] = &"MP_CHANGE_CLASS_NEXT_SPAWN"; + game["strings"]["last_stand"] = &"MPUI_LAST_STAND"; + game["strings"]["final_stand"] = &"MPUI_FINAL_STAND"; + game["strings"]["c4_death"] = &"MPUI_C4_DEATH"; + + game["strings"]["cowards_way"] = &"PLATFORM_COWARDS_WAY_OUT"; + + game["strings"]["tie"] = &"MP_MATCH_TIE"; + game["strings"]["round_draw"] = &"MP_ROUND_DRAW"; + + game["strings"]["grabbed_flag"] = &"MP_GRABBED_FLAG_FIRST"; + game["strings"]["enemies_eliminated"] = &"MP_ENEMIES_ELIMINATED"; + game["strings"]["score_limit_reached"] = &"MP_SCORE_LIMIT_REACHED"; + game["strings"]["round_limit_reached"] = &"MP_ROUND_LIMIT_REACHED"; + game["strings"]["time_limit_reached"] = &"MP_TIME_LIMIT_REACHED"; + game["strings"]["players_forfeited"] = &"MP_PLAYERS_FORFEITED"; + game["strings"]["S.A.S Win"] = &"SAS_WIN"; + game["strings"]["Spetsnaz Win"] = &"SPETSNAZ_WIN"; + + game["colors"]["blue"] = (0.25,0.25,0.75); + game["colors"]["red"] = (0.75,0.25,0.25); + game["colors"]["white"] = (1.0,1.0,1.0); + game["colors"]["black"] = (0.0,0.0,0.0); + game["colors"]["green"] = (0.25,0.75,0.25); + game["colors"]["yellow"] = (0.65,0.65,0.0); + game["colors"]["orange"] = (1.0,0.45,0.0); + + game["strings"]["allies_eliminated"] = maps\mp\gametypes\_teams::getTeamEliminatedString( "allies" ); + game["strings"]["allies_forfeited"] = maps\mp\gametypes\_teams::getTeamForfeitedString( "allies" ); + game["strings"]["allies_name"] = maps\mp\gametypes\_teams::getTeamName( "allies" ); + game["icons"]["allies"] = maps\mp\gametypes\_teams::getTeamIcon( "allies" ); + game["colors"]["allies"] = maps\mp\gametypes\_teams::getTeamColor( "allies" ); + + game["strings"]["axis_eliminated"] = maps\mp\gametypes\_teams::getTeamEliminatedString( "axis" ); + game["strings"]["axis_forfeited"] = maps\mp\gametypes\_teams::getTeamForfeitedString( "axis" ); + game["strings"]["axis_name"] = maps\mp\gametypes\_teams::getTeamName( "axis" ); + game["icons"]["axis"] = maps\mp\gametypes\_teams::getTeamIcon( "axis" ); + game["colors"]["axis"] = maps\mp\gametypes\_teams::getTeamColor( "axis" ); + + if ( game["colors"]["allies"] == (0,0,0) ) + game["colors"]["allies"] = (0.5,0.5,0.5); + + if ( game["colors"]["axis"] == (0,0,0) ) + game["colors"]["axis"] = (0.5,0.5,0.5); + + [[level.onPrecacheGameType]](); + + if ( level.console ) + { + if ( !level.splitscreen ) + level.prematchPeriod = maps\mp\gametypes\_tweakables::getTweakableValue( "game", "graceperiod" ); + } + else + { + // first round, so set up prematch + level.prematchPeriod = maps\mp\gametypes\_tweakables::getTweakableValue( "game", "playerwaittime" ); + level.prematchPeriodEnd = maps\mp\gametypes\_tweakables::getTweakableValue( "game", "matchstarttime" ); + } + } + + if ( !isDefined( game["status"] ) ) + game["status"] = "normal"; + + makeDvarServerInfo( "ui_overtime", (game["status"] == "overtime") ); + + if ( game["status"] != "overtime" && game["status"] != "halftime" ) + { + game["teamScores"]["allies"] = 0; + game["teamScores"]["axis"] = 0; + } + + if( !isDefined( game["timePassed"] ) ) + game["timePassed"] = 0; + + if( !isDefined( game["roundsPlayed"] ) ) + game["roundsPlayed"] = 0; + + if ( !isDefined( game["roundsWon"] ) ) + game["roundsWon"] = []; + + if ( level.teamBased ) + { + if ( !isDefined( game["roundsWon"]["axis"] ) ) + game["roundsWon"]["axis"] = 0; + if ( !isDefined( game["roundsWon"]["allies"] ) ) + game["roundsWon"]["allies"] = 0; + } + + level.gameEnded = false; + level.forcedEnd = false; + level.hostForcedEnd = false; + + level.hardcoreMode = getDvarInt( "g_hardcore" ); + if ( level.hardcoreMode ) + logString( "game mode: hardcore" ); + + level.dieHardMode = getDvarInt( "scr_diehard" ); + + if ( !level.teamBased ) + level.dieHardMode = 0; + + if ( level.dieHardMode ) + logString( "game mode: diehard" ); + + level.killstreakRewards = getDvarInt( "scr_game_hardpoints" ); + + /# + printLn( "SESSION INFO" ); + printLn( "=====================================" ); + printLn( " Map: " + level.script ); + printLn( " Script: " + level.gametype ); + printLn( " HardCore: " + level.hardcoreMode ); + printLn( " Diehard: " + level.dieHardMode ); + printLn( " 3rd Person: " + getDvarInt( "camera_thirdperson" ) ); + printLn( " Round: " + game[ "roundsPlayed" ] ); + printLn( " scr_" + level.gametype + "_scorelimit " + getDvar( "scr_" + level.gametype + "_scorelimit" ) ); + printLn( " scr_" + level.gametype + "_roundlimit " +getDvar( "scr_" + level.gametype + "_roundlimit" ) ); + printLn( " scr_" + level.gametype + "_winlimit " + getDvar( "scr_" + level.gametype + "_winlimit" ) ); + printLn( " scr_" + level.gametype + "_timelimit " + getDvar( "scr_" + level.gametype + "_timelimit" ) ); + printLn( " scr_" + level.gametype + "_numlives " + getDvar( "scr_" + level.gametype + "_numlives" ) ); + printLn( " scr_" + level.gametype + "_halftime " + getDvar( "scr_" + level.gametype + "_halftime" ) ); + printLn( " scr_" + level.gametype + "_roundswitch " + getDvar( "scr_" + level.gametype + "_roundswitch" ) ); + printLn( "=====================================" ); + #/ + + // this gets set to false when someone takes damage or a gametype-specific event happens. + level.useStartSpawns = true; + + // multiplier for score from objectives + level.objectivePointsMod = 1; + + if ( matchMakingGame() ) + level.maxAllowedTeamKills = 2; + else + level.maxAllowedTeamKills = -1; + + thread maps\mp\gametypes\_persistence::init(); + thread maps\mp\gametypes\_menus::init(); + thread maps\mp\gametypes\_hud::init(); + thread maps\mp\gametypes\_serversettings::init(); + thread maps\mp\gametypes\_teams::init(); + thread maps\mp\gametypes\_weapons::init(); + thread maps\mp\gametypes\_killcam::init(); + thread maps\mp\gametypes\_shellshock::init(); + thread maps\mp\gametypes\_deathicons::init(); + thread maps\mp\gametypes\_damagefeedback::init(); + thread maps\mp\gametypes\_healthoverlay::init(); + thread maps\mp\gametypes\_spectating::init(); + thread maps\mp\gametypes\_objpoints::init(); + thread maps\mp\gametypes\_gameobjects::init(); + thread maps\mp\gametypes\_spawnlogic::init(); + thread maps\mp\gametypes\_battlechatter_mp::init(); + thread maps\mp\gametypes\_music_and_dialog::init(); + thread maps\mp\_matchdata::init(); + thread maps\mp\_awards::init(); + thread maps\mp\_skill::init(); + thread maps\mp\_areas::init(); + thread maps\mp\killstreaks\_killstreaks::init(); + //thread maps\mp\_perks::init(); // No longer in use, removed from common scripts. (smart arrow) + thread maps\mp\perks\_perks::init(); + thread maps\mp\_events::init(); + thread maps\mp\_defcon::init(); + + level thread onPlayerConnect(); + + if ( level.teamBased ) + thread maps\mp\gametypes\_friendicons::init(); + + thread maps\mp\gametypes\_hud_message::init(); + + if ( !level.console ) + thread maps\mp\gametypes\_quickmessages::init(); + + foreach ( locString in game["strings"] ) + precacheString( locString ); + + foreach ( icon in game["icons"] ) + precacheShader( icon ); + + game["gamestarted"] = true; + + level.maxPlayerCount = 0; + level.waveDelay["allies"] = 0; + level.waveDelay["axis"] = 0; + level.lastWave["allies"] = 0; + level.lastWave["axis"] = 0; + level.wavePlayerSpawnIndex["allies"] = 0; + level.wavePlayerSpawnIndex["axis"] = 0; + level.alivePlayers["allies"] = []; + level.alivePlayers["axis"] = []; + level.activePlayers = []; + + makeDvarServerInfo( "ui_scorelimit", 0 ); + makeDvarServerInfo( "ui_allow_classchange", getDvar( "ui_allow_classchange" ) ); + makeDvarServerInfo( "ui_allow_teamchange", 1 ); + setDvar( "ui_allow_teamchange", 1 ); + + if ( getGametypeNumLives() ) + setdvar( "g_deadChat", 0 ); + else + setdvar( "g_deadChat", 1 ); + + waveDelay = getDvarInt( "scr_" + level.gameType + "_waverespawndelay" ); + if ( waveDelay ) + { + level.waveDelay["allies"] = waveDelay; + level.waveDelay["axis"] = waveDelay; + level.lastWave["allies"] = 0; + level.lastWave["axis"] = 0; + + level thread maps\mp\gametypes\_gamelogic::waveSpawnTimer(); + } + + gameFlagInit( "prematch_done", false ); + + level.gracePeriod = 15; + + level.inGracePeriod = level.gracePeriod; + gameFlagInit( "graceperiod_done", false ); + + level.roundEndDelay = 4; + level.halftimeRoundEndDelay = 4; + + + if ( level.teamBased ) + { + maps\mp\gametypes\_gamescore::updateTeamScore( "axis" ); + maps\mp\gametypes\_gamescore::updateTeamScore( "allies" ); + } + else + { + thread maps\mp\gametypes\_gamescore::initialDMScoreUpdate(); + } + + thread updateUIScoreLimit(); + level notify ( "update_scorelimit" ); + + + [[level.onStartGameType]](); + + // this must be after onstartgametype for scr_showspawns to work when set at start of game + /# + thread maps\mp\gametypes\_dev::init(); + #/ + + thread startGame(); + + level thread updateWatchedDvars(); + level thread timeLimitThread(); + + level thread doFinalKillcam(); +} + +onPlayerConnect() +{ + for(;;) + { + level waittill( "connected", player ); + + player thread onPlayerSpawned(); + + player thread watchIntermissionAfterConnect(); + } +} + +watchIntermissionAfterConnect() +{ + self endon("disconnect"); + + wait 0.05; + + self notify("kill_menus_on_connect"); + if(level.inVoting || level.inShowingWinner) + { + self.sessionstate = "spectator";//allow player to leave while in intermission and make voting options + self thread watchVoting(); + + if(level.inVoting) + { + self setClientDvars( "hud_ShowWinner", "0", + "hud_voteText", "^3Vote for new map:", + "vote_map", "" ); + } + else if(level.inShowingWinner) + { + self setClientDvars( "hud_WinningName", "preview_" + level.votingMapsTok[ level.highestVotedMap ], + "hud_WinningMap", consoleMapNameToMapName( level.votingMapsTok[ level.highestVotedMap ] ), + "hud_voteText", "^3Next Map:", + "hud_ShowWinner", "1" ); + } + + self openPopupMenu( game["menu_vote"] ); + } + else if(level.inIntermission) + { + self.sessionstate = "spectator";//allow player to leave while in intermission + self thread openSummaryOnMenuClose(); + self openPopupMenu( game["menu_popup_summary"] ); + } +} + +onPlayerSpawned() +{ + self endon("disconnect"); + self.printDamage = false; + firstTime = false; + for(;;) + { + self waittill("spawned_player"); + if( !firstTime && level.allowPrintDamage ) + { + firstTime = true; + self iPrintlnBold( "^7Press ^3[{+actionslot 2}] ^7to toggle ^3Print Damage" ); + } + self thread printDamage(); + } +} + +printDamage() +{ + if( !level.allowPrintDamage ) + return; + + self endon("disconnect"); + self endon("death"); + + self notifyOnPlayerCommand("printDamage", "+actionslot 2"); + for(;;) + { + self waittill("printDamage"); + self playSound( "semtex_warning" ); + if(self.printDamage) + { + self iPrintlnBold("^7Print Damage ^1Disabled"); + self.printDamage = false; + } + else + { + self iPrintlnBold("^7Print Damage ^1Enabled"); + self.printDamage = true; + } + } +} + + +Callback_CodeEndGame() +{ + endparty(); + + if ( !level.gameEnded ) + level thread maps\mp\gametypes\_gamelogic::forceEnd(); +} + + +timeLimitThread() +{ + level endon ( "game_ended" ); + + prevTimePassed = getTimePassed(); + + while ( game["state"] == "playing" ) + { + thread checkTimeLimit( prevTimePassed ); + prevTimePassed = getTimePassed(); + + // make sure we check time limit right when game ends + if ( isdefined( level.startTime ) ) + { + if ( getTimeRemaining() < 3000 ) + { + wait .1; + continue; + } + } + wait 1; + } +} + + +updateUIScoreLimit() +{ + for ( ;; ) + { + level waittill_either ( "update_scorelimit", "update_winlimit" ); + + if ( !isRoundBased() || !isObjectiveBased() ) + { + setDvar( "ui_scorelimit", getWatchedDvar( "scorelimit" ) ); + thread checkScoreLimit(); + } + else + { + setDvar( "ui_scorelimit", getWatchedDvar( "winlimit" ) ); + } + } +} + + +playTickingSound() +{ + self endon("death"); + self endon("stop_ticking"); + level endon("game_ended"); + + time = level.bombTimer; + + while(1) + { + self playSound( "ui_mp_suitcasebomb_timer" ); + + if ( time > 10 ) + { + time -= 1; + wait 1; + } + else if ( time > 4 ) + { + time -= .5; + wait .5; + } + else if ( time > 1 ) + { + time -= .4; + wait .4; + } + else + { + time -= .3; + wait .3; + } + maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone(); + } +} + +stopTickingSound() +{ + self notify("stop_ticking"); +} + +timeLimitClock() +{ + level endon ( "game_ended" ); + + wait .05; + + clockObject = spawn( "script_origin", (0,0,0) ); + clockObject hide(); + + while ( game["state"] == "playing" ) + { + if ( !level.timerStopped && getTimeLimit() ) + { + timeLeft = getTimeRemaining() / 1000; + timeLeftInt = int(timeLeft + 0.5); // adding .5 and flooring rounds it. + + if ( getHalfTime() && timeLeftInt > (getTimeLimit()*60) * 0.5 ) + timeLeftInt -= int((getTimeLimit()*60) * 0.5); + + if ( (timeLeftInt >= 30 && timeLeftInt <= 60) ) + level notify ( "match_ending_soon", "time" ); + + if ( timeLeftInt <= 10 || (timeLeftInt <= 30 && timeLeftInt % 2 == 0) ) + { + level notify ( "match_ending_very_soon" ); + // don't play a tick at exactly 0 seconds, that's when something should be happening! + if ( timeLeftInt == 0 ) + break; + + clockObject playSound( "ui_mp_timer_countdown" ); + } + + // synchronize to be exactly on the second + if ( timeLeft - floor(timeLeft) >= .05 ) + wait timeLeft - floor(timeLeft); + } + + wait ( 1.0 ); + } +} + + +gameTimer() +{ + level endon ( "game_ended" ); + + level waittill("prematch_over"); + + level.startTime = getTime(); + level.discardTime = 0; + + if ( isDefined( game["roundMillisecondsAlreadyPassed"] ) ) + { + level.startTime -= game["roundMillisecondsAlreadyPassed"]; + game["roundMillisecondsAlreadyPassed"] = undefined; + } + + prevtime = gettime(); + + while ( game["state"] == "playing" ) + { + if ( !level.timerStopped ) + { + // the wait isn't always exactly 1 second. dunno why. + game["timePassed"] += gettime() - prevtime; + } + prevtime = gettime(); + wait ( 1.0 ); + } +} + +UpdateTimerPausedness() +{ + shouldBeStopped = level.timerStoppedForGameMode || isDefined( level.hostMigrationTimer ); + if ( !gameFlag( "prematch_done" ) ) + shouldBeStopped = false; + + if ( !level.timerStopped && shouldBeStopped ) + { + level.timerStopped = true; + level.timerPauseTime = gettime(); + } + else if ( level.timerStopped && !shouldBeStopped ) + { + level.timerStopped = false; + level.discardTime += gettime() - level.timerPauseTime; + } +} + +pauseTimer() +{ + level.timerStoppedForGameMode = true; + UpdateTimerPausedness(); +} + +resumeTimer() +{ + level.timerStoppedForGameMode = false; + UpdateTimerPausedness(); +} + + +startGame() +{ + thread gameTimer(); + level.timerStopped = false; + level.timerStoppedForGameMode = false; + thread maps\mp\gametypes\_spawnlogic::spawnPerFrameUpdate(); + + prematchPeriod(); + gameFlagSet( "prematch_done" ); + level notify("prematch_over"); + + UpdateTimerPausedness(); + + thread timeLimitClock(); + thread gracePeriod(); + + thread maps\mp\gametypes\_missions::roundBegin(); +} + + +waveSpawnTimer() +{ + level endon( "game_ended" ); + + while ( game["state"] == "playing" ) + { + time = getTime(); + + if ( time - level.lastWave["allies"] > (level.waveDelay["allies"] * 1000) ) + { + level notify ( "wave_respawn_allies" ); + level.lastWave["allies"] = time; + level.wavePlayerSpawnIndex["allies"] = 0; + } + + if ( time - level.lastWave["axis"] > (level.waveDelay["axis"] * 1000) ) + { + level notify ( "wave_respawn_axis" ); + level.lastWave["axis"] = time; + level.wavePlayerSpawnIndex["axis"] = 0; + } + + wait ( 0.05 ); + } +} + + +getBetterTeam() +{ + kills["allies"] = 0; + kills["axis"] = 0; + deaths["allies"] = 0; + deaths["axis"] = 0; + + foreach ( player in level.players ) + { + team = player.pers["team"]; + if ( isDefined( team ) && (team == "allies" || team == "axis") ) + { + kills[ team ] += player.kills; + deaths[ team ] += player.deaths; + } + } + + if ( kills["allies"] > kills["axis"] ) + return "allies"; + else if ( kills["axis"] > kills["allies"] ) + return "axis"; + + // same number of kills + + if ( deaths["allies"] < deaths["axis"] ) + return "allies"; + else if ( deaths["axis"] < deaths["allies"] ) + return "axis"; + + // same number of deaths + + if ( randomint(2) == 0 ) + return "allies"; + return "axis"; +} + + +rankedMatchUpdates( winner ) +{ + //if ( matchMakingGame() ) + //{ + setXenonRanks(); + + //if ( hostIdledOut() ) + //{ + // level.hostForcedEnd = true; + // logString( "host idled out" ); + // endLobby(); + //} + + updateMatchBonusScores( winner ); + //} + + updateWinLossStats( winner ); +} + + +displayRoundEnd( winner, endReasonText ) +{ + foreach ( player in level.players ) + { + if ( isDefined( player.connectedPostGame ) || player.pers["team"] == "spectator" ) + continue; + + if ( level.teamBased ) + player thread maps\mp\gametypes\_hud_message::teamOutcomeNotify( winner, true, endReasonText ); + else + player thread maps\mp\gametypes\_hud_message::outcomeNotify( winner, endReasonText ); + } + + if ( !wasLastRound() ) + level notify ( "round_win", winner ); + + if ( wasLastRound() ) + roundEndWait( level.roundEndDelay, false ); + else + roundEndWait( level.roundEndDelay, true ); +} + + +displayGameEnd( winner, endReasonText ) +{ + // catching gametype, since DM forceEnd sends winner as player entity, instead of string + foreach ( player in level.players ) + { + if ( isDefined( player.connectedPostGame ) || player.pers["team"] == "spectator" ) + continue; + + if ( level.teamBased ) + player thread maps\mp\gametypes\_hud_message::teamOutcomeNotify( winner, false, endReasonText ); + else + player thread maps\mp\gametypes\_hud_message::outcomeNotify( winner, endReasonText ); + } + + level notify ( "game_win", winner ); + + roundEndWait( level.postRoundTime, true ); +} + + +displayRoundSwitch() +{ + switchType = level.halftimeType; + if ( switchType == "halftime" ) + { + if ( getWatchedDvar( "roundlimit" ) ) + { + if ( (game["roundsPlayed"] * 2) == getWatchedDvar( "roundlimit" ) ) + switchType = "halftime"; + else + switchType = "intermission"; + } + else if ( getWatchedDvar( "winlimit" ) ) + { + if ( game["roundsPlayed"] == (getWatchedDvar( "winlimit" ) - 1) ) + switchType = "halftime"; + else + switchType = "intermission"; + } + else + { + switchType = "intermission"; + } + } + + level notify ( "round_switch", switchType ); + + foreach ( player in level.players ) + { + if ( isDefined( player.connectedPostGame ) || player.pers["team"] == "spectator" ) + continue; + + player thread maps\mp\gametypes\_hud_message::teamOutcomeNotify( switchType, true, level.halftimeSubCaption ); + } + + roundEndWait( level.halftimeRoundEndDelay, false ); +} + + +endGameOvertime( winner, endReasonText ) +{ + visionSetNaked( "mpOutro", 0.5 ); + setDvar( "scr_gameended", 2 ); + // freeze players + foreach ( player in level.players ) + { + player thread freezePlayerForRoundEnd( 0 ); + player thread roundEndDoF( 4.0 ); + + player freeGameplayHudElems(); + + player setClientDvars( "cg_everyoneHearsEveryone", 1 ); + player setClientDvars( "cg_drawSpectatorMessages", 0, + "g_compassShowEnemies", 0 ); + + if ( player.pers["team"] == "spectator" ) + player thread maps\mp\gametypes\_playerlogic::spawnIntermission(); + } + + level notify ( "round_switch", "overtime" ); + + // catching gametype, since DM forceEnd sends winner as player entity, instead of string + foreach ( player in level.players ) + { + if ( isDefined( player.connectedPostGame ) || player.pers["team"] == "spectator" ) + continue; + + if ( level.teamBased ) + player thread maps\mp\gametypes\_hud_message::teamOutcomeNotify( winner, false, endReasonText ); + else + player thread maps\mp\gametypes\_hud_message::outcomeNotify( winner, endReasonText ); + } + + roundEndWait( level.roundEndDelay, false ); + + if ( level.forceFinalKillcam ) + waittillFinalKillcamDone(); + + game["status"] = "overtime"; + level notify ( "restarting" ); + game["state"] = "playing"; + map_restart( true ); +} + + + +endGameHalfTime() +{ + visionSetNaked( "mpOutro", 0.5 ); + setDvar( "scr_gameended", 2 ); + + game["switchedsides"] = !game["switchedsides"]; + + // freeze players + foreach ( player in level.players ) + { + player thread freezePlayerForRoundEnd( 0 ); + player thread roundEndDoF( 4.0 ); + + player freeGameplayHudElems(); + + player setClientDvars( "cg_everyoneHearsEveryone", 1 ); + player setClientDvars( "cg_drawSpectatorMessages", 0, + "g_compassShowEnemies", 0 ); + + if ( player.pers["team"] == "spectator" ) + player thread maps\mp\gametypes\_playerlogic::spawnIntermission(); + } + + foreach ( player in level.players ) + player.pers["stats"] = player.stats; + + level notify ( "round_switch", "halftime" ); + + foreach ( player in level.players ) + { + if ( isDefined( player.connectedPostGame ) || player.pers["team"] == "spectator" ) + continue; + + player thread maps\mp\gametypes\_hud_message::teamOutcomeNotify( "halftime", true, level.halftimeSubCaption ); + } + + roundEndWait( level.roundEndDelay, false ); + + if ( level.forceFinalKillcam ) + waittillFinalKillcamDone(); + + game["status"] = "halftime"; + level notify ( "restarting" ); + game["state"] = "playing"; + map_restart( true ); +} + + +endGame( winner, endReasonText, nukeDetonated ) +{ + if( !level.forceFinalKillcamWinnersKill || !isDefined( winner ) || !isString( winner ) || ( winner != "axis" && winner != "allies" ) ) + level.finalKillCam_winner = "none"; + else + level.finalKillCam_winner = winner; + + if ( !isDefined(nukeDetonated) ) + nukeDetonated = false; + + // return if already ending via host quit or victory, or nuke incoming + if ( game["state"] == "postgame" || level.gameEnded || (isDefined(level.nukeIncoming) && !nukeDetonated) && ( !isDefined( level.gtnw ) || !level.gtnw ) ) + return; + + game["state"] = "postgame"; + + level.gameEndTime = getTime(); + level.gameEnded = true; + level.inGracePeriod = false; + level notify ( "game_ended", winner ); + levelFlagSet( "game_over" ); + levelFlagSet( "block_notifies" ); + waitframe(); // give "game_ended" notifies time to process + + setGameEndTime( 0 ); // stop/hide the timers + + maps\mp\gametypes\_playerlogic::printPredictedSpawnpointCorrectness(); + + if ( isDefined( winner ) && isString( winner ) && winner == "overtime" ) + { + endGameOvertime( winner, endReasonText ); + return; + } + + if ( isDefined( winner ) && isString( winner ) && winner == "halftime" ) + { + endGameHalftime(); + return; + } + + game["roundsPlayed"]++; + + if ( level.teamBased ) + { + if ( winner == "axis" || winner == "allies" ) + game["roundsWon"][winner]++; + + maps\mp\gametypes\_gamescore::updateTeamScore( "axis" ); + maps\mp\gametypes\_gamescore::updateTeamScore( "allies" ); + } + else + { + if ( isDefined( winner ) && isPlayer( winner ) ) + game["roundsWon"][winner.guid]++; + } + + maps\mp\gametypes\_gamescore::updatePlacement(); + + rankedMatchUpdates( winner ); + + foreach ( player in level.players ) + { + player setClientDvar( "ui_opensummary", 1 ); + } + + setDvar( "g_deadChat", 1 ); + setDvar( "ui_allow_teamchange", 0 ); + + // freeze players + foreach ( player in level.players ) + { + player thread freezePlayerForRoundEnd( 1.0 ); + player thread roundEndDoF( 4.0 ); + + player freeGameplayHudElems(); + + player setClientDvars( "cg_everyoneHearsEveryone", 1 ); + player setClientDvars( "cg_drawSpectatorMessages", 0, + "g_compassShowEnemies", 0, + "cg_fovScale", 1 ); + + if ( player.pers["team"] == "spectator" ) + player thread maps\mp\gametypes\_playerlogic::spawnIntermission(); + } + + if( !nukeDetonated ) + visionSetNaked( "mpOutro", 0.5 ); + + // End of Round + if ( !wasOnlyRound() && !nukeDetonated ) + { + setDvar( "scr_gameended", 2 ); + + displayRoundEnd( winner, endReasonText ); + + if ( level.forceFinalKillcam ) + { + waittillFinalKillcamDone(); + } + else + { + if ( level.showingFinalKillcam ) + { + foreach ( player in level.players ) + player notify ( "reset_outcome" ); + + level notify ( "game_cleanup" ); + + waittillFinalKillcamDone(); + } + } + + if ( !wasLastRound() ) + { + levelFlagClear( "block_notifies" ); + if ( checkRoundSwitch() ) + displayRoundSwitch(); + + foreach ( player in level.players ) + player.pers["stats"] = player.stats; + + level notify ( "restarting" ); + game["state"] = "playing"; + map_restart( true ); + return; + } + + if ( !level.forcedEnd ) + endReasonText = updateEndReasonText( winner ); + } + + setDvar( "scr_gameended", 1 ); + + if ( !isDefined( game["clientMatchDataDef"] ) ) + { + game["clientMatchDataDef"] = "mp/clientmatchdata.def"; + setClientMatchDataDef( game["clientMatchDataDef"] ); + } + + maps\mp\gametypes\_missions::roundEnd( winner ); + + if( isRoundBased() ) + winner = getWinningTeam(); + + displayGameEnd( winner, endReasonText ); + + if( wasOnlyRound() ) + { + if ( level.forceFinalKillcam ) + { + waittillFinalKillcamDone(); + } + else + { + if ( level.showingFinalKillcam ) + { + foreach ( player in level.players ) + player notify ( "reset_outcome" ); + + level notify ( "game_cleanup" ); + + waittillFinalKillcamDone(); + } + } + } + + levelFlagClear( "block_notifies" ); + + level.intermission = true; + + level notify ( "spawning_intermission" ); + + foreach ( player in level.players ) + { + player closepopupMenu(); + player closeInGameMenu(); + player notify ( "reset_outcome" ); + player thread maps\mp\gametypes\_playerlogic::spawnIntermission(); + } + + processLobbyData(); + + wait ( 1.0 ); + + if ( matchMakingGame() ) + sendMatchData(); + + foreach ( player in level.players ) + player.pers["stats"] = player.stats; + + //logString( "game ended" ); + if( !nukeDetonated && !level.postGameNotifies ) + { + if ( !wasOnlyRound() ) + wait 6.0; + else + wait 3.0; + } + else + { + wait ( min( 10.0, 4.0 + level.postGameNotifies ) ); + } + + if ( level.voting && level.voteTime > 0.0 && level.votingMapsTok.size ) + { + level.inVoting = true; + foreach (player in level.players) + { + player.sessionstate = "spectator";//allow player to leave while in intermission and make voting options + player thread watchVoting(); + + player setClientDvars( "hud_ShowWinner", "0", + "hud_voteText", "^3Vote for new map:", + "vote_map", "" ); + + player openPopupMenu( game["menu_vote"] ); + } + + level thread doBotVoting(); + thread timeLimitClock_Intermission( level.voteTime, (84.7, 100, 0) ); + wait level.voteTime; + level.inVoting = false; + + level.highestVotedMap = getHighestVotedMap(); + + if ( level.voteWinTime > 0.0 ) + { + level.inShowingWinner = true; + foreach (player in level.players) + { + player setClientDvars( "hud_WinningName", "preview_" + level.votingMapsTok[ level.highestVotedMap ], + "hud_WinningMap", consoleMapNameToMapName( level.votingMapsTok[ level.highestVotedMap ] ), + "hud_voteText", "^3Next Map:", + "hud_ShowWinner", "1" ); + } + + thread timeLimitClock_Intermission( level.voteWinTime, (84.7, 100, 0) ); + wait level.voteWinTime; + level.inShowingWinner = false; + } + + foreach (player in level.players) + player closeMenu( game["menu_vote"] ); + + level notify( "voting_finished" ); + + level setVoteVars( level.highestVotedMap ); + } + + if ( level.allowIntermission && level.intermissionTime > 0.0 ) + { + level.inIntermission = true; + foreach (player in level.players) + { + player.sessionstate = "spectator";//allow player to leave while in intermission + player thread openSummaryOnMenuClose(); + player openPopupMenu( game["menu_popup_summary"] ); + } + + thread timeLimitClock_Intermission( level.intermissionTime ); + wait level.intermissionTime; + level.inIntermission = false; + + level notify( "intermission_finished" ); + } + + level notify( "exitLevel_called" ); + exitLevel( false ); +} + +setVoteVars( highestVotedMap ) +{ + setDvar( "sv_maprotation", "map " + level.votingMapsTok[ highestVotedMap ] ); + setDvar( "sv_maprotationCurrent", "map " + level.votingMapsTok[ highestVotedMap ] ); +} + +getHighestVotedMap() +{ + highest = 0; + for( i = 0; i < level.mapVotes.size; i++ ) + if( level.mapVotes[ i ] > highest && i < 9 ) + highest = level.mapVotes[ i ]; + + votes = []; + for( i = 0; i < level.mapVotes.size; i++ ) + if( level.mapVotes[ i ] == highest && i < 9 ) + votes[votes.size] = i; + + if ( votes.size ) + return votes[ randomInt( votes.size ) ]; + else if ( level.mapVotes.size > 9 ) + return randomIntRange( 0, 8 ); + else + return randomInt( level.mapVotes.size ); +} + +doBotVoting() +{ + if(!level.botsVote) + return; + + level endon("voting_finished"); + + bots = []; + + foreach(player in level.players) + { + if(!isDefined(player.pers["isBot"]) || !player.pers["isBot"]) + continue; + + player.botWillVoteFor = bots.size; + bots[bots.size] = player; + } + + while(bots.size) + { + wait 0.05; + bot = bots[randomInt(bots.size)]; + if(!bot.hasVoted) + { + bot castMap(bot.botWillVoteFor); + wait randomInt(3); + } + } +} + +ridVoteOnDisconnect() +{ + level endon("voting_finished"); + + self waittill_either( "disconnect", "kill_menus_on_connect" ); + + if ( self.votedNum > -1 ) + level.mapVotes[ self.votedNum ]--; +} + +updateVoteMenu() +{ + self endon("disconnect"); + self endon("kill_menus_on_connect"); + level endon("voting_finished"); + + for(i = 0; i < 9; i++) + { + if( i >= level.votingMapsTok.size ) + { + self setClientDvar( "hud_picName" + i, "white" ); + } + else + { + self setClientDvars( "hud_mapName" + i, consoleMapNameToMapName( level.votingMapsTok[ i ] ), + "hud_mapVotes" + i, level.mapVotes[ i ], + "hud_picName" + i, "preview_" + level.votingMapsTok[ i ] ); + } + } + + for(;;) + { + wait 0.5; + + for( i = 0; i < level.votingMapsTok.size; i++ ) + self setClientDvar( "hud_mapVotes" + i, level.mapVotes[ i ] ); + + highestVotedMap = getHighestVotedMap(); + self setClientDvars( "hud_gamesize", level.players.size, + "hud_mapVotes" + highestVotedMap, "^3" + level.mapVotes[ highestVotedMap ] ); + } +} + +watchVoting() +{ + self endon("disconnect"); + self endon("kill_menus_on_connect"); + level endon("voting_finished"); + + self thread ridVoteOnDisconnect(); + self thread updateVoteMenu(); + + self.hasVoted = false; + self.votedNum = -1; + + for(;;) + { + self waittill("menuresponse", menu, response); + + if( menu == game["menu_vote"] ) + { + switch(response) + { + case "map1": + self castMap(0); + break; + case "map2": + self castMap(1); + break; + case "map3": + self castMap(2); + break; + case "map4": + self castMap(3); + break; + case "map5": + self castMap(4); + break; + case "map6": + self castMap(5); + break; + case "map7": + self castMap(6); + break; + case "map8": + self castMap(7); + break; + case "map9": + self castMap(8); + break; + default: + break; + } + } + else if ( response == "back" ) + { + self closepopupMenu(); + self closeInGameMenu(); + wait 0.25; + self openPopupMenu( game["menu_vote"] ); + continue; + } + } +} + +castMap( number ) +{ + if ( !isDefined( level.votingMapsTok[ number ] ) || level.votingMapsTok[ number ] == "" ) + { + if ( self.hasVoted ) + { + self.hasVoted = false; + level.mapVotes[ self.votedNum ]--; + self.votedNum = -1; + } + self iprintln( "Invalid map selection" ); + return; + } + + if( !self.hasVoted ) + { + self.hasVoted = true; + level.mapVotes[ number ]++; + self.votedNum = number; + self iprintln( "You voted for ^3" + consoleMapNameToMapName( level.votingMapsTok[ number ] ) ); + } + else if( self.votedNum != number ) + { + level.mapVotes[ self.votedNum ]--; + level.mapVotes[ number ]++; + self.votedNum = number; + self iprintln( "You ^3re-voted ^7for ^3" + consoleMapNameToMapName( level.votingMapsTok[ number ] ) ); + } +} + +consoleMapNameToMapName(mapname) +{ + switch(mapname) + { + case "mp_abandon": + return "Carnival"; + case "mp_rundown": + return "Rundown"; + case "mp_afghan": + return "Afghan"; + case "mp_boneyard": + return "Scrapyard"; + case "mp_brecourt": + return "Wasteland"; + case "mp_cargoship": + return "Wetwork"; + case "mp_checkpoint": + return "Karachi"; + case "mp_compact": + return "Salvage"; + case "mp_complex": + return "Bailout"; + case "mp_crash": + return "Crash"; + case "mp_cross_fire": + return "Crossfire"; + case "mp_derail": + return "Derail"; + case "mp_estate": + return "Estate"; + case "mp_favela": + return "Favela"; + case "mp_fuel2": + return "Fuel"; + case "mp_highrise": + return "Highrise"; + case "mp_invasion": + return "Invasion"; + case "mp_killhouse": + return "Killhouse"; + case "mp_nightshift": + return "Skidrow"; + case "mp_nuked": + return "Nuketown"; + case "oilrig": + return "Oilrig"; + case "mp_quarry": + return "Quarry"; + case "mp_rust": + return "Rust"; + case "mp_storm": + return "Storm"; + case "mp_strike": + return "Strike"; + case "mp_subbase": + return "Subbase"; + case "mp_terminal": + return "Terminal"; + case "mp_trailerpark": + return "Trailer Park"; + case "mp_overgrown": + return "Overgrown"; + case "mp_underpass": + return "Underpass"; + case "mp_vacant": + return "Vacant"; + case "iw4_credits": + return "IW4 Test Map"; + case "airport": + return "Airport"; + case "co_hunted": + return "Hunted"; + case "invasion": + return "Burgertown"; + case "mp_bloc": + return "Bloc"; + case "mp_bog_sh": + return "Bog"; + case "contingency": + return "Contingency"; + case "gulag": + return "Gulag"; + case "so_ghillies": + return "Pripyat"; + case "ending": + return "Museum"; + case "af_chase": + return "Afghan Chase"; + case "af_caves": + return "Afghan Caves"; + case "arcadia": + return "Arcadia"; + case "boneyard": + return "Boneyard"; + case "cliffhanger": + return "Cliffhanger"; + case "dcburning": + return "DCBurning"; + case "dcemp": + return "DCEMP"; + case "downtown": + return "Downtown"; + case "estate": + return "EstateSP"; + case "favela": + return "FavelaSP"; + case "favela_escape": + return "Favela Escape"; + case "roadkill": + return "Roadkill"; + case "trainer": + return "TH3 PIT"; + case "so_bridge": + return "Bridge"; + case "dc_whitehouse": + return "Whitehouse"; + default: + return mapname; + } +} + +openSummaryOnMenuClose() +{ + self endon("disconnect"); + self endon("kill_menus_on_connect"); + level endon("intermission_finished"); + + for(;;) + { + self waittill("menuresponse", menu, response); + + if ( response == "back" && menu != game["menu_popup_summary"] ) + { + self closepopupMenu(); + self closeInGameMenu(); + wait 0.25; + self openPopupMenu( game["menu_popup_summary"] ); + continue; + } + } +} + +updateEndReasonText( winner ) +{ + if ( !level.teamBased ) + return true; + + if ( hitRoundLimit() ) + return &"MP_ROUND_LIMIT_REACHED"; + + if ( hitWinLimit() ) + return &"MP_SCORE_LIMIT_REACHED"; + + if ( winner == "axis" ) + return &"SPETSNAZ_WIN"; + else + return &"SAS_WIN"; +} + +estimatedTimeTillScoreLimit( team ) +{ + assert( isPlayer( self ) || isDefined( team ) ); + + scorePerMinute = getScorePerMinute( team ); + scoreRemaining = getScoreRemaining( team ); + + estimatedTimeLeft = 999999; + if ( scorePerMinute ) + estimatedTimeLeft = scoreRemaining / scorePerMinute; + + //println( "estimatedTimeLeft: " + estimatedTimeLeft ); + return estimatedTimeLeft; +} + +getScorePerMinute( team ) +{ + assert( isPlayer( self ) || isDefined( team ) ); + + scoreLimit = getWatchedDvar( "scorelimit" ); + timeLimit = getTimeLimit(); + minutesPassed = (getTimePassed() / (60*1000)) + 0.0001; + + if ( isPlayer( self ) ) + scorePerMinute = self.score / minutesPassed; + else + scorePerMinute = getTeamScore( team ) / minutesPassed; + + return scorePerMinute; +} + +getScoreRemaining( team ) +{ + assert( isPlayer( self ) || isDefined( team ) ); + + scoreLimit = getWatchedDvar( "scorelimit" ); + + if ( isPlayer( self ) ) + scoreRemaining = scoreLimit - self.score; + else + scoreRemaining = scoreLimit - getTeamScore( team ); + + return scoreRemaining; +} + +giveLastOnTeamWarning() +{ + self endon("death"); + self endon("disconnect"); + level endon( "game_ended" ); + + self waitTillRecoveredHealth( 3 ); + + otherTeam = getOtherTeam( self.pers["team"] ); + thread teamPlayerCardSplash( "callout_lastteammemberalive", self, self.pers["team"] ); + thread teamPlayerCardSplash( "callout_lastenemyalive", self, otherTeam ); + level notify ( "last_alive", self ); +} + +processLobbyData() +{ + curPlayer = 0; + foreach ( player in level.players ) + { + if ( !isDefined( player ) ) + continue; + + player.clientMatchDataId = curPlayer; + curPlayer++; + + // on PS3 cap long names + if ( level.ps3 && (player.name.size > level.MaxNameLength) ) + { + playerName = ""; + for ( i = 0; i < level.MaxNameLength-3; i++ ) + playerName += player.name[i]; + + playerName += "..."; + } + else + { + playerName = player.name; + } + + setClientMatchData( "players", player.clientMatchDataId, "xuid", playerName ); + } + + maps\mp\_awards::assignAwards(); + maps\mp\_scoreboard::processLobbyScoreboards(); + + sendClientMatchData(); +} + +getWinningTeam() +{ + if ( game["roundsWon"]["allies"] == game["roundsWon"]["axis"] ) + winner = "tie"; + else if ( game["roundsWon"]["axis"] > game["roundsWon"]["allies"] ) + winner = "axis"; + else + winner = "allies"; + + return winner; +} + +doFinalKillCam() +{ + level waittill ( "round_end_finished" ); + + winner = level.finalKillCam_winner; // we want to show the winner's final kill cam + delay = level.finalKillCam_delay[ winner ]; + victim = level.finalKillCam_victim[ winner ]; + attacker = level.finalKillCam_attacker[ winner ]; + attackerNum = level.finalKillCam_attackerNum[ winner ]; + killCamEntityIndex = level.finalKillCam_killCamEntityIndex[ winner ]; + killCamEntityStartTime = level.finalKillCam_killCamEntityStartTime[ winner ]; + sWeapon = level.finalKillCam_sWeapon[ winner ]; + deathTimeOffset = level.finalKillCam_deathTimeOffset[ winner ]; + psOffsetTime = level.finalKillCam_psOffsetTime[ winner ]; + timeRecorded = level.finalKillCam_timeRecorded[ winner ]/1000; + timeGameEnded = level.gameEndTime/1000; + + if( !isDefined( victim ) || !isDefined( attacker ) ) + return; + + if( !level.forceFinalKillcam || !level.allowFinalKillcam ) + return; + + // if the killcam happened longer than 15 seconds ago, don't show it + killCamBufferTime = 15; + killCamOffsetTime = timeGameEnded - timeRecorded; + + if( killCamOffsetTime > killCamBufferTime ) + return; + + level.showingFinalKillcam = true; + + if ( isDefined( attacker ) ) + { + maps\mp\_awards::addAwardWinner( "finalkill", attacker.clientid ); + attacker.finalKill = true; + } + + postDeathDelay = (( getTime() - victim.deathTime ) / 1000); + + foreach ( player in level.players ) + { + player closePopupMenu(); + player closeInGameMenu(); + + if( isDefined( level.nukeDetonated ) ) + player VisionSetNakedForPlayer( "mpnuke_aftermath", 0 ); + else + player VisionSetNakedForPlayer( getDvar( "mapname" ), 0 ); + + player.killcamentitylookat = victim getEntityNumber(); + + if ( (player != victim || (!isRoundBased() || isLastRound())) && player _hasPerk( "specialty_copycat" ) ) + player _unsetPerk( "specialty_copycat" ); + + player thread maps\mp\gametypes\_killcam::killcam( attackerNum, killcamentityindex, killcamentitystarttime, sWeapon, postDeathDelay + deathTimeOffset, psOffsetTime, 0, 10000, attacker, victim ); + } + + wait( 0.1 ); + + while ( maps\mp\gametypes\_damage::anyPlayersInKillcam() ) + wait( 0.05 ); + + level.showingFinalKillcam = false; +} + +recordFinalKillCam( delay, victim, attacker, attackerNum, killCamEntityIndex, killCamEntityStartTime, sWeapon, deathTimeOffset, psOffsetTime ) +{ + teams[0] = "none"; // none gets filled just in case we need something without a team or this is ffa + + if( level.teambased && IsDefined( attacker.team ) ) + teams[1] = attacker.team; // we want to save each team seperately so we can show the winning team's kill when applicable + + for( i = 0; i < teams.size; i++ ) + { + team = teams[i]; + + level.finalKillCam_delay[ team ] = delay; + level.finalKillCam_victim[ team ] = victim; + level.finalKillCam_attacker[ team ] = attacker; + level.finalKillCam_attackerNum[ team ] = attackerNum; + level.finalKillCam_killCamEntityIndex[ team ] = killCamEntityIndex; + level.finalKillCam_killCamEntityStartTime[ team ] = killCamEntityStartTime; + level.finalKillCam_sWeapon[ team ] = sWeapon; + level.finalKillCam_deathTimeOffset[ team ] = deathTimeOffset; + level.finalKillCam_psOffsetTime[ team ] = psOffsetTime; + level.finalKillCam_timeRecorded[ team ] = getTime(); + } +}