<p>A <em>quest</em> is a common feature of games. From classic fetch-quests like retrieving 10 flowers to complex quest chains involving drama and intrigue, quests need to be properly tracked in our game.</p>
<p>A quest follows a specific development:</p>
<olclass="simple">
<li><p>The quest is <em>started</em>. This normally involves the player accepting the quest, from a quest-giver, job board or other source. But the quest could also be thrust on the player (“save the family from the burning house before it collapses!”)</p></li>
<li><p>Once a quest has been accepted and assigned to a character, it is either either <codeclass="docutils literal notranslate"><spanclass="pre">Started</span></code> (that is, ‘in progress’), <codeclass="docutils literal notranslate"><spanclass="pre">Abandoned</span></code>, <codeclass="docutils literal notranslate"><spanclass="pre">Failed</span></code> or <codeclass="docutils literal notranslate"><spanclass="pre">Complete</span></code>.</p></li>
<li><p>At suitable times the quest’s <em>progress</em> is checked. This could happen on a timer or when trying to ‘hand in’ the quest. When checking, the current ‘step’ is checked against its finish conditions. If ok, that step is closed and the next step is checked until it either hits a step that is not yet complete, or there are no more steps, in which case the entire quest is complete.</p></li>
<p>An example implementation of quests is found under <codeclass="docutils literal notranslate"><spanclass="pre">evennia/contrib/tutorials</span></code>, in <aclass="reference internal"href="../../../api/evennia.contrib.tutorials.evadventure.quests.html#evennia-contrib-tutorials-evadventure-quests"><spanclass="std std-ref">evadvanture/quests.py</span></a>.</p>
</aside>
<p>To represent quests in code, we need</p>
<ulclass="simple">
<li><p>A convenient flexible way to code how we check the status and current steps of the quest. We want this scripting to be as flexible as possible. Ideally we want to be able to code the quests’s logic in full Python.</p></li>
<li><p>Persistence. The fact that we accepted the quest, as well as its status and other flags must be saved in the database and survive a server reboot.</p></li>
</ul>
<p>We’ll accomplish this using two pieces of Python code:</p>
<ulclass="simple">
<li><p><codeclass="docutils literal notranslate"><spanclass="pre">EvAdventureQuest</span></code>: A Python class with helper methods that we can call to check current quest status, figure if a given quest-step is complete or not. We will create and script new quests by simply inheriting from this base class and implement new methods on it in a standardized way.</p></li>
<li><p><codeclass="docutils literal notranslate"><spanclass="pre">EvAdventureQuestHandler</span></code> will sit ‘on’ each Character as <codeclass="docutils literal notranslate"><spanclass="pre">character.quests</span></code>. It will hold all <codeclass="docutils literal notranslate"><spanclass="pre">EvAdventureQuest</span></code>s that the character is or has been involved in. It is also responsible for storing quest state using <aclass="reference internal"href="../../../Components/Attributes.html"><spanclass="doc std std-doc">Attributes</span></a> on the Character.</p></li>
</ul>
<sectionid="the-quest-handler">
<h2><spanclass="section-number">14.1. </span>The Quest Handler<aclass="headerlink"href="#the-quest-handler"title="Permalink to this headline">¶</a></h2>
<blockquote>
<div><p>Create a new module <codeclass="docutils literal notranslate"><spanclass="pre">evadventure/quests.py</span></code>.</p>
</div></blockquote>
<p>We saw the implementation of an on-object handler back in the <aclass="reference internal"href="Beginner-Tutorial-AI.html#the-aihandler"><spanclass="std std-doc">lesson about NPC and monster AI</span></a> (the <codeclass="docutils literal notranslate"><spanclass="pre">AIHandler</span></code>).</p>
<p>Persistent handlers are commonly used throughout Evennia. You can read more about them in the <aclass="reference internal"href="../../Tutorial-Persistent-Handler.html"><spanclass="doc std std-doc">Making a Persistent object Handler</span></a> tutorial.</p>
</aside>
<ulclass="simple">
<li><p><strong>Line 9</strong>: We know that the quests themselves will be Python classes inheriting from <codeclass="docutils literal notranslate"><spanclass="pre">EvAdventureQuest</span></code> (which we haven’t created yet). We will store those classes in <codeclass="docutils literal notranslate"><spanclass="pre">self.quest_classes</span></code> on the handler. Note that there is a difference between a class and an <em>instance</em> of a class! The class cannot hold any <em>state</em> on its own, such as the status of that quest is for this particular character. The class only holds python code.</p></li>
<li><p><strong>Line 10</strong>: We set aside another property on the handler - <codeclass="docutils literal notranslate"><spanclass="pre">self.quest</span></code> This is dictionary that will hold <codeclass="docutils literal notranslate"><spanclass="pre">EvAdventureQuest</span></code><em>instances</em>.</p></li>
<li><p><strong>Line 11</strong>: Note that we call the <codeclass="docutils literal notranslate"><spanclass="pre">self._load()</span></code> method here, this loads up data from the database whenever this handler is accessed.</p></li>
<li><p><strong>Lines 14-18</strong>: We use <codeclass="docutils literal notranslate"><spanclass="pre">self.obj.attributes.get</span></code> to fetch an <aclass="reference internal"href="../../../Components/Attributes.html"><spanclass="doc std std-doc">Attribute</span></a> on the Character named <codeclass="docutils literal notranslate"><spanclass="pre">_quests</span></code> and with a category of <codeclass="docutils literal notranslate"><spanclass="pre">evadventure</span></code>. If it doesn’t exist yet (because we never started any quests), we just return an empty dict.</p></li>
<li><p><strong>Line 21</strong>: Here we loop over all the classes and instantiate them. We haven’t defined how these quest-classes look yet, but by instantiating them with <codeclass="docutils literal notranslate"><spanclass="pre">self.obj</span></code> (the Character) we should be covered - from the Character class the quest will be able to get to everything else (this handler itself will be accessible as <codeclass="docutils literal notranslate"><spanclass="pre">obj.quests</span></code> from that quest instance after all).</p></li>
<li><p><strong>Line 24</strong>: Here we do the corresponding save operation.</p></li>
</ul>
<p>The rest of the handler are just access methods for getting, adding and removing quests from the handler. We make one assumption in those code, namely that the quest class has a property <codeclass="docutils literal notranslate"><spanclass="pre">.key</span></code> being the unique quest-name.</p>
<p>This is how it would be used in practice:</p>
<divclass="highlight-python notranslate"><divclass="highlight"><pre><span></span><spanclass="c1"># in some questing code </span>
<pclass="sidebar-title">What can be saved in Attributes?</p>
<p>For more details, see <aclass="reference internal"href="../../../Components/Attributes.html#what-types-of-data-can-i-save-in-an-attribute"><spanclass="std std-doc">the Attributes documentation</span></a> on the matter.</p>
</aside>
<p>We chose to store classes and not instances of classes above. The reason for this has to do with what can be stored in a database <codeclass="docutils literal notranslate"><spanclass="pre">Attribute</span></code> - one limitation of an Attribute is that we can’t save a class instance <em>with other database entities baked inside it</em>. If we saved quest instances as-is, it’s highly likely they’d contain database entities ‘hidden’ inside them - a reference to the Character, maybe to objects required for the quest to be complete etc. Evennia would fail trying to save that data.
Instead we store only the classes, instantiate those classes with the Character, and let the quest store its state flags separately, like this:</p>
<divclass="highlight-python notranslate"><divclass="highlight"><pre><span></span><spanclass="c1"># in evadventure/quests.py </span>
<p>This works the same as the <codeclass="docutils literal notranslate"><spanclass="pre">_load</span></code> and <codeclass="docutils literal notranslate"><spanclass="pre">_save</span></code> methods, except it fetches a property <codeclass="docutils literal notranslate"><spanclass="pre">.data</span></code> (this will be a <codeclass="docutils literal notranslate"><spanclass="pre">dict</span></code>) on the quest instance and save it. As long as we make sure to call these methods from the quest the quest whenever that <codeclass="docutils literal notranslate"><spanclass="pre">.data</span></code> property is changed, all will be well - this is because Attributes know how to properly analyze a <codeclass="docutils literal notranslate"><spanclass="pre">dict</span></code> to find and safely serialize any database entities found within.</p>
<p>Our handler is ready. We created the <codeclass="docutils literal notranslate"><spanclass="pre">EvAdventureCharacter</span></code> class back in the <aclass="reference internal"href="Beginner-Tutorial-Characters.html"><spanclass="doc std std-doc">Character lesson</span></a> - let’s add quest-support to it.</p>
<divclass="highlight-python notranslate"><divclass="highlight"><pre><span></span><spanclass="c1"># in evadventure/characters.py</span>
<li><p><strong>Line 7</strong>: Each class must have a <codeclass="docutils literal notranslate"><spanclass="pre">.key</span></code> property unquely identifying the quest. We depend on this in the quest-handler.</p></li>
<li><p><strong>Line 12</strong>: <codeclass="docutils literal notranslate"><spanclass="pre">quester</span></code> (the Character) is passed into this class when it is initiated inside <codeclass="docutils literal notranslate"><spanclass="pre">EvAdventureQuestHandler._load()</span></code>.</p></li>
<li><p><strong>Line 13</strong>: We load the quest data into <codeclass="docutils literal notranslate"><spanclass="pre">self.data</span></code> directly using the <codeclass="docutils literal notranslate"><spanclass="pre">questhandler.load_quest-data</span></code> method (which in turn loads it from an Attribute on the Character). Note that the <codeclass="docutils literal notranslate"><spanclass="pre">.questhandler</span></code> property is defined on <strong>lines 34-36</strong> as a shortcut to get to the handler.</p></li>
</ul>
<p>The <codeclass="docutils literal notranslate"><spanclass="pre">add/get/remove_data</span></code> methods are convenient wrappers for getting data in and out of the database using the matching methods on the handler. When we implement a quest we should prefer to use <codeclass="docutils literal notranslate"><spanclass="pre">.get_data</span></code>, <codeclass="docutils literal notranslate"><spanclass="pre">add_data</span></code> and <codeclass="docutils literal notranslate"><spanclass="pre">remove_data</span></code> over manipulating <codeclass="docutils literal notranslate"><spanclass="pre">.data</span></code> directly, since the former will make sure to save said that to the database automatically.</p>
<p>The <codeclass="docutils literal notranslate"><spanclass="pre">current_step</span></code> tracks the current quest ‘step’ we are in; what this means is up to each Quest. We set up convenient properties for setting the <codeclass="docutils literal notranslate"><spanclass="pre">current_state</span></code> and also make sure to save it in the data dict as “current_step”.</p>
<p>The quest can have a few possible statuses: “started”, “completed”, “abandoned” and “failed”. We create a few properties and methods for easily control that, while saving everything under the hood:</p>
<divclass="highlight-python notranslate"><divclass="highlight"><pre><span></span><spanclass="c1"># in evadventure/quests.py</span>
<p>So far we have only added convenience functions for checking statuses. How will the actual “quest” aspect of this work?</p>
<p>What will happen when the system wants to check the progress of the quest, is that it will call a method <codeclass="docutils literal notranslate"><spanclass="pre">.progress()</span></code> on this class. Similarly, to get help for the current step, it will call a method <codeclass="docutils literal notranslate"><spanclass="pre">.help()</span></code></p>
<spanclass="sa">f</span><spanclass="s2">"You have </span><spanclass="si">{</span><spanclass="bp">self</span><spanclass="o">.</span><spanclass="n">status</span><spanclass="si">}</span><spanclass="s2"> this quest."</span><spanclass="p">)</span>
<spanclass="k">else</span><spanclass="p">:</span>
<spanclass="n">help_resource</span><spanclass="o">=</span><spanclass="nb">getattr</span><spanclass="p">(</span><spanclass="bp">self</span><spanclass="p">,</span><spanclass="sa">f</span><spanclass="s2">"help_</span><spanclass="si">{</span><spanclass="bp">self</span><spanclass="o">.</span><spanclass="n">current_step</span><spanclass="si">}</span><spanclass="s2">"</span><spanclass="p">,</span><spanclass="s2">"No help available."</span><spanclass="p">)</span>
<pclass="sidebar-title">What’s with the *args, **kwargs?</p>
<p>These are optional, but allow you to pass extra information into your quest-check. This could be very powerful if you want to add extra context to determine if a quest-step is currently complete or not.</p>
</aside>
<p>Calling the <codeclass="docutils literal notranslate"><spanclass="pre">.progress(*args,</span><spanclass="pre">**kwargs)</span></code> method will call a method named <codeclass="docutils literal notranslate"><spanclass="pre">step_<current_step>(*args,</span><spanclass="pre">**kwargs)</span></code> on this class. That is, if we are on the <em>start</em> step, the method called will be <codeclass="docutils literal notranslate"><spanclass="pre">self.step_start(*args,</span><spanclass="pre">**kwargs)</span></code>. Where is this method? It has not been implemented yet! In fact, it’s up to us to implement methods like this for each quest. By just adding a correctly added method, we will easily be able to add more steps to a quest.</p>
<p>Similarly, calling <codeclass="docutils literal notranslate"><spanclass="pre">.help(*args,</span><spanclass="pre">**kwargs)</span></code> will try to find a property <codeclass="docutils literal notranslate"><spanclass="pre">help_<current_step></span></code>. If this is a callable, it will be called as for example <codeclass="docutils literal notranslate"><spanclass="pre">self.help_start(*args,</span><spanclass="pre">**kwargs)</span></code>. If it is given as a string, then the string will be returned as-is and the <codeclass="docutils literal notranslate"><spanclass="pre">*args,</span><spanclass="pre">**kwargs</span></code> will be ignored.</p>
<sectionid="example-quest">
<h3><spanclass="section-number">14.2.1. </span>Example quest<aclass="headerlink"href="#example-quest"title="Permalink to this headline">¶</a></h3>
<divclass="highlight-python notranslate"><divclass="highlight"><pre><span></span><spanclass="c1"># in some quest module, like world/myquests.py</span>
<p>This is a very simple quest that will resolve on its own after two <codeclass="docutils literal notranslate"><spanclass="pre">.progress()</span></code> checks. Here’s the full life cycle of this quest:</p>
<divclass="highlight-python notranslate"><divclass="highlight"><pre><span></span><spanclass="c1"># in some module somewhere, using evennia shell or in-game using py</span>
<h3><spanclass="section-number">14.2.2. </span>A useful Command<aclass="headerlink"href="#a-useful-command"title="Permalink to this headline">¶</a></h3>
<p>The player must know which quests they have and be able to inspect them. Here’s a simple <codeclass="docutils literal notranslate"><spanclass="pre">quests</span></code> command to handle this:</p>
<divclass="highlight-python notranslate"><divclass="highlight"><pre><span></span><spanclass="c1"># in evadventure/quests.py</span>
<spanclass="bp">self</span><spanclass="o">.</span><spanclass="n">msg</span><spanclass="p">(</span><spanclass="sa">f</span><spanclass="s2">"Quest </span><spanclass="si">{</span><spanclass="bp">self</span><spanclass="o">.</span><spanclass="n">quest_name</span><spanclass="si">}</span><spanclass="s2"> not found."</span><spanclass="p">)</span>
<p>Add this to the <codeclass="docutils literal notranslate"><spanclass="pre">CharacterCmdSet</span></code> in <codeclass="docutils literal notranslate"><spanclass="pre">mygame/commands/default_cmdsets.py</span></code>. Follow the <aclass="reference internal"href="../Part1/Beginner-Tutorial-Adding-Commands.html#add-the-echo-command-to-the-default-cmdset"><spanclass="std std-doc">Adding a command lesson</span></a> if you are unsure how to do this. Reload and if you are playing as an <codeclass="docutils literal notranslate"><spanclass="pre">EvAdventureCharacter</span></code> you should be able to use <codeclass="docutils literal notranslate"><spanclass="pre">quests</span></code> to view your quests.</p>
</section>
</section>
<sectionid="testing">
<h2><spanclass="section-number">14.3. </span>Testing<aclass="headerlink"href="#testing"title="Permalink to this headline">¶</a></h2>
<blockquote>
<div><p>Create a new folder <codeclass="docutils literal notranslate"><spanclass="pre">evadventure/tests/test_quests.py</span></code>.</p>
</div></blockquote>
<asideclass="sidebar">
<p>An example test suite for quests is found in <codeclass="docutils literal notranslate"><spanclass="pre">evennia/contrib/tutorials/evadventure</span></code>, as <aclass="reference internal"href="../../../api/evennia.contrib.tutorials.evadventure.tests.test_quests.html#evennia-contrib-tutorials-evadventure-tests-test-quests"><spanclass="std std-ref">tests/test_quests.py</span></a>.</p>
</aside>
<p>Testing of the quests means creating a test character, making a dummy quest, add it to the character’s quest handler and making sure all methods work correcly. Create the testing quest so that it will automatically step forward when calling <codeclass="docutils literal notranslate"><spanclass="pre">.progress()</span></code>, so you can make sure it works as intended.</p>
</section>
<sectionid="conclusions">
<h2><spanclass="section-number">14.4. </span>Conclusions<aclass="headerlink"href="#conclusions"title="Permalink to this headline">¶</a></h2>
<p>What we created here is just the framework for questing. The actual complexity will come when creating the quests themselves (that is, implementing the <codeclass="docutils literal notranslate"><spanclass="pre">step_<current_step>(*args,</span><spanclass="pre">**kwargs)</span></code> methods), which is something we’ll get to later, in <aclass="reference internal"href="../Part4/Beginner-Tutorial-Part4-Overview.html"><spanclass="doc std std-doc">Part 4</span></a> of this tutorial.</p>