mirror of
https://github.com/evennia/evennia.git
synced 2026-03-18 13:56:30 +01:00
788 lines
No EOL
76 KiB
HTML
788 lines
No EOL
76 KiB
HTML
|
||
<!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/" />
|
||
|
||
<title>Tutorial for basic MUSH like game — Evennia latest documentation</title>
|
||
<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>
|
||
|
||
|
||
|
||
|
||
<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>
|
||
<li class="nav-item nav-item-0"><a href="../index.html">Evennia latest</a> »</li>
|
||
<li class="nav-item nav-item-1"><a href="Howtos-Overview.html" accesskey="U">Tutorials and How-To’s</a> »</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>
|
||
<h3>Doc Versions</h3>
|
||
<ul>
|
||
|
||
<li><a href="Tutorial-for-basic-MUSH-like-game.html">latest (main branch)</a></li>
|
||
|
||
<li><a href="../4.x/index.html">v4.0.0 branch (outdated)</a></li>
|
||
|
||
<li><a href="../3.x/index.html">v3.0.0 branch (outdated)</a></li>
|
||
|
||
<li><a href="../2.x/index.html">v2.0.0 branch (outdated)</a></li>
|
||
|
||
<li><a href="../1.x/index.html">v1.0.0 branch (outdated)</a></li>
|
||
|
||
<li><a href="../0.x/index.html">v0.9.5 branch (outdated)</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 it’s 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 don’t 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 don’t 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">"""</span>
|
||
<span class="sd"> [...]</span>
|
||
<span class="sd"> """</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">"This is called when object is first created, only."</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">@reload</span></code> the server if you had it already running (you need to reload every
|
||
time you update your python code, don’t 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 won’t 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 don’t, first make sure you <code class="docutils literal notranslate"><span class="pre">@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">"""</span>
|
||
<span class="sd"> set the power of a character</span>
|
||
|
||
<span class="sd"> Usage:</span>
|
||
<span class="sd"> +setpower <1-10></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"> """</span>
|
||
|
||
<span class="n">key</span> <span class="o">=</span> <span class="s2">"+setpower"</span>
|
||
<span class="n">help_category</span> <span class="o">=</span> <span class="s2">"mush"</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">"This performs the actual command"</span>
|
||
<span class="n">errmsg</span> <span class="o">=</span> <span class="s2">"You must supply a number between 1 and 10."</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"><=</span> <span class="n">power</span> <span class="o"><=</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'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">"Your Power was set to </span><span class="si">{</span><span class="n">power</span><span class="si">}</span><span class="s2">."</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">"""</span>
|
||
<span class="sd"> This cmdset it used in character generation areas.</span>
|
||
<span class="sd"> """</span>
|
||
<span class="n">key</span> <span class="o">=</span> <span class="s2">"Chargen"</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">"This is called at initialization"</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 it’s made available to
|
||
users. We could put it directly on the Character, but that would make it available all the time.
|
||
It’s cleaner to put it on a room, so it’s 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">"""</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"> """</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">"this is called only at first creation"</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.
|
||
Don’t 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">@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 can’t 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). Let’s 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">@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">@reload</span></code>
|
||
between fixes. Don’t 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>> +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">"""</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"> """</span>
|
||
<span class="n">key</span> <span class="o">=</span> <span class="s2">"+attack"</span>
|
||
<span class="n">help_category</span> <span class="o">=</span> <span class="s2">"mush"</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">"Calculate the random score between 1-10*Power"</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">"</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">!"</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">"You"</span><span class="p">,</span>
|
||
<span class="n">s</span><span class="o">=</span><span class="s2">""</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">"s"</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 Python’s 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">@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">@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>> look Tom
|
||
Tom (combat score: 3)
|
||
This is a great warrior.
|
||
</pre></div>
|
||
</div>
|
||
<p>We don’t 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 & 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">"""</span>
|
||
<span class="sd"> [...]</span>
|
||
<span class="sd"> """</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">"This is called when object is first created, only."</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">"""</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"> """</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">" (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">)"</span>
|
||
<span class="k">if</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</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">"</span><span class="se">\n</span><span class="s2">"</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">"</span><span class="se">\n</span><span class="s2">"</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
|
||
parent’s 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">@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>> +createnpc Anna
|
||
You created the NPC 'Anna'.
|
||
</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">"""</span>
|
||
<span class="sd"> create a new npc</span>
|
||
|
||
<span class="sd"> Usage:</span>
|
||
<span class="sd"> +createNPC <name></span>
|
||
|
||
<span class="sd"> Creates a new, named NPC. The NPC will start with a Power of 1.</span>
|
||
<span class="sd"> """</span>
|
||
<span class="n">key</span> <span class="o">=</span> <span class="s2">"+createnpc"</span>
|
||
<span class="n">aliases</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"+createNPC"</span><span class="p">]</span>
|
||
<span class="n">locks</span> <span class="o">=</span> <span class="s2">"call:not perm(nonpcs)"</span>
|
||
<span class="n">help_category</span> <span class="o">=</span> <span class="s2">"mush"</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">"creates the object and names it"</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">"Usage: +createNPC <name>"</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">"You must have a location to create an npc."</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'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">"characters.Character"</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">"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()"</span><span class="p">)</span>
|
||
<span class="c1"># announce</span>
|
||
<span class="n">message_template</span> <span class="o">=</span> <span class="s2">"</span><span class="si">{creator}</span><span class="s2"> created the NPC '</span><span class="si">{npc}</span><span class="s2">'."</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">"You"</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 caller’s 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">@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">@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">@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 let’s try something more useful. Let’s 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>> +editNPC Anna/power = 10
|
||
Set Anna's property 'power' 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">"""</span>
|
||
<span class="sd"> edit an existing NPC</span>
|
||
|
||
<span class="sd"> Usage:</span>
|
||
<span class="sd"> +editnpc <name>[/<attribute> [= 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"> """</span>
|
||
<span class="n">key</span> <span class="o">=</span> <span class="s2">"+editnpc"</span>
|
||
<span class="n">aliases</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"+editNPC"</span><span class="p">]</span>
|
||
<span class="n">locks</span> <span class="o">=</span> <span class="s2">"cmd:not perm(nonpcs)"</span>
|
||
<span class="n">help_category</span> <span class="o">=</span> <span class="s2">"mush"</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">"We need to do some parsing here"</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">"="</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">"="</span><span class="p">,</span> <span class="mi">1</span><span class="p">)]</span>
|
||
<span class="k">if</span> <span class="s2">"/"</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">"/"</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">"do the editing"</span>
|
||
|
||
<span class="n">allowed_propnames</span> <span class="o">=</span> <span class="p">(</span><span class="s2">"power"</span><span class="p">,</span> <span class="s2">"attribute1"</span><span class="p">,</span> <span class="s2">"attribute2"</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">"Usage: +editnpc name[/propname][=propval]"</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">"edit"</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">"You cannot change this NPC."</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">"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">:"</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">"N/A"</span><span class="p">)</span>
|
||
<span class="n">output</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">"</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">"</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">"You may only change </span><span class="si">%s</span><span class="s2">."</span> <span class="o">%</span>
|
||
<span class="s2">", "</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">"Set </span><span class="si">%s</span><span class="s2">'s property '</span><span class="si">%s</span><span class="s2">' to </span><span class="si">%s</span><span class="s2">"</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">"</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">"</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">"N/A"</span><span class="p">)))</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>This command example shows off the use of more advanced parsing but otherwise it’s 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 won’t even be
|
||
able to view the properties on the given NPC. It’s 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 NPC’s
|
||
name (the <code class="docutils literal notranslate"><span class="pre">key</span></code> property), you’d 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 it’s 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 doesn’t 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, 'Hello!'
|
||
</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">"""</span>
|
||
<span class="sd"> controls an NPC</span>
|
||
|
||
<span class="sd"> Usage:</span>
|
||
<span class="sd"> +npc <name> = <command></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"> """</span>
|
||
<span class="n">key</span> <span class="o">=</span> <span class="s2">"+npc"</span>
|
||
<span class="n">locks</span> <span class="o">=</span> <span class="s2">"call:not perm(nonpcs)"</span>
|
||
<span class="n">help_category</span> <span class="o">=</span> <span class="s2">"mush"</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">"Simple split of the = sign"</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">"="</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">"="</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">"Run the command"</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">"Usage: +npc <name> = <command>"</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">"edit"</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">"You may not order this NPC to do anything."</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">"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 '</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">'."</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
|
||
caller’s 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">@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>
|
||
<li class="nav-item nav-item-0"><a href="../index.html">Evennia latest</a> »</li>
|
||
<li class="nav-item nav-item-1"><a href="Howtos-Overview.html" >Tutorials and How-To’s</a> »</li>
|
||
<li class="nav-item nav-item-this"><a href="">Tutorial for basic MUSH like game</a></li>
|
||
</ul>
|
||
</div>
|
||
|
||
|
||
|
||
<div class="footer" role="contentinfo">
|
||
© Copyright 2024, The Evennia developer community.
|
||
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 3.2.1.
|
||
</div>
|
||
</body>
|
||
</html> |