"""Seed builtin world presets (fantasy + sci-fi). Idempotent: skips presets whose name already exists. """ from __future__ import annotations import uuid from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.models import WorldPreset # --------------------------------------------------------------------------- # # Fantasy preset # --------------------------------------------------------------------------- # FANTASY_PRESET = { "name": "Classic Fantasy", "description": ( "A high-fantasy world with taverns, dungeons, magic, and monsters. " "Standard d20-style stats. Default setting for new players." ), "language": "en", "rules": [ "Magic requires mana; mana regenerates with sleep.", "Combat is turn-based; stats.health is the HP pool.", "NPCs remember their relationship to the player across sessions.", "Death is permanent unless a resurrection scroll is used.", ], "time_schema": {"hours_in_day": 24, "initial_date": "day_1_hour_8"}, "schemas": [ { "type": "character", "verbose": "Character", "plural": "characters", "properties": [ {"name": "name", "type": "string", "required": True}, {"name": "description", "type": "string", "required": False}, {"name": "stats", "type": "object", "required": True, "properties": [ {"name": "health", "type": "integer", "required": True, "min": 0, "max": 100}, {"name": "mana", "type": "integer", "required": False, "min": 0, "max": 100}, {"name": "strength", "type": "integer", "required": True, "min": 1, "max": 20}, {"name": "dexterity", "type": "integer", "required": False, "min": 1, "max": 20}, {"name": "intelligence", "type": "integer", "required": False, "min": 1, "max": 20}, ]}, {"name": "inventory", "type": "array", "required": False, "items": {"type": "object"}}, {"name": "relationship", "type": "string", "required": False}, ], }, { "type": "item", "verbose": "Item", "plural": "items", "properties": [ {"name": "name", "type": "string", "required": True}, {"name": "description", "type": "string", "required": False}, {"name": "qty", "type": "integer", "required": False, "min": 1, "max": 9999}, {"name": "value", "type": "integer", "required": False, "min": 0}, ], }, { "type": "location", "verbose": "Location", "plural": "locations", "properties": [ {"name": "name", "type": "string", "required": True}, {"name": "description", "type": "string", "required": True}, {"name": "exits", "type": "array", "required": False, "items": {"type": "string"}}, {"name": "is_safe", "type": "boolean", "required": False}, ], }, { "type": "faction", "verbose": "Faction", "plural": "factions", "properties": [ {"name": "name", "type": "string", "required": True}, {"name": "description", "type": "string", "required": False}, {"name": "alignment", "type": "string", "required": False}, ], }, ], "environment_schema": [ { "name": "player", "type": "object", "required": True, "properties": [ {"name": "name", "type": "string", "required": True}, {"name": "stats", "type": "object", "required": True, "properties": [ {"name": "health", "type": "integer", "required": True, "min": 0, "max": 100}, {"name": "mana", "type": "integer", "required": False, "min": 0, "max": 100}, {"name": "strength", "type": "integer", "required": True, "min": 1, "max": 20}, ]}, {"name": "inventory", "type": "array", "required": False}, {"name": "backstory", "type": "string", "required": False}, ], }, {"name": "current_location", "type": "string", "required": True}, { "name": "plot_rails", "type": "object", "required": True, "properties": [ {"name": "hooks", "type": "array", "required": True}, {"name": "current_goals", "type": "array", "required": True}, {"name": "completed_goals", "type": "array", "required": False}, ], }, ], "environment_initial": { "player": { "name": "Hero", "stats": {"health": 100, "mana": 10, "strength": 10}, "inventory": [], "backstory": "A wanderer with a mysterious past.", }, "current_location": "The Rusty Tankard Tavern", "plot_rails": { "hooks": [ "Strange travelers have been seen near the old ruins.", "The tavern keeper is looking for someone to deliver a package.", ], "current_goals": ["Find lodging for the night and learn local rumors."], "completed_goals": [], }, }, "is_public": True, "status": "ready", } # --------------------------------------------------------------------------- # # Sci-fi preset # --------------------------------------------------------------------------- # SCI_FI_PRESET = { "name": "Deep Space Outpost", "description": ( "A sci-fi setting on a remote space station. The player is a junior officer " "investigating strange signals from the outer rim. Resource management and " "social deduction blend with exploration." ), "language": "en", "rules": [ "Oxygen and power are limited resources; track them via env_update.", "The station's AI is an NPC with its own agenda.", "Combat is lethal — avoid open conflict when possible.", "Distress signals from other ships may be traps.", ], "time_schema": {"hours_in_day": 24, "initial_date": "day_1_hour_8"}, "schemas": [ { "type": "character", "verbose": "Character", "plural": "characters", "properties": [ {"name": "name", "type": "string", "required": True}, {"name": "role", "type": "string", "required": False}, {"name": "stats", "type": "object", "required": True, "properties": [ {"name": "health", "type": "integer", "required": True, "min": 0, "max": 100}, {"name": "oxygen", "type": "integer", "required": True, "min": 0, "max": 100}, {"name": "tech_skill", "type": "integer", "required": False, "min": 1, "max": 20}, ]}, {"name": "inventory", "type": "array", "required": False}, {"name": "loyalty", "type": "string", "required": False}, ], }, { "type": "item", "verbose": "Item", "plural": "items", "properties": [ {"name": "name", "type": "string", "required": True}, {"name": "description", "type": "string", "required": False}, {"name": "qty", "type": "integer", "required": False, "min": 1, "max": 9999}, ], }, { "type": "location", "verbose": "Location", "plural": "locations", "properties": [ {"name": "name", "type": "string", "required": True}, {"name": "description", "type": "string", "required": True}, {"name": "exits", "type": "array", "required": False}, {"name": "is_sealed", "type": "boolean", "required": False}, ], }, { "type": "faction", "verbose": "Faction", "plural": "factions", "properties": [ {"name": "name", "type": "string", "required": True}, {"name": "description", "type": "string", "required": False}, {"name": "allegiance", "type": "string", "required": False}, ], }, ], "environment_schema": [ { "name": "player", "type": "object", "required": True, "properties": [ {"name": "name", "type": "string", "required": True}, {"name": "role", "type": "string", "required": False}, {"name": "stats", "type": "object", "required": True, "properties": [ {"name": "health", "type": "integer", "required": True, "min": 0, "max": 100}, {"name": "oxygen", "type": "integer", "required": True, "min": 0, "max": 100}, {"name": "tech_skill", "type": "integer", "required": False, "min": 1, "max": 20}, ]}, {"name": "inventory", "type": "array", "required": False}, {"name": "backstory", "type": "string", "required": False}, ], }, {"name": "current_location", "type": "string", "required": True}, { "name": "plot_rails", "type": "object", "required": True, "properties": [ {"name": "hooks", "type": "array", "required": True}, {"name": "current_goals", "type": "array", "required": True}, {"name": "completed_goals", "type": "array", "required": False}, ], }, ], "environment_initial": { "player": { "name": "Operative", "role": "Junior Officer", "stats": {"health": 100, "oxygen": 100, "tech_skill": 8}, "inventory": [], "backstory": "Fresh out of the academy, assigned to the Outer Rim Station.", }, "current_location": "Station Command Module", "plot_rails": { "hooks": [ "Anomalous signal detected from sector 7G.", "The station AI has been unusually quiet lately.", ], "current_goals": ["Report to the commanding officer and check the signal log."], "completed_goals": [], }, }, "is_public": True, "status": "ready", } BUILTIN_PRESETS = [FANTASY_PRESET, SCI_FI_PRESET] async def seed_builtin_presets(session: AsyncSession) -> None: """Insert builtin presets if they don't yet exist. Builtin presets have owner_id=NULL (they are system presets, not owned by any specific user). This avoids the FK violation that occurred on first startup when no admin user existed yet. """ for preset_data in BUILTIN_PRESETS: existing = ( await session.execute( select(WorldPreset).where(WorldPreset.name == preset_data["name"]) ) ).scalar_one_or_none() if existing is not None: continue preset = WorldPreset( owner_id=None, # system preset — no owner **preset_data, version=1, ) session.add(preset) await session.commit()