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.
Brief Overview
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.
Sneak Preview
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).