Starting dialogue and requirments implemented

This commit is contained in:
2026-10-05 10:17:27 +02:00
parent f2115c537b
commit b3f4eefe06
11 changed files with 136 additions and 55 deletions

Binary file not shown.

View File

@@ -9,6 +9,7 @@
#include "Speech.h" #include "Speech.h"
#include "SpeechDataAsset.h" #include "SpeechDataAsset.h"
#include "Kismet/GameplayStatics.h" #include "Kismet/GameplayStatics.h"
#include "NPC/NpcBaseCharacter.h"
#include "ProjectEleri/GameEventSystem/GameEventSubsystem.h" #include "ProjectEleri/GameEventSystem/GameEventSubsystem.h"
#include "QuestSystem/QuestSubsystem.h" #include "QuestSystem/QuestSubsystem.h"
@@ -58,27 +59,16 @@ void UDialogueComponent::StartDialogueWithNpc()
UDialogueSubsystem* DialogueSubsystem = UDialogueSubsystem::Get(this); UDialogueSubsystem* DialogueSubsystem = UDialogueSubsystem::Get(this);
ensure(DialogueSubsystem); ensure(DialogueSubsystem);
const ANpcBaseCharacter* NpcBaseCharacter = Cast<ANpcBaseCharacter>(GetOwner());
if (!NpcBaseCharacter || !NpcBaseCharacter->GetStartingSpeech().IsValid()) return;
// The NPC goes in with it, so that whoever is being talked to can be told apart from the rest of // The NPC goes in with it, so that whoever is being talked to can be told apart from the rest of
// the town. This component lives on the actor the player talks to, which makes GetOwner the // the town. This component lives on the actor the player talks to, which makes GetOwner the
// answer, and it is the single funnel every dialogue goes through. // answer, and it is the single funnel every dialogue goes through.
if (!DialogueSubsystem->StartDialogue(GetOwner())) return; if (!DialogueSubsystem->StartDialogue(GetOwner())) return;
MappedDialogueOptions.Empty(); MappedDialogueOptions.Empty();
FilterSpeechData(); TriggerDialogue(NpcBaseCharacter->GetStartingSpeech().Get(), CurrentDialogueReplacementMap);
TArray<USpeechDataAsset*> StartingDialogues = GetStartingDialogues();
if (StartingDialogues.IsEmpty()) return;
TriggerDialogue(StartingDialogues[FMath::RandRange(0, StartingDialogues.Num() - 1)], CurrentDialogueReplacementMap);
TArray<USpeechDataAsset*> ActiveDialogues = GetActiveDialogues();
TArray<FText> PlayerAsks = GetPlayerAsksFromDialogueList(ActiveDialogues);
for (int32 i = 0; i < ActiveDialogues.Num() - 1; i++)
{
FPlayerSpeakToDialogue SpeakToDialogue;
SpeakToDialogue.NextSpeechDataAsset = ActiveDialogues[i];
MappedDialogueOptions.Add(i, SpeakToDialogue);
}
TriggerDialogueSelect(PlayerAsks, true);
} }
void UDialogueComponent::TriggerDialogue_Async(TSoftObjectPtr<USpeechDataAsset> DialogueAssetPath, const TMap<FString, FText>& ReplacementMap) void UDialogueComponent::TriggerDialogue_Async(TSoftObjectPtr<USpeechDataAsset> DialogueAssetPath, const TMap<FString, FText>& ReplacementMap)
@@ -111,16 +101,17 @@ void UDialogueComponent::TriggerDialogue(USpeechDataAsset* DialogueAssetPath, co
CurrentDialogueReplacementMap = ReplacementMap; CurrentDialogueReplacementMap = ReplacementMap;
DialogueWidget->DisplayDialogue(CurrentActiveDialogue, GetOwner(), CurrentDialogueReplacementMap); DialogueWidget->DisplayDialogue(CurrentActiveDialogue, GetOwner(), CurrentDialogueReplacementMap);
if (DialogueAssetPath->RandomizeLines && !DialogueAssetPath->PlayerSpeechMap.IsEmpty()) if (!DialogueAssetPath->PlayerSpeechMap.IsEmpty())
{ {
MappedDialogueOptions.Empty(); MappedDialogueOptions.Empty();
TArray<FText> PlayerSpeakOptions; TArray<FText> PlayerSpeakOptions;
for (int32 i = 0; i <= CurrentActiveDialogue->PlayerSpeechMap.Num() - 1; i++) for (int32 i = 0; i <= CurrentActiveDialogue->PlayerSpeechMap.Num() - 1; i++)
{ {
if (!CurrentActiveDialogue->PlayerSpeechMap[i].PassesRequirements(this)) continue;
MappedDialogueOptions.Add(i, CurrentActiveDialogue->PlayerSpeechMap[i]); MappedDialogueOptions.Add(i, CurrentActiveDialogue->PlayerSpeechMap[i]);
PlayerSpeakOptions.Add(CurrentActiveDialogue->PlayerSpeechMap[i].PlayerSpeak); PlayerSpeakOptions.Add(CurrentActiveDialogue->PlayerSpeechMap[i].PlayerSpeak);
} }
//TriggerDialogueSelect(PlayerSpeakOptions, false);
} }
DialogueSubsystem->StartDialogue(GetOwner()); DialogueSubsystem->StartDialogue(GetOwner());
@@ -200,7 +191,7 @@ void UDialogueComponent::AdvanceDialogue()
{ {
for (const FGameplayTag& QuestTag : CurrentActiveDialogue->StartQuests) for (const FGameplayTag& QuestTag : CurrentActiveDialogue->StartQuests)
{ {
UQuestSubsystem::Get(this)->OfferQuest(QuestTag); UQuestSubsystem::Get(this)->StartQuest(QuestTag);
} }
} }
@@ -217,13 +208,18 @@ void UDialogueComponent::AdvanceDialogue()
TArray<FText> PlayerSpeakOptions; TArray<FText> PlayerSpeakOptions;
for (int32 i = 0; i <= CurrentActiveDialogue->PlayerSpeechMap.Num() - 1; i++) for (int32 i = 0; i <= CurrentActiveDialogue->PlayerSpeechMap.Num() - 1; i++)
{ {
if (!CurrentActiveDialogue->PlayerSpeechMap[i].PassesRequirements(this)) continue;
MappedDialogueOptions.Add(i, CurrentActiveDialogue->PlayerSpeechMap[i]); MappedDialogueOptions.Add(i, CurrentActiveDialogue->PlayerSpeechMap[i]);
PlayerSpeakOptions.Add(CurrentActiveDialogue->PlayerSpeechMap[i].PlayerSpeak); PlayerSpeakOptions.Add(CurrentActiveDialogue->PlayerSpeechMap[i].PlayerSpeak);
} }
if (!PlayerSpeakOptions.IsEmpty())
{
TriggerDialogueSelect(PlayerSpeakOptions, false); TriggerDialogueSelect(PlayerSpeakOptions, false);
return; return;
} }
} }
}
CloseDialogue(); CloseDialogue();
DialogueSubsystem->OnDialogueEnded.Broadcast(); DialogueSubsystem->OnDialogueEnded.Broadcast();

View File

@@ -5,26 +5,74 @@
#include <ProjectEleri/GameEventSystem/GameEventSubsystem.h> #include <ProjectEleri/GameEventSystem/GameEventSubsystem.h>
#include <Kismet/GameplayStatics.h> #include <Kismet/GameplayStatics.h>
#include "InventoryComponent.h"
#include "Speech.h" #include "Speech.h"
#include "QuestSystem/QuestSubsystem.h"
bool USpeechDataAsset::TestRequirements(UWorld* World) bool FEventRequirement::TestRequirement(const UObject* WorldContext) const
{ {
UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(World); const UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(WorldContext);
if (!GameInstance) return false; if (!GameInstance) return false;
if (UGameEventSubsystem* GameEventSubsystem = GameInstance->GetSubsystem<UGameEventSubsystem>()) switch (RequirementType)
{ {
for (const FEventRequirement& EventReq : EventsMustMeetRequirement) case EEventRequirementType::Event:
{ {
if (EventReq.Stage != GameEventSubsystem->GetEventStage(EventReq.EventAsset)) const UGameEventSubsystem* GameEventSubsystem = GameInstance->GetSubsystem<UGameEventSubsystem>();
if (!GameEventSubsystem) return false;
const EEventStage EventStage = GameEventSubsystem->GetEventStage(EventAsset);
if (!bMustAchieveEvent && EventStage != EEventStage::NONE)
return false; return false;
if (bMustAchieveEvent && (GameEventSubsystem->GetEventStage(EventAsset) < Stage))
return false;
return true;
}
case EEventRequirementType::Quest:
{
const UQuestSubsystem* QuestSubsystem = GameInstance->GetSubsystem<UQuestSubsystem>();
if (!QuestSubsystem) return false;
const EQuestStatus CurrentQuestStatus = QuestSubsystem->GetQuestStatus(QuestTag);
const bool bMeetsReq = CurrentQuestStatus >= QuestStatus;
return bMeetsReq == bEqual;
}
case EEventRequirementType::Item:
{
const UInventoryComponent* InventoryComponent = UMainBlueprintFunctionLibrary::GetPlayerInventory(WorldContext);
if (!InventoryComponent) return false;
if (InventoryComponent->GetStackForItem(Item) < Stacks)
return false;
return true;
}
case EEventRequirementType::Max:
break;
} }
for (const FEventRequirement& EventReq : EventsMustNotMeetRequirement) return true;
{
if (EventReq.Stage == GameEventSubsystem->GetEventStage(EventReq.EventAsset))
return false;
} }
bool FPlayerSpeakToDialogue::PassesRequirements(const UObject* WorldContext) const
{
for (const FEventRequirement& Requirement : EventRequirements)
{
if (!Requirement.TestRequirement(WorldContext)) return false;
}
return true;
}
bool USpeechDataAsset::TestRequirements(const UWorld* World)
{
for (const FEventRequirement& EventReq : EventRequirements)
{
if (!EventReq.TestRequirement(World))
return false;
} }
return true; return true;

View File

@@ -5,23 +5,50 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "Engine/DataAsset.h" #include "Engine/DataAsset.h"
#include "ProjectEleri/GameEventSystem/GameEventDataAsset.h" #include "ProjectEleri/GameEventSystem/GameEventDataAsset.h"
#include "QuestSystem/QuestTypes.h"
#include "SpeechDataAsset.generated.h" #include "SpeechDataAsset.generated.h"
struct FLineData; struct FLineData;
class USpeechDataAsset; class USpeechDataAsset;
UENUM(BlueprintType)
enum class EEventRequirementType : uint8
{
Event,
Quest,
Item,
Max UMETA(Hidden)
};
USTRUCT(BlueprintType) USTRUCT(BlueprintType)
struct FEventRequirement struct FEventRequirement
{ {
GENERATED_BODY() GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, meta = (MetaClass = "/Script/ProjectEleri.GameEventDataAsset"))
FSoftObjectPath EventAsset;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite) UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
EEventRequirementType RequirementType;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, meta = (EditCondition = "RequirementType == EEventRequirementType::Event", EditConditionHides, MetaClass = "/Script/ProjectEleri.GameEventDataAsset"))
FSoftObjectPath EventAsset;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, meta = (EditCondition = "RequirementType == EEventRequirementType::Event", EditConditionHides))
EEventStage Stage; EEventStage Stage;
// if this is false the event must not be unlocked
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, meta = (EditCondition = "RequirementType == EEventRequirementType::Event", EditConditionHides))
bool bMustAchieveEvent = true;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, meta = (EditCondition = "RequirementType == EEventRequirementType::Quest", EditConditionHides, GameplayTagFilter = "Quests"))
FGameplayTag QuestTag;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, meta = (EditCondition = "RequirementType == EEventRequirementType::Quest", EditConditionHides, GameplayTagFilter = "Quests"))
EQuestStatus QuestStatus;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, meta = (EditCondition = "RequirementType == EEventRequirementType::Quest", EditConditionHides, GameplayTagFilter = "Quests"))
bool bEqual = true;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, meta = (EditCondition = "RequirementType == EEventRequirementType::Item", EditConditionHides, Categories = "Items"))
FPrimaryAssetId Item;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, meta = (EditCondition = "RequirementType == EEventRequirementType::Item", EditConditionHides))
int32 Stacks = 1;
bool TestRequirement(const UObject* WorldContext) const;
}; };
USTRUCT(BlueprintType) USTRUCT(BlueprintType)
@@ -37,6 +64,10 @@ public:
TSoftObjectPtr<USpeechDataAsset> NextSpeechDataAsset; TSoftObjectPtr<USpeechDataAsset> NextSpeechDataAsset;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (ForceInlineRow, Categories = "Dialogue|Event")) UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (ForceInlineRow, Categories = "Dialogue|Event"))
FGameplayTag GameplayTag; FGameplayTag GameplayTag;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Eleri|Event Requirment")
TArray<FEventRequirement> EventRequirements;
bool PassesRequirements(const UObject* WorldContext) const;
}; };
/** /**
@@ -49,10 +80,7 @@ class PROJECTELERI_API USpeechDataAsset : public UDataAsset
public: public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Eleri|Event Requirment") UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Eleri|Event Requirment")
TArray<FEventRequirement> EventsMustMeetRequirement; TArray<FEventRequirement> EventRequirements;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Eleri|Event Requirment")
TArray<FEventRequirement> EventsMustNotMeetRequirement;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Eleri|Speech") UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Eleri|Speech")
TSoftClassPtr<AActor> Npc; TSoftClassPtr<AActor> Npc;
@@ -73,7 +101,7 @@ public:
TArray<FPlayerSpeakToDialogue> PlayerSpeechMap; TArray<FPlayerSpeakToDialogue> PlayerSpeechMap;
UFUNCTION() UFUNCTION()
bool TestRequirements(UWorld* World); bool TestRequirements(const UWorld* World);
UFUNCTION(BlueprintCallable, Category = "Eleri|Options") UFUNCTION(BlueprintCallable, Category = "Eleri|Options")
FText GetFormattedDialogueLine(const int32 Index, const TMap<FString, FText> ReplacementMap); FText GetFormattedDialogueLine(const int32 Index, const TMap<FString, FText> ReplacementMap);

View File

@@ -9,6 +9,7 @@
#include "ProjectEleri/NPC/ExtendedPawnMovement.h" #include "ProjectEleri/NPC/ExtendedPawnMovement.h"
#include "ProjectEleri/NPC/NpcStateMachineComponent.h" #include "ProjectEleri/NPC/NpcStateMachineComponent.h"
#include "ProjectEleri/Public/InventoryComponent.h" #include "ProjectEleri/Public/InventoryComponent.h"
#include "ProjectEleri/DialogueSystem/SpeechDataAsset.h"
ANpcBaseCharacter::ANpcBaseCharacter() ANpcBaseCharacter::ANpcBaseCharacter()
{ {
@@ -78,10 +79,7 @@ void ANpcBaseCharacter::BeginPlay()
{ {
Super::BeginPlay(); Super::BeginPlay();
// There is nothing to start here. The state machine waits to be told what to do by the schedule NpcCharacterData.StartingSpeech.LoadSynchronous();
// component, which is what keeps an NPC the player never goes near from doing any work at all, and
// an NPC with no schedule component simply stands where it was placed because nothing ever tells
// it about an assignment.
} }
void ANpcBaseCharacter::ToggleInteractableUI_Implementation(bool bActive) void ANpcBaseCharacter::ToggleInteractableUI_Implementation(bool bActive)

View File

@@ -9,6 +9,7 @@
#include "Interface/InteractableActorInterface.h" #include "Interface/InteractableActorInterface.h"
#include "NpcBaseCharacter.generated.h" #include "NpcBaseCharacter.generated.h"
class USpeechDataAsset;
class UCapsuleComponent; class UCapsuleComponent;
class UEleriFlipbookComponent; class UEleriFlipbookComponent;
class UExtendedPawnMovement; class UExtendedPawnMovement;
@@ -26,6 +27,9 @@ struct FNpcCharacterData
UPROPERTY(EditAnywhere, BlueprintReadWrite) UPROPERTY(EditAnywhere, BlueprintReadWrite)
FText NpcName; FText NpcName;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TSoftObjectPtr<USpeechDataAsset> StartingSpeech;
}; };
/** /**
@@ -78,6 +82,7 @@ public:
UNpcStateMachineComponent* GetNpcStateMachine() const { return StateMachineComponent; } UNpcStateMachineComponent* GetNpcStateMachine() const { return StateMachineComponent; }
virtual void ToggleInteractableUI_Implementation(bool bActive) override; virtual void ToggleInteractableUI_Implementation(bool bActive) override;
TSoftObjectPtr<USpeechDataAsset> GetStartingSpeech() const { return NpcCharacterData.StartingSpeech; }
protected: protected:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Components") UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Components")

View File

@@ -36,7 +36,7 @@ void UInventoryComponent::BeginPlay()
InitializeInventory(); InitializeInventory();
} }
int32 UInventoryComponent::HasItem(FPrimaryAssetId ItemId, bool HQ) int32 UInventoryComponent::HasItem(FPrimaryAssetId ItemId, bool HQ) const
{ {
for (int32 i = 0; i < ItemList.Num(); i++) for (int32 i = 0; i < ItemList.Num(); i++)
{ {
@@ -131,7 +131,7 @@ void UInventoryComponent::EmptyInventory()
OnInventoryUpdated.Broadcast(); OnInventoryUpdated.Broadcast();
} }
int32 UInventoryComponent::GetStackForItem(FPrimaryAssetId ItemId, bool HQ) int32 UInventoryComponent::GetStackForItem(FPrimaryAssetId ItemId, bool HQ) const
{ {
for (int32 i = 0; i < ItemList.Num(); i++) for (int32 i = 0; i < ItemList.Num(); i++)
{ {

View File

@@ -129,10 +129,10 @@ public:
void EmptyInventory(); void EmptyInventory();
UFUNCTION(BlueprintCallable) UFUNCTION(BlueprintCallable)
int32 GetStackForItem(FPrimaryAssetId ItemId, bool HQ = false); int32 GetStackForItem(FPrimaryAssetId ItemId, bool HQ = false) const;
UFUNCTION(BlueprintCallable) UFUNCTION(BlueprintCallable)
int32 HasItem(FPrimaryAssetId ItemToCheck, bool HQ = false); int32 HasItem(FPrimaryAssetId ItemToCheck, bool HQ = false) const;
UFUNCTION(BlueprintCallable) UFUNCTION(BlueprintCallable)
TArray<FPrimaryAssetId> GetItemAssetIds(); TArray<FPrimaryAssetId> GetItemAssetIds();