Eldoria - The Quest for Quests

Published 26.3.2025


As if I haven't agonized over complex subsystems in this project enough, I decided to do it all over again. Fortunately, this wasn't as tumultuous as the combat subsystem. Nevertheless, countless hours and notebooks were sacrificed to the programming gods and four of the seven Hell's were traversed to make this happen. Let's be clear about something. There are just some things about programming which, regardless of the amount of study and planning beforehand, don't obviate themselves until you've put some code into an editor. This is where I think TDD has value. NOT from conception. End rant.

Dumb Question - What Is A Quest?

There are such things as stupid questions, and this is very likely one of them. But, for the sake of your pets, humor me. I'm a knight. My goal in life is to be the best knight in the land. People far and wide will know my name and either fear or respect me. Parades will be issued in my honor. All the ladies will lust after me. As will the goats. The only problem? Right now, I'm shoveling shit in a stable for a stable master that hates my guts. Calls me a milk drinker. Reminds me daily that I only got this job because my dad paid him to take me on. My only friend is an aged mare who was the gift horse whose teeth I examined. Found a few cavities too. What do I need to get out of this predicament? Education? Hell no. To change parents and be born to rich people that I can inherit wealth from? Maybe, but my Tardis is borked and I owe the blacksmith a favor that I can't repay at the moment. No, what I really need is a Quest Manager that can put together quests for me in a logical way, and then I can start knocking those boys out. Once I complete them all, people will have to call me a knight. I hope.

Using a Quest Manager comes with some serious requirements though. Some I'm unsure I'm able to accept responsibility for. All joking aside, we need to consider the how best to create a basic unit of a quest by asking the aforementioned stupid question: what is a quest? We do them all the time in games but perhaps we don't really think about how they're put together. I conceive of them as a collection of steps that, when all have been completed, should mark the quest as complete. When complete, the quest should confer to the player a collection of rewards of various kinds. Some quests have relationships with each other, typically identified as a narrative relation but others can exist, while others are benign and can be completed just because with no net effect toward the furthering of anything specific. Because some quests have a narrative significance, and others have significance in subplots, while others have none, a means to manage these quests is necessary. And while we can be aware of quests that we have accepted, we only want the player to focus on completing one quest at any time; a quest manager helps with this also.

Babby Steps

The Quest Step class represents the most fundamental building block of a quest. All it does really is marry a Completion flag with a set of logic meant to toggle this flag based on contextually defined heuristics.


Class QuestStep {
	[Boolean]$Completed

	QuestStep() {
		$this.Completed = $false # UNSET THIS FLAG BY DEFAULT; THE QUESTSTEP SHOULDN'T BE COMPLETED AT THIS POINT
	}

	[Void]Update() {} # THIS METHOD IS INTENDED TO BE VIRTUAL AT THIS POINT
}
		

This base class allows us to create specializations that are relative to actions we'd want the player to complete. For example, we can have a class QSHasItem that checks to see if the player has a specific item in their item inventory and sets the Completed flag if so:


Class QSHasItem : QuestStep {
	[String]$TargetItem
	
	QSHasItem( [String]$TargetItem ) : base() {
		$this.TargetItem = $TargetItem 
	} 
	
	[Void]Update() { 
		If($Script:ThePlayer.IsItemInInventory($this.TargetItem) -EQ $true) { 
			$this.Completed = $true
		}
	} 
}
		

Another would be QSPlayerStatAtLevel where if a specific stat value for the player is greater than or equal to a specific value, the step is considered completed:


Class QSPlayerStatAtLevel : QuestStep {
	[StatId]$TargetStat

	[Int]$TargetMaxValue 
	
	QSPlayerStatAtLevel( [StatId]$TargetStat, [Int]$TargetMaxValue ) : base() {
		$this.TargetStat = $TargetStat 
		$this.TargetMaxValue = $TargetMaxValue 
	} 
	
	[Void]Update() { 
		If($Script:ThePlayer.Stats[$this.TargetStat].Max -GE $this.TargetMaxValue) { 
			$this.Completed = $true
		}
	} 
}
		

Leveraging polymorphism, we ensure that the interface to these specializations is always the same so we can change the composition of each specialization safely. Rewards are handled in much the same way: a class called QuestReward acts as a base class which specializations are derived, and each specialization relates to a kind of reward the player could receive including gold, experience points, inventory items, and techniques.

The Quest-sies

The Quest class is defined loosely as a collection of QuestStep and QuestReward instances married to some metadata.


Class Quest { 
	[Boolean]$Active 
	[Boolean]$Completed 
	[Boolean]$RewardsGiven
	
	[List[QuestStep]]$Steps 
	[List[QuestReward]]$Rewards 
	
	Quest() { 
		$this.Active = $false 
		$this.Completed = $false 
		$this.RewardsGiven = $false 
		$this.Steps = [List[QuestStep]]::new() 
		$this.Rewards = [List[QuestReward]]::new() 
	} 
	
	Quest( [List[QuestStep]]$Steps, 
			[List[QuestReward]]$Rewards ) { 
		$this.Active = $false 
		$this.Completed = $false 
		$this.RewardsGiven = $false 
		$this.Steps = $Steps 
		$this.Rewards = $Rewards 
	} 
	
	Quest( [QuestStep[]]$Steps,
			[QuestReward[]]$Rewards ) { 
		$this.Active = $false 
		$this.Completed = $false 
		$this.RewardsGiven = $false 
		$this.Steps = [List[QuestStep]]::new() 
		$this.Rewards = [List[QuestReward]]::new() 
		
		Foreach($A in $Steps) { 
			$this.Steps.Add($A) | Out-Null 
		} 
		Foreach($A in $Rewards) { 
			$this.Rewards.Add($A) | Out-Null 
		} 
	} 
	
	[Void]Update() {} 
}
		

A few things to note with the Quest class.

First we see multiple constructors. After all testing is complete, this list will be whittled down to two: the default (no argument constructor) and whichever one makes sense based off what feels "natural" when instantiating. The default constructor hangs around for two reasons: a basic requirement for cases when the interpreter would resort to its use or for splatting (more on this later). Next, we see some additional metadata worth addressing. The Active flag serves two purposes which will become obvious when we examine the two Quest specializations. To prime the pump, it acts both as a means to facilitate tracking, but also to act as an iterative processing blocker to prevent loop cycles from being wasted on Quests that don't require processing. The Completed flag is pretty self explanatory, but it too serves a dual purpose. It not only aids in tracking, but also acts as a buffer toward reward yielding. Meaning that there's no way the logic surrounding the RewardsGiven flag would ever execute if the Completed flag weren't already set. Finally, the RewardsGiven flag protects exclusively the conferment of rewards. The case guarded against here is not issuing the rewards to the player more than once. Once all rewards have been handed over, this flag is set. In the case that the Update method gets called afterward and the Completed flag is still set, the rewards won't be given out again.

If you can't tell already, the Quest class isn't meant to ever be directly instantiated. This can't be enforced with PowerShell, but know that there's no value in creating instances of it. To the contrary, there's more value to be had from one of its specializations: Linear Quest and Nonlinear Quest. A Linear Quest describes a quest where the Quest Steps are intended to be completed in sequence. The logic will only ever query the current step at any iteration. If the current step's Completed flag is set, the current step counter is incremented. This cycle repeats until at last the final Quest Step has had its Completed flag set, when the Linear Quest is considered complete itself.


Class LinearQuest : Quest { 
	[Int]$CurrentStep 
	
	LinearQuest() : base() { 
		$this.CurrentStep = 0 
	} 
	
	LinearQuest( [List[QuestStep]]$Steps, [List[QuestReward]]$Rewards ) : base($Steps, $Rewards) { 
		$this.CurrentStep = 0
	} 
	
	LinearQuest( [QuestStep[]]$Steps, [QuestReward[]]$Rewards ) : base($Steps, $Rewards) {
		$this.CurrentStep = 0 
	} 
	
	[QuestStep]GetCurrentStep() { 
		Return $this.Steps[$this.CurrentStep] 
	} 
	
	[Void]Update() { 
		If($this.Active -EQ $true) { # IS THIS QUEST'S ACTIVE FLAG SET? 
			If($this.Completed -EQ $false) { # HAS THIS QUEST'S COMPLETE FLAG NOT YET BEEN SET? 
				If($this.CurrentStep -LT $this.Steps.Count) { # IS THE CURRENT STEP COUNTER LESS THAN THE TOTAL NUMBER OF STEPS (INCLUSIVE)? 
					$this.Steps[$this.CurrentStep].Update() # UPDATE THE CURRENT STEP (SETS THE STEP'S COMPLETED FLAG IF HEURISTICS ARE SATISFIED) 
					If($this.Steps[$this.CurrentStep].Completed -EQ $true) { # AFTER HAVING CALLED UPDATE, HAS THE STEP'S COMPLETED FLAG BEEN SET? 
						$this.CurrentStep++ # YES, INCREMENT THE CURRENTSTEP COUNTER 
					} 
				} 
				If($this.CurrentStep -GE $this.Steps.Count) { # IF($THIS.CURRENTSTEP -LT $THIS.STEPS.COUNT) 
					# THIS IS A REALLY POOR MAN'S WAY OF CONCEDING THAT THE COMPLETION 
					# HAS BEEN MET, BUT GIVEN THAT THE CURRENTSTEP WOULDN'T INCREMENT 
					# UNLESS THE STEP COMPLETION FLAG HAD BEEN SET, IT SHOULD BE SANE 
					# TO ASSUME THAT ALL QUEST STEPS ARE COMPLETED BY THIS POINT. 
					$this.Completed = $true 
				} 
			} 
			If($this.Completed -EQ $true) { # IF($THIS.COMPLETED -EQ $FALSE) 
				If($this.RewardsGiven -EQ $false) { # HAS THE REWARDSGIVEN FLAG NOT YET BEEN SET? 
					# THE CODE HERE IS SIMILAR TO WHAT'S IN NONLINEAR QUEST. 
					# READ THAT FOR MORE DETAILS. 
					Foreach($R in $this.Rewards) { # LOOP THROUGH ALL THE ELEMENTS IN THE REWARDS CONTAINER 
						If($R.Given -EQ $false) { # HAS THIS ELEMENT'S GIVEN FLAG NOT YET BEEN SET? 
							$R.Give() # NO - EXECUTE THE ELEMENT'S GIVE METHOD (SETS THIS ELEMENT'S GIVEN FLAG) 
						} 
					} 
					$this.RewardsGiven = $true # SET THE REWARDSGIVEN FLAG 
					$this.Active = $false # UNSET THE ACTIVE FLAG 
				} # IF($THIS.REWARDSGIVEN -EQ $FALSE) 
			} # IF($THIS.COMPLETED -EQ $FALSE) 
		} # IF($THIS.ACTIVE -EQ $TRUE) 
	} # END UPDATE METHOD DEFINITION 
}
		

The Linear Quest introduces a property specific to its requirements called CurrentStep. This is the aforementioned probe that points to the current Quest Step in the Steps collection. A Linear Quest can also be queried for which Quest Step is the current one by using the GetCurrentStep method. The Update method is where the magic happens that makes a Linear Quest do what it does. The logic flow is, essentially, this:

  1. Check to see if the Active flag is set. If it is, proceed. If it's not, bypass this entire closure and effectively leave the method.
  2. Check to see if the Completed flag is unset. If it is, proceed. If it's not, bypass this closure and move to the next one (more on this in a moment).
  3. Check to see if the current value of CurrentStep is less than the number of elements in the Steps collection. If it is, proceed. If it's not, bypass this closure and move to the next one.
  4. Call the Update method of the Step at CurrentStep in the Step collection.
  5. Check the value of the Completed flag of the Step at CurrentStep in the Step collection. If it's set, proceed. If it's not, bypass this closure and move to the next one.
  6. Increment CurrentStep by 1.
  7. Check if the current value of CurrentStep is greater than or equal to the number of elements in the Step collection. If it is, proceed. If it's not, bypass this closure and move to the next one.
  8. Set the Completed flag.
  9. Check to see if the Completed flag is set. If it is, proceed. If it's not, bypass this closure and effectively leave the method.
  10. Check to see if the RewardsGiven flag is unset. If it is, proceed. If it's not, bypass this closure and effectively leave the method.
  11. Loop through the Rewards collection and for each QuestReward, check to see if its Given flag is unset. If it is, proceed. If it's not, iterate this closure.
  12. Call the QuestReward's Give method.
  13. Set the RewardsGiven flag,
  14. Unset the Active flag.

It's pretty straightforward, however, there are some design caveats that might at first seem like blunders but in reality aren't. For example, a question that could be raised here is why not leverage else closures for some of the nested conditionals as it could save processing cycles? The answer here is that the original code actually did. A problem emerged where the rigidity this created ultimately required higher-order management classes to needlessly call the Update method of a Quest one or two times more than was necessary after marking it as completed just so rewards, if to be conferred, were given. The conditional guard to execute this was locked away in a parent conditional that would've only been evaluated in the current iteration. To better optimize the code flow and the player experience, the use of else closures, in this case, was decided against. By contrast, a Nonlinear Quest allows Quest Steps to be completed out of order. Some of the logic is, by virtue of it being a Quest, going to overlap. The differences are going to emerge when you start looking at how the Quest Steps are checked.


Class NonlinearQuest : Quest { 
	NonlinearQuest() : base() {} 
	
	NonlinearQuest( [List[QuestStep]]$Steps, [List[QuestReward]]$Rewards ) : base($Steps, $Rewards) {} 
	
	NonlinearQuest( [QuestStep[]]$Steps, [QuestReward[]]$Rewards ) : base($Steps, $Rewards) {} 
	
	[Void]Update() { 
		If($this.Active -EQ $true) { # IS THIS QUEST'S ACTIVE FLAG SET? 
			If($this.Completed -EQ $false) { # HAS THIS QUEST'S COMPLETE FLAG NOT YET BEEN SET? 
				[Int]$A = 0 # INITIALIZE A "TRUE" COUNTER 
				
				Foreach($Q in $this.Steps) { 
					If($Q.Completed -EQ $false) { # HAS THE STEP'S COMPLETED FLAG NOT YET BEEN SET? 
						$Q.Update() # IF NOT, CALL THE STEP'S UPDATE METHOD 
					} Else { 
						$A++ # THE STEP'S COMPLETED FLAG HAS BEEN SET, INCREMENT THE "TRUE" COUNTER 
					} 
				} 
				
				If($A -EQ $this.Steps.Count) { # DO THE NUMBER OF COMPLETED STEPS EQUAL THE NUMBER OF STEPS IN THIS QUEST? 
					$this.Completed = $true # YES, SET THE QUEST'S COMPLETED FLAG 
				} 
			} 
		
			If($this.Completed -EQ $true) { # IF($THIS.COMPLETED -EQ $FALSE) 
				If($this.RewardsGiven -EQ $false) { # HAS THE REWARDSGIVEN FLAG NOT YET BEEN SET? 
					# IT'S POSSIBLE THERE AREN'T ANY REWARDS FOR A QUEST... I SUPPOSE. 
					# REGARDLESS, IT'S BETTER TO TRY AND CATER FOR IT THAN OTHERWISE. 
					Foreach($R in $this.Rewards) { # LOOP THROUGH ALL THE ELEMENTS IN THE REWARDS CONTAINER 
						If($R.Given -EQ $false) { # HAS THIS ELEMENT'S GIVEN FLAG NOT YET BEEN SET? 
							$R.Give() # NO - EXECUTE THE ELEMENT'S GIVE METHOD (SETS THIS ELEMENT'S GIVEN FLAG) 
						} 
					} 
					$this.RewardsGiven = $true # SET THE REWARDSGIVEN FLAG 
					
					# THIS MAY OR MAY NOT BE THE BEST PLACE FOR THIS, 
					# BUT AT THIS POINT, THE QUEST COULD BE CONSIDERED INACTIVE 
					# SINCE THERE'S NOTHING LEFT TO GAIN FROM IT REMAINING SO. 
					# THE CATCH HERE IS THAT EVERY QUEST SHOULD HAVE ITS REWARDSGIVEN 
					# FLAG UNSET AT TIME OF CREATION SO THAT THIS GETS PARSED. 
					$this.Active = $false 
				} # IF($THIS.REWARDSGIVEN -EQ $FALSE)
			} # IF($THIS.COMPLETED -EQ $FALSE) 
		} # IF($THIS.ACTIVE -EQ $TRUE) 
	} # END UPDATE METHOD DEFINITION 
}
		

As noted, the only major difference here is how the Quest Steps collection is queried. You can see that there's a foreach loop that will check each Quest Step. If its Completed flag is unset, its Update method is called. Alternatively, if the Completed flag is already set, a counter is incremented. Once outside this loop, the value of the counter is compared against the number of elements in the Quest Steps collection. If those two values are equal, the Quest's Completed flag is set.

Let's Bring It In

Although this may seem like an unnecessary layer of abstraction, we introduce one final aggregate called the Questline. A Questline collates both Linear and Nonlinear Quests into a single unit with the same kinds of metadata that Quests have. This is used to marry Quests together and delineate quests purposes: core narrative, subplot, random.


Class Questline {
	[Int]$CurrentQuest 

	[Boolean]$Active 
	[Boolean]$Completed 
	[Boolean]$RewardsGiven 

	[List[Quest]]$Quests 
	[List[QuestReward]]$Rewards 
	
	Questline() { 
		# THIS CTOR MAY NOT BE ABLE TO BE USED! 
		# IF A QUESTLINE IS CREATED FROM A SPLAT, THIS CTOR IS USED. THE ISSUE HERE IS THAT THE FIRST QUEST CAN'T BE SET AS ACTIVE AT THIS TIME 
		# SINCE IT DOESN'T ACTUALLY EXIST IN THE LISTING AT THE TIME THIS CTOR IS CALLED, SO IT WOULD RESULT IN AN EXCEPTION. 
		$this.CurrentQuest = 0 
		$this.Active = $false 
		$this.Completed = $false 
		$this.RewardsGiven = $false 
		$this.Quests = [List[Quest]]::new() 
		$this.Rewards = [List[QuestReward]]::new() 
	} 
	
	Questline( [List[Quest]]$Quests, [List[QuestReward]]$Rewards, [Boolean]$Active = $false ) { 
		$this.CurrentQuest = 0 
		$this.Active = $Active 
		$this.Completed = $false 
		$this.RewardsGiven = $false 
		$this.Quests = [List[Quest]]::new($Quests) 
		$this.Rewards = [List[QuestReward]]::new($Rewards) 
		$this.Quests[$this.CurrentQuest].Active = $true 
	} 
	
	Questline( [Quest[]]$Quests, [QuestReward[]]$Rewards, [Boolean]$Active = $false ) { 
		$this.CurrentQuest = 0 
		$this.Active = $Active 
		$this.Completed = $false 
		$this.RewardsGiven = $false 
		$this.Quests = [List[Quest]]::new() 
		$this.Rewards = [List[QuestReward]]::new() 
		$this.Quests[$this.CurrentQuest].Active = $true 
		
		Foreach($A in $Quests) { 
			$this.Quests.Add($A) | Out-Null 
		} 
		Foreach($A in $Rewards) { 
			$this.Rewards.Add($A) | Out-Null 
		} 
	} 
	
	[Quest]GetCurrentQuest() { 
		Return $this.Quests[$this.CurrentQuest] 
	} 
	
	[Void]Update() { 
		# THE CODE HERE IS GOING TO LOOK AWFULLY SIMILAR TO THE CODE IN THE UPDATE 
		# METHOD IN THE LINEARQUEST CLASS. WE'RE TAKING THE D OUT OF DRY BAYBEE! 
		# FOR FURTHER DETAILS ON THIS BLOCK, YOU SHOULD PROBABLY LOOK AT THE 
		# UPDATE METHOD IN THE QUEST CLASS. I'LL REPEAT MYSELF IN LOGIC, NOT IN 
		# COMMENTS; I DO HAVE *SOME* STANDARDS. 
		If($this.Active -EQ $true) { # IS THIS QUESTLINE'S ACTIVE FLAG SET? 
			If($this.Completed -EQ $false) { # HAS THIS QUESTLINE'S COMPLETE FLAG NOT YET BEEN SET? 
				If($this.CurrentQuest -LT $this.Quests.Count) { # IS THE CURRENT QUEST COUNTER LESS THAN THE TOTAL NUMBER OF QUESTS (INCLUSIVE)? 
					$this.Quests[$this.CurrentQuest].Update() # UPDATE THE CURRENT QUEST (SETS THE QUEST'S COMPLETED FLAG IF HEURISTICS ARE SATISFIED) 
					
					If($this.Quests[$this.CurrentQuest].Completed -EQ $true) { # AFTER HAVING CALLED UPDATE, HAS THE QUEST'S COMPLETED FLAG BEEN SET? 
						$this.CurrentQuest++ # YES, INCREMENT THE CURRENTQUEST COUNTER 
						
						If($this.CurrentQuest -LT $this.Quests.Count) { 
							$this.Quests[$this.CurrentQuest].Active = $true 
						} 
					} 
				} 
				
				# THERE'S A VERY SPECIFIC REASON FOR NOT USING AN ELSE FROM THE PREVIOUS CONDITIONAL; WASITNG CALL CYCLES! 
				If($this.CurrentQuest -GE $this.Quests.Count) { # IF($THIS.CURRENTQUEST -LT $THIS.QUESTS.COUNT) 
					# THIS IS A REALLY POOR MAN'S WAY OF CONCEDING THAT THE COMPLETION 
					# HAS BEEN MET, BUT GIVEN THAT THE CURRENTSTEP WOULDN'T INCREMENT 
					# UNLESS THE STEP COMPLETION FLAG HAD BEEN SET, IT SHOULD BE SANE 
					# TO ASSUME THAT ALL QUEST STEPS ARE COMPLETED BY THIS POINT. 
					$this.Completed = $true 
				} 
			} 
			
			# THERE'S A VERY SPECIFIC REASON FOR NOT USING AN ELSE FROM THE PREVIOUS CONDITIONAL; WASITING CALL CYCLES! 
			If($this.Completed -EQ $true) { # IF($THIS.COMPLETED -EQ $FALSE) 
				If($this.RewardsGiven -EQ $false) { # HAS THE REWARDSGIVEN FLAG NOT YET BEEN SET? 
					Foreach($A in $this.Rewards) { # LOOP THROUGH ALL THE ELEMENTS IN THE REWARDS CONTAINER 
						If($A.Given -EQ $false) { # HAS THIS ELEMENT'S GIVEN FLAG NOT YET BEEN SET? 
							$A.Give() # NO - EXECUTE THE ELEMENT'S GIVE METHOD (SETS THIS ELEMENT'S GIVEN FLAG) 
						} 
					} 
					
					$this.RewardsGiven = $true # SET THE REWARDSGIVEN FLAG 
					$this.Active = $false # UNSET THE ACTIVE FLAG 
				} # IF($THIS.REWARDSGIVEN -EQ $FALSE) 
			} # IF($THIS.COMPLETED -EQ $FALSE) 
		} # IF($THIS.ACTIVE -EQ $TRUE) 
	} # END UPDATE METHOD DEFINITION 
}
		

All the logic in the Update method looks very similar to the Linear Quest. The gist here is that because a Questline is so high-level, it only makes sense that any Quest contained within it be completed in sequence. Because it can contain Nonlinear Quests, there's an impression that tasks can be completed out of order, and that's really all we care about. This is just one big Rube Goldberg Machine after all. However, the Questline calls out something very specific in its default constructor and it has to do with splats. PowerShell allows one to instantiate any object using splats. Moreover, you can do this inline without having previously created a variable of the Hashtable type. If we use the Linear Quest as an example, we can create one using splats as such:


[LinearQuest]@{} 
[LinearQuest]@{ 
	Steps = @() 
	Rewards = @() 
}
		

Now, you may look at this and think that the first splat will map to the default constructor and the second to the tertiary constructor, but that's actually not the case. In both forms, PowerShell will always call the default constructor first, then use the splat to assign values to respective members in the instance. Depending upon the needs of your initial state, this could present a problem. In the secondary and tertiary constructors, there's a statement that sets the Active flag of the first Quest in the Quest collection. This statement existed, at one point, in the default constructor. If you're careful, you'll have already noticed that this causes what would've been in the old days a segmentation fault. Usually (and there are exceptions to this), default constructors accept no arguments. All it will do as a consequence is instantiate a new List of Quests, and this List is empty. If the statement to set the Active flag of the first Quest in the collection is present in the default constructor, it will fail because the List at that time is empty. The secondary and tertiary constructors, because they accept as parameters collections for the Quests and Rewards properties, would have values to mutate and the execution of said statement would be fine. That said, because splatting, regardless of the contents of the Hashtable, will always defer to the default constructor, we run into some constraints about what kinds of functionality it can achieve. There's a requirement for the initial state of a Questline to mutate itself, so the use of splatting won't work. We instead have to rely the new method. Creating a Questline is pretty straightforward:


[Questline]$SampleQuestline2 = [Questline]::new(
	@( 
		[NonlinearQuest]@{ 
			Steps = @( 
				[QAHasItem]::new(), 
				[QAPlayerHasGold]::new(), 
				[QAQuestCompleted]::new(), 
				[QuestStep]::new(), 
				[QuestStep]::new() 
			) 
			Rewards = @( 
				[QuestReward]::new(), 
				[QuestReward]::new(), 
				[QuestReward]::new() 
			) 
		}, 
		[LinearQuest]@{ 
			Steps = @( 
				[QAHasItem]::new(), 
				[QAPlayerHasGold]::new(), 
				[QAQuestCompleted]::new(), 
				[QuestStep]::new(), 
				[QuestStep]::new(), 
				[QuestStep]::new(), 
				[QuestStep]::new(), 
				[QuestStep]::new(), 
				[QuestStep]::new(), 
				[QuestStep]::new(), 
				[QuestStep]::new() 
			) 
			Rewards = @( 
				[QuestReward]::new(), 
				[QuestReward]::new(), 
				[QuestReward]::new() 
			) 
		} 
	), 
	@( 
		[QuestReward]::new(), 
		[QuestReward]::new(), 
		[QuestReward]::new(), 
		[QuestReward]::new(), 
		[QuestReward]::new() 
	), 
	$true 
)
		

Testing the Questline is done, manually, like this (it simulates multiple iterative passes from the game loop):


Write-Host 'Sample Questline Created...' 
Write-Host '' 
Write-Host '' 
Write-Host 'Calling Questline Update Method...' 
Write-Host '' 
$SampleQuestline2.Update() 

Write-Host '' 
Write-Host '' 
Write-Host 'Calling Questline Update Method Again...' 
$SampleQuestline2.Update() 

Write-Host '' 
Write-Host 'Calling Questline Update Method Again...' 
$SampleQuestline2.Update() 

Write-Host '' 
Write-Host 'Calling Questline Update Method Again...' 
$SampleQuestline2.Update() 

Write-Host '' 
Write-Host 'Calling Questline Update Method Again...' 
$SampleQuestline2.Update() 

Write-Host '' 
Write-Host 'Calling Questline Update Method Again...' 
$SampleQuestline2.Update() 

Write-Host '' 
Write-Host 'Calling Questline Update Method Again...' 
$SampleQuestline2.Update() 

Write-Host '' 
Write-Host 'Calling Questline Update Method Again...' 
$SampleQuestline2.Update() 

Write-Host '' 
Write-Host 'Calling Questline Update Method Again...' 
$SampleQuestline2.Update() 

Write-Host '' 
Write-Host 'Calling Questline Update Method Again...' 
$SampleQuestline2.Update() 

Write-Host '' 
Write-Host 'Calling Questline Update Method Again...' 
$SampleQuestline2.Update() 

Write-Host '' 
Write-Host 'Calling Questline Update Method Again...' 
$SampleQuestline2.Update() 

Write-Host ''
		

The output from this is quite long, but the good thing is this code is self-contained so it can be tested independently of the game proper. We'll save the details of the Quest Manager for another post!