diff --git a/actions.py b/actions.py index f7978f7..62de5a8 100644 --- a/actions.py +++ b/actions.py @@ -87,6 +87,11 @@ class EscapeAction(Action): raise SystemExit() +class DropItem(ItemAction): + def perform(self) -> None: + self.entity.inventory.drop(self.item) + + class WaitAction(Action): def perform(self) -> None: pass diff --git a/input_handlers.py b/input_handlers.py index 4029a63..935fa6d 100644 --- a/input_handlers.py +++ b/input_handlers.py @@ -4,6 +4,7 @@ from typing import overload, Optional, TYPE_CHECKING import tcod.event +import actions from actions import ( Action, BumpAction, @@ -16,6 +17,7 @@ import exceptions if TYPE_CHECKING: from engine import Engine + from entity import Item MOVE_KEYS = { # Arrow keys @@ -94,6 +96,136 @@ class EventHandler(tcod.event.EventDispatch[Action]): self.engine.render(console) +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.""" + # Ignore modifier keys. + if event.sym in { + 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 InventoryEventHandler(AskUserEventHandler): + """This handler lets the user select an item. + + What happens then depends on the subclass. + """ + + 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, + y, + width, + 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) + + @overload + def on_item_selected(self, item: Item) -> Optional[Action]: + """Called when the user selects a valid item.""" + + +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) + + class MainGameEventHandler(EventHandler): def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[Action]: action: Optional[Action] = None @@ -117,6 +249,11 @@ class MainGameEventHandler(EventHandler): 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