From a1e6125c34202cca144994f8bd735ec4f0776de8 Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Tue, 18 Jan 2022 13:26:26 -0500 Subject: [PATCH] Add Fireball Scoll, completing part 8 --- components/consumable.py | 41 +++++++++++++++++++++++++++++++++++++++- entity_factories.py | 6 ++++++ input_handlers.py | 37 ++++++++++++++++++++++++++++++++++++ procgen.py | 2 ++ 4 files changed, 85 insertions(+), 1 deletion(-) diff --git a/components/consumable.py b/components/consumable.py index f09e6a9..b8712ab 100644 --- a/components/consumable.py +++ b/components/consumable.py @@ -8,7 +8,7 @@ import components.ai import components.inventory from components.base_component import BaseComponent from exceptions import Impossible -from input_handlers import SingleRangedAttackHandler +from input_handlers import AreaRangedAttackHandler, SingleRangedAttackHandler if TYPE_CHECKING: from entity import Actor, Item @@ -94,6 +94,45 @@ class HealingConsumable(Consumable): raise Impossible(f"Your health is already full.") +class FireballDamageConsumable(Consumable): + def __init__(self, damage: int, radius: int): + self.damage = damage + self.radius = radius + + def get_action(self, consumer: Actor) -> Optional[actions.Action]: + self.engine.message_log.add_message( + "Select a target location.", + color.needs_target + ) + self.engine.event_handler = AreaRangedAttackHandler( + self.engine, + self.radius, + lambda xy: actions.ItemAction(consumer, self.parent, xy), + ) + + return None + + def activate(self, action: actions.ItemAction) -> None: + target_xy = action.target_xy + + if not self.engine.game_map.visible[target_xy]: + raise Impossible("You cannot target an area that you cannot see.") + + targets_hit = False + for actor in self.engine.game_map.actors: + if actor.distance(*target_xy) <= self.radius: + self.engine.message_log.add_message( + f"The {actor.name} is engulfed in a fiery explosion, taking {self.damage} damage!" + ) + actor.fighter.take_damage(self.damage) + targets_hit = True + + if not targets_hit: + raise Impossible("There are no targets in the radius.") + + self.consume() + + class LightningDamageConsumable(Consumable): def __init__(self, damage: int, maximum_range: int): self.damage = damage diff --git a/entity_factories.py b/entity_factories.py index 250aa22..d5fdf1d 100644 --- a/entity_factories.py +++ b/entity_factories.py @@ -36,6 +36,12 @@ confusion_scroll = Item( name="Confusion Scroll", consumable=consumable.ConfusionConsumable(number_of_turns=10), ) +fireball_scroll = Item( + char="~", + color=(255, 0, 0), + name="Fireball Scroll", + consumable=consumable.FireballDamageConsumable(damage=12, radius=3), +) health_potion = Item( char="!", color=(127, 0, 255), diff --git a/input_handlers.py b/input_handlers.py index dd1e2b6..d01f69c 100644 --- a/input_handlers.py +++ b/input_handlers.py @@ -310,6 +310,43 @@ class SingleRangedAttackHandler(SelectIndexHandler): return self.callback((x, y)) +class AreaRangedAttackHandler(SelectIndexHandler): + """ + Handles targeting an area within a given radius. + Any entity within the area will be affected. + """ + + def __init__( + self, + engine: Engine, + radius: int, + callback: Callable[[Tuple[int, int]], Optional[Action]] + ): + super().__init__(engine) + + self.radius = radius + self.callback = callback + + def on_render(self, console: tcod.Console) -> None: + """Highlight the tile under the cursor.""" + super().on_render(console) + + x, y = self.engine.mouse_location + + # Draw a rectangle around the targeted area, so the player can see the affected tiles. + console.draw_frame( + x=x - self.radius - 1, + y=y - self.radius - 1, + width=self.radius ** 2, + height=self.radius ** 2, + fg=color.red, + clear=False, + ) + + def on_index_selected(self, x: int, y: int) -> Optional[Action]: + return self.callback((x, y)) + + class MainGameEventHandler(EventHandler): def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[Action]: action: Optional[Action] = None diff --git a/procgen.py b/procgen.py index 7fe948e..d5147ab 100644 --- a/procgen.py +++ b/procgen.py @@ -70,6 +70,8 @@ def place_entities( if item_chance < 0.7: entity_factories.health_potion.spawn(dungeon, x, y) + elif item_chance < 0.8: + entity_factories.fireball_scroll.spawn(dungeon, x, y) elif item_chance < 0.9: entity_factories.confusion_scroll.spawn(dungeon, x, y) else: