pipeworks_mud_mapper.models.map_file ==================================== .. py:module:: pipeworks_mud_mapper.models.map_file .. autoapi-nested-parse:: MapFile model for authoring exports. This module defines the MapFile model - the JSON export format used for authoring data when exporting from SQLite. Map files contain everything needed for visual map editing: rooms, exits, items, AND coordinates. SQLite + Export Workflow ------------------------ As described in ``goblin_cartography.md`` Section 2.4, the mapper now stores authoring data in SQLite and exports MapFile/Zone JSON on demand. File Naming Convention ---------------------- Map files use the ``.map.json`` extension to distinguish from zone files:: data/exports/maps/crooked_pipe.map.json # Authoring export (has coords) data/zones/crooked_pipe.json # Game truth (no coords) This makes it clear which file is the source and which is generated. Data Flow --------- :: SQLite DB ──► MapFile ──┬──► Export ──► exports/maps/zone.map.json │ └──► Export ──► zones/zone.json (strips coords) .. admonition:: Examples Loading a map file:: from pathlib import Path from pipeworks_mud_mapper.models import MapFile content = Path("data/exports/maps/my_zone.map.json").read_text() map_file = MapFile.model_validate_json(content) Creating a new map:: map_file = MapFile( id="new_zone", name="New Zone", spawn_room="spawn", rooms={ "spawn": MapRoom( id="spawn", name="Spawn Room", coords=Coords(x=0, y=0, z=0), ), }, ) Exporting to zone (strips coordinates):: zone = map_file.to_zone() Path("data/zones/new_zone.json").write_text( zone.model_dump_json(indent=2, exclude_none=True) ) .. seealso:: :py:obj:`Zone` Game truth format (no coordinates). :py:obj:`MapRoom` Room with coordinates. Classes ------- .. autoapisummary:: pipeworks_mud_mapper.models.map_file.MapFile Module Contents --------------- .. py:class:: MapFile(/, **data) Bases: :py:obj:`pydantic.BaseModel` A map file in authoring format (includes coordinates). MapFile is the primary data structure used by the mapper tool during editing. It contains all zone data plus coordinates for visualization. When exporting to a zone file (for the MUD server), use ``to_zone()`` which strips coordinates and converts MapRooms to Rooms. .. attribute:: id Unique zone identifier. Becomes the filename for both map and zone files. :type: :py:class:`str` .. attribute:: name Human-readable display name. :type: :py:class:`str` .. attribute:: metadata Versioning metadata used to pair map and zone exports. :type: :py:class:`MapMetadata` .. attribute:: description Optional zone description. :type: :py:class:`str` .. attribute:: spawn_room Room ID where players enter. Must exist in rooms dict. :type: :py:class:`str` .. attribute:: rooms Mapping of room ID to MapRoom objects (with coordinates). :type: :py:class:`dict[str`, :py:class:`MapRoom]` .. attribute:: items Mapping of item ID to item definitions. :type: :py:class:`dict[str`, :py:class:`dict]` .. admonition:: Examples >>> map_file = MapFile( ... id="tutorial", ... name="Tutorial Area", ... spawn_room="spawn", ... rooms={ ... "spawn": MapRoom( ... id="spawn", ... name="Start", ... coords=Coords(x=0, y=0, z=0), ... ), ... }, ... ) Converting to zone format:: >>> zone = map_file.to_zone() >>> type(zone) .. seealso:: :py:obj:`Zone` Game truth format without coordinates. :py:obj:`MapRoom` Room model with coordinates. .. py:method:: validate_id(v) :classmethod: Validate zone ID format. Zone IDs must start with a letter and contain only lowercase letters, numbers, and underscores. .. py:method:: validate_spawn_room_exists() Ensure spawn_room references an existing room. .. py:method:: validate_room_ids_match_keys() Ensure room IDs match their dictionary keys. .. py:method:: to_zone() Convert to game truth format by stripping coordinates. Creates a Zone instance suitable for export to the MUD server. All MapRooms are converted to Rooms (without coordinates). Versioning metadata is carried into the Zone metadata. :returns: A Zone instance without coordinates. :rtype: :py:class:`Zone` .. admonition:: Examples >>> map_file = MapFile( ... id="test", ... name="Test", ... spawn_room="spawn", ... rooms={"spawn": MapRoom(id="spawn", name="S", coords=Coords())}, ... ) >>> zone = map_file.to_zone() >>> hasattr(zone.rooms["spawn"], 'coords') False .. py:method:: bump_revision() Increment the authoring revision counter. This is called on every map save to provide a lightweight edit history. .. py:method:: bump_version() Increment the authoring milestone version. Map versions are stored as strings for flexibility, but must be numeric for auto-increment. If non-numeric, raise a clear error. .. py:method:: get_room(room_id) Get a room by ID. :param room_id: The room ID to look up. :type room_id: :py:class:`str` :returns: The room if found, None otherwise. :rtype: :py:class:`MapRoom` or :py:obj:`None` .. py:method:: get_room_at_coords(coords) Find a room at the given coordinates. :param coords: The coordinates to search for. :type coords: :py:class:`Coords` :returns: The room at those coordinates, or None if no room exists there. :rtype: :py:class:`MapRoom` or :py:obj:`None` .. admonition:: Examples >>> room = map_file.get_room_at_coords(Coords(x=0, y=0, z=0)) .. py:method:: find_room_in_direction(from_coords, direction, exclude_room = None) Find the nearest room in a given direction from coordinates. This method searches for rooms that lie in the specified direction from the given coordinates, regardless of distance. It's used for auto-connecting exits when the target room may not be at the exact adjacent coordinate. :param from_coords: Starting position to search from. :type from_coords: :py:class:`Coords` :param direction: Direction to search in (north, south, east, west, up, down). :type direction: :py:class:`Direction` :param exclude_room: Room ID to exclude from search (typically the source room). :type exclude_room: :py:class:`str`, *optional* :returns: The nearest room in that direction, or None if no room found. :rtype: :py:class:`MapRoom` or :py:obj:`None` .. admonition:: Examples >>> room = map_file.find_room_in_direction( ... Coords(x=0, y=0, z=0), ... "north", ... exclude_room="spawn", ... ) .. py:method:: add_room(room_id, name, coords, description = '') Add a new room to the map. :param room_id: Unique identifier for the room. :type room_id: :py:class:`str` :param name: Display name for the room. :type name: :py:class:`str` :param coords: Position in 3D space. :type coords: :py:class:`Coords` :param description: Room description text. :type description: :py:class:`str`, *optional* :returns: The newly created room. :rtype: :py:class:`MapRoom` :raises ValueError: If a room with that ID already exists. .. admonition:: Examples >>> room = map_file.add_room( ... room_id="new_room", ... name="New Room", ... coords=Coords(x=5, y=0, z=0), ... ) .. py:method:: create_exit(from_room_id, direction, to_room_id, bidirectional = True) Create an exit between two rooms. :param from_room_id: Source room ID. :type from_room_id: :py:class:`str` :param direction: Direction of the exit. :type direction: :py:class:`Direction` :param to_room_id: Target room ID. :type to_room_id: :py:class:`str` :param bidirectional: If True, also create the reverse exit. :type bidirectional: :py:class:`bool`, *default* :py:obj:`True` :raises ValueError: If either room doesn't exist. .. admonition:: Examples >>> map_file.create_exit("spawn", "north", "hallway") # Creates spawn->north->hallway AND hallway->south->spawn .. py:method:: remove_exit(from_room_id, direction, bidirectional = True) Remove an exit from a room. :param from_room_id: Source room ID. :type from_room_id: :py:class:`str` :param direction: Direction of the exit to remove. :type direction: :py:class:`Direction` :param bidirectional: If True, also remove the reverse exit from the target room. :type bidirectional: :py:class:`bool`, *default* :py:obj:`True` .. admonition:: Examples >>> map_file.remove_exit("spawn", "north") # Removes spawn->north AND (if exists) target->south->spawn .. py:method:: from_dict(data) :classmethod: Create MapFile from a dictionary (legacy format support). This method handles the legacy format where rooms have coords as lists rather than Coords objects. :param data: Map file data, potentially in legacy format. :type data: :py:class:`dict` :returns: New MapFile instance. :rtype: :py:class:`MapFile` .. admonition:: Examples >>> data = { ... "id": "test", ... "name": "Test", ... "spawn_room": "spawn", ... "rooms": { ... "spawn": { ... "id": "spawn", ... "name": "Spawn", ... "coords": [0, 0, 0], ... "exits": {}, ... "items": [], ... } ... }, ... "items": {}, ... } >>> map_file = MapFile.from_dict(data) .. py:method:: to_dict_with_list_coords() Export to dictionary with coords as lists (legacy format). This method produces output compatible with existing map files. It handles two format conversions: 1. **Coords as lists**: Converts ``{"x": 0, "y": 0, "z": 0}`` to ``[0, 0, 0]`` for backwards compatibility with existing map files. 2. **Datetime as ISO string**: Uses ``mode="json"`` serialization to convert datetime objects (like ``llm_generation.generated_at``) to ISO 8601 strings. This ensures JSON compatibility when saving map files. The ``llm_generation`` field (if present) contains a ``generated_at`` datetime that must be serialized for JSON storage. Using ``mode="json"`` handles this automatically, producing strings like ``"2024-01-15T10:30:00+00:00"``. :returns: Map file data with coords as [x, y, z] lists and datetimes as ISO strings. :rtype: :py:class:`dict` .. admonition:: Examples >>> data = map_file.to_dict_with_list_coords() >>> data["rooms"]["spawn"]["coords"] [0, 0, 0] With llm_generation metadata:: >>> room_data = data["rooms"]["spawn"] >>> room_data["llm_generation"]["generated_at"] '2024-01-15T10:30:00+00:00'