Advanced Chapbook Authoring

So much to unpack there, but thank you so much for going that extra mile. I’m going look into this further with what you’ve explained. Wow. Thanks!

Hey, I’m new here and am very interested in using Chapbook as I like it’s simplicity, even as an experienced coder. I read through this entire thread and I’m glad you guys made lots of discoveries as to its inner workings.

But there’s one thing I’m struggling with:
Rather than simply auto-loading in the user’s progress, I’d like to always go to a title screen passage on startup, where the user can either start a new game, or continue existing progress (if present).

Is there a way to make this happen?

I have a rough idea, but it’s probably not the best way:

  • On startup, regardless of if the user is returning or not, go to a title passage (How?)
  • That passage has a “New Game” link (simply restarts)
  • Or, if there’s a “lastPassage” variable present, also display a “Continue” link, which goes to that named passage.

It seems very simple in theory, but may work? I’m just not sure how to “force” go to a passage on startup even if the user is being “loaded in.”

For context, I do not plan on publishing my story on a website, but rather packaging the html (and associated images/sounds) into a standalone application.

Any help would be appreciated! Thanks!

First off, welcome! :slight_smile:

More meat for the grinder! I love it! :wink:

There are some internal variables that you can change and one of them might pertain to saving and loading. I’m still digging into Chapbook. However, you can try this and see if it suits your needs. This assumes your starting passage is called “Start”.

Story JavaScript:

if (trail.slice(-1) != 'Start') {
  trail.push('Start');
}

The trail variable will always contain an array of your visited passages so when the story first loads, if the last passage in the array is not the starting one, add the starting passage to the trail array. The condition prevents adding additional “Start” passages if the last one is already “Start” so that the continue button will work (see below).

Starting Passage:

[if trail.length > 1]
{back link, label:'CONTINUE'}
[continue]

In your starting passage, if the trail length is greater than 1, then show the a {back link}. A back link will always take you to the previous passage. The current one is “Start” (because of the JavaScript code) and the previous one will be where the user previously exited the game.

Note: This will not work if you have prior screens leading up to the starting passage though, so you may have to change the number of the trail.length to be checked or compare the trail some other way. Also, the Story JavaScript will have to change to check against those screens leading up before adding the starting passage to the trail. For example, you might have a credits screen or some sort of introduction before the main menu / title screen. Let me know if you need help dealing with that. Also, it may be prudent to rest the trail and all other variables manually after upon starting a new game a new game because the {restart} link clears all state variables, but it also just takes you to the starting passage again. It’s not impossible, but it does require a bit of testing and planning, but I believe the solution will be quite succinct when it’s implemented and complete.

To really see this in action, you can output {trail} in your passages and see the chain of passages grow and then get altered when you close the browser and open the game up again.

There might be another way, but this one works. I’ve tested it and I think I might use it for my story. :wink:


Edit: There are so many ways to tackle this. It might be easier to set a boolean Chapbook variable in the passage after the user starts the story. Then in the Story JavaScript, you could check to see if it exists and then add the Start passage to trail. Then your condition for the CONTINUE button would be reliant on this boolean variable too. That might make more sense to do given the uncertainty of what you may have before the Title / Main passage. Let us know if you require any clarification for getting this new method to work for you.

1 Like

Have you tested what happens if the web-page is refreshed by the web-browser for whatever reason?

eg. a mobile device’s Operating System changes application focus back to the web-browser; the end-user uses the web-browser’s “page refresh” key; etc…

Thank you for the detailed insight, it’s super helpful! I think using a separate boolean makes a bit more sense to me too.

I’ll play around with it and report back :slight_smile: And thank you for being so welcoming. A lot of other game development (ish) forums are full of gatekeeping.

No, but I did think about it afterwards. In fact, I’ve made Harlowe fail on remembering variables by doing so in some cases. Baby steps, but thank you for bringing it up because it really does need to be fool-proof. I’m the fool in the scenario, by the way. :wink:

Seriously though. I really appreciate you keeping tabs on this topic because there is so little out there on the subject. It’s nice to have someone with so much Twine experience. I feel like we’re taming the wild Chapbook frontier. Hopefully we don’t die of scurvy.


You’re welcome… again. :wink:

Definitely try things out for yourself. We do the most learning that way. I also thought about another scenario where the {restart} insert can be used to actually begin a new game the way you want it to work. You’d have to set the Twine starting passage to the passage just after the main menu (when you click to start a new game). Then, in the Story JavaScript, you change the last passage to that of where you really want the game to launch (credits, intro stuff, etc.) Then the main menu has “Begin” and “Continue” options being {restart} and {back link}'. {restart}` will reset all the variables and send you to the next passage properly then.


Regarding the refreshing of the browser window that Greyelf mentioned (and I have not had the time to check this out to see if it’s an issue, though I suspect it is), as long as we can determine when the Story JavaScript is loading from a new browser session versus a refresh/reload, we’ll be in business. Perhaps there is something that the browser itself can determine in this scenario? I’ll give it more thought, but it sounds like it might be possible… discussion with deprecated solution and link to modern methods.

Maybe there is a way with Chapbook variables somehow. Seems unlikely though. Greyelf is probably sitting on the solution until our caveman minds connect the dots. :wink:

Edit: Oh, maybe we can use a specific type of “flag” cookie… session cookies vs persistent cookies. Hmmm…

1 Like

I would basically just put a note on the title screen: This game will use your browser to save your progress. If you’d like to start from the beginning, please click the “Restart” link in the bottom right. And leave the default restart link in the footer (or move it where you like.) I think the normal expectation is players would like to start from where they left off, but they can manually restart as they like.

Screen Shot 2023-06-06 at 2.32.01 PM

It’s a little disconcerting if you’re compiling and testing that it will remember and start you mid-story, but that’s your own browser. Any new player is always going to start from the beginning since there’s nothing remembered in their cache.

Since there is no built in save system, restarting the player when they come back without their expectation might be a less-appealing option! I believe this is justified and possibly explained in the documentation - if a player is on mobile and switches away from the browser or tab for a while, some mobile browsers might clear history as a space/speed saving option and force them to restart even if they meant to continue.

I think the previous responses have explained a way to do this - it sounds like they’re testing the transcript length to push a “continue or restart?” passage to the top of the player’s history. That might actually be a decent option without having to somehow invent a separate save system.

2 Likes

Where others see sage words of caution… I see, “Oh, you wanna be like that, do ya?! I’ll show you! I’ll make it work if it kills me!” :wink:

There is a save system on the testing side. I do wonder if it can’t be “turned on” for the regular playing side of things. One day I might look into it, but for me personally… I’m like you, I like the way it works already. I’m trying to embrace the simplicity of Chapbook as much as possible. Less is more.

My desire for extending Chapbook is more about incorporating standalone widgets of sorts. Like I want to roll some dice on the passage that look like dice and just have that insert be one line of code and let the JavaScript do the rest. Another one I want to finish testing (seems like it’s working fine) is exposing the tags assigned in Twine to the passages in Chapbook. Things of that nature. I’ll definitely share whatever I figure out.

1 Like

Okay, I’ve either discovered something or found a potential bug?

I thought ambient sounds and sound effects were different since they’re set up differently. However, today I accidentally called one of my ambient sound names as a sound effect - and it started playing over the existing ambient sound simultaneously! And then it stopped when it finished like effects do instead of repeating like an ambient sound.

Does this mean any setup sound can be either? A repeating ambient sound or a one-off sound effect depending how it’s called? That’s very useful to know if any existing clip can be used either way.

The one potential thing - I called the second clip at .5 volume as a sound effect with the ambient sound at full. Did that make a difference?

1 Like

What am I doing wrong here? I can’t seem to wrap my head around conditional display. I’m not sure if my boolean is weird or I’m not grokking the way if/else if formatted… can you not add extra carriage returns?

I’m trying to vary the display based on a string the user types in from the previous passage.

correct: skipguess === 'candy show' || skipguess === 'CANDY SHOW' || skipguess === 'Candy Show'
--

[correct]

Correct!

> [[Continue|test]]

[else]

That is incorrect.

[continue]

{restart link, label: 'Back'}

I can see in the vars section that my text input is correctly recorded into the skipguess variable.

Removing || (or statements) limiting to just one string doesn’t help either.

EDIT: It works if I directly check the variable AND INCLUDE THE WORD ‘IF’, but I can’t seem to set a true/false new variable if “this || this || this” is true…

1 Like

Could you post what you’re trying to write? This works for me:

color: 'crimson'
colorIsRed: color === 'pink' || color === 'crimson' || color === 'rose'
--
Is {color} red? {colorIsRed}

And yes, a conditional modifier always has to start with if. You can’t write just the variable or condition.

2 Likes

Thanks, I think I got it sorted and working well enough. I’ve just experimented with so many systems and different flavors of Twine that I’m confusing formatting conventions between them.

I am curious though about my previous question: By accident I discovered I can call an audio clip set up as an ambient sound as a sound effect and have it behave like a sound effect: it plays simultaneously with the already-playing ambient sound and stops after one repetition.

This is actually quite useful to know because I can set up everything as ambient sound and then basically call it as ambient sound to repeat or a sound effect to play once. Is this an undocumented feature, a bug, or just the way web sound works? (When I used AXMA, ambient sound/“music” was just played from the file in the folder, but sound effects actually got written into the code (in a media passage as a giant block of gibberish) so they could play without pre-loading and work simultaneously with a playing clip.)

Is there any reason to set things up as a sound effect specifically? I remember the guide saying it needs to preload somehow, and how an author might need to preload(?) the same sound twice with two different names to play in rapid succession. (Sorry if this is fuzzy and I’m not explaining well…)

I know there’s that section where you can manipulate music on and off with truth-statements for better control, like multiple ambient sounds at once.

It’s not hurting anything if it’s consistent and is definitely an awesome feature.

1 Like

The only real difference in how sound effects and ambient sound are loaded is that sound effects get loaded with a preload="auto" attribute on their <audio> element. I’ll point to MDN docs for details, but the effect should be that sound effects get loaded when stories begin, vs. ambients begin loading when they first begin playing. But the preload attribute is just a hint to browsers, so it might disregard this.

The goal is that if you have a sound effect play when a passage appears, the preload makes it so that the effect plays right when the passage appears, instead of having a slight delay while the browser loads the asset. I imagined most ambient sounds will have a bigger file size, so preloading them wouldn’t be ideal.

The fact that you can play ambient sound as a sound effect wasn’t intentional, so I guess it could go away in a future version, but I don’t see any harm in it.

Hope this helps clarify things!

2 Likes