Files
Kanjin/scene.py

214 lines
8.9 KiB
Python

import sys
# Add the current directory to sys.path to allow imports from sibling files
sys.path.append('.')
from player import Item, Weapon, Armor, rock, dagger, polearm, tornRags, paper, GP1, SP1, CP1
from enum_list import *
class Container:
def __init__(self, name, description, lock_dc=None, trap_dc=None):
self.name = name
self.description = description
self.lock_dc = lock_dc
self.trap_dc = trap_dc
self.trap_disarmed = False
self.contents = {}
@property
def is_locked(self):
return self.lock_dc is not None
@property
def is_trapped(self):
return self.trap_dc is not None
def add_item(self, item_obj, count=1):
self.contents[item_obj] = self.contents.get(item_obj, 0) + count
def remove_item(self, item_obj, count=1):
if item_obj in self.contents:
self.contents[item_obj] -= count
if self.contents[item_obj] <= 0:
del self.contents[item_obj]
def has_items(self):
return bool(self.contents)
class Scene:
"""
Represents a single location or area in the game.
Each scene has a name, a description, and defines its exits to other scenes.
It can also contain items that can be looted from containers/bodies or
items that are simply available to be picked up.
"""
def __init__(self, name, description, exits=None, lootable_items=None, available_items=None):
self.name = name
self.description = description
# Exits are a dictionary mapping direction (str) to Scene objects
self.exits = exits if exits is not None else {}
# Lootable items are typically found within containers or on enemies.
# Format: {"container_name": {item_obj: count, ...}, ...}
self.lootable_items = lootable_items if lootable_items is not None else {}
# Available items are items lying around in the scene.
# Format: {item_obj: count, ...}
self.available_items = available_items if available_items is not None else {}
def __str__(self):
"""Returns the description of the scene."""
return self.description
def add_exit(self, direction, scene):
"""
Adds an exit from this scene to another scene.
:param direction: The direction (e.g., "north", "east") as a string.
:param scene: The Scene object to link to.
"""
self.exits[direction.lower()] = scene
def get_exit(self, direction):
"""
Returns the Scene object for a given direction, or None if no exit exists.
:param direction: The direction to check.
:return: The Scene object or None.
"""
return self.exits.get(direction.lower())
def add_lootable_item(self, container_name, item_obj, count=1):
if container_name not in self.lootable_items:
self.lootable_items[container_name] = Container(container_name, "")
self.lootable_items[container_name].add_item(item_obj, count)
def add_container(self, container):
self.lootable_items[container.name] = container
def add_available_item(self, item_obj, count=1):
"""
Adds an item that is freely available in the scene.
:param item_obj: The Item object to add.
:param count: The quantity of the item.
"""
self.available_items[item_obj] = self.available_items.get(item_obj, 0) + count
def remove_available_item(self, item_obj, count=1):
"""
Removes a specified count of an available item from the scene.
:param item_obj: The Item object to remove.
:param count: The quantity to remove.
"""
if item_obj in self.available_items:
self.available_items[item_obj] -= count
if self.available_items[item_obj] <= 0:
del self.available_items[item_obj]
def remove_lootable_item(self, container_name, item_obj, count=1):
if container_name in self.lootable_items:
self.lootable_items[container_name].remove_item(item_obj, count)
if not self.lootable_items[container_name].has_items():
del self.lootable_items[container_name]
def add_corpse(self, enemy_name, items):
"""Creates a lootable corpse from a defeated enemy.
Multiple corpses of the same enemy are numbered: 'spider corpse', 'spider corpse 2', etc.
items: dict of {item_obj: count} or iterable of (item_obj, count) pairs.
"""
base_name = f"{enemy_name.lower()} corpse"
name = base_name
counter = 1
while name in self.lootable_items:
counter += 1
name = f"{base_name} {counter}"
corpse = Container(name, f"The lifeless body of a {enemy_name}.")
if isinstance(items, dict):
for item_obj, count in items.items():
corpse.add_item(item_obj, count)
else:
for item_obj, count in items:
corpse.add_item(item_obj, count)
self.lootable_items[name] = corpse
# --- Default Scenes ---
# Scene 0: Tutorial
tutorial = Scene(
name="Tutorial",
description="Welcome to Kanjin Text RPG!\n\n"
"This is a very simple tutorial to show you the basics of issuing commands.\n"
"Important commands you'll want to know can be found by typing 'Help'.\n\n"
"These are the available commands:\n"
"Help - Prints this help message.\n"
"Check inventory | bag | backpack - Lists items you are carrying.\n"
" Can also just enter 'inventory | bag | backpack\n"
"Check equipment | equipped | items - Lists items you have equipped\n"
" Can also just enter 'equipment | equipped | items\n"
"Check stats or Check hitpoints | hp | health - Shows your stat block or current health details\n"
" Can also just enter 'stats | health | hitpoints | hp\n"
"Go *direction* - Each scene will give you available directions i.e North\n"
"Enter cave | house | room - maybe... TBC\n"
"Scene or Location - Replays the current area's details\n"
"Look around - lists the available items in your scene without displaying the scene description.\n"
"Examine | Open | Loot *object* - Provides details of an item, opens a container, or takes an object."
)
tutorial.add_available_item(rock, 1)
tutorial.add_available_item(paper, 2)
tutorial_trunk = Container("suspicious tree trunk", "A hollow tree trunk with a small gap in the bark.")
tutorial_trunk.add_item(Item("Test Item", "This is for testing purposes", None, False, ItemType.Item, False), 1)
tutorial.add_container(tutorial_trunk)
# Scene 1: Starting Clearing
starting_clearing = Scene(
name="Starting Clearing",
description="You are in a small clearing, surrounded by tall, ancient trees. "
"A faint, overgrown path leads north, and a darker, narrower trail goes east. "
"Sunlight filters through the canopy, dappling the forest floor. "
"You notice a small, weathered wooden chest near a fallen log."
)
starting_clearing.add_available_item(rock, 3) # Rocks lying on the ground
starting_clearing_chest = Container("small wooden chest", "A small, weathered wooden chest with rusty iron bands.")
starting_clearing_chest.add_item(GP1, 15)
starting_clearing_chest.add_item(SP1, 20)
starting_clearing.add_container(starting_clearing_chest)
# Scene 2: Forest Path
forest_path = Scene(
name="Forest Path",
description="The path winds deeper into the whisper-quiet forest. The trees here are "
"ancient and gnarled, their branches intertwining overhead, creating a dense canopy. "
"The air is thick with the scent of pine needles and damp earth. "
"You hear the distant caw of a crow. The clearing is to the south."
)
forest_path.add_available_item(dagger, 1) # A lone dagger dropped on the path
# Scene 3: Dark Cave Entrance
dark_cave_entrance = Scene(
name="Dark Cave Entrance",
description="The narrow trail ends abruptly at the jagged mouth of a dark, foreboding cave. "
"A chilling, damp wind blows from within, carrying the faint smell of mildew and something else... "
"something ancient. The entrance is shrouded in thick vines. "
"The clearing is to the west."
)
dark_cave_entrance.add_available_item(polearm, 1) # A polearm leaning against the cave wall
# Link the scenes together
tutorial.add_exit("tutorial", starting_clearing)
starting_clearing.add_exit("north", forest_path)
starting_clearing.add_exit("east", dark_cave_entrance)
forest_path.add_exit("south", starting_clearing)
dark_cave_entrance.add_exit("west", starting_clearing)
# Global map for easy scene access (optional, could also be managed by an Engine class)
Map = {
"Starting Clearing": starting_clearing,
"Forest Path": forest_path,
"Dark Cave Entrance": dark_cave_entrance,
"Tutorial": tutorial,
}