This commit is contained in:
2024-12-11 11:28:08 +01:00
commit 12ac62a956
444 changed files with 303964 additions and 0 deletions

1005
maps/mp/killstreaks/_a10.gsc Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,200 @@
#include maps\mp\_utility;
#include maps\mp\gametypes\_hud_util;
#include common_scripts\utility;
//============================================
// constants
//============================================
CONST_AA_LAUNCHER_WEAPON = "iw6_maaws_mp";
CONST_AA_LAUNCHER_WEAPON_CHILD = "iw6_maawschild_mp";
CONST_AA_LAUNCHER_WEAPON_HOMING = "iw6_maawshoming_mp";
CONST_AA_LAUNCHER_WEAPON_AMMO = 2;
//============================================
// init
//============================================
init()
{
level.killstreakFuncs["aa_launcher"] = ::tryUseAALauncher;
maps\mp\_laserGuidedLauncher::LGM_init( "vfx/gameplay/mp/killstreaks/vfx_maaws_split", "vfx/gameplay/mp/killstreaks/vfx_maaws_homing" );
}
//============================================
// Getters and Setters
//============================================
getAALauncherName()
{
return CONST_AA_LAUNCHER_WEAPON;
}
getAALauncherChildName()
{
return CONST_AA_LAUNCHER_WEAPON_CHILD;
}
getAALauncherHomingName()
{
return CONST_AA_LAUNCHER_WEAPON_HOMING;
}
getAALauncherAmmo( player )
{
AssertEx( IsDefined( player.pers[ "aaLauncherAmmo" ] ), "getAALauncherAmmo() called on player with no \"aaLauncherAmmo\" array key." );
ksUniqueID = getAALauncherUniqueIndex( player );
ammo = 0;
if ( IsDefined( player.pers[ "aaLauncherAmmo" ][ ksUniqueID ] ) )
{
ammo = player.pers[ "aaLauncherAmmo" ][ ksUniqueID ];
}
return ammo;
}
clearAALauncherAmmo( player )
{
ksUniqueID = getAALauncherUniqueIndex( player );
player.pers[ "aaLauncherAmmo" ][ ksUniqueID ] = undefined;
}
setAALauncherAmmo( player, ammo, setAmmo )
{
AssertEx( IsDefined( player.pers[ "aaLauncherAmmo" ] ), "setAALauncherAmmo() called on player with no \"aaLauncherAmmo\" array key." );
// The player could also have two of the AA Launcher
// so ammo needs to be stored on a per killstreak instance
ksUniqueID = getAALauncherUniqueIndex( player );
player.pers[ "aaLauncherAmmo" ][ ksUniqueID ] = ammo;
if ( !IsDefined( setAmmo ) || setAmmo )
{
if ( player HasWeapon( getAALauncherName() ) )
{
player SetWeaponAmmoClip( getAALauncherName(), ammo );
}
}
}
getAALauncherUniqueIndex( player )
{
AssertEx( IsDefined( player.killstreakIndexWeapon ), "getAALauncherAmmo() called on player with no killstreakIndexWeapon field" );
return player.pers["killstreaks"][ player.killstreakIndexWeapon ].kID;
}
//============================================
// tryUseAALauncher
//============================================
tryUseAALauncher( lifeId, streakName )
{
return useAALauncher( self, lifeId );
}
//============================================
// useAALauncher - Returns true if the killstreak was used up
//============================================
useAALauncher( player, lifeId )
{
// Ammo is persistent to handle round switching. If this is
// the first use init the self.per[ "aaLauncherAmmo" ] array
if ( !IsDefined( self.pers[ "aaLauncherAmmo" ] ) )
{
self.pers[ "aaLauncherAmmo" ] = [];
}
// The launcher persistent ammo count will only be empty in the
// use function when the persistent ammo has yet to be set
// so set it to the starting ammo
if ( getAALauncherAmmo( player ) == 0 )
{
setAALauncherAmmo( self, CONST_AA_LAUNCHER_WEAPON_AMMO, false );
}
level thread monitorWeaponSwitch( player );
level thread monitorLauncherAmmo( player );
self thread maps\mp\_laserGuidedLauncher::LGM_firing_monitorMissileFire( getAALauncherName(), getAALauncherChildName(), getAALauncherHomingName() );
result = false;
msg = player waittill_any_return( "aa_launcher_switch", "aa_launcher_empty", "death", "disconnect" );
if ( msg == "aa_launcher_empty" )
{
// The player is potentially still guiding the missiles at this point
// so wait until the player switches out or the missiles are destroyed
player waittill_any( "weapon_change", "LGM_player_allMissilesDestroyed", "death", "disconnect" );
result = true;
}
else
{
// In case death and aa_launcher_empty came on the same frame verify ammo
if ( player HasWeapon( getAALauncherName() ) && player GetAmmoCount( getAALauncherName() ) == 0 )
{
clearAALauncherAmmo( player );
}
if ( getAALauncherAmmo( player ) == 0 )
{
result = true;
}
}
player notify( "aa_launcher_end" );
self maps\mp\_laserGuidedLauncher::LGM_firing_endMissileFire();
return result;
}
//============================================
// monitorWeaponSwitch
//============================================
monitorWeaponSwitch( player )
{
player endon( "death" );
player endon( "disconnect" );
player endon( "aa_launcher_empty" );
player endon( "aa_launcher_end" );
currentWeapon = player GetCurrentWeapon();
while( currentWeapon == getAALauncherName() )
{
player waittill( "weapon_change", currentWeapon );
}
player notify( "aa_launcher_switch" );
}
//============================================
// monitorLauncherAmmo
//============================================
monitorLauncherAmmo( player )
{
player endon( "death" );
player endon( "disconnect" );
player endon( "aa_launcher_switch" );
player endon( "aa_launcher_end" );
setAALauncherAmmo( player, getAALauncherAmmo( player ), true );
while( true )
{
player waittill( "weapon_fired", weaponName );
if ( weaponName != getAALauncherName() )
continue;
ammo = player GetAmmoCount( getAALauncherName() );
setAALauncherAmmo( player, ammo, false );
if( getAALauncherAmmo( player ) == 0 )
{
clearAALauncherAmmo( player );
player notify( "aa_launcher_empty" );
break;
}
}
}

View File

@ -0,0 +1,244 @@
#include maps\mp\_utility;
#include common_scripts\utility;
/*******************************************************************
// _aamissile.gsc
//
// Holds all the aamissile specific functions
//
// Jordan Hirsh Jan. 18th 2011
********************************************************************/
init()
{
precacheItem( "aamissile_projectile_mp" );
precacheShader( "ac130_overlay_grain" );
level.AAMissileLaunchVert = 14000;
level.AAMissileLaunchHorz = 30000;
level.AAMissileLaunchTargetDist = 1500;
level.rockets = [];
level.killstreakFuncs["aamissile"] = ::tryUseAAMissile;
}
tryUseAAMissile( lifeId, streakName )
{
self setUsingRemote( "aamissile" );
result = self maps\mp\killstreaks\_killstreaks::initRideKillstreak();
if ( result != "success" )
{
if ( result != "disconnect" )
self clearUsingRemote();
return false;
}
level thread aa_missile_fire( lifeId, self );
return true;
}
getTargets()
{
lbTargets = [];
heliTargets = [];
if ( isDefined( level.littleBirds ) && level.littleBirds.size )
{
foreach ( lb in level.littleBirds )
{
if ( lb.team != self.team )
lbTargets[lbTargets.size] = lb;
}
}
if ( isDefined(level.helis) && level.helis.size )
{
foreach ( heli in level.helis )
{
if ( heli.team != self.team )
heliTargets[heliTargets.size] = heli;
}
}
//if( level.ac130InUse && isDefined( level.ac130.owner ) && level.ac130.owner.team != self.team )
// return level.ac130.planemodel;
if( isDefined(heliTargets) && heliTargets.size )
return heliTargets[0];
else if ( isDefined(lbTargets) && lbTargets.size )
return lbTargets[0];
}
aa_missile_fire( lifeId, player )
{
aaMissileSpawn = undefined;
upVector = (0, 0, level.AAMissileLaunchVert );
backDist = level.AAMissileLaunchHorz;
targetDist = level.AAMmissileLaunchTargetDist;
bestTarget = player getTargets();
if( !isDefined(bestTarget) )
targetPos = (0,0,0);
else
{
targetPos = bestTarget.origin;
upVector = (0,0,1) * (targetPos) + (0,0,1000);
}
forward = AnglesToForward( player.angles );
startpos = player.origin + upVector + forward * backDist * -1;
rocket = MagicBullet( "aamissile_projectile_mp", startpos, targetPos, player );
//spawn f16 model and make it do somthing cool
if ( !IsDefined( rocket ) )
{
player clearUsingRemote();
return;
}
rocket.lifeId = lifeId;
rocket.type = "remote";
MissileEyes( player, rocket );
}
MissileEyes( player, rocket )
{
player endon ( "joined_team" );
player endon ( "joined_spectators" );
rocket thread Rocket_CleanupOnDeath();
player thread Player_CleanupOnGameEnded( rocket );
player thread Player_CleanupOnTeamChange( rocket );
player VisionSetMissilecamForPlayer( "black_bw", 0 );
player endon ( "disconnect" );
if ( isDefined( rocket ) )
{
player VisionSetMissilecamForPlayer( game["thermal_vision"], 1.0 );
player thread delayedFOFOverlay();
player CameraLinkTo( rocket, "tag_origin" );
player ControlsLinkTo( rocket );
if ( getDvarInt( "camera_thirdPerson" ) )
player setThirdPersonDOF( false );
rocket waittill( "death" );
// is defined check required because remote missile doesnt handle lifetime explosion gracefully
// instantly deletes its self after an explode and death notify
if ( isDefined(rocket) )
player maps\mp\_matchdata::logKillstreakEvent( "predator_missile", rocket.origin );
player ControlsUnlink();
player freezeControlsWrapper( true );
// If a player gets the final kill with a hellfire, level.gameEnded will already be true at this point
if ( !level.gameEnded || isDefined( player.finalKill ) )
player thread staticEffect( 0.5 );
wait ( 0.5 );
player ThermalVisionFOFOverlayOff();
player CameraUnlink();
if ( getDvarInt( "camera_thirdPerson" ) )
player setThirdPersonDOF( true );
}
player clearUsingRemote();
}
delayedFOFOverlay()
{
self endon ( "death" );
self endon ( "disconnect" );
level endon ( "game_ended" );
wait ( 0.15 );
self ThermalVisionFOFOverlayOn();
}
staticEffect( duration )
{
self endon ( "disconnect" );
staticBG = newClientHudElem( self );
staticBG.horzAlign = "fullscreen";
staticBG.vertAlign = "fullscreen";
staticBG setShader( "white", 640, 480 );
staticBG.archive = true;
staticBG.sort = 10;
static = newClientHudElem( self );
static.horzAlign = "fullscreen";
static.vertAlign = "fullscreen";
static setShader( "ac130_overlay_grain", 640, 480 );
static.archive = true;
static.sort = 20;
wait ( duration );
static destroy();
staticBG destroy();
}
Player_CleanupOnTeamChange( rocket )
{
rocket endon ( "death" );
self endon ( "disconnect" );
self waittill_any( "joined_team" , "joined_spectators" );
if ( self.team != "spectator" )
{
self ThermalVisionFOFOverlayOff();
self ControlsUnlink();
self CameraUnlink();
if ( getDvarInt( "camera_thirdPerson" ) )
self setThirdPersonDOF( true );
}
self clearUsingRemote();
level.remoteMissileInProgress = undefined;
}
Rocket_CleanupOnDeath()
{
entityNumber = self getEntityNumber();
level.rockets[ entityNumber ] = self;
self waittill( "death" );
level.rockets[ entityNumber ] = undefined;
}
Player_CleanupOnGameEnded( rocket )
{
rocket endon ( "death" );
self endon ( "death" );
level waittill ( "game_ended" );
self ThermalVisionFOFOverlayOff();
self ControlsUnlink();
self CameraUnlink();
if ( getDvarInt( "camera_thirdPerson" ) )
self setThirdPersonDOF( true );
}

View File

@ -0,0 +1,327 @@
#include maps\mp\_utility;
#include common_scripts\utility;
/*******************************************************************
// _aastrike.gsc
//
// Holds all the aastrike specific functions
//
// Jordan Hirsh Jan. 25th 2011
********************************************************************/
init()
{
precacheItem( "aamissile_projectile_mp" );
precacheModel( "vehicle_av8b_harrier_jet_mp" );
level.teamAirDenied["axis"] = false;
level.teamAirDenied["allies"] = false;
level.rockets = [];
level.killstreakFuncs["aastrike"] = ::tryUseAAStrike;
}
tryUseAAStrike( lifeId, streakName )
{
/#
heightEnt = GetEnt( "airstrikeheight", "targetname" );
assertEx( isDefined( heightEnt ), "NO HEIGHT ENT IN LEVEL: Don't know what this means, ask Ned or Jordan" );
if ( !isDefined(heightEnt) )
return false;
#/
self maps\mp\_matchdata::logKillstreakEvent( "aastrike", self.origin );
self thread finishAAStrike( lifeId );
self thread teamPlayerCardSplash( "used_aastrike", self, self.team );
return true;
}
cycleTargets()
{
self endon("stopFindingTargets");
self endon("disconnect");
self endon ( "owner_gone" );
self endon ( "game_ended" );
for( ;; )
{
wait(.05);
self findTargets();
wait( randomIntRange(4,5) );
}
}
findTargets()
{
self endon ( "disconnect" );
self endon ( "owner_gone" );
self endon ( "game_ended" );
lbTargets = [];
heliTargets = [];
uavTargets = [];
if ( isDefined( level.littleBirds ) && level.littleBirds.size )
{
foreach ( lb in level.littleBirds )
{
if ( isDefined( lb.team ) && lb.team != self.team )
lbTargets[lbTargets.size] = lb;
}
}
if ( isDefined(level.helis) && level.helis.size )
{
foreach ( heli in level.helis )
{
if ( heli.team != self.team )
heliTargets[heliTargets.size] = heli;
}
}
otherTeam = getOtherTeam( self.team );
if ( isDefined( level.activeUAVs[otherTeam] ) )
{
foreach ( uav in level.uavmodels[otherTeam] )
{
uavTargets[uavTargets.size] = uav;
}
}
targetCount = 0;
foreach( lb in lbTargets )
{
wait( 3 );
if ( targetCount % 2 )
self thread fireAtTarget( lb, self.team, true );
else
self thread fireAtTarget( lb, self.team, false );
targetCount++;
}
foreach( heli in heliTargets )
{
wait( 3 );
self thread fireAtTarget( heli, self.team, true );
}
foreach( uav in uavTargets )
{
wait( 0.5 );
self thread fireAtTarget( uav, self.team, false );
}
/*
if( level.ac130InUse && isDefined( level.ac130.owner ) && level.ac130.owner.team != self.team )
{
ac130Target = level.ac130.planemodel;
wait( 6 );
self thread fireAtTarget( ac130Target, self.team, true );
}
*/
}
earlyAbortWatcher()
{
self endon( "stopFindingTargets" );
team = self.team;
if ( bot_is_fireteam_mode() )
{
self waittill( "killstreak_disowned" );
}
else
{
self waittill_either( "killstreak_disowned", "game_ended" );
}
self notify( "owner_gone" );
level.teamAirDenied[getOtherTeam(team)] = false;
level.airDeniedPlayer = undefined;
}
finishAAStrike( lifeId )
{
self endon ( "disconnect" );
self endon ( "owner_gone" );
self endon ( "game_ended" );
//message air up there
level.teamAirDenied[getOtherTeam(self.team)] = true;
level.airDeniedPlayer = self;
self thread earlyAbortWatcher();
//splash incoming in 10
//wait ( 1 );
//splash incoming in 9
//wait(3)
//splash incoming in 6
//wait(4)
//splash incoming in 2
//wait( 1 )
//splash incoming in 1
self thread cycleTargets();
for( i = 0; i < 4; i++ )
{
wait ( 6 );
if ( i == 1 || i == 3 )
self thread doFlyBy( true );
else
self thread doFlyBy( false );
}
wait(3);
self notify("stopFindingTargets");
level.teamAirDenied[getOtherTeam(self.team)] = false;
level.airDeniedPlayer = undefined;
}
fireAtTarget( curTarget, team, showIcon )
{
if ( !isDefined(curTarget) )
return;
upVector = (0, 0, 14000 );
miniUpVector = (0, 0, 1500);
backDist = 15000;
forwardDist = 20000;
targetPos = curTarget.origin;
upVector = (0,0,1) * (targetPos) + (0,0,1000);
curTargetYaw = curTarget.angles * (0,1,0);
forward = AnglesToForward( curTargetYaw );
startpos = curTarget.origin + miniUpVector + forward * backDist * -1;
endPos = curTarget.origin + miniUpVector + forward * forwardDist;
rocket1 = MagicBullet( "aamissile_projectile_mp", startpos + (0,0,-75), curTarget.origin, self );
rocket1 Missile_SetTargetEnt( curTarget );
rocket1 Missile_SetFlightmodeDirect();
rocket2 = MagicBullet( "aamissile_projectile_mp", startpos + (RandomInt(500), RandomInt(500), -75), curTarget.origin, self );
rocket2 Missile_SetTargetEnt( curTarget );
rocket2 Missile_SetFlightmodeDirect();
if ( showIcon )
plane = spawnplane( self, "script_model", startpos, "compass_objpoint_airstrike_friendly", "compass_objpoint_airstrike_friendly" );
else
plane = spawnplane( self, "script_model", startpos );
if ( self.team == "allies" )
plane setModel( "vehicle_av8b_harrier_jet_mp" );
else
plane setModel( "vehicle_av8b_harrier_jet_opfor_mp" );
length = distance(startPos, endPos);
plane.angles = vectorToAngles( endPos-startPos );
plane thread AASoundManager(length);
plane thread playPlaneFx();
length = distance(startPos, endPos);
plane moveTo( endPos * 2, length/2000, 0, 0 );
wait( length/3000 );
plane delete();
}
AASoundManager( length )
{
self playloopsound( "veh_aastrike_flyover_loop" );
wait( (length/2) / 2000 );
self stopLoopSound();
self playloopsound( "veh_aastrike_flyover_outgoing_loop" );
}
doFlyBy( showIcon )
{
self endon ( "disconnect" );
randSpawn = randomInt( level.spawnPoints.size - 1 );
targetPos = level.spawnPoints[randSpawn].origin * (1,1,0);
backDist = 20000;
forwardDist = 20000;
heightEnt = GetEnt( "airstrikeheight", "targetname" );
upVector = (0, 0, heightEnt.origin[2] + randomIntRange(-100, 600) );
forward = AnglesToForward( (0,randomInt(45),0) );
startpos = targetPos + upVector + forward * backDist * -1;
endPos = targetPos + upVector + forward * forwardDist;
plane2StartPos = startpos + ( randomIntRange(400,500), randomIntRange(400,500), randomIntRange(200,300) );
plane2EndPos = endPos + ( randomIntRange(400,500), randomIntRange(400,500), randomIntRange(200,300) );
if ( showIcon )
plane = spawnplane( self, "script_model", startpos, "hud_minimap_harrier_green", "hud_minimap_harrier_red" );
else
plane = spawnplane( self, "script_model", startpos );
plane2 = spawnplane( self, "script_model", plane2StartPos );
if ( self.team == "allies" )
{
plane setModel( "vehicle_av8b_harrier_jet_mp" );
plane2 setModel( "vehicle_av8b_harrier_jet_mp" );
}
else
{
plane setModel( "vehicle_av8b_harrier_jet_opfor_mp" );
plane2 setModel( "vehicle_av8b_harrier_jet_opfor_mp" );
}
plane.angles = vectorToAngles( endPos-startPos );
plane playloopsound( "veh_aastrike_flyover_loop" );
plane thread playPlaneFx();
plane2.angles = vectorToAngles( endPos-plane2StartPos );
plane2 thread playPlaneFx();
length = distance(startPos, endPos);
plane moveTo( endPos * 2, length/1800, 0, 0 );
wait( randomFloatRange( .25, .5 ) );
plane2 moveTo( plane2EndPos * 2, length/1800, 0, 0 );
wait( length/1600 );
plane delete();
plane2 delete();
}
playPlaneFx()
{
self endon ( "death" );
wait( 0.5);
playfxontag( level.fx_airstrike_afterburner, self, "tag_engine_right" );
wait( 0.5);
playfxontag( level.fx_airstrike_afterburner, self, "tag_engine_left" );
wait( 0.5);
playfxontag( level.fx_airstrike_contrail, self, "tag_right_wingtip" );
wait( 0.5);
playfxontag( level.fx_airstrike_contrail, self, "tag_left_wingtip" );
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,214 @@
#include maps\mp\_utility;
#include maps\mp\gametypes\_hud_util;
#include common_scripts\utility;
#include maps\mp\agents\_agent_utility;
#include maps\mp\gametypes\_damage;
#include maps\mp\bots\_bots_util;
#include maps\mp\bots\_bots_strategy;
//===========================================
// constants
//===========================================
CONST_MAX_ACTIVE_KILLSTREAK_AGENTS_PER_GAME = 5;
CONST_MAX_ACTIVE_KILLSTREAK_AGENTS_PER_PLAYER = 2;
//===========================================
// init
//===========================================
init()
{
level.killStreakFuncs["agent"] = ::tryUseSquadmate;
level.killStreakFuncs["recon_agent"] = ::tryUseReconSquadmate;
}
//===========================================
// setup_callbacks
//===========================================
setup_callbacks()
{
level.agent_funcs["squadmate"] = level.agent_funcs["player"];
level.agent_funcs["squadmate"]["think"] = ::squadmate_agent_think;
level.agent_funcs["squadmate"]["on_killed"] = ::on_agent_squadmate_killed;
level.agent_funcs["squadmate"]["on_damaged"] = maps\mp\agents\_agents::on_agent_player_damaged;
level.agent_funcs["squadmate"]["gametype_update"]= ::no_gametype_update;
}
no_gametype_update()
{
return false;
}
//===========================================
// tryUseSquadmate
//===========================================
tryUseSquadmate( lifeId, streakName )
{
return useSquadmate( "agent" );
}
//===========================================
// tryUseReconSquadmate
//===========================================
tryUseReconSquadmate( lifeId, streakName )
{
return useSquadmate( "reconAgent" );
}
//===========================================
// useSquadmate
//===========================================
useSquadmate( killStreakType )
{
// limit the number of active "squadmate" agents allowed per game
if( getNumActiveAgents( "squadmate" ) >= CONST_MAX_ACTIVE_KILLSTREAK_AGENTS_PER_GAME )
{
self iPrintLnBold( &"KILLSTREAKS_AGENT_MAX" );
return false;
}
// limit the number of active agents allowed per player
if( getNumOwnedActiveAgents( self ) >= CONST_MAX_ACTIVE_KILLSTREAK_AGENTS_PER_PLAYER )
{
self iPrintLnBold( &"KILLSTREAKS_AGENT_MAX" );
return false;
}
// try to spawn the agent on a path node near the player
nearestPathNode = self getValidSpawnPathNodeNearPlayer( false, true );
if( !IsDefined(nearestPathNode) )
{
return false;
}
// make sure the player is still alive before the agent trys to spawn on the player
if( !isReallyAlive(self) )
{
return false;
}
spawnOrigin = nearestPathNode.origin;
spawnAngles = VectorToAngles( self.origin - nearestPathNode.origin );
agent = maps\mp\agents\_agents::add_humanoid_agent( "squadmate", self.team, undefined, spawnOrigin, spawnAngles, self, false, false, "veteran" );
if( !IsDefined( agent ) )
{
self iPrintLnBold( &"KILLSTREAKS_AGENT_MAX" );
return false;
}
agent.killStreakType = killStreakType;
if ( agent.killStreakType == "reconAgent" )
{
// 2013-06-26 wallace
// At the time of this comment, giveLoadout runs and finishes execution immediately
// We run sendAgentWeaponNotify and finishReconAgentLoadout since they block until giveLoadout sends its notify
agent thread sendAgentWeaponNotify( "iw6_riotshield_mp" );
agent thread finishReconAgentLoadout();
agent thread maps\mp\gametypes\_class::giveLoadout( self.pers["team"], "reconAgent", false );
agent maps\mp\agents\_agent_common::set_agent_health( 250 );
agent maps\mp\perks\_perkfunctions::setLightArmor();
}
else
{
agent maps\mp\perks\_perkfunctions::setLightArmor();
}
agent _setNameplateMaterial( "player_name_bg_green_agent", "player_name_bg_red_agent" );
self maps\mp\_matchdata::logKillstreakEvent( agent.killStreakType, self.origin );
return true;
}
finishReconAgentLoadout()
{
self endon( "death" );
self endon( "disconnect" );
level endon( "game_ended" );
self waittill( "giveLoadout" );
self maps\mp\perks\_perkfunctions::setLightArmor();
self givePerk( "specialty_quickswap", false );
self givePerk( "specialty_regenfaster", false );
// 2014-04-24 JC: Reduced accuracy on Squad Mate Support Streak
self BotSetDifficultySetting( "minInaccuracy", 1.5 * self BotGetDifficultySetting( "minInaccuracy" ) );
self BotSetDifficultySetting( "maxInaccuracy", 1.5 * self BotGetDifficultySetting( "maxInaccuracy" ) );
// 2014-04-24 JC: Reduced fire rate on Squad Mate Support Streak
// min: from 200 to 300
// max: from 400 to 500
self BotSetDifficultySetting( "minFireTime", 1.5 * self BotGetDifficultySetting( "minFireTime" ) );
self BotSetDifficultySetting( "maxFireTime", 1.25 * self BotGetDifficultySetting( "maxFireTime" ) );
}
//===========================================
// sendAgentWeaponNotify
//===========================================
sendAgentWeaponNotify( weaponName )
{
self endon( "death" );
self endon( "disconnect" );
level endon( "game_ended" );
self waittill( "giveLoadout" );
if( !IsDefined(weaponName) )
weaponName = "iw6_riotshield_mp";
self notify( "weapon_change", weaponName );
}
//=======================================================
// squadmate_agent_think
//=======================================================
squadmate_agent_think()
{
self endon( "death" );
self endon( "disconnect" );
self endon( "owner_disconnect" );
level endon( "game_ended" );
while(1)
{
// Squad mate agent prefers to have shield out when not in combat and guarding player
self BotSetFlag( "prefer_shield_out", true );
handled_by_gametype = self [[ self agentFunc("gametype_update") ]]();
if ( !handled_by_gametype )
{
if ( !self bot_is_guarding_player( self.owner ) )
self bot_guard_player( self.owner, 350 );
}
wait(0.05);
}
}
//=======================================================
// on_agent_squadmate_killed
//=======================================================
on_agent_squadmate_killed(eInflictor, eAttacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, timeOffset, deathAnimDuration)
{
self maps\mp\agents\_agents::on_humanoid_agent_killed_common(eInflictor, eAttacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, timeOffset, deathAnimDuration, false);
// award XP for killing agents
if( IsPlayer( eAttacker ) && IsDefined(self.owner) && eAttacker != self.owner )
{
self.owner leaderDialogOnPlayer( "squad_killed" );
self maps\mp\gametypes\_damage::onKillstreakKilled( eAttacker, sWeapon, sMeansOfDeath, iDamage, "destroyed_squad_mate" );
}
self maps\mp\agents\_agent_utility::deactivateAgent();
}

View File

@ -0,0 +1,298 @@
#include maps\mp\_utility;
#include common_scripts\utility;
// replacement for EMP
// only affects aircraft
// remarkably similar to aastrike, should probably get rid of that...
KS_NAME = "air_superiority";
kProjectileName = "aamissile_projectile_mp";
init()
{
config = SpawnStruct();
config.modelNames = [];
config.modelNames[ "allies" ] = "vehicle_a10_warthog_iw6_mp";
config.modelNames[ "axis" ] = "vehicle_a10_warthog_iw6_mp";
config.inboundSfx = "veh_mig29_dist_loop";
//config.inboundSfx = "veh_aastrike_flyover_loop";
//config.outboundSfx = "veh_aastrike_flyover_outgoing_loop";
config.compassIconFriendly = "compass_objpoint_airstrike_friendly";
config.compassIconEnemy = "compass_objpoint_airstrike_busy";
// sonic boom?
config.speed = 4000;
config.halfDistance = 20000;
config.distFromPlayer = 4000;
config.heightRange = 250;
//config.attackTime = 2.0;
config.numMissileVolleys = 3;
config.outboundFlightAnim = "airstrike_mp_roll";
config.sonicBoomSfx = "veh_mig29_sonic_boom";
config.onAttackDelegate = ::attackEnemyAircraft;
config.onFlybyCompleteDelegate = ::cleanupFlyby;
config.xpPopup = "destroyed_air_superiority";
config.callout = "callout_destroyed_air_superiority";
config.voDestroyed = undefined;
config.killCamOffset = (-800, 0, 200);
level.planeConfigs[ KS_NAME ] = config;
level.killstreakFuncs[KS_NAME] = ::onUse;
level.teamAirDenied["axis"] = false;
level.teamAirDenied["allies"] = false;
}
onUse( lifeId, streakName )
{
assert( isDefined( self ) );
// check for active air_superiority strikes
otherTeam = getOtherTeam( self.team );
if ( (level.teamBased && level.teamAirDenied[ otherTeam] )
|| (!level.teamBased && IsDefined( level.airDeniedPlayer ) && level.airDeniedPlayer == self )
)
{
self IPrintLnBold( &"KILLSTREAKS_AIR_SPACE_TOO_CROWDED" );
return false;
}
else
{
// scramble the fighters
self thread doStrike( lifeId, KS_NAME );
self maps\mp\_matchdata::logKillstreakEvent( "air_superiority", self.origin );
self thread teamPlayerCardSplash( "used_air_superiority", self );
return true;
}
}
doStrike( lifeId, streakName )
{
config = level.planeConfigs[ streakName ];
flightPlan = self maps\mp\killstreaks\_plane::getPlaneFlightPlan( config.distFromPlayer );
// play inbound vo
wait( 1 );
targetTeam = getOtherTeam(self.team);
level.teamAirDenied[targetTeam] = true;
level.airDeniedPlayer = self;
doOneFlyby( streakName, lifeId, flightPlan.targetPos, flightPlan.flightDir, flightPlan.height);
self waittill( "aa_flyby_complete" );
// coming back around vo
wait( 2 );
maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone();
// don't do second flyby if owner has disconnected
if ( IsDefined( self ) )
{
doOneFlyby( streakName, lifeId, flightPlan.targetPos, -1 * flightPlan.flightDir, flightPlan.height );
self waittill( "aa_flyby_complete" );
}
level.teamAirDenied[targetTeam] = false;
level.airDeniedPlayer = undefined;
// play outbound vo
// should check if there are still enemy aircraft in the air and play appropriate vo
}
doOneFlyby( streakName, lifeId, targetPos, dir, flyHeight )
{
config = level.planeConfigs[ streakName ];
// absolute height should be derived from the heightEnt
flightPath = maps\mp\killstreaks\_plane::getFlightPath( targetPos, dir, config.halfDistance, true, flyHeight, config.speed, -0.5 * config.halfDistance, streakName );
// may want to break this up into spawn, move, cleanup components
// so that we can reuse the plane
level thread maps\mp\killstreaks\_plane::doFlyby( lifeId, self, lifeId,
flightPath["startPoint"] + (0, 0, randomInt(config.heightRange) ),
flightPath["endPoint"] + (0, 0, randomInt(config.heightRange) ),
flightPath["attackTime"],
flightPath["flyTime"],
dir,
streakName );
}
attackEnemyAircraft( pathEnd, flyTime, beginAttackTime, owner, streakName ) // self == plane
{
self endon( "death" );
self.owner endon( "killstreak_disowned" );
level endon( "game_ended" );
wait (beginAttackTime);
targets = findAllTargets( self.owner, self.team );
config = level.planeConfigs[ streakName ];
numVolleys = config.numMissileVolleys;
targetIndex = targets.size - 1;
while (targetIndex >= 0
&& numVolleys > 0
)
{
target = targets[ targetIndex ];
if ( IsDefined( target ) && IsAlive( target ) )
{
self fireAtTarget( target );
numVolleys--;
wait ( 1 );
}
targetIndex--;
}
}
cleanupFlyby( owner, plane, streakName )
{
owner notify( "aa_flyby_complete" );
}
// curTargetsStruct is a struct that holds the array of targets
// !!! use this hack since arrays are passed by value, while structs are passed by reference
findTargetsOfType( attacker, victimTeam, checkFunc, candidateList, curTargetsStruct )
{
if ( IsDefined( candidateList ) )
{
foreach ( target in candidateList )
{
if ( [[ checkFunc ]]( attacker, victimTeam, target ) )
{
curTargetsStruct.targets[ curTargetsStruct.targets.size ] = target;
}
}
}
return curTargetsStruct;
}
// unlike the aa strike, we only search for targets once
// because we block new air strikes from behing launched
// also: probably could flip the order that targets are acquired if we want
// the jets to go after low-cost killstreaks first
findAllTargets( attacker, attackerTeam )
{
wrapper = SpawnStruct();
wrapper.targets = [];
// ok, I'm sorry for function pointers, but it makes me sad to do unnecessary if checks all the time
// isEnemyFunc will test if the target belongs to an enemy
isEnemyFunc = undefined;
if ( level.teamBased )
{
isEnemyFunc = ::isValidTeamTarget;
}
else
{
isEnemyFunc = ::isValidFFATarget;
}
victimTeam = undefined;
if ( IsDefined( attackerTeam ) )
{
victimTeam = getOtherTeam( attackerTeam );
}
// 2013-09-02 wallace: Since arrays are passed by value (or so JoeC tells me)
// we will wrap up the targets array in a struct that is past by reference
// this means that each call to findTargetsOfType is adding targets to the SAME array, not a copy
// Destroy player controlled and higher level-KS's last, so put them in the front of the arraw
findTargetsOfType( attacker, victimTeam, isEnemyFunc, level.heli_pilot, wrapper );
if ( IsDefined( level.lbSniper ) )
{
if ( [[ isEnemyFunc ]]( attacker, victimTeam, level.lbSniper ) )
{
wrapper.targets[ wrapper.targets.size ] = level.lbSniper;
}
}
findTargetsOfType( attacker, victimTeam, isEnemyFunc, level.planes, wrapper );
// 2013-09-03 wallace: ugh, this is stupid. Vanguard puts itself in both remote_uav and littlebird arrays. So, don't use remote_uav as possible targets
// findTargetsOfType( attacker, victimTeam, checkFunc, level.remote_uav, wrapper );
findTargetsOfType( attacker, victimTeam, isEnemyFunc, level.littleBirds, wrapper );
findTargetsOfType( attacker, victimTeam, isEnemyFunc, level.helis, wrapper );
return wrapper.targets;
}
fireAtTarget( curTarget ) // self == plane
{
if ( !isDefined(curTarget) )
return;
// do this check in case the plane's owner disconnects mid flight
// we still want this pass to finish
owner = undefined;
if ( IsDefined( self.owner ) )
owner = self.owner;
forwardVec = 384 * AnglesToForward( self.angles );
startpoint = self GetTagOrigin( "tag_missile_1" ) + forwardVec;
rocket1 = MagicBullet( kProjectileName, startPoint, startPoint + forwardVec, owner );
rocket1.vehicle_fired_from = self;
startpoint = self GetTagOrigin( "tag_missile_2" ) + forwardVec;
rocket2 = MagicBullet( kProjectileName, startPoint, startPoint + forwardVec, owner );
rocket2.vehicle_fired_from = self;
missiles = [ rocket1, rocket2 ];
curTarget notify( "targeted_by_incoming_missile", missiles );
self thread startMissileGuidance( curTarget, 0.25, missiles );
}
startMissileGuidance( curTarget, igniteTime, missileArray )
{
wait( igniteTime );
if ( IsDefined( curTarget ) )
{
targetPoint = undefined;
//AH: HACK: The harrier doesn't have the tag_missile_target, but it does have a tag_body.
// The code works fine without this check, but GetTagOrigin throws an SRE if the tag does not exist.
if ( curTarget.model != "vehicle_av8b_harrier_jet_mp" )
targetPoint = curTarget GetTagOrigin( "tag_missile_target" );
if ( !IsDefined( targetPoint ) )
{
targetPoint = curTarget GetTagOrigin( "tag_body" );
}
targetOffset = targetPoint - curTarget.origin;
foreach ( missile in missileArray )
{
if ( IsValidMissile( missile ) )
{
missile Missile_SetTargetEnt( curTarget, targetOffset );
missile Missile_SetFlightmodeDirect();
}
}
}
}
destroyActiveVehicles( attacker, victimTeam )
{
// thread all of the things that need to get destroyed, this way we can put frame waits in between each destruction so we don't hit the server with a lot at one time
maps\mp\killstreaks\_killstreaks::destroyTargetArray( attacker, victimTeam, "aamissile_projectile_mp", level.helis );
maps\mp\killstreaks\_killstreaks::destroyTargetArray( attacker, victimTeam, "aamissile_projectile_mp", level.littleBirds );
maps\mp\killstreaks\_killstreaks::destroyTargetArray( attacker, victimTeam, "aamissile_projectile_mp", level.heli_pilot );
if ( IsDefined( level.lbSniper ) )
{
// kind of hack, but destroyTargets does a lot of needed setup
tempArray = [];
tempArray[0] = level.lbSniper;
maps\mp\killstreaks\_killstreaks::destroyTargetArray( attacker, victimTeam, "aamissile_projectile_mp", tempArray );
}
maps\mp\killstreaks\_killstreaks::destroyTargetArray( attacker, victimTeam, "aamissile_projectile_mp", level.remote_uav );
maps\mp\killstreaks\_killstreaks::destroyTargetArray( attacker, victimTeam, "aamissile_projectile_mp", level.planes );
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,134 @@
#include maps\mp\_utility;
#include maps\mp\gametypes\_hud_util;
#include common_scripts\utility;
init()
{
level.killStreakFuncs["auto_shotgun"] = ::tryUseAutoShotgun;
level.killstreakSetupFuncs["auto_shotgun"] = ::shotgunSetup;
level.killStreakFuncs["thumper"] = ::tryUseThumper;
level.killstreakSetupFuncs["thumper"] = ::thumperSetup;
thread onPlayerConnect();
}
shotgunSetup()
{
self giveMaxAmmo( "aa12_mp" );
self thread saveWeaponAmmoOnDeath( "aa12_mp" );
}
tryUseAutoShotgun( lifeId )
{
self thread removeWeaponOnOutOfAmmo( "aa12_mp" );
return true;
}
thumperSetup()
{
self giveMaxAmmo( "m79_mp" );
self thread saveWeaponAmmoOnDeath( "m79_mp" );
}
tryUseThumper()
{
self thread removeWeaponOnOutOfAmmo( "m79_mp" );
return true;
}
onPlayerConnect()
{
for(;;)
{
level waittill( "connected", player );
player thread onPlayerSpawned();
}
}
onPlayerSpawned()
{
self endon( "disconnect" );
for(;;)
{
self waittill( "spawned_player" );
if ( !isDefined( self.pers["ksWeapon_clip_ammo"] ) || !isDefined( self.pers["ksWeapon_name"] ) )
continue;
weaponName = self.pers["ksWeapon_name"];
if ( isDefined( self.pers["killstreak"] ) && getKillstreakWeapon( self.pers["killstreak"] ) != weaponName )
{
self.pers["ksWeapon_name"] = undefined;
self.pers["ksWeapon_clip_ammo"] = undefined;
self.pers["ksWeapon_stock_ammo"] = undefined;
continue;
}
self maps\mp\killstreaks\_killstreaks::giveKillstreakWeapon( weaponName );
self setWeaponAmmoStock( weaponName, self.pers["ksWeapon_stock_ammo"] );
self setWeaponAmmoClip( weaponName, self.pers["ksWeapon_clip_ammo"] );
self thread removeWeaponOnOutOfAmmo( weaponName );
self thread saveWeaponAmmoOnDeath( weaponName );
}
}
saveWeaponAmmoOnDeath( weaponName )
{
self endon ( "disconnect" );
self endon ( "got_killstreak" );
self notify( "saveWeaponAmmoOnDeath" );
self endon( "saveWeaponAmmoOnDeath" );
self.pers["ksWeapon_name"] = undefined;
self.pers["ksWeapon_clip_ammo"] = undefined;
self.pers["ksWeapon_stock_ammo"] = undefined;
self waittill ( "death" );
if ( !self hasWeapon( weaponName ) )
return;
self.pers["ksWeapon_name"] = weaponName;
self.pers["ksWeapon_clip_ammo"] = self getWeaponAmmoClip( weaponName );
self.pers["ksWeapon_stock_ammo"] = self getWeaponAmmoStock( weaponName );
}
removeWeaponOnOutOfAmmo( weaponName )
{
self endon ( "disconnect" );
self endon ( "death" );
self notify( weaponName + "_ammo_monitor" );
self endon( weaponName + "_ammo_monitor" );
while( 1 )
{
self waittill ( "end_firing" );
if ( self getCurrentWeapon() != weaponName )
continue;
total_ammo = self getWeaponAmmoClip( weaponName ) + self getWeaponAmmoStock( weaponName );
if ( total_ammo )
continue;
self takeWeapon( weaponName );
return;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,846 @@
#include maps\mp\_utility;
#include common_scripts\utility;
#include maps\mp\gametypes\_hud_util;
/*
Deployable box killstreaks: the player will be able to place a box in the world and teammates can grab items from it
this will be used on multiple killstreaks where you can place a box in the world with something in it
*/
BOX_TIMEOUT_UPDATE_INTERVAL = 1.0;
DEFAULT_USE_TIME = 3000;
BOX_DEFAULT_HEALTH = 999999; // so that boxes aren't killed in code
init()
{
if ( !IsDefined( level.boxSettings ) )
{
level.boxSettings = [];
}
}
///////////////////////////////////////////////////
// MARKER FUNCTIONS
// 2012-06-21 wallace
// Stole an updated version from _uplink.gsc. Should probably unify all these funcs eventually
//////////////////////////////////////////////////
beginDeployableViaMarker( lifeId, boxType )
{
self thread watchDeployableMarkerCancel( boxType );
self thread watchDeployableMarkerPlacement( boxType, lifeId );
while ( true )
{
result = self waittill_any_return( "deployable_canceled", "deployable_deployed", "death", "disconnect" );
return ( result == "deployable_deployed" );
}
}
tryUseDeployable( lifeId, boxType ) // self == player
{
self thread watchDeployableMarkerCancel( boxType );
self thread watchDeployableMarkerPlacement( boxType, lifeId );
while ( true )
{
result = self waittill_any_return( "deployable_canceled", "deployable_deployed", "death", "disconnect" );
return ( result == "deployable_deployed" );
}
}
watchDeployableMarkerCancel( boxType )
{
self endon( "death" );
self endon( "disconnect" );
self endon( "deployable_deployed" );
boxConfig = level.boxSettings[ boxType ];
currentWeapon = self getCurrentWeapon();
while( currentWeapon == boxConfig.weaponInfo )
{
self waittill( "weapon_change", currentWeapon );
}
self notify( "deployable_canceled" );
}
watchDeployableMarkerPlacement( boxType, lifeId )
{
self endon( "spawned_player" ); // you shouldn't do endon( "death" ) here because this thread needs to run
self endon( "disconnect" );
self endon( "deployable_canceled" );
while( true )
{
self waittill( "grenade_fire", marker, weaponName );
if( isReallyAlive(self) )
{
break;
}
else
{
marker Delete();
}
}
marker MakeCollideWithItemClip( true );
self notify( "deployable_deployed" );
marker.owner = self;
marker.weaponName = weaponName;
self.marker = marker;
marker PlaySoundToPlayer( level.boxSettings[ boxType ].deployedSfx, self );
marker thread markerActivate( lifeId, boxType, ::box_setActive );
}
override_box_moving_platform_death( data )
{
self notify( "death" ); // we're doing this here instead of letting the mover code just delete us so that we can run our necessary clean-up functionality (like removal of the objective marker from the minimap)
}
markerActivate( lifeId, boxType, usedCallback ) // self == marker
{
self notify( "markerActivate" );
self endon( "markerActivate" );
//self waittill( "explode", position );
self waittill( "missile_stuck" );
owner = self.owner;
position = self.origin;
if ( !isDefined( owner ) )
return;
box = createBoxForPlayer( boxType, position, owner );
// For moving platforms.
data = SpawnStruct();
data.linkParent = self GetLinkedParent();
//fixes wall hack exploit with linked items
if ( isDefined( data.linkParent ) && isDefined( data.linkParent.model ) && DeployableExclusion( data.linkParent.model ) )
{
box.origin = data.linkParent.origin;
grandParent = data.linkParent GetLinkedParent();
if ( isDefined( grandParent ) )
data.linkParent = grandParent;
else
data.linkParent = undefined;
}
data.deathOverrideCallback = ::override_box_moving_platform_death;
box thread maps\mp\_movers::handle_moving_platforms( data );
box.moving_platform = data.linkParent;
box SetOtherEnt(owner);
// ES - 2/24/14 - This waitframe is causing an issue where, when deployed on a moving platform, the "death" notification is sent instantly, but is never caught.
wait 0.05;
//self playSound( "sentry_gun_beep" );
box thread [[ usedCallback ]]();
self delete();
if( IsDefined(box) && (box touchingBadTrigger()) )
{
box notify( "death" );
}
}
DeployableExclusion( parentModel )
{
if ( parentModel == "mp_satcom" )
return true;
else if ( IsSubStr( parentModel, "paris_catacombs_iron" ) )
return true;
else if ( IsSubStr( parentModel, "mp_warhawk_iron_gate" ) )
return true;
return false;
}
isHoldingDeployableBox()
{
curWeap = self GetCurrentWeapon();
if ( IsDefined( curWeap ) )
{
foreach( deplBoxWeap in level.boxSettings )
{
if ( curWeap == deplBoxWeap.weaponInfo )
return true;
}
}
return false;
}
///////////////////////////////////////////////////
// END MARKER FUNCTIONS
//////////////////////////////////////////////////
///////////////////////////////////////////////////
// BOX HANDLER FUNCTIONS
//////////////////////////////////////////////////
createBoxForPlayer( boxType, position, owner )
{
assertEx( isDefined( owner ), "createBoxForPlayer() called without owner specified" );
boxConfig = level.boxSettings[ boxType ];
box = Spawn( "script_model", position - (0,0,1) );
box setModel( boxConfig.modelBase );
box.health = BOX_DEFAULT_HEALTH;
box.maxHealth = boxConfig.maxHealth;
box.angles = owner.angles;
box.boxType = boxType;
box.owner = owner;
box.team = owner.team;
box.id = boxConfig.id;
if ( IsDefined( boxConfig.dpadName ) )
{
box.dpadName = boxConfig.dpadName;
}
if ( IsDefined( boxConfig.maxUses ) )
{
box.usesRemaining = boxConfig.maxUses;
}
box box_setInactive();
box thread box_handleOwnerDisconnect();
box addBoxToLevelArray();
return box;
}
box_setActive( skipOwnerUse ) // self == box
{
self setCursorHint( "HINT_NOICON" );
boxConfig = level.boxSettings[ self.boxType ];
self setHintString( boxConfig.hintString );
self.inUse = false;
curObjID = maps\mp\gametypes\_gameobjects::getNextObjID();
Objective_Add( curObjID, "invisible", (0,0,0) );
if ( !IsDefined( self GetLinkedParent() ) )
Objective_Position( curObjID, self.origin );
else
Objective_OnEntity( curObjID, self );
Objective_State( curObjID, "active" );
Objective_Icon( curObjID, boxConfig.shaderName );
self.objIdFriendly = curObjID;
if ( level.teamBased )
{
Objective_Team( curObjID, self.team );
foreach ( player in level.players )
{
if ( self.team == player.team
&& (!IsDefined(boxConfig.canUseCallback) || player [[ boxConfig.canUseCallback ]](self) )
)
{
self box_SetIcon( player, boxConfig.streakName, boxConfig.headIconOffset );
}
}
}
else
{
Objective_Player( curObjID, self.owner GetEntityNumber() );
if( !IsDefined(boxConfig.canUseCallback) || self.owner [[ boxConfig.canUseCallback ]](self) )
{
self box_SetIcon( self.owner, boxConfig.streakName, boxConfig.headIconOffset );
}
}
self MakeUsable();
self.isUsable = true;
self SetCanDamage( true );
self thread box_handleDamage();
self thread box_handleDeath();
self thread box_timeOut();
self thread disableWhenJuggernaut();
self make_entity_sentient_mp( self.team, true );
if ( IsSentient( self ) )
{
self SetThreatBiasGroup( "DogsDontAttack" );
}
if ( IsDefined( self.owner ) )
self.owner notify( "new_deployable_box", self );
if (level.teamBased)
{
foreach ( player in level.participants )
{
_box_setActiveHelper( player, self.team == player.team, boxConfig.canUseCallback );
// handle team switches for human players
if ( !IsAI( player ) )
{
self thread box_playerJoinedTeam( player );
}
}
}
else
{
foreach ( player in level.participants )
{
_box_setActiveHelper( player, IsDefined( self.owner ) && self.owner == player, boxConfig.canUseCallback );
}
}
level thread teamPlayerCardSplash( boxConfig.splashName, self.owner, self.team );
self thread box_playerConnected();
self thread box_agentConnected();
if ( IsDefined( boxConfig.onDeployCallback ) )
{
self [[ boxConfig.onDeployCallback ]]( boxConfig );
}
self thread createBombSquadModel( self.boxType );
}
_box_setActiveHelper( player, bActivate, canUseFunc )
{
if ( bActivate )
{
if ( !IsDefined( canUseFunc ) || player [[ canUseFunc ]](self) )
{
self box_enablePlayerUse( player );
}
else
{
self box_disablePlayerUse( player );
// if this player is already a juggernaut then when they die, let them use the box
self thread doubleDip( player );
}
self thread boxThink( player );
}
else
{
self box_disablePlayerUse( player );
}
}
box_playerConnected() // self == box
{
self endon( "death" );
// when new players connect they need a boxthink thread run on them
while( true )
{
level waittill( "connected", player );
self childthread box_waittill_player_spawn_and_add_box( player );
}
}
box_agentConnected() // self == box
{
self endon( "death" );
// when new agents connect they need a boxthink thread run on them
while( true )
{
level waittill( "spawned_agent_player", agent );
self box_addBoxForPlayer( agent );
}
}
box_waittill_player_spawn_and_add_box( player ) // self == box
{
player waittill( "spawned_player" );
if ( level.teamBased )
{
self box_addBoxForPlayer( player );
// handle team switches for late joins
self thread box_playerJoinedTeam( player );
}
}
box_playerJoinedTeam( player ) // self == box
{
self endon( "death" );
player endon( "disconnect" );
// when new players connect they need a boxthink thread run on them
while( true )
{
player waittill( "joined_team" );
if ( level.teamBased )
{
self box_addBoxForPlayer( player );
}
}
}
box_addBoxForPlayer( player ) // self == box
{
if ( self.team == player.team )
{
self box_enablePlayerUse( player );
self thread boxThink( player );
self box_SetIcon( player, level.boxSettings[ self.boxType ].streakName, level.boxSettings[ self.boxType ].headIconOffset );
}
else
{
self box_disablePlayerUse( player );
self maps\mp\_entityheadIcons::setHeadIcon( player, "", (0,0,0) );
}
}
box_SetIcon( player, streakName, vOffset )
{
self maps\mp\_entityheadIcons::setHeadIcon( player, getKillstreakOverheadIcon( streakName ), (0, 0, vOffset), 14, 14, undefined, undefined, undefined, undefined, undefined, false );
}
box_enablePlayerUse( player ) // self == box
{
if ( IsPlayer(player) )
self EnablePlayerUse( player );
self.disabled_use_for[player GetEntityNumber()] = false;
}
box_disablePlayerUse( player ) // self == box
{
if ( IsPlayer(player) )
self DisablePlayerUse( player );
self.disabled_use_for[player GetEntityNumber()] = true;
}
box_setInactive()
{
self makeUnusable();
self.isUsable = false;
self maps\mp\_entityheadIcons::setHeadIcon( "none", "", (0,0,0) );
if ( isDefined( self.objIdFriendly ) )
_objective_delete( self.objIdFriendly );
}
box_handleDamage() // self == box
{
boxConfig = level.boxSettings[ self.boxType ];
self maps\mp\gametypes\_damage::monitorDamage(
boxConfig.maxHealth,
boxConfig.damageFeedback,
::box_handleDeathDamage,
::box_ModifyDamage,
true // isKillstreak
);
}
box_ModifyDamage( attacker, weapon, type, damage )
{
modifiedDamage = damage;
boxConfig = level.boxSettings[ self.boxType ];
if ( boxConfig.allowMeleeDamage )
{
modifiedDamage = self maps\mp\gametypes\_damage::handleMeleeDamage( weapon, type, modifiedDamage );
}
modifiedDamage = self maps\mp\gametypes\_damage::handleMissileDamage( weapon, type, modifiedDamage );
modifiedDamage = self maps\mp\gametypes\_damage::handleGrenadeDamage( weapon, type, modifiedDamage );
modifiedDamage = self maps\mp\gametypes\_damage::handleAPDamage( weapon, type, modifiedDamage, attacker );
return modifiedDamage;
}
box_handleDeathDamage( attacker, weapon, type, damage )
{
boxConfig = level.boxSettings[ self.boxType ];
notifyAttacker = self maps\mp\gametypes\_damage::onKillstreakKilled( attacker, weapon, type, damage, boxconfig.xpPopup, boxConfig.voDestroyed );
if ( notifyAttacker )
{
attacker notify( "destroyed_equipment" );
}
}
box_handleDeath()
{
self waittill ( "death" );
// this handles cases of deletion
if ( !isDefined( self ) )
return;
self box_setInactive();
self removeBoxFromLevelArray();
boxConfig = level.boxSettings[ self.boxType ];
PlayFX( boxConfig.deathVfx, self.origin );
self PlaySound( "mp_killstreak_disappear" );
// 2013-03-08 wsh: whould probably validate all the used fields...
if ( IsDefined( boxConfig.deathDamageMax ) )
{
owner = undefined;
if ( IsDefined(self.owner) )
owner = self.owner;
// somewhat hacky:
// shift the origin of the damage because it'll collide with the box otherwise
// we could also apply the damage after we delete the item?
RadiusDamage( self.origin + (0, 0, boxConfig.headIconOffset),
boxConfig.deathDamageRadius,
boxConfig.deathDamageMax,
boxConfig.deathDamageMin,
owner,
"MOD_EXPLOSIVE",
boxConfig.deathWeaponInfo
);
}
self notify( "deleting" );
self delete();
}
box_handleOwnerDisconnect() // self == box
{
self endon ( "death" );
level endon ( "game_ended" );
self notify ( "box_handleOwner" );
self endon ( "box_handleOwner" );
self.owner waittill( "killstreak_disowned" );
self notify( "death" );
}
boxThink( player )
{
self endon ( "death" );
self thread boxCaptureThink( player );
if ( !IsDefined(player.boxes) )
{
player.boxes = [];
}
player.boxes[player.boxes.size] = self;
boxConfig = level.boxSettings[ self.boxType ];
for ( ;; )
{
self waittill ( "captured", capturer );
if (capturer == player)
{
player PlayLocalSound( boxConfig.onUseSfx );
if ( IsDefined( boxConfig.onuseCallback ) )
{
player [[ boxConfig.onUseCallback ]]( self );
}
// if this is not the owner then give the owner some xp
if( IsDefined( self.owner ) && player != self.owner )
{
self.owner thread maps\mp\gametypes\_rank::xpEventPopup( boxConfig.event );
self.owner thread maps\mp\gametypes\_rank::giveRankXP( "support", boxConfig.useXP );
}
if ( IsDefined( self.usesRemaining ) )
{
self.usesRemaining--;
if ( self.usesRemaining == 0)
{
self box_leave();
break;
}
}
if ( IsDefined( boxConfig.canUseOtherBoxes ) && boxConfig.canUseOtherBoxes )
{
// don't let the player use any other boxes
foreach( box in level.deployable_box[ boxConfig.streakName ] )
{
box maps\mp\killstreaks\_deployablebox::box_disablePlayerUse( self );
box maps\mp\_entityheadIcons::setHeadIcon( self, "", (0,0,0) );
box thread maps\mp\killstreaks\_deployablebox::doubleDip( self );
}
}
else
{
self maps\mp\_entityheadIcons::setHeadIcon( player, "", (0,0,0) );
self box_disablePlayerUse( player );
self thread doubleDip( player );
}
}
}
}
doubleDip( player ) // self == box
{
self endon( "death" );
player endon( "disconnect" );
// once they die, let them take from the box again
player waittill( "death" );
if( level.teamBased )
{
if( self.team == player.team )
{
self box_SetIcon( player, level.boxSettings[ self.boxType ].streakName, level.boxSettings[ self.boxType ].headIconOffset );
self box_enablePlayerUse( player );
}
}
else
{
if( IsDefined( self.owner ) && self.owner == player )
{
self box_SetIcon( player, level.boxSettings[ self.boxType ].streakName, level.boxSettings[ self.boxType ].headIconOffset );
self box_enablePlayerUse( player );
}
}
}
boxCaptureThink( player ) // self == box
{
level endon( "game_ended" );
while( isDefined( self ) )
{
self waittill( "trigger", tiggerer );
if (
IsDefined( level.boxSettings[ self.boxType ].noUseKillstreak )
&& level.boxSettings[ self.boxType ].noUseKillstreak
&& isKillstreakWeapon( player GetCurrentWeapon() )
)
{
continue;
}
if ( tiggerer == player && self useHoldThink( player, level.boxSettings[ self.boxType ].useTime ) )
{
self notify( "captured", player );
}
}
}
isFriendlyToBox( box )
{
return ( level.teamBased
&& self.team == box.team );
}
box_timeOut() // self == box
{
self endon( "death" );
level endon ( "game_ended" );
boxConfig = level.boxSettings[ self.boxType ];
lifeSpan = boxConfig.lifeSpan;
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( lifeSpan );
if ( IsDefined( boxConfig.voGone ) )
{
self.owner thread leaderDialogOnPlayer( boxConfig.voGone );
}
self box_leave();
}
box_leave()
{
// TODO: get sound for this
//if ( isDefined( self.owner ) )
// self.owner thread leaderDialogOnPlayer( "sentry_gone" );
wait( 0.05 );
self notify( "death" );
}
deleteOnOwnerDeath( owner ) // self == box.friendlyModel or box.enemyModel, owner == box
{
wait ( 0.25 );
self linkTo( owner, "tag_origin", (0,0,0), (0,0,0) );
owner waittill ( "death" );
box_leave();
}
box_ModelTeamUpdater( showForTeam ) // self == box model (enemy or friendly)
{
self endon ( "death" );
self hide();
foreach ( player in level.players )
{
if ( player.team == showForTeam )
self showToPlayer( player );
}
for ( ;; )
{
level waittill ( "joined_team" );
self hide();
foreach ( player in level.players )
{
if ( player.team == showForTeam )
self showToPlayer( player );
}
}
}
useHoldThink( player, useTime )
{
self maps\mp\_movers::script_mover_link_to_use_object( player );
player _disableWeapon();
player.boxParams = SpawnStruct();
player.boxParams.curProgress = 0;
player.boxParams.inUse = true;
player.boxParams.useRate = 0;
player.boxParams.id = self.id;
if ( isDefined( useTime ) )
{
player.boxParams.useTime = useTime;
}
else
{
player.boxParams.useTime = DEFAULT_USE_TIME;
}
result = useHoldThinkLoop( player );
Assert( IsDefined( result ) );
if ( isAlive( player ) )
{
player _enableWeapon();
maps\mp\_movers::script_mover_unlink_from_use_object( player );
}
if ( !isDefined( self ) )
return false;
player.boxParams.inUse = false;
player.boxParams.curProgress = 0;
return ( result );
}
useHoldThinkLoop( player )
{
config = player.boxParams;
while( player isPlayerUsingBox( config ) )
{
if ( !player maps\mp\_movers::script_mover_use_can_link( self ) )
{
player maps\mp\gametypes\_gameobjects::updateUIProgress( config, false );
return false;
}
config.curProgress += (50 * config.useRate);
if ( isDefined( player.objectiveScaler ) )
config.useRate = 1 * player.objectiveScaler;
else
config.useRate = 1;
player maps\mp\gametypes\_gameobjects::updateUIProgress( config, true );
if ( config.curProgress >= config.useTime )
{
player maps\mp\gametypes\_gameobjects::updateUIProgress( config, false );
return ( isReallyAlive( player ) );
}
wait 0.05;
}
player maps\mp\gametypes\_gameobjects::updateUIProgress( config, false );
return false;
}
disableWhenJuggernaut() // self == box
{
level endon( "game_ended" );
self endon( "death" );
while( true )
{
level waittill( "juggernaut_equipped", player );
self maps\mp\_entityheadIcons::setHeadIcon( player, "", (0,0,0) );
self box_disablePlayerUse( player );
self thread doubleDip( player );
}
}
addBoxToLevelArray() // self == box
{
// put the newly created box in the level array for the box type
level.deployable_box[ self.boxType ][ self GetEntityNumber() ] = self;
}
removeBoxFromLevelArray() // self == box
{
level.deployable_box[ self.boxType ][ self GetEntityNumber() ] = undefined;
}
createBombSquadModel( streakName ) // self == box
{
config = level.boxSettings[ streakName ];
if ( IsDefined( config.modelBombSquad ) )
{
bombSquadModel = Spawn( "script_model", self.origin );
bombSquadModel.angles = self.angles;
bombSquadModel Hide();
bombSquadModel thread maps\mp\gametypes\_weapons::bombSquadVisibilityUpdater( self.owner );
bombSquadModel SetModel( config.modelBombSquad );
bombSquadModel LinkTo( self );
bombSquadModel SetContents( 0 );
self.bombSquadModel = bombSquadModel;
self waittill ( "death" );
// Could have been deleted when the player was carrying the ims and died
if ( IsDefined( bombSquadModel ) )
{
bombSquadModel delete();
self.bombSquadModel = undefined;
}
}
}
isPlayerUsingBox( box ) // self == player
{
return ( !level.gameEnded
&& IsDefined( box )
&& isReallyAlive( self ) && self UseButtonPressed()
&& !( self IsOnLadder() ) // fix for 154828
&& !( self MeleeButtonPressed() ) // from gameobjects.gsc
&& !( IsDefined( self.throwingGrenade ) ) // from gameobjects.gsc
&& box.curProgress < box.useTime
&& ( !IsDefined( self.teleporting ) || !self.teleporting )
);
}

View File

@ -0,0 +1,176 @@
#include maps\mp\_utility;
#include common_scripts\utility;
#include maps\mp\gametypes\_hud_util;
BOX_TYPE = "deployable_ammo";
// we depend on deployablebox being init'd first
init ()
{
boxConfig = SpawnStruct();
boxConfig.weaponInfo = "deployable_vest_marker_mp";
boxConfig.modelBase = "mil_ammo_case_1_open";
boxConfig.hintString = &"KILLSTREAKS_HINTS_DEPLOYABLE_AMMO_USE"; //
boxConfig.capturingString = &"KILLSTREAKS_DEPLOYABLE_AMMO_TAKING"; //
boxConfig.event = "deployable_ammo_taken"; //
boxConfig.streakName = BOX_TYPE; //
boxConfig.splashName = "used_deployable_ammo"; //
boxConfig.shaderName = "compass_objpoint_deploy_ammo_friendly";
boxConfig.headIconOffset = 25;
boxConfig.lifeSpan = 90.0;
boxConfig.useXP = 50;
boxConfig.xpPopup = "destroyed_vest";
boxConfig.voDestroyed = "ballistic_vest_destroyed";
boxConfig.deployedSfx = "mp_vest_deployed_ui";
boxConfig.onUseSfx = "ammo_crate_use";
boxConfig.onUseCallback = ::onUseDeployable;
boxConfig.canUseCallback = ::canUseDeployable;
boxConfig.useTime = 500;
boxConfig.maxHealth = 150;
boxConfig.damageFeedback = "deployable_bag";
boxConfig.deathWeaponInfo = "deployable_ammo_mp";
boxConfig.deathVfx = loadfx( "fx/explosions/clusterbomb_exp_direct_runner" );
boxConfig.deathDamageRadius = 256; // these params taken from frag_grenade
boxConfig.deathDamageMax = 130;
boxconfig.deathDamageMin = 50;
boxConfig.allowMeleeDamage = true;
boxConfig.allowGrenadeDamage = true; // this doesn't seem dependable, like with c-4. Why isn't this in an object description?
boxConfig.maxUses = 4;
level.boxSettings[ BOX_TYPE ] = boxConfig;
level.killStreakFuncs[ BOX_TYPE ] = ::tryUseDeployableAmmo;
level.deployable_box[ BOX_TYPE ] = []; // storing each created box in their own array
}
tryUseDeployableAmmo( lifeId, streakName ) // self == player
{
result = self maps\mp\killstreaks\_deployablebox::beginDeployableViaMarker( lifeId, BOX_TYPE );
if( ( !IsDefined( result ) || !result ) )
{
return false;
}
if( !is_aliens() )
{
self maps\mp\_matchdata::logKillstreakEvent( BOX_TYPE, self.origin );
}
return true;
}
onUseDeployable( boxEnt ) // self == player
{
if( is_aliens() )
{
self addAlienWeaponAmmo( boxEnt );
}
else
{
self addAllWeaponAmmo();
}
}
addAllWeaponAmmo()
{
weaponList = self GetWeaponsListAll();
if ( IsDefined( weaponList ) )
{
foreach ( weaponName in weaponList )
{
// allow bullet weapons to get extra clips
if ( maps\mp\gametypes\_weapons::isBulletWeapon( weaponName ) )
{
self addOneWeaponAmmo( weaponName, 2 );
}
// limit the ammo of launchers so they aren't abused
else if ( WeaponClass( weaponName ) == "rocketlauncher" )
{
self addOneWeaponAmmo( weaponName, 1 );
// self GiveStartAmmo( weaponName );
}
}
}
}
addOneWeaponAmmo( weaponName, numClips ) // self == plyaer
{
// want players to be able to go above starting ammo
// but don't want it to be too crazy
clipSize = WeaponClipSize( weaponName );
curStock = self GetWeaponAmmoStock( weaponName );
self SetWeaponAmmoStock( weaponName, curStock + numClips * clipSize );
// self GiveStartAmmo( weaponName );
}
addRatioMaxStockToAllWeapons( ratio_of_max )
{
primary_weapons = self GetWeaponsListPrimaries();
foreach ( weapon in primary_weapons )
{
// only allow bullet weapons to refill
if ( maps\mp\gametypes\_weapons::isBulletWeapon( weapon ) )
{
if ( weapon != "iw6_alienminigun_mp" )
{
cur_stock = self GetWeaponAmmoStock( weapon );
max_stock = WeaponMaxAmmo( weapon );
new_stock = cur_stock + max_stock * ratio_of_max;
self SetWeaponAmmoStock( weapon, int( min( new_stock, max_stock ) ) );
}
}
}
}
addFullClipToAllWeapons()
{
primary_weapons = self GetWeaponsListPrimaries();
foreach ( weapon in primary_weapons )
{
clip_size = WeaponClipSize( weapon );
self SetWeaponAmmoClip( weapon, clip_size );
}
}
addAlienWeaponAmmo( boxEnt )
{
primary_weapons = self GetWeaponsListPrimaries();
assert( isDefined( boxEnt.upgrade_rank ) );
switch (boxEnt.upgrade_rank)
{
case 0:
self addRatioMaxStockToAllWeapons( .4 );
break;
case 1:
self addRatioMaxStockToAllWeapons( .7 );
break;
case 2:
self addRatioMaxStockToAllWeapons( 1.0 );
break;
case 3:
self addRatioMaxStockToAllWeapons( 1.0 );
self addFullClipToAllWeapons();
break;
case 4:
self addRatioMaxStockToAllWeapons( 1.0 );
self addFullClipToAllWeapons();
break;
}
}
canUseDeployable(boxEnt) // self == player
{
if( is_aliens() && isDefined( boxEnt ) && boxEnt.owner == self && !isdefined( boxEnt.air_dropped ) )
{
return false;
}
if( !is_aliens() )
return ( !self isJuggernaut() );
else
return true;
}

View File

@ -0,0 +1,96 @@
#include maps\mp\_utility;
#include common_scripts\utility;
#include maps\mp\gametypes\_hud_util;
BOX_TYPE = "deployable_grenades";
// we depend on deployablebox being init'd first
init ()
{
boxConfig = SpawnStruct();
boxConfig.weaponInfo = "deployable_vest_marker_mp";
boxConfig.modelBase = "afr_mortar_ammo_01";
boxConfig.hintString = &"KILLSTREAKS_HINTS_DEPLOYABLE_GRENADES_PICKUP"; //
boxConfig.capturingString = &"KILLSTREAKS_DEPLOYABLE_GRENADES_TAKING"; //
boxConfig.event = "deployable_grenades_taken"; //
boxConfig.streakName = BOX_TYPE; //
boxConfig.splashName = "used_deployable_grenades"; //
boxConfig.shaderName = "compass_objpoint_deploy_grenades_friendly";
boxConfig.headIconOffset = 25;
boxConfig.lifeSpan = 90.0;
boxConfig.useXP = 50;
boxConfig.xpPopup = "destroyed_vest";
boxConfig.voDestroyed = "ballistic_vest_destroyed";
boxConfig.deployedSfx = "mp_vest_deployed_ui";
boxConfig.onUseSfx = "ammo_crate_use";
boxConfig.onUseCallback = ::onUseDeployable;
boxConfig.canUseCallback = ::canUseDeployable;
boxConfig.useTime = 500;
boxConfig.maxHealth = 150;
boxConfig.damageFeedback = "deployable_bag";
boxConfig.deathWeaponInfo = "deployable_grenades_mp";
boxConfig.deathVfx = loadfx( "fx/explosions/grenadeexp_default" );
boxConfig.deathDamageRadius = 256; // these params taken from frag_grenade
boxConfig.deathDamageMax = 150;
boxconfig.deathDamageMin = 50;
boxConfig.allowMeleeDamage = true;
boxConfig.allowGrenadeDamage = true; // this doesn't seem dependable, like with c-4. Why isn't this in an object description?
boxConfig.maxUses = 3;
level.boxSettings[ BOX_TYPE ] = boxConfig;
level.killStreakFuncs[ BOX_TYPE ] = ::tryUseDeployableGrenades;
level.deployable_box[ BOX_TYPE ] = []; // storing each created box in their own array
}
tryUseDeployableGrenades( lifeId, streakName ) // self == player
{
result = self maps\mp\killstreaks\_deployablebox::beginDeployableViaMarker( lifeId, BOX_TYPE );
if( ( !IsDefined( result ) || !result ) )
{
return false;
}
self maps\mp\_matchdata::logKillstreakEvent( BOX_TYPE, self.origin );
return true;
}
onUseDeployable( boxEnt ) // self == player
{
self refillExplosiveWeapons();
}
refillExplosiveWeapons()
{
weaponList = self GetWeaponsListAll();
if ( IsDefined( weaponList ) )
{
foreach ( weaponName in weaponList )
{
if ( maps\mp\gametypes\_weapons::isGrenade( weaponName )
|| maps\mp\gametypes\_weapons::isOffhandWeapon( weaponName )
// && !maps\mp\_utility::isKillstreakWeapon( weaponName )
)
{
// for some reason, tactical grenades are getting 2
self GiveStartAmmo( weaponName );
}
}
}
if ( self _hasPerk( "specialty_tacticalinsertion" ) && self getAmmoCount( "flare_mp" ) < 1 )
self givePerkOffhand( "specialty_tacticalinsertion", false );
}
canUseDeployable(boxEnt) // self == player
{
if( is_aliens() && isDefined( boxEnt ) && boxEnt.owner == self && !isdefined( boxEnt.air_dropped ) )
{
return false;
}
return ( !self isJuggernaut() );
}

View File

@ -0,0 +1,252 @@
#include maps\mp\_utility;
#include common_scripts\utility;
#include maps\mp\gametypes\_hud_util;
BOX_TYPE = "deployable_ammo";
// we depend on deployablebox being init'd first
init ()
{
boxConfig = SpawnStruct();
boxConfig.id = "deployable_weapon_crate";
boxConfig.weaponInfo = "deployable_weapon_crate_marker_mp";
boxConfig.modelBase = "mp_weapon_crate";
boxConfig.modelBombSquad = "mp_weapon_crate_bombsquad";
boxConfig.hintString = &"KILLSTREAKS_HINTS_DEPLOYABLE_AMMO_USE"; //
boxConfig.capturingString = &"KILLSTREAKS_DEPLOYABLE_AMMO_TAKING"; //
boxConfig.event = "deployable_ammo_taken"; //
boxConfig.streakName = BOX_TYPE; //
boxConfig.splashName = "used_deployable_ammo"; //
boxConfig.shaderName = "compass_objpoint_deploy_ammo_friendly";
boxConfig.headIconOffset = 20;
boxConfig.lifeSpan = 90.0;
boxConfig.voGone = "ammocrate_gone";
boxConfig.useXP = 50;
boxConfig.xpPopup = "destroyed_ammo";
boxConfig.voDestroyed = "ammocrate_destroyed";
boxConfig.deployedSfx = "mp_vest_deployed_ui";
boxConfig.onUseSfx = "ammo_crate_use";
boxConfig.onUseCallback = ::onUseDeployable;
boxConfig.canUseCallback = ::canUseDeployable;
boxConfig.noUseKillstreak = true;
boxConfig.useTime = 1000;
boxConfig.maxHealth = 150;
boxConfig.damageFeedback = "deployable_bag";
boxConfig.deathVfx = loadfx( "vfx/gameplay/mp/killstreaks/vfx_ballistic_vest_death" );
boxConfig.allowMeleeDamage = true;
boxConfig.allowGrenadeDamage = false; // this doesn't seem dependable, like with c-4. Why isn't this in an object description?
boxConfig.maxUses = 4;
boxConfig.minigunChance = 20; // 1 in minigunChance
// artificially increase the odds for development
/#
boxConfig.miniGunChance = 10;
#/
boxConfig.minigunWeapon = "iw6_minigun_mp";
boxConfig.ammoRestockCheckFreq = 0.5; // how often we check for new players
boxConfig.ammoRestockTime = 10.0; // how often players can get restocked
boxConfig.triggerRadius = 200;
boxConfig.triggerHeight = 64;
boxConfig.onDeployCallback = ::onBoxDeployed;
boxConfig.canUseOtherBoxes = false;
level.boxSettings[ BOX_TYPE ] = boxConfig;
level.killStreakFuncs[ BOX_TYPE ] = ::tryUseDeployable;
level.deployableGunBox_BonusInXUses = RandomIntRange(1, boxConfig.minigunChance + 1);
level.deployable_box[ BOX_TYPE ] = []; // storing each created box in their own array
maps\mp\gametypes\sotf::defineChestWeapons();
}
tryUseDeployable( lifeId, streakName ) // self == player
{
result = self maps\mp\killstreaks\_deployablebox::beginDeployableViaMarker( lifeId, BOX_TYPE );
if( ( !IsDefined( result ) || !result ) )
{
return false;
}
if( !is_aliens() )
{
self maps\mp\_matchdata::logKillstreakEvent( BOX_TYPE, self.origin );
}
return true;
}
onUseDeployable( boxEnt ) // self == player
{
level.deployableGunBox_BonusInXUses--;
if ( level.deployableGunBox_BonusInXUses == 0 )
{
boxConfig = level.boxSettings[ boxEnt.boxType ];
// For when we want to replace the minigun with something else for a specific level (e.g. Venom-X in dome_ns )
if ( IsDefined( level.deployableBoxGiveWeaponFunc ) )
[[ level.deployableBoxGiveWeaponFunc ]]( true );
else
giveGun( self, boxConfig.minigunWeapon );
self maps\mp\gametypes\_missions::processChallenge( "ch_guninabox" );
// reset gun chance, but make it very unlikely to get a second one
level.deployableGunBox_BonusInXUses = RandomIntRange(boxConfig.minigunChance, boxConfig.minigunChance + 1);
}
else
{
giveRandomGun( self );
}
}
onBoxDeployed( config ) // self == box
{
self thread restockAmmoAura( config );
}
giveRandomGun( player )
{
// this is adapted from SOTF / Hunted
// give the player a new gun, but make sure he's not carrying a variant of it in either slot
baseWeapons = [];
foreach ( gun in player GetWeaponsListPrimaries() )
{
baseWeapons[ baseWeapons.size ] = GetWeaponBaseName( gun );
}
newWeapon = undefined;
while ( true )
{
// getRandomWeapon returns an array
newWeapon = maps\mp\gametypes\sotf::getRandomWeapon( level.weaponArray );
newBaseWeapon = newWeapon["name"];
// make sure he's not carrying this weapon already
if ( !array_contains( baseWeapons, newBaseWeapon ) )
break;
}
// get randomAttachments returns a full weapon string
newWeapon = maps\mp\gametypes\sotf::getRandomAttachments( newWeapon );
giveGun( player, newWeapon );
}
giveGun( player, newWeapon )
{
weaponList = player GetWeaponsListPrimaries();
primaryCount = 0;
foreach ( weap in weaponList )
{
if ( !maps\mp\gametypes\_weapons::isAltModeWeapon( weap ) )
{
primaryCount++;
}
}
if ( primaryCount > 1 )
{
playerPrimary = player.lastDroppableWeapon;
if ( IsDefined( playerPrimary ) && playerPrimary != "none" )
{
player DropItem( playerPrimary );
}
}
player _giveWeapon( newWeapon );
player SwitchToWeapon( newWeapon );
player GiveStartAmmo( newWeapon );
}
restockAmmoAura( config ) // self == box
{
self endon( "death" );
level endon( "game_eneded" );
trigger = Spawn( "trigger_radius", self.origin, 0, config.triggerRadius, config.triggerHeight );
trigger.owner = self;
self thread maps\mp\gametypes\_weapons::deleteOnDeath( trigger );
if ( IsDefined( self.moving_platform ) )
{
trigger EnableLinkTo();
trigger LinkTo( self.moving_platform );
}
rangeSq = config.triggerRadius * config.triggerRadius;
player = undefined;
while ( true )
{
// trigger waittill( "trigger", player );
touchingPlayers = trigger GetIsTouchingEntities( level.players );
foreach ( player in touchingPlayers )
{
if ( IsDefined( player )
&& !(self.owner IsEnemy( player ))
&& self shouldAddAmmo( player ) )
{
// self thread addAmmoOverTime( player, rangeSq, config.ammoRestockTime );
self addAmmo( player, config.ammoRestockTime );
}
}
wait ( config.ammoRestockCheckFreq );
}
}
shouldAddAmmo( player ) // self == box
{
return ( !IsDefined( player.deployableGunNextAmmoTime )
|| GetTime() >= player.deployableGunNextAmmoTime );
}
addAmmo( player, freq ) // self == box
{
player.deployableGunNextAmmoTime = GetTime() + (freq * 1000);
maps\mp\gametypes\_weapons::scavengerGiveAmmo( player );
player maps\mp\gametypes\_damagefeedback::hudIconType( "boxofguns" );
}
addAmmoOverTime( player, rangeSq, freq ) // self == box
{
self endon( "death" );
player endon( "death" );
player endon( "disconnect" );
level endon( "game_ended" );
while ( true )
{
self addAmmo( player );
wait ( freq );
if ( DistanceSquared( player.origin, self.origin ) > rangeSq )
{
break;
}
}
}
canUseDeployable(boxEnt) // self == player
{
if( is_aliens() && isDefined( boxEnt ) && boxEnt.owner == self && !isdefined( boxEnt.air_dropped ) )
{
return false;
}
if( !is_aliens() )
return ( !self isJuggernaut() );
else
return true;
}

View File

@ -0,0 +1,83 @@
#include maps\mp\_utility;
#include common_scripts\utility;
#include maps\mp\gametypes\_hud_util;
BOX_TYPE = "deployable_juicebox";
// we depend on deployablebox being init'd first
init ()
{
boxConfig = SpawnStruct();
boxConfig.weaponInfo = "deployable_vest_marker_mp";
boxConfig.modelBase = "afr_mortar_ammo_01";
boxConfig.hintString = &"KILLSTREAKS_HINTS_DEPLOYABLE_JUICEBOX_PICKUP"; //
boxConfig.capturingString = &"KILLSTREAKS_DEPLOYABLE_JUICEBOX_TAKING"; //
boxConfig.event = "deployable_juicebox_taken"; //
boxConfig.streakName = BOX_TYPE; //
boxConfig.splashName = "used_deployable_juicebox"; //
boxConfig.shaderName = "compass_objpoint_deploy_juiced_friendly";
boxConfig.headIconOffset = 25;
boxConfig.lifeSpan = 90.0;
boxConfig.useXP = 50;
boxConfig.xpPopup = "destroyed_vest";
boxConfig.voDestroyed = "ballistic_vest_destroyed";
boxConfig.deployedSfx = "mp_vest_deployed_ui";
boxConfig.onUseSfx = "ammo_crate_use";
boxConfig.onUseCallback = ::onUseDeployable;
boxConfig.canUseCallback = ::canUseDeployable;
boxConfig.useTime = 500;
boxConfig.maxHealth = 300;
boxConfig.damageFeedback = "deployable_bag";
boxConfig.deathWeaponInfo = "deployable_ammo_mp";
boxConfig.deathVfx = loadfx( "vfx/gameplay/mp/killstreaks/vfx_ballistic_vest_death" );
boxConfig.allowMeleeDamage = true;
boxConfig.allowGrenadeDamage = false; // this doesn't seem dependable, like with c-4. Why isn't this in an object description?
boxConfig.maxUses = 4;
level.boxSettings[ BOX_TYPE ] = boxConfig;
level.killStreakFuncs[ BOX_TYPE ] = ::tryUseDeployableJuiced;
level.deployable_box[ BOX_TYPE ] = []; // storing each created box in their own array
}
tryUseDeployableJuiced( lifeId, streakName ) // self == player
{
result = self maps\mp\killstreaks\_deployablebox::beginDeployableViaMarker( lifeId, BOX_TYPE );
if( ( !IsDefined( result ) || !result ) )
{
return false;
}
if( !is_aliens() )
{
self maps\mp\_matchdata::logKillstreakEvent( BOX_TYPE, self.origin );
}
return true;
}
onUseDeployable( boxEnt ) // self == player
{
if ( is_aliens() )
{
assert( isDefined( boxEnt.upgrade_rank) , "No rank specified for deployable juicebox" );
self thread maps\mp\perks\_perkfunctions::setJuiced( level.deployablebox_juicebox_rank[boxEnt.upgrade_rank] );
}
else
{
self thread maps\mp\perks\_perkfunctions::setJuiced( 15 );
}
}
canUseDeployable(boxEnt) // self == player
{
if( is_aliens() && isDefined( boxEnt ) && boxEnt.owner == self && !isdefined( boxEnt.air_dropped ) )
{
return false;
}
return ( !(self isJuggernaut()) && !(self maps\mp\perks\_perkfunctions::hasJuiced()) );
}

View File

@ -0,0 +1,112 @@
#include maps\mp\_utility;
#include common_scripts\utility;
#include maps\mp\gametypes\_hud_util;
BOX_TYPE = "deployable_vest";
// we depend on deployablebox being init'd first
init ()
{
boxConfig = SpawnStruct();
boxConfig.id = "deployable_vest";
boxConfig.weaponInfo = "deployable_vest_marker_mp";
boxConfig.modelBase = "prop_ballistic_vest_iw6";
boxConfig.modelBombSquad = "prop_ballistic_vest_iw6_bombsquad";
boxConfig.hintString = &"KILLSTREAKS_HINTS_LIGHT_ARMOR_PICKUP";
boxConfig.capturingString = &"KILLSTREAKS_BOX_GETTING_VEST";
boxConfig.event = "deployable_vest_taken";
boxConfig.streakName = BOX_TYPE;
boxConfig.splashName = "used_deployable_vest";
boxConfig.shaderName = "compass_objpoint_deploy_friendly";
boxConfig.headIconOffset = 20;
boxConfig.lifeSpan = 90.0;
boxConfig.useXP = 50;
boxConfig.xpPopup = "destroyed_vest";
boxConfig.voDestroyed = "ballistic_vest_destroyed";
boxConfig.deployedSfx = "mp_vest_deployed_ui";
boxConfig.onUseSfx = "ammo_crate_use";
boxConfig.onUseCallback = ::onUseDeployable;
boxConfig.canUseCallback = ::canUseDeployable;
boxConfig.useTime = 1000;
boxConfig.maxHealth = 300;
boxConfig.damageFeedback = "deployable_bag";
boxConfig.deathVfx = loadfx( "vfx/gameplay/mp/killstreaks/vfx_ballistic_vest_death" );
boxConfig.allowMeleeDamage = true;
boxConfig.allowGrenadeDamage = false; // this doesn't seem dependable, like with c-4. Why isn't this in an object description?
boxConfig.maxUses = 4;
boxConfig.canUseOtherBoxes = false;
level.boxSettings[ BOX_TYPE ] = boxConfig;
level.killStreakFuncs[ BOX_TYPE ] = ::tryUseDeployableVest;
level.deployable_box[ BOX_TYPE ] = []; // storing each created box in their own array
}
tryUseDeployableVest( lifeId, streakName ) // self == player
{
result = self maps\mp\killstreaks\_deployablebox::beginDeployableViaMarker( lifeId, BOX_TYPE );
if( ( !IsDefined( result ) || !result ) )
{
return false;
}
if( !is_aliens() )
{
self maps\mp\_matchdata::logKillstreakEvent( BOX_TYPE, self.origin );
}
// we used to give the player a vest/health after we deployed the box
// instead, we automatically call onUseCallback once in _deployabox.gsc
// (because we don't want the player to be able to manually use vest again until he dies
return true;
}
canUseDeployable(boxEnt) // self == player
{
if(!is_aliens() )
{
return ( !(self maps\mp\perks\_perkfunctions::hasLightArmor()) && !self isJuggernaut() );
}
if( isDefined( boxEnt ) && boxEnt.owner == self && !isdefined( boxEnt.air_dropped ) )
{
return false;
}
return !self isJuggernaut();
}
onUseDeployable( boxEnt ) // self == player
{
if ( is_aliens() )
{
existing_armor = 0;
if( isDefined( self.lightArmorHP ))
{
existing_armor = self.lightArmorHP;
}
assertex ( isDefined( boxEnt.upgrade_rank ), "No upgrade rank defined for deployable armor" );
armor_to_give = get_adjusted_armor(existing_armor,boxEnt.upgrade_rank );
self maps\mp\perks\_perkfunctions::setLightArmor( armor_to_give );
self notify( "enable_armor" );
}
else
{
self maps\mp\perks\_perkfunctions::setLightArmor();
}
}
get_adjusted_armor( existing_armor,rank)
{
if( existing_armor + level.deployablebox_vest_rank[rank] > level.deployablebox_vest_max)
{
return level.deployablebox_vest_max;
}
return existing_armor + level.deployablebox_vest_rank[rank];
}

View File

@ -0,0 +1,132 @@
#include maps\mp\_utility;
#include common_scripts\utility;
// 2013-03-12 wsh
// shared utility for grenade-style designators
// init in your own ks script
// callbackFunc( killstreakName, designatorGrenadeEnt );
// We assume designator weapon can only be used once
designator_Start( killstreakName, designatorName, onTargetAcquiredCallback )
{
AssertEx( IsDefined( onTargetAcquiredCallback ), "designator onTargetAcquiredCallback must be specified for " + designatorName );
self endon ( "death" );
self.marker = undefined;
if ( self GetCurrentWeapon() == designatorName )
{
self thread designator_DisableUsabilityDuringGrenadePullback( designatorName );
self thread designator_WaitForGrenadeFire( killstreakName, designatorName, onTargetAcquiredCallback );
self designator_WaitForWeaponChange( designatorName );
return ( !(self GetAmmoCount( designatorName ) && self HasWeapon( designatorName ) ) );
}
return false;
}
designator_DisableUsabilityDuringGrenadePullback( designatorName ) // self == player
{
self endon( "death" );
self endon( "disconnect" );
usedWeaponName = "";
while ( usedWeaponName != designatorName )
{
self waittill( "grenade_pullback", usedWeaponName );
}
self _disableUsability();
self designator_EnableUsabilityWhenDesignatorFinishes();
}
designator_EnableUsabilityWhenDesignatorFinishes() // self == player
{
self endon( "death" );
self endon( "disconnect" );
// self notify( "beginMarkerTracking" );
// self endon( "beginMarkerTracking" );
self waittill_any( "grenade_fire", "weapon_change" );
self _enableUsability();
}
designator_WaitForGrenadeFire( killstreakName, designatorName, callbackFunc ) // self == player
{
self endon( "designator_finished" );
self endon( "spawned_player" );
self endon( "disconnect" );
designatorGrenade = undefined;
usedWeaponName = "";
while ( usedWeaponName != designatorName )
{
self waittill( "grenade_fire", designatorGrenade, usedWeaponName );
}
if ( IsAlive( self ) )
{
designatorGrenade.owner = self;
designatorGrenade.weaponName = designatorName;
self.marker = designatorGrenade;
// maybe this should be in the callback?
// marker PlaySoundToPlayer( "", self );
self thread designator_OnTargetAcquired( killstreakName, designatorGrenade, callbackFunc );
}
else
{
designatorGrenade Delete();
}
self notify( "designator_finished" );
}
designator_WaitForWeaponChange( designatorName ) // self == player
{
self endon( "spawned_player" );
self endon( "disconnect" );
currentWeapon = self getCurrentWeapon();
while ( currentWeapon == designatorName )
{
self waittill( "weapon_change", currentWeapon );
}
// this could have ended because we switched weapons normally
// or because we used the killstreak
if ( self GetAmmoCount( designatorName ) == 0 )
{
self designator_RemoveDesignatorAndRestorePreviousWeapon( designatorName );
}
self notify( "designator_finished" );
}
designator_RemoveDesignatorAndRestorePreviousWeapon( designatorName )
{
if ( self HasWeapon( designatorName ) )
{
self TakeWeapon( designatorName );
// self SwitchToWeapon( self getLastWeapon() );
}
}
designator_OnTargetAcquired( killstreakName, designatorGrenade, onTargetAcquiredCallback ) // self == player
{
designatorGrenade waittill( "missile_stuck", stuckTo );
if ( IsDefined( designatorGrenade.owner ) )
{
self thread [[ onTargetAcquiredCallback ]]( killstreakName, designatorGrenade );
}
if ( IsDefined( designatorGrenade ) )
{
designatorGrenade Delete();
}
}

View File

@ -0,0 +1,345 @@
#include maps\mp\_utility;
#include maps\mp\gametypes\_hud_util;
#include common_scripts\utility;
#include maps\mp\agents\_agent_utility;
#include maps\mp\gametypes\_damage;
//===========================================
// constants
//===========================================
CONST_MAX_ACTIVE_KILLSTREAK_DOGS_PER_GAME = 5;
CONST_MAX_ACTIVE_KILLSTREAK_DOGS_PER_PLAYER = 1;
CONST_MAX_ACTIVE_KILLSTREAK_AGENTS_PER_PLAYER = 2;
//===========================================
// init
//===========================================
init()
{
level.killStreakFuncs["guard_dog"] = ::tryUseDog;
/#
SetDevDvarIfUninitialized( "scr_devWolf", 0 ); // 0 == dog, 1 == wolf
SetDevDvarIfUninitialized( "scr_devWolfType", 0 ); // 0 == wolfA, 1 == wolfB
#/
}
//===========================================
// setup_callbacks
//===========================================
setup_callbacks()
{
level.agent_funcs["dog"] = level.agent_funcs["player"];
level.agent_funcs["dog"]["spawn"] = ::spawn_dog;
level.agent_funcs["dog"]["on_killed"] = ::on_agent_dog_killed;
level.agent_funcs["dog"]["on_damaged"] = maps\mp\agents\_agents::on_agent_generic_damaged;
level.agent_funcs["dog"]["on_damaged_finished"] = ::on_damaged_finished;
level.agent_funcs["dog"]["think"] = maps\mp\agents\dog\_dog_think::main;
}
//===========================================
// tryUseDog
//===========================================
tryUseDog( lifeId, streakName )
{
return useDog();
}
//===========================================
// useDog
//===========================================
useDog()
{
// limit the number of active "dog" agents allowed per player
if( isDefined(self.hasDog) && self.hasDog )
{
dog_type = self GetCommonPlayerDataReservedInt( "mp_dog_type" );
if( dog_type == 1 )
self iPrintLnBold( &"KILLSTREAKS_ALREADY_HAVE_WOLF" );
else
self iPrintLnBold( &"KILLSTREAKS_ALREADY_HAVE_DOG" );
return false;
}
// limit the number of active "dog" agents allowed per game
if( getNumActiveAgents( "dog" ) >= CONST_MAX_ACTIVE_KILLSTREAK_DOGS_PER_GAME )
{
self iPrintLnBold( &"KILLSTREAKS_TOO_MANY_DOGS" );
return false;
}
// limit the number of active agents allowed per player
if( getNumOwnedActiveAgents( self ) >= CONST_MAX_ACTIVE_KILLSTREAK_AGENTS_PER_PLAYER )
{
self iPrintLnBold( &"KILLSTREAKS_AGENT_MAX" );
return false;
}
// TODO: we should probably do a queue system for these, so the player can call it but it'll go into a queue for when an agent dies to open up a spot
// limit the number of active agents allowed per player
maxagents = GetMaxAgents();
if( getNumActiveAgents() >= maxagents )
{
self iPrintLnBold( &"KILLSTREAKS_UNAVAILABLE" );
return false;
}
// make sure the player is still alive before the agent trys to spawn on the player
if( !isReallyAlive( self ) )
{
return false;
}
// try to spawn the agent on a path node near the player
nearestPathNode = self getValidSpawnPathNodeNearPlayer( true );
if( !IsDefined(nearestPathNode) )
{
return false;
}
// find an available agent
agent = maps\mp\agents\_agent_common::connectNewAgent( "dog" , self.team );
if( !IsDefined( agent ) )
{
return false;
}
self.hasDog = true;
// set the agent to the player's team
agent set_agent_team( self.team, self );
spawnOrigin = nearestPathNode.origin;
spawnAngles = VectorToAngles( self.origin - nearestPathNode.origin );
agent thread [[ agent agentFunc("spawn") ]]( spawnOrigin, spawnAngles, self );
agent _setNameplateMaterial( "player_name_bg_green_dog", "player_name_bg_red_dog" );
if ( IsDefined( self.ballDrone ) && self.ballDrone.ballDroneType == "ball_drone_backup" )
{
self maps\mp\gametypes\_missions::processChallenge( "ch_twiceasdeadly" );
}
self maps\mp\_matchdata::logKillstreakEvent( "guard_dog", self.origin );
return true;
}
//=======================================================
// on_agent_dog_killed
//=======================================================
on_agent_dog_killed( eInflictor, eAttacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, timeOffset, deathAnimDuration )
{
self.isActive = false;
self.hasDied = false;
//agent dogs in safeguard do not have an owner.
if( IsDefined( self.owner ) )
self.owner.hasDog = false;
eAttacker.lastKillDogTime = GetTime();
if ( IsDefined( self.animCBs.OnExit[ self.aiState ] ) )
self [[ self.animCBs.OnExit[ self.aiState ] ]] ();
// award XP for killing agents
if( isPlayer( eAttacker ) && IsDefined(self.owner) && (eAttacker != self.owner) )
{
self.owner leaderDialogOnPlayer( "dog_killed" );
self maps\mp\gametypes\_damage::onKillstreakKilled( eAttacker, sWeapon, sMeansOfDeath, iDamage, "destroyed_guard_dog" );
if ( IsPlayer( eAttacker ) )
{
eAttacker maps\mp\gametypes\_missions::processChallenge( "ch_notsobestfriend" );
// assume jumping?
if ( !self IsOnGround() )
{
eAttacker maps\mp\gametypes\_missions::processChallenge( "ch_hoopla" );
}
}
}
self SetAnimState( "death" );
animEntry = self GetAnimEntry();
animLength = GetAnimLength( animEntry );
deathAnimDuration = int( animLength * 1000 ); // duration in milliseconds
self.body = self CloneAgent( deathAnimDuration );
self PlaySound( ter_op( self.bIsWolf, "anml_wolf_shot_death", "anml_dog_shot_death" ) );
self maps\mp\agents\_agent_utility::deactivateAgent();
self notify( "killanimscript" );
}
on_damaged_finished( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, timeOffset )
{
if( !IsDefined( self.playing_pain_sound ) )
self thread play_pain_sound( 2.5 );
// damage needs to be modified depending on the weapon and hit location because the head is really easy to hit when the dog is running at you
// assuming 1.4 modifier for head shots
damageModified = iDamage;
if( IsDefined( sHitLoc ) && sHitLoc == "head" && level.gametype != "horde" )
{
damageModified = int( damageModified * 0.6 );
if ( iDamage > 0 && damageModified <= 0 )
damageModified = 1;
}
if ( self.health - damageModified > 0 )
{
self maps\mp\agents\dog\_dog_think::OnDamage( eInflictor, eAttacker, damageModified, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, timeOffset );
}
// attack the player that has damaged them
if( IsPlayer( eAttacker ) )
{
// is the dog already attacking?
if ( IsDefined( self.attackState ) && self.attackState != "attacking" )
{
// is the attacker within the dog damaged range?
if( DistanceSquared( self.origin, eAttacker.origin ) <= self.dogDamagedRadiusSq )
{
self.favoriteEnemy = eAttacker;
self.forceAttack = true;
self thread maps\mp\agents\dog\_dog_think::watchFavoriteEnemyDeath();
}
}
}
maps\mp\agents\_agents::agent_damage_finished( eInflictor, eAttacker, damageModified, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, timeOffset );
}
play_pain_sound( delay ) // self == dog
{
self endon( "death" );
self PlaySound( ter_op( self.bIsWolf, "anml_wolf_shot_pain", "anml_dog_shot_pain" ) );
self.playing_pain_sound = true;
wait( delay );
self.playing_pain_sound = undefined;
}
spawn_dog( optional_spawnOrigin, optional_spawnAngles, optional_owner ) // self == agent
{
dog_type = 0; // 0 == dog, 1 == wolf
wolf_type = 0; // 0 == wolfB, 1 == wolfC
if( IsDefined( optional_owner ) )
{
if( IsDefined( optional_owner.squad_bot_dog_type ) )
{
dog_type = optional_owner.squad_bot_dog_type;
}
else
{
dog_type = optional_owner GetCommonPlayerDataReservedInt( "mp_dog_type" );
}
}
/#
if( GetDvarInt( "scr_devWolf" ) != 0 )
{
dog_type = GetDvarInt( "scr_devWolf" );
wolf_type = GetDvarInt( "scr_devWolfType" );
}
#/
dog_model = "mp_fullbody_dog_a";
if( dog_type == 1 )
{
if ( wolf_type == 0 )
dog_model = "mp_fullbody_wolf_b";
else
dog_model = "mp_fullbody_wolf_c";
}
if( IsHairRunning() )
dog_model = dog_model + "_fur";
self SetModel( dog_model );
self.species = "dog";
self.OnEnterAnimState = maps\mp\agents\dog\_dog_think::OnEnterAnimState;
// allow killstreaks to pass in specific spawn locations
if( IsDefined(optional_spawnOrigin) && IsDefined(optional_spawnAngles) )
{
spawnOrigin = optional_spawnOrigin;
spawnAngles = optional_spawnAngles;
}
else
{
spawnPoint = self [[level.getSpawnPoint]]();
spawnOrigin = spawnpoint.origin;
spawnAngles = spawnpoint.angles;
}
self activateAgent();
self.spawnTime = GetTime();
self.lastSpawnTime = GetTime();
self.bIsWolf = (dog_type == 1);
self maps\mp\agents\dog\_dog_think::init();
if ( dog_type == 1 )
animclass = "wolf_animclass";
else
animclass = "dog_animclass";
// called from code when an agent is done initializing after AddAgent is called
// this should set up any state specific to this agent and game
self SpawnAgent( spawnOrigin, spawnAngles, animclass, 15, 40, optional_owner );
level notify( "spawned_agent", self );
self maps\mp\agents\_agent_common::set_agent_health( 250 );
// must set the team after SpawnAgent to fix a bug with weapon crosshairs and nametags
if( IsDefined(optional_owner) )
{
self set_agent_team( optional_owner.team, optional_owner );
}
self SetThreatBiasGroup( "Dogs" );
self TakeAllWeapons();
// hide the dog, let the whistle happen, then show the dog
if( IsDefined(self.owner) )
{
self Hide();
wait( 1.0 ); // not sure what to endon for this
// The dog could have died during the 1 second wait (for example if he spawned in a kill trigger), so if that happened,
// don't start thinking since it will cause SREs due to him missing a self.agent_type
if ( !IsAlive(self) )
return;
self Show();
}
self thread [[ self agentFunc("think") ]]();
wait( 0.1 );
if ( IsHairRunning() )
{
if ( dog_type == 1 )
furFX = level.wolfFurFX[ wolf_type ];
else
furFX = level.furFX;
assert( IsDefined( furFX ) );
PlayFXOnTag( furFX, self, "tag_origin" );
}
}

View File

@ -0,0 +1,603 @@
#include maps\mp\_utility;
#include maps\mp\gametypes\_hud_util;
#include common_scripts\utility;
//============================================
// constants
//============================================
CONST_DRONE_HIVE_DEBUG = false;
CONST_MISSILE_COUNT = 2;
CONST_WEAPON_MAIN = "drone_hive_projectile_mp";
CONST_WEAPON_CHILD = "switch_blade_child_mp";
//============================================
// init
//============================================
init()
{
level.killstreakFuncs["drone_hive"] = ::tryUseDroneHive;
level.droneMissileSpawnArray = GetEntArray( "remoteMissileSpawn", "targetname" );
foreach( missileSpawn in level.droneMissileSpawnArray )
{
missileSpawn.targetEnt = GetEnt( missileSpawn.target, "targetname" );
}
}
//============================================
// tryUseDroneHive
//============================================
tryUseDroneHive( lifeId, streakName )
{
/#
if( (!IsDefined(level.droneMissileSpawnArray) || !level.droneMissileSpawnArray.size) )
{
AssertMsg( "map needs remoteMissileSpawn entities" );
}
#/
return useDroneHive( self, lifeId );
}
//============================================
// useDroneHive
//============================================
useDroneHive( player, lifeId )
{
if ( IsDefined( self.underWater ) && self.underWater )
{
return false;
}
player setUsingRemote( "remotemissile" );
player freezeControlsWrapper( true );
player _disableWeaponSwitch();
// JC-10/01/13-Because the init ride of the killstreak has waits
// the killstreak disowned notify could get missed causing the
// usingRemote variable to never get cleared on the player. Start
// these clean up threads off before to prevent this.
level thread monitorDisownKillstreaks( player );
level thread monitorGameEnd( player );
level thread monitorObjectiveCamera(player);
result = player maps\mp\killstreaks\_killstreaks::initRideKillstreak( "drone_hive" );
if ( result == "success" )
{
player freezeControlsWrapper( false );
level thread runDroneHive( player, lifeId);
}
else
{
player notify( "end_kill_streak" ); // stop the monitor threads
player clearUsingRemote();
player _enableWeaponSwitch();
}
return result == "success";
}
//============================================
// watchHostMigrationStartedInit
//============================================
watchHostMigrationStartedInit( player )
{
player endon( "killstreak_disowned" );
player endon ( "disconnect" );
level endon( "game_ended" );
self endon ( "death" );
for (;;)
{
level waittill( "host_migration_begin" );
if ( isDefined( self ) )
{
player VisionSetMissilecamForPlayer( game["thermal_vision"], 0 );
player set_visionset_for_watching_players( "default", 0, undefined, true );
player ThermalVisionFOFOverlayOn();
}
else
{
player SetClientOmnvar( "ui_predator_missile", 2 );
}
}
}
//============================================
// watchHostMigrationFinishedInit
//============================================
watchHostMigrationFinishedInit( player )
{
player endon( "killstreak_disowned" );
player endon ( "disconnect" );
level endon( "game_ended" );
self endon ( "death" );
for (;;)
{
level waittill( "host_migration_end" );
if ( isDefined( self ) )
{
player SetClientOmnvar( "ui_predator_missile", 1 );
player SetClientOmnvar( "ui_predator_missiles_left", self.missilesLeft );
}
else
{
player SetClientOmnvar( "ui_predator_missile", 2 );
}
}
}
//============================================
// useDroneHive
//============================================
runDroneHive( player, lifeId )
{
player endon( "killstreak_disowned" );
level endon( "game_ended" );
player notifyOnPlayerCommand( "missileTargetSet", "+attack" );
player notifyOnPlayerCommand( "missileTargetSet", "+attack_akimbo_accessible" );
remoteMissileSpawn = getBestMissileSpawnPoint( player, level.droneMissileSpawnArray );
startPos = remoteMissileSpawn.origin;
targetPos = remoteMissileSpawn.targetEnt.origin;
vector = VectorNormalize( startPos - targetPos );
startPos = ( vector * 14000 ) + targetPos;
/#
if( CONST_DRONE_HIVE_DEBUG )
{
level thread drawLine( startPos, targetPos, 15, (1,0,0) );
}
#/
rocket = MagicBullet( CONST_WEAPON_MAIN, startpos, targetPos, player );
rocket SetCanDamage( true );
rocket DisableMissileBoosting();
rocket SetMissileMinimapVisible( true );
rocket.team = player.team;
rocket.lifeId = lifeId;
rocket.type = "remote";
rocket.owner = player;
rocket.entityNumber = rocket GetEntityNumber();
level.rockets[ rocket.entityNumber ] = rocket;
level.remoteMissileInProgress = true;
level thread monitorDeath( rocket, true );
level thread monitorBoost( rocket );
// this is used for challenge tracking
if ( IsDefined( player.killsThisLifePerWeapon ) )
{
player.killsThisLifePerWeapon[ CONST_WEAPON_MAIN ] = 0;
player.killsThisLifePerWeapon[ CONST_WEAPON_CHILD ] = 0;
}
missileEyes( player, rocket );
player SetClientOmnvar( "ui_predator_missile", 1 );
rocket thread watchHostMigrationStartedInit( player );
rocket thread watchHostMigrationFinishedInit( player );
missileCount = 0;
rocket.missilesLeft = CONST_MISSILE_COUNT;
player setClientOmnVar( "ui_predator_missiles_left", CONST_MISSILE_COUNT );
while( true )
{
result = rocket waittill_any_return( "death", "missileTargetSet" );
maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone();
if( result == "death" )
break;
//rocket trigger could be hit pre migration then rocket dies during migration
//causing this case
if ( !isDefined( rocket ) )
break;
if( missileCount < CONST_MISSILE_COUNT )
{
level thread spawnSwitchBlade( rocket, missileCount );
missileCount++;
rocket.missilesLeft = CONST_MISSILE_COUNT - missileCount;
player setClientOmnVar( "ui_predator_missiles_left", rocket.missilesLeft );
if( missileCount == CONST_MISSILE_COUNT )
rocket EnableMissileBoosting();
}
}
thread returnPlayer( player );
}
//============================================
// monitorLockedTarget NOT IN USE WorldPointInReticle_Circle doesnt function here
//============================================
monitorLockedTarget()
{
level endon( "game_ended" );
self endon ( "death" );
enemyTargets = [];
sortedTargets = [];
for(;;)
{
targetsInsideReticle = [];
enemyTargets = getEnemyTargets();
foreach( targ in enemyTargets )
{
targInReticle = self.owner WorldPointInReticle_Circle( targ.origin, 65, 90 );
if ( targInReticle )
{
self.owner thread drawLine( self.origin, targ.origin, 10, (0,0,1) );
targetsInsideReticle[targetsInsideReticle.size] = targ;
}
}
if( targetsInsideReticle.size )
{
sortedTargets = SortByDistance( targetsInsideReticle, self.origin );
self.lastTargetLocked = sortedTargets[0];
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( 0.25 );
}
wait ( 0.05 );
maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone();
}
}
//============================================
// getEnemyTargets
//============================================
getEnemyTargets( owner )
{
enemyTargets = [];
foreach ( player in level.participants )
{
if ( owner isEnemy( player )
&& !(player _hasPerk( "specialty_blindeye" ))
)
{
enemyTargets[ enemyTargets.size ] = player;
}
}
enemyVehicleTargets = maps\mp\gametypes\_weapons::lockOnLaunchers_getTargetArray();
if ( enemyTargets.size && enemyVehicleTargets.size )
{
finalTargets = array_combine( enemyTargets, enemyVehicleTargets );
return finalTargets;
}
else if ( enemyTargets.size )
{
return enemyTargets;
}
else
{
return enemyVehicleTargets;
}
}
//============================================
// spawnSwitchBlade
//============================================
spawnSwitchBlade( rocket, spawnOnLeft )
{
rocket.owner playLocalSound( "ammo_crate_use" );
playerViewAngles = rocket GetTagAngles( "tag_camera" );
forwardDir = AnglesToForward( playerViewAngles );
rightDir = AnglesToRight( playerViewAngles );
spawnOffset = (35,35,35);
targetOffset = (15000,15000,15000);
if(spawnOnLeft)
spawnOffset = spawnOffset * -1;
result = BulletTrace( rocket.origin, rocket.origin + (forwardDir * targetOffset), false, rocket );
targetOffset = targetOffset * result["fraction"];
startPosition = rocket.origin + (rightDir * spawnOffset );
targetLocation = rocket.origin + (forwardDir * targetOffset );
targets = rocket.owner getEnemyTargets( rocket.owner );
missile = MagicBullet( CONST_WEAPON_CHILD, startPosition, targetLocation, rocket.owner );
foreach ( targ in targets )
{
if ( Distance2dsquared( targ.origin, targetLocation ) < (512*512) )
{
missile Missile_SetTargetEnt( targ );
break;
}
}
//missile.targEffect = SpawnFx( level.lasedStrikeGlow, targetLocation );
//self thread DrawLine(startPosition, targetLocation, 20, (1,0,0) );
missile SetCanDamage( true );
missile SetMissileMinimapVisible( true );
missile.team = rocket.team;
missile.lifeId = rocket.lifeId;
missile.type = rocket.type;
missile.owner = rocket.owner;
missile.entityNumber = missile GetEntityNumber();
level.rockets[ missile.entityNumber ] = missile;
level thread monitorDeath( missile, false );
}
//============================================
// loop effect for targ
//============================================
loopTriggeredEffect( effect, missile )
{
missile endon( "death" );
level endon( "game_ended" );
self endon ( "death" );
for( ;; )
{
TriggerFX( effect );
wait ( 0.25 );
}
}
//============================================
// getNextMissileSpawnIndex
//============================================
getNextMissileSpawnIndex( oldIndex )
{
index = oldIndex + 1;
if( index == level.droneMissileSpawnArray.size )
{
index = 0;
}
return index;
}
//============================================
// monitorBoost
//============================================
monitorBoost( rocket )
{
rocket endon( "death" );
while( true )
{
rocket.owner waittill( "missileTargetSet" );
rocket notify( "missileTargetSet" );
}
}
//============================================
// getBestMissileSpawnPoint
//============================================
getBestMissileSpawnPoint( owner, remoteMissileSpawnPoints )
{
validEnemies = [];
foreach( player in level.players )
{
if( !isReallyAlive( player ) )
continue;
if( player.team == owner.team )
continue;
if( player.team == "spectator" )
continue;
validEnemies[validEnemies.size] = player;
}
if( !validEnemies.size )
{
return remoteMissileSpawnPoints[ RandomInt(remoteMissileSpawnPoints.size)];
}
remoteMissileSpawnPointsRandomized = array_randomize(remoteMissileSpawnPoints);
bestMissileSpawn = remoteMissileSpawnPointsRandomized[0];
// select a missile spawn that can see the most enemies
foreach( missileSpawn in remoteMissileSpawnPointsRandomized )
{
missileSpawn.sightedEnemies = 0;
for ( i = 0; i < validEnemies.size; i++ )
{
enemy = validEnemies[i];
if( !isReallyAlive( enemy ) )
{
validEnemies[i] = validEnemies[validEnemies.size-1];
validEnemies[validEnemies.size-1] = undefined;
i--;
continue;
}
//IW6 JH made this way cheaper if it sees an enemy it returns it.
if( BulletTracePassed( enemy.origin + (0,0,32), missileSpawn.origin, false, enemy ) )
{
missileSpawn.sightedEnemies += 1;
return missileSpawn;
}
wait(0.05); // This loop is O(n^2), and it does a trace each time, so make sure to only do one per frame
maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone();
}
if ( missileSpawn.sightedEnemies == validEnemies.size )
{
// Optimization - if this missileSpawn can see all enemies, then we're not going to find a better one, so just return it immediately
return missileSpawn;
}
if( missileSpawn.sightedEnemies > bestMissileSpawn.sightedEnemies )
{
bestMissileSpawn = missileSpawn;
}
}
return bestMissileSpawn;
}
//============================================
// missileEyes
//============================================
missileEyes( player, rocket )
{
delayTime = 1.0;
player freezeControlsWrapper( true );
player CameraLinkTo( rocket, "tag_origin" );
player ControlsLinkTo( rocket );
player VisionSetMissilecamForPlayer( "default", delayTime );
player thread set_visionset_for_watching_players( "default", delayTime, undefined, true );
player VisionSetMissilecamForPlayer( game["thermal_vision"], 1.0 );
player thread delayedFOFOverlay();
level thread unfreezeControls( player, delayTime );
}
delayedFOFOverlay()
{
self endon ( "death" );
self endon ( "disconnect" );
level endon ( "game_ended" );
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( 0.25 );
self ThermalVisionFOFOverlayOn();
}
//============================================
// unfreezeControls
//============================================
unfreezeControls( player, delayTime, i )
{
player endon( "disconnect" );
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( delayTime - 0.35 );
player freezeControlsWrapper( false );
}
//============================================
// monitorDisownKillstreaks
//============================================
monitorDisownKillstreaks( player )
{
player endon( "disconnect" );
player endon( "end_kill_streak" );
player waittill( "killstreak_disowned" );
level thread returnPlayer( player );
}
//============================================
// monitorGameEnd
//============================================
monitorGameEnd( player )
{
player endon( "disconnect" );
player endon( "end_kill_streak" );
level waittill( "game_ended" );
level thread returnPlayer( player );
}
//============================================
// monitorObjectiveCamera
//============================================
monitorObjectiveCamera( player )
{
player endon( "end_kill_streak" );
player endon( "disconnect" );
level waittill( "objective_cam" );
level thread returnPlayer( player, true );
}
//============================================
// monitorDeath
//============================================
monitorDeath( killStreakEnt, mainMissile )
{
killStreakEnt waittill( "death" );
maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone();
if ( isDefined( killStreakEnt.targEffect ) )
killStreakEnt.targEffect Delete();
if ( isDefined ( killStreakEnt.entityNumber ) )
level.rockets[ killStreakEnt.entityNumber ] = undefined;
if ( mainMissile )
level.remoteMissileInProgress = undefined;
}
//============================================
// returnPlayer
//============================================
returnPlayer( player, instant )
{
if( !IsDefined(player) )
return;
player SetClientOmnvar( "ui_predator_missile", 2 );
player notify( "end_kill_streak" );
player freezeControlsWrapper( true );
player ThermalVisionFOFOverlayOff();
player ControlsUnlink();
if ( !isDefined( instant ) )
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( 0.95 );
player CameraUnlink();
player SetClientOmnvar( "ui_predator_missile", 0 );
player clearUsingRemote();
player _enableWeaponSwitch();
}

View File

@ -0,0 +1,564 @@
#include maps\mp\_utility;
#include common_scripts\utility;
#include maps\mp\killstreaks\_emp_common;
init()
{
// 2013-06-30 wallace: make sure emp code doesn't run. jammer reuses some of the sstuff, and we don't want the two stomping on each other
/*
level._effect[ "emp_flash" ] = loadfx( "fx/explosions/emp_flash_mp" );
if( level.multiTeamBased )
{
for( i = 0; i < level.teamNameList.size; i++ )
{
level.teamEMPed[level.teamNameList[i]] = false;
}
}
else
{
level.teamEMPed["allies"] = false;
level.teamEMPed["axis"] = false;
}
level.empPlayer = undefined;
level.empTimeout = 15.0;
level.empTimeRemaining = int( level.empTimeout );
if ( level.teamBased )
level thread EMP_TeamTracker();
else
level thread EMP_PlayerTracker();
level.killstreakFuncs["emp"] = ::EMP_Use;
level thread onPlayerConnect();
*/
/#
SetDevDvarIfUninitialized( "scr_emp_timeout", 15.0 );
SetDevDvarIfUninitialized( "scr_emp_damage_debug", 0 );
#/
}
onPlayerConnect()
{
for(;;)
{
level waittill("connected", player);
player thread onPlayerSpawned();
}
}
onPlayerSpawned()
{
self endon("disconnect");
for(;;)
{
self waittill( "spawned_player" );
if ( (level.teamBased && level.teamEMPed[self.team]) || (!level.teamBased && isDefined( level.empPlayer ) && level.empPlayer != self) )
self setEMPJammed( true );
}
}
EMP_Use( lifeId, streakName )
{
assert( isDefined( self ) );
myTeam = self.pers["team"];
if( level.multiTeamBased )
{
self thread EMP_JamTeams( myTeam );
}
else if ( level.teamBased )
{
otherTeam = level.otherTeam[myTeam];
self thread EMP_JamTeam( otherTeam );
}
else
self thread EMP_JamPlayers( self );
self maps\mp\_matchdata::logKillstreakEvent( "emp", self.origin );
self notify( "used_emp" );
return true;
}
EMP_JamTeams( ownerTeam )
{
level endon ( "game_ended" );
assert( ownerTeam == "allies" || ownerTeam == "axis" || IsSubStr( ownerTeam, "team_" ));
thread teamPlayerCardSplash( "used_emp", self );
level notify ( "EMP_JamTeam" + ownerTeam );
level endon ( "EMP_JamTeam" + ownerTeam );
//turn off jammer abilities on emp'd players
foreach ( player in level.players )
{
player playLocalSound( "emp_activate" );
if ( player.team == ownerTeam )
continue;
if ( player _hasPerk( "specialty_localjammer" ) )
player ClearScrambler();
}
VisionSetNaked( "coup_sunblind", 0.1 );
thread empEffects();
wait ( 0.1 );
// resetting the vision set to the same thing won't normally have an effect.
// however, if the client receives the previous visionset change in the same packet as this one,
// this will force them to lerp from the bright one to the normal one.
VisionSetNaked( "coup_sunblind", 0 );
VisionSetNaked( "", 3.0 ); // go to default visionset
for( i = 0; i < level.teamNameList.size; i++ )
{
if( ownerTeam != level.teamNameList[i] )
{
level.teamEMPed[level.teamNameList[i]] = true;
}
}
level notify ( "emp_update" );
for( i = 0; i < level.teamNameList.size; i++ )
{
if( ownerTeam != level.teamNameList[i] )
{
level destroyActiveVehicles( self, level.teamNameList[i] );
}
}
/#
level.empTimeout = GetDvarFloat( "scr_emp_timeout" );
#/
level thread keepEMPTimeRemaining();
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( level.empTimeout );
for( i = 0; i < level.teamNameList.size; i++ )
{
if( ownerTeam != level.teamNameList[i] )
{
level.teamEMPed[level.teamNameList[i]] = false;
}
}
//turn jammer abilities back on
foreach ( player in level.players )
{
if ( player.team == ownerTeam )
continue;
if ( player _hasPerk( "specialty_localjammer" ) )
player MakeScrambler();
}
level notify ( "emp_update" );
}
//jams all players on the team passed in the argument teamName
EMP_JamTeam( teamName )
{
level endon ( "game_ended" );
assert( teamName == "allies" || teamName == "axis" );
thread teamPlayerCardSplash( "used_emp", self );
level notify ( "EMP_JamTeam" + teamName );
level endon ( "EMP_JamTeam" + teamName );
foreach ( player in level.players )
{
player playLocalSound( "emp_activate" );
if ( player.team != teamName )
continue;
if ( player _hasPerk( "specialty_localjammer" ) )
player ClearScrambler();
player VisionSetNakedForPlayer( "coup_sunblind", 0.1 );
}
thread empEffects();
wait ( 0.1 );
// resetting the vision set to the same thing won't normally have an effect.
// however, if the client receives the previous visionset change in the same packet as this one,
// this will force them to lerp from the bright one to the normal one.
VisionSetNaked( "coup_sunblind", 0 );
VisionSetNaked( "", 3.0 ); // go to default visionset
level.teamEMPed[teamName] = true;
level notify ( "emp_update" );
level destroyActiveVehicles( self, teamName );
/#
level.empTimeout = GetDvarFloat( "scr_emp_timeout" );
#/
level thread keepEMPTimeRemaining();
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( level.empTimeout );
level.teamEMPed[teamName] = false;
foreach ( player in level.players )
{
if ( player.team != teamName )
continue;
if ( player _hasPerk( "specialty_localjammer" ) )
player MakeScrambler();
}
level notify ( "emp_update" );
}
EMP_JamPlayers( owner )
{
level notify ( "EMP_JamPlayers" );
level endon ( "EMP_JamPlayers" );
assert( isDefined( owner ) );
foreach ( player in level.players )
{
player playLocalSound( "emp_activate" );
if ( player == owner )
continue;
if ( player _hasPerk( "specialty_localjammer" ) )
player ClearScrambler();
}
VisionSetNaked( "coup_sunblind", 0.1 );
thread empEffects();
wait ( 0.1 );
// resetting the vision set to the same thing won't normally have an effect.
// however, if the client receives the previous visionset change in the same packet as this one,
// this will force them to lerp from the bright one to the normal one.
VisionSetNaked( "coup_sunblind", 0 );
VisionSetNaked( "", 3.0 ); // go to default visionset
level notify ( "emp_update" );
level.empPlayer = owner;
level.empPlayer thread empPlayerFFADisconnect();
level destroyActiveVehicles( owner );
level notify ( "emp_update" );
/#
level.empTimeout = GetDvarFloat( "scr_emp_timeout" );
#/
level thread keepEMPTimeRemaining();
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( level.empTimeout );
foreach ( player in level.players )
{
if ( player == owner )
continue;
if ( player _hasPerk( "specialty_localjammer" ) )
player MakeScrambler();
}
level.empPlayer = undefined;
level notify ( "emp_update" );
level notify ( "emp_ended" );
}
keepEMPTimeRemaining()
{
level notify( "keepEMPTimeRemaining" );
level endon( "keepEMPTimeRemaining" );
level endon( "emp_ended" );
// we need to know how much time is left for the unavailable string
level.empTimeRemaining = int( level.empTimeout );
while( level.empTimeRemaining )
{
wait( 1.0 );
level.empTimeRemaining--;
}
}
empPlayerFFADisconnect()
{
level endon ( "EMP_JamPlayers" );
level endon ( "emp_ended" );
self waittill( "disconnect" );
level notify ( "emp_update" );
}
empEffects()
{
foreach( player in level.players )
{
playerForward = anglestoforward( player.angles );
playerForward = ( playerForward[0], playerForward[1], 0 );
playerForward = VectorNormalize( playerForward );
empDistance = 20000;
empEnt = Spawn( "script_model", player.origin + ( 0, 0, 8000 ) + ( playerForward * empDistance ) );
empEnt setModel( "tag_origin" );
empEnt.angles = empEnt.angles + ( 270, 0, 0 );
empEnt thread empEffect( player );
}
}
empEffect( player )
{
player endon( "disconnect" );
wait( 0.5 );
PlayFXOnTagForClients( level._effect[ "emp_flash" ], self, "tag_origin", player );
}
EMP_TeamTracker()
{
level endon ( "game_ended" );
for ( ;; )
{
level waittill_either ( "joined_team", "emp_update" );
foreach ( player in level.players )
{
if ( player.team == "spectator" )
continue;
// if this emp is over, let's do an extra check to make sure we shouldn't be nuke emped, isEmped() does the extra nuke emp check
if( !level.teamEMPed[ player.team ] && !player isEMPed() )
player enableJammedEffect( false );
else
player enableJammedEffect( true );
}
}
}
EMP_PlayerTracker()
{
level endon ( "game_ended" );
for ( ;; )
{
level waittill_either ( "joined_team", "emp_update" );
foreach ( player in level.players )
{
if ( player.team == "spectator" )
continue;
if ( isDefined( level.empPlayer ) && level.empPlayer != player )
{
player enableJammedEffect( true );
}
else
{
// if this emp is over, let's do an extra check to make sure we shouldn't be nuke emped, isEmped() does the extra nuke emp check
if( !player isEMPed() )
player enableJammedEffect( false );
}
}
}
}
destroyActiveVehicles( attacker, teamEMPed )
{
// thread all of the things that need to get destroyed, this way we can put frame waits in between each destruction so we don't hit the server with a lot at one time
thread destroyActiveHelis( attacker, teamEMPed );
thread destroyActiveLittleBirds( attacker, teamEMPed );
thread destroyActiveTurrets( attacker, teamEMPed );
thread destroyActiveRockets( attacker, teamEMPed );
thread destroyActiveUAVs( attacker, teamEMPed );
thread destroyActiveIMSs( attacker, teamEMPed );
thread destroyActiveUGVs( attacker, teamEMPed );
thread destroyActiveAC130( attacker, teamEMPed );
thread destroyActiveBallDrones( attacker, teamEMPed );
thread destroyTargets( attacker, teamEMPed, level.remote_uav );
thread destroyTargets( attacker, teamEMPed, level.uplinks );
}
destroyTargets( attacker, teamEMPed, targetList )
{
meansOfDeath = "MOD_EXPLOSIVE";
weapon = "killstreak_emp_mp";
damage = 5000;
direction_vec = ( 0, 0, 0 );
point = ( 0, 0, 0 );
modelName = "";
tagName = "";
partName = "";
iDFlags = undefined;
foreach ( target in targetList )
{
if ( level.teamBased && IsDefined( teamEMPed ) )
{
if( IsDefined( target.team ) && target.team != teamEMPed )
continue;
}
else
{
if( IsDefined( target.owner ) && target.owner == attacker )
continue;
}
target notify( "damage", damage, attacker, direction_vec, point, meansOfDeath, modelName, tagName, partName, iDFlags, weapon );
wait( 0.05 );
}
}
destroyActiveHelis( attacker, teamEMPed )
{
destroyTargets( attacker, teamEMPed, level.helis );
}
destroyActiveLittleBirds( attacker, teamEMPed )
{
destroyTargets( attacker, teamEMPed, level.littleBirds );
}
destroyActiveTurrets( attacker, teamEMPed )
{
destroyTargets( attacker, teamEMPed, level.turrets );
}
destroyActiveRockets( attacker, teamEMPed )
{
meansOfDeath = "MOD_EXPLOSIVE";
weapon = "killstreak_emp_mp";
damage = 5000;
direction_vec = ( 0, 0, 0 );
point = ( 0, 0, 0 );
modelName = "";
tagName = "";
partName = "";
iDFlags = undefined;
foreach ( rocket in level.rockets )
{
if ( level.teamBased && IsDefined( teamEMPed ) )
{
if( IsDefined( rocket.team ) && rocket.team != teamEMPed )
continue;
}
else
{
if( IsDefined( rocket.owner ) && rocket.owner == attacker )
continue;
}
// this is different from destroy target
PlayFX( level.remotemissile_fx[ "explode" ], rocket.origin );
rocket delete();
wait( 0.05 );
}
}
destroyActiveUAVs( attacker, teamEMPed )
{
uavArray = level.uavModels;
if ( level.teamBased && IsDefined( teamEMPed ) )
uavArray = level.uavModels[ teamEMPed ];
destroyTargets( attacker, teamEMPed, uavArray );
}
destroyActiveIMSs( attacker, teamEMPed )
{
destroyTargets( attacker, teamEMPed, level.ims );
}
destroyActiveUGVs( attacker, teamEMPed )
{
destroyTargets( attacker, teamEMPed, level.ugvs );
}
destroyActiveAC130( attacker, teamEMPed )
{
meansOfDeath = "MOD_EXPLOSIVE";
weapon = "killstreak_emp_mp";
damage = 5000;
direction_vec = ( 0, 0, 0 );
point = ( 0, 0, 0 );
modelName = "";
tagName = "";
partName = "";
iDFlags = undefined;
if ( level.teamBased && IsDefined( teamEMPed ) )
{
if ( IsDefined( level.ac130player ) && IsDefined( level.ac130player.team ) && level.ac130player.team == teamEMPed )
level.ac130.planeModel notify( "damage", damage, attacker, direction_vec, point, meansOfDeath, modelName, tagName, partName, iDFlags, weapon );
}
else
{
if ( IsDefined( level.ac130player ) )
{
if( !IsDefined( level.ac130.owner ) || ( IsDefined( level.ac130.owner ) && level.ac130.owner != attacker ) )
level.ac130.planeModel notify( "damage", damage, attacker, direction_vec, point, meansOfDeath, modelName, tagName, partName, iDFlags, weapon );
}
}
}
destroyActiveBallDrones( attacker, teamEMPed )
{
destroyTargets( attacker, teamEMPed, level.ballDrones );
}
enableJammedEffect( flag )
{
self setEMPJammed( flag );
shakeValue = 0;
if ( flag )
{
shakeValue = 1;
}
self thread startEmpJamSequence();
}
/#
drawEMPDamageOrigin( pos, ang, radius )
{
while( GetDvarInt( "scr_emp_damage_debug" ) )
{
Line( pos, pos + ( AnglesToForward( ang ) * radius ), ( 1, 0, 0 ) );
Line( pos, pos + ( AnglesToRight( ang ) * radius ), ( 0, 1, 0 ) );
Line( pos, pos + ( AnglesToUp( ang ) * radius ), ( 0, 0, 1 ) );
Line( pos, pos - ( AnglesToForward( ang ) * radius ), ( 1, 0, 0 ) );
Line( pos, pos - ( AnglesToRight( ang ) * radius ), ( 0, 1, 0 ) );
Line( pos, pos - ( AnglesToUp( ang ) * radius ), ( 0, 0, 1 ) );
wait( 0.05 );
}
}
#/

View File

@ -0,0 +1,227 @@
#include maps\mp\_utility;
#include common_scripts\utility;
kEMP_VISION_SET = "coup_sunblind";
kEMP_BLIND_ON_DURATION = 0.05;
kEMP_BLIND_OFF_DURATION = 0.5;
shouldPlayerBeAffectedByEMP()
{
// isEMPed will handle the team and FFA cases
return !(self _hasPerk( "specialty_empimmune" )) && (self isEMPed());
}
applyGlobalEMPEffects()
{
VisionSetNaked( "coup_sunblind", kEMP_BLIND_ON_DURATION );
wait ( kEMP_BLIND_ON_DURATION );
// resetting the vision set to the same thing won't normally have an effect.
// however, if the client receives the previous visionset change in the same packet as this one,
// this will force them to lerp from the bright one to the normal one.
VisionSetNaked( kEMP_VISION_SET, 0 );
VisionSetNaked( "", kEMP_BLIND_OFF_DURATION ); // go to default visionset
}
// these are the effects played when the EMP/jammer is triggered
applyPerPlayerEMPEffects_OnDetonate()
{
self playLocalSound( "emp_activate" );
}
// these only affect enemy players
// should be applied to all players who join late
applyPerPlayerEMPEffects()
{
self setEMPJammed( true );
if ( self _hasPerk( "specialty_localjammer" ) )
{
self ClearScrambler();
}
// self SetClientDvar( "ui_hud_shake_looping", 3 );
self thread startEmpJamSequence();
}
removePerPlayerEMPEffects()
{
self setEMPJammed( false );
if ( self _hasPerk( "specialty_localjammer" ) )
{
self MakeScrambler();
}
// self SetClientDvar( "ui_hud_shake_looping", 0 );
self thread stopEmpJamSequence();
}
kArtifactReps = 3;
kArtifactWaitTimeMin = 0.375;
kArtifactWaitTimeMax = 0.5;
kNoSignalWaitTimeMin = 0.25;
kNoSignalWaitTimeMax = 1.25;
startEmpJamSequence() // self == player
{
level endon("game_ended");
self endon("emp_stop_effect");
self endon("disconnect");
self.bIsPlayingJamEffects = true;
self thread doEmpArtifactLoop();
wait (1.0);
self SetClientOmnvar( "ui_hud_static", 2 );
wait (0.5);
self notify ("emp_stop_artifact");
self SetClientOmnvar( "ui_hud_emp_artifact", 0 );
while ( true )
{
self SetClientOmnvar( "ui_hud_static", 3 );
waitTime = RandomFloatRange(kNoSignalWaitTimeMin, kNoSignalWaitTimeMax);
wait( waitTime );
self SetClientOmnvar( "ui_hud_static", 2 );
wait (0.5);
}
}
stopEmpJamSequence()
{
level endon("game_ended");
self notify("emp_stop_effect");
self endon("disconnect");
if ( IsDefined( self.bIsPlayingJamEffects ) )
{
self.bIsPlayingJamEffects = undefined;
// make sure we're not in a field
self SetClientOmnvar( "ui_hud_static", 0 );
for (i = 0; i < kArtifactReps; i++)
{
self SetClientOmnvar( "ui_hud_emp_artifact", 1 );
wait (0.5);
}
self SetClientOmnvar( "ui_hud_emp_artifact", 0 );
self.player_static_value = 0;
}
}
stopEmpJamSequenceImmediate()
{
self notify("emp_stop_effect");
if ( IsDefined( self.bIsPlayingJamEffects )
|| IsDefined( self.player_static_value)
)
{
self.bIsPlayingJamEffects = undefined;
self.player_static_value = 0;
// make sure we're not in a field
self SetClientOmnvar( "ui_hud_static", 0 );
self SetClientOmnvar( "ui_hud_emp_artifact", 0 );
}
}
doEmpArtifactLoop()
{
self notify ( "emp_stop_artifact" );
level endon("game_ended");
self endon("emp_stop_effect");
self endon("emp_stop_artifact");
self endon("disconnect");
self endon("joined_spectators");
while ( true )
{
self SetClientOmnvar( "ui_hud_emp_artifact", 1 );
waitTime = RandomFloatRange( kArtifactWaitTimeMin, kArtifactWaitTimeMax );
wait ( waitTime );
}
}
doEmpStaticLoop( strengthVal )
{
self notify ( "emp_stop_static" );
level endon("game_ended");
self endon("emp_stop_effect");
self endon("emp_stop_static");
self endon("disconnect");
self endon("joined_spectators");
kStaticWaitTimeMin = 1.0;
kStaticWaitTimeMax = 2.0;
if ( strengthVal == 2 )
{
kStaticWaitTimeMin = 0.5;
kStaticWaitTimeMax = 0.75;
}
while ( true )
{
self SetClientOmnvar( "ui_hud_static", 2 );
waitTime = RandomFloatRange( kStaticWaitTimeMin, kStaticWaitTimeMax );
wait ( waitTime );
}
}
// ------------------------------------
// static field, for mp_snow
// or any other condition that triggers the effect based on proximity
// !!! We are relying on _jammer.gsc::onPlayerSpawned to clean up static field effects on spawn
// (in addition to other emp effects).
// ------------------------------------
staticFieldInit() // self == player
{
self.player_static_value = 0;
}
staticFieldSetStrength( strengthVal ) // self == player
{
if ( self.player_static_value != strengthVal
&& IsAlive( self )
&& !(self isEMPed())
)
{
self.player_static_value = strengthVal;
switch ( strengthVal )
{
case 0:
stopEmpJamSequence();
break;
case 1:
self.bIsPlayingJamEffects = true;
self notify ( "emp_stop_static" );
self thread doEmpArtifactLoop();
self thread doEmpStaticLoop(1);
break;
case 2:
self.bIsPlayingJamEffects = true;
self notify ( "emp_stop_static" );
self notify ( "emp_stop_artifact" );
self thread doEmpStaticLoop(2);
break;
}
}
}
staticFieldGetStrength() // self == player
{
return self.player_static_value;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,404 @@
#include common_scripts\utility;
FLARES_POP_SOUND_NPC = "veh_helo_flares_npc";
FLARES_POP_SOUND_PLR = "veh_helo_flares_plr";
// -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. //
// Flare Init and Deployment
// -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. //
flares_monitor( flareCount ) // self == vehicle
{
self.flaresReserveCount = flareCount;
self.flaresLive = [];
self thread ks_laserGuidedMissile_handleIncoming();
// We no longer have fire and forget launchers.
// self thread flares_handleIncomingSAM();
// self thread flares_handleIncomingStinger();
}
flares_playFx() // self == vehicle
{
for ( i = 0; i < 10; i++ )
{
if ( !IsDefined( self ) )
return;
PlayFXOnTag( level._effect[ "vehicle_flares" ], self, "TAG_FLARE" );
wait ( 0.15 );
}
}
flares_deploy() // self == missile target
{
flare = spawn( "script_origin", self.origin + ( 0, 0, -256 ) );
flare.angles = self.angles;
flare MoveGravity( (0, 0, -1), 5.0 );
self.flaresLive[ self.flaresLive.size ] = flare;
flare thread flares_deleteAfterTime( 5.0, 2.0, self );
playSoundAtPos( flare.origin, FLARES_POP_SOUND_NPC );
return flare;
}
flares_deleteAfterTime( delayDelete, delayStopTracking, vehicle )
{
AssertEx( !IsDefined( delayStopTracking ) || delayStopTracking < delayDelete, "flares_deleteAfterTime() delayDelete should never be greater than delayStopTracking." );
if ( IsDefined( delayStopTracking ) && IsDefined( vehicle ) )
{
delayDelete -= delayStopTracking;
wait( delayStopTracking );
if( IsDefined( vehicle ) )
vehicle.flaresLive = array_remove( vehicle.flaresLive, self );
}
wait( delayDelete );
self Delete();
}
flares_getNumLeft( vehicle )
{
return vehicle.flaresReserveCount;
}
flares_areAvailable( vehicle )
{
flares_cleanFlaresLiveArray( vehicle );
return vehicle.flaresReserveCount > 0 || vehicle.flaresLive.size > 0;
}
flares_getFlareReserve( vehicle )
{
AssertEx( vehicle.flaresReserveCount > 0, "flares_getFlareReserve() called on vehicle without any flares in reserve." );
vehicle.flaresReserveCount--;
vehicle thread flares_playFx();
flare = vehicle flares_deploy();
return flare;
}
flares_cleanFlaresLiveArray( vehicle )
{
vehicle.flaresLive = array_removeUndefined( vehicle.flaresLive );
}
flares_getFlareLive( vehicle )
{
flares_cleanFlaresLiveArray( vehicle );
flare = undefined;
if ( vehicle.flaresLive.size > 0 )
{
flare = vehicle.flaresLive[ vehicle.flaresLive.size - 1 ];
}
return flare;
}
// -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. //
// Missile Incoming Logic
// -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. //
ks_laserGuidedMissile_handleIncoming() //self == vehicle
{
level endon( "game_ended" );
self endon( "death" );
self endon( "crashing" );
self endon( "leaving" );
self endon( "helicopter_done" );
while ( flares_areAvailable( self ) )
{
level waittill( "laserGuidedMissiles_incoming", player, missiles, target );
if ( !IsDefined( target ) || target != self )
continue;
foreach ( missile in missiles )
{
if ( IsValidMissile( missile ) )
{
level thread ks_laserGuidedMissile_monitorProximity( missile, player, player.team, target );
}
}
}
}
ks_laserGuidedMissile_monitorProximity( missile, player, team, target )
{
target endon( "death" );
missile endon( "death" );
missile endon( "missile_targetChanged" );
while ( flares_areAvailable( target ) )
{
if ( !IsDefined( target ) || !IsValidMissile( missile ) )
break;
center = target GetPointInBounds( 0, 0, 0 );
if ( DistanceSquared( missile.origin, center ) < 4000000 ) // 2000 * 2000
{
flare = flares_getFlareLive( target );
if ( !IsDefined( flare ) )
{
flare = flares_getFlareReserve( target );
}
missile Missile_SetTargetEnt( flare );
missile notify( "missile_pairedWithFlare" );
break;
}
waitframe();
}
}
// -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. //
// Old Missile Incoming Logic
// -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. //
flares_handleIncomingSAM( functionOverride )
{
level endon ( "game_ended" );
self endon ( "death" );
self endon ( "crashing" );
self endon ( "leaving" );
self endon ( "helicopter_done" );
for ( ;; )
{
level waittill ( "sam_fired", player, missileGroup, lockTarget );
if ( !IsDefined( lockTarget ) || ( lockTarget != self ) )
continue;
if( IsDefined( functionOverride ) )
level thread [[ functionOverride ]]( player, player.team, lockTarget, missileGroup );
else
level thread flares_watchSAMProximity( player, player.team, lockTarget, missileGroup );
}
}
flares_watchSAMProximity( player, missileTeam, missileTarget, missileGroup ) // self == level
{
level endon ( "game_ended" );
missileTarget endon( "death" );
while( true )
{
center = missileTarget GetPointInBounds( 0, 0, 0 );
curDist = [];
for( i = 0; i < missileGroup.size; i++ )
{
if( IsDefined( missileGroup[ i ] ) )
curDist[ i ] = distance( missileGroup[ i ].origin, center );
}
for( i = 0; i < curDist.size; i++ )
{
if( IsDefined( curDist[ i ] ) )
{
if ( curDist[ i ] < 4000 && missileTarget.flaresReserveCount > 0 )
{
missileTarget.flaresReserveCount--;
missileTarget thread flares_playFx();
newTarget = missileTarget flares_deploy();
for( j = 0; j < missileGroup.size; j++ )
{
if( IsDefined( missileGroup[ j ] ) )
{
missileGroup[ j ] Missile_SetTargetEnt( newTarget );
missileGroup[ j ] notify( "missile_pairedWithFlare" );
}
}
return;
}
}
}
wait ( 0.05 );
}
}
flares_handleIncomingStinger( functionOverride )
{
level endon ( "game_ended" );
self endon ( "death" );
self endon ( "crashing" );
self endon ( "leaving" );
self endon ( "helicopter_done" );
for ( ;; )
{
level waittill ( "stinger_fired", player, missile, lockTarget );
if ( !IsDefined( lockTarget ) || (lockTarget != self) )
continue;
if( IsDefined( functionOverride ) )
missile thread [[ functionOverride ]]( player, player.team, lockTarget );
else
missile thread flares_watchStingerProximity( player, player.team, lockTarget );
}
}
flares_watchStingerProximity( player, missileTeam, missileTarget ) // self == missile
{
self endon ( "death" );
while( true )
{
if ( !isDefined( missileTarget ) )
break;
center = missileTarget GetPointInBounds( 0, 0, 0 );
curDist = distance( self.origin, center );
if ( curDist < 4000 && missileTarget.flaresReserveCount > 0 )
{
missileTarget.flaresReserveCount--;
missileTarget thread flares_playFx();
newTarget = missileTarget flares_deploy();
self Missile_SetTargetEnt( newTarget );
self notify( "missile_pairedWithFlare" );
return;
}
wait ( 0.05 );
}
}
// -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. //
// Manual Flares and Missile Incoming Logic
// -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. //
ks_setup_manual_flares( num_flares, button_action, flares_omnvar_name, incoming_omnvar_name ) // self == vehicle
{
self.flaresReserveCount = num_flares;
self.flaresLive = [];
if( IsDefined( flares_omnvar_name ) )
self.owner SetClientOmnvar( flares_omnvar_name, num_flares );
self thread ks_manualFlares_watchUse( button_action, flares_omnvar_name );
self thread ks_manualFlares_handleIncoming( incoming_omnvar_name );
}
ks_manualFlares_watchUse( button_action, omnvar_name ) // self == vehicle
{
level endon( "game_ended" );
self endon( "death" );
self endon( "crashing" );
self endon( "leaving" );
self endon( "helicopter_done" );
if ( !IsAI(self.owner) ) // Bots handle this internally
self.owner NotifyOnPlayerCommand( "manual_flare_popped", button_action );
while( flares_getNumLeft( self ) )
{
self.owner waittill( "manual_flare_popped" );
flare = flares_getFlareReserve( self );
if( IsDefined( flare ) && IsDefined( self.owner ) && !IsAI( self.owner ) )
{
self.owner PlayLocalSound( FLARES_POP_SOUND_PLR );
if( IsDefined( omnvar_name ) )
self.owner SetClientOmnvar( omnvar_name, self flares_getNumLeft( self ) );
}
}
}
ks_manualFlares_handleIncoming( omnvar_name ) // self == vehicle
{
level endon( "game_ended" );
self endon( "death" );
self endon( "crashing" );
self endon( "leaving" );
self endon( "helicopter_done" );
while( flares_areAvailable( self ) )
{
self waittill( "targeted_by_incoming_missile", missiles );
if( !IsDefined( missiles ) )
continue;
self.owner PlayLocalSound( "missile_incoming" );
self.owner thread ks_watch_death_stop_sound( self, "missile_incoming" );
if( IsDefined( omnvar_name ) )
{
// 1 for center, 2 right, 3 left
// just check the first missile because the others won't matter at this point
vec_to_target = VectorNormalize( missiles[ 0 ].origin - self.origin );
vec_to_right = VectorNormalize( AnglesToRight( self.angles ) );
vec_dot = VectorDot( vec_to_target, vec_to_right );
dir_index = 1;
if( vec_dot > 0 )
dir_index = 2;
else if( vec_dot < 0 )
dir_index = 3;
self.owner SetClientOmnvar( omnvar_name, dir_index );
}
foreach( missile in missiles )
{
if( IsValidMissile( missile ) )
{
self thread ks_manualFlares_monitorProximity( missile );
}
}
}
}
ks_manualFlares_monitorProximity( missile ) // self == vehicle
{
self endon( "death" );
missile endon( "death" );
while( true )
{
if ( !IsDefined( self ) || !IsValidMissile( missile ) )
break;
center = self GetPointInBounds( 0, 0, 0 );
if( DistanceSquared( missile.origin, center ) < 4000000 ) // 2000 * 2000
{
flare = flares_getFlareLive( self );
if( IsDefined( flare ) )
{
missile Missile_SetTargetEnt( flare );
missile notify( "missile_pairedWithFlare" );
self.owner StopLocalSound( "missile_incoming" );
break;
}
}
waitframe();
}
}
ks_watch_death_stop_sound( vehicle, sound ) // self == player
{
self endon( "disconnect" );
vehicle waittill( "death" );
self StopLocalSound( sound );
}

View File

@ -0,0 +1,264 @@
#include maps\mp\_utility;
#include common_scripts\utility;
// replacement for EMP
// only affects aircraft
// remarkably similar to aastrike, should probably get rid of that...
KS_NAME = "gas_airstrike";
init()
{
precacheLocationSelector( "map_artillery_selector" );
config = SpawnStruct();
config.modelNames = [];
config.modelNames[ "allies" ] = "vehicle_mig29_desert";
config.modelNames[ "axis" ] = "vehicle_mig29_desert";
config.inboundSfx = "veh_mig29_dist_loop";
//config.inboundSfx = "veh_aastrike_flyover_loop";
//config.outboundSfx = "veh_aastrike_flyover_outgoing_loop";
config.compassIconFriendly = "compass_objpoint_airstrike_friendly";
config.compassIconEnemy = "compass_objpoint_airstrike_busy";
// sonic boom?
config.speed = 5000;
config.halfDistance = 15000;
config.heightRange = 500;
//config.attackTime = 2.0;
config.outboundFlightAnim = "airstrike_mp_roll";
config.onAttackDelegate = ::dropBombs;
config.onFlybyCompleteDelegate = ::cleanupFlyby;
config.chooseDirection = true;
config.selectLocationVO = "KS_hqr_airstrike";
config.inboundVO = "KS_ast_inbound";
config.bombModel = "projectile_cbu97_clusterbomb";
config.numBombs = 3;
// should be 2x the effect radius to have no gaps/overlap
config.distanceBetweenBombs = 350;
config.effectRadius = 200;
config.effectHeight = 120;
// this should be something else
// remember to precache this
//config.effectVFX = LoadFX( "fx/smoke/poisonous_gas_linger_medium_thick_killer_pulse");
config.effectVFX = LoadFX( "fx/smoke/poisonous_gas_linger_medium_thick_killer_instant");
config.effectMinDelay = 0.25;
config.effectMaxDelay = 0.5;
config.effectLifeSpan = 13;
config.effectCheckFrequency = 1.0;
config.effectDamage = 10;
config.obitWeapon = "gas_strike_mp";
config.killCamOffset = (0, 0, 60);
level.planeConfigs[ KS_NAME ] = config;
level.killstreakFuncs[KS_NAME] = ::onUse;
}
onUse( lifeId, streakName )
{
assert( isDefined( self ) );
// check for active air_superiority strikes
otherTeam = getOtherTeam( self.team );
if ( IsDefined( level.numGasStrikeActive ) )
{
self IPrintLnBold( &"KILLSTREAKS_AIR_SPACE_TOO_CROWDED" );
return false;
}
else
{
result = maps\mp\killstreaks\_plane::selectAirstrikeLocation( lifeId, KS_NAME, ::doStrike );
return ( IsDefined( result ) && result );
}
}
doStrike( lifeId, location, directionYaw, streakName )
{
level.numGasStrikeActive = 0;
wait ( 1 );
planeFlyHeight = maps\mp\killstreaks\_plane::getPlaneFlyHeight();
dirVector = AnglesToForward( (0, directionYaw, 0) );
doOneFlyby( streakName, lifeId, location, dirVector, planeFlyHeight );
self waittill( "gas_airstrike_flyby_complete" );
// play outbound vo
}
doOneFlyby( streakName, lifeId, targetPos, dir, flyHeight )
{
config = level.planeConfigs[ streakName ];
// absolute height should be derived from the heightEnt
flightPath = maps\mp\killstreaks\_plane::getFlightPath( targetPos, dir, config.halfDistance, true, flyHeight, config.speed, 0, streakName );
// Box( targetPos, dir[1], (0, 0, 1), false, 200);
// may want to break this up into spawn, move, cleanup components
// so that we can reuse the plane
level thread maps\mp\killstreaks\_plane::doFlyby( lifeId, self, lifeId,
flightPath["startPoint"] + (0, 0, randomInt(config.heightRange) ),
flightPath["endPoint"] + (0, 0, randomInt(config.heightRange) ),
flightPath["attackTime"],
flightPath["flyTime"],
dir,
streakName );
}
cleanupFlyby( owner, plane, streakName )
{
owner notify( "gas_airstrike_flyby_complete" );
}
dropBombs( pathEnd, flyTime, beginAttackTime, owner, streakName ) // self == plane
{
self endon( "death" );
wait (beginAttackTime);
config = level.planeConfigs[ streakName ];
numBombsLeft = config.numBombs;
timeBetweenBombs = config.distanceBetweenBombs / config.speed;
while (numBombsLeft > 0)
{
self thread dropOneBomb( owner, streakName );
numBombsLeft--;
wait ( timeBetweenBombs );
}
}
dropOneBomb( owner, streakName ) // self == plane
{
level.numGasStrikeActive++;
plane = self;
config = level.planeConfigs[ streakName ];
planeDir = AnglesToForward( plane.angles );
bomb = spawnBomb( config.bombModel, plane.origin, plane.angles );
bomb MoveGravity( ( planeDir * ( config.speed / 1.5 ) ), 3.0 );
// bomb.lifeId = requiredDeathCount;
newBomb = Spawn( "script_model", bomb.origin );
newBomb SetModel( "tag_origin" );
newBomb.origin = bomb.origin;
newBomb.angles = bomb.angles;
bomb SetModel( "tag_origin" );
wait (0.10); // wait two server frames before playing fx
bombOrigin = newBomb.origin;
bombAngles = newBomb.angles;
if ( level.splitscreen )
{
playfxontag( level.airstrikessfx, newBomb, "tag_origin" );
}
else
{
playfxontag( level.airstrikefx, newBomb, "tag_origin" );
}
wait ( 1.0 );
trace = BulletTrace(newBomb.origin, newBomb.origin + (0,0,-1000000), false, undefined);
impactPosition = trace["position"];
// Line( newBomb.origin, impactPosition, (1, 0, 0), 1, true, 20 * config.effectLifeSpan);
// artillery damage center?
// set up kill cam?
// need to wait the right amount of time before
bomb onBombImpact( owner, impactPosition, streakName );
newBomb delete();
bomb delete();
level.numGasStrikeActive--;
if (level.numGasStrikeActive == 0)
{
level.numGasStrikeActive = undefined;
}
}
spawnBomb( modelName, origin, angles )
{
bomb = Spawn( "script_model", origin );
bomb.angles = angles;
bomb SetModel( modelname );
return bomb;
}
onBombImpact( owner, position, streakName ) // self == bomb?
{
config = level.planeConfigs[ streakName ];
// position = self.origin;
effectArea = Spawn( "trigger_radius", position, 0, config.effectRadius, config.effectHeight );
effectArea.owner = owner;
effectRadius = config.effectRadius;
vfx = SpawnFx( config.effectVFX, position );
TriggerFX( vfx );
wait ( RandomFloatRange( config.effectMinDelay, config.effectMaxDelay ) );
timeRemaining = config.effectLifeSpan;
// self.primaryWeapon = config.obitWeapon;
killCamEnt = Spawn( "script_model", position + config.killCamOffset );
killCamEnt LinkTo( effectArea );
self.killCamEnt = killCamEnt;
//self.killCamEnt SetScriptMoverKillCam( "explosive" );
while ( timeRemaining > 0.0 )
{
foreach ( character in level.characters )
{
character applyGasEffect( owner, position, effectArea, self, config.effectDamage );
}
wait ( config.effectCheckFrequency );
timeRemaining -= config.effectCheckFrequency;
}
self.killCamEnt Delete();
effectArea Delete();
vfx Delete();
}
applyGasEffect( attacker, position, trigger, inflictor, damage ) // self == target
{
if( (attacker isEnemy( self )) && IsAlive( self ) && self IsTouching( trigger ) )
{
// rumble
inflictor RadiusDamage( self.origin, 1, damage, damage, attacker, "MOD_RIFLE_BULLET", "gas_strike_mp");
// self DoDamage( damage, position, attacker, inflictor, "MOD_UNKNOWN" );
if ( !(self isUsingRemote()) )
{
duration = maps\mp\perks\_perkfunctions::applyStunResistence( 2.0 );
self ShellShock( "default", duration );
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,481 @@
#include maps\mp\_utility;
#include common_scripts\utility;
init()
{
precacheVehicle( "attack_littlebird_mp" );
//precacheString( &"HELICOPTER_FLOCK_INBOUND" );
precacheModel( "vehicle_apache_mp" );
precacheModel( "vehicle_apache_mg" );
precacheTurret( "apache_minigun_mp" );
precacheVehicle( "apache_strafe_mp" );
level.killStreakFuncs["littlebird_flock"] = ::tryUseLbFlock;
level.heli_flock = [];
}
tryUseLbFlock( lifeId, streakName )
{
numIncomingVehicles = 5;
if( heliFlockActive() || currentActiveVehicleCount() >= maxVehiclesAllowed() || level.fauxVehicleCount + numIncomingVehicles >= maxVehiclesAllowed() )
{
self iPrintLnBold( &"KILLSTREAKS_TOO_MANY_VEHICLES" );
return false;
}
// increment the faux vehicle count before we spawn the vehicle so no other vehicles try to spawn
incrementFauxVehicleCount();
incrementFauxVehicleCount();
incrementFauxVehicleCount();
incrementFauxVehicleCount();
incrementFauxVehicleCount();
result = self selectLbStrikeLocation( lifeId, "littlebird_flock" );
if ( ( !isDefined( result ) || !result ) )
{
// decrement the faux vehicle count since this failed to spawn
decrementFauxVehicleCount();
decrementFauxVehicleCount();
decrementFauxVehicleCount();
decrementFauxVehicleCount();
decrementFauxVehicleCount();
return false;
}
level thread teamPlayerCardSplash( "used_littlebird_flock", self, self.team );
return true;
}
heliFlockActive()
{
result = false;
for ( i=0; i<level.heli_flock.size; i++ )
{
if ( isDefined( level.heli_flock[i] ) )
{
result = true;
break;
}
}
return result;
}
selectLbStrikeLocation( lifeId, streakName )
{
self PlayLocalSound( game[ "voice" ][ self.team ] + "KS_lbd_inposition" );
self _beginLocationSelection( streakName, "map_artillery_selector", true, 500 );
self endon( "stop_location_selection" );
// wait for the selection
self waittill( "confirm_location", location, locationYaw);
if( heliFlockActive() || currentActiveVehicleCount() >= maxVehiclesAllowed() || level.fauxVehicleCount >= maxVehiclesAllowed() )
{
self iPrintLnBold( &"KILLSTREAKS_TOO_MANY_VEHICLES" );
self notify( "cancel_location" );
return false;
}
level.heli_flock = [];
level.heli_flock_victims = [];
self thread littlebirdMadeSelectionVO();
self thread finishLbStrikeUsage( lifeId, location, ::callStrike, locationYaw );
self setblurforplayer( 0, 0.3 );
return true;
}
littlebirdMadeSelectionVO()
{
self endon( "death" );
self endon( "disconnect" );
self PlayLocalSound( game[ "voice" ][ self.team ] + "KS_hqr_littlebird" );
wait( 3.0 );
self PlayLocalSound( game[ "voice" ][ self.team ] + "KS_lbd_inbound" );
}
finishLbStrikeUsage( lifeId, location, usedCallback, locationYaw )
{
self notify( "used" );
wait ( 0.05 );
self thread stopLocationSelection( false );
if ( isDefined( self ) )
self thread [[usedCallback]]( lifeId, location, locationYaw );
}
callStrike( lifeId, location, locationYaw )
{
level endon( "game_ended" );
self endon("disconnect");
self thread handleOwnerLeft();
// get flight paths all at once
flightPath1 = getFlightPath( location, locationYaw, 0 );
flightPath2 = getFlightPath( location, locationYaw, -520 );
flightPath3 = getFlightPath( location, locationYaw, 520 );
flightPath4 = getFlightPath( location, locationYaw, -1040 );
flightPath5 = getFlightPath( location, locationYaw, 1040 );
// leader
level thread doLbStrike( lifeId, self, flightPath1, 0 );
wait( 0.3 );
// left wingman
level thread doLbStrike( lifeId, self, flightPath2, 1 );
// right wingman
level thread doLbStrike( lifeId, self, flightPath3, 2 );
wait( 0.3 );
// left wingman
level thread doLbStrike( lifeId, self, flightPath4, 3 );
// right wingman
level thread doLbStrike( lifeId, self, flightPath5, 4 );
// log it
self maps\mp\_matchdata::logKillstreakEvent( "littlebird_flock", location );
}
getFlightPath( location, locationYaw, rightOffset )
{
location = location * (1,1,0);
initialDirection = ( 0, locationYaw, 0 );
planeHalfDistance = 12000;
flightPath = [];
if ( isDefined( rightOffset ) && rightOffset != 0 )
location = location + ( AnglesToRight( initialDirection ) * rightOffset ) + ( 0, 0, RandomInt( 300 ) );
// start point
startPoint = ( location + ( AnglesToForward( initialDirection ) * ( -1 * planeHalfDistance ) ) );
// end point
endPoint = ( location + ( AnglesToForward( initialDirection ) * planeHalfDistance ) );
// get height
flyHeight = self maps\mp\killstreaks\_airdrop::getFlyHeightOffset( location ) + 256;
flightPath["start"] = startPoint + ( 0, 0, flyHeight );
flightPath["end"] = endPoint + ( 0, 0, flyHeight );
return flightPath;
}
doLbStrike( lifeId, owner, flightPath, flockIndex )
{
level endon( "game_ended" );
if ( !isDefined( owner ) )
return;
forward = vectorToAngles( flightPath["end"] - flightPath["start"] );
lb = spawnAttackLittleBird( owner, flightPath["start"], forward, flockIndex );
lb.lifeId = lifeId;
lb.alreadyDead = false;
lb thread watchTimeOut();
lb thread watchDeath();
lb thread flock_handleDamage();
lb thread startLbFiring1();
lb thread monitorKills();
lb endon( "death" );
// initial target run
lb SetMaxPitchRoll( 120, 60 );
lb Vehicle_SetSpeed( 48, 48 );
lb setVehGoalPos( flightPath["end"], 0 );
lb waittill( "goal" );
// turn
lb SetMaxPitchRoll( 30, 40 );
lb Vehicle_SetSpeed( 32, 32 );
lb setVehGoalPos( flightPath["start"], 0 );
wait( 2 );
// return
lb SetMaxPitchRoll( 100, 60 );
lb Vehicle_SetSpeed( 64, 64 );
lb waittill ( "goal" );
lb notify( "gone" );
// remove
lb maps\mp\killstreaks\_helicopter::removeLittlebird();
}
spawnAttackLittleBird( owner, origin, forward, flockIndex )
{
lb = spawnHelicopter( owner, origin, forward, "apache_strafe_mp" , "vehicle_apache_mp" );
if ( !isDefined( lb ) )
return;
lb maps\mp\killstreaks\_helicopter::addToLittleBirdList();
lb thread maps\mp\killstreaks\_helicopter::removeFromLittleBirdListOnDeath();
lb.health = 999999; // keep it from dying anywhere in code
lb.maxHealth = 2000; // this is the health we'll check
lb.damageTaken = 0; // how much damage has it taken
lb setCanDamage( true );
lb.owner = owner;
lb.team = owner.team;
lb.killCount = 0;
lb.streakName = "littlebird_flock";
lb.heliType = "littlebird";
//lb ThermalDrawEnable();
lb.specialDamageCallback = ::Callback_VehicleDamage;
mgTurret1 = spawnTurret( "misc_turret", lb.origin, "apache_minigun_mp" );
mgTurret1 linkTo( lb, "tag_turret", (0,0,0), (0,0,0) );
mgTurret1 setModel( "vehicle_apache_mg" );
mgTurret1.angles = lb.angles;
mgTurret1.owner = lb.owner;
mgTurret1.team = mgTurret1.owner.team;
mgTurret1 makeTurretInoperable();
mgTurret1.vehicle = lb;
//mgTurret1 ThermalDrawEnable();
killCamOrigin = ( lb.origin + ( ( AnglesToForward( lb.angles ) * -200 ) + ( AnglesToRight( lb.angles ) * -200 ) ) ) + ( 0, 0, 50 );
mgTurret1.killCamEnt = Spawn( "script_model", killCamOrigin );
mgTurret1.killCamEnt SetScriptMoverKillCam( "explosive" );
mgTurret1.killCamEnt LinkTo( lb, "tag_origin" );
lb.mgTurret1 = mgTurret1;
lb.mgTurret1 SetDefaultDropPitch( 0 );
lb.mgTurret1 setMode( "auto_nonai" );
lb.mgTurret1 SetSentryOwner( lb.owner );
if ( level.teamBased )
{
lb.mgTurret1 setTurretTeam( lb.owner.team );
}
level.heli_flock[flockIndex] = lb;
return lb;
}
watchTimeOut()
{
level endon( "game_ended" );
self endon( "gone" );
self endon( "death" );
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( 60.0 );
self notify( "death" );
}
monitorKills()
{
level endon( "game_ended" );
self endon( "gone" );
self endon( "death" );
self endon( "stopFiring" );
for(;;)
{
self waittill( "killedPlayer", player );
self.killCount++;
level.heli_flock_victims[level.heli_flock_victims.size] = player;
}
}
startLbFiring1( )
{
self endon( "gone" );
self endon( "death" );
self endon( "stopFiring" );
for( ;; )
{
self.mgTurret1 waittill( "turret_on_target" );
fireOnTarget = true;
targetPlayer = self.mgTurret1 GetTurretTarget( false );
foreach ( victim in level.heli_flock_victims )
{
if ( targetPlayer == victim )
{
self.mgTurret1 ClearTargetEntity();
fireOnTarget = false;
break;
}
}
if ( fireOnTarget )
self.mgTurret1 ShootTurret();
}
}
handleOwnerLeft() // self == owner
{
level endon( "game_ended" );
self endon( "flock_done" );
self thread notifyOnFlockDone();
self waittill( "killstreak_disowned" );
for ( i=0; i<level.heli_flock.size; i++ )
{
if ( isDefined( level.heli_flock[i] ) )
level.heli_flock[i] notify( "stopFiring" );
}
for ( i=0; i<level.heli_flock.size; i++ )
{
if ( isDefined( level.heli_flock[i] ) )
{
level.heli_flock[i] notify( "death" );
wait( 0.1 );
}
}
}
notifyOnFlockDone() // self == owner
{
level endon( "game_ended" );
self endon( "disconnect" );
if ( !bot_is_fireteam_mode() )
{
self endon( "joined_team" );
self endon( "joined_spectators" );
}
while( heliFlockActive() )
wait( 0.5 );
self notify( "flock_done" );
}
flock_handleDamage() // self == heli
{
self endon( "death" );
level endon( "game_ended" );
while( true )
{
self waittill( "damage", damage, attacker, direction_vec, point, meansOfDeath, modelName, tagName, partName, iDFlags, weapon );
if( IsDefined( self.specialDamageCallback ) )
self [[self.specialDamageCallback]]( undefined, attacker, damage, iDFlags, meansOfDeath, weapon, point, direction_vec, undefined, undefined, modelName, partName );
}
}
Callback_VehicleDamage( inflictor, attacker, damage, iDFlags, meansOfDeath, weapon, point, dir, hitLoc, timeOffset, modelIndex, partName )
{
if( isDefined( self.alreadyDead ) && self.alreadyDead )
return;
if( !isDefined( attacker ) || attacker == self )
return;
// don't allow people to destroy things on their team if FF is off
if( !maps\mp\gametypes\_weapons::friendlyFireCheck( self.owner, attacker ) )
return;
if( isDefined( iDFlags ) && ( iDFlags & level.iDFLAGS_PENETRATION ) )
self.wasDamagedFromBulletPenetration = true;
self.wasDamaged = true;
modifiedDamage = damage;
if ( isPlayer( attacker ) )
{
attacker maps\mp\gametypes\_damagefeedback::updateDamageFeedback( "helicopter" );
if( meansOfDeath == "MOD_RIFLE_BULLET" || meansOfDeath == "MOD_PISTOL_BULLET" )
{
if ( attacker _hasPerk( "specialty_armorpiercing" ) )
modifiedDamage += damage * level.armorPiercingMod;
}
}
// in case we are shooting from a remote position, like being in the osprey gunner shooting this
if( IsDefined( attacker.owner ) && IsPlayer( attacker.owner ) )
{
attacker.owner maps\mp\gametypes\_damagefeedback::updateDamageFeedback( "helicopter" );
}
if( IsDefined( weapon ) )
{
switch( weapon )
{
case "ac130_105mm_mp":
case "ac130_40mm_mp":
case "stinger_mp":
case "javelin_mp":
case "remote_mortar_missile_mp":
case "remotemissile_projectile_mp":
self.largeProjectileDamage = true;
modifiedDamage = self.maxHealth + 1;
break;
case "sam_projectile_mp":
self.largeProjectileDamage = true;
modifiedDamage = self.maxHealth * 0.25; // takes about 1 burst of sam rockets
break;
case "emp_grenade_mp":
self.largeProjectileDamage = false;
modifiedDamage = self.maxHealth + 1;
break;
}
maps\mp\killstreaks\_killstreaks::killstreakHit( attacker, weapon, self );
}
self.damageTaken += modifiedDamage;
if( self.damageTaken >= self.maxHealth )
{
if ( isPlayer( attacker ) && ( !isDefined( self.owner ) || attacker != self.owner ) )
{
self.alreadyDead = true;
attacker notify( "destroyed_helicopter" );
attacker notify( "destroyed_killstreak", weapon );
thread teamPlayerCardSplash( "callout_destroyed_helicopter", attacker );
attacker thread maps\mp\gametypes\_rank::giveRankXP( "kill", 300, weapon, meansOfDeath );
attacker thread maps\mp\gametypes\_rank::xpEventPopup( "destroyed_helicopter" );
thread maps\mp\gametypes\_missions::vehicleKilled( self.owner, self, undefined, attacker, damage, meansOfDeath, weapon );
}
self notify ( "death" );
}
}
watchDeath()
{
self endon( "gone" );
self waittill( "death" );
self thread maps\mp\killstreaks\_helicopter::lbOnKilled();
}

View File

@ -0,0 +1,834 @@
#include maps\mp\_utility;
#include common_scripts\utility;
EMP_GRENADE_TIME = 3.5;
init()
{
level.killStreakFuncs[ "littlebird_support" ] = ::tryUseLBSupport;
level.heliGuardSettings = [];
level.heliGuardSettings[ "littlebird_support" ] = spawnStruct();
level.heliGuardSettings[ "littlebird_support" ].timeOut = 60.0;
level.heliGuardSettings[ "littlebird_support" ].health = 999999; // keep it from dying anywhere in code
level.heliGuardSettings[ "littlebird_support" ].maxHealth = 2000; // this is what we check against for death
level.heliGuardSettings[ "littlebird_support" ].streakName = "littlebird_support";
level.heliGuardSettings[ "littlebird_support" ].vehicleInfo = "attack_littlebird_mp";
level.heliGuardSettings[ "littlebird_support" ].weaponInfo = "littlebird_guard_minigun_mp";
level.heliGuardSettings[ "littlebird_support" ].weaponModelLeft = "vehicle_little_bird_minigun_left";
level.heliGuardSettings[ "littlebird_support" ].weaponModelRight = "vehicle_little_bird_minigun_right";
level.heliGuardSettings[ "littlebird_support" ].weaponTagLeft = "tag_flash";
level.heliGuardSettings[ "littlebird_support" ].weaponTagRight = "tag_flash_2";
level.heliGuardSettings[ "littlebird_support" ].sentryMode = "auto_nonai";
level.heliGuardSettings[ "littlebird_support" ].modelBase = level.littlebird_model;
level.heliGuardSettings[ "littlebird_support" ].teamSplash = "used_littlebird_support";
lbSupport_setAirStartNodes();
lbSupport_setAirNodeMesh();
/#
SetDevDvarIfUninitialized( "scr_lbguard_timeout", 60.0 );
#/
}
tryUseLBSupport( lifeId, streakName ) // self == player
{
heliGuardType = "littlebird_support";
numIncomingVehicles = 1;
if( IsDefined( level.littlebirdGuard ) || maps\mp\killstreaks\_helicopter::exceededMaxLittlebirds( heliGuardType ) )
{
self IPrintLnBold( &"KILLSTREAKS_AIR_SPACE_TOO_CROWDED" );
return false;
}
else if( !level.air_node_mesh.size )
{
self IPrintLnBold( &"KILLSTREAKS_UNAVAILABLE_IN_LEVEL" );
return false;
}
else if( currentActiveVehicleCount() >= maxVehiclesAllowed() || level.fauxVehicleCount + numIncomingVehicles >= maxVehiclesAllowed() )
{
self IPrintLnBold( &"KILLSTREAKS_TOO_MANY_VEHICLES" );
return false;
}
// increment the faux vehicle count before we spawn the vehicle so no other vehicles try to spawn
incrementFauxVehicleCount();
littleBird = createLBGuard( heliGuardType );
if ( !IsDefined( littleBird ) )
{
// decrement the faux vehicle count since this failed to spawn
decrementFauxVehicleCount();
return false;
}
self thread startLBSupport( littleBird );
level thread teamPlayerCardSplash( level.heliGuardSettings[ heliGuardType ].teamSplash, self, self.team );
return true;
}
createLBGuard( heliGuardType )
{
closestStartNode = lbSupport_getClosestStartNode( self.origin );
if( IsDefined( closestStartNode.angles ) )
startAng = closestStartNode.angles;
else
startAng = ( 0, 0, 0);
flyHeight = self maps\mp\killstreaks\_airdrop::getFlyHeightOffset( self.origin );
closestNode = lbSupport_getClosestNode( self.origin );
forward = AnglesToForward( self.angles );
targetPos = ( closestNode.origin*(1,1,0) ) + ( (0,0,1)*flyHeight ) + ( forward * -100 );
startPos = closestStartNode.origin;
lb = spawnHelicopter( self, startPos, startAng, level.heliGuardSettings[ heliGuardType ].vehicleInfo, level.heliGuardSettings[ heliGuardType ].modelBase );
if ( !IsDefined( lb ) )
return;
lb maps\mp\killstreaks\_helicopter::addToLittleBirdList();
lb thread maps\mp\killstreaks\_helicopter::removeFromLittleBirdListOnDeath();
lb.health = level.heliGuardSettings[ heliGuardType ].health;
lb.maxHealth = level.heliGuardSettings[ heliGuardType ].maxHealth;
lb.damageTaken = 0; // how much damage has it taken
lb.speed = 100;
lb.followSpeed = 40;
lb.owner = self;
lb SetOtherEnt(self);
lb.team = self.team;
lb SetMaxPitchRoll( 45, 45 );
lb Vehicle_SetSpeed( lb.speed, 100, 40 );
lb SetYawSpeed( 120, 60 );
lb setneargoalnotifydist( 512 );
lb.killCount = 0;
lb.heliType = "littlebird";
lb.heliGuardType = "littlebird_support";
lb.targettingRadius = 2000; // matches the maxRange on the turret gdt setting
//lb ThermalDrawEnable();
lb make_entity_sentient_mp( lb.team );
lb.targetPos = targetPos;
lb.currentNode = closestNode;
mgTurret = SpawnTurret( "misc_turret", lb.origin, level.heliGuardSettings[ heliGuardType ].weaponInfo );
mgTurret LinkTo( lb, level.heliGuardSettings[ heliGuardType ].weaponTagLeft, (0,0,0), (0,0,0) );
mgTurret SetModel( level.heliGuardSettings[ heliGuardType ].weaponModelLeft );
mgTurret.angles = lb.angles;
mgTurret.owner = lb.owner;
mgTurret.team = self.team;
mgTurret makeTurretInoperable();
mgTurret.vehicle = lb;
//mgTurret ThermalDrawEnable();
lb.mgTurretLeft = mgTurret;
lb.mgTurretLeft SetDefaultDropPitch( 0 );
killCamOrigin = ( lb.origin + ( ( AnglesToForward( lb.angles ) * -100 ) + ( AnglesToRight( lb.angles ) * -100 ) ) ) + ( 0, 0, 50 );
mgTurret.killCamEnt = Spawn( "script_model", killCamOrigin );
mgTurret.killCamEnt SetScriptMoverKillCam( "explosive" );
mgTurret.killCamEnt LinkTo( lb, "tag_origin" );
mgTurret = SpawnTurret( "misc_turret", lb.origin, level.heliGuardSettings[ heliGuardType ].weaponInfo );
mgTurret LinkTo( lb, level.heliGuardSettings[ heliGuardType ].weaponTagRight, (0,0,0), (0,0,0) );
mgTurret SetModel( level.heliGuardSettings[ heliGuardType ].weaponModelRight );
mgTurret.angles = lb.angles;
mgTurret.owner = lb.owner;
mgTurret.team = self.team;
mgTurret makeTurretInoperable();
mgTurret.vehicle = lb;
lb.mgTurretRight = mgTurret;
lb.mgTurretRight SetDefaultDropPitch( 0 );
killCamOrigin = ( lb.origin + ( ( AnglesToForward( lb.angles ) * -100 ) + ( AnglesToRight( lb.angles ) * 100 ) ) ) + ( 0, 0, 50 );
mgTurret.killCamEnt = Spawn( "script_model", killCamOrigin );
mgTurret.killCamEnt SetScriptMoverKillCam( "explosive" );
mgTurret.killCamEnt LinkTo( lb, "tag_origin" );
if ( level.teamBased )
{
lb.mgTurretLeft setTurretTeam( self.team );
lb.mgTurretRight setTurretTeam( self.team );
}
lb.mgTurretLeft SetMode( level.heliGuardSettings[ heliGuardType ].sentryMode );
lb.mgTurretRight SetMode( level.heliGuardSettings[ heliGuardType ].sentryMode );
lb.mgTurretLeft SetSentryOwner( self );
lb.mgTurretRight SetSentryOwner( self );
lb.mgTurretLeft thread lbSupport_attackTargets();
lb.mgTurretRight thread lbSupport_attackTargets();
lb.attract_strength = 10000;
lb.attract_range = 150;
lb.attractor = Missile_CreateAttractorEnt( lb, lb.attract_strength, lb.attract_range );
lb.hasDodged = false;
lb.empGrenaded = false;
lb thread lbSupport_handleDamage();
lb thread lbSupport_watchDeath();
lb thread lbSupport_watchTimeout();
lb thread lbSupport_watchOwnerLoss();
lb thread lbSupport_watchOwnerDamage();
lb thread lbSupport_watchRoundEnd();
lb thread lbSupport_lightFX();
level.littlebirdGuard = lb;
lb.owner maps\mp\_matchdata::logKillstreakEvent( level.heliGuardSettings[ lb.heliGuardType ].streakName, lb.targetPos );
return lb;
}
lbSupport_lightFX()
{
PlayFXOnTag( level.chopper_fx["light"]["left"], self, "tag_light_nose" );
wait ( 0.05 );
PlayFXOnTag( level.chopper_fx["light"]["belly"], self, "tag_light_belly" );
wait ( 0.05 );
PlayFXOnTag( level.chopper_fx["light"]["tail"], self, "tag_light_tail1" );
wait ( 0.05 );
PlayFXOnTag( level.chopper_fx["light"]["tail"], self, "tag_light_tail2" );
}
startLBSupport( littleBird ) // self == player
{
level endon( "game_ended" );
littleBird endon( "death" );
// look at the player
littleBird SetLookAtEnt( self );
// go to pos
littleBird setVehGoalPos( littleBird.targetPos );
littleBird waittill( "near_goal" );
littleBird Vehicle_SetSpeed( littleBird.speed, 60, 30 );
littleBird waittill ( "goal" );
// drop to target
littleBird setVehGoalPos( littleBird.currentNode.origin, 1 );
littleBird waittill ( "goal" );
// begin following player
littleBird thread lbSupport_followPlayer();
// dodge the first sam or lock-on attack
littleBird thread maps\mp\killstreaks\_flares::flares_handleIncomingSAM( ::lbSupport_watchSAMProximity );
littleBird thread maps\mp\killstreaks\_flares::flares_handleIncomingStinger( ::lbSupport_watchStingerProximity );
}
lbSupport_followPlayer() // self == lb
{
level endon( "game_ended" );
self endon( "death" );
self endon( "leaving" );
if( !IsDefined( self.owner ) )
{
self thread lbSupport_leave();
return;
}
self.owner endon( "disconnect" );
self endon( "owner_gone" );
self Vehicle_SetSpeed( self.followSpeed, 20, 20 );
while( true )
{
if( IsDefined( self.owner ) && IsAlive( self.owner ) )
{
currentNode = lbSupport_getClosestLinkedNode( self.owner.origin );
if( IsDefined( currentNode ) && currentNode != self.currentNode )
{
self.currentNode = currentNode;
// don't thread because we want to waittill goal before we pick the next node
lbSupport_moveToPlayer();
continue;
}
}
wait( 1 );
}
}
lbSupport_moveToPlayer() // self == lb
{
level endon( "game_ended" );
self endon( "death" );
self endon( "leaving" );
self.owner endon( "death" );
self.owner endon( "disconnect" );
self endon( "owner_gone" );
self notify( "lbSupport_moveToPlayer" );
self endon( "lbSupport_moveToPlayer" );
self.inTransit = true;
self setVehGoalPos( self.currentNode.origin + ( 0, 0, 100 ), 1 );
self waittill ( "goal" );
self.inTransit = false;
self notify( "hit_goal" );
}
//
// state trackers
//
lbSupport_watchDeath()
{
level endon( "game_ended" );
self endon( "gone" );
self waittill( "death" );
self thread maps\mp\killstreaks\_helicopter::lbOnKilled();
}
lbSupport_watchTimeout()
{
level endon ( "game_ended" );
self endon( "death" );
self.owner endon( "disconnect" );
self endon( "owner_gone" );
timeout = level.heliGuardSettings[ self.heliGuardType ].timeOut;
/#
timeout = GetDvarFloat( "scr_lbguard_timeout" );
#/
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( timeout );
self thread lbSupport_leave();
}
lbSupport_watchOwnerLoss()
{
level endon ( "game_ended" );
self endon( "death" );
self endon( "leaving" );
self.owner waittill( "killstreak_disowned" );
self notify( "owner_gone" );
// leave
self thread lbSupport_leave();
}
lbSupport_watchOwnerDamage() // self == lb
{
level endon ( "game_ended" );
self endon( "death" );
self endon( "leaving" );
self.owner endon( "disconnect" );
self endon( "owner_gone" );
while( true )
{
// if someone is attacking the owner, attack them
self.owner waittill( "damage", damage, attacker, direction_vec, point, meansOfDeath, modelName, tagName, partName, iDFlags, weapon );
if( isPlayer( attacker ) )
{
if( attacker != self.owner &&
Distance2D( attacker.origin, self.origin ) <= self.targettingRadius &&
!attacker _hasPerk( "specialty_blindeye" ) &&
!( level.hardcoreMode && level.teamBased && attacker.team == self.team ) )
{
self SetLookAtEnt( attacker );
if( IsDefined( self.mgTurretLeft ) )
self.mgTurretLeft SetTargetEntity( attacker );
if( IsDefined( self.mgTurretRight ) )
self.mgTurretRight SetTargetEntity( attacker );
}
}
}
}
lbSupport_watchRoundEnd()
{
self endon( "death" );
self endon( "leaving" );
self.owner endon( "disconnect" );
self endon( "owner_gone" );
level waittill_any( "round_end_finished", "game_ended" );
// leave
self thread lbSupport_leave();
}
lbSupport_leave()
{
self endon( "death" );
self notify( "leaving" );
level.littlebirdGuard = undefined;
self ClearLookAtEnt();
// rise
flyHeight = self maps\mp\killstreaks\_airdrop::getFlyHeightOffset( self.origin );
targetPos = self.origin + (0,0,flyHeight);
self Vehicle_SetSpeed( self.speed, 60 );
self SetMaxPitchRoll( 45, 180 );
self setVehGoalPos( targetPos );
self waittill ( "goal" );
// leave
targetPos = targetPos + AnglesToForward( self.angles ) * 15000;
// make sure it doesn't fly away backwards
endEnt = Spawn( "script_origin", targetPos );
if( IsDefined( endEnt ) )
{
self SetLookAtEnt( endEnt );
endEnt thread wait_and_delete( 3.0 );
}
self setVehGoalPos( targetPos );
self waittill ( "goal" );
// remove
self notify( "gone" );
self maps\mp\killstreaks\_helicopter::removeLittlebird();
}
wait_and_delete( waitTime )
{
self endon( "death" );
level endon( "game_ended" );
wait( waitTime );
self delete();
}
//
// Damage, death, and destruction
//
lbSupport_handleDamage() // self == lb
{
self endon( "death" );
level endon( "game_ended" );
self SetCanDamage( true );
while( true )
{
self waittill( "damage", damage, attacker, direction_vec, point, meansOfDeath, modelName, tagName, partName, iDFlags, weapon );
// don't allow people to destroy things on their team if FF is off
if ( !maps\mp\gametypes\_weapons::friendlyFireCheck( self.owner, attacker ) )
continue;
if ( !IsDefined( self ) )
return;
if ( IsDefined( iDFlags ) && ( iDFlags & level.iDFLAGS_PENETRATION ) )
self.wasDamagedFromBulletPenetration = true;
self.wasDamaged = true;
modifiedDamage = damage;
if( IsPlayer( attacker ) )
{
// attack the attacker
if( attacker != self.owner &&
Distance2D( attacker.origin, self.origin ) <= self.targettingRadius &&
!attacker _hasPerk( "specialty_blindeye" ) &&
!( level.hardcoreMode && level.teamBased && attacker.team == self.team ) )
{
self SetLookAtEnt( attacker );
if( IsDefined( self.mgTurretLeft ) )
self.mgTurretLeft SetTargetEntity( attacker );
if( IsDefined( self.mgTurretRight ) )
self.mgTurretRight SetTargetEntity( attacker );
}
attacker maps\mp\gametypes\_damagefeedback::updateDamageFeedback( "helicopter" );
if( meansOfDeath == "MOD_RIFLE_BULLET" || meansOfDeath == "MOD_PISTOL_BULLET" )
{
if ( attacker _hasPerk( "specialty_armorpiercing" ) )
modifiedDamage += damage * level.armorPiercingMod;
}
}
// in case we are shooting from a remote position, like being in the osprey gunner shooting this
if( IsDefined( attacker.owner ) && IsPlayer( attacker.owner ) )
{
attacker.owner maps\mp\gametypes\_damagefeedback::updateDamageFeedback( "helicopter" );
}
if( IsDefined( weapon ) )
{
switch( weapon )
{
case "ac130_105mm_mp":
case "ac130_40mm_mp":
case "stinger_mp":
case "javelin_mp":
case "remote_mortar_missile_mp":
case "remotemissile_projectile_mp":
self.largeProjectileDamage = true;
modifiedDamage = self.maxHealth + 1;
break;
case "sam_projectile_mp":
self.largeProjectileDamage = true;
modifiedDamage = self.maxHealth * 0.25; // takes about 1 burst of sam rockets
break;
case "emp_grenade_mp":
// NOTE: don't destroy helicopters with an emp grenade, it didn't look good in practice
modifiedDamage = 0;
self thread lbSupport_EMPGrenaded();
break;
case "osprey_player_minigun_mp":
self.largeProjectileDamage = false;
modifiedDamage *= 2; // since it's a larger caliber, make it hurt
break;
}
maps\mp\killstreaks\_killstreaks::killstreakHit( attacker, weapon, self );
}
self.damageTaken += modifiedDamage;
if ( self.damageTaken >= self.maxHealth )
{
if ( isPlayer( attacker ) && ( !IsDefined( self.owner ) || attacker != self.owner ) )
{
attacker notify( "destroyed_helicopter" );
attacker notify( "destroyed_killstreak", weapon );
thread teamPlayerCardSplash( "callout_destroyed_little_bird", attacker );
attacker thread maps\mp\gametypes\_rank::giveRankXP( "kill", 300, weapon, meansOfDeath );
attacker thread maps\mp\gametypes\_rank::xpEventPopup( "destroyed_little_bird" );
thread maps\mp\gametypes\_missions::vehicleKilled( self.owner, self, undefined, attacker, damage, meansOfDeath, weapon );
}
if( IsDefined( self.owner ) )
self.owner thread leaderDialogOnPlayer( "lbguard_destroyed" );
self notify ( "death" );
return;
}
}
}
lbSupport_EMPGrenaded() // self == vehicle
{
self notify( "lbSupport_EMPGrenaded" );
self endon( "lbSupport_EMPGrenaded" );
self endon( "death" );
self.owner endon( "disconnect" );
level endon( "game_ended" );
self.empGrenaded = true;
if( IsDefined( self.mgTurretRight ) )
self.mgTurretRight notify( "stop_shooting" );
if( IsDefined( self.mgTurretLeft ) )
self.mgTurretLeft notify( "stop_shooting" );
// TU0: using the ims fx and not creating its own because we've already locked down ConfigStrings and it will shift them if we load fx in this file
if( IsDefined( level._effect[ "ims_sensor_explode" ] ) )
{
if( IsDefined( self.mgTurretRight ) )
PlayFXOnTag( getfx( "ims_sensor_explode" ), self.mgTurretRight, "tag_aim" );
if( IsDefined( self.mgTurretLeft ) )
PlayFXOnTag( getfx( "ims_sensor_explode" ), self.mgTurretLeft, "tag_aim" );
}
wait( EMP_GRENADE_TIME );
self.empGrenaded = false;
if( IsDefined( self.mgTurretRight ) )
self.mgTurretRight notify( "turretstatechange" );
if( IsDefined( self.mgTurretLeft ) )
self.mgTurretLeft notify( "turretstatechange" );
}
// this is different from the flares monitoring that the other helicopters do
// we want this to dodge the missiles if at all possible, making for a cool evasive manuever
lbSupport_watchSAMProximity( player, missileTeam, missileTarget, missileGroup ) // self == level
{
level endon ( "game_ended" );
missileTarget endon( "death" );
for( i = 0; i < missileGroup.size; i++ )
{
if( IsDefined( missileGroup[ i ] ) && !missileTarget.hasDodged )
{
missileTarget.hasDodged = true;
newTarget = spawn( "script_origin", missileTarget.origin );
newTarget.angles = missileTarget.angles;
newTarget MoveGravity( AnglesToRight( missileGroup[ i ].angles ) * -1000, 0.05 );
newTarget thread maps\mp\killstreaks\_flares::flares_deleteAfterTime( 5.0 );
for( j = 0; j < missileGroup.size; j++ )
{
if( IsDefined( missileGroup[ j ] ) )
{
missileGroup[ j ] Missile_SetTargetEnt( newTarget );
}
}
// dodge the incoming missiles
dodgePoint = missileTarget.origin + ( AnglesToRight( missileGroup[ i ].angles ) * 200 );
missileTarget Vehicle_SetSpeed( missileTarget.speed, 100, 40 );
missileTarget SetVehGoalPos( dodgePoint, true );
wait( 2.0 );
missileTarget Vehicle_SetSpeed( missileTarget.followSpeed, 20, 20 );
break;
}
}
}
// this is different from the flares monitoring that the other helicopters do
// we want this to dodge the missiles if at all possible, making for a cool evasive manuever
lbSupport_watchStingerProximity( player, missileTeam, missileTarget ) // self == missile
{
level endon ( "game_ended" );
missileTarget endon( "death" );
if( IsDefined( self ) && !missileTarget.hasDodged )
{
missileTarget.hasDodged = true;
newTarget = spawn( "script_origin", missileTarget.origin );
newTarget.angles = missileTarget.angles;
newTarget MoveGravity( AnglesToRight( self.angles ) * -1000, 0.05 );
newTarget thread maps\mp\killstreaks\_flares::flares_deleteAfterTime( 5.0 );
self Missile_SetTargetEnt( newTarget );
// dodge the incoming missiles
dodgePoint = missileTarget.origin + ( AnglesToRight( self.angles ) * 200 );
missileTarget Vehicle_SetSpeed( missileTarget.speed, 100, 40 );
missileTarget SetVehGoalPos( dodgePoint, true );
wait( 2.0 );
missileTarget Vehicle_SetSpeed( missileTarget.followSpeed, 20, 20 );
}
}
//
// node funcs
//
lbSupport_getClosestStartNode( pos )
{
// gets the start node that is closest to the position passed in
closestNode = undefined;
closestDistance = 999999;
foreach( loc in level.air_start_nodes )
{
nodeDistance = distance( loc.origin, pos );
if ( nodeDistance < closestDistance )
{
closestNode = loc;
closestDistance = nodeDistance;
}
}
return closestNode;
}
lbSupport_getClosestNode( pos )
{
// gets the closest node to the position passed in, regardless of link
closestNode = undefined;
closestDistance = 999999;
foreach( loc in level.air_node_mesh )
{
nodeDistance = distance( loc.origin, pos );
if ( nodeDistance < closestDistance )
{
closestNode = loc;
closestDistance = nodeDistance;
}
}
return closestNode;
}
lbSupport_getClosestLinkedNode( pos ) // self == lb
{
// gets the linked node that is closest to the current position and moving towards the position passed in
closestNode = undefined;
totalDistance = Distance2D( self.currentNode.origin, pos );
closestDistance = totalDistance;
// loop through each neighbor and find the closest to the final goal
foreach( loc in self.currentNode.neighbors )
{
nodeDistance = Distance2D( loc.origin, pos );
if ( nodeDistance < totalDistance && nodeDistance < closestDistance )
{
closestNode = loc;
closestDistance = nodeDistance;
}
}
return closestNode;
}
lbSupport_arrayContains( array, compare )
{
if ( array.size <= 0 )
return false;
foreach ( member in array )
{
if ( member == compare )
return true;
}
return false;
}
lbSupport_getLinkedStructs()
{
array = [];
if ( IsDefined( self.script_linkTo ) )
{
linknames = get_links();
for ( i = 0; i < linknames.size; i++ )
{
ent = getstruct( linknames[ i ], "script_linkname" );
if ( IsDefined( ent ) )
{
array[ array.size ] = ent;
}
}
}
return array;
}
lbSupport_setAirStartNodes()
{
level.air_start_nodes = getstructarray( "chopper_boss_path_start", "targetname" );
foreach( loc in level.air_start_nodes )
{
// Grab array of path loc refs that this loc links to
loc.neighbors = loc lbSupport_getLinkedStructs();
}
}
lbSupport_setAirNodeMesh()
{
level.air_node_mesh = getstructarray( "so_chopper_boss_path_struct", "script_noteworthy" );
foreach( loc in level.air_node_mesh )
{
// Grab array of path loc refs that this loc links to
loc.neighbors = loc lbSupport_getLinkedStructs();
// Step through each loc in the map and if it
// links to this loc, add it
foreach( other_loc in level.air_node_mesh )
{
if ( loc == other_loc )
continue;
if ( !lbSupport_arrayContains( loc.neighbors, other_loc ) && lbSupport_arrayContains( other_loc lbSupport_getLinkedStructs(), loc ) )
loc.neighbors[ loc.neighbors.size ] = other_loc;
}
}
}
/* ============================
Turret Logic Functions
============================ */
lbSupport_attackTargets() // self == turret
{
self.vehicle endon( "death" );
level endon( "game_ended" );
for ( ;; )
{
self waittill( "turretstatechange" );
if ( self IsFiringTurret() && !self.vehicle.empGrenaded )
self thread lbSupport_burstFireStart();
else
self thread lbSupport_burstFireStop();
}
}
lbSupport_burstFireStart() // self == turret
{
self.vehicle endon( "death" );
self.vehicle endon( "leaving" );
self endon( "stop_shooting" );
level endon( "game_ended" );
fireTime = 0.1;
minShots = 40;
maxShots = 80;
minPause = 1.0;
maxPause = 2.0;
for ( ;; )
{
numShots = RandomIntRange( minShots, maxShots + 1 );
for ( i = 0; i < numShots; i++ )
{
targetEnt = self GetTurretTarget( false );
if( IsDefined( targetEnt ) &&
( !IsDefined( targetEnt.spawntime ) || ( gettime() - targetEnt.spawntime )/1000 > 5 ) &&
( IsDefined( targetEnt.team ) && targetEnt.team != "spectator" ) &&
isReallyAlive( targetEnt ) )
{
self.vehicle SetLookAtEnt( targetEnt );
self ShootTurret();
}
wait ( fireTime );
}
wait ( RandomFloatRange( minPause, maxPause ) );
}
}
lbSupport_burstFireStop() // self == turret
{
self notify( "stop_shooting" );
if( IsDefined( self.vehicle.owner ) )
self.vehicle SetLookAtEnt( self.vehicle.owner );
}
/* ============================
END Turret Logic Functions
============================ */

View File

@ -0,0 +1,634 @@
#include maps\mp\_utility;
#include maps\mp\gametypes\_hud_util;
#include common_scripts\utility;
init()
{
level.killstreakFuncs[ "heli_pilot" ] = ::tryUseHeliPilot;
level.heli_pilot = [];
level.heliPilotSettings = [];
level.heliPilotSettings[ "heli_pilot" ] = SpawnStruct();
level.heliPilotSettings[ "heli_pilot" ].timeOut = 60.0;
level.heliPilotSettings[ "heli_pilot" ].maxHealth = 2000; // this is what we check against for death
level.heliPilotSettings[ "heli_pilot" ].streakName = "heli_pilot";
level.heliPilotSettings[ "heli_pilot" ].vehicleInfo = "heli_pilot_mp";
level.heliPilotSettings[ "heli_pilot" ].modelBase = level.littlebird_model;
level.heliPilotSettings[ "heli_pilot" ].teamSplash = "used_heli_pilot";
heliPilot_setAirStartNodes();
// throw the mesh way up into the air, the gdt entry for the vehicle must match
level.heli_pilot_mesh = GetEnt( "heli_pilot_mesh", "targetname" );
if( !IsDefined( level.heli_pilot_mesh ) )
PrintLn( "heli_pilot_mesh doesn't exist in this level: " + level.script );
else
level.heli_pilot_mesh.origin += getHeliPilotMeshOffset();
config = SpawnStruct();
config.xpPopup = "destroyed_helo_pilot";
// !!! NEED VO
config.voDestroyed = undefined;
config.callout = "callout_destroyed_helo_pilot";
config.samDamageScale = 0.09;
config.engineVFXtag = "tag_engine_right";
// xpval = 200
level.heliConfigs[ "heli_pilot" ] = config;
/#
SetDevDvarIfUninitialized( "scr_helipilot_timeout", 60.0 );
#/
}
tryUseHeliPilot( lifeId, streakName )
{
heliPilotType = "heli_pilot";
numIncomingVehicles = 1;
if ( IsDefined( self.underWater ) && self.underWater )
{
return false;
}
else if( exceededMaxHeliPilots( self.team ) )
{
self IPrintLnBold( &"KILLSTREAKS_AIR_SPACE_TOO_CROWDED" );
return false;
}
else if( currentActiveVehicleCount() >= maxVehiclesAllowed() || level.fauxVehicleCount + numIncomingVehicles >= maxVehiclesAllowed() )
{
self IPrintLnBold( &"KILLSTREAKS_TOO_MANY_VEHICLES" );
return false;
}
// increment the faux vehicle count before we spawn the vehicle so no other vehicles try to spawn
incrementFauxVehicleCount();
heli = createHeliPilot( heliPilotType );
if( !IsDefined( heli ) )
{
// decrement the faux vehicle count since this failed to spawn
decrementFauxVehicleCount();
return false;
}
level.heli_pilot[ self.team ] = heli;
result = self startHeliPilot( heli );
if( !IsDefined( result ) )
result = false;
return result;
}
exceededMaxHeliPilots( team )
{
if ( level.gameType == "dm" )
{
if ( IsDefined( level.heli_pilot[ team ] ) || IsDefined( level.heli_pilot[ level.otherTeam[ team ] ] ) )
return true;
else
return false;
}
else
{
if ( IsDefined( level.heli_pilot[ team ] ) )
return true;
else
return false;
}
}
//============================================
// watchHostMigrationFinishedInit
//============================================
watchHostMigrationFinishedInit( player )
{
player endon( "killstreak_disowned" );
player endon( "disconnect" );
level endon( "game_ended" );
self endon ( "death" );
for (;;)
{
level waittill( "host_migration_end" );
player SetClientOmnvar( "ui_heli_pilot", 1 );
}
}
createHeliPilot( heliPilotType )
{
closestStartNode = heliPilot_getClosestStartNode( self.origin );
closestNode = heliPilot_getLinkedStruct( closestStartNode );
startAng = VectorToAngles( closestNode.origin - closestStartNode.origin );
forward = AnglesToForward( self.angles );
targetPos = closestNode.origin + ( forward * -100 );
startPos = closestStartNode.origin;
heli = SpawnHelicopter( self, startPos, startAng, level.heliPilotSettings[ heliPilotType ].vehicleInfo, level.heliPilotSettings[ heliPilotType ].modelBase );
if( !IsDefined( heli ) )
return;
// radius and offset should match vehHelicopterBoundsRadius (GDT) and bg_vehicle_sphere_bounds_offset_z.
heli MakeVehicleSolidCapsule( 18, -9, 18 );
heli maps\mp\killstreaks\_helicopter::addToLittleBirdList();
heli thread maps\mp\killstreaks\_helicopter::removeFromLittleBirdListOnDeath();
heli.maxHealth = level.heliPilotSettings[ heliPilotType ].maxHealth;
heli.speed = 40;
heli.owner = self;
heli SetOtherEnt(self);
heli.team = self.team;
heli.heliType = "littlebird";
heli.heliPilotType = "heli_pilot";
heli SetMaxPitchRoll( 45, 45 );
heli Vehicle_SetSpeed( heli.speed, 40, 40 );
heli SetYawSpeed( 120, 60 );
heli SetNearGoalNotifyDist( 32 );
heli SetHoverParams( 100, 100, 100 );
heli make_entity_sentient_mp( heli.team );
heli.targetPos = targetPos;
heli.currentNode = closestNode;
heli.attract_strength = 10000;
heli.attract_range = 150;
heli.attractor = Missile_CreateAttractorEnt( heli, heli.attract_strength, heli.attract_range );
// heli thread heliPilot_handleDamage(); // since the model is what players will be shooting at, it should handle the damage
heli thread maps\mp\killstreaks\_helicopter::heli_damage_monitor( "heli_pilot" );
heli thread heliPilot_lightFX();
heli thread heliPilot_watchTimeout();
heli thread heliPilot_watchOwnerLoss();
heli thread heliPilot_watchRoundEnd();
heli thread heliPilot_watchObjectiveCam();
heli thread heliPilot_watchDeath();
heli thread watchHostMigrationFinishedInit( self );
heli.owner maps\mp\_matchdata::logKillstreakEvent( level.heliPilotSettings[ heli.heliPilotType ].streakName, heli.targetPos );
return heli;
}
heliPilot_lightFX()
{
PlayFXOnTag( level.chopper_fx["light"]["left"], self, "tag_light_nose" );
wait ( 0.05 );
PlayFXOnTag( level.chopper_fx["light"]["belly"], self, "tag_light_belly" );
wait ( 0.05 );
PlayFXOnTag( level.chopper_fx["light"]["tail"], self, "tag_light_tail1" );
wait ( 0.05 );
PlayFXOnTag( level.chopper_fx["light"]["tail"], self, "tag_light_tail2" );
}
startHeliPilot( heli ) // self == player
{
level endon( "game_ended" );
heli endon( "death" );
self setUsingRemote( heli.heliPilotType );
if( GetDvarInt( "camera_thirdPerson" ) )
self setThirdPersonDOF( false );
self.restoreAngles = self.angles;
heli thread maps\mp\killstreaks\_flares::ks_setup_manual_flares( 2, "+smoke", "ui_heli_pilot_flare_ammo", "ui_heli_pilot_warn" );
self thread watchIntroCleared( heli );
self freezeControlsWrapper( true );
result = self maps\mp\killstreaks\_killstreaks::initRideKillstreak( heli.heliPilotType );
if( result != "success" )
{
if( IsDefined( self.disabledWeapon ) && self.disabledWeapon )
self _enableWeapon();
heli notify( "death" );
return false;
}
self freezeControlsWrapper( false );
/*
// need to link the player here but not give control yet
self CameraLinkTo( heli, "tag_player" );
// go to pos
heli SetVehGoalPos( heli.targetPos );
heli waittill( "near_goal" );
heli Vehicle_SetSpeed( heli.speed, 60, 30 );
heli waittill( "goal" );
*/
// make sure we go to the mesh as we enter the map
traceOffset = getHeliPilotTraceOffset();
traceStart = ( heli.currentNode.origin ) + ( getHeliPilotMeshOffset() + traceOffset );
traceEnd = ( heli.currentNode.origin ) + ( getHeliPilotMeshOffset() - traceOffset );
traceResult = BulletTrace( traceStart, traceEnd, false, undefined, false, false, true );
if( !IsDefined( traceResult["entity"] ) )
{
/#
// draw where it thinks this is breaking down
self thread drawSphere( traceResult[ "position" ] - getHeliPilotMeshOffset(), 32, 10000, ( 1, 0, 0 ) );
self thread drawSphere( heli.currentNode.origin, 16, 10000, ( 0, 1, 0 ) );
self thread drawLine( traceStart - getHeliPilotMeshOffset(), traceEnd - getHeliPilotMeshOffset(), 10000, ( 0, 0, 1 ) );
#/
AssertMsg( "The trace didn't hit the heli_pilot_mesh. Please grab an MP scripter." );
}
targetOrigin = ( traceResult[ "position" ] - getHeliPilotMeshOffset() ) + ( 0, 0, 250 ); // offset to make sure we're on top of the mesh
targetNode = Spawn( "script_origin", targetOrigin );
// link the heli into the mesh and give them control
self RemoteControlVehicle( heli );
heli thread heliGoToStartPosition( targetNode );
heli thread heliPilot_watchADS();
level thread teamPlayerCardSplash( level.heliPilotSettings[ heli.heliPilotType ].teamSplash, self );
heli.killCamEnt = Spawn( "script_origin", self GetViewOrigin() );
return true;
}
heliGoToStartPosition( targetNode ) // self == heli
{
self endon( "death" );
level endon( "game_ended" );
self RemoteControlVehicleTarget( targetNode );
self waittill( "goal_reached" );
self RemoteControlVehicleTargetOff();
targetNode delete();
}
watchIntroCleared( heli ) // self == player
{
self endon( "disconnect" );
self endon( "joined_team" );
self endon( "joined_spectators" );
level endon( "game_ended" );
heli endon( "death" );
self waittill( "intro_cleared" );
self SetClientOmnvar( "ui_heli_pilot", 1 );
// highlight the owner
id = outlineEnableForPlayer( self, "cyan", self, false, "killstreak" );
self removeOutline( id, heli );
// highlight enemies
foreach( player in level.participants )
{
if( !isReallyAlive( player ) || player.sessionstate != "playing" )
continue;
if( self isEnemy( player ) )
{
if( !player _hasPerk( "specialty_noplayertarget" ) )
{
id = outlineEnableForPlayer( player, "orange", self, false, "killstreak" );
player removeOutline( id, heli );
}
else
{
player thread watchForPerkRemoval( heli );
}
}
}
// watch for enemies spawning while the pilot is up
heli thread watchPlayersSpawning();
// do this here to make sure we are in the killstreak before letting them leave, it was causing a bug where the player could get stuck on a black screen
self thread watchEarlyExit( heli );
}
watchForPerkRemoval( heli ) // self == enemy player
{
self notify( "watchForPerkRemoval" );
self endon( "watchForPerkRemoval" );
self endon( "death" );
// since we give blindeye and noplayetarget each time a player spawns, we need to wait for it to be removed to turn the outline on
self waittill( "removed_specialty_noplayertarget" );
id = outlineEnableForPlayer( self, "orange", heli.owner, false, "killstreak" );
self removeOutline( id, heli );
}
watchPlayersSpawning() // self == heli
{
self endon( "leaving" );
self endon( "death" );
while( true )
{
level waittill( "player_spawned", player );
if( player.sessionstate == "playing" && self.owner isEnemy( player ) )
player thread watchForPerkRemoval( self );
}
}
removeOutline( id, heli ) // self == player
{
self thread heliRemoveOutline( id, heli );
self thread playerRemoveOutline( id, heli );
}
heliRemoveOutline( id, heli ) // self == player
{
self notify( "heliRemoveOutline" );
self endon( "heliRemoveOutline" );
self endon( "outline_removed" );
self endon( "disconnect" );
level endon( "game_ended" );
wait_array = [ "leaving", "death" ];
heli waittill_any_in_array_return_no_endon_death( wait_array );
if( IsDefined( self ) )
{
outlineDisable( id, self );
self notify( "outline_removed" );
}
}
playerRemoveOutline( id, heli ) // self == player
{
self notify( "playerRemoveOutline" );
self endon( "playerRemoveOutline" );
self endon( "outline_removed" );
self endon( "disconnect" );
level endon( "game_ended" );
wait_array = [ "death" ];
self waittill_any_in_array_return_no_endon_death( wait_array );
outlineDisable( id, self );
self notify( "outline_removed" );
}
//
// state trackers
//
heliPilot_watchDeath()
{
level endon( "game_ended" );
self endon( "gone" );
self waittill( "death" );
if( IsDefined( self.owner ) )
self.owner heliPilot_EndRide( self );
if( IsDefined( self.killCamEnt ) )
self.killCamEnt delete();
self thread maps\mp\killstreaks\_helicopter::lbOnKilled();
}
heliPilot_watchObjectiveCam()
{
level endon( "game_ended" );
self endon( "gone" );
self.owner endon( "disconnect" );
self.owner endon( "joined_team" );
self.owner endon( "joined_spectators" );
level waittill( "objective_cam" );
self thread maps\mp\killstreaks\_helicopter::lbOnKilled();
if( IsDefined( self.owner ) )
self.owner heliPilot_EndRide( self );
}
heliPilot_watchTimeout()
{
level endon ( "game_ended" );
self endon( "death" );
self.owner endon( "disconnect" );
self.owner endon( "joined_team" );
self.owner endon( "joined_spectators" );
timeout = level.heliPilotSettings[ self.heliPilotType ].timeOut;
/#
timeout = GetDvarFloat( "scr_helipilot_timeout" );
#/
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( timeout );
self thread heliPilot_leave();
}
heliPilot_watchOwnerLoss()
{
level endon ( "game_ended" );
self endon( "death" );
self endon( "leaving" );
self.owner waittill_any( "disconnect", "joined_team", "joined_spectators" );
// leave
self thread heliPilot_leave();
}
heliPilot_watchRoundEnd()
{
self endon( "death" );
self endon( "leaving" );
self.owner endon( "disconnect" );
self.owner endon( "joined_team" );
self.owner endon( "joined_spectators" );
level waittill_any( "round_end_finished", "game_ended" );
// leave
self thread heliPilot_leave();
}
heliPilot_leave()
{
self endon( "death" );
self notify( "leaving" );
if( IsDefined( self.owner ) )
self.owner heliPilot_EndRide( self );
// rise
flyHeight = self maps\mp\killstreaks\_airdrop::getFlyHeightOffset( self.origin );
targetPos = self.origin + ( 0, 0, flyHeight );
self Vehicle_SetSpeed( 140, 60 );
self SetMaxPitchRoll( 45, 180 );
self SetVehGoalPos( targetPos );
self waittill( "goal" );
// leave
targetPos = targetPos + AnglesToForward( self.angles ) * 15000;
// make sure it doesn't fly away backwards
endEnt = Spawn( "script_origin", targetPos );
if( IsDefined( endEnt ) )
{
self SetLookAtEnt( endEnt );
endEnt thread wait_and_delete( 3.0 );
}
self SetVehGoalPos( targetPos );
self waittill( "goal" );
// remove
self notify( "gone" );
self maps\mp\killstreaks\_helicopter::removeLittlebird();
}
wait_and_delete( waitTime )
{
self endon( "death" );
level endon( "game_ended" );
wait( waitTime );
self delete();
}
heliPilot_EndRide( heli )
{
if( IsDefined( heli ) )
{
self SetClientOmnvar( "ui_heli_pilot", 0 );
heli notify( "end_remote" );
if( self isUsingRemote() )
self clearUsingRemote();
if( GetDvarInt( "camera_thirdPerson" ) )
self setThirdPersonDOF( true );
self RemoteControlVehicleOff( heli );
self SetPlayerAngles( self.restoreAngles );
self thread heliPilot_FreezeBuffer();
}
}
heliPilot_FreezeBuffer()
{
self endon( "disconnect" );
self endon( "death" );
level endon( "game_ended" );
self freezeControlsWrapper( true );
wait( 0.5 );
self freezeControlsWrapper( false );
}
heliPilot_watchADS() // self == heli
{
self endon( "leaving" );
self endon( "death" );
level endon( "game_ended" );
already_set = false;
while( true )
{
if( IsDefined( self.owner ) )
{
if( self.owner AdsButtonPressed() )
{
if( !already_set )
{
self.owner SetClientOmnvar( "ui_heli_pilot", 2 );
already_set = true;
}
}
else
{
if( already_set )
{
self.owner SetClientOmnvar( "ui_heli_pilot", 1 );
already_set = false;
}
}
}
wait( 0.1 );
}
}
//
// node funcs
//
heliPilot_setAirStartNodes()
{
level.air_start_nodes = getstructarray( "chopper_boss_path_start", "targetname" );
}
heliPilot_getLinkedStruct( struct )
{
if( IsDefined( struct.script_linkTo ) )
{
linknames = struct get_links();
for( i = 0; i < linknames.size; i++ )
{
ent = getstruct( linknames[ i ], "script_linkname" );
if( IsDefined( ent ) )
{
return ent;
}
}
}
return undefined;
}
heliPilot_getClosestStartNode( pos )
{
// gets the start node that is closest to the position passed in
closestNode = undefined;
closestDistance = 999999;
foreach( loc in level.air_start_nodes )
{
nodeDistance = Distance( loc.origin, pos );
if ( nodeDistance < closestDistance )
{
closestNode = loc;
closestDistance = nodeDistance;
}
}
return closestNode;
}
watchEarlyExit( heli ) // self == player
{
level endon( "game_ended" );
heli endon( "death" );
self endon ("leaving");
heli thread maps\mp\killstreaks\_killstreaks::allowRideKillstreakPlayerExit();
heli waittill("killstreakExit");
heli thread heliPilot_leave();
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,155 @@
#include maps\mp\_utility;
#include maps\mp\gametypes\_hud_util;
#include common_scripts\utility;
#include maps\mp\agents\_agent_utility;
#include maps\mp\gametypes\_hostmigration;
//============================================
// constants
//============================================
CONST_XP_INCREASE = 0.5;
CONST_MIN_MULTIPLIER = 1;
CONST_MAX_MULTIPLIER = 4; // shouldn't hit this, but just to be safe.
CONST_MAX_ACTIVE_HVT_PER_GAME = 4;
CONST_MAX_ACTIVE_HVT_PER_PLAYER = 2;
CONST_HVT_TIMEOUT = 10;
init()
{
level.killstreakFuncs["high_value_target"] = ::tryUseHighValueTarget;
level.hvts_active[ "axis" ] = 0;
level.hvts_active[ "allies" ] = 0;
game["dialog"]["hvt_gone"] = "hvt_gone";
}
tryUseHighValueTarget( lifeId, streakName )
{
return useHighValueTarget( self, lifeId );
}
reached_max_xp_multiplier() // self == player
{
if( level.teamBased )
return ( level.hvts_active[ self.team ] >= CONST_MAX_ACTIVE_HVT_PER_GAME );
else if( IsDefined( self.hvts_active ) )
return ( self.hvts_active >= CONST_MAX_ACTIVE_HVT_PER_PLAYER );
return false;
}
useHighValueTarget( player, lifeId )
{
if( !isReallyAlive( player ) )
{
return false;
}
if( player.team == "spectator" )
{
return false;
}
// limit the number of active hvts allowed per game
if( reached_max_xp_multiplier()
// limit the number of active hvts allowed per player
|| ( IsDefined( player.hvts_active ) && player.hvts_active >= CONST_MAX_ACTIVE_HVT_PER_PLAYER )
)
{
self iPrintLnBold( &"KILLSTREAKS_HVT_MAX" );
return false;
}
player thread setHighValueTarget();
level thread teamPlayerCardSplash( "used_hvt", player, player.team );
return true;
}
setHighValueTarget() // self == player
{
level endon( "game_ended" );
self endon( "disconnect" );
team = self.team;
self increaseXPBoost();
self thread watchHVTOwner( team );
// wait for timeout
waitLongDurationWithHostMigrationPause( CONST_HVT_TIMEOUT );
if( level.teamBased )
leaderDialog( "hvt_gone", team );
else
self leaderDialogOnPlayer( "hvt_gone" );
if( level.teamBased )
level decreaseXPBoost( team );
else
self decreaseXPBoost();
}
increaseXPBoost() // self == player
{
hvts_active = 0;
if( level.teamBased )
{
level.hvts_active[ self.team ]++;
hvts_active = level.hvts_active[ self.team ];
array_index = self.team;
}
else
{
if( !IsDefined( self.hvts_active ) )
self.hvts_active = 1;
else
self.hvts_active++;
hvts_active = self.hvts_active;
array_index = self GetEntityNumber();
}
bonus = CONST_MIN_MULTIPLIER + hvts_active * CONST_XP_INCREASE;
level.teamXPScale[ array_index ] = clamp( bonus, CONST_MIN_MULTIPLIER, CONST_MAX_MULTIPLIER );
}
decreaseXPBoost( team ) // self == level or player
{
hvts_active = 0;
if( level.teamBased )
{
if( level.hvts_active[ team ] > 0 )
level.hvts_active[ team ]--;
hvts_active = level.hvts_active[ team ];
array_index = team;
}
else
{
if( self.hvts_active > 0 )
self.hvts_active--;
hvts_active = self.hvts_active;
array_index = self GetEntityNumber();
}
bonus = CONST_MIN_MULTIPLIER + hvts_active * CONST_XP_INCREASE;
level.teamXPScale[ array_index ] = clamp( bonus, CONST_MIN_MULTIPLIER, CONST_MAX_MULTIPLIER );
}
watchHVTOwner( team ) // self == player
{
level endon( "game_ended" );
result = self waittill_any_return( "disconnect", "joined_team", "joined_spectators" );
if( level.teamBased )
level decreaseXPBoost( team );
else if( IsDefined( self ) && result != "disconnect" )
self decreaseXPBoost();
}

1342
maps/mp/killstreaks/_ims.gsc Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,252 @@
#include maps\mp\_utility;
#include common_scripts\utility;
#include maps\mp\killstreaks\_emp_common;
// replacement for EMP
// only affects ground items
// 2013-06-29 wallace: I am no longer supporting multi-team code. It's just ugly.
init()
{
level.teamEMPed["allies"] = false;
level.teamEMPed["axis"] = false;
level.empPlayer = undefined;
level.empTimeout = 10.0;
level.empTimeRemaining = int( level.empTimeout );
level.killstreakFuncs["jammer"] = ::EMP_Use;
level thread onPlayerConnect();
/#
SetDevDvarIfUninitialized( "scr_emp_timeout", 15.0 );
SetDevDvarIfUninitialized( "scr_emp_damage_debug", 0 );
#/
}
onPlayerConnect()
{
for(;;)
{
level waittill("connected", player);
player thread onPlayerKilled();
player thread onPlayerSpawned();
}
}
onPlayerSpawned()
{
self endon("disconnect");
while ( true )
{
self waittill( "spawned_player" );
if ( self shouldPlayerBeAffectedByEMP() )
{
self applyPerPlayerEMPEffects();
}
}
}
onPlayerKilled()
{
self endon("disconnect");
while ( true )
{
self waittill( "death" );
self stopEmpJamSequenceImmediate();
}
}
EMP_Use( lifeId, streakName )
{
assert( isDefined( self ) );
myTeam = self.pers["team"];
if ( level.teamBased )
{
otherTeam = level.otherTeam[myTeam];
self thread EMP_JamTeam( otherTeam );
}
else
{
self thread EMP_JamPlayers( self );
}
self maps\mp\_matchdata::logKillstreakEvent( "jammer", self.origin );
self notify( "used_emp" );
level notify( "emp_used" );
return true;
}
INITIAL_DELAY = 0.5;
//jams all players on the team passed in the argument teamName
EMP_JamTeam( teamName )
{
level endon ( "game_ended" );
// time this with the button press of the remote
wait (INITIAL_DELAY);
assert( teamName == "allies" || teamName == "axis" );
// TURN ON EMP EFFECTS
thread teamPlayerCardSplash( "used_jammer", self );
level notify ( "EMP_JamTeam" + teamName );
level endon ( "EMP_JamTeam" + teamName );
level.teamEMPed[teamName] = true;
foreach ( player in level.players )
{
player applyPerPlayerEMPEffects_OnDetonate();
if ( player shouldPlayerBeAffectedByEMP() )
{
player applyPerPlayerEMPEffects();
}
}
level thread applyGlobalEMPEffects();
level notify ( "emp_update" );
level destroyGroundObjects( self, teamName );
// WAIT
/#
level.empTimeout = GetDvarFloat( "scr_emp_timeout" );
#/
level thread keepEMPTimeRemaining();
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( level.empTimeout );
// TURN OF EMP EFFECTS
level.teamEMPed[teamName] = false;
foreach ( player in level.players )
{
if ( player.team == teamName
&& !(player shouldPlayerBeAffectedByEMP() )
)
{
player removePerPlayerEMPEffects();
}
}
level notify ( "emp_update" );
}
EMP_JamPlayers( owner )
{
level notify ( "EMP_JamPlayers" );
level endon ( "EMP_JamPlayers" );
// time this with the button press of the remote
wait (INITIAL_DELAY);
if ( !IsDefined( owner ) )
{
return;
}
level.empPlayer = owner;
foreach ( player in level.players )
{
player applyPerPlayerEMPEffects_OnDetonate();
if ( player shouldPlayerBeAffectedByEMP() )
{
player applyPerPlayerEMPEffects();
}
}
level thread applyGlobalEMPEffects();
level notify ( "emp_update" );
level.empPlayer thread empPlayerFFADisconnect();
level destroyGroundObjects( owner );
// 2013-06-28 wallace: not sure why this sends an update
// the team versions only send it on Start and Stop
// level notify ( "emp_update" );
// WAIT
/#
level.empTimeout = GetDvarFloat( "scr_emp_timeout" );
#/
level thread keepEMPTimeRemaining();
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( level.empTimeout );
// STOP
level.empPlayer = undefined;
foreach ( player in level.players )
{
if ( (!IsDefined( owner ) || player != owner) // it is possible for the owner to disconnect during the jam sequence
&& !(player shouldPlayerBeAffectedByEMP() )
)
{
player removePerPlayerEMPEffects();
}
}
level notify ( "emp_update" );
level notify ( "emp_ended" );
}
keepEMPTimeRemaining()
{
level notify( "keepEMPTimeRemaining" );
level endon( "keepEMPTimeRemaining" );
level endon( "emp_ended" );
// we need to know how much time is left for the unavailable string
level.empTimeRemaining = int( level.empTimeout );
while( level.empTimeRemaining )
{
wait( 1.0 );
level.empTimeRemaining--;
}
}
empPlayerFFADisconnect()
{
level endon ( "EMP_JamPlayers" );
level endon ( "emp_ended" );
self waittill( "disconnect" );
level notify ( "emp_update" );
}
destroyGroundObjects( attacker, teamEMPed )
{
// thread all of the things that need to get destroyed, this way we can put frame waits in between each destruction so we don't hit the server with a lot at one time
maps\mp\killstreaks\_killstreaks::destroyTargetArray( attacker, teamEMPed, "killstreak_emp_mp", level.turrets );
// thread destroyActiveRockets( attacker, teamEMPed );
// IMS
maps\mp\killstreaks\_killstreaks::destroyTargetArray( attacker, teamEMPed, "killstreak_emp_mp", level.placedIMS );
// ball drones
maps\mp\killstreaks\_killstreaks::destroyTargetArray( attacker, teamEMPed, "killstreak_emp_mp", level.ballDrones );
// vanguard
// thread destroyTargets( attacker, teamEMPed, level.remote_uav );
// satcoms
thread maps\mp\killstreaks\_killstreaks::destroyTargetArray( attacker, teamEMPed, "killstreak_emp_mp", level.uplinks );
// this affects all placed equipment
maps\mp\killstreaks\_killstreaks::destroyTargetArray( attacker, teamEMPed, "killstreak_emp_mp", level.mines );
}

View File

@ -0,0 +1,334 @@
#include maps\mp\_utility;
#include common_scripts\utility;
#include maps\mp\gametypes\_hud_util;
DECAP_SCALAR_X = 6000;
DECAP_SCALAR_Y = 10000;
DECAP_SCALAR_Z = 4000;
init()
{
level.juggSettings = [];
level.juggSettings[ "juggernaut" ] = SpawnStruct();
level.juggSettings[ "juggernaut" ].splashUsedName = "used_juggernaut";
level.juggSettings[ "juggernaut_recon" ] = SpawnStruct();
level.juggSettings[ "juggernaut_recon" ].splashUsedName = "used_juggernaut_recon";
level.juggSettings[ "juggernaut_maniac" ] = SpawnStruct();
level.juggSettings[ "juggernaut_maniac" ].splashUsedName = "used_juggernaut_maniac";
level thread watchJuggHostMigrationFinishedInit();
}
giveJuggernaut( juggType ) // self == player
{
self endon( "death" );
self endon( "disconnect" );
// added this wait here because i think the disabling the weapons and re-enabling while getting the crate,
// needs a little time or else we sometimes won't have a weapon in front of us after we get juggernaut
wait(0.05);
// remove light armor if equipped
if ( IsDefined( self.lightArmorHP ) )
self maps\mp\perks\_perkfunctions::unsetLightArmor();
self maps\mp\gametypes\_weapons::disablePlantedEquipmentUse();
// remove explosive bullets if equipped
if ( self _hasPerk( "specialty_explosivebullets" ) )
self _unsetPerk( "specialty_explosivebullets" );
// give 100% health to fix some issues
// first was if you are being damaged and pick up juggernaut then you could die very quickly as juggernaut, seems weird in practice
self.health = self.maxHealth;
defaultSetup = true;
switch( juggType )
{
case "juggernaut":
self.isJuggernaut = true;
self.juggMoveSpeedScaler = .80; // for unset perk juiced
self maps\mp\gametypes\_class::giveLoadout( self.pers["team"], juggType, false );
self.moveSpeedScaler = .80;
self givePerk( "specialty_scavenger", false );
self givePerk( "specialty_quickdraw", false );
self givePerk( "specialty_detectexplosive", false );
self givePerk( "specialty_sharp_focus", false ); // juggernauts are in heavy armor and shouldn't be flinching like crazy
self givePerk( "specialty_radarjuggernaut", false ); // this shows the jugg on the minimap
break;
case "juggernaut_recon":
self.isJuggernautRecon = true;
self.juggMoveSpeedScaler = .80; // for unset perk juiced
self maps\mp\gametypes\_class::giveLoadout( self.pers["team"], juggType, false );
self.moveSpeedScaler = .80;
self givePerk( "specialty_scavenger", false );
self givePerk( "specialty_coldblooded", false );
self givePerk( "specialty_noscopeoutline", false);
self givePerk( "specialty_detectexplosive", false );
self givePerk( "specialty_sharp_focus", false ); // juggernauts are in heavy armor and shouldn't be flinching like crazy
self givePerk( "specialty_radarjuggernaut", false ); // this shows the jugg on the minimap
if( !IsAgent(self) )
{
self makePortableRadar( self );
self maps\mp\gametypes\_missions::processChallenge( "ch_airdrop_juggernaut_recon" );
}
break;
case "juggernaut_maniac":
self.isJuggernautManiac = true;
self.juggMoveSpeedScaler = 1.15; // for unset perk juiced
self maps\mp\gametypes\_class::giveLoadout( self.pers["team"], juggType, false );
//self maps\mp\killstreaks\_killstreaks::giveAllPerks();
//self _unsetPerk( "specialty_lightweight" );
//self _unsetPerk( "specialty_combat_speed" );
//self givePerk( "specialty_delaymine", false );
//self givePerk( "specialty_regenfaster", false );
//self givePerk( "specialty_extendedmelee", false );
self givePerk( "specialty_blindeye", false );
self givePerk( "specialty_coldblooded", false );
self givePerk( "specialty_noscopeoutline", false);
self givePerk( "specialty_detectexplosive", false );
self givePerk( "specialty_marathon", false );
self givePerk( "specialty_falldamage", false );
//self givePerk( "specialty_explosivedamage", false );
//self givePerk( "specialty_fastermelee", false );
//self givePerk( "specialty_radarjuggernaut", false );
self.moveSpeedScaler = 1.15; // this needs to happen last because some perks change speed
break;
default:
// we rely on self.isJuggernautLevelCustom == true
// this would be better if the juggType had some kind of standard prefix, like "juggernaut_custom_swamp_slasher" or "juggernaut_custom_predator"
AssertEx( IsDefined( level.mapCustomJuggFunc ), "Juggernaut type " + juggType + " needs to have a level.mapCustomJuggFunc defined!" );
defaultSetup = self [[ level.mapCustomJuggFunc ]]( juggType );
break;
}
// make sure to give players hardline if they previously had it equipped
// we are doing this instead of _hasPerk, because at this point all of the loadout perks would have been cleared by giveLoadout
if ( self perkCheck( "specialty_hardline" ) )
self givePerk( "specialty_hardline", false );
self maps\mp\gametypes\_weapons::updateMoveSpeedScale();
self disableWeaponPickup();
if( !IsAgent(self) )
{
// TODO: no overlay yet but if we want one we can remove these if checks
if( defaultSetup )
{
self SetClientOmnvar( "ui_juggernaut", 1 );
self thread teamPlayerCardSplash( level.juggSettings[ juggType ].splashUsedName, self );
self thread juggernautSounds();
self thread watchDisableJuggernaut();
self thread watchEnableJuggernaut();
}
}
// if we are using the specialist strike package then we need to clear it, or else players will think they have the perks while jugg
if( self.streakType == "specialist" )
{
self thread maps\mp\killstreaks\_killstreaks::clearKillstreaks();
}
// - giveLoadout() nukes action slot 4 (killstreak weapon)
// - it's usually restored after activating a killstreak but
// equipping juggernaut out of a box isn't part of killstreak activation flow
// - restore action slot 4 by re-updating killstreaks
else
{
self thread maps\mp\killstreaks\_killstreaks::updateKillstreaks( true );
}
self thread juggRemover();
// - model change happens at the end of giveLoadout(), removing any attached models
// - re-apply flag if we were carrying one
if ( IsDefined( self.carryFlag ) )
{
wait( 0.05 );
self attach( self.carryFlag, "J_spine4", true );
}
level notify( "juggernaut_equipped", self );
self maps\mp\_matchdata::logKillstreakEvent( juggType, self.origin );
}
perkCheck( perkToCheck )
{
// self == player
loadoutPerks = self.pers[ "loadoutPerks" ];
foreach ( perk in loadoutPerks )
{
if ( perk == perkToCheck )
return true;
}
return false;
}
juggernautSounds()
{
level endon ( "game_ended" );
self endon( "death" );
self endon( "disconnect" );
self endon( "jugg_removed" );
while( true )
{
wait ( 3.0 );
playPlayerAndNpcSounds( self, "juggernaut_breathing_player", "juggernaut_breathing_sound" );
}
}
//============================================
// watchHostMigrationFinishedInit
//============================================
watchJuggHostMigrationFinishedInit()
{
level endon( "game_ended" );
for (;;)
{
level waittill( "host_migration_end" );
foreach( player in level.players )
{
if ( isAI( player ) )
continue;
else if ( player isJuggernaut() && !( IsDefined( player.isJuggernautLevelCustom ) && player.isJuggernautLevelCustom ) )
player SetClientOmnvar( "ui_juggernaut", 1 );
else
player SetClientOmnvar( "ui_juggernaut", 0 );
}
}
}
juggRemover()
{
level endon("game_ended");
self endon( "disconnect" );
self endon( "jugg_removed" );
self thread juggRemoveOnGameEnded();
self waittill_any( "death", "joined_team", "joined_spectators", "lost_juggernaut" );
self enableWeaponPickup();
self.isJuggernaut = false;
self.isJuggernautDef = false;
self.isJuggernautGL = false;
self.isJuggernautRecon = false;
self.isJuggernautManiac = false;
self.isJuggernautLevelCustom = false;
if ( IsPlayer(self) )
self SetClientOmnvar( "ui_juggernaut", 0 );
self unsetPerk( "specialty_radarjuggernaut", true );
self notify( "jugg_removed" );
}
juggRemoveOnGameEnded()
{
self endon( "disconnect" );
self endon( "jugg_removed" );
level waittill( "game_ended" );
if( IsPlayer(self) )
self SetClientOmnvar( "ui_juggernaut", 0 );
}
setJugg()
{
if( IsDefined( self.headModel ) )
{
self Detach( self.headModel, "" );
self.headModel = undefined;
}
self SetModel( "mp_fullbody_juggernaut_heavy_black" );
self SetViewModel( "viewhands_juggernaut_ally" );
self SetClothType( "vestheavy" );
}
setJuggManiac()
{
if( IsDefined( self.headModel ) )
{
self Detach( self.headModel, "" );
self.headModel = undefined;
}
self SetModel( "mp_body_juggernaut_light_black" );
self SetViewModel( "viewhands_juggernaut_ally" );
self Attach( "head_juggernaut_light_black", "", true );
self.headModel = "head_juggernaut_light_black";
self SetClothType( "nylon" );
}
disableJuggernaut() // self == player
{
if( self isJuggernaut() )
{
self.juggernaut_disabled = true;
self SetClientOmnvar( "ui_juggernaut", 0 );
}
}
enableJuggernaut() // self == player
{
if( self isJuggernaut() )
{
self.juggernaut_disabled = undefined;
self SetClientOmnvar( "ui_juggernaut", 1 );
}
}
watchDisableJuggernaut() // self == player
{
self endon( "death" );
self endon( "disconnect" );
self endon( "jugg_removed" );
level endon( "game_ended" );
while( true )
{
if( !IsDefined( self.juggernaut_disabled ) && self isUsingRemote() )
{
self waittill( "black_out_done" );
disableJuggernaut();
}
wait( 0.05 );
}
}
watchEnableJuggernaut() // self == player
{
self endon( "death" );
self endon( "disconnect" );
self endon( "jugg_removed" );
level endon( "game_ended" );
while( true )
{
if( IsDefined( self.juggernaut_disabled ) && !self isUsingRemote() )
enableJuggernaut();
wait( 0.05 );
}
}
initLevelCustomJuggernaut( createFunc, loadoutFunc, modelFunc, useSplashStr )
{
level.mapCustomJuggFunc = createFunc;
level.mapCustomJuggSetClass = loadoutFunc;
level.mapCustomJuggKilledSplash = useSplashStr;
game[ "allies_model" ][ "JUGGERNAUT_CUSTOM" ] = modelFunc;
game[ "axis_model" ][ "JUGGERNAUT_CUSTOM" ] = modelFunc;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,100 @@
#include maps\mp\killstreaks\_killstreaks;
#include maps\mp\_utility;
#include maps\mp\gametypes\_hud_util;
#include common_scripts\utility;
init()
{
initKillstreakData();
level.killstreakFuncs = [];
level.killstreakSetupFuncs = [];
level.killstreakWeapons = [];
thread maps\mp\killstreaks\_uav::init();
// thread maps\mp\killstreaks\_airstrike::init(); // not using airstrikes for iw6
thread maps\mp\killstreaks\_plane::init(); // eventually, move this above airstrike and simplify the existing killstreaks
thread maps\mp\killstreaks\_airdrop::init();
thread maps\mp\killstreaks\_helicopter::init();
thread maps\mp\killstreaks\_nuke::init();
thread maps\mp\killstreaks\_a10::init();
thread maps\mp\killstreaks\_portableAOEgenerator::init();
thread maps\mp\killstreaks\_ims::init();
thread maps\mp\killstreaks\_perkstreaks::init();
thread maps\mp\killstreaks\_juggernaut::init();
thread maps\mp\killstreaks\_ball_drone::init();
thread maps\mp\killstreaks\_autosentry::init();
thread maps\mp\killstreaks\_remotemissile::init();
thread maps\mp\killstreaks\_deployablebox::init();
thread maps\mp\killstreaks\_deployablebox_vest::init();
thread maps\mp\killstreaks\_deployablebox_gun::init(); // careful, _gun and _ammo use the same setup
thread maps\mp\killstreaks\_heliSniper::init();
thread maps\mp\killstreaks\_helicopter_pilot::init();
thread maps\mp\killstreaks\_vanguard::init();
thread maps\mp\killstreaks\_uplink::init();
thread maps\mp\killstreaks\_droneHive::init();
thread maps\mp\killstreaks\_jammer::init();
thread maps\mp\killstreaks\_air_superiority::init();
thread maps\mp\killstreaks\_odin::init();
thread maps\mp\killstreaks\_highValueTarget::init();
thread maps\mp\killstreaks\_AALauncher::init();
// all killstreak weapons that kill, this is used for weapon to killstreak association
level.killstreakWeildWeapons = [];
level.killstreakWeildWeapons["sentry_minigun_mp"] = "sentry"; // Sentry Gun
level.killstreakWeildWeapons["hind_bomb_mp"] = "helicopter"; // Attack Helicopter, Missile
level.killstreakWeildWeapons["hind_missile_mp"] = "helicopter"; // Attack Helicopter, Missile
level.killstreakWeildWeapons["cobra_20mm_mp"] = "helicopter"; // Attack Helicopter
level.killstreakWeildWeapons["nuke_mp"] = "nuke"; // Nuke
level.killstreakWeildWeapons["manned_littlebird_sniper_mp"] = "heli_sniper"; // heli sniper
level.killstreakWeildWeapons["iw6_minigunjugg_mp"] = "airdrop_juggernaut"; // juggernaut assault primary
level.killstreakWeildWeapons["iw6_p226jugg_mp"] = "airdrop_juggernaut"; // juggernaut assault secondary
level.killstreakWeildWeapons["mortar_shelljugg_mp"] = "airdrop_juggernaut"; // juggernaut assault equipment
level.killstreakWeildWeapons["iw6_riotshieldjugg_mp"] = "airdrop_juggernaut_recon"; // juggernaut support primary
level.killstreakWeildWeapons["iw6_magnumjugg_mp"] = "airdrop_juggernaut_recon"; // juggernaut support secondary
level.killstreakWeildWeapons["smoke_grenadejugg_mp"] = "airdrop_juggernaut_recon"; // juggernaut support offhand
level.killstreakWeildWeapons["iw6_knifeonlyjugg_mp"] = "airdrop_juggernaut_maniac"; // juggernaut maniac primary
level.killstreakWeildWeapons["throwingknifejugg_mp"] = "airdrop_juggernaut_maniac"; // juggernaut maniac equipment
level.killstreakWeildWeapons["deployable_vest_marker_mp"] = "deployable_vest"; // deployable vest
level.killstreakWeildWeapons["deployable_weapon_crate_marker_mp"] = "deployable_ammo"; // deployable vest
level.killstreakWeildWeapons["heli_pilot_turret_mp"] = "heli_pilot"; // heli pilot
level.killstreakWeildWeapons["guard_dog_mp"] = "guard_dog"; // guard dog
level.killstreakWeildWeapons["ims_projectile_mp"] = "ims"; // ims
level.killstreakWeildWeapons["ball_drone_gun_mp"] = "ball_drone_backup"; // backup buddy
level.killstreakWeildWeapons["drone_hive_projectile_mp"] = "drone_hive"; // drone hive
level.killstreakWeildWeapons["switch_blade_child_mp"] = "drone_hive"; // drone hive
level.killstreakWeildWeapons["iw6_maaws_mp"] = "aa_launcher"; // aa launcher
level.killstreakWeildWeapons["iw6_maawschild_mp"] = "aa_launcher"; // aa launcher
level.killstreakWeildWeapons["iw6_maawshoming_mp"] = "aa_launcher"; // aa launcher
level.killstreakWeildWeapons["killstreak_uplink_mp"] = "uplink"; // uplink
level.killstreakWeildWeapons["odin_projectile_large_rod_mp"] = "odin_assault"; // odin assault
level.killstreakWeildWeapons["odin_projectile_small_rod_mp"] = "odin_assault"; // odin assault
level.killstreakWeildWeapons["iw6_gm6helisnipe_mp"] = "heli_sniper"; // heli sniper (Extra entry for stat tracking)
level.killstreakWeildWeapons["iw6_gm6helisnipe_mp_gm6scope"] = "heli_sniper"; // heli sniper
level.killstreakWeildWeapons["aamissile_projectile_mp"] = "air_superiority"; // air superiority
level.killstreakWeildWeapons["airdrop_marker_mp"] = "airdrop_assault"; // airdrop marker
level.killstreakWeildWeapons["remote_tank_projectile_mp"] = "vanguard"; // vanguard
level.killstreakWeildWeapons["killstreak_vanguard_mp"] = "vanguard"; // vanguard
level.killstreakWeildWeapons["agent_mp"] = "agent"; // squad member, even jugg from odin and loki
level.killstreakWeildWeapons["agent_support_mp"] = "recon_agent"; // support squad member (recon agent)
level.killstreakWeildWeapons[ "iw6_axe_mp" ] = "juggernaut_swamp_slasher"; // level killstreak for mp_swamp
level.killstreakWeildWeapons[ "venomxgun_mp" ] = "venom_x_gun"; // Extinction gun for mp_dome_ns. Not technically a killstreak
level.killstreakWeildWeapons[ "venomxproj_mp" ] = "venom_x_projectile"; // Projectile for Extinction gun
level.killstreakWeildWeapons[ "iw6_predatorcannon_mp" ] = "juggernaut_predator"; // level killstreak for mp_battery3
level.killstreakWeildWeapons[ "iw6_predatorsuicide_mp" ] = "juggernaut_predator"; // level killstreak for mp_battery3
level.killstreakWeildWeapons[ "volcano_rock_mp" ] = "volcano"; // level killstreak for mp_battery3
level.killstreakWeildWeapons["ac130_105mm_mp"] = "ac130"; // AC130 level killstreak for mp_favela_iw6
level.killstreakWeildWeapons["ac130_40mm_mp"] = "ac130"; // AC130 level killstreak for mp_favela_iw6
level.killstreakWeildWeapons["ac130_25mm_mp"] = "ac130"; // AC130 level killstreak for mp_favela_iw6
level.killstreakWeildWeapons[ "iw6_mariachimagnum_mp_akimbo" ] = "juggernaut_death_mariachi"; // level killstreak for mp_zulu
level.killstreakWeildWeapons[ "harrier_20mm_mp" ] = "harrier_airstrike"; // level killstreak for mp_conflict
level.killstreakWeildWeapons[ "artillery_mp" ] = "harrier_airstrike"; // level killstreak for mp_conflict
if(IsDefined(level.mapCustomKillstreakFunc))
[[ level.mapCustomKillstreakFunc ]]();
level.killstreakRoundDelay = getIntProperty( "scr_game_killstreakdelay", 8 );
level thread onPlayerConnect();
}

View File

@ -0,0 +1,890 @@
#include maps\mp\_utility;
#include maps\mp\gametypes\_hud_util;
#include common_scripts\utility;
init()
{
//PrecacheMiniMapIcon( "compass_objpoint_reaper_friendly" );
//PrecacheMiniMapIcon( "compass_objpoint_reaper_enemy" );
level.killStreakFuncs["lasedStrike"] = ::tryUseLasedStrike;
level.numberOfSoflamAmmo = 2;
level.lasedStrikeGlow = loadfx("fx/misc/laser_glow");
level.lasedStrikeExplode = LoadFX( "fx/explosions/uav_advanced_death" );
//level.lasedStrikeActive = false;
//level.lasedStrikeDrone = undefined;
remoteMissileSpawnArray = getEntArray( "remoteMissileSpawn" , "targetname" );
foreach ( startPoint in remoteMissileSpawnArray )
{
if ( isDefined( startPoint.target ) )
startPoint.targetEnt = getEnt( startPoint.target, "targetname" );
}
level.lasedStrikeEnts = remoteMissileSpawnArray;
thread onPlayerConnect();
}
onPlayerConnect()
{
for(;;)
{
level waittill("connected", player);
player thread onPlayerSpawned();
player.soflamAmmoUsed = 0;
player.hasSoflam = false;
}
}
onPlayerSpawned()
{
self endon("disconnect");
for(;;)
{
self waittill( "spawned_player" );
//if( isDefined( level.soflamCrate ) )
// level.soflamCrate maps\mp\killstreaks\_airdrop::setUsableByTeam( self.team );
}
}
tryUseLasedStrike( lifeId, streakName )
{
return useLasedStrike();
}
useLasedStrike()
{
//self maps\mp\killstreaks\_killstreaks::giveKillstreakWeapon( "iw5_soflam_mp" );
//level thread teamPlayerCardSplash( "teamstreak_earned_lasedStrike", self, self.team );
used = self watchSoflamUsage();
if ( isDefined( used ) && used )
{
self.hasSoflam = false;
return true;
}
else
{
return false;
}
}
giveMarker()
{
//wait( .25 );
self maps\mp\killstreaks\_killstreaks::giveKillstreakWeapon( "iw5_soflam_mp" );
self.hasSoflam = true;
self thread watchSoflamUsage();
}
watchSoflamUsage()
{
self notify( "watchSoflamUsage" );
self endon( "watchSoflamUsage" );
level endon( "game_ended" );
self endon( "disconnect" );
self endon( "death" );
while( self isChangingWeapon() )
wait ( 0.05 );
for(;;)
{
if ( self AttackButtonPressed() && self GetCurrentWeapon() == "iw5_soflam_mp" && self AdsButtonPressed() )
{
//self WeaponLockNoClearance( false );
self WeaponLockTargetTooClose( false );
self WeaponLockFree();
targetInfo = getTargetPoint();
if( !isDefined( targetInfo ) )
{
wait 0.05;
continue;
}
if( !isDefined( targetInfo[0] ) )
{
wait 0.05;
continue;
}
targPoint = targetInfo[0];
used = self attackLasedTarget( targPoint );
if ( used )
self.soflamAmmoUsed++;
if ( self.soflamAmmoUsed >= level.numberOfSoflamAmmo )
return true;
}
if ( self isChangingWeapon() )
return false;
wait( 0.05 );
}
}
playLockSound()
{
if ( isDefined( self.playingLockSound ) && self.playingLockSound )
return;
self PlayLocalSound( "javelin_clu_lock" );
self.playingLockSound = true;
wait( .75 );
self StopLocalSound( "javelin_clu_lock" );
self.playingLockSound = false;
}
playLockErrorSound()
{
if ( isDefined( self.playingLockSound ) && self.playingLockSound )
return;
self PlayLocalSound( "javelin_clu_aquiring_lock" );
self.playingLockSound = true;
wait( .75 );
self StopLocalSound( "javelin_clu_aquiring_lock" );
self.playingLockSound = false;
}
attackLasedTarget( targPoint )
{
finalTargetEnt = undefined;
midTargetEnt = undefined;
upQuantity = 6000;
upVector = (0, 0, upQuantity );
backDist = 3000;
forward = AnglesToForward( self.angles );
ownerOrigin = self.origin;
startpos = ownerOrigin + upVector + forward * backDist * -1;
foundAngle = false;
//straight down
skyTrace = bulletTrace( targPoint + (0,0,upQuantity), targPoint , false );
if( skyTrace["fraction"] > .99 )
{
foundAngle = true;
startPos = targPoint + (0,0,upQuantity);
}
//self thread drawLine( targPoint, targPoint + (0,0,upQuantity), 25, (1,0,0) );
if( !foundAngle )
{
skyTrace = bulletTrace( targPoint + (300,0,upQuantity), targPoint , false );
if( skyTrace["fraction"] > .99 )
{
foundAngle = true;
startPos = targPoint + (300,0,upQuantity);
}
//self thread drawLine( targPoint, targPoint + (300,0,upQuantity), 25, (1,0,0) );
}
if( !foundAngle )
{
skyTrace = bulletTrace( targPoint + (0,300,upQuantity), targPoint , false );
if( skyTrace["fraction"] > .99 )
{
foundAngle = true;
startPos = targPoint + (0,300,upQuantity);
}
//self thread drawLine( targPoint, targPoint + (0,300,upQuantity), 25, (1,0,0) );
}
if( !foundAngle )
{
skyTrace = bulletTrace( targPoint + (0,-300,upQuantity), targPoint , false );
if( skyTrace["fraction"] > .99 )
{
foundAngle = true;
startPos = targPoint + (0,-300,upQuantity);
}
//self thread drawLine( targPoint, targPoint + (0,-300,upQuantity), 25, (1,0,0) );
}
if( !foundAngle )
{
skyTrace = bulletTrace( targPoint + (300,300,upQuantity), targPoint , false );
if( skyTrace["fraction"] > .99 )
{
foundAngle = true;
startPos = targPoint + (300,300,upQuantity);
}
//self thread drawLine( targPoint, targPoint + (300,300,upQuantity), 25, (1,0,0) );
}
if( !foundAngle )
{
skyTrace = bulletTrace( targPoint + (-300,0,upQuantity), targPoint , false );
if( skyTrace["fraction"] > .99 )
{
foundAngle = true;
startPos = targPoint + (-300,0,upQuantity);
}
//self thread drawLine( targPoint, targPoint + (-300,0,upQuantity), 25, (1,0,0) );
}
if( !foundAngle )
{
skyTrace = bulletTrace( targPoint + (-300,-300,upQuantity), targPoint , false );
if( skyTrace["fraction"] > .99 )
{
foundAngle = true;
startPos = targPoint + (-300,-300,upQuantity);
}
//self thread drawLine( targPoint, targPoint + (-300,-300,upQuantity), 25, (1,0,0) );
}
if( !foundAngle )
{
skyTrace = bulletTrace( targPoint + (300,-300,upQuantity), targPoint , false );
if( skyTrace["fraction"] > .99 )
{
foundAngle = true;
startPos = targPoint + (300,-300,upQuantity);
}
//self thread drawLine( targPoint, targPoint + (300,-300,upQuantity), 25, (1,0,0) );
}
//backwards vector from player over target area
if( !foundAngle )
{
for ( i = 0; i < 5; i++ )
{
upQuantity = upQuantity / 2;
upVector = (0, 0, upQuantity );
startpos = self.origin + upVector + forward * backDist * -1;
targetSkyCheck = BulletTrace( targPoint, startpos, false );
if ( targetSkyCheck["fraction"] > .99 )
{
foundAngle = true;
break;
}
wait( 0.05 );
}
}
//check increased angle to try to get it over large objects
if( !foundAngle )
{
for ( i = 0; i < 5; i++ )
{
upQuantity = upQuantity * 2.5;
upVector = (0, 0, upQuantity );
startpos = self.origin + upVector + forward * backDist * -1;
targetSkyCheck = BulletTrace( targPoint, startpos, false );
if ( targetSkyCheck["fraction"] > .99 )
{
foundAngle = true;
break;
}
wait( 0.05 );
}
}
if( !foundAngle )
{
self thread cantHitTarget();
return false;
}
//finalTargetEnt = Spawn( "script_origin", targPoint );
finalTargetEnt = SpawnFx( level.lasedStrikeGlow, targPoint );
self thread playLockSound();
//self WeaponLockFinalize( finalTargetEnt, (0,0,0), false );
self WeaponLockFinalize( targPoint, (0,0,0), false );
missile = MagicBullet("lasedStrike_missile_mp", startPos, targPoint, self);
missile Missile_SetTargetEnt( finalTargetEnt );
self thread loopTriggeredeffect( finalTargetEnt, missile );
missile waittill( "death" );
if( isDefined( finalTargetEnt ) )
{
finalTargetEnt delete();
}
self WeaponLockFree();
return true;
}
loopTriggeredEffect( effect, missile )
{
missile endon( "death" );
level endon( "game_ended" );
for( ;; )
{
TriggerFX( effect );
wait ( 0.05 );
}
}
lasedMissileDistance( remote )
{
level endon( "game_ended" );
remote endon( "death" );
remote endon( "remote_done" );
self endon( "death" );
while( true )
{
targetDist = distance( self.origin, remote.targetent.origin );
remote.owner SetClientDvar( "ui_reaper_targetDistance", int( targetDist / 12 ) );
wait( 0.05 );
}
}
cantHitTarget()
{
self thread playLockErrorSound();
//self WeaponLockNoClearance( true );
self WeaponLockTargetTooClose( true );
}
//does one check per frame
checkBestTargetVector( remote, targPoint )
{
foreach( ent in level.lasedStrikeEnts )
{
check = BulletTrace( ent.origin, targPoint, false, remote );
if ( check["fraction"] >= .98 )
{
return ent;
}
wait (0.05 );
}
return;
}
getTargetPoint()
{
origin = self GetEye();
angles = self GetPlayerAngles();
forward = AnglesToForward( angles );
endpoint = origin + forward * 15000;
res = BulletTrace( origin, endpoint, false, undefined );
if ( res["surfacetype"] == "none" )
return undefined;
if ( res["surfacetype"] == "default" )
return undefined;
ent = res["entity"];
if ( IsDefined( ent ) )
{
if ( ent == level.ac130.planeModel )
return undefined;
}
results = [];
results[0] = res["position"];
results[1] = res["normal"];
return results;
}
//Not In Use
spawnRemote( owner )
{
remote = spawnPlane( owner, "script_model", level.UAVRig getTagOrigin( "tag_origin" ), "compass_objpoint_reaper_friendly", "compass_objpoint_reaper_enemy" );
if ( !isDefined( remote ) )
return undefined;
remote setModel( "vehicle_predator_b" );
remote.team = owner.team;
remote.owner = owner;
remote.numFlares = 2;
remote setCanDamage( true );
remote thread damageTracker();
remote.heliType = "remote_mortar";
// for target lists (javelin, stinger, sam, emp, etc)
remote.uavType = "remote_mortar";
remote maps\mp\killstreaks\_uav::addUAVModel();
// same height and radius as the AC130 with random angle and counter rotation
zOffset = 6300;
angle = randomInt( 360 );
radiusOffset = 6100;
xOffset = cos( angle ) * radiusOffset;
yOffset = sin( angle ) * radiusOffset;
angleVector = vectorNormalize( (xOffset,yOffset,zOffset) );
angleVector = ( angleVector * 6100 );
remote linkTo( level.UAVRig, "tag_origin", angleVector, (0,angle-90,10) );
remote thread handleDeath( owner );
remote thread handleOwnerChangeTeam( owner );
remote thread handleOwnerDisconnect( owner );
remote thread handleTimeOut();
remote thread handleIncomingStinger();
remote thread handleIncomingSAM();
return remote;
}
handleDeath( owner )
{
level endon( "game_ended" );
owner endon( "disconnect" );
self endon( "remote_removed" );
self endon( "remote_done" );
self waittill( "death" );
level thread removeRemote( self, true );
}
handleOwnerChangeTeam( owner )
{
level endon( "game_ended" );
self endon( "remote_done" );
self endon( "death" );
owner endon( "disconnect" );
owner endon( "removed_reaper_ammo" );
owner waittill_any( "joined_team", "joined_spectators" );
self thread remoteLeave();
}
handleOwnerDisconnect( owner )
{
level endon( "game_ended" );
self endon( "remote_done" );
self endon( "death" );
owner endon( "removed_reaper_ammo" );
owner waittill( "disconnect" );
self thread remoteLeave();
}
shotCounter()
{
level endon( "game_ended" );
self endon( "death" );
self endon( "remote_done" );
numShotsFired = 0;
for( ;; )
{
self waittill( "lasedTargetShotFired" );
numShotsFired++;
if ( numShotsFired >= 5 )
break;
}
self thread remoteLeave();
}
handleTimeOut()
{
level endon( "game_ended" );
self endon( "death" );
self endon( "remote_done" );
wait 120;
self thread remoteLeave();
}
removeRemote( remote, clearLevelRef )
{
self notify( "remote_removed" );
if ( isDefined( remote.targetEnt ) )
remote.targetEnt delete();
level.lasedStrikeActive = false;
level.lasedStrikeCrateActive = false;
if( IsDefined( remote ) )
{
remote delete();
remote maps\mp\killstreaks\_uav::removeUAVModel();
}
if ( !IsDefined( clearLevelRef ) || clearLevelRef == true )
level.remote_mortar = undefined;
}
remoteLeave()
{
// setting the level variable here because there is a bug if this gets shot down on the way out then this doesn't get cleared because of the endon("death")
// now it'll definitely get cleared as soon as it tries to leave
level.remote_mortar = undefined;
level endon( "game_ended" );
self endon( "death" );
self notify( "remote_done" );
destPoint = self.origin + ( AnglesToForward( self.angles ) * 20000 );
self moveTo( destPoint, 30 );
PlayFXOnTag( level._effect[ "ac130_engineeffect" ] , self, "tag_origin" );
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( 3 );
self moveTo( destPoint, 4, 4, 0.0 );
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( 4 );
level thread removeRemote( self, false );
}
remoteExplode()
{
self notify( "death" );
self Hide();
forward = ( AnglesToRight( self.angles ) * 200 );
playFx ( level.lasedStrikeExplode, self.origin, forward );
level.lasedStrikeActive = false;
level.lasedStrikeCrateActive = false;
}
// Entities spawned from SpawnPlane do not respond to pre-damage callbacks
// so we have to wait until we get the post-damage event.
//
// Because the damage has already happened by the time we find out about it,
// we need to use an artificially high health value, restore it on erroneous damage
// events and track a virtual damage taken against a virtual max health.
damageTracker()
{
level endon( "game_ended" );
self.owner endon( "disconnect" );
self.health = 999999; // keep it from dying anywhere in code
self.maxHealth = 1500; // this is the health we'll check
self.damageTaken = 0; // how much damage has it taken
while( true )
{
self waittill( "damage", damage, attacker, direction_vec, point, meansOfDeath, modelName, tagName, partName, iDFlags, weapon );
// don't allow people to destroy things on their team if FF is off
if ( !maps\mp\gametypes\_weapons::friendlyFireCheck( self.owner, attacker ) )
continue;
if ( !IsDefined( self ) )
return;
if ( isDefined( iDFlags ) && ( iDFlags & level.iDFLAGS_PENETRATION ) )
self.wasDamagedFromBulletPenetration = true;
self.wasDamaged = true;
modifiedDamage = damage;
if( IsPlayer( attacker ) )
{
attacker maps\mp\gametypes\_damagefeedback::updateDamageFeedback( "" );
if( meansOfDeath == "MOD_RIFLE_BULLET" || meansOfDeath == "MOD_PISTOL_BULLET" )
{
if ( attacker _hasPerk( "specialty_armorpiercing" ) )
modifiedDamage += damage * level.armorPiercingMod;
}
}
if( IsDefined( weapon ) )
{
switch( weapon )
{
case "stinger_mp":
case "javelin_mp":
self.largeProjectileDamage = true;
modifiedDamage = self.maxhealth + 1;
break;
case "sam_projectile_mp":
self.largeProjectileDamage = true;
break;
}
maps\mp\killstreaks\_killstreaks::killstreakHit( attacker, weapon, self );
}
self.damageTaken += modifiedDamage;
if( IsDefined( self.owner ) )
self.owner playLocalSound( "reaper_damaged" );
if ( self.damageTaken >= self.maxHealth )
{
if ( isPlayer( attacker ) && ( !isDefined( self.owner ) || attacker != self.owner ) )
{
attacker notify( "destroyed_killstreak", weapon );
thread teamPlayerCardSplash( "callout_destroyed_remote_mortar", attacker );
attacker thread maps\mp\gametypes\_rank::giveRankXP( "kill", 50, weapon, meansOfDeath );
attacker thread maps\mp\gametypes\_rank::xpEventPopup( "destroyed_remote_mortar" );
thread maps\mp\gametypes\_missions::vehicleKilled( self.owner, self, undefined, attacker, damage, meansOfDeath, weapon );
}
if ( isDefined( self.owner ) )
self.owner StopLocalSound( "missile_incoming" );
self thread remoteExplode();
// adding this to make sure it gets undefined, there was a weird bug where it could get killed as soon as it was leaving
// then the threads got killed because of the endon's and this never got reset
level.remote_mortar = undefined;
return;
}
}
}
handleIncomingStinger() // self == remote mortar
{
level endon ( "game_ended" );
self endon ( "death" );
self endon ( "remote_done" );
while( true )
{
level waittill ( "stinger_fired", player, missile, lockTarget );
if ( !IsDefined( lockTarget ) || (lockTarget != self) )
continue;
missile thread stingerProximityDetonate( lockTarget, player );
}
}
stingerProximityDetonate( missileTarget, player ) // self == missile
{
self endon ( "death" );
missileTarget endon( "death" );
if( IsDefined( missileTarget.owner ) )
missileTarget.owner PlayLocalSound( "missile_incoming" );
self Missile_SetTargetEnt( missileTarget );
minDist = Distance( self.origin, missileTarget GetPointInBounds( 0, 0, 0 ) );
lastCenter = missileTarget GetPointInBounds( 0, 0, 0 );
while( true )
{
// already destroyed
if( !IsDefined( missileTarget ) )
center = lastCenter;
else
center = missileTarget GetPointInBounds( 0, 0, 0 );
lastCenter = center;
curDist = Distance( self.origin, center );
if( curDist < 3000 && missileTarget.numFlares > 0 )
{
missileTarget.numFlares--;
missileTarget thread maps\mp\killstreaks\_flares::flares_playFx();
newTarget = missileTarget maps\mp\killstreaks\_flares::flares_deploy();
self Missile_SetTargetEnt( newTarget );
missileTarget = newTarget;
if( IsDefined( missileTarget.owner ) )
missileTarget.owner StopLocalSound( "missile_incoming" );
return;
}
if( curDist < minDist )
minDist = curDist;
if( curDist > minDist )
{
if( curDist > 1536 )
return;
if( IsDefined( missileTarget.owner ) )
{
missileTarget.owner stopLocalSound( "missile_incoming" );
if( level.teambased )
{
if( missileTarget.team != player.team )
RadiusDamage( self.origin, 1000, 1000, 1000, player, "MOD_EXPLOSIVE", "stinger_mp" );
}
else
{
RadiusDamage( self.origin, 1000, 1000, 1000, player, "MOD_EXPLOSIVE", "stinger_mp" );
}
}
self Hide();
wait( 0.05 );
self delete();
}
wait ( 0.05 );
}
}
handleIncomingSAM() // self == remote mortar
{
level endon ( "game_ended" );
self endon ( "death" );
self endon ( "remote_done" );
while( true )
{
level waittill ( "sam_fired", player, missileGroup, lockTarget );
if ( !IsDefined( lockTarget ) || (lockTarget != self) )
continue;
level thread samProximityDetonate( lockTarget, player, missileGroup );
}
}
samProximityDetonate( missileTarget, player, missileGroup )
{
missileTarget endon( "death" );
if( IsDefined( missileTarget.owner ) )
missileTarget.owner PlayLocalSound( "missile_incoming" );
sam_projectile_damage = 150; // this should match the gdt entry
sam_projectile_damage_radius = 1000;
minDist = [];
for( i = 0; i < missileGroup.size; i++ )
{
if( IsDefined( missileGroup[ i ] ) )
minDist[ i ] = Distance( missileGroup[ i ].origin, missileTarget GetPointInBounds( 0, 0, 0 ) );
else
minDist[ i ] = undefined;
}
while( true )
{
center = missileTarget GetPointInBounds( 0, 0, 0 );
curDist = [];
for( i = 0; i < missileGroup.size; i++ )
{
if( IsDefined( missileGroup[ i ] ) )
curDist[ i ] = Distance( missileGroup[ i ].origin, center );
}
for( i = 0; i < curDist.size; i++ )
{
if( IsDefined( curDist[ i ] ) )
{
// if one of the missiles in the group get close, set off flares and redirect them all
if( curDist[ i ] < 3000 && missileTarget.numFlares > 0 )
{
missileTarget.numFlares--;
missileTarget thread maps\mp\killstreaks\_flares::flares_playFx();
newTarget = missileTarget maps\mp\killstreaks\_flares::flares_deploy();
for( j = 0; j < missileGroup.size; j++ )
{
if( IsDefined( missileGroup[ j ] ) )
{
missileGroup[ j ] Missile_SetTargetEnt( newTarget );
}
}
if( IsDefined( missileTarget.owner ) )
missileTarget.owner StopLocalSound( "missile_incoming" );
return;
}
if( curDist[ i ] < minDist[ i ] )
minDist[ i ] = curDist[ i ];
if( curDist[ i ] > minDist[ i ] )
{
if( curDist[ i ] > 1536 )
continue;
if( IsDefined( missileTarget.owner ) )
{
missileTarget.owner StopLocalSound( "missile_incoming" );
if( level.teambased )
{
if( missileTarget.team != player.team )
RadiusDamage( missileGroup[ i ].origin, sam_projectile_damage_radius, sam_projectile_damage, sam_projectile_damage, player, "MOD_EXPLOSIVE", "sam_projectile_mp" );
}
else
{
RadiusDamage( missileGroup[ i ].origin, sam_projectile_damage_radius, sam_projectile_damage, sam_projectile_damage, player, "MOD_EXPLOSIVE", "sam_projectile_mp" );
}
}
missileGroup[ i ] Hide();
wait ( 0.05 );
missileGroup[ i ] delete();
}
}
}
wait ( 0.05 );
}
}

View File

@ -0,0 +1,715 @@
#include maps\mp\_utility;
#include common_scripts\utility;
#include maps\mp\gametypes\_hud_util;
init()
{
level.tankFire = loadfx( "fx/props/barrelexp" );
//level.tankDamage = loadfx( "fx/fire/tank_fire_turret_abrams" );
level.tankExplode = loadfx( "fx/explosions/large_vehicle_explosion" );
level.tankFlash = loadfx( "fx/muzzleflashes/ac130_105mm" );
level.tankDust1 = loadfx( "fx/smoke/ground_smoke_launch_a" );
level.tankDust2 = loadfx( "fx/smoke/ground_smoke_launch_a" );
level.ground_support_locs = [];
//
// ALPHA
level.ground_support_locs["mp_alpha"][0]["origin"] = ( -2748.91, 2921.33, 125.394 );
level.ground_support_locs["mp_alpha"][0]["angles"] = ( 0, 16, 0 );
level.ground_support_locs["mp_alpha"][1]["origin"] = ( 707.795, -499.191, -7.875 );
level.ground_support_locs["mp_alpha"][1]["angles"] = ( 0, 90, 0 );
level.ground_support_locs["mp_alpha"][2]["origin"] = ( 81.8557, 2343.87, -7.875 );
level.ground_support_locs["mp_alpha"][2]["angles"] = ( 0, -90.1, 0 );
//
// BRAVO
level.ground_support_locs["mp_bravo"][0]["origin"] = ( -761.529, 1221.7, 1212.13 );
level.ground_support_locs["mp_bravo"][0]["angles"] = ( 0, -92.373, 0 );
level.ground_support_locs["mp_bravo"][1]["origin"] = ( 1474.99, 971.865, 1140.13 );
level.ground_support_locs["mp_bravo"][1]["angles"] = ( 0, -178.401, 0 );
level.ground_support_locs["mp_bravo"][2]["origin"] = ( -1366.57, -370.995, 975.807 );
level.ground_support_locs["mp_bravo"][2]["angles"] = ( 0, 0.807495, 0 );
//
// DOME
level.ground_support_locs["mp_dome"][0]["origin"] = ( 960.279, -482.564, -388.872 );
level.ground_support_locs["mp_dome"][0]["angles"] = ( 0, 100.536, 0 );
level.ground_support_locs["mp_dome"][1]["origin"] = ( -921.941, 166.449, -418.131 );
level.ground_support_locs["mp_dome"][1]["angles"] = ( 0, 25.4524, 0 );
level.ground_support_locs["mp_dome"][2]["origin"] = ( 43.3564, 2102.85, -290.875 );
level.ground_support_locs["mp_dome"][2]["angles"] = ( 0, -95.0347, 0 );
//
// PLAZA 2
level.ground_support_locs["mp_plaza2"][0]["origin"] = ( -1579.34, -2349.41, 556.125 );
level.ground_support_locs["mp_plaza2"][0]["angles"] = ( 0, 5.32288, 0 );
level.ground_support_locs["mp_plaza2"][1]["origin"] = ( -135.286, 1622.04, 607.13 );
level.ground_support_locs["mp_plaza2"][1]["angles"] = ( 0, 173.639, 0 );
level.ground_support_locs["mp_plaza2"][2]["origin"] = ( -1544.55, 1966.84, 632.024 );
level.ground_support_locs["mp_plaza2"][2]["angles"] = ( 0, 0.796509, 0 );
//
// RADAR
level.ground_support_locs["mp_radar"][0]["origin"] = ( 896.685, 2692.74, 1208.13 );
level.ground_support_locs["mp_radar"][0]["angles"] = ( 0, -178.313, 0 );
level.ground_support_locs["mp_radar"][1]["origin"] = ( -2455.87, 1564.41, 1308.9 );
level.ground_support_locs["mp_radar"][1]["angles"] = ( 0, 1.93471, 0 );
level.ground_support_locs["mp_radar"][2]["origin"] = ( 1609, -1098.99, 1162.13 );
level.ground_support_locs["mp_radar"][2]["angles"] = ( 0, 170.421, 0 );
//
// INTERCHANGE
level.ground_support_locs["mp_interchange"][0]["origin"] = ( -2067.08, 1218.17, -82.0487 );
level.ground_support_locs["mp_interchange"][0]["angles"] = ( 0, -26.2946, 0 );
level.ground_support_locs["mp_interchange"][1]["origin"] = ( -1198.2, -1782.62, 103.665 );
level.ground_support_locs["mp_interchange"][1]["angles"] = ( 0, 23.3544, 0 );
level.ground_support_locs["mp_interchange"][2]["origin"] = ( 2391.95, 899.034, 87.7839 );
level.ground_support_locs["mp_interchange"][2]["angles"] = ( 0, -136.134, 0 );
//
// LAMBETH
level.ground_support_locs["mp_lambeth"][0]["origin"] = ( 1641.37, -1318.01, -260.173 );
level.ground_support_locs["mp_lambeth"][0]["angles"] = ( 0, 133.329, 0 );
level.ground_support_locs["mp_lambeth"][1]["origin"] = ( -1346.56, -880.226, -191.875 );
level.ground_support_locs["mp_lambeth"][1]["angles"] = ( 0, 0.432892, 0 );
level.ground_support_locs["mp_lambeth"][2]["origin"] = ( 1403.95, 3083.4, -287.354 );
level.ground_support_locs["mp_lambeth"][2]["angles"] = ( 0, -106.321, 0 );
//
// PARIS
level.ground_support_locs["mp_paris"][0]["origin"] = ( -2427.42, 619.217, 188.826 );
level.ground_support_locs["mp_paris"][0]["angles"] = ( 0, -2.90588, 0 );
level.ground_support_locs["mp_paris"][1]["origin"] = ( 2066.95, 796.542, -88.322 );
level.ground_support_locs["mp_paris"][1]["angles"] = ( 0, 177.292, 0 );
level.ground_support_locs["mp_paris"][2]["origin"] = ( 506.406, -2165.36, -64.1201 );
level.ground_support_locs["mp_paris"][2]["angles"] = ( 0, 89.5715, 0 );
//
// HARDHAT
level.ground_support_locs["mp_hardhat"][0]["origin"] = ( 2033.65, -1428.62, 299.86 );
level.ground_support_locs["mp_hardhat"][0]["angles"] = ( 0, 177.979, 0 );
level.ground_support_locs["mp_hardhat"][1]["origin"] = ( -1044.73, 82.9179, 181.022 );
level.ground_support_locs["mp_hardhat"][1]["angles"] = ( 0, -2.68066, 0 );
level.ground_support_locs["mp_hardhat"][2]["origin"] = ( 1248.95, 1322.56, 304.125 );
level.ground_support_locs["mp_hardhat"][2]["angles"] = ( 0, -93.4772, 0 );
//
// CARBON
level.ground_support_locs["mp_carbon"][0]["origin"] = ( -47.1408, -2841.26, 3940.01 );
level.ground_support_locs["mp_carbon"][0]["angles"] = ( 0, -101.667, 0 );
level.ground_support_locs["mp_carbon"][1]["origin"] = ( -1686.2, -4727.09, 3756.16 );
level.ground_support_locs["mp_carbon"][1]["angles"] = ( 0, 87.6436, 0 );
level.ground_support_locs["mp_carbon"][2]["origin"] = ( -3761.18, -3716.69, 3568.91 );
level.ground_support_locs["mp_carbon"][2]["angles"] = ( 0, -4.20761, 0 );
//
// SEATOWN
level.ground_support_locs["mp_seatown"][0]["origin"] = ( 1339.87, 763.592, 175.114 );
level.ground_support_locs["mp_seatown"][0]["angles"] = ( 0, 178.551, 0 );
level.ground_support_locs["mp_seatown"][1]["origin"] = ( 1317.92, -725.589, 232.125 );
level.ground_support_locs["mp_seatown"][1]["angles"] = ( 0, 177.738, 0 );
level.ground_support_locs["mp_seatown"][2]["origin"] = ( -961.699, -1581.56, 144.125 );
level.ground_support_locs["mp_seatown"][2]["angles"] = ( 0, 90.0176, 0 );
//
// BOOTLEG
level.ground_support_locs["mp_bootleg"][0]["origin"] = ( -988.964, 1833.74, -99.9509 );
level.ground_support_locs["mp_bootleg"][0]["angles"] = ( 0, -78.8909, 0 );
level.ground_support_locs["mp_bootleg"][1]["origin"] = ( 1105.84, -1116.13, -72.3048 );
level.ground_support_locs["mp_bootleg"][1]["angles"] = ( 0, 176.558, 0 );
level.ground_support_locs["mp_bootleg"][2]["origin"] = ( -2027.31, 84.2235, -51.875 );
level.ground_support_locs["mp_bootleg"][2]["angles"] = ( 0, -5.12868, 0 );
//
// METEORA
level.ground_support_locs["mp_meteora"][0]["origin"] = ( -590.972, 1667.65, -99.6187 );
level.ground_support_locs["mp_meteora"][0]["angles"] = ( 0, -89.7745, 0 );
level.ground_support_locs["mp_meteora"][1]["origin"] = ( -1371.02, -1095.66, 4.125 );
level.ground_support_locs["mp_meteora"][1]["angles"] = ( 0, 179.879, 0 );
level.ground_support_locs["mp_meteora"][2]["origin"] = ( 938.851, -1376.99, -60.0877 );
level.ground_support_locs["mp_meteora"][2]["angles"] = ( 0, 110.545, 0 );
level.killStreakFuncs["mobile_mortar"] = ::tryUseMobileMortar;
}
tryUseMobileMortar( lifeId, streakName )
{
// context fail cases
if ( !isDefined( level.ground_support_locs[level.script] ) )
{
self iPrintLnBold( &"KILLSTREAKS_UNAVAILABLE_IN_LEVEL" );
return false;
}
if ( isDefined( self.lastStand ) && !self _hasPerk( "specialty_finalstand" ) )
{
self iPrintLnBold( &"KILLSTREAKS_UNAVAILABLE_IN_LASTSTAND" );
return false;
}
else if ( isDefined( level.mobileMortar ) )
{
self iPrintLnBold( &"KILLSTREAKS_GROUND_APPROACHES_TOO_CROWDED" );
return false;
}
else if ( self isUsingRemote() )
{
return false;
}
// select location
locIndex = self selectEntranceLocation();
if ( !isDefined( locIndex ) )
{
return false;
}
else
self thread stopLocationSelection( false );
// create it
mobileMortar = createMobileMortar( self, locIndex );
if ( !isDefined( mobileMortar ) )
return false;
// use it
mobileMortar thread moveToPosition( "entrance" );
return true;
}
selectEntranceLocation()
{
locIndex = undefined;
for ( ;; )
{
self thread showIcons();
self _beginLocationSelection( "mobile_mortar", "map_artillery_selector", false, 500 );
self endon( "stop_location_selection" );
self waittill( "confirm_location", location );
for ( i=0; i<3; i++ )
{
targetLoc = level.ground_support_locs[level.script][i]["origin"] * (1,1,0);
distSquared = distanceSquared( location, targetLoc );
if ( distSquared < 60000 )
{
locIndex = i;
break;
}
}
if ( isDefined( locIndex ) )
{
for ( i=0; i<3; i++ )
{
if ( i == locIndex )
Objective_Icon( self.locationObjectives[i], "compass_objpoint_mortar_target" );
else
Objective_state( self.locationObjectives[i], "invisible" );
}
}
else
{
for ( i=0; i<3; i++ )
{
Objective_Icon( self.locationObjectives[i], "compass_objpoint_tank_enemy" );
}
}
wait ( 0.5 );
self notify( "picked_location" );
wait( 0.05 );
if ( isDefined( locIndex ) )
break;
}
return locIndex;
}
showIcons()
{
msg = self maps\mp\gametypes\_hud_util::createFontString( "bigfixed", 0.5 );
msg maps\mp\gametypes\_hud_util::setPoint( "CENTER", "CENTER", 0 , -150 );
msg setText( &"KILLSTREAKS_SELECT_MOBILE_MORTAR_LOCATION" );
self.locationObjectives = [];
for ( i=0; i<3; i++ )
{
self.locationObjectives[i] = maps\mp\gametypes\_gameobjects::getNextObjID();
objective_add( self.locationObjectives[i], "invisible", (0,0,0) );
objective_position( self.locationObjectives[i], level.ground_support_locs[level.script][i]["origin"] );
objective_state( self.locationObjectives[i], "active" );
objective_team( self.locationObjectives[i], self.team );
objective_icon( self.locationObjectives[i], "compass_objpoint_tank_friendly" );
}
self waittill_any( "cancel_location", "picked_location", "stop_location_selection" );
msg destroyElem();
for ( i=0; i<3; i++ )
{
_objective_delete( self.locationObjectives[i] );
}
}
createMobileMortar( owner, locIndex )
{
// position
skyHeight = self maps\mp\killstreaks\_airdrop::getFlyHeightOffset( level.ground_support_locs[level.script][locIndex]["origin"] );
primaryTrace = bulletTrace( level.ground_support_locs[level.script][locIndex]["origin"]+(0,0,skyHeight), level.ground_support_locs[level.script][locIndex]["origin"]-(0,0,skyHeight), false );
spawnPos = primaryTrace["position"] + anglesToForward(level.ground_support_locs[level.script][locIndex]["angles"])*-1000;
// create it
mobileMortar = Spawn( "script_model", spawnPos );
if ( !isDefined( mobileMortar ) )
return undefined;
// set state vars
mobileMortar.angles = level.ground_support_locs[level.script][locIndex]["angles"];
mobileMortar SetModel( "vehicle_bradley" );
mobileMortar setCanDamage( true );
mobileMortar.maxhealth = level.heli_maxhealth*2;
mobileMortar.health = mobileMortar.maxhealth;
mobileMortar.owner = owner;
mobileMortar.playersAttacked = [];
mobileMortar.lastTarget = mobileMortar.origin;
if ( level.teamBased )
mobileMortar.team = owner.team;
// save some info about the level spawn bounds for fallback random targeting
mobileMortar.lowX = level.spawnpoints[0].origin[0];
mobileMortar.highX = level.spawnpoints[0].origin[0];
mobileMortar.lowY = level.spawnpoints[0].origin[1];
mobileMortar.highY = level.spawnpoints[0].origin[1];
increment = 200;
if ( level.spawnpoints.size > 1 )
{
for( i=1; i<level.spawnpoints.size; i++ )
{
if ( level.spawnpoints[i].origin[0] < mobileMortar.lowX )
mobileMortar.lowX = level.spawnpoints[i].origin[0];
else if ( level.spawnpoints[i].origin[0] > mobileMortar.highX )
mobileMortar.highX = level.spawnpoints[i].origin[0];
if ( level.spawnpoints[i].origin[1] < mobileMortar.lowY )
mobileMortar.lowY = level.spawnpoints[i].origin[1];
else if ( level.spawnpoints[i].origin[1] > mobileMortar.highY )
mobileMortar.highY = level.spawnpoints[i].origin[1];
}
}
else
increment = -2000;
mobileMortar.lowX += increment;
mobileMortar.highX -= increment;
mobileMortar.lowY += increment;
mobileMortar.highY -= increment;
// minimap
if ( level.teamBased )
{
curObjID = maps\mp\gametypes\_gameobjects::getNextObjID();
objective_add( curObjID, "invisible", (0,0,0) );
objective_position( curObjID, primaryTrace["position"] );
objective_state( curObjID, "active" );
objective_team( curObjID, mobileMortar.team );
objective_icon( curObjID, "compass_objpoint_tank_friendly" );
mobileMortar.objIdFriendly = curObjID;
curObjID = maps\mp\gametypes\_gameobjects::getNextObjID();
objective_add( curObjID, "invisible", (0,0,0) );
objective_position( curObjID, primaryTrace["position"] );
objective_state( curObjID, "active" );
objective_team( curObjID, level.otherTeam[mobileMortar.team] );
objective_icon( curObjID, "compass_objpoint_tank_enemy" );
mobileMortar.objIdEnemy = curObjID;
}
// launch watch threads
mobileMortar thread watchTimeOut();
mobileMortar thread maps\mp\killstreaks\_helicopter::heli_damage_monitor();
//mobileMortar thread watchDamage();
mobileMortar thread watchDeath();
mobileMortar thread watchProximity();
level.mobileMortar = mobileMortar;
return mobileMortar;
}
moveToPosition( position /*lifeId, airShip, pathStart, pathGoal, pathEnd, flyHeight*/ )
{
level endon( "game_ended" );
self endon( "death" );
if ( position == "entrance" )
pos = self.origin + anglesToForward(self.angles)*1000;
else
{
self notify( "leaving" );
pos = self.origin + anglesToForward(self.angles)*-1000;
}
movetime = 3.0;
self MoveTo( pos, movetime, movetime * 0.6, movetime * 0.4 );
wait( movetime );
if ( position == "entrance" )
self thread mortarAttack();
else
{
StopFXOnTag( level.tankDust1, self.fxEnt, "tag_origin" );
StopFXOnTag( level.tankDust2, self.fxEnt, "tag_origin" );
//StopFXOnTag( level.tankDamage, self, "tag_origin" );
self.fxEnt delete();
_objective_delete( self.objIdFriendly );
_objective_delete( self.objIdEnemy );
level.mobileMortar = undefined;
self delete();
}
}
findTarget()
{
bestTarget = undefined;
foreach ( player in level.players )
{
if ( player == self.owner )
continue;
if ( player _hasPerk( "specialty_blindeye" ) )
continue;
if ( level.teamBased && player.team == self.owner.team )
continue;
if ( distanceSquared( self.origin, player.origin ) < 1000000 )
continue;
for ( i=0; i<self.playersAttacked.size; i++ )
{
if ( player == self.playersAttacked[i] )
continue;
}
if ( distanceSquared( player.origin, self.lastTarget ) < 500000 )
continue;
if ( level.teamBased )
{
friendlyClose = false;
for ( i=0; i<level.players.size; i++ )
{
if ( level.players[i].team != player.team && distanceSquared( player.origin, level.players[i].origin ) < 250000 )
{
friendlyClose = true;
break;
}
}
if ( friendlyClose == true )
continue;
}
wait( 0.05 );
trace = bulletTrace( player.origin+(0,0,player maps\mp\killstreaks\_airdrop::getFlyHeightOffset( player.origin )), player.origin+(0,0,100), false );
if ( trace["surfacetype"] != "none" )
continue;
bestTarget = player.origin;
self.lastTarget = player.origin;
self.playersAttacked[self.playersAttacked.size] = player;
break;
}
return bestTarget;
}
findRandomTarget()
{
randomTarget = undefined;
for( i=0; i<20; i++ )
{
testTarget = ( RandomFloatRange( self.lowX, self.highX ), RandomFloatRange( self.lowY, self.highY ), 0 );
if ( distanceSquared( self.origin*(1,1,0), testTarget ) < 1000000 )
continue;
if ( distanceSquared( self.owner.origin*(1,1,0), testTarget ) < 250000 )
continue;
if ( distanceSquared( self.origin*(1,1,0), self.lastTarget ) < 500000 )
continue;
friendlyClose = false;
if ( level.teamBased )
{
foreach ( player in level.players )
{
if ( player.team == self.owner.team && distanceSquared( player.origin*(1,1,0), testTarget ) < 250000 )
{
friendlyClose = true;
break;
}
}
}
if ( friendlyClose == false )
{
randomTarget = testTarget;
self.lastTarget = testTarget;
break;
}
}
// we tried
if ( !isDefined( randomTarget ) )
randomTarget = ( RandomFloatRange( self.lowX, self.highX ), RandomFloatRange( self.lowY, self.highY ), 0 );
return randomTarget;
}
mortarAttack()
{
level endon( "game_ended" );
self endon( "death" );
self endon( "leaving" );
// testing fx stuff
dustCount = 0;
self.fxEnt = Spawn( "script_model", self.origin );
self.fxEnt setModel( "tag_origin" );
self.fxEnt.angles = self.angles;
self.fxEnt AddPitch( -90 );
for(;;)
{
// target
flatPos = self findTarget();
if ( !isDefined( flatPos ) )
flatPos = self findRandomTarget();
flyHeight = self.origin[2] + 3500;
primaryTrace = bulletTrace( flatPos+(0,0,flyHeight), flatPos-(0,0,flyHeight), false );
assert( primaryTrace["surfacetype"] != "none" );
startPos = flatPos + (0,0,flyHeight);
targetPos = flatPos + (0,0,primaryTrace["position"][2]);
// fx
self playSound( "bmp_fire" );
playFX( level.tankFlash, self.origin+anglesToForward(self.angles)*50 );
if ( dustCount < 3 )
{
PlayFXOnTag( level.tankDust1, self.fxEnt, "tag_origin" );
PlayFXOnTag( level.tankDust2, self.fxEnt, "tag_origin" );
dustCount++;
}
self thread mortarRecoil();
// fire
level thread fireMortar( self, startPos, targetPos );
// wait
self waittill( "mortar_fire_done" );
wait( 2.5 );
}
}
fireMortar( mobileMortar, startPos, targetPos )
{
level endon( "game_ended" );
owner = mobileMortar.owner;
projectile = magicBullet( "javelin_mp", mobileMortar.origin+(0,0,150), startPos, owner );
curObjID1 = maps\mp\gametypes\_gameobjects::getNextObjID();
objective_add( curObjID1, "invisible", (0,0,0) );
objective_position( curObjID1, targetPos );
objective_state( curObjID1, "active" );
objective_team( curObjID1, mobileMortar.team );
objective_icon( curObjID1, "compass_objpoint_mortar_target" );
projectile.objIdFriendly = curObjID1;
curObjID2 = maps\mp\gametypes\_gameobjects::getNextObjID();
objective_add( curObjID2, "invisible", (0,0,0) );
objective_position( curObjID2, targetPos );
objective_state( curObjID2, "active" );
objective_team( curObjID2, level.otherTeam[mobileMortar.team] );
objective_icon( curObjID2, "compass_objpoint_mortar_target" );
projectile.objIdEnemy = curObjID2;
flyCount = 0;
for( ;; )
{
if ( !isDefined( projectile ) || flyCount > 115 || distanceSquared( projectile.origin, startPos ) < 500 )
break;
else
wait( 0.05 );
flyCount++;
}
if ( isDefined( projectile ) )
projectile delete();
if ( isDefined( owner ) )
projectile2 = magicBullet( "javelin_mp", startPos+(0,0,200), targetPos, owner );
else
projectile2 = magicBullet( "javelin_mp", startPos+(0,0,200), targetPos );
projectile2.objIdFriendly = curObjID1;
projectile2.objIdEnemy = curObjID2;
projectile2 thread watchProjectileOnMiniMap( mobileMortar );
}
watchProjectileOnMiniMap( mobileMortar )
{
level endon( "game_ended" );
self waittill( "death" );
_objective_delete( self.objIdFriendly );
_objective_delete( self.objIdEnemy );
if ( isDefined( mobileMortar ) )
mobileMortar notify( "mortar_fire_done" );
}
mortarRecoil()
{
level endon( "game_ended" );
self endon( "death" );
self endon( "leaving" );
backPos = self.origin + anglesToForward(self.angles)*-20;
restorePos = self.origin;
restoreAngles = self.angles;
self MoveTo( backPos, 0.10 );
self RotatePitch( -3, 0.10 );
wait( 0.10 );
self MoveTo( restorePos, 0.15 );
self RotateTo( restoreAngles, 0.15 );
wait( 0.15 );
}
watchTimeOut()
{
level endon( "game_ended" );
self endon( "death" );
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( 90.0 );
self setCanDamage( false );
self thread moveToPosition( "exit" );
}
watchProximity()
{
level endon( "game_ended" );
self endon( "death" );
self endon( "leaving" );
for ( ;; )
{
RadiusDamage( self.origin, 200, 20, 20 );
wait( 1 );
}
}
watchDeath()
{
level endon( "game_ended" );
self endon( "leaving" );
self waittill( "death" );
playFX( level.tankFire, self.origin);
playFX( level.tankExplode, self.origin);
destroyedTank = Spawn( "script_model", self.origin );
destroyedTank setModel( "vehicle_bradley_destroyed" );
destroyedTank.angles = self.angles;
StopFXOnTag( level.tankDust1, self.fxEnt, "tag_origin" );
StopFXOnTag( level.tankDust2, self.fxEnt, "tag_origin" );
//StopFXOnTag( level.tankDamage, self, "tag_origin" );
_objective_delete( self.objIdFriendly );
_objective_delete( self.objIdEnemy );
self.fxEnt delete();
self delete();
wait( 3.5 );
destroyedTank delete();
level.mobileMortar = undefined;
}
watchDamage()
{
/*
if ( !isDefined( attacker ) || attacker == self )
return;
if ( !maps\mp\gameTypes\_weapons::attackerCanDamageItem( attacker, self.owner ) )
return;
maps\mp\killstreaks\_killstreaks::killstreakHit( attacker, weapon, self );
self.damageTaken+=damage;
if ( self.damageTaken >= self.maxhealth && ( (level.teamBased && self.team != attacker.team) || !level.teamBased) )
{
validAttacker = undefined;
if ( isDefined( attacker.owner ) && (!isDefined(self.owner) || attacker.owner != self.owner) )
validAttacker = attacker.owner;
else if ( !isDefined(self.owner) || attacker != self.owner )
validAttacker = attacker;
// sanity checks
if ( !isDefined(attacker.owner) && attacker.classname == "script_vehicle" )
validAttacker = undefined;
if ( isDefined( attacker.class ) && attacker.class == "worldspawn" )
validAttacker = undefined;
if ( isDefined( validAttacker ) )
{
validAttacker notify( "destroyed_helicopter" );
validAttacker notify( "destroyed_killstreak", weapon );
thread teamPlayerCardSplash( "callout_destroyed_helicopter", validAttacker );
validAttacker thread maps\mp\gametypes\_rank::giveRankXP( "kill", 300 );
validAttacker thread maps\mp\gametypes\_rank::xpEventPopup( &"SPLASHES_DESTROYED_HELICOPTER" );
thread maps\mp\gametypes\_missions::vehicleKilled( self.owner, self, undefined, validAttacker, damage, meansOfDeath );
}
}
*/
level endon( "game_ended" );
self endon( "death" );
for ( ;; )
{
self waittill( "damage", amount, attacker, dir, point, type );
//if ( attacker == self )
//self.health += amount;
/*if ( self.health < 500 && !isDefined( self.playingDamageFX ) )
{
self.playingDamageFX = true;
PlayFXOnTag( level.tankDamage, self, "tag_origin" );
}*/
if ( self.health < 0 )
break;
}
}

View File

@ -0,0 +1,518 @@
#include maps\mp\_utility;
#include common_scripts\utility;
createMortar( config )
{
// necessary fields, see rumble for examples?
// config.crateWeight = 85;
// config.crateHint = &"KILLSTREAKS_HINTS_WARHAWK_MORTARS";
// config.debugName = "Warhawk mortars";
// config.id = "warhawk_mortars";
// config.weaponName = "warhawk_mortar_mp";
// config.splashName = "used_warhawk_mortars";
// config.sourceStructs = "rumble_mortar_source";
// config.sourceEnts = use ents for moving objects, structs for static ones. Only one is needed!
// config.targetStructs = "rumble_mortar_target";
//
// // launch delay
// config.launchDelay = 3;
//
// config.projectilePerLoop = 12;
// // air time
//
// // projectile model
// config.Model = "projectile_rpg7";
//
// // warning sfx, played in environment
// config.warningSfxEntName = "mortar_siren";
// config.warningSfx = "mortar_siren";
// config.warningSfxDuration = 10;
//
// // launch parameters
// // min delay, max delay, max air time
// // launch sfx
// config.launchSfx = "mortar_launch";
// config.launchDelayMin = 0.5;
// config.launchDelayMax = 0.6;
// config.launchAirTimeMin = 10;
// config.launchAirTimeMax = 12;
// config.strikeDuration = 25;
//
// // incoming sfx
// config.incomingSfx = "mortar_incoming";
// config.trailVfx = "random_mortars_trail"; // the projectilModel needs a tag_fx on it
//
// // impact
// config.impactVfx = "mortar_impact_00";
level.mortarConfig = config;
config thread update_mortars(); //setup and update.
level.air_raid_active = false;
level.mapCustomCrateFunc = ::mortarCustomCrateFunc;
level.mapCustomKillstreakFunc = ::mortarCustomKillstreakFunc;
level.mapCustomBotKillstreakFunc = ::mortarCustomBotKillstreakFunc;
}
RUMBLE_MORTARS_WEIGHT = 85;
mortarCustomCrateFunc()
{
if(!IsDefined(game["player_holding_level_killstrek"]))
game["player_holding_level_killstrek"] = false;
if(!allowLevelKillstreaks() || game["player_holding_level_killstrek"])
return;
maps\mp\killstreaks\_airdrop::addCrateType( "airdrop_assault",
level.mortarConfig.id,
level.mortarConfig.crateWeight,
maps\mp\killstreaks\_airdrop::killstreakCrateThink,
maps\mp\killstreaks\_airdrop::get_friendly_crate_model(),
maps\mp\killstreaks\_airdrop::get_enemy_crate_model(),
level.mortarConfig.crateHint
);
level thread watch_for_mortars_crate();
}
watch_for_mortars_crate()
{
while(1)
{
level waittill("createAirDropCrate", dropCrate);
if( IsDefined(dropCrate) && IsDefined(dropCrate.crateType) && dropCrate.crateType == level.mortarConfig.id )
{
maps\mp\killstreaks\_airdrop::changeCrateWeight("airdrop_assault", level.mortarConfig.id, 0);
captured = wait_for_capture(dropCrate);
if(!captured)
{
//reEnable warhawk mortars care packages if it expires with out anyone picking it up
maps\mp\killstreaks\_airdrop::changeCrateWeight( "airdrop_assault", level.mortarConfig.id, level.mortarConfig.crateWeight );
}
else
{
//Once its picked up it needs to remain off.
game["player_holding_level_killstrek"] = true;
break;
}
}
}
}
//death and capture are sent on the same frame but death is processed first :(
wait_for_capture(dropCrate)
{
result = watch_for_air_drop_death(dropCrate);
return !IsDefined(result); //If !isdefined the captured notify was also sent.
}
watch_for_air_drop_death(dropCrate)
{
dropCrate endon("captured");
dropCrate waittill("death");
waittillframeend;
return true;
}
mortarCustomKillstreakFunc()
{
AddDebugCommand("devgui_cmd \"MP/Killstreak/Level Event:5/Care Package/" + level.mortarConfig.debugName + "\" \"set scr_devgivecarepackage " + level.mortarConfig.id + "; set scr_devgivecarepackagetype airdrop_assault\"\n");
AddDebugCommand("devgui_cmd \"MP/Killstreak/Level Event:5/" + level.mortarConfig.debugName + "\" \"set scr_givekillstreak " + level.mortarConfig.id + "\"\n");
level.killStreakFuncs[ level.mortarConfig.id ] = ::tryUseMortars;
level.killstreakWeildWeapons[ level.mortarConfig.weaponName ] = level.mortarConfig.id;
}
mortarCustomBotKillstreakFunc()
{
AddDebugCommand("devgui_cmd \"MP/Bots(Killstreak)/Level Events:5/" + level.mortarConfig.debugName + "\" \"set scr_testclients_givekillstreak " + level.mortarConfig.id + "\"\n");
maps\mp\bots\_bots_ks::bot_register_killstreak_func( level.mortarConfig.id, maps\mp\bots\_bots_ks::bot_killstreak_simple_use );
}
tryUseMortars(lifeId, streakName)
{
if(level.air_raid_active)
{
self iPrintLnBold( &"KILLSTREAKS_AIR_SPACE_TOO_CROWDED" );
return false;
}
game["player_holding_level_killstrek"] = false;
level notify( "mortar_killstreak_used", self);
if ( IsDefined( level.mortarConfig.splashName ) )
{
self thread teamPlayerCardSplash( level.mortarConfig.splashName, self );
}
return true;
}
mortars_activate_at_end_of_match()
{
level endon( "mortar_killstreak_used" );
level waittill ( "spawning_intermission" );
level.ending_flourish = true;
// this should come from the config somewhere
mortar_fire(0.1, 0.3, 2.5, 2.5, 6, level.players[0]);
}
update_mortars() // self == mortar system
{
level endon("stop_dynamic_events");
waitframe(); //allow load main to finish
//Build list of structs
if ( IsDefined( self.sourceStructs ) )
{
self.mortar_sources = getstructarray( self.sourceStructs, "targetname" );
}
else if ( IsDefined( self.sourceEnts ) )
{
self.mortar_sources = GetEntArray( self.sourceEnts, "targetname" );
}
foreach(source in self.mortar_sources)
{
if(!IsDefined(source.radius))
source.radius = 300;
}
self.mortar_targets = getstructarray( self.targetStructs, "targetname" );
foreach(mortar_target in self.mortar_targets)
{
if(!IsDefined(mortar_target.radius))
mortar_target.radius = 100;
}
while(1)
{
level.air_raid_active = false;
level.air_raid_team_called = "none";
self thread mortars_activate_at_end_of_match();
level waittill("mortar_killstreak_used", player);
level.air_raid_active = true;
level.air_raid_team_called = player.team;
self thread warning_audio();
wait ( self.launchDelay ); //Delay between siren start and mortar fire
self mortar_fire( self.launchDelayMin, self.launchDelayMax,
self.launchAirTimeMin, self.launchAirTimeMax,
self.strikeDuration,
player
);
level notify( "mortar_killstreak_end" );
}
}
warning_audio() // self == mortar system
{
if ( !IsDefined( self.warningSfxEntName ) || !IsDefined( self.warningSfx ) )
return;
if(!IsDefined( self.warning_sfx_ent ))
{
self.warning_sfx_ent = GetEnt(self.warningSfxEntName, "targetname");
}
if( IsDefined(self.warning_sfx_ent) )
{
self.warning_sfx_ent PlaySound( self.warningSfx );
wait ( self.warningSfxDuration );
self.warning_sfx_ent StopSounds();
}
}
mortar_fire(delay_min, delay_max, airtime_min, airtime_max, mortar_time_sec, owner) // self == mortar system
{
motar_strike_end_time = GetTime() + mortar_time_sec*1000;
//pick team appropriate for owner team
source_structs = self random_mortars_get_source_structs(level.air_raid_team_called);
if (source_structs.size <= 0)
{
PrintLn( "Mortars: Didn't find any sources: targetname = " + self.sourceStructs );
return;
}
level notify( "mortar_killstreak_start" );
air_raid_num = 0;
while( motar_strike_end_time>GetTime() )
{
mortars_per_loop = self.projectilePerLoop;
mortars_launched = 0;
foreach(player in level.players)
{
if(!isReallyAlive(player))
continue;
if(level.teamBased)
{
if(player.team == level.air_raid_team_called)
continue;
}
else
{
if( IsDefined( owner ) && player == owner)
continue;
}
if( player.spawnTime + 8000 > GetTime() )
continue;
vel = player GetVelocity();
mortar_air_time = airtime_min;
if ( airtime_max > airtime_min )
mortar_air_time = RandomFloatRange( airtime_min, airtime_max );
mortar_target_pos = player.origin + (vel*mortar_air_time);
nodes_near = GetNodesInRadiusSorted(mortar_target_pos,100,0,60);
foreach(node in nodes_near)
{
if(NodeExposedToSky(node))
{
source_struct = random(source_structs);
if( self random_mortars_fire( source_struct.origin, node.origin, mortar_air_time, owner, true, true, source_struct ) )
{
wait RandomFloatRange(delay_min, delay_max);
mortars_launched++;
break;
}
}
}
}
//If there weren't enough player targets, drop the rest randomly:
if (self.mortar_targets.size > 0)
{
source_structs = array_randomize(source_structs);
while(mortars_launched<mortars_per_loop)
{
source_struct = source_structs[air_raid_num];
air_raid_num++;
if(air_raid_num >= source_structs.size)
air_raid_num = 0;//loop
target_struct = random(self.mortar_targets);
mortar_air_time = airtime_min;
if ( airtime_max > airtime_min )
mortar_air_time = RandomFloatRange( airtime_min, airtime_max );
start = random_point_in_circle(source_struct.origin, source_struct.radius);
end = random_point_in_circle(target_struct.origin, target_struct.radius);
self thread random_mortars_fire( start, end, mortar_air_time, owner, false, true, source_struct );
wait RandomFloatRange(delay_min, delay_max);
mortars_launched++;
}
}
else
{
break; // no targets!
}
if ( IsDefined( self.delayBetweenVolleys ) )
{
level notify( "mortar_volleyFinished" );
wait ( self.delayBetweenVolleys );
}
}
}
random_point_in_circle(origin, radius)
{
if(radius>0)
{
rand_dir = AnglesToForward((0,RandomFloatRange(0,360),0));
rand_radius = RandomFloatRange(0, radius);
origin = origin + (rand_dir*rand_radius);
}
return origin;
}
random_mortars_fire( start_org, end_org, air_time, owner, trace_test, play_fx, sourceObj ) // self == mortar_system
{
if ( IsDefined( self.launchSfx ) )
{
PlaySoundAtPos( start_org, self.launchSfx );
}
else if ( IsDefined( self.launchSfxArray ) && self.launchSfxArray.size > 0 && IsDefined( self.launchSfxStartId ) )
{
if ( self.launchSfxStartId >= self.launchSfxArray.size )
{
self.launchSfxStartId = 0;
}
PlaySoundAtPos( start_org, self.launchSfxArray[ self.launchSfxStartId ] );
self.launchSfxStartId++;
}
gravity = (0,0,-800);
launch_dir = TrajectoryCalculateInitialVelocity(start_org, end_org, gravity, air_time);
// ugh, would be better to use the start point's angles
if ( IsDefined( self.launchVfx ) )
{
// check model?
if ( IsDefined( sourceObj ) && sourceObj.classname != "struct" )
{
PlayFXOnTag( getfx(self.launchVfx), sourceObj, "tag_origin" );
}
else
{
PlayFX( getfx(self.launchVfx), start_org, launch_dir );
}
}
if(IsDefined(trace_test) && trace_test)
{
delta_height = TrajectoryComputeDeltaHeightAtTime(launch_dir[2], -1*gravity[2], air_time/2);
trace_point = ((end_org - start_org)/2) + start_org + (0,0,delta_height);
//self thread drawLine(trace_point, end_org, 60*10, (0,1,0));
if(BulletTracePassed(trace_point, end_org, false, undefined))
{
thread random_mortars_fire_run( start_org, end_org, air_time, owner, launch_dir, play_fx );
return true;
}
else
{
return false;
}
}
random_mortars_fire_run( start_org, end_org, air_time, owner, launch_dir, play_fx );
}
random_mortars_fire_run( start_org, end_org, air_time, owner, launch_dir, play_fx ) // self == mortar system
{
dirt_effect_radius = 350;
mortar_model = createProjectileEntity(start_org, self.model);
mortar_model.in_use = true;
waitframe();//Model may have just spawned
if ( IsDefined( self.trailVfx ) )
{
PlayFXOnTag( getfx( self.trailVfx ), mortar_model, "tag_fx");
}
mortar_model.angles = VectorToAngles(launch_dir) * (-1,1,1);
if ( IsDefined( self.incomingSfx ) )
{
thread playSoundAtPosInTime( self.incomingSfx, end_org, air_time - 2.0 );
}
mortar_model MoveGravity(launch_dir, air_time - 0.05); // dial back by a frame so the mortar/killcam doesn't go below ground.
if ( IsDefined( self.rotateProjectiles ) && self.rotateProjectiles )
{
mortar_model RotateVelocity( randomvectorrange( self.minRotatation, self.maxRotation ), air_time, 2, 0 );
}
mortar_model waittill("movedone");
if(level.createFX_enabled && !IsDefined(level.players))
level.players = [];
if( IsDefined(owner) )
{
mortar_model RadiusDamage(end_org, 250, 750, 500, owner, "MOD_EXPLOSIVE", self.weaponName );
}
else
{
mortar_model RadiusDamage(end_org, 140, 5, 5, undefined, "MOD_EXPLOSIVE", self.weaponName );
}
PlayRumbleOnPosition("artillery_rumble", end_org);
//Dirt effect on Players:
dirt_effect_radiusSq = dirt_effect_radius * dirt_effect_radius;
foreach ( player in level.players )
{
if ( player isUsingRemote() )
{
continue;
}
if ( DistanceSquared( end_org, player.origin ) > dirt_effect_radiusSq )
{
continue;
}
if ( player DamageConeTrace( end_org ) )
{
player thread maps\mp\gametypes\_shellshock::dirtEffect( end_org );
}
}
if( play_fx )
{
if ( IsDefined( self.impactVfx ) )
{
PlayFX( getfx( self.impactVfx ), end_org);
}
if ( IsDefined( self.impactSfx ) )
{
PlaySoundAtPos( end_org, self.impactSfx ) ;
}
}
mortar_model Delete();
}
playSoundAtPosInTime( sound, pos, delay )
{
wait ( delay );
playSoundAtPos( pos, sound );
}
createProjectileEntity( origin, modelName )
{
mortar_model = Spawn("script_model", origin);
mortar_model SetModel( modelName );
return mortar_model;
}
random_mortars_get_source_structs( owner_team ) // self == mortar system
{
source_structs = [];
if( level.teamBased )
{
foreach( struct in self.mortar_sources )
{
if (IsDefined(struct.script_team) && struct.script_team == owner_team )
source_structs[source_structs.size] = struct;
}
}
//No approprate team source, just use any:
if (source_structs.size == 0)
source_structs = self.mortar_sources;
return source_structs;
}

View File

@ -0,0 +1,107 @@
#include maps\mp\_utility;
#include common_scripts\utility;
KILLSTREAK_NAME = "mrsiartillery";
init()
{
level.killstreakFuncs[ KILLSTREAK_NAME ] = ::tryUseStrike;
configData = SpawnStruct();
configData.weaponName = "airdrop_marker_mp";
configData.projectileName = "mrsiartillery_projectile_mp";
// configData.explodeVfx = LoadFX( "fx/explosions/aerial_explosion" );
// configData.model = "projectile_icbm_missile";
// configData.model = "mil_ammo_case_1_open";
//configData.impactVfx = LoadFX( "fx/explosions/wall_explosion_pm_a" );
configData.numStrikes = 6;
configData.initialDelay = 1.0;
configData.minFireDelay = 0.375;
configdata.maxFireDelay = 0.5;
configData.strikeRadius = 150;
if ( !IsDefined( level.killstreakConfigData ) )
{
level.killstreakConfigData = [];
}
level.killstreakConfigData[ KILLSTREAK_NAME ] = configData;
}
tryUseStrike( lifeId, streakName )
{
configData = level.killStreakConfigData[ KILLSTREAK_NAME ];
result = self maps\mp\killstreaks\_designator_grenade::designator_Start( KILLSTREAK_NAME, configData.weaponName, ::onTargetAcquired );
if ( ( !IsDefined( result ) || !result ) )
{
return false;
}
else
{
// self maps\mp\_matchdata::logKillstreakEvent( BOX_TYPE, self.origin );
return true;
}
}
onTargetAcquired( killstreakName, designatorEnt ) // self == player
{
configData = level.killStreakConfigData[ killstreakName ];
owner = designatorEnt.owner;
endPos = designatorEnt.origin;
designatorEnt Detonate();
doStrike( owner, killstreakName, owner.origin, endPos );
}
doStrike( owner, killstreakName, startPosition, endPosition )
{
configData = level.killStreakConfigData[ killstreakName ];
// play some firing sounds
dir = endPosition - startPosition;
xyDir = ( dir[0], dir[1], 0);
dir = VectorNormalize( dir );
strikeTarget = endPosition;
// shift up slightly to not intersect with ground
strikeOrigin = maps\mp\killstreaks\_killstreaks::findUnobstructedFiringPoint( owner, endPosition + (0, 0, 10), 10000 );
if ( IsDefined( strikeOrigin ) )
{
IPrintLn( "Firing Motar!" );
// play sounds
wait( configData.initialDelay );
// always hit the target directly once
wait( RandomFloatRange( configData.minFireDelay, configData.maxFireDelay ) );
projectile = MagicBullet( configData.projectileName, strikeOrigin, strikeTarget, owner );
// then scatter around
for ( i = 1; i < configData.numStrikes; i++ )
{
wait( RandomFloatRange( configData.minFireDelay, configData.maxFireDelay ) );
// pick target points
randomTarget = pickRandomTargetPoint( strikeTarget, configData.strikeRadius );
projectile = MagicBullet( configData.projectileName, strikeOrigin, randomTarget, owner );
}
}
else
{
IPrintLn( "Mortar LOS blocked!" );
}
}
pickRandomTargetPoint( targetPoint, strikeRadius )
{
x = RandomFloatRange( -1 * strikeRadius, strikeRadius );
y = RandomFloatRange( -1 * strikeRadius, strikeRadius );
return targetPoint + (x, y, 0);
}

View File

@ -0,0 +1,675 @@
#include common_scripts\utility;
#include maps\mp\_utility;
#include maps\mp\killstreaks\_emp_common;
// the nuke ended the game in MW2, for MW3 it will be an MOAB, not end the game but kill the other team and emp them for 60 seconds, it will also change the visionset for the level
init()
{
level.nukeVisionSet = "aftermath_post";
level._effect[ "nuke_player" ] = loadfx( "fx/explosions/player_death_nuke" );
level._effect[ "nuke_flash" ] = loadfx( "fx/explosions/player_death_nuke_flash" );
level._effect[ "nuke_aftermath" ] = loadfx( "fx/dust/nuke_aftermath_mp" );
//game["strings"]["nuclear_strike"] = &"KILLSTREAKS_TACTICAL_NUKE";
level.killstreakFuncs["nuke"] = ::tryUseNuke;
SetDvarIfUninitialized( "scr_nukeTimer", 10 );
SetDvarIfUninitialized( "scr_nukeCancelMode", 0 );
level.nukeTimer = getDvarInt( "scr_nukeTimer" );
level.cancelMode = getDvarInt( "scr_nukeCancelMode" );
if( level.multiTeamBased )
{
for( i = 0; i < level.teamNameList.size; i++ )
{
level.teamNukeEMPed[level.teamNameList[i]] = false;
}
}
else
{
level.teamNukeEMPed["allies"] = false;
level.teamNukeEMPed["axis"] = false;
}
level.nukeEmpTimeout = 60.0;
level.nukeEmpTimeRemaining = int( level.nukeEmpTimeout );
level.nukeInfo = spawnStruct();
level.nukeInfo.xpScalar = 2;
level.nukeDetonated = undefined;
level thread onPlayerConnect();
/#
SetDevDvarIfUninitialized( "scr_nuke_empTimeout", 60.0 );
SetDevDvarIfUninitialized( "scr_nukeDistance", 5000 );
SetDevDvarIfUninitialized( "scr_nukeEndsGame", true );
SetDevDvarIfUninitialized( "scr_nukeDebugPosition", false );
#/
}
tryUseNuke( lifeId, streakName, allowCancel )
{
if( isDefined( level.nukeIncoming ) )
{
self iPrintLnBold( &"KILLSTREAKS_NUKE_ALREADY_INBOUND" );
return false;
}
if ( self isUsingRemote() && ( !isDefined( level.gtnw ) || !level.gtnw ) )
return false;
if ( !isDefined( allowCancel ) )
allowCancel = true;
self thread doNuke( allowCancel );
self notify( "used_nuke" );
self maps\mp\_matchdata::logKillstreakEvent( "nuke", self.origin );
return true;
}
delaythread_nuke( delay, func )
{
level endon ( "nuke_cancelled" );
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( delay );
thread [[ func ]]();
}
doNuke( allowCancel )
{
level endon ( "nuke_cancelled" );
level.nukeInfo.player = self;
level.nukeInfo.team = self.pers["team"];
level.nukeIncoming = true;
//maps\mp\gametypes\_gamelogic::pauseTimer();
//level.timeLimitOverride = true;
//setGameEndTime( int( gettime() + (level.nukeTimer * 1000) ) );
// capture the previous bomb timer value so we can go back to it after the nuke goes off
level.prevUIBombTimer = int( GetOmnvar( "ui_bomb_timer" ) );
SetOmnvar( "ui_bomb_timer", 4 ); // Nuke sets '4' to avoid briefcase icon showing
if( level.teambased )
{
thread teamPlayerCardSplash( "used_nuke", self, self.team );
}
else
{
if( !level.hardcoreMode )
self IPrintLnBold( &"KILLSTREAKS_FRIENDLY_TACTICAL_NUKE" );
}
if(!IsDefined(level.doNuke_fx) || ![[level.doNuke_fx]]() )
{
if( !IsDefined( level.nuke_soundObject ) )
{
level.nuke_soundObject = Spawn( "script_origin", (0,0,1) );
level.nuke_soundObject hide();
}
level thread delaythread_nuke( (level.nukeTimer - 3.3), ::nukeSoundIncoming );
level thread delaythread_nuke( level.nukeTimer, ::nukeSoundExplosion );
level thread delaythread_nuke( level.nukeTimer, ::nukeSlowMo );
level thread delaythread_nuke( level.nukeTimer, ::nukeEffects );
level thread delaythread_nuke( (level.nukeTimer + 0.25), ::nukeVision );
level thread delaythread_nuke( (level.nukeTimer + 1.5), ::nukeDeath );
level thread delaythread_nuke( (level.nukeTimer + 1.5), ::nukeEarthquake );
level thread nukeAftermathEffect();
if ( level.cancelMode && allowCancel )
level thread cancelNukeOnDeath( self );
}
level thread update_ui_timers();
// need objects to play sound off of, i'm keeping them on level so we don't spawn them more than once if multiple nukes get called in a match
if( !IsDefined( level.nuke_clockObject ) )
{
level.nuke_clockObject = Spawn( "script_origin", (0,0,0) );
level.nuke_clockObject hide();
}
nukeTimer = level.nukeTimer;
while( nukeTimer > 0 )
{
// TODO: get a new sound for this so we don't remind people of the old nuke
level.nuke_clockObject playSound( "ui_mp_kem_timer" );
wait( 1.0 );
nukeTimer--;
}
}
// used in aliens mode
doNukeSimple()
{
level.nukeInfo.player = self;
level.nukeInfo.team = self.pers["team"];
level.nukeIncoming = true;
level thread delaythread_nuke( (level.nukeTimer - 3.3), ::nukeSoundIncoming );
level thread delaythread_nuke( level.nukeTimer, ::nukeSoundExplosion );
level thread delaythread_nuke( level.nukeTimer, ::nukeSlowMo );
level thread delaythread_nuke( level.nukeTimer, ::nukeEffects );
level thread delaythread_nuke( (level.nukeTimer + 0.25), ::nukeVision );
level thread delaythread_nuke( (level.nukeTimer + 1.5), ::nukeDeathSimple );
level thread delaythread_nuke( (level.nukeTimer + 1.5), ::nukeEarthquake );
// need objects to play sound off of, i'm keeping them on level so we don't spawn them more than once if multiple nukes get called in a match
if( !IsDefined( level.nuke_soundObject ) )
{
level.nuke_soundObject = Spawn( "script_origin", (0,0,1) );
level.nuke_soundObject hide();
}
}
nukeDeathSimple()
{
level notify( "nuke_death" );
}
cancelNukeOnDeath( player )
{
player waittill_any( "death", "disconnect" );
if ( isDefined( player ) && level.cancelMode == 2 )
player thread maps\mp\killstreaks\_emp::EMP_Use( 0, 0 );
//maps\mp\gametypes\_gamelogic::resumeTimer();
//level.timeLimitOverride = false;
nukeClearTimer();
level.nukeIncoming = undefined;
level notify ( "nuke_cancelled" );
}
nukeSoundIncoming()
{
level endon ( "nuke_cancelled" );
if( IsDefined( level.nuke_soundObject ) )
level.nuke_soundObject PlaySound( "nuke_incoming" );
}
nukeSoundExplosion()
{
level endon ( "nuke_cancelled" );
if( IsDefined( level.nuke_soundObject ) )
{
level.nuke_soundObject PlaySound( "nuke_explosion" );
level.nuke_soundObject PlaySound( "nuke_wave" );
}
}
nukeClearTimer()
{
uiBombTimer = 0;
if( IsDefined( level.prevUIBombTimer ) )
uiBombTimer = level.prevUIBombTimer;
SetOmnvar( "ui_bomb_timer", uiBombTimer );
}
nukeEffects()
{
level endon ( "nuke_cancelled" );
nukeClearTimer();
//setGameEndTime( 0 );
level.nukeDetonated = true;
foreach( player in level.players )
{
level thread nukeEffect( player );
//player.nuked = true;
}
}
nukeEffect( player )
{
level endon ( "nuke_cancelled" );
player endon( "disconnect" );
waitframe();
nukeLoc = undefined;
dirToNuke = undefined;
// nuke location override: from aliens
if ( !IsDefined( level.nukeLoc ) )
{
yawAngle = (0, player.angles[1], 0);
dirToNuke = AnglesToForward( yawAngle );
nukeDistance = 5000;
/# nukeDistance = getDvarInt( "scr_nukeDistance" ); #/
nukeLoc = player.origin + ( dirToNuke * nukeDistance );
}
else
{
nukeLoc = level.nukeLoc;
}
effectFwd = undefined;
effectUp = (0, 0, 1);
if ( !IsDefined( level.nukeAngles ) )
{
effectFwd = AnglesToRight( (0, (player.angles[1] + 180), 0) );
}
else
{
effectFwd = AnglesToForward( level.nukeAngles );
effectUp = AnglesToUp( level.nukeAngles );
}
/#
if ( getDvarInt( "scr_nukeDebugPosition" ) )
{
thread draw_line_for_time( nukeLoc, nukeLoc + 500 * effectUp, 1, 0, 0, 20 );
thread draw_line_for_time( nukeLoc, nukeLoc + 500 * effectFwd, 0, 1, 0, 20 );
}
#/
// 2013-08-29 wallace: in FXed, forward direction is mushroom cloud rising in the air, left direction is shooting towards the player
// have to do some
fxObj = SpawnFXForClient( level._effect[ "nuke_flash" ], nukeLoc, player, effectUp, effectFwd );
TriggerFX( fxObj );
// 30s is a little arbitrary
player thread cleanupNukeEffect( fxObj, 30 );
}
cleanupNukeEffect( fxObj, lifetime )
{
self waitForTimeOrNotify( lifetime, "disconnect" );
fxObj Delete();
}
nukeAftermathEffect()
{
level endon ( "nuke_cancelled" );
level waittill ( "spawning_intermission" );
afermathEnt = getEntArray( "mp_global_intermission", "classname" );
afermathEnt = afermathEnt[0];
up = anglestoup( afermathEnt.angles );
right = anglestoright( afermathEnt.angles );
PlayFX( level._effect[ "nuke_aftermath" ], afermathEnt.origin, up, right );
}
nukeSlowMo()
{
level endon ( "nuke_cancelled" );
//SetSlowMotion( <startTimescale>, <endTimescale>, <deltaTime> )
SetSlowMotion( 1.0, 0.25, 0.5 );
level waittill( "nuke_death" );
SetSlowMotion( 0.25, 1, 2.0 );
}
nukeVision()
{
level endon ( "nuke_cancelled" );
level.nukeVisionInProgress = true;
VisionSetPostApply( "mpnuke", 3 );
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( 2 );
VisionSetPostApply("nuke_global_flash", .1);
SetExpFog(0, 956, 0.72, 0.61, 0.39, 0.968, 0.85, 1, 0.298, 0.273, 0.266, .25, (0, 0, -1), 84, 118, 2.75, .984, 124, 100);
SetDvar( "r_materialBloomHQScriptMasterEnable", 0 );
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( .5 );
level notify ( "nuke_aftermath_post_started" );
VisionSetPostApply("aftermath_post", .5);
level waittill( "nuke_death" );
level thread updateNukeVisionOnHostMigration();
level setNukeAftermathVision( 5 );
}
nukeDeath()
{
level endon ( "nuke_cancelled" );
level endon ( "game_ended" );
level notify( "nuke_death" );
maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone();
foreach( character in level.characters )
{
if( nukeCanKill(character) )
{
if( isPlayer(character) )
{
character.nuked = true;
if ( isReallyAlive( character ) )
{
character thread maps\mp\gametypes\_damage::finishPlayerDamageWrapper( level.nukeInfo.player, level.nukeInfo.player, 999999, 0, "MOD_EXPLOSIVE", "nuke_mp", character.origin, (0,0,1), "none", 0, 0 );
}
}
else
{
character maps\mp\agents\_agents::agent_damage_finished( level.nukeInfo.player, level.nukeInfo.player, 999999, 0, "MOD_EXPLOSIVE", "nuke_mp", character.origin, (0,0,1), "none", 0 );
}
}
}
// emp jam them after death, if we do before then the timing is off
level thread nuke_EMPJam();
// since the nuke death happened, the nuke is no longer incoming
level.nukeIncoming = undefined;
}
nukeCanKill(character)
{
if(!IsDefined(level.nukeInfo))
return false;
if( level.teambased )
{
if( IsDefined( level.nukeInfo.team ) && character.team == level.nukeInfo.team )
return false;
}
// ffa, don't kill the player who called it
else
{
isKillstreakPlayer = IsDefined( level.nukeInfo.player ) && (character == level.nukeInfo.player);
ownerIsPlayer = IsDefined( level.nukeInfo.player ) && IsDefined(character.owner) && (character.owner == level.nukeInfo.player);
if( isKillstreakPlayer || ownerIsPlayer )
return false;
}
return true;
}
nukeEarthquake()
{
level endon ( "nuke_cancelled" );
level waittill( "nuke_death" );
// TODO: need to get a different position to call this on
//earthquake( 0.6, 10, nukepos, 100000 );
//foreach( player in level.players )
//player PlayRumbleOnEntity( "damage_heavy" );
}
//waitForNukeCancel()
//{
// self waittill( "cancel_location" );
// self setblurforplayer( 0, 0.3 );
//}
//
//endSelectionOn( waitfor )
//{
// self endon( "stop_location_selection" );
// self waittill( waitfor );
// self thread stopNukeLocationSelection( (waitfor == "disconnect") );
//}
//
//endSelectionOnGameEnd()
//{
// self endon( "stop_location_selection" );
// level waittill( "game_ended" );
// self thread stopNukeLocationSelection( false );
//}
//
//stopNukeLocationSelection( disconnected )
//{
// if ( !disconnected )
// {
// self setblurforplayer( 0, 0.3 );
// self endLocationSelection();
// self.selectingLocation = undefined;
// }
// self notify( "stop_location_selection" );
//}
nuke_EMPJam()
{
level endon ( "game_ended" );
if ( level.teamBased )
{
level nukeEmpJamTeam( getOtherTeam( level.nukeInfo.team ) );
}
else
{
level.teamNukeEMPed[ level.nukeInfo.team ] = true;
level.teamNukeEMPed[ getOtherTeam( level.nukeInfo.team ) ] = true;
nukeEmpJamPlayers( level.nukeInfo.player );
}
}
nukeEmpJamTeam( teamName )
{
level endon ( "game_ended" );
level notify( "nuke_EMPJam" );
level endon( "nuke_EMPJam" );
assert( teamName == "allies" || teamName == "axis" );
level.teamNukeEMPed[teamName] = true;
foreach ( player in level.players )
{
if ( player shouldPlayerBeAffectedByEMP() )
{
player applyPerPlayerEMPEffects();
}
}
level notify ( "nuke_emp_update" );
level maps\mp\killstreaks\_jammer::destroyGroundObjects( level.nukeInfo.player, teamName );
level maps\mp\killstreaks\_air_superiority::destroyActiveVehicles( level.nukeInfo.player, teamName );
// WAIT
/#
level.empTimeout = GetDvarFloat( "scr_nuke_empTimeout" );
#/
level thread keepNukeEMPTimeRemaining();
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( level.nukeEmpTimeout );
// TURN OF EMP EFFECTS
level.teamNukeEMPed[teamName] = false;
foreach ( player in level.players )
{
if ( player.team == teamName
&& !(player shouldPlayerBeAffectedByEMP() )
)
{
player removePerPlayerEMPEffects();
player.nuked = undefined;
}
}
level notify ( "nuke_emp_update" );
level notify ( "nuke_emp_ended" );
}
nukeEmpJamPlayers( owner )
{
level notify ( "nuke_EMPJam" );
level endon ( "nuke_EMPJam" );
if ( !IsDefined( owner ) )
{
return;
}
foreach ( player in level.players )
{
if ( player shouldPlayerBeAffectedByEMP() )
{
player applyPerPlayerEMPEffects();
}
}
level notify ( "nuke_emp_update" );
// level.nukeInfo.player thread empPlayerFFADisconnect();
level maps\mp\killstreaks\_jammer::destroyGroundObjects( level.nukeInfo.player );
level maps\mp\killstreaks\_air_superiority::destroyActiveVehicles( level.nukeInfo.player );
// 2013-06-28 wallace: not sure why this sends an update
// the team versions only send it on Start and Stop
// level notify ( "emp_update" );
// WAIT
/#
level.empTimeout = GetDvarFloat( "scr_emp_timeout" );
#/
level thread keepNukeEMPTimeRemaining();
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( level.nukeEmpTimeout );
// STOP
level.nukeInfo.player = undefined;
foreach ( player in level.players )
{
if ( (!IsDefined( owner ) || player != owner) // it is possible for the owner to disconnect during the jam sequence
&& !(player shouldPlayerBeAffectedByEMP() )
)
{
player removePerPlayerEMPEffects();
}
}
level notify ( "nuke_emp_update" );
level notify ( "nuke_emp_ended" );
}
keepNukeEMPTimeRemaining()
{
level notify( "keepNukeEMPTimeRemaining" );
level endon( "keepNukeEMPTimeRemaining" );
level endon( "nuke_emp_ended" );
// we need to know how much time is left for the unavailable string
level.nukeEmpTimeRemaining = int( level.nukeEmpTimeout );
while( level.nukeEmpTimeRemaining )
{
wait( 1.0 );
level.nukeEmpTimeRemaining--;
}
}
onPlayerConnect()
{
for(;;)
{
level waittill("connected", player);
player thread onPlayerSpawned();
}
}
onPlayerSpawned()
{
self endon("disconnect");
while ( true )
{
self waittill( "spawned_player" );
// emp effects are already handled in _jammer.gsc; no need to repeat
/*
if ( self shouldPlayerBeAffectedByEMP() )
{
self applyPerPlayerEMPEffects();
}
else
{
self stopEmpJamSequenceImmediate();
}
*/
// make sure the vision set stays on between deaths
if( IsDefined( level.nukeDetonated ) )
{
self thread setVisionForPlayer();
}
}
}
setVisionForPlayer()
{
wait( 0.1 );
self VisionSetPostApplyForPlayer( level.nukeVisionSet, 0 );
}
update_ui_timers()
{
level endon ( "game_ended" );
level endon ( "disconnect" );
level endon ( "nuke_cancelled" );
level endon ( "nuke_death" );
nukeEndMilliseconds = (level.nukeTimer * 1000) + gettime();
SetOmnvar( "ui_nuke_end_milliseconds", nukeEndMilliseconds );
level waittill( "host_migration_begin" );
timePassed = maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone();
if ( timePassed > 0 )
{
SetOmnvar( "ui_nuke_end_milliseconds", nukeEndMilliseconds + timePassed );
}
}
updateNukeVisionOnHostMigration()
{
level endon ( "game_ended" );
while ( true )
{
level waittill( "host_migration_end" );
level setNukeAftermathVision(0);
}
}
setNukeAftermathVision( transitionTime )
{
// we should probably move these callbacks into level specific fog files for aftermath
if( IsDefined(level.nukeDeathVisionFunc) )
{
level thread [[ level.nukeDeathVisionFunc ]]();
}
else
{
VisionSetPostApply( level.nukeVisionSet, transitionTime );
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,155 @@
#include maps\mp\_utility;
#include common_scripts\utility;
/*
Perks as killstreaks:
The player will earn the killstreak and automatically get the perk.
This has been repurposed to be used for very simple killstreaks that are perk-like.
*/
KILLSTREAK_GIMME_SLOT = 0;
KILLSTREAK_SLOT_1 = 1;
KILLSTREAK_SLOT_2 = 2;
KILLSTREAK_SLOT_3 = 3;
KILLSTREAK_ALL_PERKS_SLOT = 4;
KILLSTREAK_STACKING_START_SLOT = 5;
init()
{
level.killStreakFuncs[ "specialty_fastsprintrecovery_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_fastreload_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_lightweight_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_marathon_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_stalker_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_reducedsway_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_quickswap_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_pitcher_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_bulletaccuracy_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_quickdraw_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_sprintreload_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_silentkill_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_blindeye_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_gpsjammer_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_quieter_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_incog_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_paint_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_scavenger_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_detectexplosive_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_selectivehearing_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_comexp_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_falldamage_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_regenfaster_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_sharp_focus_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_stun_resistance_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "_specialty_blastshield_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_gunsmith_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_extraammo_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_extra_equipment_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_extra_deadly_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_extra_attachment_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_explosivedamage_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_gambler_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_hardline_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_twoprimaries_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_boom_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "specialty_deadeye_ks" ] = ::tryUsePerkStreak;
level.killStreakFuncs[ "all_perks_bonus" ] = ::tryUseAllPerks;
level.killStreakFuncs[ "speed_boost" ] = ::tryUseSpeedBoost;
level.killStreakFuncs[ "refill_grenades" ] = ::tryUseRefillGrenades;
level.killStreakFuncs[ "refill_ammo" ] = ::tryUseRefillAmmo;
level.killStreakFuncs[ "regen_faster" ] = ::tryUseRegenFaster;
}
tryUseSpeedBoost( lifeId, streakName )
{
self doKillstreakFunctions( "specialty_juiced", "speed_boost" );
return true;
}
tryUseRefillGrenades( lifeId, streakName )
{
self doKillstreakFunctions( "specialty_refill_grenades", "refill_grenades" );
return true;
}
tryUseRefillAmmo( lifeId, streakName )
{
self doKillstreakFunctions( "specialty_refill_ammo", "refill_ammo" );
return true;
}
tryUseRegenFaster( lifeId, streakName )
{
self doKillstreakFunctions( "specialty_regenfaster", "regen_faster" );
return true;
}
tryUseAllPerks( lifeId, streakName )
{
// left blank on purpose
return true;
}
tryUsePerkStreak( lifeId, streakName )
{
AssertEx( IsDefined( streakName ), "tryUsePerkStreak needs a streakName instead of undefined" );
perk = strip_suffix( streakName, "_ks" );
self doPerkFunctions( perk );
return true;
}
doPerkFunctions( perkName )
{
self givePerk( perkName, false );
self thread watchDeath( perkName );
self thread checkForPerkUpgrade( perkName );
if( perkName == "specialty_hardline" )
self maps\mp\killstreaks\_killstreaks::setStreakCountToNext();
self maps\mp\_matchdata::logKillstreakEvent( perkName + "_ks", self.origin );
}
doKillstreakFunctions( perkName, killstreakEvent )
{
self givePerk( perkName, false );
if( IsDefined( killstreakEvent ) )
self maps\mp\_matchdata::logKillstreakEvent( killstreakEvent, self.origin );
}
watchDeath( perkName )
{
self endon( "disconnect" );
self waittill( "death" );
self _unsetPerk( perkName );
//self _unsetExtraPerks( perkName );
}
checkForPerkUpgrade( perkName )
{
// check for pro version
perk_upgrade = self maps\mp\gametypes\_class::getPerkUpgrade( perkName );
if( perk_upgrade != "specialty_null" )
{
self givePerk( perk_upgrade, false );
self thread watchDeath( perk_upgrade );
}
}
isPerkStreakOn( streakName ) // self == player
{
// return whether the perk is available right now or not
for( i = KILLSTREAK_SLOT_1; i < KILLSTREAK_SLOT_3 + 1; i++ )
{
if( IsDefined( self.pers[ "killstreaks" ][ i ].streakName ) && self.pers[ "killstreaks" ][ i ].streakName == streakName )
{
if( self.pers[ "killstreaks" ][ i ].available )
return true;
}
}
return false;
}

View File

@ -0,0 +1,705 @@
#include maps\mp\_utility;
#include maps\mp\gametypes\_hud_util;
#include common_scripts\utility;
init()
{
if ( !IsDefined( level.placeableConfigs ) )
{
level.placeableConfigs = [];
}
}
givePlaceable( streakName ) // self == player
{
placeable = self createPlaceable( streakName );
// returning from this streak activation seems to strip this?
// manually removing and restoring
self removePerks();
self.carriedItem = placeable;
result = self onBeginCarrying( streakName, placeable, true );
self.carriedItem = undefined;
self restorePerks();
// if the placeable exist, then it was placed
return ( IsDefined( placeable ) );
}
createPlaceable( streakName )
{
if( IsDefined( self.isCarrying ) && self.isCarrying )
return;
config = level.placeableConfigs[ streakName ];
obj = Spawn( "script_model", self.origin );
obj SetModel( config.modelBase );
obj.angles = self.angles;
obj.owner = self;
obj.team = self.team;
obj.config = config;
obj.firstPlacement = true;
/*
obj = SpawnTurret( "misc_turret", self.origin + ( 0, 0, 25 ), "sentry_minigun_mp" );
obj.angles = self.angles;
obj.owner = self;
obj SetModel( config.modelBase );
obj MakeTurretInoperable();
obj SetTurretModeChangeWait( true );
obj SetMode( "sentry_offline" );
obj MakeUnusable();
obj SetSentryOwner( self );
*/
// inits happen here
if ( IsDefined( config.onCreateDelegate ) )
{
obj [[ config.onCreateDelegate ]]( streakName );
}
obj deactivate( streakName );
obj thread timeOut( streakName );
obj thread handleUse( streakName );
obj thread onKillstreakDisowned( streakName );
obj thread onGameEnded( streakName );
obj thread createBombSquadModel( streakName );
return obj;
}
handleUse( streakName ) // self == placeable
{
self endon ( "death" );
level endon ( "game_ended" );
while ( true )
{
self waittill ( "trigger", player );
assert( player == self.owner );
assert( !IsDefined( self.carriedBy ) );
if ( !isReallyAlive( player ) )
continue;
if ( IsDefined( self GetLinkedParent() ) )
{
self Unlink();
}
// why does the IMS create a second one?
player onBeginCarrying( streakName, self, false );
}
}
// setCarrying
onBeginCarrying( streakName, placeable, allowCancel ) // self == player
{
self endon ( "death" );
self endon ( "disconnect" );
assert( isReallyAlive( self ) );
placeable thread onCarried( streakName, self );
self _disableWeapon();
if ( !IsAI(self) ) // Bots handle these internally
{
self notifyOnPlayerCommand( "placePlaceable", "+attack" );
self notifyOnPlayerCommand( "placePlaceable", "+attack_akimbo_accessible" ); // support accessibility control scheme
self notifyOnPlayerCommand( "cancelPlaceable", "+actionslot 4" );
if( !level.console )
{
self notifyOnPlayerCommand( "cancelPlaceable", "+actionslot 5" );
self notifyOnPlayerCommand( "cancelPlaceable", "+actionslot 6" );
self notifyOnPlayerCommand( "cancelPlaceable", "+actionslot 7" );
}
}
while (true)
{
result = waittill_any_return( "placePlaceable", "cancelPlaceable", "force_cancel_placement" );
// object was deleted
if ( !IsDefined( placeable ) )
{
self _enableWeapon();
return true;
}
// !!! 2013-08-08 wallace: this force cancel problem is really ugly
// it's also being used to indicate player wading in water, which we should use the "bad placement" model instead of canceling outright. But it's too late to fix now.
else if ( (result == "cancelPlaceable" && allowCancel)
|| result == "force_cancel_placement" )
{
placeable onCancel( streakName, result == "force_cancel_placement" && !IsDefined( placeable.firstPlacement ) );
return false;
}
else if ( placeable.canBePlaced )
{
placeable thread onPlaced( streakName );
self _enableWeapon();
return true;
}
}
}
onCancel( streakName, playDestroyVfx ) // self == placeable
{
if( IsDefined( self.carriedBy ) )
{
owner = self.carriedBy;
owner ForceUseHintOff();
owner.isCarrying = undefined;
owner.carriedItem = undefined;
owner _enableWeapon();
}
if( IsDefined( self.bombSquadModel ) )
{
self.bombSquadModel Delete();
}
if ( IsDefined( self.carriedObj ) )
{
self.carriedObj Delete();
}
config = level.placeableConfigs[ streakName ];
if ( IsDefined( config.onCancelDelegate ) )
{
self [[ config.onCancelDelegate ]]( streakName );
}
if ( IsDefined( playDestroyVfx ) && playDestroyVfx )
{
self maps\mp\gametypes\_weapons::equipmentDeleteVfx();
}
self Delete();
}
onPlaced( streakName ) // self == placeable
{
config = level.placeableConfigs[ streakName ];
self.origin = self.placementOrigin;
self.angles = self.carriedObj.angles;
self PlaySound( config.placedSfx );
self showPlacedModel( streakName );
if ( IsDefined( config.onPlacedDelegate ) )
{
self [[ config.onPlacedDelegate ]]( streakName );
}
self setCursorHint( "HINT_NOICON" );
self setHintString( config.hintString );
owner = self.owner;
owner ForceUseHintOff();
owner.isCarrying = undefined;
self.carriedBy = undefined;
self.isPlaced = true;
self.firstPlacement = undefined;
if ( IsDefined( config.headIconHeight ) )
{
if ( level.teamBased )
{
self maps\mp\_entityheadicons::setTeamHeadIcon( self.team, (0,0,config.headIconHeight) );
}
else
{
self maps\mp\_entityheadicons::setPlayerHeadIcon( owner, (0,0,config.headIconHeight) );
}
}
self thread handleDamage( streakName );
self thread handleDeath( streakName );
self MakeUsable();
self make_entity_sentient_mp( self.owner.team );
if ( IsSentient( self ) )
{
self SetThreatBiasGroup( "DogsDontAttack" );
}
foreach ( player in level.players )
{
if( player == owner )
self EnablePlayerUse( player );
else
self DisablePlayerUse( player );
}
if( IsDefined( self.shouldSplash ) )
{
level thread teamPlayerCardSplash( config.splashName, owner );
self.shouldSplash = false;
}
// Moving platforms.
data = SpawnStruct();
data.linkParent = self.moving_platform;
data.playDeathFx = true;
data.endonString = "carried";
if ( IsDefined( config.onMovingPlatformCollision ) )
{
data.deathOverrideCallback = config.onMovingPlatformCollision;
}
self thread maps\mp\_movers::handle_moving_platforms( data );
self thread watchPlayerConnected();
self notify ( "placed" );
self.carriedObj Delete();
self.carriedObj = undefined;
}
onCarried( streakName, carrier ) // self == placeable
{
config = level.placeableConfigs[ streakName ];
assert( isPlayer( carrier ) );
assertEx( carrier == self.owner, "_placeable::onCarried: specified carrier does not own this ims" );
self.carriedObj = carrier createCarriedObject( streakName );
// self SetModel( config.modelPlacement );
self.isPlaced = undefined;
self.carriedBy = carrier;
carrier.isCarrying = true;
self deactivate( streakName );
self hidePlacedModel( streakName );
if ( IsDefined( config.onCarriedDelegate ) )
{
self [[ config.onCarriedDelegate ]]( streakName );
}
self thread updatePlacement( streakName, carrier );
self thread onCarrierDeath( streakName, carrier );
self notify ( "carried" );
}
updatePlacement( streakName, carrier ) // self == placeable
{
carrier endon ( "death" );
carrier endon ( "disconnect" );
level endon ( "game_ended" );
self endon ( "placed" );
self endon ( "death" );
self.canBePlaced = true;
prevCanBePlaced = -1; // force initial update
config = level.placeableConfigs[ streakName ];
// allow the visuals be raised up slightly (e.g. iw6 Sat Com, so that player can see it)
placementOffset = (0 ,0, 0);
if ( IsDefined( config.placementOffsetZ ) )
{
placementOffset = (0 ,0 ,config.placementOffsetZ);
}
carriedObj = self.carriedObj;
while ( true )
{
placement = carrier CanPlayerPlaceSentry( true, config.placementRadius );
// NOTE TO SELF: Talk to Simon C about how to get vertical offset / additional rotation working with client prediction
self.placementOrigin = placement[ "origin" ];
carriedObj.origin = self.placementOrigin + placementOffset;
carriedObj.angles = placement[ "angles" ];
self.canBePlaced = carrier IsOnGround()
&& placement[ "result" ]
&& ( abs(self.placementOrigin[2] - carrier.origin[2]) < config.placementHeightTolerance );
if ( isdefined( placement[ "entity" ] ) )
{
self.moving_platform = placement[ "entity" ];
}
else
{
self.moving_platform = undefined;
}
if ( self.canBePlaced != prevCanBePlaced )
{
if ( self.canBePlaced )
{
carriedObj SetModel( config.modelPlacement );
carrier ForceUseHintOn( config.placeString );
}
else
{
carriedObj SetModel( config.modelPlacementFailed );
carrier ForceUseHintOn( config.cannotPlaceString );
}
}
prevCanBePlaced = self.canBePlaced;
wait ( 0.05 );
}
}
deactivate( streakName ) // self == placeable
{
self MakeUnusable();
self hideHeadIcons();
self FreeEntitySentient();
config = level.placeableConfigs[ streakName ];
if ( IsDefined( config.onDeactiveDelegate ) )
{
self [[ config.onDeactiveDelegate ]]( streakName );
}
}
hideHeadIcons()
{
if ( level.teamBased )
{
self maps\mp\_entityheadicons::setTeamHeadIcon( "none", ( 0, 0, 0 ) );
}
else if ( IsDefined( self.owner ) )
{
self maps\mp\_entityheadicons::setPlayerHeadIcon( undefined, ( 0, 0, 0 ) );
}
}
// important callbacks:
// onDamagedDelegate - filter out or amplify damage based on specifics
// onDestroyedDelegate - any extra handling when the object is killed
handleDamage( streakName ) // self == placeable
{
self endon( "carried" );
config = level.placeableConfigs[ streakName ];
self maps\mp\gametypes\_damage::monitorDamage(
config.maxHealth,
config.damageFeedback,
::handleDeathDamage,
::modifyDamage,
true // isKillstreak
);
}
modifyDamage( attacker, weapon, type, damage )
{
modifiedDamage = damage;
config = self.config;
if ( IsDefined( config.allowMeleeDamage ) && config.allowMeleeDamage )
{
modifiedDamage = self maps\mp\gametypes\_damage::handleMeleeDamage( weapon, type, modifiedDamage );
}
if ( IsDefined( config.allowEmpDamage ) && config.allowEmpDamage )
{
modifiedDamage = self maps\mp\gametypes\_damage::handleEmpDamage( weapon, type, modifiedDamage );
}
modifiedDamage = self maps\mp\gametypes\_damage::handleMissileDamage( weapon, type, modifiedDamage );
modifiedDamage = self maps\mp\gametypes\_damage::handleGrenadeDamage( weapon, type, modifiedDamage );
modifiedDamage = self maps\mp\gametypes\_damage::handleAPDamage( weapon, type, modifiedDamage, attacker );
if ( IsDefined( config.modifyDamage ) )
{
modifiedDamage = self [[ config.modifyDamage ]]( weapon, type, modifiedDamage );
}
return modifiedDamage;
}
handleDeathDamage( attacker, weapon, type, damage )
{
config = self.config;
notifyAttacker = self maps\mp\gametypes\_damage::onKillstreakKilled( attacker, weapon, type, damage, config.xpPopup, config.destroyedVO );
if ( notifyAttacker
&& IsDefined( config.onDestroyedDelegate )
)
{
self [[ config.onDestroyedDelegate ]]( self.streakName, attacker, self.owner, type );
}
}
handleDeath( streakName )
{
self endon( "carried" );
self waittill ( "death" );
config = level.placeableConfigs[ streakName ];
// this handles cases of deletion
if ( IsDefined( self ) )
{
// play sound
self deactivate( streakName );
// set destroyed model
if ( IsDefined( config.modelDestroyed ) )
{
self SetModel( config.modelDestroyed );
}
// or do it in the callbacks?
if ( IsDefined( config.onDeathDelegate ) )
{
self [[ config.onDeathDelegate ]]( streakName );
}
self Delete();
}
}
//--------------------------------------------------------------------
onCarrierDeath( streakName, carrier ) // self == placeable
{
self endon ( "placed" );
self endon ( "death" );
carrier endon( "disconnect" );
carrier waittill ( "death" );
if ( self.canBePlaced )
{
self thread onPlaced( streakName );
}
else
{
self onCancel( streakName );
}
}
onKillstreakDisowned( streakName ) // self == placeable
{
self endon ( "death" );
level endon ( "game_ended" );
self.owner waittill ( "killstreak_disowned" );
self cleanup( streakName );
}
onGameEnded( streakName ) // self == placeable
{
self endon ( "death" );
level waittill ( "game_ended" );
self cleanup( streakName );
}
cleanup( streakName ) // self == placeable
{
if ( IsDefined( self.isPlaced ) )
{
self notify( "death" );
}
else
{
self onCancel( streakName );
}
}
watchPlayerConnected() // self == ims
{
self endon( "death" );
while( true )
{
// when new players connect they need to not be able to use the planted ims
level waittill( "connected", player );
self thread onPlayerConnected( player );
}
}
onPlayerConnected( owner ) // self == placeable
{
self endon( "death" );
owner endon( "disconnect" );
owner waittill( "spawned_player" );
// this can't possibly be the owner because the ims is destroyed if the owner leaves the game, so disable use for this player
self DisablePlayerUse( owner );
}
timeOut( streakName )
{
self endon( "death" );
level endon ( "game_ended" );
config = level.placeableConfigs[ streakName ];
lifeSpan = config.lifeSpan;
while ( lifeSpan > 0.0 )
{
wait ( 1.0 );
maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone();
if ( !IsDefined( self.carriedBy ) )
{
lifeSpan -= 1.0;
}
}
if ( IsDefined( self.owner ) && IsDefined( config.goneVO ) )
{
self.owner thread leaderDialogOnPlayer( config.goneVO );
}
self notify ( "death" );
}
//--------------------------------------------------------------------
removeWeapons()
{
if ( self HasWeapon( "iw6_riotshield_mp" ) )
{
self.restoreWeapon = "iw6_riotshield_mp";
self takeWeapon( "iw6_riotshield_mp" );
}
}
removePerks()
{
if ( self _hasPerk( "specialty_explosivebullets" ) )
{
self.restorePerk = "specialty_explosivebullets";
self _unsetPerk( "specialty_explosivebullets" );
}
}
restoreWeapons()
{
if ( IsDefined( self.restoreWeapon ) )
{
self _giveWeapon( self.restoreWeapon );
self.restoreWeapon = undefined;
}
}
restorePerks()
{
if ( IsDefined( self.restorePerk ) )
{
self givePerk( self.restorePerk, false );
self.restorePerk = undefined;
}
}
createBombSquadModel( streakName ) // self == box
{
config = level.placeableConfigs[ streakName ];
if ( IsDefined( config.modelBombSquad ) )
{
bombSquadModel = Spawn( "script_model", self.origin );
bombSquadModel.angles = self.angles;
bombSquadModel Hide();
bombSquadModel thread maps\mp\gametypes\_weapons::bombSquadVisibilityUpdater( self.owner );
bombSquadModel SetModel( config.modelBombSquad );
bombSquadModel LinkTo( self );
bombSquadModel SetContents( 0 );
self.bombSquadModel = bombSquadModel;
self waittill ( "death" );
// Could have been deleted when the player was carrying the ims and died
if ( IsDefined( bombSquadModel ) )
{
bombSquadModel delete();
self.bombSquadModel = undefined;
}
}
}
showPlacedModel( streakname )
{
self Show();
if ( IsDefined( self.bombSquadModel ) )
{
self.bombSquadModel Show();
level notify( "update_bombsquad" );
}
}
hidePlacedModel( streakName )
{
self Hide();
if ( IsDefined( self.bombSquadModel ) )
{
self.bombSquadModel Hide();
}
}
createCarriedObject( streakName )
{
assertEx( IsDefined( self ), "createIMSForPlayer() called without owner specified" );
// need to make sure we aren't already carrying, this fixes a bug where you could start to pull a new one out as you picked the old one up
// this resulted in you being able to plant one and then pull your gun out while having another one attached to you like you're carrying it
if( IsDefined( self.isCarrying ) && self.isCarrying )
return;
carriedObj = SpawnTurret( "misc_turret", self.origin + ( 0, 0, 25 ), "sentry_minigun_mp" );
carriedObj.angles = self.angles;
carriedObj.owner = self;
config = level.placeableConfigs[ streakName ];
carriedObj SetModel( config.modelBase );
carriedObj MakeTurretInoperable();
carriedObj SetTurretModeChangeWait( true );
carriedObj SetMode( "sentry_offline" );
carriedObj MakeUnusable();
carriedObj SetSentryOwner( self );
carriedObj SetSentryCarrier( self );
carriedObj SetCanDamage( false );
carriedObj SetContents( 0 );
return carriedObj;
}

View File

@ -0,0 +1,158 @@
#include maps\mp\_utility;
#include maps\mp\gametypes\_hud_util;
#include common_scripts\utility;
KS_NAME = "placeable_barrier";
init()
{
//PreCacheModel( "portable_barrier" );
//PreCacheModel( "portable_barrier_obj" );
//PreCacheModel( "portable_barrier_obj_red" );
config = spawnStruct();
config.streakName = KS_NAME;
config.weaponInfo = "ims_projectile_mp";
// config.modelBase = "ims_scorpion_body";
config.modelBase = "placeable_barrier";
config.modelDestroyed = "placeable_barrier_destroyed";
config.modelPlacement = "placeable_barrier_obj";
config.modelPlacementFailed = "placeable_barrier_obj_red";
// config.modelDestroyed = "ims_scorpion_body";
config.hintString = &"KILLSTREAKS_HINTS_PLACEABLE_COVER_PICKUP";
config.placeString = &"KILLSTREAKS_HINTS_PLACEABLE_COVER_PLACE";
config.cannotPlaceString = &"KILLSTREAKS_HINTS_PLACEABLE_COVER_CANNOT_PLACE";
config.headIconHeight = 75;
config.splashName = "used_placeable_barrier";
config.lifeSpan = 60.0;
// config.goneVO = "ims_gone";
config.maxHealth = 500;
config.allowMeleeDamage = false;
config.damageFeedback = "ims";
config.xpPopup = "destroyed_ims";
config.destroyedVO = "ims_destroyed";
//config.onCreateDelegate = ::createObject;
config.onPlacedDelegate = ::onPlaced;
config.onCarriedDelegate = ::onCarried;
config.placedSfx = "ims_plant";
config.onDamagedDelegate = ::onDamaged;
//config.onDestroyedDelegate = ::onDestroyed;
config.onDeathDelegate = ::onDeath;
config.deathVfx = loadfx( "vfx/gameplay/mp/killstreaks/vfx_ballistic_vest_death" );
// config.onDeactivateDelegate = ::onDeactivated;
// config.onActivateDelegate = ::onActivated;
config.colRadius = 72;
config.colHeight = 36;
level.placeableConfigs[ KS_NAME ] = config;
setupBrushModel();
level.killStreakFuncs[ KS_NAME ] = ::tryUsePlaceable;
}
tryUsePlaceable( lifeId, streakName ) // self == player
{
result = self maps\mp\killstreaks\_placeable::givePlaceable( KS_NAME );
if( result )
{
self maps\mp\_matchdata::logKillstreakEvent( KS_NAME, self.origin );
}
// we're done carrying for sure and sometimes this might not get reset
// this fixes a bug where you could be carrying and have it in a place where it won't plant, get killed, now you can't scroll through killstreaks
self.isCarrying = undefined;
return result;
}
createObject( streakName ) // self == barrier
{
}
onPlaced( streakName ) // self == barrier
{
config = level.placeableConfigs[ streakName ];
self setModel( config.modelBase );
// turn on collisions
// self SetContents( 1 );
collision = spawn_tag_origin();
collision Show();
collision.origin = self.origin;
//AssertMsg( IsDefined( level.barrierCollision ), "missing barrier_collision brush model for placeable_barrier" );
if ( !IsDefined( level.barrierCollision ) )
{
setupBrushModel();
}
collision CloneBrushmodelToScriptmodel( level.barrierCollision );
otherTeam = getOtherTeam( self.owner.team );
BadPlace_Cylinder( streakName + (self GetEntityNumber()), -1, self.origin, config.colRadius, config.colHeight, otherTeam );
self.collision = collision;
}
onCarried( streakName ) // self == barrier
{
self disableCollision( streakName );
}
onDamaged( streakname, attacker, owner, damage )
{
return damage;
}
onDestroyed( streakName, attacker, owner, sMeansOfDeath ) // self == barrier / victim
{
}
onDeath( streakName )
{
self disableCollision( streakName );
config = level.placeableConfigs[ streakName ];
if ( IsDefined( config.deathSfx ) )
{
self PlaySound( config.deathSfx );
}
PlayFX( config.deathVfx, self.origin );
wait( 0.5 );
}
disableCollision( streakName )
{
// turn off the path blocker
if ( IsDefined( self.collision ) )
{
BadPlace_Delete( streakName + (self GetEntityNumber()) );
// self.collision ConnectPaths();
self.collision Delete();
self.collision = undefined;
}
}
setupBrushModel()
{
scriptModel = GetEnt( "barrier_collision", "targetname" );
if ( IsDefined ( scriptmodel ) )
{
level.barrierCollision = GetEnt( scriptModel.target, "targetname" );
scriptModel Delete();
}
if ( !IsDefined( level.barrierCollision ) )
{
Print( "!!! level does not contain a barrier_collision script model, please add one!" );
level.barrierCollision = level.airDropCrateCollision;
}
}

View File

@ -0,0 +1,444 @@
#include maps\mp\_utility;
#include maps\mp\gametypes\_hud_util;
#include common_scripts\utility;
// _plane.gsc
// a modular component intended to control a plane flying overhead
// adapted from _airstrike.gsc
kPlaneHealth = 800;
init()
{
if ( !IsDefined( level.planes ) )
{
level.planes = [];
}
if ( !IsDefined( level.planeConfigs ) )
{
level.planeConfigs = [];
}
level.fighter_deathfx = LoadFX( "vfx/gameplay/explosions/vehicle/hind_mp/vfx_x_mphnd_primary" );
level.fx_airstrike_afterburner = loadfx ("vfx/gameplay/mp/killstreaks/vfx_air_superiority_afterburner");
level.fx_airstrike_contrail = loadfx ("vfx/gameplay/mp/killstreaks/vfx_aircraft_contrail");
level.fx_airstrike_wingtip_light_green = LoadFX ( "vfx/gameplay/mp/killstreaks/vfx_acraft_light_wingtip_green" );
level.fx_airstrike_wingtip_light_red = LoadFX ( "vfx/gameplay/mp/killstreaks/vfx_acraft_light_wingtip_red" );
/*
* streakName
* modelNames[] // alllied, axis
* halfDistance
* speed
* initialHeight
* flightSound
* flyTime
* attackTime
* inboundFlightAnim?
* outboundFlightAnim = "airstrike_mp"
* onAttackDelegate
* killcam stuff?
* planeFX?
*/
}
getFlightPath( coord, directionVector, planeHalfDistance, absoluteHeight, planeFlyHeight, planeFlySpeed, attackDistance, streakName )
{
// stealth_airstrike moves this a lot more
startPoint = coord + ( directionVector * ( -1 * planeHalfDistance ) );
endPoint = coord + ( directionVector * planeHalfDistance );
if ( absoluteHeight ) // used in the new height system
{
startPoint *= (1, 1, 0);
endPoint *= (1, 1, 0);
}
startPoint += ( 0, 0, planeFlyHeight );
endPoint += ( 0, 0, planeFlyHeight );
// Make the plane fly by
d = length( startPoint - endPoint );
flyTime = ( d / planeFlySpeed );
// bomb explodes planeBombExplodeDistance after the plane passes the center
d = abs( 0.5 * d + attackDistance );
attackTime = ( d / planeFlySpeed );
assert( flyTime > attackTime );
flightPath["startPoint"] = startPoint;
flightPath["endPoint"] = endPoint;
flightPath["attackTime"] = attackTime;
flightPath["flyTime"] = flyTime;
return flightPath;
}
//doPlaneStrike( lifeId, owner, requiredDeathCount, bombsite, startPoint, endPoint, bombTime, flyTime, direction, streakName )
doFlyby( lifeId, owner, requiredDeathCount, startPoint, endPoint, attackTime, flyTime, directionVector, streakName )
{
plane = planeSpawn( lifeId, owner, startPoint, directionVector, streakName );
plane endon( "death" );
// plane spawning randomness = up to 125 units, biased towards 0
// radius of bomb damage is 512
endPathRandomness = 150;
pathEnd = endPoint + ( (RandomFloat(2) - 1) * endPathRandomness, (RandomFloat(2) - 1) * endPathRandomness, 0 );
plane planeMove( pathEnd, flyTime, attackTime, streakName );
plane planecleanup();
}
planeSpawn( lifeId, owner, startPoint, directionVector, streakName )
{
if ( !isDefined( owner ) )
return;
startPathRandomness = 100;
pathStart = startPoint + ( (RandomFloat(2) - 1) * startPathRandomness, (RandomFloat(2) - 1) * startPathRandomness, 0 );
//self thread DrawLine(pathStart, (AnglesToForward( direction ) * 200000), 120, (1,0,1) );
configData = level.planeConfigs[ streakName ];
plane = undefined;
plane = Spawn( "script_model", pathStart );
plane.team = owner.team;
plane.origin = pathStart;
plane.angles = VectorToAngles( directionVector );
plane.lifeId = lifeId;
plane.streakName = streakName;
plane.owner = owner;
plane SetModel( configData.modelNames[ owner.team ] );
if ( IsDefined( configData.compassIconFriendly ) )
{
plane setObjectiveIcons(configData.compassIconFriendly, configData.compassIconEnemy );
}
plane thread handleDamage();
plane thread handleDeath();
// handle the nuke instead?
// plane thread handleEMP( owner );
startTrackingPlane( plane );
// stealth bomber doesn't have effects
if ( !IsDefined( configData.noLightFx ) )
{
plane thread playPlaneFx();
}
plane PlayLoopSound( configData.inboundSfx );
plane createKillCam( streakName );
return plane;
}
planeMove( destination, flyTime, attackTime, streakName ) // self == plane
{
configData = level.planeConfigs[ streakName ];
// begin flight
self MoveTo( destination, flyTime, 0, 0 );
// begin attack
//thread callStrike_planeSound( plane, bombsite );
// hmm, don't like the timing of these flybys
if ( IsDefined( configData.onAttackDelegate ) )
{
self thread [[ configData.onAttackDelegate ]]( destination, flyTime, attackTime, self.owner, streakName );
}
if ( IsDefined( configData.sonicBoomSfx ) )
{
self thread playSonicBoom( configData.sonicBoomSfx, 0.5 * flyTime );
}
// fly away
wait( 0.65 * flyTime );
if ( IsDefined( configData.outboundSfx ) )
{
self StopLoopSound();
self PlayLoopSound( configData.outboundSfx );
}
if ( IsDefined( configData.outboundFlightAnim ) )
{
self ScriptModelPlayAnimDeltaMotion( configData.outboundFlightAnim );
}
wait ( 0.35 * flyTime );
}
planeCleanup() // self == plane
{
configData = level.planeConfigs[ self.streakName ];
if ( IsDefined( configData.onFlybyCompleteDelegate ) )
{
thread [[ configData.onFlybyCompleteDelegate ]]( self.owner, self, self.streakName );
}
if ( IsDefined( self.friendlyTeamId ) )
{
_objective_delete( self.friendlyTeamId );
_objective_delete( self.enemyTeamID );
}
if ( IsDefined( self.killCamEnt ) )
{
self.killCamEnt Delete();
}
stopTrackingPlane( self );
self notify( "delete" );
self delete();
}
handleEMP( owner ) // self == plane
{
self endon ( "death" );
while ( true )
{
if ( owner isEMPed() )
{
self notify( "death" );
return;
}
level waittill ( "emp_update" );
}
}
handleDeath() // self == plane
{
level endon( "game_ended" );
self endon( "delete" );
self waittill( "death" );
forward = AnglesToForward( self.angles ) * 200;
PlayFX( level.fighter_deathfx, self.origin, forward );
self thread planeCleanup();
}
handleDamage()
{
self endon( "end_remote" );
self maps\mp\gametypes\_damage::monitorDamage(
kPlaneHealth, // should be defined elsewhere
"helicopter", // should there be a death one?
::handleDeathDamage,
::modifyDamage,
true // isKillstreak
);
}
modifyDamage( attacker, weapon, type, damage )
{
modifiedDamage = damage;
modifiedDamage = self maps\mp\gametypes\_damage::handleMissileDamage( weapon, type, modifiedDamage );
modifiedDamage = self maps\mp\gametypes\_damage::handleAPDamage( weapon, type, modifiedDamage, attacker );
return modifiedDamage;
}
handleDeathDamage( attacker, weapon, type, damage ) // self == plane
{
config = level.planeConfigs[ self.streakName ];
// !!! need VO
self maps\mp\gametypes\_damage::onKillstreakKilled( attacker, weapon, type, damage, config.xpPopup, config.destroyedVO, config.callout );
maps\mp\gametypes\_missions::checkAAChallenges( attacker, self, weapon );
}
playPlaneFX()
{
self endon ( "death" );
wait( 0.5);
PlayFXOnTag( level.fx_airstrike_afterburner, self, "tag_engine_right" );
wait( 0.5);
PlayFXOnTag( level.fx_airstrike_afterburner, self, "tag_engine_left" );
wait( 0.5);
PlayFXOnTag( level.fx_airstrike_contrail, self, "tag_right_wingtip" );
wait( 0.5);
PlayFXOnTag( level.fx_airstrike_contrail, self, "tag_left_wingtip" );
wait( 0.5);
PlayFXOnTag( level.fx_airstrike_wingtip_light_red, self, "tag_right_wingtip" );
wait( 0.5);
PlayFXOnTag( level.fx_airstrike_wingtip_light_green, self, "tag_left_wingtip" );
}
getPlaneFlyHeight()
{
heightEnt = GetEnt( "airstrikeheight", "targetname" );
if ( IsDefined( heightEnt ) )
{
return heightEnt.origin[2];
}
else
{
println( "NO DEFINED AIRSTRIKE HEIGHT SCRIPT_ORIGIN IN LEVEL" );
planeFlyHeight = 950;
if ( isdefined( level.airstrikeHeightScale ) )
planeFlyHeight *= level.airstrikeHeightScale;
return planeFlyHeight;
}
}
// getPlaneFlightPlan
// Summary: On certain levels, we use airstrikeheight's position and orientation to indicate a safe flight path. The entity must have script_noteworthy="fixedposition" and have angles set.
// If not specified, we create one that across the player's current field of view.
getPlaneFlightPlan( distFromPlayer ) // self == player
{
result = SpawnStruct();
result.height = getPlaneFlyHeight();
heightEnt = GetEnt( "airstrikeheight", "targetname" );
if ( IsDefined( heightEnt )
&& IsDefined( heightEnt.script_noteworthy )
&& heightEnt.script_noteworthy == "fixedposition"
)
{
result.targetPos = heightEnt.origin;
result.flightDir = AnglesToForward( heightEnt.angles );
if ( RandomInt(2) == 0 )
result.flightDir *= -1;
}
else
{
forwardVec = AnglesToForward( self.angles );
rightVec = AnglesToRight( self.angles );
result.targetPos = self.origin + distFromPlayer * forwardVec;
result.flightDir = -1 * rightVec;
}
return result;
}
getExplodeDistance( height )
{
standardHeight = 850;
standardDistance = 1500;
distanceFrac = standardHeight/height;
newDistance = distanceFrac * standardDistance;
return newDistance;
}
startTrackingPlane( obj )
{
entNum = obj GetEntityNumber();
level.planes[ entNum ] = obj;
}
stopTrackingPlane( obj )
{
entNum = obj GetEntityNumber();
level.planes[ entNum ] = undefined;
}
selectAirstrikeLocation( lifeId, streakname, doStrikeFn )
{
targetSize = level.mapSize / 6.46875; // 138 in 720
if ( level.splitscreen )
targetSize *= 1.5;
config = level.planeConfigs[ streakname ];
if ( IsDefined( config.selectLocationVO ) )
{
self PlayLocalSound( game[ "voice" ][ self.team ] + config.selectLocationVO );
}
self _beginLocationSelection( streakname, "map_artillery_selector", config.chooseDirection, targetSize );
self endon( "stop_location_selection" );
// wait for the selection. randomize the yaw if we're not doing a precision airstrike.
self waittill( "confirm_location", location, directionYaw );
if ( !config.chooseDirection )
{
directionYaw = randomint(360);
}
self setblurforplayer( 0, 0.3 );
if ( IsDefined( config.inboundVO ) )
{
self PlayLocalSound( game[ "voice" ][ self.team ] + config.inboundVO );
}
// turn off logging for DLC killstreaks (no shipped ks uses this)
// self maps\mp\_matchdata::logKillstreakEvent( streakName, location );
self thread [[ doStrikeFn ]]( lifeId, location, directionYaw, streakName );
return true;
}
setObjectiveIcons( friendlyIcon, enemyIcon ) // self == plane
{
friendlyTeamId = maps\mp\gametypes\_gameobjects::getNextObjID();
Objective_Add( friendlyTeamId, "active", (0,0,0), friendlyIcon );
Objective_OnEntityWithRotation( friendlyTeamId, self );
self.friendlyTeamId = friendlyTeamId;
enemyTeamID = maps\mp\gametypes\_gameobjects::getNextObjID();
Objective_Add( enemyTeamID, "active", (0,0,0), enemyIcon );
Objective_OnEntityWithRotation( enemyTeamID, self );
self.enemyTeamID = enemyTeamID;
if (level.teamBased)
{
Objective_Team( friendlyTeamId, self.team );
Objective_Team( enemyTeamID, getOtherTeam(self.team) );
}
else
{
ownerEntityNum = self.owner GetEntityNumber();
Objective_PlayerTeam( friendlyTeamId, ownerEntityNum );
Objective_PlayerEnemyTeam( enemyTeamID, ownerEntityNum );
}
}
playSonicBoom( soundName, delay )
{
self endon ("death");
wait ( delay );
self PlaySoundOnMovingEnt( soundName );
}
createKillCam( streakName ) // self == plane
{
configData = level.planeConfigs[ streakName ];
if ( IsDefined( configData.killCamOffset ) )
{
planedir = AnglesToForward( self.angles );
killCamEnt = Spawn( "script_model", self.origin + (0,0,100) - planedir * 200 );
killCamEnt.startTime = GetTime();
killCamEnt SetScriptMoverKillCam( "airstrike" );
killCamEnt LinkTo( self, "tag_origin", configData.killCamOffset, ( 0,0,0 ) );
self.killCamEnt = killCamEnt;
}
}

View File

@ -0,0 +1,429 @@
#include maps\mp\_utility;
#include common_scripts\utility;
/* Portable AOE generator
* 2013-03-25 wsh
* Class of equipment that can be placed and moved in the world
* that affects all targeted players in an area around the deployed equipment
* examples: scrambler, portable radar
* Adapted from old scrambler code.
*/
init()
{
if ( !IsDefined( level.portableAOEgeneratorSettings ) )
{
level.portableAOEgeneratorSettings = [];
level.generators = [];
}
// in your specific implementation, need to set these config vars
/*
config = SpawnStruct();
config.generatorType = GENERATOR_TYPE;
config.weaponName = "scrambler_mp";
config.targetType = "enemy"; // valid types { "friendly", "enemy", "all" }
// callbacks are in the format: generator callbackFunc( player, generatorType )
config.onDeployCallback = ::onCreateScrambler;
config.onDestroyCallback = ::onDestroyScrambler;
// callbacks are in the format: player callbackFunc()
config.onEnterCallback = ::onEnterScrambler;
config.onExitCallback = ::onExitScrambler;
// config.timeLimit = undefined;
config.health = 100;
config.placementZTolerance = 40;
config.placedModel = "weapon_jammer";
config.bombSquadModel = "weapon_jammer_bombsquad";
config.damageFeedback = "scrambler";
config.useHintString = &"MP_PATCH_PICKUP_SCRAMBLER";
config.headIconHeight = 20;
config.useSound = "scavenger_pack_pickup";
config.aoeRadius = 512;
*/
}
// make sure that killstreaks don't get ammo refills
setWeapon( generatorType ) // self == player
{
config = level.portableAOEgeneratorSettings[ generatorType ];
self SetOffhandSecondaryClass( "flash" );
self _giveWeapon( config.weaponName, 0 );
self giveStartAmmo( config.weaponName );
if ( !IsDefined( self.deployedGenerators ) )
{
self.deployedGenerators = [];
}
self thread monitorGeneratorUse( generatorType );
}
unsetWeapon( generatorType ) // self == player
{
self notify( "end_monitorUse_" + generatorType );
}
deleteGenerator( generator, generatorType ) // self == player
{
if ( !IsDefined( generator ) )
{
return;
}
foreach ( player in level.players )
{
if ( IsDefined( player )
&& IsDefined( player.inGeneratorAOE )
)
{
// need to check if this array is defined
// or create it
// !!!
player.inGeneratorAOE[ generatorType ] = undefined;
}
}
// remove all references to this generator
self registerGenerator( generator, generatorType, undefined );
generator notify( "death" );
generator Delete();
}
// bRegister = true to register
// = undefined to unregister
registerGenerator( generator, generatorType, bRegister ) // self == player
{
if ( IsDefined( bRegister ) && bRegister )
{
self.deployedGenerators[ generatorType ] = generator;
}
else
{
self.deployedGenerators[ generatorType ] = undefined;
bRegister = undefined; // just to make sure people aren't passing in false
}
allGeneratorsOfThisType = level.generators[ generatorType ];
if ( !IsDefined( allGeneratorsOfThisType ) )
{
level.generators[ generatorType ] = [];
allGeneratorsOfThisType = level.generators[ generatorType ];
}
id = getID( generator );
allGeneratorsOfThisType[ id ] = bRegister;
}
monitorGeneratorUse( generatorType ) // self == player
{
self notify( "end_monitorUse_" + generatorType );
self endon( "end_monitorUse_" + generatorType );
self endon( "disconnect" );
level endon( "game_ended" );
config = level.portableAOEgeneratorSettings[ generatorType ];
while ( true )
{
self waittill( "grenade_fire", grenade, weapName );
// grenade is the entity spawned by the G_FireGrenade() since we want this to be
// script controlled, we won't actually use this entity
if ( weapName == config.weaponName || weapName == generatorType )
{
if ( !IsAlive( self ) )
{
grenade delete();
return;
}
if ( checkGeneratorPlacement( grenade, config.placementZTolerance ) )
{
previousGenerator = self.deployedGenerators[ generatorType ];
if ( IsDefined( previousGenerator ) )
{
deleteGenerator( previousGenerator, generatorType );
}
generator = self spawnNewGenerator( generatorType, grenade.origin );
// For moving platforms.
parent = grenade GetLinkedParent();
if ( IsDefined( parent ) )
{
generator LinkTo( parent );
}
if( IsDefined( grenade ) )
{
grenade Delete();
}
}
else
{
self SetWeaponAmmoStock( config.weaponName, self GetWeaponAmmoStock( "trophy_mp" ) + 1 );
}
}
}
}
checkGeneratorPlacement( grenade, maxZDistance ) // self == player
{
// need to see if this is being placed far away from the player and not let it do that
// this will fix a legacy bug where you can stand on a ledge and plant a claymore down on the ground far below you
grenade Hide();
grenade waittill( "missile_stuck", stuckTo );
if( maxZDistance * maxZDistance < DistanceSquared( grenade.origin, self.origin ) )
{
secTrace = bulletTrace( self.origin, self.origin - (0, 0, maxZDistance), false, self );
if( secTrace["fraction"] == 1 )
{
// there's nothing under us so don't place the grenade up in the air
grenade delete();
return false;
}
// move the grenade to a reasonable place
grenade.origin = secTrace["position"];
}
grenade Show();
return true;
}
spawnNewGenerator( generatorType, origin ) // self == player
{
config = level.portableAOEgeneratorSettings[ generatorType ];
generator = spawn( "script_model", origin );
generator.health = config.health;
generator.team = self.team;
generator.owner = self;
generator SetCanDamage( true );
generator SetModel( config.placedModel );
// setup icons for item so friendlies see it
if ( level.teamBased )
generator maps\mp\_entityheadIcons::setTeamHeadIcon( self.team , (0,0,config.headIconHeight) );
else
generator maps\mp\_entityheadicons::setPlayerHeadIcon( self, (0,0,config.headIconHeight) );
generator thread watchOwner( self, generatorType );
generator thread watchDamage( self, generatorType );
generator thread watchUse( self, generatorType );
// 2013-03-25 wsh:
// not sure why we need to set notUsableForJoiningPlayers
generator thread notUsableForJoiningPlayers( self );
if ( IsDefined( config.onDeployCallback ) )
{
generator [[ config.onDeployCallback ]]( self, generatorType );
}
generator thread maps\mp\gametypes\_weapons::createBombSquadModel( config.bombSquadModel, "tag_origin", self );
self registerGenerator( generator, generatorType, true );
// need to clear the changing weapon because it'll get stuck on c4_mp and player will stop spawning because we get locked in isChangingWeapon() loop when a killstreak is earned
self.changingWeapon = undefined;
wait(0.05); // allows for the plant sound to play
if( IsDefined(generator) && (generator touchingBadTrigger()) )
{
generator notify( "death" );
}
return generator;
}
watchOwner( owner, generatorType ) // self == generator
{
self endon( "death" );
level endon ( "game_ended" );
if ( bot_is_fireteam_mode() )
{
owner waittill( "killstreak_disowned" );
}
else
{
owner waittill_either( "killstreak_disowned", "death" );
}
owner thread deleteGenerator( self, generatorType );
}
watchDamage( owner, generatorType ) // self == generator
{
self.generatorType = generatorType;
config = level.portableAOEgeneratorSettings[ generatorType ];
self maps\mp\gametypes\_damage::monitorDamage(
config.health,
config.damageFeedback,
::handleDeathDamage,
::modifyDamage,
false // isKillstreak
);
}
// we should just use the default one
modifyDamage( attacker, weapon, type, damage )
{
modifiedDamage = damage;
modifiedDamage = self maps\mp\gametypes\_damage::handleMeleeDamage( weapon, type, modifiedDamage );
modifiedDamage = self maps\mp\gametypes\_damage::handleEmpDamage( weapon, type, modifiedDamage );
modifiedDamage = self maps\mp\gametypes\_damage::handleMissileDamage( weapon, type, modifiedDamage );
modifiedDamage = self maps\mp\gametypes\_damage::handleGrenadeDamage( weapon, type, modifiedDamage );
modifiedDamage = self maps\mp\gametypes\_damage::handleAPDamage( weapon, type, modifiedDamage, attacker );
return modifiedDamage;
}
handleDeathDamage( attacker, weapon, type )
{
owner = self.owner;
config = level.portableAOEgeneratorSettings[ self.generatorType ];
if ( isDefined( owner ) && attacker != owner )
{
attacker notify( "destroyed_equipment" ); // count towards SitRep Pro challenge
}
if ( IsDefined( config.onDestroyCallback ) )
{
owner [[ config.onDestroyCallback ]]( self, self.generatorType );
}
owner thread deleteGenerator( self, self.generatorType );
}
watchUse( owner, generatorType ) // self == generator
{
self endon ( "death" );
level endon ( "game_ended" );
owner endon ( "disconnect" );
config = level.portableAOEgeneratorSettings[ generatorType ];
self setCursorHint( "HINT_NOICON" );
self setHintString( config.useHintString );
self setSelfUsable( owner );
while ( true )
{
self waittill ( "trigger", player );
player playLocalSound( config.useSound );
// give item to user (only if they haven't restocked from scavenger pickup since dropping)
if ( player getAmmoCount( config.weaponName ) == 0 && !player isJuggernaut() )
{
player setWeapon( generatorType );
}
player thread deleteGenerator( self, generatorType );
}
}
// called from _perks.gsc::spawnPlayer
// because we want to accumlate the effects of all generators
// before we determine whether or not to enable the effect
// DOES NOT COUNT THE # OF EFFECTS ACTIVE, though it's easily changed
generatorAOETracker() // self == player
{
self endon( "death" );
self endon( "disconnect" );
self endon( "faux_spawn" );
level endon( "game_ended" );
// wait a random amount so that all players aren't checking at the same time
delay = RandomFloat( 0.5 );
wait( delay );
self.inGeneratorAOE = [];
while ( true )
{
wait ( 0.05 );
if ( level.generators.size > 0 || self.inGeneratorAOE.size > 0 )
{
foreach ( config in level.portableAOEgeneratorSettings )
{
self checkAllGeneratorsOfThisType( config.generatorType );
}
}
}
}
checkAllGeneratorsOfThisType( generatorType ) // self == player
{
generators = level.generators[ generatorType ];
if ( IsDefined( generators ) )
{
config = level.portableAOEgeneratorSettings[ generatorType ];
maxDistSq = config.aoeRadius * config.aoeRadius;
result = undefined;
foreach ( generator in generators )
{
// should I be cleaning the array?
if ( IsDefined( generator ) && isReallyAlive( generator ) )
{
if ( ( level.teamBased && matchesTargetTeam( generator.team, self.team, config.targetType ) )
|| ( !level.teamBased && matchesOwner( generator.owner, self, config.targetType ) )
)
{
// stop as soon as we find a valid candidate
distSq = DistanceSquared( generator.origin, self.origin );
if ( distSq < maxDistSq )
{
result = generator;
break;
}
}
}
}
isInThisGenerator = IsDefined(result);
wasInGeneratorOfThisType = IsDefined( self.inGeneratorAOE[ generatorType ] );
if ( isInThisGenerator && !wasInGeneratorOfThisType )
{
self [[ config.onEnterCallback ]]();
}
else if ( !isInThisGenerator && wasInGeneratorOfThisType )
{
self [[ config.onExitCallback ]]();
}
self.inGeneratorAOE[ generatorType ] = result;
}
}
matchesTargetTeam( myTeam, theirTeam, teamType )
{
return (
( teamType == "all" )
|| ( teamType == "friendly" && myTeam == theirTeam )
|| ( teamType == "enemy" && myTeam != theirTeam )
);
}
matchesOwner( myOwner, player, teamType )
{
return (
( teamType == "all" )
|| ( teamType == "friendly" && myOwner == player )
|| ( teamType == "enemy" && myOwner != player )
);
}
// since I can't use the generator itself as an index into the array
// create some kind of unique ID to use as an index
getID( generator )
{
return generator.owner.guid + generator.birthtime;
}

View File

@ -0,0 +1,323 @@
#include maps\mp\_utility;
#include common_scripts\utility;
init()
{
level.missileRemoteLaunchVert = 14000;
level.missileRemoteLaunchHorz = 7000;
level.missileRemoteLaunchTargetDist = 1500;
level.rockets = [];
level.killstreakFuncs[ "predator_missile" ] = ::tryUsePredatorMissile;
level.remotemissile_fx[ "explode" ] = LoadFX( "fx/explosions/aerial_explosion" );
}
tryUsePredatorMissile( lifeId, streakName )
{
self setUsingRemote( "remotemissile" );
result = self maps\mp\killstreaks\_killstreaks::initRideKillstreak();
if ( result != "success" )
{
if ( result != "disconnect" )
self clearUsingRemote();
return false;
}
self SetClientOmnvar( "ui_predator_missile", 1 );
level thread _fire( lifeId, self );
return true;
}
getBestSpawnPoint( remoteMissileSpawnPoints )
{
validEnemies = [];
foreach ( spawnPoint in remoteMissileSpawnPoints )
{
spawnPoint.validPlayers = [];
spawnPoint.spawnScore = 0;
}
foreach ( player in level.players )
{
if ( !isReallyAlive( player ) )
continue;
if ( player.team == self.team )
continue;
if ( player.team == "spectator" )
continue;
bestDistance = 999999999;
bestSpawnPoint = undefined;
foreach ( spawnPoint in remoteMissileSpawnPoints )
{
//could add a filtering component here but i dont know what it would be.
spawnPoint.validPlayers[spawnPoint.validPlayers.size] = player;
potentialBestDistance = Distance2D( spawnPoint.targetent.origin, player.origin );
if ( potentialBestDistance <= bestDistance )
{
bestDistance = potentialBestDistance;
bestSpawnpoint = spawnPoint;
}
}
assertEx( IsDefined( bestSpawnPoint ), "Closest remote-missile spawnpoint undefined for player: " + player.name );
bestSpawnPoint.spawnScore += 2;
}
bestSpawn = remoteMissileSpawnPoints[0];
foreach ( spawnPoint in remoteMissileSpawnPoints )
{
foreach ( player in spawnPoint.validPlayers )
{
spawnPoint.spawnScore += 1;
if ( BulletTracePassed( player.origin + (0,0,32), spawnPoint.origin, false, player ) )
spawnPoint.spawnScore += 3;
if ( spawnPoint.spawnScore > bestSpawn.spawnScore )
{
bestSpawn = spawnPoint;
}
else if ( spawnPoint.spawnScore == bestSpawn.spawnScore ) // equal spawn weights so we toss a coin.
{
if ( coinToss() )
bestSpawn = spawnPoint;
}
}
}
return ( bestSpawn );
}
_fire( lifeId, player )
{
remoteMissileSpawnArray = GetEntArray( "remoteMissileSpawn" , "targetname" );
foreach ( spawn in remoteMissileSpawnArray )
{
if ( IsDefined( spawn.target ) )
spawn.targetEnt = GetEnt( spawn.target, "targetname" );
}
if ( remoteMissileSpawnArray.size > 0 )
remoteMissileSpawn = player getBestSpawnPoint( remoteMissileSpawnArray );
else
remoteMissileSpawn = undefined;
if ( IsDefined( remoteMissileSpawn ) )
{
startPos = remoteMissileSpawn.origin;
targetPos = remoteMissileSpawn.targetEnt.origin;
//thread drawLine( startPos, targetPos, 30, (0,1,0) );
vector = VectorNormalize( startPos - targetPos );
startPos = ( vector * 14000 ) + targetPos;
//thread drawLine( startPos, targetPos, 15, (1,0,0) );
rocket = MagicBullet( "remotemissile_projectile_mp", startpos, targetPos, player );
}
else
{
upVector = (0, 0, level.missileRemoteLaunchVert );
backDist = level.missileRemoteLaunchHorz;
targetDist = level.missileRemoteLaunchTargetDist;
forward = AnglesToForward( player.angles );
startpos = player.origin + upVector + forward * backDist * -1;
targetPos = player.origin + forward * targetDist;
rocket = MagicBullet( "remotemissile_projectile_mp", startpos, targetPos, player );
}
if ( !IsDefined( rocket ) )
{
player clearUsingRemote();
return;
}
rocket.team = player.team;
rocket thread handleDamage();
rocket.lifeId = lifeId;
rocket.type = "remote";
level.remoteMissileInProgress = true;
missileEyes( player, rocket );
}
/#
_fire_noplayer( lifeId, player )
{
upVector = (0, 0, level.missileRemoteLaunchVert );
backDist = level.missileRemoteLaunchHorz;
targetDist = level.missileRemoteLaunchTargetDist;
forward = AnglesToForward( player.angles );
startpos = player.origin + upVector + forward * backDist * -1;
targetPos = player.origin + forward * targetDist;
rocket = MagicBullet( "remotemissile_projectile_mp", startpos, targetPos, player );
if ( !IsDefined( rocket ) )
return;
rocket thread handleDamage();
rocket.lifeId = lifeId;
rocket.type = "remote";
player CameraLinkTo( rocket, "tag_origin" );
player ControlsLinkTo( rocket );
rocket thread rocket_CleanupOnDeath();
wait ( 2.0 );
player ControlsUnlink();
player CameraUnlink();
}
#/
handleDamage()
{
self endon ( "death" );
self endon ( "deleted" );
self SetCanDamage( true );
for ( ;; )
{
self waittill( "damage" );
println ( "projectile damaged!" );
}
}
missileEyes( player, rocket )
{
//level endon ( "game_ended" );
player endon ( "joined_team" );
player endon ( "joined_spectators" );
rocket thread rocket_CleanupOnDeath();
player thread player_CleanupOnGameEnded( rocket );
player thread player_CleanupOnTeamChange( rocket );
player VisionSetMissilecamForPlayer( "black_bw", 0 );
player endon ( "disconnect" );
if ( IsDefined( rocket ) )
{
player VisionSetMissilecamForPlayer( game["thermal_vision"], 1.0 );
player ThermalVisionOn();
player thread delayedFOFOverlay();
player CameraLinkTo( rocket, "tag_origin" );
player ControlsLinkTo( rocket );
if ( GetDvarInt( "camera_thirdPerson" ) )
player SetThirdPersonDOF( false );
rocket waittill( "death" );
player ThermalVisionOff();
// is defined check required because remote missile doesnt handle lifetime explosion gracefully
// instantly deletes its self after an explode and death notify
if ( IsDefined(rocket) )
player maps\mp\_matchdata::logKillstreakEvent( "predator_missile", rocket.origin );
player ControlsUnlink();
player freezeControlsWrapper( true );
// If a player gets the final kill with a hellfire, level.gameEnded will already be true at this point
if ( !level.gameEnded || IsDefined( player.finalKill ) )
player SetClientOmnvar( "ui_predator_missile", 2 );
wait ( 0.5 );
player ThermalVisionFOFOverlayOff();
player CameraUnlink();
if ( GetDvarInt( "camera_thirdPerson" ) )
player SetThirdPersonDOF( true );
}
player SetClientOmnvar( "ui_predator_missile", 0 );
player clearUsingRemote();
}
delayedFOFOverlay()
{
self endon ( "death" );
self endon ( "disconnect" );
level endon ( "game_ended" );
wait ( 0.15 );
self ThermalVisionFOFOverlayOn();
}
player_CleanupOnTeamChange( rocket )
{
rocket endon ( "death" );
self endon ( "disconnect" );
self waittill_any( "joined_team" , "joined_spectators" );
if ( self.team != "spectator" )
{
self ThermalVisionFOFOverlayOff();
self ControlsUnlink();
self CameraUnlink();
if ( GetDvarInt( "camera_thirdPerson" ) )
self SetThirdPersonDOF( true );
}
self clearUsingRemote();
level.remoteMissileInProgress = undefined;
}
rocket_CleanupOnDeath()
{
entityNumber = self GetEntityNumber();
level.rockets[ entityNumber ] = self;
self waittill( "death" );
level.rockets[ entityNumber ] = undefined;
level.remoteMissileInProgress = undefined;
}
player_CleanupOnGameEnded( rocket )
{
rocket endon ( "death" );
self endon ( "death" );
level waittill ( "game_ended" );
self ThermalVisionFOFOverlayOff();
self ControlsUnlink();
self CameraUnlink();
if ( GetDvarInt( "camera_thirdPerson" ) )
self SetThirdPersonDOF( true );
}

View File

@ -0,0 +1,798 @@
#include maps\mp\_utility;
#include maps\mp\gametypes\_hud_util;
#include common_scripts\utility;
init()
{
level.remote_mortar_fx[ "laserTarget" ] = LoadFX( "fx/misc/laser_glow" );
level.remote_mortar_fx[ "missileExplode" ] = LoadFX( "fx/explosions/bouncing_betty_explosion" );
level.remote_mortar_fx[ "deathExplode" ] = LoadFX( "fx/explosions/uav_advanced_death" );
level.killstreakFuncs["remote_mortar"] = ::tryUseRemoteMortar;
level.remote_mortar = undefined;
/#
SetDevDvarIfUninitialized( "scr_remote_mortar_timeout", 40.0 );
#/
}
tryUseRemoteMortar( lifeId, streakName )
{
if ( isDefined( level.remote_mortar ) )
{
self iPrintLnBold( &"KILLSTREAKS_AIR_SPACE_TOO_CROWDED" );
return false;
}
self setUsingRemote( "remote_mortar" );
result = self maps\mp\killstreaks\_killstreaks::initRideKillstreak( "remote_mortar" );
if ( result != "success" )
{
if ( result != "disconnect" )
self clearUsingRemote();
return false;
}
// check again, could have filled up while initRideKillstreak was running
else if ( isDefined( level.remote_mortar ) )
{
self iPrintLnBold( &"KILLSTREAKS_AIR_SPACE_TOO_CROWDED" );
self clearUsingRemote();
return false;
}
self maps\mp\_matchdata::logKillstreakEvent( "remote_mortar", self.origin );
return startRemoteMortar( lifeId );
}
startRemoteMortar( lifeId )
{
remote = spawnRemote( lifeId, self );
if ( !isDefined( remote ) )
return false;
level.remote_mortar = remote;
self remoteRide( remote );
self thread teamPlayerCardSplash( "used_remote_mortar", self );
return true;
}
spawnRemote( lifeId, owner )
{
remote = spawnPlane( owner, "script_model", level.UAVRig getTagOrigin( "tag_origin" ), "compass_objpoint_reaper_friendly", "compass_objpoint_reaper_enemy" );
if ( !isDefined( remote ) )
return undefined;
remote setModel( "vehicle_predator_b" );
remote.lifeId = lifeId;
remote.team = owner.team;
remote.owner = owner;
remote.numFlares = 1;
//remote ThermalDrawEnable();
remote setCanDamage( true );
remote thread damageTracker();
remote.heliType = "remote_mortar";
// for target lists (javelin, stinger, sam, emp, etc)
remote.uavType = "remote_mortar";
remote maps\mp\killstreaks\_uav::addUAVModel();
// same height and radius as the AC130 with random angle and counter rotation
zOffset = 6300;
angle = randomInt( 360 );
radiusOffset = 6100;
xOffset = cos( angle ) * radiusOffset;
yOffset = sin( angle ) * radiusOffset;
angleVector = vectorNormalize( (xOffset,yOffset,zOffset) );
angleVector = ( angleVector * 6100 );
remote linkTo( level.UAVRig, "tag_origin", angleVector, (0,angle-90,10) );
// hud stuff
owner SetClientDvar( "ui_reaper_targetDistance", -1 );
owner SetClientDvar( "ui_reaper_ammoCount", 14 );
remote thread handleDeath( owner );
remote thread handleTimeout( owner );
remote thread handleOwnerChangeTeam( owner );
remote thread handleOwnerDisconnect( owner );
remote thread handleIncomingStinger();
remote thread handleIncomingSAM();
return remote;
}
lookCenter( remote )
{
self endon( "disconnect" );
level endon( "game_ended" );
remote endon( "death" );
wait( 0.05 );
// changed it from self GetEye() to remote GetTagOrigin( "tag_player" ) becauser there was a rare bug where you could be looking the opposite direction
// we thought it might be that the player didn't get linked before this thread was run so the vector was backwards
lookVec = vectorToAngles( level.UAVRig.origin - remote GetTagOrigin( "tag_player" ) );
self setPlayerAngles( lookVec );
}
remoteRide( remote )
{
self _giveWeapon("mortar_remote_mp");
self SwitchToWeapon("mortar_remote_mp");
// with the way we do visionsets we need to wait for the clearRideIntro() is done before we set thermal
self thread waitSetThermal( 1.0, remote );
self thread reInitializeThermal( remote );
if ( getDvarInt( "camera_thirdPerson" ) )
self setThirdPersonDOF( false );
self PlayerLinkWeaponviewToDelta( remote, "tag_player", 1.0, 40, 40, 25, 40 );
self thread lookCenter( remote );
self _disableWeaponSwitch();
self thread remoteTargeting( remote );
self thread remoteFiring( remote );
self thread remoteZoom( remote );
}
waitSetThermal( delay, remote )
{
self endon( "disconnect" );
remote endon( "death" );
wait( delay );
self VisionSetThermalForPlayer( level.ac130.enhanced_vision, 0 );
self.lastVisionSetThermal = level.ac130.enhanced_vision;
//self ThermalVisionOn( true );
self ThermalVisionFOFOverlayOn();
}
remoteTargeting( remote )
{
level endon( "game_ended" );
self endon( "disconnect" );
remote endon( "remote_done" );
remote endon( "death" );
remote.targetEnt = SpawnFx( level.remote_mortar_fx["laserTarget"], (0,0,0) );
while ( true )
{
origin = self GetEye();
forward = AnglesToForward( self GetPlayerAngles() );
endpoint = origin + forward * 15000;
traceData = BulletTrace( origin, endpoint, false, remote.targetEnt );
if ( isDefined( traceData["position"] ) )
{
remote.targetEnt.origin = traceData["position"];
triggerFX( remote.targetEnt );
}
wait( 0.05 );
}
}
remoteFiring( remote )
{
level endon( "game_ended" );
self endon( "disconnect" );
remote endon( "remote_done" );
remote endon( "death" );
curTime = getTime();
lastFireTime = curTime-2200;
ammo = 14;
self.firingReaper = false;
while( true )
{
curTime = getTime();
if ( self attackButtonPressed() && ( curTime - lastFireTime > 3000 ) )
{
ammo--;
self SetClientDvar( "ui_reaper_ammoCount", ammo );
lastFireTime = curTime;
self.firingReaper = true;
self playLocalSound( "reaper_fire" );
self PlayRumbleOnEntity( "damage_heavy" );
origin = self GetEye();
forward = AnglesToForward( self GetPlayerAngles() );
right = AnglesToRight( self GetPlayerAngles() );
offset = origin + ( forward * 100 ) + ( right * -100 );
missile = MagicBullet( "remote_mortar_missile_mp", offset, remote.targetEnt.origin, self );
missile.type = "remote_mortar";
Earthquake( 0.3, 0.5, origin, 256 );
//// attach a kill cam ent so we can customize the view
//killCamEnt = Spawn( "script_model", missile.origin + ( forward * -75 ) + ( right * 25 ) );
//killCamEnt LinkTo( missile );
//missile.killCamEnt = killCamEnt;
missile Missile_SetTargetEnt( remote.targetEnt );
missile Missile_SetFlightmodeDirect();
missile thread remoteMissileDistance( remote );
missile thread remoteMissileLife( remote );
missile waittill( "death" );
//if( IsDefined( killCamEnt ) )
// killCamEnt delete();
self SetClientDvar( "ui_reaper_targetDistance", -1 );
self.firingReaper = false;
if ( ammo == 0 )
break;
}
else
wait( 0.05 );
}
self notify( "removed_reaper_ammo" );
self remoteEndRide( remote );
remote thread remoteLeave();
}
handleToggleZoom( remote )
{
level endon( "game_ended" );
self endon( "disconnect" );
remote endon( "remote_done" );
remote endon( "death" );
self notifyOnPlayerCommand( "remote_mortar_toggleZoom1", "+ads_akimbo_accessible" );
if ( !level.console )
{
self notifyOnPlayerCommand( "remote_mortar_toggleZoom1", "+toggleads_throw" );
}
while ( true )
{
result = waittill_any_return( "remote_mortar_toggleZoom1" );
if ( !isDefined( self.remote_mortar_toggleZoom ) )
self.remote_mortar_toggleZoom = 0;
self.remote_mortar_toggleZoom = 1-self.remote_mortar_toggleZoom;
}
}
remoteZoom( remote )
{
level endon( "game_ended" );
self endon( "disconnect" );
remote endon( "remote_done" );
remote endon( "death" );
self.remote_mortar_toggleZoom = undefined;
self thread handleToggleZoom( remote );
remote.zoomed = false;
usingToggle = false;
// first find out how zoom was initiated
while ( true )
{
if ( self adsButtonPressed() )
{
wait( 0.05 );
if ( isDefined( self.remote_mortar_toggleZoom ) )
usingToggle = true;
break;
}
wait( 0.05 );
}
while ( true )
{
if ( ( !usingToggle && self adsButtonPressed() ) || ( usingToggle && self.remote_mortar_toggleZoom ) )
{
if ( remote.zoomed == false )
{
self _giveWeapon("mortar_remote_zoom_mp");
self SwitchToWeapon("mortar_remote_zoom_mp");
remote.zoomed = true;
}
}
else if ( ( !usingToggle && !self adsButtonPressed() ) || ( usingToggle && !self.remote_mortar_toggleZoom ) )
{
if ( remote.zoomed == true )
{
self _giveWeapon("mortar_remote_mp");
self SwitchToWeapon("mortar_remote_mp");
remote.zoomed = false;
}
}
wait( 0.05 );
}
}
remoteMissileDistance( remote )
{
level endon( "game_ended" );
remote endon( "death" );
remote endon( "remote_done" );
self endon( "death" );
while( true )
{
targetDist = distance( self.origin, remote.targetent.origin );
remote.owner SetClientDvar( "ui_reaper_targetDistance", int( targetDist / 12 ) );
wait( 0.05 );
}
}
remoteMissileLife( remote )
{
self endon( "death" );
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( 6 );
playFX( level.remote_mortar_fx["missileExplode"], self.origin );
self delete();
}
remoteEndRide( remote )
{
if ( !self isUsingRemote() )
return;
if ( isDefined( remote ) )
{
remote notify( "helicopter_done" );
//if ( isDefined( remote.laserEnt ) )
//remote.laserEnt LaserOff();
}
self ThermalVisionOff();
self ThermalVisionFOFOverlayOff();
self VisionSetThermalForPlayer( game["thermal_vision"], 0 );
self restoreBaseVisionSet( 0 );
self unlink();
self clearUsingRemote();
if ( getDvarInt( "camera_thirdPerson" ) )
self setThirdPersonDOF( true );
self switchToWeapon( self getLastWeapon() );
// take the killstreak weapons
killstreakWeapon = getKillstreakWeapon( "remote_mortar" );
self TakeWeapon( killstreakWeapon );
self TakeWeapon( "mortar_remote_zoom_mp" );
self TakeWeapon( "mortar_remote_mp" );
self _enableWeaponSwitch();
}
handleTimeout( owner )
{
level endon( "game_ended" );
owner endon( "disconnect" );
owner endon( "removed_reaper_ammo" );
self endon( "death" );
lifeSpan = 40.0;
/#
lifeSpan = GetDvarInt( "scr_remote_mortar_timeout", lifeSpan );
#/
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( lifeSpan );
while( owner.firingReaper )
wait( 0.05 );
if ( isDefined( owner ) )
owner remoteEndRide( self );
self thread remoteLeave();
}
handleDeath( owner )
{
level endon( "game_ended" );
owner endon( "disconnect" );
self endon( "remote_removed" );
self endon( "remote_done" );
self waittill( "death" );
if ( isDefined( owner ) )
owner remoteEndRide( self );
level thread removeRemote( self, true );
}
handleOwnerChangeTeam( owner )
{
level endon( "game_ended" );
self endon( "remote_done" );
self endon( "death" );
owner endon( "disconnect" );
owner endon( "removed_reaper_ammo" );
owner waittill_any( "joined_team", "joined_spectators" );
if ( isDefined( owner ) )
owner remoteEndRide( self );
self thread remoteLeave();
}
handleOwnerDisconnect( owner )
{
level endon( "game_ended" );
self endon( "remote_done" );
self endon( "death" );
owner endon( "removed_reaper_ammo" );
owner waittill( "disconnect" );
self thread remoteLeave();
}
removeRemote( remote, clearLevelRef )
{
self notify( "remote_removed" );
if ( isDefined( remote.targetEnt ) )
remote.targetEnt delete();
if( IsDefined( remote ) )
{
remote delete();
remote maps\mp\killstreaks\_uav::removeUAVModel();
}
if ( !IsDefined( clearLevelRef ) || clearLevelRef == true )
level.remote_mortar = undefined;
}
remoteLeave()
{
// setting the level variable here because there is a bug if this gets shot down on the way out then this doesn't get cleared because of the endon("death")
// now it'll definitely get cleared as soon as it tries to leave
level.remote_mortar = undefined;
level endon( "game_ended" );
self endon( "death" );
self notify( "remote_done" );
self unlink();
destPoint = self.origin + ( AnglesToForward( self.angles ) * 20000 );
self moveTo( destPoint, 30 );
PlayFXOnTag( level._effect[ "ac130_engineeffect" ] , self, "tag_origin" );
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( 3 );
self moveTo( destPoint, 4, 4, 0.0 );
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( 4 );
level thread removeRemote( self, false );
}
remoteExplode()
{
self notify( "death" );
self Hide();
forward = ( AnglesToRight( self.angles ) * 200 );
PlayFX( level.remote_mortar_fx[ "deathExplode" ], self.origin, forward );
}
// Entities spawned from SpawnPlane do not respond to pre-damage callbacks
// so we have to wait until we get the post-damage event.
//
// Because the damage has already happened by the time we find out about it,
// we need to use an artificially high health value, restore it on erroneous damage
// events and track a virtual damage taken against a virtual max health.
damageTracker()
{
level endon( "game_ended" );
self.owner endon( "disconnect" );
self.health = 999999; // keep it from dying anywhere in code
self.maxHealth = 1500; // this is the health we'll check
self.damageTaken = 0; // how much damage has it taken
while( true )
{
self waittill( "damage", damage, attacker, direction_vec, point, meansOfDeath, modelName, tagName, partName, iDFlags, weapon );
// don't allow people to destroy things on their team if FF is off
if ( !maps\mp\gametypes\_weapons::friendlyFireCheck( self.owner, attacker ) )
continue;
if ( !IsDefined( self ) )
return;
if ( isDefined( iDFlags ) && ( iDFlags & level.iDFLAGS_PENETRATION ) )
self.wasDamagedFromBulletPenetration = true;
self.wasDamaged = true;
modifiedDamage = damage;
if( IsPlayer( attacker ) )
{
attacker maps\mp\gametypes\_damagefeedback::updateDamageFeedback( "" );
if( meansOfDeath == "MOD_RIFLE_BULLET" || meansOfDeath == "MOD_PISTOL_BULLET" )
{
if ( attacker _hasPerk( "specialty_armorpiercing" ) )
modifiedDamage += damage * level.armorPiercingMod;
}
}
if( IsDefined( weapon ) )
{
switch( weapon )
{
case "stinger_mp":
case "javelin_mp":
self.largeProjectileDamage = true;
modifiedDamage = self.maxhealth + 1;
break;
case "sam_projectile_mp":
self.largeProjectileDamage = true;
break;
}
maps\mp\killstreaks\_killstreaks::killstreakHit( attacker, weapon, self );
}
self.damageTaken += modifiedDamage;
if( IsDefined( self.owner ) )
self.owner playLocalSound( "reaper_damaged" );
if ( self.damageTaken >= self.maxHealth )
{
if ( isPlayer( attacker ) && ( !isDefined( self.owner ) || attacker != self.owner ) )
{
attacker notify( "destroyed_killstreak", weapon );
thread teamPlayerCardSplash( "callout_destroyed_remote_mortar", attacker );
attacker thread maps\mp\gametypes\_rank::giveRankXP( "kill", 50, weapon, meansOfDeath );
attacker thread maps\mp\gametypes\_rank::xpEventPopup( "destroyed_remote_mortar" );
thread maps\mp\gametypes\_missions::vehicleKilled( self.owner, self, undefined, attacker, damage, meansOfDeath, weapon );
}
if ( isDefined( self.owner ) )
self.owner StopLocalSound( "missile_incoming" );
self thread remoteExplode();
// adding this to make sure it gets undefined, there was a weird bug where it could get killed as soon as it was leaving
// then the threads got killed because of the endon's and this never got reset
level.remote_mortar = undefined;
return;
}
}
}
handleIncomingStinger() // self == remote mortar
{
level endon ( "game_ended" );
self endon ( "death" );
self endon ( "remote_done" );
while( true )
{
level waittill ( "stinger_fired", player, missile, lockTarget );
if ( !IsDefined( lockTarget ) || (lockTarget != self) )
continue;
missile thread stingerProximityDetonate( lockTarget, player );
}
}
stingerProximityDetonate( missileTarget, player ) // self == missile
{
self endon ( "death" );
missileTarget endon( "death" );
if( IsDefined( missileTarget.owner ) )
missileTarget.owner PlayLocalSound( "missile_incoming" );
self Missile_SetTargetEnt( missileTarget );
minDist = Distance( self.origin, missileTarget GetPointInBounds( 0, 0, 0 ) );
lastCenter = missileTarget GetPointInBounds( 0, 0, 0 );
while( true )
{
// already destroyed
if( !IsDefined( missileTarget ) )
center = lastCenter;
else
center = missileTarget GetPointInBounds( 0, 0, 0 );
lastCenter = center;
curDist = Distance( self.origin, center );
if( curDist < 3000 && missileTarget.numFlares > 0 )
{
missileTarget.numFlares--;
missileTarget thread maps\mp\killstreaks\_flares::flares_playFx();
newTarget = missileTarget maps\mp\killstreaks\_flares::flares_deploy();
self Missile_SetTargetEnt( newTarget );
missileTarget = newTarget;
if( IsDefined( missileTarget.owner ) )
missileTarget.owner StopLocalSound( "missile_incoming" );
return;
}
if( curDist < minDist )
minDist = curDist;
if( curDist > minDist )
{
if( curDist > 1536 )
return;
if( IsDefined( missileTarget.owner ) )
{
missileTarget.owner stopLocalSound( "missile_incoming" );
if( level.teambased )
{
if( missileTarget.team != player.team )
RadiusDamage( self.origin, 1000, 1000, 1000, player, "MOD_EXPLOSIVE", "stinger_mp" );
}
else
{
RadiusDamage( self.origin, 1000, 1000, 1000, player, "MOD_EXPLOSIVE", "stinger_mp" );
}
}
self Hide();
wait( 0.05 );
self delete();
}
wait ( 0.05 );
}
}
handleIncomingSAM() // self == remote mortar
{
level endon ( "game_ended" );
self endon ( "death" );
self endon ( "remote_done" );
while( true )
{
level waittill ( "sam_fired", player, missileGroup, lockTarget );
if ( !IsDefined( lockTarget ) || (lockTarget != self) )
continue;
level thread samProximityDetonate( lockTarget, player, missileGroup );
}
}
samProximityDetonate( missileTarget, player, missileGroup )
{
missileTarget endon( "death" );
if( IsDefined( missileTarget.owner ) )
missileTarget.owner PlayLocalSound( "missile_incoming" );
sam_projectile_damage = 150; // this should match the gdt entry
sam_projectile_damage_radius = 1000;
minDist = [];
for( i = 0; i < missileGroup.size; i++ )
{
if( IsDefined( missileGroup[ i ] ) )
minDist[ i ] = Distance( missileGroup[ i ].origin, missileTarget GetPointInBounds( 0, 0, 0 ) );
else
minDist[ i ] = undefined;
}
while( true )
{
center = missileTarget GetPointInBounds( 0, 0, 0 );
curDist = [];
for( i = 0; i < missileGroup.size; i++ )
{
if( IsDefined( missileGroup[ i ] ) )
curDist[ i ] = Distance( missileGroup[ i ].origin, center );
}
for( i = 0; i < curDist.size; i++ )
{
if( IsDefined( curDist[ i ] ) )
{
// if one of the missiles in the group get close, set off flares and redirect them all
if( curDist[ i ] < 3000 && missileTarget.numFlares > 0 )
{
missileTarget.numFlares--;
missileTarget thread maps\mp\killstreaks\_flares::flares_playFx();
newTarget = missileTarget maps\mp\killstreaks\_flares::flares_deploy();
for( j = 0; j < missileGroup.size; j++ )
{
if( IsDefined( missileGroup[ j ] ) )
{
missileGroup[ j ] Missile_SetTargetEnt( newTarget );
}
}
if( IsDefined( missileTarget.owner ) )
missileTarget.owner StopLocalSound( "missile_incoming" );
return;
}
if( curDist[ i ] < minDist[ i ] )
minDist[ i ] = curDist[ i ];
if( curDist[ i ] > minDist[ i ] )
{
if( curDist[ i ] > 1536 )
continue;
if( IsDefined( missileTarget.owner ) )
{
missileTarget.owner StopLocalSound( "missile_incoming" );
if( level.teambased )
{
if( missileTarget.team != player.team )
RadiusDamage( missileGroup[ i ].origin, sam_projectile_damage_radius, sam_projectile_damage, sam_projectile_damage, player, "MOD_EXPLOSIVE", "sam_projectile_mp" );
}
else
{
RadiusDamage( missileGroup[ i ].origin, sam_projectile_damage_radius, sam_projectile_damage, sam_projectile_damage, player, "MOD_EXPLOSIVE", "sam_projectile_mp" );
}
}
missileGroup[ i ] Hide();
wait ( 0.05 );
missileGroup[ i ] delete();
}
}
}
wait ( 0.05 );
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,67 @@
#include maps\mp\_utility;
#include common_scripts\utility;
/*
Team Ammo Refill Killstreak: when the player uses this, everyone on their team gets an ammo refill
*/
init()
{
level.killStreakFuncs[ "team_ammo_refill" ] = ::tryUseTeamAmmoRefill;
}
tryUseTeamAmmoRefill( lifeId )
{
result = self giveTeamAmmoRefill();
if ( result )
self maps\mp\_matchdata::logKillstreakEvent( "team_ammo_refill", self.origin );
return ( result );
}
giveTeamAmmoRefill()
{
if( level.teambased )
{
foreach( teammate in level.players )
{
if( teammate.team == self.team )
{
teammate refillAmmo( true );
}
}
}
else
{
self refillAmmo( true );
}
level thread teamPlayerCardSplash( "used_team_ammo_refill", self );
return true;
}
refillAmmo( refillEquipment )
{
weaponList = self GetWeaponsListAll();
if ( refillEquipment )
{
if ( self _hasPerk( "specialty_tacticalinsertion" ) && self getAmmoCount( "flare_mp" ) < 1 )
self givePerkOffhand( "specialty_tacticalinsertion", false );
}
foreach ( weaponName in weaponList )
{
if ( isSubStr( weaponName, "grenade" ) || ( GetSubStr( weaponName, 0, 2 ) == "gl" ) )
{
if ( !refillEquipment || self getAmmoCount( weaponName ) >= 1 )
continue;
}
self giveMaxAmmo( weaponName );
}
self playLocalSound( "ammo_crate_use" );
}

1111
maps/mp/killstreaks/_uav.gsc Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,572 @@
#include maps\mp\_utility;
#include maps\mp\gametypes\_hud_util;
#include common_scripts\utility;
//============================================
// constants
//============================================
CONST_UPLINK_WEAPON = "killstreak_uplink_mp";
CONST_UPLINK_TIME = 30;
CONST_UPLINK_MIN = 0;
CONST_EYES_ON = 1;
CONST_UPLINK_FULL_RADAR = 2;
CONST_UPLINK_FAST_PING = 3;
CONST_DIRECTIONAL = 4;
CONST_UPLINK_MAX = 4;
CONST_FAST_SWEEP = "fast_radar";
CONST_NORMAL_SWEEP = "normal_radar";
CONST_HEAD_ICON_OFFSET = 42;
CONST_EMP_VFX = "emp_stun";
CONST_EMP_VFX_TAG = "tag_origin";
//============================================
// init
//============================================
init()
{
level.uplinks = [];
level.killstreakFuncs["uplink"] = ::tryUseUpLink;
level.killstreakFuncs["uplink_support"] = ::tryUseUpLink;
level.comExpFuncs = [];
level.comExpFuncs[ "giveComExpBenefits" ] = ::giveComExpBenefits;
level.comExpFuncs[ "removeComExpBenefits" ] = ::removeComExpBenefits;
level.comExpFuncs[ "getRadarStrengthForTeam" ] = ::getRadarStrengthForTeam;
level.comExpFuncs[ "getRadarStrengthForPlayer" ] = ::getRadarStrengthForPlayer;
unblockTeamRadar( "axis" );
unblockTeamRadar( "allies" );
level thread upLinkTracker();
level thread uplinkUpdateEyesOn();
config = spawnStruct();
config.streakName = "uplink";
config.weaponInfo = "ims_projectile_mp";
config.modelBase = "mp_satcom";
// config.modelDestroyed = "placeable_barrier_destroyed";
config.modelPlacement = "mp_satcom_obj";
config.modelPlacementFailed = "mp_satcom_obj_red";
config.modelBombSquad = "mp_satcom_bombsquad";
config.hintString = &"KILLSTREAKS_HINTS_UPLINK_PICKUP";
config.placeString = &"KILLSTREAKS_HINTS_UPLINK_PLACE";
config.cannotPlaceString = &"KILLSTREAKS_HINTS_UPLINK_CANNOT_PLACE";
config.headIconHeight = CONST_HEAD_ICON_OFFSET;
config.splashName = "used_uplink";
config.lifeSpan = CONST_UPLINK_TIME;
// config.goneVO = "satcom_gone";
config.maxHealth = 500;
config.allowMeleeDamage = true;
config.allowEmpDamage = true;
config.damageFeedback = "trophy";
config.xpPopup = "destroyed_uplink";
config.destroyedVO = "satcom_destroyed";
//config.onCreateDelegate = ::createObject;
config.placementHeightTolerance = 30.0;
config.placementRadius = 16.0;
config.placementOffsetZ = 16;
config.onPlacedDelegate = ::onPlaced;
config.onCarriedDelegate = ::onCarried;
config.placedSfx = "mp_killstreak_satcom_deploy";
config.activeSfx = "mp_killstreak_satcom_loop";
config.onMovingPlatformCollision = ::uplink_override_moving_platform_death;
// config.onDamagedDelegate = ::onDamaged;
config.onDeathDelegate = ::onDeath;
config.onDestroyedDelegate = ::onDestroyed; // when killed by a player
config.deathVfx = loadfx( "vfx/gameplay/mp/killstreaks/vfx_ballistic_vest_death" );
// config.onDeactivateDelegate = ::onDeactivated;
// config.onActivateDelegate = ::onActivated;
level.placeableConfigs[ "uplink" ] = config;
level.placeableConfigs[ "uplink_support" ] = config;
}
//============================================
// upLinkTracker
//============================================
upLinkTracker()
{
level endon ( "game_ended" );
while( true )
{
level waittill( "update_uplink" );
level childthread updateAllUplinkThreads();
}
}
// 2013-08-27 wallace: I wonder if it would be ok to just update the uplink strength for a particular player/team on notify
// since the uplinks know who they belong to
updateAllUplinkThreads()
{
self notify("updateAllUplinkThreads");
self endon("updateAllUplinkThreads");
level childthread comExpNotifyWatcher();
if( level.teamBased )
{
level childthread updateTeamUpLink( "axis" );
level childthread updateTeamUpLink( "allies" );
}
else
{
level childthread updatePlayerUpLink();
}
// Update the the UpLink for Com Specialist players
level childthread updateComExpUpLink();
}
comExpNotifyWatcher()
{
// Guarantee that the previous update functions have finished before we proceed into updating com exp
teamsFinished = [];
if ( !level.teamBased )
level waittill ( "radar_status_change_players" );
else
{
while ( teamsFinished.size < 2 )
{
level waittill ( "radar_status_change", team );
teamsFinished[ teamsFinished.size ] = team;
}
}
level notify ( "start_com_exp" );
}
//============================================
// updateTeamUpLink
//============================================
updateTeamUpLink( team )
{
currentStrengthForTeam = getRadarStrengthForTeam( team );
shouldBeEyesOn = ( currentStrengthForTeam == CONST_EYES_ON );
shouldBeFullRadar = ( currentStrengthForTeam >= CONST_UPLINK_FULL_RADAR );
shouldBeFastSweep = ( currentStrengthForTeam >= CONST_UPLINK_FAST_PING );
shouldBeDirectional = ( currentStrengthForTeam >= CONST_DIRECTIONAL );
if( shouldBeFullRadar )
{
unblockTeamRadar( team );
}
if( shouldBeFastSweep )
{
level.radarMode[team] = CONST_FAST_SWEEP;
}
else
{
level.radarMode[team] = CONST_NORMAL_SWEEP;
}
foreach( player in level.participants )
{
if ( !IsDefined(player) )
continue;
if( player.team != team )
continue;
player.shouldBeEyesOn = shouldBeEyesOn;
player SetEyesOnUplinkEnabled( shouldBeEyesOn );
player.radarMode = level.radarMode[player.team];
player.radarShowEnemyDirection = shouldBeDirectional;
player updateSatcomActiveOmnvar( team );
wait(0.05); // Setting all these player variables can get expensive, so spread it out over multiple frames
}
setTeamRadar( team, shouldBeFullRadar );
level notify( "radar_status_change", team );
}
//============================================
// updatePlayerUpLink
//============================================
updatePlayerUpLink()
{
foreach ( player in level.participants )
{
if ( !IsDefined(player) )
continue;
currentStrengthForSelf = getRadarStrengthForPlayer( player );
setPlayerRadarEffect( player, currentStrengthForSelf );
player updateSatcomActiveOmnvar();
wait(0.05); // Setting all these player variables can get expensive, so spread it out over multiple frames
}
level notify( "radar_status_change_players" );
}
//============================================
// updateComExpUpLink
//============================================
updateComExpUpLink()
{
level waittill ( "start_com_exp" );
foreach ( player in level.participants )
{
if ( !IsDefined(player) )
continue;
player giveComExpBenefits();
wait(0.05); // Setting all these player variables can get expensive, so spread it out over multiple frames
}
}
// give/remove ComExpBenefits
// encapsulate the innerworkings of uplink system for the perk's set/unset functions
giveComExpBenefits() // self == player
{
if( ( self _hasPerk( "specialty_comexp" ) ) )
{
radarStrength = getRadarStrengthForComExp( self );
setPlayerRadarEffect( self, radarStrength );
self updateSatcomActiveOmnvar();
}
}
// since multiple functions are trying to set the ui_satcom_active omnvar, I've made it one function that will handle the wiretap perk functionality
// this will make sure we always get it right and get the correct amount of radar
updateSatcomActiveOmnvar( team ) // self == player
{
radarStrength = 0;
if( IsDefined( team ) )
radarStrength = getRadarStrengthForTeam( team );
else
radarStrength = getRadarStrengthForPlayer( self );
if( self _hasPerk( "specialty_comexp" ) )
radarStrength = getRadarStrengthForComExp( self );
if( radarStrength > CONST_UPLINK_MIN )
self SetClientOmnvar( "ui_satcom_active", true );
else
self SetClientOmnvar( "ui_satcom_active", false );
}
removeComExpBenefits() // self == player
{
// mainly for clearing out radarMode, the other stuff will get set by the appropriate update function
self.shouldBeEyesOn = false;
self SetEyesOnUplinkEnabled( false );
self.radarShowEnemyDirection = false;
self.radarMode = CONST_NORMAL_SWEEP;
self.hasRadar = false;
self.isRadarBlocked = false;
}
setPlayerRadarEffect( player, radarStrength )
{
shouldBeEyesOn = ( radarStrength == CONST_EYES_ON );
shouldBeFullRadar = ( radarStrength >= CONST_UPLINK_FULL_RADAR );
shouldBeFastSweep = ( radarStrength >= CONST_UPLINK_FAST_PING );
shouldBeDirectional = ( radarStrength >= CONST_DIRECTIONAL );
player.shouldBeEyesOn = shouldBeEyesOn;
player SetEyesOnUplinkEnabled( shouldBeEyesOn );
player.radarShowEnemyDirection = shouldBeDirectional;
player.radarMode = CONST_NORMAL_SWEEP;
player.hasRadar = shouldBeFullRadar;
player.isRadarBlocked = false;
if( shouldBeFastSweep )
{
player.radarMode = CONST_FAST_SWEEP;
}
}
//============================================
// tryUseUpLink
//============================================
tryUseUpLink( lifeId, streakName )
{
result = self maps\mp\killstreaks\_placeable::givePlaceable( streakName );
if( result )
{
// we want both to log to the same event?
self maps\mp\_matchdata::logKillstreakEvent( "uplink", self.origin );
}
// we're done carrying for sure and sometimes this might not get reset
// this fixes a bug where you could be carrying and have it in a place where it won't plant, get killed, now you can't scroll through killstreaks
self.isCarrying = undefined;
return result;
}
onCarried( streakName ) // self == obj
{
entNum = self GetEntityNumber();
if ( IsDefined( level.uplinks[ entNum ] ) )
{
self stopUplink();
}
}
onPlaced( streakName ) // self == obj
{
config = level.placeableConfigs[ streakName ];
self.owner notify( "uplink_deployed" );
self SetModel( config.modelBase );
self.immediateDeath = false;
self SetOtherEnt(self.owner);
self make_entity_sentient_mp( self.owner.team, true );
self.config = config;
self startUplink( true );
self thread watchEMPDamage();
// 2013-07-25 wallace: not sure if we need this still
/*
wait( 0.75 );
if( IsDefined(self) && (self touchingBadTrigger()) )
self notify( "death" );
*/
}
// Operations that need to be done when the uplink is first spawned
// AND when it resumes from
startUplink( playOpenAnim )
{
addUplinkToLevelList( self );
self thread playUplinkAnimations( playOpenAnim );
// play operation sound
self PlayLoopSound( self.config.activeSfx );
}
stopUplink()
{
self maps\mp\gametypes\_weapons::stopBlinkingLight();
self ScriptModelClearAnim();
if ( IsDefined( self.bombSquadModel ) )
{
self.bombSquadModel ScriptModelClearAnim();
}
removeUplinkFromLevelList( self );
self StopLoopSound();
}
#using_animtree( "animated_props" );
//============================================
// playUplinkAnimations
//============================================
playUplinkAnimations( playOpenAnim ) // self = uplink
{
self endon( "emp_damage" );
self endon( "death" );
self endon( "carried" );
if ( playOpenAnim )
{
waitTime = GetNotetrackTimes( %Satcom_killStreak, "stop anim" );
animLength = GetAnimLength( %Satcom_killStreak );
self ScriptModelPlayAnim( "Satcom_killStreak" );
if ( IsDefined( self.bombSquadModel ) )
{
self.bombSquadModel ScriptModelPlayAnim( "Satcom_killStreak" );
}
wait( waitTime[0] * animLength );
}
self ScriptModelPlayAnim( "Satcom_killStreak_idle" );
if ( IsDefined( self.bombSquadModel ) )
{
self.bombSquadModel ScriptModelPlayAnim( "Satcom_killStreak_idle" );
}
// play vfx
self thread maps\mp\gametypes\_weapons::doBlinkingLight( "tag_fx" );
}
onDestroyed( streakName, attacker, owner, sMeansOfDeath )
{
attacker notify( "destroyed_equipment" );
}
onDeath( streakName, attacker, owner, sMeansOfDeath )
{
self maps\mp\gametypes\_weapons::stopBlinkingLight();
self maps\mp\gametypes\_weapons::equipmentDeathVfx();
removeUplinkFromLevelList( self );
self ScriptModelClearAnim();
if(!self.immediateDeath)
{
wait( 3.0 );
}
self maps\mp\gametypes\_weapons::equipmentDeleteVfx();
}
//============================================
// addUplinkToLevelList
//============================================
addUplinkToLevelList( obj )
{
entNum = obj GetEntityNumber();
level.uplinks[entNum] = obj;
level notify( "update_uplink" );
}
//============================================
// removeUplinkFromLevelList
//============================================
removeUplinkFromLevelList( obj )
{
entNum = obj GetEntityNumber();
level.uplinks[ entNum ] = undefined;
level notify( "update_uplink" );
}
//============================================
// getRadarStrengthForTeam
//============================================
getRadarStrengthForTeam( team )
{
currentRadarStrength = 0;
foreach( satellite in level.uplinks )
{
if( IsDefined(satellite) && (satellite.team == team) )
currentRadarStrength++;
}
// give eyes-on when heli sniper is in the air
if ( currentRadarStrength == 0
&& IsDefined( level.heliSniperEyesOn )
&& level.heliSniperEyesOn.team == team
)
{
currentRadarStrength++;
}
return clamp( currentRadarStrength, CONST_UPLINK_MIN, CONST_UPLINK_MAX );
}
//============================================
// getRadarStrengthForPlayer
//============================================
getRadarStrengthForPlayer( player )
{
currentRadarStrength = 0;
foreach( satellite in level.uplinks )
{
if( IsDefined( satellite ) )
{
// not sure if it's safe to remove an item while we're iterating through it
if ( IsDefined( satellite.owner ) )
{
if ( satellite.owner.guid == player.guid)
currentRadarStrength++;
}
else
{
// if the owner has disconnected, stop tracking this satellite
entNum = satellite GetEntityNumber();
level.uplinks[ entNum ] = undefined;
}
}
}
// in FFA, eyes-on is useless, so automatically bump the benefit to tier 2
if ( !level.teamBased && currentRadarStrength > 0 )
currentRadarStrength++;
return clamp( currentRadarStrength, CONST_UPLINK_MIN, CONST_UPLINK_MAX );
}
//============================================
// getRadarStrengthForComExp
//============================================
getRadarStrengthForComExp( player )
{
currentRadarStrength = 0;
// Add to the radar strength regardless of which team places down the SAT COM
foreach( satellite in level.uplinks )
{
if( IsDefined(satellite) )
currentRadarStrength++;
}
// in FFA, eyes-on is useless, so automatically bump the benefit to tier 2
if ( !level.teamBased && currentRadarStrength > 0 )
currentRadarStrength++;
return clamp( currentRadarStrength, CONST_UPLINK_MIN, CONST_UPLINK_MAX );
}
uplink_override_moving_platform_death( data )
{
self.immediateDeath = true;
self notify( "death" );
}
watchEMPDamage()
{
// self endon( "carried" );
self endon( "death" );
level endon( "game_ended" );
while( true )
{
// this handles any flash or concussion damage
self waittill( "emp_damage", attacker, duration );
self maps\mp\gametypes\_weapons::equipmentEmpStunVfx();
self stopUplink();
wait( duration );
self startUplink( false );
}
}
uplinkUpdateEyesOn()
{
level endon( "game_ended" );
while (true)
{
level waittill( "player_spawned", player );
eyesOn = ( IsDefined( player.shouldBeEyesOn ) && player.shouldBeEyesOn );
player SetEyesOnUplinkEnabled( eyesOn );
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,622 @@
#include maps\mp\_utility;
#include common_scripts\utility;
#include maps\mp\agents\_scriptedAgents;
#include maps\mp\agents\_agent_utility;
#include maps\mp\gametypes\_damage;
//===========================================
// constants
//===========================================
CONST_KILLSTREAK_NAME = "mine_level_killstreak";
CONST_WOLF_HEALTH = 200; // default dogs are 250
//===========================================
// init
//===========================================
init()
{
level.killStreakFuncs[CONST_KILLSTREAK_NAME] = ::tryUseWolfpack;
level.killstreakWeildWeapons[ "killstreak_wolfpack_mp" ] = CONST_KILLSTREAK_NAME;
}
//===========================================
// setup_callbacks
//===========================================
setup_callbacks()
{
level.agent_funcs["wolf"] = level.agent_funcs["dog"];
level.agent_funcs["wolf"]["spawn"] = ::spawn_dog;
level.agent_funcs["wolf"]["on_killed"] = ::on_agent_dog_killed;
level.agent_funcs["wolf"]["on_damaged"] = maps\mp\agents\_agents::on_agent_generic_damaged;
level.agent_funcs["wolf"]["on_damaged_finished"] = maps\mp\killstreaks\_dog_killstreak::on_damaged_finished;
// !!! CHANGE THIS
level.agent_funcs["wolf"]["think"] = ::think_init;
}
//===========================================
// tryUseDog
//===========================================
tryUseWolfpack( lifeId, streakName )
{
setup_callbacks();
return useDog();
}
//===========================================
// useDog
//===========================================
CONST_MAX_ACTIVE_KILLSTREAK_AGENTS_PER_PLAYER = 2;
useDog()
{
/*
// limit the number of active "dog" agents allowed per player
if( isDefined(self.hasDog) && self.hasDog )
{
dog_type = self GetCommonPlayerDataReservedInt( "mp_dog_type" );
if( dog_type == 1 )
self iPrintLnBold( &"KILLSTREAKS_ALREADY_HAVE_WOLF" );
else
self iPrintLnBold( &"KILLSTREAKS_ALREADY_HAVE_DOG" );
return false;
}
// limit the number of active "dog" agents allowed per game
if( getNumActiveAgents( "dog" ) >= CONST_MAX_ACTIVE_KILLSTREAK_DOGS_PER_GAME )
{
self iPrintLnBold( &"KILLSTREAKS_TOO_MANY_DOGS" );
return false;
}
*/
// limit the number of active agents allowed per player
if( getNumOwnedActiveAgents( self ) >= CONST_MAX_ACTIVE_KILLSTREAK_AGENTS_PER_PLAYER )
{
self iPrintLnBold( &"KILLSTREAKS_AGENT_MAX" );
return false;
}
// TODO: we should probably do a queue system for these, so the player can call it but it'll go into a queue for when an agent dies to open up a spot
// limit the number of active agents allowed per player
maxagents = GetMaxAgents();
if( getNumActiveAgents() >= maxagents )
{
self iPrintLnBold( &"KILLSTREAKS_UNAVAILABLE" );
return false;
}
// make sure the player is still alive before the agent trys to spawn on the player
if( !isReallyAlive( self ) )
{
return false;
}
result = self spawnWolf( 1 );
if ( result )
{
self PlaySound( "mp_mine_wolf_spawn" );
self thread spawnWolfPack();
}
return result;
}
spawnWolf( id )
{
// try to spawn the agent on a path node near the player
// nearestPathNode = self getValidSpawnPathNodeNearPlayer( true );
// if( !IsDefined(nearestPathNode) )
// {
// return false;
// }
// find an available agent
agent = maps\mp\agents\_agent_common::connectNewAgent( "wolf" , self.team );
if( !IsDefined( agent ) )
{
return false;
}
// set the agent to the player's team
agent set_agent_team( self.team, self );
// !!! CHANGE THIS
structName = "wolf_spawn_0" + id;
wolfStruct = getstruct( structName, "targetname" );
spawnOrigin = wolfStruct.origin;
spawnAngles = self.angles;
agent.wolfId = id;
// pick a path
agent.pathNodeArray = getstructarray( "wolf_path_0" + id, "script_noteworthy" );
agent thread [[ agent agentFunc("spawn") ]]( spawnOrigin, spawnAngles, self );
agent _setNameplateMaterial( "player_name_bg_green_dog", "player_name_bg_red_dog" );
return true;
}
CONST_NUM_WOLVES = 3;
CONST_NUM_TOTAL_WOLVES = 6;
spawnWolfPack()
{
self endon( "death" );
self endon( "disconnect" );
level endon( "game_ended" );
// spawn the initial set of solves
for ( i = 2; i <= CONST_NUM_WOLVES; i++ )
{
wait( 0.75 );
spawnWolf( i );
}
numWolvesLeft = CONST_NUM_TOTAL_WOLVES - CONST_NUM_WOLVES;
while ( true )
{
level waittill( "wolf_killed", id );
if ( numWolvesLeft > 0 )
{
wait ( 0.75 );
spawnWolf( id );
numWolvesLeft--;
}
else
{
break;
}
}
}
//=======================================================
// on_agent_dog_killed
//=======================================================
on_agent_dog_killed( eInflictor, eAttacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, timeOffset, deathAnimDuration )
{
self.isActive = false;
self.hasDied = false;
//agent dogs in safeguard do not have an owner.
if( IsDefined( self.owner ) )
self.owner.hasDog = false;
eAttacker.lastKillDogTime = GetTime();
if ( IsDefined( self.animCBs.OnExit[ self.aiState ] ) )
self [[ self.animCBs.OnExit[ self.aiState ] ]] ();
// award XP for killing agents
if( isPlayer( eAttacker ) && IsDefined(self.owner) && (eAttacker != self.owner) )
{
self maps\mp\gametypes\_damage::onKillstreakKilled( eAttacker, sWeapon, sMeansOfDeath, iDamage, "destroyed_ks_wolf" );
}
self SetAnimState( "death" );
animEntry = self GetAnimEntry();
animLength = GetAnimLength( animEntry );
deathAnimDuration = int( animLength * 1000 ); // duration in milliseconds
self.body = self CloneAgent( deathAnimDuration );
self PlaySound( "anml_wolf_shot_death" );
level notify( "wolf_killed", self.wolfId );
self maps\mp\agents\_agent_utility::deactivateAgent();
self notify( "killanimscript" );
// notify death so that we can spawn a new agent
}
spawn_dog( optional_spawnOrigin, optional_spawnAngles, optional_owner ) // self == agent
{
dog_type = 1; // 0 == dog, 1 == wolf
dog_model = "mp_fullbody_wolf_c";
wolf_type = 1;
if ( self.wolfId == 1 )
{
dog_model = "mp_fullbody_wolf_b";
wolf_type = 0;
}
if( IsHairRunning() )
dog_model = dog_model + "_fur";
self SetModel( dog_model );
self.species = "dog";
self.OnEnterAnimState = maps\mp\agents\dog\_dog_think::OnEnterAnimState;
// allow killstreaks to pass in specific spawn locations
if( IsDefined(optional_spawnOrigin) && IsDefined(optional_spawnAngles) )
{
spawnOrigin = optional_spawnOrigin;
spawnAngles = optional_spawnAngles;
}
else
{
spawnPoint = self [[level.getSpawnPoint]]();
spawnOrigin = spawnpoint.origin;
spawnAngles = spawnpoint.angles;
}
self activateAgent();
self.spawnTime = GetTime();
self.lastSpawnTime = GetTime();
self.bIsWolf = true;
animclass = "wolf_animclass";
// !!! CHANGE THIS
self maps\mp\agents\dog\_dog_think::init();
self.watchAttackStateFunc = ::watchAttackState; // overwrite the dog's version of this to play custom sounds
// called from code when an agent is done initializing after AddAgent is called
// this should set up any state specific to this agent and game
self SpawnAgent( spawnOrigin, spawnAngles, animclass, 15, 40, optional_owner );
level notify( "spawned_agent", self );
self maps\mp\agents\_agent_common::set_agent_health( CONST_WOLF_HEALTH );
// must set the team after SpawnAgent to fix a bug with weapon crosshairs and nametags
if( IsDefined(optional_owner) )
{
self set_agent_team( optional_owner.team, optional_owner );
}
self SetThreatBiasGroup( "Dogs" );
self TakeAllWeapons();
// hide the dog, let the whistle happen, then show the dog
if( IsDefined(self.owner) )
{
self Hide();
wait( 1.0 ); // not sure what to endon for this
// The dog could have died during the 1 second wait (for example if he spawned in a kill trigger), so if that happened,
// don't start thinking since it will cause SREs due to him missing a self.agent_type
if ( !IsAlive(self) )
return;
self Show();
}
self thread [[ self agentFunc("think") ]]();
wait( 0.1 );
if ( IsHairRunning() )
{
furFX = level.wolfFurFX[ wolf_type ];
assert( IsDefined( furFX ) );
PlayFXOnTag( furFX, self, "tag_origin" );
}
}
// ------------------------------------------
// Adapted from dog_think and mp_dome_ns_alien_think
think_init()
{
self maps\mp\agents\dog\_dog_think::setupDogState();
self.wolfGoalPos = get_closest( self.origin , self.pathNodeArray );
self thread think();
self thread maps\mp\agents\dog\_dog_think::watchOwnerDeath();
self thread maps\mp\agents\dog\_dog_think::watchOwnerTeamChange();
self thread maps\mp\agents\dog\_dog_think::WaitForBadPath();
self thread maps\mp\agents\dog\_dog_think::WaitForPathSet();
/#
self thread maps\mp\agents\dog\_dog_think::debug_dog();
#/
}
think()
{
self endon( "death" );
level endon( "game_ended" );
if ( IsDefined( self.owner ) )
{
self endon( "owner_disconnect" );
self thread maps\mp\agents\dog\_dog_think::destroyOnOwnerDisconnect( self.owner );
}
self thread [[self.watchAttackStateFunc]]();
self thread maps\mp\agents\dog\_dog_think::MonitorFlash();
while ( true )
{
/#
if ( self maps\mp\agents\dog\_dog_think::ProcessDebugMode() )
continue;
#/
if ( self.aiState != "melee" && !self.stateLocked && self maps\mp\agents\dog\_dog_think::readyToMeleeTarget() && !self maps\mp\agents\dog\_dog_think::DidPastMeleeFail() )
self ScrAgentBeginMelee( self.curMeleeTarget );
switch ( self.aiState )
{
case "idle":
self updateMove();
break;
case "move":
self updateMove();
break;
case "melee":
self maps\mp\agents\dog\_dog_think::updateMelee();
break;
}
wait( 0.05 );
}
}
updateMove()
{
self UpdateMoveState();
}
UpdateMoveState()
{
//IPrintLnBold ("updating move state");
if ( self.bLockGoalPos )
return;
self.prevMoveState = self.moveState;
attackPoint = undefined;
bRefreshGoal = false;
bWantedPursuitButFollowInstead = false;
cBadPathTimeOut = 500;
if ( self.bHasBadPath && GetTime() - self.lastBadPathTime < cBadPathTimeOut )
{
self.moveState = "follow";
bRefreshGoal = true;
}
else
{
self.moveState = self maps\mp\agents\dog\_dog_think::GetMoveState();
}
if ( self.moveState == "pursuit" )
{
attackPoint = self maps\mp\agents\dog\_dog_think::GetAttackPoint( self.enemy );
bLastBadMeleeTarget = false;
if ( IsDefined( self.lastBadPathTime ) && ( GetTime() - self.lastBadPathTime < 3000 ) )
{
if ( Distance2DSquared( attackPoint, self.lastBadPathGoal ) < 16 )
bLastBadMeleeTarget = true;
else if ( IsDefined( self.lastBadPathMoveState ) && self.lastBadPathMoveState == "pursuit" && Distance2DSquared( self.lastBadPathUltimateGoal, self.enemy.origin ) < 16 )
bLastBadMeleeTarget = true;
}
if ( !isReallyAlive( self.enemy )
|| bLastBadMeleeTarget
|| self maps\mp\agents\dog\_dog_think::wantToAttackTargetButCant( true )
|| self maps\mp\agents\dog\_dog_think::DidPastPursuitFail( self.enemy )
)
{
self.moveState = "follow";
bWantedPursuitButFollowInstead = true;
}
}
self maps\mp\agents\dog\_dog_think::SetPastPursuitFailed( bWantedPursuitButFollowInstead );
if ( self.moveState == "follow" )
{
self.curMeleeTarget = undefined;
self.moveMode = self maps\mp\agents\dog\_dog_think::GetFollowMoveMode( self.moveMode );
self.bArrivalsEnabled = true;
myPos = self GetPathGoalPos();
if ( !IsDefined( myPos ) )
myPos = self.origin;
if ( GetTime() - self.timeOfLastDamage < 5000 )
bRefreshGoal = true;
distFromGoalPos = Distance2DSquared( self.origin, self.wolfGoalPos.origin );
if ( ( distFromGoalPos < 800 ) )
{
self pickNewLocation();
}
self ScrAgentSetGoalPos( self.wolfGoalPos.origin );
if ( bRefreshGoal == true )
{
self ScrAgentSetGoalPos( self.origin );
}
}
else if ( self.moveState == "pursuit" )
{
self.curMeleeTarget = self.enemy;
self.moveMode = "sprint";
self.bArrivalsEnabled = false;
assert( IsDefined( attackPoint ) );
self ScrAgentSetGoalPos( attackPoint );
}
}
pickNewLocation()
{
// The new goal is the one targeted by the current goal
self.wolfGoalPos = GetStruct ( self.wolfGoalPos.target, "targetname" );
}
get_closest( origin, points, maxDist )
{
Assert( points.size );
closestPoint = points[ 0 ];
dist = Distance( origin, closestPoint.origin );
for ( index = 0; index < points.size; index++ )
{
testDist = Distance( origin, points[ index ].origin );
if ( testDist >= dist )
continue;
dist = testDist;
closestPoint = points[ index ];
}
if ( !isDefined( maxDist ) || dist <= maxDist )
return closestPoint;
return undefined;
}
// !!! UGH, this is awful
// dupliated directly from dog_think because I need to replace the sounds played by the wolves
// wish there was a better way
watchAttackState() // self == dog
{
self endon( "death" );
level endon( "game_ended" );
while ( true )
{
if ( self.aiState == "melee" )
{
if ( self.attackState != "melee" )
{
self.attackState = "melee";
self SetSoundState( undefined );
}
}
else if ( self.moveState == "pursuit" ) //( self wantsToAttackTarget() )
{
if ( self.attackState != "attacking" )
{
self.attackState = "attacking";
self SetSoundState( "bark", "attacking" );
}
}
else //if( !self wantsToAttackTarget() )
{
if ( self.attackState != "warning" )
{
if ( self maps\mp\agents\dog\_dog_think::wantsToGrowlAtTarget() )
{
self.attackState = "warning";
self SetSoundState( "growl", "warning" );
}
else
{
self.attackState = self.aiState;
self SetSoundState( "pant" );
}
}
else
{
if ( !self maps\mp\agents\dog\_dog_think::wantsToGrowlAtTarget() )
{
self.attackState = self.aiState;
self SetSoundState( "pant" );
}
}
}
wait( 0.05 );
}
}
SetSoundState( state, attackState )
{
if ( !IsDefined( state ) )
{
self notify( "end_dog_sound" );
self.soundState = undefined;
return;
}
if ( !IsDefined( self.soundState ) || self.soundState != state )
{
self notify( "end_dog_sound" );
self.soundState = state;
if ( state == "bark" )
{
self thread playBark( attackState );
}
else if ( state == "growl" )
{
self thread playGrowl( attackState );
}
else if ( state == "pant" )
{
self thread maps\mp\agents\dog\_dog_think::playPanting();
}
else
{
assertmsg( "unknown sound state " + state );
}
}
}
playBark( state ) // self == dog
{
self endon( "death" );
level endon( "game_ended" );
self endon( "end_dog_sound" );
if( !isDefined( self.barking_sound ) )
{
self PlaySoundOnMovingEnt( "mine_wolf_bark" );
self.barking_sound = true;
self watchBarking();
}
}
watchBarking() // self == dog
{
self endon( "death" );
level endon( "game_ended" );
self endon( "end_dog_sound" );
wait( RandomIntRange( 4, 6 ) ); // allow wolves to bark more frequently than dogs
self.barking_sound = undefined;
}
playGrowl( state ) // self == dog
{
self endon( "death" );
level endon( "game_ended" );
self endon( "end_dog_sound" );
if ( IsDefined( self.lastGrowlPlayedTime ) && GetTime() - self.lastGrowlPlayedTime < 3000 )
wait( 3 );
// while the dog is in this state randomly play growl
while ( true )
{
self.lastGrowlPlayedTime = GetTime();
self PlaySoundOnMovingEnt( "mine_wolf_growl" );
wait( RandomIntRange( 3, 6 ) );
}
}