1558 lines
48 KiB
Plaintext
1558 lines
48 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\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;
|
|
}
|