<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>
<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">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>All the non-dead values map to a loss of 1d4 in one of the six Abilities (but you get HP back). We need to map back to this from the above table. One also cannot have less than -10 Ability bonus, if you do, you die too.</p>
<divclass="highlight-python notranslate"><divclass="highlight"><pre><span></span><spanclass="c1"># in mygame/evadventure/rules.py </span>
<p>Here we roll on the ‘death table’ from the rules to see what happens. We give the character
a message if they survive, to let them know what happened.</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 character!</p>
</section>
</section>
<sectionid="testing">
<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 result as-if the random result was 4.</p>
<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>
<p>This concludes all the core rule mechanics of <em>Knave</em> - the rules used during play. We noticed here that we are going to soon need to establish how our <em>Character</em> actually stores data. So we will address that next.</p>