Added ranged Useables, TileSelector
Added ScrollOfLightning as an example. Not finished yet, confusion & fireball are still in part 9.
This commit is contained in:
parent
b8e5777729
commit
a7064e3db0
@ -33,7 +33,7 @@ Gobbo Swarm
|
||||
|
||||
Merchant Ship
|
||||
* A group of merchants capable of travelling between dimensions, will sell items from other worlds, including unique items not available elsewhere.
|
||||
* For each run (perhaps, with a minimum level needed, so it can't be cheesed), a counter ticks down until zero, at which point the merchant ship is guaranteed to generate. The counter only resets when the player finds it. In essence, this guarantees the ship will be encountered on a semi-regular basis.
|
||||
* For each run (perhaps, with a minimum level needed, so it can't be cheesed), a counter ticks down until zero, at which point the merchant ship is guaranteed to generate. The counter only resets when the player finds it. In essence, this guarantees the ship will be encountered on a semiregular basis.
|
||||
|
||||
Contagion
|
||||
* A strange affliction, that turns people into zombies. Could also prevent the spawning of normally present monsters, to invoke a 28-days-later vibe.
|
||||
|
@ -6,6 +6,7 @@ This file is a kind of scratch-pad for a multitude of ideas, that I can't implem
|
||||
|
||||
https://rogueliketutorials.com/
|
||||
https://python-tcod.readthedocs.io/en/latest/
|
||||
https://pyinstaller.org/en/stable/
|
||||
|
||||
```bash
|
||||
#make the virtual environment
|
||||
|
BIN
promo/screenshot_05.png
Normal file
BIN
promo/screenshot_05.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
BIN
promo/screenshot_06.png
Normal file
BIN
promo/screenshot_06.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
BIN
promo/screenshot_07.png
Normal file
BIN
promo/screenshot_07.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
@ -1,5 +1,5 @@
|
||||
from __future__ import annotations
|
||||
from typing import List, Optional, TYPE_CHECKING
|
||||
from typing import List, TYPE_CHECKING
|
||||
|
||||
import colors
|
||||
from floor_map import FloorMap
|
||||
@ -8,6 +8,7 @@ from useable import BaseUseable
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from engine import Engine
|
||||
from event_handlers import OptionSelector, TileSelector
|
||||
from entity import Entity
|
||||
|
||||
class BaseAction:
|
||||
@ -143,8 +144,6 @@ class PickupAction(BaseAction):
|
||||
self.entity.inventory.insert(item)
|
||||
engine.message_log.add_message(msg, color=colors.terminal_light)
|
||||
else:
|
||||
from event_handlers import OptionSelector #circular imports are a pain
|
||||
|
||||
#build an options list
|
||||
options: List[str] = []
|
||||
for item in item_pile:
|
||||
@ -268,17 +267,29 @@ class UsageAction(BaseAction):
|
||||
item: Entity = inventory.access(self.index)
|
||||
usable: BaseUseable = item.useable
|
||||
|
||||
if usable.apply(self.target.stats) and usable.is_stack_empty():
|
||||
#remove the item from the inventory
|
||||
inventory.discard(self.index)
|
||||
#TODO: also check visibility?
|
||||
|
||||
if self.display_callback:
|
||||
self.display_callback(-1)
|
||||
#check range
|
||||
distance = self.entity.get_distance_to(self.target.x, self.target.y)
|
||||
|
||||
if usable.minimum_range > distance:
|
||||
engine.message_log.add_message("You're too close to use this!", color=colors.terminal_light)
|
||||
return False
|
||||
|
||||
elif usable.maximum_range < distance:
|
||||
engine.message_log.add_message("You're too far to use this!", color=colors.terminal_light)
|
||||
return False
|
||||
|
||||
elif usable.apply(self.target.stats) and usable.is_stack_empty():
|
||||
#remove the item from the inventory
|
||||
inventory.discard(self.index)
|
||||
|
||||
if self.display_callback:
|
||||
self.display_callback(-1)
|
||||
|
||||
msg: str = usable.get_used_msg(item.name)
|
||||
if not msg:
|
||||
msg = f"you used a(n) {item.name}"
|
||||
msg = f"What is a(n) {item.name}?"
|
||||
|
||||
engine.message_log.add_message(msg, color=colors.terminal_light)
|
||||
|
||||
return True
|
||||
return True
|
||||
|
@ -1,16 +1,31 @@
|
||||
#Standard colors
|
||||
white = (0xFF, 0xFF, 0xFF)
|
||||
black = (0x0, 0x0, 0x0)
|
||||
#url: https://en.wikipedia.org/wiki/Web_colors
|
||||
|
||||
red = (0xFF, 0, 0)
|
||||
green = (0, 0xFF, 0)
|
||||
blue = (0, 0, 0xFF)
|
||||
#CSS standard colors
|
||||
white = (0xFF, 0xFF, 0xFF)
|
||||
silver = (0xC0, 0xC0, 0xC0)
|
||||
gray = (0x80, 0x80, 0x80)
|
||||
black = (0x00, 0x00, 0x00)
|
||||
red = (0xFF, 0x00, 0x00)
|
||||
maroon = (0x80, 0x00, 0x00)
|
||||
yellow = (0xFF, 0xFF, 0x00)
|
||||
olive = (0x80, 0x80, 0x00)
|
||||
lime = (0x00, 0xFF, 0x00)
|
||||
green = (0x00, 0x80, 0x00)
|
||||
aqua = (0x00, 0xFF, 0xFF) #identical to cyan
|
||||
teal = (0x00, 0x80, 0x80)
|
||||
blue = (0x00, 0x00, 0xFF)
|
||||
navy = (0x00, 0x00, 0x80)
|
||||
fuchsia = (0xFF, 0x00, 0xFF)
|
||||
purple = (0x80, 0x00, 0x80) #identical to magenta
|
||||
|
||||
yellow = (0xFF, 0xFF, 0)
|
||||
magenta = (0xFF, 0, 0xFF)
|
||||
cyan = (0, 0xFF, 0xFF)
|
||||
#CSS extended colors (incomplete selection, may be expanded later)
|
||||
pink = (0xFF, 0xC0, 0xCB)
|
||||
orange = (0xFF, 0xA5, 0x00)
|
||||
deep_sky_blue = (0x00, 0xBF, 0xFF)
|
||||
navajo_white = (0xFF, 0xDE, 0xAD) #misnomer
|
||||
goldenrod = (0xDA, 0xA5, 0x20)
|
||||
|
||||
#gameboy DMG-01, according to Wikipedia's CSS
|
||||
#gameboy DMG-01
|
||||
gameboy_00 = (0x29, 0x41, 0x39)
|
||||
gameboy_01 = (0x39, 0x59, 0x4a)
|
||||
gameboy_02 = (0x5a, 0x79, 0x42)
|
||||
@ -19,6 +34,3 @@ gameboy_03 = (0x7b, 0x82, 0x10)
|
||||
#terminal-like
|
||||
terminal_light = (200, 200, 200)
|
||||
terminal_dark = (100, 100, 100)
|
||||
|
||||
#extended colors
|
||||
orange = (0xFF, 0xA5, 0x00)
|
@ -19,8 +19,8 @@ class Engine:
|
||||
|
||||
def __init__(self, *, floor_map: FloorMap, initial_log: List[Message] = None, ui_width: int = None, ui_height: int = None):
|
||||
#events
|
||||
from event_handlers import GameplayHandler
|
||||
self.event_handler = GameplayHandler(self, None)
|
||||
from event_handlers import GameplayViewer
|
||||
self.event_handler = GameplayViewer(self, None)
|
||||
self.mouse_position = (0, 0)
|
||||
|
||||
#map
|
||||
@ -47,7 +47,7 @@ class Engine:
|
||||
self.update_fov()
|
||||
|
||||
if self.event_handler.handle_events(context):
|
||||
self.handle_entities() #TODO: what 'game state'?
|
||||
self.handle_entities()
|
||||
|
||||
self.handle_rendering(context, console)
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import math
|
||||
from typing import Optional, Tuple, Type, TYPE_CHECKING
|
||||
|
||||
from ai import BaseAI
|
||||
@ -72,6 +73,9 @@ class Entity:
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
def get_distance_to(self, x: int, y: int) -> float:
|
||||
return math.sqrt((x - self.x) ** 2 + (y - self.y) ** 2)
|
||||
|
||||
#monster-specific stuff
|
||||
def is_alive(self) -> bool:
|
||||
return bool(self.ai)
|
||||
|
@ -2,12 +2,13 @@ from entity import Entity
|
||||
from ai import BaseAI, AggressiveWhenSeen
|
||||
from stats import Stats
|
||||
from inventory import Inventory
|
||||
from useable import PotionOfHealing
|
||||
import useable
|
||||
import colors
|
||||
|
||||
#player and utils
|
||||
player = Entity(
|
||||
char = "@",
|
||||
color = (255, 255, 255),
|
||||
color = colors.white,
|
||||
name = "Player",
|
||||
walkable = False,
|
||||
ai_class = BaseAI, #TODO: remove this or dummy it out
|
||||
@ -18,7 +19,7 @@ player = Entity(
|
||||
#monsters - gobbos
|
||||
gobbo = Entity(
|
||||
char = "g",
|
||||
color = (30, 168, 41),
|
||||
color = colors.lime,
|
||||
name = "Gobbo",
|
||||
walkable = False,
|
||||
ai_class = AggressiveWhenSeen,
|
||||
@ -27,7 +28,7 @@ gobbo = Entity(
|
||||
|
||||
gobbo_red = Entity(
|
||||
char = "g",
|
||||
color = (168, 41, 30),
|
||||
color = colors.red,
|
||||
name = "Red Gobbo",
|
||||
walkable = False,
|
||||
ai_class = AggressiveWhenSeen,
|
||||
@ -37,9 +38,19 @@ gobbo_red = Entity(
|
||||
#items - conumables
|
||||
potion_of_healing = Entity(
|
||||
char = "!",
|
||||
color = (0, 0, 255),
|
||||
color = colors.deep_sky_blue,
|
||||
name = "Potion of Healing",
|
||||
walkable = True,
|
||||
useable=PotionOfHealing(current_stack=1, maximum_stack=255, consumable=True),
|
||||
useable=useable.PotionOfHealing(consumable=True, current_stack=1, maximum_stack=255),
|
||||
)
|
||||
|
||||
scroll_of_lightning = Entity(
|
||||
char = "!",
|
||||
color = colors.goldenrod,
|
||||
name = "Scroll of Lightning",
|
||||
walkable = True,
|
||||
useable=useable.ScrollOfLightning(consumable=True, current_stack=1, maximum_stack=255, minimum_range=0, maximum_range=6),
|
||||
)
|
||||
|
||||
#TODO: scroll of confusion, using "confused AI"
|
||||
#TODO: scroll of fireball, dealing AOE damage
|
@ -1,5 +1,5 @@
|
||||
from __future__ import annotations
|
||||
from typing import List, Optional, TYPE_CHECKING
|
||||
from typing import List, Optional, Tuple, TYPE_CHECKING
|
||||
|
||||
import tcod
|
||||
|
||||
@ -15,9 +15,14 @@ from actions import (
|
||||
UsageAction,
|
||||
)
|
||||
|
||||
from useable import (
|
||||
BaseUseable,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from engine import Engine
|
||||
from entity import Entity
|
||||
from floor_map import FloorMap
|
||||
|
||||
#input options
|
||||
MOVE_KEYS = {
|
||||
@ -63,6 +68,7 @@ WAIT_KEYS = {
|
||||
|
||||
PICKUP_KEYS = {
|
||||
tcod.event.KeySym.COMMA,
|
||||
tcod.event.KeySym.SPACE,
|
||||
}
|
||||
|
||||
CURSOR_SCROLL_KEYS = {
|
||||
@ -80,6 +86,12 @@ CURSOR_CONFIRM_KEYS = {
|
||||
tcod.event.KeySym.SPACE,
|
||||
}
|
||||
|
||||
TILE_SCROLL_KEYS = MOVE_KEYS #copied
|
||||
|
||||
# TILE_SELECTOR_KEYS
|
||||
|
||||
TILE_CONFIRM_KEYS = CURSOR_CONFIRM_KEYS #copied
|
||||
|
||||
#the event handlers are a big part of the engine
|
||||
class EventHandler(tcod.event.EventDispatch[BaseAction]):
|
||||
engine: Engine
|
||||
@ -114,7 +126,22 @@ class EventHandler(tcod.event.EventDispatch[BaseAction]):
|
||||
return result
|
||||
|
||||
|
||||
class GameplayHandler(EventHandler):
|
||||
class GameoverViewer(EventHandler):
|
||||
"""Game over, man, GAME OVER!"""
|
||||
def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[BaseAction]:
|
||||
key = event.sym #SDL stuff, neat.
|
||||
|
||||
#player input
|
||||
if key == tcod.event.KeySym.ESCAPE:
|
||||
return QuitAction()
|
||||
|
||||
if key == tcod.event.KeySym.BACKQUOTE: #lowercase tilde
|
||||
self.engine.event_handler = LogHistoryViewer(self.engine, self)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class GameplayViewer(EventHandler):
|
||||
def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[BaseAction]:
|
||||
key = event.sym #SDL stuff, neat.
|
||||
|
||||
@ -142,34 +169,33 @@ class GameplayHandler(EventHandler):
|
||||
if key == tcod.event.KeySym.TAB:
|
||||
self.engine.event_handler = InventoryViewer(self.engine, self, player)
|
||||
|
||||
#debugging - can hook this up to more later
|
||||
#debugging - for various controls and testing needs
|
||||
if (event.mod & tcod.event.Modifier.CTRL) and key == tcod.event.KeySym.d:
|
||||
self.engine.event_handler = OptionSelector(
|
||||
self.engine,
|
||||
self,
|
||||
title="Debug Selector",
|
||||
options=["Zero", "One", "Two", "Three"],
|
||||
callback=lambda x: self.engine.message_log.add_message(f"DBG: You selected {x}", colors.orange),
|
||||
margin_x=20,
|
||||
margin_y=12,
|
||||
title = "Debug Selector",
|
||||
options = ["Tile Selector"],
|
||||
callback = lambda x: self.dbg_callback(x),
|
||||
margin_x = 20,
|
||||
margin_y = 12,
|
||||
)
|
||||
|
||||
def ev_mousemotion(self, event: tcod.event.MouseMotion) -> None:
|
||||
if self.engine.floor_map.in_bounds(event.tile.x, event.tile.y):
|
||||
self.engine.mouse_location = event.tile.x, event.tile.y
|
||||
self.engine.mouse_location = (event.tile.x, event.tile.y)
|
||||
|
||||
def dbg_callback(self, selected: int) -> Optional[BaseAction]:
|
||||
if selected == 0:
|
||||
player: Entity = self.engine.player
|
||||
|
||||
class GameOverHandler(EventHandler):
|
||||
"""Game over, man, GAME OVER!"""
|
||||
def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[BaseAction]:
|
||||
key = event.sym #SDL stuff, neat.
|
||||
|
||||
#player input
|
||||
if key == tcod.event.KeySym.ESCAPE:
|
||||
return QuitAction()
|
||||
|
||||
if key == tcod.event.KeySym.BACKQUOTE: #lowercase tilde
|
||||
self.engine.event_handler = LogHistoryViewer(self.engine, self)
|
||||
self.engine.event_handler = TileSelector(
|
||||
self.engine,
|
||||
self,
|
||||
floor_map = self.engine.floor_map,
|
||||
initial_pointer = (player.x, player.y),
|
||||
callback=lambda: None
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
@ -342,25 +368,44 @@ class InventoryViewer(EventHandler):
|
||||
return None #the selector does the change
|
||||
|
||||
def use(self) -> Optional[BaseAction]:
|
||||
"""Drop the item at the cursor's position, and adjust the cursor if needed."""
|
||||
"""Use the item at the cursor's position."""
|
||||
if self.length > 0:
|
||||
index = self.cursor
|
||||
item: Entity = self.entity.inventory.access(self.cursor)
|
||||
usable: BaseUseable = item.useable
|
||||
|
||||
return UsageAction(self.entity, index, self.entity, lambda x: self.adjust_length(x))
|
||||
#for ranged items, delegate to the tile selector
|
||||
if usable.maximum_range > 0:
|
||||
self.engine.event_handler = TileSelector(
|
||||
self.engine,
|
||||
parent_handler=self.engine.event_handler,
|
||||
floor_map = self.engine.floor_map,
|
||||
initial_pointer = (self.entity.x, self.entity.y),
|
||||
callback=lambda x, y: self.use_at_range(x, y)
|
||||
)
|
||||
else:
|
||||
#non-ranged items target the entity
|
||||
return UsageAction(self.entity, self.cursor, self.entity, lambda x: self.adjust_length(x))
|
||||
|
||||
def use_at_range(self, target_x: int, target_y: int) -> Optional[BaseAction]:
|
||||
#TODO: For now, just target living entities
|
||||
targets: List[Entity] = list(filter(lambda entity: entity.is_alive() and entity.x == target_x and entity.y == target_y, self.engine.floor_map.entities))
|
||||
|
||||
#TODO: skip targeting for AOE
|
||||
#TODO: close the inventory if you've used a consumable?
|
||||
if len(targets) > 0:
|
||||
return UsageAction(self.entity, self.cursor, targets.pop(), lambda x: self.adjust_length(x))
|
||||
else:
|
||||
self.engine.message_log.add_message("No target found.", colors.yellow)
|
||||
|
||||
def drop_partial_stack(self, amount: int) -> Optional[BaseAction]:
|
||||
"""Drop part of an item stack at the cursor's position, and adjust the cursor if needed."""
|
||||
if self.length > 0:
|
||||
index = self.cursor
|
||||
|
||||
return DropPartialStackAction(self.entity, index, amount, lambda x: self.adjust_length(x))
|
||||
return DropPartialStackAction(self.entity, self.cursor, amount, lambda x: self.adjust_length(x))
|
||||
|
||||
def drop(self) -> Optional[BaseAction]:
|
||||
"""Drop the item at the cursor's position, and adjust the cursor if needed."""
|
||||
if self.length > 0:
|
||||
index = self.cursor
|
||||
|
||||
return DropAction(self.entity, index, lambda x: self.adjust_length(x))
|
||||
return DropAction(self.entity, self.cursor, lambda x: self.adjust_length(x))
|
||||
|
||||
def adjust_length(self, amount: int):
|
||||
self.length += amount
|
||||
@ -446,3 +491,56 @@ class OptionSelector(EventHandler):
|
||||
else:
|
||||
#return to the game
|
||||
self.engine.event_handler = self.parent_handler
|
||||
|
||||
|
||||
class TileSelector(EventHandler):
|
||||
floor_map: FloorMap
|
||||
cursor_x: int
|
||||
cursor_y: int
|
||||
callback: function
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
engine,
|
||||
parent_handler,
|
||||
*,
|
||||
floor_map: FloorMap,
|
||||
initial_pointer: Tuple[int, int],
|
||||
callback: function,
|
||||
):
|
||||
super().__init__(engine, parent_handler)
|
||||
self.floor_map = floor_map
|
||||
self.cursor_x, self.cursor_y = initial_pointer
|
||||
self.callback = callback
|
||||
|
||||
def render(self, console: tcod.console.Console) -> None:
|
||||
#DON'T render the parent, instead, find and render the gameplay handler
|
||||
parent: EventHandler = self.parent_handler
|
||||
while parent and parent is not GameplayViewer:
|
||||
parent = parent.parent_handler
|
||||
if parent:
|
||||
parent.render(console)
|
||||
|
||||
#highlight via inverting colors
|
||||
console.rgb["fg"][self.cursor_x, self.cursor_y] ^= 0xff
|
||||
console.rgb["bg"][self.cursor_x, self.cursor_y] ^= 0xff
|
||||
|
||||
def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[BaseAction]:
|
||||
key = event.sym #SDL stuff, neat.
|
||||
|
||||
#special keys
|
||||
if key == tcod.event.KeySym.ESCAPE:
|
||||
#return to the game
|
||||
self.engine.event_handler = self.parent_handler
|
||||
|
||||
#selection keys
|
||||
elif key in TILE_SCROLL_KEYS:
|
||||
xdir, ydir = TILE_SCROLL_KEYS[key]
|
||||
|
||||
#because actions "only" change the game state, rather than selecting something, just move the cursor from here
|
||||
self.cursor_x += xdir
|
||||
self.cursor_y += ydir
|
||||
|
||||
elif key in TILE_CONFIRM_KEYS:
|
||||
self.engine.event_handler = self.parent_handler
|
||||
return self.callback(self.cursor_x, self.cursor_y)
|
||||
|
@ -38,7 +38,7 @@ def main() -> None:
|
||||
Message(" Text Log: Backtick Items: Tab", colors.terminal_dark),
|
||||
Message(" Quit: Esc", colors.terminal_dark),
|
||||
Message(" ~ ~ ~", colors.terminal_dark),
|
||||
Message("Welcome to the Cave of Gobbos!", colors.cyan),
|
||||
Message("Welcome to the Cave of Gobbos!", colors.teal),
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -95,7 +95,10 @@ def spawn_items(floor_map: FloorMap, room: RectangularRoom, room_items_max: int,
|
||||
|
||||
#if there's no entity at that position (not really needed for walkable entities)
|
||||
if not any(entity.x == x and entity.y == y for entity in floor_map.entities):
|
||||
entity_types.potion_of_healing.spawn(x, y, floor_map)
|
||||
if random.random() < 0.8:
|
||||
entity_types.scroll_of_lightning.spawn(x, y, floor_map)
|
||||
else:
|
||||
entity_types.potion_of_healing.spawn(x, y, floor_map)
|
||||
floor_map.procgen_cache["item_count"] += 1
|
||||
|
||||
#generators
|
||||
|
@ -3,7 +3,7 @@ from typing import TYPE_CHECKING
|
||||
|
||||
import colors
|
||||
|
||||
from event_handlers import GameOverHandler
|
||||
from event_handlers import GameoverViewer
|
||||
from useable import Unuseable
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -40,11 +40,11 @@ class Stats:
|
||||
engine: Engine = self.entity.floor_map.engine
|
||||
|
||||
if self.entity is engine.player and self.entity.ai: #handle game-over states
|
||||
engine.event_handler = GameOverHandler(engine)
|
||||
engine.event_handler = GameoverViewer(engine, engine.event_handler) #TODO: more elegant way to handle GameOver than effectively freezing
|
||||
engine.message_log.add_message("You died.", colors.red)
|
||||
|
||||
else:
|
||||
engine.message_log.add_message(f"The {self.entity.name} died", colors.yellow)
|
||||
engine.message_log.add_message(f"The {self.entity.name} died", colors.green)
|
||||
|
||||
#transform into a dead body
|
||||
#TODO: dummied in a "usable" to let dead objects be treated like items
|
||||
|
@ -7,15 +7,29 @@ if TYPE_CHECKING:
|
||||
from stats import Stats
|
||||
|
||||
class BaseUseable:
|
||||
"""Base type for useable items, with various utilities"""
|
||||
"""
|
||||
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
|
||||
consumable: bool
|
||||
minimum_range: int
|
||||
maximum_range: int
|
||||
|
||||
def __init__(self, *, current_stack: int = 1, maximum_stack: int = -1, consumable: bool = False):
|
||||
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.consumable = consumable
|
||||
self.minimum_range = minimum_range
|
||||
self.maximum_range = maximum_range
|
||||
|
||||
def apply(self, stats: Stats) -> bool:
|
||||
"""
|
||||
@ -87,4 +101,25 @@ class PotionOfHealing(BaseUseable):
|
||||
return self.reduce_stack()
|
||||
|
||||
def get_used_msg(self, appearance: str) -> Optional[str]:
|
||||
return f"You restored {self.__last_roll} health."
|
||||
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
|
Loading…
x
Reference in New Issue
Block a user