Cool things that IW didn't put in the final version of multiplayer
Featured Replies
Сейчас на странице 0
- Нет пользователей, просматривающих эту страницу
A better way to browse. Learn more.
A full-screen app on your home screen with push notifications, badges and more.
Используя этот сайт, вы соглашаетесь Условия использования.
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 ); } }