#using scripts\codescripts\struct; #using scripts\shared\math_shared; #using scripts\shared\statemachine_shared; #using scripts\shared\system_shared; #using scripts\shared\array_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\clientfield_shared; #using_animtree( "generic" ); #namespace wasp; function autoexec __init__sytem__() { system::register("wasp",&__init__,undefined,undefined); } function __init__() { vehicle::add_main_callback( "wasp", &wasp_initialize ); clientfield::register( "vehicle", "rocket_wasp_hijacked", 1, 1, "int" ); } function wasp_initialize() { self UseAnimTree( #animtree ); Target_Set( self, ( 0, 0, 0 ) ); self.health = self.healthdefault; self vehicle::friendly_fire_shield(); self EnableAimAssist(); self SetNearGoalNotifyDist( 40 ); //self SetVehicleAvoidance( true ); // this is ORCA avoidance self SetHoverParams( 50, 100.0, 100.0 ); self.fovcosine = 0; // +/-90 degrees = 180 self.fovcosinebusy = 0; //+/- 55 degrees = 110 fov self.vehAirCraftCollisionEnabled = true; assert( isdefined( self.scriptbundlesettings ) ); 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.variant = "mg"; if ( IsSubStr( self.vehicleType, "rocket" ) ) { self.variant = "rocket"; } self.overrideVehicleDamage = &drone_callback_damage; self.allowFriendlyFireDamageOverride = &drone_AllowFriendlyFireDamage; self thread vehicle_ai::nudge_collision(); //disable some cybercom abilities if( IsDefined( level.vehicle_initializer_cb ) ) { [[level.vehicle_initializer_cb]]( self ); } if ( self.variant === "rocket" ) { self.ignoreFireFly = true; self vehicle_ai::InitThreatBias(); } init_guard_points(); defaultRole(); } function defaultRole() { self vehicle_ai::init_state_machine_for_role( "default" ); 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( "death" ).update_func = &state_death_update; self vehicle_ai::get_state_callbacks( "driving" ).update_func = &wasp_driving; self vehicle_ai::get_state_callbacks( "emped" ).update_func = &state_emped_update; self vehicle_ai::add_state( "guard", &state_guard_enter, &state_guard_update, &state_guard_exit ); vehicle_ai::add_utility_connection( "combat", "guard", &state_guard_can_enter ); vehicle_ai::add_utility_connection( "guard", "combat" ); vehicle_ai::add_interrupt_connection( "guard", "emped", "emped" ); vehicle_ai::add_interrupt_connection( "guard", "surge", "surge" ); vehicle_ai::add_interrupt_connection( "guard", "off", "shut_off" ); vehicle_ai::add_interrupt_connection( "guard", "pain", "pain" ); vehicle_ai::add_interrupt_connection( "guard", "driving", "enter_vehicle" ); vehicle_ai::StartInitialState( "combat" ); } // ---------------------------------------------- // State: death // ---------------------------------------------- function state_death_update( params ) { self endon( "death" ); if ( isArray( self.followers ) ) { foreach( follower in self.followers ) { if ( isdefined( follower ) ) { follower.leader = undefined; } } } death_type = vehicle_ai::get_death_type( params ); // gib death if ( !isdefined( death_type ) && isdefined( params ) ) { if ( isdefined( params.weapon ) ) { if ( params.weapon.doannihilate ) { death_type = "gibbed"; } else if ( params.weapon.dogibbing && isdefined( params.attacker ) ) { dist = Distance( self.origin, params.attacker.origin ); if ( dist < params.weapon.maxgibdistance ) { gib_chance = 1.0 - ( dist / params.weapon.maxgibdistance ); //if( RandomFloatRange( 0.0, 1.0 ) < gib_chance ) if ( RandomFloatRange( 0.0, 2.0 ) < gib_chance ) // less chance for now, weapons are tuned to gib too much { death_type = "gibbed"; } } } } if ( isdefined( params.meansOfDeath ) ) { meansOfDeath = params.meansOfDeath; if ( meansOfDeath === "MOD_EXPLOSIVE" || meansOfDeath === "MOD_GRENADE_SPLASH" || meansOfDeath === "MOD_PROJECTILE_SPLASH" || meansOfDeath === "MOD_PROJECTILE" ) { death_type = "gibbed"; } } } // crash if ( !isdefined( death_type ) ) { crash_style = RandomInt( 3 ); switch( crash_style ) { case 0: // barrel_rolling camera is too crazy, so show gibbed to the player instead if ( self.hijacked === true ) { params.death_type = "gibbed"; vehicle_ai::defaultstate_death_update( params ); } else { vehicle_death::barrel_rolling_crash(); } break; case 1: vehicle_death::plane_crash(); break; default: vehicle_death::random_crash( params.vDir ); } self vehicle_death::DeleteWhenSafe(); } else { params.death_type = death_type; vehicle_ai::defaultstate_death_update( params ); } } // ---------------------------------------------- // State: emped // ---------------------------------------------- function state_emped_update( params ) { self endon ( "death" ); self endon ( "change_state" ); {wait(.05);}; gravity = 400; self notify( "end_nudge_collision" ); // emp down time empdowntime = params.notify_param[0]; assert( isdefined( empdowntime ) ); vehicle_ai::Cooldown( "emped_timer", empdowntime ); wait RandomFloat( 0.2 ); // give it a spin ang_vel = self GetAngularVelocity(); pitch_vel = math::randomSign() * RandomFloatRange( 200, 250 ); yaw_vel = math::randomSign() * RandomFloatRange( 200, 250 ); roll_vel = math::randomSign() * RandomFloatRange( 200, 250 ); ang_vel += ( pitch_vel, yaw_vel, roll_vel ); self SetAngularVelocity( ang_vel ); // let it fall if ( IsPointInNavVolume( self.origin, "navvolume_small" ) ) { self.position_before_fall = self.origin; } self CancelAIMove(); self SetPhysAcceleration( ( 0, 0, -gravity ) ); killonimpact_speed = self.settings.killonimpact_speed; if( self.health <= 20 ) { killonimpact_speed = 1; } self fall_and_bounce( killonimpact_speed, self.settings.killonimpact_time ); self notify( "landed" ); // forcefully stablize just in case self SetVehVelocity( ( 0, 0, 0 ) ); self SetPhysAcceleration( ( 0, 0, -gravity * 0.1 ) ); self SetAngularVelocity( ( 0, 0, 0 ) ); // wait emp down time while( !vehicle_ai::IsCooldownReady( "emped_timer" ) ) { timeLeft = max( vehicle_ai::GetCooldownLeft( "emped_timer" ), 0.5 ); wait timeLeft; } // get back up self.abnormal_status.emped = false; self vehicle::toggle_emp_fx( false ); self vehicle_ai::emp_startup_fx(); bootup_timer = 1.6; while( bootup_timer > 0 ) { self vehicle::lights_on(); wait 0.4; self vehicle::lights_off(); wait 0.4; bootup_timer -= 0.8; } self vehicle::lights_on(); if ( isdefined( self.position_before_fall ) ) { originoffset = (0,0,5); goalpoint = self GetClosestPointOnNavVolume( self.origin + originoffset, 50 ); if ( isdefined( goalpoint ) && SightTracePassed( self.origin + originoffset, goalpoint, false, self ) ) { self SetVehGoalPos( goalpoint, false, false ); self util::waittill_any_timeout( 0.3, "near_goal", "goal", "change_state", "death" ); if ( isdefined( self.enemy ) ) { self SetLookAtEnt( self.enemy ); } startTime = GetTime(); self.current_pathto_pos = self.position_before_fall; foundGoal = self SetVehGoalPos( self.current_pathto_pos, true, true ); while ( !foundGoal && vehicle_ai::TimeSince( startTime ) < 3 ) { foundGoal = self SetVehGoalPos( self.current_pathto_pos, true, true ); wait 0.3; } if ( foundGoal ) { self util::waittill_any_timeout( 1, "near_goal", "goal", "change_state", "death" ); } else { self SetVehGoalPos( self.origin, true, false ); } wait 1; self.position_before_fall = undefined; self vehicle_ai::evaluate_connections(); } } // not able to get up self vehicle::lights_off(); } function fall_and_bounce( killOnImpact_speed, killOnImpact_time ) { self endon( "death" ); self endon( "change_state" ); maxBounceTime = 3; bounceScale = 0.3; velocityLoss = 0.3; maxAngle = 12; bouncedTime = 0; angularVelStablizeParams = ( 0.3, 0.5, 0.2 ); // stablize pitch and roll more anglesStablizeInitialScale = 0.6; anglesStablizeIncrement = 0.2; fallStart = GetTime(); while ( bouncedTime < maxBounceTime && LengthSquared( self.velocity ) > ( (10) * (10) ) ) { self waittill( "veh_collision", impact_vel, normal ); // if too fast or dropping for too long, kill on impact if ( LengthSquared( impact_vel ) > ( (killOnImpact_speed) * (killOnImpact_speed) ) || ( vehicle_ai::TimeSince( fallStart ) > killOnImpact_time && LengthSquared( impact_vel ) > ( (killOnImpact_speed * 0.8) * (killOnImpact_speed * 0.8) ) ) ) { self kill(); } // don't have a safe point to get back to else if ( !isdefined( self.position_before_fall ) ) { self kill(); } else { fallStart = GetTime(); } // bounce velocity oldvelocity = self.velocity; // current velocity (self.velocity) and impact velocity (impact_vel) can be very different vel_hitDir = -VectorProjection( impact_vel, normal ); // negate of impact velocity along surface normal is the bounce velocity vel_hitDirUp = VectorProjection( vel_hitDir, (0,0,1) ); velscale = min( bounceScale * ( bouncedTime + 1 ), 0.9 ); // don't go too big newVelocity = ( oldvelocity - VectorProjection( oldvelocity, vel_hitDir ) ) * ( 1 - velocityLoss ); // clear velocity in bounce direction, and add velocity loss newVelocity += vel_hitDir * velscale; // add bounce velocity // adjust angular velocity and angles so it lays flat on ground shouldBounce = ( VectorDot( normal, (0,0,1) ) > 0.76 ); // roughly 45 degrees if ( shouldBounce ) { // stablize angular velocity // stablize more if velocity is low velocityLengthSqr = LengthSquared( newVelocity ); stablizeScale = MapFloat( ( (5) * (5) ), ( (60) * (60) ), 0.1, 1, velocityLengthSqr ); ang_vel = self GetAngularVelocity(); ang_vel *= angularVelStablizeParams * stablizeScale; self SetAngularVelocity( ang_vel ); // bring angles to flat angles = self.angles; anglesStablizeScale = min( anglesStablizeInitialScale - bouncedTime * anglesStablizeIncrement, 0.1 ); // don't go too small pitch = angles[0]; yaw = angles[1]; roll = angles[2]; surfaceAngles = VectorToAngles( normal ); surfaceRoll = surfaceAngles[2]; if ( pitch < -maxAngle || pitch > maxAngle ) { pitch *= anglesStablizeScale; } if ( roll < surfaceRoll - maxAngle || roll > surfaceRoll + maxAngle ) { roll = LerpFloat( surfaceRoll, roll, anglesStablizeScale ); } self.angles = ( pitch, yaw, roll ); // setting the angles clears the velocity so we have to set velocity after this } self SetVehVelocity( newVelocity ); self vehicle_ai::collision_fx( normal ); if ( shouldBounce ) { bouncedTime++; } } } // ---------------------------------------------- // State: guard // ---------------------------------------------- function init_guard_points() { self._guard_points = []; if ( !isdefined( self._guard_points ) ) self._guard_points = []; else if ( !IsArray( self._guard_points ) ) self._guard_points = array( self._guard_points ); self._guard_points[self._guard_points.size]=(150, -110, 110);; if ( !isdefined( self._guard_points ) ) self._guard_points = []; else if ( !IsArray( self._guard_points ) ) self._guard_points = array( self._guard_points ); self._guard_points[self._guard_points.size]=(150, 110, 110);; if ( !isdefined( self._guard_points ) ) self._guard_points = []; else if ( !IsArray( self._guard_points ) ) self._guard_points = array( self._guard_points ); self._guard_points[self._guard_points.size]=(120, -110, 80);; if ( !isdefined( self._guard_points ) ) self._guard_points = []; else if ( !IsArray( self._guard_points ) ) self._guard_points = array( self._guard_points ); self._guard_points[self._guard_points.size]=(120, 110, 80);; if ( !isdefined( self._guard_points ) ) self._guard_points = []; else if ( !IsArray( self._guard_points ) ) self._guard_points = array( self._guard_points ); self._guard_points[self._guard_points.size]=(180, 0, 140);; } /# function guard_points_debug() { self endon ( "death" ); if ( self.isdebugdrawing === true ) { return; } self.isdebugdrawing = true; while( true ) { foreach( point in self.debugpointsarray ) { color = ( 1, 0, 0 ); if ( IsPointInNavVolume( point, "navvolume_small" ) ) { color = ( 0, 1, 0 ); } debugstar( point, 5, color ); } {wait(.05);}; } } #/ function get_guard_points( owner ) { assert( self._guard_points.size > 0, "wasp has no guard points" ); //self.debugpointsarray = []; // /# self thread guard_points_debug(); #/ points_array = []; foreach( point in self._guard_points ) { offset = RotatePoint( point, owner.angles ); worldPoint = offset + owner.origin + owner GetVelocity() * 0.5; //ARRAY_ADD( self.debugpointsarray, worldPoint ); if ( IsPointInNavVolume( worldPoint, "navvolume_small" ) ) { if ( !isdefined( points_array ) ) points_array = []; else if ( !IsArray( points_array ) ) points_array = array( points_array ); points_array[points_array.size]=worldPoint;; } } if ( points_array.size < 1 ) { queryResult = PositionQuery_Source_Navigation( owner.origin + (0,0,50), 25, 200, 100, 1.2 * self.radius, self ); PositionQuery_Filter_Sight( queryResult, owner.origin + (0,0,10), (0, 0, 0), self, 3 ); foreach( point in queryResult.data ) { if ( point.visibility === true && BulletTracePassed( owner.origin + (0,0,10), point.origin, false, self, self, false, true ) ) { if ( !isdefined( points_array ) ) points_array = []; else if ( !IsArray( points_array ) ) points_array = array( points_array ); points_array[points_array.size]=point.origin;; } } } return points_array; } function state_guard_can_enter( from_state, to_state, connection ) { if ( self.enable_guard !== true || !isdefined( self.owner ) ) { return false; } if ( !isdefined( self.enemy ) || !self VehSeenRecently( self.enemy, 3 ) ) { return true; } // if enemy is far away from owner, and wasp is not very close to it if ( DistanceSquared( self.owner.origin, self.enemy.origin ) > ( (1200) * (1200) ) && DistanceSquared( self.origin, self.enemy.origin ) > ( (300) * (300) ) ) { return true; } if ( !IsPointInNavVolume( self.origin, "navvolume_small" ) ) { return true; } return false; } function state_guard_enter( params ) { if ( self.enable_target_laser === true ) { self LaserOff(); } self update_main_guard(); } function update_main_guard() { if ( isdefined( self.owner ) && !isAlive( self.owner.main_guard ) || self.owner.main_guard.owner !== self.owner ) { self.owner.main_guard = self; } } function state_guard_exit( params ) { if ( isdefined( self.owner ) && self.owner.main_guard === self ) { self.owner.main_guard = undefined; } } function test_get_back_point( point ) { if ( SightTracePassed( self.origin, point, false, self ) ) { if ( BulletTracePassed( self.origin, point, false, self, self, false, true ) ) { return 1; } return 0; } return -1; } function test_get_back_queryresult( queryResult ) { getbackPoint = undefined; foreach( point in queryResult.data ) { testresult = test_get_back_point( point.origin ); if ( testresult == 1 ) { return point.origin; } else if ( testresult == 0 ) { {wait(.05);}; } } return undefined; } function state_guard_update( params ) { self endon( "death" ); self endon( "change_state" ); self SetHoverParams( 20.0, 40.0, 30.0 ); timeNotAtGoal = GetTime(); pointIndex = 0; stuckCount = 0; while( 1 ) { if( isdefined( self.enemy ) && DistanceSquared( self.owner.origin, self.enemy.origin ) < ( (1000) * (1000) ) && self VehSeenRecently( self.enemy, 1 ) && IsPointInNavVolume( self.origin, "navvolume_small" ) ) { self vehicle_ai::evaluate_connections(); wait 1; } else { owner = self.owner; if ( !isdefined( owner ) ) { wait 1; continue; } usePathfinding = true; onNavVolume = IsPointInNavVolume( self.origin, "navvolume_small" ); if ( !onNavVolume ) { // off nav volume, try getting back getbackPoint = undefined; // try closest point pointOnNavVolume = self GetClosestPointOnNavVolume( self.origin, 500 ); if ( isdefined( pointOnNavVolume ) ) { if ( test_get_back_point( pointOnNavVolume ) == 1 ) { getbackPoint = pointOnNavVolume; } } // try query points on horizontal level if ( !isdefined( getbackPoint ) ) { queryResult = PositionQuery_Source_Navigation( self.origin, 0, 1500, 200, 80, self ); getbackPoint = test_get_back_queryresult( queryResult ); } // try vertical points if ( !isdefined( getbackPoint ) ) { queryResult = PositionQuery_Source_Navigation( self.origin, 0, 300, 700, 30, self ); getbackPoint = test_get_back_queryresult( queryResult ); } if ( isdefined( getbackPoint ) ) { if ( DistanceSquared( getbackPoint, self.origin ) > ( (40 * 0.5) * (40 * 0.5) ) ) { self.current_pathto_pos = getbackPoint; usePathfinding = false; self.vehAirCraftCollisionEnabled = false; } else { onNavVolume = true; } } else { stuckCount++; if ( stuckCount == 1 ) { stuckLocation = self.origin; } else if ( stuckCount > 10 ) { /# assert( false, "Wasp 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(); } } } if ( onNavVolume ) { self update_main_guard(); if ( owner.main_guard === self ) { guardPoints = get_guard_points( owner ); if ( guardPoints.size < 1 ) { wait 1; continue; } stuckCount = 0; self.vehAirCraftCollisionEnabled = true; if ( guardPoints.size <= pointIndex ) { pointIndex = RandomInt( Int( min( self._guard_points.size, guardPoints.size ) ) ); timeNotAtGoal = GetTime(); } self.current_pathto_pos = guardPoints[pointIndex]; } else { main_guard = owner.main_guard; if( isalive( main_guard ) && isdefined( main_guard.current_pathto_pos ) ) { query_position = main_guard.current_pathto_pos; queryResult = PositionQuery_Source_Navigation( query_position, 20, 140, 100, 20, self, 15 ); if ( queryResult.data.size > 0 ) { self.current_pathto_pos = queryResult.data[ queryResult.data.size - 1 ].origin; } } } } if ( IsDefined( self.current_pathto_pos ) ) { distanceToGoalSq = DistanceSquared( self.current_pathto_pos, self.origin ); if ( !onNavVolume || distanceToGoalSq > ( (60) * (60) ) ) { if ( distanceToGoalSq > ( (600) * (600) ) ) { self SetSpeed( self.settings.defaultMoveSpeed * 2 ); } else if ( distanceToGoalSq < ( (100) * (100) ) ) { self SetSpeed( self.settings.defaultMoveSpeed * 0.3 ); } else { self SetSpeed( self.settings.defaultMoveSpeed ); } timeNotAtGoal = GetTime(); } else // already at goal { if ( vehicle_ai::TimeSince( timeNotAtGoal ) > 4 ) { pointIndex = RandomInt( self._guard_points.size ); timeNotAtGoal = GetTime(); } wait 0.2; continue; } if ( self SetVehGoalPos( self.current_pathto_pos, true, usePathfinding ) ) { self playsound ( "veh_wasp_direction" ); self ClearLookAtEnt(); self notify( "fire_stop" ); // stop shooting //self.noshoot = true; self thread path_update_interrupt(); if ( onNavVolume ) { self vehicle_ai::waittill_pathing_done( 1 ); } else { self vehicle_ai::waittill_pathing_done(); } //self.noshoot = undefined; } else { wait 0.5; } } else { wait 0.5; } } } } // ---------------------------------------------- // State: combat // ---------------------------------------------- function state_combat_enter( params ) { if ( self.enable_target_laser === true ) { self LaserOn(); } if ( IsDefined( self.owner ) && IsDefined( self.owner.enemy ) ) { self.favoriteEnemy = self.owner.enemy; } self thread turretFireUpdate(); } function turretFireUpdate() { self endon( "death" ); self endon( "change_state" ); isRocketType = ( self.variant === "rocket" ); while( 1 ) { if( isdefined( self.enemy ) && self VehCanSee( self.enemy ) ) { if( DistanceSquared( self.enemy.origin, self.origin ) < ( (0.5 * ( self.settings.engagementDistMin + self.settings.engagementDistMax ) * 3) * (0.5 * ( self.settings.engagementDistMin + self.settings.engagementDistMax ) * 3) ) ) { self SetLookAtEnt( self.enemy ); if( isRocketType ) { self SetTurretTargetEnt( self.enemy, self.enemy GetVelocity() * 0.3 - vehicle_ai::GetTargetEyeOffset( self.enemy ) * 0.3 ); } else { self SetTurretTargetEnt( self.enemy, -vehicle_ai::GetTargetEyeOffset( self.enemy ) * 0.3 ); } startAim = GetTime(); while ( !self.turretontarget && vehicle_ai::TimeSince( startAim ) < 3 ) { wait 0.2; } if ( isdefined( self.enemy ) && self.turretontarget && self.noshoot !== true ) { if( isRocketType ) { for( i = 0; i < 2 && isdefined( self.enemy ); i++ ) { self FireWeapon( 0, self.enemy ); fired = true; wait 0.25; } } else { self vehicle_ai::fire_for_time( RandomFloatRange( self.settings.turret_fire_burst_min, self.settings.turret_fire_burst_max ), 0, self.enemy ); } if( isdefined( self.settings.turret_cooldown_max ) ) { if(!isdefined(self.settings.turret_cooldown_min))self.settings.turret_cooldown_min=0; wait( RandomFloatRange( self.settings.turret_cooldown_min, self.settings.turret_cooldown_max ) ); } } else { if( isdefined( self.settings.turret_enemy_detect_freq ) ) wait self.settings.turret_enemy_detect_freq; } self SetTurretTargetRelativeAngles( (15,0,0), 0 ); } if( isRocketType ) { if( isdefined( self.enemy ) && IsAI( self.enemy ) ) { wait( RandomFloatRange( 4, 7 ) ); } else { wait( RandomFloatRange( 3, 5 ) ); } } else { if( isdefined( self.enemy ) && IsAI( self.enemy ) ) { wait( RandomFloatRange( 2, 2.5 ) ); } else { wait( RandomFloatRange( 0.5, 1.5 ) ); } } } else { wait 0.4; } } } function path_update_interrupt() { self endon( "death" ); self endon( "change_state" ); self endon( "near_goal" ); self endon( "reached_end_node" ); old_enemy = self.enemy; wait 1; while( 1 ) { if( isdefined( self.current_pathto_pos ) ) { if( distance2dSquared( self.current_pathto_pos, self.goalpos ) > ( (self.goalradius) * (self.goalradius) ) ) { wait 0.2; self notify( "near_goal" ); } } if ( isdefined( self.enemy ) ) { if ( self.noshoot !== true && self VehCanSee( self.enemy ) ) { self SetTurretTargetEnt( self.enemy ); self SetLookAtEnt( self.enemy ); } if( !isdefined( old_enemy ) ) { self notify( "near_goal" ); // new enemy } else if( self.enemy != old_enemy ) { self notify( "near_goal" ); // new enemy } if( self VehCanSee( self.enemy ) && distance2dSquared( self.origin, self.enemy.origin ) < ( (250) * (250) ) ) { self notify( "near_goal" ); } } wait 0.2; } } function wait_till_something_happens( timeout ) { self endon( "change_state" ); self endon( "death" ); wait 0.1; time = timeout; cant_see_count = 0; while( time > 0 ) { if( isdefined( self.current_pathto_pos ) ) { if( distanceSquared( self.current_pathto_pos, self.goalpos ) > ( (self.goalradius) * (self.goalradius) ) ) { break; } } if( isdefined( self.enemy ) ) { if ( !self vehCanSee( self.enemy ) ) { cant_see_count++; if( cant_see_count >= 3 ) { break; } } else { cant_see_count = 0; } if( distance2dSquared( self.origin, self.enemy.origin ) < ( (250) * (250) ) ) { break; } // height check goalHeight = self.enemy.origin[2] + 0.5 * ( self.settings.engagementHeightMin + self.settings.engagementHeightMax ); distFromPreferredHeight = abs( self.origin[2] - goalHeight ); if ( distFromPreferredHeight > 100 ) { break; } if( isplayer( self.enemy ) && self.enemy islookingat( self ) ) { if ( math::cointoss() ) { wait RandomFloatRange( 0.1, 0.5 ); } self drop_leader(); // gotta move away fast, we'll pick him up again in a bit break; } } if( isdefined( self.leader ) && isdefined( self.leader.current_pathto_pos ) ) { if( distanceSquared( self.origin, self.leader.current_pathto_pos ) > ( (140 + 25) * (140 + 25) ) ) { //wait RandomFloatRange( 0.1, 0.3 ); break; } } wait 0.3; time -= 0.3; } } function drop_leader() { if( isdefined( self.leader ) ) { ArrayRemoveValue( self.leader.followers, self ); self.leader = undefined; } } function update_leader() { //if self.no_group is set to true, don't find a leader if( isdefined( self.no_group ) && self.no_group == true ) { return; } if( isdefined( self.leader ) ) { return; // already have a leader } if( isdefined( self.followers ) ) { self.followers = array::remove_dead( self.followers, false ); if( self.followers.size > 0 ) // we are a leader { return; } } team_mates = GetAITeamArray( self.team ); foreach( guy in team_mates ) { if( isdefined( guy.archetype ) && guy.archetype == "wasp" ) { if( isdefined( guy.leader ) ) { continue; // guy is already a follower } if( guy == self ) { continue; } if( distanceSquared( self.origin, guy.origin ) > ( (700) * (700) ) ) { continue; // guy too far } if( !isdefined( guy.followers ) ) { guy.followers = []; } if( guy.followers.size >= 3-1 ) { continue; // already full group } // found a leader guy.followers[ guy.followers.size ] = self; self.leader = guy; break; } } } function should_fly_forward( distanceToGoalSq ) { if ( self.always_face_enemy === true ) { return false; } if( distanceToGoalSq < ( (250) * (250) ) ) { return false; } // always face enemy when backing away if( isdefined( self.enemy ) ) { to_goal = VectorNormalize( self.current_pathto_pos - self.origin ); to_enemy = VectorNormalize( self.enemy.origin - self.origin ); dot = VectorDot( to_goal, to_enemy ); if( abs( dot ) > 0.7 ) { return false; } } if( distanceToGoalSq > ( (400) * (400) ) ) { return RandomInt( 100 ) > 25; } return RandomInt( 100 ) > 50; } function state_combat_update( params ) { self endon( "change_state" ); self endon( "death" ); // allow script to set goalpos and whatever else before moving wait .1; stuckCount = 0; for( ;; ) { self SetSpeed( self.settings.defaultMoveSpeed ); self update_leader(); if ( ( isdefined( self.inpain ) && self.inpain ) ) { wait 0.1; } else { if ( self.enable_guard === true ) { self vehicle_ai::evaluate_connections(); } if ( IsDefined( self.enemy ) ) { self SetTurretTargetEnt( self.enemy ); self SetLookAtEnt( self.enemy ); self wait_till_something_happens( RandomFloatRange( 2, 5 ) ); } if ( !IsDefined( self.enemy ) ) { self ClearLookAtEnt(); AIArray = GetAITeamArray( "all" ); foreach ( AI in AIArray ) { self GetPerfectInfo( AI ); } players = GetPlayers( "all" ); foreach ( player in players ) { self GetPerfectInfo( player ); } wait 1; } usePathfinding = true; onNavVolume = IsPointInNavVolume( self.origin, "navvolume_small" ); if ( !onNavVolume ) { // off nav volume, try getting back getbackPoint = undefined; if ( self.aggresive_navvolume_recover === true ) // MP specific { self vehicle_ai::evaluate_connections(); } pointOnNavVolume = self GetClosestPointOnNavVolume( self.origin, 100 ); if ( isdefined( pointOnNavVolume ) ) { if ( SightTracePassed( self.origin, pointOnNavVolume, false, self ) ) { getbackPoint = pointOnNavVolume; } } if ( !isdefined( getbackPoint ) ) { queryResult = PositionQuery_Source_Navigation( self.origin, 0, 200, 100, 2 * self.radius, self ); 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, "Wasp 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, 100 ); if ( isdefined( goalpos ) ) { self.current_pathto_pos = goalpos; usePathfinding = true; } else { self.current_pathto_pos = self.goalpos; usePathfinding = false; } } else if ( IsDefined( self.enemy ) ) { self.current_pathto_pos = GetNextMovePosition_tactical(); usePathfinding = true; } else { self.current_pathto_pos = GetNextMovePosition_wander(); usePathfinding = true; } } if ( IsDefined( self.current_pathto_pos ) ) { distanceToGoalSq = DistanceSquared( self.current_pathto_pos, self.origin ); if ( !onNavVolume || distanceToGoalSq > ( (50 * 1.5) * (50 * 1.5) ) ) { if ( distanceToGoalSq > ( (2000) * (2000) ) ) { self SetSpeed( self.settings.defaultMoveSpeed * 2 ); } if ( self SetVehGoalPos( self.current_pathto_pos, true, usePathfinding ) ) { if ( IsDefined( self.enemy ) ) { self playsound ( "veh_wasp_direction" ); } else { self playsound ( "veh_wasp_vox" ); } if ( should_fly_forward( distanceToGoalSq ) ) { // fly foward if flying far self ClearLookAtEnt(); self notify( "fire_stop" ); // stop shooting self.noshoot = true; } self thread path_update_interrupt(); self vehicle_ai::waittill_pathing_done(); self.noshoot = undefined; } } } } } } function GetNextMovePosition_wander() // no self.enemy { queryMultiplier = 1; queryResult = PositionQuery_Source_Navigation( self.origin, 80, 500 * queryMultiplier, 130, 3 * self.radius * queryMultiplier, self, self.radius * queryMultiplier ); PositionQuery_Filter_DistanceToGoal( queryResult, self ); vehicle_ai::PositionQuery_Filter_OutOfGoalAnchor( queryResult ); self.isOnNav = queryResult.centerOnNav; best_point = undefined; best_score = -999999; foreach ( point in queryResult.data ) { randomScore = randomFloatRange( 0, 100 ); distToOriginScore = point.distToOrigin2D * 0.2; point.score += randomScore + distToOriginScore; /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "distToOrigin" ] = distToOriginScore; #/ point.score += distToOriginScore;; if ( point.score > best_score ) { best_score = point.score; best_point = point; } } self vehicle_ai::PositionQuery_DebugScores( queryResult ); if( !isdefined( best_point ) ) { return undefined; } return best_point.origin; } function GetNextMovePosition_tactical() // has self.enemy { if( !isdefined( self.enemy ) ) { return self GetNextMovePosition_wander(); } // distance based multipliers selfDistToTarget = Distance2D( self.origin, self.enemy.origin ); goodDist = 0.5 * ( self.settings.engagementDistMin + self.settings.engagementDistMax ); closeDist = 1.2 * goodDist; farDist = 3 * goodDist; queryMultiplier = MapFloat( closeDist, farDist, 1, 3, selfDistToTarget ); preferedHeightRange = 35; randomness = 30; avoid_locations = []; avoid_radius = 50; // query if( isalive( self.leader ) && isdefined( self.leader.current_pathto_pos ) ) { query_position = self.leader.current_pathto_pos; queryResult = PositionQuery_Source_Navigation( query_position, 0, 140, 100, 35, self, 25 ); /*foreach( guy in self.leader.followers ) { if( isdefined( guy ) && guy != self ) { if( isdefined( guy.current_pathto_pos ) ) { avoid_locations[ avoid_locations.size ] = guy.current_pathto_pos; } } }*/ } else if ( isalive( self.owner ) && self.enable_guard === true ) { ownerorigin = self GetClosestPointOnNavVolume( self.owner.origin + (0,0,40), 50 ); if ( isdefined( ownerorigin ) ) { queryResult = PositionQuery_Source_Navigation( ownerorigin, 0, 500 * min( queryMultiplier, 1.5 ), 130, 3 * self.radius, self ); if ( isdefined( queryResult ) && isdefined( queryResult.data ) ) { PositionQuery_Filter_Sight( queryResult, self.owner GetEye(), (0, 0, 0), self, 5, self, "visowner" ); PositionQuery_Filter_Sight( queryResult, self.enemy GetEye(), (0, 0, 0), self, 5, self, "visenemy" ); foreach ( point in queryResult.data ) { if ( point.visowner === true ) { /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "visowner" ] = 300; #/ point.score += 300;; } if ( point.visenemy === true ) { /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "visenemy" ] = 300; #/ point.score += 300;; } } } } } else { queryResult = PositionQuery_Source_Navigation( self.origin, 0, 500 * min( queryMultiplier, 2 ), 130, 3 * self.radius * queryMultiplier, self, 2.2 * self.radius * queryMultiplier ); team_mates = GetAITeamArray( self.team ); avoid_radius = 140; foreach( guy in team_mates ) { if( isdefined( guy.archetype ) && guy.archetype == "wasp" ) { // avoid other leaders if we are a leader if( isdefined( guy.followers ) && guy.followers.size > 0 && guy != self ) { if( isdefined( guy.current_pathto_pos ) ) { avoid_locations[ avoid_locations.size ] = guy.current_pathto_pos; } } } } } //If there are no data points to query for points to path to, then just return out undefined if( !isdefined( queryResult ) || !isdefined( queryResult.data ) || queryResult.data.size == 0) { return undefined; } // filter PositionQuery_Filter_DistanceToGoal( queryResult, self ); PositionQuery_Filter_InClaimedLocation( queryResult, self ); self vehicle_ai::PositionQuery_Filter_OutOfGoalAnchor( queryResult ); self vehicle_ai::PositionQuery_Filter_EngagementDist( queryResult, self.enemy, self.settings.engagementDistMin, self.settings.engagementDistMax ); self vehicle_ai::PositionQuery_Filter_EngagementHeight( queryResult, self.enemy, self.settings.engagementHeightMin, self.settings.engagementHeightMax ); // score points best_point = undefined; best_score = -999999; foreach ( point in queryResult.data ) { /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "random" ] = randomFloatRange( 0, randomness ); #/ point.score += randomFloatRange( 0, randomness );; /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "engagementDist" ] = -point.distAwayFromEngagementArea; #/ point.score += -point.distAwayFromEngagementArea;; /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "height" ] = -point.distEngagementHeight * 1.4; #/ point.score += -point.distEngagementHeight * 1.4;; if( point.disttoorigin2d < 120 ) { /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "tooCloseToSelf" ] = (120 - point.disttoorigin2d) * -1.5; #/ point.score += (120 - point.disttoorigin2d) * -1.5;; } foreach( location in avoid_locations ) { if( distanceSquared( point.origin, location ) < ( (avoid_radius) * (avoid_radius) ) ) { /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "tooCloseToOthers" ] = -avoid_radius; #/ point.score += -avoid_radius;; } } if( point.inClaimedLocation ) { /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "inClaimedLocation" ] = -500; #/ point.score += -500;; } if ( point.score > best_score ) { best_score = point.score; best_point = point; } } self vehicle_ai::PositionQuery_DebugScores( queryResult ); if( !isdefined( best_point ) ) { return undefined; } /# if ( ( isdefined( GetDvarInt("hkai_debugPositionQuery") ) && GetDvarInt("hkai_debugPositionQuery") ) ) { recordLine( self.origin, best_point.origin, (0.3,1,0) ); recordLine( self.origin, self.enemy.origin, (1,0,0.4) ); } #/ return best_point.origin; } function drone_callback_damage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, damageFromUnderneath, modelIndex, partName, vSurfaceNormal ) { iDamage = vehicle_ai::shared_callback_damage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, damageFromUnderneath, modelIndex, partName, vSurfaceNormal ); return iDamage; } function drone_AllowFriendlyFireDamage( eInflictor, eAttacker, sMeansOfDeath, weapon ) { if ( isdefined( eAttacker ) && isdefined( eAttacker.archetype ) && isdefined( sMeansOfDeath ) && eAttacker.archetype == "wasp" && sMeansOfDeath == "MOD_EXPLOSIVE" ) { return true; } return false; } // ---------------------------------------------- // State: driving // ---------------------------------------------- function wasp_driving( params ) { self endon( "change_state" ); driver = self GetSeatOccupant( 0 ); if( isPlayer( driver ) ) { clientfield::set( "rocket_wasp_hijacked", 1 ); } if( isPlayer( driver ) && isDefined( self.playerDrivenVersion) ) { self thread wasp_manage_camera_swaps(); } } function wasp_manage_camera_swaps() { self endon ( "death" ); self endon ( "change_state" ); driver = self GetSeatOccupant( 0 ); driver endon( "disconnect" ); cam_low_type = self.vehicletype; cam_high_type = self.playerDrivenVersion; }