{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'}[Javascript]
engine.extend('2.0.0', () => {
engine.template.inserts.add({
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({
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.template.inserts.add({
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({
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({
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;
}
}
});
});
[Javascript]
engine.extend('2.0.0', () => {
engine.template.inserts.add({
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;
}
});
});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>{_showCollectdKeepInsert}</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}"
_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]]**_firstTimeExample: '{first time: "You shield your eyes, but can\'t see who\'s behind the light."}'
--
## One Of and First Time Inserts: Showing Text the First Time
The `{firstTimeInsert}` insert only shows
its text the first time the player visits the passage. Consider the code:
<pre>
The floodlight illuminating you is painfully bright. {_firstTimeExample}
</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.
</blockquote>
The second time, and every time after that, the player will see:
<blockquote>
The floodlight illuminating you is painfully bright.
</blockquote>
> [[Show `{firstTimeInsert}` 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.'}"
--
## Showing First Time in Action
This passage contains the code:
<pre>
The cave is low and dark. {_firstTimeInsertDemo}
</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.'}
</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.'}"
_oneOfInsertSummary: "{one of: ['choice 1', 'choice 2', 'choice 3'], order: 'order'}"
--
## One Of and First Time Inserts: Summary
`{_firstTimeInsertSummary}` to show something only the first time a passage is visited.
`{_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]]**