{embed passage: '_Add Capitalization Insert'}
{embed passage: '_Add Text Collection Modifier and Insert'}
{embed passage: '_Add Conditional Insert'}
{embed passage: '_Add One Of and First Time Inserts and Modifiers'}[Javascript]
engine.extend('2.0.0', () => {
engine.template.inserts.add({
name: "^ ",
description: "Print the contents of a variable, capitalizing it.",
match: /^\^\s+\S+$/i,
render(firstArg, props, invocation) {
const variableToShow = invocation?.slice(1).trimStart();
const valueToShow = engine.state.get(variableToShow);
if (!valueToShow) {
return `{${invocation}}`;
}
return valueToShow.slice(0, 1).toLocaleUpperCase() + valueToShow.slice(1);
}
});
});[Javascript]
engine.state.set('__collected', '');
engine.extend('2.0.0', () => {
engine.template.modifiers.add({
name: "collect",
description: "Collect the text in the modifier for later display.",
match: /^collect\b/i,
process(output, {state, invocation}) {
const modifier = invocation?.slice(7).trimStart().replace(/\s.*/, '').toLowerCase() || '';
if (config.testing && !(modifier === '' || modifier === 'new' || modifier === 'no-space' || modifier === 'new-paragraph')) {
console.warn(
`"collect" modifier can only be [collect], [collect new], [collect no-space], or [collect new-paragraph], but was called as [${invocation}]`
);
}
// Only perform collection if there's any text. That
// prevents weirdness from an [if ...; collect] block.
if (output.text != '') {
if (modifier === 'new') {
__collected = '';
}
else {
__collected = __collected.trimEnd();
if (modifier === 'new-paragraph') {
__collected += '\n\n';
}
else if (modifier !== 'no-space' && __collected !== '') {
__collected += ' ';
}
}
__collected += output.text.trimStart();
output.text = '';
}
}
});
});
[Javascript]
engine.extend('2.0.0', () => {
engine.template.inserts.add({
name: "show collected",
description: "Show all text collected in previous [collect] modifiers.",
match: /^show\s+collected/i,
render: (first_arg, props, invocation) => {
if (typeof(__collected) === undefined) {
if (config.testing) {
throw new Error('{show collected} called before any text was collected');
}
}
const output = __collected;
if (!props['keep']) {
__collected = '';
}
return output;
}
})
});[Javascript]
engine.extend('2.0.0', () => {
engine.template.inserts.add({
name: "if",
description: "Show text if the contents of a variable evaluates to true.",
match: /^if\s+[^:,]/i,
render: (first_arg, props, invocation) => {
const condition = invocation.split(':')[0].slice(3);
const value = new Function(`return ${condition};`)();
return ((value) ? first_arg : props['else']) || '';
}
});
});[Javascript]
engine.state.set('zOneOfTracking', {});
libfunc = {};
libfunc.randInt = (max) => {
return Math.floor(Math.random() * max); // Random int from 0 to max-1
}
libfunc.shuffleArray = (arr) => {
for (let i = arr.length - 1; i > 0; --i) {
const j = libfunc.randInt(i + 1);
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
libfunc.embarassinglySimpleHashCode = (s) => {
let hash = 0;
for (let i = 0; i < s.length; ++i) {
let chr = s.charCodeAt(i);
hash = (hash << 5) - hash + chr;
hash |= 0;
}
return hash;
}
libfunc.hashInvocation = (invocation) => {
//return passage.name + invocation;
return libfunc.embarassinglySimpleHashCode(passage.name + invocation).toString();
}
engine.extend('2.0.0', () => {
engine.template.inserts.add({
name: "first time",
description: "Show text only the first time the insert is viewed.",
match: /^first\s+time\b/i,
render: (to_print, props, invocation) => {
const hash = libfunc.hashInvocation(invocation);
if (zOneOfTracking[hash] === undefined) {
zOneOfTracking[hash] = true;
return to_print;
}
}
});
engine.template.modifiers.add({
name: "first time",
description: "Show modified text only the first time the passage is viewed.",
match: /^first\s+time\b/i,
process(output, {invocation, state}) {
const hash = libfunc.hashInvocation(invocation);
if (zOneOfTracking[hash] === undefined) {
zOneOfTracking[hash] = true;
} else {
output.text = '';
output.startsNewParagraph = false;
}
}
});
});
[Javascript]
engine.extend('2.0.0', () => {
engine.template.inserts.add({
name: "one of",
description: "Display varying text every time the player encounters this insert.",
match: /^one\s+of\b/i,
render(choices, props, invocation) {
const hash = libfunc.hashInvocation(invocation);
let prev = zOneOfTracking[hash];
let ret;
const order = (props.order || '').toLowerCase();
if (order === 'random' || order === '') {
let choice = prev;
while (choice == prev) {
choice = libfunc.randInt(choices.length);
}
zOneOfTracking[hash] = choice;
ret = choices[choice];
}
else if (order === 'pure random') {
ret = choices[libfunc.randInt(choices.length)];
}
else if (order === 'shuffled') {
if (prev === undefined || prev.length == 0) {
prev = libfunc.shuffleArray([...choices]);
}
ret = prev.pop();
zOneOfTracking[hash] = prev;
}
else if (order === 'stopping' || order === 'cycling') {
if (prev === undefined) {
prev = 0;
}
else {
prev++;
if (prev >= choices.length) {
prev = (order === 'stopping') ? choices.length - 1 : 0;
}
}
zOneOfTracking[hash] = prev;
ret = choices[prev];
}
else {
if (config.testing) {
throw new Error(
`The {one of} order was "${order}" but must be one of "random", "pure random", "shuffled", "stopping", or "cycling"`
);
}
ret = invocation;
}
return ret;
}
});
});config.style.page.theme.enableSwitching: false
showText: false
hasKey: false
applesPicked: 1
pronounNum: 1
--
{embed passage: '_Add All Text Manipulation'}
{embed passage: 'List of Extensions'}[cont]
## Sargent Chapbook Extensions Demo
Let's see the extensions in action!
> [[Text Collection]]
> [[Conditional Insert]]
> [[One Of and First Time Inserts->One Of Insert]]
> [[Capitalization]]_collectModifier: "[collect]"
_contModifier: "[cont]"
_showCollectedInsert: "{show collected}"
--
## Text Collection: Overview
The `[collect]` modifier collects text to be shown later or manipulated. All text that follows the modifier
is saved to be shown later using the insert <code>{_showCollectedInsert}</code>.
Let's try it out. Here's what the underlying code looks like:
<pre>
First, some regular text.
{_collectModifier}
If everything worked, _this_ text was collected!
{_contModifier}
Second, some more text.
Finally, let's show what we collected! {_showCollectedInsert}
</pre>
Here's what it looks like when Chapbook renders it.
<blockquote>
First, some regular text.
[collect]
If everything worked, _this_ text was collected!
[cont]
Second, some more text.
Finally, let's show what we collected! {show collected}
</blockquote>
That's all you need to know to use it, though we've got more advanced examples.
> [[Collecting more than one chunk of text->Collect Multiple Texts]]
> [[Resetting or modifying what's collected->Reset or Modify Text Collection]]
> [[Summary of all text collection capabilities->Text Collection Summary]]
> **[[Back to the list of extensions->List of Extensions]]**_collectModifier: "[collect]"
_collectNoSpaceModifier: "[collect no-space]"
_collectParagraphModifier: "[collect new-paragraph]"
_ifShowCollectModifier: "[if showText; collect]"
showText: !showText
--
## Text Collection: Multiple Texts
You can use `[collect]` more than once to keep adding to what you've collected. For this example, we'll
collect text by embedding multiple passages that have collects in them.
By default, `[collect]` puts a single space between each new collection. If you're using it to build, say, a
description where you need to skip that space, you can use `[collect no-space]`. If you want a new paragraph
instead, use `[collect new-paragraph]`.
We'll also show optional collection of text using an `[if]` modifier controlled by the `showText` variable.
_On this visit, `showText` is **{showText}**._
Here's the code in this passage:
<pre>
{embed passage: 'Collect 1'}
{embed passage: 'Collect 2'}
{embed passage: 'Collect 3'}
{show collected}
</pre>
The `Collect 1` passage contains:
<pre>
{_collectModifier}
The first embedded passage has a single section of text.
</pre>
The `Collect 2` passage contains:
<pre>
{_collectModifier}
The second embedded passage has an optional section controlled by an [if showText] modifier.
{_ifShowCollectModifier}
That modifier is true, so this optional section was collected.
{_collectModifier}
The second passage ends with this separately-collected sentence.
</pre>
The `Collect 3` passage contains:
<pre>
{_collectNoSpaceModifier}
The third embedded passage used the {_collectNoSpaceModifier} modifier, so there's no space between
this sentence and the previous one.
{_collectParagraphModifier}
This last sentence is in a new paragraph.
</pre>
Here's how Chapbook renders all of that.
<blockquote>
{embed passage: 'Collect 1'}
{embed passage: 'Collect 2'}
{embed passage: 'Collect 3'}
{show collected}
</blockquote>
> [[Show this again, only this time with{if showText: 'out'} the conditional collection->Collect Multiple Texts]]
> [[Resetting or modifying what's collected->Reset or Modify Text Collection]]
> [[Summary of all text collection capabilities->Text Collection Summary]]
> **[[Back to the list of extensions->List of Extensions]]**[collect]
The first embedded passage has a single section of text.[collect]
The second embedded passage has an optional section controlled by an `[if showText]` modifier.
[if showText; collect]
That modifier is true, so this optional section was collected.
[collect]
The second passage ends with this separately-collected sentence.[collect no-space]
The third embedded passage used the `[collect no-space]` modifier, so there's no space between
this sentence and the previous one.
[collect new-paragraph]
This last sentence is in a new paragraph._collectModifier: "[collect]"
_collectNewModifier: "[collect new]"
_contModifier: "[cont]"
_showCollectedKeepInsert: "{show collected, keep: true}"
_collectedVariableShow: "{__collected}"
__collected: ''
--
## Text Collection: Reset or Modify Text Collection
By default, `[collect]` adds to any text that's already been collected, and <code>{_showCollectedInsert}</code> empties the
collection so you can start over. You can change both of those behaviors.
`[collect new]` gets rid of all the previously-collected text and starts over.
<code>{_showCollectedKeepInsert}</code> shows the collected text without getting rid of it.
Finally, the collected text is stored in the Chapbook variable `__collected`. You can always work directly with that variable.
Let's try that out. Here's the code in this passage:
<pre>
At the start, `__collected` contains: {_collectedVariableShow}
{_collectModifier}
If this works, this text will be thrown away.
{_contModifier}
After the first collect, `__collected` contains: {_collectedVariableShow}
{_collectNewModifier}
This should be the only text that's collected.
{_contModifier}
After the second collect, `__collected` contains: {_collectedVariableShow}
Let's show the collection using the insert with `keep: true`: {_showCollectedKeepInsert}
After the insert with `keep: true`, `__collected` contains: {_collectedVariableShow}
</pre>
Here's how Chapbook renders it.
<blockquote>
At the start, <code>__collected</code> contains: {__collected}
[collect]
If this works, this text will be thrown away.
[cont]
After the first collect, `__collected` contains: {__collected}
[collect new]
This should be the only text that's collected.
[cont]
After the second collect, `__collected` contains: {__collected}
Let's show the collection using the insert with `keep: true`: {show collected, keep: true}
After the insert with `keep: true`, `__collected` contains: {__collected}
</blockquote>
> [[Collecting more than one chunk of text->Collect Multiple Texts]]
> [[Summary of all text collection capabilities->Text Collection Summary]]
> **[[Back to the list of extensions->List of Extensions]]**## Text Collection: Summary
### The Modifier
| Modifier | What it does |
| -------- | ------------ |
| `[collect]` | Collect the text that follows and **add it** to any already-collected text. |
| `[collect no-space]` | Collect the text that follows and add it to any already-collected text **with no space between them**. |
| `[collect new]` | Collect the text that follows, **replacing** any already-collected text. |
### The Insert
| Insert | What it does |
| ------ | ------------ |
| <code>{show collected}</code> | Show the collected text and **empty the collection**. |
| <code>{show collected, keep: true}</code> | Show the collected text and **keep the collection**. |
### Under the Hood
The collected text is kept in the `__collected` variable.
> [[Collecting more than one chunk of text->Collect Multiple Texts]]
> [[Resetting or modifying what's collected->Reset or Modify Text Collection]]
> **[[Back to the list of extensions->List of Extensions]]**_ifInsert1: "{if hasKey: 'You could try [[unlocking it]].'}"
_ifInsert2: "{if hasKey: 'You could try [[unlocking it]].', else: 'You need a key.'}"
_ifInsert3: "{applesPicked} apple{if (applesPicked > 1): 's'}"
hasKey: !hasKey
applesPicked: applesPicked + 1
applesPicked (applesPicked > 2): 1
--
## Conditional Insert
### Showing Text When a Condition is True
The conditional insert is like the `[if]` conditional display modifier, except it's an insert
that you can use in the middle of other text.
In this passage, the variable `hasKey` is {hasKey}. The code:
<pre>
The door is locked. {_ifInsert1}
</pre>
produces the following:
<blockquote>
The door is locked. {if hasKey: 'You could try [[unlocking it]].'}
</blockquote>
### Showing Different Text When a Condition is False
You can also provide text to show if the condition evaluates to false. The code:
<pre>
The door is locked. {_ifInsert2}
</pre>
produces the following:
<blockquote>
The door is locked. {if hasKey: 'You could try [[unlocking it]].', else: 'You need a key.'}
</blockquote>
### Adding Text Without a Space
Where the conditional insert is especially useful is in adding a snippet of text onto another word.
In the following example, the variable `applesPicked` is {applesPicked}. The code:
<pre>
You pick {_ifInsert3}.
</pre>
produces the following:
<blockquote>
You pick {applesPicked} apple{if (applesPicked > 1): 's'}.
</blockquote>
> [[Show this again, only this time with different variable values->Conditional Insert]]
> **[[Back to the list of extensions->List of Extensions]]**You unlocked the door!
> [[Return to the Conditional Insert example->Conditional Insert]]
> **[[Back to the list of extensions->List of Extensions]]**oneOfInsert: "{one of}"
firstTimeInsert: "{first time}"
firstTimeModifier: "[first time]"
_oneOfInsertHeadsAndTails: "{one of: ['heads', 'tails']}"
_oneOfInsertWithOptions: "{one of: ['Water splashes from the ceiling.', 'From ahead, a bat's squeak echoes off of the cave walls.', 'Your torch flickers.']}"
--
## One Of and First Time Inserts: Overview
The `{oneOfInsert}` insert lets you display varying text each time a player reaches
a passage. For example, say you wanted to show different flavor text every time a
player entered a cave.
<pre>
The cave is low and dark. {_oneOfInsertWithOptions}
</pre>
The first time the player visits the passage, it might print,
<blockquote>
The cave is low and dark. From ahead, a bat's squeak echoes off of the cave walls.
</blockquote>
The next time, it might print,
<blockquote>
The cave is low and dark. Water splashes from the ceiling.
</blockquote>
`{oneOfInsert}` takes an array of text items, called _choices_, and randomly shows one of
thoise choices. The next time, it shows a different choice. While each choice is randomly
selected, the same text won't be shown twice in a row. That is, the insert
`{_oneOfInsertHeadsAndTails}` will never show `heads` and then `heads` again.
You can specify different orders to show the choices in, including shuffling the choices or
showing them in order until they run out. There's also a special case of showing choices:
showing text only the first time the player visits the passage.
> [[Controlling the order of what's shown->One Of Insert Ordering]]
> [[Showing text the first time only->First Time Insert]]
> [[Summary of all `{oneOfInsert}` insert options->One Of Insert Summary]]
> **[[Back to the list of extensions->List of Extensions]]**_oneOfInsertSummary: "{one of: ['choice 1', 'choice 2', 'choice 3'], order: 'order'}"
--
## One Of and First Time Inserts: Controlling the Order
You can control the order that the `{oneOfInsert}` insert shows its choices using the
`order` parameter:
<pre>
{_oneOfInsertSummary}
</pre>
`order` can be any of the following:
- `'random'`: Show a random choice each visit without showing the same thing twice in a row. The default.
- `'pure random'`: Like `random`, but choices can be shown twice in a row.
- `'shuffled'`: The choices are shuffled, then shown in that shuffled order. Once all of them have been shown, they're re-shuffled.
- `'stopping'`: Show the choices in order, stopping with the last one and thereafter showing it forever.
- `'cycling'`: Show the choices in order, repeating the order after the last one's shown.
> [[Show `{oneOfInsert}` ordering in action->One Of Demo]]
> [[Showing text the first time only->First Time Insert]]
> [[Summary of all `{oneOfInsert}` insert options->One Of Insert Summary]]
> **[[Back to the list of extensions->List of Extensions]]**_arrayOfChoices: ['apple', 'banana', 'chainsaw', 'durian']
_oneOfDemoFirstPart: "{one of:"
_rbrace: "}"
--
## Showing One Of Ordering in Action
Here's what happens if we take the choices `{_arrayOfChoices}` and show them
in different orders.
<blockquote>
- Random: {one of: ['apple', 'banana', 'chainsaw', 'durian'], order: 'random'}
- Pure random: {one of: ['apple', 'banana', 'chainsaw', 'durian'], order: 'pure random'}
- Shuffled: {one of: ['apple', 'banana', 'chainsaw', 'durian'], order: 'shuffled'}
- Stopping: {one of: ['apple', 'banana', 'chainsaw', 'durian'], order: 'stopping'}
- Cycling: {one of: ['apple', 'banana', 'chainsaw', 'durian'], order: 'cycling'}
</blockquote>
> [[View this passage again->One Of Demo]]
> [[Showing text the first time only->First Time Insert]]
> [[Summary of all `{oneOfInsert}` insert options->One Of Insert Summary]]
> **[[Back to the list of extensions->List of Extensions]]**_firstTimeInsertExample: '{first time: "You shield your eyes, but can\'t see who\'s behind the light."}'
_firstTimeModifierExample: "[first time]\nIf you close your eyes, it almost feels like you've been here before."
--
## One Of and First Time Inserts: Showing Text the First Time
The `{firstTimeInsert}` insert only shows its snippet of text the first time the player visits the passage. The `{firstTimeModifier}` modifier does the same, but for paragraph-sized sections of text. Consider the code:
<pre>
The floodlight illuminating you is painfully bright. {_firstTimeInsertExample}
{_firstTimeModifierExample}
</pre>
The first time the player visits the passage, they'll see:
<blockquote>
The floodlight illuminating you is painfully bright. You shield your eyes, but can't
see who's behind the light.
If you close your eyes, it almost feels like you've been here before.
</blockquote>
The second time, and every time after that, the player will see:
<blockquote>
The floodlight illuminating you is painfully bright.
</blockquote>
> [[Show `{firstTimeInsert}` insert and `{firstTimeModifier}` modifier in action->First Time Demo]]
> [[Controlling the order of what's shown->One Of Insert Ordering]]
> [[Summary of all `{oneOfInsert}` insert options->One Of Insert Summary]]
> **[[Back to the list of extensions->List of Extensions]]**_firstTimeInsertDemo: "{first time: 'You catch your foot on a stalagmite and nearly fall.'}"
_firstTimeModifierDemo: "[first time]\nYou have no memory of this place."
--
## Showing First Time in Action
This passage contains the code:
<pre>
The cave is low and dark. {_firstTimeInsertDemo}
{_firstTimeModifierDemo}
</pre>
Here's what it looks like.
<blockquote>
The cave is low and dark. {first time: 'You catch your foot on a stalagmite and nearly fall.'}
[first time]
You have no memory of this place.
[cont]
</blockquote>
> [[View this passage again->First Time Demo]]
> [[Controlling the order of what's shown->One Of Insert Ordering]]
> [[Summary of all `{oneOfInsert}` insert options->One Of Insert Summary]]
> **[[Back to the list of extensions->List of Extensions]]**_firstTimeInsertSummary: "{first time: 'This will only show up on the first visit.'}"
_firstTimeModifierSummary: "[first time]\nThis also will only show up on the first visit."
_oneOfInsertSummary: "{one of: ['choice 1', 'choice 2', 'choice 3'], order: 'order'}"
--
## One Of and First Time Inserts and Modifiers: Summary
`{_firstTimeInsertSummary}` to show a short bit of text only the first time a passage is visited.
To show a longer bit of text only the first time a passage is visited:
```
{_firstTimeModifierSummary}
```
`{_oneOfInsertSummary}` to show one of the array of choices.
| `order` | What It Does |
| -------- | ------------ |
| `'random'` | (default) Show a random choice each visit, but no choice will be shown twice in a row. |
| `'pure random'` | Show a random choice each visit. |
| `'shuffled'` | Shuffle the choice the first time and then show one at a time in that shuffled order. When all choice have been shown, they're re-shuffled. |
| `'stopping'` | Show the choices in order. After the last one's shown, it's shown again forever after. |
| `'cycling'` | Show the choices in order. After the last one's shown, the sequence starts over with the first. |
> [[Controlling the order of what's shown->One Of Insert Ordering]]
> [[Showing text the first time only->First Time Insert]]
> **[[Back to the list of extensions->List of Extensions]]**_capitalizationInsert: "{^ [variable]}"
_capHisInsert: "{^ companion.his}"
_hisInsert: "{companion.his}"
pronounNum: pronounNum + 1
pronounNum (pronounNum > 3): 1
companion.his (pronounNum == 1): "his"
companion.his (pronounNum == 2): "her"
companion.his (pronounNum == 3): "their"
--
## Capitalization
The capitalization insert `{_capitalizationInsert}` capitalizes
and displays the variable's content.
In this passage, the variable `companion.his` is `{companion.his}`. The code:
<pre>
{_capHisInsert} clothes are folded neatly at the foot of {_hisInsert} bed.
</pre>
produces the following:
<blockquote>
{^ companion.his} clothes are folded neatly at the foot of {companion.his} bed.
</blockquote>
> [[Show this again, only this time with a different choice of pronoun->Capitalization]]
> **[[Back to the list of extensions->List of Extensions]]**