Advanced Chapbook Authoring

@Sanadi

The Backstage is for testing purposes and not designed to be the way to author a story. However, it does encourage authors to change the style of some basic components and copy the code into their first passage.

I think you were going into the default variables in the State tab…

…and as you can see here, I can add content (TEST), but it is only temporary. However, all these default variables can be changed in the passage, as you have discovered, and then they persistent as any other story code would. The Backstage is very useful in exposing all that can be modified intentionally by Chapbook. It’s a very slick design.

1 Like

I believe you can include that above the line on any passage where you want the center footer to change.

For example on your title screen you can blank out whatever’s there by default:
config.footer.center: ''

Then on the credits page
config.footer.center: '[[About]]'

On the about page
config.footer.center: '{back link}'

Then within the game
config.footer.center: '[[Stats]]'

2 Likes

I just wanted to post this for posterity:

[JavaScript]
write( "Something witty." );
[continued]

Chapbook has a custom JS function called write() which outputs whatever you want from JavaScript into that exact spot in the story. This is a fantastic addition as you don’t have to create a target HTML element first, find it by searching the document, and then insert the text you want into it. This is a much, much tidier way of displaying content from JavaScript. Harlowe has nothing like this and SugarCube encourages you to pass variables to it’s <<print>> macro. Just saying that this write() function is a godsend for those wanting to dip their toes into JavaScript that works with their Chapbook story. I feel spoiled when I use it. :wink:

2 Likes

@HanonO

Just reread this thread and noticed you bringing up this question. You might want to use the {back link}.

{back link, label: 'Go Back'}

However, if a user clicks a {back link} twice in a row (or they click the “Stats” link twice before hitting the back link), it goes back and forth in an endless loop. This is because Chapbook still wants to record that you went to a passage; it’s just sending you back to the previous passage and adding that as a new visit.

I’ll look into this further because I think it’s very important and I’ll definitely let you know if I find an elegant solution. There are many ways to solve this though, I just don’t know which is the easiest way.

Oh yeah, I’ve learned all the things that can fail with a link to an inventory screen that is always available.

Even if you just use the back link, you have to be careful if the screen they return to modifies anything that it doesn’t all run again. As an example, a player could access inventory from a screen where they score a point and keep toggling back and forth to bump it endlessly.

In Axma I could use the system variable $$previous which held the name of the passage the player arrives from and an if-statement to not award points if they’re coming from the inventory passage.

2 Likes

That’s a good point there and I like the “if previous screen equals” logic you suggested.

I find it amusing that this topic is about advanced Chapbook things and we’re struggling to make a back button work right. :wink:

Edit: Just FYI, Harlowe has code to re-direct without drawing the passage to the screen. I used that technique to set variables at one time and it worked great. Harlowe 1 - Chapbook 0. :frowning:

My proposed solution is I just remove access to stats/inventory except in specified hubs and passages where it might be necessary. The player needn’t be checking all their pockets while plot is happening and variables are being switched and points are being awarded!

Which actually gives me an idea that might be cool: instead of having one inventory/stats screen every passage links to, I can copy/paste customize different ones depending on what the location and the story situation is. Then just hard code them to go back to the right place.

My brain tells me that maybe you only have access to what’s in your pockets, or in your truck, or in your house, etc. at any one given time… that’s kind of unique.


Because Chapbook is fully exposed to JavaScript, it’s really easy to modify the variables. Chapbook uses a variable called trail that’s a simple linear array of passage names that have been visited. That means it’s really easy to remove the “Inventory” passage from the end of the trail array within the inventory passage code itself. That’s progress.

For example, if I wanted to remove the currently visited passage from the trail and make a functional back link despite this, I could use:

[javascript]
trail.pop(); /* remove the current passage in the trail */
engine.state.set( "previous_passage", trail.splice(-1) ); /* grab the last passage visited in the trail */
[continue]

[[Go Back->{previous_passage}]]

Conversely, you could also place your variable manipulation code (that’s only supposed to fire once) in your story passages within an if statement that checks to see if the previous passage (2nd last one) in trail is the same as the current one (last one) and only change the variables if necessary. This would stop the variables from being changed again.


The more I see the need for dynamic Chapbook coding techniques, the more I see JavaScript as the preferable solution. Why reinvent the wheel with a new fancy language that basically extends basic programming logic already available in a very mature JavaScript? Plus there is a staggering amount of JS tutorials and solutions online, versus specific Chapbook/Twine solutions. This is why I’m attracted to Chapbook because it’s not reinventing the wheel and is as unobtrusive as possible.

1 Like

That’s actually pretty cool. Since you know JS you can do these things. I have tried a couple times to grok JS but haven’t plunged in enough to actually learn it.

I’m thinking I may go with limiting where inventory/stats is available, and I think the trick is using an embedded passage.

You take a moment to turn away from the diabolical machine to see if you have anything in your pockets that might help you fix it:

{embed passage: 'inventory'}
1 Like

I’m going to give considerable thought to writing a guide for Chapbook that makes the JavaScript side of things less daunting. It’s weird to me, because you’ll get people who don’t want to touch JavaScript with a ten foot pole, yet they are willing to learn the ins and outs of SugarCube or Harlowe and they end up programming with those languages. Teaching people how to program is the hard part, not the syntax. The language can change, but the logic remains the same.

If there was a Chapbook [modifier] that removed the last part of the trail, authors would be using that feature without hesitation, but as soon as it’s wrapped inside a [javascript] ... [continue] block, it becomes too daunting for most. My goal is to remove that barrier. This community will be my unwilling guinea pig. Moooowaaahaahaha! :slight_smile:


That’s very clever, to be honest.

1 Like

I’m curious about what situation would require both undoing the last moment in History and moving forward to the Passage that was visited two moments ago.

note: your variable name is slightly misleading, as the Passage Name is contains isn’t the ‘previous’ Passage visited, but the one prior to that. :slight_smile:

1 Like

Buckle up. :wink:

If you have a link in the footer to a hypothetical “Stats” passage, you could click the link many times over without seeing that you are indeed visiting the Stats page multiple times and the trail begins to look like [..."Stats", "Stats", "Stats"]. At this point, using the built-in {back link} insert would take you back to the last visit of the Stats passage and not the point of the story in which you first clicked the Stats link in the footer. This is why I suggested that a menu-type screen remove itself from the trail.

However, by removing the last item in the trail, Chapbook’s {back link} insert looks to the second last passage… so we have to make our own link to compensate and only look to the very last passage in the trail (after we just removed the last passage, that being the current one rendered in the browser – our Stats page). The trail never shrinks, it only grows with Chapbook’s native functions. In fact, when clicking Chapbook’s {back link} twice in a row (on different passages, of course), it will go back and forth in an endless loop because Chapbook doesn’t remove anything from the trail. It’s not an undo. Going back adds a new entry in the trail.

Hanon also mentioned about visiting a passage in which variables are altered (maybe counted up or down) and that by going back to it from the Stats (or Inventory) page would cause those variables to count up or down again. So I then suggested that he merely put a condition of checking for two passages at the end that match, Indicating that the Stats page had altered the trail, and the variables should not be calculated in that scenario.

I hope that makes sense. Let me know if I’ve overthought the situation though. I tested the code and it works well for me. Just two lines of JavaScript and a custom back link does the trick.

Edit: I’ve changed the comment in that block of JavaScript to read:

trail.pop(); /* remove the current passage in the trail */
engine.state.set( "previous_passage", trail.splice(-1) ); /* grab the last passage visited in the trail */

I was waffling over commenting on what the code was literally doing versus what the intent of the code was doing story-wise. Now I’ve changed it to the intent. I wasn’t aiming for this being a tutorial though. That’s for my next project. :wink: Good call on it needing clarification though.

Another Edit: Also this isn’t the only way to deal with a scenario like this, but it kills two birds with one stone for Hanon’s concern about doubling up on the variable changes as it doesn’t require additional variables to be tracked and accounted for. Who cares how many times the Stats page was visited versus a story passage, basically.

1 Like

Then the footer should check what Passage is currently being visited before conditionally showing the link to the “Stats” Passage. :slight_smile:

eg. never show an option to the end-user that you don’t want them to select.

1 Like

I don’t know of any way to run code within the footer in Chapbook. It doesn’t have a dedicated header and footer passage, that I know of, just a pre-built <div> structure that you can send stuff to display. That was an important missed opportunity, I feel. However…

You could replace the Stats link in the footer template with a disabled version from within in the Stats passage code. That would be quite straight forward. And if you had more menu-type passages you could disable all the those links upon visiting any one of them to avoid navigating amongst them and hindering the {back link} insert going to the proper story passage. That would work too.

For coming back to a story passage (that shouldn’t trigger variable manipulation again), I suppose you could check the third last passage in the trail for a match to the current one, since all other navigation was disabled in the Stats passage. It would look like [..., "Passage 24", "Passage 25", "Stats", "Passage 25"] at that point. You would also have to embed a passage that reinstates the menu-type links for every story passage. (Unfortunately, this draws dashed connection lines in the Twine editor from practically every story passage so it makes the layout of it all messy, but it works – and this is why proper header and footer passages would be a godsend.)

I don’t know. My flux capacitor, time travel solution might be cleaner. Just saying. :wink:

Sometimes, when you’re so used to swingin’ a hammer though, every problem looks like a nail. I’ll be the first to admit that I over engineer things.


I was able to make my own embedded (JavaScript-only, unfortunately) passage code that doesn’t draw those dashed lines all over the Twine editor passage map grid. And I have a very nice solution to check tags that are assigned to a passage. Chapbook ignores the tags that Twine provides, but the Twine raw data still exists hidden in the HTML thankfully.

I’m liking Chapbook quite a bit, but it really requires JavaScript knowledge to make it comparable to other story formats. There’s so much to Chapbook that I still don’t understand though. That said, I’m going to keep pushing hard with Chapbook, but I was doing a little reading on Snowman 2 and it looks like another potential candidate.

By the time I’m done, I’ll know everything except SugarCube. I think I’m a story format hipster. I just can’t go with the flow. Marching to beat of my own drum… right off the edge of the cliff. :wink:

Right. Normally in Twine that functionality is best employed in Sugarcube in a header or footer passage that runs consistently. That’s where you’d basically check if a passage should be restricted from tangent linking either by passage name, or some whimsical tagging mechanism #noinventory or whatever.

So that might be the one thing Chapbook could potentially benefit from: an Every Turn passage to do these sorts of checks.

The Header and Footer in Chapbook are basically status lines that can change whenever you want them to and you wouldn’t want to run code in them.

Chapbook does a lot of stuff right and I appreciate that once I figure out what I’m doing, coding in it will be fast and efficient. I wrote the first section of my WIP in Sugarcube and was just exhausted by it - mainly because the sound capabilities gave me way too many opportunities to DJ with fading and crossfading tracks and that’s sort of a distraction!! :musical_score:

1 Like

SugarCube actually scared me off a bit because it looked so complicated. You’re braver than I am. I checked out AXMA a bit and it’s very cool. Looks like it has a lot of options, yet stays manageable. I can see why you champion it. It looks solid.

I was reading up more on Snowman and it lead me to understand more about Chapbook, if you can believe it. Chapbook is so exposed under it’s thin shell of basic commands that when I looked at Snowman, which has no shell whatsoever, I learned more about the basics of what is really happening. Anyway, Snowman talks about rendering passages and such and it dawned on me that the browser’s console can expose objects quite nicely… so I outputted the engine object to the console and a whole bunch of undocumented functions are right there for the picking. I’ve also looked at the built-in modifers and inserts to see how they tick.

Note: You might even be able to turn off the autosave if you look closely at the screenshot above.

I have no idea what I’m doing (lots of trial and error), but damn if I wasn’t able to render a passage directly in the header because of this:

[javascript]
let _passage = engine.story.passageNamed("Render Me");
config.header.center = engine.render(_passage.source);
[continue]

…there is a pretty big bug in Chapbook it seems though. It’s surrounding the -- part of passages where you declare variables above it. When using Chapbook’s own example code for extending things, it causes story breaking errors if -- is used. If you don’t extend Chapbook, -- code works just fine. For example, the passage that I injected into the center of the header with the code above will crash if any -- is present in the “Render Me” passage. Of course, -- is not required to set variables because it can be done in a [javascript] modifier all the same, but it’s unfortunate. I’m going to take the time to shed more light on this bug in the GitHub pages.

If this bug gets squashed, Chapbook can be opened up to the community for custom code solutions to help extend it. As it sits right now, when extending Chapbook, you have to use [javascript] to manipulate variables and do things that typically happen above the --. That’s not ideal, but it’s not a game stopper either.

(and @HanonO )
The following TWEE Notation based example demonstrates one method for adding business logic to a footer.
note: The Passage named Story JavaScript in the following represents the Story > JavaScript area of your project.

:: Story JavaScript
config.footer.center = "{embed passage: 'StoryFooter'}";

:: StoryFooter
[unless passage.name === 'Final']
The footer's content

:: Start
Welcome to the Test, see the footer?
[[Second]]

:: Second
The Second passage, there should be a footer,
[[Final]]

:: Final
The Final passage, and no footer.

Because Chapbook automatically loads its auto-save each time you test or play a project there are times when the order you implement things is very important. The following explanation is example of this issue…

1: You create a new project and add a reference to the config object to the first Passage…

config.footer.center: "My Footer"
--
Hello world

…and when you Test or Play that project everything is fine.

2: You decide to extend your project by adding a Custom Insert that displays a smiley face emoji, and because you want to use that insert through out your project you define it in its Story > JavaScript area…

engine.extend('1.0.0', () => {
    config.template.inserts = [{
        match: /^smiley face$/i,
        render: () => '😀'
    }, ...config.template.inserts];
});

3: And you update the first Passage to use the new insert…

config.footer.center: "My Footer"
--
Hello world
{smiley face}

…however when you use the Test or Play option this time you see the text {smiley face} instead of the expected emoji, and there is a “Cannot read properties of undefined” related error message in your web-browser’s Console.

4: But if you create another new project and reverse the order of two enhancements (define & test the Custom Insert before adding the config.footer.center reference and testing that) everything works as expected! So what is happening?

The loading of the auto-save seems to be done early in the engine start-up process. And after testing/playing step 1 that auto-save contains a config.footer.center variable, and the parent config object part of that variable reference doesn’t have any of the other properties normally expected on engine’s built-in config object.

note: This can be seen in the web-browser’s Console by typing config in it.

So when the Custom Insert defined in step 2 tries to access config.template.inserts it fails because the ‘global like’ config object no longer has a template property.

And the only way I’ve found to resolve that above error is to delete the relevant entry in my web-browser’s Local Storage. The key will start with chapbook-state-<project-name>

3 Likes

WHERE HAS CHAPBOOK BEEN ALL OUR LIVES? :slight_smile:

1 Like

@Greyelf

This is fantastic! Thanks so much for digging into this. This reveals so many possibilities.

I read a lot of the Cookbook stuff, but couldn’t digest it all at once. I’m going to have to revisit it again. It’s hard because the story formats are broken up so much that trying to get a clear reference for one story format is hard.

I really appreciate you challenging me like this. I’m learning so much right now!

(@HanonO )
I updated my previous post with information about why some of the Chapbook examples, like Adding Custom Inserts, don’t always work.

2 Likes