# Part 13 - Gearing up

For the final part of this tutorial, we’ll 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, we’ll want to define the types of equipment that can be found in the dungeon. As with the `RenderOrder` class, we can use `Enum` to define the types. For now, we’ll leave it at weapons and armor, but feel free to add more types as you see fit.

Create a new file, `equipment_types.py`, and put the following contents in it:

    from enum import auto, Enum

    class EquipmentType(Enum):
        WEAPON = auto()
        ARMOR = auto()

Now it’s time to create the component that we’ll attach to the equipment. We’ll call the component `Equippable`, which will have a few different attributes:

- `equipment_type`: The type of equipment, using the `EquipmentType` enum.
- `power_bonus`: How much the wielder’s attack power will be increased. Currently used for just weapons.
- `defense_bonus`: How much the wearer’s defense will be increased. Currently just for armor.

Create the file `equippable.py` in the `components` directory, and fill it with the following:

    from __future__ import annotations

    from typing import TYPE_CHECKING

    from components.base_component import BaseComponent
    from equipment_types import EquipmentType

    if TYPE_CHECKING:
        from entity import Item

    class Equippable(BaseComponent):
        parent: Item

        def __init__(
            self,
            equipment_type: EquipmentType,
            power_bonus: int = 0,
            defense_bonus: int = 0,
        ):
            self.equipment_type = equipment_type

            self.power_bonus = power_bonus
            self.defense_bonus = defense_bonus

    class Dagger(Equippable):
        def __init__(self) -> None:
            super().__init__(equipment_type=EquipmentType.WEAPON, power_bonus=2)

    class Sword(Equippable):
        def __init__(self) -> None:
            super().__init__(equipment_type=EquipmentType.WEAPON, power_bonus=4)

    class LeatherArmor(Equippable):
        def __init__(self) -> None:
            super().__init__(equipment_type=EquipmentType.ARMOR, defense_bonus=1)

    class ChainMail(Equippable):
        def __init__(self) -> None:
            super().__init__(equipment_type=EquipmentType.ARMOR, defense_bonus=3)

Aside from creating the `Equippable` class, as described earlier, we’ve also created a few types of equippable components, for each equippable entity that we’ll end up creating, similar to what we did with the `Consumable` classes. You don’t 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 `Equippable` classes this way might make that easier. You might also want to move these classes to their own file, but that’s outside the scope of this tutorial.

To create the actual equippable entities, we’ll want to adjust our `Item` 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 `Entity`, but for the sake of keeping the number of `Entity` subclasses in this tutorial short, we’ll adjust `Item`. Make the following adjustments to `entity.py`:

    ...
    if TYPE_CHECKING:
        from components.ai import BaseAI
        from components.consumable import Consumable
    +   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 Item(Entity):
        def __init__(
            self,
            *,
            x: int = 0,
            y: int = 0,
            char: str = "?",
            color: Tuple[int, int, int] = (255, 255, 255),
            name: str = "<Unnamed>",
    -       consumable: Consumable,
    +       consumable: Optional[Consumable] = None,
    +       equippable: Optional[Equippable] = None,
        ):
            super().__init__(
                x=x,
                y=y,
                char=char,
                color=color,
                name=name,
                blocks_movement=False,
                render_order=RenderOrder.ITEM,
            )

            self.consumable = consumable
    -       self.consumable.parent = self

    +       if self.consumable:
    +           self.consumable.parent = self

    +       self.equippable = equippable

    +       if self.equippable:
    +           self.equippable.parent = self

    ...
    if TYPE_CHECKING:
        from components.ai import BaseAI
        from components.consumable import Consumable
        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 Item(Entity):
        def __init__(
            self,
            *,
            x: int = 0,
            y: int = 0,
            char: str = "?",
            color: Tuple[int, int, int] = (255, 255, 255),
            name: str = "<Unnamed>",
            consumable: Consumable,
            consumable: Optional[Consumable] = None,
            equippable: Optional[Equippable] = None,
        ):
            super().__init__(
                x=x,
                y=y,
                char=char,
                color=color,
                name=name,
                blocks_movement=False,
                render_order=RenderOrder.ITEM,
            )

            self.consumable = consumable
            self.consumable.parent = self

            if self.consumable:
                self.consumable.parent = self

            self.equippable = equippable

            if self.equippable:
                self.equippable.parent = self

We’ve added `Equippable` as an optional component for the `Item` class, and also made `Consumable` optional, so that not all `Item` instances will be consumable.

Because `consumable` is now an optional attribute, we need to adjust `actions.py` to take this into account:

    class ItemAction(Action):
        ...

        def perform(self) -> None:
            """Invoke the items ability, this action will be given to provide context."""
    -       self.item.consumable.activate(self)
    +       if self.item.consumable:
    +           self.item.consumable.activate(self)

    class ItemAction(Action):
        ...

        def perform(self) -> None:
            """Invoke the items ability, this action will be given to provide context."""
            self.item.consumable.activate(self)
            if self.item.consumable:
                self.item.consumable.activate(self)

In order to actually create the equippable entities, we’ll want to add a few examples to `entity_factories.py`. The entities we will add will correspond to the `Equippable` subclasses we already made. Edit `entity_factories.py` like this:

    from components.ai import HostileEnemy
    -from components import consumable
    +from components import consumable, equippable
    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),
    )

    +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()
    +)

    from components.ai import HostileEnemy
    from components import consumable
    from components import consumable, equippable
    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),
    )

    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()
    )

The creation of these entities is very similar to the consumables, except we give them the `Equippable` component instead of `Consumable`. This is all we need to do to create the entities themselves, but we’re far from finished. We still need to make these entities appear on the map, make them equippable (there’s nothing for them to attach to on the player right now), and make equipping them actually do something.

To handle the equipment that the player has equipped at the moment, we can create yet another component to handle the player’s (or the monster’s, for that matter) equipment. Create a new file called `equipment.py` in the `components` folder, and add these contents:

    from __future__ import annotations

    from typing import Optional, TYPE_CHECKING

    from components.base_component import BaseComponent
    from equipment_types import EquipmentType

    if TYPE_CHECKING:
        from entity import Actor, Item

    class Equipment(BaseComponent):
        parent: Actor

        def __init__(self, weapon: Optional[Item] = None, armor: Optional[Item] = None):
            self.weapon = weapon
            self.armor = armor

        @property
        def defense_bonus(self) -> int:
            bonus = 0

            if self.weapon is not None and self.weapon.equippable is not None:
                bonus += self.weapon.equippable.defense_bonus

            if self.armor is not None and self.armor.equippable is not None:
                bonus += self.armor.equippable.defense_bonus

            return bonus

        @property
        def power_bonus(self) -> int:
            bonus = 0

            if self.weapon is not None and self.weapon.equippable is not None:
                bonus += self.weapon.equippable.power_bonus

            if self.armor is not None and self.armor.equippable is not None:
                bonus += self.armor.equippable.power_bonus

            return bonus

        def item_is_equipped(self, item: Item) -> bool:
            return self.weapon == item or self.armor == item

        def unequip_message(self, item_name: str) -> None:
            self.parent.gamemap.engine.message_log.add_message(
                f"You remove the {item_name}."
            )

        def equip_message(self, item_name: str) -> None:
            self.parent.gamemap.engine.message_log.add_message(
                f"You equip the {item_name}."
            )

        def equip_to_slot(self, slot: str, item: Item, add_message: bool) -> None:
            current_item = getattr(self, slot)

            if current_item is not None:
                self.unequip_from_slot(slot, add_message)

            setattr(self, slot, item)

            if add_message:
                self.equip_message(item.name)

        def unequip_from_slot(self, slot: str, add_message: bool) -> None:
            current_item = getattr(self, slot)

            if add_message:
                self.unequip_message(current_item.name)

            setattr(self, slot, None)

        def toggle_equip(self, equippable_item: Item, add_message: bool = True) -> None:
            if (
                equippable_item.equippable
                and equippable_item.equippable.equipment_type == EquipmentType.WEAPON
            ):
                slot = "weapon"
            else:
                slot = "armor"

            if getattr(self, slot) == equippable_item:
                self.unequip_from_slot(slot, add_message)
            else:
                self.equip_to_slot(slot, equippable_item, add_message)

That’s a lot to take in, so let’s go through it bit by bit.

    class Equipment(BaseComponent):
        parent: Actor

        def __init__(self, weapon: Optional[Item] = None, armor: Optional[Item] = None):
            self.weapon = weapon
            self.armor = armor

The `weapon` and `armor` attributes are what will hold the actual equippable entity. Both can be set to `None`, which represents nothing equipped in those slots. Feel free to add more slots as you see fit (perhaps you want `armor` to be head, body, legs, etc. instead, or allow for off-hand weapons/shields).

        @property
        def defense_bonus(self) -> int:
            bonus = 0

            if self.weapon is not None and self.weapon.equippable is not None:
                bonus += self.weapon.equippable.defense_bonus

            if self.armor is not None and self.armor.equippable is not None:
                bonus += self.armor.equippable.defense_bonus

            return bonus

        @property
        def power_bonus(self) -> int:
            bonus = 0

            if self.weapon is not None and self.weapon.equippable is not None:
                bonus += self.weapon.equippable.power_bonus

            if self.armor is not None and self.armor.equippable is not None:
                bonus += self.armor.equippable.power_bonus

            return bonus

These properties do the same thing, just for different things. Both calculate the “bonus” gifted by equipment to either defense or power, based on what’s 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 won’t 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.

        def item_is_equipped(self, item: Item) -> bool:
            return self.weapon == item or self.armor == item

This allows us to quickly check if an `Item` is equipped by the player or not. It will come in handy later on.

        def unequip_message(self, item_name: str) -> None:
            self.parent.gamemap.engine.message_log.add_message(
                f"You remove the {item_name}."
            )

        def equip_message(self, item_name: str) -> None:
            self.parent.gamemap.engine.message_log.add_message(
                f"You equip the {item_name}."
            )

Both of these methods add a message to the message log, depending on whether the player is equipping or removing a piece of equipment.

        def equip_to_slot(self, slot: str, item: Item, add_message: bool) -> None:
            current_item = getattr(self, slot)

            if current_item is not None:
                self.unequip_from_slot(slot, add_message)

            setattr(self, slot, item)

            if add_message:
                self.equip_message(item.name)

        def unequip_from_slot(self, slot: str, add_message: bool) -> None:
            current_item = getattr(self, slot)

            if add_message:
                self.unequip_message(current_item.name)

            setattr(self, slot, None)

`equip_to_slot` and `unequip_from_slot` with add or remove an Item to the given “slot” (`weapon` or `armor`). We use `getattr` to get the slot, whether it’s `weapon` or `armor`. We use `getattr` because we won’t actually know which one we’re getting until the function is called. `getattr` allows us to “get an attribute” on a class (`self` in this case) and do what we want with it. We use `setattr` to “set the attribute” the same way.

`unequip_from_slot` simply removes the item. `equip_to_slot` first checks if there’s something equipped to that slot, and calls `unequip_from_slot` if there is. This way, the player can’t equip two things to the same slot.

What’s with the `add_message` part? Normally, we’ll want to add a message to the message log when we equip/remove things, but in this section, we’ll see an exception: When we set up the player’s initial equipment. We’ll use the same “equip” methods to set up the initial equipment, but there’s 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). `add_message` gives us a simple way to not add the messages if they aren’t necessary. In your game, there might be other scenarios where you don’t want to display these messages.

        def toggle_equip(self, equippable_item: Item, add_message: bool = True) -> None:
            if (
                equippable_item.equippable
                and equippable_item.equippable.equipment_type == EquipmentType.WEAPON
            ):
                slot = "weapon"
            else:
                slot = "armor"

            if getattr(self, slot) == equippable_item:
                self.unequip_from_slot(slot, add_message)
            else:
                self.equip_to_slot(slot, equippable_item, add_message)

Finally, we have `toggle_equip`, which is the method that will actually get called when the player selects an equippable item. It checks the equipment’s 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.

To sum up, this component holds references to equippable entities, calculates the bonuses the player gets from them (which will get added to the player’s power and defense values), and gives a way to equip or remove the items.

Let’s add this component to the actors now. `entity.py` and add these lines:

    ...
    if TYPE_CHECKING:
        from components.ai import BaseAI
        from components.consumable import Consumable
    +   from components.equipment import Equipment
        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 = "<Unnamed>",
            ai_cls: Type[BaseAI],
    +       equipment: Equipment,
            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)

    +       self.equipment: Equipment = equipment
    +       self.equipment.parent = self

            self.fighter = fighter
            self.fighter.parent = self

            ...

    ...
    if TYPE_CHECKING:
        from components.ai import BaseAI
        from components.consumable import Consumable
        from components.equipment import Equipment
        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 = "<Unnamed>",
            ai_cls: Type[BaseAI],
            equipment: Equipment,
            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)

            self.equipment: Equipment = equipment
            self.equipment.parent = self

            self.fighter = fighter
            self.fighter.parent = self

            ...

We also need to update `entity_factories.py` once again, to create the actors with the `Equipment` component:

    from components.ai import HostileEnemy
    from components import consumable, equippable
    +from components.equipment import Equipment
    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,
    +   equipment=Equipment(),
        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,
    +   equipment=Equipment(),
        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,
    +   equipment=Equipment(),
        fighter=Fighter(hp=16, base_defense=1, base_power=4),
        inventory=Inventory(capacity=0),
        level=Level(xp_given=100),
    )
    ...

    from components.ai import HostileEnemy
    from components import consumable, equippable
    from components.equipment import Equipment
    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,
        equipment=Equipment(),
        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,
        equipment=Equipment(),
        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,
        equipment=Equipment(),
        fighter=Fighter(hp=16, base_defense=1, base_power=4),
        inventory=Inventory(capacity=0),
        level=Level(xp_given=100),
    )
    ...

One thing we need to do is change the way `power` and `defense` are calculated in the `Fighter` component. Currently, the values are set directly in the class, but we’ll want to calculate them based on their base values (what gets leveled up), and the bonus values (based on the equipment).

We can redefine `power` and `defense` as properties, and rename what we set in the class to `base_power` and `base_defense`. `power` and `defense` will then get their values from their respective bases and equipment bonuses.

This will require edits to several places, but we’ll start first with the most obvious: the `Fighter` class itself.

    class Fighter(BaseComponent):
        parent: Actor

    -   def __init__(self, hp: int, defense: int, power: int):
    +   def __init__(self, hp: int, base_defense: int, base_power: int):
            self.max_hp = hp
            self._hp = hp
    -       self.defense = defense
    -       self.power = power
    +       self.base_defense = base_defense
    +       self.base_power = base_power

        @property
        def hp(self) -> int:
            return self._hp

        @hp.setter
        def hp(self, value: int) -> None:
            self._hp = max(0, min(value, self.max_hp))
            if self._hp == 0 and self.parent.ai:
                self.die()

    +   @property
    +   def defense(self) -> int:
    +       return self.base_defense + self.defense_bonus

    +   @property
    +   def power(self) -> int:
    +       return self.base_power + self.power_bonus

    +   @property
    +   def defense_bonus(self) -> int:
    +       if self.parent.equipment:
    +           return self.parent.equipment.defense_bonus
    +       else:
    +           return 0

    +   @property
    +   def power_bonus(self) -> int:
    +       if self.parent.equipment:
    +           return self.parent.equipment.power_bonus
    +       else:
    +           return 0

        def die(self) -> None:
            ...

    class Fighter(BaseComponent):
        parent: Actor

        def __init__(self, hp: int, defense: int, power: int):
        def __init__(self, hp: int, base_defense: int, base_power: int):
            self.max_hp = hp
            self._hp = hp
            self.defense = defense
            self.power = power
            self.base_defense = base_defense
            self.base_power = base_power

        @property
        def hp(self) -> int:
            return self._hp

        @hp.setter
        def hp(self, value: int) -> None:
            self._hp = max(0, min(value, self.max_hp))
            if self._hp == 0 and self.parent.ai:
                self.die()

        @property
        def defense(self) -> int:
            return self.base_defense + self.defense_bonus

        @property
        def power(self) -> int:
            return self.base_power + self.power_bonus

        @property
        def defense_bonus(self) -> int:
            if self.parent.equipment:
                return self.parent.equipment.defense_bonus
            else:
                return 0

        @property
        def power_bonus(self) -> int:
            if self.parent.equipment:
                return self.parent.equipment.power_bonus
            else:
                return 0

        def die(self) -> None:
            ...

`power` and `defense` are now computed based on the base values and the bonus values offered by the equipment (if any exists).

We’ll need to edit `level.py` to reflect the new attribute names as well:

    class Level(BaseComponent):
        ...

        def increase_power(self, amount: int = 1) -> None:
    -       self.parent.fighter.power += amount
    +       self.parent.fighter.base_power += amount

            self.engine.message_log.add_message("You feel stronger!")

            self.increase_level()

        def increase_defense(self, amount: int = 1) -> None:
    -       self.parent.fighter.defense += amount
    +       self.parent.fighter.base_defense += amount

            self.engine.message_log.add_message("Your movements are getting swifter!")

    class Level(BaseComponent):
        ...

        def increase_power(self, amount: int = 1) -> None:
            self.parent.fighter.power += amount
            self.parent.fighter.base_power += amount

            self.engine.message_log.add_message("You feel stronger!")

            self.increase_level()

        def increase_defense(self, amount: int = 1) -> None:
            self.parent.fighter.defense += amount
            self.parent.fighter.base_defense += amount

            self.engine.message_log.add_message("Your movements are getting swifter!")

We also have to adjust the initializations in `entity_factories.py`:

    player = Actor(
        char="@",
        color=(255, 255, 255),
        name="Player",
        ai_cls=HostileEnemy,
        equipment=Equipment(),
    -   fighter=Fighter(hp=30, defense=2, power=5),
    +   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,
        equipment=Equipment(),
    -   fighter=Fighter(hp=10, defense=0, power=3),
    +   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,
        equipment=Equipment(),
    -   fighter=Fighter(hp=16, defense=1, power=4),
    +   fighter=Fighter(hp=16, base_defense=1, base_power=4),
        inventory=Inventory(capacity=0),
        level=Level(xp_given=100),
    )
    ...

    player = Actor(
        char="@",
        color=(255, 255, 255),
        name="Player",
        ai_cls=HostileEnemy,
        equipment=Equipment(),
        fighter=Fighter(hp=30, defense=2, power=5),
        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,
        equipment=Equipment(),
        fighter=Fighter(hp=10, defense=0, power=3),
        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,
        equipment=Equipment(),
        fighter=Fighter(hp=16, defense=1, power=4),
        fighter=Fighter(hp=16, base_defense=1, base_power=4),
        inventory=Inventory(capacity=0),
        level=Level(xp_given=100),
    )
    ...

Notice that we’ve changed the player’s 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.

Now all that’s 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 `item_chances` dictionary to include weapons and armor on certain floors. Edit `procgen.py` like this:

    item_chances: Dict[int, List[Tuple[Entity, int]]] = {
        0: [(entity_factories.health_potion, 35)],
        2: [(entity_factories.confusion_scroll, 10)],
    -   4: [(entity_factories.lightning_scroll, 25)],
    -   6: [(entity_factories.fireball_scroll, 25)],
    +   4: [(entity_factories.lightning_scroll, 25), (entity_factories.sword, 5)],
    +   6: [(entity_factories.fireball_scroll, 25), (entity_factories.chain_mail, 15)],
    }

    item_chances: Dict[int, List[Tuple[Entity, int]]] = {
        0: [(entity_factories.health_potion, 35)],
        2: [(entity_factories.confusion_scroll, 10)],
        4: [(entity_factories.lightning_scroll, 25)],
        6: [(entity_factories.fireball_scroll, 25)],
        4: [(entity_factories.lightning_scroll, 25), (entity_factories.sword, 5)],
        6: [(entity_factories.fireball_scroll, 25), (entity_factories.chain_mail, 15)],
    }

This will generate swords and chain mail at levels 4 and 6, respectively. You can change the floor or the weights if you like.

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 we’ll call `EquipAction`. Add this class to `actions.py`:

    ...
    class DropItem(ItemAction):
        ...

    +class EquipAction(Action):
    +   def __init__(self, entity: Actor, item: Item):
    +       super().__init__(entity)

    +       self.item = item

    +   def perform(self) -> None:
    +       self.entity.equipment.toggle_equip(self.item)

    class WaitAction(Action):
        ...

    ...
    class DropItem(ItemAction):
        ...

    class EquipAction(Action):
        def __init__(self, entity: Actor, item: Item):
            super().__init__(entity)

            self.item = item

        def perform(self) -> None:
            self.entity.equipment.toggle_equip(self.item)

    class WaitAction(Action):
        ...

The action itself is very straightforward: It holds which item is being equipped/removed, and calls the `toggle_equip` method. The `Equipment` component handles most of the work here.

But how do we use 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, we’ll either equip the item, or remove it, if it’s already equipped. We should also show the user a visual representation of which items are already equipped.

Modify `input_handlers.py` like this:

    class InventoryEventHandler(AskUserEventHandler):
        def on_render(self, console: tcod.Console) -> None:
            ...

            if number_of_items_in_inventory > 0:
                for i, item in enumerate(self.engine.player.inventory.items):
                    item_key = chr(ord("a") + i)
    -               console.print(x + 1, y + i + 1, f"({item_key}) {item.name}")

    +               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)
            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) -> Optional[ActionOrHandler]:
    -       """Return the action for the selected item."""
    -       return item.consumable.get_action(self.engine.player)
    +       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

    class InventoryDropHandler(InventoryEventHandler):
        ...

    class InventoryEventHandler(AskUserEventHandler):
        def on_render(self, console: tcod.Console) -> None:
            ...

            if number_of_items_in_inventory > 0:
                for i, item in enumerate(self.engine.player.inventory.items):
                    item_key = chr(ord("a") + i)
                    console.print(x + 1, y + i + 1, f"({item_key}) {item.name}")

                    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)
            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) -> Optional[ActionOrHandler]:
            """Return the action for the selected item."""
            return item.consumable.get_action(self.engine.player)
            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

    class InventoryDropHandler(InventoryEventHandler):
        ...

The first change is modifying the render function to display an “(E)” next to items that are equipped. Items that aren’t equipped are displayed the same way as before.

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 `get_action` method on the `Consumable` component, just like before. If it’s instead equippable, we call the `EquipAction`. If it’s neither, nothing happens.

Run the game now, you’ll be able to pick up and equip things. I recommend adjusting the values in `procgen.py` to make equipment spawn earlier and more often, just for testing purposes.

If you play around a bit, you might notice an odd bug: If the player drops something that’s equipped… it stays equipped! That doesn’t make sense, as dropping something should unequip it as well. Luckily, the fix is quite simple: We can adjust our `DropItem` action to unequip an item if it’s being dropped and it’s equipped. Make the following additions to `actions.py`:

    class DropItem(ItemAction):
        def perform(self) -> None:
    +       if self.entity.equipment.item_is_equipped(self.item):
    +           self.entity.equipment.toggle_equip(self.item)

            self.entity.inventory.drop(self.item)

    class DropItem(ItemAction):
        def perform(self) -> None:
            if self.entity.equipment.item_is_equipped(self.item):
                self.entity.equipment.toggle_equip(self.item)

            self.entity.inventory.drop(self.item)

One last thing we can do is give the player a bit of equipment to start. We’ll spawn a dagger and leather armor, and immediately add them to the player’s inventory.

    def new_game() -> Engine:
        ...

        engine.message_log.add_message(
            "Hello and welcome, adventurer, to yet another dungeon!", color.welcome_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)

        return engine

    def new_game() -> Engine:
        ...

        engine.message_log.add_message(
            "Hello and welcome, adventurer, to yet another dungeon!", color.welcome_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)

        return engine

As mentioned earlier, we pass `add_message=False` to signify not to add a message to the message log.

With that, we’ve reached the end of the tutorial! Thank you so much for following along, and be sure to check out the extras section. More will be added there over time. If you have a suggestion for an extra, let me know!

Be sure to check out the Roguelike Development Subreddit for help, for inspiration, or to share your progress.

Best of luck on your roguelike development journey!

If you want to see the code so far in its entirety, click here.