Inventory is visible, and dropping items is enabled
Menu windows can be nested inside each other.
This commit is contained in:
parent
13ac477bad
commit
5e52e166b1
@ -16,7 +16,7 @@ Here's a few potential options, which will absolutely change over time:
|
||||
* Fairy Tales and Fables (Brothers Grimm)
|
||||
* Secret Agent Espionage
|
||||
* Mythic Odyssey
|
||||
* Dreamlands/Cthulhu Horror
|
||||
* Dreamlands/Cthulhu Horror/Gothic Horror (accessible only during the full/new moon)
|
||||
* Stargates/Sliders
|
||||
* Isekai Protag Syndrome
|
||||
* Gunslingers (Wild West)
|
||||
|
@ -5,6 +5,10 @@
|
||||
A banana taped to a wall?
|
||||
A note taped to a wall that says "I. O. U. 1 Banana"
|
||||
Could also have an art gallery room
|
||||
"Dead Souls" - Each time you die, your past souls are collected, and you might be able to spend them to unlock something new.
|
||||
A lock in one dimension needs a key (password) in another?
|
||||
It could be interesting, not necssarily a good or fun idea, but if you had another "point" or currency. Where if you spend the first one you get some kind of "cursed knowledge" point and that affects the run somehow? So the more you use your forbidden knowledge the more weird things get?
|
||||
If you use IRL time and date as a mechanic, go big or go home. Maybe the horror dimension is only accessible during full/new moons?
|
||||
|
||||
## Healing
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
from __future__ import annotations
|
||||
from typing import List, TYPE_CHECKING
|
||||
from typing import List, Optional, TYPE_CHECKING
|
||||
|
||||
import colors
|
||||
from floor_map import FloorMap
|
||||
from inventory import Inventory
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from engine import Engine
|
||||
@ -123,6 +124,7 @@ class PickupAction(BaseAction):
|
||||
y = self.entity.y
|
||||
|
||||
floor_map: FloorMap = self.entity.floor_map
|
||||
engine: Engine = floor_map.engine
|
||||
|
||||
item_stack: List[Entity] = floor_map.get_all_entities_at(x, y, items_only=True)
|
||||
|
||||
@ -131,22 +133,55 @@ class PickupAction(BaseAction):
|
||||
elif len(item_stack) == 1:
|
||||
floor_map.entities.remove(item_stack[0])
|
||||
self.entity.inventory.insert(item_stack[0])
|
||||
floor_map.engine.message_log.add_message(f"you picked up a(n) {item_stack[0].name}", color=colors.terminal_light)
|
||||
engine.message_log.add_message(f"you picked up a(n) {item_stack[0].name}", 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_stack:#not pythonic, IDC
|
||||
for item in item_stack:
|
||||
options.append(item.name)
|
||||
|
||||
from event_handlers import OptionSelector #circular imports are a pain
|
||||
floor_map.engine.event_handler = OptionSelector(
|
||||
floor_map.engine,
|
||||
floor_map.engine.event_handler,
|
||||
options,
|
||||
lambda x: (
|
||||
floor_map.entities.remove(item_stack[x]),
|
||||
self.entity.inventory.insert(item_stack[x]),
|
||||
floor_map.engine.message_log.add_message(f"you picked up a(n) {item_stack[x].name}", color=colors.terminal_light)
|
||||
),
|
||||
engine.event_handler = OptionSelector(
|
||||
engine=floor_map.engine,
|
||||
parent_handler=engine.event_handler,
|
||||
title="Pick Up Item",
|
||||
options=options,
|
||||
callback=lambda x: self.pickup_callback(engine, floor_map, self.entity, item_stack[x])
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
#utils
|
||||
def pickup_callback(self, engine: Engine, floor_map: FloorMap, entity: Entity, item: Entity) -> None:
|
||||
floor_map.entities.remove(item)
|
||||
entity.inventory.insert(item)
|
||||
engine.message_log.add_message(f"you picked up a(n) {item.name}", color=colors.terminal_light)
|
||||
|
||||
class DropAction(BaseAction):
|
||||
"""Drop an item from an entity's inventory at the entity's location"""
|
||||
index: int
|
||||
|
||||
def __init__(self, entity: Entity, index: int):
|
||||
"""override the base __init__"""
|
||||
super().__init__(entity)
|
||||
self.index = index
|
||||
|
||||
def perform(self) -> bool:
|
||||
x = self.entity.x
|
||||
y = self.entity.y
|
||||
|
||||
inventory: Inventory = self.entity.inventory
|
||||
floor_map: FloorMap = self.entity.floor_map
|
||||
engine: Engine = floor_map.engine
|
||||
|
||||
item: Entity = inventory.withdraw(self.index)
|
||||
|
||||
item.x = x
|
||||
item.y = y
|
||||
|
||||
floor_map.entities.add(item)
|
||||
|
||||
engine.message_log.add_message(f"you dropped a(n) {item.name}", color=colors.terminal_light)
|
||||
|
||||
return True
|
||||
|
@ -20,7 +20,7 @@ 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)
|
||||
self.event_handler = GameplayHandler(self, None)
|
||||
self.mouse_position = (0, 0)
|
||||
|
||||
#map
|
||||
|
@ -29,5 +29,7 @@ gobbo_red = Entity(
|
||||
name = "Red Gobbo",
|
||||
walkable = False,
|
||||
ai_class = AggressiveWhenSeen,
|
||||
stats = Stats(hp = 5, attack = 2, defense = 5),
|
||||
stats = Stats(hp = 5, attack = 1, defense = 0), #this guy can't catch a break
|
||||
)
|
||||
|
||||
#TODO: healing potion, spawned in the map
|
@ -10,10 +10,12 @@ from actions import (
|
||||
BumpAction,
|
||||
WaitAction,
|
||||
PickupAction,
|
||||
DropAction,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from engine import Engine
|
||||
from entity import Entity
|
||||
|
||||
#input options
|
||||
MOVE_KEYS = {
|
||||
@ -76,16 +78,19 @@ CURSOR_CONFIRM_KEYS = {
|
||||
tcod.event.KeySym.SPACE,
|
||||
}
|
||||
|
||||
#the event handlers are one part of the engine
|
||||
#the event handlers are a big part of the engine
|
||||
class EventHandler(tcod.event.EventDispatch[BaseAction]):
|
||||
engine: Engine
|
||||
parent_handler: EventHandler
|
||||
|
||||
def __init__(self, engine: Engine):
|
||||
def __init__(self,engine: Engine, parent_handler: EventHandler):
|
||||
super().__init__()
|
||||
self.engine = engine
|
||||
self.parent_handler = parent_handler
|
||||
|
||||
def render(self, console: tcod.console.Console) -> None:
|
||||
pass #no-op
|
||||
if self.parent_handler:
|
||||
self.parent_handler.render(console)
|
||||
|
||||
#callbacks
|
||||
def ev_quit(self, event: tcod.event.Quit) -> Optional[BaseAction]:
|
||||
@ -113,10 +118,11 @@ class GameplayHandler(EventHandler):
|
||||
|
||||
player = self.engine.player
|
||||
|
||||
#player input
|
||||
#special keys
|
||||
if key == tcod.event.KeySym.ESCAPE:
|
||||
return QuitAction()
|
||||
|
||||
#gameplay keys
|
||||
if key in MOVE_KEYS:
|
||||
xdir, ydir = MOVE_KEYS[key]
|
||||
return BumpAction(player, xdir = xdir, ydir = ydir)
|
||||
@ -127,14 +133,16 @@ class GameplayHandler(EventHandler):
|
||||
if key in PICKUP_KEYS:
|
||||
return PickupAction(player)
|
||||
|
||||
if key == tcod.event.KeySym.o: #TODO: temove this
|
||||
self.engine.event_handler = OptionSelector(self.engine, self, ["zero", "one", "two", "three"], lambda x: print("You chose", x)) #TODO: remove this
|
||||
|
||||
#menu keys
|
||||
if key == tcod.event.KeySym.BACKQUOTE: #lowercase tilde
|
||||
self.engine.event_handler = LogHistoryViewer(self.engine, self)
|
||||
|
||||
# if key == tcod.event.KeySym.TAB:
|
||||
# self.engine.event_handler = InventoryViewer(self.engine, self) #TODO: deal with this
|
||||
if key == tcod.event.KeySym.TAB:
|
||||
self.engine.event_handler = InventoryViewer(self.engine, self, player)
|
||||
|
||||
#debugging
|
||||
if key == tcod.event.KeySym.o: #TODO: remove this
|
||||
self.engine.event_handler = OptionSelector(self.engine, self, options=["zero", "one", "two", "three"], callback=lambda x: print("You chose", x))
|
||||
|
||||
def ev_mousemotion(self, event: tcod.event.MouseMotion) -> None:
|
||||
if self.engine.floor_map.in_bounds(event.tile.x, event.tile.y):
|
||||
@ -157,20 +165,17 @@ class GameOverHandler(EventHandler):
|
||||
|
||||
|
||||
class LogHistoryViewer(EventHandler):
|
||||
baseEventHandler: EventHandler
|
||||
|
||||
def __init__(self, engine: Engine, baseEventHandler: EventHandler):
|
||||
super().__init__(engine)
|
||||
self.baseEventHandler = baseEventHandler
|
||||
self.log_length = len(engine.message_log.messages)
|
||||
self.cursor = self.log_length - 1
|
||||
def __init__(self, engine: Engine, parent_handler: EventHandler):
|
||||
super().__init__(engine, parent_handler)
|
||||
self.length = len(engine.message_log.messages)
|
||||
self.cursor = self.length - 1 #start at the bottom
|
||||
|
||||
def render(self, console: tcod.console.Console) -> None:
|
||||
super().render(console)
|
||||
|
||||
log_console = tcod.console.Console(console.width - 6, console.height - 6)
|
||||
|
||||
#rendering a nice log window
|
||||
#rendering a nice window
|
||||
log_console.draw_frame(
|
||||
0,0, log_console.width, log_console.height,
|
||||
# "╔═╗║ ║╚═╝"
|
||||
@ -189,44 +194,150 @@ class LogHistoryViewer(EventHandler):
|
||||
log_console.width - 4, log_console.height - 4,
|
||||
self.engine.message_log.messages[:self.cursor + 1]
|
||||
)
|
||||
log_console.blit(console, 3, 3) #into the middle
|
||||
|
||||
log_console.blit(console, 3, 3)
|
||||
|
||||
def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[BaseAction]:
|
||||
if event.sym in CURSOR_SCROLL_KEYS:
|
||||
adjust = CURSOR_SCROLL_KEYS[event.sym]
|
||||
if adjust < 0 and self.cursor == 0:
|
||||
pass #do nothing
|
||||
elif adjust > 0 and self.cursor == self.log_length - 1:
|
||||
elif adjust > 0 and self.cursor == self.length - 1:
|
||||
pass #do nothing
|
||||
else:
|
||||
self.cursor = max(0, min(self.log_length - 1, self.cursor + adjust))
|
||||
self.cursor = max(0, min(self.length - 1, self.cursor + adjust))
|
||||
|
||||
elif event.sym == tcod.event.KeySym.HOME:
|
||||
self.cursor = 0
|
||||
elif event.sym == tcod.event.KeySym.END:
|
||||
self.cursor = self.log_length - 1
|
||||
self.cursor = self.length - 1
|
||||
else:
|
||||
#return to the game
|
||||
self.engine.event_handler = self.baseEventHandler
|
||||
#return to the game - where's the any key?
|
||||
self.engine.event_handler = self.parent_handler
|
||||
|
||||
#generic tool
|
||||
class OptionSelector(EventHandler):
|
||||
baseEventHandler: EventHandler
|
||||
|
||||
def __init__(self, engine: Engine, baseEventHandler: EventHandler, options: List[str], callback: function):
|
||||
super().__init__(engine)
|
||||
self.baseEventHandler = baseEventHandler
|
||||
self.options = options
|
||||
self.callback = callback
|
||||
self.length = len(options)
|
||||
class InventoryViewer(EventHandler):
|
||||
def __init__(self, engine: Engine, parent_handler: EventHandler, entity: Entity): #this entity's inventory
|
||||
super().__init__(engine, parent_handler)
|
||||
self.entity = entity
|
||||
self.length = len(self.entity.inventory.contents)
|
||||
self.cursor = 0
|
||||
|
||||
def render(self, console: tcod.console.Console) -> None:
|
||||
super().render(console)
|
||||
|
||||
select_console = tcod.console.Console(console.width - 20, console.height - 16)
|
||||
inner_console = tcod.console.Console(console.width - 6, console.height - 6)
|
||||
|
||||
#rendering a nice list window
|
||||
#rendering a nice window
|
||||
inner_console.draw_frame(
|
||||
0,0, inner_console.width, inner_console.height,
|
||||
# "╔═╗║ ║╚═╝"
|
||||
decoration="\\x/x x/x\\",
|
||||
fg=colors.terminal_dark, bg=colors.black
|
||||
)
|
||||
inner_console.print_box(
|
||||
0, 0, inner_console.width, inner_console.height,
|
||||
string = "Inventory",
|
||||
alignment=tcod.constants.CENTER,
|
||||
fg=colors.terminal_light, bg=colors.black
|
||||
)
|
||||
|
||||
#render the cursor & inventory contents
|
||||
offset: int = 0
|
||||
for item in self.entity.inventory.contents:
|
||||
inner_console.print(
|
||||
4, 2 + offset,
|
||||
string = item.name,
|
||||
fg=colors.terminal_light, bg=colors.black,
|
||||
)
|
||||
offset += 1
|
||||
|
||||
if self.length > 0:
|
||||
inner_console.print(2, 2 + self.cursor, string = ">", fg=colors.terminal_light, bg=colors.black)
|
||||
else:
|
||||
#if inventory is empty, show a default message
|
||||
inner_console.print_box(
|
||||
0,inner_console.height // 2, inner_console.width, inner_console.height,
|
||||
string = "EMPTY",
|
||||
fg=colors.terminal_dark, bg=colors.black,
|
||||
alignment=tcod.constants.CENTER
|
||||
)
|
||||
|
||||
inner_console.blit(console, 3, 3)
|
||||
|
||||
def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[BaseAction]:
|
||||
if event.sym in CURSOR_SCROLL_KEYS:
|
||||
adjust = CURSOR_SCROLL_KEYS[event.sym]
|
||||
if adjust < 0 and self.cursor == 0:
|
||||
pass #do nothing
|
||||
elif adjust > 0 and self.cursor == self.length - 1:
|
||||
pass #do nothing
|
||||
else:
|
||||
self.cursor = max(0, min(self.length - 1, self.cursor + adjust))
|
||||
|
||||
#extra size check, so empty inventories still work
|
||||
elif self.length > 0 and event.sym in CURSOR_CONFIRM_KEYS:
|
||||
#drop an item form this entity's inventory
|
||||
item: Entity = self.entity.inventory.access(self.cursor)
|
||||
|
||||
self.engine.event_handler = OptionSelector(self.engine, self,
|
||||
title=f"Drop The {item.name}?",
|
||||
options=["Yes", "No"],
|
||||
callback=lambda x: self.drop_callback(x)
|
||||
)
|
||||
|
||||
elif event.sym == tcod.event.KeySym.HOME:
|
||||
self.cursor = 0
|
||||
elif event.sym == tcod.event.KeySym.END:
|
||||
self.cursor = self.length - 1
|
||||
else:
|
||||
#return to the game - where's the any key?
|
||||
self.engine.event_handler = self.parent_handler
|
||||
|
||||
#utils
|
||||
def drop_callback(self, answer: int) -> Optional[BaseAction]:
|
||||
#process the answer, giving the signal of what to do
|
||||
if answer == 0:
|
||||
c = self.cursor
|
||||
|
||||
#bounds
|
||||
self.length -= 1
|
||||
if self.cursor >= self.length:
|
||||
self.cursor = self.length - 1
|
||||
|
||||
return DropAction(self.entity, c)
|
||||
|
||||
|
||||
#generic tools
|
||||
class OptionSelector(EventHandler):
|
||||
def __init__(
|
||||
self,
|
||||
engine: Engine,
|
||||
parent_handler: EventHandler,
|
||||
*,
|
||||
options: List[str],
|
||||
callback: function,
|
||||
title: str = "Select Option",
|
||||
margin_x: int = 10,
|
||||
margin_y: int = 8
|
||||
):
|
||||
super().__init__(engine, parent_handler)
|
||||
self.options = options
|
||||
self.callback = callback
|
||||
self.length = len(options)
|
||||
self.cursor = 0
|
||||
|
||||
#graphical prettiness
|
||||
self.title = title
|
||||
self.margin_x = margin_x
|
||||
self.margin_y = margin_y
|
||||
|
||||
def render(self, console: tcod.console.Console) -> None:
|
||||
super().render(console)
|
||||
|
||||
select_console = tcod.console.Console(console.width - self.margin_x*2, console.height - self.margin_y*2)
|
||||
|
||||
#rendering a nice window
|
||||
select_console.draw_frame(
|
||||
0,0, select_console.width, select_console.height,
|
||||
# "╔═╗║ ║╚═╝"
|
||||
@ -235,13 +346,13 @@ class OptionSelector(EventHandler):
|
||||
)
|
||||
select_console.print_box(
|
||||
0, 0, select_console.width, select_console.height,
|
||||
string = "Select One",
|
||||
string = self.title,
|
||||
alignment=tcod.constants.CENTER,
|
||||
fg=colors.terminal_light, bg=colors.black
|
||||
)
|
||||
|
||||
#render the cursor & options
|
||||
offset = 0
|
||||
offset: int = 0
|
||||
for option in self.options:
|
||||
select_console.print(
|
||||
4, 2 + offset,
|
||||
@ -252,7 +363,7 @@ class OptionSelector(EventHandler):
|
||||
|
||||
select_console.print(2, 2 + self.cursor, string = ">", fg=colors.terminal_light, bg=colors.black)
|
||||
|
||||
select_console.blit(console, 10, 8)
|
||||
select_console.blit(console, self.margin_x, self.margin_y)
|
||||
|
||||
def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[BaseAction]:
|
||||
if event.sym in CURSOR_SCROLL_KEYS:
|
||||
@ -265,9 +376,8 @@ class OptionSelector(EventHandler):
|
||||
self.cursor = max(0, min(self.length - 1, self.cursor + adjust))
|
||||
|
||||
elif event.sym in CURSOR_CONFIRM_KEYS:
|
||||
#got the answer
|
||||
self.callback(self.cursor)
|
||||
self.engine.event_handler = self.baseEventHandler
|
||||
self.engine.event_handler = self.parent_handler
|
||||
return self.callback(self.cursor) #confirm this selection, and exit
|
||||
|
||||
elif event.sym == tcod.event.KeySym.HOME:
|
||||
self.cursor = 0
|
||||
@ -275,4 +385,4 @@ class OptionSelector(EventHandler):
|
||||
self.cursor = self.length - 1
|
||||
else:
|
||||
#return to the game
|
||||
self.engine.event_handler = self.baseEventHandler
|
||||
self.engine.event_handler = self.parent_handler
|
||||
|
@ -1,31 +1,37 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional, Set, TYPE_CHECKING
|
||||
from typing import List, Optional, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from entity import Entity
|
||||
|
||||
class Inventory:
|
||||
"""Handles inventory for an Entity"""
|
||||
_contents: Set[Entity]
|
||||
_contents: List[Entity]
|
||||
|
||||
def __init__(self, contents: Set[Entity] = set()):
|
||||
def __init__(self, contents: List[Entity] = []):
|
||||
self._contents = contents
|
||||
|
||||
def insert(self, entity: Entity) -> bool:
|
||||
if entity in self._contents:
|
||||
return False
|
||||
|
||||
self._contents.add(entity)
|
||||
self._contents.append(entity)
|
||||
return True
|
||||
|
||||
def access(self, key: str) -> Optional[Entity]:
|
||||
return self._contents[key]
|
||||
def access(self, index: int) -> Optional[Entity]:
|
||||
if index < 0 or index >= len(self._contents):
|
||||
return None
|
||||
else:
|
||||
return self._contents[index]
|
||||
|
||||
def remove(self, key: str) -> Optional[Entity]:
|
||||
item = self._contents[key]
|
||||
self._contents.remove(key)
|
||||
return item
|
||||
def withdraw(self, index: int) -> Optional[Entity]:
|
||||
if index < 0 or index >= len(self._contents):
|
||||
return None
|
||||
else:
|
||||
return self._contents.pop(index)
|
||||
|
||||
@property
|
||||
def contents(self) -> Set[Entity]:
|
||||
def contents(self) -> List[Entity]:
|
||||
return self._contents
|
||||
|
||||
#TODO: items need a weight, inventory needs a max capacity
|
Loading…
x
Reference in New Issue
Block a user