1148 lines
39 KiB
Plaintext
1148 lines
39 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\audio_shared;
|
|
#using scripts\shared\clientfield_shared;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Distance at which raps repath range is doubled and they check for others claimed locations. Needs to match.
|
|
|
|
|
|
|
|
|
|
#namespace raps;
|
|
|
|
#using_animtree( "generic" );
|
|
|
|
function autoexec __init__sytem__() { system::register("raps",&__init__,undefined,undefined); }
|
|
|
|
function __init__()
|
|
{
|
|
clientfield::register( "vehicle", "raps_side_deathfx", 1, 1, "int" );
|
|
|
|
vehicle::add_main_callback( "raps", &raps_initialize );
|
|
|
|
slow_triggers = GetEntArray( "raps_slow", "targetname" );
|
|
array::thread_all( slow_triggers, &slow_raps_trigger );
|
|
}
|
|
|
|
function raps_initialize()
|
|
{
|
|
self.fovcosine = 0;
|
|
self.fovcosinebusy = 0;
|
|
self.delete_on_death = true;
|
|
self.health = self.healthdefault;
|
|
self.last_jump_chance_time = 0;
|
|
|
|
self UseAnimTree( #animtree );
|
|
|
|
self vehicle::friendly_fire_shield();
|
|
|
|
assert( isdefined( self.scriptbundlesettings ) );
|
|
self.settings = struct::get_script_bundle( "vehiclecustomsettings", self.scriptbundlesettings );
|
|
|
|
if( self.settings.aim_assist )
|
|
{
|
|
self EnableAimAssist();
|
|
}
|
|
|
|
self SetNearGoalNotifyDist( self.settings.near_goal_notify_dist );
|
|
self.goalRadius = 999999;
|
|
self.goalHeight = 999999;
|
|
self SetGoal( self.origin, false, self.goalRadius, self.goalHeight );
|
|
|
|
self.overrideVehicleDamage = &raps_callback_damage;
|
|
self.allowFriendlyFireDamageOverride = &raps_AllowFriendlyFireDamage;
|
|
self.do_death_fx = &do_death_fx;
|
|
|
|
self thread vehicle_ai::nudge_collision();
|
|
|
|
self thread sndFunctions();
|
|
|
|
// self thread play_rumble_on_raps();
|
|
|
|
//disable some cybercom abilities
|
|
if( IsDefined( level.vehicle_initializer_cb ) )
|
|
{
|
|
[[level.vehicle_initializer_cb]]( self );
|
|
}
|
|
|
|
defaultRole();
|
|
}
|
|
|
|
function defaultRole()
|
|
{
|
|
self vehicle_ai::init_state_machine_for_role( "default" );
|
|
|
|
self vehicle_ai::get_state_callbacks( "combat" ).update_func = &state_combat_update;
|
|
|
|
self vehicle_ai::get_state_callbacks( "driving" ).update_func = &state_scripted_update;
|
|
|
|
self vehicle_ai::get_state_callbacks( "death" ).update_func = &state_death_update;
|
|
|
|
self vehicle_ai::get_state_callbacks( "emped" ).update_func = &state_emped_update;
|
|
|
|
self vehicle_ai::call_custom_add_state_callbacks();
|
|
|
|
vehicle_ai::StartInitialState( "combat" );
|
|
}
|
|
|
|
function state_scripted_update( params )
|
|
{
|
|
self endon( "change_state" );
|
|
self endon("death");
|
|
|
|
driver = self GetSeatOccupant( 0 );
|
|
|
|
if( isPlayer( driver ) )
|
|
{
|
|
driver endon( "disconnect" );
|
|
|
|
driver util::waittill_attack_button_pressed();
|
|
|
|
self Kill( self.origin, driver );
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------
|
|
// State: death
|
|
// ----------------------------------------------
|
|
function state_death_update( params )
|
|
{
|
|
self endon ( "death" );
|
|
|
|
attacker = params.inflictor;
|
|
if( !isdefined( attacker ) )
|
|
{
|
|
attacker = params.attacker;
|
|
}
|
|
|
|
if( attacker !== self && ( !isdefined( self.owner ) || ( self.owner !== attacker ) ) && ( IsAI( attacker) || IsPlayer( attacker ) ) )
|
|
{
|
|
self.damage_on_death = false;
|
|
{wait(.05);};
|
|
|
|
// need to retest for attacker validity because of the wait
|
|
attacker = params.inflictor;
|
|
if( !isdefined( attacker ) )
|
|
{
|
|
attacker = params.attacker;
|
|
}
|
|
if ( isdefined( attacker ) && !isdefined( self.detonate_sides_disabled ) )
|
|
{
|
|
self detonate_sides( attacker );
|
|
}
|
|
else
|
|
{
|
|
self.damage_on_death = true;
|
|
}
|
|
}
|
|
|
|
self vehicle_ai::defaultstate_death_update();
|
|
}
|
|
|
|
// ----------------------------------------------
|
|
// State: emped
|
|
// ----------------------------------------------
|
|
function state_emped_update( params )
|
|
{
|
|
self endon ( "death" );
|
|
self endon ( "change_state" );
|
|
|
|
if ( self.serverShortout === true )
|
|
{
|
|
forward = VectorNormalize( ( ( self GetVelocity() )[0], ( self GetVelocity() )[1], 0 ) );
|
|
side = VectorCross( forward, (0,0,1) ) * math::randomsign();
|
|
self SetVehGoalPos( self.origin + side * 500 + forward * randomFloat(400), false, false );
|
|
wait 0.6;
|
|
self ClearVehGoalPos();
|
|
self util::waittill_any_timeout( 1.5, "veh_collision", "change_state", "death" );
|
|
self Kill( self.origin, self.abnormal_status.attacker, self.abnormal_status.inflictor, GetWeapon( "emp" ) );
|
|
}
|
|
else
|
|
{
|
|
vehicle_ai::defaultstate_emped_update( params );
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------
|
|
// State: combat
|
|
// ----------------------------------------------
|
|
function state_combat_update( params )
|
|
{
|
|
self endon( "change_state" );
|
|
self endon( "death" );
|
|
|
|
pathfailcount = 0;
|
|
foundpath = true;
|
|
|
|
self thread prevent_stuck();
|
|
self thread detonation_monitor();
|
|
self thread nudge_collision();
|
|
|
|
for( ;; )
|
|
{
|
|
if ( ( isdefined( self.inpain ) && self.inpain ) )
|
|
{
|
|
wait 0.1;
|
|
}
|
|
else if ( !IsDefined( self.enemy ) || ( isdefined( self.raps_force_patrol_behavior ) && self.raps_force_patrol_behavior ) )
|
|
{
|
|
if( isdefined( self.settings.all_knowing ) )
|
|
{
|
|
self force_get_enemies();
|
|
}
|
|
|
|
// go slower when you don't have an enemy, patrolling
|
|
self SetSpeed( self.settings.defaultMoveSpeed * 0.35 );
|
|
|
|
PixBeginEvent( "_raps::state_combat_update 1" );
|
|
queryResult = PositionQuery_Source_Navigation( self.origin, 0, self.settings.max_move_dist * 3, self.settings.max_move_dist * 3, self.radius * 2, self, self.radius * 4 );
|
|
PixEndEvent();
|
|
|
|
PositionQuery_Filter_InClaimedLocation( queryResult, self );
|
|
PositionQuery_Filter_DistanceToGoal( queryResult, self );
|
|
vehicle_ai::PositionQuery_Filter_OutOfGoalAnchor( queryResult ); // This already adds score
|
|
|
|
best_point = undefined;
|
|
best_score = -999999;
|
|
|
|
foreach ( point in queryResult.data )
|
|
{
|
|
// distance from origin
|
|
/# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "distToOrigin" ] = MapFloat( 0, 200, 0, 100, point.distToOrigin2D ); #/ point.score += MapFloat( 0, 200, 0, 100, point.distToOrigin2D );;
|
|
|
|
if( point.inClaimedLocation )
|
|
{
|
|
/# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "inClaimedLocation" ] = -500; #/ point.score += -500;;
|
|
}
|
|
|
|
/# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "random" ] = randomFloatRange( 0, 50 ); #/ point.score += randomFloatRange( 0, 50 );;
|
|
|
|
// Wander in a somewhat continuous direction
|
|
if( isdefined( self.prevMoveDir ) )
|
|
{
|
|
moveDir = VectorNormalize( point.origin - self.origin );
|
|
if( VectorDot( moveDir, self.prevMoveDir ) > 0.64 ) // cos 50'
|
|
{
|
|
/# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "currentMoveDir" ] = randomFloatRange( 50, 150 ); #/ point.score += randomFloatRange( 50, 150 );;
|
|
}
|
|
}
|
|
|
|
if ( point.score > best_score )
|
|
{
|
|
best_score = point.score;
|
|
best_point = point;
|
|
}
|
|
}
|
|
|
|
self vehicle_ai::PositionQuery_DebugScores( queryResult );
|
|
|
|
foundpath = false;
|
|
|
|
if( isdefined( best_point ) )
|
|
{
|
|
foundpath = self SetVehGoalPos( best_point.origin, false, true );
|
|
}
|
|
else
|
|
{
|
|
wait 1;
|
|
}
|
|
|
|
if ( foundpath )
|
|
{
|
|
self.prevMoveDir = VectorNormalize( best_point.origin - self.origin );
|
|
self.current_pathto_pos = undefined;
|
|
self thread path_update_interrupt();
|
|
|
|
pathfailcount = 0;
|
|
|
|
self vehicle_ai::waittill_pathing_done();
|
|
}
|
|
else
|
|
{
|
|
// avoid infinite loop when failinig to find path
|
|
wait 1;
|
|
}
|
|
}
|
|
else if ( !self.enemy.allowdeath && self GetPersonalThreatBias( self.enemy ) == 0 ) // avoid magic bullet shielded enemy
|
|
{
|
|
self SetPersonalThreatBias( self.enemy, -2000, 30.0 );
|
|
{wait(.05);};
|
|
}
|
|
else
|
|
{
|
|
foundpath = false;
|
|
targetPos = raps_get_target_position();
|
|
|
|
if ( isdefined( targetPos ) )
|
|
{
|
|
// Prevent training by not sending every raps to the same location unless they are getting close
|
|
if( DistanceSquared( self.origin, targetPos ) > ( (400) * (400) ) && self IsPosInClaimedLocation( targetPos ) )
|
|
{
|
|
PixBeginEvent( "_raps::state_combat_update 2" );
|
|
queryResult = PositionQuery_Source_Navigation( targetPos, 0, self.settings.max_move_dist, self.settings.max_move_dist, self.radius, self );
|
|
PixEndEvent();
|
|
|
|
PositionQuery_Filter_InClaimedLocation( queryResult, self.enemy );
|
|
|
|
best_point = undefined;
|
|
best_score = -999999;
|
|
foreach ( point in queryResult.data )
|
|
{
|
|
/# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "distToOrigin" ] = MapFloat( 0, 200, 0, -200, Distance( point.origin, queryResult.origin ) ); #/ point.score += MapFloat( 0, 200, 0, -200, Distance( point.origin, queryResult.origin ) );;
|
|
/# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "heightToOrigin" ] = MapFloat( 50, 200, 0, -200, Abs( point.origin[2] - queryResult.origin[2] ) ); #/ point.score += MapFloat( 50, 200, 0, -200, Abs( point.origin[2] - queryResult.origin[2] ) );;
|
|
|
|
if( point.inClaimedLocation === true )
|
|
{
|
|
/# 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 ) )
|
|
{
|
|
targetPos = best_point.origin;
|
|
}
|
|
}
|
|
|
|
foundpath = self SetVehGoalPos( targetPos, false, true );
|
|
|
|
if ( self.test_failed_path === true )
|
|
{
|
|
foundpath = vehicle_ai::waittill_pathresult( 0.5 );
|
|
}
|
|
|
|
if ( foundpath )
|
|
{
|
|
self.current_pathto_pos = targetPos;
|
|
self thread path_update_interrupt();
|
|
|
|
pathfailcount = 0;
|
|
|
|
self vehicle_ai::waittill_pathing_done();
|
|
}
|
|
else
|
|
{
|
|
{wait(.05);};
|
|
}
|
|
}
|
|
|
|
if ( !foundpath )
|
|
{
|
|
pathfailcount++;
|
|
|
|
if ( pathfailcount > 2 )
|
|
{
|
|
/#
|
|
// recordLine( self.origin, self.origin + (0,0,3000), (0.3,1,0) );
|
|
// RecordSphere( self.origin, 30, (1,1,0) );
|
|
#/
|
|
|
|
// Try to change enemies
|
|
if( IsDefined( self.enemy ) )
|
|
{
|
|
self SetPersonalThreatBias( self.enemy, -2000, 5.0 );
|
|
}
|
|
|
|
if ( pathfailcount > self.settings.max_path_fail_count )
|
|
{
|
|
detonate();
|
|
}
|
|
}
|
|
|
|
wait .2;
|
|
|
|
// just try to path strait to a nearby position on the path
|
|
PixBeginEvent( "_raps::state_combat_update 3" );
|
|
queryResult = PositionQuery_Source_Navigation( self.origin, 0, self.settings.max_move_dist, self.settings.max_move_dist, self.radius, self );
|
|
PixBeginEvent( "_raps::state_combat_update 3" );
|
|
|
|
if( queryResult.data.size )
|
|
{
|
|
point = queryResult.data[ randomint( queryResult.data.size ) ];
|
|
|
|
self SetVehGoalPos( point.origin, false, false );
|
|
self.current_pathto_pos = undefined;
|
|
self thread path_update_interrupt(); // keep trying to detonate
|
|
wait 2;
|
|
self notify( "near_goal" ); // kill the path_update_interrupt just in case
|
|
}
|
|
}
|
|
|
|
wait 0.2;
|
|
}
|
|
}
|
|
}
|
|
|
|
function prevent_stuck()
|
|
{
|
|
self endon( "change_state" );
|
|
self endon( "death" );
|
|
self notify( "end_prevent_stuck" );
|
|
self endon( "end_prevent_stuck" );
|
|
|
|
wait 2;
|
|
|
|
count = 0;
|
|
previous_origin = undefined;
|
|
|
|
// detonate if position hasn't change for N counts
|
|
while( true )
|
|
{
|
|
if ( isdefined( previous_origin ) && DistanceSquared( previous_origin, self.origin ) < ( (0.1) * (0.1) ) && !( isdefined( level.bzm_worldPaused ) && level.bzm_worldPaused ) )
|
|
{
|
|
count++;
|
|
}
|
|
else
|
|
{
|
|
previous_origin = self.origin;
|
|
count = 0;
|
|
}
|
|
|
|
if ( count > 10 )
|
|
{
|
|
detonate();
|
|
}
|
|
|
|
wait 1;
|
|
}
|
|
}
|
|
|
|
function check_detonation_dist( origin, enemy )
|
|
{
|
|
if ( isdefined( enemy ) && IsAlive( enemy ) )
|
|
{
|
|
// try to detonate when more in front of the enemy, frustrating to get killed from behind
|
|
enemy_look_dir_offst = AnglesToForward( enemy.angles ) * 30;
|
|
targetPoint = enemy.origin + enemy_look_dir_offst;
|
|
|
|
if( distance2DSquared( targetPoint, origin ) < ( (self.settings.detonation_distance) * (self.settings.detonation_distance) ) && (
|
|
Abs( targetPoint[2] - origin[2] ) < self.settings.detonation_distance ||
|
|
Abs( targetPoint[2] - 20 - origin[2] ) < self.settings.detonation_distance ) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function jump_detonate()
|
|
{
|
|
if( isdefined( self.sndAlias["jump_up"] ) )
|
|
self PlaySound( self.sndAlias["jump_up"] );
|
|
|
|
self LaunchVehicle( (0,0,1) * self.jumpforce, (0,0,0), true );
|
|
|
|
self.is_jumping = true;
|
|
|
|
// Spin the Raps too
|
|
/*
|
|
if( math::cointoss() )
|
|
{
|
|
side_dir = (0,-1,0);
|
|
}
|
|
else
|
|
{
|
|
side_dir = (0,1,0);
|
|
}
|
|
self LaunchVehicle( side_dir * 5, (400,0,0), true, 1 );
|
|
*/
|
|
wait 0.4;
|
|
|
|
time_to_land = 0.6;
|
|
while( time_to_land > 0 )
|
|
{
|
|
if( check_detonation_dist( self.origin, self.enemy ) )
|
|
{
|
|
self detonate();
|
|
}
|
|
wait 0.05;
|
|
time_to_land -= 0.05;
|
|
}
|
|
|
|
if( IsAlive( self ) )
|
|
{
|
|
self.is_jumping = false;
|
|
|
|
trace = PhysicsTrace( self.origin + ( 0, 0, self.radius * 2 ), self.origin - ( 0, 0, 1000 ), ( -10, -10, -10 ), ( 10, 10, 10 ), self, (1 << 1) );
|
|
|
|
willFall = true;
|
|
if ( trace[ "fraction" ] < 1 )
|
|
{
|
|
pos = trace[ "position" ];
|
|
pos_on_navmesh = GetClosestPointOnNavMesh( pos, 100, self.radius, 0x00ffffff & ~0x00000020 );
|
|
if ( isdefined( pos_on_navmesh ) )
|
|
{
|
|
willFall = false;
|
|
}
|
|
}
|
|
|
|
if ( willFall )
|
|
{
|
|
self detonate();
|
|
}
|
|
}
|
|
|
|
// re-enable stabilizers
|
|
//self LaunchVehicle( (0,0,1), (0,0,0), true, 2 );
|
|
}
|
|
|
|
function detonate( attacker )
|
|
{
|
|
if( !isdefined( attacker ) )
|
|
attacker = self;
|
|
|
|
self stopsounds();
|
|
|
|
self DoDamage( self.health + 1000, self.origin, attacker, self, "none", "MOD_EXPLOSIVE", 0, self.turretWeapon );
|
|
}
|
|
|
|
function detonate_damage_monitored( enemy, weapon )
|
|
{
|
|
self.selfDestruct = true;
|
|
self DoDamage( 1000, self.origin, enemy, self, "none", "MOD_EXPLOSIVE", 0, self.turretWeapon );
|
|
}
|
|
|
|
function detonation_monitor()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "change_state" );
|
|
|
|
lastEnemy = undefined;
|
|
|
|
while( 1 )
|
|
{
|
|
try_detonate();
|
|
|
|
wait 0.2;
|
|
|
|
// targeting audio
|
|
if( isdefined( self.enemy ) && IsPlayer( self.enemy ) )
|
|
{
|
|
// enemy change
|
|
if( lastEnemy !== self.enemy )
|
|
{
|
|
lastDistToEnemySquared = 10000.0 * 10000.0;
|
|
lastEnemy = self.enemy;
|
|
}
|
|
|
|
if( !IsDefined( self.looping_targeting_sound ) )
|
|
{
|
|
if( isdefined( self.sndAlias["vehRapsAlarm"] ) )
|
|
{
|
|
self.looping_targeting_sound = spawn( "script_origin", self.origin );
|
|
self.looping_targeting_sound linkto(self);
|
|
|
|
//set the client mask on the looping targeting sound to play only on the enemy
|
|
self.looping_targeting_sound SetInvisibleToAll();
|
|
self.looping_targeting_sound SetVisibleToPlayer( self.enemy );
|
|
self.looping_targeting_sound playloopsound (self.sndAlias["vehRapsAlarm"]);
|
|
|
|
self.looping_targeting_sound thread raps_audio_cleanup( self );
|
|
}
|
|
}
|
|
|
|
distToEnemySquared = distanceSquared( self.origin, self.enemy.origin );
|
|
|
|
if( distToEnemySquared < ( (250) * (250) ) )
|
|
{
|
|
if( lastDistToEnemySquared > ( (250) * (250) ) && !( isdefined( self.serverShortout ) && self.serverShortout ) && isdefined( self.sndAlias["vehRapsClose250"] ) )
|
|
self PlaySoundToPlayer( self.sndAlias["vehRapsClose250"], self.enemy );
|
|
}
|
|
else if( distToEnemySquared < ( (750) * (750) ) )
|
|
{
|
|
if( lastDistToEnemySquared > ( (750) * (750) ) && !( isdefined( self.serverShortout ) && self.serverShortout ) && isdefined( self.sndAlias["vehRapsTargeting"] ) )
|
|
self PlaySoundToPlayer( self.sndAlias["vehRapsTargeting"], self.enemy );
|
|
}
|
|
else if( distToEnemySquared < ( (1500) * (1500) ) )
|
|
{
|
|
if( lastDistToEnemySquared > ( (1500) * (1500) ) && !( isdefined( self.serverShortout ) && self.serverShortout ) && isdefined( self.sndAlias["vehRapsClose1500"] ) )
|
|
self PlaySoundToPlayer( self.sndAlias["vehRapsClose1500"], self.enemy );
|
|
}
|
|
|
|
if( distToEnemySquared < lastDistToEnemySquared )
|
|
{
|
|
lastDistToEnemySquared = distToEnemySquared;
|
|
}
|
|
// let it slowly grow so we can play the sounds again if the player gets away
|
|
lastDistToEnemySquared += ( (50 * 0.2) * (50 * 0.2) );
|
|
|
|
}
|
|
}
|
|
}
|
|
function raps_audio_cleanup( owner )
|
|
{
|
|
owner waittill( "death" );
|
|
|
|
if ( isdefined( owner ) )
|
|
{
|
|
owner stopsounds();
|
|
}
|
|
|
|
if ( isdefined( self ) )
|
|
{
|
|
self StopLoopSound();
|
|
self Delete();
|
|
}
|
|
}
|
|
|
|
function try_detonate()
|
|
{
|
|
if( ( isdefined( self.disableAutoDetonation ) && self.disableAutoDetonation ) )
|
|
return;
|
|
|
|
jump_time = 0.5;
|
|
|
|
cur_time = GetTime();
|
|
|
|
can_jump = (cur_time - self.last_jump_chance_time > 1500);
|
|
|
|
if( can_jump )
|
|
{
|
|
predicted_origin = self.origin + self GetVelocity() * jump_time;
|
|
}
|
|
|
|
if( isdefined( predicted_origin ) && check_detonation_dist( predicted_origin, self.enemy ) )
|
|
{
|
|
trace = BulletTrace( predicted_origin + (0,0,self.radius), self.enemy.origin + (0,0,self.radius), true, self );
|
|
if ( trace["fraction"] === 1.0 || isdefined( trace["entity"] ) )
|
|
{
|
|
self.last_jump_chance_time = cur_time;
|
|
jump_chance = self.settings.jump_chance;
|
|
if ( self.enemy.origin[2] - 20 > predicted_origin[2] )
|
|
{
|
|
jump_chance = self.settings.jump_chance * 2;
|
|
}
|
|
|
|
if( RandomFloat( 1 ) < jump_chance )
|
|
{
|
|
self jump_detonate();
|
|
}
|
|
}
|
|
}
|
|
else if( check_detonation_dist( self.origin, self.enemy ) )
|
|
{
|
|
trace = BulletTrace( self.origin + (0,0,self.radius), self.enemy.origin + (0,0,self.radius), true, self );
|
|
if ( trace["fraction"] === 1.0 || isdefined( trace["entity"] ) )
|
|
{
|
|
self detonate();
|
|
}
|
|
}
|
|
|
|
// just blow up if close to other enemies
|
|
if( isdefined( self.owner ) )
|
|
{
|
|
foreach( player in level.players )
|
|
{
|
|
if( self.owner util::IsEnemyPlayer( player ) && ( !isdefined( self.enemy ) || player != self.enemy ) )
|
|
{
|
|
if( player IsNoTarget() || !IsAlive( player ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( player.ignoreme === true )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( !SessionModeIsCampaignGame() && !SessionModeIsZombiesGame() && player hasPerk( "specialty_nottargetedbyraps" ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( distanceSquared( player.origin, self.origin ) < ( (self.settings.detonation_distance) * (self.settings.detonation_distance) ) )
|
|
{
|
|
trace = BulletTrace( self.origin + (0,0,self.radius), player.origin + (0,0,self.radius), true, self );
|
|
if ( trace["fraction"] === 1.0 || isdefined( trace["entity"] ) )
|
|
{
|
|
self raps::detonate();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function raps_get_target_position()
|
|
{
|
|
if( isdefined( self.settings.all_knowing ) )
|
|
{
|
|
if( isdefined( self.enemy ) )
|
|
{
|
|
target_pos = self.enemy.origin;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
target_pos = vehicle_ai::GetTargetPos( vehicle_ai::GetEnemyTarget() );
|
|
}
|
|
|
|
enemy = self.enemy;
|
|
|
|
if( isdefined( target_pos ) )
|
|
{
|
|
target_pos_onnavmesh = GetClosestPointOnNavMesh( target_pos, self.settings.detonation_distance * 1.5, self.radius, 0x00ffffff & ~0x00000020 );
|
|
}
|
|
|
|
// if we can't find a position on the navmesh then just keep going to the current position
|
|
if( !isdefined( target_pos_onnavmesh ) )
|
|
{
|
|
if( isdefined( self.enemy ) )
|
|
{
|
|
self SetPersonalThreatBias( self.enemy, -2000, 5.0 );
|
|
}
|
|
|
|
if ( isdefined( self.current_pathto_pos ) )
|
|
{
|
|
target_pos_onnavmesh = GetClosestPointOnNavMesh( self.current_pathto_pos, self.settings.detonation_distance * 2, self.settings.detonation_distance * 1.5, 0x00ffffff & ~0x00000020 );
|
|
}
|
|
|
|
if ( isdefined( target_pos_onnavmesh ) )
|
|
{
|
|
return target_pos_onnavmesh;
|
|
}
|
|
else
|
|
{
|
|
return self.current_pathto_pos;
|
|
}
|
|
}
|
|
else if ( isdefined( self.enemy ) )
|
|
{
|
|
if ( DistanceSquared( target_pos, target_pos_onnavmesh ) > ( (self.settings.detonation_distance * 0.9) * (self.settings.detonation_distance * 0.9) ) )
|
|
{
|
|
self SetPersonalThreatBias( self.enemy, -2000, 5.0 );
|
|
}
|
|
}
|
|
|
|
if( isdefined( enemy ) && IsPlayer( enemy ) )
|
|
{
|
|
enemy_vel_offset = enemy GetVelocity() * 0.5;
|
|
|
|
enemy_look_dir_offset = AnglesToForward( enemy.angles );
|
|
if( distance2dSquared( self.origin, enemy.origin ) > ( (500) * (500) ) )
|
|
{
|
|
enemy_look_dir_offset *= 110;
|
|
}
|
|
else
|
|
{
|
|
enemy_look_dir_offset *= 35;
|
|
}
|
|
|
|
offset = enemy_vel_offset + enemy_look_dir_offset;
|
|
offset = ( offset[0], offset[1], 0 ); // just 2d
|
|
|
|
if( TracePassedOnNavMesh( target_pos_onnavmesh, target_pos + offset ) )
|
|
{
|
|
target_pos += offset;
|
|
}
|
|
else
|
|
{
|
|
target_pos = target_pos_onnavmesh;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
target_pos = target_pos_onnavmesh;
|
|
}
|
|
|
|
return target_pos;
|
|
}
|
|
|
|
function path_update_interrupt()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "change_state" );
|
|
self endon( "near_goal" );
|
|
self endon( "reached_end_node" );
|
|
|
|
//ensure only one path_update_interrupt is running
|
|
self notify( "clear_interrupt_threads" );
|
|
self endon( "clear_interrupt_threads" );
|
|
|
|
wait .1; // sometimes endons may get fired off so wait a bit for the goal to get updated
|
|
|
|
while( 1 )
|
|
{
|
|
if( isdefined( self.current_pathto_pos ) )
|
|
{
|
|
if( distance2dSquared( self.current_pathto_pos, self.goalpos ) > ( (self.goalRadius) * (self.goalRadius) ) )
|
|
{
|
|
wait 0.5;
|
|
|
|
self notify( "near_goal" );
|
|
}
|
|
|
|
targetPos = raps_get_target_position();
|
|
if ( isdefined( targetPos ) )
|
|
{
|
|
// optimization, don't keep repathing as often when far away
|
|
if( DistanceSquared( self.origin, targetPos ) > ( (400) * (400) ) )
|
|
{
|
|
repath_range = self.settings.repath_range * 2;
|
|
wait 0.1;
|
|
}
|
|
else
|
|
{
|
|
repath_range = self.settings.repath_range;
|
|
}
|
|
|
|
if( distance2dSquared( self.current_pathto_pos, targetPos ) > ( (repath_range) * (repath_range) ) )
|
|
{
|
|
if( isdefined( self.sndAlias ) && isdefined( self.sndAlias["direction"] ) )
|
|
{
|
|
self playsound( self.sndAlias["direction"] );
|
|
}
|
|
|
|
self notify( "near_goal" );
|
|
}
|
|
}
|
|
|
|
if( isdefined( self.enemy ) && IsPlayer( self.enemy ) && !isdefined(self.slow_trigger) )
|
|
{
|
|
forward = AnglesToForward( self.enemy GetPlayerAngles() );
|
|
dir_to_raps = self.origin - self.enemy.origin;
|
|
|
|
speedToUse = self.settings.defaultMoveSpeed;
|
|
if( IsDefined( self._override_raps_combat_speed ) )
|
|
{
|
|
speedToUse = self._override_raps_combat_speed;
|
|
}
|
|
|
|
if( VectorDot( forward, dir_to_raps ) > 0 )
|
|
{
|
|
self SetSpeed( speedToUse );
|
|
}
|
|
else
|
|
{
|
|
self SetSpeed( speedToUse * 0.75 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
speedToUse = self.settings.defaultMoveSpeed;
|
|
if( IsDefined( self._override_raps_combat_speed ) )
|
|
{
|
|
speedToUse = self._override_raps_combat_speed;
|
|
}
|
|
self SetSpeed( speedToUse );
|
|
}
|
|
|
|
wait 0.2;
|
|
}
|
|
else
|
|
{
|
|
wait 0.4;
|
|
}
|
|
}
|
|
}
|
|
|
|
function collision_fx( normal )
|
|
{
|
|
tilted = ( normal[2] < 0.6 );
|
|
fx_origin = self.origin - normal * ( tilted ? 28 : 10 );
|
|
|
|
if( isdefined( self.sndAlias["vehRapsCollision"] ) )
|
|
self PlaySound( self.sndAlias["vehRapsCollision"] );
|
|
|
|
//PlayFX( level._effect[ "drone_nudge" ], fx_origin, normal );
|
|
}
|
|
|
|
function nudge_collision()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "change_state" );
|
|
self notify( "end_nudge_collision" );
|
|
self endon( "end_nudge_collision" );
|
|
|
|
while ( 1 )
|
|
{
|
|
self waittill( "veh_collision", velocity, normal );
|
|
ang_vel = self GetAngularVelocity() * 0.8;
|
|
self SetAngularVelocity( ang_vel );
|
|
|
|
// bounce off walls
|
|
if ( IsAlive( self ) && VectorDot( normal, (0,0,1) ) < 0.5 ) // angle is more than 60 degree away from up direction
|
|
{
|
|
self SetVehVelocity( self.velocity + normal * 400 );
|
|
self collision_fx( normal );
|
|
}
|
|
}
|
|
}
|
|
|
|
function raps_AllowFriendlyFireDamage( eInflictor, eAttacker, sMeansOfDeath, weapon )
|
|
{
|
|
if( isdefined( self.owner ) && ( eAttacker == self.owner ) && isdefined( self.settings.friendly_fire ) && int( self.settings.friendly_fire ) && !weapon.isEmp )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if ( isdefined( eAttacker ) && isdefined( eAttacker.archetype ) && isdefined( sMeansOfDeath )
|
|
&& eAttacker.archetype == "raps" && sMeansOfDeath == "MOD_EXPLOSIVE" )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function detonate_sides(eInflictor)
|
|
{
|
|
forward_direction = AnglesToForward( self.angles );
|
|
up_direction = AnglesToUp( self.angles );
|
|
|
|
origin = self.origin + VectorScale(up_direction, 15);
|
|
|
|
right_direction = VectorCross(forward_direction, up_direction);
|
|
right_direction = VectorNormalize(right_direction);
|
|
|
|
left_direction = VectorScale(right_direction, -1);
|
|
|
|
eInflictor CylinderDamage(VectorScale(right_direction, 140), origin, 15, 50, self.radiusdamagemax, self.radiusdamagemax / 5, self, "MOD_EXPLOSIVE", self.turretWeapon);
|
|
eInflictor CylinderDamage(VectorScale(left_direction, 140), origin, 15, 50, self.radiusdamagemax, self.radiusdamagemax / 5, self, "MOD_EXPLOSIVE", self.turretWeapon);
|
|
|
|
self.bSideDetonation = true;
|
|
}
|
|
|
|
function raps_callback_damage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, damageFromUnderneath, modelIndex, partName, vSurfaceNormal )
|
|
{
|
|
if ( self.drop_deploying === true && sMeansOfDeath == "MOD_TRIGGER_HURT" && ( !isdefined( self.hurt_trigger_immune_end_time ) || ( GetTime() < self.hurt_trigger_immune_end_time ) ) )
|
|
return 0;
|
|
|
|
if ( isdefined( eAttacker ) && isdefined( eAttacker.archetype ) && isdefined( sMeansOfDeath )
|
|
&& eAttacker.archetype == "raps" && sMeansOfDeath == "MOD_EXPLOSIVE" )
|
|
{
|
|
if ( eAttacker != self && isdefined( vDir ) && lengthSquared( vDir ) > 0.1 && ( !isdefined( eAttacker ) || eAttacker.team === self.team ) && ( !isdefined( eInflictor ) || eInflictor.team === self.team ) )
|
|
{
|
|
self SetVehVelocity( self.velocity + VectorNormalize( vDir ) * 300 );
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if ( vehicle_ai::should_emp( self, weapon, sMeansOfDeath, eInflictor, eAttacker ) )
|
|
{
|
|
minEmpDownTime = 0.8 * self.settings.empdowntime;
|
|
maxEmpDownTime = 1.2 * self.settings.empdowntime;
|
|
self notify ( "emped", RandomFloatRange( minEmpDownTime, maxEmpDownTime ), eAttacker, eInflictor );
|
|
}
|
|
|
|
if ( vehicle_ai::should_burn( self, weapon, sMeansOfDeath, eInflictor, eAttacker ) )
|
|
{
|
|
self thread vehicle_ai::burning_thread( eAttacker, eInflictor );
|
|
}
|
|
|
|
return iDamage;
|
|
}
|
|
|
|
function slow_raps_trigger()
|
|
{
|
|
self endon( "death" );
|
|
|
|
while( 1 )
|
|
{
|
|
self waittill( "trigger", other );
|
|
|
|
if( IsVehicle( other ) && IsDefined( other.archetype ) && other.archetype == "raps" )
|
|
{
|
|
other thread slow_raps( self );
|
|
}
|
|
|
|
wait(0.1);
|
|
}
|
|
}
|
|
|
|
function slow_raps( trigger )
|
|
{
|
|
self notify( "slow_raps" );
|
|
self endon( "slow_raps" );
|
|
self endon( "death" );
|
|
|
|
self.slow_trigger = true;
|
|
if( IsDefined( trigger.script_int ) )
|
|
{
|
|
if( IsDefined( self._override_raps_combat_speed ) && self._override_raps_combat_speed < trigger.script_int )
|
|
{
|
|
self SetSpeedImmediate( self._override_raps_combat_speed );
|
|
}
|
|
else
|
|
{
|
|
self SetSpeedImmediate( trigger.script_int, 200, 200 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( IsDefined( self._override_raps_combat_speed ) && self._override_raps_combat_speed < 0.5 * self.settings.defaultMoveSpeed )
|
|
{
|
|
self SetSpeed( self._override_raps_combat_speed );
|
|
}
|
|
else
|
|
{
|
|
self SetSpeed( 0.5 * self.settings.defaultMoveSpeed );
|
|
}
|
|
}
|
|
|
|
wait 1;
|
|
|
|
self ResumeSpeed();
|
|
self.slow_trigger = undefined;
|
|
}
|
|
|
|
function force_get_enemies()
|
|
{
|
|
if( isdefined( level.raps_force_get_enemies ) )
|
|
{
|
|
return self [[level.raps_force_get_enemies]]();
|
|
}
|
|
|
|
foreach( player in level.players )
|
|
{
|
|
if( self util::IsEnemyPlayer( player ) && !player.ignoreme )
|
|
{
|
|
self GetPerfectInfo( player );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
function sndFunctions()
|
|
{
|
|
self.sndAlias = [];
|
|
self.sndAlias["inAir"] = "veh_raps_in_air";
|
|
self.sndAlias["land"] = "veh_raps_land";
|
|
self.sndAlias["spawn"] = "veh_raps_spawn";
|
|
self.sndAlias["direction"] = "veh_raps_direction";
|
|
self.sndAlias["jump_up"] = "veh_raps_jump_up";
|
|
self.sndAlias["vehRapsClose250"] = "veh_raps_close_250";
|
|
self.sndAlias["vehRapsClose1500"] = "veh_raps_close_1500";
|
|
self.sndAlias["vehRapsTargeting"] = "veh_raps_targeting";
|
|
self.sndAlias["vehRapsAlarm"] = "evt_raps_alarm";
|
|
self.sndAlias["vehRapsCollision"] = "veh_wasp_wall_imp";
|
|
|
|
if( isdefined( self.vehicletype ) && (self.vehicletype == "spawner_enemy_zombie_vehicle_raps_suicide" || self.vehicletype == "spawner_zombietron_veh_meatball" || self.vehicletype == "spawner_zombietron_veh_meatball_med" || self.vehicletype == "spawner_zombietron_veh_meatball_small") )
|
|
{
|
|
self.sndAlias["inAir"] = "zmb_meatball_in_air";
|
|
self.sndAlias["land"] = "zmb_meatball_land";
|
|
self.sndAlias["spawn"] = undefined;
|
|
self.sndAlias["direction"] = undefined;
|
|
self.sndAlias["jump_up"] = "zmb_meatball_jump_up";
|
|
self.sndAlias["vehRapsClose250"] = "zmb_meatball_close_250";
|
|
self.sndAlias["vehRapsClose1500"] = undefined;
|
|
self.sndAlias["vehRapsTargeting"] = "zmb_meatball_targeting";
|
|
self.sndAlias["vehRapsAlarm"] = undefined;
|
|
self.sndAlias["vehRapsCollision"] = "zmb_meatball_collision";
|
|
}
|
|
|
|
if( self isDrivablePlayerVehicle() )
|
|
{
|
|
self thread drivableRapsInAir();
|
|
}
|
|
else
|
|
{
|
|
self thread raps_in_air_audio();
|
|
|
|
if( SessionModeIsCampaignGame() || SessionModeIsZombiesGame() )
|
|
self thread raps_spawn_audio();
|
|
}
|
|
}
|
|
function drivableRapsInAir()
|
|
{
|
|
self endon ("death");
|
|
|
|
while(1)
|
|
{
|
|
self waittill ("veh_landed");
|
|
if( isdefined( self.sndAlias["land"] ) )
|
|
self playsound (self.sndAlias["land"]);
|
|
}
|
|
}
|
|
function raps_in_air_audio() //need to move to client at some point
|
|
{
|
|
self endon ("death");
|
|
|
|
if( !SessionModeIsCampaignGame() && !SessionModeIsZombiesGame() )
|
|
self waittill( "veh_landed" );
|
|
|
|
while(1)
|
|
{
|
|
self waittill ("veh_inair"); //notify sent from the vehicle system when all four 'tires' are off the scripts\cp\cp_mi_zurich_newworld_underground
|
|
|
|
if( isdefined( self.sndAlias["inAir"] ) )
|
|
self playsound (self.sndAlias["inAir"]);
|
|
|
|
self waittill ("veh_landed");
|
|
|
|
if( isdefined( self.sndAlias["land"] ) )
|
|
self playsound (self.sndAlias["land"]);
|
|
}
|
|
|
|
}
|
|
function raps_spawn_audio() //need to move to client at some point
|
|
{
|
|
self endon( "death" );
|
|
wait randomfloatrange (0.25, 1.5);
|
|
|
|
if( isdefined( self.sndAlias["spawn"] ) )
|
|
self playsound (self.sndAlias["spawn"]);
|
|
}
|
|
function isDrivablePlayerVehicle()
|
|
{
|
|
str_vehicletype = self.vehicletype;
|
|
if (isdefined( str_vehicletype ) && StrEndsWith( str_vehicletype, "_player" ) )
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function do_death_fx()
|
|
{
|
|
self vehicle::do_death_dynents();
|
|
|
|
if(isdefined(self.bSideDetonation ))
|
|
self clientfield::set( "raps_side_deathfx", 1 );
|
|
else
|
|
self clientfield::set( "deathfx", 1 );
|
|
}
|
|
|
|
|
|
//function play_rumble_on_raps()
|
|
//{
|
|
// self endon( "death" );
|
|
//
|
|
// while( 1 )
|
|
// {
|
|
// vr = Abs( self GetSpeed() / self GetMaxSpeed() );
|
|
//
|
|
// if( vr >= 0.1 )
|
|
// {
|
|
// self PlayRumbleOnEntity( "damage_heavy" );
|
|
// }
|
|
//
|
|
// Wait 0.1;
|
|
// }
|
|
//
|
|
//}
|