mirror of
https://github.com/evennia/evennia.git
synced 2026-03-18 13:56:30 +01:00
649 lines
No EOL
67 KiB
HTML
649 lines
No EOL
67 KiB
HTML
|
||
<!DOCTYPE html>
|
||
|
||
<html>
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
|
||
|
||
<title>Turn based Combat System — Evennia 1.0-dev documentation</title>
|
||
<link rel="stylesheet" href="../_static/nature.css" type="text/css" />
|
||
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
|
||
<script id="documentation_options" data-url_root="../" src="../_static/documentation_options.js"></script>
|
||
<script src="../_static/jquery.js"></script>
|
||
<script src="../_static/underscore.js"></script>
|
||
<script src="../_static/doctools.js"></script>
|
||
<script src="../_static/language_data.js"></script>
|
||
<link rel="shortcut icon" href="../_static/favicon.ico"/>
|
||
<link rel="index" title="Index" href="../genindex.html" />
|
||
<link rel="search" title="Search" href="../search.html" />
|
||
<link rel="next" title="Making a sittable object" href="A-Sittable-Object.html" />
|
||
<link rel="prev" title="Implementing a game rule system" href="Implementing-a-game-rule-system.html" />
|
||
</head><body>
|
||
<div class="related" role="navigation" aria-label="related navigation">
|
||
<h3>Navigation</h3>
|
||
<ul>
|
||
<li class="right" style="margin-right: 10px">
|
||
<a href="../genindex.html" title="General Index"
|
||
accesskey="I">index</a></li>
|
||
<li class="right" >
|
||
<a href="../py-modindex.html" title="Python Module Index"
|
||
>modules</a> |</li>
|
||
<li class="right" >
|
||
<a href="A-Sittable-Object.html" title="Making a sittable object"
|
||
accesskey="N">next</a> |</li>
|
||
<li class="right" >
|
||
<a href="Implementing-a-game-rule-system.html" title="Implementing a game rule system"
|
||
accesskey="P">previous</a> |</li>
|
||
<li class="nav-item nav-item-0"><a href="../index.html">Evennia 1.0-dev</a> »</li>
|
||
<li class="nav-item nav-item-1"><a href="Howtos-Overview.html" accesskey="U">Tutorials and Howto’s</a> »</li>
|
||
<li class="nav-item nav-item-this"><a href="">Turn based Combat System</a></li>
|
||
</ul>
|
||
<div class="develop">develop branch</div>
|
||
</div>
|
||
|
||
<div class="document">
|
||
|
||
<div class="documentwrapper">
|
||
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
|
||
<div class="sphinxsidebarwrapper">
|
||
<p class="logo"><a href="../index.html">
|
||
<img class="logo" src="../_static/evennia_logo.png" alt="Logo"/>
|
||
</a></p>
|
||
<div id="searchbox" style="display: none" role="search">
|
||
<h3 id="searchlabel">Quick search</h3>
|
||
<div class="searchformwrapper">
|
||
<form class="search" action="../search.html" method="get">
|
||
<input type="text" name="q" aria-labelledby="searchlabel" />
|
||
<input type="submit" value="Go" />
|
||
</form>
|
||
</div>
|
||
</div>
|
||
<script>$('#searchbox').show(0);</script>
|
||
<h3><a href="../index.html">Table of Contents</a></h3>
|
||
<ul>
|
||
<li><a class="reference internal" href="#">Turn based Combat System</a><ul>
|
||
<li><a class="reference internal" href="#overview-of-combat-system-concepts">Overview of combat system concepts</a></li>
|
||
<li><a class="reference internal" href="#tutorial-overview">Tutorial overview</a></li>
|
||
<li><a class="reference internal" href="#the-combat-handler">The combat handler</a></li>
|
||
<li><a class="reference internal" href="#combat-commands">Combat commands</a></li>
|
||
<li><a class="reference internal" href="#rules-module">Rules module</a></li>
|
||
<li><a class="reference internal" href="#combat-initiator-command">Combat initiator command</a></li>
|
||
<li><a class="reference internal" href="#expanding-the-example">Expanding the example</a></li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
|
||
<h4>Previous topic</h4>
|
||
<p class="topless"><a href="Implementing-a-game-rule-system.html"
|
||
title="previous chapter">Implementing a game rule system</a></p>
|
||
<h4>Next topic</h4>
|
||
<p class="topless"><a href="A-Sittable-Object.html"
|
||
title="next chapter">Making a sittable object</a></p>
|
||
<div role="note" aria-label="source link">
|
||
<!--h3>This Page</h3-->
|
||
<ul class="this-page-menu">
|
||
<li><a href="../_sources/Howtos/Turn-based-Combat-System.md.txt"
|
||
rel="nofollow">Show Page Source</a></li>
|
||
</ul>
|
||
</div><h3>Links</h3>
|
||
<ul>
|
||
<li><a href="https://www.evennia.com">Home page</a> </li>
|
||
<li><a href="https://github.com/evennia/evennia">Evennia Github</a> </li>
|
||
<li><a href="http://games.evennia.com">Game Index</a> </li>
|
||
<li>
|
||
<a href="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>Versions</h3>
|
||
<ul>
|
||
<li><a href="Turn-based-Combat-System.html">1.0-dev (develop branch)</a></li>
|
||
<ul>
|
||
<li><a href="../0.9.5/index.html">0.9.5 (v0.9.5 branch)</a></li>
|
||
|
||
</ul>
|
||
|
||
</div>
|
||
</div>
|
||
<div class="bodywrapper">
|
||
<div class="body" role="main">
|
||
|
||
<section class="tex2jax_ignore mathjax_ignore" id="turn-based-combat-system">
|
||
<h1>Turn based Combat System<a class="headerlink" href="#turn-based-combat-system" title="Permalink to this headline">¶</a></h1>
|
||
<p>This tutorial gives an example of a full, if simplified, combat system for Evennia. It was inspired
|
||
by the discussions held on the <a class="reference external" href="https://groups.google.com/forum/#%21msg/evennia/wnJNM2sXSfs/-dbLRrgWnYMJ">mailing
|
||
list</a>.</p>
|
||
<section id="overview-of-combat-system-concepts">
|
||
<h2>Overview of combat system concepts<a class="headerlink" href="#overview-of-combat-system-concepts" title="Permalink to this headline">¶</a></h2>
|
||
<p>Most MUDs will use some sort of combat system. There are several main variations:</p>
|
||
<ul class="simple">
|
||
<li><p><em>Freeform</em> - the simplest form of combat to implement, common to MUSH-style roleplaying games.
|
||
This means the system only supplies dice rollers or maybe commands to compare skills and spit out
|
||
the result. Dice rolls are done to resolve combat according to the rules of the game and to direct
|
||
the scene. A game master may be required to resolve rule disputes.</p></li>
|
||
<li><p><em>Twitch</em> - This is the traditional MUD hack&slash style combat. In a twitch system there is often
|
||
no difference between your normal “move-around-and-explore mode” and the “combat mode”. You enter an
|
||
attack command and the system will calculate if the attack hits and how much damage was caused.
|
||
Normally attack commands have some sort of timeout or notion of recovery/balance to reduce the
|
||
advantage of spamming or client scripting. Whereas the simplest systems just means entering <code class="docutils literal notranslate"><span class="pre">kill</span> <span class="pre"><target></span></code> over and over, more sophisticated twitch systems include anything from defensive stances
|
||
to tactical positioning.</p></li>
|
||
<li><p><em>Turn-based</em> - a turn based system means that the system pauses to make sure all combatants can
|
||
choose their actions before continuing. In some systems, such entered actions happen immediately
|
||
(like twitch-based) whereas in others the resolution happens simultaneously at the end of the turn.
|
||
The disadvantage of a turn-based system is that the game must switch to a “combat mode” and one also
|
||
needs to take special care of how to handle new combatants and the passage of time. The advantage is
|
||
that success is not dependent on typing speed or of setting up quick client macros. This potentially
|
||
allows for emoting as part of combat which is an advantage for roleplay-heavy games.</p></li>
|
||
</ul>
|
||
<p>To implement a freeform combat system all you need is a dice roller and a roleplaying rulebook. See
|
||
<a class="reference external" href="https://github.com/evennia/evennia/blob/master/evennia/contrib/dice.py">contrib/dice.py</a> for an
|
||
example dice roller. To implement at twitch-based system you basically need a few combat
|
||
<a class="reference internal" href="../Components/Commands.html"><span class="doc std std-doc">commands</span></a>, possibly ones with a <a class="reference internal" href="Command-Cooldown.html"><span class="doc std std-doc">cooldown</span></a>. You also need a <a class="reference internal" href="Implementing-a-game-rule-system.html"><span class="doc std std-doc">game rule
|
||
module</span></a> that makes use of it. We will focus on the turn-based
|
||
variety here.</p>
|
||
</section>
|
||
<section id="tutorial-overview">
|
||
<h2>Tutorial overview<a class="headerlink" href="#tutorial-overview" title="Permalink to this headline">¶</a></h2>
|
||
<p>This tutorial will implement the slightly more complex turn-based combat system. Our example has the
|
||
following properties:</p>
|
||
<ul class="simple">
|
||
<li><p>Combat is initiated with <code class="docutils literal notranslate"><span class="pre">attack</span> <span class="pre"><target></span></code>, this initiates the combat mode.</p></li>
|
||
<li><p>Characters may join an ongoing battle using <code class="docutils literal notranslate"><span class="pre">attack</span> <span class="pre"><target></span></code> against a character already in
|
||
combat.</p></li>
|
||
<li><p>Each turn every combating character will get to enter two commands, their internal order matters
|
||
and they are compared one-to-one in the order given by each combatant. Use of <code class="docutils literal notranslate"><span class="pre">say</span></code> and <code class="docutils literal notranslate"><span class="pre">pose</span></code> is
|
||
free.</p></li>
|
||
<li><p>The commands are (in our example) simple; they can either <code class="docutils literal notranslate"><span class="pre">hit</span> <span class="pre"><target></span></code>, <code class="docutils literal notranslate"><span class="pre">feint</span> <span class="pre"><target></span></code> or
|
||
<code class="docutils literal notranslate"><span class="pre">parry</span> <span class="pre"><target></span></code>. They can also <code class="docutils literal notranslate"><span class="pre">defend</span></code>, a generic passive defense. Finally they may choose to
|
||
<code class="docutils literal notranslate"><span class="pre">disengage/flee</span></code>.</p></li>
|
||
<li><p>When attacking we use a classic [rock-paper-scissors](<a class="reference external" href="https://en.wikipedia.org/wiki/Rock-paper-">https://en.wikipedia.org/wiki/Rock-paper-</a>
|
||
scissors) mechanic to determine success: <code class="docutils literal notranslate"><span class="pre">hit</span></code> defeats <code class="docutils literal notranslate"><span class="pre">feint</span></code>, which defeats <code class="docutils literal notranslate"><span class="pre">parry</span></code> which defeats
|
||
<code class="docutils literal notranslate"><span class="pre">hit</span></code>. <code class="docutils literal notranslate"><span class="pre">defend</span></code> is a general passive action that has a percentage chance to win against <code class="docutils literal notranslate"><span class="pre">hit</span></code>
|
||
(only).</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">disengage/flee</span></code> must be entered two times in a row and will only succeed if there is no <code class="docutils literal notranslate"><span class="pre">hit</span></code>
|
||
against them in that time. If so they will leave combat mode.</p></li>
|
||
<li><p>Once every player has entered two commands, all commands are resolved in order and the result is
|
||
reported. A new turn then begins.</p></li>
|
||
<li><p>If players are too slow the turn will time out and any unset commands will be set to <code class="docutils literal notranslate"><span class="pre">defend</span></code>.</p></li>
|
||
</ul>
|
||
<p>For creating the combat system we will need the following components:</p>
|
||
<ul class="simple">
|
||
<li><p>A combat handler. This is the main mechanic of the system. This is a <a class="reference internal" href="../Components/Scripts.html"><span class="doc std std-doc">Script</span></a> object
|
||
created for each combat. It is not assigned to a specific object but is shared by the combating
|
||
characters and handles all the combat information. Since Scripts are database entities it also means
|
||
that the combat will not be affected by a server reload.</p></li>
|
||
<li><p>A combat <a class="reference internal" href="../Components/Command-Sets.html"><span class="doc std std-doc">command set</span></a> with the relevant commands needed for combat, such as the
|
||
various attack/defend options and the <code class="docutils literal notranslate"><span class="pre">flee/disengage</span></code> command to leave the combat mode.</p></li>
|
||
<li><p>A rule resolution system. The basics of making such a module is described in the <a class="reference internal" href="Implementing-a-game-rule-system.html"><span class="doc std std-doc">rule system
|
||
tutorial</span></a>. We will only sketch such a module here for our end-turn
|
||
combat resolution.</p></li>
|
||
<li><p>An <code class="docutils literal notranslate"><span class="pre">attack</span></code> <a class="reference internal" href="../Components/Commands.html"><span class="doc std std-doc">command</span></a> for initiating the combat mode. This is added to the default
|
||
command set. It will create the combat handler and add the character(s) to it. It will also assign
|
||
the combat command set to the characters.</p></li>
|
||
</ul>
|
||
</section>
|
||
<section id="the-combat-handler">
|
||
<h2>The combat handler<a class="headerlink" href="#the-combat-handler" title="Permalink to this headline">¶</a></h2>
|
||
<p>The <em>combat handler</em> is implemented as a stand-alone <a class="reference internal" href="../Components/Scripts.html"><span class="doc std std-doc">Script</span></a>. This Script is created when
|
||
the first Character decides to attack another and is deleted when no one is fighting any more. Each
|
||
handler represents one instance of combat and one combat only. Each instance of combat can hold any
|
||
number of characters but each character can only be part of one combat at a time (a player would
|
||
need to disengage from the first combat before they could join another).</p>
|
||
<p>The reason we don’t store this Script “on” any specific character is because any character may leave
|
||
the combat at any time. Instead the script holds references to all characters involved in the
|
||
combat. Vice-versa, all characters holds a back-reference to the current combat handler. While we
|
||
don’t use this very much here this might allow the combat commands on the characters to access and
|
||
update the combat handler state directly.</p>
|
||
<p><em>Note: Another way to implement a combat handler would be to use a normal Python object and handle
|
||
time-keeping with the <a class="reference internal" href="../Components/TickerHandler.html"><span class="doc std std-doc">TickerHandler</span></a>. This would require either adding custom hook
|
||
methods on the character or to implement a custom child of the TickerHandler class to track turns.
|
||
Whereas the TickerHandler is easy to use, a Script offers more power in this case.</em></p>
|
||
<p>Here is a basic combat handler. Assuming our game folder is named <code class="docutils literal notranslate"><span class="pre">mygame</span></code>, we store it in
|
||
<code class="docutils literal notranslate"><span class="pre">mygame/typeclasses/combat_handler.py</span></code>:</p>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="c1"># mygame/typeclasses/combat_handler.py</span>
|
||
|
||
<span class="kn">import</span> <span class="nn">random</span>
|
||
<span class="kn">from</span> <span class="nn">evennia</span> <span class="kn">import</span> <span class="n">DefaultScript</span>
|
||
<span class="kn">from</span> <span class="nn">world.rules</span> <span class="kn">import</span> <span class="n">resolve_combat</span>
|
||
|
||
<span class="k">class</span> <span class="nc">CombatHandler</span><span class="p">(</span><span class="n">DefaultScript</span><span class="p">):</span>
|
||
<span class="sd">"""</span>
|
||
<span class="sd"> This implements the combat handler.</span>
|
||
<span class="sd"> """</span>
|
||
|
||
<span class="c1"># standard Script hooks </span>
|
||
|
||
<span class="k">def</span> <span class="nf">at_script_creation</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="s2">"Called when script is first created"</span>
|
||
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">key</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">"combat_handler_</span><span class="si">{</span><span class="n">random</span><span class="o">.</span><span class="n">randint</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1000</span><span class="p">)</span><span class="si">}</span><span class="s2">"</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">desc</span> <span class="o">=</span> <span class="s2">"handles combat"</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">interval</span> <span class="o">=</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">2</span> <span class="c1"># two minute timeout</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">start_delay</span> <span class="o">=</span> <span class="kc">True</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">persistent</span> <span class="o">=</span> <span class="kc">True</span>
|
||
|
||
<span class="c1"># store all combatants</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">characters</span> <span class="o">=</span> <span class="p">{}</span>
|
||
<span class="c1"># store all actions for each turn</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">turn_actions</span> <span class="o">=</span> <span class="p">{}</span>
|
||
<span class="c1"># number of actions entered per combatant</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">action_count</span> <span class="o">=</span> <span class="p">{}</span>
|
||
|
||
<span class="k">def</span> <span class="nf">_init_character</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">character</span><span class="p">):</span>
|
||
<span class="sd">"""</span>
|
||
<span class="sd"> This initializes handler back-reference </span>
|
||
<span class="sd"> and combat cmdset on a character</span>
|
||
<span class="sd"> """</span>
|
||
<span class="n">character</span><span class="o">.</span><span class="n">ndb</span><span class="o">.</span><span class="n">combat_handler</span> <span class="o">=</span> <span class="bp">self</span>
|
||
<span class="n">character</span><span class="o">.</span><span class="n">cmdset</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="s2">"commands.combat.CombatCmdSet"</span><span class="p">)</span>
|
||
|
||
<span class="k">def</span> <span class="nf">_cleanup_character</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">character</span><span class="p">):</span>
|
||
<span class="sd">"""</span>
|
||
<span class="sd"> Remove character from handler and clean </span>
|
||
<span class="sd"> it of the back-reference and cmdset</span>
|
||
<span class="sd"> """</span>
|
||
<span class="n">dbref</span> <span class="o">=</span> <span class="n">character</span><span class="o">.</span><span class="n">id</span>
|
||
<span class="k">del</span> <span class="bp">self</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">characters</span><span class="p">[</span><span class="n">dbref</span><span class="p">]</span>
|
||
<span class="k">del</span> <span class="bp">self</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">turn_actions</span><span class="p">[</span><span class="n">dbref</span><span class="p">]</span>
|
||
<span class="k">del</span> <span class="bp">self</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">action_count</span><span class="p">[</span><span class="n">dbref</span><span class="p">]</span>
|
||
<span class="k">del</span> <span class="n">character</span><span class="o">.</span><span class="n">ndb</span><span class="o">.</span><span class="n">combat_handler</span>
|
||
<span class="n">character</span><span class="o">.</span><span class="n">cmdset</span><span class="o">.</span><span class="n">delete</span><span class="p">(</span><span class="s2">"commands.combat.CombatCmdSet"</span><span class="p">)</span>
|
||
|
||
<span class="k">def</span> <span class="nf">at_start</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="sd">"""</span>
|
||
<span class="sd"> This is called on first start but also when the script is restarted</span>
|
||
<span class="sd"> after a server reboot. We need to re-assign this combat handler to </span>
|
||
<span class="sd"> all characters as well as re-assign the cmdset.</span>
|
||
<span class="sd"> """</span>
|
||
<span class="k">for</span> <span class="n">character</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">characters</span><span class="o">.</span><span class="n">values</span><span class="p">():</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">_init_character</span><span class="p">(</span><span class="n">character</span><span class="p">)</span>
|
||
|
||
<span class="k">def</span> <span class="nf">at_stop</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="s2">"Called just before the script is stopped/destroyed."</span>
|
||
<span class="k">for</span> <span class="n">character</span> <span class="ow">in</span> <span class="nb">list</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">characters</span><span class="o">.</span><span class="n">values</span><span class="p">()):</span>
|
||
<span class="c1"># note: the list() call above disconnects list from database</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">_cleanup_character</span><span class="p">(</span><span class="n">character</span><span class="p">)</span>
|
||
|
||
<span class="k">def</span> <span class="nf">at_repeat</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="sd">"""</span>
|
||
<span class="sd"> This is called every self.interval seconds (turn timeout) or </span>
|
||
<span class="sd"> when force_repeat is called (because everyone has entered their </span>
|
||
<span class="sd"> commands). We know this by checking the existence of the</span>
|
||
<span class="sd"> `normal_turn_end` NAttribute, set just before calling </span>
|
||
<span class="sd"> force_repeat.</span>
|
||
<span class="sd"> </span>
|
||
<span class="sd"> """</span>
|
||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">ndb</span><span class="o">.</span><span class="n">normal_turn_end</span><span class="p">:</span>
|
||
<span class="c1"># we get here because the turn ended normally</span>
|
||
<span class="c1"># (force_repeat was called) - no msg output</span>
|
||
<span class="k">del</span> <span class="bp">self</span><span class="o">.</span><span class="n">ndb</span><span class="o">.</span><span class="n">normal_turn_end</span>
|
||
<span class="k">else</span><span class="p">:</span>
|
||
<span class="c1"># turn timeout</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">msg_all</span><span class="p">(</span><span class="s2">"Turn timer timed out. Continuing."</span><span class="p">)</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">end_turn</span><span class="p">()</span>
|
||
|
||
<span class="c1"># Combat-handler methods</span>
|
||
|
||
<span class="k">def</span> <span class="nf">add_character</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">character</span><span class="p">):</span>
|
||
<span class="s2">"Add combatant to handler"</span>
|
||
<span class="n">dbref</span> <span class="o">=</span> <span class="n">character</span><span class="o">.</span><span class="n">id</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">characters</span><span class="p">[</span><span class="n">dbref</span><span class="p">]</span> <span class="o">=</span> <span class="n">character</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">action_count</span><span class="p">[</span><span class="n">dbref</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">turn_actions</span><span class="p">[</span><span class="n">dbref</span><span class="p">]</span> <span class="o">=</span> <span class="p">[(</span><span class="s2">"defend"</span><span class="p">,</span> <span class="n">character</span><span class="p">,</span> <span class="kc">None</span><span class="p">),</span>
|
||
<span class="p">(</span><span class="s2">"defend"</span><span class="p">,</span> <span class="n">character</span><span class="p">,</span> <span class="kc">None</span><span class="p">)]</span>
|
||
<span class="c1"># set up back-reference</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">_init_character</span><span class="p">(</span><span class="n">character</span><span class="p">)</span>
|
||
|
||
<span class="k">def</span> <span class="nf">remove_character</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">character</span><span class="p">):</span>
|
||
<span class="s2">"Remove combatant from handler"</span>
|
||
<span class="k">if</span> <span class="n">character</span><span class="o">.</span><span class="n">id</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">characters</span><span class="p">:</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">_cleanup_character</span><span class="p">(</span><span class="n">character</span><span class="p">)</span>
|
||
<span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">characters</span><span class="p">:</span>
|
||
<span class="c1"># if no more characters in battle, kill this handler</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">stop</span><span class="p">()</span>
|
||
|
||
<span class="k">def</span> <span class="nf">msg_all</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="s2">"Send message to all combatants"</span>
|
||
<span class="k">for</span> <span class="n">character</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">characters</span><span class="o">.</span><span class="n">values</span><span class="p">():</span>
|
||
<span class="n">character</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="k">def</span> <span class="nf">add_action</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">action</span><span class="p">,</span> <span class="n">character</span><span class="p">,</span> <span class="n">target</span><span class="p">):</span>
|
||
<span class="sd">"""</span>
|
||
<span class="sd"> Called by combat commands to register an action with the handler.</span>
|
||
|
||
<span class="sd"> action - string identifying the action, like "hit" or "parry"</span>
|
||
<span class="sd"> character - the character performing the action</span>
|
||
<span class="sd"> target - the target character or None</span>
|
||
|
||
<span class="sd"> actions are stored in a dictionary keyed to each character, each</span>
|
||
<span class="sd"> of which holds a list of max 2 actions. An action is stored as</span>
|
||
<span class="sd"> a tuple (character, action, target). </span>
|
||
<span class="sd"> """</span>
|
||
<span class="n">dbref</span> <span class="o">=</span> <span class="n">character</span><span class="o">.</span><span class="n">id</span>
|
||
<span class="n">count</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">action_count</span><span class="p">[</span><span class="n">dbref</span><span class="p">]</span>
|
||
<span class="k">if</span> <span class="mi">0</span> <span class="o"><=</span> <span class="n">count</span> <span class="o"><=</span> <span class="mi">1</span><span class="p">:</span> <span class="c1"># only allow 2 actions </span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">turn_actions</span><span class="p">[</span><span class="n">dbref</span><span class="p">][</span><span class="n">count</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="n">action</span><span class="p">,</span> <span class="n">character</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="c1"># report if we already used too many actions</span>
|
||
<span class="k">return</span> <span class="kc">False</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">action_count</span><span class="p">[</span><span class="n">dbref</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
|
||
<span class="k">return</span> <span class="kc">True</span>
|
||
|
||
<span class="k">def</span> <span class="nf">check_end_turn</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="sd">"""</span>
|
||
<span class="sd"> Called by the command to eventually trigger </span>
|
||
<span class="sd"> the resolution of the turn. We check if everyone</span>
|
||
<span class="sd"> has added all their actions; if so we call force the</span>
|
||
<span class="sd"> script to repeat immediately (which will call</span>
|
||
<span class="sd"> `self.at_repeat()` while resetting all timers). </span>
|
||
<span class="sd"> """</span>
|
||
<span class="k">if</span> <span class="nb">all</span><span class="p">(</span><span class="n">count</span> <span class="o">></span> <span class="mi">1</span> <span class="k">for</span> <span class="n">count</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">action_count</span><span class="o">.</span><span class="n">values</span><span class="p">()):</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">ndb</span><span class="o">.</span><span class="n">normal_turn_end</span> <span class="o">=</span> <span class="kc">True</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">force_repeat</span><span class="p">()</span>
|
||
|
||
<span class="k">def</span> <span class="nf">end_turn</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="sd">"""</span>
|
||
<span class="sd"> This resolves all actions by calling the rules module. </span>
|
||
<span class="sd"> It then resets everything and starts the next turn. It</span>
|
||
<span class="sd"> is called by at_repeat().</span>
|
||
<span class="sd"> """</span>
|
||
<span class="n">resolve_combat</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">db</span><span class="o">.</span><span class="n">turn_actions</span><span class="p">)</span>
|
||
|
||
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">characters</span><span class="p">)</span> <span class="o"><</span> <span class="mi">2</span><span class="p">:</span>
|
||
<span class="c1"># less than 2 characters in battle, kill this handler</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">msg_all</span><span class="p">(</span><span class="s2">"Combat has ended"</span><span class="p">)</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">stop</span><span class="p">()</span>
|
||
<span class="k">else</span><span class="p">:</span>
|
||
<span class="c1"># reset counters before next turn</span>
|
||
<span class="k">for</span> <span class="n">character</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">characters</span><span class="o">.</span><span class="n">values</span><span class="p">():</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">characters</span><span class="p">[</span><span class="n">character</span><span class="o">.</span><span class="n">id</span><span class="p">]</span> <span class="o">=</span> <span class="n">character</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">action_count</span><span class="p">[</span><span class="n">character</span><span class="o">.</span><span class="n">id</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">turn_actions</span><span class="p">[</span><span class="n">character</span><span class="o">.</span><span class="n">id</span><span class="p">]</span> <span class="o">=</span> <span class="p">[(</span><span class="s2">"defend"</span><span class="p">,</span> <span class="n">character</span><span class="p">,</span> <span class="kc">None</span><span class="p">),</span>
|
||
<span class="p">(</span><span class="s2">"defend"</span><span class="p">,</span> <span class="n">character</span><span class="p">,</span> <span class="kc">None</span><span class="p">)]</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">msg_all</span><span class="p">(</span><span class="s2">"Next turn begins ..."</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>This implements all the useful properties of our combat handler. This Script will survive a reboot
|
||
and will automatically re-assert itself when it comes back online. Even the current state of the
|
||
combat should be unaffected since it is saved in Attributes at every turn. An important part to note
|
||
is the use of the Script’s standard <code class="docutils literal notranslate"><span class="pre">at_repeat</span></code> hook and the <code class="docutils literal notranslate"><span class="pre">force_repeat</span></code> method to end each turn.
|
||
This allows for everything to go through the same mechanisms with minimal repetition of code.</p>
|
||
<p>What is not present in this handler is a way for players to view the actions they set or to change
|
||
their actions once they have been added (but before the last one has added theirs). We leave this as
|
||
an exercise.</p>
|
||
</section>
|
||
<section id="combat-commands">
|
||
<h2>Combat commands<a class="headerlink" href="#combat-commands" title="Permalink to this headline">¶</a></h2>
|
||
<p>Our combat commands - the commands that are to be available to us during the combat - are (in our
|
||
example) very simple. In a full implementation the commands available might be determined by the
|
||
weapon(s) held by the player or by which skills they know.</p>
|
||
<p>We create them in <code class="docutils literal notranslate"><span class="pre">mygame/commands/combat.py</span></code>.</p>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="c1"># mygame/commands/combat.py</span>
|
||
|
||
<span class="kn">from</span> <span class="nn">evennia</span> <span class="kn">import</span> <span class="n">Command</span>
|
||
|
||
<span class="k">class</span> <span class="nc">CmdHit</span><span class="p">(</span><span class="n">Command</span><span class="p">):</span>
|
||
<span class="sd">"""</span>
|
||
<span class="sd"> hit an enemy</span>
|
||
|
||
<span class="sd"> Usage:</span>
|
||
<span class="sd"> hit <target></span>
|
||
|
||
<span class="sd"> Strikes the given enemy with your current weapon.</span>
|
||
<span class="sd"> """</span>
|
||
<span class="n">key</span> <span class="o">=</span> <span class="s2">"hit"</span>
|
||
<span class="n">aliases</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"strike"</span><span class="p">,</span> <span class="s2">"slash"</span><span class="p">]</span>
|
||
<span class="n">help_category</span> <span class="o">=</span> <span class="s2">"combat"</span>
|
||
|
||
<span class="k">def</span> <span class="nf">func</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="s2">"Implements the command"</span>
|
||
<span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">:</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">caller</span><span class="o">.</span><span class="n">msg</span><span class="p">(</span><span class="s2">"Usage: hit <target>"</span><span class="p">)</span>
|
||
<span class="k">return</span>
|
||
<span class="n">target</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">caller</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">)</span>
|
||
<span class="k">if</span> <span class="ow">not</span> <span class="n">target</span><span class="p">:</span>
|
||
<span class="k">return</span>
|
||
<span class="n">ok</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">caller</span><span class="o">.</span><span class="n">ndb</span><span class="o">.</span><span class="n">combat_handler</span><span class="o">.</span><span class="n">add_action</span><span class="p">(</span><span class="s2">"hit"</span><span class="p">,</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">caller</span><span class="p">,</span>
|
||
<span class="n">target</span><span class="p">)</span>
|
||
<span class="k">if</span> <span class="n">ok</span><span class="p">:</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">caller</span><span class="o">.</span><span class="n">msg</span><span class="p">(</span><span class="s2">"You add 'hit' to the combat queue"</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">caller</span><span class="o">.</span><span class="n">msg</span><span class="p">(</span><span class="s2">"You can only queue two actions per turn!"</span><span class="p">)</span>
|
||
|
||
<span class="c1"># tell the handler to check if turn is over</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">caller</span><span class="o">.</span><span class="n">ndb</span><span class="o">.</span><span class="n">combat_handler</span><span class="o">.</span><span class="n">check_end_turn</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The other commands <code class="docutils literal notranslate"><span class="pre">CmdParry</span></code>, <code class="docutils literal notranslate"><span class="pre">CmdFeint</span></code>, <code class="docutils literal notranslate"><span class="pre">CmdDefend</span></code> and <code class="docutils literal notranslate"><span class="pre">CmdDisengage</span></code> look basically the same.
|
||
We should also add a custom <code class="docutils literal notranslate"><span class="pre">help</span></code> command to list all the available combat commands and what they
|
||
do.</p>
|
||
<p>We just need to put them all in a cmdset. We do this at the end of the same module:</p>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="c1"># mygame/commands/combat.py</span>
|
||
|
||
<span class="kn">from</span> <span class="nn">evennia</span> <span class="kn">import</span> <span class="n">CmdSet</span>
|
||
<span class="kn">from</span> <span class="nn">evennia</span> <span class="kn">import</span> <span class="n">default_cmds</span>
|
||
|
||
<span class="k">class</span> <span class="nc">CombatCmdSet</span><span class="p">(</span><span class="n">CmdSet</span><span class="p">):</span>
|
||
<span class="n">key</span> <span class="o">=</span> <span class="s2">"combat_cmdset"</span>
|
||
<span class="n">mergetype</span> <span class="o">=</span> <span class="s2">"Replace"</span>
|
||
<span class="n">priority</span> <span class="o">=</span> <span class="mi">10</span>
|
||
<span class="n">no_exits</span> <span class="o">=</span> <span class="kc">True</span>
|
||
|
||
<span class="k">def</span> <span class="nf">at_cmdset_creation</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">CmdHit</span><span class="p">())</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">CmdParry</span><span class="p">())</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">CmdFeint</span><span class="p">())</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">CmdDefend</span><span class="p">())</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">CmdDisengage</span><span class="p">())</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">CmdHelp</span><span class="p">())</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">default_cmds</span><span class="o">.</span><span class="n">CmdPose</span><span class="p">())</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">default_cmds</span><span class="o">.</span><span class="n">CmdSay</span><span class="p">())</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="rules-module">
|
||
<h2>Rules module<a class="headerlink" href="#rules-module" title="Permalink to this headline">¶</a></h2>
|
||
<p>A general way to implement a rule module is found in the [rule system tutorial](Implementing-a-game-
|
||
rule-system). Proper resolution would likely require us to change our Characters to store things
|
||
like strength, weapon skills and so on. So for this example we will settle for a very simplistic
|
||
rock-paper-scissors kind of setup with some randomness thrown in. We will not deal with damage here
|
||
but just announce the results of each turn. In a real system the Character objects would hold stats
|
||
to affect their skills, their chosen weapon affect the choices, they would be able to lose health
|
||
etc.</p>
|
||
<p>Within each turn, there are “sub-turns”, each consisting of one action per character. The actions
|
||
within each sub-turn happens simultaneously and only once they have all been resolved we move on to
|
||
the next sub-turn (or end the full turn).</p>
|
||
<p><em>Note: In our simple example the sub-turns don’t affect each other (except for <code class="docutils literal notranslate"><span class="pre">disengage/flee</span></code>),
|
||
nor do any effects carry over between turns. The real power of a turn-based system would be to add
|
||
real tactical possibilities here though; For example if your hit got parried you could be out of
|
||
balance and your next action would be at a disadvantage. A successful feint would open up for a
|
||
subsequent attack and so on …</em></p>
|
||
<p>Our rock-paper-scissor setup works like this:</p>
|
||
<ul class="simple">
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">hit</span></code> beats <code class="docutils literal notranslate"><span class="pre">feint</span></code> and <code class="docutils literal notranslate"><span class="pre">flee/disengage</span></code>. It has a random chance to fail against <code class="docutils literal notranslate"><span class="pre">defend</span></code>.</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">parry</span></code> beats <code class="docutils literal notranslate"><span class="pre">hit</span></code>.</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">feint</span></code> beats <code class="docutils literal notranslate"><span class="pre">parry</span></code> and is then counted as a <code class="docutils literal notranslate"><span class="pre">hit</span></code>.</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">defend</span></code> does nothing but has a chance to beat <code class="docutils literal notranslate"><span class="pre">hit</span></code>.</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">flee/disengage</span></code> must succeed two times in a row (i.e. not beaten by a <code class="docutils literal notranslate"><span class="pre">hit</span></code> once during the
|
||
turn). If so the character leaves combat.</p></li>
|
||
</ul>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="c1"># mygame/world/rules.py</span>
|
||
|
||
<span class="kn">import</span> <span class="nn">random</span>
|
||
|
||
<span class="c1"># messages </span>
|
||
|
||
<span class="k">def</span> <span class="nf">resolve_combat</span><span class="p">(</span><span class="n">combat_handler</span><span class="p">,</span> <span class="n">actiondict</span><span class="p">):</span>
|
||
<span class="sd">"""</span>
|
||
<span class="sd"> This is called by the combat handler</span>
|
||
<span class="sd"> actiondict is a dictionary with a list of two actions</span>
|
||
<span class="sd"> for each character:</span>
|
||
<span class="sd"> {char.id:[(action1, char, target), (action2, char, target)], ...}</span>
|
||
<span class="sd"> """</span>
|
||
<span class="n">flee</span> <span class="o">=</span> <span class="p">{}</span> <span class="c1"># track number of flee commands per character</span>
|
||
<span class="k">for</span> <span class="n">isub</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">2</span><span class="p">):</span>
|
||
<span class="c1"># loop over sub-turns</span>
|
||
<span class="n">messages</span> <span class="o">=</span> <span class="p">[]</span>
|
||
<span class="k">for</span> <span class="n">subturn</span> <span class="ow">in</span> <span class="p">(</span><span class="n">sub</span><span class="p">[</span><span class="n">isub</span><span class="p">]</span> <span class="k">for</span> <span class="n">sub</span> <span class="ow">in</span> <span class="n">actiondict</span><span class="o">.</span><span class="n">values</span><span class="p">()):</span>
|
||
<span class="c1"># for each character, resolve the sub-turn</span>
|
||
<span class="n">action</span><span class="p">,</span> <span class="n">char</span><span class="p">,</span> <span class="n">target</span> <span class="o">=</span> <span class="n">subturn</span>
|
||
<span class="k">if</span> <span class="n">target</span><span class="p">:</span>
|
||
<span class="n">taction</span><span class="p">,</span> <span class="n">tchar</span><span class="p">,</span> <span class="n">ttarget</span> <span class="o">=</span> <span class="n">actiondict</span><span class="p">[</span><span class="n">target</span><span class="o">.</span><span class="n">id</span><span class="p">][</span><span class="n">isub</span><span class="p">]</span>
|
||
<span class="k">if</span> <span class="n">action</span> <span class="o">==</span> <span class="s2">"hit"</span><span class="p">:</span>
|
||
<span class="k">if</span> <span class="n">taction</span> <span class="o">==</span> <span class="s2">"parry"</span> <span class="ow">and</span> <span class="n">ttarget</span> <span class="o">==</span> <span class="n">char</span><span class="p">:</span>
|
||
<span class="n">messages</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
|
||
<span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">char</span><span class="si">}</span><span class="s2"> tries to hit </span><span class="si">{</span><span class="n">tchar</span><span class="si">}</span><span class="s2">, but </span><span class="si">{</span><span class="n">tchar</span><span class="si">}</span><span class="s2"> parries the attack!"</span>
|
||
<span class="p">)</span>
|
||
<span class="k">elif</span> <span class="n">taction</span> <span class="o">==</span> <span class="s2">"defend"</span> <span class="ow">and</span> <span class="n">random</span><span class="o">.</span><span class="n">random</span><span class="p">()</span> <span class="o"><</span> <span class="mf">0.5</span><span class="p">:</span>
|
||
<span class="n">messages</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
|
||
<span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">tchar</span><span class="si">}</span><span class="s2"> defends against the attack by </span><span class="si">{</span><span class="n">char</span><span class="si">}</span><span class="s2">."</span>
|
||
<span class="p">)</span>
|
||
<span class="k">elif</span> <span class="n">taction</span> <span class="o">==</span> <span class="s2">"flee"</span><span class="p">:</span>
|
||
<span class="n">flee</span><span class="p">[</span><span class="n">tchar</span><span class="p">]</span> <span class="o">=</span> <span class="o">-</span><span class="mi">2</span>
|
||
<span class="n">messages</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
|
||
<span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">char</span><span class="si">}</span><span class="s2"> stops </span><span class="si">{</span><span class="n">tchar</span><span class="si">}</span><span class="s2"> from disengaging, with a hit!"</span>
|
||
<span class="p">)</span>
|
||
<span class="k">else</span><span class="p">:</span>
|
||
<span class="n">messages</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
|
||
<span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">char</span><span class="si">}</span><span class="s2"> hits </span><span class="si">{</span><span class="n">tchar</span><span class="si">}</span><span class="s2">, bypassing their </span><span class="si">{</span><span class="n">taction</span><span class="si">}</span><span class="s2">!"</span>
|
||
<span class="p">)</span>
|
||
<span class="k">elif</span> <span class="n">action</span> <span class="o">==</span> <span class="s2">"parry"</span><span class="p">:</span>
|
||
<span class="k">if</span> <span class="n">taction</span> <span class="o">==</span> <span class="s2">"hit"</span><span class="p">:</span>
|
||
<span class="n">messages</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">char</span><span class="si">}</span><span class="s2"> parries the attack by </span><span class="si">{</span><span class="n">tchar</span><span class="si">}</span><span class="s2">."</span><span class="p">)</span>
|
||
<span class="k">elif</span> <span class="n">taction</span> <span class="o">==</span> <span class="s2">"feint"</span><span class="p">:</span>
|
||
<span class="n">messages</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
|
||
<span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">char</span><span class="si">}</span><span class="s2"> tries to parry, but </span><span class="si">{</span><span class="n">tchar</span><span class="si">}</span><span class="s2"> feints and hits!"</span>
|
||
<span class="p">)</span>
|
||
<span class="k">else</span><span class="p">:</span>
|
||
<span class="n">messages</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">char</span><span class="si">}</span><span class="s2"> parries to no avail."</span><span class="p">)</span>
|
||
<span class="k">elif</span> <span class="n">action</span> <span class="o">==</span> <span class="s2">"feint"</span><span class="p">:</span>
|
||
<span class="k">if</span> <span class="n">taction</span> <span class="o">==</span> <span class="s2">"parry"</span><span class="p">:</span>
|
||
<span class="n">messages</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
|
||
<span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">char</span><span class="si">}</span><span class="s2"> feints past </span><span class="si">{</span><span class="n">tchar</span><span class="si">}</span><span class="s2">'s parry, landing a hit!"</span>
|
||
<span class="p">)</span>
|
||
<span class="k">elif</span> <span class="n">taction</span> <span class="o">==</span> <span class="s2">"hit"</span><span class="p">:</span>
|
||
<span class="n">messages</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">char</span><span class="si">}</span><span class="s2"> feints but is defeated by </span><span class="si">{</span><span class="n">tchar</span><span class="si">}</span><span class="s2">'s hit!"</span><span class="p">)</span>
|
||
<span class="k">else</span><span class="p">:</span>
|
||
<span class="n">messages</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">char</span><span class="si">}</span><span class="s2"> feints to no avail."</span><span class="p">)</span>
|
||
<span class="k">elif</span> <span class="n">action</span> <span class="o">==</span> <span class="s2">"defend"</span><span class="p">:</span>
|
||
<span class="n">messages</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">char</span><span class="si">}</span><span class="s2"> defends."</span><span class="p">)</span>
|
||
<span class="k">elif</span> <span class="n">action</span> <span class="o">==</span> <span class="s2">"flee"</span><span class="p">:</span>
|
||
<span class="k">if</span> <span class="n">char</span> <span class="ow">in</span> <span class="n">flee</span><span class="p">:</span>
|
||
<span class="n">flee</span><span class="p">[</span><span class="n">char</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
|
||
<span class="k">else</span><span class="p">:</span>
|
||
<span class="n">flee</span><span class="p">[</span><span class="n">char</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span>
|
||
<span class="n">messages</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
|
||
<span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">char</span><span class="si">}</span><span class="s2"> tries to disengage (two subsequent turns needed)"</span>
|
||
<span class="p">)</span>
|
||
|
||
<span class="c1"># echo results of each subturn</span>
|
||
<span class="n">combat_handler</span><span class="o">.</span><span class="n">msg_all</span><span class="p">(</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">messages</span><span class="p">))</span>
|
||
|
||
<span class="c1"># at the end of both sub-turns, test if anyone fled</span>
|
||
<span class="k">for</span> <span class="p">(</span><span class="n">char</span><span class="p">,</span> <span class="n">fleevalue</span><span class="p">)</span> <span class="ow">in</span> <span class="n">flee</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
|
||
<span class="k">if</span> <span class="n">fleevalue</span> <span class="o">==</span> <span class="mi">2</span><span class="p">:</span>
|
||
<span class="n">combat_handler</span><span class="o">.</span><span class="n">msg_all</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">char</span><span class="si">}</span><span class="s2"> withdraws from combat."</span><span class="p">)</span>
|
||
<span class="n">combat_handler</span><span class="o">.</span><span class="n">remove_character</span><span class="p">(</span><span class="n">char</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>To make it simple (and to save space), this example rule module actually resolves each interchange
|
||
twice - first when it gets to each character and then again when handling the target. Also, since we
|
||
use the combat handler’s <code class="docutils literal notranslate"><span class="pre">msg_all</span></code> method here, the system will get pretty spammy. To clean it up,
|
||
one could imagine tracking all the possible interactions to make sure each pair is only handled and
|
||
reported once.</p>
|
||
</section>
|
||
<section id="combat-initiator-command">
|
||
<h2>Combat initiator command<a class="headerlink" href="#combat-initiator-command" title="Permalink to this headline">¶</a></h2>
|
||
<p>This is the last component we need, a command to initiate combat. This will tie everything together.
|
||
We store this with the other combat commands.</p>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="c1"># mygame/commands/combat.py</span>
|
||
|
||
<span class="kn">from</span> <span class="nn">evennia</span> <span class="kn">import</span> <span class="n">create_script</span>
|
||
|
||
<span class="k">class</span> <span class="nc">CmdAttack</span><span class="p">(</span><span class="n">Command</span><span class="p">):</span>
|
||
<span class="sd">"""</span>
|
||
<span class="sd"> initiates combat</span>
|
||
|
||
<span class="sd"> Usage:</span>
|
||
<span class="sd"> attack <target></span>
|
||
|
||
<span class="sd"> This will initiate combat with <target>. If <target is</span>
|
||
<span class="sd"> already in combat, you will join the combat. </span>
|
||
<span class="sd"> """</span>
|
||
<span class="n">key</span> <span class="o">=</span> <span class="s2">"attack"</span>
|
||
<span class="n">help_category</span> <span class="o">=</span> <span class="s2">"General"</span>
|
||
|
||
<span class="k">def</span> <span class="nf">func</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="s2">"Handle command"</span>
|
||
<span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">:</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">caller</span><span class="o">.</span><span class="n">msg</span><span class="p">(</span><span class="s2">"Usage: attack <target>"</span><span class="p">)</span>
|
||
<span class="k">return</span>
|
||
<span class="n">target</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">caller</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">)</span>
|
||
<span class="k">if</span> <span class="ow">not</span> <span class="n">target</span><span class="p">:</span>
|
||
<span class="k">return</span>
|
||
<span class="c1"># set up combat</span>
|
||
<span class="k">if</span> <span class="n">target</span><span class="o">.</span><span class="n">ndb</span><span class="o">.</span><span class="n">combat_handler</span><span class="p">:</span>
|
||
<span class="c1"># target is already in combat - join it </span>
|
||
<span class="n">target</span><span class="o">.</span><span class="n">ndb</span><span class="o">.</span><span class="n">combat_handler</span><span class="o">.</span><span class="n">add_character</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">caller</span><span class="p">)</span>
|
||
<span class="n">target</span><span class="o">.</span><span class="n">ndb</span><span class="o">.</span><span class="n">combat_handler</span><span class="o">.</span><span class="n">msg_all</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">caller</span><span class="si">}</span><span class="s2"> joins combat!"</span><span class="p">)</span>
|
||
<span class="k">else</span><span class="p">:</span>
|
||
<span class="c1"># create a new combat handler</span>
|
||
<span class="n">chandler</span> <span class="o">=</span> <span class="n">create_script</span><span class="p">(</span><span class="s2">"combat_handler.CombatHandler"</span><span class="p">)</span>
|
||
<span class="n">chandler</span><span class="o">.</span><span class="n">add_character</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">caller</span><span class="p">)</span>
|
||
<span class="n">chandler</span><span class="o">.</span><span class="n">add_character</span><span class="p">(</span><span class="n">target</span><span class="p">)</span>
|
||
<span class="bp">self</span><span class="o">.</span><span class="n">caller</span><span class="o">.</span><span class="n">msg</span><span class="p">(</span><span class="sa">f</span><span class="s2">"You attack </span><span class="si">{</span><span class="n">target</span><span class="si">}</span><span class="s2">! You are in combat."</span><span class="p">)</span>
|
||
<span class="n">target</span><span class="o">.</span><span class="n">msg</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">caller</span><span class="si">}</span><span class="s2"> attacks you! You are in combat."</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The <code class="docutils literal notranslate"><span class="pre">attack</span></code> command will not go into the combat cmdset but rather into the default cmdset. See e.g.
|
||
the <a class="reference internal" href="Beginner-Tutorial/Part1/Beginner-Tutorial-Adding-Commands.html"><span class="doc std std-doc">Adding Command Tutorial</span></a> if you are unsure about how to do this.</p>
|
||
</section>
|
||
<section id="expanding-the-example">
|
||
<h2>Expanding the example<a class="headerlink" href="#expanding-the-example" title="Permalink to this headline">¶</a></h2>
|
||
<p>At this point you should have a simple but flexible turn-based combat system. We have taken several
|
||
shortcuts and simplifications in this example. The output to the players is likely too verbose
|
||
during combat and too limited when it comes to informing about things surrounding it. Methods for
|
||
changing your commands or list them, view who is in combat etc is likely needed - this will require
|
||
play testing for each game and style. There is also currently no information displayed for other
|
||
people happening to be in the same room as the combat - some less detailed information should
|
||
probably be echoed to the room to
|
||
show others what’s going on.</p>
|
||
</section>
|
||
</section>
|
||
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
<div class="related" role="navigation" aria-label="related navigation">
|
||
<h3>Navigation</h3>
|
||
<ul>
|
||
<li class="right" style="margin-right: 10px">
|
||
<a href="../genindex.html" title="General Index"
|
||
>index</a></li>
|
||
<li class="right" >
|
||
<a href="../py-modindex.html" title="Python Module Index"
|
||
>modules</a> |</li>
|
||
<li class="right" >
|
||
<a href="A-Sittable-Object.html" title="Making a sittable object"
|
||
>next</a> |</li>
|
||
<li class="right" >
|
||
<a href="Implementing-a-game-rule-system.html" title="Implementing a game rule system"
|
||
>previous</a> |</li>
|
||
<li class="nav-item nav-item-0"><a href="../index.html">Evennia 1.0-dev</a> »</li>
|
||
<li class="nav-item nav-item-1"><a href="Howtos-Overview.html" >Tutorials and Howto’s</a> »</li>
|
||
<li class="nav-item nav-item-this"><a href="">Turn based Combat System</a></li>
|
||
</ul>
|
||
<div class="develop">develop branch</div>
|
||
</div>
|
||
<div class="footer" role="contentinfo">
|
||
© Copyright 2022, The Evennia developer community.
|
||
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 3.2.1.
|
||
</div>
|
||
</body>
|
||
</html> |