<h1><spanclass="section-number">5. </span>Handling Equipment<aclass="headerlink"href="#handling-equipment"title="Permalink to this headline">¶</a></h1>
<p>In <em>Knave</em>, you have a certain number of inventory “slots”. The amount of slots is given by <codeclass="docutils literal notranslate"><spanclass="pre">CON</span><spanclass="pre">+</span><spanclass="pre">10</span></code>. All items (except coins) have a <codeclass="docutils literal notranslate"><spanclass="pre">size</span></code>, indicating how many slots it uses. You can’t carry more items than you have slot-space for. Also items wielded or worn count towards the slots.</p>
<p>We still need to track what the character is using however: What weapon they have readied affects the damage they can do. The shield, helmet and armor they use affects their defense.</p>
<p>We have already set up the possible ‘wear/wield locations’ when we defined our Objects
<aclass="reference internal"href="Beginner-Tutorial-Objects.html"><spanclass="doc std std-doc">in the previous lesson</span></a>. This is what we have in <codeclass="docutils literal notranslate"><spanclass="pre">enums.py</span></code>:</p>
<p>Basically, all the weapon/armor locations are exclusive - you can only have one item in each (or none). The BACKPACK is special - it contains any number of items (up to the maximum slot usage).</p>
<sectionid="equipmenthandler-that-saves">
<h2><spanclass="section-number">5.1. </span>EquipmentHandler that saves<aclass="headerlink"href="#equipmenthandler-that-saves"title="Permalink to this headline">¶</a></h2>
<blockquote>
<div><p>Create a new module <codeclass="docutils literal notranslate"><spanclass="pre">mygame/evadventure/equipment.py</span></code>.</p>
</div></blockquote>
<asideclass="sidebar">
<p>If you want to understand more about behind how Evennia uses handlers, there is a
<aclass="reference internal"href="../../Tutorial-Persistent-Handler.html"><spanclass="doc std std-doc">dedicated tutorial</span></a> talking about the principle.</p>
</aside>
<p>In default Evennia, everything you pick up will end up “inside” your character object (that is, have you as its <codeclass="docutils literal notranslate"><spanclass="pre">.location</span></code>). This is called your <em>inventory</em> and has no limit. We will keep ‘moving items into us’ when we pick them up, but we will add more functionality using an <em>Equipment handler</em>.</p>
<p>A handler is (for our purposes) an object that sits “on” another entity, containing functionality for doing one specific thing (managing equipment, in our case).</p>
<p>This is the start of our handler:</p>
<divclass="highlight-python notranslate"><divclass="highlight"><pre><span></span><spanclass="c1"># in mygame/evadventure/equipment.py </span>
<p>The <codeclass="docutils literal notranslate"><spanclass="pre">@lazy_property</span></code> works such that it will not load the handler until someone actually tries to fetch it with <codeclass="docutils literal notranslate"><spanclass="pre">character.equipment</span></code>. When that happens, we start up the handler and feed it <codeclass="docutils literal notranslate"><spanclass="pre">self</span></code> (the <codeclass="docutils literal notranslate"><spanclass="pre">Character</span></code> instance itself). This is what enters <codeclass="docutils literal notranslate"><spanclass="pre">__init__</span></code> as <codeclass="docutils literal notranslate"><spanclass="pre">.obj</span></code> in the <codeclass="docutils literal notranslate"><spanclass="pre">EquipmentHandler</span></code> code above.</p>
<p>So we now have a handler on the character, and the handler has a back-reference to the character it sits on.</p>
<p>Since the handler itself is just a regular Python object, we need to use the <codeclass="docutils literal notranslate"><spanclass="pre">Character</span></code> to store
our data - our <em>Knave</em> “slots”. We must save them to the database, because we want the server to remember them even after reloading.</p>
<p>Using <codeclass="docutils literal notranslate"><spanclass="pre">self.obj.attributes.add()</span></code> and <codeclass="docutils literal notranslate"><spanclass="pre">.get()</span></code> we save the data to the Character in a specially named <aclass="reference internal"href="../../../Components/Attributes.html"><spanclass="doc std std-doc">Attribute</span></a>. Since we use a <codeclass="docutils literal notranslate"><spanclass="pre">category</span></code>, we are unlikely to collide with
other Attributes.</p>
<p>Our storage structure is a <codeclass="docutils literal notranslate"><spanclass="pre">dict</span></code> with keys after our available <codeclass="docutils literal notranslate"><spanclass="pre">WieldLocation</span></code> enums. Each can only have one item except <codeclass="docutils literal notranslate"><spanclass="pre">WieldLocation.BACKPACK</span></code>, which is a list.</p>
</section>
<sectionid="connecting-the-equipmenthandler">
<h2><spanclass="section-number">5.2. </span>Connecting the EquipmentHandler<aclass="headerlink"href="#connecting-the-equipmenthandler"title="Permalink to this headline">¶</a></h2>
<p>Whenever an object leaves from one location to the next, Evennia will call a set of <em>hooks</em> (methods) on the object that moves, on the source-location and on its destination. This is the same for all moving things - whether it’s a character moving between rooms or an item being dropping from your hand to the ground.</p>
<p>We need to tie our new <codeclass="docutils literal notranslate"><spanclass="pre">EquipmentHandler</span></code> into this system. By reading the doc page on <aclass="reference internal"href="../../../Components/Objects.html"><spanclass="doc std std-doc">Objects</span></a>, or looking at the <aclass="reference internal"href="../../../api/evennia.objects.objects.html#evennia.objects.objects.DefaultObject.move_to"title="evennia.objects.objects.DefaultObject.move_to"><spanclass="xref myst py py-meth">DefaultObject.move_to</span></a> docstring, we’ll find out what hooks Evennia will call. Here <codeclass="docutils literal notranslate"><spanclass="pre">self</span></code> is the object being moved from <codeclass="docutils literal notranslate"><spanclass="pre">source_location</span></code> to <codeclass="docutils literal notranslate"><spanclass="pre">destination</span></code>:</p>
<olclass="simple">
<li><p><codeclass="docutils literal notranslate"><spanclass="pre">self.at_pre_move(destination)</span></code> (abort if return False)</p></li>
<li><p><codeclass="docutils literal notranslate"><spanclass="pre">source_location.at_pre_object_leave(self,</span><spanclass="pre">destination)</span></code> (abort if return False)</p></li>
<li><p><codeclass="docutils literal notranslate"><spanclass="pre">destination.at_pre_object_receive(self,</span><spanclass="pre">source_location)</span></code> (abort if return False)</p></li>
<p>All of these hooks can be overridden to customize movement behavior. In this case we are interested in controlling how items ‘enter’ and ‘leave’ our character - being ‘inside’ the character is the same as them ‘carrying’ it. We have three good hook-candidates to use for this.</p>
<ulclass="simple">
<li><p><codeclass="docutils literal notranslate"><spanclass="pre">.at_pre_object_receive</span></code> - used to check if you can actually pick something up, or if your equipment-store is full.</p></li>
<li><p><codeclass="docutils literal notranslate"><spanclass="pre">.at_object_receive</span></code> - used to add the item to the equipmenthandler</p></li>
<li><p><codeclass="docutils literal notranslate"><spanclass="pre">.at_object_leave</span></code> - used to remove the item from the equipmenthandler</p></li>
</ul>
<p>You could also picture using <codeclass="docutils literal notranslate"><spanclass="pre">.at_pre_object_leave</span></code> to restrict dropping (cursed?) items, but
<p>Above we have assumed the <codeclass="docutils literal notranslate"><spanclass="pre">EquipmentHandler</span></code> (<codeclass="docutils literal notranslate"><spanclass="pre">.equipment</span></code>) has methods <codeclass="docutils literal notranslate"><spanclass="pre">.validate_slot_usage</span></code>, <codeclass="docutils literal notranslate"><spanclass="pre">.add</span></code> and <codeclass="docutils literal notranslate"><spanclass="pre">.remove</span></code>. But we haven’t actually added them yet - we just put some reasonable names! Before we can use this, we need to go actually adding those methods.</p>
<p>When you do things like <codeclass="docutils literal notranslate"><spanclass="pre">create/drop</span><spanclass="pre">monster:NPC</span></code>, the npc will briefly be in your inventory before being dropped on the ground. Since an NPC is not a valid thing to equip, the EquipmentHandler will complain with an <codeclass="docutils literal notranslate"><spanclass="pre">EquipmentError</span></code> (we define this see below). So we need to</p>
</section>
<sectionid="expanding-the-equipmenthandler">
<h2><spanclass="section-number">5.3. </span>Expanding the Equipmenthandler<aclass="headerlink"href="#expanding-the-equipmenthandler"title="Permalink to this headline">¶</a></h2>
</section>
<sectionid="validate-slot-usage">
<h2><spanclass="section-number">5.4. </span><codeclass="docutils literal notranslate"><spanclass="pre">.validate_slot_usage</span></code><aclass="headerlink"href="#validate-slot-usage"title="Permalink to this headline">¶</a></h2>
<p>Let’s start with implementing the first method we came up with above, <codeclass="docutils literal notranslate"><spanclass="pre">validate_slot_usage</span></code>:</p>
<spanclass="c1"># in case we mix with non-evadventure objects</span>
<spanclass="k">raise</span><spanclass="n">EquipmentError</span><spanclass="p">(</span><spanclass="sa">f</span><spanclass="s2">"</span><spanclass="si">{</span><spanclass="n">obj</span><spanclass="o">.</span><spanclass="n">key</span><spanclass="si">}</span><spanclass="s2"> is not something that can be equipped."</span><spanclass="p">)</span>
<p>The <codeclass="docutils literal notranslate"><spanclass="pre">@property</span></code> decorator turns a method into a property so you don’t need to ‘call’ it.
That is, you can access <codeclass="docutils literal notranslate"><spanclass="pre">.max_slots</span></code> instead of <codeclass="docutils literal notranslate"><spanclass="pre">.max_slots()</span></code>. In this case, it’s just a
little less to type.</p>
</aside>
<p>We add two helpers - the <codeclass="docutils literal notranslate"><spanclass="pre">max_slots</span></code><em>property</em> and <codeclass="docutils literal notranslate"><spanclass="pre">count_slots</span></code>, a method that calculate the current slots being in use. Let’s figure out how they work.</p>
<sectionid="max-slots">
<h3><spanclass="section-number">5.4.1. </span><codeclass="docutils literal notranslate"><spanclass="pre">.max_slots</span></code><aclass="headerlink"href="#max-slots"title="Permalink to this headline">¶</a></h3>
<p>For <codeclass="docutils literal notranslate"><spanclass="pre">max_slots</span></code>, remember that <codeclass="docutils literal notranslate"><spanclass="pre">.obj</span></code> on the handler is a back-reference to the <codeclass="docutils literal notranslate"><spanclass="pre">EvAdventureCharacter</span></code> we put this handler on. <codeclass="docutils literal notranslate"><spanclass="pre">getattr</span></code> is a Python method for retrieving a named property on an object. The <codeclass="docutils literal notranslate"><spanclass="pre">Enum</span></code><codeclass="docutils literal notranslate"><spanclass="pre">Ability.CON.value</span></code> is the string <codeclass="docutils literal notranslate"><spanclass="pre">Constitution</span></code> (check out the <aclass="reference internal"href="Beginner-Tutorial-Utilities.html"><spanclass="doc std std-doc">first Utility and Enums tutorial</span></a> if you don’t recall).</p>
<p>In our code we write <codeclass="docutils literal notranslate"><spanclass="pre">getattr(self.obj,</span><spanclass="pre">Ability.CON.value,</span><spanclass="pre">1)</span></code> - that extra <codeclass="docutils literal notranslate"><spanclass="pre">1</span></code> means that if there should happen to <em>not</em> be a property “Constitution” on <codeclass="docutils literal notranslate"><spanclass="pre">self.obj</span></code>, we should not error out but just return 1.</p>
</section>
<sectionid="count-slots">
<h3><spanclass="section-number">5.4.2. </span><codeclass="docutils literal notranslate"><spanclass="pre">.count_slots</span></code><aclass="headerlink"href="#count-slots"title="Permalink to this headline">¶</a></h3>
<p>In this helper we use two Python tools - the <codeclass="docutils literal notranslate"><spanclass="pre">sum()</span></code> function and a <aclass="reference external"href="https://www.w3schools.com/python/python_lists_comprehension.asp">list comprehension</a>. The former simply adds the values of any iterable together. The latter is a more efficient way to create a list:</p>
<divclass="highlight-none notranslate"><divclass="highlight"><pre><span></span>new_list = [item for item in some_iterable if condition]
all_above_5 = [num for num in range(10) if num > 5] # [6, 7, 8, 9]
all_below_5 = [num for num in range(10) if num < 5] # [0, 1, 2, 3, 4]
</pre></div>
</div>
<p>To make it easier to understand, try reading the last line above as “for every number in the range 0-9, pick all with a value below 5 and make a list of them”. You can also embed such comprehensions directly in a function call like <codeclass="docutils literal notranslate"><spanclass="pre">sum()</span></code> without using <codeclass="docutils literal notranslate"><spanclass="pre">[]</span></code> around it.</p>
<p>In <codeclass="docutils literal notranslate"><spanclass="pre">count_slots</span></code> we have this code:</p>
<p>We should be able to follow all except <codeclass="docutils literal notranslate"><spanclass="pre">slots.items()</span></code>. Since <codeclass="docutils literal notranslate"><spanclass="pre">slots</span></code> is a <codeclass="docutils literal notranslate"><spanclass="pre">dict</span></code>, we can use <codeclass="docutils literal notranslate"><spanclass="pre">.items()</span></code> to get a sequence of <codeclass="docutils literal notranslate"><spanclass="pre">(key,</span><spanclass="pre">value)</span></code> pairs. We store these in <codeclass="docutils literal notranslate"><spanclass="pre">slot</span></code> and <codeclass="docutils literal notranslate"><spanclass="pre">slotobj</span></code>. So the above can be understood as “for every <codeclass="docutils literal notranslate"><spanclass="pre">slot</span></code> and <codeclass="docutils literal notranslate"><spanclass="pre">slotobj</span></code>-pair in <codeclass="docutils literal notranslate"><spanclass="pre">slots</span></code>, check which slot location it is. If it is <em>not</em> in the backpack, get its size and add it to the list. Sum over all these
sizes”.</p>
<p>A less compact but maybe more readonable way to write this would be:</p>
<p>The same is done for the items actually in the BACKPACK slot. The total sizes are added
together.</p>
</section>
<sectionid="validating-slots">
<h3><spanclass="section-number">5.4.3. </span>Validating slots<aclass="headerlink"href="#validating-slots"title="Permalink to this headline">¶</a></h3>
<p>With these helpers in place, <codeclass="docutils literal notranslate"><spanclass="pre">validate_slot_usage</span></code> now becomes simple. We use <codeclass="docutils literal notranslate"><spanclass="pre">max_slots</span></code> to see how much we can carry. We then get how many slots we are already using (with <codeclass="docutils literal notranslate"><spanclass="pre">count_slots</span></code>) and see if our new <codeclass="docutils literal notranslate"><spanclass="pre">obj</span></code>’s size would be too much for us.</p>
</section>
</section>
<sectionid="add-and-remove">
<h2><spanclass="section-number">5.5. </span><codeclass="docutils literal notranslate"><spanclass="pre">.add</span></code> and <codeclass="docutils literal notranslate"><spanclass="pre">.remove</span></code><aclass="headerlink"href="#add-and-remove"title="Permalink to this headline">¶</a></h2>
<p>We will make it so <codeclass="docutils literal notranslate"><spanclass="pre">.add</span></code> puts something in the <codeclass="docutils literal notranslate"><spanclass="pre">BACKPACK</span></code> location and <codeclass="docutils literal notranslate"><spanclass="pre">remove</span></code> drops it, wherever it is (even if it was in your hands).</p>
<spanclass="k">elif</span><spanclass="n">obj_or_slot</span><spanclass="ow">in</span><spanclass="n">slots</span><spanclass="p">[</span><spanclass="n">WieldLocation</span><spanclass="o">.</span><spanclass="n">BACKPACK</span><spanclass="p">]:</span><spanclass="c1"># obj in backpack slot</span>
<p>In <codeclass="docutils literal notranslate"><spanclass="pre">.add</span></code>, we make use of <codeclass="docutils literal notranslate"><spanclass="pre">validate_slot_usage</span></code> to
double-check we can actually fit the thing, then we add the item to the backpack.</p>
<p>In <codeclass="docutils literal notranslate"><spanclass="pre">.remove</span></code>, we allow emptying both by <codeclass="docutils literal notranslate"><spanclass="pre">WieldLocation</span></code> or by explicitly saying which object to remove. Note that the first <codeclass="docutils literal notranslate"><spanclass="pre">if</span></code> statement checks if <codeclass="docutils literal notranslate"><spanclass="pre">obj_or_slot</span></code> is a slot. So if that fails then code in the other <codeclass="docutils literal notranslate"><spanclass="pre">elif</span></code> can safely assume that it must instead be an object!</p>
<p>Any removed objects are returned. If we gave <codeclass="docutils literal notranslate"><spanclass="pre">BACKPACK</span></code> as the slot, we empty the backpack and return all items inside it.</p>
<p>Whenever we change the equipment loadout we must make sure to <codeclass="docutils literal notranslate"><spanclass="pre">._save()</span></code> the result, or it will be lost after a server reload.</p>
</section>
<sectionid="moving-things-around">
<h2><spanclass="section-number">5.6. </span>Moving things around<aclass="headerlink"href="#moving-things-around"title="Permalink to this headline">¶</a></h2>
<p>With the help of <codeclass="docutils literal notranslate"><spanclass="pre">.remove()</span></code> and <codeclass="docutils literal notranslate"><spanclass="pre">.add()</span></code> we can get things in and out of the <codeclass="docutils literal notranslate"><spanclass="pre">BACKPACK</span></code> equipment location. We also need to grab stuff from the backpack and wield or wear it. We add a <codeclass="docutils literal notranslate"><spanclass="pre">.move</span></code> method on the <codeclass="docutils literal notranslate"><spanclass="pre">EquipmentHandler</span></code> to do this:</p>
<p>Here we remember that every <codeclass="docutils literal notranslate"><spanclass="pre">EvAdventureObject</span></code> has an <codeclass="docutils literal notranslate"><spanclass="pre">inventory_use_slot</span></code> property that tells us where it goes. So we just need to move the object to that slot, replacing whatever is in that place from before. Anything we replace goes back to the backpack.</p>
</section>
<sectionid="get-everything">
<h2><spanclass="section-number">5.7. </span>Get everything<aclass="headerlink"href="#get-everything"title="Permalink to this headline">¶</a></h2>
<p>In order to visualize our inventory, we need some method to get everything we are carrying.</p>
<p>Here we get all the equipment locations and add their contents together into a list of tuples
<codeclass="docutils literal notranslate"><spanclass="pre">[(item,</span><spanclass="pre">WieldLocation),</span><spanclass="pre">...]</span></code>. This is convenient for display.</p>
</section>
<sectionid="weapon-and-armor">
<h2><spanclass="section-number">5.8. </span>Weapon and armor<aclass="headerlink"href="#weapon-and-armor"title="Permalink to this headline">¶</a></h2>
<p>It’s convenient to have the <codeclass="docutils literal notranslate"><spanclass="pre">EquipmentHandler</span></code> easily tell you what weapon is currently wielded and what <em>armor</em> level all worn equipment provides. Otherwise you’d need to figure out what item is in which wield-slot and to add up armor slots manually every time you need to know.</p>
<p>In the <codeclass="docutils literal notranslate"><spanclass="pre">.armor()</span></code> method we get the item (if any) out of each relevant wield-slot (body, shield, head), and grab their <codeclass="docutils literal notranslate"><spanclass="pre">armor</span></code> Attribute. We then <codeclass="docutils literal notranslate"><spanclass="pre">sum()</span></code> them all up.</p>
<p>In <codeclass="docutils literal notranslate"><spanclass="pre">.weapon()</span></code>, we simply check which of the possible weapon slots (weapon-hand or two-hands) have something in them. If not we fall back to the ‘Bare Hands’ object we created in the <aclass="reference internal"href="Beginner-Tutorial-Objects.html#your-bare-hands"><spanclass="std std-doc">Object tutorial lesson</span></a> earlier.</p>
<sectionid="fixing-the-character-class">
<h3><spanclass="section-number">5.8.1. </span>Fixing the Character class<aclass="headerlink"href="#fixing-the-character-class"title="Permalink to this headline">¶</a></h3>
<p>So we have added our equipment handler which validate what we put in it. This will however lead to a problem when we create things like NPCs in game, e.g. with</p>
<p>The problem is that when the/ monster is created it will briefly appear in your inventory before being dropped, so this code will fire on you when you do that (assuming you are an <codeclass="docutils literal notranslate"><spanclass="pre">EvAdventureCharacter</span></code>):</p>
<p>This means that the equipmenthandler will check the NPC, and since it’s not a equippable thing, an <codeclass="docutils literal notranslate"><spanclass="pre">EquipmentError</span></code> will be raised, failing the creation. Since we want to be able to create npcs etc easily, we will handle this error with a <codeclass="docutils literal notranslate"><spanclass="pre">try...except</span></code> statement like so:</p>
<p>Using Evennia’s <codeclass="docutils literal notranslate"><spanclass="pre">logger.log_trace()</span></code> we catch the error and direct it to the server log. This allows you to see if there are real errors here as well, but once things work and these errors are spammy, you can also just replace the <codeclass="docutils literal notranslate"><spanclass="pre">logger.log_trace()</span></code> line with a <codeclass="docutils literal notranslate"><spanclass="pre">pass</span></code> to hide these errors.</p>
</section>
</section>
<sectionid="extra-credits">
<h2><spanclass="section-number">5.9. </span>Extra credits<aclass="headerlink"href="#extra-credits"title="Permalink to this headline">¶</a></h2>
<p>This covers the basic functionality of the equipment handler. There are other useful methods that
can be added:</p>
<ulclass="simple">
<li><p>Given an item, figure out which equipment slot it is currently in</p></li>
<li><p>Make a string representing the current loadout</p></li>
<li><p>Get everything in the backpack (only)</p></li>
<li><p>Get all wieldable items (weapons, shields) from backpack</p></li>
<li><p>Get all usable items (items with a use-location of <codeclass="docutils literal notranslate"><spanclass="pre">BACKPACK</span></code>) from the backpack</p></li>
</ul>
<p>Experiment with adding those. A full example is found in
<p>To test the <codeclass="docutils literal notranslate"><spanclass="pre">EquipmentHandler</span></code>, easiest is create an <codeclass="docutils literal notranslate"><spanclass="pre">EvAdventureCharacter</span></code> (this should by now
have <codeclass="docutils literal notranslate"><spanclass="pre">EquipmentHandler</span></code> available on itself as <codeclass="docutils literal notranslate"><spanclass="pre">.equipment</span></code>) and a few test objects; then test
<h2><spanclass="section-number">5.11. </span>Summary<aclass="headerlink"href="#summary"title="Permalink to this headline">¶</a></h2>
<p><em>Handlers</em> are useful for grouping functionality together. Now that we spent our time making the <codeclass="docutils literal notranslate"><spanclass="pre">EquipmentHandler</span></code>, we shouldn’t need to worry about item-slots anymore - the handler ‘handles’ all the details for us. As long as we call its methods, the details can be forgotten about.</p>
<p>We also learned to use <em>hooks</em> to tie <em>Knave</em>’s custom equipment handling into Evennia.</p>
<p>With <codeclass="docutils literal notranslate"><spanclass="pre">Characters</span></code>, <codeclass="docutils literal notranslate"><spanclass="pre">Objects</span></code> and now <codeclass="docutils literal notranslate"><spanclass="pre">Equipment</span></code> in place, we should be able to move on to character generation - where players get to make their own character!</p>