diff --git a/Content/Character/BP_Npc_Test.uasset b/Content/Character/BP_Npc_Test.uasset index 0369f7ee..d9cfc990 100644 --- a/Content/Character/BP_Npc_Test.uasset +++ b/Content/Character/BP_Npc_Test.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:02a1dc17d97fd34d8081e817b6348e4ea311170d46f6ea576fc75cbd109afab4 -size 36914 +oid sha256:e786950b06f6e4f5412005ddf85156af9e79662c059641a9bfb316ff9c99d922 +size 39106 diff --git a/Content/GameData/Dialogue/Characters/DA_Test_Speech_1.uasset b/Content/GameData/Dialogue/Characters/DA_Test_Speech_1.uasset index 72b9ab47..a0d78a12 100644 --- a/Content/GameData/Dialogue/Characters/DA_Test_Speech_1.uasset +++ b/Content/GameData/Dialogue/Characters/DA_Test_Speech_1.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:93809934f425099234564f15007bdbfdf8ea9d6f953eb559c99b64c7741e0195 -size 2588 +oid sha256:59d323ccac47e3a64850c1a68c956e87c49a8eff68c9ef1b396c41d8d6fd83bc +size 1459 diff --git a/Content/GameData/Dialogue/Characters/Test/DA_Test_Speech_1.uasset b/Content/GameData/Dialogue/Characters/Test/DA_Test_Speech_1.uasset new file mode 100644 index 00000000..a5631077 --- /dev/null +++ b/Content/GameData/Dialogue/Characters/Test/DA_Test_Speech_1.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ce6fd8ab43828d90728676674593e21d2b576ec64f94e9e300dee68129bf3b4 +size 2603 diff --git a/Content/GameData/Dialogue/Characters/Test/DA_Test_StartingDialogue.uasset b/Content/GameData/Dialogue/Characters/Test/DA_Test_StartingDialogue.uasset new file mode 100644 index 00000000..96efe177 --- /dev/null +++ b/Content/GameData/Dialogue/Characters/Test/DA_Test_StartingDialogue.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f12f3746459ad8322176ee8fc157bb1363f4c0145d14f95c6c3487365330606e +size 4587 diff --git a/Source/ProjectEleri/DialogueSystem/DialogueComponent.cpp b/Source/ProjectEleri/DialogueSystem/DialogueComponent.cpp index 93437060..046d2979 100644 --- a/Source/ProjectEleri/DialogueSystem/DialogueComponent.cpp +++ b/Source/ProjectEleri/DialogueSystem/DialogueComponent.cpp @@ -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(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 StartingDialogues = GetStartingDialogues(); - if (StartingDialogues.IsEmpty()) return; - - TriggerDialogue(StartingDialogues[FMath::RandRange(0, StartingDialogues.Num() - 1)], CurrentDialogueReplacementMap); - - TArray ActiveDialogues = GetActiveDialogues(); - TArray 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 DialogueAssetPath, const TMap& 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 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 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; + } } } diff --git a/Source/ProjectEleri/DialogueSystem/SpeechDataAsset.cpp b/Source/ProjectEleri/DialogueSystem/SpeechDataAsset.cpp index e6b24a13..4101d02f 100644 --- a/Source/ProjectEleri/DialogueSystem/SpeechDataAsset.cpp +++ b/Source/ProjectEleri/DialogueSystem/SpeechDataAsset.cpp @@ -5,26 +5,74 @@ #include #include +#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()) + switch (RequirementType) { - for (const FEventRequirement& EventReq : EventsMustMeetRequirement) - { - if (EventReq.Stage != GameEventSubsystem->GetEventStage(EventReq.EventAsset)) - return false; - } + case EEventRequirementType::Event: + { + const UGameEventSubsystem* GameEventSubsystem = GameInstance->GetSubsystem(); + if (!GameEventSubsystem) 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(); + 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) - { - if (EventReq.Stage == GameEventSubsystem->GetEventStage(EventReq.EventAsset)) - return false; - } + 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; diff --git a/Source/ProjectEleri/DialogueSystem/SpeechDataAsset.h b/Source/ProjectEleri/DialogueSystem/SpeechDataAsset.h index 0c120895..03534028 100644 --- a/Source/ProjectEleri/DialogueSystem/SpeechDataAsset.h +++ b/Source/ProjectEleri/DialogueSystem/SpeechDataAsset.h @@ -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 NextSpeechDataAsset; UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (ForceInlineRow, Categories = "Dialogue|Event")) FGameplayTag GameplayTag; + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Eleri|Event Requirment") + TArray 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 EventsMustMeetRequirement; - - UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Eleri|Event Requirment") - TArray EventsMustNotMeetRequirement; + TArray EventRequirements; UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Eleri|Speech") TSoftClassPtr Npc; @@ -73,7 +101,7 @@ public: TArray PlayerSpeechMap; UFUNCTION() - bool TestRequirements(UWorld* World); + bool TestRequirements(const UWorld* World); UFUNCTION(BlueprintCallable, Category = "Eleri|Options") FText GetFormattedDialogueLine(const int32 Index, const TMap ReplacementMap); diff --git a/Source/ProjectEleri/NPC/NpcBaseCharacter.cpp b/Source/ProjectEleri/NPC/NpcBaseCharacter.cpp index 8823adda..fc810df3 100644 --- a/Source/ProjectEleri/NPC/NpcBaseCharacter.cpp +++ b/Source/ProjectEleri/NPC/NpcBaseCharacter.cpp @@ -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) diff --git a/Source/ProjectEleri/NPC/NpcBaseCharacter.h b/Source/ProjectEleri/NPC/NpcBaseCharacter.h index b9c16453..3d7070ce 100644 --- a/Source/ProjectEleri/NPC/NpcBaseCharacter.h +++ b/Source/ProjectEleri/NPC/NpcBaseCharacter.h @@ -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 StartingSpeech; }; /** @@ -78,6 +82,7 @@ public: UNpcStateMachineComponent* GetNpcStateMachine() const { return StateMachineComponent; } virtual void ToggleInteractableUI_Implementation(bool bActive) override; + TSoftObjectPtr GetStartingSpeech() const { return NpcCharacterData.StartingSpeech; } protected: UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Components") diff --git a/Source/ProjectEleri/Private/InventoryComponent.cpp b/Source/ProjectEleri/Private/InventoryComponent.cpp index bebcbb64..38a54d01 100644 --- a/Source/ProjectEleri/Private/InventoryComponent.cpp +++ b/Source/ProjectEleri/Private/InventoryComponent.cpp @@ -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++) { diff --git a/Source/ProjectEleri/Public/InventoryComponent.h b/Source/ProjectEleri/Public/InventoryComponent.h index 83d1863d..a133052f 100644 --- a/Source/ProjectEleri/Public/InventoryComponent.h +++ b/Source/ProjectEleri/Public/InventoryComponent.h @@ -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 GetItemAssetIds();