3.6.0 Code Changes
note
Some files simply have been reordered, but functionally, they make no difference. These changes have not been included.
DialogueAssetFactory.cpp
/NarrativeDialogueEditor/Private/DialogueAssetFactory.cpp
...
//Generally we would never want the parent classes speakers to inherit down into child classes
DialogueCDO->Speakers.Empty();
////Generally we would never want the parent classes speakers to inherit down into child classes
//DialogueCDO->Speakers.Empty();
FString NameString = DialogueBP->GetFName().ToString();
//FString NameString = DialogueBP->GetFName().ToString();
//Add the Default Speaker to the dialogue
FSpeakerInfo DefaultSpeaker;
int32 UnderscoreIndex = -1;
////Add the Default Speaker to the dialogue
//FSpeakerInfo DefaultSpeaker;
//int32 UnderscoreIndex = -1;
if (NameString.FindChar(TCHAR('_'), UnderscoreIndex))
{
//remove D_SpeakerName prefix
DefaultSpeaker.SpeakerID = FName(NameString.RightChop(UnderscoreIndex + 1));
}
else
{
DefaultSpeaker.SpeakerID = FName(NameString);
}
//if (NameString.FindChar(TCHAR('_'), UnderscoreIndex))
//{
// //remove D_SpeakerName prefix
// DefaultSpeaker.SpeakerID = FName(NameString.RightChop(UnderscoreIndex + 1));
//}
//else
//{
// DefaultSpeaker.SpeakerID = FName(NameString);
//}
...
DialogueCDO->Speakers.Add(DefaultSpeaker);
//DialogueCDO->Speakers.Add(DefaultSpeaker);
}
}
DialogueGraphSchema.h
/NarrativeDialogueEditor/Private/DialogueGraphSchema.h
...
/** Template of node we want to create */
UPROPERTY()
class UDialogueGraphNode* NodeTemplate;
TObjectPtr<class UDialogueGraphNode> NodeTemplate;
...
DialogueGraphNode_NPC.cpp
/NarrativeDialogueEditor/Private/DialogueGraphNode_NPC.cpp
...
{
if (UDialogueNode_NPC* NPCNode = Cast<UDialogueNode_NPC>(DialogueNode))
{
return DialogueCDO->GetSpeaker(NPCNode->SpeakerID).NodeColor;
return DialogueCDO->GetSpeaker(NPCNode->GetSpeakerID()).NodeColor;
...
return FText::FromName(DialogueCDO->GetSpeaker(NPCNode->SpeakerID).SpeakerID);
return FText::FromName(DialogueCDO->GetSpeaker(NPCNode->GetSpeakerID()).SpeakerID);
}
}
}
DialogueEditorDetails.cpp
/NarrativeDialogueEditor/Private/DialogueEditorDetails.cpp
#include "DialogueEditorDetails.h"
#include "DetailLayoutBuilder.h"
#include "Dialogue.h"
#include "DialogueSM.h"
...
#include "DialogueSM.h"
#include "Widgets/Input/SComboBox.h"
#include "IPropertyUtilities.h"
...
return FText::FromName(NPCNode->SpeakerID);
return FText::FromName(NPCNode->GetSpeakerID());
}
}
}
...
NPCNode->SpeakerID = FName(NewSelection->ToString());
NPCNode->SetSpeakerID(FName(NewSelection->ToString()));
}
}
}
...
if (UDialogueBlueprint* DialogueBP = Cast<UDialogueBlueprint>(NPCNode->OwningDialogue->GetOuter()))
{
if (UDialogue* DialogueCDO = Cast<UDialogue>(DialogueBP->GeneratedClass->GetDefaultObject()))
{
for (const auto& Speaker : DialogueCDO->Speakers)
{
SpeakersList.Add(MakeShareable(new FText(FText::FromName(Speaker.SpeakerID))));
if (Speaker.SpeakerID == NPCNode->SpeakerID)
{
SelectedItem = SpeakersList.Last();
}
}
FText RowText = LOCTEXT("SpeakerIDLabel", "Speaker");
//Add a button to make the quest designer more simplified
FDetailWidgetRow& Row = Category.AddCustomRow(GroupLabel)
.NameContent()
[
SNew(STextBlock)
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 8))
.Text(RowText)
]
.ValueContent()
[
SNew(SComboBox<TSharedPtr<FText>>)
.OptionsSource(&SpeakersList)
.OnSelectionChanged(this, &FDialogueEditorDetails::OnSelectionChanged)
.InitiallySelectedItem(SelectedItem)
.OnGenerateWidget_Lambda([](TSharedPtr<FText> Option)
{
return SNew(STextBlock)
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 8))
.Text(*Option);
})
[
SNew(STextBlock)
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 8))
.Text(this, &FDialogueEditorDetails::GetSpeakerText)
]
];
}
}
}
//else if (UDialogue* DialogueCDO = Cast<UDialogue>(EditedObjects[0]))
//{
// //Add a button to make the quest designer more simplified
// Category.AddCustomRow(GroupLabel)
// .ValueContent()
// [
// SNew(SButton)
// .ButtonStyle(FAppStyle::Get(), "RoundButton")
// .OnClicked(this, &FDialogueEditorDetails::SetTransformsFromActorSelection)
// [
// SNew(STextBlock)
// .Font(IDetailLayoutBuilder::GetDetailFontBold())
// .ToolTipText(LOCTEXT("SetSpeakerTransformsTooltip", "")
// LOCTEXT("SetSpeakerTransforms", "Set Speaker Transforms From Selection"))
// ]
// ];
//}
}
}
...
#undef LOCTEXT_NAMESPACE// removed-end
#undef LOCTEXT_NAMESPACE
DialogueGraphSchema.cpp
/NarrativeDialogueEditor/Private/DialogueGraphSchema.cpp
#include "Framework/Commands/UIAction.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "EdGraphNode_Comment.h"
#include "IDialogueEditor.h"
...
#include "Toolkits/ToolkitManager.h"
#include "DialogueConnectionDrawingPolicy.h"
#include "DialogueGraph.h"
#include "GraphEditorActions.h"
#include "Framework/Commands/GenericCommands.h"
#include "DialogueEditorCommands.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "DialogueSM.h"
#include "DialogueGraphNode_NPC.h"
#include "Dialogue.h"
#include "DialogueSM.h"
...
NPCNode->SpeakerID = SpeakerInfo.SpeakerID;
NPCNode->SetSpeakerID(SpeakerInfo.GetSpeakerID());
}
}
...
#undef LOCTEXT_NAMESPACE// removed-end
#undef LOCTEXT_NAMESPACE
DialogueGraph.cpp
/NarrativeDialogueEditor/Private/DialogueGraph.cpp
#include "DialogueGraph.h"
#include "Dialogue.h"
#include "NarrativeFunctionLibrary.h"
#include "NarrativeComponent.h"
#include "DialogueSM.h"
#include "QuestSM.h"
...
#include "QuestSM.h"
#include "Editor/UnrealEd/Public/Editor.h"
#include "NarrativeFunctionLibrary.h"
#include "NarrativeComponent.h"
#include <DialogueSM.h>
#include "DialogueGraphNode_Player.h"
#include "DialogueEditorSettings.h"
...
if (DialogueCDO->Speakers.IsValidIndex(0))
{
RootNode->Line.Text = FText::Format(LOCTEXT("DefaultRootNodeText", "Hi there, i'm {0}."), FText::FromString(DialogueCDO->Speakers[0].SpeakerID.ToString()));
RootNode->SpeakerID = DialogueCDO->Speakers[0].SpeakerID;
}
//if (DialogueCDO->Speakers.IsValidIndex(0))
//{
// RootNode->Line.Text = FText::Format(LOCTEXT("DefaultRootNodeText", "Hi there, i'm {0}."), FText::FromString(DialogueCDO->Speakers[0].SpeakerID.ToString()));
// RootNode->SpeakerID = DialogueCDO->Speakers[0].SpeakerID;
//}
DialogueAsset->DialogueTemplate->RootDialogue = RootNode;
}
DialogueConnectionDrawingPolicy.cpp
/NarrativeDialogueEditor/Private/DialogueConnectionDrawingPolicy.cpp
#include "DialogueConnectionDrawingPolicy.h"
#include "QuestSM.h"
#include "NarrativeDialogueSettings.h"
...
#include "NarrativeDialogueSettings.h"
#include "DialogueEditorSettings.h"
DialogueGraphEditor.cpp
/NarrativeDialogueEditor/Private/DialogueGraphEditor.cpp
...
void FDialogueGraphEditor::Dialogue_PasteNodesHere(const FVector2D& Location)
{
...
return;
// return;
TSharedPtr<SGraphEditor> CurrentGraphEditor = FocusedGraphEdPtr.Pin();
if (!CurrentGraphEditor.IsValid())
// Disabled Node copy + paste and keeping text based until further testing can happen
...
TSet<UEdGraphNode*> PastedNodes;
FEdGraphUtilities::ImportNodesFromText(DialogueGraph, TextToImport, /*out*/ PastedNodes);
if (PastedNodes.Num())
{
for (TSet<UEdGraphNode*>::TIterator It(PastedNodes); It; ++It)
{
UEdGraphNode* PasteNode = *It;
UDialogueGraphNode* PasteDialogueNode = Cast<UDialogueGraphNode>(PasteNode);
if (PasteNode && PasteDialogueNode)
{
// Select the newly pasted stuff
CurrentGraphEditor->SetNodeSelection(PasteNode, true);
PasteNode->NodePosX += 400.f;
PasteNode->NodePosY += 400.f;
PasteNode->SnapToGrid(16);
// Give new node a different Guid from the old one
PasteNode->CreateNewGuid();
//New dialogue graph node will point to old dialouenode, duplicate a new one for our new node
UDialogueNode* DupNode = Cast<UDialogueNode>(StaticDuplicateObject(PasteDialogueNode->DialogueNode, PasteDialogueNode->DialogueNode->GetOuter()));
//StaticDuplicateObject won't have assigned a unique ID, grab a unique one
DupNode->EnsureUniqueID();
PasteDialogueNode->DialogueNode = DupNode;
}
}
//Now that everything has been pasted, iterate a second time to rebuild the new nodes connections
for (TSet<UEdGraphNode*>::TIterator It(PastedNodes); It; ++It)
{
UEdGraphNode* PasteNode = *It;
UDialogueGraphNode* PasteDialogueNode = Cast<UDialogueGraphNode>(PasteNode);
//Dialogue nodes connections will still be outdated, update these to the new connections
DialogueGraph->NodeAdded(PasteDialogueNode);
DialogueGraph->PinRewired(PasteDialogueNode, PasteDialogueNode->GetOutputPin());
}
}
else
{
// TSet<UEdGraphNode*> PastedNodes;
// FEdGraphUtilities::ImportNodesFromText(DialogueGraph, TextToImport, /*out*/ PastedNodes);
// if (PastedNodes.Num())
// {
// for (TSet<UEdGraphNode*>::TIterator It(PastedNodes); It; ++It)
// {
// UEdGraphNode* PasteNode = *It;
// UDialogueGraphNode* PasteDialogueNode = Cast<UDialogueGraphNode>(PasteNode);
//
//
// if (PasteNode && PasteDialogueNode)
// {
// // Select the newly pasted stuff
// CurrentGraphEditor->SetNodeSelection(PasteNode, true);
//
// PasteNode->NodePosX += 400.f;
// PasteNode->NodePosY += 400.f;
//
// PasteNode->SnapToGrid(16);
//
// // Give new node a different Guid from the old one
// PasteNode->CreateNewGuid();
//
// //New dialogue graph node will point to old dialouenode, duplicate a new one for our new node
// UDialogueNode* DupNode = Cast<UDialogueNode>(StaticDuplicateObject(PasteDialogueNode->DialogueNode, PasteDialogueNode->DialogueNode->GetOuter()));
//
// //StaticDuplicateObject won't have assigned a unique ID, grab a unique one
// DupNode->EnsureUniqueID();
//
// PasteDialogueNode->DialogueNode = DupNode;
// }
// }
//
// //Now that everything has been pasted, iterate a second time to rebuild the new nodes connections
// for (TSet<UEdGraphNode*>::TIterator It(PastedNodes); It; ++It)
// {
// UEdGraphNode* PasteNode = *It;
// UDialogueGraphNode* PasteDialogueNode = Cast<UDialogueGraphNode>(PasteNode);
//
// //Dialogue nodes connections will still be outdated, update these to the new connections
// DialogueGraph->NodeAdded(PasteDialogueNode);
// DialogueGraph->PinRewired(PasteDialogueNode, PasteDialogueNode->GetOutputPin());
// }
//
// }
// else
// {
/*
We may be trying to paste from narrative dialogue markup.
...
FDialogueSchemaAction_NewNode AddNewNode;
UDialogueGraphNode* Node;
UClass* DialogueNodeClass = SpeakerID.Equals("Player", ESearchCase::IgnoreCase) ? UDialogueGraphNode_Player::StaticClass() : UDialogueGraphNode_NPC::StaticClass();
...
NPCNode->SpeakerID = FName(SpeakerID);
NPCNode->SetSpeakerID(FName(SpeakerID));
}
}
...
}
// } // else end bracket
// Update UI
CurrentGraphEditor->NotifyGraphChanged();
.IsEditable(bGraphIsEditable)
...
#undef LOCTEXT_NAMESPACE// removed-end
#undef LOCTEXT_NAMESPACE
DialogueBlueprintCompiler.cpp
/NarrativeDialogueEditor/Private/DialogueBlueprintCompiler.cpp
...
#include "DialogueBlueprintCompiler.h"
#include "DialogueBlueprintGeneratedClass.h"
#include "DialogueGraphSchema.h"
#include "DialogueGraphSchema.h"
...
QuestGraphSchema.h
/NarrativeQuestEditor/Private/QuestGraphSchema.h
...
/** Template of node we want to create */
UPROPERTY()
class UQuestGraphNode* NodeTemplate;
TObjectPtr<class UQuestGraphNode> NodeTemplate;
...
QuestGraphSchema.cpp
/NarrativeQuestEditor/Private/QuestGraphSchema.cpp
...
#include "QuestSM.h"
#include "Quest.h"
#include "QuestGraphEditor.h"
#include <Engine/ObjectLibrary.h>
#include "QuestEditorSettings.h"
...
//Todo optimize by caching these
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
FCategorizedGraphActionListBuilder ActionMainCategory("Now the player needs to...");
//No need to cache this as once library loads the classes once they will be very quick to load next time
auto ItemLibrary = UObjectLibrary::CreateLibrary(UNarrativeTask::StaticClass(), true, GIsEditor);
TArray<FString> Paths;
if (const UQuestEditorSettings* Settings = GetDefault<UQuestEditorSettings>())
{
Paths = Settings->QuestTaskSearchPaths;
}
ItemLibrary->LoadBlueprintsFromPaths(Paths);
TArray<UClass*> Subclasses;
ItemLibrary->GetObjects<UClass>(Subclasses);
if (UQuestBlueprint* const QuestAsset = Cast<UQuestBlueprint>(ContextMenuBuilder.CurrentGraph->GetOuter()))
...
TArray<UClass*> Subclasses;
GetDerivedClasses(UNarrativeTask::StaticClass(), Subclasses);
FCategorizedGraphActionListBuilder Title("NEXT THE PLAYER HAS TO:");
ActionMainCategory.Append(Title);
QuestAssetFactory.cpp
/NarrativeQuestEditor/Private/QuestAssetFactory.cpp
...
#include "Editor/ClassViewer/Public/ClassViewerFilter.h"
#include "QuestBlueprint.h"
#include "Kismet2/SClassPickerDialog.h"
#include "Kismet2/KismetEditorUtilities.h"
...
#include "Kismet2/KismetEditorUtilities.h"
#include "Quest.h"
#include "BlueprintEditorSettings.h"
#include "QuestEditorSettings.h"
NarrativeQuestEditorModule.cpp
/NarrativeQuestEditor/Private/NarrativeQuestEditorModule.cpp
...
#include "QuestEditorDetails.h"
#include "QuestEditorSettings.h"
#include "NarrativeQuestSettings.h"
#include "QuestTask.h"
...
#include "QuestTask.h"
DEFINE_LOG_CATEGORY(LogNarrativeQuestEditor);
QuestEditorSettings.cpp
/NarrativeQuestEditor/Private/QuestEditorSettings.cpp
...
DefaultQuestWidgetClass = QuestNodeUserWidgetFinder.Class;
}
QuestTaskSearchPaths.Add("/Narrative/DefaultTasks/");
QuestTaskSearchPaths.Add("/NarrativeInventory/Narrative3/Tasks/");
QuestTaskSearchPaths.Add("/NarrativeNavigator/Narrative3/Tasks/");
QuestTaskSearchPaths.Add("/NarrativeInteraction/Integrations/Narrative3/");
QuestTaskSearchPaths.Add("/Game/Blueprints/Tasks/");
QuestTaskSearchPaths.Add("/Game/Blueprints/Narrative/Tasks/");
QuestTaskSearchPaths.Add("/Game/Narrative/Tasks/");
...
QuestBlueprintCompiler.cpp
/NarrativeQuestEditor/Private/QuestBlueprintCompiler.cpp
...
#include "QuestBlueprintCompiler.h"
#include "QuestBlueprintGeneratedClass.h"
#include "QuestGraphSchema.h"
#include "QuestGraphSchema.h"
...
SQuestGraphNode.cpp
/NarrativeQuestEditor/Private/SQuestGraphNode.cpp
...
return FAppStyle::Get().GetBrush(TEXT("Graph.StateNode.Body"));
}
#undef LOCTEXT_NAMESPACE// removed-end
#undef LOCTEXT_NAMESPACE
QuestGraphEditor.cpp
/NarrativeQuestEditor/Private/QuestGraphEditor.cpp
...
return false;
// If any of the nodes can be duplicated then we should allow copying
/*const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
for (FGraphPanelSelectionSet::TConstIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter)
{
UEdGraphNode* Node = Cast<UEdGraphNode>(*SelectedIter);
if (Node && Node->CanDuplicateNode())
{
return true;
}
}
return false;*/
//const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
//for (FGraphPanelSelectionSet::TConstIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter)
//{
// UEdGraphNode* Node = Cast<UEdGraphNode>(*SelectedIter);
// if (Node && Node->CanDuplicateNode())
// {
// return true;
// }
//}
//return false;
.IsEditable(bGraphIsEditable)
...
QuestEditorSettings.h
/NarrativeQuestEditor/Private/QuestEditorSettings.h
...
public:
UQuestEditorSettings();
//All the folders the quest editor searches in to find your quest tasks.
UPROPERTY(EditAnywhere, config, Category = "Quests", meta = (RelativeToGameContentDir))
TArray<FString> QuestTaskSearchPaths;
...
DialogueAsset.cpp
/Narrative/Private/DialogueAsset.cpp
...
Dialogue = CreateDefaultSubobject<UDialogue>(TEXT("Dialogue"));
//Any dialogues created prior to the speakers update need a speaker added
if (Dialogue->Speakers.Num() == 0)
{
FSpeakerInfo DefaultSpeaker;
DefaultSpeaker.SpeakerID = GetFName();
Dialogue->Speakers.Add(DefaultSpeaker);
}
//if (Dialogue->Speakers.Num() == 0)
//{
// FSpeakerInfo DefaultSpeaker;
// DefaultSpeaker.SpeakerID = GetFName();
// Dialogue->Speakers.Add(DefaultSpeaker);
//}
...
DialogueBlueprintGeneratedClass.cpp
/Narrative/Private/DialogueBlueprintGeneratedClass.cpp
#include "DialogueBlueprintGeneratedClass.h"
#include "Dialogue.h"
#include <DialogueSM.h>
#include "DialogueSM.h"
...
Quest.cpp
/Narrative/Private/Quest.cpp
...
#include "Quest.h"
#include "NarrativeDataTask.h"
#include "QuestSM.h"
#include "Net/UnrealNetwork.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/PlayerController.h"
#include "Net/UnrealNetwork.h"
bTracked = true;
BPPreQuestStarted(this);
void UQuest::SetTracked(const bool bNewTracked)
{
if (bNewTracked != bTracked)
{
bTracked = bNewTracked;
BPOnTrackedChanged(this, bTracked);
}
}
...
//Finally, activate our new state, therefore activating its branches allowing us to take one to progress through the quest
//We dont call delegate updates when loading, as delegates are typically just for UI updates and things
if (!OwningComp->bIsLoading)
{
//Fire delegates because we're about to activate the current state. This can actually cause another state change, which will cause delegates to fire in the wrong order.
BPOnQuestNewState(this, NewState);
if (OwningComp)
{
OwningComp->OnQuestNewState.Broadcast(this, NewState);
}
QuestNewState.Broadcast(this, CurrentState);
}
//Finally, activate our new state, therefore activating its branches allowing us to take one to progress through the quest.
CurrentState->Activate();
//If we're loading quests back in off disk we don't want to broadcast any progress or anything
if (OwningComp->bIsLoading)
{
return;
}
//Fire off delegates
BPOnQuestNewState(this, NewState);
if (OwningComp)
{
OwningComp->OnQuestNewState.Broadcast(this, NewState);
}
QuestNewState.Broadcast(this, CurrentState);
}
...
BPOnQuestFailed(this, QuestFailedMessage);
QuestFailed.Broadcast(this, QuestFailedMessage);
if (OwningComp)
{
if (OwningComp && !OwningComp->bIsLoading)
{
BPOnQuestFailed(this, QuestFailedMessage);
QuestFailed.Broadcast(this, QuestFailedMessage);
OwningComp->OnQuestFailed.Broadcast(this, QuestFailedMessage);
}
...
BPOnQuestSucceeded(this, QuestSucceededMessage);
QuestSucceeded.Broadcast(this, QuestSucceededMessage);
if (OwningComp)
{
if (OwningComp && !OwningComp->bIsLoading)
{
BPOnQuestSucceeded(this, QuestSucceededMessage);
QuestSucceeded.Broadcast(this, QuestSucceededMessage);
OwningComp->OnQuestSucceeded.Broadcast(this, QuestSucceededMessage);
}
DialogueSM.cpp
/Narrative/Private/DialogueSM.cpp
#include "Dialogue.h"
#include "NarrativeComponent.h"
#include "NarrativeCondition.h"
#include "NarrativeDialogueSettings.h"
#include "NarrativePartyComponent.h"
...
#include "NarrativeDialogueSettings.h"
#include "LevelSequencePlayer.h"
#include "LevelSequenceActor.h"
#include "Sound/SoundBase.h"
#include "NarrativePartyComponent.h"
...
TArray<class UDialogueNode_NPC*> UDialogueNode::GetNPCReplies(APlayerController* OwningController, APawn* OwningPawn, class UNarrativeComponent* NarrativeComponent)
{
TArray<class UDialogueNode_NPC*> ValidReplies;
for (auto& NPCReply : NPCReplies)
class UDialogueNode_NPC* UDialogueNode::GetFirstValidNPCReply(APlayerController* OwningController, APawn* OwningPawn, class UNarrativeComponent* NarrativeComponent)
{
TArray<class UDialogueNode_NPC*> RepliesToCheck = NPCReplies;
if (const UNarrativeDialogueSettings* DialogueSettings = GetDefault<UNarrativeDialogueSettings>())
{
//Sort the replies by their Y position in the graph
RepliesToCheck.Sort([DialogueSettings](const UDialogueNode_NPC& NodeA, const UDialogueNode_NPC& NodeB) {
return DialogueSettings->bEnableVerticalWiring ? NodeA.NodePos.X < NodeB.NodePos.X : NodeA.NodePos.Y < NodeB.NodePos.Y;
});
}
for (auto& NPCReply : RepliesToCheck)
{
if (NPCReply->AreConditionsMet(OwningPawn, OwningController, NarrativeComponent))
{
ValidReplies.Add(NPCReply);
}
}
return ValidReplies;
return NPCReply;
}
}
return nullptr;
...
Prefix = NPCNode->SpeakerID.ToString();
Prefix = NPCNode->GetSpeakerID().ToString();
}
else
{
...
return HintText;
// Replace string variables on the hint text
FText newHintText = HintText;
InDialogue->ReplaceStringVariables(this, Line, newHintText);
return newHintText;
}
FText TextToUse = FText::GetEmpty();
// Replace string variables on the hint text
InDialogue->ReplaceStringVariables(this, Line, TextToUse);
...
QuestSM.cpp
/Narrative/Private/QuestSM.cpp
...
for (auto& MyTask : QuestTasks)
{
if (!MyTask->IsComplete())
if (MyTask && !MyTask->IsComplete())
...
#undef LOCTEXT_NAMESPACE// removed-end
#undef LOCTEXT_NAMESPACE
NarrativePartyComponent.cpp
/Narrative/Private/NarrativePartyComponent.cpp
...
DOREPLIFETIME(UNarrativePartyComponent, PartyMemberStates);
}
bool UNarrativePartyComponent::BeginDialogue(TSubclassOf<class UDialogue> DialogueClass, FName StartFromID /*= NAME_None*/)
bool UNarrativePartyComponent::BeginDialogue(TSubclassOf<class UDialogue> DialogueClass, const FDialoguePlayParams PlayParams)
...
if (SetCurrentDialogue(DialogueClass, StartFromID))
if (SetCurrentDialogue(DialogueClass, PlayParams))
{
OnDialogueBegan.Broadcast(CurrentDialogue);
...
}
}// added-end
Dialogue.cpp
/Narrative/Private/Dialogue.cpp
// Copyright Narrative Tools 2022.
#include "Dialogue.h"
#include "Net/UnrealNetwork.h"
#include "UObject/ConstructorHelpers.h"
#include "NarrativeDialogueSettings.h"
#include "NarrativeDialogueSequence.h"
#include "NarrativePartyComponent.h"
#include "UObject/ConstructorHelpers.h"
...
#include "NarrativeDialogueSettings.h"
#include "Camera/CameraActor.h"
#include "Camera/CameraShakeBase.h"
#include "CineCameraActor.h"
#include "CineCameraComponent.h"
#include "NarrativeDefaultCinecam.h"
#include "Sound/SoundBase.h"
#include <Camera/CameraShakeBase.h>
#include <EngineUtils.h>
#include <Kismet/KismetMathLibrary.h>
#include <DefaultLevelSequenceInstanceData.h>
#include "NarrativeDialogueSequence.h"
#include "NarrativePartyComponent.h"
Priority = 0;
PlayerAutoAdjustTransform = FTransform(FRotator(0.f, 180.f, 0.f), FVector(200.f, 0.f, 0.f));
...
bool UDialogue::Initialize(class UNarrativeComponent* InitializingComp, FName StartFromID)
bool UDialogue::Initialize(class UNarrativeComponent* InitializingComp, const FDialoguePlayParams InPlayParams)
{
if (!HasAnyFlags(RF_ClassDefaultObject))
{
...
UDialogueNode_NPC* StartDialogue = StartFromID.IsNone() ? RootDialogue : GetNPCReplyByID(StartFromID);
if (!StartDialogue && !StartFromID.IsNone())
{
UE_LOG(LogNarrative, Warning, TEXT("UDialogue::Initialize could not find Start node with StartFromID: %s. Falling back to root node."), *StartFromID.ToString());
StartDialogue = RootDialogue;
UDialogueNode_NPC* StartDialogue = InPlayParams.StartFromID.IsNone() ? RootDialogue : GetNPCReplyByID(InPlayParams.StartFromID);
if (!StartDialogue && !InPlayParams.StartFromID.IsNone())
{
//UE_LOG(LogNarrative, Warning, TEXT("UDialogue::Initialize could not find Start node with StartFromID: %s. Falling back to root node."), *StartFromID.ToString());
return false;
//StartDialogue = RootDialogue;
}
//Initialize all the data required to begin the dialogue
PlayParams = InPlayParams;
...
if (DialogueSequencePlayer)
{
DialogueSequencePlayer->Destroy();
}
OwningComp = nullptr;
DefaultDialogueShot = nullptr;
...
if (PlayerSpeakerInfo.SpeakerID != NAME_PlayerSpeakerID)
for (auto& Speaker : Speakers)
{
PlayerSpeakerInfo.SpeakerID = NAME_PlayerSpeakerID;
}
...
for (int32 i = 0; i < PartySpeakerInfo.Num(); ++i)
{
if (PartySpeakerInfo.IsValidIndex(i))
{
PartySpeakerInfo[i].SpeakerID = FName(FString::Printf(TEXT("PartyMember%d"), i));
}
}
//If a designer clears the speakers always ensure at least one is added
if (Speakers.Num() == 0)
{
FSpeakerInfo DefaultSpeaker;
DefaultSpeaker.SpeakerID = GetFName();
Speakers.Add(DefaultSpeaker);
}
//If any NPC replies don't have a valid speaker set to the first speaker
for (auto& Node : NPCReplies)
{
if (Node)
{
bool bSpeakerNotFound = true;
for (auto& Speaker : Speakers)
{
if (Speaker.SpeakerID == Node->SpeakerID)
{
bSpeakerNotFound = false;
}
}
if (bSpeakerNotFound)
{
Node->SpeakerID = Speakers[0].SpeakerID;
}
}
}
//If any NPC replies don't have a valid speaker set to the first speaker TODO this is broken - figure out why
//for (auto& Node : NPCReplies)
//{
// if (Node)
// {
// bool bSpeakerNotFound = true;
// for (auto& Speaker : Speakers)
// {
// if (Speaker.SpeakerID == Node->SpeakerID)
// {
// bSpeakerNotFound = false;
// }
// }
// if (bSpeakerNotFound)
// {
// Node->SpeakerID = Speakers[0].SpeakerID;
// }
// }
//}
}
void UDialogue::PreEditChange(FEditPropertyChain& PropertyAboutToChange)
...
if (Speaker.SpeakerID == SpeakerID)
if (Speaker.GetSpeakerID() == SpeakerID)
{
return Speaker;
}
...
if (DialogueSequencePlayer && DialogueSequencePlayer->SequencePlayer)
{
DialogueSequencePlayer->SequencePlayer->OnFinished.RemoveAll(this);
if (DialogueSequencePlayer)
{
if (ULevelSequencePlayer* SP = DialogueSequencePlayer->GetSequencePlayer())
{
SP->OnFinished.RemoveAll(this);
}
}
if (CurrentNode->IsA<UDialogueNode_NPC>())
...
if (DialogueSequencePlayer && DialogueSequencePlayer->SequencePlayer)
{
DialogueSequencePlayer->SequencePlayer->OnFinished.RemoveAll(this);
if (DialogueSequencePlayer)
{
if (ULevelSequencePlayer* SP = DialogueSequencePlayer->GetSequencePlayer())
{
SP->OnFinished.RemoveAll(this);
}
}
if (GetWorld())
InitSpeakerAvatars();
if (bAdjustPlayerTransform)
{
AdjustPlayerTransform();
}
{
...
}
}
}
InitSpeakerAvatars();
}
}
}
}
if (OwningController && OwningController->IsLocalPlayerController())
{
...
//Track spawned avatars
SpeakerAvatars.Add(Speaker.SpeakerID, SpeakerActor);
Speaker.SpeakerAvatarTransform = SpeakerActor->GetActorTransform();
if (IsValid(SpeakerActor))
{
//Track spawned avatars
SpeakerAvatars.Add(Speaker.GetSpeakerID(), SpeakerActor);
Speaker.SpeakerAvatarTransform = SpeakerActor->GetActorTransform();
}
}
}
...
if (UNarrativePartyComponent* OwningParty = Cast<UNarrativePartyComponent>(OwningComp))
{
TArray<APlayerState*> PartyMembers = OwningParty->GetPartyMemberStates();
for (int32 i = 0; i < PartyMembers.Num(); ++i)
{
if (!PartySpeakerInfo.IsValidIndex(i))
{
FPlayerSpeakerInfo NewSpeaker;
NewSpeaker.SpeakerID = FName(FString::Printf(TEXT("PartyMember%d"), i));
PartySpeakerInfo.Add(NewSpeaker);
}
if (PartySpeakerInfo.IsValidIndex(i) && PartyMembers.IsValidIndex(i))
{
FPlayerSpeakerInfo& MemberSpeakerInfo = PartySpeakerInfo[i];
if (APlayerState* PartyMember = PartyMembers[i])
{
AActor* SpeakerActor = LinkSpeakerAvatar(MemberSpeakerInfo);
//Fallback to speaker actors pawn if can't link
if (!SpeakerActor)
{
SpeakerActor = PartyMember->GetPawn();
}
if (SpeakerActor)
{
/*There has to be a nicer way to construct an FName from a int but I sure couldnt find it!
Instead of caching speaker avatars via ID, for parties we use the players playerID which is unique.
This gives us a nice convenient way to map someones PlayerState to their Players avatar */
const FName Name_PID = FName(FString::Printf(TEXT("%d"), PartyMember->GetPlayerId()));
MemberSpeakerInfo.SpeakerID = Name_PID;
SpeakerAvatars.Add(MemberSpeakerInfo.SpeakerID, SpeakerActor);
//Hide the party members pawn; we've spawned them an avatar
if (APawn* PawnOwner = PartyMember->GetPawn())
{
//Store our local pawn in the playerspeakerinfo and the existing systems will just treat it like our solo player
if (PawnOwner->IsLocallyControlled())
{
SpeakerAvatars.Add(PlayerSpeakerInfo.SpeakerID, SpeakerActor);
}
//If we're using a speaker avatar for this player, we want to hide their pawn
if (SpeakerActor != PartyMember->GetPawn())
{
PawnOwner->SetActorHiddenInGame(true);
}
}
}
}
}
}
}
else //spawn solo players avatar in
{
//Spawn the players speaker avatar in, or just use the players pawn as their avatar if one isn't set
if (AActor* SpeakerActor = LinkSpeakerAvatar(PlayerSpeakerInfo))
{
SpeakerAvatars.Add(PlayerSpeakerInfo.SpeakerID, SpeakerActor);
PlayerSpeakerInfo.SpeakerAvatarTransform = SpeakerActor->GetActorTransform();
//By default if the player has a speaker avatar in the world we'll hide their pawn
if (OwningPawn && SpeakerActor != OwningPawn)
{
OwningPawn->SetActorHiddenInGame(true);
}
}
else if (!OwningPawn)
{
UE_LOG(LogNarrative, Warning, TEXT("Narrative wasn't able to find the avatar for the player, as a SpeakerAvatarClass wasn't set, no actors with tag 'Player' were found, and OwningPawn was invalid."));
}
}
//if (UNarrativePartyComponent* OwningParty = Cast<UNarrativePartyComponent>(OwningComp))
//{
// TArray<APlayerState*> PartyMembers = OwningParty->GetPartyMemberStates();
// for (int32 i = 0; i < PartyMembers.Num(); ++i)
// {
// if (!PartySpeakerInfo.IsValidIndex(i))
// {
// FPlayerSpeakerInfo NewSpeaker;
// //NewSpeaker.SpeakerID = FName(FString::Printf(TEXT("PartyMember%d"), i));
// PartySpeakerInfo.Add(NewSpeaker);
// }
// if (PartySpeakerInfo.IsValidIndex(i) && PartyMembers.IsValidIndex(i))
// {
// FPlayerSpeakerInfo& MemberSpeakerInfo = PartySpeakerInfo[i];
// if (APlayerState* PartyMember = PartyMembers[i])
// {
// AActor* SpeakerActor = LinkSpeakerAvatar(MemberSpeakerInfo);
// //Fallback to speaker actors pawn if can't link
// if (!SpeakerActor)
// {
// SpeakerActor = PartyMember->GetPawn();
// }
// if (SpeakerActor)
// {
// /*There has to be a nicer way to construct an FName from a int but I sure couldnt find it!
// Instead of caching speaker avatars via ID, for parties we use the players playerID which is unique.
// This gives us a nice convenient way to map someones PlayerState to their Players avatar */
// const FName Name_PID = FName(FString::Printf(TEXT("%d"), PartyMember->GetPlayerId()));
// MemberSpeakerInfo.SpeakerID = Name_PID;
// SpeakerAvatars.Add(MemberSpeakerInfo.SpeakerID, SpeakerActor);
// //Hide the party members pawn; we've spawned them an avatar
// if (APawn* PawnOwner = PartyMember->GetPawn())
// {
// //Store our local pawn in the playerspeakerinfo and the existing systems will just treat it like our solo player
// if (PawnOwner->IsLocallyControlled())
// {
// SpeakerAvatars.Add(PlayerSpeakerInfo.SpeakerID, SpeakerActor);
// }
// //If we're using a speaker avatar for this player, we want to hide their pawn
// if (SpeakerActor != PartyMember->GetPawn())
// {
// PawnOwner->SetActorHiddenInGame(true);
// }
// }
// }
// }
// }
// }
//}
//else //spawn solo players avatar in
//{
//
//
//
//Spawn the players speaker avatar in, or just use the players pawn as their avatar if one isn't set
if (AActor* SpeakerActor = LinkSpeakerAvatar(PlayerSpeakerInfo))
{
SpeakerAvatars.Add(PlayerSpeakerInfo.GetSpeakerID(), SpeakerActor);
PlayerSpeakerInfo.SpeakerAvatarTransform = SpeakerActor->GetActorTransform();
//By default if the player has a speaker avatar in the world we'll hide their pawn
if (OwningPawn && SpeakerActor != OwningPawn)
{
OwningPawn->SetActorHiddenInGame(true);
}
}
else if (!OwningPawn)
{
UE_LOG(LogNarrative, Warning, TEXT("Narrative wasn't able to find the avatar for the player, as a SpeakerAvatarClass wasn't set, no actors with tag 'Player' were found, and OwningPawn was invalid."));
}
//}
}
...
if (AActor* SpeakerAvatar = GetAvatar(Speaker.SpeakerID))
if (AActor* SpeakerAvatar = GetAvatar(Speaker.GetSpeakerID()))
{
DestroySpeakerAvatar(Speaker, SpeakerAvatar);
}
...
if (AActor* SpeakerAvatar = GetAvatar(PartySpeaker.SpeakerID))
if (AActor* SpeakerAvatar = GetAvatar(PartySpeaker.GetSpeakerID()))
{
DestroySpeakerAvatar(PartySpeaker, SpeakerAvatar);
}
...
if (DialogueSequencePlayer && DialogueSequencePlayer->SequencePlayer)
{
DialogueSequencePlayer->SequencePlayer->Stop();
if (DialogueSequencePlayer)
{
if (ULevelSequencePlayer* SP = DialogueSequencePlayer->GetSequencePlayer())
{
SP->Stop();
}
DialogueSequencePlayer->Destroy();
}
if (OwningPawn)
bool bWantsAutoSelect = bFreeMovement;
...
if ((bFreeMovement || DialogueSettings->bAutoSelectSingleResponse) && AvailableResponses.Num() == 1)
{
OwningComp->TrySelectDialogueOption(AvailableResponses.Last());
return;
}
}
if (DialogueSettings->bAutoSelectSingleResponse && AvailableResponses.Num() == 1)
{
bWantsAutoSelect = true;
}
}
//If a response is autoselect, select it and early out
for (auto& AvailableResponse : AvailableResponses)
...
if (AvailableResponse && AvailableResponse->IsAutoSelect())
if (AvailableResponse && (AvailableResponse->IsAutoSelect() || bWantsAutoSelect))
{
OwningComp->TrySelectDialogueOption(AvailableResponse);
return;
...
if (SpeakerAvatars.Contains(CurrentSpeaker.SpeakerID))
{
ListeningActor = SpeakerAvatars[CurrentSpeaker.SpeakerID];
if (SpeakerAvatars.Contains(CurrentSpeaker.GetSpeakerID()))
{
ListeningActor = SpeakerAvatars[CurrentSpeaker.GetSpeakerID()];
}
/*
...
CurrentSpeaker = GetSpeaker(NPCReply->SpeakerID);
CurrentSpeaker = GetSpeaker(NPCReply->GetSpeakerID());
ProcessNodeEvents(NPCReply, true);
...
if (SpeakerAvatars.Contains(PlayerSpeakerInfo.SpeakerID))
{
return SpeakerAvatars[PlayerSpeakerInfo.SpeakerID];
if (SpeakerAvatars.Contains(PlayerSpeakerInfo.GetSpeakerID()))
{
return SpeakerAvatars[PlayerSpeakerInfo.GetSpeakerID()];
}
else
{
...
UDialogueNode_NPC* NextReply = nullptr;
for (auto& NextNPCReply : PlayerNode->NPCReplies)
{
if (NextNPCReply && NextNPCReply->AreConditionsMet(OwningPawn, OwningController, OwningComp))
{
NextReply = NextNPCReply;
break;
}
}
UDialogueNode_NPC* NextReply = PlayerNode->GetFirstValidNPCReply(OwningController, OwningPawn, OwningComp);
//If we can generate more dialogue from the reply that was selected, do so, otherwise exit dialogue
if (GenerateDialogueChunk(NextReply))
...
AActor* SpawnedActor = Info.SpeakerID == PlayerSpeakerInfo.SpeakerID ? OwningPawn : nullptr;
if (!Info.SpeakerID.IsNone())
{
if (!SpeakerAvatars.Contains(Info.SpeakerID) && IsValid(Info.SpeakerAvatarClass))
AActor* SpawnedActor = Info.GetSpeakerID() == PlayerSpeakerInfo.GetSpeakerID() ? OwningPawn : nullptr;
if (!Info.GetSpeakerID().IsNone())
{
if (!SpeakerAvatars.Contains(Info.GetSpeakerID()) && IsValid(Info.SpeakerAvatarClass))
{
FActorSpawnParameters SpawnParams;
SpawnParams.bNoFail = true;
...
if (Actor && Actor->ActorHasTag(Info.SpeakerID))
if (Actor && Actor->ActorHasTag(Info.GetSpeakerID()))
{
FoundActors.Add(Actor);
}
...
DialogueAudio = UGameplayStatics::SpawnSoundAtLocation(OwningComp, Line.DialogueSound, Speaker->GetActorLocation(), Speaker->GetActorForwardVector().Rotation());
//DialogueAudio = UGameplayStatics::SpawnSoundAtLocation(OwningComp, Line.DialogueSound, Speaker->GetActorLocation(), Speaker->GetActorForwardVector().Rotation(), 1.f, 1.f, 0.f, DialogueSoundAttenuation);
DialogueAudio = UGameplayStatics::SpawnSoundAttached(Line.DialogueSound, Speaker->GetRootComponent(), NAME_None, FVector::ZeroVector, EAttachLocation::SnapToTarget, false, 1.f, 1.f, 0.f, DialogueSoundAttenuation);
}
else //Else just play 2D audio
{
else // If there is no shot to play, we should end any previously playing one
{
StopDialogueSequence();
}
...
if (SpeakerAvatars.Contains(SpeakerInfo.SpeakerID))
{
SpeakingActor = *SpeakerAvatars.Find(SpeakerInfo.SpeakerID);
if (SpeakerAvatars.Contains(SpeakerInfo.GetSpeakerID()))
{
SpeakingActor = *SpeakerAvatars.Find(SpeakerInfo.GetSpeakerID());
}
PlayDialogueNode(NPCReply, LineToPlay, SpeakerInfo, SpeakingActor, ListeningActor);
...
ListeningActor = GetAvatar(CurrentSpeaker.SpeakerID);
ListeningActor = GetAvatar(CurrentSpeaker.GetSpeakerID());
}
PlayDialogueNode(PlayerReply, Line, PlayerSpeakerInfo, SpeakingActor, ListeningActor);
// if the lines text is empty, return a duration of 0
if (Line.Text.IsEmptyOrWhitespace())
{
return 0.f;
}
}
void UDialogue::AdjustPlayerTransform_Implementation()
{
//Only adjust for 1 on 1 dialogue
if (Speakers.Num() == 1 && Speakers.IsValidIndex(0))
{
if (OwningPawn && OwningController)
{
const FTransform PlayerDesiredTransform = PlayerAutoAdjustTransform * Speakers[0].SpeakerAvatarTransform;
OwningPawn->TeleportTo(PlayerDesiredTransform.GetLocation(), PlayerDesiredTransform.GetRotation().Rotator());
OwningController->SetControlRotation(PlayerDesiredTransform.GetRotation().Rotator());
}
}
...
if (DialogueSequencePlayer && DialogueSequencePlayer->SequencePlayer)
{
DialogueSequencePlayer->SequencePlayer->OnFinished.RemoveAll(this);
Sequence->BeginPlaySequence(DialogueSequencePlayer, this, Speaker, Listener);
if (CurrentDialogueSequence)
{
CurrentDialogueSequence->EndSequence();
}
CurrentDialogueSequence = Sequence;
if (CurrentLine.Duration == ELineDuration::LD_WhenSequenceEnds)
{
DialogueSequencePlayer->SequencePlayer->OnFinished.AddDynamic(this, &UDialogue::EndCurrentLine);
if (DialogueSequencePlayer)
{
if (ULevelSequencePlayer* SP = DialogueSequencePlayer->GetSequencePlayer())
{
SP->OnFinished.RemoveAll(this);
if (CurrentDialogueSequence)
{
CurrentDialogueSequence->EndSequence();
}
Sequence->BeginPlaySequence(DialogueSequencePlayer, this, Speaker, Listener);
CurrentDialogueSequence = Sequence;
if (CurrentLine.Duration == ELineDuration::LD_WhenSequenceEnds)
{
SP->OnFinished.AddDynamic(this, &UDialogue::EndCurrentLine);
}
}
}
...
if (DialogueSequencePlayer && DialogueSequencePlayer->SequencePlayer)
{
DialogueSequencePlayer->SequencePlayer->Stop();
/*
If your pawn has a dialogue avatar, narrative hides your pawn as you wouldn't want it to show up in a dialogue.
However a UE5 bug - Stop() will re-show player pawn even if it was already hidden - we want to keep it hidden*/
if (OwningPawn && GetPlayerAvatar() != OwningPawn)
{
OwningPawn->SetActorHiddenInGame(true);
}
}
}
}
if (DialogueSequencePlayer)
{
if (ULevelSequencePlayer* SP = DialogueSequencePlayer->GetSequencePlayer())
{
if (CurrentDialogueSequence)
{
CurrentDialogueSequence->EndSequence();
}
SP->Stop();
/*
If your pawn has a dialogue avatar, narrative hides your pawn as you wouldn't want it to show up in a dialogue.
However a UE5 bug - Stop() will re-show player pawn even if it was already hidden - we want to keep it hidden*/
if (OwningPawn && GetPlayerAvatar() != OwningPawn)
{
OwningPawn->SetActorHiddenInGame(true);
}
}
}
}
}
QuestBlueprintGeneratedClass.cpp
/Narrative/Private/QuestBlueprintGeneratedClass.cpp
#include "QuestBlueprintGeneratedClass.h"
#include "Quest.h"
#include <QuestSM.h>
#include "QuestSM.h"
...
QuestTask.cpp
/Narrative/Private/QuestTask.cpp
#include "Quest.h"
#include "NarrativeComponent.h"
#include <TimerManager.h>
...
/*If we're loading OwningComp may be invalid as BeginTask hasnt cached it yet.
//If we're just loading a save, set the progress but don't bother updating any quest stuff except
/*
//If we're loading a save, set the progress but don't bother updating any quest stuff except
//for on the current state (this is why we also check bIsActive)*/
if (OwningComp->bIsLoading && !bIsActive)
if (OwningComp->bIsLoading)
{
CurrentProgress = FMath::Clamp(NewProgress, 0, RequiredQuantity);
if (!DescriptionOverride.IsEmptyOrWhitespace())
{
return DescriptionOverride;
}
...
#undef LOCTEXT_NAMESPACE// removed-end
#undef LOCTEXT_NAMESPACE
NarrativeNodeBase.cpp
/Narrative/Private/NarrativeNodeBase.cpp
...
return;
}
const bool bIsLoading = NarrativeComponent->bIsLoading;
...
if (Event && (Event->EventRuntime == Runtime || Event->EventRuntime == EEventRuntime::Both))
if (Event)
{
TArray<UNarrativeComponent*> CompsToExecute;
const bool bShouldFire = (!bIsLoading || Event->bRefireOnLoad ) && (Event->EventRuntime == Runtime || Event->EventRuntime == EEventRuntime::Both);
if (UNarrativePartyComponent* PartyComp = Cast<UNarrativePartyComponent>(NarrativeComponent))
if (bShouldFire)
{
if (Event->PartyEventPolicy == EPartyEventPolicy::AllPartyMembers)
TArray<UNarrativeComponent*> CompsToExecute;
if (UNarrativePartyComponent* PartyComp = Cast<UNarrativePartyComponent>(NarrativeComponent))
...
CompsToExecute.Append(PartyComp->GetPartyMembers());
if (Event->PartyEventPolicy == EPartyEventPolicy::AllPartyMembers)
{
CompsToExecute.Append(PartyComp->GetPartyMembers());
}
else if (Event->PartyEventPolicy == EPartyEventPolicy::PartyLeader)
{
CompsToExecute.Add(PartyComp->GetPartyLeader());
}
else if (Event->PartyEventPolicy == EPartyEventPolicy::Party)
{
CompsToExecute.Add(PartyComp);
}
}
else if (Event->PartyEventPolicy == EPartyEventPolicy::PartyLeader)
else
{
CompsToExecute.Add(PartyComp->GetPartyLeader());
CompsToExecute.Add(NarrativeComponent);
}
else if (Event->PartyEventPolicy == EPartyEventPolicy::Party)
for (auto& Comp : CompsToExecute)
...
CompsToExecute.Add(PartyComp);
Event->ExecuteEvent(Comp->GetOwningPawn(), Comp->GetOwningController(), Comp);
}
}
else
{
CompsToExecute.Add(NarrativeComponent);
}
for (auto& Comp : CompsToExecute)
{
Event->ExecuteEvent(Comp->GetOwningPawn(), Comp->GetOwningController(), Comp);
}
}
...
NarrativeDialogueSequence.cpp
/Narrative/Private/NarrativeDialogueSequence.cpp
...
#include "NarrativeDialogueSequence.h"
#include "Dialogue.h"
...
#include "Dialogue.h"
#include <Engine/TargetPoint.h>
static const FName NAME_AnchorTag("Anchor");
static const FName NAME_CinecamTag("Cinecam");
...
if ((LookAtTrackingSettings.bUpdateTrackingEveryFrame || FocusTrackingSettings.bUpdateTrackingEveryFrame) && Dialogue.IsValid() && SequenceActor.IsValid() && SequenceActor->SequencePlayer)
if ((LookAtTrackingSettings.bUpdateTrackingEveryFrame || FocusTrackingSettings.bUpdateTrackingEveryFrame) && Dialogue.IsValid() && SequenceActor.IsValid())
{
if (Cinecam.IsValid())
{
...
InSequenceActor->SequencePlayer->Stop();
if (InSequenceActor)
{
if (ULevelSequencePlayer* SP = InSequenceActor->GetSequencePlayer())
{
//Commented this out as in packaged builds starting the new sequence would cause another stop to be called which caused a crash in UE's sequence player
//SP->Stop();
}
}
AnchorActor = NewAnchorActor;
LookAtActor = NewLookAtActor;
...
void UNarrativeDialogueSequence::EndSequence()
{
//No longer required, moved everything over to weak ptrs
//Speaker = nullptr;
//Listener = nullptr;
//AnchorActor = nullptr;
//LookAtActor = nullptr;
//Dialogue = nullptr;
//SequenceActor = nullptr;
//Cinecam = nullptr;
void UNarrativeDialogueSequence::EndSequence_Implementation()
{
}
void UNarrativeDialogueSequence::PlaySequence_Implementation()
...
if (SelectedSequence)
if (SelectedSequence && SequenceActor.IsValid())
{
SequenceActor->PlaybackSettings = PlaybackSettings;
SequenceActor->SetSequence(SelectedSequence);
...
if (SequenceActor->SequencePlayer)
if (ULevelSequencePlayer* SP = SequenceActor->GetSequencePlayer())
{
SequenceActor->bOverrideInstanceData = AnchorOriginRule != EAnchorOriginRule::AOR_Disabled;
...
SequenceActor->SequencePlayer->Play();
SP->Play();
//Go in, and tell the cinecam to focus/track the speaker
for (auto& BoundObject : SequenceActor->SequencePlayer->GetBoundObjects(SequenceActor->FindNamedBinding(NAME_CinecamTag)))
for (auto& BoundObject : SP->GetBoundObjects(SequenceActor->FindNamedBinding(NAME_CinecamTag)))
{
...
NarrativeFunctionLibrary.cpp
/Narrative/Private/NarrativeFunctionLibrary.cpp
...
#include "NarrativeFunctionLibrary.h"
#include "Kismet/GameplayStatics.h"
#include "Kismet/GameplayStatics.h"
...
NarrativeComponent.cpp
/Narrative/Private/NarrativeComponent.cpp
...
#include "Quest.h"
#include "NarrativeDataTask.h"
#include "QuestSM.h"
#include "DialogueSM.h"
#include "Dialogue.h"
#include "NarrativeCondition.h"
#include "NarrativeEvent.h"
#include "NarrativeDialogueSettings.h"
#include "QuestTask.h"
...
#include "DialogueSM.h"
#include "Dialogue.h"
#include "NarrativeCondition.h"
#include "NarrativeEvent.h"
#include "NarrativeDialogueSettings.h"
#include "QuestTask.h"
DEFINE_LOG_CATEGORY(LogNarrative);
static TAutoConsoleVariable<bool> CVarShowQuestUpdates(
TEXT("narrative.ShowQuestUpdates"),
false,
TEXT("Show updates to any of our quests on screen.\n")
);
...
bool UNarrativeComponent::HasDialogueAvailable(TSubclassOf<class UDialogue> DialogueClass, FName StartFromID /*= NAME_None*/)
bool UNarrativeComponent::HasDialogueAvailable(TSubclassOf<class UDialogue> DialogueClass, const FDialoguePlayParams PlayParams)
{
if (IsValid(DialogueClass))
{
return MakeDialogueInstance(DialogueClass, StartFromID) != nullptr;
}
return false;
}
bool UNarrativeComponent::SetCurrentDialogue(TSubclassOf<class UDialogue> Dialogue, FName StartFromID /*= NAME_None*/)
return MakeDialogueInstance(DialogueClass, PlayParams) != nullptr;
}
return false;
}
bool UNarrativeComponent::SetCurrentDialogue(TSubclassOf<class UDialogue> Dialogue, const FDialoguePlayParams PlayParams)
//Check that our CurrentDialogue's priority isn't lower than the new one
if (UDialogue* NewDialogue = Dialogue->GetDefaultObject<UDialogue>())
{
const int32 NewPriority = PlayParams.Priority != -1 ? PlayParams.Priority : NewDialogue->Priority;
if (NewPriority > CurrentDialogue->Priority)
{
return false;
}
}
...
CurrentDialogue = MakeDialogueInstance(Dialogue, StartFromID);
CurrentDialogue = MakeDialogueInstance(Dialogue, PlayParams);
return CurrentDialogue != nullptr;
}
...
bool UNarrativeComponent::BeginDialogue(TSubclassOf<class UDialogue> DialogueClass, FName StartFromID)
bool UNarrativeComponent::BeginDialogue(TSubclassOf<class UDialogue> DialogueClass, const FDialoguePlayParams PlayParams)
{
if (HasAuthority())
{
...
if (SetCurrentDialogue(DialogueClass, StartFromID))
if (SetCurrentDialogue(DialogueClass, PlayParams))
{
OnDialogueBegan.Broadcast(CurrentDialogue);
{
}
...
check(bSelected);
if (!bSelected)
{
UE_LOG(LogTemp, Warning, TEXT("SelectDialogueOption returned false for option %s"), *GetNameSafe(Option));
}
//check(bSelected);
}
}
}
...
class UDialogue* UNarrativeComponent::MakeDialogueInstance(TSubclassOf<class UDialogue> DialogueClass, FName StartFromID /*= NAME_None*/)
class UDialogue* UNarrativeComponent::MakeDialogueInstance(TSubclassOf<class UDialogue> DialogueClass, const FDialoguePlayParams PlayParams)
{
if (IsValid(DialogueClass))
{
...
if (NewDialogue->Initialize(this, StartFromID))
if (NewDialogue->Initialize(this, PlayParams))
{
return NewDialogue;
}
...
//void UNarrativeComponent::OnRep_CurrentDialogue()
//{
// FString RoleString = HasAuthority() ? "Server" : "Client";
//
// UE_LOG(LogTemp, Warning, TEXT("dialogue %s started on %s"), *GetNameSafe(CurrentDialogue), *RoleString);
//}
//
//void UNarrativeComponent::OnRep_QuestList()
//{
// FString RoleString = HasAuthority() ? "Server" : "Client";
//
// for (auto& Quest : QuestList)
// {
// UE_LOG(LogTemp, Warning, TEXT("Quest on %s: %s"), *RoleString, *GetNameSafe(Quest));
// }
//}
bool UNarrativeComponent::IsQuestValid(const UQuest* Quest, FString& OutError)
{
NarrativeEvent.cpp
/Narrative/Private/NarrativeEvent.cpp
...
#include "NarrativeEvent.h"
bool UNarrativeEvent::ExecuteEvent_Implementation(APawn* Pawn, APlayerController* Controller, class UNarrativeComponent* NarrativeComponent)
UNarrativeEvent::UNarrativeEvent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
...
return true;
bRefireOnLoad = true;
}
void UNarrativeEvent::ExecuteEvent_Implementation(APawn* Pawn, APlayerController* Controller, class UNarrativeComponent* NarrativeComponent)
{
}
FString UNarrativeEvent::GetGraphDisplayText_Implementation()
Dialogue.h
/Narrative/Public/Dialogue.h
#include "UObject/NoExportTypes.h"
#include "LevelSequencePlayer.h"
#include "DialogueSM.h"
#include <MovieSceneSequencePlayer.h>
#include "MovieSceneSequencePlayer.h"
...
SpeakerName = FText::GetEmpty();
NodeColor = FLinearColor(0.036161, 0.115986,0.265625, 1.000000);
DefaultSpeakerShot = nullptr;
bIsPlayer = false;
...
FName GetSpeakerID() const { return SpeakerID; };
public:
UPROPERTY()
bool bIsPlayer;
};
/**Special speaker type created for the player*/
bIsPlayer = true;
};
USTRUCT(BlueprintType)
struct NARRATIVE_API FDialoguePlayParams
{
GENERATED_BODY()
FDialoguePlayParams()
{
StartFromID = NAME_None;
Priority = -1;
};
//The ID the dialogue should start playing from, if empty will play from root node.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Play Params")
FName StartFromID;
//The priority we want to play this dialogue at. -1 means use the dialogues default priority.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Play Params")
int32 Priority;
...
virtual bool Initialize(class UNarrativeComponent* InitializingComp, FName StartFromID);
virtual bool Initialize(class UNarrativeComponent* InitializingComp, const FDialoguePlayParams PlayParams);
virtual void Deinitialize();
virtual void DuplicateAndInitializeFromDialogue(UDialogue* DialogueTemplate);
//Priority. Lower values are more important. If a dialogue attempts to play with a higher priority it will be discarded.
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Configuration")
int32 Priority;
/*
* If enabled, we'll adjust the player to be at PlayerAutoAdjustTransform relative to the other speaker. Only used in 1 on 1 dialogue.
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Adjustment")
bool bAdjustPlayerTransform;
//In 1-on-1 dialogue, we can automatically adjust your players position so they stand the desired amount of units away.
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Adjustment", meta = (EditCondition="bAdjustPlayerTransform", EditConditionHides))
FTransform PlayerAutoAdjustTransform;
//The attenuation to use for dialogue lines
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audio")
class USoundAttenuation* DialogueSoundAttenuation;
* Auto-adjust the player in 1-on-1 dialogue so we're standing in front of them even if we started talking in a different location
*/
UFUNCTION(BlueprintNativeEvent, Category = "Dialogue")
void AdjustPlayerTransform();
virtual void AdjustPlayerTransform_Implementation();
/**
//Play params passed into us
UPROPERTY()
FDialoguePlayParams PlayParams;
...
};
};// added-end
NarrativeEvent.h
/Narrative/Public/NarrativeEvent.h
...
public:
UNarrativeEvent(const FObjectInitializer& ObjectInitializer);
When the game loads back in, should we fire this event off again?
For example, if we used a GiveXP event to give the player 500XP when we get to a certain quest state, this should be false.
Since XP is saved already, quitting and reloading would grant 500XP on top of the previous amount, which is not what we want.
On the other hand, since NPC behavior isn't saved to disk, we want this to be true for all NPC behavior events - this way when your
quest reloads it properly refires the event so your NPCs are ready to go when you come back to your game.
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Event")
bool bRefireOnLoad;
/**
...
bool ExecuteEvent(APawn* Pawn, APlayerController* Controller, class UNarrativeComponent* NarrativeComponent);
virtual bool ExecuteEvent_Implementation(APawn* Pawn, APlayerController* Controller, class UNarrativeComponent* NarrativeComponent);
void ExecuteEvent(APawn* Pawn, APlayerController* Controller, class UNarrativeComponent* NarrativeComponent);
virtual void ExecuteEvent_Implementation(APawn* Pawn, APlayerController* Controller, class UNarrativeComponent* NarrativeComponent);
/**Define the text that will show up on a node if this event is added to it */
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Event")
DialogueSM.h
/Narrative/Public/DialogueSM.h
...
#endif
TArray<class UDialogueNode_NPC*> GetNPCReplies(APlayerController* OwningController, APawn* OwningPawn, class UNarrativeComponent* NarrativeComponent);
class UDialogueNode_NPC* GetFirstValidNPCReply(APlayerController* OwningController, APawn* OwningPawn, class UNarrativeComponent* NarrativeComponent);
UFUNCTION(BlueprintCallable, Category = "NPC Dialogue Node")
FName GetSpeakerID() const {return SpeakerID;};
void SetSpeakerID(const FName& NewID) { SpeakerID = NewID; };
//Sequence to play when player is selecting their reply after this shot has played
UPROPERTY(EditAnywhere, Instanced, BlueprintReadOnly, Category = "Details - NPC Dialogue Node")
class UNarrativeDialogueSequence* SelectingReplyShot;
/**Grab this NPC node, appending all follow up responses to that node. Since multiple NPC replies can be linked together,
we need to grab the chain of replies the NPC has to say. */
TArray<class UDialogueNode_NPC*> GetReplyChain(APlayerController* OwningController, APawn* OwningPawn, class UNarrativeComponent* NarrativeComponent);
protected:
...
//Sequence to play when player is selecting their reply after this shot has played
UPROPERTY(EditAnywhere, Instanced, BlueprintReadOnly, Category = "Details - NPC Dialogue Node")
class UNarrativeDialogueSequence* SelectingReplyShot;
/**Grab this NPC node, appending all follow up responses to that node. Since multiple NPC replies can be linked together,
we need to grab the chain of replies the NPC has to say. */
TArray<class UDialogueNode_NPC*> GetReplyChain(APlayerController* OwningController, APawn* OwningPawn, class UNarrativeComponent* NarrativeComponent);
};
Quest.h
/Narrative/Public/Quest.h
...
protected:
//Called before tasks are ready - a good place to set up data tasks depend on
UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName="Pre Quest Started"))
void BPPreQuestStarted(const UQuest* Quest);
...
void BPOnQuestTaskProgressChanged(const UQuest* Quest, const UNarrativeTask* Task, const class UQuestBranch* Step, int32 CurrentProgress, int32 RequiredProgress);
void BPOnQuestTaskProgressChanged(const UQuest* Quest, const UNarrativeTask* Task, const class UQuestBranch* Step, int32 CurrentProgress, int32 RequiredProgress);
UFUNCTION()
void OnQuestTaskCompleted(const UNarrativeTask* Task, const class UQuestBranch* Branch);
//Tell the quest that it is tracked - by default this will enable the quests navigation markers.
UFUNCTION(BlueprintCallable, Category = "Quest")
virtual void SetTracked(const bool bNewTracked);
UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "On Tracked Changed"))
void BPOnTrackedChanged(const UQuest* Quest, const bool bNewTracked);
//Whether or not the quest is marked as tracked. Use this to show or hide a quest
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Quest Details")
bool bTracked;
UFUNCTION(BlueprintPure, Category = "Quests")
FORCEINLINE bool IsTracked() const { return bTracked;};
...
QuestTask.h
/Narrative/Public/QuestTask.h
...
public:
/**Highly recommended to implement this function! It lets you autogenerate task descriptions that the editor UI and narrative UI will use,
/** Implement this if you want to autogenerate task descriptions that the editor UI and narrative UI will use,
...
/** Optional special version of GetTaskDescription that is used for displaying info the editor nodes.
If you don't implement this function the nodes will just use GetTaskDescription instead. */
/** Optional special version of GetTaskDescription that is used for displaying info the editor nodes,
but won't be used for ingame descriptions. */
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Editor")
FText GetTaskNodeDescription() const;
virtual FText GetTaskNodeDescription_Implementation() const;
DialogueBlueprintGeneratedClass.h
/Narrative/Public/DialogueBlueprintGeneratedClass.h
...
#include "CoreMinimal.h"
#include "Engine/BlueprintGeneratedClass.h"
#include "UObject/Package.h"
...
NarrativePartyComponent.h
/Narrative/Public/NarrativePartyComponent.h
...
public:
virtual bool BeginDialogue(TSubclassOf<class UDialogue> Dialogue, FName StartFromID = NAME_None) override;
virtual bool BeginDialogue(TSubclassOf<class UDialogue> Dialogue, const FDialoguePlayParams PlayParams = FDialoguePlayParams()) override;
...
};
};// added-end
NarrativeComponent.h
/Narrative/Public/NarrativeComponent.h
...
#include "CoreMinimal.h"
#include "UObject/TextProperty.h" //Fixes a build error complaining about incomplete type UTextProperty
#include "Components/ActorComponent.h"
...
//Narrative makes a point to expose everything via delegates so your game can update your UI, or do whatever it needs to do when an update happens.
//General
...
public:
UFUNCTION(BlueprintPure, Category = "Narrative")
...
virtual bool HasDialogueAvailable(TSubclassOf<class UDialogue> Dialogue, FName StartFromID = NAME_None);
virtual bool HasDialogueAvailable(TSubclassOf<class UDialogue> Dialogue, const FDialoguePlayParams PlayParams = FDialoguePlayParams());
/**Sets CurrentDialogue to the given dialogue class, cleaning up our existing dialogue if one is going. Won't actually begin playing the dialogue. */
virtual bool SetCurrentDialogue(TSubclassOf<class UDialogue> Dialogue, FName StartFromID = NAME_None);
virtual bool SetCurrentDialogue(TSubclassOf<class UDialogue> Dialogue, const FDialoguePlayParams PlayParams = FDialoguePlayParams());
...
virtual bool BeginDialogue(TSubclassOf<class UDialogue> Dialogue, FName StartFromID = NAME_None);
virtual bool BeginDialogue(TSubclassOf<class UDialogue> Dialogue, const FDialoguePlayParams PlayParams = FDialoguePlayParams());
/**Used by the server to tell client to start dialogue. Also sends the initial chunk*/
UFUNCTION(Client, Reliable, Category = "Dialogues")
...
virtual class UDialogue* MakeDialogueInstance(TSubclassOf<class UDialogue> DialogueClass, FName StartFromID = NAME_None);
virtual class UDialogue* MakeDialogueInstance(TSubclassOf<class UDialogue> DialogueClass, const FDialoguePlayParams PlayParams = FDialoguePlayParams());
public:
...
};
};// added-end
NarrativeDialogueSequence.h
/Narrative/Public/NarrativeDialogueSequence.h
...
UNarrativeDialogueSequence();
// Allows the Object to get a valid UWorld from it's outer.
virtual UWorld* GetWorld() const override
{
if (HasAllFlags(RF_ClassDefaultObject))
{
// If we are a CDO, we must return nullptr instead of calling Outer->GetWorld() to fool UObject::ImplementsGetWorld.
return nullptr;
}
UObject* Outer = GetOuter();
while (Outer)
{
UWorld* World = Outer->GetWorld();
if (World)
{
return World;
}
Outer = Outer->GetOuter();
}
return nullptr;
}
...
virtual void EndSequence();
FORCEINLINE TArray<class ULevelSequence*> GetSequenceAssets() const { return SequenceAssets;}
FORCEINLINE FMovieSceneSequencePlaybackSettings GetPlaybackSettings() const {return PlaybackSettings;}
//Called before the shot is stopped and its sequence player is de-initialized.
UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, Category = "Dialogue Sequence")
void OnStop();
/** Plays the level sequence. Pretty rare you'd ever want to override this in BP but the option is there! */
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Dialogue Sequence")
void EndSequence();
virtual void EndSequence_Implementation();
...