This weekend I decided to start tackling what I’ll call a “narrative event” system for Eldoria. I don’t have a damn clue if this is what they’re really called in professional game development or not. It just sounds more official if when my wife asks what I’m doing I can tell her that instead of “figuring out how to string a bunch of seemingly unrelated shit together for a computer program”.

I normally don’t share these kinds of things. Not that it’s a treat for you folks to see this stuff, because it’s likely not, but I figured I’d put this one up here. When I write computer programs, I do so first with a tremendous amount of dead tree edition pseudocode. A lot of hours are spent writing stream of consciousness and code-like stuff, and refinements are made on said code-like stuff over and over again before any keyboard work begins. This doesn’t always guarantee that what ends up in an editor is correct the first time, but it’s usually damn near close and that’s what matters. What I’m going to share here is first the transcribed notes, and second the handwritten version.

Event System

My current intuition here is that the Game Core class will need to be modified to an extent.

Actually, this may not be necessary.

Start with something simple. Let’s call it the “Event Tree”. With the Event Tree, we start with the root node and work our way toward the top where the ending of the game is. Much like the branches of the tree, all the endings differ to an extent, and the paths to get to these endings aren’t all the same. Concurrently, the trunk of the tree provides for some initial degree of linearity before allowing the player to do their own objectives.

Quest Composition

A quest is conceptualized as a collection of steps that are required to be completed entirely so that the quest can be marked as complete. Some quests require the steps be completed linearly (in sequence) while others can be completed in a nonlinear fashion. For quests of the former type, steps employ a locking mechanism complemented with a pointer that references the current step. Quests of the latter type don’t use this kind of structure, instead will continuously check each step for completion until they’re all satisfied.

PSC 01

Class Quest
    QuestType type (Linear | Asynchronous)
    List<QuestCondition> conditions

Class Quest
    List<QuestCondition> conditions

Class LinearQuest : Quest
    List<LinearQuestCondition> conditions

Class NonlinearQuest : Quest
    List<NonlinearQuestCondition> conditions

Class QuestCondition
    ...

Class Quest
    List<QuestCondition> conditions

Class LinearQuestCondition : QuestCondition
    ...

Class NonlinearQuestCondition : QuestCondition
    ...

Class LinearQuest : Quest
    ctor
        conditions = new List<LinearQuestCondition>

Class NonlinearQuest : Quest
    ctor
        conditions = new List<NonlinearQuestCondition>

Class LinearQuestCondition : QuestCondition
    Boolean locked

Class LinearQuest : Quest
    Int currentStep
    ctor
        condition = ?

Class QuestStep
    ...

Class Quest
    List<QuestStep> steps

Class LinearQuestStep : QuestStep
    ...

Class NonlinearQuestStep : QuestStep
    ...

Class LinearQuest : Quest
    Int currentStep = 0
    ctor
        steps = new List<LinearQuestStepQuestStep>

Class NonlinearQuest : Quest
    ctor
        steps = new List<NonlinearQuestStepQuestStep>

Given PSC 01, a thought occurred: what if instead of storing a list of Quest Steps as a member in a Quest, what could the code look like if in the Quest Step class a reference to the next Quest Step was used? It may be pertinent to first look at how Quest Steps would be created given the paradigm created in PSC 01.

PSC 01-01

Quest sampleQuest = new Quest()
sampleQuest.steps.Add(new QuestStep(...))

PSC 01-02

Quest sampleQuest = new Quest(
    new List<QuestStep> {...}
)

PSC 01-03

Quest sampleQuest = new Quest(@(
    new QuestStep(...),
    ...
))

With these examples, we’re still dealing with a handful of anonymous objects. There’s some simplicity to this, but what it really does is reduce the invocation footprint to the quest they relate to. Specifically, a collection of explicitly named instances aren’t necessary. I may abandon this alternative line of thought until the remainder of the constructs have been fleshed out a bit further.

Quest Steps

A Quest Step is a specific state that has yet to be obtained. When achieved, it contributes, either itself or collectively, toward the completion of a quest. Quest Steps are largely unaware of each other, instead being married by the Quest they belong to; this doesn’t mean that Quest Steps are explicitly aware of their parent Quest.

Quest Steps are composed of one or more Targets. When these Targets are satisfied, the parent Quest Step is considered completed. The nature of the relationship between a Quest Step and its Targets is first expressed via the Quest Step Type, then meted out though an implementation method that will check to see if the Target has been satisfied. The heuristics that dictate a “satisfied Target” can differ between step types, so it’s required that each step define this themselves.

Despite the fact that there could end up being a large number of additions to the code base, it may also be prudent here to include multiple specializations of a base class instead of relying on an enumeration to specify the step type. This has, I believe two distinct advantages:

  • Alleviating the need to use a ScriptBlock for each integrity check.
  • Different step types may require parameters of specific types or multiple parameters that differ from other step types.

PSC 02

Class QuestStep
    Boolean IsMet()

Class QSHasItem : QuestStep
    Object targetItem
    ctor Object
        targetItem = Object

Class QSHasItems : QuestStep
    List<Object> targetItems
    ctor List<Object>
        targetItems = List<Object>

Class QSAtMapCoordinates : QuestStep
    (Map, Coordinates) targetLocation
    ctor (Map, Coordinates)
        targetLocation = (Map, Coordinates)

After transcribing some of the pseudocode, it’s obvious that creating a distinction between a Linear Quest and a Nonlinear one doesn’t make much sense. These abstractions will be scrapped. Additionally, for some Quest Step specializations, it may be pertinent to change the type of the member to be a Reference.

PSC 02-01

Class QSHasItem : QuestStep
    Ref targetItem
    ctor Ref
        targetItem = Ref

Class QSHasItems : QuestStep
    List<Ref> targetItems
    ctor List<Ref>
        targetItems = List<Ref>

Class QSAtMapCoordinates : QuestStep
    (Ref, Ref) targetLocation
    ctor (Ref, Ref)
        targetLocation = (Ref, Ref)

At this point, multiple kinds of Quest Step types should be devised. Types involve the creation of a Quest Step abstraction that has a name which summarizes its intent, is parameterized accordingly, and provides an implementation of the IsMet method. Some types could include:

  • Has Item
    • Does the player have a specific item in their inventory?
  • Has Spoken to NPC
    • Has the player spoken to a specific NPC?
  • Has Completed Quest
    • Has the player completed a specific Quest?
  • Has Gold
    • Does the player have a specific amount of gold?
  • Has Stat At Level
    • Does the player have a specific stat at a specific level?
  • Has Number of Techniques
    • Does the player have a specific number of techniques?
  • Has Dealt High Damage
    • Has the player dealt a sufficient amount of damage?
  • Has Dealt High Elemental Damage
    • Has the player dealt a sufficient amount of damage of a specific element?

While this is an incomplete list, some of these ideas reveal architectural shortcomings in the program. For example, the backing mechanisms to measure and store the data required to make these work. I’ve no opposition to building this functionality out, there simply needs to be a dive into the requirements for the add. Given the previously created speculative list, it may be pertinent to pseudocode some of them out, including implementations of the IsMet method.

PSC 02-03

Class QSHasItem : QuestStep
    Ref targetItem
    ctor Ref
        targetItem = Ref
    Boolean IsMet
        If targetItem is in Player's Item Inventory
            Return True
        Return False

Class QSHasSpokenTo : QuestStep
    Ref targetEntity
    ctor Ref
        targetEntity = Ref
    Boolean IsMet
        If Player has spoken to targetEntity
            Return True
        Return False

There are currently no provisions for this type. Not only is there no implementation for talking to a NPC, but there are also currently no NPCs implemented.

Class QSCompletedQuest : QuestState
    Ref targetQuest
    ctor Ref
        targetQuest = Ref
    Boolean IsMet
        If targetQuest is Completed
            Return True
        Return False

Class QSHasDefeatedEnemy : QuestState Ref targetEnemy ctor Ref targetEnemy = Ref Boolean IsMet If Player has defeated targetEnemy Return True Return False


This too lacks a foundation. The program has no means for tracking defeated enemies.

Class QSHasGold : QuestState Int targetGold ctor Int targetGold = Int Boolean IsMet If Player’s Gold >= targetGold Return True Return False ```

It’s not worthwhile to sketch the remainder of them since there’s enough variety to illustrate that some additional infrastructure is necessary to facilitate the adds. They also reveal a change needed to the Quest class. Specifically, the Quest needs a State Machine.

My current intuition is that there would be three possible states a Quest could be in:

  • In-Progress
    • The Quest is actively being worked by the player.
  • Completed
    • The Player has managed to satisfy all the steps for this quest.
  • Failed
    • One of the steps for this quest has been marked as failed for some reason.

These states could also apply to Quest Steps as well, however the transitions between them differ a bit. For example, a Quest will remain IN-Progress until either all its steps are marked as Completed or one of them enters a Failed state. In the former case, the Quest would change state to Completed, and to Failed in the latter. If a Quest should enter a Failed state, all steps that were intended to be completed afterwards are considered failed as well (they’ll actually be marked as such). This is a procedural protective measure. Concurrently, a Quest Step would transition to the Completed state when all its Triggers have been accomplished.