Compare commits
No commits in common. "8f3455ce0a09aa9693ec8b041d98f2d930dc0742" and "d3df09f4de375f2fc9cc8f17ee68e426dc05d458" have entirely different histories.
8f3455ce0a
...
d3df09f4de
1 changed files with 0 additions and 387 deletions
|
|
@ -1,387 +0,0 @@
|
||||||
===================
|
|
||||||
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