1
0
Fork 0
taleweave-ai/taleweave/editor.py

384 lines
12 KiB
Python
Raw Normal View History

import argparse
from os import environ, path
from typing import List, Tuple
from dotenv import load_dotenv
from packit.utils import logger_with_colors
from taleweave.context import get_dungeon_master, get_game_systems, set_game_systems
from taleweave.engine import load_or_initialize_system_data
from taleweave.game_system import GameSystem
from taleweave.generate import (
generate_character,
generate_item,
generate_portals,
generate_room,
link_rooms,
)
from taleweave.main import load_prompt_library
from taleweave.models.base import dump_model
from taleweave.models.entity import World, WorldState
from taleweave.plugins import load_plugin
from taleweave.utils.file import load_yaml, save_yaml
from taleweave.utils.search import (
find_character,
find_item,
find_portal,
find_room,
list_characters,
list_items,
list_portals,
list_rooms,
)
from taleweave.utils.world import describe_entity
ENTITY_TYPES = ["room", "portal", "item", "character"]
logger = logger_with_colors(__name__)
# load environment variables before anything else
load_dotenv(environ.get("TALEWEAVE_ENV", ".env"), override=True)
def parse_args():
parser = argparse.ArgumentParser(description="Taleweave Editor")
parser.add_argument("--prompts", type=str, nargs="*", help="Prompt files to load")
parser.add_argument("--state", type=str, help="State file to edit")
parser.add_argument("--world", type=str, help="World file to edit")
parser.add_argument("--systems", type=str, nargs="*", help="Game systems to load")
subparsers = parser.add_subparsers(dest="command", help="Command to execute")
subparsers.required = True
# Set up the 'list' command
list_parser = subparsers.add_parser("list", help="List entities of a specific type")
list_parser.add_argument(
"type", help="Type of entity to list", choices=ENTITY_TYPES
)
# Set up the 'describe' command
describe_parser = subparsers.add_parser("describe", help="Describe an entity")
describe_parser.add_argument(
"type", help="Type of entity to describe", choices=ENTITY_TYPES
)
describe_parser.add_argument("entity", type=str, help="Entity to describe")
# Set up the 'create' command
create_parser = subparsers.add_parser("create", help="Create an entity")
create_parser.add_argument(
"type", help="Type of entity to create", choices=ENTITY_TYPES
)
create_parser.add_argument("name", type=str, help="Name of the entity to create")
create_parser.add_argument("--room", type=str, help="Room the entity is in")
# Set up the 'generate' command
generate_parser = subparsers.add_parser("generate", help="Generate an entity")
generate_parser.add_argument(
"type", help="Type of entity to generate", choices=ENTITY_TYPES
)
generate_parser.add_argument(
"prompt", type=str, help="Prompt to generate the entity"
)
generate_parser.add_argument("--room", type=str, help="Room the entity is in")
generate_parser.add_argument(
"--dest-room", type=str, help="Destination room for portals"
)
# Set up the 'delete' command
delete_parser = subparsers.add_parser("delete", help="Delete an entity")
delete_parser.add_argument(
"type", help="Type of entity to delete", choices=ENTITY_TYPES
)
delete_parser.add_argument("entity", type=str, help="Entity to delete")
# Set up the 'update' command
update_parser = subparsers.add_parser("update", help="Update an entity")
update_parser.add_argument(
"type", help="Type of entity to update", choices=ENTITY_TYPES
)
update_parser.add_argument("entity", type=str, help="Entity to update")
update_parser.add_argument("--backstory", type=str, help="Backstory of the entity")
update_parser.add_argument(
"--description", type=str, help="Description of the entity"
)
# Set up the 'link' command
link_parser = subparsers.add_parser("link", help="Link rooms")
link_parser.add_argument(
"rooms",
type=str,
nargs="*",
help="Rooms to link. Leave blank to link all rooms.",
)
return parser.parse_args()
def load_world(state_file, world_file) -> Tuple[World, WorldState | None]:
systems = get_game_systems()
if state_file and path.exists(state_file):
with open(state_file, "r") as f:
state = WorldState(**load_yaml(f))
load_or_initialize_system_data(world_file, systems, state.world)
return (state.world, state)
if world_file and path.exists(world_file):
with open(world_file, "r") as f:
world = World(**load_yaml(f))
load_or_initialize_system_data(world_file, systems, world)
return (world, None)
raise ValueError("No state or world file found")
def save_world(state_file, world_file, world: World, state: WorldState | None):
"""
Save the world to the given files.
This is intentionally a noop stub until the editor is more stable.
"""
if state:
logger.warning(f"Saving world {world.name} to {state_file}")
return
with open(state_file, "w") as f:
save_yaml(f, dump_model(WorldState, state))
else:
logger.warning(f"Saving world {world.name} to {world_file}")
return
with open(world_file, "w") as f:
save_yaml(f, dump_model(World, world))
def command_list(args):
world, _ = load_world(args.state, args.world)
logger.info(f"Listing {args.type}s from world {world.name}")
if args.type == "room":
for room in list_rooms(world):
logger.info(room.name)
if args.type == "portal":
for portal in list_portals(world):
logger.info(portal.name)
if args.type == "item":
for item in list_items(
world, include_character_inventory=True, include_item_inventory=True
):
logger.info(item.name)
if args.type == "character":
for character in list_characters(world):
logger.info(character.name)
def command_describe(args):
world, _ = load_world(args.state, args.world)
logger.info(f"Describing {args.entity} from world {world.name}")
if args.type == "room":
room = find_room(world, args.entity)
if not room:
logger.error(f"Room {args.entity} not found")
else:
logger.info(describe_entity(room))
if args.type == "portal":
portal = find_portal(world, args.entity)
if not portal:
logger.error(f"Portal {args.entity} not found")
else:
logger.info(describe_entity(portal))
if args.type == "item":
item = find_item(
world,
args.entity,
include_character_inventory=True,
include_item_inventory=True,
)
if not item:
logger.error(f"Item {args.entity} not found")
else:
logger.info(describe_entity(item))
if args.type == "character":
character = find_character(world, args.entity)
if not character:
logger.error(f"Character {args.entity} not found")
else:
logger.info(describe_entity(character))
def command_create(args):
world, state = load_world(args.state, args.world)
logger.info(f"Create {args.type} named {args.name} in world {world.name}")
# TODO: Create the entity
save_world(args.state, args.world, world, state)
def command_generate(args):
world, state = load_world(args.state, args.world)
logger.info(
f"Generating {args.type} for world {world.name} using prompt: {args.prompt}"
)
dungeon_master = get_dungeon_master()
systems = get_game_systems()
if args.type == "room":
room = generate_room(dungeon_master, world, systems)
world.rooms.append(room)
if args.type == "portal":
source_room = find_room(world, args.room)
if not source_room:
logger.error(f"Room {args.room} not found")
return
destination_room = find_room(world, args.dest_room)
if not destination_room:
logger.error(f"Room {args.dest_room} not found")
return
outgoing_portal, incoming_portal = generate_portals(
dungeon_master, world, source_room, destination_room, systems
)
source_room.portals.append(outgoing_portal)
destination_room.portals.append(incoming_portal)
if args.type == "item":
# TODO: add item to character or container inventory
room = find_room(world, args.room)
if not room:
logger.error(f"Room {args.room} not found")
return
item = generate_item(dungeon_master, world, systems)
room.items.append(item)
if args.type == "character":
room = find_room(world, args.room)
if not room:
logger.error(f"Room {args.room} not found")
return
character = generate_character(
dungeon_master, world, systems, room, args.prompt
)
room.characters.append(character)
save_world(args.state, args.world, world, state)
def command_delete(args):
world, state = load_world(args.state, args.world)
logger.info(f"Delete {args.entity} from world {world.name}")
# TODO: Delete the entity
save_world(args.state, args.world, world, state)
def command_update(args):
world, state = load_world(args.state, args.world)
logger.info(f"Update {args.entity} in world {world.name}")
if args.type == "room":
room = find_room(world, args.entity)
if not room:
logger.error(f"Room {args.entity} not found")
else:
logger.info(describe_entity(room))
if args.type == "portal":
portal = find_portal(world, args.entity)
if not portal:
logger.error(f"Portal {args.entity} not found")
else:
logger.info(describe_entity(portal))
if args.type == "item":
item = find_item(
world,
args.entity,
include_character_inventory=True,
include_item_inventory=True,
)
if not item:
logger.error(f"Item {args.entity} not found")
else:
logger.info(describe_entity(item))
if args.type == "character":
character = find_character(world, args.entity)
if not character:
logger.error(f"Character {args.entity} not found")
else:
if args.backstory:
character.backstory = args.backstory
if args.description:
character.description = args.description
logger.info(describe_entity(character))
save_world(args.state, args.world, world, state)
def command_link(args):
world, state = load_world(args.state, args.world)
logger.info(f"Linking rooms {args.rooms} in world {world.name}")
dungeon_master = get_dungeon_master()
systems = get_game_systems()
link_rooms(dungeon_master, world, systems)
save_world(args.state, args.world, world, state)
COMMAND_TABLE = {
"list": command_list,
"describe": command_describe,
"create": command_create,
"generate": command_generate,
"delete": command_delete,
"update": command_update,
"link": command_link,
}
def main():
args = parse_args()
logger.debug(f"running with args: {args}")
load_prompt_library(args)
# load game systems before executing commands
systems: List[GameSystem] = []
for system_name in args.systems or []:
logger.info(f"loading extra systems from {system_name}")
module_systems = load_plugin(system_name)
logger.info(f"loaded extra systems: {module_systems}")
systems.extend(module_systems)
set_game_systems(systems)
command = COMMAND_TABLE[args.command]
command(args)
if __name__ == "__main__":
main()