#include common_scripts\utility; #include maps\_utility; #include maps\_vehicle; #include maps\_vehicle_aianim; #using_animtree( "vehicles" ); MIN_DELAY = 0.0; MAX_DELAY = 2.0; main( model, type ) { build_template( "nx_miniuav", model, type ); build_localinit( ::init_local ); build_life( 100 ); // Use this to have a death model swap //build_deathmodel( "nx_vehicle_miniuav", "nx_vehicle_miniuav" ); // tagBR Set up the death fx/sounds miniuav_death_fx[ "nx_vehicle_miniuav" ] = "nx/explosions/nx_miniuav_hit"; build_deathfx( miniuav_death_fx[ model ], undefined, "nx_miniuav_explode", undefined, undefined, undefined, 1.0 ); // This must be called for all SAV's after all build_deathfx calls build_SAV_death_delay(MIN_DELAY, MAX_DELAY); level._SAV_circling_func = maps\_attack_heli::SAV_switch_to_circling; //build_radiusdamage( ( 0, 0, 32 ), 500, 80, 20, false ); build_radiusdamage( ( 0, 0, 32 ), 120, 20, 5, false ); build_drive( %Nx_Miniuav_Idle, undefined, 0 ); // This is required for setting up the "dust kickup" fx build_treadfx( "nx_miniuav" ); build_team( "axis" ); build_mainturret(); //turret = "minigun_littlebird_spinnup"; //build_turret( turret, "TAG_BARREL", "vehicle_little_bird_minigun_left" ); build_vehicle_parent_attach_spawn( ::vehicle_parent_attach_unload ); PreCacheItem( "nx_miniuav_rifle" ); precacheitem( "smoke_grenade_miniuav" ); maps\_attack_heli::preLoad(); miniuav_init_fx(); miniuav_find_linear_dmg_func(); } //******************************************************************* // * // * //******************************************************************* miniuav_init_fx() { level._effect[ "miniuav_flashlight" ] = LoadFX( "misc/flashlight" ); level._effect[ "miniuav_muzzleflash" ] = LoadFX( "muzzleflashes/m16_flash_wv" ); } //******************************************************************* // * // * //******************************************************************* vehicle_parent_attach_unload() { wait( 1.0 ); self maps\_attack_heli::SAV_setup( "pathing" ); } //******************************************************************* // * // * //******************************************************************* init_local() { self.script_badplace = false; // All helicopters dont need to create bad places level._SAV_setup_script = maps\_attack_heli::SAV_setup; self._SAV_searching_script = ::searching_miniuav_think; self._SAV_damage_script = ::miniuav_damage_script; self._SAV_turret_type = "nx_miniuav_rifle"; // Set up accuracy. self set_baseaccuracy( .50 ); } //******************************************************************* // * // * //******************************************************************* miniuav_find_linear_dmg_func() { // Using liner regression to find a function that will provide output values // for use in determining hit anim start time based on damage // Ref: http://www.zweigmedia.com/RealWorld/calctopic1/regression.html // x is dmg, y is anim start times max_x = 200.0; min_x = 0.0; max_y = 0.0; min_y = 0.4; sum_x = ( max_x + min_x ); sum_y = ( max_y + min_y ); sum_xy = ( max_x * max_y ) + ( min_x * min_y ); sum_x2 = ( max_x * max_x ) + ( min_x * min_x ); m = ( ( 2 * sum_xy ) - ( sum_x * sum_y ) ) / ( ( 2 * sum_x2 ) - ( sum_x * sum_x ) ); b = ( sum_y - ( m * sum_x ) ) / 2; // f(x) = mx + b; // Save them off level._miniuav_dmg_func_slope = m; level._miniuav_dmg_func_intersect = b; } //******************************************************************* // * // * //******************************************************************* miniuav_get_hit_anim_start_time( damage ) { // f(x) = mx + b; return ( ( level._miniuav_dmg_func_slope * damage ) + level._miniuav_dmg_func_intersect ); } //******************************************************************* // * // * //******************************************************************* miniuav_damage_script() { self endon( "death" ); while ( 1 ) { // Wait till uav is damaged self waittill( "damage", amount, attacker, direction_vec, point ); self notify( "new_hit_react" ); // Set the anim self SetFlaggedAnim( "hitreact", %nx_miniuav_hitreact, 1, 0.1, 1 ); // Find the start time based on our dmg_func start_time = miniuav_get_hit_anim_start_time( amount ); // Add in a small offset (so the anim doesn't always look the same) start_time += RandomFloatRange( -0.1, 0.1 ); // Clamping if ( start_time < 0 ) { start_time = 0; } // For now, just getting a random time to end end_time = RandomFloatRange( 0.66, 0.98 ); // <-- 0.98 because it will never get to 1.0 apparently... self SetAnimTime( %nx_miniuav_hitreact, start_time ); // Wait until the anim is finished self thread miniuav_hitreact_anim_clear( end_time ); self waittill( "anim_cleared" ); waittillframeend; } } //******************************************************************* // * // * //******************************************************************* miniuav_hitreact_anim_clear( limit ) { self endon( "death" ); self endon( "new_hit_react" ); //self waittillmatch( "hitreact", "end" ); while ( 1 ) { // Kill the anim as soon as we've reached our limit curr_time = self GetAnimTime( %nx_miniuav_hitreact ); if ( curr_time >= limit ) { self ClearAnim( %nx_miniuav_hitreact, 0.1 ); self notify( "anim_cleared" ); break; } wait( 0.05 ); } } //******************************************************************* // * // * //******************************************************************* searching_miniuav_think() { self endon( "death" ); // If stealthgroups are in use, watch for spotted events if ( isdefined( self.script_stealthgroup ) ) { self thread miniuav_handle_stealthgroup(); } // Look for enemies and listen for gunshot events self.circling = false; self thread miniuav_listen_for_events(); self thread miniuav_looking_for_player( level._player ); //self SetJitterParams( ( 0, 0, 0 ), 0, 0 ); self SetHoverParams( 0, 0, 0.0 ); self SetYawSpeed( 120, 60, 60, 0 ); while( 1 ) { switch( self.state ) { case "move": self miniuav_move_state(); break; case "search": self miniuav_search_state(); break; case "engage": self miniuav_engage_state(); break; case "notify": self miniuav_notify_state(); break; default: Assert( "miniuav does not have an appropriate state defined." ); break; } wait 0.1; } } //******************************************************************* // * // * //******************************************************************* miniuav_handle_stealthgroup() { self endon( "death" ); self endon( "player_spotted" ); thread [[ level._global_callbacks[ "_patrol_endon_spotted_flag" ] ]](); self waittill( "end_patrol" ); thread miniuav_player_spotted(); } //******************************************************************* // * // * //******************************************************************* miniuav_move_state() { self endon( "death" ); self endon( "investigate" ); self endon( "player_spotted" ); goal = undefined; goal_yaw = undefined; // Possibly follow a path, ignoring other nodes if ( isdefined( self.current_node ) && isdefined( self.current_node.target ) ) { goal = GetEnt( self.current_node.target, "targetname" ); assert( isdefined( goal ) ); goal_yaw = goal.angles[1]; goal.used = 1; goal.use_position = []; } else { valid_nodes = []; // If there are no valid nodes in sight while ( !valid_nodes.size ) { wait 0.3; // Eventually do something else? valid_nodes = maps\_attack_heli::SAV_get_valid_nodes( self.sight_nodes ); } /# if ( GetDvar( "scr_debug_miniuav_search" ) == "1" ) { foreach ( node in valid_nodes ) { Line( self.origin, node.origin, ( 1, 0, 0 ), 1, 0, 120 ); } } #/ if ( isdefined( self.SAV_search_pos ) ) { // If we are told to search a specific position, find the closest sight node, and look at it goal = valid_nodes[0]; best_dist = distance( self.SAV_search_pos, valid_nodes[ 0 ].origin ); node_index = 1; while ( node_index < valid_nodes.size ) { new_dist = distance( self.SAV_search_pos, valid_nodes[ node_index ].origin ); if ( new_dist < best_dist ) { goal = valid_nodes[ node_index ]; best_dist = new_dist; } node_index+=1; } assert( isdefined( goal ) ); goal_yaw = VectorToYaw( self.SAV_search_pos - goal.origin ); self.SAV_using_search_pos = true; self.SAV_search_pos = undefined; } else { // Set the goal to a random node within sight goal = valid_nodes[ RandomInt( valid_nodes.size ) ]; self.SAV_using_search_pos = false; goal_yaw = goal.angles[1]; } } /# if ( !isdefined( goal.angles ) ) { AssertMsg( "Miniuav search node missing 'angles' KVP" ); } #/ maps\_attack_heli::SAV_use_node( goal ); goal_pos = maps\_attack_heli::SAV_get_offset_node_pos( goal ); self SetTargetYaw( goal_yaw ); self SetVehGoalPos( goal_pos, 1 ); // Set the heli's nodes to the nodes within sight of the goal if ( isdefined( goal.sight_nodes ) ) { self.sight_nodes = goal.sight_nodes; } self waittill( "goal" ); // Set new uav params based on the node, if it has any if ( isdefined( goal.script_yawspeed ) ) { self SetYawSpeed( goal.script_yawspeed, 60 ); } else { self SetYawSpeed( 120, 60, 60, 0 ); } self ClearTargetYaw(); wait 1; // Transition to search state if we are at the end of a path if ( !isdefined( self.current_node.target ) || ( isdefined( self.current_node.script_noteworthy ) && self.current_node.script_noteworthy == "search_node" ) ) { self.state = "search"; } } //******************************************************************* // * // * //******************************************************************* miniuav_turn( offset_angle ) { yaw = self.angles[ 1 ] + offset_angle; self SetTargetYaw( yaw ); EPSILON = 1.0; while( AngleClamp( self.angles[ 1 ] - yaw ) > EPSILON ) { wait 0.1; } } //******************************************************************* // * // * //******************************************************************* miniuav_search_state() { self endon( "death" ); self endon( "investigate" ); // Stop searching when player spotted self endon( "player_spotted" ); EPSILON = 5.0; // Relatively high epsilon to account for animation affecting the turn // Set speeds // Do searching --- turn_angle = 135; if ( isdefined( self.current_node.script_goalyaw ) ) { turn_angle = self.current_node.script_goalyaw; } self miniuav_turn( turn_angle ); self miniuav_turn( turn_angle*-1 ); self miniuav_turn( turn_angle*-1 ); // Stop searching, and transition back to move state self notify( "stop_searching" ); self.state = "move"; } //******************************************************************* // * // * //******************************************************************* miniuav_looking_for_player( player ) { self endon( "death" ); while( 1 ) { if ( !self.circling ) { tag_flash_loc = self GetTagOrigin( "tag_flash" ); // If player is within heli's FOV if( within_fov( tag_flash_loc, self.angles, player GetEye(), level._cosine[ "45" ] ) ) { // And, the heli can see the player if( SightTracePassed( tag_flash_loc, player GetEye(), false, self ) ) { // And, distance check if( distance( self.origin, player.origin ) < self.sight_distance ) { miniuav_player_spotted(); } } } } wait 0.1; } } //******************************************************************* // * // * //******************************************************************* miniuav_player_spotted() { self.eTarget = level._player; self notify( "player_spotted" ); //IPrintLnBold( "PLAYER SPOTTED!!!" ); self notify( "enemy" ); if( self.behavior_type == "searching" ) { self.state = "engage"; } else // "notifying" { self.state = "notify"; } } //******************************************************************* // * // * //******************************************************************* miniuav_listen_for_events() { self endon( "death" ); self endon( "player_spotted" ); self.investigate_goal = undefined; self addAIEventListener( "bulletwhizby" ); self addAIEventListener( "gunshot" ); while ( 1 ) { self waittill( "ai_event", event, originator, position ); if ( ( event == "bulletwhizby" ) || ( event == "gunshot" ) ) { if ( !self.circling && !self.SAV_using_search_pos ) { self.SAV_search_pos = position; self.state = "move"; //IPrintLnBold( "BULLET DETECTED" ); self notify( "investigate" ); } } } } //******************************************************************* // * // * //******************************************************************* miniuav_engage_state() { self endon( "death" ); self.eTarget = level._player; self thread miniuav_check_for_stop_engage( level._player ); // Go to circling/attack behavior self SetYawSpeed( 120, 60, 60, 0 ); // If our yaw speed has changed, reset it back to combat levels self maps\_attack_heli::start_circling_heli_logic( self.circle_nodes, maps\_attack_heli::SAV_shoot_think, maps\_attack_heli::SAV_circle_node_choice, maps\_attack_heli::SAV_get_offset_node_pos ); // The circling won't proceed until you are near your goal...well, we are already at our goal self notify( "near_goal" ); self waittill( "stop_engaging" ); // Stop circling behavior, transition to move state self notify( "stop_circling" ); self notify( "stop_shooting" ); self.circling = false; self ClearLookAtEnt(); self.state = "move"; // Also need to re-process the sight nodes now self maps\_attack_heli::process_heli_sight_nodes( self.search_nodes ); } //******************************************************************* // * // * //******************************************************************* miniuav_check_for_stop_engage( player ) { self endon( "death" ); // Just doing a distance check, if player leaves the "bubble", stop engaging // tagBR< note >: May want other conditions here? while( 1 ) { if ( isdefined( self.can_lose_player ) && self.can_lose_player ) { if( distance( self.origin, player.origin ) > ( self.sight_distance * 2.0 ) ) { self notify( "stop_engaging" ); IPrintLnBold( "TARGET LOST" ); return; } } wait 0.1; } } //******************************************************************* // * // * //******************************************************************* miniuav_notify_state() { self endon( "death" ); // miniuav will return to this location once the notify logic is complete return_origin = Spawn( "script_origin", self.origin ); self notify( "notify_begin" ); // --- Wait for notify logic --- self waittill( "notify_complete" ); // Once we've notified once, we revert to searching logic in the engage state self.behavior_type = "searching"; self.state = "engage"; // Return to the position self SetLookAtEnt( return_origin ); self SetVehGoalPos( return_origin.origin, 1 ); self waittill( "goal" ); // Don't need this anymore return_origin delete(); } //******************************************************************* // * // * //******************************************************************* /*QUAKED script_vehicle_nx_miniuav (1 0 0) (-16 -16 -24) (16 16 32) USABLE SPAWNER maps\_nx_miniuav::main( "nx_vehicle_miniuav", "nx_miniuav" ); include,nx_vehicle_miniuav include,_attack_heli defaultmdl="nx_vehicle_miniuav" default:"vehicletype" "nx_miniuav" default:"script_team" "axis" */