Writing:Working the Example
Back to Dusty Hill. We can do much more with this world.
desc of the location to look like this:
You are standing at the foot of a hill in the middle of a dusty steppe. Stands of wicker-dry [grass] dot the landscape, rattling thinly in the breeze. A [trail] leads upwards.
Now add a new property, called
trail. Change its pop-up menu from
trail property line now has four entries. For now, we’ll fill in just the
Destination field; set it to
hilltop and leave the other three lines alone. Save this.
When you click on the “trail” link (in the game), you’ll see an error “No such location: hilltop”. Because, of course, there isn’t. Let’s fix this.
Add a second location. (Remember, you do this from the
Dusty Hill world screen.) Give it the name
Top of Hill, the key
hilltop (lower case!), and this description:
You are on top of a rocky hill, overlooking a plain of sparse yellow grass and not much besides. A rough [trail] leads downwards.
Now the “trail” link at the foot of the hill works – it brings you to the top.
However, the “trail” link at the top does not yet work. Remember, the properties you’ve created so far are attached to the
foot location; they’re not accessible from anywhere else. Add a new
trail property in the
hilltop screen to remedy this. The destination line should be
To make life more interesting, add this text on the
(Move message) line:
You make your way back down the hill.
This message appears to the player (in the event pane) when he clicks the move link. To keep the balance, add an appropriate hill-climbing message to the
trail property in the
The third and fourth lines of the move property are messages to other players; one to the players you are departing from, and one to the players you are heading towards. You don’t have to fill in these lines, because the system provides simple defaults. But you might want to provide a colorful customization, perhaps “[$name] trudges wearily up towards the distant summit.”
(As you’ve probably guessed,
[$name] is a magic token which is filled in with the acting player’s name. We’ll get to names and other pronoun magic later.)
In my sample Ages, I’ve tried to make my travel hyperlinks longer, and include a clear description of the motion involved. For example:
You are standing at the foot of a hill in the middle of a dusty steppe. Stands of wicker-dry [grass] dot the landscape, rattling thinly in the breeze. A [trail leads upwards].
Changing the text breaks the hyperlink, because the
Move property is still named
trail. How to fix it?
We’d like to rename the property to “trail leads upwards”, but I said earlier that a property name cannot contain spaces. But try it anyway! You’ll find that the spaces are converted to underscores:
trail_leads_upwards. And indeed this works. By default, the text of a hyperlink is converted into a property name which is lowercase, with spaces and punctuation replaced by underscores.
(We call this “sluggification”, because... (blah blah blah long explanation...) Linotype type-casting machines. Never mind. The exact sluggification rules are a bit tricky. You can experiment if you like. The ultimate goal is to generate a valid Python identifier that looks like the original text.)
But what if we don’t want that long property name? It’s awkward, and prone to breakage if we happen to update the room description again.
Change the property name back to
trail, and then update the
desc to this:
You are standing at the foot of a hill in the middle of a dusty steppe. Stands of wicker-dry [grass] dot the landscape, rattling thinly in the breeze. A [trail leads upwards|trail].
The vertical bar
| indicates an explicit property name. The text before the bar is displayed to the player; the text after the bar is the property name to invoke:
trail. No sluggification is performed here, so be sure to use lower-case after the bar.
Here’s another handy shortcut:
You are standing at the foot of a hill in the middle of a dusty steppe. Stands of [wicker-dry||grass] dot the landscape, rattling thinly in the breeze. A [trail leads upwards|trail].
The double bar in
[wicker-dry||grass] means that the whole text is displayed, but only the last part (
grass) is used to invoke the property. (The text after the double bar is sluggified.) This is a common pattern, when you want a long descriptive link but a short property name.
Let’s go back to that “hazy destination” message, which is what appears if you look at the “Dusty Hill” entry in your linking booklet.
Add a new property in the
foot location, with the name
portaldesc and the text:
The page shows a dry, rocky hillside.
portaldesc is another special property. Add it to any location that you plan to use as a link-in spot. It is shown as the “linking page image.”
(A warning: unlike most text properties,
portaldesc cannot use magic square-bracket tokens like
[$name] or the other interpolations we’ll talk about soon. If you try to put them in, they won’t behave magically; the player will see the square brackets as-is.)
Not all actions involve walking around. At the top of the hill, set this description:
You are on top of a rocky hill, overlooking a plain of sparse yellow grass and not much besides. A [rough trail leads downwards|trail].
A single dead tree stands near the summit – a [grey, hollowed-out||bole] without branch or leaf.
This is two paragraphs of text in the same property. Be sure to enter it that way! They must have a blank line between them, to mark a paragraph break – that is, you should hit Enter twice. (If you just hit Enter once, the paragraphs will flow together.)
bole property in this location. (Remember, the double bar
|| in the link text indicates that the whole string will be displayed to the player, but only last part names a property.)
Using the pop-up menu, change the type of the
bole property to
Event. Now you see two text fields: a message to display to the player, and a message to display to other players in the same location. These messages will appear in the event pane. Try this line in the first field:
You give the hollow tree a good thump. A resonant booming sound rolls down the hill.
Click the link in the game to observe the effect.
Most events are visible to every nearby player. (Or, in this case, audible!) So it’s usually worth filling in the second “other players” field of the
Event property. Try this:
[$name] gives the hollow tree a thump with [$their] hand.
You won’t be able to test this message, since you’ve only got one character in the world. It does what you think, though.
We’ve seen the
[$name] token before.
[$their] is a pronoun token; it gets filled in with “his”, “her”, “its” as appropriate. (Yes, it’s third-person singular. Use
[$our] if it really bugs you.)
Interpolation and assignment
Extend the second line of
desc to look like this:
A single dead tree stands near the summit – a [grey, hollowed-out||bole] without branch or leaf. A bit of chalk lies at the tree’s base, and someone has used it to scrawl: “[[count]] people have been here.”
(From now on, I’ll just be giving the second paragraph of the
desc property. The first paragraph, with the trail link, will stay the same. Be sure not to erase it, or you’ll be stuck on top of the hill...)
[[count]] is in double square brackets. This token disappears in the game – the display reads “ people have been here,” which is hardly informative. That’s because it’s looking for a
count property to insert, and we haven’t defined one.
Do that now; create a property called
count whose value is
0. As soon as you save it, the location in the game will update to show the value: “0 people have been here.” Notice that double square brackets don’t produce a hyperlink; instead, they look up and insert the named property as-is. We call this “interpolation”, if we’re in a mood for three-bit words.
No, you can’t insert a
Eventproperty into a room description this way. That would be silly. Only texts and other static values can be interpolated.
This isn’t an interesting example until the
count property changes. You can have fun editing it in the build window, but really the point is for players to change it. So try this:
A single dead tree stands near the summit – a [grey, hollowed-out||bole] without branch or leaf. A bit of [chalk|count=count+1] lies at the tree’s base, and someone has used it to scrawl: “[[count]] people have been here.”
The word “chalk” is now in single brackets, which makes it a hyperlink. However, the target of the link (after the bar) isn’t a property; it’s a line of code. As you might expect, this increases the
...Or not. If you try it, you’ll get a truculent TypeError.
The problem here is that the
count property is text right now. The text string “0” is not a numeric 0. To fix this, change the type selector to
Value, and fill in the 0 value. Now the “chalk” link works; you can click away, increasing the displayed value each time.
Notice, by the way, that the value in the build screen does not change when you click the link. The property is still defined as 0, even though it appears as 9 or 27 or some such exuberant value in the game. Why?
If you answered “property inheritance,” you understood more of that earlier chapter than I feared! The first time you clicked, the
count=count+1 statement read in the world value (zero), added one, and stored an instance value of 1. (The instance property is created as soon as a player action tries to store a property value.) Subsequent clicks increased this instance property.
If another player were standing in a different instance, she would still see the count as zero. And if she clicked the link, she would see her own instance-copy of the property increase.
The rule is: the build screen only shows the world definition, and player actions cannot change the world definition. Only the Writer can do that.
The chalk message is still quite primitive. Let’s fancy it up.
A single dead tree stands near the summit – a [grey, hollowed-out||bole] without branch or leaf. A bit of [chalk|count=count+1] [$if count]and an [eraser|del count][$end] lies at the tree’s base, and someone has used it to scrawl:
[$if count == 0]
“Nobody has been here.”
[$elif count == 1]
“One person has been here.”
“[[count]] people have been here.”
The new feature here is the
[$if ...] token, which lets us vary the text. In the first line, we’ve added “...and an eraser” (with another action link) but only if the
count property exists and is not zero. The code of the eraser link is
del count, which deletes the
count instance property and allows the original world property (zero) to shine through. So the eraser link actually erases itself, which is sort of cool.
The eraser link could have been
count=0instead. This doesn’t do exactly the same thing, but the result would look the same to the player.
The written message is a more complicated
[$if ...] [$elif ...] [$else] [$end] sequence. Here we test an expression (
count == 0) rather than a bare property.
For clarity, I’ve written the text out on multiple lines. Remember that adjacent lines of text get reassembled into a paragraph, so you can always use single line breaks to make your code easier to read. (A double line break – that is, a blank line – is displayed as a true paragraph break.)
If the player uses the chalk, it should generate an event message as well as updating the
It’s possible to jam multiple lines of code into a link, but the formatting gets ugly. Let’s use this opportunity to break the code out into a separate property.
A single dead tree stands near the summit – a [grey, hollowed-out||bole] without branch or leaf. A bit of [chalk|bumpcount] [$if count]and an [eraser|clearcount][$end] lies at the tree’s base, and someone has used it to scrawl:
[$if count == 0]
“Nobody has been here.”
[$elif count == 1]
“One person has been here.”
“[[count]] people have been here.”
Now create a
bumpcount property, set its type to
Code, and fill in these lines:
count = count+1 event("You grab the chalk and mark your presence on the tree.")
Clicking the link now increments the number and displays an event. The
event() function does what you think.
(If the link pops up a focus pane displaying the code, then you forgot to change the property type.
Text properties are displayed as focus close-ups; all other properties are executed.)
Event properties permit both a message to the acting player and a message to everybody else. You can do the same thing with the
count = count+1 event("You grab the chalk and mark your presence on the tree.", text("[$name] picks up the chalk and scrawls on the tree."))
text() function here ensures that
"[$name] picks up the chalk and scrawls on the tree." is interpreted as wiki text rather than a raw string. In a raw string, the
[$name] token would appear as-is to viewers. (We could apply
text() to the first argument too, but it’s not necessary, because there are no magic tokens there.)
I’ll leave the
clearcount property as an exercise.
One more example. We’ve seen that a link like
[weathered sign|sign] brings up a close-up view if
sign is a text property. You might conclude that
sign, as a statement by itself, does this job, and you’d be right. You could instead write
[weathered sign|codesign], and have a one-line
This accomplishes the same thing – popping up the
sign text property – in a more complicated way. Which looks silly, but you could extend the
codesign property to do more:
event("You peer at the sign.", text("[$name] peers at the sign.")) sign
The code above displays “one person” when
count is 1, but higher values appear as digits. Let‘s allow values up to ten to appear as English.
Create a property
saycount, and set its type to
Code (args). The property will now act as a function with arguments.
The first field is the list of arguments; enter
num. This indicates that
saycount takes one argument, which will be given the temporary internal name
num. (If there were multiple arguments, they would be separated by commas.)
In the second field, enter the following:
_names = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine', 'Ten'] if num >= len(_names): return num else: return _names[num]
(The entries ‘Zero’ and ‘One’ won't actually be used in this example, but by filling the appropriate spaces, they make sure the number names align properly with the array indexes.)
desc by replacing
[[count]] people have been here. with
[[saycount(count)]] people have been here. When this is displayed, the
saycount property will be called with
count as its argument.
Note that adding more number words is as easy as adding more entries to the end of the list – you don't need a separate elif for each case.
(This example is a bit artificial. We could have written
saycount as an ordinary
Code property, checking the value of
count directly. But by passing it as an argument, we’ve created a function which could be reused in several places in the Age.)
A rich environment may want to provide effects that happen “in the background” – either on a regular schedule, or as delayed reactions to player actions. Here is a simple example.
Go back to the foot of the hill. Add a second paragraph to
desc, as follows:
A rusty [pump||handle] sticks up from the ground nearby.
handle property should be
Code, containing these two lines:
event("You pump the handle hard, but only faint wheezing noises emerge.", text("[$name] pumps the handle.")) sched(5, pumptimer)
This won’t work until you add a second
Code property, called
eventloc(locations.foot, "A few drops of water dribble from the pump.")
Always schedule a code property, rather than a line of code.
sched(5, eventloc(...))will not do what you want.
When you click the pump handle now, you’ll see one event immediately, and a second event five seconds later. The
sched() function causes another code property to be invoked after a fixed delay. The delay is specified in seconds.
eventloc() function is like
event(), but it specifies the location where the event is displayed. (
event() triggers an event around the acting player, but there is no acting player in a timer event.)
eventloc() only takes a single argument; everyone in the specified location will see the same event message.
We’ve also introduced the
locations.foot notation, which specifies a location by its short name. You can also use this notation to access location properties. For example,
locations.hilltop.count is the
count property of the
This brings up an important warning about delayed code, which is that it does not run in a particular location. When the player triggers the
handle property in this example, it runs in the player’s location, so
pumptimer refers to a location property. But then when
pumptimer runs, it’s floating around in space. There is no “acting player”, so
event() won’t work – that’s why we use
eventloc(). And there is no current location. Symbols refer to realm properties rather than location properties.
So if you want your delayed code to set a location property, you’d have to write
locations.hilltop.count=1. Writing just
count=1 would set the realm property
(Planned feature: properties should run in the location where they are defined. This would be a sort of “lexical scope”, and I think it would be less confusing overall, although the details are odd.)
You can also set up a repeating event:
sched(60, clockchime, repeat=True)
This invokes the
clockchime code property every sixty seconds, forever.
Except not actually forever. The server has a notion of which instances are “awake” – currently inhabited. Once every player has left an instance, the server waits ten minutes (in case someone comes back). After ten minutes uninhabited, the server puts the instance to “sleep”. All outstanding timers are cancelled. There will be no more activity in the instance until a player returns.
Usually you’ll want your repeating event to run all the time, as long as the instance is awake. You can set this up by creating a realm-level code property named
on_wake. Put your
sched() call in this, and it will be fired every time the instance is awakened.
A minor nuisance: if you set up an
on_wake property with the
sched() line above, and then test it, you’ll have trouble editing the
clockchime property. Once the timer is running, changes to the
clockchime property will seem to have no effect – the code is already loaded into the timer.
To work around this, put the instance to sleep and wake it up again. Of course the ten-minute wait is tedious, so there’s a special command to do this immediately. Type
/bootinstance in the chat pane, and the server will immediately kick you out and put the instance to sleep.
(You can only do this in worlds that you created!)
on_wakeproperty is one type of hook: a specially-named property that the server invokes in certain circumstances. There are several of these, which are listed in the Reference Section.
Creating a bookshelf
The most distinctive feature of Myst’s Ages are the linking books. We’ll certainly want a way to include links in our own worlds.
This is a little more complicated than adding an action or exit. It’s a three-step process: you must create a portal list (bookshelf), add portals (links) to the list, and then create a
PortList property to display the list to the player.
Return to the world page in the build interface, and look at the “Portal lists” table. This shows all the bookshelves available in the world. (A standalone linking book is just a bookshelf with exactly one link.) Press the “New Portal List” button to create a new one.
You are now looking at an empty bookshelf. Set the key to
mylist. This string will be used later in a property to identify this list. (It’s rather like the key-name of a location.)
Now hit the “New Portal” button. You’ll get a new link... to the Information Booth in the starting world. Not very exciting! But this is just an initial placeholder.
Hit the “World” button on the left to change the link’s destination. The menu that appears will allow you to select any link you have in your personal booklet. The booklet is your “clipboard” for copying links around.
When you add a link this way, it copies the world, location, and scope from your linking booklet. So you may well wind up with a link to somebody else’s personal instance. You can stick with that, or change it. Press the “Instance” button to customize the destination instance.
The options here are a little confusing:
Player’s personal: The link will always go to the player’s own personal instance.
Player’s current: The link will always go to the scope the player was in when they used it. If the player is in a global instance, the link will be to a global instance. If the player is in a personal instance, the link will be to the parallel personal instance. (Not necessarily the player’s personal instance!)
Player’s global: The link will always go to the global instance. (“Player’s global” is a confusing label, sorry.)
Global: The link will always go to the global instance. (Just like the previous option. There’s a minor difference in the database entry but the result is identical. I should drop one of them.)
Personal: (you): The link will go to your personal instance (not the player’s!)
(The “Instance” button will be disabled if the target world has
shared instancing. By definition, a
solo world only has
Player’s personal available, and a
shared world only has
(Planned feature: the ability to select a friend’s personal scope, if that player has given you access.)
(Planned feature: group scopes.)
Once you have the list set up the way you want, go to a location in your world and create a
PortList property. Set the key entry to
mylist, matching the list you just created. When the player looks at property, they’ll see your list.
The other lines of the
PortList customize the behavior of the shelf. The read access level defaults to
visitor, meaning that anybody can look at the contents of the shelf. The edit access level defaults to
member, which currently means that only the instance owner can add new books to the shelf.
The links you’ve added are part of the world definition, so only you can delete them. World-defined links appear in every instance. But the instance owner can add and remove additional links, which are specific to that instance.
The “Label” line will be displayed as a description of the bookshelf. You can use this to detail what the shelf looks like, or where it is in relation to the room. (This line may contain links.)
If you want to create a standalone book -- not part of a bookshelf -- you should create a list with exactly one link, and then set the property’s “Focus” line to
true. In this case, the “Label” line describes the book rather than the shelf. (But not the book’s linking image; that comes from the destination world’s
If you want to create a public bookshelf, which anybody can add books to, create an empty portal list. Then set up a
PropList whose edit access is
visitor. (This is how the public shelf in the Seltani gallery works.)
Creating an age-wide booklet page
If you create a realm-level text property named
instancepane, its contents will appear as a special booklet page (in the right column) for players in that world.
instancepane can contain links and interpolations, like any text property. You can use this facility to offer the player information and controls that are available throughout the Age.
Any code in the property will execute at the realm level; that is, there will be no "current location". The "current player" will be set correctly, but
location() will return
None. (If you need this value, use
location(player). Yes, this is an untoward nuisance.)
Note that the booklet page will only appear if the
instancepane exists and generates any text. So if you make the entire text conditional:
...then the booklet page will only appear when the condition is true. You might do this, to have a booklet page that only appears for privileged players:
[$if access.level(level=access.MEMBER)] ... [$end]