Scene Tags

Since Six Ages is our second iteration of a storytelling game, I wanted to make sure it would be easier to do certain things. Being able to categorize scenes was something that was easy to improve on, since in King of Dragon Pass, everything was a special case. For example, when we wanted to make sure you got to deal with tribal disputes when you were a king, we had to list them all.

switch (COpal::DieRoll(4)) {
 case 1:
 scene = scene_R397TradeDispute;
 break;

case 2:
 scene = scene_R398MagicalDispute;
 break;

case 3:
 scene = scene_R399PoliticalDispute;
 break;

case 4:
 scene = scene_218ComplainORama;
 break;
}

This was fragile — scene_218ComplainORama was actually a fairly late addition in version 2.x, and I had to make sure that it was included in that code.

Likewise any news that was considered a random rumor had to be listed. This at least was data instead of being buried in C++ code, but it was still separate from the actual news.

SceneIDT CScene::gRumors[] = {
 news_FF13MoreChaos,
 news_FF14LessChaos,
 news_FF15MoreUndead,
 news_FF16LessUndead,

So I came up with the idea of tagging scenes. Here’s what the OSL documentation says:

Tags begin with @. They can be any word (e.g. @help, @hurt, @tutorial, @actThree, @endgame, @notEnd, @codeTrigger, @frequent, @infrequent). Tags can be purely informational, only a few have predefined meanings. Feel free to invent tags. Tags could be used when triggering scenes at random. (For example, if the player is doing poorly, there could be an increased chance of an @help scene. If the player is very wealthy, it might be time for an @hurt scene.)

So there could be an @kingly tag and an @rumor tag. Implementing a random rumor can start with something like

rumors = [self scriptsWithTag: @"rumor" ofType: type_News];

which searches all scripts for the tag (also choosing only news scripts). A typical rumor will include the tag

news: news_R14Quake
@rumor
speaker: WhoHasHighestSkill(ClanMembers, 'magic)
text: I don’t have to tell you about the earthquake, since everyone felt it.

Most of the tags are indeed invented (usually by Robin Laws as he writes scenes). To help check on consistency, the scene compiler outputs a report of tag usage, which lets us make sure it’s consistently @dwarves and not sometimes @mostali or @dwarfs. A few are used internally (e.g. @nameEntry marks a script that needs a name field added to the UI), but most of these let us control the story.

For example, Robin can create a scene where you perform anti-dwarf magic and write, “Scenes tagged @dwarves do not trigger 30 + q seasons,” which can be coded

DisableTag("@dwarves") 
t = "@dwarves"
w = 30 + q
triggerWithValue code_EnableTag t w

(This takes 4 lines instead of two because of some limitations in the OSL scripting language.) A typical dwarf-related scene would then be tagged

scene: scene_37DwarfTrade
scene037, left, random, @dwarves, @trade, mayRepeat

The arbitrary tags also help implement the magic system. For example, a magical effect can be defined as

{
 name = "Elf Friend", tag = "@elves",
 explanation = "Helps our dealings with the Aldryami"
}

which gives a bonus to any scene that has been properly tagged. One of the tasks to do once all scenes are coded is to verify the tagging for magical blessings that depend on it.

Robin extended the idea with what I call dynamic tags. For example, he came up with a scene which could have a variety of outsiders. So he wrote, “scene gains tag @dwarves.” The implementation is

AddSceneTag(ThisScene, "@dwarves")

Obviously, making sure a scene has the right tag for a random outsider is important when there are blessings that are tied to tags.

I’m also making use of tags to help ensure correctness. For example, sometimes a scene wants to trigger another scene years later, but perhaps a character in the scene is off exploring when the time falls due. We handle this by triggering a code fragment, which can handle whether the character is exploring, died, or whatever. This sort of check is usually added once we start testing, and it would be nice to make sure that we caught every place that triggered the delayed scene. So the scene compiler makes sure that any scene marked @triggerFromCode can’t be triggered directly (but only from a code fragment tagged @triggeringCode).

And our unit test that runs every script simply runs them in an arbitrary order. Scenes may rely on having been run in a particular order, so the unit test may need to do setup that would have happened in normal execution. So there are tags like @unitTestWithClan and @unitTestWithPerson. This isn’t necessarily an exact match for normal execution, but it works for this level of brute force testing.

By the way, the @help/@hurt idea is not implemented at the moment. I’m not sure if it will be, but the flexible tagging system makes it easy to add at any time.

Author: David

Creator of Six Ages and King of Dragon Pass