IW4-Dump-Files/animscripts/battlechatter_ai.gsc

1487 lines
30 KiB
Plaintext

/****************************************************************************
battleChatter_ai.gsc
*****************************************************************************/
#include common_scripts\utility;
#include maps\_utility;
#include animscripts\utility;
#include animscripts\battlechatter;
/****************************************************************************
initialization
*****************************************************************************/
addToSystem( squadName )
{
self endon( "death" );
//prof_begin("addToSystem");
if ( !bcsEnabled() )
return;
if ( self.chatInitialized )
return;
assert( isdefined( self.squad ) );
// initialize battlechatter data for this AI's squad if it hasn't been already
if ( !isdefined( self.squad.chatInitialized ) || !self.squad.chatInitialized )
self.squad init_squadBattleChatter();
self.enemyClass = "infantry";
self.calledOut = [];
if ( isPlayer( self ) )
{
self.battleChatter = false;
self.flavorbursts = false;
self.type = "human";
return;
}
if ( self.type == "dog" )
{
self.enemyClass = undefined;
self.battlechatter = false;
self.flavorbursts = false;
return;
}
// don't want civilians doing battlechatter
if ( self.team == "neutral" )
{
self.enemyClass = undefined;
self.battlechatter = false;
self.flavorbursts = false;
return;
}
if ( forceEnglish() )
{
if ( self.team == "allies" )
self.script_battlechatter = false;
else
self.voice = "american";
}
// SRS 1/31/09: turning off multilingual voices to avoid a bunch of errors that don't really
// make sense right now since we're not sure if we even want multilingual functionality anymore
if ( self.voice == "multilingual" )
{
ASSERTMSG( "Actor with classname '" + self.code_classname + "' has their character asset marked as 'multilingual' in the character GDT. This is no longer supported, please change it!" );
//sLanguage = get_random_nationality();
//self.countryID = anim.countryIDs[ sLanguage ];
sLanguage = "russian";
self.countryID = anim.countryIDs[ sLanguage ];
self.voice = sLanguage;
}
else
{
self.countryID = anim.countryIDs[ self.voice ];
}
if ( isdefined( self.script_friendname ) )
{
friendname = ToLower( self.script_friendname );
if ( IsSubStr( friendname, "price" ) )
{
self.npcID = "pri";
}
else if ( IsSubStr( friendname, "mactavish" ) || IsSubStr( friendname, "soap" ) )
{
self.npcID = "mct";
}
else if ( IsSubStr( friendname, "ghost" ) )
{
self.npcID = "gst";
}
else if ( IsSubStr( friendname, "dunn" ) )
{
self.npcID = "cpd";
}
else if ( IsSubStr( friendname, "foley" ) )
{
self.npcID = "mcy";
}
/* DEPRECATED
if ( IsSubStr( friendname, "grigsby" ) || IsSubStr( friendname, "griggs" ) )
{
self.npcID = "grg";
}
else if ( IsSubStr( friendname, "gaz" ) )
{
self.npcID = "gaz";
}
*/
else
{
self setNPCID();
}
}
else
{
self setNPCID();
}
self thread aiNameAndRankWaiter();
self init_aiBattleChatter();
self thread aiThreadThreader();
//prof_end("addToSystem");
}
/* SRS 1/31/09: DEPRECATED
get_random_nationality()
{
//used for multilingual PMC enemies
//determine what language the multilingual PMC will speak
sMultiLang = "";
iRand = RandomIntrange( 1, 4 );
if ( iRand == 1 )
sMultiLang = "german";
else if ( iRand == 2 )
sMultiLang = "italian";
else
sMultiLang = "spanish";
return sMultiLang;
}
*/
forceEnglish()
{
if ( !getDvarInt( "bcs_forceEnglish", 0 ) )
return false;
switch( level.script )
{
case "airlift":
case "armada":
case "bog_a":
case "bog_b":
case "launchfacility_a":
case "launchfacility_b":
case "scoutsniper":
case "sniperescape":
case "co_armada":
case "co_break":
case "co_crossfire":
case "co_hunted":
case "co_launchfacility_a":
case "co_scoutsniper":
case "co_strike":
case "pmc_strike":
case "so_ac130_co_hunted":
return true;
}
return false;
}
// semi hackish way to make large numbers of ai spawning take less time
aiThreadThreader()
{
self endon( "death" );
self endon( "removed from battleChatter" );
waitTime = 0.5;
wait( waitTime );
self thread aiGrenadeDangerWaiter();
self thread aiFollowOrderWaiter();
if ( self.team == "allies" )
{
wait( waitTime );
self thread aiDisplaceWaiter();
}
else if( ( self.team == "axis" || self.team == "team3" ) && !isAlliedCountryID( self.countryID ) )
{
self thread aiHostileBurstLoop();
}
if( self.team == level.player.team )
{
self thread player_friendlyfire_waiter();
}
wait( waitTime );
self thread aiBattleChatterLoop();
}
isAlliedCountryID( id )
{
if( id == "UK" || id == "US" || id == "NS" || id == "TF" || id == "SS" )
{
return true;
}
return false;
}
setNPCID()
{
//prof_begin("setNPCID");
assert( !isDefined( self.npcID ) );
usedIDs = anim.usedIDs[ self.voice ];
numIDs = usedIDs.size;
startIndex = randomIntRange( 0, numIDs );
lowestID = startIndex;
for ( index = 0; index <= numIDs; index++ )
{
if ( usedIDs[ ( startIndex + index )%numIDs ].count < usedIDs[ lowestID ].count )
lowestID = ( startIndex + index ) % numIDs;
}
self thread npcIDTracker( lowestID );
self.npcID = usedIDs[ lowestID ].npcID;
//prof_end("setNPCID");
}
npcIDTracker( lowestID )
{
// self endon ("removed from battleChatter");
anim.usedIDs[ self.voice ][ lowestID ].count++ ;
self waittill( "death" );
if ( !bcsEnabled() )
return;
anim.usedIDs[ self.voice ][ lowestID ].count-- ;
}
aiHostileBurstLoop()
{
self endon( "death" );
self endon( "removed from battleChatter" );
while( 1 )
{
if( Distance( self.origin, level.player.origin ) < 1024 )
{
// don't burst unless there's at least one other guy to hear you
if( IsDefined( self.squad.memberCount ) && self.squad.memberCount > 1 )
{
self addReactionEvent( "taunt", "hostileburst" );
}
}
wait( RandomFloatRange( 2, 5 ) );
}
}
aiBattleChatterLoop()
{
self endon( "death" );
self endon( "removed from battleChatter" );
while ( true )
{
//prof_begin( "aiBattleChatterLoop" );
self playBattleChatter();
//prof_end( "aiBattleChatterLoop" );
wait( 0.3 + randomfloat( 0.2 ) );
}
}
aiNameAndRankWaiter()
{
self endon( "death" );
self endon( "removed from battleChatter" );
while ( 1 )
{
self.bcName = self animscripts\battlechatter::getName();
self.bcRank = self animscripts\battlechatter::getRank();
self waittill( "set name and rank" );
}
}
removeFromSystem( squadName )
{
if ( !IsAlive( self ) && bcsEnabled() )
{
self aiDeathFriendly();
self aiDeathEnemy();
}
if ( IsDefined( self ) )
{
self.battleChatter = false;
self.chatInitialized = false;
}
self notify( "removed from battleChatter" );
if ( IsDefined( self ) )
{
self.chatQueue = undefined;
self.nextSayTime = undefined;
self.nextSayTimes = undefined;
self.isSpeaking = undefined;
self.enemyClass = undefined;
self.calledOut = undefined;
self.countryID = undefined;
self.npcID = undefined;
}
}
init_aiBattleChatter()
{
//prof_begin("init_aiBattleChatter");
self.chatQueue = [];
self.chatQueue[ "threat" ] = spawnstruct();
self.chatQueue[ "threat" ].expireTime = 0;
self.chatQueue[ "threat" ].priority = 0.0;
self.chatQueue[ "response" ] = spawnstruct();
self.chatQueue[ "response" ].expireTime = 0;
self.chatQueue[ "response" ].priority = 0.0;
self.chatQueue[ "reaction" ] = spawnstruct();
self.chatQueue[ "reaction" ].expireTime = 0;
self.chatQueue[ "reaction" ].priority = 0.0;
self.chatQueue[ "inform" ] = spawnstruct();
self.chatQueue[ "inform" ].expireTime = 0;
self.chatQueue[ "inform" ].priority = 0.0;
self.chatQueue[ "order" ] = spawnstruct();
self.chatQueue[ "order" ].expireTime = 0;
self.chatQueue[ "order" ].priority = 0.0;
self.chatQueue[ "custom" ] = spawnstruct();
self.chatQueue[ "custom" ].expireTime = 0;
self.chatQueue[ "custom" ].priority = 0.0;
self.nextSayTime = getTime() + 50;
self.nextSayTimes[ "threat" ] = 0;
self.nextSayTimes[ "reaction" ] = 0;
self.nextSayTimes[ "response" ] = 0;
self.nextSayTimes[ "inform" ] = 0;
self.nextSayTimes[ "order" ] = 0;
self.nextSayTimes[ "custom" ] = 0;
self.isSpeaking = false;
self.bcs_minPriority = 0.0;
/*-------- ALLOWED THREAT CALLOUTS --------
Here we set up the types of threat callouts that this AI is allowed to use.
- these should always match the values that index the anim.threatCallouts[] array,
which is set up in battlechatter::init_battleChatter()
------------------------------------------*/
self.allowedCallouts = [];
// global
self addAllowedThreatCallout( "rpg" );
self addAllowedThreatCallout( "exposed" );
// shadow company doesn't do these kinds of callouts
if( self.voice != "shadowcompany" )
{
self addAllowedThreatCallout( "ai_contact_clock" );
self addAllowedThreatCallout( "ai_target_clock" );
self addAllowedThreatCallout( "ai_cardinal" );
}
// allies only
if( self.team == "allies" )
{
self addAllowedThreatCallout( "ai_yourclock" );
self addAllowedThreatCallout( "player_yourclock" );
self addAllowedThreatCallout( "player_contact_clock" );
self addAllowedThreatCallout( "player_target_clock" );
self addAllowedThreatCallout( "player_cardinal" );
self addAllowedThreatCallout( "player_obvious" );
self addAllowedThreatCallout( "player_object_yourclock" );
self addAllowedThreatCallout( "ai_object_yourclock" );
self addAllowedThreatCallout( "player_object_clock" );
self addAllowedThreatCallout( "player_location" );
self addAllowedThreatCallout( "ai_location" );
}
if ( IsDefined( self.script_battlechatter ) && !self.script_battlechatter )
{
self.battleChatter = false;
}
else
{
self.battleChatter = level.battlechatter[ self.team ];
}
if( self voiceCanBurst() )
{
self.flavorbursts = true;
}
else
{
self.flavorbursts = false;
}
// doesn't impact friendlyfire warnings normally played when battlechatter is on,
// just whether it plays when battlechatter is otherwise turned off
if( level.friendlyfire_warnings )
{
self set_friendlyfire_warnings( true );
}
else
{
self set_friendlyfire_warnings( false );
}
self.chatInitialized = true;
//prof_end("init_aiBattleChatter");
}
/****************************************************************************
ai event queue
*****************************************************************************/
// adds a threat callout to this AIs queue
addThreatEvent( eventType, threat, priority )
{
self endon( "death" );
self endon( "removed from battleChatter" );
ASSERTEX( IsDefined( eventType ), "addThreatEvent called with undefined eventType" );
if ( !self canSay( "threat", eventType, priority ) )
{
return;
}
// check if the threat has already been called out by someone in our squad
if( threatWasAlreadyCalledOut( threat ) && !IsPlayer( threat ) )
{
return;
}
chatEvent = self createChatEvent( "threat", eventType, priority );
switch( eventType )
{
case "infantry":
chatEvent.threat = threat;
break;
/*
case "emplacement":
chatEvent.threat = threat;
break;
case "vehicle":
chatEvent.threat = threat;
break;
*/
}
if( IsDefined( threat.squad ) )
{
self.squad updateContact( threat.squad.squadName, self );
}
self.chatQueue[ "threat" ] = undefined;
self.chatQueue[ "threat" ] = chatEvent;
}
// adds a response to this AIs queue
// reportAlias = in the case of a report/echo situation, this is the alias
// that the reporter used, and will have a specifically corresponding "echo" alias
// location = for QA situations, so we have the location trigger object
addResponseEvent( eventType, modifier, respondTo, priority, reportAlias, location )
{
self endon( "death" );
self endon( "removed from battleChatter" );
self thread addResponseEvent_internal( eventType, modifier, respondTo, priority, reportAlias, location );
}
addResponseEvent_internal( eventType, modifier, respondTo, priority, reportAlias, location )
{
self endon( "death" );
self endon( "removed from battleChatter" );
self endon( "responseEvent_failsafe" );
// wait until respondTo is done talking
self thread responseEvent_failSafe( respondTo );
message = respondTo waittill_any_return( "death", "done speaking", "cancel speaking" );
if ( message == "cancel speaking" )
{
return;
}
if ( !IsAlive( respondTo ) )
{
return;
}
if ( !self canSay( "response", eventType, priority, modifier ) )
{
return;
}
if ( !IsPlayer( respondTo ) )
{
// make sure that we don't respond in the same voice
if( self isUsingSameVoice( respondTo ) )
{
return;
}
}
chatEvent = self createChatEvent( "response", eventType, priority );
if( IsDefined( reportAlias ) )
{
chatEvent.reportAlias = reportAlias;
}
if( IsDefined( location ) )
{
chatEvent.location = location;
}
chatEvent.respondTo = respondTo;
chatEvent.modifier = modifier;
self.chatQueue[ "response" ] = undefined;
self.chatQueue[ "response" ] = chatEvent;
}
responseEvent_failSafe( respondTo )
{
self endon( "death" );
self endon( "removed from battleChatter" );
respondTo endon( "death" );
respondTo endon( "done speaking" );
respondTo endon( "cancel speaking" );
wait( 25 );
self notify( "responseEvent_failsafe" );
}
// adds a informative callout to this AIs queue
addInformEvent( eventType, modifier, informTo, priority )
{
self endon( "death" );
self endon( "removed from battleChatter" );
if ( !self canSay( "inform", eventType, priority, modifier ) )
{
return;
}
chatEvent = self createChatEvent( "inform", eventType, priority );
switch( eventType )
{
case "reloading":
chatEvent.modifier = modifier;
chatEvent.informTo = informTo;
break;
default:
chatEvent.modifier = modifier;
}
self.chatQueue[ "inform" ] = undefined;
self.chatQueue[ "inform" ] = chatEvent;
}
// adds a response to this AIs queue
addReactionEvent( eventType, modifier, reactTo, priority )
{
self endon( "death" );
self endon( "removed from battleChatter" );
/*
if ( !self canSay( "reaction", eventType, priority, modifier ) )
{
return;
}
*/
if ( !isdefined( self.chatQueue ) )
return;
chatEvent = self createChatEvent( "reaction", eventType, priority );
chatEvent.reactTo = reactTo;
chatEvent.modifier = modifier;
self.chatQueue[ "reaction" ] = undefined;
self.chatQueue[ "reaction" ] = chatEvent;
}
// adds an order to this AIs queue
addOrderEvent( eventType, modifier, orderTo, priority )
{
self endon( "death" );
self endon( "removed from battleChatter" );
if ( !self canSay( "order", eventType, priority, modifier ) )
{
return;
}
if ( IsDefined( orderTo ) && orderTo.type == "dog" )
{
return;
}
chatEvent = self createChatEvent( "order", eventType, priority );
chatEvent.modifier = modifier;
chatEvent.orderTo = orderTo;
self.chatQueue[ "order" ] = undefined;
self.chatQueue[ "order" ] = chatEvent;
}
/****************************************************************************
ai trackers/waiters
*****************************************************************************/
squadOfficerWaiter()
{
anim endon( "battlechatter disabled" );
anim endon( "squad deleted " + self.squadName );
while ( 1 )
{
officer = undefined;
if ( self.officers.size )
members = self.officers;
else
members = self.members;
officers = [];
for ( index = 0; index < members.size; index++ )
{
if ( isalive( members[ index ] ) )
officers[ officers.size ] = members[ index ];
}
if ( officers.size )
{
officer = getClosest( level.player.origin, officers );
officer aiOfficerOrders();
officer waittill( "death" );
}
wait( 3.0 );
}
}
getThreats( potentialThreats )
{
threats = [];
for ( i = 0; i < potentialThreats.size; i++ )
{
if ( !IsDefined( potentialThreats[ i ].enemyClass ) )
{
continue;
}
if( !threatIsViable( potentialThreats[i] ) )
{
continue;
}
potentialThreats[ i ].threatID = threats.size;
threats[ threats.size ] = potentialThreats[ i ];
}
// sort by distance from the player
threats = get_array_of_closest( level.player.origin, threats );
// deliver guys in locational triggers first
haveLocs = [];
noLocs = [];
foreach( threat in threats )
{
location = threat GetLocation();
if( IsDefined( location ) && !location_called_out_recently( location ) )
{
haveLocs[ haveLocs.size ] = threat;
}
else
{
noLocs[ noLocs.size ] = threat;
}
}
// array_combine adds the first argument to the returned array first
threats = array_combine( haveLocs, noLocs );
return( threats );
}
threatIsViable( threat )
{
if( !level.player entInFrontArc( threat ) )
{
return false;
}
maxDistSqd = level.bcs_maxThreatDistFromPlayer * level.bcs_maxThreatDistFromPlayer;
if( DistanceSquared( level.player.origin, threat.origin ) > maxDistSqd )
{
return false;
}
return true;
}
squadThreatWaiter()
{
anim endon( "battlechatter disabled" );
anim endon( "squad deleted " + self.squadName );
while ( 1 )
{
wait( RandomFloatRange( 0.25, 0.75 ) );
//prof_begin("squadThreatWaiter");
if ( self.team == "allies" )
{
validEnemies = getThreats( GetAIArray( "axis", "team3" ) );
}
else if ( self.team == "team3" )
{
validEnemies = getThreats( GetAIArray( "allies", "axis" ) );
}
else
{
validEnemies = GetAIArray( "allies", "team3" );
validEnemies[ validEnemies.size ] = level.player;
}
if ( !validEnemies.size )
{
continue;
}
addedEnemies = [];
foreach( i, member in self.members )
{
if ( !IsAlive( member ) )
{
continue;
}
if ( !validEnemies.size )
{
validEnemies = addedEnemies;
addedEnemies = [];
}
foreach( j, enemy in validEnemies )
{
if ( !IsDefined( enemy ) )
{
if ( j == 0 )
{
validEnemies = [];
}
continue;
}
if ( !IsAlive( enemy ) )
{
continue;
}
if( !member CanSee( enemy ) )
{
if( IsPlayer( enemy ) )
{
continue;
}
if( enemy.team == level.player.team )
{
continue;
}
// we want enemies that the player can see to get called out even if other team members can't see them
if( !player_can_see_ai( enemy ) )
{
continue;
}
}
member addThreatEvent( enemy.enemyClass, enemy );
addedEnemies[ addedEnemies.size ] = enemy;
validEnemies = array_remove( validEnemies, enemy );
break;
}
wait( 0.05 );
}
//prof_end("squadThreatWaiter");
}
}
aiDeathFriendly()
{
attacker = self.attacker;
// reaction event
array_thread( self.squad.members, ::aiDeathEventThread );
// if the guy who killed him is a regular AI, call him out if we can
if ( IsAlive( attacker ) && IsSentient( attacker ) && IsDefined( attacker.squad ) && attacker.battleChatter )
{
// reset this guy's calledOut status since he's dangerous again
if ( IsDefined( attacker.calledOut[ attacker.squad.squadName ] ) )
{
attacker.calledOut[ attacker.squad.squadName ] = undefined;
}
// only infantry do this
if ( !IsDefined( attacker.enemyClass ) )
{
return;
}
// only if the attacker is in a location we can talk about
if ( !attacker is_in_callable_location() )
{
return;
}
foreach( member in self.squad.members )
{
// make sure we've seen someone lately
if ( GetTime() > ( member.lastEnemySightTime + 2000 ) )
{
continue;
}
// re-add this attacker as a threat
member addThreatEvent( attacker.enemyClass, attacker );
}
}
}
aiDeathEventThread()
{
if( !IsAlive( self ) )
{
return;
}
self endon( "death" );
self endon( "removed from battleChatter" );
wait( 1.5 );
self addReactionEvent( "casualty", "generic", self, 0.9 );
}
aiDeathEnemy()
{
attacker = self.attacker;
if ( !IsAlive( attacker ) || !IsSentient( attacker ) || !IsDefined( attacker.squad ) )
{
return;
}
// only SEALs get to do killfirms
if( !IsDefined( attacker.countryID ) || attacker.countryID != "NS" )
{
return;
}
if ( !IsPlayer( attacker ) )
{
// attacker says "got one" or something similar
attacker thread aiKillEventThread();
}
}
aiKillEventThread()
{
self endon( "death" );
self endon( "removed from battleChatter" );
wait( 1.5 );
self addInformEvent( "killfirm", "generic" );
}
aiOfficerOrders()
{
self endon( "death" );
self endon( "removed from battleChatter" );
if ( !isdefined( self.squad.chatInitialized ) )
self.squad waittill( "squad chat initialized" );
while ( 1 )
{
if ( getdvar( "bcs_enable", "on" ) == "off" )
{
wait( 1.0 );
continue;
}
self addSituationalOrder();
wait( RandomFloatRange( 3.0, 6.0 ) );
}
}
aiGrenadeDangerWaiter()
{
self endon( "death" );
self endon( "removed from battleChatter" );
while ( 1 )
{
self waittill( "grenade danger", grenade );
if ( getdvar( "bcs_enable", "on" ) == "off" )
continue;
if ( !isdefined( grenade ) || grenade.model != "projectile_m67fraggrenade" )
continue;
if ( distance( grenade.origin, level.player.origin ) < 512 )// grenade radius is 220
self addInformEvent( "incoming", "grenade" );
}
}
aiDisplaceWaiter()
{
self endon( "death" );
self endon( "removed from battleChatter" );
while( true )
{
self waittill( "trigger" );
if ( getdvar( "bcs_enable", "on" ) == "off" )
continue;
// no acknowledgement if you just took pain, looks dumb
if ( GetTime() < self.a.painTime + 4000 )
{
continue;
}
self addResponseEvent( "ack", "yes", level.player, 1.0 );
}
}
evaluateMoveEvent( wasInCover )
{
self endon( "death" );
self endon( "removed from battleChatter" );
if ( !bcsEnabled() )
{
return;
}
if ( !IsDefined( self.node ) )
{
return;
}
dist = Distance( self.origin, self.node.origin );
// it looks silly to have an order for a short distance
if ( dist < 512 )
{
return;
}
if ( !self isNodeCoverOrConceal() )
{
return;
}
if( !self nationalityOkForMoveOrder() )
{
return;
}
// figure out who to talk to
responder = self getResponder( 24, 1024, "response" );
if( self.team != "axis" && self.team != "team3" )
{
if( !IsDefined( responder ) )
{
responder = level.player;
}
// if we do have a responder, sometimes we want to pick the player anyway, for variety
else
{
if( RandomInt( 100 ) < anim.eventChance[ "moveEvent" ][ "ordertoplayer" ] )
{
responder = level.player;
}
}
}
// if we're in combat...
if( self.combatTime > 0.0 )
{
if( RandomInt( 100 ) < anim.eventChance[ "moveEvent" ][ "coverme" ] )
{
self addOrderEvent( "action", "coverme", responder );
}
// sometimes we do a different kind of order
else
{
self addOrderEvent( "move", "combat", responder );
}
}
else
{
if( self nationalityOkForMoveOrderNoncombat() )
{
self addOrderEvent( "move", "noncombat", responder );
}
}
}
nationalityOkForMoveOrder()
{
// secretservice do not talk about move events
if( self.countryID == "SS" )
{
return false;
}
return true;
}
nationalityOkForMoveOrderNoncombat()
{
// only Marines do noncombat move orders
if( self.countryID == "US" )
{
return true;
}
return false;
}
aiFollowOrderWaiter()
{
self endon( "death" );
self endon( "removed from battleChatter" );
while ( true )
{
level waittill( "follow order", speaker );
if ( !bcsEnabled() )
return;
if ( speaker.team != self.team )
continue;
if ( distance( self.origin, speaker.origin ) < 600 )
{
self addResponseEvent( "ack", "yes", speaker, 0.9 );
}
}
}
// waits/reacts to the player shooting near the friendlies
player_friendlyfire_waiter()
{
self endon( "death" );
self endon( "removed from battleChatter" );
self thread player_friendlyfire_waiter_damage();
while( 1 )
{
self waittill( "bulletwhizby", shooter, whizByDist );
if( !bcsEnabled() )
{
continue;
}
if( !IsPlayer( shooter ) )
{
continue;
}
if( self friendlyfire_whizby_distances_valid( shooter, whizbyDist ) )
{
self player_friendlyfire_addReactionEvent();
wait( 3 );
}
}
}
player_friendlyfire_addReactionEvent()
{
self addReactionEvent( "friendlyfire", undefined, level.player, 1.0 );
}
// player damaging friendly should always get noticed
player_friendlyfire_waiter_damage()
{
self endon( "death" );
self endon( "removed from battleChatter" );
while( 1 )
{
self waittill( "damage", amount, attacker, direction_vec, point, type );
if( IsDefined( attacker ) && IsPlayer( attacker ) )
{
if( damage_is_valid_for_friendlyfire_warning( type ) )
{
self player_friendlyfire_addReactionEvent();
}
}
}
}
damage_is_valid_for_friendlyfire_warning( type )
{
if( !IsDefined( type ) )
{
return false;
}
switch( type )
{
case "MOD_MELEE":
case "MOD_GRENADE":
case "MOD_GRENADE_SPLASH":
case "MOD_CRUSH":
case "MOD_IMPACT":
return false;
}
return true;
}
friendlyfire_whizby_distances_valid( shooter, whizbyDist )
{
minDistFromAI = 256 * 256;
maxWhizbyDist = 42;
if( DistanceSquared( shooter.origin, self.origin ) < minDistFromAI )
{
return false;
}
if( whizbyDist > maxWhizbyDist )
{
return false;
}
return true;
}
evaluateReloadEvent()
{
self endon( "death" );
self endon( "removed from battleChatter" );
if ( !bcsEnabled() )
{
return;
}
self addInformEvent( "reloading", "generic" );
}
// doesn't do anything atm, it's a good hook for melee events though
evaluateMeleeEvent()
{
self endon( "death" );
self endon( "removed from battleChatter" );
if ( !bcsEnabled() )
return( false );
if ( !isdefined( self.enemy ) )
return( false );
// self addReactionEvent("taunt", "generic", self.enemy);
// return (true);
return( false );
}
evaluateFiringEvent()
{
self endon( "death" );
self endon( "removed from battleChatter" );
if ( !bcsEnabled() )
return;
if ( !isdefined( self.enemy ) )
return;
// if (distance(self.origin, self.enemy.origin) > 384)
// self addReactionEvent("taunt", "generic", self.enemy, 0.4);
}
evaluateSuppressionEvent()
{
self endon( "death" );
self endon( "removed from battleChatter" );
if ( !bcsEnabled() )
return;
if ( !self.suppressed )
return;
self addInformEvent( "suppressed", "generic" );
}
evaluateAttackEvent( type )
{
self endon( "death" );
self endon( "removed from battleChatter" );
if ( !bcsEnabled() )
{
return;
}
ASSERTEX( IsDefined( type ), "Grenade type [self.grenadeWeapon] thrown is undefined!" );
// just do frag callouts for all kinds of grenades
self addInformEvent( "attack", "grenade" );
/*
switch( type )
{
case "flash_grenade":
self addInformEvent( "attack", "flash" );
break;
default:
self addInformEvent( "attack", "grenade" );
return;
}
*/
}
addSituationalOrder()
{
self endon( "death" );
self endon( "removed from battleChatter" );
if ( self.squad.squadStates[ "combat" ].isActive )
{
self addSituationalCombatOrder();
}
}
addSituationalCombatOrder()
{
self endon( "death" );
self endon( "removed from battleChatter" );
squad = self.squad;
squad animscripts\squadmanager::updateStates();
if ( squad.squadStates[ "suppressed" ].isActive )
{
if ( squad.squadStates[ "cover" ].isActive )
{
responder = self getResponder( 96, 512, "response" );
self addOrderEvent( "action", "grenade", responder );
}
else
{
self addOrderEvent( "displace", "generic" );
}
}
else if ( squad.squadStates[ "combat" ].isActive )
{
// secretservice don't do suppress orders
if( self.countryID != "SS" )
{
responder = self getResponder( 24, 1024, "response" );
self addOrderEvent( "action", "suppress", responder );
}
}
}
/****************************************************************************
custom battlechatter event functions
*****************************************************************************/
custom_battlechatter_init_valid_phrases()
{
// when this list changes, update the documentation in
// _utility::custom_battlechatter to reflect it!
phrases = [];
phrases[ phrases.size ] = "order_move_combat"; // "Move move move!"
phrases[ phrases.size ] = "order_move_noncombat"; // "Move out."
phrases[ phrases.size ] = "order_action_coverme"; // "Covering fire!"
phrases[ phrases.size ] = "inform_reloading"; // "Reloading!"
level.customBCS_validPhrases = phrases;
}
custom_battlechatter_validate_phrase( string )
{
foundIt = false;
foreach( phrase in level.customBCS_validPhrases )
{
if( phrase == string )
{
foundIt = true;
break;
}
}
return foundIt;
}
custom_battlechatter_internal( string )
{
if ( !IsDefined( level.customBcs_validPhrases ) )
{
custom_battlechatter_init_valid_phrases();
}
string = tolower( string );
phraseInvalidStr = anim.bcPrintFailPrefix + "custom battlechatter phrase '" + string + "' isn't valid. look at _utility::custom_battlechatter_init_valid_phrases(), or the util script documentation for custom_battlechatter(), for a list of valid phrases.";
badCountryIdStr = anim.bcPrintFailPrefix + "AI at origin " + self.origin + "wasn't able to play custom battlechatter because his nationality is '" + self.countryID + "'.";
if( !custom_battlechatter_validate_phrase( string ) )
{
ASSERTMSG( phraseInvalidStr );
return false;
}
responder = self getResponder( 24, 512, "response" );
self beginCustomEvent();
switch( string )
{
case "order_move_combat":
if( !self nationalityOkForMoveOrder() )
{
/#
println( badCountryIdStr );
#/
return false;
}
self tryOrderTo( self.customChatPhrase, responder );
self addMoveCombatAliasEx();
break;
case "order_move_noncombat":
if( !self nationalityOkForMoveOrderNoncombat() )
{
/#
println( badCountryIdStr );
#/
return false;
}
self addMoveNoncombatAliasEx();
break;
case "order_action_coverme":
self tryOrderTo( self.customChatPhrase, responder );
self addActionCovermeAliasEx();
break;
case "inform_reloading":
self addInformReloadingAliasEx();
break;
default:
// we validated this already, so we shouldn't ever get here
ASSERTMSG( phraseInvalidStr );
return false;
}
self endCustomEvent( 2000 );
return true;
}
beginCustomEvent()
{
if ( !bcsEnabled() )
return;
self.customChatPhrase = createChatPhrase();
}
addActionCovermeAliasEx()
{
self.customChatPhrase addOrderAlias( "action", "coverme" );
}
addMoveCombatAliasEx()
{
self.customChatPhrase addOrderAlias( "move", "combat" );
}
addMoveNoncombatAliasEx()
{
self.customChatPhrase addOrderAlias( "move", "noncombat" );
}
addInformReloadingAliasEx()
{
self.customChatPhrase addInformAlias( "reloading", "generic" );
}
addNameAliasEx( name )
{
if ( !bcsEnabled() )
return;
self.customChatPhrase addNameAlias( name );
}
endCustomEvent( eventDuration, typeOverride )
{
if ( !bcsEnabled() )
return;
chatEvent = self createChatEvent( "custom", "generic", 1.0 );
if ( isdefined( eventDuration ) )
chatEvent.expireTime = gettime() + eventDuration;
if ( isDefined( typeOverride ) )
chatEvent.type = typeOverride;
else
chatEvent.type = "custom";
self.chatQueue[ "custom" ] = undefined;
self.chatQueue[ "custom" ] = chatEvent;
}