Files
ProjectEleri/Source/ProjectEleri/QuestSystem/QuestDataAsset.h

138 lines
6.7 KiB
C++

// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "GameplayTagContainer.h"
#include "QuestTypes.h"
#include "QuestDataAsset.generated.h"
class ULevelSequence;
class UTexture2D;
/**
* One quest, authored as an asset.
*
* A quest is a tag, a list of steps and a list of things that have to be true before it can be taken.
* Everything else about it, the journal entry, the rewards and the conditions on individual steps, hangs
* off those three, so a quest with no code of its own is a data asset drop and one line of dialogue.
*
* Steps are not a state machine. Each one listens to whatever the game already broadcasts for it, so an
* InteractWith step is satisfied by the same interaction the player performs to open a chest, and two
* quests watching the same NPC both count the same conversation without either of them owning it.
*/
UCLASS(BlueprintType, PrioritizeCategories = ("Quest", "Objectives", "Requirements", "Rewards"))
class PROJECTELERI_API UQuestDataAsset : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
/** Identity of the quest. Every lookup, every requirement and every save entry goes through this
* tag, so it is the one field that has to be filled in. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Quest", meta = (GameplayTagFilter = "Quests"))
FGameplayTag QuestTag;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Quest")
FText QuestName;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Quest", meta = (MultiLine = true))
FText QuestDescription;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Quest")
TSoftObjectPtr<UTexture2D> QuestIcon;
/** The NPC this quest is handed out by. The journal uses it to say who to go back to, and a
* dialogue can ask "do you have anything for me" through it. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Quest", meta = (ForceInlineRow, Categories = "NpcTags"))
FGameplayTag QuestGiverTag;
/** Days the player has to hand the quest in. 0 means it never runs out. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Quest", meta = (ClampMin = 0))
int32 DeadlineInDays = 0;
/**
* Cutscenes the quest plays as it changes stage, keyed by the status it moves to.
*
* A stage a quest enters is a status, so this is what makes a quest able to show its own beat
* without any blueprint being involved: the scene under Completed plays the moment the quest is
* handed in, and the one under Failed plays when a deadline runs out, which is what makes failing a
* quest something the player sees rather than something that happens to the journal.
*
* The scenes go through UDialogueSceneSubsystem, which is what holds the player still for the
* length of the sequence and gives the controls back the moment it stops. One scene at a time is
* played, so a quest that changes stage twice in a single frame shows the first scene it asked for
* and stays quiet about the second.
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Cutscene")
TMap<EQuestStatus, TSoftObjectPtr<ULevelSequence>> StatusScenes;
/** The steps, in the order the journal lists them. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Objectives", meta = (TitleProperty = "Description"))
TArray<FQuestObjective> Objectives;
/** When set, a step starts listening only once the step before it is done. Off by default, so a
* quest that asks for three separate things reads as one list of three rather than as a chain. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Objectives")
bool bSequentialObjectives = false;
/** The items a DeliverItems step asks for are taken out of the inventory at the moment the quest
* is handed in, which is what makes a delivery a delivery. Turn it off for a quest that only
* wants to see the item and give it back. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Objectives")
bool bConsumeDeliveredItems = true;
/** Everything that has to be true before this quest can be offered. A prerequisite quest is one of
* these rather than a field of its own: "the fisherman's request", Completed. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Requirements", meta = (ExcludeBaseStruct, BaseStruct = "/Script/ProjectEleri.GameEventRequirement"))
TArray<FInstancedStruct> OfferRequirements;
/** Checked a second time when the quest is handed in, for a quest whose conditions can lapse
* between finishing the last step and getting back to the giver. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Requirements", meta = (ExcludeBaseStruct, BaseStruct = "/Script/ProjectEleri.GameEventRequirement"))
TArray<FInstancedStruct> TurnInRequirements;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Rewards")
FQuestRewardBundle Rewards;
/**
* True when everything the quest asks for before it can be taken holds right now.
*
* TestContext is the player character, the same context the event system hands its requirements, so
* a condition written for one system behaves identically in the other.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Eleri|Quest")
bool AreOfferRequirementsMet(const UObject* TestContext) const;
/** Checked when the quest is handed in rather than when it is offered */
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Eleri|Quest")
bool AreTurnInRequirementsMet(const UObject* TestContext) const;
/** The step with this id, or null. Objective ids are unique inside one quest. */
const FQuestObjective* FindObjective(FName ObjectiveId) const;
/** The step with this id and where it sits in Objectives, or INDEX_NONE */
int32 FindObjectiveIndex(FName ObjectiveId) const;
/** True when the quest asks the player to hand anything over */
bool HasDeliverStep() const;
/** Every step that has to be done for the quest to be handed in. Hidden and optional steps are
* left out, since neither of them can hold a quest back. */
int32 CountRequiredObjectives() const;
/** True when the step at Index may run: everything before it is done when the quest asks for its
* steps in order, and it is free to run when it does not. */
bool IsObjectiveUnlocked(int32 Index, const FQuestRuntimeState& State) const;
/** The items every finished DeliverItems step is asking for, resolved to what the inventory files
* them under. Empty when there is nothing to hand over. */
void CollectDeliverables(const FQuestRuntimeState& State, TArray<TPair<FPrimaryAssetId, int32>>& OutItems) const;
/** Says whether the asset is worth offering at all: a quest with no tag, no steps or a duplicate
* step id would otherwise fail quietly in the middle of a playthrough. */
bool IsValidQuest(FString& OutError) const;
};