465 lines
12 KiB
Ucode
465 lines
12 KiB
Ucode
/*
|
|
--------------------------------------------------------------
|
|
KF_DialogueSpot
|
|
--------------------------------------------------------------
|
|
|
|
Displays dialogue on the screen with a header and image.
|
|
Optionally plays voicover audio.
|
|
|
|
Author : Alex Quick
|
|
|
|
--------------------------------------------------------------
|
|
*/
|
|
|
|
|
|
class KF_DialogueSpot extends Info
|
|
dependson(KFStoryGameInfo)
|
|
placeable;
|
|
|
|
var int CurrentMsgIdx;
|
|
|
|
var Controller DialogueInstigator;
|
|
|
|
var() array<KFStoryGameInfo.SDialogueEntry> Dialogues;
|
|
|
|
/* true if this dialogue spot should display its text when a player touches the dialogue actor's collision cylinder */
|
|
var bool bTouchTriggered;
|
|
|
|
|
|
/* true if this dialogue can be 'skipped' by moving out of range of the speaker*/
|
|
var bool bCanSkipDlg;
|
|
|
|
/* the range at which dialogue will be skipped if bCanSkipDlg*/
|
|
var float SkipDlgRange;
|
|
|
|
/* if true, the dialogue will play randomly each time it is triggered rather than in order */
|
|
var() bool bRandomize;
|
|
|
|
// if true, this dialogue can be played multiple times. If not, only allow one play of each dialogue
|
|
var() bool bAllowRepeatDialogue;
|
|
|
|
var bool bDebugDialogue;
|
|
|
|
var bool bTraversing;
|
|
|
|
var bool bFinished;
|
|
|
|
var bool bSingleTouchOnly;
|
|
|
|
var bool bTouched;
|
|
|
|
|
|
function Reset()
|
|
{
|
|
local int i, SavedIdx;
|
|
local array<byte> SavedTriggerStates;
|
|
|
|
Super.Reset();
|
|
|
|
/* Partial reset using saved positions*/
|
|
|
|
bTraversing = false;
|
|
|
|
if(!bFinished)
|
|
{
|
|
SavedIdx = KFStoryGameInfo(Level.Game).CurrentCheckPoint.GetSavedDialogueIndexFor(self,SavedTriggerStates) ;
|
|
CurrentMsgIdx = SavedIdx;
|
|
|
|
if(bDebugDialogue)
|
|
{
|
|
log("----- DIALOGUE DEBUG ------ Resetting "@self@". Dialogue will play back from Index : "@SavedIdx, 'Story_Debug');
|
|
}
|
|
|
|
for(i = 0 ; i < Dialogues.length ; i ++)
|
|
{
|
|
Dialogues[i].bWasTriggered = bool(SavedTriggerStates[i]);
|
|
}
|
|
}
|
|
else /* Full reset from the start. */
|
|
{
|
|
for(i = 0 ; i < Dialogues.length ; i ++)
|
|
{
|
|
Dialogues[i].bWasTriggered = false;
|
|
}
|
|
|
|
CurrentMsgIdx = 0;
|
|
bFinished = false;
|
|
}
|
|
}
|
|
|
|
function Destroyed()
|
|
{
|
|
local int i;
|
|
|
|
/* Perform cleanup on any event listeners */
|
|
for(i = 0 ; i < Dialogues.length ; i ++)
|
|
{
|
|
if(Dialogues[i].EventListener != none)
|
|
{
|
|
Dialogues[i].EventListener.Destroy();
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Dialogue callback - timing is handled in the event listener */
|
|
function OnDialogueDisplayComplete(int Index)
|
|
{
|
|
// log("Dialogue - Triggering Displayed Event : "@Dialogues[Index].Events.DisplayedEvent);
|
|
if(Dialogues[Index].Events.DisplayedEvent != '')
|
|
{
|
|
TriggerEvent(Dialogues[Index].Events.DisplayedEvent,self,DialogueInstigator.Pawn);
|
|
}
|
|
|
|
Dialogues[Index].bWasTriggered = false;
|
|
}
|
|
|
|
function PostBeginPlay()
|
|
{
|
|
local int i;
|
|
local Dialogue_EventListener NewListener;
|
|
|
|
/* No KFO Gametype, no Initialization */
|
|
if(KFStoryGameinfo(Level.Game) == none)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Spawn Event listeners for Dialogue entries which require them.
|
|
for(i = 0 ; i < Dialogues.length ; i ++)
|
|
{
|
|
NewListener = Spawn(class 'Dialogue_EventListener',self,Dialogues[i].Events.RequiredEvent);
|
|
NewListener.AssociatedIndex = i ;
|
|
Dialogues[i].EventListener = NewListener;
|
|
}
|
|
}
|
|
|
|
function Trigger( actor Other, pawn EventInstigator )
|
|
{
|
|
if(EventInstigator != none &&
|
|
EventInstigator.Controller != none)
|
|
{
|
|
DialogueInstigator = EventInstigator.Controller;
|
|
}
|
|
|
|
TraverseDialogue();
|
|
}
|
|
|
|
function bool SelfTriggered()
|
|
{
|
|
return bTouchTriggered;
|
|
}
|
|
|
|
function Touch( Actor Other )
|
|
{
|
|
if ( Pawn(Other) != None &&
|
|
PlayerController(Pawn(Other).Controller) != none &&
|
|
SelfTriggered() &&
|
|
(!bSingleTouchOnly || !bTouched))
|
|
{
|
|
bTouched = true;
|
|
Trigger(self,Pawn(Other));
|
|
}
|
|
}
|
|
|
|
function int GetIndexFor(KFstoryGameInfo.SDialogueEntry TestDlg)
|
|
{
|
|
local int i;
|
|
|
|
for(i = 0 ; i < Dialogues.length ; i ++)
|
|
{
|
|
if(Dialogues[i] == TestDlg)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return - 1 ;
|
|
}
|
|
|
|
|
|
/* Retrieves the index of the next dialogue entry in a DialogueSpot actor with a 'required event'.
|
|
Necessary in cases where the game is restarting from an objective in a non-linear fashion and
|
|
certain intermediate dialogue needs to be skipped
|
|
*/
|
|
|
|
function int GetNextDlgRequiredEventIdx(int TestIndex)
|
|
{
|
|
local int i;
|
|
|
|
for( i = TestIndex + 1 ; i < Dialogues.length ; i ++)
|
|
{
|
|
if(Dialogues[i].Events.RequiredEvent != '')
|
|
{
|
|
return i ;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
function TraverseDialogue()
|
|
{
|
|
local bool bWaitingForEvent ;
|
|
|
|
/* Hit the end. early out */
|
|
if( Dialogues.length <= CurrentMsgIdx || bTraversing || (bFinished && !bAllowRepeatDialogue))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(bRandomize)
|
|
{
|
|
CurrentMsgIdx = RandRange(0,Dialogues.length) ;
|
|
}
|
|
|
|
/* If the next index after this one is event-triggered, don't set the timer */
|
|
if(Dialogues[CurrentMsgIdx].Events.RequiredEvent != '' &&
|
|
!Dialogues[CurrentMsgIdx].bLooping)
|
|
{
|
|
bWaitingForEvent = !Dialogues[CurrentMsgIdx].bWasTriggered;
|
|
}
|
|
|
|
if(!bWaitingForEvent)
|
|
{
|
|
if(bDebugDialogue)
|
|
{
|
|
log("----- DIALOGUE DEBUG ------ Showing Dialogue at index .. "@CurrentMsgIdx, 'Story_Debug');
|
|
}
|
|
|
|
ShowDialogue(CurrentMsgIdx);
|
|
if(CurrentMsgIdx + 1 < Dialogues.length )
|
|
{
|
|
bTraversing = true;
|
|
|
|
if(GetCurrentDisplayDur() > 0)
|
|
{
|
|
SetTimer(GetCurrentDisplayDur(),false);
|
|
}
|
|
else
|
|
{
|
|
Timer();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* we're finished. Reset the actor */
|
|
OnDialogueCompleted();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(bDebugDialogue)
|
|
{
|
|
log("----- DIALOGUE DEBUG ------ Freezing dialogue at index :"@CurrentMsgIdx@"for event : "@Dialogues[CurrentMsgIdx].Events.RequiredEvent, 'Story_Debug');
|
|
}
|
|
}
|
|
}
|
|
|
|
function OnDialogueCompleted()
|
|
{
|
|
bFinished = true;
|
|
}
|
|
|
|
function Timer()
|
|
{
|
|
if(bTraversing)
|
|
{
|
|
bTraversing = false;
|
|
|
|
if(CurrentMsgIdx + 1 < Dialogues.length && !bRandomize)
|
|
{
|
|
if(!Dialogues[CurrentMsgIdx].bLooping)
|
|
{
|
|
CurrentMsgIdx ++ ;
|
|
if(bDebugDialogue)
|
|
{
|
|
log("----- DIALOGUE DEBUG ------ Incrementing Dialogue Index .. ", 'Story_Debug');
|
|
}
|
|
|
|
TraverseDialogue();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Helper function - calculates the amount of time a string should
|
|
be displayed for based on its word length */
|
|
|
|
function float CalcDisplayTime(string InString)
|
|
{
|
|
local float TimePerWord;
|
|
local array<string> Words;
|
|
|
|
TimePerWord = 0.5f;
|
|
Split(InString," ",Words);
|
|
|
|
return FMax(Words.length * TimePerWord,3.f);
|
|
}
|
|
|
|
function ShowDialogue(int DlgIndex)
|
|
{
|
|
local Controller C;
|
|
local KFPlayerController_Story StoryPC;
|
|
local string SpeakerName;
|
|
local actor VoiceOverSource;
|
|
local Material Portrait;
|
|
local Pawn MyInstigator;
|
|
|
|
/* nothing to show, nothing to play ... early out */
|
|
if(Dialogues[DlgIndex].Display.Dialogue_Text == "" &&
|
|
Dialogues[DlgIndex].VoiceOver.VoiceOverSound == none)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (C = Level.ControllerList; C != None; C = C.NextController)
|
|
{
|
|
StoryPC = KFPlayerController_Story(C);
|
|
if(StoryPC != none &&
|
|
(Dialogues[DlgIndex].BroadcastScope != InstigatorOnly ||
|
|
(DialogueInstigator != none && DialogueInstigator == C) ))
|
|
{
|
|
SpeakerName = Dialogues[DlgIndex].Display.Dialogue_Header ;
|
|
Portrait = Dialogues[DlgIndex].Display.Portrait_Material ;
|
|
|
|
if(SpeakerName == "self")
|
|
{
|
|
SpeakerName = StoryPC.PlayerReplicationinfo.PlayerName ;
|
|
Portrait = xPlayerReplicationInfo(StoryPC.PlayerReplicationInfo).Rec.Portrait;
|
|
}
|
|
|
|
if(GetCurrentDisplayDur() > 0)
|
|
{
|
|
Dialogues[DlgIndex].EventListener.SetTimer(GetCurrentDisplayDur(),false);
|
|
}
|
|
else
|
|
{
|
|
OnDialogueDisplayComplete(DlgIndex);
|
|
}
|
|
|
|
if(!SkipDialogue())
|
|
{
|
|
StoryPC.ClientShowStoryDialogue(name ,DlgIndex,GetCurrentDisplayDur());
|
|
}
|
|
|
|
if ( !SkipDialogue() && Dialogues[DlgIndex].VoiceOver.VoiceOverSound != None )
|
|
{
|
|
/* attempt to play a spatialised sound from the source actor (if there is one) */
|
|
VoiceOverSource = Dialogues[DlgIndex].VoiceOver.SourceActor ;
|
|
if(VoiceOverSource != none)
|
|
{
|
|
VoiceOverSource.PlaySound(Dialogues[DlgIndex].VoiceOver.VoiceOverSound,SLOT_Talk,VoiceOverSource.SoundVolume,,VoiceOverSource.SoundRadius,VoiceOverSource.SoundPitch,VoiceOverSource.bFullVolume);
|
|
}
|
|
else /*otherwise just play a ClientSound */
|
|
{
|
|
StoryPC.ClientPlayStorySound(Dialogues[DlgIndex].VoiceOver.VoiceOverSound, 2.0, false );
|
|
}
|
|
}
|
|
|
|
if(Dialogues[DlgIndex].Events.DisplayingEvent != '')
|
|
{
|
|
MyInstigator = DialogueInstigator.Pawn;
|
|
TriggerEvent(Dialogues[DlgIndex].Events.DisplayingEvent,self,MyInstigator);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function bool SkipDialogue()
|
|
{
|
|
return KFStoryGameInfo(Level.Game) != none && KFStoryGameInfo(Level.Game).bSkipDialogue;
|
|
}
|
|
|
|
function float GetCurrentDisplayDur()
|
|
{
|
|
local float Duration,VODuration;
|
|
|
|
if( SkipDialogue())
|
|
{
|
|
return 0.f;
|
|
}
|
|
|
|
Duration = CalcDisplayTime(Dialogues[CurrentMsgIdx].Display.Dialogue_Text);
|
|
|
|
/* Always make sure the text sticks around for at least as long as the VO */
|
|
if(Dialogues[CurrentMsgIdx].VoiceOver.VoiceOverSound != none)
|
|
{
|
|
VODuration = Dialogues[CurrentMsgIdx].VoiceOver.VoiceOverSound.Duration;
|
|
Duration = VODuration * 1.15; // Gamespeed is 1.1 so you always have to multiplay sound durations by 1.1. Adding a bit more buffer just in case - Ramm
|
|
}
|
|
|
|
return Duration;
|
|
}
|
|
|
|
function GetRequiredEvents( out array<Name> AllRequiredEvents)
|
|
{
|
|
local int i;
|
|
|
|
for(i = 0 ; i < Dialogues.length ; i ++)
|
|
{
|
|
if(Dialogues[i].Events.RequiredEvent != '')
|
|
{
|
|
AllRequiredEvents[AllRequiredEvents.length] = Dialogues[i].Events.RequiredEvent;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function GetDisplayEvents( out array<Name> AllDisplayEvents)
|
|
{
|
|
local int i;
|
|
|
|
for(i = 0 ; i < Dialogues.length ; i ++)
|
|
{
|
|
if(Dialogues[i].Events.DisplayedEvent != '')
|
|
{
|
|
AllDisplayEvents[AllDisplayEvents.length] = Dialogues[i].Events.DisplayedEvent;
|
|
}
|
|
|
|
if(Dialogues[i].Events.DisplayingEvent != '')
|
|
{
|
|
AllDisplayEvents[AllDisplayEvents.length] = Dialogues[i].Events.DisplayingEvent;
|
|
}
|
|
}
|
|
}
|
|
|
|
function GetEvents(out array<name> TriggeredEvents, out array<name> ReceivedEvents)
|
|
{
|
|
local int i;
|
|
|
|
Super.GetEvents(TriggeredEvents,ReceivedEvents);
|
|
|
|
for(i = 0 ; i < Dialogues.length ; i ++)
|
|
{
|
|
/* Things I trigger */
|
|
if(Dialogues[i].Events.DisplayedEvent != '')
|
|
{
|
|
TriggeredEvents[TriggeredEvents.length] = Dialogues[i].Events.DisplayedEvent;
|
|
}
|
|
if(Dialogues[i].Events.DisplayingEvent != '')
|
|
{
|
|
TriggeredEvents[TriggeredEvents.length] = Dialogues[i].Events.DisplayingEvent;
|
|
}
|
|
/* Things that trigger me*/
|
|
if(Dialogues[i].Events.RequiredEvent != '')
|
|
{
|
|
ReceivedEvents[ReceivedEvents.length] = Dialogues[i].Events.RequiredEvent;
|
|
}
|
|
}
|
|
}
|
|
|
|
defaultproperties
|
|
{
|
|
DrawScale = 0.5
|
|
|
|
bDebugDialogue = true
|
|
bNoDelete = true
|
|
bCollideActors = true
|
|
SkipDlgRange = 250
|
|
|
|
Texture = Texture 'KFStoryGame_Tex.Editor.KF_Dlgspot_Ico'
|
|
}
|
|
|