evennia/docs/0.x/Custom-Protocols.html
2023-12-20 19:10:09 +01:00

343 lines
No EOL
27 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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>Custom Protocols &#8212; Evennia 0.9.5 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>
<script async="async" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/latest.js?config=TeX-AMS-MML_HTMLorMML"></script>
<script type="text/x-mathjax-config">MathJax.Hub.Config({"tex2jax": {"processClass": "tex2jax_process|mathjax_process|math|output_area"}})</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" />
</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="nav-item nav-item-0"><a href="index.html">Evennia 0.9.5</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Custom Protocols</a></li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body" role="main">
<section class="tex2jax_ignore mathjax_ignore" id="custom-protocols">
<h1>Custom Protocols<a class="headerlink" href="#custom-protocols" title="Permalink to this headline"></a></h1>
<p><em>Note: This is considered an advanced topic and is mostly of interest to users planning to implement
their own custom client protocol.</em></p>
<p>A <a class="reference internal" href="Sessions.html#portal-and-server-sessions"><span class="std std-doc">PortalSession</span></a> is the basic data object representing an
external
connection to the Evennia <a class="reference internal" href="Portal-And-Server.html"><span class="doc std std-doc">Portal</span></a> usually a human player running a mud client
of some kind. The way they connect (the language the players client and Evennia use to talk to
each other) is called the connection <em>Protocol</em>. The most common such protocol for MUD:s is the
<em>Telnet</em> protocol. All Portal Sessions are stored and managed by the Portals <em>sessionhandler</em>.</p>
<p>Its technically sometimes hard to separate the concept of <em>PortalSession</em> from the concept of
<em>Protocol</em> since both depend heavily on the other (they are often created as the same class). When
data flows through this part of the system, this is how it goes</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># In the Portal</span>
<span class="n">You</span> <span class="o">&lt;-&gt;</span>
<span class="n">Protocol</span> <span class="o">+</span> <span class="n">PortalSession</span> <span class="o">&lt;-&gt;</span>
<span class="n">PortalSessionHandler</span> <span class="o">&lt;-&gt;</span>
<span class="p">(</span><span class="n">AMP</span><span class="p">)</span> <span class="o">&lt;-&gt;</span>
<span class="n">ServerSessionHandler</span> <span class="o">&lt;-&gt;</span>
<span class="n">ServerSession</span> <span class="o">&lt;-&gt;</span>
<span class="n">InputFunc</span>
</pre></div>
</div>
<p>(See the <a class="reference internal" href="Messagepath.html"><span class="doc std std-doc">Message Path</span></a> for the bigger picture of how data flows through Evennia). The
parts that needs to be customized to make your own custom protocol is the <code class="docutils literal notranslate"><span class="pre">Protocol</span> <span class="pre">+</span> <span class="pre">PortalSession</span></code>
(which translates between data coming in/out over the wire to/from Evennia internal representation)
as well as the <code class="docutils literal notranslate"><span class="pre">InputFunc</span></code> (which handles incoming data).</p>
<section id="adding-custom-protocols">
<h2>Adding custom Protocols<a class="headerlink" href="#adding-custom-protocols" title="Permalink to this headline"></a></h2>
<p>Evennia has a plugin-system that add the protocol as a new “service” to the application.</p>
<p>Take a look at <code class="docutils literal notranslate"><span class="pre">evennia/server/portal/portal.py</span></code>, notably the sections towards the end of that file.
These are where the various in-built services like telnet, ssh, webclient etc are added to the
Portal (there is an equivalent but shorter list in <code class="docutils literal notranslate"><span class="pre">evennia/server/server.py</span></code>).</p>
<p>To add a new service of your own (for example your own custom client protocol) to the Portal or
Server, look at <code class="docutils literal notranslate"><span class="pre">mygame/server/conf/server_services_plugins</span></code> and <code class="docutils literal notranslate"><span class="pre">portal_services_plugins</span></code>. By
default Evennia will look into these modules to find plugins. If you wanted to have it look for more
modules, you could do the following:</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span> <span class="c1"># add to the Server</span>
<span class="n">SERVER_SERVICES_PLUGIN_MODULES</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s1">&#39;server.conf.my_server_plugins&#39;</span><span class="p">)</span>
<span class="c1"># or, if you want to add to the Portal</span>
<span class="n">PORTAL_SERVICES_PLUGIN_MODULES</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s1">&#39;server.conf.my_portal_plugins&#39;</span><span class="p">)</span>
</pre></div>
</div>
<p>When adding a new connection youll most likely only need to add new things to the
<code class="docutils literal notranslate"><span class="pre">PORTAL_SERVICES_PLUGIN_MODULES</span></code>.</p>
<p>This module can contain whatever you need to define your protocol, but it <em>must</em> contain a function
<code class="docutils literal notranslate"><span class="pre">start_plugin_services(app)</span></code>. This is called by the Portal as part of its upstart. The function
<code class="docutils literal notranslate"><span class="pre">start_plugin_services</span></code> must contain all startup code the server need. The <code class="docutils literal notranslate"><span class="pre">app</span></code> argument is a
reference to the Portal/Server application itself so the custom service can be added to it. The
function should not return anything.</p>
<p>This is how it looks:</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span> <span class="c1"># mygame/server/conf/portal_services_plugins.py</span>
<span class="c1"># here the new Portal Twisted protocol is defined</span>
<span class="k">class</span> <span class="nc">MyOwnFactory</span><span class="p">(</span> <span class="o">...</span> <span class="p">):</span>
<span class="p">[</span><span class="o">...</span><span class="p">]</span>
<span class="c1"># some configs</span>
<span class="n">MYPROC_ENABLED</span> <span class="o">=</span> <span class="kc">True</span> <span class="c1"># convenient off-flag to avoid having to edit settings all the time</span>
<span class="n">MY_PORT</span> <span class="o">=</span> <span class="mi">6666</span>
<span class="k">def</span> <span class="nf">start_plugin_services</span><span class="p">(</span><span class="n">portal</span><span class="p">):</span>
<span class="s2">&quot;This is called by the Portal during startup&quot;</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">MYPROC_ENABLED</span><span class="p">:</span>
<span class="k">return</span>
<span class="c1"># output to list this with the other services at startup</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot; myproc: </span><span class="si">%s</span><span class="s2">&quot;</span> <span class="o">%</span> <span class="n">MY_PORT</span><span class="p">)</span>
<span class="c1"># some setup (simple example)</span>
<span class="n">factory</span> <span class="o">=</span> <span class="n">MyOwnFactory</span><span class="p">()</span>
<span class="n">my_service</span> <span class="o">=</span> <span class="n">internet</span><span class="o">.</span><span class="n">TCPServer</span><span class="p">(</span><span class="n">MY_PORT</span><span class="p">,</span> <span class="n">factory</span><span class="p">)</span>
<span class="c1"># all Evennia services must be uniquely named</span>
<span class="n">my_service</span><span class="o">.</span><span class="n">setName</span><span class="p">(</span><span class="s2">&quot;MyService&quot;</span><span class="p">)</span>
<span class="c1"># add to the main portal application</span>
<span class="n">portal</span><span class="o">.</span><span class="n">services</span><span class="o">.</span><span class="n">addService</span><span class="p">(</span><span class="n">my_service</span><span class="p">)</span>
</pre></div>
</div>
<p>Once the module is defined and targeted in settings, just reload the server and your new
protocol/services should start with the others.</p>
</section>
<section id="writing-your-own-protocol">
<h2>Writing your own Protocol<a class="headerlink" href="#writing-your-own-protocol" title="Permalink to this headline"></a></h2>
<p>Writing a stable communication protocol from scratch is not something well cover here, its no
trivial task. The good news is that Twisted offers implementations of many common protocols, ready
for adapting.</p>
<p>Writing a protocol implementation in Twisted usually involves creating a class inheriting from an
already existing Twisted protocol class and from <code class="docutils literal notranslate"><span class="pre">evennia.server.session.Session</span></code> (multiple
inheritance), then overloading the methods that particular protocol uses to link them to the
Evennia-specific inputs.</p>
<p>Heres a example to show the concept:</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="c1"># In module that we&#39;ll later add to the system through PORTAL_SERVICE_PLUGIN_MODULES</span>
<span class="c1"># pseudo code</span>
<span class="kn">from</span> <span class="nn">twisted.something</span> <span class="kn">import</span> <span class="n">TwistedClient</span>
<span class="c1"># this class is used both for Portal- and Server Sessions</span>
<span class="kn">from</span> <span class="nn">evennia.server.session</span> <span class="kn">import</span> <span class="n">Session</span>
<span class="kn">from</span> <span class="nn">evennia.server.portal.portalsessionhandler</span> <span class="kn">import</span> <span class="n">PORTAL_SESSIONS</span>
<span class="k">class</span> <span class="nc">MyCustomClient</span><span class="p">(</span><span class="n">TwistedClient</span><span class="p">,</span> <span class="n">Session</span><span class="p">):</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">sessionhandler</span> <span class="o">=</span> <span class="n">PORTAL_SESSIONS</span>
<span class="c1"># these are methods we must know that TwistedClient uses for</span>
<span class="c1"># communication. Name and arguments could vary for different Twisted protocols</span>
<span class="k">def</span> <span class="nf">onOpen</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="c1"># let&#39;s say this is called when the client first connects</span>
<span class="c1"># we need to init the session and connect to the sessionhandler. The .factory</span>
<span class="c1"># is available through the Twisted parents</span>
<span class="n">client_address</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">getClientAddress</span><span class="p">()</span> <span class="c1"># get client address somehow</span>
<span class="bp">self</span><span class="o">.</span><span class="n">init_session</span><span class="p">(</span><span class="s2">&quot;mycustom_protocol&quot;</span><span class="p">,</span> <span class="n">client_address</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">factory</span><span class="o">.</span><span class="n">sessionhandler</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">sessionhandler</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">onClose</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">reason</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="c1"># called when the client connection is dropped</span>
<span class="c1"># link to the Evennia equivalent</span>
<span class="bp">self</span><span class="o">.</span><span class="n">disconnect</span><span class="p">(</span><span class="n">reason</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">onMessage</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">indata</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="c1"># called with incoming data</span>
<span class="c1"># convert as needed here</span>
<span class="bp">self</span><span class="o">.</span><span class="n">data_in</span><span class="p">(</span><span class="n">data</span><span class="o">=</span><span class="n">indata</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">sendMessage</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">outdata</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="c1"># called to send data out</span>
<span class="c1"># modify if needed</span>
<span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">sendMessage</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">outdata</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="c1"># these are Evennia methods. They must all exist and look exactly like this</span>
<span class="c1"># The above twisted-methods call them and vice-versa. This connects the protocol</span>
<span class="c1"># the Evennia internals.</span>
<span class="k">def</span> <span class="nf">disconnect</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">reason</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> Called when connection closes.</span>
<span class="sd"> This can also be called directly by Evennia when manually closing the connection.</span>
<span class="sd"> Do any cleanups here.</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="bp">self</span><span class="o">.</span><span class="n">sessionhandler</span><span class="o">.</span><span class="n">disconnect</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">at_login</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> Called when this session authenticates by the server (if applicable)</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="k">def</span> <span class="nf">data_in</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> Data going into the server should go through this method. It</span>
<span class="sd"> should pass data into `sessionhandler.data_in`. THis will be called</span>
<span class="sd"> by the sessionhandler with the data it gets from the approrpriate</span>
<span class="sd"> send_* method found later in this protocol.</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="bp">self</span><span class="o">.</span><span class="n">sessionhandler</span><span class="o">.</span><span class="n">data_in</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">text</span><span class="o">=</span><span class="n">kwargs</span><span class="p">[</span><span class="s1">&#39;data&#39;</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">data_out</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> Data going out from the server should go through this method. It should</span>
<span class="sd"> hand off to the protocol&#39;s send method, whatever it&#39;s called.</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="c1"># we assume we have a &#39;text&#39; outputfunc</span>
<span class="bp">self</span><span class="o">.</span><span class="n">onMessage</span><span class="p">(</span><span class="n">kwargs</span><span class="p">[</span><span class="s1">&#39;text&#39;</span><span class="p">])</span>
<span class="c1"># &#39;outputfuncs&#39; are defined as `send_&lt;outputfunc_name&gt;`. From in-code, they are called</span>
<span class="c1"># with `msg(outfunc_name=&lt;data&gt;)`.</span>
<span class="k">def</span> <span class="nf">send_text</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">txt</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> Send text, used with e.g. `session.msg(text=&quot;foo&quot;)`</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="c1"># we make use of the</span>
<span class="bp">self</span><span class="o">.</span><span class="n">data_out</span><span class="p">(</span><span class="n">text</span><span class="o">=</span><span class="n">txt</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">send_default</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">cmdname</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> Handles all outputfuncs without an explicit `send_*` method to handle them.</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="bp">self</span><span class="o">.</span><span class="n">data_out</span><span class="p">(</span><span class="o">**</span><span class="p">{</span><span class="n">cmdname</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">args</span><span class="p">)})</span>
</pre></div>
</div>
<p>The principle here is that the Twisted-specific methods are overridden to redirect inputs/outputs to
the Evennia-specific methods.</p>
<section id="sending-data-out">
<h3>Sending data out<a class="headerlink" href="#sending-data-out" title="Permalink to this headline"></a></h3>
<p>To send data out through this protocol, youd need to get its Session and then you could e.g.</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span> <span class="n">session</span><span class="o">.</span><span class="n">msg</span><span class="p">(</span><span class="n">text</span><span class="o">=</span><span class="s2">&quot;foo&quot;</span><span class="p">)</span>
</pre></div>
</div>
<p>The message will pass through the system such that the sessionhandler will dig out the session and
check if it has a <code class="docutils literal notranslate"><span class="pre">send_text</span></code> method (it has). It will then pass the “foo” into that method, which
in our case means sending “foo” across the network.</p>
</section>
<section id="receiving-data">
<h3>Receiving data<a class="headerlink" href="#receiving-data" title="Permalink to this headline"></a></h3>
<p>Just because the protocol is there, does not mean Evennia knows what to do with it. An
<a class="reference internal" href="Inputfuncs.html"><span class="doc std std-doc">Inputfunc</span></a> must exist to receive it. In the case of the <code class="docutils literal notranslate"><span class="pre">text</span></code> input exemplified above,
Evennia alredy handles this input - it will parse it as a Command name followed by its inputs. So
handle that you need to simply add a cmdset with commands on your receiving Session (and/or the
Object/Character it is puppeting). If not you may need to add your own Inputfunc (see the
<a class="reference internal" href="Inputfuncs.html"><span class="doc std std-doc">Inputfunc</span></a> page for how to do this.</p>
<p>These might not be as clear-cut in all protocols, but the principle is there. These four basic
components - however they are accessed - links to the <em>Portal Session</em>, which is the actual common
interface between the different low-level protocols and Evennia.</p>
</section>
</section>
<section id="assorted-notes">
<h2>Assorted notes<a class="headerlink" href="#assorted-notes" title="Permalink to this headline"></a></h2>
<p>To take two examples, Evennia supports the <em>telnet</em> protocol as well as <em>webclient</em>, via ajax or
websockets. Youll find that whereas telnet is a textbook example of a Twisted protocol as seen
above, the ajax protocol looks quite different due to how it interacts with the
webserver through long-polling (comet) style requests. All the necessary parts
mentioned above are still there, but by necessity implemented in very different
ways.</p>
</section>
</section>
<div class="clearer"></div>
</div>
</div>
</div>
<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>
<p><h3><a href="index.html">Table of Contents</a></h3>
<ul>
<li><a class="reference internal" href="#">Custom Protocols</a><ul>
<li><a class="reference internal" href="#adding-custom-protocols">Adding custom Protocols</a></li>
<li><a class="reference internal" href="#writing-your-own-protocol">Writing your own Protocol</a><ul>
<li><a class="reference internal" href="#sending-data-out">Sending data out</a></li>
<li><a class="reference internal" href="#receiving-data">Receiving data</a></li>
</ul>
</li>
<li><a class="reference internal" href="#assorted-notes">Assorted notes</a></li>
</ul>
</li>
</ul>
<div role="note" aria-label="source link">
<!--h3>This Page</h3-->
<ul class="this-page-menu">
<li><a href="_sources/Custom-Protocols.md.txt"
rel="nofollow">Show Page Source</a></li>
</ul>
</div><h3>Links</h3>
<ul>
<li><a href="https://www.evennia.com">Home page</a> </li>
<li><a href="https://github.com/evennia/evennia">Evennia Github</a> </li>
<li><a href="http://games.evennia.com">Game Index</a> </li>
<li><a href="http://webchat.freenode.net/?channels=evennia&uio=MT1mYWxzZSY5PXRydWUmMTE9MTk1JjEyPXRydWUbb">IRC</a> -
<a href="https://discord.gg/NecFePw">Discord</a> -
<a href="https://groups.google.com/forum/#%21forum/evennia">Forums</a>
</li>
<li><a href="http://evennia.blogspot.com/">Evennia Dev blog</a> </li>
</ul>
<h3>Versions</h3>
<ul>
<li><a href="../1.0-dev/index.html">1.0-dev (develop branch)</a></li>
<li><a href="Custom-Protocols.html">0.9.5 (v0.9.5 branch)</a></li>
</ul>
</div>
</div>
<div class="clearer"></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="nav-item nav-item-0"><a href="index.html">Evennia 0.9.5</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Custom Protocols</a></li>
</ul>
</div>
<div class="footer" role="contentinfo">
&#169; Copyright 2020, The Evennia developer community.
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 3.2.1.
</div>
</body>
</html>