A year ago, I talked about moving to DGD and building a persistent game. I managed to get enough code written that you could fire up the game, create a character, and look at the void in which you found yourself.
Since then, I’ve run into a wall. I love test driven development, but LPC environments don’t really have any good test libraries. I started writing one, but given how I would have to integrate it into the running game (or not, depending on some #defines), I decided to start over a bit with Ruby and EventMachine. The combination provides much of the core of an LPC driver, so it’s not as bad as it sounds.
So now, I’m developing Second Contract as a Ruby/EventMachine driver with some scripting. I’m keeping the LPC version in GitHub and created a new repo for the Ruby edition. I’m keeping the LPC version around for when anyone wants to take a look at it — it implements an HTTP REST service and some parsers. Development will focus on the Ruby side of things.
I’ve structured the Ruby version somewhat after the DGD version. I have parsers and compilers, archetypes and objects, player information separate from character information. What I won’t have is a web interface for editing domains, archetypes, or objects. Instead, I’m borrowing some ideas from the static build community: the game loads archetypes, traits, verbs, and other metadata from files. This makes it easier to manage game definitions in a source code control system, something that was difficult with a web interface.
Over the next few weeks, I’ll write posts talking about each aspect of this system. Along the way, we’ll see the scripting language used to respond to events and how the game manages data and objects.
Game objects consist of data and an archetype. Archetypes consist of data, event handlers, and data calculations along with a potential archetype and traits. Traits consist of event handlers and data calculations. Archetypes may inherit from at most one archetype but as many traits as desired. Traits may inherit from as many traits as desired as well.
This means that archetypes and traits define code, and only archetypes and objects define data. Data follows single-inheritance rules while calculations and event handlers follow multi-inheritance rules. Data are things like object details and descriptions, exits, enters, flags, skills, stats. Things that make an object unique.
The game stores objects in a database, providing object persistence. The game loads archetypes and traits from files.
Hospitals (to borrow the term from the Discworld mudlib) will manage transient content. This includes wandering mobs, bosses, and gatherable resources, including loot tables for mobs. If scenes contain inventory, then the hospital data file defines inventory items as well.
Domains consist of a coherent set of scenes, paths, mobs, quests, etc. A domain or sub-domain may use a map file to define the starting set of scenes, but this is for convenience. The map file provides all the data for the objects making up the scenes, paths, and terrains, including guard objects (e.g., doors). Loading a map file will make sure that the objects named in the map have the specified data. It won’t remove objects from the domain that aren’t mentioned in the file.
By moving all scripting off to archetypes and traits, data files can describe the rest of the game, hopefully reducing the amount of time needed to code an area. Since most LPC-based rooms tend to be instances that set up some data and then rely on inherited functionality, this seems reasonable. For those rooms that need unique event handlers, it’s easy enough to create an archetype. Who knows, you might want a second room similar enough to the first somewhere else.
The following is the equivalent (at this point) of the basic character archetype in the LPC version of Second Contract:
--- flags: living: true details: default: noun: - human adjective: - simple --- based on std:item is positional, movable can scan:brief as actor can scan:item as actor can move:accept can see reacts to pre-move:accept with True ## # msg:sight:env # # Used to report on events around that can be seen. # reacts to msg:sight with Emit("narrative:sight", text) ## # pre-scan:item # reacts to pre-scan:item as actor with do set flag:scan-item set flag:scanning end reacts to post-scan:item as actor with if flag:scan-item then :"<actor:name> <examine> <direct>." reset flag:scan-item reset flag:scanning Emit("env:sight", Describe(direct)) end ## # pre-scan:brief # # We set the flag that we'll be looking around. # This lets other things react to this. reacts to pre-scan:brief as actor with do set flag:brief-scan set flag:scanning end reacts to post-scan:brief as actor with if flag:brief-scan then :"<actor:name> <look> around." reset flag:brief-scan reset flag:scanning Emit("env:title", ( physical:environment.detail:default:name // "somewhere" ) ) if physical:position then set $description to "You are " _ physical:position else set $description to "You are" end if physical:location.detail:default:name if physical:location:relation then set $description to $description _ " " _ physical:location:relation end if physical:location.detail:default:name then if physical:location.detail:default:article then set $description to $description _ " " _ physical:location.detail:default:article end set $description to $description _ " " _ physical:location.detail:default:name else set $description to $description _ " somewhere" end end if physical:location <> physical:environment then set $description to $description _ " in" if physical:environment.detail:default:article then set $description to $description _ " " _ physical:environment.detail:default:article end set $description to $description _ " " _ physical:environment.detail:default:name end Emit("env:sight", $description _ ". " _ Describe(physical:environment) ) Emit("env:exits", ItemList(Keys(Exits(physical:location)))) # now we can list items/mobs nearby/in the scene end
Note that the top of the file is YAML-formatted data. This allows you to set up the data without having to write a lot of code to do so. Archetypes can consist of just data if they don’t want to define new event handlers.
An example description of inside the inn:
> look You look around. Old Seadog You are standing behind the bar in the Old Seadog. The inn radiates age and charm with its oak beam roof and many seafaring mementos secured firmly around the walls. Even the bar is a single thick oak slab, worn and chipped with years of service to rowdy sailors. An old glass window lets in a little light from outside. A game room is to the south and a parlor is to the southeast. A party room is at the bottom of a flight of stairs. Obvious exits: west >
The rest of the files needed to get a two-room game up and running are in the game directory. The available verbs allow you to go west or east, enter the inn, stand behind the bar, on the bar, on the floor, crouch, kneel, and sit. You can look (at the scene) as well as details in the scene (the bar, the walls, floor, window, and ceiling in the inn).