768 lines
31 KiB
Plaintext
768 lines
31 KiB
Plaintext
#using scripts\codescripts\struct;
|
|
|
|
#using scripts\shared\math_shared;
|
|
#using scripts\shared\statemachine_shared;
|
|
#using scripts\shared\callbacks_shared;
|
|
#using scripts\shared\clientfield_shared;
|
|
#using scripts\shared\system_shared;
|
|
#using scripts\shared\array_shared;
|
|
#using scripts\shared\util_shared;
|
|
#using scripts\shared\ai_shared;
|
|
|
|
#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\ai\systems\ai_interface;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#using scripts\shared\ai\zombie_utility;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//reduce these defines to have the parasite make more movements close together
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#namespace parasite;
|
|
|
|
function autoexec __init__sytem__() { system::register("parasite",&__init__,undefined,undefined); }
|
|
|
|
#using_animtree( "generic" );
|
|
|
|
function __init__()
|
|
{
|
|
vehicle::add_main_callback( "parasite", ¶site_initialize );
|
|
|
|
clientfield::register( "vehicle", "parasite_tell_fx", 1, 1, "int" );
|
|
clientfield::register( "vehicle", "parasite_secondary_deathfx", 1, 1, "int" );
|
|
clientfield::register( "toplayer", "parasite_damage", 1, 1, "counter" );
|
|
|
|
callback::on_spawned( ¶site_damage );
|
|
|
|
ai::RegisterMatchedInterface(
|
|
"parasite",
|
|
"firing_rate",
|
|
"slow",
|
|
array( "slow", "medium", "fast" ) );
|
|
}
|
|
|
|
function parasite_damage()
|
|
{
|
|
self notify( "parasite_damage_thread" );
|
|
self endon( "parasite_damage_thread" );
|
|
|
|
self endon( "death" );
|
|
|
|
while ( true )
|
|
{
|
|
self waittill( "damage", n_ammount, e_attacker );
|
|
|
|
if ( IsDefined( e_attacker ) && ( isdefined( e_attacker.is_parasite ) && e_attacker.is_parasite ) && !( isdefined( e_attacker.squelch_damage_overlay ) && e_attacker.squelch_damage_overlay ) )
|
|
{
|
|
self clientfield::increment_to_player( "parasite_damage" );
|
|
}
|
|
}
|
|
}
|
|
|
|
function private is_target_valid( target )
|
|
{
|
|
if( !IsDefined( target ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( !IsAlive( target ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( IsPlayer( target ) && target.sessionstate == "spectator" )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( IsPlayer( target ) && target.sessionstate == "intermission" )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( ( isdefined( target.ignoreme ) && target.ignoreme ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( target IsNoTarget() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( IsDefined( self.is_target_valid_cb ) )
|
|
{
|
|
return self [[ self.is_target_valid_cb ]]( target );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function get_parasite_enemy()
|
|
{
|
|
parasite_targets = GetPlayers();
|
|
least_hunted = parasite_targets[0];
|
|
for( i = 0; i < parasite_targets.size; i++ )
|
|
{
|
|
if ( !isdefined( parasite_targets[i].hunted_by ) )
|
|
{
|
|
parasite_targets[i].hunted_by = 0;
|
|
}
|
|
|
|
if( !is_target_valid( parasite_targets[i] ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( !is_target_valid( least_hunted ) )
|
|
{
|
|
least_hunted = parasite_targets[i];
|
|
}
|
|
|
|
if( parasite_targets[i].hunted_by < least_hunted.hunted_by )
|
|
{
|
|
least_hunted = parasite_targets[i];
|
|
}
|
|
|
|
}
|
|
// do not return the default first player if he is invalid
|
|
if( !is_target_valid( least_hunted ) )
|
|
{
|
|
return undefined;
|
|
}
|
|
else
|
|
{
|
|
return least_hunted;
|
|
}
|
|
}
|
|
|
|
function set_parasite_enemy( enemy )
|
|
{
|
|
if( !is_target_valid( enemy ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( IsDefined( self.parasiteEnemy ) )
|
|
{
|
|
if( !IsDefined( self.parasiteEnemy.hunted_by ) )
|
|
{
|
|
self.parasiteEnemy.hunted_by = 0;
|
|
}
|
|
if( self.parasiteEnemy.hunted_by > 0 )
|
|
{
|
|
self.parasiteEnemy.hunted_by--;
|
|
}
|
|
}
|
|
|
|
self.parasiteEnemy = enemy;
|
|
if( !IsDefined( self.parasiteEnemy.hunted_by ) )
|
|
{
|
|
self.parasiteEnemy.hunted_by = 0;
|
|
}
|
|
self.parasiteEnemy.hunted_by++;
|
|
self SetLookAtEnt( self.parasiteEnemy );
|
|
self SetTurretTargetEnt( self.parasiteEnemy );
|
|
}
|
|
|
|
function private parasite_target_selection()
|
|
{
|
|
self endon( "change_state" );
|
|
self endon( "death" );
|
|
|
|
for( ;; )
|
|
{
|
|
if ( ( isdefined( self.ignoreall ) && self.ignoreall ) )
|
|
{
|
|
wait 0.5;
|
|
continue;
|
|
}
|
|
|
|
if( is_target_valid( self.parasiteEnemy ) )
|
|
{
|
|
wait 0.5;
|
|
continue;
|
|
}
|
|
//decide who the enemy should be
|
|
target = get_parasite_enemy();
|
|
|
|
if( !isDefined( target ) )
|
|
{
|
|
self.parasiteEnemy = undefined;
|
|
}
|
|
else
|
|
{
|
|
self.parasiteEnemy = target;
|
|
//update hunted_by
|
|
self.parasiteEnemy.hunted_by += 1;
|
|
self SetLookAtEnt( self.parasiteEnemy );
|
|
self SetTurretTargetEnt( self.parasiteEnemy );
|
|
}
|
|
|
|
wait 0.5;
|
|
}
|
|
}
|
|
|
|
function parasite_initialize()
|
|
{
|
|
self useanimtree( #animtree );
|
|
|
|
// AI SPECIFIC INITIALIZATION
|
|
blackboard::CreateBlackBoardForEntity( self );
|
|
self Blackboard::RegisterVehicleBlackBoardAttributes();
|
|
ai::CreateInterfaceForEntity( self );
|
|
|
|
Blackboard::RegisterBlackBoardAttribute(self,"_parasite_firing_rate","slow",&getParasiteFiringRate); if( IsActor(self) ) { /#self TrackBlackBoardAttribute("_parasite_firing_rate");#/ };
|
|
|
|
self.health = self.healthdefault;
|
|
|
|
self vehicle::friendly_fire_shield();
|
|
|
|
self EnableAimAssist();
|
|
self SetNearGoalNotifyDist( 25 );
|
|
|
|
self SetDrawInfrared( true );
|
|
|
|
self.fovcosine = 0;
|
|
self.fovcosinebusy = 0;
|
|
|
|
self.vehAirCraftCollisionEnabled = true;
|
|
|
|
assert( isdefined( self.scriptbundlesettings ) );
|
|
|
|
self.settings = struct::get_script_bundle( "vehiclecustomsettings", self.scriptbundlesettings );
|
|
self.goalRadius = 999999;
|
|
self.goalHeight = 4000;
|
|
self SetGoal( self.origin, false, self.goalRadius, self.goalHeight );
|
|
|
|
self.is_parasite = true;
|
|
|
|
self thread vehicle_ai::nudge_collision();
|
|
|
|
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( "death" ).update_func = &state_death_update;
|
|
|
|
self vehicle_ai::call_custom_add_state_callbacks();
|
|
|
|
vehicle_ai::StartInitialState( "combat" );
|
|
}
|
|
|
|
// ----------------------------------------------
|
|
//BB getter functions
|
|
// ----------------------------------------------
|
|
function getParasiteFiringRate()
|
|
{
|
|
return self ai::get_behavior_attribute( "firing_rate" );
|
|
}
|
|
|
|
// ----------------------------------------------
|
|
// State: death
|
|
// ----------------------------------------------
|
|
function state_death_update( params )
|
|
{
|
|
self endon( "death" );
|
|
|
|
self ASMRequestSubstate( "death@stationary" );
|
|
|
|
if( IsDefined( self.parasiteEnemy ) && IsDefined( self.parasiteEnemy.hunted_by ) )
|
|
{
|
|
self.parasiteEnemy.hunted_by--;
|
|
}
|
|
|
|
self SetPhysAcceleration( ( 0, 0, -300 ) );
|
|
self.vehcheckforpredictedcrash = true;
|
|
|
|
self thread vehicle_death::death_fx();
|
|
self playsound( "zmb_parasite_explo" );
|
|
|
|
self util::waittill_notify_or_timeout( "veh_predictedcollision", 4.0 );
|
|
|
|
self clientfield::set( "parasite_secondary_deathfx", 1 );
|
|
|
|
//make sure the client gets a chance to play the second fx before deleting
|
|
wait 0.2;
|
|
|
|
self Delete();
|
|
}
|
|
|
|
// ----------------------------------------------
|
|
// State: combat
|
|
// ----------------------------------------------
|
|
function state_combat_enter( params )
|
|
{
|
|
if ( IsDefined( self.owner ) && IsDefined( self.owner.enemy ) )
|
|
{
|
|
self.parasiteEnemy = self.owner.enemy;
|
|
}
|
|
self thread parasite_target_selection();
|
|
}
|
|
|
|
function state_combat_update( params )
|
|
{
|
|
self endon( "change_state" );
|
|
self endon( "death" );
|
|
|
|
lastTimeChangePosition = 0;
|
|
self.shouldGotoNewPosition = false;
|
|
self.lastTimeTargetInSight = 0;
|
|
self.lastTimeJuked = false;
|
|
|
|
//request movement animations
|
|
self ASMRequestSubstate( "locomotion@movement" );
|
|
|
|
for( ;; )
|
|
{
|
|
if( IsDefined( self._override_parasite_combat_speed ) )
|
|
{
|
|
self SetSpeed( self._override_parasite_combat_speed );
|
|
}
|
|
else
|
|
{
|
|
self SetSpeed( self.settings.defaultMoveSpeed );
|
|
}
|
|
|
|
if ( ( isdefined( self.inpain ) && self.inpain ) )
|
|
{
|
|
wait 0.1;
|
|
}
|
|
else if ( !IsDefined( self.parasiteEnemy ) )
|
|
{
|
|
//no enemy, do nothing
|
|
wait( 0.25 );
|
|
}
|
|
else
|
|
{
|
|
if( self.goalforced )
|
|
{
|
|
returnData = [];
|
|
returnData[ "origin" ] = self GetClosestPointOnNavVolume( self.goalpos, 100 );
|
|
returnData[ "centerOnNav" ] = IsPointInNavVolume( self.origin, "navvolume_small" );
|
|
}
|
|
//prevent two consecutive forward jukes, and force juke if path was interrupted ( possibly due to collision )
|
|
else if( ( RandomInt( 100 ) < self.settings.jukeprobability && !( isdefined( self.lastTimeJuked ) && self.lastTimeJuked ) ) || ( isdefined( self._override_juke ) && self._override_juke ) )
|
|
{
|
|
returnData = GetNextMovePosition_forwardjuke();
|
|
self.lastTimeJuked = true;
|
|
self._override_juke = undefined;
|
|
}
|
|
else
|
|
{
|
|
returnData = GetNextMovePosition_tactical();
|
|
self.lastTimeJuked = false;
|
|
}
|
|
self.current_pathto_pos = returnData[ "origin" ];
|
|
if ( IsDefined( self.current_pathto_pos ) )
|
|
{
|
|
if( IsDefined( self.stuckTime ) )
|
|
{
|
|
self.stuckTime = undefined;
|
|
}
|
|
if ( self SetVehGoalPos( self.current_pathto_pos, true, returnData[ "centerOnNav" ] ) )
|
|
{
|
|
self thread path_update_interrupt();
|
|
|
|
//AUDIO: Was on JUST the juke, but I've moved it for any movement to make it more interesting
|
|
self playsound( "zmb_vocals_parasite_juke" );
|
|
|
|
self vehicle_ai::waittill_pathing_done( 5 );
|
|
}
|
|
else
|
|
{
|
|
//failsafe, cannot pathfind
|
|
wait( 0.1 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( !( isdefined( returnData[ "centerOnNav" ] ) && returnData[ "centerOnNav" ] ) )
|
|
{
|
|
//not on navmesh, no valid goto points, kill parasite as failsafe
|
|
if( !IsDefined( self.stuckTime ) )
|
|
{
|
|
self.stuckTime = GetTime();
|
|
}
|
|
if( GetTime() - self.stuckTime > 10000 )
|
|
{
|
|
self DoDamage( self.health + 100, self.origin );
|
|
}
|
|
}
|
|
}
|
|
|
|
if( ( isdefined( self.lastTimeJuked ) && self.lastTimeJuked ) )
|
|
{
|
|
if( RandomInt( 100 ) < 50 && IsDefined( self.parasiteEnemy ) && Distance2DSquared( self.origin, self.parasiteEnemy.origin ) < ( (64) * (64) ) )
|
|
{
|
|
//parasite melee bite
|
|
self.parasiteEnemy DoDamage( self.settings.meleedamage, self.parasiteEnemy.origin, self );
|
|
}
|
|
else
|
|
{
|
|
self fire_pod_logic( self.lastTimeJuked );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( RandomInt( 100 ) < 30 )
|
|
{
|
|
self fire_pod_logic( self.lastTimeJuked );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function fire_pod_logic( choseToJuke )
|
|
{
|
|
if( IsDefined( self.parasiteEnemy ) && self VehCanSee( self.parasiteEnemy ) && Distance2DSquared( self.parasiteEnemy.origin, self.origin ) < ( (0.5 * ( self.settings.engagementDistMin + self.settings.engagementDistMax ) * 3) * (0.5 * ( self.settings.engagementDistMin + self.settings.engagementDistMax ) * 3) ) )
|
|
{
|
|
//switch to fire animation
|
|
self ASMRequestSubstate( "fire@stationary" );
|
|
|
|
//do the tell
|
|
self PlaySound( "zmb_vocals_parasite_preattack" );
|
|
self clientfield::set( "parasite_tell_fx", 1 );
|
|
|
|
//reached the point in the animation to adjust the turret
|
|
self waittill( "pre_fire" );
|
|
|
|
if( IsDefined( self.parasiteEnemy ) && self VehCanSee( self.parasiteEnemy ) && Distance2DSquared( self.parasiteEnemy.origin, self.origin ) < ( (0.5 * ( self.settings.engagementDistMin + self.settings.engagementDistMax ) * 3) * (0.5 * ( self.settings.engagementDistMin + self.settings.engagementDistMax ) * 3) ) )
|
|
{
|
|
//fire the projectile at the predicted player position
|
|
self SetTurretTargetEnt( self.parasiteEnemy, self.parasiteEnemy GetVelocity() * 0.3 );
|
|
}
|
|
|
|
self vehicle_ai::waittill_asm_complete( "fire@stationary", 5 );
|
|
|
|
//switch back to locomotion
|
|
self ASMRequestSubstate( "locomotion@movement" );
|
|
|
|
//cleanup the fx
|
|
self clientfield::set( "parasite_tell_fx", 0 );
|
|
|
|
if( !choseToJuke )
|
|
{
|
|
wait RandomFloatRange( 0.25, 0.5 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
wait RandomFloatRange( 1, 2 );
|
|
}
|
|
}
|
|
|
|
function GetNextMovePosition_tactical() // has self.parasiteEnemy
|
|
{
|
|
self endon( "change_state" );
|
|
self endon( "death" );
|
|
|
|
// distance based multipliers
|
|
selfDistToTarget = Distance2D( self.origin, self.parasiteEnemy.origin );
|
|
|
|
goodDist = 0.5 * ( self.settings.engagementDistMin + self.settings.engagementDistMax );
|
|
|
|
closeDist = 1.2 * goodDist;
|
|
farDist = 3 * goodDist;
|
|
|
|
queryMultiplier = MapFloat( closeDist, farDist, 1, 3, selfDistToTarget );
|
|
|
|
//the preferred height range should be half the max possible difference to not exceed valid heights for points near the goalheight
|
|
preferedHeightRange = 0.5 * ( self.settings.engagementHeightMax - self.settings.engagementHeightMin );
|
|
randomness = 30;
|
|
|
|
// query
|
|
queryResult = PositionQuery_Source_Navigation( self.origin, 75, 225 * queryMultiplier, 75, 20 * queryMultiplier, self, 20 * queryMultiplier );
|
|
//turn off collision, we are not on the navvolume
|
|
if( !( isdefined( queryResult.centerOnNav ) && queryResult.centerOnNav ) )
|
|
{
|
|
self.vehAirCraftCollisionEnabled = false;
|
|
}
|
|
else
|
|
{
|
|
self.vehAirCraftCollisionEnabled = true;
|
|
}
|
|
// filter
|
|
PositionQuery_Filter_DistanceToGoal( queryResult, self );
|
|
vehicle_ai::PositionQuery_Filter_OutOfGoalAnchor( queryResult );
|
|
self vehicle_ai::PositionQuery_Filter_EngagementDist( queryResult, self.parasiteEnemy, self.settings.engagementDistMin, self.settings.engagementDistMax );
|
|
|
|
goalHeight = self.parasiteEnemy.origin[2] + 0.5 * ( self.settings.engagementHeightMin + self.settings.engagementHeightMax );
|
|
|
|
// score points
|
|
best_point = undefined;
|
|
best_score = -999999;
|
|
trace_count = 0;
|
|
foreach ( point in queryResult.data )
|
|
{
|
|
if( !( isdefined( queryResult.centerOnNav ) && queryResult.centerOnNav ) )
|
|
{
|
|
if( SightTracePassed( self.origin, point.origin, false, undefined ) )
|
|
{
|
|
trace_count++;
|
|
if( trace_count > 3 )
|
|
{
|
|
{wait(.05);};
|
|
trace_count = 0;
|
|
}
|
|
if( !BulletTracePassed( self.origin, point.origin, false, self ) )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/# 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;;
|
|
|
|
// height
|
|
distFromPreferredHeight = abs( point.origin[2] - goalHeight );
|
|
if ( distFromPreferredHeight > preferedHeightRange )
|
|
{
|
|
heightScore = MapFloat( 0, 500, 0, 2000, distFromPreferredHeight );
|
|
/# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "height" ] = -heightScore; #/ point.score += -heightScore;;
|
|
}
|
|
|
|
if ( point.score > best_score )
|
|
{
|
|
best_score = point.score;
|
|
best_point = point;
|
|
}
|
|
}
|
|
self vehicle_ai::PositionQuery_DebugScores( queryResult );
|
|
|
|
/#
|
|
if ( ( isdefined( GetDvarInt("hkai_debugPositionQuery") ) && GetDvarInt("hkai_debugPositionQuery") ) )
|
|
{
|
|
recordLine( self.origin, best_point.origin, (0.3,1,0) );
|
|
recordLine( self.origin, self.parasiteEnemy.origin, (1,0,0.4) );
|
|
}
|
|
#/
|
|
returnData = [];
|
|
returnData[ "origin" ] = ( ( IsDefined( best_point ) ) ? best_point.origin : undefined );
|
|
returnData[ "centerOnNav" ] = queryResult.centerOnNav;
|
|
return returnData;
|
|
}
|
|
|
|
|
|
function GetNextMovePosition_forwardjuke() // has self.parasiteEnemy
|
|
{
|
|
self endon( "change_state" );
|
|
self endon( "death" );
|
|
|
|
// distance based multipliers
|
|
selfDistToTarget = Distance2D( self.origin, self.parasiteEnemy.origin );
|
|
|
|
goodDist = 0.5 * ( self.settings.forwardJukeEngagementDistMin + self.settings.forwardJukeEngagementDistMax );
|
|
|
|
closeDist = 1.2 * goodDist;
|
|
farDist = 3 * goodDist;
|
|
|
|
queryMultiplier = MapFloat( closeDist, farDist, 1, 3, selfDistToTarget );
|
|
|
|
preferedHeightRange = 0.5 * ( self.settings.forwardJukeEngagementHeightMax - self.settings.forwardJukeEngagementHeightMin );
|
|
randomness = 30;
|
|
|
|
// query
|
|
queryResult = PositionQuery_Source_Navigation( self.origin, 75, 300 * queryMultiplier, 75, 20 * queryMultiplier, self, 20 * queryMultiplier );
|
|
if( !( isdefined( queryResult.centerOnNav ) && queryResult.centerOnNav ) )
|
|
{
|
|
self.vehAirCraftCollisionEnabled = false;
|
|
}
|
|
else
|
|
{
|
|
self.vehAirCraftCollisionEnabled = true;
|
|
}
|
|
// filter
|
|
PositionQuery_Filter_DistanceToGoal( queryResult, self );
|
|
vehicle_ai::PositionQuery_Filter_OutOfGoalAnchor( queryResult );
|
|
self vehicle_ai::PositionQuery_Filter_EngagementDist( queryResult, self.parasiteEnemy, self.settings.forwardJukeEngagementDistMin, self.settings.forwardJukeEngagementDistMax );
|
|
|
|
goalHeight = self.parasiteEnemy.origin[2] + 0.5 * ( self.settings.forwardJukeEngagementHeightMin + self.settings.forwardJukeEngagementHeightMax );
|
|
|
|
// score points
|
|
best_point = undefined;
|
|
best_score = -999999;
|
|
trace_count = 0;
|
|
|
|
foreach ( point in queryResult.data )
|
|
{
|
|
if( !( isdefined( queryResult.centerOnNav ) && queryResult.centerOnNav ) )
|
|
{
|
|
if( SightTracePassed( self.origin, point.origin, false, undefined ) )
|
|
{
|
|
trace_count++;
|
|
if( trace_count > 3 )
|
|
{
|
|
{wait(.05);};
|
|
trace_count = 0;
|
|
}
|
|
if( !BulletTracePassed( self.origin, point.origin, false, self ) )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/# 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;;
|
|
|
|
// height
|
|
distFromPreferredHeight = abs( point.origin[2] - goalHeight );
|
|
if ( distFromPreferredHeight > preferedHeightRange )
|
|
{
|
|
heightScore = MapFloat( 0, 500, 0, 2000, distFromPreferredHeight );
|
|
/# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "height" ] = -heightScore; #/ point.score += -heightScore;;
|
|
}
|
|
|
|
if ( point.score > best_score )
|
|
{
|
|
best_score = point.score;
|
|
best_point = point;
|
|
}
|
|
}
|
|
self vehicle_ai::PositionQuery_DebugScores( queryResult );
|
|
|
|
/#
|
|
if ( ( isdefined( GetDvarInt("hkai_debugPositionQuery") ) && GetDvarInt("hkai_debugPositionQuery") ) )
|
|
{
|
|
recordLine( self.origin, best_point.origin, (0.3,1,0) );
|
|
recordLine( self.origin, self.parasiteEnemy.origin, (1,0,0.4) );
|
|
}
|
|
#/
|
|
|
|
returnData = [];
|
|
returnData[ "origin" ] = ( ( IsDefined( best_point ) ) ? best_point.origin : undefined );
|
|
returnData[ "centerOnNav" ] = queryResult.centerOnNav;
|
|
return returnData;
|
|
}
|
|
|
|
function path_update_interrupt()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "change_state" );
|
|
self endon( "near_goal" );
|
|
self endon( "reached_end_node" );
|
|
|
|
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._override_juke = true;
|
|
self notify( "near_goal" );
|
|
}
|
|
}
|
|
wait 0.2;
|
|
}
|
|
}
|
|
|
|
|
|
// ----------------------------------------------
|
|
// pain/hit reaction
|
|
// ----------------------------------------------
|
|
function drone_pain_for_time( time, stablizeParam, restoreLookPoint )
|
|
{
|
|
self endon( "death" );
|
|
|
|
self.painStartTime = GetTime();
|
|
|
|
if ( !( isdefined( self.inpain ) && self.inpain ) )
|
|
{
|
|
self.inpain = true;
|
|
|
|
self PlaySound( "zmb_vocals_parasite_pain" );
|
|
|
|
while ( GetTime() < self.painStartTime + time * 1000 )
|
|
{
|
|
self SetVehVelocity( self.velocity * stablizeParam );
|
|
self SetAngularVelocity( self GetAngularVelocity() * stablizeParam );
|
|
wait 0.1;
|
|
}
|
|
|
|
if ( isdefined( restoreLookPoint ) )
|
|
{
|
|
restoreLookEnt = Spawn( "script_model", restoreLookPoint );
|
|
restoreLookEnt SetModel( "tag_origin" );
|
|
|
|
self ClearLookAtEnt();
|
|
self SetLookAtEnt( restoreLookEnt );
|
|
self setTurretTargetEnt( restoreLookEnt );
|
|
wait 1.5;
|
|
|
|
self ClearLookAtEnt();
|
|
self ClearTurretTarget();
|
|
restoreLookEnt delete();
|
|
}
|
|
|
|
self.inpain = false;
|
|
}
|
|
}
|
|
|
|
function drone_pain( eAttacker, damageType, hitPoint, hitDirection, hitLocationInfo, partName )
|
|
{
|
|
if ( !( isdefined( self.inpain ) && self.inpain ) )
|
|
{
|
|
yaw_vel = math::randomSign() * RandomFloatRange( 280, 320 );
|
|
|
|
ang_vel = self GetAngularVelocity();
|
|
ang_vel += ( RandomFloatRange( -120, -100 ), yaw_vel, RandomFloatRange( -200, 200 ) );
|
|
self SetAngularVelocity( ang_vel );
|
|
|
|
self thread drone_pain_for_time( 0.8, 0.7 );
|
|
}
|
|
} |