Files
timmy-home/scripts/evennia/build_bezalel_world.py
Alexander Whitestone b3359e1bae
Some checks failed
Smoke Test / smoke (pull_request) Failing after 19s
feat: add Bezalel Evennia world scaffold (#536)
2026-04-15 00:46:03 -04:00

179 lines
6.2 KiB
Python

#!/usr/bin/env python3
"""Idempotent builder for Bezalel's themed Evennia world."""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[2]
if str(REPO_ROOT) not in sys.path:
sys.path.insert(0, str(REPO_ROOT))
from evennia_tools.bezalel_layout import CHARACTERS, EXITS, OBJECTS, PORTAL_COMMANDS, ROOMS
def describe_build_plan() -> dict:
return {
"room_count": len(ROOMS),
"character_count": len(CHARACTERS),
"object_count": len(OBJECTS),
"portal_command_count": len(PORTAL_COMMANDS),
"room_names": [room.key for room in ROOMS],
"character_starts": {character.key: character.starting_room for character in CHARACTERS},
"portal_commands": [command.key for command in PORTAL_COMMANDS],
}
def _import_evennia_runtime():
from evennia.accounts.models import AccountDB
from evennia.accounts.accounts import DefaultAccount
from evennia.objects.objects import DefaultCharacter, DefaultExit, DefaultObject, DefaultRoom
from evennia.utils.create import create_object
from evennia.utils.search import search_object
return {
"AccountDB": AccountDB,
"DefaultAccount": DefaultAccount,
"DefaultCharacter": DefaultCharacter,
"DefaultExit": DefaultExit,
"DefaultObject": DefaultObject,
"DefaultRoom": DefaultRoom,
"create_object": create_object,
"search_object": search_object,
}
def _find_named(search_object, key: str, *, location=None):
matches = search_object(key, exact=True)
if location is None:
return matches[0] if matches else None
for match in matches:
if getattr(match, "location", None) == location:
return match
return None
def _ensure_room(runtime, room_spec):
room = _find_named(runtime["search_object"], room_spec.key)
if room is None:
room = runtime["create_object"](runtime["DefaultRoom"], key=room_spec.key)
room.db.desc = room_spec.desc
room.save()
return room
def _ensure_exit(runtime, exit_spec, room_map):
source = room_map[exit_spec.source]
destination = room_map[exit_spec.destination]
existing = _find_named(runtime["search_object"], exit_spec.key, location=source)
if existing is None:
existing = runtime["create_object"](
runtime["DefaultExit"],
key=exit_spec.key,
aliases=list(exit_spec.aliases),
location=source,
destination=destination,
)
else:
existing.destination = destination
if exit_spec.aliases:
existing.aliases.add(list(exit_spec.aliases))
existing.save()
return existing
def _ensure_object(runtime, object_spec, room_map):
location = room_map[object_spec.location]
existing = _find_named(runtime["search_object"], object_spec.key, location=location)
if existing is None:
existing = runtime["create_object"](
runtime["DefaultObject"],
key=object_spec.key,
aliases=list(object_spec.aliases),
location=location,
home=location,
)
existing.db.desc = object_spec.desc
existing.home = location
if existing.location != location:
existing.move_to(location, quiet=True, move_hooks=False)
existing.save()
return existing
def _ensure_character(runtime, character_spec, room_map, password: str):
account = runtime["AccountDB"].objects.filter(username__iexact=character_spec.key).first()
if account is None:
account, errors = runtime["DefaultAccount"].create(username=character_spec.key, password=password)
if not account:
raise RuntimeError(f"failed to create account for {character_spec.key}: {errors}")
character = list(account.characters)[0]
start = room_map[character_spec.starting_room]
character.db.desc = character_spec.desc
character.home = start
character.move_to(start, quiet=True, move_hooks=False)
character.save()
return character
def _ensure_portal_command(runtime, portal_spec, room_map):
portal_room = room_map["The Portal Room"]
fallback = room_map[portal_spec.fallback_room]
existing = _find_named(runtime["search_object"], portal_spec.key, location=portal_room)
if existing is None:
existing = runtime["create_object"](
runtime["DefaultExit"],
key=portal_spec.key,
aliases=list(portal_spec.aliases),
location=portal_room,
destination=fallback,
)
else:
existing.destination = fallback
if portal_spec.aliases:
existing.aliases.add(list(portal_spec.aliases))
existing.db.desc = portal_spec.desc
existing.db.travel_target = portal_spec.target_world
existing.db.portal_stub = True
existing.save()
return existing
def build_world(password: str = "bezalel-world-dev") -> dict:
runtime = _import_evennia_runtime()
room_map = {room.key: _ensure_room(runtime, room) for room in ROOMS}
for exit_spec in EXITS:
_ensure_exit(runtime, exit_spec, room_map)
for object_spec in OBJECTS:
_ensure_object(runtime, object_spec, room_map)
for character_spec in CHARACTERS:
_ensure_character(runtime, character_spec, room_map, password=password)
for portal_spec in PORTAL_COMMANDS:
_ensure_portal_command(runtime, portal_spec, room_map)
return {
"rooms": [room.key for room in ROOMS],
"characters": {character.key: character.starting_room for character in CHARACTERS},
"portal_commands": {command.key: command.target_world for command in PORTAL_COMMANDS},
}
def main() -> None:
parser = argparse.ArgumentParser(description="Build Bezalel's themed Evennia world")
parser.add_argument("--plan", action="store_true", help="Print the static build plan without importing Evennia")
parser.add_argument("--password", default="bezalel-world-dev", help="Password to use for created account-backed characters")
args = parser.parse_args()
if args.plan:
print(json.dumps(describe_build_plan(), indent=2))
return
print(json.dumps(build_world(password=args.password), indent=2))
if __name__ == "__main__":
main()