diff --git a/iw4x/iw4x_00/images/dogtags_col.iwi b/iw4x/iw4x_00/images/dogtags_col.iwi new file mode 100644 index 0000000..106ed24 Binary files /dev/null and b/iw4x/iw4x_00/images/dogtags_col.iwi differ diff --git a/iw4x/iw4x_00/images/dogtags_nml.iwi b/iw4x/iw4x_00/images/dogtags_nml.iwi new file mode 100644 index 0000000..c13944f Binary files /dev/null and b/iw4x/iw4x_00/images/dogtags_nml.iwi differ diff --git a/iw4x/iw4x_00/images/waypoint_dogtags.iwi b/iw4x/iw4x_00/images/waypoint_dogtags.iwi index decda32..4b57ddf 100644 Binary files a/iw4x/iw4x_00/images/waypoint_dogtags.iwi and b/iw4x/iw4x_00/images/waypoint_dogtags.iwi differ diff --git a/iw4x/iw4x_00/images/~dogtags_spc-rgb&dogtags_cos-l-67.iwi b/iw4x/iw4x_00/images/~dogtags_spc-rgb&dogtags_cos-l-67.iwi new file mode 100644 index 0000000..47dcae8 Binary files /dev/null and b/iw4x/iw4x_00/images/~dogtags_spc-rgb&dogtags_cos-l-67.iwi differ diff --git a/iw4x/iw4x_00/localizedstrings/iw4x_english.str b/iw4x/iw4x_00/localizedstrings/iw4x_english.str index dbac326..3c36bb1 100644 --- a/iw4x/iw4x_00/localizedstrings/iw4x_english.str +++ b/iw4x/iw4x_00/localizedstrings/iw4x_english.str @@ -13,8 +13,23 @@ LANG_ENGLISH "Gun Game" REFERENCE MPUI_GUNGAME_CAPS LANG_ENGLISH "GUN GAME" -REFERENCE MENU_GUNGAME_DESC -LANG_ENGLISH "Be the first to dominate with every gun." +REFERENCE MENU_GUNGAME +LANG_ENGLISH "Be the first player to score a kill with each one of the provided weapons." + +REFERENCE SPLASHES_DROPPED_ENEMY_GUN_RANK +LANG_ENGLISH "Dropped Enemy Gun Rank!" + +REFERENCE SPLASHES_DROPPED_GUN_RANK +LANG_ENGLISH "Dropped Gun Rank!" + +REFERENCE SPLASHES_GAINED_GUN_RANK +LANG_ENGLISH "Gained Gun Rank!" + +REFERENCE SPLASHES_TOP_GUN_RANK +LANG_ENGLISH "Top Gun Rank!" + +REFERENCE MP_WEAPON +LANG_ENGLISH "Weapon:" REFERENCE MPUI_SHARPSHOOTER LANG_ENGLISH "Sharpshooter" @@ -40,11 +55,32 @@ LANG_ENGLISH "Kill Confirmed" REFERENCE MPUI_CONF_CAPS LANG_ENGLISH "KILL CONFIRMED" +REFERENCE CONF_KILL_CONFIRMED +LANG_ENGLISH "KILL CONFIRMED" + REFERENCE MENU_CONF LANG_ENGLISH "Recover Dog Tags to score for your team and deny enemy scores." -REFERENCE CONF_GAME_DESC -LANG_ENGLISH "Collect Dog Tags to win." +REFERENCE OBJECTIVES_CONF +LANG_ENGLISH "Recover Dog Tags for the win." + +REFERENCE OBJECTIVES_CONF_SCORE +LANG_ENGLISH "Recover Dog Tags to score for your team and deny enemy score. First team to &&1 wins." + +REFERENCE OBJECTIVES_CONF_HINT +LANG_ENGLISH "Recover Dog Tags for the win." + +REFERENCE SPLASHES_TAGS_RETRIEVED +LANG_ENGLISH "Got Your Tags!" + +REFERENCE SPLASHES_KILL_DENIED +LANG_ENGLISH "Kill Denied!" + +REFERENCE SPLASHES_DENIED_KILL +LANG_ENGLISH "Denied Kill!" + +REFERENCE SPLASHES_KILL_CONFIRMED +LANG_ENGLISH "Kill Confirmed!" REFERENCE MPUI_INF LANG_ENGLISH "Infected" @@ -55,35 +91,36 @@ LANG_ENGLISH "INFECTED" REFERENCE MENU_INF LANG_ENGLISH "Eliminated Survivors become Infected. Infect everyone, or survive the game to win." -REFERENCE INF_GAME_DESC +REFERENCE OBJECTIVES_INFECT LANG_ENGLISH "Eliminated Survivors become Infected. Infect everyone, or survive the game to win." -REFERENCE INF_WAIT_FOR_PLAYERS -LANG_ENGLISH "Waiting for more players..." +REFERENCE OBJECTIVES_INFECT_SCORE +LANG_ENGLISH "Survivors become Infected. Infect everyone, or survive the game to win." -REFERENCE INF_COUNTDOWN -LANG_ENGLISH "Infection countdown: " +REFERENCE OBJECTIVES_INFECT_HINT +LANG_ENGLISH "Watch your back." -REFERENCE INF_ALLIES_ELIM -LANG_ENGLISH "Survivors eliminated." +REFERENCE MP_DRAFT_STARTS_IN +LANG_ENGLISH "Infection countdown: &&1" -REFERENCE INF_GOT_INFECTED -LANG_ENGLISH "Infected!" - -REFERENCE INF_LAST_ALIVE -LANG_ENGLISH "Last Alive!" - -REFERENCE INF_SURVIVOR +REFERENCE SPLASHES_SURVIVOR LANG_ENGLISH "Survivor!" -REFERENCE CONF_PICKEDUP_TAGS -LANG_ENGLISH "RETREIVED TAGS" +REFERENCE SPLASHES_FIRST_MERCENARY +LANG_ENGLISH "First Infected!" -REFERENCE CONF_KILL_DENIED -LANG_ENGLISH "KILL DENIED" +REFERENCE SPLASHES_DRAFTED +LANG_ENGLISH "Infected!" + +REFERENCE SPLASHES_FINAL_ROGUE +LANG_ENGLISH "Final Survivor!" + +REFERENCE SPLASHES_GOT_DRAFTED +LANG_ENGLISH "Got Infected!" + +REFERENCE SPLASHES_DRAFTED_ROGUE +LANG_ENGLISH "Got Infected!" -REFERENCE CONF_KILL_CONFIRMED -LANG_ENGLISH "KILL CONFIRMED" REFERENCE MPUI_CRANKED LANG_ENGLISH "Cranked" diff --git a/iw4x/iw4x_00/maps/mp/_utility.gsc b/iw4x/iw4x_00/maps/mp/_utility.gsc new file mode 100644 index 0000000..82d61b6 --- /dev/null +++ b/iw4x/iw4x_00/maps/mp/_utility.gsc @@ -0,0 +1,2755 @@ +#include common_scripts\utility; +#include maps\mp\gametypes\_hud_util; + +exploder_sound() +{ + if ( isdefined( self.script_delay ) ) + wait self.script_delay; + + self playSound( level.scr_sound[ self.script_sound ] ); +} + +/* +saveModel() +{ + info["model"] = self.model; + info["viewmodel"] = self getViewModel(); + attachSize = self getAttachSize(); + info["attach"] = []; + + assert(info["viewmodel"] != ""); // No viewmodel was associated with the player's model + + for(i = 0; i < attachSize; i++) + { + info["attach"][i]["model"] = self getAttachModelName(i); + info["attach"][i]["tag"] = self getAttachTagName(i); + info["attach"][i]["ignoreCollision"] = self getAttachIgnoreCollision(i); + } + + return info; +} + +loadModel(info) +{ + self detachAll(); + self setModel(info["model"]); + self setViewModel(info["viewmodel"]); + + attachInfo = info["attach"]; + attachSize = attachInfo.size; + + for(i = 0; i < attachSize; i++) + self attach(attachInfo[i]["model"], attachInfo[i]["tag"], attachInfo[i]["ignoreCollision"]); +} +*/ + +/* +============= +///ScriptDocBegin +"Name: delayThread( , , , , , )" +"Summary: Delaythread is cool! It saves you from having to write extra script for once off commands. Note you don’t have to thread it off. Delaythread is that smart!" +"Module: Utility" +"MandatoryArg: : The delay before the function occurs" +"MandatoryArg: : The function to run." +"OptionalArg: : parameter 1 to pass to the process" +"OptionalArg: : parameter 2 to pass to the process" +"OptionalArg: : parameter 3 to pass to the process" +"OptionalArg: : parameter 4 to pass to the process" +"OptionalArg: : parameter 5 to pass to the process" +"Example: delayThread( 3, ::flag_set, "player_can_rappel" ); +"SPMP: both" +///ScriptDocEnd +============= +*/ +delayThread( timer, func, param1, param2, param3, param4, param5 ) +{ + // to thread it off + thread delayThread_proc( func, timer, param1, param2, param3, param4, param5 ); +} + + +delayThread_proc( func, timer, param1, param2, param3, param4, param5 ) +{ + wait( timer ); + if ( !IsDefined( param1 ) ) + { + assertex( !isdefined( param2 ), "Delaythread does not support vars after undefined." ); + assertex( !isdefined( param3 ), "Delaythread does not support vars after undefined." ); + assertex( !isdefined( param4 ), "Delaythread does not support vars after undefined." ); + assertex( !isdefined( param5 ), "Delaythread does not support vars after undefined." ); + thread [[ func ]](); + } + else + if ( !IsDefined( param2 ) ) + { + assertex( !isdefined( param3 ), "Delaythread does not support vars after undefined." ); + assertex( !isdefined( param4 ), "Delaythread does not support vars after undefined." ); + assertex( !isdefined( param5 ), "Delaythread does not support vars after undefined." ); + thread [[ func ]]( param1 ); + } + else + if ( !IsDefined( param3 ) ) + { + assertex( !isdefined( param4 ), "Delaythread does not support vars after undefined." ); + assertex( !isdefined( param5 ), "Delaythread does not support vars after undefined." ); + thread [[ func ]]( param1, param2 ); + } + else + if ( !IsDefined( param4 ) ) + { + assertex( !isdefined( param5 ), "Delaythread does not support vars after undefined." ); + thread [[ func ]]( param1, param2, param3 ); + } + else + if ( !IsDefined( param5 ) ) + { + thread [[ func ]]( param1, param2, param3, param4 ); + } + else + { + thread [[ func ]]( param1, param2, param3, param4, param5 ); + } +} + +getPlant() +{ + start = self.origin + ( 0, 0, 10 ); + + range = 11; + forward = anglesToForward( self.angles ); + forward = vector_multiply( forward, range ); + + traceorigins[ 0 ] = start + forward; + traceorigins[ 1 ] = start; + + trace = bulletTrace( traceorigins[ 0 ], ( traceorigins[ 0 ] + ( 0, 0, -18 ) ), false, undefined ); + if ( trace[ "fraction" ] < 1 ) + { + //println("^6Using traceorigins[0], tracefraction is", trace["fraction"]); + + temp = spawnstruct(); + temp.origin = trace[ "position" ]; + temp.angles = orientToNormal( trace[ "normal" ] ); + return temp; + } + + trace = bulletTrace( traceorigins[ 1 ], ( traceorigins[ 1 ] + ( 0, 0, -18 ) ), false, undefined ); + if ( trace[ "fraction" ] < 1 ) + { + //println("^6Using traceorigins[1], tracefraction is", trace["fraction"]); + + temp = spawnstruct(); + temp.origin = trace[ "position" ]; + temp.angles = orientToNormal( trace[ "normal" ] ); + return temp; + } + + traceorigins[ 2 ] = start + ( 16, 16, 0 ); + traceorigins[ 3 ] = start + ( 16, -16, 0 ); + traceorigins[ 4 ] = start + ( -16, -16, 0 ); + traceorigins[ 5 ] = start + ( -16, 16, 0 ); + + besttracefraction = undefined; + besttraceposition = undefined; + for ( i = 0; i < traceorigins.size; i++ ) + { + trace = bulletTrace( traceorigins[ i ], ( traceorigins[ i ] + ( 0, 0, -1000 ) ), false, undefined ); + + //ent[i] = spawn("script_model",(traceorigins[i]+(0, 0, -2))); + //ent[i].angles = (0, 180, 180); + //ent[i] setmodel("105"); + + //println("^6trace ", i ," fraction is ", trace["fraction"]); + + if ( !isdefined( besttracefraction ) || ( trace[ "fraction" ] < besttracefraction ) ) + { + besttracefraction = trace[ "fraction" ]; + besttraceposition = trace[ "position" ]; + + //println("^6besttracefraction set to ", besttracefraction, " which is traceorigin[", i, "]"); + } + } + + if ( besttracefraction == 1 ) + besttraceposition = self.origin; + + temp = spawnstruct(); + temp.origin = besttraceposition; + temp.angles = orientToNormal( trace[ "normal" ] ); + return temp; +} + +orientToNormal( normal ) +{ + hor_normal = ( normal[ 0 ], normal[ 1 ], 0 ); + hor_length = length( hor_normal ); + + if ( !hor_length ) + return( 0, 0, 0 ); + + hor_dir = vectornormalize( hor_normal ); + neg_height = normal[ 2 ] * - 1; + tangent = ( hor_dir[ 0 ] * neg_height, hor_dir[ 1 ] * neg_height, hor_length ); + plant_angle = vectortoangles( tangent ); + + //println("^6hor_normal is ", hor_normal); + //println("^6hor_length is ", hor_length); + //println("^6hor_dir is ", hor_dir); + //println("^6neg_height is ", neg_height); + //println("^6tangent is ", tangent); + //println("^6plant_angle is ", plant_angle); + + return plant_angle; +} + +deletePlacedEntity( entity ) +{ + entities = getentarray( entity, "classname" ); + for ( i = 0; i < entities.size; i++ ) + { + //println("DELETED: ", entities[i].classname); + entities[ i ] delete(); + } +} + +playSoundOnPlayers( sound, team, excludeList ) +{ + assert( isdefined( level.players ) ); + + if ( level.splitscreen ) + { + if ( isdefined( level.players[ 0 ] ) ) + level.players[ 0 ] playLocalSound( sound ); + } + else + { + if ( isDefined( team ) ) + { + if ( isdefined( excludeList ) ) + { + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[ i ]; + if ( isdefined( player.pers[ "team" ] ) && ( player.pers[ "team" ] == team ) && !isExcluded( player, excludeList ) ) + player playLocalSound( sound ); + } + } + else + { + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[ i ]; + if ( isdefined( player.pers[ "team" ] ) && ( player.pers[ "team" ] == team ) ) + player playLocalSound( sound ); + } + } + } + else + { + if ( isdefined( excludeList ) ) + { + for ( i = 0; i < level.players.size; i++ ) + { + if ( !isExcluded( level.players[ i ], excludeList ) ) + level.players[ i ] playLocalSound( sound ); + } + } + else + { + for ( i = 0; i < level.players.size; i++ ) + level.players[ i ] playLocalSound( sound ); + } + } + } +} + + +sortLowerMessages() +{ + for ( i = 1; i < self.lowerMessages.size; i++ ) + { + message = self.lowerMessages[ i ]; + priority = message.priority; + for ( j = i - 1; j >= 0 && priority > self.lowerMessages[ j ].priority; j -- ) + self.lowerMessages[ j + 1 ] = self.lowerMessages[ j ]; + self.lowerMessages[ j + 1 ] = message; + } +} + + +addLowerMessage( name, text, time, priority ) +{ + newMessage = undefined; + foreach ( message in self.lowerMessages ) + { + if ( message.name == name ) + { + if ( message.text == text && message.priority == priority ) + return; + + newMessage = message; + break; + } + } + + if ( !isDefined( newMessage ) ) + { + newMessage = spawnStruct(); + self.lowerMessages[ self.lowerMessages.size ] = newMessage; + } + + newMessage.name = name; + newMessage.text = text; + newMessage.time = time; + newMessage.addTime = getTime(); + newMessage.priority = priority; + + sortLowerMessages(); +} + + +removeLowerMessage( name ) +{ + for ( i = 0; i < self.lowerMessages.size; i++ ) + { + if ( self.lowerMessages[ i ].name != name ) + continue; + + message = self.lowerMessages[ i ]; + if ( i < self.lowerMessages.size - 1 ) + self.lowerMessages[ i ] = self.lowerMessages[ self.lowerMessages.size - 1 ]; + + self.lowerMessages[ self.lowerMessages.size - 1 ] = undefined; + } + + sortLowerMessages(); +} + + +getLowerMessage() +{ + return self.lowerMessages[ 0 ]; +} + + +setLowerMessage( name, text, time, priority ) +{ + if ( !isDefined( priority ) ) + priority = 1; + + if ( !isDefined( time ) ) + time = 0; + + self addLowerMessage( name, text, time, priority ); + self updateLowerMessage(); + //self notify( "lower_message_set" ); +} + + +updateLowerMessage() +{ + message = self getLowerMessage(); + + if ( !isDefined( message ) ) + { + self.lowerMessage.alpha = 0; + self.lowerTimer.alpha = 0; + return; + } + + self.lowerMessage setText( message.text ); + if ( isDefined( message.time ) && message.time > 0 ) + self.lowerTimer setTimer( max( message.time - ( ( getTime() - message.addTime ) / 1000 ), 0.1 ) ); + else + self.lowerTimer setText( "" ); + + self.lowerMessage.alpha = 0.85; + self.lowerTimer.alpha = 1; +} + +clearLowerMessage( name, fadetime ) +{ + self removeLowerMessage( name ); + self updateLowerMessage(); +} + +clearLowerMessages() +{ + for ( i = 0; i < self.lowerMessages.size; i++ ) + self.lowerMessages[ i ] = undefined; + + if ( !isDefined( self.lowerMessage ) ) + return; + + self updateLowerMessage(); +} + +printOnTeam( printString, team ) +{ + foreach ( player in level.players ) + { + if ( player.team != team ) + continue; + + player iPrintLn( printString ); + } +} + +printBoldOnTeam( text, team ) +{ + assert( isdefined( level.players ) ); + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[ i ]; + if ( ( isdefined( player.pers[ "team" ] ) ) && ( player.pers[ "team" ] == team ) ) + player iprintlnbold( text ); + } +} + +printBoldOnTeamArg( text, team, arg ) +{ + assert( isdefined( level.players ) ); + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[ i ]; + if ( ( isdefined( player.pers[ "team" ] ) ) && ( player.pers[ "team" ] == team ) ) + player iprintlnbold( text, arg ); + } +} + +printOnTeamArg( text, team, arg ) +{ + assert( isdefined( level.players ) ); + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[ i ]; + if ( ( isdefined( player.pers[ "team" ] ) ) && ( player.pers[ "team" ] == team ) ) + player iprintln( text, arg ); + } +} + +printOnPlayers( text, team ) +{ + players = level.players; + for ( i = 0; i < players.size; i++ ) + { + if ( isDefined( team ) ) + { + if ( ( isdefined( players[ i ].pers[ "team" ] ) ) && ( players[ i ].pers[ "team" ] == team ) ) + players[ i ] iprintln( text ); + } + else + { + players[ i ] iprintln( text ); + } + } +} + +printAndSoundOnEveryone( team, otherteam, printFriendly, printEnemy, soundFriendly, soundEnemy, printarg ) +{ + shouldDoSounds = isDefined( soundFriendly ); + + shouldDoEnemySounds = false; + if ( isDefined( soundEnemy ) ) + { + assert( shouldDoSounds );// can't have an enemy sound without a friendly sound + shouldDoEnemySounds = true; + } + + if ( level.splitscreen || !shouldDoSounds ) + { + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[ i ]; + playerteam = player.pers[ "team" ]; + if ( isdefined( playerteam ) ) + { + if ( playerteam == team && isdefined( printFriendly ) ) + player iprintln( printFriendly, printarg ); + else if ( playerteam == otherteam && isdefined( printEnemy ) ) + player iprintln( printEnemy, printarg ); + } + } + if ( shouldDoSounds ) + { + assert( level.splitscreen ); + level.players[ 0 ] playLocalSound( soundFriendly ); + } + } + else + { + assert( shouldDoSounds ); + if ( shouldDoEnemySounds ) + { + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[ i ]; + playerteam = player.pers[ "team" ]; + if ( isdefined( playerteam ) ) + { + if ( playerteam == team ) + { + if ( isdefined( printFriendly ) ) + player iprintln( printFriendly, printarg ); + player playLocalSound( soundFriendly ); + } + else if ( playerteam == otherteam ) + { + if ( isdefined( printEnemy ) ) + player iprintln( printEnemy, printarg ); + player playLocalSound( soundEnemy ); + } + } + } + } + else + { + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[ i ]; + playerteam = player.pers[ "team" ]; + if ( isdefined( playerteam ) ) + { + if ( playerteam == team ) + { + if ( isdefined( printFriendly ) ) + player iprintln( printFriendly, printarg ); + player playLocalSound( soundFriendly ); + } + else if ( playerteam == otherteam ) + { + if ( isdefined( printEnemy ) ) + player iprintln( printEnemy, printarg ); + } + } + } + } + } +} + +printAndSoundOnTeam( team, printString, soundAlias ) +{ + foreach ( player in level.players ) + { + if ( player.team != team ) + continue; + + player printAndSoundOnPlayer( printString, soundAlias ); + } +} + +printAndSoundOnPlayer( printString, soundAlias ) +{ + self iPrintLn( printString ); + self playLocalSound( soundAlias ); +} + +_playLocalSound( soundAlias ) +{ + if ( level.splitscreen && self getEntityNumber() != 0 ) + return; + + self playLocalSound( soundAlias ); +} + +dvarIntValue( dVar, defVal, minVal, maxVal ) +{ + dVar = "scr_" + level.gameType + "_" + dVar; + if ( getDvar( dVar ) == "" ) + { + setDvar( dVar, defVal ); + return defVal; + } + + value = getDvarInt( dVar ); + + if ( value > maxVal ) + value = maxVal; + else if ( value < minVal ) + value = minVal; + else + return value; + + setDvar( dVar, value ); + return value; +} + +dvarFloatValue( dVar, defVal, minVal, maxVal ) +{ + dVar = "scr_" + level.gameType + "_" + dVar; + if ( getDvar( dVar ) == "" ) + { + setDvar( dVar, defVal ); + return defVal; + } + + value = getDvarFloat( dVar ); + + if ( value > maxVal ) + value = maxVal; + else if ( value < minVal ) + value = minVal; + else + return value; + + setDvar( dVar, value ); + return value; +} + +play_sound_on_tag( alias, tag ) +{ + if ( isdefined( tag ) ) + { + playsoundatpos( self getTagOrigin( tag ), alias ); + } + else + { + playsoundatpos( self.origin, alias ); + } +} + +getOtherTeam( team ) +{ + if ( team == "allies" ) + return "axis"; + else if ( team == "axis" ) + return "allies"; + + assertMsg( "getOtherTeam: invalid team " + team ); +} + +wait_endon( waitTime, endOnString, endonString2, endonString3 ) +{ + self endon( endOnString ); + if ( isDefined( endonString2 ) ) + self endon( endonString2 ); + if ( isDefined( endonString3 ) ) + self endon( endonString3 ); + + wait( waitTime ); +} + +isMG( weapon ) +{ + return ( isSubStr( weapon, "_bipod_" ) || weapon == "turret_minigun_mp" ); +} + +initPersStat( dataName ) +{ + if ( !isDefined( self.pers[ dataName ] ) ) + self.pers[ dataName ] = 0; +} + +getPersStat( dataName ) +{ + return self.pers[ dataName ]; +} + +incPersStat( dataName, increment ) +{ + self.pers[ dataName ] += increment; + self maps\mp\gametypes\_persistence::statAdd( dataName, increment ); +} + +setPersStat( dataName, value ) +{ + assertEx( isDefined( dataName ), "Called setPersStat with no dataName defined." ); + assertEx( isDefined( value ), "Called setPersStat for " + dataName + " with no value defined." ); + + self.pers[ dataName ] = value; +} + +initPlayerStat( ref, defaultvalue ) +{ + if ( !isDefined( self.stats["stats_" + ref ] ) ) + { + if ( !isDefined( defaultvalue ) ) + defaultvalue = 0; + + self.stats["stats_" + ref ] = spawnstruct(); + self.stats["stats_" + ref ].value = defaultvalue; + } +} + +incPlayerStat( ref, increment ) +{ + stat = self.stats["stats_" + ref ]; + stat.value += increment; +} + +setPlayerStat( ref, value ) +{ + stat = self.stats["stats_" + ref ]; + stat.value = value; + stat.time = getTime(); +} + +getPlayerStat( ref ) +{ + return self.stats["stats_" + ref ].value; +} + +getPlayerStatTime( ref ) +{ + return self.stats["stats_" + ref ].time; +} + +setPlayerStatIfGreater( ref, newvalue ) +{ + currentvalue = self getPlayerStat( ref ); + + if ( newvalue > currentvalue ) + self setPlayerStat( ref, newvalue ); +} + +setPlayerStatIfLower( ref, newvalue ) +{ + currentvalue = self getPlayerStat( ref ); + + if ( newvalue < currentvalue ) + self setPlayerStat( ref, newvalue ); +} + +updatePersRatio( ratio, num, denom ) +{ + numValue = self maps\mp\gametypes\_persistence::statGet( num ); + denomValue = self maps\mp\gametypes\_persistence::statGet( denom ); + if ( denomValue == 0 ) + denomValue = 1; + + self maps\mp\gametypes\_persistence::statSet( ratio, int( ( numValue * 1000 ) / denomValue ) ); +} + +updatePersRatioBuffered( ratio, num, denom ) +{ + numValue = self maps\mp\gametypes\_persistence::statGetBuffered( num ); + denomValue = self maps\mp\gametypes\_persistence::statGetBuffered( denom ); + if ( denomValue == 0 ) + denomValue = 1; + + self maps\mp\gametypes\_persistence::statSetBuffered( ratio, int( ( numValue * 1000 ) / denomValue ) ); +} + + +// to be used with things that are slow. +// unfortunately, it can only be used with things that aren't time critical. +WaitTillSlowProcessAllowed( allowLoop ) +{ + // wait only a few frames if necessary + // if we wait too long, we might get too many threads at once and run out of variables + // i'm trying to avoid using a loop because i don't want any extra variables + if ( level.lastSlowProcessFrame == gettime() ) + { + if ( isDefined( allowLoop ) && allowLoop ) + { + while ( level.lastSlowProcessFrame == getTime() ) + wait( 0.05 ); + } + else + { + wait .05; + if ( level.lastSlowProcessFrame == gettime() ) + { + wait .05; + if ( level.lastSlowProcessFrame == gettime() ) + { + wait .05; + if ( level.lastSlowProcessFrame == gettime() ) + { + wait .05; + } + } + } + } + } + + level.lastSlowProcessFrame = getTime(); +} + + +waitForTimeOrNotify( time, notifyname ) +{ + self endon( notifyname ); + wait time; +} + + +isExcluded( entity, entityList ) +{ + for ( index = 0; index < entityList.size; index++ ) + { + if ( entity == entityList[ index ] ) + return true; + } + return false; +} + + +leaderDialog( dialog, team, group, excludeList ) +{ + assert( isdefined( level.players ) ); + + if ( level.splitscreen ) + return; + + if ( dialog == "null" ) + return; + + if ( !isDefined( team ) ) + { + leaderDialogBothTeams( dialog, "allies", dialog, "axis", group, excludeList ); + return; + } + + if ( level.splitscreen ) + { + if ( level.players.size ) + level.players[ 0 ] leaderDialogOnPlayer( dialog, group ); + return; + } + + if ( isDefined( excludeList ) ) + { + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[ i ]; + if ( ( isDefined( player.pers[ "team" ] ) && ( player.pers[ "team" ] == team ) ) && !isExcluded( player, excludeList ) ) + player leaderDialogOnPlayer( dialog, group ); + } + } + else + { + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[ i ]; + if ( isDefined( player.pers[ "team" ] ) && ( player.pers[ "team" ] == team ) ) + player leaderDialogOnPlayer( dialog, group ); + } + } +} + + +leaderDialogBothTeams( dialog1, team1, dialog2, team2, group, excludeList ) +{ + assert( isdefined( level.players ) ); + + if ( level.splitscreen ) + return; + + if ( level.splitscreen ) + { + if ( level.players.size ) + level.players[ 0 ] leaderDialogOnPlayer( dialog1, group ); + return; + } + + if ( isDefined( excludeList ) ) + { + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[ i ]; + team = player.pers[ "team" ]; + + if ( !isDefined( team ) ) + continue; + + if ( isExcluded( player, excludeList ) ) + continue; + + if ( team == team1 ) + player leaderDialogOnPlayer( dialog1, group ); + else if ( team == team2 ) + player leaderDialogOnPlayer( dialog2, group ); + } + } + else + { + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[ i ]; + team = player.pers[ "team" ]; + + if ( !isDefined( team ) ) + continue; + + if ( team == team1 ) + player leaderDialogOnPlayer( dialog1, group ); + else if ( team == team2 ) + player leaderDialogOnPlayer( dialog2, group ); + } + } +} + + +leaderDialogOnPlayers( dialog, players, group ) +{ + foreach ( player in players ) + player leaderDialogOnPlayer( dialog, group ); +} + + +leaderDialogOnPlayer( dialog, group, groupOverride ) +{ + if ( !isDefined( groupOverride ) ) + groupOverride = false; + + team = self.pers[ "team" ]; + + if ( level.splitscreen ) + return; + + if ( !isDefined( team ) ) + return; + + if ( team != "allies" && team != "axis" ) + return; + + if ( isDefined( group ) ) + { + // ignore the message if one from the same group is already playing + if ( self.leaderDialogGroup == group ) + { + if ( groupOverride ) + { + self stopLocalSound( self.leaderDialogActive ); + self thread playLeaderDialogOnPlayer( dialog, team ); + } + + return; + } + + hadGroupDialog = isDefined( self.leaderDialogGroups[ group ] ); + + self.leaderDialogGroups[ group ] = dialog; + dialog = group; + + // exit because the "group" dialog call is already in the queue + if ( hadGroupDialog ) + return; + } + + if ( self.leaderDialogActive == "" ) + self thread playLeaderDialogOnPlayer( dialog, team ); + else + self.leaderDialogQueue[ self.leaderDialogQueue.size ] = dialog; +} + + +playLeaderDialogOnPlayer( dialog, team ) +{ + self endon( "disconnect" ); + + self notify ( "playLeaderDialogOnPlayer" ); + self endon ( "playLeaderDialogOnPlayer" ); + + if ( isDefined( self.leaderDialogGroups[ dialog ] ) ) + { + group = dialog; + dialog = self.leaderDialogGroups[ group ]; + self.leaderDialogGroups[ group ] = undefined; + self.leaderDialogGroup = group; + } + + assertEx( isDefined( game[ "dialog" ][ dialog ] ), "Dialog " + dialog + " was not defined in game[dialog] array." ); + + if ( isSubStr( game[ "dialog" ][ dialog ], "null" ) ) + return; + + self.leaderDialogActive = game[ "voice" ][ team ] + game[ "dialog" ][ dialog ]; + self playLocalSound( game[ "voice" ][ team ] + game[ "dialog" ][ dialog ] ); + + wait( 3.0 ); + self.leaderDialogLocalSound = ""; + + self.leaderDialogActive = ""; + self.leaderDialogGroup = ""; + + if ( self.leaderDialogQueue.size > 0 ) + { + nextDialog = self.leaderDialogQueue[ 0 ]; + + for ( i = 1; i < self.leaderDialogQueue.size; i++ ) + self.leaderDialogQueue[ i - 1 ] = self.leaderDialogQueue[ i ]; + self.leaderDialogQueue[ i - 1 ] = undefined; + + self thread playLeaderDialogOnPlayer( nextDialog, team ); + } +} + + +updateMainMenu() +{ + if (self.pers[ "team" ] == "spectator" ) + { + self setClientDvar("g_scriptMainMenu", game["menu_team"]); + } + else + { + self setClientDvar( "g_scriptMainMenu", game[ "menu_class_" + self.pers["team"] ] ); + } +} + + +updateObjectiveText() +{ + if ( self.pers[ "team" ] == "spectator" ) + { + self setClientDvar( "cg_objectiveText", "" ); + return; + } + + if ( getWatchedDvar( "scorelimit" ) > 0 && !isObjectiveBased() ) + { + if ( level.splitScreen ) + self setclientdvar( "cg_objectiveText", getObjectiveScoreText( self.pers[ "team" ] ) ); + else + self setclientdvar( "cg_objectiveText", getObjectiveScoreText( self.pers[ "team" ] ), getWatchedDvar( "scorelimit" ) ); + } + else + { + self setclientdvar( "cg_objectiveText", getObjectiveText( self.pers[ "team" ] ) ); + } +} + + +setObjectiveText( team, text ) +{ + game[ "strings" ][ "objective_" + team ] = text; + precacheString( text ); +} + +setObjectiveScoreText( team, text ) +{ + game[ "strings" ][ "objective_score_" + team ] = text; + precacheString( text ); +} + +setObjectiveHintText( team, text ) +{ + game[ "strings" ][ "objective_hint_" + team ] = text; + precacheString( text ); +} + +getObjectiveText( team ) +{ + return game[ "strings" ][ "objective_" + team ]; +} + +getObjectiveScoreText( team ) +{ + return game[ "strings" ][ "objective_score_" + team ]; +} + +getObjectiveHintText( team ) +{ + return game[ "strings" ][ "objective_hint_" + team ]; +} + + + +getTimePassed() +{ + if ( !isDefined( level.startTime ) ) + return 0; + + if ( level.timerStopped ) + return( level.timerPauseTime - level.startTime ) - level.discardTime; + else + return( gettime() - level.startTime ) - level.discardTime; + +} + +getSecondsPassed() +{ + return (getTimePassed() / 1000); +} + +getMinutesPassed() +{ + return (getSecondsPassed() / 60); +} + +ClearKillcamState() +{ + self.forcespectatorclient = -1; + self.killcamentity = -1; + self.archivetime = 0; + self.psoffsettime = 0; +} + +isInKillcam() +{ + return ( self.forcespectatorclient != -1 || self.killcamentity != -1 ); +} + +isValidClass( class ) +{ + return isDefined( class ) && class != ""; +} + + + +getValueInRange( value, minValue, maxValue ) +{ + if ( value > maxValue ) + return maxValue; + else if ( value < minValue ) + return minValue; + else + return value; +} + + + + +waitForTimeOrNotifies( desiredDelay ) +{ + startedWaiting = getTime(); + + waitedTime = ( getTime() - startedWaiting ) / 1000; + + if ( waitedTime < desiredDelay ) + { + wait desiredDelay - waitedTime; + return desiredDelay; + } + else + { + return waitedTime; + } +} + +closeMenus() +{ + self closepopupMenu(); + self closeInGameMenu(); +} + + +logXPGains() +{ + if ( !isDefined( self.xpGains ) ) + return; + + xpTypes = getArrayKeys( self.xpGains ); + for ( index = 0; index < xpTypes.size; index++ ) + { + gain = self.xpGains[ xpTypes[ index ] ]; + if ( !gain ) + continue; + + self logString( "xp " + xpTypes[ index ] + ": " + gain ); + } +} + + +registerRoundSwitchDvar( dvarString, defaultValue, minValue, maxValue ) +{ + registerWatchDvarInt( "roundswitch", defaultValue ); + + dvarString = ( "scr_" + dvarString + "_roundswitch" ); + + level.roundswitchDvar = dvarString; + level.roundswitchMin = minValue; + level.roundswitchMax = maxValue; + level.roundswitch = getDvarInt( dvarString, defaultValue ); + + if ( level.roundswitch < minValue ) + level.roundswitch = minValue; + else if ( level.roundswitch > maxValue ) + level.roundswitch = maxValue; +} + + +registerRoundLimitDvar( dvarString, defaultValue, minValue, maxValue ) +{ + registerWatchDvarInt( "roundlimit", defaultValue ); +} + + +registerWinLimitDvar( dvarString, defaultValue, minValue, maxValue ) +{ + registerWatchDvarInt( "winlimit", defaultValue ); +} + + +registerScoreLimitDvar( dvarString, defaultValue, minValue, maxValue ) +{ + registerWatchDvarInt( "scorelimit", defaultValue ); +} + + +registerTimeLimitDvar( dvarString, defaultValue, minValue, maxValue ) +{ + registerWatchDvarFloat( "timelimit", defaultValue ); + makeDvarServerInfo( "ui_timelimit", getTimeLimit() ); +} + +registerHalfTimeDvar( dvarString, defaultValue, minValue, maxValue) +{ + registerWatchDvarInt( "halftime", defaultValue ); + makeDvarServerInfo( "ui_halftime", getHalfTime() ); +} + +registerNumLivesDvar( dvarString, defaultValue, minValue, maxValue ) +{ + registerWatchDvarInt( "numlives", defaultValue ); +} + +setOverTimeLimitDvar( value ) +{ + makeDvarServerInfo( "overtimeTimeLimit", value ); +} + +get_damageable_player( player, playerpos ) +{ + newent = spawnstruct(); + newent.isPlayer = true; + newent.isADestructable = false; + newent.entity = player; + newent.damageCenter = playerpos; + return newent; +} + +get_damageable_sentry( sentry, sentryPos ) +{ + newent = spawnstruct(); + newent.isPlayer = false; + newent.isADestructable = false; + newent.isSentry = true; + newent.entity = sentry; + newent.damageCenter = sentryPos; + return newent; +} + +get_damageable_grenade( grenade, entpos ) +{ + newent = spawnstruct(); + newent.isPlayer = false; + newent.isADestructable = false; + newent.entity = grenade; + newent.damageCenter = entpos; + return newent; +} + +get_damageable_player_pos( player ) +{ + return player.origin + ( 0, 0, 32 ); +} + +get_damageable_grenade_pos( grenade ) +{ + return grenade.origin; +} + +// this should be a code function. +getDvarVec( dvarName ) +{ + dvarString = getDvar( dvarName ); + + if ( dvarString == "" ) + return( 0, 0, 0 ); + + dvarTokens = strTok( dvarString, " " ); + + if ( dvarTokens.size < 3 ) + return( 0, 0, 0 ); + + setDvar( "tempR", dvarTokens[ 0 ] ); + setDvar( "tempG", dvarTokens[ 1 ] ); + setDvar( "tempB", dvarTokens[ 2 ] ); + + return( ( getDvarFloat( "tempR" ), getDvarFloat( "tempG" ), getDvarFloat( "tempB" ) ) ); +} + +strip_suffix( lookupString, stripString ) +{ + if ( lookupString.size <= stripString.size ) + return lookupString; + + if ( getSubStr( lookupString, lookupString.size - stripString.size, lookupString.size ) == stripString ) + return getSubStr( lookupString, 0, lookupString.size - stripString.size ); + + return lookupString; +} + +_takeWeaponsExcept( saveWeapon ) +{ + weaponsList = self GetWeaponsListAll(); + + foreach ( weapon in weaponsList ) + { + if ( weapon == saveWeapon ) + { + continue; + } + else + { + self takeWeapon( weapon ); + } + } +} + +saveData() +{ + saveData = spawnstruct(); + + saveData.offhandClass = self getOffhandSecondaryClass(); + saveData.actionSlots = self.saved_actionSlotData; + + saveData.currentWeapon = self getCurrentWeapon(); + + weaponsList = self GetWeaponsListAll(); + saveData.weapons = []; + foreach ( weapon in weaponsList ) + { + if ( weaponInventoryType( weapon ) == "exclusive" ) + continue; + + if ( weaponInventoryType( weapon ) == "altmode" ) + continue; + + saveWeapon = spawnStruct(); + saveWeapon.name = weapon; + saveWeapon.clipAmmoR = self getWeaponAmmoClip( weapon, "right" ); + saveWeapon.clipAmmoL = self getWeaponAmmoClip( weapon, "left" ); + saveWeapon.stockAmmo = self getWeaponAmmoStock( weapon ); + /* save camo? */ + + if ( isDefined( self.throwingGrenade ) && self.throwingGrenade == weapon ) + saveWeapon.stockAmmo--; + + assert( saveWeapon.stockAmmo >= 0 ); + + saveData.weapons[saveData.weapons.size] = saveWeapon; + } + + self.script_saveData = saveData; +} + + +restoreData() +{ + saveData = self.script_saveData; + + self setOffhandSecondaryClass( saveData.offhandClass ); + + foreach ( weapon in saveData.weapons ) + { + //if ( weapon.name == self.loadoutPrimary + "_mp" ) + self _giveWeapon( weapon.name, int(tableLookup( "mp/camoTable.csv", 1, self.loadoutPrimaryCamo, 0 )) ); + //else + //self _giveWeapon( weapon.name ); + + self setWeaponAmmoClip( weapon.name, weapon.clipAmmoR, "right" ); + if ( isSubStr( weapon.name, "akimbo" ) ) + self setWeaponAmmoClip( weapon.name, weapon.clipAmmoL, "left" ); + + self setWeaponAmmoStock( weapon.name, weapon.stockAmmo ); + } + + foreach ( slotID, actionSlot in saveData.actionSlots ) + self _setActionSlot( slotID, actionSlot.type, actionSlot.item ); + + if ( self getCurrentWeapon() == "none" ) + { + weapon = saveData.currentWeapon; + + if ( weapon == "none" ) + weapon = self getLastWeapon(); + + // Can remove this when "spawn" isn't used after final stand + self setSpawnWeapon( weapon ); + self switchToWeapon( weapon ); + } +} + + +_setActionSlot( slotID, type, item ) +{ + self.saved_actionSlotData[slotID].type = type; + self.saved_actionSlotData[slotID].item = item; + + self setActionSlot( slotID, type, item ); +} + + +isFloat( value ) +{ + if ( int( value ) != value ) + return true; + + return false; +} + +registerWatchDvarInt( nameString, defaultValue ) +{ + dvarString = "scr_" + level.gameType + "_" + nameString; + + level.watchDvars[ dvarString ] = spawnStruct(); + level.watchDvars[ dvarString ].value = getDvarInt( dvarString, defaultValue ); + level.watchDvars[ dvarString ].type = "int"; + level.watchDvars[ dvarString ].notifyString = "update_" + nameString; +} + + +registerWatchDvarFloat( nameString, defaultValue ) +{ + dvarString = "scr_" + level.gameType + "_" + nameString; + + level.watchDvars[ dvarString ] = spawnStruct(); + level.watchDvars[ dvarString ].value = getDvarFloat( dvarString, defaultValue ); + level.watchDvars[ dvarString ].type = "float"; + level.watchDvars[ dvarString ].notifyString = "update_" + nameString; +} + + +registerWatchDvar( nameString, defaultValue ) +{ + dvarString = "scr_" + level.gameType + "_" + nameString; + + level.watchDvars[ dvarString ] = spawnStruct(); + level.watchDvars[ dvarString ].value = getDvar( dvarString, defaultValue ); + level.watchDvars[ dvarString ].type = "string"; + level.watchDvars[ dvarString ].notifyString = "update_" + nameString; +} + + +setOverrideWatchDvar( dvarString, value ) +{ + dvarString = "scr_" + level.gameType + "_" + dvarString; + level.overrideWatchDvars[dvarString] = value; +} + + +getWatchedDvar( dvarString ) +{ + dvarString = "scr_" + level.gameType + "_" + dvarString; + + if ( isDefined( level.overrideWatchDvars ) && isDefined( level.overrideWatchDvars[dvarString] ) ) + { + return level.overrideWatchDvars[dvarString]; + } + + return( level.watchDvars[ dvarString ].value ); +} + + +updateWatchedDvars() +{ + while ( game[ "state" ] == "playing" ) + { + watchDvars = getArrayKeys( level.watchDvars ); + + foreach ( dvarString in watchDvars ) + { + if ( level.watchDvars[ dvarString ].type == "string" ) + dvarValue = getProperty( dvarString, level.watchDvars[ dvarString ].value ); + else if ( level.watchDvars[ dvarString ].type == "float" ) + dvarValue = getFloatProperty( dvarString, level.watchDvars[ dvarString ].value ); + else + dvarValue = getIntProperty( dvarString, level.watchDvars[ dvarString ].value ); + + if ( dvarValue != level.watchDvars[ dvarString ].value ) + { + level.watchDvars[ dvarString ].value = dvarValue; + level notify( level.watchDvars[ dvarString ].notifyString, dvarValue ); + } + } + + wait( 1.0 ); + } +} + + +isRoundBased() +{ + if ( !level.teamBased ) + return false; + + if ( getWatchedDvar( "winlimit" ) != 1 && getWatchedDvar( "roundlimit" ) != 1 ) + return true; + + return false; +} + + +isLastRound() +{ + if ( !level.teamBased ) + return true; + + if ( getWatchedDvar( "roundlimit" ) > 1 && game[ "roundsPlayed" ] >= ( getWatchedDvar( "roundlimit" ) - 1 ) ) + return true; + + if ( getWatchedDvar( "winlimit" ) > 1 && game[ "roundsWon" ][ "allies" ] >= getWatchedDvar( "winlimit" ) - 1 && game[ "roundsWon" ][ "axis" ] >= getWatchedDvar( "winlimit" ) - 1 ) + return true; + + return false; +} + + +wasOnlyRound() +{ + if ( !level.teamBased ) + return true; + + if ( getWatchedDvar( "winlimit" ) == 1 && hitWinLimit() ) + return true; + + if ( getWatchedDvar( "roundlimit" ) == 1 ) + return true; + + return false; +} + + +wasLastRound() +{ + if ( level.forcedEnd ) + return true; + + if ( !level.teamBased ) + return true; + + if ( hitRoundLimit() || hitWinLimit() ) + return true; + + return false; +} + + +hitRoundLimit() +{ + if ( getWatchedDvar( "roundlimit" ) <= 0 ) + return false; + + return( game[ "roundsPlayed" ] >= getWatchedDvar( "roundlimit" ) ); +} + + +hitScoreLimit() +{ + if ( isObjectiveBased() ) + return false; + + if ( getWatchedDvar( "scorelimit" ) <= 0 ) + return false; + + if ( level.teamBased ) + { + if ( game[ "teamScores" ][ "allies" ] >= getWatchedDvar( "scorelimit" ) || game[ "teamScores" ][ "axis" ] >= getWatchedDvar( "scorelimit" ) ) + return true; + } + else + { + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[ i ]; + if ( isDefined( player.score ) && player.score >= getWatchedDvar( "scorelimit" ) ) + return true; + } + } + return false; +} + + +hitWinLimit() +{ + if ( getWatchedDvar( "winlimit" ) <= 0 ) + return false; + + if ( !level.teamBased ) + return true; + + if ( getRoundsWon( "allies" ) >= getWatchedDvar( "winlimit" ) || getRoundsWon( "axis" ) >= getWatchedDvar( "winlimit" ) ) + return true; + + return false; +} + + +getScoreLimit() +{ + if ( isRoundBased() ) + { + if ( getWatchedDvar( "roundlimit" ) ) + return ( getWatchedDvar( "roundlimit" ) ); + else + return ( getWatchedDvar( "winlimit" ) ); + } + else + { + return ( getWatchedDvar( "scorelimit" ) ); + } +} + + +getRoundsWon( team ) +{ + return game[ "roundsWon" ][ team ]; +} + + +isObjectiveBased() +{ + return level.objectiveBased; +} + + +getTimeLimit() +{ + if ( inOvertime() && ( !isDefined(game[ "inNukeOvertime" ]) || !game[ "inNukeOvertime" ] ) ) + { + timeLimit = int( getDvar( "overtimeTimeLimit" ) ); + + if ( isDefined( timeLimit ) ) + return timeLimit; + else + return 1; + } + else if ( isDefined(level.dd) && level.dd && isDefined( level.bombexploded ) && level.bombexploded > 0 ) //to handle extra time added by dd bombs + { + return ( getWatchedDvar( "timelimit" ) + ( level.bombexploded * level.ddTimeToAdd ) ); + } + else + { + return getWatchedDvar( "timelimit" ); + } +} + + +getHalfTime() +{ + if ( inOvertime() ) + return false; + else if ( isDefined( game[ "inNukeOvertime" ] ) && game[ "inNukeOvertime" ] ) + return false; + else + return getWatchedDvar( "halftime" ); +} + + +inOvertime() +{ + return ( isDefined( game["status"] ) && game["status"] == "overtime" ); +} + + +gameHasStarted() +{ + if ( level.teamBased ) + return( level.hasSpawned[ "axis" ] && level.hasSpawned[ "allies" ] ); + else + return( level.maxPlayerCount > 1 ); +} + + +getAverageOrigin( ent_array ) +{ + avg_origin = ( 0, 0, 0 ); + + if ( !ent_array.size ) + return undefined; + + foreach ( ent in ent_array ) + avg_origin += ent.origin; + + avg_x = int( avg_origin[ 0 ] / ent_array.size ); + avg_y = int( avg_origin[ 1 ] / ent_array.size ); + avg_z = int( avg_origin[ 2 ] / ent_array.size ); + + avg_origin = ( avg_x, avg_y, avg_z ); + + return avg_origin; +} + + +getLivingPlayers( team ) +{ + player_array = []; + + foreach ( player in level.players ) + { + if ( !isAlive( player ) ) + continue; + + if ( level.teambased && isdefined( team ) ) + { + if ( team == player.pers[ "team" ] ) + player_array[ player_array.size ] = player; + } + else + { + player_array[ player_array.size ] = player; + } + } + + return player_array; +} + + +setUsingRemote( remoteName ) +{ + if ( isDefined( self.carryIcon) ) + self.carryIcon.alpha = 0; + + assert( !self isUsingRemote() ); + self.usingRemote = remoteName; + + self _disableOffhandWeapons(); + self notify( "using_remote" ); +} + +getRemoteName() +{ + assert( self isUsingRemote() ); + + return self.usingRemote; +} + +freezeControlsWrapper( frozen ) +{ + if ( isDefined( level.hostMigrationTimer ) ) + { + self freezeControls( true ); + return; + } + + self freezeControls( frozen ); +} + + +clearUsingRemote() +{ + //if ( !isWeaponEnabled() ) + // self disableWeapons(); + + if ( isDefined( self.carryIcon) ) + self.carryIcon.alpha = 1; + + self.usingRemote = undefined; + self _enableOffhandWeapons(); + + curWeapon = self getCurrentWeapon(); + + if( curWeapon == "none" || isKillstreakWeapon( curWeapon ) ) + self switchToWeapon( self Getlastweapon() ); + + self freezeControlsWrapper( false ); + + self notify( "stopped_using_remote" ); +} + + +isUsingRemote() +{ + return( isDefined( self.usingRemote ) ); +} + + +queueCreate( queueName ) +{ + if ( !isDefined( level.queues ) ) + level.queues = []; + + assert( !isDefined( level.queues[ queueName ] ) ); + + level.queues[ queueName ] = []; +} + + +queueAdd( queueName, entity ) +{ + assert( isDefined( level.queues[ queueName ] ) ); + level.queues[ queueName ][ level.queues[ queueName ].size ] = entity; +} + + +queueRemoveFirst( queueName ) +{ + assert( isDefined( level.queues[ queueName ] ) ); + + first = undefined; + newQueue = []; + foreach ( element in level.queues[ queueName ] ) + { + if ( !isDefined( element ) ) + continue; + + if ( !isDefined( first ) ) + first = element; + else + newQueue[ newQueue.size ] = element; + } + + level.queues[ queueName ] = newQueue; + + return first; +} + + +_giveWeapon( weapon, variant, dualWieldOverRide ) +{ + if ( !isDefined(variant) ) + variant = 0; + + if ( isSubstr( weapon, "_akimbo" ) || isDefined(dualWieldOverRide) && dualWieldOverRide == true) + self giveWeapon(weapon, variant, true); + else + self giveWeapon(weapon, variant, false); +} + +_hasPerk( perkName ) +{ + if ( isDefined( self.perks[perkName] ) ) + return true; + + return false; +} + + +_setPerk( perkName ) +{ + self.perks[perkName] = true; + + if ( isDefined( level.perkSetFuncs[perkName] ) ) + self thread [[level.perkSetFuncs[perkName]]](); + + self setPerk( perkName, !isDefined( level.scriptPerks[perkName] ) ); +} + + +_unsetPerk( perkName ) +{ + self.perks[perkName] = undefined; + + if ( isDefined( level.perkUnsetFuncs[perkName] ) ) + self thread [[level.perkUnsetFuncs[perkName]]](); + + self unsetPerk( perkName, !isDefined( level.scriptPerks[perkName] ) ); +} + + +_clearPerks() +{ + foreach ( perkName, perkValue in self.perks ) + { + if ( isDefined( level.perkUnsetFuncs[perkName] ) ) + self [[level.perkUnsetFuncs[perkName]]](); + } + + self.perks = []; + self clearPerks(); +} + +// Quick Sort - pass it an array it will come back sorted +quickSort(array) +{ + return quickSortMid(array, 0, array.size -1 ); +} + +quickSortMid(array, start, end) +{ + i = start; + k = end; + + if (end - start >= 1) + { + pivot = array[start]; + + while (k > i) + { + while (array[i] <= pivot && i <= end && k > i) + i++; + while (array[k] > pivot && k >= start && k >= i) + k--; + if (k > i) + array = swap(array, i, k); + } + array = swap(array, start, k); + array = quickSortMid(array, start, k - 1); + array = quickSortMid(array, k + 1, end); + } + else + return array; + + return array; +} + +swap(array, index1, index2) +{ + temp = array[index1]; + array[index1] = array[index2]; + array[index2] = temp; + return array; +} + +_suicide() +{ + if ( self isUsingRemote() && !isDefined( self.fauxDead ) ) + self thread maps\mp\gametypes\_damage::PlayerKilled_internal( self, self, self, 10000, "MOD_SUICIDE", "frag_grenade_mp", (0,0,0), "none", 0, 1116, true ); + else if( !self isUsingRemote() && !isDefined( self.fauxDead ) ) + self suicide(); +} + +isReallyAlive( player ) +{ + if ( isAlive( player ) && !isDefined( player.fauxDead ) ) + return true; + + return false; +} + +playDeathSound() +{ + rand = RandomIntRange( 1,8 ); + + if ( self.team == "axis" ) + self PlaySound( "generic_death_russian_"+ rand ); + else + self PlaySound( "generic_death_american_"+ rand ); + +} + + +rankingEnabled() +{ + assert( isPlayer( self ) ); + return ( level.rankedMatch && !self.usingOnlineDataOffline ); +} + +// only true for private match +privateMatch() +{ + return ( level.onlineGame && getDvarInt( "xblive_privatematch" ) ); +} + +// only true for playlist based LIVE and PSN games +matchMakingGame() +{ + return ( level.onlineGame && !getDvarInt( "xblive_privatematch" ) ); +} + +setAltSceneObj( object, tagName, fov, forceLink ) +{ + /* + if ( !isDefined( forceLink ) ) + forceLink = false; + + if ( !getDvarInt( "scr_pipmode" ) && !forceLink ) + return; + + self endon ( "disconnect" ); + + if ( !isReallyAlive( self ) ) + return; + + if ( !forceLink && isDefined( self.altSceneObject ) ) + return; + + self notify ( "altscene" ); + + self.altSceneObject = object; + + self AlternateSceneCameraLinkTo( object, tagName, fov ); + self setClientDvar( "ui_altscene", 1 ); + + self thread endSceneOnDeath( object ); + self thread endSceneOnDeath( self ); + + self waittill ( "end_altScene" ); + + self.altSceneObject = undefined; + self AlternateSceneCameraUnlink(); + + if ( !forceLink ) + { + self setClientDvar( "ui_altscene", 2 ); + + self endon ( "altscene" ); + wait ( 2.0 ); + } + self setClientDvar( "ui_altscene", 0 ); + */ +} + + +endSceneOnDeath( object ) +{ + self endon ( "altscene" ); + + object waittill ( "death" ); + self notify ( "end_altScene" ); +} + + +getGametypeNumLives() +{ + //commented out to allow diehardhard rules to support mulitiple life gametypes + //if ( level.dieHardMode && !getWatchedDvar( "numlives" ) ) + // return 1; + //else + return getWatchedDvar( "numlives" ); +} + + +registerAdrenalineInfo( type, value ) +{ + if ( !isDefined( level.adrenalineInfo ) ) + level.adrenalineInfo = []; + + level.adrenalineInfo[type] = value; +} + + +giveAdrenaline( type ) +{ + /* + if ( self.adrenaline >= 1000 ) + return; + + assertEx( isDefined( level.adrenalineInfo[type] ), "Unknown adrenaline type: " + type ); + + printLn( "setting: " + type + " " + level.adrenalineInfo[type] ); + + self setAdrenaline( self.adrenaline + level.adrenalineInfo[type] ); + + if ( self.adrenaline == 1000 ) + { + + giveCombatHigh( "specialty_endgame" ); + } + */ +} + + +setAdrenaline( value ) +{ + self.adrenaline = min( value, 1000 ); + self setClientDvar( "ui_adrenaline", self.adrenaline ); + + if ( self.adrenaline < 1000 ) + self.combatHigh = undefined; +} + + +giveCombatHigh( combatHighName ) +{ + self.combatHigh = combatHighName; +} + + +arrayInsertion( array, item, index ) +{ + if ( array.size != 0 ) + { + for ( i = array.size; i >= index; i-- ) + { + array[i+1] = array[i]; + } + } + + array[index] = item; +} + + +getProperty( dvar, defValue ) +{ + value = defValue; + /# + setDevDvarIfUninitialized( dvar, defValue ); + #/ + + value = getDvar( dvar, defValue ); + return value; +} + + +getIntProperty( dvar, defValue ) +{ + value = defValue; + + /# + setDevDvarIfUninitialized( dvar, defValue ); + #/ + + value = getDvarInt( dvar, defValue ); + return value; +} + + +getFloatProperty( dvar, defValue ) +{ + value = defValue; + /# + setDevDvarIfUninitialized( dvar, defValue ); + #/ + + value = getDvarFloat( dvar, defValue ); + return value; +} + + + +statusMenu( duration ) +{ + self endon ( "disconnect" ); + + if ( !isDefined( self._statusMenu ) ) + self.statusMenu = false; + + if ( self.statusMenu ) + return; + + self.statusMenu = true; + + self openpopupMenu( "status_update" ); + + wait ( duration ); + + self closepopupMenu( "status_update" ); + + // debounce + wait ( 10.0 ); + + self.statusMenu = false; +} + +isChangingWeapon() +{ + return ( isDefined( self.changingWeapon ) ); +} + +isKillstreakWeapon( weapon ) +{ + if ( weapon == "none" ) + return false; + + if ( weaponInventoryType( weapon ) == "exclusive" && weapon != "destructible_car" ) + return true; + + if ( isSubStr( weapon, "killstreak" ) ) + return true; + + switch ( weapon ) + { + case "airdrop_sentry_marker_mp": + case "airdrop_mega_marker_mp": + case "airdrop_marker_mp": + case "cobra_player_minigun_mp": // Chopper Gunner + case "artillery_mp": // Precision Airstrike + case "stealth_bomb_mp": // Stealth Bomber + case "pavelow_minigun_mp": // Pave Low + case "sentry_minigun_mp": // Sentry Gun + case "harrier_20mm_mp": // Harrier Strike + case "ac130_105mm_mp": // AC130 + case "ac130_40mm_mp": // AC130 + case "ac130_25mm_mp": // AC130 + case "remotemissile_projectile_mp": // Hellfire + case "cobra_20mm_mp": // Attack Helicopter + case "nuke_mp": // Nuke + return true; + default: + return false; + } +} + + +getWeaponClass( weapon ) +{ + tokens = strTok( weapon, "_" ); + + weaponClass = tablelookup( "mp/statstable.csv", 4, tokens[0], 2 ); + + // handle special case weapons like grenades, airdrop markers, etc... + if ( weaponClass == "" ) + { + weaponName = strip_suffix( weapon, "_mp" ); + weaponClass = tablelookup( "mp/statstable.csv", 4, weaponName, 2 ); + } + + if ( isMG( weapon ) ) + weaponClass = "weapon_mg"; + else if ( isKillstreakWeapon( weapon ) ) + weaponClass = "killstreak"; + else if ( isDeathStreakWeapon( weapon ) ) + weaponClass = "deathstreak"; + else if ( weapon == "none" ) //airdrop crates + weaponClass = "other"; + else if ( weaponClass == "" ) + weaponClass = "other"; + + assertEx( weaponClass != "", "ERROR: invalid weapon class for weapon " + weapon ); + + return weaponClass; +} + +isDeathStreakWeapon( weapon ) +{ + if( weapon == "c4death_mp" || weapon == "frag_grenade_short_mp" ) + return true; + else + return false; +} + +getBaseWeaponName( weaponName ) +{ + tokens = strTok( weaponName, "_" ); + return tokens[0]; +} + +playSoundinSpace( alias, origin ) +{ + playSoundAtPos( origin, alias ); +} + +limitDecimalPlaces( value, places ) +{ + modifier = 1; + for ( i = 0; i < places; i++ ) + modifier *= 10; + + newvalue = value * modifier; + newvalue = Int( newvalue ); + newvalue = newvalue / modifier; + + return newvalue; +} + +roundDecimalPlaces( value, places, style ) +{ + if ( !isdefined( style ) ) + style = "nearest"; + + modifier = 1; + for ( i = 0; i < places; i++ ) + modifier *= 10; + + newValue = value * modifier; + + if ( style == "up" ) + roundedValue = ceil( newValue ); + else if ( style == "down" ) + roundedValue = floor( newValue ); + else + roundedValue = newvalue + 0.5; + + newvalue = Int( roundedValue ); + newvalue = newvalue / modifier; + + return newvalue; +} + +playerForClientId( clientId ) +{ + foreach ( player in level.players ) + { + if ( player.clientId == clientId ) + return player; + } + + return undefined; +} + +isRested() +{ + if ( !self rankingEnabled() ) + return false; + + return ( self getPlayerData( "restXPGoal" ) > self getPlayerData( "experience" ) ); +} + +stringToFloat( stringVal ) +{ + floatElements = strtok( stringVal, "." ); + + floatVal = int( floatElements[0] ); + if ( isDefined( floatElements[1] ) ) + { + modifier = 1; + for ( i = 0; i < floatElements[1].size; i++ ) + modifier *= 0.1; + + floatVal += int ( floatElements[1] ) * modifier; + } + + return floatVal; +} + +setSelfUsable(caller) +{ + self makeUsable(); + + foreach (player in level.players) + { + if (player != caller ) + self disablePlayerUse( player ); + else + self enablePlayerUse( player ); + } +} + +makeTeamUsable( team ) +{ + self makeUsable(); + self thread _updateTeamUsable( team ); +} + +_updateTeamUsable( team ) +{ + self endon ( "death" ); + + for ( ;; ) + { + foreach (player in level.players) + { + if ( player.team == team ) + self enablePlayerUse( player ); + else + self disablePlayerUse( player ); + } + + level waittill ( "joined_team" ); + } +} + +// More general version of makeTeamUsable() which handles FFA +makeEnemyUsable( owner ) +{ + self makeUsable(); + self thread _updateEnemyUsable( owner ); +} + +// Only used for Tactical Insertion for now +// If used for other things, handle owner disappearing or changing team +_updateEnemyUsable( owner ) +{ + // check what happens if the owner leaves + + self endon ( "death" ); + + team = owner.team; + + for ( ;; ) + { + if ( level.teambased ) + { + foreach (player in level.players) + { + if ( player.team != team ) + self enablePlayerUse( player ); + else + self disablePlayerUse( player ); + } + } + else + { + foreach (player in level.players) + { + if ( player != owner ) + self enablePlayerUse( player ); + else + self disablePlayerUse( player ); + } + } + + level waittill ( "joined_team" ); + } +} + +getNextLifeId() +{ + lifeId = getMatchData( "lifeCount" ); + if ( lifeId < level.MaxLives ) + setMatchData( "lifeCount", lifeId+1 ); + + return ( lifeId ); +} + +initGameFlags() +{ + if ( !isDefined( game["flags"] ) ) + game["flags"] = []; +} + +gameFlagInit( flagName, isEnabled ) +{ + assert( isDefined( game["flags"] ) ); + game["flags"][flagName] = isEnabled; +} + +gameFlag( flagName ) +{ + assertEx( isDefined( game["flags"][flagName] ), "gameFlag " + flagName + " referenced without being initialized; usegameFlagInit( , )" ); + return ( game["flags"][flagName] ); +} + +gameFlagSet( flagName ) +{ + assertEx( isDefined( game["flags"][flagName] ), "gameFlag " + flagName + " referenced without being initialized; usegameFlagInit( , )" ); + game["flags"][flagName] = true; + + level notify ( flagName ); +} + +gameFlagClear( flagName ) +{ + assertEx( isDefined( game["flags"][flagName] ), "gameFlag " + flagName + " referenced without being initialized; usegameFlagInit( , )" ); + game["flags"][flagName] = false; +} + +gameFlagWait( flagName ) +{ + assertEx( isDefined( game["flags"][flagName] ), "gameFlag " + flagName + " referenced without being initialized; usegameFlagInit( , )" ); + while ( !gameFlag( flagName ) ) + level waittill ( flagName ); +} + +// including grenade launcher, grenade, RPG, C4, claymore +isExplosiveDamage( meansofdeath ) +{ + explosivedamage = "MOD_GRENADE MOD_GRENADE_SPLASH MOD_PROJECTILE MOD_PROJECTILE_SPLASH MOD_EXPLOSIVE mod_explosive"; + if( isSubstr( explosivedamage, meansofdeath ) ) + return true; + return false; +} + +// if primary weapon damage +isPrimaryDamage( meansofdeath ) +{ + // including pistols as well since sometimes they share ammo + if( meansofdeath == "MOD_RIFLE_BULLET" || meansofdeath == "MOD_PISTOL_BULLET" || meansofdeath == "MOD_EXPLOSIVE_BULLET" ) + return true; + return false; +} + + +initLevelFlags() +{ + if ( !isDefined( level.levelFlags ) ) + level.levelFlags = []; +} + +levelFlagInit( flagName, isEnabled ) +{ + assert( isDefined( level.levelFlags ) ); + level.levelFlags[flagName] = isEnabled; +} + +levelFlag( flagName ) +{ + assertEx( isDefined( level.levelFlags[flagName] ), "levelFlag " + flagName + " referenced without being initialized; use levelFlagInit( , )" ); + return ( level.levelFlags[flagName] ); +} + +levelFlagSet( flagName ) +{ + assertEx( isDefined( level.levelFlags[flagName] ), "levelFlag " + flagName + " referenced without being initialized; use levelFlagInit( , )" ); + level.levelFlags[flagName] = true; + + level notify ( flagName ); +} + +levelFlagClear( flagName ) +{ + assertEx( isDefined( level.levelFlags[flagName] ), "levelFlag " + flagName + " referenced without being initialized; use levelFlagInit( , )" ); + level.levelFlags[flagName] = false; + + level notify ( flagName ); +} + +levelFlagWait( flagName ) +{ + assertEx( isDefined( level.levelFlags[flagName] ), "levelFlag " + flagName + " referenced without being initialized; use levelFlagInit( , )" ); + while ( !levelFlag( flagName ) ) + level waittill ( flagName ); +} + +levelFlagWaitOpen( flagName ) +{ + assertEx( isDefined( level.levelFlags[flagName] ), "levelFlag " + flagName + " referenced without being initialized; use levelFlagInit( , )" ); + while ( levelFlag( flagName ) ) + level waittill ( flagName ); +} + +getWeaponAttachments( weapon ) +{ + tokenizedWeapon = strTok( weapon, "_" ); + attachmentArray = []; + + if( tokenizedWeapon.size < 3 || tokenizedWeapon[1] == "_mp" ) + { + attachmentArray[0] = "none"; + } + else if( tokenizedWeapon.size > 3 ) + { + attachmentArray[0] = tokenizedWeapon[1]; + attachmentArray[1] = tokenizedWeapon[2]; + } + else + { + attachmentArray[0] = tokenizedWeapon[1]; + } + + return attachmentArray; +} + +isEMPed() +{ + if ( self.team == "spectator" ) + return false; + + if ( level.teamBased ) + return ( level.teamEMPed[self.team] ); + else + return ( isDefined( level.empPlayer ) && level.empPlayer != self ); +} + +isNuked() +{ + if ( self.team == "spectator" ) + return false; + + return ( isDefined( self.nuked ) ); +} + +isBulletDamage( meansofdeath ) +{ + bulletDamage = "MOD_RIFLE_BULLET MOD_PISTOL_BULLET MOD_HEADSHOT"; + if( isSubstr( bulletDamage, meansofdeath ) ) + return true; + return false; +} + + +getPlayerForGuid( guid ) +{ + foreach ( player in level.players ) + { + if ( player.guid == guid ) + return player; + } + + return undefined; +} + +teamPlayerCardSplash( splash, owner, team ) +{ + if ( level.hardCoreMode ) + return; + + foreach ( player in level.players ) + { + if ( isDefined( team ) && player.team != team ) + continue; + + player thread maps\mp\gametypes\_hud_message::playerCardSplashNotify( splash, owner ); + } +} + + +isCACPrimaryWeapon( weapName ) +{ + switch ( getWeaponClass( weapName ) ) + { + case "weapon_smg": + case "weapon_assault": + case "weapon_riot": + case "weapon_sniper": + case "weapon_lmg": + return true; + default: + return false; + } +} + + +isCACSecondaryWeapon( weapName ) +{ + switch ( getWeaponClass( weapName ) ) + { + case "weapon_projectile": + case "weapon_pistol": + case "weapon_machine_pistol": + case "weapon_shotgun": + return true; + default: + return false; + } +} + + +getLastLivingPlayer( team ) +{ + livePlayer = undefined; + + foreach ( player in level.players ) + { + if ( isDefined( team ) && player.team != team ) + continue; + + if ( !isReallyAlive( player ) && !player maps\mp\gametypes\_playerlogic::maySpawn() ) + continue; + + assertEx( !isDefined( livePlayer ), "getLastLivingPlayer() found more than one live player on team." ); + + livePlayer = player; + } + + return livePlayer; +} + + +getPotentialLivingPlayers() +{ + livePlayers = []; + + foreach ( player in level.players ) + { + if ( !isReallyAlive( player ) && !player maps\mp\gametypes\_playerlogic::maySpawn() ) + continue; + + livePlayers[livePlayers.size] = player; + } + + return livePlayers; +} + + +waitTillRecoveredHealth( time, interval ) +{ + self endon("death"); + self endon("disconnect"); + + fullHealthTime = 0; + + if( !isDefined( interval ) ) + interval = .05; + + if( !isDefined( time ) ) + time = 0; + + while(1) + { + if ( self.health != self.maxhealth ) + fullHealthTime = 0; + else + fullHealthTime += interval; + + wait interval; + + if ( self.health == self.maxhealth && fullHealthTime >= time ) + break; + } + + return; +} + +_objective_delete( objID ) +{ + objective_delete( objID); + + if ( !isDefined( level.reclaimedReservedObjectives ) ) + { + level.reclaimedReservedObjectives = []; + level.reclaimedReservedObjectives[0] = objID; + } + else + { + level.reclaimedReservedObjectives[ level.reclaimedReservedObjectives.size ] = objID; + } +} + + +touchingBadTrigger() +{ + killTriggers = getEntArray( "trigger_hurt", "classname" ); + foreach ( trigger in killTriggers ) + { + if ( self isTouching( trigger ) ) + return true; + } + + radTriggers = getEntArray( "radiation", "targetname" ); + foreach ( trigger in radTriggers ) + { + if ( self isTouching( trigger ) ) + return true; + } + + return false; +} + +setThirdPersonDOF( isEnabled ) +{ + if ( isEnabled ) + self setDepthOfField( 0, 110, 512, 4096, 6, 1.8 ); + else + self setDepthOfField( 0, 0, 512, 512, 4, 0 ); +} + + + +killTrigger( pos, radius, height ) +{ + trig = spawn( "trigger_radius", pos, 0, radius, height ); + + /# + if ( getdvar( "scr_killtriggerdebug" ) == "1" ) + thread killTriggerDebug( pos, radius, height ); + #/ + + for ( ;; ) + { + /# + if ( getdvar( "scr_killtriggerradius" ) != "" ) + radius = int(getdvar( "scr_killtriggerradius" )); + #/ + + trig waittill( "trigger", player ); + + if ( !isPlayer( player ) ) + continue; + + player suicide(); + } +} + +/# +killTriggerDebug( pos, radius, height ) +{ + for ( ;; ) + { + for ( i = 0; i < 20; i++ ) + { + angle = i / 20 * 360; + nextangle = (i+1) / 20 * 360; + + linepos = pos + (cos(angle) * radius, sin(angle) * radius, 0); + nextlinepos = pos + (cos(nextangle) * radius, sin(nextangle) * radius, 0); + + line( linepos, nextlinepos ); + line( linepos + (0,0,height), nextlinepos + (0,0,height) ); + line( linepos, linepos + (0,0,height) ); + } + wait .05; + } +} +#/ + +allowTeamChoice() +{ + allowed = int( tableLookup( "mp/gametypesTable.csv", 0, level.gameType, 4 ) ); + assert( isDefined( allowed ) ); + + return allowed; +} + +allowClassChoice() +{ + allowed = int( tableLookup( "mp/gametypesTable.csv", 0, level.gameType, 5 ) ); + assert( isDefined( allowed ) ); + + return allowed; +} diff --git a/iw4x/iw4x_00/maps/mp/gametypes/_class.gsc b/iw4x/iw4x_00/maps/mp/gametypes/_class.gsc index c16ea20..63a4547 100644 --- a/iw4x/iw4x_00/maps/mp/gametypes/_class.gsc +++ b/iw4x/iw4x_00/maps/mp/gametypes/_class.gsc @@ -60,7 +60,6 @@ init() level thread onPlayerConnecting(); } - getClassChoice( response ) { assert( isDefined( level.classMap[response] ) ); @@ -77,7 +76,6 @@ getWeaponChoice( response ) return 0; } - logClassChoice( class, primaryWeapon, specialType, perks ) { if ( class == self.lastClass ) @@ -90,7 +88,6 @@ logClassChoice( class, primaryWeapon, specialType, perks ) self.lastClass = class; } - cac_getWeapon( classIndex, weaponIndex ) { return self getPlayerData( "customClasses", classIndex, "weaponSetups", weaponIndex, "weapon" ); @@ -131,8 +128,6 @@ cac_getOffhand( classIndex ) return self getPlayerData( "customClasses", classIndex, "specialGrenade" ); } - - table_getWeapon( tableName, classIndex, weaponIndex ) { if ( weaponIndex == 0 ) @@ -303,6 +298,8 @@ giveLoadout( team, class, allowCopycat ) { self takeAllWeapons(); + self.changingWeapon = undefined; + primaryIndex = 0; // initialize specialty array @@ -312,7 +309,15 @@ giveLoadout( team, class, allowCopycat ) allowCopycat = true; primaryWeapon = undefined; + + clearAmmo = false; + // set in game mode custom class + loadoutKillstreak1 = undefined; + loadoutKillstreak2 = undefined; + loadoutKillstreak3 = undefined; + + clonedLoadout = []; if ( isDefined( self.pers["copyCatLoadout"] ) && self.pers["copyCatLoadout"]["inUse"] && allowCopycat ) { self maps\mp\gametypes\_class::setClass( "copycat" ); @@ -356,6 +361,64 @@ giveLoadout( team, class, allowCopycat ) loadoutOffhand = cac_getOffhand( class_num ); loadoutDeathStreak = cac_getDeathstreak( class_num ); } + else if ( class == "gamemode" ) + { + gamemodeLoadout = self.pers["gamemodeLoadout"]; + + loadoutPrimary = gamemodeLoadout["loadoutPrimary"]; + loadoutPrimaryAttachment = gamemodeLoadout["loadoutPrimaryAttachment"]; + loadoutPrimaryAttachment2 = gamemodeLoadout["loadoutPrimaryAttachment2"] ; + loadoutPrimaryCamo = gamemodeLoadout["loadoutPrimaryCamo"]; + loadoutSecondary = gamemodeLoadout["loadoutSecondary"]; + loadoutSecondaryAttachment = gamemodeLoadout["loadoutSecondaryAttachment"]; + loadoutSecondaryAttachment2 = gamemodeLoadout["loadoutSecondaryAttachment2"]; + loadoutSecondaryCamo = gamemodeLoadout["loadoutSecondaryCamo"]; + + // replace the placeholder throwing knife with the valid secondary if there is one + if ( loadoutPrimary == "throwingknife" && loadoutSecondary != "none" ) + { + loadoutPrimary = loadoutSecondary; + loadoutPrimaryAttachment = loadoutSecondaryAttachment; + loadoutPrimaryAttachment2 = loadoutSecondaryAttachment2; + loadoutPrimaryCamo = loadoutSecondaryCamo; + + loadoutSecondary = "none"; + loadoutSecondaryAttachment = "none"; + loadoutSecondaryAttachment2 = "none"; + loadoutSecondaryCamo = "none"; + } + // otherwise replace with normal default for invalid class + else if ( loadoutPrimary == "throwingknife" && loadoutSecondary == "none" ) + { + clearAmmo = true; + loadoutPrimary = "usp"; + loadoutPrimaryAttachment = "tactical"; + } + + loadoutEquipment = gamemodeLoadout["loadoutEquipment"]; + loadoutOffhand = gamemodeLoadout["loadoutOffhand"]; + // hack, until game mode default class data can be reset + if ( loadoutOffhand == "specialty_null" ) + loadoutOffhand = "none"; + loadoutPerk1 = gamemodeLoadout["loadoutPerk1"]; + loadoutPerk2 = gamemodeLoadout["loadoutPerk2"]; + loadoutPerk3 = gamemodeLoadout["loadoutPerk3"]; + + if ( level.killstreakRewards ) + { + loadoutKillstreak1 = gamemodeLoadout["loadoutKillstreak1"]; + loadoutKillstreak2 = gamemodeLoadout["loadoutKillstreak2"]; + loadoutKillstreak3 = gamemodeLoadout["loadoutKillstreak3"]; + } + else + { + loadoutKillstreak1 = "none"; + loadoutKillstreak2 = "none"; + loadoutKillstreak3 = "none"; + } + + loadoutDeathStreak = gamemodeLoadout["loadoutDeathstreak"]; + } else { class_num = getClassIndex( class ); @@ -377,8 +440,10 @@ giveLoadout( team, class, allowCopycat ) loadoutOffhand = table_getOffhand( level.classTableName, class_num ); loadoutDeathstreak = table_getDeathstreak( level.classTableName, class_num ); } + + isGameModeClass = ( class == "gamemode" ); - if ( !(isDefined( self.pers["copyCatLoadout"] ) && self.pers["copyCatLoadout"]["inUse"] && allowCopycat) ) + if ( !isGameModeClass && !(isDefined( self.pers["copyCatLoadout"] ) && self.pers["copyCatLoadout"]["inUse"] && allowCopycat) ) { isCustomClass = isSubstr( class, "custom" ); @@ -435,8 +500,33 @@ giveLoadout( team, class, allowCopycat ) loadoutSecondary = table_getWeapon( level.classTableName, 10, 1 ); //loadoutSecondaryCamo = "none"; + + self.loadoutPrimaryCamo = int(tableLookup( "mp/camoTable.csv", 1, loadoutPrimaryCamo, 0 )); + self.loadoutPrimary = loadoutPrimary; + self.loadoutSecondary = loadoutSecondary; + self.loadoutSecondaryCamo = int(tableLookup( "mp/camoTable.csv", 1, loadoutSecondaryCamo, 0 )); + if ( loadoutSecondary == "none" ) + secondaryName = "none"; + else + { + + secondaryName = buildWeaponName( loadoutSecondary, loadoutSecondaryAttachment, loadoutSecondaryAttachment2 ); + self _giveWeapon( secondaryName, int(tableLookup( "mp/camoTable.csv", 1, loadoutSecondaryCamo, 0 ) ) ); + } + + self SetOffhandPrimaryClass( "other" ); + + // Action Slots + //self _SetActionSlot( 1, "" ); + self _SetActionSlot( 1, "nightvision" ); + self _SetActionSlot( 3, "altMode" ); + self _SetActionSlot( 4, "" ); + // Perks + self _clearPerks(); + self _detachAll(); + if ( level.killstreakRewards ) { if ( getDvarInt( "scr_classic" ) == 1 ) @@ -459,56 +549,39 @@ giveLoadout( team, class, allowCopycat ) loadoutKillstreak3 = "none"; } - secondaryName = buildWeaponName( loadoutSecondary, loadoutSecondaryAttachment, loadoutSecondaryAttachment2 ); - self _giveWeapon( secondaryName, int(tableLookup( "mp/camoTable.csv", 1, loadoutSecondaryCamo, 0 ) ) ); - - self.loadoutPrimaryCamo = int(tableLookup( "mp/camoTable.csv", 1, loadoutPrimaryCamo, 0 )); - self.loadoutPrimary = loadoutPrimary; - self.loadoutSecondary = loadoutSecondary; - self.loadoutSecondaryCamo = int(tableLookup( "mp/camoTable.csv", 1, loadoutSecondaryCamo, 0 )); - - self SetOffhandPrimaryClass( "other" ); - - // Action Slots - //self _SetActionSlot( 1, "" ); - self _SetActionSlot( 1, "nightvision" ); - self _SetActionSlot( 3, "altMode" ); - self _SetActionSlot( 4, "" ); - - // Perks - self _clearPerks(); - self _detachAll(); - // these special case giving pistol death have to come before // perk loadout to ensure player perk icons arent overwritten if ( level.dieHardMode ) self maps\mp\perks\_perks::givePerk( "specialty_pistoldeath" ); - - // only give the deathstreak for the initial spawn for this life. - if ( loadoutDeathStreak != "specialty_null" && getTime() == self.spawnTime ) - { - deathVal = int( tableLookup( "mp/perkTable.csv", 1, loadoutDeathStreak, 6 ) ); - - if ( self getPerkUpgrade( loadoutPerk1 ) == "specialty_rollover" || self getPerkUpgrade( loadoutPerk2 ) == "specialty_rollover" || self getPerkUpgrade( loadoutPerk3 ) == "specialty_rollover" ) - deathVal -= 1; - if ( self.pers["cur_death_streak"] == deathVal ) - { - self thread maps\mp\perks\_perks::givePerk( loadoutDeathStreak ); - self thread maps\mp\gametypes\_hud_message::splashNotify( loadoutDeathStreak ); - } - else if ( self.pers["cur_death_streak"] > deathVal ) - { - self thread maps\mp\perks\_perks::givePerk( loadoutDeathStreak ); - } - } - self loadoutAllPerks( loadoutEquipment, loadoutPerk1, loadoutPerk2, loadoutPerk3 ); self setKillstreaks( loadoutKillstreak1, loadoutKillstreak2, loadoutKillstreak3 ); if ( self hasPerk( "specialty_extraammo", true ) && getWeaponClass( secondaryName ) != "weapon_projectile" ) - self giveMaxAmmo( secondaryName ); + self giveMaxAmmo( secondaryName ); + + // only give the deathstreak for the initial spawn for this life. + if( self.pers["cur_death_streak"] > 0 ) + { + if ( loadoutDeathStreak != "specialty_null" ) + { + deathVal = int( tableLookup( "mp/perkTable.csv", 1, loadoutDeathStreak, 6 ) ); + + if ( self getPerkUpgrade( loadoutPerk1 ) == "specialty_rollover" || self getPerkUpgrade( loadoutPerk2 ) == "specialty_rollover" || self getPerkUpgrade( loadoutPerk3 ) == "specialty_rollover" ) + deathVal -= 1; + + if ( self.pers["cur_death_streak"] == deathVal ) + { + self thread maps\mp\perks\_perks::givePerk( loadoutDeathStreak ); + self thread maps\mp\gametypes\_hud_message::splashNotify( loadoutDeathStreak ); + } + else if ( self.pers["cur_death_streak"] > deathVal ) + { + self thread maps\mp\perks\_perks::givePerk( loadoutDeathStreak ); + } + } + } // Primary Weapon primaryName = buildWeaponName( loadoutPrimary, loadoutPrimaryAttachment, loadoutPrimaryAttachment2 ); @@ -530,24 +603,42 @@ giveLoadout( team, class, allowCopycat ) // Secondary Offhand offhandSecondaryWeapon = loadoutOffhand + "_mp"; - if ( loadoutOffhand == "flash_grenade" ) - self SetOffhandSecondaryClass( "flash" ); - else - self SetOffhandSecondaryClass( "smoke" ); - self giveWeapon( offhandSecondaryWeapon ); - if( loadOutOffhand == "smoke_grenade" ) - self setWeaponAmmoClip( offhandSecondaryWeapon, 1 ); - else if( loadOutOffhand == "flash_grenade" ) - self setWeaponAmmoClip( offhandSecondaryWeapon, 2 ); - else if( loadOutOffhand == "concussion_grenade" ) - self setWeaponAmmoClip( offhandSecondaryWeapon, 2 ); - else - self setWeaponAmmoClip( offhandSecondaryWeapon, 1 ); + if ( loadoutOffhand == "none" ) + self SetOffhandSecondaryClass( "none" ); + else if ( loadoutOffhand == "flash_grenade" ) + self SetOffhandSecondaryClass( "flash" ); + else if ( loadoutOffhand == "smoke_grenade" || loadoutOffhand == "concussion_grenade" ) + self SetOffhandSecondaryClass( "smoke" ); + else + self SetOffhandSecondaryClass( "flash" ); + + switch( loadoutOffhand ) + { + case "none": + break; + default: + self giveWeapon( offhandSecondaryWeapon ); + + if ( loadOutOffhand == "flash_grenade" ) + self setWeaponAmmoClip( offhandSecondaryWeapon, 2 ); + else if( loadOutOffhand == "concussion_grenade" ) + self setWeaponAmmoClip( offhandSecondaryWeapon, 2 ); + else + self setWeaponAmmoClip( offhandSecondaryWeapon, 1 ); + break; + } primaryWeapon = primaryName; self.primaryWeapon = primaryWeapon; self.secondaryWeapon = secondaryName; + + // clear ammo for created default classes using placeholder gun when primary and secondary was set to none + if ( clearAmmo ) + { + self SetWeaponAmmoClip( self.primaryWeapon, 0 ); + self SetWeaponAmmoStock( self.primaryWeapon, 0 ); + } self maps\mp\gametypes\_teams::playerModelForWeapon( self.pers["primaryWeapon"], getBaseWeaponName( secondaryName ) ); @@ -703,7 +794,6 @@ trackRiotShield() } } - tryAttach( placement ) // deprecated; hopefully we won't need to bring this defensive function back { if ( !isDefined( placement ) || placement != "back" ) @@ -747,8 +837,6 @@ tryDetach( placement ) // deprecated; hopefully we won't need to bring this defe return; } - - buildWeaponName( baseName, attachment1, attachment2 ) { if ( !isDefined( level.letterToNumber ) ) @@ -814,7 +902,6 @@ buildWeaponName( baseName, attachment1, attachment2 ) return ( weaponName + "_mp" ); } - makeLettersToNumbers() { array = []; @@ -934,7 +1021,6 @@ setKillstreaks( streak1, streak2, streak3 ) self.killstreaks = newKillstreaks; } - replenishLoadout() // used by ammo hardpoint. { team = self.pers["team"]; @@ -959,7 +1045,6 @@ replenishLoadout() // used by ammo hardpoint. self SetWeaponAmmoClip( level.classGrenades[class]["secondary"]["type"], level.classGrenades[class]["secondary"]["count"] ); } - onPlayerConnecting() { for(;;) @@ -970,14 +1055,18 @@ onPlayerConnecting() { player.pers["class"] = ""; } + if ( !isDefined( player.pers["lastClass"] ) ) + { + player.pers["lastClass"] = ""; + } player.class = player.pers["class"]; - player.lastClass = ""; + player.lastClass = player.pers["lastClass"]; player.detectExplosives = false; player.bombSquadIcons = []; player.bombSquadIds = []; } -} +} fadeAway( waitDelay, fadeDelay ) { @@ -987,7 +1076,6 @@ fadeAway( waitDelay, fadeDelay ) self.alpha = 0; } - setClass( newClass ) { self.curClass = newClass; @@ -1003,7 +1091,6 @@ getPerkForClass( perkSlot, className ) return table_getPerk( level.classTableName, class_num, perkSlot ); } - classHasPerk( className, perkName ) { return( getPerkForClass( 0, className ) == perkName || getPerkForClass( 1, className ) == perkName || getPerkForClass( 2, className ) == perkName ); diff --git a/iw4x/iw4x_00/maps/mp/gametypes/_gamescore.gsc b/iw4x/iw4x_00/maps/mp/gametypes/_gamescore.gsc new file mode 100644 index 0000000..f149b35 --- /dev/null +++ b/iw4x/iw4x_00/maps/mp/gametypes/_gamescore.gsc @@ -0,0 +1,416 @@ +#include maps\mp\_utility; +#include maps\mp\gametypes\_hud_util; +#include common_scripts\utility; + + +getHighestScoringPlayer() +{ + updatePlacement(); + + if ( !level.placement["all"].size ) + return ( undefined ); + else + return ( level.placement["all"][0] ); +} + + +getLosingPlayers() +{ + updatePlacement(); + + players = level.placement["all"]; + losingPlayers = []; + + foreach ( player in players ) + { + if ( player == level.placement["all"][0] ) + continue; + + losingPlayers[losingPlayers.size] = player; + } + + return losingPlayers; +} + + +givePlayerScore( event, player, victim, overridePointsPopup ) +{ + if ( isDefined( level.nukeIncoming ) ) + return; + + if ( !isDefined( overridePointsPopup ) ) + overridePointsPopup = false; + + score = player.pers["score"]; + onPlayerScore( event, player, victim ); + + if ( score == player.pers["score"] ) + return; + + if ( !player rankingEnabled() && !level.hardcoreMode && !overridePointsPopup ) + player thread maps\mp\gametypes\_rank::scorePopup( (player.pers["score"] - score), false, (0.85,0.85,0.85), 0 ); + + player maps\mp\gametypes\_persistence::statAdd( "score", (player.pers["score"] - score) ); + + player.score = player.pers["score"]; + player maps\mp\gametypes\_persistence::statSetChild( "round", "score", player.score ); + + if ( !level.teambased ) + thread sendUpdatedDMScores(); + + player maps\mp\gametypes\_gamelogic::checkPlayerScoreLimitSoon(); + scoreEndedMatch = player maps\mp\gametypes\_gamelogic::checkScoreLimit(); + + if ( scoreEndedMatch && event == "kill" ) + player.finalKill = true; +} + + +onPlayerScore( event, player, victim ) +{ + score = maps\mp\gametypes\_rank::getScoreInfoValue( event ); + + assert( isDefined( score ) ); + + player.pers["score"] += score * level.objectivePointsMod; +} + + +// Seems to only be used for reducing a player's score due to suicide +_setPlayerScore( player, score ) +{ + if ( score == player.pers["score"] ) + return; + + player.pers["score"] = score; + player.score = player.pers["score"]; + + player thread maps\mp\gametypes\_gamelogic::checkScoreLimit(); +} + + +_getPlayerScore( player ) +{ + return player.pers["score"]; +} + + +giveTeamScoreForObjective( team, score ) +{ + if ( isDefined( level.nukeIncoming ) ) + return; + + score *= level.objectivePointsMod; + + teamScore = game["teamScores"][team]; + + otherTeam = level.otherTeam[team]; + + if ( game["teamScores"][team] > game["teamScores"][otherTeam] ) + level.wasWinning = team; + else if ( game["teamScores"][otherTeam] > game["teamScores"][team] ) + level.wasWinning = otherTeam; + + _setTeamScore( team, _getTeamScore( team ) + score ); + + isWinning = "none"; + if ( game["teamScores"][team] > game["teamScores"][otherTeam] ) + isWinning = team; + else if ( game["teamScores"][otherTeam] > game["teamScores"][team] ) + isWinning = otherTeam; + + if ( !level.splitScreen && isWinning != "none" && isWinning != level.wasWinning && getTime() - level.lastStatusTime > 5000 && getScoreLimit() != 1 ) + { + level.lastStatusTime = getTime(); + leaderDialog( "lead_taken", isWinning, "status" ); + if ( level.wasWinning != "none") + leaderDialog( "lead_lost", level.wasWinning, "status" ); + } + + if ( isWinning != "none" ) + level.wasWinning = isWinning; +} + + +getWinningTeam() +{ + if ( game["teamScores"]["allies"] > game["teamScores"]["axis"] ) + return ( "allies" ); + else if ( game["teamScores"]["allies"] < game["teamScores"]["axis"] ) + return ( "axis" ); + + return ( "none" ); +} + +_setTeamScore( team, teamScore ) +{ + if ( teamScore == game["teamScores"][team] ) + return; + + if ( isDefined( level.nukeIncoming ) ) + return; + + game["teamScores"][team] = teamScore; + + updateTeamScore( team ); + + if ( game["status"] == "overtime" ) + thread maps\mp\gametypes\_gamelogic::onScoreLimit(); + else + { + thread maps\mp\gametypes\_gamelogic::checkTeamScoreLimitSoon( team ); + thread maps\mp\gametypes\_gamelogic::checkScoreLimit(); + } +} + + +updateTeamScore( team ) +{ + assert( level.teamBased ); + + teamScore = 0; + if ( !isRoundBased() || !isObjectiveBased() ) + teamScore = _getTeamScore( team ); + else + teamScore = game["roundsWon"][team]; + + setTeamScore( team, teamScore ); + + //thread sendUpdatedTeamScores(); +} + + +_getTeamScore( team ) +{ + return game["teamScores"][team]; +} + + +sendUpdatedTeamScores() +{ + level notify("updating_scores"); + level endon("updating_scores"); + wait .05; + + WaitTillSlowProcessAllowed(); + + foreach ( player in level.players ) + player updateScores(); +} + +sendUpdatedDMScores() +{ + level notify("updating_dm_scores"); + level endon("updating_dm_scores"); + wait .05; + + WaitTillSlowProcessAllowed(); + + for ( i = 0; i < level.players.size; i++ ) + { + level.players[i] updateDMScores(); + level.players[i].updatedDMScores = true; + } +} + + +removeDisconnectedPlayerFromPlacement() +{ + offset = 0; + numPlayers = level.placement["all"].size; + found = false; + for ( i = 0; i < numPlayers; i++ ) + { + if ( level.placement["all"][i] == self ) + found = true; + + if ( found ) + level.placement["all"][i] = level.placement["all"][ i + 1 ]; + } + if ( !found ) + return; + + level.placement["all"][ numPlayers - 1 ] = undefined; + assert( level.placement["all"].size == numPlayers - 1 ); + + if ( level.teamBased ) + { + updateTeamPlacement(); + return; + } + + numPlayers = level.placement["all"].size; + for ( i = 0; i < numPlayers; i++ ) + { + player = level.placement["all"][i]; + player notify( "update_outcome" ); + } + +} + +updatePlacement() +{ + prof_begin("updatePlacement"); + + placementAll = []; + foreach ( player in level.players ) + { + if ( isDefined( player.connectedPostGame ) || (player.pers["team"] != "allies" && player.pers["team"] != "axis") ) + continue; + + placementAll[placementAll.size] = player; + } + + for ( i = 1; i < placementAll.size; i++ ) + { + player = placementAll[i]; + playerScore = player.score; +// for ( j = i - 1; j >= 0 && (player.score > placementAll[j].score || (player.score == placementAll[j].score && player.deaths < placementAll[j].deaths)); j-- ) + for ( j = i - 1; j >= 0 && getBetterPlayer( player, placementAll[j] ) == player; j-- ) + placementAll[j + 1] = placementAll[j]; + placementAll[j + 1] = player; + } + + level.placement["all"] = placementAll; + + if ( level.teamBased ) + updateTeamPlacement(); + + prof_end("updatePlacement"); +} + + +getBetterPlayer( playerA, playerB ) +{ + if ( playerA.score > playerB.score ) + return playerA; + + if ( playerB.score > playerA.score ) + return playerB; + + if ( playerA.deaths < playerB.deaths ) + return playerA; + + if ( playerB.deaths < playerA.deaths ) + return playerB; + + // TODO: more metrics for getting the better player + + if ( cointoss() ) + return playerA; + else + return playerB; +} + + +updateTeamPlacement() +{ + placement["allies"] = []; + placement["axis"] = []; + placement["spectator"] = []; + + assert( level.teamBased ); + + placementAll = level.placement["all"]; + placementAllSize = placementAll.size; + + for ( i = 0; i < placementAllSize; i++ ) + { + player = placementAll[i]; + team = player.pers["team"]; + + placement[team][ placement[team].size ] = player; + } + + level.placement["allies"] = placement["allies"]; + level.placement["axis"] = placement["axis"]; +} + + +initialDMScoreUpdate() +{ + // the first time we call updateDMScores on a player, we have to send them the whole scoreboard. + // by calling updateDMScores on each player one at a time, + // we can avoid having to send the entire scoreboard to every single player + // the first time someone kills someone else. + wait .2; + numSent = 0; + while(1) + { + didAny = false; + + players = level.players; + for ( i = 0; i < players.size; i++ ) + { + player = players[i]; + + if ( !isdefined( player ) ) + continue; + + if ( isdefined( player.updatedDMScores ) ) + continue; + + player.updatedDMScores = true; + player updateDMScores(); + + didAny = true; + wait .5; + } + + if ( !didAny ) + wait 3; // let more players connect + } +} + + +processAssist( killedplayer ) +{ + self endon("disconnect"); + killedplayer endon("disconnect"); + + wait .05; // don't ever run on the same frame as the playerkilled callback. + WaitTillSlowProcessAllowed(); + + if ( self.pers["team"] != "axis" && self.pers["team"] != "allies" ) + return; + + if ( self.pers["team"] == killedplayer.pers["team"] ) + return; + + self thread [[level.onXPEvent]]( "assist" ); + self incPersStat( "assists", 1 ); + self.assists = self getPersStat( "assists" ); + self incPlayerStat( "assists", 1 ); + + givePlayerScore( "assist", self, killedplayer ); + self thread giveAdrenaline( "assist" ); + + self thread maps\mp\gametypes\_missions::playerAssist(); +} + +processShieldAssist( killedPlayer ) +{ + self endon( "disconnect" ); + killedPlayer endon( "disconnect" ); + + wait .05; // don't ever run on the same frame as the playerkilled callback. + WaitTillSlowProcessAllowed(); + + if ( self.pers["team"] != "axis" && self.pers["team"] != "allies" ) + return; + + if ( self.pers["team"] == killedplayer.pers["team"] ) + return; + + self thread [[level.onXPEvent]]( "assist" ); + self thread [[level.onXPEvent]]( "assist" ); + self incPersStat( "assists", 1 ); + self.assists = self getPersStat( "assists" ); + self incPlayerStat( "assists", 1 ); + + givePlayerScore( "assist", self, killedplayer ); + + self thread maps\mp\gametypes\_hud_message::SplashNotifyDelayed( "shield_assist" ); + + self thread maps\mp\gametypes\_missions::playerAssist(); +} \ No newline at end of file diff --git a/iw4x/iw4x_00/maps/mp/gametypes/_gametypes.txt b/iw4x/iw4x_00/maps/mp/gametypes/_gametypes.txt new file mode 100644 index 0000000..bb66fb4 --- /dev/null +++ b/iw4x/iw4x_00/maps/mp/gametypes/_gametypes.txt @@ -0,0 +1,14 @@ +dm +dom +sd +sab +war +koth +oneflag +arena +dd +ctf +gtnw +conf +gun +infect \ No newline at end of file diff --git a/iw4x/iw4x_00/maps/mp/gametypes/_menus.gsc b/iw4x/iw4x_00/maps/mp/gametypes/_menus.gsc index 3067dfa..e38b309 100644 --- a/iw4x/iw4x_00/maps/mp/gametypes/_menus.gsc +++ b/iw4x/iw4x_00/maps/mp/gametypes/_menus.gsc @@ -123,9 +123,9 @@ onMenuResponse() { self endon("disconnect"); - for (;;) + for(;;) { - self waittill( "menuresponse", menu, response ); + self waittill("menuresponse", menu, response); if ( response == "back" ) { @@ -142,21 +142,20 @@ onMenuResponse() continue; } - if (response == "changeteam") + if(response == "changeteam") { self closepopupMenu(); self closeInGameMenu(); self openpopupMenu(game["menu_team"]); } - if (response == "changeclass_marines") + if(response == "changeclass_marines" ) { self closepopupMenu(); self closeInGameMenu(); - if ( getDvar( "g_gametype" ) != "oitc" && getDvar( "g_gametype" ) != "gg" && getDvar( "g_gametype" ) != "ss" && !isDefined(level.customClassCB) ) - { + if ( allowClassChoice() ) self openpopupMenu( game["menu_changeclass_allies"] ); - } + continue; } @@ -164,17 +163,17 @@ onMenuResponse() { self closepopupMenu(); self closeInGameMenu(); - if ( getDvar( "g_gametype" ) != "oitc" && getDvar( "g_gametype" ) != "gg" && getDvar( "g_gametype" ) != "ss" && !isDefined(level.customClassCB) ) - { + + if ( allowClassChoice() ) self openpopupMenu( game["menu_changeclass_axis"] ); - } + continue; } - if (response == "changeclass_marines_splitscreen" ) + if(response == "changeclass_marines_splitscreen" ) self openpopupMenu( "changeclass_marines_splitscreen" ); - if (response == "changeclass_opfor_splitscreen" ) + if(response == "changeclass_opfor_splitscreen" ) self openpopupMenu( "changeclass_opfor_splitscreen" ); if ( response == "endround" ) @@ -186,7 +185,7 @@ onMenuResponse() if ( !level.gameEnded ) { - setDvar( "sv_dontRotate", 1 ); + setDvar("sv_dontrotate", 1); level thread maps\mp\gametypes\_gamelogic::forceEnd(); } else @@ -198,9 +197,9 @@ onMenuResponse() continue; } - if (menu == game["menu_team"]) + if(menu == game["menu_team"]) { - switch (response) + switch(response) { case "allies": self [[level.allies]](); @@ -320,29 +319,30 @@ menuAutoAssign() beginClassChoice( forceNewChoice ) { assert( self.pers["team"] == "axis" || self.pers["team"] == "allies" ); - - if ( getDvar( "g_gametype" ) == "oitc" || getDvar( "g_gametype" ) == "gg" || getDvar( "g_gametype" ) == "ss" || isDefined(level.customClassCB) ) - { - if ( !isAlive( self ) ) - self thread maps\mp\gametypes\_playerlogic::predictAboutToSpawnPlayerOverTime( 0.1 ); - - self.selectedClass = true; - self menuClass( "assault_mp,0" ); - - return; - } team = self.pers["team"]; // menu_changeclass_team is the one where you choose one of the n classes to play as. // menu_class_team is where you can choose to change your team, class, controls, or leave game. - self openpopupMenu( game[ "menu_changeclass_" + team ] ); + + // if game mode allows class choice + if ( allowClassChoice() ) + self openpopupMenu( game[ "menu_changeclass_" + team ] ); + else + self thread bypassClassChoice(); if ( !isAlive( self ) ) self thread maps\mp\gametypes\_playerlogic::predictAboutToSpawnPlayerOverTime( 0.1 ); } +bypassClassChoice() +{ + self.selectedClass = true; + self [[level.class]]("class0"); +} + + beginTeamChoice() { if ( getDvarInt( "scr_player_forceautoassign" ) != 0 ) @@ -538,7 +538,7 @@ addToTeam( team, firstConnect ) self.team = team; // session team is readonly in ranked matches on console - if ( !matchMakingGame() || isDefined( self.pers["isBot"] ) ) + if ( !matchMakingGame() || isDefined( self.pers["isBot"] ) || !allowTeamChoice() ) { if ( level.teamBased ) { diff --git a/iw4x/iw4x_00/maps/mp/gametypes/_playerlogic.gsc b/iw4x/iw4x_00/maps/mp/gametypes/_playerlogic.gsc index 3e3d2fd..b93da23 100644 --- a/iw4x/iw4x_00/maps/mp/gametypes/_playerlogic.gsc +++ b/iw4x/iw4x_00/maps/mp/gametypes/_playerlogic.gsc @@ -116,6 +116,12 @@ maySpawn() spawnClient() { + if ( isDefined( self.addtoteam ) ) + { + maps\mp\gametypes\_menus::addToTeam( self.addtoteam ); + self.addtoteam = undefined; + } + assert( isDefined( self.team ) ); assert( isValidClass( self.class ) ); @@ -504,13 +510,16 @@ tiValidationCheck() return true; } -spawnPlayer() +spawnPlayer( fauxSpawn ) { self endon( "disconnect" ); self endon( "joined_spectators" ); self notify( "spawned" ); self notify( "end_respawn" ); + if ( !isDefined( fauxSpawn ) ) + fauxSpawn = false; + if ( isDefined( self.setSpawnPoint ) && self tiValidationCheck() ) { spawnPoint = self.setSpawnPoint; @@ -559,62 +568,69 @@ spawnPlayer() self.fauxDead = undefined; - self.killsThisLife = []; - - self updateSessionState( "playing", "" ); - self ClearKillcamState(); - self.cancelkillcam = 1; - self openMenu( "killedby_card_hide" ); + if ( !fauxSpawn ) + { + self.killsThisLife = []; + + self updateSessionState( "playing", "" ); + self ClearKillcamState(); + self.cancelkillcam = 1; + self openMenu( "killedby_card_hide" ); - self.maxhealth = maps\mp\gametypes\_tweakables::getTweakableValue( "player", "maxhealth" ); + self.maxhealth = maps\mp\gametypes\_tweakables::getTweakableValue( "player", "maxhealth" ); - self.health = self.maxhealth; - - self.friendlydamage = undefined; - self.hasSpawned = true; - self.spawnTime = getTime(); - self.wasTI = !isDefined( spawnPoint ); - self.afk = false; - self.lastStand = undefined; - self.infinalStand = undefined; - self.inC4Death = undefined; - self.damagedPlayers = []; - self.moveSpeedScaler = 1; - self.killStreakScaler = 1; - self.xpScaler = 1; - self.objectiveScaler = 1; - self.inLastStand = false; - self.clampedHealth = undefined; - self.shieldDamage = 0; - self.shieldBulletHits = 0; - self.recentShieldXP = 0; + self.health = self.maxhealth; + + self.friendlydamage = undefined; + self.hasSpawned = true; + self.spawnTime = getTime(); + self.wasTI = !isDefined( spawnPoint ); + self.afk = false; + self.lastStand = undefined; + self.infinalStand = undefined; + self.inC4Death = undefined; + self.damagedPlayers = []; + self.moveSpeedScaler = 1; + self.killStreakScaler = 1; + self.xpScaler = 1; + self.objectiveScaler = 1; + self.inLastStand = false; + self.clampedHealth = undefined; + self.shieldDamage = 0; + self.shieldBulletHits = 0; + self.recentShieldXP = 0; + } self.disabledWeapon = 0; self.disabledWeaponSwitch = 0; self.disabledOffhandWeapons = 0; self resetUsability(); - - if ( self.pers["lives"] == getGametypeNumLives() ) - { - maps\mp\gametypes\_playerlogic::addToLivesCount(); - } - if ( self.pers["lives"] ) - self.pers["lives"]--; - - self maps\mp\gametypes\_playerlogic::addToAliveCount(); - - if ( !hadSpawned || gameHasStarted() || (gameHasStarted() && level.inGracePeriod && self.hasDoneCombat) ) - self maps\mp\gametypes\_playerlogic::removeFromLivesCount(); - - if ( !self.wasAliveAtMatchStart ) + if ( !fauxSpawn ) { - acceptablePassedTime = 20; - if ( getTimeLimit() > 0 && acceptablePassedTime < getTimeLimit() * 60 / 4 ) - acceptablePassedTime = getTimeLimit() * 60 / 4; + + if ( self.pers["lives"] == getGametypeNumLives() ) + { + maps\mp\gametypes\_playerlogic::addToLivesCount(); + } - if ( level.inGracePeriod || getTimePassed() < acceptablePassedTime * 1000 ) - self.wasAliveAtMatchStart = true; + if ( self.pers["lives"] ) + self.pers["lives"]--; + + self maps\mp\gametypes\_playerlogic::addToAliveCount(); + + if ( !hadSpawned || gameHasStarted() || (gameHasStarted() && level.inGracePeriod && self.hasDoneCombat) ) + self maps\mp\gametypes\_playerlogic::removeFromLivesCount(); + + if ( !self.wasAliveAtMatchStart ) + { + acceptablePassedTime = 20; + if ( getTimeLimit() > 0 && acceptablePassedTime < getTimeLimit() * 60 / 4 ) + acceptablePassedTime = getTimeLimit() * 60 / 4; + + if ( level.inGracePeriod || getTimePassed() < acceptablePassedTime * 1000 ) + self.wasAliveAtMatchStart = true; + } } self setClientDvar( "cg_thirdPerson", "0" ); @@ -636,6 +652,14 @@ spawnPlayer() self.spawnPos = spawnOrigin; self spawn( spawnOrigin, spawnAngles ); + + // immediately fix our stance if we were spawned in place so we don't get stuck in geo + if ( fauxSpawn && isDefined( self.faux_spawn_stance ) ) + { + self setStance( self.faux_spawn_stance ); + self.faux_spawn_stance = undefined; + } + [[level.onSpawnPlayer]](); // Don't do this stuff for TI spawn points @@ -684,7 +708,7 @@ spawnPlayer() prof_end( "spawnPlayer_postUTS" ); - self logstring( "S " + self.origin[0] + " " + self.origin[1] + " " + self.origin[2] ); + //self logstring( "S " + self.origin[0] + " " + self.origin[1] + " " + self.origin[2] ); // give "connected" handlers a chance to start // many of these start onPlayerSpawned handlers which rely on the "spawned_player" @@ -1122,17 +1146,21 @@ Callback_PlayerConnect() logPrint("J;" + self.guid + ";" + self getEntityNumber() + ";" + self.name + "\n"); - if ( matchMakingGame() && game["clientid"] <= 24 && game["clientid"] != getMatchData( "playerCount" ) ) + if ( game["clientid"] <= 24 && game["clientid"] != getMatchData( "playerCount" ) ) { setMatchData( "playerCount", game["clientid"] ); setMatchData( "players", self.clientid, "xuid", self getXuid() ); - /# - if ( ( getDvarInt( "scr_forcerankedmatch" ) && level.teamBased ) || ( isDefined( self.pers["isBot"] ) && level.teamBased ) ) - self.sessionteam = maps\mp\gametypes\_menus::getTeamAssignment(); - #/ - assert( getdvarint( "scr_runlevelandquit" ) == 1 || (level.teamBased && (self.sessionteam == "allies" || self.sessionteam == "axis")) || (!level.teamBased && self.sessionteam == "none" ) ); - //assert( (level.teamBased && self.sessionteam == self.team) || (!level.teamBased && self.sessionteam == "none") ); - setMatchData( "players", self.clientid, "team", self.sessionteam ); + + if( matchMakingGame() && allowTeamChoice() ) + { + /# + if ( ( getDvarInt( "scr_forcerankedmatch" ) && level.teamBased ) || ( isDefined( self.pers["isBot"] ) && level.teamBased ) ) + self.sessionteam = maps\mp\gametypes\_menus::getTeamAssignment(); + #/ + assert( getdvarint( "scr_runlevelandquit" ) == 1 || (level.teamBased && (self.sessionteam == "allies" || self.sessionteam == "axis")) || (!level.teamBased && self.sessionteam == "none" ) ); + //assert( (level.teamBased && self.sessionteam == self.team) || (!level.teamBased && self.sessionteam == "none") ); + setMatchData( "players", self.clientid, "team", self.sessionteam ); + } } if ( !level.teamBased ) @@ -1236,9 +1264,16 @@ Callback_PlayerConnect() self thread kickIfDontSpawn(); return; } - - self [[level.spectator]](); - self maps\mp\gametypes\_menus::beginTeamChoice(); + else if ( allowTeamChoice() ) + { + self [[level.spectator]](); + self maps\mp\gametypes\_menus::beginTeamChoice(); + } + else + { + self [[ level.spectator ]](); + self [[level.autoassign]](); + } } else { @@ -1253,7 +1288,12 @@ Callback_PlayerConnect() self thread spawnSpectator(); if ( self.pers["team"] == "spectator" ) - self maps\mp\gametypes\_menus::beginTeamChoice(); + { + if ( allowTeamChoice() ) + self maps\mp\gametypes\_menus::beginTeamChoice(); + else + self [[level.autoassign]](); + } else self maps\mp\gametypes\_menus::beginClassChoice(); } diff --git a/iw4x/iw4x_00/maps/mp/gametypes/_rank.gsc b/iw4x/iw4x_00/maps/mp/gametypes/_rank.gsc new file mode 100644 index 0000000..7b42ff4 --- /dev/null +++ b/iw4x/iw4x_00/maps/mp/gametypes/_rank.gsc @@ -0,0 +1,763 @@ +#include common_scripts\utility; +#include maps\mp\_utility; +#include maps\mp\gametypes\_hud_util; + + +init() +{ + level.scoreInfo = []; + level.xpScale = getDvarInt( "scr_xpscale" ); + + if ( level.xpScale > 4 || level.xpScale < 0) + exitLevel( false ); + + level.xpScale = min( level.xpScale, 4 ); + level.xpScale = max( level.xpScale, 0 ); + + 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" ); + + while ( !isDefined( level.players ) || !level.players.size ) + wait ( 0.05 ); + + 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.hud_xpEventPopup = player createXpEventPopup(); + + 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; +} + +createXpEventPopup() +{ + hud_xpEventPopup = newClientHudElem( self ); + hud_xpEventPopup.children = []; + hud_xpEventPopup.horzAlign = "center"; + hud_xpEventPopup.vertAlign = "middle"; + hud_xpEventPopup.alignX = "center"; + hud_xpEventPopup.alignY = "middle"; + hud_xpEventPopup.x = 55; + hud_xpEventPopup.y = -35; + hud_xpEventPopup.font = "hudbig"; + hud_xpEventPopup.fontscale = 0.65; + hud_xpEventPopup.archived = false; + hud_xpEventPopup.color = (0.5,0.5,0.5); + hud_xpEventPopup.sort = 10000; + hud_xpEventPopup.elemType = "msgText"; + hud_xpEventPopup maps\mp\gametypes\_hud::fontPulseInit( 3.0 ); + return hud_xpEventPopup; +} + +xpEventPopupFinalize( event, hudColor, glowAlpha ) +{ + self endon( "disconnect" ); + self endon( "joined_team" ); + self endon( "joined_spectators" ); + + self notify( "xpEventPopup" ); + self endon( "xpEventPopup" ); + + if ( level.hardcoremode ) + return; + + wait ( 0.05 ); + + if ( !isDefined( hudColor ) ) + hudColor = (1,1,0.5); + if ( !isDefined( glowAlpha ) ) + glowAlpha = 0; + + if ( !isDefined( self ) ) + return; + + self.hud_xpEventPopup.color = hudColor; + self.hud_xpEventPopup.glowColor = hudColor; + self.hud_xpEventPopup.glowAlpha = glowAlpha; + + self.hud_xpEventPopup setText(event); + self.hud_xpEventPopup.alpha = 0.85; + + wait ( 1.0 ); + + if ( !isDefined( self ) ) + return; + + self.hud_xpEventPopup fadeOverTime( 0.75 ); + self.hud_xpEventPopup.alpha = 0; + self notify( "PopComplete" ); +} + +xpEventPopupTerminate() +{ + self endon( "PopComplete" ); + + waittill_any( "joined_team", "joined_spectators" ); + + self.hud_xpEventPopup fadeOverTime( 0.05 ); + self.hud_xpEventPopup .alpha = 0; +} + +xpEventPopup( event, hudColor, glowAlpha ) +{ + thread xpEventPopupFinalize( event, hudColor, glowAlpha ); + thread xpEventPopupTerminate(); +} + +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 = (int( min( xp, getRankInfoMaxXP( level.maxRank ) ) ) + 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() +{ + if ( level.xpScale > 4 || level.xpScale <= 0) + exitLevel( false ); + + xp = self getRankXP(); + + self maps\mp\gametypes\_persistence::statSet( "experience", xp ); +} \ No newline at end of file diff --git a/iw4x/iw4x_00/maps/mp/gametypes/_weapons.gsc b/iw4x/iw4x_00/maps/mp/gametypes/_weapons.gsc new file mode 100644 index 0000000..5190b74 --- /dev/null +++ b/iw4x/iw4x_00/maps/mp/gametypes/_weapons.gsc @@ -0,0 +1,2706 @@ +#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( level.blockWeaponDrops ) ) + return; + + 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/iw4x/iw4x_00/maps/mp/gametypes/conf.gsc b/iw4x/iw4x_00/maps/mp/gametypes/conf.gsc new file mode 100644 index 0000000..8ecb0ea --- /dev/null +++ b/iw4x/iw4x_00/maps/mp/gametypes/conf.gsc @@ -0,0 +1,398 @@ +#include maps\mp\_utility; +#include maps\mp\gametypes\_hud_util; +#include common_scripts\utility; +/* + Confirmed Kill + Objective: Score points for your team by eliminating players on the opposing team. + Score bonus points for picking up dogtags from downed enemies. + Map ends: When one team reaches the score limit, or time limit is reached + Respawning: No wait / Near teammates + + Level requirementss + ------------------ + Spawnpoints: + classname mp_tdm_spawn + All players spawn from these. The spawnpoint chosen is dependent on the current locations of teammates and enemies + at the time of spawn. Players generally spawn behind their teammates relative to the direction of enemies. + + Spectator Spawnpoints: + classname mp_global_intermission + Spectators spawn from these and intermission is viewed from these positions. + Atleast one is required, any more and they are randomly chosen between. +*/ + + +main() +{ + if(getdvar("mapname") == "mp_background") + return; + + maps\mp\gametypes\_globallogic::init(); + maps\mp\gametypes\_callbacksetup::SetupCallbacks(); + maps\mp\gametypes\_globallogic::SetupCallbacks(); + + registerRoundSwitchDvar( level.gameType, 0, 0, 9 ); + registerTimeLimitDvar( level.gameType, 5, 0, 20 ); + registerScoreLimitDvar( level.gameType, 65, 0, 100 ); + registerRoundLimitDvar( level.gameType, 1, 0, 10 ); + registerWinLimitDvar( level.gameType, 1, 0, 10 ); + registerRoundSwitchDvar( level.gameType, 3, 0, 30 ); + registerNumLivesDvar( level.gameType, 0, 0, 10 ); + registerHalfTimeDvar( level.gameType, 0, 0, 1 ); + + level.teamBased = true; + level.onStartGameType = ::onStartGameType; + level.getSpawnPoint = ::getSpawnPoint; + level.onNormalDeath = ::onNormalDeath; + level.onPrecacheGameType = ::onPrecacheGameType; + + game["dialog"]["gametype"] = "kill_confirmed"; + + level.conf_fx["vanish"] = loadFx( "impacts/small_snowhit" ); +} + +onPrecacheGameType() +{ + precachemodel( "prop_dogtags_friend" ); + precachemodel( "prop_dogtags_foe" ); + precacheshader( "waypoint_dogtags" ); +} + + +onStartGameType() +{ + setClientNameMode("auto_change"); + + if ( !isdefined( game["switchedsides"] ) ) + game["switchedsides"] = false; + + if ( game["switchedsides"] ) + { + oldAttackers = game["attackers"]; + oldDefenders = game["defenders"]; + game["attackers"] = oldDefenders; + game["defenders"] = oldAttackers; + } + + setObjectiveText( "allies", &"OBJECTIVES_CONF" ); + setObjectiveText( "axis", &"OBJECTIVES_CONF" ); + + setObjectiveScoreText( "allies", &"OBJECTIVES_CONF_SCORE" ); + setObjectiveScoreText( "axis", &"OBJECTIVES_CONF_SCORE" ); + + setObjectiveHintText( "allies", &"OBJECTIVES_CONF_HINT" ); + setObjectiveHintText( "axis", &"OBJECTIVES_CONF_HINT" ); + + level.spawnMins = ( 0, 0, 0 ); + level.spawnMaxs = ( 0, 0, 0 ); + maps\mp\gametypes\_spawnlogic::placeSpawnPoints( "mp_tdm_spawn_allies_start" ); + maps\mp\gametypes\_spawnlogic::placeSpawnPoints( "mp_tdm_spawn_axis_start" ); + maps\mp\gametypes\_spawnlogic::addSpawnPoints( "allies", "mp_tdm_spawn" ); + maps\mp\gametypes\_spawnlogic::addSpawnPoints( "axis", "mp_tdm_spawn" ); + + level.mapCenter = maps\mp\gametypes\_spawnlogic::findBoxCenter( level.spawnMins, level.spawnMaxs ); + setMapCenter( level.mapCenter ); + + maps\mp\gametypes\_rank::registerScoreInfo( "kill", 50 ); + maps\mp\gametypes\_rank::registerScoreInfo( "kill_confirmed", 50 ); + maps\mp\gametypes\_rank::registerScoreInfo( "kill_denied", 50 ); + maps\mp\gametypes\_rank::registerScoreInfo( "tags_retrieved", 250 ); + + level.dogtags = []; + + allowed[0] = level.gameType; + + maps\mp\gametypes\_gameobjects::main(allowed); +} + + +getSpawnPoint() +{ + spawnteam = self.pers["team"]; + if ( game["switchedsides"] ) + spawnteam = getOtherTeam( spawnteam ); + + if ( level.inGracePeriod ) + { + spawnPoints = maps\mp\gametypes\_spawnlogic::getSpawnpointArray( "mp_tdm_spawn_" + spawnteam + "_start" ); + spawnPoint = maps\mp\gametypes\_spawnlogic::getSpawnpoint_Random( spawnPoints ); + } + else + { + spawnPoints = maps\mp\gametypes\_spawnlogic::getTeamSpawnPoints( spawnteam ); + spawnPoint = maps\mp\gametypes\_spawnlogic::getSpawnpoint_NearTeam( spawnPoints ); + } + + return spawnPoint; +} + + +onNormalDeath( victim, attacker, lifeId ) +{ + level thread spawnDogTags( victim, attacker ); + + if ( game["state"] == "postgame" && game["teamScores"][attacker.team] > game["teamScores"][level.otherTeam[attacker.team]] ) + attacker.finalKill = true; +} + + +spawnDogTags( victim, attacker ) +{ + if ( isDefined( level.dogtags[victim.guid] ) ) + { + PlayFx( level.conf_fx["vanish"], level.dogtags[victim.guid].curOrigin ); + level.dogtags[victim.guid] notify( "reset" ); + } + else + { + visuals[0] = spawn( "script_model", (0,0,0) ); + visuals[0] setModel( "prop_dogtags_foe" ); + visuals[1] = spawn( "script_model", (0,0,0) ); + visuals[1] setModel( "prop_dogtags_friend" ); + + trigger = spawn( "trigger_radius", (0,0,0), 0, 32, 32 ); + + level.dogtags[victim.guid] = maps\mp\gametypes\_gameobjects::createUseObject( "any", trigger, visuals, (0,0,16) ); + + // we don't need these + _objective_delete( level.dogtags[victim.guid].objIDAllies ); + _objective_delete( level.dogtags[victim.guid].objIDAxis ); + maps\mp\gametypes\_objpoints::deleteObjPoint( level.dogtags[victim.guid].objPoints["allies"] ); + maps\mp\gametypes\_objpoints::deleteObjPoint( level.dogtags[victim.guid].objPoints["axis"] ); + + level.dogtags[victim.guid] maps\mp\gametypes\_gameobjects::setUseTime( 0 ); + level.dogtags[victim.guid].onUse = ::onUse; + level.dogtags[victim.guid].victim = victim; + level.dogtags[victim.guid].victimTeam = victim.pers["team"]; + + level.dogtags[victim.guid].objId = maps\mp\gametypes\_gameobjects::getNextObjID(); + objective_add( level.dogtags[victim.guid].objId, "invisible", (0,0,0) ); + objective_icon( level.dogtags[victim.guid].objId, "waypoint_dogtags" ); + + level thread clearOnVictimDisconnect( victim ); + victim thread tagTeamUpdater( level.dogtags[victim.guid] ); + } + + pos = victim.origin + (0,0,14); + level.dogtags[victim.guid].curOrigin = pos; + level.dogtags[victim.guid].trigger.origin = pos; + level.dogtags[victim.guid].visuals[0].origin = pos; + level.dogtags[victim.guid].visuals[1].origin = pos; + + level.dogtags[victim.guid] maps\mp\gametypes\_gameobjects::allowUse( "any" ); + + level.dogtags[victim.guid].visuals[0] thread showToTeam( level.dogtags[victim.guid], getOtherTeam( victim.pers["team"] ) ); + level.dogtags[victim.guid].visuals[1] thread showToTeam( level.dogtags[victim.guid], victim.pers["team"] ); + + level.dogtags[victim.guid].attacker = attacker; + //level.dogtags[victim.guid] thread timeOut( victim ); + + objective_position( level.dogtags[victim.guid].objId, pos ); + objective_state( level.dogtags[victim.guid].objId, "active" ); + //objective_player( level.dogtags[victim.guid].objId, attacker getEntityNumber() ); + + playSoundAtPos( pos, "mp_killconfirm_tags_drop" ); + + level.dogtags[victim.guid] thread bounce(); +} + + +showToTeam( gameObject, team ) +{ + gameObject endon ( "death" ); + gameObject endon( "reset" ); + + self hide(); + + foreach ( player in level.players ) + { + if( player.team == team ) + self ShowToPlayer( player ); + } + + for ( ;; ) + { + level waittill ( "joined_team" ); + + self hide(); + foreach ( player in level.players ) + { + if ( player.team == team ) + self ShowToPlayer( player ); + + if ( gameObject.victimTeam == player.team && player == gameObject.attacker ) + objective_state( gameObject.objId, "invisible" ); + } + } +} + + +onUse( player ) +{ + // friendly pickup + if ( player.pers["team"] == self.victimTeam ) + { + self.trigger playSound( "mp_killconfirm_tags_deny" ); + + if ( self.victim == player ) + { + event = "tags_retrieved"; + splash = &"SPLASHES_TAGS_RETRIEVED"; + } + else + { + event = "kill_denied"; + splash = &"SPLASHES_KILL_DENIED"; + } + + // tell the attacker their kill was denied + if ( isDefined( self.attacker ) ) + self.attacker thread maps\mp\gametypes\_rank::xpEventPopup( &"SPLASHES_DENIED_KILL", ( 1,0.5,0.5) ); + } + // enemy pickup + else + { + self.trigger playSound( "mp_killconfirm_tags_pickup" ); + + event = "kill_confirmed"; + splash = &"SPLASHES_KILL_CONFIRMED"; + + // if not us, tell the attacker their kill was confirmed + if ( self.attacker != player ) + self.attacker onPickup( event, splash ); + + self.trigger playSoundToPlayer( game["voice"][player.pers["team"]] + "kill_confirmed", player ); + + player maps\mp\gametypes\_gamescore::giveTeamScoreForObjective( player.pers["team"], 1 ); + } + + player onPickup( event, splash ); + + // do all this at the end now so the location doesn't change before playing the sound on the entity + self resetTags(); +} + + +onPickup( event, splash ) +{ + level endon( "game_ended" ); + self endon( "disconnect" ); + + self thread maps\mp\gametypes\_rank::xpEventPopup( splash ); + maps\mp\gametypes\_gamescore::givePlayerScore( event, self, undefined, true ); + self thread maps\mp\gametypes\_rank::giveRankXP( event ); +} + + +resetTags() +{ + self.attacker = undefined; + self notify( "reset" ); + self.visuals[0] hide(); + self.visuals[1] hide(); + self.curOrigin = (0,0,1000); + self.trigger.origin = (0,0,1000); + self.visuals[0].origin = (0,0,1000); + self.visuals[1].origin = (0,0,1000); + self maps\mp\gametypes\_gameobjects::allowUse( "none" ); + objective_state( self.objId, "invisible" ); +} + + +bounce() +{ + level endon( "game_ended" ); + self endon( "reset" ); + + bottomPos = self.curOrigin; + topPos = self.curOrigin + (0,0,12); + + while( true ) + { + self.visuals[0] moveTo( topPos, 0.5, 0.15, 0.15 ); + self.visuals[0] rotateYaw( 180, 0.5 ); + self.visuals[1] moveTo( topPos, 0.5, 0.15, 0.15 ); + self.visuals[1] rotateYaw( 180, 0.5 ); + + wait( 0.5 ); + + self.visuals[0] moveTo( bottomPos, 0.5, 0.15, 0.15 ); + self.visuals[0] rotateYaw( 180, 0.5 ); + self.visuals[1] moveTo( bottomPos, 0.5, 0.15, 0.15 ); + self.visuals[1] rotateYaw( 180, 0.5 ); + + wait( 0.5 ); + } +} + + +timeOut( victim ) +{ + level endon( "game_ended" ); + victim endon( "disconnect" ); + self notify( "timeout" ); + self endon( "timeout" ); + + level maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( 30.0 ); + + self.visuals[0] hide(); + self.visuals[1] hide(); + self.curOrigin = (0,0,1000); + self.trigger.origin = (0,0,1000); + self.visuals[0].origin = (0,0,1000); + self.visuals[1].origin = (0,0,1000); + self maps\mp\gametypes\_gameobjects::allowUse( "none" ); +} + + +tagTeamUpdater( tags ) +{ + level endon( "game_ended" ); + self endon( "disconnect" ); + + while( true ) + { + self waittill( "joined_team" ); + + tags.victimTeam = self.pers["team"]; + tags resetTags(); + } +} + + +clearOnVictimDisconnect( victim ) +{ + level endon( "game_ended" ); + + guid = victim.guid; + victim waittill( "disconnect" ); + + if ( isDefined( level.dogtags[guid] ) ) + { + // block further use + level.dogtags[guid] maps\mp\gametypes\_gameobjects::allowUse( "none" ); + + // tell the attacker their kill was denied + if ( isDefined( level.dogtags[guid].attacker ) ) + level.dogtags[guid].attacker thread maps\mp\gametypes\_rank::xpEventPopup( &"SPLASHES_DENIED_KILL", (1,0.5,0.5) ); + + // play vanish effect, reset, and wait for reset to process + PlayFx( level.conf_fx["vanish"], level.dogtags[guid].curOrigin ); + level.dogtags[guid] notify( "reset" ); + wait( 0.05 ); + + // sanity check before removal + if ( isDefined( level.dogtags[guid] ) ) + { + // delete objective and visuals + objective_delete( level.dogtags[guid].objId ); + level.dogtags[guid].trigger delete(); + for ( i=0; i self.gunGameGunIndex ) + { + self thread maps\mp\gametypes\_rank::xpEventPopup( &"SPLASHES_DROPPED_GUN_RANK" ); + maps\mp\gametypes\_gamescore::givePlayerScore( "dropped_gun_score", self, undefined, true, true ); + } + + if ( sMeansOfDeath == "MOD_MELEE" ) + { + if ( self.gunGamePrevGunIndex ) + { + attacker thread maps\mp\gametypes\_rank::xpEventPopup( &"SPLASHES_DROPPED_ENEMY_GUN_RANK" ); + attacker thread maps\mp\gametypes\_rank::giveRankXP( "dropped_enemy_gun_rank" ); + } + } + } + else if ( ( sMeansOfDeath == "MOD_PISTOL_BULLET" ) + || ( sMeansOfDeath == "MOD_RIFLE_BULLET" ) + || ( sMeansOfDeath == "MOD_HEAD_SHOT" ) + || ( sMeansOfDeath == "MOD_PROJECTILE" ) || ( sMeansOfDeath == "MOD_PROJECTILE_SPLASH" ) + || ( sMeansOfDeath == "MOD_IMPACT" ) + || ( sMeansOfDeath == "MOD_GRENADE" ) || ( sMeansOfDeath == "MOD_GRENADE_SPLASH" ) + || ( sMeansOfDeath == "MOD_MELEE" && isMeleeWeapon ) + ) + { + // Prevent sequential kills from counting by validating the primary weapon + // Let throwing knife kills count even though they're not the primary weapon + if ( sWeapon != attacker.primaryWeapon ) + return; + + attacker.gunGamePrevGunIndex = attacker.gunGameGunIndex; + attacker.gunGameGunIndex++; + + attacker thread maps\mp\gametypes\_rank::giveRankXP( "gained_gun_rank" ); + maps\mp\gametypes\_gamescore::givePlayerScore( "gained_gun_score", attacker, self, true, true ); + + if ( attacker.gunGameGunIndex == level.gun_guns.size-1 ) + { + playSoundOnPlayers( "mp_enemy_obj_captured" ); + level thread teamPlayerCardSplash( "callout_top_gun_rank", attacker ); + } + + if ( attacker.gunGameGunIndex < level.gun_guns.size ) + { + attacker thread maps\mp\gametypes\_rank::xpEventPopup( &"SPLASHES_GAINED_GUN_RANK" ); + attacker playLocalSound( "mp_war_objective_taken" ); + attacker giveNextGun(); + } + } + } +} + +giveNextGun( doSetSpawnWeapon ) +{ + // get the next one + newWeapon = level.gun_guns[self.gunGameGunIndex]; + + // add attachments + newWeapon = addAttachments( newWeapon ); + + // give gun + self takeAllWeapons(); + _giveWeapon( newWeapon ); + + // set vars + if ( isDefined( doSetSpawnWeapon ) ) + self setSpawnWeapon( newWeapon ); + weaponTokens = StrTok( newWeapon, "_" ); + weaponName = weaponTokens[0]; + + self.pers["primaryWeapon"] = weaponName; + self.primaryWeapon = newWeapon; + + // use it! + self GiveStartAmmo( newWeapon ); + self switchToWeapon( newWeapon ); + + // gain/drop scoring/messaging + if ( self.gunGamePrevGunIndex > self.gunGameGunIndex ) + { + // we dropped :( + self thread maps\mp\gametypes\_rank::xpEventPopup( &"SPLASHES_DROPPED_GUN_RANK" ); + } + else if ( self.gunGamePrevGunIndex < self.gunGameGunIndex ) + { + // we gained :) + self thread maps\mp\gametypes\_rank::xpEventPopup( &"SPLASHES_GAINED_GUN_RANK" ); + } + self.gunGamePrevGunIndex = self.gunGameGunIndex; + + // update the personal gun progress hud + self updateGunHUD(); +} + +addAttachments( weaponName ) +{ + if ( isDefined( level.gun_attachments[weaponName] ) && isDefined( level.gun_attachments[weaponName]["attach1"] ) ) + attach1 = level.gun_attachments[weaponName]["attach1"]; + else + attach1 = "none"; + + if ( isDefined( level.gun_attachments[weaponName] ) && isDefined( level.gun_attachments[weaponName]["attach2"] ) ) + attach2 = level.gun_attachments[weaponName]["attach2"]; + else + attach2 = "none"; + + fullWeaponName = buildWeaponName( weaponName, attach1, attach2 ); + return fullWeaponName; +} + +onTimeLimit() +{ + level.finalKillCam_winner = "none"; + winners = getHighestProgressedPlayers(); + + if ( !isDefined( winners ) || !winners.size ) + thread maps\mp\gametypes\_gamelogic::endGame( "tie", game[ "end_reason" ][ "time_limit_reached" ] ); + else if ( winners.size == 1 ) + thread maps\mp\gametypes\_gamelogic::endGame( winners[0], game[ "end_reason" ][ "time_limit_reached" ] ); + else + { + if ( winners[winners.size-1].gunGameGunIndex > winners[winners.size-2].gunGameGunIndex ) + thread maps\mp\gametypes\_gamelogic::endGame( winners[winners.size-1], game[ "end_reason" ][ "time_limit_reached" ] ); + else + thread maps\mp\gametypes\_gamelogic::endGame( "tie", game[ "end_reason" ][ "time_limit_reached" ] ); + } +} + +getHighestProgressedPlayers() +{ + highestProgress = -1; + highestProgressedPlayers = []; + foreach( player in level.players ) + { + if ( isDefined( player.gunGameGunIndex ) && player.gunGameGunIndex >= highestProgress ) + { + highestProgress = player.gunGameGunIndex; + highestProgressedPlayers[highestProgressedPlayers.size] = player; + } + } + return highestProgressedPlayers; +} + +refillAmmo() +{ + level endon( "game_ended" ); + self endon( "disconnect" ); + + while ( true ) + { + self waittill( "reload" ); + self GiveStartAmmo( self.primaryWeapon ); + } +} + +refillSingleCountAmmo() +{ + level endon( "game_ended" ); + self endon( "disconnect" ); + + while ( true ) + { + if ( isReallyAlive( self ) && self.team != "spectator" && isDefined( self.primaryWeapon ) && self getAmmoCount( self.primaryWeapon ) == 0 ) + { + // fake a reload time + wait( 2 ); + self notify( "reload" ); + wait( 1 ); + } + else + wait( 0.05 ); + } +} + +initGunHUD() +{ + self.gun_progressDisplay[0] = self createFontString( "small", 1.6 ); + self.gun_progressDisplay[0] setPoint( "TOP LEFT", "TOP LEFT", 115, 5 ); + self.gun_progressDisplay[0] setText( &"MP_WEAPON" ); + self.gun_progressDisplay[0].alpha = 1; + self.gun_progressDisplay[0].archived = false; + self.gun_progressDisplay[0].hideWhenInMenu = true; + + self.gun_progressDisplay[1] = self createFontString( "small", 1.6 ); + self.gun_progressDisplay[1] setPoint( "TOP LEFT", "TOP LEFT", 115, 23 ); + self.gun_progressDisplay[1] setText( "1 / " + level.gun_guns.size ); + self.gun_progressDisplay[1].alpha = 1; + self.gun_progressDisplay[1].archived = false; + self.gun_progressDisplay[1].hideWhenInMenu = true; + + thread hideOnGameEnd(); + thread hideInKillCam(); +} + +updateGunHUD() +{ + self.gun_progressDisplay[1] setText( (self.gunGameGunIndex+1) + " / " + level.gun_guns.size ); +} + +hideInKillCam() +{ + self endon( "disconnect" ); + var_0 = 1; + + for (;;) + { + if ( var_0 && ( !isalive( self ) || isinkillcam() ) ) + { + self.gun_progressdisplay[0].alpha = 0; + self.gun_progressdisplay[1].alpha = 0; + var_0 = 0; + } + else if ( !var_0 && ( isalive( self ) && !isinkillcam() ) ) + { + self.gun_progressdisplay[0].alpha = 1; + self.gun_progressdisplay[1].alpha = 1; + var_0 = 1; + } + + wait 0.05; + } +} + +hideOnGameEnd() +{ + self endon( "disconnect" ); + level waittill( "game_ended" ); + + self.gun_progressDisplay[0].alpha = 0; + self.gun_progressDisplay[1].alpha = 0; +} + +setGuns() +{ + level.gun_guns = []; + + // hand guns + level.gun_guns[0] = "deserteagle"; + // machine pistols + level.gun_guns[1] = "glock"; + level.gun_guns[2] = "tmp"; + // sub + level.gun_guns[3] = "mp5k"; + level.gun_guns[4] = "p90"; + level.gun_guns[5] = "kriss"; + // assault - auto + level.gun_guns[6] = "ak47"; + level.gun_guns[7] = "masada"; + level.gun_guns[8] = "scar"; + // lmg + level.gun_guns[9] = "sa80"; + level.gun_guns[10] = "aug"; + // shotgun + level.gun_guns[11] = "spas12"; + level.gun_guns[12] = "aa12"; + level.gun_guns[13] = "model1887"; + // assault - burst + level.gun_guns[14] = "famas"; + level.gun_guns[15] = "m16"; + // sniper + level.gun_guns[16] = "barrett"; + level.gun_guns[17] = "cheytac"; + // launcher + level.gun_guns[18] = "m79"; + level.gun_guns[19] = "javelin"; + + // default attachments for all guns that take them + level.gun_attachments = []; + // pistol + level.gun_attachments["coltanaconda"]["attach1"] = "tactical"; + level.gun_attachments["usp"]["attach1"] = "tactical"; + level.gun_attachments["deserteagle"]["attach1"] = "tactical"; + level.gun_attachments["deserteaglegold"]["attach1"] = "tactical"; + level.gun_attachments["beretta"]["attach1"] = "tactical"; + // machine pistol + level.gun_attachments["tmp"]["attach1"] = "reflex"; + level.gun_attachments["glock"]["attach1"] = "reflex"; + level.gun_attachments["pp2000"]["attach1"] = "reflex"; + level.gun_attachments["beretta393"]["attach1"] = "reflex"; + // smg + level.gun_attachments["mp5k"]["attach1"] = "reflex"; + level.gun_attachments["mp5k"]["attach2"] = "xmags"; + level.gun_attachments["uzi"]["attach1"] = "reflex"; + level.gun_attachments["uzi"]["attach2"] = "xmags"; + level.gun_attachments["p90"]["attach1"] = "reflex"; + level.gun_attachments["p90"]["attach2"] = "xmags"; + level.gun_attachments["kriss"]["attach1"] = "reflex"; + level.gun_attachments["kriss"]["attach2"] = "xmags"; + level.gun_attachments["ump45"]["attach1"] = "reflex"; + level.gun_attachments["ump45"]["attach2"] = "xmags"; + level.gun_attachments["ak74u"]["attach1"] = "reflex"; + level.gun_attachments["ak74u"]["attach2"] = "xmags"; + // assault + level.gun_attachments["ak47"]["attach1"] = "reflex"; + level.gun_attachments["ak47"]["attach2"] = "xmags"; + level.gun_attachments["ak47classic"]["attach1"] = "reflex"; + level.gun_attachments["ak47classic"]["attach2"] = "xmags"; + level.gun_attachments["m16"]["attach1"] = "reflex"; + level.gun_attachments["m16"]["attach2"] = "xmags"; + level.gun_attachments["m4"]["attach1"] = "reflex"; + level.gun_attachments["m4"]["attach2"] = "xmags"; + level.gun_attachments["fn2000"]["attach1"] = "reflex"; + level.gun_attachments["fn2000"]["attach2"] = "xmags"; + level.gun_attachments["masada"]["attach1"] = "reflex"; + level.gun_attachments["masada"]["attach2"] = "xmags"; + level.gun_attachments["famas"]["attach1"] = "reflex"; + level.gun_attachments["famas"]["attach2"] = "xmags"; + level.gun_attachments["fal"]["attach1"] = "reflex"; + level.gun_attachments["fal"]["attach2"] = "xmags"; + level.gun_attachments["scar"]["attach1"] = "reflex"; + level.gun_attachments["scar"]["attach2"] = "xmags"; + level.gun_attachments["tavor"]["attach1"] = "reflex"; + level.gun_attachments["tavor"]["attach2"] = "xmags"; + level.gun_attachments["tavor"]["attach1"] = "reflex"; + level.gun_attachments["tavor"]["attach2"] = "xmags"; + // sniper + level.gun_attachments["barrett"]["attach1"] = "xmags"; + level.gun_attachments["wa2000"]["attach1"] = "xmags"; + level.gun_attachments["m21"]["attach1"] = "xmags"; + level.gun_attachments["cheytac"]["attach1"] = "xmags"; + level.gun_attachments["dragunov"]["attach1"] = "xmags"; + level.gun_attachments["m40a3"]["attach1"] = "xmags"; + // shotgun + level.gun_attachments["ranger"]["attach1"] = "akimbo"; + level.gun_attachments["model1887"]["attach1"] = "akimbo"; + level.gun_attachments["striker"]["attach1"] = "grip"; + level.gun_attachments["striker"]["attach2"] = "xmags"; + level.gun_attachments["aa12"]["attach1"] = "grip"; + level.gun_attachments["aa12"]["attach2"] = "xmags"; + level.gun_attachments["m1014"]["attach1"] = "grip"; + level.gun_attachments["m1014"]["attach2"] = "xmags"; + level.gun_attachments["spas12"]["attach1"] = "grip"; + level.gun_attachments["spas12"]["attach2"] = "xmags"; + // lmg + level.gun_attachments["rpd"]["attach1"] = "grip"; + level.gun_attachments["rpd"]["attach2"] = "reflex"; + level.gun_attachments["sa80"]["attach1"] = "grip"; + level.gun_attachments["sa80"]["attach2"] = "reflex"; + level.gun_attachments["mg4"]["attach1"] = "grip"; + level.gun_attachments["mg4"]["attach2"] = "reflex"; + level.gun_attachments["m240"]["attach1"] = "grip"; + level.gun_attachments["m240"]["attach2"] = "reflex"; + level.gun_attachments["aug"]["attach1"] = "grip"; + level.gun_attachments["aug"]["attach2"] = "reflex"; +} + +setSpecialLoadout() +{ + // no killstreaks defined for special classes + level.gun_loadouts["axis"]["loadoutPrimary"] = "masada"; // can't use "none" for primary, this is replaced on spawn anyway + level.gun_loadouts["axis"]["loadoutPrimaryAttachment"] = "none"; + level.gun_loadouts["axis"]["loadoutPrimaryAttachment2"] = "none"; + level.gun_loadouts["axis"]["loadoutPrimaryCamo"] = "none"; + + level.gun_loadouts["axis"]["loadoutSecondary"] = "none"; + level.gun_loadouts["axis"]["loadoutSecondaryAttachment"] = "none"; + level.gun_loadouts["axis"]["loadoutSecondaryAttachment2"] = "none"; + level.gun_loadouts["axis"]["loadoutSecondaryCamo"] = "none"; + + level.gun_loadouts["axis"]["loadoutEquipment"] = "specialty_null"; + level.gun_loadouts["axis"]["loadoutOffhand"] = "none"; + level.gun_loadouts["axis"]["loadoutPerk1"] = "specialty_null"; + level.gun_loadouts["axis"]["loadoutPerk2"] = "specialty_null"; + level.gun_loadouts["axis"]["loadoutPerk3"] = "specialty_null"; + + // FFA games don't have teams, but players are allowed to choose team on the way in + // just for character model and announcer voice variety. Same loadout for both. + level.gun_loadouts["allies"] = level.gun_loadouts["axis"]; +} \ No newline at end of file diff --git a/iw4x/iw4x_00/maps/mp/gametypes/gun.txt b/iw4x/iw4x_00/maps/mp/gametypes/gun.txt new file mode 100644 index 0000000..336a301 --- /dev/null +++ b/iw4x/iw4x_00/maps/mp/gametypes/gun.txt @@ -0,0 +1 @@ +"MPUI_GUNGAME" \ No newline at end of file diff --git a/iw4x/iw4x_00/maps/mp/gametypes/infect.gsc b/iw4x/iw4x_00/maps/mp/gametypes/infect.gsc new file mode 100644 index 0000000..d35fab1 --- /dev/null +++ b/iw4x/iw4x_00/maps/mp/gametypes/infect.gsc @@ -0,0 +1,839 @@ +#include maps\mp\_utility; +#include maps\mp\gametypes\_hud_util; +#include common_scripts\utility; + +main() +{ + maps\mp\gametypes\_globallogic::init(); + maps\mp\gametypes\_callbacksetup::SetupCallbacks(); + maps\mp\gametypes\_globallogic::SetupCallbacks(); + + registerTimeLimitDvar( level.gameType, 10 ); + setOverrideWatchDvar( "scorelimit", 0 ); + registerRoundLimitDvar( level.gameType, 1 ); + registerWinLimitDvar( level.gameType, 1 ); + registerNumLivesDvar( level.gameType, 0 ); + registerHalfTimeDvar( level.gameType, 0 ); + + setSpecialLoadouts(); + + level.teamBased = true; + // don't sit there waiting for both teams to contain players, + // as everyone starts a survivor + level.prematchWaitForTeams = false; + level.onPrecacheGameType = ::onPrecacheGameType; + level.onStartGameType = ::onStartGameType; + level.onSpawnPlayer = ::onSpawnPlayer; + level.getSpawnPoint = ::getSpawnPoint; + level.onPlayerKilled = ::onPlayerKilled; + level.onTimeLimit = ::onTimeLimit; + + level.infect_perks = []; + level.infect_perks[level.infect_perks.size] = "specialty_marathon"; + level.infect_perks[level.infect_perks.size] = "specialty_fastreload"; + level.infect_perks[level.infect_perks.size] = "specialty_scavenger"; + level.infect_perks[level.infect_perks.size] = "specialty_fastmantle"; + level.infect_perks[level.infect_perks.size] = "specialty_quickdraw"; + level.infect_perks[level.infect_perks.size] = "specialty_lightweight"; + level.infect_perks[level.infect_perks.size] = "specialty_hardline"; + level.infect_perks[level.infect_perks.size] = "specialty_coldblooded"; + level.infect_perks[level.infect_perks.size] = "specialty_explosivedamage"; + level.infect_perks[level.infect_perks.size] = "specialty_bombsquad"; + level.infect_perks[level.infect_perks.size] = "specialty_bulletaccuracy"; + level.infect_perks[level.infect_perks.size] = "specialty_localjammer"; + level.infect_perks[level.infect_perks.size] = "specialty_quieter"; + + level.determineWinningTeam = ::determineWinningTeam; +} + +determineWinningTeam() +{ + // if the game's still going on, the allies are winning since they're still alive + return "allies"; +} + +onPrecacheGameType() +{ + precachestring( &"MP_CONSCRIPTION_STARTS_IN" ); +} + +onStartGameType() +{ + setclientnamemode( "auto_change" ); + setObjectiveText( "allies", &"OBJECTIVES_INFECT" ); + setObjectiveText( "axis", &"OBJECTIVES_INFECT" ); + + setObjectiveScoreText( "allies", &"OBJECTIVES_INFECT_SCORE" ); + setObjectiveScoreText( "axis", &"OBJECTIVES_INFECT_SCORE" ); + + setObjectiveHintText( "allies", &"OBJECTIVES_INFECT_HINT" ); + setObjectiveHintText( "axis", &"OBJECTIVES_INFECT_HINT" ); + + level.spawnMins = ( 0, 0, 0 ); + level.spawnMaxs = ( 0, 0, 0 ); + maps\mp\gametypes\_spawnlogic::addSpawnPoints( "allies", "mp_tdm_spawn" ); + maps\mp\gametypes\_spawnlogic::addSpawnPoints( "axis", "mp_tdm_spawn" ); + level.mapCenter = maps\mp\gametypes\_spawnlogic::findBoxCenter( level.spawnMins, level.spawnMaxs ); + setMapCenter( level.mapCenter ); + + allowed = []; + maps\mp\gametypes\_gameobjects::main(allowed); + + maps\mp\gametypes\_rank::registerScoreInfo( "firstblood", 0 ); + maps\mp\gametypes\_rank::registerScoreInfo( "first_draft", 350 ); + maps\mp\gametypes\_rank::registerScoreInfo( "final_rogue", 200 ); + maps\mp\gametypes\_rank::registerScoreInfo( "kill", 50 ); + maps\mp\gametypes\_rank::registerScoreInfo( "draft_rogue", 200 ); + maps\mp\gametypes\_rank::registerScoreInfo( "survivor", 50 ); + + level.QuickMessageToAll = true; + level.blockWeaponDrops = true; + level.infect_allowSuicide = false; + + SetDvar( "ui_allow_teamchange", 0 ); + SetDvar( "scr_teambalance", 0 ); + + level.infect_timerDisplay = createServerTimer( "objective", 1.4 ); + level.infect_timerDisplay setPoint( "TOPLEFT", "TOPLEFT", 115, 5 ); + level.infect_timerDisplay.label = &"MP_DRAFT_STARTS_IN"; + level.infect_timerDisplay.alpha = 0; + level.infect_timerDisplay.archived = false; + level.infect_timerDisplay.hideWhenInMenu = true; + + level.infect_choseFirstInfected = false; + level.infect_choosingFirstInfected = false; + level.infect_awardedFinalSurvivor = false; + level.infect_countdownInProgress = false; + + level.infect_teamScores["axis"] = 0; + level.infect_teamScores["allies"] = 0; + level.infect_players = []; + + level thread onPlayerConnect(); + level thread watchInfectForfeit(); +} + +onPlayerConnect() +{ + for (;;) + { + level waittill( "connected", player ); + + player.infect_firstSpawn = true; + player.infected_Rejoined = false; + player.infect_JoinedAtStart = true; + + if( gameFlag( "prematch_done" ) ) + { + player.infect_JoinedAtStart = false; + } + // infected who quit and rejoined same game + if( IsDefined( level.infect_players[player.name] ) ) + { + player.infected_Rejoined = true; + } + } +} + +getSpawnPoint() +{ + // first time here? + if ( self.infect_firstSpawn ) + { + self.infect_firstSpawn = false; + + // everyone is a gamemode class in infect, no class selection + self.pers["class"] = "gamemode"; + self.pers["lastClass"] = ""; + self.class = self.pers["class"]; + self.lastClass = self.pers["lastClass"]; + + // survivors are allies + teamChoice = "allies"; + + // everyone starts as survivors, unless an infected quit and rejoined same game + if( self.infected_Rejoined ) + { + teamChoice = "axis"; + } + + self maps\mp\gametypes\_menus::addToTeam( teamChoice, true ); + + thread onPlayerDisconnect(); + } + + if ( level.inGracePeriod ) + { + spawnPoints = maps\mp\gametypes\_spawnlogic::getSpawnpointArray( "mp_tdm_spawn" ); + spawnPoint = maps\mp\gametypes\_spawnlogic::getSpawnpoint_Random( spawnPoints ); + } + else + { + spawnPoints = maps\mp\gametypes\_spawnlogic::getTeamSpawnPoints( self.pers["team"] ); + spawnPoint = maps\mp\gametypes\_spawnlogic::getSpawnpoint_NearTeam( spawnPoints ); + } + + return spawnPoint; +} + +onSpawnPlayer() +{ + self.teamChangedThisFrame = undefined; + self.infect_spawnPos = self.origin; + + // resynch teams + updateTeamScores(); + + // let the first spawned player kick this off + if ( !level.infect_choosingFirstInfected ) + { + level.infect_choosingFirstInfected = true; + level thread chooseFirstInfected(); + } + + // infected who quit and rejoined same game? + if( self.infected_Rejoined ) + { + self.infect_rejoined = undefined; + + // stop initial infect countdown (if quit/join player was initial and a new coutdown is underway) + if ( !level.infect_allowSuicide ) + { + level notify( "infect_stopCountdown" ); + level.infect_choseFirstInfected = true; + level.infect_allowSuicide = true; + foreach( player in level.players ) + { + if ( isDefined( player.infect_isBeingChosen ) ) + player.infect_isBeingChosen = undefined; + } + } + + // if an initial was already chosen while they were gone and they are still initial, set them to normal + foreach( player in level.players ) + { + if ( isDefined( player.isInitialInfected ) ) + player thread setInitialToNormalInfected(); + } + + // if they're the only infected, then set them as initial infected + if ( level.infect_teamScores["axis"] == 1 ) + self.isInitialInfected = true; + } + + // onSpawnPlayer() is called before giveLoadout() + // set self.pers["gamemodeLoadout"] for giveLoadout() to use + if ( isDefined( self.isInitialInfected ) ) + self.pers["gamemodeLoadout"] = level.infect_loadouts["axis_initial"]; + else + self.pers["gamemodeLoadout"] = level.infect_loadouts[self.pers["team"]]; + + self thread onSpawnFinished(); + + level notify ( "spawned_player" ); +} + +onSpawnFinished() +{ + self endon( "death" ); + self endon( "disconnect" ); + self waittill( "spawned_player" ); + + if ( self.pers["team"] == "axis" ) + thread setInfectedMsg(); + + wait 0.05; + + if ( self.pers["team"] == "axis" ) + maps\mp\killstreaks\_killstreaks::clearKillstreaks(); + + if ( matchMakingGame() ) + { + foreach ( player in level.infect_perks ) + { + if ( _hasPerk( player ) ) + { + var_2 = tablelookup( "mp/perktable.csv", 1, player, 8 ); + + if ( !_hasPerk( var_2 ) ) + _setPerk( var_2, 0 ); + } + } + + if ( self.pers["team"] == "allies" ) // SURVIVORS + { + if ( !_hasPerk( "specialty_scavenger" ) ) + { + _setPerk( "specialty_scavenger", 0 ); + var_2 = tablelookup( "mp/perktable.csv", 1, "specialty_scavenger", 8 ); + + if ( !_hasPerk( var_2 ) ) + _setPerk( var_2, 0 ); + } + } + else if ( self.pers["team"] == "axis" ) // INFECTED + { + if ( isDefined( self.isInitialInfected ) && !_hasPerk( "specialty_marathon" ) ) + { + _setPerk( "specialty_marathon", 0 ); + var_2 = tablelookup( "mp/perktable.csv", 1, "specialty_marathon", 8 ); + + if ( !_hasPerk( var_2 ) ) + _setPerk( var_2, 0 ); + } + + if ( !_hasPerk( "specialty_falldamage" ) ) + _setPerk( "specialty_falldamage", 0 ); + + if ( _hasPerk( "specialty_marathon" ) ) + { + _setPerk( "specialty_lightweight", 0 ); + self setMoveSpeedScale( 1.2 ); + } + } + } +} + +setInfectedMsg() +{ + + if ( isDefined( self.isInitialInfected ) ) + { + thread maps\mp\gametypes\_rank::xpEventPopup( &"SPLASHES_FIRST_MERCENARY", ( 1, 0, 0 ), 0.3 ); + } + else if ( isDefined( self.changingToRegularInfected ) ) + { + self.changingToRegularInfected = undefined; + if ( isDefined( self.changingToRegularInfectedByKill ) ) + { + self.changingToRegularInfectedByKill = undefined; + thread maps\mp\gametypes\_rank::xpEventPopup( &"SPLASHES_FIRSTBLOOD" ); + maps\mp\gametypes\_gamescore::givePlayerScore( "first_draft", self ); + thread maps\mp\gametypes\_rank::giveRankXP( "first_draft" ); + } + } + else + { + thread maps\mp\gametypes\_rank::xpEventPopup( &"SPLASHES_DRAFTED", ( 1, 0, 0 ), 0.3 ); + } +} + +chooseFirstInfected() +{ + level endon( "game_ended" ); + level endon( "infect_stopCountdown" ); + + level.infect_allowSuicide = false; + gameFlagWait( "prematch_done" ); + + level.infect_timerDisplay.label = &"MP_DRAFT_STARTS_IN"; + level.infect_timerDisplay setTimer( 15 ); + level.infect_timerDisplay.alpha = 1; + + maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( 15.0 ); + + level.infect_timerDisplay.alpha = 0; + + possibleInfected = []; + + foreach( player in level.players ) + { + if ( player.team == "spectator" ) + continue; + + if ( !player.hasSpawned ) + continue; + + possibleInfected[possibleInfected.size] = player; + } + + firstInfectedPlayer = possibleInfected[ randomInt( possibleInfected.size ) ]; + firstInfectedPlayer setFirstInfected( true ); +} + +setFirstInfected( wasChosen ) +{ + self endon( "disconnect" ); + + if ( wasChosen ) + self.infect_isBeingChosen = true; + + // wait alive + while( !isReallyAlive( self ) || self isUsingRemote() ) + wait( 0.05 ); + + // remove placement item if carrying + if ( IsDefined( self.isCarrying ) && self.isCarrying == true ) + { + self notify( "force_cancel_placement" ); + wait( 0.05 ); + } + + // not while mantling + while ( self IsMantling() ) + wait( 0.05 ); + + // not while in air + while ( !self isOnGround() && !self IsOnLadder() ) + wait( 0.05 ); + + // move to other team + if ( wasChosen ) + { + self maps\mp\gametypes\_menus::addToTeam( "axis" ); + level.infect_choseFirstInfected = true; + self.infect_isBeingChosen = undefined; + + // resynch teams + level notify( "update_game_time" ); + updateTeamScores(); + + // allow suicides now + level.infect_allowSuicide = true; + } + + // store initial + self.isInitialInfected = true; + + // set the gamemodeloadout for giveLoadout() to use + self.pers["gamemodeLoadout"] = level.infect_loadouts["axis_initial"]; + + // remove old TI if it exists + if ( isDefined ( self.setSpawnpoint ) ) + self maps\mp\perks\_perkfunctions::deleteTI( self.setSpawnpoint ); + + // set faux TI to respawn in place + spawnPoint = spawn( "script_model", self.origin ); + spawnPoint.angles = self.angles; + spawnPoint.playerSpawnPos = self.origin; + spawnPoint.notTI = true; + self.setSpawnPoint = spawnPoint; + + // faux spawn + self notify( "faux_spawn" ); + self.faux_spawn_stance = self getStance(); + thread maps\mp\gametypes\_playerlogic::spawnPlayer( true ); + + // store infected + if ( wasChosen ) + level.infect_players[self.name] = true; + + // tell the world! + thread teamPlayerCardSplash( "callout_first_mercenary", self ); + playSoundOnPlayers( "mp_enemy_obj_captured" ); +} + +setInitialToNormalInfected( gotKill ) +{ + level endon( "game_ended" ); + + self.isInitialInfected = undefined; + self.changingToRegularInfected = true; + if ( isDefined( gotKill ) ) + self.changingToRegularInfectedByKill = true; + + // wait till we spawn if we died at the same time + while ( !isReallyAlive( self ) ) + wait( 0.05 ); + + // remove placement item if carrying + if ( IsDefined( self.isCarrying ) && self.isCarrying == true ) + { + self notify( "force_cancel_placement" ); + wait( 0.05 ); + } + + // not while mantling + while ( self IsMantling() ) + wait( 0.05 ); + + // not while in air + while ( !self isOnGround() ) + wait( 0.05 ); + + // Gotta check again, more time has passed, wait till we spawn if we died at the same time + while ( !isReallyAlive( self ) ) + wait( 0.05 ); + + // set the gamemodeloadout for giveLoadout() to use + self.pers["gamemodeLoadout"] = level.infect_loadouts["axis"]; + + // remove old TI if it exists + if ( isDefined ( self.setSpawnpoint ) ) + self maps\mp\perks\_perkfunctions::deleteTI( self.setSpawnpoint ); + + // set faux TI to respawn in place + spawnPoint = spawn( "script_model", self.origin ); + spawnPoint.angles = self.angles; + spawnPoint.playerSpawnPos = self.origin; + spawnPoint.notTI = true; + self.setSpawnPoint = spawnPoint; + + // faux spawn + self notify( "faux_spawn" ); + self.faux_spawn_stance = self getStance(); + + thread maps\mp\gametypes\_playerlogic::spawnPlayer( true ); +} + +onPlayerKilled( eInflictor, attacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration ) +{ + processKill = false; + wasSuicide = false; + + if ( self.team == "allies" && isDefined( attacker ) ) + { + if ( isPlayer( attacker ) && attacker != self ) + processKill = true; + else if ( level.infect_allowSuicide && ( attacker == self || !isPlayer( attacker ) ) ) + { + processKill = true; + wasSuicide = true; + } + } + + if ( processKill ) + { + // track team change for this frame (so explosives can't team kill, now that they changed teams) + self.teamChangedThisFrame = true; + + // move victim to infected + self maps\mp\gametypes\_menus::addToTeam( "axis" ); + + // resynch teams + updateTeamScores(); + + // store infected + level.infect_players[self.name] = true; + + if ( wasSuicide ) + { + // set initial infected to regular infected since a survivor just commited suicide and initial infected is no longer alone + if ( level.infect_teamScores["axis"] > 1 ) + { + foreach( player in level.players ) + { + if ( isDefined( player.isInitialInfected ) ) + player thread setInitialToNormalInfected(); + } + } + } + else + { + // set attacker to regular infected if they were the first infected + if ( isDefined( attacker.isInitialInfected ) ) + attacker thread setInitialToNormalInfected( true ); + else + { + // regular attacker reward + attacker thread maps\mp\gametypes\_rank::xpEventPopup( &"SPLASHES_DRAFTED" ); + maps\mp\gametypes\_gamescore::givePlayerScore( "draft_rogue", attacker, self, true ); + attacker thread maps\mp\gametypes\_rank::giveRankXP( "draft_rogue" ); + } + } + + // generic messages/sounds, and reward survivors + if ( level.infect_teamScores["allies"] > 1 ) + { + playSoundOnPlayers( "mp_enemy_obj_captured", "allies" ); + playSoundOnPlayers( "mp_war_objective_taken", "axis" ); + thread teamPlayerCardSplash( "callout_got_drafted", self, "allies" ); + + if ( !wasSuicide ) + { + thread teamPlayerCardSplash( "callout_drafted_rogue", attacker, "axis" ); + + foreach ( player in level.players ) + { + if( !isReallyAlive( player ) || self.sessionstate == "spectator" ) + continue; + + if ( player.team == "allies" && player != self && distance( player.infect_spawnPos, player.origin ) > 32 ) + { + player thread maps\mp\gametypes\_rank::xpEventPopup( &"SPLASHES_SURVIVOR" ); + maps\mp\gametypes\_gamescore::givePlayerScore( "survivor", player, undefined, true ); + player thread maps\mp\gametypes\_rank::giveRankXP( "survivor" ); + } + } + } + } + // inform/reward last + else if ( level.infect_teamScores["allies"] == 1 ) + { + onFinalSurvivor(); + } + // infected win + else if ( level.infect_teamScores["allies"] == 0 ) + { + onSurvivorsEliminated(); + } + + } +} + +onFinalSurvivor() +{ + playSoundOnPlayers( "mp_obj_captured" ); + foreach ( player in level.players ) + { + if( !isDefined(player) ) + continue; + + if ( player.team == "allies" ) + { + player thread maps\mp\gametypes\_rank::xpEventPopup( &"SPLASHES_FINAL_ROGUE" ); + if ( !level.infect_awardedFinalSurvivor ) + { + if ( player.gameModeJoinedAtStart && isDefined( player.infect_spawnPos ) && distance( player.infect_spawnPos, player.origin ) > 32 ) + { + maps\mp\gametypes\_gamescore::givePlayerScore( "final_rogue", player, undefined, true ); + player thread maps\mp\gametypes\_rank::giveRankXP( "final_rogue" ); + } + + level.infect_awardedFinalSurvivor = true; + } + + thread teamplayercardsplash( "callout_final_rogue", player ); + + if (matchMakingGame() ) + level thread finalsurvivoruav( player ); + + break; + } + } +} + +finalSurvivorUAV( finalPlayer ) +{ + level endon( "game_ended" ); + finalPlayer endon( "disconnect" ); + finalPlayer endon( "eliminated" ); + level endon( "infect_lateJoiner" ); + level thread endUAVonLateJoiner( finalPlayer ); + + removeUAV = false; + level.radarMode["axis"] = "normal_radar"; + foreach ( player in level.players ) + { + if ( player.team == "axis" ) + player.radarMode = "normal_radar"; + } + + //setTeamRadarStrength( "axis", 1 ); + + while( true ) + { + prevPos = finalPlayer.origin; + + wait( 4 ); + if ( removeUAV ) + { + setTeamRadar( "axis", 0 ); + removeUAV = false; + } + + wait( 6 ); + if ( distance( prevPos, finalPlayer.origin ) < 200 ) + { + setTeamRadar( "axis", 1 ); + removeUAV = true; + + foreach ( player in level.players ) + player playLocalSound( "recondrone_tag" ); + } + } +} + +endUAVonLateJoiner( finalPlayer ) +{ + level endon( "game_ended" ); + finalPlayer endon( "disconnect" ); + finalPlayer endon( "eliminated" ); + + while( true ) + { + if ( level.infect_teamScores["allies"] > 1 ) + { + level notify( "infect_lateJoiner" ); + wait( 0.05 ); + setTeamRadar( "axis", 0 ); + break; + } + wait( 0.05 ); + } +} + +onPlayerDisconnect() +{ + level endon( "game_ended" ); + self endon( "eliminated" ); + + self waittill( "disconnect" ); + + // resynch teams + updateTeamScores(); + + // team actions or win condition necessary? + if ( isDefined( self.infect_isBeingChosen ) || level.infect_choseFirstInfected ) + { + if ( level.infect_teamScores["axis"] && level.infect_teamScores["allies"] ) + { + if ( self.team == "allies" && level.infect_teamScores["allies"] == 1 ) + { + // final survivor was abandoned: inform, reward, call uav + onFinalSurvivor(); + } + else if ( self.team == "axis" && level.infect_teamScores["axis"] == 1 ) + { + // final infected was abandoned: inform, set initial + foreach ( player in level.players ) + { + if ( player != self && player.team == "axis" ) + player setFirstInfected( false ); + } + } + } + else if ( level.infect_teamScores["allies"] == 0 ) + { + // no more survivors, infected win + onSurvivorsEliminated(); + } + else if ( level.infect_teamScores["axis"] == 0 ) + { + if ( level.infect_teamScores["allies"] == 1 ) + { + // last survivor wins + level.finalKillCam_winner = "allies"; + level thread maps\mp\gametypes\_gamelogic::endGame( "allies", game[ "end_reason" ][ "axis_eliminated" ] ); + } + else if ( level.infect_teamScores["allies"] > 1 ) + { + // pick a new infected and keep the game going + level.infect_choseFirstInfected = false; + level thread chooseFirstInfected(); + } + } + } + + // clear this regardless on the way out + self.isInitialInfected = undefined; +} + +watchInfectForfeit() +{ + level endon( "game_ended" ); + level.forfeitInProgress = true; + + for (;;) + { + if ( !isDefined( level.forfeitInProgress ) ) + level.forfeitInProgress = true; + + wait 0.05; + } +} + +onDeadEvent( team ) +{ + // override default to supress the normal game ending process + return; +} + +onTimeLimit() +{ + // survivors win + level.finalKillCam_winner = "allies"; + level thread maps\mp\gametypes\_gamelogic::endGame( "allies", game[ "end_reason" ][ "time_limit_reached" ] ); +} + +onSurvivorsEliminated() +{ + // infected win + level.finalKillCam_winner = "axis"; + level thread maps\mp\gametypes\_gamelogic::endGame( "axis", game[ "end_reason" ][ "allies_eliminated" ] ); +} + +getTeamSize( team ) +{ + size = 0; + + foreach( player in level.players ) + { + // Make sure we don't check for spectators + // Also need to check for if they are in killcam, because player session states are set to spectator when killcam happens + if ( player.sessionstate == "spectator" && !player.spectatekillcam ) + continue; + + if( player.team == team ) + size++; + } + + return size; +} + +updateTeamScores() +{ + // survivors + level.infect_teamScores["allies"] = getTeamSize( "allies" ); + game["teamScores"]["allies"] = level.infect_teamScores["allies"]; + setTeamScore( "allies", level.infect_teamScores["allies"] ); + + // infected + level.infect_teamScores["axis"] = getTeamSize( "axis" ); + game["teamScores"]["axis"] = level.infect_teamScores["axis"]; + setTeamScore( "axis", level.infect_teamScores["axis"] ); +} + +setSpecialLoadouts() +{ + level.infect_loadouts["axis"]["loadoutPrimary"] = "throwingknife"; + level.infect_loadouts["axis"]["loadoutPrimaryAttachment"] = "none"; + level.infect_loadouts["axis"]["loadoutPrimaryAttachment2"] = "none"; + level.infect_loadouts["axis"]["loadoutPrimaryCamo"] = "none"; + level.infect_loadouts["axis"]["loadoutSecondary"] = "none"; + level.infect_loadouts["axis"]["loadoutSecondaryAttachment"] = "none"; + level.infect_loadouts["axis"]["loadoutSecondaryAttachment2"] = "none"; + level.infect_loadouts["axis"]["loadoutSecondaryCamo"] = "none"; + level.infect_loadouts["axis"]["loadoutEquipment"] = "throwingknife_mp"; + level.infect_loadouts["axis"]["loadoutOffhand"] = "none"; + level.infect_loadouts["axis"]["loadoutPerk1"] = "specialty_marathon"; + level.infect_loadouts["axis"]["loadoutPerk2"] = "specialty_lightweight"; + level.infect_loadouts["axis"]["loadoutPerk3"] = "specialty_heartbreaker"; + level.infect_loadouts["axis"]["loadoutKillstreak1"] = "none"; + level.infect_loadouts["axis"]["loadoutKillstreak2"] = "none"; + level.infect_loadouts["axis"]["loadoutKillstreak3"] = "none"; + level.infect_loadouts["axis"]["loadoutDeathstreak"] = "specialty_grenadepulldeath"; + + level.infect_loadouts["axis_initial"]["loadoutPrimary"] = "scar"; + level.infect_loadouts["axis_initial"]["loadoutPrimaryAttachment"] = "reflex"; + level.infect_loadouts["axis_initial"]["loadoutPrimaryAttachment2"] = "xmags"; + level.infect_loadouts["axis_initial"]["loadoutPrimaryCamo"] = "none"; + level.infect_loadouts["axis_initial"]["loadoutSecondary"] = "none"; + level.infect_loadouts["axis_initial"]["loadoutSecondaryAttachment"] = "none"; + level.infect_loadouts["axis_initial"]["loadoutSecondaryAttachment2"] = "none"; + level.infect_loadouts["axis_initial"]["loadoutSecondaryCamo"] = "none"; + level.infect_loadouts["axis_initial"]["loadoutEquipment"] = "throwingknife_mp"; + level.infect_loadouts["axis_initial"]["loadoutOffhand"] = "none"; + level.infect_loadouts["axis_initial"]["loadoutPerk1"] = "specialty_marathon"; + level.infect_loadouts["axis_initial"]["loadoutPerk2"] = "specialty_lightweight"; + level.infect_loadouts["axis_initial"]["loadoutPerk3"] = "specialty_bulletaccuracy"; + level.infect_loadouts["axis_initial"]["loadoutKillstreak1"] = "none"; + level.infect_loadouts["axis_initial"]["loadoutKillstreak2"] = "none"; + level.infect_loadouts["axis_initial"]["loadoutKillstreak3"] = "none"; + level.infect_loadouts["axis_initial"]["loadoutDeathstreak"] = "specialty_grenadepulldeath"; + + level.infect_loadouts["allies"]["loadoutPrimary"] = "spas12"; + level.infect_loadouts["allies"]["loadoutPrimaryAttachment"] = "xmags"; + level.infect_loadouts["allies"]["loadoutPrimaryAttachment2"] = "grip"; + level.infect_loadouts["allies"]["loadoutPrimaryCamo"] = "none"; + level.infect_loadouts["allies"]["loadoutSecondary"] = "pp2000"; + level.infect_loadouts["allies"]["loadoutSecondaryAttachment"] = "reflex"; + level.infect_loadouts["allies"]["loadoutSecondaryAttachment2"] = "xmags"; + level.infect_loadouts["allies"]["loadoutSecondaryCamo"] = "none"; + level.infect_loadouts["allies"]["loadoutEquipment"] = "claymore_mp"; + level.infect_loadouts["allies"]["loadoutOffhand"] = "flash_grenade"; + level.infect_loadouts["allies"]["loadoutPerk1"] = "specialty_scavenger"; + level.infect_loadouts["allies"]["loadoutPerk2"] = "specialty_lightweight"; + level.infect_loadouts["allies"]["loadoutPerk3"] = "specialty_heartbreaker"; + level.infect_loadouts["allies"]["loadoutDeathstreak"] = "specialty_null"; +} \ No newline at end of file diff --git a/iw4x/iw4x_00/maps/mp/gametypes/infect.txt b/iw4x/iw4x_00/maps/mp/gametypes/infect.txt new file mode 100644 index 0000000..f63a51b --- /dev/null +++ b/iw4x/iw4x_00/maps/mp/gametypes/infect.txt @@ -0,0 +1 @@ +"MPUI_INF" \ No newline at end of file diff --git a/iw4x/iw4x_00/mp/gameTypesTable.csv b/iw4x/iw4x_00/mp/gameTypesTable.csv index 210d052..8d3df15 100644 --- a/iw4x/iw4x_00/mp/gameTypesTable.csv +++ b/iw4x/iw4x_00/mp/gameTypesTable.csv @@ -1,20 +1,19 @@ -a0,b1,c2,d3 -dm,MPUI_DEATHMATCH,MENU_KILL_EVERYONE_FIRST_PLAYER,weapon_missing_image -war,MPUI_TEAM_DEATHMATCH,MENU_KILL_PLAYERS_ON_THE_OPPOSING,weapon_missing_image -sd,MPUI_SEARCH_AND_DESTROY,MENU_TEAMS_TAKE_TURNS_DEFENDING,weapon_missing_image -sab,MPUI_SABOTAGE,MENU_1_BOMB_IN_THE_CENTER,weapon_missing_image -dom,MPUI_DOMINATION,MENU_CAPTURE_AND_HOLD_THE,weapon_missing_image -koth,MPUI_HEADQUARTERS,MENU_CAPTURE_THE_HEADQUARTERS,weapon_missing_image -oneflag,MPUI_ONE_FLAG,MENU_TEAMS_TAKE_TURNS_CAPTURING,weapon_missing_image -arena,MPUI_ARENA,MENU_ARENA,weapon_missing_image -dd,MPUI_DD,MENU_DD,weapon_missing_image -vip,MPUI_VIP,MENU_VIP,weapon_missing_image -ctf,MPUI_CTF,MENU_CTF_DESC,weapon_missing_image -gtnw,MPUI_GTNW,MENU_GTNW_DESC,weapon_missing_image -ss,MPUI_SHARPSHOOTER,MENU_SHARPSHOOTER,weapon_missing_image -gg,MPUI_GUNGAME,MENU_GUNGAME,weapon_missing_image -oitc,MPUI_OITC,MENU_OITC,weapon_missing_image -conf,MPUI_CONF,MENU_CONF,weapon_missing_image -infected,MPUI_INF,MENU_INF,weapon_missing_image -cranked,MPUI_CRANKED,MENU_CRANKED,weapon_missing_image -tdef,MPUI_TDEF,MENU_TDEF,weapon_missing_image +a0,b1,c2,d3,e4,f5 +dm,MPUI_DEATHMATCH,MENU_KILL_EVERYONE_FIRST_PLAYER,weapon_missing_image,1,1 +war,MPUI_TEAM_DEATHMATCH,MENU_KILL_PLAYERS_ON_THE_OPPOSING,weapon_missing_image,1,1 +sd,MPUI_SEARCH_AND_DESTROY,MENU_TEAMS_TAKE_TURNS_DEFENDING,weapon_missing_image,1,1 +sab,MPUI_SABOTAGE,MENU_1_BOMB_IN_THE_CENTER,weapon_missing_image,1,1 +dom,MPUI_DOMINATION,MENU_CAPTURE_AND_HOLD_THE,weapon_missing_image,1,1 +koth,MPUI_HEADQUARTERS,MENU_CAPTURE_THE_HEADQUARTERS,weapon_missing_image,1,1 +oneflag,MPUI_ONE_FLAG,MENU_TEAMS_TAKE_TURNS_CAPTURING,weapon_missing_image,1,1 +arena,MPUI_ARENA,MENU_ARENA,weapon_missing_image,1,1 +dd,MPUI_DD,MENU_DD,weapon_missing_image,1,1 +vip,MPUI_VIP,MENU_VIP,weapon_missing_image,1,1 +ctf,MPUI_CTF,MENU_CTF_DESC,weapon_missing_image,1,1 +gtnw,MPUI_GTNW,MENU_GTNW_DESC,weapon_missing_image,1,1 +gun,MPUI_GUNGAME,MENU_GUNGAME,weapon_missing_image,0,0 +oic,MPUI_OITC,MENU_OITC,weapon_missing_image,0,0 +conf,MPUI_CONF,MENU_CONF,weapon_missing_image,1,1 +infect,MPUI_INF,MENU_INF,weapon_missing_image,0,0 +cranked,MPUI_CRANKED,MENU_CRANKED,weapon_missing_image,1,1 +tdef,MPUI_TDEF,MENU_TDEF,weapon_missing_image,1,1 diff --git a/iw4x/iw4x_00/mp/splashtable.csv b/iw4x/iw4x_00/mp/splashtable.csv new file mode 100644 index 0000000..192596e --- /dev/null +++ b/iw4x/iw4x_00/mp/splashtable.csv @@ -0,0 +1,633 @@ +headshot,SPLASHES_HEADSHOT,SPLASHES_HEADSHOT_DESC,crosshair_red,1.5,0.75,0.25,0.25,1,mp_last_stand,dummy,splash +assistedsuicide,SPLASHES_ASSISTEDSUICIDE,SPLASHES_ASSISTEDSUICIDE_DESC,,1.5,0.75,0.25,0.25,1,mp_last_stand,,splash +longshot,SPLASHES_LONGSHOT,SPLASHES_LONGSHOT_DESC,crosshair_red,1.5,0.75,0.25,0.25,1,mp_last_stand,,splash +execution,SPLASHES_EXECUTION,SPLASHES_EXECUTION_DESC,crosshair_red,1.5,0.75,0.25,0.25,1,mp_last_stand,,splash +avenger,SPLASHES_AVENGER,SPLASHES_AVENGER_DESC,,1.5,0.75,0.25,0.25,1,mp_last_stand,,splash +defender,SPLASHES_DEFENDER,SPLASHES_DEFENDER_DESC,,1.5,0.75,0.25,0.25,1,mp_last_stand,,splash +posthumous,SPLASHES_POSTHUMOUS,SPLASHES_POSTHUMOUS_DESC,,1.5,0.75,0.25,0.25,1,mp_last_stand,,splash +revenge,SPLASHES_REVENGE,SPLASHES_REVENGE_DESC,,1.5,0.75,0.25,0.25,1,mp_last_stand,,splash +doublekill,SPLASHES_DOUBLEKILL,SPLASHES_DOUBLEKILL_DESC,,1.5,0.75,0.25,0.25,1,mp_last_stand,,splash +triplekill,SPLASHES_TRIPLEKILL,SPLASHES_TRIPLEKILL_DESC,,1.5,0.75,0.25,0.25,1,mp_last_stand,,splash +multikill,SPLASHES_MULTIKILL,SPLASHES_MULTIKILL_DESC,,1.5,0.75,0.25,0.25,1,mp_last_stand,,splash +firstblood,SPLASHES_FIRSTBLOOD,SPLASHES_FIRSTBLOOD_DESC,crosshair_red,1.5,0.75,0.25,0.25,1,mp_last_stand,,splash +buzzkill,SPLASHES_BUZZKILL,SPLASHES_BUZZKILL_DESC,,1.5,0.75,0.25,0.25,1,mp_last_stand,,splash +comeback,SPLASHES_COMEBACK,SPLASHES_COMEBACK_DESC,,1.5,0.75,0.25,0.25,1,mp_last_stand,,splash +flagreturn,SPLASHES_FLAGRETURN,SPLASHES_FLAGRETURN_DESC,,1.5,0.75,0.25,0.25,1,mp_last_stand,,splash +flagpickup,SPLASHES_FLAGPICKUP,SPLASHES_FLAGPICKUP_DESC,,1.5,0.75,0.25,0.25,1,mp_last_stand,,splash +shield_assist,SPLASHES_SHIELDASSIST,SPLASHES_SHIELDASSIST_DESC,,1.5,0.75,0.25,0.25,1,mp_last_stand,,splash +time_added,SPLASHES_TIME_ADDED,SPLASHES_TIME_ADDED_DESC,,4,1,0.8,0.4,1,mp_last_stand,,splash +uav,MP_KILLSTREAK_N,MP_EARNED_UAV,,2.5,0.25,0.75,0.25,1,mp_killstreak_radar,uavrecon,killstreak +airstrike,MP_KILLSTREAK_N,MP_EARNED_AIRSTRIKE,,2.5,0.25,0.75,0.25,1,mp_killstreak_jet,airstrike,killstreak +helicopter,MP_KILLSTREAK_N,MP_EARNED_HELICOPTER,,2.5,0.25,0.75,0.25,1,mp_killstreak_heli,helisupport,killstreak +helicopter_minigun,MP_KILLSTREAK_N,MP_EARNED_HELICOPTER_MINIGUN,,2.5,0.25,0.75,0.25,1,mp_killstreak_heli,achieve_heli_mg,killstreak +helicopter_mk19,MP_KILLSTREAK_N,MP_EARNED_HELICOPTER_MK19,,2.5,0.25,0.75,0.25,1,mp_killstreak_heli,achieve_heli_gl,killstreak +helicopter_blackbox,MP_KILLSTREAK_N,MP_EARNED_HELICOPTER_BLACKBOX,,2.5,0.25,0.75,0.25,1,mp_killstreak_heli,helisupport,killstreak +helicopter_flares,MP_KILLSTREAK_N,MP_EARNED_HELICOPTER_FLARES,,2.5,0.25,0.75,0.25,1,mp_killstreak_heli,helisupport,killstreak +ac130,MP_KILLSTREAK_N,MP_EARNED_AC130,,2.5,0.25,0.75,0.25,1,mp_killstreak_jet,achieve_ac130,killstreak +predator_missile,MP_KILLSTREAK_N,MP_EARNED_PREDATOR_MISSILE,,2.5,0.25,0.75,0.25,1,mp_killstreak_jet,achieve_missile,killstreak +nuke,MP_KILLSTREAK_N,MP_EARNED_NUKE,,2.5,0.25,0.75,0.25,1,mp_killstreak_heli,achieve_nuke,killstreak +double_uav,MP_KILLSTREAK_N,MP_EARNED_DOUBLE_UAV,,2.5,0.25,0.75,0.25,1,mp_killstreak_radar,achieve_a_uav,killstreak +auto_shotgun,MP_KILLSTREAK_N,MP_EARNED_AUTO_SHOTGUN,,2.5,0.25,0.75,0.25,1,mp_killstreak_heli,achieve_autoshotgun,killstreak +precision_airstrike,MP_KILLSTREAK_N,MP_EARNED_PRECISION_AIRSTRIKE,,2.5,0.25,0.75,0.25,1,mp_killstreak_jet,achieve_p_airstrike,killstreak +stealth_airstrike,MP_KILLSTREAK_N,MP_EARNED_STEALTH_AIRSTRIKE,,2.5,0.25,0.75,0.25,1,mp_killstreak_jet,airstrike,killstreak +harrier_airstrike,MP_KILLSTREAK_N,MP_EARNED_HARRIER_AIRSTRIKE,,2.5,0.25,0.75,0.25,1,mp_killstreak_jet,airstrike,killstreak +super_airstrike,MP_KILLSTREAK_N,MP_EARNED_SUPER_AIRSTRIKE,,2.5,0.25,0.75,0.25,1,mp_killstreak_jet,airstrike,killstreak +counter_uav,MP_KILLSTREAK_N,MP_EARNED_COUNTER_UAV,,2.5,0.25,0.75,0.25,1,mp_killstreak_radar,achieve_c_uav,killstreak +sentry,MP_KILLSTREAK_N,MP_EARNED_AUTO_SENTRY,,2.5,0.25,0.75,0.25,1,mp_killstreak_radar,achieve_sentrygun,killstreak +sentry_gl,MP_KILLSTREAK_N,MP_EARNED_AUTO_SENTRY_GL,,2.5,0.25,0.75,0.25,1,mp_killstreak_radar,achieve_sentry_gl,killstreak +thumper,MP_KILLSTREAK_N,MP_EARNED_THUMPER,,2.5,0.25,0.75,0.25,1,mp_killstreak_heli,achieve_thumper,killstreak +airdrop,MP_KILLSTREAK_N,MP_EARNED_AIRDROP,,2.5,0.25,0.75,0.25,1,mp_killstreak_jet,uavrecon,killstreak +tank,MP_KILLSTREAK_N,MP_EARNED_TANK,,2.5,0.25,0.75,0.25,1,mp_killstreak_jet,achieve_tank,killstreak +emp,MP_KILLSTREAK_N,MP_EARNED_EMP,,2.5,0.25,0.75,0.25,1,mp_killstreak_jet,achieve_tank,killstreak +airdrop_sentry_minigun,MP_KILLSTREAK_N,MP_EARNED_SENTRY_AIRDROP,,2.5,0.25,0.75,0.25,1,mp_killstreak_jet,uavrecon,killstreak +airdrop_predator_missile,MP_KILLSTREAK_N,MP_EARNED_PREDATOR_AIRDROP,,2.5,0.25,0.75,0.25,1,mp_killstreak_jet,uavrecon,killstreak +littlebird_support,MP_KILLSTREAK_N,MP_EARNED_LITTLEBIRD_SUPPORT,,2.5,0.25,0.75,0.25,1,mp_killstreak_jet,uavrecon,killstreak +airdrop_mega,MP_KILLSTREAK_N,MP_EARNED_AIRDROP_MEGA,,2.5,0.25,0.75,0.25,1,mp_killstreak_jet,uavrecon,killstreak +uav_pickup,KILLSTREAKS_UAV,MP_EARNED_UAV,specialty_uav,2.5,0.25,0.75,0.25,1,mp_killstreak_radar,uavrecon,killstreak_pickup +helicopter_pickup,KILLSTREAKS_HELICOPTER,MP_EARNED_HELICOPTER,specialty_helicopter_support,2.5,0.25,0.75,0.25,1,mp_killstreak_heli,helisupport,killstreak_pickup +helicopter_minigun_pickup,KILLSTREAKS_HELICOPTER_MINIGUN,MP_EARNED_HELICOPTER_MINIGUN,specialty_cobra_gunner,2.5,0.25,0.75,0.25,1,mp_killstreak_heli,achieve_heli_mg,killstreak_pickup +helicopter_flares_pickup,KILLSTREAKS_PAVELOW,MP_EARNED_HELICOPTER_FLARES,specialty_helicopter_flares,2.5,0.25,0.75,0.25,1,mp_killstreak_heli,helisupport,killstreak_pickup +ac130_pickup,KILLSTREAKS_AC130,MP_EARNED_AC130,specialty_ac130,2.5,0.25,0.75,0.25,1,mp_killstreak_jet,achieve_ac130,killstreak_pickup +predator_missile_pickup,KILLSTREAKS_PREDATOR_MISSILE,MP_EARNED_PREDATOR_MISSILE,specialty_predator_missile,2.5,0.25,0.75,0.25,1,mp_killstreak_jet,achieve_missile,killstreak_pickup +nuke_pickup,KILLSTREAKS_TACTICAL_NUKE,MP_EARNED_NUKE,specialty_nuke,2.5,0.25,0.75,0.25,1,mp_killstreak_heli,achieve_nuke,killstreak_pickup +precision_airstrike_pickup,KILLSTREAKS_PRECISION_AIRSTRIKE,MP_EARNED_PRECISION_AIRSTRIKE,specialty_precision_airstrike,2.5,0.25,0.75,0.25,1,mp_killstreak_jet,achieve_p_airstrike,killstreak_pickup +stealth_airstrike_pickup,KILLSTREAKS_STEALTH_AIRSTRIKE,MP_EARNED_STEALTH_AIRSTRIKE,specialty_stealth_bomber,2.5,0.25,0.75,0.25,1,mp_killstreak_jet,airstrike,killstreak_pickup +harrier_airstrike_pickup,KILLSTREAKS_HARRIER_AIRSTRIKE,MP_EARNED_HARRIER_AIRSTRIKE,specialty_harrier_strike,2.5,0.25,0.75,0.25,1,mp_killstreak_jet,airstrike,killstreak_pickup +counter_uav_pickup,KILLSTREAKS_COUNTER_UAV,MP_EARNED_COUNTER_UAV,specialty_counter_uav,2.5,0.25,0.75,0.25,1,mp_killstreak_radar,achieve_c_uav,killstreak_pickup +sentry_pickup,KILLSTREAKS_SENTRY,MP_EARNED_AUTO_SENTRY,specialty_airdrop_sentry_minigun,2.5,0.25,0.75,0.25,1,mp_killstreak_radar,achieve_sentrygun,killstreak_pickup +airdrop_pickup,KILLSTREAKS_AIRDROP,MP_EARNED_AIRDROP,specialty_carepackage,2.5,0.25,0.75,0.25,1,mp_killstreak_jet,uavrecon,killstreak_pickup +emp_pickup,KILLSTREAKS_EMP,MP_EARNED_EMP,specialty_emp,2.5,0.25,0.75,0.25,1,mp_killstreak_jet,achieve_tank,killstreak_pickup +airdrop_sentry_minigun_pickup,KILLSTREAKS_AIRDROP_SENTRY,MP_EARNED_SENTRY_AIRDROP,specialty_airdrop_sentry_minigun,2.5,0.25,0.75,0.25,1,mp_killstreak_jet,uavrecon,killstreak_pickup +airdrop_mega_pickup,KILLSTREAKS_AIRDROP_MEGA,MP_EARNED_AIRDROP_MEGA,specialty_carepackage,2.5,0.25,0.75,0.25,1,mp_killstreak_jet,uavrecon,killstreak_pickup +caused_defcon,MP_CAUSED_DEFCON,MP_EARNED_MEGADROP,,2.5,0.25,0.75,0.25,1,mp_killstreak_jet,uavrecon,killstreak_pickup +captured_nuke,SPLASHES_CAPTURED_NUKE,SPLASHES_DESC_CAPTURED_NUKE,,1.5,0.75,0.25,0.25,1,mp_last_stand,,splash +capture,SPLASHES_CAPTURE,SPLASHES_CAPTURE_DESC,,1.5,0.75,0.25,0.25,1,mp_last_stand,,splash +defend,SPLASHES_DEFEND,SPLASHES_DEFEND_DESC,,1.5,0.75,0.25,0.25,1,mp_last_stand,,splash +assault,SPLASHES_ASSAULT,SPLASHES_ASSAULT_DESC,,1.5,0.75,0.25,0.25,1,mp_last_stand,,splash +flag_capture,SPLASHES_FLAG_CAPTURED,SPLASHES_FLAG_CAPTURED_DESC,,1.5,0.75,0.25,0.25,1,mp_last_stand,,splash +flag_defend,SPLASHES_FLAG_DEFEND,SPLASHES_FLAG_DEFEND_DESC,,1.5,0.75,0.25,0.25,1,mp_last_stand,,splash +flag_assault,SPLASHES_FLAG_ASSAULT,SPLASHES_FLAG_ASSAULT_DESC,,1.5,0.75,0.25,0.25,1,mp_last_stand,,splash +plant,SPLASHES_PLANT,SPLASHES_PLANT_DESC,,1.5,0.75,0.25,0.25,1,mp_last_stand,,splash +defuse,SPLASHES_DEFUSE,SPLASHES_DEFUSE_DESC,,1,0.75,0.25,0.25,1,mp_last_stand,,splash +sab_overtime,SPLASHES_SAB_OVERTIME,SPLASHES_SAB_OVERTIME_DESC,,3,0.75,0.25,0.25,1,mp_last_stand,,splash +gtnw_overtime,SPLASHES_GTNW_OVERTIME,SPLASHES_GTNW_OVERTIME_DESC,,3,0.75,0.25,0.25,1,mp_last_stand,,splash +specialty_grenadepulldeath,PERKS_MARTYRDOM,SPLASHES_MARTYRDOM_DESC,specialty_grenadepulldeath,2.5,0.75,0.25,0.25,1,mp_last_stand,,splash +specialty_finalstand,PERKS_FINALSTAND,SPLASHES_FINALSTAND_DESC,specialty_finalstand,2.5,0.75,0.25,0.25,1,mp_last_stand,,splash +specialty_combathigh,PERKS_COMBATHIGH,SPLASHES_COMBATHIGH_DESC,specialty_painkiller,2.5,0.75,0.25,0.25,1,mp_last_stand,,splash +specialty_endgame,PERKS_ENDGAME,SPLASHES_ENDGAME_DESC,specialty_endgame,2.5,0.75,0.25,0.25,1,mp_last_stand,,splash +specialty_c4death,PERKS_C4DEATH,SPLASHES_C4DEATH_DESC,specialty_c4_death,2.5,0.75,0.25,0.25,1,mp_last_stand,,splash +specialty_copycat,PERKS_COPYCAT,SPLASHES_COPYCAT_DESC,specialty_copycat,2.5,0.75,0.25,0.25,1,mp_last_stand,,splash +sudden_death,MP_SUDDEN_DEATH,MP_NO_RESPAWN,,3,0.75,0.25,0.25,1,mp_last_stand,,splash +knifethrow,SPLASHES_KNIFETHROW,SPLASHES_KNIFETHROW_DESC,crosshair_red,1.5,0.75,0.25,0.25,1,mp_last_stand,,splash +stuck_semtex,SPLASHES_STUCK_SEMTEX,SPLASHES_STUCK_SEMTEX_DESC,crosshair_red,1.5,0.75,0.25,0.25,1,mp_last_stand,,splash +hijacker,SPLASHES_HIJACKER,SPLASHES_HIJACKER_DESC,,2.5,0.75,0.25,0.25,1,mp_last_stand,,splash +sharepackage,SPLASHES_SHAREPACKAGE,SPLASHES_SHAREPACKAGE_DESC,,2.5,0.75,0.25,0.25,1,mp_last_stand,,splash +rested,SPLASHES_RESTED,SPLASHES_RESTED_DESC,,4,1,0.4,0,1,mp_bonus_start,,splash +rested_done,SPLASHES_RESTED_DONE,SPLASHES_RESTED_DONE_DESC,,2.5,1,0.4,0,1,mp_bonus_end,,splash +semtex_stuck,SPLASHES_SEMTEX_STUCK,SPLASHES_SEMTEX_STUCK,,2.5,0.6,0.6,1,1,mp_defcon_down,,playercard_splash +hijacked_airdrop,SPLASHES_HIJACKED_AIRDROP,SPLASHES_HIJACKED_AIRDROP,,2.5,0.6,0.6,1,1,mp_defcon_down,,playercard_splash +hijacked_sentry,SPLASHES_HIJACKED_SENTRY,SPLASHES_HIJACKED_SENTRY,,2.5,0.6,0.6,1,1,mp_defcon_down,,playercard_splash +giveaway_airdrop,SPLASHES_GIVEAWAY_AIRDROP,SPLASHES_GIVEAWAY_AIRDROP,,2.5,0.6,0.6,1,1,mp_bonus_start,,playercard_splash +giveaway_sentry,SPLASHES_GIVEAWAY_SENTRY,SPLASHES_GIVEAWAY_SENTRY,,2.5,0.6,0.6,1,1,mp_bonus_start,,playercard_splash +hijacked_emergency_airdrop,SPLASHES_HIJACKED_EMERGENCY_AIRDROP,SPLASHES_HIJACKED_EMERGENCY_AIRDROP,,2.5,0.6,0.6,1,1,mp_defcon_down,,playercard_splash +two_from_defcon,SPLASHES_TWO_FROM_DEFCON,SPLASHES_TWO_FROM_DEFCON,,4,0.6,0.6,1,1,null,,playercard_splash +one_from_defcon,SPLASHES_ONE_FROM_DEFCON,SPLASHES_ONE_FROM_DEFCON,,4,0.6,0.6,1,1,null,,playercard_splash +changed_defcon,SPLASHES_CHANGED_DEFCON,SPLASHES_CHANGED_DEFCON,,4,0.6,0.6,1,1,null,,playercard_splash +used_ac130,KILLSTREAKS_AC130,KILLSTREAKS_AC130,,4,1,1,1,1,null,,playercard_splash +used_helicopter_minigun,KILLSTREAKS_HELICOPTER_MINIGUN,KILLSTREAKS_HELICOPTER_MINIGUN,,4,1,1,1,1,null,,playercard_splash +used_emp,KILLSTREAKS_EMP,KILLSTREAKS_EMP,,4,1,1,1,1,null,,playercard_splash +used_nuke,KILLSTREAKS_TACTICAL_NUKE,KILLSTREAKS_TACTICAL_NUKE,,4,1,1,1,1,null,,playercard_splash +used_helicopter_flares,KILLSTREAKS_PAVELOW,KILLSTREAKS_PAVELOW,,4,1,1,1,1,null,,playercard_splash +used_airdrop_mega,KILLSTREAKS_AIRDROP_MEGA,KILLSTREAKS_AIRDROP_MEGA,,4,1,1,1,1,null,,playercard_splash +used_stealth_airstrike,KILLSTREAKS_STEALTH_AIRSTRIKE,KILLSTREAKS_STEALTH_AIRSTRIKE,,4,1,1,1,1,null,,playercard_splash +callout_3xkill,SPLASHES_TRIPLEKILL,SPLASHES_TRIPLEKILL,,4,1,1,1,1,null,,playercard_splash +callout_3xpluskill,SPLASHES_MULTIKILL,SPLASHES_MULTIKILL,,4,1,1,1,1,null,,playercard_splash +callout_bombplanted,SPLASHES_BOMB_PLANTED,SPLASHES_BOMB_PLANTED,,4,1,1,1,1,null,,playercard_splash +callout_bombdefused,SPLASHES_BOMB_DEFUSED,SPLASHES_BOMB_DEFUSED,,4,1,1,1,1,null,,playercard_splash +callout_firstblood,SPLASHES_FIRSTBLOOD,SPLASHES_FIRSTBLOOD,,4,1,1,1,1,null,,playercard_splash +callout_grabbedtheflag,SPLASHES_GRABBED_THE_FLAG,SPLASHES_GRABBED_THE_FLAG,,4,1,1,1,1,null,,playercard_splash +callout_lastenemyalive,SPLASHES_LAST_ENEMY_ALIVE,SPLASHES_LAST_ENEMY_ALIVE,,4,1,1,1,1,null,,playercard_splash +callout_lastteammemberalive,SPLASHES_LAST_TEAM_MEMBER_ALIVE,SPLASHES_LAST_TEAM_MEMBER_ALIVE,,4,1,1,1,1,null,,playercard_splash +callout_destroyed_helicopter_flares,SPLASHES_DESTROYED_PAVELOW,SPLASHES_DESTROYED_PAVELOW,,4,1,1,1,1,null,,playercard_splash +callout_destroyed_helicopter_minigun,SPLASHES_DESTROYED_MINIGUNNER,SPLASHES_DESTROYED_MINIGUNNER,,4,1,1,1,1,null,,playercard_splash +callout_destroyed_helicopter,SPLASHES_DESTROYED_HELICOPTER,SPLASHES_DESTROYED_HELICOPTER,,4,1,1,1,1,null,,playercard_splash +callout_destroyed_uav,SPLASHES_DESTROYED_UAV,SPLASHES_DESTROYED_UAV,,4,1,1,1,1,null,,playercard_splash +callout_destroyed_counter_uav,SPLASHES_DESTROYED_COUNTER_UAV,SPLASHES_DESTROYED_COUNTER_UAV,,4,1,1,1,1,null,,playercard_splash +callout_destroyed_harrier,SPLASHES_DESTROYED_HARRIER,SPLASHES_DESTROYED_HARRIER,,4,1,1,1,1,null,,playercard_splash +callout_destroyed_ac130,SPLASHES_DESTROYED_AC130,SPLASHES_DESTROYED_AC130,,4,1,1,1,1,null,,playercard_splash +callout_flagreturn,SPLASHES_FLAG_RETURN,SPLASHES_FLAG_RETURN,,4,1,1,1,1,null,,playercard_splash +callout_flagcapture,SPLASHES_FLAG_CAPTURE,SPLASHES_FLAG_CAPTURE,,4,1,1,1,1,null,,playercard_splash +callout_flagpickup_enemy,SPLASHES_FLAG_PICKUP_ENEMY,SPLASHES_FLAG_PICKUP_ENEMY,,4,1,1,1,1,null,,playercard_splash +callout_flagpickup_friendly,SPLASHES_FLAG_PICKUP_FRIENDLY,SPLASHES_FLAG_PICKUP_FRIENDLY,,4,1,1,1,1,null,,playercard_splash +callout_flagpickup,SPLASHES_FLAG_PICKUP,SPLASHES_FLAG_PICKUP,,4,1,1,1,1,null,,playercard_splash +callout_capturedhq,SPLASHES_CAPTURED_HQ,SPLASHES_CAPTURED_HQ,,4,1,1,1,1,null,,playercard_splash +callout_destroyedhq,SPLASHES_DESTROYED_HQ,SPLASHES_DESTROYED_HQ,,4,1,1,1,1,null,,playercard_splash +callout_bombtaken,SPLASHES_BOMB_TAKEN,SPLASHES_BOMB_TAKEN,,4,1,1,1,1,null,,playercard_splash +callout_bombdropped,SPLASHES_BOMB_DROPPED,SPLASHES_BOMB_DROPPED,,4,1,1,1,1,null,,playercard_splash +callout_securedposition,SPLASHES_SECURED_POSITION,SPLASHES_SECURED_POSITION,,4,1,1,1,1,null,,playercard_splash +callout_securedposition_a,SPLASHES_SECURED_POSITION_A,SPLASHES_SECURED_POSITION_A,,4,1,1,1,1,null,,playercard_splash +callout_securedposition_b,SPLASHES_SECURED_POSITION_B,SPLASHES_SECURED_POSITION_B,,4,1,1,1,1,null,,playercard_splash +callout_securedposition_c,SPLASHES_SECURED_POSITION_C,SPLASHES_SECURED_POSITION_C,,4,1,1,1,1,null,,playercard_splash +callout_killcarrier,SPLASHES_KILLED_CARRIER,SPLASHES_KILLED_CARRIER,,4,1,1,1,1,null,,playercard_splash +callout_got_drafted,SPLASHES_GOT_DRAFTED,SPLASHES_GOT_DRAFTED,,4,1,1,1,1,null,,playercard_splash +callout_drafted_rogue,SPLASHES_DRAFTED_ROGUE,SPLASHES_DRAFTED_ROGUE,,4,1,1,1,1,null,,playercard_splash +callout_final_rogue,SPLASHES_FINAL_ROGUE,SPLASHES_FINAL_ROGUE,,4,1,1,1,1,null,,playercard_splash +callout_first_mercenary,SPLASHES_FIRST_MERCENARY,SPLASHES_FIRST_MERCENARY,,4,1,1,1,1,null,,playercard_splash +callout_top_gun_rank,SPLASHES_TOP_GUN_RANK,SPLASHES_TOP_GUN_RANK,,4,1,1,1,1,null,,playercard_splash +copied,SPLASHES_COPIED,SPLASHES_COPIED,,3,0.75,0.25,0.25,1,null,,playercard_splash +revived,SPLASHES_REVIVED,SPLASHES_REVIVED,,3,0.75,0.25,0.25,1,null,,playercard_splash +callout_time_added,SPLASHES_TIMEADDED,SPLASHES_TIMEADDED,,4,1,1,1,1,null,,playercard_splash +callout_destroyed_objective,SPLASHES_DESTROYED_OBJECTIVE,SPLASHES_DESTROYED_OBJECTIVE,,4,1,1,1,1,null,,playercard_splash +callout_saved_objective,SPLASHES_SAVED_OBJECTIVE,SPLASHES_SAVED_OBJECTIVE,,4,1,1,1,1,null,,playercard_splash +heroic,SPLASHES_HEROIC,SPLASHES_HEROIC_DESC,,1.5,0.75,0.25,0.25,1,mp_last_stand,,splash +denied,SPLASHES_DENIED,SPLASHES_DENIED_DESC,,2.5,0.75,0.25,0.25,1,mp_last_stand,,splash +destroyed_insertion,SPLASHES_DESTROYED_INSERTION,SPLASHES_DESTROYED_INSERTION,,2.5,0.75,0.25,0.25,1,mp_defcon_down,,playercard_splash +reviver,SPLASHES_REVIVER,SPLASHES_REVIVER_DESC,,1.5,0.75,0.25,0.25,1,mp_last_stand,,splash +one_shot_kill,SPLASHES_ONE_SHOT_KILL,SPLASHES_ONE_SHOT_KILL_DESC,crosshair_red,1.5,0.75,0.25,0.25,1,mp_last_stand,,splash +ninja_defuse,SPLASHES_NINJA_DEFUSE,SPLASHES_NINJA_DEFUSE_DESC,,1.5,0.75,0.25,0.25,1,mp_last_stand,,splash +promotion,RANK_PROMOTED,RANK_PROMOTED,,4,0.75,0.25,0.25,1,mp_level_up,,promotion +ch_marksman_m16,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_m16,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_famas,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_famas,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_tavor,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_tavor,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_fal,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_fal,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_ak47,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_ak47,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_m4,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_m4,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_masada,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_masada,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_scar,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_scar,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_fn2000,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_fn2000,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_kriss,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_kriss,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_ump45,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_ump45,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_uzi,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_uzi,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_p90,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_p90,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_mp5k,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_mp5k,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_rpd,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_rpd,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_sa80,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_sa80,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_aug,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_aug,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_striker,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_striker,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_m1014,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_m1014,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_aa12,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_aa12,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_model1887,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_model1887,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_ranger,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_ranger,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_spas12,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_spas12,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_cheytac,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_cheytac,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_m21,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_m21,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_barrett,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_barrett,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_wa2000,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_wa2000,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_beretta,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_beretta,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_usp,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_usp,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_deserteagle,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_deserteagle,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_coltanaconda,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_coltanaconda,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_tmp,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_tmp,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_ump45,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_ump45,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_glock,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_glock,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_beretta393,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_beretta393,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_pp2000,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_pp2000,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_m240,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_m240,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_mg4,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_mg4,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_at4,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_rpg,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_javelin,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_m79,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marksman_stinger,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_m16_fmj,#,SPLASHES_UNLOCKED_EXTENDED_MAGS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_famas_fmj,#,SPLASHES_UNLOCKED_EXTENDED_MAGS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_tavor_fmj,#,SPLASHES_UNLOCKED_EXTENDED_MAGS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_fal_fmj,#,SPLASHES_UNLOCKED_EXTENDED_MAGS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_ak47_fmj,#,SPLASHES_UNLOCKED_EXTENDED_MAGS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_m4_fmj,#,SPLASHES_UNLOCKED_EXTENDED_MAGS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_masada_fmj,#,SPLASHES_UNLOCKED_EXTENDED_MAGS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_scar_fmj,#,SPLASHES_UNLOCKED_EXTENDED_MAGS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_fn2000_fmj,#,SPLASHES_UNLOCKED_EXTENDED_MAGS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_kriss_fmj,#,SPLASHES_UNLOCKED_EXTENDED_MAGS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_ump45_fmj,#,SPLASHES_UNLOCKED_EXTENDED_MAGS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_uzi_fmj,#,SPLASHES_UNLOCKED_EXTENDED_MAGS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_p90_fmj,#,SPLASHES_UNLOCKED_EXTENDED_MAGS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_mp5k_fmj,#,SPLASHES_UNLOCKED_EXTENDED_MAGS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_rpd_fmj,#,SPLASHES_UNLOCKED_EXTENDED_MAGS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_sa80_fmj,#,SPLASHES_UNLOCKED_EXTENDED_MAGS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_mg4_fmj,#,SPLASHES_UNLOCKED_EXTENDED_MAGS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_aug_fmj,#,SPLASHES_UNLOCKED_EXTENDED_MAGS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_cheytac_fmj,#,SPLASHES_UNLOCKED_EXTENDED_MAGS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_m21_fmj,#,SPLASHES_UNLOCKED_EXTENDED_MAGS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_barrett_fmj,#,SPLASHES_UNLOCKED_EXTENDED_MAGS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_wa2000_fmj,#,SPLASHES_UNLOCKED_EXTENDED_MAGS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_m16_silencer,#,SPLASHES_UNLOCKED_HEARTBEAT,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_famas_silencer,#,SPLASHES_UNLOCKED_HEARTBEAT,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_tavor_silencer,#,SPLASHES_UNLOCKED_HEARTBEAT,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_fal_silencer,#,SPLASHES_UNLOCKED_HEARTBEAT,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_ak47_silencer,#,SPLASHES_UNLOCKED_HEARTBEAT,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_m4_silencer,#,SPLASHES_UNLOCKED_HEARTBEAT,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_masada_silencer,#,SPLASHES_UNLOCKED_HEARTBEAT,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_scar_silencer,#,SPLASHES_UNLOCKED_HEARTBEAT,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_fn2000_silencer,#,SPLASHES_UNLOCKED_HEARTBEAT,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_kriss_silencer,#,SPLASHES_UNLOCKED_HEARTBEAT,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_ump45_silencer,#,SPLASHES_UNLOCKED_HEARTBEAT,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_uzi_silencer,#,SPLASHES_UNLOCKED_HEARTBEAT,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_p90_silencer,#,SPLASHES_UNLOCKED_HEARTBEAT,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_mp5k_silencer,#,SPLASHES_UNLOCKED_HEARTBEAT,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_rpd_silencer,#,SPLASHES_UNLOCKED_HEARTBEAT,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_sa80_silencer,#,SPLASHES_UNLOCKED_HEARTBEAT,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_mg4_silencer,#,SPLASHES_UNLOCKED_HEARTBEAT,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_aug_silencer,#,SPLASHES_UNLOCKED_HEARTBEAT,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_cheytac_silencer,#,SPLASHES_UNLOCKED_HEARTBEAT,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_m21_silencer,#,SPLASHES_UNLOCKED_HEARTBEAT,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_barrett_silencer,#,SPLASHES_UNLOCKED_HEARTBEAT,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_wa2000_silencer,#,SPLASHES_UNLOCKED_HEARTBEAT,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_m16_gl,#,SPLASHES_UNLOCKED_SHOTGUN,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_famas_gl,#,SPLASHES_UNLOCKED_SHOTGUN,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_tavor_gl,#,SPLASHES_UNLOCKED_SHOTGUN,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_fal_gl,#,SPLASHES_UNLOCKED_SHOTGUN,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_ak47_gl,#,SPLASHES_UNLOCKED_SHOTGUN,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_m4_gl,#,SPLASHES_UNLOCKED_SHOTGUN,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_masada_gl,#,SPLASHES_UNLOCKED_SHOTGUN,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_scar_gl,#,SPLASHES_UNLOCKED_SHOTGUN,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_fn2000_gl,#,SPLASHES_UNLOCKED_SHOTGUN,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_m16_reflex,#,SPLASHES_UNLOCKED_EOTECH,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_famas_reflex,#,SPLASHES_UNLOCKED_EOTECH,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_tavor_reflex,#,SPLASHES_UNLOCKED_EOTECH,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_fal_reflex,#,SPLASHES_UNLOCKED_EOTECH,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_ak47_reflex,#,SPLASHES_UNLOCKED_EOTECH,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_m4_reflex,#,SPLASHES_UNLOCKED_EOTECH,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_masada_reflex,#,SPLASHES_UNLOCKED_EOTECH,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_scar_reflex,#,SPLASHES_UNLOCKED_EOTECH,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_fn2000_reflex,#,SPLASHES_UNLOCKED_EOTECH,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_kriss_reflex,#,SPLASHES_UNLOCKED_EOTECH,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_ump45_reflex,#,SPLASHES_UNLOCKED_EOTECH,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_uzi_reflex,#,SPLASHES_UNLOCKED_EOTECH,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_p90_reflex,#,SPLASHES_UNLOCKED_EOTECH,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_mp5k_reflex,#,SPLASHES_UNLOCKED_EOTECH,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_rpd_reflex,#,SPLASHES_UNLOCKED_EOTECH,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_sa80_reflex,#,SPLASHES_UNLOCKED_EOTECH,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_mg4_reflex,#,SPLASHES_UNLOCKED_EOTECH,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_aug_reflex,#,SPLASHES_UNLOCKED_EOTECH,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_m16_acog,#,SPLASHES_UNLOCKED_THERMAL,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_famas_acog,#,SPLASHES_UNLOCKED_THERMAL,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_tavor_acog,#,SPLASHES_UNLOCKED_THERMAL,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_fal_acog,#,SPLASHES_UNLOCKED_THERMAL,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_ak47_acog,#,SPLASHES_UNLOCKED_THERMAL,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_m4_acog,#,SPLASHES_UNLOCKED_THERMAL,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_masada_acog,#,SPLASHES_UNLOCKED_THERMAL,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_scar_acog,#,SPLASHES_UNLOCKED_THERMAL,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_fn2000_acog,#,SPLASHES_UNLOCKED_THERMAL,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_kriss_acog,#,SPLASHES_UNLOCKED_THERMAL,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_ump45_acog,#,SPLASHES_UNLOCKED_THERMAL,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_uzi_acog,#,SPLASHES_UNLOCKED_THERMAL,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_p90_acog,#,SPLASHES_UNLOCKED_THERMAL,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_mp5k_acog,#,SPLASHES_UNLOCKED_THERMAL,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_rpd_acog,#,SPLASHES_UNLOCKED_THERMAL,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_sa80_acog,#,SPLASHES_UNLOCKED_THERMAL,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_mg4_acog,#,SPLASHES_UNLOCKED_THERMAL,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_aug_acog,#,SPLASHES_UNLOCKED_THERMAL,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_cheytac_acog,#,SPLASHES_UNLOCKED_THERMAL,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_m21_acog,#,SPLASHES_UNLOCKED_THERMAL,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_barrett_acog,#,SPLASHES_UNLOCKED_THERMAL,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_wa2000_acog,#,SPLASHES_UNLOCKED_THERMAL,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_kriss_rof,#,SPLASHES_UNLOCKED_AKIMBO,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_ump45_rof,#,SPLASHES_UNLOCKED_AKIMBO,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_uzi_rof,#,SPLASHES_UNLOCKED_AKIMBO,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_p90_rof,#,SPLASHES_UNLOCKED_AKIMBO,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_mp5k_rof,#,SPLASHES_UNLOCKED_AKIMBO,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_m240_silencer,#,SPLASHES_UNLOCKED_HEARTBEAT,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_m240_fmj,#,SPLASHES_UNLOCKED_EXTENDED_MAGS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_m240_reflex,#,SPLASHES_UNLOCKED_EOTECH,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_m240_acog,#,SPLASHES_UNLOCKED_THERMAL,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_marathon_pro,CHALLENGE_MARATHON_PRO,CHALLENGE_RUN_N_MILES,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,perk_challenge +ch_bulletaccuracy_pro,CHALLENGE_BULLETACCURACY_PRO,CHALLENGE_GET_N_HIPFIRE_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,perk_challenge +ch_deadsilence_pro,CHALLENGE_DEADSILENCE_PRO,CHALLENGE_GET_N_CLOSE_DS_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,perk_challenge +ch_scrambler_pro,CHALLENGE_SCRAMBLER_PRO,CHALLENGE_GET_N_CLOSE_SC_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,perk_challenge +ch_sleightofhand_pro,CHALLENGE_SLEIGHTOFHAND_PRO,CHALLENGE_GET_N_SOH_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,perk_challenge +ch_bling_pro,CHALLENGE_BLING_PRO,CHALLENGE_GET_N_BLING_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,perk_challenge +ch_stoppingpower_pro,CHALLENGE_STOPPINGPOWER_PRO,CHALLENGE_GET_N_SP_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,perk_challenge +ch_hardline_pro,CHALLENGE_HARDLINE_PRO,CHALLENGE_GET_N_KILLSTREAKS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,perk_challenge +ch_onemanarmy_pro,CHALLENGE_ONEMANARMY_PRO,CHALLENGE_GET_N_KILLS_OMA,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,perk_challenge +ch_coldblooded_pro,CHALLENGE_COLDBLOODED_PRO,CHALLENGE_KILL_N_KILLSTREAKS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,perk_challenge +ch_laststand_pro,CHALLENGE_LASTSTAND_PRO,CHALLENGE_GET_N_LASTSTAND_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,perk_challenge +ch_scavenger_pro,CHALLENGE_SCAVENGER_PRO,CHALLENGE_PICKUP_N_SCAVENGER_PACKS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,perk_challenge +ch_extendedmelee_pro,CHALLENGE_EXTENDEDMELEE_PRO,CHALLENGE_GET_N_MELEE_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,perk_challenge +ch_detectexplosives_pro,CHALLENGE_DETECTEXPLOSIVES_PRO,CHALLENGE_GET_N_DE_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,perk_challenge +ch_lightweight_pro,CHALLENGE_LIGHTWEIGHT_PRO,CHALLENGE_SPRINT_N_MILES,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,perk_challenge +ch_dangerclose_pro,CHALLENGE_DANGERCLOSE_PRO,CHALLENGE_GET_N_KS_EXPLOSIVE_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,perk_challenge +ch_uav,CHALLENGE_UAV,CHALLENGE_DESC_UAV,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_airdrop,CHALLENGE_AIRDROP,CHALLENGE_DESC_AIRDROP,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_counter_uav,CHALLENGE_COUNTER_UAV,CHALLENGE_DESC_COUNTER_UAV,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_sentry,CHALLENGE_SENTRY,CHALLENGE_DESC_SENTRY,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_predator_missile,CHALLENGE_PREDATOR_MISSILE,CHALLENGE_DESC_PREDATOR_MISSILE,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_precision_airstrike,CHALLENGE_PRECISION_AIRSTRIKE,CHALLENGE_DESC_PRECISION_AIRSTRIKE,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_harrier_strike,CHALLENGE_HARRIER_AIRSTRIKE,CHALLENGE_DESC_HARRIER_AIRSTRIKE,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_helicopter,CHALLENGE_HELICOPTER,CHALLENGE_DESC_HELICOPTER,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_airdrop_mega,CHALLENGE_AIRDROP_MEGA,CHALLENGE_DESC_AIRDROP_MEGA,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_helicopter_flares,CHALLENGE_HELICOPTER_FLARES,CHALLENGE_DESC_HELICOPTER_FLARES,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_stealth_airstrike,CHALLENGE_STEALTH_AIRSTRIKE,CHALLENGE_DESC_STEALTH_AIRSTRIKE,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_helicopter_minigun,CHALLENGE_HELICOPTER_MINIGUN,CHALLENGE_DESC_HELICOPTER_MINIGUN,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_ac130,CHALLENGE_AC130,CHALLENGE_DESC_AC130,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_emp,CHALLENGE_EMP,CHALLENGE_DESC_EMP,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_nuke,CHALLENGE_NUKE,CHALLENGE_DESC_NUKE,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_uavs,CHALLENGE_UAVS,CHALLENGE_DESC_UAVS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_airstrikes,CHALLENGE_AIRSTRIKES,CHALLENGE_DESC_AIRSTRIKES,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_helicopters,CHALLENGE_HELICOPTERS,CHALLENGE_DESC_HELICOPTERS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_airdrops,CHALLENGE_AIRDROPS,CHALLENGE_DESC_AIRDROPS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_grenadekill,CHALLENGE_GRENADE_KILL,CHALLENGE_KILL_N_ENEMIES_WITH_A_GRENADE,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_claymoreshot,CHALLENGE_CLAYMORE_SHOT,CHALLENGE_KILL_5_ENEMIES_BY_USING,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_jackinthebox,CHALLENGE_JACKINTHEBOX,CHALLENGE_DESC_JACKINTHEBOX,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_carnie,CHALLENGE_CARNIE,CHALLENGE_DESC_CARNIE,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_masterblaster,CHALLENGE_MASTERBLASTER,CHALLENGE_DESC_MASTERBLASTER,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_bullseye,CHALLENGE_BULLSEYE,CHALLENGE_DESC_BULLSEYE,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_c4shot,CHALLENGE_C4SHOT,CHALLENGE_DESC_C4SHOT,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_didyouseethat,CHALLENGE_DIDYOUSEETHAT,CHALLENGE_DESC_DIDYOUSEETHAT,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_darkbringer,CHALLENGE_DARKBRINGER,CHALLENGE_DESC_DARKBRINGER,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_tacticaldeletion,CHALLENGE_TACTICALDELETION,CHALLENGE_DESC_TACTICALDELETION,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_its_personal,CHALLENGE_ITS_PERSONAL,CHALLENGE_DESC_ITS_PERSONAL,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_heads_up,CHALLENGE_HEADS_UP,CHALLENGE_DESC_HEADS_UP,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_looknohands,CHALLENGE_LOOKNOHANDS,CHALLENGE_DESC_LOOKNOHANDS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_predator,CHALLENGE_PREDATOR,CHALLENGE_DESC_PREDATOR,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_carpetbomber,CHALLENGE_CARPETBOMBER,CHALLENGE_DESC_CARPETBOMBER,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_yourefired,CHALLENGE_YOUREFIRED,CHALLENGE_DESC_YOUREFIRED,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_choppervet,CHALLENGE_CHOPPER_VETERAN,CHALLENGE_KILL_N_ENEMIES_BY_CALLING1,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_jollygreengiant,CHALLENGE_JOLLYGREENGIANT,CHALLENGE_DESC_JOLLYGREENGIANT,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_thespirit,CHALLENGE_THESPIRIT,CHALLENGE_DESC_THESPIRIT,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_cobracommander,CHALLENGE_COBRACOMMANDER,CHALLENGE_DESC_COBRACOMMANDER,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_spectre,CHALLENGE_SPECTRE,CHALLENGE_DESC_SPECTRE,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_droppincrates,CHALLENGE_DROPPINCRATES,CHALLENGE_DESC_DROPPINCRATES,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_absentee,CHALLENGE_ABSENTEE,CHALLENGE_DESC_ABSENTEE,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_dronekiller,CHALLENGE_DRONEKILLER,CHALLENGE_DESC_DRONEKILLER,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_finishingtouch,CHALLENGE_FINISHINGTOUCH,CHALLENGE_DESC_FINISHINGTOUCH,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_truelies,CHALLENGE_TRUELIES,CHALLENGE_DESC_TRUELIES,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_og,CHALLENGE_OG,CHALLENGE_DESC_OG,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_transformer,CHALLENGE_TRANSFORMER,CHALLENGE_DESC_TRANSFORMER,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_technokiller,CHALLENGE_TECHNOKILLER,CHALLENGE_DESC_TECHNOKILLER,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_hidef,CHALLENGE_HIDEF,CHALLENGE_DESC_HIDEF,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_deathfromabove,CHALLENGE_DEATHFROMABOVE,CHALLENGE_DESC_DEATHFROMABOVE,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_theedge,CHALLENGE_THE_EDGE,CHALLENGE_GET_THE_MATCH_WINNING_KILL_N_TIMES,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_unbelievable,CHALLENGE_UNBELIEVABLE,CHALLENGE_DESC_UNBELIEVABLE,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_owned,CHALLENGE_OWNED,CHALLENGE_DESC_OWNED,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_stickman,CHALLENGE_STICKMAN,CHALLENGE_DESC_STICKMAN,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_lastresort,CHALLENGE_LASTRESORT,CHALLENGE_DESC_LASTRESORT,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_ghillie,CHALLENGE_GHILLIE,CHALLENGE_DESC_GHILLIE,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_hotpotato,CHALLENGE_HOT_POTATO,CHALLENGE_KILL_N_ENEMIES_WITH_THROWN,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_carbomb,CHALLENGE_CAR_BOMB,CHALLENGE_DESC_CAR_BOMB,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_backstabber,CHALLENGE_BACKSTABBER,CHALLENGE_STAB_AN_ENEMY_IN_THE,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_slowbutsure,CHALLENGE_SLOW_BUT_SURE,CHALLENGE_KILL_1_ENEMY_WHILE_BEING,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_miserylovescompany,CHALLENGE_MISERY_LOVES_COMPANY,CHALLENGE_KILL_YOURSELF_AND_1_ENEMY,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_ouch,CHALLENGE_OUCH,CHALLENGE_KILL_N_ENEMIES_WITH_A_RIFLEMOUNTED,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_rival,CHALLENGE_RIVAL,CHALLENGE_KILL_THE_SAME_ENEMY_5,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_cruelty,CHALLENGE_CRUELTY,CHALLENGE_KILL_AN_ENEMY_PICK_UP,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_thinkfast,CHALLENGE_THINK_FAST,CHALLENGE_FINISH_AN_ENEMY_OFF_BY,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_thinkfastconcussion,CHALLENGE_THINK_FAST_STUN,CHALLENGE_FINISH_AN_ENEMY_OFF_BY1,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_thinkfastflash,CHALLENGE_THINK_FAST_FLASH,CHALLENGE_FINISH_AN_ENEMY_OFF_BY2,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_returntosender,CHALLENGE_RETURN_TO_SENDER,CHALLENGE_KILL_AN_ENEMY_BY_SHOOTING,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_blindfire,CHALLENGE_BLINDFIRE,CHALLENGE_KILL_AN_ENEMY_WHILE_YOU,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_hardlanding,CHALLENGE_HARD_LANDING,CHALLENGE_KILL_AN_ENEMY_THAT_IS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_extremecruelty,CHALLENGE_EXTREME_CRUELTY,CHALLENGE_KILL_EVERY_MEMBER_OF1,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_tangodown,CHALLENGE_TANGO_DOWN,CHALLENGE_KILL_EVERY_MEMBER_OF,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_countermvp,CHALLENGE_COUNTERMVP,CHALLENGE_KILL_THE_1_PLAYER_ON,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_goodbye,CHALLENGE_GOODBYE,CHALLENGE_FALL_30_FEET_OR_MORE,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_basejump,CHALLENGE_BASE_JUMP,CHALLENGE_FALL_15_FEET_OR_MORE,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_flyswatter,CHALLENGE_FLYSWATTER,CHALLENGE_SHOOT_DOWN_AN_ENEMY_HELICOPTER,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_vandalism,CHALLENGE_VANDALISM,CHALLENGE_KILL_1_ENEMY_BY_DESTROYING,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_crouchshot,CHALLENGE_CROUCH_SHOT,CHALLENGE_KILL_N_ENEMIES_WHILE_CROUCHING,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_proneshot,CHALLENGE_PRONE_SHOT,CHALLENGE_KILL_N_ENEMIES_WHILE_PRONE,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_assists,CHALLENGE_POINT_GUARD,CHALLENGE_GET_N_ASSISTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_xrayvision,CHALLENGE_XRAY_VISION,CHALLENGE_KILL_N_ENEMY_THROUGH,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_backdraft,CHALLENGE_BACKDRAFT,CHALLENGE_DESTROY_N_ENEMY_EXPLOSIVES,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_shieldvet,CHALLENGE_SHIELD_VETERAN,CHALLENGE_KILL_N_ENEMY_WITH_SHIELD,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_smasher,CHALLENGE_SMASHER,CHALLENGE_GET_A_3_SHIELD_KILL_STREAK,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_backsmasher,CHALLENGE_BACKSMASHER,CHALLENGE_CRUSH_AN_ENEMY,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_shield_damage,CHALLENGE_SHIELD_DAMAGE,CHALLENGE_DESC_SHIELD_DAMAGE,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_shield_bullet,CHALLENGE_SHIELD_BULLET,CHALLENGE_DESC_SHIELD_BULLET,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_shield_explosive,CHALLENGE_SHIELD_EXPLOSIVE,CHALLENGE_DESC_SHIELD_EXPLOSIVE,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_surgical_assault,CHALLENGE_SURGICAL_ASSAULT,CHALLENGE_DESC_SURGICAL_ASSAULT,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_surgical_smg,CHALLENGE_SURGICAL_SMG,CHALLENGE_DESC_SURGICAL_SMG,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_surgical_lmg,CHALLENGE_SURGICAL_LMG,CHALLENGE_DESC_SURGICAL_LMG,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_surgical_sniper,CHALLENGE_SURGICAL_SNIPER,CHALLENGE_DESC_SURGICAL_SNIPER,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_assault,CHALLENGE_ASSAULT_EXPERT,CHALLENGE_KILL_N_ENEMIES_WITH_A1,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_smg,CHALLENGE_SMG_EXPERT,CHALLENGE_KILL_N_ENEMIES_WITH_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_expert_lmg,CHALLENGE_LMG_EXPERT,CHALLENGE_KILL_N_ENEMIES_WITH_HEADSHOTS1,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_multirpg,CHALLENGE_MULTIRPG,CHALLENGE_KILL_2_OR_MORE_ENEMIES,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_multiclaymore,CHALLENGE_CLAY_MORE,CHALLENGE_KILL_2_OR_MORE_ENEMIES1,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_multifrag,CHALLENGE_MULTIFRAG,CHALLENGE_KILL_2_OR_MORE_ENEMIES2,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_multic4,CHALLENGE_MULTIC4,CHALLENGE_KILL_2_OR_MORE_ENEMIES3,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_collateraldamage,CHALLENGE_COLLATERAL_DAMAGE,CHALLENGE_KILL_2_OR_MORE_ENEMIES4,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_flawless,CHALLENGE_FLAWLESS,CHALLENGE_PLAY_AN_ENTIRE_FULLLENGTH,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_fearless,CHALLENGE_FEARLESS,CHALLENGE_KILL_10_ENEMIES_IN_A,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_grouphug,CHALLENGE_GROUPHUG,CHALLENGE_DESC_GROUPHUG,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_nbk,CHALLENGE_NBK,CHALLENGE_DESC_NBK,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_allpro,CHALLENGE_ALLPRO,CHALLENGE_DESC_ALLPRO,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_airborne,CHALLENGE_AIRBORNE,CHALLENGE_GET_A_2_KILL_STREAK_WHILE,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_moneyshot,CHALLENGE_MONEYSHOT,CHALLENGE_DESC_MONEYSHOT,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_robinhood,CHALLENGE_ROBINHOOD,CHALLENGE_DESC_ROBINHOOD,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_bangforbuck,CHALLENGE_BANGFORBUCK,CHALLENGE_DESC_BANGFORBUCK,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_overdraft,CHALLENGE_OVERDRAFT,CHALLENGE_DESC_OVERDRAFT,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_identitytheft,CHALLENGE_IDENTITYTHEFT,CHALLENGE_DESC_IDENTITYTHEFT,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_atm,CHALLENGE_ATM,CHALLENGE_DESC_ATM,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_timeismoney,CHALLENGE_TIMEISMONEY,CHALLENGE_DESC_TIMEISMONEY,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_iamrich,CHALLENGE_IAMRICH,CHALLENGE_DESC_IAMRICH,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_breakbank,CHALLENGE_BREAKBANK,CHALLENGE_DESC_BREAKBANK,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_colorofmoney,CHALLENGE_COLOROFMONEY,CHALLENGE_DESC_COLOROFMONEY,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_neverforget,CHALLENGE_NEVERFORGET,CHALLENGE_DESC_NEVERFORGET,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_thebrink,CHALLENGE_THE_BRINK,CHALLENGE_GET_A_3_OR_MORE_KILL,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_fastswap,CHALLENGE_FAST_SWAP,CHALLENGE_HURT_AN_ENEMY_WITH_A,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_starplayer,CHALLENGE_STAR_PLAYER,CHALLENGE_PLAY_AN_ENTIRE_MATCH,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_howthe,CHALLENGE_HOW_THE_,CHALLENGE_KILL_AN_ENEMY_BY_USING,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_dominos,CHALLENGE_DOMINOS,CHALLENGE_KILL_AN_ENEMY_BY_SETTING,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_masterchef,CHALLENGE_MASTER_CHEF,CHALLENGE_KILL_N_ENEMIES_WITH_COOKED,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_invincible,CHALLENGE_INVINCIBLE,CHALLENGE_GET_5_HEALTH_REGENERATIONS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_survivalist,CHALLENGE_SURVIVALIST,CHALLENGE_SURVIVE_FOR_5_CONSECUTIVE,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_counterclaymore,CHALLENGE_COUNTERCLAYMORE,CHALLENGE_KILL_N_ENEMIES_BY_SHOOTING,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_counterc4,CHALLENGE_COUNTERC4,CHALLENGE_KILL_N_ENEMIES_BY_SHOOTING1,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_enemyofthestate,CHALLENGE_ENEMYOFTHESTATE,CHALLENGE_DESC_ENEMYOFTHESTATE,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_resourceful,CHALLENGE_RESOURCEFUL,CHALLENGE_DESC_RESOURCEFUL,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_survivor,CHALLENGE_SURVIVOR,CHALLENGE_DESC_SURVIVOR,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_bothbarrels,CHALLENGE_BOTHBARRELS,CHALLENGE_DESC_BOTHBARRELS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_omnicide,CHALLENGE_OMNICIDE,CHALLENGE_DESC_OMNICIDE,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_wargasm,CHALLENGE_WARGASM,CHALLENGE_DESC_WARGASM,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_thebiggertheyare,CHALLENGE_THEBIGGERTHEYARE,CHALLENGE_DESC_THEBIGGERTHEYARE,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_thehardertheyfall,CHALLENGE_THEHARDERTHEYFALL,CHALLENGE_DESC_THEHARDERTHEYFALL,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_crabmeat,CHALLENGE_CRABMEAT,CHALLENGE_DESC_CRABMEAT,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_wopr,CHALLENGE_WOPR,CHALLENGE_DESC_WOPR,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_thedenier,CHALLENGE_THEDENIER,CHALLENGE_DESC_THEDENIER,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_carpetbomb,CHALLENGE_CARPET_BOMB,CHALLENGE_KILL_5_ENEMIES_WITH_A3,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_redcarpet,CHALLENGE_RED_CARPET,CHALLENGE_DESC_RED_CARPET,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_reaper,CHALLENGE_REAPER,CHALLENGE_DESC_REAPER,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_nosecrets,CHALLENGE_NO_SECRETS,CHALLENGE_CALL_IN_A_UAV_3_TIMES,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_sunblock,CHALLENGE_SUNBLOCK,CHALLENGE_DESC_SUNBLOCK,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_afterburner,CHALLENGE_AFTERBURNER,CHALLENGE_CALL_IN_AN_AIRSTRIKE,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_airsuperiority,CHALLENGE_AIR_SUPERIORITY,CHALLENGE_CALL_IN_A_HELICOPTER,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_mgmaster,CHALLENGE_MG_MASTER,CHALLENGE_GET_A_5_KILL_STREAK_WHILE,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_slasher,CHALLENGE_SLASHER,CHALLENGE_GET_A_3_MELEE_KILL_STREAK,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_radiationsickness,CHALLENGE_RADIATIONSICKNESS,CHALLENGE_DESC_RADIATIONSICKNESS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_infected,CHALLENGE_INFECTED,CHALLENGE_DESC_INFECTED,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_plague,CHALLENGE_PLAGUE,CHALLENGE_DESC_PLAGUE,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_renaissance,CHALLENGE_RENAISSANCE,CHALLENGE_DESC_RENAISSANCE,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_theloner,CHALLENGE_THELONER,CHALLENGE_DESC_THELONER,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_6fears7,CHALLENGE_6FEARS7,CHALLENGE_DESC_6FEARS7,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_thenumb,CHALLENGE_THENUMB,CHALLENGE_DESC_THENUMB,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_martyr,CHALLENGE_MARTYR,CHALLENGE_DESC_MARTYR,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_livingdead,CHALLENGE_LIVINGDEAD,CHALLENGE_DESC_LIVINGDEAD,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_sidekick,CHALLENGE_SIDEKICK,CHALLENGE_DESC_SIDEKICK,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_clickclickboom,CHALLENGE_CLICKCLICKBOOM,CHALLENGE_DESC_CLICKCLICKBOOM,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_hijacker,CHALLENGE_HIJACKER,CHALLENGE_DESC_HIJACKER,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_no,CHALLENGE_NO,CHALLENGE_DESC_NO,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_avenger,CHALLENGE_AVENGER,CHALLENGE_DESC_AVENGER,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_victor_dm,CHALLENGE_FREEFORALL_VICTOR,CHALLENGE_PLACE_IN_THE_TOP_3_IN,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_teamplayer,CHALLENGE_TEAM_PLAYER,CHALLENGE_WIN_N_TEAM_DEATHMATCH,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_victor_sd,CHALLENGE_SD_VICTOR,CHALLENGE_WIN_N_SEARCH_AND_DESTROY,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_mvp_tdm,CHALLENGE_MVP_TEAM_DEATHMATCH,CHALLENGE_PLAY_TEAM_DEATHMATCH,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_teamplayer_hc,CHALLENGE_HARDCORE_TEAM_PLAYER,CHALLENGE_WIN_N_HARDCORE_TEAM_DEATHMATCH,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_victor_sab,CHALLENGE_SABOTAGE_VICTOR,CHALLENGE_WIN_N_SABOTAGE_MATCHES,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_mvp_thc,CHALLENGE_MVP_TEAM_HARDCORE,CHALLENGE_WIN_A_TEAM_HARDCORE_MATCH,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_bombdown,CHALLENGE_BOMB_DOWN,CHALLENGE_KILL_THE_BOMB_CARRIER,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_bombdefender,CHALLENGE_BOMB_DEFENDER,CHALLENGE_KILL_A_BOMB_DEFUSER_IN,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_bombplanter,CHALLENGE_BOMB_PLANTER,CHALLENGE_KILL_A_BOMB_PLANTER_IN,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_hero,CHALLENGE_DEFUSER,CHALLENGE_DEFUSE_A_BOMB_IN_SABOTAGE,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_lastmanstanding,CHALLENGE_LAST_MAN_STANDING,CHALLENGE_BE_THE_LAST_MAN_STANDING,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_saboteur,CHALLENGE_SABOTEUR,CHALLENGE_DESC_SABOTEUR,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_knifevet,CHALLENGE_KNIFE_VETERAN,CHALLENGE_KILL_N_ENEMY_WITH_THE,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_laststandvet,CHALLENGE_LAST_STAND_VETERAN,CHALLENGE_KILL_N_ENEMY_WHILE_USING,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_stealth,CHALLENGE_STEALTH,CHALLENGE_KILL_N_ENEMIES_SILENCED,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_concussionvet,CHALLENGE_STUN_VETERAN,CHALLENGE_KILL_N_ENEMIES_STILL,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_flashbangvet,CHALLENGE_FLASHBANG_VETERAN,CHALLENGE_KILL_N_ENEMIES_DAZED,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_ak47_mastery,CHALLENGE_AK47_MASTERY,CHALLENGE_GET_ALL_ATTACHMENTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_fal_mastery,CHALLENGE_FAL_MASTERY,CHALLENGE_GET_ALL_ATTACHMENTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_famas_mastery,CHALLENGE_FAMAS_MASTERY,CHALLENGE_GET_ALL_ATTACHMENTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_fn2000_mastery,CHALLENGE_FN2000_MASTERY,CHALLENGE_GET_ALL_ATTACHMENTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_m4_mastery,CHALLENGE_M4_MASTERY,CHALLENGE_GET_ALL_ATTACHMENTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_m16_mastery,CHALLENGE_M16_MASTERY,CHALLENGE_GET_ALL_ATTACHMENTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_masada_mastery,CHALLENGE_MASADA_MASTERY,CHALLENGE_GET_ALL_ATTACHMENTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_scar_mastery,CHALLENGE_SCAR_MASTERY,CHALLENGE_GET_ALL_ATTACHMENTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_tavor_mastery,CHALLENGE_TAVOR_MASTERY,CHALLENGE_GET_ALL_ATTACHMENTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_mp5k_mastery,CHALLENGE_MP5K_MASTERY,CHALLENGE_GET_ALL_ATTACHMENTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_uzi_mastery,CHALLENGE_UZI_MASTERY,CHALLENGE_GET_ALL_ATTACHMENTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_kriss_mastery,CHALLENGE_KRISS_MASTERY,CHALLENGE_GET_ALL_ATTACHMENTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_ump45_mastery,CHALLENGE_UMP45_MASTERY,CHALLENGE_GET_ALL_ATTACHMENTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_p90_mastery,CHALLENGE_P90_MASTERY,CHALLENGE_GET_ALL_ATTACHMENTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_m240_mastery,CHALLENGE_M240_MASTERY,CHALLENGE_GET_ALL_ATTACHMENTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_aug_mastery,CHALLENGE_AUG_MASTERY,CHALLENGE_GET_ALL_ATTACHMENTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_sa80_mastery,CHALLENGE_SA80_MASTERY,CHALLENGE_GET_ALL_ATTACHMENTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_rpd_mastery,CHALLENGE_RPD_MASTERY,CHALLENGE_GET_ALL_ATTACHMENTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_mg4_mastery,CHALLENGE_MG4_MASTERY,CHALLENGE_GET_ALL_ATTACHMENTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_cheytac_mastery,CHALLENGE_CHEYTAC_MASTERY,CHALLENGE_GET_ALL_ATTACHMENTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_barrett_mastery,CHALLENGE_BARRETT_MASTERY,CHALLENGE_GET_ALL_ATTACHMENTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_m21_mastery,CHALLENGE_M21_MASTERY,CHALLENGE_GET_ALL_ATTACHMENTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +ch_wa2000_mastery,CHALLENGE_WA2000_MASTERY,CHALLENGE_GET_ALL_ATTACHMENTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_ak47,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_ak47,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_fal,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_fal,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_famas,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_famas,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_fn2000,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_fn2000,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_m4,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_m4,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_m16,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_m16,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_masada,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_masada,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_scar,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_scar,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_tavor,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_tavor,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_mp5k,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_mp5k,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_uzi,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_uzi,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_kriss,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_kriss,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_ump45,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_ump45,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_p90,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_p90,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_m240,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_m240,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_aug,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_aug,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_sa80,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_sa80,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_rpd,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_rpd,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_mg4,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_mg4,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_cheytac,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_cheytac,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_barrett,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_barrett,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_m21,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_m21,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_wa2000,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_wa2000,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_glock,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_glock,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_tmp,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_tmp,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_beretta393,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_beretta393,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_pp2000,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_pp2000,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_striker,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_striker,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_aa12,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_aa12,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_m1014,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_m1014,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_spas12,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_spas12,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_ranger,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_ranger,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_model1887,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_model1887,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_usp,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_usp,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_beretta,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_beretta,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_coltanaconda,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_coltanaconda,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_deserteagle,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_deserteagle,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_at4,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_at4,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_rpg,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_rpg,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_javelin,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_javelin,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_m79,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_m79,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_marksman_stinger,#,CHALLENGE_GET_N_KILLS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge +pr_expert_stinger,#,CHALLENGE_GET_N_HEADSHOTS,,3.5,0.25,0.75,0.25,1,mp_challenge_complete,,challenge diff --git a/iw4x/iw4x_00/sound/battlechatter/UK/mp/UK_1mc_kill_confirmed_01.wav b/iw4x/iw4x_00/sound/battlechatter/UK/mp/UK_1mc_kill_confirmed_01.wav new file mode 100644 index 0000000..22f79a9 Binary files /dev/null and b/iw4x/iw4x_00/sound/battlechatter/UK/mp/UK_1mc_kill_confirmed_01.wav differ diff --git a/iw4x/iw4x_00/sound/battlechatter/US/MP/US_1mc_kill_confirmed_01.wav b/iw4x/iw4x_00/sound/battlechatter/US/MP/US_1mc_kill_confirmed_01.wav new file mode 100644 index 0000000..6d9cdcf Binary files /dev/null and b/iw4x/iw4x_00/sound/battlechatter/US/MP/US_1mc_kill_confirmed_01.wav differ diff --git a/iw4x/iw4x_00/sound/battlechatter/ru/MP/RU_1mc_kill_confirmed_01.wav b/iw4x/iw4x_00/sound/battlechatter/ru/MP/RU_1mc_kill_confirmed_01.wav new file mode 100644 index 0000000..1a1027d Binary files /dev/null and b/iw4x/iw4x_00/sound/battlechatter/ru/MP/RU_1mc_kill_confirmed_01.wav differ diff --git a/iw4x/iw4x_00/ui_mp/settings_gametype.menu b/iw4x/iw4x_00/ui_mp/settings_gametype.menu index 4cf268a..d02ba76 100644 --- a/iw4x/iw4x_00/ui_mp/settings_gametype.menu +++ b/iw4x/iw4x_00/ui_mp/settings_gametype.menu @@ -570,6 +570,9 @@ GAMETYPE_BUTTON(10, 1, gtnw) GAMETYPE_BUTTON(11, 1, oneflag) GAMETYPE_BUTTON(12, 1, arena) + GAMETYPE_BUTTON(13, 1, conf) + GAMETYPE_BUTTON(14, 1, gun) + GAMETYPE_BUTTON(15, 1, infect) /* GAMETYPE_BUTTON(1, dm) diff --git a/zone/patch/iw4x_patch_mp.ff b/zone/patch/iw4x_patch_mp.ff index 2873bab..7597e25 100644 Binary files a/zone/patch/iw4x_patch_mp.ff and b/zone/patch/iw4x_patch_mp.ff differ