1658 lines
45 KiB
Plaintext
1658 lines
45 KiB
Plaintext
#include maps\mp\_utility;
|
|
#include common_scripts\utility;
|
|
#include maps\mp\gametypes\_hud_util;
|
|
|
|
|
|
CONST_MISSILE_HEALTH = 10;
|
|
CONST_HOSTILE = "hud_fofbox_hostile";
|
|
CONST_HOSTIL_LOCK = "hud_fofbox_hostile_ms_target";
|
|
CONST_SELF = "hud_fofbox_self";
|
|
|
|
|
|
init()
|
|
{
|
|
SetDvar( "missileRemoteSteerPitchRange", "60 87" );
|
|
|
|
level._missile_strike_setting = [];
|
|
|
|
level._missile_strike_setting[ "Particle_FX" ] = SpawnStruct();
|
|
level._missile_strike_setting[ "Particle_FX" ].Gas = LoadFX( "vfx/unique/vfx_killstreak_missilestrike_dna" );
|
|
level._missile_strike_setting[ "Particle_FX" ].GasFriendly = LoadFX( "vfx/unique/vfx_killstreak_missilestrike_dna_friendly" );
|
|
|
|
level._missile_strike_setting[ "Audio" ] = SpawnStruct();
|
|
|
|
level._missile_strike_setting[ "Launch_Value" ] = SpawnStruct();
|
|
mapname = getDvar( "mapname" );
|
|
if ( mapname == "mp_suburbia" )
|
|
{
|
|
level._missile_strike_setting[ "Launch_Value" ].Vert = 7000;
|
|
level._missile_strike_setting[ "Launch_Value" ].Horz = 10000;
|
|
level._missile_strike_setting[ "Launch_Value" ].TargetDest = 2000;
|
|
}
|
|
else if ( mapname == "mp_mainstreet" )
|
|
{
|
|
level._missile_strike_setting[ "Launch_Value" ].Vert = 7000;
|
|
level._missile_strike_setting[ "Launch_Value" ].Horz = 10000;
|
|
level._missile_strike_setting[ "Launch_Value" ].TargetDest = 2000;
|
|
}
|
|
else
|
|
{
|
|
level._missile_strike_setting[ "Launch_Value" ].Vert = 24000;
|
|
level._missile_strike_setting[ "Launch_Value" ].Horz = 7000;
|
|
level._missile_strike_setting[ "Launch_Value" ].TargetDest = 1500;
|
|
|
|
}
|
|
|
|
level.rockets = [];
|
|
level.missile_strike_gas_clouds = [];
|
|
|
|
level.killstreakFuncs[ "missile_strike" ] = ::tryUseMissileStrike;
|
|
|
|
level.killstreakWieldWeapons["remotemissile_projectile_mp"] = "missile_strike";
|
|
level.killstreakWieldWeapons["remotemissile_projectile_gas_mp"] = "missile_strike";
|
|
level.killstreakWieldWeapons["remotemissile_projectile_cluster_parent_mp"] = "missile_strike";
|
|
level.killstreakWieldWeapons["remotemissile_projectile_cluster_child_mp"] = "missile_strike";
|
|
level.killstreakWieldWeapons["remotemissile_projectile_cluster_child_hellfire_mp"] = "missile_strike";
|
|
level.killstreakWieldWeapons["remotemissile_projectile_secondary_mp"] = "missile_strike";
|
|
level.killstreakWieldWeapons["killstreak_strike_missile_gas_mp"] = "missile_strike";
|
|
|
|
level.remotemissile_fx[ "explode" ] = LoadFX( "vfx/explosion/rocket_explosion_airburst" );
|
|
|
|
thread OnPlayerConnect();
|
|
}
|
|
|
|
|
|
tryUseMissileStrike( lifeId, modules )
|
|
{
|
|
result = self maps\mp\killstreaks\_killstreaks::initRideKillstreak("missile_strike");
|
|
if ( result != "success" )
|
|
return false;
|
|
|
|
self playerSaveAngles();
|
|
|
|
self setUsingRemote( "missile_strike" );
|
|
MissileWeapon = self BuildWeaponSettings( modules );
|
|
|
|
if( IsDefined( level.isHorde ) && level.isHorde )
|
|
{
|
|
self.missileWeapon = MissileWeapon;
|
|
|
|
//Track if this is a Missile Strike called as a built-in Demolitions scoretreak in co-op so we can start the class cool-down
|
|
//and also start cooldown on the owner when another player picks up the built-in scorestreak goliath
|
|
if ( self.killstreakIndexWeapon == 1 )
|
|
self notify ( "used_horde_missile_strike" );
|
|
}
|
|
|
|
level thread _fire( lifeId, self, MissileWeapon );
|
|
|
|
return true;
|
|
}
|
|
|
|
BuildWeaponSettings( modules )
|
|
{
|
|
// Find Weapon Type
|
|
weapon = spawnstruct();
|
|
|
|
weapon.modules = modules;
|
|
|
|
// Determine projectile type
|
|
weapon.name = "remotemissile_projectile_mp";
|
|
weapon.gasMissile = false;
|
|
weapon.clusterMissile = false;
|
|
weapon.clusterHellfire = false;
|
|
weapon.clusterSpiral = false;
|
|
|
|
if ( array_contains( weapon.modules, "missile_strike_hellfire" ) )
|
|
{
|
|
weapon.name = "remotemissile_projectile_cluster_parent_mp";
|
|
weapon.clusterMissile = true;
|
|
weapon.clusterHellfire = true;
|
|
}
|
|
if ( array_contains( weapon.modules, "missile_strike_cluster" ) )
|
|
{
|
|
weapon.name = "remotemissile_projectile_cluster_parent_mp";
|
|
weapon.clusterMissile = true;
|
|
weapon.clusterSpiral = true;
|
|
thread preSpawnClusterRotationEntities( weapon );
|
|
}
|
|
else if ( array_contains( weapon.modules, "missile_strike_chem" ) )
|
|
{
|
|
weapon.name = "remotemissile_projectile_gas_mp";
|
|
weapon.gasMissile = true;
|
|
}
|
|
|
|
// Determine ammo count
|
|
weapon.RocketAmmo = 1;
|
|
if ( array_contains( weapon.modules, "missile_strike_extra_1" ) )
|
|
weapon.RocketAmmo++;
|
|
if ( array_contains( weapon.modules, "missile_strike_extra_2" ) )
|
|
weapon.RocketAmmo++;
|
|
|
|
return weapon;
|
|
}
|
|
|
|
preSpawnClusterRotationEntities( MissileWeapon )
|
|
{
|
|
// it is expensive to spawn this many entities on one frame when the cluster missile is fired so prespawn some of them, about 2 per snapshot
|
|
prespawnOrigin = ( 0, 0, -1000 );
|
|
|
|
MissileWeapon.preSpawnedKillcamEnt = Spawn( "script_model", prespawnOrigin );
|
|
MissileWeapon.preSpawnedKillcamEnt SetContents(0);
|
|
MissileWeapon.preSpawnedKillcamEnt SetScriptMoverKillCam( "large explosive" );
|
|
waitframe();
|
|
MissileWeapon.preSpawnedRotationEnts = [];
|
|
MissileWeapon.preSpawnedIndex = 0;
|
|
for ( i = 0; i < 12; i++ )
|
|
{
|
|
ent = Spawn( "script_origin", prespawnOrigin );
|
|
MissileWeapon.preSpawnedRotationEnts[MissileWeapon.preSpawnedRotationEnts.size] = ent;
|
|
waitframe();
|
|
}
|
|
}
|
|
|
|
|
|
////=================================================================================================================//
|
|
//// On CONNECT //
|
|
////=================================================================================================================//
|
|
|
|
OnPlayerConnect()
|
|
{
|
|
while( true )
|
|
{
|
|
level waittill("connected", player);
|
|
player thread WaitingForSpawnDuringStreak();
|
|
}
|
|
}
|
|
|
|
WaitingForSpawnDuringStreak()
|
|
{
|
|
|
|
self waittill("spawned_player");
|
|
self.MissileStrikeGasTime = 0;
|
|
self thread CreateGasTrackingOverlay();
|
|
self thread WaitForGasDamage();
|
|
}
|
|
|
|
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 ROCKET //
|
|
////=================================================================================================================//
|
|
|
|
_fire( lifeId, player, MissileWeapon )
|
|
{
|
|
missileType = MissileWeapon.name;
|
|
|
|
player playerAddNotifyCommands();
|
|
|
|
rocket = fireOrbitalMissile( player, missileType );
|
|
|
|
if( IsDefined( level.isHorde ) && level.isHorde )
|
|
player.rocket = rocket;
|
|
|
|
MissileWeapon.RocketAmmo--;
|
|
if( MissileWeapon.RocketAmmo > 0 || MissileWeapon.clusterMissile )
|
|
rocket DisableMissileBoosting();
|
|
|
|
rocket.owner = player;
|
|
rocket.team = player.team;
|
|
rocket.lifeId = lifeId;
|
|
rocket.type = "remote";
|
|
level.remoteMissileInProgress = true;
|
|
|
|
rocket thread maps\mp\gametypes\_damage::setEntityDamageCallback( CONST_MISSILE_HEALTH, undefined, ::missileStrikeOnDeath, undefined, true );
|
|
|
|
if( IsDefined( level.ishorde ) && level.ishorde )
|
|
{
|
|
rocket thread rocketImpact_droneKillCheck( player );
|
|
}
|
|
|
|
rocket SetMissileMinimapVisible( true );
|
|
rocket PlaySoundToTeam( "mstrike_entry_npc", getOtherTeam(rocket.team) );
|
|
rocket PlaySoundToTeam( "mstrike_entry_npc", rocket.team, player );
|
|
|
|
MissileEyes( player, rocket, MissileWeapon );
|
|
}
|
|
|
|
rocketImpact_droneKillCheck( player )
|
|
{
|
|
self waittill( "death" );
|
|
|
|
if( isdefined( level.flying_attack_drones ) )
|
|
{
|
|
foreach( drone in level.flying_attack_drones )
|
|
{
|
|
if( drone.origin[2] > ( self.origin[2] ) && drone.origin[2] < ( self.origin[2] + 1200 ) && Distance2DSquared( drone.origin, self.origin ) < ( 40000 ))
|
|
drone DoDamage( 350, self.origin, player, player, "MOD_PROJECTILE_SPLASH", "remotemissile_projectile_mp" );
|
|
}
|
|
}
|
|
}
|
|
missileStrikeOnDeath( attacker, weapon, meansOfDeath, damage )
|
|
{
|
|
if( IsDefined( level.isHorde ) && level.isHorde )
|
|
{
|
|
if( IsDefined( self.owner ) )
|
|
{
|
|
self.owner.missileWeapon = undefined;
|
|
self.owner.rocket = undefined;
|
|
}
|
|
}
|
|
|
|
PlayFX( level.remotemissile_fx[ "explode" ], self.origin );
|
|
|
|
self maps\mp\gametypes\_damage::onKillstreakKilled( attacker, weapon, meansOfDeath, damage, "missile_strike_destroyed", undefined, undefined, false );
|
|
|
|
self delete();
|
|
}
|
|
|
|
fireOrbitalMissile( player, missileType )
|
|
{
|
|
remoteMissileSpawnArray = maps\mp\killstreaks\_aerial_utility::getEntOrStructArray( "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;
|
|
|
|
startPos = undefined;
|
|
targetPos = undefined;
|
|
if ( isDefined( remoteMissileSpawn ) )
|
|
{
|
|
startPos = remoteMissileSpawn.origin;
|
|
targetPos = remoteMissileSpawn.targetEnt.origin;
|
|
backDist = 24000;
|
|
if( IsDefined( level.remote_missile_height_override ) )
|
|
backDist = level.remote_missile_height_override;
|
|
|
|
vector = vectorNormalize( startPos - targetPos );
|
|
startPos = ( vector * backDist ) + targetPos;
|
|
}
|
|
else
|
|
{
|
|
upVector = (0, 0, level._missile_strike_setting[ "Launch_Value" ].Vert );
|
|
backDist = level._missile_strike_setting[ "Launch_Value" ].Horz ;
|
|
targetDist = level._missile_strike_setting[ "Launch_Value" ].TargetDest;
|
|
|
|
forward = AnglesToForward( player.angles );
|
|
startpos = player.origin + upVector + forward * backDist * -1;
|
|
targetPos = player.origin + forward * targetDist;
|
|
}
|
|
|
|
return MagicBullet( missileType, startpos, targetPos, player );
|
|
}
|
|
|
|
|
|
////=================================================================================================================//
|
|
//// CONTROL AND RIDE ROCKET //
|
|
////=================================================================================================================//
|
|
|
|
MissileEyes( player, rocket, MissileWeapon )
|
|
{
|
|
player endon ( "joined_team" );
|
|
player endon ( "joined_spectators" );
|
|
MissileWeapon endon ( "missile_strike_complete" );
|
|
player endon ( "disconnect" );
|
|
|
|
rocket thread Rocket_CleanupOnDeath();
|
|
player thread Player_CleanupOnGameEnded( rocket, MissileWeapon );
|
|
player thread Player_CleanupOnTeamChange( rocket, MissileWeapon );
|
|
|
|
player.ClusterDeployed = false;
|
|
player.MissileBoostUsed = false;
|
|
|
|
player CameraLinkTo( rocket, "tag_origin" );
|
|
player ControlsLinkTo( rocket );
|
|
|
|
if ( getDvarInt( "camera_thirdPerson" ) )
|
|
player setThirdPersonDOF( false );
|
|
|
|
player thread init_hud( rocket, MissileWeapon );
|
|
|
|
player thread playerWaitReset( MissileWeapon );
|
|
|
|
///////////////////// FIND EXOTIC MISSILES TYPES AND DO SPECIAL STUFF /////////////////////////////////////
|
|
///
|
|
|
|
if ( MissileWeapon.clusterMissile )
|
|
player thread WatchForClusterSplit( rocket, MissileWeapon );
|
|
|
|
//
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
|
///
|
|
|
|
if( MissileWeapon.RocketAmmo <= 0 && !MissileWeapon.clusterMissile )
|
|
{
|
|
// Boost available immediately
|
|
rocket EnableMissileBoosting();
|
|
player thread hud_watch_for_boost_active( rocket, MissileWeapon );
|
|
}
|
|
else
|
|
{
|
|
player thread WatchForExtraMissileFire( rocket, MissileWeapon );
|
|
}
|
|
|
|
player thread playerWatchForEarlyExit( MissileWeapon );
|
|
|
|
rocket waittill( "death" ); ///////////////// WAIT DEATH HERE ///////////////////////////////////////////
|
|
|
|
/////////////////////// AFTER DEATH CHECK AGAIN FOR MISSILE TYPES ///////////////////////////////////
|
|
///////
|
|
///
|
|
|
|
if ( MissileWeapon.gasMissile )
|
|
{
|
|
if( isDefined( rocket ) ) //////// Find out if rocket failed or not. If fired into void, it is removed and fails.
|
|
{
|
|
player thread ReleaseGas( rocket.origin );
|
|
}
|
|
}
|
|
|
|
// missile strike needs its own stat
|
|
// 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( "missile_strike", rocket.origin );
|
|
}
|
|
|
|
//////
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
///
|
|
MissileWeapon notify( "missile_strike_complete" );
|
|
}
|
|
|
|
playerWatchForEarlyExit( MissileWeapon )
|
|
{
|
|
level endon( "game_ended" );
|
|
MissileWeapon endon( "missile_strike_complete" );
|
|
self endon( "disconnect" );
|
|
|
|
while ( true )
|
|
{
|
|
useHold = 0;
|
|
while ( self UseButtonPressed() )
|
|
{
|
|
useHold += 0.05;
|
|
if ( useHold > 0.5 )
|
|
{
|
|
MissileWeapon notify( "ms_early_exit" );
|
|
return;
|
|
}
|
|
waitframe();
|
|
}
|
|
|
|
waitframe();
|
|
}
|
|
}
|
|
|
|
playerWaitReset( MissileWeapon )
|
|
{
|
|
MissileWeapon waittill_either( "missile_strike_complete", "ms_early_exit" );
|
|
|
|
self playerReset();
|
|
}
|
|
|
|
playerReset()
|
|
{
|
|
self endon( "disconnect" );
|
|
|
|
self ControlsUnlink();
|
|
self freezeControlsWrapper( true );
|
|
self playerRemoveNotifyCommands();
|
|
|
|
self stopMissileBoostSounds(); // Played by code during the boost
|
|
|
|
// If a player gets the final kill with a hellfire, level.gameEnded will already be true at this point
|
|
if ( !level.gameEnded || isDefined( self.finalKill ) )
|
|
self maps\mp\killstreaks\_aerial_utility::playerShowFullStatic();
|
|
|
|
wait( 0.5 );
|
|
maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone();
|
|
|
|
self remove_hud();
|
|
|
|
self CameraUnlink();
|
|
|
|
self freezeControlsWrapper( false );
|
|
|
|
if ( self isUsingRemote() )
|
|
self clearUsingRemote();
|
|
|
|
if ( getDvarInt( "camera_thirdPerson" ) )
|
|
self setThirdPersonDOF( true );
|
|
|
|
self playerRestoreAngles();
|
|
}
|
|
|
|
stopMissileBoostSounds()
|
|
{
|
|
self StopLocalSound( "mstrike_boost_shot" );
|
|
self StopLocalSound( "mstrike_boost_boom" );
|
|
self StopLocalSound( "mstrike_boost_swoop" );
|
|
self StopLocalSound( "mstrike_boost_jet" );
|
|
self StopLocalSound( "mstrike_boost_roar" );
|
|
}
|
|
|
|
WatchForExtraMissileFire( MasterRocket, MissileWeapon )
|
|
{
|
|
MasterRocket endon("death");
|
|
|
|
wait( 0.5 );
|
|
|
|
while( true )
|
|
{
|
|
self waittill("FireButtonPressed");
|
|
|
|
if( MissileWeapon.RocketAmmo > 0 )
|
|
{
|
|
self thread FireBabyMissile( MasterRocket, MissileWeapon );
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
|
|
wait( 0.1 );
|
|
}
|
|
|
|
}
|
|
|
|
EnableBoost(MasterRocket)
|
|
{
|
|
wait( 0.5 );
|
|
MasterRocket EnableMissileBoosting();
|
|
}
|
|
|
|
FireBabyMissile( MasterRocket, MissileWeapon ) // VehiclAloc for number of vehicle slots reserved for drones
|
|
{
|
|
MissileType = MissileWeapon.name;
|
|
|
|
offset = (0,32,0);
|
|
EvenOrOdd = MissileWeapon.RocketAmmo % 2;
|
|
if(EvenOrOdd == 0)
|
|
{
|
|
offset = (0,-64,0);
|
|
}
|
|
BulletTraceTarget = MasterRocket.origin + (anglestoforward(MasterRocket.angles) * 32000);
|
|
TraceArray = BulletTrace(MasterRocket.origin, BulletTraceTarget, false, MasterRocket, false, false, false, true, false);
|
|
|
|
startpos = (MasterRocket.origin + offset) + (anglestoforward(MasterRocket.angles) * 750);
|
|
targetPos = TraceArray["position"];
|
|
// targetPos = MasterRocket.origin + (anglestoforward(MasterRocket.angles) * 2500);
|
|
|
|
rocket = MagicBullet( "remotemissile_projectile_secondary_mp", startpos, targetPos, self );
|
|
|
|
if( !IsDefined( rocket ) )
|
|
return;
|
|
|
|
MissileWeapon.RocketAmmo--;
|
|
if( MissileWeapon.RocketAmmo <= 0 && !MissileWeapon.clusterMissile )
|
|
{
|
|
self hud_update_fire_text( rocket, MissileWeapon );
|
|
self thread hud_watch_for_boost_active( MasterRocket, MissileWeapon );
|
|
thread EnableBoost(MasterRocket);
|
|
}
|
|
|
|
if(isdefined(MissileType))
|
|
{
|
|
if ( IsDefined( MasterRocket.targets ) && MasterRocket.targets.size )
|
|
{
|
|
rocket Missile_SetTargetEnt( MasterRocket.targets[0] );
|
|
}
|
|
}
|
|
|
|
rocket.team = self.team;
|
|
rocket SetMissileMinimapVisible( true );
|
|
|
|
// player feedback
|
|
self PlayRumbleOnEntity( "sniper_fire" );
|
|
Earthquake( 0.2, 0.2, MasterRocket.origin, 200 );
|
|
|
|
rocket thread maps\mp\gametypes\_damage::setEntityDamageCallback( CONST_MISSILE_HEALTH, undefined, ::missileStrikeOnDeath, undefined, true );
|
|
}
|
|
|
|
|
|
////////////////////////////// CLUSTER MISSILE STUFF /////////////////////////////////////////////////////
|
|
/////////////
|
|
|
|
WatchForClusterSplit( rocket, MissileWeapon )
|
|
{
|
|
MissileWeapon endon( "missile_strike_complete" );
|
|
self endon( "spawned_player" );
|
|
rocket endon( "death" );
|
|
|
|
// Only handle the main (final) missile
|
|
while( MissileWeapon.RocketAmmo > 0 )
|
|
self waittill( "FireButtonPressed" );
|
|
|
|
wait( 0.25 );
|
|
|
|
self waittill( "FireButtonPressed" );
|
|
|
|
self.ClusterDeployed = true;
|
|
|
|
self hud_update_fire_text( rocket, MissileWeapon );
|
|
|
|
self thread split_rocket( rocket, MissileWeapon );
|
|
}
|
|
|
|
split_rocket( rocket, MissileWeapon ) // self == player
|
|
{
|
|
assert( MissileWeapon.clusterMissile );
|
|
if ( MissileWeapon.clusterHellfire )
|
|
{
|
|
self thread split_rocket_hellfire( rocket, MissileWeapon );
|
|
}
|
|
else
|
|
{
|
|
assert( MissileWeapon.clusterSpiral );
|
|
self thread split_rocket_spiral( rocket, MissileWeapon );
|
|
}
|
|
}
|
|
|
|
split_rocket_hellfire( rocket, MissileWeapon ) // self == player
|
|
{
|
|
waitFrames = 2;
|
|
|
|
// Valid targets to fire at?
|
|
targets = [];
|
|
|
|
// Tracking
|
|
if ( IsDefined( rocket.targets ) && rocket.targets.size )
|
|
{
|
|
targets = rocket.targets;
|
|
}
|
|
|
|
self thread fire_straight_bomblet( rocket, 0, MissileWeapon );
|
|
|
|
foreach( target in targets )
|
|
{
|
|
self thread fire_targeted_bomblet( rocket, target, waitFrames, MissileWeapon );
|
|
waitFrames++;
|
|
}
|
|
|
|
for ( i = targets.size; i <= 8; i++ )
|
|
{
|
|
self thread fire_random_bomblet( rocket, i % 6, waitFrames, MissileWeapon );
|
|
waitFrames++;
|
|
}
|
|
|
|
// player feedback
|
|
self PlayRumbleOnEntity( "sniper_fire" );
|
|
Earthquake( 0.2, 0.2, rocket.origin, 200 );
|
|
|
|
// Slow the rocket down for viewing the bomblets
|
|
rocket SetMissileCoasting( true );
|
|
|
|
// Quickly fade to white and back
|
|
self thread fade_to_white();
|
|
|
|
// Play a loop sound on the rocket for deploying the bomblets
|
|
|
|
self thread bomblet_camera_waiter( rocket, MissileWeapon );
|
|
}
|
|
|
|
split_rocket_spiral( rocket, MissileWeapon ) // self == player
|
|
{
|
|
MissileWeapon endon( "missile_strike_complete" );
|
|
self endon( "spawned_player" );
|
|
rocket endon( "death" );
|
|
|
|
self thread fade_to_white();
|
|
|
|
self waittill( "full_white" );
|
|
|
|
self thread freezeControlsWrapper( true );
|
|
|
|
if ( !IsDefined( rocket ) )
|
|
return;
|
|
|
|
self.StrikeRocketStoredPosition = rocket.origin;
|
|
self.StrikeRocketStoredAngles = rocket.angles;
|
|
DistanceToScale = Distance(rocket.origin, self.origin) + 10000;
|
|
|
|
RocketStoredForwardPosition = rocket.origin + (AnglesToForward(rocket.angles) * DistanceToScale);
|
|
ClusterStartPoint = self.StrikeRocketStoredPosition;
|
|
RotationStartPoint = rocket.origin + (AnglesToForward(rocket.angles) * 3250);
|
|
|
|
rocket SetMissileCoasting( true );
|
|
|
|
self thread bomblet_camera_waiter( rocket, MissileWeapon );
|
|
|
|
self thread SpawnClusterChildren( RocketStoredForwardPosition, self.StrikeRocketStoredPosition, RotationStartPoint, MissileWeapon );
|
|
|
|
self waittill("fade_white_over");
|
|
|
|
wait(9);
|
|
|
|
self freezeControlsWrapper(false);
|
|
}
|
|
|
|
fire_straight_bomblet( rocket, waitFrames, MissileWeapon ) // self == player
|
|
{
|
|
MissileWeapon endon( "missile_strike_complete" );
|
|
|
|
origin = rocket.origin;
|
|
angles = rocket.angles;
|
|
owner = rocket.owner;
|
|
|
|
if ( waitFrames > 0 )
|
|
wait ( waitFrames * 0.05 );
|
|
|
|
bomblet = MagicBullet( "remotemissile_projectile_cluster_child_hellfire_mp", origin, origin + AnglesToForward( angles ) * 1000, self );
|
|
bomblet.team = self.team;
|
|
bomblet.killCamEnt = self;
|
|
|
|
bomblet SetMissileMinimapVisible( true );
|
|
|
|
bomblet thread bomblet_explosion_waiter( self, MissileWeapon );
|
|
}
|
|
|
|
fire_targeted_bomblet( rocket, target, waitFrames, MissileWeapon ) // self == player
|
|
{
|
|
MissileWeapon endon( "missile_strike_complete" );
|
|
|
|
// Cache info about the rocket (in case it dies)
|
|
origin = rocket.origin;
|
|
angles = rocket.angles;
|
|
owner = rocket.owner;
|
|
target_origin = target.origin;
|
|
|
|
wait ( waitFrames * 0.05 );
|
|
|
|
if ( IsDefined( target ) && Distance2DSquared( target.origin, target_origin ) < ( 240.0 * 240.0 ) )
|
|
target_origin = target.origin;
|
|
|
|
bomblet = MagicBullet( "remotemissile_projectile_cluster_child_hellfire_mp", origin, target_origin, self );
|
|
bomblet.team = self.team;
|
|
bomblet.killCamEnt = self;
|
|
|
|
if ( IsDefined( target ) )
|
|
bomblet Missile_SetTargetEnt( target );
|
|
|
|
bomblet SetMissileMinimapVisible( true );
|
|
|
|
bomblet thread bomblet_explosion_waiter( self, MissileWeapon );
|
|
}
|
|
|
|
fire_random_bomblet( rocket, index, waitFrames, MissileWeapon ) // self == player
|
|
{
|
|
MissileWeapon endon( "missile_strike_complete" );
|
|
|
|
targeting_radius = 600;
|
|
|
|
// Cache info about the rocket (in case it dies)
|
|
origin = rocket.origin;
|
|
angles = rocket.angles;
|
|
owner = rocket.owner;
|
|
aimTarget = rocket.aimtarget;
|
|
|
|
wait ( waitFrames * 0.05 );
|
|
|
|
angle = RandomIntRange( 10 + ( 60 * index ), 50 + ( 60 * index ) );
|
|
radius = RandomIntRange( 200, targeting_radius + 100 );
|
|
x = min( radius, targeting_radius - 50 ) * Cos( angle );
|
|
y = min( radius, targeting_radius - 50 ) * Sin( angle );
|
|
|
|
bomblet = MagicBullet( "remotemissile_projectile_cluster_child_hellfire_mp", origin, aimtarget + ( x, y, 0 ), self );
|
|
bomblet.team = self.team;
|
|
bomblet.killCamEnt = self;
|
|
|
|
bomblet SetMissileMinimapVisible( true );
|
|
|
|
bomblet thread bomblet_explosion_waiter( self, MissileWeapon );
|
|
}
|
|
|
|
bomblet_explosion_waiter( player, MissileWeapon ) // self == bomblet
|
|
{
|
|
player endon( "disconnect" );
|
|
MissileWeapon endon( "ms_early_exit" );
|
|
MissileWeapon endon( "missile_strike_complete" );
|
|
level endon( "game_ended" );
|
|
|
|
self waittill( "death" );
|
|
|
|
MissileWeapon notify( "bomblet_exploded" );
|
|
}
|
|
|
|
bomblet_camera_waiter( rocket, MissileWeapon ) // self == player
|
|
{
|
|
self endon( "disconnect" );
|
|
MissileWeapon endon( "ms_early_exit" );
|
|
MissileWeapon endon( "missile_strike_complete" );
|
|
rocket endon( "death" );
|
|
level endon ( "game_ended" );
|
|
|
|
MissileWeapon waittill( "bomblet_exploded" );
|
|
|
|
wait( 1.0 );
|
|
|
|
self thread bomblet_camera_waiter_complete( rocket, MissileWeapon );
|
|
}
|
|
|
|
bomblet_camera_waiter_complete( rocket, MissileWeapon )
|
|
{
|
|
rocket notify( "death" );
|
|
MissileWeapon notify( "missile_strike_complete" );
|
|
}
|
|
|
|
getPrespawnedClusterRotationEnt( MissileWeapon, newOrigin )
|
|
{
|
|
while ( MissileWeapon.preSpawnedRotationEnts.size < (MissileWeapon.preSpawnedIndex+1) )
|
|
waitframe();
|
|
|
|
newEnt = MissileWeapon.preSpawnedRotationEnts[MissileWeapon.preSpawnedIndex];
|
|
MissileWeapon.preSpawnedIndex++;
|
|
if ( IsDefined( newOrigin ) )
|
|
newEnt.origin = newOrigin;
|
|
return newEnt;
|
|
}
|
|
|
|
SpawnClusterChildren( RocketStoredForwardPosition, ClusterStartPoint, RotationStartPoint, MissileWeapon )
|
|
{
|
|
// ClusterStartPoint = rocket's origin
|
|
// RotationStartPoint = rocket's origin + some forward offset
|
|
|
|
///////// single middle child rocket///////////////////
|
|
RandomX = RandomIntRange(16,64);
|
|
if(RandomInt(100) > 50)
|
|
{
|
|
RandomX = RandomX *-1;
|
|
}
|
|
RandomY = RandomIntRange(16,64);
|
|
if(RandomInt(100) > 50)
|
|
{
|
|
RandomY = RandomY *-1;
|
|
}
|
|
RandomPosition = ClusterStartPoint + (RandomX, RandomY,0);
|
|
RandomTargetPosition = RotationStartPoint + (RandomX, RandomY,0);
|
|
|
|
RotatingCenterEnt = getPrespawnedClusterRotationEnt( MissileWeapon, RotationStartPoint );
|
|
RotatingCenterEnt.RotatingLinkArray = [];
|
|
RotatingCenterEnt.RotatingLinkArray[0] = getPrespawnedClusterRotationEnt( MissileWeapon, RandomTargetPosition );
|
|
RotatingCenterEnt.RotatingLinkArray[0] LinkToSynchronizedParent(RotatingCenterEnt);
|
|
|
|
|
|
rocket = MagicBullet( "remotemissile_projectile_cluster_child_mp", RandomPosition, RandomTargetPosition, self );
|
|
rocket Missile_SetTargetEnt(RotatingCenterEnt.RotatingLinkArray[0]);
|
|
rocket Missile_SetFlightmodeDirect();
|
|
rocket.owner = self;
|
|
rocket.team = self.team;
|
|
rocket SetMissileMinimapVisible( true );
|
|
rocket thread bomblet_explosion_waiter( self, MissileWeapon );
|
|
|
|
view_offset = RandomTargetPosition - RandomPosition;
|
|
view_offset = VectorNormalize( (view_offset[0], view_offset[1], 0) );
|
|
view_offset *= -30;
|
|
view_offset += (0,0, 200);
|
|
|
|
killcam = MissileWeapon.preSpawnedKillcamEnt;
|
|
killcam.origin = RandomPosition + view_offset;
|
|
killcam.killCamStartTime = GetTime();
|
|
killcam LinkTo( rocket );
|
|
killcam thread killCamRocketDeath( rocket );
|
|
|
|
rocket.killCamEnt = killcam;
|
|
|
|
ChildrenRockets = 10;
|
|
MissileWeapon.RotatingLinkArrayIndex = RotatingCenterEnt.RotatingLinkArray.size;
|
|
for(i = 0; i < ChildrenRockets; i++)
|
|
{
|
|
Index = RotatingCenterEnt.RotatingLinkArray.size;
|
|
RotatingCenterEnt.RotatingLinkArray[Index] = getPrespawnedClusterRotationEnt( MissileWeapon );
|
|
}
|
|
|
|
rocket endon( "death" );
|
|
self thread DeleteRotationEnts(RotatingCenterEnt, rocket);
|
|
self thread RotateTargets(RotatingCenterEnt, rocket);
|
|
self thread MoveTargets(RotatingCenterEnt, rocket);
|
|
|
|
// each MagicBullet spawns 2 entities on a frame so a waitframe between each firing means 4 ents spawned per snapshot
|
|
waitframe();
|
|
|
|
////////// Random child rockets //////////////////////////////////////////////
|
|
for(i = 1; i <= ChildrenRockets; i++)
|
|
{
|
|
rotateEnt = getMissilePosition( ClusterStartPoint, RotationStartPoint, RotatingCenterEnt, MissileWeapon );
|
|
RandomTargetPosition = rotateEnt.origin;
|
|
RandomPosition = rotateEnt.RandomPosition;
|
|
child_rocket = MagicBullet( "remotemissile_projectile_cluster_child_mp", RandomPosition, RandomTargetPosition, self );
|
|
child_rocket Missile_SetTargetEnt(RotatingCenterEnt.RotatingLinkArray[i]);
|
|
child_rocket Missile_SetFlightmodeDirect();
|
|
child_rocket.owner = self;
|
|
child_rocket.team = self.team;
|
|
child_rocket.killCamEnt = killcam;
|
|
child_rocket SetMissileMinimapVisible( true );
|
|
child_rocket thread bomblet_explosion_waiter( self, MissileWeapon );
|
|
waitframe();
|
|
}
|
|
}
|
|
|
|
getMissilePosition( ClusterStartPoint, RotationStartPoint, RotatingCenterEnt, MissileWeapon )
|
|
{
|
|
rotateEnt = RotatingCenterEnt.RotatingLinkArray[MissileWeapon.RotatingLinkArrayIndex];
|
|
MissileWeapon.RotatingLinkArrayIndex++;
|
|
|
|
offset = RotatingCenterEnt.origin - RotationStartPoint;
|
|
ClusterStartPoint = ClusterStartPoint + offset;
|
|
|
|
RandomStartX = RandomIntRange(64,500);
|
|
RandomTargetStartX = RandomStartX + 0;
|
|
if(RandomInt(100) > 50)
|
|
{
|
|
RandomStartX = RandomStartX *-1;
|
|
RandomTargetStartX = RandomStartX - 0;
|
|
}
|
|
|
|
RandomStartY = RandomIntRange(64,500);
|
|
RandomTargetStartY = RandomStartY + 0;
|
|
if(RandomInt(100) > 50)
|
|
{
|
|
RandomStartY = RandomStartY *-1;
|
|
RandomTargetStartY = RandomStartY - 0;
|
|
}
|
|
RandomPosition = ClusterStartPoint + (RandomStartX, RandomStartY,0);
|
|
RandomTargetPosition = RotatingCenterEnt.origin + (RandomTargetStartX, RandomTargetStartY,0);
|
|
rotateEnt.origin = RandomTargetPosition;
|
|
rotateEnt.RandomPosition = RandomPosition;
|
|
rotateEnt LinkToSynchronizedParent( RotatingCenterEnt );
|
|
return rotateEnt;
|
|
}
|
|
|
|
killCamRocketDeath(rocket)
|
|
{
|
|
self endon("death");
|
|
|
|
rocket waittill("death");
|
|
|
|
self Unlink();
|
|
self.origin += (0,0,50);
|
|
wait 3;
|
|
self Delete();
|
|
}
|
|
|
|
RotateTargets(RotatingCenterEnt, MasterRocket)
|
|
{
|
|
MasterRocket endon("death");
|
|
|
|
Time = 10;
|
|
while(true)
|
|
{
|
|
RotatingCenterEnt RotateVelocity( (0,120,0), Time );
|
|
wait(Time);
|
|
}
|
|
}
|
|
|
|
/// MasterRocket is center child cluster rocket
|
|
// StoredAngles and StoredPosition is original parent rocket, must copy from self to new vector so doesn't get stomped if new missile fired
|
|
MoveTargets(RotatingCenterEnt, MasterRocket)
|
|
{
|
|
MasterRocket endon("death");
|
|
|
|
StoredMasterRocketPos = MasterRocket.origin;
|
|
ConstDistanceBetween = Distance(MasterRocket.origin, RotatingCenterEnt.origin);
|
|
StoredAngles = self.StrikeRocketStoredAngles;
|
|
StoredPosition = self.StrikeRocketStoredPosition;
|
|
|
|
while(true)
|
|
{
|
|
DistanceBetweenStart = Distance(MasterRocket.origin, StoredMasterRocketPos);
|
|
NewPosition = StoredPosition + (AnglesToForward(StoredAngles) * (ConstDistanceBetween + DistanceBetweenStart));
|
|
RotatingCenterEnt.origin = NewPosition;
|
|
waitframe();
|
|
}
|
|
}
|
|
|
|
DeleteRotationEnts(RotatingCenterEnt, MasterRocket)
|
|
{
|
|
MasterRocket waittill("death");
|
|
|
|
if(isdefined(RotatingCenterEnt))
|
|
{
|
|
foreach(Ent in RotatingCenterEnt.RotatingLinkArray)
|
|
{
|
|
Ent delete();
|
|
}
|
|
|
|
RotatingCenterEnt delete();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//////
|
|
///////////////////////////////////// END CLUSTER ROCKET ///////////////////////////////////////////////
|
|
|
|
|
|
///////////////////////////// GAS MISSILE STUFF///////////////////////////////////////////////////
|
|
///////////
|
|
////////
|
|
|
|
ReleaseGas( LastRocketPos )
|
|
{
|
|
GasPosition = LastRocketPos + (0,0,40);
|
|
|
|
GasCloud = spawn("script_model", GasPosition);
|
|
GasCloud setModel( "tag_origin" );
|
|
killCamEnt = Spawn( "script_model", GasCloud.origin);
|
|
GasCloud.killCamEnt = killCamEnt;
|
|
GasCloud.killCamEnt SetScriptMoverKillCam( "explosive" );
|
|
GasCloud.killCamEnt LinkToSynchronizedParent( GasCloud);
|
|
|
|
gas_center = spawnstruct();
|
|
gas_center.origin = GasPosition;
|
|
gas_center.team = self.team;
|
|
|
|
level.missile_strike_gas_clouds[level.missile_strike_gas_clouds.size] = gas_center;
|
|
|
|
GasCloud thread showGasCloud( self );
|
|
|
|
// enemy compass icon
|
|
GasCloud.objIDEnemy = maps\mp\gametypes\_gameobjects::getNextObjID();
|
|
objective_add( GasCloud.objIDEnemy, "active", GasCloud.origin, "hud_gas_enemy" );
|
|
Objective_PlayerEnemyTeam( GasCloud.objIDEnemy, self GetEntityNumber() );
|
|
|
|
// friendly compass icon
|
|
GasCloud.objIDFriendly = maps\mp\gametypes\_gameobjects::getNextObjID();
|
|
objective_add( GasCloud.objIDFriendly, "active", GasCloud.origin, "hud_gas_friendly" );
|
|
Objective_PlayerTeam( GasCloud.objIDFriendly, self GetEntityNumber() );
|
|
|
|
self thread ChemDamageThink( GasCloud, GasPosition, self );
|
|
|
|
self waittill_any_timeout_no_endon_death( 20, "joined_team", "joined_spectators", "disconnect" );
|
|
|
|
_objective_delete( GasCloud.objIDEnemy );
|
|
_objective_delete( GasCloud.objIDFriendly );
|
|
|
|
wait(2);
|
|
GasCloud friendlyEnemyEffectsStop();
|
|
GasCloud.killCamEnt delete();
|
|
GasCloud delete();
|
|
|
|
found = false;
|
|
newarray = [];
|
|
for ( i = 0; i < level.missile_strike_gas_clouds.size; i++ )
|
|
{
|
|
if ( !found && level.missile_strike_gas_clouds[i].origin == GasPosition )
|
|
{
|
|
found = true;
|
|
continue;
|
|
}
|
|
|
|
newarray[ newarray.size ] = level.missile_strike_gas_clouds[i];
|
|
}
|
|
assert( found );
|
|
assert( newarray.size == level.missile_strike_gas_clouds.size - 1 );
|
|
level.missile_strike_gas_clouds = newarray;
|
|
}
|
|
|
|
showGasCloud( owner )
|
|
{
|
|
friendlyFxId = level._missile_strike_setting[ "Particle_FX" ].GasFriendly;
|
|
enemyFxId = level._missile_strike_setting[ "Particle_FX" ].Gas;
|
|
origin = self GetTagOrigin( "tag_origin" );
|
|
fwd = ( 1, 0, 0 );
|
|
self.friendlyFX = spawnFXShowToTeam( friendlyFxId, owner.team, origin, fwd );
|
|
self.enemyFX = spawnFXShowToTeam( enemyFxId, getOtherTeam(owner.team), origin, fwd );
|
|
}
|
|
|
|
friendlyEnemyEffectsStop()
|
|
{
|
|
if(IsDefined(self.friendlyFX))
|
|
self.friendlyFX Delete();
|
|
if(IsDefined(self.enemyFX))
|
|
self.enemyFX Delete();
|
|
}
|
|
|
|
CreateGasTrackingOverlay()
|
|
{
|
|
// TODO: Cleanup HUD
|
|
if ( !IsDefined( self.StrikeGasTrackingOverlay ) )
|
|
{
|
|
self.StrikeGasTrackingOverlay = newClientHudElem( self );
|
|
self.StrikeGasTrackingOverlay.x = 0;
|
|
self.StrikeGasTrackingOverlay.y = 0;
|
|
self.StrikeGasTrackingOverlay setshader( "lab_gas_overlay", 640, 480 );
|
|
self.StrikeGasTrackingOverlay.alignX = "left";
|
|
self.StrikeGasTrackingOverlay.alignY = "top";
|
|
self.StrikeGasTrackingOverlay.horzAlign = "fullscreen";
|
|
self.StrikeGasTrackingOverlay.vertAlign = "fullscreen";
|
|
self.StrikeGasTrackingOverlay.alpha = 0;
|
|
}
|
|
}
|
|
|
|
ChemDamageThink (GasCloud, GasPosition, attacker )
|
|
{
|
|
GasCloud endon( "death" );
|
|
attacker endon( "joined_team" );
|
|
attacker endon( "joined_spectators" );
|
|
attacker endon( "disconnect" );
|
|
|
|
Radius = 200;
|
|
Damage = 20;
|
|
|
|
while( true )
|
|
{
|
|
if( !IsDefined(attacker) )
|
|
return;
|
|
|
|
// EInflictor is self on radius damage and needs to be kill cam ent
|
|
GasCloud.killCamEnt RadiusDamage(GasPosition, Radius, Damage, Damage, attacker, "MOD_TRIGGER_HURT", "killstreak_strike_missile_gas_mp", false);
|
|
|
|
wait( 1 );
|
|
}
|
|
}
|
|
|
|
WaitForGasDamage()
|
|
{
|
|
while(1)
|
|
{
|
|
self waittill( "damage", amount, attacker, direction, point, means_of_death, model, tag, part_name, damage_flags, weapon_name );
|
|
{
|
|
if( IsDefined(attacker) && (self == attacker) )
|
|
continue;
|
|
|
|
if( self _hasPerk("specialty_stun_resistance") )
|
|
continue;
|
|
|
|
if( IsDefined(weapon_name) && (weapon_name == "killstreak_strike_missile_gas_mp") )
|
|
{
|
|
self thread ShockThink();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ShockThink()
|
|
{
|
|
if(self.MissileStrikeGasTime <= 0)
|
|
{
|
|
self thread fadeInOutGasTrackingOverlay();
|
|
self thread RemoveOverlayDeath();
|
|
}
|
|
self.MissileStrikeGasTime = 2;
|
|
self shellshock( "mp_lab_gas", 1);
|
|
while(self.MissileStrikeGasTime > 0)
|
|
{
|
|
self.MissileStrikeGasTime--;
|
|
wait(1);
|
|
}
|
|
self notify( "missile_strike_gas_end" );
|
|
self EndGasTrackingOverlay();
|
|
}
|
|
|
|
fadeInOutGasTrackingOverlay()
|
|
{
|
|
|
|
level endon( "game_ended" );
|
|
self endon( "missile_strike_gas_end" );
|
|
self endon( "death" );
|
|
|
|
if (IsDefined( self.StrikeGasTrackingOverlay ) )
|
|
{
|
|
while ( true )
|
|
{
|
|
self.StrikeGasTrackingOverlay FadeOverTime( 0.4 );
|
|
self.StrikeGasTrackingOverlay.alpha = 1;
|
|
wait( 0.5 );
|
|
self.StrikeGasTrackingOverlay FadeOverTime( 0.4 );
|
|
self.StrikeGasTrackingOverlay.alpha = 0.5;
|
|
wait( 0.5 );
|
|
}
|
|
}
|
|
}
|
|
|
|
EndGasTrackingOverlay()
|
|
{
|
|
if (IsDefined( self.StrikeGasTrackingOverlay ) )
|
|
{
|
|
self.StrikeGasTrackingOverlay FadeOverTime( 0.2 );
|
|
self.StrikeGasTrackingOverlay.alpha = 0.0;
|
|
}
|
|
}
|
|
|
|
EndGasTrackingOverlayDeath()
|
|
{
|
|
if (IsDefined( self.StrikeGasTrackingOverlay ) )
|
|
{
|
|
// self.StrikeGasTrackingOverlay FadeOverTime( 0.0 );
|
|
self.StrikeGasTrackingOverlay.alpha = 0.0;
|
|
}
|
|
}
|
|
|
|
RemoveOverlayDeath()
|
|
{
|
|
self endon( "missile_strike_gas_end" );
|
|
self waittill( "death" );
|
|
self thread EndGasTrackingOverlayDeath();
|
|
}
|
|
|
|
|
|
/////
|
|
///////
|
|
//////////////////////////////////// END GAS MISSILE STUFF /////////////////////////////////////////////
|
|
|
|
|
|
Player_CleanupOnTeamChange( rocket, MissileWeapon )
|
|
{
|
|
rocket endon ( "death" );
|
|
self endon ( "disconnect" );
|
|
|
|
self waittill_any( "joined_team", "joined_spectators" );
|
|
|
|
MissileWeapon notify( "missile_strike_complete" );
|
|
|
|
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, MissileWeapon )
|
|
{
|
|
rocket endon ( "death" );
|
|
self endon ( "disconnect" );
|
|
|
|
level waittill ( "game_ended" );
|
|
|
|
MissileWeapon notify( "missile_strike_complete" );
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Targeting
|
|
// ----------------------------------------------------------------------------
|
|
|
|
player_is_valid_target( player ) // self == player
|
|
{
|
|
if ( !IsDefined( player ) )
|
|
return false;
|
|
|
|
if ( IsAlliedSentient( player, self ) )
|
|
return false;
|
|
|
|
if ( !IsAlive( player ) )
|
|
return false;
|
|
|
|
if ( player _hasPerk( "specialty_blindeye" ) )
|
|
return false;
|
|
|
|
// 5 second ignore on spawn
|
|
if ( isDefined( player.spawntime ) && ( ( ( GetTime() - player.spawntime ) / 1000 ) < 5 ) )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
getEnemyTargets() // self == player
|
|
{
|
|
enemyPlayers = [];
|
|
|
|
foreach ( player in level.players )
|
|
{
|
|
if ( !player_is_valid_target( player ) )
|
|
continue;
|
|
|
|
enemyPlayers[enemyPlayers.size] = player;
|
|
}
|
|
|
|
return enemyPlayers;
|
|
}
|
|
|
|
getValidTargetsSorted( rocket, shouldTrace, MissileWeapon )
|
|
{
|
|
missileType = MissileWeapon.name;
|
|
|
|
targeting_radius = 600;
|
|
targeting_radius_squared = targeting_radius * targeting_radius;
|
|
|
|
targets = [];
|
|
|
|
forward = AnglesToForward ( rocket.angles );
|
|
|
|
rocketZ = rocket.origin[2];
|
|
mapCenterZ = level.mapCenter[2];
|
|
diff = mapCenterZ - rocketZ;
|
|
|
|
ratio = diff / forward[2];
|
|
|
|
aimTarget = rocket.origin + forward * ratio;
|
|
rocket.aimTarget = aimTarget;
|
|
|
|
// /#
|
|
// circle( rocket.aimTarget, targeting_radius, (0,1,0), true, true, 2000 );
|
|
// #/
|
|
|
|
maxTargets = 0;
|
|
|
|
// Baby missiles get a single lockon target
|
|
// Cluster missiles (parents) get up to 10 lockon targets
|
|
|
|
if ( MissileWeapon.RocketAmmo > 0 )
|
|
maxTargets = 1;
|
|
else if ( MissileWeapon.clusterMissile && MissileWeapon.clusterHellfire && !self.ClusterDeployed ) // Only the hellfire gets lock-on support
|
|
maxTargets = 10;
|
|
else
|
|
return targets;
|
|
|
|
enemies = self getEnemyTargets();
|
|
|
|
foreach( player in enemies )
|
|
{
|
|
if ( Distance2DSquared( player.origin, aimTarget) < targeting_radius_squared )
|
|
{
|
|
if ( shouldTrace )
|
|
{
|
|
if ( BulletTracePassed( player.origin + (0,0,60), player.origin + (0,0,180), false, player) )
|
|
{
|
|
targets[targets.size] = player;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
targets[targets.size] = player;
|
|
}
|
|
}
|
|
}
|
|
|
|
sorted_targets = get_array_of_closest( rocket.aimTarget, targets, undefined, maxTargets );
|
|
|
|
return sorted_targets;
|
|
}
|
|
|
|
targeting_hud_init() // self == player
|
|
{
|
|
self targeting_hud_destroy();
|
|
|
|
max_targeting_icons = 10;
|
|
|
|
self.missile_target_icons = [];
|
|
|
|
for ( i = 0; i < max_targeting_icons; i++ )
|
|
{
|
|
newIcon = NewClientHudElem( self );
|
|
newIcon.x = 0;
|
|
newIcon.y = 0;
|
|
newIcon.z = 0;
|
|
newIcon.alpha = 0;
|
|
newIcon.archived = false;
|
|
newIcon.shader = CONST_HOSTILE;
|
|
newIcon SetShader( CONST_HOSTILE, 450, 450 );
|
|
newIcon SetWayPoint( false, false, false, false );
|
|
newIcon SetWaypointIconFadeAtCenter( false );
|
|
newIcon SetWaypointAerialTargeting( true );
|
|
|
|
self.missile_target_icons[i] = newIcon;
|
|
}
|
|
}
|
|
|
|
targeting_hud_destroy() // self == player
|
|
{
|
|
if ( !IsDefined( self.missile_target_icons ) )
|
|
return;
|
|
|
|
max_targeting_icons = 10;
|
|
|
|
for ( i = 0; i < max_targeting_icons; i++ )
|
|
{
|
|
if ( IsDefined( self.missile_target_icons[i] ) )
|
|
self.missile_target_icons[i] Destroy();
|
|
}
|
|
|
|
self.missile_target_icons = undefined;
|
|
}
|
|
|
|
targeting_hud_think( rocket, MissileWeapon ) // self == player
|
|
{
|
|
self endon( "disconnect" );
|
|
MissileWeapon endon( "ms_early_exit" );
|
|
MissileWeapon endon( "missile_strike_complete" );
|
|
rocket endon("death");
|
|
level endon ( "game_ended" );
|
|
|
|
wait( 1 );
|
|
|
|
missileType = MissileWeapon.name;
|
|
rocket.targets = self getValidTargetsSorted( rocket, true, MissileWeapon );
|
|
|
|
max_targeting_icons = 10;
|
|
|
|
framesBetweenTargetScans = 5;
|
|
framesSinceTargetScan = 0;
|
|
player_z_offset = 47;
|
|
|
|
while( true )
|
|
{
|
|
// Hide all of the icons
|
|
foreach ( icon in self.missile_target_icons )
|
|
icon.alpha = 0;
|
|
|
|
framesSinceTargetScan++;
|
|
|
|
if( framesSinceTargetScan > framesBetweenTargetScans )
|
|
{
|
|
rocket.targets = self getValidTargetsSorted( rocket, true, MissileWeapon );
|
|
framesSinceTargetScan = 0;
|
|
}
|
|
|
|
// icon for the kill streak user
|
|
HUDicon = self.missile_target_icons[0];
|
|
HUDicon.x = self.origin[0];
|
|
HUDicon.y = self.origin[1];
|
|
HUDicon.z = self.origin[2];
|
|
HUDicon.alpha = 1;
|
|
|
|
if( HUDicon.shader != CONST_SELF )
|
|
{
|
|
HUDicon.shader = CONST_SELF;
|
|
HUDicon SetShader( CONST_SELF, 450, 450 );
|
|
HUDicon SetWayPoint( false, false, false, false );
|
|
HUDicon SetWaypointIconFadeAtCenter( false );
|
|
HUDicon SetWaypointAerialTargeting( true );
|
|
}
|
|
|
|
valid_target_index = 1;
|
|
newTargets = 0;
|
|
|
|
agents = level.agentArray;
|
|
if ( !IsDefined( agents ) )
|
|
agents = [];
|
|
potentialTargets = array_combine( level.players, agents );
|
|
|
|
// Cull the rocket targets from the array to be added back later (they always take priority)
|
|
rocket.targets = array_removeUndefined( rocket.targets );
|
|
nonTargets = array_remove_array( potentialTargets, rocket.targets );
|
|
validNonTargets = [];
|
|
foreach( ent in nonTargets )
|
|
{
|
|
if ( player_is_valid_target( ent ) )
|
|
validNonTargets[validNonTargets.size] = ent;
|
|
}
|
|
|
|
// Sort the array of nonTargets by distance from the aim target
|
|
sortedNonTargets = get_array_of_closest( rocket.aimTarget, validNonTargets, undefined, undefined );
|
|
|
|
// Combine the arrays, but give the actual targets priority
|
|
potentialTargets = array_combine( rocket.targets, sortedNonTargets );
|
|
|
|
foreach( player in potentialTargets )
|
|
{
|
|
if( !IsDefined(player) )
|
|
continue;
|
|
|
|
HUDicon = self.missile_target_icons[valid_target_index];
|
|
if ( !IsDefined( HUDicon ) )
|
|
break;
|
|
|
|
if( ( IsPlayer(player) || IsAgent ( player ) ) && player_is_valid_target(player))
|
|
{
|
|
HUDicon.x = player.origin[0];
|
|
HUDicon.y = player.origin[1];
|
|
HUDicon.z = player.origin[2];
|
|
HUDicon.alpha = 1;
|
|
valid_target_index++;
|
|
|
|
if( array_contains( rocket.targets, player) && (HUDicon.shader == CONST_HOSTILE) )
|
|
{
|
|
// set lock on icon
|
|
HUDicon.shader = CONST_HOSTIL_LOCK;
|
|
HUDicon SetShader( CONST_HOSTIL_LOCK, 450, 450 );
|
|
HUDicon SetWayPoint( false, false, false, false, false );
|
|
HUDicon fadeOverTime( 0.05 );
|
|
HUDicon SetWaypointIconFadeAtCenter( false );
|
|
HUDicon SetWaypointAerialTargeting( true );
|
|
newTargets++;
|
|
|
|
}
|
|
else if( !array_contains( rocket.targets, player) && (HUDicon.shader == CONST_HOSTIL_LOCK) )
|
|
{
|
|
HUDicon.shader = CONST_HOSTILE;
|
|
HUDicon SetShader( CONST_HOSTILE, 450, 450 );
|
|
HUDicon SetWayPoint( false, false, false, false );
|
|
HUDicon fadeOverTime( 0.05 );
|
|
HUDicon SetWaypointIconFadeAtCenter( false );
|
|
HUDicon SetWaypointAerialTargeting( true );
|
|
}
|
|
}
|
|
}
|
|
|
|
if( newTargets == 1 )
|
|
rocket PlaySoundToPlayer( "mstrike_locked_on_single", rocket.owner );
|
|
|
|
if( newTargets > 1 )
|
|
rocket PlaySoundToPlayer( "mstrike_locked_on_multiple", rocket.owner );
|
|
|
|
waitframe();
|
|
}
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// HUD
|
|
// ----------------------------------------------------------------------------
|
|
init_hud( rocket, MissileWeapon ) // self == player
|
|
{
|
|
self endon( "disconnect" );
|
|
|
|
self thread targeting_hud_init();
|
|
self thread targeting_hud_think( rocket, MissileWeapon );
|
|
|
|
self SetClientOmnvar( "ui_predator_missile", 1 );
|
|
self SetClientOmnvar( "ui_predator_missile_count", MissileWeapon.RocketAmmo );
|
|
|
|
missileType = MissileWeapon.name;
|
|
|
|
if ( missileType == "remotemissile_projectile_mp" )
|
|
{
|
|
self SetClientOmnvar( "ui_predator_missile_type", 1 );
|
|
}
|
|
else if ( missileType == "remotemissile_projectile_gas_mp" )
|
|
{
|
|
self SetClientOmnvar( "ui_predator_missile_type", 2 );
|
|
}
|
|
else if ( missileType == "remotemissile_projectile_cluster_parent_mp" )
|
|
{
|
|
if ( MissileWeapon.clusterHellfire )
|
|
self SetClientOmnvar( "ui_predator_missile_type", 4 );
|
|
else
|
|
self SetClientOmnvar( "ui_predator_missile_type", 3 );
|
|
}
|
|
|
|
waitframe(); // Give the handlers time to get in place before we set the rest
|
|
|
|
self hud_update_fire_text( rocket, MissileWeapon );
|
|
self maps\mp\killstreaks\_aerial_utility::playerEnableStreakStatic();
|
|
}
|
|
|
|
remove_hud() // self == player
|
|
{
|
|
// Reset all of the omnvars
|
|
self SetClientOmnvar( "ui_predator_missile", 0 );
|
|
self SetClientOmnvar( "ui_predator_missile_text", 0 );
|
|
self SetClientOmnvar( "ui_predator_missile_type", 0 );
|
|
self SetClientOmnvar( "ui_predator_missile_count", 0 );
|
|
self targeting_hud_destroy();
|
|
|
|
self maps\mp\killstreaks\_aerial_utility::playerDisableStreakStatic();
|
|
}
|
|
|
|
hud_update_fire_text( rocket, MissileWeapon ) // self == player
|
|
{
|
|
self SetClientOmnvar( "ui_predator_missile_count", MissileWeapon.RocketAmmo );
|
|
if ( MissileWeapon.RocketAmmo > 0 )
|
|
{
|
|
assert( MissileWeapon.RocketAmmo <= 2 );
|
|
|
|
// Missiles remaining: 2 (or 1)
|
|
self SetClientOmnvar( "ui_predator_missile_text", MissileWeapon.RocketAmmo );
|
|
}
|
|
else if ( MissileWeapon.clusterMissile )
|
|
{
|
|
if ( !self.ClusterDeployed )
|
|
{
|
|
// Cluster Ready
|
|
self SetClientOmnvar( "ui_predator_missile_text", 5 );
|
|
}
|
|
else
|
|
{
|
|
// Cluster Deployed
|
|
self SetClientOmnvar( "ui_predator_missile_text", 6 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( !self.MissileBoostUsed )
|
|
{
|
|
// Boost Ready
|
|
self SetClientOmnvar( "ui_predator_missile_text", 3 );
|
|
}
|
|
else
|
|
{
|
|
// Boost Active
|
|
self SetClientOmnvar( "ui_predator_missile_text", 4 );
|
|
}
|
|
}
|
|
}
|
|
|
|
hud_watch_for_boost_active( rocket, MissileWeapon ) // self == player
|
|
{
|
|
self endon( "disconnect" );
|
|
MissileWeapon endon( "missile_strike_complete" );
|
|
MissileWeapon endon( "ms_early_exit" );
|
|
|
|
self waittill( "FireButtonPressed" );
|
|
|
|
self PlayRumbleOnEntity( "sniper_fire" );
|
|
|
|
self.MissileBoostUsed = true;
|
|
|
|
self hud_update_fire_text( rocket, MissileWeapon );
|
|
}
|
|
|
|
fade_to_white() // self == player
|
|
{
|
|
self endon( "disconnect" );
|
|
|
|
if ( !IsDefined( self.StrikeWhiteFade ) )
|
|
{
|
|
self.StrikeWhiteFade = newClientHudElem( self );
|
|
self.StrikeWhiteFade.x = 0;
|
|
self.StrikeWhiteFade.y = 0;
|
|
self.StrikeWhiteFade setshader( "white", 640, 480 );
|
|
self.StrikeWhiteFade.alignX = "left";
|
|
self.StrikeWhiteFade.alignY = "top";
|
|
self.StrikeWhiteFade.horzAlign = "fullscreen";
|
|
self.StrikeWhiteFade.vertAlign = "fullscreen";
|
|
self.StrikeWhiteFade.alpha = 0;
|
|
}
|
|
|
|
self.StrikeWhiteFade FadeOverTime( 0.15 );
|
|
self.StrikeWhiteFade.alpha = 1;
|
|
|
|
wait( 0.15 );
|
|
|
|
self notify( "full_white" );
|
|
|
|
self.StrikeWhiteFade FadeOverTime( 0.2 );
|
|
self.StrikeWhiteFade.alpha = 0;
|
|
|
|
wait( 0.2);
|
|
|
|
self notify("fade_white_over");
|
|
|
|
self.StrikeWhiteFade Destroy();
|
|
}
|
|
|
|
playerAddNotifyCommands()
|
|
{
|
|
self NotifyOnPlayerCommand( "FireButtonPressed", "+attack" );
|
|
self NotifyOnPlayerCommand( "FireButtonPressed", "+attack_akimbo_accessible" );
|
|
}
|
|
|
|
playerRemoveNotifyCommands()
|
|
{
|
|
self NotifyOnPlayerCommandRemove( "FireButtonPressed", "+attack" );
|
|
self NotifyOnPlayerCommandRemove( "FireButtonPressed", "+attack_akimbo_accessible" );
|
|
}
|