MouseQBN - a complete system for storylet-based games in SugarCube

Here is the first release of MouseQBN (MQBN) a complete system for storylet-based games in SugarCube.

MQBN contains code for creating storylets (and optionally placing them in the relevant passages) selecting them, displaying them, linking to them, and using them. In addition it contains macros for working with Sequences, which are MQBN’s version of Fallen London’s Qualities and which can create linear and cycling sequences (such as seasonal cycles) as well as named numeric qualities, like XP-tracks and quest progress.

MQBN is the engine behind my recent games A Mouse Speaks with Death and Zenith.

Features

* storylet creation
* storylet conditions based on game state
* storylet priority and weighting
* multiple storylet stores in one game
* optionally store storylets in passages

Macros

* <<storyletinit>> - sets up storylets
* <<storyletlink>> - makes links to storylets
* <<storyletgoto>> - like <<goto>> for storylets
* <<storyletuse>> - mark a storylet used
* <<storylet>> - define a storylet in a passage
* <<storyletscan>> - load storylets from passages
* <<storyletprune>> - remove unneeded storylets
* <<sequence>> - make a sequence
* <<sequenceadvance>> <<sequencerewind>> - advance or rewind a sequence

Comes with a tutorial!

10 Likes

Exciting! Love to see new work in the Twine and storylet space!

I like how you solved the DSL problem around conditions as a property of the larger object (i.e. op: "gt|gte|le|lte|eq|neq|includes|notincludes|has"). I hadn’t thought of that, and I like the simplicity it enables instead of needing to parse the comparison expression format of $var op value every time. I’m definitely going to have to steal that in my future update to SimpleQBN.

4 Likes

The alternative is to use scripting.evalTwinescript on everything, which is what I think TME is planning for the native implementation. I really didn’t want to go down that route though, because I wanted storylets to be composable from normal objects without needing string keys everywhere. There’s nothing to stop you adding new storylets to a store mid game, to cut down on the possibility space (though you need a way to restore that, of course).

Multiple stores are another take on that same issue. You can just switch from one to another at some point. In A Mouse speaks to Death I had one store for the main bank of memories (the ones you choose on cards) and another for Death’s conversations.

That’s the tricky part, right?

That’s one of the central problem spaces for storylets. They are:

  1. How metadata is stored. Harlowe, for example, uses the (metadata:) macro to add extra information to its internal passage object. TinyQBN uses passage tags, saving the information as part of the internal passage object in SugarCube. As you mentioned, MouseQBN uses an array of objects, and my own SimpleQBN does the same thing for the same reasons, separating the storylet data from passage or other data.

  2. How parts are selected. Harlowe uses a when clause; TinyQBN uses a domain-specific language of hyphens between values and operators in the format of “var-op-value”, SimpleQBN uses a NoSQL-variant, and MouseQBN uses a per-property encoding where, at least as I can tell, the variables, operators, and values are each a separate property.

  3. How availability or openness is determined and when. There are three different ways to do this: after a data operation happens (such as a value change in a global store), after a story event (such as passage transition), or when the author/program asks for it (using a macro or function to ask for all or a limit).

One of the things I have been researching is if it was possible to create a micro-language, like how TinyQBN handles part selection, and pair this with a C++ library for usage with JavaScript, C#, and other languages. In other words, if it is possible to solve that part of the problem space fairly well for most use cases and then create a library that could be used in either Node.js, Unity, or Unreal projects based on a plugin to supply object types and expression parsing.

A thing I really like about MouseQBN is how you solved this problem by separating the expression into parts. One for the variable, one for the operator, and one for the value or other variables as part of the comparison. That solution path might be really useful for the C++ library approach I’ve been thinking about.

Nice! I like how you’ve designed this. A little more programmer-ish in some respects, but not too bad, and a cleaner overall feature-set than TinyQBN’s mish-mash, I think.

If it were not for TinyQBN’s progress variables I wouldn’t have gone down the rabbit hole I fell into with Sequences. Sadly JS doesn’t do operator overloading, so I was stuck with $sequence.value ++ instead of $sequence ++.

I tired to go for a hybrid approach. You can put the storylet data inside the relevant passage with <<storylet>> and then use <<storyletscan>> to go look for them. I don’t think I’d do that personally, because scanning all passages is an expensive operation. I know TME’s proposal for native storylets doesn’t let you declare them in arbitrary passages for that very reason.

But I like the idea that the storylet conditions are metadata about a passage, that makes sense (at least where the storylet points at a passage, it doesn’t have to) and I suspect it would make sense to most users as well. Unfortunately, the only metadata we have are title and tags, which is why TinyQBN’s approach is natural, and also awkward, because tags just aren’t really designed for that sort of information — which is no doubt why it also supports putting the conditions in comments.

One thing I like about SimpleQBN’s approach is that your decks have an order until shuffle()'d. While an MQBN store has an order (the order storylets were inserted), there’s no way to draw in that order. I wonder if that’s something I should add?

2 Likes

I do too!

So, technically, we have whatever metadata we want. The Twee3 spec allows for any metadata in the passage header. It’s just that Twine doesn’t support editing it directly and, you know, adding large objects is not a great idea.

I think the TinyQBN and Harlowe approach of having the first line in the passage changing its metadata is a great one given the constraint of passage editing options available in Twine. I’ll probably end up changing Snowman 3.0 to support adding arbitrary metadata like Harlowe does in the same way. That way, storylet conditional expressions, and other things, could be added to passages and searched/acted on in a similar way to Harlowe allows for accessing a passage’s metadata.

1 Like