mud/docs/how/things-and-building.txt

384 lines
12 KiB
Text

things and building
===================
overview
--------
this doc sketches what it looks like to create interactive objects in the MUD.
from the builder's perspective (what commands do i type) and the code
perspective (what does the python look like).
not a spec yet. a sketch to react against.
the decorator pattern
---------------------
the core idea: things are python classes with decorators. no custom DSL.
python IS the language.
here's a full example of a pepsi can:
from mudlib.things import thing, verb
@thing
class PepsiCan:
name = "pepsi can"
aliases = ["can", "pepsi"]
description = "a cold can of pepsi. unopened."
opened = False
empty = False
@verb("open")
def open(self, player):
if self.opened:
player.tell("it's already open.")
return
self.opened = True
self.description = "an open can of pepsi."
player.tell("you crack open the pepsi. psssht.")
player.room.emit(f"{player.name} opens a pepsi.", exclude=player)
@verb("drink")
def drink(self, player):
if not self.opened:
player.tell("you'd need to open it first.")
elif self.empty:
player.tell("it's empty.")
else:
self.empty = True
self.description = "an empty pepsi can."
player.tell("you chug the pepsi. refreshing.")
@verb("crumple")
def crumple(self, player):
if not self.empty:
player.tell("it's still got pepsi in it.")
return
player.tell("you crush the can in your fist.")
player.room.emit(f"{player.name} crushes a pepsi can.", exclude=player)
self.destroy()
what's happening here:
- @thing registers the class as a spawnable template, looked up by name
- @verb makes methods into in-game commands that work on this object
- base behaviors are free: every @thing can be dropped, picked up, given,
looked at
- the class is the template. instances are spawned from it. 10 cans = 10
instances, one class.
- state (opened, empty) lives on the instance, not the class
- self.destroy() removes the instance from the world
- no yaml needed for behavioral things. the python file IS the definition.
for static items (no behavior, just a description), we could support TOML-only
for simple cases:
[thing]
name = "rock"
aliases = ["stone"]
description = "a smooth grey rock."
weight = 2.0
builder commands
----------------
the @ prefix for builder/admin commands. not combat, not normal play. building.
@create item <name> opens editor with a template python file
@create room <name> opens editor for a new room definition
@edit <name> opens editor for an existing thing/room
@copy <name> <newname> opens editor with a copy, requires new name
@spawn <name> create an instance of a thing in current location
@destroy remove a thing instance you're looking at / holding
@inspect <name> view the definition (source code or toml)
@dig <direction> create a new room connected to current location
the flow for @create item pepsi can:
1. command parsed, "pepsi can" becomes the name
2. engine generates a template python file with the name filled in
3. editor mode is pushed onto mode stack
4. player edits the template (they see python code)
5. on save: file written to items/pepsi_can.py, hot-reloaded
6. editor mode pops, back to normal
7. the thing is now in the registry, spawnable
the flow for @copy pepsi can cola can:
1. loads pepsi can's source
2. replaces name with "cola can"
3. opens editor with the modified copy
4. on save: new file, new registration
5. cola can is its own independent thing now
permissions & ownership
-----------------------
things have an owner - whoever created them.
permission levels (rough sketch):
player - can interact with things (use verbs, pick up, drop)
builder - can @create, @edit own things, @spawn, @dig
admin - can @edit anyone's things, manage permissions
owning a name: there's only one "pepsi can" template. the owner controls it.
anyone can spawn instances, but only the owner (or admin) can edit the
definition.
instances vs templates:
- template: the class/definition. owned by creator. one per name.
- instance: a spawned copy in the world. can be picked up, dropped,
destroyed.
- instances don't have individual owners in the filesystem sense.
- dropped instances will probably get cleaned up eventually but that's a
runtime concern, not a design blocker.
open questions:
- do we store permissions in the file? in sqlite? in a separate registry?
- how granular? per-thing? per-directory? role-based?
- can a builder grant co-ownership?
map editing
-----------
the map format (TOML) is already writable:
[map]
width = 20
height = 20
data = """
.........===.....
...etc...
"""
[palette]
entries = """
o|tile|#26a269
.|gravel|#9a9996
=|brick|#a51d2d
"""
an in-game map editor could:
- show the current map in the viewport
- let you paint tiles with a cursor
- write changes back to the TOML file on save
- this is essentially the editor mode but for spatial data instead of text
open questions:
- locking: what if two builders edit the same map?
- versioning: do edits go through git? just overwrite?
- for now: single builder, no locking. solve concurrency if it matters.
rooms and interiors
-------------------
the overworld is terrain tiles. but buildings, caves, dungeons are interiors.
interiors are rooms (classic MUD style) or small grids.
entering: a tile on the overworld has an "enter" action that transitions you
to an interior. the interior has an "exit" that puts you back on the overworld.
a room definition might look like:
[room]
name = "tavern"
description = "a warm tavern. fire crackling. smells like stew."
exits = { out = "overworld:12,45" }
or as python if it has behavior:
@room
class Tavern:
name = "tavern"
description = "..."
exits = {"out": "overworld:12,45"}
@on("enter")
def on_enter(self, player):
player.tell("the warmth hits you as you step inside.")
@dig north from inside a room creates a new connected room and opens editor.
roadmap
-------
this is the important part. mix of implementation and design work. each phase
builds on the last. design decisions that block implementation are called out.
phase 1: things exist
~~~~~~~~~~~~~~~~~~~~~
status: not started
depends on: entity model (done), command system (done)
DESIGN: finalize @thing/@verb decorator API
- what methods does the base thing provide?
- how does verb dispatch work with the command system?
- does @thing register globally or per-module?
> this blocks everything below
BUILD: thing registry
- like command registry but for spawnable templates
- lookup by name, list all, filter by type
BUILD: base behaviors
- drop, get/take, give, inventory, look at
- these are commands that operate on thing instances
- "get can" finds a PepsiCan instance in the room, moves to inventory
BUILD: spawn/destroy lifecycle
- create instance from template
- place in room or inventory
- destroy removes from world
BUILD: thing persistence
- which instances persist? (dropped items, placed furniture)
- which are ephemeral? (consumables, quest items)
> DESIGN DECISION: do we persist instance state to sqlite?
> or just persist "there is a pepsi can at 12,45 with opened=true"?
> this can be deferred - start with ephemeral, add persistence later
phase 2: builder commands
~~~~~~~~~~~~~~~~~~~~~~~~~~
status: not started
depends on: phase 1, editor mode (done)
BUILD: @create command
- parses args (type + name)
- generates template file
- pushes editor mode
BUILD: @edit command
- finds thing by name
- loads source file
- pushes editor mode
BUILD: @copy command
- loads source, substitutes name
- pushes editor mode
BUILD: @spawn / @destroy commands
- spawn: create instance from registry
- destroy: remove instance from world
BUILD: @inspect command
- show definition source (read-only)
DESIGN: hot-reload mechanism
- when a file is saved from the editor, how does the registry update?
- reimport the module? invalidate instances of the old version?
> this blocks @edit (creating works without reload, editing needs it)
phase 3: world building
~~~~~~~~~~~~~~~~~~~~~~~
status: not started
depends on: phase 2
DESIGN: how interiors connect to overworld
- which tiles are "enterable"?
- how is the transition represented in data?
> this blocks @dig and room creation
BUILD: room system
- room definitions (TOML or python)
- enter/exit transitions
- room-local commands and things
BUILD: @dig command
- creates room, connects via exit
- opens editor for new room
BUILD: map editing from in-game
- cursor-based tile painting
- write back to TOML
- start simple: single builder, no locking
phase 4: permissions
~~~~~~~~~~~~~~~~~~~~
status: not started
depends on: phase 2 (at minimum)
DESIGN: permission model
- role-based? (player/builder/admin)
- where are permissions stored?
- how granular?
> can be deferred if you're the only builder for now
> becomes critical when others build
BUILD: permission checks on @ commands
BUILD: ownership tracking on templates
phase 5: npcs and AI
~~~~~~~~~~~~~~~~~~~~
status: not started
depends on: phase 1, combat system (done)
DESIGN: mob behavior composition
- patrol routes, aggro radius, dialogue trees
- how do these compose? decorators? data files?
> can design alongside phase 1-2 since Mob class exists
BUILD: mob spawning from templates
BUILD: basic AI loop (runs in game tick)
BUILD: mob persistence (respawn rules, loot tables)
phase 6: the full loop
~~~~~~~~~~~~~~~~~~~~~~
depends on: phases 1-4
this is "a builder sits down and creates a thing from scratch, tests it,
publishes it, other players use it." the MOO dream but with python.
BUILD: thing catalog / library browser
BUILD: testing mode (try your thing before publishing)
BUILD: sharing / discovery
no design blockers here - if phases 1-4 work, this is just polish.
open questions (parking lot)
----------------------------
- sandboxing: if someone writes python in the editor, what can they import?
what modules are available? can they access the filesystem? the network?
this is the MOO permissions problem. not urgent if builders are trusted.
becomes critical if/when players can create.
- hot reload details: reimport module? use importlib.reload? what about
instances that reference the old class? do they get upgraded or stay old?
- naming conflicts: what if two builders both want "sword"? namespacing?
builder/sword vs items/sword? or first-come-first-served?
- static vs behavioral things: is the TOML path worth maintaining? or just
make everything python, even simple things? simpler to have one path.
- composition: the architecture plan warns against deep inheritance
(Item > Weapon > MagicSword). how do decorators encourage composition?
maybe @trait decorators that add capabilities?
- the editor experience: telnet editors are rough. can we make it not awful?
study how MOO's @program worked. study the existing editor mode we have.