<h1><spanclass="section-number">2. </span>Rules and dice rolling<aclass="headerlink"href="#rules-and-dice-rolling"title="Permalink to this headline">¶</a></h1>
<p>In <em>EvAdventure</em> we have decided to use the <aclass="reference external"href="https://www.drivethrurpg.com/product/250888/Knave">Knave</a>
RPG ruleset. This is commercial, but released under Creative Commons 4.0, meaning it’s okay to share and
adapt <em>Knave</em> for any purpose, even commercially. If you don’t want to buy it but still follow
along, you can find a <aclass="reference external"href="http://abominablefancy.blogspot.com/2018/10/knaves-fancypants.html">free fan-version here</a>.</p>
<sectionid="summary-of-knave-rules">
<h2><spanclass="section-number">2.1. </span>Summary of <em>Knave</em> rules<aclass="headerlink"href="#summary-of-knave-rules"title="Permalink to this headline">¶</a></h2>
<p>Knave, being inspired by early Dungeons & Dragons, is very simple.</p>
and <em>Charisma</em> (CHA). These are rated from <codeclass="docutils literal notranslate"><spanclass="pre">+1</span></code> to <codeclass="docutils literal notranslate"><spanclass="pre">+10</span></code>.</p></li>
<li><p>Rolls are made with a twenty-sided die (<codeclass="docutils literal notranslate"><spanclass="pre">1d20</span></code>), usually adding a suitable Ability bonus to the roll.</p></li>
<li><p>If you roll <em>with advantage</em>, you roll <codeclass="docutils literal notranslate"><spanclass="pre">2d20</span></code> and pick the
<em>highest</em> value, If you roll <em>with disadvantage</em>, you roll <codeclass="docutils literal notranslate"><spanclass="pre">2d20</span></code> and pick the <em>lowest</em>.</p></li>
<li><p>Rolling a natural <codeclass="docutils literal notranslate"><spanclass="pre">1</span></code> is a <em>critical failure</em>. A natural <codeclass="docutils literal notranslate"><spanclass="pre">20</span></code> is a <em>critical success</em>. Rolling such
in combat means your weapon or armor loses quality, which will eventually destroy it.</p></li>
<li><p>A <em>saving throw</em> (trying to succeed against the environment) means making a roll to beat <codeclass="docutils literal notranslate"><spanclass="pre">15</span></code> (always).
So if you are lifting a heavy stone and have <codeclass="docutils literal notranslate"><spanclass="pre">STR</span><spanclass="pre">+2</span></code>, you’d roll <codeclass="docutils literal notranslate"><spanclass="pre">1d20</span><spanclass="pre">+</span><spanclass="pre">2</span></code> and hope the result
is higher than <codeclass="docutils literal notranslate"><spanclass="pre">15</span></code>.</p></li>
<li><p>An <em>opposed saving throw</em> means beating the enemy’s suitable Ability ‘defense’, which is always their
<codeclass="docutils literal notranslate"><spanclass="pre">Ability</span><spanclass="pre">bonus</span><spanclass="pre">+</span><spanclass="pre">10</span></code>. So if you have <codeclass="docutils literal notranslate"><spanclass="pre">STR</span><spanclass="pre">+1</span></code> and are arm wrestling someone with <codeclass="docutils literal notranslate"><spanclass="pre">STR</span><spanclass="pre">+2</span></code>, you roll
<codeclass="docutils literal notranslate"><spanclass="pre">1d20</span><spanclass="pre">+</span><spanclass="pre">1</span></code> and hope to roll higher than <codeclass="docutils literal notranslate"><spanclass="pre">2</span><spanclass="pre">+</span><spanclass="pre">10</span><spanclass="pre">=</span><spanclass="pre">12</span></code>.</p></li>
<li><p>A special bonus is <codeclass="docutils literal notranslate"><spanclass="pre">Armor</span></code>, <codeclass="docutils literal notranslate"><spanclass="pre">+1</span></code> is unarmored, additional armor is given by equipment. Melee attacks
test <codeclass="docutils literal notranslate"><spanclass="pre">STR</span></code> versus the <codeclass="docutils literal notranslate"><spanclass="pre">Armor</span></code> defense value while ranged attacks uses <codeclass="docutils literal notranslate"><spanclass="pre">WIS</span></code> vs <codeclass="docutils literal notranslate"><spanclass="pre">Armor</span></code>.</p></li>
<li><p><em>Knave</em> has no skills or classes. Everyone can use all items and using magic means having a special
‘rune stone’ in your hands; one spell per stone and day.</p></li>
<li><p>A character has <codeclass="docutils literal notranslate"><spanclass="pre">CON</span><spanclass="pre">+</span><spanclass="pre">10</span></code> carry ‘slots’. Most normal items uses one slot, armor and large weapons uses
two or three.</p></li>
<li><p>Healing is random, <codeclass="docutils literal notranslate"><spanclass="pre">1d8</span><spanclass="pre">+</span><spanclass="pre">CON</span></code> health healed after food and sleep.</p></li>
<li><p>Monster difficulty is listed by hy many 1d8 HP they have; this is called their “hit die” or HD. If
needing to test Abilities, monsters have HD bonus in every Ability.</p></li>
<li><p>Monsters have a <em>morale rating</em>. When things go bad, they have a chance to panic and flee if
rolling <codeclass="docutils literal notranslate"><spanclass="pre">2d6</span></code> over their morale rating.</p></li>
<li><p>All Characters in <em>Knave</em> are mostly randomly generated. HP is <codeclass="docutils literal notranslate"><spanclass="pre"><level>d8</span></code> but we give every
new character max HP to start.</p></li>
<li><p><em>Knave</em> also have random tables, such as for starting equipment and to see if dying when
hitting 0. Death, if it happens, is permanent.</p></li>
</ul>
</section>
<sectionid="making-a-rule-module">
<h2><spanclass="section-number">2.2. </span>Making a rule module<aclass="headerlink"href="#making-a-rule-module"title="Permalink to this headline">¶</a></h2>
<blockquote>
<div><p>Create a new module mygame/evadventure/rules.py</p>
</div></blockquote>
<asideclass="sidebar">
<p>A complete version of the rule module is found in
<p>There are three broad sets of rules for most RPGS:</p>
<ulclass="simple">
<li><p>Character generation rules, often only used during character creation</p></li>
<li><p>Regular gameplay rules - rolling dice and resolving game situations</p></li>
<li><p>Character improvement - getting and spending experience to improve the character</p></li>
</ul>
<p>We want our <codeclass="docutils literal notranslate"><spanclass="pre">rules</span></code> module to cover as many aspeects of what we’d otherwise would have to look up
in a rulebook.</p>
</section>
<sectionid="rolling-dice">
<h2><spanclass="section-number">2.3. </span>Rolling dice<aclass="headerlink"href="#rolling-dice"title="Permalink to this headline">¶</a></h2>
<p>We will start by making a dice roller. Let’s group all of our dice rolling into a structure like this
<p>This groups all dice-related code into one ‘container’ that is easy to import. But it’s mostly a matter
of taste. You <em>could</em> also break up the class’ methods into normal functions at the top-level of the
module if you wanted.</p>
</aside>
<p>This structure (called a <em>singleton</em>) means we group all dice rolls into one class that we then initiate
into a variable <codeclass="docutils literal notranslate"><spanclass="pre">dice</span></code> at the end of the module. This means that we can do the following from other
<h3><spanclass="section-number">2.3.1. </span>Generic dice roller<aclass="headerlink"href="#generic-dice-roller"title="Permalink to this headline">¶</a></h3>
<p>We want to be able to do <codeclass="docutils literal notranslate"><spanclass="pre">roll("1d20")</span></code> and get a random result back from the roll.</p>
<divclass="highlight-python notranslate"><divclass="highlight"><pre><span></span><spanclass="c1"># in mygame/evadventure/rules.py </span>
<p>For this tutorial we have opted to not use any contribs, so we create
our own dice roller. But normally you could instead use the <aclass="reference internal"href="../../../Contribs/Contrib-Dice.html"><spanclass="doc std std-doc">dice</span></a> contrib for this.
We’ll point out possible helpful contribs in sidebars as we proceed.</p>
</aside>
<p>The <codeclass="docutils literal notranslate"><spanclass="pre">randint</span></code> standard Python library module produces a random integer<br/>
<li><p>For a certain <codeclass="docutils literal notranslate"><spanclass="pre">number</span></code> of times …</p></li>
<li><p>… create a random integer between <codeclass="docutils literal notranslate"><spanclass="pre">1</span></code> and <codeclass="docutils literal notranslate"><spanclass="pre">diesize</span></code> …</p></li>
<li><p>… and <codeclass="docutils literal notranslate"><spanclass="pre">sum</span></code> all those integers together.</p></li>
</ul>
<p>You could write the same thing less compactly like this:</p>
<p>Note that <codeclass="docutils literal notranslate"><spanclass="pre">range</span></code> generates a value <codeclass="docutils literal notranslate"><spanclass="pre">0...number-1</span></code>. We use <codeclass="docutils literal notranslate"><spanclass="pre">_</span></code> in the <codeclass="docutils literal notranslate"><spanclass="pre">for</span></code> loop to
indicate we don’t really care what this value is - we just want to repeat the loop
a certain amount of times.</p>
</aside>
<p>We don’t ever expect end users to call this method; if we did, we would have to validate the inputs
much more - We would have to make sure that <codeclass="docutils literal notranslate"><spanclass="pre">number</span></code> or <codeclass="docutils literal notranslate"><spanclass="pre">diesize</span></code> are valid inputs and not
crazy big so the loop takes forever!</p>
</section>
<sectionid="rolling-with-advantage">
<h3><spanclass="section-number">2.3.2. </span>Rolling with advantage<aclass="headerlink"href="#rolling-with-advantage"title="Permalink to this headline">¶</a></h3>
<p>Now that we have the generic roller, we can start using it to do a more complex roll.</p>
<p>The <codeclass="docutils literal notranslate"><spanclass="pre">min()</span></code> and <codeclass="docutils literal notranslate"><spanclass="pre">max()</span></code> functions are standard Python fare for getting the biggest/smallest
of two arguments.</p>
</section>
<sectionid="saving-throws">
<h3><spanclass="section-number">2.3.3. </span>Saving throws<aclass="headerlink"href="#saving-throws"title="Permalink to this headline">¶</a></h3>
<p>We want the saving throw to itself figure out if it succeeded or not. This means it needs to know
the Ability bonus (like STR <codeclass="docutils literal notranslate"><spanclass="pre">+1</span></code>). It would be convenient if we could just pass the entity
doing the saving throw to this method, tell it what type of save was needed, and then
<p>The return will be a boolean <codeclass="docutils literal notranslate"><spanclass="pre">True/False</span></code> if they pass, as well as a <codeclass="docutils literal notranslate"><spanclass="pre">quality</span></code> that tells us if
a perfect fail/success was rolled or not.</p>
<p>To make the saving throw method this clever, we need to think some more about how we want to store our
data on the character.</p>
<p>For our purposes it sounds reasonable that we will be using <aclass="reference internal"href="../../../Components/Attributes.html"><spanclass="doc std std-doc">Attributes</span></a> for storing
the Ability scores. To make it easy, we will name them the same as the
<aclass="reference internal"href="Beginner-Tutorial-Utilities.html#enums"><spanclass="std std-doc">Enum values</span></a> we set up in the previous lesson. So if we have
an enum <codeclass="docutils literal notranslate"><spanclass="pre">STR</span><spanclass="pre">=</span><spanclass="pre">"strength"</span></code>, we want to store the Ability on the character as an Attribute <codeclass="docutils literal notranslate"><spanclass="pre">strength</span></code>.</p>
<p>From the Attribute documentation, we can see that we can use <codeclass="docutils literal notranslate"><spanclass="pre">AttributeProperty</span></code> to make it so the
Attribute is available as <codeclass="docutils literal notranslate"><spanclass="pre">character.strength</span></code>, and this is what we will do.</p>
<p>So, in short, we’ll create the saving throws method with the assumption that we will be able to do
<codeclass="docutils literal notranslate"><spanclass="pre">character.strength</span></code>, <codeclass="docutils literal notranslate"><spanclass="pre">character.constitution</span></code>, <codeclass="docutils literal notranslate"><spanclass="pre">character.charisma</span></code> etc to get the relevant Abilities.</p>
<divclass="highlight-python notranslate"><divclass="highlight"><pre><span></span><spanclass="c1"># in mygame/evadventure/rules.py </span>
<p>The <codeclass="docutils literal notranslate"><spanclass="pre">getattr(obj,</span><spanclass="pre">attrname,</span><spanclass="pre">default)</span></code> function is a very useful Python tool for getting an attribute
off an object and getting a default value if the attribute is not defined.</p>
</section>
<sectionid="opposed-saving-throw">
<h3><spanclass="section-number">2.3.4. </span>Opposed saving throw<aclass="headerlink"href="#opposed-saving-throw"title="Permalink to this headline">¶</a></h3>
<p>With the building pieces we already created, this method is simple. Remember that the defense you have
to beat is always the relevant bonus + 10 in <em>Knave</em>. So if the enemy defends with <codeclass="docutils literal notranslate"><spanclass="pre">STR</span><spanclass="pre">+3</span></code>, you must
roll higher than <codeclass="docutils literal notranslate"><spanclass="pre">13</span></code>.</p>
<divclass="highlight-python notranslate"><divclass="highlight"><pre><span></span><spanclass="c1"># in mygame/evadventure/rules.py </span>
<h3><spanclass="section-number">2.3.5. </span>Morale check<aclass="headerlink"href="#morale-check"title="Permalink to this headline">¶</a></h3>
<p>We will make the assumption that the <codeclass="docutils literal notranslate"><spanclass="pre">morale</span></code> value is available from the creature simply as
<codeclass="docutils literal notranslate"><spanclass="pre">monster.morale</span></code> - we need to remember to make this so later!</p>
<p>In <em>Knave</em>, a creature have roll with <codeclass="docutils literal notranslate"><spanclass="pre">2d6</span></code> equal or under its morale to not flee or surrender
when things go south. The standard morale value is 9.</p>
<divclass="highlight-python notranslate"><divclass="highlight"><pre><span></span><spanclass="c1"># in mygame/evadventure/rules.py </span>
<h3><spanclass="section-number">2.3.6. </span>Roll for Healing<aclass="headerlink"href="#roll-for-healing"title="Permalink to this headline">¶</a></h3>
<p>To be able to handle healing, we need to make some more assumptions about how we store
health on game entities. We will need <codeclass="docutils literal notranslate"><spanclass="pre">hp_max</span></code> (the total amount of available HP) and <codeclass="docutils literal notranslate"><spanclass="pre">hp</span></code>
(the current health value). We again assume these will be available as <codeclass="docutils literal notranslate"><spanclass="pre">obj.hp</span></code> and <codeclass="docutils literal notranslate"><spanclass="pre">obj.hp_max</span></code>.</p>
<p>According to the rules, after consuming a ration and having a full night’s sleep, a character regains
<p>We make another assumption here - that <codeclass="docutils literal notranslate"><spanclass="pre">character.heal()</span></code> is a thing. We tell this function how
much the character should heal, and it will do so, making sure to not heal more than its max
number of HPs</p>
<blockquote>
<div><p>Knowing what is available on the character and what rule rolls we need is a bit of a chicken-and-egg
problem. We will make sure to implement the matching <em>Character</em> class next lesson.</p>
</div></blockquote>
</section>
<sectionid="rolling-on-a-table">
<h3><spanclass="section-number">2.3.7. </span>Rolling on a table<aclass="headerlink"href="#rolling-on-a-table"title="Permalink to this headline">¶</a></h3>
<p>We occasionally need to roll on a ‘table’ - a selection of choices. There are two main table-types
we need to support:</p>
<p>Simply one element per row of the table (same odds to get each result).</p>
<spanclass="c1"># if we get here we must have set a dieroll producing a value </span>
<spanclass="c1"># outside of the table boundaries - raise error</span>
<spanclass="k">raise</span><spanclass="ne">RuntimeError</span><spanclass="p">(</span><spanclass="s2">"roll_random_table: Invalid die roll"</span><spanclass="p">)</span>
<p>If <codeclass="docutils literal notranslate"><spanclass="pre">valrange</span></code> is the string <codeclass="docutils literal notranslate"><spanclass="pre">1-5</span></code>, then <codeclass="docutils literal notranslate"><spanclass="pre">valrange.split("-",</span><spanclass="pre">1)</span></code> would result in a tuple <codeclass="docutils literal notranslate"><spanclass="pre">("1",</span><spanclass="pre">"5")</span></code>.
But if the string was in fact just <codeclass="docutils literal notranslate"><spanclass="pre">"20"</span></code> (possible for a single entry in an RPG table), this would
lead to an error since it would only split out a single element - and we expected two.</p>
<p>By using <codeclass="docutils literal notranslate"><spanclass="pre">*maxval</span></code> (with the <codeclass="docutils literal notranslate"><spanclass="pre">*</span></code>), <codeclass="docutils literal notranslate"><spanclass="pre">maxval</span></code> is told to expect <em>0 or more</em> elements in a tuple.
So the result for <codeclass="docutils literal notranslate"><spanclass="pre">1-5</span></code> will be <codeclass="docutils literal notranslate"><spanclass="pre">("1",</span><spanclass="pre">("5",))</span></code> and for <codeclass="docutils literal notranslate"><spanclass="pre">20</span></code> it will become <codeclass="docutils literal notranslate"><spanclass="pre">("20",</span><spanclass="pre">())</span></code>. In the line</p>
<p>we check if <codeclass="docutils literal notranslate"><spanclass="pre">maxval</span></code> actually has a value <codeclass="docutils literal notranslate"><spanclass="pre">("5",)</span></code> or if its empty <codeclass="docutils literal notranslate"><spanclass="pre">()</span></code>. The result is either
<codeclass="docutils literal notranslate"><spanclass="pre">"5"</span></code> or the value of <codeclass="docutils literal notranslate"><spanclass="pre">minval</span></code>.</p>
</section>
<sectionid="roll-for-death">
<h3><spanclass="section-number">2.3.8. </span>Roll for death<aclass="headerlink"href="#roll-for-death"title="Permalink to this headline">¶</a></h3>
<p>While original Knave suggests hitting 0 HP means insta-death, we will grab the optional “death table”
from the “prettified” Knave’s optional rules to make it a little less punishing. We also changed the
result of <codeclass="docutils literal notranslate"><spanclass="pre">2</span></code> to ‘dead’ since we don’t simulate ‘dismemberment’ in this tutorial:</p>
<p>We don’t yet know what ‘killing the character’ technically means, so we mark this as <codeclass="docutils literal notranslate"><spanclass="pre">TODO</span></code> and
return to it in a later lesson. We just know that we need to do <em>something</em> here to kill off the
<h2><spanclass="section-number">2.4. </span>Testing<aclass="headerlink"href="#testing"title="Permalink to this headline">¶</a></h2>
<blockquote>
<div><p>Make a new module <codeclass="docutils literal notranslate"><spanclass="pre">mygame/evadventure/tests/test_rules.py</span></code></p>
</div></blockquote>
<p>Testing the <codeclass="docutils literal notranslate"><spanclass="pre">rules</span></code> module will also showcase some very useful tools when testing.</p>
<spanclass="c1"># test of the other rule methods below ...</span>
</pre></div>
</div>
<p>As before, run the specific test with</p>
<divclass="highlight-none notranslate"><divclass="highlight"><pre><span></span>evennia test --settings settings.py .evadventure.tests.test_rules
</pre></div>
</div>
<sectionid="mocking-and-patching">
<h3><spanclass="section-number">2.4.1. </span>Mocking and patching<aclass="headerlink"href="#mocking-and-patching"title="Permalink to this headline">¶</a></h3>
<p>The <codeclass="docutils literal notranslate"><spanclass="pre">setUp</span></code> method is a special method of the testing class. It will be run before every
test method. We use <codeclass="docutils literal notranslate"><spanclass="pre">super().setUp()</span></code> to make sure the parent class’ version of this method
always fire. Then we create a fresh <codeclass="docutils literal notranslate"><spanclass="pre">EvAdventureRollEngine</span></code> we can test with.</p>
<p>In our test, we import <codeclass="docutils literal notranslate"><spanclass="pre">patch</span></code> from the <codeclass="docutils literal notranslate"><spanclass="pre">unittest.mock</span></code> library. This is a very useful tool for testing.
Normally the <codeclass="docutils literal notranslate"><spanclass="pre">randint</span></code> function we imported in <codeclass="docutils literal notranslate"><spanclass="pre">rules</span></code> will return a random value. That’s very hard to
test for, since the value will be different every test.</p>
<p>With <codeclass="docutils literal notranslate"><spanclass="pre">@patch</span></code> (this is called a <em>decorator</em>), we temporarily replace <codeclass="docutils literal notranslate"><spanclass="pre">rules.randint</span></code> with a ‘mock’ - a
dummy entity. This mock is passed into the testing method. We then take this <codeclass="docutils literal notranslate"><spanclass="pre">mock_randint</span></code> and set
<codeclass="docutils literal notranslate"><spanclass="pre">.return_value</span><spanclass="pre">=</span><spanclass="pre">4</span></code> on it.</p>
<p>Adding <codeclass="docutils literal notranslate"><spanclass="pre">return_value</span></code> to the mock means that every time this mock is called, it will return 4. For the
duration of the test we can now check with <codeclass="docutils literal notranslate"><spanclass="pre">self.assertEqual</span></code> that our <codeclass="docutils literal notranslate"><spanclass="pre">roll</span></code> method always returns a
<p>There are <aclass="reference external"href="https://realpython.com/python-mock-library/">many resources for understanding mock</a>, refer to
them for further help.</p>
<blockquote>
<div><p>The <codeclass="docutils literal notranslate"><spanclass="pre">EvAdventureRollEngine</span></code> have many methods to test. We leave this as an extra exercise!</p>
</div></blockquote>
</section>
</section>
<sectionid="summary">
<h2><spanclass="section-number">2.5. </span>Summary<aclass="headerlink"href="#summary"title="Permalink to this headline">¶</a></h2>