2999 lines
90 KiB
Ucode
2999 lines
90 KiB
Ucode
|
//=============================================================================
|
||
|
// KFDialogManager
|
||
|
//=============================================================================
|
||
|
// Manager class for player dialog. Tracks delays, cooldowns, active dialog, etc.
|
||
|
//=============================================================================
|
||
|
// Killing Floor 2
|
||
|
// Copyright (C) 2015 Tripwire Interactive LLC
|
||
|
// - Jeff Robinson
|
||
|
//=============================================================================
|
||
|
|
||
|
class KFDialogManager extends Actor
|
||
|
dependson(KFPawnVoiceGroupEventData)
|
||
|
config(Game);
|
||
|
|
||
|
`include(KFGame/KFGameDialog.uci)
|
||
|
|
||
|
var bool bEnabled;
|
||
|
var config bool bLogDialog;
|
||
|
|
||
|
/** Information about dialog that has been spoken and is cooling down */
|
||
|
struct DialogCoolDownInfo
|
||
|
{
|
||
|
var int EventID;
|
||
|
var KFPawn Speaker;
|
||
|
var float EndTime;
|
||
|
var vector Location;
|
||
|
};
|
||
|
|
||
|
/** Event info for events that are cooling down */
|
||
|
var array< DialogCoolDownInfo > CoolDowns;
|
||
|
|
||
|
/** We only update one player pawn per "spotted" update, so we need to remember who's next */
|
||
|
var transient Pawn NextSpotterPawn;
|
||
|
|
||
|
/** Last time we checked if there should be "spotted" dialog */
|
||
|
var transient float LastSpotUpdateTime;
|
||
|
|
||
|
struct DelayedDialogInfo
|
||
|
{
|
||
|
var KFPawn Speaker;
|
||
|
var int EventID;
|
||
|
var float EndTime;
|
||
|
};
|
||
|
|
||
|
/** Event info for events that have a delay */
|
||
|
var array< DelayedDialogInfo > DelayedDialog;
|
||
|
|
||
|
/** Last time we checked if there should be idle dialog */
|
||
|
var float LastIdleUpdateTime;
|
||
|
|
||
|
/** Dialog at this priority level and above are considered "urgent" and will interrupt dialog of lower priority */
|
||
|
var byte InterruptPriorityThreshold;
|
||
|
/** Dialog at this priority level and below can be interrupted by any higher-priority dialog, disregarding InterruptPriorityThreshold */
|
||
|
var byte InterruptedByAnyPriorityThreshold;
|
||
|
|
||
|
/** Set at beginning of wave and beginning of trader time */
|
||
|
var bool bIsTraderTime;
|
||
|
|
||
|
|
||
|
// Variables to control dialog
|
||
|
|
||
|
// welding
|
||
|
var int WeldAboutToBreakThreshold;
|
||
|
|
||
|
// healing
|
||
|
var float NeedMoreHealingPctThreshold;
|
||
|
var float NeedNoMoreHealingPctThreshold;
|
||
|
|
||
|
// dosh
|
||
|
var int CaughtSomeDoshAmt;
|
||
|
var int CaughtMuchDoshAmt;
|
||
|
|
||
|
// reload/ammo
|
||
|
var int NumZedsForPressureReload;
|
||
|
var float LowSpareAmmoPctThreshold;
|
||
|
|
||
|
// kill zed
|
||
|
var int NumKillsForOnARoll;
|
||
|
var float CloseCallKillHealthPctThreshold;
|
||
|
|
||
|
var int NumZedsInAreaForMassacre;
|
||
|
var float AreaRadiusForMassacre;
|
||
|
var float TimeLimitForMassacre;
|
||
|
|
||
|
// zed damage
|
||
|
var int NumHitsForDeadHorse;
|
||
|
var float TimeBetweenHitsForDeadHorse;
|
||
|
|
||
|
var float TimeForContinuousDamageThreshold;
|
||
|
var float TimeBetweenHitsForContinuousDamage;
|
||
|
|
||
|
// player damage
|
||
|
var float PlayerHealthPctForNearDeath;
|
||
|
var float PlayerTakeDamageStreakInterval;
|
||
|
var float PlayerTakeDamageStreakPctForScream;
|
||
|
|
||
|
// spot zed
|
||
|
var float IdleTimeforSpottingZed;
|
||
|
var int SpotLargeHordeNumZeds;
|
||
|
|
||
|
var float ZedAlmostDeadHealthPctThreshold;
|
||
|
|
||
|
var float SprintTowardZedDuration;
|
||
|
|
||
|
// situational/idle
|
||
|
var float IdleTimeForSituationalDialog;
|
||
|
var int IdleLowDoshThreshold;
|
||
|
var int IdleHighDoshThreshold;
|
||
|
|
||
|
var int IdleLowAmmoPctThreshold;
|
||
|
var int IdleHighAmmoPctThreshold;
|
||
|
|
||
|
// sprinting
|
||
|
var float TimeUntilStartSprintPanting;
|
||
|
|
||
|
// global AkEvents
|
||
|
var AkEvent StopBreathingAkEvent;
|
||
|
|
||
|
/** Same as defined in KFPawn_HUman. This one should always match that one. */
|
||
|
delegate OnFinishedDialog( const out DialogResponseInfo ResponseInfo );
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* Utility functions for getting event data
|
||
|
*****************************************************************************/
|
||
|
|
||
|
/** Gets the radius within which the event can occur */
|
||
|
function float GetEventRadius( int EventID, class< KFPawnVoiceGroupEventData > EventDataClass )
|
||
|
{
|
||
|
if( EventDataClass != none )
|
||
|
{
|
||
|
return EventDataClass.default.Events[EventID].Radius;
|
||
|
}
|
||
|
|
||
|
`warn("KFDialogManager::GetEventRadius - EventDataClass is none, using probably useless default value");
|
||
|
return 0.f;
|
||
|
}
|
||
|
|
||
|
/** Get the event radius squared */
|
||
|
function float GetEventRadiusSq( int EventID, class< KFPawnVoiceGroupEventData > EventDataClass )
|
||
|
{
|
||
|
local float Rad;
|
||
|
|
||
|
Rad = GetEventRadius( EventID, EventDataClass );
|
||
|
|
||
|
return Rad * Rad;
|
||
|
}
|
||
|
|
||
|
/** Gets the field-of-view within which the event can occur */
|
||
|
function float GetEventFOV( int EventID, class< KFPawnVoiceGroupEventData > EventDataClass )
|
||
|
{
|
||
|
if( EventDataClass != none )
|
||
|
{
|
||
|
return EventDataClass.default.Events[EventID].FOV;
|
||
|
}
|
||
|
|
||
|
`warn("KFDialogManager::GetEventFOV - EventDataClass is none, using probably useless default value");
|
||
|
return 0.f;
|
||
|
}
|
||
|
|
||
|
/** Gets the priority of the event */
|
||
|
function byte GetEventPriority( int EventID, class< KFPawnVoiceGroupEventData > EventDataClass )
|
||
|
{
|
||
|
if( EventDataClass != none )
|
||
|
{
|
||
|
return EventDataClass.default.Events[EventID].Priority;
|
||
|
}
|
||
|
|
||
|
`warn("KFDialogManager::GetEventPriority - EventDataClass is none, using probably useless default value");
|
||
|
return 255;
|
||
|
}
|
||
|
|
||
|
/** Gets the cooldown of the event */
|
||
|
function float GetEventCoolDownTime( int EventID, class< KFPawnVoiceGroupEventData > EventDataClass )
|
||
|
{
|
||
|
if( EventDataClass != none )
|
||
|
{
|
||
|
return EventDataClass.default.Events[EventID].CoolDownTime;
|
||
|
}
|
||
|
|
||
|
`warn("KFDialogManager::GetEventCoolDownTime - EventDataClass is none, using probably useless default value");
|
||
|
return 0.f;
|
||
|
}
|
||
|
|
||
|
/** Gets the radius of effect of the cooldown of the event */
|
||
|
function float GetEventCoolDownRadius( int EventID, class< KFPawnVoiceGroupEventData > EventDataClass )
|
||
|
{
|
||
|
if( EventDataClass != none )
|
||
|
{
|
||
|
return EventDataClass.default.Events[EventID].CoolDownRadius;
|
||
|
}
|
||
|
|
||
|
`warn("KFDialogManager::GetEventCoolDownRadius - EventDataClass is none, using probably useless default value");
|
||
|
return 0.f;
|
||
|
}
|
||
|
|
||
|
/** Gets the cooldown radius squared */
|
||
|
function float GetEventCoolDownRadiusSq( int EventID, class< KFPawnVoiceGroupEventData > EventDataClass )
|
||
|
{
|
||
|
local float CoolDownRadius;
|
||
|
|
||
|
CoolDownRadius = GetEventCoolDownRadius( EventID, EventDataClass );
|
||
|
|
||
|
return CoolDownRadius * CoolDownRadius;
|
||
|
}
|
||
|
|
||
|
/** Gets the cooldown category of the event */
|
||
|
function int GetEventCoolDownCategory( int EventID, class< KFPawnVoiceGroupEventData > EventDataClass )
|
||
|
{
|
||
|
if( EventDataClass != none )
|
||
|
{
|
||
|
if( EventDataClass.default.Events.Length > EventID )
|
||
|
{
|
||
|
return EventDataClass.default.Events[EventID].CoolDownCategory;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return 255;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
`warn("KFDialogManager::GetEventCoolDownCategory - EventDataClass is none, using probably useless default value");
|
||
|
return 255;
|
||
|
}
|
||
|
|
||
|
/** Gets the cooldown category of the event */
|
||
|
function bool GetEventIsOnlyPlayedLocally( int EventID, class< KFPawnVoiceGroupEventData > EventDataClass )
|
||
|
{
|
||
|
if( EventDataClass != none )
|
||
|
{
|
||
|
return EventDataClass.default.Events[EventID].bOnlyPlayLocally;
|
||
|
}
|
||
|
|
||
|
`warn("KFDialogManager::GetEventIsOnlyPlayedLocally - EventDataClass is none, using probably useless default value");
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* Utility functions for checking pawn validigy, proximity, FOV, and LOS
|
||
|
*****************************************************************************/
|
||
|
|
||
|
function bool PawnIsValidPlayer( KFPawn_Human KFPH )
|
||
|
{
|
||
|
// gotta be human!
|
||
|
if( KFPH.GetTeamNum() != 0 )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// gotta be alive!
|
||
|
if( !KFPH.IsAliveAndWell() )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if( KFPH.VoiceGroupArch == none || KFPH.VoiceGroupArch.default.EventDataClass == none )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function bool PawnCanSpotPawn( Pawn P1, Pawn P2, float MaxDistanceSq, float FOVCosine, optional bool CheckLOS, optional bool DoubleCheckLOS )
|
||
|
{
|
||
|
// check distance
|
||
|
if( VSizeSq(P2.Location - P1.Location) >= MaxDistanceSq )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if( !ActorWithinPawnFOV(P2, P1, FOVCosine) )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// check LOS
|
||
|
return !CheckLOS || PawnWithinPawnLOS(P2, P1, DoubleCheckLOS);
|
||
|
}
|
||
|
|
||
|
function bool PawnCanSpotActor( Pawn P, Actor A, float MaxDistanceSq, float FOVCosine, optional bool CheckLOS )
|
||
|
{
|
||
|
// check distance
|
||
|
if( VSizeSq(A.Location - P.Location) >= MaxDistanceSq )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if( !ActorWithinPawnFOV(A, P, FOVCosine) )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// check LOS
|
||
|
if( CheckLOS && !FastTrace(P.GetPawnViewLocation(), A.Location) )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function bool ActorWithinPawnFOV( Actor A, Pawn P, float FOVCosine )
|
||
|
{
|
||
|
local vector PtoA, PFacing;
|
||
|
|
||
|
PFacing = vector( P.GetViewRotation() );
|
||
|
PtoA = Normal( A.Location - P.Location );
|
||
|
|
||
|
// check FOV
|
||
|
if( PtoA dot PFacing < FOVCosine )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function bool ActorWithinPawnRadius( Actor A, Pawn P, float Radius )
|
||
|
{
|
||
|
if( VSizeSq(A.Location - P.Location) > (Radius * Radius) )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function bool PawnWithinPawnLOS( Pawn P1, Pawn P2, optional bool bDoubleCheckLOS )
|
||
|
{
|
||
|
if( !FastTrace(P2.GetPawnViewLocation(), P1.GetPawnViewLocation()) )
|
||
|
{
|
||
|
if( !bDoubleCheckLOS || !FastTrace(P1.GetPawnViewLocation(), P2.Location) )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function bool PlayerCanSpotEnemy( KFPawn_Human Player, KFPawn_Monster Enemy, optional float RadiusSq = -1.f, optional float FOVCosine = -1.f )
|
||
|
{
|
||
|
local class< KFPawnVoiceGroupEventData > EventDataClass;
|
||
|
|
||
|
EventDataClass = Player.GetVoiceGroupEventDataClass();
|
||
|
|
||
|
if( RadiusSq < 0 )
|
||
|
{
|
||
|
RadiusSq = GetEventRadiusSq(`SPOTZ_Generic, EventDataClass);
|
||
|
}
|
||
|
|
||
|
if( FOVCosine < 0 )
|
||
|
{
|
||
|
FOVCosine = GetEventFOV(`SPOTZ_Generic, EventDataClass);
|
||
|
}
|
||
|
|
||
|
return PawnCanSpotPawn(Player, Enemy, RadiusSq, FOVCosine, true, true );
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* Start real dialog manager functionality
|
||
|
*****************************************************************************/
|
||
|
|
||
|
function SetTraderTime( bool bTraderTime )
|
||
|
{
|
||
|
bIsTraderTime = bTraderTime;
|
||
|
}
|
||
|
|
||
|
event Tick( float DeltaTime )
|
||
|
{
|
||
|
if( !bEnabled || !WorldInfo.GRI.bMatchHasBegun )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if( DelayedDialog.Length > 0 )
|
||
|
{
|
||
|
UpdateDelayedDialog();
|
||
|
}
|
||
|
|
||
|
if( `TimeSince(LastSpotUpdateTime) > 0.25 )
|
||
|
{
|
||
|
CheckSpottedDialog();
|
||
|
LastSpotUpdateTime = WorldInfo.TimeSeconds;
|
||
|
}
|
||
|
|
||
|
if( `TimeSince(LastIdleUpdateTime) > 0.5 )
|
||
|
{
|
||
|
CheckIdleDialog();
|
||
|
LastIdleUpdateTime = WorldInfo.TimeSeconds;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Updates delayed dialog, plays delayed dialog when delay time has elapsed */
|
||
|
function UpdateDelayedDialog()
|
||
|
{
|
||
|
local int i;
|
||
|
|
||
|
for( i = 0; i < DelayedDialog.Length; ++i )
|
||
|
{
|
||
|
if( DelayedDialog[i].EndTime < WorldInfo.TimeSeconds )
|
||
|
{
|
||
|
PlayDialogEvent( DelayedDialog[i].Speaker, DelayedDialog[i].EventID );
|
||
|
DelayedDialog.Remove( i--, 1 );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Checks if dialog should be considered to be "cooling down" */
|
||
|
function bool DialogIsCoolingDown( KFPawn KFP, int EventID, int EventCoolDownCategory )
|
||
|
{
|
||
|
local int i, CoolDownID, iCoolDownCategory;
|
||
|
local class< KFPawnVoiceGroupEventData > EventDataClass;
|
||
|
|
||
|
EventDataClass = KFP.GetVoiceGroupEventDataClass();
|
||
|
|
||
|
for( i = 0; i < CoolDowns.Length; ++i )
|
||
|
{
|
||
|
CoolDownID = CoolDowns[i].EventID;
|
||
|
iCoolDownCategory = GetEventCoolDownCategory(CoolDownID, EventDataClass);
|
||
|
|
||
|
// check cooldown for:
|
||
|
// - same event
|
||
|
// - any event, in the case that EventID is an idle/situational event (don't remark about the weather unless no one around you has talked for a while)
|
||
|
// - any event, in the case that CoolDownID is a response event (don't talk over responses... it gets confusing)
|
||
|
// - any event in the same cooldown category
|
||
|
if( EventID == CoolDownID ||
|
||
|
EventCoolDownCategory == ECC_Situ ||
|
||
|
iCoolDownCategory == ECC_Resp ||
|
||
|
(EventCoolDownCategory != 255 && EventCoolDownCategory == iCoolDownCategory) )
|
||
|
{
|
||
|
// instead of removing on tick, remove on demand
|
||
|
if( CoolDowns[i].EndTime <= WorldInfo.TimeSeconds )
|
||
|
{
|
||
|
CoolDowns.Remove( i--, 1 );
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if( KFP == Cooldowns[i].Speaker )
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if( VSizeSq(CoolDowns[i].Location - KFP.Location) < GetEventCoolDownRadiusSq(CoolDownID, EventDataClass) )
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/** Checks if a dialog event can be played based on priority, cooldown, etc. */
|
||
|
function bool DialogEventCanBePlayed( KFPawn KFP, const out DialogEventInfo EventInfo )
|
||
|
{
|
||
|
if( KFP.IsSpeaking() )
|
||
|
{
|
||
|
// event isn't allowed to interrupt anything
|
||
|
if( EventInfo.Priority > InterruptPriorityThreshold && KFP.CurrDialogPriority < InterruptedByAnyPriorityThreshold )
|
||
|
{
|
||
|
`log(KFP.VoiceGroupArch.static.GetEventName(EventInfo.EventID)$" can't be played for "$Right(KFP.VoiceGroupArch.Name,Len(KFP.VoiceGroupArch.Name)-InStr(KFP.VoiceGroupArch.Name, "_")-1)$" - Already speaking "$KFP.VoiceGroupArch.static.GetEventName(KFP.CurrDialogEventID)$" (priority too low to cause interupt)", bLogDialog, 'BattleChatter');
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// event is lower priority than dialog being spoken, so it can't interrupt it
|
||
|
if( EventInfo.Priority > KFP.CurrDialogPriority )
|
||
|
{
|
||
|
`log(KFP.VoiceGroupArch.static.GetEventName(EventInfo.EventID)$" can't be played for "$Right(KFP.VoiceGroupArch.Name,Len(KFP.VoiceGroupArch.Name)-InStr(KFP.VoiceGroupArch.Name, "_")-1)$" - Already speaking "$KFP.VoiceGroupArch.static.GetEventName(KFP.CurrDialogEventID)$" (lower priority)", bLogDialog, 'BattleChatter');
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// event is same priority and isn't allowed to interrupt equal priority dialog
|
||
|
if( EventInfo.Priority == KFP.CurrDialogPriority && !EventInfo.bCanInterruptEqualPriority )
|
||
|
{
|
||
|
`log(KFP.VoiceGroupArch.static.GetEventName(EventInfo.EventID)$" can't be played for "$Right(KFP.VoiceGroupArch.Name,Len(KFP.VoiceGroupArch.Name)-InStr(KFP.VoiceGroupArch.Name, "_")-1)$" - Already speaking "$KFP.VoiceGroupArch.static.GetEventName(KFP.CurrDialogEventID)$" (same priority, not allowed to interrupt same priority)", bLogDialog, 'BattleChatter');
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// don't re-play dialog in cool-down
|
||
|
if( DialogIsCoolingDown(KFP, EventInfo.EventID, EventInfo.CoolDownCategory) )
|
||
|
{
|
||
|
`log(KFP.VoiceGroupArch.static.GetEventName(EventInfo.EventID)$" can't be played for "$Right(KFP.VoiceGroupArch.Name,Len(KFP.VoiceGroupArch.Name)-InStr(KFP.VoiceGroupArch.Name, "_")-1)$" - Cooling down", bLogDialog, 'BattleChatter');
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// don't play if it would be out of context with respect to number of players in the game
|
||
|
if( !EventInfo.bCanPlayAlone && KFGameInfo(WorldInfo.Game).GetLivingPlayerCount() < 2 )
|
||
|
{
|
||
|
`log(KFP.VoiceGroupArch.static.GetEventName(EventInfo.EventID)$" can't be played for "$Right(KFP.VoiceGroupArch.Name,Len(KFP.VoiceGroupArch.Name)-InStr(KFP.VoiceGroupArch.Name, "_")-1)$" - Alone", bLogDialog, 'BattleChatter');
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// only allow dialog in the ECC_TTime category to be played during trader time
|
||
|
if( EventInfo.CoolDownCategory == ECC_TTime && !bIsTraderTime )
|
||
|
{
|
||
|
`log(KFP.VoiceGroupArch.static.GetEventName(EventInfo.EventID)$" can't be played for "$Right(KFP.VoiceGroupArch.Name,Len(KFP.VoiceGroupArch.Name)-InStr(KFP.VoiceGroupArch.Name, "_")-1)$" - Trader time only", bLogDialog, 'BattleChatter');
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// only allow high-pri dialog (mostly pain/damages sounds) if being grabbed by Hans
|
||
|
if( KFP.SpecialMove == SM_HansGrappleVictim && EventInfo.Priority > InterruptPriorityThreshold )
|
||
|
{
|
||
|
`log(KFP.VoiceGroupArch.static.GetEventName(EventInfo.EventID)$" can't be played for "$Right(KFP.VoiceGroupArch.Name,Len(KFP.VoiceGroupArch.Name)-InStr(KFP.VoiceGroupArch.Name, "_")-1)$" - Being grabbed by Hans", bLogDialog, 'BattleChatter');
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/** Play dialog from given speaker and dialog anim on given speaker */
|
||
|
function PlayDialogEvent( KFPawn Speaker, int EventID )
|
||
|
{
|
||
|
local DialogCoolDownInfo CoolDownInfo;
|
||
|
local DialogEventInfo EventInfo;
|
||
|
local AkEvent EventAudioCue;
|
||
|
local name SpeakerName;
|
||
|
|
||
|
if( !bEnabled || Speaker == none )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if( EventID < 0 || Speaker.VoiceGroupArch == none )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
EventInfo = Speaker.VoiceGroupArch.static.GetDialogEventInfo(EventID);
|
||
|
|
||
|
if( EventInfo.Delay > 0 )
|
||
|
{
|
||
|
if( AddDelayedDialogEvent(Speaker, EventID, EventInfo.Delay) )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( !DialogEventCanBePlayed(Speaker, EventInfo) )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if( FRand() > EventInfo.Chance )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
EventAudioCue = Speaker.VoiceGroupArch.static.GetDialogAkEvent( EventID, bIsTraderTime );
|
||
|
if( EventAudioCue == none )
|
||
|
{
|
||
|
`log("KFDialogManager::PlayDialogEvent - event "$Speaker.VoiceGroupArch.static.GetEventName(EventInfo.EventID)$" for speaker "$Speaker$" has no audio, bailing out", bLogDialog);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if( EventAudioCue.Duration <= 0 )
|
||
|
{
|
||
|
`log("KFDialogManager::PlayDialogEvent - event "$Speaker.VoiceGroupArch.static.GetEventName(EventInfo.EventID)$" for speaker "$Speaker$" has invalid duration, bailing out", bLogDialog);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
SpeakerName = Speaker.VoiceGroupArch.Name;
|
||
|
|
||
|
if( Speaker.IsSpeaking() )
|
||
|
{
|
||
|
`log("Interrupting "$Speaker.VoiceGroupArch.static.GetEventName(Speaker.CurrDialogEventID)$" for "$Right(SpeakerName,Len(SpeakerName)-InStr(SpeakerName, "_")-1), bLogDialog, 'BattleChatter');
|
||
|
|
||
|
// interrupt current dialog
|
||
|
Speaker.StopDialog();
|
||
|
}
|
||
|
|
||
|
`log("Playing "$Speaker.VoiceGroupArch.static.GetEventName(EventInfo.EventID)$" for "$Right(SpeakerName,Len(SpeakerName)-InStr(SpeakerName, "_")-1), bLogDialog, 'BattleChatter');
|
||
|
|
||
|
Speaker.CurrDialogEventID = EventInfo.EventID;
|
||
|
Speaker.CurrDialogPriority = EventInfo.Priority;
|
||
|
|
||
|
// manage cool down info
|
||
|
if( EventInfo.CoolDownTime > 0.f )
|
||
|
{
|
||
|
CoolDownInfo.EventID = EventInfo.EventID;
|
||
|
CoolDownInfo.Speaker = Speaker;
|
||
|
CoolDownInfo.Location = Speaker.Location;
|
||
|
CoolDownInfo.EndTime = WorldInfo.TimeSeconds + EventInfo.CoolDownTime;
|
||
|
CoolDowns.AddItem( CoolDownInfo );
|
||
|
}
|
||
|
|
||
|
if( EventInfo.bOnlyPlayLocally )
|
||
|
{
|
||
|
if( Speaker.IsLocallyControlled() )
|
||
|
{
|
||
|
KFPlayerController(Speaker.Controller).ClientHearDialog( Speaker, EventAudioCue, EventInfo.bCanBeMinimized );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Speaker.PlayDialog( EventAudioCue, EventInfo.bCanBeMinimized );
|
||
|
}
|
||
|
|
||
|
// keep track of end-of-event with a timer (duration is max duration of all permutations of dialog)
|
||
|
SetTimer( EventAudioCue.Duration, false, nameof(Speaker.EndOfDialogTimer), Speaker );
|
||
|
}
|
||
|
|
||
|
/** Checks if a given player is speaking or is set to speak a given line of dialog */
|
||
|
function bool PlayerIsPlayingDialogEvent( KFPawn_Human Player, int EventID )
|
||
|
{
|
||
|
local int DelayedDialogIdx;
|
||
|
|
||
|
if( Player.IsPlayingDialogEvent(EventiD) )
|
||
|
{
|
||
|
// the player is currently saying the line
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
DelayedDialogIdx = DelayedDialog.Find('EventID', EventID);
|
||
|
if( DelayedDialogIdx != INDEX_NONE && DelayedDialog[DelayedDialogIdx].Speaker == Player )
|
||
|
{
|
||
|
// the player will start saying the line soon, so consider him to be speaking already
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/** Delegate for all responses that only require basic information and logic */
|
||
|
function BasicResponseDelegate( const out DialogResponseInfo RespInfo )
|
||
|
{
|
||
|
if( !RespInfo.Speaker.bDeleteMe && RespInfo.Speaker.IsAliveAndWell() )
|
||
|
{
|
||
|
PlayDialogEvent( RespInfo.Speaker, RespInfo.EventID );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Plays or sets up a basic (requires no special information or logic) response to a given line of dialog */
|
||
|
function PlayBasicDialogResponse( KFPawn_Human RespondToPawn, int RespondingToID, int ResponseID, optional KFPawn_Human Responder, optional KFPawn Target, optional bool bPlayOnlyAsResponse )
|
||
|
{
|
||
|
if( Responder == none )
|
||
|
{
|
||
|
Responder = FindPlayerResponder( RespondToPawn, ResponseID, Target );
|
||
|
}
|
||
|
|
||
|
if( Responder == none )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// if Speaker is speaking the dialog Responder is waiting for, then wait for it to finish
|
||
|
if( PlayerIsPlayingDialogEvent(RespondToPawn, RespondingToID) )
|
||
|
{
|
||
|
RespondToPawn.SetDialogResponseDelegate( Responder, BasicResponseDelegate, ResponseID, RespondingToID );
|
||
|
}
|
||
|
// otherwise just play the "response" right away, if we're allowed to do so
|
||
|
else if( !bPlayOnlyAsResponse )
|
||
|
{
|
||
|
PlayDialogEvent( Responder, ResponseID );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Finds a nearby player to respond to a given line of dialog */
|
||
|
function KFPawn_Human FindPlayerResponder( KFPawn_Human Speaker, int ResponseEventID, optional KFPawn Target )
|
||
|
{
|
||
|
local KFPawn_Human Responder;
|
||
|
local class< KFPawnVoiceGroupEventData > EventDataClass;
|
||
|
|
||
|
EventDataClass = Speaker.GetVoiceGroupEventDataClass();
|
||
|
|
||
|
foreach WorldInfo.AllPawns( class'KFPawn_Human', Responder )
|
||
|
{
|
||
|
if( Responder == Speaker || !PawnIsValidPlayer(Responder) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if( ActorWithinPawnRadius(Speaker, Responder, GetEventRadius(ResponseEventID, EventDataClass))
|
||
|
&& (Target == none || (ActorWithinPawnFOV(Target, Responder, GetEventFOV(ResponseEventID, EventDataClass)) && PawnWithinPawnLOS(Target, Responder)))
|
||
|
)
|
||
|
{
|
||
|
return Responder;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return none;
|
||
|
}
|
||
|
|
||
|
/** Plays or sets up a response that requires customized information or logic */
|
||
|
function PlayCustomDialogResponse( KFPawn_Human Speaker, KFPawn Responder, int RespondingToID, delegate<OnFinishedDialog> CustomDelegate, optional bool bPlayOnlyAsResponse )
|
||
|
{
|
||
|
// if Speaker is speaking the dialog Responder is waiting for, then wait for it to finish
|
||
|
if( PlayerIsPlayingDialogEvent(Speaker, RespondingToID) )
|
||
|
{
|
||
|
Speaker.SetDialogResponseDelegate( Responder, CustomDelegate, -1, RespondingToID );
|
||
|
}
|
||
|
// otherwise just play the "response" right away, if we're allowed to do so
|
||
|
else if( !bPlayOnlyAsResponse )
|
||
|
{
|
||
|
Speaker.SetDialogResponseDelegate( Responder, none, -1, RespondingToID );
|
||
|
CustomDelegate( Speaker.DlgRespInfo );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Simulates adding elements to an array and picking a random element (without using an array) */
|
||
|
function AddRandomDialogOption( KFPawn Speaker, int OptionID, out int NumOptions, out int BestOptionID )
|
||
|
{
|
||
|
local DialogEventInfo NewOptionEventInfo, BestOptionEventInfo;
|
||
|
local int WeightCount;
|
||
|
|
||
|
if( OptionID < 0 || Speaker.VoiceGroupArch == none )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if( BestOptionID >= 0 )
|
||
|
{
|
||
|
NewOptionEventInfo = Speaker.VoiceGroupArch.static.GetDialogEventInfo(OptionID);
|
||
|
BestOptionEventInfo = Speaker.VoiceGroupArch.static.GetDialogEventInfo(BestOptionID);
|
||
|
if( NewOptionEventInfo.Priority < BestOptionEventInfo.Priority )
|
||
|
{
|
||
|
NumOptions = 0;
|
||
|
}
|
||
|
else if( NewOptionEventInfo.Priority > BestOptionEventInfo.Priority )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
NumOptions++;
|
||
|
WeightCount = NewOptionEventInfo.Weight;
|
||
|
while( WeightCount-- > 0 )
|
||
|
{
|
||
|
if( Frand() <= 1.f / float(NumOptions) )
|
||
|
{
|
||
|
BestOptionID = OptionID;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Adds an entry to the delayed dialog array */
|
||
|
function bool AddDelayedDialogEvent( KFPawn Speaker, int EventID, float Delay )
|
||
|
{
|
||
|
local int DelayedDialogIdx;
|
||
|
|
||
|
DelayedDialogIdx = DelayedDialog.Find('EventID', EventID);
|
||
|
if( DelayedDialogIdx == INDEX_NONE )
|
||
|
{
|
||
|
DelayedDialog.Add( 1 );
|
||
|
DelayedDialog[ DelayedDialog.Length - 1 ].Speaker = Speaker;
|
||
|
DelayedDialog[ DelayedDialog.Length - 1 ].EventID = EventID;
|
||
|
DelayedDialog[ DelayedDialog.Length - 1 ].EndTime = WorldInfo.TimeSeconds + Delay;
|
||
|
return true;
|
||
|
}
|
||
|
else if( DelayedDialog[DelayedDialogIdx].Speaker != Speaker )
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/************************************************
|
||
|
* Door Dialog
|
||
|
************************************************/
|
||
|
|
||
|
function PlayWeldDialog( KFPawn Speaker, KFDoorActor Door, KFPawn Welder )
|
||
|
{
|
||
|
local int WeldDialogID, WeldRespDialogID;
|
||
|
|
||
|
if( Speaker.VoiceGroupArch == none || Speaker.VoiceGroupArch.default.EventDataClass == none )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if( Door.UnderAttack() )
|
||
|
{
|
||
|
WeldDialogID = `ACT_WeldDoorUnderAttack;
|
||
|
WeldRespDialogID = `ACT_WeldDoorUnderAttackResp;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
WeldDialogID = `ACT_WeldDoor;
|
||
|
WeldRespDialogID = `ACT_WeldDoorResp;
|
||
|
}
|
||
|
|
||
|
if( Speaker == Welder )
|
||
|
{
|
||
|
// if door is now welded in full, play finished dialog
|
||
|
if( Door.WeldIntegrity >= Door.MaxWeldIntegrity )
|
||
|
{
|
||
|
PlayDialogEvent( KFPawn_Human(Speaker), `ACT_WeldDoorFinish );
|
||
|
}
|
||
|
// else play general "welding" dialog, but only when the player first starts welding
|
||
|
else if( !Door.BeingWelded() )
|
||
|
{
|
||
|
PlayDialogEvent( KFPawn_Human(Speaker), WeldDialogID );
|
||
|
}
|
||
|
// else if door is under attack and about to break, play "about to break" dialog
|
||
|
else if( WeldDialogID == `ACT_WeldDoorUnderAttack && Door.WeldIntegrity < WeldAboutToBreakThreshold )
|
||
|
{
|
||
|
PlayDialogEvent( KFPawn_Human(Speaker), `ACT_WeldDoorAboutToBreak );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
PlayBasicDialogResponse( KFPawn_Human(Speaker), WeldDialogID, WeldRespDialogID, KFPawn_Human(Welder) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function PlayUnweldDialog( KFPawn Speaker, KFDoorActor Door, KFPawn Unwelder )
|
||
|
{
|
||
|
if( Speaker.VoiceGroupArch == none || Speaker.VoiceGroupArch.default.EventDataClass == none )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if( Speaker == Unwelder )
|
||
|
{
|
||
|
// if door is now unwelded in full, play finished dialog
|
||
|
if( Door.WeldIntegrity <= 0 )
|
||
|
{
|
||
|
PlayDialogEvent( KFPawn_Human(Speaker), `ACT_UnweldDoorFinish );
|
||
|
}
|
||
|
// else play unwelding dialog, but only when the player first starts unwelding
|
||
|
else if( !Door.BeingUnwelded() )
|
||
|
{
|
||
|
PlayDialogEvent( KFPawn_Human(Speaker), `ACT_UnweldDoor );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function PlayDoorTakeDamageDialog( KFDoorActor Door )
|
||
|
{
|
||
|
local KFPawn_Human KFPH;
|
||
|
local bool bDoorWeldBroken;
|
||
|
local class< KFPawnVoiceGroupEventData > EventDataClass;
|
||
|
|
||
|
bDoorWeldBroken = Door.WeldIntegrity <= 0;
|
||
|
|
||
|
foreach WorldInfo.AllPawns( class'KFPawn_Human', KFPH )
|
||
|
{
|
||
|
if( !PawnIsValidPlayer(KFPH) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
EventDataClass = KFPH.GetVoiceGroupEventDataClass();
|
||
|
|
||
|
//@todo: should probably check LOS, but that doesn't seem to be reliable with door actors...
|
||
|
if( !PawnCanSpotActor(KFPH, Door, GetEventRadiusSq(`ACT_SpotDoorAttacked, EventDataClass), GetEventFOV(`ACT_SpotDoorAttacked, EventDataClass)) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if( bDoorWeldBroken )
|
||
|
{
|
||
|
PlayDialogEvent( KFPH, `ACT_SpotDoorBreak );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
PlayDialogEvent( KFPH, `ACT_SpotDoorAttacked );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************
|
||
|
* Heal Dialog
|
||
|
************************************************/
|
||
|
|
||
|
function PlayHealMissDialog( KFPawn Healer, KFPawn IntendedHealee )
|
||
|
{
|
||
|
local KFPawn_Human KFPH, KFPH_Healee;
|
||
|
|
||
|
if( Healer.VoiceGroupArch == none || Healer.VoiceGroupArch.default.EventDataClass == none )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
KFPH = KFPawn_Human(Healer);
|
||
|
|
||
|
if( KFPH == none )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
KFPH_Healee = KFPawn_Human(IntendedHealee);
|
||
|
|
||
|
if( KFPH_Healee == none )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
PlayDialogEvent( KFPH, `ACT_HealMiss );
|
||
|
PlayCustomDialogResponse( KFPawn_Human(Healer), IntendedHealee, `ACT_HealMiss, PlayHealMissResponse, true );
|
||
|
}
|
||
|
|
||
|
function PlayHealMissResponse( const out DialogResponseInfo RespInfo )
|
||
|
{
|
||
|
if( !RespInfo.Speaker.bIsMoving && ActorWithinPawnRadius(RespInfo.Speaker, RespInfo.RespondingToPawn, GetEventRadius(`ACT_HealMissResp, RespInfo.Speaker.GetVoiceGroupEventDataClass())) )
|
||
|
{
|
||
|
PlayDialogEvent( RespInfo.Speaker, `ACT_HealMissResp );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function PlayHealingDialog( KFPawn Healer, KFPawn Healee, float HealeeHealthPct )
|
||
|
{
|
||
|
local KFPawn_Human KFPH;
|
||
|
|
||
|
if( Healer.VoiceGroupArch == none || Healer.VoiceGroupArch.default.EventDataClass == none )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
KFPH = KFPawn_Human(Healer);
|
||
|
|
||
|
if( KFPH == none )
|
||
|
{
|
||
|
// not an act of healing by a player
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if( KFPH != Healee )
|
||
|
{
|
||
|
PlayDialogEvent( KFPH, `ACT_HealSomeone );
|
||
|
|
||
|
if( HealeeHealthPct < NeedMoreHealingPctThreshold )
|
||
|
{
|
||
|
PlayBasicDialogResponse( KFPH, `ACT_HealSomeone, `ACT_HealSomeoneRespLT50pct, KFPawn_Human(Healee) );
|
||
|
}
|
||
|
else if( HealeeHealthPct >= NeedNoMoreHealingPctThreshold )
|
||
|
{
|
||
|
PlayBasicDialogResponse( KFPH, `ACT_HealSomeone, `ACT_HealSomeoneRespMT75pct, KFPawn_Human(Healee) );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
PlayDialogEvent( KFPH, `ACT_HealSelf );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************
|
||
|
* Dosh Dialog
|
||
|
************************************************/
|
||
|
|
||
|
function PlayDoshCaughtDialog( KFPawn_Human Speaker )
|
||
|
{
|
||
|
if( Speaker.VoiceGroupArch == none || Speaker.VoiceGroupArch.default.EventDataClass == none )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if( Speaker.DoshCaughtStreakAmt >= CaughtMuchDoshAmt )
|
||
|
{
|
||
|
PlayDialogEvent( Speaker, `ACT_DoshAtSomeoneRespLots );
|
||
|
}
|
||
|
else if( Speaker.DoshCaughtStreakAmt >= CaughtSomeDoshAmt )
|
||
|
{
|
||
|
PlayDialogEvent( Speaker, `ACT_DoshAtSomeoneRespSome );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function PlayDoshTossDialog( KFPawn Speaker )
|
||
|
{
|
||
|
local Actor HitActor;
|
||
|
local vector HitLocation, HitNormal;
|
||
|
local vector TraceStart, TraceEnd;
|
||
|
|
||
|
TraceEnd = Speaker.GetPawnViewLocation();
|
||
|
TraceStart = TraceEnd + vector(Speaker.GetViewRotation()) * 500;
|
||
|
HitActor = Trace( HitLocation, HitNormal, TraceEnd, TraceStart, true );
|
||
|
if( HitActor != Speaker && KFPawn_Human(HitActor) != none )
|
||
|
{
|
||
|
PlayDialogEvent( Speaker, `ACT_DoshAtSomeone );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
PlayDialogEvent( Speaker, `ACT_DoshAtGround );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************
|
||
|
* Weapon Dialog
|
||
|
************************************************/
|
||
|
|
||
|
function PlayReloadDialog( KFPawn Speaker )
|
||
|
{
|
||
|
if( Speaker.VoiceGroupArch == none || Speaker.VoiceGroupArch.default.EventDataClass == none )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if( Speaker.IsSurrounded(true, NumZedsForPressureReload, GetEventRadius(`ACT_ReloadUnderPres, Speaker.GetVoiceGroupEventDataClass())) )
|
||
|
{
|
||
|
PlayDialogEvent( Speaker, `ACT_ReloadUnderPres );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
PlayDialogEvent( Speaker, `ACT_Reload );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function PlayAmmoDialog( KFPawn Speaker, float SpareAmmoLeftPct )
|
||
|
{
|
||
|
if( Speaker.VoiceGroupArch == none || Speaker.VoiceGroupArch.default.EventDataClass == none )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if( SpareAmmoLeftPct <= LowSpareAmmoPctThreshold )
|
||
|
{
|
||
|
PlayDialogEvent( Speaker, `ACT_AmmoLow );
|
||
|
PlayBasicDialogResponse( KFPawn_Human(Speaker), `ACT_AmmoLow, `ACT_AmmoLowResp, /*Responder*/, /*Target*/, true );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function PlayDropWeaponDialog( KFPawn Speaker )
|
||
|
{
|
||
|
if( Speaker.VoiceGroupArch == none || Speaker.VoiceGroupArch.default.EventDataClass == none )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
PlayDialogEvent( Speaker, `ACT_DropWeap );
|
||
|
}
|
||
|
|
||
|
function PlayMeleeAttackDialog( KFPawn Speaker, bool bIsHeavyAttack )
|
||
|
{
|
||
|
if( Speaker.VoiceGroupArch == none || Speaker.VoiceGroupArch.default.EventDataClass == none )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if( bIsHeavyAttack )
|
||
|
{
|
||
|
PlayDialogEvent( Speaker, `ACT_AttackHeavy );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
PlayDialogEvent( Speaker, `ACT_AttackLight );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function PlayIronsightsDialog( KFPawn Speaker )
|
||
|
{
|
||
|
if( Speaker.VoiceGroupArch == none || Speaker.VoiceGroupArch.default.EventDataClass == none )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
PlayDialogEvent( Speaker, `ACT_Ironsights );
|
||
|
}
|
||
|
|
||
|
function PlayJumpDialog( KFPawn Speaker )
|
||
|
{
|
||
|
if( Speaker.VoiceGroupArch == none || Speaker.VoiceGroupArch.default.EventDataClass == none )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
PlayDialogEvent( Speaker, `ACT_Jump );
|
||
|
}
|
||
|
|
||
|
/************************************************
|
||
|
* Sprint Dialog
|
||
|
************************************************/
|
||
|
|
||
|
function PlaySprintPantingDialog( KFPawn_Human Speaker, bool bNewSprintStatus )
|
||
|
{
|
||
|
if( bNewSprintStatus && !Speaker.bIsSprinting )
|
||
|
{
|
||
|
Speaker.SprintStartTime = WorldInfo.TimeSeconds;
|
||
|
}
|
||
|
else if ( !bNewSprintStatus )
|
||
|
{
|
||
|
StopBreathingDialog(Speaker);
|
||
|
}
|
||
|
|
||
|
if( `TimeSince(Speaker.SprintStartTime) > TimeUntilStartSprintPanting )
|
||
|
{
|
||
|
Speaker.SprintStartTime = WorldInfo.TimeSeconds;
|
||
|
PlayDialogEvent( Speaker, `ACT_Sprint );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* A stop event for sprinting and ironsight breathing. The breathing sounds also have a max duration,
|
||
|
* so even though this event is not perfect (changing controllers, etc...) it won't break anything.
|
||
|
*/
|
||
|
function StopBreathingDialog(KFPawn Speaker)
|
||
|
{
|
||
|
if( Speaker.IsSpeaking() )
|
||
|
{
|
||
|
if ( Speaker.CurrDialogEventID == `ACT_Sprint || Speaker.CurrDialogEventID == `ACT_Ironsights )
|
||
|
{
|
||
|
KFPlayerController(Speaker.Controller).ClientHearDialog( Speaker, StopBreathingAkEvent, 0 );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************
|
||
|
* Level Up Dialog
|
||
|
************************************************/
|
||
|
|
||
|
function PlayLevelUpDialog( KFPlayerController LevellerKFPC )
|
||
|
{
|
||
|
local KFPawn_Human Leveller, Commenter;
|
||
|
local int CommentEventID;
|
||
|
|
||
|
local int LevellerLevel, CommenterLevel;
|
||
|
|
||
|
LevellerLevel = KFPlayerReplicationInfo( LevellerKFPC.PlayerReplicationInfo ).GetActivePerkLevel();
|
||
|
|
||
|
if( LevellerLevel < 5 )
|
||
|
{
|
||
|
CommentEventID = `ACT_CommentLvlLT10;
|
||
|
}
|
||
|
else if( LevellerLevel < 15 )
|
||
|
{
|
||
|
CommentEventID = `ACT_CommentLvlLT25;
|
||
|
}
|
||
|
else if( LevellerLevel < 25 )
|
||
|
{
|
||
|
CommentEventID = `ACT_CommentLvlLT50;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
CommentEventID = `ACT_CommentLvlMax;
|
||
|
}
|
||
|
|
||
|
Leveller = KFPawn_Human( LevellerKFPC.Pawn );
|
||
|
Commenter = FindPlayerResponder( Leveller, CommentEventID );
|
||
|
|
||
|
if( Commenter != none )
|
||
|
{
|
||
|
PlayDialogEvent( Commenter, CommentEventID );
|
||
|
|
||
|
CommenterLevel = KFPlayerReplicationInfo( KFPlayerController(Commenter.Controller).PlayerReplicationInfo ).GetActivePerkLevel();
|
||
|
if( CommenterLevel > LevellerLevel )
|
||
|
{
|
||
|
PlayBasicDialogResponse( Commenter, CommentEventID, `ACT_CommentLvlUpRespHigher, Leveller, Commenter, true );
|
||
|
}
|
||
|
else if( CommenterLevel < LevellerLevel )
|
||
|
{
|
||
|
PlayBasicDialogResponse( Commenter, CommentEventID, `ACT_CommentLvlUpRespLower, Leveller, Commenter, true );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************
|
||
|
* KillZed Dialog
|
||
|
************************************************/
|
||
|
|
||
|
function PlayKilledZedDialog( KFPawn_Human Killer, KFPawn_Monster Zed, class<DamageType> DamageType, bool bWasKnockedDown )
|
||
|
{
|
||
|
local int NumOptions, BestOptionID;
|
||
|
|
||
|
if( Killer.VoiceGroupArch == none || Killer.VoiceGroupArch.default.EventDataClass == none )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
BestOptionID = -1;
|
||
|
|
||
|
CheckOnARollDialog( Killer, NumOptions, BestOptionID );
|
||
|
CheckMassacreDialog( Killer, Zed, NumOptions, BestOptionID );
|
||
|
CheckCloseCallKillDialog( Killer, Zed, NumOptions, BestOptionID );
|
||
|
CheckKillAsLastAliveDialog( Killer, Zed, NumOptions, BestOptionID );
|
||
|
CheckKilledWithFavoriteWeaponDialog( Killer, NumOptions, BestOptionID );
|
||
|
|
||
|
AddRandomDialogOption( Killer, Zed.GetKillerDialogID(), NumOptions, BestOptionID );
|
||
|
|
||
|
if( class<KFDamageType>(DamageType) != none )
|
||
|
{
|
||
|
AddRandomDialogOption( Killer, class<KFDamageType>(DamageType).static.GetKillerDialogID(), NumOptions, BestOptionID );
|
||
|
}
|
||
|
|
||
|
if( bWasKnockedDown )
|
||
|
{
|
||
|
AddRandomDialogOption( Killer, `KILL_KnockedDown, NumOptions, BestOptionID );
|
||
|
}
|
||
|
|
||
|
PlayDialogEvent( Killer, BestOptionID );
|
||
|
PlayBasicKilledResponse( Killer, Zed, BestOptionID );
|
||
|
}
|
||
|
|
||
|
/** Adds "on a roll" random dialog if player has killed enough zeds without taking damage */
|
||
|
function CheckOnARollDialog( KFPawn_Human Speaker, out int out_NumOptions, out int out_BestOptionID )
|
||
|
{
|
||
|
Speaker.UpdateKillStreak();
|
||
|
|
||
|
if( Speaker.ZedsKilledStreakAmt >= NumKillsForOnARoll )
|
||
|
{
|
||
|
AddRandomDialogOption( Speaker, `KILL_OnARoll, out_NumOptions, out_BestOptionID );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Adds "massacre" random dialog if enough zeds have been killed in a certain amount of time and within a certain radius */
|
||
|
function CheckMassacreDialog( KFPawn_Human Killer, KFPawn_Monster KilledZed, out int out_NumOptions, out int out_BestOptionID )
|
||
|
{
|
||
|
local int i, MassacreCount;
|
||
|
local KFGoreManager KFGM;
|
||
|
local KFPawn Corpse;
|
||
|
local float AreaRadiusSq;
|
||
|
|
||
|
KFGM = KFGoreManager(WorldInfo.MyGoreEffectManager);
|
||
|
|
||
|
if( KFGM == none )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
AreaRadiusSq = AreaRadiusForMassacre * AreaRadiusForMassacre;
|
||
|
|
||
|
// check corpses for proximity to KilledZed
|
||
|
for( i = 0; i < KFGM.CorpsePool.Length && MassacreCount < NumZedsInAreaForMassacre; ++i )
|
||
|
{
|
||
|
Corpse = KFGM.CorpsePool[i];
|
||
|
|
||
|
// don't count KilledZed toward massacre
|
||
|
if( Corpse == KilledZed )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// only check zeds that have died within the time limit for a "massacre"
|
||
|
if( `TimeSince(Corpse.TimeOfDeath) > TimeLimitForMassacre )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// only check zeds that are within the radius limit for a "massacre"
|
||
|
if( VSizeSq(Corpse.Location - KilledZed.Location) > AreaRadiusSq )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
++MassacreCount;
|
||
|
}
|
||
|
|
||
|
if( MassacreCount >= NumZedsInAreaForMassacre )
|
||
|
{
|
||
|
AddRandomDialogOption( Killer, `KILL_Massacre, out_NumOptions, out_BestOptionID );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Add "close call" random dialog if player killed zed right before zed killed him */
|
||
|
function CheckCloseCallKillDialog( KFPawn_Human Killer, KFPawn_Monster Zed, out int out_NumOptions, out int out_BestOptionID )
|
||
|
{
|
||
|
if( Killer.LastHitBy == Zed.MyKFAIC
|
||
|
&& (float(Killer.Health) / float(Killer.HealthMax) <= CloseCallKillHealthPctThreshold) )
|
||
|
{
|
||
|
AddRandomDialogOption( Killer, `KILL_CloseCall, out_NumOptions, out_BestOptionID );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function CheckKillAsLastAliveDialog( KFPawn_Human Killer, KFPawn_Monster Zed, out int out_NumOptions, out int out_BestOptionID )
|
||
|
{
|
||
|
if( KFGameInfo(WorldInfo.Game).GetLivingPlayerCount() == 1 )
|
||
|
{
|
||
|
AddRandomDialogOption( Killer, `KILL_AsLastPlayer, out_NumOptions, out_BestOptionID );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function CheckKilledWithFavoriteWeaponDialog( KFPawn_Human Killer, out int out_NumOptions, out int out_BestOptionID )
|
||
|
{
|
||
|
local int FavoriteWeaponIndex;
|
||
|
local KFCharacterInfo_Human KFCI;
|
||
|
|
||
|
KFCI = KFCharacterInfo_Human( Killer.CharacterArch );
|
||
|
if( KFCI == none )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
FavoriteWeaponIndex = KFCI.GetFavoriteWeaponIndexOf( Killer.Weapon );
|
||
|
if( FavoriteWeaponIndex < 0 )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
AddRandomDialogOption( Killer, `KILL_Fave1 + FavoriteWeaponIndex, out_NumOptions, out_BestOptionID );
|
||
|
}
|
||
|
|
||
|
/** Set up response for selected "kill zed" dialog */
|
||
|
function PlayBasicKilledResponse( KFPawn_Human Killer, KFPawn_Monster KilledZed, int KilledEventID )
|
||
|
{
|
||
|
local int ResponseEventID;
|
||
|
local bool bPlayOnlyAsResponse;
|
||
|
|
||
|
switch( KilledEventID )
|
||
|
{
|
||
|
case `KILL_OnARoll:
|
||
|
ResponseEventID = `KILL_OnARollResp;
|
||
|
bPlayOnlyAsResponse = true;
|
||
|
break;
|
||
|
|
||
|
case `KILL_Massacre:
|
||
|
ResponseEventID = `KILL_MassacreResp;
|
||
|
bPlayOnlyAsResponse = true;
|
||
|
break;
|
||
|
|
||
|
case `KILL_Slashing:
|
||
|
ResponseEventID = `KILL_SlashingResp;
|
||
|
break;
|
||
|
|
||
|
case `KILL_Blunt:
|
||
|
ResponseEventID = `KILL_BluntResp;
|
||
|
break;
|
||
|
|
||
|
case `KILL_Ballistic:
|
||
|
ResponseEventID = `KILL_BallisticResp;
|
||
|
break;
|
||
|
|
||
|
case `KILL_Explosive:
|
||
|
ResponseEventID = `KILL_ExplosiveResp;
|
||
|
break;
|
||
|
|
||
|
case `KILL_Fire:
|
||
|
ResponseEventID = `KILL_FireResp;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
return; // do nothing
|
||
|
};
|
||
|
|
||
|
PlayBasicDialogResponse( Killer, KilledEventID, ResponseEventID, /*Responder*/, KilledZed, bPlayOnlyAsResponse );
|
||
|
}
|
||
|
|
||
|
/************************************************
|
||
|
* Damage Zed Dialog
|
||
|
************************************************/
|
||
|
|
||
|
function PlayDamagedZedDialog( KFPawn_Human Damager, KFPawn_Monster Zed, class<DamageType> DamageType )
|
||
|
{
|
||
|
local int NumOptions, BestOptionID;
|
||
|
|
||
|
if( Damager.VoiceGroupArch == none || Damager.VoiceGroupArch.default.EventDataClass == none )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
BestOptionID = -1;
|
||
|
|
||
|
if( Zed.IsDoingSpecialMove(SM_Knockdown) )
|
||
|
{
|
||
|
AddRandomDialogOption( Damager, `DAMZ_Knockdown, NumOptions, BestOptionID );
|
||
|
}
|
||
|
else if( Zed.IsDoingSpecialMove(SM_Stumble) )
|
||
|
{
|
||
|
AddRandomDialogOption( Damager, `DAMZ_Stumble, NumOptions, BestOptionID );
|
||
|
}
|
||
|
else if( Zed.IsDoingSpecialMove(SM_Stunned) )
|
||
|
{
|
||
|
AddRandomDialogOption( Damager, `DAMZ_Stun, NumOptions, BestOptionID );
|
||
|
}
|
||
|
|
||
|
if( class<KFDamageType>(DamageType) != none )
|
||
|
{
|
||
|
AddRandomDialogOption( Damager, class< KFDamageType >( DamageType ).static.GetDamagerDialogID(), NumOptions, BestOptionID );
|
||
|
}
|
||
|
|
||
|
PlayDialogEvent( Damager, BestOptionID );
|
||
|
}
|
||
|
|
||
|
/** Play "over and over" random dialog if player deals enough consecutive damage to a given zed
|
||
|
* NOTE: this needs to be called before zed's LastHitBy and LastPainTime are set for current hit */
|
||
|
function PlayDamageZedContinuousDialog( KFPawn_Human Damager, KFPawn_Monster Zed )
|
||
|
{
|
||
|
Damager.UpdateContinuousDamage( Zed, TimeBetweenHitsForContinuousDamage );
|
||
|
if( `TimeSince(Damager.InitialContinousDamageTime) >= TimeForContinuousDamageThreshold )
|
||
|
{
|
||
|
PlayDialogEvent( Damager, `DAMZ_OverAndOver );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Add "dead horse" random dialog if player hits a corpse repeatedly */
|
||
|
function PlayBeatDeadHorseDialog( KFPawn_Human Speaker, KFPawn_Monster DeadZed )
|
||
|
{
|
||
|
if( DeadZed.DeadHorseHitStreakAmt < NumHitsForDeadHorse && `TimeSince(DeadZed.LastDeadHorseHitTime) < TimeBetweenHitsForDeadHorse )
|
||
|
{
|
||
|
DeadZed.UpdateDeadHorseStreak( true );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
DeadZed.UpdateDeadHorseStreak( false );
|
||
|
}
|
||
|
|
||
|
if( DeadZed.DeadHorseHitStreakAmt >= NumHitsForDeadHorse )
|
||
|
{
|
||
|
PlayDialogEvent( Speaker, `KILL_BeatDeadHorse );
|
||
|
PlayBasicDialogResponse( Speaker, `KILL_BeatDeadHorse, `KILL_BeatDeadHorseResp, /*Responder*/, DeadZed );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************
|
||
|
* Player Damage Dialog
|
||
|
************************************************/
|
||
|
|
||
|
function PlayPlayerDamageDialog( KFPawn_Human Player, class< DamageType > DamageType, int Damage )
|
||
|
{
|
||
|
local int NumOptions, BestOptionID;
|
||
|
|
||
|
if( Player.VoiceGroupArch == none || Player.VoiceGroupArch.default.EventDataClass == none )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
BestOptionID = -1;
|
||
|
|
||
|
if( (float(Player.Health) / float(Player.HealthMax) < PlayerHealthPctForNearDeath) )
|
||
|
{
|
||
|
AddRandomDialogOption( Player, `DAMP_NearDeath, NumOptions, BestOptionID );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
CheckPlayerDamageGeneric( Player, Damage, NumOptions, BestOptionID );
|
||
|
}
|
||
|
|
||
|
if( class<KFDamageType>(DamageType) != none )
|
||
|
{
|
||
|
AddRandomDialogOption( Player, class<KFDamageType>(DamageType).static.GetDamageeDialogID(), NumOptions, BestOptionID );
|
||
|
|
||
|
// if player has been blinded (due to toxic damage, though berserker doesn't get blinded), add "blinded" random option
|
||
|
if( ClassIsChildOf(DamageType, class'KFDT_Toxic') && !ClassIsChildOf(Player.GetPerk().Class, class'KFPerk_Berserker') )
|
||
|
{
|
||
|
AddRandomDialogOption( Player, `DAMP_Blinded, NumOptions, BestOptionID );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
PlayDialogEvent( Player, BestOptionID );
|
||
|
|
||
|
// if playing "blinded" dialog, prepare for "blinded" dialog response
|
||
|
if( BestOptionID == `DAMP_Blinded )
|
||
|
{
|
||
|
PlayBasicDialogResponse( Player, `DAMP_Blinded, `DAMP_BlindedResp, /*Responder*/, /*Target*/, true );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Add generic damage vocalizations depending on how hard the player was hit */
|
||
|
function CheckPlayerDamageGeneric( KFPawn_Human Player, int Damage, out int out_NumOptions, out int out_BestOptionID )
|
||
|
{
|
||
|
local float DamageTakenStreakPct;
|
||
|
|
||
|
Player.UpdateDamageTakenStreak( Damage, PlayerTakeDamageStreakInterval );
|
||
|
DamageTakenStreakPct = float( Player.DamageTakenStreakAmt ) / float( Player.HealthMax );
|
||
|
if( DamageTakenStreakPct >= PlayerTakeDamageStreakPctForScream )
|
||
|
{
|
||
|
AddRandomDialogOption( Player, `DAMP_Scream, out_NumOptions, out_BestOptionID );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
AddRandomDialogOption( Player, `DAMP_Grunt, out_NumOptions, out_BestOptionID );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function PlayPlayerGrabbedDialog( KFPawn_Human Speaker )
|
||
|
{
|
||
|
if( Speaker != none )
|
||
|
{
|
||
|
PlayDialogEvent( Speaker, `SPOTZ_GrabbedMe );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function PlayPlayerGrabbedByPatriarchDialog( KFPawn_Human Speaker )
|
||
|
{
|
||
|
if( Speaker != none )
|
||
|
{
|
||
|
PlayDialogEvent( Speaker, `SPOTZ_PulledMeIn );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************
|
||
|
* Player Death Dialog
|
||
|
************************************************/
|
||
|
|
||
|
function PlayPlayerDeathDialog( KFPawn_Human Player )
|
||
|
{
|
||
|
local KFPawn_Human KFPH;
|
||
|
local float MaxDistanceSq;
|
||
|
|
||
|
if( Player.VoiceGroupArch == none || Player.VoiceGroupArch.default.EventDataClass == none )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
PlayDialogEvent( Player, `DAMP_Dying );
|
||
|
|
||
|
MaxDistanceSq = GetEventRadiusSq( `DAMP_SpotTeamDeathLast, Player.GetVoiceGroupEventDataClass() );
|
||
|
|
||
|
foreach WorldInfo.AllPawns( class'KFPawn_Human', KFPH )
|
||
|
{
|
||
|
if( !PawnIsValidPlayer(KFPH) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if( PawnCanSpotPawn(KFPH, Player, MaxDistanceSq, GetEventFOV(`DAMP_SpotTeamDeathLast, KFPH.GetVoiceGroupEventDataClass()), true) )
|
||
|
{
|
||
|
// player just saw only other player die
|
||
|
if( KFGameInfo(WorldInfo.Game).GetLivingPlayerCount() == 1 )
|
||
|
{
|
||
|
PlayDialogEvent( KFPH, `DAMP_SpotTeamDeathLast );
|
||
|
}
|
||
|
// player just saw female teammate die
|
||
|
else if( KFCharacterInfo_Human(KFPH.CharacterArch).bIsFemale )
|
||
|
{
|
||
|
PlayDialogEvent( KFPH, `DAMP_SpotTeamDeathF );
|
||
|
}
|
||
|
// player just saw male teammate die
|
||
|
else
|
||
|
{
|
||
|
PlayDialogEvent( KFPH, `DAMP_SpotTeamDeathM );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************
|
||
|
* Spot Zed Dialog
|
||
|
************************************************/
|
||
|
|
||
|
/** Compares priority of two events */
|
||
|
function bool CheckSpottedDialogPriority( KFPawn_Human Spotter, int CheckEventID, int BestEventID )
|
||
|
{
|
||
|
local DialogEventInfo CheckEventInfo, BestEventInfo;
|
||
|
|
||
|
if( BestEventID < 0 )
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
CheckEventInfo = Spotter.VoiceGroupArch.static.GetDialogEventInfo(CheckEventID);
|
||
|
BestEventInfo = Spotter.VoiceGroupArch.static.GetDialogEventInfo(BestEventID);
|
||
|
|
||
|
if( CheckEventInfo.Priority > BestEventInfo.Priority )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Called when a zed spots a player (line of sight check has already passed).
|
||
|
* We use this in reverse... when a zed spots a player, then check if the player spots the zed
|
||
|
*/
|
||
|
function CheckSpotMonsterDialog( Pawn Spotter, KFPawn_Monster Spotted )
|
||
|
{
|
||
|
local int SpottedDialogEventID, BestSpottedEventID;
|
||
|
local KFPawn_Human KFPHSpotter;
|
||
|
local class<KFPawnVoiceGroupEventData> EventDataClass;
|
||
|
|
||
|
BestSpottedEventID = -1;
|
||
|
|
||
|
Spotted.MyKFAIC.bWasVisibleToEnemy = Spotted.MyKFAIC.bIsVisibleToEnemy;
|
||
|
|
||
|
KFPHSpotter = KFPawn_Human(Spotter);
|
||
|
if (KFPHSpotter == none)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
EventDataClass = KFPHSpotter.GetVoiceGroupEventDataClass();
|
||
|
|
||
|
// if zed is within FOV
|
||
|
if( ActorWithinPawnFOV(Spotted, Spotter, GetEventFOV(`SPOTZ_Generic, EventDataClass)) )
|
||
|
{
|
||
|
Spotted.MyKFAIC.bIsVisibleToEnemy = true;
|
||
|
|
||
|
// if zed just entered a player's view
|
||
|
if( !Spotted.MyKFAIC.bWasVisibleToEnemy && Spotted.MyKFAIC.bIsVisibleToEnemy )
|
||
|
{
|
||
|
// if zed is within "surprise" (really close) range
|
||
|
if( ActorWithinPawnRadius(Spotted, Spotter, GetEventRadius(`SPOTZ_Surprise, EventDataClass)) )
|
||
|
{
|
||
|
PlayDialogEvent( KFPHSpotter, `SPOTZ_Surprise );
|
||
|
}
|
||
|
else if( ActorWithinPawnRadius(Spotted, Spotter, GetEventRadius(`SPOTZ_Generic, EventDataClass)) )
|
||
|
{
|
||
|
SpottedDialogEventID = Spotted.GetSpotterDialogID();
|
||
|
if( CheckSpottedDialogPriority(KFPHSpotter, SpottedDialogEventID, BestSpottedEventID) )
|
||
|
{
|
||
|
BestSpottedEventID = SpottedDialogEventID;
|
||
|
}
|
||
|
PlayDialogEvent( KFPHSpotter, BestSpottedEventID );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Spotted.MyKFAIC.bIsVisibleToEnemy = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Check all of the things that can be spotted and pick the best one to comment on.
|
||
|
* Called from Tick.
|
||
|
*/
|
||
|
function CheckSpottedDialog()
|
||
|
{
|
||
|
local Pawn P;
|
||
|
local KFPawn_Human KFPH;
|
||
|
local KFPawn_Monster BestMonster;
|
||
|
local int SpottedCount, SpottedEventID;
|
||
|
|
||
|
local int NumOptions, BestOptionID;
|
||
|
|
||
|
SpottedEventID = -1;
|
||
|
BestOptionID = -1;
|
||
|
|
||
|
// we only check one human pawn per call to CheckSpottedDialog, so we cache the next pawn we need to check.
|
||
|
// if that pawn is invalid, reset it.
|
||
|
if( NextSpotterPawn == none || NextSpotterPawn.bDeleteMe || !NextSpotterPawn.IsAliveAndWell() )
|
||
|
{
|
||
|
NextSpotterPawn = WorldInfo.PawnList;
|
||
|
}
|
||
|
|
||
|
// check what each player can spot
|
||
|
for( P = NextSpotterPawn; P != none; P = P.NextPawn )
|
||
|
{
|
||
|
KFPH = KFPawn_Human(P);
|
||
|
if( KFPH == none || !PawnIsValidPlayer(KFPH) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// only call out zeds if you haven't been engaged with zeds for a little while
|
||
|
if( KFPH.TimeSpentIdling() > IdleTimeForSpottingZed )
|
||
|
{
|
||
|
// FindBestSpottedMonster checks proximity, FOV, and dialog priority, but NOT line-of-sight.
|
||
|
// We check line of sight only against the BestMonster returned by FindBestSpottedMonster for performance's sake.
|
||
|
BestMonster = FindBestSpottedMonster( KFPH, SpottedCount, SpottedEventID );
|
||
|
if( BestMonster != none && FastTrace(KFPH.GetPawnViewLocation(), BestMonster.Location) )
|
||
|
{
|
||
|
AddRandomDialogOption( KFPH, SpottedEventID, NumOptions, BestOptionID );
|
||
|
|
||
|
if( float(BestMonster.Health) / float(BestMonster.HealthMax) <= ZedAlmostDeadHealthPctThreshold )
|
||
|
{
|
||
|
AddRandomDialogOption( KFPH, `SPOTZ_AlmostDead, NumOptions, BestOptionID );
|
||
|
}
|
||
|
|
||
|
if( SpottedCount >= SpotLargeHordeNumZeds )
|
||
|
{
|
||
|
AddRandomDialogOption( KFPH, `SPOTZ_Horde, NumOptions, BestOptionID );
|
||
|
}
|
||
|
|
||
|
// check if BestMonster is "behind" any teammates
|
||
|
if( SpotEnemyBehindPlayer(KFPH, BestMonster) )
|
||
|
{
|
||
|
AddRandomDialogOption( KFPH, `SPOTZ_Behind, NumOptions, BestOptionID );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// SprintTowardZedStartTime is set/reset in FindBestSpottedMonster, called above
|
||
|
if( KFPH.SprintTowardZedStartTime > 0 && `TimeSince(KFPH.SprintTowardZedStartTime) >= SprintTowardZedDuration )
|
||
|
{
|
||
|
AddRandomDialogOption( KFPH, `DAMZ_SprintToward, NumOptions, BestOptionID );
|
||
|
}
|
||
|
|
||
|
// only check for SpotItem and SpotAmmo dialog if we haven't already added higher priority dialog (like SpotZed)
|
||
|
if( BestOptionID < 0 || GetEventPriority(BestOptionID, KFPH.GetVoiceGroupEventDataClass()) >= 5 )
|
||
|
{
|
||
|
CheckSpotPickupsDialog( KFPH, NumOptions, BestOptionID );
|
||
|
}
|
||
|
|
||
|
PlayDialogEvent( KFPH, BestOptionID );
|
||
|
PlayBasicSpottedResponse(KFPH, BestMonster, BestOptionID);
|
||
|
|
||
|
// since we're only checking one pawn per call, cache the next pawn in line and break
|
||
|
NextSpotterPawn = KFPH.NextPawn;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Finds the best zed to comment on based on proximity, field of view, and zed dialog priority */
|
||
|
function KFPawn_Monster FindBestSpottedMonster( KFPawn_Human Spotter, out int NumSpotted, out int BestSpottedEventID )
|
||
|
{
|
||
|
local Pawn MP;
|
||
|
local KFPawn_Monster KFPM, BestMonster;
|
||
|
local int SpottedDialogEventID;
|
||
|
local bool bSprintingTowardZed;
|
||
|
local class<KFPawnVoiceGroupEventData> EventDataClass;
|
||
|
|
||
|
EventDataClass = Spotter.GetVoiceGroupEventDataClass();
|
||
|
|
||
|
for( MP = WorldInfo.PawnList; MP != none; MP = MP.NextPawn )
|
||
|
{
|
||
|
if( !MP.IsAliveAndWell() || MP.GetTeamNum() == 0 )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
KFPM = KFPawn_Monster( MP );
|
||
|
if( KFPM == none )
|
||
|
{
|
||
|
// should never happen, right?
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if( KFPM.bIsHeadless )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if( VSizeSq(KFPM.Location - Spotter.Location) >= GetEventRadiusSq(`SPOTZ_Generic, EventDataClass) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if( ActorWithinPawnFOV(KFPM, Spotter, GetEventFOV(`SPOTZ_Generic, EventDataClass)) )
|
||
|
{
|
||
|
NumSpotted++;
|
||
|
|
||
|
SpottedDialogEventID = KFPM.GetSpotterDialogID();
|
||
|
|
||
|
// check priority of spotted dialog event for this zed against priority of best-so-far zed
|
||
|
if( CheckSpottedDialogPriority(Spotter, SpottedDialogEventID, BestSpottedEventID) )
|
||
|
{
|
||
|
BestSpottedEventID = SpottedDialogEventID;
|
||
|
BestMonster = KFPM;
|
||
|
}
|
||
|
|
||
|
// check if player is charging toward a zed
|
||
|
if( Spotter.bIsSprinting && !bSprintingTowardZed && ActorWithinPawnFOV(KFPM, Spotter, GetEventFOV(`DAMZ_SprintToward, EventDataClass)) )
|
||
|
{
|
||
|
bSprintingTowardZed = true;
|
||
|
if( Spotter.SprintTowardZedStartTime < 0 )
|
||
|
{
|
||
|
Spotter.SprintTowardZedStartTime = WorldInfo.TimeSeconds;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// reset sprint-toward-zed start time if not sprinting toward zed
|
||
|
if( !bSprintingTowardZed )
|
||
|
{
|
||
|
Spotter.SprintTowardZedStartTime = -1;
|
||
|
}
|
||
|
|
||
|
return BestMonster;
|
||
|
}
|
||
|
|
||
|
/** Checks if given zed is within spotter's view and right behind another player */
|
||
|
function bool SpotEnemyBehindPlayer( KFPawn_Human Spotter, KFPawn_Monster Enemy )
|
||
|
{
|
||
|
local Pawn OtherPlayer;
|
||
|
local vector FacingDir, ToEnemyDir, ToPlayerDir;
|
||
|
local float EventRadiusSq;
|
||
|
local class<KFPawnVoiceGroupEventData> EventDataClass;
|
||
|
|
||
|
EventDataClass = Spotter.GetVoiceGroupEventDataClass();
|
||
|
|
||
|
for( OtherPlayer = WorldInfo.PawnList; OtherPlayer != none; OtherPlayer = OtherPlayer.NextPawn )
|
||
|
{
|
||
|
if( !OtherPlayer.IsAliveAndWell() || OtherPlayer.GetTeamNum() != 0 || OtherPlayer == Spotter )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// check if the player is within view
|
||
|
ToPlayerDir = OtherPlayer.Location - Spotter.Location;
|
||
|
if( VSizeSq(ToPlayerDir) >= GetEventRadiusSq(`SPOTZ_Generic, EventDataClass) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
FacingDir = vector( Spotter.Rotation );
|
||
|
ToPlayerDir = Normal( ToPlayerDir );
|
||
|
|
||
|
if( ToPlayerDir dot FacingDir < GetEventFOV(`SPOTZ_Generic, EventDataClass) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
EventRadiusSq = GetEventRadiusSq(`SPOTZ_Behind, EventDataClass);
|
||
|
|
||
|
// check if Enemy is within "behind view" of the player
|
||
|
ToEnemyDir = Enemy.Location - OtherPlayer.Location;
|
||
|
if( VSizeSq(ToEnemyDir) >= EventRadiusSq )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
FacingDir = vector( OtherPlayer.Rotation );
|
||
|
ToEnemyDir = Normal( ToEnemyDir );
|
||
|
|
||
|
if( ToEnemyDir dot FacingDir <= -GetEventFOV(`SPOTZ_Behind, EventDataClass) )
|
||
|
{
|
||
|
// only consider behind if we can actually see the player whom Enemy is behind
|
||
|
if( FastTrace(Spotter.GetPawnViewLocation(), OtherPlayer.GetPawnViewLocation()) )
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// for efficiency's sake, break out after we find one good candidate, even if we don't succeed trace
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/** Checks if Spotter can see any pickups in the map */
|
||
|
function CheckSpotPickupsDialog( KFPawn_Human Spotter, out int out_NumOptions, out int out_BestOptionID )
|
||
|
{
|
||
|
local int i;
|
||
|
local KFGameInfo KFGI;
|
||
|
local bool bSpottedWeapon, bSpottedArmor, bSpottedAmmo;
|
||
|
local class< KFPawnVoiceGroupEventData > EventDataClass;
|
||
|
|
||
|
KFGI = KFGameInfo( WorldInfo.Game );
|
||
|
|
||
|
if( KFGI == none )
|
||
|
{
|
||
|
// should never happen, right?
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
EventDataClass = Spotter.GetVoiceGroupEventDataClass();
|
||
|
|
||
|
for( i = 0; i < KFGI.AllPickupFactories.Length; ++i )
|
||
|
{
|
||
|
// ignore factories without visible pickups
|
||
|
if( KFGI.AllPickupFactories[i].bPickupHidden )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// ignore weapon factories if a weapon was already spotted
|
||
|
if( KFGI.AllPickupFactories[i].CurrentPickupIsWeapon() && bSpottedWeapon )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// ignore armor factories if armor was already spotted
|
||
|
if( KFGI.AllPickupFactories[i].CurrentPickupIsArmor() && bSpottedArmor )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// ignore ammo factories if ammo was already spotted
|
||
|
if( KFGI.AllPickupFactories[i].CurrentPickupIsAmmo() && bSpottedAmmo )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if( !PawnCanSpotActor(Spotter, KFGI.AllPickupFactories[i], GetEventRadiusSq(`ACT_SpotWeap, EventDataClass), GetEventFOV(`ACT_SpotWeap, EventDataClass), true) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if( KFGI.AllPickupFactories[i].CurrentPickupIsWeapon() )
|
||
|
{
|
||
|
AddRandomDialogOption( Spotter, `ACT_SpotWeap, out_NumOptions, out_BestOptionID );
|
||
|
bSpottedWeapon = true;
|
||
|
}
|
||
|
else if( KFGI.AllPickupFactories[i].CurrentPickupIsArmor() )
|
||
|
{
|
||
|
AddRandomDialogOption( Spotter, `ACT_SpotArmor, out_NumOptions, out_BestOptionID );
|
||
|
bSpottedArmor = true;
|
||
|
}
|
||
|
else if( KFGI.AllPickupFactories[i].CurrentPickupIsAmmo() )
|
||
|
{
|
||
|
AddRandomDialogOption( Spotter, `ACT_SpotAmmo, out_NumOptions, out_BestOptionID );
|
||
|
bSpottedAmmo = true;
|
||
|
}
|
||
|
|
||
|
// break out early if we've added all possible random dialog for pickups
|
||
|
if( bSpottedWeapon && bSpottedArmor && bSpottedAmmo )
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Checks if any player can spot a zed that just cloaked/uncloaked */
|
||
|
function PlaySpotCloakDialog( KFPawn_Monster Cloaker, bool bNewCloaking )
|
||
|
{
|
||
|
local KFPawn_Human KFPH;
|
||
|
|
||
|
foreach WorldInfo.AllPawns( class'KFPawn_Human', KFPH )
|
||
|
{
|
||
|
if( !PawnIsValidPlayer(KFPH) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if( PlayerCanSpotEnemy(KFPH, Cloaker) )
|
||
|
{
|
||
|
if( bNewCloaking )
|
||
|
{
|
||
|
PlayDialogEvent( KFPH, `SPOTZ_JustCloaked );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
PlayDialogEvent( KFPH, `SPOTZ_JustUncloaked );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Checks if any player can spot a zed that just became enraged */
|
||
|
function PlaySpotEnragedDialog( KFPawn_Monster EnragedEnemy )
|
||
|
{
|
||
|
local KFPawn_Human KFPH;
|
||
|
|
||
|
foreach WorldInfo.AllPawns( class'KFPawn_Human', KFPH )
|
||
|
{
|
||
|
if( !PawnIsValidPlayer(KFPH) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if( PlayerCanSpotEnemy(KFPH, EnragedEnemy) )
|
||
|
{
|
||
|
PlayDialogEvent( KFPH, `SPOTZ_RagingAtMe );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Checks if player being charged can spot the zed charging him */
|
||
|
function PlaySpotChargeDialog( KFPawn_Monster ChargingEnemy, KFPawn TargetPlayer )
|
||
|
{
|
||
|
local KFPawn_Human KFPH;
|
||
|
|
||
|
KFPH = KFPawn_Human( TargetPlayer );
|
||
|
if( KFPH == none )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if( KFPH.VoiceGroupArch == none || KFPH.VoiceGroupArch.default.EventDataClass == none )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if( PlayerCanSpotEnemy(KFPH, ChargingEnemy) )
|
||
|
{
|
||
|
PlayDialogEvent( KFPH, `SPOTZ_ChargingAtMe );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Checks if any player can spot a zed about to shoot a rocket */
|
||
|
function PlaySpotRocketsDialog( KFPawn_Monster Spotted )
|
||
|
{
|
||
|
local KFPawn_Human KFPH;
|
||
|
local class< KFPawnVoiceGroupEventData > EventDataClass;
|
||
|
|
||
|
foreach WorldInfo.AllPawns( class'KFPawn_Human', KFPH )
|
||
|
{
|
||
|
if( !PawnIsValidPlayer(KFPH) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
EventDataClass = KFPH.GetVoiceGroupEventDataClass();
|
||
|
|
||
|
if( PlayerCanSpotEnemy(KFPH, Spotted, GetEventRadiusSq(`SPOTZ_ShootingRockets, EventDataClass), GetEventFOV(`SPOTZ_ShootingRockets, EventDataClass)) )
|
||
|
{
|
||
|
PlayDialogEvent( KFPH, `SPOTZ_ShootingRockets );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Checks if any player can spot a zed about to throw a grenade */
|
||
|
function PlaySpotGrenadeDialog( KFPawn_Monster Spotted )
|
||
|
{
|
||
|
local KFPawn_Human KFPH;
|
||
|
local class< KFPawnVoiceGroupEventData > EventDataClass;
|
||
|
|
||
|
foreach WorldInfo.AllPawns( class'KFPawn_Human', KFPH )
|
||
|
{
|
||
|
if( !PawnIsValidPlayer(KFPH) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
EventDataClass = KFPH.GetVoiceGroupEventDataClass();
|
||
|
|
||
|
if( PlayerCanSpotEnemy(KFPH, Spotted, GetEventRadiusSq(`SPOTZ_ThrowGrenade, EventDataClass), GetEventFOV(`SPOTZ_ThrowGrenade, EventDataClass)) )
|
||
|
{
|
||
|
PlayDialogEvent( KFPH, `SPOTZ_ThrowGrenade );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Checks if a player being shot at can see shooter */
|
||
|
function PlayBeingShotAtDialog( KFPawn_Human Target, KFPawn_Monster Shooter )
|
||
|
{
|
||
|
local class< KFPawnVoiceGroupEventData > EventDataClass;
|
||
|
|
||
|
if( Target == none )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
EventDataClass = Target.GetVoiceGroupEventDataClass();
|
||
|
|
||
|
if( PlayerCanSpotEnemy(Target, Shooter, GetEventRadiusSq(`SPOTZ_ShootingAtMe, EventDataClass), GetEventFOV(`SPOTZ_ShootingAtMe, EventDataClass)) )
|
||
|
{
|
||
|
PlayDialogEvent( Target, `SPOTZ_ShootingAtMe );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Checks if any player can spot a zed about to heal */
|
||
|
function PlaySpotZedHealingDialog( KFPawn_Monster Spotted )
|
||
|
{
|
||
|
local KFPawn_Human KFPH;
|
||
|
|
||
|
foreach WorldInfo.AllPawns( class'KFPawn_Human', KFPH )
|
||
|
{
|
||
|
if( !PawnIsValidPlayer(KFPH) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if( PlayerCanSpotEnemy(KFPH, Spotted) )
|
||
|
{
|
||
|
PlayDialogEvent( KFPH, `SPOTZ_Healing );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Checks if any player can spot a zed that is running away */
|
||
|
function PlaySpotRunAwayDialog( KFPawn_Monster Spotted )
|
||
|
{
|
||
|
local KFPawn_Human KFPH;
|
||
|
|
||
|
foreach WorldInfo.AllPawns( class'KFPawn_Human', KFPH )
|
||
|
{
|
||
|
if( !PawnIsValidPlayer(KFPH) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if( PlayerCanSpotEnemy(KFPH, Spotted) )
|
||
|
{
|
||
|
PlayDialogEvent( KFPH, `SPOTZ_RunningAway );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Checks if player who could see boss can no longer see boss */
|
||
|
function PlayLoseSightOfBossDialog( KFPawn_Monster Boss, KFPawn_Human Speaker )
|
||
|
{
|
||
|
local class< KFPawnVoiceGroupEventData > EventDataClass;
|
||
|
|
||
|
EventDataClass = Speaker.GetVoiceGroupEventDataClass();
|
||
|
|
||
|
if( ActorWithinPawnFOV(Boss, Speaker, GetEventFOV(`SPOTZ_NotSeen, EventDataClass)) )
|
||
|
{
|
||
|
PlayDialogEvent( Speaker, `SPOTZ_NotSeen );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Sets up basic response for spoken "spotted" dialog */
|
||
|
function PlayBasicSpottedResponse( KFPawn_Human Spotter, KFPawn_Monster Spotted, int SpottedEventID )
|
||
|
{
|
||
|
local int ResponseEventID;
|
||
|
|
||
|
if( Spotted == none )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
switch( SpottedEventID )
|
||
|
{
|
||
|
case `SPOTZ_Siren:
|
||
|
ResponseEventID = `SPOTZ_FemaleResp;
|
||
|
break;
|
||
|
|
||
|
case `SPOTZ_Fleshpound:
|
||
|
case `SPOTZ_Scrake:
|
||
|
case `SPOTZ_BossGeneric: // ?
|
||
|
case `SPOTZ_BossFemale: // ?
|
||
|
ResponseEventID = `SPOTZ_BigZedResp;
|
||
|
break;
|
||
|
|
||
|
case `SPOTZ_Generic:
|
||
|
ResponseEventID =`SPOTZ_GenericResp;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
return; // do nothing
|
||
|
};
|
||
|
|
||
|
PlayBasicDialogResponse( Spotter, SpottedEventID, ResponseEventID, /*Responder*/, Spotted, true );
|
||
|
}
|
||
|
|
||
|
/************************************************
|
||
|
* Situational Dialog
|
||
|
************************************************/
|
||
|
|
||
|
/** Checks if any player should play idle dialog */
|
||
|
function CheckIdleDialog()
|
||
|
{
|
||
|
local KFPawn_Human KFPH;
|
||
|
|
||
|
foreach WorldInfo.AllPawns( class'KFPawn_Human', KFPH )
|
||
|
{
|
||
|
if( !PawnIsValidPlayer(KFPH) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if( KFPH.TimeSpentIdling() > IdleTimeForSituationalDialog )
|
||
|
{
|
||
|
PlaySituationalDialog( KFPH );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Plays idle/situational dialog for a given speaker to a target player */
|
||
|
function PlaySituationalDialog( KFPawn_Human Speaker )
|
||
|
{
|
||
|
local KFPawn_Human Target;
|
||
|
local class< KFPawnVoiceGroupEventData > EventDataClass;
|
||
|
|
||
|
EventDataClass = Speaker.GetVoiceGroupEventDataClass();
|
||
|
|
||
|
foreach WorldInfo.AllPawns( class'KFPawn_Human', Target )
|
||
|
{
|
||
|
if( Target == Speaker || !PawnIsValidPlayer(Target) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if( PawnCanSpotPawn(Speaker, Target, GetEventRadiusSq(`SITU_General, EventDataClass), GetEventFOV(`SITU_General, EventDataClass)) )
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
PlayRandomSituationalDialog( Speaker, Target );
|
||
|
}
|
||
|
|
||
|
/** Calculates situational dialog options, then plays a random one */
|
||
|
function PlayRandomSituationalDialog( KFPawn_Human Speaker, KFPawn_Human Target )
|
||
|
{
|
||
|
local int NumPlayers, NumLivingPlayers;
|
||
|
|
||
|
local int NumOptions, BestOptionID;
|
||
|
|
||
|
NumPlayers = WorldInfo.Game.NumPlayers;
|
||
|
NumLivingPlayers = KFGameInfo( WorldInfo.Game ).GetLivingPlayerCount();
|
||
|
|
||
|
BestOptionID = -1;
|
||
|
|
||
|
// add option for environmental dialog (dark, cold, etc.) if player is within certain volumes
|
||
|
if( Speaker.EnvironmentDialogEventID > 0 )
|
||
|
{
|
||
|
AddRandomDialogOption( Speaker, Speaker.EnvironmentDialogEventID, NumOptions, BestOptionID );
|
||
|
}
|
||
|
|
||
|
// if speaker has someone to speak to, play conversational dialog
|
||
|
if( Target != none )
|
||
|
{
|
||
|
if( Target.MyKFWeapon != none && Target.MyKFWeapon.GetMaxAmmoAmount(0) > 0 )
|
||
|
{
|
||
|
AddRandomDialogOption( Speaker, `SITU_AmmoCheck, NumOptions, BestOptionID );
|
||
|
}
|
||
|
AddRandomDialogOption( Speaker, `SITU_HealthCheck, NumOptions, BestOptionID );
|
||
|
AddRandomDialogOption( Speaker, `SITU_General, NumOptions, BestOptionID );
|
||
|
|
||
|
if( NumLivingPlayers == NumPlayers )
|
||
|
{
|
||
|
AddRandomDialogOption( Speaker, `SITU_AllAlive, NumOptions, BestOptionID );
|
||
|
}
|
||
|
else if( NumLivingPlayers > NumPlayers / 2 )
|
||
|
{
|
||
|
AddRandomDialogOption( Speaker, `SITU_MostAlive, NumOptions, BestOptionID );
|
||
|
}
|
||
|
else if( NumLivingPlayers == NumPlayers / 2 )
|
||
|
{
|
||
|
AddRandomDialogOption( Speaker, `SITU_HalfAlive, NumOptions, BestOptionID );
|
||
|
}
|
||
|
|
||
|
if( Speaker.PlayerReplicationInfo.Score >= IdleHighDoshThreshold )
|
||
|
{
|
||
|
AddRandomDialogOption( Speaker, `SITU_LotsOfDosh, NumOptions, BestOptionID );
|
||
|
}
|
||
|
else if( Speaker.PlayerReplicationInfo.Score <= IdleLowDoshThreshold )
|
||
|
{
|
||
|
AddRandomDialogOption( Speaker, `SITU_LittleDosh, NumOptions, BestOptionID );
|
||
|
}
|
||
|
|
||
|
PlayDialogEvent( Speaker, BestOptionID );
|
||
|
PlayCustomDialogResponse( Speaker, Target, BestOptionID, PlayRandomSituationalResponse, true );
|
||
|
}
|
||
|
// otherwise talk to self
|
||
|
else if( NumPlayers > 1 && NumLivingPlayers == 1 )
|
||
|
{
|
||
|
AddRandomDialogOption( Speaker, `SITU_TalkSelf, NumOptions, BestOptionID );
|
||
|
}
|
||
|
|
||
|
PlayDialogEvent( Speaker, BestOptionID );
|
||
|
}
|
||
|
|
||
|
/** Plays response to random situational dialog */
|
||
|
function PlayRandomSituationalResponse( const out DialogResponseInfo RespInfo )
|
||
|
{
|
||
|
local int ResponseID, ResponderDoshAmt;
|
||
|
local float ResponderHealthPct, ResponderAmmoPCt;
|
||
|
|
||
|
ResponseID = -1;
|
||
|
|
||
|
switch( RespInfo.RespondingToID )
|
||
|
{
|
||
|
case `SITU_HealthCheck:
|
||
|
ResponderHealthPct = RespInfo.Speaker.GetHealthPercentage();
|
||
|
if( ResponderHealthPct >= NeedNoMoreHealingPctThreshold )
|
||
|
{
|
||
|
ResponseID = `SITU_HealthCheckRespHigh;
|
||
|
}
|
||
|
else if( ResponderHealthPct < NeedMoreHealingPctThreshold )
|
||
|
{
|
||
|
ResponseID = `SITU_HealthCheckRespLow;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case `SITU_AmmoCheck:
|
||
|
ResponderAmmoPCt = RespInfo.Speaker.MyKFWeapon.GetAmmoPercentage();
|
||
|
if( ResponderAmmoPct >= 0.f )
|
||
|
{
|
||
|
if( ResponderAmmoPct > IdleHighAmmoPctThreshold)
|
||
|
{
|
||
|
ResponseID = `SITU_AmmoCheckRespHigh;
|
||
|
}
|
||
|
else if( ResponderAmmoPCt < IdleLowAmmoPctThreshold)
|
||
|
{
|
||
|
ResponseID = `SITU_AmmoCheckRespLow;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case `SITU_AllAlive:
|
||
|
ResponseID = `SITU_AllAliveResp;
|
||
|
break;
|
||
|
|
||
|
case `SITU_HalfAlive:
|
||
|
ResponseID = `SITU_HalfAliveResp;
|
||
|
break;
|
||
|
|
||
|
case `SITU_MostAlive:
|
||
|
ResponseID = `SITU_MostAliveResp;
|
||
|
break;
|
||
|
|
||
|
case `SITU_LotsOfDosh:
|
||
|
ResponderDoshAmt = RespInfo.Speaker.PlayerReplicationInfo.Score;
|
||
|
if( ResponderDoshAmt > IdleHighDoshThreshold)
|
||
|
{
|
||
|
ResponseID = `SITU_LotsOfDoshRespLots;
|
||
|
}
|
||
|
else if( ResponderDoshAmt < IdleLowDoshThreshold )
|
||
|
{
|
||
|
ResponseID = `SITU_LotsOfDoshRespLittle;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case `SITU_LittleDosh:
|
||
|
ResponderDoshAmt = RespInfo.Speaker.PlayerReplicationInfo.Score;
|
||
|
if( ResponderDoshAmt < IdleLowDoshThreshold )
|
||
|
{
|
||
|
ResponseID = `SITU_LittleDoshResp;
|
||
|
}
|
||
|
break;
|
||
|
};
|
||
|
|
||
|
if( ResponseID >= 0 )
|
||
|
{
|
||
|
PlayDialogEvent( RespInfo.Speaker, ResponseID );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Plays the "wave start" dialog from a random pawn */
|
||
|
function PlayWaveStartDialog( bool bBossWave )
|
||
|
{
|
||
|
local int RandomPlayer;
|
||
|
local KFPawn_Human KFPH;
|
||
|
|
||
|
RandomPlayer = rand( KFGameInfo(WorldInfo.Game).GetLivingPlayerCount() );
|
||
|
|
||
|
foreach WorldInfo.AllPawns( class'KFPawn_Human', KFPH )
|
||
|
{
|
||
|
if( PawnIsValidPlayer(KFPH) && (RandomPlayer-- == 0) )
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( bBossWave )
|
||
|
{
|
||
|
PlayDialogEvent( KFPH, `SITU_WaveStartBoss );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
PlayDialogEvent( KFPH, `SITU_WaveStartGeneral );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function PlayEnvironmentalDialog( KFPawn_Human Speaker, int EventID )
|
||
|
{
|
||
|
PlayDialogEvent( Speaker, EventID );
|
||
|
}
|
||
|
|
||
|
/************************************************
|
||
|
* Objective Dialog
|
||
|
************************************************/
|
||
|
|
||
|
function PlayAcceptObjectiveDialog( array<PlayerReplicationInfo> PRIsInObj )
|
||
|
{
|
||
|
local array< KFPawn_Human > PawnsInObj;
|
||
|
|
||
|
GetHumanPawnsInObj( PawnsInObj, PRIsInObj );
|
||
|
PlayObjectiveDialogOnRandomPlayers( PawnsInObj, `OBJ_Accept );
|
||
|
}
|
||
|
|
||
|
function PlayDeclineObjectiveDialog( array<PlayerReplicationInfo> PRIsInObj )
|
||
|
{
|
||
|
local array< KFPawn_Human > PawnsInObj;
|
||
|
|
||
|
GetHumanPawnsInObj( PawnsInObj, PRIsInObj );
|
||
|
PlayObjectiveDialogOnRandomPlayers( PawnsInObj, `OBJ_Decline );
|
||
|
}
|
||
|
|
||
|
function PlayWinObjectiveDialog( array<PlayerReplicationInfo> PRIsInObj )
|
||
|
{
|
||
|
local array< KFPawn_Human > PawnsInObj;
|
||
|
|
||
|
GetHumanPawnsInObj( PawnsInObj, PRIsInObj );
|
||
|
PlayObjectiveDialogOnRandomPlayers( PawnsInObj, `OBJ_Win, `OBJ_WinResp );
|
||
|
}
|
||
|
|
||
|
function PlayLoseObjectiveDialog( array<PlayerReplicationInfo> PRIsInObj )
|
||
|
{
|
||
|
local array< KFPawn_Human > PawnsInObj;
|
||
|
|
||
|
GetHumanPawnsInObj( PawnsInObj, PRIsInObj );
|
||
|
PlayObjectiveDialogOnRandomPlayers( PawnsInObj, `OBJ_Lose, `OBJ_LoseResp );
|
||
|
}
|
||
|
|
||
|
/** Fills an "out" array with pawns that are in an objective given the PRIs in the objective */
|
||
|
function GetHumanPawnsInObj( out array<KFPawn_Human> out_PawnsInObj, const out array<PlayerReplicationInfo> PRIsInObj )
|
||
|
{
|
||
|
local KFPawn_Human KFPH;
|
||
|
local int PRIIdx;
|
||
|
|
||
|
foreach WorldInfo.AllPawns( class'KFPawn_Human', KFPH )
|
||
|
{
|
||
|
if( PawnIsValidPlayer(KFPH) )
|
||
|
{
|
||
|
PRIIdx = PRIsInObj.Find( KFPH.PlayerReplicationInfo );
|
||
|
if( PRIIdx != INDEX_None )
|
||
|
{
|
||
|
out_PawnsInObj.AddItem( KFPH );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Plays dialog for random player within an objective */
|
||
|
function PlayObjectiveDialogOnRandomPlayers( array<KFPawn_Human> PawnsInObj, int EventID, optional int ResponseID = -1 )
|
||
|
{
|
||
|
local int RandIdx;
|
||
|
local KFPawn_Human Speaker, Responder;
|
||
|
|
||
|
RandIdx = Rand( PawnsInObj.Length );
|
||
|
Speaker = PawnsInObj[RandIdx];
|
||
|
PawnsInObj.Remove( RandIdx, 1 );
|
||
|
if( Speaker != none )
|
||
|
{
|
||
|
PlayDialogEvent( Speaker, EventID );
|
||
|
}
|
||
|
|
||
|
if( ResponseID < 0 )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
RandIdx = Rand( PawnsInObj.Length );
|
||
|
Responder = PawnsInObj[RandIdx];
|
||
|
if( Responder != none )
|
||
|
{
|
||
|
PlayBasicDialogResponse( Speaker, EventID, ResponseID, Responder,, true );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function PlayDefendObjDialog( byte PrevObjProgress, byte CurrObjProgress )
|
||
|
{
|
||
|
local KFPawn_Human KFPH;
|
||
|
|
||
|
// check obj progress
|
||
|
foreach WorldInfo.AllPawns( class'KFPawn_Human', KFPH )
|
||
|
{
|
||
|
if( !PawnIsValidPlayer(KFPH) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if( CurrObjProgress == 0 )
|
||
|
{
|
||
|
PlayDialogEvent( KFPH, `OBJ_DefendAStart );
|
||
|
}
|
||
|
else if( PrevObjProgress < 100 && CurrObjProgress >= 100 )
|
||
|
{
|
||
|
PlayDialogEvent( KFPH, `OBJ_Win );
|
||
|
}
|
||
|
else if( PrevObjProgress < 50 && CurrObjProgress >= 50 )
|
||
|
{
|
||
|
PlayDialogEvent( KFPH, `OBJ_DefendAAlmostSecured );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/************************************************
|
||
|
* Favorite Weapons Dialog
|
||
|
************************************************/
|
||
|
|
||
|
function PlaySwitchToFavoriteWeaponDialog( KFPawn_Human Speaker )
|
||
|
{
|
||
|
local int FavoriteWeaponIndex;
|
||
|
local KFCharacterInfo_Human KFCI;
|
||
|
|
||
|
if( Speaker == none || Speaker.Weapon == none )
|
||
|
{
|
||
|
// wut?
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
KFCI = KFCharacterInfo_Human( Speaker.CharacterArch );
|
||
|
if( KFCI == none )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
FavoriteWeaponIndex = KFCI.GetFavoriteWeaponIndexOf( Speaker.Weapon );
|
||
|
if( FavoriteWeaponIndex < 0 )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
PlayDialogEvent( Speaker, `ACT_SelectFaveWeap1 + FavoriteWeaponIndex );
|
||
|
}
|
||
|
|
||
|
/************************************************
|
||
|
* Voice Command Dialog
|
||
|
************************************************/
|
||
|
|
||
|
function PlayVoiceCommandDialog( KFPawn Speaker, int CommandIndex )
|
||
|
{
|
||
|
switch( CommandIndex )
|
||
|
{
|
||
|
case 0:
|
||
|
PlayDialogEvent( Speaker, `COMM_RequestHeal);
|
||
|
break;
|
||
|
|
||
|
case 1:
|
||
|
PlayDialogEvent( Speaker, `COMM_RequestDosh);
|
||
|
break;
|
||
|
case 2:
|
||
|
PlayDialogEvent( Speaker, `COMM_RequestHelp);
|
||
|
break;
|
||
|
case 3:
|
||
|
PlayDialogEvent( Speaker, `COMM_InsultZeds);
|
||
|
break;
|
||
|
case 4:
|
||
|
PlayDialogEvent( Speaker, `COMM_FollowMe);
|
||
|
break;
|
||
|
case 5:
|
||
|
PlayDialogEvent( Speaker, `COMM_GetToTrader);
|
||
|
break;
|
||
|
case 6:
|
||
|
PlayDialogEvent( Speaker, `COMM_ConfirmGeneric);
|
||
|
break;
|
||
|
case 7:
|
||
|
PlayDialogEvent( Speaker, `COMM_DenyGeneric);
|
||
|
break;
|
||
|
case 9:
|
||
|
PlayDialogEvent(Speaker, `COMM_ThankYou);
|
||
|
break;
|
||
|
};
|
||
|
}
|
||
|
|
||
|
/************************************************
|
||
|
* Common Boss Dialog
|
||
|
************************************************/
|
||
|
|
||
|
function PlayBossMonologue( KFPawn Boss, byte MonologueType )
|
||
|
{
|
||
|
if( ETheatricType(MonologueType) == THEATRIC_Entrance )
|
||
|
{
|
||
|
PlayDialogEvent( Boss, `BOSS_Intro );
|
||
|
}
|
||
|
else if( ETheatricType(MonologueType) == THEATRIC_Victory )
|
||
|
{
|
||
|
PlayDialogEvent( Boss, `BOSS_Victory );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function PlayBossLoseSightOfDialog( KFPawn_Monster BossSpeaker, KFPawn_Human Target )
|
||
|
{
|
||
|
local class< KFPawnVoiceGroupEventData > EventDataClass;
|
||
|
|
||
|
EventDataClass = BossSpeaker.GetVoiceGroupEventDataClass();
|
||
|
|
||
|
if( ActorWithinPawnFOV(Target, BossSpeaker, GetEventFOV(`BOSS_LoseTarget, EventDataClass)) )
|
||
|
{
|
||
|
PlayDialogEvent( BossSpeaker, `BOSS_LoseTarget );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function PlayBossChallengeDialog( KFPawn Boss )
|
||
|
{
|
||
|
PlayDialogEvent( Boss, `BOSS_Challenge );
|
||
|
}
|
||
|
|
||
|
function PlayBossGrabDialog( KFPawn Boss )
|
||
|
{
|
||
|
PlayDialogEvent( Boss, `BOSS_Grab );
|
||
|
}
|
||
|
|
||
|
function PlayBossHealDialog( KFPawn Boss )
|
||
|
{
|
||
|
PlayDialogEvent( Boss, `BOSS_Heal );
|
||
|
}
|
||
|
|
||
|
function PlayBossTakeDamageDialog( KFPawn Boss )
|
||
|
{
|
||
|
if( float(Boss.Health) / float(Boss.HealthMax) < 0.25f )
|
||
|
{
|
||
|
PlayDialogEvent( Boss, `BOSS_TakeDmg_LT25 );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
PlayDialogEvent( Boss, `BOSS_TakeDmgBase );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function PlayBossDeathDialog( KFPawn Boss )
|
||
|
{
|
||
|
PlayDialogEvent( Boss, `BOSS_Death );
|
||
|
}
|
||
|
|
||
|
function PlayBossLeapedDialog( KFPawn Boss )
|
||
|
{
|
||
|
PlayDialogEvent( Boss, `BOSS_Jump );
|
||
|
}
|
||
|
|
||
|
function PlayBossLandedDialog( KFPawn Boss )
|
||
|
{
|
||
|
PlayDialogEvent( Boss, `BOSS_Land );
|
||
|
}
|
||
|
|
||
|
/************************************************
|
||
|
* Hans Dialog
|
||
|
************************************************/
|
||
|
|
||
|
function PlayHansTickDialog( KFPawn Hans )
|
||
|
{
|
||
|
local int NumPlayers, NumLivingPlayers, NumOptions, BestOptionID;
|
||
|
local float PlayersAlivePct, DistToTargetSq, MaxDistToTargetSq;
|
||
|
local KFPawn_ZedHansBase HansBase;
|
||
|
|
||
|
BestOptionID = -1;
|
||
|
|
||
|
if( Hans.bIsSprinting && Hans.MyKFAIC.Enemy != none )
|
||
|
{
|
||
|
DistToTargetSq = VSizeSq(Hans.Location - Hans.MyKFAIC.Enemy.Location);
|
||
|
MaxDistToTargetSq = GetEventRadiusSq( `HANS_SprintToward, Hans.GetVoiceGroupEventDataClass() );
|
||
|
if( DistToTargetSq <= MaxDistToTargetSq )
|
||
|
{
|
||
|
AddRandomDialogOption( Hans, `HANS_SprintToward, NumOptions, BestOptionID );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
NumPlayers = WorldInfo.Game.NumPlayers;
|
||
|
NumLivingPlayers = KFGameInfo( WorldInfo.Game ).GetLivingPlayerCount();
|
||
|
if( NumLivingPlayers > 0 )
|
||
|
{
|
||
|
HansBase = KFPawn_ZedHansBase( Hans );
|
||
|
if( HansBase != none && HansBase.bInHuntAndHealMode )
|
||
|
{
|
||
|
AddRandomDialogOption( Hans, `HANS_HuntTaunt, NumOptions, BestOptionID );
|
||
|
}
|
||
|
|
||
|
PlayersAlivePct = float(NumLivingPlayers) / float(NumPlayers);
|
||
|
|
||
|
if( PlayersAlivePct >= 0.5 )
|
||
|
{
|
||
|
AddRandomDialogOption( Hans, `BOSS_TauntBase, NumOptions, BestOptionID );
|
||
|
}
|
||
|
else if( PlayersAlivePct >= 0.25 )
|
||
|
{
|
||
|
AddRandomDialogOption( Hans, `BOSS_Taunt_LT50, NumOptions, BestOptionID );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
AddRandomDialogOption( Hans, `BOSS_Taunt_LT25, NumOptions, BestOptionID );
|
||
|
|
||
|
// @todo: hook up taunt for unfrozen German soldier
|
||
|
}
|
||
|
}
|
||
|
|
||
|
PlayDialogEvent( Hans, BestOptionID );
|
||
|
}
|
||
|
|
||
|
function PlayHansDrawGunsDialog( KFPawn Hans )
|
||
|
{
|
||
|
PlayDialogEvent( Hans, `HANS_DrawGuns );
|
||
|
}
|
||
|
|
||
|
function PlayHansNadeDialog( KFPawn Hans, bool bBarrage )
|
||
|
{
|
||
|
if( bBarrage )
|
||
|
{
|
||
|
PlayDialogEvent( Hans, `HANS_NadeBarrage );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
PlayDialogEvent( Hans, `HANS_NadeToss );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function PlayHansSmokeDialog( KFPawn Hans, bool bBarrage )
|
||
|
{
|
||
|
if( bBarrage )
|
||
|
{
|
||
|
PlayDialogEvent( Hans, `HANS_SmokeBarrage );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
PlayDialogEvent( Hans, `HANS_SmokeToss );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function PlayHansGasDialog( KFPawn Hans, bool bBarrage )
|
||
|
{
|
||
|
if( bBarrage )
|
||
|
{
|
||
|
PlayDialogEvent( Hans, `HANS_GasBarrage );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
PlayDialogEvent( Hans, `HANS_GasToss );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function PlayHansKilledDialog( KFPawn Hans, class<DamageType> DmgType )
|
||
|
{
|
||
|
local int NumOptions, BestOptionID;
|
||
|
|
||
|
BestOptionID = -1;
|
||
|
|
||
|
AddRandomDialogOption( Hans, `BOSS_KillBase, NumOptions, BestOptionID );
|
||
|
|
||
|
if( class<KFDamageType>(DmgType) != none )
|
||
|
{
|
||
|
AddRandomDialogOption( Hans, class<KFDamageType>(DmgType).static.GetKillerDialogID(), NumOptions, BestOptionID );
|
||
|
}
|
||
|
|
||
|
// @todo: figure out a way to make this less hard-coded? (see also KFAIController::DoStrike)
|
||
|
if( Hans.GetSpecialMoveTag() == 'Frenzy_Lunge' )
|
||
|
{
|
||
|
AddRandomDialogOption( Hans, `HANS_KillFrenzy, NumOptions, BestOptionID );
|
||
|
}
|
||
|
|
||
|
PlayDialogEvent( Hans, BestOptionID );
|
||
|
}
|
||
|
|
||
|
function PlayHansDamagePlayerDialog( KFPawn Hans, class<DamageType> DmgType )
|
||
|
{
|
||
|
if( DmgType.Name == 'KFDT_Ballistic_HansAK12' ) // Hans' MkB42s use this damage type at the moment. @todo: fix this if/when necessary
|
||
|
{
|
||
|
PlayDialogEvent( Hans, `HANS_DmgGuns );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function PlayHansFrenzyDialog( KFPawn Hans )
|
||
|
{
|
||
|
PlayDialogEvent( Hans, `HANS_Frenzy );
|
||
|
}
|
||
|
|
||
|
function PlayHansAOEDialog( KFPawn Hans )
|
||
|
{
|
||
|
PlayDialogEvent( Hans, `HANS_AOE );
|
||
|
}
|
||
|
|
||
|
function PlayHansBattlePhaseDialog( KFPawn Hans, int CurrBattlePhase )
|
||
|
{
|
||
|
if( !Hans.IsAliveAndWell() )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
switch( CurrBattlePhase )
|
||
|
{
|
||
|
case 2:
|
||
|
PlayDialogEvent( Hans, `HANS_NextBattlePhase1 );
|
||
|
break;
|
||
|
|
||
|
case 3:
|
||
|
PlayDialogEvent( Hans, `HANS_NextBattlePhase2 );
|
||
|
break;
|
||
|
|
||
|
case 4:
|
||
|
PlayDialogEvent( Hans, `HANS_NextBattlePhase3 );
|
||
|
break;
|
||
|
};
|
||
|
}
|
||
|
|
||
|
/************************************************
|
||
|
* Patriarch Dialog
|
||
|
************************************************/
|
||
|
|
||
|
function PlayPatriarchTickDialog( KFPawn Patty )
|
||
|
{
|
||
|
local int NumPlayers, NumLivingPlayers, NumOptions, BestOptionID;
|
||
|
local float PlayersAlivePct;
|
||
|
|
||
|
BestOptionID = -1;
|
||
|
|
||
|
NumPlayers = WorldInfo.Game.NumPlayers;
|
||
|
NumLivingPlayers = KFGameInfo( WorldInfo.Game ).GetLivingPlayerCount();
|
||
|
if( NumLivingPlayers > 0 )
|
||
|
{
|
||
|
PlayersAlivePct = float(NumLivingPlayers) / float(NumPlayers);
|
||
|
|
||
|
if( PlayersAlivePct >= 0.5 )
|
||
|
{
|
||
|
AddRandomDialogOption( Patty, `BOSS_TauntBase, NumOptions, BestOptionID );
|
||
|
}
|
||
|
else if( PlayersAlivePct >= 0.25 )
|
||
|
{
|
||
|
AddRandomDialogOption( Patty, `BOSS_Taunt_LT50, NumOptions, BestOptionID );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
AddRandomDialogOption( Patty, `BOSS_Taunt_LT25, NumOptions, BestOptionID );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
PlayDialogEvent( Patty, BestOptionID );
|
||
|
}
|
||
|
|
||
|
function PlayPattyMinigunWarnDialog( KFPawn Patty )
|
||
|
{
|
||
|
PlayDialogEvent( Patty, `PATTY_MinigunWarn );
|
||
|
}
|
||
|
|
||
|
function PlayPattyMinigunAttackDialog( KFPawn Patty )
|
||
|
{
|
||
|
PlayDialogEvent( Patty, `PATTY_MinigunAttack );
|
||
|
}
|
||
|
|
||
|
function PlayPattyTentaclePullDialog( KFPawn Patty )
|
||
|
{
|
||
|
PlayDialogEvent( Patty, `PATTY_TentaclePull );
|
||
|
}
|
||
|
|
||
|
function PlayPattyChildKilledDialog( KFPawn Patty )
|
||
|
{
|
||
|
PlayDialogEvent( Patty, `PATTY_ChildKilled );
|
||
|
}
|
||
|
|
||
|
function PlayPattyKilledDialog( KFPawn Patty, class<DamageType> DmgType )
|
||
|
{
|
||
|
local int NumOptions, BestOptionID;
|
||
|
|
||
|
BestOptionID = -1;
|
||
|
|
||
|
AddRandomDialogOption( Patty, `BOSS_KillBase, NumOptions, BestOptionID );
|
||
|
|
||
|
if( class<KFDamageType>(DmgType) != none )
|
||
|
{
|
||
|
AddRandomDialogOption( Patty, class<KFDamageType>(DmgType).static.GetKillerDialogID(), NumOptions, BestOptionID );
|
||
|
}
|
||
|
|
||
|
PlayDialogEvent( Patty, BestOptionID );
|
||
|
}
|
||
|
|
||
|
function PlayPattyBattlePhaseDialog( KFPawn Patty, int CurrBattlePhase )
|
||
|
{
|
||
|
if( !Patty.IsAliveAndWell() )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
switch( CurrBattlePhase )
|
||
|
{
|
||
|
case 1:
|
||
|
PlayDialogEvent( Patty, `PATTY_NextBattlePhase1 );
|
||
|
break;
|
||
|
|
||
|
case 2:
|
||
|
PlayDialogEvent( Patty, `PATTY_NextBattlePhase2 );
|
||
|
break;
|
||
|
|
||
|
case 3:
|
||
|
PlayDialogEvent( Patty, `PATTY_NextBattlePhase3 );
|
||
|
break;
|
||
|
};
|
||
|
}
|
||
|
|
||
|
function PlayPattyWhirlwindDialog( KFPawn Patty )
|
||
|
{
|
||
|
PlayDialogEvent( Patty, `PATTY_WhirlwindAttack );
|
||
|
}
|
||
|
|
||
|
/************************************************
|
||
|
* Matriarch Dialog
|
||
|
************************************************/
|
||
|
|
||
|
function PlayMatriarchBattlePhaseDialog(KFPawn Matriarch, int CurrentBattlePhase)
|
||
|
{
|
||
|
if (!Matriarch.IsAliveAndWell())
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
switch (CurrentBattlePhase)
|
||
|
{
|
||
|
case 1:
|
||
|
PlayDialogEvent(Matriarch, `MATTY_NextBattlePhase1);
|
||
|
break;
|
||
|
|
||
|
case 2:
|
||
|
PlayDialogEvent(Matriarch, `MATTY_NextBattlePhase2);
|
||
|
break;
|
||
|
|
||
|
case 3:
|
||
|
PlayDialogEvent(Matriarch, `MATTY_NextBattlePhase3);
|
||
|
break;
|
||
|
};
|
||
|
}
|
||
|
|
||
|
function PlayMatriarchTickDialog(KFPawn Matriarch, int ArmorZoneStatus, int CurrentBattlePhase)
|
||
|
{
|
||
|
switch (CurrentBattlePhase)
|
||
|
{
|
||
|
case 0:
|
||
|
PlayDialogEvent(Matriarch, `MATTY_Taunt_Phase1);
|
||
|
break;
|
||
|
|
||
|
case 1:
|
||
|
PlayDialogEvent(Matriarch, `MATTY_Taunt_Phase2);
|
||
|
break;
|
||
|
|
||
|
case 2:
|
||
|
PlayDialogEvent(Matriarch, `MATTY_Taunt_Phase3);
|
||
|
break;
|
||
|
|
||
|
case 3:
|
||
|
PlayDialogEvent(Matriarch, `MATTY_Taunt_Phase4);
|
||
|
break;
|
||
|
};
|
||
|
}
|
||
|
|
||
|
function PlayMatriarchExplodeArmorDialog(KFPawn Matriarch, name ArmorZoneName)
|
||
|
{
|
||
|
switch (ArmorZoneName)
|
||
|
{
|
||
|
case 'head':
|
||
|
PlayDialogEvent(Matriarch, `MATTY_TauntPilotCompartDestroyed);
|
||
|
break;
|
||
|
|
||
|
case 'claw':
|
||
|
PlayDialogEvent(Matriarch, `MATTY_TauntPowerClawDestroyed);
|
||
|
break;
|
||
|
};
|
||
|
}
|
||
|
|
||
|
function PlayMattyKilledDialog(KFPawn Matriarch)
|
||
|
{
|
||
|
if (Matriarch.IsDoingSpecialMove(SM_Custom1))
|
||
|
{
|
||
|
PlayDialogEvent(Matriarch, `MATTY_SweepingClawKill);
|
||
|
}
|
||
|
else if (Matriarch.IsDoingSpecialMove(SM_StandAndShootAttack))
|
||
|
{
|
||
|
PlayDialogEvent(Matriarch, `MATTY_TeslaBlastKill);
|
||
|
}
|
||
|
else if (Matriarch.IsDoingSpecialMove(SM_HoseWeaponAttack))
|
||
|
{
|
||
|
PlayDialogEvent(Matriarch, `MATTY_PlasmaCannonKill);
|
||
|
}
|
||
|
else if (Matriarch.IsDoingSpecialMove(SM_Custom2))
|
||
|
{
|
||
|
PlayDialogEvent(Matriarch, `MATTY_LightningStormKill);
|
||
|
}
|
||
|
else if (Matriarch.IsDoingSpecialMove(SM_GrappleAttack))
|
||
|
{
|
||
|
PlayDialogEvent(Matriarch, `MATTY_ScorpionWhipKill);
|
||
|
}
|
||
|
else if (Matriarch.IsDoingSpecialMove(SM_SonicAttack))
|
||
|
{
|
||
|
PlayDialogEvent(Matriarch, `MATTY_WarningSirenKill);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function PlayMattyMinionKilledDialog(KFPawn Matriarch)
|
||
|
{
|
||
|
PlayDialogEvent(Matriarch, `MATTY_DeadEDAR);
|
||
|
}
|
||
|
|
||
|
function PlayMatriarchSweepingClawEvent(KFPawn Matriarch)
|
||
|
{
|
||
|
PlayDialogEvent(Matriarch, `MATTY_SweepingClawAttack);
|
||
|
}
|
||
|
|
||
|
function PlayMatriarchTeslaBlastEvent(KFPawn Matriarch)
|
||
|
{
|
||
|
PlayDialogEvent(Matriarch, `MATTY_TeslaBlastWarning);
|
||
|
}
|
||
|
|
||
|
function PlayMatriarchPlasmaCannonEvent(KFPawn Matriarch)
|
||
|
{
|
||
|
PlayDialogEvent(Matriarch, `MATTY_PlasmaCannonWarning);
|
||
|
}
|
||
|
|
||
|
function PlayMatriarchLightningStormEvent(KFPawn Matriarch)
|
||
|
{
|
||
|
PlayDialogEvent(Matriarch, `MATTY_LightningStormWarning);
|
||
|
}
|
||
|
|
||
|
function PlayMatriarchScorpionWhipEvent(KFPawn Matriarch)
|
||
|
{
|
||
|
PlayDialogEvent(Matriarch, `MATTY_ScorpionWhipWarning);
|
||
|
}
|
||
|
|
||
|
function PlayMatriarchWarningSirenEvent(KFPawn Matriarch)
|
||
|
{
|
||
|
PlayDialogEvent(Matriarch, `MATTY_WarningSirenWarning);
|
||
|
}
|
||
|
|
||
|
function PlayMatriarchShieldUpEvent(KFPawn Matriarch)
|
||
|
{
|
||
|
PlayDialogEvent(Matriarch, `MATTY_ShieldActivate);
|
||
|
}
|
||
|
|
||
|
function PlayMatriarchCloakedEvent(KFPawn Matriarch)
|
||
|
{
|
||
|
PlayDialogEvent(Matriarch, `MATTY_Cloak);
|
||
|
}
|
||
|
|
||
|
DefaultProperties
|
||
|
{
|
||
|
bEnabled=true
|
||
|
|
||
|
InterruptPriorityThreshold=2
|
||
|
InterruptedByAnyPriorityThreshold=7
|
||
|
|
||
|
WeldAboutToBreakThreshold=60
|
||
|
|
||
|
NeedMoreHealingPctThreshold=0.75
|
||
|
NeedNoMoreHealingPctThreshold=0.75
|
||
|
|
||
|
CaughtSomeDoshAmt=50
|
||
|
CaughtMuchDoshAmt=250
|
||
|
|
||
|
NumZedsForPressureReload=1
|
||
|
|
||
|
LowSpareAmmoPctThreshold=0.15
|
||
|
|
||
|
NumKillsForOnARoll=6
|
||
|
CloseCallKillHealthPctThreshold=0.3
|
||
|
|
||
|
NumHitsForDeadHorse=3
|
||
|
TimeBetweenHitsForDeadHorse=1.0
|
||
|
|
||
|
TimeForContinuousDamageThreshold=3.0
|
||
|
TimeBetweenHitsForContinuousDamage=0.75
|
||
|
|
||
|
PlayerHealthPctForNearDeath=0.2
|
||
|
PlayerTakeDamageStreakInterval=1.0
|
||
|
PlayerTakeDamageStreakPctForScream=0.25
|
||
|
|
||
|
IdleTimeforSpottingZed=5.f
|
||
|
SpotLargeHordeNumZeds=6
|
||
|
|
||
|
ZedAlmostDeadHealthPctThreshold=0.3
|
||
|
|
||
|
SprintTowardZedDuration=1.5
|
||
|
|
||
|
NumZedsInAreaForMassacre=9
|
||
|
AreaRadiusForMassacre=2500.0
|
||
|
TimeLimitForMassacre=30.0
|
||
|
|
||
|
IdleTimeForSituationalDialog=7.0
|
||
|
IdleLowDoshThreshold=350
|
||
|
IdleHighDoshThreshold=3500
|
||
|
IdleLowAmmoPctThreshold=0.2
|
||
|
IdleHighAmmoPctThreshold=0.3
|
||
|
|
||
|
TimeUntilStartSprintPanting=3.0
|
||
|
|
||
|
// global ak events
|
||
|
StopBreathingAkEvent=AkEvent'WW_GLO_Runtime.Stop_Breathing'
|
||
|
}
|