2009 lines
49 KiB
Plaintext
2009 lines
49 KiB
Plaintext
#include maps\_utility;
|
|
#include common_scripts\utility;
|
|
#using_animtree( "generic_human" );
|
|
|
|
init_mgTurretsettings()
|
|
{
|
|
level.mgTurretSettings[ "easy" ][ "convergenceTime" ] = 2.5;
|
|
level.mgTurretSettings[ "easy" ][ "suppressionTime" ] = 3.0;
|
|
level.mgTurretSettings[ "easy" ][ "accuracy" ] = 0.38;
|
|
level.mgTurretSettings[ "easy" ][ "aiSpread" ] = 2;
|
|
level.mgTurretSettings[ "easy" ][ "playerSpread" ] = 0.5;
|
|
|
|
level.mgTurretSettings[ "medium" ][ "convergenceTime" ] = 1.5;
|
|
level.mgTurretSettings[ "medium" ][ "suppressionTime" ] = 3.0;
|
|
level.mgTurretSettings[ "medium" ][ "accuracy" ] = 0.38;
|
|
level.mgTurretSettings[ "medium" ][ "aiSpread" ] = 2;
|
|
level.mgTurretSettings[ "medium" ][ "playerSpread" ] = 0.5;
|
|
|
|
level.mgTurretSettings[ "hard" ][ "convergenceTime" ] = .8;
|
|
level.mgTurretSettings[ "hard" ][ "suppressionTime" ] = 3.0;
|
|
level.mgTurretSettings[ "hard" ][ "accuracy" ] = 0.38;
|
|
level.mgTurretSettings[ "hard" ][ "aiSpread" ] = 2;
|
|
level.mgTurretSettings[ "hard" ][ "playerSpread" ] = 0.5;
|
|
|
|
level.mgTurretSettings[ "fu" ][ "convergenceTime" ] = .4;
|
|
level.mgTurretSettings[ "fu" ][ "suppressionTime" ] = 3.0;
|
|
level.mgTurretSettings[ "fu" ][ "accuracy" ] = 0.38;
|
|
level.mgTurretSettings[ "fu" ][ "aiSpread" ] = 2;
|
|
level.mgTurretSettings[ "fu" ][ "playerSpread" ] = 0.5;
|
|
}
|
|
|
|
main()
|
|
{
|
|
if ( getDvar( "mg42" ) == "" )
|
|
setDvar( "mgTurret", "off" );
|
|
|
|
level.magic_distance = 24;
|
|
|
|
turretInfos = getEntArray( "turretInfo", "targetname" );
|
|
for ( index = 0; index < turretInfos.size; index++ )
|
|
turretInfos[ index ] delete();
|
|
}
|
|
|
|
portable_mg_behavior()
|
|
{
|
|
// turret guys have the turret attached to precache it. This'll have to
|
|
// support allied guns or different guns as we add more, but right now
|
|
// it can be generic
|
|
self detach( "weapon_mg42_carry", "tag_origin" );
|
|
self endon( "death" );
|
|
|
|
// first set his goalpos and goalradius
|
|
self.goalradius = level.default_goalradius;
|
|
if ( isdefined( self.target ) )
|
|
{
|
|
node = getnode( self.target, "targetname" );
|
|
if ( isdefined( node ) )
|
|
{
|
|
if ( isdefined( node.radius ) )
|
|
self.goalradius = node.radius;
|
|
|
|
self setgoalnode( node );
|
|
}
|
|
}
|
|
|
|
while ( !isdefined( self.node ) )
|
|
{
|
|
// wait until the AI picks a node to run to
|
|
wait( 0.05 );
|
|
}
|
|
|
|
// try to find a turret to run to
|
|
|
|
// first try the node we're targetted to
|
|
turret_node = undefined;
|
|
if ( isdefined( self.target ) )
|
|
{
|
|
node = getnode( self.target, "targetname" );
|
|
turret_node = node;
|
|
}
|
|
|
|
// next try the node we're going to, maybe its a turret node
|
|
if ( !isdefined( turret_node ) )
|
|
{
|
|
turret_node = self.node;
|
|
}
|
|
|
|
// just be a normal guy if we're not going to a turret node
|
|
if ( !isdefined( turret_node ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( turret_node.type != "Turret" )
|
|
return;
|
|
|
|
taken_nodes = getTakenNodes();
|
|
taken_nodes[ self.node.origin + "" ] = undefined;// clear our own node since its taken by us
|
|
|
|
// some random AI has this node already, probably doing cover there
|
|
if ( isdefined( taken_nodes[ turret_node.origin + "" ] ) )
|
|
return;
|
|
|
|
turret = turret_node.turret;
|
|
|
|
if ( isdefined( turret.reserved ) )
|
|
{
|
|
assert( turret.reserved != self );
|
|
return;
|
|
}
|
|
|
|
reserve_turret( turret );
|
|
|
|
// we're not at the turret position so we have to run to it
|
|
if ( turret.isSetup )
|
|
{
|
|
// its already setup so just go there and use it
|
|
leave_gun_and_run_to_new_spot( turret );
|
|
}
|
|
else
|
|
{
|
|
// its not setup yet so go there and set it up then use it
|
|
run_to_new_spot_and_setup_gun( turret );
|
|
}
|
|
|
|
maps\_mg_penetration::gunner_think( turret_node.turret );
|
|
}
|
|
|
|
|
|
|
|
|
|
mg42_trigger()
|
|
{
|
|
self waittill( "trigger" );
|
|
level notify( self.targetname );
|
|
level.mg42_trigger[ self.targetname ] = true;
|
|
// println ("trigger at ", self getorigin()," was triggered");
|
|
self delete();
|
|
}
|
|
|
|
mgTurret_auto( trigger )
|
|
{
|
|
trigger waittill( "trigger" );
|
|
ai = getaiarray( "bad_guys" );
|
|
for ( i = 0;i < ai.size;i++ )
|
|
{
|
|
if ( ( isdefined( ai[ i ].script_mg42auto ) ) && ( trigger.script_mg42auto == ai[ i ].script_mg42auto ) )
|
|
{
|
|
ai[ i ] notify( "auto_ai" );
|
|
println( "^a ai auto on!" );
|
|
}
|
|
}
|
|
|
|
spawners = getspawnerarray();
|
|
for ( i = 0;i < spawners.size;i++ )
|
|
if ( ( isdefined( spawners[ i ].script_mg42auto ) ) && ( trigger.script_mg42auto == spawners[ i ].script_mg42auto ) )
|
|
{
|
|
spawners[ i ].ai_mode = "auto_ai";
|
|
println( "^aspawner ", i, " set to auto" );
|
|
}
|
|
|
|
maps\_spawner::kill_trigger( trigger );
|
|
}
|
|
|
|
mg42_suppressionFire( targets )
|
|
{
|
|
self endon( "death" );
|
|
self endon( "stop_suppressionFire" );
|
|
if ( !isdefined( self.suppresionFire ) )
|
|
self.suppresionFire = true;
|
|
|
|
for ( ;; )
|
|
{
|
|
while ( self.suppresionFire )
|
|
{
|
|
self settargetentity( targets[ randomint( targets.size ) ] );
|
|
wait( 2 + randomfloat( 2 ) );
|
|
}
|
|
|
|
self cleartargetentity();
|
|
while ( !self.suppresionFire )
|
|
wait 1;
|
|
}
|
|
}
|
|
|
|
manual_think( mg42 )// For regular spawned guys that are told to use mg42s manually vs static target
|
|
{
|
|
org = self.origin;
|
|
self waittill( "auto_ai" );
|
|
mg42 notify( "stopfiring" );
|
|
mg42 setmode( "auto_ai" );// auto, auto_ai, manual
|
|
mg42 settargetentity( level.player );
|
|
/*
|
|
mg42 setmode("auto_ai"); // auto, auto_ai, manual
|
|
self useturret(mg42); // dude should be near the mg42
|
|
self.favoriteEnemy = level.player;
|
|
// self doDamage ( 25, self.origin );
|
|
*/
|
|
}
|
|
|
|
burst_fire_settings( setting )
|
|
{
|
|
if ( setting == "delay" )
|
|
return 0.2;
|
|
else
|
|
if ( setting == "delay_range" )
|
|
return 0.5;
|
|
else
|
|
if ( setting == "burst" )
|
|
return 0.5;
|
|
else
|
|
// if (setting == "burst_range")
|
|
return 1.5;
|
|
}
|
|
|
|
burst_fire_unmanned()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "stop_burst_fire_unmanned" );
|
|
|
|
if ( isdefined( self.script_delay_min ) )
|
|
mg42_delay = self.script_delay_min;
|
|
else
|
|
mg42_delay = burst_fire_settings( "delay" );
|
|
|
|
if ( isdefined( self.script_delay_max ) )
|
|
mg42_delay_range = self.script_delay_max - mg42_delay;
|
|
else
|
|
mg42_delay_range = burst_fire_settings( "delay_range" );
|
|
|
|
if ( isdefined( self.script_burst_min ) )
|
|
mg42_burst = self.script_burst_min;
|
|
else
|
|
mg42_burst = burst_fire_settings( "burst" );
|
|
|
|
if ( isdefined( self.script_burst_max ) )
|
|
mg42_burst_range = self.script_burst_max - mg42_burst;
|
|
else
|
|
mg42_burst_range = burst_fire_settings( "burst_range" );
|
|
|
|
pauseUntilTime = gettime();
|
|
turretState = "start";
|
|
|
|
for ( ;; )
|
|
{
|
|
duration = ( pauseUntilTime - gettime() ) * 0.001;
|
|
if ( self isFiringTurret() && ( duration <= 0 ) )
|
|
{
|
|
if ( turretState != "fire" )
|
|
{
|
|
turretState = "fire";
|
|
|
|
// self setAnimKnobRestart(%standMG42gun_fire_foward);
|
|
|
|
thread DoShoot();
|
|
}
|
|
|
|
duration = mg42_burst + randomfloat( mg42_burst_range );
|
|
|
|
//println("fire duration: ", duration);
|
|
thread TurretTimer( duration );
|
|
|
|
self waittill( "turretstatechange" );// code or script
|
|
|
|
duration = mg42_delay + randomfloat( mg42_delay_range );
|
|
//println("stop fire duration: ", duration);
|
|
|
|
pauseUntilTime = gettime() + int( duration * 1000 );
|
|
}
|
|
else
|
|
{
|
|
if ( turretState != "aim" )
|
|
{
|
|
turretState = "aim";
|
|
|
|
// self setAnimKnobRestart(%standMG42gun_aim_foward);
|
|
}
|
|
|
|
//println("aim duration: ", duration);
|
|
thread TurretTimer( duration );
|
|
|
|
self waittill( "turretstatechange" );// code or script
|
|
}
|
|
}
|
|
}
|
|
|
|
DoShoot()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "turretstatechange" );// code or script
|
|
|
|
for ( ;; )
|
|
{
|
|
self ShootTurret();
|
|
wait 0.1;
|
|
}
|
|
}
|
|
|
|
TurretTimer( duration )
|
|
{
|
|
if ( duration <= 0 )
|
|
return;
|
|
|
|
self endon( "turretstatechange" );// code
|
|
|
|
//println("start turret timer");
|
|
|
|
wait duration;
|
|
if ( isdefined( self ) )
|
|
self notify( "turretstatechange" );
|
|
|
|
//println("end turret timer");
|
|
}
|
|
|
|
random_spread( ent )
|
|
{
|
|
self endon( "death" );
|
|
|
|
self notify( "stop random_spread" );
|
|
self endon( "stop random_spread" );
|
|
|
|
self endon( "stopfiring" );
|
|
self settargetentity( ent );
|
|
|
|
while ( 1 )
|
|
{
|
|
if ( isplayer( ent ) )
|
|
ent.origin = self.manual_target getorigin();
|
|
else
|
|
ent.origin = self.manual_target.origin;
|
|
|
|
ent.origin += ( 20 - randomfloat( 40 ), 20 - randomfloat( 40 ), 20 - randomfloat( 60 ) );
|
|
wait( 0.2 );
|
|
}
|
|
}
|
|
|
|
mg42_firing( mg42 )
|
|
{
|
|
self notify( "stop_using_built_in_burst_fire" );
|
|
self endon( "stop_using_built_in_burst_fire" );
|
|
|
|
mg42 stopfiring();
|
|
|
|
while ( 1 )
|
|
{
|
|
mg42 waittill( "startfiring" );
|
|
self thread burst_fire( mg42 );
|
|
mg42 startfiring();
|
|
|
|
mg42 waittill( "stopfiring" );
|
|
mg42 stopfiring();
|
|
}
|
|
}
|
|
|
|
|
|
burst_fire( mg42, manual_target )
|
|
{
|
|
mg42 endon( "stopfiring" );
|
|
self endon( "stop_using_built_in_burst_fire" );
|
|
|
|
|
|
if ( isdefined( mg42.script_delay_min ) )
|
|
mg42_delay = mg42.script_delay_min;
|
|
else
|
|
mg42_delay = maps\_mgturret::burst_fire_settings( "delay" );
|
|
|
|
if ( isdefined( mg42.script_delay_max ) )
|
|
mg42_delay_range = mg42.script_delay_max - mg42_delay;
|
|
else
|
|
mg42_delay_range = maps\_mgturret::burst_fire_settings( "delay_range" );
|
|
|
|
if ( isdefined( mg42.script_burst_min ) )
|
|
mg42_burst = mg42.script_burst_min;
|
|
else
|
|
mg42_burst = maps\_mgturret::burst_fire_settings( "burst" );
|
|
|
|
if ( isdefined( mg42.script_burst_max ) )
|
|
mg42_burst_range = mg42.script_burst_max - mg42_burst;
|
|
else
|
|
mg42_burst_range = maps\_mgturret::burst_fire_settings( "burst_range" );
|
|
|
|
while ( 1 )
|
|
{
|
|
mg42 startfiring();
|
|
|
|
if ( isdefined( manual_target ) )
|
|
mg42 thread random_spread( manual_target );
|
|
|
|
wait( mg42_burst + randomfloat( mg42_burst_range ) );
|
|
|
|
mg42 stopfiring();
|
|
|
|
wait( mg42_delay + randomfloat( mg42_delay_range ) );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
_spawner_mg42_think()
|
|
{
|
|
if ( !isdefined( self.flagged_for_use ) )
|
|
self.flagged_for_use = false;
|
|
|
|
if ( !isdefined( self.targetname ) )
|
|
return;
|
|
|
|
node = getnode( self.targetname, "target" );
|
|
if ( !isdefined( node ) )
|
|
return;
|
|
|
|
if ( !isdefined( node.script_mg42 ) )
|
|
return;
|
|
|
|
if ( !isdefined( node.mg42_enabled ) )
|
|
node.mg42_enabled = true;
|
|
|
|
self.script_mg42 = node.script_mg42;
|
|
|
|
first_run = true;
|
|
while ( 1 )
|
|
{
|
|
if ( first_run )
|
|
{
|
|
first_run = false;
|
|
|
|
if ( ( isdefined( node.targetname ) ) || ( self.flagged_for_use ) )
|
|
self waittill( "get new user" );
|
|
}
|
|
|
|
if ( !node.mg42_enabled )
|
|
{
|
|
node waittill( "enable mg42" );
|
|
node.mg42_enabled = true;
|
|
}
|
|
|
|
excluders = [];
|
|
ai = getaiarray();
|
|
for ( i = 0;i < ai.size;i++ )
|
|
{
|
|
excluded = true;
|
|
if ( ( isdefined( ai[ i ].script_mg42 ) ) && ( ai[ i ].script_mg42 == self.script_mg42 ) )
|
|
excluded = false;
|
|
|
|
if ( isdefined( ai[ i ].used_an_mg42 ) )
|
|
excluded = true;
|
|
|
|
if ( excluded )
|
|
excluders[ excluders.size ] = ai[ i ];
|
|
}
|
|
|
|
if ( excluders.size )
|
|
ai = maps\_utility::get_closest_ai_exclude( node.origin, undefined, excluders );
|
|
else
|
|
ai = maps\_utility::get_closest_ai( node.origin, undefined );
|
|
excluders = undefined;
|
|
|
|
if ( isdefined( ai ) )
|
|
{
|
|
ai notify( "stop_going_to_node" );
|
|
ai thread maps\_spawner::go_to_node( node );
|
|
ai waittill( "death" );
|
|
}
|
|
else
|
|
self waittill( "get new user" );
|
|
}
|
|
}
|
|
|
|
mg42_think()
|
|
{
|
|
if ( !isdefined( self.ai_mode ) )
|
|
self.ai_mode = "manual_ai";
|
|
|
|
node = getnode( self.target, "targetname" );
|
|
if ( !isdefined( node ) )
|
|
{
|
|
println( "Guy at org ", self.origin, " had no node" );
|
|
return;
|
|
}
|
|
mg42 = getent( node.target, "targetname" );
|
|
mg42.org = node.origin;
|
|
|
|
if ( isdefined( mg42.target ) )
|
|
{
|
|
if ( ( !isdefined( level.mg42_trigger ) ) || ( !isdefined( level.mg42_trigger[ mg42.target ] ) ) )
|
|
{
|
|
level.mg42_trigger[ mg42.target ] = false;
|
|
getent( mg42.target, "targetname" ) thread mg42_trigger();
|
|
}
|
|
trigger = true;
|
|
}
|
|
else
|
|
trigger = false;
|
|
|
|
while ( 1 )
|
|
{
|
|
if ( self.count == 0 )
|
|
return;
|
|
|
|
mg42_gunner = undefined;
|
|
while ( !isdefined( mg42_gunner ) )
|
|
{
|
|
mg42_gunner = self dospawn();
|
|
wait( 1 );
|
|
}
|
|
|
|
// println ("gunner thinking");
|
|
|
|
mg42_gunner thread mg42_gunner_think( mg42, trigger, self.ai_mode );
|
|
mg42_gunner thread mg42_firing( mg42 );
|
|
|
|
mg42_gunner waittill( "death" );
|
|
// println ("gunner thought");
|
|
if ( isdefined( self.script_delay ) )
|
|
wait( self.script_delay );
|
|
else
|
|
if ( ( isdefined( self.script_delay_min ) ) && ( isdefined( self.script_delay_max ) ) )
|
|
wait( self.script_delay_min + randomfloat( self.script_delay_max - self.script_delay_min ) );
|
|
else
|
|
wait( 1 );
|
|
}
|
|
}
|
|
|
|
kill_objects( owner, msg, temp1, temp2 )
|
|
{
|
|
owner waittill( msg );
|
|
if ( isdefined( temp1 ) )
|
|
temp1 delete();
|
|
|
|
if ( isdefined( temp2 ) )
|
|
temp2 delete();
|
|
}
|
|
|
|
mg42_gunner_think( mg42, trigger, ai_mode )
|
|
{
|
|
self endon( "death" );
|
|
|
|
if ( ai_mode == "manual_ai" )
|
|
{
|
|
while ( 1 )
|
|
{
|
|
self thread mg42_gunner_manual_think( mg42, trigger );
|
|
self waittill( "auto_ai" );
|
|
self move_use_turret( mg42, "auto_ai" );// was setmode( "auto_ai" ) and cleartargetentity()
|
|
self waittill( "manual_ai" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while ( 1 )
|
|
{
|
|
self move_use_turret( mg42, "auto_ai", level.player );// was setmode( "auto_ai" ) and settargetentity( level.player )
|
|
self waittill( "manual_ai" );
|
|
self thread mg42_gunner_manual_think( mg42, trigger );
|
|
self waittill( "auto_ai" );
|
|
}
|
|
}
|
|
}
|
|
|
|
player_safe()
|
|
{
|
|
if ( !isdefined( level.player_covertrigger ) )
|
|
return false;
|
|
|
|
if ( level.player getstance() == "prone" )
|
|
return true;
|
|
|
|
if ( ( level.player_covertype == "cow" ) && ( level.player getstance() == "crouch" ) )
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
stance_num()
|
|
{
|
|
if ( level.player getstance() == "prone" )
|
|
return( 0, 0, 5 );
|
|
else
|
|
if ( level.player getstance() == "crouch" )
|
|
return( 0, 0, 25 );
|
|
|
|
return( 0, 0, 50 );
|
|
}
|
|
|
|
mg42_gunner_manual_think( mg42, trigger )
|
|
{
|
|
self endon( "death" );
|
|
self endon( "auto_ai" );
|
|
|
|
// maps\_utility::debug_message ("MANUAL", self.origin);
|
|
|
|
self.pacifist = true;
|
|
self setgoalpos( mg42.org );
|
|
self.goalradius = level.magic_distance;
|
|
self waittill( "goal" );
|
|
|
|
if ( trigger )
|
|
{
|
|
if ( !level.mg42_trigger[ mg42.target ] )
|
|
level waittill( mg42.target );
|
|
}
|
|
|
|
self.pacifist = false;
|
|
|
|
// mg42 setmode("manual_ai"); // auto, auto_ai, manual
|
|
mg42 setmode( "auto_ai" );// auto, auto_ai, manual
|
|
mg42 cleartargetentity();
|
|
targ_org = spawn( "script_origin", ( 0, 0, 0 ) );
|
|
|
|
tempmodel = spawn( "script_model", ( 0, 0, 0 ) );
|
|
tempmodel.scale = 3;
|
|
if ( getdvar( "mg42" ) != "off" )
|
|
tempmodel setmodel( "temp" );
|
|
tempmodel thread temp_think( mg42, targ_org );
|
|
level thread kill_objects( self, "death", targ_org, tempmodel );
|
|
level thread kill_objects( self, "auto_ai", targ_org, tempmodel );
|
|
|
|
mg42.player_target = false;
|
|
mg42timer = 0;
|
|
targets = getentarray( "mg42_target", "targetname" );
|
|
|
|
if ( targets.size > 0 )
|
|
{
|
|
script_targets = true;
|
|
current_org = targets[ randomint( targets.size ) ].origin;
|
|
|
|
self thread shoot_mg42_script_targets( targets );
|
|
self move_use_turret( mg42 );
|
|
self.target_entity = targ_org;
|
|
mg42 setmode( "manual_ai" );// auto, auto_ai, manual
|
|
mg42 settargetentity( targ_org );
|
|
mg42 notify( "startfiring" );
|
|
|
|
mindist = 15;
|
|
wait_time = 0.08;// 2.8 / 20;
|
|
dif = 0.05;// 1 / 20;
|
|
// player_safe_time = gettime() + 5500 + randomfloat (5000);
|
|
targ_org.origin = targets[ randomint( targets.size ) ].origin;
|
|
|
|
shoot_timer = 0;
|
|
// while (gettime() < player_safe_time)
|
|
|
|
while ( !isdefined( level.player_covertrigger ) )
|
|
{
|
|
current_org = targ_org.origin;
|
|
if ( distance( current_org, targets[ self.gun_targ ].origin ) > mindist )
|
|
{
|
|
temp_vec = vectornormalize( targets[ self.gun_targ ].origin - current_org );
|
|
temp_vec = vector_multiply( temp_vec, mindist );
|
|
current_org += temp_vec;
|
|
}
|
|
else
|
|
self notify( "next_target" );
|
|
|
|
targ_org.origin = current_org;
|
|
|
|
wait( 0.1 );
|
|
}
|
|
|
|
while ( 1 )
|
|
{
|
|
for ( i = 0;i < 1;i += dif )
|
|
{
|
|
targ_org.origin = vector_multiply( current_org, 1.0 - i ) +
|
|
vector_multiply( level.player getorigin() + stance_num(), i );
|
|
|
|
if ( player_safe() )
|
|
i = 2;
|
|
|
|
wait( wait_time );
|
|
}
|
|
|
|
old_org = level.player getorigin();
|
|
while ( !player_safe() )
|
|
{
|
|
targ_org.origin = level.player getorigin();
|
|
vec_dif = targ_org.origin - old_org;
|
|
targ_org.origin = targ_org.origin + vec_dif + stance_num();
|
|
old_org = level.player getorigin();
|
|
wait( 0.1 );
|
|
}
|
|
|
|
if ( player_safe() )
|
|
{
|
|
shoot_timer = gettime() + 1500 + randomfloat( 4000 );
|
|
while ( ( player_safe() ) && ( isdefined( level.player_covertrigger.target ) ) && ( gettime() < shoot_timer ) )
|
|
{
|
|
target = getentarray( level.player_covertrigger.target, "targetname" );
|
|
target = target[ randomint( target.size ) ];
|
|
targ_org.origin = target.origin +
|
|
( randomfloat( 30 ) - 15, randomfloat( 30 ) - 15, randomfloat( 40 ) - 60 );
|
|
|
|
wait( 0.1 );
|
|
}
|
|
}
|
|
|
|
self notify( "next_target" );
|
|
while ( player_safe() )
|
|
{
|
|
current_org = targ_org.origin;
|
|
if ( distance( current_org, targets[ self.gun_targ ].origin ) > mindist )
|
|
{
|
|
temp_vec = vectornormalize( targets[ self.gun_targ ].origin - current_org );
|
|
temp_vec = vector_multiply( temp_vec, mindist );
|
|
current_org += temp_vec;
|
|
}
|
|
else
|
|
self notify( "next_target" );
|
|
|
|
targ_org.origin = current_org;
|
|
|
|
wait( 0.1 );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while ( 1 )
|
|
{
|
|
// Play is not safe, shoot player.
|
|
self move_use_turret( mg42 );
|
|
while ( !isdefined( level.player_covertrigger ) )
|
|
{
|
|
if ( !mg42.player_target )
|
|
{
|
|
mg42 settargetentity( level.player );
|
|
mg42.player_target = true;
|
|
// mg42 settargetentity(tempmodel);
|
|
tempmodel.targent = level.player;
|
|
}
|
|
wait( 0.2 );
|
|
}
|
|
|
|
// Player is safe so shoot in front of him.
|
|
mg42 setmode( "manual_ai" );// auto, auto_ai, manual
|
|
self move_use_turret( mg42 );
|
|
mg42 notify( "startfiring" );
|
|
shoot_timer = gettime() + 1500 + randomfloat( 4000 );
|
|
while ( shoot_timer > gettime() )
|
|
{
|
|
if ( isdefined( level.player_covertrigger ) )
|
|
{
|
|
target = getentarray( level.player_covertrigger.target, "targetname" );
|
|
target = target[ randomint( target.size ) ];
|
|
targ_org.origin = target.origin +
|
|
( randomfloat( 30 ) - 15, randomfloat( 30 ) - 15, randomfloat( 40 ) - 60 );
|
|
mg42 settargetentity( targ_org );
|
|
tempmodel.targent = targ_org;
|
|
wait( randomfloat( 1 ) );
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
mg42 notify( "stopfiring" );
|
|
|
|
// Play is still safe, shoot friendlies.
|
|
self move_use_turret( mg42 );
|
|
if ( mg42.player_target )
|
|
{
|
|
mg42 setmode( "auto_ai" );// auto, auto_ai, manual
|
|
mg42 cleartargetentity();
|
|
mg42.player_target = false;
|
|
tempmodel.targent = tempmodel;
|
|
tempmodel.origin = ( 0, 0, 0 );
|
|
}
|
|
|
|
while ( isdefined( level.player_covertrigger ) )
|
|
wait( 0.2 );
|
|
|
|
wait( .750 + randomfloat( .200 ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
shoot_mg42_script_targets( targets )
|
|
{
|
|
self endon( "death" );
|
|
while ( 1 )
|
|
{
|
|
targ_filled = [];
|
|
for ( i = 0;i < targets.size;i++ )
|
|
targ_filled[ i ] = false;
|
|
|
|
for ( i = 0;i < targets.size;i++ )
|
|
{
|
|
self.gun_targ = randomint( targets.size );
|
|
self waittill( "next_target" );
|
|
while ( targ_filled[ self.gun_targ ] )
|
|
{
|
|
self.gun_targ++ ;
|
|
if ( self.gun_targ >= targets.size )
|
|
self.gun_targ = 0;
|
|
}
|
|
|
|
targ_filled[ self.gun_targ ] = true;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
move_use_turret( mg42, aitype, target )
|
|
{
|
|
self setgoalpos( mg42.org );
|
|
self.goalradius = level.magic_distance;
|
|
self waittill( "goal" );
|
|
if ( isdefined( aitype ) && aitype == "auto_ai" )
|
|
{
|
|
mg42 setmode( "auto_ai" );
|
|
if ( isdefined( target ) )
|
|
mg42 settargetentity( target );
|
|
else
|
|
mg42 cleartargetentity();
|
|
}
|
|
self useturret( mg42 );// dude should be near the mg42
|
|
}
|
|
|
|
temp_think( mg42, targ )
|
|
{
|
|
if ( getdvar( "mg42" ) == "off" )
|
|
return;
|
|
|
|
self.targent = self;
|
|
while ( 1 )
|
|
{
|
|
self.origin = targ.origin;
|
|
line( self.origin, mg42.origin, ( 0.2, 0.5, 0.8 ), 0.5 );
|
|
wait( 0.1 );
|
|
}
|
|
}
|
|
|
|
// This is a thread that runs for each turret managing AI users of the turret
|
|
turret_think( node )
|
|
{
|
|
turret = getent( node.auto_mg42_target, "targetname" );
|
|
mintime = 0.5;
|
|
if ( isdefined( turret.script_turret_reuse_min ) )
|
|
mintime = turret.script_turret_reuse_min;
|
|
maxtime = 2;
|
|
if ( isdefined( turret.script_turret_reuse_max ) )
|
|
mintime = turret.script_turret_reuse_max;
|
|
assert( maxtime >= mintime );
|
|
for ( ;; )
|
|
{
|
|
turret waittill( "turret_deactivate" );
|
|
wait( mintime + randomfloat( maxtime - mintime ) );// Wait for a bit before calling the next AI over.
|
|
while ( !( isturretactive( turret ) ) )
|
|
{
|
|
turret_find_user( node, turret );
|
|
wait 1.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
turret_find_user( node, turret )
|
|
{
|
|
ai = getaiarray();
|
|
for ( i = 0;i < ai.size;i++ )
|
|
{
|
|
if ( ai[ i ] isingoal( node.origin ) && ai[ i ] canuseturret( turret ) )
|
|
{
|
|
savekeepclaimed = ai[ i ].keepClaimedNodeIfValid;
|
|
ai[ i ].keepClaimedNodeIfValid = false;
|
|
if ( !( ai[ i ] usecovernode( node ) ) )
|
|
{
|
|
ai[ i ].keepClaimedNodeIfValid = savekeepclaimed;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
setDifficulty()
|
|
{
|
|
init_mgTurretsettings();
|
|
|
|
mg42s = getEntArray( "misc_turret", "code_classname" );
|
|
|
|
difficulty = getdifficulty();
|
|
|
|
for ( index = 0; index < mg42s.size; index++ )
|
|
{
|
|
if ( isdefined( mg42s[ index ].script_skilloverride ) )
|
|
{
|
|
switch( mg42s[ index ].script_skilloverride )
|
|
{
|
|
case "easy":
|
|
difficulty = "easy";
|
|
break;
|
|
case "medium":
|
|
difficulty = "medium";
|
|
break;
|
|
case "hard":
|
|
difficulty = "hard";
|
|
break;
|
|
case "fu":
|
|
difficulty = "fu";
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
}
|
|
mg42_setdifficulty( mg42s[ index ], difficulty );
|
|
}
|
|
}
|
|
|
|
mg42_setdifficulty( mg42, difficulty )
|
|
{
|
|
mg42.convergenceTime = level.mgTurretSettings[ difficulty ][ "convergenceTime" ];
|
|
mg42.suppressionTime = level.mgTurretSettings[ difficulty ][ "suppressionTime" ];
|
|
mg42.accuracy = level.mgTurretSettings[ difficulty ][ "accuracy" ];
|
|
mg42.aiSpread = level.mgTurretSettings[ difficulty ][ "aiSpread" ];
|
|
mg42.playerSpread = level.mgTurretSettings[ difficulty ][ "playerSpread" ];
|
|
}
|
|
|
|
|
|
mg42_target_drones( nonai, team, fakeowner )
|
|
{
|
|
if ( !isdefined( fakeowner ) )
|
|
fakeowner = false;
|
|
self endon( "death" );
|
|
self.dronefailed = false;
|
|
if ( !isdefined( self.script_fireondrones ) )
|
|
self.script_fireondrones = false;
|
|
if ( !isdefined( nonai ) )
|
|
nonai = false;
|
|
self setmode( "manual_ai" );
|
|
difficulty = getdifficulty();
|
|
if ( !isdefined( level.drones ) )
|
|
waitfornewdrone = true;
|
|
else
|
|
waitfornewdrone = false;
|
|
while ( 1 )
|
|
{
|
|
if ( fakeowner && !isdefined( self.fakeowner ) )
|
|
{
|
|
self setmode( "manual" );
|
|
while ( !isdefined( self.fakeowner ) )
|
|
wait .2;
|
|
|
|
}
|
|
else if ( nonai )
|
|
self setmode( "auto_nonai" );
|
|
else
|
|
self setmode( "auto_ai" );
|
|
|
|
if ( waitfornewdrone )
|
|
level waittill( "new_drone" );
|
|
|
|
if ( !isdefined( self.oldconvergencetime ) )
|
|
self.oldconvergencetime = self.convergencetime;
|
|
self.convergencetime = 2;
|
|
|
|
if ( !nonai )
|
|
{
|
|
turretowner = self getturretowner();
|
|
if ( !isalive( turretowner ) || isplayer( turretowner ) )
|
|
{
|
|
wait .05;
|
|
continue;
|
|
}
|
|
else
|
|
team = turretowner.team;
|
|
}
|
|
else
|
|
{
|
|
if ( fakeowner && !isdefined( self.fakeowner ) )
|
|
{
|
|
wait .05;
|
|
continue;
|
|
}
|
|
assert( isdefined( team ) );
|
|
turretowner = undefined;
|
|
}
|
|
if ( team == "allies" )
|
|
targetteam = "axis";
|
|
else
|
|
targetteam = "allies";
|
|
|
|
while ( level.drones[ targetteam ].lastindex )
|
|
{
|
|
//self gettagangles ("tag_flash")
|
|
target = get_bestdrone( targetteam );
|
|
if ( !isdefined( self.script_fireondrones ) || !self.script_fireondrones )
|
|
{
|
|
wait .2;
|
|
break;
|
|
}
|
|
if ( !isdefined( target ) )
|
|
{
|
|
wait .2;
|
|
break;
|
|
}
|
|
if ( nonai )
|
|
self setmode( "manual" );
|
|
else
|
|
self setmode( "manual_ai" );
|
|
|
|
thread drone_fail( target, 3 );
|
|
if ( !self.dronefailed )
|
|
{
|
|
self settargetentity( target.turrettarget );
|
|
self shootturret();
|
|
self startfiring();
|
|
}
|
|
else
|
|
{
|
|
self.dronefailed = false;
|
|
wait .05;
|
|
continue;
|
|
|
|
}
|
|
target waittill_any( "death", "drone_mg42_fail" );
|
|
waittillframeend;
|
|
if ( !nonai && !( isdefined( self getturretowner() ) && self getturretowner() == turretowner ) )
|
|
break;
|
|
}
|
|
self.convergencetime = self.oldconvergencetime;
|
|
self.oldconvergencetime = undefined;
|
|
self cleartargetentity();
|
|
self stopfiring();
|
|
if ( level.drones[ targetteam ].lastindex )
|
|
waitfornewdrone = false;
|
|
else
|
|
waitfornewdrone = true;
|
|
}
|
|
}
|
|
|
|
drone_fail( drone, time )
|
|
{
|
|
self endon( "death" );
|
|
drone endon( "death" );
|
|
timer = gettime() + ( time * 1000 );
|
|
while ( timer > gettime() )
|
|
{
|
|
turrettarget = self getturrettarget( true );
|
|
// bullettracepassed(<start>, <end>, <hit characters>, <ignore entity>)
|
|
if ( !sighttracepassed( self gettagorigin( "tag_flash" ), drone.origin + ( 0, 0, 40 ), 0, drone ) )
|
|
{
|
|
self.dronefailed = true;
|
|
wait .2;
|
|
break;
|
|
}
|
|
else if ( isdefined( turrettarget ) && distance( turrettarget.origin, self.origin ) < distance( self.origin, drone.origin ) )
|
|
{
|
|
self.dronefailed = true;
|
|
wait .1;
|
|
break;
|
|
}
|
|
wait .1;
|
|
}
|
|
// maps\_utility::structarray_swaptolast(level.drones[drone.team],drone);
|
|
maps\_utility::structarray_shuffle( level.drones[ drone.team ], 1 );
|
|
drone notify( "drone_mg42_fail" );
|
|
}
|
|
|
|
get_bestdrone( team )
|
|
{
|
|
//prof_begin("drone_mg42");
|
|
// dotvalue = .88;
|
|
// dist = 9999999;
|
|
if ( level.drones[ team ].lastindex < 1 )
|
|
return;
|
|
ent = undefined;
|
|
dotforward = anglestoforward( self.angles );
|
|
for ( i = 0;i < level.drones[ team ].lastindex;i++ )
|
|
{
|
|
angles = vectortoangles( level.drones[ team ].array[ i ].origin - self.origin );
|
|
forward = anglestoforward( angles );
|
|
if ( vectordot( dotforward, forward ) < .88 )
|
|
continue;
|
|
// if(!sighttracepassed(level.drones[team].array[i].origin,self.origin+(0,0,10),0,level.drones[team].array[i]))
|
|
// continue;
|
|
// newdist = distance(level.drones[team].array[i].origin, self.origin);
|
|
// if (newdist >= dist)
|
|
// continue;
|
|
// dist = newdist;
|
|
ent = level.drones[ team ].array[ i ];
|
|
break;
|
|
}
|
|
aitarget = self getturrettarget( true );
|
|
if ( isdefined( ent ) && isdefined( aitarget ) && distance( self.origin, aitarget.origin ) < distance( self.origin, ent.origin ) )
|
|
ent = undefined; // shoot at ai if they are closer
|
|
//prof_end("drone_mg42");
|
|
return ent;
|
|
}
|
|
|
|
|
|
saw_mgTurretLink( nodes )
|
|
{
|
|
possible_turrets = getEntArray( "misc_turret", "code_classname" );
|
|
turrets = [];
|
|
for ( i = 0; i < possible_turrets.size; i++ )
|
|
{
|
|
if ( isDefined( possible_turrets[ i ].targetname ) )
|
|
continue;
|
|
|
|
if ( isdefined( possible_turrets[ i ].isvehicleattached ) )
|
|
{
|
|
assertEx( possible_turrets[ i ].isvehicleattached != 0, "Setting must be either true or undefined" );
|
|
continue;
|
|
}
|
|
|
|
turrets[ possible_turrets[ i ].origin + "" ] = possible_turrets[ i ];
|
|
}
|
|
|
|
// did we find any turrets?
|
|
if ( !turrets.size )
|
|
return;
|
|
|
|
for ( nodeIndex = 0; nodeIndex < nodes.size; nodeIndex++ )
|
|
{
|
|
node = nodes[ nodeIndex ];
|
|
if ( node.type == "Path" )
|
|
continue;
|
|
if ( node.type == "Begin" )
|
|
continue;
|
|
if ( node.type == "End" )
|
|
continue;
|
|
|
|
nodeForward = anglesToForward( ( 0, node.angles[ 1 ], 0 ) );
|
|
|
|
foreach ( index, turret in turrets )
|
|
{
|
|
if ( distance( node.origin, turret.origin ) > 50 )
|
|
continue;
|
|
|
|
turretForward = anglesToForward( ( 0, turret.angles[ 1 ], 0 ) );
|
|
|
|
dot = vectorDot( nodeForward, turretForward );
|
|
if ( dot < 0.9 )
|
|
continue;
|
|
|
|
node.turretInfo = spawn( "script_origin", turret.origin );
|
|
node.turretInfo.angles = turret.angles;
|
|
node.turretInfo.node = node;
|
|
|
|
node.turretInfo.leftArc = 45;
|
|
node.turretInfo.rightArc = 45;
|
|
node.turretInfo.topArc = 15;
|
|
node.turretInfo.bottomArc = 15;
|
|
|
|
if ( isDefined( turret.leftArc ) )
|
|
node.turretInfo.leftArc = min( turret.leftArc, 45 );
|
|
|
|
if ( isDefined( turret.rightArc ) )
|
|
node.turretInfo.rightArc = min( turret.rightArc, 45 );
|
|
|
|
if ( isDefined( turret.topArc ) )
|
|
node.turretInfo.topArc = min( turret.topArc, 15 );
|
|
|
|
if ( isDefined( turret.bottomArc ) )
|
|
node.turretInfo.bottomArc = min( turret.bottomArc, 15 );
|
|
|
|
|
|
turrets[ index ] = undefined;
|
|
turret delete();
|
|
}
|
|
}
|
|
|
|
/#
|
|
foreach ( turret in turrets )
|
|
{
|
|
assertMsg( "ERROR: turret at " + turret.origin + " could not link to any node!" );
|
|
}
|
|
#/
|
|
}
|
|
|
|
|
|
auto_mgTurretLink( nodes )
|
|
{
|
|
// Attaches MG turrets with targetname auto_mgTurret to cover crouch and stand nodes.
|
|
possible_turrets = getEntArray( "misc_turret", "code_classname" );
|
|
turrets = [];
|
|
for ( i = 0; i < possible_turrets.size; i++ )
|
|
{
|
|
if ( !isDefined( possible_turrets[ i ].targetname ) || tolower( possible_turrets[ i ].targetname ) != "auto_mgturret" )
|
|
continue;
|
|
// if the turret is legit, create a unique string reference to it
|
|
if ( !isdefined( possible_turrets[ i ].export ) )
|
|
continue;
|
|
if ( !isdefined( possible_turrets[ i ].script_dont_link_turret ) )
|
|
turrets[ possible_turrets[ i ].origin + "" ] = possible_turrets[ i ];
|
|
}
|
|
|
|
// did we find any turrets?
|
|
if ( !turrets.size )
|
|
return;
|
|
|
|
for ( nodeIndex = 0; nodeIndex < nodes.size; nodeIndex++ )
|
|
{
|
|
node = nodes[ nodeIndex ];
|
|
if ( node.type == "Path" )
|
|
continue;
|
|
if ( node.type == "Begin" )
|
|
continue;
|
|
if ( node.type == "End" )
|
|
continue;
|
|
// if ( node.type != "Turret" )
|
|
// continue;
|
|
|
|
nodeForward = anglesToForward( ( 0, node.angles[ 1 ], 0 ) );
|
|
|
|
keys = getArrayKeys( turrets );
|
|
for ( i = 0; i < keys.size; i++ )
|
|
{
|
|
turret = turrets[ keys[ i ] ];
|
|
if ( distance( node.origin, turret.origin ) > 70 )
|
|
continue;
|
|
|
|
turretForward = anglesToForward( ( 0, turret.angles[ 1 ], 0 ) );
|
|
|
|
dot = vectorDot( nodeForward, turretForward );
|
|
if ( dot < 0.9 )
|
|
continue;
|
|
|
|
node.turret = turret;
|
|
turret.node = node;
|
|
turret.isSetup = true;
|
|
assertEx( isdefined( turret.export ), "Turret at " + turret.origin + " does not have a .export value but is near a cover node. If you do not want them to link, use .script_dont_link_turret." );
|
|
|
|
// remove the reference for it so that the other nodes dont
|
|
// scan for this turret
|
|
turrets[ keys[ i ] ] = undefined;
|
|
}
|
|
|
|
// assertEx( isdefined( node.turret ), "Cover node at " + node.origin + " has no turret!" );
|
|
}
|
|
|
|
/#
|
|
// err the unclaimed turrets
|
|
keys = getArrayKeys( turrets );
|
|
if ( keys.size )
|
|
{
|
|
println( "The turrets at the following origins were not auto-bound to a node_turret." );
|
|
println( "Set .script_dont_link_turret if you do not want a turret to use a node_turret." );
|
|
for ( i = 0; i < keys.size; i++ )
|
|
{
|
|
println( keys[ i ] );
|
|
}
|
|
assertEx( 0, "Turrets failed to be linked to node_turrets, see list above." );
|
|
}
|
|
#/
|
|
|
|
/*
|
|
// logic that makes the node "call" for ai
|
|
if ( isDefined( node.auto_mgTurret_target ) )
|
|
{
|
|
thread maps\_mgturret::turret_think( node );
|
|
}
|
|
*/
|
|
|
|
|
|
nodes = undefined;
|
|
}
|
|
|
|
|
|
save_turret_sharing_info()
|
|
{
|
|
// shares turrets so a guy at a turret knows where to run if he decides to move the turret
|
|
self.shared_turrets = [];
|
|
self.shared_turrets[ "connected" ] = [];
|
|
self.shared_turrets[ "ambush" ] = [];
|
|
|
|
if ( !isdefined( self.export ) )
|
|
{
|
|
assertEx( !isdefined( self.script_turret_share ), "Turret at " + self.origin + " has script_turret_share but has no .export value, so script_turret_share won't have any effect." );
|
|
assertEx( !isdefined( self.script_turret_ambush ), "Turret at " + self.origin + " has script_turret_ambush but has no .export value, so script_turret_ambush won't have any effect." );
|
|
return;
|
|
}
|
|
|
|
level.shared_portable_turrets[ self.export ] = self;
|
|
|
|
if ( isdefined( self.script_turret_share ) )
|
|
{
|
|
// turn the origin into a unique string
|
|
|
|
// record which turrets share with this one
|
|
strings = strtok( self.script_turret_share, " " );
|
|
|
|
for ( i = 0; i < strings.size; i++ )
|
|
{
|
|
self.shared_turrets[ "connected" ][ strings[ i ] ] = true;
|
|
}
|
|
}
|
|
|
|
if ( isdefined( self.script_turret_ambush ) )
|
|
{
|
|
// turn the origin into a unique string
|
|
|
|
// record which turrets share with this one
|
|
strings = strtok( self.script_turret_ambush, " " );
|
|
|
|
for ( i = 0; i < strings.size; i++ )
|
|
{
|
|
self.shared_turrets[ "ambush" ][ strings[ i ] ] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
mg42setup_gun()
|
|
{
|
|
assertEX (isdefined(self.target), "Portable MG42 guy at origin " + self.origin + " has no target");
|
|
mg42node = getnode (self.target,"targetname");
|
|
mg42 = getent (self.target,"targetname");
|
|
|
|
if ( !isdefined( mg42.shared_turrets ) )
|
|
mg42 save_turret_sharing_info();
|
|
|
|
// If the portable gunner targets a node then he's going to do a chain of nodes to the destination, which should
|
|
// be an mg42. Otherwise he's directly targetting an mg42.
|
|
if (isdefined (mg42node))
|
|
{
|
|
// Set this so later we can run along it as a chain.
|
|
self.mg42node = mg42node;
|
|
assert (!isdefined (mg42), "guy at " + self.origin + " targets an ent and a node");
|
|
for (;;)
|
|
{
|
|
newnode = getnode (mg42node.target,"targetname");
|
|
if (!isdefined (newnode))
|
|
{
|
|
mg42 = getent (mg42node.target,"targetname");
|
|
break;
|
|
}
|
|
mg42node = newnode;
|
|
}
|
|
}
|
|
|
|
assertEX (isdefined(mg42), "Portable MG42 guy at origin " + self.origin + " doesn't target an mg42");
|
|
assertEX (mg42.classname == "misc_turret", "Portable MG42 guy at origin " + self.origin + " doesn't target an mg42");
|
|
if (!isdefined(mg42.isSetup))
|
|
{
|
|
mg42 hide_turret();
|
|
}
|
|
return mg42;
|
|
}
|
|
*/
|
|
|
|
restoreDefaultPitch()
|
|
{
|
|
self notify( "gun_placed_again" );
|
|
self endon( "gun_placed_again" );
|
|
self waittill( "restore_default_drop_pitch" );
|
|
wait( 1 );
|
|
self restoredefaultdroppitch();
|
|
}
|
|
|
|
dropTurret()
|
|
{
|
|
thread dropTurretProc();
|
|
}
|
|
|
|
dropTurretProc()
|
|
{
|
|
turret = spawn( "script_model", ( 0, 0, 0 ) );
|
|
turret.origin = self gettagorigin( level.portable_mg_gun_tag );
|
|
turret.angles = self gettagangles( level.portable_mg_gun_tag );
|
|
turret setmodel( self.turretModel );
|
|
forward = anglestoforward( self.angles );
|
|
forward = vector_multiply( forward, 100 );
|
|
turret moveGravity( forward, 0.5 );
|
|
self detach( self.turretModel, level.portable_mg_gun_tag );
|
|
self.turretmodel = undefined;
|
|
wait( 0.7 );
|
|
turret delete();
|
|
}
|
|
|
|
|
|
turretDeathDetacher()
|
|
{
|
|
self endon( "kill_turret_detach_thread" );
|
|
self endon( "dropped_gun" );
|
|
self waittill( "death" );
|
|
if ( !isdefined( self ) )
|
|
return;// in case many guys die at once and we are removed
|
|
dropTurret();
|
|
}
|
|
|
|
turretDetacher()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "kill_turret_detach_thread" );
|
|
// in case the enemy gets close to a portable turret guy
|
|
self waittill( "dropped_gun" );
|
|
self detach( self.turretModel, level.portable_mg_gun_tag );
|
|
}
|
|
|
|
restoreDefaults()
|
|
{
|
|
// self.goalradius = 350;
|
|
self.run_overrideanim = undefined;
|
|
self set_all_exceptions( animscripts\init::empty );
|
|
}
|
|
|
|
restorePitch()
|
|
{
|
|
self waittill( "turret_deactivate" );
|
|
self restoredefaultdroppitch();
|
|
}
|
|
|
|
update_enemy_target_pos_while_running( ent )
|
|
{
|
|
self endon( "death" );
|
|
self endon( "end_mg_behavior" );
|
|
self endon( "stop_updating_enemy_target_pos" );
|
|
|
|
for ( ;; )
|
|
{
|
|
self waittill( "saw_enemy" );
|
|
ent.origin = self.last_enemy_sighting_position;
|
|
}
|
|
}
|
|
|
|
move_target_pos_to_new_turrets_visibility( ent, new_spot )
|
|
{
|
|
// moves the target position to a point where the new turret
|
|
// can see it. If the position gets updated by seeing an enemy
|
|
// then that position also gets pushed towards the last turret to
|
|
// the point of visibility on the assumption that towards the old
|
|
// turret will bring it into visibility without causing it to
|
|
// go to a weird point.
|
|
|
|
// in any case the whole system probably needs a failsafe in case
|
|
// the target position gets way outside the gun's allowed angles
|
|
|
|
self endon( "death" );
|
|
self endon( "end_mg_behavior" );
|
|
self endon( "stop_updating_enemy_target_pos" );
|
|
|
|
old_turret_pos = self.turret.origin + ( 0, 0, 16 );// turrets are on geo so it could abstruct;
|
|
dest_pos = new_spot.origin + ( 0, 0, 16 );
|
|
|
|
for ( ;; )
|
|
{
|
|
wait( 0.05 );// plenty of time while he runs, doesn't have to happen often
|
|
|
|
if ( sighttracepassed( ent.origin, dest_pos, 0, undefined ) )
|
|
{
|
|
// line( ent.origin, dest_pos, ( 0,1,0 ) );
|
|
continue;
|
|
}
|
|
|
|
// line( ent.origin, dest_pos, ( 1,0,0 ) );
|
|
|
|
// move the target pos towards the last turret position
|
|
angles = vectortoangles( old_turret_pos - ent.origin );
|
|
forward = anglestoforward( angles );
|
|
forward = vector_multiply( forward, 8 );
|
|
|
|
ent.origin = ent.origin + forward;
|
|
}
|
|
}
|
|
|
|
record_bread_crumbs_for_ambush( ent )
|
|
{
|
|
self endon( "death" );
|
|
self endon( "end_mg_behavior" );
|
|
self endon( "stop_updating_enemy_target_pos" );
|
|
|
|
ent.bread_crumbs = [];
|
|
for ( ;; )
|
|
{
|
|
// print3d( self.origin + (0,0,50), "*", (1,1,0), 1, 1.5, 10*20 );
|
|
ent.bread_crumbs[ ent.bread_crumbs.size ] = self.origin + ( 0, 0, 50 );
|
|
wait( 0.35 );
|
|
}
|
|
}
|
|
|
|
aim_turret_at_ambush_point_or_visible_enemy( turret, ent )
|
|
{
|
|
if ( !isalive( self.current_enemy ) && self canSee( self.current_enemy ) )
|
|
{
|
|
// if we can see our enemy then just aim at him
|
|
ent.origin = self.last_enemy_sighting_position;
|
|
return;
|
|
}
|
|
|
|
|
|
forward = anglestoforward( turret.angles );
|
|
|
|
// find the best bread crumb to aim at
|
|
// start a few from the back because the crumbs from while we were walking at the gun
|
|
// arent going to be good
|
|
for ( i = ent.bread_crumbs.size - 3; i >= 0; i -- )
|
|
{
|
|
// dot check it so we're not aiming at the breadcrumbs we walked over
|
|
crumb = ent.bread_crumbs[ i ];
|
|
normal = vectorNormalize( crumb - turret.origin );
|
|
dot = vectorDot( forward, normal );
|
|
if ( dot < 0.75 )
|
|
continue;
|
|
|
|
ent.origin = crumb;
|
|
|
|
// find the first one we cant see and aim there
|
|
if ( sighttracepassed( turret.origin, crumb, 0, undefined ) )
|
|
{
|
|
// linetime( turret.origin, crumb, ( 0, 1, 0 ), 10 );
|
|
continue;
|
|
}
|
|
|
|
// linetime( turret.origin, crumb, ( 1, 0, 0 ), 10 );
|
|
break;
|
|
}
|
|
}
|
|
|
|
find_a_new_turret_spot( ent )
|
|
{
|
|
// find a new spot to go to
|
|
array = get_portable_mg_spot( ent );
|
|
new_spot = array[ "spot" ];
|
|
connection_type = array[ "type" ];
|
|
|
|
if ( !isdefined( new_spot ) )
|
|
return;
|
|
|
|
reserve_turret( new_spot );
|
|
|
|
// if we see the enemy while we run, update the target position
|
|
thread update_enemy_target_pos_while_running( ent );
|
|
thread move_target_pos_to_new_turrets_visibility( ent, new_spot );
|
|
|
|
if ( connection_type == "ambush" )
|
|
{
|
|
thread record_bread_crumbs_for_ambush( ent );
|
|
}
|
|
|
|
if ( new_spot.isSetup )
|
|
{
|
|
leave_gun_and_run_to_new_spot( new_spot );
|
|
}
|
|
else
|
|
{
|
|
pickup_gun( new_spot );
|
|
run_to_new_spot_and_setup_gun( new_spot );
|
|
}
|
|
|
|
self notify( "stop_updating_enemy_target_pos" );
|
|
|
|
if ( connection_type == "ambush" )
|
|
{
|
|
aim_turret_at_ambush_point_or_visible_enemy( new_spot, ent );
|
|
}
|
|
|
|
// thread snap_lock_turret_onto_target( new_spot );
|
|
|
|
new_spot setTargetEntity( ent );
|
|
}
|
|
|
|
snap_lock_turret_onto_target( turret )
|
|
{
|
|
// turn manual on for a moment so he aims quickly to the spot he wants to aim at.
|
|
turret setmode( "manual" );
|
|
wait( 0.5 );
|
|
turret setmode( "manual_ai" );
|
|
}
|
|
|
|
leave_gun_and_run_to_new_spot( spot )
|
|
{
|
|
assert( spot.reserved == self );
|
|
// spot is a bit of a misnomer as its actually a hidden gun we
|
|
// rematerialize when he runs to it
|
|
|
|
self stopuseturret();
|
|
self animscripts\shared::placeWeaponOn( self.primaryweapon, "none" );
|
|
|
|
// run to the spot where the gun is setup
|
|
setup_anim = get_turret_setup_anim( spot );
|
|
org = getStartOrigin( spot.origin, spot.angles, setup_anim );
|
|
self setruntopos( org );
|
|
assertEx( distance( org, self.goalpos ) < self.goalradius, "Tried to set the run pos outside the goalradius" );
|
|
|
|
self waittill( "runto_arrived" );
|
|
|
|
use_the_turret( spot );
|
|
}
|
|
|
|
pickup_gun( spot )
|
|
{
|
|
// spot is a bit of a misnomer as its actually a hidden gun we
|
|
// rematerialize when he runs to it
|
|
|
|
self stopuseturret();
|
|
self.turret hide_turret();
|
|
}
|
|
|
|
get_turret_setup_anim( turret )
|
|
{
|
|
spot_types = [];
|
|
spot_types[ "saw_bipod_stand" ] = level.mg_animmg[ "bipod_stand_setup" ];
|
|
spot_types[ "saw_bipod_crouch" ] = level.mg_animmg[ "bipod_crouch_setup" ];
|
|
spot_types[ "saw_bipod_prone" ] = level.mg_animmg[ "bipod_prone_setup" ];
|
|
|
|
return spot_types[ turret.weaponinfo ];
|
|
}
|
|
|
|
run_to_new_spot_and_setup_gun( spot )
|
|
{
|
|
assert( spot.reserved == self );
|
|
|
|
oldhealth = self.health;
|
|
spot endon( "turret_deactivate" );
|
|
|
|
self.mg42 = spot;// used in the animscript exceptions
|
|
self endon( "death" );
|
|
self endon( "dropped_gun" );
|
|
|
|
setup_anim = get_turret_setup_anim( spot );
|
|
|
|
self.turretModel = "weapon_mg42_carry";
|
|
|
|
// guys are meant to get their gun back automatically when they ditch an mg
|
|
self notify( "kill_get_gun_back_on_killanimscript_thread" );
|
|
self animscripts\shared::placeWeaponOn( self.weapon, "none" );
|
|
if ( self isBadGuy() )
|
|
self.health = 1;
|
|
|
|
// set the run anim
|
|
self.run_overrideanim = %saw_gunner_run_fast;
|
|
self.crouchrun_combatanim = %saw_gunner_run_fast;
|
|
|
|
// attach the carry version of the gun
|
|
self attach( self.turretModel, level.portable_mg_gun_tag );
|
|
thread turretDeathDetacher();
|
|
|
|
// run to the spot where the gun is going to be setup
|
|
org = getStartOrigin( spot.origin, spot.angles, setup_anim );
|
|
self setruntopos( org );
|
|
assertEx( distance( org, self.goalpos ) < self.goalradius, "Tried to set the run pos outside the goalradius" );
|
|
|
|
wait( 0.05 );// must figure out what this wait is needed for
|
|
self set_all_exceptions( animscripts\combat::exception_exposed_mg42_portable );
|
|
clear_exception( "move" );
|
|
set_exception( "cover_crouch", ::hold_indefintely );
|
|
|
|
while ( distance( self.origin, org ) > 16 )
|
|
{
|
|
self setruntopos( org );
|
|
wait( 0.05 );
|
|
}
|
|
// self waittill( "runto_arrived" );
|
|
|
|
self notify( "kill_turret_detach_thread" );
|
|
|
|
if ( self isBadGuy() )
|
|
self.health = oldhealth;
|
|
|
|
|
|
if ( soundexists( "weapon_setup" ) )
|
|
thread play_sound_in_space( "weapon_setup" );
|
|
|
|
self animscripted( "setup_done", spot.origin, spot.angles, setup_anim );
|
|
|
|
restoreDefaults();// reset the run anims
|
|
|
|
self waittillmatch( "setup_done", "end" );
|
|
spot notify( "restore_default_drop_pitch" );
|
|
spot show_turret();
|
|
|
|
self animscripts\shared::placeWeaponOn( self.primaryweapon, "right" );
|
|
|
|
use_the_turret( spot );
|
|
self detach( self.turretModel, level.portable_mg_gun_tag );
|
|
self set_all_exceptions( animscripts\init::empty );
|
|
|
|
self notify( "bcs_portable_turret_setup" );
|
|
}
|
|
|
|
move_to_run_pos()
|
|
{
|
|
self setruntopos( self.runpos );
|
|
}
|
|
|
|
hold_indefintely()
|
|
{
|
|
self endon( "killanimscript" );
|
|
self waittill( "death" );
|
|
}
|
|
|
|
using_a_turret()
|
|
{
|
|
if ( !isdefined( self.turret ) )
|
|
return false;
|
|
|
|
return self.turret.owner == self;
|
|
}
|
|
|
|
|
|
turret_user_moves()
|
|
{
|
|
// we've been forced to move by goalradius change or becoming exposed
|
|
if ( !using_a_turret() )
|
|
{
|
|
clear_exception( "move" );
|
|
return;
|
|
}
|
|
|
|
array = find_connected_turrets( "connected" );
|
|
new_spots = array[ "spots" ];
|
|
|
|
if ( !new_spots.size )
|
|
{
|
|
// none of the turrets in the area we're moving to are connected to the
|
|
// one we were at so we turn back to normal guy now
|
|
clear_exception( "move" );
|
|
return;
|
|
}
|
|
|
|
// lets see if we have a new node, and if we do, if its a compatible turret node
|
|
turret_node = self.node;
|
|
|
|
// if its not, then lets use a random node from the connected nodes that are
|
|
// within our goal radius
|
|
if ( !isdefined( turret_node ) || !is_in_array( new_spots, turret_node ) )
|
|
{
|
|
taken_nodes = getTakenNodes();
|
|
for ( i = 0; i < new_spots.size; i++ )
|
|
{
|
|
turret_node = random( new_spots );
|
|
|
|
// some random AI has this node already, probably doing cover there
|
|
// if we get the ability to push AI off their node then we'll do that here later
|
|
if ( isdefined( taken_nodes[ turret_node.origin + "" ] ) )
|
|
return;
|
|
}
|
|
}
|
|
|
|
turret = turret_node.turret;
|
|
|
|
if ( isdefined( turret.reserved ) )
|
|
{
|
|
assert( turret.reserved != self );
|
|
return;
|
|
}
|
|
|
|
reserve_turret( turret );
|
|
|
|
// we're not at the turret position so we have to run to it
|
|
if ( turret.isSetup )
|
|
{
|
|
// its already setup so just go there and use it
|
|
leave_gun_and_run_to_new_spot( turret );
|
|
}
|
|
else
|
|
{
|
|
// its not setup yet so go there and set it up then use it
|
|
run_to_new_spot_and_setup_gun( turret );
|
|
}
|
|
|
|
maps\_mg_penetration::gunner_think( turret_node.turret );
|
|
}
|
|
|
|
use_the_turret( spot )
|
|
{
|
|
turretWasUsed = self useturret( spot );
|
|
|
|
if ( turretWasUsed )
|
|
{
|
|
set_exception( "move", ::turret_user_moves );// run this before running move so we might move the turret
|
|
|
|
self.turret = spot;
|
|
self thread mg42_firing( spot );// does the burst fire timings
|
|
spot setmode( "manual_ai" );
|
|
spot thread restorePitch();
|
|
self.turret = spot;
|
|
spot.owner = self;
|
|
// self useturret( spot ); // dude should be near the mg42
|
|
// spot setmode( "manual_ai" ); // auto, auto_ai, manual
|
|
// self.turret = spot;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
spot restoredefaultdroppitch();
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
get_portable_mg_spot( ent )
|
|
{
|
|
find_spot_funcs = [];
|
|
find_spot_funcs[ find_spot_funcs.size ] = ::find_different_way_to_attack_last_seen_position;
|
|
find_spot_funcs[ find_spot_funcs.size ] = ::find_good_ambush_spot;
|
|
|
|
find_spot_funcs = array_randomize( find_spot_funcs );
|
|
|
|
for ( i = 0; i < find_spot_funcs.size; i++ )
|
|
{
|
|
array = [[ find_spot_funcs[ i ] ]]( ent );
|
|
|
|
if ( !isdefined( array[ "spots" ] ) )
|
|
continue;
|
|
|
|
array[ "spot" ] = random( array[ "spots" ] );
|
|
return array;
|
|
}
|
|
}
|
|
|
|
getTakenNodes()
|
|
{
|
|
// returns an array of node origins owned by AI
|
|
array = [];
|
|
ai = getaiarray();
|
|
|
|
for ( i = 0; i < ai.size; i++ )
|
|
{
|
|
if ( !isdefined( ai[ i ].node ) )
|
|
continue;
|
|
|
|
array[ ai[ i ].node.origin + "" ] = true;
|
|
}
|
|
|
|
return array;
|
|
}
|
|
|
|
find_connected_turrets( connection_type )
|
|
{
|
|
spots = level.shared_portable_turrets; // an array of shared turrets, using their origin as the index
|
|
usable_spots = [];
|
|
|
|
spot_exports = getArrayKeys( spots );
|
|
|
|
taken_nodes = getTakenNodes();
|
|
taken_nodes[ self.node.origin + "" ] = undefined;
|
|
|
|
// find turrets that share the same keys
|
|
for ( i = 0; i < spot_exports.size; i++ )
|
|
{
|
|
export = spot_exports[ i ];
|
|
if ( spots[ export ] == self.turret )
|
|
continue;
|
|
|
|
|
|
keys = getArrayKeys( self.turret.shared_turrets[ connection_type ] );
|
|
for ( p = 0; p < keys.size; p++ )
|
|
{
|
|
// go through each key that the turret the guy is currently on has,
|
|
// and see if any turrets share keys.
|
|
// cast export as a string because arraykeys returns strings
|
|
if ( spots[ export ].export + "" != keys[ p ] )
|
|
continue;
|
|
|
|
// somebody else has this one or they're running to it
|
|
if ( isdefined( spots[ export ].reserved ) )
|
|
continue;
|
|
|
|
// some random AI has this node already, probably doing cover there
|
|
if ( isdefined( taken_nodes[ spots[ export ].node.origin + "" ] ) )
|
|
continue;
|
|
|
|
// don't pick one that is outside the goalradius
|
|
if ( distance( self.goalpos, spots[ export ].origin ) > self.goalradius )
|
|
continue;
|
|
|
|
// this spot is usable
|
|
usable_spots[ usable_spots.size ] = spots[ export ];
|
|
}
|
|
}
|
|
|
|
array = [];
|
|
// store it so we can figure out the best place for an ambusher to aim
|
|
array[ "type" ] = connection_type;
|
|
array[ "spots" ] = usable_spots;
|
|
return array;
|
|
}
|
|
|
|
find_good_ambush_spot( ent )
|
|
{
|
|
return find_connected_turrets( "ambush" );
|
|
}
|
|
|
|
find_different_way_to_attack_last_seen_position( ent )
|
|
{
|
|
array = find_connected_turrets( "connected" );
|
|
usable_spots = array[ "spots" ];
|
|
|
|
if ( !usable_spots.size )
|
|
return;
|
|
|
|
good_spot = [];
|
|
|
|
// find a spot that has a good fov and LOS on the target spot
|
|
for ( i = 0; i < usable_spots.size; i++ )
|
|
{
|
|
|
|
if ( !within_fov( usable_spots[ i ].origin, usable_spots[ i ].angles, ent.origin, 0.75 ) )
|
|
continue;
|
|
|
|
/*
|
|
normal = vectorNormalize( ent.origin - ( usable_spots[ i ].origin + offset ) );
|
|
forward = anglestoforward( usable_spots[ i ].angles );
|
|
dot = vectorDot( forward, normal );
|
|
|
|
thread linetime( usable_spots[ i ].origin + offset, usable_spots[ i ].origin + offset + vector_multiply( forward, 1000 ), ( 1, 0, 0 ), 10 );
|
|
thread linetime( ent.origin, usable_spots[ i ].origin + offset, ( 0, 0, 1 ), 10 );
|
|
*/
|
|
|
|
if ( !sighttracepassed( ent.origin, usable_spots[ i ].origin + ( 0, 0, 16 ), 0, undefined ) )
|
|
continue;
|
|
|
|
good_spot[ good_spot.size ] = usable_spots[ i ];
|
|
}
|
|
|
|
array[ "spots" ] = good_spot;
|
|
return array;
|
|
}
|
|
|
|
portable_mg_spot()
|
|
{
|
|
save_turret_sharing_info();
|
|
|
|
turret_preplaced = 1;
|
|
self.isSetup = true;
|
|
assert( !isdefined( self.reserved ) );
|
|
self.reserved = undefined;
|
|
if ( isdefined( self.isvehicleattached ) )
|
|
return; // nate
|
|
if ( self.spawnflags & turret_preplaced )
|
|
return;
|
|
|
|
// a spot where a gun could be placed
|
|
hide_turret();
|
|
|
|
}
|
|
|
|
|
|
hide_turret()
|
|
{
|
|
assert( self.isSetup );
|
|
self notify( "stop_checking_for_flanking" );
|
|
self.isSetup = false;
|
|
self hide();
|
|
self.solid = false;
|
|
self makeUnusable();
|
|
self setdefaultdroppitch( 0 );
|
|
self thread restoreDefaultPitch();
|
|
}
|
|
|
|
show_turret()
|
|
{
|
|
self show();
|
|
self.solid = true;
|
|
self makeUsable();
|
|
assert( !self.isSetup );
|
|
self.isSetup = true;
|
|
thread stop_mg_behavior_if_flanked();
|
|
}
|
|
|
|
stop_mg_behavior_if_flanked()
|
|
{
|
|
self endon( "stop_checking_for_flanking" );
|
|
|
|
self waittill( "turret_deactivate" );
|
|
if ( isalive( self.owner ) )
|
|
self.owner notify( "end_mg_behavior" );
|
|
}
|
|
|
|
turret_is_mine( turret )
|
|
{
|
|
owner = turret getTurretOwner();
|
|
if ( !isdefined( owner ) )
|
|
return false;
|
|
|
|
return owner == self;
|
|
}
|
|
|
|
end_turret_reservation( turret )
|
|
{
|
|
waittill_turret_is_released( turret );
|
|
turret.reserved = undefined;
|
|
}
|
|
|
|
waittill_turret_is_released( turret )
|
|
{
|
|
turret endon( "turret_deactivate" );
|
|
self endon( "death" );
|
|
self waittill( "end_mg_behavior" );
|
|
}
|
|
|
|
reserve_turret( turret )
|
|
{
|
|
turret.reserved = self;
|
|
thread end_turret_reservation( turret );
|
|
}
|
|
|
|
|
|
/*QUAKED misc_turret_50cal_turret_technical (1 0 0) (-16 -16 0) (16 16 56) pre-placed
|
|
Spawn Flags:
|
|
pre-placed - Means it already exists in map. Used by script only.
|
|
|
|
Key Pairs:
|
|
leftarc - horizonal left fire arc.
|
|
rightarc - horizonal left fire arc.
|
|
toparc - vertical top fire arc.
|
|
bottomarc - vertical bottom fire arc.
|
|
yawconvergencetime - time (in seconds) to converge horizontally to target.
|
|
pitchconvergencetime - time (in seconds) to converge vertically to target.
|
|
suppressionTime - time (in seconds) that the turret will suppress a target hidden behind cover
|
|
maxrange - maximum firing/sight range.
|
|
aiSpread - spread of the bullets out of the muzzle in degrees when used by the AI
|
|
playerSpread - spread of the bullets out of the muzzle in degrees when used by the player
|
|
defaultmdl="weapon_m2_50cal_center"
|
|
default:"weaponinfo" "50cal_turret_technical"
|
|
default:"targetname" "50cal_turret_technical"
|
|
*/
|
|
|