1089 lines
38 KiB
Plaintext
1089 lines
38 KiB
Plaintext
#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\weapons\_heatseekingmissile;
|
|
|
|
#using scripts\shared\vehicle_shared;
|
|
#using scripts\shared\vehicle_death_shared;
|
|
#using scripts\shared\vehicle_ai_shared;
|
|
|
|
|
|
|
|
|
|
|
|
#using scripts\shared\ai\systems\blackboard;
|
|
#using scripts\shared\ai\blackboard_vehicle;
|
|
|
|
|
|
#using scripts\shared\turret_shared;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#using_animtree( "generic" );
|
|
|
|
#namespace amws;
|
|
|
|
function autoexec __init__sytem__() { system::register("amws",&__init__,undefined,undefined); }
|
|
|
|
function __init__()
|
|
{
|
|
vehicle::add_main_callback( "amws", &amws_initialize );
|
|
}
|
|
|
|
function amws_initialize()
|
|
{
|
|
self UseAnimTree( #animtree );
|
|
|
|
Target_Set( self, ( 0, 0, 0 ) );
|
|
|
|
blackboard::CreateBlackBoardForEntity( self );
|
|
self Blackboard::RegisterVehicleBlackBoardAttributes();
|
|
|
|
self.health = self.healthdefault;
|
|
|
|
self vehicle::friendly_fire_shield();
|
|
|
|
self EnableAimAssist();
|
|
self SetNearGoalNotifyDist( 40 );
|
|
|
|
self.fovcosine = 0; // +/-90 degrees = 180
|
|
self.fovcosinebusy = 0.574; //+/- 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 = 512;
|
|
self SetGoal( self.origin, false, self.goalRadius, self.goalHeight );
|
|
|
|
//self.waittime_before_delete = 40;
|
|
self.delete_on_death = false;
|
|
|
|
self.overrideVehicleDamage = &drone_callback_damage;
|
|
|
|
self thread vehicle_ai::nudge_collision();
|
|
|
|
self.cobra = false;
|
|
self ASMRequestSubstate( "locomotion@movement" );
|
|
|
|
self.variant = "light_weight";
|
|
if( IsSubStr( self.vehicleType, "pamws" ) )
|
|
{
|
|
self.variant = "armored";
|
|
}
|
|
|
|
self vehicle_ai::Cooldown( "cobra_up", 10 );
|
|
|
|
//disable some cybercom abilities
|
|
if( IsDefined( level.vehicle_initializer_cb ) )
|
|
{
|
|
[[level.vehicle_initializer_cb]]( self );
|
|
}
|
|
|
|
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( "driving" ).update_func = &state_driving_update;
|
|
self vehicle_ai::get_state_callbacks( "emped" ).update_func = &state_emped_update;
|
|
self vehicle_ai::get_state_callbacks( "surge" ).update_func = &state_surge_update;
|
|
self vehicle_ai::get_state_callbacks( "surge" ).exit_func = &state_surge_exit;
|
|
|
|
self vehicle_ai::get_state_callbacks( "death" ).update_func = &state_death_update;
|
|
|
|
self vehicle_ai::add_state( "stationary",
|
|
&state_stationary_enter,
|
|
&state_stationary_update,
|
|
&state_stationary_exit );
|
|
|
|
vehicle_ai::add_interrupt_connection( "stationary", "scripted", "enter_scripted" );
|
|
vehicle_ai::add_interrupt_connection( "stationary", "emped", "emped" );
|
|
vehicle_ai::add_interrupt_connection( "stationary", "off", "shut_off" );
|
|
vehicle_ai::add_interrupt_connection( "stationary", "driving", "enter_vehicle" );
|
|
vehicle_ai::add_interrupt_connection( "stationary", "pain", "pain" );
|
|
vehicle_ai::add_interrupt_connection( "stationary", "surge", "surge" );
|
|
|
|
vehicle_ai::add_utility_connection( "stationary", "combat" );
|
|
vehicle_ai::add_utility_connection( "combat", "stationary" );
|
|
|
|
self vehicle_ai::StartInitialState( "combat" );
|
|
}
|
|
|
|
// ----------------------------------------------
|
|
// State: death
|
|
// ----------------------------------------------
|
|
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;
|
|
}
|
|
|
|
if ( death_type === "suicide_crash" )
|
|
{
|
|
self death_suicide_crash( params );
|
|
}
|
|
|
|
self vehicle_ai::defaultstate_death_update( params );
|
|
}
|
|
|
|
function death_suicide_crash( params )
|
|
{
|
|
self endon( "death" );
|
|
|
|
goaldir = AnglesToForward( self.angles );
|
|
goalDist = RandomFloatRange( 300, 400 );
|
|
goalpos = self.origin + goaldir * goalDist;
|
|
self SetMaxSpeedScale( 50 * 17.6 / ( self GetMaxSpeed( true ) ) );
|
|
self SetMaxAccelerationScale( 50 / self GetDefaultAcceleration() );
|
|
self SetSpeed( self.settings.surgespeedmultiplier * self.settings.defaultMoveSpeed );
|
|
self SetVehGoalPos( goalpos, false );
|
|
self util::waittill_any_timeout( 3.5, "near_goal", "veh_collision" );
|
|
|
|
self SetMaxSpeedScale( 0.1 );
|
|
self SetSpeed( 0.1 );
|
|
self vehicle_ai::ClearAllMovement();
|
|
self vehicle_ai::ClearAllLookingAndTargeting();
|
|
self.death_type = "gibbed";
|
|
}
|
|
|
|
// ----------------------------------------------
|
|
// State: driving
|
|
// ----------------------------------------------
|
|
function state_driving_update( params )
|
|
{
|
|
self endon( "change_state" );
|
|
self endon( "death" );
|
|
|
|
driver = self GetSeatOccupant( 0 );
|
|
|
|
if ( isPlayer( driver ) )
|
|
{
|
|
while ( true )
|
|
{
|
|
driver endon( "disconnect" );
|
|
|
|
driver util::waittill_vehicle_move_up_button_pressed();
|
|
|
|
if ( self.cobra === false )
|
|
{
|
|
self cobra_raise();
|
|
}
|
|
else
|
|
{
|
|
self cobra_retract();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// rise up and turn into a turret
|
|
function cobra_raise()
|
|
{
|
|
self.cobra = true;
|
|
if ( isdefined( self.settings.cobra_fx_1 ) && isdefined( self.settings.cobra_tag_1 ) )
|
|
{
|
|
PlayFxOnTag( self.settings.cobra_fx_1, self, self.settings.cobra_tag_1 );
|
|
}
|
|
self ASMRequestSubstate( "cobra@stationary" );
|
|
self vehicle_ai::waittill_asm_complete( "cobra@stationary", 4 );
|
|
self laserOn();
|
|
}
|
|
|
|
// retract turret and be mobile again
|
|
function cobra_retract()
|
|
{
|
|
self.cobra = false;
|
|
self laserOff();
|
|
self notify( "disable_lens_flare" );
|
|
self ASMRequestSubstate( "locomotion@movement" );
|
|
self vehicle_ai::waittill_asm_complete( "locomotion@movement", 4 );
|
|
}
|
|
|
|
// ----------------------------------------------
|
|
// State: emped
|
|
// ----------------------------------------------
|
|
function state_emped_update( params )
|
|
{
|
|
self endon ("death");
|
|
self endon ("change_state");
|
|
|
|
angles = self GetTagAngles( "tag_turret" );
|
|
self SetTurretTargetRelativeAngles( ( 45, angles[1] - self.angles[1], 0 ), 0 );
|
|
angles = self GetTagAngles( "tag_gunner_turret1" );
|
|
self SetTurretTargetRelativeAngles( ( 45, angles[1] - self.angles[1], 0 ), 1 );
|
|
|
|
self vehicle_ai::defaultstate_emped_update( params );
|
|
}
|
|
|
|
// ----------------------------------------------
|
|
// State: surge
|
|
// ----------------------------------------------
|
|
function state_surge_update( params )
|
|
{
|
|
self endon( "change_state" );
|
|
self endon( "death" );
|
|
|
|
self SetMaxSpeedScale( 50 * 17.6 / ( self GetMaxSpeed( true ) ) );
|
|
self SetMaxAccelerationScale( 50 / self GetDefaultAcceleration() );
|
|
|
|
self vehicle_ai::defaultstate_surge_update( params );
|
|
}
|
|
|
|
function state_surge_exit( params )
|
|
{
|
|
self SetMaxSpeedScale( 0.1 );
|
|
self SetSpeed( 0.1 );
|
|
self vehicle_ai::ClearAllMovement();
|
|
self vehicle_ai::ClearAllLookingAndTargeting();
|
|
}
|
|
|
|
// ----------------------------------------------
|
|
// State: stationary
|
|
// ----------------------------------------------
|
|
function state_stationary_enter( params )
|
|
{
|
|
vehicle_ai::ClearAllLookingAndTargeting();
|
|
vehicle_ai::ClearAllMovement();
|
|
self SetBrake( 1 );
|
|
//self SetVehWeapon( GetWeapon( WEAPON_STATIONARY ) );
|
|
}
|
|
|
|
function state_stationary_update( params )
|
|
{
|
|
self endon( "death" );
|
|
self endon( "change_state" );
|
|
|
|
self notify( "stop_rocket_firing_thread" );
|
|
vehicle_ai::ClearAllLookingAndTargeting();
|
|
vehicle_ai::ClearAllMovement();
|
|
|
|
wait 1;
|
|
|
|
self cobra_raise();
|
|
|
|
minTime = 6; // don't retract before this
|
|
maxTime = 12; // retract after this
|
|
transformWhenEnemyClose = ( RandomInt( 100 ) < 25 );
|
|
losePatientTime = 3 + RandomFloat( 2 ); // retract if don't see enemy for this long
|
|
|
|
startTime = GetTime();
|
|
vehicle_ai::Cooldown( "rocket", 2 );
|
|
|
|
evade_now = false;
|
|
|
|
while ( true )
|
|
{
|
|
// /# self vehicle_ai::UpdatePersonalThreatBias_Bots( 800, 2.0 ); #/ // give bots more threat (for testing purposes -- DO NOT CHECK IN active)
|
|
|
|
evade_now = ( ( ( self.settings.evade_enemies_locked_on_me === true ) && self.locked_on ) || ( ( self.settings.evade_enemies_locking_on_me === true ) && self.locking_on ) );
|
|
|
|
if ( vehicle_ai::TimeSince( startTime ) > maxTime || evade_now )
|
|
{
|
|
break;
|
|
}
|
|
|
|
if( isdefined( self.enemy ) )
|
|
{
|
|
distSqr = DistanceSquared( self.enemy.origin, self.origin );
|
|
|
|
// check get out of stationary state
|
|
if ( vehicle_ai::TimeSince( startTime ) > minTime )
|
|
{
|
|
// enemy is too close, emergency transform
|
|
if ( transformWhenEnemyClose && distSqr < ( (200) * (200) ) )
|
|
{
|
|
break;
|
|
}
|
|
|
|
// haven't seen enemy for too long, transform
|
|
if ( !self VehSeenRecently( self.enemy, losePatientTime ) )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( self VehCanSee( self.enemy ) )
|
|
{
|
|
if( distSqr < ( (self.settings.engagementDistMax * 3) * (self.settings.engagementDistMax * 3) ) )
|
|
{
|
|
self SetTurretTargetEnt( self.enemy, (0,0,-5) );
|
|
self SetGunnerTargetEnt( self.enemy, (0,0,-5), 0 );
|
|
|
|
if ( vehicle_ai::IsCooldownReady( "rocket" ) && self.turretontarget && self.gib_rocket !== true )
|
|
{
|
|
self thread FireRocketLauncher( self.enemy );
|
|
vehicle_ai::Cooldown( "rocket", self.settings.rocketcooldown );
|
|
}
|
|
|
|
weapon = self SeatGetWeapon( 1 );
|
|
if ( weapon.name=="none" )
|
|
idx = 0;
|
|
else
|
|
idx = 1;
|
|
|
|
self vehicle_ai::fire_for_time( 1, idx, self.enemy, 0.5 );
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
wait 0.1;
|
|
}
|
|
|
|
self notify( "stop_rocket_firing_thread" );
|
|
vehicle_ai::ClearAllLookingAndTargeting();
|
|
vehicle_ai::ClearAllMovement();
|
|
|
|
if ( evade_now )
|
|
{
|
|
self wait_evasion_reaction_time();
|
|
}
|
|
else
|
|
{
|
|
self state_stationary_update_wait( 0.5 );
|
|
}
|
|
|
|
self cobra_retract();
|
|
|
|
self vehicle_ai::evaluate_connections();
|
|
}
|
|
|
|
function state_stationary_update_wait( wait_time ) // self == sentient
|
|
{
|
|
waittill_weapon_lock_or_timeout( wait_time );
|
|
}
|
|
|
|
function state_stationary_exit( params )
|
|
{
|
|
//self SetVehWeapon( GetWeapon( WEAPON_REGULAR ) );
|
|
vehicle_ai::ClearAllLookingAndTargeting();
|
|
vehicle_ai::ClearAllMovement();
|
|
self SetBrake( 0 );
|
|
self vehicle_ai::Cooldown( "cobra_up", 10 );
|
|
}
|
|
|
|
// ----------------------------------------------
|
|
// State: combat
|
|
// ----------------------------------------------
|
|
function state_combat_enter( params )
|
|
{
|
|
self thread turretFireUpdate();
|
|
}
|
|
|
|
function is_ai_using_minigun()
|
|
{
|
|
return (isdefined(self.settings.ai_uses_minigun)?self.settings.ai_uses_minigun:true);
|
|
}
|
|
|
|
function turretFireUpdate()
|
|
{
|
|
weapon = self SeatGetWeapon( 1 );
|
|
if ( weapon.name=="none" )
|
|
return;
|
|
|
|
self endon( "death" );
|
|
self endon( "change_state" );
|
|
|
|
self SetOnTargetAngle( 7 );
|
|
self SetOnTargetAngle( 7, 0 );
|
|
|
|
|
|
|
|
|
|
while( 1 )
|
|
{
|
|
if ( self.avoid_shooting_owner === true && isdefined( self.owner ) )
|
|
{
|
|
if ( self vehicle_ai::owner_in_line_of_fire() )
|
|
{
|
|
wait 0.5;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if( isdefined( self.enemy ) && self VehCanSee( self.enemy ) && DistanceSquared( self.enemy.origin, self.origin ) < ( (self.settings.engagementDistMax * 3) * (self.settings.engagementDistMax * 3) ) )
|
|
{
|
|
self SetGunnerTargetEnt( self.enemy, (0,0,0), 0 );
|
|
|
|
if ( self is_ai_using_minigun() )
|
|
{
|
|
self SetTurretSpinning( true );
|
|
}
|
|
|
|
wait 0.05; // allow gunner1ontarget to update before checking
|
|
|
|
if ( !self.gunner1ontarget )
|
|
{
|
|
wait 0.5;
|
|
}
|
|
|
|
if ( self.gunner1ontarget )
|
|
{
|
|
if( isdefined( self.enemy ) && self VehCanSee( self.enemy ) )
|
|
{
|
|
self vehicle_ai::fire_for_time( RandomFloatRange( self.settings.burstFireDurationMin, self.settings.burstFireDurationMax ), 1, self.enemy );
|
|
}
|
|
|
|
if ( self is_ai_using_minigun() )
|
|
{
|
|
self SetTurretSpinning( false );
|
|
}
|
|
|
|
if( isdefined( self.enemy ) && IsAI( self.enemy ) )
|
|
{
|
|
wait( RandomFloatRange( self.settings.burstFireAIDelayMin, self.settings.burstFireAIDelayMax ) );
|
|
}
|
|
else
|
|
{
|
|
wait( RandomFloatRange( self.settings.burstFireDelayMin, self.settings.burstFireDelayMax ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
wait 0.5;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
wait 0.4;
|
|
}
|
|
}
|
|
}
|
|
|
|
function state_combat_update( params )
|
|
{
|
|
self endon( "change_state" );
|
|
self endon( "death" );
|
|
|
|
lastTimeChangePosition = 0;
|
|
self.shouldGotoNewPosition = false;
|
|
self.lastTimeTargetInSight = 0;
|
|
|
|
heatseekingmissile::InitLockField( self );
|
|
self.lock_evading = 0; // for use with self.locked_on and self.locking_on
|
|
|
|
for( ;; )
|
|
{
|
|
if ( self.lock_evading == 0 )
|
|
{
|
|
self SetSpeed( self.settings.defaultMoveSpeed );
|
|
self SetAcceleration( (isdefined(self.settings.default_move_acceleration)?self.settings.default_move_acceleration:10.0) );
|
|
}
|
|
|
|
if ( RandomInt( 100 ) < 3 && vehicle_ai::IsCooldownReady( "cobra_up" ) && ( self.lock_evading == 0 ))
|
|
{
|
|
if ( isdefined( self.enemy ) && DistanceSquared( self.enemy.origin, self.origin ) > ( (200) * (200) ) )
|
|
{
|
|
if( DistanceSquared( self.enemy.origin, self.origin ) < ( (self.settings.engagementDistMax * 2) * (self.settings.engagementDistMax * 2) ) )
|
|
{
|
|
self vehicle_ai::evaluate_connections();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// /# self vehicle_ai::UpdatePersonalThreatBias_Bots( 800, 2.0 ); #/ // give bots more threat (for testing purposes -- DO NOT CHECK IN active)
|
|
|
|
|
|
// evaluate lock threats to engage them
|
|
if ( self.settings.engage_enemies_locked_on_me === true && self.locked_on )
|
|
{
|
|
self vehicle_ai::UpdatePersonalThreatBias_AttackerLockedOnToMe( (isdefined(self.settings.enemies_locked_on_me_threat_bias)?self.settings.enemies_locked_on_me_threat_bias:5000), (isdefined(self.settings.enemies_locked_on_me_threat_bias_duration)?self.settings.enemies_locked_on_me_threat_bias_duration:1.0) );
|
|
self.shouldGotoNewPosition = true;
|
|
}
|
|
else if ( self.settings.engage_enemies_locking_on_me === true && self.locking_on )
|
|
{
|
|
self vehicle_ai::UpdatePersonalThreatBias_AttackerLockingOnToMe( (isdefined(self.settings.enemies_locking_on_me_threat_bias)?self.settings.enemies_locking_on_me_threat_bias:2000), (isdefined(self.settings.enemies_locking_on_me_threat_bias_duration)?self.settings.enemies_locking_on_me_threat_bias_duration:1.0) );
|
|
self.shouldGotoNewPosition = true;
|
|
}
|
|
|
|
|
|
// evalate lock threats for evading
|
|
self.lock_evading = 0;
|
|
if ( self.settings.evade_enemies_locked_on_me === true )
|
|
{
|
|
self.lock_evading |= self.locked_on;
|
|
}
|
|
if ( self.settings.evade_enemies_locking_on_me === true )
|
|
{
|
|
self.lock_evading |= self.locking_on;
|
|
self.lock_evading |= self.locking_on_hacking;
|
|
}
|
|
|
|
|
|
if ( ( isdefined( self.inpain ) && self.inpain ) )
|
|
{
|
|
wait 0.1;
|
|
}
|
|
else if ( !IsDefined( self.enemy ) )
|
|
{
|
|
should_slow_down_at_goal = true;
|
|
|
|
if ( self.lock_evading )
|
|
{
|
|
self.current_pathto_pos = GetNextMovePosition_evasive( self.lock_evading );
|
|
should_slow_down_at_goal = false;
|
|
}
|
|
else
|
|
{
|
|
self.current_pathto_pos = GetNextMovePosition_wander();
|
|
}
|
|
|
|
if ( IsDefined( self.current_pathto_pos ) )
|
|
{
|
|
if ( self SetVehGoalPos( self.current_pathto_pos, should_slow_down_at_goal, true ) )
|
|
{
|
|
self thread path_update_interrupt_by_attacker();
|
|
self thread path_update_interrupt();
|
|
self vehicle_ai::waittill_pathing_done();
|
|
self notify( "amws_end_interrupt_watch" );
|
|
self playsound ("veh_amws_scan");
|
|
}
|
|
}
|
|
|
|
self state_combat_update_wait( 0.5 );
|
|
}
|
|
else
|
|
{
|
|
self SetTurretTargetEnt( self.enemy );
|
|
|
|
if ( self vehCanSee( self.enemy ) )
|
|
{
|
|
self.lastTimeTargetInSight = GetTime();
|
|
}
|
|
|
|
if ( self.shouldGotoNewPosition == false )
|
|
{
|
|
if ( GetTime() > lastTimeChangePosition + 1000 * 1.0 )
|
|
{
|
|
self.shouldGotoNewPosition = true;
|
|
}
|
|
else if ( GetTime() > self.lastTimeTargetInSight + 1000 * 0.5 )
|
|
{
|
|
self.shouldGotoNewPosition = true;
|
|
}
|
|
}
|
|
|
|
if ( self.shouldGotoNewPosition )
|
|
{
|
|
should_slow_down_at_goal = true;
|
|
|
|
if ( self.lock_evading )
|
|
{
|
|
self.current_pathto_pos = GetNextMovePosition_evasive( self.lock_evading );
|
|
should_slow_down_at_goal = false;
|
|
}
|
|
else
|
|
{
|
|
self.current_pathto_pos = GetNextMovePosition_tactical( self.enemy );
|
|
}
|
|
|
|
|
|
if ( IsDefined( self.current_pathto_pos ) )
|
|
{
|
|
if ( self SetVehGoalPos( self.current_pathto_pos, should_slow_down_at_goal, true ) )
|
|
{
|
|
self thread path_update_interrupt_by_attacker();
|
|
self thread path_update_interrupt();
|
|
self vehicle_ai::waittill_pathing_done();
|
|
self notify( "amws_end_interrupt_watch" );
|
|
}
|
|
|
|
if ( isdefined( self.enemy ) && vehicle_ai::IsCooldownReady( "rocket", 0.5 ) && self VehCanSee( self.enemy ) && self.gib_rocket !== true )
|
|
{
|
|
self thread aim_and_fire_rocket_launcher( 0.4 ); // call as thread to prevent blocking movement while aiming rockets
|
|
}
|
|
|
|
lastTimeChangePosition = GetTime();
|
|
self.shouldGotoNewPosition = false;
|
|
}
|
|
}
|
|
|
|
self state_combat_update_wait( 0.5 );
|
|
}
|
|
}
|
|
}
|
|
|
|
function aim_and_fire_rocket_launcher( aim_time ) // self == amws
|
|
{
|
|
self endon( "death" );
|
|
self endon( "change_state" );
|
|
self notify( "stop_rocket_firing_thread" );
|
|
self endon( "stop_rocket_firing_thread" );
|
|
|
|
if ( !self.turretontarget )
|
|
{
|
|
wait aim_time;
|
|
}
|
|
|
|
if ( isdefined( self.enemy ) && self.turretontarget )
|
|
{
|
|
vehicle_ai::Cooldown( "rocket", self.settings.rocketcooldown );
|
|
self thread FireRocketLauncher( self.enemy );
|
|
}
|
|
}
|
|
|
|
function state_combat_update_wait( wait_time ) // self == sentient
|
|
{
|
|
self waittill_weapon_lock_or_timeout( wait_time );
|
|
}
|
|
|
|
function waittill_weapon_lock_or_timeout( wait_time ) // self == sentient
|
|
{
|
|
if ( self.lock_evade_now === true )
|
|
{
|
|
perform_evasion_reaction_wait = true;
|
|
}
|
|
else
|
|
{
|
|
locked_on_notify = undefined;
|
|
locking_on_notify = undefined;
|
|
|
|
reacting_to_locks = ( self.settings.evade_enemies_locked_on_me === true ) || ( self.settings.engage_enemies_locked_on_me === true );
|
|
reacting_to_locking = ( self.settings.evade_enemies_locking_on_me === true ) || ( self.settings.engage_enemies_locking_on_me === true );
|
|
|
|
previous_locked_on_to_me = self.locked_on;
|
|
previous_locking_on_to_me = self.locking_on;
|
|
|
|
if ( reacting_to_locks )
|
|
{
|
|
locked_on_notify = "missle_lock";
|
|
}
|
|
|
|
if ( reacting_to_locking )
|
|
{
|
|
locking_on_notify = "locking on";
|
|
}
|
|
|
|
self util::waittill_any_timeout( wait_time, "damage", locking_on_notify, locked_on_notify );
|
|
|
|
locked_on_to_me_just_changed = previous_locked_on_to_me != self.locked_on && self.locked_on;
|
|
locking_on_to_me_just_changed = previous_locking_on_to_me != self.locking_on && self.locking_on;
|
|
|
|
perform_evasion_reaction_wait = ( ( reacting_to_locks && locked_on_to_me_just_changed ) || ( reacting_to_locking && locking_on_to_me_just_changed ) );
|
|
}
|
|
|
|
// perform evasion reaction time if need be
|
|
if ( perform_evasion_reaction_wait )
|
|
self wait_evasion_reaction_time();
|
|
}
|
|
|
|
function wait_evasion_reaction_time() // self == vehicle with setting
|
|
{
|
|
wait RandomFloatRange( (isdefined(self.settings.enemy_evasion_reaction_time_min)?self.settings.enemy_evasion_reaction_time_min:0.1), (isdefined(self.settings.enemy_evasion_reaction_time_max)?self.settings.enemy_evasion_reaction_time_max:0.2) );
|
|
}
|
|
|
|
function FireRocketLauncher( enemy )
|
|
{
|
|
self endon( "death" );
|
|
self endon( "change_state" );
|
|
self notify( "stop_rocket_firing_thread" );
|
|
self endon( "stop_rocket_firing_thread" );
|
|
|
|
if ( isdefined( enemy ) )
|
|
{
|
|
self SetTurretTargetEnt( enemy );
|
|
self util::waittill_any_timeout( 1, "turret_on_target" );
|
|
|
|
if ( self.variant == "armored" )
|
|
{
|
|
vehicle_ai::fire_for_rounds( 1, 0, enemy );
|
|
}
|
|
else
|
|
{
|
|
vehicle_ai::fire_for_rounds( 2, 0, enemy );
|
|
}
|
|
}
|
|
}
|
|
|
|
function GetNextMovePosition_wander() // no self.enemy
|
|
{
|
|
if( self.goalforced )
|
|
{
|
|
return self.goalpos;
|
|
}
|
|
|
|
queryMultiplier = 1.5;
|
|
|
|
queryResult = PositionQuery_Source_Navigation( self.origin, 80, 500 * queryMultiplier, 0.5 * 500, 3 * self.radius * queryMultiplier, self, self.radius * queryMultiplier );
|
|
if ( queryResult.data.size == 0 )
|
|
{
|
|
// try to move a little bit away since we couldn't find any points in the first position query
|
|
queryResult = PositionQuery_Source_Navigation( self.origin, 36, 120, 240, self.radius, self );
|
|
}
|
|
|
|
PositionQuery_Filter_DistanceToGoal( queryResult, self );
|
|
vehicle_ai::PositionQuery_Filter_OutOfGoalAnchor( queryResult );
|
|
PositionQuery_Filter_InClaimedLocation( queryResult, self );
|
|
|
|
best_point = undefined;
|
|
best_score = -999999;
|
|
|
|
foreach ( point in queryResult.data )
|
|
{
|
|
randomScore = randomFloatRange( 0, 100 );
|
|
distToOriginScore = point.distToOrigin2D * 0.2;
|
|
|
|
if( point.inClaimedLocation )
|
|
{
|
|
point.score -= 500;
|
|
}
|
|
|
|
point.score += randomScore + distToOriginScore;
|
|
|
|
if ( point.score > best_score )
|
|
{
|
|
best_score = point.score;
|
|
best_point = point;
|
|
}
|
|
}
|
|
|
|
/# self.debug_ai_move_to_points_considered = queryResult.data; #/
|
|
|
|
if( !isdefined( best_point ) )
|
|
{
|
|
/# self.debug_ai_movement_type = "wander ( 0 / " + queryResult.data.size + " )"; #/
|
|
/# self.debug_ai_move_to_point = undefined; #/
|
|
return undefined;
|
|
}
|
|
|
|
/# self.debug_ai_movement_type = "wander - " + queryResult.data.size; #/
|
|
/# self.debug_ai_move_to_point = best_point.origin; #/
|
|
return best_point.origin;
|
|
}
|
|
|
|
function GetNextMovePosition_evasive( client_flags )
|
|
{
|
|
assert( isdefined( client_flags ) );
|
|
|
|
self SetSpeed( self.settings.defaultMoveSpeed * (isdefined(self.settings.lock_evade_speed_boost)?self.settings.lock_evade_speed_boost:2.0) );
|
|
self SetAcceleration( (isdefined(self.settings.default_move_acceleration)?self.settings.default_move_acceleration:10.0) * (isdefined(self.settings.lock_evade_acceleration_boost)?self.settings.lock_evade_acceleration_boost:2.0) );
|
|
|
|
// query points to where to evade
|
|
queryResult = PositionQuery_Source_Navigation( self.origin,
|
|
(isdefined(self.settings.lock_evade_dist_min)?self.settings.lock_evade_dist_min:120),
|
|
(isdefined(self.settings.lock_evade_dist_max)?self.settings.lock_evade_dist_max:360),
|
|
math::clamp( (isdefined(self.settings.lock_evade_dist_half_height)?self.settings.lock_evade_dist_half_height:( 500 * 0.5 )), 0.1, 99000 ),
|
|
(isdefined(self.settings.lock_evade_point_spacing_factor)?self.settings.lock_evade_point_spacing_factor:( 1.5 )) * self.radius,
|
|
self );
|
|
|
|
// initial filter
|
|
PositionQuery_Filter_InClaimedLocation( queryResult, self );
|
|
//note: goalpos related filters intentionally left out as the amws is trying to evade getting killed by a rocket
|
|
|
|
// process claimed location score
|
|
foreach ( point in queryResult.data )
|
|
{
|
|
if( point.inClaimedLocation )
|
|
{
|
|
/# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "inClaimedLocation" ] = -500; #/ point.score += -500;;
|
|
}
|
|
}
|
|
|
|
remaining_lock_threats_to_evaluate = 3; // used as iteration cap
|
|
|
|
remaining_flags_to_process = client_flags;
|
|
for ( i = 0; remaining_flags_to_process && remaining_lock_threats_to_evaluate > 0 && i < level.players.size; i++ )
|
|
{
|
|
attacker = level.players[ i ];
|
|
if ( isdefined( attacker ) )
|
|
{
|
|
client_flag = ( 1 << attacker getEntityNumber() );
|
|
if ( client_flag & remaining_flags_to_process )
|
|
{
|
|
// filter directness relative to lock threat
|
|
PositionQuery_Filter_Directness( queryResult, self.origin, attacker.origin );
|
|
|
|
// update score based on directness
|
|
foreach ( point in queryResult.data )
|
|
{
|
|
abs_directness = Abs( point.directness );
|
|
if( abs_directness < 0.2 )
|
|
{
|
|
/# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "evading_directness" ] = 200; #/ point.score += 200;;
|
|
}
|
|
else if ( abs_directness > (isdefined(self.settings.lock_evade_enemy_line_of_sight_directness)?self.settings.lock_evade_enemy_line_of_sight_directness:0.9) )
|
|
{
|
|
/# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "evading_directness_line_of_sight" ] = -101; #/ point.score += -101;;
|
|
}
|
|
}
|
|
|
|
// update for next threat
|
|
remaining_flags_to_process &= ~client_flag;
|
|
remaining_lock_threats_to_evaluate--;
|
|
}
|
|
}
|
|
}
|
|
|
|
// give a point "ahead" of the amws more points
|
|
PositionQuery_Filter_Directness( queryResult, self.origin, self.origin + ( AnglesToForward( self.angles ) * 360 ) );
|
|
foreach ( point in queryResult.data )
|
|
{
|
|
if( point.directness > 0.5 )
|
|
{
|
|
/# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "prefer forward motion" ] = 105; #/ point.score += 105;;
|
|
}
|
|
}
|
|
|
|
// score points
|
|
best_point = undefined;
|
|
best_score = -999999;
|
|
|
|
foreach ( point in queryResult.data )
|
|
{
|
|
if ( point.score > best_score )
|
|
{
|
|
best_score = point.score;
|
|
best_point = point;
|
|
}
|
|
}
|
|
|
|
self.lock_evade_now = false;
|
|
|
|
self vehicle_ai::PositionQuery_DebugScores( queryResult );
|
|
|
|
/# self.debug_ai_move_to_points_considered = queryResult.data; #/
|
|
|
|
if( !isdefined( best_point ) )
|
|
{
|
|
/# self.debug_ai_movement_type = "evasive ( 0 / " + queryResult.data.size + " )"; #/
|
|
/# self.debug_ai_move_to_point = undefined; #/
|
|
return undefined;
|
|
}
|
|
|
|
/# self.debug_ai_movement_type = "evasive - " + queryResult.data.size; #/
|
|
/# self.debug_ai_move_to_point = best_point.origin; #/
|
|
return best_point.origin;
|
|
}
|
|
|
|
function GetNextMovePosition_tactical( enemy )
|
|
{
|
|
if( self.goalforced )
|
|
{
|
|
return self.goalpos;
|
|
}
|
|
|
|
// distance based multipliers
|
|
selfDistToTarget = Distance2D( self.origin, enemy.origin );
|
|
|
|
goodDist = 0.5 * ( self.settings.engagementDistMin + self.settings.engagementDistMax );
|
|
|
|
tooCloseDist = ( 0.8 * 0.5 * ( self.settings.engagementDistMin + self.settings.engagementDistMax ) );
|
|
closeDist = 1.2 * goodDist;
|
|
farDist = 3 * goodDist;
|
|
|
|
queryMultiplier = MapFloat( closeDist, farDist, 1, 3, selfDistToTarget );
|
|
preferedDirectness = 0;
|
|
if ( selfDistToTarget > goodDist )
|
|
{
|
|
preferedDirectness = MapFloat( closeDist, farDist, 0, 1, selfDistToTarget );
|
|
}
|
|
else
|
|
{
|
|
preferedDirectness = MapFloat( tooCloseDist * 0.4, tooCloseDist, -1, -0.6, selfDistToTarget );
|
|
}
|
|
|
|
preferedDistAwayFromOrigin = 300;
|
|
randomness = 30;
|
|
|
|
// query
|
|
queryResult = PositionQuery_Source_Navigation( self.origin, 80, 500 * queryMultiplier, 0.5 * 500, 2 * self.radius * queryMultiplier, self, 1 * self.radius * queryMultiplier );
|
|
|
|
// filter
|
|
PositionQuery_Filter_Directness( queryResult, self.origin, enemy.origin );
|
|
PositionQuery_Filter_DistanceToGoal( queryResult, self );
|
|
vehicle_ai::PositionQuery_Filter_OutOfGoalAnchor( queryResult );
|
|
PositionQuery_Filter_InClaimedLocation( queryResult, self );
|
|
vehicle_ai::PositionQuery_Filter_EngagementDist( queryResult, enemy, self.settings.engagementDistMin, self.settings.engagementDistMax );
|
|
|
|
if ( isdefined( self.avoidEntities ) && isdefined( self.avoidEntitiesDistance ) )
|
|
{
|
|
vehicle_ai::PositionQuery_Filter_DistAwayFromTarget( queryResult, self.avoidEntities, self.avoidEntitiesDistance, -500 );
|
|
}
|
|
|
|
// score points
|
|
best_point = undefined;
|
|
best_score = -999999;
|
|
|
|
foreach ( point in queryResult.data )
|
|
{
|
|
// directness
|
|
diffToPreferedDirectness = abs( point.directness - preferedDirectness );
|
|
directnessScore = MapFloat( 0, 1, 100, 0, diffToPreferedDirectness );
|
|
if ( diffToPreferedDirectness > 0.2 )
|
|
{
|
|
directnessScore -= 200;
|
|
}
|
|
/# 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;;
|
|
|
|
// distance from origin
|
|
/# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "distToOrigin" ] = MapFloat( 0, preferedDistAwayFromOrigin, 0, 100, point.distToOrigin2D ); #/ point.score += MapFloat( 0, preferedDistAwayFromOrigin, 0, 100, point.distToOrigin2D );;
|
|
|
|
// distance to target
|
|
targetDistScore = 0;
|
|
if ( point.targetDist < tooCloseDist )
|
|
{
|
|
targetDistScore -= 200;
|
|
}
|
|
|
|
if( point.inClaimedLocation )
|
|
{
|
|
/# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "inClaimedLocation" ] = -500; #/ point.score += -500;;
|
|
}
|
|
|
|
/# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "distToTarget" ] = targetDistScore; #/ point.score += targetDistScore;;
|
|
|
|
/# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "random" ] = randomFloatRange( 0, randomness ); #/ point.score += randomFloatRange( 0, randomness );;
|
|
|
|
if ( point.score > best_score )
|
|
{
|
|
best_score = point.score;
|
|
best_point = point;
|
|
}
|
|
}
|
|
|
|
self vehicle_ai::PositionQuery_DebugScores( queryResult );
|
|
|
|
/# self.debug_ai_move_to_points_considered = queryResult.data; #/
|
|
|
|
if( !isdefined( best_point ) )
|
|
{
|
|
/# self.debug_ai_movement_type = "tactical ( 0 / " + queryResult.data.size + " )"; #/
|
|
/# self.debug_ai_move_to_point = undefined; #/
|
|
return undefined;
|
|
}
|
|
|
|
/#
|
|
if ( ( isdefined( GetDvarInt("hkai_debugPositionQuery") ) && GetDvarInt("hkai_debugPositionQuery") ) )
|
|
{
|
|
recordLine( self.origin, best_point.origin, (0.3,1,0) );
|
|
recordLine( self.origin, enemy.origin, (1,0,0.4) );
|
|
}
|
|
#/
|
|
|
|
/# self.debug_ai_movement_type = "tactical - " + queryResult.data.size; #/
|
|
/# self.debug_ai_move_to_point = best_point.origin; #/
|
|
|
|
return best_point.origin;
|
|
}
|
|
|
|
function path_update_interrupt_by_attacker() //self == vehicle
|
|
{
|
|
self endon( "death" );
|
|
self endon( "change_state" );
|
|
self endon( "near_goal" );
|
|
self endon( "reached_end_node" );
|
|
self endon( "amws_end_interrupt_watch" );
|
|
|
|
self util::waittill_any( "locking on", "missile_lock", "damage" );
|
|
|
|
if ( self.locked_on || self.locking_on )
|
|
{
|
|
/# self.debug_ai_move_to_points_considered = []; #/
|
|
/# self.debug_ai_movement_type = "interrupted"; #/
|
|
/# self.debug_ai_move_to_point = undefined; #/
|
|
|
|
self ClearVehGoalPos(); // do this to prevent the "stopping" of the vehicle
|
|
|
|
self.lock_evade_now = true;
|
|
}
|
|
|
|
self notify( "near_goal" );
|
|
}
|
|
|
|
function path_update_interrupt()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "change_state" );
|
|
self endon( "near_goal" );
|
|
self endon( "reached_end_node" );
|
|
self endon( "amws_end_interrupt_watch" );
|
|
|
|
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 VehCanSee( self.enemy ) && distance2dSquared( self.origin, self.enemy.origin ) < ( (( 0.8 * 0.5 * ( self.settings.engagementDistMin + self.settings.engagementDistMax ) )) * (( 0.8 * 0.5 * ( self.settings.engagementDistMin + self.settings.engagementDistMax ) )) ) )
|
|
{
|
|
self notify( "near_goal" );
|
|
}
|
|
|
|
if ( vehicle_ai::IsCooldownReady( "rocket" ) && vehicle_ai::IsCooldownReady( "rocket_launcher_check" ) )
|
|
{
|
|
vehicle_ai::Cooldown( "rocket_launcher_check", 2.5 );
|
|
self notify( "near_goal" );
|
|
}
|
|
}
|
|
|
|
wait 0.2;
|
|
}
|
|
}
|
|
|
|
function gib( attacker )
|
|
{
|
|
if ( self.gibbed !== true )
|
|
{
|
|
self vehicle::do_gib_dynents();
|
|
self.gibbed = true;
|
|
self.death_type = "suicide_crash";
|
|
self kill( self.origin + (0,0,10), attacker );
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|