evennia/docs/3.x/Howtos/Tutorial-for-basic-MUSH-like-game.html

780 lines
76 KiB
HTML
Raw Normal View History

2023-12-20 23:10:55 +01:00
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
2023-12-21 00:12:31 +01:00
<title>Tutorial for basic MUSH like game &#8212; Evennia 3.x documentation</title>
2023-12-20 23:10:55 +01:00
<link rel="stylesheet" href="../_static/nature.css" type="text/css" />
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
<script id="documentation_options" data-url_root="../" src="../_static/documentation_options.js"></script>
<script src="../_static/jquery.js"></script>
<script src="../_static/underscore.js"></script>
<script src="../_static/doctools.js"></script>
<script src="../_static/language_data.js"></script>
<link rel="shortcut icon" href="../_static/favicon.ico"/>
<link rel="index" title="Index" href="../genindex.html" />
<link rel="search" title="Search" href="../search.html" />
<link rel="next" title="Core Components" href="../Components/Components-Overview.html" />
<link rel="prev" title="Turn based Combat System" href="Turn-based-Combat-System.html" />
</head><body>
2023-12-21 00:12:31 +01:00
<div class="admonition important">
<p class="first admonition-title">Note</p>
<p class="last">You are reading an old version of the Evennia documentation. <a href="https://www.evennia.com/docs/latest/index.html">The latest version is here</a></p>.
</div>
2023-12-20 23:10:55 +01:00
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="../genindex.html" title="General Index"
accesskey="I">index</a></li>
<li class="right" >
<a href="../py-modindex.html" title="Python Module Index"
>modules</a> |</li>
<li class="right" >
<a href="../Components/Components-Overview.html" title="Core Components"
accesskey="N">next</a> |</li>
<li class="right" >
<a href="Turn-based-Combat-System.html" title="Turn based Combat System"
accesskey="P">previous</a> |</li>
2023-12-21 00:12:31 +01:00
<li class="nav-item nav-item-0"><a href="../index.html">Evennia 3.x</a> &#187;</li>
2023-12-20 23:10:55 +01:00
<li class="nav-item nav-item-1"><a href="Howtos-Overview.html" accesskey="U">Tutorials and How-Tos</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Tutorial for basic MUSH like game</a></li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
<p class="logo"><a href="../index.html">
<img class="logo" src="../_static/evennia_logo.png" alt="Logo"/>
</a></p>
<div id="searchbox" style="display: none" role="search">
<h3 id="searchlabel">Quick search</h3>
<div class="searchformwrapper">
<form class="search" action="../search.html" method="get">
<input type="text" name="q" aria-labelledby="searchlabel" />
<input type="submit" value="Go" />
</form>
</div>
</div>
<script>$('#searchbox').show(0);</script>
<h3><a href="../index.html">Table of Contents</a></h3>
<ul>
<li><a class="reference internal" href="#">Tutorial for basic MUSH like game</a><ul>
<li><a class="reference internal" href="#server-settings">Server Settings</a></li>
<li><a class="reference internal" href="#creating-the-character">Creating the Character</a></li>
<li><a class="reference internal" href="#character-generation">Character Generation</a><ul>
<li><a class="reference internal" href="#the-setpower-command">The +setpower command</a></li>
<li><a class="reference internal" href="#chargen-areas">Chargen areas</a></li>
<li><a class="reference internal" href="#testing-chargen">Testing chargen</a></li>
</ul>
</li>
<li><a class="reference internal" href="#combat-system">Combat System</a><ul>
<li><a class="reference internal" href="#attacking-with-the-attack-command">Attacking with the +attack command</a></li>
<li><a class="reference internal" href="#have-look-show-combat-scores">Have “look” show combat scores</a></li>
</ul>
</li>
<li><a class="reference internal" href="#npc-system">NPC system</a><ul>
<li><a class="reference internal" href="#creating-an-npc-with-createnpc">Creating an NPC with +createNPC</a></li>
<li><a class="reference internal" href="#editing-the-npc-with-editnpc">Editing the NPC with +editNPC</a></li>
<li><a class="reference internal" href="#making-the-npc-do-stuff-the-npc-command">Making the NPC do stuff - the +npc command</a></li>
</ul>
</li>
<li><a class="reference internal" href="#concluding-remarks">Concluding remarks</a></li>
</ul>
</li>
</ul>
<h4>Previous topic</h4>
<p class="topless"><a href="Turn-based-Combat-System.html"
title="previous chapter">Turn based Combat System</a></p>
<h4>Next topic</h4>
<p class="topless"><a href="../Components/Components-Overview.html"
title="next chapter">Core Components</a></p>
<div role="note" aria-label="source link">
<!--h3>This Page</h3-->
<ul class="this-page-menu">
<li><a href="../_sources/Howtos/Tutorial-for-basic-MUSH-like-game.md.txt"
rel="nofollow">Show Page Source</a></li>
</ul>
</div><h3>Links</h3>
<ul>
<li><a href="https://www.evennia.com/docs/latest/index.html">Documentation Top</a> </li>
<li><a href="https://www.evennia.com">Evennia Home</a> </li>
<li><a href="https://github.com/evennia/evennia">Github</a> </li>
<li><a href="http://games.evennia.com">Game Index</a> </li>
<li>
<a href="https://discord.gg/AJJpcRUhtF">Discord</a> -
<a href="https://github.com/evennia/evennia/discussions">Discussions</a> -
<a href="https://evennia.blogspot.com/">Blog</a>
</li>
</ul>
</div>
</div>
<div class="bodywrapper">
<div class="body" role="main">
<section class="tex2jax_ignore mathjax_ignore" id="tutorial-for-basic-mush-like-game">
<h1>Tutorial for basic MUSH like game<a class="headerlink" href="#tutorial-for-basic-mush-like-game" title="Permalink to this headline"></a></h1>
<p>This tutorial lets you code a small but complete and functioning MUSH-like game in Evennia. A
<a class="reference external" href="https://en.wikipedia.org/wiki/MUSH">MUSH</a> is, for our purposes, a class of roleplay-centric games
focused on free form storytelling. Even if you are not interested in MUSH:es, this is still a good
first game-type to try since its not so code heavy. You will be able to use the same principles for
building other types of games.</p>
<p>The tutorial starts from scratch. If you did the <a class="reference internal" href="Beginner-Tutorial/Part1/Beginner-Tutorial-Part1-Overview.html"><span class="doc std std-doc">First Steps Coding</span></a> tutorial
already you should have some ideas about how to do some of the steps already.</p>
<p>The following are the (very simplistic and cut-down) features we will implement (this was taken from
a feature request from a MUSH user new to Evennia). A Character in this system should:</p>
<ul class="simple">
<li><p>Have a “Power” score from 1 to 10 that measures how strong they are (stand-in for the stat
system).</p></li>
<li><p>Have a command (e.g. <code class="docutils literal notranslate"><span class="pre">+setpower</span> <span class="pre">4</span></code>) that sets their power (stand-in for character generation
code).</p></li>
<li><p>Have a command (e.g. <code class="docutils literal notranslate"><span class="pre">+attack</span></code>) that lets them roll their power and produce a “Combat Score”
between <code class="docutils literal notranslate"><span class="pre">1</span></code> and <code class="docutils literal notranslate"><span class="pre">10*Power</span></code>, displaying the result and editing their object to record this number
(stand-in for <code class="docutils literal notranslate"><span class="pre">+actions</span></code> in the command code).</p></li>
<li><p>Have a command that displays everyone in the room and what their most recent “Combat Score” roll
was (stand-in for the combat code).</p></li>
<li><p>Have a command (e.g. <code class="docutils literal notranslate"><span class="pre">+createNPC</span> <span class="pre">Jenkins</span></code>) that creates an NPC with full abilities.</p></li>
<li><p>Have a command to control NPCs, such as <code class="docutils literal notranslate"><span class="pre">+npc/cmd</span> <span class="pre">(name)=(command)</span></code> (stand-in for the NPC
controlling code).</p></li>
</ul>
<p>In this tutorial we will assume you are starting from an empty database without any previous
modifications.</p>
<section id="server-settings">
<h2>Server Settings<a class="headerlink" href="#server-settings" title="Permalink to this headline"></a></h2>
<p>To emulate a MUSH, the default <code class="docutils literal notranslate"><span class="pre">MULTISESSION_MODE=0</span></code> is enough (one unique session per
account/character). This is the default so you dont need to change anything. You will still be able
to puppet/unpuppet objects you have permission to, but there is no character selection out of the
box in this mode.</p>
<p>We will assume our game folder is called <code class="docutils literal notranslate"><span class="pre">mygame</span></code> henceforth. You should be fine with the default
SQLite3 database.</p>
</section>
<section id="creating-the-character">
<h2>Creating the Character<a class="headerlink" href="#creating-the-character" title="Permalink to this headline"></a></h2>
<p>First thing is to choose how our Character class works. We dont need to define a special NPC object
an NPC is after all just a Character without an Account currently controlling them.</p>
<p>Make your changes in the <code class="docutils literal notranslate"><span class="pre">mygame/typeclasses/characters.py</span></code> file:</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="c1"># mygame/typeclasses/characters.py</span>
<span class="kn">from</span> <span class="nn">evennia</span> <span class="kn">import</span> <span class="n">DefaultCharacter</span>
<span class="k">class</span> <span class="nc">Character</span><span class="p">(</span><span class="n">DefaultCharacter</span><span class="p">):</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> [...]</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="k">def</span> <span class="nf">at_object_creation</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="s2">&quot;This is called when object is first created, only.&quot;</span>
<span class="bp">self</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">power</span> <span class="o">=</span> <span class="mi">1</span>
<span class="bp">self</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">combat_score</span> <span class="o">=</span> <span class="mi">1</span>
</pre></div>
</div>
<p>We defined two new <a class="reference internal" href="../Components/Attributes.html"><span class="doc std std-doc">Attributes</span></a> <code class="docutils literal notranslate"><span class="pre">power</span></code> and <code class="docutils literal notranslate"><span class="pre">combat_score</span></code> and set them to default
values. Make sure to <code class="docutils literal notranslate"><span class="pre">&#64;reload</span></code> the server if you had it already running (you need to reload every
time you update your python code, dont worry, no accounts will be disconnected by the reload).</p>
<p>Note that only <em>new</em> characters will see your new Attributes (since the <code class="docutils literal notranslate"><span class="pre">at_object_creation</span></code> hook is
called when the object is first created, existing Characters wont have it). To update yourself,
run</p>
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span> @typeclass/force self
</pre></div>
</div>
<p>This resets your own typeclass (the <code class="docutils literal notranslate"><span class="pre">/force</span></code> switch is a safety measure to not do this
accidentally), this means that <code class="docutils literal notranslate"><span class="pre">at_object_creation</span></code> is re-run.</p>
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span> examine self
</pre></div>
</div>
<p>Under the “Persistent attributes” heading you should now find the new Attributes <code class="docutils literal notranslate"><span class="pre">power</span></code> and <code class="docutils literal notranslate"><span class="pre">score</span></code>
set on yourself by <code class="docutils literal notranslate"><span class="pre">at_object_creation</span></code>. If you dont, first make sure you <code class="docutils literal notranslate"><span class="pre">&#64;reload</span></code>ed into the new
code, next look at your server log (in the terminal/console) to see if there were any syntax errors
in your code that may have stopped your new code from loading correctly.</p>
</section>
<section id="character-generation">
<h2>Character Generation<a class="headerlink" href="#character-generation" title="Permalink to this headline"></a></h2>
<p>We assume in this example that Accounts first connect into a “character generation area”. Evennia
also supports full OOC menu-driven character generation, but for this example, a simple start room
is enough. When in this room (or rooms) we allow character generation commands. In fact, character
generation commands will <em>only</em> be available in such rooms.</p>
<p>Note that this again is made so as to be easy to expand to a full-fledged game. With our simple
example, we could simply set an <code class="docutils literal notranslate"><span class="pre">is_in_chargen</span></code> flag on the account and have the <code class="docutils literal notranslate"><span class="pre">+setpower</span></code> command
check it. Using this method however will make it easy to add more functionality later.</p>
<p>What we need are the following:</p>
<ul class="simple">
<li><p>One character generation <a class="reference internal" href="../Components/Commands.html"><span class="doc std std-doc">Command</span></a> to set the “Power” on the <code class="docutils literal notranslate"><span class="pre">Character</span></code>.</p></li>
<li><p>A chargen <a class="reference internal" href="../Components/Command-Sets.html"><span class="doc std std-doc">CmdSet</span></a> to hold this command. Lets call it <code class="docutils literal notranslate"><span class="pre">ChargenCmdset</span></code>.</p></li>
<li><p>A custom <code class="docutils literal notranslate"><span class="pre">ChargenRoom</span></code> type that makes this set of commands available to players in such rooms.</p></li>
<li><p>One such room to test things in.</p></li>
</ul>
<section id="the-setpower-command">
<h3>The +setpower command<a class="headerlink" href="#the-setpower-command" title="Permalink to this headline"></a></h3>
<p>For this tutorial we will add all our new commands to <code class="docutils literal notranslate"><span class="pre">mygame/commands/command.py</span></code> but you could
split your commands into multiple module if you prefered.</p>
<p>For this tutorial character generation will only consist of one <a class="reference internal" href="../Components/Commands.html"><span class="doc std std-doc">Command</span></a> to set the
Character s “power” stat. It will be called on the following MUSH-like form:</p>
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span> +setpower 4
</pre></div>
</div>
<p>Open <code class="docutils literal notranslate"><span class="pre">command.py</span></code> file. It contains documented empty templates for the base command and the
“MuxCommand” type used by default in Evennia. We will use the plain <code class="docutils literal notranslate"><span class="pre">Command</span></code> type here, the
<code class="docutils literal notranslate"><span class="pre">MuxCommand</span></code> class offers some extra features like stripping whitespace that may be useful - if so,
just import from that instead.</p>
<p>Add the following to the end of the <code class="docutils literal notranslate"><span class="pre">command.py</span></code> file:</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="c1"># end of command.py</span>
<span class="kn">from</span> <span class="nn">evennia</span> <span class="kn">import</span> <span class="n">Command</span> <span class="c1"># just for clarity; already imported above</span>
<span class="k">class</span> <span class="nc">CmdSetPower</span><span class="p">(</span><span class="n">Command</span><span class="p">):</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> set the power of a character</span>
<span class="sd"> Usage:</span>
<span class="sd"> +setpower &lt;1-10&gt;</span>
<span class="sd"> This sets the power of the current character. This can only be</span>
<span class="sd"> used during character generation.</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="n">key</span> <span class="o">=</span> <span class="s2">&quot;+setpower&quot;</span>
<span class="n">help_category</span> <span class="o">=</span> <span class="s2">&quot;mush&quot;</span>
<span class="k">def</span> <span class="nf">func</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="s2">&quot;This performs the actual command&quot;</span>
<span class="n">errmsg</span> <span class="o">=</span> <span class="s2">&quot;You must supply a number between 1 and 10.&quot;</span>
<span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">caller</span><span class="o">.</span><span class="n">msg</span><span class="p">(</span><span class="n">errmsg</span><span class="p">)</span>
<span class="k">return</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">power</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">ValueError</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">caller</span><span class="o">.</span><span class="n">msg</span><span class="p">(</span><span class="n">errmsg</span><span class="p">)</span>
<span class="k">return</span>
<span class="k">if</span> <span class="ow">not</span> <span class="p">(</span><span class="mi">1</span> <span class="o">&lt;=</span> <span class="n">power</span> <span class="o">&lt;=</span> <span class="mi">10</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">caller</span><span class="o">.</span><span class="n">msg</span><span class="p">(</span><span class="n">errmsg</span><span class="p">)</span>
<span class="k">return</span>
<span class="c1"># at this point the argument is tested as valid. Let&#39;s set it.</span>
<span class="bp">self</span><span class="o">.</span><span class="n">caller</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">power</span> <span class="o">=</span> <span class="n">power</span>
<span class="bp">self</span><span class="o">.</span><span class="n">caller</span><span class="o">.</span><span class="n">msg</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;Your Power was set to </span><span class="si">{</span><span class="n">power</span><span class="si">}</span><span class="s2">.&quot;</span><span class="p">)</span>
</pre></div>
</div>
<p>This is a pretty straightforward command. We do some error checking, then set the power on ourself.
We use a <code class="docutils literal notranslate"><span class="pre">help_category</span></code> of “mush” for all our commands, just so they are easy to find and separate
in the help list.</p>
<p>Save the file. We will now add it to a new <a class="reference internal" href="../Components/Command-Sets.html"><span class="doc std std-doc">CmdSet</span></a> so it can be accessed (in a full
chargen system you would of course have more than one command here).</p>
<p>Open <code class="docutils literal notranslate"><span class="pre">mygame/commands/default_cmdsets.py</span></code> and import your <code class="docutils literal notranslate"><span class="pre">command.py</span></code> module at the top. We also
import the default <code class="docutils literal notranslate"><span class="pre">CmdSet</span></code> class for the next step:</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">evennia</span> <span class="kn">import</span> <span class="n">CmdSet</span>
<span class="kn">from</span> <span class="nn">commands</span> <span class="kn">import</span> <span class="n">command</span>
</pre></div>
</div>
<p>Next scroll down and define a new command set (based on the base <code class="docutils literal notranslate"><span class="pre">CmdSet</span></code> class we just imported at
the end of this file, to hold only our chargen-specific command(s):</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="c1"># end of default_cmdsets.py</span>
<span class="k">class</span> <span class="nc">ChargenCmdset</span><span class="p">(</span><span class="n">CmdSet</span><span class="p">):</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> This cmdset it used in character generation areas.</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="n">key</span> <span class="o">=</span> <span class="s2">&quot;Chargen&quot;</span>
<span class="k">def</span> <span class="nf">at_cmdset_creation</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="s2">&quot;This is called at initialization&quot;</span>
<span class="bp">self</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">command</span><span class="o">.</span><span class="n">CmdSetPower</span><span class="p">())</span>
</pre></div>
</div>
<p>In the future you can add any number of commands to this cmdset, to expand your character generation
system as you desire. Now we need to actually put that cmdset on something so its made available to
users. We could put it directly on the Character, but that would make it available all the time.
Its cleaner to put it on a room, so its only available when players are in that room.</p>
</section>
<section id="chargen-areas">
<h3>Chargen areas<a class="headerlink" href="#chargen-areas" title="Permalink to this headline"></a></h3>
<p>We will create a simple Room typeclass to act as a template for all our Chargen areas. Edit
<code class="docutils literal notranslate"><span class="pre">mygame/typeclasses/rooms.py</span></code> next:</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">commands.default_cmdsets</span> <span class="kn">import</span> <span class="n">ChargenCmdset</span>
<span class="c1"># ...</span>
<span class="c1"># down at the end of rooms.py</span>
<span class="k">class</span> <span class="nc">ChargenRoom</span><span class="p">(</span><span class="n">Room</span><span class="p">):</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> This room class is used by character-generation rooms. It makes</span>
<span class="sd"> the ChargenCmdset available.</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="k">def</span> <span class="nf">at_object_creation</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="s2">&quot;this is called only at first creation&quot;</span>
<span class="bp">self</span><span class="o">.</span><span class="n">cmdset</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">ChargenCmdset</span><span class="p">,</span> <span class="n">persistent</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</pre></div>
</div>
<p>Note how new rooms created with this typeclass will always start with <code class="docutils literal notranslate"><span class="pre">ChargenCmdset</span></code> on themselves.
Dont forget the <code class="docutils literal notranslate"><span class="pre">persistent=True</span></code> keyword or you will lose the cmdset after a server reload. For
more information about <a class="reference internal" href="../Components/Command-Sets.html"><span class="doc std std-doc">Command Sets</span></a> and <a class="reference internal" href="../Components/Commands.html"><span class="doc std std-doc">Commands</span></a>, see the respective
links.</p>
</section>
<section id="testing-chargen">
<h3>Testing chargen<a class="headerlink" href="#testing-chargen" title="Permalink to this headline"></a></h3>
<p>First, make sure you have <code class="docutils literal notranslate"><span class="pre">&#64;reload</span></code>ed the server (or use <code class="docutils literal notranslate"><span class="pre">evennia</span> <span class="pre">reload</span></code> from the terminal) to have
your new python code added to the game. Check your terminal and fix any errors you see - the error
traceback lists exactly where the error is found - look line numbers in files you have changed.</p>
<p>We cant test things unless we have some chargen areas to test. Log into the game (you should at
this point be using the new, custom Character class). Lets dig a chargen area to test.</p>
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span> @dig chargen:rooms.ChargenRoom = chargen,finish
</pre></div>
</div>
<p>If you read the help for <code class="docutils literal notranslate"><span class="pre">&#64;dig</span></code> you will find that this will create a new room named <code class="docutils literal notranslate"><span class="pre">chargen</span></code>. The
part after the <code class="docutils literal notranslate"><span class="pre">:</span></code> is the python-path to the Typeclass you want to use. Since Evennia will
automatically try the <code class="docutils literal notranslate"><span class="pre">typeclasses</span></code> folder of our game directory, we just specify
<code class="docutils literal notranslate"><span class="pre">rooms.ChargenRoom</span></code>, meaning it will look inside the module <code class="docutils literal notranslate"><span class="pre">rooms.py</span></code> for a class named
<code class="docutils literal notranslate"><span class="pre">ChargenRoom</span></code> (which is what we created above). The names given after <code class="docutils literal notranslate"><span class="pre">=</span></code> are the names of exits to
and from the room from your current location. You could also append aliases to each one name, such
as <code class="docutils literal notranslate"><span class="pre">chargen;character</span> <span class="pre">generation</span></code>.</p>
<p>So in summary, this will create a new room of type ChargenRoom and open an exit <code class="docutils literal notranslate"><span class="pre">chargen</span></code> to it and
an exit back here named <code class="docutils literal notranslate"><span class="pre">finish</span></code>. If you see errors at this stage, you must fix them in your code.
<code class="docutils literal notranslate"><span class="pre">&#64;reload</span></code>
between fixes. Dont continue until the creation seems to have worked okay.</p>
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span> chargen
</pre></div>
</div>
<p>This should bring you to the chargen room. Being in there you should now have the <code class="docutils literal notranslate"><span class="pre">+setpower</span></code>
command available, so test it out. When you leave (via the <code class="docutils literal notranslate"><span class="pre">finish</span></code> exit), the command will go away
and trying <code class="docutils literal notranslate"><span class="pre">+setpower</span></code> should now give you a command-not-found error. Use <code class="docutils literal notranslate"><span class="pre">ex</span> <span class="pre">me</span></code> (as a privileged
user) to check so the <code class="docutils literal notranslate"><span class="pre">Power</span></code> <a class="reference internal" href="../Components/Attributes.html"><span class="doc std std-doc">Attribute</span></a> has been set correctly.</p>
<p>If things are not working, make sure your typeclasses and commands are free of bugs and that you
have entered the paths to the various command sets and commands correctly. Check the logs or command
line for tracebacks and errors.</p>
</section>
</section>
<section id="combat-system">
<h2>Combat System<a class="headerlink" href="#combat-system" title="Permalink to this headline"></a></h2>
<p>We will add our combat command to the default command set, meaning it will be available to everyone
at all times. The combat system consists of a <code class="docutils literal notranslate"><span class="pre">+attack</span></code> command to get how successful our attack is.
We also change the default <code class="docutils literal notranslate"><span class="pre">look</span></code> command to display the current combat score.</p>
<section id="attacking-with-the-attack-command">
<h3>Attacking with the +attack command<a class="headerlink" href="#attacking-with-the-attack-command" title="Permalink to this headline"></a></h3>
<p>Attacking in this simple system means rolling a random “combat score” influenced by the <code class="docutils literal notranslate"><span class="pre">power</span></code> stat
set during Character generation:</p>
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>&gt; +attack
You +attack with a combat score of 12!
</pre></div>
</div>
<p>Go back to <code class="docutils literal notranslate"><span class="pre">mygame/commands/command.py</span></code> and add the command to the end like this:</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">random</span>
<span class="c1"># ...</span>
<span class="k">class</span> <span class="nc">CmdAttack</span><span class="p">(</span><span class="n">Command</span><span class="p">):</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> issues an attack</span>
<span class="sd"> Usage:</span>
<span class="sd"> +attack</span>
<span class="sd"> This will calculate a new combat score based on your Power.</span>
<span class="sd"> Your combat score is visible to everyone in the same location.</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="n">key</span> <span class="o">=</span> <span class="s2">&quot;+attack&quot;</span>
<span class="n">help_category</span> <span class="o">=</span> <span class="s2">&quot;mush&quot;</span>
<span class="k">def</span> <span class="nf">func</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="s2">&quot;Calculate the random score between 1-10*Power&quot;</span>
<span class="n">caller</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">caller</span>
<span class="n">power</span> <span class="o">=</span> <span class="n">caller</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">power</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">power</span><span class="p">:</span>
<span class="c1"># this can happen if caller is not of</span>
<span class="c1"># our custom Character typeclass</span>
<span class="n">power</span> <span class="o">=</span> <span class="mi">1</span>
<span class="n">combat_score</span> <span class="o">=</span> <span class="n">random</span><span class="o">.</span><span class="n">randint</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">10</span> <span class="o">*</span> <span class="n">power</span><span class="p">)</span>
<span class="n">caller</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">combat_score</span> <span class="o">=</span> <span class="n">combat_score</span>
<span class="c1"># announce</span>
<span class="n">message_template</span> <span class="o">=</span> <span class="s2">&quot;</span><span class="si">{attacker}</span><span class="s2"> +attack</span><span class="si">{s}</span><span class="s2"> with a combat score of </span><span class="si">{c_score}</span><span class="s2">!&quot;</span>
<span class="n">caller</span><span class="o">.</span><span class="n">msg</span><span class="p">(</span><span class="n">message_template</span><span class="o">.</span><span class="n">format</span><span class="p">(</span>
<span class="n">attacker</span><span class="o">=</span><span class="s2">&quot;You&quot;</span><span class="p">,</span>
<span class="n">s</span><span class="o">=</span><span class="s2">&quot;&quot;</span><span class="p">,</span>
<span class="n">c_score</span><span class="o">=</span><span class="n">combat_score</span><span class="p">,</span>
<span class="p">))</span>
<span class="n">caller</span><span class="o">.</span><span class="n">location</span><span class="o">.</span><span class="n">msg_contents</span><span class="p">(</span><span class="n">message_template</span><span class="o">.</span><span class="n">format</span><span class="p">(</span>
<span class="n">attacker</span><span class="o">=</span><span class="n">caller</span><span class="o">.</span><span class="n">key</span><span class="p">,</span>
<span class="n">s</span><span class="o">=</span><span class="s2">&quot;s&quot;</span><span class="p">,</span>
<span class="n">c_score</span><span class="o">=</span><span class="n">combat_score</span><span class="p">,</span>
<span class="p">),</span> <span class="n">exclude</span><span class="o">=</span><span class="n">caller</span><span class="p">)</span>
</pre></div>
</div>
<p>What we do here is simply to generate a “combat score” using Pythons inbuilt <code class="docutils literal notranslate"><span class="pre">random.randint()</span></code>
function. We then store that and echo the result to everyone involved.</p>
<p>To make the <code class="docutils literal notranslate"><span class="pre">+attack</span></code> command available to you in game, go back to
<code class="docutils literal notranslate"><span class="pre">mygame/commands/default_cmdsets.py</span></code> and scroll down to the <code class="docutils literal notranslate"><span class="pre">CharacterCmdSet</span></code> class. At the correct
place add this line:</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="bp">self</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">command</span><span class="o">.</span><span class="n">CmdAttack</span><span class="p">())</span>
</pre></div>
</div>
<p><code class="docutils literal notranslate"><span class="pre">&#64;reload</span></code> Evennia and the <code class="docutils literal notranslate"><span class="pre">+attack</span></code> command should be available to you. Run it and use e.g. <code class="docutils literal notranslate"><span class="pre">&#64;ex</span></code> to
make sure the <code class="docutils literal notranslate"><span class="pre">combat_score</span></code> attribute is saved correctly.</p>
</section>
<section id="have-look-show-combat-scores">
<h3>Have “look” show combat scores<a class="headerlink" href="#have-look-show-combat-scores" title="Permalink to this headline"></a></h3>
<p>Players should be able to view all current combat scores in the room. We could do this by simply
adding a second command named something like <code class="docutils literal notranslate"><span class="pre">+combatscores</span></code>, but we will instead let the default
<code class="docutils literal notranslate"><span class="pre">look</span></code> command do the heavy lifting for us and display our scores as part of its normal output, like
this:</p>
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>&gt; look Tom
Tom (combat score: 3)
This is a great warrior.
</pre></div>
</div>
<p>We dont actually have to modify the <code class="docutils literal notranslate"><span class="pre">look</span></code> command itself however. To understand why, take a look
at how the default <code class="docutils literal notranslate"><span class="pre">look</span></code> is actually defined. It sits in <a class="reference internal" href="../api/evennia.commands.default.general.html#evennia-commands-default-general"><span class="std std-ref">evennia/commands/default/general.py</span></a>.</p>
<p>You will find that the actual return text is done by the <code class="docutils literal notranslate"><span class="pre">look</span></code> command calling a <em>hook method</em>
named <code class="docutils literal notranslate"><span class="pre">return_appearance</span></code> on the object looked at. All the <code class="docutils literal notranslate"><span class="pre">look</span></code> does is to echo whatever this hook
returns. So what we need to do is to edit our custom Character typeclass and overload its
<code class="docutils literal notranslate"><span class="pre">return_appearance</span></code> to return what we want (this is where the advantage of having a custom typeclass
comes into play for real).</p>
<p>Go back to your custom Character typeclass in <code class="docutils literal notranslate"><span class="pre">mygame/typeclasses/characters.py</span></code>. The default
implementation of <code class="docutils literal notranslate"><span class="pre">return</span> <span class="pre">appearance</span></code> is found in <a class="reference internal" href="../api/evennia.objects.objects.html#evennia.objects.objects.DefaultCharacter" title="evennia.objects.objects.DefaultCharacter"><span class="xref myst py py-class">evennia.DefaultCharacter</span></a>.</p>
<p>If you want to make bigger changes you could copy &amp; paste the whole default thing into our overloading method. In our case the change is small though:</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Character</span><span class="p">(</span><span class="n">DefaultCharacter</span><span class="p">):</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> [...]</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="k">def</span> <span class="nf">at_object_creation</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="s2">&quot;This is called when object is first created, only.&quot;</span>
<span class="bp">self</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">power</span> <span class="o">=</span> <span class="mi">1</span>
<span class="bp">self</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">combat_score</span> <span class="o">=</span> <span class="mi">1</span>
<span class="k">def</span> <span class="nf">return_appearance</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">looker</span><span class="p">):</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> The return from this method is what</span>
<span class="sd"> looker sees when looking at this object.</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="n">text</span> <span class="o">=</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">return_appearance</span><span class="p">(</span><span class="n">looker</span><span class="p">)</span>
<span class="n">cscore</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&quot; (combat score: </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">combat_score</span><span class="si">}</span><span class="s2">)&quot;</span>
<span class="k">if</span> <span class="s2">&quot;</span><span class="se">\n</span><span class="s2">&quot;</span> <span class="ow">in</span> <span class="n">text</span><span class="p">:</span>
<span class="c1"># text is multi-line, add score after first line</span>
<span class="n">first_line</span><span class="p">,</span> <span class="n">rest</span> <span class="o">=</span> <span class="n">text</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&quot;</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">text</span> <span class="o">=</span> <span class="n">first_line</span> <span class="o">+</span> <span class="n">cscore</span> <span class="o">+</span> <span class="s2">&quot;</span><span class="se">\n</span><span class="s2">&quot;</span> <span class="o">+</span> <span class="n">rest</span>
<span class="k">else</span><span class="p">:</span>
<span class="c1"># text is only one line; add score to end</span>
<span class="n">text</span> <span class="o">+=</span> <span class="n">cscore</span>
<span class="k">return</span> <span class="n">text</span>
</pre></div>
</div>
<p>What we do is to simply let the default <code class="docutils literal notranslate"><span class="pre">return_appearance</span></code> do its thing (<code class="docutils literal notranslate"><span class="pre">super</span></code> will call the
parents version of the same method). We then split out the first line of this text, append our
<code class="docutils literal notranslate"><span class="pre">combat_score</span></code> and put it back together again.</p>
<p><code class="docutils literal notranslate"><span class="pre">&#64;reload</span></code> the server and you should be able to look at other Characters and see their current combat
scores.</p>
<blockquote>
<div><p>Note: A potentially more useful way to do this would be to overload the entire <code class="docutils literal notranslate"><span class="pre">return_appearance</span></code>
of the <code class="docutils literal notranslate"><span class="pre">Room</span></code>s of your mush and change how they list their contents; in that way one could see all
combat scores of all present Characters at the same time as looking at the room. We leave this as an
exercise.</p>
</div></blockquote>
</section>
</section>
<section id="npc-system">
<h2>NPC system<a class="headerlink" href="#npc-system" title="Permalink to this headline"></a></h2>
<p>Here we will re-use the Character class by introducing a command that can create NPC objects. We
should also be able to set its Power and order it around.</p>
<p>There are a few ways to define the NPC class. We could in theory create a custom typeclass for it
and put a custom NPC-specific cmdset on all NPCs. This cmdset could hold all manipulation commands.
Since we expect NPC manipulation to be a common occurrence among the user base however, we will
instead put all relevant NPC commands in the default command set and limit eventual access with
<a class="reference internal" href="../Components/Permissions.html"><span class="doc std std-doc">Permissions and Locks</span></a>.</p>
<section id="creating-an-npc-with-createnpc">
<h3>Creating an NPC with +createNPC<a class="headerlink" href="#creating-an-npc-with-createnpc" title="Permalink to this headline"></a></h3>
<p>We need a command for creating the NPC, this is a very straightforward command:</p>
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>&gt; +createnpc Anna
You created the NPC &#39;Anna&#39;.
</pre></div>
</div>
<p>At the end of <code class="docutils literal notranslate"><span class="pre">command.py</span></code>, create our new command:</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">evennia</span> <span class="kn">import</span> <span class="n">create_object</span>
<span class="k">class</span> <span class="nc">CmdCreateNPC</span><span class="p">(</span><span class="n">Command</span><span class="p">):</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> create a new npc</span>
<span class="sd"> Usage:</span>
<span class="sd"> +createNPC &lt;name&gt;</span>
<span class="sd"> Creates a new, named NPC. The NPC will start with a Power of 1.</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="n">key</span> <span class="o">=</span> <span class="s2">&quot;+createnpc&quot;</span>
<span class="n">aliases</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&quot;+createNPC&quot;</span><span class="p">]</span>
<span class="n">locks</span> <span class="o">=</span> <span class="s2">&quot;call:not perm(nonpcs)&quot;</span>
<span class="n">help_category</span> <span class="o">=</span> <span class="s2">&quot;mush&quot;</span>
<span class="k">def</span> <span class="nf">func</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="s2">&quot;creates the object and names it&quot;</span>
<span class="n">caller</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">caller</span>
<span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">:</span>
<span class="n">caller</span><span class="o">.</span><span class="n">msg</span><span class="p">(</span><span class="s2">&quot;Usage: +createNPC &lt;name&gt;&quot;</span><span class="p">)</span>
<span class="k">return</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">caller</span><span class="o">.</span><span class="n">location</span><span class="p">:</span>
<span class="c1"># may not create npc when OOC</span>
<span class="n">caller</span><span class="o">.</span><span class="n">msg</span><span class="p">(</span><span class="s2">&quot;You must have a location to create an npc.&quot;</span><span class="p">)</span>
<span class="k">return</span>
<span class="c1"># make name always start with capital letter</span>
<span class="n">name</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">capitalize</span><span class="p">()</span>
<span class="c1"># create npc in caller&#39;s location</span>
<span class="n">npc</span> <span class="o">=</span> <span class="n">create_object</span><span class="p">(</span><span class="s2">&quot;characters.Character&quot;</span><span class="p">,</span>
<span class="n">key</span><span class="o">=</span><span class="n">name</span><span class="p">,</span>
<span class="n">location</span><span class="o">=</span><span class="n">caller</span><span class="o">.</span><span class="n">location</span><span class="p">,</span>
<span class="n">locks</span><span class="o">=</span><span class="sa">f</span><span class="s2">&quot;edit:id(</span><span class="si">{</span><span class="n">caller</span><span class="o">.</span><span class="n">id</span><span class="si">}</span><span class="s2">) and perm(Builders);call:false()&quot;</span><span class="p">)</span>
<span class="c1"># announce</span>
<span class="n">message_template</span> <span class="o">=</span> <span class="s2">&quot;</span><span class="si">{creator}</span><span class="s2"> created the NPC &#39;</span><span class="si">{npc}</span><span class="s2">&#39;.&quot;</span>
<span class="n">caller</span><span class="o">.</span><span class="n">msg</span><span class="p">(</span><span class="n">message_template</span><span class="o">.</span><span class="n">format</span><span class="p">(</span>
<span class="n">creator</span><span class="o">=</span><span class="s2">&quot;You&quot;</span><span class="p">,</span>
<span class="n">npc</span><span class="o">=</span><span class="n">name</span><span class="p">,</span>
<span class="p">))</span>
<span class="n">caller</span><span class="o">.</span><span class="n">location</span><span class="o">.</span><span class="n">msg_contents</span><span class="p">(</span><span class="n">message_template</span><span class="o">.</span><span class="n">format</span><span class="p">(</span>
<span class="n">creator</span><span class="o">=</span><span class="n">caller</span><span class="o">.</span><span class="n">key</span><span class="p">,</span>
<span class="n">npc</span><span class="o">=</span><span class="n">name</span><span class="p">,</span>
<span class="p">),</span> <span class="n">exclude</span><span class="o">=</span><span class="n">caller</span><span class="p">)</span>
</pre></div>
</div>
<p>Here we define a <code class="docutils literal notranslate"><span class="pre">+createnpc</span></code> (<code class="docutils literal notranslate"><span class="pre">+createNPC</span></code> works too) that is callable by everyone <em>not</em> having the
<code class="docutils literal notranslate"><span class="pre">nonpcs</span></code><a class="reference internal" href="../Components/Permissions.html"><span class="doc std std-doc">permission</span></a>” (in Evennia, a “permission” can just as well be used to
block access, it depends on the lock we define). We create the NPC object in the callers current
location, using our custom <code class="docutils literal notranslate"><span class="pre">Character</span></code> typeclass to do so.</p>
<p>We set an extra lock condition on the NPC, which we will use to check who may edit the NPC later
we allow the creator to do so, and anyone with the Builders permission (or higher). See
<a class="reference internal" href="../Components/Locks.html"><span class="doc std std-doc">Locks</span></a> for more information about the lock system.</p>
<p>Note that we just give the object default permissions (by not specifying the <code class="docutils literal notranslate"><span class="pre">permissions</span></code> keyword
to the <code class="docutils literal notranslate"><span class="pre">create_object()</span></code> call). In some games one might want to give the NPC the same permissions
as the Character creating them, this might be a security risk though.</p>
<p>Add this command to your default cmdset the same way you did the <code class="docutils literal notranslate"><span class="pre">+attack</span></code> command earlier.
<code class="docutils literal notranslate"><span class="pre">&#64;reload</span></code> and it will be available to test.</p>
</section>
<section id="editing-the-npc-with-editnpc">
<h3>Editing the NPC with +editNPC<a class="headerlink" href="#editing-the-npc-with-editnpc" title="Permalink to this headline"></a></h3>
<p>Since we re-used our custom character typeclass, our new NPC already has a <em>Power</em> value - it
defaults to 1. How do we change this?</p>
<p>There are a few ways we can do this. The easiest is to remember that the <code class="docutils literal notranslate"><span class="pre">power</span></code> attribute is just a
simple <a class="reference internal" href="../Components/Attributes.html"><span class="doc std std-doc">Attribute</span></a> stored on the NPC object. So as a Builder or Admin we could set this
right away with the default <code class="docutils literal notranslate"><span class="pre">&#64;set</span></code> command:</p>
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span> @set mynpc/power = 6
</pre></div>
</div>
<p>The <code class="docutils literal notranslate"><span class="pre">&#64;set</span></code> command is too generally powerful though, and thus only available to staff. We will add a
custom command that only changes the things we want players to be allowed to change. We could in
principle re-work our old <code class="docutils literal notranslate"><span class="pre">+setpower</span></code> command, but lets try something more useful. Lets make a
<code class="docutils literal notranslate"><span class="pre">+editNPC</span></code> command.</p>
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>&gt; +editNPC Anna/power = 10
Set Anna&#39;s property &#39;power&#39; to 10.
</pre></div>
</div>
<p>This is a slightly more complex command. It goes at the end of your <code class="docutils literal notranslate"><span class="pre">command.py</span></code> file as before.</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">CmdEditNPC</span><span class="p">(</span><span class="n">Command</span><span class="p">):</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> edit an existing NPC</span>
<span class="sd"> Usage:</span>
<span class="sd"> +editnpc &lt;name&gt;[/&lt;attribute&gt; [= value]]</span>
<span class="sd"> Examples:</span>
<span class="sd"> +editnpc mynpc/power = 5</span>
<span class="sd"> +editnpc mynpc/power - displays power value</span>
<span class="sd"> +editnpc mynpc - shows all editable</span>
<span class="sd"> attributes and values</span>
<span class="sd"> This command edits an existing NPC. You must have</span>
<span class="sd"> permission to edit the NPC to use this.</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="n">key</span> <span class="o">=</span> <span class="s2">&quot;+editnpc&quot;</span>
<span class="n">aliases</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&quot;+editNPC&quot;</span><span class="p">]</span>
<span class="n">locks</span> <span class="o">=</span> <span class="s2">&quot;cmd:not perm(nonpcs)&quot;</span>
<span class="n">help_category</span> <span class="o">=</span> <span class="s2">&quot;mush&quot;</span>
<span class="k">def</span> <span class="nf">parse</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="s2">&quot;We need to do some parsing here&quot;</span>
<span class="n">args</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">args</span>
<span class="n">propname</span><span class="p">,</span> <span class="n">propval</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span> <span class="kc">None</span>
<span class="k">if</span> <span class="s2">&quot;=&quot;</span> <span class="ow">in</span> <span class="n">args</span><span class="p">:</span>
<span class="n">args</span><span class="p">,</span> <span class="n">propval</span> <span class="o">=</span> <span class="p">[</span><span class="n">part</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="k">for</span> <span class="n">part</span> <span class="ow">in</span> <span class="n">args</span><span class="o">.</span><span class="n">rsplit</span><span class="p">(</span><span class="s2">&quot;=&quot;</span><span class="p">,</span> <span class="mi">1</span><span class="p">)]</span>
<span class="k">if</span> <span class="s2">&quot;/&quot;</span> <span class="ow">in</span> <span class="n">args</span><span class="p">:</span>
<span class="n">args</span><span class="p">,</span> <span class="n">propname</span> <span class="o">=</span> <span class="p">[</span><span class="n">part</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="k">for</span> <span class="n">part</span> <span class="ow">in</span> <span class="n">args</span><span class="o">.</span><span class="n">rsplit</span><span class="p">(</span><span class="s2">&quot;/&quot;</span><span class="p">,</span> <span class="mi">1</span><span class="p">)]</span>
<span class="c1"># store, so we can access it below in func()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">args</span>
<span class="bp">self</span><span class="o">.</span><span class="n">propname</span> <span class="o">=</span> <span class="n">propname</span>
<span class="c1"># a propval without a propname is meaningless</span>
<span class="bp">self</span><span class="o">.</span><span class="n">propval</span> <span class="o">=</span> <span class="n">propval</span> <span class="k">if</span> <span class="n">propname</span> <span class="k">else</span> <span class="kc">None</span>
<span class="k">def</span> <span class="nf">func</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="s2">&quot;do the editing&quot;</span>
<span class="n">allowed_propnames</span> <span class="o">=</span> <span class="p">(</span><span class="s2">&quot;power&quot;</span><span class="p">,</span> <span class="s2">&quot;attribute1&quot;</span><span class="p">,</span> <span class="s2">&quot;attribute2&quot;</span><span class="p">)</span>
<span class="n">caller</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">caller</span>
<span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">args</span> <span class="ow">or</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="p">:</span>
<span class="n">caller</span><span class="o">.</span><span class="n">msg</span><span class="p">(</span><span class="s2">&quot;Usage: +editnpc name[/propname][=propval]&quot;</span><span class="p">)</span>
<span class="k">return</span>
<span class="n">npc</span> <span class="o">=</span> <span class="n">caller</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">npc</span><span class="p">:</span>
<span class="k">return</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">npc</span><span class="o">.</span><span class="n">access</span><span class="p">(</span><span class="n">caller</span><span class="p">,</span> <span class="s2">&quot;edit&quot;</span><span class="p">):</span>
<span class="n">caller</span><span class="o">.</span><span class="n">msg</span><span class="p">(</span><span class="s2">&quot;You cannot change this NPC.&quot;</span><span class="p">)</span>
<span class="k">return</span>
<span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">propname</span><span class="p">:</span>
<span class="c1"># this means we just list the values</span>
<span class="n">output</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&quot;Properties of </span><span class="si">{</span><span class="n">npc</span><span class="o">.</span><span class="n">key</span><span class="si">}</span><span class="s2">:&quot;</span>
<span class="k">for</span> <span class="n">propname</span> <span class="ow">in</span> <span class="n">allowed_propnames</span><span class="p">:</span>
<span class="n">propvalue</span> <span class="o">=</span> <span class="n">npc</span><span class="o">.</span><span class="n">attributes</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">propname</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="s2">&quot;N/A&quot;</span><span class="p">)</span>
<span class="n">output</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&quot;</span><span class="se">\n</span><span class="s2"> </span><span class="si">{</span><span class="n">propname</span><span class="si">}</span><span class="s2"> = </span><span class="si">{</span><span class="n">propvalue</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="n">caller</span><span class="o">.</span><span class="n">msg</span><span class="p">(</span><span class="n">output</span><span class="p">)</span>
<span class="k">elif</span> <span class="bp">self</span><span class="o">.</span><span class="n">propname</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">allowed_propnames</span><span class="p">:</span>
<span class="n">caller</span><span class="o">.</span><span class="n">msg</span><span class="p">(</span><span class="s2">&quot;You may only change </span><span class="si">%s</span><span class="s2">.&quot;</span> <span class="o">%</span>
<span class="s2">&quot;, &quot;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">allowed_propnames</span><span class="p">))</span>
<span class="k">elif</span> <span class="bp">self</span><span class="o">.</span><span class="n">propval</span><span class="p">:</span>
<span class="c1"># assigning a new propvalue</span>
<span class="c1"># in this example, the properties are all integers...</span>
<span class="n">intpropval</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">propval</span><span class="p">)</span>
<span class="n">npc</span><span class="o">.</span><span class="n">attributes</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">propname</span><span class="p">,</span> <span class="n">intpropval</span><span class="p">)</span>
<span class="n">caller</span><span class="o">.</span><span class="n">msg</span><span class="p">(</span><span class="s2">&quot;Set </span><span class="si">%s</span><span class="s2">&#39;s property &#39;</span><span class="si">%s</span><span class="s2">&#39; to </span><span class="si">%s</span><span class="s2">&quot;</span> <span class="o">%</span>
<span class="p">(</span><span class="n">npc</span><span class="o">.</span><span class="n">key</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">propname</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">propval</span><span class="p">))</span>
<span class="k">else</span><span class="p">:</span>
<span class="c1"># propname set, but not propval - show current value</span>
<span class="n">caller</span><span class="o">.</span><span class="n">msg</span><span class="p">(</span><span class="s2">&quot;</span><span class="si">%s</span><span class="s2"> has property </span><span class="si">%s</span><span class="s2"> = </span><span class="si">%s</span><span class="s2">&quot;</span> <span class="o">%</span>
<span class="p">(</span><span class="n">npc</span><span class="o">.</span><span class="n">key</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">propname</span><span class="p">,</span>
<span class="n">npc</span><span class="o">.</span><span class="n">attributes</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">propname</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="s2">&quot;N/A&quot;</span><span class="p">)))</span>
</pre></div>
</div>
<p>This command example shows off the use of more advanced parsing but otherwise its mostly error
checking. It searches for the given npc in the same room, and checks so the caller actually has
permission to “edit” it before continuing. An account without the proper permission wont even be
able to view the properties on the given NPC. Its up to each game if this is the way it should be.</p>
<p>Add this to the default command set like before and you should be able to try it out.</p>
<p><em>Note: If you wanted a player to use this command to change an on-object property like the NPCs
name (the <code class="docutils literal notranslate"><span class="pre">key</span></code> property), youd need to modify the command since “key” is not an Attribute (it is
not retrievable via <code class="docutils literal notranslate"><span class="pre">npc.attributes.get</span></code> but directly via <code class="docutils literal notranslate"><span class="pre">npc.key</span></code>). We leave this as an optional
exercise.</em></p>
</section>
<section id="making-the-npc-do-stuff-the-npc-command">
<h3>Making the NPC do stuff - the +npc command<a class="headerlink" href="#making-the-npc-do-stuff-the-npc-command" title="Permalink to this headline"></a></h3>
<p>Finally, we will make a command to order our NPC around. For now, we will limit this command to only
be usable by those having the “edit” permission on the NPC. This can be changed if its possible for
anyone to use the NPC.</p>
<p>The NPC, since it inherited our Character typeclass has access to most commands a player does. What
it doesnt have access to are Session and Player-based cmdsets (which means, among other things that
they cannot chat on channels, but they could do that if you just added those commands). This makes
the <code class="docutils literal notranslate"><span class="pre">+npc</span></code> command simple:</p>
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>+npc Anna = say Hello!
Anna says, &#39;Hello!&#39;
</pre></div>
</div>
<p>Again, add to the end of your <code class="docutils literal notranslate"><span class="pre">command.py</span></code> module:</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">CmdNPC</span><span class="p">(</span><span class="n">Command</span><span class="p">):</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> controls an NPC</span>
<span class="sd"> Usage:</span>
<span class="sd"> +npc &lt;name&gt; = &lt;command&gt;</span>
<span class="sd"> This causes the npc to perform a command as itself. It will do so</span>
<span class="sd"> with its own permissions and accesses.</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="n">key</span> <span class="o">=</span> <span class="s2">&quot;+npc&quot;</span>
<span class="n">locks</span> <span class="o">=</span> <span class="s2">&quot;call:not perm(nonpcs)&quot;</span>
<span class="n">help_category</span> <span class="o">=</span> <span class="s2">&quot;mush&quot;</span>
<span class="k">def</span> <span class="nf">parse</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="s2">&quot;Simple split of the = sign&quot;</span>
<span class="n">name</span><span class="p">,</span> <span class="n">cmdname</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span> <span class="kc">None</span>
<span class="k">if</span> <span class="s2">&quot;=&quot;</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">:</span>
<span class="n">name</span><span class="p">,</span> <span class="n">cmdname</span> <span class="o">=</span> <span class="p">[</span><span class="n">part</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
<span class="k">for</span> <span class="n">part</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="o">.</span><span class="n">rsplit</span><span class="p">(</span><span class="s2">&quot;=&quot;</span><span class="p">,</span> <span class="mi">1</span><span class="p">)]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">cmdname</span> <span class="o">=</span> <span class="n">name</span><span class="p">,</span> <span class="n">cmdname</span>
<span class="k">def</span> <span class="nf">func</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="s2">&quot;Run the command&quot;</span>
<span class="n">caller</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">caller</span>
<span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">cmdname</span><span class="p">:</span>
<span class="n">caller</span><span class="o">.</span><span class="n">msg</span><span class="p">(</span><span class="s2">&quot;Usage: +npc &lt;name&gt; = &lt;command&gt;&quot;</span><span class="p">)</span>
<span class="k">return</span>
<span class="n">npc</span> <span class="o">=</span> <span class="n">caller</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">npc</span><span class="p">:</span>
<span class="k">return</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">npc</span><span class="o">.</span><span class="n">access</span><span class="p">(</span><span class="n">caller</span><span class="p">,</span> <span class="s2">&quot;edit&quot;</span><span class="p">):</span>
<span class="n">caller</span><span class="o">.</span><span class="n">msg</span><span class="p">(</span><span class="s2">&quot;You may not order this NPC to do anything.&quot;</span><span class="p">)</span>
<span class="k">return</span>
<span class="c1"># send the command order</span>
<span class="n">npc</span><span class="o">.</span><span class="n">execute_cmd</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">cmdname</span><span class="p">)</span>
<span class="n">caller</span><span class="o">.</span><span class="n">msg</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;You told </span><span class="si">{</span><span class="n">npc</span><span class="o">.</span><span class="n">key</span><span class="si">}</span><span class="s2"> to do &#39;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">cmdname</span><span class="si">}</span><span class="s2">&#39;.&quot;</span><span class="p">)</span>
</pre></div>
</div>
<p>Note that if you give an erroneous command, you will not see any error message, since that error
will be returned to the npc object, not to you. If you want players to see this, you can give the
callers session ID to the <code class="docutils literal notranslate"><span class="pre">execute_cmd</span></code> call, like this:</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">npc</span><span class="o">.</span><span class="n">execute_cmd</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">cmdname</span><span class="p">,</span> <span class="n">sessid</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">caller</span><span class="o">.</span><span class="n">sessid</span><span class="p">)</span>
</pre></div>
</div>
<p>Another thing to remember is however that this is a very simplistic way to control NPCs. Evennia
supports full puppeting very easily. An Account (assuming the “puppet” permission was set correctly)
could simply do <code class="docutils literal notranslate"><span class="pre">&#64;ic</span> <span class="pre">mynpc</span></code> and be able to play the game “as” that NPC. This is in fact just what
happens when an Account takes control of their normal Character as well.</p>
</section>
</section>
<section id="concluding-remarks">
<h2>Concluding remarks<a class="headerlink" href="#concluding-remarks" title="Permalink to this headline"></a></h2>
<p>This ends the tutorial. It looks like a lot of text but the amount of code you have to write is
actually relatively short. At this point you should have a basic skeleton of a game and a feel for
what is involved in coding your game.</p>
<p>From here on you could build a few more ChargenRooms and link that to a bigger grid. The <code class="docutils literal notranslate"><span class="pre">+setpower</span></code>
command can either be built upon or accompanied by many more to get a more elaborate character
generation.</p>
<p>The simple “Power” game mechanic should be easily expandable to something more full-fledged and
useful, same is true for the combat score principle. The <code class="docutils literal notranslate"><span class="pre">+attack</span></code> could be made to target a
specific player (or npc) and automatically compare their relevant attributes to determine a result.</p>
<p>To continue from here, you can take a look at the <a class="reference internal" href="Beginner-Tutorial/Part1/Beginner-Tutorial-Tutorial-World.html"><span class="doc std std-doc">Tutorial World</span></a>. For
more specific ideas, see the <a class="reference internal" href="Howtos-Overview.html"><span class="doc std std-doc">other tutorials and hints</span></a> as well
as the <a class="reference internal" href="../Components/Components-Overview.html"><span class="doc std std-doc">Evennia Component overview</span></a>.</p>
</section>
</section>
</div>
</div>
</div>
</div>
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="../genindex.html" title="General Index"
>index</a></li>
<li class="right" >
<a href="../py-modindex.html" title="Python Module Index"
>modules</a> |</li>
<li class="right" >
<a href="../Components/Components-Overview.html" title="Core Components"
>next</a> |</li>
<li class="right" >
<a href="Turn-based-Combat-System.html" title="Turn based Combat System"
>previous</a> |</li>
2023-12-21 00:12:31 +01:00
<li class="nav-item nav-item-0"><a href="../index.html">Evennia 3.x</a> &#187;</li>
2023-12-20 23:10:55 +01:00
<li class="nav-item nav-item-1"><a href="Howtos-Overview.html" >Tutorials and How-Tos</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Tutorial for basic MUSH like game</a></li>
</ul>
</div>
2023-12-21 00:12:31 +01:00
<div class="admonition important">
<p class="first admonition-title">Note</p>
<p class="last">You are reading an old version of the Evennia documentation. <a href="https://www.evennia.com/docs/latest/index.html">The latest version is here</a></p>.
</div>
2023-12-20 23:10:55 +01:00
<div class="footer" role="contentinfo">
&#169; Copyright 2023, The Evennia developer community.
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 3.2.1.
</div>
</body>
</html>