164 lines
5.2 KiB
Python
164 lines
5.2 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Optional, TYPE_CHECKING
|
|
|
|
import actions
|
|
import color
|
|
import components.ai
|
|
import components.inventory
|
|
from components.base_component import BaseComponent
|
|
from exceptions import Impossible
|
|
from input_handlers import (
|
|
ActionOrHandler,
|
|
AreaRangedAttackHandler,
|
|
SingleRangedAttackHandler
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from entity import Actor, Item
|
|
|
|
|
|
class Consumable(BaseComponent):
|
|
parent: Item
|
|
|
|
def get_action(self, consumer: Actor) -> Optional[ActionOrHandler]:
|
|
"""Try to return the action for this item."""
|
|
return actions.ItemAction(consumer, self.parent)
|
|
|
|
def activate(self, action: actions.ItemAction) -> None:
|
|
"""Invoke this item's 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 ConfusionConsumable(Consumable):
|
|
def __init__(self, number_of_turns: int):
|
|
self.number_of_turns = number_of_turns
|
|
|
|
def get_action(self, consumer: Actor) -> SingleRangedAttackHandler:
|
|
self.engine.message_log.add_message(
|
|
"Select a target location.",
|
|
color.needs_target
|
|
)
|
|
|
|
return SingleRangedAttackHandler(
|
|
self.engine,
|
|
callback=lambda xy: actions.ItemAction(consumer, self.parent, xy),
|
|
)
|
|
|
|
def activate(self, action: actions.ItemAction) -> None:
|
|
consumer = action.entity
|
|
target = action.target_actor
|
|
|
|
if not self.engine.game_map.visible[action.target_xy]:
|
|
raise Impossible("You cannot target an area that you cannot see.")
|
|
if not target:
|
|
raise Impossible("You must select an enemy to target.")
|
|
if target is consumer:
|
|
raise Impossible("You cannot confuse yourself!")
|
|
|
|
self.engine.message_log.add_message(
|
|
f"The eyes of the {target.name} look vacant, as it starts to stumble around!",
|
|
color.status_effect_applied,
|
|
)
|
|
target.ai = components.ai.ConfusedEnemy(
|
|
entity=target,
|
|
previous_ai=target.ai,
|
|
turns_remaining=self.number_of_turns,
|
|
)
|
|
|
|
self.consume()
|
|
|
|
|
|
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.")
|
|
|
|
|
|
class FireballDamageConsumable(Consumable):
|
|
def __init__(self, damage: int, radius: int):
|
|
self.damage = damage
|
|
self.radius = radius
|
|
|
|
def get_action(self, consumer: Actor) -> AreaRangedAttackHandler:
|
|
self.engine.message_log.add_message(
|
|
"Select a target location.",
|
|
color.needs_target
|
|
)
|
|
|
|
return AreaRangedAttackHandler(
|
|
self.engine,
|
|
self.radius,
|
|
lambda xy: actions.ItemAction(consumer, self.parent, xy),
|
|
)
|
|
|
|
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
|
|
self.maximum_range = maximum_range
|
|
|
|
def activate(self, action: actions.ItemAction) -> None:
|
|
consumer = action.entity
|
|
target = None
|
|
closest_distance = self.maximum_range + 1.0
|
|
|
|
for actor in self.engine.game_map.actors:
|
|
if actor is not consumer and self.parent.gamemap.visible[actor.x, actor.y]:
|
|
distance = consumer.distance(actor.x, actor.y)
|
|
|
|
if distance < closest_distance:
|
|
target = actor
|
|
closest_distance = distance
|
|
|
|
if target:
|
|
self.engine.message_log.add_message(
|
|
f"A lightning bolt stikes the {target.name} with a loud thunder, for {self.damage} damage!"
|
|
)
|
|
target.fighter.take_damage(self.damage)
|
|
self.consume()
|
|
else:
|
|
raise Impossible("No enemy is close enough to strike.")
|