diff --git a/docs/how/things-and-building.txt b/docs/how/things-and-building.txt new file mode 100644 index 0000000..9b5ff42 --- /dev/null +++ b/docs/how/things-and-building.txt @@ -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 opens editor with a template python file + @create room opens editor for a new room definition + @edit opens editor for an existing thing/room + @copy opens editor with a copy, requires new name + @spawn create an instance of a thing in current location + @destroy remove a thing instance you're looking at / holding + @inspect view the definition (source code or toml) + @dig 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.