evennia/docs/6.x/Contribs/Contrib-Components.html
2026-02-15 19:06:04 +01:00

384 lines
No EOL
26 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 lang="en" data-content_root="../">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Components &#8212; Evennia latest documentation</title>
<link rel="stylesheet" type="text/css" href="../_static/pygments.css?v=d75fae25" />
<link rel="stylesheet" type="text/css" href="../_static/nature.css?v=279e0f84" />
<link rel="stylesheet" type="text/css" href="../_static/custom.css?v=e4a91a55" />
<script src="../_static/documentation_options.js?v=c6e86fd7"></script>
<script src="../_static/doctools.js?v=9bcbadda"></script>
<script src="../_static/sphinx_highlight.js?v=dc90522c"></script>
<link rel="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="Custom gameime" href="Contrib-Custom-Gametime.html" />
<link rel="prev" title="Additional Color markups" href="Contrib-Color-Markups.html" />
</head><body>
<div class="related" role="navigation" aria-label="Related">
<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="Contrib-Custom-Gametime.html" title="Custom gameime"
accesskey="N">next</a> |</li>
<li class="right" >
<a href="Contrib-Color-Markups.html" title="Additional Color markups"
accesskey="P">previous</a> |</li>
<li class="nav-item nav-item-0"><a href="../index.html">Evennia</a> &#187;</li>
<li class="nav-item nav-item-1"><a href="Contribs-Overview.html" accesskey="U">Contribs</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Components</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="components">
<h1>Components<a class="headerlink" href="#components" title="Link to this heading"></a></h1>
<p>Contrib by ChrisLR, 2021</p>
<p>Expand typeclasses using a components/composition approach.</p>
<section id="the-components-contrib">
<h2>The Components Contrib<a class="headerlink" href="#the-components-contrib" title="Link to this heading"></a></h2>
<p>This contrib introduces Components and Composition to Evennia.
Each Component class represents a feature that will be enabled on a typeclass instance.
You can register these components on an entire typeclass or a single object at runtime.
It supports both persisted attributes and in-memory attributes by using Evennias AttributeHandler.</p>
</section>
<section id="pros">
<h2>Pros<a class="headerlink" href="#pros" title="Link to this heading"></a></h2>
<ul class="simple">
<li><p>You can reuse a feature across multiple typeclasses without inheritance</p></li>
<li><p>You can cleanly organize each feature into a self-contained class.</p></li>
<li><p>You can check if your object supports a feature without checking its instance.</p></li>
</ul>
</section>
<section id="cons">
<h2>Cons<a class="headerlink" href="#cons" title="Link to this heading"></a></h2>
<ul class="simple">
<li><p>It introduces additional complexity.</p></li>
<li><p>A host typeclass instance is required.</p></li>
</ul>
</section>
<section id="how-to-install">
<h2>How to install<a class="headerlink" href="#how-to-install" title="Link to this heading"></a></h2>
<p>To enable component support for a typeclass,
import and inherit the ComponentHolderMixin, similar to this</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">evennia.contrib.base_systems.components</span><span class="w"> </span><span class="kn">import</span> <span class="n">ComponentHolderMixin</span>
<span class="k">class</span><span class="w"> </span><span class="nc">Character</span><span class="p">(</span><span class="n">ComponentHolderMixin</span><span class="p">,</span> <span class="n">DefaultCharacter</span><span class="p">):</span>
<span class="c1"># ...</span>
</pre></div>
</div>
<p>Components need to inherit the Component class and require a unique name.
Components may inherit from other components but must specify another name.
You can assign the same slot to both components to have alternative implementations.</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">evennia.contrib.base_systems.components</span><span class="w"> </span><span class="kn">import</span> <span class="n">Component</span>
<span class="k">class</span><span class="w"> </span><span class="nc">Health</span><span class="p">(</span><span class="n">Component</span><span class="p">):</span>
<span class="n">name</span> <span class="o">=</span> <span class="s2">&quot;health&quot;</span>
<span class="k">class</span><span class="w"> </span><span class="nc">ItemHealth</span><span class="p">(</span><span class="n">Health</span><span class="p">):</span>
<span class="n">name</span> <span class="o">=</span> <span class="s2">&quot;item_health&quot;</span>
<span class="n">slot</span> <span class="o">=</span> <span class="s2">&quot;health&quot;</span>
</pre></div>
</div>
<p>Components may define DBFields or NDBFields at the class level.
DBField will store its values in the hosts DB with a prefixed key.
NDBField will store its values in the hosts NDB and will not persist.
The key used will be component_name::field_name.
They use AttributeProperty under the hood.</p>
<p>Example:</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">evennia.contrib.base_systems.components</span><span class="w"> </span><span class="kn">import</span> <span class="n">Component</span><span class="p">,</span> <span class="n">DBField</span>
<span class="k">class</span><span class="w"> </span><span class="nc">Health</span><span class="p">(</span><span class="n">Component</span><span class="p">):</span>
<span class="n">health</span> <span class="o">=</span> <span class="n">DBField</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
</pre></div>
</div>
<p>Note that default is optional and will default to None.</p>
<p>Adding a component to a host will also a similarly named tag with components as category.
A Component named health will appear as key=”health, category=“components”.
This allows you to retrieve objects with specific components by searching with the tag.</p>
<p>It is also possible to add Component Tags the same way, using TagField.
TagField accepts a default value and can be used to store a single or multiple tags.
Default values are automatically added when the component is added.
Component Tags are cleared from the host if the component is removed.</p>
<p>Example:</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">evennia.contrib.base_systems.components</span><span class="w"> </span><span class="kn">import</span> <span class="n">Component</span><span class="p">,</span> <span class="n">TagField</span>
<span class="k">class</span><span class="w"> </span><span class="nc">Health</span><span class="p">(</span><span class="n">Component</span><span class="p">):</span>
<span class="n">resistances</span> <span class="o">=</span> <span class="n">TagField</span><span class="p">()</span>
<span class="n">vulnerability</span> <span class="o">=</span> <span class="n">TagField</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="s2">&quot;fire&quot;</span><span class="p">,</span> <span class="n">enforce_single</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</pre></div>
</div>
<p>The resistances field in this example can be set to multiple times and it will keep the added tags.
The vulnerability field in this example will override the previous tag with the new one.</p>
<p>Each typeclass using the ComponentHolderMixin can declare its components
in the class via the ComponentProperty.
These are components that will always be present in a typeclass.
You can also pass kwargs to override the default values
Example</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">evennia.contrib.base_systems.components</span><span class="w"> </span><span class="kn">import</span> <span class="n">ComponentHolderMixin</span>
<span class="k">class</span><span class="w"> </span><span class="nc">Character</span><span class="p">(</span><span class="n">ComponentHolderMixin</span><span class="p">,</span> <span class="n">DefaultCharacter</span><span class="p">):</span>
<span class="n">health</span> <span class="o">=</span> <span class="n">ComponentProperty</span><span class="p">(</span><span class="s2">&quot;health&quot;</span><span class="p">,</span> <span class="n">hp</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="n">max_hp</span><span class="o">=</span><span class="mi">50</span><span class="p">)</span>
</pre></div>
</div>
<p>You can then use character.components.health to access it.
The shorter form character.cmp.health also exists.
character.health would also be accessible but only for typeclasses that have
this component defined on the class.</p>
<p>Alternatively you can add those components at runtime.
You will have to access those via the component handler.
Example</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">character</span> <span class="o">=</span> <span class="bp">self</span>
<span class="n">vampirism</span> <span class="o">=</span> <span class="n">components</span><span class="o">.</span><span class="n">Vampirism</span><span class="o">.</span><span class="n">create</span><span class="p">(</span><span class="n">character</span><span class="p">)</span>
<span class="n">character</span><span class="o">.</span><span class="n">components</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">vampirism</span><span class="p">)</span>
<span class="o">...</span>
<span class="n">vampirism</span> <span class="o">=</span> <span class="n">character</span><span class="o">.</span><span class="n">components</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;vampirism&quot;</span><span class="p">)</span>
<span class="c1"># Alternatively</span>
<span class="n">vampirism</span> <span class="o">=</span> <span class="n">character</span><span class="o">.</span><span class="n">cmp</span><span class="o">.</span><span class="n">vampirism</span>
</pre></div>
</div>
<p>Keep in mind that all components must be imported to be visible in the listing.
As such, I recommend regrouping them in a package.
You can then import all your components in that packages <strong>init</strong></p>
<p>Because of how Evennia import typeclasses and the behavior of python imports
I recommend placing the components package inside the typeclass package.
In other words, create a folder named components inside your typeclass folder.
Then, inside the typeclasses/<strong>init</strong>.py file add the import to the folder, like</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">typeclasses</span><span class="w"> </span><span class="kn">import</span> <span class="n">components</span>
</pre></div>
</div>
<p>This ensures that the components package will be imported when the typeclasses are imported.
You will also need to import each components inside the packages own typeclasses/components/<strong>init</strong>.py file.
You only need to import each module/file from there but importing the right class is a good practice.</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">typeclasses.components.health</span><span class="w"> </span><span class="kn">import</span> <span class="n">Health</span>
</pre></div>
</div>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">typeclasses.components</span><span class="w"> </span><span class="kn">import</span> <span class="n">health</span>
</pre></div>
</div>
<p>Both of the above examples will work.</p>
</section>
<section id="known-issues">
<h2>Known Issues<a class="headerlink" href="#known-issues" title="Link to this heading"></a></h2>
<p>Assigning mutable default values such as a list to a DBField will share it across instances.
To avoid this, you must set autocreate=True on the field, like this.</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">health</span> <span class="o">=</span> <span class="n">DBField</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="p">[],</span> <span class="n">autocreate</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</pre></div>
</div>
</section>
<section id="full-example">
<h2>Full Example<a class="headerlink" href="#full-example" title="Link to this heading"></a></h2>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">evennia.contrib.base_systems</span><span class="w"> </span><span class="kn">import</span> <span class="n">components</span>
<span class="c1"># This is the Component class</span>
<span class="k">class</span><span class="w"> </span><span class="nc">Health</span><span class="p">(</span><span class="n">components</span><span class="o">.</span><span class="n">Component</span><span class="p">):</span>
<span class="n">name</span> <span class="o">=</span> <span class="s2">&quot;health&quot;</span>
<span class="c1"># Stores the current and max values as Attributes on the host, defaulting to 100</span>
<span class="n">current</span> <span class="o">=</span> <span class="n">components</span><span class="o">.</span><span class="n">DBField</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
<span class="nb">max</span> <span class="o">=</span> <span class="n">components</span><span class="o">.</span><span class="n">DBField</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">damage</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">current</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">:</span>
<span class="k">return</span>
<span class="bp">self</span><span class="o">.</span><span class="n">current</span> <span class="o">-=</span> <span class="n">value</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">current</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">:</span>
<span class="k">return</span>
<span class="bp">self</span><span class="o">.</span><span class="n">current</span> <span class="o">=</span> <span class="mi">0</span>
<span class="bp">self</span><span class="o">.</span><span class="n">on_death</span><span class="p">()</span>
<span class="k">def</span><span class="w"> </span><span class="nf">heal</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
<span class="n">hp</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">current</span>
<span class="n">hp</span> <span class="o">+=</span> <span class="n">value</span>
<span class="k">if</span> <span class="n">hp</span> <span class="o">&gt;=</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_hp</span><span class="p">:</span>
<span class="n">hp</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_hp</span>
<span class="bp">self</span><span class="o">.</span><span class="n">current</span> <span class="o">=</span> <span class="n">hp</span>
<span class="nd">@property</span>
<span class="k">def</span><span class="w"> </span><span class="nf">is_dead</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">current</span> <span class="o">&lt;=</span> <span class="mi">0</span>
<span class="k">def</span><span class="w"> </span><span class="nf">on_death</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="c1"># Behavior is defined on the typeclass</span>
<span class="bp">self</span><span class="o">.</span><span class="n">host</span><span class="o">.</span><span class="n">on_death</span><span class="p">()</span>
<span class="c1"># This is how the Character inherits the mixin and registers the component &#39;health&#39;</span>
<span class="k">class</span><span class="w"> </span><span class="nc">Character</span><span class="p">(</span><span class="n">ComponentHolderMixin</span><span class="p">,</span> <span class="n">DefaultCharacter</span><span class="p">):</span>
<span class="n">health</span> <span class="o">=</span> <span class="n">ComponentProperty</span><span class="p">(</span><span class="s2">&quot;health&quot;</span><span class="p">)</span>
<span class="c1"># This is an example of a command that checks for the component</span>
<span class="k">class</span><span class="w"> </span><span class="nc">Attack</span><span class="p">(</span><span class="n">Command</span><span class="p">):</span>
<span class="n">key</span> <span class="o">=</span> <span class="s2">&quot;attack&quot;</span>
<span class="n">aliases</span> <span class="o">=</span> <span class="p">(</span><span class="s1">&#39;melee&#39;</span><span class="p">,</span> <span class="s1">&#39;hit&#39;</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">at_pre_cmd</span><span class="p">(</span><span class="bp">self</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="n">targets</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">search</span><span class="p">(</span><span class="n">args</span><span class="p">,</span> <span class="n">quiet</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="n">valid_target</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">for</span> <span class="n">target</span> <span class="ow">in</span> <span class="n">targets</span><span class="p">:</span>
<span class="c1"># Attempt to retrieve the component, None is obtained if it does not exist.</span>
<span class="k">if</span> <span class="n">target</span><span class="o">.</span><span class="n">components</span><span class="o">.</span><span class="n">health</span><span class="p">:</span>
<span class="n">valid_target</span> <span class="o">=</span> <span class="n">target</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">valid_target</span><span class="p">:</span>
<span class="n">caller</span><span class="o">.</span><span class="n">msg</span><span class="p">(</span><span class="s2">&quot;You can&#39;t attack that!&quot;</span><span class="p">)</span>
<span class="k">return</span> <span class="kc">True</span>
</pre></div>
</div>
<hr class="docutils" />
<p><small>This document page is generated from <code class="docutils literal notranslate"><span class="pre">evennia/contrib/base_systems/components/README.md</span></code>. Changes to this
file will be overwritten, so edit that file rather than this one.</small></p>
</section>
</section>
<div class="clearer"></div>
</div>
</div>
</div>
<div class="sphinxsidebar" role="navigation" aria-label="Main">
<div class="sphinxsidebarwrapper">
<p class="logo"><a href="../index.html">
<img class="logo" src="../_static/evennia_logo.png" alt="Logo of Evennia"/>
</a></p>
<search 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" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
<input type="submit" value="Go" />
</form>
</div>
</search>
<script>document.getElementById('searchbox').style.display = "block"</script>
<h3><a href="../index.html">Table of Contents</a></h3>
<ul>
<li><a class="reference internal" href="#">Components</a><ul>
<li><a class="reference internal" href="#the-components-contrib">The Components Contrib</a></li>
<li><a class="reference internal" href="#pros">Pros</a></li>
<li><a class="reference internal" href="#cons">Cons</a></li>
<li><a class="reference internal" href="#how-to-install">How to install</a></li>
<li><a class="reference internal" href="#known-issues">Known Issues</a></li>
<li><a class="reference internal" href="#full-example">Full Example</a></li>
</ul>
</li>
</ul>
<div>
<h4>Previous topic</h4>
<p class="topless"><a href="Contrib-Color-Markups.html"
title="previous chapter">Additional Color markups</a></p>
</div>
<div>
<h4>Next topic</h4>
<p class="topless"><a href="Contrib-Custom-Gametime.html"
title="next chapter">Custom gameime</a></p>
</div>
<div role="note" aria-label="source link">
<!--h3>This Page</h3-->
<ul class="this-page-menu">
<li><a href="../_sources/Contribs/Contrib-Components.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="https://www.evennia.com/docs/latest/index.html">latest (main branch)</a>
</li>
<li>
<a href="https://www.evennia.com/docs/6.x/index.html">v6.0.0 branch (outdated)</a>
</li>
<li>
<a href="https://www.evennia.com/docs/5.x/index.html">v5.0.0 branch (outdated)</a>
</li>
<li>
<a href="https://www.evennia.com/docs/4.x/index.html">v4.0.0 branch (outdated)</a>
</li>
<li>
<a href="https://www.evennia.com/docs/3.x/index.html">v3.0.0 branch (outdated)</a>
</li>
<li>
<a href="https://www.evennia.com/docs/2.x/index.html">v2.0.0 branch (outdated)</a>
</li>
<li>
<a href="https://www.evennia.com/docs/1.x/index.html">v1.0.0 branch (outdated)</a>
</li>
<li>
<a href="https://www.evennia.com/docs/0.x/index.html">v0.9.5 branch (outdated)</a>
</li>
</ul>
</div>
</div>
<div class="clearer"></div>
</div>
<div class="related" role="navigation" aria-label="Related">
<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="Contrib-Custom-Gametime.html" title="Custom gameime"
>next</a> |</li>
<li class="right" >
<a href="Contrib-Color-Markups.html" title="Additional Color markups"
>previous</a> |</li>
<li class="nav-item nav-item-0"><a href="../index.html">Evennia</a> &#187;</li>
<li class="nav-item nav-item-1"><a href="Contribs-Overview.html" >Contribs</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Components</a></li>
</ul>
</div>
<div class="footer" role="contentinfo">
&#169; Copyright 2024, The Evennia developer community.
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 8.2.3.
</div>
</body>
</html>