diff --git a/.gitignore b/.gitignore index 3f9a08b..76254db 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -adventure/custom_actions.py +adventure/custom_*.py worlds/ __pycache__/ .env diff --git a/adventure/generate.py b/adventure/generate.py index 8209f50..8904d07 100644 --- a/adventure/generate.py +++ b/adventure/generate.py @@ -9,6 +9,13 @@ from adventure.models import Actor, Item, Room, World logger = getLogger(__name__) +OPPOSITE_DIRECTIONS = { + "north": "south", + "south": "north", + "east": "west", + "west": "east", +} + def generate_room( agent: Agent, world_theme: str, existing_rooms: List[str], callback @@ -147,13 +154,14 @@ def generate_world( existing_actors: List[str] = [] existing_items: List[str] = [] + existing_rooms: List[str] = [] # generate the rooms rooms = [] for i in range(room_count): - existing_rooms = [room.name for room in rooms] room = generate_room(agent, theme, existing_rooms, callback=callback) rooms.append(room) + existing_rooms.append(room.name) item_count = randint(0, 3) callback(f"Generating {item_count} items for room: {room.name}") @@ -198,13 +206,6 @@ def generate_world( actor.items.append(item) existing_items.append(item.name) - opposite_directions = { - "north": "south", - "south": "north", - "east": "west", - "west": "east", - } - # generate portals to link the rooms together for room in rooms: directions = ["north", "south", "east", "west"] @@ -213,7 +214,7 @@ def generate_world( logger.debug(f"Room {room.name} already has a {direction} portal") continue - opposite_direction = opposite_directions[direction] + opposite_direction = OPPOSITE_DIRECTIONS[direction] if randint(0, 1): dest_room = choice([r for r in rooms if r.name != room.name]) @@ -233,7 +234,7 @@ def generate_world( # create bidirectional links room.portals[direction] = dest_room.name - dest_room.portals[opposite_directions[direction]] = room.name + dest_room.portals[OPPOSITE_DIRECTIONS[direction]] = room.name # ensure actors act in a stable order order = [actor.name for room in rooms for actor in room.actors] diff --git a/adventure/main.py b/adventure/main.py index 8fabc8e..c79160a 100644 --- a/adventure/main.py +++ b/adventure/main.py @@ -1,4 +1,3 @@ -from importlib import import_module from json import load from logging.config import dictConfig from os import environ, path @@ -12,6 +11,7 @@ from packit.toolbox import Toolbox from packit.utils import logger_with_colors from adventure.context import set_current_broadcast +from adventure.plugins import load_plugin # Configure logging LOG_PATH = "logging.json" @@ -209,6 +209,9 @@ def parse_args(): parser.add_argument( "--max-rooms", type=int, help="The maximum number of rooms to generate" ) + parser.add_argument( + "--optional-actions", type=bool, help="Whether to include optional actions" + ) parser.add_argument( "--server", type=str, help="The address on which to run the server" ) @@ -326,6 +329,16 @@ def main(): ) extra_actions.extend(module_actions) + if args.optional_actions: + logger.info("Loading optional actions") + from adventure.optional_actions import init as init_optional_actions + + optional_actions = init_optional_actions() + logger.info( + f"Loaded optional actions: {[action.__name__ for action in optional_actions]}" + ) + extra_actions.extend(optional_actions) + # load extra systems def snapshot_system(world: World, step: int) -> None: logger.debug("Snapshotting world state") @@ -356,12 +369,5 @@ def main(): ) -def load_plugin(name): - module_name, function_name = name.rsplit(":", 1) - plugin_module = import_module(module_name) - plugin_entry = getattr(plugin_module, function_name) - return plugin_entry() - - if __name__ == "__main__": main() diff --git a/adventure/plugins.py b/adventure/plugins.py new file mode 100644 index 0000000..fab5687 --- /dev/null +++ b/adventure/plugins.py @@ -0,0 +1,12 @@ +from importlib import import_module + + +def load_plugin(name: str, override_function: str | None = None): + plugin_entry = get_plugin_function(name, override_function) + return plugin_entry() + + +def get_plugin_function(name: str, override_function: str | None = None): + module_name, function_name = name.rsplit(":", 1) + plugin_module = import_module(module_name) + return getattr(plugin_module, override_function or function_name) diff --git a/adventure/systems/logic.py b/adventure/systems/logic.py index 98c9ee6..bab9c3e 100644 --- a/adventure/systems/logic.py +++ b/adventure/systems/logic.py @@ -5,7 +5,8 @@ from typing import Dict, List, Optional from pydantic import Field from yaml import Loader, load -from adventure.models import World, dataclass +from adventure.models import Actor, Item, Room, World, dataclass +from adventure.plugins import get_plugin_function logger = getLogger(__name__) @@ -22,6 +23,7 @@ class LogicRule: chance: float = 1.0 remove: Optional[List[str]] = None set: Optional[Dict[str, str]] = None + trigger: Optional[List[str]] = None @dataclass @@ -32,10 +34,17 @@ class LogicTable: with open("./worlds/logic.yaml") as file: logic_rules = LogicTable(**load(file, Loader=Loader)) + logic_triggers = { + rule: [get_plugin_function(trigger) for trigger in rule.trigger] + for rule in logic_rules.rules + if rule.trigger + } def update_attributes( - attributes: Dict[str, str], dataset: LogicTable + entity: Room | Actor | Item, + attributes: Dict[str, str], + dataset: LogicTable, ) -> Dict[str, str]: for rule in dataset.rules: if rule.match.items() <= attributes.items(): @@ -52,18 +61,22 @@ def update_attributes( for key in rule.remove or []: attributes.pop(key, None) + if rule in logic_triggers: + for trigger in logic_triggers[rule]: + attributes = trigger(entity, attributes) + return attributes def update_logic(world: World, step: int) -> None: for room in world.rooms: - room.attributes = update_attributes(room.attributes, logic_rules) + room.attributes = update_attributes(room, room.attributes, logic_rules) for actor in room.actors: - actor.attributes = update_attributes(actor.attributes, logic_rules) + actor.attributes = update_attributes(actor, actor.attributes, logic_rules) for item in actor.items: - item.attributes = update_attributes(item.attributes, logic_rules) + item.attributes = update_attributes(item, item.attributes, logic_rules) for item in room.items: - item.attributes = update_attributes(item.attributes, logic_rules) + item.attributes = update_attributes(item, item.attributes, logic_rules) logger.info("updated world attributes")