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 "SpeechDataAsset.h"
#include "Kismet/GameplayStatics.h"
#include "NPC/NpcBaseCharacter.h"
#include "ProjectEleri/GameEventSystem/GameEventSubsystem.h"
#include "QuestSystem/QuestSubsystem.h"
@@ -58,27 +59,16 @@ void UDialogueComponent::StartDialogueWithNpc()
UDialogueSubsystem* DialogueSubsystem = UDialogueSubsystem::Get(this);
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 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.
if (!DialogueSubsystem->StartDialogue(GetOwner())) return;
MappedDialogueOptions.Empty();
FilterSpeechData();
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);
TriggerDialogue(NpcBaseCharacter->GetStartingSpeech().Get(), CurrentDialogueReplacementMap);
}
void UDialogueComponent::TriggerDialogue_Async(TSoftObjectPtr<USpeechDataAsset> DialogueAssetPath, const TMap<FString, FText>& ReplacementMap)
@@ -111,16 +101,17 @@ void UDialogueComponent::TriggerDialogue(USpeechDataAsset* DialogueAssetPath, co
CurrentDialogueReplacementMap = ReplacementMap;
DialogueWidget->DisplayDialogue(CurrentActiveDialogue, GetOwner(), CurrentDialogueReplacementMap);
if (DialogueAssetPath->RandomizeLines && !DialogueAssetPath->PlayerSpeechMap.IsEmpty())
if (!DialogueAssetPath->PlayerSpeechMap.IsEmpty())
{
MappedDialogueOptions.Empty();
TArray<FText> PlayerSpeakOptions;
for (int32 i = 0; i <= CurrentActiveDialogue->PlayerSpeechMap.Num() - 1; i++)
{
if (!CurrentActiveDialogue->PlayerSpeechMap[i].PassesRequirements(this)) continue;
MappedDialogueOptions.Add(i, CurrentActiveDialogue->PlayerSpeechMap[i]);
PlayerSpeakOptions.Add(CurrentActiveDialogue->PlayerSpeechMap[i].PlayerSpeak);
}
//TriggerDialogueSelect(PlayerSpeakOptions, false);
}
DialogueSubsystem->StartDialogue(GetOwner());
@@ -200,7 +191,7 @@ void UDialogueComponent::AdvanceDialogue()
{
for (const FGameplayTag& QuestTag : CurrentActiveDialogue->StartQuests)
{
UQuestSubsystem::Get(this)->OfferQuest(QuestTag);
UQuestSubsystem::Get(this)->StartQuest(QuestTag);
}
}
@@ -217,11 +208,16 @@ void UDialogueComponent::AdvanceDialogue()
TArray<FText> PlayerSpeakOptions;
for (int32 i = 0; i <= CurrentActiveDialogue->PlayerSpeechMap.Num() - 1; i++)
{
if (!CurrentActiveDialogue->PlayerSpeechMap[i].PassesRequirements(this)) continue;
MappedDialogueOptions.Add(i, CurrentActiveDialogue->PlayerSpeechMap[i]);
PlayerSpeakOptions.Add(CurrentActiveDialogue->PlayerSpeechMap[i].PlayerSpeak);
}
TriggerDialogueSelect(PlayerSpeakOptions, false);
return;
if (!PlayerSpeakOptions.IsEmpty())
{
TriggerDialogueSelect(PlayerSpeakOptions, false);
return;
}
}
}

View File

@@ -5,26 +5,74 @@
#include <ProjectEleri/GameEventSystem/GameEventSubsystem.h>
#include <Kismet/GameplayStatics.h>
#include "InventoryComponent.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 (UGameEventSubsystem* GameEventSubsystem = GameInstance->GetSubsystem<UGameEventSubsystem>())
switch (RequirementType)
{
for (const FEventRequirement& EventReq : EventsMustMeetRequirement)
{
if (EventReq.Stage != GameEventSubsystem->GetEventStage(EventReq.EventAsset))
return false;
}
case EEventRequirementType::Event:
{
const UGameEventSubsystem* GameEventSubsystem = GameInstance->GetSubsystem<UGameEventSubsystem>();
if (!GameEventSubsystem) return false;
for (const FEventRequirement& EventReq : EventsMustNotMeetRequirement)
{
if (EventReq.Stage == GameEventSubsystem->GetEventStage(EventReq.EventAsset))
return false;
}
const EEventStage EventStage = GameEventSubsystem->GetEventStage(EventAsset);
if (!bMustAchieveEvent && EventStage != EEventStage::NONE)
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;
}
return true;
}
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;

View File

@@ -5,23 +5,50 @@
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "ProjectEleri/GameEventSystem/GameEventDataAsset.h"
#include "QuestSystem/QuestTypes.h"
#include "SpeechDataAsset.generated.h"
struct FLineData;
class USpeechDataAsset;
UENUM(BlueprintType)
enum class EEventRequirementType : uint8
{
Event,
Quest,
Item,
Max UMETA(Hidden)
};
USTRUCT(BlueprintType)
struct FEventRequirement
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, meta = (MetaClass = "/Script/ProjectEleri.GameEventDataAsset"))
FSoftObjectPath EventAsset;
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;
// 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)
@@ -37,6 +64,10 @@ public:
TSoftObjectPtr<USpeechDataAsset> NextSpeechDataAsset;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (ForceInlineRow, Categories = "Dialogue|Event"))
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:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Eleri|Event Requirment")
TArray<FEventRequirement> EventsMustMeetRequirement;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Eleri|Event Requirment")
TArray<FEventRequirement> EventsMustNotMeetRequirement;
TArray<FEventRequirement> EventRequirements;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Eleri|Speech")
TSoftClassPtr<AActor> Npc;
@@ -73,7 +101,7 @@ public:
TArray<FPlayerSpeakToDialogue> PlayerSpeechMap;
UFUNCTION()
bool TestRequirements(UWorld* World);
bool TestRequirements(const UWorld* World);
UFUNCTION(BlueprintCallable, Category = "Eleri|Options")
FText GetFormattedDialogueLine(const int32 Index, const TMap<FString, FText> ReplacementMap);

View File

@@ -9,6 +9,7 @@
#include "ProjectEleri/NPC/ExtendedPawnMovement.h"
#include "ProjectEleri/NPC/NpcStateMachineComponent.h"
#include "ProjectEleri/Public/InventoryComponent.h"
#include "ProjectEleri/DialogueSystem/SpeechDataAsset.h"
ANpcBaseCharacter::ANpcBaseCharacter()
{
@@ -78,10 +79,7 @@ void ANpcBaseCharacter::BeginPlay()
{
Super::BeginPlay();
// There is nothing to start here. The state machine waits to be told what to do by the schedule
// 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.
NpcCharacterData.StartingSpeech.LoadSynchronous();
}
void ANpcBaseCharacter::ToggleInteractableUI_Implementation(bool bActive)

View File

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

View File

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

View File

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