Add things and building design doc
This commit is contained in:
parent
d3df09f4de
commit
0395aaadad
1 changed files with 384 additions and 0 deletions
384
docs/how/things-and-building.txt
Normal file
384
docs/how/things-and-building.txt
Normal file
|
|
@ -0,0 +1,384 @@
|
|||
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.
|
||||
Loading…
Reference in a new issue