/**************************************************************************** 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; }