# Part 8 - Items and Inventory

## Quick refactors Link to heading

Once again, apologies to everyone reading this right now. After publishing the last two parts, there were once again a few refactors on code written in those parts, like at the beginning of part 6. Luckily, the changes are much less extensive this time.

`ai.py`

    ...
    import numpy as np  # type: ignore
    import tcod

    from actions import Action, MeleeAction, MovementAction, WaitAction
    -from components.base_component import BaseComponent

    if TYPE_CHECKING:
        from entity import Actor

    -class BaseAI(Action, BaseComponent):
    +class BaseAI(Action):
        entity: Actor

        def perform(self) -> None:
            raise NotImplementedError()
        ...

    ...
    import numpy as np  # type: ignore
    import tcod

    from actions import Action, MeleeAction, MovementAction, WaitAction
    from components.base_component import BaseComponent

    if TYPE_CHECKING:
        from entity import Actor

    class BaseAI(Action, BaseComponent):
    class BaseAI(Action):
        entity: Actor

        def perform(self) -> None:
            raise NotImplementedError()
        ...

`message_log.py`

    +from typing import Iterable, List, Reversible, Tuple
    -from typing import List, Reversible, Tuple
    import textwrap

    import tcod

    import color
    ...

    class MessageLog:
        ...

        def render(
            self, console: tcod.Console, x: int, y: int, width: int, height: int,
        ) -> None:
            """Render this log over the given area.

            `x`, `y`, `width`, `height` is the rectangular region to render onto
            the `console`.
            """
            self.render_messages(console, x, y, width, height, self.messages)

    +   @staticmethod
    +   def wrap(string: str, width: int) -> Iterable[str]:
    +       """Return a wrapped text message."""
    +       for line in string.splitlines():  # Handle newlines in messages.
    +           yield from textwrap.wrap(
    +               line, width, expand_tabs=True,
    +           )

    -   @staticmethod
    +   @classmethod
        def render_messages(
    +       cls,
            console: tcod.Console,
            x: int,
            y: int,
            width: int,
            height: int,
            messages: Reversible[Message],
        ) -> None:
            """Render the messages provided.

            The `messages` are rendered starting at the last message and working
            backwards.
            """
            y_offset = height - 1

            for message in reversed(messages):
    -           for line in reversed(textwrap.wrap(message.full_text, width)):
    +           for line in reversed(list(cls.wrap(message.full_text, width))):
                    console.print(x=x, y=y + y_offset, string=line, fg=message.fg)
                    y_offset -= 1
                    if y_offset < 0:
                        return  # No more space to print messages.

    from typing import Iterable, List, Reversible, Tuple
    from typing import List, Reversible, Tuple
    import textwrap

    import tcod

    import color
    ...

    class MessageLog:
        ...

        def render(
            self, console: tcod.Console, x: int, y: int, width: int, height: int,
        ) -> None:
            """Render this log over the given area.

            `x`, `y`, `width`, `height` is the rectangular region to render onto
            the `console`.
            """
            self.render_messages(console, x, y, width, height, self.messages)

        @staticmethod
        def wrap(string: str, width: int) -> Iterable[str]:
            """Return a wrapped text message."""
            for line in string.splitlines():  # Handle newlines in messages.
                yield from textwrap.wrap(
                    line, width, expand_tabs=True,
                )

        @staticmethod
        @classmethod
        def render_messages(
            cls,
            console: tcod.Console,
            x: int,
            y: int,
            width: int,
            height: int,
            messages: Reversible[Message],
        ) -> None:
            """Render the messages provided.

            The `messages` are rendered starting at the last message and working
            backwards.
            """
            y_offset = height - 1

            for message in reversed(messages):
                for line in reversed(textwrap.wrap(message.full_text, width)):
                for line in reversed(list(cls.wrap(message.full_text, width))):
                    console.print(x=x, y=y + y_offset, string=line, fg=message.fg)
                    y_offset -= 1
                    if y_offset < 0:
                        return  # No more space to print messages.

`game_map.py`

    class GameMap:
        ...
        )  # Tiles the player has seen before

    +   @property
    +   def gamemap(self) -> GameMap:
    +       return self

         @property
         def actors(self) -> Iterator[Actor]:
            ...

    class GameMap:
        ...
        )  # Tiles the player has seen before

        @property
        def gamemap(self) -> GameMap:
            return self

         @property
         def actors(self) -> Iterator[Actor]:
            ...

`entity.py`

    class Entity:
        """
        A generic object to represent players, enemies, items, etc.
        """

    -   gamemap: GameMap
    +   parent: GameMap

        def __init__(
            self,
    -       gamemap: Optional[GameMap] = None,
    +       parent: Optional[GameMap] = None,
            x: int = 0,
            y: int = 0,
            char: str = "?",
            color: Tuple[int, int, int] = (255, 255, 255),
            name: str = "<Unnamed>",
            blocks_movement: bool = False,
            render_order: RenderOrder = RenderOrder.CORPSE,
        ):
            self.x = x
            self.y = y
            self.char = char
            self.color = color
            self.name = name
            self.blocks_movement = blocks_movement
            self.render_order = render_order
    -       if gamemap:
    -           # If gamemap isn't provided now then it will be set later.
    -           self.gamemap = gamemap
    -           gamemap.entities.add(self)
    +       if parent:
    +           # If parent isn't provided now then it will be set later.
    +           self.parent = parent
    +           parent.entities.add(self)

    +   @property
    +   def gamemap(self) -> GameMap:
    +       return self.parent.gamemap

        def spawn(self: T, gamemap: GameMap, x: int, y: int) -> T:
            """Spawn a copy of this instance at the given location."""
            clone = copy.deepcopy(self)
            clone.x = x
            clone.y = y
    -       clone.gamemap = gamemap
    +       clone.parent = gamemap
            gamemap.entities.add(clone)
            return clone

        def place(self, x: int, y: int, gamemap: Optional[GameMap] = None) -> None:
            """Place this entity at a new location.  Handles moving across GameMaps."""
            self.x = x
            self.y = y
            if gamemap:
    -           if hasattr(self, "gamemap"):  # Possibly uninitialized.
    -               self.gamemap.entities.remove(self)
    -           self.gamemap = gamemap
    +           if hasattr(self, "parent"):  # Possibly uninitialized.
    +               if self.parent is self.gamemap:
    +                   self.gamemap.entities.remove(self)
    +           self.parent = gamemap
                gamemap.entities.add(self)

        def move(self, dx: int, dy: int) -> None:
            # Move the entity by a given amount
            self.x += dx
            self.y += dy

    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],
            fighter: Fighter
        ):
            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.fighter = fighter
    -       self.fighter.entity = self
    +       self.fighter.parent = self

        @property
        def is_alive(self) -> bool:
            """Returns True as long as this actor can perform actions."""
            return bool(self.ai)

    class Entity:
        """
        A generic object to represent players, enemies, items, etc.
        """

        gamemap: GameMap
        parent: GameMap

        def __init__(
            self,
            gamemap: Optional[GameMap] = None,
            parent: Optional[GameMap] = None,
            x: int = 0,
            y: int = 0,
            char: str = "?",
            color: Tuple[int, int, int] = (255, 255, 255),
            name: str = "<Unnamed>",
            blocks_movement: bool = False,
            render_order: RenderOrder = RenderOrder.CORPSE,
        ):
            self.x = x
            self.y = y
            self.char = char
            self.color = color
            self.name = name
            self.blocks_movement = blocks_movement
            self.render_order = render_order
            if gamemap:
                # If gamemap isn't provided now then it will be set later.
                self.gamemap = gamemap
                gamemap.entities.add(self)
            if parent:
                # If parent isn't provided now then it will be set later.
                self.parent = parent
                parent.entities.add(self)

        @property
        def gamemap(self) -> GameMap:
            return self.parent.gamemap

        def spawn(self: T, gamemap: GameMap, x: int, y: int) -> T:
            """Spawn a copy of this instance at the given location."""
            clone = copy.deepcopy(self)
            clone.x = x
            clone.y = y
            clone.gamemap = gamemap
            clone.parent = gamemap
            gamemap.entities.add(clone)
            return clone

        def place(self, x: int, y: int, gamemap: Optional[GameMap] = None) -> None:
            """Place this entity at a new location.  Handles moving across GameMaps."""
            self.x = x
            self.y = y
            if gamemap:
                if hasattr(self, "gamemap"):  # Possibly uninitialized.
                    self.gamemap.entities.remove(self)
                self.gamemap = gamemap
                if hasattr(self, "parent"):  # Possibly uninitialized.
                    if self.parent is self.gamemap:
                        self.gamemap.entities.remove(self)
                self.parent = gamemap
                gamemap.entities.add(self)

        def move(self, dx: int, dy: int) -> None:
            # Move the entity by a given amount
            self.x += dx
            self.y += dy

    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],
            fighter: Fighter
        ):
            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.fighter = fighter
            self.fighter.entity = self
            self.fighter.parent = self

        @property
        def is_alive(self) -> bool:
            """Returns True as long as this actor can perform actions."""
            return bool(self.ai)

`base_component.py`

    from __future__ import annotations

    from typing import TYPE_CHECKING

    if TYPE_CHECKING:
        from engine import Engine
        from entity import Entity
    +   from game_map import GameMap

    class BaseComponent:
    -   entity: Entity  # Owning entity instance.
    +   parent: Entity  # Owning entity instance.

    +   @property
    +   def gamemap(self) -> GameMap:
    +       return self.parent.gamemap

        @property
        def engine(self) -> Engine:
    -       return self.entity.gamemap.engine
    +       return self.gamemap.engine

    from __future__ import annotations

    from typing import TYPE_CHECKING

    if TYPE_CHECKING:
        from engine import Engine
        from entity import Entity
        from game_map import GameMap

    class BaseComponent:
        entity: Entity  # Owning entity instance.
        parent: Entity  # Owning entity instance.

        @property
        def gamemap(self) -> GameMap:
            return self.parent.gamemap

        @property
        def engine(self) -> Engine:
            return self.entity.gamemap.engine
            return self.gamemap.engine

`fighter.py`

    class Fighter(BaseComponent):
    -   entity: Actor
    +   parent: Actor

        def __init__(self, hp: int, defense: int, power: int):
            self.max_hp = hp
            self._hp = hp
            self.defense = defense
            self.power = 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.entity.ai:
    +       if self._hp == 0 and self.parent.ai:
                self.die()

        def die(self) -> None:
    -       if self.engine.player is self.entity:
    +       if self.engine.player is self.parent:
                death_message = "You died!"
                death_message_color = color.player_die
                self.engine.event_handler = GameOverEventHandler(self.engine)
            else:
    -           death_message = f"{self.entity.name} is dead!"
    +           death_message = f"{self.parent.name} is dead!"
                death_message_color = color.enemy_die

    +       self.parent.char = "%"
    +       self.parent.color = (191, 0, 0)
    +       self.parent.blocks_movement = False
    +       self.parent.ai = None
    +       self.parent.name = f"remains of {self.parent.name}"
    +       self.parent.render_order = RenderOrder.CORPSE
    -       self.entity.char = "%"
    -       self.entity.color = (191, 0, 0)
    -       self.entity.blocks_movement = False
    -       self.entity.ai = None
    -       self.entity.name = f"remains of {self.entity.name}"
    -       self.entity.render_order = RenderOrder.CORPSE

            self.engine.message_log.add_message(death_message, death_message_color)

    class Fighter(BaseComponent):
        entity: Actor
        parent: Actor

        def __init__(self, hp: int, defense: int, power: int):
            self.max_hp = hp
            self._hp = hp
            self.defense = defense
            self.power = 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.entity.ai:
            if self._hp == 0 and self.parent.ai:
                self.die()

        def die(self) -> None:
            if self.engine.player is self.entity:
            if self.engine.player is self.parent:
                death_message = "You died!"
                death_message_color = color.player_die
                self.engine.event_handler = GameOverEventHandler(self.engine)
            else:
                death_message = f"{self.entity.name} is dead!"
                death_message = f"{self.parent.name} is dead!"
                death_message_color = color.enemy_die

            self.parent.char = "%"
            self.parent.color = (191, 0, 0)
            self.parent.blocks_movement = False
            self.parent.ai = None
            self.parent.name = f"remains of {self.parent.name}"
            self.parent.render_order = RenderOrder.CORPSE
            self.entity.char = "%"
            self.entity.color = (191, 0, 0)
            self.entity.blocks_movement = False
            self.entity.ai = None
            self.entity.name = f"remains of {self.entity.name}"
            self.entity.render_order = RenderOrder.CORPSE

            self.engine.message_log.add_message(death_message, death_message_color)

## Part 8 Link to heading

So far, our game has movement, dungeon exploring, combat, and AI (okay, we’re stretching the meaning of “intelligence” in artificial intelligence to its limits, but bear with me here). Now it’s time for another staple of the roguelike genre: items! Why would our rogue venture into the dungeons of doom if not for some sweet loot, after all?

In this part of the tutorial, we’ll achieve a few things: a working inventory, and a functioning healing potion. The next part will add more items that can be picked up, but for now, just the healing potion will suffice.

For this part, we’ll need four more colors. Let’s get adding those out of the way now. Open up `color.py` and add these colors:

    white = (0xFF, 0xFF, 0xFF)
    black = (0x0, 0x0, 0x0)

    player_atk = (0xE0, 0xE0, 0xE0)
    enemy_atk = (0xFF, 0xC0, 0xC0)

    player_die = (0xFF, 0x30, 0x30)
    enemy_die = (0xFF, 0xA0, 0x30)

    +invalid = (0xFF, 0xFF, 0x00)
    +impossible = (0x80, 0x80, 0x80)
    +error = (0xFF, 0x40, 0x40)

    welcome_text = (0x20, 0xA0, 0xFF)
    +health_recovered = (0x0, 0xFF, 0x0)

    bar_text = white
    bar_filled = (0x0, 0x60, 0x0)
    bar_empty = (0x40, 0x10, 0x10)

    white = (0xFF, 0xFF, 0xFF)
    black = (0x0, 0x0, 0x0)

    player_atk = (0xE0, 0xE0, 0xE0)
    enemy_atk = (0xFF, 0xC0, 0xC0)

    player_die = (0xFF, 0x30, 0x30)
    enemy_die = (0xFF, 0xA0, 0x30)

    invalid = (0xFF, 0xFF, 0x00)
    impossible = (0x80, 0x80, 0x80)
    error = (0xFF, 0x40, 0x40)

    welcome_text = (0x20, 0xA0, 0xFF)
    health_recovered = (0x0, 0xFF, 0x0)

    bar_text = white
    bar_filled = (0x0, 0x60, 0x0)
    bar_empty = (0x40, 0x10, 0x10)

These will become useful shortly.

There’s another thing we can knock out right now that we’ll use later: The ability for a `Fighter` component to recover health, and the ability to take damage directly (without the defense modifier). We won’t use the damage function this chapter, but since the two functions are effectively opposites, we can get writing it over with now.

Open up `fighter.py` and add these two functions:

    class Fighter:
        ...
    +   def heal(self, amount: int) -> int:
    +       if self.hp == self.max_hp:
    +           return 0

    +       new_hp_value = self.hp + amount

    +       if new_hp_value > self.max_hp:
    +           new_hp_value = self.max_hp

    +       amount_recovered = new_hp_value - self.hp

    +       self.hp = new_hp_value

    +       return amount_recovered

    +   def take_damage(self, amount: int) -> None:
    +       self.hp -= amount

    class Fighter:
        ...
        def heal(self, amount: int) -> int:
            if self.hp == self.max_hp:
                return 0

            new_hp_value = self.hp + amount

            if new_hp_value > self.max_hp:
                new_hp_value = self.max_hp

            amount_recovered = new_hp_value - self.hp

            self.hp = new_hp_value

            return amount_recovered

        def take_damage(self, amount: int) -> None:
            self.hp -= amount

`heal` will restore a certain amount of HP, up to the maximum, and return the amount that was healed. If the entity’s health is at full, then just return 0. The function that handles this should display an error if the returned amount is 0, since the entity can’t be healed.

One thing we’re going to need is a way to not consume an item or take a turn if something goes wrong during the process. For our health potion, think about what should happen if the player declares they want to use a health potion, but their health is already full. What should happen?

We could just consume the potion anyway, and have it go to waste, but if you’ve played a game that does that, you know how frustrating it can be, especially if the player clicked the health potion on accident. A better way would be to warn the user that they’re trying to do something that makes no sense, and save the player from wasting both the potion and their turn.

But how can we achieve that? We’ll discuss it a bit more later on, but the idea is that if we do something impossible, we should raise an exception. Which one? Well, we can define a custom exception, which can give us details on what happened. Create a new file called `exceptions.py` and put the following class into it:

    class Impossible(Exception):
        """Exception raised when an action is impossible to be performed.

        The reason is given as the exception message.
        """

… And that’s it! When we write `raise Impossible("An exception message")` in our program, the `Impossible` exception will be raised, with the given message.

So what do we do with the raised exception? Well, we should catch it! But where?

Let’s modify the `main.py` file to catch the exceptions, like this:

    #!/usr/bin/env python3
    import copy
    +import traceback

    import tcod

    ...

                context.present(root_console)

    +           try:
    +               for event in tcod.event.wait():
    +                   context.convert_event(event)
    +                   engine.event_handler.handle_events(event)
    +           except Exception:  # Handle exceptions in game.
    +               traceback.print_exc()  # Print error to stderr.
    +               # Then print the error to the message log.
    +               engine.message_log.add_message(traceback.format_exc(), color.error)
    -           engine.event_handler.handle_events(context)

    #!/usr/bin/env python3
    import copy
    import traceback

    import tcod

    ...

                context.present(root_console)

                try:
                    for event in tcod.event.wait():
                        context.convert_event(event)
                        engine.event_handler.handle_events(event)
                except Exception:  # Handle exceptions in game.
                    traceback.print_exc()  # Print error to stderr.
                    # Then print the error to the message log.
                    engine.message_log.add_message(traceback.format_exc(), color.error)
                engine.event_handler.handle_events(context)

This is a generalized, catch all solution, which will print all exceptions to the message log, not just instances of `Impossible`. This can be helpful for debugging your game, or getting error reports from users.

However, this solution doesn’t mesh with our current implementation of the `EventHandler`. `EventHandler` currently loops through the events and converts them (to get the mouse information). We’ll need to edit a few things in `input_handlers.py` to get back on track.

    import tcod

    +from actions import (
    +   Action,
    +   BumpAction,
    +   EscapeAction,
    +   WaitAction
    +)
    +import color
    +import exceptions
    -from actions import Action, BumpAction, EscapeAction, WaitAction

    class EventHandler(tcod.event.EventDispatch[Action]):
        def __init__(self, engine: Engine):
            self.engine = engine

    +   def handle_events(self, event: tcod.event.Event) -> None:
    +       self.handle_action(self.dispatch(event))

    +   def handle_action(self, action: Optional[Action]) -> bool:
    +       """Handle actions returned from event methods.

    +       Returns True if the action will advance a turn.
    +       """
    +       if action is None:
    +           return False

    +       try:
    +           action.perform()
    +       except exceptions.Impossible as exc:
    +           self.engine.message_log.add_message(exc.args[0], color.impossible)
    +           return False  # Skip enemy turn on exceptions.

    +       self.engine.handle_enemy_turns()

    +       self.engine.update_fov()
    +       return True

    -   def handle_events(self, context: tcod.context.Context) -> None:
    -       for event in tcod.event.wait():
    -           context.convert_event(event)
    -           self.dispatch(event)

        ...

    class MainGameEventHandler(EventHandler):
    -   def handle_events(self, context: tcod.context.Context) -> None:
    -       for event in tcod.event.wait():
    -           context.convert_event(event)

    -           action = self.dispatch(event)

    -           if action is None:
    -               continue

    -           action.perform()

    -           self.engine.handle_enemy_turns()

    -           self.engine.update_fov()  # Update the FOV before the players next action.

        ...

    class GameOverEventHandler(EventHandler):
    -   def handle_events(self, context: tcod.context.Context) -> None:
    -       for event in tcod.event.wait():
    -           action = self.dispatch(event)

    -           if action is None:
    -               continue

    -           action.perform()

    -   def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[Action]:
    -       action: Optional[Action] = None

    -       key = event.sym

    -       if key == tcod.event.K_ESCAPE:
    -           action = EscapeAction(self.engine.player)

    -       # No valid key was pressed
    -       return action
    +   def ev_keydown(self, event: tcod.event.KeyDown) -> None:
    +       if event.sym == tcod.event.K_ESCAPE:
    +           raise SystemExit()

    import tcod

    from actions import (
        Action,
        BumpAction,
        EscapeAction,
        WaitAction
    )
    import color
    import exceptions
    from actions import Action, BumpAction, EscapeAction, WaitAction

    class EventHandler(tcod.event.EventDispatch[Action]):
        def __init__(self, engine: Engine):
            self.engine = engine

        def handle_events(self, event: tcod.event.Event) -> None:
            self.handle_action(self.dispatch(event))

        def handle_action(self, action: Optional[Action]) -> bool:
            """Handle actions returned from event methods.

            Returns True if the action will advance a turn.
            """
            if action is None:
                return False

            try:
                action.perform()
            except exceptions.Impossible as exc:
                self.engine.message_log.add_message(exc.args[0], color.impossible)
                return False  # Skip enemy turn on exceptions.

            self.engine.handle_enemy_turns()

            self.engine.update_fov()
            return True

        def handle_events(self, context: tcod.context.Context) -> None:
            for event in tcod.event.wait():
                context.convert_event(event)
                self.dispatch(event)

        ...

    class MainGameEventHandler(EventHandler):
        def handle_events(self, context: tcod.context.Context) -> None:
            for event in tcod.event.wait():
                context.convert_event(event)

                action = self.dispatch(event)

                if action is None:
                    continue

                action.perform()

                self.engine.handle_enemy_turns()

                self.engine.update_fov()  # Update the FOV before the players next action.

        ...

    class GameOverEventHandler(EventHandler):
        def handle_events(self, context: tcod.context.Context) -> None:
            for event in tcod.event.wait():
                action = self.dispatch(event)

                if action is None:
                    continue

                action.perform()

        def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[Action]:
            action: Optional[Action] = None

            key = event.sym

            if key == tcod.event.K_ESCAPE:
                action = EscapeAction(self.engine.player)

            # No valid key was pressed
            return action
        def ev_keydown(self, event: tcod.event.KeyDown) -> None:
            if event.sym == tcod.event.K_ESCAPE:
                raise SystemExit()

Now that we’ve got our event handlers updated, let’s actually put the `Impossible` exception to good use. We can start by editing `actions.py` to make use of it when the player tries to move into an invalid area:

    ...
    import color
    +import exceptions

    if TYPE_CHECKING:
        ...

    class MeleeAction(ActionWithDirection):
        def perform(self) -> None:
            target = self.target_actor
            if not target:
    -           return  # No entity to attack.
    +           raise exceptions.Impossible("Nothing to attack.")

            ...

    class MovementAction(ActionWithDirection):
        def perform(self) -> None:
            dest_x, dest_y = self.dest_xy

            if not self.engine.game_map.in_bounds(dest_x, dest_y):
    +           # Destination is out of bounds.
    +           raise exceptions.Impossible("That way is blocked.")
    -           return  # Destination is out of bounds.
             if not self.engine.game_map.tiles["walkable"][dest_x, dest_y]:
    +           # Destination is blocked by a tile.
    +           raise exceptions.Impossible("That way is blocked.")
    -           return  # Destination is blocked by a tile.
             if self.engine.game_map.get_blocking_entity_at_location(dest_x, dest_y):
    +           # Destination is blocked by an entity.
    +           raise exceptions.Impossible("That way is blocked.")
    -           return  # Destination is blocked by an entity.

    ...
    import color
    import exceptions

    if TYPE_CHECKING:
        ...

    class MeleeAction(ActionWithDirection):
        def perform(self) -> None:
            target = self.target_actor
            if not target:
                return  # No entity to attack.
                raise exceptions.Impossible("Nothing to attack.")

            ...

    class MovementAction(ActionWithDirection):
        def perform(self) -> None:
            dest_x, dest_y = self.dest_xy

            if not self.engine.game_map.in_bounds(dest_x, dest_y):
                # Destination is out of bounds.
                raise exceptions.Impossible("That way is blocked.")
                return  # Destination is out of bounds.
             if not self.engine.game_map.tiles["walkable"][dest_x, dest_y]:
                # Destination is blocked by a tile.
                raise exceptions.Impossible("That way is blocked.")
                return  # Destination is blocked by a tile.
             if self.engine.game_map.get_blocking_entity_at_location(dest_x, dest_y):
                # Destination is blocked by an entity.
                raise exceptions.Impossible("That way is blocked.")
                return  # Destination is blocked by an entity.

Now, if you try moving into a wall, you’ll get a message in the log, and the player’s turn won’t be wasted.

So what about when the enemies try doing something impossible? You might want to know when that happens for debugging purposes, but during normal execution of our game, we can simply ignore it, and have the enemy skip their turn. To do this, modify `engine.py` like this:

    ...
    from tcod.map import compute_fov

    +import exceptions
    from input_handlers import MainGameEventHandler
    from message_log import MessageLog

    ...

        def handle_enemy_turns(self) -> None:
            for entity in set(self.game_map.actors) - {self.player}:
                if entity.ai:
    +               try:
    +                   entity.ai.perform()
    +               except exceptions.Impossible:
    +                   pass  # Ignore impossible action exceptions from AI.
    -               entity.ai.perform()

    ...
    from tcod.map import compute_fov

    import exceptions
    from input_handlers import MainGameEventHandler
    from message_log import MessageLog

    ...

        def handle_enemy_turns(self) -> None:
            for entity in set(self.game_map.actors) - {self.player}:
                if entity.ai:
                    try:
                        entity.ai.perform()
                    except exceptions.Impossible:
                        pass  # Ignore impossible action exceptions from AI.
                    entity.ai.perform()

This is great and all, but wasn’t this chapter supposed to be about implementing items? And, yes, that’s true, and we’re going to transition to that now, but it’ll be helpful to have a way to stop the player from wasting a turn in just a moment.

The way we’ll implement our health potions will be similar to how we implemented enemies: We’ll create a component that holds the functionality we want, and we’ll create a subclass of `Entity` that holds the relevant component. From the `Consumable` component, we can create subclasses that implement the specific functionality we want for each item. In this case, it’ll be a health potion, but in the next chapter, we’ll be implementing other types of consumables, so we’ll want to stay flexible.

In the `components` directory, create a file called `consumable.py` and fill it with the following contents:

    from __future__ import annotations

    from typing import Optional, TYPE_CHECKING

    import actions
    import color
    from components.base_component import BaseComponent
    from exceptions import Impossible

    if TYPE_CHECKING:
        from entity import Actor, Item

    class Consumable(BaseComponent):
        parent: Item

        def get_action(self, consumer: Actor) -> Optional[actions.Action]:
            """Try to return the action for this item."""
            return actions.ItemAction(consumer, self.parent)

        def activate(self, action: actions.ItemAction) -> None:
            """Invoke this items ability.

            `action` is the context for this activation.
            """
            raise NotImplementedError()

    class HealingConsumable(Consumable):
        def __init__(self, amount: int):
            self.amount = amount

        def activate(self, action: actions.ItemAction) -> None:
            consumer = action.entity
            amount_recovered = consumer.fighter.heal(self.amount)

            if amount_recovered > 0:
                self.engine.message_log.add_message(
                    f"You consume the {self.parent.name}, and recover {amount_recovered} HP!",
                    color.health_recovered,
                )
            else:
                raise Impossible(f"Your health is already full.")

The `Consumable` class knows its parent, and it defines two methods: `get_action` and `activate`.

`get_action` gets `ItemAction`, which we haven’t defined just yet (we will soon). Subclasses can override this to provide more information to `ItemAction` if needed, such as the position of a potential target (this will be useful when we have ranged targeting).

`activate` is just an abstract method, it’s up to the subclasses to define their own implementation. The subclasses should call this method when they’re trying to actually cause the effect that they’ve defined for themselves (healing for healing potions, damage for lightning scrolls, etc.).

`HealingConsumable` is initialized with an `amount`, which is how much the user will be healed when using the item. The `activate` function calls `fighter.heal`, and logs a message to the message log, if the entity recovered health. If not (because the user had full health already), we return that `Impossible` exception we defined earlier. This will give us a message in the log that the player’s health is already full, and it won’t waste the health potion.

So what does this component get attached to? In order to create our health potions, we can create another subclass of `Entity`, which will represent non-actor items. Open up `entity.py` and add the following class:

    from render_order import RenderOrder

    if TYPE_CHECKING:
        from components.ai import BaseAI
    +   from components.consumable import Consumable
        from components.fighter import Fighter
        from game_map import GameMap

    ...

        ...
        @property
        def is_alive(self) -> bool:
            """Returns True as long as this actor can perform actions."""
            return bool(self.ai)

    +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,
    +   ):
    +       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

    from render_order import RenderOrder

    if TYPE_CHECKING:
        from components.ai import BaseAI
        from components.consumable import Consumable
        from components.fighter import Fighter
        from game_map import GameMap

    ...

        ...
        @property
        def is_alive(self) -> bool:
            """Returns True as long as this actor can perform actions."""
            return bool(self.ai)

    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,
        ):
            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

`Item` isn’t too different from `Actor`, except instead of implementing `fighter` and `ai`, it does `consumable`. When we create an item, we’ll assign the `consumable`, which will determine what actually happens when the item gets used.

The next thing we need to implement that we used in the `Consumable` class is the `ItemAction` class. Open up `actions.py` and put the following:

    if TYPE_CHECKING:
        from engine import Engine
    -   from entity import Actor, Entity
    +   from entity import Actor, Entity, Item
    ...

    class Action:
        ...

    +class ItemAction(Action):
    +   def __init__(
    +       self, entity: Actor, item: Item, target_xy: Optional[Tuple[int, int]] = None
    +   ):
    +       super().__init__(entity)
    +       self.item = item
    +       if not target_xy:
    +           target_xy = entity.x, entity.y
    +       self.target_xy = target_xy

    +   @property
    +   def target_actor(self) -> Optional[Actor]:
    +       """Return the actor at this actions destination."""
    +       return self.engine.game_map.get_actor_at_location(*self.target_xy)

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

    class EscapeAction(Action):
        ...

    if TYPE_CHECKING:
        from engine import Engine
        from entity import Actor, Entity
        from entity import Actor, Entity, Item
    ...

    class Action:
        ...

    class ItemAction(Action):
        def __init__(
            self, entity: Actor, item: Item, target_xy: Optional[Tuple[int, int]] = None
        ):
            super().__init__(entity)
            self.item = item
            if not target_xy:
                target_xy = entity.x, entity.y
            self.target_xy = target_xy

        @property
        def target_actor(self) -> Optional[Actor]:
            """Return the actor at this actions destination."""
            return self.engine.game_map.get_actor_at_location(*self.target_xy)

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

    class EscapeAction(Action):
        ...

`ItemAction` takes several arguments in its `__init__` function: `entity`, which is the entity using the item, `item`, which is the item itself, and `target_xy`, which is the x and y coordinates of the “target” of the item, if there is one. We won’t actually use this in this chapter, but it’ll come in handy soon.

`target_actor` gets the actor at the target location. Again, we won’t actually use it this chapter, since health potions don’t “target” anything.

`perform` activates the consumable, with its `activate` method we defined earlier.

To utilize our new `Item`, let’s add the health potion to `entity_factories.py`:

    from components.ai import HostileEnemy
    +from components.consumable import HealingConsumable
    from components.fighter import Fighter
    -from entity import Actor
    +from entity import Actor, Item

    player = Actor(
        char="@",
        color=(255, 255, 255),
        name="Player",
        ai_cls=HostileEnemy,
        fighter=Fighter(hp=30, defense=2, power=5),
    )

    orc = Actor(
        char="o",
        color=(63, 127, 63),
        name="Orc",
        ai_cls=HostileEnemy,
        fighter=Fighter(hp=10, defense=0, power=3),
    )
    troll = Actor(
        char="T",
        color=(0, 127, 0),
        name="Troll",
        ai_cls=HostileEnemy,
        fighter=Fighter(hp=16, defense=1, power=4),
    )

    +health_potion = Item(
    +   char="!",
    +   color=(127, 0, 255),
    +   name="Health Potion",
    +   consumable=HealingConsumable(amount=4),
    +)

    from components.ai import HostileEnemy
    from components.consumable import HealingConsumable
    from components.fighter import Fighter
    from entity import Actor
    from entity import Actor, Item

    player = Actor(
        char="@",
        color=(255, 255, 255),
        name="Player",
        ai_cls=HostileEnemy,
        fighter=Fighter(hp=30, defense=2, power=5),
    )

    orc = Actor(
        char="o",
        color=(63, 127, 63),
        name="Orc",
        ai_cls=HostileEnemy,
        fighter=Fighter(hp=10, defense=0, power=3),
    )
    troll = Actor(
        char="T",
        color=(0, 127, 0),
        name="Troll",
        ai_cls=HostileEnemy,
        fighter=Fighter(hp=16, defense=1, power=4),
    )

    health_potion = Item(
        char="!",
        color=(127, 0, 255),
        name="Health Potion",
        consumable=HealingConsumable(amount=4),
    )

We’re defining a new entity type, called `health_potion` (no surprises there), and utilizing the `Item` and `HealingConsumable` classes we just wrote. The health potion will recover 4 HP of the user’s health. Feel free to adjust that value however you see fit.

Alright, we’re now ready to put some health potions in the dungeon. As you may have already guessed, we’ll need to adjust the `generate_dungeon` and `place_entities` functions in `procgen.py` to actually put the potions in. Edit `procgen.py` like this:

    def place_entities(
    -   room: RectangularRoom, dungeon: GameMap, maximum_monsters: int,
    +   room: RectangularRoom, dungeon: GameMap, maximum_monsters: int, maximum_items: int
    ) -> None:
        number_of_monsters = random.randint(0, maximum_monsters)
    +   number_of_items = random.randint(0, maximum_items)

        for i in range(number_of_monsters):
            x = random.randint(room.x1 + 1, room.x2 - 1)
            y = random.randint(room.y1 + 1, room.y2 - 1)

            if not any(entity.x == x and entity.y == y for entity in dungeon.entities):
                if random.random() < 0.8:
                    entity_factories.orc.spawn(dungeon, x, y)
                else:
                    entity_factories.troll.spawn(dungeon, x, y)

    +   for i in range(number_of_items):
    +       x = random.randint(room.x1 + 1, room.x2 - 1)
    +       y = random.randint(room.y1 + 1, room.y2 - 1)

    +       if not any(entity.x == x and entity.y == y for entity in dungeon.entities):
    +           entity_factories.health_potion.spawn(dungeon, x, y)

    def tunnel_between(
        ...

    def generate_dungeon(
        map_width: int,
        map_height: int,
        max_monsters_per_room: int,
    +   max_items_per_room: int,
        engine: Engine,
    ) -> GameMap:
        """Generate a new dungeon map."""
        ...

            ...
    -       place_entities(new_room, dungeon, max_monsters_per_room)
    +       place_entities(new_room, dungeon, max_monsters_per_room, max_items_per_room)

    def place_entities(
        room: RectangularRoom, dungeon: GameMap, maximum_monsters: int,
        room: RectangularRoom, dungeon: GameMap, maximum_monsters: int, maximum_items: int
    ) -> None:
        number_of_monsters = random.randint(0, maximum_monsters)
        number_of_items = random.randint(0, maximum_items)

        for i in range(number_of_monsters):
            x = random.randint(room.x1 + 1, room.x2 - 1)
            y = random.randint(room.y1 + 1, room.y2 - 1)

            if not any(entity.x == x and entity.y == y for entity in dungeon.entities):
                if random.random() < 0.8:
                    entity_factories.orc.spawn(dungeon, x, y)
                else:
                    entity_factories.troll.spawn(dungeon, x, y)

        for i in range(number_of_items):
            x = random.randint(room.x1 + 1, room.x2 - 1)
            y = random.randint(room.y1 + 1, room.y2 - 1)

            if not any(entity.x == x and entity.y == y for entity in dungeon.entities):
                entity_factories.health_potion.spawn(dungeon, x, y)

    def tunnel_between(
        ...

    def generate_dungeon(
        map_width: int,
        map_height: int,
        max_monsters_per_room: int,
        max_items_per_room: int,
        engine: Engine,
    ) -> GameMap:
        """Generate a new dungeon map."""
        ...

            ...
            place_entities(new_room, dungeon, max_monsters_per_room)
            place_entities(new_room, dungeon, max_monsters_per_room, max_items_per_room)

We’re doing essentially the same thing we did to create our enemies: Giving a maximum possible number for the number of items in each room, selecting a random number between that and 0, and spawning the items in a random spot in the room, assuming nothing else already exists there.

Lastly, to make the health potions appear, we need to update our call in `main.py` to `generate_dungeon`, since we’ve added the `max_items_per_room` argument. Open up `main.py` and add the following lines:

        ...
        max_monsters_per_room = 2
    +   max_items_per_room = 2

        tileset = tcod.tileset.load_tilesheet(
            "dejavu10x10_gs_tc.png", 32, 8, tcod.tileset.CHARMAP_TCOD
        )

        player = copy.deepcopy(entity_factories.player)

        engine = Engine(player=player)

        engine.game_map = generate_dungeon(
            max_rooms=max_rooms,
            room_min_size=room_min_size,
            room_max_size=room_max_size,
            map_width=map_width,
            map_height=map_height,
            max_monsters_per_room=max_monsters_per_room,
    +       max_items_per_room=max_items_per_room,
            engine=engine,
        )
        ...

        ...
        max_monsters_per_room = 2
        max_items_per_room = 2

        tileset = tcod.tileset.load_tilesheet(
            "dejavu10x10_gs_tc.png", 32, 8, tcod.tileset.CHARMAP_TCOD
        )

        player = copy.deepcopy(entity_factories.player)

        engine = Engine(player=player)

        engine.game_map = generate_dungeon(
            max_rooms=max_rooms,
            room_min_size=room_min_size,
            room_max_size=room_max_size,
            map_width=map_width,
            map_height=map_height,
            max_monsters_per_room=max_monsters_per_room,
            max_items_per_room=max_items_per_room,
            engine=engine,
        )
        ...

Run the project now, and you should see a few health potions laying around. Success! Well, not really…

Those potions don’t do our rogue any good right now, because we can’t pick them up! We need to add the items to an inventory before we can start chugging them.

To implement the inventory, we can create a new component, called `Inventory`. Create a new file in the `components` directory, called `inventory.py`, and add this class:

    from __future__ import annotations

    from typing import List, TYPE_CHECKING

    from components.base_component import BaseComponent

    if TYPE_CHECKING:
        from entity import Actor, Item

    class Inventory(BaseComponent):
        parent: Actor

        def __init__(self, capacity: int):
            self.capacity = capacity
            self.items: List[Item] = []

        def drop(self, item: Item) -> None:
            """
            Removes an item from the inventory and restores it to the game map, at the player's current location.
            """
            self.items.remove(item)
            item.place(self.parent.x, self.parent.y, self.gamemap)

            self.engine.message_log.add_message(f"You dropped the {item.name}.")

The `Inventory` class belongs to an `Actor`, and its initialized with a `capacity`, which is the maximum number of items that can be held, and the `items` list, which will actually hold the items. The `drop` method, as the name implies, will be called when the player decides to drop something out of the inventory, back onto the ground.

Let’s add this new component to our `Actor` class. Open up `entity.py` and modify `Actor` like this:

    ...
    if TYPE_CHECKING:
        from components.ai import BaseAI
        from components.consumable import Consumable
        from components.fighter import Fighter
    +   from components.inventory import Inventory
        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],
            fighter: Fighter,
    +       inventory: Inventory,
        ):
            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.fighter = fighter
            self.fighter.parent = self

    +       self.inventory = inventory
    +       self.inventory.parent = self

    ...
    if TYPE_CHECKING:
        from components.ai import BaseAI
        from components.consumable import Consumable
        from components.fighter import Fighter
        from components.inventory import Inventory
        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],
            fighter: Fighter,
            inventory: Inventory,
        ):
            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.fighter = fighter
            self.fighter.parent = self

            self.inventory = inventory
            self.inventory.parent = self

Now, each actor will have their own inventory. Our tutorial won’t implement monster inventories (they won’t pick up, hold, or use items), but hopefully this setup gives you a good starting place to implement it yourself, if you so choose.

We’ll need to update `entity_factories.py` to take the new component into account:

    from components.ai import HostileEnemy
    from components.consumable import HealingConsumable
    from components.fighter import Fighter
    +from components.inventory import Inventory
    from entity import Actor, Item

    player = Actor(
        char="@",
        color=(255, 255, 255),
        name="Player",
        ai_cls=HostileEnemy,
        fighter=Fighter(hp=30, defense=2, power=5),
    +   inventory=Inventory(capacity=26),
    )

    orc = Actor(
        char="o",
        color=(63, 127, 63),
        name="Orc",
        ai_cls=HostileEnemy,
        fighter=Fighter(hp=10, defense=0, power=3),
    +   inventory=Inventory(capacity=0),
    )
    troll = Actor(
        char="T",
        color=(0, 127, 0),
        name="Troll",
        ai_cls=HostileEnemy,
        fighter=Fighter(hp=16, defense=1, power=4),
    +   inventory=Inventory(capacity=0),
    )

    health_potion = Item(
        char="!",
        color=(127, 0, 255),
        name="Health Potion",
        consumable=HealingConsumable(amount=4),
    )

    from components.ai import HostileEnemy
    from components.consumable import HealingConsumable
    from components.fighter import Fighter
    from components.inventory import Inventory
    from entity import Actor, Item

    player = Actor(
        char="@",
        color=(255, 255, 255),
        name="Player",
        ai_cls=HostileEnemy,
        fighter=Fighter(hp=30, defense=2, power=5),
        inventory=Inventory(capacity=26),
    )

    orc = Actor(
        char="o",
        color=(63, 127, 63),
        name="Orc",
        ai_cls=HostileEnemy,
        fighter=Fighter(hp=10, defense=0, power=3),
        inventory=Inventory(capacity=0),
    )
    troll = Actor(
        char="T",
        color=(0, 127, 0),
        name="Troll",
        ai_cls=HostileEnemy,
        fighter=Fighter(hp=16, defense=1, power=4),
        inventory=Inventory(capacity=0),
    )

    health_potion = Item(
        char="!",
        color=(127, 0, 255),
        name="Health Potion",
        consumable=HealingConsumable(amount=4),
    )

We’re setting the player’s inventory to 26, because when we implement the menu system, each letter in the (English) alphabet will correspond to one item slot. You can expand the inventory if you want, though you’ll need to come up with an alternative menu system to accommodate having more choices.

In order to actually pick up an item of the floor, we’ll require the rogue to move onto the same tile and press a key. First, we’ll want an easy way to grab all the items that currently exist in the map. Open up `game_map.py` and add the following:

    ...
    import numpy as np  # type: ignore
    from tcod.console import Console

    -from entity import Actor
    +from entity import Actor, Item
    import tile_types
    ...

        ...
        @property
        def actors(self) -> Iterator[Actor]:
            """Iterate over this maps living actors."""
            yield from (
                entity
                for entity in self.entities
                if isinstance(entity, Actor) and entity.is_alive
            )

    +   @property
    +   def items(self) -> Iterator[Item]:
    +       yield from (entity for entity in self.entities if isinstance(entity, Item))

        def get_blocking_entity_at_location(
            ...

    ...
    import numpy as np  # type: ignore
    from tcod.console import Console

    from entity import Actor
    from entity import Actor, Item
    import tile_types
    ...

        ...
        @property
        def actors(self) -> Iterator[Actor]:
            """Iterate over this maps living actors."""
            yield from (
                entity
                for entity in self.entities
                if isinstance(entity, Actor) and entity.is_alive
            )

        @property
        def items(self) -> Iterator[Item]:
            yield from (entity for entity in self.entities if isinstance(entity, Item))

        def get_blocking_entity_at_location(
            ...

We can use this new property in an action to find the item(s) on the same tile as the player. Let’s define a `PickupAction`, which will handle picking up the item and adding it to the inventory.

Open up `actions.py` and define `PickupAction` like this:

    class Action:
        ...

    +class PickupAction(Action):
    +   """Pickup an item and add it to the inventory, if there is room for it."""

    +   def __init__(self, entity: Actor):
    +       super().__init__(entity)

    +   def perform(self) -> None:
    +       actor_location_x = self.entity.x
    +       actor_location_y = self.entity.y
    +       inventory = self.entity.inventory

    +       for item in self.engine.game_map.items:
    +           if actor_location_x == item.x and actor_location_y == item.y:
    +               if len(inventory.items) >= inventory.capacity:
    +                   raise exceptions.Impossible("Your inventory is full.")

    +               self.engine.game_map.entities.remove(item)
    +               item.parent = self.entity.inventory
    +               inventory.items.append(item)

    +               self.engine.message_log.add_message(f"You picked up the {item.name}!")
    +               return

    +       raise exceptions.Impossible("There is nothing here to pick up.")

    class ItemAction(Action):
        ...

    class Action:
        ...

    class PickupAction(Action):
        """Pickup an item and add it to the inventory, if there is room for it."""

        def __init__(self, entity: Actor):
            super().__init__(entity)

        def perform(self) -> None:
            actor_location_x = self.entity.x
            actor_location_y = self.entity.y
            inventory = self.entity.inventory

            for item in self.engine.game_map.items:
                if actor_location_x == item.x and actor_location_y == item.y:
                    if len(inventory.items) >= inventory.capacity:
                        raise exceptions.Impossible("Your inventory is full.")

                    self.engine.game_map.entities.remove(item)
                    item.parent = self.entity.inventory
                    inventory.items.append(item)

                    self.engine.message_log.add_message(f"You picked up the {item.name}!")
                    return

            raise exceptions.Impossible("There is nothing here to pick up.")

    class ItemAction(Action):
        ...

The action gets the entity’s location, and tries to find an item that exists in the same location, iterating through `self.engine.game_map.items` (which we just defined). If an item is found, we try to add it to the inventory, checking the capacity first, and returning `Impossible` if its full. When adding an item to the inventory, we remove it from the game map and store it in the inventory, and print out a message. We then return, since only one item can be picked up per turn (it’ll be possible later for multiple items to be on the same spot).

If no item is found in the location, we just return `Impossible`, informing the player that there’s nothing there.

Let’s add our new action to the event handler. Open up `input_handlers.py` and edit the key checking section of `MainGameEventHandler` to add the key for picking up items:

    from actions import (
        Action,
        BumpAction,
        EscapeAction,
    +   PickupAction,
        WaitAction,
    )
    ...

            ...
            elif key == tcod.event.K_v:
                self.engine.event_handler = HistoryViewer(self.engine)

    +       elif key == tcod.event.K_g:
    +           action = PickupAction(player)

            # No valid key was pressed
            return action

    from actions import (
        Action,
        BumpAction,
        EscapeAction,
        PickupAction,
        WaitAction,
    )
    ...

            ...
            elif key == tcod.event.K_v:
                self.engine.event_handler = HistoryViewer(self.engine)

            elif key == tcod.event.K_g:
                action = PickupAction(player)

            # No valid key was pressed
            return action

Simple enough, if the player presses the “g” key (“g” for “get”), we call the `PickupAction`. Run the project now, and pick up those potions!

Now that the player can pick up items, we’ll need to create our inventory menu, where the player can see what items are in the inventory, and select which one to use. This will require a few steps.

First, we need a way to get input from the user. When the user opens the inventory menu, we need to get the input from the user, and if it was valid, we return to the main game’s event handler, so the enemies can take their turns.

To start, let’s create a new event handler, which will return to the `MainGameEventHandler` when it handles an action successfully. Open `input_handlers.py` and add the following class:

    class EventHandler(tcod.event.EventDispatch[Action]):
        ...

    +class AskUserEventHandler(EventHandler):
    +   """Handles user input for actions which require special input."""

    +   def handle_action(self, action: Optional[Action]) -> bool:
    +       """Return to the main event handler when a valid action was performed."""
    +       if super().handle_action(action):
    +           self.engine.event_handler = MainGameEventHandler(self.engine)
    +           return True
    +       return False

    +   def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[Action]:
    +       """By default any key exits this input handler."""
    +       if event.sym in {  # Ignore modifier keys.
    +           tcod.event.K_LSHIFT,
    +           tcod.event.K_RSHIFT,
    +           tcod.event.K_LCTRL,
    +           tcod.event.K_RCTRL,
    +           tcod.event.K_LALT,
    +           tcod.event.K_RALT,
    +       }:
    +           return None
    +       return self.on_exit()

    +   def ev_mousebuttondown(self, event: tcod.event.MouseButtonDown) -> Optional[Action]:
    +       """By default any mouse click exits this input handler."""
    +       return self.on_exit()

    +   def on_exit(self) -> Optional[Action]:
    +       """Called when the user is trying to exit or cancel an action.

    +       By default this returns to the main event handler.
    +       """
    +       self.engine.event_handler = MainGameEventHandler(self.engine)
    +       return None

    class EventHandler(tcod.event.EventDispatch[Action]):
        ...

    class AskUserEventHandler(EventHandler):
        """Handles user input for actions which require special input."""

        def handle_action(self, action: Optional[Action]) -> bool:
            """Return to the main event handler when a valid action was performed."""
            if super().handle_action(action):
                self.engine.event_handler = MainGameEventHandler(self.engine)
                return True
            return False

        def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[Action]:
            """By default any key exits this input handler."""
            if event.sym in {  # Ignore modifier keys.
                tcod.event.K_LSHIFT,
                tcod.event.K_RSHIFT,
                tcod.event.K_LCTRL,
                tcod.event.K_RCTRL,
                tcod.event.K_LALT,
                tcod.event.K_RALT,
            }:
                return None
            return self.on_exit()

        def ev_mousebuttondown(self, event: tcod.event.MouseButtonDown) -> Optional[Action]:
            """By default any mouse click exits this input handler."""
            return self.on_exit()

        def on_exit(self) -> Optional[Action]:
            """Called when the user is trying to exit or cancel an action.

            By default this returns to the main event handler.
            """
            self.engine.event_handler = MainGameEventHandler(self.engine)
            return None

`AskUserEventHandler`, by default, just exits itself when any key is pressed, besides one of the “modifier” keys (shift, control, and alt). It also exits when clicking the mouse.

What’s the point of this class? By itself, nothing really. But we can create subclasses of it that actually do something useful, which is what we’ll do now. Let’s keep editing `input_handlers.py` and add this class:

    if TYPE_CHECKING:
        from engine import Engine
    +   from entity import Item
    ...

    class AskUserEventHandler(EventHandler):
        ...

    +class InventoryEventHandler(AskUserEventHandler):
    +   """This handler lets the user select an item.

    +   What happens then depends on the subclass.
    +   """

    +   TITLE = "<missing title>"

    +   def on_render(self, console: tcod.Console) -> None:
    +       """Render an inventory menu, which displays the items in the inventory, and the letter to select them.
    +       Will move to a different position based on where the player is located, so the player can always see where
    +       they are.
    +       """
    +       super().on_render(console)
    +       number_of_items_in_inventory = len(self.engine.player.inventory.items)

    +       height = number_of_items_in_inventory + 2

    +       if height <= 3:
    +           height = 3

    +       if self.engine.player.x <= 30:
    +           x = 40
    +       else:
    +           x = 0

    +       y = 0

    +       width = len(self.TITLE) + 4

    +       console.draw_frame(
    +           x=x,
    +           y=y,
    +           width=width,
    +           height=height,
    +           title=self.TITLE,
    +           clear=True,
    +           fg=(255, 255, 255),
    +           bg=(0, 0, 0),
    +       )

    +       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}")
    +       else:
    +           console.print(x + 1, y + 1, "(Empty)")

    +   def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[Action]:
    +       player = self.engine.player
    +       key = event.sym
    +       index = key - tcod.event.K_a

    +       if 0 <= index <= 26:
    +           try:
    +               selected_item = player.inventory.items[index]
    +           except IndexError:
    +               self.engine.message_log.add_message("Invalid entry.", color.invalid)
    +               return None
    +           return self.on_item_selected(selected_item)
    +       return super().ev_keydown(event)

    +   def on_item_selected(self, item: Item) -> Optional[Action]:
    +       """Called when the user selects a valid item."""
    +       raise NotImplementedError()

    if TYPE_CHECKING:
        from engine import Engine
        from entity import Item
    ...

    class AskUserEventHandler(EventHandler):
        ...

    class InventoryEventHandler(AskUserEventHandler):
        """This handler lets the user select an item.

        What happens then depends on the subclass.
        """

        TITLE = "<missing title>"

        def on_render(self, console: tcod.Console) -> None:
            """Render an inventory menu, which displays the items in the inventory, and the letter to select them.
            Will move to a different position based on where the player is located, so the player can always see where
            they are.
            """
            super().on_render(console)
            number_of_items_in_inventory = len(self.engine.player.inventory.items)

            height = number_of_items_in_inventory + 2

            if height <= 3:
                height = 3

            if self.engine.player.x <= 30:
                x = 40
            else:
                x = 0

            y = 0

            width = len(self.TITLE) + 4

            console.draw_frame(
                x=x,
                y=y,
                width=width,
                height=height,
                title=self.TITLE,
                clear=True,
                fg=(255, 255, 255),
                bg=(0, 0, 0),
            )

            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}")
            else:
                console.print(x + 1, y + 1, "(Empty)")

        def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[Action]:
            player = self.engine.player
            key = event.sym
            index = key - tcod.event.K_a

            if 0 <= index <= 26:
                try:
                    selected_item = player.inventory.items[index]
                except IndexError:
                    self.engine.message_log.add_message("Invalid entry.", color.invalid)
                    return None
                return self.on_item_selected(selected_item)
            return super().ev_keydown(event)

        def on_item_selected(self, item: Item) -> Optional[Action]:
            """Called when the user selects a valid item."""
            raise NotImplementedError()

`InventoryEventHandler` subclasses `AskUserEventHandler`, and renders the items within the player’s `Inventory`. Depending on where the player is standing, the menu will render off to the side, so the menu won’t cover the player. If there’s nothing in the inventory, it just prints “Empty”. Notice that it doesn’t give itself a title, as that will be defined in a different subclass (more on that in a bit).

The `ev_keydown` function takes the user’s input, from letters a - z, and associates that with an index in the inventory. If the player pressed “b”, for example, the second item in the inventory will be selected and returned. If the player presses a key like “c” (item 3) but only has one item, then the message “Invalid entry” will display. If any other key is pressed, the menu will close.

This class, still, does not actually do anything for us right now, but I promise we’re close. Before we implement the menus to use and drop items, we’ll need to define the `Action` that drops items. Add the following class to `actions.py`:

    class EscapeAction(Action):
        def perform(self) -> None:
            raise SystemExit()

    +class DropItem(ItemAction):
    +   def perform(self) -> None:
    +       self.entity.inventory.drop(self.item)

    class WaitAction(Action):
        def perform(self) -> None:
            pass
    ...

    class EscapeAction(Action):
        def perform(self) -> None:
            raise SystemExit()

    class DropItem(ItemAction):
        def perform(self) -> None:
            self.entity.inventory.drop(self.item)

    class WaitAction(Action):
        def perform(self) -> None:
            pass
    ...

`DropItem` will be used to drop something from the inventory. It just calls the `drop` method of the `Inventory` component.

Now, let’s put this new action into… well… action! Open up `input_handlers.py` once again, and let’s add the handlers that will handle both selecting an item and dropping one.

    ...
    import tcod

    +import actions
    from actions import (
        Action,
        BumpAction,
        EscapeAction,
        PickupAction,
        WaitAction,
    )
    ...

    class InventoryEventHandler(AskUserEventHandler):
        ...

    +class InventoryActivateHandler(InventoryEventHandler):
    +   """Handle using an inventory item."""

    +   TITLE = "Select an item to use"

    +   def on_item_selected(self, item: Item) -> Optional[Action]:
    +       """Return the action for the selected item."""
    +       return item.consumable.get_action(self.engine.player)

    +class InventoryDropHandler(InventoryEventHandler):
    +   """Handle dropping an inventory item."""

    +   TITLE = "Select an item to drop"

    +   def on_item_selected(self, item: Item) -> Optional[Action]:
    +       """Drop this item."""
    +       return actions.DropItem(self.engine.player, item)

    ...
    import tcod

    import actions
    from actions import (
        Action,
        BumpAction,
        EscapeAction,
        PickupAction,
        WaitAction,
    )
    ...

    class InventoryEventHandler(AskUserEventHandler):
        ...

    class InventoryActivateHandler(InventoryEventHandler):
        """Handle using an inventory item."""

        TITLE = "Select an item to use"

        def on_item_selected(self, item: Item) -> Optional[Action]:
            """Return the action for the selected item."""
            return item.consumable.get_action(self.engine.player)

    class InventoryDropHandler(InventoryEventHandler):
        """Handle dropping an inventory item."""

        TITLE = "Select an item to drop"

        def on_item_selected(self, item: Item) -> Optional[Action]:
            """Drop this item."""
            return actions.DropItem(self.engine.player, item)

At long last, we’ve got the `InventoryActivateHandler` and the `InventoryDropHandler`, which will handle using and dropping items, respectively. They both inherit from `InventoryEventHandler`, allowing the player to select an item in both menus using what we wrote in that class (selecting an item with a letter), but both handlers display a different title and call different actions, depending on the selection.

All that’s left now is to utilize these event handlers, based on the key we press. Let’s set the game up to open the inventory menu when pressing “i”, and the drop menu when pressing “d”. Open `input_handlers.py`, and add the following lines to `MainGameEventHandler`:

            ...
            elif key == tcod.event.K_g:
                action = PickupAction(player)

    +       elif key == tcod.event.K_i:
    +           self.engine.event_handler = InventoryActivateHandler(self.engine)
    +       elif key == tcod.event.K_d:
    +           self.engine.event_handler = InventoryDropHandler(self.engine)

            # No valid key was pressed
            return action

            ...
            elif key == tcod.event.K_g:
                action = PickupAction(player)

            elif key == tcod.event.K_i:
                self.engine.event_handler = InventoryActivateHandler(self.engine)
            elif key == tcod.event.K_d:
                self.engine.event_handler = InventoryDropHandler(self.engine)

            # No valid key was pressed
            return action

Now, when you run the project, you can, at long last, use and drop the health potions!

There’s a major bug with our implementation though: used items won’t disappear after using them. This means the player could keep consuming the same health potion over and over!

Let’s fix that, by opening up `consumable.py` and add the following:

    from __future__ import annotations

    from typing import Optional, TYPE_CHECKING

    import actions
    import color
    +import components.inventory
    from components.base_component import BaseComponent
    from exceptions import Impossible

    if TYPE_CHECKING:
        from entity import Actor, Item

    class Consumable(BaseComponent):
        parent: Item

        def get_action(self, consumer: Actor) -> Optional[actions.Action]:
            """Try to return the action for this item."""
            return actions.ItemAction(consumer, self.parent)

        def activate(self, action: actions.ItemAction) -> None:
            """Invoke this items ability.

            `action` is the context for this activation.
            """
            raise NotImplementedError()

    +   def consume(self) -> None:
    +       """Remove the consumed item from its containing inventory."""
    +       entity = self.parent
    +       inventory = entity.parent
    +       if isinstance(inventory, components.inventory.Inventory):
    +           inventory.items.remove(entity)

    class HealingConsumable(Consumable):
        def __init__(self, amount: int):
            self.amount = amount

        def activate(self, action: actions.ItemAction) -> None:
            consumer = action.entity
            amount_recovered = consumer.fighter.heal(self.amount)

            if amount_recovered > 0:
                self.engine.message_log.add_message(
                    f"You consume the {self.parent.name}, and recover {amount_recovered} HP!",
                    color.health_recovered,
                )
    +           self.consume()
            else:
                raise Impossible(f"Your health is already full.")

    from __future__ import annotations

    from typing import Optional, TYPE_CHECKING

    import actions
    import color
    import components.inventory
    from components.base_component import BaseComponent
    from exceptions import Impossible

    if TYPE_CHECKING:
        from entity import Actor, Item

    class Consumable(BaseComponent):
        parent: Item

        def get_action(self, consumer: Actor) -> Optional[actions.Action]:
            """Try to return the action for this item."""
            return actions.ItemAction(consumer, self.parent)

        def activate(self, action: actions.ItemAction) -> None:
            """Invoke this items ability.

            `action` is the context for this activation.
            """
            raise NotImplementedError()

        def consume(self) -> None:
            """Remove the consumed item from its containing inventory."""
            entity = self.parent
            inventory = entity.parent
            if isinstance(inventory, components.inventory.Inventory):
                inventory.items.remove(entity)

    class HealingConsumable(Consumable):
        def __init__(self, amount: int):
            self.amount = amount

        def activate(self, action: actions.ItemAction) -> None:
            consumer = action.entity
            amount_recovered = consumer.fighter.heal(self.amount)

            if amount_recovered > 0:
                self.engine.message_log.add_message(
                    f"You consume the {self.parent.name}, and recover {amount_recovered} HP!",
                    color.health_recovered,
                )
                self.consume()
            else:
                raise Impossible(f"Your health is already full.")

`consume` removes the item from the `Inventory` container it occupies. Since it no longer belongs to the inventory or the map, it disappears from the game. We use the `consume` method when the health potion is successfully used, and we don’t if it’s not.

With that, the health potions will disappear after use.

There’s two last bits of housekeeping we need to do before moving on to the next part. The `parent` class attribute in the `Entity` class has a bit of a problem: it’s designated as a `GameMap` type right now, but when an item moves from the map to the inventory, that isn’t really true any more. Let’s fix that now:

    from __future__ import annotations

    import copy
    -from typing import Optional, Tuple, Type, TypeVar, TYPE_CHECKING
    +from typing import Optional, Tuple, Type, TypeVar, TYPE_CHECKING, Union

    from render_order import RenderOrder

    ...
    class Entity:
        """
        A generic object to represent players, enemies, items, etc.
        """

    -   parent: GameMap
    +   parent: Union[GameMap, Inventory]

        def __init__(
            ...

    from __future__ import annotations

    import copy
    from typing import Optional, Tuple, Type, TypeVar, TYPE_CHECKING
    from typing import Optional, Tuple, Type, TypeVar, TYPE_CHECKING, Union

    from render_order import RenderOrder

    ...
    class Entity:
        """
        A generic object to represent players, enemies, items, etc.
        """

        parent: GameMap
        parent: Union[GameMap, Inventory]

        def __init__(
            ...

Lastly, we can actually remove `EscapeAction`, as it can just be handled by the event handlers. Open `actions.py` and remove `EscapeAction`:

    class PickupAction(Action):
        ...

    -class EscapeAction(Action):
    -   def perform(self) -> None:
    -       raise SystemExit()

    class ItemAction(Action):
        ...

    class PickupAction(Action):
        ...

    class EscapeAction(Action):
        def perform(self) -> None:
            raise SystemExit()

    class ItemAction(Action):
        ...

Then, remove `EscapeAction` from `input_handlers.py`:

    ...
    from actions import (
        Action,
        BumpAction,
    -   EscapeAction,
        PickupAction,
        WaitAction
    )
    ...

            ...
            elif key == tcod.event.K_ESCAPE:
    -           action = EscapeAction(player)
    +           raise SystemExit()
            elif key == tcod.event.K_v:
                self.engine.event_handler = HistoryViewer(self.engine)
            ...

    ...
    from actions import (
        Action,
        BumpAction,
        EscapeAction,
        PickupAction,
        WaitAction
    )
    ...

            ...
            elif key == tcod.event.K_ESCAPE:
                action = EscapeAction(player)
                raise SystemExit()
            elif key == tcod.event.K_v:
                self.engine.event_handler = HistoryViewer(self.engine)
            ...

This was another long chapter, but this is an important step towards a functioning game. Next chapter, we’ll add a few more item types to use.

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

Click here to move on to the next part of this tutorial.