#using scripts\codescripts\struct; #using scripts\shared\gameskill_shared; #using scripts\shared\hostmigration_shared; #using scripts\shared\math_shared; #using scripts\shared\array_shared; #using scripts\shared\statemachine_shared; #using scripts\shared\system_shared; #using scripts\shared\util_shared; #using scripts\shared\vehicle_shared; #using scripts\shared\vehicle_death_shared; #using scripts\shared\vehicle_ai_shared; #using scripts\shared\vehicles\_attack_drone; #using scripts\shared\flag_shared; #using scripts\shared\turret_shared; #using scripts\shared\ai_shared; #using scripts\shared\ai\systems\ai_interface; //#using scripts\cp\_util; // for getOtherTeam function // Speeds // scanner // 0 as horizontal, 90 as straight down //90 // weapon // tunable length of persistent sight on target before fire rocket // drone // will wait this amount of time after player run into a non-reachable space to deploy attack drones // other // pain // 0 to disable // feature control // if true, side turret will not choose same target as main turret (so the player being targeted won't get cross fired by all three turrets) #namespace hunter; function autoexec __init__sytem__() { system::register("hunter",&__init__,undefined,undefined); } #using_animtree( "generic" ); // ---------------------------------------------- // initialization // ---------------------------------------------- function __init__() { RegisterInterfaceAttributes( "hunter" ); vehicle::add_main_callback( "hunter", &hunter_initialize ); } function RegisterInterfaceAttributes( archetype ) { vehicle_ai::RegisterSharedInterfaceAttributes( archetype ); // Strafe distance ai::RegisterNumericInterface( archetype, "strafe_speed", 0, 0, 100 ); ai::RegisterNumericInterface( archetype, "strafe_distance", 0, 0, 10000 ); } function hunter_initTagArrays() { self.weakSpotTags = []; if ( false ) { if ( !isdefined( self.weakSpotTags ) ) self.weakSpotTags = []; else if ( !IsArray( self.weakSpotTags ) ) self.weakSpotTags = array( self.weakSpotTags ); self.weakSpotTags[self.weakSpotTags.size]="tag_target_l";; if ( !isdefined( self.weakSpotTags ) ) self.weakSpotTags = []; else if ( !IsArray( self.weakSpotTags ) ) self.weakSpotTags = array( self.weakSpotTags ); self.weakSpotTags[self.weakSpotTags.size]="tag_target_r";; } self.explosiveWeakSpotTags = []; if ( false ) { if ( !isdefined( self.explosiveWeakSpotTags ) ) self.explosiveWeakSpotTags = []; else if ( !IsArray( self.explosiveWeakSpotTags ) ) self.explosiveWeakSpotTags = array( self.explosiveWeakSpotTags ); self.explosiveWeakSpotTags[self.explosiveWeakSpotTags.size]="tag_fan_base_l";; if ( !isdefined( self.explosiveWeakSpotTags ) ) self.explosiveWeakSpotTags = []; else if ( !IsArray( self.explosiveWeakSpotTags ) ) self.explosiveWeakSpotTags = array( self.explosiveWeakSpotTags ); self.explosiveWeakSpotTags[self.explosiveWeakSpotTags.size]="tag_fan_base_r";; } self.missileTags = []; if ( !isdefined( self.missileTags ) ) self.missileTags = []; else if ( !IsArray( self.missileTags ) ) self.missileTags = array( self.missileTags ); self.missileTags[self.missileTags.size]="tag_rocket1";; if ( !isdefined( self.missileTags ) ) self.missileTags = []; else if ( !IsArray( self.missileTags ) ) self.missileTags = array( self.missileTags ); self.missileTags[self.missileTags.size]="tag_rocket2";; self.droneAttachTags = []; if ( false ) { if ( !isdefined( self.droneAttachTags ) ) self.droneAttachTags = []; else if ( !IsArray( self.droneAttachTags ) ) self.droneAttachTags = array( self.droneAttachTags ); self.droneAttachTags[self.droneAttachTags.size]="tag_drone_attach_l";; if ( !isdefined( self.droneAttachTags ) ) self.droneAttachTags = []; else if ( !IsArray( self.droneAttachTags ) ) self.droneAttachTags = array( self.droneAttachTags ); self.droneAttachTags[self.droneAttachTags.size]="tag_drone_attach_r";; } } function hunter_SpawnDrones() { self.dronesOwned = []; if ( false ) { foreach( droneTag in self.droneAttachTags ) { origin = self GetTagOrigin( droneTag ); angles = self GetTagAngles( droneTag ); drone = SpawnVehicle( "spawner_bo3_attack_drone_enemy", origin, angles ); drone.owner = self; drone.attachTag = droneTag; drone.team = self.team; if ( !isdefined( self.dronesOwned ) ) self.dronesOwned = []; else if ( !IsArray( self.dronesOwned ) ) self.dronesOwned = array( self.dronesOwned ); self.dronesOwned[self.dronesOwned.size]=drone;; } } } function hunter_initialize() { self endon( "death" ); self UseAnimTree( #animtree ); Target_Set( self, ( 0, 0, 90 ) ); ai::CreateInterfaceForEntity( self ); self.health = self.healthdefault; self vehicle::friendly_fire_shield(); //self EnableAimAssist(); self SetNearGoalNotifyDist( 50 ); self SetHoverParams( 15, 100, 40 ); self.flyheight = GetDvarFloat( "g_quadrotorFlyHeight" ); self.fovcosine = 0; // +/-90 degrees = 180 self.fovcosinebusy = 0.574; //+/- 55 degrees = 110 fov self.vehAirCraftCollisionEnabled = true; self.original_vehicle_type = self.vehicletype; self.settings = struct::get_script_bundle( "vehiclecustomsettings", self.scriptbundlesettings ); self.goalRadius = 999999; self.goalHeight = 999999; self SetGoal( self.origin, false, self.goalRadius, self.goalHeight ); self hunter_initTagArrays(); self.overrideVehicleDamage = &HunterCallback_VehicleDamage; self thread vehicle_ai::nudge_collision(); //disable some cybercom abilities if( IsDefined( level.vehicle_initializer_cb ) ) { [[level.vehicle_initializer_cb]]( self ); } self.ignoreFireFly = true; self.ignoreDecoy = true; self vehicle_ai::InitThreatBias(); // self thread hunter_frontScanning(); // self hunter_SpawnDrones(); // // wait 0.5; // // foreach ( drone in self.dronesOwned ) // { // if ( isalive( drone ) ) // { // drone vehicle_ai::set_state( "attached" ); // } // } self turret::_init_turret( 1 ); self turret::_init_turret( 2 ); self turret::set_best_target_func( &side_turret_get_best_target, 1 ); self turret::set_best_target_func( &side_turret_get_best_target, 2 ); self turret::set_burst_parameters( 1, 2, 1, 2, 1 ); self turret::set_burst_parameters( 1, 2, 1, 2, 2 ); self turret::set_target_flags( 1 | 2, 1 ); self turret::set_target_flags( 1 | 2, 2 ); self side_turrets_forward(); self PathVariableOffset( (10, 10, -30), 1 ); defaultRole(); } function defaultRole() { self vehicle_ai::init_state_machine_for_role(); self vehicle_ai::get_state_callbacks( "combat" ).enter_func = &state_combat_enter; self vehicle_ai::get_state_callbacks( "combat" ).update_func = &state_combat_update; self vehicle_ai::get_state_callbacks( "combat" ).exit_func = &state_combat_exit; self vehicle_ai::get_state_callbacks( "driving" ).enter_func = &hunter_scripted; self vehicle_ai::get_state_callbacks( "scripted" ).enter_func = &hunter_scripted; self vehicle_ai::get_state_callbacks( "death" ).enter_func = &state_death_enter; self vehicle_ai::get_state_callbacks( "death" ).update_func = &state_death_update; self vehicle_ai::get_state_callbacks( "emped" ).update_func = &hunter_emped; self vehicle_ai::add_state( "unaware", undefined, &state_unaware_update, &state_unaware_exit ); vehicle_ai::add_interrupt_connection( "unaware", "scripted", "enter_scripted" ); vehicle_ai::add_interrupt_connection( "unaware", "emped", "emped" ); vehicle_ai::add_interrupt_connection( "unaware", "off", "shut_off" ); vehicle_ai::add_interrupt_connection( "unaware", "driving", "enter_vehicle" ); vehicle_ai::add_interrupt_connection( "unaware", "pain", "pain" ); self vehicle_ai::add_state( "strafe", &state_strafe_enter, &state_strafe_update, &state_strafe_exit ); vehicle_ai::add_interrupt_connection( "strafe", "scripted", "enter_scripted" ); vehicle_ai::add_interrupt_connection( "strafe", "emped", "emped" ); vehicle_ai::add_interrupt_connection( "strafe", "off", "shut_off" ); vehicle_ai::add_interrupt_connection( "strafe", "driving", "enter_vehicle" ); vehicle_ai::add_interrupt_connection( "strafe", "pain", "pain" ); vehicle_ai::add_utility_connection( "strafe", "combat" ); vehicle_ai::add_utility_connection( "emped", "strafe" ); vehicle_ai::add_utility_connection( "pain", "strafe" ); vehicle_ai::StartInitialState(); } // ---------------------------------------------- // State: death // ---------------------------------------------- function shut_off_fx() { self endon( "death" ); self notify( "death_shut_off" ); if ( isdefined( self.frontScanner ) ) { self.frontScanner.sndScanningEnt delete(); self.frontScanner delete(); } } function kill_drones() { self endon( "death" ); foreach ( drone in self.dronesOwned ) { if ( isalive( drone ) && Distance2DSquared( self.origin, drone.origin ) < ( (80) * (80) ) ) { damageOrigin = self.origin + (0,0,1); drone finishVehicleRadiusDamage(self.death_info.attacker, self.death_info.attacker, 32000, 32000, 10, 0, "MOD_EXPLOSIVE", level.weaponNone, damageOrigin, 400, -1, (0,0,1), 0); } } } function state_death_enter( params ) { self endon( "death" ); if ( isdefined( self.fakeTargetEnt ) ) { self.fakeTargetEnt Delete(); } vehicle_ai::defaultstate_death_enter(); self.inpain = true; self thread shut_off_fx(); //self thread kill_drones(); } function state_death_update( params ) { self endon( "death" ); death_type = vehicle_ai::get_death_type( params ); if ( !isdefined( death_type ) ) { params.death_type = "gibbed"; death_type = params.death_type; } self vehicle_ai::ClearAllLookingAndTargeting(); self vehicle_ai::ClearAllMovement(); self CancelAIMove(); self SetSpeedImmediate( 0 ); self SetVehVelocity( ( 0, 0, 0 ) ); self SetPhysAcceleration( ( 0, 0, 0 ) ); self SetAngularVelocity( ( 0, 0, 0 ) ); self vehicle_ai::defaultstate_death_update( params ); } // ---------------------------------------------- // ---------------------------------------------- // State: unaware // ---------------------------------------------- function state_unaware_enter( params ) { ratio = 0.5; accel = self GetDefaultAcceleration(); self SetSpeed( ratio * self.settings.defaultmovespeed, ratio * accel, ratio * accel ); } function state_unaware_update( params ) { self endon( "change_state" ); self endon( "death" ); if ( isdefined( self.enemy ) ) { self vehicle_ai::set_state( "combat" ); } self ClearLookAtEnt(); self disable_turrets(); self thread Movement_Thread_Wander(); while ( 1 ) { self waittill( "enemy" ); self vehicle_ai::set_state( "combat" ); } } function state_unaware_exit( params ) { self notify( "end_movement_thread" ); } function Movement_Thread_Wander() { self endon( "death" ); self notify( "end_movement_thread" ); self endon( "end_movement_thread" ); constMinSearchRadius = 120; constMaxSearchRadius = 800; minSearchRadius = math::clamp( constMinSearchRadius, 0, self.goalRadius ); maxSearchRadius = math::clamp( constMaxSearchRadius, constMinSearchRadius, self.goalRadius ); halfHeight = 400; innerSpacing = 80; outerSpacing = 50; maxGoalTimeout = 15; timeAtSamePosition = 2.5 + randomfloat( 1 ); while ( true ) { queryResult = PositionQuery_Source_Navigation( self.origin, minSearchRadius, maxSearchRadius, halfHeight, innerSpacing, self, outerSpacing ); PositionQuery_Filter_DistanceToGoal( queryResult, self ); vehicle_ai::PositionQuery_Filter_OutOfGoalAnchor( queryResult ); vehicle_ai::PositionQuery_Filter_Random( queryResult, 0, 10 ); vehicle_ai::PositionQuery_PostProcess_SortScore( queryResult ); stayAtGoal = timeAtSamePosition > 0.2; foundpath = false; for ( i = 0; i < queryResult.data.size && !foundpath; i++ ) { goalPos = queryResult.data[i].origin; foundpath = self SetVehGoalPos( goalPos, stayAtGoal, true ); } if ( foundPath ) { msg = self util::waittill_any_timeout( maxGoalTimeout, "near_goal", "force_goal", "reached_end_node", "goal" ); if ( stayAtGoal ) { wait randomFloatRange( 0.5 * timeAtSamePosition, timeAtSamePosition ); } } else { wait 1; } } } // ---------------------------------------------- // ---------------------------------------------- // State: combat // ---------------------------------------------- function enable_turrets() { //self turret::enable( 0, false ); self turret::enable( 1, false ); self turret::enable( 2, false ); } function disable_turrets() { //self turret::disable( 0 ); self turret::disable( 1 ); self turret::disable( 2 ); self side_turrets_forward(); } function side_turrets_forward() { self SetTurretTargetRelativeAngles( (10, -90, 0), 1 ); self SetTurretTargetRelativeAngles( (10, 90, 0), 2 ); } function state_combat_enter( params ) { ratio = 1.0; accel = self GetDefaultAcceleration(); self SetSpeed( ratio * self.settings.defaultmovespeed, ratio * accel, ratio * accel ); self hunter_lockon_fx(); self enable_turrets(); } function state_combat_update( params ) { self endon( "change_state" ); self endon( "death" ); if ( !isdefined( self.enemy ) ) { self vehicle_ai::set_state( "unaware" ); } self thread Movement_Thread_StayInDistance(); self thread Attack_Thread_MainTurret(); self thread Attack_Thread_rocket(); while ( 1 ) { self waittill( "no_enemy" ); self vehicle_ai::set_state( "unaware" ); } } function state_combat_exit( params ) { self notify( "end_attack_thread" ); self notify( "end_movement_thread" ); self ClearTurretTarget(); } // ---------------------------------------------- // ---------------------------------------------- // State: strafe // ---------------------------------------------- function state_strafe_enter( params ) { ratio = 2.0; accel = ratio * self GetDefaultAcceleration(); speed = ratio * self.settings.defaultmovespeed; strafe_speed_attribute = ai::get_behavior_attribute("strafe_speed"); if ( strafe_speed_attribute > 0 ) { speed = strafe_speed_attribute; } self SetSpeed( speed , accel, accel ); } function state_strafe_update( params ) { self endon( "change_state" ); self endon( "death" ); self ClearVehGoalPos(); distanceToTarget = 0.5 * ( self.settings.engagementDistMin + self.settings.engagementDistMax ); target = self.origin + AnglesToForward( self.angles ) * distanceToTarget; if ( isdefined( self.enemy ) ) { distanceToTarget = Distance( self.origin, self.enemy.origin ); } distanceThreshold = 500 + distanceToTarget * 0.08; strafe_distance_attribute = ai::get_behavior_attribute("strafe_distance"); if ( strafe_distance_attribute > 0 ) { distanceThreshold = strafe_distance_attribute; } maxSearchRadius = distanceThreshold * 1.5; halfHeight = 300; outerSpacing = maxSearchRadius * 0.05; innerSpacing = outerSpacing * 2; queryResult = PositionQuery_Source_Navigation( self.origin, 0, maxSearchRadius, halfHeight, innerSpacing, self, outerSpacing ); PositionQuery_Filter_Directness( queryResult, self.origin, target ); PositionQuery_Filter_DistanceToGoal( queryResult, self ); PositionQuery_Filter_InClaimedLocation( queryResult, self ); self vehicle_ai::PositionQuery_Filter_OutOfGoalAnchor( queryResult, 200 ); foreach ( point in queryResult.data ) { distanceToPointSqr = distanceSquared( point.origin, self.origin ); if( distanceToPointSqr < distanceThreshold * 0.5 ) { /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "distAway" ] = -distanceThreshold; #/ point.score += -distanceThreshold;; } /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "distAway" ] = sqrt( distanceToPointSqr ); #/ point.score += sqrt( distanceToPointSqr );; diffToPreferedDirectness = abs( point.directness - 0 ); directnessScore = MapFloat( 0, 1, 1000, 0, diffToPreferedDirectness ); if ( diffToPreferedDirectness > 0.1 ) { directnessScore -= 500; } /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "directnessRaw" ] = point.directness; #/ point.score += point.directness;; /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "directness" ] = directnessScore; #/ point.score += directnessScore;; if ( point.directionChange < 0.6 ) { /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "directionChange" ] = -2000; #/ point.score += -2000;; } /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "directionChangeRaw" ] = point.directionChange; #/ point.score += point.directionChange;; } vehicle_ai::PositionQuery_PostProcess_SortScore( queryResult ); self vehicle_ai::PositionQuery_DebugScores( queryResult ); foreach ( point in queryResult.data ) { self.current_pathto_pos = point.origin; foundpath = self SetVehGoalPos( self.current_pathto_pos, true, true ); if ( foundPath ) { msg = self util::waittill_any_timeout( 5, "near_goal", "force_goal", "goal", "enemy_visible" ); break; } } previous_state = self vehicle_ai::get_previous_state(); if ( !isdefined( previous_state ) || previous_state == "strafe" ) { previous_state = "combat"; } self vehicle_ai::set_state( previous_state ); } function state_strafe_exit( params ) { vehicle_ai::Cooldown( "strafe_again", 2.0 ); } // ---------------------------------------------- function GetNextMovePosition_tactical( enemy ) { if( self.goalforced ) { return self.goalpos; } selfDistToEnemy = Distance2D( self.origin, enemy.origin ); // distance based multipliers goodDist = 0.5 * ( self.settings.engagementDistMin + self.settings.engagementDistMax ); tooCloseDist = 0.8 * goodDist; closeDist = 1.2 * goodDist; farDist = 3 * goodDist; queryMultiplier = MapFloat( closeDist, farDist, 1, 3, selfDistToEnemy ); preferedDistAwayFromOrigin = 150; maxSearchRadius = 1000 * queryMultiplier; halfHeight = 300 * queryMultiplier; innerSpacing = 80 * queryMultiplier; outerSpacing = 80 * queryMultiplier; queryResult = PositionQuery_Source_Navigation( self.origin, 0, maxSearchRadius, halfHeight, innerSpacing, self, outerSpacing ); PositionQuery_Filter_DistanceToGoal( queryResult, self ); PositionQuery_Filter_InClaimedLocation( queryResult, self ); PositionQuery_Filter_Sight( queryResult, enemy.origin, self GetEye() - self.origin, self, 0, enemy ); self vehicle_ai::PositionQuery_Filter_OutOfGoalAnchor( queryResult, 200 ); self vehicle_ai::PositionQuery_Filter_EngagementDist( queryResult, enemy, self.settings.engagementDistMin, self.settings.engagementDistMax ); self vehicle_ai::PositionQuery_Filter_Random( queryResult, 0, 30 ); goalHeight = enemy.origin[2] + 0.5 * ( self.settings.engagementHeightMin + self.settings.engagementHeightMax ); foreach ( point in queryResult.data ) { if ( !point.visibility ) { /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "no visibility" ] = -600; #/ point.score += -600;; } /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "engagementDist" ] = -point.distAwayFromEngagementArea; #/ point.score += -point.distAwayFromEngagementArea;; // distance from origin /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "distToOrigin" ] = MapFloat( 0, preferedDistAwayFromOrigin, 0, 600, point.distToOrigin2D ); #/ point.score += MapFloat( 0, preferedDistAwayFromOrigin, 0, 600, point.distToOrigin2D );; if( point.inClaimedLocation ) { /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "inClaimedLocation" ] = -500; #/ point.score += -500;; } // height preferedHeightRange = 75; distFromPreferredHeight = abs( point.origin[2] - goalHeight ); if ( distFromPreferredHeight > preferedHeightRange ) { heightScore = -MapFloat( preferedHeightRange, 5000, 0, 9000, distFromPreferredHeight ); /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "height" ] = heightScore; #/ point.score += heightScore;; } } self vehicle_ai::PositionQuery_DebugScores( queryResult ); vehicle_ai::PositionQuery_PostProcess_SortScore( queryResult ); if( queryResult.data.size ) { return queryResult.data[0].origin; } return self.origin; } function Movement_Thread_StayInDistance() { self endon( "death" ); self notify( "end_movement_thread" ); self endon( "end_movement_thread" ); maxGoalTimeout = 10; stuckCount = 0; while ( true ) { enemy = self.enemy; if ( !isdefined( enemy ) ) { wait 1; continue; } usePathfinding = true; onNavVolume = IsPointInNavVolume( self.origin, "navvolume_big" ); if ( !onNavVolume ) { // off nav volume, try getting back getbackPoint = undefined; pointOnNavVolume = self GetClosestPointOnNavVolume( self.origin, 500 ); if ( isdefined( pointOnNavVolume ) ) { if ( SightTracePassed( self.origin, pointOnNavVolume, false, self ) ) { getbackPoint = pointOnNavVolume; } } if ( !isdefined( getbackPoint ) ) { queryResult = PositionQuery_Source_Navigation( self.origin, 0, 800, 400, 1.5 * self.radius ); PositionQuery_Filter_Sight( queryResult, self.origin, (0, 0, 0), self, 1 ); getbackPoint = undefined; foreach( point in queryResult.data ) { if ( point.visibility === true ) { getbackPoint = point.origin; break; } } } if ( isdefined( getbackPoint ) ) { self.current_pathto_pos = getbackPoint; usePathfinding = false; } else { stuckCount++; if ( stuckCount == 1 ) { stuckLocation = self.origin; } else if ( stuckCount > 10 ) { /# assert( false, "Hunter fall outside of NavVolume at " + self.origin ); v_box_min = ( -self.radius, -self.radius, -self.radius ); v_box_max = ( self.radius, self.radius, self.radius ); Box( self.origin, v_box_min, v_box_max, self.angles[1], (1,0,0), 1, false, 1000000 ); if ( isdefined( stuckLocation ) ) { Line( stuckLocation, self.origin, (1,0,0), 1, true, 1000000 ); } #/ self Kill(); } } } else { stuckCount = 0; if( self.goalforced ) { goalpos = self GetClosestPointOnNavVolume( self.goalpos, 200 ); if ( isdefined( goalpos ) ) { self.current_pathto_pos = goalpos; usePathfinding = true; } else { self.current_pathto_pos = self.goalpos; usePathfinding = false; } } else { self.current_pathto_pos = GetNextMovePosition_tactical( enemy ); usePathfinding = true; } } if ( !isDefined( self.current_pathto_pos ) ) { wait 0.5; continue; } distanceToGoalSq = DistanceSquared( self.current_pathto_pos, self.origin ); if ( distanceToGoalSq > ( (0.5 * ( self.settings.engagementDistMin + self.settings.engagementDistMax )) * (0.5 * ( self.settings.engagementDistMin + self.settings.engagementDistMax )) ) ) { self SetSpeed( self.settings.defaultMoveSpeed * 2 ); } else { self SetSpeed( self.settings.defaultMoveSpeed ); } self SetLookAtEnt( enemy ); foundpath = self SetVehGoalPos( self.current_pathto_pos, true, usePathfinding ); if ( foundPath ) { /# if ( ( isdefined( GetDvarInt("hkai_debugPositionQuery") ) && GetDvarInt("hkai_debugPositionQuery") ) ) { recordLine( self.origin, self.current_pathto_pos, (0.3,1,0) ); recordLine( self.origin, enemy.origin, (1,0,0.4) ); } #/ msg = self util::waittill_any_timeout( maxGoalTimeout, "near_goal", "force_goal", "goal" ); } else { wait 0.5; } enemy = self.enemy; if ( isdefined( enemy ) ) { goalHeight = enemy.origin[2] + 0.5 * ( self.settings.engagementHeightMin + self.settings.engagementHeightMax ); distFromPreferredHeight = abs( self.origin[2] - goalHeight ); farDist = self.settings.engagementDistMax; nearDist = self.settings.engagementDistMin; selfDistToEnemy = Distance2D( self.origin, enemy.origin ); if ( self VehCanSee( enemy ) && selfDistToEnemy < farDist && selfDistToEnemy > nearDist && distFromPreferredHeight < 230 ) { msg = self util::waittill_any_timeout( RandomFloatRange( 2, 4 ), "enemy_not_visible" ); if ( msg == "enemy_not_visible" ) { msg = self util::waittill_any_timeout( 1.0, "enemy_visible" ); if ( msg != "timeout" ) { wait 1; } } } } else { wait 1; } } } function Delay_Target_ToEnemy_Thread( point, enemy, timeToHit ) { self endon( "death" ); self endon( "change_state" ); self endon( "end_attack_thread" ); self endon( "faketarget_stop_moving" ); enemy endon( "death" ); if ( !isdefined( self.fakeTargetEnt ) ) { self.fakeTargetEnt = Spawn( "script_origin", point ); } self.fakeTargetEnt Unlink(); self.fakeTargetEnt.origin = point; self SetTurretTargetEnt( self.fakeTargetEnt ); self waittill( "turret_on_target" ); timeStart = GetTime(); offset = (0, 0, 0); if( IsSentient( enemy ) ) { offset = enemy GetEye() - enemy.origin; } while( GetTime() < timeStart + timeToHit * 1000 ) { self.fakeTargetEnt.origin = LerpVector( point, enemy.origin + offset, ( GetTime() - timeStart ) / ( timeToHit * 1000 ) ); ///#debugstar( self.fakeTargetEnt.origin, 1000, (0,1,0) ); #/ {wait(.05);}; } self.fakeTargetEnt.origin = enemy.origin + offset; {wait(.05);}; self.fakeTargetEnt LinkTo( enemy ); } function Attack_Thread_MainTurret() { self endon( "death" ); self endon( "change_state" ); self endon( "end_attack_thread" ); while( 1 ) { enemy = self.enemy; if( isdefined( enemy ) ) { self SetLookAtEnt( enemy ); if( self VehCanSee( enemy ) ) { vectorFromEnemy = VectorNormalize( ( (self.origin - enemy.origin)[0], (self.origin - enemy.origin)[1], 0 ) ); self thread Delay_Target_ToEnemy_Thread( enemy.origin + vectorFromEnemy * 300, enemy, 1.5 ); self waittill( "turret_on_target" ); self vehicle_ai::fire_for_time( 2 + RandomFloat( 0.8 ) ); self ClearTurretTarget(); self SetTurretTargetRelativeAngles( (15,0,0), 0 ); if( isdefined( enemy ) && IsAI( enemy ) ) { wait( 2.5 + RandomFloat( 0.5 ) ); } else { wait( 2.0 + RandomFloat( 0.4 ) ); } } else { wait 0.4; } } else { self ClearTurretTarget(); self ClearLookAtEnt(); wait 0.4; } } } function Attack_Thread_rocket() { self endon( "death" ); self endon( "change_state" ); self endon( "end_attack_thread" ); while( true ) { enemy = self.enemy; if ( !isdefined( enemy ) ) { wait 1; continue; } if ( isdefined( enemy ) && self VehCanSee( enemy ) && vehicle_ai::IsCooldownReady( "rocket_launcher" ) ) { vehicle_ai::Cooldown( "rocket_launcher", 8 ); self notify( "end_movement_thread" ); self ClearVehGoalPos(); self SetVehGoalPos( self.origin, true, false ); target = enemy.origin; self SetLookAtEnt( enemy ); self hunter_lockon_fx(); wait 1.5; eye = self GetTagOrigin( "tag_eye" ); if ( isdefined( enemy ) ) { anglesToTarget = VectorToAngles( enemy.origin - eye ); angles = anglesToTarget - self.angles; if ( -30 < angles[0] && angles[0] < 60 && -70 < angles[1] && angles[1] < 70 ) { target = enemy.origin; } else { anglesToTarget = VectorToAngles( target - eye ); } } else { anglesToTarget = VectorToAngles( target - eye ); } rightDir = AnglesToRight( anglesToTarget ); randomRange = 30; offset = []; offset[0] = -rightDir * randomRange * 2 + ( RandomFloatRange( -randomRange, randomRange ), RandomFloatRange( -randomRange, randomRange ), 0 ); offset[1] = rightDir * randomRange * 2 + ( RandomFloatRange( -randomRange, randomRange ), RandomFloatRange( -randomRange, randomRange ), 0 ); self hunter_fire_one_missile( 0, target, offset[0] ); wait 0.5; if ( isdefined( enemy ) ) { eye = self GetTagOrigin( "tag_eye" ); angles = VectorToAngles( enemy.origin - eye ) - self.angles; if ( -30 < angles[0] && angles[0] < 60 && -70 < angles[1] && angles[1] < 70 ) { target = enemy.origin; } } self hunter_fire_one_missile( 1, target, offset[1] ); wait 1; self thread Movement_Thread_StayInDistance(); } wait 0.5; } } // ---------------------------------------------- // best target of side turrets: closest, can hit, and not target of main turret or other turret function side_turret_get_best_target( a_potential_targets, n_index ) { if ( self.ignoreall === true ) { return undefined; } shouldYield = true && level.gameskill < 3; main_turret_target = self.enemy; if ( n_index === 2 ) { other_turret_target = turret::get_target( 1 ); } if ( shouldYield ) { ArrayRemoveValue( a_potential_targets, main_turret_target ); ArrayRemoveValue( a_potential_targets, other_turret_target ); } e_best_target = undefined; while ( !isdefined( e_best_target ) && ( a_potential_targets.size > 0 ) ) { e_closest_target = ArrayGetClosest( self.origin, a_potential_targets ); if( self turret::can_hit_target( e_closest_target, n_index ) ) { e_best_target = e_closest_target; } else { ArrayRemoveValue( a_potential_targets, e_closest_target ); } } return e_best_target; } // ---------------------------------------------- // missile // ---------------------------------------------- function hunter_fire_one_missile( launcher_index, target, offset, blinkLights, waittimeAfterBlinkLights ) { self endon( "death" ); if ( ( isdefined( blinkLights ) && blinkLights ) ) { self vehicle_ai::blink_lights_for_time( 1 ); if ( isdefined( waittimeAfterBlinkLights ) && waittimeAfterBlinkLights > 0 ) { wait waittimeAfterBlinkLights; } } if ( !isdefined( offset ) ) { offset = ( 0, 0, 0 ); } spawnTag = self.missileTags[ launcher_index ]; origin = self GetTagOrigin( spawnTag ); angles = self GetTagAngles( spawnTag ); forward = AnglesToForward( angles ); up = AnglesToUp( angles ); if ( isdefined( spawnTag ) && isdefined( target ) ) { weapon = GetWeapon( "hunter_rocket_turret" ); //only do the full MagicBullet parameter list if target is a real entity, and not a script_struct if ( IsEntity( target ) ) { missile = MagicBullet( weapon, origin, target.origin + offset, self, target, offset ); //missile thread remote_missile_life(); } else if ( IsVec( target ) ) { missile = MagicBullet( weapon, origin, target + offset, self ); } else { missile = MagicBullet( weapon, origin, target.origin + offset, self ); } } } function remote_missile_life() { self endon( "death" ); hostmigration::waitLongDurationWithHostMigrationPause( 10 ); playFX( level.remote_mortar_fx["missileExplode"], self.origin ); self playlocalsound( "mpl_ks_reaper_explosion" ); self delete(); } function hunter_lockon_fx() { self thread vehicle_ai::blink_lights_for_time( 1.5 ); self playsound( "veh_hunter_alarm_target" ); } //self == hunter function getEnemyArray( include_ai, include_player ) { enemyArray = []; enemy_team = "allies";//util::getOtherTeam( self.team ); if ( ( isdefined( include_ai ) && include_ai ) ) { aiArray = GetAITeamArray( enemy_team ); enemyArray = ArrayCombine( enemyArray, aiArray, false, false ); } if ( ( isdefined( include_player ) && include_player ) ) { playerArray = GetPlayers( enemy_team ); enemyArray = ArrayCombine( enemyArray, playerArray, false, false ); } return enemyArray; } // ---------------------------------------------- // scanner // ---------------------------------------------- //self == hunter function is_point_in_view( point, do_trace ) { if ( !isdefined( point ) ) { return false; } scanner = self.frontScanner; vector_to_point = point - scanner.origin; in_view = ( LengthSquared( vector_to_point ) <= ( (10000) * (10000) ) ); if ( in_view ) { in_view = util::within_fov( scanner.origin, scanner.angles, point, Cos( 190 ) ); } if ( in_view && ( isdefined( do_trace ) && do_trace ) && isdefined( self.enemy ) ) { in_view = SightTracePassed( scanner.origin, point, false, self.enemy ); } return in_view; } //self == hunter function is_valid_target( target, do_trace ) { target_is_valid = true; // check script properties if ( ( isdefined( target.ignoreme ) && target.ignoreme ) || ( target.health <= 0 ) ) { target_is_valid = false; } // check sentient properties else if ( IsSentient( target ) && ( ( target IsNoTarget() ) || ( target ai::is_dead_sentient() ) ) ) { target_is_valid = false; } // check fov else if ( isdefined( target.origin ) && !is_point_in_view( target.origin, do_trace ) ) { target_is_valid = false; } return target_is_valid; } //self == hunter function get_enemies_in_view( do_trace ) { validEnemyArray = []; enemyArray = getEnemyArray( true, true ); foreach( enemy in enemyArray ) { if ( is_valid_target( enemy, do_trace ) ) { if ( !isdefined( validEnemyArray ) ) validEnemyArray = []; else if ( !IsArray( validEnemyArray ) ) validEnemyArray = array( validEnemyArray ); validEnemyArray[validEnemyArray.size]=enemy;; } } return validEnemyArray; } // self == hunter function hunter_scanner_init() { self.frontScanner = Spawn( "script_model", self GetTagOrigin( "tag_gunner_flash3" ) ); self.frontScanner SetModel( "tag_origin" ); self.frontScanner.angles = self GetTagAngles( "tag_gunner_flash3" ); self.frontScanner LinkTo( self, "tag_gunner_flash3" ); self.frontScanner.owner = self; self.frontScanner.hasTargetEnt = false; self.frontScanner.sndScanningEnt = spawn( "script_origin", self.frontScanner.origin + anglesToForward( self.angles ) * 1000 ); self.frontScanner.sndScanningEnt linkto( self.frontScanner ); wait 0.25; //self.frontScanner thread hunter_scanner_lookTarget( self ); if ( false ) { PlayFxOnTag( self.settings.spotlightfx, self.frontScanner, "tag_origin" ); } } // self == hunter function hunter_scanner_SetTargetEntity( targetEnt, offset ) { if ( !isdefined( offset ) ) { offset = ( 0, 0, 0 ); } if( IsDefined( targetEnt ) ) { self.frontScanner.targetEnt = targetEnt; self.frontScanner.hasTargetEnt = true; self SetGunnerTargetEnt( self.frontScanner.targetEnt, offset, 2 ); } } // self == hunter function hunter_scanner_ClearLookTarget() { self.frontScanner.hasTargetEnt = false; self ClearGunnerTarget( 2 ); } // self == hunter function hunter_scanner_SetTargetPosition( targetPos ) { if( IsDefined( targetPos ) ) { self.frontScanner.targetPos = targetPos; self SetGunnerTargetVec( self.frontScanner.targetPos, 2 ); } } function hunter_frontScanning() { self endon( "death_shut_off" ); self endon( "crash_done" ); self endon( "death" ); hunter_scanner_init(); offsetFactorPitch = 0; offsetFactorYaw = 0; // use 2 different irrational numbers here to help avoiding repetitive patterns pitchStep = 1 * 2.23606797749978969640; // irrational number sqrt(5) yawStep = 1 * 3.14159265358979323846; // irrational number PI pitchRange = 20; yawRange = 45; scannerDirection = undefined; while ( 1 ) { scannerOrigin = self.frontScanner.origin; if ( ( isdefined( self.inpain ) && self.inpain ) ) { wait 0.3; offset = ( 50, 0, 0 ) + ( math::randomSign() * RandomFloatRange( 1, 2 ) * pitchRange, math::randomSign() * RandomFloatRange( 1, 2 ) * yawRange, 0 ); scannerDirection = anglesToForward( self.angles + offset ); } else if ( !IsDefined( self.enemy ) ) { if ( false ) { self.frontScanner.sndScanningEnt playloopsound( "veh_hunter_scanner_loop", 1 ); } offsetFactorPitch = offsetFactorPitch + pitchStep; offsetFactorYaw = offsetFactorYaw + yawStep; offset = ( 50, 0, 0 ) + ( Sin( offsetFactorPitch ) * pitchRange, Cos( offsetFactorYaw ) * yawRange, 0 ); scannerDirection = anglesToForward( self.angles + offset ); enemies = get_enemies_in_view( true ); if ( enemies.size > 0 ) { closest_enemy = ArrayGetClosest( self.origin, enemies ); self.favoriteEnemy = closest_enemy; /# line( scannerOrigin, closest_enemy.origin, ( 0, 1, 0 ), 1, 3 ); #/ } } else { if ( self is_point_in_view( self.enemy.origin, true ) ) { self notify ( "hunter_lockOnTargetInSight" ); } else { self notify ( "hunter_lockOnTargetOutSight" ); } scannerDirection = VectorNormalize( self.enemy.origin - scannerOrigin ); if ( false ) { self.frontScanner.sndScanningEnt stoploopsound( 1 ); } } targetLocation = scannerOrigin + scannerDirection * 1000; // any big value will do self hunter_scanner_SetTargetPosition( targetLocation ); /# line( scannerOrigin, self.frontScanner.targetPos, ( 0, 1, 0 ), 1, 1000 ); #/ wait 0.1; } } function hunter_exit_vehicle() { self waittill( "exit_vehicle", player ); player.ignoreme = false; player DisableInvulnerability(); self SetHeliHeightLock( false ); self EnableAimAssist(); self SetVehicleType( self.original_vehicle_type ); self.attachedpath = undefined; self SetGoal( self.origin, false, 4096, 512 ); } function hunter_scripted( params ) { // do nothing state params.driver = self GetSeatOccupant( 0 ); if ( isdefined( params.driver ) ) { self DisableAimAssist(); self thread vehicle_death::vehicle_damage_filter( "firestorm_turret" ); //self thread hunter_set_team( driver.team ); params.driver.ignoreme = true; params.driver EnableInvulnerability(); if ( isdefined( self.vehicle_weapon_override ) ) { self SetVehWeapon( self.vehicle_weapon_override ); } self thread hunter_exit_vehicle(); //self thread hunter_update_rumble(); self thread hunter_collision_player(); //self thread hunter_self_destruct(); self thread player_fire_update_side_turret_1(); self thread player_fire_update_side_turret_2(); self thread player_fire_update_rocket(); } if ( isdefined( self.goal_node ) && isdefined( self.goal_node.hunter_claimed ) ) { self.goal_node.hunter_claimed = undefined; } self ClearTargetEntity(); self ClearVehGoalPos(); self PathVariableOffsetClear(); self PathFixedOffsetClear(); self ClearLookAtEnt(); self ResumeSpeed(); } function player_fire_update_side_turret_1() { self endon( "death" ); self endon( "exit_vehicle" ); weapon = self SeatGetWeapon( 1 ); fireTime = weapon.fireTime; while( 1 ) { self SetGunnerTargetVec( self GetTurretTargetVec( 0 ), 0 ); if( self IsDriverFiring( ) ) { self FireWeapon( 1 ); } wait fireTime; } } function player_fire_update_side_turret_2() { self endon( "death" ); self endon( "exit_vehicle" ); weapon = self SeatGetWeapon( 2 ); fireTime = weapon.fireTime; while( 1 ) { self SetGunnerTargetVec( self GetTurretTargetVec( 0 ), 1 ); if( self IsDriverFiring( ) ) { self FireWeapon( 2 ); } wait fireTime; } } function player_fire_update_rocket() { self endon( "death" ); self endon( "exit_vehicle" ); weapon = GetWeapon( "hunter_rocket_turret_player" ); fireTime = weapon.fireTime; driver = self GetSeatOccupant( 0 ); while( 1 ) { if( driver ButtonPressed( "BUTTON_A" ) ) { spawnTag0 = self.missileTags[ 0 ]; spawnTag1 = self.missileTags[ 1 ]; origin0 = self GetTagOrigin( spawnTag0 ); origin1 = self GetTagOrigin( spawnTag1 ); target = self GetTurretTargetVec( 0 ); MagicBullet( weapon, origin0, target ); MagicBullet( weapon, origin1, target ); wait fireTime; } Wait 0.05; } } function hunter_collision_player() { self endon( "change_state" ); self endon( "crash_done" ); self endon( "death" ); while ( 1 ) { self waittill( "veh_collision", velocity, normal ); driver = self GetSeatOccupant( 0 ); if ( isdefined( driver ) && LengthSquared( velocity ) > 70 * 70 ) { Earthquake( 0.25, 0.25, driver.origin, 50 ); driver PlayRumbleOnEntity( "damage_heavy" ); } } } // Lots of gross hardcoded values! :( function hunter_update_rumble() { self endon( "death" ); self endon( "exit_vehicle" ); while ( 1 ) { vr = Abs( self GetSpeed() / self GetMaxSpeed() ); if ( vr < 0.1 ) { level.player PlayRumbleOnEntity( "hunter_fly" ); wait 0.35; } else { time = RandomFloatRange( 0.1, 0.2 ); Earthquake( RandomFloatRange( 0.1, 0.15 ), time, self.origin, 200 ); level.player PlayRumbleOnEntity( "hunter_fly" ); wait time; } } } function hunter_self_destruct() { self endon( "death" ); self endon( "exit_vehicle" ); const max_self_destruct_time = 5; self_destruct = false; self_destruct_time = 0; while ( 1 ) { if ( !self_destruct ) { if ( level.player MeleeButtonPressed() ) { self_destruct = true; self_destruct_time = max_self_destruct_time; } {wait(.05);}; continue; } else { IPrintLnBold( self_destruct_time ); wait 1; self_destruct_time -= 1; if ( self_destruct_time == 0 ) { driver = self GetSeatOccupant( 0 ); if ( isdefined( driver ) ) { driver DisableInvulnerability(); } Earthquake( 3, 1, self.origin, 256 ); RadiusDamage( self.origin, 1000, 15000, 15000, level.player, "MOD_EXPLOSIVE" ); self DoDamage( self.health + 1000, self.origin ); } continue; } } } function hunter_level_out_for_landing() { self endon( "death" ); self endon( "emped" ); self endon( "landed" ); while ( isdefined( self.emped ) ) { velocity = self.velocity; // setting the angles clears the velocity so we save it off and set it back self.angles = ( self.angles[0] * 0.85, self.angles[1], self.angles[2] * 0.85 ); ang_vel = self GetAngularVelocity() * 0.85; self SetAngularVelocity( ang_vel ); self SetVehVelocity( velocity ); {wait(.05);}; } } function hunter_emped( params ) { self endon( "death" ); self endon( "emped" ); self.emped = true; wait RandomFloatRange( 4, 7 ); self vehicle_ai::evaluate_connections(); /* PlaySoundAtPosition( "veh_qrdrone_emp_down", self.origin ); if ( !isdefined( self.stun_fx ) ) { self.stun_fx = Spawn( "script_model", self.origin ); self.stun_fx SetModel( "tag_origin" ); self.stun_fx LinkTo( self, "tag_origin", ( 0, 0, 0 ), ( 0, 0, 0 ) ); PlayFXOnTag( level._effect[ "hunter_stun" ], self.stun_fx, "tag_origin" ); } wait RandomFloatRange( 4, 7 ); self.stun_fx delete(); self playsound ( "veh_qrdrone_boot_qr" ); */ } // ---------------------------------------------- // pain/hit reaction // ---------------------------------------------- function hunter_pain_for_time( time, velocityStablizeParam, rotationStablizeParam, restoreLookPoint ) { self endon( "death" ); self.painStartTime = GetTime(); if ( !( isdefined( self.inpain ) && self.inpain ) ) { self.inpain = true; while ( GetTime() < self.painStartTime + time * 1000 ) { self SetVehVelocity( self.velocity * velocityStablizeParam ); self SetAngularVelocity( self GetAngularVelocity() * rotationStablizeParam ); wait 0.1; } if ( isdefined( restoreLookPoint ) ) { restoreLookEnt = Spawn( "script_model", restoreLookPoint ); restoreLookEnt SetModel( "tag_origin" ); self ClearLookAtEnt(); self SetLookAtEnt( restoreLookEnt ); self setTurretTargetEnt( restoreLookEnt ); wait 1.5; self ClearLookAtEnt(); self ClearTurretTarget(); restoreLookEnt delete(); } self.inpain = false; } } function hunter_pain_small( eAttacker, damageType, hitPoint, hitDirection, hitLocationInfo, partName ) { if( !isdefined( hitPoint ) || !isdefined( hitDirection ) ) { return; } self SetVehVelocity( self.velocity + VectorNormalize( hitDirection ) * 20 ); if ( !( isdefined( self.inpain ) && self.inpain ) ) { vecRight = anglesToRight( self.angles ); sign = math::sign( vectorDot( vecRight, hitDirection ) ); yaw_vel = sign * RandomFloatRange( 100, 140 ); ang_vel = self GetAngularVelocity(); ang_vel += ( RandomFloatRange( -120, -100 ), yaw_vel, RandomFloatRange( -100, 100 ) ); self SetAngularVelocity( ang_vel ); self thread hunter_pain_for_time( 1.5, 1.0, 0.8 ); } self vehicle_ai::set_state( "strafe" ); } function HunterCallback_VehicleDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, damageFromUnderneath, modelIndex, partName, vSurfaceNormal ) { driver = self GetSeatOccupant( 0 ); // no friendly fire if ( isdefined( eAttacker ) && eAttacker.team == self.team ) { return 0; } num_players = GetPlayers().size; maxDamage = self.healthdefault * ( 0.35 - 0.025 * num_players ); if ( sMeansOfDeath !== "MOD_UNKNOWN" && iDamage > maxDamage ) { iDamage = maxDamage; } if ( !isdefined( self.damageLevel ) ) { self.damageLevel = 0; self.newDamageLevel = self.damageLevel; } newDamageLevel = vehicle::should_update_damage_fx_level( self.health, iDamage, self.healthdefault ); if ( newDamageLevel > self.damageLevel ) { self.newDamageLevel = newDamageLevel; } if ( self.newDamageLevel > self.damageLevel ) { self.damageLevel = self.newDamageLevel; if ( self.pain_when_damagelevel_change === true ) { hunter_pain_small( eAttacker, sMeansOfDeath, vPoint, vDir, sHitLoc, partName ); } vehicle::set_damage_fx_level( self.damageLevel ); } if ( vehicle_ai::should_emp( self, weapon, sMeansOfDeath, eInflictor, eAttacker ) ) { hunter_pain_small( eAttacker, sMeansOfDeath, vPoint, vDir, sHitLoc, partName ); } return iDamage; }