mirror of
https://github.com/evennia/evennia.git
synced 2026-03-17 13:26:30 +01:00
918 lines
No EOL
97 KiB
HTML
918 lines
No EOL
97 KiB
HTML
<!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>9. Combat base framework — 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="10. Twitch Combat" href="Beginner-Tutorial-Combat-Twitch.html" />
|
||
<link rel="prev" title="8. Non-Player-Characters" href="Beginner-Tutorial-NPCs.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="Beginner-Tutorial-Combat-Twitch.html" title="10. Twitch Combat"
|
||
accesskey="N">next</a> |</li>
|
||
<li class="right" >
|
||
<a href="Beginner-Tutorial-NPCs.html" title="8. Non-Player-Characters"
|
||
accesskey="P">previous</a> |</li>
|
||
<li class="nav-item nav-item-0"><a href="../../../index.html">Evennia</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-2"><a href="../Beginner-Tutorial-Overview.html" >Beginner Tutorial</a> »</li>
|
||
<li class="nav-item nav-item-3"><a href="Beginner-Tutorial-Part3-Overview.html" accesskey="U">Part 3: How We Get There (Example Game)</a> »</li>
|
||
<li class="nav-item nav-item-this"><a href=""><span class="section-number">9. </span>Combat base framework</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="combat-base-framework">
|
||
<h1><span class="section-number">9. </span>Combat base framework<a class="headerlink" href="#combat-base-framework" title="Link to this heading">¶</a></h1>
|
||
<p>Combat is core to many games. Exactly how it works is very game-dependent. In this lesson we will build a framework to implement two common flavors:</p>
|
||
<ul class="simple">
|
||
<li><p>“Twitch-based” combat (<a class="reference internal" href="Beginner-Tutorial-Combat-Twitch.html"><span class="std std-doc">specific lesson here</span></a>) means that you perform a combat action by entering a command, and after some delay (which may depend on your skills etc), the action happens. It’s called ‘twitch’ because actions often happen fast enough that changing your strategy may involve some element of quick thinking and a ‘twitchy trigger finger’.</p></li>
|
||
<li><p>“Turn-based” combat (<a class="reference internal" href="Beginner-Tutorial-Combat-Turnbased.html"><span class="std std-doc">specific lesson here</span></a>) means that players input actions in clear turns. Timeout for entering/queuing your actions is often much longer than twitch-based style. Once everyone made their choice (or the timeout is reached), everyone’s action happens all at once, after which the next turn starts. This style of combat requires less player reflexes.</p></li>
|
||
</ul>
|
||
<p>We will design a base combat system that supports both styles.</p>
|
||
<ul class="simple">
|
||
<li><p>We need a <code class="docutils literal notranslate"><span class="pre">CombatHandler</span></code> to track the progress of combat. This will be a <a class="reference internal" href="../../../Components/Scripts.html"><span class="std std-doc">Script</span></a>. Exactly how this works (and where it is stored) will be a bit different between Twitch- and Turnbased combat. We will create its common framework in this lesson.</p></li>
|
||
<li><p>Combat are divided into <em>actions</em>. We want to be able to easily extend our combat with more possible actions. An action needs Python code to show what actually happens when the action is performed. We will define such code in <code class="docutils literal notranslate"><span class="pre">Action</span></code> classes.</p></li>
|
||
<li><p>We also need a way to describe a <em>specific instance</em> of a given action. That is, when we do an “attack” action, we need at the minimum to know who is being attacked. For this will we use Python <code class="docutils literal notranslate"><span class="pre">dicts</span></code> that we will refer to as <code class="docutils literal notranslate"><span class="pre">action_dicts</span></code>.</p></li>
|
||
</ul>
|
||
<section id="combathandler">
|
||
<h2><span class="section-number">9.1. </span>CombatHandler<a class="headerlink" href="#combathandler" title="Link to this heading">¶</a></h2>
|
||
<blockquote>
|
||
<div><p>Create a new module <code class="docutils literal notranslate"><span class="pre">evadventure/combat_base.py</span></code></p>
|
||
</div></blockquote>
|
||
<aside class="sidebar">
|
||
<p>Under <code class="docutils literal notranslate"><span class="pre">evennia/contrib/tutorials/evadventure/</span></code>, in <a class="reference internal" href="../../../api/evennia.contrib.tutorials.evadventure.combat_base.html#evennia-contrib-tutorials-evadventure-combat-base"><span class="std std-ref">combat_base.py</span></a> you’ll find a complete implementation of the base combat module.</p>
|
||
</aside>
|
||
<p>Our “Combat Handler” will handle the administration around combat. It needs to be <em>persistent</em> (even is we reload the server your combat should keep going).</p>
|
||
<p>Creating the CombatHandler is a little of a catch-22 - how it works depends on how Actions and Action-dicts look. But without having the CombatHandler, it’s hard to know how to design Actions and Action-dicts. So we’ll start with its general structure and fill out the details later in this lesson.</p>
|
||
<p>Below, methods with <code class="docutils literal notranslate"><span class="pre">pass</span></code> will be filled out this lesson while those raising <code class="docutils literal notranslate"><span class="pre">NotImplementedError</span></code> will be different for Twitch/Turnbased combat and will be implemented in their respective lessons following this one.</p>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="c1"># in evadventure/combat_base.py </span>
|
||
|
||
<span class="kn">from</span><span class="w"> </span><span class="nn">evennia</span><span class="w"> </span><span class="kn">import</span> <span class="n">DefaultScript</span>
|
||
|
||
|
||
<span class="k">class</span><span class="w"> </span><span class="nc">CombatFailure</span><span class="p">(</span><span class="ne">RuntimeError</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">"""If some error happens in combat"""</span>
|
||
<span class="k">pass</span>
|
||
|
||
|
||
<span class="k">class</span><span class="w"> </span><span class="nc">EvAdventureCombatBaseHandler</span><span class="p">(</span><span class="n">DefaultSCript</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">""" </span>
|
||
<span class="sd"> This should be created when combat starts. It 'ticks' the combat </span>
|
||
<span class="sd"> and tracks all sides of it.</span>
|
||
<span class="sd"> </span>
|
||
<span class="sd"> """</span>
|
||
<span class="c1"># common for all types of combat</span>
|
||
|
||
<span class="n">action_classes</span> <span class="o">=</span> <span class="p">{}</span> <span class="c1"># to fill in later </span>
|
||
<span class="n">fallback_action_dict</span> <span class="o">=</span> <span class="p">{}</span>
|
||
|
||
<span class="nd">@classmethod</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">get_or_create_combathandler</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">""" Get or create combathandler on `obj`."""</span>
|
||
<span class="k">pass</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">msg</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="p">,</span> <span class="n">combatant</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">broadcast</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">location</span><span class="o">=</span><span class="kc">True</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">""" </span>
|
||
<span class="sd"> Send a message to all combatants.</span>
|
||
<span class="sd"> </span>
|
||
<span class="sd"> """</span>
|
||
<span class="k">pass</span> <span class="c1"># TODO</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">get_combat_summary</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">combatant</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">""" </span>
|
||
<span class="sd"> Get a nicely formatted 'battle report' of combat, from the </span>
|
||
<span class="sd"> perspective of the combatant.</span>
|
||
<span class="sd"> </span>
|
||
<span class="sd"> """</span>
|
||
<span class="k">pass</span> <span class="c1"># TODO</span>
|
||
|
||
<span class="c1"># implemented differently by Twitch- and Turnbased combat</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">get_sides</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">combatant</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">""" </span>
|
||
<span class="sd"> Get who's still alive on the two sides of combat, as a </span>
|
||
<span class="sd"> tuple `([allies], [enemies])` from the perspective of `combatant` </span>
|
||
<span class="sd"> (who is _not_ included in the `allies` list.</span>
|
||
<span class="sd"> </span>
|
||
<span class="sd"> """</span>
|
||
<span class="k">raise</span> <span class="ne">NotImplementedError</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">give_advantage</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">recipient</span><span class="p">,</span> <span class="n">target</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">""" </span>
|
||
<span class="sd"> Give advantage to recipient against target.</span>
|
||
<span class="sd"> </span>
|
||
<span class="sd"> """</span>
|
||
<span class="k">raise</span> <span class="ne">NotImplementedError</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">give_disadvantage</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">recipient</span><span class="p">,</span> <span class="n">target</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">"""</span>
|
||
<span class="sd"> Give disadvantage to recipient against target. </span>
|
||
|
||
<span class="sd"> """</span>
|
||
<span class="k">raise</span> <span class="ne">NotImplementedError</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">has_advantage</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">combatant</span><span class="p">,</span> <span class="n">target</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">""" </span>
|
||
<span class="sd"> Does combatant have advantage against target?</span>
|
||
<span class="sd"> </span>
|
||
<span class="sd"> """</span>
|
||
<span class="k">raise</span> <span class="ne">NotImplementedError</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">has_disadvantage</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">combatant</span><span class="p">,</span> <span class="n">target</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">""" </span>
|
||
<span class="sd"> Does combatant have disadvantage against target?</span>
|
||
<span class="sd"> </span>
|
||
<span class="sd"> """</span>
|
||
<span class="k">raise</span> <span class="ne">NotImplementedError</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">queue_action</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">combatant</span><span class="p">,</span> <span class="n">action_dict</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">""" </span>
|
||
<span class="sd"> Queue an action for the combatant by providing </span>
|
||
<span class="sd"> action dict.</span>
|
||
<span class="sd"> </span>
|
||
<span class="sd"> """</span>
|
||
<span class="k">raise</span> <span class="ne">NotImplementedError</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">execute_next_action</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">combatant</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">""" </span>
|
||
<span class="sd"> Perform a combatant's next action.</span>
|
||
<span class="sd"> </span>
|
||
<span class="sd"> """</span>
|
||
<span class="k">raise</span> <span class="ne">NotImplementedError</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">start_combat</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">""" </span>
|
||
<span class="sd"> Start combat.</span>
|
||
<span class="sd"> </span>
|
||
<span class="sd"> """</span>
|
||
<span class="k">raise</span> <span class="ne">NotImplementedError</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">check_stop_combat</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">"""</span>
|
||
<span class="sd"> Check if the combat is over and if it should be stopped.</span>
|
||
<span class="sd"> </span>
|
||
<span class="sd"> """</span>
|
||
<span class="k">raise</span> <span class="ne">NotImplementedError</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">stop_combat</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">""" </span>
|
||
<span class="sd"> Stop combat and do cleanup.</span>
|
||
<span class="sd"> </span>
|
||
<span class="sd"> """</span>
|
||
<span class="k">raise</span> <span class="ne">NotImplementedError</span>
|
||
|
||
|
||
</pre></div>
|
||
</div>
|
||
<p>The Combat Handler is a <a class="reference internal" href="../../../Components/Scripts.html"><span class="std std-doc">Script</span></a>. Scripts are typeclassed entities, which means that they are persistently stored in the database. Scripts can optionally be stored “on” other objects (such as on Characters or Rooms) or be ‘global’ without any such connection. While Scripts has an optional timer component, it is not active by default and Scripts are commonly used just as plain storage. Since Scripts don’t have an in-game existence, they are great for storing data on ‘systems’ of all kinds, including our combat.</p>
|
||
<p>Let’s implement the generic methods we need.</p>
|
||
<section id="combathandler-get-or-create-combathandler">
|
||
<h3><span class="section-number">9.1.1. </span>CombatHandler.get_or_create_combathandler<a class="headerlink" href="#combathandler-get-or-create-combathandler" title="Link to this heading">¶</a></h3>
|
||
<p>A helper method for quickly getting the combathandler for an ongoing combat and combatant.</p>
|
||
<p>We expect to create the script “on” an object (which one we don’t know yet, but we expect it to be a typeclassed entity).</p>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="c1"># in evadventure/combat_base.py</span>
|
||
|
||
<span class="kn">from</span><span class="w"> </span><span class="nn">evennia</span><span class="w"> </span><span class="kn">import</span> <span class="n">create_script</span>
|
||
|
||
<span class="c1"># ... </span>
|
||
|
||
<span class="k">class</span><span class="w"> </span><span class="nc">EvAdventureCombatBaseHandler</span><span class="p">(</span><span class="n">DefaultScript</span><span class="p">):</span>
|
||
|
||
<span class="c1"># ... </span>
|
||
|
||
<span class="nd">@classmethod</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">get_or_create_combathandler</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">"""</span>
|
||
<span class="sd"> Get or create a combathandler on `obj`.</span>
|
||
<span class="sd"> </span>
|
||
<span class="sd"> Args:</span>
|
||
<span class="sd"> obj (any): The Typeclassed entity to store this Script on. </span>
|
||
<span class="sd"> Keyword Args:</span>
|
||
<span class="sd"> combathandler_key (str): Identifier for script. 'combathandler' by</span>
|
||
<span class="sd"> default.</span>
|
||
<span class="sd"> **kwargs: Extra arguments to the Script, if it is created.</span>
|
||
<span class="sd"> </span>
|
||
<span class="sd"> """</span>
|
||
<span class="k">if</span> <span class="ow">not</span> <span class="n">obj</span><span class="p">:</span>
|
||
<span class="k">raise</span> <span class="n">CombatFailure</span><span class="p">(</span><span class="s2">"Cannot start combat without a place to do it!"</span><span class="p">)</span>
|
||
|
||
<span class="n">combathandler_key</span> <span class="o">=</span> <span class="n">kwargs</span><span class="o">.</span><span class="n">pop</span><span class="p">(</span><span class="s2">"key"</span><span class="p">,</span> <span class="s2">"combathandler"</span><span class="p">)</span>
|
||
<span class="n">combathandler</span> <span class="o">=</span> <span class="n">obj</span><span class="o">.</span><span class="n">ndb</span><span class="o">.</span><span class="n">combathandler</span>
|
||
<span class="k">if</span> <span class="ow">not</span> <span class="n">combathandler</span> <span class="ow">or</span> <span class="ow">not</span> <span class="n">combathandler</span><span class="o">.</span><span class="n">id</span><span class="p">:</span>
|
||
<span class="n">combathandler</span> <span class="o">=</span> <span class="n">obj</span><span class="o">.</span><span class="n">scripts</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">combathandler_key</span><span class="p">)</span><span class="o">.</span><span class="n">first</span><span class="p">()</span>
|
||
<span class="k">if</span> <span class="ow">not</span> <span class="n">combathandler</span><span class="p">:</span>
|
||
<span class="c1"># have to create from scratch</span>
|
||
<span class="n">persistent</span> <span class="o">=</span> <span class="n">kwargs</span><span class="o">.</span><span class="n">pop</span><span class="p">(</span><span class="s2">"persistent"</span><span class="p">,</span> <span class="kc">True</span><span class="p">)</span>
|
||
<span class="n">combathandler</span> <span class="o">=</span> <span class="n">create_script</span><span class="p">(</span>
|
||
<span class="bp">cls</span><span class="p">,</span>
|
||
<span class="n">key</span><span class="o">=</span><span class="n">combathandler_key</span><span class="p">,</span>
|
||
<span class="n">obj</span><span class="o">=</span><span class="n">obj</span><span class="p">,</span>
|
||
<span class="n">persistent</span><span class="o">=</span><span class="n">persistent</span><span class="p">,</span>
|
||
<span class="o">**</span><span class="n">kwargs</span><span class="p">,</span>
|
||
<span class="p">)</span>
|
||
<span class="n">obj</span><span class="o">.</span><span class="n">ndb</span><span class="o">.</span><span class="n">combathandler</span> <span class="o">=</span> <span class="n">combathandler</span>
|
||
<span class="k">return</span> <span class="n">combathandler</span>
|
||
|
||
<span class="c1"># ... </span>
|
||
|
||
</pre></div>
|
||
</div>
|
||
<p>This helper method uses <code class="docutils literal notranslate"><span class="pre">obj.scripts.get()</span></code> to find if the combat script already exists ‘on’ the provided <code class="docutils literal notranslate"><span class="pre">obj</span></code>. If not, it will create it using Evennia’s <a class="reference internal" href="../../../api/evennia.utils.create.html#evennia.utils.create.create_script" title="evennia.utils.create.create_script"><span class="xref myst py py-func">create_script</span></a> function. For some extra speed we cache the handler as <code class="docutils literal notranslate"><span class="pre">obj.ndb.combathandler</span></code> The <code class="docutils literal notranslate"><span class="pre">.ndb.</span></code> (non-db) means that handler is cached only in memory.</p>
|
||
<aside class="sidebar">
|
||
<p class="sidebar-title">Checking .id (or .pk)</p>
|
||
<p>When getting it from cache, we make sure to also check if the combathandler we got has a database <code class="docutils literal notranslate"><span class="pre">.id</span></code> that is not <code class="docutils literal notranslate"><span class="pre">None</span></code> (we could also check <code class="docutils literal notranslate"><span class="pre">.pk</span></code>, stands for “primary key”) . If it’s <code class="docutils literal notranslate"><span class="pre">None</span></code>, this means the database entity was deleted and we just got its cached python representation from memory - we need to recreate it.</p>
|
||
</aside>
|
||
<p><code class="docutils literal notranslate"><span class="pre">get_or_create_combathandler</span></code> is decorated to be a <a class="reference external" href="https://docs.python.org/3/library/functions.html#classmethod">classmethod</a>, meaning it should be used on the handler class directly (rather than on an <em>instance</em> of said class). This makes sense because this method actually should return the new instance.</p>
|
||
<p>As a class method we’ll need to call this directly on the class, like this:</p>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">combathandler</span> <span class="o">=</span> <span class="n">EvAdventureCombatBaseHandler</span><span class="o">.</span><span class="n">get_or_create_combathandler</span><span class="p">(</span><span class="n">combatant</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The result will be a new handler <em>or</em> one that was already defined.</p>
|
||
</section>
|
||
<section id="combathandler-msg">
|
||
<h3><span class="section-number">9.1.2. </span>CombatHandler.msg<a class="headerlink" href="#combathandler-msg" title="Link to this heading">¶</a></h3>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="c1"># in evadventure/combat_base.py </span>
|
||
|
||
<span class="c1"># ... </span>
|
||
|
||
<span class="k">class</span><span class="w"> </span><span class="nc">EvAdventureCombatBaseHandler</span><span class="p">(</span><span class="n">DefaultScript</span><span class="p">):</span>
|
||
<span class="c1"># ... </span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">msg</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="p">,</span> <span class="n">combatant</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">broadcast</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">location</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">"""</span>
|
||
<span class="sd"> Central place for sending messages to combatants. This allows</span>
|
||
<span class="sd"> for adding any combat-specific text-decoration in one place.</span>
|
||
|
||
<span class="sd"> Args:</span>
|
||
<span class="sd"> message (str): The message to send.</span>
|
||
<span class="sd"> combatant (Object): The 'You' in the message, if any.</span>
|
||
<span class="sd"> broadcast (bool): If `False`, `combatant` must be included and</span>
|
||
<span class="sd"> will be the only one to see the message. If `True`, send to</span>
|
||
<span class="sd"> everyone in the location.</span>
|
||
<span class="sd"> location (Object, optional): If given, use this as the location to</span>
|
||
<span class="sd"> send broadcast messages to. If not, use `self.obj` as that</span>
|
||
<span class="sd"> location.</span>
|
||
|
||
<span class="sd"> Notes:</span>
|
||
<span class="sd"> If `combatant` is given, use `$You/you()` markup to create</span>
|
||
<span class="sd"> a message that looks different depending on who sees it. Use</span>
|
||
<span class="sd"> `$You(combatant_key)` to refer to other combatants.</span>
|
||
|
||
<span class="sd"> """</span>
|
||
<span class="k">if</span> <span class="ow">not</span> <span class="n">location</span><span class="p">:</span>
|
||
<span class="n">location</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">obj</span>
|
||
|
||
<span class="n">location_objs</span> <span class="o">=</span> <span class="n">location</span><span class="o">.</span><span class="n">contents</span>
|
||
|
||
<span class="n">exclude</span> <span class="o">=</span> <span class="p">[]</span>
|
||
<span class="k">if</span> <span class="ow">not</span> <span class="n">broadcast</span> <span class="ow">and</span> <span class="n">combatant</span><span class="p">:</span>
|
||
<span class="n">exclude</span> <span class="o">=</span> <span class="p">[</span><span class="n">obj</span> <span class="k">for</span> <span class="n">obj</span> <span class="ow">in</span> <span class="n">location_objs</span> <span class="k">if</span> <span class="n">obj</span> <span class="ow">is</span> <span class="ow">not</span> <span class="n">combatant</span><span class="p">]</span>
|
||
|
||
<span class="n">location</span><span class="o">.</span><span class="n">msg_contents</span><span class="p">(</span>
|
||
<span class="n">message</span><span class="p">,</span>
|
||
<span class="n">exclude</span><span class="o">=</span><span class="n">exclude</span><span class="p">,</span>
|
||
<span class="n">from_obj</span><span class="o">=</span><span class="n">combatant</span><span class="p">,</span>
|
||
<span class="n">mapping</span><span class="o">=</span><span class="p">{</span><span class="n">locobj</span><span class="o">.</span><span class="n">key</span><span class="p">:</span> <span class="n">locobj</span> <span class="k">for</span> <span class="n">locobj</span> <span class="ow">in</span> <span class="n">location_objs</span><span class="p">},</span>
|
||
<span class="p">)</span>
|
||
|
||
<span class="c1"># ... </span>
|
||
</pre></div>
|
||
</div>
|
||
<aside class="sidebar">
|
||
<p>The <code class="docutils literal notranslate"><span class="pre">self.obj</span></code> property of a Script is the entity on which the Script ‘sits’. If set on a Character, <code class="docutils literal notranslate"><span class="pre">self.obj</span></code> will be that Character. If on a room, it’d be that room. For a global script, <code class="docutils literal notranslate"><span class="pre">self.obj</span></code> is <code class="docutils literal notranslate"><span class="pre">None</span></code>.</p>
|
||
</aside>
|
||
<p>We saw the <code class="docutils literal notranslate"><span class="pre">location.msg_contents()</span></code> method before in the <a class="reference internal" href="Beginner-Tutorial-Objects.html#weapons"><span class="std std-ref">Weapon class of the Objects lesson</span></a>. Its purpose is to take a string on the form <code class="docutils literal notranslate"><span class="pre">"$You()</span> <span class="pre">do</span> <span class="pre">stuff</span> <span class="pre">against</span> <span class="pre">$you(key)"</span></code> and make sure all sides see a string suitable just to them. Our <code class="docutils literal notranslate"><span class="pre">msg()</span></code> method will by default broadcast the message to everyone in the room.</p>
|
||
<div style="clear: right;"></div>
|
||
<p>You’d use it like this:</p>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">combathandler</span><span class="o">.</span><span class="n">msg</span><span class="p">(</span>
|
||
<span class="sa">f</span><span class="s2">"$You() $conj(throw) </span><span class="si">{</span><span class="n">item</span><span class="o">.</span><span class="n">key</span><span class="si">}</span><span class="s2"> at $you(</span><span class="si">{</span><span class="n">target</span><span class="o">.</span><span class="n">key</span><span class="si">}</span><span class="s2">)."</span><span class="p">,</span>
|
||
<span class="n">combatant</span><span class="o">=</span><span class="n">combatant</span><span class="p">,</span>
|
||
<span class="n">location</span><span class="o">=</span><span class="n">combatant</span><span class="o">.</span><span class="n">location</span>
|
||
<span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>If combatant is <code class="docutils literal notranslate"><span class="pre">Trickster</span></code>, <code class="docutils literal notranslate"><span class="pre">item.key</span></code> is “a colorful ball” and <code class="docutils literal notranslate"><span class="pre">target.key</span></code> is “Goblin”, then</p>
|
||
<p>The combatant would see:</p>
|
||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>You throw a colorful ball at Goblin.
|
||
</pre></div>
|
||
</div>
|
||
<p>The Goblin sees</p>
|
||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>Trickster throws a colorful ball at you.
|
||
</pre></div>
|
||
</div>
|
||
<p>Everyone else in the room sees</p>
|
||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>Trickster throws a colorful ball at Goblin.
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="combathandler-get-combat-summary">
|
||
<h3><span class="section-number">9.1.3. </span>Combathandler.get_combat_summary<a class="headerlink" href="#combathandler-get-combat-summary" title="Link to this heading">¶</a></h3>
|
||
<p>We want to be able to show a nice summary of the current combat:</p>
|
||
<div class="highlight-shell notranslate"><div class="highlight"><pre><span></span><span class="w"> </span>Goblin<span class="w"> </span>shaman<span class="w"> </span><span class="o">(</span>Perfect<span class="o">)</span>
|
||
<span class="w"> </span>Gregor<span class="w"> </span><span class="o">(</span>Hurt<span class="o">)</span><span class="w"> </span>Goblin<span class="w"> </span>brawler<span class="o">(</span>Hurt<span class="o">)</span>
|
||
<span class="w"> </span>Bob<span class="w"> </span><span class="o">(</span>Perfect<span class="o">)</span><span class="w"> </span>vs<span class="w"> </span>Goblin<span class="w"> </span>grunt<span class="w"> </span><span class="m">1</span><span class="w"> </span><span class="o">(</span>Hurt<span class="o">)</span>
|
||
<span class="w"> </span>Goblin<span class="w"> </span>grunt<span class="w"> </span><span class="m">2</span><span class="w"> </span><span class="o">(</span>Perfect<span class="o">)</span>
|
||
<span class="w"> </span>Goblin<span class="w"> </span>grunt<span class="w"> </span><span class="m">3</span><span class="w"> </span><span class="o">(</span>Wounded<span class="o">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="linenos"> 1</span><span class="c1"># in evadventure/combat_base.py</span>
|
||
<span class="linenos"> 2</span>
|
||
<span class="linenos"> 3</span><span class="c1"># ...</span>
|
||
<span class="linenos"> 4</span>
|
||
<span class="linenos"> 5</span><span class="kn">from</span><span class="w"> </span><span class="nn">evennia</span><span class="w"> </span><span class="kn">import</span> <span class="n">EvTable</span>
|
||
<span class="linenos"> 6</span>
|
||
<span class="linenos"> 7</span><span class="c1"># ... </span>
|
||
<span class="linenos"> 8</span>
|
||
<span class="linenos"> 9</span><span class="k">class</span><span class="w"> </span><span class="nc">EvAdventureCombatBaseHandler</span><span class="p">(</span><span class="n">DefaultScript</span><span class="p">):</span>
|
||
<span class="linenos">10</span>
|
||
<span class="linenos">11</span> <span class="c1"># ... </span>
|
||
<span class="linenos">12</span>
|
||
<span class="linenos">13</span> <span class="k">def</span><span class="w"> </span><span class="nf">get_combat_summary</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">combatant</span><span class="p">):</span>
|
||
<span class="linenos">14</span>
|
||
<span class="hll"><span class="linenos">15</span> <span class="n">allies</span><span class="p">,</span> <span class="n">enemies</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_sides</span><span class="p">(</span><span class="n">combatant</span><span class="p">)</span>
|
||
</span><span class="linenos">16</span> <span class="n">nallies</span><span class="p">,</span> <span class="n">nenemies</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">allies</span><span class="p">),</span> <span class="nb">len</span><span class="p">(</span><span class="n">enemies</span><span class="p">)</span>
|
||
<span class="hll"><span class="linenos">17</span>
|
||
</span><span class="linenos">18</span> <span class="c1"># prepare colors and hurt-levels</span>
|
||
<span class="linenos">19</span> <span class="n">allies</span> <span class="o">=</span> <span class="p">[</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">ally</span><span class="si">}</span><span class="s2"> (</span><span class="si">{</span><span class="n">ally</span><span class="o">.</span><span class="n">hurt_level</span><span class="si">}</span><span class="s2">)"</span> <span class="k">for</span> <span class="n">ally</span> <span class="ow">in</span> <span class="n">allies</span><span class="p">]</span>
|
||
<span class="linenos">20</span> <span class="n">enemies</span> <span class="o">=</span> <span class="p">[</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">enemy</span><span class="si">}</span><span class="s2"> (</span><span class="si">{</span><span class="n">enemy</span><span class="o">.</span><span class="n">hurt_level</span><span class="si">}</span><span class="s2">)"</span> <span class="k">for</span> <span class="n">enemy</span> <span class="ow">in</span> <span class="n">enemies</span><span class="p">]</span>
|
||
<span class="hll"><span class="linenos">21</span>
|
||
</span><span class="hll"><span class="linenos">22</span> <span class="c1"># the center column with the 'vs'</span>
|
||
</span><span class="linenos">23</span> <span class="n">vs_column</span> <span class="o">=</span> <span class="p">[</span><span class="s2">""</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">max</span><span class="p">(</span><span class="n">nallies</span><span class="p">,</span> <span class="n">nenemies</span><span class="p">))]</span>
|
||
<span class="linenos">24</span> <span class="n">vs_column</span><span class="p">[</span><span class="nb">len</span><span class="p">(</span><span class="n">vs_column</span><span class="p">)</span> <span class="o">//</span> <span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"|wvs|n"</span>
|
||
<span class="linenos">25</span>
|
||
<span class="linenos">26</span> <span class="c1"># the two allies / enemies columns should be centered vertically</span>
|
||
<span class="linenos">27</span> <span class="n">diff</span> <span class="o">=</span> <span class="nb">abs</span><span class="p">(</span><span class="n">nallies</span> <span class="o">-</span> <span class="n">nenemies</span><span class="p">)</span>
|
||
<span class="hll"><span class="linenos">28</span> <span class="n">top_empty</span> <span class="o">=</span> <span class="n">diff</span> <span class="o">//</span> <span class="mi">2</span>
|
||
</span><span class="linenos">29</span> <span class="n">bot_empty</span> <span class="o">=</span> <span class="n">diff</span> <span class="o">-</span> <span class="n">top_empty</span>
|
||
<span class="linenos">30</span> <span class="n">topfill</span> <span class="o">=</span> <span class="p">[</span><span class="s2">""</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">top_empty</span><span class="p">)]</span>
|
||
<span class="linenos">31</span> <span class="n">botfill</span> <span class="o">=</span> <span class="p">[</span><span class="s2">""</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">bot_empty</span><span class="p">)]</span>
|
||
<span class="linenos">32</span>
|
||
<span class="linenos">33</span> <span class="k">if</span> <span class="n">nallies</span> <span class="o">>=</span> <span class="n">nenemies</span><span class="p">:</span>
|
||
<span class="linenos">34</span> <span class="n">enemies</span> <span class="o">=</span> <span class="n">topfill</span> <span class="o">+</span> <span class="n">enemies</span> <span class="o">+</span> <span class="n">botfill</span>
|
||
<span class="linenos">35</span> <span class="k">else</span><span class="p">:</span>
|
||
<span class="linenos">36</span> <span class="n">allies</span> <span class="o">=</span> <span class="n">topfill</span> <span class="o">+</span> <span class="n">allies</span> <span class="o">+</span> <span class="n">botfill</span>
|
||
<span class="linenos">37</span>
|
||
<span class="linenos">38</span> <span class="c1"># make a table with three columns</span>
|
||
<span class="linenos">39</span> <span class="k">return</span> <span class="n">evtable</span><span class="o">.</span><span class="n">EvTable</span><span class="p">(</span>
|
||
<span class="linenos">40</span> <span class="n">table</span><span class="o">=</span><span class="p">[</span>
|
||
<span class="hll"><span class="linenos">41</span> <span class="n">evtable</span><span class="o">.</span><span class="n">EvColumn</span><span class="p">(</span><span class="o">*</span><span class="n">allies</span><span class="p">,</span> <span class="n">align</span><span class="o">=</span><span class="s2">"l"</span><span class="p">),</span>
|
||
</span><span class="linenos">42</span> <span class="n">evtable</span><span class="o">.</span><span class="n">EvColumn</span><span class="p">(</span><span class="o">*</span><span class="n">vs_column</span><span class="p">,</span> <span class="n">align</span><span class="o">=</span><span class="s2">"c"</span><span class="p">),</span>
|
||
<span class="linenos">43</span> <span class="n">evtable</span><span class="o">.</span><span class="n">EvColumn</span><span class="p">(</span><span class="o">*</span><span class="n">enemies</span><span class="p">,</span> <span class="n">align</span><span class="o">=</span><span class="s2">"r"</span><span class="p">),</span>
|
||
<span class="linenos">44</span> <span class="p">],</span>
|
||
<span class="linenos">45</span> <span class="n">border</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||
<span class="linenos">46</span> <span class="n">maxwidth</span><span class="o">=</span><span class="mi">78</span><span class="p">,</span>
|
||
<span class="linenos">47</span> <span class="p">)</span>
|
||
<span class="linenos">48</span>
|
||
<span class="linenos">49</span> <span class="c1"># ... </span>
|
||
</pre></div>
|
||
</div>
|
||
<p>This may look complex, but the complexity is only in figuring out how to organize three columns, especially how to to adjust to the two sides on each side of the <code class="docutils literal notranslate"><span class="pre">vs</span></code> are roughly vertically aligned.</p>
|
||
<ul class="simple">
|
||
<li><p><strong>Line 15</strong> : We make use of the <code class="docutils literal notranslate"><span class="pre">self.get_sides(combatant)</span></code> method which we haven’t actually implemented yet. This is because turn-based and twitch-based combat will need different ways to find out what the sides are. The <code class="docutils literal notranslate"><span class="pre">allies</span></code> and <code class="docutils literal notranslate"><span class="pre">enemies</span></code> are lists.</p></li>
|
||
<li><p><strong>Line 17</strong>: The <code class="docutils literal notranslate"><span class="pre">combatant</span></code> is not a part of the <code class="docutils literal notranslate"><span class="pre">allies</span></code> list (this is how we defined <code class="docutils literal notranslate"><span class="pre">get_sides</span></code> to work), so we insert it at the top of the list (so they show first on the left-hand side).</p></li>
|
||
<li><p><strong>Lines 21, 22</strong>: We make use of the <code class="docutils literal notranslate"><span class="pre">.hurt_level</span></code> values of all living things (see the <a class="reference internal" href="Beginner-Tutorial-Characters.html"><span class="std std-doc">LivingMixin of the Character lesson</span></a>).</p></li>
|
||
<li><p><strong>Lines 28-39</strong>: We determine how to vertically center the two sides by adding empty lines above and below the content.</p></li>
|
||
<li><p><strong>Line 41</strong>: The <a class="reference internal" href="../../../Components/EvTable.html"><span class="std std-doc">Evtable</span></a> is an Evennia utility for making, well, text tables. Once we are happy with the columns, we feed them to the table and let Evennia do the rest. It’s worth to explore <code class="docutils literal notranslate"><span class="pre">EvTable</span></code> since it can help you create all sorts of nice layouts.</p></li>
|
||
</ul>
|
||
</section>
|
||
</section>
|
||
<section id="actions">
|
||
<h2><span class="section-number">9.2. </span>Actions<a class="headerlink" href="#actions" title="Link to this heading">¶</a></h2>
|
||
<p>In EvAdventure we will only support a few common combat actions, mapping to the equivalent rolls and checks used in <em>Knave</em>. We will design our combat framework so that it’s easy to expand with other actions later.</p>
|
||
<ul class="simple">
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">hold</span></code> - The simplest action. You just lean back and do nothing.</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">attack</span></code> - You attack a given <code class="docutils literal notranslate"><span class="pre">target</span></code> using your currently equipped weapon. This will become a roll of STR or WIS against the targets’ ARMOR.</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">stunt</span></code> - You make a ‘stunt’, which in roleplaying terms would mean you tripping your opponent, taunting or otherwise trying to gain the upper hand without hurting them. You can do this to give yourself (or an ally) <em>advantage</em> against a <code class="docutils literal notranslate"><span class="pre">target</span></code> on the next action. You can also give a <code class="docutils literal notranslate"><span class="pre">target</span></code> <em>disadvantage</em> against you or an ally for their next action.</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">use</span> <span class="pre">item</span></code> - You make use of a <code class="docutils literal notranslate"><span class="pre">Consumable</span></code> in your inventory. When used on yourself, it’d normally be something like a healing potion. If used on an enemy it could be a firebomb or a bottle of acid.</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">wield</span></code> - You wield an item. Depending on what is being wielded, it will be wielded in different ways: A helmet will be placed on the head, a piece of armor on the chest. A sword will be wielded in one hand, a shield in another. A two-handed axe will use up two hands. Doing so will move whatever was there previously to the backpack.</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">flee</span></code> - You run away/disengage. This action is only applicable in turn-based combat (in twitch-based combat you just move to another room to flee). We will thus wait to define this action until the <a class="reference internal" href="Beginner-Tutorial-Combat-Turnbased.html"><span class="std std-doc">Turnbased combat lesson</span></a>.</p></li>
|
||
</ul>
|
||
</section>
|
||
<section id="action-dicts">
|
||
<h2><span class="section-number">9.3. </span>Action dicts<a class="headerlink" href="#action-dicts" title="Link to this heading">¶</a></h2>
|
||
<p>To pass around the details of an attack (the second point above), we will use a <code class="docutils literal notranslate"><span class="pre">dict</span></code>. A <code class="docutils literal notranslate"><span class="pre">dict</span></code> is simple and also easy to save in an <code class="docutils literal notranslate"><span class="pre">Attribute</span></code>. We’ll call this the <code class="docutils literal notranslate"><span class="pre">action_dict</span></code> and here’s what we need for each action.</p>
|
||
<blockquote>
|
||
<div><p>You don’t need to type these out anywhere, it’s listed here for reference. We will use these dicts when calling <code class="docutils literal notranslate"><span class="pre">combathandler.queue_action(combatant,</span> <span class="pre">action_dict)</span></code>.</p>
|
||
</div></blockquote>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">hold_action_dict</span> <span class="o">=</span> <span class="p">{</span>
|
||
<span class="s2">"key"</span><span class="p">:</span> <span class="s2">"hold"</span>
|
||
<span class="p">}</span>
|
||
<span class="n">attack_action_dict</span> <span class="o">=</span> <span class="p">{</span>
|
||
<span class="s2">"key"</span><span class="p">:</span> <span class="s2">"attack"</span><span class="p">,</span>
|
||
<span class="s2">"target"</span><span class="p">:</span> <span class="o"><</span><span class="n">Character</span><span class="o">/</span><span class="n">NPC</span><span class="o">></span>
|
||
<span class="p">}</span>
|
||
<span class="n">stunt_action_dict</span> <span class="o">=</span> <span class="p">{</span>
|
||
<span class="s2">"key"</span><span class="p">:</span> <span class="s2">"stunt"</span><span class="p">,</span>
|
||
<span class="s2">"recipient"</span><span class="p">:</span> <span class="o"><</span><span class="n">Character</span><span class="o">/</span><span class="n">NPC</span><span class="o">></span><span class="p">,</span> <span class="c1"># who gains advantage/disadvantage</span>
|
||
<span class="s2">"target"</span><span class="p">:</span> <span class="o"><</span><span class="n">Character</span><span class="o">/</span><span class="n">NPC</span><span class="o">></span><span class="p">,</span> <span class="c1"># who the recipient gainst adv/dis against</span>
|
||
<span class="s2">"advantage"</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span> <span class="c1"># grant advantage or disadvantage?</span>
|
||
<span class="s2">"stunt_type"</span><span class="p">:</span> <span class="n">Ability</span><span class="p">,</span> <span class="c1"># Ability to use for the challenge</span>
|
||
<span class="s2">"defense_type"</span><span class="p">:</span> <span class="n">Ability</span><span class="p">,</span> <span class="c1"># what Ability for recipient to defend with if we</span>
|
||
<span class="c1"># are trying to give disadvantage </span>
|
||
<span class="p">}</span>
|
||
<span class="n">use_item_action_dict</span> <span class="o">=</span> <span class="p">{</span>
|
||
<span class="s2">"key"</span><span class="p">:</span> <span class="s2">"use"</span><span class="p">,</span>
|
||
<span class="s2">"item"</span><span class="p">:</span> <span class="o"><</span><span class="n">Object</span><span class="o">></span>
|
||
<span class="s2">"target"</span><span class="p">:</span> <span class="o"><</span><span class="n">Character</span><span class="o">/</span><span class="n">NPC</span><span class="o">/</span><span class="kc">None</span><span class="o">></span> <span class="c1"># if using item against someone else </span>
|
||
<span class="p">}</span>
|
||
<span class="n">wield_action_dict</span> <span class="o">=</span> <span class="p">{</span>
|
||
<span class="s2">"key"</span><span class="p">:</span> <span class="s2">"wield"</span><span class="p">,</span>
|
||
<span class="s2">"item"</span><span class="p">:</span> <span class="o"><</span><span class="n">Object</span><span class="o">></span>
|
||
<span class="p">}</span>
|
||
|
||
<span class="c1"># used only for the turnbased combat, so its Action will be defined there</span>
|
||
<span class="n">flee_action_dict</span> <span class="o">=</span> <span class="p">{</span>
|
||
<span class="s2">"key"</span><span class="p">:</span> <span class="s2">"flee"</span>
|
||
<span class="p">}</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Apart from the <code class="docutils literal notranslate"><span class="pre">stunt</span></code> action, these dicts are all pretty simple. The <code class="docutils literal notranslate"><span class="pre">key</span></code> identifes the action to perform and the other fields identifies the minimum things you need to know in order to resolve each action.</p>
|
||
<p>We have not yet written the code to set these dicts, but we will assume that we know who is performing each of these actions. So if <code class="docutils literal notranslate"><span class="pre">Beowulf</span></code> attacks <code class="docutils literal notranslate"><span class="pre">Grendel</span></code>, Beowulf is not himself included in the attack dict:</p>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">attack_action_dict</span> <span class="o">=</span> <span class="p">{</span>
|
||
<span class="s2">"key"</span><span class="p">:</span> <span class="s2">"attack"</span><span class="p">,</span>
|
||
<span class="s2">"target"</span><span class="p">:</span> <span class="n">Grendel</span>
|
||
<span class="p">}</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Let’s explain the longest action dict, the <code class="docutils literal notranslate"><span class="pre">Stunt</span></code> action dict in more detail as well. In this example, The <code class="docutils literal notranslate"><span class="pre">Trickster</span></code> is performing a <em>Stunt</em> in order to help his friend <code class="docutils literal notranslate"><span class="pre">Paladin</span></code> to gain an INT- <em>advantage</em> against the <code class="docutils literal notranslate"><span class="pre">Goblin</span></code> (maybe the paladin is preparing to cast a spell of something). Since <code class="docutils literal notranslate"><span class="pre">Trickster</span></code> is doing the action, he’s not showing up in the dict:</p>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">stunt_action_dict</span> <span class="o">-</span> <span class="p">{</span>
|
||
<span class="s2">"key"</span><span class="p">:</span> <span class="s2">"stunt"</span><span class="p">,</span>
|
||
<span class="s2">"recipient"</span><span class="p">:</span> <span class="n">Paladin</span><span class="p">,</span>
|
||
<span class="s2">"target"</span><span class="p">:</span> <span class="n">Goblin</span><span class="p">,</span>
|
||
<span class="s2">"advantage"</span><span class="p">:</span> <span class="kc">True</span><span class="p">,</span>
|
||
<span class="s2">"stunt_type"</span><span class="p">:</span> <span class="n">Ability</span><span class="o">.</span><span class="n">INT</span><span class="p">,</span>
|
||
<span class="s2">"defense_type"</span><span class="p">:</span> <span class="n">Ability</span><span class="o">.</span><span class="n">INT</span><span class="p">,</span>
|
||
<span class="p">}</span>
|
||
</pre></div>
|
||
</div>
|
||
<aside class="sidebar">
|
||
<p>In EvAdventure, we’ll always set <code class="docutils literal notranslate"><span class="pre">stunt_type</span> <span class="pre">==</span> <span class="pre">defense_type</span></code> for simplicity. But you could also consider mixing things up so you could use DEX to confuse someone and give them INT disadvantage, for example.</p>
|
||
</aside>
|
||
<p>This should result in an INT vs INT based check between the <code class="docutils literal notranslate"><span class="pre">Trickster</span></code> and the <code class="docutils literal notranslate"><span class="pre">Goblin</span></code> (maybe the trickster is trying to confuse the goblin with some clever word play). If the <code class="docutils literal notranslate"><span class="pre">Trickster</span></code> wins, the <code class="docutils literal notranslate"><span class="pre">Paladin</span></code> gains advantage against the Goblin on the <code class="docutils literal notranslate"><span class="pre">Paladin</span></code>’s next action .</p>
|
||
</section>
|
||
<section id="action-classes">
|
||
<h2><span class="section-number">9.4. </span>Action classes<a class="headerlink" href="#action-classes" title="Link to this heading">¶</a></h2>
|
||
<p>Once our <code class="docutils literal notranslate"><span class="pre">action_dict</span></code> identifies the particular action we should use, we need something that reads those keys/values and actually <em>performs</em> the action.</p>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="c1"># in evadventure/combat_base.py </span>
|
||
|
||
<span class="k">class</span><span class="w"> </span><span class="nc">CombatAction</span><span class="p">:</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">combathandler</span><span class="p">,</span> <span class="n">combatant</span><span class="p">,</span> <span class="n">action_dict</span><span class="p">):</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">combathandler</span> <span class="o">=</span> <span class="n">combathandler</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">combatant</span> <span class="o">=</span> <span class="n">combatant</span>
|
||
|
||
<span class="k">for</span> <span class="n">key</span><span class="p">,</span> <span class="n">val</span> <span class="ow">in</span> <span class="n">action_dict</span><span class="o">.</span><span class="n">items</span><span class="p">();</span>
|
||
<span class="k">if</span> <span class="n">key</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">"_"</span><span class="p">):</span>
|
||
<span class="nb">setattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">val</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>We will create a new instance of this class <em>every time an action is happening</em>. So we store some key things every action will need - we will need a reference to the common <code class="docutils literal notranslate"><span class="pre">combathandler</span></code> (which we will design in the next section), and to the <code class="docutils literal notranslate"><span class="pre">combatant</span></code> (the one performing this action). The <code class="docutils literal notranslate"><span class="pre">action_dict</span></code> is a dict matching the action we want to perform.</p>
|
||
<p>The <code class="docutils literal notranslate"><span class="pre">setattr</span></code> Python standard function assigns the keys/values of the <code class="docutils literal notranslate"><span class="pre">action_dict</span></code> to be properties “on” this action. This is very convenient to use in other methods. So for the <code class="docutils literal notranslate"><span class="pre">stunt</span></code> action, other methods could just access <code class="docutils literal notranslate"><span class="pre">self.key</span></code>, <code class="docutils literal notranslate"><span class="pre">self.recipient</span></code>, <code class="docutils literal notranslate"><span class="pre">self.target</span></code> and so on directly.</p>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="c1"># in evadventure/combat_base.py </span>
|
||
|
||
<span class="k">class</span><span class="w"> </span><span class="nc">CombatAction</span><span class="p">:</span>
|
||
|
||
<span class="c1"># ... </span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">msg</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="p">,</span> <span class="n">broadcast</span><span class="o">=</span><span class="kc">True</span><span class="p">):</span>
|
||
<span class="s2">"Send message to others in combat"</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">combathandler</span><span class="o">.</span><span class="n">msg</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">combatant</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">combatant</span><span class="p">,</span> <span class="n">broadcast</span><span class="o">=</span><span class="n">broadcast</span><span class="p">)</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">can_use</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">"""Return False if combatant can's use this action right now"""</span>
|
||
<span class="k">return</span> <span class="kc">True</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">execute</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">"""Does the actional action"""</span>
|
||
<span class="k">pass</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">post_execute</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">"""Called after `execute`"""</span>
|
||
<span class="k">pass</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>It’s <em>very</em> common to want to send messages to everyone in combat - you need to tell people they are getting attacked, if they get hurt and so on. So having a <code class="docutils literal notranslate"><span class="pre">msg</span></code> helper method on the action is convenient. We offload all the complexity to the combathandler.msg() method.</p>
|
||
<p>The <code class="docutils literal notranslate"><span class="pre">can_use</span></code>, <code class="docutils literal notranslate"><span class="pre">execute</span></code> and <code class="docutils literal notranslate"><span class="pre">post_execute</span></code> should all be called in a chain and we should make sure the <code class="docutils literal notranslate"><span class="pre">combathandler</span></code> calls them like this:</p>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">if</span> <span class="n">action</span><span class="o">.</span><span class="n">can_use</span><span class="p">():</span>
|
||
<span class="n">action</span><span class="o">.</span><span class="n">execute</span><span class="p">()</span>
|
||
<span class="n">action</span><span class="o">.</span><span class="n">post_execute</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<section id="hold-action">
|
||
<h3><span class="section-number">9.4.1. </span>Hold Action<a class="headerlink" href="#hold-action" title="Link to this heading">¶</a></h3>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="c1"># in evadventure/combat_base.py </span>
|
||
|
||
<span class="c1"># ... </span>
|
||
|
||
<span class="k">class</span><span class="w"> </span><span class="nc">CombatActionHold</span><span class="p">(</span><span class="n">CombatAction</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">""" </span>
|
||
<span class="sd"> Action that does nothing </span>
|
||
<span class="sd"> </span>
|
||
<span class="sd"> action_dict = {</span>
|
||
<span class="sd"> "key": "hold"</span>
|
||
<span class="sd"> }</span>
|
||
<span class="sd"> </span>
|
||
<span class="sd"> """</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Holding does nothing but it’s cleaner to nevertheless have a separate class for it. We use the docstring to specify how its action-dict should look.</p>
|
||
</section>
|
||
<section id="attack-action">
|
||
<h3><span class="section-number">9.4.2. </span>Attack Action<a class="headerlink" href="#attack-action" title="Link to this heading">¶</a></h3>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="c1"># in evadventure/combat_base.py</span>
|
||
|
||
<span class="c1"># ... </span>
|
||
|
||
<span class="k">class</span><span class="w"> </span><span class="nc">CombatActionAttack</span><span class="p">(</span><span class="n">CombatAction</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">"""</span>
|
||
<span class="sd"> A regular attack, using a wielded weapon.</span>
|
||
<span class="sd"> </span>
|
||
<span class="sd"> action-dict = {</span>
|
||
<span class="sd"> "key": "attack",</span>
|
||
<span class="sd"> "target": Character/Object</span>
|
||
<span class="sd"> }</span>
|
||
<span class="sd"> </span>
|
||
<span class="sd"> """</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">execute</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="n">attacker</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">combatant</span>
|
||
<span class="n">weapon</span> <span class="o">=</span> <span class="n">attacker</span><span class="o">.</span><span class="n">weapon</span>
|
||
<span class="n">target</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">target</span>
|
||
|
||
<span class="k">if</span> <span class="n">weapon</span><span class="o">.</span><span class="n">at_pre_use</span><span class="p">(</span><span class="n">attacker</span><span class="p">,</span> <span class="n">target</span><span class="p">):</span>
|
||
<span class="n">weapon</span><span class="o">.</span><span class="n">use</span><span class="p">(</span>
|
||
<span class="n">attacker</span><span class="p">,</span> <span class="n">target</span><span class="p">,</span> <span class="n">advantage</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">combathandler</span><span class="o">.</span><span class="n">has_advantage</span><span class="p">(</span><span class="n">attacker</span><span class="p">,</span> <span class="n">target</span><span class="p">)</span>
|
||
<span class="p">)</span>
|
||
<span class="n">weapon</span><span class="o">.</span><span class="n">at_post_use</span><span class="p">(</span><span class="n">attacker</span><span class="p">,</span> <span class="n">target</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Refer to how we <a class="reference internal" href="Beginner-Tutorial-Objects.html#weapons"><span class="std std-ref">designed Evadventure weapons</span></a> to understand what happens here - most of the work is performed by the weapon class - we just plug in the relevant arguments.</p>
|
||
</section>
|
||
<section id="stunt-action">
|
||
<h3><span class="section-number">9.4.3. </span>Stunt Action<a class="headerlink" href="#stunt-action" title="Link to this heading">¶</a></h3>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="c1"># in evadventure/combat_base.py </span>
|
||
|
||
<span class="c1"># ... </span>
|
||
|
||
<span class="k">class</span><span class="w"> </span><span class="nc">CombatActionStunt</span><span class="p">(</span><span class="n">CombatAction</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">"""</span>
|
||
<span class="sd"> Perform a stunt the grants a beneficiary (can be self) advantage on their next action against a </span>
|
||
<span class="sd"> target. Whenever performing a stunt that would affect another negatively (giving them</span>
|
||
<span class="sd"> disadvantage against an ally, or granting an advantage against them, we need to make a check</span>
|
||
<span class="sd"> first. We don't do a check if giving an advantage to an ally or ourselves.</span>
|
||
|
||
<span class="sd"> action_dict = {</span>
|
||
<span class="sd"> "key": "stunt",</span>
|
||
<span class="sd"> "recipient": Character/NPC,</span>
|
||
<span class="sd"> "target": Character/NPC,</span>
|
||
<span class="sd"> "advantage": bool, # if False, it's a disadvantage</span>
|
||
<span class="sd"> "stunt_type": Ability, # what ability (like STR, DEX etc) to use to perform this stunt. </span>
|
||
<span class="sd"> "defense_type": Ability, # what ability to use to defend against (negative) effects of</span>
|
||
<span class="sd"> this stunt.</span>
|
||
<span class="sd"> }</span>
|
||
|
||
<span class="sd"> """</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">execute</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="n">combathandler</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">combathandler</span>
|
||
<span class="n">attacker</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">combatant</span>
|
||
<span class="n">recipient</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">recipient</span> <span class="c1"># the one to receive the effect of the stunt</span>
|
||
<span class="n">target</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">target</span> <span class="c1"># the affected by the stunt (can be the same as recipient/combatant)</span>
|
||
<span class="n">txt</span> <span class="o">=</span> <span class="s2">""</span>
|
||
|
||
<span class="k">if</span> <span class="n">recipient</span> <span class="o">==</span> <span class="n">target</span><span class="p">:</span>
|
||
<span class="c1"># grant another entity dis/advantage against themselves</span>
|
||
<span class="n">defender</span> <span class="o">=</span> <span class="n">recipient</span>
|
||
<span class="k">else</span><span class="p">:</span>
|
||
<span class="c1"># recipient not same as target; who will defend depends on disadvantage or advantage</span>
|
||
<span class="c1"># to give.</span>
|
||
<span class="n">defender</span> <span class="o">=</span> <span class="n">target</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">advantage</span> <span class="k">else</span> <span class="n">recipient</span>
|
||
|
||
<span class="c1"># trying to give advantage to recipient against target. Target defends against caller</span>
|
||
<span class="n">is_success</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">txt</span> <span class="o">=</span> <span class="n">rules</span><span class="o">.</span><span class="n">dice</span><span class="o">.</span><span class="n">opposed_saving_throw</span><span class="p">(</span>
|
||
<span class="n">attacker</span><span class="p">,</span>
|
||
<span class="n">defender</span><span class="p">,</span>
|
||
<span class="n">attack_type</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">stunt_type</span><span class="p">,</span>
|
||
<span class="n">defense_type</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">defense_type</span><span class="p">,</span>
|
||
<span class="n">advantage</span><span class="o">=</span><span class="n">combathandler</span><span class="o">.</span><span class="n">has_advantage</span><span class="p">(</span><span class="n">attacker</span><span class="p">,</span> <span class="n">defender</span><span class="p">),</span>
|
||
<span class="n">disadvantage</span><span class="o">=</span><span class="n">combathandler</span><span class="o">.</span><span class="n">has_disadvantage</span><span class="p">(</span><span class="n">attacker</span><span class="p">,</span> <span class="n">defender</span><span class="p">),</span>
|
||
<span class="p">)</span>
|
||
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">msg</span><span class="p">(</span><span class="sa">f</span><span class="s2">"$You() $conj(attempt) stunt on $You(</span><span class="si">{</span><span class="n">defender</span><span class="o">.</span><span class="n">key</span><span class="si">}</span><span class="s2">). </span><span class="si">{</span><span class="n">txt</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
|
||
|
||
<span class="c1"># deal with results</span>
|
||
<span class="k">if</span> <span class="n">is_success</span><span class="p">:</span>
|
||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">advantage</span><span class="p">:</span>
|
||
<span class="n">combathandler</span><span class="o">.</span><span class="n">give_advantage</span><span class="p">(</span><span class="n">recipient</span><span class="p">,</span> <span class="n">target</span><span class="p">)</span>
|
||
<span class="k">else</span><span class="p">:</span>
|
||
<span class="n">combathandler</span><span class="o">.</span><span class="n">give_disadvantage</span><span class="p">(</span><span class="n">recipient</span><span class="p">,</span> <span class="n">target</span><span class="p">)</span>
|
||
<span class="k">if</span> <span class="n">recipient</span> <span class="o">==</span> <span class="bp">self</span><span class="o">.</span><span class="n">combatant</span><span class="p">:</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">msg</span><span class="p">(</span>
|
||
<span class="sa">f</span><span class="s2">"$You() $conj(gain) </span><span class="si">{</span><span class="s1">'advantage'</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="bp">self</span><span class="o">.</span><span class="n">advantage</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="s1">'disadvantage'</span><span class="si">}</span><span class="s2"> "</span>
|
||
<span class="sa">f</span><span class="s2">"against $You(</span><span class="si">{</span><span class="n">target</span><span class="o">.</span><span class="n">key</span><span class="si">}</span><span class="s2">)!"</span>
|
||
<span class="p">)</span>
|
||
<span class="k">else</span><span class="p">:</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">msg</span><span class="p">(</span>
|
||
<span class="sa">f</span><span class="s2">"$You() $conj(cause) $You(</span><span class="si">{</span><span class="n">recipient</span><span class="o">.</span><span class="n">key</span><span class="si">}</span><span class="s2">) "</span>
|
||
<span class="sa">f</span><span class="s2">"to gain </span><span class="si">{</span><span class="s1">'advantage'</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="bp">self</span><span class="o">.</span><span class="n">advantage</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="s1">'disadvantage'</span><span class="si">}</span><span class="s2"> "</span>
|
||
<span class="sa">f</span><span class="s2">"against $You(</span><span class="si">{</span><span class="n">target</span><span class="o">.</span><span class="n">key</span><span class="si">}</span><span class="s2">)!"</span>
|
||
<span class="p">)</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">msg</span><span class="p">(</span>
|
||
<span class="s2">"|yHaving succeeded, you hold back to plan your next move.|n [hold]"</span><span class="p">,</span>
|
||
<span class="n">broadcast</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
|
||
<span class="p">)</span>
|
||
<span class="k">else</span><span class="p">:</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">msg</span><span class="p">(</span><span class="sa">f</span><span class="s2">"$You(</span><span class="si">{</span><span class="n">defender</span><span class="o">.</span><span class="n">key</span><span class="si">}</span><span class="s2">) $conj(resist)! $You() $conj(fail) the stunt."</span><span class="p">)</span>
|
||
|
||
</pre></div>
|
||
</div>
|
||
<p>The main action here is the call to the <code class="docutils literal notranslate"><span class="pre">rules.dice.opposed_saving_throw</span></code> to determine if the stunt succeeds. After that, most lines is about figuring out who should be given advantage/disadvantage and to communicate the result to the affected parties.</p>
|
||
<p>Note that we make heavy use of the helper methods on the <code class="docutils literal notranslate"><span class="pre">combathandler</span></code> here, even those that are not yet implemented. As long as we pass the <code class="docutils literal notranslate"><span class="pre">action_dict</span></code> into the <code class="docutils literal notranslate"><span class="pre">combathandler</span></code>, the action doesn’t actually care what happens next.</p>
|
||
<p>After we have performed a successful stunt, we queue the <code class="docutils literal notranslate"><span class="pre">combathandler.fallback_action_dict</span></code>. This is because stunts are meant to be one-off things are if we are repeating actions, it would not make sense to repeat the stunt over and over.</p>
|
||
</section>
|
||
<section id="use-item-action">
|
||
<h3><span class="section-number">9.4.4. </span>Use Item Action<a class="headerlink" href="#use-item-action" title="Link to this heading">¶</a></h3>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="c1"># in evadventure/combat_base.py </span>
|
||
|
||
<span class="c1"># ... </span>
|
||
|
||
<span class="k">class</span><span class="w"> </span><span class="nc">CombatActionUseItem</span><span class="p">(</span><span class="n">CombatAction</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">"""</span>
|
||
<span class="sd"> Use an item in combat. This is meant for one-off or limited-use items (so things like scrolls and potions, not swords and shields). If this is some sort of weapon or spell rune, we refer to the item to determine what to use for attack/defense rolls.</span>
|
||
|
||
<span class="sd"> action_dict = {</span>
|
||
<span class="sd"> "key": "use",</span>
|
||
<span class="sd"> "item": Object</span>
|
||
<span class="sd"> "target": Character/NPC/Object/None</span>
|
||
<span class="sd"> }</span>
|
||
|
||
<span class="sd"> """</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">execute</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="n">item</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">item</span>
|
||
<span class="n">user</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">combatant</span>
|
||
<span class="n">target</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">target</span>
|
||
|
||
<span class="k">if</span> <span class="n">item</span><span class="o">.</span><span class="n">at_pre_use</span><span class="p">(</span><span class="n">user</span><span class="p">,</span> <span class="n">target</span><span class="p">):</span>
|
||
<span class="n">item</span><span class="o">.</span><span class="n">use</span><span class="p">(</span>
|
||
<span class="n">user</span><span class="p">,</span>
|
||
<span class="n">target</span><span class="p">,</span>
|
||
<span class="n">advantage</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">combathandler</span><span class="o">.</span><span class="n">has_advantage</span><span class="p">(</span><span class="n">user</span><span class="p">,</span> <span class="n">target</span><span class="p">),</span>
|
||
<span class="n">disadvantage</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">combathandler</span><span class="o">.</span><span class="n">has_disadvantage</span><span class="p">(</span><span class="n">user</span><span class="p">,</span> <span class="n">target</span><span class="p">),</span>
|
||
<span class="p">)</span>
|
||
<span class="n">item</span><span class="o">.</span><span class="n">at_post_use</span><span class="p">(</span><span class="n">user</span><span class="p">,</span> <span class="n">target</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>See the <a class="reference internal" href="Beginner-Tutorial-Objects.html"><span class="std std-doc">Consumable items in the Object lesson</span></a> to see how consumables work. Like with weapons, we offload all the logic to the item we use.</p>
|
||
</section>
|
||
<section id="wield-action">
|
||
<h3><span class="section-number">9.4.5. </span>Wield Action<a class="headerlink" href="#wield-action" title="Link to this heading">¶</a></h3>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="c1"># in evadventure/combat_base.py </span>
|
||
|
||
<span class="c1"># ... </span>
|
||
|
||
<span class="k">class</span><span class="w"> </span><span class="nc">CombatActionWield</span><span class="p">(</span><span class="n">CombatAction</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">"""</span>
|
||
<span class="sd"> Wield a new weapon (or spell) from your inventory. This will </span>
|
||
<span class="sd"> swap out the one you are currently wielding, if any.</span>
|
||
|
||
<span class="sd"> action_dict = {</span>
|
||
<span class="sd"> "key": "wield",</span>
|
||
<span class="sd"> "item": Object</span>
|
||
<span class="sd"> }</span>
|
||
|
||
<span class="sd"> """</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">execute</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">combatant</span><span class="o">.</span><span class="n">equipment</span><span class="o">.</span><span class="n">move</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">item</span><span class="p">)</span>
|
||
|
||
</pre></div>
|
||
</div>
|
||
<p>We rely on the <a class="reference internal" href="Beginner-Tutorial-Equipment.html"><span class="std std-doc">Equipment handler</span></a> we created to handle the swapping of items for us. Since it doesn’t make sense to keep swapping over and over, we queue the fallback action after this one.</p>
|
||
</section>
|
||
</section>
|
||
<section id="testing">
|
||
<h2><span class="section-number">9.5. </span>Testing<a class="headerlink" href="#testing" title="Link to this heading">¶</a></h2>
|
||
<blockquote>
|
||
<div><p>Create a module <code class="docutils literal notranslate"><span class="pre">evadventure/tests/test_combat.py</span></code>.</p>
|
||
</div></blockquote>
|
||
<aside class="sidebar">
|
||
<p>Look under <code class="docutils literal notranslate"><span class="pre">evennia/contrib/tutorials/evadventure/</span></code>, in <a class="reference internal" href="../../../api/evennia.contrib.tutorials.evadventure.tests.test_combat.html#evennia-contrib-tutorials-evadventure-tests-test-combat"><span class="std std-ref">tests/test_combat.py</span></a> for ready-made combat unit tests.</p>
|
||
</aside>
|
||
<p>Unit testing the combat base classes can seem impossible because we have not yet implemented most of it. We can however get very far by the use of <a class="reference external" href="https://docs.python.org/3/library/unittest.mock.html">Mocks</a>. The idea of a Mock is that you <em>replace</em> a piece of code with a dummy object (a ‘mock’) that can be called to return some specific value.</p>
|
||
<p>For example, consider this following test of the <code class="docutils literal notranslate"><span class="pre">CombatHandler.get_combat_summary</span></code>. We can’t just call this because it internally calls <code class="docutils literal notranslate"><span class="pre">.get_sides</span></code> which would raise a <code class="docutils literal notranslate"><span class="pre">NotImplementedError</span></code>.</p>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="linenos"> 1</span><span class="c1"># in evadventure/tests/test_combat.py </span>
|
||
<span class="linenos"> 2</span>
|
||
<span class="linenos"> 3</span><span class="kn">from</span><span class="w"> </span><span class="nn">unittest.mock</span><span class="w"> </span><span class="kn">import</span> <span class="n">Mock</span>
|
||
<span class="linenos"> 4</span>
|
||
<span class="linenos"> 5</span><span class="kn">from</span><span class="w"> </span><span class="nn">evennia.utils.test_resources</span><span class="w"> </span><span class="kn">import</span> <span class="n">EvenniaTestCase</span>
|
||
<span class="linenos"> 6</span><span class="kn">from</span><span class="w"> </span><span class="nn">evennia</span><span class="w"> </span><span class="kn">import</span> <span class="n">create_object</span>
|
||
<span class="linenos"> 7</span><span class="kn">from</span><span class="w"> </span><span class="nn">..</span><span class="w"> </span><span class="kn">import</span> <span class="n">combat_base</span>
|
||
<span class="linenos"> 8</span><span class="kn">from</span><span class="w"> </span><span class="nn">..rooms</span><span class="w"> </span><span class="kn">import</span> <span class="n">EvAdventureRoom</span>
|
||
<span class="linenos"> 9</span><span class="kn">from</span><span class="w"> </span><span class="nn">..characters</span><span class="w"> </span><span class="kn">import</span> <span class="n">EvAdventureCharacter</span>
|
||
<span class="linenos">10</span>
|
||
<span class="linenos">11</span>
|
||
<span class="linenos">12</span><span class="k">class</span><span class="w"> </span><span class="nc">TestEvAdventureCombatBaseHandler</span><span class="p">(</span><span class="n">EvenniaTestCase</span><span class="p">):</span>
|
||
<span class="linenos">13</span>
|
||
<span class="linenos">14</span> <span class="k">def</span><span class="w"> </span><span class="nf">setUp</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="linenos">15</span>
|
||
<span class="linenos">16</span> <span class="bp">self</span><span class="o">.</span><span class="n">location</span> <span class="o">=</span> <span class="n">create_object</span><span class="p">(</span><span class="n">EvAdventureRoom</span><span class="p">,</span> <span class="n">key</span><span class="o">=</span><span class="s2">"testroom"</span><span class="p">)</span>
|
||
<span class="linenos">17</span> <span class="bp">self</span><span class="o">.</span><span class="n">combatant</span> <span class="o">=</span> <span class="n">create_object</span><span class="p">(</span><span class="n">EvAdventureCharacter</span><span class="p">,</span> <span class="n">key</span><span class="o">=</span><span class="s2">"testchar"</span><span class="p">)</span>
|
||
<span class="linenos">18</span> <span class="bp">self</span><span class="o">.</span><span class="n">target</span> <span class="o">=</span> <span class="n">create_object</span><span class="p">(</span><span class="n">EvAdventureMob</span><span class="p">,</span> <span class="n">key</span><span class="o">=</span><span class="s2">"testmonster"</span><span class="p">)</span>
|
||
<span class="linenos">19</span>
|
||
<span class="linenos">20</span> <span class="bp">self</span><span class="o">.</span><span class="n">combathandler</span> <span class="o">=</span> <span class="n">combat_base</span><span class="o">.</span><span class="n">get_combat_summary</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">location</span><span class="p">)</span>
|
||
<span class="linenos">21</span>
|
||
<span class="linenos">22</span> <span class="k">def</span><span class="w"> </span><span class="nf">test_get_combat_summary</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="linenos">23</span>
|
||
<span class="linenos">24</span> <span class="c1"># do the test from perspective of combatant</span>
|
||
<span class="hll"><span class="linenos">25</span> <span class="bp">self</span><span class="o">.</span><span class="n">combathandler</span><span class="o">.</span><span class="n">get_sides</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">(</span><span class="n">return_value</span><span class="o">=</span><span class="p">([],</span> <span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">target</span><span class="p">]))</span>
|
||
</span><span class="linenos">26</span> <span class="n">result</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">combathandler</span><span class="o">.</span><span class="n">get_combat_summary</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">combatant</span><span class="p">))</span>
|
||
<span class="linenos">27</span> <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span>
|
||
<span class="linenos">28</span> <span class="n">result</span><span class="p">,</span>
|
||
<span class="linenos">29</span> <span class="s2">" testchar (Perfect) vs testmonster (Perfect)"</span>
|
||
<span class="linenos">30</span> <span class="p">)</span>
|
||
<span class="linenos">31</span> <span class="c1"># test from the perspective of the monster </span>
|
||
<span class="hll"><span class="linenos">32</span> <span class="bp">self</span><span class="o">.</span><span class="n">combathandler</span><span class="o">.</span><span class="n">get_sides</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">(</span><span class="n">return_value</span><span class="o">=</span><span class="p">([],</span> <span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">combatant</span><span class="p">]))</span>
|
||
</span><span class="linenos">33</span> <span class="n">result</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">combathandler</span><span class="o">.</span><span class="n">get_combat_summary</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">target</span><span class="p">))</span>
|
||
<span class="linenos">34</span> <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span>
|
||
<span class="linenos">35</span> <span class="n">result</span><span class="p">,</span>
|
||
<span class="linenos">36</span> <span class="s2">" testmonster (Perfect) vs testchar (Perfect)"</span>
|
||
<span class="linenos">37</span> <span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The interesting places are where we apply the mocks:</p>
|
||
<ul class="simple">
|
||
<li><p><strong>Line 25</strong> and <strong>Line 32</strong>: While <code class="docutils literal notranslate"><span class="pre">get_sides</span></code> is not implemented yet, we know what it is <em>supposed</em> to return - a tuple of lists. So for the sake of the test, we <em>replace</em> the <code class="docutils literal notranslate"><span class="pre">get_sides</span></code> method with a mock that when called will return something useful.</p></li>
|
||
</ul>
|
||
<p>With this kind of approach it’s possible to fully test a system also when it’s not ‘complete’ yet.</p>
|
||
</section>
|
||
<section id="conclusions">
|
||
<h2><span class="section-number">9.6. </span>Conclusions<a class="headerlink" href="#conclusions" title="Link to this heading">¶</a></h2>
|
||
<p>We have the core functionality we need for our combat system! In the following two lessons we will make use of these building blocks to create two styles of combat.</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="#">9. Combat base framework</a><ul>
|
||
<li><a class="reference internal" href="#combathandler">9.1. CombatHandler</a><ul>
|
||
<li><a class="reference internal" href="#combathandler-get-or-create-combathandler">9.1.1. CombatHandler.get_or_create_combathandler</a></li>
|
||
<li><a class="reference internal" href="#combathandler-msg">9.1.2. CombatHandler.msg</a></li>
|
||
<li><a class="reference internal" href="#combathandler-get-combat-summary">9.1.3. Combathandler.get_combat_summary</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#actions">9.2. Actions</a></li>
|
||
<li><a class="reference internal" href="#action-dicts">9.3. Action dicts</a></li>
|
||
<li><a class="reference internal" href="#action-classes">9.4. Action classes</a><ul>
|
||
<li><a class="reference internal" href="#hold-action">9.4.1. Hold Action</a></li>
|
||
<li><a class="reference internal" href="#attack-action">9.4.2. Attack Action</a></li>
|
||
<li><a class="reference internal" href="#stunt-action">9.4.3. Stunt Action</a></li>
|
||
<li><a class="reference internal" href="#use-item-action">9.4.4. Use Item Action</a></li>
|
||
<li><a class="reference internal" href="#wield-action">9.4.5. Wield Action</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#testing">9.5. Testing</a></li>
|
||
<li><a class="reference internal" href="#conclusions">9.6. Conclusions</a></li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
|
||
<div>
|
||
<h4>Previous topic</h4>
|
||
<p class="topless"><a href="Beginner-Tutorial-NPCs.html"
|
||
title="previous chapter"><span class="section-number">8. </span>Non-Player-Characters</a></p>
|
||
</div>
|
||
<div>
|
||
<h4>Next topic</h4>
|
||
<p class="topless"><a href="Beginner-Tutorial-Combat-Twitch.html"
|
||
title="next chapter"><span class="section-number">10. </span>Twitch Combat</a></p>
|
||
</div>
|
||
<div role="note" aria-label="source link">
|
||
<!--h3>This Page</h3-->
|
||
<ul class="this-page-menu">
|
||
<li><a href="../../../_sources/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Combat-Base.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/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="Beginner-Tutorial-Combat-Twitch.html" title="10. Twitch Combat"
|
||
>next</a> |</li>
|
||
<li class="right" >
|
||
<a href="Beginner-Tutorial-NPCs.html" title="8. Non-Player-Characters"
|
||
>previous</a> |</li>
|
||
<li class="nav-item nav-item-0"><a href="../../../index.html">Evennia</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-2"><a href="../Beginner-Tutorial-Overview.html" >Beginner Tutorial</a> »</li>
|
||
<li class="nav-item nav-item-3"><a href="Beginner-Tutorial-Part3-Overview.html" >Part 3: How We Get There (Example Game)</a> »</li>
|
||
<li class="nav-item nav-item-this"><a href=""><span class="section-number">9. </span>Combat base framework</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> 8.2.3.
|
||
</div>
|
||
</body>
|
||
</html> |