#include maps\mp\_utility; #include maps\mp\gametypes\_hud_util; #include common_scripts\utility; MIN_NUM_KILLS_GIVE_BONUS_PERKS = 8; NUM_ABILITY_CATEGORIES = 7; NUM_SUB_ABILITIES = 5; KILLSTREAK_GIMME_SLOT = 0; KILLSTREAK_SLOT_1 = 1; KILLSTREAK_SLOT_2 = 2; KILLSTREAK_SLOT_3 = 3; KILLSTREAK_BONUS_PERKS_SLOT = 4; KILLSTREAK_STACKING_START_SLOT = 5; initKillstreakData() { for ( i = 1; true; i++ ) { retVal = TableLookup( level.global_tables[ "killstreakTable" ].path, level.global_tables[ "killstreakTable" ].index_col, i, level.global_tables[ "killstreakTable" ].ref_col ); if ( !IsDefined( retVal ) || retVal == "" ) break; streakRef = TableLookup( level.global_tables[ "killstreakTable" ].path, level.global_tables[ "killstreakTable" ].index_col, i, level.global_tables[ "killstreakTable" ].ref_col ); assert( streakRef != "" ); streakUseHint = TableLookupIString( level.global_tables[ "killstreakTable" ].path, level.global_tables[ "killstreakTable" ].index_col, i, level.global_tables[ "killstreakTable" ].earned_hint_col ); assert( streakUseHint != &"" ); streakEarnDialog = TableLookup( level.global_tables[ "killstreakTable" ].path, level.global_tables[ "killstreakTable" ].index_col, i, level.global_tables[ "killstreakTable" ].earned_dialog_col ); assert( streakEarnDialog != "" ); game["dialog"][ streakRef ] = streakEarnDialog; streakAlliesUseDialog = TableLookup( level.global_tables[ "killstreakTable" ].path, level.global_tables[ "killstreakTable" ].index_col, i, level.global_tables[ "killstreakTable" ].allies_dialog_col ); assert( streakAlliesUseDialog != "" ); game["dialog"][ "allies_friendly_" + streakRef + "_inbound" ] = "friendly_" + streakAlliesUseDialog; game["dialog"][ "allies_enemy_" + streakRef + "_inbound" ] = "enemy_" + streakAlliesUseDialog; streakAxisUseDialog = TableLookup( level.global_tables[ "killstreakTable" ].path, level.global_tables[ "killstreakTable" ].index_col, i, level.global_tables[ "killstreakTable" ].enemy_dialog_col ); assert( streakAxisUseDialog != "" ); game["dialog"][ "axis_friendly_" + streakRef + "_inbound" ] = "friendly_" + streakAxisUseDialog; game["dialog"][ "axis_enemy_" + streakRef + "_inbound" ] = "enemy_" + streakAxisUseDialog; streakPoints = int( TableLookup( level.global_tables[ "killstreakTable" ].path, level.global_tables[ "killstreakTable" ].index_col, i, level.global_tables[ "killstreakTable" ].score_col ) ); assert( streakPoints != 0 ); maps\mp\gametypes\_rank::registerScoreInfo( "killstreak_" + streakRef, streakPoints ); } } onPlayerConnect() { for ( ;; ) { level waittill( "connected", player ); if( !IsDefined ( player.pers[ "killstreaks" ] ) ) player.pers[ "killstreaks" ] = []; if( !IsDefined ( player.pers[ "kID" ] ) ) player.pers[ "kID" ] = 10; //if( !IsDefined ( player.pers[ "kIDs_valid" ] ) ) // player.pers[ "kIDs_valid" ] = []; player.lifeId = 0; player.curDefValue = 0; if ( IsDefined( player.pers["deaths"] ) ) player.lifeId = player.pers["deaths"]; player VisionSetMissilecamForPlayer( game["thermal_vision"] ); player thread onPlayerSpawned(); player thread monitorDisownKillstreaks(); player.spUpdateTotal = 0; } } onPlayerSpawned() { self endon( "disconnect" ); if ( is_aliens() ) //don't do this for Aliens return; for ( ;; ) { self waittill( "spawned_player" ); self thread killstreakUseWaiter(); self thread waitForChangeTeam(); // these three threads need to be run regardless of the streak type because you could switch during the grace period from specialist to assault or support and not be able to toggle up/down self thread streakSelectUpTracker(); self thread streakSelectDownTracker(); if( level.console ) { self thread streakUseTimeTracker(); } else { // pc doesn't do killstreak selections, just a single button press self thread pc_watchStreakUse(); // they could be on pc but using a game pad, we need to monitor that self thread pc_watchGamepad(); } self thread streakNotifyTracker(); if ( !IsDefined( self.pers["killstreaks"][ KILLSTREAK_GIMME_SLOT ] ) ) self initPlayerKillstreaks(); if ( !IsDefined( self.earnedStreakLevel ) ) self.earnedStreakLevel = 0; // we want to reset the adrenaline back to what it was for round based games // we reset the adrenaline on first connect in playerlogic and if they are in the game until the end // 2014-01-24 wallace: in CL 656322, .adrenaline is always initialized for MLG purposes, so we can't count it being undefined any more if( game["roundsPlayed"] > 0 && self.adrenaline == 0 ) { self.adrenaline = self GetCommonPlayerData( "killstreaksState", "count" ); } // if we reset stats then countToNext will be 0 and no bars will show until you kill someone // this also means the first time someone plays the game they won't see bars, so we need to set it //if( self.adrenaline == self GetCommonPlayerData( "killstreaksState", "countToNext" ) ) { self setStreakCountToNext(); self updateStreakSlots(); } if ( self.streakType == "specialist" ) self updateSpecialistKillstreaks(); else self giveOwnedKillstreakItem(); } } initPlayerKillstreaks() { // this IsDefined check keeps the clearkillstreaks call when we quit the game without selecting a class, from erroring out if( !IsDefined( self.streakType ) ) return; if ( self.streakType == "specialist" ) self setCommonPlayerData( "killstreaksState", "isSpecialist", true ); else self setCommonPlayerData( "killstreaksState", "isSpecialist", false ); // gimme slot is where care package items and special given items go // we want the gimme slot to be stackable so we don't lose killstreaks when we pick another up // so we'll make index 0 be a pointer of sorts to show where the next usable killstreak is in the killstreak array self_pers_killstreaks_gimme_slot = spawnStruct(); self_pers_killstreaks_gimme_slot.available = false; self_pers_killstreaks_gimme_slot.streakName = undefined; self_pers_killstreaks_gimme_slot.earned = false; self_pers_killstreaks_gimme_slot.awardxp = undefined; self_pers_killstreaks_gimme_slot.owner = undefined; self_pers_killstreaks_gimme_slot.kID = undefined; self_pers_killstreaks_gimme_slot.lifeId = undefined; self_pers_killstreaks_gimme_slot.isGimme = true; self_pers_killstreaks_gimme_slot.isSpecialist = false; self_pers_killstreaks_gimme_slot.nextSlot = undefined; self.pers["killstreaks"][ KILLSTREAK_GIMME_SLOT ] = self_pers_killstreaks_gimme_slot; // reserved for each killstreak whether they have them or not for( i = 1; i < KILLSTREAK_BONUS_PERKS_SLOT; i++ ) { self_pers_killstreaks_i = spawnStruct(); self_pers_killstreaks_i.available = false; self_pers_killstreaks_i.streakName = undefined; self_pers_killstreaks_i.earned = true; self_pers_killstreaks_i.awardxp = 1; self_pers_killstreaks_i.owner = undefined; self_pers_killstreaks_i.kID = undefined; self_pers_killstreaks_i.lifeId = -1; self_pers_killstreaks_i.isGimme = false; self_pers_killstreaks_i.isSpecialist = false; self.pers["killstreaks"][ i ] = self_pers_killstreaks_i; } // reserved for specialist all perks bonus self_pers_killstreaks_bonus_perks_slot = spawnStruct(); self_pers_killstreaks_bonus_perks_slot.available = false; self_pers_killstreaks_bonus_perks_slot.streakName = "all_perks_bonus"; self_pers_killstreaks_bonus_perks_slot.earned = true; self_pers_killstreaks_bonus_perks_slot.awardxp = 0; self_pers_killstreaks_bonus_perks_slot.owner = undefined; self_pers_killstreaks_bonus_perks_slot.kID = undefined; self_pers_killstreaks_bonus_perks_slot.lifeId = -1; self_pers_killstreaks_bonus_perks_slot.isGimme = false; self_pers_killstreaks_bonus_perks_slot.isSpecialist = true; self.pers["killstreaks"][ KILLSTREAK_BONUS_PERKS_SLOT ] = self_pers_killstreaks_bonus_perks_slot; // init all of the icons to 0 in case the player hasn't selected all 3 streaks // also init the hasStreak to false for( i = KILLSTREAK_GIMME_SLOT; i < KILLSTREAK_SLOT_3 + 1; i++ ) { self setCommonPlayerData( "killstreaksState", "icons", i, 0 ); self setCommonPlayerData( "killstreaksState", "hasStreak", i, false ); } self setCommonPlayerData( "killstreaksState", "hasStreak", KILLSTREAK_GIMME_SLOT, false ); index = 1; foreach ( streakName in self.killstreaks ) { self_pers_killstreaks_index = self.pers["killstreaks"][index]; self_pers_killstreaks_index.streakName = streakName; self_pers_killstreaks_index.isSpecialist = ( self.streakType == "specialist" ); killstreakIndexName = self_pers_killstreaks_index.streakName; // if specialist then we need to check to see if they have the pro version of the perk and get that icon if( self.streakType == "specialist" ) { perkTokens = StrTok( self_pers_killstreaks_index.streakName, "_" ); if( perkTokens[ perkTokens.size - 1 ] == "ks" ) { perkName = undefined; foreach( token in perkTokens ) { if( token != "ks" ) { if( !IsDefined( perkName ) ) perkName = token; else perkName += ( "_" + token ); } } // blastshield has an _ at the beginning if( isStrStart( self_pers_killstreaks_index.streakName, "_" ) ) perkName = "_" + perkName; if( IsDefined( perkName ) && self maps\mp\gametypes\_class::getPerkUpgrade( perkName ) != "specialty_null" ) killstreakIndexName = self_pers_killstreaks_index.streakName + "_pro"; } } self setCommonPlayerData( "killstreaksState", "icons", index, getKillstreakIndex( killstreakIndexName ) ); self setCommonPlayerData( "killstreaksState", "hasStreak", index, false ); index++; } self setCommonPlayerData( "killstreaksState", "nextIndex", 1 ); self setCommonPlayerData( "killstreaksState", "selectedIndex", -1 ); self setCommonPlayerData( "killstreaksState", "numAvailable", 0 ); // specialist shows one more icon self setCommonPlayerData( "killstreaksState", "hasStreak", KILLSTREAK_BONUS_PERKS_SLOT, false ); } updateStreakCount() { if ( !IsDefined( self.pers["killstreaks"] ) ) return; if ( self.adrenaline == self.previousAdrenaline ) return; curCount = self.adrenaline; self setCommonPlayerData( "killstreaksState", "count", self.adrenaline ); if ( self.adrenaline >= self getCommonPlayerData( "killstreaksState", "countToNext" ) ) self setStreakCountToNext(); } resetStreakCount() { self setCommonPlayerData( "killstreaksState", "count", 0 ); self setStreakCountToNext(); } setStreakCountToNext() { // this IsDefined check keeps the resetadrenaline call when we first connect in playerlogic, from erroring out if( !IsDefined( self.streakType ) ) { // if they have no streak then count to next should be zero self setCommonPlayerData( "killstreaksState", "countToNext", 0 ); return; } // if they have no killstreaks if( self getMaxStreakCost() == 0 ) { self setCommonPlayerData( "killstreaksState", "countToNext", 0 ); return; } // specialist but have maxed out if( self.streakType == "specialist" ) { if( self.adrenaline >= self getMaxStreakCost() ) return; } // set the next streaks cost nextStreakName = getNextStreakName(); if ( !IsDefined( nextStreakname ) ) return; nextStreakCost = getStreakCost( nextStreakName ); self setCommonPlayerData( "killstreaksState", "countToNext", nextStreakCost ); } getNextStreakName() { if ( self.adrenaline == self getMaxStreakCost() && ( self.streakType != "specialist" ) ) { adrenaline = 0; } else { adrenaline = self.adrenaline; } foreach ( streakName in self.killstreaks ) { streakVal = self getStreakCost( streakName ); if ( streakVal > adrenaline ) { return streakName; } } return undefined; } getMaxStreakCost() { maxCost = 0; foreach ( streakName in self.killstreaks ) { streakVal = self getStreakCost( streakName ); if ( streakVal > maxCost ) { maxCost = streakVal; } } return maxCost; } updateStreakSlots() { // this IsDefined check keeps the clearkillstreaks call when we quit the game without selecting a class, from erroring out if( !IsDefined( self.streakType ) ) return; if ( !isReallyAlive(self) ) return; self_pers_killstreaks = self.pers["killstreaks"]; // what's available? numStreaks = 0; for( i = 0; i < KILLSTREAK_SLOT_3 + 1; i++ ) { if( IsDefined( self_pers_killstreaks[i] ) && IsDefined( self_pers_killstreaks[i].streakName ) ) { self setCommonPlayerData( "killstreaksState", "hasStreak", i, self_pers_killstreaks[i].available ); if ( self_pers_killstreaks[i].available == true ) numStreaks++; if( IsDefined(level.removeKillStreakIcons) && level.removeKillStreakIcons && !self_pers_killstreaks[i].available ) self setCommonPlayerData( "killstreaksState", "icons", i, 0 ); } } if ( self.streakType != "specialist" ) self setCommonPlayerData( "killstreaksState", "numAvailable", numStreaks ); // next to earn minLevel = self.earnedStreakLevel; maxLevel = self getMaxStreakCost(); if ( self.earnedStreakLevel == maxLevel && self.streakType != "specialist" ) minLevel = 0; nextIndex = 1; foreach ( streakName in self.killstreaks ) { streakVal = self getStreakCost( streakName ); if ( streakVal > minLevel ) { nextStreak = streakName; break; } // for specialsit we don't want the next index to go above the max if( self.streakType == "specialist" ) { if( self.earnedStreakLevel == maxLevel ) break; } nextIndex++; } self setCommonPlayerData( "killstreaksState", "nextIndex", nextIndex ); // selected index if ( IsDefined( self.killstreakIndexWeapon ) && ( self.streakType != "specialist" ) ) { self setCommonPlayerData( "killstreaksState", "selectedIndex", self.killstreakIndexWeapon ); } else { if( self.streakType == "specialist" && self_pers_killstreaks[ KILLSTREAK_GIMME_SLOT ].available ) self setCommonPlayerData( "killstreaksState", "selectedIndex", 0 ); else self setCommonPlayerData( "killstreaksState", "selectedIndex", -1 ); } } waitForChangeTeam() { self endon ( "disconnect" ); self endon( "faux_spawn" ); self notify ( "waitForChangeTeam" ); self endon ( "waitForChangeTeam" ); for ( ;; ) { self waittill ( "joined_team" ); clearKillstreaks(); } } killstreakUsePressed() { self_pers_killstreaks = self.pers["killstreaks"]; streakName = self_pers_killstreaks[self.killstreakIndexWeapon].streakName; lifeId = self_pers_killstreaks[self.killstreakIndexWeapon].lifeId; isEarned = self_pers_killstreaks[self.killstreakIndexWeapon].earned; awardXp = self_pers_killstreaks[self.killstreakIndexWeapon].awardXp; kID = self_pers_killstreaks[self.killstreakIndexWeapon].kID; isGimme = self_pers_killstreaks[self.killstreakIndexWeapon].isGimme; if( !self validateUseStreak() ) return false; //// Balance for anyone using the explosive ammo killstreak, remove it when they activate the next killstreak //removeExplosiveAmmo = false; //if ( self _hasPerk( "specialty_explosivebullets" ) && !issubstr( streakName, "explosive_ammo" ) ) // removeExplosiveAmmo = true; if ( !self [[ level.killstreakFuncs[ streakName ] ]]( lifeId, streakName ) ) return ( false ); /# // let test client bots (not AI bots) use the full functionality of the killstreak usage if( !IsBot( self ) && IsDefined( self.pers[ "isBot" ] ) && self.pers[ "isBot" ] ) return true; #/ //// Balance for anyone using the explosive ammo killstreak, remove it when they activate the next killstreak //if ( removeExplosiveAmmo ) // self _unsetPerk( "specialty_explosivebullets" ); self thread updateKillstreaks(); self usedKillstreak( streakName, awardXp ); //// NOTE: match leveling prototype //// clear the active killstreak bonus after use, this keeps it from being given back after death if nothing else has been earned //if( IsDefined( self.pers[ "activeKillstreakBonuses" ][0] ) ) // self.pers[ "activeKillstreakBonuses" ][0] = undefined; return ( true ); } usedKillstreak( streakName, awardXp ) { 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 ); if( isAssaultKillstreak( streakName ) ) { self thread incPlayerStat( "assaultkillstreaksused", 1 ); } else if( isSupportKillstreak( streakName ) ) { self thread incPlayerStat( "supportkillstreaksused", 1 ); } else if( isSpecialistKillstreak( streakName ) ) { self thread incPlayerStat( "specialistkillstreaksearned", 1 ); // no need to play specialist because we do leader dialog on the player with killstreakSplashNotify() and not just team specific things return; } // play killstreak dialog team = self.team; if ( level.teamBased ) { thread leaderDialog( team + "_friendly_" + streakName + "_inbound", team ); if ( getKillstreakEnemyUseDialog( streakName ) ) { if ( self playEnemyDialog( streakName ) ) thread leaderDialog( team + "_enemy_" + streakName + "_inbound", level.otherTeam[ team ] ); } } else { self thread leaderDialogOnPlayer( team + "_friendly_" + streakName + "_inbound" ); if ( getKillstreakEnemyUseDialog( streakName ) ) { excludeList[0] = self; if ( self playEnemyDialog( streakName ) ) thread leaderDialog( team + "_enemy_" + streakName + "_inbound", undefined, undefined, excludeList ); } } } playEnemyDialog( streakName ) { // self == player // Only for multiplayer sat com usage if ( !is_aliens() ) { // Only play the enemy dialog for Sat Coms, if it's the only one in existence if ( level.teamBased && streakName == "uplink" && [[level.comExpFuncs[ "getRadarStrengthForTeam" ]]]( self.team ) != 1 ) return false; // FFA uplink strength starts at 2 else if ( !level.teamBased && streakName == "uplink" && [[level.comExpFuncs[ "getRadarStrengthForPlayer" ]]]( self ) != 2 ) return false; } // For all other killstreaks / situations return true; } updateKillstreaks( keepCurrent ) { // early exit for when you give bots a killstreak to use if( IsAI( self ) && !IsDefined( self.killstreakIndexWeapon ) ) return; if ( !IsDefined( keepCurrent ) ) { self.pers["killstreaks"][self.killstreakIndexWeapon].available = false; // if this is the gimme slot and we still have some stacked then leave available and set the new icon if( self.killstreakIndexWeapon == KILLSTREAK_GIMME_SLOT ) { // if this is the gimme slot then clear the last used stacked killstreak before updating killstreaks self.pers["killstreaks"][ self.pers["killstreaks"][ KILLSTREAK_GIMME_SLOT ].nextSlot ] = undefined; // loop through the stacked killstreaks and find the next available one streakName = undefined; kID = undefined; self_pers_killstreaks = self.pers["killstreaks"]; for( i = KILLSTREAK_STACKING_START_SLOT; i < self_pers_killstreaks.size; i++ ) { if( !IsDefined( self_pers_killstreaks[i] ) || !IsDefined( self_pers_killstreaks[i].streakName ) ) continue; streakName = self_pers_killstreaks[i].streakName; kID = self_pers_killstreaks[i].kID; self_pers_killstreaks[ KILLSTREAK_GIMME_SLOT ].nextSlot = i; } if( IsDefined( streakName ) ) { self_pers_killstreaks[ KILLSTREAK_GIMME_SLOT ].available = true; self_pers_killstreaks[ KILLSTREAK_GIMME_SLOT ].streakName = streakName; self_pers_killstreaks[ KILLSTREAK_GIMME_SLOT ].kID = kID; streakIndex = getKillstreakIndex( streakName ); self setCommonPlayerData( "killstreaksState", "icons", KILLSTREAK_GIMME_SLOT, streakIndex ); // pc need to put this new one in the actionslot for use if( !level.console && !self is_player_gamepad_enabled() ) { killstreakWeapon = getKillstreakWeapon( streakName ); _setActionSlot( 4, "weapon", killstreakWeapon ); } } } } // find the highest remaining streak and select it highestStreakIndex = undefined; if( self.streakType == "specialist" ) { if ( self.pers["killstreaks"][ KILLSTREAK_GIMME_SLOT ].available ) highestStreakIndex = KILLSTREAK_GIMME_SLOT; } else { for ( i = KILLSTREAK_GIMME_SLOT; i < KILLSTREAK_SLOT_3 + 1; i++ ) { self_pers_killstreaks_i = self.pers["killstreaks"][i]; if( IsDefined( self_pers_killstreaks_i ) && IsDefined( self_pers_killstreaks_i.streakName ) && self_pers_killstreaks_i.available ) { highestStreakIndex = i; } } } if ( IsDefined( highestStreakIndex ) ) { if( level.console || self is_player_gamepad_enabled() ) { self.killstreakIndexWeapon = highestStreakIndex; self.pers["lastEarnedStreak"] = self.pers["killstreaks"][highestStreakIndex].streakName; self giveSelectedKillstreakItem(); } // pc doesn't select killstreaks else { // make sure we still have all of the available killstreak weapons, things like the airdrop will get taken if you have more than one for ( i = KILLSTREAK_GIMME_SLOT; i < KILLSTREAK_SLOT_3 + 1; i++ ) { self_pers_killstreaks_i = self.pers["killstreaks"][i]; if( IsDefined( self_pers_killstreaks_i ) && IsDefined( self_pers_killstreaks_i.streakName ) && self_pers_killstreaks_i.available ) { killstreakWeapon = getKillstreakWeapon( self_pers_killstreaks_i.streakName ); weaponsListItems = self GetWeaponsListItems(); hasKillstreakWeapon = false; for( j = 0; j < weaponsListItems.size; j++ ) { if( killstreakWeapon == weaponsListItems[j] ) { hasKillstreakWeapon = true; break; } } if( !hasKillstreakWeapon ) { self _giveWeapon( killstreakWeapon ); } else { // if we have more than one airdrop type weapon the ammo gets set to 0 because we give the next airdrop weapon before we take the last one // this is a quicker fix than trying to figure out how to take and give at the right times if( IsSubStr( killstreakWeapon, "airdrop_" ) ) self SetWeaponAmmoClip( killstreakWeapon, 1 ); } // we should re-set the action slot just to make sure everything is correct (juggernaut needs this or they won't be able to use their killstreaks once obtained because we clear the action slots in giveLoadout()) self _setActionSlot( i + 4, "weapon", killstreakWeapon ); } } self.killstreakIndexWeapon = undefined; self.pers["lastEarnedStreak"] = self.pers["killstreaks"][highestStreakIndex].streakName; self updateStreakSlots(); } } else { self.killstreakIndexWeapon = undefined; self.pers["lastEarnedStreak"] = undefined; self updateStreakSlots(); // NOTE: we used to take item weapons from the player here but that stopped killstreak weapon animations from playing if it was the only killstreak // since we take the item weapons when we give a killstreak weapon anyways, no need to do that here // we've also added the waitTakeKillstreakWeapon() function to take them when appropriate // VERY IMPORTANT: with the current system, we NEVER want to loop and take all weapon list items } } clearKillstreaks() { self_pers_killstreaks = self.pers["killstreaks"]; if( !IsDefined(self_pers_killstreaks) ) return; for( i = self_pers_killstreaks.size - 1; i > -1; i-- ) { self.pers["killstreaks"][i] = undefined; } initPlayerKillstreaks(); self resetAdrenaline(); self.killstreakIndexWeapon = undefined; self updateStreakSlots(); } updateSpecialistKillstreaks() { // reset if no adrenaline if( self.adrenaline == 0 ) { for( i = KILLSTREAK_SLOT_1; i < KILLSTREAK_SLOT_3 + 1; i++ ) { if( IsDefined( self.pers["killstreaks"][i] ) ) { self.pers["killstreaks"][i].available = false; self setCommonPlayerData( "killstreaksState", "hasStreak", i, false ); } } self setCommonPlayerData( "killstreaksState", "nextIndex", 1 ); self setCommonPlayerData( "killstreaksState", "hasStreak", KILLSTREAK_BONUS_PERKS_SLOT, false ); } else { // loop through each earnable killstreak for( i = KILLSTREAK_SLOT_1; i < KILLSTREAK_SLOT_3 + 1; i++ ) { self_pers_killstreaks_i = self.pers["killstreaks"][i]; if( IsDefined( self_pers_killstreaks_i ) && IsDefined( self_pers_killstreaks_i.streakName ) && self_pers_killstreaks_i.available ) { streakVal = getStreakCost( self_pers_killstreaks_i.streakName ); if( streakVal > self.adrenaline ) { // reset them because we're going to check them again and set them self.pers["killstreaks"][i].available = false; self setCommonPlayerData( "killstreaksState", "hasStreak", i, false ); continue; } if( self.adrenaline >= streakVal ) { // no need to give this again if we've already got it, this fixes a bug where all of the achieved sounds play as you enter the next round // this also fixes a possibility of getting credit for getting another set of this killstreak in your player stats if( self getCommonPlayerData( "killstreaksState", "hasStreak", i ) ) { // just call the killstreak function so we give the specialist perk back to the player each round self [[ level.killstreakFuncs[ self_pers_killstreaks_i.streakName ] ]]( undefined, self_pers_killstreaks_i.streakName ); continue; } self giveKillstreak( self_pers_killstreaks_i.streakName, self_pers_killstreaks_i.earned, false, self ); } } } // at a certain number of kills we'll give you bonus perks specialist_max_kills = self getMaxStreakCost();; if ( isAI( self ) ) specialist_max_kills = self.pers["specialistStreakKills"][2]; numKills = int( max( MIN_NUM_KILLS_GIVE_BONUS_PERKS, ( specialist_max_kills + 2 ) ) ); if( self _hasPerk( "specialty_hardline" ) ) numKills--; if( self.adrenaline >= numKills ) { self setCommonPlayerData( "killstreaksState", "hasStreak", KILLSTREAK_BONUS_PERKS_SLOT, true ); self giveBonusPerks(); } else self setCommonPlayerData( "killstreaksState", "hasStreak", KILLSTREAK_BONUS_PERKS_SLOT, false ); } // update gimme slot killstreak regardless if ( self.pers["killstreaks"][ KILLSTREAK_GIMME_SLOT ].available ) { streakName = self.pers["killstreaks"][ KILLSTREAK_GIMME_SLOT ].streakName; killstreakWeapon = getKillstreakWeapon( streakName ); if( level.console || self is_player_gamepad_enabled() ) { self giveKillstreakWeapon( killstreakWeapon ); self.killstreakIndexWeapon = KILLSTREAK_GIMME_SLOT; } else { self _giveWeapon( killstreakWeapon ); self _setActionSlot( 4, "weapon", killstreakWeapon ); self.killstreakIndexWeapon = undefined; } } } getFirstPrimaryWeapon() { weaponsList = self getWeaponsListPrimaries(); assert ( IsDefined( weaponsList[0] ) ); // the juggernaut primary weapon is a killstreak weapon so we shouldn't assert for killstreak weapons //assert ( !isKillstreakWeapon( weaponsList[0] ) ); return weaponsList[0]; } isTryingToUseKillstreakInGimmeSlot() { return IsDefined( self.tryingToUseKS ) && self.tryingToUseKS && IsDefined( self.killstreakIndexWeapon ) && self.killstreakIndexWeapon == 0; } isTryingToUseKillstreakSlot() { return IsDefined( self.tryingToUseKS ) && self.tryingToUseKS && IsDefined( self.killstreakIndexWeapon ); } waitForKillstreakWeaponSwitchStarted() { self endon( "weapon_switch_invalid" ); self waittill( "weapon_switch_started", newWeapon ); self notify( "killstreak_weapon_change", "switch_started", newWeapon ); } waitForKillstreakWeaponSwitchInvalid() { self endon( "weapon_switch_started" ); self waittill( "weapon_switch_invalid", invalidWeapon ); self notify( "killstreak_weapon_change", "switch_invalid", invalidWeapon ); } waitForKillstreakWeaponChange() { self childthread waitForKillstreakWeaponSwitchStarted(); self childthread waitForKillstreakWeaponSwitchInvalid(); self waittill( "killstreak_weapon_change", result, weapon ); if ( result == "switch_started" ) return weapon; // caused by client server mistmatch where the client selects a killstreak that is no longer in his inventory assert( result == "switch_invalid" ); assert( isTryingToUseKillstreakSlot() ); killstreakWeapon = getKillstreakWeapon( self.pers["killstreaks"][self.killstreakIndexWeapon].streakName ); PrintLn( "Invalid killstreak weapon switch: " + weapon + ". Forcing switch to " + killstreakWeapon + " instead." ); self SwitchToWeapon( killstreakWeapon ); waittillframeend; newWeapon = undefined; if ( IsDefined( self.changingWeapon ) ) { PrintLn( "changing weapon defined\n" ); newWeapon = self.changingWeapon; } else { PrintLn( "waiting for weapon switch\n" ); self waittill( "weapon_switch_started", newWeapon ); } PrintLn( "Weapon switche started: " + newWeapon + "\n" ); // player changed weapons while waiting for switch. if ( newWeapon != killstreakWeapon ) { PrintLn( "Player switched weapons after script forced killstreak weapon. Skipping killstreak weapon change. " + newWeapon + " != " + killstreakWeapon ); return undefined; } return killstreakWeapon; } killstreakUseWaiter() { self endon( "disconnect" ); self endon( "finish_death" ); self endon( "joined_team" ); self endon( "faux_spawn" ); self endon( "spawned" ); level endon( "game_ended" ); self notify( "killstreakUseWaiter" ); self endon( "killstreakUseWaiter" ); self.lastKillStreak = 0; if ( !IsDefined( self.pers["lastEarnedStreak"] ) ) self.pers["lastEarnedStreak"] = undefined; self thread finishDeathWaiter(); // adding the notify array wait so we only do killstreak use stuff if the user presses to use it instead of always happening on weapon_change // this fixes an issue where you could pick up a juggernaut from a care package and get the weapon taken away notify_array = [ "streakUsed", "streakUsed1", "streakUsed2", "streakUsed3", "streakUsed4" ]; while( true ) { self.tryingToUseKS = undefined; notify_result = self waittill_any_in_array_return_no_endon_death( notify_array ); self.tryingToUseKS = true; // try to ensure that we run after any thread that is changing self.killstreakIndexWeapon // this is particularly bad for PC, which uses pc_watchStreakUse to set that value, and they may get out of order if you use a KS, then immediately start sprinting waittillframeend; // we've pressed a streak used button, but there's no killstreak to use if ( !IsDefined( self.killstreakIndexWeapon ) || !IsDefined( self.pers["killstreaks"][self.killstreakIndexWeapon] ) || !IsDefined( self.pers["killstreaks"][self.killstreakIndexWeapon].streakName ) ) { continue; } // Special killstreak use check if the player is a custom juggernaut if ( !canCustomJuggUseKillstreak( self.pers["killstreaks"][self.killstreakIndexWeapon].streakName ) ) { printCustomJuggKillstreakErrorMsg(); // fix 180771 - on PC, predator can be in a no-weapon state if he tries to use killstreaks if ( notify_result != "streakUsed" ) // this should only happen on keyboard { lastWeapon = self GetCurrentWeapon(); self switch_to_last_weapon( lastWeapon ); } continue; } // corresponding to code change that we're no longer allowing player to 'queue up' a killstreak // when they're cooking a grenade or holding up the underbarrel grenade launcher if( self IsOffhandWeaponReadyToThrow() ) continue; // make sure we are switching to a KS weapon before we turn off weapon switches // self.changingWeapon is set by watchStartWeaponChange, so this is a safety check in case we have already missed the weapon_switch_started notify if ( IsDefined( self.changingWeapon ) ) { newWeapon = self.changingWeapon; } else { self waittill( "weapon_switch_started", newWeapon ); } killstreakWeapon = getKillstreakWeapon( self.pers["killstreaks"][self.killstreakIndexWeapon].streakName ); if ( newWeapon != killstreakWeapon ) { // it's possible, through a series of weapon switches and cancels, to get equip the killstreak weapon after this fail case and thus not run the killstreak logic // take the weapon away if that happens self thread removeUnitializedKillstreakWeapon(); continue; } // 2013-09-20 wallace: Block weapon switch inputs once we've started the killstreak. // Addresses bugs like 124029, where players can get in a bad state if the player mashes weapon switch as the killstreak weapon is being raised. self beginKillstreakWeaponSwitch(); // We want to switch to a killstreak weapon, but we haven't switched to it yet // ?? handles some weird ordering of notify issues that occur if you // 1. give yourself a trinity rocket and equip juggernaut // 2. start sprinting // 3. use trinity rocket // 4. immediately start sprinting // 5. bug would leave player with laptop out, but wouldn't start ks if ( newWeapon != self GetCurrentWeapon() ) { // !!! HACK 2013-10-19 wallace: because we client predict weapon switches, // it is possible to use a KS and then quickly switch to another weapon and get stuck in a bad state. The timeout is a failsafe. // In IW7, we should let code handle these KS switches just like weapon switches self thread killstreakWaitForWeaponChange(); result = self waittill_any_timeout_no_endon_death( 1.5, "ks_weapon_change", "ks_alt_weapon_change" ); // if killstreak is activated while weapon is in alt mode, there will be an extra 'weapon_change' // notify that comes first which is the alt toggle change, skip this to wait for the actual weapon change if ( result == "ks_alt_weapon_change" ) { self waittill( "weapon_change", newWeapon, isAltToggle ); } else { newWeapon = self GetCurrentWeapon(); } } if ( !IsAlive( self ) ) { self endKillstreakWeaponSwitch(); continue; } if ( newWeapon != killstreakWeapon ) { // 2013-09-25 wallace: it's possible that the only thing we need to do inhere is endKillstreakWeaponSwitch // I don't want to rock the boat and change these juggernaut/heli sniper fixes // However, it leads to a bug if you call in a KS; host migrate as the weapon is lowered; then switch weapons after migration, you will see 2 weaon switches // since this weapon is not the killstreak we have selected, go back to the last weapon // this fixes an issue where you could be pulling out a killstreak weapon at the same time that you earned a new killstreak // the new killstreak would run giveKillstreak and change the killstreakIndexWeapon before the weapon_change event happens switch_to_weapon = self.lastdroppableweapon; if( isKillstreakWeapon( newWeapon ) ) { // we need see if we're trying to go back to a juggernaut weapon, since they are killstreak weapons we fall into this // this will fix an issue where you could be a juggernaut, call in a dog, call in a sat com, and get stuck with no weapon if( self isJuggernaut() && isJuggernautWeapon( newWeapon ) ) switch_to_weapon = newWeapon; else if ( newWeapon == "iw6_gm6helisnipe_mp_gm6scope" ) switch_to_weapon = newWeapon; else self TakeWeapon( newWeapon ); } self SwitchToWeapon( switch_to_weapon ); self endKillstreakWeaponSwitch(); continue; } // Fix for infinite MAAWS and other killstreaks. See comment in // _weapons.gsc::watchWeaponChange() where KS_aboutToUse is // checked. 06/26/2014-JC self.KS_aboutToUse = true; waittillframeend; self.KS_aboutToUse = undefined; // get this stuff now because self.killstreakIndexWeapon will change after killstreakUsePressed() streakName = self.pers["killstreaks"][self.killstreakIndexWeapon].streakName; isGimme = self.pers["killstreaks"][self.killstreakIndexWeapon].isGimme; assert( IsDefined( streakName ) ); assert( IsDefined( level.killstreakFuncs[ streakName ] ) ); // We need to re-enable weapon switch once the KS weapon is equipped so that players can stow vest / maaws / airdrop / etc. to cancel its use self endKillstreakWeaponSwitch(); result = self killstreakUsePressed(); self beginKillstreakWeaponSwitch(); lastWeapon = self getLastWeapon(); if ( !self HasWeapon( lastWeapon ) ) { if ( isReallyAlive( self ) ) { lastWeapon = self getFirstPrimaryWeapon(); } else { // - JC: 09/27/13 The previous logic assumed in the case of death that // the result would be false and the killstreak hadn't been used. In the // case of the MAAWS the player could die after firing all rockets and // so on death the killStreakUsePressed func would return true. Fix below: // If the player is not alive the last weapon may not be equipped. Insure // the player has a last weapon self _giveWeapon( lastWeapon ); } } // we need to take the killstreak weapon away once we've switched back to our last weapon // this fixes an issue where you can call in a killstreak and then press right again to pull the killstreak weapon out if( result ) { self thread waitTakeKillstreakWeapon( killstreakWeapon, lastWeapon ); } //no force switching weapon for ridable killstreaks if ( shouldSwitchWeaponPostKillstreak( result, streakName ) ) { self switch_to_last_weapon( lastWeapon ); } // 2013-09-25 wallace: we need to renable weapon switches once the player has his gun back // some KS's, like airdrop and MAAWS, will auto switch for us, which the while statement checks for currentWeapon = self GetCurrentWeapon(); while ( currentWeapon != lastWeapon ) { self waittill( "weapon_change", currentWeapon ); } self endKillstreakWeaponSwitch(); } } removeUnitializedKillstreakWeapon() { self notify( "removeUnitializedKillstreakWeapon" ); self endon( "removeUnitializedKillstreakWeapon" ); self endon( "death" ); self endon( "disconnect" ); // self endon( "weapon_switch_started" ); // self endon( "weapon_switch_invalid" ); self waittill( "weapon_change", weaponName ); // If the player queues up an IED (or another cancellable equipment) // and then immediately cancels it by pressing Y and also presses right // on the d-pad to activate a killstreak code will only send one // weapon_change_started notify for the switch back to the primary. This // means no weapon_change_started is sent for the killstreak. This causes the // player to end up with the killstreak without the killstreak management logic // getting started. Players can then use the killstreak without actually having // it subtracted from their inventory. To fix this make sure the player is // not holding a killstreak after the weapon_change without the killstreak // logic running. If they are, switch them to their last weapon. // 06/26/2014-JC weaponIsStreakInFocus = IsDefined( self.killstreakIndexWeapon ) && IsDefined( self.pers[ "killstreaks" ] ) && IsDefined( self.pers[ "killstreaks" ][ self.killstreakIndexWeapon ] ) && IsDefined( self.pers["killstreaks"][self.killstreakIndexWeapon].streakName ) && weaponName == getKillstreakWeapon( self.pers["killstreaks"][self.killstreakIndexWeapon].streakName ); if ( weaponIsStreakInFocus && !IsDefined( self.KS_aboutToUse ) ) { // Take the weapon and reset the action slot so that the player can // call it in again. self TakeWeapon( weaponName ); self _giveWeapon( weaponName, 0 ); self _setActionSlot( 4, "weapon", weaponName ); lastWeapon = self getLastWeapon(); if ( !self HasWeapon( lastWeapon ) ) { lastWeapon = self maps\mp\killstreaks\_killstreaks::getFirstPrimaryWeapon(); } if ( IsDefined( lastWeapon ) ) { self switch_to_last_weapon( lastWeapon ); } } } beginKillstreakWeaponSwitch() { self _disableWeaponSwitch(); self _disableUsability(); self thread killstreakWeaponSwitchWatchHostMigration(); } endKillstreakWeaponSwitch() { self notify( "endKillstreakWeaponSwitch" ); self _enableWeaponSwitch(); self _enableUsability(); } killstreakWaitForWeaponChange() { self waittill( "weapon_change", newWeapon, isAltMode ); if ( !isAltMode ) { self notify( "ks_weapon_change" ); } else { self notify( "ks_alt_weapon_change" ); } } // killstreakWeaponSwitchWatchHostMigration // the host could migrate while we are pulling out or putting away a KS weapon (and we've disabled weapon switches) // since this cancels the KS switching, renable weapon switches in killstreakWeaponSwitchWatchHostMigration() { self endon( "death" ); level endon( "game_ended" ); self endon( "endKillstreakWeaponSwitch" ); level waittill( "host_migration_end" ); // use the code call, instead of the wrapper, because we are in a bad state after host migration. // useKillstreakWaiter is waiting for a weapon_changed event; if we re-use our KS, everything's great // otherwise, that function will fail and call the _enableWeaponSwitch wrapper again if ( isDefined( self ) ) self enableWeaponSwitch(); } waitTakeKillstreakWeapon( killstreakWeapon, lastWeapon ) { self endon( "disconnect" ); self endon( "finish_death" ); self endon( "joined_team" ); level endon( "game_ended" ); self notify( "waitTakeKillstreakWeapon" ); self endon( "waitTakeKillstreakWeapon" ); // planted killstreaks like the sam, sentry, remote turret, and ims, will come in here with none as the current weapon sometimes because we _disableWeapons() while you carry them // we need to know this so we can take the weapon correctly in these cases wasNone = ( self GetCurrentWeapon() == "none" ); // this lets the killstreak weapon animation play and then take it once we switch away from it self waittill( "weapon_change", newWeapon ); if( newWeapon == lastWeapon ) { takeKillstreakWeaponIfNoDupe( killstreakWeapon ); // pc needs to reset the killstreakIndexWeapon because we set this when they press the use button and we don't want the value lingering if( !level.console && !self is_player_gamepad_enabled() ) self.killstreakIndexWeapon = undefined; } // this could happen with ridden killstreaks like the ac130 else if( newWeapon != killstreakWeapon ) { self thread waitTakeKillstreakWeapon( killstreakWeapon, lastWeapon ); } // this could happen with planted killstreaks like the sam, sentry, remote turret, and ims // they come into this function with current weapon as none and then the weapon change fires off immediately because we call _enableWeapons() // that gives us back the killstreak weapon and it plays an animation before switching back to your normal weapon else if( wasNone && self GetCurrentWeapon() == killstreakWeapon ) { self thread waitTakeKillstreakWeapon( killstreakWeapon, lastWeapon ); } } takeKillstreakWeaponIfNoDupe( killstreakWeapon ) { // only take the killstreak weapon if they don't have anymore // the player could have two of the same killstreak and if we take the weapon then they can't use the second one hasKillstreak = false; self_pers_killstreaks = self.pers["killstreaks"]; for( i = 0; i < self_pers_killstreaks.size; i++ ) { if( IsDefined( self_pers_killstreaks[i] ) && IsDefined( self_pers_killstreaks[i].streakName ) && self_pers_killstreaks[i].available ) { // the specialist streaks use the killstreak_uav_mp weapon so don't try to compare specialist killstreak weapons // this fixes a bug where you earn a uav, change classes to specialist and earn the first streak, use the uav and the killstreak weapon doesn't get taken because it thinks you still have one if( !isSpecialistKillstreak( self_pers_killstreaks[i].streakName ) && killstreakWeapon == getKillstreakWeapon( self_pers_killstreaks[i].streakName ) ) { hasKillstreak = true; break; } } } // if they have the killstreak then check to see if the currently selected killstreak is the same killstreak, if not take the weapon because it'll be given to them when they select it if( hasKillstreak ) { if( level.console || self is_player_gamepad_enabled() ) { if( IsDefined( self.killstreakIndexWeapon ) && killstreakWeapon != getKillstreakWeapon( self_pers_killstreaks[self.killstreakIndexWeapon].streakName ) ) { // take the weapon because it's currently not the selected killstreak self TakeWeapon( killstreakWeapon ); } else if( IsDefined( self.killstreakIndexWeapon ) && killstreakWeapon == getKillstreakWeapon( self_pers_killstreaks[self.killstreakIndexWeapon].streakName ) ) { // take and give it right back, this fixes an issue where you could have two of the same weapons and after using the first then you couldn't use the second // this was reproduced by doing predator, precision airstrike, strafe run, where airstrike and strafe run use the same weapon // so if you called in the predator, then called in the strafe, you couldn't use the airstrike because you no longer have the weapon // script isn't taking the weapon from you but code was saying clear that slot because the weapons were 'clip only', they shouldn't be self TakeWeapon( killstreakWeapon ); self _giveWeapon( killstreakWeapon, 0 ); self _setActionSlot( 4, "weapon", killstreakWeapon ); } } // pc doesn't have selected killstreaks else { // we still want to take and give to make sure they have the weapon self TakeWeapon( killstreakWeapon ); self _giveWeapon( killstreakWeapon, 0 ); } } else { //for the case of queued hellicopter if ( killstreakWeapon == "" ) return; self TakeWeapon( killstreakWeapon ); } } shouldSwitchWeaponPostKillstreak( result, streakName ) { // certain killstreaks handle the weapon switching if( !result ) return true; if( isRideKillstreak( streakName ) ) return false; return true; } finishDeathWaiter() { self endon ( "disconnect" ); level endon ( "game_ended" ); self notify ( "finishDeathWaiter" ); self endon ( "finishDeathWaiter" ); self waittill ( "death" ); wait ( 0.05 ); self notify ( "finish_death" ); self.pers["lastEarnedStreak"] = undefined; } checkStreakReward() { foreach ( streakName in self.killstreaks ) { streakVal = getStreakCost( streakName ); if ( streakVal > self.adrenaline ) break; if ( self.previousAdrenaline < streakVal && self.adrenaline >= streakVal ) { // to avoid confusion about not really earning a killstreak if you already have it and come around again // we're going to give you the killstreak again and also allow it to chain self earnKillstreak( streakName, streakVal ); //// No stacking (double earning) //alreadyEarned = false; //for ( i=1; i KILLSTREAK_STACKING_START_SLOT && self isTryingToUseKillstreakInGimmeSlot() ) { // Copy current gimme slot killstreak info to the new top // of the stack addedToTop = false; addedSlot = nextSlot; currSlot = nextSlot - 1; currStruct = self.pers[ "killstreaks" ][ currSlot ]; addedStruct = self.pers[ "killstreaks" ][ addedSlot ]; addedStruct.available = currStruct.available; addedStruct.streakName = currStruct.streakName; addedStruct.earned = currStruct.earned; addedStruct.awardxp = currStruct.awardxp; addedStruct.owner = currStruct.owner; addedStruct.kID = currStruct.kID; addedStruct.lifeId = currStruct.lifeId; addedStruct.isGimme = currStruct.isGimme; addedStruct.isSpecialist = currStruct.isSpecialist; // Update next slot index to be the original top. The new killstreak // will be copied into here. nextSlot = currSlot; } self_pers_killstreak_nextSlot = self.pers[ "killstreaks" ][ nextSlot ]; self_pers_killstreak_nextSlot.available = false; self_pers_killstreak_nextSlot.streakName = streakName; self_pers_killstreak_nextSlot.earned = false; self_pers_killstreak_nextSlot.awardxp = IsDefined( awardXp ) && awardXp; self_pers_killstreak_nextSlot.owner = owner; self_pers_killstreak_nextSlot.kID = streakID; self_pers_killstreak_nextSlot.lifeId = -1; self_pers_killstreak_nextSlot.isGimme = true; self_pers_killstreak_nextSlot.isSpecialist = false; // If the killstreak was not added to the top of the stack, it was slid // underneath the currently in use gimme slot killstreak. In this case // update the stored gimme slot stack index, increment the killstreak // ID for future killstreaks and get the F out! - JC-09/17-13 if ( !addedToTop ) { // Point the gimme slot to the new top of the stack self.pers[ "killstreaks" ][ KILLSTREAK_GIMME_SLOT ].nextSlot = nextSlot + 1; // Because the new killstreak was not added to the top of the gimme // queue there is no need to update the gimme slot or to give the // player any weapons. return; } if( !IsDefined(slotNumber) ) slotNumber = KILLSTREAK_GIMME_SLOT; self.pers[ "killstreaks" ][ slotNumber ].nextSlot = nextSlot; self.pers[ "killstreaks" ][ slotNumber ].streakName = streakName; index = slotNumber; streakIndex = getKillstreakIndex( streakName ); self setCommonPlayerData( "killstreaksState", "icons", slotNumber, streakIndex ); } else { for( i = KILLSTREAK_SLOT_1; i < KILLSTREAK_SLOT_3 + 1; i++ ) { self_pers_killstreak_i = self.pers["killstreaks"][i]; if( IsDefined( self_pers_killstreak_i ) && IsDefined( self_pers_killstreak_i.streakName ) && streakName == self_pers_killstreak_i.streakName ) { index = i; break; } } if ( !IsDefined( index ) ) { AssertMsg( "earnKillstreak() trying to give unearnable killstreak with giveKillstreak(): " + streakName ); return; } } self_pers_killstreak_index = self.pers["killstreaks"][index]; self_pers_killstreak_index.available = true; self_pers_killstreak_index.earned = IsDefined( isEarned ) && isEarned; self_pers_killstreak_index.awardxp = IsDefined( awardXp ) && awardXp; self_pers_killstreak_index.owner = owner; self_pers_killstreak_index.kID = streakID; //self.pers["kIDs_valid"][self.pers["kID"]] = true; if ( !self_pers_killstreak_index.earned ) self_pers_killstreak_index.lifeId = -1; else self_pers_killstreak_index.lifeId = self.pers["deaths"]; AssertEx( isDefined(self), "Player to be rewarded is undefined" ); AssertEx( IsPlayer(self), "Somehow a non player ent is receiving a killstreak reward" ); AssertEx( isDefined(self.streakType), "Player: "+ self.name + " doesn't have a streakType defined" ); // the specialist streak type automatically turns on and there is no weapon to use if( self.streakType == "specialist" && index != KILLSTREAK_GIMME_SLOT ) { self_pers_killstreak_index.isSpecialist = true; if( IsDefined( level.killstreakFuncs[ streakName ] ) ) self [[ level.killstreakFuncs[ streakName ] ]]( -1, streakName ); //self thread updateKillstreaks(); self usedKillstreak( streakName, awardXp ); } else { if( level.console || self is_player_gamepad_enabled() ) { weapon = getKillstreakWeapon( streakName ); self giveKillstreakWeapon( weapon ); // NOTE_A (also see NOTE_B): before we change the killstreakIndexWeapon, let's make sure it's not the one we're holding // if we're currently holding something like an airdrop marker and we earned a killstreak while holding it then we want that to remain the weapon index // because if it's not, then when you throw it, it'll think we're using a different killstreak and not take it away but it'll take away the other one if( IsDefined( self.killstreakIndexWeapon ) ) { streakName = self.pers["killstreaks"][self.killstreakIndexWeapon].streakName; killstreakWeapon = getKillstreakWeapon( streakName ); if( !( self isHoldingWeapon( killstreakWeapon ) ) ) { self.killstreakIndexWeapon = index; } } else { self.killstreakIndexWeapon = index; } } else { // for pc, we need to give you the killstreak weapon in the right action slot // if this is the gimme slot then take away the weapon for what is in there right now and just give them this new one // we don't want to keep giving weapons every time they get something in the gimme slot because there is a cap eventually if( KILLSTREAK_GIMME_SLOT == index && self.pers[ "killstreaks" ][ KILLSTREAK_GIMME_SLOT ].nextSlot > KILLSTREAK_STACKING_START_SLOT ) { // since nextSlot has already been incremented, get the next lowest and take the weapon slotToTake = self.pers[ "killstreaks" ][ KILLSTREAK_GIMME_SLOT ].nextSlot - 1; killstreakWeaponToTake = getKillstreakWeapon( self.pers["killstreaks"][ slotToTake ].streakName ); self TakeWeapon( killstreakWeaponToTake ); } killstreakWeapon = getKillstreakWeapon( streakName ); self _giveWeapon( killstreakWeapon, 0 ); enableActionSlot = true; // disable the new action slot if we need to if( IsDefined( self.killstreakIndexWeapon ) ) { streakName = self.pers["killstreaks"][self.killstreakIndexWeapon].streakName; killstreakWeapon = getKillstreakWeapon( streakName ); // don't update the action slot if we are currently holding the killstreak weapon // or "none" which means we are trying to place an IMS/SatCom enableActionSlot = !( self isHoldingWeapon( killstreakWeapon ) ) && (self GetCurrentWeapon() != "none"); } if ( enableActionSlot ) { self _setActionSlot( index + 4, "weapon", killstreakWeapon ); } else { self _setActionSlot( index + 4, "" ); self.actionSlotEnabled[ index ] = false; } } } self updateStreakSlots(); if ( IsDefined( level.killstreakSetupFuncs[ streakName ] ) ) self [[ level.killstreakSetupFuncs[ streakName ] ]](); if ( IsDefined( isEarned ) && isEarned && IsDefined( awardXp ) && awardXp ) self notify( "received_earned_killstreak" ); } giveKillstreakWeapon( weapon ) { self endon( "disconnect" ); // pc doesn't need to give the weapon because you use on a single button press (unless using gamepad) if( !level.console && !self is_player_gamepad_enabled() ) return; // If the custom juggernaut cannot use the specified killstreak, make sure we clear out the slot to prevent them from using it streakName = getKillstreakReferenceByWeapon( weapon ); if ( !canCustomJuggUseKillstreak( streakName ) ) { self _setActionSlot( 4, "" ); return; } weaponList = self GetWeaponsListItems(); foreach( item in weaponList ) { if( !isStrStart( item, "killstreak_" ) && !isStrStart( item, "airdrop_" ) && !isStrStart( item, "deployable_" ) ) continue; // need to do an extra check here because current weapon could be "none" but the weapon we're changing to could be one of the items in the weaponList // this fixes a bug where you could be pulling out a care package when you earned the next killstreak and it would not give you the next killstreak but give you an extra care package instead if ( self isHoldingWeapon( item ) ) continue; while( self isChangingWeapon() ) wait ( 0.05 ); self TakeWeapon( item ); } // NOTE_B (also see NOTE_A) : before we giving the killstreak weapon, let's make sure it's not the one we're holding // if we're currently holding something like an airdrop marker and we earned a killstreak while holding it then we want that to remain the killstreak weapon // because if it's not, then when we earn the new killstreak, we won't be able to put this one away because it thinks it's something else if( IsDefined( self.killstreakIndexWeapon ) ) { streakName = self.pers["killstreaks"][self.killstreakIndexWeapon].streakName; killstreakWeapon = getKillstreakWeapon( streakName ); if ( !(self isHoldingWeapon( killstreakWeapon )) ) { if( weapon != "" ) { self _giveWeapon( weapon, 0 ); self _setActionSlot( 4, "weapon", weapon ); } } } else { self _giveWeapon( weapon, 0 ); self _setActionSlot( 4, "weapon", weapon ); } } isHoldingWeapon( weapon ) { return ( self GetCurrentWeapon() == weapon || (IsDefined( self.changingWeapon ) && self.changingWeapon == weapon )); } getStreakCost( streakName ) { cost = int( getKillstreakKills( streakName ) ); if( IsDefined( self ) && IsPlayer( self ) ) { if( isSpecialistKillstreak( streakName ) ) { if ( isDefined( self.pers["gamemodeLoadout"] ) ) { if ( isDefined( self.pers["gamemodeLoadout"]["loadoutKillstreak1"] ) && self.pers["gamemodeLoadout"]["loadoutKillstreak1"] == streakName ) cost = 2; else if ( isDefined( self.pers["gamemodeLoadout"]["loadoutKillstreak2"] ) && self.pers["gamemodeLoadout"]["loadoutKillstreak2"] == streakName ) cost = 4; else if ( isDefined( self.pers["gamemodeLoadout"]["loadoutKillstreak3"] ) && self.pers["gamemodeLoadout"]["loadoutKillstreak3"] == streakName ) cost = 6; else AssertMsg( "getStreakCost: killstreak doesn't exist in player's loadout" ); } else if ( IsSubStr( self.curClass, "custom" ) ) { index = 0; for( ; index < 3; index++ ) { killstreak = self getCaCPlayerData( "loadouts", self.class_num, "specialistStreaks", index ); if( killstreak == streakName ) break; } AssertEx( index <= 2, "getStreakCost: killstreak index greater than 2 when it shouldn't be" ); cost = self getCaCPlayerData( "loadouts", self.class_num, "specialistStreakKills", index ); } else if ( IsSubStr( self.curClass, "callback" ) ) { assert( isAI( self ) ); assert( isdefined( self.pers[ "specialistStreaks" ] ) ); assert( isdefined( self.pers[ "specialistStreakKills" ] ) ); assert( self.pers[ "specialistStreakKills" ].size == self.pers[ "specialistStreaks" ].size ); index = 0; foreach ( index, streak in self.pers[ "specialistStreaks" ] ) { if ( streak == streakName ) break; } assert( index >= 0 && index < self.pers[ "specialistStreakKills" ].size ); cost = self.pers[ "specialistStreakKills" ][index]; } else if ( isSubstr( self.curClass, "axis" ) || isSubstr( self.curClass, "allies" ) ) { index = 0; teamName = "none"; if( isSubstr( self.curClass, "axis" ) ) { teamName = "axis"; } else if( isSubstr( self.curClass, "allies" ) ) { teamName = "allies"; } classIndex = getClassIndex( self.curClass ); for( ; index < 3; index++ ) { killstreak = GetMatchRulesData( "defaultClasses", teamName, classIndex, "class", "specialistStreaks", index ); if( killstreak == streakName ) break; } AssertEx( index <= 2, "getStreakCost: killstreak index greater than 2 when it shouldn't be" ); cost = GetMatchRulesData( "defaultClasses", teamName, classIndex, "class", "specialistStreakKills", index ); } } if( self _hasPerk( "specialty_hardline" ) && cost > 0 ) cost--; } // JC-12/03/13- Hackers are adjusting their specialist perk kill // requirement in player data to be enormous. This is causing some // suspect script in _class::setKillstreaks() to throw an infinite // loop assert. In ship this is killing the thread and preventing // the player from getting a body. This results in an invisible player // who is also invincible. Simple fix, clamp the killstreak cost. // JC-ToDo: May want to clamp this according to a precalculated // max killstreak cost or make the logic in setKillstreaks() better. cost = Int( clamp( cost, 0, 30 ) ); return cost; } streakTypeResetsOnDeath( streakType ) { switch ( streakType ) { case "assault": case "specialist": return true; case "support": return false; } } giveOwnedKillstreakItem( skipDialog ) { self_pers_killstreaks = self.pers["killstreaks"]; if( level.console || self is_player_gamepad_enabled() ) { // find the highest costing streak keepIndex = -1; highestCost = -1; for( i = KILLSTREAK_GIMME_SLOT; i < KILLSTREAK_SLOT_3 + 1; i++ ) { if( IsDefined( self_pers_killstreaks[i] ) && IsDefined( self_pers_killstreaks[i].streakName ) && self_pers_killstreaks[i].available && getStreakCost( self_pers_killstreaks[i].streakName ) > highestCost ) { // make sure the gimme slot is the lowest regardless of the cost of the killstreak in it highestCost = 0; if( !self_pers_killstreaks[i].isGimme ) highestCost = getStreakCost( self_pers_killstreaks[i].streakName ); keepIndex = i; } } if ( keepIndex != -1 ) { // select it self.killstreakIndexWeapon = keepIndex; // give the weapon streakName = self_pers_killstreaks[self.killstreakIndexWeapon].streakName; weapon = getKillstreakWeapon( streakName ); self giveKillstreakWeapon( weapon ); } else self.killstreakIndexWeapon = undefined; } // pc doesn't select killstreaks, unless game pad is enabled else { keepIndex = -1; highestCost = -1; // make sure we still have all of the available killstreak weapons for( i = KILLSTREAK_GIMME_SLOT; i < KILLSTREAK_SLOT_3 + 1; i++ ) { if( IsDefined( self_pers_killstreaks[i] ) && IsDefined( self_pers_killstreaks[i].streakName ) && self_pers_killstreaks[i].available ) { killstreakWeapon = getKillstreakWeapon( self_pers_killstreaks[i].streakName ); weaponsListItems = self GetWeaponsListItems(); hasKillstreakWeapon = false; for( j = 0; j < weaponsListItems.size; j++ ) { if( killstreakWeapon == weaponsListItems[j] ) { hasKillstreakWeapon = true; break; } } if( !hasKillstreakWeapon ) { self _giveWeapon( killstreakWeapon ); } else { // if we have more than one airdrop type weapon the ammo gets set to 0 because we give the next airdrop weapon before we take the last one // this is a quicker fix than trying to figure out how to take and give at the right times if( IsSubStr( killstreakWeapon, "airdrop_" ) ) self SetWeaponAmmoClip( killstreakWeapon, 1 ); } // since the killstreak is available, make sure the actionslot is set correctly // this fixes a bug where you could have, for example, a uav in your gimme slot and earned a uav before you died, when you respawned the earned uav actionslot wasn't set and you couldn't use it self _setActionSlot( i + 4, "weapon", killstreakWeapon ); // get the highest value killstreak so we can show hint text for it on spawn // make sure the gimme slot is the lowest regardless of the cost of the killstreak in it if( getStreakCost( self_pers_killstreaks[i].streakName ) > highestCost ) { highestCost = 0; if( !self_pers_killstreaks[i].isGimme ) highestCost = getStreakCost( self_pers_killstreaks[i].streakName ); keepIndex = i; } } } if ( keepIndex != -1 ) { streakName = self_pers_killstreaks[ keepIndex ].streakName; } self.killstreakIndexWeapon = undefined; } updateStreakSlots(); } initRideKillstreak( streak ) { self _disableUsability(); result = self initRideKillstreak_internal( streak ); if ( IsDefined( self ) ) self _enableUsability(); return result; } initRideKillstreak_internal( streak ) { if ( IsDefined( streak ) && isLaptopTimeoutKillstreak( streak ) ) laptopWait = "timeout"; else laptopWait = self waittill_any_timeout( 1.0, "disconnect", "death", "weapon_switch_started" ); maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone(); 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 isKillStreakDenied() ) { return ( "fail" ); } if( !IsDefined(streak) || !IsSubStr( streak, "odin" ) ) { self VisionSetNakedForPlayer( "black_bw", 0.75 ); self thread set_visionset_for_watching_players( "black_bw", 0.75, 1.0, undefined, true ); blackOutWait = self waittill_any_timeout( 0.80, "disconnect", "death" ); } else { blackOutWait = self waittill_any_timeout( 1.0, "disconnect", "death" ); } self notify( "black_out_done" ); maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone(); if ( blackOutWait != "disconnect" ) { if( !IsDefined(streak) || !IsSubStr( streak, "odin" ) ) self thread clearRideIntro( 1.0 ); else self notify( "intro_cleared" ); if ( self.team == "spectator" ) return "fail"; } if ( self isOnLadder() ) return "fail"; if ( !isAlive( self ) ) return "fail"; if ( self isKillStreakDenied() ) return "fail"; if ( blackOutWait == "disconnect" ) return ( "disconnect" ); else return ( "success" ); } isLaptopTimeoutKillstreak( streak ) { switch( streak ) { case "osprey_gunner": case "remote_uav": case "remote_tank": case "heli_pilot": case "vanguard": case "drone_hive": case "odin_support": case "odin_assault": case "ca_a10_strafe": // Level Specific case "ac130": // Level Specific return true; } return false; } clearRideIntro( delay, fadeBack ) { self endon( "disconnect" ); if ( IsDefined( delay ) ) wait( delay ); if ( !isDefined( fadeBack ) ) fadeBack = 0; //self freezeControlsWrapper( false ); self VisionSetNakedForPlayer( "", fadeBack ); // go to default visionset self set_visionset_for_watching_players( "", fadeBack ); self notify( "intro_cleared" ); } allowRideKillstreakPlayerExit( earlyEndNotify ) // self == killstreak { if ( isDefined( earlyEndNotify ) ) { self endon( earlyEndNotify ); } if( !IsDefined( self.owner ) ) return; owner = self.owner; level endon( "game_ended" ); owner endon ( "disconnect" ); owner endon ( "end_remote" ); self endon ( "death" ); while( true ) { timeUsed = 0; while( owner UseButtonPressed() ) { timeUsed += 0.05; if( timeUsed > 0.75 ) { self notify( "killstreakExit" ); return; } wait( 0.05 ); } wait( 0.05 ); } } giveSelectedKillstreakItem() { streakName = self.pers["killstreaks"][self.killstreakIndexWeapon].streakName; weapon = getKillstreakWeapon( streakName ); self giveKillstreakWeapon( weapon ); self updateStreakSlots(); } getKillstreakCount() { numAvailable = 0; for( i = KILLSTREAK_GIMME_SLOT; i < KILLSTREAK_SLOT_3 + 1; i++ ) { if( IsDefined( self.pers["killstreaks"][i] ) && IsDefined( self.pers["killstreaks"][i].streakName ) && self.pers["killstreaks"][i].available ) { numAvailable++; } } return numAvailable; } shuffleKillstreaksUp() { if ( getKillstreakCount() > 1 ) { while ( true ) { self.killstreakIndexWeapon++; if ( self.killstreakIndexWeapon > KILLSTREAK_SLOT_3 ) self.killstreakIndexWeapon = 0; if ( self.pers["killstreaks"][self.killstreakIndexWeapon].available == true ) break; } giveSelectedKillstreakItem(); } } shuffleKillstreaksDown() { if ( getKillstreakCount() > 1 ) { while ( true ) { self.killstreakIndexWeapon--; if ( self.killstreakIndexWeapon < 0 ) self.killstreakIndexWeapon = KILLSTREAK_SLOT_3; if ( self.pers["killstreaks"][self.killstreakIndexWeapon].available == true ) break; } giveSelectedKillstreakItem(); } } streakSelectUpTracker() { self endon ( "death" ); self endon ( "disconnect" ); self endon ( "faux_spawn" ); level endon ( "game_ended" ); for (;;) { self waittill( "toggled_up" ); if ( !level.Console && !self is_player_gamepad_enabled() ) continue; if( IsDefined( self.showingTacticalSelections ) && self.showingTacticalSelections ) continue; if( !self isMantling() && ( !IsDefined( self.changingWeapon ) || ( IsDefined( self.changingWeapon ) && self.changingWeapon == "none" ) ) && ( !isKillstreakWeapon( self GetCurrentWeapon() ) || isMiniGun(self GetCurrentWeapon()) || self GetCurrentWeapon() == "venomxgun_mp" || ( isKillstreakWeapon( self GetCurrentWeapon() ) && self isJuggernaut() ) ) && self.streakType != "specialist" && ( !IsDefined( self.isCarrying ) || ( IsDefined( self.isCarrying ) && self.isCarrying == false ) ) && ( !IsDefined( self.lastStreakUsed ) || ( IsDefined( self.lastStreakUsed ) && ( GetTime() - self.lastStreakUsed ) > 100 ) ) ) { self shuffleKillstreaksUp(); self SetClientOmnvar( "ui_killstreak_scroll", 1 ); } wait( .12 ); } } isMiniGun( sWeapon ) { return (sWeapon == "iw6_minigunjugg_mp" ); } streakSelectDownTracker() { self endon ( "death" ); self endon ( "disconnect" ); self endon ( "faux_spawn" ); level endon ( "game_ended" ); for (;;) { self waittill( "toggled_down" ); if ( !level.Console && !self is_player_gamepad_enabled() ) continue; if( IsDefined( self.showingTacticalSelections ) && self.showingTacticalSelections ) continue; if( !self isMantling() && ( !IsDefined( self.changingWeapon ) || ( IsDefined( self.changingWeapon ) && self.changingWeapon == "none" ) ) && ( !isKillstreakWeapon( self GetCurrentWeapon() ) || isMiniGun(self GetCurrentWeapon()) || self GetCurrentWeapon() == "venomxgun_mp" || ( isKillstreakWeapon( self GetCurrentWeapon() ) && self isJuggernaut() ) ) && self.streakType != "specialist" && ( !IsDefined( self.isCarrying ) || ( IsDefined( self.isCarrying ) && self.isCarrying == false ) ) && ( !IsDefined( self.lastStreakUsed ) || ( IsDefined( self.lastStreakUsed ) && ( GetTime() - self.lastStreakUsed ) > 100 ) ) ) { self shuffleKillstreaksDown(); self SetClientOmnvar( "ui_killstreak_scroll", 1 ); } wait( .12 ); } } streakUseTimeTracker() { self endon ( "death" ); self endon ( "disconnect" ); self endon ( "faux_spawn" ); level endon ( "game_ended" ); for (;;) { self waittill( "streakUsed" ); self.lastStreakUsed = GetTime(); } } streakNotifyTracker() { self endon ( "death" ); self endon ( "disconnect" ); level endon ( "game_ended" ); if ( IsBot(self) ) return; // Bots handle killstreaks internally gameFlagWait( "prematch_done" ); if( level.console || self is_player_gamepad_enabled() ) { self notifyOnPlayerCommand( "toggled_up", "+actionslot 1" ); self notifyOnPlayerCommand( "toggled_down", "+actionslot 2" ); self notifyOnPlayerCommand( "streakUsed", "+actionslot 4" ); self notifyOnPlayerCommand( "streakUsed", "+actionslot 5" ); self notifyOnPlayerCommand( "streakUsed", "+actionslot 6" ); self notifyOnPlayerCommand( "streakUsed", "+actionslot 7" ); } if( !level.console ) { self notifyOnPlayerCommand( "streakUsed1", "+actionslot 4" ); self notifyOnPlayerCommand( "streakUsed2", "+actionslot 5" ); self notifyOnPlayerCommand( "streakUsed3", "+actionslot 6" ); self notifyOnPlayerCommand( "streakUsed4", "+actionslot 7" ); } } // ADRENALINE STUFF MOVED FROM _UTILITY.GSC // TODO: rename registerAdrenalineInfo( type, value ) { if ( !IsDefined( level.adrenalineInfo ) ) level.adrenalineInfo = []; level.adrenalineInfo[type] = value; } giveAdrenaline( type ) { assertEx( IsDefined( level.adrenalineInfo[type] ), "Unknown adrenaline type: " + type ); if ( level.adrenalineInfo[type] == 0 ) return; //fixes bug with juggernaut bomb carrier if ( self isJuggernaut() && self.streakType == "specialist" ) return; newAdrenaline = self.adrenaline + level.adrenalineInfo[type]; adjustedAdrenaline = newAdrenaline; maxStreakCost = self getMaxStreakCost(); if ( adjustedAdrenaline > maxStreakCost && ( self.streakType != "specialist" ) ) { adjustedAdrenaline = adjustedAdrenaline - maxStreakCost; } else if ( level.killstreakRewards && adjustedAdrenaline > maxStreakCost && self.streakType == "specialist" ) { // at a certain number of kills we'll give you the bonus perks specialist_max_kills = maxStreakCost; if ( isAI( self ) ) specialist_max_kills = self.pers["specialistStreakKills"][2]; numKills = int( max( MIN_NUM_KILLS_GIVE_BONUS_PERKS, ( specialist_max_kills + 2 ) ) ); if( self _hasPerk( "specialty_hardline" ) ) numKills--; // there is a case where you could get hardline as your last specialist perk and not get the bonus because your kills will be more than numKills needed should_give_bonus = ( adjustedAdrenaline >= numKills && self GetCommonPlayerData( "killstreaksState", "hasStreak", KILLSTREAK_BONUS_PERKS_SLOT ) == false ); if( should_give_bonus ) { //self thread giveKillstreak( "airdrop_assault", false, true, self ); //self thread maps\mp\gametypes\_hud_message::killstreakSplashNotify( "airdrop_assault", 8 ); self giveBonusPerks(); self usedKillstreak( "all_perks_bonus", true ); self thread maps\mp\gametypes\_hud_message::killstreakSplashNotify( "all_perks_bonus", numKills ); self setCommonPlayerData( "killstreaksState", "hasStreak", KILLSTREAK_BONUS_PERKS_SLOT, true ); self.pers["killstreaks"][ KILLSTREAK_BONUS_PERKS_SLOT ].available = true; } // give a little xp for being maxed out and continued streaking, for the specialist only // every two kills after max if( maxStreakCost > 0 && !( ( adjustedAdrenaline - maxStreakCost ) % 2 ) ) { self thread maps\mp\gametypes\_rank::xpEventPopup( "specialist_streaking_xp" ); self thread maps\mp\gametypes\_rank::giveRankXP( "kill" ); } } self setAdrenaline( adjustedAdrenaline ); self checkStreakReward(); if ( newAdrenaline == maxStreakCost && ( self.streakType != "specialist" ) ) setAdrenaline( 0 ); } giveBonusPerks() // self == player { // for the specialist strike package when you get to a certain number of kills // give them bonus perks if ( isAI( self ) ) { if ( IsDefined( self.pers ) && IsDefined( self.pers[ "specialistBonusStreaks" ] ) ) { foreach ( abilityRef in self.pers["specialistBonusStreaks"] ) { if( !self _hasPerk( abilityRef ) ) { self givePerk( abilityRef, false ); } } } } else { for( abilityCategoryIndex = 0; abilityCategoryIndex < NUM_ABILITY_CATEGORIES; abilityCategoryIndex++ ) { for( abilityIndex = 0; abilityIndex < NUM_SUB_ABILITIES; abilityIndex++ ) { picked = false; if( IsDefined( self.teamName ) ) { picked = getMatchRulesData( "defaultClasses", self.teamName, self.class_num, "class", "specialistBonusStreaks", abilityCategoryIndex, abilityIndex ); } else { picked = self GetCaCPlayerData( "loadouts", self.class_num, "specialistBonusStreaks", abilityCategoryIndex, abilityIndex ); } if( IsDefined( picked ) && picked ) { abilityRef = TableLookup( "mp/cacAbilityTable.csv", 0, abilityCategoryIndex + 1, 4 + abilityIndex ); if( !self _hasPerk( abilityRef ) ) { self givePerk( abilityRef, false ); } } } } } } resetAdrenaline() { self.earnedStreakLevel = 0; self setAdrenaline(0); self resetStreakCount(); self.pers["lastEarnedStreak"] = undefined; self.pers["objectivePointStreak"] = 0; self SetClientOmnvar( "ui_half_tick", false ); } setAdrenaline( value ) { if ( value < 0 ) value = 0; if ( IsDefined( self.adrenaline ) ) self.previousAdrenaline = self.adrenaline; else self.previousAdrenaline = 0; self.adrenaline = value; self setClientDvar( "ui_adrenaline", self.adrenaline ); self updateStreakCount(); } pc_watchGamepad() // self == player { self endon ( "death" ); self endon ( "disconnect" ); level endon ( "game_ended" ); game_pad_enabled = self is_player_gamepad_enabled(); while( true ) { if( game_pad_enabled != self is_player_gamepad_enabled() ) { game_pad_enabled = self is_player_gamepad_enabled(); if( !game_pad_enabled ) { if( IsDefined( self.actionSlotEnabled ) ) { // turn on all of the actionSlotEnables for( i = KILLSTREAK_GIMME_SLOT; i < KILLSTREAK_SLOT_3 + 1; i++ ) { self.actionSlotEnabled[ i ] = true; } // make sure the action notifies are correct self notifyOnPlayerCommand( "streakUsed1", "+actionslot 4" ); self notifyOnPlayerCommand( "streakUsed2", "+actionslot 5" ); self notifyOnPlayerCommand( "streakUsed3", "+actionslot 6" ); self notifyOnPlayerCommand( "streakUsed4", "+actionslot 7" ); self giveOwnedKillstreakItem(); } } else { // take all of the killstreak weapons, unless you're holding one weapon_list = self GetWeaponsListItems(); foreach( weapon in weapon_list ) { if( isKillstreakWeapon( weapon ) && weapon == self GetCurrentWeapon() ) { self SwitchToWeapon( self getLastWeapon() ); while( self isChangingWeapon() ) wait( 0.05 ); } if( isKillstreakWeapon( weapon ) ) self TakeWeapon( weapon ); } // clear the action slots, it should get filled by the function call below for( i = KILLSTREAK_GIMME_SLOT; i < KILLSTREAK_SLOT_3 + 1; i++ ) { self _setActionSlot( i + 4, "" ); self.actionSlotEnabled[ i ] = false; } // make sure the action notifies are correct self notifyOnPlayerCommand( "toggled_up", "+actionslot 1" ); self notifyOnPlayerCommand( "toggled_down", "+actionslot 2" ); self notifyOnPlayerCommand( "streakUsed", "+actionslot 4" ); self notifyOnPlayerCommand( "streakUsed", "+actionslot 5" ); self notifyOnPlayerCommand( "streakUsed", "+actionslot 6" ); self notifyOnPlayerCommand( "streakUsed", "+actionslot 7" ); self giveOwnedKillstreakItem(); } } wait( 0.05 ); } } pc_watchStreakUse() // self == player { self endon ( "death" ); self endon ( "disconnect" ); level endon ( "game_ended" ); self.actionSlotEnabled = []; self.actionSlotEnabled[ KILLSTREAK_GIMME_SLOT ] = true; self.actionSlotEnabled[ KILLSTREAK_SLOT_1 ] = true; self.actionSlotEnabled[ KILLSTREAK_SLOT_2 ] = true; self.actionSlotEnabled[ KILLSTREAK_SLOT_3 ] = true; while( true ) { result = self waittill_any_return( "streakUsed1", "streakUsed2", "streakUsed3", "streakUsed4" ); if( self is_player_gamepad_enabled() ) continue; if( !IsDefined( result ) ) continue; // specialist can only use the gimme slot if( self.streakType == "specialist" && result != "streakUsed1" ) continue; // don't let the killstreakIndexWeapon change while we are at none weapon because that could mean we're carrying something like sentry or ims if( IsDefined( self.changingWeapon ) && self.changingWeapon == "none" ) continue; // corresponding to code change that we're no longer allowing player to 'queue up' a killstreak // when they're cooking a grenade or holding up the underbarrel grenade launcher if( self IsOffhandWeaponReadyToThrow() ) continue; switch( result ) { case "streakUsed1": if( self.pers["killstreaks"][ KILLSTREAK_GIMME_SLOT ].available && self.actionSlotEnabled[ KILLSTREAK_GIMME_SLOT ] ) self.killstreakIndexWeapon = KILLSTREAK_GIMME_SLOT; break; case "streakUsed2": if( self.pers["killstreaks"][ KILLSTREAK_SLOT_1 ].available && self.actionSlotEnabled[ KILLSTREAK_SLOT_1 ] ) self.killstreakIndexWeapon = KILLSTREAK_SLOT_1; break; case "streakUsed3": if( self.pers["killstreaks"][ KILLSTREAK_SLOT_2 ].available && self.actionSlotEnabled[ KILLSTREAK_SLOT_2 ] ) self.killstreakIndexWeapon = KILLSTREAK_SLOT_2; break; case "streakUsed4": if( self.pers["killstreaks"][ KILLSTREAK_SLOT_3 ].available && self.actionSlotEnabled[ KILLSTREAK_SLOT_3 ] ) self.killstreakIndexWeapon = KILLSTREAK_SLOT_3; break; } // just a sanity check to make sure we reset the killstreakIndexWeapon if( IsDefined( self.killstreakIndexWeapon ) && !self.pers["killstreaks"][ self.killstreakIndexWeapon ].available ) self.killstreakIndexWeapon = undefined; if( IsDefined( self.killstreakIndexWeapon ) ) { self disableKillstreakActionSlots(); while( true ) { self waittill( "weapon_change", newWeapon, isAltToggle ); if( IsDefined( self.killstreakIndexWeapon ) ) { killstreakWeapon = getKillstreakWeapon( self.pers["killstreaks"][ self.killstreakIndexWeapon ].streakName ); // if this is the killstreak weapon or none or alt mode switch then continue and wait for the next weapon change // remote uav gives you a different weapon than the killstreak weapon from the killstreaktable.csv, so we need to also check for that if( newWeapon == killstreakWeapon || newWeapon == "none" || ( killstreakWeapon == "killstreak_uav_mp" && newWeapon == "killstreak_remote_uav_mp" ) || ( killstreakWeapon == "killstreak_uav_mp" && newWeapon == "uav_remote_mp" ) || isAltToggle ) continue; break; } break; } // they either used the killstreak or cancelled it self enableKillstreakActionSlots(); self.killstreakIndexWeapon = undefined; } } } disableKillstreakActionSlots() // self == player { for( i = KILLSTREAK_GIMME_SLOT; i < KILLSTREAK_SLOT_3 + 1; i++ ) { if( !IsDefined( self.killstreakIndexWeapon ) ) break; if( self.killstreakIndexWeapon == i ) continue; // clear all other killstreak slots while we are using a killstreak so they can't try to use another one self _setActionSlot( i + 4, "" ); self.actionSlotEnabled[ i ] = false; } } enableKillstreakActionSlots() // self == player { for( i = KILLSTREAK_GIMME_SLOT; i < KILLSTREAK_SLOT_3 + 1; i++ ) { // turn all of the action slots back on if( self.pers["killstreaks"][ i ].available ) { killstreakWeapon = getKillstreakWeapon( self.pers["killstreaks"][ i ].streakName ); self _setActionSlot( i + 4, "weapon", killstreakWeapon ); } else { // since this killstreak isn't available, clear the action slot so they can't pull an empty weapon out self _setActionSlot( i + 4, "" ); } // even if they don't have a killstreak in this slot we need to switch this flag for later uses // if we don't, then once they earn it they won't be able to use it because this flag is off self.actionSlotEnabled[ i ] = true; } } killstreakHit( attacker, weapon, vehicle ) { if( IsDefined( weapon ) && isPlayer( attacker ) && IsDefined( vehicle.owner ) && IsDefined( vehicle.owner.team ) ) { if ( ( (level.teamBased && vehicle.owner.team != attacker.team) || !level.teamBased ) && attacker != vehicle.owner ) { if( isKillstreakWeapon( weapon ) ) return; if ( !isDefined( attacker.lastHitTime[ weapon ] ) ) attacker.lastHitTime[ weapon ] = 0; // already hit with this weapon on this frame if ( attacker.lastHitTime[ weapon ] == getTime() ) return; attacker.lastHitTime[ weapon ] = getTime(); attacker thread maps\mp\gametypes\_gamelogic::threadedSetWeaponStatByName( weapon, 1, "hits" ); if ( !IsSquadsMode() ) { totalShots = attacker maps\mp\gametypes\_persistence::statGetBuffered( "totalShots" ); hits = attacker maps\mp\gametypes\_persistence::statGetBuffered( "hits" ) + 1; if ( hits <= totalShots ) { attacker maps\mp\gametypes\_persistence::statSetBuffered( "hits", hits ); attacker maps\mp\gametypes\_persistence::statSetBuffered( "misses", int(totalShots - hits) ); attacker maps\mp\gametypes\_persistence::statSetBuffered( "accuracy", int(hits * 10000 / totalShots) ); } } } } } copy_killstreak_status( from, noTransfer ) { self.streakType = from.streakType; self.pers[ "cur_kill_streak" ] = from.pers[ "cur_kill_streak" ]; //self setPlayerStat( "killstreak", from getPlayerStat( "killstreak" ) ); self maps\mp\gametypes\_persistence::statSetChild( "round", "killStreak", self.pers[ "cur_kill_streak" ] ); self.pers["killstreaks"] = from.pers["killstreaks"]; self.killstreaks = from.killstreaks; if ( !IsDefined( noTransfer ) || noTransfer == false ) { //this is pretty ugly and possibly quite unsafe, but the owner of a killstreak needs to change allEntities = GetEntArray(); foreach( ent in allEntities ) { if ( !IsDefined( ent ) || IsPlayer( ent ) )//don't transfer ownership of clients, this is just for owned objects in the world, not leadership transfer continue; if ( IsDefined( ent.owner ) && ent.owner == from ) { if ( ent.classname == "misc_turret" ) ent maps\mp\killstreaks\_autosentry::sentry_setOwner( self ); else ent.owner = self; } } } self.adrenaline = undefined; self setAdrenaline( from.adrenaline ); self resetStreakCount(); self updateStreakCount(); if ( IsDefined( noTransfer ) && noTransfer == true && IsDefined( self.killstreaks ) ) {//just copying the info, need to update our HUD //update the icons index = 1; foreach ( streakName in self.killstreaks ) { killstreakIndexName = self.pers["killstreaks"][index].streakName; // if specialist then we need to check to see if they have the pro version of the perk and get that icon if( self.streakType == "specialist" ) { perkTokens = StrTok( self.pers["killstreaks"][index].streakName, "_" ); if( perkTokens[ perkTokens.size - 1 ] == "ks" ) { perkName = undefined; foreach( token in perkTokens ) { if( token != "ks" ) { if( !IsDefined( perkName ) ) perkName = token; else perkName += ( "_" + token ); } } // blastshield has an _ at the beginning if( isStrStart( self.pers["killstreaks"][index].streakName, "_" ) ) perkName = "_" + perkName; if( IsDefined( perkName ) && self maps\mp\gametypes\_class::getPerkUpgrade( perkName ) != "specialty_null" ) killstreakIndexName = self.pers["killstreaks"][index].streakName + "_pro"; } } self setCommonPlayerData( "killstreaksState", "icons", index, getKillstreakIndex( killstreakIndexName ) ); index++; } } self updateStreakSlots(); //copy over perks foreach( perkName in from.perksPerkName ) { //FIXME: this will restart a perk's threads - timer, etc. So all timed perks get reset to full length if ( !self _hasPerk( perkName ) ) { useSlot = false; if ( IsDefined( self.perksUseSlot[ perkName ] ) ) useSlot = self.perksUseSlot[ perkName ]; self givePerk( perkName, useSlot ); } if ( !IsDefined( noTransfer ) || noTransfer == false ) {//stop & remove perks from other guy from _unsetPerk( perkName ); } } } copy_adrenaline( from ) { self.adrenaline = undefined; self setAdrenaline( from.adrenaline ); self resetStreakCount(); self updateStreakCount(); self updateStreakSlots(); } is_using_killstreak() { curWeap = self GetCurrentWeapon(); usingKS = IsSubStr( curWeap, "killstreak" ) || (IsDefined(self.selectingLocation) && self.selectingLocation == true) || !(self isWeaponEnabled()) && !(self maps\mp\gametypes\_damage::attackerInRemoteKillstreak()); return usingKS; } monitorDisownKillstreaks() { while(IsDefined(self)) { if ( bot_is_fireteam_mode() ) { self waittill( "disconnect" ); } else { self waittill_any( "disconnect", "joined_team", "joined_spectators" ); } self notify( "killstreak_disowned" ); } } PROJECTILE_TRACE_OBSTRUCTED_THRESHOLD = 0.99; // this allows us to reject cases where we only see a tiny part of the target point PROJECTILE_TRACE_YAW_ANGLE_INCREMENT = 30; // should I allow user to specify flight distance or height? /* ============= ///ScriptDocBegin "Name: findUnobstructedFiringPointAroundZ( , , , )" "Summary: Find a suitible flight path for a projectile around the +Z axis. Starts from behind the player and sweeps around in a circle in 30 degree increments. Returns undefined if all paths are blocked. Useful for tall narrow spaces." "Module: Killstreaks" "MandatoryArg: : The player whose POV we'll use as a reference" "MandatoryArg: : The position to aim at." "MandatoryArg: : # of units for the projectile to travel. Very large values may collide with the skybox." "MandatoryArg: : The angle from +Z axis" "Example: findUnobstructedFiringPointAroundZ( player, designatorEntity.origin, 10000, 30 ) "SPMP: multiplayer" ///ScriptDocEnd ============= */ findUnobstructedFiringPointAroundZ( player, targetPosition, flightDistance, angleOfAttack ) // self == player { initialVector = RotateVector( (0, 0, 1), (-1 * angleOfAttack, 0, 0) ); anglesToPlayer = VectorToAngles( targetPosition - player.origin ); for ( deltaAngle = 0; deltaAngle < 360; deltaAngle += PROJECTILE_TRACE_YAW_ANGLE_INCREMENT ) { // want to start from behind the target approachVector = flightDistance * RotateVector( initialVector, (0, deltaAngle + anglesToPlayer[1], 0 ) ); startPosition = targetPosition + approachVector; // if ( deltaAngle == 0 ) // player thread drawLine( startPosition, targetPosition, 20, (0, 0, 1) ); if ( _findUnobstructedFiringPointHelper( player, startPosition, targetPosition ) ) { return startPosition; } } return undefined; } /* ============= ///ScriptDocBegin "Name: findUnobstructedFiringPointAroundY( , , , , , )" "Summary: Find a suitible flight path for a projectile behind the player. Starts high and lowers angle of attack. Useful for getting into doorways and windows. Returns undefined if all paths blocked. "Module: Killstreaks" "MandatoryArg: : The player whose POV we'll use as a reference" "MandatoryArg: : The position to aim at." "MandatoryArg: : # of units for the projectile to travel. Very large values may collide with the skybox." "MandatoryArg: : shallowest pitch angle (0 = parallel to ground) "MandatoryArg: : steepest ptich angle (90 = straight up) "MandatoryArg: : # of degrees to step from max to min pitch "Example: findUnobstructedFiringPointAroundZ( player, designatorEntity.origin, 10000, 15, 75, 15 ) "SPMP: multiplayer" ///ScriptDocEnd ============= */ findUnobstructedFiringPointAroundY( player, targetPosition, flightDistance, minPitch, maxPitch, angleStep ) { anglesToPlayer = VectorToAngles( player.origin - targetPosition ); for ( deltaAngle = minPitch; deltaAngle <= maxPitch; deltaAngle += angleStep ) { // since we're starting with the front vector, keep pitching up (so the firing angle becomes shallower) initialVector = RotateVector( (1, 0, 0), ( deltaAngle - 90, 0, 0 ) ); // not sure why, but can't yaw the vector towards the player before pitching // so we have to do two rotates per check approachVector = flightDistance * RotateVector( initialVector, (0, anglesToPlayer[1], 0) ); startPosition = targetPosition + approachVector; //if ( deltaAngle == minPitch ) // player thread drawLine( startPosition, targetPosition, 20, (0, 0, 1) ); if ( _findUnobstructedFiringPointHelper( player, startPosition, targetPosition ) ) { return startPosition; } } return undefined; } _findUnobstructedFiringPointHelper( player, startPosition, targetPosition ) { traceResult = BulletTrace( startPosition, targetPosition, false ); if ( traceResult[ "fraction" ] > PROJECTILE_TRACE_OBSTRUCTED_THRESHOLD ) { //player thread drawLine( startPosition, targetPosition, 20, (0, 1, 0) ); // player thread drawSphere( traceResult[ "position" ], 3, (0, 1, 0) ); return true; } /* else { player thread drawLine( startPosition, targetPosition, 20, (1, 0, 0) ); } */ return false; } /* ============= ///ScriptDocBegin "Name: findUnobstructedFiringPoint( , , )" "Summary: Find a suitible flight path for a projectile to hit the target point. Will try to find a firing point high and behind the player." "Module: Killstreaks" "MandatoryArg: : The player whose POV we'll use as a reference" "MandatoryArg: : The position to aim at." "MandatoryArg: : # of units for the projectile to travel. Very large values may collide with the skybox." "Example: findUnobstructedFiringPointAroundZ( player, designatorEntity.origin, 10000 ) "SPMP: multiplayer" ///ScriptDocEnd ============= */ findUnobstructedFiringPoint( player, targetPosition, flightDistance ) { result = findUnobstructedFiringPointAroundZ( player, targetPosition, flightDistance, 30 ); if ( !IsDefined( result ) ) { result = findUnobstructedFiringPointAroundY( player, targetPosition, flightDistance, 15, 75, 15 ); } return result; } isAirdropMarker( weaponName ) { switch ( weaponName ) { case "airdrop_marker_mp": case "airdrop_marker_assault_mp": case "airdrop_marker_support_mp": case "airdrop_mega_marker_mp": case "airdrop_sentry_marker_mp": case "airdrop_juggernaut_mp": case "airdrop_juggernaut_def_mp": case "airdrop_juggernaut_maniac_mp": case "airdrop_tank_marker_mp": case "airdrop_escort_marker_mp": return true; default: return false; } } isUsingHeliSniper() { return ( IsDefined( self.OnHeliSniper ) && self.OnHeliSniper ); } destroyTargetArray( attacker, victimTeam, weaponName, targetList ) { meansOfDeath = "MOD_EXPLOSIVE"; damage = 5000; direction_vec = ( 0, 0, 0 ); point = ( 0, 0, 0 ); modelName = ""; tagName = ""; partName = ""; iDFlags = undefined; if ( level.teamBased ) { foreach ( target in targetList ) { if ( isValidTeamTarget( attacker, victimTeam, target ) ) { target notify( "damage", damage, attacker, direction_vec, point, meansOfDeath, modelName, tagName, partName, iDFlags, weaponName ); wait( 0.05 ); } } } else { foreach ( target in targetList ) { if ( isValidFFATarget( attacker, victimTeam, target ) ) { target notify( "damage", damage, attacker, direction_vec, point, meansOfDeath, modelName, tagName, partName, iDFlags, weaponName ); wait( 0.05 ); } } } }