Перейти к содержимому
View in the app

A better way to browse. Learn more.

Zloplay community

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.

Cool things that IW didn't put in the final version of multiplayer

Опубликовано:

I have found some interesting things that IW was planned to put in multiplayer, but they didn't, maybe because they didn't had time or just from being lazy. It's all from gsc files from raw folder:

 

Elevators and rappel in Highrise:

#include maps\mp\_utility;
#include common_scripts\utility;

main()
{
  maps\mp\mp_highrise_precache::main();
  maps\createart\mp_highrise_art::main();
  maps\mp\mp_highrise_fx::main();
  maps\mp\_explosive_barrels::main();
  maps\mp\_load::main();

  maps\mp\_compass::setupMiniMap( "compass_map_mp_highrise" );

  setdvar( "r_lightGridEnableTweaks", 1 );
  setdvar( "r_lightGridIntensity", 1.11 );
  setdvar( "r_lightGridContrast", .9 );

  VisionSetNaked( "mp_highrise" );
  ambientPlay( "embient_mp_highrise" );

  game[ "attackers" ] = "axis";
  game[ "defenders" ] = "allies";

  // raise up planes to avoid them flying through buildings
  level.airstrikeHeightScale = 3;

  setdvar( "compassmaxrange", "2100" );
  //thread Elevator( "elev1" );
  //thread Elevator( "elev2" );
  //thread Elevator( "elev3" );   
  //thread Elevator( "elev4" );

  //thread SetupRappel();
}

Elevator( elevatorID )
{
  elevator = getent( elevatorID, "targetname" );
  elevFloors = [];
  elevFloors[ 0 ] = getent( elevatorID + "_floor1", "targetname" ).origin;
  elevFloors[ 1 ] = getent( elevatorID + "_floor2", "targetname" ).origin;

  moveSpeed = 192;// units / second

  waitTime = 2.0;

  floorIndex = 0;
  while ( 1 )
  {
     newPos = elevFloors[ floorIndex ];
     moveDist = distance( elevator.origin, newPos );
     if ( moveDist <= 0.0 )
     {
        floorIndex = ( floorIndex + 1 ) % elevFloors.size;
        continue;
     }
     moveTime = moveDist / moveSpeed;
     elevator moveTo( newPos, moveTime, moveTime * 0.25, moveTime * 0.25 );

     floorIndex = ( floorIndex + 1 ) % elevFloors.size;

     wait moveTime + waitTime;
  }
}



SetupRappel()
{
  // Press and hold ^3[{+activate}]^7 to rappel
  //precacheString( &"MP_PRESS_TO_RAPPEL" );
  trigs = getentarray( "rappeltrigger", "targetname" );
  foreach ( trig in trigs )
  {
     org = getent( trig.target, "targetname" );
     trig.rappelPoint = org.origin;
     trig.dir = anglesToForward( org.angles );
     org delete();
     trig thread RappelThink();
  }
  foreach ( trig in trigs )
  {
     org = getent( trig.target, "targetname" );
     if ( isdefined( org ) )
        org delete();
  }
}

RappelThink()
{
  // Press and hold ^3[{+activate}]^7 to rappel
  //self setHintString( &"MP_PRESS_TO_RAPPEL" );

  while ( 1 )
  {
     self waittill( "trigger", player );
     if ( !isPlayer( player ) )
        continue;

     if ( !player isOnGround() )
        continue;

     if ( isdefined( player.raqelling ) )
        continue;

     player thread Rappel( self );
  }
}

Rappel( trig )
{
  toRappelPoint = trig.rappelPoint - self.origin;
  rappelPoint = self.origin + vectordot( toRappelPoint, trig.dir ) * trig.dir;
  rappelPoint = ( rappelPoint[ 0 ], rappelPoint[ 1 ], trig.rappelPoint[ 2 ] );

  upTime = .5;// sec
  overTime = .75;// sec
  downSpeed = 512;// units / sec

  upPoint = self.origin;
  upPoint = ( upPoint[ 0 ], upPoint[ 1 ], rappelPoint[ 2 ] );
  overPoint = rappelPoint + trig.dir * 20;
  tracePosition = playerPhysicsTrace( overPoint, overPoint + ( 0, 0, -10000 ), false, self );
  downPoint = tracePosition + ( 0, 0, 16 );

  org = spawn( "script_origin", self.origin );
  org hide();

  self.raqelling = true;
  self _disableWeapon();
  self linkto( org );
  self PlayerLinkedOffsetEnable();

  org moveto( upPoint, upTime, 0, 0 );
  org waittill( "movedone" );
  org moveto( overPoint, overTime, 0, 0 );
  org waittill( "movedone" );

  downTime = distance( overPoint, downPoint ) / downSpeed;

  org moveto( downPoint, downTime, 0, 0 );
  org waittill( "movedone" );

  self _enableWeapon();
  self unlink();
  org delete();

  self.raqelling = undefined;
}

 

Trains which planned to be in some MP maps:

#include maps\mp\_utility;
#include common_scripts\utility;

SOUND_DELAY_MIN = 0.1;
SOUND_DELAY_MAX = 1;

init()
{
  train = getEnt( "the_l_train", "targetname" );
  assertex( isdefined( train ), "Calling to setup train in map without having the train in map! [targetname]" );
  train thread train_setup();
}

train_setup()
{
  precacheItem( "train_mp" );   

  delay_until_first_train = 2;
  Units_per_second = 1200;// train speed
  train_cars = 24;
  distance_between_cars = 368;
  eq_radius = 900;//    radius of earthquakes broadcast from the train tracks

  min_time_between_trains = 100;
  max_time_between_trains = 200;

  lead_in_dist = 8000;
  lead_out_dist = 4000;

  train_car_sound_interval = 3;

  flag_init( "train_running" );

  yaw = self.angles[ 1 ];
  while ( yaw >= 90 )
  {
     yaw -= 90;
  }
  assertEx( yaw == 0, "Trains must have a yaw of 0, 90, 180, or 270 currently( for collision purposes )" );

  wheels = getentarray( "wheel", "targetname" );
  wheel_model = wheels[ 0 ].model;
  wheel_offset = [];
  foreach ( wheel in wheels )
  {
     wheel linkto( self );
     wheel.offset = self.origin - wheel.origin;
  }

  // ****************
  // Set up the other cars
  // ****************   
  forward = anglestoforward( self.angles );
  forward *= distance_between_cars;
  car_separation_vec = forward;
  cars = [];
  for ( i = 0; i < train_cars - 1; i++ )// - 1 car self is the lead car
  {
     car = spawn( "script_model", self.origin - car_separation_vec );
     car.angles = self.angles;
     car setmodel( self.model );
     car linkto( self );
     car_separation_vec += forward;
     cars[ cars.size ] = car;
     car.wheels = [];

     foreach ( owner_wheel in wheels )
     {
        wheel = spawn( "script_model", car.origin + owner_wheel.offset );
        wheel setmodel( wheel_model );
        wheel.angles = owner_wheel.angles;
        wheel linkto( car );
        car.wheels[ car.wheels.size ] = wheel;
     }
  }

  // ****************
  // The start and end points for the train, set from script origins
  // ****************   
  start = getent( self.target, "targetname" );
  start.origin = ( start.origin[ 0 ], start.origin[ 1 ], self.origin[ 2 ] );

  end = getent( start.target, "targetname" );
  end.origin = ( end.origin[ 0 ], end.origin[ 1 ], self.origin[ 2 ] );
  forward = anglestoforward( self.angles );
  start.origin = start.origin - forward * lead_in_dist;
  end.origin = end.origin + forward * lead_out_dist;

  track_dist = distance( start.origin, end.origin );
  travel_dist = track_dist + train_cars * distance_between_cars;
  travel_time = travel_dist / Units_per_second;
  track_time = track_dist / Units_per_second;
  full_train_time = train_cars * distance_between_cars / Units_per_second;



  // ****************
  // set up the earthquakes that play to make the train tracks rattle
  // ****************   
  distance_between_eq_orgs = eq_radius * 0.5;
  eq_count = track_dist / distance_between_eq_orgs;

  // the amount of time it takes for all the EQs to start before the train moves in
  delay_between_eqs = track_time / eq_count;
  travel_time_segment = track_time / eq_count;
  min_time_between_trains -= track_time;
  if ( min_time_between_trains < 0.1 )
     min_time_between_trains = 0.1;
  max_time_between_trains -= track_time;
  if ( max_time_between_trains < min_time_between_trains )
     max_time_between_trains = min_time_between_trains + 0.1;

  eq_points = [];
  for ( i = 0; i < eq_count; i++ )
  {
     progress = i / eq_count;
     eq_points[ i ] = start.origin * ( 1 - progress ) + end.origin * progress;
  }

  self.cars = cars;
  self.wheels = wheels;

  self.origin = start.origin;
  wait( delay_until_first_train );

  for ( ;; )
  {
     hide_trains();
     wait( 0.05 );
     self.origin = start.origin;


     // First there is a lead in of small EQs to shake the platform
     timer = gettime();
     for ( i = 0; i < eq_count; i++ )
     {
        thread train_eq( eq_points[ i ], eq_radius, track_time, full_train_time );
        wait( delay_between_eqs );
     }

     show_trains();
     thread train_kills_players( train_cars, distance_between_cars );
     thread train_spawns_dust();

     // Now the train goes by
     self moveto( end.origin + car_separation_vec, travel_time );
     train_play_sounds( train_car_sound_interval );

     wait( travel_time );
     hide_trains();

     train_stop_sounds( train_car_sound_interval );

     flag_set( "train_running" );
     self notify( "train_stops_killing_players" );
     // Wait until the next train
     wait( randomfloatrange( min_time_between_trains, max_time_between_trains ) );
  }
}

train_play_sound_delayed( alias )
{
  wait( randomfloatrange( SOUND_DELAY_MIN, SOUND_DELAY_MAX ) );   
  self playLoopSound( alias );
}

train_play_sounds( max )
{   
  self thread train_play_sound_delayed( "veh_train_eng_dist1_loop" );
  self thread train_play_sound_delayed( "veh_train_eng_dist2_loop" );
  self thread train_play_sound_delayed( "veh_train_eng_mid_loop" );
  self thread train_play_sound_delayed( "veh_train_eng_close1_loop" );
  self thread train_play_sound_delayed( "veh_train_eng_close2_loop" );

  // dont play sounds on every car
  count = 0;
  for ( i = 0; i < self.cars.size; i++ )
  {
     count++;
     if ( count < max )
        continue;
     count = 0;

     self.cars[ i ] thread train_play_sound_delayed( "veh_train_car_dist_loop" );
     self.cars[ i ] thread train_play_sound_delayed( "veh_train_car_mid_loop" );
     self.cars[ i ] thread train_play_sound_delayed( "veh_train_car_close_loop" );
  }
}

train_stop_sounds( max )
{
  self stopLoopSound( "veh_train_eng_dist1_loop" );   
  self stopLoopSound( "veh_train_eng_dist2_loop" );   
  self stopLoopSound( "veh_train_eng_mid_loop" );     
  self stopLoopSound( "veh_train_eng_close1_loop" );  
  self stopLoopSound( "veh_train_eng_close2_loop" );  

  count = 0;
  for ( i = 0; i < self.cars.size; i++ )
  {
     count++;
     if ( count < max )
        continue;
     count = 0;

     self.cars[ i ] stopLoopSound( "veh_train_car_dist_loop" );
     self.cars[ i ] stopLoopSound( "veh_train_car_mid_loop" );
     self.cars[ i ] stopLoopSound( "veh_train_car_close_loop" );
  }
}

hide_trains()
{
  // Hide the train while it warps to position
  self hide();
  foreach ( wheel in self.wheels )
  {
     wheel hide();
  }
  foreach ( car in self.cars )
  {
     car hide();
     foreach ( wheel in car.wheels )
     {
        wheel hide();
     }
  }
}

show_trains()
{
  // show the train while it warps to position
  self show();
  foreach ( wheel in self.wheels )
  {
     wheel show();
  }
  foreach ( car in self.cars )
  {
     car show();
     foreach ( wheel in car.wheels )
     {
        wheel show();
     }
  }
}

train_spawns_dust()
{
  if ( !isdefined( level._effect[ "train_dust" ] ) )
     return;

  range = 40;
  self endon( "train_stops_killing_players" );

  for ( ;; )
  {
     /*
     for ( i = 0; i < 10; i ++ )
     {
        x = randomfloatrange( self.min_x, self.max_x );
        y = randomfloatrange( self.min_y, self.max_y );
        PlayFX( level._effect[ "train_dust" ], ( x, y, self.origin[ 2 ] - 30 ) );
     }
     */

     count = randomintrange( 1, 3 ); // which means 1 to 2 fx per frame
     for ( i = 0; i < count; i ++ )
     {
        x = randomfloatrange( self.min_x - range, self.max_x + range );
        y = randomfloatrange( self.min_y - range, self.max_y + range );
        PlayFX( level._effect[ "train_dust_linger" ], ( x, y, self.origin[ 2 ] - 10 ) );
     }
     wait( 0.05 );
  }
}

train_kills_players( train_cars, distance_between_cars )
{
  // find the extents of the train, presuming it is going straight n/s/e/w and then test vs player origins to see
  // if they should be run over
  train_width = 68;
  self endon( "train_stops_killing_players" );

  forward = anglestoforward( self.angles );
  right = anglestoright( self.angles );
  full_car_vec = forward * distance_between_cars;
  half_car_vec = full_car_vec * 0.5;
  train_width_vec = right * train_width;

  sides = [];
  sides[ "front" ] = self.origin + half_car_vec;
  sides[ "rear" ] = self.origin + train_cars * full_car_vec * - 1;
  sides[ "right" ] = self.origin + train_width_vec;
  sides[ "left" ] = self.origin - train_width_vec;
  start = self.origin;

  max_x = sides[ "front" ][ 0 ];
  min_x = sides[ "front" ][ 0 ];
  max_y = sides[ "front" ][ 1 ];
  min_y = sides[ "front" ][ 1 ];
  foreach ( side in sides )
  {
     if ( side[ 0 ] > max_x )
        max_x = side[ 0 ];
     if ( side[ 0 ] < min_x )
        min_x = side[ 0 ];
     if ( side[ 1 ] > max_y )
        max_y = side[ 1 ];
     if ( side[ 1 ] < min_y )
        min_y = side[ 1 ];
  }

  for ( ;; )
  {
     dif = start - self.origin;
     start = self.origin;
     max_x -= dif[ 0 ];
     min_x -= dif[ 0 ];
     max_y -= dif[ 1 ];
     min_y -= dif[ 1 ];

     // store it for the dust fx
     self.min_x = min_x;
     self.max_x = max_x;
     self.min_y = min_y;
     self.max_y = max_y;

     //print3d( ( max_x, max_y, self.origin[ 2 ] ), " * " );
     //print3d( ( min_x, max_y, self.origin[ 2 ] ), " * " );
     //print3d( ( max_x, min_y, self.origin[ 2 ] ), " * " );
     //print3d( ( min_x, min_y, self.origin[ 2 ] ), " * " );

     hit_ents = [];

     foreach ( player in level.players )
     {
        if ( !isalive( player ) )
           continue;
        if ( player.sessionstate != "playing" )
           continue;
        if ( !train_hits( player, min_x, min_y, max_x, max_y ) )
           continue;

        player playsound( "melee_knife_hit_watermelon" );

        pos = get_damageable_player_pos( player );
        hit_ents[ hit_ents.size ] = get_damageable_player( player, pos );
     }

     grenades = getentarray( "grenade", "classname" );
     foreach ( grenade in grenades )
     {
        if ( !train_hits( grenade, min_x, min_y, max_x, max_y ) )
           continue;

        pos = get_damageable_grenade_pos( grenade );
        hit_ents[ hit_ents.size ] = get_damageable_grenade( grenade, pos );
     }

     foreach ( ent in hit_ents )
     {            
        ent.damage = 5000;
        ent.pos = self.origin;
        ent.damageOwner = self;
        ent.eInflictor = self;

        ent maps\mp\gametypes\_weapons::damageEnt(
           ent.eInflictor, // eInflictor = the entity that causes the damage (e.g. a claymore)
           ent.damageOwner, // eAttacker = the player that is attacking
           ent.damage, // iDamage = the amount of damage to do
           "MOD_PROJECTILE_SPLASH", // sMeansOfDeath = string specifying the method of death (e.g. "MOD_PROJECTILE_SPLASH")
           "train_mp", // sWeapon = string specifying the weapon used (e.g. "claymore_mp")
           ent.pos, // damagepos = the position damage is coming from
           vectornormalize(ent.damageCenter - ent.pos) // damagedir = the direction damage is moving in
        );         
     }

     wait( 0.05 );
  }
}

train_hits( entity, min_x, min_y, max_x, max_y )
{
  if ( entity.origin[ 2 ] < self.origin[ 2 ] - 5 )
     return false;

  if ( entity.origin[ 2 ] > self.origin[ 2 ] + 162 )
     return false;

  x = entity.origin[ 0 ];
  y = entity.origin[ 1 ];
  if ( x < min_x )
     return false;
  if ( y < min_y )
     return false;
  if ( x > max_x )
     return false;
  if ( y > max_y )
     return false;
  return true;
}


train_eq( origin, eq_radius, track_time, full_train_time )
{
  train_eq_lerp_for_time( origin, 0.0, 0.09, eq_radius, track_time, 0.5 );
  train_eq_for_time( origin, 0.17, eq_radius, full_train_time, 0.5 );
  train_eq_lerp_for_time( origin, 0.09, 0, eq_radius, track_time, 0.5 );
  //level notify( "stop_train_debug" + origin );
}

train_eq_for_time( origin, eq, eq_radius, eq_time, eq_time_slice )
{
  // earthquake makes the quake taper off over time so we
  // are going to do a lot of earthquakes to simulate a steady quake
  //thread print3d_eq( origin, eq );
  steps = int( eq_time / eq_time_slice );
  for ( i = 0; i < steps; i++ )
  {
     Earthquake( eq, eq_time_slice * 3, origin, eq_radius );
     wait( eq_time_slice );
  }
  remainder = eq_time - steps * eq_time_slice;
  if ( remainder > 0 )
  {
     wait( remainder );
  }
}

train_eq_lerp_for_time( origin, eq1, eq2, eq_radius, eq_time, eq_time_slice )
{
  // earthquake makes the quake taper off over time so we
  // are going to do a lot of earthquakes to simulate a steady quake
  //thread print3d_eq( origin, eq );
  steps = int( eq_time / eq_time_slice );
  for ( i = 0; i < steps; i++ )
  {
     progress = i / steps;
     eq = eq2 * progress + eq1 * ( 1 - progress );
     if ( eq > 0 )
        Earthquake( eq, eq_time_slice * 3, origin, eq_radius );
     wait( eq_time_slice );
  }
  remainder = eq_time - steps * eq_time_slice;
  if ( remainder > 0 )
  {
     wait( remainder );
  }
}

print3d_eq( origin, msg )
{
  level notify( "stop_train_debug" + origin );
  level endon( "stop_train_debug" + origin );
  for ( ;; )
  {
     Print3d( origin, msg );
     wait( 0.05 );
  }
}


 

Snowmobiles:

#include maps\mp\_utility;
#include common_scripts\utility;

/*QUAKED trigger_multiple_area (0.12 0.23 1.0)
defaulttexture="trigger"
"script_area" - A localized string that names the area. e.g. "MP_FLOWER_SHOP"
Defines an area that the player is in.*/

/*QUAKED trigger_multiple_softlanding (0.12 0.23 1.0)
defaulttexture="trigger"
"script_type" - "car", "boxes", "trash"
Defines a soft landing area.*/

/*QUAKED script_vehicle_snowmobile_player_mp (1 0 0) (-16 -16 -24) (16 16 32) USABLE SPAWNER

put this in your GSC:
maps\_snowmobile_player::main( "vehicle_snowmobile" );

and these lines in your CSV:
include,vehicle_snowmobile_snowmobile_player
sound,vehicle_snowmobile,vehicle_standard,all_sp


defaultmdl="vehicle_snowmobile"
default:"vehicletype" "snowmobile_player_mp"
*/

init()
{
  level.softLandingTriggers = getEntArray( "trigger_multiple_softlanding", "classname" );

  destructibles = getEntArray( "destructible_vehicle", "targetname" );

  foreach ( trigger in level.softLandingTriggers )
  {
     if ( trigger.script_type != "car" )
        continue;

     foreach ( destructible in destructibles )
     {
        /*
        if ( !trigger isTouching( destructible ) )
        {
           println( distance( trigger.origin, destructible.origin ) );
           continue;
        }
        */

        if ( distance( trigger.origin, destructible.origin ) > 64.0 )
           continue;

        assert( !isDefined( trigger.destructible ) );

        trigger.destructible = destructible;
     }   
  }

  //foreach ( trigger in level.softLandingTriggers )
  //   trigger thread common_scripts\_dynamic_world::triggerTouchThink( ::playerEnterSoftLanding, ::playerLeaveSoftLanding );

  thread onPlayerConnect();
}


onPlayerConnect()
{
  for ( ;; )
  {
     level waittill ( "connected", player );

     player.softLanding = undefined;

     player thread softLandingWaiter();
  }
}


playerEnterSoftLanding( trigger )
{
  self.softLanding = trigger;
}


playerLeaveSoftLanding( trigger )
{
  self.softLanding = undefined;
}


softLandingWaiter()
{
  self endon ( "disconnect" );

  for ( ;; )
  {
     self waittill ( "soft_landing", trigger, damage );

     //if ( damage < 10 )
     //   continue;

     if ( !isDefined( trigger.destructible ) )
        continue;

     //magicBullet( "mp5_mp", self.origin, self.origin + (0,0,-100), self );

     //self waittill( "damage", damage, attacker, direction_vec, point, type, modelName, tagName, partName, dflags );

     //traceData = http://www.infinityward.com/forum/bulletTrace( self.origin, self.origin + (0,0,-100), true, self );

  }
}

 

Tank killstreak:

 #include maps\mp\_utility;
#include common_scripts\utility;
//#include _vehicleLogic.gsc;

init()
{
//tank support cut
return;
//tank support cut
/*
PrecacheVehicle( "bmp_mp" );
PrecacheVehicle( "m1a1_mp" );
PrecacheVehicle( "bradley_mp" );

precacheModel("vehicle_bmp");
precacheModel("vehicle_bradley");

precacheModel("sentry_gun");
precacheModel("vehicle_m1a1_abrams_d_static");
precacheTurret( "abrams_minigun_mp" );

/#
setDevDvar( "tankDir", "" );
setDevDvar( "tankForceTrigger", 0 );
if ( getDvar( "tankDebug" ) == "" )
	setDevDvar( "tankDebug", 0 );
#/

level.killstreakFuncs["tank"] = ::useTank;
level.tankFire = loadfx( "explosions/large_vehicle_explosion" );
level.tankCover = loadfx( "props/american_smoke_grenade_mp" );

level.otherDir["forward"] = "reverse";
level.otherDir["reverse"] = "forward";

tankSpawners = Vehicle_GetSpawnerArray();

if ( !tankSpawners.size )
	return;

if (!isDefined( getVehicleNode( "startnode", "targetname" ) ) )
{
	assertEx ( !isDefined( getVehicleNode( "startnode", "targetname" ) ), "Vehicle spawn is setup but tank path is not setup in this level bug your friendly neighborhood LD."  );
	return false;
}

level.tankSpawner["allies"] = tankSpawners[0];
level.tankSpawner["axis"] = tankSpawners[0];
level.pathCount = 0; 

foreach ( spawner in tankSpawners )
{
	if ( isSubStr( spawner.model, "bradley" ) )
		level.tankSpawner["allies"] = spawner;

	if ( isSubStr( spawner.model, "bmp" ) )
		level.tankSpawner["axis"] = spawner;
}

level setupPaths();

*/
}


spawnArmor( owner, vehicletype, model )
{
armor = self Vehicle_DoSpawn( "tank", owner );
//armor setModel( model );

armor.health = 3000;
armor.targeting_delay = 1;
armor.team = owner.team;
armor.pers["team"] = armor.team;
armor.owner = owner;
armor setCanDamage( true );
armor.standardSpeed = 12;

armor thread deleteOnZ();
armor addToTankList();
armor.damageCallback = ::Callback_VehicleDamage;

return armor;
}

deleteOnZ()
{
self endon ( "death" );

originalZ = self.origin[2];

for ( ;; )
{
	if ( originalZ - self.origin[2] > 2048 )
	{
		self.health = 0;
		self notify( "death" );
		return;
	}
	wait ( 1.0 );
}
}


useTank( lifeId )
{
return ( self tryUseTank( ) );
}

tryUseTank( )
{
if ( isDefined( level.tankInUse ) && level.tankInUse )
{
	self iPrintLnBold( "Armor support unavailable." );
	return false;
}

if (!isDefined( getVehicleNode( "startnode", "targetname" ) ) )
{
	self iPrintLnBold( "Tank is currently not supported in this level, bug your friendly neighborhood LD." );
	return false;
}

if ( !Vehicle_GetSpawnerArray().size )
	return false;

if ( self.team == "allies" )
	tank = level.tankSpawner["allies"] spawnArmor( self, "vehicle_bradley" );	
else
	tank = level.tankSpawner["axis"] spawnArmor( self, "vehicle_bmp" );	

//level.tank = tank;
tank startTank();
return true;
}


startTank( tankType )
{
startNode = getVehicleNode( "startnode", "targetname" );
waitNode = getVehicleNode( "waitnode", "targetname" );
self.nodes = GetVehicleNodeArray( "info_vehicle_node", "classname" );

level.tankInUse = true;
self thread tankUpdate( startNode, waitNode );
//self thread tankUpdateReverse( startNode, waitNode );

self thread tankDamageMonitor();
level.tank = self;


if ( level.teamBased )
{
	objIDAllies = maps\mp\gametypes\_gameobjects::getNextObjID();
	objective_add( objIDAllies, "invisible", (0,0,0) );
	objective_team( objIDAllies, "allies" );
	level.tank.objID["allies"] = objIDAllies;

	objIDAxis = maps\mp\gametypes\_gameobjects::getNextObjID();
	objective_add( objIDAxis, "invisible", (0,0,0) );
	objective_team( objIDAxis, "axis" );
	level.tank.objID["axis"] = objIDAxis;

	team = self.team;
	level.tank.team = team;
	level.tank.pers[ "team" ] = team;
}

mgTurret = spawnTurret( "misc_turret", self.origin, "abrams_minigun_mp" );
   mgTurret linkTo( self, "tag_engine_left", (0,0,-20), (0,0,0) );
   mgTurret setModel( "sentry_minigun" );
   mgTurret.angles = self.angles; 
   mgTurret.owner = self.owner;
   mgTurret makeTurretInoperable();
   self.mgTurret = mgTurret; 
   self.mgTurret SetDefaultDropPitch( 0 );

oldangles = self.angles;
self.angles = (0,0,0);
tankTurretPoint = self getTagOrigin( "tag_flash" );
self.angles = oldangles;
offset = tankTurretPoint - self.origin;

self thread waitForChangeTeams();
self thread waitForDisco();

self.timeLastFired = getTime();

neutralTargetEnt = spawn("script_origin", self getTagOrigin("tag_flash") );
neutralTargetEnt linkTo( self, "tag_origin", offset, (0,0,0) );
neutralTargetEnt hide();
self.neutralTarget = neutralTargetEnt;

self thread tankGetTargets();
self thread destroyTank();
self thread tankGetMiniTargets();
self thread checkDanger();
self thread watchForThreat(); //reacts to players about to fire with rockets

/#
self thread forceDirection();
#/

}

waitForChangeTeams()
{
self endon ( "death" );
self.owner endon ( "disconnect" );

self.owner waittill ( "joined_team" );
self.health = 0;
self notify( "death" );
}

waitForDisco()
{
self endon ( "death" );

self.owner waittill ( "disconnect" );
self.health = 0;
self notify( "death" );
}

/#
forceDirection()
{
for ( ;; )
{
	if ( getDvar( "tankDir" ) != "" )
	{
		forceDir = getDvar( "tankDir" );
		if ( self.veh_pathdir != forceDir )
		{
			if ( forceDir == "forward" )
				self stopToForward();
			else
				self stopToReverse();
		}
	}

	wait ( 0.05 );
}
}
#/

//=================================================================
//
//					Movement/Update Functions
//
//=================================================================

setDirection( direction )
{
if ( self.veh_pathdir != direction )
{
	if ( direction == "forward" )
		self stopToForward();
	else
		self stopToReverse();
}
}

setEngagementSpeed()
{
self endon( "death" );

self notify ( "path_abandoned" );

while ( isDefined( self.changingDirection ) )
	wait ( 0.05 );

newSpeed = 2;
self vehicle_SetSpeed( newSpeed, 10, 10 );
self.speedType = "engage";
}

setMiniEngagementSpeed()
{
self endon( "death" );

self notify ( "path_abandoned" );

while ( isDefined( self.changingDirection ) )
	wait ( 0.05 );

newSpeed = 2;
self vehicle_SetSpeed( newSpeed, 10, 10 );
self.speedType = "engage";
}

setStandardSpeed()
{
self endon( "death" );

while ( isDefined( self.changingDirection ) )
	wait ( 0.05 );

self vehicle_SetSpeed( self.standardSpeed, 10, 10 );
self.speedType = "standard";
}

setEvadeSpeed()
{
self endon( "death" );

while ( isDefined( self.changingDirection ) )
	wait ( 0.05 );

self vehicle_setSpeed( 15, 15, 15 );
self.speedType = "evade";
wait(1.5);
self vehicle_setSpeed( self.standardSpeed, 10, 10);	
}

setDangerSpeed()
{
self endon( "death" );

while ( isDefined( self.changingDirection ) )
	wait ( 0.05 );

self vehicle_SetSpeed( 5, 5, 5 );
self.speedType = "danger";
}

stopToReverse()
{
debugPrintLn2( "tank changing direction at " + getTime() );
self vehicle_setSpeed( 0, 5, 6 );

self.changingDirection = true;
while ( self.veh_speed > 0 )
	wait ( 0.05 );

wait( 0.25 );
self.changingDirection = undefined;
debugPrintLn2( "tank done changing direction" );

self.veh_transmission = "reverse";
self.veh_pathDir = "reverse";
self vehicle_setSpeed( self.standardSpeed, 5, 6 );
}

stopToForward()
{
debugPrintLn2( "tank changing direction at " + getTime() );
self vehicle_setSpeed( 0, 5, 6 );

self.changingDirection = true;
while ( self.veh_speed > 0 )
	wait ( 0.05 );

wait( 0.25 );
self.changingDirection = undefined;
debugPrintLn2( "tank done changing direction" );

self.veh_transmission = "forward";
self.veh_pathDir = "forward";
self vehicle_setSpeed( self.standardSpeed, 5, 6 );
}

checkDanger()
{
self endon( "death" );

targets = [];
players = level.players;
self.numEnemiesClose = 0;

for( ;; )
{
	foreach (potentialTarget in players)
	{
		if ( !isDefined( potentialTarget ) )
			continue;

		if ( potentialTarget.team == self.team )
		{
			wait( .05 );
			continue;
		}

		dist = Distance2d( potentialTarget.origin, self.origin );

		if ( dist < 2048 )
		{
			self.numEnemiesClose++;	
		}
		wait( .05 );
	}

	if ( isDefined( self.speedType ) && ( self.speedType == "evade" || self.speedType == "engage" ) )
	{
		self.numEnemiesClose = 0;
		continue;
	}

	if ( self.numEnemiesClose > 1 )
		self thread setDangerSpeed();
	else
		self thread setStandardSpeed();

	self.numEnemiesClose = 0;
	wait( .05 );
}
}

tankUpdate( startNode, waitNode )
{
self endon( "tankDestroyed" );
self endon( "death" );

if ( !isDefined( level.graphNodes ) )
{
	self startPath( startNode );
	return;
}

self attachPath( startNode );
self startPath( startNode );
startNode notify ( "trigger", self, true );

wait ( 0.05 );

for ( ;; )
{
	/#
	while ( getDvar( "tankDir" ) != "" )
		wait ( 0.05 );
	#/

	while ( isDefined( self.changingDirection ) )
		wait ( 0.05 );

	endNode = self getNodeNearEnemies();

	if ( isDefined( endNode ) )
		self.endNode = endNode;
	else
		self.endNode = undefined;

	wait ( 0.65 );
}
}


Callback_VehicleDamage( inflictor, attacker, damage, dFlags, meansOfDeath, weapon, point, dir, hitLoc, timeOffset, modelIndex, partName )
{
if ( ( attacker == self || attacker == self.mgTurret || ( isDefined( attacker.pers ) && attacker.pers["team"] == self.team ) ) && ( attacker != self.owner || meansOfDeath == "MOD_MELEE" ) )
	return;

tankDamage = modifyDamage( meansOfDeath, damage, attacker );

self Vehicle_FinishDamage( inflictor, attacker, tankDamage, dFlags, meansOfDeath, weapon, point, dir, hitLoc, timeOffset, modelIndex, partName );
}


// accumulate damage and react
tankDamageMonitor()
{
self endon( "death" );
self.damageTaken = 0;
speed = self vehicle_GetSpeed();
maxHealth = self.health;
stage1 = false;
stage2 = false;
stage3 = false;

for( ;; )
{
	self waittill( "damage", amount, attacker, direction_vec, point, damageType );

	if ( isDefined( attacker.classname ) && attacker.classname == "script_vehicle"  )
	{
		if ( isDefined( self.bestTarget ) && self.bestTarget != attacker )
		{
			self.forcedTarget = attacker;
			println( "Abandoning Target due to vehicle attacker" );	
			self thread explicitAbandonTarget();
		}			
	}
	else
	{
		if ( isPlayer( attacker ) )
		{
			attacker maps\mp\gametypes\_damagefeedback::updateDamageFeedback( "hitHelicopter" );

			if ( attacker _hasPerk( "specialty_armorpiercing" ) )
			{
				damageAdd = amount*level.armorPiercingMod;
				self.health -= int(damageAdd);
			}			
		}
	}

	//stages will be used to effect effeciency of the tank 
	//accuracy, speed, smoke emitters etc....
	if ( self.health <= 0 )
	{
		self notify( "death" );	
		print("sent death notify via script");
		return;
	}
	else if (self.health < (maxHealth/4) && stage3 == false )
	{
		//newSpeed = 4;
		//self vehicle_SetSpeed( newSpeed, 10, 10 );
		//self.standardSpeed = newSpeed;
		stage3 = true;


	}
	else if (self.health < (maxHealth/2) && stage2 == false )
	{
		//newSpeed = 6;
		//self vehicle_SetSpeed( newSpeed, 10, 10 );
		//self.standardSpeed = newSpeed;
		stage2 = true;


	}
	else if (self.health < (maxHealth/1.5) && stage1 == false )
	{
		//newSpeed = 10;
		//self vehicle_SetSpeed( newSpeed, 10, 10 );
		//self.standardSpeed = newSpeed;
		stage1 = true;
	}

	if ( amount > 1000 )
	{
		self handleThreat( attacker );
	}
}
}

handleThreat( attacker )
{
self endon( "death" );

rand = randomInt(100);

if ( isDefined( self.bestTarget) && self.bestTarget != attacker && rand > 30 )
{	
	targ = [];
	targ[0] = self.bestTarget;
	explicitAbandonTarget( true, self.bestTarget );
	self thread acquireTarget( targ );
}
else if ( !isDefined( self.bestTarget ) && rand > 30 )
{
	targ = [];
	targ[0] = attacker;
	self thread acquireTarget( targ );	
}
else if ( rand < 30 )
{
	// all we know here is that it didnt hit the 70%
	playFX( level.tankCover, self.origin);
	self thread setEvadeSpeed();
}
else
{
	self fireWeapon();	
	self playSound( "bmp_fire" );
}
}

handlePossibleThreat( attacker )
{
self endon( "death" );

position = relativeAngle( attacker );
distance = distance( self.origin, attacker.origin );

if ( RandomInt( 4 ) < 3 )
	return;

if( position == "front"  && distance < 768 ) //attempts to crush player
{
	self thread setEvadeSpeed();
}
else if ( position == "rear_side" || ( position == "rear" && distance >= 768 ) )
{
	playFX( level.tankCover, self.origin);
	self thread setEvadeSpeed();	
}
else if( position == "rear" && distance < 768 ) //attempts to crush player
{	

	self stopToReverse();
	self setEvadeSpeed();
	wait( 4 );
	self stopToForward();

}
else if( position == "front_side" || position == "front" )
{
	playFX( level.tankCover, self.origin);
	self stopToReverse();
	self setEvadeSpeed();
	wait( 8 );
	self stopToForward();
}
}

relativeAngle( ent1 )
{
self endon( "death" );
ent1 endon( "death" );
ent1 endon( "disconnect" );

tankForwardVector = anglesToForward( self.angles );
tankToEnt = ent1.origin - self.origin;
tankForwardVector *= (1,1,0);
tankToEnt *= (1,1,0 );

tankToEnt = VectorNormalize( tankToEnt );
TankForwardVector = VectorNormalize( tankForwardVector );

targetCosine = VectorDot( tankToEnt, tankForwardVector );

if ( targetCosine > 0 )
{
	if ( targetCosine > .9 )
		return "front";
	else
		return "front_side";
}
else
{
	if ( targetCosine < -.9 )
		return "rear";
	else
		return "rear_side";
}	

ent1 iPrintLnBold( targetCosine );
}

watchForThreat()
{
self endon( "death" );

for ( ;; )
{
	targets = [];
	players = level.players;

	foreach (player in players)
	{
		if ( !isDefined( player ) )
		{
			wait( .05 );
			continue;
		}

		if ( !isTarget( player ) )
		{
			wait ( .05 );
			continue;
		}

		currentWeapon = player GetCurrentWeapon();

		if ( isSubStr( currentWeapon, "at4" ) || isSubStr( currentWeapon, "stinger" ) || isSubStr( currentWeapon, "javelin" ) )
		{
			self thread handlePossibleThreat( player );
			wait( 8 );
		}

		wait( .15 );
	}
}
}

//=================================================================
//
//					Accessory Functions
//
//=================================================================

// checks if owner is valid, returns false if not valid
checkOwner()
{
if ( !isdefined( self.owner ) || !isdefined( self.owner.pers["team"] ) || self.owner.pers["team"] != self.team )
{
	self notify ( "abandoned" );
	return false;	
}
return true;
}

drawLine( start, end, timeSlice, color )
{
drawTime = int(timeSlice * 20);
for( time = 0; time < drawTime; time++ )
{
	line( start, end, color,false, 1 );
	wait ( 0.05 );
}
}

modifyDamage( damageType, amount, attacker )
{
if ( damageType == "MOD_RIFLE_BULLET" )	
	return ( amount );
else if ( damageType == "MOD_PISTOL_BULLET" )
	return ( amount );
else if ( damageType == "MOD_IMPACT" )
	return ( amount );
else if (damageType == "MOD_MELEE" )
	return ( 0 );
else if (damageType == "MOD_EXPLOSIVE_BULLET" )
	return ( amount );
else if (damageType == "MOD_GRENADE" )
	return ( amount * 5 );
else if (damageType == "MOD_GRENADE_SPLASH" )
	return ( amount * 5 );
else 
	return amount * 10;
}

destroyTank()
{
self waittill ( "death" );

if ( level.teamBased )
{
	team = level.tank.team;
	objective_state( level.tank.objID[team], "invisible" );		
	objective_state( level.tank.objID[level.otherTeam[team]], "invisible" );
}

/* get the current team
if ( isDefined( level.tankSpawner["axis"] ) )
	destroyedModel = ;
else
	destroyedModel = ;
*/

// award attacker
self notify( "tankDestroyed" );
self Vehicle_SetSpeed( 0,10,10 );
level.tankInUse = false;
playFX( level.spawnFire, self.origin);

playFX( level.tankFire, self.origin);

self removeFromTankList();

	destroyedTank = Spawn( "script_model", self.origin );
	// set model to current destroyed model.
   destroyedTank setModel( "vehicle_m1a1_abrams_d_static" );
   destroyedTank.angles = self.angles; 
self.mgTurret delete();
self delete();

wait(4);
destroyedTank delete();
}

//=================================================================
//
//					Main Weapon Targeting Functions
//
//=================================================================

onHitPitchClamp()
{	
self notify( "onTargOrTimeOut" );

self endon( "onTargOrTimeOut" );
self endon( "turret_on_target" );

self waittill( "turret_pitch_clamped" );
println( "Abandoning Target due to turret not being able to reach target" );	
self thread explicitAbandonTarget( false, self.bestTarget );
}

fireOnTarget()
{
self endon( "abandonedTarget" );
self endon( "killedTarget" );
self endon( "death" );
self endon( "targetRemoved" );
self endon( "lostLOS" );


for ( ;; )
{
	self onHitPitchClamp();

	if ( !isDefined( self.bestTarget ) )
		continue;

	flashOrigin = self GetTagOrigin( "tag_flash" );	
	trace = BulletTrace( self.origin, flashOrigin, false, self );
	if ( trace["position"] != flashOrigin )
	{
		println( "Abandoning Target due to turret not being able to reach target without clipping" );	
		self thread explicitAbandonTarget( false, self.bestTarget );
	}

	trace = BulletTrace( flashOrigin, self.bestTarget.origin, true, self );
	distance = Distance(self.origin, trace["position"] );
	realDistance = Distance( self.bestTarget.origin, self.origin );

	//hitting somthing not even close
	if ( distance < 384 || distance + 256 < realDistance ) 
	{
		wait( .5 );

		if ( distance > 384 )
		{
			self waitForTurretReady();
			self FireWeapon();
			self playSound( "bmp_fire" );
			self.timeLastFired = getTime();
		}
		println( "Abandoning due to not hitting intended space" );

		// Adjust forward or backward to hit target...
		// check angle of target
		position = relativeAngle( self.bestTarget );

		//if ( position == "rear_side" )
			// backup
		//if ( position == "front_side" )


		self thread explicitAbandonTarget( false, self.bestTarget );
		return;
	}

	self waitForTurretReady();

	self FireWeapon();
	self playSound( "bmp_fire" );
	self.timeLastFired = getTime();
}
}

waitForTurretReady()
{
self endon( "abandonedTarget" );
self endon( "killedTarget" );
self endon( "death" );
self endon( "targetRemoved" );
self endon( "lostLOS" );

timeWaited = getTime() - self.timeLastFired;

if ( timeWaited < 1499 )
		wait( 1.5 - timeWaited/1000 );
}

tankGetTargets( badTarget )
{
self endon( "death" );
self endon( "leaving" );
targets = [];

prof_begin( "tankTargets" );

for ( ;; )
{
	targets = [];
	players = level.players;

	if ( isDefined( self.forcedTarget ) )
	{
		targets = [];
		targets[0] = self.ForcedTarget;
		self acquireTarget( targets );
		self.forcedTarget = undefined;
	}

	if ( isDefined( level.harrier ) && level.harrier.team != self.team && isAlive( level.harrier ) )
	{
		if( isVehicleTarget( level.tank ) )
			targets[targets.size] = level.tank;
	}

	if ( isDefined( level.chopper ) && level.chopper.team != self.team  && isAlive( level.chopper ) )
	{	
		if( isVehicleTarget( level.chopper ) )
			targets[targets.size] = level.chopper;
	}

	foreach ( potentialTarget in players )
	{
		if (!isDefined( potentialTarget ) )
		{
			wait(.05);
			continue;
		}

		if ( isDefined( badTarget ) && potentialTarget == badTarget ) 
			continue;

		if ( isTarget( potentialTarget ) )
		{
			if( isDefined( potentialTarget ) )
				targets[targets.size] = potentialTarget;
		}
		else
			continue;
	}
	if ( targets.size > 0 )
	{
		self acquireTarget( targets );
	}
	else
		wait( 1 );
}
prof_end( "tankTargets" );
}

acquireTarget( targets )
{
self endon( "death" );

if ( targets.size == 1 )
	self.bestTarget = targets[0];
else
	self.bestTarget = self getBestTarget( targets );

self thread setEngagementSpeed(); // slows tank down to fire on target

// checks to abandon target
//self thread lostTarget(); // sets lost LOS and time of lost target
//self thread abandonTarget(); // if target is lost for 3+ seconds drops target and gets new one
self thread watchTargetDeath( targets ); //abandons target when target killed


self SetTurretTargetEnt( self.bestTarget );	// sets turret to target entity
self fireOnTarget(); // fires on current target.
self thread setNoTarget();
}

setNoTarget()
{
self endon( "death" );

self setStandardSpeed();
self removeTarget();
self setTurretTargetEnt( self.neutralTarget );
}

getBestTarget( targets )
{
self endon( "death" );
mainGunPointOrigin = self getTagOrigin( "tag_flash" );
tankOrigin = self.origin;

bestYaw = undefined;
bestTarget = undefined;
targetHasRocket = false;

foreach ( targ in targets )
{
	angle = abs ( vectorToAngles ( ( targ.origin - self.origin ) )[1] );
	cannonAngle = abs( self getTagAngles( "tag_flash" )[1] );
	angle = abs ( angle - cannonAngle );			

	//vehicle priorities
	if ( isDefined( level.chopper ) && targ == level.chopper )
		return targ;

	if ( isDefined( level.harrier ) && targ == level.harrier )
		return targ;

	// in this calculation having a rocket removes 40d of rotation cost from best target calculation
	// to prioritize targeting dangerous targets.
	weaponsArray = targ GetWeaponsListItems();
	foreach ( weapon in weaponsArray )
	{
		if ( isSubStr( weapon, "at4" ) || isSubStr( weapon, "jav" ) || isSubStr( weapon, "c4" ) )
			angle -= 40;
	}

	if ( !isDefined( bestYaw ) )
	{				
		bestYaw = angle;
		bestTarget = targ;
	} 
	else if ( bestYaw > angle )
	{
		bestYaw = angle;
		bestTarget = targ;			
	}
}

return ( bestTarget );
}

watchTargetDeath( targets )
{
self endon( "abandonedTarget" );
self endon( "lostLOS" );
self endon( "death" );
self endon( "targetRemoved" );

bestTarg = self.bestTarget;
bestTarg endon ( "disconnect" );

bestTarg waittill( "death" );

self notify( "killedTarget" );
self removeTarget();
self setStandardSpeed();
self thread setNoTarget();
}

explicitAbandonTarget( noNewTarget, targ )
{
self endon( "death" );

self notify( "abandonedTarget" );
println("ABANDON TARGET EXPLICIT");
self setStandardSpeed();
self thread Setnotarget();
self removeTarget();

if ( isDefined(targ) )
{
	self.badTarget = targ;
	badTargetReset();
}	

if ( isDefined(noNewTarget) && noNewTarget )
	return;

return;
}

badTargetReset()
{
self endon("death");

wait (1.5);
self.badTarget = undefined;		
}

removeTarget()
{
self notify( "targetRemoved" );

self.bestTarget = undefined;
self.lastLostTime = undefined;	
}

isVehicleTarget( potentialTarget )
{
if ( distance2D( potentialTarget.origin, self.origin ) > 4096 )
	return false;

if ( distance( potentialTarget.origin , self.origin ) < 512 )
	return false;

return turretSightTrace( potentialTarget, false );
}

isTarget( potentialTarget )
{
self endon( "death" );

dist = distanceSquared( potentialTarget.origin, self.origin );

if ( !level.teamBased && isDefined( self.owner ) && potentialTarget == self.owner )
	return false;

if ( !isalive( potentialTarget ) || potentialTarget.sessionstate != "playing" )
	return false;

if ( dist > 4096*4096 )
	return false;

if ( dist < 512*512 )
	return false;

if ( !isdefined( potentialTarget.pers["team"] ) )
	return false;

if ( potentialTarget == self.owner )
	return false;

if ( level.teamBased && potentialTarget.pers["team"] == self.team )
	return false;

if ( potentialTarget.pers["team"] == "spectator" )
	return false;

if ( isdefined( potentialTarget.spawntime ) && ( gettime() - potentialTarget.spawntime )/1000 <= 5 )
	return false;

if ( potentialTarget _hasPerk( "specialty_coldblooded" ) )
	return false;

return self Vehicle_CanTurretTargetPoint( potentialTarget.origin, 1, self );

//return self turretSightTrace( potentialTarget, false );
}

turretSightTrace( targ, debug )
{
turretCanSeeTarget = targ sightConeTrace( self getTagOrigin( "tag_turret" ), self );

if ( turretCanSeeTarget < .7 )
{
	return false;	
}

if ( isDefined(debug) && debug )
	self thread drawLine( targ.origin, self getTagOrigin( "tag_turret" ), 10, (1,0,0) );

return true;	
}

//=================================================================
//
//					Secondary Weapon Targeting Functions
//
//=================================================================

isMiniTarget( potentialTarget )
{
self endon( "death" );

if ( !isalive( potentialTarget ) || potentialTarget.sessionstate != "playing" )
	return false;

if ( !isdefined( potentialTarget.pers["team"] ) )
	return false;

if ( potentialTarget == self.owner )
	return false;

if ( distanceSquared( potentialTarget.origin , self.origin ) > 1024*1024 )
	return false;

if ( level.teamBased && potentialTarget.pers["team"] == self.team )
	return false;

if ( potentialTarget.pers["team"] == "spectator" )
	return false;

if ( isdefined( potentialTarget.spawntime ) && ( gettime() - potentialTarget.spawntime )/1000 <= 5 )
	return false;


if ( isDefined( self ) )
{
	minTurretEye = self.mgTurret.origin + ( 0, 0, 64 );
	minTurretCanSeeTarget = potentialTarget sightConeTrace( minTurretEye, self );

	if ( minTurretCanSeeTarget < 1 )
		return false;	
}

return true;
}

tankGetMiniTargets()
{
self endon( "death" );
self endon( "leaving" );
miniTargets = [];
	println( "Geting Mini Targets" );

for ( ;; )
{
	miniTargets = [];
	players = level.players;

	for (i = 0; i <= players.size; i++)
	{
		if ( isMiniTarget( players[i] ) )
		{
			if( isdefined( players[i] ) )
				miniTargets[miniTargets.size] = players[i];
		}
		else
			continue;

		wait( .05 );
	}
	if ( miniTargets.size > 0 )
	{
		self acquireMiniTarget( miniTargets );
		return;	
	}	
	else
		wait( .5 );
}
}

getBestMiniTarget( targets )
{
self endon( "death" );
tankOrigin = self.origin;

closest = undefined;
bestTarget = undefined;

foreach ( targ in targets )
{		
	curDist = Distance( self.origin, targ.origin );

	// in this calculation having a rocket javelin or c4 increases mini turret priority
	// to prioritize targeting dangerous targets.
	curWeaon = targ GetCurrentWeapon();
	if ( isSubStr( curWeaon, "at4" ) || isSubStr( curWeaon, "jav" ) || isSubStr( curWeaon, "c4" ) || isSubStr( curWeaon, "smart" ) || isSubStr( curWeaon, "grenade" ) )
			curDist -= 200;

	if ( !isDefined( closest ) )
	{
		closest = curDist;
		bestTarget = targ;
	} 
	else if ( closest > curDist )
	{
		closest = curDist;
		bestTarget = targ;
	}	
}
return ( bestTarget );
}

acquireMiniTarget( targets )
{
self endon( "death" );

if ( targets.size == 1 )
	self.bestMiniTarget = targets[0];
else
	self.bestMiniTarget = self getBestMiniTarget( targets );

if ( distance2D( self.origin, self.bestMiniTarget.origin) > 768 ) 
	self thread setMiniEngagementSpeed();

self notify( "acquiringMiniTarget" );
self.mgTurret SetTargetEntity( self.bestMiniTarget, ( 0,0,64 ) );	// sets turret to target entity
wait( .15 );
self thread fireMiniOnTarget(); // fires on current target.
self thread watchMiniTargetDeath( targets ); //abandons target when target killed	
self thread watchMiniTargetDistance( targets );
self thread watchMiniTargetThreat( self.bestMiniTarget );
}

fireMiniOnTarget()
{
self endon( "death" );
self endon( "abandonedMiniTarget" );
self endon( "killedMiniTarget" );
noTargTime = undefined;
miniAcquiredTime = getTime();

if ( !isDefined( self.bestMiniTarget ) )
{
	println("No Targ to fire on");
	return;
}

println("firing on best target");

while( 1 )
{
	if ( !isDefined ( self.mgTurret getTurretTarget( true ) ) )
	{
		if ( !isDefined( noTargTime ) )
			noTargTime = getTime();

		curTime = getTime();

		if ( noTargTime - curTime > 1 )
		{	
			noTargTime = undefined;
			self thread explicitAbandonMiniTarget();
			return;
		}

		//println("Waiting because the turret doesnt have a target" );

		wait ( .5 );
		continue;
	}

	if ( getTime() > miniAcquiredTime + 1000 && !isDefined( self.bestTarget ) )
	{
		if ( distance2D(self.origin, self.bestMiniTarget.origin ) > 768 )
		{
			targets[0] = self.bestMiniTarget;
			self acquireTarget( targets );
		}
	}

	numShots = randomIntRange( 10, 16 );
	for ( i = 0; i < numShots; i++ )
	{
		self.mgTurret ShootTurret();
		wait ( .1 );
	}
	wait ( randomFloatRange( 0.5, 3.0 ) );
}
}

watchMiniTargetDeath( targets )
{
self endon( "abandonedMiniTarget" );
self endon( "death" );
if ( ! isDefined( self.bestMiniTarget ) )
	return;

self.bestMiniTarget waittill( "death" );

self notify( "killedMiniTarget" );
println( "Killed Mini Target" );

self.bestMiniTarget = undefined;
self.mgTurret ClearTargetEntity();
self tankGetMiniTargets();
}

watchMiniTargetDistance( targets )
{
self endon( "abandonedMiniTarget" );
self endon( "death" );

for ( ;; )
{
	if (! isDefined( self.bestMiniTarget ) )
		return;

	trace = BulletTrace( self.mgTurret.origin, self.bestMiniTarget.origin, false, self );
	traceDistance = Distance(self.origin, trace["position"] );

	if ( traceDistance > 1024 )
	{
		println( "MINI TARGET DIST TOO FAR!!!" );
		self thread explicitAbandonMiniTarget();
		return;	
	}
	println( traceDistance );
	wait ( 2 );
}	
}

watchMiniTargetThreat( curTarget )
{
self endon( "abandonedMiniTarget" );
self endon( "death" );
self endon( "killedMiniTarget" );

for ( ;; )
{
	miniTargets = [];
	players = level.players;

	for (i = 0; i <= players.size; i++)
	{
		if ( isMiniTarget( players[i] ) )
		{
			if( !isdefined( players[i] ) )
				continue;

			if( !isdefined(curTarget) )
				return;

			traceOldTarg = Distance(self.origin, CurTarget.origin );
			traceNewTarg = Distance(self.origin, players[i].origin );

			if ( traceNewTarg < traceOldTarg )
			{
				self thread explicitAbandonMiniTarget();
				return;	
			}
		}

		wait( .05 );
	}

	wait( .25 );		
}	
}

explicitAbandonMiniTarget( noNewTarget )
{

self notify( "abandonedMiniTarget" );

println( "ABANDONED MINI TARGET" );

self.bestMiniTarget = undefined;
self.mgTurret ClearTargetEntity();

if ( isDefined(noNewTarget) && noNewTarget )
	return;

self thread tankGetMiniTargets();
return;
}


addToTankList()
{
level.tanks[self getEntityNumber()] = self;	
}

removeFromTankList()
{
level.tanks[self getEntityNumber()] = undefined;
}	


/*************************************************************************
*
*	PATHFINDING AND PATH NODE FUNCTIONS
*
***************************************************************************/

getNodeNearEnemies()
{
validEnemies = [];

foreach ( player in level.players )
{
	if ( player.team == "spectator" )
		continue;

	if ( player.team == self.team )
		continue;

	if ( !isAlive( player ) )
		continue;

	player.dist = 0;
	validEnemies[validEnemies.size] = player;		
}

if ( !validEnemies.size )
	return undefined;

for ( i = 0; i < validEnemies.size; i++ )
{
	for ( j = i + 1; j < validEnemies.size; j++ )
	{
		dist = distanceSquared( validEnemies[i].origin, validEnemies[j].origin );

		validEnemies[i].dist += dist;
		validEnemies[j].dist += dist;
	}
}

bestPlayer = validEnemies[0];
foreach ( player in validEnemies )
{
	if ( player.dist < bestPlayer.dist )
		bestPlayer = player;
}

bestOrigin = bestPlayer.origin;

sortedNodes = sortByDistance( level.graphNodes, bestOrigin );

//thread drawLine( bestOrigin, sortedNodes[0].origin, 10.0, (1,0,1) );

return ( sortedNodes[0] );
}


setupPaths()
{
tankNodes = [];
startNodes = [];
endNodes = [];
aStarGraphNodes = [];

// setup the start node
tankNode = GetVehicleNode( "startnode", "targetname" );
tankNodes[tankNodes.size] = tankNode;
startNodes[startNodes.size] = tankNode;

while ( isDefined( tankNode.target ) )
{
	lastNode = tankNode;
	tankNode = GetVehicleNode( tankNode.target, "targetname" );
	tankNode.prev = lastNode;

	// case for connected path
	if ( tankNode == tankNodes[0] )	
		break;

	tankNodes[tankNodes.size] = tankNode;

	// case for disconnected path
	if ( !isDefined( tankNode.target ) )
		return;
}

tankNodes[0].branchNodes = [];
tankNodes[0] thread handleBranchNode( "forward" );
aStarGraphNodes[aStarGraphNodes.size] = tankNodes[0];

// find the start and end nodes of the branches
branchNodes = GetVehicleNodeArray( "branchnode", "targetname" );
foreach ( branchNode in branchNodes )
{
	tankNode = branchNode;
	tankNodes[tankNodes.size] = tankNode;
	startNodes[startNodes.size] = tankNode;

	while ( isDefined( tankNode.target ) )
	{
		lastNode = tankNode;
		tankNode = GetVehicleNode( tankNode.target, "targetname" );
		tankNodes[tankNodes.size] = tankNode;
		tankNode.prev = lastNode;

		if ( !isDefined( tankNode.target ) )
			endNodes[endNodes.size] = tankNode;
	}
}

// detect and initialize the branch nodes.  These will be used for the aStar node graph
foreach ( tankNode in tankNodes )
{		
	isBranchNode = false;
	foreach ( startNode in startNodes )
	{
		if ( startNode == tankNode )
			continue;

		if ( startNode.target == tankNode.targetname )
			continue;

		if ( isDefined( tankNode.target ) && tankNode.target == startNode.targetname )
			continue;

		if ( distance2d( tankNode.origin, startNode.origin ) > 80 )
			continue;

		startNode thread handleCapNode( tankNode, "reverse" );
		startNode.prev = tankNode;

		if ( !isDefined( tankNode.branchNodes ) )
			tankNode.branchNodes = [];

		tankNode.branchNodes[tankNode.branchNodes.size] = startNode;

		isBranchNode = true;
	}

	if ( isBranchNode )
		tankNode thread handleBranchNode( "forward" );

	isJoinNode = false;
	foreach ( endNode in endNodes)
	{
		if ( endNode == tankNode )
			continue;

		if ( !isDefined( tankNode.target ) )
			continue;

		if ( tankNode.target == endNode.targetname )
			continue;	

		if ( isDefined( endNode.target ) && endNode.target == tankNode.targetname )
			continue;	

		if ( distance2d( tankNode.origin, endNode.origin ) > 80 )
			continue;

		endNode thread handleCapNode( tankNode, "forward" );
		endNode.next = getVehicleNode( tankNode.targetname, "targetname" );
		//endNode.target = tankNode.targetname; // READ-ONLY field...
		endNode.length = distance( endNode.origin, tankNode.origin );

		if ( !isDefined( tankNode.branchNodes ) )
			tankNode.branchNodes = [];

		tankNode.branchNodes[tankNode.branchNodes.size] = endNode;

		isJoinNode = true;
	}

	if ( isJoinNode )
	{
		assert( !isBranchNode );
		tankNode thread handleBranchNode( "reverse" );
	}

	if ( isJoinNode || isBranchNode )
		aStarGraphNodes[aStarGraphNodes.size] = tankNode;
}

if ( aStarGraphNodes.size < 3 )
{
	level notify ( "end_tankPathHandling" );
	return;
}

// subdivide the path a bit...
segmentNodes = [];
foreach( tankNode in tankNodes )
{
	if ( !isDefined( tankNode.branchNodes ) )
		continue;

	segmentNodes[segmentNodes.size] = tankNode;
}

foreach ( segmentNode in segmentNodes )
{
	tankNode = segmentNode;
	pathLength = 0;

	while ( isDefined( tankNode.target ) )
	{
		prevNode = tankNode;
		tankNode = GetVehicleNode( tankNode.target, "targetname" );
		pathLength += distance( tankNode.origin, prevNode.origin );

		if ( tankNode == segmentNode )	
			break;

		if ( isDefined( tankNode.branchNodes ) )
			break;
	}

	if ( pathLength > 1000 )
	{
		tankNode = segmentNode;
		curLength = 0;

		while ( isDefined( tankNode.target ) )
		{
			prevNode = tankNode;
			tankNode = GetVehicleNode( tankNode.target, "targetname" );

			curLength += distance( tankNode.origin, prevNode.origin );
			if ( curLength < pathLength / 2 )
				continue;

			tankNode.branchNodes = []; // necessary?
			tankNode thread handleBranchNode( "forward" );
			aStarGraphNodes[aStarGraphNodes.size] = tankNode;
			break;
		}
	}
}

level.graphNodes = initNodeGraph( aStarGraphNodes );

foreach ( tankNode in tankNodes )
{
	if ( !isDefined( tankNode.graphId ) )
		tankNode thread nodeTracker();
}
}



getRandomBranchNode( direction )
{
branchNodes = [];
foreach ( graphId, linkNode in self.links )
{
	// pick a branch in the direction we're already heading
	if ( self.linkDirs[graphId] != direction )
		continue;

	branchNodes[branchNodes.size] = linkNode;
}

return ( branchNodes[randomInt( branchNodes.size )] );
}


getNextNodeForEndNode( endNode, direction )
{
graphNode = level.graphNodes[self.graphId];

continuePath = generatePath( graphNode, endNode, undefined, direction );
continueG = continuePath[0].g;

changePath = generatePath( graphNode, endNode, undefined, level.otherDir[direction] );
changeG = changePath[0].g;

// temporarily force the tank to only go forward
if ( !getDvarInt( "tankDebug" ) )
	changeG = 9999999;

if ( continueG <= changeG )
	return ( continuePath[1] );
}


handleBranchNode( direction )
{
level endon ( "end_tankPathHandling" );
for ( ;; )
{
	self waittill( "trigger", tank, wasForced );

	graphNode = level.graphNodes[self.graphId];

	tank.node = self;

	nextGraphNode = undefined;
	if ( isDefined( tank.endNode ) && tank.endNode != graphNode )
	{
		nextGraphNode = getNextNodeForEndNode( tank.endNode, tank.veh_pathdir );

		if ( !isDefined( nextGraphNode ) )
			tank thread setDirection( level.otherDir[tank.veh_pathdir] );
	}

	if ( !isDefined( nextGraphNode ) || nextGraphNode == graphNode )
	{
		nextGraphNode = graphNode getRandomBranchNode( tank.veh_pathdir );
	}

	goalNode = graphNode.linkStartNodes[nextGraphNode.graphId];

	if ( tank.veh_pathdir == "forward" )
		nextLinkNode = self getNextNode();
	else
		nextLinkNode = self getPrevNode();

	// if we're already on this path, just keep going
	if ( nextLinkNode != goalNode )
		tank startPath( goalNode );
}
}


handleCapNode( joinNode, direction )
{
for ( ;; )
{
	self waittill( "trigger", tank );

	if ( tank.veh_pathdir != direction )
		continue;

	debugPrintLn2( "tank starting path at join node: " + joinNode.graphId );

	tank startPath( joinNode );
}
}


nodeTracker()
{
self.forwardGraphId = getForwardGraphNode().graphId;
self.reverseGraphId = getReverseGraphNode().graphId;

for ( ;; )
{
	self waittill ( "trigger", tank, wasForced );

	tank.node = self;

	/#
	if ( getDvarInt( "tankForceTrigger" ) )
	{
		if ( tank.veh_pathdir == "forward" )
			tank thread forceTrigger( self, self getNextNode(), tank );
		else
			tank thread forceTrigger( self, self getPrevNode(), tank );
		}
	#/

	tank.forwardGraphId = self.forwardGraphId;
	tank.reverseGraphId = self.reverseGraphId;

	if ( !isDefined( self.target ) || self.targetname == "branchnode" )
		nodeType = "TRANS";
	else
		nodeType = "NODE";

	if ( isDefined( wasForced ) )
		debugPrint3D( self.origin, nodeType, (1,0.5,0), 1, 2, 100 );
	else
		debugPrint3D( self.origin, nodeType, (0,1,0), 1, 2, 100 );
}
}


forceTrigger( prevNode, nextNode, tank )
{
nextNode endon ( "trigger" );
prevNode endon ( "trigger" );
tank endon ( "death" );

minDist = distanceSquared( tank.origin, nextNode.origin );
tankDir = tank.veh_pathdir;

debugPrint3D( prevNode.origin+(0,0,30), "LAST", (0,0,1), 0.5, 1, 100 );
debugPrint3D( nextNode.origin+(0,0,60), "NEXT", (0,1,0), 0.5, 1, 100 );

timeOutNextFrame = false;
for ( ;; )
{
	wait ( 0.05 );

	// tank changed direction
	if ( tankDir != tank.veh_pathdir )
	{
		debugPrintLn2( "tank missed node: reversing direction" );
		tank thread forceTrigger( nextNode, prevNode, tank );
		return;
	}

	if ( timeOutNextFrame )
	{
		debugPrintLn2( "... sending notify." );
		nextNode notify ( "trigger", tank, true );
		return;
	}

	curDist = distanceSquared( tank.origin, nextNode.origin );

	if ( curDist > minDist )
	{
		timeOutNextFrame = true;
		debugPrintLn2( "tank missed node: forcing notify in one frame..." );
	}

	minDist = curDist;		
}
}


getForwardGraphNode()
{
assert( !isDefined( self.graphId ) );

checkNode = self;	
while ( !isDefined( checkNode.graphId ) )
	checkNode = checkNode getNextNode();

return checkNode;
}


getReverseGraphNode()
{
assert( !isDefined( self.graphId ) );

checkNode = self;	
while ( !isDefined( checkNode.graphId ) )
	checkNode = checkNode getPrevNode();

return checkNode;
}


getNextNode()
{
if ( isDefined( self.target ) )
	return ( GetVehicleNode( self.target, "targetname" ) );
else
	return ( self.next );
}


getPrevNode()
{
return self.prev;
}



// Builds the aStar node graph
initNodeGraph( astarBaseNodes )
{
graphNodes = [];
foreach ( pathNode in aStarBaseNodes )
{
	graphNode = spawnStruct();
	graphNode.linkInfos = [];
	graphNode.links = [];
	graphNode.linkLengths = [];
	graphNode.linkDirs = [];
	graphNode.linkStartNodes = [];
	graphNode.node = pathNode;
	graphNode.origin = pathNode.origin;
	graphNode.graphId = graphNodes.size;
	pathNode.graphId = graphNodes.size;

	debugPrint3D( graphNode.origin + (0,0,80), graphNode.graphId, (1,1,1), 0.65, 2, 100000 );

	graphNodes[graphNodes.size] = graphNode;		
}

foreach ( pathNode in aStarBaseNodes )
{
	graphId = pathNode.graphId;

	checkNode = GetVehicleNode( pathNode.target, "targetname" );
	linkLength = distance( pathNode.origin, checkNode.origin );
	linkStartNode = checkNode;

	while ( !isDefined( checkNode.graphId ) )
	{
		linkLength += distance( checkNode.origin, checkNode.prev.origin );

		if ( isDefined( checkNode.target ) )
			checkNode = GetVehicleNode( checkNode.target, "targetname" );
		else
			checkNode = checkNode.next;
	}

	assert( checkNode != pathNode );
	graphNodes[graphId] addLinkNode( graphNodes[checkNode.graphId], linkLength, "forward", linkStartNode );

	checkNode = pathNode.prev;
	linkLength = distance( pathNode.origin, checkNode.origin );
	linkStartNode = checkNode;

	while ( !isDefined( checkNode.graphId ) )
	{
		linkLength += distance( checkNode.origin, checkNode.prev.origin );
		checkNode = checkNode.prev;
	}

	assert( checkNode != pathNode );		
	graphNodes[graphId] addLinkNode( graphNodes[checkNode.graphId], linkLength, "reverse", linkStartNode );

	foreach ( branchNode in pathNode.branchNodes )
	{
		checkNode = branchNode;
		linkLength = distance( pathNode.origin, checkNode.origin );
		linkStartNode = checkNode;

		if ( checkNode.targetname == "branchnode" )
		{
			while ( !isDefined( checkNode.graphId ) )
			{
				if ( isDefined( checkNode.target ) )
					nextNode = GetVehicleNode( checkNode.target, "targetname" );
				else
					nextNode = checkNode.next;

				linkLength += distance( checkNode.origin, nextNode.origin );
				checkNode = nextNode;
			}

			assert( checkNode != pathNode );		
			graphNodes[graphId] addLinkNode( graphNodes[checkNode.graphId], linkLength, "forward", linkStartNode );
		}	
		else
		{
			while ( !isDefined( checkNode.graphId ) )
			{
				linkLength += distance( checkNode.origin, checkNode.prev.origin );
				checkNode = checkNode.prev;
			}

			assert( checkNode != pathNode );		
			graphNodes[graphId] addLinkNode( graphNodes[checkNode.graphId], linkLength, "reverse", linkStartNode );
		}
	}
}

return graphNodes;
}


addLinkNode( graphNode, linkLength, linkDir, linkStartNode )
{
assert( self.graphId != graphNode.graphId );
assert( !isDefined( self.links[graphNode.graphId] ) );

self.links[graphNode.graphId] = graphNode;
self.linkLengths[graphNode.graphId] = linkLength;
self.linkDirs[graphNode.graphId] = linkDir;
self.linkStartNodes[graphNode.graphId] = linkStartNode;

linkInfo = spawnStruct();
linkInfo.toGraphNode = graphNode;
linkInfo.toGraphId = graphNode.graphId;
linkInfo.length = linkLength;
linkInfo.direction = linkDir;
linkInfo.startNode = linkStartNode;

self.linkInfos[graphNode.graphId] = linkInfo;
}


// call function as generatePath(startNode, destNode), otherwise paths will be reversed
generatePath( destNode, startNode, blockedNodes, direction )
{
level.openList = [];
level.closedList = [];
foundPath = false;
pathNodes = [];

if ( !isDefined( blockedNodes ) )
	blockedNodes = [];

startNode.g = 0;
startNode.h = getHValue( startNode, destNode );
startNode.f = startNode.g + startNode.h;

addToClosedList( startNode );

curNode = startNode;
for ( ;; )
{
	foreach ( linkId, checkNode in curNode.links )
	{
		if ( is_in_array( blockedNodes, checkNode ) )
			continue;

		if ( is_in_array( level.closedList, checkNode ) )
			continue;

		if ( isDefined( direction ) && checkNode.linkDirs[curNode.graphId] != direction )
			continue;

		if ( !is_in_array( level.openList, checkNode ) )
		{
			addToOpenList( checkNode );

			checkNode.parentNode = curNode;
			checkNode.g = getGValue( checkNode, curNode );
			checkNode.h = getHValue( checkNode, destNode );
			checkNode.f = checkNode.g + checkNode.h;

			if ( checkNode == destNode )
				foundPath = true;	
		}
		else
		{
			if ( checkNode.g < getGValue( curNode, checkNode ) )
				continue;

			checkNode.parentNode = curNode;
			checkNode.g = getGValue( checkNode, curNode );
			checkNode.f = checkNode.g + checkNode.h;
		}
	}

	if ( foundPath )
		break;

	addToClosedList( curNode );

	bestNode = level.openList[0];

	foreach ( testNode in level.openList )
	{
		if ( testNode.f > bestNode.f )
			continue;

		bestNode = testNode;
	}

	assert( isDefined( bestNode ) ); // the tank should always have a path

	addToClosedList( bestNode );
	curNode = bestNode;
}

assert( isDefined( destNode.parentNode ) );

curNode = destNode;
while (curNode != startNode)
{
	pathNodes[pathNodes.size] = curNode;
	curNode = curNode.parentNode;
}
pathNodes[pathNodes.size] = curNode;

return pathNodes;	
}


addToOpenList( node )
{
node.openListID = level.openList.size;
level.openList[level.openList.size] = node;
node.closedListID = undefined;
}


addToClosedList( node )
{
if (isdefined (node.closedListID))
	return;

node.closedListID = level.closedList.size;
level.closedList[level.closedList.size] = node;

if (!is_in_array (level.openList, node))
	return;

level.openList[node.openListID] = level.openList[level.openList.size - 1];
level.openList[node.openListID].openListID = node.openListID;
level.openList[level.openList.size - 1] = undefined;
node.openListID = undefined;
}


getHValue (node1, node2)
{
return (distance (node1.node.origin, node2.node.origin));
}


getGValue(node1, node2)
{
return ( node1.parentNode.g + node1.linkLengths[node2.graphId] );
}


is_in_array( aeCollection, eFindee )
{
for ( i = 0; i < aeCollection.size; i++ )
{
	if ( aeCollection[ i ] == eFindee )
		return( true );
}

return( false );
}


drawPath( pathNodes )
{
for ( i = 1; i < pathNodes.size; i++ )
{
	startNode = pathNodes[i-1];
	endNode = pathNodes[i];

	if ( startNode.linkDirs[endNode.graphId] == "reverse" )
		level thread drawLink( startNode.node.origin, endNode.node.origin, (1,0,0) );
	else
		level thread drawLink( startNode.node.origin, endNode.node.origin, (0,1,0) );

	vehNode = startNode.linkStartNodes[endNode.graphId];
	level thread drawLink( startNode.node.origin + (0,0,4), vehNode.origin + (0,0,4), (0,0,1) );

	if ( startNode.linkDirs[endNode.graphId] == "reverse" )
	{
		while ( !isDefined( vehNode.graphId ) )
		{
			lastVehNode = vehNode;
			vehNode = vehNode.prev;
			level thread drawLink( lastVehNode.origin + (0,0,4), vehNode.origin + (0,0,4), (0,1,1) );
		}
	}
	else
	{
		while ( !isDefined( vehNode.graphId ) )
		{
			lastVehNode = vehNode;

			if ( isDefined( vehNode.target ) )
				vehNode = GetVehicleNode( vehNode.target, "targetname" );				
			else
				vehNode = vehNode.next;

			level thread drawLink( lastVehNode.origin + (0,0,4), vehNode.origin + (0,0,4), (0,1,1) );
		}			
	}
}
}


drawGraph( pathNodes )
{
/*
level.pathZOffset = 0;
foreach ( node in pathNodes )
{
	println( node.links.size );
	foreach ( linkId, graphNode in node.links )
	{
		if ( node.linkDirs[linkId] == "reverse" )
			level thread drawLink( node.node.origin, graphNode.node.origin, (0,1,0) );
		else
			level thread drawLink( node.node.origin, graphNode.node.origin, (1,0,0) );

		//if ( node.linkDirs[linkId] == "reverse" )
		//	continue;

		//level thread drawLink( pathNodes[graphId].node.origin, pathNodes[node.graphId].node.origin, (randomFloat( 2 ), randomFloat( 2 ), randomFloat( 2 )) );		
	}
}
*/
}


drawLink( start, end, color )
{
level endon ( "endpath" );
for ( ;; )
{
	line(start, end, color, true);
	wait 0.05;
}
}

debugPrintLn2( printString )
{
/#
if ( getDvarInt( "tankDebug" ) )
	printLn( printString );
#/
}

debugPrint( printString )
{
/#
if ( getDvarInt( "tankDebug" ) )
	print( printString );
#/
}


debugPrint3D( origin, printString, color, alpha, scale, duration )
{
/#
if ( getDvarInt( "tankDebug" ) )
{
	print3d( origin, printString, color, alpha, scale, duration );
	println( "3D: " + printString );
}
#/
}


drawTankGraphIds()
{
/#
if ( getDvarInt( "tankDebug" ) )
{
	self notify ( "drawTankGraphIds" );
	self endon ( "drawTankGraphIds" );

	for ( ;; )
	{
		print3d( self.origin + (0,0,128), "FW: " + self.forwardGraphId + " RV: " + self.reverseGraphId, (0,1,0), 1, 3, 1 );
		wait ( 0.05 );
	}
}
#/
}

 

Attack Little Bird Killstreak:

#include maps\mp\_utility;
#include common_scripts\utility;

/******************************************************************* 
//						_littleBird.gsc  
//	
//	Holds all the littlebird specific functions
//	
//	Jordan Hirsh	Jan. 6th 	2009
********************************************************************/


init()
{
precacheString( &"MP_CIVILIAN_AIR_TRAFFIC" );
precacheString( &"MP_AIR_SPACE_TOO_CROWDED" );

return;
//precacheString( &"MP_WAR_AIRSTRIKE_INBOUND_NEAR_YOUR_POSITION" );
//precacheString( &"MP_WAR_AIRSTRIKE_INBOUND" );

//precacheTurret( "minigun_littlebird_mp" );
//precacheModel( "vehicle_little_bird_minigun_left" );
//precacheModel( "vehicle_little_bird_minigun_right" );

//level.attackLB = [];
//level.lbStrike = 0;

//level.killStreakFuncs["littlebird_support"] = ::tryUseLbStrike;
}


tryUseLbStrike( lifeId )
{
if ( isDefined( level.civilianJetFlyBy ) )
{
	self iPrintLnBold( &"MP_CIVILIAN_AIR_TRAFFIC" );
	return false;
}

if ( self isUsingRemote() )
{
	return false;
}

if ( level.lbStrike >= 1 )
{
	self iPrintLnBold( &"MP_AIR_SPACE_TOO_CROWDED" );
	return false;	
}

result = self selectLbStrikeLocation( lifeId );

if ( !isDefined( result ) || !result )
	return false;

level.lbStrike++;
return true;
}


startLBStrike( lifeId, origin, owner, team, yawDir )
{	
while ( isDefined( level.airstrikeInProgress ) )
{
	level waittill ( "begin_airstrike" );
}

level.airstrikeInProgress = true;

num = 17 + randomint(3);
trace = bullettrace(origin, origin + (0,0,-1000000), false, undefined);
targetpos = trace["position"];

//yaw = getBestLbDirection( targetpos );

//yaw = 90;
yaw = yawDir;

if ( level.teambased )
{
	players = level.players;

	for ( i = 0; i < level.players.size; i++ )
	{
		player = level.players[i];
		playerteam = player.pers["team"];
		if ( isdefined( playerteam ) )
		{
			if ( playerteam == team )
				player iprintln( &"MP_WAR_AIRSTRIKE_INBOUND", owner );
		}
	}
}

// buffer between airstrikes
level.airstrikeInProgress = undefined;

owner notify ( "begin_airstrike" );
level notify ( "begin_airstrike" );

if ( !isDefined( owner ) )
	return;

callStrike( lifeId, owner, targetpos, yaw );
}


clearProgress( delay )
{
if( !isDefined( delay ) )
	delay = 0;

wait ( delay );
level.lbStrike = 0;	
}


doLbStrike( lifeId, owner, requiredDeathCount, coord, startPoint, endPoint, direction )
{
self endon( "death" );

if ( !isDefined( owner ) ) 
	return;

lb = spawnAttackLittleBird( owner, startPoint, endPoint, coord );
lb.lifeId = lifeId;

lb thread watchDeath();
lb thread waitTillGone();

lb endon( "death" );

totalDist = Distance2d( startPoint, coord );
midTime = ( totalDist / lb.speed ) / 2 * .1 + 2.5;

assert ( isDefined( lb ) );

lb SetMaxPitchRoll( 45, 45 );			
//moving to end point
lb setVehGoalPos( endPoint, 1 );
wait( midTime - 1 );

//slowing down and firing 
lb Vehicle_SetSpeed( 45, 60 );
wait( 1 );
lb SetMaxPitchRoll( 200, 200 );	
wait ( 5 );	
lb thread startLbFiring();
wait ( 7 );

//stops firing and turns around
lb notify ( "stopFiring" );
lb Vehicle_SetSpeed( 75, 60 );
lb SetMaxPitchRoll( 65, 65 );
wait(2.5);
lb setVehGoalPos( startPoint, 1 );	
wait ( 4 );
lb SetMaxPitchRoll( 180, 180 );
wait ( .75 );

//slows down firing opposite direction
lb Vehicle_SetSpeed( 45, 60 );
lb thread startLbFiring();
wait ( 6 );

//off into the sunset
lb Vehicle_SetSpeed( lb.speed, 60 );
lb notify ( "stopFiring" ); 
lb SetMaxPitchRoll( 75, 180 );

lb waittill ( "goal" );
lb notify( "gone" );
lb delete();

}

waitTillGone()
{
self waittill( "gone" );
clearProgress( 0 );
}


// spawn helicopter at a start node and monitors it
spawnAttackLittleBird( owner, pathStart, pathGoal, coord )
{

forward = vectorToAngles( pathGoal - pathStart );
lb = spawnHelicopter( owner, pathStart, forward, "littlebird_mp" , "vehicle_little_bird_armed" );

if ( !isDefined( lb ) )
	return;
lb.speed = 400;
lb.health = 350; 
lb setCanDamage( true );
lb.owner = owner;
lb.team = owner.team;
lb SetMaxPitchRoll( 45, 45 );	
lb Vehicle_SetSpeed( lb.speed, 60 );

lb.damageCallback = ::Callback_VehicleDamage;

mgTurret1 = spawnTurret( "misc_turret", lb.origin, "minigun_littlebird_mp" );
mgTurret1 linkTo( lb, "tag_minigun_attach_right", (0,0,0), (0,0,0) );
mgTurret1 setModel( "vehicle_little_bird_minigun_right" );
mgTurret1.angles = lb.angles; 
mgTurret1.owner = lb.owner;
mgTurret1.team = mgTurret1.owner.team;

mgTurret1 SetPlayerSpread( .65 );
mgTurret1 makeTurretInoperable();
lb.mgTurret1 = mgTurret1; 
lb.mgTurret1 SetDefaultDropPitch( 0 );

mgTurret2 = spawnTurret( "misc_turret", lb.origin, "minigun_littlebird_mp" );
mgTurret2 linkTo( lb, "tag_minigun_attach_left", (0,0,0), (0,0,0) );
mgTurret2 setModel( "vehicle_little_bird_minigun_right" );
mgTurret2 SetPlayerSpread( .65 );
mgTurret2.angles = lb.angles; 
mgTurret2.owner = lb.owner;
mgTurret2.team = mgTurret2.owner.team;

mgTurret2 makeTurretInoperable();
lb.mgTurret2 = mgTurret2; 
lb.mgTurret2 SetDefaultDropPitch( 0 );

level.littlebird[level.littlebird.size] = lb;

return lb;
}

startLbFiring( )
{
self endon( "gone" );
self endon( "death" );
self endon( "stopFiring" );

i = 0;

for( ;; )
{
	self.mgTurret1 ShootTurret();
	self.mgTurret2 ShootTurret();
	wait ( 0.05 );	
}	
}

getBestLbDirection( hitpos )
{
//if ( !self.precisionAirstrike )
//	return randomFloat( 360 );

checkPitch = -25;

numChecks = 15;

startpos = hitpos + (0,0,64);

bestangle = randomfloat( 360 );
bestanglefrac = 0;

fullTraceResults = [];

for ( i = 0; i < numChecks; i++ )
{
	yaw = ((i * 1.0 + randomfloat(1)) / numChecks) * 360.0;
	angle = (checkPitch, yaw + 180, 0);
	dir = anglesToForward( angle );

	endpos = startpos + dir * 1500;

	trace = bullettrace( startpos, endpos, false, undefined );

	if ( trace["fraction"] > bestanglefrac )
	{
		bestanglefrac = trace["fraction"];
		bestangle = yaw;

		if ( trace["fraction"] >= 1 )
			fullTraceResults[ fullTraceResults.size ] = yaw;
	}

	if ( i % 3 == 0 )
		wait .05;
}

if ( fullTraceResults.size > 0 )
	return fullTraceResults[ randomint( fullTraceResults.size ) ];

return bestangle;
}


callStrike( lifeId, owner, coord, yaw )
{	
// Get starting and ending point for the plane
direction = ( 0, yaw, 0 );
planeHalfDistance = 24000;
planeFlyHeight = 850;
planeFlySpeed = 7000;

if ( isdefined( level.airstrikeHeightScale ) )
	planeFlyHeight *= level.airstrikeHeightScale;

startPoint = coord + vector_multiply( anglestoforward( direction ), -1 * planeHalfDistance );
startPoint += ( 0, 0, planeFlyHeight );

endPoint = coord + vector_multiply( anglestoforward( direction ), planeHalfDistance );
endPoint += ( 0, 0, planeFlyHeight );

owner endon("disconnect");

requiredDeathCount = owner.lifeId;

level thread doLbStrike( lifeId, owner, requiredDeathCount, coord, startPoint, endPoint, direction );

}


waitForLbStrikeCancel()
{
self waittill( "cancel_location" );
self setblurforplayer( 0, 0.3 );
}


selectLbStrikeLocation( lifeId )
{
self setClientDvar( "ui_selecting_location", "1");
self beginLocationSelection( "map_artillery_selector", true, 500 );
self.selectingLocation = true;

self setblurforplayer( 10.3, 0.3 );
self thread waitForLbStrikeCancel();

self thread endSelectionOn( "cancel_location" );
self thread endSelectionOn( "death" );
self thread endSelectionOn( "disconnect" );
self thread endSelectionOn( "used" );
self thread endSelectionOnGameEnd();

self endon( "stop_location_selection" );

// wait for the selection. randomize the yaw if we ever stop doing a precision selection
self waittill( "confirm_location", location, locationYaw);

self setblurforplayer( 0, 0.3 );

self thread finishLbStrikeUsage( lifeId, location, ::useLbStrike, locationYaw );
return true;
}


finishLbStrikeUsage( lifeId, location, usedCallback, locationYaw )
{
self notify( "used" );
wait ( 0.05 );
self thread stopLbStrikeLocationSelection( false );
self thread [[usedCallback]]( lifeId, location, locationYaw );
return true;
}


endSelectionOn( waitfor )
{
self endon( "stop_location_selection" );
self waittill( waitfor );
self thread stopLbStrikeLocationSelection( (waitfor == "disconnect") );
}


endSelectionOnGameEnd()
{
self endon( "stop_location_selection" );
level waittill( "game_ended" );
self thread stopLbStrikeLocationSelection( false );
}


stopLbStrikeLocationSelection( disconnected )
{
if ( !disconnected )
{
	self setblurforplayer( 0, 0.3 );
	self endLocationSelection();
	self.selectingLocation = undefined;
}
self notify( "stop_location_selection" );
}

useLbStrike( lifeId, pos, yawDir )
{
// find underside of top of skybox
trace = bullettrace( level.mapCenter + (0,0,1000000), level.mapCenter, false, undefined );
pos = (pos[0], pos[1], trace["position"][2] - 514);

thread startLBStrike( lifeId, pos, self, self.pers["team"], yawDir );
}

Callback_VehicleDamage( inflictor, attacker, damage, dFlags, meansOfDeath, weapon, point, dir, hitLoc, timeOffset, modelIndex, partName )
{
if ( ( attacker == self || ( isDefined( attacker.pers ) && attacker.pers["team"] == self.team ) ) && ( attacker != self.owner || meansOfDeath == "MOD_MELEE" ) )
	return;


self Vehicle_FinishDamage( inflictor, attacker, damage, dFlags, meansOfDeath, weapon, point, dir, hitLoc, timeOffset, modelIndex, partName );
}

watchDeath()
{
self endon( "gone" );

self waittill( "death" );
self thread heliDestroyed();

level.littleBirds--;
clearProgress( 0.05 );

return;
}

heliDestroyed()
{
self endon( "gone" );

if (! isDefined(self) )
	return;

//self trimActiveBirdList();
self Vehicle_SetSpeed( 25, 5 );
self thread lbSpin( RandomIntRange(180, 220) );

wait( RandomFloatRange( .5, 1.5 ) );

lbExplode();
}

lbExplode()
{
forward = ( self.origin + ( 0, 0, 1 ) ) - self.origin;
playfx ( level.chopper_fx["explode"]["air_death"], self.origin, forward );

deathAngles = self getTagAngles( "tag_deathfx" );		
playFx( level.chopper_fx["explode"]["air_death"]["littlebird"], self getTagOrigin( "tag_deathfx" ), anglesToForward( deathAngles ), anglesToUp( deathAngles ) );

self playSound( "cobra_helicopter_crash" );
self notify ( "explode" );

if ( isDefined( self.mgTurret1 ) )
	self.mgTurret1 delete();

if ( isDefined( self.mgTurret2 ) )
	self.mgTurret2 delete();

self clearProgress( 0 );

self delete();
}

lbSpin( speed )
{
self endon( "explode" );

// tail explosion that caused the spinning
playfxontag( level.chopper_fx["explode"]["medium"], self, "tail_rotor_jnt" );
	self thread trail_fx( level.chopper_fx["smoke"]["trail"], "tail_rotor_jnt", "stop tail smoke" );

self setyawspeed( speed, speed, speed );
while ( isdefined( self ) )
{
	self settargetyaw( self.angles[1]+(speed*0.9) );
	wait ( 1 );
}
}

trail_fx( trail_fx, trail_tag, stop_notify )
{
// only one instance allowed
self notify( stop_notify );
self endon( stop_notify );
self endon( "death" );

for ( ;; )
{
	playfxontag( trail_fx, self, trail_tag );
	wait( 0.05 );
}
}

Featured Replies

Опубликовано:
Neat stuff. Not sure all the models would be there to actually put this stuff to use though.

The train was made for skidrow but the model(s) dont exist.

I believe the rappel needed an SP dependency (maybe/probably doable with "IW4C")

Rendy made the snowmobiles drivable on Contingecy, I'd assume it'd have a dependency too.

Dunno about the rest.

Опубликовано:
  • Автор
JoeyB":2vp6exte]
Neat stuff. Not sure all the models would be there to actually put this stuff to use though.

The train was made for skidrow but the model(s) dont exist.

I believe the rappel needed an SP dependency (maybe/probably doable with "IW4C")

Rendy made the snowmobiles drivable on Contingecy, I'd assume it'd have a dependency too.

Dunno about the rest.

Invasion has tank model so it might be possible to make it work on this map. The rappel from highrise can be used to sp maps such as af_chase, I think Rendflex already used it in his rush mod.

Опубликовано:

I think i have seen this already, the train and snowmobile thing, i think elevators to, not sure about tank and little bird killstreaks, but those things are not really cool, well, for me.

Опубликовано:
I think i have seen this already, the train and snowmobile thing, i think elevators to, not sure about tank and little bird killstreaks, but those things are not really cool, well, for me.

You know nothing, Jon Snow.

Опубликовано:

I don't think they were lazy, they might have scrapped it due to balancing cuz the only place up in highrise are the rooftops and MAYBE

the elavators are those window washing platforms...or those elavators by the cranes which only goes down,

the rappel would be useless as the is nowhere to rappel to, the KS are just one of the few things the devs might have thought to be OP like the Juggernaut suit.

(the littlebird would have most likekly been like the overwatch and the tank just doesn't fit the maps probably)

Опубликовано:
JoeyB":2zqy7vhh]
Neat stuff. Not sure all the models would be there to actually put this stuff to use though.

The train was made for skidrow but the model(s) dont exist.

I believe the rappel needed an SP dependency (maybe/probably doable with "IW4C")

Rendy made the snowmobiles drivable on Contingecy, I'd assume it'd have a dependency too.

Dunno about the rest.

Invasion has tank model so it might be possible to make it work on this map. The rappel from highrise can be used to sp maps such as af_chase, I think Rendflex already used it in his rush mod.

old. the tank is done.

http://nedermeme.nl/wp-content/uploads/ ... roeder.gif

Опубликовано:

The tank was - AFAIK - an AI-controlled killstreak much alike the helicopter-killstreaks, which were supposed to move around the map and kill the opposing team. As stated earlier in this thread, the main cause for them not releasing it was probably that it didn't fit most maps.

 

Regarding the rappelling in my rush mod, it is a simple code I wrote for using the SP rope models.

Опубликовано:
The tank was - AFAIK - an AI-controlled killstreak much alike the helicopter-killstreaks, which were supposed to move around the map and kill the opposing team. As stated earlier in this thread, the main cause for them not releasing it was probably that it didn't fit most maps.

 

Regarding the rappelling in my rush mod, it is a simple code I wrote for using the SP rope models.

would be cool if it was instead like a mortar strike.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Гость
Ответить в тему...

Сейчас на странице 0

  • Нет пользователей, просматривающих эту страницу

Важная информация

Используя этот сайт, вы соглашаетесь Условия использования.

Account

Navigation

Поиск

Поиск

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.