from __future__ import annotations from typing import Optional, TYPE_CHECKING from utils import roll_dice if TYPE_CHECKING: from stats import Stats class BaseUseable: """ Base type for useable items, with various utilities. Please note that distances are calculated with the Pythagorean theorem, so a maximum range of `1` won't work in diagonally adjacent tiles. """ consumable: bool current_stack: int maximum_stack: int minimum_range: int maximum_range: int def __init__(self, *, consumable: bool = False, current_stack: int = 1, maximum_stack: int = -1, minimum_range: int = 0, maximum_range: int = -1, ): self.consumable = consumable self.current_stack = current_stack self.maximum_stack = maximum_stack self.minimum_range = minimum_range self.maximum_range = maximum_range def apply(self, stats: Stats) -> bool: """ Use this item's effects. Returns `True` if the item's state changed. """ raise NotImplementedError() def get_used_msg(self, appearance: str) -> Optional[str]: """ May return a string to display to the user. `appearance` is what the item looks like, and can be substituted into the result. """ return None #default #utils def reduce_stack(self, amount: int = 1) -> bool: """ Reduce the size of a stack by an amount. Returns `True` if this item should be deleted. """ if self.maximum_stack > 0: self.current_stack -= amount return self.current_stack <= 0 return self.consumable def is_stack_empty(self) -> bool: return self.consumable and self.maximum_stack > 0 and self.current_stack <= 0 def is_stack_mergable(self, other: BaseUseable) -> bool: """ If this returns `True`, this instance can be merged with the other instance. """ if self.__class__ is not other.__class__: return False max_stack = max(self.maximum_stack, other.maximum_stack) return self.current_stack + other.current_stack <= max_stack class Unuseable(BaseUseable): """A placeholder Useable for dead entities.""" def __init__(self): super().__init__() #enforce defaults def apply(self, stats: Stats) -> bool: return None def get_used_msg(self, appearance: str) -> Optional[str]: return f"This {appearance} is utterly useless." class PotionOfHealing(BaseUseable): #NetHack's healing potions, (B | U | C): #Healing: 8d4 | 6d4 | 4d4. If the result is above MaxHP, MaxHP is incrased by 1 | 1 | 0. #Extra Healing: 8d8 | 6d8 | 4d8. If the result is above MaxHP, MaxHP is incrased by 5 | 2 | 0. #Full Healing: 400 | 400 | 400. If the result is above MaxHP, MaxHP is incrased by 8 | 4 | 0. """Restores 4d4 health when applied.""" __last_roll: int = -1 def apply(self, stats: Stats) -> bool: self.__last_roll = roll_dice(4, 4) stats.current_hp += self.__last_roll return self.reduce_stack() def get_used_msg(self, appearance: str) -> Optional[str]: if self.__last_roll >= 0: return f"The {appearance} restored {self.__last_roll} health." else: return None class ScrollOfLightning(BaseUseable): """Deals 2d4 damage when applied.""" __last_roll: int = -1 def apply(self, stats: Stats) -> bool: self.__last_roll = roll_dice(2, 4) stats.current_hp -= self.__last_roll return self.reduce_stack() def get_used_msg(self, appearance: str) -> Optional[str]: if self.__last_roll >= 0: return f"The {appearance} dealt {self.__last_roll} damage." else: return None #TODO: "The gobbo died" and "You dealt X damage" are in the wrong order in the log