diff --git a/userraw/maps/mp/gametypes/_damage.gsc b/userraw/maps/mp/gametypes/_damage.gsc new file mode 100644 index 0000000..bb8ed1e --- /dev/null +++ b/userraw/maps/mp/gametypes/_damage.gsc @@ -0,0 +1,2496 @@ +#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; + + 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] ) ) // this is an optimization + { + switch ( sWeapon ) + { + case "ac130_105mm_mp": + case "ac130_40mm_mp": + case "ac130_25mm_mp": + if ( attacker.ac130LifeId == attacker.pers["deaths"] ) + attacker.pers["cur_kill_streak"]++; + break; + case "cobra_player_minigun_mp": + case "weapon_cobra_mk19_mp": + if ( attacker.heliRideLifeId == attacker.pers["deaths"] ) + 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": + if ( isDefined( eInflictor ) && isDefined( eInflictor.lifeId ) ) + killstreakLifeId = eInflictor.lifeId; + else + killstreakLifeId = attacker.lifeId; + + if ( killstreakLifeId == attacker.pers["deaths"] ) + attacker.pers["cur_kill_streak"]++; + break; + default: + attacker.pers["cur_kill_streak"]++; + break; + } + } + else + { + 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 ); + + 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" ) + 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"; + 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 ); + } + else if ( attacker == victim ) + { + handleSuicideDeath( sMeansOfDeath, sHitLoc ); + } + else if ( friendlyFire ) + { + if ( !isDefined( victim.nuked ) ) + { + handleFriendlyFireDeath( attacker ); + } + } + 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 + attackerNum = -1; + killcamentity = victim getKillcamEntity( attacker, eInflictor, sWeapon ); + 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; + #/ + + 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 ) +{ + if ( !isDefined( eInflictor ) ) + return undefined; + + 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 ( !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 + damager = eAttacker; + + if ( isDefined( damager) && damager != victim && iDamage > 0 ) + { + if ( iDFlags & level.iDFLAGS_STUN ) + typeHit = "stun"; + else if ( victim hasPerk( "specialty_armorvest", true ) || (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 ( (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.10; + 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..0bce1b3 --- /dev/null +++ b/userraw/maps/mp/gametypes/_gamelogic.gsc @@ -0,0 +1,2327 @@ +#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.showingFinalKillcam ) + return false; + + while ( level.showingFinalKillcam ) + wait ( 0.05 ); + + return true; +} + + +timeLimitClock_Intermission( waitTime ) +{ + setGameEndTime( getTime() + int(waitTime*1000) ); + clockObject = spawn( "script_origin", (0,0,0) ); + clockObject hide(); + + if ( waitTime >= 10.0 ) + wait ( waitTime - 10.0 ); + + for ( ;; ) + { + clockObject playSound( "ui_mp_timer_countdown" ); + wait ( 1.0 ); + } +} + + +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 ); +} + + +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; + + 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["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(); + + 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(); +} + + +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 ) +{ + // 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 ); + + 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 ); + + game["status"] = "halftime"; + level notify ( "restarting" ); + game["state"] = "playing"; + map_restart( true ); +} + + +endGame( winner, endReasonText, nukeDetonated ) +{ + 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.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 ); + + displayGameEnd( winner, endReasonText ); + + if ( level.showingFinalKillcam && wasOnlyRound() ) + { + 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 ) ); + } + + level notify( "exitLevel_called" ); + exitLevel( false ); +} + +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(); +} \ No newline at end of file diff --git a/userraw/maps/mp/gametypes/_rank.gsc b/userraw/maps/mp/gametypes/_rank.gsc new file mode 100644 index 0000000..4603887 --- /dev/null +++ b/userraw/maps/mp/gametypes/_rank.gsc @@ -0,0 +1,676 @@ +#include common_scripts\utility; +#include maps\mp\_utility; +#include maps\mp\gametypes\_hud_util; + + +init() +{ + level.scoreInfo = []; + level.xpScale = getDvarInt( "scr_xpscale" ); + + level.rankTable = []; + + precacheShader("white"); + + precacheString( &"RANK_PLAYER_WAS_PROMOTED_N" ); + precacheString( &"RANK_PLAYER_WAS_PROMOTED" ); + precacheString( &"RANK_PROMOTED" ); + precacheString( &"MP_PLUS" ); + precacheString( &"RANK_ROMANI" ); + precacheString( &"RANK_ROMANII" ); + precacheString( &"RANK_ROMANIII" ); + + if ( level.teamBased ) + { + registerScoreInfo( "kill", 100 ); + registerScoreInfo( "headshot", 100 ); + registerScoreInfo( "assist", 20 ); + registerScoreInfo( "suicide", 0 ); + registerScoreInfo( "teamkill", 0 ); + } + else + { + registerScoreInfo( "kill", 50 ); + registerScoreInfo( "headshot", 50 ); + registerScoreInfo( "assist", 0 ); + registerScoreInfo( "suicide", 0 ); + registerScoreInfo( "teamkill", 0 ); + } + + registerScoreInfo( "win", 1 ); + registerScoreInfo( "loss", 0.5 ); + registerScoreInfo( "tie", 0.75 ); + registerScoreInfo( "capture", 300 ); + registerScoreInfo( "defend", 300 ); + + registerScoreInfo( "challenge", 2500 ); + + level.maxRank = int(tableLookup( "mp/rankTable.csv", 0, "maxrank", 1 )); + level.maxPrestige = int(tableLookup( "mp/rankIconTable.csv", 0, "maxprestige", 1 )); + + pId = 0; + rId = 0; + for ( pId = 0; pId <= level.maxPrestige; pId++ ) + { + for ( rId = 0; rId <= level.maxRank; rId++ ) + precacheShader( tableLookup( "mp/rankIconTable.csv", 0, rId, pId+1 ) ); + } + + rankId = 0; + rankName = tableLookup( "mp/ranktable.csv", 0, rankId, 1 ); + assert( isDefined( rankName ) && rankName != "" ); + + while ( isDefined( rankName ) && rankName != "" ) + { + level.rankTable[rankId][1] = tableLookup( "mp/ranktable.csv", 0, rankId, 1 ); + level.rankTable[rankId][2] = tableLookup( "mp/ranktable.csv", 0, rankId, 2 ); + level.rankTable[rankId][3] = tableLookup( "mp/ranktable.csv", 0, rankId, 3 ); + level.rankTable[rankId][7] = tableLookup( "mp/ranktable.csv", 0, rankId, 7 ); + + precacheString( tableLookupIString( "mp/ranktable.csv", 0, rankId, 16 ) ); + + rankId++; + rankName = tableLookup( "mp/ranktable.csv", 0, rankId, 1 ); + } + + maps\mp\gametypes\_missions::buildChallegeInfo(); + + level thread patientZeroWaiter(); + + level thread onPlayerConnect(); +} + +patientZeroWaiter() +{ + level endon( "game_ended" ); + + level waittill( "prematch_over" ); + + if ( !matchMakingGame() ) + { + if ( getDvar( "mapname" ) == "mp_rust" && randomInt( 1000 ) == 999 ) + level.patientZeroName = level.players[0].name; + } + else + { + if ( getDvar( "scr_patientZero" ) != "" ) + level.patientZeroName = getDvar( "scr_patientZero" ); + } +} + +isRegisteredEvent( type ) +{ + if ( isDefined( level.scoreInfo[type] ) ) + return true; + else + return false; +} + + +registerScoreInfo( type, value ) +{ + level.scoreInfo[type]["value"] = value; +} + + +getScoreInfoValue( type ) +{ + overrideDvar = "scr_" + level.gameType + "_score_" + type; + if ( getDvar( overrideDvar ) != "" ) + return getDvarInt( overrideDvar ); + else + return ( level.scoreInfo[type]["value"] ); +} + + +getScoreInfoLabel( type ) +{ + return ( level.scoreInfo[type]["label"] ); +} + + +getRankInfoMinXP( rankId ) +{ + return int(level.rankTable[rankId][2]); +} + + +getRankInfoXPAmt( rankId ) +{ + return int(level.rankTable[rankId][3]); +} + + +getRankInfoMaxXp( rankId ) +{ + return int(level.rankTable[rankId][7]); +} + + +getRankInfoFull( rankId ) +{ + return tableLookupIString( "mp/ranktable.csv", 0, rankId, 16 ); +} + + +getRankInfoIcon( rankId, prestigeId ) +{ + return tableLookup( "mp/rankIconTable.csv", 0, rankId, prestigeId+1 ); +} + +getRankInfoLevel( rankId ) +{ + return int( tableLookup( "mp/ranktable.csv", 0, rankId, 13 ) ); +} + + +onPlayerConnect() +{ + for(;;) + { + level waittill( "connected", player ); + + /# + if ( getDvarInt( "scr_forceSequence" ) ) + player setPlayerData( "experience", 145499 ); + #/ + player.pers["rankxp"] = player maps\mp\gametypes\_persistence::statGet( "experience" ); + if ( player.pers["rankxp"] < 0 ) // paranoid defensive + player.pers["rankxp"] = 0; + + rankId = player getRankForXp( player getRankXP() ); + player.pers[ "rank" ] = rankId; + player.pers[ "participation" ] = 0; + + player.xpUpdateTotal = 0; + player.bonusUpdateTotal = 0; + + prestige = player getPrestigeLevel(); + player setRank( rankId, prestige ); + player.pers["prestige"] = prestige; + + player.postGamePromotion = false; + if ( !isDefined( player.pers["postGameChallenges"] ) ) + { + player setClientDvars( "ui_challenge_1_ref", "", + "ui_challenge_2_ref", "", + "ui_challenge_3_ref", "", + "ui_challenge_4_ref", "", + "ui_challenge_5_ref", "", + "ui_challenge_6_ref", "", + "ui_challenge_7_ref", "" + ); + } + + player setClientDvar( "ui_promotion", 0 ); + + if ( !isDefined( player.pers["summary"] ) ) + { + player.pers["summary"] = []; + player.pers["summary"]["xp"] = 0; + player.pers["summary"]["score"] = 0; + player.pers["summary"]["challenge"] = 0; + player.pers["summary"]["match"] = 0; + player.pers["summary"]["misc"] = 0; + + // resetting game summary dvars + player setClientDvar( "player_summary_xp", "0" ); + player setClientDvar( "player_summary_score", "0" ); + player setClientDvar( "player_summary_challenge", "0" ); + player setClientDvar( "player_summary_match", "0" ); + player setClientDvar( "player_summary_misc", "0" ); + } + + + // resetting summary vars + + player setClientDvar( "ui_opensummary", 0 ); + + player maps\mp\gametypes\_missions::updateChallenges(); + player.explosiveKills[0] = 0; + player.xpGains = []; + + player.hud_scorePopup = newClientHudElem( player ); + player.hud_scorePopup.horzAlign = "center"; + player.hud_scorePopup.vertAlign = "middle"; + player.hud_scorePopup.alignX = "center"; + player.hud_scorePopup.alignY = "middle"; + player.hud_scorePopup.x = 0; + if ( level.splitScreen ) + player.hud_scorePopup.y = -40; + else + player.hud_scorePopup.y = -60; + player.hud_scorePopup.font = "hudbig"; + player.hud_scorePopup.fontscale = 0.75; + player.hud_scorePopup.archived = false; + player.hud_scorePopup.color = (0.5,0.5,0.5); + player.hud_scorePopup.sort = 10000; + player.hud_scorePopup maps\mp\gametypes\_hud::fontPulseInit( 3.0 ); + + player thread onPlayerSpawned(); + player thread onJoinedTeam(); + player thread onJoinedSpectators(); + } +} + + +onJoinedTeam() +{ + self endon("disconnect"); + + for(;;) + { + self waittill( "joined_team" ); + self thread removeRankHUD(); + } +} + + +onJoinedSpectators() +{ + self endon("disconnect"); + + for(;;) + { + self waittill( "joined_spectators" ); + self thread removeRankHUD(); + } +} + + +onPlayerSpawned() +{ + self endon("disconnect"); + + for(;;) + { + self waittill("spawned_player"); + } +} + + +roundUp( floatVal ) +{ + if ( int( floatVal ) != floatVal ) + return int( floatVal+1 ); + else + return int( floatVal ); +} + + +giveRankXP( type, value ) +{ + self endon("disconnect"); + + lootType = "none"; + + if ( !self rankingEnabled() ) + return; + + if ( level.teamBased && (!level.teamCount["allies"] || !level.teamCount["axis"]) ) + return; + else if ( !level.teamBased && (level.teamCount["allies"] + level.teamCount["axis"] < 2) ) + return; + + if ( !isDefined( value ) ) + value = getScoreInfoValue( type ); + + if ( !isDefined( self.xpGains[type] ) ) + self.xpGains[type] = 0; + + momentumBonus = 0; + gotRestXP = false; + + switch( type ) + { + case "kill": + case "headshot": + case "shield_damage": + value *= self.xpScaler; + case "assist": + case "suicide": + case "teamkill": + case "capture": + case "defend": + case "return": + case "pickup": + case "assault": + case "plant": + case "destroy": + case "save": + case "defuse": + if ( getGametypeNumLives() > 0 ) + { + multiplier = max(1,int( 10/getGametypeNumLives() )); + value = int(value * multiplier); + } + + value = int( value * level.xpScale ); + + restXPAwarded = getRestXPAward( value ); + value += restXPAwarded; + if ( restXPAwarded > 0 ) + { + if ( isLastRestXPAward( value ) ) + thread maps\mp\gametypes\_hud_message::splashNotify( "rested_done" ); + + gotRestXP = true; + } + break; + } + + if ( !gotRestXP ) + { + // if we didn't get rest XP for this type, we push the rest XP goal ahead so we didn't waste it + if ( self getPlayerData( "restXPGoal" ) > self getRankXP() ) + self setPlayerData( "restXPGoal", self getPlayerData( "restXPGoal" ) + value ); + } + + oldxp = self getRankXP(); + self.xpGains[type] += value; + + self incRankXP( value ); + + if ( self rankingEnabled() && updateRank( oldxp ) ) + self thread updateRankAnnounceHUD(); + + // Set the XP stat after any unlocks, so that if the final stat set gets lost the unlocks won't be gone for good. + self syncXPStat(); + + if ( !level.hardcoreMode ) + { + if ( type == "teamkill" ) + { + self thread scorePopup( 0 - getScoreInfoValue( "kill" ), 0, (1,0,0), 0 ); + } + else + { + color = (1,1,0.5); + if ( gotRestXP ) + color = (1,.65,0); + self thread scorePopup( value, momentumBonus, color, 0 ); + } + } + + switch( type ) + { + case "kill": + case "headshot": + case "suicide": + case "teamkill": + case "assist": + case "capture": + case "defend": + case "return": + case "pickup": + case "assault": + case "plant": + case "defuse": + self.pers["summary"]["score"] += value; + self.pers["summary"]["xp"] += value; + break; + + case "win": + case "loss": + case "tie": + self.pers["summary"]["match"] += value; + self.pers["summary"]["xp"] += value; + break; + + case "challenge": + self.pers["summary"]["challenge"] += value; + self.pers["summary"]["xp"] += value; + break; + + default: + self.pers["summary"]["misc"] += value; //keeps track of ungrouped match xp reward + self.pers["summary"]["match"] += value; + self.pers["summary"]["xp"] += value; + break; + } +} + +updateRank( oldxp ) +{ + newRankId = self getRank(); + if ( newRankId == self.pers["rank"] ) + return false; + + oldRank = self.pers["rank"]; + rankId = self.pers["rank"]; + self.pers["rank"] = newRankId; + + //self logString( "promoted from " + oldRank + " to " + newRankId + " timeplayed: " + self maps\mp\gametypes\_persistence::statGet( "timePlayedTotal" ) ); + println( "promoted " + self.name + " from rank " + oldRank + " to " + newRankId + ". Experience went from " + oldxp + " to " + self getRankXP() + "." ); + + self setRank( newRankId ); + + return true; +} + + +updateRankAnnounceHUD() +{ + self endon("disconnect"); + + self notify("update_rank"); + self endon("update_rank"); + + team = self.pers["team"]; + if ( !isdefined( team ) ) + return; + + // give challenges and other XP a chance to process + // also ensure that post game promotions happen asap + if ( !levelFlag( "game_over" ) ) + level waittill_notify_or_timeout( "game_over", 0.25 ); + + + newRankName = self getRankInfoFull( self.pers["rank"] ); + rank_char = level.rankTable[self.pers["rank"]][1]; + subRank = int(rank_char[rank_char.size-1]); + + thread maps\mp\gametypes\_hud_message::promotionSplashNotify(); + + if ( subRank > 1 ) + return; + + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[i]; + playerteam = player.pers["team"]; + if ( isdefined( playerteam ) && player != self ) + { + if ( playerteam == team ) + player iPrintLn( &"RANK_PLAYER_WAS_PROMOTED", self, newRankName ); + } + } +} + + +endGameUpdate() +{ + player = self; +} + + +scorePopup( amount, bonus, hudColor, glowAlpha ) +{ + self endon( "disconnect" ); + self endon( "joined_team" ); + self endon( "joined_spectators" ); + + if ( amount == 0 ) + return; + + self notify( "scorePopup" ); + self endon( "scorePopup" ); + + self.xpUpdateTotal += amount; + self.bonusUpdateTotal += bonus; + + wait ( 0.05 ); + + if ( self.xpUpdateTotal < 0 ) + self.hud_scorePopup.label = &""; + else + self.hud_scorePopup.label = &"MP_PLUS"; + + self.hud_scorePopup.color = hudColor; + self.hud_scorePopup.glowColor = hudColor; + self.hud_scorePopup.glowAlpha = glowAlpha; + + self.hud_scorePopup setValue(self.xpUpdateTotal); + self.hud_scorePopup.alpha = 0.85; + self.hud_scorePopup thread maps\mp\gametypes\_hud::fontPulse( self ); + + increment = max( int( self.bonusUpdateTotal / 20 ), 1 ); + + if ( self.bonusUpdateTotal ) + { + while ( self.bonusUpdateTotal > 0 ) + { + self.xpUpdateTotal += min( self.bonusUpdateTotal, increment ); + self.bonusUpdateTotal -= min( self.bonusUpdateTotal, increment ); + + self.hud_scorePopup setValue( self.xpUpdateTotal ); + + wait ( 0.05 ); + } + } + else + { + wait ( 1.0 ); + } + + self.hud_scorePopup fadeOverTime( 0.75 ); + self.hud_scorePopup.alpha = 0; + + self.xpUpdateTotal = 0; +} + +removeRankHUD() +{ + self.hud_scorePopup.alpha = 0; +} + +getRank() +{ + rankXp = self.pers["rankxp"]; + rankId = self.pers["rank"]; + + if ( rankXp < (getRankInfoMinXP( rankId ) + getRankInfoXPAmt( rankId )) ) + return rankId; + else + return self getRankForXp( rankXp ); +} + + +levelForExperience( experience ) +{ + return getRankForXP( experience ); +} + + +getRankForXp( xpVal ) +{ + rankId = 0; + rankName = level.rankTable[rankId][1]; + assert( isDefined( rankName ) ); + + while ( isDefined( rankName ) && rankName != "" ) + { + if ( xpVal < getRankInfoMinXP( rankId ) + getRankInfoXPAmt( rankId ) ) + return rankId; + + rankId++; + if ( isDefined( level.rankTable[rankId] ) ) + rankName = level.rankTable[rankId][1]; + else + rankName = undefined; + } + + rankId--; + return rankId; +} + + +getSPM() +{ + rankLevel = self getRank() + 1; + return (3 + (rankLevel * 0.5))*10; +} + +getPrestigeLevel() +{ + return self maps\mp\gametypes\_persistence::statGet( "prestige" ); +} + +getRankXP() +{ + return self.pers["rankxp"]; +} + +incRankXP( amount ) +{ + if ( !self rankingEnabled() ) + return; + + if ( isDefined( self.isCheater ) ) + return; + + xp = self getRankXP(); + newXp = (xp + amount); + + if ( self.pers["rank"] == level.maxRank && newXp >= getRankInfoMaxXP( level.maxRank ) ) + newXp = getRankInfoMaxXP( level.maxRank ); + + self.pers["rankxp"] = newXp; +} + +getRestXPAward( baseXP ) +{ + if ( !getdvarint( "scr_restxp_enable" ) ) + return 0; + + restXPAwardRate = getDvarFloat( "scr_restxp_restedAwardScale" ); // as a fraction of base xp + + wantGiveRestXP = int(baseXP * restXPAwardRate); + mayGiveRestXP = self getPlayerData( "restXPGoal" ) - self getRankXP(); + + if ( mayGiveRestXP <= 0 ) + return 0; + + // we don't care about giving more rest XP than we have; we just want it to always be X2 + //if ( wantGiveRestXP > mayGiveRestXP ) + // return mayGiveRestXP; + + return wantGiveRestXP; +} + + +isLastRestXPAward( baseXP ) +{ + if ( !getdvarint( "scr_restxp_enable" ) ) + return false; + + restXPAwardRate = getDvarFloat( "scr_restxp_restedAwardScale" ); // as a fraction of base xp + + wantGiveRestXP = int(baseXP * restXPAwardRate); + mayGiveRestXP = self getPlayerData( "restXPGoal" ) - self getRankXP(); + + if ( mayGiveRestXP <= 0 ) + return false; + + if ( wantGiveRestXP >= mayGiveRestXP ) + return true; + + return false; +} + +syncXPStat() +{ + xp = self getRankXP(); + + self maps\mp\gametypes\_persistence::statSet( "experience", xp ); +} diff --git a/userraw/maps/mp/gametypes/_weapons.gsc b/userraw/maps/mp/gametypes/_weapons.gsc new file mode 100644 index 0000000..f00e142 --- /dev/null +++ b/userraw/maps/mp/gametypes/_weapons.gsc @@ -0,0 +1,2703 @@ +#include common_scripts\utility; +#include maps\mp\_utility; + + +attachmentGroup( attachmentName ) +{ + return tableLookup( "mp/attachmentTable.csv", 4, attachmentName, 2 ); +} + +getAttachmentList() +{ + attachmentList = []; + + index = 0; + attachmentName = tableLookup( "mp/attachmentTable.csv", 9, index, 4 ); + + while ( attachmentName != "" ) + { + attachmentList[attachmentList.size] = attachmentName; + + index++; + attachmentName = tableLookup( "mp/attachmentTable.csv", 9, index, 4 ); + } + + return alphabetize( attachmentList ); +} + +init() +{ + level.scavenger_altmode = true; + level.scavenger_secondary = true; + + // 0 is not valid + level.maxPerPlayerExplosives = max( getIntProperty( "scr_maxPerPlayerExplosives", 2 ), 1 ); + level.riotShieldXPBullets = getIntProperty( "scr_riotShieldXPBullets", 15 ); + + switch ( getIntProperty( "perk_scavengerMode", 0 ) ) + { + case 1: // disable altmode + level.scavenger_altmode = false; + break; + + case 2: // disable secondary + level.scavenger_secondary = false; + break; + + case 3: // disable altmode and secondary + level.scavenger_altmode = false; + level.scavenger_secondary = false; + break; + } + + attachmentList = getAttachmentList(); + + // assigns weapons with stat numbers from 0-149 + // attachments are now shown here, they are per weapon settings instead + + max_weapon_num = 149; + + level.weaponList = []; + for( weaponId = 0; weaponId <= max_weapon_num; weaponId++ ) + { + weapon_name = tablelookup( "mp/statstable.csv", 0, weaponId, 4 ); + if( weapon_name == "" ) + continue; + + if ( !isSubStr( tableLookup( "mp/statsTable.csv", 0, weaponId, 2 ), "weapon_" ) ) + continue; + + level.weaponList[level.weaponList.size] = weapon_name + "_mp"; + /# + if ( getDvar( "scr_dump_weapon_assets" ) != "" ) + { + printLn( "" ); + printLn( "// " + weapon_name + " real assets" ); + printLn( "weapon,mp/" + weapon_name + "_mp" ); + } + #/ + + // the alphabetize function is slow so we try not to do it for every weapon/attachment combo; a code solution would be better. + attachmentNames = []; + for ( innerLoopCount = 0; innerLoopCount < 10; innerLoopCount++ ) + { + // generating attachment combinations + attachmentName = tablelookup( "mp/statStable.csv", 0, weaponId, innerLoopCount + 11 ); + + if( attachmentName == "" ) + break; + + attachmentNames[attachmentName] = true; + } + + // generate an alphabetized attachment list + attachments = []; + foreach ( attachmentName in attachmentList ) + { + if ( !isDefined( attachmentNames[attachmentName] ) ) + continue; + + level.weaponList[level.weaponList.size] = weapon_name + "_" + attachmentName + "_mp"; + attachments[attachments.size] = attachmentName; + /# + if ( getDvar( "scr_dump_weapon_assets" ) != "" ) + println( "weapon,mp/" + weapon_name + "_" + attachmentName + "_mp" ); + #/ + } + + attachmentCombos = []; + for ( i = 0; i < (attachments.size - 1); i++ ) + { + colIndex = tableLookupRowNum( "mp/attachmentCombos.csv", 0, attachments[i] ); + for ( j = i + 1; j < attachments.size; j++ ) + { + if ( tableLookup( "mp/attachmentCombos.csv", 0, attachments[j], colIndex ) == "no" ) + continue; + + attachmentCombos[attachmentCombos.size] = attachments[i] + "_" + attachments[j]; + } + } + + /# + if ( getDvar( "scr_dump_weapon_assets" ) != "" && attachmentCombos.size ) + println( "// " + weapon_name + " virtual assets" ); + #/ + + foreach ( combo in attachmentCombos ) + { + /# + if ( getDvar( "scr_dump_weapon_assets" ) != "" ) + println( "weapon,mp/" + weapon_name + "_" + combo + "_mp" ); + #/ + + level.weaponList[level.weaponList.size] = weapon_name + "_" + combo + "_mp"; + } + } + + foreach ( weaponName in level.weaponList ) + { + precacheItem( weaponName ); + + /# + if ( getDvar( "scr_dump_weapon_assets" ) != "" ) + { + altWeapon = weaponAltWeaponName( weaponName ); + if ( altWeapon != "none" ) + println( "weapon,mp/" + altWeapon ); + } + #/ + } + + precacheItem( "flare_mp" ); + precacheItem( "scavenger_bag_mp" ); + precacheItem( "frag_grenade_short_mp" ); + precacheItem( "destructible_car" ); + + precacheShellShock( "default" ); + precacheShellShock( "concussion_grenade_mp" ); + thread maps\mp\_flashgrenades::main(); + thread maps\mp\_entityheadicons::init(); + + claymoreDetectionConeAngle = 70; + level.claymoreDetectionDot = cos( claymoreDetectionConeAngle ); + level.claymoreDetectionMinDist = 20; + level.claymoreDetectionGracePeriod = .75; + level.claymoreDetonateRadius = 192; + + // this should move to _stinger.gsc + level.stingerFXid = loadfx ("explosions/aerial_explosion_large"); + + // generating weapon type arrays which classifies the weapon as primary (back stow), pistol, or inventory (side pack stow) + // using mp/statstable.csv's weapon grouping data ( numbering 0 - 149 ) + level.primary_weapon_array = []; + level.side_arm_array = []; + level.grenade_array = []; + level.inventory_array = []; + level.stow_priority_model_array = []; + level.stow_offset_array = []; + + max_weapon_num = 149; + for( i = 0; i < max_weapon_num; i++ ) + { + weapon = tableLookup( "mp/statsTable.csv", 0, i, 4 ); + stow_model = tableLookup( "mp/statsTable.csv", 0, i, 9 ); + + if ( stow_model == "" ) + continue; + + precacheModel( stow_model ); + + if ( isSubStr( stow_model, "weapon_stow_" ) ) + level.stow_offset_array[ weapon ] = stow_model; + else + level.stow_priority_model_array[ weapon + "_mp" ] = stow_model; + } + + precacheModel( "weapon_claymore_bombsquad" ); + precacheModel( "weapon_c4_bombsquad" ); + precacheModel( "projectile_m67fraggrenade_bombsquad" ); + precacheModel( "projectile_semtex_grenade_bombsquad" ); + precacheModel( "weapon_light_stick_tactical_bombsquad" ); + + level.killStreakSpecialCaseWeapons = []; + level.killStreakSpecialCaseWeapons["cobra_player_minigun_mp"] = true; + level.killStreakSpecialCaseWeapons["artillery_mp"] = true; + level.killStreakSpecialCaseWeapons["stealth_bomb_mp"] = true; + level.killStreakSpecialCaseWeapons["pavelow_minigun_mp"] = true; + level.killStreakSpecialCaseWeapons["sentry_minigun_mp"] = true; + level.killStreakSpecialCaseWeapons["harrier_20mm_mp"] = true; + level.killStreakSpecialCaseWeapons["ac130_105mm_mp"] = true; + level.killStreakSpecialCaseWeapons["ac130_40mm_mp"] = true; + level.killStreakSpecialCaseWeapons["ac130_25mm_mp"] = true; + level.killStreakSpecialCaseWeapons["remotemissile_projectile_mp"] = true; + level.killStreakSpecialCaseWeapons["cobra_20mm_mp"] = true; + level.killStreakSpecialCaseWeapons["sentry_minigun_mp"] = true; + + + level thread onPlayerConnect(); + + level.c4explodethisframe = false; + + array_thread( getEntArray( "misc_turret", "classname" ), ::turret_monitorUse ); + +// thread dumpIt(); +} + + +dumpIt() +{ + + wait ( 5.0 ); + /# + max_weapon_num = 149; + + for( weaponId = 0; weaponId <= max_weapon_num; weaponId++ ) + { + weapon_name = tablelookup( "mp/statstable.csv", 0, weaponId, 4 ); + if( weapon_name == "" ) + continue; + + if ( !isSubStr( tableLookup( "mp/statsTable.csv", 0, weaponId, 2 ), "weapon_" ) ) + continue; + + if ( getDvar( "scr_dump_weapon_challenges" ) != "" ) + { + /* + sharpshooter + marksman + veteran + expert + master + */ + + weaponLStringName = tableLookup( "mp/statsTable.csv", 0, weaponId, 3 ); + weaponRealName = tableLookupIString( "mp/statsTable.csv", 0, weaponId, 3 ); + + prefix = "WEAPON_"; + weaponCapsName = getSubStr( weaponLStringName, prefix.size, weaponLStringName.size ); + + weaponGroup = tableLookup( "mp/statsTable.csv", 0, weaponId, 2 ); + + weaponGroupSuffix = getSubStr( weaponGroup, prefix.size, weaponGroup.size ); + + /* + iprintln( "REFERENCE TITLE_" + weaponCapsName + "_SHARPSHOOTER" ); + iprintln( "LANG_ENGLISH ", weaponRealName, ": Sharpshooter" ); + iprintln( "" ); + iprintln( "REFERENCE TITLE_" + weaponCapsName + "_MARKSMAN" ); + iprintln( "LANG_ENGLISH ", weaponRealName, ": Marksman" ); + iprintln( "" ); + iprintln( "REFERENCE TITLE_" + weaponCapsName + "_VETERAN" ); + iprintln( "LANG_ENGLISH ", weaponRealName, ": Veteran" ); + iprintln( "" ); + iprintln( "REFERENCE TITLE_" + weaponCapsName + "_EXPERT" ); + iprintln( "LANG_ENGLISH ", weaponRealName, ": Expert" ); + iprintln( "" ); + iprintln( "REFERENCE TITLE_" + weaponCapsName + "_Master" ); + iprintln( "LANG_ENGLISH ", weaponRealName, ": Master" ); + */ + + iprintln( "cardtitle_" + weapon_name + "_sharpshooter,PLAYERCARDS_TITLE_" + weaponCapsName + "_SHARPSHOOTER,cardtitle_" + weaponGroupSuffix + "_sharpshooter,1,1,1" ); + iprintln( "cardtitle_" + weapon_name + "_marksman,PLAYERCARDS_TITLE_" + weaponCapsName + "_MARKSMAN,cardtitle_" + weaponGroupSuffix + "_marksman,1,1,1" ); + iprintln( "cardtitle_" + weapon_name + "_veteran,PLAYERCARDS_TITLE_" + weaponCapsName + "_VETERAN,cardtitle_" + weaponGroupSuffix + "_veteran,1,1,1" ); + iprintln( "cardtitle_" + weapon_name + "_expert,PLAYERCARDS_TITLE_" + weaponCapsName + "_EXPERT,cardtitle_" + weaponGroupSuffix + "_expert,1,1,1" ); + iprintln( "cardtitle_" + weapon_name + "_master,PLAYERCARDS_TITLE_" + weaponCapsName + "_MASTER,cardtitle_" + weaponGroupSuffix + "_master,1,1,1" ); + + wait ( 0.05 ); + } + } + #/ +} + +bombSquadWaiter() +{ + self endon ( "disconnect" ); + + for ( ;; ) + { + self waittill ( "grenade_fire", weaponEnt, weaponName ); + + team = level.otherTeam[self.team]; + + if ( weaponName == "c4_mp" ) + weaponEnt thread createBombSquadModel( "weapon_c4_bombsquad", "tag_origin", team, self ); + else if ( weaponName == "claymore_mp" ) + weaponEnt thread createBombSquadModel( "weapon_claymore_bombsquad", "tag_origin", team, self ); + else if ( weaponName == "frag_grenade_mp" ) + weaponEnt thread createBombSquadModel( "projectile_m67fraggrenade_bombsquad", "tag_weapon", team, self ); + else if ( weaponName == "frag_grenade_short_mp" ) + weaponEnt thread createBombSquadModel( "projectile_m67fraggrenade_bombsquad", "tag_weapon", team, self ); + else if ( weaponName == "semtex_mp" ) + weaponEnt thread createBombSquadModel( "projectile_semtex_grenade_bombsquad", "tag_weapon", team, self ); + } +} + + +createBombSquadModel( modelName, tagName, teamName, owner ) +{ + bombSquadModel = spawn( "script_model", (0,0,0) ); + bombSquadModel hide(); + wait ( 0.05 ); + + if (!isDefined( self ) ) //grenade model may not be around if picked up + return; + + bombSquadModel thread bombSquadVisibilityUpdater( teamName, owner ); + bombSquadModel setModel( modelName ); + bombSquadModel linkTo( self, tagName, (0,0,0), (0,0,0) ); + bombSquadModel SetContents( 0 ); + + self waittill ( "death" ); + + bombSquadModel delete(); +} + + +bombSquadVisibilityUpdater( teamName, owner ) +{ + self endon ( "death" ); + + foreach ( player in level.players ) + { + if ( level.teamBased ) + { + if ( player.team == teamName && player _hasPerk( "specialty_detectexplosive" ) ) + self showToPlayer( player ); + } + else + { + if ( isDefined( owner ) && player == owner ) + continue; + + if ( !player _hasPerk( "specialty_detectexplosive" ) ) + continue; + + self showToPlayer( player ); + } + } + + for ( ;; ) + { + level waittill_any( "joined_team", "player_spawned", "changed_kit" ); + + self hide(); + + foreach ( player in level.players ) + { + if ( level.teamBased ) + { + if ( player.team == teamName && player _hasPerk( "specialty_detectexplosive" ) ) + self showToPlayer( player ); + } + else + { + if ( isDefined( owner ) && player == owner ) + continue; + + if ( !player _hasPerk( "specialty_detectexplosive" ) ) + continue; + + self showToPlayer( player ); + } + } + } +} + + +onPlayerConnect() +{ + for(;;) + { + level waittill("connected", player); + + player.hits = 0; + player.hasDoneCombat = false; + + player KC_RegWeaponForFXRemoval( "remotemissile_projectile_mp" ); + + player thread onPlayerSpawned(); + player thread bombSquadWaiter(); + } +} + + +onPlayerSpawned() +{ + self endon("disconnect"); + + for(;;) + { + self waittill("spawned_player"); + + self.currentWeaponAtSpawn = self getCurrentWeapon(); // optimization so these threads we start don't have to call it. + + self.empEndTime = 0; + self.concussionEndTime = 0; + self.hasDoneCombat = false; + self thread watchWeaponUsage(); + self thread watchGrenadeUsage(); + self thread watchWeaponChange(); + self thread watchStingerUsage(); + self thread watchJavelinUsage(); + self thread watchMissileUsage(); + self thread watchSentryUsage(); + self thread watchWeaponReload(); + self thread maps\mp\gametypes\_class::trackRiotShield(); + + self.lastHitTime = []; + + self.droppedDeathWeapon = undefined; + self.tookWeaponFrom = []; + + self thread updateStowedWeapon(); + + self thread updateSavedLastWeapon(); + + if ( self hasWeapon( "semtex_mp" ) ) + self thread monitorSemtex(); + + self.currentWeaponAtSpawn = undefined; + } +} + +WatchStingerUsage() +{ + self maps\mp\_stinger::StingerUsageLoop(); +} + + +WatchJavelinUsage() +{ + self maps\mp\_javelin::JavelinUsageLoop(); +} + +watchWeaponChange() +{ + self endon("death"); + self endon("disconnect"); + + self thread watchStartWeaponChange(); + self.lastDroppableWeapon = self.currentWeaponAtSpawn; + self.hitsThisMag = []; + + weapon = self getCurrentWeapon(); + + if ( isCACPrimaryWeapon( weapon ) && !isDefined( self.hitsThisMag[ weapon ] ) ) + self.hitsThisMag[ weapon ] = weaponClipSize( weapon ); + + self.bothBarrels = undefined; + + if ( isSubStr( weapon, "ranger" ) ) + self thread watchRangerUsage( weapon ); + + while(1) + { + self waittill( "weapon_change", newWeapon ); + + tokedNewWeapon = StrTok( newWeapon, "_" ); + + self.bothBarrels = undefined; + + if ( isSubStr( newWeapon, "ranger" ) ) + self thread watchRangerUsage( newWeapon ); + + if ( tokedNewWeapon[0] == "gl" || ( tokedNewWeapon.size > 2 && tokedNewWeapon[2] == "attach" ) ) + newWeapon = self getCurrentPrimaryWeapon(); + + if ( newWeapon != "none" ) + { + if ( isCACPrimaryWeapon( newWeapon ) && !isDefined( self.hitsThisMag[ newWeapon ] ) ) + self.hitsThisMag[ newWeapon ] = weaponClipSize( newWeapon ); + } + self.changingWeapon = undefined; + if ( mayDropWeapon( newWeapon ) ) + self.lastDroppableWeapon = newWeapon; + } +} + + +watchStartWeaponChange() +{ + self endon("death"); + self endon("disconnect"); + self.changingWeapon = undefined; + + while(1) + { + self waittill( "weapon_switch_started", newWeapon ); + self.changingWeapon = newWeapon; + } +} + +watchWeaponReload() +{ + self endon("death"); + self endon("disconnect"); + + for ( ;; ) + { + self waittill( "reload" ); + + weaponName = self getCurrentWeapon(); + + self.bothBarrels = undefined; + + if ( !isSubStr( weaponName, "ranger" ) ) + continue; + + self thread watchRangerUsage( weaponName ); + } +} + + +watchRangerUsage( rangerName ) +{ + rightAmmo = self getWeaponAmmoClip( rangerName, "right" ); + leftAmmo = self getWeaponAmmoClip( rangerName, "left" ); + + self endon ( "reload" ); + self endon ( "weapon_change" ); + + for ( ;; ) + { + self waittill ( "weapon_fired", weaponName ); + + if ( weaponName != rangerName ) + continue; + + self.bothBarrels = undefined; + + if ( isSubStr( rangerName, "akimbo" ) ) + { + newLeftAmmo = self getWeaponAmmoClip( rangerName, "left" ); + newRightAmmo = self getWeaponAmmoClip( rangerName, "right" ); + + if ( leftAmmo != newLeftAmmo && rightAmmo != newRightAmmo ) + self.bothBarrels = true; + + if ( !newLeftAmmo || !newRightAmmo ) + return; + + + leftAmmo = newLeftAmmo; + rightAmmo = newRightAmmo; + } + else if ( rightAmmo == 2 && !self getWeaponAmmoClip( rangerName, "right" ) ) + { + self.bothBarrels = true; + return; + } + } +} + + +isHackWeapon( weapon ) +{ + if ( weapon == "radar_mp" || weapon == "airstrike_mp" || weapon == "helicopter_mp" ) + return true; + if ( weapon == "briefcase_bomb_mp" ) + return true; + return false; +} + + +mayDropWeapon( weapon ) +{ + if ( weapon == "none" ) + return false; + + if ( isSubStr( weapon, "ac130" ) ) + return false; + + invType = WeaponInventoryType( weapon ); + if ( invType != "primary" ) + return false; + + return true; +} + +dropWeaponForDeath( attacker ) +{ + weapon = self.lastDroppableWeapon; + + if ( isdefined( self.droppedDeathWeapon ) ) + return; + + if ( level.inGracePeriod ) + return; + + if ( !isdefined( weapon ) ) + { + /# + if ( getdvar("scr_dropdebug") == "1" ) + println( "didn't drop weapon: not defined" ); + #/ + return; + } + + if ( weapon == "none" ) + { + /# + if ( getdvar("scr_dropdebug") == "1" ) + println( "didn't drop weapon: weapon == none" ); + #/ + return; + } + + if ( !self hasWeapon( weapon ) ) + { + /# + if ( getdvar("scr_dropdebug") == "1" ) + println( "didn't drop weapon: don't have it anymore (" + weapon + ")" ); + #/ + return; + } + + if ( weapon != "riotshield_mp" ) + { + if ( !(self AnyAmmoForWeaponModes( weapon )) ) + { + /# + if ( getdvar("scr_dropdebug") == "1" ) + println( "didn't drop weapon: no ammo for weapon modes" ); + #/ + return; + } + + clipAmmoR = self GetWeaponAmmoClip( weapon, "right" ); + clipAmmoL = self GetWeaponAmmoClip( weapon, "left" ); + if ( !clipAmmoR && !clipAmmoL ) + { + /# + if ( getdvar("scr_dropdebug") == "1" ) + println( "didn't drop weapon: no ammo in clip" ); + #/ + return; + } + + stockAmmo = self GetWeaponAmmoStock( weapon ); + stockMax = WeaponMaxAmmo( weapon ); + if ( stockAmmo > stockMax ) + stockAmmo = stockMax; + + item = self dropItem( weapon ); + item ItemWeaponSetAmmo( clipAmmoR, stockAmmo, clipAmmoL ); + } + else + { + item = self dropItem( weapon ); + if ( !isDefined( item ) ) + return; + item ItemWeaponSetAmmo( 1, 1, 0 ); + } + + /# + if ( getdvar("scr_dropdebug") == "1" ) + println( "dropped weapon: " + weapon ); + #/ + + self.droppedDeathWeapon = true; + + item.owner = self; + item.ownersattacker = attacker; + + item thread watchPickup(); + + item thread deletePickupAfterAWhile(); + + detach_model = getWeaponModel( weapon ); + + if ( !isDefined( detach_model ) ) + return; + + if( isDefined( self.tag_stowed_back ) && detach_model == self.tag_stowed_back ) + self detach_back_weapon(); + + if ( !isDefined( self.tag_stowed_hip ) ) + return; + + if( detach_model == self.tag_stowed_hip ) + self detach_hip_weapon(); +} + + +detachIfAttached( model, baseTag ) +{ + attachSize = self getAttachSize(); + + for ( i = 0; i < attachSize; i++ ) + { + attach = self getAttachModelName( i ); + + if ( attach != model ) + continue; + + tag = self getAttachTagName( i ); + self detach( model, tag ); + + if ( tag != baseTag ) + { + attachSize = self getAttachSize(); + + for ( i = 0; i < attachSize; i++ ) + { + tag = self getAttachTagName( i ); + + if ( tag != baseTag ) + continue; + + model = self getAttachModelName( i ); + self detach( model, tag ); + + break; + } + } + return true; + } + return false; +} + + +deletePickupAfterAWhile() +{ + self endon("death"); + + wait 60; + + if ( !isDefined( self ) ) + return; + + self delete(); +} + +getItemWeaponName() +{ + classname = self.classname; + assert( getsubstr( classname, 0, 7 ) == "weapon_" ); + weapname = getsubstr( classname, 7 ); + return weapname; +} + +watchPickup() +{ + self endon("death"); + + weapname = self getItemWeaponName(); + + while(1) + { + self waittill( "trigger", player, droppedItem ); + + if ( isdefined( droppedItem ) ) + break; + // otherwise, player merely acquired ammo and didn't pick this up + } + + /# + if ( getdvar("scr_dropdebug") == "1" ) + println( "picked up weapon: " + weapname + ", " + isdefined( self.ownersattacker ) ); + #/ + + assert( isdefined( player.tookWeaponFrom ) ); + + // make sure the owner information on the dropped item is preserved + droppedWeaponName = droppedItem getItemWeaponName(); + if ( isdefined( player.tookWeaponFrom[ droppedWeaponName ] ) ) + { + droppedItem.owner = player.tookWeaponFrom[ droppedWeaponName ]; + droppedItem.ownersattacker = player; + player.tookWeaponFrom[ droppedWeaponName ] = undefined; + } + droppedItem thread watchPickup(); + + // take owner information from self and put it onto player + if ( isdefined( self.ownersattacker ) && self.ownersattacker == player ) + { + player.tookWeaponFrom[ weapname ] = self.owner; + } + else + { + player.tookWeaponFrom[ weapname ] = undefined; + } +} + +itemRemoveAmmoFromAltModes() +{ + origweapname = self getItemWeaponName(); + + curweapname = weaponAltWeaponName( origweapname ); + + altindex = 1; + while ( curweapname != "none" && curweapname != origweapname ) + { + self itemWeaponSetAmmo( 0, 0, 0, altindex ); + curweapname = weaponAltWeaponName( curweapname ); + altindex++; + } +} + + +handleScavengerBagPickup( scrPlayer ) +{ + self endon( "death" ); + level endon ( "game_ended" ); + + assert( isDefined( scrPlayer ) ); + + // Wait for the pickup to happen + self waittill( "scavenger", destPlayer ); + assert( isDefined ( destPlayer ) ); + + destPlayer notify( "scavenger_pickup" ); + destPlayer playLocalSound( "scavenger_pack_pickup" ); + + offhandWeapons = destPlayer getWeaponsListOffhands(); + + if ( destPlayer _hasPerk( "specialty_tacticalinsertion" ) && destPlayer getAmmoCount( "flare_mp" ) < 1 ) + destPlayer _setPerk( "specialty_tacticalinsertion"); + + foreach ( offhand in offhandWeapons ) + { + currentClipAmmo = destPlayer GetWeaponAmmoClip( offhand ); + destPlayer SetWeaponAmmoClip( offhand, currentClipAmmo + 1); + } + + primaryWeapons = destPlayer getWeaponsListPrimaries(); + foreach ( primary in primaryWeapons ) + { + if ( !isCACPrimaryWeapon( primary ) && !level.scavenger_secondary ) + continue; + + currentStockAmmo = destPlayer GetWeaponAmmoStock( primary ); + addStockAmmo = weaponClipSize( primary ); + + destPlayer setWeaponAmmoStock( primary, currentStockAmmo + addStockAmmo ); + + altWeapon = weaponAltWeaponName( primary ); + + if ( !isDefined( altWeapon ) || (altWeapon == "none") || !level.scavenger_altmode ) + continue; + + currentStockAmmo = destPlayer GetWeaponAmmoStock( altWeapon ); + addStockAmmo = weaponClipSize( altWeapon ); + + destPlayer setWeaponAmmoStock( altWeapon, currentStockAmmo + addStockAmmo ); + } + + destPlayer maps\mp\gametypes\_damagefeedback::updateDamageFeedback( "scavenger" ); +} + + +dropScavengerForDeath( attacker ) +{ + if ( level.inGracePeriod ) + return; + + if( !isDefined( attacker ) ) + return; + + if( attacker == self ) + return; + + dropBag = self dropScavengerBag( "scavenger_bag_mp" ); + dropBag thread handleScavengerBagPickup( self ); + +} + +getWeaponBasedGrenadeCount(weapon) +{ + return 2; +} + +getWeaponBasedSmokeGrenadeCount(weapon) +{ + return 1; +} + +getFragGrenadeCount() +{ + grenadetype = "frag_grenade_mp"; + + count = self getammocount(grenadetype); + return count; +} + +getSmokeGrenadeCount() +{ + grenadetype = "smoke_grenade_mp"; + + count = self getammocount(grenadetype); + return count; +} + + +watchWeaponUsage( weaponHand ) +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon ( "game_ended" ); + + for ( ;; ) + { + self waittill ( "weapon_fired", weaponName ); + + self.hasDoneCombat = true; + + if ( !maps\mp\gametypes\_weapons::isPrimaryWeapon( weaponName ) && !maps\mp\gametypes\_weapons::isSideArm( weaponName ) ) + continue; + + if ( isDefined( self.hitsThisMag[ weaponName ] ) ) + self thread updateMagShots( weaponName ); + + totalShots = self maps\mp\gametypes\_persistence::statGetBuffered( "totalShots" ) + 1; + hits = self maps\mp\gametypes\_persistence::statGetBuffered( "hits" ); + self maps\mp\gametypes\_persistence::statSetBuffered( "totalShots", totalShots ); + self maps\mp\gametypes\_persistence::statSetBuffered( "accuracy", int(hits * 10000 / totalShots) ); + self maps\mp\gametypes\_persistence::statSetBuffered( "misses", int(totalShots - hits) ); + } +} + + +updateMagShots( weaponName ) +{ + self endon ( "death" ); + self endon ( "disconnect" ); + self endon ( "updateMagShots_" + weaponName ); + + self.hitsThisMag[ weaponName ]--; + + wait ( 0.05 ); + + self.hitsThisMag[ weaponName ] = weaponClipSize( weaponName ); +} + + +checkHitsThisMag( weaponName ) +{ + self endon ( "death" ); + self endon ( "disconnect" ); + + self notify ( "updateMagShots_" + weaponName ); + waittillframeend; + + if ( self.hitsThisMag[ weaponName ] == 0 ) + { + weaponClass = getWeaponClass( weaponName ); + + maps\mp\gametypes\_missions::genericChallenge( weaponClass ); + + self.hitsThisMag[ weaponName ] = weaponClipSize( weaponName ); + } +} + + +checkHit( weaponName, victim ) +{ + if ( !maps\mp\gametypes\_weapons::isPrimaryWeapon( weaponName ) && !maps\mp\gametypes\_weapons::isSideArm( weaponName ) ) + return; + + // sometimes the "weapon_fired" notify happens after we hit the guy... + waittillframeend; + + if ( isDefined( self.hitsThisMag[ weaponName ] ) ) + self thread checkHitsThisMag( weaponName ); + + if ( !isDefined( self.lastHitTime[ weaponName ] ) ) + self.lastHitTime[ weaponName ] = 0; + + // already hit with this weapon on this frame + if ( self.lastHitTime[ weaponName ] == getTime() ) + return; + + self.lastHitTime[ weaponName ] = getTime(); + + totalShots = self maps\mp\gametypes\_persistence::statGetBuffered( "totalShots" ); + hits = self maps\mp\gametypes\_persistence::statGetBuffered( "hits" ) + 1; + + if ( hits <= totalShots ) + { + self maps\mp\gametypes\_persistence::statSetBuffered( "hits", hits ); + self maps\mp\gametypes\_persistence::statSetBuffered( "misses", int(totalShots - hits) ); + self maps\mp\gametypes\_persistence::statSetBuffered( "accuracy", int(hits * 10000 / totalShots) ); + } +} + + +attackerCanDamageItem( attacker, itemOwner ) +{ + return friendlyFireCheck( itemOwner, attacker ); +} + +// returns true if damage should be done to the item given its owner and the attacker +friendlyFireCheck( owner, attacker, forcedFriendlyFireRule ) +{ + if ( !isdefined( owner ) )// owner has disconnected? allow it + return true; + + if ( !level.teamBased )// not a team based mode? allow it + return true; + + attackerTeam = attacker.team; + + friendlyFireRule = level.friendlyfire; + if ( isdefined( forcedFriendlyFireRule ) ) + friendlyFireRule = forcedFriendlyFireRule; + + if ( friendlyFireRule != 0 )// friendly fire is on? allow it + return true; + + if ( attacker == owner )// owner may attack his own items + return true; + + if ( !isdefined( attackerTeam ) )// attacker not on a team? allow it + return true; + + if ( attackerTeam != owner.team )// attacker not on the same team as the owner? allow it + return true; + + return false;// disallow it +} + +watchGrenadeUsage() +{ + self endon( "death" ); + self endon( "disconnect" ); + + self.throwingGrenade = undefined; + self.gotPullbackNotify = false; + + if ( getIntProperty( "scr_deleteexplosivesonspawn", 1 ) == 1 ) + { + // delete c4 from previous spawn + if ( isdefined( self.c4array ) ) + { + for ( i = 0; i < self.c4array.size; i++ ) + { + if ( isdefined( self.c4array[ i ] ) ) + self.c4array[ i ] delete(); + } + } + self.c4array = []; + // delete claymores from previous spawn + if ( isdefined( self.claymorearray ) ) + { + for ( i = 0; i < self.claymorearray.size; i++ ) + { + if ( isdefined( self.claymorearray[ i ] ) ) + self.claymorearray[ i ] delete(); + } + } + self.claymorearray = []; + } + else + { + if ( !isdefined( self.c4array ) ) + self.c4array = []; + if ( !isdefined( self.claymorearray ) ) + self.claymorearray = []; + } + + thread watchC4(); + thread watchC4Detonation(); + thread watchC4AltDetonation(); + thread watchClaymores(); + thread deleteC4AndClaymoresOnDisconnect(); + + self thread watchForThrowbacks(); + + for ( ;; ) + { + self waittill( "grenade_pullback", weaponName ); + + self.hasDoneCombat = true; + + if ( weaponName == "claymore_mp" ) + continue; + + self.throwingGrenade = weaponName; + self.gotPullbackNotify = true; + + if ( weaponName == "c4_mp" ) + self beginC4Tracking(); + else + self beginGrenadeTracking(); + + self.throwingGrenade = undefined; + } +} + +beginGrenadeTracking() +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "offhand_end" ); + self endon( "weapon_change" ); + + startTime = getTime(); + + self waittill( "grenade_fire", grenade, weaponName ); + + if ( ( getTime() - startTime > 1000 ) && weaponName == "frag_grenade_mp" ) + grenade.isCooked = true; + + self.changingWeapon = undefined; + + if ( weaponName == "frag_grenade_mp" || weaponName == "semtex_mp" ) + { + grenade thread maps\mp\gametypes\_shellshock::grenade_earthQuake(); + grenade.originalOwner = self; + } + + if ( weaponName == "flash_grenade_mp" || weaponName == "concussion_grenade_mp" ) + { + grenade.owner = self; + grenade thread empExplodeWaiter(); + } +} + +AddMissileToSightTraces( team ) +{ + self.team = team; + level.missilesForSightTraces[ level.missilesForSightTraces.size ] = self; + + self waittill( "death" ); + + newArray = []; + foreach( missile in level.missilesForSightTraces ) + { + if ( missile != self ) + newArray[ newArray.size ] = missile; + } + level.missilesForSightTraces = newArray; +} + +watchMissileUsage() +{ + self endon( "death" ); + self endon( "disconnect" ); + + for ( ;; ) + { + self waittill( "missile_fire", missile, weaponName ); + + if ( isSubStr( weaponName, "gl_" ) ) + { + missile.primaryWeapon = self getCurrentPrimaryWeapon(); + missile thread maps\mp\gametypes\_shellshock::grenade_earthQuake(); + } + + switch ( weaponName ) + { + case "at4_mp": + case "stinger_mp": + level notify ( "stinger_fired", self, missile, self.stingerTarget ); + self thread setAltSceneObj( missile, "tag_origin", 65 ); + break; + case "javelin_mp": + level notify ( "stinger_fired", self, missile, self.javelinTarget ); + self thread setAltSceneObj( missile, "tag_origin", 65 ); + break; + default: + break; + } + + switch ( weaponName ) + { + case "at4_mp": + case "javelin_mp": + case "rpg_mp": + case "ac130_105mm_mp": + case "ac130_40mm_mp": + case "remotemissile_projectile_mp": + missile thread maps\mp\gametypes\_shellshock::grenade_earthQuake(); + default: + break; + } + } +} + + +watchSentryUsage() +{ + self endon( "death" ); + self endon( "disconnect" ); + + for ( ;; ) + { + self waittill( "sentry_placement_finished", sentry ); + + self thread setAltSceneObj( sentry, "tag_flash", 65 ); + } +} + + +empExplodeWaiter() +{ + self thread maps\mp\gametypes\_shellshock::endOnDeath(); + self endon( "end_explode" ); + + self waittill( "explode", position ); + + ents = getEMPDamageEnts( position, 512, false ); + + foreach ( ent in ents ) + { + if ( isDefined( ent.owner ) && !friendlyFireCheck( self.owner, ent.owner ) ) + continue; + + ent notify( "emp_damage", self.owner, 8.0 ); + } +} + + +beginC4Tracking() +{ + self endon( "death" ); + self endon( "disconnect" ); + + self waittill_any( "grenade_fire", "weapon_change", "offhand_end" ); +} + + +watchForThrowbacks() +{ + self endon( "death" ); + self endon( "disconnect" ); + + for ( ;; ) + { + self waittill( "grenade_fire", grenade, weapname ); + + if ( self.gotPullbackNotify ) + { + self.gotPullbackNotify = false; + continue; + } + if ( !isSubStr( weapname, "frag_" ) && !isSubStr( weapname, "semtex_" ) ) + continue; + + // no grenade_pullback notify! we must have picked it up off the ground. + grenade.threwBack = true; + self thread incPlayerStat( "throwbacks", 1 ); + + grenade thread maps\mp\gametypes\_shellshock::grenade_earthQuake(); + grenade.originalOwner = self; + } +} + + +watchC4() +{ + self endon( "spawned_player" ); + self endon( "disconnect" ); + + //maxc4 = 2; + + while ( 1 ) + { + self waittill( "grenade_fire", c4, weapname ); + if ( weapname == "c4" || weapname == "c4_mp" ) + { + if ( !self.c4array.size ) + self thread watchC4AltDetonate(); + + if ( self.c4array.size ) + { + self.c4array = array_removeUndefined( self.c4array ); + + if( self.c4array.size >= level.maxPerPlayerExplosives ) + { + self.c4array[0] detonate(); + } + } + + self.c4array[ self.c4array.size ] = c4; + c4.owner = self; + c4.team = self.team; + c4.activated = false; + c4.weaponName = weapname; + + c4 thread maps\mp\gametypes\_shellshock::c4_earthQuake(); + c4 thread c4Activate(); + c4 thread c4Damage(); + c4 thread c4EMPDamage(); + c4 thread c4EMPKillstreakWait(); + //c4 thread c4DetectionTrigger( self.pers[ "team" ] ); + } + } +} + + +c4EMPDamage() +{ + self endon( "death" ); + + for ( ;; ) + { + self waittill( "emp_damage", attacker, duration ); + + playfxOnTag( getfx( "sentry_explode_mp" ), self, "tag_origin" ); + + self.disabled = true; + self notify( "disabled" ); + + wait( duration ); + + self.disabled = undefined; + self notify( "enabled" ); + } +} + + +c4EMPKillstreakWait() +{ + self endon( "death" ); + + for ( ;; ) + { + level waittill( "emp_update" ); + + if ( (level.teamBased && level.teamEMPed[self.team]) || (!level.teamBased && isDefined( level.empPlayer ) && level.empPlayer != self.owner ) ) + { + self.disabled = true; + self notify( "disabled" ); + } + else + { + self.disabled = undefined; + self notify( "enabled" ); + } + } +} + + +setClaymoreTeamHeadIcon( team ) +{ + self endon( "death" ); + wait .05; + if ( level.teamBased ) + self maps\mp\_entityheadicons::setTeamHeadIcon( team, ( 0, 0, 20 ) ); + else if ( isDefined( self.owner ) ) + self maps\mp\_entityheadicons::setPlayerHeadIcon( self.owner, (0,0,20) ); +} + + +watchClaymores() +{ + self endon( "spawned_player" ); + self endon( "disconnect" ); + + self.claymorearray = []; + while ( 1 ) + { + self waittill( "grenade_fire", claymore, weapname ); + if ( weapname == "claymore" || weapname == "claymore_mp" ) + { + self.claymorearray = array_removeUndefined( self.claymorearray ); + + if( self.claymoreArray.size >= level.maxPerPlayerExplosives ) + self.claymoreArray[0] detonate(); + + self.claymorearray[ self.claymorearray.size ] = claymore; + claymore.owner = self; + claymore.team = self.team; + claymore.weaponName = weapname; + + claymore thread c4Damage(); + claymore thread c4EMPDamage(); + claymore thread c4EMPKillstreakWait(); + claymore thread claymoreDetonation(); + //claymore thread claymoreDetectionTrigger_wait( self.pers[ "team" ] ); + claymore thread setClaymoreTeamHeadIcon( self.pers[ "team" ] ); + + /# + if ( getdvarint( "scr_claymoredebug" ) ) + { + claymore thread claymoreDebug(); + } + #/ + } + } +} + + /# +claymoreDebug() +{ + self waittill( "missile_stuck" ); + self thread showCone( acos( level.claymoreDetectionDot ), level.claymoreDetonateRadius, ( 1, .85, 0 ) ); + self thread showCone( 60, 256, ( 1, 0, 0 ) ); +} + +vectorcross( v1, v2 ) +{ + return( v1[ 1 ] * v2[ 2 ] - v1[ 2 ] * v2[ 1 ], v1[ 2 ] * v2[ 0 ] - v1[ 0 ] * v2[ 2 ], v1[ 0 ] * v2[ 1 ] - v1[ 1 ] * v2[ 0 ] ); +} + +showCone( angle, range, color ) +{ + self endon( "death" ); + + start = self.origin; + forward = anglestoforward( self.angles ); + right = vectorcross( forward, ( 0, 0, 1 ) ); + up = vectorcross( forward, right ); + + fullforward = forward * range * cos( angle ); + sideamnt = range * sin( angle ); + + while ( 1 ) + { + prevpoint = ( 0, 0, 0 ); + for ( i = 0; i <= 20; i++ ) + { + coneangle = i / 20.0 * 360; + point = start + fullforward + sideamnt * ( right * cos( coneangle ) + up * sin( coneangle ) ); + if ( i > 0 ) + { + line( start, point, color ); + line( prevpoint, point, color ); + } + prevpoint = point; + } + wait .05; + } +} +#/ + +claymoreDetonation() +{ + self endon( "death" ); + + self waittill( "missile_stuck" ); + + damagearea = spawn( "trigger_radius", self.origin + ( 0, 0, 0 - level.claymoreDetonateRadius ), 0, level.claymoreDetonateRadius, level.claymoreDetonateRadius * 2 ); + self thread deleteOnDeath( damagearea ); + + while ( 1 ) + { + damagearea waittill( "trigger", player ); + + if ( getdvarint( "scr_claymoredebug" ) != 1 ) + { + if ( isdefined( self.owner ) && player == self.owner ) + continue; + if ( !friendlyFireCheck( self.owner, player, 0 ) ) + continue; + } + if ( lengthsquared( player getVelocity() ) < 10 ) + continue; + + if ( !player shouldAffectClaymore( self ) ) + continue; + + if ( player damageConeTrace( self.origin, self ) > 0 ) + break; + } + + self playsound ("claymore_activated"); + + + if ( player _hasPerk( "specialty_delaymine" ) ) + wait 3.0; + else + wait level.claymoreDetectionGracePeriod; + + self detonate(); +} + +shouldAffectClaymore( claymore ) +{ + if ( isDefined( claymore.disabled ) ) + return false; + + pos = self.origin + ( 0, 0, 32 ); + + dirToPos = pos - claymore.origin; + claymoreForward = anglesToForward( claymore.angles ); + + dist = vectorDot( dirToPos, claymoreForward ); + if ( dist < level.claymoreDetectionMinDist ) + return false; + + dirToPos = vectornormalize( dirToPos ); + + dot = vectorDot( dirToPos, claymoreForward ); + return( dot > level.claymoreDetectionDot ); +} + +deleteOnDeath( ent ) +{ + self waittill( "death" ); + wait .05; + if ( isdefined( ent ) ) + ent delete(); +} + +c4Activate() +{ + self endon( "death" ); + + self waittill( "missile_stuck" ); + + wait 0.05; + + self notify( "activated" ); + self.activated = true; +} + +watchC4AltDetonate() +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "detonated" ); + level endon( "game_ended" ); + + buttonTime = 0; + for ( ;; ) + { + if ( self UseButtonPressed() ) + { + buttonTime = 0; + while ( self UseButtonPressed() ) + { + buttonTime += 0.05; + wait( 0.05 ); + } + + println( "pressTime1: " + buttonTime ); + if ( buttonTime >= 0.5 ) + continue; + + buttonTime = 0; + while ( !self UseButtonPressed() && buttonTime < 0.5 ) + { + buttonTime += 0.05; + wait( 0.05 ); + } + + println( "delayTime: " + buttonTime ); + if ( buttonTime >= 0.5 ) + continue; + + if ( !self.c4Array.size ) + return; + + self notify( "alt_detonate" ); + } + wait( 0.05 ); + } +} + +watchC4Detonation() +{ + self endon( "death" ); + self endon( "disconnect" ); + + while ( 1 ) + { + self waittillmatch( "detonate", "c4_mp" ); + newarray = []; + for ( i = 0; i < self.c4array.size; i++ ) + { + c4 = self.c4array[ i ]; + if ( isdefined( self.c4array[ i ] ) ) + c4 thread waitAndDetonate( 0.1 ); + } + self.c4array = newarray; + self notify( "detonated" ); + } +} + + +watchC4AltDetonation() +{ + self endon( "death" ); + self endon( "disconnect" ); + + while ( 1 ) + { + self waittill( "alt_detonate" ); + weap = self getCurrentWeapon(); + if ( weap != "c4_mp" ) + { + newarray = []; + for ( i = 0; i < self.c4array.size; i++ ) + { + c4 = self.c4array[ i ]; + if ( isdefined( self.c4array[ i ] ) ) + c4 thread waitAndDetonate( 0.1 ); + } + self.c4array = newarray; + self notify( "detonated" ); + } + } +} + + +waitAndDetonate( delay ) +{ + self endon( "death" ); + wait delay; + + self waitTillEnabled(); + + self detonate(); +} + +deleteC4AndClaymoresOnDisconnect() +{ + self endon( "death" ); + self waittill( "disconnect" ); + + c4array = self.c4array; + claymorearray = self.claymorearray; + + wait .05; + + for ( i = 0; i < c4array.size; i++ ) + { + if ( isdefined( c4array[ i ] ) ) + c4array[ i ] delete(); + } + for ( i = 0; i < claymorearray.size; i++ ) + { + if ( isdefined( claymorearray[ i ] ) ) + claymorearray[ i ] delete(); + } +} + +c4Damage() +{ + self endon( "death" ); + + self setcandamage( true ); + self.maxhealth = 100000; + self.health = self.maxhealth; + + attacker = undefined; + + while ( 1 ) + { + self waittill( "damage", damage, attacker, direction_vec, point, type, modelName, tagName, partName, iDFlags ); + if ( !isPlayer( attacker ) ) + continue; + + // don't allow people to destroy C4 on their team if FF is off + if ( !friendlyFireCheck( self.owner, attacker ) ) + continue; + + if ( damage < 5 )// ignore concussion grenades + continue; + + break; + } + + if ( level.c4explodethisframe ) + wait .1 + randomfloat( .4 ); + else + wait .05; + + if ( !isdefined( self ) ) + return; + + level.c4explodethisframe = true; + + thread resetC4ExplodeThisFrame(); + + if ( isDefined( type ) && ( isSubStr( type, "MOD_GRENADE" ) || isSubStr( type, "MOD_EXPLOSIVE" ) ) ) + self.wasChained = true; + + if ( isDefined( iDFlags ) && ( iDFlags & level.iDFLAGS_PENETRATION ) ) + self.wasDamagedFromBulletPenetration = true; + + self.wasDamaged = true; + + if ( level.teamBased ) + { + // "destroyed_explosive" notify, for challenges + if ( isdefined( attacker ) && isdefined( attacker.pers[ "team" ] ) && isdefined( self.owner ) && isdefined( self.owner.pers[ "team" ] ) ) + { + if ( attacker.pers[ "team" ] != self.owner.pers[ "team" ] ) + attacker notify( "destroyed_explosive" ); + } + } + else + { + // checking isDefined attacker is defensive but it's too late in the project to risk issues by not having it + if ( isDefined( self.owner ) && isDefined( attacker ) && attacker != self.owner ) + attacker notify( "destroyed_explosive" ); + } + + self detonate( attacker ); + // won't get here; got death notify. +} + +resetC4ExplodeThisFrame() +{ + wait .05; + level.c4explodethisframe = false; +} + +saydamaged( orig, amount ) +{ + for ( i = 0; i < 60; i++ ) + { + print3d( orig, "damaged! " + amount ); + wait .05; + } +} + +waitTillEnabled() +{ + if ( !isDefined( self.disabled ) ) + return; + + self waittill( "enabled" ); + assert( !isDefined( self.disabled ) ); +} + + +c4DetectionTrigger( ownerTeam ) +{ + self waittill( "activated" ); + + trigger = spawn( "trigger_radius", self.origin - ( 0, 0, 128 ), 0, 512, 256 ); + trigger.detectId = "trigger" + getTime() + randomInt( 1000000 ); + + trigger.owner = self; + trigger thread detectIconWaiter( level.otherTeam[ ownerTeam ] ); + + self waittill( "death" ); + trigger notify( "end_detection" ); + + if ( isDefined( trigger.bombSquadIcon ) ) + trigger.bombSquadIcon destroy(); + + trigger delete(); +} + + +claymoreDetectionTrigger_wait( ownerTeam ) +{ + self endon( "death" ); + self waittill( "missile_stuck" ); + + self thread claymoreDetectionTrigger( ownerTeam ); +} + +claymoreDetectionTrigger( ownerTeam ) +{ + trigger = spawn( "trigger_radius", self.origin - ( 0, 0, 128 ), 0, 512, 256 ); + trigger.detectId = "trigger" + getTime() + randomInt( 1000000 ); + + trigger.owner = self; + trigger thread detectIconWaiter( level.otherTeam[ ownerTeam ] ); + + self waittill( "death" ); + trigger notify( "end_detection" ); + + if ( isDefined( trigger.bombSquadIcon ) ) + trigger.bombSquadIcon destroy(); + + trigger delete(); +} + + +detectIconWaiter( detectTeam ) +{ + self endon( "end_detection" ); + level endon( "game_ended" ); + + while ( !level.gameEnded ) + { + self waittill( "trigger", player ); + + if ( !player.detectExplosives ) + continue; + + if ( level.teamBased && player.team != detectTeam ) + continue; + else if ( !level.teamBased && player == self.owner.owner ) + continue; + + if ( isDefined( player.bombSquadIds[ self.detectId ] ) ) + continue; + + player thread showHeadIcon( self ); + } +} + + +setupBombSquad() +{ + self.bombSquadIds = []; + + if ( self.detectExplosives && !self.bombSquadIcons.size ) + { + for ( index = 0; index < 4; index++ ) + { + self.bombSquadIcons[ index ] = newClientHudElem( self ); + self.bombSquadIcons[ index ].x = 0; + self.bombSquadIcons[ index ].y = 0; + self.bombSquadIcons[ index ].z = 0; + self.bombSquadIcons[ index ].alpha = 0; + self.bombSquadIcons[ index ].archived = true; + self.bombSquadIcons[ index ] setShader( "waypoint_bombsquad", 14, 14 ); + self.bombSquadIcons[ index ] setWaypoint( false, false ); + self.bombSquadIcons[ index ].detectId = ""; + } + } + else if ( !self.detectExplosives ) + { + for ( index = 0; index < self.bombSquadIcons.size; index++ ) + self.bombSquadIcons[ index ] destroy(); + + self.bombSquadIcons = []; + } +} + + +showHeadIcon( trigger ) +{ + triggerDetectId = trigger.detectId; + useId = -1; + for ( index = 0; index < 4; index++ ) + { + detectId = self.bombSquadIcons[ index ].detectId; + + if ( detectId == triggerDetectId ) + return; + + if ( detectId == "" ) + useId = index; + } + + if ( useId < 0 ) + return; + + self.bombSquadIds[ triggerDetectId ] = true; + + self.bombSquadIcons[ useId ].x = trigger.origin[ 0 ]; + self.bombSquadIcons[ useId ].y = trigger.origin[ 1 ]; + self.bombSquadIcons[ useId ].z = trigger.origin[ 2 ] + 24 + 128; + + self.bombSquadIcons[ useId ] fadeOverTime( 0.25 ); + self.bombSquadIcons[ useId ].alpha = 1; + self.bombSquadIcons[ useId ].detectId = trigger.detectId; + + while ( isAlive( self ) && isDefined( trigger ) && self isTouching( trigger ) ) + wait( 0.05 ); + + if ( !isDefined( self ) ) + return; + + self.bombSquadIcons[ useId ].detectId = ""; + self.bombSquadIcons[ useId ] fadeOverTime( 0.25 ); + self.bombSquadIcons[ useId ].alpha = 0; + self.bombSquadIds[ triggerDetectId ] = undefined; +} + + +// these functions are used with scripted weapons (like c4, claymores, artillery) +// returns an array of objects representing damageable entities (including players) within a given sphere. +// each object has the property damageCenter, which represents its center (the location from which it can be damaged). +// each object also has the property entity, which contains the entity that it represents. +// to damage it, call damageEnt() on it. +getDamageableEnts( pos, radius, doLOS, startRadius ) +{ + ents = []; + + if ( !isdefined( doLOS ) ) + doLOS = false; + + if ( !isdefined( startRadius ) ) + startRadius = 0; + + radiusSq = radius * radius; + + // players + players = level.players; + for ( i = 0; i < players.size; i++ ) + { + if ( !isalive( players[ i ] ) || players[ i ].sessionstate != "playing" ) + continue; + + playerpos = get_damageable_player_pos( players[ i ] ); + distSq = distanceSquared( pos, playerpos ); + if ( distSq < radiusSq && ( !doLOS || weaponDamageTracePassed( pos, playerpos, startRadius, players[ i ] ) ) ) + { + ents[ ents.size ] = get_damageable_player( players[ i ], playerpos ); + } + } + + // grenades + grenades = getentarray( "grenade", "classname" ); + for ( i = 0; i < grenades.size; i++ ) + { + entpos = get_damageable_grenade_pos( grenades[ i ] ); + distSq = distanceSquared( pos, entpos ); + if ( distSq < radiusSq && ( !doLOS || weaponDamageTracePassed( pos, entpos, startRadius, grenades[ i ] ) ) ) + { + ents[ ents.size ] = get_damageable_grenade( grenades[ i ], entpos ); + } + } + + destructibles = getentarray( "destructible", "targetname" ); + for ( i = 0; i < destructibles.size; i++ ) + { + entpos = destructibles[ i ].origin; + distSq = distanceSquared( pos, entpos ); + if ( distSq < radiusSq && ( !doLOS || weaponDamageTracePassed( pos, entpos, startRadius, destructibles[ i ] ) ) ) + { + newent = spawnstruct(); + newent.isPlayer = false; + newent.isADestructable = false; + newent.entity = destructibles[ i ]; + newent.damageCenter = entpos; + ents[ ents.size ] = newent; + } + } + + destructables = getentarray( "destructable", "targetname" ); + for ( i = 0; i < destructables.size; i++ ) + { + entpos = destructables[ i ].origin; + distSq = distanceSquared( pos, entpos ); + if ( distSq < radiusSq && ( !doLOS || weaponDamageTracePassed( pos, entpos, startRadius, destructables[ i ] ) ) ) + { + newent = spawnstruct(); + newent.isPlayer = false; + newent.isADestructable = true; + newent.entity = destructables[ i ]; + newent.damageCenter = entpos; + ents[ ents.size ] = newent; + } + } + + //sentries + sentries = getentarray( "misc_turret", "classname" ); + foreach ( sentry in sentries ) + { + entpos = sentry.origin + (0,0,32); + distSq = distanceSquared( pos, entpos ); + if ( distSq < radiusSq && ( !doLOS || weaponDamageTracePassed( pos, entpos, startRadius, sentry ) ) ) + { + if ( sentry.model == "sentry_minigun" ) + ents[ ents.size ] = get_damageable_sentry(sentry, entpos); + } + } + + return ents; +} + + +getEMPDamageEnts( pos, radius, doLOS, startRadius ) +{ + ents = []; + + if ( !isDefined( doLOS ) ) + doLOS = false; + + if ( !isDefined( startRadius ) ) + startRadius = 0; + + grenades = getEntArray( "grenade", "classname" ); + foreach ( grenade in grenades ) + { + //if ( !isDefined( grenade.weaponName ) ) + // continue; + + entpos = grenade.origin; + dist = distance( pos, entpos ); + if ( dist < radius && ( !doLOS || weaponDamageTracePassed( pos, entpos, startRadius, grenade ) ) ) + ents[ ents.size ] = grenade; + } + + turrets = getEntArray( "misc_turret", "classname" ); + foreach ( turret in turrets ) + { + //if ( !isDefined( turret.weaponName ) ) + // continue; + + entpos = turret.origin; + dist = distance( pos, entpos ); + if ( dist < radius && ( !doLOS || weaponDamageTracePassed( pos, entpos, startRadius, turret ) ) ) + ents[ ents.size ] = turret; + } + + return ents; +} + + +weaponDamageTracePassed( from, to, startRadius, ent ) +{ + midpos = undefined; + + diff = to - from; + if ( lengthsquared( diff ) < startRadius * startRadius ) + return true; + + dir = vectornormalize( diff ); + midpos = from + ( dir[ 0 ] * startRadius, dir[ 1 ] * startRadius, dir[ 2 ] * startRadius ); + + trace = bullettrace( midpos, to, false, ent ); + + if ( getdvarint( "scr_damage_debug" ) != 0 ) + { + thread debugprint( from, ".dmg" ); + if ( isdefined( ent ) ) + thread debugprint( to, "." + ent.classname ); + else + thread debugprint( to, ".undefined" ); + if ( trace[ "fraction" ] == 1 ) + { + thread debugline( midpos, to, ( 1, 1, 1 ) ); + } + else + { + thread debugline( midpos, trace[ "position" ], ( 1, .9, .8 ) ); + thread debugline( trace[ "position" ], to, ( 1, .4, .3 ) ); + } + } + + return( trace[ "fraction" ] == 1 ); +} + +// eInflictor = the entity that causes the damage (e.g. a claymore) +// eAttacker = the player that is attacking +// iDamage = the amount of damage to do +// sMeansOfDeath = string specifying the method of death (e.g. "MOD_PROJECTILE_SPLASH") +// sWeapon = string specifying the weapon used (e.g. "claymore_mp") +// damagepos = the position damage is coming from +// damagedir = the direction damage is moving in +damageEnt( eInflictor, eAttacker, iDamage, sMeansOfDeath, sWeapon, damagepos, damagedir ) +{ + if ( self.isPlayer ) + { + self.damageOrigin = damagepos; + self.entity thread [[ level.callbackPlayerDamage ]]( + eInflictor,// eInflictor The entity that causes the damage.( e.g. a turret ) + eAttacker,// eAttacker The entity that is attacking. + iDamage,// iDamage Integer specifying the amount of damage done + 0,// iDFlags Integer specifying flags that are to be applied to the damage + sMeansOfDeath,// sMeansOfDeath Integer specifying the method of death + sWeapon,// sWeapon The weapon number of the weapon used to inflict the damage + damagepos,// vPoint The point the damage is from? + damagedir,// vDir The direction of the damage + "none",// sHitLoc The location of the hit + 0// psOffsetTime The time offset for the damage + ); + } + else + { + // destructable walls and such can only be damaged in certain ways. + if ( self.isADestructable && ( sWeapon == "artillery_mp" || sWeapon == "claymore_mp" ) || sWeapon == "stealth_bomb_mp" ) + return; + + self.entity notify( "damage", iDamage, eAttacker, ( 0, 0, 0 ), ( 0, 0, 0 ), "mod_explosive", "", "" ); + } +} + + +debugline( a, b, color ) +{ + for ( i = 0; i < 30 * 20; i++ ) + { + line( a, b, color ); + wait .05; + } +} + +debugprint( pt, txt ) +{ + for ( i = 0; i < 30 * 20; i++ ) + { + print3d( pt, txt ); + wait .05; + } +} + + +onWeaponDamage( eInflictor, sWeapon, meansOfDeath, damage, eAttacker ) +{ + self endon( "death" ); + self endon( "disconnect" ); + + switch( sWeapon ) + { + case "concussion_grenade_mp": + // should match weapon settings in gdt + radius = 512; + scale = 1 - ( distance( self.origin, eInflictor.origin ) / radius ); + + if ( scale < 0 ) + scale = 0; + + time = 2 + ( 4 * scale ); + + wait( 0.05 ); + eAttacker notify( "stun_hit" ); + self shellShock( "concussion_grenade_mp", time ); + self.concussionEndTime = getTime() + ( time * 1000 ); + break; + + case "weapon_cobra_mk19_mp": + // mk19 is too powerful with shellshock slowdown + break; + + default: + // shellshock will only be done if meansofdeath is an appropriate type and if there is enough damage. + maps\mp\gametypes\_shellshock::shellshockOnDamage( meansOfDeath, damage ); + break; + } + +} + +// weapon stowing logic =================================================================== + +// weapon class boolean helpers +isPrimaryWeapon( weapName ) +{ + if ( weapName == "none" ) + return false; + + if ( weaponInventoryType( weapName ) != "primary" ) + return false; + + switch ( weaponClass( weapName ) ) + { + case "rifle": + case "smg": + case "mg": + case "spread": + case "pistol": + case "rocketlauncher": + case "sniper": + return true; + + default: + return false; + } +} + + +isAltModeWeapon( weapName ) +{ + if ( weapName == "none" ) + return false; + + return ( weaponInventoryType( weapName ) == "altmode" ); +} + +isInventoryWeapon( weapName ) +{ + if ( weapName == "none" ) + return false; + + return ( weaponInventoryType( weapName ) == "item" ); +} + +isRiotShield( weapName ) +{ + if ( weapName == "none" ) + return false; + + return ( WeaponType( weapName ) == "riotshield" ); +} + +isOffhandWeapon( weapName ) +{ + if ( weapName == "none" ) + return false; + + return ( weaponInventoryType( weapName ) == "offhand" ); +} + +isSideArm( weapName ) +{ + if ( weapName == "none" ) + return false; + + if ( weaponInventoryType( weapName ) != "primary" ) + return false; + + return ( weaponClass( weapName ) == "pistol" ); +} + + +// This needs for than this.. this would qualify c4 as a grenade +isGrenade( weapName ) +{ + weapClass = weaponClass( weapName ); + weapType = weaponInventoryType( weapName ); + + if ( weapClass != "grenade" ) + return false; + + if ( weapType != "offhand" ) + return false; +} + + +getStowOffsetModel( weaponName ) +{ + assert( isDefined( level.stow_offset_array ) ); + + baseName = getBaseWeaponName( weaponName ); + + return( level.stow_offset_array[baseName] ); +} + + +stowPriorityWeapon() +{ + assert( isdefined( level.stow_priority_model_array ) ); + + // returns the first large projectil the player owns in case player owns more than one + foreach ( weapon_name, priority_weapon in level.stow_priority_model_array ) + { + weaponName = getBaseWeaponName( weapon_name ); + weaponList = self getWeaponsListAll(); + + foreach ( weapon in weaponList ) + { + if( self getCurrentWeapon() == weapon ) + continue; + + if ( weaponName == getBaseWeaponName( weapon ) ) + return weaponName + "_mp"; + } + } + + return ""; +} + +// thread loop life = player's life +updateStowedWeapon() +{ + self endon( "spawned" ); + self endon( "killed_player" ); + self endon( "disconnect" ); + + self.tag_stowed_back = undefined; + self.tag_stowed_hip = undefined; + + team = self.team; + class = self.class; + + self thread stowedWeaponsRefresh(); + + while ( true ) + { + self waittill( "weapon_change", newWeapon ); + + if ( newWeapon == "none" ) + continue; + + self thread stowedWeaponsRefresh(); + } +} + +stowedWeaponsRefresh() +{ + self endon( "spawned" ); + self endon( "killed_player" ); + self endon( "disconnect" ); + + detach_all_weapons(); + stow_on_back(); + stow_on_hip(); +} + + +detach_all_weapons() +{ + if ( isDefined( self.tag_stowed_back ) ) + self detach_back_weapon(); + + if ( isDefined( self.tag_stowed_hip ) ) + self detach_hip_weapon(); +} + + +detach_back_weapon() +{ + detach_success = self detachIfAttached( self.tag_stowed_back, "tag_stowed_back" ); + + // test for bug + //assertex( detach_success, "Detaching: " + self.tag_stowed_back + " from tag: tag_stowed_back failed." ); + self.tag_stowed_back = undefined; +} + + +detach_hip_weapon() +{ + detach_success = self detachIfAttached( self.tag_stowed_hip, "tag_stowed_hip" ); + + // test for bug + //assertex( detach_success, "Detaching: " + detach_model + " from tag: tag_stowed_hip failed." ); + self.tag_stowed_hip = undefined; +} + + +stow_on_back() +{ + prof_begin( "stow_on_back" ); + currentWeapon = self getCurrentWeapon(); + currentIsAlt = isAltModeWeapon( currentWeapon ); + + assert( !isDefined( self.tag_stowed_back ) ); + + stowWeapon = undefined; + stowCamo = 0; + large_projectile = self stowPriorityWeapon(); + stowOffsetModel = undefined; + + if ( large_projectile != "" ) + { + stowWeapon = large_projectile; + } + else + { + weaponsList = self getWeaponsListPrimaries(); + foreach ( weaponName in weaponsList ) + { + if ( weaponName == currentWeapon ) + continue; + + invType = weaponInventoryType( weaponName ); + + if ( invType != "primary" ) + { + if ( invType == "altmode" ) + continue; + + if ( weaponClass( weaponName ) == "pistol" ) + continue; + } + + if ( WeaponType( weaponName ) == "riotshield" ) + continue; + + // Don't stow the current on our back when we're using the alt + if ( currentIsAlt && weaponAltWeaponName( weaponName ) == currentWeapon ) + continue; + + stowWeapon = weaponName; + stowOffsetModel = getStowOffsetModel( stowWeapon ); + + if ( stowWeapon == self.primaryWeapon ) + stowCamo = self.loadoutPrimaryCamo; + else if ( stowWeapon == self.secondaryWeapon ) + stowCamo = self.loadoutSecondaryCamo; + else + stowCamo = 0; + } + } + + if ( !isDefined( stowWeapon ) ) + { + prof_end( "stow_on_back" ); + return; + } + + if ( large_projectile != "" ) + { + self.tag_stowed_back = level.stow_priority_model_array[ large_projectile ]; + } + else + { + self.tag_stowed_back = getWeaponModel( stowWeapon, stowCamo ); + } + + if ( isDefined( stowOffsetModel ) ) + { + self attach( stowOffsetModel, "tag_stowed_back", true ); + attachTag = "tag_stow_back_mid_attach"; + } + else + { + attachTag = "tag_stowed_back"; + } + + self attach( self.tag_stowed_back, attachTag, true ); + + hideTagList = GetWeaponHideTags( stowWeapon ); + + if ( !isDefined( hideTagList ) ) + { + prof_end( "stow_on_back" ); + return; + } + + for ( i = 0; i < hideTagList.size; i++ ) + self HidePart( hideTagList[ i ], self.tag_stowed_back ); + + prof_end( "stow_on_back" ); +} + +stow_on_hip() +{ + currentWeapon = self getCurrentWeapon(); + + assert( !isDefined( self.tag_stowed_hip ) ); + + stowWeapon = undefined; + + weaponsList = self getWeaponsListOffhands(); + foreach ( weaponName in weaponsList ) + { + if ( weaponName == currentWeapon ) + continue; + + if ( weaponName != "c4_mp" && weaponName != "claymore_mp" ) + continue; + + stowWeapon = weaponName; + } + + if ( !isDefined( stowWeapon ) ) + return; + + self.tag_stowed_hip = getWeaponModel( stowWeapon ); + self attach( self.tag_stowed_hip, "tag_stowed_hip_rear", true ); + + hideTagList = GetWeaponHideTags( stowWeapon ); + + if ( !isDefined( hideTagList ) ) + return; + + for ( i = 0; i < hideTagList.size; i++ ) + self HidePart( hideTagList[ i ], self.tag_stowed_hip ); +} + + +updateSavedLastWeapon() +{ + self endon( "death" ); + self endon( "disconnect" ); + + currentWeapon = self.currentWeaponAtSpawn; + self.saved_lastWeapon = currentWeapon; + + for ( ;; ) + { + self waittill( "weapon_change", newWeapon ); + + if ( newWeapon == "none" ) + { + self.saved_lastWeapon = currentWeapon; + continue; + } + + weaponInvType = weaponInventoryType( newWeapon ); + + if ( weaponInvType != "primary" && weaponInvType != "altmode" ) + { + self.saved_lastWeapon = currentWeapon; + continue; + } + + if ( newWeapon == "onemanarmy_mp" ) + { + self.saved_lastWeapon = currentWeapon; + continue; + } + + self updateMoveSpeedScale( "primary" ); + + self.saved_lastWeapon = currentWeapon; + currentWeapon = newWeapon; + } +} + + +EMPPlayer( numSeconds ) +{ + self endon( "disconnect" ); + self endon( "death" ); + + self thread clearEMPOnDeath(); + +} + + +clearEMPOnDeath() +{ + self endon( "disconnect" ); + + self waittill( "death" ); +} + + +updateMoveSpeedScale( weaponType ) +{ + /* + if ( self _hasPerk( "specialty_lightweight" ) ) + self.moveSpeedScaler = 1.10; + else + self.moveSpeedScaler = 1; + */ + + if ( !isDefined( weaponType ) || weaponType == "primary" || weaponType != "secondary" ) + weaponType = self.primaryWeapon; + else + weaponType = self.secondaryWeapon; + + if( isDefined(self.primaryWeapon ) && self.primaryWeapon == "riotshield_mp" ) + { + self setMoveSpeedScale( .8 * self.moveSpeedScaler ); + return; + } + + if ( !isDefined( weaponType ) ) + weapClass = "none"; + else + weapClass = weaponClass( weaponType ); + + + switch ( weapClass ) + { + case "rifle": + self setMoveSpeedScale( 0.95 * self.moveSpeedScaler ); + break; + case "pistol": + self setMoveSpeedScale( 1.0 * self.moveSpeedScaler ); + break; + case "mg": + self setMoveSpeedScale( 0.875 * self.moveSpeedScaler ); + break; + case "smg": + self setMoveSpeedScale( 1.0 * self.moveSpeedScaler ); + break; + case "spread": + self setMoveSpeedScale( .95 * self.moveSpeedScaler ); + break; + case "rocketlauncher": + self setMoveSpeedScale( 0.80 * self.moveSpeedScaler ); + break; + case "sniper": + self setMoveSpeedScale( 1.0 * self.moveSpeedScaler ); + break; + default: + self setMoveSpeedScale( 1.0 * self.moveSpeedScaler ); + break; + } +} + + +buildWeaponData( filterPerks ) +{ + attachmentList = getAttachmentList(); + max_weapon_num = 149; + + baseWeaponData = []; + + for( weaponId = 0; weaponId <= max_weapon_num; weaponId++ ) + { + baseName = tablelookup( "mp/statstable.csv", 0, weaponId, 4 ); + if( baseName == "" ) + continue; + + assetName = baseName + "_mp"; + + if ( !isSubStr( tableLookup( "mp/statsTable.csv", 0, weaponId, 2 ), "weapon_" ) ) + continue; + + if ( weaponInventoryType( assetName ) != "primary" ) + continue; + + weaponInfo = spawnStruct(); + weaponInfo.baseName = baseName; + weaponInfo.assetName = assetName; + weaponInfo.variants = []; + + weaponInfo.variants[0] = assetName; + // the alphabetize function is slow so we try not to do it for every weapon/attachment combo; a code solution would be better. + attachmentNames = []; + for ( innerLoopCount = 0; innerLoopCount < 6; innerLoopCount++ ) + { + // generating attachment combinations + attachmentName = tablelookup( "mp/statStable.csv", 0, weaponId, innerLoopCount + 11 ); + + if ( filterPerks ) + { + switch ( attachmentName ) + { + case "fmj": + case "xmags": + case "rof": + continue; + } + } + + if( attachmentName == "" ) + break; + + attachmentNames[attachmentName] = true; + } + + // generate an alphabetized attachment list + attachments = []; + foreach ( attachmentName in attachmentList ) + { + if ( !isDefined( attachmentNames[attachmentName] ) ) + continue; + + weaponInfo.variants[weaponInfo.variants.size] = baseName + "_" + attachmentName + "_mp"; + attachments[attachments.size] = attachmentName; + } + + for ( i = 0; i < (attachments.size - 1); i++ ) + { + colIndex = tableLookupRowNum( "mp/attachmentCombos.csv", 0, attachments[i] ); + for ( j = i + 1; j < attachments.size; j++ ) + { + if ( tableLookup( "mp/attachmentCombos.csv", 0, attachments[j], colIndex ) == "no" ) + continue; + + weaponInfo.variants[weaponInfo.variants.size] = baseName + "_" + attachments[i] + "_" + attachments[j] + "_mp"; + } + } + + baseWeaponData[baseName] = weaponInfo; + } + + return ( baseWeaponData ); +} + +monitorSemtex() +{ + self endon( "disconnect" ); + self endon( "death" ); + + for( ;; ) + { + self waittill( "grenade_fire", weapon ); + + if ( !isSubStr(weapon.model, "semtex" ) ) + continue; + + weapon waittill( "missile_stuck", stuckTo ); + + if ( !isPlayer( stuckTo ) ) + continue; + + if ( level.teamBased && isDefined( stuckTo.team ) && stuckTo.team == self.team ) + { + weapon.isStuck = "friendly"; + continue; + } + + weapon.isStuck = "enemy"; + weapon.stuckEnemyEntity = stuckTo; + + stuckTo maps\mp\gametypes\_hud_message::playerCardSplashNotify( "semtex_stuck", self ); + + self thread maps\mp\gametypes\_hud_message::SplashNotify( "stuck_semtex", 100 ); + self notify( "process", "ch_bullseye" ); + } +} + + +turret_monitorUse() +{ + for( ;; ) + { + self waittill ( "trigger", player ); + + self thread turret_playerThread( player ); + } +} + +turret_playerThread( player ) +{ + player endon ( "death" ); + player endon ( "disconnect" ); + + player notify ( "weapon_change", "none" ); + + self waittill ( "turret_deactivate" ); + + player notify ( "weapon_change", player getCurrentWeapon() ); +} diff --git a/userraw/maps/mp/killstreaks/_emp.gsc b/userraw/maps/mp/killstreaks/_emp.gsc new file mode 100644 index 0000000..d74558a --- /dev/null +++ b/userraw/maps/mp/killstreaks/_emp.gsc @@ -0,0 +1,321 @@ +#include maps\mp\_utility; +#include common_scripts\utility; + + +init() +{ + level._effect[ "emp_flash" ] = loadfx( "explosions/emp_flash_mp" ); + + level.teamEMPed["allies"] = false; + level.teamEMPed["axis"] = false; + level.empPlayer = undefined; + + if ( level.teamBased ) + level thread EMP_TeamTracker(); + else + level thread EMP_PlayerTracker(); + + level.killstreakFuncs["emp"] = ::EMP_Use; + + level thread onPlayerConnect(); + +} + + + +onPlayerConnect() +{ + for(;;) + { + level waittill("connected", player); + player thread onPlayerSpawned(); + } +} + + +onPlayerSpawned() +{ + self endon("disconnect"); + + for(;;) + { + self waittill( "spawned_player" ); + + if ( (level.teamBased && level.teamEMPed[self.team]) || (!level.teamBased && isDefined( level.empPlayer ) && level.empPlayer != self) ) + self setEMPJammed( true ); + } +} + + +EMP_Use( lifeId, delay ) +{ + assert( isDefined( self ) ); + + if ( !isDefined( delay ) ) + delay = 5.0; + + myTeam = self.pers["team"]; + otherTeam = level.otherTeam[myTeam]; + + if ( level.teamBased ) + self thread EMP_JamTeam( otherTeam, 60.0, delay ); + else + self thread EMP_JamPlayers( self, 60.0, delay ); + + self maps\mp\_matchdata::logKillstreakEvent( "emp", self.origin ); + self notify( "used_emp" ); + + return true; +} + + +EMP_JamTeam( teamName, duration, delay ) +{ + level endon ( "game_ended" ); + + assert( teamName == "allies" || teamName == "axis" ); + + //wait ( delay ); + + thread teamPlayerCardSplash( "used_emp", self ); + + level notify ( "EMP_JamTeam" + teamName ); + level endon ( "EMP_JamTeam" + teamName ); + + foreach ( player in level.players ) + { + player playLocalSound( "emp_activate" ); + + if ( player.team != teamName ) + continue; + + if ( player _hasPerk( "specialty_localjammer" ) ) + player RadarJamOff(); + } + + visionSetNaked( "coup_sunblind", 0.1 ); + thread empEffects(); + + wait ( 0.1 ); + + // resetting the vision set to the same thing won't normally have an effect. + // however, if the client receives the previous visionset change in the same packet as this one, + // this will force them to lerp from the bright one to the normal one. + visionSetNaked( "coup_sunblind", 0 ); + visionSetNaked( getDvar( "mapname" ), 3.0 ); + + level.teamEMPed[teamName] = true; + level notify ( "emp_update" ); + + level destroyActiveVehicles( self ); + + maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( duration ); + + level.teamEMPed[teamName] = false; + + foreach ( player in level.players ) + { + if ( player.team != teamName ) + continue; + + if ( player _hasPerk( "specialty_localjammer" ) ) + player RadarJamOn(); + } + + level notify ( "emp_update" ); +} + +EMP_JamPlayers( owner, duration, delay ) +{ + level notify ( "EMP_JamPlayers" ); + level endon ( "EMP_JamPlayers" ); + + assert( isDefined( owner ) ); + + //wait ( delay ); + + foreach ( player in level.players ) + { + player playLocalSound( "emp_activate" ); + + if ( player == owner ) + continue; + + if ( player _hasPerk( "specialty_localjammer" ) ) + player RadarJamOff(); + } + + visionSetNaked( "coup_sunblind", 0.1 ); + thread empEffects(); + + wait ( 0.1 ); + + // resetting the vision set to the same thing won't normally have an effect. + // however, if the client receives the previous visionset change in the same packet as this one, + // this will force them to lerp from the bright one to the normal one. + visionSetNaked( "coup_sunblind", 0 ); + visionSetNaked( getDvar( "mapname" ), 3.0 ); + + level notify ( "emp_update" ); + + level.empPlayer = owner; + level.empPlayer thread empPlayerFFADisconnect(); + level destroyActiveVehicles( owner ); + + level notify ( "emp_update" ); + + maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( duration ); + + foreach ( player in level.players ) + { + if ( player == owner ) + continue; + + if ( player _hasPerk( "specialty_localjammer" ) ) + player RadarJamOn(); + } + + level.empPlayer = undefined; + level notify ( "emp_update" ); + level notify ( "emp_ended" ); +} + +empPlayerFFADisconnect() +{ + level endon ( "EMP_JamPlayers" ); + level endon ( "emp_ended" ); + + self waittill( "disconnect" ); + level notify ( "emp_update" ); +} + +empEffects() +{ + foreach( player in level.players ) + { + playerForward = anglestoforward( player.angles ); + playerForward = ( playerForward[0], playerForward[1], 0 ); + playerForward = VectorNormalize( playerForward ); + + empDistance = 20000; + + empEnt = Spawn( "script_model", player.origin + ( 0, 0, 8000 ) + Vector_Multiply( playerForward, empDistance ) ); + empEnt setModel( "tag_origin" ); + empEnt.angles = empEnt.angles + ( 270, 0, 0 ); + empEnt thread empEffect( player ); + } +} + +empEffect( player ) +{ + player endon( "disconnect" ); + + wait( 0.5 ); + PlayFXOnTagForClients( level._effect[ "emp_flash" ], self, "tag_origin", player ); +} + +EMP_TeamTracker() +{ + level endon ( "game_ended" ); + + for ( ;; ) + { + level waittill_either ( "joined_team", "emp_update" ); + + foreach ( player in level.players ) + { + if ( player.team == "spectator" ) + continue; + + player setEMPJammed( level.teamEMPed[player.team] ); + } + } +} + + +EMP_PlayerTracker() +{ + level endon ( "game_ended" ); + + for ( ;; ) + { + level waittill_either ( "joined_team", "emp_update" ); + + foreach ( player in level.players ) + { + if ( player.team == "spectator" ) + continue; + + if ( isDefined( level.empPlayer ) && level.empPlayer != player ) + player setEMPJammed( true ); + else + player setEMPJammed( false ); + } + } +} + +destroyActiveVehicles( attacker ) +{ + if ( isDefined( attacker ) ) + { + foreach ( heli in level.helis ) + radiusDamage( heli.origin, 384, 5000, 5000, attacker ); + + foreach ( littleBird in level.littleBird ) + radiusDamage( littleBird.origin, 384, 5000, 5000, attacker ); + + foreach ( turret in level.turrets ) + radiusDamage( turret.origin, 16, 5000, 5000, attacker ); + + foreach ( rocket in level.rockets ) + rocket notify ( "death" ); + + if ( level.teamBased ) + { + foreach ( uav in level.uavModels["allies"] ) + radiusDamage( uav.origin, 384, 5000, 5000, attacker ); + + foreach ( uav in level.uavModels["axis"] ) + radiusDamage( uav.origin, 384, 5000, 5000, attacker ); + } + else + { + foreach ( uav in level.uavModels ) + radiusDamage( uav.origin, 384, 5000, 5000, attacker ); + } + + if ( isDefined( level.ac130player ) ) + radiusDamage( level.ac130.planeModel.origin+(0,0,10), 1000, 5000, 5000, attacker ); + } + else + { + foreach ( heli in level.helis ) + radiusDamage( heli.origin, 384, 5000, 5000 ); + + foreach ( littleBird in level.littleBird ) + radiusDamage( littleBird.origin, 384, 5000, 5000 ); + + foreach ( turret in level.turrets ) + radiusDamage( turret.origin, 16, 5000, 5000 ); + + foreach ( rocket in level.rockets ) + rocket notify ( "death" ); + + if ( level.teamBased ) + { + foreach ( uav in level.uavModels["allies"] ) + radiusDamage( uav.origin, 384, 5000, 5000 ); + + foreach ( uav in level.uavModels["axis"] ) + radiusDamage( uav.origin, 384, 5000, 5000 ); + } + else + { + foreach ( uav in level.uavModels ) + radiusDamage( uav.origin, 384, 5000, 5000 ); + } + + if ( isDefined( level.ac130player ) ) + radiusDamage( level.ac130.planeModel.origin+(0,0,10), 1000, 5000, 5000 ); + } +} \ No newline at end of file diff --git a/userraw/maps/mp/killstreaks/_killstreaks.gsc b/userraw/maps/mp/killstreaks/_killstreaks.gsc new file mode 100644 index 0000000..dfd9f9d --- /dev/null +++ b/userraw/maps/mp/killstreaks/_killstreaks.gsc @@ -0,0 +1,666 @@ +#include maps\mp\_utility; +#include maps\mp\gametypes\_hud_util; +#include common_scripts\utility; + +KILLSTREAK_STRING_TABLE = "mp/killstreakTable.csv"; + +init() +{ + // &&1 Kill Streak! + precacheString( &"MP_KILLSTREAK_N" ); + precacheString( &"MP_NUKE_ALREADY_INBOUND" ); + precacheString( &"MP_UNAVILABLE_IN_LASTSTAND" ); + precacheString( &"MP_UNAVAILABLE_WHEN_EMP" ); + precacheString( &"MP_UNAVAILABLE_USING_TURRET" ); + precacheString( &"MP_UNAVAILABLE_WHEN_INCAP" ); + precacheString( &"MP_HELI_IN_QUEUE" ); + + initKillstreakData(); + + level.killstreakFuncs = []; + level.killstreakSetupFuncs = []; + level.killstreakWeapons = []; + + level.killStreakMod = 0; + + thread maps\mp\killstreaks\_ac130::init(); + thread maps\mp\killstreaks\_remotemissile::init(); + thread maps\mp\killstreaks\_uav::init(); + thread maps\mp\killstreaks\_airstrike::init(); + thread maps\mp\killstreaks\_airdrop::init(); + thread maps\mp\killstreaks\_helicopter::init(); + thread maps\mp\killstreaks\_autosentry::init(); + thread maps\mp\killstreaks\_tank::init(); + thread maps\mp\killstreaks\_emp::init(); + thread maps\mp\killstreaks\_nuke::init(); + + level.killstreakRoundDelay = getIntProperty( "scr_game_killstreakdelay", 8 ); + + level thread onPlayerConnect(); +} + + +initKillstreakData() +{ + for ( i = 1; true; i++ ) + { + retVal = tableLookup( KILLSTREAK_STRING_TABLE, 0, i, 1 ); + if ( !isDefined( retVal ) || retVal == "" ) + break; + + streakRef = tableLookup( KILLSTREAK_STRING_TABLE, 0, i, 1 ); + assert( streakRef != "" ); + + streakUseHint = tableLookupIString( KILLSTREAK_STRING_TABLE, 0, i, 6 ); + assert( streakUseHint != &"" ); + precacheString( streakUseHint ); + + streakEarnDialog = tableLookup( KILLSTREAK_STRING_TABLE, 0, i, 8 ); + assert( streakEarnDialog != "" ); + game[ "dialog" ][ streakRef ] = streakEarnDialog; + + streakAlliesUseDialog = tableLookup( KILLSTREAK_STRING_TABLE, 0, i, 9 ); + assert( streakAlliesUseDialog != "" ); + game[ "dialog" ][ "allies_friendly_" + streakRef + "_inbound" ] = "use_" + streakAlliesUseDialog; + game[ "dialog" ][ "allies_enemy_" + streakRef + "_inbound" ] = "enemy_" + streakAlliesUseDialog; + + streakAxisUseDialog = tableLookup( KILLSTREAK_STRING_TABLE, 0, i, 10 ); + assert( streakAxisUseDialog != "" ); + game[ "dialog" ][ "axis_friendly_" + streakRef + "_inbound" ] = "use_" + streakAxisUseDialog; + game[ "dialog" ][ "axis_enemy_" + streakRef + "_inbound" ] = "enemy_" + streakAxisUseDialog; + + streakWeapon = tableLookup( KILLSTREAK_STRING_TABLE, 0, i, 12 ); + precacheItem( streakWeapon ); + + streakPoints = int( tableLookup( KILLSTREAK_STRING_TABLE, 0, i, 13 ) ); + assert( streakPoints != 0 ); + maps\mp\gametypes\_rank::registerScoreInfo( "killstreak_" + streakRef, streakPoints ); + + streakShader = tableLookup( KILLSTREAK_STRING_TABLE, 0, i, 14 ); + precacheShader( streakShader ); + + streakShader = tableLookup( KILLSTREAK_STRING_TABLE, 0, i, 15 ); + if ( streakShader != "" ) + precacheShader( streakShader ); + } +} + + +onPlayerConnect() +{ + for ( ;; ) + { + level waittill( "connected", player ); + + if( !isDefined ( player.pers[ "killstreaks" ] ) ) + player.pers[ "killstreaks" ] = []; + + player.lifeId = 0; + + if ( isDefined( player.pers["deaths"] ) ) + player.lifeId = player.pers["deaths"]; + + player VisionSetMissilecamForPlayer( game["thermal_vision"] ); + + player thread onPlayerSpawned(); + player thread onPlayerChangeKit(); + } +} + + +onPlayerSpawned() +{ + self endon( "disconnect" ); + + for ( ;; ) + { + self waittill( "spawned_player" ); + self thread killstreakUseWaiter(); + self thread waitForChangeTeam(); + + self giveOwnedKillstreakItem( true ); + } +} + +onPlayerChangeKit() +{ + self endon( "disconnect" ); + + for ( ;; ) + { + self waittill( "changed_kit" ); + self giveOwnedKillstreakItem(); + } +} + + +waitForChangeTeam() +{ + self endon ( "disconnect" ); + + self notify ( "waitForChangeTeam" ); + self endon ( "waitForChangeTeam" ); + + for ( ;; ) + { + self waittill ( "joined_team" ); + clearKillstreaks(); + } +} + + +isRideKillstreak( streakName ) +{ + switch( streakName ) + { + case "helicopter_minigun": + case "helicopter_mk19": + case "ac130": + case "predator_missile": + return true; + + default: + return false; + } +} + +isCarryKillstreak( streakName ) +{ + switch( streakName ) + { + case "sentry": + case "sentry_gl": + return true; + + default: + return false; + } +} + + +deadlyKillstreak( streakName ) +{ + switch ( streakName ) + { + case "predator_missile": + case "precision_airstrike": + case "harrier_airstrike": + //case "helicopter": + //case "helicopter_flares": + case "stealth_airstrike": + //case "helicopter_minigun": + case "ac130": + return true; + } + + return false; +} + + +killstreakUsePressed() +{ + streakName = self.pers["killstreaks"][0].streakName; + lifeId = self.pers["killstreaks"][0].lifeId; + isEarned = self.pers["killstreaks"][0].earned; + awardXp = self.pers["killstreaks"][0].awardXp; + + assert( isDefined( streakName ) ); + assert( isDefined( level.killstreakFuncs[ streakName ] ) ); + + if ( !self isOnGround() && ( isRideKillstreak( streakName ) || isCarryKillstreak( streakName ) ) ) + return ( false ); + + if ( self isUsingRemote() ) + return ( false ); + + if ( isDefined( self.selectingLocation ) ) + return ( false ); + + if ( deadlyKillstreak( streakName ) && level.killstreakRoundDelay && getGametypeNumLives() ) + { + if ( level.gracePeriod - level.inGracePeriod < level.killstreakRoundDelay ) + { + self iPrintLnBold( &"MP_UNAVAILABLE_FOR_N", (level.killstreakRoundDelay - (level.gracePeriod - level.inGracePeriod)) ); + return ( false ); + } + } + + if ( (level.teamBased && level.teamEMPed[self.team]) || (!level.teamBased && isDefined( level.empPlayer ) && level.empPlayer != self) ) + { + self iPrintLnBold( &"MP_UNAVAILABLE_WHEN_EMP" ); + return ( false ); + } + + if ( self IsUsingTurret() && ( isRideKillstreak( streakName ) || isCarryKillstreak( streakName ) ) ) + { + self iPrintLnBold( &"MP_UNAVAILABLE_USING_TURRET" ); + return ( false ); + } + + if ( isDefined( self.lastStand ) && isRideKillstreak( streakName ) ) + { + self iPrintLnBold( &"MP_UNAVILABLE_IN_LASTSTAND" ); + return ( false ); + } + + if ( !self isWeaponEnabled() ) + return ( false ); + + if ( !self [[ level.killstreakFuncs[ streakName ] ]]( lifeId ) ) + return ( false ); + + self usedKillstreak( streakName, awardXp ); + self shuffleKillStreaksFILO( streakName ); + self giveOwnedKillstreakItem(); + + return ( true ); +} + + +//this overwrites killstreak at index 0 and decrements all other killstreaks (FCLS style) +shuffleKillStreaksFILO( streakName ) +{ + self _setActionSlot( 4, "" ); + + arraySize = self.pers["killstreaks"].size; + + streakIndex = -1; + for ( i = 0; i < arraySize; i++ ) + { + if ( self.pers["killstreaks"][i].streakName != streakName ) + continue; + + streakIndex = i; + break; + } + assert( streakIndex >= 0 ); + + self.pers["killstreaks"][streakIndex] = undefined; + + for( i = streakIndex + 1; i < arraySize; i++ ) + { + if ( i == arraySize - 1 ) + { + self.pers["killstreaks"][i-1] = self.pers["killstreaks"][i]; + self.pers["killstreaks"][i] = undefined; + } + else + { + self.pers["killstreaks"][i-1] = self.pers["killstreaks"][i]; + } + } +} + + +usedKillstreak( streakName, awardXp ) +{ + self playLocalSound( "weap_c4detpack_trigger_plr" ); + + if ( awardXp ) + self thread [[ level.onXPEvent ]]( "killstreak_" + streakName ); + + self thread maps\mp\gametypes\_missions::useHardpoint( streakName ); + + awardref = maps\mp\_awards::getKillstreakAwardRef( streakName ); + if ( isDefined( awardref ) ) + self thread incPlayerStat( awardref, 1 ); + + team = self.team; + + if ( level.teamBased ) + { + thread leaderDialog( team + "_friendly_" + streakName + "_inbound", team ); + + if ( getKillstreakInformEnemy( streakName ) ) + thread leaderDialog( team + "_enemy_" + streakName + "_inbound", level.otherTeam[ team ] ); + } + else + { + self thread leaderDialogOnPlayer( team + "_friendly_" + streakName + "_inbound" ); + + if ( getKillstreakInformEnemy( streakName ) ) + { + excludeList[0] = self; + thread leaderDialog( team + "_enemy_" + streakName + "_inbound", undefined, undefined, excludeList ); + } + } +} + + +clearKillstreaks() +{ + foreach ( index, streakStruct in self.pers["killstreaks"] ) + self.pers["killstreaks"][index] = undefined; +} + + +killstreakUseWaiter() +{ + self endon( "disconnect" ); + self endon( "finish_death" ); + level endon( "game_ended" ); + + self.lastKillStreak = 0; + if ( !isDefined( self.pers["lastEarnedStreak"] ) ) + self.pers["lastEarnedStreak"] = undefined; + + self thread finishDeathWaiter(); + + for ( ;; ) + { + self waittill ( "weapon_change", newWeapon ); + + if ( !isAlive( self ) ) + continue; + + if ( !isDefined( self.pers["killstreaks"][0] ) ) + continue; + + if ( newWeapon != getKillstreakWeapon( self.pers["killstreaks"][0].streakName ) ) + continue; + + waittillframeend; + + streakName = self.pers["killstreaks"][0].streakName; + result = self killstreakUsePressed(); + + //no force switching weapon for ridable killstreaks + if ( !isRideKillstreak( streakName ) || !result ) + self switchToWeapon( self getLastWeapon() ); + + // give time to switch to the near weapon; when the weapon is none (such as during a "disableWeapon()" period + // re-enabling the weapon immediately does a "weapon_change" to the killstreak weapon we just used. In the case that + // we have two of that killstreak, it immediately uses the second one + if ( self getCurrentWeapon() == "none" ) + { + while ( self getCurrentWeapon() == "none" ) + wait ( 0.05 ); + + waittillframeend; + } + } +} + + +finishDeathWaiter() +{ + self endon ( "disconnect" ); + + self waittill ( "death" ); + wait ( 0.05 ); + self notify ( "finish_death" ); + self.pers["lastEarnedStreak"] = undefined; +} + + +checkKillstreakReward( streakCount ) +{ + self notify( "got_killstreak", streakCount ); + + maxVal = 0; + killStreaks = []; + foreach ( streakVal, streakName in self.killStreaks ) + { + killStreaks[streakName] = streakVal; + if ( streakVal > maxVal ) + maxVal = streakVal; + } + + foreach ( streakVal, streakName in self.killStreaks ) + { + actualVal = streakVal + level.killStreakMod; + + if ( actualVal > streakCount ) + break; + + if ( isDefined( self.pers["lastEarnedStreak"] ) && killStreaks[streakName] <= killStreaks[self.pers["lastEarnedStreak"]] ) + continue; + + if ( isSubStr( streakName, "-rollover" ) ) + { + continue; + /* + if ( game["defcon"] > 2 ) + { + self.pers["lastEarnedStreak"] = streakName; + continue; + } + + useStreakName = strTok( streakName, "-" )[0]; + */ + } + else + { + useStreakName = streakName; + } + + if ( self tryGiveKillstreak( useStreakName, int(max( actualVal, streakCount )) ) ) + { + self thread killstreakEarned( useStreakName ); + self.pers["lastEarnedStreak"] = streakName; + } + } +} + + +killstreakEarned( streakName ) +{ + if ( self getPlayerData( "killstreaks", 0 ) == streakName ) + { + self.firstKillstreakEarned = getTime(); + } + else if ( self getPlayerData( "killstreaks", 2 ) == streakName && isDefined( self.firstKillstreakEarned ) ) + { + if ( getTime() - self.firstKillstreakEarned < 20000 ) + self thread maps\mp\gametypes\_missions::genericChallenge( "wargasm" ); + } +} + + +rewardNotify( streakName, streakVal ) +{ + self endon( "disconnect" ); + + self maps\mp\gametypes\_hud_message::killstreakSplashNotify( streakName, streakVal ); +} + + +tryGiveKillstreak( streakName, streakVal ) +{ + level notify ( "gave_killstreak", streakName ); + + if ( !level.gameEnded ) + self thread rewardNotify( streakName, streakVal ); + + self giveKillstreak( streakName, streakVal, true ); + return true; +} + + +giveKillstreak( streakName, isEarned, awardXp, owner ) +{ + self endon ( "disconnect" ); + + weapon = getKillstreakWeapon( streakName ); + + self giveKillstreakWeapon( weapon ); + + // shuffle existing killstreaks up a notch + for( i = self.pers["killstreaks"].size; i >= 0; i-- ) + self.pers["killstreaks"][i + 1] = self.pers["killstreaks"][i]; + + self.pers["killstreaks"][0] = spawnStruct(); + self.pers["killstreaks"][0].streakName = streakName; + self.pers["killstreaks"][0].earned = isDefined( isEarned ) && isEarned; + self.pers["killstreaks"][0].awardxp = isDefined( awardXp ) && awardXp; + self.pers["killstreaks"][0].owner = owner; + if ( !self.pers["killstreaks"][0].earned ) + self.pers["killstreaks"][0].lifeId = -1; + else + self.pers["killstreaks"][0].lifeId = self.pers["deaths"]; + + // probably obsolete unless we bring back the autoshotty + if ( isdefined( level.killstreakSetupFuncs[ streakName ] ) ) + self [[ level.killstreakSetupFuncs[ streakName ] ]](); + + if ( isDefined( isEarned ) && isEarned && isDefined( awardXp ) && awardXp ) + self notify( "received_earned_killstreak" ); +} + + +giveKillstreakWeapon( weapon ) +{ + weaponList = self getWeaponsListItems(); + + foreach ( item in weaponList ) + { + if ( !isSubStr( item, "killstreak" ) ) + continue; + + if ( self getCurrentWeapon() == item ) + continue; + + self takeWeapon( item ); + } + + self _giveWeapon( weapon, 0 ); + self _setActionSlot( 4, "weapon", weapon ); +} + + +getStreakCost( streakName ) +{ + return int( tableLookup( KILLSTREAK_STRING_TABLE, 1, streakName, 4 ) ); +} + + +getKillstreakHint( streakName ) +{ + return tableLookupIString( KILLSTREAK_STRING_TABLE, 1, streakName, 6 ); +} + + +getKillstreakInformEnemy( streakName ) +{ + return int( tableLookup( KILLSTREAK_STRING_TABLE, 1, streakName, 11 ) ); +} + + +getKillstreakSound( streakName ) +{ + return tableLookup( KILLSTREAK_STRING_TABLE, 1, streakName, 7 ); +} + + +getKillstreakDialog( streakName ) +{ + return tableLookup( KILLSTREAK_STRING_TABLE, 1, streakName, 8 ); +} + + +getKillstreakWeapon( streakName ) +{ + return tableLookup( KILLSTREAK_STRING_TABLE, 1, streakName, 12 ); +} + +getKillstreakIcon( streakName ) +{ + return tableLookup( KILLSTREAK_STRING_TABLE, 1, streakName, 14 ); +} + +getKillstreakCrateIcon( streakName ) +{ + return tableLookup( KILLSTREAK_STRING_TABLE, 1, streakName, 15 ); +} + + +giveOwnedKillstreakItem( skipDialog ) +{ + if ( !isDefined( self.pers["killstreaks"][0] ) ) + return; + + streakName = self.pers["killstreaks"][0].streakName; + + weapon = getKillstreakWeapon( streakName ); + self giveKillstreakWeapon( weapon ); + + if ( !isDefined( skipDialog ) && !level.inGracePeriod ) + self leaderDialogOnPlayer( streakName, "killstreak_earned" ); +} + + +initRideKillstreak() +{ + self _disableUsability(); + result = self initRideKillstreak_internal(); + + if ( isDefined( self ) ) + self _enableUsability(); + + return result; +} + +initRideKillstreak_internal() +{ + laptopWait = self waittill_any_timeout( 1.0, "disconnect", "death", "weapon_switch_started" ); + + if ( laptopWait == "weapon_switch_started" ) + return ( "fail" ); + + if ( !isAlive( self ) ) + return "fail"; + + if ( laptopWait == "disconnect" || laptopWait == "death" ) + { + if ( laptopWait == "disconnect" ) + return ( "disconnect" ); + + if ( self.team == "spectator" ) + return "fail"; + + return ( "success" ); + } + + if ( self isEMPed() || self isNuked() ) + { + return ( "fail" ); + } + + self VisionSetNakedForPlayer( "black_bw", 0.75 ); + blackOutWait = self waittill_any_timeout( 0.80, "disconnect", "death" ); + + if ( blackOutWait != "disconnect" ) + { + self thread clearRideIntro( 1.0 ); + + if ( self.team == "spectator" ) + return "fail"; + } + + if ( !isAlive( self ) ) + return "fail"; + + if ( self isEMPed() || self isNuked() ) + return "fail"; + + if ( blackOutWait == "disconnect" ) + return ( "disconnect" ); + else + return ( "success" ); +} + + +clearRideIntro( delay ) +{ + self endon( "disconnect" ); + + if ( isDefined( delay ) ) + wait( delay ); + + //self freezeControlsWrapper( false ); + + if ( !isDefined( level.nukeVisionInProgress ) ) + self VisionSetNakedForPlayer( getDvar( "mapname" ), 0 ); +} + + diff --git a/userraw/maps/mp/killstreaks/_nuke.gsc b/userraw/maps/mp/killstreaks/_nuke.gsc new file mode 100644 index 0000000..c3f3f96 --- /dev/null +++ b/userraw/maps/mp/killstreaks/_nuke.gsc @@ -0,0 +1,316 @@ +#include common_scripts\utility; +#include maps\mp\_utility; + +init() +{ + precacheItem( "nuke_mp" ); + precacheLocationSelector( "map_nuke_selector" ); + precacheString( &"MP_TACTICAL_NUKE_CALLED" ); + precacheString( &"MP_FRIENDLY_TACTICAL_NUKE" ); + precacheString( &"MP_TACTICAL_NUKE" ); + + level._effect[ "nuke_player" ] = loadfx( "explosions/player_death_nuke" ); + level._effect[ "nuke_flash" ] = loadfx( "explosions/player_death_nuke_flash" ); + level._effect[ "nuke_aftermath" ] = loadfx( "dust/nuke_aftermath_mp" ); + + game["strings"]["nuclear_strike"] = &"MP_TACTICAL_NUKE"; + + level.killstreakFuncs["nuke"] = ::tryUseNuke; + + setDvarIfUninitialized( "scr_nukeTimer", 10 ); + setDvarIfUninitialized( "scr_nukeCancelMode", 0 ); + + level.nukeTimer = getDvarInt( "scr_nukeTimer" ); + level.cancelMode = getDvarInt( "scr_nukeCancelMode" ); + + /# + setDevDvarIfUninitialized( "scr_nukeDistance", 5000 ); + setDevDvarIfUninitialized( "scr_nukeEndsGame", true ); + setDevDvarIfUninitialized( "scr_nukeDebugPosition", false ); + #/ +} + +tryUseNuke( lifeId, allowCancel ) +{ + if( isDefined( level.nukeIncoming ) ) + { + self iPrintLnBold( &"MP_NUKE_ALREADY_INBOUND" ); + return false; + } + + if ( self isUsingRemote() && ( !isDefined( level.gtnw ) || !level.gtnw ) ) + return false; + + if ( !isDefined( allowCancel ) ) + allowCancel = true; + + self thread doNuke( allowCancel ); + self notify( "used_nuke" ); + + return true; +} + +delaythread_nuke( delay, func ) +{ + level endon ( "nuke_cancelled" ); + + wait ( delay ); + + thread [[ func ]](); +} + +doNuke( allowCancel ) +{ + level endon ( "nuke_cancelled" ); + + level.nukeInfo = spawnStruct(); + level.nukeInfo.player = self; + level.nukeInfo.team = self.pers["team"]; + + level.nukeIncoming = true; + + maps\mp\gametypes\_gamelogic::pauseTimer(); + level.timeLimitOverride = true; + setGameEndTime( int( gettime() + (level.nukeTimer * 1000) ) ); + setDvar( "ui_bomb_timer", 4 ); // Nuke sets '4' to avoid briefcase icon showing + + if ( level.teambased ) + { + thread teamPlayerCardSplash( "used_nuke", self, self.team ); + /* + players = level.players; + + foreach( player in level.players ) + { + playerteam = player.pers["team"]; + if ( isdefined( playerteam ) ) + { + if ( playerteam == self.pers["team"] ) + player iprintln( &"MP_TACTICAL_NUKE_CALLED", self ); + } + } + */ + } + else + { + if ( !level.hardcoreMode ) + self iprintlnbold(&"MP_FRIENDLY_TACTICAL_NUKE"); + } + + level thread delaythread_nuke( (level.nukeTimer - 3.3), ::nukeSoundIncoming ); + level thread delaythread_nuke( level.nukeTimer, ::nukeSoundExplosion ); + level thread delaythread_nuke( level.nukeTimer, ::nukeSlowMo ); + level thread delaythread_nuke( level.nukeTimer, ::nukeEffects ); + level thread delaythread_nuke( (level.nukeTimer + 0.25), ::nukeVision ); + level thread delaythread_nuke( (level.nukeTimer + 1.5), ::nukeDeath ); + level thread delaythread_nuke( (level.nukeTimer + 1.5), ::nukeEarthquake ); + level thread nukeAftermathEffect(); + + if ( level.cancelMode && allowCancel ) + level thread cancelNukeOnDeath( self ); + + // leaks if lots of nukes are called due to endon above. + clockObject = spawn( "script_origin", (0,0,0) ); + clockObject hide(); + + while ( !isDefined( level.nukeDetonated ) ) + { + clockObject playSound( "ui_mp_nukebomb_timer" ); + wait( 1.0 ); + } +} + +cancelNukeOnDeath( player ) +{ + player waittill_any( "death", "disconnect" ); + + if ( isDefined( player ) && level.cancelMode == 2 ) + player thread maps\mp\killstreaks\_emp::EMP_Use( 0, 0 ); + + + maps\mp\gametypes\_gamelogic::resumeTimer(); + level.timeLimitOverride = false; + + setDvar( "ui_bomb_timer", 0 ); // Nuke sets '4' to avoid briefcase icon showing + + level notify ( "nuke_cancelled" ); +} + +nukeSoundIncoming() +{ + level endon ( "nuke_cancelled" ); + + foreach( player in level.players ) + player playlocalsound( "nuke_incoming" ); +} + +nukeSoundExplosion() +{ + level endon ( "nuke_cancelled" ); + + foreach( player in level.players ) + { + player playlocalsound( "nuke_explosion" ); + player playlocalsound( "nuke_wave" ); + } +} + +nukeEffects() +{ + level endon ( "nuke_cancelled" ); + + setDvar( "ui_bomb_timer", 0 ); + setGameEndTime( 0 ); + + level.nukeDetonated = true; + level maps\mp\killstreaks\_emp::destroyActiveVehicles( level.nukeInfo.player ); + + foreach( player in level.players ) + { + playerForward = anglestoforward( player.angles ); + playerForward = ( playerForward[0], playerForward[1], 0 ); + playerForward = VectorNormalize( playerForward ); + + nukeDistance = 5000; + /# nukeDistance = getDvarInt( "scr_nukeDistance" ); #/ + + nukeEnt = Spawn( "script_model", player.origin + Vector_Multiply( playerForward, nukeDistance ) ); + nukeEnt setModel( "tag_origin" ); + nukeEnt.angles = ( 0, (player.angles[1] + 180), 90 ); + + /# + if ( getDvarInt( "scr_nukeDebugPosition" ) ) + { + lineTop = ( nukeEnt.origin[0], nukeEnt.origin[1], (nukeEnt.origin[2] + 500) ); + thread draw_line_for_time( nukeEnt.origin, lineTop, 1, 0, 0, 10 ); + } + #/ + + nukeEnt thread nukeEffect( player ); + player.nuked = true; + } +} + +nukeEffect( player ) +{ + level endon ( "nuke_cancelled" ); + + player endon( "disconnect" ); + + waitframe(); + PlayFXOnTagForClients( level._effect[ "nuke_flash" ], self, "tag_origin", player ); +} + +nukeAftermathEffect() +{ + level endon ( "nuke_cancelled" ); + + level waittill ( "spawning_intermission" ); + + afermathEnt = getEntArray( "mp_global_intermission", "classname" ); + afermathEnt = afermathEnt[0]; + up = anglestoup( afermathEnt.angles ); + right = anglestoright( afermathEnt.angles ); + + PlayFX( level._effect[ "nuke_aftermath" ], afermathEnt.origin, up, right ); +} + +nukeSlowMo() +{ + level endon ( "nuke_cancelled" ); + + //SetSlowMotion( , , ) + setSlowMotion( 1.0, 0.25, 0.5 ); + level waittill( "nuke_death" ); + setSlowMotion( 0.25, 1, 2.0 ); +} + +nukeVision() +{ + level endon ( "nuke_cancelled" ); + + level.nukeVisionInProgress = true; + visionSetNaked( "mpnuke", 3 ); + + level waittill( "nuke_death" ); + + visionSetNaked( "mpnuke_aftermath", 5 ); + wait 5; + level.nukeVisionInProgress = undefined; +} + +nukeDeath() +{ + level endon ( "nuke_cancelled" ); + + level notify( "nuke_death" ); + + maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone(); + + AmbientStop(1); + + foreach( player in level.players ) + { + if ( isAlive( player ) ) + player thread maps\mp\gametypes\_damage::finishPlayerDamageWrapper( level.nukeInfo.player, level.nukeInfo.player, 999999, 0, "MOD_EXPLOSIVE", "nuke_mp", player.origin, player.origin, "none", 0, 0 ); + } + + level.postRoundTime = 10; + + nukeEndsGame = true; + + if ( level.teamBased ) + thread maps\mp\gametypes\_gamelogic::endGame( level.nukeInfo.team, game["strings"]["nuclear_strike"], true ); + else + { + if ( isDefined( level.nukeInfo.player ) ) + thread maps\mp\gametypes\_gamelogic::endGame( level.nukeInfo.player, game["strings"]["nuclear_strike"], true ); + else + thread maps\mp\gametypes\_gamelogic::endGame( level.nukeInfo, game["strings"]["nuclear_strike"], true ); + } +} + +nukeEarthquake() +{ + level endon ( "nuke_cancelled" ); + + level waittill( "nuke_death" ); + + // TODO: need to get a different position to call this on + //earthquake( 0.6, 10, nukepos, 100000 ); + + //foreach( player in level.players ) + //player PlayRumbleOnEntity( "damage_heavy" ); +} + + +waitForNukeCancel() +{ + self waittill( "cancel_location" ); + self setblurforplayer( 0, 0.3 ); +} + +endSelectionOn( waitfor ) +{ + self endon( "stop_location_selection" ); + self waittill( waitfor ); + self thread stopNukeLocationSelection( (waitfor == "disconnect") ); +} + +endSelectionOnGameEnd() +{ + self endon( "stop_location_selection" ); + level waittill( "game_ended" ); + self thread stopNukeLocationSelection( false ); +} + +stopNukeLocationSelection( disconnected ) +{ + if ( !disconnected ) + { + self setblurforplayer( 0, 0.3 ); + self endLocationSelection(); + self.selectingLocation = undefined; + } + self notify( "stop_location_selection" ); +} diff --git a/userraw/maps/mp/perks/_perkfunctions.gsc b/userraw/maps/mp/perks/_perkfunctions.gsc new file mode 100644 index 0000000..cbe345e --- /dev/null +++ b/userraw/maps/mp/perks/_perkfunctions.gsc @@ -0,0 +1,1011 @@ +/******************************************************************* +// _perkfunctions.gsc +// +// Holds all the perk set/unset and listening functions +// +// Jordan Hirsh Sept. 11th 2008 +********************************************************************/ + +#include common_scripts\utility; +#include maps\mp\_utility; +#include maps\mp\gametypes\_hud_util; +#include maps\mp\perks\_perks; + + +blastshieldUseTracker( perkName, useFunc ) +{ + self endon ( "death" ); + self endon ( "disconnect" ); + self endon ( "end_perkUseTracker" ); + level endon ( "game_ended" ); + + for ( ;; ) + { + self waittill ( "empty_offhand" ); + + if ( !isOffhandWeaponEnabled() ) + continue; + + self [[useFunc]]( self _hasPerk( "_specialty_blastshield" ) ); + } +} + +perkUseDeathTracker() +{ + self endon ( "disconnect" ); + + self waittill("death"); + self._usePerkEnabled = undefined; +} + +setRearView() +{ + //self thread perkUseTracker( "specialty_rearview", ::toggleRearView ); +} + +unsetRearView() +{ + self notify ( "end_perkUseTracker" ); +} + +toggleRearView( isEnabled ) +{ + if ( isEnabled ) + { + self _setPerk( "_specialty_rearview" ); + self SetRearViewRenderEnabled(true); + } + else + { + self _unsetPerk( "_specialty_rearview" ); + self SetRearViewRenderEnabled(false); + } +} + + +setEndGame() +{ + if ( isdefined( self.endGame ) ) + return; + + self.maxhealth = ( maps\mp\gametypes\_tweakables::getTweakableValue( "player", "maxhealth" ) * 4 ); + self.health = self.maxhealth; + self.endGame = true; + self.attackerTable[0] = ""; + self visionSetNakedForPlayer("end_game", 5 ); + self thread endGameDeath( 7 ); + self.hasDoneCombat = true; +} + + +unsetEndGame() +{ + self notify( "stopEndGame" ); + self.endGame = undefined; + revertVisionSet(); + + if (! isDefined( self.endGameTimer ) ) + return; + + self.endGameTimer destroyElem(); + self.endGameIcon destroyElem(); +} + + +revertVisionSet() +{ + self VisionSetNakedForPlayer( getDvar( "mapname" ), 1 ); +} + +endGameDeath( duration ) +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "joined_team" ); + level endon( "game_ended" ); + self endon( "stopEndGame" ); + + wait( duration + 1 ); + //self visionSetNakedForPlayer("end_game2", 1 ); + //wait(1); + self _suicide(); +} + +setCombatHigh() +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "unset_combathigh" ); + level endon( "end_game" ); + + self.damageBlockedTotal = 0; + //self visionSetNakedForPlayer( "end_game", 1 ); + + if ( level.splitscreen ) + { + yOffset = 56; + iconSize = 21; // 32/1.5 + } + else + { + yOffset = 112; + iconSize = 32; + } + + self.combatHighOverlay = newClientHudElem( self ); + self.combatHighOverlay.x = 0; + self.combatHighOverlay.y = 0; + self.combatHighOverlay.alignX = "left"; + self.combatHighOverlay.alignY = "top"; + self.combatHighOverlay.horzAlign = "fullscreen"; + self.combatHighOverlay.vertAlign = "fullscreen"; + self.combatHighOverlay setshader ( "combathigh_overlay", 640, 480 ); + self.combatHighOverlay.sort = -10; + self.combatHighOverlay.archived = true; + + self.combatHighTimer = createTimer( "hudsmall", 1.0 ); + self.combatHighTimer setPoint( "CENTER", "CENTER", 0, yOffset ); + self.combatHighTimer setTimer( 10.0 ); + self.combatHighTimer.color = (.8,.8,0); + self.combatHighTimer.archived = false; + self.combatHighTimer.foreground = true; + + self.combatHighIcon = self createIcon( "specialty_painkiller", iconSize, iconSize ); + self.combatHighIcon.alpha = 0; + self.combatHighIcon setParent( self.combatHighTimer ); + self.combatHighIcon setPoint( "BOTTOM", "TOP" ); + self.combatHighIcon.archived = true; + self.combatHighIcon.sort = 1; + self.combatHighIcon.foreground = true; + + self.combatHighOverlay.alpha = 0.0; + self.combatHighOverlay fadeOverTime( 1.0 ); + self.combatHighIcon fadeOverTime( 1.0 ); + self.combatHighOverlay.alpha = 1.0; + self.combatHighIcon.alpha = 0.85; + + self thread unsetCombatHighOnDeath(); + + wait( 8 ); + + self.combatHighIcon fadeOverTime( 2.0 ); + self.combatHighIcon.alpha = 0.0; + + self.combatHighOverlay fadeOverTime( 2.0 ); + self.combatHighOverlay.alpha = 0.0; + + self.combatHighTimer fadeOverTime( 2.0 ); + self.combatHighTimer.alpha = 0.0; + + wait( 2 ); + self.damageBlockedTotal = undefined; + + self _unsetPerk( "specialty_combathigh" ); +} + +unsetCombatHighOnDeath() +{ + self endon ( "disconnect" ); + self endon ( "unset_combathigh" ); + + self waittill ( "death" ); + + self thread _unsetPerk( "specialty_combathigh" ); +} + +unsetCombatHigh() +{ + self notify ( "unset_combathigh" ); + self.combatHighOverlay destroy(); + self.combatHighIcon destroy(); + self.combatHighTimer destroy(); +} + +setSiege() +{ + self thread trackSiegeEnable(); + self thread trackSiegeDissable(); +} + +trackSiegeEnable() +{ + self endon ( "death" ); + self endon ( "disconnect" ); + self endon ( "stop_trackSiege" ); + + for ( ;; ) + { + self waittill ( "gambit_on" ); + + //self setStance( "crouch" ); + //self thread stanceStateListener(); + //self thread jumpStateListener(); + self.moveSpeedScaler = 0; + self maps\mp\gametypes\_weapons::updateMoveSpeedScale( "primary" ); + class = weaponClass( self getCurrentWeapon() ); + + if ( class == "pistol" || class == "smg" ) + self setSpreadOverride( 1 ); + else + self setSpreadOverride( 2 ); + + self player_recoilScaleOn( 0 ); + self allowJump(false); + } +} + +trackSiegeDissable() +{ + self endon ( "death" ); + self endon ( "disconnect" ); + self endon ( "stop_trackSiege" ); + + for ( ;; ) + { + self waittill ( "gambit_off" ); + + unsetSiege(); + } +} + +stanceStateListener() +{ + self endon ( "death" ); + self endon ( "disconnect" ); + + self notifyOnPlayerCommand( "adjustedStance", "+stance" ); + + for ( ;; ) + { + self waittill( "adjustedStance" ); + if ( self.moveSPeedScaler != 0 ) + continue; + + unsetSiege(); + } +} + +jumpStateListener() +{ + self endon ( "death" ); + self endon ( "disconnect" ); + + self notifyOnPlayerCommand( "jumped", "+goStand" ); + + for ( ;; ) + { + self waittill( "jumped" ); + if ( self.moveSPeedScaler != 0 ) + continue; + + unsetSiege(); + } +} + +unsetSiege() +{ + self.moveSpeedScaler = 1; + //if siege is not cut add check to see if + //using lightweight and siege for movespeed scaler + self resetSpreadOverride(); + self maps\mp\gametypes\_weapons::updateMoveSpeedScale( "primary" ); + self player_recoilScaleOff(); + self allowJump(true); +} + + +setFinalStand() +{ + self _setperk( "specialty_pistoldeath"); +} + +unsetFinalStand() +{ + self _unsetperk( "specialty_pistoldeath" ); +} + + +setChallenger() +{ + if ( !level.hardcoreMode ) + { + self.maxhealth = maps\mp\gametypes\_tweakables::getTweakableValue( "player", "maxhealth" ); + + if ( isDefined( self.xpScaler ) && self.xpScaler == 1 && self.maxhealth > 30 ) + { + self.xpScaler = 2; + } + } +} + +unsetChallenger() +{ + self.xpScaler = 1; +} + + +setSaboteur() +{ + self.objectiveScaler = 1.2; +} + +unsetSaboteur() +{ + self.objectiveScaler = 1; +} + + +setLightWeight() +{ + self.moveSpeedScaler = 1.10; + self maps\mp\gametypes\_weapons::updateMoveSpeedScale( "primary" ); +} + +unsetLightWeight() +{ + self.moveSpeedScaler = 1; + self maps\mp\gametypes\_weapons::updateMoveSpeedScale( "primary" ); +} + + +setBlackBox() +{ + self.killStreakScaler = 1.5; +} + +unsetBlackBox() +{ + self.killStreakScaler = 1; +} + +setSteelNerves() +{ + self _setperk( "specialty_bulletaccuracy" ); + self _setperk( "specialty_holdbreath" ); +} + +unsetSteelNerves() +{ + self _unsetperk( "specialty_bulletaccuracy" ); + self _unsetperk( "specialty_holdbreath" ); +} + +setDelayMine() +{ +} + +unsetDelayMine() +{ +} + + +setBackShield() +{ + self AttachShieldModel( "weapon_riot_shield_mp", "tag_shield_back" ); +} + + +unsetBackShield() +{ + self DetachShieldModel( "weapon_riot_shield_mp", "tag_shield_back" ); +} + + +setLocalJammer() +{ + if ( !self isEMPed() ) + self RadarJamOn(); +} + + +unsetLocalJammer() +{ + self RadarJamOff(); +} + + +setAC130() +{ + self thread killstreakThink( "ac130", 7, "end_ac130Think" ); +} + +unsetAC130() +{ + self notify ( "end_ac130Think" ); +} + + +setSentryMinigun() +{ + self thread killstreakThink( "airdrop_sentry_minigun", 2, "end_sentry_minigunThink" ); +} + +unsetSentryMinigun() +{ + self notify ( "end_sentry_minigunThink" ); +} + +setCarePackage() +{ + self thread killstreakThink( "airdrop", 2, "endCarePackageThink" ); +} + +unsetCarePackage() +{ + self notify ( "endCarePackageThink" ); +} + +setTank() +{ + self thread killstreakThink( "tank", 6, "end_tankThink" ); +} + +unsetTank() +{ + self notify ( "end_tankThink" ); +} + +setPrecision_airstrike() +{ + println( "!precision airstrike!" ); + self thread killstreakThink( "precision_airstrike", 6, "end_precision_airstrike" ); +} + +unsetPrecision_airstrike() +{ + self notify ( "end_precision_airstrike" ); +} + +setPredatorMissile() +{ + self thread killstreakThink( "predator_missile", 4, "end_predator_missileThink" ); +} + +unsetPredatorMissile() +{ + self notify ( "end_predator_missileThink" ); +} + + +setHelicopterMinigun() +{ + self thread killstreakThink( "helicopter_minigun", 5, "end_helicopter_minigunThink" ); +} + +unsetHelicopterMinigun() +{ + self notify ( "end_helicopter_minigunThink" ); +} + + + +killstreakThink( streakName, streakVal, endonString ) +{ + self endon ( "death" ); + self endon ( "disconnect" ); + self endon ( endonString ); + + for ( ;; ) + { + self waittill ( "killed_enemy" ); + + if ( self.pers["cur_kill_streak"] != streakVal ) + continue; + + self thread maps\mp\killstreaks\_killstreaks::giveKillstreak( streakName ); + self thread maps\mp\gametypes\_hud_message::killstreakSplashNotify( streakName, streakVal ); + return; + } +} + + +setThermal() +{ + self ThermalVisionOn(); +} + + +unsetThermal() +{ + self ThermalVisionOff(); +} + + +setOneManArmy() +{ + self thread oneManArmyWeaponChangeTracker(); +} + + +unsetOneManArmy() +{ + self notify ( "stop_oneManArmyTracker" ); +} + + +oneManArmyWeaponChangeTracker() +{ + self endon ( "death" ); + self endon ( "disconnect" ); + level endon ( "game_ended" ); + self endon ( "stop_oneManArmyTracker" ); + + for ( ;; ) + { + self waittill( "weapon_change", newWeapon ); + + if ( newWeapon != "onemanarmy_mp" ) + continue; + + //if ( self isUsingRemote() ) + // continue; + + self thread selectOneManArmyClass(); + } +} + + +isOneManArmyMenu( menu ) +{ + if ( menu == game["menu_onemanarmy"] ) + return true; + + if ( isDefined( game["menu_onemanarmy_defaults_splitscreen"] ) && menu == game["menu_onemanarmy_defaults_splitscreen"] ) + return true; + + if ( isDefined( game["menu_onemanarmy_custom_splitscreen"] ) && menu == game["menu_onemanarmy_custom_splitscreen"] ) + return true; + + return false; +} + + +selectOneManArmyClass() +{ + self endon ( "death" ); + self endon ( "disconnect" ); + level endon ( "game_ended" ); + + self _disableWeaponSwitch(); + + self openPopupMenu( game["menu_onemanarmy"] ); + + self thread closeOMAMenuOnDeath(); + + self waittill ( "menuresponse", menu, className ); + + self _enableWeaponSwitch(); + + if ( className == "back" || !isOneManArmyMenu( menu ) || self isUsingRemote() ) + { + if ( self getCurrentWeapon() == "onemanarmy_mp" ) + { + self _disableWeaponSwitch(); + self switchToWeapon( self getLastWeapon() ); + self waittill ( "weapon_change" ); + self _enableWeaponSwitch(); + } + return; + } + + self thread giveOneManArmyClass( className ); +} + +closeOMAMenuOnDeath() +{ + self endon ( "menuresponse" ); + self endon ( "disconnect" ); + level endon ( "game_ended" ); + + self waittill ( "death" ); + + self _enableWeaponSwitch(); + + self closePopupMenu(); +} + +giveOneManArmyClass( className ) +{ + self endon ( "death" ); + self endon ( "disconnect" ); + level endon ( "game_ended" ); + + if ( self _hasPerk( "specialty_omaquickchange" ) ) + { + changeDuration = 3.0; + self playLocalSound( "foly_onemanarmy_bag3_plr" ); + self playSoundToTeam( "foly_onemanarmy_bag3_npc", "allies", self ); + self playSoundToTeam( "foly_onemanarmy_bag3_npc", "axis", self ); + } + else + { + changeDuration = 6.0; + self playLocalSound( "foly_onemanarmy_bag6_plr" ); + self playSoundToTeam( "foly_onemanarmy_bag6_npc", "allies", self ); + self playSoundToTeam( "foly_onemanarmy_bag6_npc", "axis", self ); + } + + self thread omaUseBar( changeDuration ); + + self _disableWeapon(); + self _disableOffhandWeapons(); + + wait ( changeDuration ); + + self _enableWeapon(); + self _enableOffhandWeapons(); + self.OMAClassChanged = true; + + self maps\mp\gametypes\_class::giveLoadout( self.pers["team"], className, false ); + + // handle the fact that detachAll in giveLoadout removed the CTF flag from our back + // it would probably be better to handle this in _detachAll itself, but this is a safety fix + if ( isDefined( self.carryFlag ) ) + self attach( self.carryFlag, "J_spine4", true ); + + self notify ( "changed_kit" ); + level notify ( "changed_kit" ); +} + + +omaUseBar( duration ) +{ + self endon( "disconnect" ); + + useBar = createPrimaryProgressBar( 25 ); + useBarText = createPrimaryProgressBarText( 25 ); + useBarText setText( &"MPUI_CHANGING_KIT" ); + + useBar updateBar( 0, 1 / duration ); + for ( waitedTime = 0; waitedTime < duration && isAlive( self ) && !level.gameEnded; waitedTime += 0.05 ) + wait ( 0.05 ); + + useBar destroyElem(); + useBarText destroyElem(); +} + + +setBlastShield() +{ + self thread blastshieldUseTracker( "specialty_blastshield", ::toggleBlastShield ); + self SetWeaponHudIconOverride( "primaryoffhand", "specialty_blastshield" ); +} + + +unsetBlastShield() +{ + self notify ( "end_perkUseTracker" ); + self SetWeaponHudIconOverride( "primaryoffhand", "none" ); +} + +toggleBlastShield( isEnabled ) +{ + if ( !isEnabled ) + { + self VisionSetNakedForPlayer( "black_bw", 0.15 ); + wait ( 0.15 ); + self _setPerk( "_specialty_blastshield" ); + self VisionSetNakedForPlayer( getDvar( "mapname" ), 0 ); + self playSoundToPlayer( "item_blast_shield_on", self ); + } + else + { + self VisionSetNakedForPlayer( "black_bw", 0.15 ); + wait ( 0.15 ); + self _unsetPerk( "_specialty_blastshield" ); + self VisionSetNakedForPlayer( getDvar( "mapname" ), 0 ); + self playSoundToPlayer( "item_blast_shield_off", self ); + } +} + + +setFreefall() +{ + //eventually set a listener to do a roll when falling damage is taken +} + +unsetFreefall() +{ +} + + +setTacticalInsertion() +{ + self _giveWeapon( "flare_mp", 0 ); + self giveStartAmmo( "flare_mp" ); + + self thread monitorTIUse(); +} + +unsetTacticalInsertion() +{ + self notify( "end_monitorTIUse" ); +} + +clearPreviousTISpawnpoint() +{ + self waittill_any ( "disconnect", "joined_team", "joined_spectators" ); + + if ( isDefined ( self.setSpawnpoint ) ) + self deleteTI( self.setSpawnpoint ); +} + +updateTISpawnPosition() +{ + self endon ( "death" ); + self endon ( "disconnect" ); + level endon ( "game_ended" ); + self endon ( "end_monitorTIUse" ); + + while ( isReallyAlive( self ) ) + { + if ( self isValidTISpawnPosition() ) + self.TISpawnPosition = self.origin; + + wait ( 0.05 ); + } +} + +isValidTISpawnPosition() +{ + if ( CanSpawn( self.origin ) && self IsOnGround() ) + return true; + else + return false; +} + +monitorTIUse() +{ + self endon ( "death" ); + self endon ( "disconnect" ); + level endon ( "game_ended" ); + self endon ( "end_monitorTIUse" ); + + self thread updateTISpawnPosition(); + self thread clearPreviousTISpawnpoint(); + + for ( ;; ) + { + self waittill( "grenade_fire", lightstick, weapName ); + + if ( weapName != "flare_mp" ) + continue; + + //lightstick delete(); + + if ( isDefined( self.setSpawnPoint ) ) + self deleteTI( self.setSpawnPoint ); + + if ( !isDefined( self.TISpawnPosition ) ) + continue; + + if ( self touchingBadTrigger() ) + continue; + + TIGroundPosition = playerPhysicsTrace( self.TISpawnPosition + (0,0,16), self.TISpawnPosition - (0,0,2048) ) + (0,0,1); + + glowStick = spawn( "script_model", TIGroundPosition ); + glowStick.angles = self.angles; + glowStick.team = self.team; + glowStick.enemyTrigger = spawn( "script_origin", TIGroundPosition ); + glowStick thread GlowStickSetupAndWaitForDeath( self ); + glowStick.playerSpawnPos = self.TISpawnPosition; + + glowStick thread maps\mp\gametypes\_weapons::createBombSquadModel( "weapon_light_stick_tactical_bombsquad", "tag_fire_fx", level.otherTeam[self.team], self ); + + self.setSpawnPoint = glowStick; + return; + } +} + + +GlowStickSetupAndWaitForDeath( owner ) +{ + self setModel( level.spawnGlowModel["enemy"] ); + if ( level.teamBased ) + self maps\mp\_entityheadIcons::setTeamHeadIcon( self.team , (0,0,20) ); + else + self maps\mp\_entityheadicons::setPlayerHeadIcon( owner, (0,0,20) ); + + self thread GlowStickDamageListener( owner ); + self thread GlowStickEnemyUseListener( owner ); + self thread GlowStickUseListener( owner ); + self thread GlowStickTeamUpdater( level.otherTeam[self.team], level.spawnGlow["enemy"], owner ); + + dummyGlowStick = spawn( "script_model", self.origin+ (0,0,0) ); + dummyGlowStick.angles = self.angles; + dummyGlowStick setModel( level.spawnGlowModel["friendly"] ); + dummyGlowStick setContents( 0 ); + dummyGlowStick thread GlowStickTeamUpdater( self.team, level.spawnGlow["friendly"], owner ); + + dummyGlowStick playLoopSound( "emt_road_flare_burn" ); + + self waittill ( "death" ); + + dummyGlowStick stopLoopSound(); + dummyGlowStick delete(); +} + + +GlowStickTeamUpdater( showForTeam, showEffect, owner ) +{ + self endon ( "death" ); + + // PlayFXOnTag fails if run on the same frame the parent entity was created + wait ( 0.05 ); + + //PlayFXOnTag( showEffect, self, "TAG_FX" ); + angles = self getTagAngles( "tag_fire_fx" ); + fxEnt = SpawnFx( showEffect, self getTagOrigin( "tag_fire_fx" ), anglesToForward( angles ), anglesToUp( angles ) ); + TriggerFx( fxEnt ); + + self thread deleteOnDeath( fxEnt ); + + for ( ;; ) + { + self hide(); + fxEnt hide(); + foreach ( player in level.players ) + { + if ( player.team == showForTeam && level.teamBased ) + { + self showToPlayer( player ); + fxEnt showToPlayer( player ); + } + else if ( !level.teamBased && player == owner && showEffect == level.spawnGlow["friendly"] ) + { + self showToPlayer( player ); + fxEnt showToPlayer( player ); + } + else if ( !level.teamBased && player != owner && showEffect == level.spawnGlow["enemy"] ) + { + self showToPlayer( player ); + fxEnt showToPlayer( player ); + } + } + + level waittill_either ( "joined_team", "player_spawned" ); + } +} + +deleteOnDeath( ent ) +{ + self waittill( "death" ); + if ( isdefined( ent ) ) + ent delete(); +} + +GlowStickDamageListener( owner ) +{ + self endon ( "death" ); + + self setCanDamage( true ); + // use large health to work around teamkilling issue + self.health = 5000; + + for ( ;; ) + { + self waittill ( "damage", amount, attacker ); + + if ( level.teambased && isDefined( owner ) && attacker != owner && ( isDefined( attacker.team ) && attacker.team == self.team ) ) + { + self.health += amount; + continue; + } + + if ( self.health < (5000-20) ) + { + if ( isDefined( owner ) && attacker != owner ) + { + attacker notify ( "destroyed_insertion", owner ); + attacker notify( "destroyed_explosive" ); // count towards SitRep Pro challenge + owner thread leaderDialogOnPlayer( "ti_destroyed" ); + } + + attacker thread deleteTI( self ); + } + } +} + +GlowStickUseListener( owner ) +{ + self endon ( "death" ); + level endon ( "game_ended" ); + owner endon ( "disconnect" ); + + self setCursorHint( "HINT_NOICON" ); + self setHintString( &"MP_PICKUP_TI" ); + + self thread updateEnemyUse( owner ); + + for ( ;; ) + { + self waittill ( "trigger", player ); + + player playSound( "chemlight_pu" ); + player thread setTacticalInsertion(); + player thread deleteTI( self ); + } +} + +updateEnemyUse( owner ) +{ + self endon ( "death" ); + + for ( ;; ) + { + self setSelfUsable( owner ); + level waittill_either ( "joined_team", "player_spawned" ); + } +} + +deleteTI( TI ) +{ + if (isDefined( TI.enemyTrigger ) ) + TI.enemyTrigger Delete(); + + spot = TI.origin; + spotAngles = TI.angles; + + TI Delete(); + + dummyGlowStick = spawn( "script_model", spot ); + dummyGlowStick.angles = spotAngles; + dummyGlowStick setModel( level.spawnGlowModel["friendly"] ); + + dummyGlowStick setContents( 0 ); + thread dummyGlowStickDelete( dummyGlowStick ); +} + +dummyGlowStickDelete( stick ) +{ + wait(2.5); + stick Delete(); +} + +GlowStickEnemyUseListener( owner ) +{ + self endon ( "death" ); + level endon ( "game_ended" ); + owner endon ( "disconnect" ); + + self.enemyTrigger setCursorHint( "HINT_NOICON" ); + self.enemyTrigger setHintString( &"MP_DESTROY_TI" ); + self.enemyTrigger makeEnemyUsable( owner ); + + for ( ;; ) + { + self.enemyTrigger waittill ( "trigger", player ); + + player notify ( "destroyed_insertion", owner ); + player notify( "destroyed_explosive" ); // count towards SitRep Pro challenge + + //playFX( level.spawnGlowSplat, self.origin); + + if ( isDefined( owner ) && player != owner ) + owner thread leaderDialogOnPlayer( "ti_destroyed" ); + + player thread deleteTI( self ); + } +} + +setLittlebirdSupport() +{ + self thread killstreakThink( "littlebird_support", 2, "end_littlebird_support_think" ); +} + +unsetLittlebirdSupport() +{ + self notify ( "end_littlebird_support_think" ); +} + +setC4Death() +{ + if ( ! self _hasperk( "specialty_pistoldeath" ) ) + self _setperk( "specialty_pistoldeath"); +} + +unsetC4Death() +{ + +} \ No newline at end of file diff --git a/userraw/maps/mp/perks/_perks.gsc b/userraw/maps/mp/perks/_perks.gsc new file mode 100644 index 0000000..574b933 --- /dev/null +++ b/userraw/maps/mp/perks/_perks.gsc @@ -0,0 +1,400 @@ +#include common_scripts\utility; +#include maps\mp\_utility; +#include maps\mp\gametypes\_hud_util; +#include maps\mp\perks\_perkfunctions; + +init() +{ + level.perkFuncs = []; + + precacheShader( "combathigh_overlay" ); + precacheShader( "specialty_painkiller" ); + + precacheModel( "weapon_riot_shield_mp" ); + precacheModel( "viewmodel_riot_shield_mp" ); + precacheString( &"MPUI_CHANGING_KIT" ); + + //level.spawnGlowSplat = loadfx( "misc/flare_ambient_destroy" ); + + level.spawnGlowModel["enemy"] = "mil_emergency_flare_mp"; + level.spawnGlowModel["friendly"] = "mil_emergency_flare_mp"; + level.spawnGlow["enemy"] = loadfx( "misc/flare_ambient" ); + level.spawnGlow["friendly"] = loadfx( "misc/flare_ambient_green" ); + level.c4Death = loadfx( "explosions/oxygen_tank_explosion" ); + + level.spawnFire = loadfx( "props/barrelexp" ); + + precacheModel( level.spawnGlowModel["friendly"] ); + precacheModel( level.spawnGlowModel["enemy"] ); + + precacheString( &"MP_DESTROY_TI" ); + + precacheShaders(); + + level._effect["ricochet"] = loadfx( "impacts/large_metalhit_1" ); + + // perks that currently only exist in script: these will error if passed to "setPerk", etc... CASE SENSITIVE! must be lower + level.scriptPerks = []; + level.perkSetFuncs = []; + level.perkUnsetFuncs = []; + level.fauxPerks = []; + + level.scriptPerks["specialty_blastshield"] = true; + level.scriptPerks["_specialty_blastshield"] = true; + level.scriptPerks["specialty_akimbo"] = true; + level.scriptPerks["specialty_siege"] = true; + level.scriptPerks["specialty_falldamage"] = true; + level.scriptPerks["specialty_fmj"] = true; + level.scriptPerks["specialty_shield"] = true; + level.scriptPerks["specialty_feigndeath"] = true; + level.scriptPerks["specialty_shellshock"] = true; + level.scriptPerks["specialty_delaymine"] = true; + level.scriptPerks["specialty_localjammer"] = true; + level.scriptPerks["specialty_thermal"] = true; + level.scriptPerks["specialty_finalstand"] = true; + level.scriptPerks["specialty_blackbox"] = true; + level.scriptPerks["specialty_steelnerves"] = true; + level.scriptPerks["specialty_flashgrenade"] = true; + level.scriptPerks["specialty_smokegrenade"] = true; + level.scriptPerks["specialty_concussiongrenade"] = true; + level.scriptPerks["specialty_challenger"] = true; + level.scriptPerks["specialty_tacticalinsertion"] = true; + level.scriptPerks["specialty_saboteur"] = true; + level.scriptPerks["specialty_endgame"] = true; + level.scriptPerks["specialty_rearview"] = true; + level.scriptPerks["specialty_hardline"] = true; + level.scriptPerks["specialty_ac130"] = true; + level.scriptPerks["specialty_sentry_minigun"] = true; + level.scriptPerks["specialty_predator_missile"] = true; + level.scriptPerks["specialty_helicopter_minigun"] = true; + level.scriptPerks["specialty_tank"] = true; + level.scriptPerks["specialty_precision_airstrike"] = true; + level.scriptPerks["specialty_bling"] = true; + level.scriptPerks["specialty_carepackage"] = true; + level.scriptPerks["specialty_onemanarmy"] = true; + level.scriptPerks["specialty_littlebird_support"] = true; + level.scriptPerks["specialty_primarydeath"] = true; + level.scriptPerks["specialty_secondarybling"] = true; + level.scriptPerks["specialty_combathigh"] = true; + level.scriptPerks["specialty_c4death"] = true; + level.scriptPerks["specialty_explosivedamage"] = true; + level.scriptPerks["specialty_copycat"] = true; + level.scriptPerks["specialty_laststandoffhand"] = true; + level.scriptPerks["specialty_dangerclose"] = true; + + level.scriptPerks["specialty_extraspecialduration"] = true; + level.scriptPerks["specialty_rollover"] = true; + level.scriptPerks["specialty_armorpiercing"] = true; + level.scriptPerks["specialty_omaquickchange"] = true; + level.scriptPerks["specialty_fastmeleerecovery"] = true; + + level.scriptPerks["_specialty_rearview"] = true; + level.scriptPerks["_specialty_onemanarmy"] = true; + + level.fauxPerks["specialty_tacticalinsertion"] = true; + level.fauxPerks["specialty_shield"] = true; + + + /* + level.perkSetFuncs[""] = ::; + level.perkUnsetFuncs[""] = ::; + */ + + level.perkSetFuncs["specialty_blastshield"] = ::setBlastShield; + level.perkUnsetFuncs["specialty_blastshield"] = ::unsetBlastShield; + + level.perkSetFuncs["specialty_siege"] = ::setSiege; + level.perkUnsetFuncs["specialty_siege"] = ::unsetSiege; + + level.perkSetFuncs["specialty_falldamage"] = ::setFreefall; + level.perkUnsetFuncs["specialty_falldamage"] = ::unsetFreefall; + + level.perkSetFuncs["specialty_localjammer"] = ::setLocalJammer; + level.perkUnsetFuncs["specialty_localjammer"] = ::unsetLocalJammer; + + level.perkSetFuncs["specialty_thermal"] = ::setThermal; + level.perkUnsetFuncs["specialty_thermal"] = ::unsetThermal; + + level.perkSetFuncs["specialty_blackbox"] = ::setBlackBox; + level.perkUnsetFuncs["specialty_blackbox"] = ::unsetBlackBox; + + level.perkSetFuncs["specialty_lightweight"] = ::setLightWeight; + level.perkUnsetFuncs["specialty_lightweight"] = ::unsetLightWeight; + + level.perkSetFuncs["specialty_steelnerves"] = ::setSteelNerves; + level.perkUnsetFuncs["specialty_steelnerves"] = ::unsetSteelNerves; + + level.perkSetFuncs["specialty_delaymine"] = ::setDelayMine; + level.perkUnsetFuncs["specialty_delaymine"] = ::unsetDelayMine; + + level.perkSetFuncs["specialty_finalstand"] = ::setFinalStand; + level.perkUnsetFuncs["specialty_finalstand"] = ::unsetFinalStand; + + level.perkSetFuncs["specialty_combathigh"] = ::setCombatHigh; + level.perkUnsetFuncs["specialty_combathigh"] = ::unsetCombatHigh; + + level.perkSetFuncs["specialty_challenger"] = ::setChallenger; + level.perkUnsetFuncs["specialty_challenger"] = ::unsetChallenger; + + level.perkSetFuncs["specialty_saboteur"] = ::setSaboteur; + level.perkUnsetFuncs["specialty_saboteur"] = ::unsetSaboteur; + + level.perkSetFuncs["specialty_endgame"] = ::setEndGame; + level.perkUnsetFuncs["specialty_endgame"] = ::unsetEndGame; + + level.perkSetFuncs["specialty_rearview"] = ::setRearView; + level.perkUnsetFuncs["specialty_rearview"] = ::unsetRearView; + + level.perkSetFuncs["specialty_ac130"] = ::setAC130; + level.perkUnsetFuncs["specialty_ac130"] = ::unsetAC130; + + level.perkSetFuncs["specialty_sentry_minigun"] = ::setSentryMinigun; + level.perkUnsetFuncs["specialty_sentry_minigun"] = ::unsetSentryMinigun; + + level.perkSetFuncs["specialty_predator_missile"] = ::setPredatorMissile; + level.perkUnsetFuncs["specialty_predator_missile"] = ::unsetPredatorMissile; + + level.perkSetFuncs["specialty_tank"] = ::setTank; + level.perkUnsetFuncs["specialty_tank"] = ::unsetTank; + + level.perkSetFuncs["specialty_precision_airstrike"] = ::setPrecision_airstrike; + level.perkUnsetFuncs["specialty_precision_airstrike"] = ::unsetPrecision_airstrike; + + level.perkSetFuncs["specialty_helicopter_minigun"] = ::setHelicopterMinigun; + level.perkUnsetFuncs["specialty_helicopter_minigun"] = ::unsetHelicopterMinigun; + + level.perkSetFuncs["specialty_carepackage"] = ::setCarePackage; + level.perkUnsetFuncs["specialty_carepackage"] = ::unsetCarePackage; + + level.perkSetFuncs["specialty_onemanarmy"] = ::setOneManArmy; + level.perkUnsetFuncs["specialty_onemanarmy"] = ::unsetOneManArmy; + + level.perkSetFuncs["specialty_littlebird_support"] = ::setLittlebirdSupport; + level.perkUnsetFuncs["specialty_littlebird_support"] = ::unsetLittlebirdSupport; + + level.perkSetFuncs["specialty_c4death"] = ::setC4Death; + level.perkUnsetFuncs["specialty_c4death"] = ::unsetC4Death; + + level.perkSetFuncs["specialty_tacticalinsertion"] = ::setTacticalInsertion; + level.perkUnsetFuncs["specialty_tacticalinsertion"] = ::unsetTacticalInsertion; + + initPerkDvars(); + + level thread onPlayerConnect(); +} + + + +precacheShaders() +{ + precacheShader( "specialty_blastshield" ); +} + + +givePerk( perkName ) +{ + if ( IsSubStr( perkName, "_mp" ) ) + { + if ( perkName == "frag_grenade_mp" ) + self SetOffhandPrimaryClass( "frag" ); + if ( perkName == "throwingknife_mp" ) + self SetOffhandPrimaryClass( "throwingknife" ); + + self _giveWeapon( perkName, 0 ); + self giveStartAmmo( perkName ); + + self setPerk( perkName, false ); + return; + } + + if ( isSubStr( perkName, "specialty_null" ) || isSubStr( perkName, "specialty_weapon_" ) ) + { + self setPerk( perkName, false ); + return; + } + + self _setPerk( perkName ); + +} + + +validatePerk( perkIndex, perkName ) +{ + if ( getDvarInt ( "scr_game_perks" ) == 0 ) + { + if ( tableLookup( "mp/perkTable.csv", 1, perkName, 5 ) != "equipment" ) + return "specialty_null"; + } + + /* Validation disabled for now + if ( tableLookup( "mp/perkTable.csv", 1, perkName, 5 ) != ("perk"+perkIndex) ) + { + println( "^1Warning: (" + self.name + ") Perk " + perkName + " is not allowed for perk slot index " + perkIndex + "; replacing with no perk" ); + return "specialty_null"; + } + */ + + return perkName; +} + + +onPlayerConnect() +{ + for(;;) + { + level waittill( "connected", player ); + player thread onPlayerSpawned(); + } +} + + +onPlayerSpawned() +{ + self endon( "disconnect" ); + + self.perks = []; + self.weaponList = []; + self.omaClassChanged = false; + + for( ;; ) + { + self waittill( "spawned_player" ); + + self.omaClassChanged = false; + self thread gambitUseTracker(); + } +} + + +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 ); + } +} + + +cac_modified_damage( victim, attacker, damage, meansofdeath, weapon, impactPoint, impactDir, hitLoc ) +{ + assert( isPlayer( victim ) ); + assert( isDefined( victim.team ) ); + + damageAdd = 0; + + if ( isPrimaryDamage( meansOfDeath ) ) + { + assert( isDefined( attacker ) ); + + if ( isPlayer( attacker ) && weaponInheritsPerks( weapon ) && attacker _hasPerk( "specialty_bulletdamage" ) && victim _hasPerk( "specialty_armorvest" ) ) + damageAdd += 0; + else if ( isPlayer( attacker ) && weaponInheritsPerks( weapon ) && attacker _hasPerk( "specialty_bulletdamage" ) ) + damageAdd += damage*level.bulletDamageMod; + else if ( victim _hasPerk( "specialty_armorvest" ) ) + damageAdd -= damage*(1-level.armorVestMod); + + if ( isPlayer( attacker ) && attacker _hasPerk( "specialty_fmj" ) && victim _hasPerk ( "specialty_armorvest" ) ) + damageAdd += damage*level.hollowPointDamageMod; + } + else if ( isExplosiveDamage( meansOfDeath ) ) + { + if ( isPlayer( attacker ) && weaponInheritsPerks( weapon ) && attacker _hasPerk( "specialty_explosivedamage" ) && victim _hasPerk( "_specialty_blastshield" ) ) + damageAdd += 0; + else if ( isPlayer( attacker ) && weaponInheritsPerks( weapon ) && attacker _hasPerk( "specialty_explosivedamage" ) ) + damageAdd += damage*level.explosiveDamageMod; + else if ( victim _hasPerk( "_specialty_blastshield" ) ) + damageAdd -= damage*(1-level.blastShieldMod); + + if ( isKillstreakWeapon( weapon ) && isPlayer( attacker ) && attacker _hasPerk("specialty_dangerclose") ) + damageAdd += damage*level.dangerCloseMod; + } + else if (meansOfDeath == "MOD_FALLING") + { + if ( victim _hasPerk( "specialty_falldamage" ) ) + { + //eventually set a msg to do a roll + damageAdd = 0; + damage = 0; + } + } + + if ( ( victim.xpScaler == 2 && isDefined( attacker ) ) && ( isPlayer( attacker ) || attacker.classname == "scrip_vehicle" ) ) + damageAdd += 200; + + if ( victim _hasperk( "specialty_combathigh" ) ) + { + if ( IsDefined( self.damageBlockedTotal ) && (!level.teamBased || (isDefined( attacker ) && isDefined( attacker.team ) && victim.team != attacker.team)) ) + { + damageTotal = damage + damageAdd; + damageBlocked = (damageTotal - ( damageTotal / 3 )); + self.damageBlockedTotal += damageBlocked; + + if ( self.damageBlockedTotal >= 101 ) + { + self notify( "combathigh_survived" ); + self.damageBlockedTotal = undefined; + } + } + + if ( weapon != "throwingknife_mp" ) + { + switch ( meansOfDeath ) + { + case "MOD_FALLING": + case "MOD_MELEE": + break; + default: + damage = damage/3; + damageAdd = damageAdd/3; + break; + } + } + } + + return int( damage + damageAdd ); +} + +initPerkDvars() +{ + level.bulletDamageMod = getIntProperty( "perk_bulletDamage", 40 )/100; // increased bullet damage by this % + level.hollowPointDamageMod = getIntProperty( "perk_hollowPointDamage", 65 )/100; // increased bullet damage by this % + level.armorVestMod = getIntProperty( "perk_armorVest", 75 )/100; // percentage of damage you take + level.explosiveDamageMod = getIntProperty( "perk_explosiveDamage", 40 )/100; // increased explosive damage by this % + level.blastShieldMod = getIntProperty( "perk_blastShield", 45 )/100; // percentage of damage you take + level.riotShieldMod = getIntProperty( "perk_riotShield", 100 )/100; + level.dangerCloseMod = getIntProperty( "perk_dangerClose", 100 )/100; + level.armorPiercingMod = getIntProperty( "perk_armorPiercingDamage", 40 )/100; // increased bullet damage by this % +} + +// CAC: Selector function, calls the individual cac features according to player's class settings +// Info: Called every time player spawns during loadout stage +cac_selector() +{ + perks = self.specialty; + + /* + self.detectExplosives = false; + + if ( self _hasPerk( "specialty_detectexplosive" ) ) + self.detectExplosives = true; + + maps\mp\gametypes\_weapons::setupBombSquad(); + */ +} + + +gambitUseTracker() +{ + self endon ( "death" ); + self endon ( "disconnect" ); + level endon ( "game_ended" ); + + if ( getDvarInt ( "scr_game_perks" ) != 1 ) + return; + + gameFlagWait( "prematch_done" ); + + self notifyOnPlayerCommand( "gambit_on", "+frag" ); +}