Stepwise/source/useable.py
Kayne Ruse a7064e3db0 Added ranged Useables, TileSelector
Added ScrollOfLightning as an example.

Not finished yet, confusion & fireball are still in part 9.
2025-04-05 19:06:23 +11:00

125 lines
3.4 KiB
Python

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