McRogueFace/roguelike_tutorial/rogueliketutorials.com/Part 13 - Gearing up · Rogu...

1524 lines
101 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

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

<!DOCTYPE html>
<html lang="en" style="color-scheme: dark;"><head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>
Part 13 - Gearing up · Roguelike Tutorials
</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="light dark">
<meta name="description" content="For the final part of this tutorial, well implement something that most roguelikes have: equipment. Our implementation will be extremely simple: equipping a weapon increases attack power, and equipping armor increases defense. Many roguelikes have more equipment types than just these two, and the effects of equipment can go much further than this, but this should be enough to get you started.
First, well want to define the types of equipment that can be found in the dungeon.">
<meta name="keywords" content="">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Part 13 - Gearing up">
<meta name="twitter:description" content="For the final part of this tutorial, well implement something that most roguelikes have: equipment. Our implementation will be extremely simple: equipping a weapon increases attack power, and equipping armor increases defense. Many roguelikes have more equipment types than just these two, and the effects of equipment can go much further than this, but this should be enough to get you started.
First, well want to define the types of equipment that can be found in the dungeon.">
<meta property="og:title" content="Part 13 - Gearing up">
<meta property="og:description" content="For the final part of this tutorial, well implement something that most roguelikes have: equipment. Our implementation will be extremely simple: equipping a weapon increases attack power, and equipping armor increases defense. Many roguelikes have more equipment types than just these two, and the effects of equipment can go much further than this, but this should be enough to get you started.
First, well want to define the types of equipment that can be found in the dungeon.">
<meta property="og:type" content="article">
<meta property="og:url" content="https://rogueliketutorials.com/tutorials/tcod/v2/part-13/"><meta property="article:section" content="tutorials">
<meta property="article:published_time" content="2020-07-28T00:00:00+00:00">
<meta property="article:modified_time" content="2020-07-28T00:00:00+00:00">
<link rel="canonical" href="https://rogueliketutorials.com/tutorials/tcod/v2/part-13/">
<link rel="preload" href="https://rogueliketutorials.com/fonts/forkawesome-webfont.woff2?v=1.2.0" as="font" type="font/woff2" crossorigin="">
<link rel="stylesheet" href="Part%2013%20-%20Gearing%20up%20%C2%B7%20Roguelike%20Tutorials_files/coder.min.c4d7e93a158eda5a65b3df343745d2092a0a1e2170feeec909.css" integrity="sha256-xNfpOhWO2lpls980N0XSCSoKHiFw/u7JCbiolEOQPGo=" crossorigin="anonymous" media="screen">
<link rel="stylesheet" href="Part%2013%20-%20Gearing%20up%20%C2%B7%20Roguelike%20Tutorials_files/coder-dark.min.78b5fe3864945faf5207fb8fe3ab2320d49c3365def0e.css" integrity="sha256-eLX+OGSUX69SB/uP46sjINScM2Xe8OiKwd8N2txUoDw=" crossorigin="anonymous" media="screen">
<link rel="stylesheet" href="Part%2013%20-%20Gearing%20up%20%C2%B7%20Roguelike%20Tutorials_files/style.min.9d3eb202952dddb888856ff12c83bc88de866c596286bfb4c1.css" integrity="sha256-nT6yApUt3biIhW/xLIO8iN6GbFlihr+0wfjmvq2a42Y=" crossorigin="anonymous" media="screen">
<link rel="icon" type="image/png" href="https://rogueliketutorials.com/images/favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="https://rogueliketutorials.com/images/favicon-16x16.png" sizes="16x16">
<link rel="apple-touch-icon" href="https://rogueliketutorials.com/images/apple-touch-icon.png">
<link rel="apple-touch-icon" sizes="180x180" href="https://rogueliketutorials.com/images/apple-touch-icon.png">
<link rel="manifest" href="https://rogueliketutorials.com/site.webmanifest">
<link rel="mask-icon" href="https://rogueliketutorials.com/images/safari-pinned-tab.svg" color="#5bbad5">
<meta name="generator" content="Hugo 0.110.0">
<style>:is([id*='google_ads_iframe'],[id*='taboola-'],.taboolaHeight,.taboola-placeholder,#top-ad,#credential_picker_container,#credentials-picker-container,#credential_picker_iframe,[id*='google-one-tap-iframe'],#google-one-tap-popup-container,.google-one-tap__module,.google-one-tap-modal-div,#amp_floatingAdDiv,#ez-content-blocker-container) {display:none!important;min-height:0!important;height:0!important;}</style></head>
<body class="colorscheme-dark vsc-initialized">
<div class="float-container">
<a id="dark-mode-toggle" class="colorscheme-toggle">
<i class="fa fa-adjust fa-fw" aria-hidden="true"></i>
</a>
</div>
<main class="wrapper">
<nav class="navigation">
<section class="container">
<a class="navigation-title" href="https://rogueliketutorials.com/">
Roguelike Tutorials
</a>
<input type="checkbox" id="menu-toggle">
<label class="menu-button float-right" for="menu-toggle">
<i class="fa fa-bars fa-fw" aria-hidden="true"></i>
</label>
<ul class="navigation-list">
<li class="navigation-item">
<a class="navigation-link" href="https://rogueliketutorials.com/">Home</a>
</li>
<li class="navigation-item">
<a class="navigation-link" href="https://rogueliketutorials.com/tutorials/tcod/v2/">TCOD Tutorial (2020)</a>
</li>
<li class="navigation-item">
<a class="navigation-link" href="https://rogueliketutorials.com/tutorials/tcod/2019/">TCOD Tutorial (2019)</a>
</li>
<li class="navigation-item">
<a class="navigation-link" href="https://rogueliketutorials.com/about/">About</a>
</li>
</ul>
</section>
</nav>
<div class="content">
<section class="container page">
<article>
<header>
<h1 class="title">
<a class="title-link" href="https://rogueliketutorials.com/tutorials/tcod/v2/part-13/">
Part 13 - Gearing up
</a>
</h1>
</header>
<p>For the final part of this tutorial, well implement something that <em>most</em>
roguelikes have: equipment. Our implementation will be extremely
simple: equipping a weapon increases attack power, and equipping armor
increases defense. Many roguelikes have more equipment types than just
these two, and the effects of equipment can go much further than this,
but this should be enough to get you started.</p>
<p>First, well want to define the types of equipment that can be found in the dungeon. As with the <code>RenderOrder</code> class, we can use <code>Enum</code> to define the types. For now, well leave it at weapons and armor, but feel free to add more types as you see fit.</p>
<p>Create a new file, <code>equipment_types.py</code>, and put the following contents in it:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span><span style="color:#f92672">from</span> enum <span style="color:#f92672">import</span> auto, Enum
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">EquipmentType</span>(Enum):
</span></span><span style="display:flex;"><span> WEAPON <span style="color:#f92672">=</span> auto()
</span></span><span style="display:flex;"><span> ARMOR <span style="color:#f92672">=</span> auto()
</span></span></code></pre></div><p>Now its time to create the component that well attach to the equipment. Well call the component <code>Equippable</code>, which will have a few different attributes:</p>
<ul>
<li><code>equipment_type</code>: The type of equipment, using the <code>EquipmentType</code> enum.</li>
<li><code>power_bonus</code>: How much the wielders attack power will be increased. Currently used for just weapons.</li>
<li><code>defense_bonus</code>: How much the wearers defense will be increased. Currently just for armor.</li>
</ul>
<p>Create the file <code>equippable.py</code> in the <code>components</code> directory, and fill it with the following:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span><span style="color:#f92672">from</span> __future__ <span style="color:#f92672">import</span> annotations
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> typing <span style="color:#f92672">import</span> TYPE_CHECKING
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> components.base_component <span style="color:#f92672">import</span> BaseComponent
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> equipment_types <span style="color:#f92672">import</span> EquipmentType
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> TYPE_CHECKING:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">from</span> entity <span style="color:#f92672">import</span> Item
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Equippable</span>(BaseComponent):
</span></span><span style="display:flex;"><span> parent: Item
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __init__(
</span></span><span style="display:flex;"><span> self,
</span></span><span style="display:flex;"><span> equipment_type: EquipmentType,
</span></span><span style="display:flex;"><span> power_bonus: int <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>,
</span></span><span style="display:flex;"><span> defense_bonus: int <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>,
</span></span><span style="display:flex;"><span> ):
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>equipment_type <span style="color:#f92672">=</span> equipment_type
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>power_bonus <span style="color:#f92672">=</span> power_bonus
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>defense_bonus <span style="color:#f92672">=</span> defense_bonus
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Dagger</span>(Equippable):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __init__(self) <span style="color:#f92672">-&gt;</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span> super()<span style="color:#f92672">.</span>__init__(equipment_type<span style="color:#f92672">=</span>EquipmentType<span style="color:#f92672">.</span>WEAPON, power_bonus<span style="color:#f92672">=</span><span style="color:#ae81ff">2</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Sword</span>(Equippable):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __init__(self) <span style="color:#f92672">-&gt;</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span> super()<span style="color:#f92672">.</span>__init__(equipment_type<span style="color:#f92672">=</span>EquipmentType<span style="color:#f92672">.</span>WEAPON, power_bonus<span style="color:#f92672">=</span><span style="color:#ae81ff">4</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">LeatherArmor</span>(Equippable):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __init__(self) <span style="color:#f92672">-&gt;</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span> super()<span style="color:#f92672">.</span>__init__(equipment_type<span style="color:#f92672">=</span>EquipmentType<span style="color:#f92672">.</span>ARMOR, defense_bonus<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">ChainMail</span>(Equippable):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __init__(self) <span style="color:#f92672">-&gt;</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span> super()<span style="color:#f92672">.</span>__init__(equipment_type<span style="color:#f92672">=</span>EquipmentType<span style="color:#f92672">.</span>ARMOR, defense_bonus<span style="color:#f92672">=</span><span style="color:#ae81ff">3</span>)
</span></span></code></pre></div><p>Aside from creating the <code>Equippable</code>
class, as described earlier, weve also created a few types of
equippable components, for each equippable entity that well end up
creating, similar to what we did with the <code>Consumable</code>
classes. You dont have to do it this way, you could just define these
when creating the entities, but you might want to add additional
functionality to weapons and armor at some point, and defining the <code>Equippable</code>
classes this way might make that easier. You might also want to move
these classes to their own file, but thats outside the scope of this
tutorial.</p>
<p>To create the actual equippable entities, well want to adjust our <code>Item</code>
class. We can use the same class that we used for our consumables, and
just handle them slightly differently. Another approach would be to
create another subclass of <code>Entity</code>, but for the sake of keeping the number of <code>Entity</code> subclasses in this tutorial short, well adjust <code>Item</code>. Make the following adjustments to <code>entity.py</code>:</p>
<div>
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
Diff
</button>
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
Original
</button>
<div class="data-pane active" data-pane="diff">
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>if TYPE_CHECKING:
</span></span><span style="display:flex;"><span> from components.ai import BaseAI
</span></span><span style="display:flex;"><span> from components.consumable import Consumable
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ from components.equippable import Equippable
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> from components.fighter import Fighter
</span></span><span style="display:flex;"><span> from components.inventory import Inventory
</span></span><span style="display:flex;"><span> from components.level import Level
</span></span><span style="display:flex;"><span> from game_map import GameMap
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>class Item(Entity):
</span></span><span style="display:flex;"><span> def __init__(
</span></span><span style="display:flex;"><span> self,
</span></span><span style="display:flex;"><span> *,
</span></span><span style="display:flex;"><span> x: int = 0,
</span></span><span style="display:flex;"><span> y: int = 0,
</span></span><span style="display:flex;"><span> char: str = "?",
</span></span><span style="display:flex;"><span> color: Tuple[int, int, int] = (255, 255, 255),
</span></span><span style="display:flex;"><span> name: str = "&lt;Unnamed&gt;",
</span></span><span style="display:flex;"><span><span style="color:#f92672">- consumable: Consumable,
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ consumable: Optional[Consumable] = None,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ equippable: Optional[Equippable] = None,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> ):
</span></span><span style="display:flex;"><span> super().__init__(
</span></span><span style="display:flex;"><span> x=x,
</span></span><span style="display:flex;"><span> y=y,
</span></span><span style="display:flex;"><span> char=char,
</span></span><span style="display:flex;"><span> color=color,
</span></span><span style="display:flex;"><span> name=name,
</span></span><span style="display:flex;"><span> blocks_movement=False,
</span></span><span style="display:flex;"><span> render_order=RenderOrder.ITEM,
</span></span><span style="display:flex;"><span> )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> self.consumable = consumable
</span></span><span style="display:flex;"><span><span style="color:#f92672">- self.consumable.parent = self
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ if self.consumable:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.consumable.parent = self
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.equippable = equippable
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ if self.equippable:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.equippable.parent = self
</span></span></span></code></pre></div>
</div>
<div class="data-pane" data-pane="original">
<pre>...
if TYPE_CHECKING:
from components.ai import BaseAI
from components.consumable import Consumable
<span class="new-text">from components.equippable import Equippable</span>
from components.fighter import Fighter
from components.inventory import Inventory
from components.level import Level
from game_map import GameMap
...
class Item(Entity):
def __init__(
self,
*,
x: int = 0,
y: int = 0,
char: str = "?",
color: Tuple[int, int, int] = (255, 255, 255),
name: str = "&lt;Unnamed&gt;",
<span class="crossed-out-text">consumable: Consumable,</span>
<span class="new-text">consumable: Optional[Consumable] = None,
equippable: Optional[Equippable] = None,</span>
):
super().__init__(
x=x,
y=y,
char=char,
color=color,
name=name,
blocks_movement=False,
render_order=RenderOrder.ITEM,
)
self.consumable = consumable
<span class="crossed-out-text">self.consumable.parent = self</span>
<span class="new-text">if self.consumable:
self.consumable.parent = self
self.equippable = equippable
if self.equippable:
self.equippable.parent = self</span></pre>
</div>
</div>
<p>Weve added <code>Equippable</code> as an optional component for the <code>Item</code> class, and also made <code>Consumable</code> optional, so that not all <code>Item</code> instances will be consumable.</p>
<p>Because <code>consumable</code> is now an optional attribute, we need to adjust <code>actions.py</code> to take this into account:</p>
<div>
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
Diff
</button>
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
Original
</button>
<div class="data-pane active" data-pane="diff">
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>class ItemAction(Action):
</span></span><span style="display:flex;"><span> ...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> def perform(self) -&gt; None:
</span></span><span style="display:flex;"><span> """Invoke the items ability, this action will be given to provide context."""
</span></span><span style="display:flex;"><span><span style="color:#f92672">- self.item.consumable.activate(self)
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ if self.item.consumable:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.item.consumable.activate(self)
</span></span></span></code></pre></div>
</div>
<div class="data-pane" data-pane="original">
<pre>class ItemAction(Action):
...
def perform(self) -&gt; None:
"""Invoke the items ability, this action will be given to provide context."""
<span class="crossed-out-text">self.item.consumable.activate(self)</span>
<span class="new-text">if self.item.consumable:
self.item.consumable.activate(self)</span></pre>
</div>
</div>
<p>In order to actually create the equippable entities, well want to add a few examples to <code>entity_factories.py</code>. The entities we will add will correspond to the <code>Equippable</code> subclasses we already made. Edit <code>entity_factories.py</code> like this:</p>
<div>
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
Diff
</button>
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
Original
</button>
<div class="data-pane active" data-pane="diff">
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>from components.ai import HostileEnemy
</span></span><span style="display:flex;"><span><span style="color:#f92672">-from components import consumable
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+from components import consumable, equippable
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>from components.fighter import Fighter
</span></span><span style="display:flex;"><span>from components.inventory import Inventory
</span></span><span style="display:flex;"><span>from components.level import Level
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>lightning_scroll = Item(
</span></span><span style="display:flex;"><span> char="~",
</span></span><span style="display:flex;"><span> color=(255, 255, 0),
</span></span><span style="display:flex;"><span> name="Lightning Scroll",
</span></span><span style="display:flex;"><span> consumable=consumable.LightningDamageConsumable(damage=20, maximum_range=5),
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+dagger = Item(
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ char="/", color=(0, 191, 255), name="Dagger", equippable=equippable.Dagger()
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+sword = Item(char="/", color=(0, 191, 255), name="Sword", equippable=equippable.Sword())
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+leather_armor = Item(
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ char="[",
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ color=(139, 69, 19),
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ name="Leather Armor",
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ equippable=equippable.LeatherArmor(),
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+chain_mail = Item(
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ char="[", color=(139, 69, 19), name="Chain Mail", equippable=equippable.ChainMail()
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+)
</span></span></span></code></pre></div>
</div>
<div class="data-pane" data-pane="original">
<pre>from components.ai import HostileEnemy
<span class="crossed-out-text">from components import consumable</span>
<span class="new-text">from components import consumable, equippable</span>
from components.fighter import Fighter
from components.inventory import Inventory
from components.level import Level
...
lightning_scroll = Item(
char="~",
color=(255, 255, 0),
name="Lightning Scroll",
consumable=consumable.LightningDamageConsumable(damage=20, maximum_range=5),
)
<span class="new-text">dagger = Item(
char="/", color=(0, 191, 255), name="Dagger", equippable=equippable.Dagger()
)
sword = Item(char="/", color=(0, 191, 255), name="Sword", equippable=equippable.Sword())
leather_armor = Item(
char="[",
color=(139, 69, 19),
name="Leather Armor",
equippable=equippable.LeatherArmor(),
)
chain_mail = Item(
char="[", color=(139, 69, 19), name="Chain Mail", equippable=equippable.ChainMail()
)</span></pre>
</div>
</div>
<p>The creation of these entities is very similar to the consumables, except we give them the <code>Equippable</code> component instead of <code>Consumable</code>.
This is all we need to do to create the entities themselves, but were
far from finished. We still need to make these entities appear on the
map, make them equippable (theres nothing for them to attach <em>to</em> on the player right now), and make equipping them actually do something.</p>
<p>To handle the equipment that the player has equipped at the moment,
we can create yet another component to handle the players (or the
monsters, for that matter) equipment. Create a new file called <code>equipment.py</code> in the <code>components</code> folder, and add these contents:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span><span style="color:#f92672">from</span> __future__ <span style="color:#f92672">import</span> annotations
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> typing <span style="color:#f92672">import</span> Optional, TYPE_CHECKING
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> components.base_component <span style="color:#f92672">import</span> BaseComponent
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> equipment_types <span style="color:#f92672">import</span> EquipmentType
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> TYPE_CHECKING:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">from</span> entity <span style="color:#f92672">import</span> Actor, Item
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Equipment</span>(BaseComponent):
</span></span><span style="display:flex;"><span> parent: Actor
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __init__(self, weapon: Optional[Item] <span style="color:#f92672">=</span> <span style="color:#66d9ef">None</span>, armor: Optional[Item] <span style="color:#f92672">=</span> <span style="color:#66d9ef">None</span>):
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>weapon <span style="color:#f92672">=</span> weapon
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>armor <span style="color:#f92672">=</span> armor
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">@property</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">defense_bonus</span>(self) <span style="color:#f92672">-&gt;</span> int:
</span></span><span style="display:flex;"><span> bonus <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> self<span style="color:#f92672">.</span>weapon <span style="color:#f92672">is</span> <span style="color:#f92672">not</span> <span style="color:#66d9ef">None</span> <span style="color:#f92672">and</span> self<span style="color:#f92672">.</span>weapon<span style="color:#f92672">.</span>equippable <span style="color:#f92672">is</span> <span style="color:#f92672">not</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span> bonus <span style="color:#f92672">+=</span> self<span style="color:#f92672">.</span>weapon<span style="color:#f92672">.</span>equippable<span style="color:#f92672">.</span>defense_bonus
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> self<span style="color:#f92672">.</span>armor <span style="color:#f92672">is</span> <span style="color:#f92672">not</span> <span style="color:#66d9ef">None</span> <span style="color:#f92672">and</span> self<span style="color:#f92672">.</span>armor<span style="color:#f92672">.</span>equippable <span style="color:#f92672">is</span> <span style="color:#f92672">not</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span> bonus <span style="color:#f92672">+=</span> self<span style="color:#f92672">.</span>armor<span style="color:#f92672">.</span>equippable<span style="color:#f92672">.</span>defense_bonus
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> bonus
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">@property</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">power_bonus</span>(self) <span style="color:#f92672">-&gt;</span> int:
</span></span><span style="display:flex;"><span> bonus <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> self<span style="color:#f92672">.</span>weapon <span style="color:#f92672">is</span> <span style="color:#f92672">not</span> <span style="color:#66d9ef">None</span> <span style="color:#f92672">and</span> self<span style="color:#f92672">.</span>weapon<span style="color:#f92672">.</span>equippable <span style="color:#f92672">is</span> <span style="color:#f92672">not</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span> bonus <span style="color:#f92672">+=</span> self<span style="color:#f92672">.</span>weapon<span style="color:#f92672">.</span>equippable<span style="color:#f92672">.</span>power_bonus
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> self<span style="color:#f92672">.</span>armor <span style="color:#f92672">is</span> <span style="color:#f92672">not</span> <span style="color:#66d9ef">None</span> <span style="color:#f92672">and</span> self<span style="color:#f92672">.</span>armor<span style="color:#f92672">.</span>equippable <span style="color:#f92672">is</span> <span style="color:#f92672">not</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span> bonus <span style="color:#f92672">+=</span> self<span style="color:#f92672">.</span>armor<span style="color:#f92672">.</span>equippable<span style="color:#f92672">.</span>power_bonus
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> bonus
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">item_is_equipped</span>(self, item: Item) <span style="color:#f92672">-&gt;</span> bool:
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> self<span style="color:#f92672">.</span>weapon <span style="color:#f92672">==</span> item <span style="color:#f92672">or</span> self<span style="color:#f92672">.</span>armor <span style="color:#f92672">==</span> item
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">unequip_message</span>(self, item_name: str) <span style="color:#f92672">-&gt;</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>parent<span style="color:#f92672">.</span>gamemap<span style="color:#f92672">.</span>engine<span style="color:#f92672">.</span>message_log<span style="color:#f92672">.</span>add_message(
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">f</span><span style="color:#e6db74">"You remove the </span><span style="color:#e6db74">{</span>item_name<span style="color:#e6db74">}</span><span style="color:#e6db74">."</span>
</span></span><span style="display:flex;"><span> )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">equip_message</span>(self, item_name: str) <span style="color:#f92672">-&gt;</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>parent<span style="color:#f92672">.</span>gamemap<span style="color:#f92672">.</span>engine<span style="color:#f92672">.</span>message_log<span style="color:#f92672">.</span>add_message(
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">f</span><span style="color:#e6db74">"You equip the </span><span style="color:#e6db74">{</span>item_name<span style="color:#e6db74">}</span><span style="color:#e6db74">."</span>
</span></span><span style="display:flex;"><span> )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">equip_to_slot</span>(self, slot: str, item: Item, add_message: bool) <span style="color:#f92672">-&gt;</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span> current_item <span style="color:#f92672">=</span> getattr(self, slot)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> current_item <span style="color:#f92672">is</span> <span style="color:#f92672">not</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>unequip_from_slot(slot, add_message)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> setattr(self, slot, item)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> add_message:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>equip_message(item<span style="color:#f92672">.</span>name)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">unequip_from_slot</span>(self, slot: str, add_message: bool) <span style="color:#f92672">-&gt;</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span> current_item <span style="color:#f92672">=</span> getattr(self, slot)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> add_message:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>unequip_message(current_item<span style="color:#f92672">.</span>name)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> setattr(self, slot, <span style="color:#66d9ef">None</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">toggle_equip</span>(self, equippable_item: Item, add_message: bool <span style="color:#f92672">=</span> <span style="color:#66d9ef">True</span>) <span style="color:#f92672">-&gt;</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> (
</span></span><span style="display:flex;"><span> equippable_item<span style="color:#f92672">.</span>equippable
</span></span><span style="display:flex;"><span> <span style="color:#f92672">and</span> equippable_item<span style="color:#f92672">.</span>equippable<span style="color:#f92672">.</span>equipment_type <span style="color:#f92672">==</span> EquipmentType<span style="color:#f92672">.</span>WEAPON
</span></span><span style="display:flex;"><span> ):
</span></span><span style="display:flex;"><span> slot <span style="color:#f92672">=</span> <span style="color:#e6db74">"weapon"</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span> slot <span style="color:#f92672">=</span> <span style="color:#e6db74">"armor"</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> getattr(self, slot) <span style="color:#f92672">==</span> equippable_item:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>unequip_from_slot(slot, add_message)
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>equip_to_slot(slot, equippable_item, add_message)
</span></span></code></pre></div><p>Thats a lot to take in, so lets go through it bit by bit.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Equipment</span>(BaseComponent):
</span></span><span style="display:flex;"><span> parent: Actor
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __init__(self, weapon: Optional[Item] <span style="color:#f92672">=</span> <span style="color:#66d9ef">None</span>, armor: Optional[Item] <span style="color:#f92672">=</span> <span style="color:#66d9ef">None</span>):
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>weapon <span style="color:#f92672">=</span> weapon
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>armor <span style="color:#f92672">=</span> armor
</span></span></code></pre></div><p>The <code>weapon</code> and <code>armor</code> attributes are what will hold the actual equippable entity. Both can be set to <code>None</code>, which represents nothing equipped in those slots. Feel free to add more slots as you see fit (perhaps you want <code>armor</code> to be head, body, legs, etc. instead, or allow for off-hand weapons/shields).</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> <span style="color:#a6e22e">@property</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">defense_bonus</span>(self) <span style="color:#f92672">-&gt;</span> int:
</span></span><span style="display:flex;"><span> bonus <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> self<span style="color:#f92672">.</span>weapon <span style="color:#f92672">is</span> <span style="color:#f92672">not</span> <span style="color:#66d9ef">None</span> <span style="color:#f92672">and</span> self<span style="color:#f92672">.</span>weapon<span style="color:#f92672">.</span>equippable <span style="color:#f92672">is</span> <span style="color:#f92672">not</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span> bonus <span style="color:#f92672">+=</span> self<span style="color:#f92672">.</span>weapon<span style="color:#f92672">.</span>equippable<span style="color:#f92672">.</span>defense_bonus
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> self<span style="color:#f92672">.</span>armor <span style="color:#f92672">is</span> <span style="color:#f92672">not</span> <span style="color:#66d9ef">None</span> <span style="color:#f92672">and</span> self<span style="color:#f92672">.</span>armor<span style="color:#f92672">.</span>equippable <span style="color:#f92672">is</span> <span style="color:#f92672">not</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span> bonus <span style="color:#f92672">+=</span> self<span style="color:#f92672">.</span>armor<span style="color:#f92672">.</span>equippable<span style="color:#f92672">.</span>defense_bonus
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> bonus
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">@property</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">power_bonus</span>(self) <span style="color:#f92672">-&gt;</span> int:
</span></span><span style="display:flex;"><span> bonus <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> self<span style="color:#f92672">.</span>weapon <span style="color:#f92672">is</span> <span style="color:#f92672">not</span> <span style="color:#66d9ef">None</span> <span style="color:#f92672">and</span> self<span style="color:#f92672">.</span>weapon<span style="color:#f92672">.</span>equippable <span style="color:#f92672">is</span> <span style="color:#f92672">not</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span> bonus <span style="color:#f92672">+=</span> self<span style="color:#f92672">.</span>weapon<span style="color:#f92672">.</span>equippable<span style="color:#f92672">.</span>power_bonus
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> self<span style="color:#f92672">.</span>armor <span style="color:#f92672">is</span> <span style="color:#f92672">not</span> <span style="color:#66d9ef">None</span> <span style="color:#f92672">and</span> self<span style="color:#f92672">.</span>armor<span style="color:#f92672">.</span>equippable <span style="color:#f92672">is</span> <span style="color:#f92672">not</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span> bonus <span style="color:#f92672">+=</span> self<span style="color:#f92672">.</span>armor<span style="color:#f92672">.</span>equippable<span style="color:#f92672">.</span>power_bonus
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> bonus
</span></span></code></pre></div><p>These properties do the same thing,
just for different things. Both calculate the “bonus” gifted by
equipment to either defense or power, based on whats equipped. Notice
that we take the “power” bonus from both weapons and armor, and the same
applies to the “defense” bonus. This allows you to create weapons that
increase both attack and defense (maybe some sort of spiked shield) and
armor that increases attack (something magical, maybe). We wont do that
in this tutorial (weapons will only increase power, armor will only
increase defense), but you should experiment with different equipment
types on your own.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">item_is_equipped</span>(self, item: Item) <span style="color:#f92672">-&gt;</span> bool:
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> self<span style="color:#f92672">.</span>weapon <span style="color:#f92672">==</span> item <span style="color:#f92672">or</span> self<span style="color:#f92672">.</span>armor <span style="color:#f92672">==</span> item
</span></span></code></pre></div><p>This allows us to quickly check if an <code>Item</code> is equipped by the player or not. It will come in handy later on.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">unequip_message</span>(self, item_name: str) <span style="color:#f92672">-&gt;</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>parent<span style="color:#f92672">.</span>gamemap<span style="color:#f92672">.</span>engine<span style="color:#f92672">.</span>message_log<span style="color:#f92672">.</span>add_message(
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">f</span><span style="color:#e6db74">"You remove the </span><span style="color:#e6db74">{</span>item_name<span style="color:#e6db74">}</span><span style="color:#e6db74">."</span>
</span></span><span style="display:flex;"><span> )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">equip_message</span>(self, item_name: str) <span style="color:#f92672">-&gt;</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>parent<span style="color:#f92672">.</span>gamemap<span style="color:#f92672">.</span>engine<span style="color:#f92672">.</span>message_log<span style="color:#f92672">.</span>add_message(
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">f</span><span style="color:#e6db74">"You equip the </span><span style="color:#e6db74">{</span>item_name<span style="color:#e6db74">}</span><span style="color:#e6db74">."</span>
</span></span><span style="display:flex;"><span> )
</span></span></code></pre></div><p>Both of these methods add a message
to the message log, depending on whether the player is equipping or
removing a piece of equipment.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">equip_to_slot</span>(self, slot: str, item: Item, add_message: bool) <span style="color:#f92672">-&gt;</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span> current_item <span style="color:#f92672">=</span> getattr(self, slot)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> current_item <span style="color:#f92672">is</span> <span style="color:#f92672">not</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>unequip_from_slot(slot, add_message)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> setattr(self, slot, item)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> add_message:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>equip_message(item<span style="color:#f92672">.</span>name)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">unequip_from_slot</span>(self, slot: str, add_message: bool) <span style="color:#f92672">-&gt;</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span> current_item <span style="color:#f92672">=</span> getattr(self, slot)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> add_message:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>unequip_message(current_item<span style="color:#f92672">.</span>name)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> setattr(self, slot, <span style="color:#66d9ef">None</span>)
</span></span></code></pre></div><p><code>equip_to_slot</code> and <code>unequip_from_slot</code> with add or remove an Item to the given “slot” (<code>weapon</code> or <code>armor</code>). We use <code>getattr</code> to get the slot, whether its <code>weapon</code> or <code>armor</code>. We use <code>getattr</code> because we wont actually know which one were getting until the function is called. <code>getattr</code> allows us to “get an attribute” on a class (<code>self</code> in this case) and do what we want with it. We use <code>setattr</code> to “set the attribute” the same way.</p>
<p><code>unequip_from_slot</code> simply removes the item. <code>equip_to_slot</code> first checks if theres something equipped to that slot, and calls <code>unequip_from_slot</code> if there is. This way, the player cant equip two things to the same slot.</p>
<p>Whats with the <code>add_message</code> part? Normally, well want
to add a message to the message log when we equip/remove things, but in
this section, well see an exception: When we set up the players
initial equipment. Well use the same “equip” methods to set up the
initial equipment, but theres no need to begin every game with messages
that say the player put on their starting equipment (presumably, the
player character did this before walking into the deadly dungeon). <code>add_message</code>
gives us a simple way to not add the messages if they arent necessary.
In your game, there might be other scenarios where you dont want to
display these messages.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">toggle_equip</span>(self, equippable_item: Item, add_message: bool <span style="color:#f92672">=</span> <span style="color:#66d9ef">True</span>) <span style="color:#f92672">-&gt;</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> (
</span></span><span style="display:flex;"><span> equippable_item<span style="color:#f92672">.</span>equippable
</span></span><span style="display:flex;"><span> <span style="color:#f92672">and</span> equippable_item<span style="color:#f92672">.</span>equippable<span style="color:#f92672">.</span>equipment_type <span style="color:#f92672">==</span> EquipmentType<span style="color:#f92672">.</span>WEAPON
</span></span><span style="display:flex;"><span> ):
</span></span><span style="display:flex;"><span> slot <span style="color:#f92672">=</span> <span style="color:#e6db74">"weapon"</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span> slot <span style="color:#f92672">=</span> <span style="color:#e6db74">"armor"</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> getattr(self, slot) <span style="color:#f92672">==</span> equippable_item:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>unequip_from_slot(slot, add_message)
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>equip_to_slot(slot, equippable_item, add_message)
</span></span></code></pre></div><p>Finally, we have <code>toggle_equip</code>,
which is the method that will actually get called when the player
selects an equippable item. It checks the equipments type (to know
which slot to put it in), and then checks to see if the same item is
already equipped to that slot. If it is, the player presumably wants to
remove it. If not, the player wants to equip it.</p>
<p>To sum up, this component holds references to equippable entities,
calculates the bonuses the player gets from them (which will get added
to the players power and defense values), and gives a way to equip or
remove the items.</p>
<p>Lets add this component to the actors now. <code>entity.py</code> and add these lines:</p>
<div>
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
Diff
</button>
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
Original
</button>
<div class="data-pane active" data-pane="diff">
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>if TYPE_CHECKING:
</span></span><span style="display:flex;"><span> from components.ai import BaseAI
</span></span><span style="display:flex;"><span> from components.consumable import Consumable
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ from components.equipment import Equipment
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> from components.equippable import Equippable
</span></span><span style="display:flex;"><span> from components.fighter import Fighter
</span></span><span style="display:flex;"><span> from components.inventory import Inventory
</span></span><span style="display:flex;"><span> from components.level import Level
</span></span><span style="display:flex;"><span> from game_map import GameMap
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>class Actor(Entity):
</span></span><span style="display:flex;"><span> def __init__(
</span></span><span style="display:flex;"><span> self,
</span></span><span style="display:flex;"><span> *,
</span></span><span style="display:flex;"><span> x: int = 0,
</span></span><span style="display:flex;"><span> y: int = 0,
</span></span><span style="display:flex;"><span> char: str = "?",
</span></span><span style="display:flex;"><span> color: Tuple[int, int, int] = (255, 255, 255),
</span></span><span style="display:flex;"><span> name: str = "&lt;Unnamed&gt;",
</span></span><span style="display:flex;"><span> ai_cls: Type[BaseAI],
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ equipment: Equipment,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> fighter: Fighter,
</span></span><span style="display:flex;"><span> inventory: Inventory,
</span></span><span style="display:flex;"><span> level: Level,
</span></span><span style="display:flex;"><span> ):
</span></span><span style="display:flex;"><span> super().__init__(
</span></span><span style="display:flex;"><span> x=x,
</span></span><span style="display:flex;"><span> y=y,
</span></span><span style="display:flex;"><span> char=char,
</span></span><span style="display:flex;"><span> color=color,
</span></span><span style="display:flex;"><span> name=name,
</span></span><span style="display:flex;"><span> blocks_movement=True,
</span></span><span style="display:flex;"><span> render_order=RenderOrder.ACTOR,
</span></span><span style="display:flex;"><span> )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> self.ai: Optional[BaseAI] = ai_cls(self)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.equipment: Equipment = equipment
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.equipment.parent = self
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span> self.fighter = fighter
</span></span><span style="display:flex;"><span> self.fighter.parent = self
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> ...
</span></span></code></pre></div>
</div>
<div class="data-pane" data-pane="original">
<pre>...
if TYPE_CHECKING:
from components.ai import BaseAI
from components.consumable import Consumable
<span class="new-text">from components.equipment import Equipment</span>
from components.equippable import Equippable
from components.fighter import Fighter
from components.inventory import Inventory
from components.level import Level
from game_map import GameMap
...
class Actor(Entity):
def __init__(
self,
*,
x: int = 0,
y: int = 0,
char: str = "?",
color: Tuple[int, int, int] = (255, 255, 255),
name: str = "&lt;Unnamed&gt;",
ai_cls: Type[BaseAI],
<span class="new-text">equipment: Equipment,</span>
fighter: Fighter,
inventory: Inventory,
level: Level,
):
super().__init__(
x=x,
y=y,
char=char,
color=color,
name=name,
blocks_movement=True,
render_order=RenderOrder.ACTOR,
)
self.ai: Optional[BaseAI] = ai_cls(self)
<span class="new-text">self.equipment: Equipment = equipment
self.equipment.parent = self</span>
self.fighter = fighter
self.fighter.parent = self
...</pre>
</div>
</div>
<p>We also need to update <code>entity_factories.py</code> once again, to create the actors with the <code>Equipment</code> component:</p>
<div>
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
Diff
</button>
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
Original
</button>
<div class="data-pane active" data-pane="diff">
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>from components.ai import HostileEnemy
</span></span><span style="display:flex;"><span>from components import consumable, equippable
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+from components.equipment import Equipment
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>from components.fighter import Fighter
</span></span><span style="display:flex;"><span>from components.inventory import Inventory
</span></span><span style="display:flex;"><span>from components.level import Level
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>player = Actor(
</span></span><span style="display:flex;"><span> char="@",
</span></span><span style="display:flex;"><span> color=(255, 255, 255),
</span></span><span style="display:flex;"><span> name="Player",
</span></span><span style="display:flex;"><span> ai_cls=HostileEnemy,
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ equipment=Equipment(),
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> fighter=Fighter(hp=30, base_defense=1, base_power=2),
</span></span><span style="display:flex;"><span> inventory=Inventory(capacity=26),
</span></span><span style="display:flex;"><span> level=Level(level_up_base=200),
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>orc = Actor(
</span></span><span style="display:flex;"><span> char="o",
</span></span><span style="display:flex;"><span> color=(63, 127, 63),
</span></span><span style="display:flex;"><span> name="Orc",
</span></span><span style="display:flex;"><span> ai_cls=HostileEnemy,
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ equipment=Equipment(),
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> fighter=Fighter(hp=10, base_defense=0, base_power=3),
</span></span><span style="display:flex;"><span> inventory=Inventory(capacity=0),
</span></span><span style="display:flex;"><span> level=Level(xp_given=35),
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>troll = Actor(
</span></span><span style="display:flex;"><span> char="T",
</span></span><span style="display:flex;"><span> color=(0, 127, 0),
</span></span><span style="display:flex;"><span> name="Troll",
</span></span><span style="display:flex;"><span> ai_cls=HostileEnemy,
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ equipment=Equipment(),
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> fighter=Fighter(hp=16, base_defense=1, base_power=4),
</span></span><span style="display:flex;"><span> inventory=Inventory(capacity=0),
</span></span><span style="display:flex;"><span> level=Level(xp_given=100),
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>...
</span></span></code></pre></div>
</div>
<div class="data-pane" data-pane="original">
<pre>from components.ai import HostileEnemy
from components import consumable, equippable
<span class="new-text">from components.equipment import Equipment</span>
from components.fighter import Fighter
from components.inventory import Inventory
from components.level import Level
player = Actor(
char="@",
color=(255, 255, 255),
name="Player",
ai_cls=HostileEnemy,
<span class="new-text">equipment=Equipment(),</span>
fighter=Fighter(hp=30, base_defense=1, base_power=2),
inventory=Inventory(capacity=26),
level=Level(level_up_base=200),
)
orc = Actor(
char="o",
color=(63, 127, 63),
name="Orc",
ai_cls=HostileEnemy,
<span class="new-text">equipment=Equipment(),</span>
fighter=Fighter(hp=10, base_defense=0, base_power=3),
inventory=Inventory(capacity=0),
level=Level(xp_given=35),
)
troll = Actor(
char="T",
color=(0, 127, 0),
name="Troll",
ai_cls=HostileEnemy,
<span class="new-text">equipment=Equipment(),</span>
fighter=Fighter(hp=16, base_defense=1, base_power=4),
inventory=Inventory(capacity=0),
level=Level(xp_given=100),
)
...</pre>
</div>
</div>
<p>One thing we need to do is change the way <code>power</code> and <code>defense</code> are calculated in the <code>Fighter</code>
component. Currently, the values are set directly in the class, but
well want to calculate them based on their base values (what gets
leveled up), and the bonus values (based on the equipment).</p>
<p>We can redefine <code>power</code> and <code>defense</code> as properties, and rename what we set in the class to <code>base_power</code> and <code>base_defense</code>. <code>power</code> and <code>defense</code> will then get their values from their respective bases and equipment bonuses.</p>
<p>This will require edits to several places, but well start first with the most obvious: the <code>Fighter</code> class itself.</p>
<div>
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
Diff
</button>
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
Original
</button>
<div class="data-pane active" data-pane="diff">
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>class Fighter(BaseComponent):
</span></span><span style="display:flex;"><span> parent: Actor
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">- def __init__(self, hp: int, defense: int, power: int):
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ def __init__(self, hp: int, base_defense: int, base_power: int):
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> self.max_hp = hp
</span></span><span style="display:flex;"><span> self._hp = hp
</span></span><span style="display:flex;"><span><span style="color:#f92672">- self.defense = defense
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">- self.power = power
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ self.base_defense = base_defense
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.base_power = base_power
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span> @property
</span></span><span style="display:flex;"><span> def hp(self) -&gt; int:
</span></span><span style="display:flex;"><span> return self._hp
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> @hp.setter
</span></span><span style="display:flex;"><span> def hp(self, value: int) -&gt; None:
</span></span><span style="display:flex;"><span> self._hp = max(0, min(value, self.max_hp))
</span></span><span style="display:flex;"><span> if self._hp == 0 and self.parent.ai:
</span></span><span style="display:flex;"><span> self.die()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ @property
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ def defense(self) -&gt; int:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ return self.base_defense + self.defense_bonus
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ @property
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ def power(self) -&gt; int:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ return self.base_power + self.power_bonus
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ @property
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ def defense_bonus(self) -&gt; int:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ if self.parent.equipment:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ return self.parent.equipment.defense_bonus
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ else:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ return 0
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ @property
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ def power_bonus(self) -&gt; int:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ if self.parent.equipment:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ return self.parent.equipment.power_bonus
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ else:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ return 0
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span> def die(self) -&gt; None:
</span></span><span style="display:flex;"><span> ...
</span></span></code></pre></div>
</div>
<div class="data-pane" data-pane="original">
<pre>class Fighter(BaseComponent):
parent: Actor
<span class="crossed-out-text">def __init__(self, hp: int, defense: int, power: int):</span>
<span class="new-text">def __init__(self, hp: int, base_defense: int, base_power: int):</span>
self.max_hp = hp
self._hp = hp
<span class="crossed-out-text">self.defense = defense</span>
<span class="crossed-out-text">self.power = power</span>
<span class="new-text">self.base_defense = base_defense
self.base_power = base_power</span>
@property
def hp(self) -&gt; int:
return self._hp
@hp.setter
def hp(self, value: int) -&gt; None:
self._hp = max(0, min(value, self.max_hp))
if self._hp == 0 and self.parent.ai:
self.die()
<span class="new-text">@property
def defense(self) -&gt; int:
return self.base_defense + self.defense_bonus
@property
def power(self) -&gt; int:
return self.base_power + self.power_bonus
@property
def defense_bonus(self) -&gt; int:
if self.parent.equipment:
return self.parent.equipment.defense_bonus
else:
return 0
@property
def power_bonus(self) -&gt; int:
if self.parent.equipment:
return self.parent.equipment.power_bonus
else:
return 0</span>
def die(self) -&gt; None:
...</pre>
</div>
</div>
<p><code>power</code> and <code>defense</code> are now computed based on the base values and the bonus values offered by the equipment (if any exists).</p>
<p>Well need to edit <code>level.py</code> to reflect the new attribute names as well:</p>
<div>
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
Diff
</button>
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
Original
</button>
<div class="data-pane active" data-pane="diff">
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>class Level(BaseComponent):
</span></span><span style="display:flex;"><span> ...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> def increase_power(self, amount: int = 1) -&gt; None:
</span></span><span style="display:flex;"><span><span style="color:#f92672">- self.parent.fighter.power += amount
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ self.parent.fighter.base_power += amount
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span> self.engine.message_log.add_message("You feel stronger!")
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> self.increase_level()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> def increase_defense(self, amount: int = 1) -&gt; None:
</span></span><span style="display:flex;"><span><span style="color:#f92672">- self.parent.fighter.defense += amount
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ self.parent.fighter.base_defense += amount
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span> self.engine.message_log.add_message("Your movements are getting swifter!")
</span></span></code></pre></div>
</div>
<div class="data-pane" data-pane="original">
<pre>class Level(BaseComponent):
...
def increase_power(self, amount: int = 1) -&gt; None:
<span class="crossed-out-text">self.parent.fighter.power += amount</span>
<span class="new-text">self.parent.fighter.base_power += amount</span>
self.engine.message_log.add_message("You feel stronger!")
self.increase_level()
def increase_defense(self, amount: int = 1) -&gt; None:
<span class="crossed-out-text">self.parent.fighter.defense += amount</span>
<span class="new-text">self.parent.fighter.base_defense += amount</span>
self.engine.message_log.add_message("Your movements are getting swifter!")</pre>
</div>
</div>
<p>We also have to adjust the initializations in <code>entity_factories.py</code>:</p>
<div>
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
Diff
</button>
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
Original
</button>
<div class="data-pane active" data-pane="diff">
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>player = Actor(
</span></span><span style="display:flex;"><span> char="@",
</span></span><span style="display:flex;"><span> color=(255, 255, 255),
</span></span><span style="display:flex;"><span> name="Player",
</span></span><span style="display:flex;"><span> ai_cls=HostileEnemy,
</span></span><span style="display:flex;"><span> equipment=Equipment(),
</span></span><span style="display:flex;"><span><span style="color:#f92672">- fighter=Fighter(hp=30, defense=2, power=5),
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ fighter=Fighter(hp=30, base_defense=1, base_power=2),
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> inventory=Inventory(capacity=26),
</span></span><span style="display:flex;"><span> level=Level(level_up_base=200),
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>orc = Actor(
</span></span><span style="display:flex;"><span> char="o",
</span></span><span style="display:flex;"><span> color=(63, 127, 63),
</span></span><span style="display:flex;"><span> name="Orc",
</span></span><span style="display:flex;"><span> ai_cls=HostileEnemy,
</span></span><span style="display:flex;"><span> equipment=Equipment(),
</span></span><span style="display:flex;"><span><span style="color:#f92672">- fighter=Fighter(hp=10, defense=0, power=3),
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ fighter=Fighter(hp=10, base_defense=0, base_power=3),
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> inventory=Inventory(capacity=0),
</span></span><span style="display:flex;"><span> level=Level(xp_given=35),
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>troll = Actor(
</span></span><span style="display:flex;"><span> char="T",
</span></span><span style="display:flex;"><span> color=(0, 127, 0),
</span></span><span style="display:flex;"><span> name="Troll",
</span></span><span style="display:flex;"><span> ai_cls=HostileEnemy,
</span></span><span style="display:flex;"><span> equipment=Equipment(),
</span></span><span style="display:flex;"><span><span style="color:#f92672">- fighter=Fighter(hp=16, defense=1, power=4),
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ fighter=Fighter(hp=16, base_defense=1, base_power=4),
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> inventory=Inventory(capacity=0),
</span></span><span style="display:flex;"><span> level=Level(xp_given=100),
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>...
</span></span></code></pre></div>
</div>
<div class="data-pane" data-pane="original">
<pre>player = Actor(
char="@",
color=(255, 255, 255),
name="Player",
ai_cls=HostileEnemy,
equipment=Equipment(),
<span class="crossed-out-text">fighter=Fighter(hp=30, defense=2, power=5),</span>
<span class="new-text">fighter=Fighter(hp=30, base_defense=1, base_power=2),</span>
inventory=Inventory(capacity=26),
level=Level(level_up_base=200),
)
orc = Actor(
char="o",
color=(63, 127, 63),
name="Orc",
ai_cls=HostileEnemy,
equipment=Equipment(),
<span class="crossed-out-text">fighter=Fighter(hp=10, defense=0, power=3),</span>
<span class="new-text">fighter=Fighter(hp=10, base_defense=0, base_power=3),</span>
inventory=Inventory(capacity=0),
level=Level(xp_given=35),
)
troll = Actor(
char="T",
color=(0, 127, 0),
name="Troll",
ai_cls=HostileEnemy,
equipment=Equipment(),
<span class="crossed-out-text">fighter=Fighter(hp=16, defense=1, power=4),</span>
<span class="new-text">fighter=Fighter(hp=16, base_defense=1, base_power=4),</span>
inventory=Inventory(capacity=0),
level=Level(xp_given=100),
)
...</pre>
</div>
</div>
<p>Notice that weve changed the players base values a bit. This is to
compensate for the fact that the player will be getting bonuses from the
equipment soon. Feel free to tweak these values however you see fit.</p>
<p>Now all thats left to do is allow generate the equipment to the map,
and allow the player to interact with it. To create equipment, we can
simply edit our <code>item_chances</code> dictionary to include weapons and armor on certain floors. Edit <code>procgen.py</code> like this:</p>
<div>
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
Diff
</button>
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
Original
</button>
<div class="data-pane active" data-pane="diff">
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>item_chances: Dict[int, List[Tuple[Entity, int]]] = {
</span></span><span style="display:flex;"><span> 0: [(entity_factories.health_potion, 35)],
</span></span><span style="display:flex;"><span> 2: [(entity_factories.confusion_scroll, 10)],
</span></span><span style="display:flex;"><span><span style="color:#f92672">- 4: [(entity_factories.lightning_scroll, 25)],
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">- 6: [(entity_factories.fireball_scroll, 25)],
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ 4: [(entity_factories.lightning_scroll, 25), (entity_factories.sword, 5)],
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ 6: [(entity_factories.fireball_scroll, 25), (entity_factories.chain_mail, 15)],
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>}
</span></span></code></pre></div>
</div>
<div class="data-pane" data-pane="original">
<pre>item_chances: Dict[int, List[Tuple[Entity, int]]] = {
0: [(entity_factories.health_potion, 35)],
2: [(entity_factories.confusion_scroll, 10)],
<span class="crossed-out-text">4: [(entity_factories.lightning_scroll, 25)],</span>
<span class="crossed-out-text">6: [(entity_factories.fireball_scroll, 25)],</span>
<span class="new-text">4: [(entity_factories.lightning_scroll, 25), (entity_factories.sword, 5)],</span>
<span class="new-text">6: [(entity_factories.fireball_scroll, 25), (entity_factories.chain_mail, 15)],</span>
}</pre>
</div>
</div>
<p>This will generate swords and chain mail at levels 4 and 6, respectively. You can change the floor or the weights if you like.</p>
<p>Now that equipment will spawn on the map, we need to allow the user
to equip and remove equippable entities. The first step is to add an
action to equip things, which well call <code>EquipAction</code>. Add this class to <code>actions.py</code>:</p>
<div>
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
Diff
</button>
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
Original
</button>
<div class="data-pane active" data-pane="diff">
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>class DropItem(ItemAction):
</span></span><span style="display:flex;"><span> ...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+class EquipAction(Action):
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ def __init__(self, entity: Actor, item: Item):
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ super().__init__(entity)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.item = item
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ def perform(self) -&gt; None:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.entity.equipment.toggle_equip(self.item)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>class WaitAction(Action):
</span></span><span style="display:flex;"><span> ...
</span></span></code></pre></div>
</div>
<div class="data-pane" data-pane="original">
<pre>...
class DropItem(ItemAction):
...
<span class="new-text">class EquipAction(Action):
def __init__(self, entity: Actor, item: Item):
super().__init__(entity)
self.item = item
def perform(self) -&gt; None:
self.entity.equipment.toggle_equip(self.item)</span>
class WaitAction(Action):
...</pre>
</div>
</div>
<p>The action itself is very straightforward: It holds which item is being equipped/removed, and calls the <code>toggle_equip</code> method. The <code>Equipment</code> component handles most of the work here.</p>
<p>But how do we <em>use</em> this action? The simplest way would be to
expand the functionality of our original inventory menu. If the user
selects a piece of equipment from that menu, well either equip the
item, or remove it, if its already equipped. We should also show the
user a visual representation of which items are already equipped.</p>
<p>Modify <code>input_handlers.py</code> like this:</p>
<div>
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
Diff
</button>
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
Original
</button>
<div class="data-pane active" data-pane="diff">
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>class InventoryEventHandler(AskUserEventHandler):
</span></span><span style="display:flex;"><span> def on_render(self, console: tcod.Console) -&gt; None:
</span></span><span style="display:flex;"><span> ...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> if number_of_items_in_inventory &gt; 0:
</span></span><span style="display:flex;"><span> for i, item in enumerate(self.engine.player.inventory.items):
</span></span><span style="display:flex;"><span> item_key = chr(ord("a") + i)
</span></span><span style="display:flex;"><span><span style="color:#f92672">- console.print(x + 1, y + i + 1, f"({item_key}) {item.name}")
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ is_equipped = self.engine.player.equipment.item_is_equipped(item)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ item_string = f"({item_key}) {item.name}"
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ if is_equipped:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ item_string = f"{item_string} (E)"
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ console.print(x + 1, y + i + 1, item_string)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> else:
</span></span><span style="display:flex;"><span> console.print(x + 1, y + 1, "(Empty)")
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> ...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>class InventoryActivateHandler(InventoryEventHandler):
</span></span><span style="display:flex;"><span> """Handle using an inventory item."""
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> TITLE = "Select an item to use"
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> def on_item_selected(self, item: Item) -&gt; Optional[ActionOrHandler]:
</span></span><span style="display:flex;"><span><span style="color:#f92672">- """Return the action for the selected item."""
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">- return item.consumable.get_action(self.engine.player)
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ if item.consumable:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ # Return the action for the selected item.
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ return item.consumable.get_action(self.engine.player)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ elif item.equippable:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ return actions.EquipAction(self.engine.player, item)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ else:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ return None
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>class InventoryDropHandler(InventoryEventHandler):
</span></span><span style="display:flex;"><span> ...
</span></span></code></pre></div>
</div>
<div class="data-pane" data-pane="original">
<pre>class InventoryEventHandler(AskUserEventHandler):
def on_render(self, console: tcod.Console) -&gt; None:
...
if number_of_items_in_inventory &gt; 0:
for i, item in enumerate(self.engine.player.inventory.items):
item_key = chr(ord("a") + i)
<span class="crossed-out-text">console.print(x + 1, y + i + 1, f"({item_key}) {item.name}")</span>
<span class="new-text">is_equipped = self.engine.player.equipment.item_is_equipped(item)
item_string = f"({item_key}) {item.name}"
if is_equipped:
item_string = f"{item_string} (E)"
console.print(x + 1, y + i + 1, item_string)</span>
else:
console.print(x + 1, y + 1, "(Empty)")
...
class InventoryActivateHandler(InventoryEventHandler):
"""Handle using an inventory item."""
TITLE = "Select an item to use"
def on_item_selected(self, item: Item) -&gt; Optional[ActionOrHandler]:
<span class="crossed-out-text">"""Return the action for the selected item."""</span>
<span class="crossed-out-text">return item.consumable.get_action(self.engine.player)</span>
<span class="new-text">if item.consumable:
# Return the action for the selected item.
return item.consumable.get_action(self.engine.player)
elif item.equippable:
return actions.EquipAction(self.engine.player, item)
else:
return None</span>
class InventoryDropHandler(InventoryEventHandler):
...</pre>
</div>
</div>
<p>The first change is modifying the render function to display an “(E)”
next to items that are equipped. Items that arent equipped are
displayed the same way as before.</p>
<p>The second change has to do with using the item. Before, we were just
assuming the item was a consumable. Now, if the item is a consumable,
we call the <code>get_action</code> method on the <code>Consumable</code> component, just like before. If its instead equippable, we call the <code>EquipAction</code>. If its neither, nothing happens.</p>
<p>Run the game now, youll be able to pick up and equip things. I recommend adjusting the values in <code>procgen.py</code> to make equipment spawn earlier and more often, just for testing purposes.</p>
<p>If you play around a bit, you might notice an odd bug: If the player
drops something thats equipped… it stays equipped! That doesnt make
sense, as dropping something should unequip it as well. Luckily, the fix
is quite simple: We can adjust our <code>DropItem</code> action to unequip an item if its being dropped and its equipped. Make the following additions to <code>actions.py</code>:</p>
<div>
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
Diff
</button>
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
Original
</button>
<div class="data-pane active" data-pane="diff">
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>class DropItem(ItemAction):
</span></span><span style="display:flex;"><span> def perform(self) -&gt; None:
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ if self.entity.equipment.item_is_equipped(self.item):
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.entity.equipment.toggle_equip(self.item)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span> self.entity.inventory.drop(self.item)
</span></span></code></pre></div>
</div>
<div class="data-pane" data-pane="original">
<pre>class DropItem(ItemAction):
def perform(self) -&gt; None:
<span class="new-text">if self.entity.equipment.item_is_equipped(self.item):
self.entity.equipment.toggle_equip(self.item)</span>
self.entity.inventory.drop(self.item)</pre>
</div>
</div>
<p>One last thing we can do is give the player a bit of equipment to
start. Well spawn a dagger and leather armor, and immediately add them
to the players inventory.</p>
<div>
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
Diff
</button>
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
Original
</button>
<div class="data-pane active" data-pane="diff">
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>def new_game() -&gt; Engine:
</span></span><span style="display:flex;"><span> ...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> engine.message_log.add_message(
</span></span><span style="display:flex;"><span> "Hello and welcome, adventurer, to yet another dungeon!", color.welcome_text
</span></span><span style="display:flex;"><span> )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ dagger = copy.deepcopy(entity_factories.dagger)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ leather_armor = copy.deepcopy(entity_factories.leather_armor)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ dagger.parent = player.inventory
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ leather_armor.parent = player.inventory
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ player.inventory.items.append(dagger)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ player.equipment.toggle_equip(dagger, add_message=False)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ player.inventory.items.append(leather_armor)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ player.equipment.toggle_equip(leather_armor, add_message=False)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span> return engine
</span></span></code></pre></div>
</div>
<div class="data-pane" data-pane="original">
<pre>def new_game() -&gt; Engine:
...
engine.message_log.add_message(
"Hello and welcome, adventurer, to yet another dungeon!", color.welcome_text
)
<span class="new-text">dagger = copy.deepcopy(entity_factories.dagger)
leather_armor = copy.deepcopy(entity_factories.leather_armor)
dagger.parent = player.inventory
leather_armor.parent = player.inventory
player.inventory.items.append(dagger)
player.equipment.toggle_equip(dagger, add_message=False)
player.inventory.items.append(leather_armor)
player.equipment.toggle_equip(leather_armor, add_message=False)</span>
return engine</pre>
</div>
</div>
<p>As mentioned earlier, we pass <code>add_message=False</code> to signify not to add a message to the message log.</p>
<p><img src="Part%2013%20-%20Gearing%20up%20%C2%B7%20Roguelike%20Tutorials_files/part-13-end.png" alt="Part 13 - End"></p>
<p>With that, weve reached the end of the tutorial! Thank you so much for following along, and be sure to check out the <a href="https://rogueliketutorials.com/tutorials/tcod/v2">extras section</a>. More will be added there over time. If you have a suggestion for an extra, let me know!</p>
<p>Be sure to check out the <a href="https://www.reddit.com/r/roguelikedev">Roguelike Development Subreddit</a> for help, for inspiration, or to share your progress.</p>
<p>Best of luck on your roguelike development journey!</p>
<p>If you want to see the code so far in its entirety, <a href="https://github.com/TStand90/tcod_tutorial_v2/tree/2020/part-13">click here</a>.</p>
</article>
</section>
</div>
<footer class="footer">
<section class="container">
©
2023
·
Powered by <a href="https://gohugo.io/">Hugo</a> &amp; <a href="https://github.com/luizdepra/hugo-coder/">Coder</a>.
</section>
</footer>
</main>
<script src="Part%2013%20-%20Gearing%20up%20%C2%B7%20Roguelike%20Tutorials_files/coder.min.236049395dc3682fb2719640872958e12f1f24067bb09c327b2.js" integrity="sha256-I2BJOV3DaC+ycZZAhylY4S8fJAZ7sJwyeyM+YpDH7aw="></script>
<script src="Part%2013%20-%20Gearing%20up%20%C2%B7%20Roguelike%20Tutorials_files/codetabs.min.cc52451e7f25e50f64c1c893826f606d58410d742c214dce.js" integrity="sha256-zFJFHn8l5Q9kwciTgm9gbVhBDXQsIU3OI/tEfJlh8rA="></script>
</body></html>