179 lines
6.2 KiB
Python
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()
|