696 lines
16 KiB
Text
696 lines
16 KiB
Text
#include maps\_utility;
|
|
|
|
// TODO:
|
|
// - Add comments throughout this script
|
|
|
|
init()
|
|
{
|
|
self animscripts\banzai::banzai_init_anims();
|
|
|
|
// Since the Banzai attacks happen in the American Campaigns, we assume we already have the knife in the FF.
|
|
PrecacheModel( "weapon_usa_kbar_knife" );
|
|
PrecacheShellshock( "banzai_impact" );
|
|
PrecacheString( &"SCRIPT_PLATFORM_BANZAI_HINT" );
|
|
PrecacheString( &"SCRIPT_PLATFORM_BANZAI_DEATH_DO_NOTHING" );
|
|
PrecacheString( &"SCRIPT_PLATFORM_BANZAI_DEATH_TOO_SOON" );
|
|
PrecacheString( &"SCRIPT_PLATFORM_BANZAI_DEATH_TOO_LATE" );
|
|
|
|
level._effects[ "stab_wound" ] = loadfx( "impacts/fx_flesh_bayonet_neck" );
|
|
|
|
level thread check_interactive_hands();
|
|
}
|
|
|
|
check_interactive_hands()
|
|
{
|
|
/#
|
|
if( !IsDefined( level.loadoutComplete ) || !level.loadoutComplete )
|
|
{
|
|
level waittill( "loadout complete" );
|
|
}
|
|
|
|
assertEx( IsDefined( level.player_interactive_hands ), "level.player_interactive_hands is not defined. Use _loadout::set_player_interactivate_hands() to set it up." );
|
|
#/
|
|
}
|
|
|
|
spawned_banzai_immediate()
|
|
{
|
|
// Must wait until after the _spawner goalradius check before calling banzai_force, since
|
|
// that calls banzai_charge() and banzai_charge() sets the goalradius.
|
|
if ( spawn_failed( self ) )
|
|
return;
|
|
|
|
banzai_force();
|
|
}
|
|
|
|
spawned_banzai_dynamic()
|
|
{
|
|
banzai();
|
|
}
|
|
|
|
banzai()
|
|
{
|
|
self animscripts\banzai::init();
|
|
|
|
self endon( "death" );
|
|
self endon( "stop_banzai_thread" );
|
|
|
|
if( IsDefined( self.target ) && ( !IsDefined( self.banzai_no_wait ) || ( Isdefined( self.banzai_no_wait ) && !self.banzai_no_wait ) ) )
|
|
{
|
|
// If script_forcegoal isn't true when we reach our goal, the goalradius will get reset.
|
|
self.script_forcegoal = true;
|
|
self waittill( "reached_path_end" );
|
|
}
|
|
|
|
wait_time = 3 + RandomFloat( 2 );
|
|
|
|
self thread banzai_print( "Banzai wait: " + wait_time );
|
|
|
|
wait( wait_time );
|
|
|
|
self thread banzai_print( "Getting nearby Banzai-ers" );
|
|
|
|
others = self get_nearby_banzai_guys();
|
|
|
|
// Plays the "pump up" dialogue, we must wait for it to finish the sound before actually charging.
|
|
self banzai_pump_up();
|
|
|
|
self thread staggered_banzai_charge();
|
|
for( i = 0; i < others.size; i++ )
|
|
{
|
|
if ( isalive(others[i]) && isdefined(others[i]) )
|
|
{
|
|
others[i] thread staggered_banzai_charge();
|
|
}
|
|
}
|
|
}
|
|
|
|
banzai_force()
|
|
{
|
|
self animscripts\banzai::init();
|
|
|
|
self endon( "death" );
|
|
|
|
self.banzai = true;
|
|
self.inmeleecharge = true;
|
|
|
|
if( IsDefined( self.target ) && ( !IsDefined( self.banzai_no_wait ) || ( Isdefined( self.banzai_no_wait ) && !self.banzai_no_wait ) ) )
|
|
{
|
|
// If script_forcegoal isn't true when we reach our goal, the goalradius will get reset.
|
|
self.script_forcegoal = true;
|
|
self waittill( "reached_path_end" );
|
|
}
|
|
|
|
self banzai_charge( true );
|
|
}
|
|
|
|
may_banzai_attack( enemy, maxAttackers )
|
|
{
|
|
assert ( IsDefined( enemy ) );
|
|
|
|
// Our banzai attack animations were authored with the victim standing; crouch works well enough, but not prone or back.
|
|
//if ( enemy.a.pose == "prone" || enemy.a.pose == "back" )
|
|
// return false;
|
|
|
|
if ( enemy animscripts\banzai::in_banzai_melee() && DistanceSquared( self.origin, enemy.origin ) < 96 * 96 )
|
|
return false;
|
|
|
|
if ( IsDefined( enemy.num_banzai_chargers ) && enemy.num_banzai_chargers >= maxAttackers )
|
|
return false;
|
|
|
|
if ( IsDefined( enemy.no_banzai_attack ) && enemy.no_banzai_attack )
|
|
return false;
|
|
|
|
if ( IsDefined( enemy.magic_bullet_shield ) && enemy.magic_bullet_shield )
|
|
{
|
|
if ( enemy.a.pose != "stand" )
|
|
return false;
|
|
}
|
|
|
|
if ( IsPlayer( enemy ) )
|
|
{
|
|
if ( IsDefined( enemy.usingturret ) && enemy.usingturret )
|
|
return false;
|
|
|
|
if ( IsDefined( enemy.usingvehicle ) && enemy.usingvehicle )
|
|
return false;
|
|
|
|
if ( !check_player_can_see_me( enemy ) )
|
|
return false;
|
|
|
|
if ( enemy maps\_laststand::player_is_in_laststand() )
|
|
return false;
|
|
|
|
//chris_p - make sure typ100 smg guys can't banzai attack a player
|
|
if( IsDefined(self.primaryweapon) && self.primaryweapon == "type100_smg")
|
|
return false;
|
|
}
|
|
|
|
//if ( path_blocked( enemy ) )
|
|
// return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
should_switch_immediately( enemy )
|
|
{
|
|
if ( !IsDefined( enemy ) )
|
|
return true;
|
|
|
|
if ( !IsAlive( enemy ) )
|
|
return true;
|
|
|
|
if ( IsDefined( enemy.no_banzai_attack ) && enemy.no_banzai_attack )
|
|
return true;
|
|
|
|
if ( IsDefined( enemy.magic_bullet_shield ) && enemy.magic_bullet_shield )
|
|
{
|
|
if ( enemy.a.pose != "stand" )
|
|
return true;
|
|
}
|
|
|
|
if ( IsPlayer( enemy ) )
|
|
{
|
|
if ( IsDefined( enemy.usingTurret ) && enemy.usingTurret )
|
|
return true;
|
|
|
|
if ( IsDefined( enemy.usingVehicle ) && enemy.usingvehicle )
|
|
return true;
|
|
|
|
if ( enemy maps\_laststand::player_is_in_laststand() )
|
|
return true;
|
|
|
|
//within 12 feet. am stopped, and my target is 2 feet above me.
|
|
if ( distanceSquared(self.origin, enemy.origin) < 20736 )
|
|
{
|
|
if ( self.a.movement == "stop" && (self.origin[2] < (enemy.origin[2]-24)) )
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
find_enemy()
|
|
{
|
|
self endon("death");
|
|
|
|
if(!isDefined(self.team))
|
|
{
|
|
return undefined;
|
|
}
|
|
|
|
if( self.team == "axis" )
|
|
{
|
|
opposite_team = "allies";
|
|
}
|
|
else
|
|
{
|
|
opposite_team = "axis";
|
|
}
|
|
|
|
maxDistance = 4000;
|
|
|
|
if ( IsDefined( self.script_max_banzai_distance ) )
|
|
{
|
|
maxDistance = self.script_max_banzai_distance;
|
|
}
|
|
|
|
clear_blocked_enemy_cache();
|
|
|
|
// If script_player_chance is not defined, this means to just pick the closest eligible target.
|
|
// If a most-eligible target already has one banzai attacker, go after the next most eligible.
|
|
// If all eligible targets have one banzai attacker already, start again from the closest.
|
|
// Repeat until we've reached the max number of banzai attackers for all eligible targets.
|
|
|
|
if ( !IsDefined( self.script_player_chance ) )
|
|
{
|
|
ais = GetAiArray( opposite_team );
|
|
players = get_players();
|
|
enemies = array_combine( players, ais );
|
|
enemies = get_array_of_closest( self.origin, enemies, undefined, undefined, maxDistance );
|
|
|
|
for ( numAttackers = 1; numAttackers <= 2; numAttackers++)
|
|
{
|
|
for ( i = 0; i < enemies.size; i++ )
|
|
{
|
|
if ( may_banzai_attack( enemies[i], numAttackers ) )
|
|
return enemies[i];
|
|
}
|
|
}
|
|
|
|
players = get_array_of_closest( self.origin, players, undefined, undefined, maxDistance );
|
|
|
|
for ( i = 0; i < players.size; i++ )
|
|
{
|
|
if ( may_banzai_attack( players[i], numAttackers ) )
|
|
return players[i];
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
// If there's any chance we choose a player, try them first.
|
|
if ( self.script_player_chance > 0 )
|
|
{
|
|
enemies = get_players();
|
|
enemies = get_array_of_closest( self.origin, enemies, undefined, undefined, maxDistance );
|
|
|
|
for ( i = 0; i < enemies.size; i++ )
|
|
{
|
|
if ( may_banzai_attack( enemies[i], 3 ) )
|
|
{
|
|
dieRoll = RandomInt( 100 );
|
|
if ( dieRoll < self.script_player_chance )
|
|
{
|
|
return enemies[i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we didn't choose a player, try AIs, closest first.
|
|
enemies = GetAiArray( opposite_team );
|
|
enemies = get_array_of_closest( self.origin, enemies, undefined, undefined, maxDistance );
|
|
|
|
// Construct a list of eligible targets, still sorted closest first
|
|
for ( i = 0; i < enemies.size; i++ )
|
|
{
|
|
if ( may_banzai_attack( enemies[i], 2 ) )
|
|
return enemies[i];
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
check_player_can_see_me( player )
|
|
{
|
|
if ( !IsDefined( self.script_banzai_within_fov ) || !self.script_banzai_within_fov )
|
|
return true;
|
|
|
|
return player_can_see_me( player );
|
|
}
|
|
|
|
player_can_see_me( player )
|
|
{
|
|
playerAngles = player getplayerangles();
|
|
playerForwardVec = AnglesToForward( playerAngles );
|
|
playerUnitForwardVec = VectorNormalize( playerForwardVec );
|
|
|
|
banzaiPos = self GetOrigin();
|
|
playerPos = player GetOrigin();
|
|
playerToBanzaiVec = banzaiPos - playerPos;
|
|
playerToBanzaiUnitVec = VectorNormalize( playerToBanzaiVec );
|
|
|
|
forwardDotBanzai = VectorDot( playerUnitForwardVec, playerToBanzaiUnitVec );
|
|
angleFromCenter = ACos( forwardDotBanzai );
|
|
|
|
playerFOV = GetDVarFloat( "cg_fov" );
|
|
banzaiVsPlayerFOVBuffer = GetDVarFloat( "g_banzai_player_fov_buffer" );
|
|
if ( banzaiVsPlayerFOVBuffer <= 0 )
|
|
{
|
|
banzaiVsPlayerFOVBuffer = 0.2;
|
|
}
|
|
|
|
//println( "Banzai is " + angleFromCenter + " degrees from straight ahead. Player FOV is" + playerFOV );
|
|
|
|
playerCanSeeMe = ( angleFromCenter <= ( playerFOV * 0.5 * ( 1 - banzaiVsPlayerFOVBuffer ) ) );
|
|
|
|
return playerCanSeeMe;
|
|
}
|
|
|
|
get_nearby_banzai_guys()
|
|
{
|
|
guys = GetAiArray( self.team );
|
|
|
|
banzai_guys = [];
|
|
for( i = 0; i < guys.size; i++ )
|
|
{
|
|
if( guys[i] == self )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( IsAlive( guys[i] ) && IsDefined( guys[i].script_banzai ) && guys[i].script_banzai )
|
|
{
|
|
if( DistanceSquared( self.origin, guys[i].origin ) < 512 * 512 )
|
|
{
|
|
guys[i] notify( "stop_banzai_thread" );
|
|
guys[i].script_banzai = 0;
|
|
|
|
banzai_guys[banzai_guys.size] = guys[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
return banzai_guys;
|
|
}
|
|
|
|
staggered_banzai_charge()
|
|
{
|
|
wait( RandomFloat( 1.0 ) );
|
|
banzai_charge();
|
|
}
|
|
|
|
banzai_charge( spawned_charge )
|
|
{
|
|
self endon( "death" );
|
|
|
|
if( !IsDefined( spawned_charge ) )
|
|
{
|
|
spawned_charge = false;
|
|
}
|
|
|
|
if( !spawned_charge )
|
|
{
|
|
self.banzai = true;
|
|
self.inmeleecharge = true;
|
|
}
|
|
|
|
// Must reset the goalradius before we get into the run loop
|
|
// (which is run whenever self.banzai is true), so that when
|
|
// the guy reaches his goal it's not some huge default number
|
|
// that allows the banzai guy to go wander off and do
|
|
// something else.
|
|
self.goalradius = 64;
|
|
|
|
self thread start_banzai_announce();
|
|
|
|
self.favoriteenemy = undefined;
|
|
thread find_new_enemy_immediately();
|
|
thread find_closer_enemy();
|
|
thread find_new_enemy_if_blocked();
|
|
|
|
wait ( 0.05 );
|
|
|
|
// Only necessary because we cannot SetGoalEntity() when the
|
|
// goal entity is an AI or Player.
|
|
while ( 1 )
|
|
{
|
|
if ( IsDefined( self.favoriteenemy ) )
|
|
{
|
|
self SetGoalPos( self.favoriteenemy.origin );
|
|
}
|
|
|
|
wait( 0.1 );
|
|
}
|
|
}
|
|
|
|
distance_to_enemy_less_than( lessThanThis )
|
|
{
|
|
assert( IsDefined( self.favoriteenemy ) );
|
|
return DistanceSquared( self.origin, self.favoriteenemy.origin ) < lessThanThis * lessThanThis;
|
|
}
|
|
|
|
find_new_enemy_immediately()
|
|
{
|
|
self endon( "death" );
|
|
|
|
while ( 1 )
|
|
{
|
|
if ( !self animscripts\banzai::in_banzai_attack() )
|
|
{
|
|
enemy = self.favoriteenemy;
|
|
|
|
if ( should_switch_immediately( enemy ) )
|
|
{
|
|
switch_enemies();
|
|
}
|
|
}
|
|
|
|
wait( 0.1 );
|
|
}
|
|
}
|
|
|
|
find_closer_enemy()
|
|
{
|
|
self endon( "death" );
|
|
|
|
lastPos = undefined;
|
|
|
|
while ( 1 )
|
|
{
|
|
if ( !self animscripts\banzai::in_banzai_attack() )
|
|
{
|
|
enemy = self.favoriteenemy;
|
|
if ( IsDefined( enemy ) )
|
|
{
|
|
if ( !IsPlayer( enemy ) && !distance_to_enemy_less_than( 192 ) )
|
|
{
|
|
newEnemy = self find_enemy();
|
|
if ( IsDefined( newEnemy ) && newEnemy != enemy )
|
|
{
|
|
if ( self CanSee( newEnemy ) )
|
|
{
|
|
self notify( "banzai_new_enemy" );
|
|
self.favoriteenemy.num_banzai_chargers--;
|
|
|
|
banzai_set_enemy( newEnemy );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
wait( 0.05 );
|
|
}
|
|
}
|
|
|
|
find_new_enemy_if_blocked()
|
|
{
|
|
self endon( "death" );
|
|
|
|
lastPos = undefined;
|
|
|
|
while ( 1 )
|
|
{
|
|
if ( !self animscripts\banzai::in_banzai_attack() )
|
|
{
|
|
enemy = self.favoriteenemy;
|
|
if ( IsDefined( enemy ) )
|
|
{
|
|
currPos = self.origin;
|
|
if ( IsDefined( lastPos ) && DistanceSquared( currPos, lastPos ) < 64 )
|
|
{
|
|
// Only check if path is blocked if we've been stuck in the same spot for a while.
|
|
if ( !findpath( currPos, enemy.origin ) )
|
|
{
|
|
switch_enemies();
|
|
}
|
|
else
|
|
{
|
|
lastPos = currPos;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lastPos = currPos;
|
|
}
|
|
}
|
|
}
|
|
|
|
wait( RandomFloatRange( 0.25, 0.35 ) );
|
|
}
|
|
}
|
|
|
|
switch_enemies()
|
|
{
|
|
if ( IsDefined( self.favoriteenemy ) )
|
|
{
|
|
self notify( "banzai_new_enemy" );
|
|
self.favoriteenemy.num_banzai_chargers--;
|
|
}
|
|
|
|
enemy = keep_trying_find_enemy();
|
|
|
|
banzai_set_enemy( enemy );
|
|
}
|
|
|
|
keep_trying_find_enemy()
|
|
{
|
|
while ( 1 )
|
|
{
|
|
enemy = self find_enemy();
|
|
if ( IsDefined( enemy ) && ( !IsDefined( self.favoriteenemy ) || enemy != self.favoriteenemy ) )
|
|
{
|
|
return enemy;
|
|
}
|
|
wait( 0.05 );
|
|
}
|
|
}
|
|
|
|
path_blocked( enemy )
|
|
{
|
|
for ( i = 0; i < self.blocked_enemies.size; i++ )
|
|
{
|
|
if ( enemy == self.blocked_enemies[i] )
|
|
{
|
|
return self.blocked_enemy_flags[i];
|
|
}
|
|
}
|
|
|
|
blocked = !findPath( self.origin, enemy.origin );
|
|
newIndex = self.blocked_enemies.size;
|
|
|
|
// Make sure the cache doesn't grow too large.
|
|
if ( newIndex < 10 )
|
|
{
|
|
self.blocked_enemies[ newIndex ] = enemy;
|
|
self.blocked_enemy_flags[ newIndex ] = blocked;
|
|
}
|
|
|
|
return blocked;
|
|
}
|
|
|
|
clear_blocked_enemy_cache()
|
|
{
|
|
self.blocked_enemies = [];
|
|
self.blocked_enemy_flags = [];
|
|
}
|
|
|
|
banzai_set_enemy( enemy )
|
|
{
|
|
level thread banzai_death_thread( self, enemy );
|
|
|
|
self.favoriteEnemy = enemy;
|
|
self SetGoalPos( enemy.origin );
|
|
|
|
// Notify last set of debug lines to turn off before starting new set.
|
|
//self notify( "banzai_enemy_set" );
|
|
|
|
//thread draw_line_from_ent_to_ent_until_notify( self, enemy, 1, 1, 0, self, "banzai_enemy_set" );
|
|
|
|
//thread draw_forward_line_until_notify( self, 1, 0, 1, self, "banzai_enemy_set" );
|
|
//thread draw_forward_line_until_notify( enemy, 1, 0, 1, self, "banzai_enemy_set" );
|
|
|
|
if( !IsDefined( enemy.num_banzai_chargers ) )
|
|
{
|
|
enemy.num_banzai_chargers = 0;
|
|
}
|
|
|
|
enemy.num_banzai_chargers++;
|
|
}
|
|
|
|
draw_forward_line_until_notify( ent, r, g, b, notifyEnt, notifyString )
|
|
{
|
|
assert( isdefined( notifyEnt ) );
|
|
assert( isdefined( notifyString ) );
|
|
|
|
ent endon( "death" );
|
|
notifyEnt endon( "death" );
|
|
notifyEnt endon( notifyString );
|
|
|
|
while( 1 )
|
|
{
|
|
forwardVec = VectorNormalize( AnglesToForward( ent.angles ) );
|
|
pointForward = ent.origin + forwardVec * 64;
|
|
line( ent.origin, pointForward, ( r, g, b ), 0.05 );
|
|
wait .05;
|
|
}
|
|
}
|
|
|
|
banzai_death_thread( attacker, enemy )
|
|
{
|
|
attacker endon( "banzai_new_enemy" );
|
|
attacker waittill( "death" );
|
|
|
|
if( IsDefined( enemy ) )
|
|
{
|
|
enemy.num_banzai_chargers--;
|
|
}
|
|
}
|
|
|
|
|
|
start_banzai_announce()
|
|
{
|
|
self endon( "death" );
|
|
|
|
// This will wait until battlechatter is finished before continuing with the actual banzai announcement.
|
|
//self maps\_utility::set_battlechatter( false );
|
|
self.battlechatter = false;
|
|
|
|
// Only ever want to do this once, not every time we restart banzai behavior.
|
|
if ( !IsDefined( self.banzai_announcing ) )
|
|
{
|
|
self.banzai_announcing = true;
|
|
self thread listen_for_end_of_banzai_announce();
|
|
self banzai_dialogue( "banzai_charge_announce", undefined, "banzai_announce_ended" );
|
|
}
|
|
}
|
|
|
|
listen_for_end_of_banzai_announce()
|
|
{
|
|
self endon( "death" );
|
|
self waittill( "banzai_announce_ended" );
|
|
self.banzai_announcing = false;
|
|
}
|
|
|
|
banzai_charge_yell( enemy )
|
|
{
|
|
self endon( "death" );
|
|
self endon( "pain" );
|
|
|
|
// TODO: Get facial anims for the charge
|
|
facial_anim = undefined;
|
|
|
|
if ( IsPlayer( enemy ) )
|
|
{
|
|
soundalias = "banzai_charge_plr";
|
|
}
|
|
else
|
|
{
|
|
soundalias = "banzai_charge";
|
|
}
|
|
|
|
self banzai_dialogue( soundalias, facial_anim );
|
|
|
|
self thread end_banzai_charge_yell();
|
|
}
|
|
|
|
end_banzai_charge_yell()
|
|
{
|
|
self endon( "banzai_new_enemy" );
|
|
self waittill( "pain" );
|
|
self stopSounds();
|
|
}
|
|
|
|
banzai_pump_up()
|
|
{
|
|
// TODO: Get facial anims for the charge
|
|
facial_anim = undefined;
|
|
|
|
// TODO: Mix up the yell sounds
|
|
soundalias = "banzai_pump";
|
|
|
|
self banzai_dialogue( soundalias, facial_anim, "banzai_pump" );
|
|
self waittill( "banzai_pump" );
|
|
}
|
|
|
|
banzai_print( msg )
|
|
{
|
|
/#
|
|
if( GetDvar( "debug_banzai" ) != "1" )
|
|
{
|
|
return;
|
|
}
|
|
|
|
self endon( "death" );
|
|
|
|
self notify( "stop_banzai_print" );
|
|
self endon( "stop_banzai_print" );
|
|
|
|
time = GetTime() + ( 3 * 1000 );
|
|
offset = ( 0, 0, 0 );
|
|
while( GetTime() < time )
|
|
{
|
|
offset = offset + ( 0, 0, 2 );
|
|
print3d( self GetTagOrigin( "J_Head" ) + offset, msg, ( 1, 1, 1 ) );
|
|
wait( 0.05 );
|
|
}
|
|
#/
|
|
}
|
|
|
|
#using_animtree( "generic_human" );
|
|
banzai_dialogue( soundalias, facial_anim, notify_string )
|
|
{
|
|
self animscripts\face::SaySpecificDialogue( facial_anim, soundalias, 0.9, notify_string );
|
|
}
|