2023-04-13 17:30:38 +02:00

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;
}