from __future__ import annotations from dataclasses import dataclass, field from engine.defs import DefRegistry from engine.map_loader import MapLoader from engine.world import World @dataclass(frozen=True) class WorldTemplate: """One selectable starting-world option on the 'create world' screen. For now every template is just a fixed map file this engine ships with. Planned follow-up game modes (campaign/survival/creative) will add templates that build a World from procedural generation instead of a map_file - map_file is left optional so those can slot in as templates with map_file=None and their own generator, without changing this shape. """ id: str name: str description: str = "" map_file: str | None = None # resources/defs/maps/*.json, loaded via MapLoader AVAILABLE_WORLD_TEMPLATES: list[WorldTemplate] = [ WorldTemplate( id="test_map", name="Test Map", description="A small sample field with a docked, rotatable two-deck ship - exercises " "the engine's core mechanics (embedding, steering, staircases, weather).", map_file="harbor_world.json", ), ] @dataclass class WorldCreationMenu: """Pure interaction state for the 'create world' screen: pick which starting world template to build a fresh World from. No rendering here - same boundary as StartScreen/CharacterMenu (see their docstrings in this module and engine/ui.py) - this is the state/logic side: which templates exist, which is selected, and handing back the chosen template once confirmed. """ templates: list[WorldTemplate] = field(default_factory=lambda: list(AVAILABLE_WORLD_TEMPLATES)) selected_index: int | None = None def select(self, index: int) -> None: if 0 <= index < len(self.templates): self.selected_index = index def selected_template(self) -> WorldTemplate | None: if self.selected_index is None: return None return self.templates[self.selected_index] def build_world_from_template(template: WorldTemplate, registry: DefRegistry, map_loader: MapLoader) -> World: """Builds an empty (player-less) World from a template's map - the "confirm" action once a WorldCreationMenu selection is made. Spawning/joining a character into it is a separate step - see engine/start_screen.py::join_world_with_character. """ if template.map_file is None: raise NotImplementedError(f"world template {template.id!r} has no map_file and no generator yet") world = World(registry=registry) root_map = map_loader.load(template.map_file) world.add_map(root_map) for embedding in root_map.embeddings: world.add_map(embedding.child_map) return world