/* -------------------------------------------------------------- KF_PlayerDialogue -------------------------------------------------------------- Displayes player's portrait and nickname as title to act as if it is a player to speaks it. Author : PooSH -------------------------------------------------------------- */ class KF_PlayerDialogue extends KF_DialogueSpot_SE placeable; // Trigers when dialogue was triggered but didn't matched player conditions, so it was skipped // You can set SkippedEvent=Tag, to force dialogue displaying, if requirements were not matched. // In that case Intigator will be used as speaker, or random player, if instigated by non-player. var(Events) name SkippedEvent; var() byte MinPlayerCount; var() byte MaxPlayerCount; var() bool bOnlyAlivePlayers; /* SpeakerName Unique name which dialogue assosiates with a player matching the following criterias. If player is already assigned to a local name, then he will be used. Otherwice player lookup will be made and the result assigned to a local name. ExcludeNames list of local names that can not be used for the current dialogue bUseInstigator Use dialogue's instigator (DialogueInstigator) for the current dialogue. bSetInstigator Change dialogue's instigator to the chosen speaker bForceDisplayEvents If true, DisplayingEvent and DisplayedEvent will be triggered even if dialogue is skipped. Filters Array of DialoguePlayerFilter instances, each of them returns a number between 0 and defined Value depending of specific player stats. Player with highest total value will be chosen for speaking. MinFilterValue Players with filter value < MinFilterValue are skipped and can't be used for displaying the dialogue. MinFilterValue=0 (default) turns off min value filtering! Use MinFilterValue=0.000001 to set it next to 0. If no players found with a given criteria, dialogue will not be displayed, so use it with caution! WARNING! If both bUseInstigator=True and SpeakerName was set, then use priority depends from bSetInstigator: If bSetInstigator=True, then SpeakerName has higher priority, i.e. speaker will be chosen by name and set as instigator If bSetInstigator=False, then bUseInstigator has higher priority, i.e. instigator will be used as a speaker, overriding the value stored by SpeakerName */ struct SDialogueFilter { var() name SpeakerName; var() name ExcludeNames[5]; var() bool bUseInstigator; var() bool bSetInstigator; var() bool bForceDisplayEvents; var() export editinline Array Filters; var() float MinFilterValue; }; var() array DialogueFilters; var protected NamedPRITable NamedPRIs; var PlayerDialogueReplicationInfo PlayerDialogueReplicationInfo; function PostBeginPlay() { local int i; super.PostBeginPlay(); NamedPRIs = NamedPRITable(class'NamedPRITable'.static.InitTable(Level)); if ( Level.NetMode != NM_Standalone ) { PlayerDialogueReplicationInfo = spawn(class'PlayerDialogueReplicationInfo', self); if ( PlayerDialogueReplicationInfo != none ) { PlayerDialogueReplicationInfo.DialogueActorName = self.name; } } for ( i=0; i TriggeredEvents, out array ReceivedEvents) { super.GetEvents(TriggeredEvents, ReceivedEvents); if ( SkippedEvent != '' ) ReceivedEvents[ReceivedEvents.length] = SkippedEvent; } function DialogueSkipped() { if ( SkippedEvent != '' ) TriggerEvent(SkippedEvent, self, GetInstigator()); } function Pawn GetInstigator() { if ( DialogueInstigator == none ) return none; return DialogueInstigator.Pawn; } function RetrieveAllPRIs(out array PRIs) { local Controller C; PRIs.length = 0; for ( C = Level.ControllerList; C != None; C = C.NextController ) { if ( C.PlayerReplicationInfo != none && PlayerController(C) != none && !C.PlayerReplicationInfo.bOnlySpectator && (!bOnlyAlivePlayers || (C.Pawn != none && !C.Pawn.bDeleteMe && C.Pawn.Health > 0) ) ) PRIs[PRIs.length] = C.PlayerReplicationInfo; } } function PlayerReplicationInfo GetSpeakingPRI(int DlgIdx) { local int i, j; local array PRIs; local PlayerReplicationInfo result, NamedPRI; local float TopValue, FilteredValue; local array FilteredValues; if ( DlgIdx < DialogueFilters.length ) { // if PRI is assigned to SpeakerName already - use it if ( DialogueFilters[DlgIdx].SpeakerName != '' ); NamedPRI = NamedPRIs.GetPRI(DialogueFilters[DlgIdx].SpeakerName); if ( DialogueFilters[DlgIdx].bUseInstigator ) { if ( PlayerController(DialogueInstigator) != none && DialogueInstigator.PlayerReplicationInfo != none ) result = DialogueInstigator.PlayerReplicationInfo; if ( NamedPRI != none && NamedPRI != result ) { // if bSetInstigator=True, then SpeakerName has higher priority than instigator if ( result == none || DialogueFilters[DlgIdx].bSetInstigator ) { result = NamedPRI; if ( DialogueFilters[DlgIdx].bSetInstigator ) DialogueInstigator = PlayerController(NamedPRI.Owner); } } } else { if ( NamedPRI != none ) result = NamedPRI; else { RetrieveAllPRIs(PRIs); // removed excluded names for ( i=0; i<5; ++i) { if ( DialogueFilters[DlgIdx].ExcludeNames[i] != '' ) { NamedPRI = NamedPRIs.GetPRI(DialogueFilters[DlgIdx].ExcludeNames[i]); if ( NamedPRI != none ) { for ( j=0; j 0 ) { TopValue = DialogueFilters[DlgIdx].MinFilterValue; // calculate total filtered value of each PRI for ( j=0; j TopValue || (j == 0 && TopValue == 0.f) ) TopValue = FilteredValue; } // leave only PRIs with TopValue for ( j=0; j 0.01 ) { PRIs.remove(j, 1); FilteredValues.remove(j, 1); j--; } } } result = PRIs[rand(PRIs.length)]; } } if ( DialogueFilters[DlgIdx].SpeakerName != '' && result != NamedPRI ) { NamedPRI = result; NamedPRIs.SetPRI(DialogueFilters[DlgIdx].SpeakerName, NamedPRI); } } else { RetrieveAllPRIs(PRIs); result = PRIs[rand(PRIs.length)]; } return result; } function bool RequirementsMatched() { local int PlayerCount; if ( bOnlyAlivePlayers ) PlayerCount = KFStoryGameInfo(Level.Game).GetTotalActivePlayers(); else PlayerCount = Level.Game.NumPlayers; return PlayerCount >= MinPlayerCount && (PlayerCount <= MaxPlayerCount || MaxPlayerCount == 0); } // set protrait and title from speaking player, then call TraverseDialogue function TraverseDialogue() { local bool bOriginalRandomize; if( Dialogues.length <= CurrentMsgIdx || bTraversing || (bFinished && !bAllowRepeatDialogue) ) return; if ( !RequirementsMatched() ) { DialogueSkipped(); CurrentMsgIdx = Dialogues.length; return; } if ( bRandomize ) { // we need to know dialogue index already bOriginalRandomize = true; bRandomize = false; CurrentMsgIdx = rand(Dialogues.length); } SetPlayerDialogue(CurrentMsgIdx, GetSpeakingPRI(CurrentMsgIdx)); if ( Dialogues[CurrentMsgIdx].Display.Dialogue_Header == "" ) { if ( CurrentMsgIdx < DialogueFilters.length && DialogueFilters[CurrentMsgIdx].bForceDisplayEvents ) { if ( Dialogues[CurrentMsgIdx].Events.DisplayingEvent != '' ) TriggerEvent(Dialogues[CurrentMsgIdx].Events.DisplayingEvent,self,GetInstigator()); if ( Dialogues[CurrentMsgIdx].Events.DisplayedEvent != '' ) TriggerEvent(Dialogues[CurrentMsgIdx].Events.DisplayedEvent,self,GetInstigator()); } if ( !bOriginalRandomize ) { CurrentMsgIdx++; TraverseDialogue(); } } else { super.TraverseDialogue(); } bRandomize = bOriginalRandomize; } function SetPlayerDialogue(int DlgIdx, PlayerReplicationInfo SpeakerPRI) { if ( DlgIdx < 0 || DlgIdx >= Dialogues.length ) return; if ( SpeakerPRI != none ) { Dialogues[DlgIdx].Display.Portrait_Material = SpeakerPRI.GetPortrait(); Dialogues[DlgIdx].Display.Dialogue_Header = SpeakerPRI.PlayerName; //avoid skipping a dialogue in cases when player doesn't have a name (is it possible?) if ( Dialogues[DlgIdx].Display.Dialogue_Header == "" ) Dialogues[DlgIdx].Display.Dialogue_Header = "Fresh Meat"; if ( PlayerDialogueReplicationInfo != none ) { PlayerDialogueReplicationInfo.DlgIndex = DlgIdx; PlayerDialogueReplicationInfo.SpeakerPRI = SpeakerPRI; PlayerDialogueReplicationInfo.NetUpdateTime = Level.TimeSeconds - 1; } } else { Dialogues[DlgIdx].Display.Portrait_Material = none; Dialogues[DlgIdx].Display.Dialogue_Header = ""; } } defaultproperties { bOnlyAlivePlayers=True }