What is the best way to modify the value of a variable after the player clicks the link but before the next passage evaluates that var? (Chapbook)

Im using Chapbook, and i want to achieve this: i have a passage with two links, [[this]] and [[that]]. Both of them link to the same passage, but i want the text of this passage to be a little different according to the way the player got there. What is the best way to modify the value of a variable after the player clicks but before the next passage shows? In the next passage the values of the variables should be evaluated.

As explained in The Vars Section documentation, variables can only be assigned a value in the Var section of a Passage.

However that Var section can belong to either the Passage being visited, or to a ā€˜childā€™ Passage that is being embedded (using {embed}) within the visited Passage.
eg. the following TWEE Notation based example demonstrates such variable updating via embeddingā€¦

:: Start
counter: 1
--
before embedding: Counter: { counter }  (displays 1)

{embed passage: 'Increase Counter'}
after embedding: Counter: { counter }  (displays 2)

:: Increase Counter
counter: counter + 1
--

Some story formats support a concept known as a ā€œLink with Setterā€ (aka a ā€œSetter Linkā€), which is a link that assigns values to variables before preforming a Passage Transitionā€¦

/* SugarCube based examples... */
[[Link Text|Target Passage][$variable to "value"]]

<<link 'Link Text' 'Target Passage'>><<set $variable to "value">><</link>>

/* Harlowe based example. */
(link-reveal-goto: 'Link Text', 'Target Passage')[(set: $variable to "value")]

ā€¦but unfortunately Chapbookā€™s Link Inserts currently donā€™t support such functionality, so you would need to create your own custom insert.

1 Like

Thank you Grey!
One more question. If i made my own insert, wouldnā€™t i loose the visual representation of links in Twineā€™s visual editor?
In case i remain in Chapobook, i was thinking about linking each choice to a different ghost passage that modifies, each in itā€™s own way, the values of the variables but embeds the same real passage. Did you mean something like that?

First problem of my approach. If 5 different passages lead to the same passage in different ways, and i want to modify the value of different variables acording to the choice made, i should add 5 ghost passagesā€¦
Chapbook suddenly looses its appeal. Its chapbook or Twineā€™s Visual Editor.
Another chapbook issue: no tag expositionā€¦

Yes. And the same is true if you use any of the Link Inserts.

But, the Twine 2.x applicationā€™s ā€œfind all links within a Passageā€ feature doesnā€™t understand what a HTML Comment is, so it doesnā€™t exclude a commentā€™s content like it should when it is searching through a Passage. On the other hand Story Format runtimes do know what a HTML Comment is, so it handles them correctly when process a Passage.

So you can use this misunderstanding to your advantage while using the Twine 2.x application, by using HTML Comments to trick the ā€œfind all links within a Passageā€ feature.

eg. the ā€œfind all links within a Passageā€ feature will ignore the following Link Insertā€¦

{link to: 'Target Passage', label: 'Label Text'}

ā€¦so it wonā€™t create a ā€˜Target Passageā€™ if it is missing, or a Connection Arrow to it if it already exists. But if you add a HTML Comment like soā€¦

<!-- [[Label Text|Target Passage]] -->
{link to: 'Target Passage', label: 'Label Text'}

ā€¦then the ā€œfind all links within a Passageā€ feature will do both things, even though it shouldnā€™t :slight_smile:

3 Likes

You have definitely escaped the Matrix, Grey!

I have found that if you create a JavaScript tag inside a passage, and inside that tag you code a link using the function ā€œwriteā€, the linked passage is automatically created and/or linked in Twineā€™s visual editor:

[JavaScript]
write ("[[Go left]]");
[continue]

It automatically creates a passage named ā€œGo leftā€ (if it yet didnā€™t existed) and adds a visual connection to it.

For the same reason that having Link Markup in a comment does.

eg. The ā€œfind all links within a Passageā€ feature doesnā€™t understand what JavaScript code is either, so it just sees the [[Go left]] and decides that it looks like a standard Markup based link.

Using a String variable literal that contains mark-up link like characters will also trick the featureā€¦

/* Chapbook */
links: '[[Apple]] [[Banana]]'
--

/* Harlowe */
(set: _links to '[[Apple]] [[Banana]]')

/* SugarCube */
<<set _links to '[[Apple]] [[Banana]]'>>
1 Like

Here is a custom insert that does what you need

engine.extend('1.0.0', () => {

	engine.event.prependListener('dom-click', el => {
		let vars    = el.dataset.cbSetVars;
		if (vars) {
		  let result = engine.render(vars.replace(';',"\n")+"\n--");
		}
	});

	config.template.inserts = [{
		match: /^set\s+link/i,
		render(passage, props) {
		  if (passage) {
			return `<a href='javascript:void(0)'
					   data-cb-go="${passage}"
					   data-cb-set-vars="${props.set}">${props.label}</a>`;
		  }
		}
	}, ...config.template.inserts];
});

You can use it like a link {set link: 'Another passage', label: 'A link', set: 'foo: 2'}. The code in set is like the vars section, except that you can indicate multiple lines with a ; instead of a line break.

e.g.
{set link: 'Another passage', label: 'A link', set: 'foo: 2;bar: 4;name "hello"'}

3 Likes

This will expose the Twine passage tags in Chapbook:

window.passageTags = {}; // <== object format { passage name : tags }
let _storyData = document.getElementsByTagName( 'tw-storydata' )[0];
for ( let _child = _storyData.firstChild; _child !== null; _child = _child.nextSibling ) {
  if ( _child.tagName.toLowerCase() == 'tw-passagedata' ) {
    passageTags[ _child.getAttribute( 'name' ) ] = _child.getAttribute( 'tags' );
  }
}
window.getTags = function ( _passageName ) {
  return passageTags[ _passageName ].toLowerCase();
}

It basically grabs the Twine story data and stores the tags and then you can then do whatever you want with that data. For example, I could run:

if ( getTags( passage.name ).includes( "specific-tag-name" ) ) {
  // ==> do something if "specific-tag-name" exists in the current passage
}

You can also put that logic into a custom insert if need be.

1 Like

If I could offer alternative getTags() functions that both leverages Chapbookā€™s exported APIs and returns an unmodified array of tags.

Included are two versions (use only one). The code goes into the storyā€™s JavaScript section.

Throws an error if the specified passage is nonexistent.

CODE:

/**
 * getTags(name) - Passage tags retrieval function.
 *
 * @param {string} name - Passage name.
 * @returns {Array<string>} Passage tags.
 * @throws {Error} Passage must exist.
 */
function getTags(name) {
	const passage = engine.story.passageNamed(name);

	if (!passage) {
		throw new Error(`There is no passage named "${name}".`);
	}

	return passage.tags;
}
Returns an empty array if the specified passage is nonexistent.

CODE:

/**
 * getTags(name) - Passage tags retrieval function.
 *
 * @param {string} name - Passage name.
 * @returns {Array<string>} Passage tags.
 */
function getTags(name) {
	const passage = engine.story.passageNamed(name);
	return passage ? passage.tags : [];
}

USAGE:

if (getTags('some passage').includes('some-tag')) {
	// The tag `some-tag` exists on the specified passage.
}
2 Likes

With thanks to @TheMadExile and @klembot, Iā€™ve re-written my suggested insert above to be more elegant (and more performant)

2 Likes

Definitely, Chapbook has the best community and the most commited developers! Thanks to you all!

2 Likes

And Iā€™ve scrapped my tag code altogether. :wink:

I didnā€™t think to do a console.log() of the passage object. Iā€™m such a dummy, but I still had fun figuring it out the hard way.


@TheMadExile You wouldnā€™t happen to have a super secret, rough draft of the API document kicking around, would you?


We are the best! All 5 or 6 of us! High fives all around! :wink:

1 Like

This code doesnā€™t seem to work:
{set link: 'Another passage', label: 'A link', set: 'foo: 2;bar: 4;name "hello"'}

This is what is rendered in the browser:

The straight quotation marks are converted into curly quotation marks, and the returned value is rendered as a string, not as a link.
Its really messy to mix different types of quotation marks.

But if you set the value of the variable ā€œnameā€ outside the insert (fin the vars section, for example) the code works flawlessly.

1 Like

Youā€™re missing a colon after name, I believe. Though that probably has nothing to do with the curly quotes rendering problem. I only mention it, just in case.

By the way, Iā€™m learning so much about Chapbook from your questions and the answers from others. Thanks for that.

1 Like

You are right, even with the colon it renders improperly:

1 Like

#METOO :slightly_smiling_face:

1 Like

No, just the code in its GitHub repo. Itā€™s ā€¦/extensibility.js file shows what it exports via the engine API for user use.

1 Like

Changing out all of the typographic (curly) quotes, both single and double, for regular quotes will probably fix the problems youā€™re having. Even if you do have other issues to work out, you still need to do that as typographic quotes are not valid syntax.